Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions cli/audit.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -496,18 +496,24 @@ The decision surface has exactly three shippable categories:
|:---------|:--------|
| `coupling-boundary` | A new cross-zone dependency edge. |
| `public-api-contract` | A new exported public-API surface, or a changed contract consumed by modules outside this diff. |
| `dependency` | A new third-party dependency. |
| `dependency` | A new third-party dependency, or a declared one moved across a major version (or a `0.x` minor). One batched decision per manifest per kind, weighted by the graph's in-repo importers of the affected packages. |

Minor and patch bumps and non-numeric ranges (workspace, file, git, tags) are never dependency candidates. The importer count is a union over the batched packages (a module importing two bumped packages counts once). Entries outside `dependencies` are tagged in the question (`dev`, `optional`, `peer`); a package listed in several sections keeps its `dependencies` range and anchor line. A `dependency` decision anchors on `package.json`, which cannot carry a comment, so it has no `suppress` action; `ask-expert` still applies. Only `coupling-boundary` and `public-api-contract` decisions are suppressible.

Each decision is a framed judgment question anchored to a `signal_id` fallow deterministically derived from the graph, ranked by consequence (blast radius x reversibility), capped to [`--max-decisions`](#options) (default 4, clamped to a 3 to 5 band), and paired with the routed expert (who to ask). A decision may carry `previous_signal_id`: the `signal_id` it would have had before a rename of its anchor file, so a review surface can re-attach a prior reviewer comment across a `git mv`.

Alongside the question, each decision carries a `tradeoff` clause (the named structural sacrifice stated as a fact, never a recommendation, e.g. "Couples app to infra; 4 in-repo modules already depend on this anchor") and `internal_consumer_count` (the honest count of in-repo modules outside the diff that already depend on the anchor). The human view shows the question and the trade-off; you read reversibility from the count itself, fallow never labels the decision a one-way or two-way door or tells you which way to choose.

The same decision surface is available to agents through the [`decision_surface` MCP tool](/integrations/mcp#available-mcp-tools).
The same decision surface is available to agents through the [`decision_surface` MCP tool](/integrations/mcp#available-mcp-tools), dependency decisions included: both routes share the manifest diff.

### JSON envelope

`fallow review --format json` carries `decisions`, `focus`, `deltas`, `impact_closure`, `partition`, and `graph_facts`. The `deprioritized` escape-hatch list inside `focus` is always present in JSON regardless of `--show-deprioritized`; that flag only re-expands the collapse-by-default human focus render.

`deltas` also carries the dependency keys the decision surface reads: `dependency_added` (`<manifest>::<name>`) and `dependency_major_bumped` (`<manifest>::<name>@<from>-><to>`), so the envelope names what changed even when the cap collapses the decision. Both are always present, as empty arrays when nothing changed.

`partition` carries `independent_slices` only when there are two or more of them: the connected components of the inter-unit dependency graph, each a sorted list of module directories. Two or more slices mean the change splits along a graph-proven seam into pieces that can be reviewed and merged on their own; a single slice would only repeat `order`, so the field is absent. An orientation fact, never a demand to split.

### Agent walkthrough loop

The walkthrough guide turns the brief into a strict agent contract. The verifier is the graph, not a second model: every judgment an agent returns is post-validated against the live graph, and the agent's free-text framing is fenced as non-deterministic and never gates.
Expand All @@ -520,6 +526,10 @@ fallow review --base main --walkthrough-guide --format json

Returns `digest` (brief + decision surface), `direction`, `graph_snapshot_hash`, `agent_schema`, `injection_note`, `emitted_signal_ids`, and `change_anchors`. The digest is built from the graph only (PR prose is never folded in, so it is injection-resistant), and the snapshot hash is pinned into the digest.

Each `direction.units[]` entry carries `test_adjacency`: `none` (no test file imports the changed unit directly), `untouched` (a test imports it and none of those tests is in the diff), or `changed` (at least one importing test moved with the change). It is absent when the graph was not retained or the unit is itself a test file. A graph fact about direct importers, not a coverage claim.

`agent_schema` publishes `action_vocabulary` (`block`, `address`, `consider`, `fyi`), the closed set a judgment's `action` may use, and a recommended `concern_vocabulary` (the thirteen trade-off lenses: `abstraction`, `coupling`, `data-model`, `error-handling`, `control-flow`, `performance`, `dependencies`, `api-ergonomics`, `compatibility`, `state-ownership`, `extensibility`, `testability`, `trust-boundary`). `concern` stays free text on the wire; a value from the list lets a review surface group judgments by lens.

`change_anchors` is a per-hunk anchor set: one stable, content-addressed `chg:` id per changed region, derived from the same diff source the run used. Its id is content-addressed (file path + normalized added text, line numbers excluded) so it survives an edit above the hunk and a whitespace-only change, and it is rename-durable via `previous_change_anchor`. It lets an agent anchor a judgment about a changed REGION that has no graph finding (a trade-off with no `signal_id`), so the broader trade-off can still be post-validated rather than hallucinated.

**2. The agent returns judgment JSON.** Each judgment cites a `signal_id` fallow emitted OR a `change_anchor` fallow emitted; echo the hash.
Expand All @@ -532,28 +542,34 @@ Returns `digest` (brief + decision surface), `direction`, `graph_snapshot_hash`,
"signal_id": "<one fallow emitted>",
"change_anchor": "<or one fallow emitted chg: id, for a changed region with no finding>",
"framing": "<reasoning>",
"concern": "<optional>"
"concern": "<optional>",
"action": "<optional: block | address | consider | fyi>"
}
]
}
```

`action` tells the receiving author what to do with the judgment: `block` and `address` are required actions, `consider` is optional, `fyi` needs nothing. It is the reviewer's instruction, fenced with the framing, never a gate.

**3. Post-validate the judgment against the live graph.**

```bash
fallow review --base main --walkthrough-file judgment.json --format json
```

Each judgment is sorted into one of three outcomes:
Each judgment is sorted into one of these outcomes:

| Outcome | Condition | Shape |
|:--------|:----------|:------|
| `accepted` (signal) | The `signal_id` is anchored and the snapshot still matches. | Framing fenced `deterministic: false`; `anchor_kind: "signal"` (the strong anchor, a graph finding). |
| `accepted` (change) | No `signal_id`, but the `change_anchor` is one fallow emitted and the snapshot still matches. | Framing fenced `deterministic: false`; `anchor_kind: "change"` (the weaker anchor, a changed region only). |
| `rejected` (unanchored) | The `signal_id` is not one fallow emitted (a hallucinated id). | `reason: "unanchored-signal-id"`. |
| `rejected` (unknown change anchor) | The `change_anchor` is not one fallow emitted (a hallucinated region). | `reason: "unknown-change-anchor"`. |
| `rejected` (invalid action) | The anchor resolved but the `action` is outside the closed vocabulary. A judgment with a hallucinated anchor and a bad label is rejected for the anchor, never as `invalid-action`. | `reason: "invalid-action"`, `invalid_value: "<the offending label>"` so the agent can fix it in one round trip. |
| `rejected` (stale) | The echoed graph-snapshot hash no longer matches (the tree moved). | `reason: "stale-snapshot"`, `stale: true`. Re-fetch the guide. |

An accepted judgment echoes `action` next to `agent_framing` when the agent set one.

Both `--walkthrough-guide` and `--walkthrough-file` imply the brief and always exit 0.

## See also
Expand Down
43 changes: 24 additions & 19 deletions public-content-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"visibility": "public-only"
},
"content": {
"sha256": "b9b9ae4674554e077a773d859fd8057296e3c266a6ff185d893ac1ee598da017",
"sha256": "1289a91cfccbbb180df434e875f3a2a9a27d76d5589c8f12aebda6e7ffbe5055",
"files": [
{
"path": ".mintignore",
Expand Down Expand Up @@ -37,7 +37,7 @@
{
"path": "analysis/dead-code.mdx",
"bytes": 23809,
"sha256": "c4b09df2b4d5e98000b00e9d61933a0953f70d4e6bc789a4450982cdf2226b36"
"sha256": "276df0f806ed096f93e0c1c90ea88fed99a2cce07e21ff09dbafb9de44083262"
},
{
"path": "analysis/debugging.mdx",
Expand Down Expand Up @@ -84,10 +84,15 @@
"bytes": 5603,
"sha256": "6184059d782b71ae93b018ea6bc0b592283249cedd6fff7e90ad72ecade0c4f7"
},
{
"path": "cli/agent.mdx",
"bytes": 6168,
"sha256": "9cfe753c97c73b2b5e530fb3b4544be8bb1bb14140eb26f0d91d6a5cbeac16f5"
},
{
"path": "cli/audit.mdx",
"bytes": 39027,
"sha256": "94eac58336a0a26c213692c546ab0dae6481f0e3ae9edcf568cc4355d9519e14"
"bytes": 42250,
"sha256": "b6f4812e8bdc7cbe7bb13da53ee8e89f2ea4625ff55490322570f45771916a4d"
},
{
"path": "cli/config.mdx",
Expand Down Expand Up @@ -127,7 +132,7 @@
{
"path": "cli/flags.mdx",
"bytes": 9074,
"sha256": "23fd6da841ad029acaf37a02c297fa432815f46d5bef59f0ad05df3e8ca927d2"
"sha256": "2ae24a7cfc963b36045b62d49893bef1bf840a4dd199ea1b4decb7436448cb49"
},
{
"path": "cli/global-flags.mdx",
Expand All @@ -137,7 +142,7 @@
{
"path": "cli/health.mdx",
"bytes": 75820,
"sha256": "c8c7dbf36c027148a85c6b60354be25552b5a808df557eb52be15495bc535e33"
"sha256": "92e89207b6a7f25388253882183b20fe5086835ad39bb321b59642c81dcb8103"
},
{
"path": "cli/impact.mdx",
Expand All @@ -146,8 +151,8 @@
},
{
"path": "cli/init.mdx",
"bytes": 6283,
"sha256": "dcddae0d6a9b2f27cfddf7dd75337ac89bd8d94f379f622f44b3cb5d76d2409d"
"bytes": 6445,
"sha256": "1451c5dbe2d6dae4a1e95894919b4ca56a4c6db167d66bf5a1f6d8f25e5ec783"
},
{
"path": "cli/inspect.mdx",
Expand Down Expand Up @@ -186,8 +191,8 @@
},
{
"path": "cli/schema.mdx",
"bytes": 5280,
"sha256": "973c3d4bb12ea2d1cac629481f55bc34078943375dad5e2acc91c446275e66ab"
"bytes": 5582,
"sha256": "d258a1abc8ce910002c45fc9645d63e817dc47a885a54602637f065c295995b6"
},
{
"path": "cli/security-agent-verification.mdx",
Expand All @@ -197,7 +202,7 @@
{
"path": "cli/security.mdx",
"bytes": 25344,
"sha256": "52aab409154c9e5d701b3c6163ff29f52c915955607b502d723d5e308e0429cf"
"sha256": "d1cfc1da68551925d2b7ad541b34dc23fd40448aca5a69634eed7b5f8ed57b2c"
},
{
"path": "cli/suppressions.mdx",
Expand Down Expand Up @@ -271,8 +276,8 @@
},
{
"path": "docs.json",
"bytes": 8256,
"sha256": "dd067f72b15e8fc1bd91ea1cf7c196ff116bd51a21fa14f01f161d187b609ca3"
"bytes": 8283,
"sha256": "0783bda770750fab895e4c0bf5af3c51cdf82d7eb551bafb39ef0fcbc59888e9"
},
{
"path": "explanations/dead-code.mdx",
Expand Down Expand Up @@ -371,8 +376,8 @@
},
{
"path": "integrations/agent-skills.mdx",
"bytes": 8130,
"sha256": "b502b9908f497c0e72b725d424ff6389eba766e59f56153e3ec8523af6746e37"
"bytes": 8451,
"sha256": "649d5b1ca6f2057fa3f88224d8ee475bf0c1a2c18aee2c0532ef49da20a8a12c"
},
{
"path": "integrations/badges.mdx",
Expand All @@ -386,8 +391,8 @@
},
{
"path": "integrations/claude-hooks.mdx",
"bytes": 15257,
"sha256": "59a1f7a6b83e103effd6e18d55ca93358acda0d2ac22320e382663810db0df88"
"bytes": 15574,
"sha256": "0b5e68ce87913efcf2bc950aabb69f1aa18353fcf382ea375c2d952f33f2d0cb"
},
{
"path": "integrations/index.mdx",
Expand All @@ -396,8 +401,8 @@
},
{
"path": "integrations/mcp.mdx",
"bytes": 45784,
"sha256": "9e9333086980caf853ea1200217419805d97386f1a7feee14cde2998ba7d3410"
"bytes": 47948,
"sha256": "447e654693f7734380520e39cdf00568a9ab1badd33e3a5379f92da468e20da8"
},
{
"path": "integrations/neovim.mdx",
Expand Down