@sabaiway/agent-workflow-kit 5.7.0 → 5.8.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,38 @@ 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
+ ## 5.8.0 — the bundled agy bridge speaks the catalog the installed CLI actually serves, and grounding stops needing a shell (AD-095; antigravity-cli-bridge 5.2.0)
8
+
9
+ **A model display string is matched exactly, so a stale one is not a documentation bug — it is a
10
+ dispatch that refuses before it spends anything.** The bundled agy bridge had been describing a
11
+ catalog captured from CLI v1.0.13 while the installed binary moved to 1.1.13. The user-facing
12
+ catalog tables and every default pin now carry bytes read off `agy models` on the live binary,
13
+ which also turned up a `Gemini 3.6 Flash` family no announcement mentioned and confirmed 3.5 Flash
14
+ is still served. (The wrapper's own header comment stays a compact family inventory and now says
15
+ so, pointing at `agy models` for the exact strings.)
16
+
17
+ - **`Gemini 3.7 Flash (High)` is the bridge default for both roles** — `agy-run` and `agy-review` —
18
+ and joins the review wrapper's frontier set, so a council review on it stays advisory-silent.
19
+ Asserting frontier grade for the review lane was the maintainer's explicit call, not a default
20
+ that drifted. Any model remains selectable through `AGY_MODEL`; the sub-frontier advisory is
21
+ still a silenceable warning, never a gate.
22
+ - **The catalog lists every served row individually.** The 3.6 and 3.5 families are enumerated
23
+ Low/Medium/High rather than collapsed into `(Low|Medium|High)` — a collapsed row reads as a
24
+ display string that does not exist, and pasting it fails.
25
+ - **The reference stops overclaiming the wrapper's output.** v1.1.13 adds `--output-format
26
+ json|stream-json`, `--json-schema`, `--effort` and `--mode`; the wrapper adopts none of them, so
27
+ the docs now say text-DEFAULT and state plainly that raw passthrough JSON rides without
28
+ first-class parsing or validation. The old "no JSON output mode" sentence had become false.
29
+ - **`grounding.mjs --extra <text|@file>` merges orchestrator facts inside the tool.** Appending
30
+ live judgment facts to an emitted facts payload had been a shell append four times running; it is
31
+ now an input. Extras append byte-verbatim after the mechanical sections. The read surface is
32
+ deliberately narrow and fail-closed: an `@file` must sit inside a PROVEN work tree (a successful
33
+ `git rev-parse --show-toplevel` — never a `cwd` fallback) or the system temp surface, never
34
+ inside the git dir including a linked worktree's `.git` gitfile, and it is read through the kit's
35
+ no-follow descriptor door so a FIFO cannot block the assembler and a symlink leaf cannot
36
+ substitute its target. Git location variables (`GIT_DIR`, `GIT_WORK_TREE`, …) are scrubbed before
37
+ every git query, so an ambient override cannot prove a foreign tree.
38
+
7
39
  ## 5.7.0 — what the kit emits is now something a test executed, a claim it proved, or a command you can run (AD-092 + AD-093 + AD-094; memory 4.3.0)
8
40
 
9
41
  **Three feedback plans, one release.** A live upgrade session on a deployed TypeScript project
package/README.md CHANGED
@@ -237,7 +237,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
237
237
  | `/agent-workflow-kit set-recipe` | any time | **config writer** for `docs/ai/orchestration.json`: tell the agent your preference in plain language and it maps it to explicit `--set <activity>.<slot>=<recipe>` / `--unset` ops; the kit validates, **previews by default**, and writes only on `--write` (deployment-gated, atomic, symlink/TOCTOU-safe), resolving the effective recipe vs live readiness. Writes **only** that file — **never runs a backend, never commits**; hand-editing stays fully supported. |
238
238
  | `/agent-workflow-kit review-state` | any time | **read-only review-receipt checker** — makes "reviewed ≠ shipped" detectable: the bridge review wrappers append a receipt per successful review (into a file inside the git dir — never committable); this checks that every backend your configured `plan-execution.review` recipe names holds a **fresh, grounded receipt for the current uncommitted tree** (any later edit moves the fingerprint and stales the receipt; a review continuation never re-attests a folded tree). `--check` gives a gate exit code to declare in `docs/ai/gates.json` **by hand or via the consent-gated seeder** (preview → your explicit yes; never without consent). `--await [--timeout <s>]` (AD-049) BLOCKS until every recipe-named backend has receipted the current tree — the durable completion signal is the receipt, never a process event — so you wait for the bridges instead of hand-polling. Never writes, never commits, never runs a subscription CLI; it spawns read-only `git` queries to fingerprint the tree — and `git commit --no-verify` stays possible (discipline, not a sandbox). |
239
239
  | `/agent-workflow-kit sandbox-masks` | any time | **cosmetic exclude lane for sandbox device masks** — an OS sandbox (Claude Code) injects character-device masks into the work tree as untracked `git status` noise; the review domain already ignores them **by construction** (never-committable untracked classes — char/block devices, FIFOs, sockets — are excluded from the fingerprint, the assembled review payload, and the clean checks). This mode hides them from `git status` too: flagless = read-only probe (derives the CURRENT mask set from the unfiltered walk + lstat — never a frozen list — and revalidates fenced entries, loudly flagging one that became a real path); `--apply` = consent-gated FULL-BLOCK replace of its own fenced block in `git rev-parse --git-path info/exclude` (stale masks drop by construction; `--clear` always means REMOVE the block — it takes precedence over the derivation). Writes ONLY its fence — never `.gitignore`, never global config; symlinked/non-regular exclude paths and malformed fences fail closed. Watch note: a real file at an excluded path is silently skipped by bulk staging (`git add -A`/`git add .`) — delete the stale line first; the probe flags exactly this case. |
240
- | `/agent-workflow-kit grounding` | any time | **grounded-review facts assembler** — mechanizes populating `agy-review --facts @f`: slices your entry-point's **Hard Constraints** section verbatim (exactly one match, else a loud stop) and/or a plan's decision-bearing sections (`## Approach` + `## Verification` required, `## Decisions (locked)` when present; duplicates stop), under the same byte budget the agy wrapper enforces (minus `--reserve-bytes` for the artifact share), with a loud tail-trim on overflow. `--autonomy` (AD-044) appends the COMPUTED effective autonomy policy from the git-top `docs/ai/autonomy.json` (every red-line + per-activity level, stated source line; absent file → the computed defaults ARE the policy, exit 0; a malformed policy fails CLOSED, exit 1). Prints to stdout; `--out` writes **one scratch file only** — system-temp outside the repo ($TMPDIR / /tmp, rewritable) or a **fresh** gitignored in-repo path (create-only, exclusive write; an existing in-repo file, even gitignored, is refused — the `.env` clobber class); tracked, not-ignored-in-repo, other outside-repo, and symlink/non-regular destinations are all refused. Never commits, never runs a subscription CLI. |
240
+ | `/agent-workflow-kit grounding` | any time | **grounded-review facts assembler** — mechanizes populating `agy-review --facts @f`: slices your entry-point's **Hard Constraints** section verbatim (exactly one match, else a loud stop) and/or a plan's decision-bearing sections (`## Approach` + `## Verification` required, `## Decisions (locked)` when present; duplicates stop), under the same byte budget the agy wrapper enforces (minus `--reserve-bytes` for the artifact share), with a loud tail-trim on overflow. `--autonomy` (AD-044) appends the COMPUTED effective autonomy policy from the git-top `docs/ai/autonomy.json` (every red-line + per-activity level, stated source line; absent file → the computed defaults ARE the policy, exit 0; a malformed policy fails CLOSED, exit 1). `--extra <text|@file>` (repeatable) appends your own facts **byte-verbatim** after the mechanical sections, so the merge is a tool input rather than a shell append — an `@file` must sit inside the proven git work tree (never the git dir) or the system temp surface, and is read through a no-follow descriptor (a FIFO cannot block it, a symlink leaf cannot substitute its target). Prints to stdout; `--out` writes **one scratch file only** — system-temp outside the repo ($TMPDIR / /tmp, rewritable) or a **fresh** gitignored in-repo path (create-only, exclusive write; an existing in-repo file, even gitignored, is refused — the `.env` clobber class); tracked, not-ignored-in-repo, other outside-repo, and symlink/non-regular destinations are all refused. Never commits, never runs a subscription CLI. |
241
241
  | `/agent-workflow-kit core-evidence` | any time | **the ONE loop-evidence writer** (strip-the-kit) — every core evidence record lands in a single append-only JSONL store inside the git dir (never committable; versioned schema, latest-per-key supersession, byte-identical duplicates refused, malformed lines fail every reader closed). `red-proof "<file>#<pattern>"` declares an observed-red **BEFORE a bugfix** (N/N red runs + content custody + base + the pre-fix fingerprint; green/mixed/timeout are DISTINGUISHED refusals — nothing written); `degrade --backend --reason` is the ONLY escape for an unavailable review backend (per-tree, never all backends); `summary` renders the whole loop state statelessly (gate result · per-backend verdicts · red-proofs · degrades) — no ledger, no rounds, nothing remembered. Honest residual: records are forgeable — self-discipline, not a security boundary. Never commits, never runs a subscription CLI. |
242
242
  | `/agent-workflow-kit coverage-check` | any time | **the final-run checker** (D3(c)+(d)) — **certifies coverage ONLY inside the `--final` run that owns the lcov** (ownership is exclusive by CONVENTION over the fixed path, not enforced — a concurrent writer to it is a stated residual, queued as LCOV-EXCLUSIVE-OWNERSHIP): an artifact on disk proves nothing about the tree it came from, so a standalone run prints its findings and states `attested=no` / `NO VERDICT` rather than a PASS (an lcov that predates an edit would otherwise certify a line the suite never executed). `attested=` claims a verdict was ISSUED, pass or fail — a run over uncovered lines still reads `attested=yes` and still exits 1, and a run whose coverage arm never executed (no lcov) reads `attested=no` even inside `--final`, because nothing was read and nothing is certified. The runner passes a nonce whose one-way commitment over `{nonce, fingerprint, base}` is the `final-start.attempt` it recorded; a context describing another tree, or matching no recorded attempt, is a REFUSAL, never a verdict. Findings are unchanged — reads the lcov the declared `unit-tests` gate produced at the FIXED git-dir path and fails on any uncovered CHANGED executable Node line (listed `file:line`; a changed file absent from the map is a file-level red; out-of-domain/unsupported files are LISTED — the claim narrowed honestly); VERIFIES every current-base red-proof declaration (bound test exists · custody hash unchanged · green N/N now · pre-fix fingerprint differs); prints `lcov-sha256=<hex|none>` of the exact bytes it consumed — the sha the `--final` receipt binds and re-hashes. An absent lcov is a LOUD `skipped-no-lcov`; a symlinked path is a refusal. `--check` is the gate exit code — declare it as the LAST gate (`run-gates --final` refuses otherwise). Read-only. |
243
243
  | `/agent-workflow-kit commit-guard` | any time | **the read-only pre-commit guard** (D10) — makes the commit capture the whole current working tree, so «verified» and «about to be committed» are the same bytes (the receipt itself has a stated residual — see the mode doc). FIRST it refuses an **INDEX that lags the verified working tree** (the gates and the fingerprint describe the WORKING tree while `git commit` takes the INDEX alone, and the fingerprint cannot tell them apart — so a lagging index used to ship a strict subset of what was verified): unstaged tracked paths or reviewable untracked-not-ignored paths, named up to a bounded cap with the remainder stated, a dirty tracked **submodule** named separately with its own recovery, and fail-closed on an undecidable probe. This deliberately blocks a partial commit. Then it binds the LATEST completed `run-gates --final` receipt to the EXACT current tree: refuses on a missing/red/stale receipt, fingerprint drift under the run, a dangling later attempt, declaration content drift, evidence-hash or lcov drift, or unsatisfied review obligations (the same review-state decision, recomputed over a sanitized env — forged out-of-repo stores never satisfy). Re-runs NO gate or test. Wire it into `.git/hooks/pre-commit` (the installer writes the RESOLVED invocation). `git commit --no-verify` stays the stated residual. |
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: '5.7.0'
6
+ version: '5.8.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-kit
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: antigravity-cli-bridge
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.
3
+ description: Delegate work to Google's Antigravity CLI (`agy`) — the successor to Gemini CLI — to reach Gemini, Claude, and GPT-OSS models under a Google AI Pro/Ultra subscription from the terminal. Use when the user wants to run a headless `agy` prompt, hand a focused task or second-opinion review to `agy`, install or authenticate Antigravity CLI, check or economise its quota/models, bridge project context into `agy`, set up a second delegated-execution backend beside Codex, or troubleshoot `agy` flags, models, auth, conversations, or its headless behaviour.
4
4
  metadata:
5
- version: '5.1.1'
5
+ version: '5.2.0'
6
6
  ---
7
7
 
8
8
  # antigravity-cli-bridge
@@ -53,16 +53,22 @@ keep probes short (see *How the main agent drives agy*).
53
53
  ## Models
54
54
 
55
55
  Pass the **exact display string** to `--model` (or set `AGY_MODEL`). The wrapper defaults to
56
- `Gemini 3.1 Pro (High)`. Run `agy models` for the live list — if it differs from this table, the live
57
- list wins.
56
+ `Gemini 3.7 Flash (High)`. Run `agy models` for the live list — if it differs from this table, the
57
+ live list wins.
58
58
 
59
59
  | Model string | Use it for |
60
60
  |---|---|
61
- | `Gemini 3.5 Flash (Low)` | cheapest; reachability checks, smoke tests, simple transforms |
62
- | `Gemini 3.5 Flash (Medium)` | cheap probes, context-reachability checks, quick summaries |
63
- | `Gemini 3.5 Flash (High)` | fast drafting / review when a little more effort helps |
61
+ | `Gemini 3.7 Flash (Low)` | cheapest; reachability checks, smoke tests, simple transforms |
62
+ | `Gemini 3.7 Flash (Medium)` | cheap probes, context-reachability checks, quick summaries |
63
+ | `Gemini 3.7 Flash (High)` | wrapper + review default; asserted frontier-grade (fork (a)) |
64
+ | `Gemini 3.6 Flash (Low)` | previous Flash generation, still served — prefer 3.7 |
65
+ | `Gemini 3.6 Flash (Medium)` | previous Flash generation, still served — prefer 3.7 |
66
+ | `Gemini 3.6 Flash (High)` | previous Flash generation, still served — prefer 3.7 |
67
+ | `Gemini 3.5 Flash (Low)` | older Flash generation, still served — prefer 3.7 |
68
+ | `Gemini 3.5 Flash (Medium)` | older Flash generation, still served — prefer 3.7 |
69
+ | `Gemini 3.5 Flash (High)` | older Flash generation, still served — prefer 3.7 |
64
70
  | `Gemini 3.1 Pro (Low)` | cheaper Pro pass for medium reasoning |
65
- | `Gemini 3.1 Pro (High)` | wrapper default; hard reasoning, plan critique, architecture review |
71
+ | `Gemini 3.1 Pro (High)` | hard reasoning, plan critique, architecture review (slower, deeper) |
66
72
  | `Claude Sonnet 4.6 (Thinking)` | a Claude second opinion through the same subscription |
67
73
  | `Claude Opus 4.6 (Thinking)` | strongest Claude reasoning available via `agy` |
68
74
  | `GPT-OSS 120B (Medium)` | an open-weights cross-check / diversity pass |
@@ -89,10 +95,12 @@ AGY_HARD_TIMEOUT=8m agy-run "..." # hard wall-clock cap via timeout(
89
95
  agy-run "..." -- --add-dir . # passthrough agy flags (never a permission widener)
90
96
  ```
91
97
 
92
- `agy` is **headless-only** here (`-p`/`--print`) and there is **no JSON output mode** in v1.0.13 you
93
- get plain text. If you need structure, ask for Markdown with explicit headings and validate it
98
+ `agy` is **headless-only** here (`-p`/`--print`); v1.1.13 adds `--output-format json|stream-json`,
99
+ and text stays the wrapper default JSON can ride passthrough (`-- --output-format json`) but with
100
+ no first-class parsing/validation (adoption is a backlog item). If you need structure, ask
101
+ for Markdown with explicit headings and validate it
94
102
  yourself. Wrapper inputs: first argument is the prompt (`text`, `-` for stdin, or `@file`);
95
- `AGY_MODEL` (default `Gemini 3.1 Pro (High)`); `AGY_TIMEOUT` → `--print-timeout` (default `5m`);
103
+ `AGY_MODEL` (default `Gemini 3.7 Flash (High)`); `AGY_TIMEOUT` → `--print-timeout` (default `5m`);
96
104
  `AGY_HARD_TIMEOUT` → hard `timeout(1)` wall-clock cap (default = `AGY_TIMEOUT`); extra `agy` flags
97
105
  after `--`. Full detail: [`references/models-and-flags.md`](references/models-and-flags.md).
98
106
 
@@ -154,7 +162,7 @@ release). `agy-review … --nonce <n>` is the plain-argument equivalent
154
162
  (one seam; flag and a non-empty env must agree, a disagreeing pair refuses pre-spend) — the lane
155
163
  for hosts whose dispatch policy has no env-prefix form.
156
164
 
157
- Frontier default `Gemini 3.1 Pro (High)`; **any** model is allowed (a sub-frontier one earns a
165
+ Frontier default `Gemini 3.7 Flash (High)`; **any** model is allowed (a sub-frontier one earns a
158
166
  silenceable `AGY_PROBE=1` advisory). An oversized `code` review is **DELIVERED, not refused**: the
159
167
  change set is cut into under-cap parts, fed over continuation turns and reviewed in a final turn, and
160
168
  the answer must reproduce a line the wrapper picked from each part — a missing or wrong echo is a
@@ -190,9 +198,9 @@ without an explicit `--add-dir`, so ground a review **self-contained** via `agy-
190
198
  rather than relying on `agy` to read the change set. Re-runnable from a project root (use a cheap model):
191
199
 
192
200
  ```bash
193
- AGY_MODEL="Gemini 3.5 Flash (Low)" agy-run \
201
+ AGY_MODEL="Gemini 3.7 Flash (Low)" agy-run \
194
202
  "Read the cwd context file and state the dialogue language plus one Hard Constraint, in two lines."
195
- AGY_MODEL="Gemini 3.5 Flash (Low)" agy-run \
203
+ AGY_MODEL="Gemini 3.7 Flash (Low)" agy-run \
196
204
  "Without me pointing you at any file, name a project-specific skill under .agents/skills/ here and cite its path."
197
205
  ```
198
206
 
@@ -241,15 +249,16 @@ checklist, prompt templates, output handling). Essentials:
241
249
  - Subdirectory `CLAUDE.md` files are **not** auto-loaded by `agy` (only the cwd context file +
242
250
  `.agents/skills/`). Put cross-cutting rules in the root context file, or include local rules in the
243
251
  prompt when they matter.
244
- - **No JSON output** and **no `agy inspect`** in v1.0.13 parse text; there is no machine-readable
245
- introspection.
252
+ - **Text is the wrapper default** the CLI's 1.1.x `--output-format json|stream-json` can ride
253
+ passthrough but with no first-class parsing/validation (adoption is backlog) — and there is
254
+ **no `agy inspect`**: no machine-readable introspection.
246
255
  - Model names must match the `agy models` display strings **exactly**.
247
256
  - **Quota is finite.** Heavy use of Pro/Claude models can exhaust the subscription; prefer Flash for
248
257
  cheap work.
249
258
  - **A run can't hang forever.** The wrapper caps `agy` with `timeout(1)` (`AGY_HARD_TIMEOUT`,
250
259
  default = `AGY_TIMEOUT`) because `agy`'s own `--print-timeout` is **not** a reliable wall-clock
251
260
  kill (a run was seen surviving 32 min past a 10m `--print-timeout`). A heavy `--add-dir` agentic
252
- prompt on the slowest model (`Gemini 3.1 Pro (High)`) can run unbounded — prefer a faster model or
261
+ prompt on a slow model (e.g. `Gemini 3.1 Pro (High)`) can run unbounded — prefer a faster model or
253
262
  a **self-contained prompt** (no `--add-dir`); an "exceeded the hard cap" error is the guard firing.
254
263
  - `agy` output is plain text and may be incomplete or out of date — treat it as advisory until the
255
264
  main agent verifies it.
@@ -35,7 +35,7 @@
35
35
  # agy-review --conversation <id> [--decided @f] [--focus "…"] # resume a specific conversation
36
36
  #
37
37
  # Environment (every optional var has an explicit default so a no-env run is safe under set -u):
38
- # AGY_MODEL default "Gemini 3.1 Pro (High)"; ANY model allowed (advisory warn off-frontier).
38
+ # AGY_MODEL default "Gemini 3.7 Flash (High)"; ANY model allowed (advisory warn off-frontier).
39
39
  # Set empty (AGY_MODEL=) to drop --model and use agy's settings.json.
40
40
  # AGY_HARD_TIMEOUT default 30m (duration string; the timeout(1) hard cap via agy-run)
41
41
  # AGY_TIMEOUT default = AGY_HARD_TIMEOUT (agy's soft --print-timeout)
@@ -322,11 +322,11 @@ aw_resolve_timeout_bin() {
322
322
  printf '%s' "$bin"
323
323
  }
324
324
 
325
- DEFAULT_AGY_REVIEW_MODEL="Gemini 3.1 Pro (High)"
325
+ DEFAULT_AGY_REVIEW_MODEL="Gemini 3.7 Flash (High)"
326
326
  # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
327
327
  # version (drift-guarded by agy-review.test.mjs against capability.json).
328
328
  AW_RECEIPT_BACKEND="agy"
329
- AW_BRIDGE_VERSION="5.1.1" # aw-version-anchor
329
+ AW_BRIDGE_VERSION="5.2.0" # aw-version-anchor
330
330
  # `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
331
331
  AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
332
332
  # D5 control-byte screen — IMMEDIATELY after resolution, BEFORE the off-frontier advisory (or any
@@ -335,8 +335,9 @@ if [[ "$AGY_MODEL" == *[$'\x01'-$'\x1f'$'\x7f']* ]]; then
335
335
  echo "error: AGY_MODEL contains control bytes — fix the setting (env or bridge-settings.conf) and re-run." >&2
336
336
  exit 2
337
337
  fi
338
- # Frontier review models. ANY model is allowed; a sub-frontier one only earns a soft, silenceable warning.
339
- FRONTIER_SET=("Gemini 3.1 Pro (High)" "Claude Opus 4.6 (Thinking)" "Claude Sonnet 4.6 (Thinking)")
338
+ # Frontier review models. ANY model is allowed; a sub-frontier one only earns a soft, silenceable
339
+ # warning. Gemini 3.7 Flash (High) is asserted frontier-grade (fork (a), maintainer 2026-08-14).
340
+ FRONTIER_SET=("Gemini 3.7 Flash (High)" "Gemini 3.1 Pro (High)" "Claude Opus 4.6 (Thinking)" "Claude Sonnet 4.6 (Thinking)")
340
341
 
341
342
  # Duration-string timeouts (NOT codex's bare seconds): agy-run forwards a duration to --print-timeout,
342
343
  # and the timeout(1) hard cap is a duration too — never numerically compared, so 30m vs 2h is fine.
@@ -285,7 +285,16 @@ describe('agy-review.sh — model policy advisory (1)', () => {
285
285
  rmSync(sb.home, { recursive: true, force: true });
286
286
  assert.equal(r.status, 0, r.stderr);
287
287
  assert.doesNotMatch(r.stderr, /non-frontier model/);
288
- assert.match(r.argv, /Gemini 3\.1 Pro \(High\)/, 'the frontier default reaches agy');
288
+ assert.match(r.argv, /Gemini 3\.7 Flash \(High\)/, 'the frontier default reaches agy');
289
+ });
290
+
291
+ it('an explicit Gemini 3.7 Flash (High) is FRONTIER — no advisory (fork (a), maintainer 2026-08-14)', () => {
292
+ const sb = makeSandbox();
293
+ const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: 'Gemini 3.7 Flash (High)' } });
294
+ rmSync(sb.home, { recursive: true, force: true });
295
+ assert.equal(r.status, 0, r.stderr);
296
+ assert.doesNotMatch(r.stderr, /non-frontier model/, 'a FRONTIER_SET member never warns');
297
+ assert.match(r.argv, /Gemini 3\.7 Flash \(High\)/);
289
298
  });
290
299
  });
291
300
 
@@ -2444,8 +2453,8 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
2444
2453
  const receipts = readReceipts(sb.repo);
2445
2454
  rmSync(sb.home, { recursive: true, force: true });
2446
2455
  assert.equal(r.status, 0, r.stderr);
2447
- assert.match(r.stderr, /review posture: model=Gemini 3\.1 Pro \(High\)/, 'the banner states the actual run posture');
2448
- assert.deepEqual(receipts[0].posture, { model: 'Gemini 3.1 Pro (High)' }, 'banner ↔ receipt parity');
2456
+ assert.match(r.stderr, /review posture: model=Gemini 3\.7 Flash \(High\)/, 'the banner states the actual run posture');
2457
+ assert.deepEqual(receipts[0].posture, { model: 'Gemini 3.7 Flash (High)' }, 'banner ↔ receipt parity');
2449
2458
  assert.deepEqual(Object.keys(receipts[0]), Object.keys(RECEIPT_FIXTURE), 'fixture key set + order');
2450
2459
  });
2451
2460
 
@@ -2498,7 +2507,7 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
2498
2507
  const receipts = readReceipts(sb.repo);
2499
2508
  rmSync(sb.home, { recursive: true, force: true });
2500
2509
  assert.equal(r.status, 0, r.stderr);
2501
- assert.match(r.stderr, /^review posture: model=Gemini 3\.1 Pro \(High\) timeout=30m$/m);
2510
+ assert.match(r.stderr, /^review posture: model=Gemini 3\.7 Flash \(High\) timeout=30m$/m);
2502
2511
  assert.deepEqual(Object.keys(receipts[0].posture), ['model'], 'timeout never enters the receipt posture');
2503
2512
  });
2504
2513
 
@@ -17,7 +17,9 @@
17
17
  # policy (no plan contract, no auto-approve, no workspace edits) — that is left
18
18
  # to whatever flow we design later, which can opt in via passthrough flags.
19
19
  #
20
- # Models (pass the exact display string from `agy models`, or set AGY_MODEL):
20
+ # Model families (compact inventory only; run `agy models` for exact display strings, then pass
21
+ # one via AGY_MODEL):
22
+ # Gemini 3.7 Flash (Low|Medium|High), Gemini 3.6 Flash (Low|Medium|High),
21
23
  # Gemini 3.5 Flash (Low|Medium|High), Gemini 3.1 Pro (Low|High),
22
24
  # Claude Sonnet 4.6 (Thinking), Claude Opus 4.6 (Thinking), GPT-OSS 120B (Medium)
23
25
  #
@@ -237,8 +239,9 @@ if ! command -v agy >/dev/null 2>&1; then
237
239
  exit 127
238
240
  fi
239
241
 
240
- # `-` (empty) => skip --model and let agy use settings.json; default to Pro.
241
- AGY_MODEL="${AGY_MODEL-Gemini 3.1 Pro (High)}"
242
+ # `-` (empty) => skip --model and let agy use settings.json; default to Flash (High) — asserted
243
+ # frontier-grade (fork (a), maintainer 2026-08-14).
244
+ AGY_MODEL="${AGY_MODEL-Gemini 3.7 Flash (High)}"
242
245
  AGY_TIMEOUT="${AGY_TIMEOUT:-5m}"
243
246
  AGY_TIMEOUT="$(aw_effective_timeout AGY_TIMEOUT 5m)"
244
247
  # Hard wall-clock cap (defaults to AGY_TIMEOUT). agy's own --print-timeout is NOT a reliable
@@ -355,7 +358,7 @@ set -e
355
358
  if [[ $rc -eq 124 || $rc -eq 137 ]]; then
356
359
  echo "error: agy exceeded the hard cap AGY_HARD_TIMEOUT=$AGY_HARD_TIMEOUT and was terminated." >&2
357
360
  echo " This usually means a heavy '--add-dir' agentic run, or the slowest model looping." >&2
358
- echo " Retry with a faster model (e.g. AGY_MODEL='Gemini 3.5 Flash (High)') or a" >&2
361
+ echo " Retry with a faster model (e.g. AGY_MODEL='Gemini 3.7 Flash (Low)') or a" >&2
359
362
  echo " self-contained prompt without --add-dir. Raise AGY_HARD_TIMEOUT only if the run is healthy." >&2
360
363
  fi
361
364
  exit $rc
@@ -447,6 +447,30 @@ describe('agy.sh — bridge settings file (bridges 2.3.0)', { concurrency: true
447
447
  });
448
448
  });
449
449
 
450
+ // ── the wrapper's OWN default model (fork (a), round-1 council F3) ────────────────
451
+ describe('agy.sh — default model pin', () => {
452
+ it('absent AGY_MODEL → the stub argv carries --model Gemini 3.7 Flash (High)', () => {
453
+ const home = makeSandbox(ARGV_STUB);
454
+ const argvFile = join(home, 'argv.txt');
455
+ const r = runWrapper(home, { AGY_STUB_ARGV: argvFile });
456
+ const argv = readFileSync(argvFile, 'utf8');
457
+ rmSync(home, { recursive: true, force: true });
458
+ assert.equal(r.status, 0, r.stderr);
459
+ assert.match(argv, /^--model$/m, 'the default run passes --model');
460
+ assert.match(argv, /^Gemini 3\.7 Flash \(High\)$/m, 'the wrapper default is the probed display string');
461
+ });
462
+
463
+ it("explicit AGY_MODEL='' drops --model entirely (settings.json takes over)", () => {
464
+ const home = makeSandbox(ARGV_STUB);
465
+ const argvFile = join(home, 'argv.txt');
466
+ const r = runWrapper(home, { AGY_MODEL: '', AGY_STUB_ARGV: argvFile });
467
+ const argv = readFileSync(argvFile, 'utf8');
468
+ rmSync(home, { recursive: true, force: true });
469
+ assert.equal(r.status, 0, r.stderr);
470
+ assert.doesNotMatch(argv, /^--model$/m, 'an explicit empty model never emits --model');
471
+ });
472
+ });
473
+
450
474
  // ── settings surface ⟷ manifest (drift guard, D6) ────────────────────────────────
451
475
  // agy-run's --help stays candidate-C (not contract-pinned), but its SETTINGS surface
452
476
  // is manifest-pinned like the other three wrappers: the Settings help section and the
@@ -3,9 +3,9 @@
3
3
  "schema": 1,
4
4
  "name": "antigravity-cli-bridge",
5
5
  "kind": "execution-backend",
6
- "version": "5.1.1",
6
+ "version": "5.2.0",
7
7
  "provides": ["review", "probe"],
8
- "posture": { "model": "Gemini 3.1 Pro (High)" },
8
+ "posture": { "model": "Gemini 3.7 Flash (High)" },
9
9
  "roles": {
10
10
  "review": {
11
11
  "cmd": "agy-review",
@@ -180,7 +180,7 @@
180
180
  ],
181
181
  "guardrails": [
182
182
  { "value": "subscription-only — every *_API_KEY env var is unset before the run", "enforcement": "enforced", "source": "bin/agy.sh" },
183
- { "value": "any model is selectable via AGY_MODEL; the default is Gemini 3.1 Pro (High)", "enforcement": "advisory", "source": "bin/agy.sh" },
183
+ { "value": "any model is selectable via AGY_MODEL; the default is Gemini 3.7 Flash (High)", "enforcement": "advisory", "source": "bin/agy.sh" },
184
184
  { "value": "hard wall-clock cap AGY_HARD_TIMEOUT (built-in default 5m)", "enforcement": "enforced", "condition": "only while timeout(1)/gtimeout is on PATH — otherwise the wrapper warns and runs uncapped", "source": "capability.json settings.AGY_HARD_TIMEOUT" },
185
185
  { "value": "the prompt rides a single argv — over AGY_MAX_PROMPT_BYTES (120000) the run refuses", "enforcement": "enforced", "source": "bin/agy.sh" },
186
186
  { "value": "no review posture, no grounding, no receipt — the reply is raw model output", "enforcement": "advisory", "source": "bin/agy.sh" }
@@ -18,13 +18,14 @@ edits, verification, and final judgment.
18
18
 
19
19
  | Task | Model |
20
20
  |---|---|
21
- | Reachability / smoke / "is it wired?" | `Gemini 3.5 Flash (Low)` |
22
- | Cheap probes, summaries | `Gemini 3.5 Flash (Medium)` |
23
- | Quick review with a little more effort | `Gemini 3.5 Flash (High)` |
24
- | Reasoning, plan critique, careful drafting | `Gemini 3.1 Pro (High)` (wrapper default) |
25
- | Same reasoning, lower quota cost | `Gemini 3.1 Pro (Low)` |
21
+ | Reachability / smoke / "is it wired?" | `Gemini 3.7 Flash (Low)` |
22
+ | Cheap probes, summaries | `Gemini 3.7 Flash (Medium)` |
23
+ | Review, reasoning, careful drafting | `Gemini 3.7 Flash (High)` (wrapper default; asserted frontier-grade) |
24
+ | Slower, deeper reasoning pass | `Gemini 3.1 Pro (High)` |
25
+ | Same Pro reasoning, lower quota cost | `Gemini 3.1 Pro (Low)` |
26
26
  | A different engine's opinion | `Claude Sonnet 4.6 (Thinking)`, `Claude Opus 4.6 (Thinking)`, or `GPT-OSS 120B (Medium)` |
27
27
 
28
+ The 3.6 / 3.5 Flash families remain served — prefer 3.7 (newest generation at the same tiers).
28
29
  Don't reach for Pro by reflex — Flash answers most reachability/probe questions for a fraction of the
29
30
  quota.
30
31
 
@@ -94,7 +95,7 @@ What it does for you, and what YOU must supply:
94
95
  `AGY_REVIEW_ALLOW_ADDDIR` is **RETIRED** (recognized, arms nothing): headless `agy` auto-denies its
95
96
  own `read_file`, so the offload it armed could return a confident fabrication with no way to tell.
96
97
  The kit never grants that permission — the feed exists so none is needed.
97
- - **Model:** frontier default `Gemini 3.1 Pro (High)`; any model is allowed (a sub-frontier one earns a
98
+ - **Model:** frontier default `Gemini 3.7 Flash (High)`; any model is allowed (a sub-frontier one earns a
98
99
  silenceable `AGY_PROBE=1` advisory). The service can still **stall on large/substantive prompts**
99
100
  (Issue-001) — keep reviews **focused**; the hard timeout is the guard.
100
101
  - **Posture banner — quote it verbatim.** Every review states its ACTUAL posture on ONE stderr line
@@ -127,9 +128,9 @@ Probe **reachability** from a project root (cheap model) — this is the one pla
127
128
  context file is the point of the prompt:
128
129
 
129
130
  ```bash
130
- AGY_MODEL="Gemini 3.5 Flash (Low)" agy-run \
131
+ AGY_MODEL="Gemini 3.7 Flash (Low)" agy-run \
131
132
  "Read the cwd context file and report the dialogue language plus one Hard Constraint."
132
- AGY_MODEL="Gemini 3.5 Flash (Low)" agy-run \
133
+ AGY_MODEL="Gemini 3.7 Flash (Low)" agy-run \
133
134
  "Without using a file pointer, is there a project-specific planning skill in this repo? Name it and cite its path."
134
135
  ```
135
136
 
@@ -1,14 +1,18 @@
1
1
  # `agy` models & flags (reference)
2
2
 
3
3
  The source of truth is the live binary: `agy --version`, `agy --help`, `agy models`. The tables below
4
- were captured from **v1.0.13**; if the binary disagrees, the binary wins. The wrapper command is
4
+ were captured from **v1.1.13**; if the binary disagrees, the binary wins. The wrapper command is
5
5
  `agy-run`, backed by `bin/agy.sh`.
6
6
 
7
7
  ## Headless behaviour
8
8
 
9
9
  Use `-p`, `--print`, or `--prompt` to run one non-interactive prompt and print the text response. The
10
- wrapper always uses headless `-p`. **There is no JSON output mode in v1.0.13** ask for Markdown,
11
- bullets, tables, or fenced blocks when the caller needs structure, then validate the text yourself.
10
+ wrapper always uses headless `-p`. v1.1.13 adds `--output-format text|json|stream-json` and
11
+ `--json-schema` (structured output); **text stays the wrapper default** raw passthrough
12
+ (`agy-run "…" -- --output-format json`) is possible but rides with NO first-class parsing or schema
13
+ validation (first-class adoption is a separate backlog item). When the caller needs structure
14
+ through the wrapper, ask for Markdown, bullets, tables, or fenced blocks, then validate the text
15
+ yourself.
12
16
 
13
17
  ## Wrapper contract
14
18
 
@@ -30,7 +34,7 @@ Environment:
30
34
 
31
35
  | Var | Default | Effect |
32
36
  |---|---|---|
33
- | `AGY_MODEL` | `Gemini 3.1 Pro (High)` | model display string; set empty (`AGY_MODEL=`) to drop `--model` and let `agy` use `settings.json` |
37
+ | `AGY_MODEL` | `Gemini 3.7 Flash (High)` | model display string; set empty (`AGY_MODEL=`) to drop `--model` and let `agy` use `settings.json` |
34
38
  | `AGY_TIMEOUT` | `5m` | value passed to `--print-timeout` |
35
39
  | `AGY_HARD_TIMEOUT` | `= AGY_TIMEOUT` | hard `timeout(1)` wall-clock cap (a duration string) |
36
40
  | `AGY_MAX_PROMPT_BYTES` | `120000` | single-argv byte ceiling. `agy` takes the prompt as ONE `-p` argv; past `MAX_ARG_STRLEN` (~131072) `execve` fails with a cryptic `Argument list too long`. The wrapper measures the resolved `-`/`@file` prompt and fails loud over the ceiling. A huge **literal** `agy-run "<huge>"` fails at the wrapper's own `exec`, so route large prompts via `-`/`@file`. |
@@ -55,7 +59,7 @@ agy-review --continue | --conversation <id> [--decided @f] [--focus "…"] #
55
59
 
56
60
  | Var | Default | Effect |
57
61
  |---|---|---|
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) |
62
+ | `AGY_MODEL` | `Gemini 3.7 Flash (High)` | frontier default (fork (a), 2026-08-14); **any** model is allowed — a sub-frontier one earns a silenceable advisory (quality-first, not a gate) |
59
63
  | `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) |
60
64
  | `AGY_REVIEW_MAX_TOTAL_BYTES` | `240000` | the ceiling on the SUM of all outgoing prompt bytes an oversized `code` review's chunked feed may send; checked BEFORE the first turn is spent |
61
65
  | `AGY_REVIEW_ALLOW_ADDDIR` | `0` | **RETIRED** — recognized so an existing settings line never warns as unknown, but it arms nothing. An oversized `code` review is a chunked feed with a per-part delivery proof; the `--add-dir` offload it armed could not be verified (headless `agy` auto-denies `read_file`) |
@@ -72,11 +76,17 @@ Pass the **exact display string** from `agy models`, or set `AGY_MODEL`.
72
76
 
73
77
  | Model string | Practical use |
74
78
  |---|---|
75
- | `Gemini 3.5 Flash (Low)` | lowest-cost smoke tests and simple rewrites |
76
- | `Gemini 3.5 Flash (Medium)` | cheap probes, fast summaries, context-reachability checks |
77
- | `Gemini 3.5 Flash (High)` | fast review when a little more reasoning effort is useful |
79
+ | `Gemini 3.7 Flash (Low)` | lowest-cost smoke tests, cheap probes, simple rewrites (newest Flash) |
80
+ | `Gemini 3.7 Flash (Medium)` | fast summaries, context-reachability checks |
81
+ | `Gemini 3.7 Flash (High)` | wrapper + review default asserted frontier-grade (fork (a)) |
82
+ | `Gemini 3.6 Flash (Low)` | previous Flash generation, still served — prefer 3.7 |
83
+ | `Gemini 3.6 Flash (Medium)` | previous Flash generation, still served — prefer 3.7 |
84
+ | `Gemini 3.6 Flash (High)` | previous Flash generation, still served — prefer 3.7 |
85
+ | `Gemini 3.5 Flash (Low)` | older Flash generation, still served — prefer 3.7 |
86
+ | `Gemini 3.5 Flash (Medium)` | older Flash generation, still served — prefer 3.7 |
87
+ | `Gemini 3.5 Flash (High)` | older Flash generation, still served — prefer 3.7 |
78
88
  | `Gemini 3.1 Pro (Low)` | cheaper Pro pass for medium reasoning |
79
- | `Gemini 3.1 Pro (High)` | wrapper default; hard reasoning, plan critique, architecture review |
89
+ | `Gemini 3.1 Pro (High)` | hard reasoning, plan critique, architecture review (slower, deeper) |
80
90
  | `Claude Sonnet 4.6 (Thinking)` | cross-vendor reasoning comparison |
81
91
  | `Claude Opus 4.6 (Thinking)` | expensive deep critique when the user wants another high-end pass |
82
92
  | `GPT-OSS 120B (Medium)` | open-weights-style comparison / diversity pass |
@@ -84,11 +94,11 @@ Pass the **exact display string** from `agy models`, or set `AGY_MODEL`.
84
94
  Examples:
85
95
 
86
96
  ```bash
87
- AGY_MODEL="Gemini 3.5 Flash (Medium)" agy-run "Read AGENTS.md and report one Hard Constraint."
97
+ AGY_MODEL="Gemini 3.7 Flash (Low)" agy-run "Read AGENTS.md and report one Hard Constraint."
88
98
  AGY_MODEL="Claude Sonnet 4.6 (Thinking)" AGY_TIMEOUT=10m agy-run @review-prompt.md
89
99
  ```
90
100
 
91
- ## Flags (from `agy --help`, v1.0.13)
101
+ ## Flags (from `agy --help`, v1.1.13)
92
102
 
93
103
  | Flag | Meaning | Notes |
94
104
  |---|---|---|
@@ -102,12 +112,19 @@ AGY_MODEL="Claude Sonnet 4.6 (Thinking)" AGY_TIMEOUT=10m agy-run @review-prompt.
102
112
  | `--dangerously-skip-permissions` | auto-approve all tool permissions | avoid by default; use only with explicit user approval |
103
113
  | `--sandbox` | run with terminal restrictions enabled | prefer when delegating a prompt that might trigger tool/terminal work |
104
114
  | `--log-file <path>` | override the CLI log-file path | keep logs secret-free and out of committed artifacts |
115
+ | `--output-format <fmt>` | print-mode output: `text` (default), `json`, `stream-json` | NEW in 1.1.x; not wrapper-adopted (backlog) |
116
+ | `--json-schema <s\|path>` | enforce structured output (stream-json final result) | NEW in 1.1.x; not wrapper-adopted (backlog) |
117
+ | `--effort <low\|medium\|high>` | reasoning effort for the session | NEW in 1.1.x; the display strings already carry an effort tier — the wrapper keeps model selection in ONE place (`AGY_MODEL`) |
118
+ | `--mode <m>` | agent execution mode (`accept-edits`, `plan`) | NEW in 1.1.x; not used by the wrapper |
119
+ | `--agent` / `--project <id>` / `--new-project` | agent + project selection for the session | NEW in 1.1.x; not used by the wrapper |
120
+ | `--disable-slash-commands` | disable slash command/skill expansion in print mode | NEW in 1.1.x; not used by the wrapper |
105
121
 
106
- ## Subcommands (v1.0.13)
122
+ ## Subcommands (v1.1.13)
107
123
 
108
- `changelog`, `help`, `install`, `models`, `plugin` / `plugins`, `update`.
124
+ `agent` / `agents`, `changelog`, `help`, `install`, `models`, `plugin` / `plugins`, `update`.
109
125
 
110
- **Not available in v1.0.13:** any JSON output mode, and any `agy inspect`. Output is plain text.
126
+ **Still not available in v1.1.13:** any `agy inspect`. Wrapper output stays plain text (the
127
+ `--output-format` lane is not adopted here — see the backlog row).
111
128
 
112
129
  ## Project-context flags
113
130
 
@@ -9,7 +9,7 @@ works in any project.
9
9
  ```bash
10
10
  curl -fsSL https://antigravity.google/cli/install.sh | bash
11
11
  export PATH="$HOME/.local/bin:$PATH" # add to ~/.bashrc / ~/.zshrc to persist
12
- agy --version # expect 1.0.13 or newer
12
+ agy --version # expect 1.1.13 or newer
13
13
  ```
14
14
 
15
15
  - The binary is **`agy`** (not `antigravity`); it installs to `~/.local/bin/agy`.
@@ -59,12 +59,13 @@ agy --version
59
59
  echo "say OK" | agy-run -
60
60
  ```
61
61
 
62
- Expected: the version prints (`1.0.13` or newer), then a short reply containing `OK`. If `agy-run`
62
+ Expected: the version prints (`1.1.13` or newer), then a short reply containing `OK`. If `agy-run`
63
63
  reports `'agy' not found`, fix your `PATH` (step 1). If it asks you to sign in, complete step 2.
64
64
 
65
65
  ## Notes
66
66
 
67
- - `agy-run` is headless and plain-text only; there is no JSON output mode.
67
+ - `agy-run` is headless and text by default; the CLI's 1.1.x `--output-format json` can ride
68
+ passthrough (`-- --output-format json`) but the wrapper adds no parsing/validation.
68
69
  - `AGY_MODEL` selects the exact model display string; `AGY_TIMEOUT` controls `--print-timeout`.
69
70
  - Extra `agy` flags go after `--`, e.g. `agy-run @prompt.md -- --add-dir .`.
70
71
  - Re-run interactive `agy` only when the OAuth token expires or the account changes.
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": "5.7.0",
6
+ "version": "5.8.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": "5.7.0",
3
+ "version": "5.8.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",
@@ -2,14 +2,15 @@
2
2
 
3
3
  <!-- opt-in-capability: none — an on-demand assembler that leaves no persistent state the advisor could observe -->
4
4
 
5
- The **grounded-review facts assembler** (AD-038) — an ungrounded `agy` review GUESSES, and while the grounding contract is mechanized (`agy-review code --facts @f`), populating the facts file was a manual chore. This mode emits the **mechanical** facts sources of a payload (constraints / autonomy / plan / ledger digest); the orchestrator still owns any judgment-bearing additions. **Catalogued honestly as a WRITER** — `--out <path>` writes one file — with the invariant: `--out` accepts **only system-temp destinations outside the repo ($TMPDIR / /tmp — rewritable scratch) or a FRESH gitignored in-repo path (create-only, exclusive write)** and REFUSES everything else: a tracked path, an in-repo not-ignored path (a new untracked file would itself move the review fingerprint the facts are about to ground), an EXISTING in-repo file even when gitignored (the `.env` clobber class — this writer is bridge-tier auto-allowable), any other outside-repo destination, and symlink/non-regular leaves; **stdout is the default**. It never commits and never runs a subscription CLI.
5
+ The **grounded-review facts assembler** (AD-038) — an ungrounded `agy` review GUESSES, and while the grounding contract is mechanized (`agy-review code --facts @f`), populating the facts file was a manual chore. This mode emits the **mechanical** facts sources of a payload (constraints / autonomy / plan / ledger digest); the orchestrator still owns any judgment-bearing additions — delivered via `--extra`, never a shell append onto the emitted file (corpus #88/#95). **Catalogued honestly as a WRITER** — `--out <path>` writes one file — with the invariant: `--out` accepts **only system-temp destinations outside the repo ($TMPDIR / /tmp — rewritable scratch) or a FRESH gitignored in-repo path (create-only, exclusive write)** and REFUSES everything else: a tracked path, an in-repo not-ignored path (a new untracked file would itself move the review fingerprint the facts are about to ground), an EXISTING in-repo file even when gitignored (the `.env` clobber class — this writer is bridge-tier auto-allowable), any other outside-repo destination, and symlink/non-regular leaves; **stdout is the default**. It never commits and never runs a subscription CLI.
6
6
 
7
- Run `node ${CLAUDE_SKILL_DIR}/tools/grounding.mjs [--constraints] [--autonomy] [--plan <path>] [--reserve-bytes <n>] [--out <path>]`:
7
+ Run `node ${CLAUDE_SKILL_DIR}/tools/grounding.mjs [--constraints] [--autonomy] [--plan <path>] [--extra <text|@file>]... [--reserve-bytes <n>] [--out <path>]`:
8
8
 
9
9
  1. **`--constraints`** — slice the root `AGENTS.md` **Hard Constraints** section, verbatim and whole (exactly ONE matching heading; zero or several is a loud STOP, never a guess — the marker-slot discipline).
10
10
  2. **`--autonomy`** (AD-044 Plan 3) — append the COMPUTED effective autonomy policy: the shared resolver (`resolveAutonomy`) over the **git-top** `docs/ai/autonomy.json` renders every red-line + per-activity level with a stated source line. An ABSENT file is exit 0 — the computed defaults ARE the effective policy (unlike `velocity-profile --autonomy`, which writes settings and refuses an undeclared policy); a malformed/unreadable policy fails CLOSED (exit 1). Informational for the reviewing backend — enforcement stays the OS sandbox + the orchestrator. Rides between the constraints and the plan sections.
11
11
  3. **`--plan <path>`** — extract the plan's decision-bearing canonical sections, verbatim + whole: `## Approach` (REQUIRED — its "What we are NOT doing" text rides inside; it is not a heading in canon) and `## Verification` (REQUIRED — STOP if missing), plus `## Decisions (locked)` when present (the optional engine §7 heading); a DUPLICATE heading is always a STOP.
12
+ 4. **`--extra <text|@file>`** (corpus #88/#95 — the fourth firing of the same pre-dispatch append step) — append orchestrator-supplied facts byte-VERBATIM after the mechanical sections (repeatable, argv order; the `agy-review --facts` convention: literal text, or `@path` read whole through a race-free descriptor). An `@file` must resolve inside the PROVEN git work tree (rev-parse success — no cwd fallback; the git dir itself is refused) or the system temp surface — anything else refuses loudly (the exfil class: this tool is bridge-tier auto-allowable), as does a missing, EMPTY, or non-regular file (a FIFO cannot block the read). The merge happens INSIDE the tool, so the promptless lane is the invocation itself.
12
13
  5. **Byte budget** — the output honors the same `AGY_MAX_PROMPT_BYTES` contract the agy wrapper enforces (override may only tighten; the OS argv ceiling is rejected), MINUS **`--reserve-bytes <n>`** — the artifact share the caller expects `agy-review` to add around these facts. Overflow is trimmed tail-first with a loud in-band marker + stderr report — never a silent cut.
13
14
  6. Feed the result to the wrapper: `agy-review code --facts @<out>`. The `procedures` advisor renders this invocation as a concrete pre-step whenever the resolved review dispatch includes agy — populated with the in-flight plan path when exactly one plan is in flight.
14
15
 
15
- **Invariants:** writer (writes at most the ONE `--out` scratch file — system-temp, or a fresh gitignored in-repo path, exclusive-create) · never commits · never runs a subscription CLI · verbatim slices only (assembly is mechanical; facts judgment stays with the orchestrator).
16
+ **Invariants:** writer (writes at most the ONE `--out` scratch file — system-temp, or a fresh gitignored in-repo path, exclusive-create) · never commits · never runs a subscription CLI · verbatim slices + verbatim extras (assembly is mechanical; facts judgment stays with the orchestrator — `--extra` carries it, the tool never edits it).
@@ -11,6 +11,9 @@
11
11
  // it is not a heading in canon) and `## Verification` (REQUIRED — STOP if
12
12
  // missing), plus `## Decisions (locked)` (optional-if-absent, the engine §7
13
13
  // heading this release adds); a DUPLICATE heading is always a STOP.
14
+ // --extra <text|@file> append orchestrator-supplied facts verbatim AFTER the mechanical halves
15
+ // (repeatable; @file reads are confined to the work tree + the system temp
16
+ // surface — the merge happens INSIDE the tool, corpus #88/#95).
14
17
  //
15
18
  // Byte budget: the output honors the same AGY_MAX_PROMPT_BYTES contract the agy wrapper enforces
16
19
  // (default 120000; the override may only TIGHTEN — above the OS single-argv ceiling ~131000 is
@@ -31,6 +34,7 @@ import { tmpdir } from 'node:os';
31
34
  import { pathToFileURL } from 'node:url';
32
35
  import { spawnSync } from 'node:child_process';
33
36
  import { fail } from './orchestration-config.mjs';
37
+ import { readRegularFileNoFollow } from './fs-read-nofollow.mjs';
34
38
  // (f) --autonomy (AD-044 Plan 3): the effective per-project autonomy policy for the facts payload.
35
39
  // READ core only — never autonomy-write.mjs (the import-split invariant).
36
40
  import { AUTONOMY_REL, loadAutonomy, resolveAutonomy, isSparseSeedConfig } from './autonomy-config.mjs';
@@ -81,7 +85,7 @@ export const sliceSection = (text, heading, { optional = false, label = 'documen
81
85
 
82
86
  // ── assembly ───────────────────────────────────────────────────────────────────────
83
87
 
84
- export const assembleGrounding = ({ constraintsText = null, autonomyText = null, planText = null, planLabel = 'plan' } = {}) => {
88
+ export const assembleGrounding = ({ constraintsText = null, autonomyText = null, planText = null, planLabel = 'plan', extraTexts = [] } = {}) => {
85
89
  const parts = [];
86
90
  if (constraintsText != null) {
87
91
  parts.push(sliceSection(constraintsText, CONSTRAINTS_HEADING, { label: 'AGENTS.md' }));
@@ -96,6 +100,10 @@ export const assembleGrounding = ({ constraintsText = null, autonomyText = null,
96
100
  if (section != null) parts.push(section);
97
101
  }
98
102
  }
103
+ // Orchestrator extras ride LAST, verbatim in argv order — live judgment facts read after the
104
+ // mechanical slices, and the merge happens INSIDE the tool (corpus #88/#95: a shell append onto
105
+ // the emitted facts file was the recurring un-covered lane).
106
+ for (const t of extraTexts) parts.push(t);
99
107
  return parts.join('\n');
100
108
  };
101
109
 
@@ -151,10 +159,23 @@ const resolveAutonomyFacts = ({ cwd }) => {
151
159
  return renderAutonomyFacts(config, source);
152
160
  };
153
161
 
162
+ // The realpath'd system temp surface ($TMPDIR / os.tmpdir() / /tmp) — the shared scratch boundary
163
+ // for the --out write guard and the --extra read guard.
164
+ const systemTempRoots = () => [...new Set([tmpdir(), process.env.TMPDIR, '/tmp'].filter(Boolean).map((p) => {
165
+ try {
166
+ return realpathSync(p);
167
+ } catch {
168
+ return null;
169
+ }
170
+ }).filter(Boolean))];
171
+
154
172
  // ── the --out destination guard (gitignored / out-of-repo scratch ONLY) ────────────────
155
173
 
156
174
  const gitLine = (args, cwd) => {
157
- const r = spawnSync('git', args, { cwd, encoding: 'utf8', windowsHide: true });
175
+ // Ambient GIT_* location vars (GIT_DIR / GIT_WORK_TREE / …) would let rev-parse prove a FOREIGN
176
+ // tree — every location answer must come from cwd alone, for every gitLine consumer.
177
+ const env = Object.fromEntries(Object.entries(process.env).filter(([k]) => !/^GIT_/i.test(k)));
178
+ const r = spawnSync('git', args, { cwd, env, encoding: 'utf8', windowsHide: true });
158
179
  return r.error || r.status == null ? null : { status: r.status, stdout: r.stdout ?? '' };
159
180
  };
160
181
 
@@ -199,13 +220,7 @@ export const assertScratchDestination = (outPath, cwd) => {
199
220
  // the repo is scratch" would let an unattended run overwrite e.g. ~/.bashrc promptless.
200
221
  // $TMPDIR / os.tmpdir() / /tmp are the scratch surface; everything else refuses loudly.
201
222
  const assertTempScratch = () => {
202
- const tempRoots = [...new Set([tmpdir(), process.env.TMPDIR, '/tmp'].filter(Boolean).map((p) => {
203
- try {
204
- return realpathSync(p);
205
- } catch {
206
- return null;
207
- }
208
- }).filter(Boolean))];
223
+ const tempRoots = systemTempRoots();
209
224
  if (!tempRoots.some((t) => full === t || full.startsWith(`${t}${sep}`))) {
210
225
  throw fail(1, `--out refuses an outside-repo destination that is not under a system temp root (${full}) — grounding output is scratch: use $TMPDIR//tmp, or a fresh gitignored in-repo path (temp roots checked: ${tempRoots.join(', ')})`);
211
226
  }
@@ -243,7 +258,8 @@ export const assertScratchDestination = (outPath, cwd) => {
243
258
  const HELP = `grounding — grounded-review facts assembler for the agent-workflow family (AD-038).
244
259
 
245
260
  Usage:
246
- node grounding.mjs [--constraints] [--autonomy] [--plan <path>] [--reserve-bytes <n>] [--out <path>]
261
+ node grounding.mjs [--constraints] [--autonomy] [--plan <path>] [--extra <text|@file>]...
262
+ [--reserve-bytes <n>] [--out <path>]
247
263
 
248
264
  --constraints slice the root AGENTS.md "Hard Constraints" section verbatim
249
265
  (exactly one matching heading, else a loud STOP)
@@ -255,6 +271,14 @@ Usage:
255
271
  --plan <path> extract the plan's decision-bearing sections verbatim + whole:
256
272
  "## Approach" + "## Verification" (REQUIRED — STOP if missing),
257
273
  "## Decisions (locked)" when present; a duplicate heading is a STOP
274
+ --extra <text|@file> append orchestrator-supplied extra facts byte-verbatim AFTER the
275
+ mechanical sections (repeatable, argv order; the agy-review --facts
276
+ convention: literal text, or @path read whole through a race-free
277
+ descriptor). An @file must resolve inside the PROVEN git work tree
278
+ (rev-parse success; the git dir itself refused) or the system temp
279
+ surface — anything else refuses loudly, as does a missing, empty, or
280
+ non-regular file. The merge happens INSIDE the tool: no shell append
281
+ onto the emitted facts file
258
282
  --reserve-bytes <n> the artifact share agy-review will add around these facts — the output
259
283
  budget becomes AGY_MAX_PROMPT_BYTES − n (loud tail-trim on overflow)
260
284
  --out <path> write instead of stdout — system-temp scratch (rewritable), or a FRESH
@@ -274,11 +298,19 @@ const parseArgs = (argv) => {
274
298
  let plan = null;
275
299
  let out = null;
276
300
  let reserve = 0;
301
+ const extra = [];
277
302
  for (let i = 0; i < argv.length; i += 1) {
278
303
  const a = argv[i];
279
304
  if (a === '--constraints') constraints = true;
280
305
  else if (a === '--autonomy') autonomy = true;
281
- else if (a === '--plan') {
306
+ else if (a === '--extra') {
307
+ const val = argv[i + 1];
308
+ if (val == null || val === '' || val === '@' || val.startsWith('--')) {
309
+ throw fail(2, '--extra requires <text|@file> (repeatable)');
310
+ }
311
+ extra.push(val);
312
+ i += 1;
313
+ } else if (a === '--plan') {
282
314
  plan = argv[i + 1];
283
315
  if (!plan || plan.startsWith('--')) throw fail(2, '--plan requires a <path>');
284
316
  i += 1;
@@ -293,10 +325,10 @@ const parseArgs = (argv) => {
293
325
  i += 1;
294
326
  } else throw fail(2, `unknown argument: ${a}`);
295
327
  }
296
- if (!constraints && !autonomy && plan == null) {
297
- throw fail(2, 'nothing to assemble — pass --constraints, --autonomy, and/or --plan <path>');
328
+ if (!constraints && !autonomy && plan == null && extra.length === 0) {
329
+ throw fail(2, 'nothing to assemble — pass --constraints, --autonomy, --plan <path>, and/or --extra <text|@file>');
298
330
  }
299
- return { constraints, autonomy, plan, out, reserve };
331
+ return { constraints, autonomy, plan, out, reserve, extra };
300
332
  };
301
333
 
302
334
  const resolveBudget = (env, reserve) => {
@@ -316,7 +348,7 @@ export const main = (argv, ctx = {}) => {
316
348
  const env = ctx.env ?? process.env;
317
349
  try {
318
350
  if (argv.includes('--help') || argv.includes('-h')) return { code: 0, stdout: HELP, stderr: '' };
319
- const { constraints, autonomy, plan, out, reserve } = parseArgs(argv);
351
+ const { constraints, autonomy, plan, out, reserve, extra } = parseArgs(argv);
320
352
  const budget = resolveBudget(env, reserve);
321
353
 
322
354
  const readOrStop = (path, label) => {
@@ -348,8 +380,65 @@ export const main = (argv, ctx = {}) => {
348
380
  }
349
381
  const planText = plan != null ? readOrStop(plan, 'plan file') : null;
350
382
 
383
+ // --extra @file reads are CONFINED: the bridge tier auto-allows this tool with an args
384
+ // wildcard, so an unconfined @file would let an unattended run ship ANY readable file
385
+ // (~/.ssh, ~/.bashrc) into a prompt payload bound for a subscription CLI. The admitted read
386
+ // surface — computed ONCE per invocation — is the PROVEN git work tree (rev-parse success
387
+ // required; a cwd fallback would collapse the guard when cwd=$HOME) plus the system temp
388
+ // surface, MINUS the git dir(s) — repository internals never enter a facts payload. A non-@
389
+ // value is literal fact text (the agy-review --facts convention).
390
+ const extraReadSurface = () => {
391
+ const tempRoots = systemTempRoots();
392
+ const top = gitLine(['rev-parse', '--show-toplevel'], cwd);
393
+ if (top == null || top.status !== 0) return { tempRoots, topReal: null, gitDirsReal: [] };
394
+ const topReal = realpathSync(top.stdout.replace(/\r?\n$/, ''));
395
+ const gitDirsReal = ['--absolute-git-dir', '--git-common-dir'].map((flag) => {
396
+ const r = gitLine(['rev-parse', flag], cwd);
397
+ if (r == null || r.status !== 0) {
398
+ throw fail(1, `--extra cannot resolve the git dir (git rev-parse ${flag} failed) — refusing @file reads in an unmappable repo`);
399
+ }
400
+ return realpathSync(resolve(cwd, r.stdout.replace(/\r?\n$/, '')));
401
+ });
402
+ // The linked-worktree `.git` is a FILE inside the tree yet outside both answers above —
403
+ // repository metadata all the same.
404
+ gitDirsReal.push(join(topReal, '.git'));
405
+ return { tempRoots, topReal, gitDirsReal };
406
+ };
407
+ const surface = extra.some((v) => v.startsWith('@')) ? extraReadSurface() : null;
408
+ const resolveExtra = (value) => {
409
+ if (!value.startsWith('@')) return value;
410
+ const ref = value.slice(1);
411
+ const real = (() => {
412
+ try {
413
+ // Canonicalize the PARENT only — the leaf stays un-dereferenced so the no-follow open
414
+ // refuses a symlink leaf instead of silently reading its target.
415
+ const lexical = resolve(cwd, ref);
416
+ return join(realpathSync(dirname(lexical)), basename(lexical));
417
+ } catch (err) {
418
+ throw fail(1, `--extra file '${ref}' is unreadable (${(err && err.code) || err}) — STOP`);
419
+ }
420
+ })();
421
+ const within = (root) => real === root || real.startsWith(`${root}${sep}`);
422
+ const inTree = surface.topReal != null && within(surface.topReal);
423
+ if (!inTree && !surface.tempRoots.some(within)) {
424
+ throw fail(1, `--extra '@${ref}' resolves outside the work tree and the system temp surface (${real}) — refusing to read it into the facts payload`);
425
+ }
426
+ if (surface.gitDirsReal.some(within)) {
427
+ throw fail(1, `--extra '@${ref}' resolves inside the git dir (${real}) — repository internals never enter a facts payload`);
428
+ }
429
+ // Descriptor-bound read (the kit's ONE no-follow door): a FIFO cannot block the open, and a
430
+ // leaf swapped after the containment checks cannot change what the fd reads.
431
+ const r = readRegularFileNoFollow(real);
432
+ if (r.outcome === 'absent') throw fail(1, `--extra file '${ref}' is unreadable (ENOENT) — STOP`);
433
+ if (r.outcome === 'foreign') throw fail(1, `--extra file '${ref}' is not a regular file (${r.className}) — refusing; STOP`);
434
+ if (r.outcome !== 'ok') throw fail(1, `--extra file '${ref}' is unreadable (${r.code}) — STOP`);
435
+ if (r.content.trim() === '') throw fail(1, `--extra file '${ref}' is empty — nothing to append; STOP`);
436
+ return r.content; // byte-verbatim — no trailing-newline normalization
437
+ };
438
+ const extraTexts = extra.map(resolveExtra);
439
+
351
440
  const parts = [];
352
- const assembled = assembleGrounding({ constraintsText, autonomyText, planText, planLabel: plan ?? 'plan' });
441
+ const assembled = assembleGrounding({ constraintsText, autonomyText, planText, planLabel: plan ?? 'plan', extraTexts });
353
442
  if (assembled) parts.push(assembled);
354
443
  const payload = parts.join('\n');
355
444
  const { text, trimmedBytes } = trimToBudget(payload, budget);