@sabaiway/agent-workflow-kit 1.25.0 → 1.26.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 +35 -0
- package/README.md +11 -7
- package/SKILL.md +52 -24
- package/bin/install.mjs +74 -29
- package/capability.json +1 -1
- package/package.json +1 -1
- package/tools/family-members.mjs +2 -1
- package/tools/family-registry.mjs +111 -13
- package/tools/labels.mjs +13 -0
- package/tools/recipes.mjs +56 -17
- package/tools/renderers.mjs +12 -1
- package/tools/semver-lite.mjs +23 -0
- package/tools/setup-backends.mjs +143 -6
- package/tools/view-model.mjs +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,41 @@ 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.26.0 — Deterministic bridge freshness & delivery; machine-composed status line; honest installer messaging
|
|
8
|
+
|
|
9
|
+
A **feature** release (ships the bundled bridges unchanged at **2.1.0**). One architecture across
|
|
10
|
+
four fixes: **the registry computes, the tools speak, the agent pastes** — no factual line on these
|
|
11
|
+
surfaces is agent-composed anymore.
|
|
12
|
+
|
|
13
|
+
- **Bridge freshness is now visible.** `family-registry` compares each placed bridge against the
|
|
14
|
+
kit-bundled mirror (both local files — nothing checks npm): behind → a plain caveat + the runnable
|
|
15
|
+
`/agent-workflow-kit setup` recommend + `refresh.behind:true` in `--json`, reaching the
|
|
16
|
+
bootstrap/upgrade footers and the welcome mat (priority 1 is now caveat-generic and quotes the
|
|
17
|
+
firing note's OWN recovery verbatim); uncheckable → an explicit unknown note (never "current",
|
|
18
|
+
never "behind"); zero-behind → the TOOL prints a checked-scope verdict (`all N checked members are
|
|
19
|
+
current` — any unknown blocks the all-current claim). New dependency-free `tools/semver-lite.mjs`.
|
|
20
|
+
- **Placed bridges refresh on `init` and `upgrade`.** A refresh-only driver in `setup-backends.mjs`
|
|
21
|
+
(`--refresh-placed`) refreshes proven-managed placed bridges and NEVER places an absent one —
|
|
22
|
+
placement stays opt-in via `/agent-workflow-kit setup`. `npx … init` calls it best-effort (a miss
|
|
23
|
+
is a loud warning + a recovery command composed from the resolved install target + exit 0;
|
|
24
|
+
`--no-bridges` opts out; win32 is a stated skip); `Mode: upgrade` runs it as a fourth
|
|
25
|
+
stamp-independent reconcile and pastes the output verbatim. **Never-downgrade:** a placed bridge
|
|
26
|
+
NEWER than the bundle is a stated keep + "update the kit", enforced at both the plan and the write
|
|
27
|
+
boundary (TOCTOU re-inspect at apply); an unparseable version is treated as legacy repair, stated.
|
|
28
|
+
- **The one-line backend status is machine-composed.** `tools/recipes.mjs --status-line` emits the
|
|
29
|
+
exact line (deterministic order, one alias table; additive `statusLine` in `--json`; strict argv —
|
|
30
|
+
an unknown flag exits loudly instead of masquerading as the human render); SKILL.md now says run
|
|
31
|
+
the tool and paste its line verbatim — the realistic example that once got echoed as fact is
|
|
32
|
+
replaced by an explicitly-placeholder template.
|
|
33
|
+
- **The installer speaks facts.** The final verb is keyed on the OBSERVED version comparison
|
|
34
|
+
(installed / updated / refreshed-the-already-current / downgraded-under-`--allow-downgrade`); the
|
|
35
|
+
same-version note states that the copy ran (a re-run repairs locally modified files) + a
|
|
36
|
+
CONDITIONAL `@latest` hint; the false "npx likely served a cached build" accusation is gone. One
|
|
37
|
+
message contract with the engine installer (engine `1.9.0`).
|
|
38
|
+
- Lens sync everywhere — "placed by `setup` (opt-in), refreshed by `init`/`upgrade` once placed" —
|
|
39
|
+
across SKILL.md, both READMEs and `family-members.mjs`, guarded by the new region+token
|
|
40
|
+
`test/init-refresh-lens.test.mjs` (non-vacuous, injected red→green proven).
|
|
41
|
+
|
|
7
42
|
## 1.25.0 — The bridge driving contract at the point of use (advisor render + wrapper `--help`)
|
|
8
43
|
|
|
9
44
|
A **feature** release (additive; ships the bundled bridges at **2.1.0**). An agent told to run a
|
package/README.md
CHANGED
|
@@ -139,8 +139,9 @@ npx @sabaiway/agent-workflow-kit@latest init
|
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
`init` installs/refreshes the skill at `~/.claude/skills/agent-workflow-kit/` and wires launchers for
|
|
142
|
-
any Claude Code / Codex / Devin Desktop it finds. It **does not** deploy into a project, and **
|
|
143
|
-
|
|
142
|
+
any Claude Code / Codex / Devin Desktop it finds. It **does not** deploy into a project, and **never
|
|
143
|
+
places** the optional bridges — **once placed** (by `/agent-workflow-kit setup`) it **refreshes** them
|
|
144
|
+
from its bundled copies (never a downgrade; skip with `--no-bridges`).
|
|
144
145
|
|
|
145
146
|
### 2. Deploy into a project — once per repo
|
|
146
147
|
|
|
@@ -202,11 +203,13 @@ refreshes the other npm core members so a returning `init` leaves **no stale cor
|
|
|
202
203
|
| `~/.claude/skills/agent-workflow-kit/` | the kit itself (refreshed on every `init`) |
|
|
203
204
|
| `~/.claude/skills/agent-workflow-memory/` | the **memory substrate**, refreshed via `npx @sabaiway/agent-workflow-memory@latest init` — **best-effort:** a failure is a **loud degraded success** (warning + the exact recovery command + exit 0), never silent; skip with `--no-memory` |
|
|
204
205
|
| `~/.claude/skills/agent-workflow-engine/` | the **methodology engine** the kit reads live, refreshed via `npx @sabaiway/agent-workflow-engine@latest init` — **required** (the live read STOPs without it); skip with `--no-engine` |
|
|
206
|
+
| `~/.claude/skills/{codex,antigravity}-cli-bridge/` | the placed **bridges**, refreshed from the kit's bundled copies (local files, no network) — **only if `/agent-workflow-kit setup` already placed them**: an absent bridge is never placed, a newer one never downgraded; skip with `--no-bridges` |
|
|
205
207
|
| `~/.codex/skills/agent-workflow-kit` | a symlink — only if you have Codex |
|
|
206
208
|
| `…/global_workflows/agent-workflow-kit.md` | a managed file — only if you have Devin Desktop |
|
|
207
209
|
|
|
208
|
-
The **execution-backend bridges** (`codex` / `agy`) are **
|
|
209
|
-
demand with `/agent-workflow-kit setup
|
|
210
|
+
The **execution-backend bridges** (`codex` / `agy`) are never first **placed** by `init` — set one up
|
|
211
|
+
on demand with `/agent-workflow-kit setup`; after that, a returning `init` keeps the placed copy
|
|
212
|
+
fresh (the table row above). Your other Codex skills and Devin Desktop workflows are
|
|
210
213
|
never touched. If one of those exact slots already holds a file the kit didn't write, it is **left
|
|
211
214
|
alone** and you're told — re-run with `--force` to replace it (the original is first copied to
|
|
212
215
|
`*.bak.<timestamp>` and the restore command is printed).
|
|
@@ -222,7 +225,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
|
|
|
222
225
|
| Command | When | What happens |
|
|
223
226
|
|---------|------|--------------|
|
|
224
227
|
| `/agent-workflow-kit` | new / empty project | recon → **asks visible-or-hidden** + **conversational language** + **agent attribution** (default off) → deploys `AGENTS.md` + `docs/ai/` filled with real recon data → installs enforcement → **asks before committing** |
|
|
225
|
-
| `/agent-workflow-kit upgrade` | existing deployment | reads `docs/ai/.workflow-version`, shows the changelog diff, preserves your authored memory, applies migrations, re-stamps — then prints a **read-only** one-line backend-status line (what's set up vs missing); never installs a
|
|
228
|
+
| `/agent-workflow-kit upgrade` | existing deployment | reads `docs/ai/.workflow-version`, shows the changelog diff, preserves your authored memory, applies migrations, re-stamps — then prints a **read-only** one-line backend-status line (what's set up vs missing); refreshes the already-placed bridges from the kit's bundled copies (never installs a new one — set one up with `/agent-workflow-kit setup`) |
|
|
226
229
|
| `/agent-workflow-kit help` | any time | **read-only command index** — every command, grouped (Inspect / Configure / Orchestrate / Lifecycle) and tagged read-only / writer / guarded. The discoverable entry point, and where any unrecognized invocation lands (always read-only — a garbage invocation never writes). Never writes, never commits, never runs a subscription CLI. |
|
|
227
230
|
| `/agent-workflow-kit backends` | any time | **read-only** check of the optional execution-backends (the `codex` / `agy` bridges): what's set up vs missing and the next step. Never writes, never commits, never runs a subscription CLI (credentials = marker-file presence, not a live login). |
|
|
228
231
|
| `/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. |
|
|
@@ -274,8 +277,9 @@ agent-workflow-kit — the composition root (installed via npx … init)
|
|
|
274
277
|
**orchestration recipes** (Solo / Reviewed / Council / Delegated) — read **live** from the installed
|
|
275
278
|
**`agent-workflow-engine`** (the canonical narrative; a published member, never one of the shipped
|
|
276
279
|
backends). `/agent-workflow-kit recipes` surfaces + plans a recipe for your environment, read-only.
|
|
277
|
-
- **Detects & (opt-in) sets up** the optional `codex` / `agy` **bridges** — agent skills (not npm
|
|
278
|
-
|
|
280
|
+
- **Detects & (opt-in) sets up** the optional `codex` / `agy` **bridges** — agent skills (not npm;
|
|
281
|
+
never first placed by `init` — `setup` places them, and once placed `init`/`upgrade` refresh
|
|
282
|
+
them). They plug into the workflow's **execute** and **review** phases — for *what
|
|
279
283
|
each adds and why*, see the
|
|
280
284
|
[family front door](https://github.com/sabaiway/agent-workflow#readme). `/agent-workflow-kit backends`
|
|
281
285
|
reports readiness **read-only**;
|
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.26.0'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-kit
|
|
@@ -31,7 +31,10 @@ runtime dependency placed by `init` and read live; see *Methodology slot reconci
|
|
|
31
31
|
`init` also **refreshes the installed memory substrate** (best-effort — a miss is a loud DEGRADED
|
|
32
32
|
success: a warning with the exact recovery command + exit 0, never silent, never the engine's hard
|
|
33
33
|
STOP; `--no-memory` skips it), so a returning `init` leaves **no stale core member**. The
|
|
34
|
-
execution-backend bridges are still **
|
|
34
|
+
execution-backend bridges are still never **placed** by `init` (placed on demand by `setup`, opt-in);
|
|
35
|
+
**once placed**, `init` **refreshes** them from the kit's own bundled copies (refresh-only, local
|
|
36
|
+
files, never a downgrade; `--no-bridges` skips it) — so a returning `init` leaves no stale placed
|
|
37
|
+
bridge either.
|
|
35
38
|
|
|
36
39
|
**Detection (kit-owned, decided BEFORE any project write).** Run the kit's **own shipped**
|
|
37
40
|
validator — `node ${CLAUDE_SKILL_DIR}/tools/manifest/validate.mjs <memory-skill-dir>` — never a
|
|
@@ -108,7 +111,7 @@ Pick the mode from the user's invocation. Auto-detect an existing `docs/ai/` to
|
|
|
108
111
|
- **`/agent-workflow-kit upgrade`** — upgrade an existing deployment to the skill's current `version`.
|
|
109
112
|
- **`/agent-workflow-kit help`** — read-only **command index**: enumerate every command, grouped (Inspect / Configure / Orchestrate / Lifecycle) and tagged read-only / writer / guarded. The single discoverable entry point, and the landing spot for any unrecognized invocation. Never writes, never commits, never runs a subscription CLI.
|
|
110
113
|
- **`/agent-workflow-kit backends`** — read-only environment check: which optional **execution-backends** (the `codex` / `agy` bridges) are set up vs missing. Never writes, never commits, never runs a subscription CLI.
|
|
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.
|
|
114
|
+
- **`/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; once placed, `init`/`upgrade` refresh them (refresh-only). 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
115
|
- **`/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
116
|
- **`/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
117
|
- **`/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.
|
|
@@ -137,29 +140,32 @@ Before acting, read `docs/ai/.workflow-version` (the project's stamp) to decide
|
|
|
137
140
|
### The one-line backend-status line (shared by bootstrap + upgrade)
|
|
138
141
|
|
|
139
142
|
Bootstrap (step 11) and **every** successful `upgrade` exit (steps 4 + 8) print the **same**
|
|
140
|
-
read-only, one-line summary of the optional execution-backends.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
`
|
|
145
|
-
|
|
143
|
+
read-only, one-line summary of the optional execution-backends. The line is **machine-composed** —
|
|
144
|
+
run the status-line composer and **paste its single emitted line verbatim**:
|
|
145
|
+
`node ${CLAUDE_SKILL_DIR}/tools/recipes.mjs --status-line`
|
|
146
|
+
The tool runs the backend detector and appends the recipe recommendation itself
|
|
147
|
+
(`composeStatusLine`); the agent **composes nothing factual** — no readiness token, no glyph, no
|
|
148
|
+
recipe clause of its own, ever.
|
|
149
|
+
|
|
150
|
+
- **Placeholder template (structure only — never copy this example; paste the tool's line):**
|
|
151
|
+
`backends: <alias> <✓|✗> <readiness> · <alias> <✓|✗> <readiness> — run /agent-workflow-kit backends · recipes: <recommendation clause> — see /agent-workflow-kit recipes`
|
|
146
152
|
- The **`recipes:` clause is appended after** the `— run /agent-workflow-kit backends` pointer (never
|
|
147
153
|
replacing it) and routes to the read-only `recipes` mode (`see /agent-workflow-kit recipes`). It is
|
|
148
154
|
**never blank**: both backends ready → *"Council available, Reviewed the everyday default"*; one
|
|
149
155
|
ready → *"Reviewed available (via …)"*; **none installed → *"Solo — run /agent-workflow-kit setup to
|
|
150
156
|
add a backend"***; a backend present-but-not-ready → Solo with that backend's specific remedy.
|
|
151
|
-
(
|
|
157
|
+
(Composed by `recommendRecipe` inside the same tool run — never by the agent.)
|
|
152
158
|
- **Invariants:** **read-only · never blocks the commit gate · never runs a subscription CLI · the
|
|
153
159
|
pointer is the in-agent `backends` mode, never a network fetch · the appended `recipes:` clause
|
|
154
|
-
routes to the in-agent `recipes` mode the same way · `init`/npx
|
|
155
|
-
|
|
156
|
-
- **
|
|
160
|
+
routes to the in-agent `recipes` mode the same way · `init`/npx never *places* bridges (it
|
|
161
|
+
refreshes only what `setup` already placed, AD-011 §5).**
|
|
162
|
+
- **Composer unavailable → skip with a stated reason, never silently.** The composer is a Node script
|
|
157
163
|
the **agent host** runs (not the target project), so the only skip condition is "**the agent host
|
|
158
|
-
can't run it**" — `node` is not on the agent's PATH, or the
|
|
164
|
+
can't run it**" — `node` is not on the agent's PATH, or the tool itself errors — **not** "the
|
|
159
165
|
project has no Node runtime". On that condition, skip the line and say so **with the concrete
|
|
160
|
-
reason**, e.g. *"Couldn't run the backend
|
|
161
|
-
I'm skipping the backend-status line."* — never a silent skip (Hard Constraint — no
|
|
162
|
-
failures).
|
|
166
|
+
reason**, e.g. *"Couldn't run the backend status-line composer here (node is not on the agent host
|
|
167
|
+
PATH), so I'm skipping the backend-status line."* — never a silent skip (Hard Constraint — no
|
|
168
|
+
silent failures).
|
|
163
169
|
|
|
164
170
|
### The version block + welcome mat (shared by bootstrap + upgrade)
|
|
165
171
|
|
|
@@ -206,8 +212,12 @@ the rest of the report — and the commit gate — proceeds.
|
|
|
206
212
|
line, print *"Run `/agent-workflow-kit help` to see every command."* then **one** recommended next
|
|
207
213
|
step, chosen **caveat-aware** from signals already in hand (the version block's notes + the
|
|
208
214
|
backend-status line — no new helper call) in this priority order:
|
|
209
|
-
1. a member is **behind** (
|
|
210
|
-
|
|
215
|
+
1. a member is **behind** (a behind-class `installed[].notes` caveat fired — any member, the bridges
|
|
216
|
+
included) → *refresh the behind member first*, quoting **that note's own recovery command
|
|
217
|
+
verbatim** (a memory/engine note carries its `npx …@latest init` + restart the session; a bridge
|
|
218
|
+
note carries `/agent-workflow-kit setup`). An **uncheckable** member ("couldn't be checked" — an
|
|
219
|
+
unknown-freshness note) is **never** a refresh trigger: only a behind note fires this step — the
|
|
220
|
+
uncheckable note already appears in the version block, add nothing more;
|
|
211
221
|
2. else **no backend is ready** (the backend-status line shows none ready) → *set one up with
|
|
212
222
|
`/agent-workflow-kit setup`*;
|
|
213
223
|
3. else **a backend is ready but the orchestration config is still all-Solo** (no `reviewed` /
|
|
@@ -316,15 +326,27 @@ Fill strategy:
|
|
|
316
326
|
**Hidden-mode footprint reconcile — stamp-independent, same gate, BEFORE the equal-head short-circuit (D9 / AD-014).** A deployment does not record whether it chose `hidden`, so first **infer visibility**: `node ${CLAUDE_SKILL_DIR}/tools/hide-footprint.mjs --dir <project> --reconcile --dry-run` (writes **zero bytes**). It reports one of — **visible** (the entry point is tracked) → nothing to do; **ambiguous** (untracked but not ignored — could be a fresh uncommitted repo, or a hide that broke) → **ASK** the user which it is, never guess; **hidden** → re-run without `--dry-run` to migrate any older **machine-global** hide to the **project-local** `.git/info/exclude` (one managed block; folds in the legacy `.claude/skills/` line), idempotently (a clean re-run is zero-diff). Handle its surfaced paths exactly as bootstrap step 9 (already-committed → show `git rm --cached`, ask before `--include`; generic-name present file → ask; **leftover machine-wide ignore block → ASK before `--remove-global`**, default keep + report). No Node on the agent host / Windows → as step 9. This runs on **every** hidden upgrade, like the methodology slot — no lineage-head bump, no migration file.
|
|
317
327
|
|
|
318
328
|
**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*).
|
|
329
|
+
|
|
330
|
+
**Placed-bridge refresh — stamp-independent, same gate, BEFORE the equal-head short-circuit.** Run
|
|
331
|
+
`node ${CLAUDE_SKILL_DIR}/tools/setup-backends.mjs --refresh-placed` and **paste its per-bridge
|
|
332
|
+
output lines verbatim** — every outcome line is composed by the tool (*refreshed* / *already
|
|
333
|
+
current* / *skipped — not placed* / *could not refresh* + its recovery); you compose nothing
|
|
334
|
+
factual. It is **refresh-only**: it refreshes a bridge **`setup` already placed** from this kit's
|
|
335
|
+
bundled copies and re-links its wrappers; an **absent** bridge is a stated skip, **never a first
|
|
336
|
+
placement** (placement stays the opt-in *Mode: setup* — AD-009/AD-011 honesty intact), and a
|
|
337
|
+
placed bridge **newer** than the bundled copy is a stated skip naming the kit update (**never a
|
|
338
|
+
downgrade**). Like the other three reconciles it runs on **every** upgrade — including an
|
|
339
|
+
equal-head one — with no lineage-head bump. A *could not refresh* line is non-fatal: relay it
|
|
340
|
+
plainly with its recovery and continue the upgrade.
|
|
319
341
|
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:
|
|
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).
|
|
342
|
+
- **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); the **placed-bridge refresh** outcome — paste the tool's per-bridge lines verbatim (they are already plain: *refreshed* / *already current* / *skipped — not placed* / *could not refresh* + recovery); 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).
|
|
321
343
|
- **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*.
|
|
322
344
|
- **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`).
|
|
323
345
|
- **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).
|
|
324
346
|
5. Show the relevant `${CLAUDE_SKILL_DIR}/CHANGELOG.md` diff (entries newer than the project's stamp).
|
|
325
347
|
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.
|
|
326
348
|
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`.
|
|
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**.
|
|
349
|
+
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), plus the step-3 **placed-bridge refresh** lines (pasted verbatim) — 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**.
|
|
328
350
|
|
|
329
351
|
### Mode: backends
|
|
330
352
|
|
|
@@ -338,13 +360,19 @@ Read-only. Answers *"which optional execution-backends are set up vs missing, an
|
|
|
338
360
|
|
|
339
361
|
### Mode: setup
|
|
340
362
|
|
|
341
|
-
The **only writer** among the backend modes, and **opt-in / in-agent only** —
|
|
363
|
+
The **only writer** among the backend modes, and **opt-in / in-agent only** — **placement** is **never** part of `init`. The npx installer deploys the *kit* and bundles the bridge skills in its tarball, but **does not place** them (that honesty claim is load-bearing — see `decisions.md` AD-009 / AD-011); **once placed** by `setup`, `init` and *Mode: upgrade* keep the placed copy fresh via the refresh-only `--refresh-placed` (below) — **never a first placement, never a downgrade**. `setup` owns exactly the two deterministic, secret-free steps and **guides** the rest. It **never commits and never runs a subscription CLI**.
|
|
342
364
|
|
|
343
365
|
Run `node ${CLAUDE_SKILL_DIR}/tools/setup-backends.mjs [<backend>] [--bindir <path>] [--dry-run]`:
|
|
344
366
|
|
|
345
367
|
- `<backend>` — `codex` | `agy` | `antigravity` | `codex-cli-bridge` | `antigravity-cli-bridge`; omit for **all**.
|
|
346
368
|
- `--bindir <path>` — where to link the wrappers (default `~/.local/bin`).
|
|
347
369
|
- `--dry-run` — print the per-backend plan and change **nothing** (run this first).
|
|
370
|
+
- `--refresh-placed` — the **refresh-only** mode (what `init` runs automatically and *Mode: upgrade*
|
|
371
|
+
runs as its fourth stamp-independent reconcile): refresh every bridge `setup` **already placed**
|
|
372
|
+
from the kit's bundled copies + re-link its wrappers; an absent bridge is a stated skip (**never**
|
|
373
|
+
placed), a placed bridge newer than the bundle is a stated skip naming the kit update (**never**
|
|
374
|
+
downgraded), and every outcome line is composed by the tool — paste verbatim. Does not combine
|
|
375
|
+
with `--dry-run`.
|
|
348
376
|
- `--help`, `-h` — usage.
|
|
349
377
|
|
|
350
378
|
For each backend it:
|
|
@@ -367,7 +395,7 @@ Read-only. The **single answer to "versions + deployment + settings + bridges"**
|
|
|
367
395
|
|
|
368
396
|
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:
|
|
369
397
|
|
|
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"*).
|
|
398
|
+
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). A member whose **`refresh.freshness`** is **`unknown`** is surfaced too — a localized *"couldn't be checked"* label; it is **never counted as current and never as behind** (its `notes` caveat carries the detail on the notes-based surfaces; here the label is enough). Lead with a one-line **headline count** derived from `installed[].state` + `refresh.behind` + `refresh.freshness` (e.g. *"5 members installed · 1 needs a refresh · 1 couldn't be checked"* — omit a zero count).
|
|
371
399
|
|
|
372
400
|
> **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`.
|
|
373
401
|
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.
|
|
@@ -554,6 +582,6 @@ Deploy these into `AGENTS.md`; remove rows that don't apply to the stack.
|
|
|
554
582
|
- [`references/scripts/`](references/scripts/) — the Node enforcement scripts (caps + staleness + index-freshness gate, 3-tier archive, hook installer) and their unit tests.
|
|
555
583
|
- [`migrations/`](migrations/) — per-version upgrade steps; see `migrations/README.md`.
|
|
556
584
|
- [`launchers/`](launchers/) — run the bootstrapper from non-Claude agents (`SKILL.md` is a native Codex skill; a Devin Desktop workflow launcher + install script). See `launchers/README.md`.
|
|
557
|
-
- [`tools/`](tools/) — the family-wide tooling the kit **owns and ships**: `manifest/{schema.md,validate.mjs}` (the `capability.json` schema + the validator the kit runs as the memory detector, and root CI invokes), `commands.mjs` (the canonical **command catalog** + the pure `routeInvocation` router behind `/agent-workflow-kit help` and the safe unknown-invocation routing rule — one source of truth for the command surface, drift-guarded against the `### Mode:` headers), `delegation.mjs` (the executable delegate/fallback decision + hand-off plan), `inject-methodology.mjs` + `engine-source.mjs` (the bounded **two-slot** reconciliation — ensure-slot / inject-if-empty / cap for the `workflow:methodology` **and** `workflow:orchestration` pointers; both fragments read **live** from the installed `agent-workflow-engine` via `engine-source.mjs` (`deps.rel` selects which) — the family's one source of truth, no bundled mirror; fail-loud when the engine is needed but absent, orchestration soft-skipped when it would bust the cap), `detect-backends.mjs` (the read-only **backend detector** behind `/agent-workflow-kit backends`, plus the axis-aware `guideFor`; exports the readiness consts the planner reuses), `recipes.mjs` (the read-only **recipe planner** behind `/agent-workflow-kit recipes` — `RECIPES` / `planRecipe` / `recommendRecipe` over the bridges' role vocabulary, drift-guarded against `provides`/`cost`/`quota` + an engine↔kit recipe-name parity guard; pure, never runs a subscription CLI; also exports the pure `ACTIVITIES` / `resolveActivityRecipe` activity-procedures resolver), `procedures.mjs` (the read-only **activity-procedures advisor** behind `/agent-workflow-kit procedures` — reads `references/procedures.md` live from the engine via `engine-source.mjs`, reads + validates the agent-writable `docs/ai/orchestration.json` via `orchestration-config.mjs`, and prints the steps + the resolved effective recipe per slot; drift-guarded activity/slot table vs the canon; read-only, never imports the writer, never runs a subscription CLI), `orchestration-config.mjs` (the config **schema/read/pure-transform core** — `loadConfig`/`validateConfig`/the shared slot-recipe validity + `parseOp`/`applySetOps`/`serializeConfig`/the canonical-refresh helpers; no fs writes), `orchestration-write.mjs` (the **only** config fs-writer — a deployment-gated, atomic, symlink/TOCTOU-safe `writeConfig`; imported by `set-recipe` alone, never by `procedures`), `set-recipe.mjs` (the **config writer** behind `/agent-workflow-kit set-recipe` — validate → merge → preview-by-default → write; never runs a backend, never commits), `setup-backends.mjs` (the **link-only** backend setup behind `/agent-workflow-kit setup` — place the bundled bridge + link wrappers), `fs-safe.mjs` (the shared symlink-traversal-safe copy/link/**remove/unlink** primitives that `setup-backends`, the npx installer, and the uninstaller use), `known-footprint.mjs` + `hide-footprint.mjs` (the **hidden-mode** registry + the single hide-writer behind step 9 / the upgrade reconcile — one managed block in the **project-local** `.git/info/exclude` covering the full AI/agent footprint; pinned by `known-footprint.test.mjs` drift-guard + `hide-footprint.test.mjs` / `.integration.test.mjs`), `family-registry.mjs` (the **unified family registry** behind `/agent-workflow-kit status` — aggregates every member's `capability.json`; pinned by a `family-registry.test.mjs` drift-guard), `uninstall.mjs` (the **guarded teardown** behind `/agent-workflow-kit uninstall` — classify each surface, preflight-then-mutate, never delete user-authored content), and `release-scan.mjs` (the attribution-off release gate). The bundled bridge skill mirrors live under [`bridges/`](bridges/) (byte-identical to the repo-root bridges, pinned by `test/bridges-mirror.test.mjs`). See [`tools/manifest/schema.md`](tools/manifest/schema.md).
|
|
585
|
+
- [`tools/`](tools/) — the family-wide tooling the kit **owns and ships**: `manifest/{schema.md,validate.mjs}` (the `capability.json` schema + the validator the kit runs as the memory detector, and root CI invokes), `commands.mjs` (the canonical **command catalog** + the pure `routeInvocation` router behind `/agent-workflow-kit help` and the safe unknown-invocation routing rule — one source of truth for the command surface, drift-guarded against the `### Mode:` headers), `delegation.mjs` (the executable delegate/fallback decision + hand-off plan), `inject-methodology.mjs` + `engine-source.mjs` (the bounded **two-slot** reconciliation — ensure-slot / inject-if-empty / cap for the `workflow:methodology` **and** `workflow:orchestration` pointers; both fragments read **live** from the installed `agent-workflow-engine` via `engine-source.mjs` (`deps.rel` selects which) — the family's one source of truth, no bundled mirror; fail-loud when the engine is needed but absent, orchestration soft-skipped when it would bust the cap), `detect-backends.mjs` (the read-only **backend detector** behind `/agent-workflow-kit backends`, plus the axis-aware `guideFor`; exports the readiness consts the planner reuses), `recipes.mjs` (the read-only **recipe planner** behind `/agent-workflow-kit recipes` — `RECIPES` / `planRecipe` / `recommendRecipe` over the bridges' role vocabulary, drift-guarded against `provides`/`cost`/`quota` + an engine↔kit recipe-name parity guard; pure, never runs a subscription CLI; also exports the pure `ACTIVITIES` / `resolveActivityRecipe` activity-procedures resolver and the machine-composed one-line backend status — `composeStatusLine` behind `--status-line`, pasted verbatim by the bootstrap/upgrade report footers), `procedures.mjs` (the read-only **activity-procedures advisor** behind `/agent-workflow-kit procedures` — reads `references/procedures.md` live from the engine via `engine-source.mjs`, reads + validates the agent-writable `docs/ai/orchestration.json` via `orchestration-config.mjs`, and prints the steps + the resolved effective recipe per slot; drift-guarded activity/slot table vs the canon; read-only, never imports the writer, never runs a subscription CLI), `orchestration-config.mjs` (the config **schema/read/pure-transform core** — `loadConfig`/`validateConfig`/the shared slot-recipe validity + `parseOp`/`applySetOps`/`serializeConfig`/the canonical-refresh helpers; no fs writes), `orchestration-write.mjs` (the **only** config fs-writer — a deployment-gated, atomic, symlink/TOCTOU-safe `writeConfig`; imported by `set-recipe` alone, never by `procedures`), `set-recipe.mjs` (the **config writer** behind `/agent-workflow-kit set-recipe` — validate → merge → preview-by-default → write; never runs a backend, never commits), `setup-backends.mjs` (the **link-only** backend setup behind `/agent-workflow-kit setup` — place the bundled bridge + link wrappers), `fs-safe.mjs` (the shared symlink-traversal-safe copy/link/**remove/unlink** primitives that `setup-backends`, the npx installer, and the uninstaller use), `known-footprint.mjs` + `hide-footprint.mjs` (the **hidden-mode** registry + the single hide-writer behind step 9 / the upgrade reconcile — one managed block in the **project-local** `.git/info/exclude` covering the full AI/agent footprint; pinned by `known-footprint.test.mjs` drift-guard + `hide-footprint.test.mjs` / `.integration.test.mjs`), `family-registry.mjs` (the **unified family registry** behind `/agent-workflow-kit status` — aggregates every member's `capability.json`; pinned by a `family-registry.test.mjs` drift-guard), `uninstall.mjs` (the **guarded teardown** behind `/agent-workflow-kit uninstall` — classify each surface, preflight-then-mutate, never delete user-authored content), and `release-scan.mjs` (the attribution-off release gate). The bundled bridge skill mirrors live under [`bridges/`](bridges/) (byte-identical to the repo-root bridges, pinned by `test/bridges-mirror.test.mjs`). See [`tools/manifest/schema.md`](tools/manifest/schema.md).
|
|
558
586
|
- [`capability.json`](capability.json) — the kit's own `agent-workflow` family manifest (`kind: composition-root`).
|
|
559
587
|
- [`CHANGELOG.md`](CHANGELOG.md) — version history of this kernel.
|
package/bin/install.mjs
CHANGED
|
@@ -23,8 +23,10 @@
|
|
|
23
23
|
// member — the memory substrate (`npx @sabaiway/agent-workflow-memory@latest init`, best-effort: a
|
|
24
24
|
// failure is a loud DEGRADED success, never silent — skippable with `--no-memory`) and the
|
|
25
25
|
// methodology engine the kit reads live (`npx @sabaiway/agent-workflow-engine@latest init`, fatal —
|
|
26
|
-
// the live read STOPs without it — skippable with `--no-engine`). The bridges are
|
|
27
|
-
// `init` (
|
|
26
|
+
// the live read STOPs without it — skippable with `--no-engine`). The bridges are never PLACED by
|
|
27
|
+
// `init` (first placement stays `/agent-workflow-kit setup`, opt-in); once placed, `init` REFRESHES
|
|
28
|
+
// them from this package's own bundled copies — a purely LOCAL copy, no third server contact —
|
|
29
|
+
// skippable with `--no-bridges`. No tracking either way.
|
|
28
30
|
//
|
|
29
31
|
// Dependency-free, Node >= 18.
|
|
30
32
|
|
|
@@ -35,11 +37,22 @@ import { fileURLToPath } from 'node:url';
|
|
|
35
37
|
import { homedir } from 'node:os';
|
|
36
38
|
import { spawnSync } from 'node:child_process';
|
|
37
39
|
import { copyTreeRefresh } from '../tools/fs-safe.mjs';
|
|
40
|
+
// Dependency-free semver, shared with the family registry's bridge freshness probe (one leaf, no
|
|
41
|
+
// second drifting copy). The null-on-unparseable contract is load-bearing here: legacy installs
|
|
42
|
+
// predate any version stamp, so an unparseable side means "no gate", never a false ordering.
|
|
43
|
+
import { compareSemver } from '../tools/semver-lite.mjs';
|
|
38
44
|
// The ONE registry of family members (npm packages, kinds). The init-refresh cascade derives its
|
|
39
45
|
// membership from this table — no second source of "who gets refreshed" — so it can't drift from the
|
|
40
46
|
// manifests (a drift-guard test pins the derivation). Imported from the DATA LEAF (family-members.mjs),
|
|
41
|
-
// NOT family-registry.mjs
|
|
47
|
+
// NOT family-registry.mjs. Leanness note: the bridge-refresh driver below does pull in the backend
|
|
48
|
+
// detector + the manifest validator (setup-backends' imports), but still NOT the status/presenter
|
|
49
|
+
// graph (family-registry, renderers, recipes) — the npx cold-start path stays presenter-free.
|
|
42
50
|
import { FAMILY_MEMBERS } from '../tools/family-members.mjs';
|
|
51
|
+
// The refresh-only bridge driver (shared with `/agent-workflow-kit setup --refresh-placed` and the
|
|
52
|
+
// upgrade reconcile): refreshes ONLY a bridge `setup` already placed — an absent bridge is a stated
|
|
53
|
+
// skip (never a first placement, AD-009/AD-011) and a placed bridge newer than this kit's bundled
|
|
54
|
+
// mirror is a stated skip too (never a downgrade). Every line it returns is tool-composed.
|
|
55
|
+
import { refreshPlacedBridges } from '../tools/setup-backends.mjs';
|
|
43
56
|
|
|
44
57
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
45
58
|
const PKG_ROOT = resolve(__dirname, '..');
|
|
@@ -81,22 +94,6 @@ const readVersion = async () => {
|
|
|
81
94
|
}
|
|
82
95
|
};
|
|
83
96
|
|
|
84
|
-
// Dependency-free semver: parse the leading `x.y.z` (prerelease/build ignored — kit versions are
|
|
85
|
-
// plain). compareSemver returns -1 | 0 | 1, or null when either side is unparseable (legacy installs
|
|
86
|
-
// predate any version stamp). No `let`: a small functional comparison (AGENTS.md §2.3).
|
|
87
|
-
const parseSemver = (str) => {
|
|
88
|
-
const m = typeof str === 'string' ? str.trim().match(/^(\d+)\.(\d+)\.(\d+)/) : null;
|
|
89
|
-
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const compareSemver = (a, b) => {
|
|
93
|
-
const pa = parseSemver(a);
|
|
94
|
-
const pb = parseSemver(b);
|
|
95
|
-
if (!pa || !pb) return null;
|
|
96
|
-
const firstDiff = [0, 1, 2].map((i) => (pa[i] === pb[i] ? 0 : pa[i] < pb[i] ? -1 : 1)).find((c) => c !== 0);
|
|
97
|
-
return firstDiff ?? 0;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
97
|
// Extract the version that is a DIRECT child of the top-level `metadata:` key — never a top-level
|
|
101
98
|
// or deeper-nested decoy `version:` (mirrors the manifest validator's rigor; the kit ships manifest
|
|
102
99
|
// fixtures that probe exactly those decoys). Pure string walk over the frontmatter block, no deps.
|
|
@@ -157,6 +154,7 @@ const parseArgs = (argv) => {
|
|
|
157
154
|
noLaunchers: argv.includes('--no-launchers'),
|
|
158
155
|
noMemory: argv.includes('--no-memory'),
|
|
159
156
|
noEngine: argv.includes('--no-engine'),
|
|
157
|
+
noBridges: argv.includes('--no-bridges'),
|
|
160
158
|
force: argv.includes('--force'),
|
|
161
159
|
allowDowngrade: argv.includes('--allow-downgrade'),
|
|
162
160
|
dir: dirFlag >= 0 ? argv[dirFlag + 1] : undefined,
|
|
@@ -278,7 +276,7 @@ const printHelp = (version) => {
|
|
|
278
276
|
console.log(`agent-workflow-kit ${version}
|
|
279
277
|
|
|
280
278
|
Usage:
|
|
281
|
-
npx @sabaiway/agent-workflow-kit@latest init [--dir <path>] [--no-launchers] [--no-memory] [--no-engine] [--force] [--allow-downgrade]
|
|
279
|
+
npx @sabaiway/agent-workflow-kit@latest init [--dir <path>] [--no-launchers] [--no-memory] [--no-engine] [--no-bridges] [--force] [--allow-downgrade]
|
|
282
280
|
npx @sabaiway/agent-workflow-kit@latest --version
|
|
283
281
|
npx @sabaiway/agent-workflow-kit@latest --help
|
|
284
282
|
|
|
@@ -294,8 +292,10 @@ Installs/refreshes the kit at ~/.claude/skills/agent-workflow-kit
|
|
|
294
292
|
stale, refresh it yourself + restart — a miss is otherwise a non-fatal degraded success,
|
|
295
293
|
never the engine's hard STOP); --no-engine skips the engine install (the live methodology
|
|
296
294
|
read then STOPs until you install it by hand); --force replaces a pre-existing non-kit
|
|
297
|
-
launcher file (backed up first). The bridges are
|
|
298
|
-
/agent-workflow-kit setup)
|
|
295
|
+
launcher file (backed up first). The bridges are never PLACED by init (first placement
|
|
296
|
+
stays /agent-workflow-kit setup, opt-in); once placed, init refreshes them from the kit's
|
|
297
|
+
own bundled copies — local files only, never a downgrade — and --no-bridges skips that
|
|
298
|
+
refresh. init is additive — it never deletes your settings. If the
|
|
299
299
|
installed kit is newer than the version you ran, init refuses (no network — it compares
|
|
300
300
|
the version on disk) and points you at @latest; --allow-downgrade overrides that
|
|
301
301
|
refusal (distinct from --force, which is launcher-only).
|
|
@@ -376,15 +376,26 @@ const main = async () => {
|
|
|
376
376
|
console.log(`[agent-workflow-kit] removed retired file ${tildify(retired)} (now read live from the engine).`);
|
|
377
377
|
}
|
|
378
378
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
//
|
|
379
|
+
// Verb keyed on the OBSERVED version relation (cmp), never on mere presence: null (fresh install,
|
|
380
|
+
// or a legacy/unstamped one whose prior version is unknowable) → "installed"; -1 → a real update;
|
|
381
|
+
// 0 → already current (the copy still ran — see the note below); 1 is reachable only under
|
|
382
|
+
// --allow-downgrade (the gate above refused otherwise) and says so plainly. One message contract
|
|
383
|
+
// with the engine installer (same shapes, member noun differs).
|
|
384
|
+
const verb =
|
|
385
|
+
cmp === 0 ? 'refreshed the already-current kit'
|
|
386
|
+
: cmp === 1 ? 'downgraded the kit to'
|
|
387
|
+
: cmp === -1 ? 'updated the kit to'
|
|
388
|
+
: 'installed';
|
|
389
|
+
console.log(`[agent-workflow-kit] ${verb} v${version} -> ${tildify(target)}`);
|
|
390
|
+
|
|
391
|
+
// Same-version re-run: state observable facts only. The copy DID run (repair-on-rerun is a feature —
|
|
392
|
+
// it restores locally modified/deleted files), and whether npx served a cached build is NOT
|
|
393
|
+
// observable here (no network), so the note never claims it; the @latest hint is conditional.
|
|
384
394
|
if (cmp === 0) {
|
|
385
395
|
console.log(
|
|
386
|
-
`[agent-workflow-kit] note:
|
|
387
|
-
`
|
|
396
|
+
`[agent-workflow-kit] note: the kit was already v${version} — the copy still ran, restoring ` +
|
|
397
|
+
`any locally modified or deleted kit file to this version's packaged contents. If you ` +
|
|
398
|
+
`expected a NEWER version, invoke the @latest tag explicitly:\n` +
|
|
388
399
|
` npx @sabaiway/agent-workflow-kit@latest init`,
|
|
389
400
|
);
|
|
390
401
|
}
|
|
@@ -404,6 +415,40 @@ const main = async () => {
|
|
|
404
415
|
}
|
|
405
416
|
}
|
|
406
417
|
|
|
418
|
+
// Placed-bridge refresh — AFTER the kit copy (the bundled bridges/ just landed), BEFORE the two
|
|
419
|
+
// network cascade steps (this one is LOCAL files only: bundle → placed copy). Refresh-only, so the
|
|
420
|
+
// AD-009/AD-011 honesty claim survives as "init never PLACES bridges; it refreshes what setup
|
|
421
|
+
// already placed" — an absent bridge is a stated skip, a placed-newer one a stated skip too (never
|
|
422
|
+
// a downgrade). Best-effort in the memory DEGRADED shape: any miss is a loud warning + a
|
|
423
|
+
// host-runnable recovery composed from the RESOLVED target (a --dir / AGENT_WORKFLOW_KIT_DIR
|
|
424
|
+
// install must get a line that runs) + exit 0 — never silent, never the engine's hard STOP.
|
|
425
|
+
const bridgeRefreshCmd = `node ${tildify(resolve(target, 'tools/setup-backends.mjs'))} --refresh-placed`;
|
|
426
|
+
if (args.noBridges) {
|
|
427
|
+
console.log(
|
|
428
|
+
`[agent-workflow-kit] --no-bridges: skipped refreshing the placed bridges. If one is stale, ` +
|
|
429
|
+
`refresh it yourself:\n ${bridgeRefreshCmd}`,
|
|
430
|
+
);
|
|
431
|
+
} else if (process.platform === 'win32') {
|
|
432
|
+
console.log('[agent-workflow-kit] Windows: skipped the placed-bridge refresh (POSIX wrappers — refresh under WSL).');
|
|
433
|
+
} else {
|
|
434
|
+
console.log('[agent-workflow-kit] refreshing the bridges setup already placed (an absent bridge is never placed here):');
|
|
435
|
+
try {
|
|
436
|
+
const results = refreshPlacedBridges();
|
|
437
|
+
for (const r of results) console.log(r.line);
|
|
438
|
+
if (results.some((r) => r.outcome === 'failed')) {
|
|
439
|
+
console.warn(
|
|
440
|
+
`[agent-workflow-kit] could not refresh every placed bridge — continuing; the kit itself IS ` +
|
|
441
|
+
`installed and works. Re-run the refresh yourself:\n ${bridgeRefreshCmd}`,
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
} catch (err) {
|
|
445
|
+
console.warn(
|
|
446
|
+
`[agent-workflow-kit] could not refresh the placed bridges (${err.message}) — continuing; the ` +
|
|
447
|
+
`kit itself IS installed and works. Re-run the refresh yourself:\n ${bridgeRefreshCmd}`,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
407
452
|
// Memory substrate refresh — AFTER the kit + launchers, BEFORE the (fatal) engine and the success
|
|
408
453
|
// block, so a returning `init` leaves no stale memory. Unlike the engine, a memory miss is a
|
|
409
454
|
// DEGRADED success: warn with the exact recovery command + the on-disk version (read crash-proof)
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
package/tools/family-members.mjs
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
// ── the unified registry ───────────────────────────────────────────────────────
|
|
14
14
|
// One entry per family member. `installed` is the detect.installed spec (env + home-relative default
|
|
15
15
|
// + marker file); `deployed` is the project-relative stamp a deploy writes (kit + memory only);
|
|
16
|
-
// `npm` is the install package (null for the bridges
|
|
16
|
+
// `npm` is the install package (null for the bridges — placed by `setup`, and once placed refreshed
|
|
17
|
+
// by `init`/`upgrade` from the kit's bundled copies, never npm);
|
|
17
18
|
// `wrapperCmds` is the deduped roles[].cmd set the `setup` linker creates on PATH (bridges only).
|
|
18
19
|
// Kept in lockstep with the 5 in-repo capability.json by the drift-guard test. The two release skills
|
|
19
20
|
// (release-engineering / release-marketing) are deliberately NOT here — they are not family members
|
|
@@ -17,10 +17,13 @@
|
|
|
17
17
|
// side effects on import (the isDirectRun idiom) — tests import the helpers with nothing run.
|
|
18
18
|
|
|
19
19
|
import { existsSync, statSync, readFileSync, lstatSync } from 'node:fs';
|
|
20
|
-
import { join, resolve } from 'node:path';
|
|
21
|
-
import { pathToFileURL } from 'node:url';
|
|
20
|
+
import { join, resolve, dirname } from 'node:path';
|
|
21
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
22
22
|
import os from 'node:os';
|
|
23
23
|
import { resolveDir, detectBackends, findOnPath } from './detect-backends.mjs';
|
|
24
|
+
// The ONE dependency-free semver (shared with bin/install.mjs) — the bridge freshness probe compares
|
|
25
|
+
// the placed version against the kit-bundled mirror; null-on-unparseable maps to 'unknown' (INV-B).
|
|
26
|
+
import { parseSemver, compareSemver } from './semver-lite.mjs';
|
|
24
27
|
import { validateManifest, readAuthoritativeVersion, UNSUPPORTED, INVALID } from './manifest/validate.mjs';
|
|
25
28
|
import { START_MARKER, excludePath, inferVisibility } from './hide-footprint.mjs';
|
|
26
29
|
import { readEngineFragment, ORCHESTRATION_FRAGMENT_REL, PROCEDURES_FRAGMENT_REL } from './engine-source.mjs';
|
|
@@ -57,6 +60,10 @@ import {
|
|
|
57
60
|
VISIBILITY_PUBLIC,
|
|
58
61
|
DISPLAY_NAMES,
|
|
59
62
|
displayOf,
|
|
63
|
+
FRESH_CURRENT,
|
|
64
|
+
FRESH_BEHIND,
|
|
65
|
+
FRESH_UNKNOWN,
|
|
66
|
+
FRESH_NOT_CHECKED,
|
|
60
67
|
} from './labels.mjs';
|
|
61
68
|
// The capability-adaptive direct-CLI presenter (Plan §4.2/§4.5): the surface detector, the
|
|
62
69
|
// envelope→ViewModel transform, and the plain/ansi renderers. main() composes them; the agent-mediated
|
|
@@ -135,7 +142,18 @@ export const classifyMember = (member, deps = {}) => {
|
|
|
135
142
|
const report = markerPresent ? validate(skillDir) : { result: NOT_INSTALLED };
|
|
136
143
|
const manifestState = classifyState(markerPresent, report, member);
|
|
137
144
|
const installed = manifestState !== NOT_INSTALLED;
|
|
138
|
-
|
|
145
|
+
// Crash-safe version read: readAuthoritativeVersion THROWS on a present-but-unreadable SKILL.md
|
|
146
|
+
// (stat needs no read permission, readFileSync does — a TOCTOU/EACCES window after validate).
|
|
147
|
+
// The read-only survey must never crash on it: degrade to null, which the bridge freshness probe
|
|
148
|
+
// then reports as 'unknown' (INV-B), never as a throw and never as a silent claim.
|
|
149
|
+
const version = (() => {
|
|
150
|
+
if (manifestState !== OK) return null;
|
|
151
|
+
try {
|
|
152
|
+
return readVersion(skillDir).version ?? null;
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
})();
|
|
139
157
|
|
|
140
158
|
return { name: member.name, kind: member.kind, installed, skillDir: installed ? skillDir : null, manifestState, version };
|
|
141
159
|
};
|
|
@@ -165,6 +183,51 @@ export const MEMORY_ORCH_TEMPLATE_REL = 'references/templates/orchestration.json
|
|
|
165
183
|
const MEMORY_BEHIND_NOTE =
|
|
166
184
|
"the memory installed here doesn't include the current orchestration template — refresh it with `npx @sabaiway/agent-workflow-memory@latest init`, then restart the session.";
|
|
167
185
|
|
|
186
|
+
// ── the bridge freshness probe (deterministic-first — INV-A / INV-B) ─────────────
|
|
187
|
+
// The bridges are not npm packages: their ONLY delivery channel is the copy bundled inside this kit
|
|
188
|
+
// (`bridges/<name>/`, placed by `/agent-workflow-kit setup`). A placed bridge therefore has exactly
|
|
189
|
+
// one authoritative freshness comparison — its installed version (readAuthoritativeVersion, already
|
|
190
|
+
// on the row) vs the kit-bundled mirror's capability.json. BOTH are local files, so the "never
|
|
191
|
+
// checks npm" invariant holds. The probe sets the internal row field `freshness`
|
|
192
|
+
// ('behind' | 'current' | 'unknown'); refreshOf derives the public refresh block STRUCTURALLY from
|
|
193
|
+
// that field (INV-2 — never parsed from caveat text). INV-B: an unreadable/unparseable version on
|
|
194
|
+
// EITHER side degrades to 'unknown' + a plain-language note — never a false claim in either direction.
|
|
195
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
196
|
+
// bridges/ ships beside tools/ in both the repo and the installed kit (the setup-backends.mjs
|
|
197
|
+
// resolution), so this resolves in both. Injectable via deps.bundleRoot for tests.
|
|
198
|
+
const BUNDLE_ROOT = resolve(__dirname, '..', 'bridges');
|
|
199
|
+
|
|
200
|
+
// Crash-safe on the bundled side: an absent / unreadable / malformed bundle manifest → null (INV-B
|
|
201
|
+
// 'unknown'), never a throw — the read-only survey must survive a broken kit install.
|
|
202
|
+
const readBundledBridgeVersion = (name, deps = {}) => {
|
|
203
|
+
const read = deps.readFile ?? readFileSync;
|
|
204
|
+
const root = deps.bundleRoot ?? BUNDLE_ROOT;
|
|
205
|
+
try {
|
|
206
|
+
const manifest = JSON.parse(String(read(join(root, name, 'capability.json'), 'utf8')));
|
|
207
|
+
return typeof manifest.version === 'string' ? manifest.version : null;
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const bridgeBehindNote = (display, placed, bundled) =>
|
|
214
|
+
`the ${display} installed here (v${placed}) is older than the copy bundled with this kit (v${bundled}) — refresh it with \`/agent-workflow-kit setup\`.`;
|
|
215
|
+
// The recovery differs per failing SIDE: `setup` re-places FROM the bundle, so it can repair an
|
|
216
|
+
// unreadable INSTALLED copy but can never repair an unreadable BUNDLED copy — that needs a kit
|
|
217
|
+
// refresh first (the npx installer), then `setup`.
|
|
218
|
+
const bridgeUnknownNote = (display, side, recovery) =>
|
|
219
|
+
`couldn't compare the ${display} installed here with the copy bundled with this kit (${side}), so its freshness is unknown — ${recovery}.`;
|
|
220
|
+
const UNKNOWN_SIDES = Object.freeze({
|
|
221
|
+
placed: {
|
|
222
|
+
side: 'the installed copy has no readable version',
|
|
223
|
+
recovery: '`/agent-workflow-kit setup` re-places the bundled copy',
|
|
224
|
+
},
|
|
225
|
+
bundled: {
|
|
226
|
+
side: "the kit's bundled copy has no readable version",
|
|
227
|
+
recovery: 'refresh the kit first (`npx @sabaiway/agent-workflow-kit@latest init`), then `/agent-workflow-kit setup`',
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
168
231
|
export const surveyFamily = (deps = {}) =>
|
|
169
232
|
FAMILY_MEMBERS.map((member) => {
|
|
170
233
|
const row = classifyMember(member, deps);
|
|
@@ -184,8 +247,30 @@ export const surveyFamily = (deps = {}) =>
|
|
|
184
247
|
// Only attach when it is provably ABSENT (a non-ENOENT probe error → 'unknown' → skip, never a
|
|
185
248
|
// false "missing" claim). Mirrors the engine-caveat SHAPE; keyed on the Step-2.4 required asset.
|
|
186
249
|
if (row.kind === 'memory-substrate' && row.manifestState === OK && row.skillDir) {
|
|
187
|
-
|
|
250
|
+
const templateProbe = probeMarker(join(row.skillDir, MEMORY_ORCH_TEMPLATE_REL), deps);
|
|
251
|
+
if (templateProbe === 'absent') {
|
|
188
252
|
row.caveats = [...(row.caveats ?? []), MEMORY_BEHIND_NOTE];
|
|
253
|
+
} else if (templateProbe === 'unknown') {
|
|
254
|
+
// Could not verify → this row must not be counted "checked, current" by the verdict (INV-B).
|
|
255
|
+
row.freshness = FRESH_UNKNOWN;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Bridge freshness probe (INV-A / INV-B): only for a provably-OURS placed bridge (manifestState
|
|
259
|
+
// OK). Compares two LOCAL files; a non-OK / absent bridge is out of scope ('not-checked').
|
|
260
|
+
if (row.kind === 'execution-backend' && row.manifestState === OK && row.skillDir) {
|
|
261
|
+
const bundled = readBundledBridgeVersion(row.name, deps);
|
|
262
|
+
const cmp = compareSemver(row.version, bundled);
|
|
263
|
+
if (cmp === null) {
|
|
264
|
+
const { side, recovery } = parseSemver(row.version) === null ? UNKNOWN_SIDES.placed : UNKNOWN_SIDES.bundled;
|
|
265
|
+
row.freshness = FRESH_UNKNOWN;
|
|
266
|
+
row.caveats = [...(row.caveats ?? []), bridgeUnknownNote(displayOf(row.name), side, recovery)];
|
|
267
|
+
} else if (cmp < 0) {
|
|
268
|
+
row.freshness = FRESH_BEHIND;
|
|
269
|
+
row.caveats = [...(row.caveats ?? []), bridgeBehindNote(displayOf(row.name), row.version, bundled)];
|
|
270
|
+
} else {
|
|
271
|
+
// Equal OR newer-than-bundled → not behind. A newer placed bridge is a Phase-2 concern for
|
|
272
|
+
// the refresh DRIVER (INV-D never-downgrade skip); the read-only status axis never flags it.
|
|
273
|
+
row.freshness = FRESH_CURRENT;
|
|
189
274
|
}
|
|
190
275
|
}
|
|
191
276
|
return row;
|
|
@@ -376,18 +461,31 @@ export const surveyBridges = (deps = {}) => {
|
|
|
376
461
|
});
|
|
377
462
|
};
|
|
378
463
|
|
|
379
|
-
// INV-2 (structural refresh — additive, derived, never parsed from a caveat string)
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
464
|
+
// INV-2 (structural refresh — additive, derived, never parsed from a caveat string), per KIND:
|
|
465
|
+
// • memory / engine (REFRESHABLE_KINDS): `behind` ⟷ an OK row carrying a refresh-recommending
|
|
466
|
+
// caveat (surveyFamily attaches caveats to those kinds only then, and every such caveat is
|
|
467
|
+
// refresh-recommending); `recommend` is composed from FAMILY_MEMBERS[].npm — never the caveat text.
|
|
468
|
+
// • execution-backend: `behind` ⟷ the bridge freshness probe's row field (freshness === 'behind');
|
|
469
|
+
// `recommend` is composed PER-KIND — bridges have npm:null (placed by `setup`, never npx), so the
|
|
470
|
+
// runnable recovery is `/agent-workflow-kit setup`, never an npx composition.
|
|
471
|
+
// • composition-root (the kit): no freshness probe exists on this surface (the two-axes doctrine —
|
|
472
|
+
// kit freshness is the npx installer's axis) → never behind, 'not-checked'.
|
|
473
|
+
// `freshness` is the checked-vs-unknown signal the zero-behind verdict scopes itself with (INV-C):
|
|
474
|
+
// behind:false alone cannot distinguish "checked, current" from "could not be checked".
|
|
386
475
|
const REFRESHABLE_KINDS = new Set(['memory-substrate', 'methodology-engine']);
|
|
387
476
|
const npmOf = (name) => FAMILY_MEMBERS.find((m) => m.name === name)?.npm ?? null;
|
|
477
|
+
export const BRIDGE_REFRESH_RECOMMEND = '/agent-workflow-kit setup';
|
|
388
478
|
const refreshOf = (m) => {
|
|
479
|
+
if (m.kind === 'execution-backend') {
|
|
480
|
+
const freshness = m.freshness ?? FRESH_NOT_CHECKED;
|
|
481
|
+
const behind = freshness === FRESH_BEHIND;
|
|
482
|
+
return { behind, recommend: behind ? BRIDGE_REFRESH_RECOMMEND : null, freshness };
|
|
483
|
+
}
|
|
389
484
|
const behind = REFRESHABLE_KINDS.has(m.kind) && Boolean(m.caveats?.length);
|
|
390
|
-
|
|
485
|
+
const freshness = REFRESHABLE_KINDS.has(m.kind) && m.manifestState === OK
|
|
486
|
+
? m.freshness ?? (behind ? FRESH_BEHIND : FRESH_CURRENT)
|
|
487
|
+
: FRESH_NOT_CHECKED;
|
|
488
|
+
return { behind, recommend: behind ? `npx ${npmOf(m.name)}@latest init` : null, freshness };
|
|
391
489
|
};
|
|
392
490
|
|
|
393
491
|
export const buildEnvelope = (family, project = null, extras = {}) => {
|
|
@@ -399,7 +497,7 @@ export const buildEnvelope = (family, project = null, extras = {}) => {
|
|
|
399
497
|
state: STATE_PUBLIC[m.manifestState] ?? STATE_PUBLIC[UNKNOWN],
|
|
400
498
|
};
|
|
401
499
|
if (m.caveats?.length) entry.notes = m.caveats; // plain-language observations (Steps 2.2 / engine)
|
|
402
|
-
entry.refresh = refreshOf(m); // additive (INV-1): always present; { behind, recommend } (INV-2)
|
|
500
|
+
entry.refresh = refreshOf(m); // additive (INV-1): always present; { behind, recommend, freshness } (INV-2)
|
|
403
501
|
return entry;
|
|
404
502
|
});
|
|
405
503
|
const envelope = { deploymentHead: EXPECTED_WORKFLOW_VERSION, installed };
|
package/tools/labels.mjs
CHANGED
|
@@ -41,6 +41,19 @@ export const STATE_PUBLIC = Object.freeze({
|
|
|
41
41
|
// "hidden fence" / marker terms. ('hidden' here is the PUBLIC visibility word, not the internal fence.)
|
|
42
42
|
export const VISIBILITY_PUBLIC = Object.freeze({ visible: 'visible', hidden: 'hidden', ambiguous: 'unclear' });
|
|
43
43
|
|
|
44
|
+
// ── refresh freshness tokens (PUBLIC — the envelope's installed[].refresh.freshness) ────────────────
|
|
45
|
+
// The checked-vs-unknown signal the zero-behind verdict scopes itself with (INV-C): `behind:false`
|
|
46
|
+
// alone cannot distinguish "checked, current" from "could not be checked".
|
|
47
|
+
// 'current' / 'behind' — a freshness probe RAN and concluded (these two are the "checked" scope);
|
|
48
|
+
// 'unknown' — a probe ran but could not conclude (INV-B: never collapsed to a claim
|
|
49
|
+
// in either direction);
|
|
50
|
+
// 'not-checked' — no freshness probe exists for this member/state (e.g. the kit itself:
|
|
51
|
+
// its freshness is the npx installer's axis, never checked here).
|
|
52
|
+
export const FRESH_CURRENT = 'current';
|
|
53
|
+
export const FRESH_BEHIND = 'behind';
|
|
54
|
+
export const FRESH_UNKNOWN = 'unknown';
|
|
55
|
+
export const FRESH_NOT_CHECKED = 'not-checked';
|
|
56
|
+
|
|
44
57
|
// Short, user-facing labels for the version block (kit · memory · engine · codex-bridge · …), so the
|
|
45
58
|
// render is deterministic and the agent never invents a label.
|
|
46
59
|
export const DISPLAY_NAMES = Object.freeze({
|
package/tools/recipes.mjs
CHANGED
|
@@ -283,21 +283,43 @@ export const resolveActivityRecipe = ({ config = {}, readiness = [], activity, s
|
|
|
283
283
|
};
|
|
284
284
|
};
|
|
285
285
|
|
|
286
|
+
// ── the one-line backend status (deterministic-first: the tool speaks, the agent pastes) ───────────
|
|
287
|
+
|
|
288
|
+
// composeStatusLine(detection, recommendation) → the ENTIRE one-line backend-status summary the
|
|
289
|
+
// bootstrap/upgrade report footers print. Machine-composed so the agent pastes it verbatim and
|
|
290
|
+
// composes NOTHING factual (this closes the realistic-example contamination class: a session once
|
|
291
|
+
// echoed SKILL.md's canonical example while the detector said otherwise). Display names come from
|
|
292
|
+
// DISPLAY_ALIASES — the ONE alias table the recommendation clause already uses; ordering is the
|
|
293
|
+
// deterministic BACKEND_PRIORITY (codex before agy), independent of detection emission order.
|
|
294
|
+
// Always exactly one line: no part may carry a newline (pinned by tests).
|
|
295
|
+
export const composeStatusLine = (detection, recommendation) => {
|
|
296
|
+
const backends = [...detection]
|
|
297
|
+
.sort((a, b) => priorityIndex(a.name) - priorityIndex(b.name))
|
|
298
|
+
.map((b) => `${DISPLAY_ALIASES[b.name] ?? b.name} ${b.readiness === READY ? '✓' : '✗'} ${b.readiness}`)
|
|
299
|
+
.join(' · ');
|
|
300
|
+
return `backends: ${backends} — run /agent-workflow-kit backends · recipes: ${recommendation.clause} — see /agent-workflow-kit recipes`;
|
|
301
|
+
};
|
|
302
|
+
|
|
286
303
|
// ── report + CLI ─────────────────────────────────────────────────────────────────
|
|
287
304
|
|
|
288
|
-
// The structured report behind `--json` — the recipes, the recommendation,
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
role,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
})
|
|
305
|
+
// The structured report behind `--json` — the recipes, the recommendation, a plan per recipe, and
|
|
306
|
+
// (additive) the pasteable one-line backend status composed from the same detection.
|
|
307
|
+
export const buildReport = (detection) => {
|
|
308
|
+
const recommendation = recommendRecipe(detection);
|
|
309
|
+
return {
|
|
310
|
+
recipes: RECIPES.map(({ id, title, role, minBackends, degradesTo, summary }) => ({
|
|
311
|
+
id,
|
|
312
|
+
title,
|
|
313
|
+
role,
|
|
314
|
+
minBackends,
|
|
315
|
+
degradesTo,
|
|
316
|
+
summary,
|
|
317
|
+
})),
|
|
318
|
+
recommendation,
|
|
319
|
+
plans: RECIPES.map((r) => planRecipe(r.id, detection)),
|
|
320
|
+
statusLine: composeStatusLine(detection, recommendation),
|
|
321
|
+
};
|
|
322
|
+
};
|
|
301
323
|
|
|
302
324
|
// formatRecipes(detection) → deterministic human advisor text: the four recipes, the recommendation,
|
|
303
325
|
// and the per-recipe plan for the current environment (degradation reasons + dispatch + notes).
|
|
@@ -320,20 +342,37 @@ export const formatRecipes = (detection) => {
|
|
|
320
342
|
return lines.join('\n');
|
|
321
343
|
};
|
|
322
344
|
|
|
345
|
+
// The full argv vocabulary — anything else rejects LOUDLY. The old parse silently routed unknown
|
|
346
|
+
// args into the multi-line human render; with `--status-line` (whose output is pasted as fact) a
|
|
347
|
+
// mistyped flag masquerading as a mode would be a silent failure, so the parse is strict now.
|
|
348
|
+
const KNOWN_ARGS = new Set(['--help', '-h', '--json', '--status-line']);
|
|
349
|
+
|
|
323
350
|
const main = (argv) => {
|
|
324
351
|
if (argv.includes('--help') || argv.includes('-h')) {
|
|
325
352
|
console.log(`recipes — read-only orchestration-recipe advisor for the agent-workflow family.
|
|
326
353
|
|
|
327
354
|
Usage:
|
|
328
|
-
node recipes.mjs [--json]
|
|
355
|
+
node recipes.mjs [--json | --status-line]
|
|
329
356
|
|
|
330
357
|
Lists the four recipes (Solo / Reviewed / Council / Delegated) and, from the read-only backend
|
|
331
|
-
detector, plans + recommends one for the current environment.
|
|
332
|
-
|
|
358
|
+
detector, plans + recommends one for the current environment. --status-line prints exactly ONE
|
|
359
|
+
line — the machine-composed backend-status summary the bootstrap/upgrade reports paste verbatim.
|
|
360
|
+
--json emits the structured report (incl. the same line as \`statusLine\`); the two are mutually
|
|
361
|
+
exclusive. Detection only — never writes, never commits, never runs a subscription CLI.`);
|
|
333
362
|
return;
|
|
334
363
|
}
|
|
364
|
+
const unknown = argv.find((a) => !KNOWN_ARGS.has(a));
|
|
365
|
+
if (unknown !== undefined) {
|
|
366
|
+
console.error(`[agent-workflow-kit] unknown argument: ${unknown}`);
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
if (argv.includes('--json') && argv.includes('--status-line')) {
|
|
370
|
+
console.error('[agent-workflow-kit] --json and --status-line are mutually exclusive — pick one output');
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
335
373
|
const detection = detectBackends();
|
|
336
|
-
if (argv.includes('--
|
|
374
|
+
if (argv.includes('--status-line')) console.log(composeStatusLine(detection, recommendRecipe(detection)));
|
|
375
|
+
else if (argv.includes('--json')) console.log(JSON.stringify(buildReport(detection), null, 2));
|
|
337
376
|
else console.log(formatRecipes(detection));
|
|
338
377
|
};
|
|
339
378
|
|
package/tools/renderers.mjs
CHANGED
|
@@ -34,7 +34,18 @@ const renderMembers = (vm, { color, glyph }) => {
|
|
|
34
34
|
// refresh.behind drives a HEADLINE COUNT only — the recovery command stays in the verbatim notes
|
|
35
35
|
// above (the direct CLI never dedupes, never re-prints it). Omitted when nothing is behind.
|
|
36
36
|
if (vm.headline.behind > 0) {
|
|
37
|
-
|
|
37
|
+
// A mixed behind+unknown state must not swallow the unknown count (INV-B): append it here so an
|
|
38
|
+
// uncheckable member never disappears behind the refresh headline.
|
|
39
|
+
const unknownTail = vm.headline.unknown > 0 ? ` · ${vm.headline.unknown} could not be checked` : '';
|
|
40
|
+
lines.push(` ${vm.headline.behind} member(s) need a refresh (see the ${glyph.note} notes above)${unknownTail}.`);
|
|
41
|
+
} else if (vm.headline.unknown > 0) {
|
|
42
|
+
// INV-B × INV-C: an uncheckable member BLOCKS the all-current verdict — state the split, never a
|
|
43
|
+
// blanket freshness claim in either direction.
|
|
44
|
+
lines.push(` freshness: ${vm.headline.checked} member(s) checked and current · ${vm.headline.unknown} could not be checked.`);
|
|
45
|
+
} else if (vm.headline.checked > 0) {
|
|
46
|
+
// INV-C: the zero-behind verdict is composed by the TOOL, scoped to what was actually checked —
|
|
47
|
+
// the agent has no gap to fill with an "everything is fresh" gloss.
|
|
48
|
+
lines.push(` freshness: all ${vm.headline.checked} checked member(s) are current.`);
|
|
38
49
|
}
|
|
39
50
|
return lines;
|
|
40
51
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// semver-lite.mjs — the dependency-free semver LEAF shared by the npx installer (the never-downgrade
|
|
2
|
+
// gate, bin/install.mjs) and the family registry (the bridge freshness probe).
|
|
3
|
+
//
|
|
4
|
+
// Parses the leading `x.y.z` only (prerelease/build ignored — family versions are plain).
|
|
5
|
+
// compareSemver returns -1 | 0 | 1, or null when EITHER side is unparseable. The null contract is
|
|
6
|
+
// load-bearing at both call sites: a legacy install predates any version stamp (→ no gate), and a
|
|
7
|
+
// freshness probe that cannot parse a version must degrade to "unknown" — never to a false ordering
|
|
8
|
+
// claim in either direction (INV-B). No `let`: a small functional comparison (AGENTS.md §2.3).
|
|
9
|
+
//
|
|
10
|
+
// Pure, no imports, no side effects, Node >= 18.
|
|
11
|
+
|
|
12
|
+
export const parseSemver = (str) => {
|
|
13
|
+
const m = typeof str === 'string' ? str.trim().match(/^(\d+)\.(\d+)\.(\d+)/) : null;
|
|
14
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const compareSemver = (a, b) => {
|
|
18
|
+
const pa = parseSemver(a);
|
|
19
|
+
const pb = parseSemver(b);
|
|
20
|
+
if (!pa || !pb) return null;
|
|
21
|
+
const firstDiff = [0, 1, 2].map((i) => (pa[i] === pb[i] ? 0 : pa[i] < pb[i] ? -1 : 1)).find((c) => c !== 0);
|
|
22
|
+
return firstDiff ?? 0;
|
|
23
|
+
};
|
package/tools/setup-backends.mjs
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
// symlinks. Binary install + the interactive subscription login stay GUIDED (printed, never run) —
|
|
6
6
|
// guideFor() supplies the exact commands. The read-only detector is the reader; this is the writer.
|
|
7
7
|
//
|
|
8
|
+
// Two write modes: plain setup (opt-in, in-agent — the ONLY path that ever PLACES a bridge) and the
|
|
9
|
+
// refresh-only driver (`--refresh-placed` / refreshPlacedBridges, run by `init` and Mode: upgrade) —
|
|
10
|
+
// it refreshes what setup already placed and re-links wrappers, states an absent bridge as a skip
|
|
11
|
+
// (never a first placement), and never downgrades: a placed bridge newer than the kit-bundled mirror
|
|
12
|
+
// is a stated skip naming the kit update (guarded again at the write boundary in placeSkill).
|
|
13
|
+
//
|
|
8
14
|
// Safety posture (AD-011): drive off the decoupled axes (a per-bindir wrapper check + an independent
|
|
9
15
|
// skill-dir inspection), never the detector's collapsed readiness. REFRESH only a dir we provably own
|
|
10
16
|
// (valid manifest, name+kind match) or one that is absent/empty; STOP on anything else (a marker fs
|
|
@@ -27,6 +33,7 @@ import os from 'node:os';
|
|
|
27
33
|
import { KNOWN_BACKENDS, detectBackend, detectBackends, resolveDir, guideFor, READY } from './detect-backends.mjs';
|
|
28
34
|
import { copyTreeRefresh, linkManaged } from './fs-safe.mjs';
|
|
29
35
|
import { validateManifest, readAuthoritativeVersion, UNSUPPORTED, INVALID } from './manifest/validate.mjs';
|
|
36
|
+
import { compareSemver } from './semver-lite.mjs';
|
|
30
37
|
|
|
31
38
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
39
|
// bridges/ ships beside tools/ in both the repo and the installed kit, so this resolves in both.
|
|
@@ -215,11 +222,43 @@ const inspectDst = (dst, source, fs) => {
|
|
|
215
222
|
|
|
216
223
|
// ── mutating primitives ───────────────────────────────────────────────────────
|
|
217
224
|
|
|
225
|
+
// The one kit-update recovery the never-downgrade skip names (the bundle only gets newer via the kit).
|
|
226
|
+
const KIT_UPDATE_RECOVERY = 'npx @sabaiway/agent-workflow-kit@latest init';
|
|
227
|
+
|
|
228
|
+
// Crash-safe placed-version read for the downgrade guard: an unreadable placed version yields null →
|
|
229
|
+
// compareSemver returns null → no ordering claim → the refresh stays allowed (legacy repair). The
|
|
230
|
+
// guard forbids only a PROVEN downgrade; it never turns an unreadable stamp into a false refusal.
|
|
231
|
+
const readPlacedVersionSafe = (skillDir, deps = {}) => {
|
|
232
|
+
const readVersion = deps.readVersion ?? readAuthoritativeVersion;
|
|
233
|
+
try {
|
|
234
|
+
return readVersion(skillDir).version ?? null;
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Never-downgrade guard at the WRITE boundary (belt to planFor's braces — both read the versions
|
|
241
|
+
// fresh): a placed bridge NEWER than the kit-bundled mirror (an older npx runner / a kit downgrade)
|
|
242
|
+
// is a typed STOP naming the kit update, never a copy. Load-bearing once init/upgrade run the
|
|
243
|
+
// refresh automatically.
|
|
244
|
+
const downgradeReason = (placed, bundled) =>
|
|
245
|
+
`placed bridge is v${placed} but this kit bundles the older v${bundled} — refusing to downgrade; ` +
|
|
246
|
+
`update the kit first: ${KIT_UPDATE_RECOVERY}`;
|
|
247
|
+
const assertNoDowngrade = (plan, deps = {}) => {
|
|
248
|
+
if (plan.action !== 'refresh') return;
|
|
249
|
+
const placed = readPlacedVersionSafe(plan.skillDir, deps);
|
|
250
|
+
const manifest = readBundledManifest(plan.bundleDir, deps);
|
|
251
|
+
const bundled = typeof manifest.version === 'string' ? manifest.version : null;
|
|
252
|
+
// `wouldDowngrade` lets a caller classify this STOP structurally (never by matching message text).
|
|
253
|
+
if (compareSemver(placed, bundled) === 1) throw stop(downgradeReason(placed, bundled), { skillDir: plan.skillDir, wouldDowngrade: true });
|
|
254
|
+
};
|
|
255
|
+
|
|
218
256
|
// Place/refresh the bundled bridge skill. Re-inspects before writing (never trusts a stale plan).
|
|
219
257
|
export const placeSkill = (name, deps = {}) => {
|
|
220
258
|
const entry = registryEntry(name);
|
|
221
259
|
if (!entry) throw stop(`unknown backend: ${name}`);
|
|
222
260
|
const plan = inspectSkillDir(entry, deps);
|
|
261
|
+
assertNoDowngrade(plan, deps);
|
|
223
262
|
copyTreeRefresh(plan.bundleDir, plan.skillDir, plan.skillDir, fsDeps(deps));
|
|
224
263
|
return plan;
|
|
225
264
|
};
|
|
@@ -326,6 +365,17 @@ export const planFor = (backend, deps = {}) => {
|
|
|
326
365
|
// setup never invents a second version reader. A place / absent-prior shows no arrow (never "vnull").
|
|
327
366
|
version = typeof bundledManifest.version === 'string' ? bundledManifest.version : null;
|
|
328
367
|
priorVersion = place.action === 'refresh' ? readVersion(skillDir).version ?? null : null;
|
|
368
|
+
// Never-downgrade (predicted here so a --dry-run is faithful and a real run stops before any
|
|
369
|
+
// write; placeSkill re-checks at the write boundary). An unparseable side → compareSemver null →
|
|
370
|
+
// no ordering claim → the refresh stays allowed (legacy repair). `wouldDowngrade` lets the
|
|
371
|
+
// refresh-only driver report this as a stated skip rather than a failure.
|
|
372
|
+
if (place.action === 'refresh' && compareSemver(priorVersion, version) === 1) {
|
|
373
|
+
return {
|
|
374
|
+
name: entry.name, skillDir, bindir, platform, place, links: [], guides: [], bindirHint: null,
|
|
375
|
+
outcome: 'stop', wouldDowngrade: true, version, priorVersion,
|
|
376
|
+
reason: downgradeReason(priorVersion, version),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
329
379
|
const derived = deriveLinks(bundledManifest, skillDir);
|
|
330
380
|
// Preflight the BUNDLE sources (what we will copy → link). After place, the skill source IS the
|
|
331
381
|
// bundle source, so checking it here makes a dry-run faithfully predict linkWrappers instead of
|
|
@@ -401,6 +451,77 @@ const formatBackend = (plan, applied) => {
|
|
|
401
451
|
return lines.join('\n');
|
|
402
452
|
};
|
|
403
453
|
|
|
454
|
+
// ── the refresh-only driver (the init/upgrade delivery hook — refresh, never place) ──
|
|
455
|
+
|
|
456
|
+
// Refresh-only apply: re-inspects at APPLY time and copies ONLY when the fresh inspection still says
|
|
457
|
+
// `refresh` (TOCTOU: a dir gone absent between plan and apply is a reported skip, NEVER a first
|
|
458
|
+
// placement — placement stays setup's opt-in step, AD-009/AD-011), and re-asserts the downgrade
|
|
459
|
+
// guard against a freshly-read placed version (a NEWER bridge landing between plan and apply is a
|
|
460
|
+
// typed STOP, never overwritten). No cross-process lock beyond that: like every mutating primitive
|
|
461
|
+
// here (see applyBackend), concurrent writers are resolved by CONVERGE-ON-RE-RUN — the Phase-1
|
|
462
|
+
// freshness probe flags any interleaving loser as behind and the next init/upgrade/setup repairs it;
|
|
463
|
+
// a per-bridge lock file would be new machinery for a user-driven, self-healing race.
|
|
464
|
+
const refreshSkillOnly = (entry, deps = {}) => {
|
|
465
|
+
const fresh = inspectSkillDir(entry, deps);
|
|
466
|
+
if (fresh.action !== 'refresh') return false;
|
|
467
|
+
assertNoDowngrade(fresh, deps);
|
|
468
|
+
copyTreeRefresh(fresh.bundleDir, fresh.skillDir, fresh.skillDir, fsDeps(deps));
|
|
469
|
+
return true;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const NOT_PLACED_LINE = 'skipped — not placed (placement is opt-in: /agent-workflow-kit setup)';
|
|
473
|
+
const stripPrefix = (message) => message.replace('[agent-workflow-kit] ', '');
|
|
474
|
+
|
|
475
|
+
// Refresh every ALREADY-PLACED bridge from the kit's bundled copies and re-link its wrappers (a newer
|
|
476
|
+
// bridge can add one). One reported outcome per backend — never a crash; a per-backend STOP/error
|
|
477
|
+
// becomes a `failed` result. `line` is the tool-composed sentence callers print VERBATIM (the agent
|
|
478
|
+
// pastes, never composes — deterministic-first). Outcomes: refreshed · already-current · not-placed
|
|
479
|
+
// (absent — NEVER placed here) · kept-newer (INV-D stated skip) · unsupported (win32) · failed.
|
|
480
|
+
export const refreshPlacedBridges = (deps = {}, names = KNOWN_BACKENDS.map((b) => b.name)) =>
|
|
481
|
+
names.map((name) => {
|
|
482
|
+
const canonical = resolveBackendName(name);
|
|
483
|
+
// The whole per-backend path is crash-proof, not just the apply: planFor itself can throw on a
|
|
484
|
+
// truly unexpected dependency error, and one broken backend must never abort the others' report.
|
|
485
|
+
try {
|
|
486
|
+
const plan = planFor(name, deps);
|
|
487
|
+
if (plan.outcome === 'unsupported') {
|
|
488
|
+
return { name: plan.name, outcome: 'unsupported', line: ` ${plan.name}: skipped — ${plan.reason}` };
|
|
489
|
+
}
|
|
490
|
+
if (plan.wouldDowngrade) {
|
|
491
|
+
return { name: plan.name, outcome: 'kept-newer', line: ` ${plan.name}: skipped — ${stripPrefix(plan.reason)}` };
|
|
492
|
+
}
|
|
493
|
+
// An absent/empty skill dir is `not placed` REGARDLESS of any later plan trouble (a foreign
|
|
494
|
+
// wrapper conflict, a bundle-source problem): the refresh-only driver skips an unplaced bridge
|
|
495
|
+
// before those axes matter, so it must never claim "could not refresh" what it would not touch.
|
|
496
|
+
if (plan.place && plan.place.action !== 'refresh') {
|
|
497
|
+
return { name: plan.name, outcome: 'not-placed', line: ` ${plan.name}: ${NOT_PLACED_LINE}` };
|
|
498
|
+
}
|
|
499
|
+
if (plan.outcome === 'stop' || plan.outcome === 'error') {
|
|
500
|
+
return { name: plan.name, outcome: 'failed', line: ` ${plan.name}: could not refresh — ${stripPrefix(plan.reason)}; recover with /agent-workflow-kit setup` };
|
|
501
|
+
}
|
|
502
|
+
if (!refreshSkillOnly(registryEntry(plan.name), deps)) {
|
|
503
|
+
return { name: plan.name, outcome: 'not-placed', line: ` ${plan.name}: ${NOT_PLACED_LINE}` };
|
|
504
|
+
}
|
|
505
|
+
const manifest = readBundledManifest(plan.place.bundleDir, deps);
|
|
506
|
+
linkWrappers(plan.skillDir, manifest, { ...deps, bindir: plan.bindir, platform: plan.platform });
|
|
507
|
+
const current = plan.version !== null && plan.priorVersion === plan.version;
|
|
508
|
+
// The equal-version line still states the copy that ran (repair-on-rerun) — the tool never
|
|
509
|
+
// reports a mutation-free "already current" while it re-synced files underneath.
|
|
510
|
+
return {
|
|
511
|
+
name: plan.name,
|
|
512
|
+
outcome: current ? 'already-current' : 'refreshed',
|
|
513
|
+
line: ` ${plan.name}: ${current ? `already current${versionLabel(plan)} — files re-synced from the bundled copy` : `refreshed${versionLabel(plan)}`}`,
|
|
514
|
+
};
|
|
515
|
+
} catch (err) {
|
|
516
|
+
// A downgrade STOP raised at the write boundary (a newer bridge landed between plan and apply)
|
|
517
|
+
// is the same stated skip as the planned one — classified structurally via the typed field.
|
|
518
|
+
if (err.wouldDowngrade) {
|
|
519
|
+
return { name: canonical, outcome: 'kept-newer', line: ` ${canonical}: skipped — ${stripPrefix(err.message)}` };
|
|
520
|
+
}
|
|
521
|
+
return { name: canonical, outcome: 'failed', line: ` ${canonical}: could not refresh — ${stripPrefix(err.message)}; recover with /agent-workflow-kit setup` };
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
404
525
|
// ── close-the-loop: surface versions + a proactive recipe offer ───────────────────
|
|
405
526
|
|
|
406
527
|
// The closing pointer: setup surfaces only the bridge it touched; the full family + deployment version
|
|
@@ -438,23 +559,28 @@ export const proactiveReviewOffer = (before, after) => {
|
|
|
438
559
|
|
|
439
560
|
// ── CLI ─────────────────────────────────────────────────────────────────────────
|
|
440
561
|
|
|
441
|
-
const USAGE = `usage: setup-backends [<backend>] [--bindir <path>] [--dry-run] [--help]
|
|
562
|
+
const USAGE = `usage: setup-backends [<backend>] [--bindir <path>] [--dry-run | --refresh-placed] [--help]
|
|
442
563
|
|
|
443
|
-
<backend>
|
|
444
|
-
--bindir
|
|
445
|
-
--dry-run
|
|
446
|
-
--
|
|
564
|
+
<backend> codex | agy | antigravity | codex-cli-bridge | antigravity-cli-bridge (default: all)
|
|
565
|
+
--bindir where to link the wrappers (default: ~/.local/bin)
|
|
566
|
+
--dry-run print the plan; change nothing
|
|
567
|
+
--refresh-placed refresh ONLY the already-placed bridges from the kit's bundled copies —
|
|
568
|
+
an absent bridge is a stated skip (never placed), a placed bridge newer
|
|
569
|
+
than the bundle is a stated skip (never downgraded). The refresh-only
|
|
570
|
+
mode init/upgrade run; does not combine with --dry-run.
|
|
571
|
+
--help, -h this help
|
|
447
572
|
|
|
448
573
|
Places the bundled bridge skill + links its wrappers (idempotent; refuses to clobber a non-symlink).
|
|
449
574
|
Binary install + the interactive subscription login stay manual — the printed guidance has the exact
|
|
450
575
|
commands. The detector ("/agent-workflow-kit backends") stays read-only; this is the only writer.`;
|
|
451
576
|
|
|
452
577
|
const parseArgs = (argv) => {
|
|
453
|
-
const out = { dryRun: false, help: false, bindir: undefined, backend: undefined, bad: null };
|
|
578
|
+
const out = { dryRun: false, refreshPlaced: false, help: false, bindir: undefined, backend: undefined, bad: null };
|
|
454
579
|
for (let i = 0; i < argv.length; i += 1) {
|
|
455
580
|
const a = argv[i];
|
|
456
581
|
if (a === '--help' || a === '-h') out.help = true;
|
|
457
582
|
else if (a === '--dry-run') out.dryRun = true;
|
|
583
|
+
else if (a === '--refresh-placed') out.refreshPlaced = true;
|
|
458
584
|
else if (a === '--bindir') {
|
|
459
585
|
// Do NOT greedily swallow a following flag (e.g. `--bindir --dry-run` must not become
|
|
460
586
|
// bindir="--dry-run" and silently mutate); a missing or flag-like value is a usage error.
|
|
@@ -482,6 +608,7 @@ export const main = (argv = process.argv.slice(2), deps = {}) => {
|
|
|
482
608
|
log(USAGE);
|
|
483
609
|
return 0;
|
|
484
610
|
}
|
|
611
|
+
if (args.refreshPlaced && args.dryRun) args.bad = '--refresh-placed does not combine with --dry-run';
|
|
485
612
|
if (args.bad) {
|
|
486
613
|
errlog(args.bad);
|
|
487
614
|
errlog(USAGE);
|
|
@@ -501,6 +628,16 @@ export const main = (argv = process.argv.slice(2), deps = {}) => {
|
|
|
501
628
|
}
|
|
502
629
|
|
|
503
630
|
const runDeps = { ...deps, bindir: args.bindir ?? deps.bindir };
|
|
631
|
+
|
|
632
|
+
// Refresh-only mode: act on what setup already placed; never place, never downgrade. Every line is
|
|
633
|
+
// tool-composed — callers (init, Mode: upgrade) print/paste them verbatim.
|
|
634
|
+
if (args.refreshPlaced) {
|
|
635
|
+
log('agent-workflow placed-bridge refresh (refresh-only — placement stays opt-in: /agent-workflow-kit setup)');
|
|
636
|
+
const results = refreshPlacedBridges(runDeps, targets);
|
|
637
|
+
for (const r of results) log(r.line);
|
|
638
|
+
return results.some((r) => r.outcome === 'failed') ? 1 : 0;
|
|
639
|
+
}
|
|
640
|
+
|
|
504
641
|
log(args.dryRun ? 'agent-workflow backend setup — DRY RUN (no changes)' : 'agent-workflow backend setup (link-only)');
|
|
505
642
|
// Snapshot review readiness BEFORE applying, so we can offer the recipe only on a true readiness flip.
|
|
506
643
|
const reviewBefore = args.dryRun ? null : reviewReadyCount(runDeps);
|
package/tools/view-model.mjs
CHANGED
|
@@ -21,6 +21,9 @@ const memberVm = (m) => {
|
|
|
21
21
|
notes: m.notes ?? [], // the verbatim caveats — printed as ↳ sub-lines (INV-3: no dedupe)
|
|
22
22
|
behind: Boolean(refresh.behind), // used ONLY for the headline count on the direct CLI (INV-3)
|
|
23
23
|
recommend: refresh.recommend ?? null,
|
|
24
|
+
// The checked-vs-unknown freshness signal (INV-C): 'current' | 'behind' | 'unknown' | 'not-checked'.
|
|
25
|
+
// An envelope predating the field defaults to not-checked (behind stays behind) — never a claim.
|
|
26
|
+
freshness: refresh.freshness ?? (refresh.behind ? 'behind' : 'not-checked'),
|
|
24
27
|
};
|
|
25
28
|
};
|
|
26
29
|
|
|
@@ -82,7 +85,14 @@ export const toViewModel = (envelope = {}) => {
|
|
|
82
85
|
return {
|
|
83
86
|
deploymentHead: envelope.deploymentHead ?? null,
|
|
84
87
|
members,
|
|
85
|
-
headline: {
|
|
88
|
+
headline: {
|
|
89
|
+
total: members.length,
|
|
90
|
+
behind: members.filter((m) => m.behind).length,
|
|
91
|
+
// The zero-behind verdict scope (INV-C): checked = a freshness probe RAN and concluded;
|
|
92
|
+
// unknown = it ran but could not conclude (INV-B — blocks the all-current verdict).
|
|
93
|
+
checked: members.filter((m) => m.freshness === 'current' || m.freshness === 'behind').length,
|
|
94
|
+
unknown: members.filter((m) => m.freshness === 'unknown').length,
|
|
95
|
+
},
|
|
86
96
|
bridges: envelope.bridges ? envelope.bridges.map(bridgeVm) : null,
|
|
87
97
|
project: projectVm(envelope.project),
|
|
88
98
|
};
|