@sabaiway/agent-workflow-kit 14.33.0 → 14.35.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,83 @@ 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
+ ## 14.35.0 — every advisor item that only said "do it by hand" now names the kit tool the agent runs on your yes, or plainly says it is your own step; a shipped table lists each item's writer (AD-187)
8
+
9
+ **The `HAND-APPLY` marker is gone from the advisor, its mode docs and the README: each item it marked either has a
10
+ writer the agent runs on the chat yes or is a user item whose apply line is your exact step.** Before, those advisor
11
+ lines and the masked `mcp` report carried `HAND-APPLY`, which left the step to you even where a kit tool could make
12
+ the write.
13
+
14
+ - **Three `docs/ai/gates.json` arms and `review-recipe` are user items.** The fill only adds gates, so a coverage
15
+ checker with no producer it can place, a checker with no producer it can recognise, and a gate squatting the
16
+ `source-size` id are your own edits: the new variant `gates-inert.unfillable` and the moved
17
+ `gates-inert.producer-unrecognized` and `source-size.id-squatter` render `your own step — in docs/ai/gates.json …,
18
+ then run /agent-workflow-kit recommendations`, with the item's own facts on the `recipe:` line of the two
19
+ `gates-inert` arms (the squatter's message states its fact). The squatter's step
20
+ prints the `node "<tools>/source-size-check.mjs" --check` repoint only when that path is safe to print in a gate cmd,
21
+ else the rename alone. `review-recipe` names `/agent-workflow-kit setup` or `set-recipe` as your step;
22
+ `/agent-workflow-kit backends` is a report, not a writer. `init` lists each as `listed, not applied here` and applies
23
+ nothing. Lanes: nine user, fifteen chat, twenty-one harness.
24
+ - **The ADR store migration keeps its writer, under two yeses.** `adr-store-migration`'s apply is the bare
25
+ `migrate-adr-store.mjs --dry-run` line: on your first yes the agent runs it and shows the whole plan; the same line
26
+ with `--apply` in place of `--dry-run` runs only on a second yes given over that plan.
27
+ - **A declined offer is recorded by the agent.** `spec-adoption`, `jev-connect`, `jev-skill` and `jev-skill.earlier`
28
+ carry a `decline alternative` on their `recipe:` line: on your decline the agent runs the `ack-write.mjs` line and
29
+ its printed `--apply` instead of the apply, and the item stays away (a Jev skill decline lasts until the kit pins a
30
+ newer skill). Seed, connect or install keep their route.
31
+ - **The masked `mcp` report names the route.** Where a sandbox hides `.mcp.json`, `mcp` writes nothing, exits 0 and
32
+ names the advisor's `mcp-channel` item with its check and apply lines (the `mcp-channel.differing` arm's under
33
+ `--replace`); it no longer prints an entry or a settings body to paste.
34
+ `mcp --help`, the README `mcp` row, the catalog line and `mcp.md` say the same; `renderFragments` and the masked
35
+ result's `fragments` field are removed.
36
+ - **`references/advisor-writers.md`, a new shipped table.** One row per advisor variant (forty-five): its lane and its
37
+ writer — the harness form's file, the chat apply's tools or writer mode — or `user step`. A suite fails on a
38
+ missing, extra or duplicated row, a wrong lane or writer, and a chat apply naming a tool its row does not list.
39
+ - **Fix.** Over a schema-invalid `docs/ai/orchestration.json` the `executor-vehicle` probe reports one stated skip
40
+ and no longer also renders the `executor-vehicle.differing` item.
41
+ - **Stated limits.** The three `gates.json` edits have no writer by design (the fill only adds gates: it never edits,
42
+ removes, reorders or renames an existing entry). Under `init`, the `footprint-global` probe and its check can judge different global ignore files when the
43
+ console env sets `GIT_DIR` or `GIT_CONFIG_GLOBAL` (debt row FOOTPRINT-GLOBAL-ONE-GIT-ENV).
44
+
45
+ ## 14.34.0 — bootstrap and the upgrade stop making five protected writes mid-session: attribution, the legacy global ignore block, a hand-edited executor and the commit guard are applied on the chat yes (AD-186)
46
+
47
+ **Five writes that bootstrap, the upgrade or a migration note made by themselves mid-session are now advisor items the
48
+ agent applies on the chat yes, after the danger check, or that stay pending.** Before, under the sandbox each one met a
49
+ refused write with no route: bootstrap and note 1.2.0 set `includeCoAuthoredBy` in `.claude/settings.json`; bootstrap
50
+ and the hide removed the legacy machine-global ignore block with `--remove-global` after an ask; note 3.0.0 migrated
51
+ the commit guard; and the configs step rewrote a hand-edited `.claude/agents/executor.md`.
52
+
53
+ - **`attribution`, a new item and its writer.** When `AGENTS.md` reads `Agent attribution: off` and the effective
54
+ project and local settings do not hide the harness's Co-Authored-By trailer and PR text,
55
+ `node <kit>/tools/attribution.mjs --cwd <root>` previews and `--apply` sets `attribution` to
56
+ `{"commit":"","pr":"","sessionUrl":false}` in the project `.claude/settings.json`, in place, keeping every other key.
57
+ An existing `"includeCoAuthoredBy": false` already counts unless an `attribution` object carries a `commit` or `pr`
58
+ field. A wrong-typed key in the project file refuses (`ATTRIBUTION_MALFORMED`); one in the local file is a stated
59
+ skip, and a local-scope override is your own edit. With
60
+ `agent-workflow-memory` installed, bootstrap tells memory to skip its own settings write; this item sets it.
61
+ - **`footprint-global`, a new item.** A legacy kit block in the machine-global ignore file (`core.excludesFile`) is
62
+ removed only by this item: `hide-footprint.mjs --remove-global` is now its own mode, which removes each recognised
63
+ legacy line, keeps every other byte, prints the removed lines as a backup, and needs at least one kit-specific line
64
+ before it removes anything. The hide, `--reconcile` and `--unhide` never write that file; the hide and `--unhide`
65
+ report the block kept, as `--reconcile` does on a hidden deployment.
66
+ `--keep-global` is removed (it was the default). A refused write stops with `cannot write the global excludes …`.
67
+ - **`executor-vehicle.differing`, a new arm.** The configs step no longer rewrites a placed executor whose body differs
68
+ from the one `docs/ai/vehicles.json` derives: it reports `pending` (the tokens `would-re-derive` and `re-derived` are
69
+ gone), and the optional item re-derives it through the agents writer on your yes.
70
+ - **`commit-guard` verifies the arm.** After the project's installer exits 0, the apply reads the pre-commit hook back;
71
+ an installer older than the arm ignores `--commit-guard`, so the apply exits 1 with one line naming the hook (the
72
+ danger check's apply line reports 6) and the item returns at the next run. Refreshing that installer from the kit's
73
+ `references/scripts/` copy is your own step.
74
+ - **The danger check binds what a file write replaces.** Every `writes:` line over an existing regular file now ends with
75
+ `— replaces sha256:<the bytes there now>`, so a file edited after the check moves the digest and needs a new yes.
76
+ `init` runs each check under the same environment it launches the apply with.
77
+ - **Shipped text.** Bootstrap steps 4 and 9, the upgrade's configs and footprint paragraphs, notes 1.2.0 and 3.0.0,
78
+ `deploy-tail.md`, `composition-handoff.md`, `contracts.md` and `recommendations.md` name the items, and the
79
+ `vehicles.json` seed note says the upgrade reports a differing body as pending; none keeps a "No Node" branch for
80
+ these writes.
81
+ - **Stated limits.** `status` and `uninstall` still read only `includeCoAuthoredBy`; a pre-arm installer is refreshed
82
+ by you; a declined `footprint-global` returns at the next run.
83
+
7
84
  ## 14.33.0 — the worktrees grant gets a writer: the agent writes it into this copy's local settings on the chat yes, and the worktrees stops name that route (AD-185)
8
85
 
9
86
  **The `worktrees-dir` upgrade item, which used to hand the user a settings line to paste, is now a harness item the
package/README.md CHANGED
@@ -144,7 +144,7 @@ any Claude Code / Codex / Devin Desktop it finds. A fresh project is deployed in
144
144
  places** the optional bridges — **once placed** (by `/agent-workflow-kit setup`) it **refreshes** them
145
145
  from its bundled copies (never a downgrade; skip with `--no-bridges`).
146
146
 
147
- The agent applies the chat items on your "yes" and the harness items after the check; a user item, and any item marked HAND-APPLY, stays your own step; `npx @sabaiway/agent-workflow-kit@latest init` is an optional console route for the harness items **from the project's folder** —
147
+ The agent applies the chat items on your "yes" and the harness items after the check; a user item stays your own step; `npx @sabaiway/agent-workflow-kit@latest init` is an optional console route for the harness items **from the project's folder** —
148
148
  it covers the harness's own configuration (`.claude/settings.json`, `.claude/agents/`, `.claude/hooks/`, `.mcp.json`).
149
149
  A pending API-key line comes first: Enter skips; its `y` runs the key command on that terminal.
150
150
  Then `init` previews the pending items it can apply and applies the previewed batch on one terminal `y` (sudo asks separately).
@@ -165,7 +165,7 @@ different sub-commands:
165
165
 
166
166
  <sub>`/agent-workflow-kit` bootstraps a fresh deployment (and asks your **visibility**, **conversational language**, and whether the agent may **attribute work to itself / AI** — default off); `/agent-workflow-kit upgrade` migrates an existing one to the kit's current version.</sub>
167
167
 
168
- After `init`, run `/agent-workflow-kit upgrade` in an agent session: the agent applies the chat items on your "yes" and the harness items after the check; a user item, and any item marked HAND-APPLY, stays your own step; `init` is an optional console route for the harness items.
168
+ After `init`, run `/agent-workflow-kit upgrade` in an agent session: the agent applies the chat items on your "yes" and the harness items after the check; a user item stays your own step; `init` is an optional console route for the harness items.
169
169
 
170
170
  > **Optional standalone memory substrate.** The memory layer is also published standalone as
171
171
  > [`@sabaiway/agent-workflow-memory`](https://www.npmjs.com/package/@sabaiway/agent-workflow-memory).
@@ -251,12 +251,12 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
251
251
  | `/agent-workflow-kit set-recipe` | any time | **config writer** for `docs/ai/orchestration.json`: tell the agent your preference in plain language and it maps it to explicit `--set <activity>.<slot>=<value>` / `--unset` ops; the kit validates, **previews by default**, and writes only on `--write` (deployment-gated, atomic, symlink/TOCTOU-safe), resolving the effective recipe vs live readiness. Writes **only** that file — **never runs a backend or a subagent, never commits**; hand-editing stays fully supported. |
252
252
  | `/agent-workflow-kit review-state` | any time | **read-only review-receipt checker** — makes "reviewed ≠ shipped" detectable: the bridge review wrappers append a receipt per successful review (into a file inside the git dir — never committable); this checks that every backend your configured `plan-execution.review` recipe names holds a **fresh, grounded receipt for the current uncommitted tree** (any later edit the payload can see moves the fingerprint and stales the receipt; a review continuation never re-attests a folded tree). `--check` gives a gate exit code to declare in `docs/ai/gates.json` **by hand or via the consent-gated seeder** (preview → your explicit yes; never without consent). `--await [--timeout <s>]` (AD-049) BLOCKS until every recipe-named backend has receipted the current tree — the durable completion signal is the receipt, never a process event — so you wait for the bridges instead of hand-polling. Never writes, never commits, never runs a subscription CLI; it spawns read-only `git` queries to fingerprint the tree — and `git commit --no-verify` stays possible (discipline, not a sandbox). |
253
253
  | `/agent-workflow-kit sandbox-masks` | any time | **exclude lane for sandbox masks** — an OS sandbox (Claude Code) masks work-tree paths with a device or, on Linux, a foreign bind mount, as untracked `git status` noise; the review domain already ignores a **device mask** **by construction** (never-committable untracked classes — char/block devices, FIFOs, sockets — are excluded from the fingerprint, the assembled review payload, and the clean checks), while a **mount-only mask** rides the fingerprint and `git add -A` while git does not ignore it (load-bearing there). This mode hides them from `git status` too: flagless = read-only probe (derives the CURRENT mask set from the unfiltered walk + lstat + `/proc/self/mountinfo` — never a frozen list; off Linux the mount signal is not read and it says so, an unreadable or malformed table refuses — and revalidates fenced entries, loudly flagging one that became a real path); `--apply` = consent-gated FULL-BLOCK replace of its own fenced block in `git rev-parse --git-path info/exclude` (stale masks drop by construction; `--clear` always means REMOVE the block — it takes precedence over the derivation). Writes ONLY its fence — never `.gitignore`, never global config; symlinked/non-regular exclude paths and malformed fences fail closed. Watch note: a real file at an excluded path is silently skipped by bulk staging (`git add -A`/`git add .`) — delete the stale line first; the probe flags exactly this case. |
254
- | `/agent-workflow-kit mcp` | Claude Code · opt-in | **typed-channel registration** — the kit ships a read-only stdio **MCP server** exposing its two promptless readers as TYPED tools (`path_inventory`: exists / type / size / line count / listing / a small file's text, many paths in ONE call; `repo_search`: literal search, the pattern a JSON string). Their arguments are named JSON fields rather than 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 — legal bytes to search for, with no shell to read them as operators. Shipping the server does nothing on its own — a client sees it only once the project declares it, and this mode is that declaration: the `agent-workflow` entry in **`.mcp.json`** (command `node`, args = the absolute path of the RUNNING kit's server) plus `enabledMcpjsonServers` and the two derived tool allow rules in **`.claude/settings.json`**. Preview by default and the **exact entry is printed before consent** (registering a server means your client will run that command); `--apply` writes `.mcp.json` FIRST, then settings, merge-don't-clobber with each file's EOL kept, and a re-apply adds nothing twice. An existing `agent-workflow` entry that **structurally differs** from what this kit copy would write is refused unwritten unless `--replace` is given (the comparison ignores key order, so re-serialized identical bytes are the same registration) — silently changing what an MCP server launches is what consent must not slide past. `--replace` prints the entry standing in `.mcp.json` and the entry it would write, and replaces it only with `--apply`. An existing `.mcp.json` or `.claude/settings.json` is written in place: it keeps its inode, never removed or renamed over; an absent file is created. Where an OS sandbox masks `.mcp.json` with a device node, it writes nothing, hands you both paste-ready texts and exits 0. The server is a read-only child of your client (no write, no exec API) running outside the Bash sandbox as the client does. Never writes `settings.local.json`; never commits. |
254
+ | `/agent-workflow-kit mcp` | Claude Code · opt-in | **typed-channel registration** — the kit ships a read-only stdio **MCP server** exposing its two promptless readers as TYPED tools (`path_inventory`: exists / type / size / line count / listing / a small file's text, many paths in ONE call; `repo_search`: literal search, the pattern a JSON string). Their arguments are named JSON fields rather than 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 — legal bytes to search for, with no shell to read them as operators. Shipping the server does nothing on its own — a client sees it only once the project declares it, and this mode is that declaration: the `agent-workflow` entry in **`.mcp.json`** (command `node`, args = the absolute path of the RUNNING kit's server) plus `enabledMcpjsonServers` and the two derived tool allow rules in **`.claude/settings.json`**. Preview by default and the **exact entry is printed before consent** (registering a server means your client will run that command); `--apply` writes `.mcp.json` FIRST, then settings, merge-don't-clobber with each file's EOL kept, and a re-apply adds nothing twice. An existing `agent-workflow` entry that **structurally differs** from what this kit copy would write is refused unwritten unless `--replace` is given (the comparison ignores key order, so re-serialized identical bytes are the same registration) — silently changing what an MCP server launches is what consent must not slide past. `--replace` prints the entry standing in `.mcp.json` and the entry it would write, and replaces it only with `--apply`. An existing `.mcp.json` or `.claude/settings.json` is written in place: it keeps its inode, never removed or renamed over; an absent file is created. Where an OS sandbox masks `.mcp.json` with a device node, it writes nothing, exits 0 and names the advisor's `mcp-channel` item, which applies the registration by the consent route. The server is a read-only child of your client (no write, no exec API) running outside the Bash sandbox as the client does. Never writes `settings.local.json`; never commits. |
255
255
  | `/agent-workflow-kit grounding` | any time | **grounded-review facts assembler** — mechanizes populating `agy-review --facts @f`: slices your entry-point's **Hard Constraints** section verbatim (exactly one match, else a loud stop) and/or a plan's three canon sections (`## Goal and boundary` + `## Module ledger` + `## Verification`, each required; duplicates stop), under the same byte budget the agy wrapper enforces (minus `--reserve-bytes` for the artifact share), with a loud tail-trim on overflow. `--autonomy` (AD-044) appends the COMPUTED effective autonomy policy from the git-top `docs/ai/autonomy.json` (every red-line + per-activity level, stated source line; absent file → the computed defaults ARE the policy, exit 0; a malformed policy fails CLOSED, exit 1). `--extra <text|@file>` (repeatable) appends your own facts **byte-verbatim** after the mechanical sections, so the merge is a tool input rather than a shell append — an `@file` must sit inside the proven git work tree (never the git dir) or the system temp surface, and is read through a no-follow descriptor (a FIFO cannot block it, a symlink leaf cannot substitute its target). Prints to stdout; `--out` writes **one scratch file only** — system-temp outside the repo ($TMPDIR / /tmp, rewritable) or a **fresh** gitignored in-repo path (create-only, exclusive write; an existing in-repo file, even gitignored, is refused — the `.env` clobber class); tracked, not-ignored-in-repo, other outside-repo, and symlink/non-regular destinations are all refused. Never commits, never runs a subscription CLI. |
256
256
  | `/agent-workflow-kit core-evidence` | any time | **the ONE loop-evidence writer** (strip-the-kit) — every core evidence record lands in a single append-only JSONL store inside the git dir (never committable; versioned schema, latest-per-key supersession, byte-identical duplicates refused, malformed lines fail every reader closed). `red-proof "<file>#<pattern>"` declares an observed-red **BEFORE a bugfix** (N/N red runs + content custody + base + the pre-fix fingerprint; green/mixed/timeout are DISTINGUISHED refusals — nothing written); `degrade --backend --reason` is the ONLY escape for an unavailable review backend (per-tree, never all backends); `summary` renders the whole loop state statelessly (gate result · per-backend verdicts · red-proofs · degrades) — no ledger, no rounds, nothing remembered. Honest residual: records are forgeable — self-discipline, not a security boundary. Never commits, never runs a subscription CLI. |
257
257
  | `/agent-workflow-kit coverage-check` | any time | **the final-run checker** (D3(c)+(d)) — **certifies coverage ONLY inside the `--final` run that owns the lcov** (ownership is exclusive by CONVENTION over the fixed path, not enforced — a concurrent writer to it is a stated residual, queued as LCOV-EXCLUSIVE-OWNERSHIP): an artifact on disk proves nothing about the tree it came from, so a standalone run prints its findings and states `attested=no` / `NO VERDICT` rather than a PASS (an lcov that predates an edit would otherwise certify a line the suite never executed). `attested=` claims a verdict was ISSUED, pass or fail — a run over uncovered lines still reads `attested=yes` and still exits 1, and a run whose coverage arm never executed (no lcov) reads `attested=no` even inside `--final`, because nothing was read and nothing is certified. The runner passes a nonce whose one-way commitment over `{nonce, fingerprint, base}` is the `final-start.attempt` it recorded; a context describing another tree, or matching no recorded attempt, is a REFUSAL, never a verdict. Findings are unchanged — reads the lcov the declared `unit-tests` gate produced at the FIXED git-dir path and fails on any uncovered CHANGED executable Node line (listed `file:line`; a changed file absent from the map is a file-level red; out-of-domain/unsupported files are LISTED — the claim narrowed honestly); VERIFIES every current-base red-proof declaration (bound test exists · custody hash unchanged · green N/N now · pre-fix fingerprint differs); prints `lcov-sha256=<hex|none>` of the exact bytes it consumed — the sha the `--final` receipt binds and re-hashes. An absent lcov is a LOUD `skipped-no-lcov`; a symlinked path is a refusal. `--check` is the gate exit code — declare it as the LAST gate (`run-gates --final` refuses otherwise). Read-only. |
258
258
  | `/agent-workflow-kit commit-guard` | any time | **the read-only pre-commit guard** (D10) — makes the commit capture the whole current working tree, so «verified» and «about to be committed» are the same bytes (the receipt itself has a stated residual — see the mode doc). FIRST it refuses an **INDEX that lags the verified working tree** (the gates and the fingerprint describe the WORKING tree while `git commit` takes the INDEX alone, and the fingerprint cannot tell them apart — so a lagging index used to ship a strict subset of what was verified): unstaged tracked paths or reviewable untracked-not-ignored paths, named up to a bounded cap with the remainder stated, a dirty tracked **submodule** named separately with its own recovery, and fail-closed on an undecidable probe. This deliberately blocks a partial commit. Then it binds the LATEST completed `run-gates --final` receipt to the EXACT current tree: refuses on a missing/red/stale receipt, fingerprint drift under the run, a dangling later attempt, declaration content drift, evidence-hash or lcov drift, or unsatisfied review obligations (the same review-state decision, recomputed over a sanitized env — forged out-of-repo stores never satisfy). Re-runs NO gate or test. Wire it into `.git/hooks/pre-commit` (the installer writes the RESOLVED invocation). `git commit --no-verify` stays the stated residual. |
259
- | `/agent-workflow-kit recommendations` | any time (every `upgrade` ends with it) | **read-only deployment advisor** (AD-044) — computes what in THIS deployment is configured sub-optimally (allowlist not seeded, autonomy render drifted, OS sandbox unavailable, gates undeclared, bridge friction, sandbox-mask clutter, a feature-spec layer never adopted) and renders **verdict-first**: one composed verdict line (does anything need attention?), then each item as **{severity · what · one-line benefit · an optional `recipe:` line (the bridge-tier item's missing-entries line — each missing entry by kind: allow rules, exclusions, hosts, state dirs, then what the execution wrapper codex-exec needs, then the host condition; the worktrees-dir ack fallback for a host that ignores the settings key, the agents hidden-mode reconcile follow-up, the spec-adoption decline preview, or another offer's decline preview) · the exact consent-gated apply one-liner}**. The agent PRESENTS the section in the user's conversational language — every fact and count, nothing added or dropped; commands, paths, hosts and rule strings byte-exact; raw tool block on request — and runs EXACTLY the rendered one-liners only on your yes, surfacing each item's posture note first. Renders **present-even-when-empty** (`no recommendations — flow optimal.`); a failed probe degrades to a stated skip line. Registry strings are fact-true frozen one-line data (posture/risk notes live in the mode doc at the consent moment); `velocity --bridge-tier` seeds the used bridges' review allow rules, `<wrapper> *` exclusions (`codex-exec *` where an execute slot delegates, keeping its permission prompt), declared hosts into `sandbox.network.allowedDomains`, and home-relative state dirs into `sandbox.filesystem.allowWrite`; hosts and dirs are declared per bridge in `capability.json` `networkHosts` + `writableDirs`, and the advisor's `bridge-tier` item counts and names each missing one. `--cwd` is required (the target project is explicit); writes nothing, never commits, never runs a subscription CLI. |
259
+ | `/agent-workflow-kit recommendations` | any time (every `upgrade` ends with it) | **read-only deployment advisor** (AD-044) — computes what in THIS deployment is configured sub-optimally (allowlist not seeded, autonomy render drifted, OS sandbox unavailable, gates undeclared, bridge friction, sandbox-mask clutter, a feature-spec layer never adopted) and renders **verdict-first**: one composed verdict line (does anything need attention?), then each item as **{severity · what · one-line benefit · an optional `recipe:` line (the bridge-tier item's missing-entries line — each missing entry by kind: allow rules, exclusions, hosts, state dirs, then what the execution wrapper codex-exec needs, then the host condition; the worktrees-dir ack fallback for a host that ignores the settings key, the agents hidden-mode reconcile follow-up, a user item's own facts, the spec-adoption decline preview, or another offer's decline preview) · the exact consent-gated apply one-liner}**; `references/advisor-writers.md` names each variant's writer or user step. The agent PRESENTS the section in the user's conversational language — every fact and count, nothing added or dropped; commands, paths, hosts and rule strings byte-exact; raw tool block on request — and runs EXACTLY the rendered one-liners and the follow-up commands required by the mode doc, only on the answer authorizing that step — your yes to apply or your explicit decline to record it — with a second yes where the mode doc requires it, surfacing each item's posture note first. Renders **present-even-when-empty** (`no recommendations — flow optimal.`); a failed probe degrades to a stated skip line. Registry strings are fact-true frozen one-line data (posture/risk notes live in the mode doc at the consent moment); `velocity --bridge-tier` seeds the used bridges' review allow rules, `<wrapper> *` exclusions (`codex-exec *` where an execute slot delegates, keeping its permission prompt), declared hosts into `sandbox.network.allowedDomains`, and home-relative state dirs into `sandbox.filesystem.allowWrite`; hosts and dirs are declared per bridge in `capability.json` `networkHosts` + `writableDirs`, and the advisor's `bridge-tier` item counts and names each missing one. `--cwd` is required (the target project is explicit); writes nothing, never commits, never runs a subscription CLI. |
260
260
  | `/agent-workflow-kit doc-parity` | any time | **read-only doc-parity lint** (AD-049) — kills the doc-drift class where a mode-contract doc silently lags a code constant (a `--check` doc still reading `300` after the diff cap moved to `400`): a **closed, exported registry** binds each live constant (review caps, schema versions, the ledger's own class/scope vocabulary, and the autonomy-doctor EXIT/status/trusted-dir contract) to the exact token its `references/modes/*.md` contract must carry, and asserts the CURRENT value renders into every bound file — a drifted doc, an unreadable file, or an absent token **fails closed**. The values are sourced from the live imports (never re-typed), so the lint can't itself go stale; adding a binding is adding a checked entry (closed-world, edit-safe). `--check` is the gate exit code a kit maintainer runs: the lint checks the kit's own package. Never writes, never commits, never runs a subscription CLI. |
261
261
  | `/agent-workflow-kit dispatch` | any time | **the delegation engine** — makes "how much does delegating a sub-task actually buy?" a measured number instead of a feeling. `check <file>` validates a sub-task brief's contract block and exits 0/1 naming the first violated field — **form only**: whether the task is genuinely bounded, its design decided and its acceptance adequate stays your judgment, and a well-formed absurdity passes here by construction. `advise --step-class <c>` answers the question that comes BEFORE that one — which vehicle should carry this kind of sub-task, whether it is even present on this machine, and what the ledger has already recorded for threads of that class (finished, failed, closed without a fold, and still open — counted separately) — and it decides nothing: it refuses no dispatch, gates no verb, and a choice that diverges from the advice is noted rather than blocked. It reads the filesystem only: nothing is launched to find out what is installed, an absent ledger prints "no recorded history", an unreadable one prints the store's own words, and either way the advice still prints. The same block appears as a footer under a form-valid `check` — never under a refusal, so it can never hide one. `register` pre-registers an acceptance wave (step classes, pairing key, minimum observations per class, the mean and first-pass thresholds) so the bar can never be chosen after the results it judges — immutable per wave. `observe` records ONE hand-written observation (`solo-construction`, the baseline whose ratio is 1 by construction — except over a scope measuring zero bytes, which has no ratio at all and is recorded ineligible by name — or `self-reported`) — delegated numbers are never hand-written, they are derived. The writer verbs are where that derivation happens: `open` puts a delegated thread on the record with every mint-time field copied from the brief's own header, refusing a deadline the wrapper's cap plus its kill grace would not fit inside, and recording whether the tree it started from was clean against the thread's base — HEAD by default, or the tree `--checkpoint <oid>` names, so the second task of a story is measured over the first one's uncommitted work instead of opening dirty (a dirty one makes the result honestly unmeasurable rather than quietly counted), while `--task <brief>` also records which task brief the thread carries out and the files that brief lists, refusing, since the last commit, a task thread whose files overlap by path prefix those of an open task thread or of a folded thread of another brief (`files-overlap`), and a mix of the two kinds — an untasked open beside an open task thread or, for a `code` open of backend `codex`, after a task fold, and a task open beside an open untasked thread (`mixed-open`), refusing at open a Files path the snapshot scope can never hold (`task-files-scope`), and measuring a task thread over its files alone, so `return` and `fold` refuse a changed path that no open or folded task thread on the same base claims (`out-of-files`); `await` waits for that one dispatch to answer and writes nothing — only the finished receipt satisfies it, a run still holding the nonce means keep waiting, and a wait that runs out says so with its own exit code, names whether the deadline or your own timeout ended it, and authorizes nothing: a wait that ended without an answer is a question for you, never permission to dispatch again; `return` absorbs the receipt the wrapper minted — only a finished one, checked against the contract it actually ran, its deadline and its own report, and refused outright when the tree hides changes from git or moves while the return is being computed — and derives the bytes from git rather than from anyone's claim; `fold` is the acceptance, and it refuses if the tree moved since the return — precisely, it re-confirms **equality of the visible canonical payload**, which is a change detector rather than a cryptographic identity of the tree: under the default head base that payload's untracked section is unframed and carries no file mode, so a content or symlink target that imitates the marker opening the next entry can alias two trees, and making an untracked file executable moves nothing (both named as residuals in the mode doc, with the fix queued); under a checkpoint base it is one indexed diff that carries modes. A change set carrying a binary, a non-regular path, a submodule, or a symlink whose target is not valid UTF-8 is **refused** at `return` and `fold` — CONSERVATIVELY, rather than promise what the default head payload may not be able to follow — and the same refusals hold for a checkpoint thread. `degrade` closes a thread that never earned a fold, on the record and with its reason. `aggregate` prints one wave: the registered thresholds, every observation as context, and per step class the finished threads — a folded success with git-provable bytes contributes its ratio, one whose bytes are unprovable is excluded from the mean but still counts in the first-pass rate, and a failed thread, a degrade-closed one, or one folded after failing acceptance each count as a real zero. Below the registered minimum nothing is computed (insufficient); at or above it is computed and labeled PILOT evidence. It REFUSES rather than guessing: no pre-registration, an unfinished thread in scope, a recorded refusal-to-delegate that never opened a thread, or an ambiguous wave each stop the computation by name. `handoff-return --slug <s>` closes the worktree loop after `land --prepare`: it re-attests that main still holds exactly the prepared tree under the same HEAD (both recorded in the handoff at prepare time), prints the satellite handoff's user-owned content byte for byte with its boundaries, byte lengths and the main-owned destinations each part folds into, prints the handoff digest and both OIDs as its proof, states the after-the-fold order (a fold landed after the gates leaves them stale), and records ONE self-reported worktree-stream observation only when the whole prepared change set is measurable — a deletion, a rename, a symlink, a submodule or a mode-only change ends with a named NOT RECORDED instead, never a partial number. Appends only to its own ledger inside the git dir (never committable); it also writes `handoff-return`'s `write-tree` tree object and, for a checkpoint measurement, the leaf's OWN temporary index file and the blobs its forced adds store — never a ref, never the real index; never commits, never runs a subscription CLI. |
262
262
  | `/agent-workflow-kit worktrees` | any time | **parallel feature worktrees** — run several features in DIFFERENT agent sessions on one repo, zero interference on working-tree files (the ONE exception is the dependency cache, below): `provision <slug> --plan <file>` creates a sibling git worktree on branch `aw/<slug>` and populates it (registry-derived footprint copy-if-missing — a tracked file is never overwritten; EXACTLY ONE seeded feature plan; the `handoff-<slug>.md` record from minute zero; `node_modules` symlinked where the link stays ignored — a shared MUTABLE dependency cache: writes through it hit MAIN's node_modules; for isolation run the printed isolated-install command (`--install` only PRINTS it; on `--resume` run the printed unlink-first recovery first — printed only for a link whose RAW TARGET is MAIN's `node_modules`, since a foreign or unreadable link is reported as such with no removal advised); absolute root-pinned gate commands rebased on untracked copies only, and only while their bytes equal the MAIN source or its rebased form — user-modified copies stay untouched); `list` is read-only (slug, branch, base, dirty, handoff); `prompt <slug>` is read-only too and re-prints the satellite's cold-start prompt — the same text `provision` ends its report with: where the worktree is, its ONE seeded plan, the handoff as the one channel back, MAIN's orientation (shared series index, landing) and, under its own heading because it is probed on the satellite, that checkout's install posture — every value derived LIVE rather than replayed from the frozen provision record, a recorded value that no longer matches NAMED as a divergence (with the cause its source makes likely) instead of printed as the runnable one, every offered command marked with WHO runs it (`MAIN $ …` for the landing, `HERE $ …` for this checkout's own install, a posture with nothing to run staying prose), and a control character in any rendered value a typed STOP rather than a forged prompt line; `land <slug> --prepare` locks the common git dir, fail-closes on divergence or incomplete satellite state, transfers the complete accepted satellite diff onto a CLEAN main, runs sync plus the declared gates, and reports HEAD/TRANSFER/PREPARED OIDs — the commit ALWAYS stays a dialogue ask; `cleanup <slug>` takes the same lock and removes a LANDED worktree only after live landed-verification against main HEAD, while `--abandon` is the ONE destructive arm (destroys unlanded work; **no preview step** on any writer). The parent dir is the `docs/ai/worktrees.json` `parentDir` setting (default: the repo's sibling parent); an unwritable parent prints the `worktrees-dir` check line: the agent applies the grant on your chat yes after the check, one entry in this copy's `.claude/settings.local.json`, never the committed project settings (`init` is the route you may take instead, and the ack is the fallback for a host that ignores the key), else the printed terminal command runs it. Never commits, never pushes, never runs a subscription CLI. |
@@ -274,7 +274,7 @@ It **never auto-commits** and **never overwrites** an existing `AGENTS.md` witho
274
274
  > own files** in `~/.claude/skills/`; `/agent-workflow-kit upgrade` then migrates a **project's**
275
275
  > `docs/ai/` deployment to that kit version.
276
276
  > When a kit update moves the deployment stamp ahead of the project's, `init` lists no project item and says `run /agent-workflow-kit upgrade in your agent: its yes applies the items`.
277
- > The agent applies the chat items on your "yes" and the harness items after the check; a user item, and any item marked HAND-APPLY, stays your own step; `init` is an optional console route for the harness items from the project's folder.
277
+ > The agent applies the chat items on your "yes" and the harness items after the check; a user item stays your own step; `init` is an optional console route for the harness items from the project's folder.
278
278
 
279
279
  ---
280
280
 
@@ -293,7 +293,7 @@ The dependency-free **Node** enforcement scripts (`node --test`, no package mana
293
293
  ## 🧩 The composition root of the family
294
294
 
295
295
  The kit is the member you install — the family's **composition root**. `npx … init` installs the kit
296
- globally; the agent applies the chat items on your "yes" and the harness items after the check; a user item, and any item marked HAND-APPLY, stays your own step; `init` is an optional console route for the harness items from the project's folder; composition starts when you **deploy it in a repo** (`/agent-workflow-kit`):
296
+ globally; the agent applies the chat items on your "yes" and the harness items after the check; a user item stays your own step; `init` is an optional console route for the harness items from the project's folder; composition starts when you **deploy it in a repo** (`/agent-workflow-kit`):
297
297
 
298
298
  ```
299
299
  agent-workflow-kit — the composition root (installed via npx … init)
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". New to the epic, story and task tier? Run `/agent-workflow-kit tier`. A typed decision with a confidence from Jev? Run `/agent-workflow-kit jev`.
4
4
  disable-model-invocation: true
5
5
  metadata:
6
- version: '14.33.0'
6
+ version: '14.35.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-kit
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": "14.33.0",
6
+ "version": "14.35.0",
7
7
  "provides": [],
8
8
  "roles": {},
9
9
  "detect": {
@@ -29,10 +29,12 @@ keeps it permanently via PR refs — so the safe default is "no mention anywhere
29
29
  Relay the writer's lines; a named refusal on stderr stops the migration and is relayed as worded.
30
30
  For `answer-missing`, ask exactly the missing answers and retry `--apply` once; a second withheld run stops.
31
31
  5. **Whenever the Attribution block reads `off`**, whether just inserted or already present,
32
- keep this hand step outside the writer: for Claude Code, set the harness flag so the automatic
33
- trailer stops (a doc directive cannot stop a harness-added trailer):
34
- - Ensure `.claude/settings.json` exists in the project root; create it as `{}` if absent.
35
- - Set `"includeCoAuthoredBy": false` (merge into the existing JSON; do not clobber other keys).
32
+ the harness setting stays outside the writer: for Claude Code it is the advisor's `attribution`
33
+ item, which the agent applies on the user's yes after its check, so the automatic trailer stops
34
+ (a doc directive cannot stop a harness-added trailer). It sets `attribution` to
35
+ `{"commit":"","pr":"","sessionUrl":false}` in the project's `.claude/settings.json`, every other
36
+ key kept. An existing "includeCoAuthoredBy": false already converges the item unless an
37
+ "attribution" key sets a commit or PR text.
36
38
  - For other tools, disable their equivalent co-author / footer setting if present.
37
39
  6. The writer refuses with `cap` rather than push `AGENTS.md` past its 100-line budget;
38
40
  trim the entry point and re-run the upgrade. Do **not**
@@ -42,12 +44,12 @@ keeps it permanently via PR refs — so the safe default is "no mention anywhere
42
44
 
43
45
  - `AGENTS.md` has exactly one `## ✍️ Attribution` block, with `off` or `on` (no leftover
44
46
  `{{AGENT_ATTRIBUTION}}` placeholder).
45
- - If `off` + Claude Code: `.claude/settings.json` contains `"includeCoAuthoredBy": false`, and
46
- the file is valid JSON (`node -e "JSON.parse(require('fs').readFileSync('.claude/settings.json'))"`).
47
+ - If `off` + Claude Code: the advisor renders no `attribution` item, and `.claude/settings.json`
48
+ is valid JSON (`node -e "JSON.parse(require('fs').readFileSync('.claude/settings.json'))"`).
47
49
  - The docs cap-validator is still green (`node scripts/check-docs-size.mjs` for Node projects).
48
50
 
49
51
  ## Rollback
50
52
 
51
53
  Delete the inserted `## ✍️ Attribution` block (and its trailing `---`) from `AGENTS.md`, and
52
- remove the `includeCoAuthoredBy` key from `.claude/settings.json` if you added it. No other
53
- files were changed.
54
+ remove the `attribution` key from `.claude/settings.json` if the `attribution` item wrote it. No
55
+ other files were changed.
@@ -23,19 +23,15 @@ loudly, never silently.
23
23
  3. **Review receipts:** receipts minted by pre-3.0.0/4.0.0 bridge wrappers carry no `posture`
24
24
  field and STOP satisfying the review-state gate (fail-closed, the probe-marker precedent).
25
25
  Refresh the placed bridges (init/upgrade does this), then re-run the required reviews.
26
- 4. **Commit guard (optional, consented):** a 2.x deployment's project-local
27
- `scripts/install-git-hooks.mjs` PREDATES the arm and the upgrade preserves existing scripts —
28
- refresh it FIRST, classified deterministically:
29
- `cmp -s scripts/install-git-hooks.mjs <installed-kit>/references/scripts/install-git-hooks.mjs`
30
- — identical ⇒ already current; DIFFERING ⇒ the copy is UNKNOWN (older deploy or customized —
31
- a consumer cannot enumerate shipped priors) ⇒ STOP and merge/replace by hand, never
32
- overwrite unasked (an explicit "my copy is an unmodified older deploy" answer is the one
33
- auto-replace lane). Then, once the declaration is final-run-capable, arm it:
34
- `node scripts/install-git-hooks.mjs --commit-guard "<installed-kit>/tools/commit-guard.mjs"`
35
- (the Recommendations advisor surfaces the exact one-liner) — and VERIFY the arm took:
36
- `grep -Fx 'node "<installed-kit>/tools/commit-guard.mjs" --check' "$(git rev-parse --git-path hooks)/pre-commit"`
37
- (the FIXED-STRING resolved line, exactly as the installer writes it) — absent ⇒ the arm did
38
- not take; re-run the installer refresh above before re-arming.
26
+ 4. **Commit guard (optional, consented):** once the declaration is final-run-capable, the
27
+ advisor's `commit-guard` item arms the pre-commit hook: the agent applies it on the user's yes
28
+ after its check, by the consent route of the Recommendations mode, and the apply reads the hook
29
+ back to verify the arm took. A 2.x deployment's project-local `scripts/install-git-hooks.mjs`
30
+ predates the arm, and the upgrade preserves existing scripts: an unarmed hook after the
31
+ installer's exit 0 makes the apply exit 6 with one line naming the hook, and the item returns
32
+ at the next run. A cmp -s against the kit's references/scripts/ copy tells whether the
33
+ project's copy is the current one; a differing copy may be an older deploy or the user's own
34
+ edit, and refreshing it is the user's own step.
39
35
  5. **Re-stamp:** init/upgrade re-stamps `docs/ai/.workflow-version` to 3.0.0.
40
36
 
41
37
  ## Verification
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sabaiway/agent-workflow-kit",
3
- "version": "14.33.0",
3
+ "version": "14.35.0",
4
4
  "description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -0,0 +1,51 @@
1
+ # Advisor writers
2
+
3
+ `tools/write-lanes.mjs` is the one source of the advisor's variant set and lanes; each row below names a variant's lane and its writer — a harness form's file, a chat apply's writers or writer mode — or `user step`.
4
+
5
+ | variant | lane | writer |
6
+ | --- | --- | --- |
7
+ | velocity-core | harness | velocity-profile.mjs |
8
+ | kit-tools-tier | harness | velocity-profile.mjs |
9
+ | bridge-tier | harness | velocity-profile.mjs |
10
+ | autonomy-policy | chat | set-autonomy |
11
+ | autonomy-render | harness | velocity-profile.mjs |
12
+ | sandbox-provision | user | user step |
13
+ | sandbox-provision.installable | user | user step |
14
+ | review-recipe | user | user step |
15
+ | gates-declaration | chat | gates-init.mjs |
16
+ | gates-inert | chat | gates-init.mjs |
17
+ | gates-inert.unfillable | user | user step |
18
+ | gates-inert.no-verification | chat | gates-init.mjs |
19
+ | gates-inert.producer-unrecognized | user | user step |
20
+ | gates-inert.coverage-domain-narrow | chat | ack-write.mjs |
21
+ | source-size | chat | source-size-check.mjs |
22
+ | source-size.unminted | chat | source-size-check.mjs |
23
+ | source-size.adopted-elsewhere | chat | ack-write.mjs |
24
+ | source-size.id-squatter | user | user step |
25
+ | gate-hook | harness | gate-hook.mjs |
26
+ | gate-hook.marker-stale | harness | gate-hook.mjs |
27
+ | commit-guard | harness | git-hooks.mjs |
28
+ | enforcement | harness | git-hooks.mjs |
29
+ | read-lane | chat | gate-hook.mjs |
30
+ | read-lane.stale | harness | gate-hook.mjs |
31
+ | read-lane.missing | harness | gate-hook.mjs |
32
+ | state-block | harness | state-block-hook.mjs |
33
+ | mcp-channel | harness | mcp.mjs |
34
+ | mcp-channel.differing | harness | mcp.mjs |
35
+ | agents | harness | cheap-agents.mjs |
36
+ | executor-vehicle | harness | cheap-agents.mjs |
37
+ | executor-vehicle.differing | harness | cheap-agents.mjs |
38
+ | attribution | harness | attribution.mjs |
39
+ | footprint-global | harness | hide-footprint.mjs |
40
+ | family-freshness | user | user step |
41
+ | adr-store-migration | chat | migrate-adr-store.mjs |
42
+ | sandbox-masks | chat | sandbox-masks.mjs |
43
+ | sandbox-masks.unfenced-mount | chat | sandbox-masks.mjs |
44
+ | worktrees-dir | harness | velocity-profile.mjs |
45
+ | worktrees-dir.home | user | user step |
46
+ | spec-adoption | chat | ensure-configs.mjs |
47
+ | profile-gap | chat | rules-insert.mjs, tier-preview.mjs, checker-gates.mjs |
48
+ | jev-connect | user | user step |
49
+ | jev-skill | harness | jev-skill.mjs |
50
+ | jev-skill.earlier | harness | jev-skill.mjs |
51
+ | spec-adoption.adopting | chat | ack-write.mjs |
@@ -18,7 +18,7 @@ The user chooses at bootstrap whether the AI artifacts are visible in the repo o
18
18
  diverge:
19
19
 
20
20
  - **visible** — artifacts are committed. When a `package.json` exists and the enforcement scripts are in place, wire the project's `package.json` scripts (`docs:check` / `docs:index` / `docs:index:check` / `docs:archive` / `docs:archive:check` / `docs:archive:issues` / `docs:archive:issues:check` / `prepare: node scripts/install-git-hooks.mjs`); add a minimal `.gitignore` (`docs/plans/`, `.claude/settings.local.json`) either way. This is the canonical model.
21
- - **hidden** (in-tree) — same files on disk, but the repo "looks normal": the AI/agent footprint is git-ignored via **one managed block in the project-local `.git/info/exclude`** (resolved with `git rev-parse --git-path info/exclude` — never the machine-global `core.excludesFile`, which would affect every repo on the host; **AD-014** amends **AD-006**). The kit's `tools/hide-footprint.mjs` is the single writer: it covers `KIT_OWN_PATHS ∪ the present subset of KNOWN_FOOTPRINT` (the full footprint table below — the kit's own artifacts **and** every other AI/agent tool's files). Per path: a **tracked** file → **ASK** (an exclude does nothing for it; only `git rm --cached` un-tracks it — the tool prints that command and never runs it); an untracked path already covered by a **tracked `.gitignore`** → dropped (redundant); a **present** file whose name is generic enough to be ambiguous (`falsePositiveRisk`) → **ASK**; everything else → **hidden**. `asks` are excluded from the block unless explicitly opted in. **Verify** treats a path as hidden only when it is **untracked AND ignored by our project-local block** (or a tracked `.gitignore`) — being ignored by the global excludes does **not** count. Re-running re-derives the block wholesale (sorted/deduped) → a clean re-run is a **zero-diff** no-op. On an existing global-excludes deployment the tool **detects + reports the residual legacy machine-global block and keeps it by default** (a harmless double-ignore; the local block wins precedence); removal is the explicit `--remove-global` (it prints the removed lines as a restorable backup), which **the agent only runs after asking** — another of the user's hidden repos on the same host may rely on the same root-anchored global lines. **Do not edit `package.json`** — that is a tracked change and would leak; the pre-commit hook (always untracked in `.git/hooks/`) calls the scripts via `node scripts/<x>.mjs` directly. Windows is supported (text edit, no symlinks; CRLF preserved).
21
+ - **hidden** (in-tree) — same files on disk, but the repo "looks normal": the AI/agent footprint is git-ignored via **one managed block in the project-local `.git/info/exclude`** (resolved with `git rev-parse --git-path info/exclude` — never the machine-global `core.excludesFile`, which would affect every repo on the host; **AD-014** amends **AD-006**). The kit's `tools/hide-footprint.mjs` is the single writer: it covers `KIT_OWN_PATHS ∪ the present subset of KNOWN_FOOTPRINT` (the full footprint table below — the kit's own artifacts **and** every other AI/agent tool's files). Per path: a **tracked** file → **ASK** (an exclude does nothing for it; only `git rm --cached` un-tracks it — the tool prints that command and never runs it); an untracked path already covered by a **tracked `.gitignore`** → dropped (redundant); a **present** file whose name is generic enough to be ambiguous (`falsePositiveRisk`) → **ASK**; everything else → **hidden**. `asks` are excluded from the block unless explicitly opted in. **Verify** treats a path as hidden only when it is **untracked AND ignored by our project-local block** (or a tracked `.gitignore`) — being ignored by the global excludes does **not** count. Re-running re-derives the block wholesale (sorted/deduped) → a clean re-run is a **zero-diff** no-op. On an existing global-excludes deployment the tool **detects + reports the residual legacy machine-global block and keeps it** (a harmless double-ignore; the local block wins precedence); removal is the advisor's `footprint-global` item, which the agent applies on the user's yes after its check — its `--remove-global` mode prints the removed lines as a restorable backup — since another of the user's hidden repos on the same host may rely on the same root-anchored global lines. **Do not edit `package.json`** — that is a tracked change and would leak; the pre-commit hook (always untracked in `.git/hooks/`) calls the scripts via `node scripts/<x>.mjs` directly. Windows is supported (text edit, no symlinks; CRLF preserved).
22
22
 
23
23
  **Visibility changes what git sees — never how the agent works (the load-bearing invariant).** Both modes carry the **same** working memory and run the **same** session protocol: `docs/ai` and the deployed workflow artifacts (`AGENTS.md`, the added `scripts/`) are read at session start and maintained on task completion **identically** whether visible or hidden. (Hidden mode git-ignores a *wider* footprint — other AI/agent tools' files too — but that is only a **visibility** surface; the working memory the agent actually reads and maintains is `docs/ai` plus those artifacts.) Hidden mode only makes the repo *look* normal to git; it is **never** a license to skip, defer, or down-prioritise a doc/state update. The single difference is the destination: in hidden mode those updates live on disk and never enter a commit (the footprint is excluded), so a commit there carries only the project's own tracked files while `docs/ai` is still kept current locally — **git-ignored ≠ optional-to-maintain**. An agent that reads "git-ignored / `git status` clean" and infers the working memory is optional has the model backwards. (This is why bootstrap/upgrade never wire hidden-mode docs into a commit, yet the session protocol still mandates maintaining them.)
24
24
 
@@ -78,7 +78,7 @@ When attribution is **`off`**, no mention of the agent, AI, or the model appears
78
78
 
79
79
  - **No `Co-Authored-By` trailers** and **no "Generated with …" footers** on commits or PRs.
80
80
  - **No AI/agent/model references** in code, comments, commit messages, PR titles/bodies, branch names, or `docs/` prose. The work reads as the human author's.
81
- - **Two enforcement layers** — the *Attribution* block binds everything an agent writes **by hand**; the automatic `Co-Authored-By` trailer is added by the **harness**, not the prose, so for **Claude Code** the kit also sets `"includeCoAuthoredBy": false` in the project's `.claude/settings.json`. Other tools: disable their equivalent co-author/footer setting if present.
81
+ - **Two enforcement layers** — the *Attribution* block binds everything an agent writes **by hand**; the automatic `Co-Authored-By` trailer is added by the **harness**, not the prose, so for **Claude Code** the advisor's `attribution` item sets the harness setting in the project's `.claude/settings.json`, applied on the user's yes after its check. Other tools: disable their equivalent co-author/footer setting if present.
82
82
 
83
83
  When **`on`**, the agent may add its standard trailer / footer per the user's tooling defaults. This
84
84
  block is about *attribution*, not authorship of the actual changes — quality, tests, and the "ask
@@ -1,6 +1,8 @@
1
1
  ### Mode: bootstrap
2
2
 
3
3
  <!-- opt-in-capability: enforcement -->
4
+ <!-- opt-in-capability: attribution -->
5
+ <!-- opt-in-capability: footprint-global -->
4
6
 
5
7
  Requires: ${CLAUDE_SKILL_DIR}/references/shared/report-footer.md · ${CLAUDE_SKILL_DIR}/references/shared/composition-handoff.md · ${CLAUDE_SKILL_DIR}/references/shared/deploy-tail.md · ${CLAUDE_SKILL_DIR}/references/shared/command-shapes.md
6
8
 
@@ -19,7 +21,7 @@ Requires: ${CLAUDE_SKILL_DIR}/references/shared/report-footer.md · ${CLAUDE_SKI
19
21
  - **First-contact orientation — say this before the first question (read-only).** In 1–2 plain lines, tell the user what this is — a portable memory & workflow system for this repo, so future sessions boot from a small structured memory instead of re-reading everything — and that they can run **`/agent-workflow-kit help`** any time to see every command. This is the "start here"; keep it to a sentence or two, then ask the three setup questions (the step-2 batched prompt).
20
22
  2. **Choose visibility — ask the batched prompt NOW (all three questions, per the preamble above) and wait until every answer is in.** This decides what gets tracked and is hard to reverse after a commit, so never assume the default silently: `visible` (committed — canonical, recommended) or `hidden` (in-tree, git-ignored via the **project-local** `.git/info/exclude` — one managed block covering the full AI/agent footprint, never the machine-global excludes). See [Visibility contract](${CLAUDE_SKILL_DIR}/references/contracts.md#visibility-contract).
21
23
  3. **Choose conversational language — answered in the step-2 batch.** 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](${CLAUDE_SKILL_DIR}/references/contracts.md#communication-contract). This sets the **dialogue** language only — never the files.
22
- 4. **Choose agent attribution — answered in the step-2 batch.** 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](${CLAUDE_SKILL_DIR}/references/contracts.md#attribution-contract).
24
+ 4. **Choose agent attribution — answered in the step-2 batch.** 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`**, the advisor's `attribution` item of step 11 sets the harness setting in the project's `.claude/settings.json`, applied on the user's yes after its check — the trailer is added by the harness, so a doc directive alone won't stop it. See [Attribution contract](${CLAUDE_SKILL_DIR}/references/contracts.md#attribution-contract).
23
25
  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).
24
26
  6. **Deploy `docs/ai/`.** Create every `docs/ai/` file + `pages/` + the seed `adr/` store + the seed `specs/` store from `${CLAUDE_SKILL_DIR}/references/templates/` (the template loop deploys every template except `AGENTS.md`, `adr-record.md` and `SPEC_TEMPLATE.md` — the latter two are skill-home authoring references, never deployed). This deploys the HOT ADR window **`docs/ai/decisions.md`** and the seed navigator **`docs/ai/adr/log.md`** (its `adr/` subdir copies in wholesale), the spec store root **`docs/ai/specs/index.md`** (its `specs/` subdir copies in wholesale), the `.md` docs, **and** the four seeded, user-editable strict-JSON configs: **`docs/ai/orchestration.json`** (the per-project recipe defaults the `procedures` advisor reads), **`docs/ai/gates.json`** (the project's gate declaration — seeded as the no-consent EMPTY list; FILLING it is the consented `gates-init` preview, `${CLAUDE_SKILL_DIR}/references/modes/gates.md`), **`docs/ai/autonomy.json`** (the per-project autonomy policy — seeded SPARSE with the onboarding note only, so it is exactly the computed defaults until the user declares levels via `/agent-workflow-kit set-autonomy` or by hand), and **`docs/ai/vehicles.json`** (the executor vehicle's model, effort and fallback as a setting the kit's readers resolve into one posture; the placed executor's body follows it — seeded once, hand-edited, never clobbered). Keep each `.md` file's frontmatter (`type / lastUpdated / scope / staleAfter / owner / maxLines`); the `.json` seeds carry no frontmatter (the docs cap-validator globs `*.md` only, so they are inherently skipped).
25
27
  7. **Fill templates** per the table below. **Then materialize the navigator, as the last `docs/ai` step:** the entry point declares `docs/ai/index.md` always-loaded and it is a GENERATED artifact with no template, so run `node ${CLAUDE_SKILL_DIR}/references/scripts/check-docs-size.mjs --ensure-index --root=<project>` and relay its one outcome line (`ensure-index: regenerated` / `already-current`, or a `write-refused` / `probe-failed` line naming the offending path — either is a STOP: resolve it before reporting the deploy, since the navigator the entry point declares does not exist yet). The generator runs from **this skill's own directory**, so a project with **no Node runtime** is covered too: the agent host runs it, and step 8's copy is not a precondition.
@@ -29,8 +31,8 @@ Requires: ${CLAUDE_SKILL_DIR}/references/shared/report-footer.md · ${CLAUDE_SKI
29
31
  - **hidden** — run the kit's hide writer (one managed block in the **project-local** `.git/info/exclude`, covering the kit's own artifacts **and** every known AI/agent tool's footprint — never the machine-global excludes): `node ${CLAUDE_SKILL_DIR}/tools/hide-footprint.mjs --dir <project> --dry-run` **first** (changes nothing, prints the plan), then the same without `--dry-run`. Handle what it surfaces, in plain language (never the tool's internal terms):
30
32
  - A path it reports as **already committed** can't be hidden by ignoring it and is **never un-tracked silently** — show the user the printed `git rm --cached <path>`, let them decide, then opt it in with `--include=<path>`.
31
33
  - A **present file with a generic name** it flags (e.g. `GEMINI.md`) → ask before `--include=<path>`.
32
- - If it reports a **leftover machine-wide ignore block** from an older deployment, **ask before removing it** — it could affect another of the user's repos that relies on the same machine-wide rules; on a yes, re-run with `--remove-global` (prints a restorable backup). Otherwise it is kept (harmless — the project-local rules win).
33
- - Report the result plainly (what is now hidden). **No Node on the agent host** → write the one managed block into `.git/info/exclude` by hand from the contract's path list, and report the manual step. **Windows is supported.**
34
+ - A **leftover machine-wide ignore block** from an older deployment is kept and reported (harmless — the project-local rules win); the advisor's `footprint-global` item of step 11 removes it on the user's yes after its check, which prints each line it removes — another of the user's repos may rely on the same machine-wide rules.
35
+ - Report the result plainly (what is now hidden). **Windows is supported.**
34
36
  - **Do not edit `package.json`** in hidden mode — a tracked change leaks the whole system.
35
37
  10. **Stamp the deployment lineage.** **First re-run the step-7 navigator finalizer**
36
38
  (`--ensure-index`) whenever a later step touched `docs/ai` — the reconciles that follow the
@@ -27,7 +27,7 @@ Declared gates can also be **auto-approved** (no permission prompt on a byte-exa
27
27
 
28
28
  **Consent-gated filling — the init preview, not part of the runner (D9).** The template `gates.json` is seeded EMPTY; FILLING it is a consented preview at init (`node ${CLAUDE_SKILL_DIR}/tools/gates-init.mjs --cwd <project>`, dry-run by default — prints the derived entries and **writes NOTHING**; `--apply [--only <id>]…` writes exactly the consented entries on your explicit yes; add-only, id collisions refused). **Placement, not blind appending:** a consented entry lands at the END, except that a non-checker entry goes **BEFORE** a trailing canonical coverage checker — that checker must stay last, so appending after it would write a declaration the validator refuses, leaving a hand edit as the only lane on any final-capable declaration. Existing entries are never modified, removed or reordered. The offer derivation is **closed-world** (AD-052): only a terminating-class script NAME (test / lint / type-check / build — never dev/watch/serve, never a write-mode or release/publish/deploy variant) whose BODY is a member of the literal runner allowlist is offered — membership, never blocklist screening: the worst case is a legit command not offered, never a dangerous one offered. The offered cmd is the uniform hook-free **`COREPACK_ENABLE_NETWORK=0 <pm> exec -- <allowlisted-body>`** — `exec` runs a command, not a named script, so no pre/post hook can fire (npm/pnpm/yarn alike; never `<pm> run <name>`, which re-exposes hooks), and the Corepack env prefix blocks a hostile `packageManager` pin from fetching the PM binary before exec. npm is pinned `--offline --script-shell /bin/sh`; pnpm/yarn refuse an absent runner without network (a user-installed cache/global/PATH runner executing is user machine state — part of the disclosed residual); a family without a verified fail-closed exec contract is WITHHELD loudly. A screened-out body is always named — and when nothing but kit checkers remains, the preview says so in plain words: the offer carries **no project-verification gate at all**. **Disclose before the yes** (the preview prints it): gates.json is a PRIVILEGED file — the wired hook auto-approves byte-exact declared commands — and a script gate runs project-controlled tooling the preview does not sandbox (safe-by-construction = the OFFER DERIVATION). At upgrade, step 3's run-list changes an existing gates.json only through the consented legacy migration and, under the `tier` ask, `tools/checker-gates.mjs` (`${CLAUDE_SKILL_DIR}/references/modes/upgrade.md`).
29
29
 
30
- **The coverage PRODUCER and the canonical checker are declared together or not at all.** `coverage-check` READS an lcov; something must WRITE it, and a checker with no producer PASSES (`skipped-no-lcov`) certifying nothing. A **producer** is a CLOSED set of full command forms, never a substring probe: the suite body `node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination="${AW_GIT_DIR:?exported by run-gates}/agent-workflow-lcov.info" --test-reporter=spec --test-reporter-destination=stdout` (optionally + the project's own test paths), bare or behind ONE verified per-PM `exec` wrapper. A cmd that merely MENTIONS the destination (in an `echo`, as a bare substring) or carries a PARTIAL flag set is **not** one. The tail must be path-shaped — path/glob/quoting bytes only, nothing that could sequence, redirect or substitute a command, no plain leading `-`. **Residual:** the screen judges SOURCE bytes — brace *sequence* expansion can produce others (`{Y..a}`), though bash never re-scans an expansion result as syntax, so such a byte is literal argument data, not an operator; the leading-`-` rule is first-order only (`'--flag'`, `{path,--flag}` pass); neither proves the lcov SURVIVES — "producer" means *configured with the reporters*, and a run producing none is caught at runtime as `skipped-no-lcov`. The destination rides `AW_GIT_DIR`, exported to every gate child on plain and `--final` runs alike (`AW_LCOV_FILE` is `--final`-only), so one cmd survives the unmet-producer preflight in both modes; the `:?` is the required-parameter form, so a hand-run with `AW_GIT_DIR` unset or empty fails naming it instead of writing the lcov to the filesystem ROOT (a STALE exported value is not caught), and the preflight counts it as a reference (a `${VAR:-…}` fallback stays unmatched). Recognition is APPEND-ONLY — every destination form the kit has emitted stays a producer, so an earlier kit's declaration never re-reads as customized; emission uses the newest alone. Recognition never WIDENS: a suite the closed set cannot express declares itself with **`"lcovProducer": true`** — the ONE other way to be a producer. Only the literal `true` claims (a string is a loud refusal); the claim binds the DECLARATION, never the run (a marked gate producing no lcov still ends `skipped-no-lcov` / `attested=no`); and it is FORWARD-ONLY — an older kit rejects a marker-carrying declaration by name (exit 5) instead of reading a claim it cannot honor. On BOTH declaration paths: the fill preview WIRES those reporters onto a `node --test` body — the one allowlist member producing lcov unaided, every other body is emitted unchanged — WITHHOLDS the `coverage-check` candidate, with a named note, when neither the offer nor the declaration carries a producer; and `--apply` REFUSES a checker with no producer, a checker that is not LAST (an ORDERING refusal about entries that are ALREADY declared — reorder by hand; the fill places new entries before a trailing checker but never reorders what it did not write), or a SECOND canonical checker. The legacy migration likewise never ADDS the checker into a producer-less declaration, and reports an already-declared one as INERT. A producer declared AFTER the checker leaves it just as inert (it reads nothing, or stale bytes) — ORDER is the rule. An already-declared inert pair, and a matrix of nothing but kit checkers, are surfaced by the advisor's `gates-inert` item (`${CLAUDE_SKILL_DIR}/references/modes/recommendations.md`); its cause-A remedy is the fill preview when the checker is LAST and the project's own scripts yield an offerable producer (the placement rule lands it before the checker), and HAND-APPLY otherwise.
30
+ **The coverage PRODUCER and the canonical checker are declared together or not at all.** `coverage-check` READS an lcov; something must WRITE it, and a checker with no producer PASSES (`skipped-no-lcov`) certifying nothing. A **producer** is a CLOSED set of full command forms, never a substring probe: the suite body `node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination="${AW_GIT_DIR:?exported by run-gates}/agent-workflow-lcov.info" --test-reporter=spec --test-reporter-destination=stdout` (optionally + the project's own test paths), bare or behind ONE verified per-PM `exec` wrapper. A cmd that merely MENTIONS the destination (in an `echo`, as a bare substring) or carries a PARTIAL flag set is **not** one. The tail must be path-shaped — path/glob/quoting bytes only, nothing that could sequence, redirect or substitute a command, no plain leading `-`. **Residual:** the screen judges SOURCE bytes — brace *sequence* expansion can produce others (`{Y..a}`), though bash never re-scans an expansion result as syntax, so such a byte is literal argument data, not an operator; the leading-`-` rule is first-order only (`'--flag'`, `{path,--flag}` pass); neither proves the lcov SURVIVES — "producer" means *configured with the reporters*, and a run producing none is caught at runtime as `skipped-no-lcov`. The destination rides `AW_GIT_DIR`, exported to every gate child on plain and `--final` runs alike (`AW_LCOV_FILE` is `--final`-only), so one cmd survives the unmet-producer preflight in both modes; the `:?` is the required-parameter form, so a hand-run with `AW_GIT_DIR` unset or empty fails naming it instead of writing the lcov to the filesystem ROOT (a STALE exported value is not caught), and the preflight counts it as a reference (a `${VAR:-…}` fallback stays unmatched). Recognition is APPEND-ONLY — every destination form the kit has emitted stays a producer, so an earlier kit's declaration never re-reads as customized; emission uses the newest alone. Recognition never WIDENS: a suite the closed set cannot express declares itself with **`"lcovProducer": true`** — the ONE other way to be a producer. Only the literal `true` claims (a string is a loud refusal); the claim binds the DECLARATION, never the run (a marked gate producing no lcov still ends `skipped-no-lcov` / `attested=no`); and it is FORWARD-ONLY — an older kit rejects a marker-carrying declaration by name (exit 5) instead of reading a claim it cannot honor. On BOTH declaration paths: the fill preview WIRES those reporters onto a `node --test` body — the one allowlist member producing lcov unaided, every other body is emitted unchanged — WITHHOLDS the `coverage-check` candidate, with a named note, when neither the offer nor the declaration carries a producer; and `--apply` REFUSES a checker with no producer, a checker that is not LAST (an ORDERING refusal about entries that are ALREADY declared — reorder by hand; the fill places new entries before a trailing checker but never reorders what it did not write), or a SECOND canonical checker. The legacy migration likewise never ADDS the checker into a producer-less declaration, and reports an already-declared one as INERT. A producer declared AFTER the checker leaves it just as inert (it reads nothing, or stale bytes) — ORDER is the rule. An already-declared inert pair, and a matrix of nothing but kit checkers, are surfaced by the advisor's `gates-inert` item (`${CLAUDE_SKILL_DIR}/references/modes/recommendations.md`); its cause-A remedy is the fill preview when the checker is LAST and the project's own scripts yield an offerable producer (the placement rule lands it before the checker), and the user's own step otherwise.
31
31
 
32
32
  **The marker as it appears in a declaration** — the three required keys plus the boolean, nothing else:
33
33
 
@@ -27,9 +27,9 @@ An existing `.mcp.json` and `.claude/settings.json` are written in place: each k
27
27
  With `--replace`, the preview prints the entry standing in `.mcp.json` now and the entry this registration declares, writes nothing and exits 0.
28
28
  With `--replace --apply`, it replaces that entry, keeping every other server and key of `.mcp.json`, and prints the old entry as the one it replaced and ours as the one now in the file.
29
29
  This is the harness item `mcp-channel.differing`: the agent applies it on the chat yes after the check; `init` is the optional console route that previews it in the user's console and applies it on the terminal "y".
30
- - **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.
30
+ - **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 **writes nothing and exits 0**, and its report names the advisor's `mcp-channel` item, which applies the registration by the consent route: the report prints the item's check and apply lines (the `mcp-channel.differing` arm's under `--replace`); the check runs once more outside the sandbox, where the real `.mcp.json` is readable, and the apply runs, with the digest that check printed, on the yes given on that result. Every other never-committable class (block device, FIFO, socket) takes the same arm. The arm 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.
31
31
 
32
- **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 writer never writes through or over a link: following it would write a file outside the project's view, replacing it would silently destroy a deliberate indirection). 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 without `--replace`.
32
+ **Refusals (STOP, exit 1, zero writes) — decided BEFORE the masked arm, so a mask can never swallow one:** a symlinked project root · a symlinked or **directory** `.mcp.json` / `settings.json` (the writer never writes through or over a link: following it would write a file outside the project's view, replacing it would silently destroy a deliberate indirection). Note what is **not** in this list: a device node, FIFO or socket at `.mcp.json` takes the successful MASKED arm above, which names the `mcp-channel` item and its consent route, not a refusal — only at `settings.json` is that class a refusal, since no such route 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 without `--replace`.
33
33
 
34
34
  **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.)
35
35
 
@@ -41,4 +41,4 @@ The **residual is stated rather than implied**: every guard here is a path check
41
41
 
42
42
  **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.
43
43
 
44
- **Exit codes:** `0` done / dry-run / the hand-apply masked state; `1` a precondition STOP; `2` bad arguments.
44
+ **Exit codes:** `0` done / dry-run / the masked state, which the `mcp-channel` item applies by the consent route; `1` a precondition STOP; `2` bad arguments.