@sabaiway/agent-workflow-kit 1.23.0 → 1.25.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 +53 -0
- package/README.md +1 -1
- package/SKILL.md +44 -17
- package/bridges/antigravity-cli-bridge/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +35 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +186 -0
- package/bridges/antigravity-cli-bridge/bin/agy.sh +23 -0
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +58 -2
- package/bridges/antigravity-cli-bridge/capability.json +25 -2
- package/bridges/codex-cli-bridge/SKILL.md +2 -2
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +34 -0
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +160 -1
- package/bridges/codex-cli-bridge/bin/codex-review.sh +28 -0
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +127 -0
- package/bridges/codex-cli-bridge/capability.json +36 -3
- package/bridges/codex-cli-bridge/references/driving-codex.md +1 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/tools/commands.mjs +1 -1
- package/tools/detect-backends.mjs +59 -0
- package/tools/manifest/schema.md +15 -0
- package/tools/procedures.mjs +37 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,59 @@ 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.25.0 — The bridge driving contract at the point of use (advisor render + wrapper `--help`)
|
|
8
|
+
|
|
9
|
+
A **feature** release (additive; ships the bundled bridges at **2.1.0**). An agent told to run a
|
|
10
|
+
bridge no longer re-derives the invocation from wrapper source — where it missed documented levers
|
|
11
|
+
(agy's `--facts`/`--decided` grounding, the `agy-review --continue` round-2 delta) and wasted
|
|
12
|
+
subscription runs on ungrounded reviews. The contract is now **delivered at the moment a recipe
|
|
13
|
+
dispatches a backend**, from ONE machine-readable source:
|
|
14
|
+
|
|
15
|
+
- **Manifest as source.** Each bridge `capability.json` dispatchable role (`review`, `execute`) now
|
|
16
|
+
carries a structured `contract`: exact copy-pasteable **invocation descriptors** (operands and
|
|
17
|
+
alternatives included), the **grounding** note, the closed **flag** set (agy-review), the
|
|
18
|
+
**round-2 / resume** descriptors, and codex-exec's **tiered guarded passthrough**
|
|
19
|
+
(always-blocked vs `CODEX_PROBE=1`-relaxable). Documented in `tools/manifest/schema.md`.
|
|
20
|
+
- **Advisor renders it at the point of use.** `/agent-workflow-kit procedures <activity>` prints,
|
|
21
|
+
under every dispatched backend of every slot (review recipes AND `execute=delegated`), the full
|
|
22
|
+
driving contract VERBATIM — e.g. council shows `agy-review code [--facts @f] [--decided @f] …`
|
|
23
|
+
plus the `agy-review --continue` delta beside `codex-review plan|code`. `--json` carries the same
|
|
24
|
+
in an **additive** `slots[*].contracts` field (`backends: string[]` unchanged).
|
|
25
|
+
- **Every wrapper answers `--help`/`-h`** — pre-preflight (no CLI, no login, no git tree, no
|
|
26
|
+
AGENTS.md needed); keyed on the FIRST argument only, so an open wrapper's passthrough payload
|
|
27
|
+
(`codex-exec - -- --help`) is never intercepted. The three **dispatchable** wrappers
|
|
28
|
+
(`codex-review`, `agy-review`, `codex-exec`) print the manifest contract; `agy-run` (probe role —
|
|
29
|
+
never dispatched by a recipe slot) ships a lightweight wrapper-authored help, pinned for
|
|
30
|
+
pre-preflight reachability only, with no manifest pivot by design.
|
|
31
|
+
- **Drift-guarded in both directions (test-as-spec), for the dispatchable wrappers.** The kit
|
|
32
|
+
registry mirror (`wrapperContractFor`) deep-equals each manifest; the advisor's rendered
|
|
33
|
+
descriptor set set-EQUALS the manifest (a missing AND a stale-extra descriptor both fail); each
|
|
34
|
+
dispatchable wrapper's `--help` set-EQUALS the manifest; and a **source-level reverse guard**
|
|
35
|
+
extracts each dispatchable wrapper's real parser arms (mode/flag/resume/passthrough-tier `case`
|
|
36
|
+
arms, heredocs excluded) and pins them to the manifest — adding a wrapper mode or flag without
|
|
37
|
+
updating the surfaced contract fails a test.
|
|
38
|
+
- Stale "unguarded codex flags" wording in the codex bridge docs corrected to the real **guarded**
|
|
39
|
+
passthrough contract.
|
|
40
|
+
|
|
41
|
+
## 1.24.0 — Humanize the deploy/version report: hide the internal structure number in the happy path
|
|
42
|
+
|
|
43
|
+
A **feature** release (report-contract only — no logic, migration, or lineage change; the
|
|
44
|
+
deployment-lineage head stays `1.3.0`).
|
|
45
|
+
|
|
46
|
+
The bootstrap/upgrade report no longer leads with the internal `docs/ai` **structure version**
|
|
47
|
+
(`deploymentHead`) — an un-actionable number that leaked into **every** successful report, including
|
|
48
|
+
zero-diff no-ops, and read as "smaller than the version on npm/GitHub".
|
|
49
|
+
|
|
50
|
+
- **Happy path is now plain.** A zero-diff no-op `upgrade` says **settings already current — no update
|
|
51
|
+
needed** (rendered in the user's language); a fresh `bootstrap` keeps its "deployed and ready"
|
|
52
|
+
framing. Neither surfaces the structure semver, the stamp filename, or any head/lineage vocabulary.
|
|
53
|
+
- **The number survives only where it is actionable** — the never-downgrade STOP gate and the explicit
|
|
54
|
+
`Mode: status` view — now **named "the `docs/ai` structure version"** (never "lineage head"), paired
|
|
55
|
+
with a plain, on-demand two-axes note (*Version disclosure*). A migration that ran is described in
|
|
56
|
+
**human terms**; the raw number is omitted, never recited on a successful report.
|
|
57
|
+
- **The version-status check is framed as internal routing**, not a line printed on every invocation.
|
|
58
|
+
- Pinned by a new static contract test (`test/report-contract.test.mjs`, invariants A1–A6).
|
|
59
|
+
|
|
7
60
|
## 1.23.0 — Surface the review-loop economics + the resolved backend set in the procedures advisor
|
|
8
61
|
|
|
9
62
|
A **feature** release. `/agent-workflow-kit procedures <activity>` now makes the review-loop discipline
|
package/README.md
CHANGED
|
@@ -228,7 +228,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
|
|
|
228
228
|
| `/agent-workflow-kit setup [backend]` | opt-in, any time | **link-only** auto-setup of a bridge: places the bundled bridge skill (only into an absent / empty / managed dir — never overwrites an unmanaged one) + links its wrappers onto `PATH` via managed symlinks (idempotent; refuses to clobber a non-symlink; try `--dry-run` to preview). The binary install + the one-time subscription login stay **manual**: it prints the exact **login** command and points the binary install at each bridge's `setup/README.md`. POSIX wrappers — on Windows use WSL. Never commits, never runs a subscription CLI. |
|
|
229
229
|
| `/agent-workflow-kit status` | any time | **read-only** single view of **versions + deployment + settings + bridges**: which members (kit / memory / engine / the two bridges) are installed and at what version (with an honest "installed on this machine" note when one is behind) and — in a project — what's deployed (`docs/ai`, the version stamps, and the **visibility**: visible / hidden / unclear), plus your settings (orchestration recipes, attribution, velocity) and the bridges' readiness. The two version axes (package number vs deployment-structure head) stay decoupled. Never writes, never commits, never runs a subscription CLI. |
|
|
230
230
|
| `/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. |
|
|
231
|
-
| `/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). `--override <slot>=<recipe>` adjusts one slot per run. Composes with `recipes`; never writes, never commits, never runs a subscription CLI. |
|
|
231
|
+
| `/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. |
|
|
232
232
|
| `/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. |
|
|
233
233
|
| `/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. |
|
|
234
234
|
| `/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 redirection/command-substitution residual remains, closed by a deferred hook); a direct commit/push/publish still asks. `--dry-run` first. |
|
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.25.0'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-kit
|
|
@@ -111,16 +111,16 @@ Pick the mode from the user's invocation. Auto-detect an existing `docs/ai/` to
|
|
|
111
111
|
- **`/agent-workflow-kit setup [backend]`** — the **link-only**, opt-in companion to `backends`: place the bundled bridge skill + link its wrappers onto `PATH`. **In-agent only** — `init` (npx) never places bridges. The binary install + the interactive subscription login stay **manual** (it prints the exact commands); idempotent; refuses to clobber a non-symlink; never commits, never runs a subscription CLI.
|
|
112
112
|
- **`/agent-workflow-kit status`** — read-only view of the **whole family**: which members (kit / memory / engine / the two bridges) are installed, at what version, and — in a project — what is deployed (`docs/ai`, the version stamps, the hidden-mode fence). Never writes, never commits, never runs a subscription CLI.
|
|
113
113
|
- **`/agent-workflow-kit recipes`** — read-only **orchestration advisor**: present the four recipes (Solo / Reviewed / Council / Delegated) over the bridges' role vocabulary, plan + recommend one for the current environment, and offer the choice. **The orchestrator executes the chosen recipe via the bridge skills and always commits** — the kit only surfaces/selects/plans it; it never executes a recipe, never runs a subscription CLI, never commits.
|
|
114
|
-
- **`/agent-workflow-kit procedures <activity>`** — read-only **activity-procedures advisor**: print the ordered steps of a named activity (`plan-authoring` / `plan-execution`) read **live** from the installed engine (`references/procedures.md`), and the **resolved effective recipe per slot** from the per-project `docs/ai/orchestration.json` (strict JSON; agent-writable via `set-recipe` or hand-edit) + backend readiness (default = Reviewed when a backend is ready, Council on request, slot-aware incl. Delegated; graceful default vs loud override degradation). A per-run `--override <slot>=<recipe>` overrides one slot. Composes with `recipes` (which stays read-only); never writes, never commits, never runs a subscription CLI.
|
|
114
|
+
- **`/agent-workflow-kit procedures <activity>`** — read-only **activity-procedures advisor**: print the ordered steps of a named activity (`plan-authoring` / `plan-execution`) read **live** from the installed engine (`references/procedures.md`), and the **resolved effective recipe per slot** from the per-project `docs/ai/orchestration.json` (strict JSON; agent-writable via `set-recipe` or hand-edit) + backend readiness (default = Reviewed when a backend is ready, Council on request, slot-aware incl. Delegated; graceful default vs loud override degradation) — plus, for every dispatched backend, the **full driving contract at the point of use**: the exact copy-pasteable invocation descriptor(s), the grounding levers (agy `--facts`/`--decided`), the round-2 `--continue` delta, and the guarded passthrough tiers, rendered verbatim from the bridge manifests (drift-guarded; each wrapper's `--help` prints the same contract). A per-run `--override <slot>=<recipe>` overrides one slot. Composes with `recipes` (which stays read-only); never writes, never commits, never runs a subscription CLI.
|
|
115
115
|
- **`/agent-workflow-kit set-recipe`** — the **config writer** for `docs/ai/orchestration.json`: turn the user's plain language into explicit `--set <activity>.<slot>=<recipe>` / `--unset` ops; the kit validates, merges, **previews by default**, and writes only on `--write` (deployment-gated, atomic, symlink/TOCTOU-safe). Resolves the effective recipe vs live readiness (degradation honesty on both paths). **Writes only `docs/ai/orchestration.json`; never runs a backend, never commits.** Hand-editing the file stays fully supported.
|
|
116
116
|
- **`/agent-workflow-kit uninstall`** — the **guarded teardown** companion to `init`/`setup`. Removes what they placed — installed skill dirs + the bridge wrappers — and, in a project, reverses the hidden-mode fence + the marker pre-commit hook. **Never deletes user-authored content**: it prints the exact `rm` for `docs/ai` / `AGENTS.md` and an **edit** for `.claude/settings.json` (the `includeCoAuthoredBy` key + any velocity `permissions.*`), for you to run by hand. `--dry-run` first, always; preflight-then-mutate; never commits.
|
|
117
117
|
- **`/agent-workflow-kit velocity`** — opt-in onboarding **velocity profile**: seed a fixed, audited **read-only** Claude Code allowlist into `.claude/settings.json` so routine read-only commands stop idling on approval prompts; 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.** `--dry-run` first.
|
|
118
118
|
|
|
119
|
-
### Version status & the two axes —
|
|
119
|
+
### Version status & the two axes — the internal routing check
|
|
120
120
|
|
|
121
121
|
**Safe-routing rule (which mode did the user invoke?).** Map the invocation token with `tools/commands.mjs` `routeInvocation`: a **known** subcommand → its mode; the **bare/empty** invocation → `bootstrap` — the one writer reachable without a token, and only on an undeployed project (if `docs/ai/` already exists, **ask upgrade-vs-bootstrap**, never overwrite); **any unrecognized/ambiguous** token → `help`, which is **read-only**. The invariant: **no unrecognized/garbage invocation ever triggers a write** (only an explicit known token or the acknowledged bare-bootstrap exception can). The mapping is unit-tested, so it is not left to interpretation.
|
|
122
122
|
|
|
123
|
-
Before acting, read `docs/ai/.workflow-version` (the project's stamp),
|
|
123
|
+
Before acting, read `docs/ai/.workflow-version` (the project's stamp) to decide the route — this is an **internal** routing decision, **not** a line you print to the user (the number itself is shown only per *Version disclosure*). Route:
|
|
124
124
|
|
|
125
125
|
- **absent** → bootstrap (a fresh deployment).
|
|
126
126
|
- **stamp < `1.3.0`** (the deployment-lineage head) → `upgrade`.
|
|
@@ -172,18 +172,22 @@ the welcome mat **composes signals already gathered** (the version block's notes
|
|
|
172
172
|
line), not a fresh helper call. Present everything in the user's conversational language; never paste
|
|
173
173
|
the JSON or any internal field name.
|
|
174
174
|
|
|
175
|
-
**
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
**Success state — the happy path never leads with a structure number.** No happy-path report surfaces
|
|
176
|
+
the project's internal `docs/ai` structure version, the stamp filename, or the internal versioning
|
|
177
|
+
vocabulary — that number is inert here and only confuses; it belongs to *Version disclosure* (below).
|
|
178
|
+
Frame the success itself plainly, in the **user's conversational language** (never hardcode a phrase):
|
|
179
|
+
- a **zero-diff no-op `upgrade`** (step 4) → **settings already current — no update is required**
|
|
180
|
+
(illustrative tone for a Russian-speaking user, an example of the meaning, not a literal string to
|
|
181
|
+
embed: *«Настройки уже актуальны — обновление не требуется»*);
|
|
182
|
+
- a **fresh `bootstrap`** → its normal "deployed and ready" success, minus the number.
|
|
183
|
+
|
|
184
|
+
**Version block — the installed package versions, fed from `--json`** (the `docs/ai` structure version
|
|
185
|
+
is shown on demand only — see *Version disclosure* below, **not** here):
|
|
178
186
|
- **`Installed on this machine — package versions:`** `kit <v> · memory <v> · engine <v> ·
|
|
179
187
|
codex-bridge <v> · antigravity-bridge <v>` — one per `installed[]` entry, labelled by its `display`
|
|
180
188
|
and showing its `version`, or, when there is no version, the plain phrase for its `state` (map
|
|
181
189
|
below). Append any `installed[].notes` **in plain words** (e.g. the memory-behind refresh+restart
|
|
182
190
|
line).
|
|
183
|
-
- **The two axes are different — say so even when the numbers coincide:** an installed *package*
|
|
184
|
-
version (e.g. engine `1.3.0`) is a package number, **not** the deployment-structure version on line
|
|
185
|
-
one (which is also `1.3.0` today). The head advances only when the deployed `docs/ai` structure
|
|
186
|
-
changes, so a higher package number on npm/GitHub is **not** a newer deployment.
|
|
187
191
|
|
|
188
192
|
**`state` → plain language** (map the envelope's `installed[].state` token; never show the raw token):
|
|
189
193
|
`installed` → its version · `absent` → "not installed" · `other-tool` → "a different tool occupies
|
|
@@ -214,6 +218,29 @@ backend-status line — no new helper call) in this priority order:
|
|
|
214
218
|
|
|
215
219
|
Keep it compact — a few short lines, plain language, no kit-internal terms.
|
|
216
220
|
|
|
221
|
+
### Version disclosure — the `docs/ai` structure version, on demand only
|
|
222
|
+
|
|
223
|
+
The deployment carries an internal **`docs/ai` structure version** (the envelope's `deploymentHead`) —
|
|
224
|
+
the number `upgrade` compares the project's stamp against to decide whether a migration is due. The
|
|
225
|
+
happy path deliberately **hides** it: a user cannot act on it, and because it advances far more slowly
|
|
226
|
+
than the published package version, it reads as *"why is this smaller than what GitHub shows?"* Surface
|
|
227
|
+
it in exactly **three** places, and nowhere else:
|
|
228
|
+
1. the **never-downgrade STOP** (*Mode: upgrade* step 2) — the stamp is ahead of what this kit knows,
|
|
229
|
+
so the number IS the message;
|
|
230
|
+
2. the **explicit version-status view** (*Mode: status*) the user deliberately opens;
|
|
231
|
+
3. when the **user explicitly asks** about versions.
|
|
232
|
+
|
|
233
|
+
When you show it, **name what it versions — "the `docs/ai` structure version"** (render that meaning in
|
|
234
|
+
the user's conversational language) — **never** "lineage head", "deployment head", or any raw internal
|
|
235
|
+
token. Pair it with **one plain-language line** telling the two axes apart, on demand only:
|
|
236
|
+
|
|
237
|
+
> the number your project carries versions its `docs/ai` **structure**; the (usually larger) number on
|
|
238
|
+
> npm/GitHub is the **tool's own package version** — the two advance independently, so a bigger package
|
|
239
|
+
> number is **not** a newer deployment.
|
|
240
|
+
|
|
241
|
+
**Never** print this two-axes line on a successful equal-head exit — only at the STOP, the status view,
|
|
242
|
+
or on an explicit ask.
|
|
243
|
+
|
|
217
244
|
### Mode: help
|
|
218
245
|
|
|
219
246
|
Read-only. The single discoverable **command index** — it answers *"what can `/agent-workflow-kit` do, and which commands change things?"* It **never writes, never commits, and never runs a subscription CLI**.
|
|
@@ -273,7 +300,7 @@ Fill strategy:
|
|
|
273
300
|
### Mode: upgrade
|
|
274
301
|
|
|
275
302
|
1. Read `docs/ai/.workflow-version` (the project's stamped lineage). If missing, treat as a pre-versioned deployment and offer to re-bootstrap conservatively.
|
|
276
|
-
2. **Never-downgrade gate — FIRST, before any write.** Compare the stamp to the **deployment-lineage head** (`1.3.0` — NOT this kit's package version). If the stamp is **greater than the head** or unparseable → **STOP and report**; do not touch a newer / unknown deployment at all (not even the methodology slot).
|
|
303
|
+
2. **Never-downgrade gate — FIRST, before any write.** Compare the stamp to the **deployment-lineage head** (`1.3.0` — NOT this kit's package version). If the stamp is **greater than the head** or unparseable → **STOP and report**; do not touch a newer / unknown deployment at all (not even the methodology slot). This STOP is one of the few places the number is actionable (*Version disclosure*): show the user **the `docs/ai` structure version** their deployment carries versus the one this kit expects, plus the plain one-line two-axes note — naming it the structure version, **never** "lineage head".
|
|
277
304
|
3. **Reconcile the bounded pointers — stamp-independent, BEFORE the equal-head short-circuit.** Reached only when the stamp **≤ head**. Run `node ${CLAUDE_SKILL_DIR}/tools/inject-methodology.mjs reconcile <project>/AGENTS.md`. ONE call reconciles **two** pointers — the **workflow-methodology** pointer and, right below it, the **orchestration-recipes** pointer (Solo / Reviewed / Council / Delegated, routing to `/agent-workflow-kit recipes`) — and is filled on **every** upgrade, idempotently (zero-diff when both are already present + filled), so even a legacy / current **`1.3.0`** deployment gains them **without a lineage-head bump or a migration** (the deployment-lineage head stays `1.3.0`; the `agent-workflow-memory` **package** template did get a docs-only headroom trim for the second pointer, but no deployed-`docs/ai` structure changed). Per slot it inserts an empty pair at its anchor if absent, preserves a customized pair verbatim, and STOPs (never edits) on a malformed pair, a missing / duplicate anchor, or **when a fill is needed but the installed `agent-workflow-engine` is absent/invalid** (both fragments are read live from it — see the distinct outcomes below).
|
|
278
305
|
|
|
279
306
|
**Classify the exit — there are THREE non-zero exits + one soft in-band skip; handle each differently:**
|
|
@@ -291,13 +318,13 @@ Fill strategy:
|
|
|
291
318
|
**Orchestration config ensure (seed-or-refresh) — stamp-independent, same gate, BEFORE the equal-head short-circuit.** Ensure `docs/ai/orchestration.json` exists **and its onboarding note is current**: **create it from the template if missing**; **if it already exists, preserve every activity/slot the user set, and refresh ONLY the `_README` note when the existing one still matches a known prior canonical** — the tested `refreshIfCanonical` / `refreshReadme` in `tools/orchestration-config.mjs` is the source of truth for that decision (normalize CRLF/whitespace before comparing; a *customized* `_README` is preserved verbatim; a *malformed* existing config is **preserved + a loud warning**, never clobbered or silently skipped). The current note points at `/agent-workflow-kit set-recipe` (the config is now agent-writable — no more "never written for you"). **The refresh helper is kit-owned** — in the **delegated** path memory only seeds/preserves the file (memory upgrade step 2) and the **kit** then applies the `_README` refresh; in the **fallback** path the kit seeds-or-refreshes directly from `${CLAUDE_SKILL_DIR}/references/templates/orchestration.json`. (Memory stays standalone — it never depends on this helper.) Like the pointer slots + the footprint reconcile, this reaches an equal-head (`1.3.0`) deployment **without a lineage-head bump or a migration file** (it is a `.json`, inherently outside the docs cap-validator). Report it in the step 4 / step 8 success report (config *seeded* / *note refreshed* / *already current* / *customized — preserved*).
|
|
292
319
|
4. **Equal-head exit — a real successful-exit report, not a bare stop.** If the stamp **equals** the head, the lineage is up to date — but step 3 (the methodology-slot **and** hidden-mode footprint reconciles) ran first and may have changed things, so this is a proper exit report, not a no-op:
|
|
293
320
|
- **Report step 3's outcome in plain language** — for **each** pointer (workflow-methodology and orchestration-recipes) whether it was *added*, was *already present* (nothing changed), or was *skipped because the entry point is over its line limit* (the cap-refusal soft-skip from step 3, with its reason); whether the `docs/ai/orchestration.json` config was *seeded* (created from the template), had its onboarding note *refreshed*, was *already current*, or carried a *customized note that was preserved* (a user edit is never clobbered); and, for a hidden deployment, whether the hidden-mode footprint was *moved to project-local*, was *already project-local* (nothing changed), or needed a question (ambiguous visibility / a leftover machine-wide block). Plain wording only — never the reconcile/slot/anchor/marker terms (Gotcha: never leak kit internals).
|
|
294
|
-
- **
|
|
321
|
+
- **Never surface the structure number on this exit.** Whatever step 3 did, do **not** recite the `docs/ai` structure version, the internal versioning vocabulary, or the two-axes note here — the number is inert on an equal-head exit; it belongs to *Version disclosure* (shown at the never-downgrade STOP, the explicit status view, or on an explicit ask). Frame the success itself per the final bullet: if step 3 changed anything, say **what changed** in plain human terms; only a pure zero-diff no-op is *settings already current — no update needed*.
|
|
295
322
|
- **Print the report footer** in the canonical order (version block → one-line backend-status line → welcome mat — the shared contracts above; rendered from the helpers, same host-can't-run skip-with-reason). The welcome mat closes on **one** caveat-aware next step (a behind member first, else `setup` / `recipes` / `velocity`).
|
|
296
|
-
- **Then ask before committing — never auto-commit.** If step 3 added the slot (or anything else changed), report it and ask. If step 3 was a pure zero-diff no-op and nothing else changed,
|
|
323
|
+
- **Then ask before committing — never auto-commit.** If step 3 added the slot (or anything else changed), report it and ask. If step 3 was a pure zero-diff no-op and nothing else changed, give the plain **settings already current — no update needed** message (the *Success state* contract) and still print the read-only version block (installed package versions) + backend line — but **no `docs/ai` structure version and no two-axes note** (nothing changed, so the number is inert here).
|
|
297
324
|
5. Show the relevant `${CLAUDE_SKILL_DIR}/CHANGELOG.md` diff (entries newer than the project's stamp).
|
|
298
325
|
6. Apply `${CLAUDE_SKILL_DIR}/migrations/<version>-<slug>.md` in **semver order**, only those newer than the project's stamp. Migrations are **idempotent** — safe to re-run.
|
|
299
326
|
7. Reconcile drift: add any kernel files/scripts the project is missing; never clobber project-authored content (their `decisions.md`, `known_issues.md`, page specs stay). Any user question a migration raises follows the same rule as bootstrap — **structured multiple-choice where supported** (`AskUserQuestion` in Claude Code), otherwise prose. If `AGENTS.md` has no *Communication language* block (pre-1.1.0 deployment), **ask the user their conversational language** and insert the block — see `migrations/1.1.0-communication-language.md`. If it has no *Attribution* block (pre-1.2.0 deployment), **ask whether the agent may attribute work to itself / AI** and insert the block (defaulting to `off`) — see `migrations/1.2.0-agent-attribution.md`.
|
|
300
|
-
8. Re-stamp `docs/ai/.workflow-version` to the **deployment-lineage head** (`1.3.0`, not the package version
|
|
327
|
+
8. Re-stamp `docs/ai/.workflow-version` to the **deployment-lineage head** (`1.3.0`, not the package version — mechanics unchanged: the atomic write to the stamp file). In the report, **describe what the upgrade changed in plain human terms** — which parts of their `docs/ai` are now different (the migrations that ran) — rather than reciting a version number; **omit the raw structure number**, and do **not** print the two-axes note here (it belongs to *Version disclosure*, on demand only). Then **print the report footer** in the canonical order (version block → one-line backend-status line → welcome mat — the shared contracts above; rendered from the helpers, same host-can't-run skip-with-reason; the welcome mat closes on one caveat-aware next step). Then **ask before committing**.
|
|
301
328
|
|
|
302
329
|
### Mode: backends
|
|
303
330
|
|
|
@@ -340,7 +367,7 @@ Read-only. The **single answer to "versions + deployment + settings + bridges"**
|
|
|
340
367
|
|
|
341
368
|
Run `node ${CLAUDE_SKILL_DIR}/tools/family-registry.mjs --json [--dir <project>]` and render it **compact**, in the user's conversational language — **never paste the JSON or any internal field name** (no-leak rule). Map the **`installed[].state`** token via the value→plain-language map under *The version block + welcome mat* (the `visibility` and wrapper states have their own phrasings, below). Present, each area on its own line(s), routing detail to its domain mode:
|
|
342
369
|
|
|
343
|
-
1. **Versions — a status-only render from `installed[]` + `deploymentHead`** (this is **NOT** the shared notes-based version block — see the separation note below): the
|
|
370
|
+
1. **Versions — a status-only render from `installed[]` + `deploymentHead`** (this is **NOT** the shared notes-based version block — see the separation note below): the **`docs/ai` structure version** (named as such, never "lineage head"), then each member by its `display` showing its `version` (or, when there is no version, the plain phrase for its `state`, mapped above), plus the two-axes disambiguation. **Freshness comes from `installed[].refresh`, not from `notes`:** for each member whose **`refresh.behind`** is `true`, show a **localized "needs refresh"** label and the **verbatim `refresh.recommend`** command **exactly once** (the command/package name stays source-language; **do not also paste the English `notes` caveats** — `refresh.recommend` is the single source of the recovery step, so the command is never duplicated on this surface). Lead with a one-line **headline count** derived from `installed[].state` + `refresh.behind` (e.g. *"5 members installed · 1 needs a refresh"*).
|
|
344
371
|
|
|
345
372
|
> **Status reads `refresh`; the shared version block + the bootstrap/upgrade footers stay `notes`-based (unchanged this release).** *Mode: status* has its OWN status-only render (above), keyed on `installed[].refresh.behind` / `refresh.recommend`. The shared **version block** (under *The version block + welcome mat*) and the bootstrap (step 11) + every upgrade (steps 4 / 8) report footer still consume `installed[].notes` verbatim — that wiring is deliberately **untouched** here (their migration onto `refresh` is deferred). Do not rewrite those footers onto `refresh`.
|
|
346
373
|
2. **Deployment (`--dir`)** (from `project`): whether `docs/ai/` is deployed + the deploy stamps by `display`; and **visibility** — render `project.visibility.state` in **user-safe words only**: *visible (tracked)* / *hidden (git-ignored, local-only)* / *unclear (uncommitted or partially set up)* — **never** the words "hidden fence" or any marker term. A `visibility.error` → surface it plainly.
|
|
@@ -351,7 +378,7 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/family-registry.mjs --json [--dir <project>]
|
|
|
351
378
|
- Any area's **`error`** field → surface it **loudly** in plain language; the rest of `status` still renders (never a crash).
|
|
352
379
|
4. **Bridges (host, one line)** (from `bridges[]`): per bridge — readiness + wrapper PATH-presence; render each wrapper's `state` as *on PATH* (`present`) / *not on PATH* (`missing`) / *couldn't check* (`unknown`) (detail → `/agent-workflow-kit backends` / `setup`). **No default-model claim.** "credentials present" means a marker file exists, not a live login.
|
|
353
380
|
|
|
354
|
-
Restate the **two-axes honesty** — an installed *package* version is not
|
|
381
|
+
Restate the **two-axes honesty** — an installed *package* version is not the project's **`docs/ai` structure version** (see *Version disclosure*); the installed version is whatever is on disk under `~/.claude/skills/…`, so a stale install shows its real (older) version, honestly. A host that can't run the helper → **skip with the concrete reason** (the helper-failure contract), never silently.
|
|
355
382
|
|
|
356
383
|
**Invariants:** read-only · never writes · never commits · never runs a subscription CLI · plain language only, no leaked internal terms.
|
|
357
384
|
|
|
@@ -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.1.0'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# antigravity-cli-bridge
|
|
@@ -44,6 +44,41 @@
|
|
|
44
44
|
# staging dir and pass it via --add-dir (re-enables Issue-001 stall risk)
|
|
45
45
|
set -euo pipefail
|
|
46
46
|
|
|
47
|
+
# --- --help / -h (pre-preflight: no agy, no login, no git tree needed) ---------
|
|
48
|
+
# Keyed ONLY on the FIRST argument — never a scan of all args (uniform rule across
|
|
49
|
+
# the four wrappers). Drift-guarded against capability.json roles.review.contract.
|
|
50
|
+
case "${1:-}" in
|
|
51
|
+
--help|-h)
|
|
52
|
+
cat <<'HELP'
|
|
53
|
+
agy-review — grounded read-only ADVISORY review by Google's Antigravity CLI (agy; subscription-only).
|
|
54
|
+
|
|
55
|
+
Usage:
|
|
56
|
+
agy-review code [--facts @f] [--decided @f] [--focus "…"] [extra focus…]
|
|
57
|
+
agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus "…"]
|
|
58
|
+
agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus "…"]
|
|
59
|
+
|
|
60
|
+
Flags:
|
|
61
|
+
--facts @f — verified facts the review runs AGAINST (omit ⇒ loud ungrounded-review warning)
|
|
62
|
+
--decided @f — already-decided / already-addressed list; do NOT re-raise (anti-circling; the round-2 payload)
|
|
63
|
+
--focus "…" — extra focus (repeatable; code mode also takes trailing focus words)
|
|
64
|
+
|
|
65
|
+
Grounding:
|
|
66
|
+
grounded review — agy reads NOTHING by default, an ungrounded review GUESSES:
|
|
67
|
+
--facts @f = the verified facts to review AGAINST; --decided @f = decisions
|
|
68
|
+
already made, do NOT re-raise (anti-circling)
|
|
69
|
+
|
|
70
|
+
Round-2 / resume:
|
|
71
|
+
agy-review --continue [--decided @f] [--focus "…"]
|
|
72
|
+
agy-review --conversation <id> [--decided @f] [--focus "…"]
|
|
73
|
+
(a continuation sends a small delta — agy holds the artifact server-side; --facts is invalid on a continuation)
|
|
74
|
+
|
|
75
|
+
Closed grammar: unknown flags are rejected; no '--' passthrough (the only escapes are AGY_PROBE=1 and AGY_REVIEW_ALLOW_ADDDIR=1).
|
|
76
|
+
Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
|
|
77
|
+
HELP
|
|
78
|
+
exit 0
|
|
79
|
+
;;
|
|
80
|
+
esac
|
|
81
|
+
|
|
47
82
|
DEFAULT_AGY_REVIEW_MODEL="Gemini 3.1 Pro (High)"
|
|
48
83
|
# `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
|
|
49
84
|
AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
|
|
@@ -439,3 +439,189 @@ describe('agy-review.sh — subdir invocation is repo-complete (13)', () => {
|
|
|
439
439
|
assert.match(r.prompt, /SUBDIR_RELATIVE_FACT/, 'a relative --facts path resolves against the invocation cwd, before the cd');
|
|
440
440
|
});
|
|
441
441
|
});
|
|
442
|
+
|
|
443
|
+
// ── driving contract: --help ⟷ manifest ⟷ real arg-parsing (drift-guarded) ─────
|
|
444
|
+
// The manifest roles.review.contract is the single machine-readable source of the
|
|
445
|
+
// driving contract; these suites pin (a) --help renders it verbatim (set-EQUALITY,
|
|
446
|
+
// both directions), (b) the wrapper's REAL parser arms equal the declared sets
|
|
447
|
+
// (source-level reverse guard), (c) each declared mode/flag is really accepted and
|
|
448
|
+
// the CLOSED grammar really rejects an invented flag. Helpers are inline — each
|
|
449
|
+
// bridge test file stays standalone (mirror byte-equality).
|
|
450
|
+
|
|
451
|
+
const MANIFEST = JSON.parse(readFileSync(join(HERE, '..', 'capability.json'), 'utf8'));
|
|
452
|
+
const REVIEW_CONTRACT = MANIFEST.roles.review.contract;
|
|
453
|
+
const norm = (s) => s.replace(/\s+/g, ' ').trim();
|
|
454
|
+
const setEq = (got, want, msg) => assert.deepEqual([...got].sort(), [...want].sort(), msg);
|
|
455
|
+
const leadingFlag = (descriptor) => {
|
|
456
|
+
const m = norm(descriptor).match(/(^|\s)(--[a-z-]+)/);
|
|
457
|
+
assert.ok(m, `descriptor "${descriptor}" carries no --flag token`);
|
|
458
|
+
return m[2];
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// Run `--help`/-h with PATH stripped of codex/agy/git, from a non-git cwd —
|
|
462
|
+
// proving the short-circuit fires BEFORE every preflight.
|
|
463
|
+
const runHelp = (arg) => {
|
|
464
|
+
const root = mkdtempSync(join(tmpdir(), 'agy-review-help-'));
|
|
465
|
+
const nongit = join(root, 'nongit');
|
|
466
|
+
mkdirSync(nongit, { recursive: true });
|
|
467
|
+
const path = makePathWithout(root, ['codex', 'agy', 'git']);
|
|
468
|
+
const r = spawnSync('bash', [WRAPPER, arg], {
|
|
469
|
+
cwd: nongit, encoding: 'utf8', timeout: 15000, env: { HOME: root, PATH: path },
|
|
470
|
+
});
|
|
471
|
+
rmSync(root, { recursive: true, force: true });
|
|
472
|
+
return r;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// The lines of a labelled --help section (header line → the next blank line).
|
|
476
|
+
const helpSection = (text, header) => {
|
|
477
|
+
const lines = text.split('\n');
|
|
478
|
+
const i = lines.findIndex((l) => l.trim() === header);
|
|
479
|
+
assert.notEqual(i, -1, `--help must carry a "${header}" section`);
|
|
480
|
+
const out = [];
|
|
481
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
482
|
+
if (lines[j].trim() === '') break;
|
|
483
|
+
out.push(lines[j].trim());
|
|
484
|
+
}
|
|
485
|
+
return out;
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
// Source-level parser-arm extractor — the reverse drift guard. Scans ONLY `case`
|
|
489
|
+
// statements whose SUBJECT is a CLI-argument variable (allowlisted), skipping
|
|
490
|
+
// heredoc bodies (a heredoc may carry non-CLI `case` arms — e.g. codex-exec's
|
|
491
|
+
// git-shim). Returns Map(subject → [raw arm label, …]) in source order.
|
|
492
|
+
const ARG_SUBJECTS = new Set(['"$mode"', '"${1:-}"', '"$1"', '"$_arg"']);
|
|
493
|
+
const extractArgCaseArms = (source) => {
|
|
494
|
+
const arms = new Map();
|
|
495
|
+
const stack = [];
|
|
496
|
+
let heredoc = null;
|
|
497
|
+
for (const raw of source.split('\n')) {
|
|
498
|
+
if (heredoc) {
|
|
499
|
+
if (raw.trim() === heredoc) heredoc = null;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (raw.trimStart().startsWith('#')) continue; // a comment line may carry a stray ')'
|
|
503
|
+
const hd = raw.match(/<<-?\s*['"]?([A-Za-z_][A-Za-z0-9_]*)['"]?/);
|
|
504
|
+
if (hd) { heredoc = hd[1]; continue; }
|
|
505
|
+
const cs = raw.match(/^\s*case\s+(\S+)\s+in\b/);
|
|
506
|
+
if (cs) { stack.push(cs[1]); continue; }
|
|
507
|
+
if (/^\s*esac\b/.test(raw)) { stack.pop(); continue; }
|
|
508
|
+
if (stack.length && ARG_SUBJECTS.has(stack[stack.length - 1])) {
|
|
509
|
+
const arm = raw.match(/^\s*([^)(\s][^)(]*)\)/);
|
|
510
|
+
if (arm) {
|
|
511
|
+
const subject = stack[stack.length - 1];
|
|
512
|
+
if (!arms.has(subject)) arms.set(subject, []);
|
|
513
|
+
arms.get(subject).push(arm[1].trim());
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return arms;
|
|
518
|
+
};
|
|
519
|
+
const splitArms = (labels) => (labels ?? []).flatMap((l) => l.split('|'));
|
|
520
|
+
|
|
521
|
+
describe('agy-review.sh — --help contract (manifest-pinned)', () => {
|
|
522
|
+
it('--help and -h exit 0 pre-preflight (no agy, no git)', () => {
|
|
523
|
+
for (const arg of ['--help', '-h']) {
|
|
524
|
+
const r = runHelp(arg);
|
|
525
|
+
assert.equal(r.status, 0, `${arg}: ${r.stderr}`);
|
|
526
|
+
assert.match(r.stdout, /Usage:/, `${arg} prints the contract to stdout`);
|
|
527
|
+
assert.equal(r.stderr, '', `${arg} prints nothing to stderr`);
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('Usage set-EQUALS the manifest invocation descriptors (both directions)', () => {
|
|
532
|
+
const help = runHelp('--help').stdout;
|
|
533
|
+
const got = helpSection(help, 'Usage:').filter((l) => l.startsWith('agy-review')).map(norm);
|
|
534
|
+
assert.ok(REVIEW_CONTRACT.invocations.length > 0, 'manifest invocations must be non-empty');
|
|
535
|
+
setEq(got, REVIEW_CONTRACT.invocations.map(norm), 'help Usage ⟷ manifest invocations');
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('Flags set-EQUALS the manifest flag descriptors (both directions)', () => {
|
|
539
|
+
const help = runHelp('--help').stdout;
|
|
540
|
+
const got = helpSection(help, 'Flags:').filter((l) => l.startsWith('--')).map(norm);
|
|
541
|
+
assert.ok(REVIEW_CONTRACT.flags.length > 0, 'manifest flags must be non-empty');
|
|
542
|
+
setEq(got, REVIEW_CONTRACT.flags.map(norm), 'help Flags ⟷ manifest flags');
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('Grounding renders the manifest grounding note verbatim', () => {
|
|
546
|
+
const help = runHelp('--help').stdout;
|
|
547
|
+
assert.equal(norm(helpSection(help, 'Grounding:').join(' ')), norm(REVIEW_CONTRACT.grounding));
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('Round-2 / resume set-EQUALS the manifest continue descriptors', () => {
|
|
551
|
+
const help = runHelp('--help').stdout;
|
|
552
|
+
const got = helpSection(help, 'Round-2 / resume:').filter((l) => l.startsWith('agy-review')).map(norm);
|
|
553
|
+
assert.ok(REVIEW_CONTRACT.continue.length > 0, 'manifest continue must be non-empty');
|
|
554
|
+
setEq(got, REVIEW_CONTRACT.continue.map(norm), 'help continue ⟷ manifest continue');
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
describe('agy-review.sh — source-level reverse guard (parser arms ⟷ manifest)', () => {
|
|
559
|
+
const arms = extractArgCaseArms(readFileSync(WRAPPER, 'utf8'));
|
|
560
|
+
|
|
561
|
+
it('the real mode arms equal the manifest modes (adding a mode without the manifest fails here)', () => {
|
|
562
|
+
// Deliberately a UNION over every `case "$mode"` in the wrapper (the CLI dispatch AND the
|
|
563
|
+
// emit_artifact renderer): the union can only be conservative — a mode added to EITHER case
|
|
564
|
+
// without the manifest goes red; no renderer-only arm can make a missing manifest entry green.
|
|
565
|
+
const modes = splitArms(arms.get('"$mode"')).filter((a) => a !== '*');
|
|
566
|
+
assert.ok(MANIFEST.roles.review.modes.length > 0, 'manifest modes must be non-empty');
|
|
567
|
+
setEq(new Set(modes), MANIFEST.roles.review.modes, 'parser mode arms ⟷ manifest modes');
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('the real flag arms equal the manifest flag set (closed grammar; catch-alls excluded)', () => {
|
|
571
|
+
const flagArms = splitArms(arms.get('"$1"')).filter((a) => !['--', '--*', '*'].includes(a));
|
|
572
|
+
const declared = REVIEW_CONTRACT.flags.map(leadingFlag);
|
|
573
|
+
assert.ok(declared.length > 0, 'manifest flag set must be non-empty');
|
|
574
|
+
setEq(new Set(flagArms), new Set(declared), 'parser flag arms ⟷ manifest flags');
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it('the first-arg entrypoints are exactly --help/-h + the manifest continue flags', () => {
|
|
578
|
+
const declared = REVIEW_CONTRACT.continue.map(leadingFlag);
|
|
579
|
+
assert.ok(declared.length > 0, 'manifest continue set must be non-empty');
|
|
580
|
+
setEq(new Set(splitArms(arms.get('"${1:-}"'))), new Set(['--help', '-h', ...declared]));
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
describe('agy-review.sh — declared contract is really accepted (forward guard)', () => {
|
|
585
|
+
it('every manifest mode runs green', () => {
|
|
586
|
+
const drive = {
|
|
587
|
+
code: () => ['code', '--facts', 'f'],
|
|
588
|
+
plan: (sb) => { writeFileSync(join(sb.repo, 'p.md'), '# p\n'); return ['plan', 'p.md', '--facts', 'f']; },
|
|
589
|
+
diff: (sb) => { writeFileSync(join(sb.repo, 'c.diff'), 'diff body\n'); return ['diff', 'c.diff', '--facts', 'f']; },
|
|
590
|
+
};
|
|
591
|
+
for (const mode of MANIFEST.roles.review.modes) {
|
|
592
|
+
assert.ok(drive[mode], `no test drive for manifest mode "${mode}" — add one`);
|
|
593
|
+
const sb = makeSandbox();
|
|
594
|
+
const r = run(sb, { args: drive[mode](sb) });
|
|
595
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
596
|
+
assert.equal(r.status, 0, `mode ${mode}: ${r.stderr}`);
|
|
597
|
+
assert.equal(r.invoked, true, `mode ${mode} must reach agy`);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it('every manifest flag is accepted in code mode', () => {
|
|
602
|
+
for (const descriptor of REVIEW_CONTRACT.flags) {
|
|
603
|
+
const flag = leadingFlag(descriptor);
|
|
604
|
+
const sb = makeSandbox();
|
|
605
|
+
const r = run(sb, { args: ['code', flag, 'f'] });
|
|
606
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
607
|
+
assert.equal(r.status, 0, `${flag}: ${r.stderr}`);
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('an invented flag is rejected (closed grammar negative)', () => {
|
|
612
|
+
const sb = makeSandbox();
|
|
613
|
+
const r = run(sb, { args: ['code', '--facts', 'f', '--bogus-flag'] });
|
|
614
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
615
|
+
assert.equal(r.status, 2);
|
|
616
|
+
assert.match(r.stderr, /unknown flag '--bogus-flag'/);
|
|
617
|
+
assert.equal(r.invoked, false, 'an unknown flag must not spend a run');
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('--help NOT in first position is an unknown flag, never an intercepted help', () => {
|
|
621
|
+
const sb = makeSandbox();
|
|
622
|
+
const r = run(sb, { args: ['code', '--facts', 'f', '--help'] });
|
|
623
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
624
|
+
assert.equal(r.status, 2, 'help is keyed on the FIRST argument only');
|
|
625
|
+
assert.doesNotMatch(r.stdout, /Usage:/);
|
|
626
|
+
});
|
|
627
|
+
});
|
|
@@ -34,6 +34,29 @@
|
|
|
34
34
|
# # passthrough agy flags (future flows)
|
|
35
35
|
set -euo pipefail
|
|
36
36
|
|
|
37
|
+
# --- --help / -h (pre-preflight: no agy, no login needed) ----------------------
|
|
38
|
+
# Keyed ONLY on the FIRST argument — never a scan of all args, else a passthrough
|
|
39
|
+
# payload like `agy-run "prompt" -- --help` would be intercepted. agy-run is the
|
|
40
|
+
# probe role (not dispatched by any activity slot), so this help is authored here
|
|
41
|
+
# — not manifest-pinned (candidate C only).
|
|
42
|
+
case "${1:-}" in
|
|
43
|
+
--help|-h)
|
|
44
|
+
cat <<'HELP'
|
|
45
|
+
agy-run — thin, flow-agnostic wrapper around Google's Antigravity CLI (agy; subscription-only, hard wall-clock cap).
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
agy-run "your prompt"
|
|
49
|
+
echo "your prompt" | agy-run -
|
|
50
|
+
agy-run @path/to/prompt.md
|
|
51
|
+
agy-run <prompt|-|@file> -- <extra agy flags...>
|
|
52
|
+
|
|
53
|
+
Environment: AGY_MODEL (exact display string from `agy models`; empty ⇒ agy's settings.json), AGY_TIMEOUT / AGY_HARD_TIMEOUT (duration strings), AGY_MAX_PROMPT_BYTES (single-argv byte ceiling; the override only lowers it).
|
|
54
|
+
Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
|
|
55
|
+
HELP
|
|
56
|
+
exit 0
|
|
57
|
+
;;
|
|
58
|
+
esac
|
|
59
|
+
|
|
37
60
|
# 1. Make `agy` findable even when ~/.bashrc was not sourced.
|
|
38
61
|
export PATH="$HOME/.local/bin:$PATH"
|
|
39
62
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, it } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtempSync, mkdirSync, writeFileSync, chmodSync, rmSync, existsSync } from 'node:fs';
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, chmodSync, rmSync, existsSync, readdirSync, symlinkSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join, dirname } from 'node:path';
|
|
5
|
+
import { join, dirname, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
8
|
|
|
@@ -152,3 +152,59 @@ describe('agy.sh — argv byte-ceiling guard (AGY_MAX_PROMPT_BYTES)', () => {
|
|
|
152
152
|
assert.equal(invoked, false, 'raising the ceiling past the OS limit must fail loud, not pass through to E2BIG');
|
|
153
153
|
});
|
|
154
154
|
});
|
|
155
|
+
|
|
156
|
+
// ── --help (candidate C only — the probe role is not dispatched by any activity
|
|
157
|
+
// slot, so this help is authored in the wrapper, NOT manifest-pinned; the lighter
|
|
158
|
+
// guard pins pre-preflight reachability + the documented usage forms). ──────────
|
|
159
|
+
|
|
160
|
+
// A PATH farm mirroring the real one MINUS the named binaries — so the reachability claim
|
|
161
|
+
// ("--help needs no agy/git") holds even on a host that has the real CLIs installed.
|
|
162
|
+
// Ported from agy-review.test.mjs (inline: each bridge test file stays standalone).
|
|
163
|
+
const makePathWithout = (root, exclude = []) => {
|
|
164
|
+
const skip = new Set(exclude);
|
|
165
|
+
const dir = mkdtempSync(join(root, 'nobin-'));
|
|
166
|
+
for (const d of (process.env.PATH || '').split(':').filter(Boolean)) {
|
|
167
|
+
let names;
|
|
168
|
+
try { names = readdirSync(d); } catch { continue; }
|
|
169
|
+
for (const name of names) {
|
|
170
|
+
if (skip.has(name)) continue;
|
|
171
|
+
const link = join(dir, name);
|
|
172
|
+
if (existsSync(link)) continue;
|
|
173
|
+
try { symlinkSync(resolve(d, name), link); } catch { /* dup / race — ignore */ }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return dir;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
describe('agy.sh — --help (pre-preflight, candidate C)', () => {
|
|
180
|
+
it('--help and -h exit 0 with NO agy on PATH and name the documented usage', () => {
|
|
181
|
+
for (const arg of ['--help', '-h']) {
|
|
182
|
+
// A bare HOME with no ~/.local/bin/agy stub AND a PATH farm stripped of agy/git —
|
|
183
|
+
// the help must not need the CLI even when the host has a real agy installed.
|
|
184
|
+
const home = mkdtempSync(join(tmpdir(), 'agy-help-'));
|
|
185
|
+
const r = spawnSync('bash', [WRAPPER, arg], {
|
|
186
|
+
env: { HOME: home, PATH: makePathWithout(home, ['agy', 'git']) },
|
|
187
|
+
encoding: 'utf8',
|
|
188
|
+
timeout: 15000,
|
|
189
|
+
});
|
|
190
|
+
rmSync(home, { recursive: true, force: true });
|
|
191
|
+
assert.equal(r.status, 0, `${arg}: ${r.stderr}`);
|
|
192
|
+
assert.equal(r.stderr, '', `${arg} prints nothing to stderr`);
|
|
193
|
+
assert.match(r.stdout, /Usage:/);
|
|
194
|
+
assert.match(r.stdout, /agy-run "your prompt"/);
|
|
195
|
+
assert.match(r.stdout, /agy-run @path\/to\/prompt\.md/);
|
|
196
|
+
assert.match(r.stdout, /agy-run <prompt\|-\|@file> -- <extra agy flags\.\.\.>/);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('--help after the -- separator is passthrough payload, never intercepted', () => {
|
|
201
|
+
const home = makeSandbox(RECORDING_STUB);
|
|
202
|
+
const sentinel = join(home, 'sentinel');
|
|
203
|
+
const r = runArgs(home, { args: ['prompt', '--', '--help'], env: { AGY_STUB_SENTINEL: sentinel } });
|
|
204
|
+
const invoked = existsSync(sentinel);
|
|
205
|
+
rmSync(home, { recursive: true, force: true });
|
|
206
|
+
assert.equal(r.status, 0, r.stderr);
|
|
207
|
+
assert.doesNotMatch(r.stdout, /Usage:/, 'help is keyed on the FIRST argument only');
|
|
208
|
+
assert.equal(invoked, true, 'the run must proceed to agy with the payload');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -3,10 +3,33 @@
|
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"name": "antigravity-cli-bridge",
|
|
5
5
|
"kind": "execution-backend",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.1.0",
|
|
7
7
|
"provides": ["review", "probe"],
|
|
8
8
|
"roles": {
|
|
9
|
-
"review": {
|
|
9
|
+
"review": {
|
|
10
|
+
"cmd": "agy-review",
|
|
11
|
+
"source": "bin/agy-review.sh",
|
|
12
|
+
"template": "references/review-prompt.md",
|
|
13
|
+
"modes": ["code", "plan", "diff"],
|
|
14
|
+
"output": "advisory",
|
|
15
|
+
"contract": {
|
|
16
|
+
"invocations": [
|
|
17
|
+
"agy-review code [--facts @f] [--decided @f] [--focus \"…\"] [extra focus…]",
|
|
18
|
+
"agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus \"…\"]",
|
|
19
|
+
"agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus \"…\"]"
|
|
20
|
+
],
|
|
21
|
+
"grounding": "grounded review — agy reads NOTHING by default, an ungrounded review GUESSES: --facts @f = the verified facts to review AGAINST; --decided @f = decisions already made, do NOT re-raise (anti-circling)",
|
|
22
|
+
"flags": [
|
|
23
|
+
"--facts @f — verified facts the review runs AGAINST (omit ⇒ loud ungrounded-review warning)",
|
|
24
|
+
"--decided @f — already-decided / already-addressed list; do NOT re-raise (anti-circling; the round-2 payload)",
|
|
25
|
+
"--focus \"…\" — extra focus (repeatable; code mode also takes trailing focus words)"
|
|
26
|
+
],
|
|
27
|
+
"continue": [
|
|
28
|
+
"agy-review --continue [--decided @f] [--focus \"…\"]",
|
|
29
|
+
"agy-review --conversation <id> [--decided @f] [--focus \"…\"]"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
},
|
|
10
33
|
"probe": { "cmd": "agy-run", "source": "bin/agy.sh", "output": "advisory" }
|
|
11
34
|
},
|
|
12
35
|
"detect": {
|