@sabaiway/agent-workflow-kit 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +82 -1
  2. package/README.md +1 -0
  3. package/SKILL.md +7 -3
  4. package/bin/install.mjs +10 -1
  5. package/bridges/antigravity-cli-bridge/SKILL.md +6 -4
  6. package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +20 -7
  7. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +84 -7
  8. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +189 -18
  9. package/bridges/antigravity-cli-bridge/bin/agy.sh +55 -6
  10. package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +77 -41
  11. package/bridges/antigravity-cli-bridge/capability.json +4 -2
  12. package/bridges/antigravity-cli-bridge/references/driving-agy.md +5 -0
  13. package/bridges/codex-cli-bridge/SKILL.md +8 -4
  14. package/bridges/codex-cli-bridge/bin/codex-exec.sh +129 -11
  15. package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +278 -23
  16. package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +20 -7
  17. package/bridges/codex-cli-bridge/bin/codex-review.sh +76 -13
  18. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +112 -17
  19. package/bridges/codex-cli-bridge/capability.json +19 -4
  20. package/bridges/codex-cli-bridge/references/driving-codex.md +11 -0
  21. package/capability.json +1 -1
  22. package/package.json +1 -1
  23. package/references/modes/bootstrap.md +3 -2
  24. package/references/modes/recommendations.md +1 -0
  25. package/references/modes/upgrade.md +9 -4
  26. package/references/modes/velocity.md +2 -0
  27. package/references/modes/worktrees.md +120 -0
  28. package/references/scripts/archive-decisions.mjs +2 -2
  29. package/references/scripts/archive-decisions.test.mjs +5 -5
  30. package/references/scripts/check-docs-size-cli.test.mjs +41 -0
  31. package/references/scripts/check-docs-size.mjs +46 -29
  32. package/references/shared/command-shapes.md +22 -0
  33. package/references/shared/composition-handoff.md +7 -2
  34. package/references/templates/agent_rules.md +10 -1
  35. package/tools/autonomy-doctor.mjs +1 -1
  36. package/tools/bridge-settings-read.mjs +25 -5
  37. package/tools/changed-surface.mjs +8 -8
  38. package/tools/commands.mjs +9 -0
  39. package/tools/delegation.mjs +2 -2
  40. package/tools/detect-backends.mjs +10 -0
  41. package/tools/grounding.mjs +13 -13
  42. package/tools/inject-methodology.mjs +71 -40
  43. package/tools/lens-region.mjs +113 -36
  44. package/tools/migrate-adr-store.mjs +3 -3
  45. package/tools/procedures.mjs +1 -1
  46. package/tools/recipes.mjs +2 -2
  47. package/tools/recommendations.mjs +34 -7
  48. package/tools/release-scan.mjs +12 -2
  49. package/tools/review-state.mjs +3 -3
  50. package/tools/sandbox-masks.mjs +13 -13
  51. package/tools/set-recipe.mjs +1 -1
  52. package/tools/velocity-profile.mjs +7 -7
  53. package/tools/worktrees.mjs +2292 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,87 @@ 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
+ ## 3.2.0 — plain language + posture as code: the friction cluster shipped as mechanism (AD-061)
8
+
9
+ Carrier release for a friction cluster (kit 3.2.0 bundles the bridges + shared contract + the
10
+ plain-language template bar; memory 3.1.0, codex bridge 3.1.0, agy bridge 4.1.0 ride alongside;
11
+ engine unbumped). The packaged fixes below ship as MECHANISM for every user, never as one agent's
12
+ discipline.
13
+
14
+ - **Shared command-shapes contract.** New `references/shared/command-shapes.md` states the
15
+ promptless bar for improvised reads/probes — the host's file-read tool, else ONE plain
16
+ undecorated command (no compounds, redirects, pipes, or command substitution; improvised writes
17
+ use the host's file-edit tools). Bound via the existing `Requires:` mechanism to exactly the
18
+ probe-instructing modes (bootstrap / upgrade / velocity, closed-world pinned) + a one-sentence
19
+ inline bar on the SKILL.md router stamp-read. The honest residual is stated (a decorated form is
20
+ host/config-dependent) — never a false "always prompts" guarantee.
21
+ - **Plain-language §2.5 Communication bar** in BOTH deployed `agent_rules` templates,
22
+ byte-identically (the kit fallback deploy path communicates under the same bar): user-facing
23
+ narration is short and plain in the dialogue language; transliterated jargon is banned; an
24
+ English term survives only as the NAME of a thing (flag / command / file / test), glossed.
25
+ `lens-region.mjs` now reconciles a SECOND region (Communication, canon from the kit's own bundled
26
+ template) so EXISTING deployments gain the bar on `upgrade`, not only new bootstraps.
27
+ - **Posture as code on the codex-exec dispatch and both review dispatches** (bridges bundled here).
28
+ `codex-exec` gains a D5 exec posture banner (fresh + resume, resolved post-validation values, a
29
+ validated resume-id grammar + raw-byte NUL screen, control-byte refusal on every field). Both
30
+ review banners gain a BANNER-ONLY `timeout` field — outside D5 banner↔receipt parity — from ONE
31
+ shared effective-timeout resolver (byte-identical across the four wrappers) that closes the old
32
+ env-validation bypass and resolves the timeout binary shadow-proof; no capping binary →
33
+ `timeout=uncapped`, never fabricated. (The raw `agy-run` probe lane keeps no posture banner —
34
+ out of scope this release.) The quote-the-banner-verbatim duty rides both bridges' driving
35
+ contracts, capability notes, and the kit registry mirror, drift-guarded by a new cross-file test;
36
+ the threat-model boundary is stated on the codex execute capability and `codex-exec --help`
37
+ surfaces, including the kit mirror. The read-only `bridge-settings` advisor mirrors the wrapper's
38
+ control-byte refusal semantics with escaped output.
39
+
40
+ ## 3.1.0 — parallel feature worktrees v1: provision · list · land --prepare · cleanup (AD-060)
41
+
42
+ New `worktrees` mode + `tools/worktrees.mjs` — several features implemented simultaneously in
43
+ DIFFERENT agent sessions on one machine/repo, zero interference on working-tree files (the ONE
44
+ exception: the default `node_modules` symlink is a shared MUTABLE dependency cache — the printed
45
+ isolated install is the isolation lane), unambiguous ownership. One thin dependency-free tool over git: every verification datum is recomputed live
46
+ from git, never read from stored metadata (the ONE exception: the PREPARED OID in the handoff,
47
+ read back only for recovery). Git >= 2.36 floor (NUL-terminated worktree porcelain); typed STOPs,
48
+ never a guess.
49
+
50
+ - **`provision <slug> --plan <path>`** — visible sibling worktree + `aw/<slug>` branch: the
51
+ registry-derived footprint copy-if-missing (a tracked file is NEVER overwritten), exactly one
52
+ seeded feature plan, the `handoff-<slug>.md` stub, shared-`node_modules` symlink advice
53
+ (`--install` only PRINTS the isolated install — zero spawn, zero write). `--resume` completes a
54
+ half-done provision behind fail-closed identity: at most one handoff, slug AND branch must
55
+ match, the section-required `## Provision record` (a duplicated field or section is a STOP,
56
+ never last-wins), user content preserved byte-exact.
57
+ - **`list`** — read-only and honest: any read failure renders `handoff: (unreadable)` — a silent
58
+ "no" never appears.
59
+ - **`land <slug> --prepare`** — the transient common-git-dir lock (shared with cleanup),
60
+ dirty-main / graph-divergence / docs-ai-drift / red-review-state refusals, the satellite
61
+ working-tree diff versus its base — staged AND unstaged inspected, every unstaged or
62
+ untracked-not-ignored leftover listed and refused (ignored content is outside observation by
63
+ design) — binary-safe transfer excluding exactly `docs/ai` + `docs/plans`, the optional
64
+ porcelain-visible sync adapter, the main gate matrix, and an OID report (main HEAD · TRANSFER ·
65
+ PREPARED · sync delta). The commit is NEVER run by the tool — it stays a dialogue ask. A second
66
+ prepare is reset-only against the recorded PREPARED OID; transfer-apply and post-launch sync
67
+ failures attempt a byte-clean rollback (rollback failures are composed without losing the
68
+ primary error), while a red gate matrix that leaves the snapshot unchanged intentionally KEEPS
69
+ the prepared tree and names both recovery lanes.
70
+ - **`cleanup <slug>`** — live landed-verification against main HEAD (exact land-exclusion
71
+ parity), typed-EXACT ownership of ignored content (file/glob roots own only files), literal
72
+ pathspecs, branch `-d`, prune; foreign content stops it. `--abandon` is the ONE destructive arm
73
+ (`-D`; requires the handoff identity).
74
+ - **Hardening:** every content read and regular-file copy goes through two no-follow descriptor
75
+ doors (identity-bound source · exclusive destination · descriptor-mode update), pinned as the
76
+ only paths by tripwire tests; strict parsers everywhere (NUL porcelain fields,
77
+ scan-before-parse JSON with any-depth duplicate-key refusal, atomic section surgery).
78
+ - **release-scan:** the reviewer-round-identity rung — an `agy` or `codex` name followed by an
79
+ R-number reference is refused; shippable finding IDs use neutral
80
+ `review-<scope>-rNN-<severity>-NN` IDs; the clean line now reads "no AI attribution or
81
+ reviewer-round identity found".
82
+ - **recommendations:** the worktrees parent-dir item — write access stays "not confirmed" without
83
+ a trusted host-capability signal; HAND-APPLY lines are host-qualified.
84
+ - **Docs:** the `references/modes/worktrees.md` contract — the MAIN/SATELLITE ownership matrix
85
+ (shared git state included), satellite forbidden verbs (the v1 docs-only bar), the
86
+ host-specific consent lane, and the other-harnesses PROVEN/ASSUMED split.
87
+
7
88
  ## 3.0.0 — strip-the-kit: the hardened computed core replaces the ledger machinery (AD-059)
8
89
 
9
90
  > ### ⚠ BREAKING — the review loop is now computed, not recorded
@@ -502,7 +583,7 @@ was blocked on an honest degrade.
502
583
 
503
584
  Contract surfaces moved in lockstep (the tool header, `--help`, the human render, the `--await`
504
585
  comment, `references/modes/review-state.md`). Council-converged both segments (S1 codex + agy SHIP; S2
505
- codex R1 revise → allPresent fold → R2 both SHIP), the two-gate agreement dogfooded live + pinned by a
586
+ review round 1 revise → allPresent fold → review round 2 both SHIP), the two-gate agreement dogfooded live + pinned by a
506
587
  detector-independent `two-gate-agreement.test.mjs` + a 17-case matrix; full suite 2969 green.
507
588
 
508
589
  ## 1.40.0 — Universal verification profile + session-loop economics (a)–(h) (BUGFREE-3, AD-049)
package/README.md CHANGED
@@ -243,6 +243,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
243
243
  | `/agent-workflow-kit commit-guard` | any time | **the read-only pre-commit guard** (D10) — 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. |
244
244
  | `/agent-workflow-kit recommendations` | any time (every `upgrade` ends with it) | **read-only deployment advisor** (AD-044) — computes what in THIS deployment is configured sub-optimally (allowlist not seeded, autonomy render drifted, OS sandbox unavailable, gates undeclared, bridge friction, sandbox-mask clutter, an unacknowledged sandbox recipe) and renders **verdict-first**: one composed verdict line (does anything need attention?), then each item as **{severity · what · one-line benefit · an optional `recipe:` line (sandbox-lane only) · the exact consent-gated apply one-liner}**. The agent PRESENTS the section in the user's conversational language — every fact and count, nothing added or dropped; commands, paths, hosts and rule strings byte-exact; raw tool block on request — and runs EXACTLY the rendered one-liners only on your yes, surfacing each item's posture note first. Renders **present-even-when-empty** (`no recommendations — flow optimal.`); a failed probe degrades to a stated skip line. Registry strings are fact-true frozen one-line data (posture/risk notes live in the mode doc at the consent moment); the kit never seeds `sandbox.network.allowedDomains` / `filesystem.allowWrite` (**HAND-APPLY** territory), and the sandbox-lane item's convergence is a neutral fingerprint acknowledgement recorded by a consent-gated ack writer into `docs/ai/acks.json` — never a security key (the recipe is documented per bridge in `capability.json` `networkHosts` + `writableDirs`). `--cwd` is required (the target project is explicit); never writes, never commits, never runs a subscription CLI. |
245
245
  | `/agent-workflow-kit doc-parity` | any time | **read-only doc-parity lint** (AD-049) — kills the doc-drift class where a mode-contract doc silently lags a code constant (a `--check` doc still reading `300` after the diff cap moved to `400`): a **closed, exported registry** binds each live constant (review caps, schema versions, the ledger's own class/scope vocabulary, and the autonomy-doctor EXIT/status/trusted-dir contract) to the exact token its `references/modes/*.md` contract must carry, and asserts the CURRENT value renders into every bound file — a drifted doc, an unreadable file, or an absent token **fails closed**. The values are sourced from the live imports (never re-typed), so the lint can't itself go stale; adding a binding is adding a checked entry (closed-world, edit-safe). `--check` is a gate exit code for `docs/ai/gates.json`. Never writes, never commits, never runs a subscription CLI. |
246
+ | `/agent-workflow-kit worktrees` | any time | **parallel feature worktrees** — run several features in DIFFERENT agent sessions on one repo, zero interference on working-tree files (the ONE exception is the dependency cache, below): `provision <slug> --plan <file>` creates a sibling git worktree on branch `aw/<slug>` and populates it (registry-derived footprint copy-if-missing — a tracked file is never overwritten; EXACTLY ONE seeded feature plan; the `handoff-<slug>.md` record from minute zero; `node_modules` symlinked where the link stays ignored — a shared MUTABLE dependency cache: writes through it hit MAIN's node_modules; for isolation run the printed isolated-install command (`--install` only PRINTS it; on `--resume` run the printed unlink-first recovery first); absolute root-pinned gate commands rebased on untracked copies only, and only while their bytes equal the MAIN source or its rebased form — user-modified copies stay untouched); `list` is read-only (slug, branch, base, dirty, handoff); `land <slug> --prepare` locks the common git dir, fail-closes on divergence or incomplete satellite state, transfers the complete accepted satellite diff onto a CLEAN main, runs sync plus the declared gates, and reports HEAD/TRANSFER/PREPARED OIDs — the commit ALWAYS stays a dialogue ask; `cleanup <slug>` takes the same lock and removes a LANDED worktree only after live landed-verification against main HEAD, while `--abandon` is the ONE destructive arm (destroys unlanded work; **no preview step** on any writer). The parent dir is the `docs/ai/worktrees.json` `parentDir` setting (default: the repo's sibling parent); an unwritable parent degrades to printed maintainer-pasted commands, and the one-time host consent that makes it promptless surfaces via `recommendations`. Never commits, never pushes, never runs a subscription CLI. |
246
247
  | `/agent-workflow-kit uninstall` | opt-in, any time | **guarded teardown** — the inverse of `init` / `setup`. Removes only what's **provably ours** (managed skill dirs + bridge wrappers; in a project, the hidden-mode git-ignore block it added + the pre-commit hook it installed); **never deletes** your `docs/ai` / `AGENTS.md` (prints the exact `rm` to run by hand) or your `.claude/settings.json` (prints an **edit** — remove the attribution key, review any velocity `permissions.*` — never an `rm`). Always `--dry-run` first; preflight-then-mutate; never commits. |
247
248
  | `/agent-workflow-kit velocity` | Claude Code · opt-in | **onboarding velocity profile** — seeds a fixed, audited **read-only** allowlist into `.claude/settings.json` so routine read-only commands stop idling on approval prompts while you're away; opt-in `acceptEdits`; plus a **read-only advisory** of likely project gate commands to add by hand. Writes **only** `.claude/settings.json` — **never** allowlists commit/push/publish, never writes `settings.local.json`, never commits. A seeded entry is a **trust posture, not a sandbox** (a runtime residual remains at the settings level — its guard ships as the opt-in `hook` command); a direct commit/push/publish still asks. `--dry-run` first. |
248
249
  | `/agent-workflow-kit agents` | Claude Code · opt-in | **cheap-lane subagents** — places bundled subagent definitions (`.claude/agents/`) pinned to a **cheap model** (haiku, low effort, read-only tools) for mechanical work: extraction sweeps, changelog fact-skeletons, gate-failure triage. Judgment, review, and real code stay on your main lane — these vehicles only extract and draft, and their output is verified. Preview by default (`--apply` writes); an existing customized file is **preserved, never overwritten**; never touches `settings*.json`, never commits. |
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: '3.0.0'
6
+ version: '3.2.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-kit
@@ -68,7 +68,7 @@ Pick the mode from the user's invocation — the mapping is pinned by `tools/com
68
68
 
69
69
  **Safe-routing rule (which mode did the user invoke?).** Map the invocation token with `tools/commands.mjs` `routeInvocation`: a **known** subcommand → its mode; the **bare/empty** invocation → `bootstrap` — the one writer reachable without a token, and only on an undeployed project (if `docs/ai/` already exists, **ask upgrade-vs-bootstrap**, never overwrite); **any unrecognized/ambiguous** token → `help`, which is **read-only**. The invariant: **no unrecognized/garbage invocation ever triggers a write** (only an explicit known token or the acknowledged bare-bootstrap exception can).
70
70
 
71
- Before acting, read `docs/ai/.workflow-version` (the project's stamp) to decide the route — an **internal** routing decision, **not** a line you print (version display: *Version disclosure* in `${CLAUDE_SKILL_DIR}/references/shared/report-footer.md`). Route:
71
+ Before acting, read `docs/ai/.workflow-version` (the project's stamp) — via the host's file-read tool, else ONE plain undecorated command (no compounds/redirects/pipes) — to decide the route — an **internal** routing decision, **not** a line you print (version display: *Version disclosure* in `${CLAUDE_SKILL_DIR}/references/shared/report-footer.md`). Route:
72
72
 
73
73
  - **absent** → bootstrap (a fresh deployment).
74
74
  - **stamp < `3.0.0`** (the deployment-lineage head) → `upgrade`.
@@ -187,11 +187,15 @@ read-only — read `${CLAUDE_SKILL_DIR}/references/modes/commit-guard.md` before
187
187
 
188
188
  read-only — read `${CLAUDE_SKILL_DIR}/references/modes/doc-parity.md` before acting.
189
189
 
190
+ ### Mode: worktrees
191
+
192
+ writer — read `${CLAUDE_SKILL_DIR}/references/modes/worktrees.md` before acting.
193
+
190
194
  ---
191
195
 
192
196
  ## References
193
197
 
194
- - [`references/modes/`](${CLAUDE_SKILL_DIR}/references/modes/) — one file per mode; [`references/shared/`](${CLAUDE_SKILL_DIR}/references/shared/) — the shared point-of-use contracts (report footer · composition hand-off · deploy tail).
198
+ - [`references/modes/`](${CLAUDE_SKILL_DIR}/references/modes/) — one file per mode; [`references/shared/`](${CLAUDE_SKILL_DIR}/references/shared/) — the shared point-of-use contracts (report footer · composition hand-off · deploy tail · command shapes).
195
199
  - [`references/contracts.md`](${CLAUDE_SKILL_DIR}/references/contracts.md) — the three setup contracts.
196
200
  - [`references/templates/`](${CLAUDE_SKILL_DIR}/references/templates/) — the `AGENTS.md` + `docs/ai/` templates + seeded configs; [`references/scripts/`](${CLAUDE_SKILL_DIR}/references/scripts/) — the Node enforcement scripts; [`migrations/`](${CLAUDE_SKILL_DIR}/migrations/) — per-version upgrade steps; [`launchers/`](${CLAUDE_SKILL_DIR}/launchers/) — non-Claude agent launchers.
197
201
  - [`tools/`](${CLAUDE_SKILL_DIR}/tools/) — the family-wide tooling; each tool's contract lives with its mode file and its own header; bridge mirrors under [`bridges/`](${CLAUDE_SKILL_DIR}/bridges/).
package/bin/install.mjs CHANGED
@@ -211,7 +211,16 @@ const spawnMember = ({ command, args, options }) => spawnSync(command, args, opt
211
211
  // npm/network blip (rate-limit, registry hiccup, momentary DNS) — an immediate retry tends to hit the
212
212
  // same blip, so wait briefly first. Atomics.wait is a dependency-free sync sleep (the install flow is
213
213
  // already synchronous here). Injected in tests as a 0ms no-op so the suite never actually sleeps.
214
- const RETRY_DELAY_MS = 1500;
214
+ // AW_INSTALL_RETRY_DELAY_MS is a test seam (the subprocess cascade suite zeroes the wait; the
215
+ // retry POLICY itself stays exercised). Unset, empty, malformed and negative all keep the shipped
216
+ // default — `Number('')` is 0, so the raw value is screened BEFORE coercion (the R1 council bug).
217
+ export const resolveRetryDelayMs = (env) => {
218
+ const raw = env.AW_INSTALL_RETRY_DELAY_MS;
219
+ if (typeof raw !== 'string' || raw.trim() === '') return 1500;
220
+ const parsed = Number(raw);
221
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1500;
222
+ };
223
+ const RETRY_DELAY_MS = resolveRetryDelayMs(process.env);
215
224
  const sleepSync = (ms) => {
216
225
  if (ms > 0) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
217
226
  };
@@ -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: '4.0.0'
5
+ version: '4.1.0'
6
6
  ---
7
7
 
8
8
  # antigravity-cli-bridge
@@ -134,10 +134,12 @@ agy-review --continue --decided @round1.md --focus "still-open items" # round-
134
134
 
135
135
  **Honesty + posture (D4/D5):** a run with no recognized `### Verdict` section — empty output
136
136
  included — **exits 4 with NO receipt**: treat it as a *failed review to re-run*, never a fatal
137
- session error. One stderr banner states the actual posture (`review posture: model=…`) and the
138
- receipt records the same `posture {model}`; an attesting review with `AGY_MODEL` explicitly
137
+ session error. One stderr banner states the actual posture (`review posture: model=… timeout=…`)
138
+ and the receipt records the same `posture {model}`; an attesting review with `AGY_MODEL` explicitly
139
139
  emptied refuses pre-spend (`AGY_PROBE=1` exempt), and control bytes in a model string refuse
140
- pre-spend in every mode.
140
+ pre-spend in every mode. The `timeout=` field is **banner-only** (exactly the duration `agy-run`
141
+ hands to `timeout(1)`, or `uncapped`) — informational, never a receipt field. **Quote the posture
142
+ banner verbatim** when labeling a dispatch.
141
143
 
142
144
  Frontier default `Gemini 3.1 Pro (High)`; **any** model is allowed (a sub-frontier one earns a
143
145
  silenceable `AGY_PROBE=1` advisory). An oversized `code` review trips the byte ceiling with trim/split
@@ -5,9 +5,9 @@
5
5
  // posture.model null). Colocated separately from agy-review.test.mjs — that file is
6
6
  // red-proof-frozen; this one carries its own minimal standalone harness (the family idiom).
7
7
 
8
- import { describe, it } from 'node:test';
8
+ import { describe, it, after } from 'node:test';
9
9
  import assert from 'node:assert/strict';
10
- import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, chmodSync, existsSync, readdirSync, symlinkSync } from 'node:fs';
10
+ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, chmodSync, existsSync, readdirSync, symlinkSync, cpSync } from 'node:fs';
11
11
  import { tmpdir } from 'node:os';
12
12
  import { join, dirname, resolve } from 'node:path';
13
13
  import { fileURLToPath } from 'node:url';
@@ -41,12 +41,17 @@ const makePathWithout = (root, exclude = []) => {
41
41
  return dir;
42
42
  };
43
43
 
44
- const makeSandbox = () => {
45
- const home = mkdtempSync(join(tmpdir(), 'agy-honesty-'));
44
+ // Farm + sandbox base are READ-ONLY per invocation — built ONCE, shared (a per-run farm rebuild
45
+ // plus a per-test `git init`+commit dominate the wall otherwise).
46
+ const SHARED_ROOT = mkdtempSync(join(tmpdir(), 'agy-honesty-shared-'));
47
+ after(() => rmSync(SHARED_ROOT, { recursive: true, force: true }));
48
+ const FARM = makePathWithout(SHARED_ROOT, ['agy', 'agy-run']);
49
+
50
+ const TEMPLATE_HOME = (() => {
51
+ const home = join(SHARED_ROOT, 'template-home');
46
52
  const bin = join(home, '.local', 'bin');
47
53
  mkdirSync(bin, { recursive: true });
48
54
  writeFileSync(join(bin, 'agy'), FAKE_AGY, { mode: 0o755 });
49
- chmodSync(join(bin, 'agy'), 0o755);
50
55
  const repo = join(home, 'repo');
51
56
  mkdirSync(repo);
52
57
  const g = (...args) => spawnSync('git', args, { cwd: repo, encoding: 'utf8' });
@@ -58,7 +63,15 @@ const makeSandbox = () => {
58
63
  g('commit', '-qm', 'base');
59
64
  writeFileSync(join(repo, 'pending.txt'), 'PENDING\n');
60
65
  writeFileSync(join(repo, 'plan.md'), '# a plan artifact\n');
61
- return { home, bin, repo };
66
+ return home;
67
+ })();
68
+
69
+ const makeSandbox = () => {
70
+ const home = mkdtempSync(join(tmpdir(), 'agy-honesty-'));
71
+ cpSync(TEMPLATE_HOME, home, { recursive: true });
72
+ const bin = join(home, '.local', 'bin');
73
+ chmodSync(join(bin, 'agy'), 0o755);
74
+ return { home, bin, repo: join(home, 'repo') };
62
75
  };
63
76
 
64
77
  const run = (sb, { args, env = {} } = {}) => {
@@ -69,7 +82,7 @@ const run = (sb, { args, env = {} } = {}) => {
69
82
  timeout: 30000,
70
83
  env: {
71
84
  HOME: sb.home,
72
- PATH: `${sb.bin}:${makePathWithout(sb.home, ['agy', 'agy-run'])}`,
85
+ PATH: `${sb.bin}:${FARM}`,
73
86
  TMPDIR: process.env.TMPDIR ?? '/tmp',
74
87
  AGY_FAKE_SENTINEL: sentinel,
75
88
  ...env,
@@ -77,6 +77,11 @@ Notes:
77
77
  pre-dispatch host-diff: before the FIRST dispatch of this bridge, diff its declared networkHosts
78
78
  against the live sandbox allow-list — a missing host is surfaced to the maintainer BEFORE
79
79
  dispatching, never fired into a known prompt
80
+ the review posture banner appends a banner-only timeout=<duration|uncapped> field — exactly the
81
+ duration agy-run hands to timeout(1), uncapped when no timeout/gtimeout binary caps the run;
82
+ INFORMATIONAL only: it never enters the receipt posture or the D5 banner↔receipt parity
83
+ quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated
84
+ posture; a prose re-type drifts
80
85
 
81
86
  Round-2 / resume:
82
87
  agy-review --continue [--decided @f] [--focus "…"]
@@ -115,10 +120,12 @@ Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins ov
115
120
  Honesty + posture (D4/D5):
116
121
  a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4
117
122
  with NO receipt: a FAILED review to RE-RUN, never a fatal session error. One stderr banner line
118
- states the ACTUAL run posture (review posture: model=…) and the receipt records the same
119
- posture {model} (agy has no tier). An ATTESTING review with AGY_MODEL explicitly emptied
120
- refuses pre-spend (the actual model would be unknowable; AGY_PROBE=1 is exempt), and a model
121
- string carrying control bytes refuses pre-spend in every mode.
123
+ states the ACTUAL run posture (review posture: model=… timeout=…) and the receipt records the
124
+ same posture {model} (agy has no tier; the timeout field is banner-only never a receipt
125
+ field). Quote the posture banner verbatim when labeling this dispatch. An ATTESTING review with
126
+ AGY_MODEL explicitly emptied refuses pre-spend (the actual model would be unknowable;
127
+ AGY_PROBE=1 is exempt), and a model string carrying control bytes refuses pre-spend in every
128
+ mode.
122
129
 
123
130
  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).
124
131
  Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
@@ -235,16 +242,66 @@ aw_apply_settings() {
235
242
  }
236
243
  aw_apply_settings
237
244
 
245
+ # --- Effective-timeout resolver (D5 banner honesty; AD-061) --------------------
246
+ # ONE rule, both bridges: the posture banner prints EXACTLY the duration handed to timeout(1) —
247
+ # an integer-seconds value rendered with the `s` suffix, a duration string verbatim — and
248
+ # `timeout=uncapped` when no timeout/gtimeout binary can cap the run; never a fabricated number.
249
+ # The EFFECTIVE value (env included — closing the aw_settings_valid env bypass) is validated by
250
+ # the same per-key rule as the settings file, plus a 7-digit integer-part bound (overflow); an
251
+ # invalid value warns + falls back to the built-in default — a typo never silently masquerades
252
+ # as a cap. AGY_TIMEOUT shares AGY_HARD_TIMEOUT's duration rule (it has no settings-file arm).
253
+ aw_effective_timeout() {
254
+ local key="$1" default="$2" value="${!1:-}" rule="$1" intpart
255
+ [[ "$rule" == "AGY_TIMEOUT" ]] && rule="AGY_HARD_TIMEOUT"
256
+ [[ -n "$value" ]] || { printf '%s' "$default"; return 0; }
257
+ intpart="${value%%[!0-9]*}"
258
+ if ! aw_settings_valid "$rule" "$value" || (( ${#intpart} > 7 )); then
259
+ # %q escapes the raw value so a control byte in it can never forge an extra diagnostic line
260
+ # (the direct agy-run lane has no pre-spend screen — the warning itself must be injection-proof).
261
+ printf "warning: invalid value '%q' for %s — using the built-in default %s.\n" "$value" "$key" "$default" >&2
262
+ printf '%s' "$default"
263
+ return 0
264
+ fi
265
+ printf '%s' "$value"
266
+ }
267
+ aw_timeout_label() {
268
+ local bin="$1" value="$2"
269
+ [[ -n "$bin" ]] || { printf 'uncapped'; return 0; }
270
+ case "$value" in
271
+ *[!0-9]*) printf '%s' "$value" ;;
272
+ *) printf '%ss' "$value" ;;
273
+ esac
274
+ }
275
+ aw_resolve_timeout_bin() {
276
+ local bin dir base
277
+ bin="$(builtin type -P timeout 2>/dev/null || true)"
278
+ [[ -n "$bin" ]] || bin="$(builtin type -P gtimeout 2>/dev/null || true)"
279
+ [[ -n "$bin" ]] || { printf ''; return 0; }
280
+ case "$bin" in
281
+ /*) ;;
282
+ *)
283
+ case "$bin" in
284
+ */*) dir="${bin%/*}"; base="${bin##*/}" ;;
285
+ *) dir="."; base="$bin" ;;
286
+ esac
287
+ dir="$(builtin cd -- "$dir" 2>/dev/null && builtin pwd -P)" || { printf ''; return 0; }
288
+ bin="$dir/$base"
289
+ ;;
290
+ esac
291
+ [[ -f "$bin" && -x "$bin" ]] || { printf ''; return 0; }
292
+ printf '%s' "$bin"
293
+ }
294
+
238
295
  DEFAULT_AGY_REVIEW_MODEL="Gemini 3.1 Pro (High)"
239
296
  # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
240
297
  # version (drift-guarded by agy-review.test.mjs against capability.json).
241
298
  AW_RECEIPT_BACKEND="agy"
242
- AW_BRIDGE_VERSION="4.0.0"
299
+ AW_BRIDGE_VERSION="4.1.0"
243
300
  # `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
244
301
  AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
245
302
  # D5 control-byte screen — IMMEDIATELY after resolution, BEFORE the off-frontier advisory (or any
246
303
  # other interpolation) can echo raw newline/ESC bytes into stderr/the terminal (round-2 fold).
247
- if [[ "$AGY_MODEL" == *[$'\x01'-$'\x1f']* ]]; then
304
+ if [[ "$AGY_MODEL" == *[$'\x01'-$'\x1f'$'\x7f']* ]]; then
248
305
  echo "error: AGY_MODEL contains control bytes — fix the setting (env or bridge-settings.conf) and re-run." >&2
249
306
  exit 2
250
307
  fi
@@ -254,7 +311,20 @@ FRONTIER_SET=("Gemini 3.1 Pro (High)" "Claude Opus 4.6 (Thinking)" "Claude Sonne
254
311
  # Duration-string timeouts (NOT codex's bare seconds): agy-run forwards a duration to --print-timeout,
255
312
  # and the timeout(1) hard cap is a duration too — never numerically compared, so 30m vs 2h is fine.
256
313
  AGY_HARD_TIMEOUT="${AGY_HARD_TIMEOUT:-30m}"
314
+ # D5 banner-field control-byte screen (AD-061) — like AGY_MODEL above, BEFORE any interpolation.
315
+ if [[ "$AGY_HARD_TIMEOUT" == *[$'\x01'-$'\x1f'$'\x7f']* ]]; then
316
+ echo "error: AGY_HARD_TIMEOUT contains control bytes — fix the setting (env or bridge-settings.conf) and re-run." >&2
317
+ exit 2
318
+ fi
319
+ AGY_HARD_TIMEOUT="$(aw_effective_timeout AGY_HARD_TIMEOUT 30m)"
257
320
  AGY_TIMEOUT="${AGY_TIMEOUT:-$AGY_HARD_TIMEOUT}"
321
+ # D5 banner-field control-byte screen for AGY_TIMEOUT (AD-061): agy-review forwards it to the
322
+ # child agy-run's --print-timeout, so a control byte must refuse HERE, pre-spawn — the child's
323
+ # own diagnostic would otherwise echo it. (agy-run screens its own direct-lane env separately.)
324
+ if [[ "$AGY_TIMEOUT" == *[$'\x01'-$'\x1f'$'\x7f']* ]]; then
325
+ echo "error: AGY_TIMEOUT contains control bytes — fix the setting (env or bridge-settings.conf) and re-run." >&2
326
+ exit 2
327
+ fi
258
328
  AGY_PROBE="${AGY_PROBE:-0}"
259
329
  # A probe run is RECORDED, not just silently allowed: the receipt carries probe:true so the kit's
260
330
  # review-state gate rejects it — a guards-relaxed review must never attest a tree.
@@ -882,7 +952,14 @@ if [[ -z "$AGY_MODEL" && -z "$resume_mode" && "$REVIEW_PROBE" != "true" && "$REV
882
952
  echo " AGY_MODEL (wrapper default), or set the real model display string; AGY_PROBE=1 is exempt." >&2
883
953
  exit 2
884
954
  fi
885
- echo "review posture: model=${AGY_MODEL:-<agy settings default>}" >&2
955
+ # The timeout field is BANNER-ONLY (AD-061): it prints exactly the duration agy-run hands to
956
+ # timeout(1), or `uncapped` without a capping binary, and never enters the receipt posture.
957
+ # aw_resolve_timeout_bin: builtin type -P (an exported function can shadow neither `timeout` nor
958
+ # `type` itself), normalized to an absolute path fail-closed — the banner can never claim a cap
959
+ # the agy-run child would not have.
960
+ aw_banner_timeout_bin="$(aw_resolve_timeout_bin)"
961
+ aw_timeout_banner="$(aw_timeout_label "$aw_banner_timeout_bin" "$AGY_HARD_TIMEOUT")"
962
+ echo "review posture: model=${AGY_MODEL:-<agy settings default>} timeout=$aw_timeout_banner" >&2
886
963
 
887
964
  # --- Execute via agy-run (single home of timeout + subscription + byte ceiling) ---
888
965
  # The output is teed into the private staging dir so the mandated '### Verdict' section can be