@sabaiway/agent-workflow-kit 1.29.0 → 1.31.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 (43) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +7 -2
  3. package/SKILL.md +33 -519
  4. package/bridges/antigravity-cli-bridge/SKILL.md +1 -1
  5. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +145 -4
  6. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +169 -1
  7. package/bridges/antigravity-cli-bridge/capability.json +3 -2
  8. package/bridges/codex-cli-bridge/SKILL.md +1 -1
  9. package/bridges/codex-cli-bridge/bin/codex-review.sh +120 -3
  10. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +132 -0
  11. package/bridges/codex-cli-bridge/capability.json +3 -2
  12. package/capability.json +1 -1
  13. package/launchers/windsurf-workflow.md +4 -2
  14. package/package.json +1 -1
  15. package/references/modes/agents.md +11 -0
  16. package/references/modes/backends.md +9 -0
  17. package/references/modes/bootstrap.md +49 -0
  18. package/references/modes/gates.md +17 -0
  19. package/references/modes/grounding.md +12 -0
  20. package/references/modes/help.md +7 -0
  21. package/references/modes/hook.md +24 -0
  22. package/references/modes/procedures.md +18 -0
  23. package/references/modes/recipes.md +18 -0
  24. package/references/modes/review-state.md +13 -0
  25. package/references/modes/set-recipe.md +23 -0
  26. package/references/modes/setup.md +30 -0
  27. package/references/modes/status.md +22 -0
  28. package/references/modes/uninstall.md +18 -0
  29. package/references/modes/upgrade.md +46 -0
  30. package/references/modes/velocity.md +18 -0
  31. package/references/shared/composition-handoff.md +39 -0
  32. package/references/shared/deploy-tail.md +61 -0
  33. package/references/shared/report-footer.md +116 -0
  34. package/references/templates/agent_rules.md +3 -2
  35. package/references/templates/handover.md +1 -0
  36. package/tools/commands.mjs +15 -1
  37. package/tools/detect-backends.mjs +2 -0
  38. package/tools/engine-source.mjs +2 -1
  39. package/tools/grounding.mjs +263 -0
  40. package/tools/procedures.mjs +50 -5
  41. package/tools/recipes.mjs +78 -12
  42. package/tools/review-state.mjs +395 -0
  43. package/tools/set-recipe.mjs +15 -5
@@ -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 no-JSON headless behaviour.
4
4
  metadata:
5
- version: '2.1.0'
5
+ version: '2.2.0'
6
6
  ---
7
7
 
8
8
  # antigravity-cli-bridge
@@ -42,6 +42,8 @@
42
42
  # AGY_PROBE=1 throwaway probe — silences the off-frontier model advisory
43
43
  # AGY_REVIEW_ALLOW_ADDDIR=1 oversized CODE review: offload the change set to a private
44
44
  # staging dir and pass it via --add-dir (re-enables Issue-001 stall risk)
45
+ # AW_REVIEW_RECEIPTS override the review-receipt file (default: <git dir>/
46
+ # agent-workflow-review-receipts.jsonl — see the --help Receipt block)
45
47
  set -euo pipefail
46
48
 
47
49
  # --- --help / -h (pre-preflight: no agy, no login, no git tree needed) ---------
@@ -72,6 +74,18 @@ Round-2 / resume:
72
74
  agy-review --conversation <id> [--decided @f] [--focus "…"]
73
75
  (a continuation sends a small delta — agy holds the artifact server-side; --facts is invalid on a continuation)
74
76
 
77
+ Receipt:
78
+ side effect — a successful review appends one JSON receipt line to
79
+ <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside
80
+ a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical
81
+ uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the
82
+ review-payload domain) in code mode, the artifact-file sha256 in plan/diff mode; verdict
83
+ recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK);
84
+ grounded = whether a NON-EMPTY --facts payload was supplied (an empty payload records
85
+ grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts
86
+ payload; a continuation receipt is fresh:false (informational-only — it cannot attest the
87
+ folded tree); a write failure warns, never fails the review
88
+
75
89
  Closed grammar: unknown flags are rejected; no '--' passthrough (the only escapes are AGY_PROBE=1 and AGY_REVIEW_ALLOW_ADDDIR=1).
76
90
  Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
77
91
  HELP
@@ -80,6 +94,10 @@ HELP
80
94
  esac
81
95
 
82
96
  DEFAULT_AGY_REVIEW_MODEL="Gemini 3.1 Pro (High)"
97
+ # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
98
+ # version (drift-guarded by agy-review.test.mjs against capability.json).
99
+ AW_RECEIPT_BACKEND="agy"
100
+ AW_BRIDGE_VERSION="2.2.0"
83
101
  # `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
84
102
  AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
85
103
  # Frontier review models. ANY model is allowed; a sub-frontier one only earns a soft, silenceable warning.
@@ -181,6 +199,95 @@ is_binary() {
181
199
  [[ "${nul:-0}" -gt 0 ]]
182
200
  }
183
201
 
202
+ # --- Review receipts (AD-038) — byte-identical in codex-review.sh and agy-review.sh ---------------
203
+ # sha256 hex of stdin. sha256sum, else shasum -a 256; neither → warn + fail (the caller records a
204
+ # null fingerprint — a null never satisfies the review-state checker, fail-safe direction).
205
+ sha256_stdin() {
206
+ if command -v sha256sum >/dev/null 2>&1; then sha256sum | awk '{print $1}'
207
+ elif command -v shasum >/dev/null 2>&1; then shasum -a 256 | awk '{print $1}'
208
+ else
209
+ echo "warning: no sha256sum/shasum on PATH — cannot compute the review fingerprint." >&2
210
+ return 1
211
+ fi
212
+ }
213
+
214
+ # The canonical uncommitted-state fingerprint payload (code mode). Domain == the review payload:
215
+ # tracked staged + unstaged changes + untracked-not-ignored file contents (binary/symlink/non-regular
216
+ # untracked paths ride as name-only notes, mirroring the assembled change set). The prose definition
217
+ # lives in capability.json roles.review.contract.receipt (both bridges, lockstep); the kit checker
218
+ # (tools/review-state.mjs) implements the SAME serialization in node — cross-checked by the kit's
219
+ # review-fingerprint-parity.test.mjs.
220
+ emit_fingerprint_payload() {
221
+ git diff --cached --no-ext-diff
222
+ git diff --no-ext-diff
223
+ local path
224
+ while IFS= read -r -d '' path; do
225
+ if [[ -L "$path" ]]; then
226
+ printf 'untracked-symlink:%s -> %s\n' "$path" "$(readlink -- "$path" 2>/dev/null || echo '?')"
227
+ elif [[ ! -f "$path" ]]; then
228
+ printf 'untracked-nonregular:%s\n' "$path"
229
+ elif is_binary "$path"; then
230
+ printf 'untracked-binary:%s\n' "$path"
231
+ else
232
+ printf 'untracked:%s\n' "$path"
233
+ cat -- "$path"
234
+ fi
235
+ done < <(git ls-files --others --exclude-standard -z)
236
+ }
237
+
238
+ # sha256 of the canonical payload, emitted from the work-tree ROOT (a subdir invocation hashes the
239
+ # same bytes). Empty output on failure (no git tree / no sha256 tool) — recorded as null.
240
+ compute_tree_fingerprint() {
241
+ ( cd "$(git rev-parse --show-toplevel)" && emit_fingerprint_payload ) | sha256_stdin
242
+ }
243
+
244
+ # JSON-encode a receipt scalar: empty → null, else a quoted string (every value comes from a closed
245
+ # vocabulary or a hex digest — no escaping needed by construction).
246
+ receipt_json_scalar() {
247
+ if [[ -z "${1:-}" ]]; then printf 'null'; else printf '"%s"' "$1"; fi
248
+ }
249
+
250
+ # write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|"">
251
+ # Appends ONE receipt line (the AD-038 fixture shape) as a side effect of a SUCCESSFUL review —
252
+ # to $AW_REVIEW_RECEIPTS when set, else <git dir>/agent-workflow-review-receipts.jsonl (inside the
253
+ # git dir by construction, so it is never committable). Fail-safe: every failure here warns loudly
254
+ # and returns 0 — a missing receipt fails the kit's review-state CHECKER, never the review run.
255
+ write_review_receipt() {
256
+ local artifact="$1" fresh="$2" fingerprint="$3" verdict="$4" grounded="$5" facts_hash="$6"
257
+ local receipts="${AW_REVIEW_RECEIPTS:-}"
258
+ if [[ -z "$receipts" ]]; then
259
+ local receipt_git_dir
260
+ if ! receipt_git_dir="$(git rev-parse --absolute-git-dir 2>/dev/null)"; then
261
+ echo "warning: not inside a git work tree and AW_REVIEW_RECEIPTS is unset — skipping the review receipt." >&2
262
+ return 0
263
+ fi
264
+ receipts="$receipt_git_dir/agent-workflow-review-receipts.jsonl"
265
+ fi
266
+ local line
267
+ line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"}' \
268
+ "$(receipt_json_scalar "$artifact")" "$fresh" "$(receipt_json_scalar "$fingerprint")" \
269
+ "$AW_RECEIPT_BACKEND" "$verdict" "$grounded" "$(receipt_json_scalar "$facts_hash")" \
270
+ "$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)")"
271
+ if ! printf '%s\n' "$line" >>"$receipts" 2>/dev/null; then
272
+ echo "warning: could not append the review receipt to $receipts — the review itself succeeded;" >&2
273
+ echo " the review-state gate will read the current tree as un-receipted." >&2
274
+ fi
275
+ }
276
+
277
+ # Parse the mandated '### Verdict' section of a captured review: the first non-empty line after the
278
+ # heading, matched against the closed verdict vocabulary (SHIP WITH NITS before SHIP — substring).
279
+ # No heading / no match → "unknown" (recorded, never guessed).
280
+ parse_agy_verdict() { # $1 = captured-output file
281
+ local line
282
+ line="$(awk '/^### Verdict/{flag=1; next} flag && NF {print; exit}' "$1" 2>/dev/null)"
283
+ case "$line" in
284
+ *"SHIP WITH NITS"*) printf 'SHIP WITH NITS' ;;
285
+ *REWORK*) printf 'REWORK' ;;
286
+ *SHIP*) printf 'SHIP' ;;
287
+ *) printf 'unknown' ;;
288
+ esac
289
+ }
290
+
184
291
  # Emit the full code-review surface: repo map, status, staged + unstaged diffs, and the CONTENTS of
185
292
  # every untracked REGULAR file (NUL-safe iteration). Symlinks are shown as their target (never
186
293
  # followed — no out-of-repo leak); other non-regular paths (FIFO/socket/device/dir) are skipped.
@@ -246,6 +353,8 @@ mode=""
246
353
  target=""
247
354
  PLAN_CONTENT=""
248
355
  DIFF_CONTENT=""
356
+ REVIEW_ARTIFACT=""
357
+ REVIEW_FINGERPRINT=""
249
358
  if [[ -z "$resume_mode" ]]; then
250
359
  mode="${1:-}"; shift || true
251
360
  case "$mode" in
@@ -259,7 +368,10 @@ if [[ -z "$resume_mode" ]]; then
259
368
  echo "error: $mode file '$target' not found." >&2; exit 2
260
369
  fi
261
370
  # Read the target NOW (before any cd) — its path is relative to the invocation cwd.
262
- if [[ "$mode" == "plan" ]]; then PLAN_CONTENT="$(cat -- "$target")"; else DIFF_CONTENT="$(cat -- "$target")"; fi ;;
371
+ if [[ "$mode" == "plan" ]]; then PLAN_CONTENT="$(cat -- "$target")"; else DIFF_CONTENT="$(cat -- "$target")"; fi
372
+ # Plan/diff receipt identity: the artifact-file sha256 (informational-only for the tree checker).
373
+ REVIEW_ARTIFACT="$mode"
374
+ REVIEW_FINGERPRINT="$(sha256_stdin <"$target" || true)" ;;
263
375
  *)
264
376
  usage; exit 2 ;;
265
377
  esac
@@ -392,6 +504,10 @@ else
392
504
  echo "agy-review: no uncommitted changes to review — the working tree is clean." >&2
393
505
  exit 0
394
506
  fi
507
+ # The canonical fingerprint of the tree agy is about to review — computed at assembly time,
508
+ # so the receipt attests exactly the reviewed state.
509
+ REVIEW_ARTIFACT="code"
510
+ REVIEW_FINGERPRINT="$(compute_tree_fingerprint || true)"
395
511
  fi
396
512
 
397
513
  emit_artifact() {
@@ -468,14 +584,39 @@ else
468
584
  fi
469
585
 
470
586
  # --- Execute via agy-run (single home of timeout + subscription + byte ceiling) ---
587
+ # The output is teed into the private staging dir so the mandated '### Verdict' section can be
588
+ # parsed into the review receipt — the user-facing stream is unchanged.
589
+ review_out_file="$staging/review-output"
471
590
  set +e
472
591
  if (( ${#run_passthrough[@]} > 0 )); then
473
592
  AGY_MODEL="$AGY_MODEL" AGY_TIMEOUT="$AGY_TIMEOUT" AGY_HARD_TIMEOUT="$AGY_HARD_TIMEOUT" \
474
- "$AGY_RUN" "@$prompt_file" -- "${run_passthrough[@]}"
593
+ "$AGY_RUN" "@$prompt_file" -- "${run_passthrough[@]}" | tee "$review_out_file"
594
+ rc=${PIPESTATUS[0]}
475
595
  else
476
596
  AGY_MODEL="$AGY_MODEL" AGY_TIMEOUT="$AGY_TIMEOUT" AGY_HARD_TIMEOUT="$AGY_HARD_TIMEOUT" \
477
- "$AGY_RUN" "@$prompt_file"
597
+ "$AGY_RUN" "@$prompt_file" | tee "$review_out_file"
598
+ rc=${PIPESTATUS[0]}
478
599
  fi
479
- rc=$?
480
600
  set -e
601
+
602
+ # --- Review receipt (AD-038): only a SUCCESSFUL review attests --------------------
603
+ if [[ $rc -eq 0 ]]; then
604
+ verdict="$(parse_agy_verdict "$review_out_file")"
605
+ if [[ -n "$resume_mode" ]]; then
606
+ # A continuation never re-embeds the current artifact (agy holds the ORIGINAL round server-side;
607
+ # --facts is rejected above), so it cannot attest the folded tree: fresh:false, artifact /
608
+ # fingerprint / factsHash null, grounded false — informational-only, ignored by the state gate.
609
+ write_review_receipt "" false "" "$verdict" false ""
610
+ echo "notice: a continuation receipt is fresh:false (informational-only) — only a fresh grounded run" >&2
611
+ echo " (agy-review code --facts @f) mints a receipt that satisfies the review-state gate." >&2
612
+ else
613
+ grounded=false
614
+ facts_hash=""
615
+ if [[ -n "$FACTS_CONTENT" ]]; then
616
+ grounded=true
617
+ facts_hash="$(printf '%s' "$FACTS_CONTENT" | sha256_stdin || true)"
618
+ fi
619
+ write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" "$grounded" "$facts_hash"
620
+ fi
621
+ fi
481
622
  exit $rc
@@ -38,7 +38,7 @@ const FAKE_AGY = [
38
38
  ' fi; prev="$a"',
39
39
  'done',
40
40
  'if [[ -n "${AGY_FAKE_SLEEP:-}" ]]; then sleep "$AGY_FAKE_SLEEP"; fi',
41
- 'echo "FAKE_AGY_REVIEW_OUTPUT"',
41
+ 'printf "%s\\n" "${AGY_FAKE_OUTPUT:-FAKE_AGY_REVIEW_OUTPUT}"',
42
42
  'exit "${AGY_FAKE_EXIT:-0}"',
43
43
  '',
44
44
  ].join('\n');
@@ -553,6 +553,13 @@ describe('agy-review.sh — --help contract (manifest-pinned)', () => {
553
553
  assert.ok(REVIEW_CONTRACT.continue.length > 0, 'manifest continue must be non-empty');
554
554
  setEq(got, REVIEW_CONTRACT.continue.map(norm), 'help continue ⟷ manifest continue');
555
555
  });
556
+
557
+ it('Receipt renders the manifest receipt contract verbatim (AD-038 three-way lockstep)', () => {
558
+ const help = runHelp('--help').stdout;
559
+ assert.equal(norm(helpSection(help, 'Receipt:').join(' ')), norm(REVIEW_CONTRACT.receipt));
560
+ assert.match(REVIEW_CONTRACT.receipt, /sha256 over the canonical uncommitted-state payload/, 'the fingerprint definition lives in the manifest contract');
561
+ assert.match(REVIEW_CONTRACT.receipt, /fresh:false/, 'the continuation informational-only clause is contractual');
562
+ });
556
563
  });
557
564
 
558
565
  describe('agy-review.sh — source-level reverse guard (parser arms ⟷ manifest)', () => {
@@ -625,3 +632,164 @@ describe('agy-review.sh — declared contract is really accepted (forward guard)
625
632
  assert.doesNotMatch(r.stdout, /Usage:/);
626
633
  });
627
634
  });
635
+
636
+ // ── review receipts (AD-038) ─────────────────────────────────────────────────────
637
+ // The normative fixture (docs: the AD-038 plan Decisions — copied verbatim; backend/verdict here
638
+ // carry this bridge's vocabulary; dynamic values are asserted by shape):
639
+ const RECEIPT_FIXTURE = JSON.parse(
640
+ '{"schema":1,"artifact":"code","fresh":true,"fingerprint":"<sha256hex>","backend":"codex","verdict":"revise","grounded":true,"factsHash":null,"wrapperVersion":"2.2.0","timestamp":"2026-07-03T12:00:00Z"}',
641
+ );
642
+ const RECEIPTS_REL = join('.git', 'agent-workflow-review-receipts.jsonl');
643
+ const readReceipts = (repo) => {
644
+ const p = join(repo, RECEIPTS_REL);
645
+ if (!existsSync(p)) return [];
646
+ return readFileSync(p, 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
647
+ };
648
+ const sha256HexOf = async (buf) => {
649
+ const { createHash } = await import('node:crypto');
650
+ return createHash('sha256').update(buf).digest('hex');
651
+ };
652
+ const VERDICT_OUTPUT = '### Verdict\nSHIP WITH NITS — solid, two nits.\n### Blocking\nnone\n### Non-blocking\n1. nit\n### Questions\nnone';
653
+
654
+ describe('agy-review.sh — review receipts (AD-038)', () => {
655
+ it('a fresh grounded code review appends ONE fixture-shaped receipt (verdict verbatim, factsHash real)', async () => {
656
+ const sb = makeSandbox();
657
+ const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
658
+ const receipts = readReceipts(sb.repo);
659
+ rmSync(sb.home, { recursive: true, force: true });
660
+ assert.equal(r.status, 0, r.stderr);
661
+ assert.equal(receipts.length, 1, 'exactly one receipt line');
662
+ const receipt = receipts[0];
663
+ assert.deepEqual(Object.keys(receipt), Object.keys(RECEIPT_FIXTURE), 'fixture key set + order');
664
+ assert.equal(receipt.schema, 1);
665
+ assert.equal(receipt.artifact, 'code');
666
+ assert.equal(receipt.fresh, true);
667
+ assert.match(receipt.fingerprint, /^[0-9a-f]{64}$/, 'a real sha256 hex fingerprint');
668
+ assert.equal(receipt.backend, 'agy');
669
+ assert.equal(receipt.verdict, 'SHIP WITH NITS', 'the mandated ### Verdict section is recorded verbatim');
670
+ assert.equal(receipt.grounded, true, '--facts was supplied');
671
+ assert.equal(receipt.factsHash, await sha256HexOf('a tiny fact'), 'sha256 of the facts payload — an empty/changed facts file is visible');
672
+ assert.equal(receipt.wrapperVersion, MANIFEST.version, 'receipt version ⟷ capability.json version');
673
+ assert.match(receipt.timestamp, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/);
674
+ });
675
+
676
+ it('an ungrounded fresh run records grounded:false + factsHash null (the vacuous-grounding hole stays visible)', () => {
677
+ const sb = makeSandbox();
678
+ const r = run(sb, { args: ['code'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
679
+ const receipts = readReceipts(sb.repo);
680
+ rmSync(sb.home, { recursive: true, force: true });
681
+ assert.equal(r.status, 0, r.stderr);
682
+ assert.equal(receipts[0].grounded, false);
683
+ assert.equal(receipts[0].factsHash, null);
684
+ });
685
+
686
+ it('an EMPTY --facts file records grounded:false (fail-closed — vacuous grounding never satisfies the gate) and still warns', () => {
687
+ const sb = makeSandbox();
688
+ writeFileSync(join(sb.home, 'empty-facts.md'), '');
689
+ const r = run(sb, { args: ['code', '--facts', `@${join(sb.home, 'empty-facts.md')}`], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
690
+ const receipts = readReceipts(sb.repo);
691
+ rmSync(sb.home, { recursive: true, force: true });
692
+ assert.equal(r.status, 0, r.stderr);
693
+ assert.equal(receipts[0].grounded, false, 'an empty payload is recorded as ungrounded, never as grounded-by-flag');
694
+ assert.equal(receipts[0].factsHash, null);
695
+ assert.match(r.stderr, /no --facts supplied|ungrounded review GUESSES/, 'the ungrounded warning fires for an empty payload');
696
+ });
697
+
698
+ it('parses REWORK and plain SHIP; records "unknown" when the mandated section is absent', () => {
699
+ for (const [output, want] of [
700
+ ['### Verdict\nREWORK — the contract is violated.', 'REWORK'],
701
+ ['### Verdict\nSHIP — clean.', 'SHIP'],
702
+ ['free-form text with no verdict heading', 'unknown'],
703
+ ]) {
704
+ const sb = makeSandbox();
705
+ const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: output } });
706
+ const receipts = readReceipts(sb.repo);
707
+ rmSync(sb.home, { recursive: true, force: true });
708
+ assert.equal(r.status, 0, r.stderr);
709
+ assert.equal(receipts[0].verdict, want, `verdict for: ${output.slice(0, 30)}`);
710
+ }
711
+ });
712
+
713
+ it('a continuation receipt is fresh:false with null identity fields, and the wrapper prints the fresh-run notice', () => {
714
+ const sb = makeSandbox();
715
+ const r = run(sb, { args: ['--continue', '--decided', 'already folded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
716
+ const receipts = readReceipts(sb.repo);
717
+ rmSync(sb.home, { recursive: true, force: true });
718
+ assert.equal(r.status, 0, r.stderr);
719
+ assert.equal(receipts.length, 1);
720
+ const receipt = receipts[0];
721
+ assert.deepEqual(Object.keys(receipt), Object.keys(RECEIPT_FIXTURE), 'same fixture shape');
722
+ assert.equal(receipt.fresh, false, 'a continuation cannot attest the folded tree');
723
+ assert.equal(receipt.artifact, null);
724
+ assert.equal(receipt.fingerprint, null);
725
+ assert.equal(receipt.grounded, false);
726
+ assert.equal(receipt.factsHash, null);
727
+ assert.equal(receipt.verdict, 'SHIP WITH NITS', 'the round-2 verdict is still recorded (informational)');
728
+ assert.match(r.stderr, /fresh grounded run/, 'the one-line notice names the required fresh run');
729
+ assert.match(r.stderr, /review-state gate/);
730
+ });
731
+
732
+ it('plan mode: artifact "plan", fingerprint = the artifact-file sha256', async () => {
733
+ const sb = makeSandbox();
734
+ writeFileSync(join(sb.repo, 'p.md'), '# plan body\n');
735
+ const r = run(sb, { args: ['plan', 'p.md', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
736
+ const receipts = readReceipts(sb.repo);
737
+ rmSync(sb.home, { recursive: true, force: true });
738
+ assert.equal(r.status, 0, r.stderr);
739
+ assert.equal(receipts[0].artifact, 'plan');
740
+ assert.equal(receipts[0].fingerprint, await sha256HexOf('# plan body\n'), 'plan fingerprint = file sha256');
741
+ });
742
+
743
+ it('plan/diff outside a git work tree: warn + skip the receipt (exit 0) unless AW_REVIEW_RECEIPTS is set', () => {
744
+ const sb = makeSandbox();
745
+ const outside = join(sb.home, 'no-repo');
746
+ mkdirSync(outside, { recursive: true });
747
+ writeFileSync(join(outside, 'p.md'), '# plan outside git\n');
748
+
749
+ const skipped = run(sb, { args: ['plan', 'p.md', '--facts', 'f'], cwd: outside, env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
750
+ assert.equal(skipped.status, 0, skipped.stderr);
751
+ assert.match(skipped.stderr, /not inside a git work tree and AW_REVIEW_RECEIPTS is unset — skipping/);
752
+
753
+ const override = join(sb.home, 'receipts-override.jsonl');
754
+ const written = run(sb, {
755
+ args: ['plan', 'p.md', '--facts', 'f'],
756
+ cwd: outside,
757
+ env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_RECEIPTS: override },
758
+ });
759
+ const body = existsSync(override) ? readFileSync(override, 'utf8') : '';
760
+ rmSync(sb.home, { recursive: true, force: true });
761
+ assert.equal(written.status, 0, written.stderr);
762
+ assert.match(body, /"backend":"agy"/, 'the override path receives the receipt outside a git tree');
763
+ assert.match(body, /"artifact":"plan"/);
764
+ });
765
+
766
+ it('a receipt write failure warns loudly but never fails the review (fail-safe direction)', () => {
767
+ const sb = makeSandbox();
768
+ const r = run(sb, {
769
+ args: ['code', '--facts', 'f'],
770
+ env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_RECEIPTS: join(sb.home, 'no-such-dir', 'r.jsonl') },
771
+ });
772
+ rmSync(sb.home, { recursive: true, force: true });
773
+ assert.equal(r.status, 0, 'the review run itself succeeds');
774
+ assert.match(r.stderr, /could not append the review receipt/);
775
+ assert.match(r.stdout, /SHIP WITH NITS/, 'the findings still reach stdout');
776
+ });
777
+
778
+ it('a failed agy run writes NO receipt (only a successful review attests)', () => {
779
+ const sb = makeSandbox();
780
+ const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_EXIT: '7' } });
781
+ const receipts = readReceipts(sb.repo);
782
+ rmSync(sb.home, { recursive: true, force: true });
783
+ assert.notEqual(r.status, 0);
784
+ assert.equal(receipts.length, 0);
785
+ });
786
+
787
+ it('the clean-tree preflight exits before any receipt is written', () => {
788
+ const sb = makeSandbox({ clean: true });
789
+ const r = run(sb, { args: ['code', '--facts', 'f'] });
790
+ const receipts = readReceipts(sb.repo);
791
+ rmSync(sb.home, { recursive: true, force: true });
792
+ assert.equal(r.status, 0);
793
+ assert.equal(receipts.length, 0, 'no review ran — no receipt');
794
+ });
795
+ });
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "antigravity-cli-bridge",
5
5
  "kind": "execution-backend",
6
- "version": "2.1.0",
6
+ "version": "2.2.0",
7
7
  "provides": ["review", "probe"],
8
8
  "roles": {
9
9
  "review": {
@@ -27,7 +27,8 @@
27
27
  "continue": [
28
28
  "agy-review --continue [--decided @f] [--focus \"…\"]",
29
29
  "agy-review --conversation <id> [--decided @f] [--focus \"…\"]"
30
- ]
30
+ ],
31
+ "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) 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 (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); a write failure warns, never fails the review"
31
32
  }
32
33
  },
33
34
  "probe": { "cmd": "agy-run", "source": "bin/agy.sh", "output": "advisory" }
@@ -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: '2.1.0'
5
+ version: '2.2.0'
6
6
  ---
7
7
 
8
8
  # codex-cli-bridge
@@ -52,7 +52,16 @@ Grounding:
52
52
  Round-2 / resume:
53
53
  (none — one-shot; a follow-up review is a fresh run)
54
54
 
55
- Environment: CODEX_REVIEW_SCHEMA=1 (structured JSON findings), CODEX_HARD_TIMEOUT (seconds, default 1800), CODEX_PROBE=1 (throwaway probe only).
55
+ Receipt:
56
+ side effect — a successful review appends one JSON receipt line to
57
+ <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides): fingerprint =
58
+ sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff +
59
+ untracked-not-ignored contents — the review-payload domain) in code mode, the artifact-file
60
+ sha256 in plan mode; verdict parsed from the mandated literal verdict line (schema mode: the
61
+ verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge,
62
+ factsHash null); a write failure warns, never fails the review
63
+
64
+ Environment: CODEX_REVIEW_SCHEMA=1 (structured JSON findings), CODEX_HARD_TIMEOUT (seconds, default 1800), CODEX_PROBE=1 (throwaway probe only), AW_REVIEW_RECEIPTS (receipt file override).
56
65
  Requires at run time: the codex CLI on PATH, a ChatGPT-subscription login, a git work tree with a root AGENTS.md (--help needs none of these).
57
66
  HELP
58
67
  exit 0
@@ -61,6 +70,10 @@ esac
61
70
 
62
71
  DEFAULT_CODEX_MODEL="gpt-5.5"
63
72
  DEFAULT_CODEX_EFFORT="xhigh"
73
+ # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
74
+ # version (drift-guarded by codex-review.test.mjs against capability.json).
75
+ AW_RECEIPT_BACKEND="codex"
76
+ AW_BRIDGE_VERSION="2.2.0"
64
77
  CODEX_MODEL="${CODEX_MODEL:-$DEFAULT_CODEX_MODEL}"
65
78
  CODEX_EFFORT="${CODEX_EFFORT:-$DEFAULT_CODEX_EFFORT}"
66
79
  # Generous hard cap for a slow xhigh review (subscription latency varies).
@@ -140,8 +153,10 @@ if [[ "${CODEX_REVIEW_SCHEMA:-}" == "1" ]]; then
140
153
  OUTPUT_FORMAT_CODE="$_json_fmt"
141
154
  OUTPUT_FORMAT_PLAN="$_json_fmt"
142
155
  else
143
- OUTPUT_FORMAT_CODE="Output findings ONLY, one per line, as: [blocker|major|minor|nit] file:line issue — suggested fix. End with a one-line overall verdict (ship / revise / rethink)."
144
- OUTPUT_FORMAT_PLAN="Output findings ONLY, one per line, as: [blocker|major|minor|nit] location issue suggested change. End with a one-line overall verdict (ship / revise / rethink)."
156
+ # The verdict line is machine-parsed into the review receipt (AD-038) mandate ONE exact literal.
157
+ _verdict_fmt="End with EXACTLY ONE final verdict line in this literal, machine-parsed form (that word pair alone on the line, nothing else): 'Verdict: ship' or 'Verdict: revise' or 'Verdict: rethink'."
158
+ OUTPUT_FORMAT_CODE="Output findings ONLY, one per line, as: [blocker|major|minor|nit] — file:line — issue — suggested fix. ${_verdict_fmt}"
159
+ OUTPUT_FORMAT_PLAN="Output findings ONLY, one per line, as: [blocker|major|minor|nit] — location — issue — suggested change. ${_verdict_fmt}"
145
160
  fi
146
161
 
147
162
  # True (exit 0) when $1 looks BINARY: a NUL byte in the first 8 KiB (git's own
@@ -153,6 +168,81 @@ is_binary() {
153
168
  [[ "${nul:-0}" -gt 0 ]]
154
169
  }
155
170
 
171
+ # --- Review receipts (AD-038) — byte-identical in codex-review.sh and agy-review.sh ---------------
172
+ # sha256 hex of stdin. sha256sum, else shasum -a 256; neither → warn + fail (the caller records a
173
+ # null fingerprint — a null never satisfies the review-state checker, fail-safe direction).
174
+ sha256_stdin() {
175
+ if command -v sha256sum >/dev/null 2>&1; then sha256sum | awk '{print $1}'
176
+ elif command -v shasum >/dev/null 2>&1; then shasum -a 256 | awk '{print $1}'
177
+ else
178
+ echo "warning: no sha256sum/shasum on PATH — cannot compute the review fingerprint." >&2
179
+ return 1
180
+ fi
181
+ }
182
+
183
+ # The canonical uncommitted-state fingerprint payload (code mode). Domain == the review payload:
184
+ # tracked staged + unstaged changes + untracked-not-ignored file contents (binary/symlink/non-regular
185
+ # untracked paths ride as name-only notes, mirroring the assembled change set). The prose definition
186
+ # lives in capability.json roles.review.contract.receipt (both bridges, lockstep); the kit checker
187
+ # (tools/review-state.mjs) implements the SAME serialization in node — cross-checked by the kit's
188
+ # review-fingerprint-parity.test.mjs.
189
+ emit_fingerprint_payload() {
190
+ git diff --cached --no-ext-diff
191
+ git diff --no-ext-diff
192
+ local path
193
+ while IFS= read -r -d '' path; do
194
+ if [[ -L "$path" ]]; then
195
+ printf 'untracked-symlink:%s -> %s\n' "$path" "$(readlink -- "$path" 2>/dev/null || echo '?')"
196
+ elif [[ ! -f "$path" ]]; then
197
+ printf 'untracked-nonregular:%s\n' "$path"
198
+ elif is_binary "$path"; then
199
+ printf 'untracked-binary:%s\n' "$path"
200
+ else
201
+ printf 'untracked:%s\n' "$path"
202
+ cat -- "$path"
203
+ fi
204
+ done < <(git ls-files --others --exclude-standard -z)
205
+ }
206
+
207
+ # sha256 of the canonical payload, emitted from the work-tree ROOT (a subdir invocation hashes the
208
+ # same bytes). Empty output on failure (no git tree / no sha256 tool) — recorded as null.
209
+ compute_tree_fingerprint() {
210
+ ( cd "$(git rev-parse --show-toplevel)" && emit_fingerprint_payload ) | sha256_stdin
211
+ }
212
+
213
+ # JSON-encode a receipt scalar: empty → null, else a quoted string (every value comes from a closed
214
+ # vocabulary or a hex digest — no escaping needed by construction).
215
+ receipt_json_scalar() {
216
+ if [[ -z "${1:-}" ]]; then printf 'null'; else printf '"%s"' "$1"; fi
217
+ }
218
+
219
+ # write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|"">
220
+ # Appends ONE receipt line (the AD-038 fixture shape) as a side effect of a SUCCESSFUL review —
221
+ # to $AW_REVIEW_RECEIPTS when set, else <git dir>/agent-workflow-review-receipts.jsonl (inside the
222
+ # git dir by construction, so it is never committable). Fail-safe: every failure here warns loudly
223
+ # and returns 0 — a missing receipt fails the kit's review-state CHECKER, never the review run.
224
+ write_review_receipt() {
225
+ local artifact="$1" fresh="$2" fingerprint="$3" verdict="$4" grounded="$5" facts_hash="$6"
226
+ local receipts="${AW_REVIEW_RECEIPTS:-}"
227
+ if [[ -z "$receipts" ]]; then
228
+ local receipt_git_dir
229
+ if ! receipt_git_dir="$(git rev-parse --absolute-git-dir 2>/dev/null)"; then
230
+ echo "warning: not inside a git work tree and AW_REVIEW_RECEIPTS is unset — skipping the review receipt." >&2
231
+ return 0
232
+ fi
233
+ receipts="$receipt_git_dir/agent-workflow-review-receipts.jsonl"
234
+ fi
235
+ local line
236
+ line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"}' \
237
+ "$(receipt_json_scalar "$artifact")" "$fresh" "$(receipt_json_scalar "$fingerprint")" \
238
+ "$AW_RECEIPT_BACKEND" "$verdict" "$grounded" "$(receipt_json_scalar "$facts_hash")" \
239
+ "$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)")"
240
+ if ! printf '%s\n' "$line" >>"$receipts" 2>/dev/null; then
241
+ echo "warning: could not append the review receipt to $receipts — the review itself succeeded;" >&2
242
+ echo " the review-state gate will read the current tree as un-receipted." >&2
243
+ fi
244
+ }
245
+
156
246
  # Emit the full review surface to stdout: repo map, status, staged + unstaged
157
247
  # diffs, and the CONTENTS of every untracked REGULAR file (NUL-safe iteration).
158
248
  # Symlinks are shown as their target (never followed — no out-of-repo leak); other
@@ -191,6 +281,9 @@ assemble_code_diff() {
191
281
  mode="${1:-}"
192
282
  shift || true
193
283
 
284
+ REVIEW_ARTIFACT=""
285
+ REVIEW_FINGERPRINT=""
286
+
194
287
  case "$mode" in
195
288
  plan)
196
289
  target="${1:-}"
@@ -203,6 +296,9 @@ case "$mode" in
203
296
  echo "error: unexpected arguments after plan file: $*" >&2
204
297
  exit 2
205
298
  fi
299
+ # Plan-mode receipt identity: the artifact-file sha256 (informational-only for the tree checker).
300
+ REVIEW_ARTIFACT="plan"
301
+ REVIEW_FINGERPRINT="$(sha256_stdin <"$target" || true)"
206
302
  fence_plan="Do not read files outside this git working tree; the plan above plus the in-repo code it references are your whole surface."
207
303
  directive="You are REVIEWING an implementation plan — ADVISORY ONLY. You are in a read-only sandbox: do NOT edit, create, or delete any file, and do NOT rewrite the plan. Obey the project's Hard Constraints from its root AGENTS.md (already merged into your context). Read the plan below and the relevant repository code it references. ${fence_plan} ${OUTPUT_FORMAT_PLAN} Cover: correctness risks, missing or mis-ordered steps, ambiguities a cold executor would trip on, violated project Hard Constraints, scope creep, and missing verification/gates."
208
304
  prompt="${directive}"$'\n\nPLAN:\n'"$(cat -- "$target")"
@@ -214,6 +310,10 @@ case "$mode" in
214
310
  echo "codex-review: no uncommitted changes to review — the working tree is clean." >&2
215
311
  exit 0
216
312
  fi
313
+ # The canonical fingerprint of the tree codex is about to review — computed at assembly time
314
+ # (BEFORE the EXIT trap can fire), so the receipt attests exactly the reviewed state.
315
+ REVIEW_ARTIFACT="code"
316
+ REVIEW_FINGERPRINT="$(compute_tree_fingerprint || true)"
217
317
  # Assemble DIRECTLY to a git-dir-local temp file (git-invisible + worktree /
218
318
  # submodule safe; 600 perms). Measuring size from the file keeps the whole
219
319
  # change set off the bash-variable path (no NUL drop, byte-accurate size).
@@ -370,3 +470,20 @@ else
370
470
  echo "warning: codex produced no final-message file — printing the run-trace tail instead." >&2
371
471
  tail -n 40 "$trace"
372
472
  fi
473
+
474
+ # --- Review receipt (AD-038): parse the verdict, append one receipt line ------
475
+ # Text mode parses the mandated literal 'Verdict: <ship|revise|rethink>' line; CODEX_REVIEW_SCHEMA=1
476
+ # reads the schema's "verdict" field instead; absent either way → "unknown" (recorded, never guessed).
477
+ verdict=""
478
+ if [[ -f "$out" && -s "$out" ]]; then
479
+ if [[ "${CODEX_REVIEW_SCHEMA:-}" == "1" ]]; then
480
+ verdict="$(sed -nE 's/.*"verdict"[[:space:]]*:[[:space:]]*"(ship|revise|rethink)".*/\1/p' "$out" | tail -n1)"
481
+ else
482
+ verdict="$(sed -nE 's/^Verdict: (ship|revise|rethink)[[:space:]]*$/\1/p' "$out" | tail -n1)"
483
+ fi
484
+ fi
485
+ [[ -z "$verdict" ]] && verdict="unknown"
486
+ # codex is grounded by construction (AGENTS.md auto-merge + the precomputed change set): grounded
487
+ # true, factsHash null (native grounding — no separate facts payload exists). Every codex run is a
488
+ # full fresh run (one-shot, no resume) → fresh:true.
489
+ write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" true ""