@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 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.13.0'
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 **and** the hidden-mode footprint, *Mode: upgrade* step 3).
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 the 11 files + `pages/` from `${CLAUDE_SKILL_DIR}/references/templates/`. Keep each file's frontmatter (`type / lastUpdated / scope / staleAfter / owner / maxLines`).
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
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "agent-workflow-kit",
5
5
  "kind": "composition-root",
6
- "version": "1.13.0",
6
+ "version": "1.14.0",
7
7
  "provides": [],
8
8
  "roles": {},
9
9
  "detect": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sabaiway/agent-workflow-kit",
3
- "version": "1.13.0",
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
- const walkMarkdownFiles = async (dir) => {
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
+ }
@@ -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 OLDER than 1.2.0 has a valid manifest + version but ships no orchestration-recipes
148
- // fragment (references/orchestration-slot.md), so it cannot supply the recipes pointer the kit
149
- // injects. surveyFamily attaches a plain-language caveat to that engine row instead of a bare "ok".
150
- // The check mirrors what a RECONCILE actually does `readEngineFragment(..., { rel: orchestration })`
151
- // validates the manifest AND reads the fragment so an absent, non-file, OR present-but-unreadable
152
- // fragment all surface as a caveat (status never claims "ok" for a fragment the reconcile would STOP
153
- // on), and a current, readable fragment never gets the caveat. Read-only, best-effort.
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 orchUsable = (() => {
165
+ const fragmentUsable = (rel) => {
159
166
  try {
160
- readEngineFragment(row.skillDir, { source: 'default', rel: ORCHESTRATION_FRAGMENT_REL, ...deps });
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 the pointer
170
+ return false; // absent / non-file / unreadable fragment → the engine can't supply it
164
171
  }
165
- })();
166
- if (!orchUsable) {
167
- row.caveat = 'engine present but does not supply the recipes pointer (too old / incomplete) run `npx @sabaiway/agent-workflow-engine@latest init`';
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
- if (m.caveat) lines.push(` ↳ ${m.caveat}`);
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 caveat mirrors the reconcile: it reads the orchestration fragment (readEngineFragment), so an
114
- // absent / non-file / unreadable fragment all surface; a current readable fragment does not.
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.2.0' }),
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('an OK engine MISSING the orchestration fragment gets a plain caveat', () => {
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.ok(engine.caveat, 'an engine without the recipes fragment carries a caveat');
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('a current engine WITH a readable orchestration fragment carries NO caveat', () => {
147
+ it('an OK engine MISSING the orchestration fragment gets the recipes caveat (only)', () => {
137
148
  const rows = surveyFamily(engineDeps({
138
- statType: (p) => (String(p).endsWith('orchestration-slot.md') ? 'file' : 'dir'),
139
- readFileSync: () => '> orchestration recipes pointer', // present + readable
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 engine = rows.find((r) => r.kind === 'methodology-engine');
142
- assert.equal(engine.manifestState, OK);
143
- assert.ok(!engine.caveat);
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('a broken engine whose orchestration "fragment" is a DIRECTORY is NOT a false "ok"', () => {
170
+ it('an engine MISSING BOTH fragments surfaces BOTH caveats (neither overwrites the other)', () => {
147
171
  const rows = surveyFamily(engineDeps({
148
- statType: () => 'dir', // orchestration path is a directory, not a file
172
+ readVersion: () => ({ version: '1.1.0' }),
173
+ statType: fragmentStat({ orch: null, proc: null }),
149
174
  }));
150
- assert.ok(rows.find((r) => r.kind === 'methodology-engine').caveat, 'a non-file fragment is caveated');
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 current engine whose orchestration fragment is PRESENT but UNREADABLE is NOT a false "ok"', () => {
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: (p) => (String(p).endsWith('orchestration-slot.md') ? 'file' : 'dir'), // present as a file
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
- const engine = rows.find((r) => r.kind === 'methodology-engine');
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