@sabaiway/agent-workflow-kit 1.15.2 → 1.16.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 +34 -0
- package/README.md +2 -1
- package/SKILL.md +91 -15
- package/capability.json +1 -1
- package/package.json +1 -1
- package/tools/commands.mjs +206 -0
- package/tools/delegation.mjs +8 -0
- package/tools/family-registry.mjs +263 -13
- package/tools/procedures.mjs +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,40 @@ 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.16.0 — Onboarding & discoverability: `help`, honest versioning, an enriched `status` (kit)
|
|
8
|
+
|
|
9
|
+
A **feature** release (additive, backward-compatible). Makes the kit self-explanatory: a discoverable
|
|
10
|
+
command surface, honest installed-on-this-machine version legibility, and one `status` that answers
|
|
11
|
+
"versions + deployment + settings + bridges". The deployment-lineage head stays **`1.3.0`** — nothing
|
|
12
|
+
in the deployed `docs/ai` *structure* changed, so there is **no migration**. The kit **package**
|
|
13
|
+
version is a separate axis from that head.
|
|
14
|
+
|
|
15
|
+
- **`/agent-workflow-kit help` + safe unknown-invocation routing.** New `tools/commands.mjs`: a frozen
|
|
16
|
+
command catalog (grouped Inspect / Configure / Orchestrate / Lifecycle, each tagged read-only /
|
|
17
|
+
writer / guarded) + a pure `routeInvocation` router. A discoverable index, and the read-only landing
|
|
18
|
+
spot for any unrecognized invocation — **no unrecognized/garbage token ever reaches a writer/guarded
|
|
19
|
+
mode** (only an explicit known token, or the acknowledged bare-bootstrap exception). Drift-guarded
|
|
20
|
+
against the `### Mode:` headers.
|
|
21
|
+
- **Honest version legibility.** `tools/family-registry.mjs` gains a no-leak `--json` envelope (user-safe
|
|
22
|
+
field names only — never the internal manifest/stamp terms) feeding a shared **version block**:
|
|
23
|
+
deployment-structure head · installed package versions per member · the two-axes disambiguation when
|
|
24
|
+
the numbers coincide. An **offline caveat** flags a memory install too old to ship the current
|
|
25
|
+
orchestration template ("installed on this machine", no network). Bootstrap/upgrade now print a
|
|
26
|
+
**welcome mat** (success → version block → backend line → one caveat-aware next step) and bootstrap
|
|
27
|
+
opens with a one-line first-contact orientation.
|
|
28
|
+
- **Settings & bridges in `status`.** `status` is now the single answer to "versions + deployment +
|
|
29
|
+
settings + bridges": orchestration recipes (effective per slot), attribution
|
|
30
|
+
(`includeCoAuthoredBy`), velocity (`defaultMode`), the bridges (readiness + wrapper PATH presence,
|
|
31
|
+
no model claim), and **visibility** (visible / hidden / unclear, via `inferVisibility`) — each in
|
|
32
|
+
plain language, **localized-on-error** (a malformed file surfaces its own error, the rest still
|
|
33
|
+
renders). `loadConfig` is shared with the procedures advisor (one strict-JSON reader).
|
|
34
|
+
- **Surgical delegation gate.** `references/templates/orchestration.json` joins the memory
|
|
35
|
+
required-asset set: a memory too old to seed `docs/ai/orchestration.json` now **falls back** to the
|
|
36
|
+
kit's bundled substrate (which seeds it) instead of being delegate-classified — closing the
|
|
37
|
+
stale-memory trap the read-only note only informs about.
|
|
38
|
+
- **Tarball guard `69 → 70`** (the new shipped `tools/commands.mjs`); `npm pack --dry-run --json`
|
|
39
|
+
re-verified. No `docs/ai` structural change → lineage head unchanged.
|
|
40
|
+
|
|
7
41
|
## 1.15.2 — Strip the package's own tests + fixtures from the npm tarball (kit)
|
|
8
42
|
|
|
9
43
|
Packaging only — no API/behaviour change; removed the package's own colocated tests + fixtures from
|
package/README.md
CHANGED
|
@@ -218,9 +218,10 @@ command is printed).
|
|
|
218
218
|
|---------|------|--------------|
|
|
219
219
|
| `/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** |
|
|
220
220
|
| `/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 bridge — set one up with `/agent-workflow-kit setup` |
|
|
221
|
+
| `/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. |
|
|
221
222
|
| `/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). |
|
|
222
223
|
| `/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. |
|
|
223
|
-
| `/agent-workflow-kit status` | any time | **read-only** view of
|
|
224
|
+
| `/agent-workflow-kit status` | any time | **read-only** single view of **versions + deployment + settings + bridges**: which members (kit / memory / engine / the two bridges) are installed and at what version (with an honest "installed on this machine" note when one is behind) and — in a project — what's deployed (`docs/ai`, the version stamps, and the **visibility**: visible / hidden / unclear), plus your settings (orchestration recipes, attribution, velocity) and the bridges' readiness. The two version axes (package number vs deployment-structure head) stay decoupled. Never writes, never commits, never runs a subscription CLI. |
|
|
224
225
|
| `/agent-workflow-kit recipes` | any time | **read-only** orchestration advisor: presents four named recipes for composing the bridges into plan → execute → review — **Solo / Reviewed / Council / Delegated** — plans + recommends one for your environment (degrading with a stated reason when a backend isn't ready), and offers the choice. The orchestrator runs it via the bridge skills and **always commits**; the kit never executes a recipe, never runs a subscription CLI, never commits. |
|
|
225
226
|
| `/agent-workflow-kit procedures <activity>` | any time | **read-only** activity-procedures advisor: prints a named activity's ordered steps (`plan-authoring` / `plan-execution`) read **live** from the engine, plus the **resolved recipe per slot** from your hand-edited `docs/ai/orchestration.json` + backend readiness (default Reviewed when a backend is ready, Council on request, slot-aware incl. Delegated). `--override <slot>=<recipe>` adjusts one slot per run. Composes with `recipes`; never writes, never commits, never runs a subscription CLI. |
|
|
226
227
|
| `/agent-workflow-kit uninstall` | opt-in, any time | **guarded teardown** — the inverse of `init` / `setup`. Removes only what's **provably ours** (managed skill dirs + bridge wrappers; in a project, the hidden-mode git-ignore block it added + the pre-commit hook it installed); **never deletes** your `docs/ai` / `AGENTS.md` (prints the exact `rm` to run by hand) or your `.claude/settings.json` (prints an **edit** — remove the attribution key, review any velocity `permissions.*` — never an `rm`). Always `--dry-run` first; preflight-then-mutate; never commits. |
|
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.16.0'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-kit
|
|
@@ -35,9 +35,11 @@ validator shipped by the candidate (which could itself be broken). Delegate only
|
|
|
35
35
|
hold:
|
|
36
36
|
- result is **valid** and `kind` is `memory-substrate`;
|
|
37
37
|
- **every required asset is present** in the candidate, at its real path:
|
|
38
|
-
`references/templates/`,
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
`references/templates/`, **`references/templates/orchestration.json`** (the orchestration-config
|
|
39
|
+
template — a memory too old to ship it, pre-`1.2.0`, can't seed `docs/ai/orchestration.json`, so it
|
|
40
|
+
falls back to the kit's own bundled substrate, which does), `references/contracts.md`,
|
|
41
|
+
`references/scripts/`, `scripts/stamp-takeover.mjs`, `migrations/`, `capability.json`. A partial
|
|
42
|
+
install (manifest + `SKILL.md` only) is treated as **invalid**.
|
|
41
43
|
|
|
42
44
|
On **unsupported** (unknown schema), **invalid**, **unavailable**, **wrong-family**, or
|
|
43
45
|
**wrong-name**, **use the bundled copy** — never block. The fallback decision is final once
|
|
@@ -96,10 +98,11 @@ ever deleted.
|
|
|
96
98
|
|
|
97
99
|
## Modes
|
|
98
100
|
|
|
99
|
-
Pick the mode from the user's invocation. Auto-detect an existing `docs/ai/` to guard against bootstrapping over a live system, but the user makes the final call.
|
|
101
|
+
Pick the mode from the user's invocation. Auto-detect an existing `docs/ai/` to guard against bootstrapping over a live system, but the user makes the final call. The invocation→mode mapping is pinned by `tools/commands.mjs` `routeInvocation` (unit-tested): a **known** subcommand routes to its mode; the **bare/empty** invocation routes to `bootstrap`; **any unrecognized/ambiguous** token routes to `help`, which is **read-only** — so a garbage invocation never triggers a write (see the safe-routing rule under *Version status & the two axes*).
|
|
100
102
|
|
|
101
103
|
- **`/agent-workflow-kit`** (default) — bootstrap a new or empty project. If `docs/ai/` already exists, stop and ask whether they meant `upgrade`.
|
|
102
104
|
- **`/agent-workflow-kit upgrade`** — upgrade an existing deployment to the skill's current `version`.
|
|
105
|
+
- **`/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.
|
|
103
106
|
- **`/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.
|
|
104
107
|
- **`/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.
|
|
105
108
|
- **`/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.
|
|
@@ -110,6 +113,8 @@ Pick the mode from the user's invocation. Auto-detect an existing `docs/ai/` to
|
|
|
110
113
|
|
|
111
114
|
### Version status & the two axes — surface this on every invocation
|
|
112
115
|
|
|
116
|
+
**Safe-routing rule (which mode did the user invoke?).** Map the invocation token with `tools/commands.mjs` `routeInvocation`: a **known** subcommand → its mode; the **bare/empty** invocation → `bootstrap` — the one writer reachable without a token, and only on an undeployed project (if `docs/ai/` already exists, **ask upgrade-vs-bootstrap**, never overwrite); **any unrecognized/ambiguous** token → `help`, which is **read-only**. The invariant: **no unrecognized/garbage invocation ever triggers a write** (only an explicit known token or the acknowledged bare-bootstrap exception can). The mapping is unit-tested, so it is not left to interpretation.
|
|
117
|
+
|
|
113
118
|
Before acting, read `docs/ai/.workflow-version` (the project's stamp), state a one-line status, then route:
|
|
114
119
|
|
|
115
120
|
- **absent** → bootstrap (a fresh deployment).
|
|
@@ -151,6 +156,67 @@ readiness, then **append an actionable recipe recommendation** from the recipe p
|
|
|
151
156
|
I'm skipping the backend-status line."* — never a silent skip (Hard Constraint — no silent
|
|
152
157
|
failures).
|
|
153
158
|
|
|
159
|
+
### The version block + welcome mat (shared by bootstrap + upgrade)
|
|
160
|
+
|
|
161
|
+
Bootstrap (step 11) and **every** successful `upgrade` exit (steps 4 + 8) close with the same
|
|
162
|
+
**report footer**, in this **canonical order**: success state → **version block** → the **one-line
|
|
163
|
+
backend-status line** → **welcome mat**. Only the version block comes from
|
|
164
|
+
`node ${CLAUDE_SKILL_DIR}/tools/family-registry.mjs --json` (add `--dir <project>` for the deploy
|
|
165
|
+
axis) — **never hardcoded semver**; the backend-status line is its own shared contract (above), and
|
|
166
|
+
the welcome mat **composes signals already gathered** (the version block's notes + the backend-status
|
|
167
|
+
line), not a fresh helper call. Present everything in the user's conversational language; never paste
|
|
168
|
+
the JSON or any internal field name.
|
|
169
|
+
|
|
170
|
+
**Version block — three plain lines, fed from `--json`:**
|
|
171
|
+
- **`Deployment structure: <deploymentHead> (current)`** — the deployed `docs/ai` structure version
|
|
172
|
+
(the envelope's `deploymentHead`); **the only axis `upgrade` compares**.
|
|
173
|
+
- **`Installed on this machine — package versions:`** `kit <v> · memory <v> · engine <v> ·
|
|
174
|
+
codex-bridge <v> · antigravity-bridge <v>` — one per `installed[]` entry, labelled by its `display`
|
|
175
|
+
and showing its `version`, or, when there is no version, the plain phrase for its `state` (map
|
|
176
|
+
below). Append any `installed[].notes` **in plain words** (e.g. the memory-behind refresh+restart
|
|
177
|
+
line).
|
|
178
|
+
- **The two axes are different — say so even when the numbers coincide:** an installed *package*
|
|
179
|
+
version (e.g. engine `1.3.0`) is a package number, **not** the deployment-structure version on line
|
|
180
|
+
one (which is also `1.3.0` today). The head advances only when the deployed `docs/ai` structure
|
|
181
|
+
changes, so a higher package number on npm/GitHub is **not** a newer deployment.
|
|
182
|
+
|
|
183
|
+
**`state` → plain language** (map the envelope's `installed[].state` token; never show the raw token):
|
|
184
|
+
`installed` → its version · `absent` → "not installed" · `other-tool` → "a different tool occupies
|
|
185
|
+
that skill slot" · `placeholder` → "a placeholder, not a working install" · `invalid` → "installed
|
|
186
|
+
but its manifest didn't validate" · `unsupported` → "installed but its manifest schema is too new for
|
|
187
|
+
this kit" · `uncheckable` → "couldn't be checked (a permission error)".
|
|
188
|
+
|
|
189
|
+
**Helper-failure contract (mirror the backend-status line).** The version block needs the
|
|
190
|
+
family-registry helper, which the **agent host** runs. If the host can't run it (`node` not on the
|
|
191
|
+
agent's PATH, or the helper errors), **skip the version block and say so with the concrete reason** —
|
|
192
|
+
e.g. *"Couldn't run the family-registry helper here (node is not on the agent host PATH), so I'm
|
|
193
|
+
skipping the installed-versions block."* — never a silent skip (Hard Constraint). It is non-essential:
|
|
194
|
+
the rest of the report — and the commit gate — proceeds.
|
|
195
|
+
|
|
196
|
+
**Welcome mat — the last line(s) of the footer.** After the version block and the backend-status
|
|
197
|
+
line, print *"Run `/agent-workflow-kit help` to see every command."* then **one** recommended next
|
|
198
|
+
step, chosen **caveat-aware** from signals already in hand (the version block's notes + the
|
|
199
|
+
backend-status line — no new helper call) in this priority order:
|
|
200
|
+
1. a member is **behind** (an `installed[].notes` caveat fired — memory/engine) → *refresh the behind
|
|
201
|
+
member first* (its `npx …@latest init` + restart the session);
|
|
202
|
+
2. else **no backend is ready** (the backend-status line shows none ready) → *set one up with
|
|
203
|
+
`/agent-workflow-kit setup`*;
|
|
204
|
+
3. else **a backend is ready but the orchestration config is still all-Solo** (no `reviewed` /
|
|
205
|
+
`council` / `delegated` slot anywhere — inspect `docs/ai/orchestration.json`, or read the
|
|
206
|
+
procedures advisor's resolved recipes) → *put it to work with `/agent-workflow-kit recipes`*;
|
|
207
|
+
4. else (a backend is ready **and** a backend-backed recipe is already selected) → the optional
|
|
208
|
+
*`/agent-workflow-kit velocity`* opt-in (never run it without a yes).
|
|
209
|
+
|
|
210
|
+
Keep it compact — a few short lines, plain language, no kit-internal terms.
|
|
211
|
+
|
|
212
|
+
### Mode: help
|
|
213
|
+
|
|
214
|
+
Read-only. The single discoverable **command index** — it answers *"what can `/agent-workflow-kit` do, and which commands change things?"* It **never writes, never commits, and never runs a subscription CLI**.
|
|
215
|
+
|
|
216
|
+
Run `node ${CLAUDE_SKILL_DIR}/tools/commands.mjs` (add `--json` for the machine-readable catalog) and present its grouped index — **Inspect / Configure / Orchestrate / Lifecycle**, each command tagged **read-only / writer / guarded** — in the user's conversational language. That catalog is the **single source of truth** for the command surface (the same one the bootstrap / upgrade report footers point at); `routeInvocation(token)` in the same file is the executable contract for which invocation maps to which mode.
|
|
217
|
+
|
|
218
|
+
`help` is also the landing spot for any **unrecognized or ambiguous** invocation — and that path is **always read-only** (the safe-routing rule under *Version status & the two axes*). When you arrive here that way, render the index and, in plain language, note that the invocation wasn't recognized so nothing was changed.
|
|
219
|
+
|
|
154
220
|
### Mode: bootstrap
|
|
155
221
|
|
|
156
222
|
> Bundled sources below (templates, scripts) live in **this skill's own directory** — `${CLAUDE_SKILL_DIR}/` in Claude Code, or the folder containing this `SKILL.md` in Codex / other agents. Use that as the copy/read source; the working directory is the **target project**, not the skill.
|
|
@@ -164,6 +230,7 @@ readiness, then **append an actionable recipe recommendation** from the recipe p
|
|
|
164
230
|
- `src/` (or equivalent) 2–3 levels deep → modules, routes/pages, components, services, types.
|
|
165
231
|
- Tests (framework, location, E2E?) and linter rules.
|
|
166
232
|
- Record: stack, package manager, daily commands (`dev`/`test`/`lint`/`type-check`), routes/pages, architecture layers.
|
|
233
|
+
- **First-contact orientation — say this before the first question (read-only).** In 1–2 plain lines, tell the user what this is — a portable memory & workflow system for this repo, so future sessions boot from a small structured memory instead of re-reading everything — and that they can run **`/agent-workflow-kit help`** any time to see every command. This is the "start here"; keep it to a sentence or two, then ask the visibility question.
|
|
167
234
|
2. **Choose visibility — ASK the user explicitly and wait for the answer, before writing anything.** This decides what gets tracked and is hard to reverse after a commit, so never assume the default silently: `visible` (committed — canonical, recommended) or `hidden` (in-tree, git-ignored via the **project-local** `.git/info/exclude` — one managed block covering the full AI/agent footprint, never the machine-global excludes). See [Visibility contract](references/contracts.md#visibility-contract).
|
|
168
235
|
3. **Choose conversational language — ASK the user explicitly and wait for the answer.** Which language should the agent *talk to them* in — questions, explanations, summaries, status updates? Offer the language they're already writing in as the default. Carry the answer into the `{{COMM_LANGUAGE}}` slot of the *Communication language* block when `AGENTS.md` is created (step 5). See [Communication contract](references/contracts.md#communication-contract). This sets the **dialogue** language only — never the files.
|
|
169
236
|
4. **Choose agent attribution — ASK the user explicitly and wait for the answer.** May the agent attribute work to itself / to AI — `Co-Authored-By` trailers, "Generated with …" footers, "AI"/agent/model mentions in code, comments, commit messages, PR titles/bodies, or docs? **Default to `off`** (no agent/AI mention anywhere) unless they opt in — people are routinely surprised to find an AI listed as a repo contributor. Carry the answer into the `{{AGENT_ATTRIBUTION}}` slot of the *Attribution* block when `AGENTS.md` is created (step 5). **If `off` and the project uses Claude Code**, also set `"includeCoAuthoredBy": false` in the project's `.claude/settings.json` (create it if absent) — the trailer is added by the harness, so a doc directive alone won't stop it. See [Attribution contract](references/contracts.md#attribution-contract).
|
|
@@ -186,7 +253,7 @@ readiness, then **append an actionable recipe recommendation** from the recipe p
|
|
|
186
253
|
independent axes: a packaging-only release bumps the package but leaves the lineage head until a
|
|
187
254
|
migration actually changes the deployed `docs/ai` structure. A stamp greater than the head →
|
|
188
255
|
STOP (never downgrade).
|
|
189
|
-
11. **Report & ask.** Show `tree docs/ai/`, 2–3 lines on what was filled with real data vs left as TODO, then print the **one-line backend-status line
|
|
256
|
+
11. **Report & ask.** Show `tree docs/ai/`, 2–3 lines on what was filled with real data vs left as TODO, 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 ends on **one** recommended next step — including the optional, opt-in `/agent-workflow-kit velocity` (a read-only allowlist so routine read-only commands stop prompting; *Mode: velocity*) when nothing more pressing applies, never run without a yes. Then **ask before committing** — never auto-commit.
|
|
190
257
|
|
|
191
258
|
Fill strategy:
|
|
192
259
|
|
|
@@ -220,12 +287,12 @@ Fill strategy:
|
|
|
220
287
|
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:
|
|
221
288
|
- **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* or was *already present* (a user edit is preserved); 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).
|
|
222
289
|
- **Name the version axis so the package number isn't mistaken for a newer release.** When you state the deployment is up to date, say it is current at the **deployment-lineage head** (`1.3.0`) and add one plain line: this head versions the deployed `docs/ai` structure and is a **separate axis** from the kit **package** version published on npm/GitHub (the larger number users see on the repo/npm page). A packaging-only release can bump that package version **without** moving the head — the head advances only when the deployed `docs/ai` structure changes — so an equal-head report is **not** a stale deployment even though GitHub shows a higher package number. (This is the only place the two axes meet the user in `upgrade`; surfacing it here prevents the recurring "but GitHub shows a higher version" confusion.)
|
|
223
|
-
- **Print the one-line backend-status line
|
|
224
|
-
- **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, say **"already up to date"** and still print the read-only backend line.
|
|
290
|
+
- **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`).
|
|
291
|
+
- **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, say **"already up to date"** and still print the read-only version block + backend line.
|
|
225
292
|
5. Show the relevant `${CLAUDE_SKILL_DIR}/CHANGELOG.md` diff (entries newer than the project's stamp).
|
|
226
293
|
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.
|
|
227
294
|
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`.
|
|
228
|
-
8. Re-stamp `docs/ai/.workflow-version` to the **deployment-lineage head** (`1.3.0`, not the package version). Report changes — and when you report the stamp, **name the axis**: the head versions the deployed `docs/ai` structure and is a separate axis from the kit **package** version on npm/GitHub (the larger number users see there), so the user doesn't read the bigger package number as a newer deployment (same framing as the step 4 equal-head exit). Then **print the one-line backend-status line
|
|
295
|
+
8. Re-stamp `docs/ai/.workflow-version` to the **deployment-lineage head** (`1.3.0`, not the package version). Report changes — and when you report the stamp, **name the axis**: the head versions the deployed `docs/ai` structure and is a separate axis from the kit **package** version on npm/GitHub (the larger number users see there), so the user doesn't read the bigger package number as a newer deployment (same framing as the step 4 equal-head exit). 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**.
|
|
229
296
|
|
|
230
297
|
### Mode: backends
|
|
231
298
|
|
|
@@ -259,14 +326,22 @@ For each backend it:
|
|
|
259
326
|
|
|
260
327
|
### Mode: status
|
|
261
328
|
|
|
262
|
-
Read-only.
|
|
329
|
+
Read-only. The **single answer to "versions + deployment + settings + bridges"** across the whole family — `tools/family-registry.mjs` aggregates every member's `capability.json` and surveys the project. It **never writes, never commits, and never runs a subscription CLI**.
|
|
330
|
+
|
|
331
|
+
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:
|
|
263
332
|
|
|
264
|
-
|
|
333
|
+
1. **Versions — the version block** (from `installed[]` + `deploymentHead`): the deployment-structure head, the installed package versions by `display`, the two-axes disambiguation, plus any member `notes` (e.g. a behind member's refresh+restart line). This is the shared version block above.
|
|
334
|
+
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.
|
|
335
|
+
3. **Settings (`--dir`, one line each)** (from `project.settings`):
|
|
336
|
+
- **recipes** — the effective recipe per slot (detail → `/agent-workflow-kit procedures` / `recipes`); a `recipes.detectError` → say the backends couldn't be checked, so recipes floored at solo.
|
|
337
|
+
- **attribution** — `includeCoAuthoredBy` effective; call out a **local override** only when `local` is non-null **and** differs from `project` (a `null` `local` means the key is absent there, so the project value stands — that is not an override).
|
|
338
|
+
- **velocity** — the effective `permissions.defaultMode` + whether an allowlist is seeded (detail → `/agent-workflow-kit velocity`).
|
|
339
|
+
- Any area's **`error`** field → surface it **loudly** in plain language; the rest of `status` still renders (never a crash).
|
|
340
|
+
4. **Bridges (host, one line)** (from `bridges[]`): per bridge — readiness + wrapper PATH-presence; render each wrapper's `state` as *on PATH* (`present`) / *not on PATH* (`missing`) / *couldn't check* (`unknown`) (detail → `/agent-workflow-kit backends` / `setup`). **No default-model claim.** "credentials present" means a marker file exists, not a live login.
|
|
265
341
|
|
|
266
|
-
|
|
267
|
-
2. **Deploy axis (`--dir <project>`):** the deployment stamps (`docs/ai/.workflow-version`, `.memory-version`), whether `docs/ai/` exists, and whether the hidden-mode managed fence is present.
|
|
342
|
+
Restate the **two-axes honesty** — an installed *package* version is not a project's deployment-lineage stamp (see *Version status & the two axes*); the installed version is whatever is on disk under `~/.claude/skills/…`, so a stale install shows its real (older) version, honestly. A host that can't run the helper → **skip with the concrete reason** (the helper-failure contract), never silently.
|
|
268
343
|
|
|
269
|
-
|
|
344
|
+
**Invariants:** read-only · never writes · never commits · never runs a subscription CLI · plain language only, no leaked internal terms.
|
|
270
345
|
|
|
271
346
|
### Mode: recipes
|
|
272
347
|
|
|
@@ -354,6 +429,7 @@ The non-obvious traps — scan these before bootstrapping or upgrading. Each is
|
|
|
354
429
|
- **Hidden mode is project-local, and the hide tool owns the known footprint.** `tools/hide-footprint.mjs` writes **one managed block** in the **project-local `.git/info/exclude`** — never the machine-global `core.excludesFile` (which would silently affect every repo on the host; **AD-014** amends **AD-006**). It hides the kit's own artifacts **and** the known external AI/agent footprint (the `KNOWN_FOOTPRINT` table in [contracts](references/contracts.md#visibility-contract)). A **tracked** file is **never silently un-tracked** — the tool prints the `git rm --cached` it will not run. Never leak its internal marker / asks terms to the user; translate every outcome to plain language.
|
|
355
430
|
- **`CLAUDE.md` is a symlink, not a copy.** `ln -s AGENTS.md CLAUDE.md` — single source, no duplication. A copy drifts; a symlink can't.
|
|
356
431
|
- **Never overwrite an existing entry point or hook.** If `AGENTS.md` / `CLAUDE.md` already exist, or the installer reports a pre-existing non-marker git hook, **stop and ask** the user to merge vs replace — don't clobber.
|
|
432
|
+
- **Unrecognized invocations are read-only.** Only a **known** subcommand reaches its mode; the **bare** invocation bootstraps (and an existing `docs/ai/` makes it ask upgrade-vs-bootstrap first, never overwrite); **any other / ambiguous** token routes to `help` (read-only). A garbage invocation never writes. The mapping is pinned by `tools/commands.mjs` `routeInvocation` (unit-tested) — don't hand-route around it.
|
|
357
433
|
- **No Node runtime → skip enforcement.** If the project has no Node (recon step 1), skip bootstrap steps 8–9 (scripts + hook) and follow the cap/archive/index policy manually, or port the scripts to the project's language.
|
|
358
434
|
- **Conversational language never translates artifacts.** It governs *dialogue only*. Code, identifiers, paths, commands, log output, abbreviations, and every deployed `docs/ai/` / `AGENTS.md` file stay in their source language. See [Communication contract](references/contracts.md#communication-contract).
|
|
359
435
|
- **Never auto-commit.** Report quality-gate results and wait for explicit approval — in both modes.
|
|
@@ -415,6 +491,6 @@ Deploy these into `AGENTS.md`; remove rows that don't apply to the stack.
|
|
|
415
491
|
- [`references/scripts/`](references/scripts/) — the Node enforcement scripts (caps + staleness + index-freshness gate, 3-tier archive, hook installer) and their unit tests.
|
|
416
492
|
- [`migrations/`](migrations/) — per-version upgrade steps; see `migrations/README.md`.
|
|
417
493
|
- [`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`.
|
|
418
|
-
- [`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), `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 hand-edited `docs/ai/orchestration.json`, and prints the steps + the resolved effective recipe per slot; drift-guarded activity/slot table vs the canon; read-only, never runs a subscription CLI), `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).
|
|
494
|
+
- [`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 hand-edited `docs/ai/orchestration.json`, and prints the steps + the resolved effective recipe per slot; drift-guarded activity/slot table vs the canon; read-only, never runs a subscription CLI), `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).
|
|
419
495
|
- [`capability.json`](capability.json) — the kit's own `agent-workflow` family manifest (`kind: composition-root`).
|
|
420
496
|
- [`CHANGELOG.md`](CHANGELOG.md) — version history of this kernel.
|
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.16.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",
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// commands.mjs — the kit's canonical command catalog + the pure invocation router behind the
|
|
3
|
+
// `/agent-workflow-kit help` index and the safe unknown-invocation routing rule.
|
|
4
|
+
//
|
|
5
|
+
// Until now the modes existed only as `### Mode:` headers in SKILL.md, never enumerated for the user:
|
|
6
|
+
// there was no discoverable command surface and no executable answer to "what did the user actually
|
|
7
|
+
// invoke?". This module is the SINGLE source of truth for both — one frozen catalog (one entry per
|
|
8
|
+
// mode, grouped + tagged read-only/writer/guarded with a plain-language one-liner) that the `help`
|
|
9
|
+
// mode renders and the report footers point at, plus `routeInvocation(token)` that maps a raw
|
|
10
|
+
// subcommand token to its mode.
|
|
11
|
+
//
|
|
12
|
+
// Safety invariant (pinned by the tests): `bootstrap` is a writer and the BARE/EMPTY invocation maps
|
|
13
|
+
// to it — that is the ONE acknowledged exception, itself guarded downstream by the existing
|
|
14
|
+
// "docs/ai/ exists → ask upgrade-vs-bootstrap" check (SKILL.md Mode: bootstrap). The invariant is
|
|
15
|
+
// therefore: NO unrecognized/garbage token ever maps to a writer/guarded mode — every such token
|
|
16
|
+
// routes to `help`, which is read-only.
|
|
17
|
+
//
|
|
18
|
+
// Source of truth = the COMMANDS table below; a drift-guard test (commands.test.mjs) pins its keys to
|
|
19
|
+
// the `### Mode:` headers in SKILL.md, so the catalog cannot silently drift from the documented modes.
|
|
20
|
+
// Pure, dependency-free, Node >= 18. No side effects on import (the isDirectRun idiom).
|
|
21
|
+
|
|
22
|
+
import { pathToFileURL } from 'node:url';
|
|
23
|
+
|
|
24
|
+
const SKILL_NAME = 'agent-workflow-kit';
|
|
25
|
+
const BARE_INVOCATION = `/${SKILL_NAME}`;
|
|
26
|
+
const invocationOf = (token) => `${BARE_INVOCATION}${token ? ` ${token}` : ''}`;
|
|
27
|
+
|
|
28
|
+
// ── kinds ────────────────────────────────────────────────────────────────────────
|
|
29
|
+
// read-only — never writes, never commits, never runs a subscription CLI.
|
|
30
|
+
// writer — writes files (a project deployment or a settings/skill placement).
|
|
31
|
+
// guarded — a destructive teardown gated behind a mandatory dry-run-first + explicit consent.
|
|
32
|
+
// `writer` and `guarded` are the "acts on the system" kinds; only an explicit known token may reach
|
|
33
|
+
// one (plus the bare bootstrap exception). Garbage routes to `help` (read-only) — see routeInvocation.
|
|
34
|
+
export const READ_ONLY = 'read-only';
|
|
35
|
+
export const WRITER = 'writer';
|
|
36
|
+
export const GUARDED = 'guarded';
|
|
37
|
+
const KINDS = new Set([READ_ONLY, WRITER, GUARDED]);
|
|
38
|
+
|
|
39
|
+
// ── groups (fixed render order) ────────────────────────────────────────────────────
|
|
40
|
+
export const GROUP_ORDER = Object.freeze(['Inspect', 'Configure', 'Orchestrate', 'Lifecycle']);
|
|
41
|
+
|
|
42
|
+
// ── the canonical catalog ──────────────────────────────────────────────────────────
|
|
43
|
+
// One entry per `### Mode:` in SKILL.md. `key` is the mode (and, for every non-bootstrap mode, the
|
|
44
|
+
// routable subcommand token — bootstrap is the bare invocation, so it has no token). `oneLine` is
|
|
45
|
+
// plain-language: NO internal terms (no reconcile/fence/stamp/manifest/anchor) a third-party user
|
|
46
|
+
// hasn't read SKILL.md for. The drift-guard test pins these keys to the SKILL.md headers.
|
|
47
|
+
const CATALOG = [
|
|
48
|
+
{
|
|
49
|
+
key: 'bootstrap',
|
|
50
|
+
invocation: BARE_INVOCATION,
|
|
51
|
+
group: 'Lifecycle',
|
|
52
|
+
kind: WRITER,
|
|
53
|
+
oneLine: 'Deploy the memory & workflow system into a new or empty project (asks visibility, language, and attribution first).',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: 'upgrade',
|
|
57
|
+
invocation: invocationOf('upgrade'),
|
|
58
|
+
group: 'Lifecycle',
|
|
59
|
+
kind: WRITER,
|
|
60
|
+
oneLine: 'Bring an existing deployment up to the current version — your authored notes are preserved.',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
key: 'uninstall',
|
|
64
|
+
invocation: invocationOf('uninstall'),
|
|
65
|
+
group: 'Lifecycle',
|
|
66
|
+
kind: GUARDED,
|
|
67
|
+
oneLine: 'Remove only what setup placed; it never deletes your notes and always previews before changing anything.',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: 'status',
|
|
71
|
+
invocation: invocationOf('status'),
|
|
72
|
+
group: 'Inspect',
|
|
73
|
+
kind: READ_ONLY,
|
|
74
|
+
oneLine: 'Show what is installed and at what version, your settings and backends, and what is deployed in this project.',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
key: 'backends',
|
|
78
|
+
invocation: invocationOf('backends'),
|
|
79
|
+
group: 'Inspect',
|
|
80
|
+
kind: READ_ONLY,
|
|
81
|
+
oneLine: 'Check which optional review/execute backends (codex, agy) are set up versus missing, and the next step.',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
key: 'help',
|
|
85
|
+
invocation: invocationOf('help'),
|
|
86
|
+
group: 'Inspect',
|
|
87
|
+
kind: READ_ONLY,
|
|
88
|
+
oneLine: 'List every command, grouped, marking each as read-only or as one that makes changes.',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
key: 'setup',
|
|
92
|
+
invocation: invocationOf('setup'),
|
|
93
|
+
group: 'Configure',
|
|
94
|
+
kind: WRITER,
|
|
95
|
+
oneLine: 'Set up an optional backend — place its helper and link its commands onto your PATH (opt-in; preview first).',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
key: 'velocity',
|
|
99
|
+
invocation: invocationOf('velocity'),
|
|
100
|
+
group: 'Configure',
|
|
101
|
+
kind: WRITER,
|
|
102
|
+
oneLine: 'Seed a read-only command allowlist so routine read-only commands stop prompting (Claude Code; opt-in; preview first).',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
key: 'recipes',
|
|
106
|
+
invocation: invocationOf('recipes'),
|
|
107
|
+
group: 'Orchestrate',
|
|
108
|
+
kind: READ_ONLY,
|
|
109
|
+
oneLine: 'See the orchestration recipes (Solo / Reviewed / Council / Delegated) and which one fits this environment.',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
key: 'procedures',
|
|
113
|
+
invocation: invocationOf('procedures'),
|
|
114
|
+
group: 'Orchestrate',
|
|
115
|
+
kind: READ_ONLY,
|
|
116
|
+
oneLine: 'Show a named activity’s steps and which recipe applies at each stage here.',
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
// Deep-freeze: freeze the array AND every entry, so the catalog is genuinely immutable at runtime
|
|
121
|
+
// (Object.freeze on the array alone leaves the entry objects writable).
|
|
122
|
+
export const COMMANDS = Object.freeze(CATALOG.map((c) => Object.freeze(c)));
|
|
123
|
+
|
|
124
|
+
// The mode every garbage/unrecognized token routes to. Read-only by contract (see routeInvocation).
|
|
125
|
+
export const UNKNOWN_INVOCATION_MODE = 'help';
|
|
126
|
+
// The mode the bare/empty invocation maps to — the one writer reachable without an explicit token,
|
|
127
|
+
// guarded downstream by the docs/ai upgrade-vs-bootstrap check.
|
|
128
|
+
export const BARE_INVOCATION_MODE = 'bootstrap';
|
|
129
|
+
|
|
130
|
+
const byKey = new Map(COMMANDS.map((c) => [c.key, c]));
|
|
131
|
+
// Routable subcommand tokens = every mode except bootstrap (whose invocation is the bare default).
|
|
132
|
+
const ROUTABLE_TOKENS = new Set(COMMANDS.filter((c) => c.key !== BARE_INVOCATION_MODE).map((c) => c.key));
|
|
133
|
+
|
|
134
|
+
export const commandFor = (key) => byKey.get(key) ?? null;
|
|
135
|
+
export const kindOf = (key) => byKey.get(key)?.kind ?? null;
|
|
136
|
+
|
|
137
|
+
// ── the pure router ────────────────────────────────────────────────────────────────
|
|
138
|
+
// routeInvocation(token) → a mode key. `token` is the subcommand the user typed — either the raw word
|
|
139
|
+
// (`upgrade`) OR the full slash form (`/agent-workflow-kit upgrade`); the first word is significant
|
|
140
|
+
// and trailing args are ignored. Precise semantics:
|
|
141
|
+
// undefined / null / '' / whitespace-only / the exact bare invocation → 'bootstrap'
|
|
142
|
+
// a known first token (upgrade/status/setup/backends/recipes/procedures/velocity/uninstall/help)
|
|
143
|
+
// → that mode
|
|
144
|
+
// anything else (unrecognized / ambiguous) → 'help' (read-only — NEVER a writer/guarded mode)
|
|
145
|
+
export const routeInvocation = (token) => {
|
|
146
|
+
if (token == null) return BARE_INVOCATION_MODE;
|
|
147
|
+
const trimmed = String(token).trim();
|
|
148
|
+
if (trimmed === '' || trimmed === BARE_INVOCATION) return BARE_INVOCATION_MODE;
|
|
149
|
+
// Accept either the raw subcommand token OR the full slash invocation the user types — strip a
|
|
150
|
+
// leading `/agent-workflow-kit ` prefix so `/agent-workflow-kit upgrade` routes like `upgrade`.
|
|
151
|
+
const rest = trimmed.startsWith(`${BARE_INVOCATION} `)
|
|
152
|
+
? trimmed.slice(BARE_INVOCATION.length).trim()
|
|
153
|
+
: trimmed;
|
|
154
|
+
if (rest === '') return BARE_INVOCATION_MODE;
|
|
155
|
+
const first = rest.split(/\s+/)[0];
|
|
156
|
+
return ROUTABLE_TOKENS.has(first) ? first : UNKNOWN_INVOCATION_MODE;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ── render ──────────────────────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
const KIND_TAG = { [READ_ONLY]: '[read-only]', [WRITER]: '[writer] ', [GUARDED]: '[guarded] ' };
|
|
162
|
+
const pad = (s, n) => (s.length >= n ? s : s + ' '.repeat(n - s.length));
|
|
163
|
+
|
|
164
|
+
// formatHelp() → the grouped, kind-tagged human index. Deterministic; groups in GROUP_ORDER, modes in
|
|
165
|
+
// catalog order within each group. The header states the index itself is read-only.
|
|
166
|
+
export const formatHelp = () => {
|
|
167
|
+
const invocWidth = Math.max(...COMMANDS.map((c) => c.invocation.length));
|
|
168
|
+
const lines = [
|
|
169
|
+
`${SKILL_NAME} — command index (this list is read-only)`,
|
|
170
|
+
'',
|
|
171
|
+
'Each command is tagged read-only · writer (makes changes) · guarded (destructive, previews first).',
|
|
172
|
+
];
|
|
173
|
+
for (const group of GROUP_ORDER) {
|
|
174
|
+
const inGroup = COMMANDS.filter((c) => c.group === group);
|
|
175
|
+
if (!inGroup.length) continue;
|
|
176
|
+
lines.push('', group);
|
|
177
|
+
for (const c of inGroup) {
|
|
178
|
+
lines.push(` ${pad(c.invocation, invocWidth)} ${KIND_TAG[c.kind] ?? c.kind} ${c.oneLine}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return lines.join('\n');
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// The machine form behind `--json`: the flat catalog (machines group by `group` themselves) plus the
|
|
185
|
+
// two routing anchors, so a consumer can reproduce routeInvocation without re-deriving the rules.
|
|
186
|
+
export const buildJson = () => ({
|
|
187
|
+
skill: SKILL_NAME,
|
|
188
|
+
groupOrder: [...GROUP_ORDER],
|
|
189
|
+
unknownInvocationMode: UNKNOWN_INVOCATION_MODE,
|
|
190
|
+
bareInvocationMode: BARE_INVOCATION_MODE,
|
|
191
|
+
commands: COMMANDS.map(({ key, invocation, group, kind, oneLine }) => ({ key, invocation, group, kind, oneLine })),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ── CLI ──────────────────────────────────────────────────────────────────────────────
|
|
195
|
+
const main = (argv) => {
|
|
196
|
+
if (argv.includes('--json')) {
|
|
197
|
+
console.log(JSON.stringify(buildJson(), null, 2));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
console.log(formatHelp());
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
204
|
+
if (isDirectRun) main(process.argv.slice(2));
|
|
205
|
+
|
|
206
|
+
export { KINDS, SKILL_NAME, BARE_INVOCATION };
|
package/tools/delegation.mjs
CHANGED
|
@@ -20,8 +20,16 @@ export const EXPECTED_MEMORY_NAME = 'agent-workflow-memory';
|
|
|
20
20
|
// The assets a memory candidate must carry, AND their required type. A partial install (manifest +
|
|
21
21
|
// SKILL.md only) is missing these → invalid → fallback. Checking the type (not just existence)
|
|
22
22
|
// rejects a wrong-shaped install (e.g. a file where a dir is expected) BEFORE any project write.
|
|
23
|
+
//
|
|
24
|
+
// `references/templates/orchestration.json` (Step 2.4) is a SURGICAL gate: a memory too old to ship
|
|
25
|
+
// the orchestration-config template (pre-1.2.0, e.g. v1.0.0) can't seed `docs/ai/orchestration.json`,
|
|
26
|
+
// so it must NOT be delegate-classified — the kit then falls back to its OWN bundled substrate, which
|
|
27
|
+
// DOES seed orchestration.json (Mode: upgrade step 3). This closes the stale-memory trap that the
|
|
28
|
+
// read-only family-registry note (MEMORY_ORCH_TEMPLATE_REL) only INFORMS about — the gate ACTS. The
|
|
29
|
+
// two key on the same asset; a cross-tool parity test pins them in lockstep.
|
|
23
30
|
export const REQUIRED_MEMORY_ASSETS = [
|
|
24
31
|
{ path: 'references/templates', type: 'dir' },
|
|
32
|
+
{ path: 'references/templates/orchestration.json', type: 'file' },
|
|
25
33
|
{ path: 'references/contracts.md', type: 'file' },
|
|
26
34
|
{ path: 'references/scripts', type: 'dir' },
|
|
27
35
|
{ path: 'scripts/stamp-takeover.mjs', type: 'file' },
|
|
@@ -16,14 +16,29 @@
|
|
|
16
16
|
// Pure, dependency-injectable (fs/env/home/validator are deps), dependency-free, Node >= 18. No
|
|
17
17
|
// side effects on import (the isDirectRun idiom) — tests import the helpers with nothing run.
|
|
18
18
|
|
|
19
|
-
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
19
|
+
import { existsSync, statSync, readFileSync, lstatSync } from 'node:fs';
|
|
20
20
|
import { join, resolve } from 'node:path';
|
|
21
21
|
import { pathToFileURL } from 'node:url';
|
|
22
22
|
import os from 'node:os';
|
|
23
|
-
import { resolveDir } from './detect-backends.mjs';
|
|
23
|
+
import { resolveDir, detectBackends, findOnPath } from './detect-backends.mjs';
|
|
24
24
|
import { validateManifest, readAuthoritativeVersion, UNSUPPORTED, INVALID } from './manifest/validate.mjs';
|
|
25
|
-
import { START_MARKER, excludePath } from './hide-footprint.mjs';
|
|
25
|
+
import { START_MARKER, excludePath, inferVisibility } from './hide-footprint.mjs';
|
|
26
26
|
import { readEngineFragment, ORCHESTRATION_FRAGMENT_REL, PROCEDURES_FRAGMENT_REL } from './engine-source.mjs';
|
|
27
|
+
import { ACTIVITIES, resolveActivityRecipe } from './recipes.mjs';
|
|
28
|
+
import { loadConfig } from './procedures.mjs';
|
|
29
|
+
// The deployment-lineage head + the shared settings readers are reused from velocity-profile so the
|
|
30
|
+
// `--json` envelope/settings-survey has ONE implementation each, never a drifting copy:
|
|
31
|
+
// EXPECTED_WORKFLOW_VERSION — the head literal (drift-guarded vs memory LINEAGE_HEAD)
|
|
32
|
+
// readSettingsFile / resolveEffectiveMode — the loud `.claude/settings.*` reader + mode precedence
|
|
33
|
+
// (No import cycle: velocity-profile / recipes / procedures import only node builtins + siblings, none
|
|
34
|
+
// import family-registry, and none has a side effect on import — every CLI is isDirectRun-guarded.)
|
|
35
|
+
import {
|
|
36
|
+
EXPECTED_WORKFLOW_VERSION,
|
|
37
|
+
SETTINGS_FILE,
|
|
38
|
+
SETTINGS_LOCAL_FILE,
|
|
39
|
+
readSettingsFile,
|
|
40
|
+
resolveEffectiveMode,
|
|
41
|
+
} from './velocity-profile.mjs';
|
|
27
42
|
|
|
28
43
|
// ── manifestState values (the detect-backends precedence, generalized to any member kind) ──────────
|
|
29
44
|
export const NOT_INSTALLED = 'not-installed';
|
|
@@ -93,14 +108,14 @@ export const FAMILY_MEMBERS = [
|
|
|
93
108
|
export const isGlobalSkill = (member) => member.kind !== undefined; // every member is a global skill today
|
|
94
109
|
|
|
95
110
|
// ── pure probes ──────────────────────────────────────────────────────────────────
|
|
96
|
-
// Wrapped marker probe → 'present' (a regular file) | 'absent' (ENOENT / not a file) |
|
|
97
|
-
// non-ENOENT fs error, e.g. EACCES).
|
|
98
|
-
//
|
|
111
|
+
// Wrapped marker probe → 'present' (a regular file) | 'absent' (ENOENT / not a regular file) |
|
|
112
|
+
// 'unknown' (a non-ENOENT fs error, e.g. EACCES). STAT-FIRST on purpose: `existsSync()` SWALLOWS an
|
|
113
|
+
// EACCES into a bare `false`, which would mask a permission error as 'absent' (a silent failure);
|
|
114
|
+
// `statSync` THROWS the EACCES so it surfaces as 'unknown'. 'unknown' is never collapsed to 'absent' —
|
|
115
|
+
// classifyMember reports it (uninstall then leaves the dir alone) and the memory caveat skips it.
|
|
99
116
|
const probeMarker = (path, deps = {}) => {
|
|
100
|
-
const exists = deps.exists ?? existsSync;
|
|
101
117
|
const stat = deps.stat ?? statSync;
|
|
102
118
|
try {
|
|
103
|
-
if (!exists(path)) return 'absent';
|
|
104
119
|
return stat(path).isFile() ? 'present' : 'absent';
|
|
105
120
|
} catch (err) {
|
|
106
121
|
return err && err.code === 'ENOENT' ? 'absent' : 'unknown';
|
|
@@ -158,6 +173,17 @@ const ENGINE_FRAGMENT_CAVEATS = [
|
|
|
158
173
|
{ rel: PROCEDURES_FRAGMENT_REL, caveat: 'engine present but does not ship the activity-procedures canon (too old / incomplete) — run `npx @sabaiway/agent-workflow-engine@latest init`' },
|
|
159
174
|
];
|
|
160
175
|
|
|
176
|
+
// The orchestration-config TEMPLATE a current memory ships (added in memory 1.2.0; absent in older
|
|
177
|
+
// installs such as v1.0.0). It is the SAME asset Step 2.4 adds to delegation.mjs's
|
|
178
|
+
// REQUIRED_MEMORY_ASSETS: the read-only note here INFORMS, the delegation gate ACTS. Step 2.4 also
|
|
179
|
+
// adds a parity drift-guard tying this path to that required-asset set, so the note and the gate can
|
|
180
|
+
// never key on different files (until then they are kept in lockstep by review).
|
|
181
|
+
export const MEMORY_ORCH_TEMPLATE_REL = 'references/templates/orchestration.json';
|
|
182
|
+
// Worded as an honest OBSERVATION, not a diagnosis (absence can mean old OR incomplete), and it makes
|
|
183
|
+
// NO claim about an orchestration.json seeding outcome (that depends on delegate-vs-fallback).
|
|
184
|
+
const MEMORY_BEHIND_NOTE =
|
|
185
|
+
"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.";
|
|
186
|
+
|
|
161
187
|
export const surveyFamily = (deps = {}) =>
|
|
162
188
|
FAMILY_MEMBERS.map((member) => {
|
|
163
189
|
const row = classifyMember(member, deps);
|
|
@@ -173,6 +199,14 @@ export const surveyFamily = (deps = {}) =>
|
|
|
173
199
|
const caveats = ENGINE_FRAGMENT_CAVEATS.filter((f) => !fragmentUsable(f.rel)).map((f) => f.caveat);
|
|
174
200
|
if (caveats.length) row.caveats = caveats;
|
|
175
201
|
}
|
|
202
|
+
// Memory offline caveat (Step 2.2): a distinct probe — the orchestration TEMPLATE file's existence.
|
|
203
|
+
// Only attach when it is provably ABSENT (a non-ENOENT probe error → 'unknown' → skip, never a
|
|
204
|
+
// false "missing" claim). Mirrors the engine-caveat SHAPE; keyed on the Step-2.4 required asset.
|
|
205
|
+
if (row.kind === 'memory-substrate' && row.manifestState === OK && row.skillDir) {
|
|
206
|
+
if (probeMarker(join(row.skillDir, MEMORY_ORCH_TEMPLATE_REL), deps) === 'absent') {
|
|
207
|
+
row.caveats = [...(row.caveats ?? []), MEMORY_BEHIND_NOTE];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
176
210
|
return row;
|
|
177
211
|
});
|
|
178
212
|
|
|
@@ -234,13 +268,41 @@ export const surveyProject = (projectDir, deps = {}) => {
|
|
|
234
268
|
// ── report ───────────────────────────────────────────────────────────────────────
|
|
235
269
|
const pad = (s, n) => (s.length >= n ? s : s + ' '.repeat(n - s.length));
|
|
236
270
|
|
|
237
|
-
|
|
271
|
+
// The human (non-JSON) settings render. A dev view — the agent consumes `--json` + renders in plain
|
|
272
|
+
// language (Mode: status). Each area shows its `error` field loudly when one fired.
|
|
273
|
+
const formatSettings = (s) => {
|
|
274
|
+
const out = ['', 'settings'];
|
|
275
|
+
if (s.recipes?.error) out.push(` ${pad('recipes', 14)}error: ${s.recipes.error}`);
|
|
276
|
+
else {
|
|
277
|
+
const parts = [];
|
|
278
|
+
for (const [act, slots] of Object.entries(s.recipes?.activities ?? {})) {
|
|
279
|
+
for (const [slot, r] of Object.entries(slots)) parts.push(`${act}.${slot}=${r.recipe}`);
|
|
280
|
+
}
|
|
281
|
+
out.push(` ${pad('recipes', 14)}${parts.join(' · ') || '—'}`);
|
|
282
|
+
if (s.recipes?.detectError) out.push(` ${pad('', 14)}↳ couldn't check backends (${s.recipes.detectError}); recipes floored at solo`);
|
|
283
|
+
}
|
|
284
|
+
if (s.attribution?.error) out.push(` ${pad('attribution', 14)}error: ${s.attribution.error}`);
|
|
285
|
+
else out.push(` ${pad('attribution', 14)}includeCoAuthoredBy effective=${String(s.attribution?.effective)}`);
|
|
286
|
+
if (s.velocity?.error) out.push(` ${pad('velocity', 14)}error: ${s.velocity.error}`);
|
|
287
|
+
else out.push(` ${pad('velocity', 14)}defaultMode=${String(s.velocity?.defaultMode)} · allow project/local=${s.velocity?.allowEntries?.project}/${s.velocity?.allowEntries?.local}`);
|
|
288
|
+
return out;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export const formatStatus = (family, project = null, extras = {}) => {
|
|
238
292
|
const lines = ['agent-workflow family — installed skills (skill axis)', ''];
|
|
239
293
|
for (const m of family) {
|
|
240
294
|
const ver = m.version ? `v${m.version}` : '—';
|
|
241
295
|
lines.push(` ${pad(m.name, 26)}[${pad(m.manifestState, 16)}] ${pad(ver, 10)} ${m.kind}`);
|
|
242
296
|
for (const c of m.caveats ?? []) lines.push(` ↳ ${c}`);
|
|
243
297
|
}
|
|
298
|
+
if (extras.bridges) {
|
|
299
|
+
const WRAP_MARK = { present: '✓', missing: '✗', unknown: '?' };
|
|
300
|
+
lines.push('', 'execution backends (host)', '');
|
|
301
|
+
for (const b of extras.bridges) {
|
|
302
|
+
const w = b.wrappers.map((x) => `${x.cmd} ${WRAP_MARK[x.state] ?? '?'}`).join(', ') || '—';
|
|
303
|
+
lines.push(` ${pad(b.display, 20)}${pad(b.readiness, 18)}wrappers: ${w}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
244
306
|
if (project) {
|
|
245
307
|
lines.push('', `project deployment (${project.dir})`, '');
|
|
246
308
|
if (!project.deployed) {
|
|
@@ -250,16 +312,197 @@ export const formatStatus = (family, project = null) => {
|
|
|
250
312
|
lines.push(` ${pad(s.file, 26)}${s.version ?? '—'}`);
|
|
251
313
|
}
|
|
252
314
|
lines.push(` ${pad('docs/ai present', 26)}${project.docsAiPresent ? 'yes' : 'no'}`);
|
|
253
|
-
|
|
315
|
+
if (extras.visibility) {
|
|
316
|
+
lines.push(` ${pad('visibility', 26)}${extras.visibility.error ? `error: ${extras.visibility.error}` : extras.visibility.state}`);
|
|
317
|
+
} else {
|
|
318
|
+
lines.push(` ${pad('hidden-mode fence', 26)}${project.hiddenFence ? 'present' : 'absent'}`);
|
|
319
|
+
}
|
|
254
320
|
}
|
|
321
|
+
if (extras.settings) lines.push(...formatSettings(extras.settings));
|
|
255
322
|
}
|
|
256
323
|
return lines.join('\n');
|
|
257
324
|
};
|
|
258
325
|
|
|
326
|
+
// ── the no-leak --json envelope ──────────────────────────────────────────────────
|
|
327
|
+
// A machine-readable view with USER-SAFE field names only — NEVER the internal manifestState /
|
|
328
|
+
// hiddenFence terms or the raw stamp FILENAMES. The render (SKILL.md version block + Mode: status)
|
|
329
|
+
// consumes THIS, never the human table verbatim. An envelope-shape test pins its shape so later phases
|
|
330
|
+
// (the settings/visibility block) can't silently break the Phase-2 version consumer.
|
|
331
|
+
|
|
332
|
+
// internal manifestState → a STABLE, user-safe token (SKILL.md owns the value→plain-language phrasing).
|
|
333
|
+
// Deliberately NOT the internal literals (foreign/stub/…): those must never leak past this boundary.
|
|
334
|
+
const STATE_PUBLIC = {
|
|
335
|
+
[OK]: 'installed',
|
|
336
|
+
[NOT_INSTALLED]: 'absent',
|
|
337
|
+
[FOREIGN]: 'other-tool',
|
|
338
|
+
[STUB]: 'placeholder',
|
|
339
|
+
[INVALID_MANIFEST]: 'invalid',
|
|
340
|
+
[UNSUPPORTED_SCHEMA]: 'unsupported',
|
|
341
|
+
[UNKNOWN]: 'uncheckable',
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// Short, user-facing labels for the version block (kit · memory · engine · codex-bridge · …), so the
|
|
345
|
+
// render is deterministic and the agent never invents a label.
|
|
346
|
+
export const DISPLAY_NAMES = {
|
|
347
|
+
'agent-workflow-kit': 'kit',
|
|
348
|
+
'agent-workflow-memory': 'memory',
|
|
349
|
+
'agent-workflow-engine': 'engine',
|
|
350
|
+
'codex-cli-bridge': 'codex-bridge',
|
|
351
|
+
'antigravity-cli-bridge': 'antigravity-bridge',
|
|
352
|
+
};
|
|
353
|
+
const displayOf = (name) => DISPLAY_NAMES[name] ?? name;
|
|
354
|
+
|
|
355
|
+
// ── the settings survey (Phase 3) — read-only, honest, localized-on-error ──────────
|
|
356
|
+
// Each sub-survey returns a small user-safe object OR a single `{ error }` field (a localized message,
|
|
357
|
+
// never a crash): a malformed/unreadable file in ONE area must not break the rest of `status`. The
|
|
358
|
+
// readers are REUSED (one implementation each): loadConfig (procedures.mjs), readSettingsFile /
|
|
359
|
+
// resolveEffectiveMode (velocity-profile.mjs), resolveActivityRecipe (recipes.mjs), inferVisibility
|
|
360
|
+
// (hide-footprint.mjs). Engine-free throughout (the effective-recipe view never reads the engine).
|
|
361
|
+
|
|
362
|
+
// A localized, user-safe error string. The reused readers already build cwd-relative `path: reason`
|
|
363
|
+
// messages (loadConfig / velocity), so this just normalizes to a string — never a raw stack/abs path.
|
|
364
|
+
const localizeError = (err) => (err && err.message ? String(err.message) : String(err));
|
|
365
|
+
|
|
366
|
+
const hasOwn = (o, k) => o != null && Object.prototype.hasOwnProperty.call(o, k);
|
|
367
|
+
|
|
368
|
+
// The backend detector is a SECONDARY input to the survey — a corrupt bridge must never break the
|
|
369
|
+
// read-only view, but the failure must NOT be silent (Hard Constraint). Run it defensively: a throw →
|
|
370
|
+
// { detection: [], error: <localized> } so callers floor gracefully (recipes → solo, bridges →
|
|
371
|
+
// 'unknown') AND can surface the concrete reason. Returns a value so callers stay `const`.
|
|
372
|
+
const detectSafe = (deps) => {
|
|
373
|
+
try {
|
|
374
|
+
return { detection: (deps.detect ?? detectBackends)(deps), error: null };
|
|
375
|
+
} catch (err) {
|
|
376
|
+
return { detection: [], error: localizeError(err) };
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// visibility: the THREE honest states from inferVisibility (NOT the single hiddenFence bit) → user-safe
|
|
381
|
+
// words. Never the internal "hidden fence" / marker terms. A git/probe error → a localized error field.
|
|
382
|
+
const VISIBILITY_PUBLIC = { visible: 'visible', hidden: 'hidden', ambiguous: 'unclear' };
|
|
383
|
+
export const surveyVisibility = (dir, deps = {}) => {
|
|
384
|
+
try {
|
|
385
|
+
const vis = inferVisibility(deps, resolve(dir));
|
|
386
|
+
return { state: VISIBILITY_PUBLIC[vis.visibility] ?? 'unclear' };
|
|
387
|
+
} catch (err) {
|
|
388
|
+
return { error: localizeError(err) };
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// orchestration recipes: the EFFECTIVE recipe per slot (config · default · effective), engine-free —
|
|
393
|
+
// shared loadConfig + resolveActivityRecipe + the read-only backend detector. A malformed config → a
|
|
394
|
+
// localized error field; a detection failure floors at solo (a corrupt bridge must not break the view).
|
|
395
|
+
export const surveyRecipes = (dir, deps = {}) => {
|
|
396
|
+
// A detector failure floors recipes at solo (mirrors procedures) but is surfaced as `detectError`, so
|
|
397
|
+
// the render says "couldn't check backends" instead of letting a real solo-default look identical.
|
|
398
|
+
const { detection, error: detectError } = detectSafe(deps);
|
|
399
|
+
try {
|
|
400
|
+
const { config, source } = loadConfig(resolve(dir), deps.readFile ?? readFileSync, deps.lstat ?? lstatSync);
|
|
401
|
+
const activities = {};
|
|
402
|
+
for (const [activity, def] of Object.entries(ACTIVITIES)) {
|
|
403
|
+
activities[activity] = {};
|
|
404
|
+
for (const slot of Object.keys(def.slots)) {
|
|
405
|
+
const r = resolveActivityRecipe({ config: config ?? {}, readiness: detection, activity, slot });
|
|
406
|
+
activities[activity][slot] = { recipe: r.recipe, source: r.source, degradedFrom: r.degradedFrom ?? null };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return { configSource: source, activities, ...(detectError ? { detectError } : {}) };
|
|
410
|
+
} catch (err) {
|
|
411
|
+
return { error: localizeError(err) };
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// attribution: includeCoAuthoredBy across .claude/settings.json + settings.local.json as
|
|
416
|
+
// project · local override · effective (local WINS only when it actually sets the key — presence-based,
|
|
417
|
+
// mirroring resolveEffectiveMode). Reuses the loud readSettingsFile; the precedence read is new code.
|
|
418
|
+
export const surveyAttribution = (dir, deps = {}) => {
|
|
419
|
+
try {
|
|
420
|
+
const d = resolve(dir);
|
|
421
|
+
const project = readSettingsFile(join(d, SETTINGS_FILE), { ...deps, cwd: d });
|
|
422
|
+
const local = readSettingsFile(join(d, SETTINGS_LOCAL_FILE), { ...deps, cwd: d });
|
|
423
|
+
const projVal = project.present && hasOwn(project.data, 'includeCoAuthoredBy') ? project.data.includeCoAuthoredBy : null;
|
|
424
|
+
const localHasKey = local.present && hasOwn(local.data, 'includeCoAuthoredBy');
|
|
425
|
+
const localVal = localHasKey ? local.data.includeCoAuthoredBy : null;
|
|
426
|
+
return { project: projVal, local: localVal, effective: localHasKey ? localVal : projVal };
|
|
427
|
+
} catch (err) {
|
|
428
|
+
return { error: localizeError(err) };
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// velocity: the effective permissions.defaultMode (local > project, via resolveEffectiveMode) + the
|
|
433
|
+
// per-source count of allowlist entries — a read-only view of what the velocity profile may have seeded.
|
|
434
|
+
export const surveyVelocity = (dir, deps = {}) => {
|
|
435
|
+
try {
|
|
436
|
+
const d = resolve(dir);
|
|
437
|
+
const project = readSettingsFile(join(d, SETTINGS_FILE), { ...deps, cwd: d });
|
|
438
|
+
const local = readSettingsFile(join(d, SETTINGS_LOCAL_FILE), { ...deps, cwd: d });
|
|
439
|
+
const { effectiveMode } = resolveEffectiveMode(project.data, local.data);
|
|
440
|
+
const allowOf = (s) => (s.present && Array.isArray(s.data?.permissions?.allow) ? s.data.permissions.allow.length : 0);
|
|
441
|
+
return { defaultMode: effectiveMode ?? null, allowEntries: { project: allowOf(project), local: allowOf(local) } };
|
|
442
|
+
} catch (err) {
|
|
443
|
+
return { error: localizeError(err) };
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
// the project-scoped settings survey (needs a project dir). Each area is independently localized-on-error.
|
|
448
|
+
export const surveySettings = (dir, deps = {}) => ({
|
|
449
|
+
recipes: surveyRecipes(dir, deps),
|
|
450
|
+
attribution: surveyAttribution(dir, deps),
|
|
451
|
+
velocity: surveyVelocity(dir, deps),
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// bridges: HOST-scoped (no project needed). Wrapper command NAMES come from FAMILY_MEMBERS[].wrapperCmds
|
|
455
|
+
// (static, always present), their PATH-presence is probed DIRECTLY via findOnPath over those names (NOT
|
|
456
|
+
// detect-backends' wrappers[], which is [] when the bridge isn't ok — the onboarding case), and the
|
|
457
|
+
// readiness summary comes from the detector. NO default-model claim (a negative drift-guard asserts it).
|
|
458
|
+
export const surveyBridges = (deps = {}) => {
|
|
459
|
+
const { detection } = detectSafe(deps); // a detector failure → every readiness reads 'unknown' (honest)
|
|
460
|
+
const probe = deps.findOnPath ?? findOnPath;
|
|
461
|
+
return FAMILY_MEMBERS.filter((m) => m.kind === 'execution-backend').map((m) => {
|
|
462
|
+
const det = detection.find((d) => d.name === m.name);
|
|
463
|
+
// Preserve findOnPath's THREE-state result (present | missing | unknown): an `unknown` (EACCES,
|
|
464
|
+
// "cannot confirm") must stay distinct from a real `missing` — never flattened to a false boolean.
|
|
465
|
+
const wrappers = m.wrapperCmds.map((cmd) => ({ cmd, state: probe(cmd, deps).state }));
|
|
466
|
+
return { member: m.name, display: displayOf(m.name), readiness: det?.readiness ?? 'unknown', wrappers };
|
|
467
|
+
});
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
export const buildEnvelope = (family, project = null, extras = {}) => {
|
|
471
|
+
const installed = family.map((m) => {
|
|
472
|
+
const entry = {
|
|
473
|
+
member: m.name,
|
|
474
|
+
display: displayOf(m.name),
|
|
475
|
+
version: m.version ?? null,
|
|
476
|
+
state: STATE_PUBLIC[m.manifestState] ?? STATE_PUBLIC[UNKNOWN],
|
|
477
|
+
};
|
|
478
|
+
if (m.caveats?.length) entry.notes = m.caveats; // plain-language observations (Steps 2.2 / engine)
|
|
479
|
+
return entry;
|
|
480
|
+
});
|
|
481
|
+
const envelope = { deploymentHead: EXPECTED_WORKFLOW_VERSION, installed };
|
|
482
|
+
if (extras.bridges) envelope.bridges = extras.bridges; // HOST-scoped (no project needed)
|
|
483
|
+
if (project) {
|
|
484
|
+
envelope.project = {
|
|
485
|
+
dir: project.dir,
|
|
486
|
+
deployed: project.deployed,
|
|
487
|
+
docsAi: project.docsAiPresent,
|
|
488
|
+
// member + display + version only — never the internal stamp FILENAME (s.file).
|
|
489
|
+
deployStamps: project.stamps.map((s) => ({ member: s.name, display: displayOf(s.name), version: s.version ?? null })),
|
|
490
|
+
};
|
|
491
|
+
// project-scoped Phase-3 additions (visibility from inferVisibility, not the hiddenFence bit).
|
|
492
|
+
if (extras.visibility) envelope.project.visibility = extras.visibility;
|
|
493
|
+
if (extras.settings) envelope.project.settings = extras.settings;
|
|
494
|
+
}
|
|
495
|
+
return envelope;
|
|
496
|
+
};
|
|
497
|
+
|
|
259
498
|
// ── CLI ────────────────────────────────────────────────────────────────────────
|
|
260
499
|
const parseArgs = (argv) => {
|
|
261
500
|
const dirFlag = argv.indexOf('--dir');
|
|
262
|
-
return {
|
|
501
|
+
return {
|
|
502
|
+
help: argv.includes('--help') || argv.includes('-h'),
|
|
503
|
+
json: argv.includes('--json'),
|
|
504
|
+
dir: dirFlag >= 0 ? argv[dirFlag + 1] : undefined,
|
|
505
|
+
};
|
|
263
506
|
};
|
|
264
507
|
|
|
265
508
|
const main = (argv) => {
|
|
@@ -268,14 +511,21 @@ const main = (argv) => {
|
|
|
268
511
|
console.log(`family-registry — read-only view of the agent-workflow family.
|
|
269
512
|
|
|
270
513
|
Usage:
|
|
271
|
-
node family-registry.mjs [--dir <project>]
|
|
514
|
+
node family-registry.mjs [--dir <project>] [--json]
|
|
515
|
+
# skill axis always; deploy axis when --dir is given; --json = the no-leak machine envelope
|
|
272
516
|
|
|
273
517
|
Detection only — never writes, never commits, never runs a subscription CLI.`);
|
|
274
518
|
return;
|
|
275
519
|
}
|
|
276
520
|
const family = surveyFamily();
|
|
521
|
+
const bridges = surveyBridges();
|
|
277
522
|
const project = args.dir ? surveyProject(args.dir) : null;
|
|
278
|
-
|
|
523
|
+
const extras = { bridges };
|
|
524
|
+
if (args.dir) {
|
|
525
|
+
extras.visibility = surveyVisibility(args.dir);
|
|
526
|
+
extras.settings = surveySettings(args.dir);
|
|
527
|
+
}
|
|
528
|
+
console.log(args.json ? JSON.stringify(buildEnvelope(family, project, extras), null, 2) : formatStatus(family, project, extras));
|
|
279
529
|
};
|
|
280
530
|
|
|
281
531
|
const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
package/tools/procedures.mjs
CHANGED
|
@@ -141,7 +141,9 @@ const validateConfig = (config) => {
|
|
|
141
141
|
// Load + validate the config from <cwd>/docs/ai/orchestration.json. Absent FILE → computed defaults
|
|
142
142
|
// (NOT an error): { config: null, source: 'none' }. Malformed JSON / schema-invalid / unreadable →
|
|
143
143
|
// loud `path: reason` (exit 1). The resolver receives the parsed+validated object (§2.2 IO/resolver split).
|
|
144
|
-
|
|
144
|
+
// Exported so the read-only status settings-survey (family-registry.mjs) reuses ONE config reader —
|
|
145
|
+
// same strict-JSON + loud-on-malformed contract, no second drifting implementation (Plan 3.1).
|
|
146
|
+
export const loadConfig = (cwd, readFile = readFileSync, lstat = lstatSync) => {
|
|
145
147
|
const full = join(cwd, CONFIG_REL);
|
|
146
148
|
// Distinguish a TRULY-absent config (no entry at all → computed defaults) from a present-but-
|
|
147
149
|
// unreadable one (a directory, a DANGLING SYMLINK, a permission error → loud exit 1). lstat does NOT
|