@sabaiway/agent-workflow-kit 5.11.2 → 7.0.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +3 -2
  3. package/SKILL.md +5 -1
  4. package/bridges/antigravity-cli-bridge/bin/agy-review-await-guard.test.mjs +176 -0
  5. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +61 -14
  6. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +606 -467
  7. package/bridges/antigravity-cli-bridge/references/review-prompt.md +42 -4
  8. package/bridges/codex-cli-bridge/SKILL.md +18 -5
  9. package/bridges/codex-cli-bridge/bin/codex-await-guard.test.mjs +161 -0
  10. package/bridges/codex-cli-bridge/bin/codex-exec.sh +22 -17
  11. package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +356 -363
  12. package/bridges/codex-cli-bridge/bin/codex-review.sh +6 -6
  13. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +275 -286
  14. package/bridges/codex-cli-bridge/capability.json +1 -1
  15. package/bridges/codex-cli-bridge/references/driving-codex.md +4 -2
  16. package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +3 -2
  17. package/bridges/codex-cli-bridge/setup/README.md +3 -1
  18. package/capability.json +1 -1
  19. package/package.json +1 -1
  20. package/references/hooks/gate-approve.mjs +1 -1
  21. package/references/modes/grounding.md +1 -1
  22. package/references/modes/mcp.md +37 -0
  23. package/references/modes/procedures.md +3 -3
  24. package/references/modes/recommendations.md +1 -0
  25. package/references/modes/uninstall.md +2 -1
  26. package/references/templates/agent_rules.md +4 -5
  27. package/tools/commands.mjs +7 -0
  28. package/tools/direct-run.mjs +3 -0
  29. package/tools/doc-parity.mjs +18 -2
  30. package/tools/grounding.mjs +10 -20
  31. package/tools/inject-methodology.mjs +2 -0
  32. package/tools/mcp-registration.mjs +283 -0
  33. package/tools/mcp-server.mjs +314 -0
  34. package/tools/mcp-stdio.mjs +229 -0
  35. package/tools/mcp.mjs +299 -0
  36. package/tools/procedures.mjs +7 -8
  37. package/tools/recommendations.mjs +90 -1
  38. package/tools/uninstall.mjs +356 -45
@@ -141,7 +141,7 @@
141
141
  ],
142
142
  "guardrails": [
143
143
  { "value": "read-only sandbox — codex cannot edit, create or delete a file", "enforcement": "enforced", "source": "bin/codex-review.sh" },
144
- { "value": "runs on the pinned frontier model at max effort", "enforcement": "enforced", "condition": "unless CODEX_PROBE=1 relaxes the guard for a throwaway probe", "source": "bin/codex-review.sh" },
144
+ { "value": "runs on the pinned model at max effort", "enforcement": "enforced", "condition": "unless CODEX_PROBE=1 relaxes the guard for a throwaway probe", "source": "bin/codex-review.sh" },
145
145
  { "value": "a successful review appends one receipt line the review-state gate reads", "enforcement": "enforced", "condition": "a receipt write failure warns and the review still succeeds — the tree then reads un-receipted", "source": "capability.json roles.review.contract.receipt" }
146
146
  ],
147
147
  "customHooks": ["CODEX_PROBE"]
@@ -20,8 +20,10 @@ judgment.
20
20
 
21
21
  ## Quality-first: model & effort are pinned
22
22
 
23
- Delegated codex work ALWAYS runs on the frontier model at max effort: `gpt-5.6-sol` / `xhigh` are **pinned**
24
- and a non-default `CODEX_MODEL`/`CODEX_EFFORT` is **refused** (exit 2). Do not try to "tune down" the
23
+ Delegated codex work ALWAYS runs on the PINNED model at max effort: `gpt-5.6-sol` / `xhigh` are **pinned**
24
+ and a non-default `CODEX_MODEL`/`CODEX_EFFORT` is **refused** (exit 2). That the pinned id is still the
25
+ strongest selectable model is a hand-checked claim with no automated gate and no recorded date — see
26
+ SKILL.md. Do not try to "tune down" the
25
27
  model or effort for a real run — the wrapper will stop you. Quota is metered in **messages** (rolling
26
28
  5h + weekly), so economy comes from removing waste (clean capture, the precomputed review diff, resume),
27
29
  never from a downgrade. The only opt-out is a throwaway, effort-independent probe: `CODEX_PROBE=1`
@@ -64,8 +64,9 @@ failure is proven.
64
64
  ## Quality-first guard (pinned model & effort)
65
65
 
66
66
  The wrappers default `CODEX_MODEL=gpt-5.6-sol` and `CODEX_EFFORT=xhigh` and **refuse** (exit 2, loud) any
67
- non-default — delegated work always uses the frontier model at max effort; quality is never traded for
68
- quota. `CODEX_PROBE=1` relaxes this for a throwaway, effort-independent probe only (echoed loudly), and
67
+ non-default — delegated work always uses the PINNED model at max effort; quality is never traded for
68
+ quota. (Whether the pinned id is still the strongest selectable model is a hand-checked claim with no
69
+ automated gate — see SKILL.md.) `CODEX_PROBE=1` relaxes this for a throwaway, effort-independent probe only (echoed loudly), and
69
70
  a probe still runs on the subscription, in the sandbox, with clean capture.
70
71
 
71
72
  ## Passthrough guard (two tiers, after a literal `--`)
@@ -70,7 +70,9 @@ if it reports a missing git work tree or root `AGENTS.md`, run it from a project
70
70
 
71
71
  - The wrappers are **subscription-only** by design and will not use api-key billing.
72
72
  - `codex-exec` runs a **workspace-write** sandbox with **network OFF**; `codex-review` runs
73
- **read-only**. They also pin the frontier model/effort (refusing a downgrade), enforce a hard
73
+ **read-only**. They also pin the model/effort (refusing ANY non-pinned model or effort, a stronger
74
+ one included; whether the pinned id is still the strongest selectable model is a hand-checked claim
75
+ with no gate — see `../SKILL.md`), enforce a hard
74
76
  timeout, capture only codex's final message, and block codex from writing git via a shim — see
75
77
  [`../references/sandbox-and-flags.md`](../references/sandbox-and-flags.md) and the knob table in
76
78
  [`../SKILL.md`](../SKILL.md#environment-knobs). No setup is needed to enable these — they are on by
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": "5.11.2",
6
+ "version": "7.0.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": "5.11.2",
3
+ "version": "7.0.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",
@@ -418,7 +418,7 @@ export const isReadLaneCommand = (command) => {
418
418
  // the behaviour that existed before — silence. Nothing is refused, so a hint that is wrong for a
419
419
  // particular command costs the caller a sentence, never their work. That is the property no deny
420
420
  // design could ever claim.
421
- export const HINT_CORE = 'agent-workflow: this read left the promptless lane — a plain read-only command with no redirection, no banner and no quotes stays inside it. If the byte is a decoration you do not need, drop it and re-issue; if it is ordinary text inside an argument, a search belongs in `repo-search.mjs` (`--pattern-file`), and several path questions (exists / size / line count / listing / small-file contents) are ONE `path-inventory.mjs` call. If the byte is a deliberate operator, keep it and confirm by hand.';
421
+ export const HINT_CORE = 'agent-workflow: this read left the promptless lane — a plain read-only command with no redirection, no banner and no quotes stays inside it. If the byte is a decoration you do not need, drop it and re-issue; if it is ordinary text inside an argument, take it out of the command string entirely: where this project registers the kit\'s MCP server (`/agent-workflow-kit mcp`), a search is the `repo_search` tool and several path questions (exists / size / line count / listing / small-file contents) are ONE `path_inventory` call — the pattern and the paths are JSON fields, so no byte of them is ever shell. The same two readers on the command line are `repo-search.mjs` (`--pattern-file`) and ONE `path-inventory.mjs` call. If the byte is a deliberate operator, keep it and confirm by hand.';
422
422
 
423
423
  // The hint must not GUESS which of the two causes produced the residual — the guard cannot tell a
424
424
  // byte that is ordinary text inside an argument from a real shell operator, and pretending otherwise
@@ -8,7 +8,7 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/grounding.mjs [--constraints] [--autonomy] [
8
8
 
9
9
  1. **`--constraints`** — slice the root `AGENTS.md` **Hard Constraints** section, verbatim and whole (exactly ONE matching heading; zero or several is a loud STOP, never a guess — the marker-slot discipline).
10
10
  2. **`--autonomy`** (AD-044 Plan 3) — append the COMPUTED effective autonomy policy: the shared resolver (`resolveAutonomy`) over the **git-top** `docs/ai/autonomy.json` renders every red-line + per-activity level with a stated source line. An ABSENT file is exit 0 — the computed defaults ARE the effective policy (unlike `velocity-profile --autonomy`, which writes settings and refuses an undeclared policy); a malformed/unreadable policy fails CLOSED (exit 1). Informational for the reviewing backend — enforcement stays the OS sandbox + the orchestrator. Rides between the constraints and the plan sections.
11
- 3. **`--plan <path>`** — extract the plan's decision-bearing canonical sections, verbatim + whole: `## Approach` (REQUIRED its "What we are NOT doing" text rides inside; it is not a heading in canon) and `## Verification` (REQUIRED STOP if missing), plus `## Decisions (locked)` when present (the optional engine §7 heading); a DUPLICATE heading is always a STOP.
11
+ 3. **`--plan <path>`** — extract the plan's three canon sections, verbatim + whole: `## Goal and boundary`, `## Module ledger`, `## Verification` (the engine `planning.md` literal headings; each REQUIRED a missing or DUPLICATE heading is always a STOP).
12
12
  4. **`--extra <text|@file>`** (corpus #88/#95 — the fourth firing of the same pre-dispatch append step) — append orchestrator-supplied facts byte-VERBATIM after the mechanical sections (repeatable, argv order; the `agy-review --facts` convention: literal text, or `@path` read whole through a race-free descriptor). An `@file` must resolve inside the PROVEN git work tree (rev-parse success — no cwd fallback; the git dir itself is refused) or the system temp surface — anything else refuses loudly (the exfil class: this tool is bridge-tier auto-allowable), as does a missing, EMPTY, or non-regular file (a FIFO cannot block the read). The merge happens INSIDE the tool, so the promptless lane is the invocation itself.
13
13
  5. **Byte budget** — the output honors the same `AGY_MAX_PROMPT_BYTES` contract the agy wrapper enforces (override may only tighten; the OS argv ceiling is rejected), MINUS **`--reserve-bytes <n>`** — the artifact share the caller expects `agy-review` to add around these facts. Overflow is trimmed tail-first with a loud in-band marker + stderr report — never a silent cut.
14
14
  6. Feed the result to the wrapper: `agy-review code --facts @<out>`. The `procedures` advisor renders this invocation as a concrete pre-step whenever the resolved review dispatch includes agy — populated with the in-flight plan path when exactly one plan is in flight.
@@ -0,0 +1,37 @@
1
+ ### Mode: mcp
2
+
3
+ <!-- opt-in-capability: mcp-channel -->
4
+
5
+ **The typed channel, registered in ONE project.** The kit ships a stdio MCP server (`tools/mcp-server.mjs`) exposing the two promptless readers as TYPED tools — `path_inventory` (exists / type / size / line count / listing / a small file's text, many paths in ONE call) and `repo_search` (LITERAL search, the pattern a JSON string). Both take their arguments as named JSON fields rather than as a string handed to a shell, so a pipe, a redirect, a quote or an `||` inside a pattern or a path stays DATA and is never interpreted — they are legal bytes to search for, and there is no shell to read them as operators. Shipping that server does nothing on its own: a client only sees it once the project DECLARES it. This mode is that declaration, and nothing else.
6
+
7
+ It writes exactly two files:
8
+
9
+ - **`.mcp.json`** at the project root — the `"agent-workflow"` entry under `mcpServers`: `"type": "stdio"`, `"command": "node"`, and `args` = the **absolute path of the RUNNING kit's** `tools/mcp-server.mjs` (the literal path, resolved at write time — a moved kit fails visibly rather than resolving to something else).
10
+ - **`.claude/settings.json`** — `enabledMcpjsonServers` gains `"agent-workflow"` (a project-scoped `.mcp.json` server is inert until it is enabled), and `permissions.allow` gains the two rules derived from the server's own name and tool list: `mcp__agent-workflow__path_inventory` and `mcp__agent-workflow__repo_search`. Both rules are DERIVED, never re-typed — a renamed tool cannot ship a rule that grants nothing.
11
+
12
+ **Trust posture (state it plainly when asking consent):** the registered server is a **read-only child of your MCP client** — it answers path facts and literal searches over the project root, and exposes no write and no exec API. It runs **outside the Bash sandbox**, exactly as the client itself does, so the sandbox is not what bounds it; the server's own root containment is. The two allow rules make its tool calls promptless — that is the whole velocity gain, and it applies to those two tools only. Registering a server means your client will RUN the named command, which is why the **exact entry is printed before consent is asked**, never described.
13
+
14
+ Run `node ${CLAUDE_SKILL_DIR}/tools/mcp.mjs [--dry-run | --apply] [--cwd <dir>]`:
15
+
16
+ 1. **`--dry-run` first, always** (the default — changes nothing): prints what each file would gain and the **exact entry it would declare** — the same structured value, re-serialized for the preview rather than copied byte for byte — plus the apply one-liner. The preflight is **READ-ONLY**: an absent `.claude/` is a named state here and is created on `--apply` only — a preview that made a directory would not be a preview.
17
+ 2. **Ask consent** via **`AskUserQuestion` where supported**, the no-change option first: keep the shell lanes, or register the typed channel — presenting the trust posture above in plain language.
18
+ 3. **Only on an explicit yes**, re-run with `--apply`. It writes **`.mcp.json` FIRST, then `.claude/settings.json`** — settings that enable a server whose entry is not yet there is a client error on every startup. Both writes are merge-don't-clobber (foreign servers, foreign keys and existing allow rules preserved; a re-apply adds nothing twice) and keep each file's existing EOL. New MCP servers are picked up when the client next starts — unlike the `hook` mode, this is not a hot reload.
19
+
20
+ **The two arms that are not a write:**
21
+
22
+ - **DIFFERING** — `.mcp.json` already carries an `"agent-workflow"` entry that **structurally differs** from ours (another kit copy, a hand-edited path, an added `env`, a `null`). The comparison is deliberately key-order-independent, so a re-serialized identical entry is the SAME registration, while any real difference in what would be launched is a difference. The run **STOPs unwritten, on both lanes**: silently changing what an MCP server launches is exactly what consent must not slide past. The recovery is named — review that entry, then remove or rename it and re-run. The kit never resolves this for you.
23
+ - **MASKED** — an OS sandbox injects a **character device** where `.mcp.json` would be (this is the normal condition of a sandboxed session, not a fault; see `${CLAUDE_SKILL_DIR}/references/modes/sandbox-masks.md`). Writing through a device node is refused by construction, so the mode **hands over what to merge, writes nothing, and exits 0**. The two halves of that handoff are worded differently, and the difference is load-bearing: `.claude/settings.json` was **observable — and read where present** — so its body is a real merge already carrying every foreign key it had, while the masked `.mcp.json` was not observable at all, so the mode hands over **the entry alone**, to be merged under `mcpServers` keeping every other server. A whole-file body would name only our server, and pasted as instructed it would delete every server the mask hid. Every other never-committable class (block device, FIFO, socket) takes the same arm. The handoff is scoped to `.mcp.json`: a masked `settings.json` is a **refusal**, because this mode can neither write it nor merge into what it cannot read.
24
+
25
+ **Refusals (STOP, exit 1, zero writes) — decided BEFORE the handoff, so a mask can never swallow one:** a symlinked project root · a symlinked or **directory** `.mcp.json` / `settings.json` (the atomic publish renames over the path, which would replace the **link itself** — silently destroying a deliberate indirection, while the file the reader was actually looking at goes untouched). Note what is **not** in this list: a device node, FIFO or socket at `.mcp.json` takes the successful MASKED handoff above, not a refusal — only at `settings.json` is that class a refusal, since no handoff is sanctioned there · a symlinked or non-directory `.claude` · a masked `settings.json` · malformed or unreadable JSON in either target · a **managed key of the wrong type** — `mcpServers`, `enabledMcpjsonServers`, `permissions` or `permissions.allow` present but not the shape this mode writes, which read as empty containers would be merged over and silently destroyed (a foreign key of any shape is data and is never judged) · the DIFFERING entry above.
26
+
27
+ **Reading posture (the residual, stated).** The **container is classified before the file inside it is read**: path resolution follows an *intermediate* symlink (`O_NOFOLLOW` guards only the final component), so a symlinked `.claude` would otherwise deliver a settings file from outside the work tree — into memory, into a verdict, and into a rendered merge body — before anything could refuse it. A foreign container makes its settings target `unreadable` with that reason, and nothing inside it is read. Beyond that, every target is classified by `lstat` **before** any open, so a target decided **foreign** is never followed, never opened and never read: a symlink is refused by class, and a device node or FIFO is named rather than consumed. (A target decided **regular** is of course opened — that is the read.)
28
+
29
+ The **residual is stated rather than implied**: every guard here is a path check followed by a path read, and Node exposes no `openat`. What is closed is the **static** case — a symlinked container or leaf that is already there is never followed and never read. What is **not** closed is a path that **changes underneath** the guard, and the leaf and the container are not the same problem. The **leaf** is protected as far as a path-based reader can go — the shared reader opens `O_NOFOLLOW`, stats the descriptor and reads through it, so a swapped *symlink* can never be followed; what escapes it is substitution by another *regular file*, which needs the open bound to an earlier inode observation inside that shared primitive (four consumers use it, so it is that leaf's decision, not this mode's). The **container** cannot be closed that way at all: nothing about the leaf's descriptor speaks for the directory the path was resolved through, and that needs directory-relative opening (`openat`), which Node does not expose — a platform limit rather than a missing check. **The bar this mode meets is therefore: a path already foreign is never followed or read, and a target decided foreign is never opened** — nothing here claims more.
30
+
31
+ **Invariants:** writes ONLY `.mcp.json` + `.claude/settings.json` · **never `settings.local.json`** · never commits · never edits `docs/ai/gates.json`, `lanes.json` or any other declaration · the server itself gains no writer tool from this mode · user MCP scope and other clients (Codex, Cursor) are out of scope — this registers the project scope for Claude Code.
32
+
33
+ **Out of scope, by name: `disabledMcpjsonServers`.** A server listed there is rejected by the client in **every** mode, so a project can hold the entry, the enable and both rules and still have a dark channel. **This mode does not detect that**, and "registered" therefore means *the three things this mode writes are in place* — never *the client will load it*. The check was built during review and then **subtracted**: honouring a veto means reading it from every scope the client merges, and each scope has its own masked, symlinked, malformed and unreadable states in which a hidden deny still yields a confident answer. Three review rounds each closed one such hole and opened the next. A check that is wrong in states it cannot enumerate is worse than a stated limit — so this is the stated limit, and the queue carries it.
34
+
35
+ **Reporting and removal.** The read half (`tools/mcp-registration.mjs`) is what the `recommendations` advisor's `mcp-channel` item and `uninstall` both ask; it never edits. `uninstall` reports both seams as **KEEP / report-only** with the exact edit to make by hand — the same posture it takes for every `.claude/settings.json` key.
36
+
37
+ **Exit codes:** `0` done / dry-run / the hand-apply masked state; `1` a precondition STOP; `2` bad arguments.
@@ -2,12 +2,12 @@
2
2
 
3
3
  <!-- opt-in-capability: none — an inspection surface that configures nothing -->
4
4
 
5
- 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 (`${CLAUDE_SKILL_DIR}/references/modes/recipes.md`) 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.
5
+ 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 (`${CLAUDE_SKILL_DIR}/references/modes/recipes.md`) 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 ledger row.
6
6
 
7
7
  The two v1 activities (canon in the **installed engine**, `references/procedures.md`):
8
8
 
9
9
  - **`plan-authoring`** (slot: `review`) — research → draft → self-review → **review {recipe}** → fold/loop → present for approval; enforce the mandatory Cleanup.
10
- - **`plan-execution`** (slots: `execute`, `review`) — per Step: resolve the recipe → if Delegated, dispatch execution first → implement → self-review → **review {recipe}** → gates → commit boundary.
10
+ - **`plan-execution`** (slots: `execute`, `review`) — per ledger row: resolve the recipe → if Delegated, dispatch execution first → implement → self-review → **review {recipe}** → gates → commit boundary; after the last row, the project-declared stages and Phase: Cleanup run as rows of their own.
11
11
 
12
12
  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:
13
13
 
@@ -17,7 +17,7 @@ Run **`node ${CLAUDE_SKILL_DIR}/tools/procedures.mjs <activity> [--override <slo
17
17
 
18
18
  **Flow armed-halves block (session-start read side).** When the config carries a `flow` block, the advisor also renders `Flow (schema 1) — armed halves (config · chain · bookkeeping):` — the **config half** (preset · councilRounds · kitMinVersion), the **chain half** (a light read-only probe of the flow store on the checker's fixed path: ARMED at an adoption record, UNARMED for an absent or unadopted store, fail-closed BROKEN wording for a malformed one), and the **bookkeeping half** (each declared path: declared non-excluded — the tracked-file floor verifies on the `set-flow` arming path — vs loudly DECLARED-EXCLUDED). A config with no `flow` block renders byte-identically to before and pays no store probe.
19
19
 
20
- **Declared source-size practice block (upfront knowledge, D-17 U1).** When the project declares a source-size practice in `docs/ai/source-size.json`, the advisor also renders `Declared source-size practice (docs/ai/source-size.json) — known BEFORE the code is written:` — the caps, what is recorded, the canonical WHY sentence and the plan-time rung (every Step that creates a file names the file and its single responsibility, and the planned layout fits the caps). `--json` carries the SAME lines under **`declaredPractice`** (an empty array when nothing is declared), so the human and structured renders never diverge. The four config states each read as themselves: **ABSENT** renders nothing at all (a project declaring no practice is handed no invented limits), **AUTHORED** and **INCOMPLETE** are pre-mint (the second names the half-written machine key), **MINTED** carries the recorded counts. A declaration that cannot be read (malformed, unknown-keyed, a dangling symlink) renders ONE loud `UNREADABLE` line and the advisor **still exits 0** — the practice's own checker owns the exit code for its config (exit 2 there) and its declared gate reds the gate matrix. Read through the practice's pure read core, so this advisor never reaches its writer half.
20
+ **Declared source-size practice block (upfront knowledge, D-17 U1).** When the project declares a source-size practice in `docs/ai/source-size.json`, the advisor also renders `Declared source-size practice (docs/ai/source-size.json) — known BEFORE the code is written:` — the caps, what is recorded and the canonical WHY sentence (the plan-time layout itself is the planning canon's Module ledger). `--json` carries the SAME lines under **`declaredPractice`** (an empty array when nothing is declared), so the human and structured renders never diverge. The four config states each read as themselves: **ABSENT** renders nothing at all (a project declaring no practice is handed no invented limits), **AUTHORED** and **INCOMPLETE** are pre-mint (the second names the half-written machine key), **MINTED** carries the recorded counts. A declaration that cannot be read (malformed, unknown-keyed, a dangling symlink) renders ONE loud `UNREADABLE` line and the advisor **still exits 0** — the practice's own checker owns the exit code for its config (exit 2 there) and its declared gate reds the gate matrix. Read through the practice's pure read core, so this advisor never reaches its writer half.
21
21
 
22
22
  **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.
23
23
 
@@ -31,6 +31,7 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/recommendations.mjs --cwd <project-root> [--
31
31
 
32
32
  - `source-size` — the practice's arms differ by what the declared gate actually IS. **`adopted-elsewhere`** — the gate runs a DIFFERENT real copy of this checker (a vendored deployment): the practice is running, the advisor simply cannot resolve it as its own sibling, and the realpath anchor deliberately does not widen (it is what stops a lookalike from certifying). So the convergence is the ack writer's preview (`--lane source-size-copy` → `sourceSizeCopyAck`), never `--adopt`, which would mint the record and then be refused by the fill on the id already declared. The fingerprint binds the declared claims AS AUTHORED, sorted — not their resolved paths, which are machine-specific and would churn a committed ack between machines — so a second external copy re-fires it. **`id-squatter`** — a gate carries the `source-size` id and is not this checker under any reading: the id claims the practice while nothing measures module size, so it is **HAND-APPLY** (rename the id, or repoint the cmd at this checker's `--check` form). **`unminted`** stays what it was for both copies: a gate certain to refuse on every run, attention, and NO acknowledgement silences it — only the rendered verb differs (with the gate already declared elsewhere the missing half is the record alone, so the line is `--write-baseline`). Risk profile: `docs/ai/gates.json` is a PRIVILEGED file, so every arm here either records an acknowledgement or hands you an edit — the kit declares nothing on its own.
33
33
  - `gate-hook` — the base arm is the ordinary opt-in wiring offer. The **`marker-stale`** arm is not: the placed hook validates your declaration through its OWN baked copy and goes dark on any key it does not know, so a declaration carrying the `lcovProducer` key under a hook that predates it silently turns auto-approval OFF, and every gate prompts again with no error anywhere. The condition is the key's PRESENCE, not its value — an older hook rejects a key it does not know whatever that key says, so `"lcovProducer": false` darkens it exactly as `true` does. The arm is deliberately marker-scoped — a stale hook is otherwise harmless — and its recovery is the writer's own: `gate-hook --apply` places only an ABSENT target, so converging means deleting the placed hook and re-placing it. That makes it a destructive **HAND-APPLY** (`rm` + `--apply`, absolute path so it can only delete this project's hook), never something the consent flow runs for you. When the read-lane is also enabled, this arm and `read-lane.stale` would report the same file with the same recovery, so exactly one renders — **this one**, because its cause is the true one: a hook that postdates the read-lane and merely predates the marker key reads `lanes.json` perfectly well, and the read-lane arm's wording would be false over it. Risk profile: deleting one placed hook file, then re-placing it from the bundle; the declaration is never touched.
34
+ - `mcp-channel` — the offer is to REGISTER the kit's read-only stdio MCP server in this project, which is a different kind of consent from the other velocity items: a registration is a command your MCP client will RUN, so the apply here is the mode's own **flagless preview**, which prints the exact entry and writes nothing — the `--apply` that follows is a SEPARATE step you run after reading that entry, and the consent flow never carries it. What the registration buys: path questions and literal searches become typed tool calls whose arguments are named JSON fields instead of a string handed to a shell, so a pipe, a redirect or a quote inside a pattern or a path stays DATA and is never interpreted — they are perfectly legal bytes to search for, there is simply no shell to read them as operators. Posture: the server is a **read-only child of your client** (path/type/size/line facts and literal search over this project root; no write and no exec API), and like the client itself it runs **outside the Bash sandbox** — the sandbox is not what bounds it, the server's own root containment is. The two allow rules it adds make those two tools promptless and nothing else. Two arms are **HAND-APPLY** and never run for you: **`.differing`** — an `agent-workflow` entry already stands in `.mcp.json` and **structurally differs** from what this kit copy would write (another kit copy, a hand-edited path, an added `env`; the comparison ignores key order, so re-serialized identical bytes are the same registration), and silently changing what an MCP server launches is exactly what consent must not slide past, so the remedy is your edit; and the **masked** arm, where an OS sandbox hides `.mcp.json` behind a device node — the kit cannot write there, so it hands you the text to paste from outside the sandbox. When the file is masked but the settings half is already complete, the item **does not render at all**: what is unobservable becomes a stated SKIP, so optimality is withheld rather than a registration you already made being offered again. Stated limit: this item does **not** detect a `disabledMcpjsonServers` veto, so a converged `mcp-channel` means *what the mode writes is in place*, not *the client will load it* — see `${CLAUDE_SKILL_DIR}/references/modes/mcp.md` for why that check was subtracted rather than half-built. Risk profile: a new read-only channel your client will launch; no write or exec exposure, and no existing declaration is touched.
34
35
  - ADDITIONAL `gates-inert` arms (the third outcomes) — two further arms, and they differ in whether anything is BROKEN. **`producer-unrecognized`** — a checker with no producer anywhere in the declaration, on a tracked tree the changed-line coverage domain cannot reach (`.ts`/`.tsx`/`.jsx`/`.mts`/`.cts` strictly outnumber `.mjs`/`.cjs`/`.js`): the dead pair is real, so this arm is **HAND-APPLY** and the two remedies are marking the real producer with `"lcovProducer": true` or dropping the checker. Never a `node --test` prescription over a project that has no such suite, never the fill preview, and never an acknowledgement — a dead pair is broken, not narrow, and removing a producer after an acknowledgement lands right back in this arm. **`coverage-domain-narrow`** — the producer/checker pair IS live and the tree is still dominated by what the domain excludes: nothing is broken, and the honest sentence is that certification covers the assessable minority. Its apply is the consent-gated **ack writer** preview (a NEUTRAL fingerprint into `docs/ai/acks.json` as `coverageDomainAck`, never a security key); after the SAME confirmation you run the `--apply` it prints. The fingerprint binds the FACT — the verdict plus the unsupported extensions present, never the file counts — so an acknowledged project stays quiet as it grows and re-fires when a new unsupported language arrives or the verdict flips. The census reads the TRACKED tree with a read-only `git ls-files`; a tree it cannot read (a non-git deployment) becomes a stated skip, so optimality is withheld rather than assumed. Risk profile: no enforcement change of any kind — one acknowledgement recorded in a family-owned file, and one hand edit that stays the maintainer's.
35
36
  - `adr-store-migration` — other items write project files too; what is unique here is that the crossing **overwrites and deletes files the project already has**: it replaces the deployed enforcement scripts in `scripts/` (the directional subset — only basenames the project already has; a locally-edited copy is snapshotted first, never silently clobbered) and, where a retired archive file exists, DELETES it once conservation has been proven. That is why it is **HAND-APPLY** and why the command shown in the apply slot is a **`--dry-run`** — it writes nothing and prints the whole plan. `--apply` is a SEPARATE step, run only after that plan has been shown and **fresh consent** obtained for it; the consent flow executes only the apply slot, so an item that needs consent AFTER its preview cannot use that lane at all. Every write is idempotent and the run is re-runnable to completion after any interruption, so a re-run repairs rather than double-applies. It never commits. Risk profile: overwrite + delete of existing project files, gated on a preview you have actually read.
36
37
 
@@ -13,8 +13,9 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/uninstall.mjs [<member>] [--dir <project>] [
13
13
 
14
14
  It classifies every surface into four classes and acts accordingly:
15
15
 
16
- - **remove** (safe) — an installed skill dir that is **provably ours** (valid manifest, `name`+`kind` match). A dir present but **not provably ours** (`foreign`/`stub`/`invalid`/unreadable) → **STOP**: left untouched **and reported**, while the teardown still removes the members that ARE ours (a not-ours surface is never clobbered, and never blocks removing the rest — the per-item `setup` posture). **Preflight-then-mutate:** if a mutable surface **changed since the dry-run** (a skill no longer ours, a wrapper turned foreign, a hook that lost our marker, a malformed fence), the run **aborts with zero changes**.
16
+ - **remove** (safe) — an installed skill dir that is **provably ours** (valid manifest, `name`+`kind` match). A dir present but **not provably ours** (`foreign`/`stub`/`invalid`/unreadable) → **STOP**: left untouched **and reported**, while the teardown still removes the members that ARE ours (a not-ours surface is never clobbered, and never blocks removing the rest — the per-item `setup` posture). **Preflight-then-mutate:** the apply run builds its OWN fresh plan and preflights every mutable surface against it; if one has changed by then (a skill no longer ours, a wrapper turned foreign, a hook that lost our marker, is unreadable or is no longer a regular file, a malformed fence, or a path now reachable only through a symlink), the run **aborts with zero changes**. A surface that changes AFTER that preflight — during the mutation itself — cannot be answered that way, and the report distinguishes two outcomes rather than flattening them. A removal **refused before it touched anything** (containment, or a skill directory that stopped being provably ours — re-established immediately before the recursive delete, not only at preflight) is reported as **left untouched**, which is a claim the refusal proves. A removal that **failed part way** proves nothing of the sort: it is reported as **possibly partially removed**, and it STOPS the run, because continuing to delete later surfaces on top of an unknown state is not a teardown. Either way the run ends with a typed **INCOMPLETE** stop listing both categories plus what had already been applied — or stating plainly that nothing had been. That outcome is a real one, not a promise of atomicity.
17
17
  - **reverse** (managed-marker) — a bridge **wrapper symlink that points at our source** (a foreign/non-symlink one → STOP); the hidden-mode **managed fence** (via the existing `--unhide` path — only the fenced lines); a **pre-commit hook carrying our marker** (an unmarked / user hook → left + reported).
18
18
  - **KEEP — never deleted** (report-only) — `docs/ai`, `AGENTS.md`, `CLAUDE.md`, `docs/plans`, and `.claude/settings.json` (the `includeCoAuthoredBy` edit **and** any `permissions.defaultMode`/`permissions.allow` the velocity profile may have seeded). The tool **prints the exact `rm` / `git rm --cached`** for the docs/entry files and an **edit** instruction for `settings.json` (never an `rm` — it may hold your own settings); the **user** runs them. Surface this in plain language; never delete on their behalf.
19
+ - **KEEP — the MCP registration** (report-only, same posture, two seams): the `"agent-workflow"` entry in **`.mcp.json`** at the project root, and in `.claude/settings.json` the `enabledMcpjsonServers` membership plus the two tool rules `mcp__agent-workflow__path_inventory` and `mcp__agent-workflow__repo_search`. Both are reported with the exact **edit** to make by hand — `.mcp.json` may declare servers this kit never placed, and the settings file may hold your own rules, so neither is ever rewritten or removed. A `.mcp.json` that is a symlink or a sandbox device mask is reported **unread**, never followed and never parsed.
19
20
 
20
21
  **Shared globals:** removing `agent-workflow-memory` / `agent-workflow-engine` / a bridge removes a **global** skill that another project on the machine may use — say so before applying. **Windows:** the wrappers are POSIX; the skill-dir + project arms still work, the wrapper arm reports *use WSL*.
@@ -77,11 +77,11 @@ Apply this as part of §2 before any user-facing summary:
77
77
  Apply these when authoring a plan, reviewing, folding a finding, or editing code — the layer read **before any code change**. (Full canon: the project's planning / workflow-methodology + orchestration canon. This section is rendered from that canon and refreshed on upgrade; a custom edit is preserved verbatim, but flagged.)
78
78
  - **Fold by code, not prose.** Before folding a code-touching finding into a plan or change, read the cited `file:line` and cite it — a prose fold drifts from the code and seeds the next bug.
79
79
  - **Right altitude.** Pin intent + invariants + acceptance criteria (named tests); leave fine code-mechanics to Execute, where prose cannot diverge from reality.
80
- - **No code-mechanics in the plan.** A Step still carries its exact paths + commands (the plan-structure / self-review canon) — checked syntax: the plan's own Verification runs them against an explicit expected outcome or gate; the only other syntax a plan may carry is a literal fixture/schema fragment a named test copies or validates. Un-run, logic-bearing syntax — control-flow, a regex, a glob, a grammar, an algorithm body, a mini-DSL — never lives in plan prose, however plausible or shell-verified it looks: a fold or draft that wants one is the trigger to write the test instead.
80
+ - **No code-mechanics in the plan.** A ledger row carries its path and anchor, and Verification carries the exact commands (the plan-shape canon) — checked syntax: the plan's own Verification runs them against an explicit expected outcome or gate; the only other syntax a plan may carry is a literal fixture/schema fragment a named test copies or validates. Un-run, logic-bearing syntax — control-flow, a regex, a glob, a grammar, an algorithm body, a mini-DSL — never lives in plan prose, however plausible or shell-verified it looks: a fold or draft that wants one is the trigger to write the test instead.
81
81
  - **Test-as-spec.** Fold a code-touching finding into a red→green TEST, not a prose paragraph — the gate is the only deterministic checker; a paragraph cannot self-check.
82
82
  - **Characterize-first.** Before editing UNCOVERED code, pin its current behavior in a green test, then edit — any unintended change goes red. Never edit what has no checker; first give it one. Keep edits atomic/reversible; prefer SUBTRACTIVE folds.
83
83
  - **Fold minimally — prose has no checker.** An ephemeral, gitignored plan is prose with no executable checker; fold **minimally, in ONE place** and run a **self-consistency** read across the plan before every re-review — a fold that drifts several prose spots is what turns a 2-round review into churn.
84
- - **Heavy review at the diff.** Plan-review settles architecture only (≤2 rounds, stop at the pre-existing→fold-induced crossover); the exhaustive per-Step review runs against real compiling code + the full suite, where a regression fails a gate immediately. **Backend divergence** (one backend grounded-ships while another keeps revising mechanics) IS that crossover — resolve at altitude, don't exhaust the strictest backend; route an all-mechanics/CI or prose-only artifact to a **thin plan + diff-review**.
84
+ - **Heavy review at the diff.** Plan-review settles architecture only (≤2 rounds, stop at the pre-existing→fold-induced crossover); the exhaustive per-row review runs against real compiling code + the full suite, where a regression fails a gate immediately. **Backend divergence** (one backend grounded-ships while another keeps revising mechanics) IS that crossover — resolve at altitude, don't exhaust the strictest backend; route an all-mechanics/CI or prose-only artifact to a **thin plan + diff-review**.
85
85
  - **Convergence bar.** A review loop is CLEAN only when one round returns **0 blockers + 0 majors** from EVERY backend the recipe names (nits + a ship verdict is the stop). Folding ≠ convergence — re-review after folding.
86
86
  - **Per-round emission.** Every review round emits **{round N · finding-origin tally · per-backend verdict}** so the crossover is a computed, visible signal, not a remembered rule.
87
87
  - **Recipe fidelity.** Council runs every backend the recipe names, **every round**; silently dropping a ready backend for quota/convenience is a forbidden downgrade — an unavailable backend is a LOUD, stated degrade, never a quiet drop.
@@ -96,8 +96,7 @@ Split a complex task across sessions for **focus and review hygiene**, not becau
96
96
 
97
97
  - **Split (separate sessions)** when the *change* is large enough to deserve an isolated review checkpoint: creates/deletes a source or test file, touches several files, alters a dependency / core config / data model / routes, or needs new E2E tests. These are review-hygiene triggers, independent of context size.
98
98
  - **Run inline** for small, self-contained work: a few files, no new files, no dependency/schema/route change, light discussion (typos, tweaks, single-line fixes, test additions).
99
- - **Context size is a soft, secondary signal** — split only when the working context is genuinely large *relative to the window* or you notice degraded recall, never at a fixed token count. Prefer the planning skill's **session-continuity heuristic**: keep going in-session when the accumulated context IS the execution payload (targeted-deep reads of the exact files you'll edit); split when it was broad fan-out noise.
100
-
99
+ - **Context size is a soft, secondary signal** — split only when the working context is genuinely large *relative to the window* or you notice degraded recall, never at a fixed token count.
101
100
  ---
102
101
 
103
102
  ## 4. User Interaction
@@ -109,4 +108,4 @@ Split a complex task across sessions for **focus and review hygiene**, not becau
109
108
 
110
109
  ## 5. Planning Workflow
111
110
 
112
- All plan-file rules — vocabulary (Plan Phase Step → Substep), lifecycle (`docs/plans/<slug>.md`, gitignored, never committed), the mandatory final **Phase: Cleanup**, the `docs/plans/queue.md` series-index, "all work in plans", the plan-then-execute split — live in the project's planning skill. It is the single source of truth and overrides the generic `writing-plans` skill.
111
+ All plan-file rules — shape (goal and boundary, module ledger, verification), lifecycle (`docs/plans/<slug>.md`, gitignored, never committed), the mandatory final **Phase: Cleanup**, the `docs/plans/queue.md` series-index, "all work in plans" — live in the project's planning skill. It is the single source of truth and overrides the generic `writing-plans` skill.
@@ -168,6 +168,13 @@ const CATALOG = [
168
168
  kind: GUARDED,
169
169
  oneLine: 'Hide the sandbox’s injected device masks from git status via one managed block in the repo’s local exclude file — probe-derived (never a frozen list), preview first, full-block replace on your yes; reviews already ignore the masks by construction.',
170
170
  },
171
+ {
172
+ key: 'mcp',
173
+ invocation: invocationOf('mcp'),
174
+ group: 'Configure',
175
+ kind: GUARDED,
176
+ oneLine: 'Register the kit’s read-only MCP server in this project, so path questions and literal searches run as typed tool calls instead of shell command strings — previews the exact entry it would declare, writes only on your yes, and where a sandbox hides the file it hands you the text to paste instead.',
177
+ },
171
178
  {
172
179
  key: 'recipes',
173
180
  invocation: invocationOf('recipes'),
@@ -53,6 +53,9 @@ export const LIBRARY_ONLY_MODULES = Object.freeze({
53
53
  // command that APPLIES that decision is the ensure CLI, and the one that edits the config by intent
54
54
  // is set-recipe.
55
55
  'orchestration-config.mjs': '/agent-workflow-kit set-recipe',
56
+ // Named by references/modes/mcp.md as the read half the advisor and uninstall ask. It only ever
57
+ // REPORTS; the command that acts on what it reports is the mode itself.
58
+ 'mcp-registration.mjs': '/agent-workflow-kit mcp',
56
59
  });
57
60
 
58
61
  // The frozen refusal line. One line, names the module, names the command.
@@ -69,6 +69,9 @@ import { COVERAGE_PRODUCER_BODY } from './coverage-producer.mjs';
69
69
  // Imported from the VOCABULARY leaf, never from the ops: a read-only lint must not pull the ensure
70
70
  // implementation — and through it the orchestration writer — into its import graph.
71
71
  import { RELAYED_ENSURE_TOKENS, RELAYED_FAILURE_CAUSES } from './ensure-vocabulary.mjs';
72
+ // The MCP registration's four public strings. Imported from the READ-ONLY leaf, never from the
73
+ // writer: a read-only lint must not pull the atomic-write core into its import graph.
74
+ import { ENABLED_KEY as MCP_ENABLED_KEY, MCP_JSON_REL, SERVER_NAME as MCP_SERVER_NAME, allowRulesFor } from './mcp-registration.mjs';
72
75
 
73
76
  const AUTONOMY_DOCTOR_DOC = 'references/modes/autonomy-doctor.md';
74
77
  const RECOMMENDATIONS_DOC = 'references/modes/recommendations.md';
@@ -81,6 +84,8 @@ const PROCEDURES_DOC = 'references/modes/procedures.md';
81
84
  const SET_FLOW_DOC = 'references/modes/set-flow.md';
82
85
  const RECEIPT_DEADLINE_DOC = 'references/modes/receipt-deadline.md';
83
86
  const GATES_DOC = 'references/modes/gates.md';
87
+ const MCP_DOC = 'references/modes/mcp.md';
88
+ const UNINSTALL_DOC = 'references/modes/uninstall.md';
84
89
  // One literal for the dispatch mode doc: the structure leaf already names it as the file it anchors
85
90
  // its table in, and a second copy here is exactly the drift this lint exists to catch.
86
91
  const DISPATCH_DOC = ADVISOR_MATRIX_DOC;
@@ -222,6 +227,15 @@ export const BINDINGS = Object.freeze([
222
227
  // sees the doc. Bound here, a future move fails a declared gate instead of leaving the contract
223
228
  // doc quietly describing a command the kit no longer emits.
224
229
  valueBinding('coverage-producer-body', COVERAGE_PRODUCER_BODY, COVERAGE_PRODUCER_BODY, [GATES_DOC]),
230
+ // The MCP registration's four public strings (mode: mcp). TWO docs quote each of them: the mode
231
+ // contract, which tells a reader what will be declared, and the uninstall KEEP list, which tells
232
+ // them what to edit to undo it — so a renamed key or tool would otherwise leave one of the two
233
+ // teaching a registration nobody writes. The server name is bound QUOTED: the bare word rides
234
+ // nearly every kit doc, and a token that is trivially present pins nothing.
235
+ valueBinding('mcp-json-rel', MCP_JSON_REL, `\`${MCP_JSON_REL}\``, [MCP_DOC, UNINSTALL_DOC]),
236
+ valueBinding('mcp-enabled-key', MCP_ENABLED_KEY, `\`${MCP_ENABLED_KEY}\``, [MCP_DOC, UNINSTALL_DOC]),
237
+ valueBinding('mcp-server-name', MCP_SERVER_NAME, `\`"${MCP_SERVER_NAME}"\``, [MCP_DOC, UNINSTALL_DOC]),
238
+ ...allowRulesFor().map((rule) => valueBinding(`mcp-allow-rule:${rule}`, rule, `\`${rule}\``, [MCP_DOC, UNINSTALL_DOC])),
225
239
  ].map((b) => Object.freeze(b)));
226
240
 
227
241
  // ── the pure checker (readText is injectable for hermetic tests) ────────────────────────
@@ -290,8 +304,10 @@ lagging-kit sentence, procedures.md), the receipt-deadline arrival contract, the
290
304
  FORM-only + aggregate-refusal contract and the routing advisor's two honesty sentences — the
291
305
  advice never gates, and the harness-subagent lane carries no availability verdict (dispatch.md), the
292
306
  runner's closed coverage= summary
293
- vocabulary (gates.md), and the canonical coverage-producer-body command the same doc prints in full
294
- (gates.md) — to
307
+ vocabulary (gates.md), the canonical coverage-producer-body command the same doc prints in full
308
+ (gates.md), and the MCP registration's five public strings mcp-json-rel, mcp-enabled-key,
309
+ mcp-server-name and the two mcp-allow-rule entries, each quoted by BOTH the mcp mode contract and the
310
+ uninstall KEEP list that undoes it (mcp.md, uninstall.md) — to
295
311
  the exact token its references/modes/*.md contract must carry, and
296
312
  asserts the CURRENT value renders into every bound file. A drifted doc, an unreadable bound file,
297
313
  or an absent token FAILS CLOSED.
@@ -6,11 +6,9 @@
6
6
  //
7
7
  // --constraints slice the root AGENTS.md `## 🚫 Hard Constraints` section, verbatim
8
8
  // (exactly-one-match — 0 or >1 headings is a loud STOP, never a guess);
9
- // --plan <path> extract the plan's decision-bearing canonical sections, verbatim + whole:
10
- // `## Approach` (REQUIRED its "What we are NOT doing" text rides inside;
11
- // it is not a heading in canon) and `## Verification` (REQUIRED STOP if
12
- // missing), plus `## Decisions (locked)` (optional-if-absent, the engine §7
13
- // heading this release adds); a DUPLICATE heading is always a STOP.
9
+ // --plan <path> extract the plan's three canon sections, verbatim + whole — `## Goal and
10
+ // boundary`, `## Module ledger`, `## Verification` (the engine planning.md
11
+ // literal headings; each REQUIRED a missing or DUPLICATE heading is a STOP).
14
12
  // --extra <text|@file> append orchestrator-supplied facts verbatim AFTER the mechanical halves
15
13
  // (repeatable; @file reads are confined to the work tree + the system temp
16
14
  // surface — the merge happens INSIDE the tool, corpus #88/#95).
@@ -46,18 +44,14 @@ export const DEFAULT_MAX_PROMPT_BYTES = 120000;
46
44
  export const ARGV_HARD_MAX = 131000;
47
45
 
48
46
  export const CONSTRAINTS_HEADING = /^## .*Hard Constraints$/;
49
- export const PLAN_SECTIONS = [
50
- { heading: '## Approach', optional: false },
51
- { heading: '## Verification', optional: false },
52
- { heading: '## Decisions (locked)', optional: true },
53
- ];
47
+ export const PLAN_SECTIONS = ['## Goal and boundary', '## Module ledger', '## Verification'];
54
48
 
55
49
  // ── pure section slicing (exactly-one-match; the inject-methodology discipline) ────────
56
50
 
57
51
  // Slice ONE `## `-level section (its heading line through the line before the next `## ` heading),
58
52
  // verbatim. `heading` is a string (trimmed-line equality) or a RegExp over the trimmed line.
59
- // 0 matches → null when optional, else STOP; >1 matches → always STOP (never guess which).
60
- export const sliceSection = (text, heading, { optional = false, label = 'document' } = {}) => {
53
+ // 0 matches → STOP; >1 matches → STOP (never guess which).
54
+ export const sliceSection = (text, heading, { label = 'document' } = {}) => {
61
55
  const lines = text.split('\n');
62
56
  const matchesAt = [];
63
57
  for (let i = 0; i < lines.length; i += 1) {
@@ -66,7 +60,6 @@ export const sliceSection = (text, heading, { optional = false, label = 'documen
66
60
  }
67
61
  const shown = typeof heading === 'string' ? heading : String(heading);
68
62
  if (matchesAt.length === 0) {
69
- if (optional) return null;
70
63
  throw fail(1, `${label}: required section "${shown}" not found — STOP (nothing sliced)`);
71
64
  }
72
65
  if (matchesAt.length > 1) {
@@ -95,10 +88,7 @@ export const assembleGrounding = ({ constraintsText = null, autonomyText = null,
95
88
  // how autonomous this session is → what this plan decides).
96
89
  if (autonomyText != null) parts.push(autonomyText);
97
90
  if (planText != null) {
98
- for (const { heading, optional } of PLAN_SECTIONS) {
99
- const section = sliceSection(planText, heading, { optional, label: planLabel });
100
- if (section != null) parts.push(section);
101
- }
91
+ for (const heading of PLAN_SECTIONS) parts.push(sliceSection(planText, heading, { label: planLabel }));
102
92
  }
103
93
  // Orchestrator extras ride LAST, verbatim in argv order — live judgment facts read after the
104
94
  // mechanical slices, and the merge happens INSIDE the tool (corpus #88/#95: a shell append onto
@@ -268,9 +258,9 @@ Usage:
268
258
  stated source line; absent file → the computed defaults ARE the policy
269
259
  (exit 0); malformed/unreadable → fail-closed STOP (exit 1); informational —
270
260
  enforcement stays the sandbox + the orchestrator
271
- --plan <path> extract the plan's decision-bearing sections verbatim + whole:
272
- "## Approach" + "## Verification" (REQUIRED — STOP if missing),
273
- "## Decisions (locked)" when present; a duplicate heading is a STOP
261
+ --plan <path> extract the plan's three canon sections verbatim + whole: "## Goal and
262
+ boundary", "## Module ledger", "## Verification" (each REQUIRED — a
263
+ missing or duplicate heading is a STOP)
274
264
  --extra <text|@file> append orchestrator-supplied extra facts byte-verbatim AFTER the
275
265
  mechanical sections (repeatable, argv order; the agy-review --facts
276
266
  convention: literal text, or @path read whole through a race-free
@@ -84,6 +84,8 @@ export const AUTONOMY_EMPTY_SLOT = `${AUTONOMY_START_MARKER}\n${AUTONOMY_END_MAR
84
84
  export const KNOWN_PRIOR_METHODOLOGY_SLOT = [
85
85
  // v1.3.0 — pre-communication-contract methodology pointer (procedures route, no §1.9 clause).
86
86
  '> **Workflow methodology** — plan → execute → review. Plans are ephemeral `docs/plans/*.md` (gitignored, **never committed**); every Plan ends with a mandatory **Phase: Cleanup**; series order lives in `docs/plans/queue.md`. Full vocabulary, lifecycle, and the plan-then-execute split live in the project\'s **planning skill** (it overrides the generic `writing-plans`); summary in `docs/ai/agent_rules.md` §5. Named activities (plan-authoring, plan-execution) have procedures — see `/agent-workflow-kit procedures <activity>` for the steps + resolved recipe.',
87
+ // engine 2.1.0 — the pre-canon-rewrite pointer (vocabulary + plan-then-execute wording, with the communication contract).
88
+ '> **Workflow methodology** — plan → execute → review. Plans are ephemeral `docs/plans/*.md` (gitignored, **never committed**); every Plan ends with a mandatory **Phase: Cleanup**; series order lives in `docs/plans/queue.md`. Full vocabulary, lifecycle, and the plan-then-execute split live in the project\'s **planning skill** (it overrides the generic `writing-plans`); summary in `docs/ai/agent_rules.md` §5. Named activities (plan-authoring, plan-execution) have procedures — see `/agent-workflow-kit procedures <activity>` for the steps + resolved recipe. **Communication:** user-facing messages deliver the artifact inline (paste the prompt / diff / command — never "see §X" as a substitute), lead with the result, show exactly what was asked, and never read as mockery (a large artifact: a real summary inline + a link).',
87
89
  ];
88
90
  export const KNOWN_PRIOR_ORCH_SLOT = [
89
91
  // v1.3.0 — pre-read-at-start orchestration pointer (recipes vocabulary, no orchestration.json clause).