@sabaiway/agent-workflow-kit 10.3.0 → 10.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +5 -5
  3. package/SKILL.md +1 -1
  4. package/bridges/antigravity-cli-bridge/SKILL.md +7 -1
  5. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +69 -17
  6. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +73 -2
  7. package/bridges/antigravity-cli-bridge/capability.json +2 -2
  8. package/bridges/antigravity-cli-bridge/references/review-prompt.md +3 -0
  9. package/bridges/codex-cli-bridge/SKILL.md +8 -1
  10. package/bridges/codex-cli-bridge/bin/codex-exec.sh +1 -1
  11. package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +1 -1
  12. package/bridges/codex-cli-bridge/bin/codex-review.sh +89 -18
  13. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +55 -2
  14. package/bridges/codex-cli-bridge/capability.json +2 -2
  15. package/capability.json +1 -1
  16. package/package.json +1 -1
  17. package/references/agents/executor.md +40 -0
  18. package/references/agents/review-lens.md +5 -3
  19. package/references/modes/agents.md +9 -4
  20. package/references/modes/procedures.md +21 -8
  21. package/references/modes/recipes.md +7 -4
  22. package/references/modes/recommendations.md +3 -1
  23. package/references/modes/set-recipe.md +23 -6
  24. package/references/modes/status.md +2 -2
  25. package/references/modes/upgrade.md +1 -1
  26. package/references/modes/velocity.md +1 -0
  27. package/references/shared/composition-handoff.md +1 -1
  28. package/references/shared/deploy-tail.md +1 -1
  29. package/references/templates/orchestration.json +1 -1
  30. package/tools/autonomy-config.mjs +1 -1
  31. package/tools/bridge-posture.mjs +48 -0
  32. package/tools/carriers.mjs +152 -0
  33. package/tools/cheap-agents-read.mjs +234 -0
  34. package/tools/cheap-agents.mjs +101 -109
  35. package/tools/commands.mjs +3 -3
  36. package/tools/detect-backends.mjs +2 -2
  37. package/tools/direct-run.mjs +9 -0
  38. package/tools/family-registry.mjs +38 -18
  39. package/tools/flow-check.mjs +2 -7
  40. package/tools/fold-scope.mjs +5 -60
  41. package/tools/grounding.mjs +2 -2
  42. package/tools/inject-methodology.mjs +4 -0
  43. package/tools/orchestration-config.mjs +23 -61
  44. package/tools/orchestration-readme.mjs +70 -0
  45. package/tools/plan-shape-cli.mjs +112 -0
  46. package/tools/plan-shape-facts.mjs +204 -0
  47. package/tools/plan-shape.mjs +348 -0
  48. package/tools/procedures.mjs +197 -83
  49. package/tools/recipes.mjs +183 -230
  50. package/tools/recommendations.mjs +77 -11
  51. package/tools/renderers.mjs +27 -7
  52. package/tools/repo-lex.mjs +40 -0
  53. package/tools/review-roster-resolve.mjs +104 -0
  54. package/tools/review-roster.mjs +128 -0
  55. package/tools/review-rounds-cli.mjs +92 -0
  56. package/tools/review-rounds.mjs +115 -0
  57. package/tools/review-state.mjs +10 -11
  58. package/tools/set-recipe-roster.mjs +167 -0
  59. package/tools/set-recipe.mjs +138 -42
  60. package/tools/velocity-profile.mjs +8 -22
  61. package/tools/view-model.mjs +17 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,91 @@ Semantically versioned ([semver](https://semver.org)), newest first. The `versio
4
4
  is the current release. `upgrade` mode reads a project's `docs/ai/.workflow-version` and applies
5
5
  every `migrations/<version>-<slug>.md` newer than it, in semver order.
6
6
 
7
+ ## 10.5.0 — the plan-review loop's measured costs are rungs, values and receipt fields (AD-125)
8
+
9
+ One feature cost five hours of review by the kit's own defaults: plan findings that were plan SHAPE
10
+ no tool checked, round-1 blockers that were READERS of a changed value nobody enumerated, a
11
+ consult-before-fold rule living in an agent's memory, folds riding the executor vehicle, no run
12
+ duration anywhere, and a crossover stop that had to be remembered. Each of those is now a checker,
13
+ a procedure step, a slot or a receipt field — advisory by contract (`docs/ai/specs/kit/plan-review-loop/`,
14
+ S1–S28): no new gate by default, the commit gate stays on the bridges, a lens never becomes a receipt.
15
+
16
+ **The plan-shape checker** (`tools/plan-shape.mjs` the rules, `plan-shape-facts.mjs` one repository
17
+ snapshot, `plan-shape-cli.mjs`): `--check <plan>` refuses a plan by shape BEFORE its first review — the
18
+ five headings in order, the row cap (100 lines AND ≤25 ledger rows, 200 UTF-8 bytes per row counted
19
+ without the path and anchor fields; the 8000-byte cap is gone), red-first row order, a
20
+ `package-content` pin row for every shipped `create` (from the package `files`), budgets under the cap
21
+ and the recorded size, anchors that resolve to exactly one candidate; `--verify <plan>` closes a
22
+ ledger after execution; `--in-flight` judges every titled plan by shape. Exit 0 / 1 / 2, every refusal
23
+ naming the line, the rule and the fix. `grounding` and `fold-scope` import the headings from it.
24
+
25
+ **The round table** (`tools/review-rounds.mjs`, `review-rounds-cli.mjs --artifact <plan>
26
+ [--activity …]`): receipts grouped by fingerprint into rounds, latest per backend, complete by the
27
+ configured slot's obligation; ONE of six signals in order — `converged` is a complete round (every listed backend under a
28
+ per-backend obligation, at least the obligation's minimum otherwise) whose every present backend
29
+ reports blocking 0 with a ship-class verdict, `crossover` needs a negative — with the receipted duration per round and
30
+ cumulative, the pathless and malformed receipts counted. `procedures plan-authoring` prints that
31
+ command populated with the plan in flight, beside the grounding pre-step, as the verdict half of the
32
+ per-round emission; the finding-origin tally stays the orchestrator's. Beneath the command one fact
33
+ line holds for every source: the table judges the obligation `review-rounds-cli` resolves from the config
34
+ (S27) — never a run's `--override` or a recipe the advisor degraded — and reads receipts only (a backend
35
+ that did not run shows as missing; its degrade record is `review-state`'s and `core-evidence summary`'s);
36
+ a requested review recipe renders the command even when every bridge is unavailable here (a degradation
37
+ removes no configured obligation); a roster with no bridge gets, in the command's place, the fact that no
38
+ receipt can exist; a mixed roster adds the lens reminder. The `--kit-tools` tier gains
39
+ `tools/review-rounds-cli.mjs` (read-only: the receipts store plus read-only `git rev-parse` queries —
40
+ `--show-toplevel` here and `--absolute-git-dir` through the receipts-path resolver; the advisor
41
+ names it every round, so it renders BARE in the tier's byte-form). The plan operand of every rendered
42
+ command — the round table, the grounding pre-step, `agy-review plan` — rides `shellQuoteArg` (bare when
43
+ safe, single-quoted otherwise); a plan name a one-line render cannot carry (a control or line-breaking
44
+ character) falls back to the placeholder on every command, and a name the receipt encoder refuses (a
45
+ double quote, a backslash, a control byte — the S21 set) falls back on the commands that mint or match
46
+ a receipt (`agy-review plan`, the round table) while the file-reading `grounding --plan` stays
47
+ populated; the caveat names the plan escaped and the operand(s) that fell back.
48
+
49
+ **The procedure and the config.** `plan-authoring` gains the `fold` carrier slot (`solo | subagent`,
50
+ default `solo` even beside `author = subagent`; resolved, validated, previewed, written and unset like
51
+ every carrier slot; the slice wording keyed by slot). The advisor renders the readers sweep for
52
+ plan-authoring — the typed `repo_search` line when the MCP registration is complete on disk and ALWAYS
53
+ the command line in the tier's seedable byte-form — and the consult-before-fold order in the review-loop
54
+ block (agy `--continue --decided @f --focus …`, codex a fresh `codex-review plan <consult-brief>`, a
55
+ placed lens a re-dispatch; on an armed flow the bridge `consult-attestation` sequence). A `Slots:`
56
+ line an installed engine renders differently from the kit's registry is said in `warnings`.
57
+ `requiredBackendsForConfiguredRecipe` takes an `activity`; the shell lexing predicate
58
+ `isSeedablePathToken` lives in the pure leaf `repo-lex.mjs`. Bundled bridges: codex-cli-bridge
59
+ 3.6.0 and antigravity-cli-bridge 5.5.0 (the receipt fields, the node preflight, the no-file-read
60
+ prepend, the single schema parse — see each bridge's SKILL.md). `package-content` pins 275 files.
61
+
62
+ ## 10.4.0 — who does what is a project setting, and a subagent is a first-class carrier of every kind of work (AD-124)
63
+
64
+ The rule "design, then hand execution to a frontier subagent" lived in an agent's memory and in a
65
+ prose bullet of one project's `agent_rules.md`. It is now a VALUE in `docs/ai/orchestration.json`,
66
+ resolved and rendered by the surfaces that already resolve and render every recipe.
67
+
68
+ **One registry** (`tools/carriers.mjs`, contract `docs/ai/specs/kit/carriers.md`, re-exported by
69
+ `recipes.mjs` so no importer moves): three activities — `plan-authoring` (`author`, `review`),
70
+ `plan-execution` (`execute`, `review`) and the new `routine` (`carrier`, `parallel`); the fifth recipe
71
+ `subagent` (a full-tool frontier subagent carries a bounded, file-disjoint slice; the orchestrator
72
+ verifies it and commits); `parallel` is a switch (`on | off`) resolved outside the recipe lattice.
73
+ Computed defaults stay `solo` (placing the vehicle never flips one); the seed config keeps its
74
+ two-activity shape, so an older kit accepts it; `routine` carries no autonomy level of its own.
75
+
76
+ **The executor vehicle.** `references/agents/executor.md` — model opus, all tools, the dispatch form in
77
+ its prompt, never a git write, the project's own language rules — is the fifth bundled vehicle the
78
+ `agents` writer places beside the four read-only ones. `tools/cheap-agents-read.mjs` (the writer's
79
+ read core, so the read-only advisors never import a writer) surveys it as `placed | customized |
80
+ unusable | missing` through a stated YAML subset; `composeReadiness` surveys the vehicle first and
81
+ hands a bridge-detector failure to `onDetectError`, so neither axis masks the other; a review-role
82
+ computation never counts the executor.
83
+
84
+ **Every surface speaks the carrier.** `set-recipe` writes every slot (value-neutral diagnostics);
85
+ `procedures` renders the dispatch form per `subagent` slot and the `parallel × carrier` cells;
86
+ `status` shows the resolved carrier per slot and the executor state; the advisor raises ONE
87
+ `attention` item for a configured `subagent` whose vehicle is missing or unusable; the catalog and
88
+ every mode doc name five recipes and three activities. A routine chore is classified first:
89
+ read-only rides a placed read-only vehicle (or is carried solo, stated), write-capable rides the
90
+ executor.
91
+
7
92
  ## 10.3.0 — a deployed project is never in an unnamed spec-adoption state, and a skip names a fact the tree can check (AD-123)
8
93
 
9
94
  A consumer's `upgrade` printed `specs: skipped-no-node — no package.json at the project root` beside
package/README.md CHANGED
@@ -232,9 +232,9 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
232
232
  | `/agent-workflow-kit gates` | any time | **project gate runner** — runs the verification commands **your project itself declares** in `docs/ai/gates.json` (seeded at deploy; hand-editable `{ id, title, cmd }` entries plus the optional boolean `lcovProducer`, each `cmd` one bash line) as one batch: a per-gate **PASS/FAIL table** + one machine-readable summary line, exit 0 iff all green; a failing gate's own output is shown verbatim; `--only <id>` re-runs one. Honest distinct outcomes for a missing / empty / malformed declaration — never a silent green. The runner writes nothing **by default** and never commits — opt-in `--final` runs the FULL declared matrix and mints ONE final-run receipt into the git-dir core-evidence store (status green/red, pre/post tree fingerprints, the declaration, the consumed lcov's sha; the receipt the commit-guard binds); it executes only your own declared commands (a batching convenience, not a sandbox). The velocity tier auto-approves only the exact no-`--final` form — the recording run stays explicit. |
233
233
  | `/agent-workflow-kit setup [backend]` | opt-in, any time | **link-only** auto-setup of a bridge: places the bundled bridge skill (only into an absent / empty / managed dir — never overwrites an unmanaged one) + links its wrappers onto `PATH` via managed symlinks (idempotent; refuses to clobber a non-symlink; try `--dry-run` to preview). The binary install + the one-time subscription login stay **manual**: it prints the exact **login** command and points the binary install at each bridge's `setup/README.md`. POSIX wrappers — on Windows use WSL. Never commits, never runs a subscription CLI. |
234
234
  | `/agent-workflow-kit status` | any time | **read-only** single view of **versions + deployment + settings + bridges**: which members (kit / memory / engine / the two bridges) are installed and at what version (with an honest "installed on this machine" note when one is behind) and — in a project — what's deployed (`docs/ai`, the version stamps, and the **visibility**: visible / hidden / unclear), and the **feature-spec adoption state** (`not adopted` / `adopting (N draft)` / `adopted (N live, M draft)` / `could not be read`, plus `declined` when recorded), plus your settings (orchestration recipes, attribution, velocity) and the bridges' readiness. The two version axes (package number vs deployment-structure head) stay decoupled. Never writes, never commits, never runs a subscription CLI. |
235
- | `/agent-workflow-kit recipes` | any time | **read-only** orchestration advisor: presents four named recipes for composing the bridges into plan → execute → review — **Solo / Reviewed / Council / Delegated** — plans + recommends one for your environment (degrading with a stated reason when a backend isn't ready), and offers the choice. The orchestrator runs it via the bridge skills and **always commits**; the kit never executes a recipe, never runs a subscription CLI, never commits. |
236
- | `/agent-workflow-kit procedures <activity>` | any time | **read-only** activity-procedures advisor: prints a named activity's ordered steps (`plan-authoring` / `plan-execution`) read **live** from the engine, plus the **resolved recipe per slot** from your `docs/ai/orchestration.json` (agent-writable via `set-recipe`, or hand-edited) + backend readiness (default Reviewed when a backend is ready, Council on request, slot-aware incl. Delegated) — and, for every dispatched backend, the **full driving contract at the point of use** (exact copy-pasteable invocation, grounding levers like agy's `--facts`/`--decided`, the round-2 `--continue` delta, guarded passthrough), verbatim from the bridge manifests (drift-guarded; each wrapper's `--help` prints the same). `--override <slot>=<recipe>` adjusts one slot per run. Composes with `recipes`; never writes, never commits, never runs a subscription CLI. |
237
- | `/agent-workflow-kit set-recipe` | any time | **config writer** for `docs/ai/orchestration.json`: tell the agent your preference in plain language and it maps it to explicit `--set <activity>.<slot>=<recipe>` / `--unset` ops; the kit validates, **previews by default**, and writes only on `--write` (deployment-gated, atomic, symlink/TOCTOU-safe), resolving the effective recipe vs live readiness. Writes **only** that file — **never runs a backend, never commits**; hand-editing stays fully supported. |
235
+ | `/agent-workflow-kit recipes` | any time | **read-only** orchestration advisor: presents five named recipes for composing the carriers of a step — the bridges and the full-tool executor subagent — into plan → execute → review — **Solo / Reviewed / Council / Delegated / Subagent** — plans + recommends one for your environment (degrading with a stated reason when a backend isn't ready, or when the executor vehicle is missing/unusable), and offers the choice. The activity/slot registry (three activities) is rendered by `procedures` and by `set-recipe --help`. The orchestrator runs it via the bridge skills or the executor vehicle and **always commits**; the kit never executes a recipe, never runs a subscription CLI, never commits. |
236
+ | `/agent-workflow-kit procedures <activity>` | any time | **read-only** activity-procedures advisor: prints a named activity's ordered steps (`plan-authoring` / `plan-execution` / `routine`) read **live** from the engine, plus the **resolved recipe per slot** from your `docs/ai/orchestration.json` (agent-writable via `set-recipe`, or hand-edited) + carrier readiness (`plan-authoring`: author, fold, review; default Reviewed when a backend is ready, Council on request; slot-aware incl. Delegated and Subagent) — and, for every dispatched backend, the **full driving contract at the point of use** (exact copy-pasteable invocation, grounding levers like agy's `--facts`/`--decided`, the round-2 `--continue` delta, guarded passthrough), verbatim from the bridge manifests (drift-guarded; each wrapper's `--help` prints the same). `--override <slot>=<value>` adjusts one slot per run. Composes with `recipes`; never writes, never commits, never runs a subscription CLI. |
237
+ | `/agent-workflow-kit set-recipe` | any time | **config writer** for `docs/ai/orchestration.json`: tell the agent your preference in plain language and it maps it to explicit `--set <activity>.<slot>=<value>` / `--unset` ops; the kit validates, **previews by default**, and writes only on `--write` (deployment-gated, atomic, symlink/TOCTOU-safe), resolving the effective recipe vs live readiness. Writes **only** that file — **never runs a backend or a subagent, never commits**; hand-editing stays fully supported. |
238
238
  | `/agent-workflow-kit review-state` | any time | **read-only review-receipt checker** — makes "reviewed ≠ shipped" detectable: the bridge review wrappers append a receipt per successful review (into a file inside the git dir — never committable); this checks that every backend your configured `plan-execution.review` recipe names holds a **fresh, grounded receipt for the current uncommitted tree** (any later edit moves the fingerprint and stales the receipt; a review continuation never re-attests a folded tree). `--check` gives a gate exit code to declare in `docs/ai/gates.json` **by hand or via the consent-gated seeder** (preview → your explicit yes; never without consent). `--await [--timeout <s>]` (AD-049) BLOCKS until every recipe-named backend has receipted the current tree — the durable completion signal is the receipt, never a process event — so you wait for the bridges instead of hand-polling. Never writes, never commits, never runs a subscription CLI; it spawns read-only `git` queries to fingerprint the tree — and `git commit --no-verify` stays possible (discipline, not a sandbox). |
239
239
  | `/agent-workflow-kit sandbox-masks` | any time | **cosmetic exclude lane for sandbox device masks** — an OS sandbox (Claude Code) injects character-device masks into the work tree as untracked `git status` noise; the review domain already ignores them **by construction** (never-committable untracked classes — char/block devices, FIFOs, sockets — are excluded from the fingerprint, the assembled review payload, and the clean checks). This mode hides them from `git status` too: flagless = read-only probe (derives the CURRENT mask set from the unfiltered walk + lstat — never a frozen list — and revalidates fenced entries, loudly flagging one that became a real path); `--apply` = consent-gated FULL-BLOCK replace of its own fenced block in `git rev-parse --git-path info/exclude` (stale masks drop by construction; `--clear` always means REMOVE the block — it takes precedence over the derivation). Writes ONLY its fence — never `.gitignore`, never global config; symlinked/non-regular exclude paths and malformed fences fail closed. Watch note: a real file at an excluded path is silently skipped by bulk staging (`git add -A`/`git add .`) — delete the stale line first; the probe flags exactly this case. |
240
240
  | `/agent-workflow-kit mcp` | Claude Code · opt-in | **typed-channel registration** — the kit ships a read-only stdio **MCP server** exposing its two promptless readers as TYPED tools (`path_inventory`: exists / type / size / line count / listing / a small file's text, many paths in ONE call; `repo_search`: literal search, the pattern a JSON string). Their arguments are named JSON fields rather than a string handed to a shell, so a pipe, a redirect or a quote inside a pattern or a path stays DATA and is never interpreted — legal bytes to search for, with no shell to read them as operators. Shipping the server does nothing on its own — a client sees it only once the project declares it, and this mode is that declaration: the `agent-workflow` entry in **`.mcp.json`** (command `node`, args = the absolute path of the RUNNING kit's server) plus `enabledMcpjsonServers` and the two derived tool allow rules in **`.claude/settings.json`**. Preview by default and the **exact entry is printed before consent** (registering a server means your client will run that command); `--apply` writes `.mcp.json` FIRST, then settings, merge-don't-clobber with each file's EOL kept, and a re-apply adds nothing twice. An existing `agent-workflow` entry that **structurally differs** from what this kit copy would write is refused unwritten (the comparison ignores key order, so re-serialized identical bytes are the same registration) — silently changing what an MCP server launches is what consent must not slide past. Where an OS sandbox masks `.mcp.json` with a device node, it writes nothing, hands you both paste-ready texts and exits 0. The server is a read-only child of your client (no write, no exec API) running outside the Bash sandbox as the client does. Never writes `settings.local.json`; never commits. |
@@ -248,7 +248,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
248
248
  | `/agent-workflow-kit worktrees` | any time | **parallel feature worktrees** — run several features in DIFFERENT agent sessions on one repo, zero interference on working-tree files (the ONE exception is the dependency cache, below): `provision <slug> --plan <file>` creates a sibling git worktree on branch `aw/<slug>` and populates it (registry-derived footprint copy-if-missing — a tracked file is never overwritten; EXACTLY ONE seeded feature plan; the `handoff-<slug>.md` record from minute zero; `node_modules` symlinked where the link stays ignored — a shared MUTABLE dependency cache: writes through it hit MAIN's node_modules; for isolation run the printed isolated-install command (`--install` only PRINTS it; on `--resume` run the printed unlink-first recovery first — printed only for a link whose RAW TARGET is MAIN's `node_modules`, since a foreign or unreadable link is reported as such with no removal advised); absolute root-pinned gate commands rebased on untracked copies only, and only while their bytes equal the MAIN source or its rebased form — user-modified copies stay untouched); `list` is read-only (slug, branch, base, dirty, handoff); `prompt <slug>` is read-only too and re-prints the satellite's cold-start prompt — the same text `provision` ends its report with: where the worktree is, its ONE seeded plan, the handoff as the one channel back, MAIN's orientation (shared series index, landing) and, under its own heading because it is probed on the satellite, that checkout's install posture — every value derived LIVE rather than replayed from the frozen provision record, a recorded value that no longer matches NAMED as a divergence (with the cause its source makes likely) instead of printed as the runnable one, every offered command marked with WHO runs it (`MAIN $ …` for the landing, `HERE $ …` for this checkout's own install, a posture with nothing to run staying prose), and a control character in any rendered value a typed STOP rather than a forged prompt line; `land <slug> --prepare` locks the common git dir, fail-closes on divergence or incomplete satellite state, transfers the complete accepted satellite diff onto a CLEAN main, runs sync plus the declared gates, and reports HEAD/TRANSFER/PREPARED OIDs — the commit ALWAYS stays a dialogue ask; `cleanup <slug>` takes the same lock and removes a LANDED worktree only after live landed-verification against main HEAD, while `--abandon` is the ONE destructive arm (destroys unlanded work; **no preview step** on any writer). The parent dir is the `docs/ai/worktrees.json` `parentDir` setting (default: the repo's sibling parent); an unwritable parent degrades to printed maintainer-pasted commands, and the one-time host consent that makes it promptless surfaces via `recommendations`. Never commits, never pushes, never runs a subscription CLI. |
249
249
  | `/agent-workflow-kit uninstall` | opt-in, any time | **guarded teardown** — the inverse of `init` / `setup`. Removes only what's **provably ours** (managed skill dirs + bridge wrappers; in a project, the hidden-mode git-ignore block it added + the pre-commit hook it installed); **never deletes** your `docs/ai` / `AGENTS.md` (prints the exact `rm` to run by hand) or your `.claude/settings.json` (prints an **edit** — remove the attribution key, review any velocity `permissions.*` — never an `rm`). Always `--dry-run` first; preflight-then-mutate; never commits. |
250
250
  | `/agent-workflow-kit velocity` | Claude Code · opt-in | **onboarding velocity profile** — seeds a fixed, audited **read-only** allowlist into `.claude/settings.json` so routine read-only commands stop idling on approval prompts while you're away; opt-in `acceptEdits`; plus a **read-only advisory** of likely project gate commands to add by hand. Writes **only** `.claude/settings.json` — **never** allowlists commit/push/publish, never writes `settings.local.json`, never commits. A seeded entry is a **trust posture, not a sandbox** (a runtime residual remains at the settings level — its guard ships as the opt-in `hook` command); a direct commit/push/publish still asks. `--dry-run` first. |
251
- | `/agent-workflow-kit agents` | Claude Code · opt-in | **read-only subagents** — places bundled subagent definitions into `.claude/agents/`. **No vehicle gets `Bash`**, and that is the load-bearing property: a read-only fan-out on a full-tool subagent shells out for facts it could have read, and every shelled command is an approval prompt you never needed to see. Three ride a **cheap model** (haiku, low effort) for mechanical work — extraction sweeps, changelog fact-skeletons, gate-failure triage; the fourth, **`review-lens`**, is an ADDITIONAL independent read-only review opinion on code your configured backends have already seen (never a replacement for your review recipe, advisory like every review). Writing code and running gates stay on your main lane, and every vehicle's output is verified. Preview by default (`--apply` writes); an existing customized file is **preserved, never overwritten**; never touches `settings*.json`, never commits. |
251
+ | `/agent-workflow-kit agents` | Claude Code · opt-in | **subagent vehicles** — places the five bundled subagent definitions into `.claude/agents/`: **four read-only vehicles + one full-tool `executor`**. **No read-only vehicle gets `Bash`**, and that is the load-bearing property: a read-only fan-out on a full-tool subagent shells out for facts it could have read, and every shelled command is an approval prompt you never needed to see. Three of the four ride a **cheap model** (haiku, low effort) for mechanical work — extraction sweeps, changelog fact-skeletons, gate-failure triage; the fourth, **`review-lens`**, is an ADDITIONAL independent read-only review opinion on code your configured backends have already seen (never a replacement for your review recipe, advisory like every review). The fifth, **`executor`** (`model: opus`, all tools), is the ONE vehicle with a shell and the instrument behind the **Subagent** recipe: dispatched only for a bounded, file-disjoint execution / authoring / routine slice you verify afterwards — never for read-only work, never as a review backend, and it never commits. Its state (`placed` / `customized` / `unusable` / `missing`) is what a `subagent` slot's readiness resolves from. Writing code and running gates stay off the four read-only vehicles; the executor carries a bounded slice you re-verify on your main lane, and every vehicle's output is verified. Preview by default (`--apply` writes); an existing customized file is **preserved, never overwritten**; never touches `settings*.json`, never commits. |
252
252
  | `/agent-workflow-kit hook` | Claude Code · opt-in | **gate-approval hook** — places a self-contained PreToolUse hook (`.claude/hooks/`) and wires it into `.claude/settings.json`: a Bash command **byte-identical** to a gate you declared in `docs/ai/gates.json` (run from the project root) is auto-approved — no prompt, no idle; a seeded read-only command carrying a runtime residual (output redirection, command substitution, `--output` writes) now **asks** even where an allow rule would have silently passed it (proven live — hook `ask` overrides an allow rule). Detection is a conservative string scan, so it over-asks on a byte that is ordinary text — searching for `=>`, or a read wearing `2>/dev/null`, prompts. That is a documented open limit: 4.1.0 built three mechanisms to narrow it and removed all three in review, each counterexample kept as a test (see Mode: hook). Exact matches only, never patterns; never `deny`; a broken `gates.json` only disables auto-approval, never the guard. An opt-in **read-lane** (`--read-lane` → `docs/ai/lanes.json`, a **separate** file from `gates.json`) additionally auto-approves *compounds* of seeded read-only commands carrying no shell metaprogramming; `--apply --read-lane` verifies the placed hook is current first (delete-to-reseed otherwise). Preview by default; never writes `settings.local.json`; never commits. |
253
253
  | `/agent-workflow-kit state-block-guard` | Claude Code · opt-in | **closing-block detector** — a `Stop` hook that reads the turn's final assistant message and warns when the closing state block is defective: the «what I need from you» slot answering *nothing* (a turn that ENDS always needs a resume, so the answer is false, not merely unhelpful), or a first-person promise of imminent work in a turn that is over. Judged on the slot's first clause, so a real ask followed by a clause break and "nothing else" passes, and a promise gated on something named passes; matching is word-bounded and Unicode-aware, quoted and fenced examples are stripped, and the last STARTED block decides — an incomplete tail counts as no block rather than falling back to an earlier one. A message carrying no block at all is silent unless you pass `--require-block` — this kit does not mandate the block, and a hook that runs every turn must not warn every turn. The judgement is lexical and the mode doc names every residual it leaves. **Detection, never prevention** — a `Stop` hook cannot un-send the message it judges; what it buys is that a silent recurrence becomes a visible one. Warns via `systemMessage` and **exits 0 on every path**: never `deny`, never blocks the stop, never re-enters the model. Reads nothing but the message, makes no network call, approves nothing. This mode is **read-only and has no writer** — it ships the self-contained hook and a paste-ready wiring block you merge into `.claude/settings.json` yourself. |
254
254
  | `/agent-workflow-kit bridge-settings` | opt-in, any time | **host-level bridge settings** — read or change the bridges' `KEY=VALUE` config file (`${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf`), the **one place a knob survives kit upgrades** (it lives outside every kit tree; a refresh never touches it, and now states loudly if it ever overwrote a local edit). First knobs: the **codex Fast tier** (`CODEX_SERVICE_TIER=priority` — ~1.5× speed at a **2.5× credit rate**, quality-neutral, default off — a consented spend), the codex/agy hard timeouts, the codex review byte cap, and `AGY_REVIEW_MAX_TOTAL_BYTES` (the ceiling on what an oversized agy code review may feed before it refuses pre-spend). `AGY_REVIEW_ALLOW_ADDDIR` is a **retired** compatibility key: still recognized so an existing line never warns as unknown, but it arms nothing — the writer refuses to set it and `--unset` clears it. Allowed keys + value rules come from the bundled bridge manifests; **model/effort are never settable** (the quality guard is untouched). **Previews by default**; refuses an unknown key, an out-of-range value, or a duplicate-carrying file; writes atomically (symlink/TOCTOU-safe). Never commits, never runs a subscription CLI. |
@@ -292,7 +292,7 @@ agent-workflow-kit — the composition root (installed via npx … init)
292
292
  - **Delegates** substrate deployment to **`@sabaiway/agent-workflow-memory`** when a healthy
293
293
  standalone copy is present, else uses its **bundled fallback** — same `docs/ai/` either way.
294
294
  - **Injects** three bounded pointers into the deployed `AGENTS.md` — the workflow **methodology**, the
295
- **orchestration recipes** (Solo / Reviewed / Council / Delegated), and the **autonomy policy** (the
295
+ **orchestration recipes** (Solo / Reviewed / Council / Delegated / Subagent), and the **autonomy policy** (the
296
296
  `docs/ai/autonomy.json` read contract) — read **live** from the installed
297
297
  **`agent-workflow-engine`** (the canonical narrative; a published member, never one of the shipped
298
298
  backends). `/agent-workflow-kit recipes` surfaces + plans a recipe for your environment, read-only.
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: agent-workflow-kit
3
3
  description: Deploy or upgrade a portable AI-agent memory-and-workflow system in any project. Use when the user wants to bootstrap `docs/ai/` + an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) + cap/archive/index enforcement in a new or existing repo, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-kit` / `/agent-workflow-kit upgrade`. Triggers on phrases like "set up the memory system", "deploy the AI workflow here", "bootstrap docs/ai", "upgrade the workflow".
4
4
  disable-model-invocation: true
5
5
  metadata:
6
- version: '10.3.0'
6
+ version: '10.5.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-kit
@@ -2,7 +2,7 @@
2
2
  name: antigravity-cli-bridge
3
3
  description: Delegate work to Google's Antigravity CLI (`agy`) — the successor to Gemini CLI — to reach Gemini, Claude, and GPT-OSS models under a Google AI Pro/Ultra subscription from the terminal. Use when the user wants to run a headless `agy` prompt, hand a focused task or second-opinion review to `agy`, install or authenticate Antigravity CLI, check or economise its quota/models, bridge project context into `agy`, set up a second delegated-execution backend beside Codex, or troubleshoot `agy` flags, models, auth, conversations, or its headless behaviour.
4
4
  metadata:
5
- version: '5.4.0'
5
+ version: '5.5.0'
6
6
  ---
7
7
 
8
8
  # antigravity-cli-bridge
@@ -173,6 +173,12 @@ hands to `timeout(1)`; without a capping binary `agy-review` fails CLOSED pre-sp
173
173
  informational, never a receipt field. **Quote the posture banner verbatim** when labeling a
174
174
  dispatch.
175
175
 
176
+ Every successful review receipt carries integer `durationS` and `blocking`; the wrapper prints
177
+ `review duration: <n>s`. A plan or diff receipt also carries `artifactPath`, normalized to a
178
+ repo-relative realpath inside the work tree and an absolute realpath otherwise. A double quote,
179
+ backslash or control byte in that path refuses pre-spend because the receipt encoder cannot carry
180
+ it. Agy counts numbered items in the first `### Blocking` section.
181
+
176
182
  **Flow dispatch nonce (`AW_REVIEW_NONCE`, unset by default):** under the safe grammar
177
183
  `[A-Za-z0-9._-]{1,64}` (anything else refuses pre-spend), a successful review first mints the
178
184
  finding MANIFEST `agent-workflow-finding-manifest-agy-<nonce>.json` beside the receipts file —
@@ -114,8 +114,13 @@ Receipt:
114
114
  uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the
115
115
  review-payload domain; never-committable untracked paths — character/block devices, FIFOs,
116
116
  sockets — are excluded from the domain entirely, untracked symlinks/directories ride as
117
- name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; verdict
118
- recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK);
117
+ name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; durationS = integer
118
+ wall-clock seconds from CLI start through verdict parsing and the wrapper prints review duration:
119
+ <n>s; blocking = the count of numbered items in the first ### Blocking section; artifactPath =
120
+ the normalized realpath on plan/diff receipts only (repo-relative inside the work tree, absolute
121
+ otherwise), while a double quote, backslash or control byte refuses pre-spend because the receipt
122
+ encoder cannot carry it; verdict recorded verbatim from the mandated '### Verdict' section
123
+ (SHIP / SHIP WITH NITS / REWORK);
119
124
  grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without
120
125
  one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records
121
126
  grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts
@@ -163,7 +168,7 @@ Honesty + posture (D4/D5):
163
168
  mode.
164
169
 
165
170
  Closed grammar: unknown flags are rejected; no '--' passthrough (the flag escape is --ungrounded; the env escape is AGY_PROBE=1).
166
- Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
171
+ Requires at run time: the agy CLI on PATH, a Google AI subscription login, Node >= 22 (--help needs none of these).
167
172
  HELP
168
173
  exit 0
169
174
  ;;
@@ -338,7 +343,7 @@ DEFAULT_AGY_REVIEW_MODEL="Gemini 3.7 Flash (High)"
338
343
  # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
339
344
  # version (drift-guarded by agy-review.test.mjs against capability.json).
340
345
  AW_RECEIPT_BACKEND="agy"
341
- AW_BRIDGE_VERSION="5.4.0" # aw-version-anchor
346
+ AW_BRIDGE_VERSION="5.5.0" # aw-version-anchor
342
347
  # `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
343
348
  AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
344
349
  # D5 control-byte screen — IMMEDIATELY after resolution, BEFORE the off-frontier advisory (or any
@@ -891,6 +896,42 @@ receipt_json_scalar() {
891
896
  if [[ -z "${1:-}" ]]; then printf 'null'; else printf '"%s"' "$1"; fi
892
897
  }
893
898
 
899
+ # refuse_uncarriable_artifact_byte <label> <value> — the receipt encoder escapes nothing beyond its
900
+ # grammar-bound fields, so an artifact path is BOUNDED, never escaped: a byte the line could not
901
+ # carry refuses pre-spend, by name.
902
+ refuse_uncarriable_artifact_byte() {
903
+ local label="$1" value="$2" byte=""
904
+ case "$value" in
905
+ *'"'*) byte="a double quote" ;;
906
+ *'\'*) byte="a backslash" ;;
907
+ *[$'\x01'-$'\x1f'$'\x7f']*) byte="a control" ;;
908
+ esac
909
+ if [[ -z "$byte" ]]; then return 0; fi
910
+ echo "error: $label contains $byte byte, which the receipt encoder cannot carry." >&2
911
+ return 2
912
+ }
913
+
914
+ normalize_artifact_path() {
915
+ local input="$1" normalized
916
+ refuse_uncarriable_artifact_byte "artifact path" "$input" || return 2
917
+ if ! normalized="$(node -e '
918
+ const { realpathSync } = require("node:fs");
919
+ const { isAbsolute, relative, sep } = require("node:path");
920
+ const { spawnSync } = require("node:child_process");
921
+ const absolute = realpathSync(process.argv[1]);
922
+ const git = spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" });
923
+ const root = git.status === 0 ? realpathSync(git.stdout.replace(/\r?\n$/, "")) : null;
924
+ const rel = root === null ? null : relative(root, absolute);
925
+ const contained = rel !== null && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
926
+ process.stdout.write((contained ? rel : absolute).split(sep).join("/"));
927
+ ' -- "$input" 2>/dev/null)"; then
928
+ echo "error: artifact path '$input' could not be normalized to a real path." >&2
929
+ return 2
930
+ fi
931
+ refuse_uncarriable_artifact_byte "normalized artifact path" "$normalized" || return 2
932
+ printf '%s' "$normalized"
933
+ }
934
+
894
935
  # STRICT JSON string encoding for the ONE free-form receipt field (the posture model display
895
936
  # string): backslash then double-quote escaped. Control bytes never reach here — the posture
896
937
  # resolution refuses them pre-spend (D5), so these two escapes make the encoding total.
@@ -1008,7 +1049,7 @@ process.exit(code);
1008
1049
  return 0
1009
1050
  }
1010
1051
 
1011
- # write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|""> [probe: true|false] [delivery|""] [findings-file]
1052
+ # write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|""> [probe: true|false] [delivery|""] [findings-file] [artifact-path] [durationS] [blocking]
1012
1053
  # Appends ONE receipt line (the AD-038 fixture shape) as a side effect of a SUCCESSFUL review —
1013
1054
  # to $AW_REVIEW_RECEIPTS when set, else <git dir>/agent-workflow-review-receipts.jsonl (inside the
1014
1055
  # git dir by construction, so it is never committable). Fail-safe: every failure here warns loudly
@@ -1022,6 +1063,7 @@ process.exit(code);
1022
1063
  # minted FIRST (atomic, no-clobber, ORDERED) and a failed mint EXCLUDES the receipt append.
1023
1064
  write_review_receipt() {
1024
1065
  local artifact="$1" fresh="$2" fingerprint="$3" verdict="$4" grounded="$5" facts_hash="$6" probe="${7:-false}" delivery="${8:-}" findings_file="${9:-}"
1066
+ local artifact_path="${10:-}" duration_s="${11:-0}" blocking="${12:-0}"
1025
1067
  local receipts="${AW_REVIEW_RECEIPTS:-}"
1026
1068
  if [[ -z "$receipts" ]]; then
1027
1069
  local receipt_git_dir
@@ -1039,16 +1081,16 @@ write_review_receipt() {
1039
1081
  fi
1040
1082
  # A nonce-SUPPLIED dispatch stamps its nonce into the receipt too (the flow round-land matcher
1041
1083
  # requires exact {backend, nonce} equality — dispatch identity end-to-end); the nonce is
1042
- # grammar-safe by the pre-spend check, and a nonce-less receipt stays BYTE-EXACT (the frozen
1043
- # compatibility floor).
1044
- local line probe_field=',"probe":false' delivery_field="" nonce_field=""
1084
+ # grammar-safe by the pre-spend check; a nonce-less receipt adds NO nonce field.
1085
+ local line probe_field=',"probe":false' artifact_path_field="" delivery_field="" nonce_field=""
1045
1086
  if [[ "$probe" == "true" ]]; then probe_field=',"probe":true'; fi
1087
+ if [[ -n "$artifact_path" ]]; then artifact_path_field=",\"artifactPath\":\"$artifact_path\""; fi
1046
1088
  if [[ -n "$delivery" ]]; then delivery_field=",\"delivery\":\"$delivery\""; fi
1047
1089
  if [[ -n "${AW_REVIEW_NONCE:-}" ]]; then nonce_field=",\"nonce\":\"${AW_REVIEW_NONCE}\""; fi
1048
- line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"%s,"posture":%s%s%s}' \
1090
+ line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"%s,"durationS":%s,"blocking":%s%s,"posture":%s%s%s}' \
1049
1091
  "$(receipt_json_scalar "$artifact")" "$fresh" "$(receipt_json_scalar "$fingerprint")" \
1050
1092
  "$AW_RECEIPT_BACKEND" "$verdict" "$grounded" "$(receipt_json_scalar "$facts_hash")" \
1051
- "$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field" "$(posture_json)" "$delivery_field" "$nonce_field")"
1093
+ "$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field" "$duration_s" "$blocking" "$artifact_path_field" "$(posture_json)" "$delivery_field" "$nonce_field")"
1052
1094
  if ! printf '%s\n' "$line" >>"$receipts" 2>/dev/null; then
1053
1095
  echo "warning: could not append the review receipt to $receipts — the review itself succeeded;" >&2
1054
1096
  echo " the review-state gate will read the current tree as un-receipted." >&2
@@ -1074,8 +1116,8 @@ parse_agy_verdict_line() { # $1 = captured-output file
1074
1116
  awk '/^### Verdict[[:space:]]*$/{flag=1; next} flag && NF {print; exit}' "$1" 2>/dev/null
1075
1117
  }
1076
1118
 
1077
- parse_agy_blocking_first_numbered() { # $1 = captured-output file
1078
- awk 'flag && /^### /{exit} /^### Blocking[[:space:]]*$/{flag=1; next} flag && /^[0-9]+[.)]/{print; exit}' "$1" 2>/dev/null
1119
+ parse_agy_blocking_numbered() { # $1 = captured-output file
1120
+ awk 'flag && /^### /{exit} /^### Blocking[[:space:]]*$/{flag=1; next} flag && /^[0-9]+[.)]/{print}' "$1" 2>/dev/null
1079
1121
  }
1080
1122
 
1081
1123
  # The repo file map is a FIXED cost that scales with REPO SIZE, not change size (measured 28,735
@@ -1569,6 +1611,7 @@ PLAN_CONTENT=""
1569
1611
  DIFF_CONTENT=""
1570
1612
  REVIEW_ARTIFACT=""
1571
1613
  REVIEW_FINGERPRINT=""
1614
+ REVIEW_ARTIFACT_PATH=""
1572
1615
  # D8/D8b: an agy `code` receipt SELF-DECLARES how the change set reached the model — `inline` when
1573
1616
  # the whole set rode ONE prompt (delivery proven BY CONSTRUCTION), `fed` when a chunked feed proved
1574
1617
  # it by echo. The kit's gate requires the field present and valid, never a particular value, and a
@@ -1586,6 +1629,7 @@ if [[ -z "$resume_mode" ]]; then
1586
1629
  if [[ ! -f "$target" ]]; then
1587
1630
  echo "error: $mode file '$target' not found." >&2; exit 2
1588
1631
  fi
1632
+ if ! REVIEW_ARTIFACT_PATH="$(normalize_artifact_path "$target")"; then exit 2; fi
1589
1633
  # Read the target NOW (before any cd) — its path is relative to the invocation cwd.
1590
1634
  if [[ "$mode" == "plan" ]]; then PLAN_CONTENT="$(cat -- "$target")"; else DIFF_CONTENT="$(cat -- "$target")"; fi
1591
1635
  # Plan/diff receipt identity: the artifact-file sha256 (informational-only for the tree checker).
@@ -1803,11 +1847,13 @@ else
1803
1847
  REVIEW_DELIVERY="inline"
1804
1848
  fi
1805
1849
 
1850
+ # Plan/diff carry the prohibition themselves: headless agy auto-denies read_file, and a denied
1851
+ # read ends the review without an answer.
1806
1852
  emit_artifact() {
1807
1853
  case "$mode" in
1808
1854
  code) echo "## The change set under review (assembled working-tree diff — repo-complete)"; assemble_code_diff ;;
1809
- plan) echo "## The implementation plan under review"; printf '%s\n' "$PLAN_CONTENT" ;;
1810
- diff) echo "## The diff under review"; printf '%s\n' "$DIFF_CONTENT" ;;
1855
+ plan) echo "The artifact below is complete and inline. Do not read any file, call any tool, or request one."; echo; echo "## The implementation plan under review"; printf '%s\n' "$PLAN_CONTENT" ;;
1856
+ diff) echo "The artifact below is complete and inline. Do not read any file, call any tool, or request one."; echo; echo "## The diff under review"; printf '%s\n' "$DIFF_CONTENT" ;;
1811
1857
  esac
1812
1858
  }
1813
1859
  emit_grounding() { # POSTURE + GUARD + FACTS + DECIDED + FOCUS
@@ -2061,6 +2107,7 @@ review_out_file="$staging/review-output"
2061
2107
  envelope_file="$staging/review-envelope"
2062
2108
  dispatch_flags=("${AGY_TRANSPORT_FLAGS[@]}")
2063
2109
  if (( ${#run_passthrough[@]} > 0 )); then dispatch_flags+=("${run_passthrough[@]}"); fi
2110
+ review_started_at="$(date +%s)"
2064
2111
  set +e
2065
2112
  if (( FED_MODE == 1 )); then
2066
2113
  run_fed_review
@@ -2119,8 +2166,9 @@ if [[ $rc -eq 0 ]]; then
2119
2166
  echo " the review; if it recurs, inspect the captured output for what the model produced." >&2
2120
2167
  exit 4
2121
2168
  fi
2169
+ blocking_items="$(parse_agy_blocking_numbered "$review_out_file")"
2122
2170
  if [[ "$verdict" == "SHIP" || "$verdict" == "SHIP WITH NITS" ]]; then
2123
- first_blocking_item="$(parse_agy_blocking_first_numbered "$review_out_file")"
2171
+ first_blocking_item="${blocking_items%%$'\n'*}"
2124
2172
  if [[ -n "$first_blocking_item" ]]; then
2125
2173
  verdict_line="$(parse_agy_verdict_line "$review_out_file")"
2126
2174
  echo "error: verdict-body contradiction — '### Verdict' reads '$verdict_line' while '### Blocking'" >&2
@@ -2129,11 +2177,14 @@ if [[ $rc -eq 0 ]]; then
2129
2177
  exit 4
2130
2178
  fi
2131
2179
  fi
2180
+ blocking_count=0
2181
+ if [[ -n "$blocking_items" ]]; then blocking_count=$(( $(printf '%s\n' "$blocking_items" | wc -l) )); fi
2182
+ review_duration_s=$(( $(date +%s) - review_started_at ))
2132
2183
  if [[ -n "$resume_mode" ]]; then
2133
2184
  # A continuation never re-embeds the current artifact (agy holds the ORIGINAL round server-side;
2134
2185
  # --facts is rejected above), so it cannot attest the folded tree: fresh:false, artifact /
2135
2186
  # fingerprint / factsHash null, grounded false — informational-only, ignored by the state gate.
2136
- write_review_receipt "" false "" "$verdict" false "" "$REVIEW_PROBE" "" "$review_out_file"
2187
+ write_review_receipt "" false "" "$verdict" false "" "$REVIEW_PROBE" "" "$review_out_file" "" "$review_duration_s" "$blocking_count"
2137
2188
  echo "notice: a continuation receipt is fresh:false (informational-only) — only a fresh grounded run" >&2
2138
2189
  echo " (agy-review code --facts @f) mints a receipt that satisfies the review-state gate." >&2
2139
2190
  else
@@ -2143,7 +2194,8 @@ if [[ $rc -eq 0 ]]; then
2143
2194
  grounded=true
2144
2195
  facts_hash="$(printf '%s' "$FACTS_CONTENT" | sha256_stdin || true)"
2145
2196
  fi
2146
- write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" "$grounded" "$facts_hash" "$REVIEW_PROBE" "$REVIEW_DELIVERY" "$review_out_file"
2197
+ write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" "$grounded" "$facts_hash" "$REVIEW_PROBE" "$REVIEW_DELIVERY" "$review_out_file" "$REVIEW_ARTIFACT_PATH" "$review_duration_s" "$blocking_count"
2147
2198
  fi
2199
+ echo "review duration: ${review_duration_s}s" >&2
2148
2200
  fi
2149
2201
  exit $rc
@@ -2,7 +2,7 @@ import { describe, it, after } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import {
4
4
  mkdtempSync, mkdirSync, writeFileSync, chmodSync, rmSync, readFileSync,
5
- existsSync, readdirSync, symlinkSync, cpSync,
5
+ existsSync, readdirSync, symlinkSync, cpSync, realpathSync,
6
6
  } from 'node:fs';
7
7
  import { tmpdir } from 'node:os';
8
8
  import { join, dirname, resolve } from 'node:path';
@@ -10,6 +10,7 @@ import { fileURLToPath } from 'node:url';
10
10
  import { spawnSync, execFile } from 'node:child_process';
11
11
 
12
12
  const HERE = dirname(fileURLToPath(import.meta.url));
13
+ const NO_FILE_READ = 'The artifact below is complete and inline. Do not read any file, call any tool, or request one.';
13
14
  import {
14
15
  WRAPPER, farmFor, makeSandbox, run, runAsync, readReceipts, RECEIPTS_REL,
15
16
  ARTIFACT_HEADER, SHAPE_HEADER, FED_CAP, FED_WIDE_CAP, MAX_COUNTABLE_PROOF_ADDRESS,
@@ -411,6 +412,25 @@ describe('agy-review.sh — guard + grounding (2, 3)', { concurrency: 2 }, () =>
411
412
  assert.match(r.prompt, /Do NOT comment on AI model names\/versions or your own knowledge cutoff/);
412
413
  });
413
414
 
415
+ it('plan and diff prepend the no-file-read instruction before the artifact; code mode stays unchanged', async () => {
416
+ // spec:plan-review-loop/S23
417
+ for (const [mode, file, header] of [['plan', 'p.md', '## The implementation plan under review'], ['diff', 'd.patch', '## The diff under review']]) {
418
+ const sb = makeSandbox();
419
+ writeFileSync(join(sb.repo, file), `${mode} body\n`);
420
+ const r = await run(sb, { args: [mode, file, '--facts', 'f'] });
421
+ rmSync(sb.home, { recursive: true, force: true });
422
+ assert.equal(r.status, 0, r.stderr);
423
+ assert.ok(r.prompt.indexOf(NO_FILE_READ) >= 0, `${mode}: instruction present`);
424
+ assert.ok(r.prompt.indexOf(NO_FILE_READ) < r.prompt.indexOf(header), `${mode}: instruction precedes artifact`);
425
+ }
426
+
427
+ const code = makeSandbox();
428
+ const r = await run(code, { args: ['code', '--facts', 'f'] });
429
+ rmSync(code.home, { recursive: true, force: true });
430
+ assert.equal(r.status, 0, r.stderr);
431
+ assert.doesNotMatch(r.prompt, new RegExp(NO_FILE_READ.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), 'code prompt is unchanged');
432
+ });
433
+
414
434
  it('--facts / --decided / --focus all reach the prompt', async () => {
415
435
  const sb = makeSandbox();
416
436
  const r = await run(sb, { args: [
@@ -2041,7 +2061,7 @@ describe('agy-review.sh — declared contract is really accepted (forward guard)
2041
2061
  // The normative fixture: the AD-038 shape + the D3 self-declaring probe marker (backend/verdict here
2042
2062
  // carry this bridge's vocabulary; dynamic values are asserted by shape):
2043
2063
  const RECEIPT_FIXTURE = JSON.parse(
2044
- '{"schema":1,"artifact":"code","fresh":true,"fingerprint":"<sha256hex>","backend":"codex","verdict":"revise","grounded":true,"factsHash":null,"wrapperVersion":"2.3.0","timestamp":"2026-07-03T12:00:00Z","probe":false,"posture":{"model":"<display>"},"delivery":"inline"}',
2064
+ '{"schema":1,"artifact":"code","fresh":true,"fingerprint":"<sha256hex>","backend":"codex","verdict":"revise","grounded":true,"factsHash":null,"wrapperVersion":"2.3.0","timestamp":"2026-07-03T12:00:00Z","probe":false,"durationS":0,"blocking":0,"posture":{"model":"<display>"},"delivery":"inline"}',
2045
2065
  );
2046
2066
  const sha256HexOf = async (buf) => {
2047
2067
  const { createHash } = await import('node:crypto');
@@ -2051,6 +2071,7 @@ const VERDICT_OUTPUT = '### Verdict\nSHIP WITH NITS — solid, two nits.\n### Bl
2051
2071
 
2052
2072
  describe('agy-review.sh — review receipts (AD-038)', { concurrency: 2 }, () => {
2053
2073
  it('a fresh grounded code review appends ONE fixture-shaped receipt (verdict verbatim, factsHash real)', async () => {
2074
+ // spec:plan-review-loop/S22
2054
2075
  const sb = makeSandbox();
2055
2076
  const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
2056
2077
  const receipts = readReceipts(sb.repo);
@@ -2069,6 +2090,11 @@ describe('agy-review.sh — review receipts (AD-038)', { concurrency: 2 }, () =>
2069
2090
  assert.equal(receipt.factsHash, await sha256HexOf('a tiny fact'), 'sha256 of the facts payload — an empty/changed facts file is visible');
2070
2091
  assert.equal(receipt.wrapperVersion, MANIFEST.version, 'receipt version ⟷ capability.json version');
2071
2092
  assert.match(receipt.timestamp, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/);
2093
+ assert.ok(Number.isInteger(receipt.durationS) && receipt.durationS >= 0);
2094
+ assert.equal(receipt.blocking, 0);
2095
+ assert.equal(Object.hasOwn(receipt, 'artifactPath'), false, 'a code receipt carries no artifact path');
2096
+ assert.match(r.stderr, /review duration: \d+s/);
2097
+ for (const field of ['durationS', 'blocking', 'artifactPath']) assert.ok(REVIEW_CONTRACT.receipt.includes(`${field} = `), `the manifest receipt contract declares ${field}`);
2072
2098
  });
2073
2099
 
2074
2100
  // The probe marker (BRIDGE-MODES-CATALOG, D3) — the twin of the sibling bridge's arm: an
@@ -2141,6 +2167,34 @@ describe('agy-review.sh — review receipts (AD-038)', { concurrency: 2 }, () =>
2141
2167
  }
2142
2168
  });
2143
2169
 
2170
+ it('a REWORK receipt counts every numbered item in the first Blocking section', async () => {
2171
+ const sb = makeSandbox();
2172
+ const output = '### Verdict\nREWORK\n### Blocking\n1. first\n2) second\n### Non-blocking\nnone';
2173
+ const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: output } });
2174
+ const receipts = readReceipts(sb.repo);
2175
+ rmSync(sb.home, { recursive: true, force: true });
2176
+ assert.equal(r.status, 0, r.stderr);
2177
+ assert.equal(receipts[0].blocking, 2);
2178
+ });
2179
+
2180
+ it('a large Blocking section is counted whole and the contradiction arm still exits 4 (no SIGPIPE)', async () => {
2181
+ const items = Array.from({ length: 5000 }, (_, i) => `${i + 1}. finding ${i + 1}`).join('\n');
2182
+ const rework = makeSandbox();
2183
+ const r = await run(rework, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: `### Verdict\nREWORK\n### Blocking\n${items}\n### Non-blocking\nnone` } });
2184
+ const receipts = readReceipts(rework.repo);
2185
+ rmSync(rework.home, { recursive: true, force: true });
2186
+ assert.equal(r.status, 0, r.stderr);
2187
+ assert.equal(receipts[0].blocking, 5000);
2188
+
2189
+ const ship = makeSandbox();
2190
+ const s = await run(ship, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: `### Verdict\nSHIP\n### Blocking\n${items}\n### Non-blocking\nnone` } });
2191
+ const shipReceipts = readReceipts(ship.repo);
2192
+ rmSync(ship.home, { recursive: true, force: true });
2193
+ assert.equal(s.status, 4, s.stderr);
2194
+ assert.match(s.stderr, /verdict-body contradiction/);
2195
+ assert.equal(shipReceipts.length, 0);
2196
+ });
2197
+
2144
2198
  // The wrapper-minted finding manifest (flow-orchestration Phase 4.2, Decision 2/P5/P24-25):
2145
2199
  // nonce-supplied dispatches mint {schema, backend, nonce, fingerprint, findings} beside the
2146
2200
  // receipt, atomic + no-clobber + ORDERED — a failed mint EXCLUDES the receipt append.
@@ -2363,6 +2417,21 @@ describe('agy-review.sh — review receipts (AD-038)', { concurrency: 2 }, () =>
2363
2417
  assert.equal(r.status, 0, r.stderr);
2364
2418
  assert.equal(receipts[0].artifact, 'plan');
2365
2419
  assert.equal(receipts[0].fingerprint, await sha256HexOf('# plan body\n'), 'plan fingerprint = file sha256');
2420
+ assert.equal(receipts[0].artifactPath, 'p.md', 'realpath inside the work tree normalizes repo-relative');
2421
+ });
2422
+
2423
+ it('plan and diff refuse an uncarriable artifact-path byte before agy runs', async () => {
2424
+ for (const mode of ['plan', 'diff']) {
2425
+ for (const [name, diagnostic] of [['bad"name.md', /double quote/], ['bad\\name.md', /backslash/], ['bad\nname.md', /control byte/]]) {
2426
+ const sb = makeSandbox();
2427
+ writeFileSync(join(sb.repo, name), '# artifact\n');
2428
+ const r = await run(sb, { args: [mode, name, '--facts', 'f'] });
2429
+ rmSync(sb.home, { recursive: true, force: true });
2430
+ assert.equal(r.status, 2, `${mode}: ${name}`);
2431
+ assert.match(r.stderr, diagnostic, `${mode}: ${name}`);
2432
+ assert.equal(r.invoked, false, `${mode}: ${name}: refusal is pre-spend`);
2433
+ }
2434
+ }
2366
2435
  });
2367
2436
 
2368
2437
  it('plan/diff outside a git work tree: warn + skip the receipt (exit 0) unless AW_REVIEW_RECEIPTS is set', async () => {
@@ -2382,10 +2451,12 @@ describe('agy-review.sh — review receipts (AD-038)', { concurrency: 2 }, () =>
2382
2451
  env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_RECEIPTS: override },
2383
2452
  });
2384
2453
  const body = existsSync(override) ? readFileSync(override, 'utf8') : '';
2454
+ const expectedPath = realpathSync(join(outside, 'p.md'));
2385
2455
  rmSync(sb.home, { recursive: true, force: true });
2386
2456
  assert.equal(written.status, 0, written.stderr);
2387
2457
  assert.match(body, /"backend":"agy"/, 'the override path receives the receipt outside a git tree');
2388
2458
  assert.match(body, /"artifact":"plan"/);
2459
+ assert.equal(JSON.parse(body).artifactPath, expectedPath, 'outside a work tree the normalized path is absolute');
2389
2460
  });
2390
2461
 
2391
2462
  it('a receipt write failure warns loudly but never fails the review (fail-safe direction)', async () => {
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "antigravity-cli-bridge",
5
5
  "kind": "execution-backend",
6
- "version": "5.4.0",
6
+ "version": "5.5.0",
7
7
  "provides": ["review", "probe"],
8
8
  "posture": { "model": "Gemini 3.7 Flash (High)" },
9
9
  "roles": {
@@ -31,7 +31,7 @@
31
31
  "agy-review --continue [--decided @f] [--focus \"…\"] [--nonce <n>]",
32
32
  "agy-review --conversation <id> [--decided @f] [--focus \"…\"] [--nonce <n>]"
33
33
  ],
34
- "receipt": "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model} (agy has no tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, an ATTESTING review with AGY_MODEL explicitly emptied refuses pre-spend, and a model string carrying control bytes refuses pre-spend in every mode; delivery = how the change set REACHED the model, currently emitted as 'inline' (the whole set rode one prompt — proven by construction) or 'fed' (a chunked feed whose per-part echo proof verified); REQUIRED on every agy code receipt and its ABSENCE is what stops a pre-fed-lane receipt attesting, while the gate accepts any well-formed declaration rather than a particular value; absent by construction on plan/diff/continuation receipts, which carry no change set; a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review",
34
+ "receipt": "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; durationS = integer wall-clock seconds from CLI start through verdict parsing and the wrapper prints review duration: <n>s; blocking = the count of numbered items in the first ### Blocking section; artifactPath = the normalized realpath on plan/diff receipts only (repo-relative inside the work tree, absolute otherwise), while a double quote, backslash or control byte refuses pre-spend because the receipt encoder cannot carry it; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model} (agy has no tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, an ATTESTING review with AGY_MODEL explicitly emptied refuses pre-spend, and a model string carrying control bytes refuses pre-spend in every mode; delivery = how the change set REACHED the model, currently emitted as 'inline' (the whole set rode one prompt — proven by construction) or 'fed' (a chunked feed whose per-part echo proof verified); REQUIRED on every agy code receipt and its ABSENCE is what stops a pre-fed-lane receipt attesting, while the gate accepts any well-formed declaration rather than a particular value; absent by construction on plan/diff/continuation receipts, which carry no change set; a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review",
35
35
  "notes": [
36
36
  "transport: every review dispatch drives the CLI in --output-format json (plus --disable-slash-commands) and the returned envelope is parsed in node (bin/agy-envelope.mjs) — the operator-facing invocations and flags above do NOT change, and on a ZERO exit the wrapper still PRINTS the review text, never JSON. A missing or unreadable envelope on a zero exit is a loud failure with NO receipt, never a downgraded verdict and never a fallback to raw-stdout parsing; a non-zero CLI exit keeps its own code and message, and publishes the captured stdout unchanged from the SINGLE dispatch or the FINAL fed turn (which may therefore be a JSON or partial payload — the envelope is parsed only on a zero exit); an INTERMEDIATE feed turn is the exception, its output stays private (Invariant E) and its failure prints only a named error. Enforced by a PRE-SPEND capability probe, not a version floor: agy --help must advertise --output-format and --disable-slash-commands, node must be >= 22, and bin/agy-envelope.mjs must be present — otherwise the review refuses before any run is spent and names the missing capability",
37
37
  "pre-dispatch host-diff: before the FIRST dispatch of this bridge, diff its declared networkHosts against the live sandbox allow-list — a missing host is surfaced to the maintainer BEFORE dispatching, never fired into a known prompt",
@@ -31,6 +31,9 @@ GUARD Do NOT comment on AI model names/versions or your own knowledge cutoff
31
31
  ## Focus
32
32
  {{FOCUS}} # from --focus "…" + any trailing focus words, merged in parse order (optional)
33
33
 
34
+ PLAN/DIFF ONLY The artifact below is complete and inline. Do not read any file, call any tool,
35
+ or request one.
36
+
34
37
  ## The change set / plan / diff under review
35
38
  {{ARTIFACT}} # code: the assembled, repo-complete working-tree change set (when oversized it
36
39
  # is not inlined at all — see the chunked feed below)
@@ -2,7 +2,7 @@
2
2
  name: codex-cli-bridge
3
3
  description: Delegate work to the OpenAI Codex CLI (`codex`) under a ChatGPT subscription — run plan/instruction EXECUTION in a sandboxed workspace, or get a read-only ADVISORY review of a plan or working-tree diff — as a second delegated-execution backend beside Antigravity. Use when the user wants to hand a bounded coding task or plan to `codex exec`, get a second-opinion review from codex, install or authenticate Codex CLI, understand its sandbox/network/approval policy, drive codex efficiently from the main agent (exec vs review, resume, the commit boundary), bridge project context (`AGENTS.md`) into codex, or troubleshoot codex flags, models, auth, or its no-TTY headless behaviour.
4
4
  metadata:
5
- version: '3.5.0'
5
+ version: '3.6.0'
6
6
  ---
7
7
 
8
8
  # codex-cli-bridge
@@ -124,6 +124,13 @@ is **banner-only** (exactly the duration handed to `timeout(1)`; on exec `uncapp
124
124
  capping binary, while `codex-review` **fails CLOSED pre-spend** there) — informational, never a
125
125
  receipt field. **Quote the posture banner verbatim** when labeling a dispatch.
126
126
 
127
+ Every successful review receipt carries integer `durationS` and `blocking`; the wrapper prints
128
+ `review duration: <n>s`. A plan receipt also carries `artifactPath`, normalized to a repo-relative
129
+ realpath inside the work tree and an absolute realpath otherwise. A double quote, backslash or
130
+ control byte in that path refuses pre-spend because the receipt encoder cannot carry it. Codex
131
+ counts `[blocker]` and `[major]` lines, or those two finding severities in schema mode — a schema
132
+ payload whose findings cannot be counted fails the run (exit 4, no receipt).
133
+
127
134
  `codex exec` is headless: there is **no TTY**, so `approval_policy=never` — anything needing
128
135
  escalation is refused and reported, never interactively approved. The wrappers capture only codex's
129
136
  **final message** (`-o`), so output is clean; the JSON event stream + reasoning go to a run trace