@sabaiway/agent-workflow-kit 1.13.0 → 1.14.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 +27 -0
- package/README.md +2 -0
- package/SKILL.md +28 -6
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/scripts/check-docs-size.mjs +4 -1
- package/references/scripts/check-docs-size.test.mjs +24 -0
- package/references/templates/orchestration.json +5 -0
- package/tools/engine-source.mjs +5 -0
- package/tools/engine-source.test.mjs +48 -0
- package/tools/family-registry.mjs +22 -16
- package/tools/family-registry.test.mjs +55 -23
- package/tools/inject-methodology.mjs +25 -0
- package/tools/inject-methodology.test.mjs +73 -0
- package/tools/procedures.mjs +324 -0
- package/tools/procedures.test.mjs +303 -0
- package/tools/recipes.mjs +64 -0
- package/tools/recipes.test.mjs +175 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,33 @@ 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.14.0 — Activity procedures: recipe-aware, configurable playbooks
|
|
8
|
+
|
|
9
|
+
A new read-only **`/agent-workflow-kit procedures <activity>`** advisor turns a bare command like
|
|
10
|
+
"write a plan" into a codified, recipe-aware procedure. It reads the named activity's ordered steps
|
|
11
|
+
**live** from the installed engine (`references/procedures.md`) and prints them verbatim, then resolves
|
|
12
|
+
the **effective recipe per slot** from a new per-project, hand-edited config and the read-only backend
|
|
13
|
+
detector. Two v1 activities: **`plan-authoring`** (slot: `review`) and **`plan-execution`** (slots:
|
|
14
|
+
`execute`, `review`). It composes with the AD-018 recipes; **`recipes` stays read-only** (the config is
|
|
15
|
+
hand-edited, never written by the kit).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`tools/procedures.mjs`** — the read-only CLI: live engine read + per-activity section extraction,
|
|
19
|
+
config IO + validation, and the resolved recipe per slot (default = Reviewed when a backend is ready,
|
|
20
|
+
Council on request, slot-aware incl. Delegated). A repeatable `--override <slot>=<recipe>` adjusts one
|
|
21
|
+
slot per run. Exit codes: `0` success (an unsatisfiable override degrades **loudly** but still `0`),
|
|
22
|
+
`2` usage (unknown activity / bad `--override`), `1` config or engine error (loud `path: reason`).
|
|
23
|
+
- **`docs/ai/orchestration.json`** — the per-project, strict-JSON config (`{ activity: { slot: recipe } }`;
|
|
24
|
+
all slots optional; an optional `"_README"` is allowed + ignored). Hand-edited; kit-validated.
|
|
25
|
+
- **`resolveActivityRecipe` / `ACTIVITIES` / `SLOT_RECIPES`** in `tools/recipes.mjs` — the pure resolver
|
|
26
|
+
(graceful default vs loud override degradation), drift-guarded against the engine canon's `Slots:`
|
|
27
|
+
lines. `planRecipe` / `recommendRecipe` are unchanged.
|
|
28
|
+
- A **`workflow:methodology`** pointer clause routing to `/agent-workflow-kit procedures <activity>`
|
|
29
|
+
(the feature's only auto-discovery route — both engine + kit are `disable-model-invocation`).
|
|
30
|
+
|
|
31
|
+
The deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change; no migration file). See
|
|
32
|
+
**AD-019**.
|
|
33
|
+
|
|
7
34
|
## 1.13.0 — Orchestration recipes: a named way to compose the bridges
|
|
8
35
|
|
|
9
36
|
The kit now knows **how to put the optional execution-backends to work**, not just whether they're set
|
package/README.md
CHANGED
|
@@ -222,6 +222,7 @@ command is printed).
|
|
|
222
222
|
| `/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
223
|
| `/agent-workflow-kit status` | any time | **read-only** view of the whole family: which members (kit / memory / engine / the two bridges) are installed and at what version, and — with a project — what's deployed (`docs/ai`, the version stamps, and whether the AI files are git-ignored for hidden mode). Never writes, never commits, never runs a subscription CLI. |
|
|
224
224
|
| `/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
|
+
| `/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. |
|
|
225
226
|
| `/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` / settings — for those it prints the exact `rm` commands to run by hand. Always `--dry-run` first; preflight-then-mutate; never commits. |
|
|
226
227
|
|
|
227
228
|
It **never auto-commits** and **never overwrites** an existing `AGENTS.md` without asking.
|
|
@@ -311,6 +312,7 @@ agent-workflow-kit/
|
|
|
311
312
|
│ ├── engine-source.mjs ← live engine fragment read (fail-loud)
|
|
312
313
|
│ ├── detect-backends.mjs ← read-only backend detector
|
|
313
314
|
│ ├── recipes.mjs ← read-only recipe planner (recipes)
|
|
315
|
+
│ ├── procedures.mjs ← activity-procedures advisor (procedures)
|
|
314
316
|
│ ├── setup-backends.mjs ← link-only backend setup
|
|
315
317
|
│ ├── fs-safe.mjs ← symlink-safe copy/link/remove/unlink
|
|
316
318
|
│ ├── family-registry.mjs ← unified family registry (status)
|
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.14.0'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-kit
|
|
@@ -104,6 +104,7 @@ Pick the mode from the user's invocation. Auto-detect an existing `docs/ai/` to
|
|
|
104
104
|
- **`/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
105
|
- **`/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.
|
|
106
106
|
- **`/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.
|
|
107
|
+
- **`/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` (hand-edited, strict JSON) + backend readiness (default = Reviewed when a backend is ready, Council on request, slot-aware incl. Delegated; graceful default vs loud override degradation). A per-run `--override <slot>=<recipe>` overrides one slot. Composes with `recipes` (which stays read-only); never writes, never commits, never runs a subscription CLI.
|
|
107
108
|
- **`/agent-workflow-kit uninstall`** — the **guarded teardown** companion to `init`/`setup`. Removes what they placed — installed skill dirs + the bridge wrappers — and, in a project, reverses the hidden-mode fence + the marker pre-commit hook. **Never deletes user-authored content** (`docs/ai`, `AGENTS.md`, `.claude/settings.json`): it prints the exact commands for you to run by hand. `--dry-run` first, always; preflight-then-mutate; never commits.
|
|
108
109
|
|
|
109
110
|
### Version status & the two axes — surface this on every invocation
|
|
@@ -112,7 +113,7 @@ Before acting, read `docs/ai/.workflow-version` (the project's stamp), state a o
|
|
|
112
113
|
|
|
113
114
|
- **absent** → bootstrap (a fresh deployment).
|
|
114
115
|
- **stamp < `1.3.0`** (the deployment-lineage head) → `upgrade`.
|
|
115
|
-
- **stamp == `1.3.0`** → already current; only the stamp-independent reconciles may run (the methodology slot
|
|
116
|
+
- **stamp == `1.3.0`** → already current; only the stamp-independent reconciles may run (the methodology slot, the hidden-mode footprint, **and** the `docs/ai/orchestration.json` config ensure, *Mode: upgrade* step 3).
|
|
116
117
|
- **stamp > head / unparseable** → STOP — never-downgrade gate (see *Mode: upgrade* step 2).
|
|
117
118
|
|
|
118
119
|
**Two independent version axes — never conflate them:**
|
|
@@ -166,7 +167,7 @@ readiness, then **append an actionable recipe recommendation** from the recipe p
|
|
|
166
167
|
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.
|
|
167
168
|
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).
|
|
168
169
|
5. **Entry-point doc.** If `AGENTS.md` / `CLAUDE.md` already exist (step-1 recon), do **not** overwrite — show the user and ask whether to merge or replace. Otherwise create `AGENTS.md` (the cross-agent standard — Codex / Cursor / Devin Desktop / Copilot read it natively) from `${CLAUDE_SKILL_DIR}/references/templates/AGENTS.md`, and symlink `CLAUDE.md -> AGENTS.md` (`ln -s AGENTS.md CLAUDE.md`) for Claude Code — single source, no duplication. For nested context, add a subdir `AGENTS.md` (+ a `CLAUDE.md` symlink beside it for Claude Code).
|
|
169
|
-
6. **Deploy `docs/ai/`.** Create
|
|
170
|
+
6. **Deploy `docs/ai/`.** Create every `docs/ai/` file + `pages/` from `${CLAUDE_SKILL_DIR}/references/templates/` (the template loop deploys each non-`AGENTS.md` template — the `.md` docs **and** the seeded, user-editable **`docs/ai/orchestration.json`** config, strict JSON, the per-project recipe defaults the `procedures` advisor reads). Keep each `.md` file's frontmatter (`type / lastUpdated / scope / staleAfter / owner / maxLines`); `orchestration.json` carries no frontmatter (the docs cap-validator globs `*.md` only, so it is inherently skipped).
|
|
170
171
|
7. **Fill templates** per the table below.
|
|
171
172
|
8. **Install enforcement (Node projects).** Copy `${CLAUDE_SKILL_DIR}/references/scripts/*.mjs` (+ `*.test.mjs`) into the project's `scripts/`. They self-configure (project name from `package.json`, hierarchical/on-demand sections auto-discovered). **If the project has no Node runtime** (step-1 recon), skip this step and the hook in step 9 — follow the cap/archive/index policy manually, or port the scripts to the project's language.
|
|
172
173
|
9. **Wire / hide** per visibility (see [Visibility contract](references/contracts.md#visibility-contract)). Install the pre-commit hook (Node projects): `node scripts/install-git-hooks.mjs`. If the installer reports a pre-existing non-marker hook, stop and ask the user to merge it manually rather than overwriting.
|
|
@@ -213,8 +214,10 @@ Fill strategy:
|
|
|
213
214
|
**No-Node project:** the fragments live only in the **installed `agent-workflow-engine`** (`references/methodology-slot.md` + `references/orchestration-slot.md`, under `~/.claude/skills/agent-workflow-engine` or `$AGENT_WORKFLOW_ENGINE_DIR`) — there is no bundled copy, and a No-Node host cannot run the `npx` engine install. Open `AGENTS.md` and classify **each** pointer by hand: a **filled / customized** pair → leave it verbatim (no engine needed); a **malformed** pair (not exactly one ordered `start → end`) → STOP, do not edit. A pair that needs filling — **absent markers OR a present-but-empty pair** — needs the engine's fragment, so: if the engine is **not installed**, that pointer **cannot be added** — report it plainly (the methodology is already in `docs/ai/agent_rules.md`; the recipes are available via `/agent-workflow-kit recipes`; install the engine to add the pointers). If the engine **is** present, **count the lines first** — if adding/filling would take the file over 100 lines, **skip that pointer and report the skip** (methodology first, then orchestration; the orchestration pair sits right under the methodology end marker). Fill each empty pair from its engine fragment (`methodology-slot.md` / `orchestration-slot.md`) — never inline a copy (that would re-create the retired mirror).
|
|
214
215
|
|
|
215
216
|
**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.
|
|
217
|
+
|
|
218
|
+
**Orchestration config ensure — stamp-independent, same gate, BEFORE the equal-head short-circuit.** Ensure `docs/ai/orchestration.json` exists: **create it if missing**, **preserve it byte-for-byte if it already exists** (a user may have edited it — never clobber it). In the **delegated** path memory does this from its own template (memory upgrade step 2); in the **fallback** path the kit seeds it from `${CLAUDE_SKILL_DIR}/references/templates/orchestration.json`. Like the pointer slots + the footprint reconcile, this lets an equal-head (`1.3.0`) deployment gain the config seed **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* vs *already present*).
|
|
216
219
|
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:
|
|
217
|
-
- **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); 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).
|
|
220
|
+
- **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).
|
|
218
221
|
- **Print the one-line backend-status line** — the shared contract above (run `node ${CLAUDE_SKILL_DIR}/tools/detect-backends.mjs`; same format, invariants, and detector-unavailable skip-with-reason).
|
|
219
222
|
- **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.
|
|
220
223
|
5. Show the relevant `${CLAUDE_SKILL_DIR}/CHANGELOG.md` diff (entries newer than the project's stamp).
|
|
@@ -280,6 +283,25 @@ The four recipes (defined over each bridge's `provides` roles — `codex`: execu
|
|
|
280
283
|
|
|
281
284
|
**Invariants:** read-only · never runs a subscription CLI · never commits · the orchestrator executes the recipe via the bridge skills, not the kit.
|
|
282
285
|
|
|
286
|
+
### Mode: procedures
|
|
287
|
+
|
|
288
|
+
Read-only **activity-procedures advisor**. Answers *"what are the steps of this named activity, and which recipe applies at each slot here?"* It composes the orchestration recipes (Mode: recipes) into **named activities** with **typed recipe slots**. It **never writes, never commits, never runs a subscription CLI** — the deterministic resolution lives in the kit; the orchestrator runs the resolved recipe via the bridge skills and **owns any commit when the activity has a commit boundary** (a backend never commits). Not every activity commits: `plan-authoring` ends at approval and produces no commit (plans are ephemeral, never committed); `plan-execution` commits per Step.
|
|
289
|
+
|
|
290
|
+
The two v1 activities (canon in the **installed engine**, `references/procedures.md`):
|
|
291
|
+
|
|
292
|
+
- **`plan-authoring`** (slot: `review`) — research → draft → self-review → **review {recipe}** → fold/loop → present for approval; enforce the mandatory Cleanup.
|
|
293
|
+
- **`plan-execution`** (slots: `execute`, `review`) — per Step: resolve the recipe → if Delegated, dispatch execution first → implement → self-review → **review {recipe}** → gates → commit boundary.
|
|
294
|
+
|
|
295
|
+
Run **`node ${CLAUDE_SKILL_DIR}/tools/procedures.mjs <activity> [--override <slot>=<recipe>]… [--json]`**. It reads the activity's steps live from the engine and prints them **verbatim**, then the **resolved effective recipe per slot** from the per-project config + the read-only backend detector:
|
|
296
|
+
|
|
297
|
+
1. **Config = `docs/ai/orchestration.json`** — strict JSON, **hand-edited** (the kit reads + validates it; `recipes` stays read-only — there is no writer). Shape: `{ "<activity>": { "<slot>": "<recipe>" } }`; all slots optional (an absent slot → its computed default, stated); an optional `"_README"` string is allowed + ignored. `review` accepts `solo|reviewed|council`; `execute` accepts `solo|delegated`. Seeded by `init` (a user-editable template) — see *Mode: bootstrap*.
|
|
298
|
+
2. **Default resolution (config silent):** `review` → Reviewed if any review-capable backend is `ready`, else Solo (never Council by default); `execute` → Solo (Delegated is opt-in). **Degradation:** a config/computed default degrades **gracefully with a stated reason** (Council → Reviewed → Solo; Delegated → Solo); a per-run **`--override <slot>=<recipe>`** that can't be satisfied degrades **loudly** (a flagged warning, so you tell the user) — but is **still exit 0** (a valid request that gracefully degraded).
|
|
299
|
+
3. **Exit codes:** `0` success; `2` usage (unknown `<activity>` / bad `--override` — a bare `--override <recipe>`, an unknown slot, an invalid recipe-for-slot, or a duplicate slot); `1` config error (malformed / schema-invalid / unreadable `orchestration.json`) **or** engine error (the installed engine is absent / invalid / **too old** to ship `references/procedures.md` — upgrade it with `npx @sabaiway/agent-workflow-engine@latest init`). A `1`/`2` failure is loud (`path: reason`), never a silent fallback.
|
|
300
|
+
|
|
301
|
+
**Cap-soft-skip degradation (the feature's only AUTO route).** The activity procedures are auto-discoverable only through the one-line **`workflow:methodology`** pointer (this kit + the engine carry `disable-model-invocation:true`, so NL like "write a plan" does **not** auto-load this skill). On a deployment whose methodology pointer was cap-soft-skipped — or whose pre-existing customized pointer lacks the procedures clause — the procedures are still reachable by **explicitly** invoking `/agent-workflow-kit procedures`; surface that plainly rather than treating it as a gap.
|
|
302
|
+
|
|
303
|
+
**Invariants:** read-only · never writes · never commits · never runs a subscription CLI · the deterministic resolution is the kit's, the recipe execution is the orchestrator's.
|
|
304
|
+
|
|
283
305
|
### Mode: uninstall
|
|
284
306
|
|
|
285
307
|
The **guarded teardown** — the inverse of `init` (the kit + engine skills) + `setup` (the bridges) + a hidden deploy. **In-agent, opt-in**, and built around one hard rule: **it never deletes user-authored content.** Run **`--dry-run` first, always**, show the user the classified plan in plain language, get explicit consent, then re-run with `--yes`. It **never commits**.
|
|
@@ -368,10 +390,10 @@ Deploy these into `AGENTS.md`; remove rows that don't apply to the stack.
|
|
|
368
390
|
|
|
369
391
|
- [`references/contracts.md`](references/contracts.md) — the three setup contracts (visibility, conversational language, agent attribution) in full; the *Setup contracts* section above points here.
|
|
370
392
|
- **Plan vocabulary** (Plan→Phase→Step→Substep), lifecycle, `queue.md` series-index, mandatory Cleanup, session-continuity heuristic — the single home is the **installed `agent-workflow-engine`** canon (`~/.claude/skills/agent-workflow-engine/references/planning.md`, or `$AGENT_WORKFLOW_ENGINE_DIR`); there is no bundled mirror. `npx @sabaiway/agent-workflow-kit@latest init` installs the engine.
|
|
371
|
-
- [`references/templates/`](references/templates/) — stack-agnostic `AGENTS.md`, `agent_rules.md`, and all `docs/ai/` files to deploy.
|
|
393
|
+
- [`references/templates/`](references/templates/) — stack-agnostic `AGENTS.md`, `agent_rules.md`, the seeded user-editable `orchestration.json` config (byte-identical to the memory copy — `test/template-parity.test.mjs`), and all `docs/ai/` files to deploy.
|
|
372
394
|
- [`references/scripts/`](references/scripts/) — the Node enforcement scripts (caps + staleness + index-freshness gate, 3-tier archive, hook installer) and their unit tests.
|
|
373
395
|
- [`migrations/`](migrations/) — per-version upgrade steps; see `migrations/README.md`.
|
|
374
396
|
- [`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`.
|
|
375
|
-
- [`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), `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).
|
|
397
|
+
- [`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).
|
|
376
398
|
- [`capability.json`](capability.json) — the kit's own `agent-workflow` family manifest (`kind: composition-root`).
|
|
377
399
|
- [`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.14.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",
|
|
@@ -139,7 +139,10 @@ export const parseStaleAfter = (value) => {
|
|
|
139
139
|
return Number(m[1]);
|
|
140
140
|
};
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
// Discover the docs to validate: ONLY `*.md` files (recursively). Non-`.md` files — e.g. a hand-edited
|
|
143
|
+
// `docs/ai/orchestration.json` config — are inherently skipped, so they are never subject to the
|
|
144
|
+
// frontmatter / maxLines caps. Exported so that skip is pinned by a regression test.
|
|
145
|
+
export const walkMarkdownFiles = async (dir) => {
|
|
143
146
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
144
147
|
const files = [];
|
|
145
148
|
for (const entry of entries) {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
inspectFile,
|
|
11
11
|
buildIndex,
|
|
12
12
|
checkIndexFreshness,
|
|
13
|
+
walkMarkdownFiles,
|
|
13
14
|
} from './check-docs-size.mjs';
|
|
14
15
|
|
|
15
16
|
describe('parseFrontmatter', () => {
|
|
@@ -126,6 +127,29 @@ describe('inspectFile', () => {
|
|
|
126
127
|
});
|
|
127
128
|
});
|
|
128
129
|
|
|
130
|
+
// The cap-validator discovers ONLY `*.md` files, so a non-`.md` doc — e.g. a hand-edited
|
|
131
|
+
// `docs/ai/orchestration.json` config — is inherently skipped: never validated for frontmatter / caps.
|
|
132
|
+
// This belt-and-suspenders regression pins that skip so adding a config `.json` under docs/ai can never
|
|
133
|
+
// start failing the docs gate.
|
|
134
|
+
describe('walkMarkdownFiles — only *.md is discovered (a config .json is skipped)', () => {
|
|
135
|
+
let dir;
|
|
136
|
+
beforeEach(async () => {
|
|
137
|
+
dir = await mkdtemp(join(tmpdir(), 'walk-md-test-'));
|
|
138
|
+
});
|
|
139
|
+
afterEach(async () => {
|
|
140
|
+
await rm(dir, { recursive: true, force: true });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('returns the .md file and NOT a sibling orchestration.json', async () => {
|
|
144
|
+
await writeFile(join(dir, 'doc.md'), '---\ntype: reference\nmaxLines: 100\n---\n\nbody.\n');
|
|
145
|
+
await writeFile(join(dir, 'orchestration.json'), '{ "plan-authoring": { "review": "reviewed" } }\n');
|
|
146
|
+
const found = await walkMarkdownFiles(dir);
|
|
147
|
+
expect(found.some((f) => f.endsWith('doc.md'))).toBe(true);
|
|
148
|
+
expect(found.some((f) => f.endsWith('.json'))).toBe(false);
|
|
149
|
+
expect(found.some((f) => f.endsWith('orchestration.json'))).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
129
153
|
// Synthetic row matching the shape produced by `inspectFile` + `formatRow`.
|
|
130
154
|
const makeRow = (path, overrides = {}) => ({
|
|
131
155
|
path,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_README": "Per-project orchestration config: the recipe used at each step (slot) of each named activity. Hand-edit this file — it is never written for you. Each activity is configured independently (e.g. plan-authoring, plan-execution), and so is each slot within it. A slot's value is a recipe: a 'review' slot accepts solo | reviewed | council (you self-review / one backend reviews / both review and you synthesize); an 'execute' slot accepts solo | delegated (you implement / a backend runs a bounded sub-task). The default below is 'solo' everywhere — no execution backend required. Raise a slot to reviewed or council for a second opinion, or to delegated to hand off execution; those need an execution backend set up first. Remove a slot's line to fall back to the computed default (reviewed when a review backend is ready, otherwise solo). Run the read-only procedures advisor to see an activity's steps plus the recipe resolved for your environment, and pass a per-run override to change one slot just once. Strict JSON — no comments.",
|
|
3
|
+
"plan-authoring": { "review": "solo" },
|
|
4
|
+
"plan-execution": { "execute": "solo", "review": "solo" }
|
|
5
|
+
}
|
package/tools/engine-source.mjs
CHANGED
|
@@ -26,6 +26,11 @@ export const ENGINE_FRAGMENT_REL = 'references/methodology-slot.md';
|
|
|
26
26
|
// engine older than 1.2.0 does not ship it; detectEngine({ rel }) lets a caller verify the specific
|
|
27
27
|
// fragment so `status` can caveat a too-old engine instead of failing the methodology read.
|
|
28
28
|
export const ORCHESTRATION_FRAGMENT_REL = 'references/orchestration-slot.md';
|
|
29
|
+
// The activity-procedures canon — a NEW live-read engine fragment (engine >= 1.3.0). The procedures
|
|
30
|
+
// CLI reads it via readEngineFragment({ rel }); detectEngine({ rel }) lets `status` caveat — and the
|
|
31
|
+
// CLI fail loudly on — an engine too old to ship it. readEngineFragment accepts an arbitrary `rel`
|
|
32
|
+
// (no whitelist), so no further plumbing is needed beyond this constant.
|
|
33
|
+
export const PROCEDURES_FRAGMENT_REL = 'references/procedures.md';
|
|
29
34
|
const ENGINE_DEFAULT_REL = '.claude/skills/agent-workflow-engine';
|
|
30
35
|
|
|
31
36
|
const defaultStatType = (path) => {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
EXPECTED_ENGINE_NAME,
|
|
10
10
|
ENGINE_FRAGMENT_REL,
|
|
11
11
|
ORCHESTRATION_FRAGMENT_REL,
|
|
12
|
+
PROCEDURES_FRAGMENT_REL,
|
|
12
13
|
} from './engine-source.mjs';
|
|
13
14
|
|
|
14
15
|
// A valid engine dir = it exists (dir), the fragment file exists, and the validator reports a
|
|
@@ -230,3 +231,50 @@ describe('detectEngine / readEngineFragment — orchestration fragment via rel',
|
|
|
230
231
|
);
|
|
231
232
|
});
|
|
232
233
|
});
|
|
234
|
+
|
|
235
|
+
// The activity-procedures canon is a THIRD live-read fragment (engine >= 1.3.0), selected the same way
|
|
236
|
+
// (deps.rel / detectEngine({ rel })). An engine too old to ship it must read as not-ok naming the
|
|
237
|
+
// fragment, and readEngineFragment must STOP loudly — the procedures CLI maps that to a clean exit 1.
|
|
238
|
+
describe('detectEngine / readEngineFragment — procedures fragment via rel', () => {
|
|
239
|
+
const proceduresPresent = (path) =>
|
|
240
|
+
path === ENGINE_DIR
|
|
241
|
+
? 'dir'
|
|
242
|
+
: path === join(ENGINE_DIR, ENGINE_FRAGMENT_REL) || path === join(ENGINE_DIR, PROCEDURES_FRAGMENT_REL)
|
|
243
|
+
? 'file'
|
|
244
|
+
: null;
|
|
245
|
+
|
|
246
|
+
it('exposes the procedures fragment rel constant', () => {
|
|
247
|
+
assert.equal(PROCEDURES_FRAGMENT_REL, 'references/procedures.md');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('verifies the procedures fragment when rel is the procedures path', () => {
|
|
251
|
+
const out = detectEngine(ENGINE_DIR, { source: 'default', rel: PROCEDURES_FRAGMENT_REL }, deps({ statType: proceduresPresent }));
|
|
252
|
+
assert.equal(out.ok, true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('an older engine (no procedures fragment) → detect not-ok, the reason names that fragment', () => {
|
|
256
|
+
const out = detectEngine(ENGINE_DIR, { source: 'default', rel: PROCEDURES_FRAGMENT_REL }, deps()); // okStatType: only the methodology fragment is a file
|
|
257
|
+
assert.equal(out.ok, false);
|
|
258
|
+
assert.match(out.reason, /procedures\.md/);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('readEngineFragment reads the procedures fragment bytes when deps.rel is set', () => {
|
|
262
|
+
const out = readEngineFragment(
|
|
263
|
+
ENGINE_DIR,
|
|
264
|
+
deps({ source: 'default', rel: PROCEDURES_FRAGMENT_REL, statType: proceduresPresent, readFileSync: () => 'PROCEDURES BODY' }),
|
|
265
|
+
);
|
|
266
|
+
assert.equal(out, 'PROCEDURES BODY');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('readEngineFragment STOPs loudly when the procedures fragment is absent (engine < 1.3.0)', () => {
|
|
270
|
+
assert.throws(
|
|
271
|
+
() => readEngineFragment(ENGINE_DIR, deps({ source: 'default', rel: PROCEDURES_FRAGMENT_REL })),
|
|
272
|
+
(err) => {
|
|
273
|
+
assert.match(err.message, /methodology engine not found\/invalid/);
|
|
274
|
+
assert.match(err.message, /procedures\.md/);
|
|
275
|
+
assert.match(err.message, /npx @sabaiway\/agent-workflow-engine@latest init/);
|
|
276
|
+
return true;
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
@@ -23,7 +23,7 @@ import os from 'node:os';
|
|
|
23
23
|
import { resolveDir } from './detect-backends.mjs';
|
|
24
24
|
import { validateManifest, readAuthoritativeVersion, UNSUPPORTED, INVALID } from './manifest/validate.mjs';
|
|
25
25
|
import { START_MARKER, excludePath } from './hide-footprint.mjs';
|
|
26
|
-
import { readEngineFragment, ORCHESTRATION_FRAGMENT_REL } from './engine-source.mjs';
|
|
26
|
+
import { readEngineFragment, ORCHESTRATION_FRAGMENT_REL, PROCEDURES_FRAGMENT_REL } from './engine-source.mjs';
|
|
27
27
|
|
|
28
28
|
// ── manifestState values (the detect-backends precedence, generalized to any member kind) ──────────
|
|
29
29
|
export const NOT_INSTALLED = 'not-installed';
|
|
@@ -144,28 +144,34 @@ export const classifyMember = (member, deps = {}) => {
|
|
|
144
144
|
return { name: member.name, kind: member.kind, installed, skillDir: installed ? skillDir : null, manifestState, version };
|
|
145
145
|
};
|
|
146
146
|
|
|
147
|
-
// An engine
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
147
|
+
// An installed engine may be a VALID methodology-engine yet too old (or incomplete) to ship one of the
|
|
148
|
+
// kit's live-read fragments: `references/orchestration-slot.md` (the recipes pointer, engine >= 1.2.0)
|
|
149
|
+
// and `references/procedures.md` (the activity-procedures canon, engine >= 1.3.0). Each missing
|
|
150
|
+
// fragment is a DISTINCT, plain-language caveat. They are collected into `row.caveats` (an ARRAY) so an
|
|
151
|
+
// engine missing BOTH surfaces both — a single `row.caveat` would overwrite one with the other. The
|
|
152
|
+
// check mirrors what each consumer actually does — `readEngineFragment(..., { rel })` validates
|
|
153
|
+
// the manifest AND reads the fragment — so an absent, non-file, OR present-but-unreadable fragment all
|
|
154
|
+
// surface (status never claims "ok" for a fragment a reconcile / the procedures CLI would STOP on), and
|
|
155
|
+
// a current, readable fragment never gets the caveat. Read-only, best-effort.
|
|
156
|
+
const ENGINE_FRAGMENT_CAVEATS = [
|
|
157
|
+
{ rel: ORCHESTRATION_FRAGMENT_REL, caveat: 'engine present but does not supply the recipes pointer (too old / incomplete) — run `npx @sabaiway/agent-workflow-engine@latest init`' },
|
|
158
|
+
{ 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
|
+
];
|
|
160
|
+
|
|
154
161
|
export const surveyFamily = (deps = {}) =>
|
|
155
162
|
FAMILY_MEMBERS.map((member) => {
|
|
156
163
|
const row = classifyMember(member, deps);
|
|
157
164
|
if (row.kind === 'methodology-engine' && row.manifestState === OK && row.skillDir) {
|
|
158
|
-
const
|
|
165
|
+
const fragmentUsable = (rel) => {
|
|
159
166
|
try {
|
|
160
|
-
readEngineFragment(row.skillDir, { source: 'default', rel
|
|
167
|
+
readEngineFragment(row.skillDir, { source: 'default', rel, ...deps });
|
|
161
168
|
return true;
|
|
162
169
|
} catch {
|
|
163
|
-
return false; // absent / non-file / unreadable fragment → the engine can't supply
|
|
170
|
+
return false; // absent / non-file / unreadable fragment → the engine can't supply it
|
|
164
171
|
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
172
|
+
};
|
|
173
|
+
const caveats = ENGINE_FRAGMENT_CAVEATS.filter((f) => !fragmentUsable(f.rel)).map((f) => f.caveat);
|
|
174
|
+
if (caveats.length) row.caveats = caveats;
|
|
169
175
|
}
|
|
170
176
|
return row;
|
|
171
177
|
});
|
|
@@ -233,7 +239,7 @@ export const formatStatus = (family, project = null) => {
|
|
|
233
239
|
for (const m of family) {
|
|
234
240
|
const ver = m.version ? `v${m.version}` : '—';
|
|
235
241
|
lines.push(` ${pad(m.name, 26)}[${pad(m.manifestState, 16)}] ${pad(ver, 10)} ${m.kind}`);
|
|
236
|
-
|
|
242
|
+
for (const c of m.caveats ?? []) lines.push(` ↳ ${c}`);
|
|
237
243
|
}
|
|
238
244
|
if (project) {
|
|
239
245
|
lines.push('', `project deployment (${project.dir})`, '');
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from './family-registry.mjs';
|
|
19
19
|
import { VALID, INVALID, UNSUPPORTED } from './manifest/validate.mjs';
|
|
20
20
|
import { START_MARKER } from './hide-footprint.mjs';
|
|
21
|
+
import { ORCHESTRATION_FRAGMENT_REL, PROCEDURES_FRAGMENT_REL } from './engine-source.mjs';
|
|
21
22
|
|
|
22
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
24
|
const REPO_ROOT = resolve(__dirname, '../..'); // agent-workflow-kit/tools → repo root
|
|
@@ -110,55 +111,86 @@ describe('surveyFamily', () => {
|
|
|
110
111
|
? { result: VALID, name: 'agent-workflow-engine', kind: 'methodology-engine', available: true }
|
|
111
112
|
: { result: VALID, name: 'x', kind: 'x', available: true };
|
|
112
113
|
|
|
113
|
-
// The
|
|
114
|
-
//
|
|
114
|
+
// The caveats mirror the consumers: each reads a live engine fragment (readEngineFragment) — the
|
|
115
|
+
// recipes pointer (orchestration-slot.md, engine >= 1.2.0) AND the activity-procedures canon
|
|
116
|
+
// (procedures.md, engine >= 1.3.0). An absent / non-file / unreadable fragment surfaces as a DISTINCT
|
|
117
|
+
// caveat in `row.caveats` (an array, so missing BOTH surfaces BOTH); a current readable
|
|
118
|
+
// fragment never gets one. Helpers below model each fragment's on-disk state independently.
|
|
115
119
|
const engineDeps = (over) => ({
|
|
116
120
|
exists: () => true, // SKILL.md marker present (classifyMember)
|
|
117
121
|
stat: () => ({ isFile: () => true }),
|
|
118
122
|
getenv: {},
|
|
119
123
|
home: '/home/test',
|
|
120
124
|
validate: engineValidate,
|
|
121
|
-
readVersion: () => ({ version: '1.
|
|
125
|
+
readVersion: () => ({ version: '1.3.0' }),
|
|
122
126
|
...over,
|
|
123
127
|
});
|
|
128
|
+
// statType where each fragment is independently a readable file ('file') or absent (null); the engine
|
|
129
|
+
// dir + everything else is a 'dir'. readFileSync returns content for a present fragment.
|
|
130
|
+
// Pin the mock to the AUTHORITATIVE engine-source constants (mirrors engine-source.test.mjs), so a
|
|
131
|
+
// fragment-path rename follows here instead of silently passing against the old basename.
|
|
132
|
+
const fragmentStat = ({ orch = 'file', proc = 'file' }) => (p) => {
|
|
133
|
+
const s = String(p);
|
|
134
|
+
if (s.endsWith(ORCHESTRATION_FRAGMENT_REL)) return orch;
|
|
135
|
+
if (s.endsWith(PROCEDURES_FRAGMENT_REL)) return proc;
|
|
136
|
+
return 'dir';
|
|
137
|
+
};
|
|
138
|
+
const caveatsOf = (rows) => rows.find((r) => r.kind === 'methodology-engine').caveats ?? [];
|
|
124
139
|
|
|
125
|
-
it('
|
|
126
|
-
const rows = surveyFamily(engineDeps({
|
|
127
|
-
readVersion: () => ({ version: '1.1.0' }),
|
|
128
|
-
statType: (p) => (String(p).endsWith('orchestration-slot.md') ? null : 'dir'), // fragment ABSENT
|
|
129
|
-
}));
|
|
140
|
+
it('a current engine WITH BOTH fragments readable carries NO caveat', () => {
|
|
141
|
+
const rows = surveyFamily(engineDeps({ statType: fragmentStat({}), readFileSync: () => '> a bounded fragment' }));
|
|
130
142
|
const engine = rows.find((r) => r.kind === 'methodology-engine');
|
|
131
143
|
assert.equal(engine.manifestState, OK);
|
|
132
|
-
assert.
|
|
133
|
-
assert.match(engine.caveat, /recipes pointer|too old|incomplete/i);
|
|
144
|
+
assert.equal(engine.caveats, undefined, 'no caveats when both live fragments are present + readable');
|
|
134
145
|
});
|
|
135
146
|
|
|
136
|
-
it('
|
|
147
|
+
it('an OK engine MISSING the orchestration fragment gets the recipes caveat (only)', () => {
|
|
137
148
|
const rows = surveyFamily(engineDeps({
|
|
138
|
-
|
|
139
|
-
|
|
149
|
+
readVersion: () => ({ version: '1.2.0' }),
|
|
150
|
+
statType: fragmentStat({ orch: null }), // recipes fragment ABSENT, procedures present
|
|
151
|
+
readFileSync: () => '> a bounded fragment',
|
|
140
152
|
}));
|
|
141
|
-
const
|
|
142
|
-
assert.equal(
|
|
143
|
-
assert.
|
|
153
|
+
const caveats = caveatsOf(rows);
|
|
154
|
+
assert.equal(caveats.length, 1);
|
|
155
|
+
assert.match(caveats[0], /recipes pointer/i);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('an OK engine MISSING the procedures canon gets the activity-procedures caveat (only)', () => {
|
|
159
|
+
// The realistic post-release case: an engine at 1.2.0 ships the recipes pointer but not procedures.md.
|
|
160
|
+
const rows = surveyFamily(engineDeps({
|
|
161
|
+
readVersion: () => ({ version: '1.2.0' }),
|
|
162
|
+
statType: fragmentStat({ proc: null }), // procedures canon ABSENT, recipes present
|
|
163
|
+
readFileSync: () => '> a bounded fragment',
|
|
164
|
+
}));
|
|
165
|
+
const caveats = caveatsOf(rows);
|
|
166
|
+
assert.equal(caveats.length, 1);
|
|
167
|
+
assert.match(caveats[0], /activity-procedures|procedures canon/i);
|
|
144
168
|
});
|
|
145
169
|
|
|
146
|
-
it('
|
|
170
|
+
it('an engine MISSING BOTH fragments surfaces BOTH caveats (neither overwrites the other)', () => {
|
|
147
171
|
const rows = surveyFamily(engineDeps({
|
|
148
|
-
|
|
172
|
+
readVersion: () => ({ version: '1.1.0' }),
|
|
173
|
+
statType: fragmentStat({ orch: null, proc: null }),
|
|
149
174
|
}));
|
|
150
|
-
|
|
175
|
+
const caveats = caveatsOf(rows);
|
|
176
|
+
assert.equal(caveats.length, 2, 'both missing fragments are reported');
|
|
177
|
+
assert.ok(caveats.some((c) => /recipes pointer/i.test(c)));
|
|
178
|
+
assert.ok(caveats.some((c) => /activity-procedures|procedures canon/i.test(c)));
|
|
151
179
|
});
|
|
152
180
|
|
|
153
|
-
it('a
|
|
181
|
+
it('a broken engine whose fragments are DIRECTORIES is NOT a false "ok"', () => {
|
|
182
|
+
const rows = surveyFamily(engineDeps({ statType: () => 'dir' })); // every fragment path is a dir
|
|
183
|
+
assert.equal(caveatsOf(rows).length, 2, 'non-file fragments are caveated');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('a fragment PRESENT but UNREADABLE is NOT a false "ok" (mirrors the consumer STOP)', () => {
|
|
154
187
|
const rows = surveyFamily(engineDeps({
|
|
155
|
-
statType: (
|
|
188
|
+
statType: fragmentStat({}), // both present as files
|
|
156
189
|
readFileSync: () => {
|
|
157
190
|
throw Object.assign(new Error('EACCES'), { code: 'EACCES' }); // but unreadable
|
|
158
191
|
},
|
|
159
192
|
}));
|
|
160
|
-
|
|
161
|
-
assert.ok(engine.caveat, 'an unreadable fragment is caveated (mirrors the reconcile STOP), not reported clean');
|
|
193
|
+
assert.equal(caveatsOf(rows).length, 2, 'unreadable fragments are caveated, not reported clean');
|
|
162
194
|
});
|
|
163
195
|
});
|
|
164
196
|
|
|
@@ -201,6 +201,22 @@ export const ensureSlot = (text) => ensureMarkerSlot(text, METHODOLOGY_DESCRIPTO
|
|
|
201
201
|
export const reconcileSlot = (text, fragment, opts) => reconcileMarkerSlot(text, METHODOLOGY_DESCRIPTOR, fragment, opts);
|
|
202
202
|
export const slotNeedsFill = (text) => markerSlotNeedsFill(text, METHODOLOGY_DESCRIPTOR);
|
|
203
203
|
|
|
204
|
+
// The routing token the methodology pointer should carry so NL like "write a plan" auto-discovers the
|
|
205
|
+
// activity procedures. A deployment whose methodology slot was filled (legacy / customized) BEFORE this
|
|
206
|
+
// clause existed will NOT auto-receive it — reconcile preserves a filled slot verbatim (AD-019 §3.1a).
|
|
207
|
+
export const PROCEDURES_POINTER = '/agent-workflow-kit procedures';
|
|
208
|
+
|
|
209
|
+
// Read-only upgrade advisory (NO mutation): when the methodology slot is present + FILLED but lacks the
|
|
210
|
+
// procedures route, return a one-line note the upgrade flow surfaces — add it for auto-discovery; the
|
|
211
|
+
// feature is reachable now via the explicit command. Returns null for an absent / empty / malformed slot
|
|
212
|
+
// or one that already routes to procedures. Pure; never edits the file.
|
|
213
|
+
export const methodologyProceduresHint = (text) => {
|
|
214
|
+
const content = extractSlot(text);
|
|
215
|
+
if (content == null || content.trim() === '') return null; // only a FILLED methodology slot
|
|
216
|
+
if (content.includes(PROCEDURES_POINTER)) return null; // already routes to the procedures advisor
|
|
217
|
+
return `the methodology pointer has no procedures route — add "${PROCEDURES_POINTER} <activity>" for auto-discovery; the activity procedures are reachable now via ${PROCEDURES_POINTER}.`;
|
|
218
|
+
};
|
|
219
|
+
|
|
204
220
|
// A cap-refusal is a SOFT, reported skip (distinct from a malformed/anchor STOP) — keyed off the
|
|
205
221
|
// stable "(cap N)" substring both cap messages carry, so the dual-slot reconcile can skip the
|
|
206
222
|
// orchestration pointer (loud) while keeping the methodology fill, instead of aborting both.
|
|
@@ -307,6 +323,13 @@ const main = async (argv) => {
|
|
|
307
323
|
'reconciled-filled': 'filled the empty workflow-methodology pointer',
|
|
308
324
|
'present-filled': 'workflow-methodology pointer already present',
|
|
309
325
|
}[methResult.status];
|
|
326
|
+
// Read-only upgrade advisory (AD-019 §3.1a): a pre-existing FILLED methodology pointer that predates
|
|
327
|
+
// the procedures clause won't be re-rendered (reconcile preserves it verbatim), so surface a hint to
|
|
328
|
+
// add the procedures route. No mutation — purely a reported note appended to the success report.
|
|
329
|
+
const proceduresNote = methResult.status === 'present-filled' ? methodologyProceduresHint(afterMeth) : null;
|
|
330
|
+
const reportNote = () => {
|
|
331
|
+
if (proceduresNote) console.log(`[inject-methodology] note: ${proceduresNote}`);
|
|
332
|
+
};
|
|
310
333
|
|
|
311
334
|
// ── Explicit [fragment.md] binds methodology ONLY → skip the orchestration reconcile ──
|
|
312
335
|
if (explicitFragmentArg) {
|
|
@@ -361,10 +384,12 @@ const main = async (argv) => {
|
|
|
361
384
|
// Byte-unchanged. Still report a cap-skip (it is not "nothing to do" — a pointer was withheld).
|
|
362
385
|
if (orchSkipped) console.log(`[inject-methodology] reconcile: ${describeMeth}; ${describeOrch}.`);
|
|
363
386
|
else console.log('[inject-methodology] reconcile: both pointers already present and filled — nothing to do (zero-diff).');
|
|
387
|
+
reportNote();
|
|
364
388
|
return;
|
|
365
389
|
}
|
|
366
390
|
await writeAtomic(finalText);
|
|
367
391
|
console.log(`[inject-methodology] reconcile: ${describeMeth}; ${describeOrch}.`);
|
|
392
|
+
reportNote();
|
|
368
393
|
return;
|
|
369
394
|
}
|
|
370
395
|
|