@sabaiway/agent-workflow-kit 1.20.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,35 @@ Semantically versioned ([semver](https://semver.org)), newest first. The `versio
4
4
  is the current release. `upgrade` mode reads a project's `docs/ai/.workflow-version` and applies
5
5
  every `migrations/<version>-<slug>.md` newer than it, in semver order.
6
6
 
7
+ ## 1.22.0 — Harden the planning canon: two-set lens drift guard + the deployed lens
8
+
9
+ A **feature** release. The kit's fallback `agent_rules.md` **§2.5** lens is generalized from *Right-altitude
10
+ & code-grounded* to **Planning, review & process-fidelity invariants** (byte-identical to memory's §2.6
11
+ block) and now carries all **seven** methodology invariants. The cross-package **`test/lens-mirror.test.mjs`**
12
+ is extended to **two scoped, non-vacuous token sets**:
13
+
14
+ - **Set 1 (cross-all-four)** pins the §9-native review/fold + convergence disciplines in EVERY region —
15
+ planning §9, procedures (`## plan-authoring` onward), and both template lens blocks: `0 blockers + 0
16
+ majors`, `test-as-spec`, `no code-mechanics`, `at the diff`, `characterize-first` (alongside the
17
+ existing `fold by code` / `file:line` / `altitude`).
18
+ - **Set 2 (template-scoped)** pins the process-fidelity invariants A1/A2 (`ExitPlanMode`, recipe-fidelity
19
+ `every round`) PRESENT in both template lens blocks — closing the gap the byte-identical check alone
20
+ cannot (it only proves the two templates AGREE, so both could drop A1/A2 and stay green).
21
+
22
+ The lens heading + the `extractLensBlock` regex move in lockstep. The deployment-lineage head stays
23
+ **`1.3.0`** (no `docs/ai` structural change, no migration); the kit **package** version is a separate axis.
24
+
25
+ ## 1.21.0 — Ships the antigravity-cli-bridge 2.0.0 mirror (grounded agy-review)
26
+
27
+ A **feature** release. The kit now bundles the **antigravity-cli-bridge 2.0.0** byte-identical mirror —
28
+ a grounded `agy-review` review wrapper beside the `agy-run` probe. The two-wrapper ripple lands kit-side:
29
+ `detect-backends` readiness probes the **EXPECTED** bundled wrapper set (a stale install missing
30
+ `agy-review` now reports **DEGRADED**, not a false "ready 1/1"), `setup` / `uninstall` manage **both**
31
+ wrappers, `release-scan` allowlists `agy-review`, and the recipes ↔ engine Issue-001 caveat is loosened
32
+ (grounded review is a sound second opinion; the service-stall risk is kept). The deployment-lineage head
33
+ stays **`1.3.0`** (no `docs/ai` structural change, no migration); the kit **package** version is a
34
+ separate axis.
35
+
7
36
  ## 1.20.0 — Fallback-template lens + the cross-package lens-mirror guard (kit)
8
37
 
9
38
  A **feature** release. The kit's fallback `agent_rules.md` template gains the **§2.5 Right-altitude &
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: agent-workflow-kit
3
3
  description: Deploy or upgrade a portable AI-agent memory-and-workflow system in any project. Use when the user wants to bootstrap `docs/ai/` + an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) + cap/archive/index enforcement in a new or existing repo, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-kit` / `/agent-workflow-kit upgrade`. Triggers on phrases like "set up the memory system", "deploy the AI workflow here", "bootstrap docs/ai", "upgrade the workflow".
4
4
  disable-model-invocation: true
5
5
  metadata:
6
- version: '1.20.0'
6
+ version: '1.22.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-kit
@@ -322,7 +322,7 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/setup-backends.mjs [<backend>] [--bindir <pa
322
322
 
323
323
  For each backend it:
324
324
  1. **Places / refreshes the bundled bridge skill** (from the kit's `bridges/<name>/` mirror) into its canonical dir — but only when that dir is **absent / empty / proven-managed** (valid manifest, matching `name`+`kind`). A `stub` / `foreign` / `invalid` / `unsupported` dir, a marker fs-error, or a symlinked dir → **STOP**, never overwritten. Refresh re-runs on a proven-managed dir so re-running `setup` delivers bundled fixes.
325
- 2. **Links its wrappers** (`codex-exec` / `codex-review`; `agy-run`) onto `--bindir` via **managed symlinks** — replacing only a symlink that already points at our source. A non-symlink or a foreign symlink → **STOP**; it **preflights every target first**, so a conflict on one wrapper makes **zero** changes. If `--bindir` is not on `PATH`, it prints the one-line `export PATH=…` to add — it never edits a shell rc.
325
+ 2. **Links its wrappers** (`codex-exec` / `codex-review`; `agy-review` / `agy-run`) onto `--bindir` via **managed symlinks** — replacing only a symlink that already points at our source. A non-symlink or a foreign symlink → **STOP**; it **preflights every target first**, so a conflict on one wrapper makes **zero** changes. If `--bindir` is not on `PATH`, it prints the one-line `export PATH=…` to add — it never edits a shell rc.
326
326
  3. **Guides the manual, secret-bearing steps it will NOT automate** — the binary install (each bridge's `setup/README.md` §1) and the one-time interactive subscription login (`codex login` / `agy`) — printing the exact command for whichever axis is still missing (axis-aware: it can ask for both the CLI and the login at once).
327
327
 
328
328
  **Close-the-loop output (surface both, localized).** The tool prints, after the per-backend report:
@@ -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: '1.0.0'
5
+ version: '2.0.0'
6
6
  ---
7
7
 
8
8
  # antigravity-cli-bridge
@@ -81,13 +81,39 @@ AGY_HARD_TIMEOUT=8m agy-run "..." # hard wall-clock cap via timeout(
81
81
  agy-run "..." -- --add-dir . --dangerously-skip-permissions # passthrough agy flags
82
82
  ```
83
83
 
84
- `agy` is **headless-only** here (`-p`/`--print`) and there is **no JSON output mode** in v1.0.10 — you
84
+ `agy` is **headless-only** here (`-p`/`--print`) and there is **no JSON output mode** in v1.0.13 — you
85
85
  get plain text. If you need structure, ask for Markdown with explicit headings and validate it
86
86
  yourself. Wrapper inputs: first argument is the prompt (`text`, `-` for stdin, or `@file`);
87
87
  `AGY_MODEL` (default `Gemini 3.1 Pro (High)`); `AGY_TIMEOUT` → `--print-timeout` (default `5m`);
88
88
  `AGY_HARD_TIMEOUT` → hard `timeout(1)` wall-clock cap (default = `AGY_TIMEOUT`); extra `agy` flags
89
89
  after `--`. Full detail: [`references/models-and-flags.md`](references/models-and-flags.md).
90
90
 
91
+ ## Review mode (`agy-review`)
92
+
93
+ For a **code / plan / diff review**, drive the dedicated **`agy-review`** wrapper
94
+ ([`bin/agy-review.sh`](bin/agy-review.sh)) — the `review` role — instead of hand-rolling an `agy-run`
95
+ prompt. Because `agy` reads nothing by default and its training predates your codebase, an *ungrounded*
96
+ review **guesses** (stale-model and partial-diff false positives). `agy-review` mechanizes the
97
+ **grounded contract** (see [`references/review-prompt.md`](references/review-prompt.md)): it assembles
98
+ POSTURE + a **model/cutoff GUARD** + your **`--facts`** (the verified facts the model reviews AGAINST)
99
+ + **`--decided`** (the anti-circling list) + **`--focus`** + the artifact + a strict output shape, then
100
+ delegates execution to `agy-run` (one home for the timeout, the subscription invariant, and the byte
101
+ ceiling).
102
+
103
+ ```bash
104
+ agy-review code [--facts @facts.md] [--decided @decided.md] [--focus "…"] # the repo-complete diff
105
+ agy-review plan <plan-file> [--facts @f] … # critique a plan
106
+ agy-review diff <diff-file> [--facts @f] … # review a supplied diff
107
+ agy-review --continue --decided @round1.md --focus "still-open items" # round-2 delta, no re-assembly
108
+ ```
109
+
110
+ Frontier default `Gemini 3.1 Pro (High)`; **any** model is allowed (a sub-frontier one earns a
111
+ silenceable `AGY_PROBE=1` advisory). An oversized `code` review trips the byte ceiling with trim/split
112
+ guidance; `AGY_REVIEW_ALLOW_ADDDIR=1` offloads only the change set to a private `--add-dir` staging dir
113
+ (grounding stays inline). The service can still **stall on large/substantive prompts** (Issue-001) — keep
114
+ reviews **focused**; the inherited hard timeout is the guard. Full playbook:
115
+ [`references/driving-agy.md`](references/driving-agy.md).
116
+
91
117
  ## Project context (how `agy` sees the repo)
92
118
 
93
119
  From its **current working directory** `agy` reads one root context file by priority, plus
@@ -110,7 +136,9 @@ wins, or include them in the prompt. Probe results in a real repo confirmed `agy
110
136
  per-workspace skills by directory scan. In testing it also **named a project-specific skill without
111
137
  being pointed at any file** (auto-discovery), but that is `agy`'s own mechanism, not a guaranteed
112
138
  Claude-style description-dispatch engine; don't promise more than the probe shows in a given repo.
113
- Re-runnable from a project root (use a cheap model):
139
+ **A review, though, must never *depend* on this** — `agy` does not read your repo code or a diff
140
+ without an explicit `--add-dir`, so ground a review **self-contained** via `agy-review --facts` (above)
141
+ rather than relying on `agy` to read the change set. Re-runnable from a project root (use a cheap model):
114
142
 
115
143
  ```bash
116
144
  AGY_MODEL="Gemini 3.5 Flash (Low)" agy-run \
@@ -164,7 +192,7 @@ checklist, prompt templates, output handling). Essentials:
164
192
  - Subdirectory `CLAUDE.md` files are **not** auto-loaded by `agy` (only the cwd context file +
165
193
  `.agents/skills/`). Put cross-cutting rules in the root context file, or include local rules in the
166
194
  prompt when they matter.
167
- - **No JSON output** and **no `agy inspect`** in v1.0.10 — parse text; there is no machine-readable
195
+ - **No JSON output** and **no `agy inspect`** in v1.0.13 — parse text; there is no machine-readable
168
196
  introspection.
169
197
  - Model names must match the `agy models` display strings **exactly**.
170
198
  - **Quota is finite.** Heavy use of Pro/Claude models can exhaust the subscription; prefer Flash for
@@ -0,0 +1,446 @@
1
+ #!/usr/bin/env bash
2
+ # Grounded read-only ADVISORY review BY Google's Antigravity CLI (`agy`).
3
+ #
4
+ # Project-agnostic wrapper for the antigravity-cli-bridge skill (review role). It
5
+ # MECHANIZES the grounded-review contract so grounding is the enforced default,
6
+ # not an ad-hoc per-call effort. `agy` reads NOTHING by default (the wrapper
7
+ # passes no --add-dir), and its training predates the current codebase/model
8
+ # facts, so an ungrounded `agy` review GUESSES — stale-model false positives
9
+ # ("that model doesn't exist") and partial-diff false positives ("missing code").
10
+ # The fix is the agy analog of codex's precomputed diff: feed agy a self-contained
11
+ # prompt of VERIFIED FACTS plus the full artifact, and forbid model/cutoff opining.
12
+ #
13
+ # Assembled prompt (byte-stable order):
14
+ # 1. POSTURE read-only second-opinion reviewer; findings only, no edits/commits
15
+ # 2. GUARD do NOT opine on AI model names/versions or your knowledge cutoff
16
+ # 3. FACTS "## Grounded facts — review AGAINST these, do NOT guess the code"
17
+ # (omitted -> a one-line note in-prompt + a LOUD stderr warning)
18
+ # 4. DECIDED "## Decisions already made / already addressed — do NOT re-raise"
19
+ # (optional; the anti-circling lever — the round-2 payload)
20
+ # 5. FOCUS the merged --focus / trailing focus text (optional)
21
+ # 6. ARTIFACT the working-tree change set (code) | a supplied plan/diff file
22
+ # 7. SHAPE strict Markdown output shape (Verdict / Blocking / Non-blocking / Questions)
23
+ #
24
+ # Execution is DELEGATED to `agy-run` (bin/agy.sh) so the hard-timeout cap, the
25
+ # subscription invariant, and the single-argv byte ceiling live in exactly one
26
+ # place. The orchestrator supplies only what a script can't generate: the verified
27
+ # facts (--facts), the already-decided list (--decided), and the focus (--focus).
28
+ #
29
+ # Usage (installed on PATH as `agy-review`):
30
+ # agy-review code [--facts @f] [--decided @f] [--focus "…"] [extra focus…]
31
+ # agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus "…"]
32
+ # agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus "…"]
33
+ # agy-review --continue [--decided @f] [--focus "…"] # round-2 delta (no mode, no re-assembly)
34
+ # agy-review --conversation <id> [--decided @f] [--focus "…"] # resume a specific conversation
35
+ #
36
+ # Environment (every optional var has an explicit default so a no-env run is safe under set -u):
37
+ # AGY_MODEL default "Gemini 3.1 Pro (High)"; ANY model allowed (advisory warn off-frontier).
38
+ # Set empty (AGY_MODEL=) to drop --model and use agy's settings.json.
39
+ # AGY_HARD_TIMEOUT default 30m (duration string; the timeout(1) hard cap via agy-run)
40
+ # AGY_TIMEOUT default = AGY_HARD_TIMEOUT (agy's soft --print-timeout)
41
+ # AGY_MAX_PROMPT_BYTES default 120000 (single-argv byte ceiling; see agy.sh)
42
+ # AGY_PROBE=1 throwaway probe — silences the off-frontier model advisory
43
+ # AGY_REVIEW_ALLOW_ADDDIR=1 oversized CODE review: offload the change set to a private
44
+ # staging dir and pass it via --add-dir (re-enables Issue-001 stall risk)
45
+ set -euo pipefail
46
+
47
+ DEFAULT_AGY_REVIEW_MODEL="Gemini 3.1 Pro (High)"
48
+ # `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
49
+ AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
50
+ # Frontier review models. ANY model is allowed; a sub-frontier one only earns a soft, silenceable warning.
51
+ FRONTIER_SET=("Gemini 3.1 Pro (High)" "Claude Opus 4.6 (Thinking)" "Claude Sonnet 4.6 (Thinking)")
52
+
53
+ # Duration-string timeouts (NOT codex's bare seconds): agy-run forwards a duration to --print-timeout,
54
+ # and the timeout(1) hard cap is a duration too — never numerically compared, so 30m vs 2h is fine.
55
+ AGY_HARD_TIMEOUT="${AGY_HARD_TIMEOUT:-30m}"
56
+ AGY_TIMEOUT="${AGY_TIMEOUT:-$AGY_HARD_TIMEOUT}"
57
+ AGY_PROBE="${AGY_PROBE:-0}"
58
+ AGY_REVIEW_ALLOW_ADDDIR="${AGY_REVIEW_ALLOW_ADDDIR:-0}"
59
+ AGY_MAX_PROMPT_BYTES="${AGY_MAX_PROMPT_BYTES:-120000}"
60
+ if [[ ! "$AGY_MAX_PROMPT_BYTES" =~ ^[0-9]+$ ]]; then
61
+ echo "error: AGY_MAX_PROMPT_BYTES='$AGY_MAX_PROMPT_BYTES' is not a non-negative integer." >&2
62
+ exit 2
63
+ fi
64
+ # The override may only TIGHTEN the ceiling — raising it past the OS single-argv limit (~131072) would
65
+ # defeat the guard (the prompt fails at exec with E2BIG). Reject above a safe hard maximum (matches agy.sh).
66
+ AGY_ARGV_HARD_MAX=131000
67
+ if (( AGY_MAX_PROMPT_BYTES > AGY_ARGV_HARD_MAX )); then
68
+ echo "error: AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES} exceeds the OS single-argv ceiling (~${AGY_ARGV_HARD_MAX})." >&2
69
+ echo " The override may LOWER the ceiling (stricter), never raise it past the OS limit." >&2
70
+ exit 2
71
+ fi
72
+
73
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
74
+
75
+ # --- Subscription invariant (reuse agy.sh's security pattern verbatim) --------
76
+ export PATH="$HOME/.local/bin:$PATH"
77
+ unset ANTIGRAVITY_API_KEY GEMINI_API_KEY GOOGLE_API_KEY GOOGLE_GENAI_API_KEY 2>/dev/null || true
78
+ while IFS= read -r _api_key_var; do
79
+ unset "$_api_key_var" 2>/dev/null || true
80
+ done < <(compgen -v 2>/dev/null | grep '_API_KEY$' || true)
81
+
82
+ if ! command -v agy >/dev/null 2>&1; then
83
+ echo "error: 'agy' (Antigravity CLI) not found on PATH. See this skill's setup/README.md." >&2
84
+ exit 127
85
+ fi
86
+ # Delegate execution to agy-run (the single home of the timeout + subscription + byte-ceiling guards);
87
+ # fall back to the sibling agy.sh on a fresh checkout / hermetic test where agy-run is not yet linked.
88
+ if command -v agy-run >/dev/null 2>&1; then
89
+ AGY_RUN="agy-run"
90
+ else
91
+ AGY_RUN="$HERE/agy.sh"
92
+ fi
93
+
94
+ # --- Model policy (advisory, NOT a gate) -------------------------------------
95
+ is_frontier=0
96
+ for _m in "${FRONTIER_SET[@]}"; do
97
+ [[ "$AGY_MODEL" == "$_m" ]] && { is_frontier=1; break; }
98
+ done
99
+ if [[ "$is_frontier" != "1" && "$AGY_PROBE" != "1" ]]; then
100
+ echo "warning: reviewing with a non-frontier model '${AGY_MODEL:-<settings.json default>}' — results may be" >&2
101
+ echo " weaker (quality-first). Set AGY_PROBE=1 to silence, or AGY_MODEL to a frontier model." >&2
102
+ fi
103
+
104
+ # --- Output shape + grounding helpers (the wrapper is the source of truth) -----
105
+ emit_posture() {
106
+ cat <<'POSTURE'
107
+ You are a meticulous staff-level engineer giving a read-only SECOND OPINION on a change.
108
+ You are READ-ONLY: do NOT propose to edit files, run commands, or make any git change — return
109
+ findings ONLY. Your output is advisory; the orchestrator verifies every finding and owns each change.
110
+ POSTURE
111
+ }
112
+ emit_guard() {
113
+ cat <<'GUARD'
114
+ GUARD: Do NOT comment on AI model names/versions or your own knowledge cutoff — that is irrelevant
115
+ here and a known source of false positives. Review ONLY the engineering of the material below,
116
+ AGAINST the grounded facts. If something contradicts your training, trust the facts, not your memory.
117
+ GUARD
118
+ }
119
+ emit_shape() {
120
+ cat <<'SHAPE'
121
+ ## Output — Markdown, this exact shape, nothing else
122
+ ### Verdict
123
+ One line: SHIP / SHIP WITH NITS / REWORK, plus a one-sentence reason.
124
+ ### Blocking
125
+ Numbered. Correctness bugs, contract violations, data loss, security. Cite file:line. Empty? write "none".
126
+ ### Non-blocking
127
+ Numbered. Simplifications, reuse, naming, missing tests. Cite file:line. Empty? write "none".
128
+ ### Questions
129
+ Anything ambiguous that would change your verdict if answered.
130
+ SHAPE
131
+ }
132
+ emit_resume_reminder() {
133
+ cat <<'REMINDER'
134
+ CONTINUE the review you already started in THIS conversation, under the SAME read-only posture: do
135
+ NOT propose edits, run commands, or make any git change — findings ONLY. The material you reviewed
136
+ is already in this conversation; do NOT ask for it again and do NOT re-review what you already
137
+ passed. This is a round-2 DELTA.
138
+ GUARD: Do NOT comment on AI model names/versions or your own knowledge cutoff — review only the engineering.
139
+ REMINDER
140
+ }
141
+
142
+ # True (exit 0) when $1 looks BINARY: a NUL byte in the first 8 KiB (git's own heuristic).
143
+ is_binary() {
144
+ local nul
145
+ nul="$(LC_ALL=C head -c 8192 -- "$1" 2>/dev/null | LC_ALL=C tr -dc '\000' | wc -c)"
146
+ [[ "${nul:-0}" -gt 0 ]]
147
+ }
148
+
149
+ # Emit the full code-review surface: repo map, status, staged + unstaged diffs, and the CONTENTS of
150
+ # every untracked REGULAR file (NUL-safe iteration). Symlinks are shown as their target (never
151
+ # followed — no out-of-repo leak); other non-regular paths (FIFO/socket/device/dir) are skipped.
152
+ # Ported verbatim from codex-cli-bridge/bin/codex-review.sh.
153
+ assemble_code_diff() {
154
+ echo "=== repo file map (git ls-files) ==="
155
+ git ls-files
156
+ echo
157
+ echo "=== git status (porcelain) ==="
158
+ git status --porcelain=v1
159
+ echo
160
+ echo "=== staged diff (git diff --cached) ==="
161
+ git diff --cached --no-ext-diff
162
+ echo
163
+ echo "=== unstaged diff (git diff) ==="
164
+ git diff --no-ext-diff
165
+ echo
166
+ echo "=== untracked file contents ==="
167
+ local path
168
+ while IFS= read -r -d '' path; do
169
+ if [[ -L "$path" ]]; then
170
+ printf '=== untracked (symlink): %s -> %s ===\n' "$path" "$(readlink -- "$path" 2>/dev/null || echo '?')"
171
+ elif [[ ! -f "$path" ]]; then
172
+ printf '=== untracked (non-regular, skipped): %s ===\n' "$path"
173
+ elif is_binary "$path"; then
174
+ printf '=== untracked (binary, skipped): %s ===\n' "$path"
175
+ else
176
+ printf '=== untracked: %s ===\n' "$path"
177
+ cat -- "$path"
178
+ printf '\n'
179
+ fi
180
+ done < <(git ls-files --others --exclude-standard -z)
181
+ }
182
+
183
+ # --- Resume detection (must be the FIRST argument) ---------------------------
184
+ # A continuation takes NO <mode> and assembles NO artifact (agy keeps it server-side); it accepts only
185
+ # --decided / --focus and sends a small delta. `code`/`plan`/`diff` trigger assembly, which must never
186
+ # happen on a continuation.
187
+ resume_mode=""
188
+ resume_id=""
189
+ case "${1:-}" in
190
+ --continue)
191
+ resume_mode="continue"; shift ;;
192
+ --conversation)
193
+ resume_mode="conversation"; shift
194
+ resume_id="${1:-}"; shift || true
195
+ if [[ -z "$resume_id" || "${resume_id:0:2}" == "--" ]]; then
196
+ echo "error: --conversation needs a <conversation-id> argument before the flags." >&2
197
+ exit 2
198
+ fi ;;
199
+ esac
200
+
201
+ usage() {
202
+ echo "usage: $0 code [--facts @f] [--decided @f] [--focus \"…\"] [extra focus…]" >&2
203
+ echo " $0 plan <plan-file> [--facts @f] [--decided @f] [--focus \"…\"]" >&2
204
+ echo " $0 diff <diff-file> [--facts @f] [--decided @f] [--focus \"…\"]" >&2
205
+ echo " $0 --continue [--decided @f] [--focus \"…\"]" >&2
206
+ echo " $0 --conversation <id> [--decided @f] [--focus \"…\"]" >&2
207
+ }
208
+
209
+ # --- Mode dispatch (non-resume) ----------------------------------------------
210
+ mode=""
211
+ target=""
212
+ PLAN_CONTENT=""
213
+ DIFF_CONTENT=""
214
+ if [[ -z "$resume_mode" ]]; then
215
+ mode="${1:-}"; shift || true
216
+ case "$mode" in
217
+ code) ;;
218
+ plan|diff)
219
+ target="${1:-}"; shift || true
220
+ if [[ -z "$target" ]]; then
221
+ echo "error: $mode mode needs a <file> argument." >&2; usage; exit 2
222
+ fi
223
+ if [[ ! -f "$target" ]]; then
224
+ echo "error: $mode file '$target' not found." >&2; exit 2
225
+ fi
226
+ # Read the target NOW (before any cd) — its path is relative to the invocation cwd.
227
+ if [[ "$mode" == "plan" ]]; then PLAN_CONTENT="$(cat -- "$target")"; else DIFF_CONTENT="$(cat -- "$target")"; fi ;;
228
+ *)
229
+ usage; exit 2 ;;
230
+ esac
231
+ fi
232
+
233
+ # --- Flag parse (--facts / --decided / --focus + trailing focus) -------------
234
+ FACTS_RAW=""
235
+ DECIDED_RAW=""
236
+ FOCUS_PARTS=()
237
+ # A value-taking flag must be followed by a real value — never end-of-args and never another flag.
238
+ # Otherwise `agy-review code --facts --focus x` would silently take "--focus" as the facts and spend a
239
+ # review on bogus grounding. ($2 is referenced only as ${2:-} so an unset value is safe under set -u.)
240
+ need_value() { # $1 = flag name, $2 = candidate value
241
+ if [[ -z "${2:-}" || "${2:0:2}" == "--" ]]; then
242
+ echo "error: $1 needs a value; got '${2:-<end of args>}' (empty or a misplaced flag)." >&2
243
+ exit 2
244
+ fi
245
+ }
246
+ while [[ $# -gt 0 ]]; do
247
+ case "$1" in
248
+ --facts)
249
+ if [[ -n "$resume_mode" ]]; then
250
+ echo "error: --facts is not valid on a continuation (the facts are already in the conversation)." >&2
251
+ exit 2
252
+ fi
253
+ need_value "$1" "${2:-}"; FACTS_RAW="$2"; shift 2 ;;
254
+ --decided)
255
+ need_value "$1" "${2:-}"; DECIDED_RAW="$2"; shift 2 ;;
256
+ --focus)
257
+ need_value "$1" "${2:-}"; FOCUS_PARTS+=("$2"); shift 2 ;;
258
+ --)
259
+ echo "error: this wrapper OWNS the review posture — no '--' passthrough. The only escapes are" >&2
260
+ echo " AGY_PROBE=1 (off-frontier model) and AGY_REVIEW_ALLOW_ADDDIR=1 (oversized code review)." >&2
261
+ exit 2 ;;
262
+ --*)
263
+ echo "error: unknown flag '$1'." >&2; usage; exit 2 ;;
264
+ *)
265
+ if [[ -n "$resume_mode" ]]; then
266
+ echo "error: a continuation takes no positional args (only --decided / --focus): '$1'." >&2
267
+ exit 2
268
+ fi
269
+ if [[ "$mode" != "code" ]]; then
270
+ echo "error: $mode mode takes no extra positional args — use --focus \"…\": '$1'." >&2
271
+ exit 2
272
+ fi
273
+ FOCUS_PARTS+=("$1"); shift ;;
274
+ esac
275
+ done
276
+ # Merge --focus values and trailing focus words, in parse order, into ONE focus block.
277
+ FOCUS="${FOCUS_PARTS[*]:-}"
278
+
279
+ # Resolve @file / literal for --facts and --decided NOW (cwd = invocation, before any code-mode cd).
280
+ # The `@file` existence check runs at TOP LEVEL (not inside a command substitution) so its exit-2 exits
281
+ # the whole script, not just a subshell.
282
+ FACTS_CONTENT=""
283
+ if [[ -n "$FACTS_RAW" ]]; then
284
+ if [[ "${FACTS_RAW:0:1}" == "@" ]]; then
285
+ _ff="${FACTS_RAW:1}"
286
+ [[ -f "$_ff" ]] || { echo "error: --facts file '$_ff' not found." >&2; exit 2; }
287
+ FACTS_CONTENT="$(cat -- "$_ff")"
288
+ else
289
+ FACTS_CONTENT="$FACTS_RAW"
290
+ fi
291
+ fi
292
+ DECIDED_CONTENT=""
293
+ if [[ -n "$DECIDED_RAW" ]]; then
294
+ if [[ "${DECIDED_RAW:0:1}" == "@" ]]; then
295
+ _df="${DECIDED_RAW:1}"
296
+ [[ -f "$_df" ]] || { echo "error: --decided file '$_df' not found." >&2; exit 2; }
297
+ DECIDED_CONTENT="$(cat -- "$_df")"
298
+ else
299
+ DECIDED_CONTENT="$DECIDED_RAW"
300
+ fi
301
+ fi
302
+
303
+ if [[ -z "$FACTS_CONTENT" && -z "$resume_mode" ]]; then
304
+ echo "warning: no --facts supplied. agy reads NOTHING by default, so an ungrounded review GUESSES" >&2
305
+ echo " (stale-model and partial-diff false positives). Pass --facts @file with the verified" >&2
306
+ echo " facts the model must review AGAINST. Proceeding without grounding." >&2
307
+ fi
308
+
309
+ # --- Private staging dir (mode 0700, trap-cleaned) ---------------------------
310
+ # Mode-agnostic: works in all modes incl. plan/diff outside a git repo, and never exposes .git to agy.
311
+ staging=""
312
+ trap 'rm -rf "$staging" 2>/dev/null; true' EXIT
313
+ staging="$(mktemp -d)"
314
+ chmod 700 "$staging"
315
+ prompt_file="$staging/prompt"
316
+
317
+ # --- Assemble the prompt + size guard ----------------------------------------
318
+ run_passthrough=()
319
+ if [[ -n "$resume_mode" ]]; then
320
+ # Round-2 DELTA: posture/guard reminder + new focus + restated SHAPE (so formatting holds) + decided.
321
+ # NEVER re-send the artifact — agy keeps it in the server-side conversation.
322
+ ( umask 077; {
323
+ emit_resume_reminder
324
+ echo
325
+ if [[ -n "$FOCUS" ]]; then echo "## New focus for this round"; printf '%s\n\n' "$FOCUS"; fi
326
+ emit_shape
327
+ echo
328
+ if [[ -n "$DECIDED_CONTENT" ]]; then
329
+ echo "## Decisions already made / already addressed — do NOT re-raise these"
330
+ printf '%s\n' "$DECIDED_CONTENT"
331
+ fi
332
+ } > "$prompt_file" )
333
+ delta_bytes=$(( $(wc -c < "$prompt_file") ))
334
+ if (( delta_bytes > AGY_MAX_PROMPT_BYTES )); then
335
+ echo "error: the round-2 delta is ${delta_bytes} bytes, over AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES}." >&2
336
+ echo " A continuation must stay small (agy holds the artifact server-side). Trim --decided / --focus." >&2
337
+ exit 2
338
+ fi
339
+ if [[ "$resume_mode" == "continue" ]]; then
340
+ run_passthrough=(--continue)
341
+ else
342
+ run_passthrough=(--conversation "$resume_id")
343
+ fi
344
+ else
345
+ # code mode: assemble against the FULL repo. cd to the work-tree root FIRST so git status/diff/ls-files
346
+ # are repo-complete (a subdir invocation would otherwise silently miss root/sibling changes). Every
347
+ # file-path argument was already resolved/read above, before this cd.
348
+ if [[ "$mode" == "code" ]]; then
349
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
350
+ echo "error: 'agy-review code' must run inside a git working tree (the diff is the review surface)." >&2
351
+ exit 2
352
+ fi
353
+ cd "$(git rev-parse --show-toplevel)"
354
+ # No-diff preflight — never spend a run on a clean tree.
355
+ if git diff --quiet && git diff --cached --quiet \
356
+ && [[ -z "$(git ls-files --others --exclude-standard)" ]]; then
357
+ echo "agy-review: no uncommitted changes to review — the working tree is clean." >&2
358
+ exit 0
359
+ fi
360
+ fi
361
+
362
+ emit_artifact() {
363
+ case "$mode" in
364
+ code) echo "## The change set under review (assembled working-tree diff — repo-complete)"; assemble_code_diff ;;
365
+ plan) echo "## The implementation plan under review"; printf '%s\n' "$PLAN_CONTENT" ;;
366
+ diff) echo "## The diff under review"; printf '%s\n' "$DIFF_CONTENT" ;;
367
+ esac
368
+ }
369
+ emit_grounding() { # POSTURE + GUARD + FACTS + DECIDED + FOCUS
370
+ emit_posture; echo
371
+ emit_guard; echo
372
+ if [[ -n "$FACTS_CONTENT" ]]; then
373
+ echo "## Grounded facts — review AGAINST these, do NOT guess the code"
374
+ printf '%s\n' "$FACTS_CONTENT"
375
+ else
376
+ echo "## Grounded facts"
377
+ echo "(none supplied — review the material as given; do NOT invent facts about the codebase or its model/version context.)"
378
+ fi
379
+ echo
380
+ if [[ -n "$DECIDED_CONTENT" ]]; then
381
+ echo "## Decisions already made / already addressed — do NOT re-raise these"
382
+ printf '%s\n\n' "$DECIDED_CONTENT"
383
+ fi
384
+ if [[ -n "$FOCUS" ]]; then
385
+ echo "## Focus"
386
+ printf '%s\n\n' "$FOCUS"
387
+ fi
388
+ }
389
+
390
+ # Full prompt = grounding + artifact (inline) + shape.
391
+ ( umask 077; { emit_grounding; emit_artifact; echo; emit_shape; } > "$prompt_file" )
392
+ prompt_bytes=$(( $(wc -c < "$prompt_file") ))
393
+
394
+ if (( prompt_bytes > AGY_MAX_PROMPT_BYTES )); then
395
+ if [[ "$AGY_REVIEW_ALLOW_ADDDIR" == "1" ]]; then
396
+ # Offload ONLY the artifact to a private 0600 file; the -p prompt still carries the full grounding
397
+ # inline and points agy at the file. --add-dir targets the private staging dir, never .git/work-tree.
398
+ artifact_file="$staging/precomputed-change-set"
399
+ ( umask 077; emit_artifact > "$artifact_file" )
400
+ ( umask 077; {
401
+ emit_grounding
402
+ echo "## The change set under review"
403
+ echo "The full change set is too large to inline. It is in the precomputed file at:"
404
+ echo " $artifact_file"
405
+ echo "Read it IN FULL, then review it against the grounded facts above."
406
+ echo
407
+ emit_shape
408
+ } > "$prompt_file" )
409
+ small_bytes=$(( $(wc -c < "$prompt_file") ))
410
+ if (( small_bytes > AGY_MAX_PROMPT_BYTES )); then
411
+ echo "error: even the grounding-only prompt is ${small_bytes} bytes (> AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES})." >&2
412
+ echo " Trim --facts / --decided / --focus." >&2
413
+ exit 2
414
+ fi
415
+ echo "warning: the assembled prompt was ${prompt_bytes} bytes (> AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES})." >&2
416
+ echo " AGY_REVIEW_ALLOW_ADDDIR=1 — offloading the change set to a private staging dir and" >&2
417
+ echo " passing it via --add-dir. This RE-ENABLES the Issue-001 stall risk (heavy agentic" >&2
418
+ echo " roaming); the inherited hard timeout (AGY_HARD_TIMEOUT=$AGY_HARD_TIMEOUT) bounds it." >&2
419
+ run_passthrough=(--add-dir "$staging")
420
+ else
421
+ echo "error: the assembled prompt is ${prompt_bytes} bytes, over AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES}." >&2
422
+ echo " agy takes the prompt as a single argv; past ~131072 bytes it fails with a cryptic" >&2
423
+ echo " 'Argument list too long'. Trim to the relevant hunks, or split into focused per-area" >&2
424
+ if [[ "$mode" == "code" ]]; then
425
+ echo " reviews. For a large CODE review, AGY_REVIEW_ALLOW_ADDDIR=1 offloads the change set via" >&2
426
+ echo " a private --add-dir staging dir (re-enables the Issue-001 stall risk)." >&2
427
+ else
428
+ echo " reviews (split the $mode into focused parts)." >&2
429
+ fi
430
+ exit 2
431
+ fi
432
+ fi
433
+ fi
434
+
435
+ # --- Execute via agy-run (single home of timeout + subscription + byte ceiling) ---
436
+ set +e
437
+ if (( ${#run_passthrough[@]} > 0 )); then
438
+ AGY_MODEL="$AGY_MODEL" AGY_TIMEOUT="$AGY_TIMEOUT" AGY_HARD_TIMEOUT="$AGY_HARD_TIMEOUT" \
439
+ "$AGY_RUN" "@$prompt_file" -- "${run_passthrough[@]}"
440
+ else
441
+ AGY_MODEL="$AGY_MODEL" AGY_TIMEOUT="$AGY_TIMEOUT" AGY_HARD_TIMEOUT="$AGY_HARD_TIMEOUT" \
442
+ "$AGY_RUN" "@$prompt_file"
443
+ fi
444
+ rc=$?
445
+ set -e
446
+ exit $rc