# gy — good,yes
English | [日本語](https://github.com/aq2bq/gy/blob/main/README.ja.md)
A Rust CLI that manages decisions, questions, needs, requirements, acceptance criteria, and continuation gates as a graph of Markdown files. AI agents operate through the CLI or MCP; people read the ledger generated by `render` and individual records with `show`.
`lint` checks the graph's internal consistency. Users verify whether the ledger matches the code, GitHub, and production. gy does not call the GitHub API.
The initial command set, including requirement compression, is implemented.
## Installation and first records
Install from crates.io:
```sh
cargo install gy --locked
gy init demo --parent-issue 6000
gy criterion add "Retries must not cause duplicate deliveries" --scope demo
gy need add "Make retries safe" --targets AC-1 --scope demo
gy question add "How should deliveries be ordered?" \
--decider master --options "Publication time" --options "Arrival time" --scope demo
gy decide "Order deliveries by publication time" \
--closes Q-1 --scope-note "Applies to production workers; excludes batch replays" --scope demo
gy lint
gy render
```
Initialization creates `.gy-dir`, `docs/ledger/gy.toml`, and the scope directories, and appends a ledger reference to an existing `AGENTS.md`. Commands can discover the ledger from subdirectories. Reads cover all scopes by default. To create a node, run inside its scope directory or supply `--scope`. Commands that update an existing ID write to that node's scope.
To install from a source checkout, run `cargo install --path crates/gy --locked`.
## Commands
| Command | Result |
| --- | --- |
| `init <scope>` | Create a ledger and scope |
| `need add` / `need file` | Create a need tied to acceptance criteria and associate it with a requirement |
| `question add` / `question close` | Search across scopes before registering a question; close it in one of three ways |
| `q "one sentence"` | Capture a quick human note; lint fails until the required information is supplied |
| `decide` | Record a decision with its applicability conditions and close specified questions |
| `link <source> <label> <target>` | Record a relationship in both nodes' frontmatter |
| `req add` / `req advance` | Register requirements and record state transitions with evidence |
| `req compress <issue>` | Check that constraints have become decisions, then compress an archived record into six items |
| `criterion add` / `criterion satisfy` | Create acceptance criteria and record evidence of satisfaction |
| `gate add` | Create a gate for deciding whether an approach should continue |
| `node set` | Update arbitrary attributes or the Markdown body |
| `find` / `show` | Search attributes and text; display nodes and their relationships |
| `next` | List needs whose prerequisites have been resolved |
| `lint` / `handover` | Check consistency and the records needed for a handover |
| `stats` | Report satisfied acceptance criteria and question arrival rates from git history |
| `render` | Generate paginated Markdown or DOT |
| `import <directory>` | Import ADRs while preserving their IDs |
| `cheatsheet` / `completions <shell>` | Print a workflow reference or shell completions |
| `skills install <dir>` / `mcp serve` | Install agent instructions or start the MCP server |
All commands accept `--json`, `--quiet`, `--verbose`, `-C <dir>`, and `--scope`. With `--json`, results go to standard output and diagnostics go to standard error as JSON. `--quiet` suppresses ordinary result output while retaining JSON results and errors.
Exit codes are 0 for success, 1 for a failed check, 2 for missing or invalid input, nonexistent references, or guard violations, and 3 for ledger parsing failures or corruption. `handover` also returns 1 when next-transition evidence, responsibility, or referenced nodes are missing.
## Attributes and relationships
The five required frontmatter attributes are `id`, `type`, `title`, `scope`, and `created`. Unknown attributes survive reads and writes. Set values with `node set --set key=value`; values that parse as JSON are stored as arrays, numbers, booleans, or objects.
```sh
gy node set N-1 --set 'waiting-on=["Q-2"]' --set 'custom={"region":"east"}'
gy find --where custom.region=east
gy find delivery --where type=decision --where 'created>=2026-09-01'
gy node set D-1 --body-file decision-body.md
```
Search operators are `=`, `!=`, `>=`, `<=`, `>`, `<`, and `~` (substring matching). Numeric values are compared numerically; other values are compared as strings. ISO-format dates sort chronologically as strings. Results identify matching sections, including Context, Decision, and applicability conditions.
| Attribute | Meaning |
| --- | --- |
| `decision_scope` | Conditions under which a decision applies; supplied through `--scope-note` at creation |
| `decider` / `options` | The decision-maker and an array of distinct options for a question |
| `bundle` / `bundle-rationale` | A question bundle and why the same intervention resolves its questions |
| `waiting-on` / `unresolved` | Arrays of IDs referenced as unresolved |
| `belongs-to` | A question's owning node IDs; checked together with `raised-by` for multiple owners |
| `bearer_count` | The stated number of needs supporting an acceptance criterion; compared with actual `targets` links |
| `parent_issue` | A confirmed parent Issue number; compared with the configured value |
| `status` | One of 11 requirement states, or `open` / `closed` for questions |
| `pr_url` / `pr_base` / `pr_files` | The PR URL, base branch, and changed-file count recorded for a requirement |
| `remaining_work` | A legacy remaining-work count or array; checked for consistency with `residual` when present |
| `summary` / `contracts_changed` | A one-sentence outcome and free-form text or a list of changed contracts |
| `artifacts` | `pr`, `merge_commit`, and `base_branch`: pointers to deliverables |
| `production` | An object containing production measurements and verification results, or `none` if there was no production work |
| `deviations` / `residual` | Design deviations and additional decisions; destinations of transferred work. Explicitly use `none` when absent |
| `compressed` / `compressed_from` | Compression date and archive Issue comment URL; not counted among the six items |
| `next_evidence` / `responsible` | Evidence for the next transition and the responsible party for an active requirement |
| `constraints` | An array of downstream constraints, each with `text` and `decision` |
| `constraints_reviewed` | A record that each constraint has been reviewed |
| `satisfied` / `satisfied_at` | Acceptance criterion satisfaction and its recorded timestamp |
The CLI, generated views, and bundled agent instructions use English. Requirement states use the values listed below; `none` explicitly records the absence of production work, deviations, or residual work.
Relationships have the following directions and inverse attribute names. `link` writes both sides; `lint` checks symmetry and node types.
| Direction | Label | Inverse attribute |
| --- | --- | --- |
| question → decision | `closes` | `closes` |
| decision → decision | `narrows` / `widens` / `supersedes` / `completes` | `narrowed-by` / `widened-by` / `superseded-by` / `completed-by` |
| need / requirement → criterion | `targets` | `targeted-by` |
| need → decision | `spawned-by` | `spawns` |
| need → requirement | `filed-as` | `filed-from` |
| need → need | `depends-on` | `needed-by` |
| requirement → decision | `relies-on` | `relied-on-by` |
| requirement → question | `raised` | `raised-by` |
| gate → question | `measured-by` | `measures` |
For `narrows` and `supersedes`, use `--mark` to identify the affected passage in the older decision. The stored body is unchanged; `show` and `render` annotate it at display time. If the passage cannot be found, it is displayed separately, so a marker cannot silently move to an unrelated passage.
## Requirement state transitions
Use `req advance --evidence` to record what was checked. gy enforces the following 11 states and the main guards, rather than a complete transition table:
```text
unfiled / defining / awaiting-design / awaiting-approval / awaiting-implementation /
awaiting-audit / awaiting-pr / awaiting-merge / awaiting-production / awaiting-cleanup / complete
```
`defining` means the requirement is being defined. Optional context can follow a state in parentheses, such as `awaiting-implementation (phase 2)`. Ledgers created with the earlier Japanese values must have those state values and the explicit absence value updated before use; gy does not migrate them automatically.
```sh
gy req add "Retry control" --issue 6006 --parent-issue 6000 --scope demo
gy need file N-1 --issue 6006
gy node set '#6006' --set pr_url=https://github.com/org/repo/pull/6007 \
--set pr_base=main --set pr_files=3
gy req advance 6006 --to awaiting-merge --evidence "Record of PR diff review" \
--reported-base main --reported-files 3
```
To enter `awaiting-merge`, the user supplies confirmed values through `--reported-base` and `--reported-files`; gy compares them with frontmatter. It does not query the PR or its diff.
Transitions to `awaiting-production` (awaiting production work), `awaiting-cleanup` (awaiting cleanup), or `complete` (complete) require `--data-migration true|false` and `--production-only true|false`. If migration or production-only verification is needed and `production_done` is not recorded, the state is awaiting production work. If production work is unnecessary or complete, but cleanup is unconfirmed or work remains, the state is awaiting cleanup. Completion requires `--cleanup-done true` and no unassigned remaining work.
Completed requirements must explicitly record `deviations` and `residual`. Set `residual` to `none` or to existing destination IDs of the form `N-xx`, `Q-xx`, or `#Issue`. If `remaining_work` is still present, it must be zero or an empty array.
```sh
gy node set '#6006' --set remaining_work=0 --set deviations=none --set residual=none
gy req advance 6006 --to complete --evidence "Reviewed the remaining-work list" \
--data-migration false --production-only false --cleanup-done true
```
Before archiving constraints, associate every `constraints` entry with a decision that states its applicability conditions, and add a `relies-on` link. `constraints_reviewed=true` records the user's review. gy cannot determine whether constraints have been omitted from the record.
## Compressing completed requirements
First associate each downstream constraint with a decision, then record the six retained items in frontmatter. `summary` describes the outcome in one sentence on one line, separately from the requirement title. `contracts_changed` is free-form; gy does not normalize it against project-specific quality-gate tables.
```sh
gy node set '#6006' \
--set 'summary=Prevented duplicate order lines with a unique constraint.' \
--set 'contracts_changed=["orders.order_lines (added UNIQUE constraint)","POST /api/v1/orders (added 409 response)"]' \
--set 'artifacts={"pr":"https://github.com/org/repo/pull/6007","merge_commit":"a1b2c3d","base_branch":"main"}' \
--set 'production={"migration_total":12431,"migration_updated":87,"migration_remaining":0,"verified_env":"production","verified_at":"2026-09-09"}' \
--set deviations=none --set residual=none
gy node set '#6006' \
--set 'constraints=[{"text":"Prevent duplicate order lines","decision":"D-1"}]' \
--set constraints_reviewed=true
gy link '#6006' relies-on D-1
gy req compress 6006 > archive.md
```
The user posts the full contents of `archive.md` as a comment on the corresponding Issue, then supplies that comment's URL. gy does not fetch the archive, so the user must check that the full record was archived. If the original requirement changes after archiving, archive its latest contents again.
```sh
gy req compress 6006 \
--evidence 'https://github.com/org/repo/issues/6006#issuecomment-123'
gy find --where 'contracts_changed~orders.order_lines'
```
Without `--evidence`, the command checks the record and outputs the original file in full without changing it. With `--evidence`, it still returns the original text to standard output and replaces the body with six items. JSON output contains the full text in `archive` and the compressed record in `node`. The update rechecks the constraints and all six items.
Compression retains the ID, required attributes including `created`, both sides of graph edges, and unknown attributes. The example retains the existing requirement ID spelling, `#6006`. The six items remain searchable attributes, and `show` / `render` generate the body from them. Relationships such as acceptance criteria are not duplicated in the body. Direct `targets` links from requirements also retain their inverse links; L2 counts only needs as supporting an acceptance criterion.
Compression removes these known transient attributes: `constraints`, `constraints_reviewed`, `remaining_work`, `transitions`, `next_evidence`, `responsible`, `pr_url`, `pr_base`, `pr_files`, `data_migration`, `production_only`, `production_done`, `cleanup_done`, `evidence`, `quality_gates`, `design_proposal`, and `audit_records`. They remain in the archive along with the original body. Other extension attributes are preserved. A compressed record cannot be compressed again to overwrite its original archive pointer.
For transferred work, use a value such as `residual=[{"id":"N-2","note":"Transferred performance improvements"},"Q-3","#6010"]`. Each destination must be an existing node. Blank values, null, and empty arrays are distinct from the explicit `none` and are reported by L11.
## Configuring lint and render
```toml
# docs/ledger/gy.toml
parent_issue = 6000
[scopes.demo]
parent_issue = 6000
[lint]
L1 = "error"
L6 = "warn"
L2 = { enabled = true, severity = "error" }
# Use false or "off" to disable an individual rule.
[render]
output = "{scope}/README.md"
split_threshold = 100
```
| Rule | Check |
| --- | --- |
| L1 | Closed questions still referenced as unresolved |
| L2 | Stated bearer counts differ from actual supporting needs |
| L3 | Needs without acceptance criteria |
| L4 | Questions with multiple owners |
| L5 | Requirements relying on superseded decisions |
| L6 | Missing marks for narrowed or superseded passages |
| L7 | Decisions without applicability conditions |
| L8 | Questions without a decision-maker |
| L9 | Questions with fewer than two distinct options |
| L10 | Requirement states outside the allowed set |
| L11 | Missing or inconsistent completion and compression records |
| L12 | Question bundles without a rationale |
| L13 | References to nonexistent nodes |
L1 also checks body lines containing a question ID and `undecided`, `unresolved`, or `waiting`. L2 can read a body count written as `bearers 3`; prefer the structured `bearer_count` attribute.
L6 defaults to `warn`; the others default to `error`. The additional `edges` rule checks inverse links, edge types, and matching marks. Incomplete questions created with `q` report missing information as errors regardless of the L8/L9 settings.
`render` splits each scope into pages of the configured node count and creates a README index when multiple pages are needed. Output paths must be relative to the ledger directory and cannot point into the directories containing source nodes.
`stats --days 7` reports new question counts, daily rates, and changes for the most recent seven days and the preceding seven days. It counts the first addition of each ID across all git refs; body edits are not new arrivals. Uncommitted questions are excluded. If the previous period had no arrivals, the decay fraction is null. Record acceptance criterion satisfaction with `criterion satisfy --evidence`.
## MCP and bundled skills
```sh
gy skills install .agents/skills
gy mcp serve
```
MCP exchanges one JSON-RPC message per line over standard input and output. Configure the client to run `gy` with arguments `mcp serve -C /absolute/project/path`. The server exposes 18 tools, including `gy_find`, `gy_show`, `gy_question`, and `gy_decide`. Each tool accepts an `args` array containing arguments after its corresponding CLI command. For example, `gy_find` accepts `{"args":["delivery","--where","type=decision"]}`.
The three bundled skills are `gy-ledger`, `gy-question`, and `gy-decide`. If a destination skill has been edited, installation refuses to overwrite it and requests a different destination.
## Development and distribution checks
```sh
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --locked
cargo package --workspace --allow-dirty
cargo install --path crates/gy --locked --root target/install-check
```
`gy-core` provides storage, operations, validation, and views; `gy` provides the clap CLI and MCP interface. Both reads and writes acquire a ledger-wide file lock. Multi-file updates are journaled before replacing individual files, and an interrupted update is completed on the next startup. Commit `.gy-ids.json` with the ledger: it records allocated numbers to prevent reuse after deletion. Do not commit `.gy.lock`.
CI is configured to test and install on macOS, Linux, and Windows. The pre-commit hook entry is in [.pre-commit-hooks.yaml](.pre-commit-hooks.yaml).