@sabaiway/agent-workflow-kit 2.0.0 → 3.0.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 (102) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +6 -5
  3. package/SKILL.md +12 -8
  4. package/bin/install.mjs +1 -1
  5. package/bridges/antigravity-cli-bridge/SKILL.md +16 -4
  6. package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +150 -0
  7. package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +76 -0
  8. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +131 -23
  9. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +193 -15
  10. package/bridges/antigravity-cli-bridge/capability.json +12 -8
  11. package/bridges/antigravity-cli-bridge/references/driving-agy.md +6 -2
  12. package/bridges/antigravity-cli-bridge/references/models-and-flags.md +3 -2
  13. package/bridges/antigravity-cli-bridge/references/review-prompt.md +1 -1
  14. package/bridges/codex-cli-bridge/SKILL.md +8 -1
  15. package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +143 -0
  16. package/bridges/codex-cli-bridge/bin/codex-review.sh +71 -10
  17. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +93 -10
  18. package/bridges/codex-cli-bridge/capability.json +3 -2
  19. package/capability.json +1 -1
  20. package/migrations/3.0.0-hardened-core-loop.md +44 -0
  21. package/migrations/README.md +1 -1
  22. package/package.json +2 -2
  23. package/references/hooks/gate-approve.mjs +1 -1
  24. package/references/modes/bootstrap.md +3 -3
  25. package/references/modes/commit-guard.md +19 -0
  26. package/references/modes/core-evidence.md +15 -0
  27. package/references/modes/coverage-check.md +12 -0
  28. package/references/modes/doc-parity.md +7 -6
  29. package/references/modes/gates.md +7 -6
  30. package/references/modes/grounding.md +1 -2
  31. package/references/modes/hook.md +1 -1
  32. package/references/modes/review-state.md +3 -3
  33. package/references/modes/upgrade.md +6 -6
  34. package/references/modes/velocity.md +2 -2
  35. package/references/scripts/archive-decisions.mjs +1 -1
  36. package/references/scripts/install-git-hooks-repo-exec.test.mjs +82 -0
  37. package/references/scripts/install-git-hooks.mjs +90 -18
  38. package/references/scripts/install-git-hooks.test.mjs +102 -0
  39. package/references/scripts/migrate-gates-branches.test.mjs +157 -0
  40. package/references/scripts/migrate-gates.mjs +395 -0
  41. package/references/scripts/migrate-gates.test.mjs +284 -0
  42. package/references/templates/agent_rules.md +2 -2
  43. package/tools/ack-write.mjs +1 -1
  44. package/tools/atomic-write.mjs +2 -2
  45. package/tools/autonomy-config.mjs +1 -1
  46. package/tools/autonomy-doctor.mjs +1 -1
  47. package/tools/autonomy-write.mjs +1 -1
  48. package/tools/bridge-settings-read.mjs +1 -1
  49. package/tools/bridge-settings.mjs +1 -1
  50. package/tools/changed-surface.mjs +8 -69
  51. package/tools/cheap-agents.mjs +2 -2
  52. package/tools/commands.mjs +17 -10
  53. package/tools/commit-guard.mjs +167 -0
  54. package/tools/core-evidence.mjs +914 -0
  55. package/tools/coverage-check.mjs +260 -0
  56. package/tools/delegation.mjs +1 -1
  57. package/tools/detect-backends.mjs +7 -6
  58. package/tools/doc-parity.mjs +11 -27
  59. package/tools/engine-source.mjs +1 -1
  60. package/tools/family-members.mjs +1 -1
  61. package/tools/family-registry.mjs +1 -1
  62. package/tools/fs-safe.mjs +1 -1
  63. package/tools/gate-hook.mjs +1 -1
  64. package/tools/{seed-gates.mjs → gates-init.mjs} +58 -52
  65. package/tools/grounding.mjs +6 -83
  66. package/tools/hide-footprint.mjs +1 -1
  67. package/tools/inject-methodology.mjs +1 -1
  68. package/tools/known-footprint.mjs +1 -1
  69. package/tools/labels.mjs +1 -1
  70. package/tools/lcov.mjs +6 -10
  71. package/tools/lens-region.mjs +1 -1
  72. package/tools/manifest/validate.mjs +19 -0
  73. package/tools/migrate-adr-store.mjs +2 -2
  74. package/tools/orchestration-config.mjs +1 -1
  75. package/tools/orchestration-write.mjs +1 -1
  76. package/tools/presentation.mjs +1 -1
  77. package/tools/procedures.mjs +2 -2
  78. package/tools/recipes.mjs +60 -9
  79. package/tools/recommendations.mjs +63 -7
  80. package/tools/release-scan.mjs +1 -1
  81. package/tools/renderers.mjs +1 -1
  82. package/tools/review-state.mjs +288 -340
  83. package/tools/run-gates.mjs +216 -92
  84. package/tools/sandbox-masks.mjs +1 -1
  85. package/tools/semver-lite.mjs +1 -1
  86. package/tools/set-autonomy.mjs +1 -1
  87. package/tools/set-recipe.mjs +1 -1
  88. package/tools/setup-backends.mjs +1 -1
  89. package/tools/surface.mjs +1 -1
  90. package/tools/uninstall.mjs +1 -1
  91. package/tools/velocity-profile.mjs +2 -2
  92. package/tools/view-model.mjs +1 -1
  93. package/references/modes/fold-completeness.md +0 -30
  94. package/references/modes/review-ledger.md +0 -34
  95. package/references/templates/verification-profile.json +0 -10
  96. package/tools/fold-completeness-run.mjs +0 -1120
  97. package/tools/fold-completeness.mjs +0 -672
  98. package/tools/review-ledger-core.mjs +0 -428
  99. package/tools/review-ledger-write.mjs +0 -647
  100. package/tools/review-ledger.mjs +0 -630
  101. package/tools/sarif.mjs +0 -52
  102. package/tools/verification-profile.mjs +0 -219
@@ -14,7 +14,8 @@
14
14
  # 1. POSTURE read-only second-opinion reviewer; findings only, no edits/commits
15
15
  # 2. GUARD do NOT opine on AI model names/versions or your knowledge cutoff
16
16
  # 3. FACTS "## Grounded facts — review AGAINST these, do NOT guess the code"
17
- # (omitted -> a one-line note in-prompt + a LOUD stderr warning)
17
+ # (code mode REFUSES pre-spend without a non-empty payload escapes:
18
+ # --ungrounded / AGY_PROBE=1; plan/diff omitted -> a LOUD stderr warning)
18
19
  # 4. DECIDED "## Decisions already made / already addressed — do NOT re-raise"
19
20
  # (optional; the anti-circling lever — the round-2 payload)
20
21
  # 5. FOCUS the merged --focus / trailing focus text (optional)
@@ -27,7 +28,7 @@
27
28
  # facts (--facts), the already-decided list (--decided), and the focus (--focus).
28
29
  #
29
30
  # Usage (installed on PATH as `agy-review`):
30
- # agy-review code [--facts @f] [--decided @f] [--focus "…"] [extra focus…]
31
+ # agy-review code [--facts @f] [--ungrounded] [--decided @f] [--focus "…"] [extra focus…]
31
32
  # agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus "…"]
32
33
  # agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus "…"]
33
34
  # agy-review --continue [--decided @f] [--focus "…"] # round-2 delta (no mode, no re-assembly)
@@ -55,19 +56,22 @@ case "${1:-}" in
55
56
  agy-review — grounded read-only ADVISORY review by Google's Antigravity CLI (agy; subscription-only).
56
57
 
57
58
  Usage:
58
- agy-review code [--facts @f] [--decided @f] [--focus "…"] [extra focus…]
59
+ agy-review code [--facts @f] [--ungrounded] [--decided @f] [--focus "…"] [extra focus…]
59
60
  agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus "…"]
60
61
  agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus "…"]
61
62
 
62
63
  Flags:
63
- --facts @f — verified facts the review runs AGAINST (omit loud ungrounded-review warning)
64
+ --facts @f — verified facts the review runs AGAINST (code mode REQUIRES a non-empty payload; plan/diff warn loudly when omitted)
65
+ --ungrounded — deliberately ungrounded CODE review, a throwaway opinion (code mode only, contradicts --facts; the receipt records grounded:false and never attests)
64
66
  --decided @f — already-decided / already-addressed list; do NOT re-raise (anti-circling; the round-2 payload)
65
67
  --focus "…" — extra focus (repeatable; code mode also takes trailing focus words)
66
68
 
67
69
  Grounding:
68
70
  grounded review — agy reads NOTHING by default, an ungrounded review GUESSES:
69
71
  --facts @f = the verified facts to review AGAINST; --decided @f = decisions
70
- already made, do NOT re-raise (anti-circling)
72
+ already made, do NOT re-raise (anti-circling). code mode REQUIRES a non-empty
73
+ --facts payload and refuses BEFORE spending a run (escapes: --ungrounded,
74
+ AGY_PROBE=1); plan/diff proceed with a loud warning
71
75
 
72
76
  Notes:
73
77
  pre-dispatch host-diff: before the FIRST dispatch of this bridge, diff its declared networkHosts
@@ -88,20 +92,35 @@ Receipt:
88
92
  sockets — are excluded from the domain entirely, untracked symlinks/directories ride as
89
93
  name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; verdict
90
94
  recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK);
91
- grounded = whether a NON-EMPTY --facts payload was supplied (an empty payload records
95
+ grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without
96
+ one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records
92
97
  grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts
93
98
  payload; a continuation receipt is fresh:false (informational-only — it cannot attest the
94
99
  folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY
95
100
  receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe
96
- review never attests) and equally rejects an unmarked one (silence is not a declaration); a write
97
- failure warns, never fails the review
101
+ review never attests) and equally rejects an unmarked one (silence is not a declaration);
102
+ posture = the ACTUAL run posture {model} (agy has no tier), written on EVERY receipt (D5) — the
103
+ gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the
104
+ review), one stderr banner line states the same posture, an ATTESTING review with AGY_MODEL
105
+ explicitly emptied refuses pre-spend, and a model string carrying control bytes refuses
106
+ pre-spend in every mode; a run whose output carries NO recognized '### Verdict' section — empty
107
+ output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session
108
+ error); a write failure warns, never fails the review
98
109
 
99
110
  Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins over built-in default):
100
111
  ${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf
101
112
  AGY_HARD_TIMEOUT — hard wall-clock cap, duration string like 5m/30m/90s (built-in default 30m)
102
113
  AGY_REVIEW_ALLOW_ADDDIR — boolean 0/1: 1 arms the oversized --add-dir escape (re-enables the Issue-001 stall risk; default 0)
103
114
 
104
- Closed grammar: unknown flags are rejected; no '--' passthrough (the only escapes are AGY_PROBE=1 and AGY_REVIEW_ALLOW_ADDDIR=1).
115
+ Honesty + posture (D4/D5):
116
+ a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4
117
+ with NO receipt: a FAILED review to RE-RUN, never a fatal session error. One stderr banner line
118
+ states the ACTUAL run posture (review posture: model=…) and the receipt records the same
119
+ posture {model} (agy has no tier). An ATTESTING review with AGY_MODEL explicitly emptied
120
+ refuses pre-spend (the actual model would be unknowable; AGY_PROBE=1 is exempt), and a model
121
+ string carrying control bytes refuses pre-spend in every mode.
122
+
123
+ Closed grammar: unknown flags are rejected; no '--' passthrough (the flag escape is --ungrounded; the env escapes are AGY_PROBE=1 and AGY_REVIEW_ALLOW_ADDDIR=1).
105
124
  Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
106
125
  HELP
107
126
  exit 0
@@ -220,9 +239,15 @@ DEFAULT_AGY_REVIEW_MODEL="Gemini 3.1 Pro (High)"
220
239
  # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
221
240
  # version (drift-guarded by agy-review.test.mjs against capability.json).
222
241
  AW_RECEIPT_BACKEND="agy"
223
- AW_BRIDGE_VERSION="2.7.0"
242
+ AW_BRIDGE_VERSION="4.0.0"
224
243
  # `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
225
244
  AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
245
+ # D5 control-byte screen — IMMEDIATELY after resolution, BEFORE the off-frontier advisory (or any
246
+ # other interpolation) can echo raw newline/ESC bytes into stderr/the terminal (round-2 fold).
247
+ if [[ "$AGY_MODEL" == *[$'\x01'-$'\x1f']* ]]; then
248
+ echo "error: AGY_MODEL contains control bytes — fix the setting (env or bridge-settings.conf) and re-run." >&2
249
+ exit 2
250
+ fi
226
251
  # Frontier review models. ANY model is allowed; a sub-frontier one only earns a soft, silenceable warning.
227
252
  FRONTIER_SET=("Gemini 3.1 Pro (High)" "Claude Opus 4.6 (Thinking)" "Claude Sonnet 4.6 (Thinking)")
228
253
 
@@ -439,6 +464,26 @@ receipt_json_scalar() {
439
464
  if [[ -z "${1:-}" ]]; then printf 'null'; else printf '"%s"' "$1"; fi
440
465
  }
441
466
 
467
+ # STRICT JSON string encoding for the ONE free-form receipt field (the posture model display
468
+ # string): backslash then double-quote escaped. Control bytes never reach here — the posture
469
+ # resolution refuses them pre-spend (D5), so these two escapes make the encoding total.
470
+ json_string_escape() {
471
+ local s="${1//\\/\\\\}"
472
+ s="${s//\"/\\\"}"
473
+ printf '%s' "$s"
474
+ }
475
+
476
+ # The D5 posture object this wrapper writes into EVERY receipt (agy has no tier — the label is
477
+ # the model display string). An unknowable model (explicitly emptied on an exempt probe run) is
478
+ # recorded null, never guessed.
479
+ posture_json() {
480
+ if [[ -z "${AGY_MODEL:-}" ]]; then
481
+ printf '{"model":null}'
482
+ else
483
+ printf '{"model":"%s"}' "$(json_string_escape "$AGY_MODEL")"
484
+ fi
485
+ }
486
+
442
487
  # write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|""> [probe: true|false]
443
488
  # Appends ONE receipt line (the AD-038 fixture shape) as a side effect of a SUCCESSFUL review —
444
489
  # to $AW_REVIEW_RECEIPTS when set, else <git dir>/agent-workflow-review-receipts.jsonl (inside the
@@ -462,28 +507,29 @@ write_review_receipt() {
462
507
  fi
463
508
  local line probe_field=',"probe":false'
464
509
  if [[ "$probe" == "true" ]]; then probe_field=',"probe":true'; fi
465
- line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"%s}' \
510
+ 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}' \
466
511
  "$(receipt_json_scalar "$artifact")" "$fresh" "$(receipt_json_scalar "$fingerprint")" \
467
512
  "$AW_RECEIPT_BACKEND" "$verdict" "$grounded" "$(receipt_json_scalar "$facts_hash")" \
468
- "$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field")"
513
+ "$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field" "$(posture_json)")"
469
514
  if ! printf '%s\n' "$line" >>"$receipts" 2>/dev/null; then
470
515
  echo "warning: could not append the review receipt to $receipts — the review itself succeeded;" >&2
471
516
  echo " the review-state gate will read the current tree as un-receipted." >&2
472
517
  fi
473
518
  }
474
519
 
475
- # Parse the mandated '### Verdict' section of a captured review: the first non-empty line after the
476
- # heading, matched against the closed verdict vocabulary (SHIP WITH NITS before SHIP substring).
477
- # No heading / no match "unknown" (recorded, never guessed).
520
+ # Parse the mandated '### Verdict' section of a captured review: the first non-empty line after
521
+ # the EXACT heading (`### Verdicts` or any suffix never matches), tested against the closed
522
+ # vocabulary as an ANCHORED PREFIX with a word boundary — the verdict line must START with the
523
+ # token and the next char must be non-word/EOL (SHIP WITH NITS before SHIP; `NOT SHIP` and
524
+ # `SHIPPING` never match). No heading / no match → "unknown" (the D4 failed-run arm owns it).
478
525
  parse_agy_verdict() { # $1 = captured-output file
479
526
  local line
480
- line="$(awk '/^### Verdict/{flag=1; next} flag && NF {print; exit}' "$1" 2>/dev/null)"
481
- case "$line" in
482
- *"SHIP WITH NITS"*) printf 'SHIP WITH NITS' ;;
483
- *REWORK*) printf 'REWORK' ;;
484
- *SHIP*) printf 'SHIP' ;;
485
- *) printf 'unknown' ;;
486
- esac
527
+ line="$(awk '/^### Verdict[[:space:]]*$/{flag=1; next} flag && NF {print; exit}' "$1" 2>/dev/null)"
528
+ if [[ "$line" =~ ^SHIP\ WITH\ NITS([^[:alnum:]_]|$) ]]; then printf 'SHIP WITH NITS'
529
+ elif [[ "$line" =~ ^REWORK([^[:alnum:]_]|$) ]]; then printf 'REWORK'
530
+ elif [[ "$line" =~ ^SHIP([^[:alnum:]_]|$) ]]; then printf 'SHIP'
531
+ else printf 'unknown'
532
+ fi
487
533
  }
488
534
 
489
535
  # Emit the full review surface to stdout: repo map, status (never-committable untracked records
@@ -541,7 +587,7 @@ case "${1:-}" in
541
587
  esac
542
588
 
543
589
  usage() {
544
- echo "usage: $0 code [--facts @f] [--decided @f] [--focus \"…\"] [extra focus…]" >&2
590
+ echo "usage: $0 code [--facts @f] [--ungrounded] [--decided @f] [--focus \"…\"] [extra focus…]" >&2
545
591
  echo " $0 plan <plan-file> [--facts @f] [--decided @f] [--focus \"…\"]" >&2
546
592
  echo " $0 diff <diff-file> [--facts @f] [--decided @f] [--focus \"…\"]" >&2
547
593
  echo " $0 --continue [--decided @f] [--focus \"…\"]" >&2
@@ -580,6 +626,7 @@ fi
580
626
  # --- Flag parse (--facts / --decided / --focus + trailing focus) -------------
581
627
  FACTS_RAW=""
582
628
  DECIDED_RAW=""
629
+ UNGROUNDED=0
583
630
  FOCUS_PARTS=()
584
631
  # A value-taking flag must be followed by a real value — never end-of-args and never another flag.
585
632
  # Otherwise `agy-review code --facts --focus x` would silently take "--focus" as the facts and spend a
@@ -598,6 +645,12 @@ while [[ $# -gt 0 ]]; do
598
645
  exit 2
599
646
  fi
600
647
  need_value "$1" "${2:-}"; FACTS_RAW="$2"; shift 2 ;;
648
+ --ungrounded)
649
+ if [[ -n "$resume_mode" ]]; then
650
+ echo "error: --ungrounded is not valid on a continuation (the original round already set its grounding)." >&2
651
+ exit 2
652
+ fi
653
+ UNGROUNDED=1; shift ;;
601
654
  --decided)
602
655
  need_value "$1" "${2:-}"; DECIDED_RAW="$2"; shift 2 ;;
603
656
  --focus)
@@ -623,6 +676,18 @@ done
623
676
  # Merge --focus values and trailing focus words, in parse order, into ONE focus block.
624
677
  FOCUS="${FOCUS_PARTS[*]:-}"
625
678
 
679
+ # --ungrounded closed grammar (D4): code-mode only, and an explicit contradiction with --facts refuses.
680
+ if [[ "$UNGROUNDED" == "1" ]]; then
681
+ if [[ -n "$FACTS_RAW" ]]; then
682
+ echo "error: --ungrounded contradicts --facts — pass the verified facts (grounded) or drop them (--ungrounded), never both." >&2
683
+ exit 2
684
+ fi
685
+ if [[ "$mode" != "code" ]]; then
686
+ echo "error: --ungrounded is only valid in code mode — $mode mode already proceeds ungrounded with a loud warning." >&2
687
+ exit 2
688
+ fi
689
+ fi
690
+
626
691
  # Resolve @file / literal for --facts and --decided NOW (cwd = invocation, before any code-mode cd).
627
692
  # The `@file` existence check runs at TOP LEVEL (not inside a command substitution) so its exit-2 exits
628
693
  # the whole script, not just a subshell.
@@ -648,6 +713,25 @@ if [[ -n "$DECIDED_RAW" ]]; then
648
713
  fi
649
714
 
650
715
  if [[ -z "$FACTS_CONTENT" && -z "$resume_mode" ]]; then
716
+ # code mode fails CLOSED before the spend (D4): an ungrounded CODE receipt records grounded:false,
717
+ # which the review-state gate rejects — the run would be paid for and attest nothing. Keyed on the
718
+ # resolved CONTENT, so --facts naming an empty payload refuses identically.
719
+ if [[ "$mode" == "code" && "$UNGROUNDED" != "1" && "$AGY_PROBE" != "1" ]]; then
720
+ # The recovery hint resolves the kit's grounding tool from THIS wrapper's location (monorepo
721
+ # canon / deployed skills sibling / kit-bundled mirror) — a repo-relative path would not exist
722
+ # for a globally installed kit. Quoted: an install path may carry spaces.
723
+ grounding_tool="node <agent-workflow-kit>/tools/grounding.mjs"
724
+ for _g in "$HERE/../../agent-workflow-kit/tools/grounding.mjs" "$HERE/../../../tools/grounding.mjs"; do
725
+ if [[ -f "$_g" ]]; then grounding_tool="node \"$_g\""; break; fi
726
+ done
727
+ echo "error: 'agy-review code' requires grounded facts and refuses BEFORE spending a run — an" >&2
728
+ echo " ungrounded code review GUESSES, and its receipt (grounded:false) never attests." >&2
729
+ echo " Assemble the verified facts, then re-run:" >&2
730
+ echo " $grounding_tool --constraints [--plan <plan-file>] --out <facts-file>" >&2
731
+ echo " agy-review code --facts @<facts-file>" >&2
732
+ echo " Explicit escapes: --ungrounded (throwaway opinion) · AGY_PROBE=1 (a probe never attests)." >&2
733
+ exit 2
734
+ fi
651
735
  echo "warning: no --facts supplied. agy reads NOTHING by default, so an ungrounded review GUESSES" >&2
652
736
  echo " (stale-model and partial-diff false positives). Pass --facts @file with the verified" >&2
653
737
  echo " facts the model must review AGAINST. Proceeding without grounding." >&2
@@ -785,6 +869,21 @@ else
785
869
  fi
786
870
  fi
787
871
 
872
+ # --- D5 pre-spend posture gate + banner (one line, the ACTUAL run posture) ------------------
873
+ # The control-byte screen already ran at AGY_MODEL resolution (before any interpolation). An
874
+ # ATTESTING review (fresh code mode, guards on) whose wrapper cannot know the actual model
875
+ # (AGY_MODEL explicitly emptied → the CLI's own settings default decides) refuses pre-spend;
876
+ # AGY_PROBE=1 runs are exempt (their receipts never attest and record model null).
877
+ # Scoped to the ATTESTING branch only (grounded code, guards on): plan / diff / --ungrounded
878
+ # code mint receipts that never attest, so an emptied model just records posture.model null.
879
+ if [[ -z "$AGY_MODEL" && -z "$resume_mode" && "$REVIEW_PROBE" != "true" && "$REVIEW_ARTIFACT" == "code" && -n "$FACTS_CONTENT" ]]; then
880
+ echo "error: AGY_MODEL is explicitly empty, so the ACTUAL review model is unknowable (the agy CLI's" >&2
881
+ echo " own settings default decides) — an attesting review refuses pre-spend. Fix: unset" >&2
882
+ echo " AGY_MODEL (wrapper default), or set the real model display string; AGY_PROBE=1 is exempt." >&2
883
+ exit 2
884
+ fi
885
+ echo "review posture: model=${AGY_MODEL:-<agy settings default>}" >&2
886
+
788
887
  # --- Execute via agy-run (single home of timeout + subscription + byte ceiling) ---
789
888
  # The output is teed into the private staging dir so the mandated '### Verdict' section can be
790
889
  # parsed into the review receipt — the user-facing stream is unchanged.
@@ -802,8 +901,17 @@ fi
802
901
  set -e
803
902
 
804
903
  # --- Review receipt (AD-038): only a SUCCESSFUL review attests --------------------
904
+ # D4 (wrapper honesty): a run that produced NO recognized '### Verdict' section — empty output
905
+ # included — is a FAILED review: non-zero exit, NO receipt. This is a failed review to RE-RUN,
906
+ # never a fatal session error (documented in --help).
805
907
  if [[ $rc -eq 0 ]]; then
806
908
  verdict="$(parse_agy_verdict "$review_out_file")"
909
+ if [[ "$verdict" == "unknown" ]]; then
910
+ echo "error: the review output carries no recognized '### Verdict' section (closed vocabulary:" >&2
911
+ echo " SHIP / SHIP WITH NITS / REWORK) — a FAILED review; NO receipt was written. Re-run" >&2
912
+ echo " the review; if it recurs, inspect the captured output for what the model produced." >&2
913
+ exit 4
914
+ fi
807
915
  if [[ -n "$resume_mode" ]]; then
808
916
  # A continuation never re-embeds the current artifact (agy holds the ORIGINAL round server-side;
809
917
  # --facts is rejected above), so it cannot attest the folded tree: fresh:false, artifact /
@@ -38,7 +38,9 @@ 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
- 'printf "%s\\n" "${AGY_FAKE_OUTPUT:-FAKE_AGY_REVIEW_OUTPUT}"',
41
+ // Unset AGY_FAKE_OUTPUT → a verdict-carrying default (D4: a verdict-less run is a FAILURE, so
42
+ // the success-path tests need one); an EXPLICIT empty value exercises the empty-output failure.
43
+ 'if [[ -z "${AGY_FAKE_OUTPUT+x}" ]]; then printf "FAKE_AGY_REVIEW_OUTPUT\\n### Verdict\\nSHIP\\n"; else printf "%s\\n" "$AGY_FAKE_OUTPUT"; fi',
42
44
  'exit "${AGY_FAKE_EXIT:-0}"',
43
45
  '',
44
46
  ].join('\n');
@@ -190,17 +192,100 @@ describe('agy-review.sh — guard + grounding (2, 3)', () => {
190
192
  assert.match(r.prompt, /## Focus\nfirst second third/);
191
193
  });
192
194
 
193
- it('warns LOUDLY when --facts is omitted, and proceeds', () => {
195
+ it('plan mode with no --facts keeps the warning and proceeds (unchanged contract)', () => {
194
196
  const sb = makeSandbox();
195
- const r = run(sb, { args: ['code'] });
197
+ writeFileSync(join(sb.repo, 'p.md'), '# plan body\n');
198
+ const r = run(sb, { args: ['plan', 'p.md'] });
196
199
  rmSync(sb.home, { recursive: true, force: true });
197
200
  assert.equal(r.status, 0, r.stderr);
198
201
  assert.match(r.stderr, /no --facts supplied/);
199
- assert.equal(r.invoked, true, 'an ungrounded review still proceeds (warn, not block)');
202
+ assert.equal(r.invoked, true, 'an ungrounded plan review still proceeds (warn, not block)');
200
203
  assert.match(r.prompt, /none supplied/, 'the prompt notes the missing facts in-band');
201
204
  });
202
205
  });
203
206
 
207
+ // ── code mode fails CLOSED without grounded facts (D4) ───────────────────────────
208
+ // An ungrounded CODE receipt records grounded:false, which the kit's review-state gate rejects —
209
+ // the run would be paid for and attest nothing. The wrapper refuses BEFORE the spend, keyed on the
210
+ // resolved CONTENT (an empty --facts payload refuses identically). Escapes: the explicit
211
+ // --ungrounded flag (throwaway opinion) and AGY_PROBE=1 (a probe receipt never attests anyway).
212
+ describe('agy-review.sh — code mode fails CLOSED without grounded facts (D4)', () => {
213
+ it('code mode with no --facts exits 2 before any agy invocation', () => {
214
+ const sb = makeSandbox();
215
+ const r = run(sb, { args: ['code'] });
216
+ rmSync(sb.home, { recursive: true, force: true });
217
+ assert.equal(r.status, 2, r.stderr);
218
+ assert.equal(r.invoked, false, 'the refusal must fire before any agy invocation — zero runs spent');
219
+ assert.match(r.stderr, /grounding\.mjs/, 'the refusal names the facts assembler');
220
+ assert.match(r.stderr, /agy-review code --facts @/, 'the refusal prints the exact re-run line');
221
+ const hint = r.stderr.match(/node "([^"]+grounding\.mjs)"/);
222
+ assert.ok(hint, 'the recovery hint resolves and QUOTES a real grounding.mjs path (an install path may carry spaces)');
223
+ assert.ok(existsSync(hint[1]), 'the resolved hint path exists on this layout');
224
+ });
225
+
226
+ it('code mode with --facts naming an EMPTY payload exits 2 before any agy invocation', () => {
227
+ const sb = makeSandbox();
228
+ writeFileSync(join(sb.repo, 'empty-facts.md'), '');
229
+ const r = run(sb, { args: ['code', '--facts', '@empty-facts.md'] });
230
+ rmSync(sb.home, { recursive: true, force: true });
231
+ assert.equal(r.status, 2, 'the refusal keys on the CONTENT, not the flag');
232
+ assert.equal(r.invoked, false, 'an empty payload must not spend a run');
233
+ assert.match(r.stderr, /agy-review code --facts @/);
234
+ });
235
+
236
+ it('code --ungrounded proceeds and the receipt records grounded:false', () => {
237
+ const sb = makeSandbox();
238
+ const r = run(sb, { args: ['code', '--ungrounded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
239
+ const receipts = readReceipts(sb.repo);
240
+ rmSync(sb.home, { recursive: true, force: true });
241
+ assert.equal(r.status, 0, r.stderr);
242
+ assert.equal(r.invoked, true, 'the explicit escape lets the run proceed');
243
+ assert.equal(receipts.length, 1);
244
+ assert.equal(receipts[0].grounded, false, 'an --ungrounded run still records grounded:false');
245
+ assert.equal(receipts[0].factsHash, null);
246
+ assert.match(r.stderr, /no --facts supplied/, 'the escape path stays loud, never silent');
247
+ });
248
+
249
+ it('AGY_PROBE=1 code with no --facts proceeds and the receipt records probe:true', () => {
250
+ const sb = makeSandbox();
251
+ const r = run(sb, { args: ['code'], env: { AGY_PROBE: '1', AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
252
+ const receipts = readReceipts(sb.repo);
253
+ rmSync(sb.home, { recursive: true, force: true });
254
+ assert.equal(r.status, 0, r.stderr);
255
+ assert.equal(r.invoked, true, 'an ungrounded probe is coherent — a probe receipt never attests');
256
+ assert.equal(receipts[0].probe, true);
257
+ assert.equal(receipts[0].grounded, false);
258
+ });
259
+
260
+ it('--ungrounded with --facts is a refusal (contradiction)', () => {
261
+ const sb = makeSandbox();
262
+ const r = run(sb, { args: ['code', '--ungrounded', '--facts', 'f'] });
263
+ rmSync(sb.home, { recursive: true, force: true });
264
+ assert.equal(r.status, 2);
265
+ assert.equal(r.invoked, false);
266
+ assert.match(r.stderr, /--ungrounded contradicts --facts/);
267
+ });
268
+
269
+ it('--ungrounded outside code mode is a refusal', () => {
270
+ const sb = makeSandbox();
271
+ writeFileSync(join(sb.repo, 'p.md'), '# p\n');
272
+ const r = run(sb, { args: ['plan', 'p.md', '--ungrounded'] });
273
+ rmSync(sb.home, { recursive: true, force: true });
274
+ assert.equal(r.status, 2);
275
+ assert.equal(r.invoked, false);
276
+ assert.match(r.stderr, /--ungrounded is only valid in code mode/);
277
+ });
278
+
279
+ it('--ungrounded on a continuation is a refusal', () => {
280
+ const sb = makeSandbox();
281
+ const r = run(sb, { args: ['--continue', '--ungrounded'] });
282
+ rmSync(sb.home, { recursive: true, force: true });
283
+ assert.equal(r.status, 2);
284
+ assert.equal(r.invoked, false);
285
+ assert.match(r.stderr, /--ungrounded is not valid on a continuation/);
286
+ });
287
+ });
288
+
204
289
  describe('agy-review.sh — code-mode precomputed diff (4, 5, 8)', () => {
205
290
  it('assembles repo map + status + untracked CONTENTS', () => {
206
291
  const sb = makeSandbox();
@@ -754,7 +839,12 @@ describe('agy-review.sh — declared contract is really accepted (forward guard)
754
839
  for (const descriptor of REVIEW_CONTRACT.flags) {
755
840
  const flag = leadingFlag(descriptor);
756
841
  const sb = makeSandbox();
757
- const r = run(sb, { args: ['code', flag, 'f'] });
842
+ // D4: code mode refuses without grounded facts, so a non-facts value flag is driven on a
843
+ // grounded run; --ungrounded takes no value, contradicts --facts, and is driven alone.
844
+ const args = flag === '--facts' ? ['code', '--facts', 'f']
845
+ : flag === '--ungrounded' ? ['code', '--ungrounded']
846
+ : ['code', '--facts', 'f', flag, 'f'];
847
+ const r = run(sb, { args });
758
848
  rmSync(sb.home, { recursive: true, force: true });
759
849
  assert.equal(r.status, 0, `${flag}: ${r.stderr}`);
760
850
  }
@@ -782,7 +872,7 @@ describe('agy-review.sh — declared contract is really accepted (forward guard)
782
872
  // The normative fixture: the AD-038 shape + the D3 self-declaring probe marker (backend/verdict here
783
873
  // carry this bridge's vocabulary; dynamic values are asserted by shape):
784
874
  const RECEIPT_FIXTURE = JSON.parse(
785
- '{"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}',
875
+ '{"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>"}}',
786
876
  );
787
877
  const RECEIPTS_REL = join('.git', 'agent-workflow-review-receipts.jsonl');
788
878
  const readReceipts = (repo) => {
@@ -853,9 +943,9 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
853
943
  assert.equal(receipts[0].probe, true, 'both write paths carry the marker — no unmarked probe lane');
854
944
  });
855
945
 
856
- it('an ungrounded fresh run records grounded:false + factsHash null (the vacuous-grounding hole stays visible)', () => {
946
+ it('an --ungrounded fresh run records grounded:false + factsHash null (the vacuous-grounding hole stays visible)', () => {
857
947
  const sb = makeSandbox();
858
- const r = run(sb, { args: ['code'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
948
+ const r = run(sb, { args: ['code', '--ungrounded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
859
949
  const receipts = readReceipts(sb.repo);
860
950
  rmSync(sb.home, { recursive: true, force: true });
861
951
  assert.equal(r.status, 0, r.stderr);
@@ -863,23 +953,21 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
863
953
  assert.equal(receipts[0].factsHash, null);
864
954
  });
865
955
 
866
- it('an EMPTY --facts file records grounded:false (fail-closedvacuous grounding never satisfies the gate) and still warns', () => {
956
+ it('an EMPTY --facts file in code mode refuses pre-spendno run, no receipt (D4 fail-closed)', () => {
867
957
  const sb = makeSandbox();
868
958
  writeFileSync(join(sb.home, 'empty-facts.md'), '');
869
959
  const r = run(sb, { args: ['code', '--facts', `@${join(sb.home, 'empty-facts.md')}`], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
870
960
  const receipts = readReceipts(sb.repo);
871
961
  rmSync(sb.home, { recursive: true, force: true });
872
- assert.equal(r.status, 0, r.stderr);
873
- assert.equal(receipts[0].grounded, false, 'an empty payload is recorded as ungrounded, never as grounded-by-flag');
874
- assert.equal(receipts[0].factsHash, null);
875
- assert.match(r.stderr, /no --facts supplied|ungrounded review GUESSES/, 'the ungrounded warning fires for an empty payload');
962
+ assert.equal(r.status, 2, 'vacuous grounding no longer spends a run');
963
+ assert.equal(r.invoked, false);
964
+ assert.equal(receipts.length, 0, 'no run — no receipt');
876
965
  });
877
966
 
878
- it('parses REWORK and plain SHIP; records "unknown" when the mandated section is absent', () => {
967
+ it('parses REWORK and plain SHIP verbatim (an absent section is a FAILED run — the D4 describe owns that arm)', () => {
879
968
  for (const [output, want] of [
880
969
  ['### Verdict\nREWORK — the contract is violated.', 'REWORK'],
881
970
  ['### Verdict\nSHIP — clean.', 'SHIP'],
882
- ['free-form text with no verdict heading', 'unknown'],
883
971
  ]) {
884
972
  const sb = makeSandbox();
885
973
  const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: output } });
@@ -1177,3 +1265,93 @@ describe('agy-review.sh — settings surface ⟷ manifest (D6, manifest-pinned)'
1177
1265
  }
1178
1266
  });
1179
1267
  });
1268
+
1269
+ // ── strip-the-kit Phase 4: wrapper honesty (D4) + dispatch-posture labeling (D5) ────────────────
1270
+ describe('agy-review.sh — wrapper honesty: a verdict-less run is a FAILED review (D4)', () => {
1271
+ it('a VERDICT-LESS review output: non-zero exit, NO receipt, the stated re-run recovery', () => {
1272
+ const sb = makeSandbox();
1273
+ const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: 'prose without the mandated section' } });
1274
+ const receipts = readReceipts(sb.repo);
1275
+ rmSync(sb.home, { recursive: true, force: true });
1276
+ assert.notEqual(r.status, 0, 'a verdict-less review never exits 0');
1277
+ assert.equal(receipts.length, 0, 'NO receipt is minted for a failed review');
1278
+ assert.match(r.stderr, /### Verdict/, 'the missing section is named');
1279
+ assert.match(r.stderr, /re-run/i, 'documented as a failed review — re-run, never fatal');
1280
+ });
1281
+
1282
+ it('EMPTY review output is the same failed run (non-zero, no receipt)', () => {
1283
+ const sb = makeSandbox();
1284
+ const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: '' } });
1285
+ const receipts = readReceipts(sb.repo);
1286
+ rmSync(sb.home, { recursive: true, force: true });
1287
+ assert.notEqual(r.status, 0);
1288
+ assert.equal(receipts.length, 0);
1289
+ });
1290
+
1291
+ it('the closed vocabulary still parses (SHIP WITH NITS before SHIP; REWORK) and a recognized run exits 0', () => {
1292
+ for (const [out, want] of [[VERDICT_OUTPUT, 'SHIP WITH NITS'], ['### Verdict\nREWORK — reasons.\n', 'REWORK']]) {
1293
+ const sb = makeSandbox();
1294
+ const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: out } });
1295
+ const receipts = readReceipts(sb.repo);
1296
+ rmSync(sb.home, { recursive: true, force: true });
1297
+ assert.equal(r.status, 0, r.stderr);
1298
+ assert.equal(receipts[0].verdict, want);
1299
+ }
1300
+ });
1301
+ });
1302
+
1303
+ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
1304
+ it('ONE banner line carries the ACTUAL model and the receipt carries the SAME posture (agy has no tier)', () => {
1305
+ const sb = makeSandbox();
1306
+ const r = run(sb, { args: ['code', '--facts', 'a tiny fact'] });
1307
+ const receipts = readReceipts(sb.repo);
1308
+ rmSync(sb.home, { recursive: true, force: true });
1309
+ assert.equal(r.status, 0, r.stderr);
1310
+ assert.match(r.stderr, /review posture: model=Gemini 3\.1 Pro \(High\)/, 'the banner states the actual run posture');
1311
+ assert.deepEqual(receipts[0].posture, { model: 'Gemini 3.1 Pro (High)' }, 'banner ↔ receipt parity');
1312
+ assert.deepEqual(Object.keys(receipts[0]), Object.keys(RECEIPT_FIXTURE), 'fixture key set + order');
1313
+ });
1314
+
1315
+ it('an ATTESTING review with AGY_MODEL explicitly emptied REFUSES pre-spend naming the fix', () => {
1316
+ const sb = makeSandbox();
1317
+ const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: '' } });
1318
+ const receipts = readReceipts(sb.repo);
1319
+ rmSync(sb.home, { recursive: true, force: true });
1320
+ assert.notEqual(r.status, 0);
1321
+ assert.equal(r.invoked, false, 'refused BEFORE any spend');
1322
+ assert.equal(receipts.length, 0);
1323
+ assert.match(r.stderr, /AGY_MODEL/, 'the fix is named');
1324
+ });
1325
+
1326
+ it('AGY_PROBE=1 with AGY_MODEL emptied still runs (probe exempt; posture model null on the probe receipt)', () => {
1327
+ const sb = makeSandbox();
1328
+ const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: '', AGY_PROBE: '1' } });
1329
+ const receipts = readReceipts(sb.repo);
1330
+ rmSync(sb.home, { recursive: true, force: true });
1331
+ assert.equal(r.status, 0, r.stderr);
1332
+ assert.equal(receipts[0].probe, true);
1333
+ assert.deepEqual(receipts[0].posture, { model: null }, 'an unknowable model is recorded null, never guessed');
1334
+ });
1335
+
1336
+ it('a HOSTILE model string (quotes + backslash) rides the receipt strictly JSON-encoded', () => {
1337
+ const hostile = 'we"ird \\ mo"del';
1338
+ const sb = makeSandbox();
1339
+ const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: hostile } });
1340
+ const receipts = readReceipts(sb.repo); // JSON.parse throwing here IS the encoding failure
1341
+ rmSync(sb.home, { recursive: true, force: true });
1342
+ assert.equal(r.status, 0, r.stderr);
1343
+ assert.equal(receipts[0].posture.model, hostile, 'the exact bytes round-trip through strict encoding');
1344
+ assert.match(r.stderr, /review posture: /, 'the banner still renders');
1345
+ });
1346
+
1347
+ it('a model string carrying CONTROL BYTES refuses pre-spend (never a broken banner or receipt)', () => {
1348
+ const sb = makeSandbox();
1349
+ const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: `bad${String.fromCharCode(1)}model` } });
1350
+ const receipts = readReceipts(sb.repo);
1351
+ rmSync(sb.home, { recursive: true, force: true });
1352
+ assert.notEqual(r.status, 0);
1353
+ assert.equal(r.invoked, false);
1354
+ assert.equal(receipts.length, 0);
1355
+ assert.match(r.stderr, /control/i);
1356
+ });
1357
+ });
@@ -3,8 +3,9 @@
3
3
  "schema": 1,
4
4
  "name": "antigravity-cli-bridge",
5
5
  "kind": "execution-backend",
6
- "version": "2.7.0",
6
+ "version": "4.0.0",
7
7
  "provides": ["review", "probe"],
8
+ "posture": { "model": "Gemini 3.1 Pro (High)" },
8
9
  "roles": {
9
10
  "review": {
10
11
  "cmd": "agy-review",
@@ -14,13 +15,14 @@
14
15
  "output": "advisory",
15
16
  "contract": {
16
17
  "invocations": [
17
- "agy-review code [--facts @f] [--decided @f] [--focus \"…\"] [extra focus…]",
18
+ "agy-review code [--facts @f] [--ungrounded] [--decided @f] [--focus \"…\"] [extra focus…]",
18
19
  "agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus \"…\"]",
19
20
  "agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus \"…\"]"
20
21
  ],
21
- "grounding": "grounded review — agy reads NOTHING by default, an ungrounded review GUESSES: --facts @f = the verified facts to review AGAINST; --decided @f = decisions already made, do NOT re-raise (anti-circling)",
22
+ "grounding": "grounded review — agy reads NOTHING by default, an ungrounded review GUESSES: --facts @f = the verified facts to review AGAINST; --decided @f = decisions already made, do NOT re-raise (anti-circling). code mode REQUIRES a non-empty --facts payload and refuses BEFORE spending a run (escapes: --ungrounded, AGY_PROBE=1); plan/diff proceed with a loud warning",
22
23
  "flags": [
23
- "--facts @f — verified facts the review runs AGAINST (omit loud ungrounded-review warning)",
24
+ "--facts @f — verified facts the review runs AGAINST (code mode REQUIRES a non-empty payload; plan/diff warn loudly when omitted)",
25
+ "--ungrounded — deliberately ungrounded CODE review, a throwaway opinion (code mode only, contradicts --facts; the receipt records grounded:false and never attests)",
24
26
  "--decided @f — already-decided / already-addressed list; do NOT re-raise (anti-circling; the round-2 payload)",
25
27
  "--focus \"…\" — extra focus (repeatable; code mode also takes trailing focus words)"
26
28
  ],
@@ -28,7 +30,7 @@
28
30
  "agy-review --continue [--decided @f] [--focus \"…\"]",
29
31
  "agy-review --conversation <id> [--decided @f] [--focus \"…\"]"
30
32
  ],
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; 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 (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); a write failure warns, never fails the review",
33
+ "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; 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); a write failure warns, never fails the review",
32
34
  "notes": [
33
35
  "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"
34
36
  ]
@@ -50,7 +52,8 @@
50
52
  "whenNotTo": ["a clean tree — the wrapper exits before spending a run"],
51
53
  "invocationRefs": [{ "contractField": "invocations", "index": 0 }],
52
54
  "operands": [
53
- { "slot": "[--facts @f]", "required": false, "description": "the verified facts to review AGAINST; omitting it warns loudly and records the review ungrounded" },
55
+ { "slot": "[--facts @f]", "required": false, "description": "the verified facts to review AGAINST; code mode refuses pre-spend without a non-empty payload (escapes: --ungrounded, AGY_PROBE=1)" },
56
+ { "slot": "[--ungrounded]", "required": false, "description": "deliberately ungrounded code review — a throwaway opinion; the receipt records grounded:false and never attests" },
54
57
  { "slot": "[--decided @f]", "required": false, "description": "decisions already made; the reviewer must not re-raise them (anti-circling)" },
55
58
  { "slot": "[--focus \"…\"]", "required": false, "description": "what this review must look at (repeatable)" },
56
59
  { "slot": "[extra focus…]", "required": false, "description": "extra focus words appended to the review directive" }
@@ -59,6 +62,7 @@
59
62
  { "value": "read-only posture — the prompt forbids edits, commands and git writes", "enforcement": "advisory", "source": "bin/agy-review.sh" },
60
63
  { "value": "receives no ambient repo/file context by default — the wrapper passes no --add-dir", "enforcement": "enforced", "source": "bin/agy-review.sh" },
61
64
  { "value": "an ungrounded review guesses — stale-model and partial-diff false positives", "enforcement": "advisory", "source": "bin/agy-review.sh" },
65
+ { "value": "without a non-empty --facts payload the run refuses BEFORE the spend (exit 2) — the only escapes are --ungrounded and AGY_PROBE=1", "enforcement": "enforced", "source": "bin/agy-review.sh" },
62
66
  { "value": "an ungrounded run records grounded:false and the review-state gate rejects it", "enforcement": "enforced", "source": "capability.json roles.review.contract.receipt" },
63
67
  { "value": "an oversized prompt refuses rather than truncate", "enforcement": "enforced", "condition": "unless AGY_REVIEW_ALLOW_ADDDIR=1 offloads it to a private --add-dir staging dir", "source": "capability.json settings.AGY_REVIEW_ALLOW_ADDDIR" }
64
68
  ],
@@ -185,9 +189,9 @@
185
189
  "deliberately reviewing with a cheaper non-frontier model"
186
190
  ],
187
191
  "whenNotTo": ["any run whose output informs what ships — a probe never attests"],
188
- "descriptor": "AGY_PROBE=1 agy-review code --facts @<facts-file>",
192
+ "descriptor": "AGY_PROBE=1 agy-review code [--facts @<facts-file>]",
189
193
  "operands": [
190
- { "slot": "@<facts-file>", "required": true, "description": "the verified facts the review runs AGAINST" }
194
+ { "slot": "[--facts @<facts-file>]", "required": false, "description": "the verified facts the review runs AGAINST — a probe may run ungrounded (its receipt never attests either way)" }
191
195
  ],
192
196
  "guardrails": [
193
197
  { "value": "a probe review mints a probe-marked receipt the review-state gate rejects", "enforcement": "enforced", "source": "agent-workflow-kit tools/review-state.mjs" }