@sabaiway/agent-workflow-kit 2.0.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,35 @@ 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
+
7
36
  ## 2.0.0 — Bridge mode catalog (manifest-as-source) + a review receipt that SELF-DECLARES (AD-057)
8
37
 
9
38
  > ### ⚠ BREAKING — a review receipt written before this release no longer attests a tree
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: '2.0.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.7.0'
5
+ version: '3.0.0'
6
6
  ---
7
7
 
8
8
  # antigravity-cli-bridge
@@ -118,11 +118,16 @@ review **guesses** (stale-model and partial-diff false positives). `agy-review`
118
118
  POSTURE + a **model/cutoff GUARD** + your **`--facts`** (the verified facts the model reviews AGAINST)
119
119
  + **`--decided`** (the anti-circling list) + **`--focus`** + the artifact + a strict output shape, then
120
120
  delegates execution to `agy-run` (one home for the timeout, the subscription invariant, and the byte
121
- 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.
122
126
 
123
127
  ```bash
124
- agy-review code [--facts @facts.md] [--decided @decided.md] [--focus "…"] # the repo-complete diff
125
- 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)
126
131
  agy-review diff <diff-file> [--facts @f] … # review a supplied diff
127
132
  agy-review --continue --decided @round1.md --focus "still-open items" # round-2 delta, no re-assembly
128
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,7 +92,8 @@ 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
@@ -101,7 +106,7 @@ Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins ov
101
106
  AGY_HARD_TIMEOUT — hard wall-clock cap, duration string like 5m/30m/90s (built-in default 30m)
102
107
  AGY_REVIEW_ALLOW_ADDDIR — boolean 0/1: 1 arms the oversized --add-dir escape (re-enables the Issue-001 stall risk; default 0)
103
108
 
104
- 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).
105
110
  Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
106
111
  HELP
107
112
  exit 0
@@ -220,7 +225,7 @@ DEFAULT_AGY_REVIEW_MODEL="Gemini 3.1 Pro (High)"
220
225
  # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
221
226
  # version (drift-guarded by agy-review.test.mjs against capability.json).
222
227
  AW_RECEIPT_BACKEND="agy"
223
- AW_BRIDGE_VERSION="2.7.0"
228
+ AW_BRIDGE_VERSION="3.0.0"
224
229
  # `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
225
230
  AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
226
231
  # Frontier review models. ANY model is allowed; a sub-frontier one only earns a soft, silenceable warning.
@@ -541,7 +546,7 @@ case "${1:-}" in
541
546
  esac
542
547
 
543
548
  usage() {
544
- 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
545
550
  echo " $0 plan <plan-file> [--facts @f] [--decided @f] [--focus \"…\"]" >&2
546
551
  echo " $0 diff <diff-file> [--facts @f] [--decided @f] [--focus \"…\"]" >&2
547
552
  echo " $0 --continue [--decided @f] [--focus \"…\"]" >&2
@@ -580,6 +585,7 @@ fi
580
585
  # --- Flag parse (--facts / --decided / --focus + trailing focus) -------------
581
586
  FACTS_RAW=""
582
587
  DECIDED_RAW=""
588
+ UNGROUNDED=0
583
589
  FOCUS_PARTS=()
584
590
  # A value-taking flag must be followed by a real value — never end-of-args and never another flag.
585
591
  # Otherwise `agy-review code --facts --focus x` would silently take "--focus" as the facts and spend a
@@ -598,6 +604,12 @@ while [[ $# -gt 0 ]]; do
598
604
  exit 2
599
605
  fi
600
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 ;;
601
613
  --decided)
602
614
  need_value "$1" "${2:-}"; DECIDED_RAW="$2"; shift 2 ;;
603
615
  --focus)
@@ -623,6 +635,18 @@ done
623
635
  # Merge --focus values and trailing focus words, in parse order, into ONE focus block.
624
636
  FOCUS="${FOCUS_PARTS[*]:-}"
625
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
+
626
650
  # Resolve @file / literal for --facts and --decided NOW (cwd = invocation, before any code-mode cd).
627
651
  # The `@file` existence check runs at TOP LEVEL (not inside a command substitution) so its exit-2 exits
628
652
  # the whole script, not just a subshell.
@@ -648,6 +672,25 @@ if [[ -n "$DECIDED_RAW" ]]; then
648
672
  fi
649
673
 
650
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
651
694
  echo "warning: no --facts supplied. agy reads NOTHING by default, so an ungrounded review GUESSES" >&2
652
695
  echo " (stale-model and partial-diff false positives). Pass --facts @file with the verified" >&2
653
696
  echo " facts the model must review AGAINST. Proceeding without grounding." >&2
@@ -190,17 +190,100 @@ describe('agy-review.sh — guard + grounding (2, 3)', () => {
190
190
  assert.match(r.prompt, /## Focus\nfirst second third/);
191
191
  });
192
192
 
193
- it('warns LOUDLY when --facts is omitted, and proceeds', () => {
193
+ it('plan mode with no --facts keeps the warning and proceeds (unchanged contract)', () => {
194
194
  const sb = makeSandbox();
195
- const r = run(sb, { args: ['code'] });
195
+ writeFileSync(join(sb.repo, 'p.md'), '# plan body\n');
196
+ const r = run(sb, { args: ['plan', 'p.md'] });
196
197
  rmSync(sb.home, { recursive: true, force: true });
197
198
  assert.equal(r.status, 0, r.stderr);
198
199
  assert.match(r.stderr, /no --facts supplied/);
199
- assert.equal(r.invoked, true, 'an ungrounded review still proceeds (warn, not block)');
200
+ assert.equal(r.invoked, true, 'an ungrounded plan review still proceeds (warn, not block)');
200
201
  assert.match(r.prompt, /none supplied/, 'the prompt notes the missing facts in-band');
201
202
  });
202
203
  });
203
204
 
205
+ // ── code mode fails CLOSED without grounded facts (D4) ───────────────────────────
206
+ // An ungrounded CODE receipt records grounded:false, which the kit's review-state gate rejects —
207
+ // the run would be paid for and attest nothing. The wrapper refuses BEFORE the spend, keyed on the
208
+ // resolved CONTENT (an empty --facts payload refuses identically). Escapes: the explicit
209
+ // --ungrounded flag (throwaway opinion) and AGY_PROBE=1 (a probe receipt never attests anyway).
210
+ describe('agy-review.sh — code mode fails CLOSED without grounded facts (D4)', () => {
211
+ it('code mode with no --facts exits 2 before any agy invocation', () => {
212
+ const sb = makeSandbox();
213
+ const r = run(sb, { args: ['code'] });
214
+ rmSync(sb.home, { recursive: true, force: true });
215
+ assert.equal(r.status, 2, r.stderr);
216
+ assert.equal(r.invoked, false, 'the refusal must fire before any agy invocation — zero runs spent');
217
+ assert.match(r.stderr, /grounding\.mjs/, 'the refusal names the facts assembler');
218
+ assert.match(r.stderr, /agy-review code --facts @/, 'the refusal prints the exact re-run line');
219
+ const hint = r.stderr.match(/node "([^"]+grounding\.mjs)"/);
220
+ assert.ok(hint, 'the recovery hint resolves and QUOTES a real grounding.mjs path (an install path may carry spaces)');
221
+ assert.ok(existsSync(hint[1]), 'the resolved hint path exists on this layout');
222
+ });
223
+
224
+ it('code mode with --facts naming an EMPTY payload exits 2 before any agy invocation', () => {
225
+ const sb = makeSandbox();
226
+ writeFileSync(join(sb.repo, 'empty-facts.md'), '');
227
+ const r = run(sb, { args: ['code', '--facts', '@empty-facts.md'] });
228
+ rmSync(sb.home, { recursive: true, force: true });
229
+ assert.equal(r.status, 2, 'the refusal keys on the CONTENT, not the flag');
230
+ assert.equal(r.invoked, false, 'an empty payload must not spend a run');
231
+ assert.match(r.stderr, /agy-review code --facts @/);
232
+ });
233
+
234
+ it('code --ungrounded proceeds and the receipt records grounded:false', () => {
235
+ const sb = makeSandbox();
236
+ const r = run(sb, { args: ['code', '--ungrounded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
237
+ const receipts = readReceipts(sb.repo);
238
+ rmSync(sb.home, { recursive: true, force: true });
239
+ assert.equal(r.status, 0, r.stderr);
240
+ assert.equal(r.invoked, true, 'the explicit escape lets the run proceed');
241
+ assert.equal(receipts.length, 1);
242
+ assert.equal(receipts[0].grounded, false, 'an --ungrounded run still records grounded:false');
243
+ assert.equal(receipts[0].factsHash, null);
244
+ assert.match(r.stderr, /no --facts supplied/, 'the escape path stays loud, never silent');
245
+ });
246
+
247
+ it('AGY_PROBE=1 code with no --facts proceeds and the receipt records probe:true', () => {
248
+ const sb = makeSandbox();
249
+ const r = run(sb, { args: ['code'], env: { AGY_PROBE: '1', AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
250
+ const receipts = readReceipts(sb.repo);
251
+ rmSync(sb.home, { recursive: true, force: true });
252
+ assert.equal(r.status, 0, r.stderr);
253
+ assert.equal(r.invoked, true, 'an ungrounded probe is coherent — a probe receipt never attests');
254
+ assert.equal(receipts[0].probe, true);
255
+ assert.equal(receipts[0].grounded, false);
256
+ });
257
+
258
+ it('--ungrounded with --facts is a refusal (contradiction)', () => {
259
+ const sb = makeSandbox();
260
+ const r = run(sb, { args: ['code', '--ungrounded', '--facts', 'f'] });
261
+ rmSync(sb.home, { recursive: true, force: true });
262
+ assert.equal(r.status, 2);
263
+ assert.equal(r.invoked, false);
264
+ assert.match(r.stderr, /--ungrounded contradicts --facts/);
265
+ });
266
+
267
+ it('--ungrounded outside code mode is a refusal', () => {
268
+ const sb = makeSandbox();
269
+ writeFileSync(join(sb.repo, 'p.md'), '# p\n');
270
+ const r = run(sb, { args: ['plan', 'p.md', '--ungrounded'] });
271
+ rmSync(sb.home, { recursive: true, force: true });
272
+ assert.equal(r.status, 2);
273
+ assert.equal(r.invoked, false);
274
+ assert.match(r.stderr, /--ungrounded is only valid in code mode/);
275
+ });
276
+
277
+ it('--ungrounded on a continuation is a refusal', () => {
278
+ const sb = makeSandbox();
279
+ const r = run(sb, { args: ['--continue', '--ungrounded'] });
280
+ rmSync(sb.home, { recursive: true, force: true });
281
+ assert.equal(r.status, 2);
282
+ assert.equal(r.invoked, false);
283
+ assert.match(r.stderr, /--ungrounded is not valid on a continuation/);
284
+ });
285
+ });
286
+
204
287
  describe('agy-review.sh — code-mode precomputed diff (4, 5, 8)', () => {
205
288
  it('assembles repo map + status + untracked CONTENTS', () => {
206
289
  const sb = makeSandbox();
@@ -754,7 +837,12 @@ describe('agy-review.sh — declared contract is really accepted (forward guard)
754
837
  for (const descriptor of REVIEW_CONTRACT.flags) {
755
838
  const flag = leadingFlag(descriptor);
756
839
  const sb = makeSandbox();
757
- const r = run(sb, { args: ['code', flag, 'f'] });
840
+ // D4: code mode refuses without grounded facts, so a non-facts value flag is driven on a
841
+ // grounded run; --ungrounded takes no value, contradicts --facts, and is driven alone.
842
+ const args = flag === '--facts' ? ['code', '--facts', 'f']
843
+ : flag === '--ungrounded' ? ['code', '--ungrounded']
844
+ : ['code', '--facts', 'f', flag, 'f'];
845
+ const r = run(sb, { args });
758
846
  rmSync(sb.home, { recursive: true, force: true });
759
847
  assert.equal(r.status, 0, `${flag}: ${r.stderr}`);
760
848
  }
@@ -853,9 +941,9 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
853
941
  assert.equal(receipts[0].probe, true, 'both write paths carry the marker — no unmarked probe lane');
854
942
  });
855
943
 
856
- it('an ungrounded fresh run records grounded:false + factsHash null (the vacuous-grounding hole stays visible)', () => {
944
+ it('an --ungrounded fresh run records grounded:false + factsHash null (the vacuous-grounding hole stays visible)', () => {
857
945
  const sb = makeSandbox();
858
- const r = run(sb, { args: ['code'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
946
+ const r = run(sb, { args: ['code', '--ungrounded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
859
947
  const receipts = readReceipts(sb.repo);
860
948
  rmSync(sb.home, { recursive: true, force: true });
861
949
  assert.equal(r.status, 0, r.stderr);
@@ -863,16 +951,15 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
863
951
  assert.equal(receipts[0].factsHash, null);
864
952
  });
865
953
 
866
- it('an EMPTY --facts file records grounded:false (fail-closedvacuous grounding never satisfies the gate) and still warns', () => {
954
+ it('an EMPTY --facts file in code mode refuses pre-spendno run, no receipt (D4 fail-closed)', () => {
867
955
  const sb = makeSandbox();
868
956
  writeFileSync(join(sb.home, 'empty-facts.md'), '');
869
957
  const r = run(sb, { args: ['code', '--facts', `@${join(sb.home, 'empty-facts.md')}`], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
870
958
  const receipts = readReceipts(sb.repo);
871
959
  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');
960
+ assert.equal(r.status, 2, 'vacuous grounding no longer spends a run');
961
+ assert.equal(r.invoked, false);
962
+ assert.equal(receipts.length, 0, 'no run — no receipt');
876
963
  });
877
964
 
878
965
  it('parses REWORK and plain SHIP; records "unknown" when the mandated section is absent', () => {
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "antigravity-cli-bridge",
5
5
  "kind": "execution-backend",
6
- "version": "2.7.0",
6
+ "version": "3.0.0",
7
7
  "provides": ["review", "probe"],
8
8
  "roles": {
9
9
  "review": {
@@ -14,13 +14,14 @@
14
14
  "output": "advisory",
15
15
  "contract": {
16
16
  "invocations": [
17
- "agy-review code [--facts @f] [--decided @f] [--focus \"…\"] [extra focus…]",
17
+ "agy-review code [--facts @f] [--ungrounded] [--decided @f] [--focus \"…\"] [extra focus…]",
18
18
  "agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus \"…\"]",
19
19
  "agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus \"…\"]"
20
20
  ],
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)",
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). 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
22
  "flags": [
23
- "--facts @f — verified facts the review runs AGAINST (omit loud ungrounded-review warning)",
23
+ "--facts @f — verified facts the review runs AGAINST (code mode REQUIRES a non-empty payload; plan/diff warn loudly when omitted)",
24
+ "--ungrounded — deliberately ungrounded CODE review, a throwaway opinion (code mode only, contradicts --facts; the receipt records grounded:false and never attests)",
24
25
  "--decided @f — already-decided / already-addressed list; do NOT re-raise (anti-circling; the round-2 payload)",
25
26
  "--focus \"…\" — extra focus (repeatable; code mode also takes trailing focus words)"
26
27
  ],
@@ -28,7 +29,7 @@
28
29
  "agy-review --continue [--decided @f] [--focus \"…\"]",
29
30
  "agy-review --conversation <id> [--decided @f] [--focus \"…\"]"
30
31
  ],
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",
32
+ "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); a write failure warns, never fails the review",
32
33
  "notes": [
33
34
  "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
35
  ]
@@ -50,7 +51,8 @@
50
51
  "whenNotTo": ["a clean tree — the wrapper exits before spending a run"],
51
52
  "invocationRefs": [{ "contractField": "invocations", "index": 0 }],
52
53
  "operands": [
53
- { "slot": "[--facts @f]", "required": false, "description": "the verified facts to review AGAINST; omitting it warns loudly and records the review ungrounded" },
54
+ { "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)" },
55
+ { "slot": "[--ungrounded]", "required": false, "description": "deliberately ungrounded code review — a throwaway opinion; the receipt records grounded:false and never attests" },
54
56
  { "slot": "[--decided @f]", "required": false, "description": "decisions already made; the reviewer must not re-raise them (anti-circling)" },
55
57
  { "slot": "[--focus \"…\"]", "required": false, "description": "what this review must look at (repeatable)" },
56
58
  { "slot": "[extra focus…]", "required": false, "description": "extra focus words appended to the review directive" }
@@ -59,6 +61,7 @@
59
61
  { "value": "read-only posture — the prompt forbids edits, commands and git writes", "enforcement": "advisory", "source": "bin/agy-review.sh" },
60
62
  { "value": "receives no ambient repo/file context by default — the wrapper passes no --add-dir", "enforcement": "enforced", "source": "bin/agy-review.sh" },
61
63
  { "value": "an ungrounded review guesses — stale-model and partial-diff false positives", "enforcement": "advisory", "source": "bin/agy-review.sh" },
64
+ { "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
65
  { "value": "an ungrounded run records grounded:false and the review-state gate rejects it", "enforcement": "enforced", "source": "capability.json roles.review.contract.receipt" },
63
66
  { "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
67
  ],
@@ -185,9 +188,9 @@
185
188
  "deliberately reviewing with a cheaper non-frontier model"
186
189
  ],
187
190
  "whenNotTo": ["any run whose output informs what ships — a probe never attests"],
188
- "descriptor": "AGY_PROBE=1 agy-review code --facts @<facts-file>",
191
+ "descriptor": "AGY_PROBE=1 agy-review code [--facts @<facts-file>]",
189
192
  "operands": [
190
- { "slot": "@<facts-file>", "required": true, "description": "the verified facts the review runs AGAINST" }
193
+ { "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
194
  ],
192
195
  "guardrails": [
193
196
  { "value": "a probe review mints a probe-marked receipt the review-state gate rejects", "enforcement": "enforced", "source": "agent-workflow-kit tools/review-state.mjs" }
@@ -58,13 +58,17 @@ prompt for `agy-run`. It **mechanizes the grounded-review contract** (see
58
58
  [`review-prompt.md`](./review-prompt.md)) so grounding is the enforced default, not a per-call effort:
59
59
 
60
60
  ```bash
61
- agy-review code [--facts @facts.md] [--decided @decided.md] [--focus "…"] [extra focus…]
62
- agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus "…"]
61
+ agy-review code --facts @facts.md [--decided @decided.md] [--focus "…"] [extra focus…] # facts REQUIRED — refuses pre-spend
62
+ agy-review code --ungrounded # explicit escape: throwaway ungrounded opinion (never attests)
63
+ agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus "…"] # no facts ⇒ loud warning, proceeds
63
64
  agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus "…"]
64
65
  agy-review --continue [--decided @f] [--focus "…"] # round-2 delta — no re-assembly
65
66
  agy-review --conversation <id> [--decided @f] [--focus "…"]
66
67
  ```
67
68
 
69
+ `code` fails CLOSED without a non-empty `--facts` payload — the refusal fires before any run is
70
+ spent; the explicit escapes are `--ungrounded` and `AGY_PROBE=1` (a probe never attests).
71
+
68
72
  What it does for you, and what YOU must supply:
69
73
 
70
74
  - **It** assembles POSTURE + a model/cutoff GUARD + your facts + your already-decided list + your focus
@@ -48,14 +48,15 @@ subscription invariant, and the byte ceiling all apply once. The playbook is in
48
48
  [`driving-agy.md`](./driving-agy.md); the surface:
49
49
 
50
50
  ```bash
51
- agy-review code|plan <file>|diff <file> [--facts @f] [--decided @f] [--focus "…"]
51
+ agy-review code --facts @f [--decided @f] [--focus "…"] # facts REQUIRED — refuses pre-spend (escapes: --ungrounded, AGY_PROBE=1)
52
+ agy-review plan <file>|diff <file> [--facts @f] [--decided @f] [--focus "…"]
52
53
  agy-review --continue | --conversation <id> [--decided @f] [--focus "…"] # round-2 delta
53
54
  ```
54
55
 
55
56
  | Var | Default | Effect |
56
57
  |---|---|---|
57
58
  | `AGY_MODEL` | `Gemini 3.1 Pro (High)` | frontier default; **any** model is allowed — a sub-frontier one earns a silenceable advisory (quality-first, not a gate) |
58
- | `AGY_PROBE` | `0` | `1` silences the off-frontier model advisory (throwaway probe) |
59
+ | `AGY_PROBE` | `0` | `1` silences the off-frontier model advisory AND lets `code` run without `--facts` (an ungrounded probe never attests — its receipt is probe-marked) |
59
60
  | `AGY_REVIEW_ALLOW_ADDDIR` | `0` | `1` lets an oversized `code` review offload ONLY the change set to a private staging dir via `--add-dir` (the grounding stays inline; re-enables the Issue-001 stall risk — prefer splitting into focused reviews) |
60
61
  | `AGY_HARD_TIMEOUT` | `30m` | the review's hard cap (longer default than a probe — reviews are slower) |
61
62
  | `AGY_MAX_PROMPT_BYTES` | `120000` | the same single-argv byte ceiling; oversized → trim/split (or the `--add-dir` escape above) |
@@ -23,7 +23,7 @@ GUARD Do NOT comment on AI model names/versions or your own knowledge cutoff
23
23
  and a known source of false positives. Review ONLY the engineering, AGAINST the facts.
24
24
 
25
25
  ## Grounded facts — review AGAINST these, do NOT guess the code
26
- {{GROUNDED_FACTS}} # from --facts @file (omitted -> an in-band note + a LOUD stderr warning)
26
+ {{GROUNDED_FACTS}} # from --facts @file (code: REQUIRED non-empty, refuses pre-spend — escapes --ungrounded / AGY_PROBE=1; plan/diff omitted -> an in-band note + a LOUD stderr warning)
27
27
 
28
28
  ## Decisions already made / already addressed — do NOT re-raise these
29
29
  {{ALREADY_DECIDED}} # from --decided @file (optional — the anti-circling lever; the round-2 payload)
package/capability.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "agent-workflow-kit",
5
5
  "kind": "composition-root",
6
- "version": "2.0.0",
6
+ "version": "2.1.0",
7
7
  "provides": [],
8
8
  "roles": {},
9
9
  "detect": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sabaiway/agent-workflow-kit",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -110,13 +110,14 @@ const RAW_BACKENDS = [
110
110
  roleContracts: {
111
111
  review: {
112
112
  invocations: [
113
- 'agy-review code [--facts @f] [--decided @f] [--focus "…"] [extra focus…]',
113
+ 'agy-review code [--facts @f] [--ungrounded] [--decided @f] [--focus "…"] [extra focus…]',
114
114
  'agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus "…"]',
115
115
  'agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus "…"]',
116
116
  ],
117
- 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)',
117
+ 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',
118
118
  flags: [
119
- '--facts @f — verified facts the review runs AGAINST (omit loud ungrounded-review warning)',
119
+ '--facts @f — verified facts the review runs AGAINST (code mode REQUIRES a non-empty payload; plan/diff warn loudly when omitted)',
120
+ '--ungrounded — deliberately ungrounded CODE review, a throwaway opinion (code mode only, contradicts --facts; the receipt records grounded:false and never attests)',
120
121
  '--decided @f — already-decided / already-addressed list; do NOT re-raise (anti-circling; the round-2 payload)',
121
122
  '--focus "…" — extra focus (repeatable; code mode also takes trailing focus words)',
122
123
  ],
@@ -124,7 +125,7 @@ const RAW_BACKENDS = [
124
125
  'agy-review --continue [--decided @f] [--focus "…"]',
125
126
  'agy-review --conversation <id> [--decided @f] [--focus "…"]',
126
127
  ],
127
- 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",
128
+ 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); a write failure warns, never fails the review",
128
129
  notes: [
129
130
  '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',
130
131
  ],