@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 +29 -0
- package/SKILL.md +2 -2
- package/bridges/antigravity-cli-bridge/SKILL.md +32 -4
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +446 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +441 -0
- package/bridges/antigravity-cli-bridge/bin/agy.sh +33 -0
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +96 -1
- package/bridges/antigravity-cli-bridge/capability.json +2 -2
- package/bridges/antigravity-cli-bridge/references/driving-agy.md +43 -19
- package/bridges/antigravity-cli-bridge/references/models-and-flags.md +36 -5
- package/bridges/antigravity-cli-bridge/references/review-prompt.md +40 -32
- package/bridges/antigravity-cli-bridge/setup/README.md +17 -12
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/templates/agent_rules.md +9 -2
- package/tools/detect-backends.mjs +9 -28
- package/tools/family-members.mjs +2 -1
- package/tools/recipes.mjs +3 -2
- package/tools/release-scan.mjs +1 -0
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.
|
|
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: '
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|