@sabaiway/agent-workflow-kit 10.4.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.
- package/CHANGELOG.md +55 -0
- package/README.md +1 -1
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +7 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +69 -17
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +73 -2
- package/bridges/antigravity-cli-bridge/capability.json +2 -2
- package/bridges/antigravity-cli-bridge/references/review-prompt.md +3 -0
- package/bridges/codex-cli-bridge/SKILL.md +8 -1
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +1 -1
- package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +1 -1
- package/bridges/codex-cli-bridge/bin/codex-review.sh +89 -18
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +55 -2
- package/bridges/codex-cli-bridge/capability.json +2 -2
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/agents/review-lens.md +5 -3
- package/references/modes/agents.md +1 -1
- package/references/modes/procedures.md +9 -5
- package/references/modes/recipes.md +2 -2
- package/references/modes/set-recipe.md +4 -4
- package/references/modes/status.md +1 -1
- package/references/modes/velocity.md +1 -0
- package/references/templates/orchestration.json +1 -1
- package/tools/bridge-posture.mjs +48 -0
- package/tools/carriers.mjs +21 -9
- package/tools/cheap-agents-read.mjs +86 -24
- package/tools/cheap-agents.mjs +47 -7
- package/tools/detect-backends.mjs +2 -2
- package/tools/direct-run.mjs +3 -0
- package/tools/fold-scope.mjs +5 -60
- package/tools/grounding.mjs +2 -2
- package/tools/orchestration-config.mjs +19 -78
- package/tools/orchestration-readme.mjs +70 -0
- package/tools/plan-shape-cli.mjs +112 -0
- package/tools/plan-shape-facts.mjs +204 -0
- package/tools/plan-shape.mjs +348 -0
- package/tools/procedures.mjs +132 -31
- package/tools/recipes.mjs +60 -79
- package/tools/repo-lex.mjs +40 -0
- package/tools/review-roster-resolve.mjs +104 -0
- package/tools/review-roster.mjs +128 -0
- package/tools/review-rounds-cli.mjs +92 -0
- package/tools/review-rounds.mjs +115 -0
- package/tools/set-recipe-roster.mjs +167 -0
- package/tools/set-recipe.mjs +80 -23
- package/tools/velocity-profile.mjs +8 -22
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,61 @@ 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
|
+
|
|
7
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)
|
|
8
63
|
|
|
9
64
|
The rule "design, then hand execution to a frontier subagent" lived in an agent's memory and in a
|
package/README.md
CHANGED
|
@@ -233,7 +233,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
|
|
|
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
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 (default Reviewed when a backend is ready, Council on request
|
|
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
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. |
|
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.
|
|
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.
|
|
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;
|
|
118
|
-
|
|
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
|
|
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.
|
|
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
|
|
1043
|
-
|
|
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
|
-
|
|
1078
|
-
awk 'flag && /^### /{exit} /^### Blocking[[:space:]]*$/{flag=1; next} flag && /^[0-9]+[.)]/{print
|
|
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="$
|
|
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.
|
|
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
|
+
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
|
|
@@ -372,7 +372,7 @@ CHATGPT_LOGIN_GUARD="Logged in using ChatGPT"
|
|
|
372
372
|
# receipt this wrapper mints; scripts/release/version-sync.mjs bumps it under the one-anchor-per-file
|
|
373
373
|
# rule, so a release can never leave it behind (the AD-053 drift class).
|
|
374
374
|
AW_RECEIPT_BACKEND="codex"
|
|
375
|
-
AW_BRIDGE_VERSION="3.
|
|
375
|
+
AW_BRIDGE_VERSION="3.6.0" # aw-version-anchor
|
|
376
376
|
# The kill grace handed to timeout(1) as --kill-after, and recorded in the receipt as killGraceS:
|
|
377
377
|
# ONE constant, so the number the ledger checks against the dispatch deadline is the number the run
|
|
378
378
|
# actually applied.
|
|
@@ -114,7 +114,7 @@ describe('codex-review — schema-mode verdict is parsed STRUCTURALLY (M2)', ()
|
|
|
114
114
|
|
|
115
115
|
it('a DECOY "verdict" inside a findings STRING never substitutes the top-level field', () => {
|
|
116
116
|
const sb = makeSandbox();
|
|
117
|
-
const decoy = '{"verdict":"revise","findings":["the text \\"verdict\\": \\"ship\\" appeared in a doc line"]}';
|
|
117
|
+
const decoy = '{"verdict":"revise","findings":[{"severity":"nit","location":"doc","issue":"the text \\"verdict\\": \\"ship\\" appeared in a doc line","suggested_change":"none"}]}';
|
|
118
118
|
const r = run(sb, { env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: decoy } });
|
|
119
119
|
const receipts = readReceipts(sb.repo);
|
|
120
120
|
rmSync(sb.root, { recursive: true, force: true });
|