@sabaiway/agent-workflow-kit 1.29.0 → 1.30.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 +57 -0
- package/README.md +2 -0
- package/SKILL.md +37 -3
- package/bridges/antigravity-cli-bridge/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +145 -4
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +169 -1
- package/bridges/antigravity-cli-bridge/capability.json +3 -2
- package/bridges/codex-cli-bridge/SKILL.md +1 -1
- package/bridges/codex-cli-bridge/bin/codex-review.sh +120 -3
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +132 -0
- package/bridges/codex-cli-bridge/capability.json +3 -2
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/templates/agent_rules.md +3 -2
- package/references/templates/handover.md +1 -0
- package/tools/commands.mjs +15 -1
- package/tools/detect-backends.mjs +2 -0
- package/tools/grounding.mjs +263 -0
- package/tools/procedures.mjs +50 -5
- package/tools/recipes.mjs +78 -12
- package/tools/review-state.mjs +395 -0
- package/tools/set-recipe.mjs +15 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,63 @@ 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.30.0 — Review-recipe enforcement: the configured recipe is impossible to miss, "reviewed ≠ shipped" is detectable, grounding is a command (AD-038)
|
|
8
|
+
|
|
9
|
+
A **feature** release (ships the bundled bridges refreshed to **2.2.0**). Origin: a real
|
|
10
|
+
council-substitution incident + independent kit-user feedback — the configured review recipe could
|
|
11
|
+
be silently skipped, downgraded, or run before later edits, and nothing could detect it. Three
|
|
12
|
+
mechanisms, each self-firing at its point of use:
|
|
13
|
+
|
|
14
|
+
- **The discovery line** — `tools/recipes.mjs --active-line` (`Mode: recipes`): exactly ONE
|
|
15
|
+
machine-composed line rendering the **CONFIGURED** recipe of every activity/slot from
|
|
16
|
+
`docs/ai/orchestration.json` + live readiness — source labeled, degradation stated, wrapper set
|
|
17
|
+
named, explicitly contrasted with the readiness recommendation (which is informational). Wired
|
|
18
|
+
where a session already reads: the deployed `agent_rules.md` §1.1 gains step 2 (read the
|
|
19
|
+
orchestration config BEFORE picking a task; a silent recipe downgrade is a forbidden
|
|
20
|
+
substitution) and `handover.md` gains a standing **"Active recipes:"** slot — both template
|
|
21
|
+
regions byte-identical with the memory copies (new `test/template-region-parity.test.mjs`,
|
|
22
|
+
injected-divergence non-vacuous); `set-recipe` now ECHOES the freshly composed line + a
|
|
23
|
+
handover-slot reminder after every successful `--write` (additive `activeLine` field in
|
|
24
|
+
`--json`). New `test/active-recipe-line.test.mjs` proves the line derives from the CONFIG, not
|
|
25
|
+
the recommendation.
|
|
26
|
+
- **`/agent-workflow-kit review-state`** (`tools/review-state.mjs`, read-only + colocated tests) —
|
|
27
|
+
makes "reviewed ≠ shipped" mechanically detectable. The 2.2.0 review wrappers append one JSONL
|
|
28
|
+
receipt per SUCCESSFUL review to `<git dir>/agent-workflow-review-receipts.jsonl`
|
|
29
|
+
(`AW_REVIEW_RECEIPTS` overrides; never committable by construction); the checker resolves the
|
|
30
|
+
effective `plan-execution.review` recipe, recomputes the canonical **uncommitted-state
|
|
31
|
+
fingerprint** (sha256 over staged diff + unstaged diff + untracked-not-ignored contents — exactly
|
|
32
|
+
the review-payload domain), and `--check` exits 0 only when every recipe-named backend holds a
|
|
33
|
+
**fresh, grounded, current-fingerprint** receipt (**presence, not unanimity** — verdicts stay
|
|
34
|
+
orchestrator judgment). Any later edit stales the receipts; plan/diff receipts and continuations
|
|
35
|
+
(`fresh:false`) are informational-only — after a fold, only a fresh grounded re-run restores
|
|
36
|
+
green. Normative exit contract in the tool header; plan-in-flight detector keyed on the
|
|
37
|
+
documented `docs/plans` naming convention; honest residual stated (`--no-verify`, receipt-file
|
|
38
|
+
deletion — discipline, not a sandbox). The gate line is **never auto-seeded** ([[AD-021]]): the
|
|
39
|
+
template `gates.json` stays empty; the candidate line lives in `Mode: review-state`/`Mode: gates`
|
|
40
|
+
prose. New `test/review-fingerprint-parity.test.mjs` proves bash (both wrappers, byte-identical
|
|
41
|
+
block) ↔ node fingerprint parity — hash, serialization, AND behavioral domain equality.
|
|
42
|
+
- **`/agent-workflow-kit grounding`** (`tools/grounding.mjs` + colocated tests) — the
|
|
43
|
+
grounded-review facts assembler, catalogued honestly as a **WRITER**: `--constraints` slices the
|
|
44
|
+
root `AGENTS.md` Hard-Constraints section verbatim (exactly-one-match, else a loud STOP);
|
|
45
|
+
`--plan <path>` extracts the decision-bearing sections (`## Approach` + `## Verification`
|
|
46
|
+
required, `## Decisions (locked)` when present; duplicates STOP); output honors the wrapper's
|
|
47
|
+
`AGY_MAX_PROMPT_BYTES` budget minus `--reserve-bytes` with a loud tail-trim; `--out` accepts only
|
|
48
|
+
gitignored / out-of-repo scratch (a tracked or in-repo not-ignored path is refused — a new
|
|
49
|
+
untracked file would move the fingerprint it grounds). `procedures.mjs` renders the invocation as
|
|
50
|
+
a POPULATED pre-step whenever agy is dispatched (exactly one plan in flight → its path; else a
|
|
51
|
+
placeholder + discovery caveat; additive `groundingPreStep` in `--json`).
|
|
52
|
+
- **Bundled bridges 2.2.0** (mirrors byte-refreshed): `codex-review.sh` mandates + parses ONE
|
|
53
|
+
literal `Verdict: ship|revise|rethink` line (schema mode reads the JSON field); `agy-review.sh`
|
|
54
|
+
records the `### Verdict` token verbatim (SHIP / SHIP WITH NITS / REWORK), `grounded` +
|
|
55
|
+
`factsHash` from `--facts` (an empty payload is visible), and marks continuations `fresh:false`
|
|
56
|
+
with a one-line fresh-run notice; a receipt write failure warns and never fails the review. The
|
|
57
|
+
review-role `capability.json` contracts gain the `receipt` block (the fingerprint definition
|
|
58
|
+
home) — three-way lockstep wrapper `--help` ↔ manifest ↔ `detect-backends.mjs` registry,
|
|
59
|
+
drift-guarded.
|
|
60
|
+
- **Catalog/report wiring:** two new SKILL modes + README rows; the bootstrap/upgrade report
|
|
61
|
+
footers paste the active-recipe line beside the backend-status line; `package-content` pin
|
|
62
|
+
94 → 96 (the two new tools).
|
|
63
|
+
|
|
7
64
|
## 1.29.0 — Velocity scope C: an opt-in PreToolUse gate-approval hook
|
|
8
65
|
|
|
9
66
|
A **feature** release (ships the bundled bridges unchanged at **2.1.0**). The shipped, probe-proven
|
package/README.md
CHANGED
|
@@ -234,6 +234,8 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
|
|
|
234
234
|
| `/agent-workflow-kit recipes` | any time | **read-only** orchestration advisor: presents four named recipes for composing the bridges into plan → execute → review — **Solo / Reviewed / Council / Delegated** — plans + recommends one for your environment (degrading with a stated reason when a backend isn't ready), and offers the choice. The orchestrator runs it via the bridge skills and **always commits**; the kit never executes a recipe, never runs a subscription CLI, never commits. |
|
|
235
235
|
| `/agent-workflow-kit procedures <activity>` | any time | **read-only** activity-procedures advisor: prints a named activity's ordered steps (`plan-authoring` / `plan-execution`) read **live** from the engine, plus the **resolved recipe per slot** from your `docs/ai/orchestration.json` (agent-writable via `set-recipe`, or hand-edited) + backend readiness (default Reviewed when a backend is ready, Council on request, slot-aware incl. Delegated) — and, for every dispatched backend, the **full driving contract at the point of use** (exact copy-pasteable invocation, grounding levers like agy's `--facts`/`--decided`, the round-2 `--continue` delta, guarded passthrough), verbatim from the bridge manifests (drift-guarded; each wrapper's `--help` prints the same). `--override <slot>=<recipe>` adjusts one slot per run. Composes with `recipes`; never writes, never commits, never runs a subscription CLI. |
|
|
236
236
|
| `/agent-workflow-kit set-recipe` | any time | **config writer** for `docs/ai/orchestration.json`: tell the agent your preference in plain language and it maps it to explicit `--set <activity>.<slot>=<recipe>` / `--unset` ops; the kit validates, **previews by default**, and writes only on `--write` (deployment-gated, atomic, symlink/TOCTOU-safe), resolving the effective recipe vs live readiness. Writes **only** that file — **never runs a backend, never commits**; hand-editing stays fully supported. |
|
|
237
|
+
| `/agent-workflow-kit review-state` | any time | **read-only review-receipt checker** — makes "reviewed ≠ shipped" detectable: the bridge review wrappers append a receipt per successful review (into a file inside the git dir — never committable); this checks that every backend your configured `plan-execution.review` recipe names holds a **fresh, grounded receipt for the current uncommitted tree** (any later edit moves the fingerprint and stales the receipt; a review continuation never re-attests a folded tree). `--check` gives a gate exit code to declare in `docs/ai/gates.json` **by hand** (never auto-seeded). Never writes, never commits, never runs a subscription CLI; it spawns read-only `git` queries to fingerprint the tree — and `git commit --no-verify` stays possible (discipline, not a sandbox). |
|
|
238
|
+
| `/agent-workflow-kit grounding` | any time | **grounded-review facts assembler** — mechanizes populating `agy-review --facts @f`: slices your entry-point's **Hard Constraints** section verbatim (exactly one match, else a loud stop) and/or a plan's decision-bearing sections (`## Approach` + `## Verification` required, `## Decisions (locked)` when present; duplicates stop), under the same byte budget the agy wrapper enforces (minus `--reserve-bytes` for the artifact share), with a loud tail-trim on overflow. Prints to stdout; `--out` writes **one scratch file only** (gitignored / outside the repo — a tracked or not-ignored in-repo path is refused). Never commits, never runs a subscription CLI. |
|
|
237
239
|
| `/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. |
|
|
238
240
|
| `/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. |
|
|
239
241
|
| `/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: '1.
|
|
6
|
+
version: '1.30.0'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-kit
|
|
@@ -177,8 +177,11 @@ backend-status line** → **welcome mat**. Only the version block comes from
|
|
|
177
177
|
`node ${CLAUDE_SKILL_DIR}/tools/family-registry.mjs --json` (add `--dir <project>` for the deploy
|
|
178
178
|
axis) — **never hardcoded semver**; the backend-status line is its own shared contract (above), and
|
|
179
179
|
the welcome mat **composes signals already gathered** (the version block's notes + the backend-status
|
|
180
|
-
line), not a fresh helper call.
|
|
181
|
-
the
|
|
180
|
+
line), not a fresh helper call. **Beside the backend-status line**, when the target project carries a
|
|
181
|
+
`docs/ai/orchestration.json`, also paste the one-line **configured-recipe line** verbatim from
|
|
182
|
+
`node ${CLAUDE_SKILL_DIR}/tools/recipes.mjs --active-line` (run from the project root; *Mode: recipes*
|
|
183
|
+
documents it; same agent-host skip-with-reason contract as the status line). Present everything in the
|
|
184
|
+
user's conversational language; never paste the JSON or any internal field name.
|
|
182
185
|
|
|
183
186
|
**Success state — the happy path never leads with a structure number.** No happy-path report surfaces
|
|
184
187
|
the project's internal `docs/ai` structure version, the stamp filename, or the internal versioning
|
|
@@ -275,6 +278,8 @@ The declaration is **seeded at bootstrap** (the template loop, step 6) and **ens
|
|
|
275
278
|
|
|
276
279
|
Declared gates can also be **auto-approved** (no permission prompt on a byte-exact invocation from the project root) via the opt-in PreToolUse hook — *Mode: hook*: the SAME declaration, a second consumer; editing gates.json needs no re-wiring.
|
|
277
280
|
|
|
281
|
+
**Candidate line — the review-receipt gate (opt-in, never auto-seeded; AD-021).** Projects that configure a reviewed/council `plan-execution.review` recipe can declare the AD-038 review-state check as one more gate — the exact candidate `{ id, title, cmd }` line and its contract live under *Mode: review-state* (step 3). The template `gates.json` stays EMPTY; adding the line is the maintainer's explicit consent, by hand.
|
|
282
|
+
|
|
278
283
|
**Invariants:** the runner writes nothing · never commits · never runs a subscription CLI · executes only the project's OWN declared commands (never a kit-invented one) · the bash contract fails loud, never reinterprets.
|
|
279
284
|
|
|
280
285
|
### Mode: bootstrap
|
|
@@ -447,6 +452,8 @@ The four recipes (defined over each bridge's `provides` roles — `codex`: execu
|
|
|
447
452
|
2. **Offer the choice** via **`AskUserQuestion` where your agent supports it** (`AskUserQuestion` in Claude Code) — one option per recipe, the `recommendRecipe` choice listed **first** — otherwise in prose. Then print `planRecipe(chosen, detection)` (the per-stage dispatch + degradation reasons + quota/health notes) so the user sees exactly what running it entails.
|
|
448
453
|
3. **Availability = `readiness === ready`, full stop.** Every other readiness supplies the human reason (needs-skill → "not installed — `/agent-workflow-kit setup`"; needs-cli → "install the CLI"; needs-credentials → "log in"; degraded → "wrapper not on PATH — `/agent-workflow-kit setup`"). This is set-up state only — **never** a claim that a backend's service is responsive (the detector cannot observe a runtime stall; `agy`'s Issue-001 is a *standing advisory*, not a readiness signal).
|
|
449
454
|
|
|
455
|
+
**The configured-recipe line (`--active-line`, read-only).** `node ${CLAUDE_SKILL_DIR}/tools/recipes.mjs --active-line` prints exactly **one** machine-composed line: the **CONFIGURED** recipe of every activity/slot, resolved from the target project's `docs/ai/orchestration.json` (read from the current directory) + live readiness — each slot with its source (configured vs computed default), its degradation stated, and its dispatched wrapper set — explicitly contrasted with the readiness **recommendation** (which is informational; the configured recipes are what runs). Paste it verbatim: it fills the session-start discovery step (the deployed `agent_rules.md` §1.1) and the handover "Active recipes:" slot; `set-recipe` echoes the same line after every successful write. A malformed config fails loud (exit 1), never a silent fallback.
|
|
456
|
+
|
|
450
457
|
**Invariants:** read-only · never runs a subscription CLI · never commits · the orchestrator executes the recipe via the bridge skills, not the kit.
|
|
451
458
|
|
|
452
459
|
### Mode: procedures
|
|
@@ -567,6 +574,33 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/gate-hook.mjs [--dry-run | --apply] [--cwd <
|
|
|
567
574
|
|
|
568
575
|
**Exit codes:** `0` done / dry-run (incl. the reported diverged-but-wired state); `1` a precondition STOP; `2` bad arguments.
|
|
569
576
|
|
|
577
|
+
### Mode: review-state
|
|
578
|
+
|
|
579
|
+
Read-only **review-receipt checker** (AD-038) — it makes *"reviewed ≠ shipped"* mechanically detectable. The bridge review wrappers (`codex-review` / `agy-review`, from bridge version 2.2.0) append one receipt line per **successful** review to a file **inside the git dir** (`<git dir>/agent-workflow-review-receipts.jsonl` — never committable by construction; `AW_REVIEW_RECEIPTS` overrides). This mode resolves the effective `plan-execution.review` recipe (the SAME config + read-only detector the `procedures` advisor reads), recomputes the canonical **uncommitted-state fingerprint** (staged + unstaged + untracked-not-ignored contents — exactly the review-payload domain; the prose definition lives in each bridge's `capability.json` review contract, implementations cross-checked by the kit's fingerprint-parity test), and reports, per recipe-named backend, whether a **fresh, grounded, current-fingerprint** receipt exists. It **never writes, never commits, never runs a subscription CLI**; it does spawn **read-only `git` queries** to compute the fingerprint — stated honestly (that is still read-only, but it is a subprocess).
|
|
580
|
+
|
|
581
|
+
Run `node ${CLAUDE_SKILL_DIR}/tools/review-state.mjs [--check] [--json]`:
|
|
582
|
+
|
|
583
|
+
1. Plain run → the human report: resolved recipe + source, plan-in-flight, tree fingerprint, per-backend receipt state (current / stale / ungrounded / missing) with verdict + grounding + timestamp.
|
|
584
|
+
2. **`--check`** → the gate exit code. The **normative exit contract lives in the tool header** (the single home — do not re-enumerate it elsewhere): exit 0 for a solo-resolved recipe (configured, or degraded there — no ready reviewer), no plan in flight (the `docs/plans` naming convention: `queue.md` and `EXECUTE-`/`FEEDBACK-`-prefixed or `PROMPT`/`prompt`/`handoff`-carrying names are scratch), a clean tree, a non-git cwd, or every recipe-named backend receipted **current + grounded**; exit 1 when a backend is missing, **stale** (ANY edit after its review moves the fingerprint), or grounded:false under reviewed/council. **Presence, not unanimity:** verdict adjudication (ship/revise, the divergence crossover) stays orchestrator judgment — the gate only proves the configured backends really reviewed THIS tree. Plan/diff receipts and continuations (`agy-review --continue`) are **informational-only**: after a fold, only a **fresh grounded re-run** (`codex-review code`; `agy-review code --facts @f`) restores green.
|
|
585
|
+
3. **Wire it as a gate by hand — never auto-seeded (AD-021).** The candidate line for your own `docs/ai/gates.json`: `{ "id": "review-state", "title": "Review receipts current for the uncommitted tree", "cmd": "node <path-to-this-skill>/tools/review-state.mjs --check" }` — with the path your project actually reaches the kit by. Once declared, the opt-in *Mode: hook* auto-approves it like any other declared gate.
|
|
586
|
+
|
|
587
|
+
**Human residual (stated, accepted):** `git commit --no-verify` and receipt-file deletion/forgery remain possible — this is a self-discipline mechanism against silent process drift, not a security boundary.
|
|
588
|
+
|
|
589
|
+
**Invariants:** read-only · never writes · never commits · never runs a subscription CLI · spawns read-only `git` queries only · the receipt writers are the bridge review wrappers, never the kit.
|
|
590
|
+
|
|
591
|
+
### Mode: grounding
|
|
592
|
+
|
|
593
|
+
The **grounded-review facts assembler** (AD-038) — an ungrounded `agy` review GUESSES, and while the grounding contract is mechanized (`agy-review code --facts @f`), populating the facts file was a manual chore. This mode emits the two **mechanical** halves of a facts payload; the orchestrator still owns any judgment-bearing additions. **Catalogued honestly as a WRITER** — `--out <path>` writes one file — with the invariant: `--out` accepts **only gitignored / out-of-repo scratch destinations** and REFUSES a tracked path AND an in-repo not-ignored path (a new untracked file would itself move the review fingerprint the facts are about to ground); **stdout is the default**. It never commits and never runs a subscription CLI.
|
|
594
|
+
|
|
595
|
+
Run `node ${CLAUDE_SKILL_DIR}/tools/grounding.mjs [--constraints] [--plan <path>] [--reserve-bytes <n>] [--out <path>]`:
|
|
596
|
+
|
|
597
|
+
1. **`--constraints`** — slice the root `AGENTS.md` **Hard Constraints** section, verbatim and whole (exactly ONE matching heading; zero or several is a loud STOP, never a guess — the marker-slot discipline).
|
|
598
|
+
2. **`--plan <path>`** — extract the plan's decision-bearing canonical sections, verbatim + whole: `## Approach` (REQUIRED — its "What we are NOT doing" text rides inside; it is not a heading in canon) and `## Verification` (REQUIRED — STOP if missing), plus `## Decisions (locked)` when present (the optional engine §7 heading); a DUPLICATE heading is always a STOP.
|
|
599
|
+
3. **Byte budget** — the output honors the same `AGY_MAX_PROMPT_BYTES` contract the agy wrapper enforces (override may only tighten; the OS argv ceiling is rejected), MINUS **`--reserve-bytes <n>`** — the artifact share the caller expects `agy-review` to add around these facts. Overflow is trimmed tail-first with a loud in-band marker + stderr report — never a silent cut.
|
|
600
|
+
4. Feed the result to the wrapper: `agy-review code --facts @<out>`. The `procedures` advisor renders this invocation as a concrete pre-step whenever the resolved review dispatch includes agy — populated with the in-flight plan path when exactly one plan is in flight.
|
|
601
|
+
|
|
602
|
+
**Invariants:** writer (writes at most the ONE `--out` scratch file — gitignored / out-of-repo only) · never commits · never runs a subscription CLI · verbatim slices only (assembly is mechanical; facts judgment stays with the orchestrator).
|
|
603
|
+
|
|
570
604
|
---
|
|
571
605
|
|
|
572
606
|
## Gotchas
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: antigravity-cli-bridge
|
|
3
3
|
description: Delegate work to Google's Antigravity CLI (`agy`) — the successor to Gemini CLI — to reach Gemini, Claude, and GPT-OSS models under a Google AI Pro/Ultra subscription from the terminal. Use when the user wants to run a headless `agy` prompt, hand a focused task or second-opinion review to `agy`, install or authenticate Antigravity CLI, check or economise its quota/models, bridge project context into `agy`, set up a second delegated-execution backend beside Codex, or troubleshoot `agy` flags, models, auth, conversations, or its no-JSON headless behaviour.
|
|
4
4
|
metadata:
|
|
5
|
-
version: '2.
|
|
5
|
+
version: '2.2.0'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# antigravity-cli-bridge
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
# AGY_PROBE=1 throwaway probe — silences the off-frontier model advisory
|
|
43
43
|
# AGY_REVIEW_ALLOW_ADDDIR=1 oversized CODE review: offload the change set to a private
|
|
44
44
|
# staging dir and pass it via --add-dir (re-enables Issue-001 stall risk)
|
|
45
|
+
# AW_REVIEW_RECEIPTS override the review-receipt file (default: <git dir>/
|
|
46
|
+
# agent-workflow-review-receipts.jsonl — see the --help Receipt block)
|
|
45
47
|
set -euo pipefail
|
|
46
48
|
|
|
47
49
|
# --- --help / -h (pre-preflight: no agy, no login, no git tree needed) ---------
|
|
@@ -72,6 +74,18 @@ Round-2 / resume:
|
|
|
72
74
|
agy-review --conversation <id> [--decided @f] [--focus "…"]
|
|
73
75
|
(a continuation sends a small delta — agy holds the artifact server-side; --facts is invalid on a continuation)
|
|
74
76
|
|
|
77
|
+
Receipt:
|
|
78
|
+
side effect — a successful review appends one JSON receipt line to
|
|
79
|
+
<git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside
|
|
80
|
+
a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical
|
|
81
|
+
uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the
|
|
82
|
+
review-payload domain) in code mode, the artifact-file sha256 in plan/diff mode; verdict
|
|
83
|
+
recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK);
|
|
84
|
+
grounded = whether a NON-EMPTY --facts payload was supplied (an empty payload records
|
|
85
|
+
grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts
|
|
86
|
+
payload; a continuation receipt is fresh:false (informational-only — it cannot attest the
|
|
87
|
+
folded tree); a write failure warns, never fails the review
|
|
88
|
+
|
|
75
89
|
Closed grammar: unknown flags are rejected; no '--' passthrough (the only escapes are AGY_PROBE=1 and AGY_REVIEW_ALLOW_ADDDIR=1).
|
|
76
90
|
Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
|
|
77
91
|
HELP
|
|
@@ -80,6 +94,10 @@ HELP
|
|
|
80
94
|
esac
|
|
81
95
|
|
|
82
96
|
DEFAULT_AGY_REVIEW_MODEL="Gemini 3.1 Pro (High)"
|
|
97
|
+
# Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
|
|
98
|
+
# version (drift-guarded by agy-review.test.mjs against capability.json).
|
|
99
|
+
AW_RECEIPT_BACKEND="agy"
|
|
100
|
+
AW_BRIDGE_VERSION="2.2.0"
|
|
83
101
|
# `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
|
|
84
102
|
AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
|
|
85
103
|
# Frontier review models. ANY model is allowed; a sub-frontier one only earns a soft, silenceable warning.
|
|
@@ -181,6 +199,95 @@ is_binary() {
|
|
|
181
199
|
[[ "${nul:-0}" -gt 0 ]]
|
|
182
200
|
}
|
|
183
201
|
|
|
202
|
+
# --- Review receipts (AD-038) — byte-identical in codex-review.sh and agy-review.sh ---------------
|
|
203
|
+
# sha256 hex of stdin. sha256sum, else shasum -a 256; neither → warn + fail (the caller records a
|
|
204
|
+
# null fingerprint — a null never satisfies the review-state checker, fail-safe direction).
|
|
205
|
+
sha256_stdin() {
|
|
206
|
+
if command -v sha256sum >/dev/null 2>&1; then sha256sum | awk '{print $1}'
|
|
207
|
+
elif command -v shasum >/dev/null 2>&1; then shasum -a 256 | awk '{print $1}'
|
|
208
|
+
else
|
|
209
|
+
echo "warning: no sha256sum/shasum on PATH — cannot compute the review fingerprint." >&2
|
|
210
|
+
return 1
|
|
211
|
+
fi
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# The canonical uncommitted-state fingerprint payload (code mode). Domain == the review payload:
|
|
215
|
+
# tracked staged + unstaged changes + untracked-not-ignored file contents (binary/symlink/non-regular
|
|
216
|
+
# untracked paths ride as name-only notes, mirroring the assembled change set). The prose definition
|
|
217
|
+
# lives in capability.json roles.review.contract.receipt (both bridges, lockstep); the kit checker
|
|
218
|
+
# (tools/review-state.mjs) implements the SAME serialization in node — cross-checked by the kit's
|
|
219
|
+
# review-fingerprint-parity.test.mjs.
|
|
220
|
+
emit_fingerprint_payload() {
|
|
221
|
+
git diff --cached --no-ext-diff
|
|
222
|
+
git diff --no-ext-diff
|
|
223
|
+
local path
|
|
224
|
+
while IFS= read -r -d '' path; do
|
|
225
|
+
if [[ -L "$path" ]]; then
|
|
226
|
+
printf 'untracked-symlink:%s -> %s\n' "$path" "$(readlink -- "$path" 2>/dev/null || echo '?')"
|
|
227
|
+
elif [[ ! -f "$path" ]]; then
|
|
228
|
+
printf 'untracked-nonregular:%s\n' "$path"
|
|
229
|
+
elif is_binary "$path"; then
|
|
230
|
+
printf 'untracked-binary:%s\n' "$path"
|
|
231
|
+
else
|
|
232
|
+
printf 'untracked:%s\n' "$path"
|
|
233
|
+
cat -- "$path"
|
|
234
|
+
fi
|
|
235
|
+
done < <(git ls-files --others --exclude-standard -z)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# sha256 of the canonical payload, emitted from the work-tree ROOT (a subdir invocation hashes the
|
|
239
|
+
# same bytes). Empty output on failure (no git tree / no sha256 tool) — recorded as null.
|
|
240
|
+
compute_tree_fingerprint() {
|
|
241
|
+
( cd "$(git rev-parse --show-toplevel)" && emit_fingerprint_payload ) | sha256_stdin
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
# JSON-encode a receipt scalar: empty → null, else a quoted string (every value comes from a closed
|
|
245
|
+
# vocabulary or a hex digest — no escaping needed by construction).
|
|
246
|
+
receipt_json_scalar() {
|
|
247
|
+
if [[ -z "${1:-}" ]]; then printf 'null'; else printf '"%s"' "$1"; fi
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
# write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|"">
|
|
251
|
+
# Appends ONE receipt line (the AD-038 fixture shape) as a side effect of a SUCCESSFUL review —
|
|
252
|
+
# to $AW_REVIEW_RECEIPTS when set, else <git dir>/agent-workflow-review-receipts.jsonl (inside the
|
|
253
|
+
# git dir by construction, so it is never committable). Fail-safe: every failure here warns loudly
|
|
254
|
+
# and returns 0 — a missing receipt fails the kit's review-state CHECKER, never the review run.
|
|
255
|
+
write_review_receipt() {
|
|
256
|
+
local artifact="$1" fresh="$2" fingerprint="$3" verdict="$4" grounded="$5" facts_hash="$6"
|
|
257
|
+
local receipts="${AW_REVIEW_RECEIPTS:-}"
|
|
258
|
+
if [[ -z "$receipts" ]]; then
|
|
259
|
+
local receipt_git_dir
|
|
260
|
+
if ! receipt_git_dir="$(git rev-parse --absolute-git-dir 2>/dev/null)"; then
|
|
261
|
+
echo "warning: not inside a git work tree and AW_REVIEW_RECEIPTS is unset — skipping the review receipt." >&2
|
|
262
|
+
return 0
|
|
263
|
+
fi
|
|
264
|
+
receipts="$receipt_git_dir/agent-workflow-review-receipts.jsonl"
|
|
265
|
+
fi
|
|
266
|
+
local line
|
|
267
|
+
line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"}' \
|
|
268
|
+
"$(receipt_json_scalar "$artifact")" "$fresh" "$(receipt_json_scalar "$fingerprint")" \
|
|
269
|
+
"$AW_RECEIPT_BACKEND" "$verdict" "$grounded" "$(receipt_json_scalar "$facts_hash")" \
|
|
270
|
+
"$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)")"
|
|
271
|
+
if ! printf '%s\n' "$line" >>"$receipts" 2>/dev/null; then
|
|
272
|
+
echo "warning: could not append the review receipt to $receipts — the review itself succeeded;" >&2
|
|
273
|
+
echo " the review-state gate will read the current tree as un-receipted." >&2
|
|
274
|
+
fi
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Parse the mandated '### Verdict' section of a captured review: the first non-empty line after the
|
|
278
|
+
# heading, matched against the closed verdict vocabulary (SHIP WITH NITS before SHIP — substring).
|
|
279
|
+
# No heading / no match → "unknown" (recorded, never guessed).
|
|
280
|
+
parse_agy_verdict() { # $1 = captured-output file
|
|
281
|
+
local line
|
|
282
|
+
line="$(awk '/^### Verdict/{flag=1; next} flag && NF {print; exit}' "$1" 2>/dev/null)"
|
|
283
|
+
case "$line" in
|
|
284
|
+
*"SHIP WITH NITS"*) printf 'SHIP WITH NITS' ;;
|
|
285
|
+
*REWORK*) printf 'REWORK' ;;
|
|
286
|
+
*SHIP*) printf 'SHIP' ;;
|
|
287
|
+
*) printf 'unknown' ;;
|
|
288
|
+
esac
|
|
289
|
+
}
|
|
290
|
+
|
|
184
291
|
# Emit the full code-review surface: repo map, status, staged + unstaged diffs, and the CONTENTS of
|
|
185
292
|
# every untracked REGULAR file (NUL-safe iteration). Symlinks are shown as their target (never
|
|
186
293
|
# followed — no out-of-repo leak); other non-regular paths (FIFO/socket/device/dir) are skipped.
|
|
@@ -246,6 +353,8 @@ mode=""
|
|
|
246
353
|
target=""
|
|
247
354
|
PLAN_CONTENT=""
|
|
248
355
|
DIFF_CONTENT=""
|
|
356
|
+
REVIEW_ARTIFACT=""
|
|
357
|
+
REVIEW_FINGERPRINT=""
|
|
249
358
|
if [[ -z "$resume_mode" ]]; then
|
|
250
359
|
mode="${1:-}"; shift || true
|
|
251
360
|
case "$mode" in
|
|
@@ -259,7 +368,10 @@ if [[ -z "$resume_mode" ]]; then
|
|
|
259
368
|
echo "error: $mode file '$target' not found." >&2; exit 2
|
|
260
369
|
fi
|
|
261
370
|
# Read the target NOW (before any cd) — its path is relative to the invocation cwd.
|
|
262
|
-
if [[ "$mode" == "plan" ]]; then PLAN_CONTENT="$(cat -- "$target")"; else DIFF_CONTENT="$(cat -- "$target")"; fi
|
|
371
|
+
if [[ "$mode" == "plan" ]]; then PLAN_CONTENT="$(cat -- "$target")"; else DIFF_CONTENT="$(cat -- "$target")"; fi
|
|
372
|
+
# Plan/diff receipt identity: the artifact-file sha256 (informational-only for the tree checker).
|
|
373
|
+
REVIEW_ARTIFACT="$mode"
|
|
374
|
+
REVIEW_FINGERPRINT="$(sha256_stdin <"$target" || true)" ;;
|
|
263
375
|
*)
|
|
264
376
|
usage; exit 2 ;;
|
|
265
377
|
esac
|
|
@@ -392,6 +504,10 @@ else
|
|
|
392
504
|
echo "agy-review: no uncommitted changes to review — the working tree is clean." >&2
|
|
393
505
|
exit 0
|
|
394
506
|
fi
|
|
507
|
+
# The canonical fingerprint of the tree agy is about to review — computed at assembly time,
|
|
508
|
+
# so the receipt attests exactly the reviewed state.
|
|
509
|
+
REVIEW_ARTIFACT="code"
|
|
510
|
+
REVIEW_FINGERPRINT="$(compute_tree_fingerprint || true)"
|
|
395
511
|
fi
|
|
396
512
|
|
|
397
513
|
emit_artifact() {
|
|
@@ -468,14 +584,39 @@ else
|
|
|
468
584
|
fi
|
|
469
585
|
|
|
470
586
|
# --- Execute via agy-run (single home of timeout + subscription + byte ceiling) ---
|
|
587
|
+
# The output is teed into the private staging dir so the mandated '### Verdict' section can be
|
|
588
|
+
# parsed into the review receipt — the user-facing stream is unchanged.
|
|
589
|
+
review_out_file="$staging/review-output"
|
|
471
590
|
set +e
|
|
472
591
|
if (( ${#run_passthrough[@]} > 0 )); then
|
|
473
592
|
AGY_MODEL="$AGY_MODEL" AGY_TIMEOUT="$AGY_TIMEOUT" AGY_HARD_TIMEOUT="$AGY_HARD_TIMEOUT" \
|
|
474
|
-
"$AGY_RUN" "@$prompt_file" -- "${run_passthrough[@]}"
|
|
593
|
+
"$AGY_RUN" "@$prompt_file" -- "${run_passthrough[@]}" | tee "$review_out_file"
|
|
594
|
+
rc=${PIPESTATUS[0]}
|
|
475
595
|
else
|
|
476
596
|
AGY_MODEL="$AGY_MODEL" AGY_TIMEOUT="$AGY_TIMEOUT" AGY_HARD_TIMEOUT="$AGY_HARD_TIMEOUT" \
|
|
477
|
-
"$AGY_RUN" "@$prompt_file"
|
|
597
|
+
"$AGY_RUN" "@$prompt_file" | tee "$review_out_file"
|
|
598
|
+
rc=${PIPESTATUS[0]}
|
|
478
599
|
fi
|
|
479
|
-
rc=$?
|
|
480
600
|
set -e
|
|
601
|
+
|
|
602
|
+
# --- Review receipt (AD-038): only a SUCCESSFUL review attests --------------------
|
|
603
|
+
if [[ $rc -eq 0 ]]; then
|
|
604
|
+
verdict="$(parse_agy_verdict "$review_out_file")"
|
|
605
|
+
if [[ -n "$resume_mode" ]]; then
|
|
606
|
+
# A continuation never re-embeds the current artifact (agy holds the ORIGINAL round server-side;
|
|
607
|
+
# --facts is rejected above), so it cannot attest the folded tree: fresh:false, artifact /
|
|
608
|
+
# fingerprint / factsHash null, grounded false — informational-only, ignored by the state gate.
|
|
609
|
+
write_review_receipt "" false "" "$verdict" false ""
|
|
610
|
+
echo "notice: a continuation receipt is fresh:false (informational-only) — only a fresh grounded run" >&2
|
|
611
|
+
echo " (agy-review code --facts @f) mints a receipt that satisfies the review-state gate." >&2
|
|
612
|
+
else
|
|
613
|
+
grounded=false
|
|
614
|
+
facts_hash=""
|
|
615
|
+
if [[ -n "$FACTS_CONTENT" ]]; then
|
|
616
|
+
grounded=true
|
|
617
|
+
facts_hash="$(printf '%s' "$FACTS_CONTENT" | sha256_stdin || true)"
|
|
618
|
+
fi
|
|
619
|
+
write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" "$grounded" "$facts_hash"
|
|
620
|
+
fi
|
|
621
|
+
fi
|
|
481
622
|
exit $rc
|
|
@@ -38,7 +38,7 @@ const FAKE_AGY = [
|
|
|
38
38
|
' fi; prev="$a"',
|
|
39
39
|
'done',
|
|
40
40
|
'if [[ -n "${AGY_FAKE_SLEEP:-}" ]]; then sleep "$AGY_FAKE_SLEEP"; fi',
|
|
41
|
-
'
|
|
41
|
+
'printf "%s\\n" "${AGY_FAKE_OUTPUT:-FAKE_AGY_REVIEW_OUTPUT}"',
|
|
42
42
|
'exit "${AGY_FAKE_EXIT:-0}"',
|
|
43
43
|
'',
|
|
44
44
|
].join('\n');
|
|
@@ -553,6 +553,13 @@ describe('agy-review.sh — --help contract (manifest-pinned)', () => {
|
|
|
553
553
|
assert.ok(REVIEW_CONTRACT.continue.length > 0, 'manifest continue must be non-empty');
|
|
554
554
|
setEq(got, REVIEW_CONTRACT.continue.map(norm), 'help continue ⟷ manifest continue');
|
|
555
555
|
});
|
|
556
|
+
|
|
557
|
+
it('Receipt renders the manifest receipt contract verbatim (AD-038 three-way lockstep)', () => {
|
|
558
|
+
const help = runHelp('--help').stdout;
|
|
559
|
+
assert.equal(norm(helpSection(help, 'Receipt:').join(' ')), norm(REVIEW_CONTRACT.receipt));
|
|
560
|
+
assert.match(REVIEW_CONTRACT.receipt, /sha256 over the canonical uncommitted-state payload/, 'the fingerprint definition lives in the manifest contract');
|
|
561
|
+
assert.match(REVIEW_CONTRACT.receipt, /fresh:false/, 'the continuation informational-only clause is contractual');
|
|
562
|
+
});
|
|
556
563
|
});
|
|
557
564
|
|
|
558
565
|
describe('agy-review.sh — source-level reverse guard (parser arms ⟷ manifest)', () => {
|
|
@@ -625,3 +632,164 @@ describe('agy-review.sh — declared contract is really accepted (forward guard)
|
|
|
625
632
|
assert.doesNotMatch(r.stdout, /Usage:/);
|
|
626
633
|
});
|
|
627
634
|
});
|
|
635
|
+
|
|
636
|
+
// ── review receipts (AD-038) ─────────────────────────────────────────────────────
|
|
637
|
+
// The normative fixture (docs: the AD-038 plan Decisions — copied verbatim; backend/verdict here
|
|
638
|
+
// carry this bridge's vocabulary; dynamic values are asserted by shape):
|
|
639
|
+
const RECEIPT_FIXTURE = JSON.parse(
|
|
640
|
+
'{"schema":1,"artifact":"code","fresh":true,"fingerprint":"<sha256hex>","backend":"codex","verdict":"revise","grounded":true,"factsHash":null,"wrapperVersion":"2.2.0","timestamp":"2026-07-03T12:00:00Z"}',
|
|
641
|
+
);
|
|
642
|
+
const RECEIPTS_REL = join('.git', 'agent-workflow-review-receipts.jsonl');
|
|
643
|
+
const readReceipts = (repo) => {
|
|
644
|
+
const p = join(repo, RECEIPTS_REL);
|
|
645
|
+
if (!existsSync(p)) return [];
|
|
646
|
+
return readFileSync(p, 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
|
|
647
|
+
};
|
|
648
|
+
const sha256HexOf = async (buf) => {
|
|
649
|
+
const { createHash } = await import('node:crypto');
|
|
650
|
+
return createHash('sha256').update(buf).digest('hex');
|
|
651
|
+
};
|
|
652
|
+
const VERDICT_OUTPUT = '### Verdict\nSHIP WITH NITS — solid, two nits.\n### Blocking\nnone\n### Non-blocking\n1. nit\n### Questions\nnone';
|
|
653
|
+
|
|
654
|
+
describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
655
|
+
it('a fresh grounded code review appends ONE fixture-shaped receipt (verdict verbatim, factsHash real)', async () => {
|
|
656
|
+
const sb = makeSandbox();
|
|
657
|
+
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
658
|
+
const receipts = readReceipts(sb.repo);
|
|
659
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
660
|
+
assert.equal(r.status, 0, r.stderr);
|
|
661
|
+
assert.equal(receipts.length, 1, 'exactly one receipt line');
|
|
662
|
+
const receipt = receipts[0];
|
|
663
|
+
assert.deepEqual(Object.keys(receipt), Object.keys(RECEIPT_FIXTURE), 'fixture key set + order');
|
|
664
|
+
assert.equal(receipt.schema, 1);
|
|
665
|
+
assert.equal(receipt.artifact, 'code');
|
|
666
|
+
assert.equal(receipt.fresh, true);
|
|
667
|
+
assert.match(receipt.fingerprint, /^[0-9a-f]{64}$/, 'a real sha256 hex fingerprint');
|
|
668
|
+
assert.equal(receipt.backend, 'agy');
|
|
669
|
+
assert.equal(receipt.verdict, 'SHIP WITH NITS', 'the mandated ### Verdict section is recorded verbatim');
|
|
670
|
+
assert.equal(receipt.grounded, true, '--facts was supplied');
|
|
671
|
+
assert.equal(receipt.factsHash, await sha256HexOf('a tiny fact'), 'sha256 of the facts payload — an empty/changed facts file is visible');
|
|
672
|
+
assert.equal(receipt.wrapperVersion, MANIFEST.version, 'receipt version ⟷ capability.json version');
|
|
673
|
+
assert.match(receipt.timestamp, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it('an ungrounded fresh run records grounded:false + factsHash null (the vacuous-grounding hole stays visible)', () => {
|
|
677
|
+
const sb = makeSandbox();
|
|
678
|
+
const r = run(sb, { args: ['code'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
679
|
+
const receipts = readReceipts(sb.repo);
|
|
680
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
681
|
+
assert.equal(r.status, 0, r.stderr);
|
|
682
|
+
assert.equal(receipts[0].grounded, false);
|
|
683
|
+
assert.equal(receipts[0].factsHash, null);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
it('an EMPTY --facts file records grounded:false (fail-closed — vacuous grounding never satisfies the gate) and still warns', () => {
|
|
687
|
+
const sb = makeSandbox();
|
|
688
|
+
writeFileSync(join(sb.home, 'empty-facts.md'), '');
|
|
689
|
+
const r = run(sb, { args: ['code', '--facts', `@${join(sb.home, 'empty-facts.md')}`], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
690
|
+
const receipts = readReceipts(sb.repo);
|
|
691
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
692
|
+
assert.equal(r.status, 0, r.stderr);
|
|
693
|
+
assert.equal(receipts[0].grounded, false, 'an empty payload is recorded as ungrounded, never as grounded-by-flag');
|
|
694
|
+
assert.equal(receipts[0].factsHash, null);
|
|
695
|
+
assert.match(r.stderr, /no --facts supplied|ungrounded review GUESSES/, 'the ungrounded warning fires for an empty payload');
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it('parses REWORK and plain SHIP; records "unknown" when the mandated section is absent', () => {
|
|
699
|
+
for (const [output, want] of [
|
|
700
|
+
['### Verdict\nREWORK — the contract is violated.', 'REWORK'],
|
|
701
|
+
['### Verdict\nSHIP — clean.', 'SHIP'],
|
|
702
|
+
['free-form text with no verdict heading', 'unknown'],
|
|
703
|
+
]) {
|
|
704
|
+
const sb = makeSandbox();
|
|
705
|
+
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: output } });
|
|
706
|
+
const receipts = readReceipts(sb.repo);
|
|
707
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
708
|
+
assert.equal(r.status, 0, r.stderr);
|
|
709
|
+
assert.equal(receipts[0].verdict, want, `verdict for: ${output.slice(0, 30)}`);
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it('a continuation receipt is fresh:false with null identity fields, and the wrapper prints the fresh-run notice', () => {
|
|
714
|
+
const sb = makeSandbox();
|
|
715
|
+
const r = run(sb, { args: ['--continue', '--decided', 'already folded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
716
|
+
const receipts = readReceipts(sb.repo);
|
|
717
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
718
|
+
assert.equal(r.status, 0, r.stderr);
|
|
719
|
+
assert.equal(receipts.length, 1);
|
|
720
|
+
const receipt = receipts[0];
|
|
721
|
+
assert.deepEqual(Object.keys(receipt), Object.keys(RECEIPT_FIXTURE), 'same fixture shape');
|
|
722
|
+
assert.equal(receipt.fresh, false, 'a continuation cannot attest the folded tree');
|
|
723
|
+
assert.equal(receipt.artifact, null);
|
|
724
|
+
assert.equal(receipt.fingerprint, null);
|
|
725
|
+
assert.equal(receipt.grounded, false);
|
|
726
|
+
assert.equal(receipt.factsHash, null);
|
|
727
|
+
assert.equal(receipt.verdict, 'SHIP WITH NITS', 'the round-2 verdict is still recorded (informational)');
|
|
728
|
+
assert.match(r.stderr, /fresh grounded run/, 'the one-line notice names the required fresh run');
|
|
729
|
+
assert.match(r.stderr, /review-state gate/);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it('plan mode: artifact "plan", fingerprint = the artifact-file sha256', async () => {
|
|
733
|
+
const sb = makeSandbox();
|
|
734
|
+
writeFileSync(join(sb.repo, 'p.md'), '# plan body\n');
|
|
735
|
+
const r = run(sb, { args: ['plan', 'p.md', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
736
|
+
const receipts = readReceipts(sb.repo);
|
|
737
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
738
|
+
assert.equal(r.status, 0, r.stderr);
|
|
739
|
+
assert.equal(receipts[0].artifact, 'plan');
|
|
740
|
+
assert.equal(receipts[0].fingerprint, await sha256HexOf('# plan body\n'), 'plan fingerprint = file sha256');
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
it('plan/diff outside a git work tree: warn + skip the receipt (exit 0) unless AW_REVIEW_RECEIPTS is set', () => {
|
|
744
|
+
const sb = makeSandbox();
|
|
745
|
+
const outside = join(sb.home, 'no-repo');
|
|
746
|
+
mkdirSync(outside, { recursive: true });
|
|
747
|
+
writeFileSync(join(outside, 'p.md'), '# plan outside git\n');
|
|
748
|
+
|
|
749
|
+
const skipped = run(sb, { args: ['plan', 'p.md', '--facts', 'f'], cwd: outside, env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
750
|
+
assert.equal(skipped.status, 0, skipped.stderr);
|
|
751
|
+
assert.match(skipped.stderr, /not inside a git work tree and AW_REVIEW_RECEIPTS is unset — skipping/);
|
|
752
|
+
|
|
753
|
+
const override = join(sb.home, 'receipts-override.jsonl');
|
|
754
|
+
const written = run(sb, {
|
|
755
|
+
args: ['plan', 'p.md', '--facts', 'f'],
|
|
756
|
+
cwd: outside,
|
|
757
|
+
env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_RECEIPTS: override },
|
|
758
|
+
});
|
|
759
|
+
const body = existsSync(override) ? readFileSync(override, 'utf8') : '';
|
|
760
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
761
|
+
assert.equal(written.status, 0, written.stderr);
|
|
762
|
+
assert.match(body, /"backend":"agy"/, 'the override path receives the receipt outside a git tree');
|
|
763
|
+
assert.match(body, /"artifact":"plan"/);
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
it('a receipt write failure warns loudly but never fails the review (fail-safe direction)', () => {
|
|
767
|
+
const sb = makeSandbox();
|
|
768
|
+
const r = run(sb, {
|
|
769
|
+
args: ['code', '--facts', 'f'],
|
|
770
|
+
env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_RECEIPTS: join(sb.home, 'no-such-dir', 'r.jsonl') },
|
|
771
|
+
});
|
|
772
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
773
|
+
assert.equal(r.status, 0, 'the review run itself succeeds');
|
|
774
|
+
assert.match(r.stderr, /could not append the review receipt/);
|
|
775
|
+
assert.match(r.stdout, /SHIP WITH NITS/, 'the findings still reach stdout');
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it('a failed agy run writes NO receipt (only a successful review attests)', () => {
|
|
779
|
+
const sb = makeSandbox();
|
|
780
|
+
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_EXIT: '7' } });
|
|
781
|
+
const receipts = readReceipts(sb.repo);
|
|
782
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
783
|
+
assert.notEqual(r.status, 0);
|
|
784
|
+
assert.equal(receipts.length, 0);
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
it('the clean-tree preflight exits before any receipt is written', () => {
|
|
788
|
+
const sb = makeSandbox({ clean: true });
|
|
789
|
+
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
790
|
+
const receipts = readReceipts(sb.repo);
|
|
791
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
792
|
+
assert.equal(r.status, 0);
|
|
793
|
+
assert.equal(receipts.length, 0, 'no review ran — no receipt');
|
|
794
|
+
});
|
|
795
|
+
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"name": "antigravity-cli-bridge",
|
|
5
5
|
"kind": "execution-backend",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.2.0",
|
|
7
7
|
"provides": ["review", "probe"],
|
|
8
8
|
"roles": {
|
|
9
9
|
"review": {
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"continue": [
|
|
28
28
|
"agy-review --continue [--decided @f] [--focus \"…\"]",
|
|
29
29
|
"agy-review --conversation <id> [--decided @f] [--focus \"…\"]"
|
|
30
|
-
]
|
|
30
|
+
],
|
|
31
|
+
"receipt": "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain) in code mode, the artifact-file sha256 in plan/diff mode; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); a write failure warns, never fails the review"
|
|
31
32
|
}
|
|
32
33
|
},
|
|
33
34
|
"probe": { "cmd": "agy-run", "source": "bin/agy.sh", "output": "advisory" }
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: codex-cli-bridge
|
|
3
3
|
description: Delegate work to the OpenAI Codex CLI (`codex`) under a ChatGPT subscription — run plan/instruction EXECUTION in a sandboxed workspace, or get a read-only ADVISORY review of a plan or working-tree diff — as a second delegated-execution backend beside Antigravity. Use when the user wants to hand a bounded coding task or plan to `codex exec`, get a second-opinion review from codex, install or authenticate Codex CLI, understand its sandbox/network/approval policy, drive codex efficiently from the main agent (exec vs review, resume, the commit boundary), bridge project context (`AGENTS.md`) into codex, or troubleshoot codex flags, models, auth, or its no-TTY headless behaviour.
|
|
4
4
|
metadata:
|
|
5
|
-
version: '2.
|
|
5
|
+
version: '2.2.0'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# codex-cli-bridge
|