@sabaiway/agent-workflow-kit 1.49.0 → 2.1.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 CHANGED
@@ -4,6 +4,106 @@ 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
+ ## 2.1.0 — agy code review fails CLOSED pre-spend: grounded facts required, `--ungrounded` is the explicit escape (AD-058)
8
+
9
+ > ### ⚠ the bundled antigravity bridge takes MAJOR 3.0.0 — bare `agy-review code` now refuses
10
+ >
11
+ > `agy-review code` without a NON-EMPTY `--facts` payload exits 2 **before spending a run**
12
+ > (previously: a loud warning, then a spent subscription run whose `grounded:false` receipt the
13
+ > review-state gate rejects — a guaranteed-wasted spend). Scripts calling bare `agy-review code`
14
+ > must add `--facts @f` (grounded) or `--ungrounded` (explicit throwaway opinion; the receipt still
15
+ > records `grounded:false` and never attests). `plan`/`diff` modes and continuations are unchanged.
16
+ > The kit itself is a MINOR carrier — the incompatibility is created, and declared, by the bridge.
17
+
18
+ A **fail-closed** release (kit MINOR carrying antigravity-cli-bridge **3.0.0** MAJOR in-tarball;
19
+ codex-cli-bridge 2.8.0 / engine 1.17.0 / memory 2.3.0 unchanged; the deployment-lineage head stays
20
+ `2.0.0`). One theme: **a run that cannot produce a usable result refuses before the money is spent.**
21
+
22
+ - **The refusal prints the recovery:** the exact `grounding.mjs` invocation — resolved from the
23
+ wrapper's own location across the three real install layouts (monorepo canon / deployed skills
24
+ sibling / kit-bundled mirror), quoted so an install path with spaces still yields a runnable
25
+ command — plus the `agy-review code --facts @<file>` re-run line.
26
+ - **Escapes, both honest:** `--ungrounded` (code-mode only, contradicts `--facts`, refused on a
27
+ continuation) and `AGY_PROBE=1` (a probe may now run ungrounded — a probe receipt never attests
28
+ either way; its `modeCatalog` descriptor now marks the facts file optional).
29
+ - **Every declaration home moves in lockstep** (drift-guarded both directions): `--help` ⟷
30
+ `capability.json` (`roles.review.contract` + `modeCatalog` `review.code` operands/guardrails +
31
+ the `AGY_PROBE` env-hook) ⟷ the kit registry mirror (`tools/detect-backends.mjs`) ⟷ `SKILL.md` ⟷
32
+ `references/driving-agy.md` / `models-and-flags.md` / `review-prompt.md`.
33
+ - **Receipt contract scoped honestly:** in `code` mode an absent/empty facts payload now means NO
34
+ run and NO receipt; the `grounded:false`-on-empty clause applies to `plan`/`diff` only.
35
+
36
+ ## 2.0.0 — Bridge mode catalog (manifest-as-source) + a review receipt that SELF-DECLARES (AD-057)
37
+
38
+ > ### ⚠ BREAKING — a review receipt written before this release no longer attests a tree
39
+ >
40
+ > The kit now **rejects an unmarked receipt**: a receipt must self-declare whether it came from a
41
+ > probe run, and silence is not a declaration. Every receipt on disk from kit ≤ 1.49.0 / bridges
42
+ > ≤ 2.7.1 / ≤ 2.6.1 predates the marker, so `review-state --check` and `review-ledger --check` will
43
+ > RED on it where they previously passed.
44
+ >
45
+ > **How to upgrade:** `npx @sabaiway/agent-workflow-kit@latest init`, which also ATTEMPTS to refresh
46
+ > the placed bridges — then read its per-bridge outcome. **`skipped-readonly` or `could not refresh`
47
+ > means a compatible writer is not guaranteed:** the new reader may still be paired with an old bridge
48
+ > writer, whose reviews write unmarked receipts this gate now rejects. Re-run the refresh from a
49
+ > writable environment (using the recovery command if one was printed), then **re-run the review** on
50
+ > the tree you are working on. That is the whole migration; no project file changes, and the
51
+ > deployment-lineage stamp is untouched.
52
+ >
53
+ > **Why it is not opt-in.** The pre-marker wrappers already honoured `CODEX_PROBE` / `AGY_PROBE` and
54
+ > wrote no marker, so an unmarked receipt is **indistinguishable from a probe receipt** — a review
55
+ > that ran with the frontier-model/max-effort guard switched off. Any transitional window that keeps
56
+ > accepting unmarked receipts keeps the hole fully open for exactly the receipts it targets: it would
57
+ > not defer the break, it would cancel the fix.
58
+
59
+ A **breaking + feature** release (kit MAJOR carrying codex-cli-bridge **2.8.0** + antigravity-cli-bridge
60
+ **2.7.0** MINOR in-tarball — the bridges only ADD a field to the receipt they write, which is additive;
61
+ the incompatibility is created by the kit READER that now refuses the old form. engine 1.17.0 / memory
62
+ 2.3.0 unchanged; the deployment-lineage head is a separate axis and stays `2.0.0`). Two independent
63
+ contracts, one theme: **what a bridge offers, and what a receipt claims, must both be readable off the
64
+ artifact itself — never inferred from source or from silence.**
65
+
66
+ - **`modeCatalog` — a machine-readable answer to "what modes does this bridge offer, and why?"** A new
67
+ **top-level, additive-optional** manifest block (schema stays 1), typed-validated exactly like
68
+ `settings`: absent → valid (a bridge predating it keeps validating), present-but-malformed → invalid.
69
+ Both bridges now declare their real mode set — codex `{exec, exec.resume-last, exec.resume,
70
+ review.plan, review.code, CODEX_PROBE}`, agy `{review.code/plan/diff, review.continue,
71
+ review.conversation, run, AGY_PROBE}` — each entry carrying a closed taxonomy (`primary` /
72
+ `continuation` / `env-hook`, where an env-hook names `parents[]` rather than faking a role), a
73
+ required one-line `purpose` + `whenToUse`, and — declared only where they apply — `whenNotTo`, typed
74
+ `operands[]`, structured `guardrails` `{value, enforcement, condition?, source}` and `customHooks[]`
75
+ (`exec.resume` carries no `guardrails`; a mode with no operands omits `operands`). Invocation forms compose **by
76
+ reference** (`invocationRefs[]`) into the existing AD-033 driving contract — the catalog is the
77
+ user-facing DISCOVERY layer and never shadows it.
78
+ - **Descriptor honesty is enforced, not promised.** Declared operand slots must set-EQUAL the
79
+ placeholders the referenced forms really carry, in **both** directions over the deduplicated union of
80
+ an entry's forms: an undeclared placeholder is as dishonest as an invented slot, since either way the
81
+ render shows a form as ready-to-run that the reader cannot actually fill. `enforced` is claimable only
82
+ for an OS- or code-enforced fact; a runtime bound rides in `condition`; anything a prompt merely asks
83
+ for renders `advisory`. Catalog `submode` values are drift-guarded against the wrappers' real parser
84
+ arms, and every declared env-hook must be a real EXECUTABLE condition (heredocs and comments excluded,
85
+ so a name-grep cannot stay green after the logic is deleted).
86
+ - **A probe review can no longer attest a tree — and the receipt says so itself.** Both wrappers wrote
87
+ receipts unconditionally, so a `CODEX_PROBE=1` / `AGY_PROBE=1` review — which runs with the
88
+ frontier-model/max-effort guard **off** — minted a `fresh:true`/`grounded:true` receipt the
89
+ review-state gate accepted. Both wrappers now write `probe` on **every** successful review, `true` or
90
+ `false`, through the shared byte-identical `write_review_receipt` block: a receipt self-declares and
91
+ nothing has to infer it. The kit rejects a probe-marked receipt (a probe never attests) and **equally
92
+ rejects an unmarked one — silence is not a declaration**.
93
+ - **One attesting predicate, three consumers.** `classifyReviewReceiptForTree` /
94
+ `summarizeReviewReceiptsForTree` / `describeMissingReviewAttestation` live in the neutral
95
+ `review-ledger-core.mjs` and are read by `review-state.mjs`, `receiptCrossCheck` and the round writer
96
+ — two gates disagreeing about what attests is precisely the class AD-050 closed, and a second copy
97
+ would re-open it. The shared summary also fixes a latent hole: the ledger took `own[own.length - 1]`,
98
+ so a probe landing **after** a real review became the authoritative verdict — a probe SHIP could bury
99
+ a real REWORK and let both gates report convergence. The summary now returns the latest **attesting**
100
+ receipt, never the last line.
101
+ - **Scope of the claim.** What a marker carries is UNTRUSTWORTHINESS, never provenance — receipts are
102
+ not authenticated, and a forger could write `probe:false` as easily as any other field. This is
103
+ self-discipline made legible, not a security boundary. See the BREAKING callout above for the upgrade
104
+ path; full record, including the mid-execution amendment of the original design and the stated
105
+ residuals, in AD-057.
106
+
7
107
  ## 1.49.0 — Honesty/robustness bundle: refresh EROFS stated skip · settings integer-overflow parity (AD-056)
8
108
 
9
109
  A small **honesty/robustness** release (kit MINOR carrying the two bridge PATCH bumps in-tarball —
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: '1.49.0'
6
+ version: '2.1.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 no-JSON headless behaviour.
4
4
  metadata:
5
- version: '2.6.1'
5
+ version: '3.0.0'
6
6
  ---
7
7
 
8
8
  # antigravity-cli-bridge
@@ -69,6 +69,14 @@ list wins.
69
69
 
70
70
  ## Usage
71
71
 
72
+ > **The machine-readable mode catalog lives in [`capability.json`](capability.json) `modeCatalog`** —
73
+ > every documented mode (`agy-run` raw prompts, the three `agy-review` modes, their continuations,
74
+ > the `AGY_PROBE` escape) with its purpose, when to use it (and when not), the exact invocation form
75
+ > with its operand slots, and the guardrails that really apply. The catalog tracks **the documented
76
+ > wrapper mode set** (never "the CLI's modes"): an upstream Antigravity CLI change reaches it through
77
+ > a bridge release, where the source-level drift tests fail loudly until the catalog is updated.
78
+ > Nothing probes a live CLI. The prose below stays the human tour.
79
+
72
80
  Drive `agy` only through the wrapper [`bin/agy.sh`](bin/agy.sh) (installed on `PATH` as `agy-run`):
73
81
 
74
82
  ```bash
@@ -110,11 +118,16 @@ review **guesses** (stale-model and partial-diff false positives). `agy-review`
110
118
  POSTURE + a **model/cutoff GUARD** + your **`--facts`** (the verified facts the model reviews AGAINST)
111
119
  + **`--decided`** (the anti-circling list) + **`--focus`** + the artifact + a strict output shape, then
112
120
  delegates execution to `agy-run` (one home for the timeout, the subscription invariant, and the byte
113
- ceiling).
121
+ ceiling). In `code` mode grounding **fails closed**: without a non-empty `--facts` payload the wrapper
122
+ refuses **before spending a run** (an ungrounded code receipt records `grounded:false`, which the
123
+ review-state gate rejects — the run would attest nothing). The explicit escapes are `--ungrounded`
124
+ (a throwaway opinion) and `AGY_PROBE=1` (a probe never attests anyway); `plan`/`diff` keep the loud
125
+ warning and proceed.
114
126
 
115
127
  ```bash
116
- agy-review code [--facts @facts.md] [--decided @decided.md] [--focus "…"] # the repo-complete diff
117
- agy-review plan <plan-file> [--facts @f] … # critique a plan
128
+ agy-review code --facts @facts.md [--decided @decided.md] [--focus "…"] # the repo-complete diff (facts REQUIRED — fails closed pre-spend)
129
+ agy-review code --ungrounded # explicit escape: throwaway ungrounded opinion (receipt never attests)
130
+ agy-review plan <plan-file> [--facts @f] … # critique a plan (no facts ⇒ loud warning)
118
131
  agy-review diff <diff-file> [--facts @f] … # review a supplied diff
119
132
  agy-review --continue --decided @round1.md --focus "still-open items" # round-2 delta, no re-assembly
120
133
  ```
@@ -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,17 +92,21 @@ 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
- folded tree); a write failure warns, never fails the review
99
+ folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY
100
+ receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe
101
+ review never attests) and equally rejects an unmarked one (silence is not a declaration); a write
102
+ failure warns, never fails the review
95
103
 
96
104
  Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins over built-in default):
97
105
  ${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf
98
106
  AGY_HARD_TIMEOUT — hard wall-clock cap, duration string like 5m/30m/90s (built-in default 30m)
99
107
  AGY_REVIEW_ALLOW_ADDDIR — boolean 0/1: 1 arms the oversized --add-dir escape (re-enables the Issue-001 stall risk; default 0)
100
108
 
101
- Closed grammar: unknown flags are rejected; no '--' passthrough (the only escapes are AGY_PROBE=1 and AGY_REVIEW_ALLOW_ADDDIR=1).
109
+ 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).
102
110
  Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
103
111
  HELP
104
112
  exit 0
@@ -217,7 +225,7 @@ DEFAULT_AGY_REVIEW_MODEL="Gemini 3.1 Pro (High)"
217
225
  # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
218
226
  # version (drift-guarded by agy-review.test.mjs against capability.json).
219
227
  AW_RECEIPT_BACKEND="agy"
220
- AW_BRIDGE_VERSION="2.6.1"
228
+ AW_BRIDGE_VERSION="3.0.0"
221
229
  # `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
222
230
  AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
223
231
  # Frontier review models. ANY model is allowed; a sub-frontier one only earns a soft, silenceable warning.
@@ -228,6 +236,10 @@ FRONTIER_SET=("Gemini 3.1 Pro (High)" "Claude Opus 4.6 (Thinking)" "Claude Sonne
228
236
  AGY_HARD_TIMEOUT="${AGY_HARD_TIMEOUT:-30m}"
229
237
  AGY_TIMEOUT="${AGY_TIMEOUT:-$AGY_HARD_TIMEOUT}"
230
238
  AGY_PROBE="${AGY_PROBE:-0}"
239
+ # A probe run is RECORDED, not just silently allowed: the receipt carries probe:true so the kit's
240
+ # review-state gate rejects it — a guards-relaxed review must never attest a tree.
241
+ REVIEW_PROBE=false
242
+ if [[ "$AGY_PROBE" == "1" ]]; then REVIEW_PROBE=true; fi
231
243
  AGY_REVIEW_ALLOW_ADDDIR="${AGY_REVIEW_ALLOW_ADDDIR:-0}"
232
244
  AGY_MAX_PROMPT_BYTES="${AGY_MAX_PROMPT_BYTES:-120000}"
233
245
  if [[ ! "$AGY_MAX_PROMPT_BYTES" =~ ^[0-9]+$ ]]; then
@@ -432,13 +444,18 @@ receipt_json_scalar() {
432
444
  if [[ -z "${1:-}" ]]; then printf 'null'; else printf '"%s"' "$1"; fi
433
445
  }
434
446
 
435
- # write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|"">
447
+ # write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|""> [probe: true|false]
436
448
  # Appends ONE receipt line (the AD-038 fixture shape) as a side effect of a SUCCESSFUL review —
437
449
  # to $AW_REVIEW_RECEIPTS when set, else <git dir>/agent-workflow-review-receipts.jsonl (inside the
438
450
  # git dir by construction, so it is never committable). Fail-safe: every failure here warns loudly
439
451
  # and returns 0 — a missing receipt fails the kit's review-state CHECKER, never the review run.
452
+ # The optional 7th argument marks a PROBE run (CODEX_PROBE=1 / AGY_PROBE=1 — the quality guards
453
+ # relaxed), which the kit's review-state gate rejects: a probe review must never attest a tree. The
454
+ # marker is written ALWAYS, true or false: the receipt SELF-DECLARES, so the gate reads the fact
455
+ # itself instead of inferring it from this wrapper's version (which bumps in a different release
456
+ # phase). Silence is not a declaration — an unmarked receipt is untrustworthy and the gate rejects it.
440
457
  write_review_receipt() {
441
- local artifact="$1" fresh="$2" fingerprint="$3" verdict="$4" grounded="$5" facts_hash="$6"
458
+ local artifact="$1" fresh="$2" fingerprint="$3" verdict="$4" grounded="$5" facts_hash="$6" probe="${7:-false}"
442
459
  local receipts="${AW_REVIEW_RECEIPTS:-}"
443
460
  if [[ -z "$receipts" ]]; then
444
461
  local receipt_git_dir
@@ -448,11 +465,12 @@ write_review_receipt() {
448
465
  fi
449
466
  receipts="$receipt_git_dir/agent-workflow-review-receipts.jsonl"
450
467
  fi
451
- local line
452
- line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"}' \
468
+ local line probe_field=',"probe":false'
469
+ if [[ "$probe" == "true" ]]; then probe_field=',"probe":true'; fi
470
+ line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"%s}' \
453
471
  "$(receipt_json_scalar "$artifact")" "$fresh" "$(receipt_json_scalar "$fingerprint")" \
454
472
  "$AW_RECEIPT_BACKEND" "$verdict" "$grounded" "$(receipt_json_scalar "$facts_hash")" \
455
- "$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)")"
473
+ "$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field")"
456
474
  if ! printf '%s\n' "$line" >>"$receipts" 2>/dev/null; then
457
475
  echo "warning: could not append the review receipt to $receipts — the review itself succeeded;" >&2
458
476
  echo " the review-state gate will read the current tree as un-receipted." >&2
@@ -528,7 +546,7 @@ case "${1:-}" in
528
546
  esac
529
547
 
530
548
  usage() {
531
- echo "usage: $0 code [--facts @f] [--decided @f] [--focus \"…\"] [extra focus…]" >&2
549
+ echo "usage: $0 code [--facts @f] [--ungrounded] [--decided @f] [--focus \"…\"] [extra focus…]" >&2
532
550
  echo " $0 plan <plan-file> [--facts @f] [--decided @f] [--focus \"…\"]" >&2
533
551
  echo " $0 diff <diff-file> [--facts @f] [--decided @f] [--focus \"…\"]" >&2
534
552
  echo " $0 --continue [--decided @f] [--focus \"…\"]" >&2
@@ -567,6 +585,7 @@ fi
567
585
  # --- Flag parse (--facts / --decided / --focus + trailing focus) -------------
568
586
  FACTS_RAW=""
569
587
  DECIDED_RAW=""
588
+ UNGROUNDED=0
570
589
  FOCUS_PARTS=()
571
590
  # A value-taking flag must be followed by a real value — never end-of-args and never another flag.
572
591
  # Otherwise `agy-review code --facts --focus x` would silently take "--focus" as the facts and spend a
@@ -585,6 +604,12 @@ while [[ $# -gt 0 ]]; do
585
604
  exit 2
586
605
  fi
587
606
  need_value "$1" "${2:-}"; FACTS_RAW="$2"; shift 2 ;;
607
+ --ungrounded)
608
+ if [[ -n "$resume_mode" ]]; then
609
+ echo "error: --ungrounded is not valid on a continuation (the original round already set its grounding)." >&2
610
+ exit 2
611
+ fi
612
+ UNGROUNDED=1; shift ;;
588
613
  --decided)
589
614
  need_value "$1" "${2:-}"; DECIDED_RAW="$2"; shift 2 ;;
590
615
  --focus)
@@ -610,6 +635,18 @@ done
610
635
  # Merge --focus values and trailing focus words, in parse order, into ONE focus block.
611
636
  FOCUS="${FOCUS_PARTS[*]:-}"
612
637
 
638
+ # --ungrounded closed grammar (D4): code-mode only, and an explicit contradiction with --facts refuses.
639
+ if [[ "$UNGROUNDED" == "1" ]]; then
640
+ if [[ -n "$FACTS_RAW" ]]; then
641
+ echo "error: --ungrounded contradicts --facts — pass the verified facts (grounded) or drop them (--ungrounded), never both." >&2
642
+ exit 2
643
+ fi
644
+ if [[ "$mode" != "code" ]]; then
645
+ echo "error: --ungrounded is only valid in code mode — $mode mode already proceeds ungrounded with a loud warning." >&2
646
+ exit 2
647
+ fi
648
+ fi
649
+
613
650
  # Resolve @file / literal for --facts and --decided NOW (cwd = invocation, before any code-mode cd).
614
651
  # The `@file` existence check runs at TOP LEVEL (not inside a command substitution) so its exit-2 exits
615
652
  # the whole script, not just a subshell.
@@ -635,6 +672,25 @@ if [[ -n "$DECIDED_RAW" ]]; then
635
672
  fi
636
673
 
637
674
  if [[ -z "$FACTS_CONTENT" && -z "$resume_mode" ]]; then
675
+ # code mode fails CLOSED before the spend (D4): an ungrounded CODE receipt records grounded:false,
676
+ # which the review-state gate rejects — the run would be paid for and attest nothing. Keyed on the
677
+ # resolved CONTENT, so --facts naming an empty payload refuses identically.
678
+ if [[ "$mode" == "code" && "$UNGROUNDED" != "1" && "$AGY_PROBE" != "1" ]]; then
679
+ # The recovery hint resolves the kit's grounding tool from THIS wrapper's location (monorepo
680
+ # canon / deployed skills sibling / kit-bundled mirror) — a repo-relative path would not exist
681
+ # for a globally installed kit. Quoted: an install path may carry spaces.
682
+ grounding_tool="node <agent-workflow-kit>/tools/grounding.mjs"
683
+ for _g in "$HERE/../../agent-workflow-kit/tools/grounding.mjs" "$HERE/../../../tools/grounding.mjs"; do
684
+ if [[ -f "$_g" ]]; then grounding_tool="node \"$_g\""; break; fi
685
+ done
686
+ echo "error: 'agy-review code' requires grounded facts and refuses BEFORE spending a run — an" >&2
687
+ echo " ungrounded code review GUESSES, and its receipt (grounded:false) never attests." >&2
688
+ echo " Assemble the verified facts, then re-run:" >&2
689
+ echo " $grounding_tool --constraints [--plan <plan-file>] --out <facts-file>" >&2
690
+ echo " agy-review code --facts @<facts-file>" >&2
691
+ echo " Explicit escapes: --ungrounded (throwaway opinion) · AGY_PROBE=1 (a probe never attests)." >&2
692
+ exit 2
693
+ fi
638
694
  echo "warning: no --facts supplied. agy reads NOTHING by default, so an ungrounded review GUESSES" >&2
639
695
  echo " (stale-model and partial-diff false positives). Pass --facts @file with the verified" >&2
640
696
  echo " facts the model must review AGAINST. Proceeding without grounding." >&2
@@ -795,7 +851,7 @@ if [[ $rc -eq 0 ]]; then
795
851
  # A continuation never re-embeds the current artifact (agy holds the ORIGINAL round server-side;
796
852
  # --facts is rejected above), so it cannot attest the folded tree: fresh:false, artifact /
797
853
  # fingerprint / factsHash null, grounded false — informational-only, ignored by the state gate.
798
- write_review_receipt "" false "" "$verdict" false ""
854
+ write_review_receipt "" false "" "$verdict" false "" "$REVIEW_PROBE"
799
855
  echo "notice: a continuation receipt is fresh:false (informational-only) — only a fresh grounded run" >&2
800
856
  echo " (agy-review code --facts @f) mints a receipt that satisfies the review-state gate." >&2
801
857
  else
@@ -805,7 +861,7 @@ if [[ $rc -eq 0 ]]; then
805
861
  grounded=true
806
862
  facts_hash="$(printf '%s' "$FACTS_CONTENT" | sha256_stdin || true)"
807
863
  fi
808
- write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" "$grounded" "$facts_hash"
864
+ write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" "$grounded" "$facts_hash" "$REVIEW_PROBE"
809
865
  fi
810
866
  fi
811
867
  exit $rc