@sabaiway/agent-workflow-kit 14.34.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 +38 -0
- package/README.md +6 -6
- package/SKILL.md +1 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/advisor-writers.md +51 -0
- package/references/modes/gates.md +1 -1
- package/references/modes/mcp.md +3 -3
- package/references/modes/recommendations.md +9 -9
- package/references/modes/upgrade.md +1 -1
- package/tools/commands.mjs +1 -1
- package/tools/mcp-registration.mjs +1 -14
- package/tools/mcp.mjs +22 -23
- package/tools/recommendations.mjs +42 -39
- package/tools/write-lanes.mjs +35 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,44 @@ 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
|
+
|
|
7
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)
|
|
8
46
|
|
|
9
47
|
**Five writes that bootstrap, the upgrade or a migration note made by themselves mid-session are now advisor items 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
|
|
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
|
|
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,
|
|
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}
|
|
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
|
|
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
|
|
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.
|
|
6
|
+
version: '14.35.0'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-kit
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-kit",
|
|
3
|
-
"version": "14.
|
|
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 |
|
|
@@ -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
|
|
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
|
|
package/references/modes/mcp.md
CHANGED
|
@@ -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 **
|
|
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
|
|
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
|
|
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.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Requires: ${CLAUDE_SKILL_DIR}/references/shared/report-footer.md
|
|
6
6
|
|
|
7
|
-
The **read-only deployment advisor** — the deterministic section every `upgrade` run ends with, also invocable on its own. It computes what in THIS deployment is configured sub-optimally (allowlist not seeded, autonomy render drifted, sandbox unavailable, gates undeclared, a declared gate matrix that verifies nothing, bridge friction, sandbox-mask clutter, a configured `subagent` carrier whose executor vehicle cannot carry it, a difference from the reference profile the project has not declined, Jev not connected on this host, the Jev skill not installed for every agent) and renders **verdict-first**: one composed verdict line, then each item as **{severity · what is sub-optimal · the benefit in ONE plain line · `lane: chat`, `lane: harness` or `lane: user` · an optional `recipe:` line (the `bridge-tier` missing-entries line
|
|
7
|
+
The **read-only deployment advisor** — the deterministic section every `upgrade` run ends with, also invocable on its own. It computes what in THIS deployment is configured sub-optimally (allowlist not seeded, autonomy render drifted, sandbox unavailable, gates undeclared, a declared gate matrix that verifies nothing, bridge friction, sandbox-mask clutter, a configured `subagent` carrier whose executor vehicle cannot carry it, a difference from the reference profile the project has not declined, Jev not connected on this host, the Jev skill not installed for every agent) and renders **verdict-first**: one composed verdict line, then each item as **{severity · what is sub-optimal · the benefit in ONE plain line · `lane: chat`, `lane: harness` or `lane: user` · an optional `recipe:` line (an item's own facts or its alternative, such as the `bridge-tier` missing-entries line or the `worktrees-dir` ack fallback) · a harness item's `check:` line before its apply slot · the apply slot: a chat one-liner, a harness item's apply line or a user item's own step}**. The tool computes deterministic English DATA; **you PRESENT the section in the user's conversational language** — every fact, count and item from the tool, nothing added or dropped; commands, paths, hosts and rule strings stay **byte-exact**; show the raw tool block on request (the AD-032 report-contract lane — the tool cannot know the dialogue language, so the language rendering is your presentation layer).
|
|
8
8
|
|
|
9
9
|
**Live host/session facts are tool-composed only.** Every fact this section states about the current
|
|
10
10
|
host or session — prompts fired, sandbox scope, whether a bypass was needed, network reachability,
|
|
@@ -17,7 +17,7 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/recommendations.mjs --cwd <project-root> [--
|
|
|
17
17
|
|
|
18
18
|
1. **`--cwd` is REQUIRED** — the target project is explicit, never inferred from the shell's current directory (a subdirectory invocation still advises on the named root).
|
|
19
19
|
2. **Verdict first (present-even-when-empty, opening at the `## Recommendations (agent-workflow)` header).** With everything optimal the body is exactly `no recommendations — flow optimal.` — the empty-state line ALONE is the verdict, zero added lines. Every other state opens the body with ONE verdict line composed from the frozen templates: `{K} item(s) need attention` leads when any item's severity is `attention` (a CONFIGURED declaration that is broken, drifted, degrading or invalid — the deployment needs review); `{N} optional recommendation(s), apply any you want` covers the `optional` class (offers to enable an unconfigured capability), led by `nothing is broken` ONLY when no item needs attention AND no probe check was skipped (a skipped probe could hide an attention-class problem — the claim never overreaches); `optimality NOT attested — {M} probe check(s) skipped` is appended last whenever probe checks were skipped. (The `(s)` invariant form IS the pinned pluralization — no singular/plural branching.) Items render attention-first, each tagged `needs attention:` / `optional:`. A failed probe renders as a stated `⚠ skipped item …` line — never a crash, never a fabricated item.
|
|
20
|
-
3. **The apply-through-agent lane — an explicit informed-consent checkpoint:** the agent applies chat and harness items on the chat yes, harness items by the consent route below. A chat item is never retried outside the sandbox and every failure of a chat step is a STOP. A user item
|
|
20
|
+
3. **The apply-through-agent lane — an explicit informed-consent checkpoint:** the agent applies chat and harness items on the chat yes, harness items by the consent route below. A chat item is never retried outside the sandbox and every failure of a chat step is a STOP. A user item is the user's own step, handed over as worded with its posture note (e.g. `source-size.id-squatter`). After presenting, OFFER to apply; the user selects items in plain language; for each selected item you **surface its posture note inline** where one exists (the per-item notes below cover exactly the risk-marked keys). For a chat item: the user **explicitly confirms**, and **only then** — **no command runs before confirmation** — do you run the rendered command. Do NOT infer safety from the presence or absence of an `--apply` flag: most chat items' rendered command IS the mutation and completes on that one run, **including a no-`--apply` mutation** such as `spec-adoption`'s `ensure-configs --reconcile --only specs` (behind an edited file it prints a replace line that takes its own yes). Some items instead render a **dry-run preview** that changes nothing and prints an explicit follow-up `--apply`/mutating command to run NEXT (e.g. the `gates-declaration` seeder): after the SAME confirmation you run that printed follow-up command — no second ask, no improvisation (each writer keeps its own consent semantics: previews, `--apply` flags, refusals) — except profile-gap and adr-store-migration, whose apply line runs with --apply (appended, or in place of --dry-run) only on a second yes over the relayed preview. For `worktrees-dir` you apply the grant on the chat yes after its check, like every harness item, with no hand step to wait for; its `recipe:` line's `ack-write` preview is the ack fallback, run only on the user's word that the host ignores the settings key, instead of the grant, and against a trusted host NO no ack renders.
|
|
21
21
|
4. Registry strings (benefits + item texts) are frozen tool data, fact-true, ONE line under the pinned shape cap — posture/risk detail lives in the notes below at the consent moment, never inline in the overview. The dual velocity+security wording (`safer — blast radius bounded by the OS sandbox, not human attention`) rides ONLY the items with a real security delta (the autonomy render, the sandbox provisioning); the bridge-wrappers item claims **velocity only**.
|
|
22
22
|
|
|
23
23
|
The **consent route** for a harness item: run its `check:` line in the sandbox and show its result. On the check's `masked:` line, say so and run that same check line once more outside the sandbox, so it re-judges in the apply's own environment; the yes is taken on the outside result. Take the user's yes on that result and run its `apply:` line with that result's digest once, outside the sandbox at once, so the harness asks at the point of action (with the apply ask rule seeded, its own dialog); never another line, never unseen. When velocity-core, kit-tools-tier or bridge-tier is among the user's picks (velocity-core preferred), check, confirm and apply it, or leave it pending, before any other pick's check runs, so its apply seeds the ask rule before the other harness applies. When the harness's auto mode refuses the check or the apply, say so and give the one plain step: leave auto mode for this one action (Shift+Tab in the terminal, the mode indicator in an editor) and say yes, or run `npx @sabaiway/agent-workflow-kit@latest init` in a terminal of your own. On the user's word, run the same line once more when they left auto mode, never another, never unseen; when they ran `init` instead, run the advisor again and offer only what is still pending, each through this route from its check (after a restart `init` asked for, the next session's upgrade does it). When that refusal hits the velocity-core, kit-tools-tier or bridge-tier pick taken first, run no other pick's check until the user answers the plain step. A check that refuses (exit 1) leaves the item unapplied and pending, its result shown; a changed digest (exit 5) applies nothing, shows the new result and asks a new yes. A no, a harness refusal and any other failure of a harness apply leave the item pending with its output shown, and the agent continues. `init`, run in a console from the project's folder, is the route the user may take instead.
|
|
@@ -33,20 +33,20 @@ A variant's posture note, where its key has one, is printed by `init` from this
|
|
|
33
33
|
- `read-lane` — enabling the opt-in read-only compound lane auto-approves *compounds* (and singles) of the seeded read-only core that carry ZERO shell metaprogramming: an UNATTENDED trust extension, bounded by the audited read-only core (never a command outside it; prompt-bypass only, never a sandbox bypass) and applied regardless of which of those core commands you seeded as individual settings rules. It is a PROJECT-PERSISTENT declaration in `docs/ai/lanes.json` — every future session, subagents' Bash too where the host fires hooks on subagent Bash, and (committed) every checkout. The apply depends on state: when the lane is OFF, it is the `gate-hook --read-lane` preview (whose own currency check refuses a stale hook — a pre-1.48 hook never reads `lanes.json`); when the placed hook is MISSING, the item instead surfaces the re-place offer; when it is STALE (an enabled lane over an old hook), the **stale** arm is an attention item on the harness lane: its writer is `gate-hook --apply --reseed`, which replaces the placed hook with the bundled bytes in place, and the agent applies it on the chat yes after the check, whose result names the replaced file with both digests; `init` applies it on the terminal y as the route the user may take instead. Risk profile: a bounded read-only trust-posture extension — no write/exec exposure beyond the audited core.
|
|
34
34
|
- `worktrees-dir` — the agent applies the item on the chat yes after the check: one `sandbox.filesystem.allowWrite` entry naming the worktrees parent itself, written to this copy's `.claude/settings.local.json` only, never the committed project settings, the parent created when missing. `init` applies it on the batch terminal yes as the route the user may take instead. Where the host honors the key, the entry widens the OS-sandbox WRITE surface to the whole parent dir: every sibling path under it (other repositories included) becomes agent-writable, for every later session and every worktree provisioned afterwards. Narrow it BEFORE the first apply: point `docs/ai/worktrees.json` `parentDir` at a dedicated dir — no apply removes an entry, so an earlier one stays until the user deletes it. A parent that is the home or holds it renders `worktrees-dir.home` instead, the user's own step. **Convergence** is two-path: a declared `sandbox.filesystem.allowWrite` entry covering the probed dir (either settings scope — `~` and `~/…` resolve against home, and coverage is path-segment-aware, so a grant on a sibling or on a child never counts), or the dir-bound acknowledgement `worktreesDirAck` in the family-owned `docs/ai/acks.json`, recorded by the `ack-write --lane worktrees-dir` line the `recipe:` line carries — the ack fallback for a host that ignores the key, run only on the user's word, instead of the grant; against a trusted host NO no ack renders. Neither is proof of write CAPABILITY: the provision preflight's real create+delete probe stays the runtime truth, and the ack is bound to the **resolved probe dir**, so the item re-fires only when that resolved dir changes. Risk profile: a real write-surface widening where honored — scope it deliberately.
|
|
35
35
|
|
|
36
|
-
- `gates-inert` — the gate matrix is DECLARED but verifies nothing, and the two causes have different remedies, so the item renders a different apply for each. **Cause A** — a canonical `coverage-check` gate with no producer gate declared BEFORE it (none at all, or one declared after it, which writes the lcov too late): the run certifies no coverage of its own — `coverage=not-run` when nothing wrote an lcov, or a verdict over STALE bytes an earlier run left in the git dir, which is worse because it reads as `coverage=certified`. This item is what surfaces that state at upgrade instead of leaving it to be noticed. The remedy needs the producer to run BEFORE the checker — declared or MOVED there — and what the fill can do about that is exactly what the apply says. When the checker is the LAST declared gate and the project's own scripts yield an offerable producer, the fill PLACES that producer before it, so the apply is the ordinary `gates-init` dry-run preview. Otherwise — no offerable producer, or a checker that is not last —
|
|
36
|
+
- `gates-inert` — the gate matrix is DECLARED but verifies nothing, and the two causes have different remedies, so the item renders a different apply for each. **Cause A** — a canonical `coverage-check` gate with no producer gate declared BEFORE it (none at all, or one declared after it, which writes the lcov too late): the run certifies no coverage of its own — `coverage=not-run` when nothing wrote an lcov, or a verdict over STALE bytes an earlier run left in the git dir, which is worse because it reads as `coverage=certified`. This item is what surfaces that state at upgrade instead of leaving it to be noticed. The remedy needs the producer to run BEFORE the checker — declared or MOVED there — and what the fill can do about that is exactly what the apply says. When the checker is the LAST declared gate and the project's own scripts yield an offerable producer, the fill PLACES that producer before it, so the apply is the ordinary `gates-init` dry-run preview. Otherwise — no offerable producer, or a checker that is not last — it is the user item `gates-inert.unfillable`: the user's own step is to edit `docs/ai/gates.json` (the exact producer form lives in `${CLAUDE_SKILL_DIR}/references/modes/gates.md`), because the fill never reorders entries it did not write. **Cause B** — every declared gate is one of the kit's own canonical checkers, so the matrix runs no project-verification command at all: the apply is the `gates-init` dry-run preview, the same consent-gated seeder the `gates-declaration` item renders, and after the SAME confirmation you run the `--apply` line it prints. Neither cause changes any gate result, exit code or receipt — the item adds no enforcement, only the offer. Risk profile: `docs/ai/gates.json` is a PRIVILEGED file (a declared gate is auto-approvable by the wired hook), so both causes land in the same place — where the fill can help, it declares a project-controlled command on your explicit yes; where it cannot, the edit is the user's own step.
|
|
37
37
|
|
|
38
|
-
- `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
|
|
38
|
+
- `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 a user item, the user's own step (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.
|
|
39
39
|
- `gate-hook` — the base arm is the ordinary opt-in wiring offer on the harness lane: the agent applies it on the chat yes after the check; `init` previews and applies it on the terminal yes as the route the user may take instead. 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 it is a harness item: its writer is `gate-hook --apply --reseed`, which replaces the placed hook with the bundled bytes in place (and wires the entry only while it is unwired), so the agent applies it on the chat yes after the check, whose result names the replaced file with the old and the new digest; `init` applies it on the terminal y as the route the user may take instead. 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: one placed hook file replaced by the bundled runtime; the declaration is never touched.
|
|
40
40
|
- `enforcement` — copies the bundled `references/scripts/*.mjs` runnable scripts and their tests, then runs the project's own hook installer, which installs the pre-commit hook. The lane is harness: the agent applies the item on the chat yes after the check, whose result shows each script the kit places with its bytes and the installer it runs with its bytes; `init` applies it on the batch terminal yes as the route the user may take instead. The copy is create-only: an existing file stands and nothing is written through a link, and the offer renders only when `scripts/` is absent or a plain directory contains no bundled basename. The hook invokes `node` directly, so a committer without it on PATH gets a loud failure. The installer refuses to overwrite a pre-commit hook it did not write: an install it refuses removes the scripts this apply copied, and the item is offered again with the installer's output shown; over a pre-commit hook the installer did not write the item is not offered — its skip names the user's own step. Risk profile: local tooling and a Git hook are added on the chat yes after the check, or on init's y.
|
|
41
41
|
- `attribution` — the agent applies the item on the chat yes after the check, setting one key in the project `.claude/settings.json`; `init` applies it on the terminal y as the route the user may take instead. An existing "includeCoAuthoredBy": false already converges the item unless an "attribution" key sets a commit or PR text; a local-scope override is the user's own edit. Risk profile: one settings key, nothing widened.
|
|
42
42
|
- `footprint-global` — the agent applies the item on the chat yes after the check, which prints each line it removes; `init` applies it on the terminal y as the route the user may take instead. Another of the user's repositories may rely on the same lines: a no keeps the block, and the item returns at the next run. The apply prints the removed lines as a backup. Risk profile: lines removed from the machine-global excludes file.
|
|
43
43
|
- `mcp-channel` — the offer is to REGISTER the kit's read-only stdio MCP server in this project on the harness lane: a registration is a command your MCP client will RUN. The agent applies it on the chat yes after the check; `init` previews the exact entry and applies it on the terminal y as the route the user may take instead. 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. The **`.differing`** arm is also a harness item: 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). The check shows the entry standing in `.mcp.json` and the entry it would write; the chat yes confirms the replacement. `init` previews `mcp --replace` and replaces only on the terminal y. 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; a differing registration is replaced only on the checked chat yes or the terminal y and every other declaration is kept.
|
|
44
|
-
- 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
|
|
45
|
-
- `spec-adoption` — the feature-spec layer's ADOPTION STATE, judged from `docs/ai/specs/` alone through the same census `spec-check --all` runs, so a store the checker would refuse to observe is never counted here either. Both arms are OFFERS (`optional`) — the layer is opt-in, and `attention` stays reserved for a configured declaration that is broken. **`not-adopted`** (no store): every plan then cites zero governing specs by default and nothing says adoption never started; its apply is the spec-layer ensure one-liner (`ensure-configs --reconcile --only specs`, the same seed `upgrade` runs — it seeds the reader/checker pairs and the store root, and skips with a stated reason where no Node evidence exists), and its `recipe:` line carries
|
|
44
|
+
- 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 a user item, the user's own step, 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 edit that is the user's own step.
|
|
45
|
+
- `spec-adoption` — the feature-spec layer's ADOPTION STATE, judged from `docs/ai/specs/` alone through the same census `spec-check --all` runs, so a store the checker would refuse to observe is never counted here either. Both arms are OFFERS (`optional`) — the layer is opt-in, and `attention` stays reserved for a configured declaration that is broken. **`not-adopted`** (no store): every plan then cites zero governing specs by default and nothing says adoption never started; its apply is the spec-layer ensure one-liner (`ensure-configs --reconcile --only specs`, the same seed `upgrade` runs — it seeds the reader/checker pairs and the store root, and skips with a stated reason where no Node evidence exists), and its `recipe:` line carries the decline alternative — the DECLINE preview (`ack-write --lane spec-adoption`, a dry-run that prints the exact `--apply`), which records `specAdoptionAck` in the family-owned `docs/ai/acks.json`. **The two are exclusive, and the order is part of the contract:** ask which the user wants, apart, BEFORE the confirmation; on "seed" run the apply; on "decline" the agent records it — the preview line, then on that same answer its printed `--apply`; never run the seed after a decline, nor the decline after the seed. **`adopting`** (a store with no live contract): an optional item naming the draft count; its apply is that same decline preview (a dry-run; after the SAME confirmation you run the `--apply` it prints), and the honest remedy is authoring a contract from the skill's `SPEC_TEMPLATE.md`. A recorded decline silences BOTH arms and shows on `status` as ` — declined`; one live contract makes the decline moot. An unreadable store is a stated skip, never `flow optimal`. Risk profile: the seed writes create-only files under `scripts/` and `docs/ai/specs/` and refreshes a script body a release shipped; behind an edited file it prints a replace line that takes its own yes; the decline writes one key into the ack store; nothing here authors a spec or blocks a plan.
|
|
46
46
|
- `profile-gap` — each item's apply is a PREVIEW that writes nothing, and consent takes two steps: the confirmed pick runs the preview and you relay every line it prints (the environment-resolved slot values, each checker `entry:` line with its trust-chain disclosure, a Communication region the insert plans in the same run); the same line with ` --apply` appended runs only on a SECOND yes over those lines, never carried by the first. Items whose apply lines are byte-equal are one writer and take one answer: the tier preview's `--apply` writes the absent slots, the store seed and the queue seed together; the checker verb writes only its own entries; the insert writes every region it plans, all or nothing, so a `communication-absent` skip beside a declined `story-sessions-section` gets its own ask after the insert's full preview. A no records nothing and the item stays on the screen. A DECLINE is asked apart from later, before anything runs, and runs `node ${CLAUDE_SKILL_DIR}/tools/tier-preview.mjs --decline --cwd <project-root>` only when the user refuses EVERY item the tier preview then reports offered: it records each of them at the shipped lineage, and a declined item leaves the screen until a lineage step changes the profile. An undecidable entry is a stated skip naming its reason, the remedy by reason: a prior → the upgrade's `lens` reconcile; an absent region → the insert; a customized region → a hand edit only (the lens preserves it), so that skip stands and withholds `flow optimal`. Risk profile: the tier preview's `--apply` rewrites `docs/ai/orchestration.json` in canonical form with the `_README` note and creates `docs/ai/epics/.gitkeep` and `docs/plans/queue.md` create-only; the insert adds regions to `docs/ai/agent_rules.md`; the checker verb adds entries to the PRIVILEGED `docs/ai/gates.json`; the decline writes `docs/ai/profile-declines.json`; nothing here commits.
|
|
47
|
-
- `jev-connect` — the lane is user and env-dependent: the connect command is the user's own step in a terminal of their own, where it asks for the key; the key never reaches the agent or the chat (the command refuses without a terminal, and a terminal is not consent). When the recipe line opens with `run the apply in <place>`, that place is the terminal to run it in; `init`, run there, offers the command on its own consent line. The command reads the key with no echo, makes one network call to api.typesafe.ai with the vendor's sample text only on the user's own run, and writes the key in plain text — as managed export lines in the shell's startup files (bash: ~/.bashrc and the login file, created as ~/.bash_profile when none exists; zsh: ~/.zshrc), as the kit's own file ~/.config/fish/conf.d/typesafe-api-key.fish for fish, or on Windows as the user environment variable TYPESAFE_API_KEY, stored in the Windows user environment; a process already running at the save can keep its old environment and hand it to what it starts — the guide's key mark confirms the agent sees it (a file is readable by any process running as that user, and by other local accounts when one is world-readable in a traversable home); the vendor keeps data under its processing agreement and offers zero retention only to enterprise accounts. The item reads the environment the agent's commands run in: a host setting that filters it shows the item while the key is set in the user's shell. The decline
|
|
48
|
-
- `jev-skill` — the lane is harness; both `jev-skill` and `jev-skill.earlier` are env-dependent: the agent applies the item on the chat yes after the check, showing the files it would write; it changes no harness setting. `init` applies it on the batch terminal yes as the route the user may take instead. When the recipe line opens with `run the apply in <place>`, that place is where a user who prefers their own terminal runs it. The skill is third-party text — the vendor's, MIT — that the kit ships pinned and installs as a verified copy into three agent skill roots: `~/.agents/skills`, the Claude Code skills dir (`CLAUDE_CONFIG_DIR` or `~/.claude`) and `~/.gemini/config/skills`. It is never updated silently: a copy at an earlier pin is replaced only by that command, and a foreign copy is left untouched and named on the recipe line. A copy the vendor's `skills` CLI wrote with the same bytes reads current, and a later pin move replaces it like a kit copy; a vendor plugin install is invisible to the file check, so the Claude Code root may read absent while the plugin serves it. Only when the user asks to see the plan does the agent run the command without `--apply` (a read-only dry run). The decline
|
|
49
|
-
- `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
|
|
47
|
+
- `jev-connect` — the lane is user and env-dependent: the connect command is the user's own step in a terminal of their own, where it asks for the key; the key never reaches the agent or the chat (the command refuses without a terminal, and a terminal is not consent). When the recipe line opens with `run the apply in <place>`, that place is the terminal to run it in; `init`, run there, offers the command on its own consent line. The command reads the key with no echo, makes one network call to api.typesafe.ai with the vendor's sample text only on the user's own run, and writes the key in plain text — as managed export lines in the shell's startup files (bash: ~/.bashrc and the login file, created as ~/.bash_profile when none exists; zsh: ~/.zshrc), as the kit's own file ~/.config/fish/conf.d/typesafe-api-key.fish for fish, or on Windows as the user environment variable TYPESAFE_API_KEY, stored in the Windows user environment; a process already running at the save can keep its old environment and hand it to what it starts — the guide's key mark confirms the agent sees it (a file is readable by any process running as that user, and by other local accounts when one is world-readable in a traversable home); the vendor keeps data under its processing agreement and offers zero retention only to enterprise accounts. The item reads the environment the agent's commands run in: a host setting that filters it shows the item while the key is set in the user's shell. The decline alternative rides the recipe line, asked apart BEFORE the confirmation (the `spec-adoption` order): on "connect" hand over the apply line; on "decline" the agent records it — the recipe line's preview, then its printed `--apply` — per project as `jevConnectAck` in `docs/ai/acks.json`. Risk profile: a plain-text key in the user's startup files or the Windows user environment, written by a command the user runs; one network call from that run.
|
|
48
|
+
- `jev-skill` — the lane is harness; both `jev-skill` and `jev-skill.earlier` are env-dependent: the agent applies the item on the chat yes after the check, showing the files it would write; it changes no harness setting. `init` applies it on the batch terminal yes as the route the user may take instead. When the recipe line opens with `run the apply in <place>`, that place is where a user who prefers their own terminal runs it. The skill is third-party text — the vendor's, MIT — that the kit ships pinned and installs as a verified copy into three agent skill roots: `~/.agents/skills`, the Claude Code skills dir (`CLAUDE_CONFIG_DIR` or `~/.claude`) and `~/.gemini/config/skills`. It is never updated silently: a copy at an earlier pin is replaced only by that command, and a foreign copy is left untouched and named on the recipe line. A copy the vendor's `skills` CLI wrote with the same bytes reads current, and a later pin move replaces it like a kit copy; a vendor plugin install is invisible to the file check, so the Claude Code root may read absent while the plugin serves it. Only when the user asks to see the plan does the agent run the command without `--apply` (a read-only dry run). The decline alternative rides the recipe line, asked apart BEFORE the confirmation: on "install" run the check line and, on the yes given on its result, the apply line by the consent route; on "decline" the agent records it — the recipe line's preview, then its printed `--apply` — per project as `jevSkillAck` in `docs/ai/acks.json`. Risk profile: third-party text written into the user's agent skill roots by the kit's skill command; no network.
|
|
49
|
+
- `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 the apply slot is a **`--dry-run`** — it writes nothing and prints the whole plan — and consent takes two steps: the first yes runs the dry run and you relay its whole plan (every line, the closing `--apply` line included); the same line with `--apply` in place of `--dry-run` runs only on a SECOND yes given over that plan. 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.
|
|
50
50
|
|
|
51
51
|
**Sandbox lanes (what to DO with the bridge surfaces, per host class):**
|
|
52
52
|
|
|
@@ -47,7 +47,7 @@ Requires: ${CLAUDE_SKILL_DIR}/references/shared/report-footer.md · ${CLAUDE_SKI
|
|
|
47
47
|
|
|
48
48
|
**What the navigator ensure does.** `docs/ai/index.md` is the always-loaded navigator the entry point declares, and it is GENERATED — no template ships it, so a deployment that never ran the generator boots from a broken entry point (and, on a Node project, carries a pre-commit hook that fails its own index check). The ensure runs the bundled generator's finalizer and reports `regenerated` (it was missing or stale — it was written) or `already-current` (nothing written); under a hook module that is not the bundled one, a write the project's own checker rejects is put back → `customized-preserved`. It never skips a No-Node project: the generator runs from `${CLAUDE_SKILL_DIR}/references/scripts/` on the agent host, not from the project's `scripts/`. **Its position in the run-list is EARLY and therefore NOT authoritative** — `lens` (and, on the migrated path, steps 6–7) still change `docs/ai` afterwards — so the authoritative run is the LATE `--only index` rung documented at both exits; the early one is idempotent: an untouched tree changes nothing.
|
|
49
49
|
|
|
50
|
-
**What the spec-layer ensure does.** A deployment older than the feature-spec layer lacks `scripts/spec-schema.mjs` and `docs/ai/specs/index.md`, and its deployed `check-docs-size.mjs` predates the store collapse. In ONE order: the reader pair, then the checker pair — each seeded when absent and REFRESHED only while a file's bytes are a body a release shipped (an append-only digest catalog → `refreshed`; an edited body is preserved verbatim and withholds the writes that depend on it, the checker lane waiting on a byte-current reader pair); the store root is seeded from the bundled template, date rendered, ONLY behind a checker pair current after the run — an older or edited checker renders the store row by row and reds the hook's `--check-index`; edited files are replaced only on row 3's yes. One token by precedence: `seeded` > `refreshed` > `customized-preserved` (an edited pair, and this run wrote nothing) > `already-present`; `skipped-no-node-evidence` only when neither a root `package.json` nor any kit-seeded `scripts/*.mjs` is present (the same Node-evidence probe the enforcement-script ensure uses; an unreadable probe is `failed` / `node-evidence-unverifiable`); every line states what this run did, and a write that stops partway names what landed. The legacy-ADR instruct never withholds it. **The adoption state this seed leaves behind is reported, never assumed:** the Recommendations section (step 4 / 8) carries the `spec-adoption` item — `not adopted` (the store is absent: the seed offered, the decline
|
|
50
|
+
**What the spec-layer ensure does.** A deployment older than the feature-spec layer lacks `scripts/spec-schema.mjs` and `docs/ai/specs/index.md`, and its deployed `check-docs-size.mjs` predates the store collapse. In ONE order: the reader pair, then the checker pair — each seeded when absent and REFRESHED only while a file's bytes are a body a release shipped (an append-only digest catalog → `refreshed`; an edited body is preserved verbatim and withholds the writes that depend on it, the checker lane waiting on a byte-current reader pair); the store root is seeded from the bundled template, date rendered, ONLY behind a checker pair current after the run — an older or edited checker renders the store row by row and reds the hook's `--check-index`; edited files are replaced only on row 3's yes. One token by precedence: `seeded` > `refreshed` > `customized-preserved` (an edited pair, and this run wrote nothing) > `already-present`; `skipped-no-node-evidence` only when neither a root `package.json` nor any kit-seeded `scripts/*.mjs` is present (the same Node-evidence probe the enforcement-script ensure uses; an unreadable probe is `failed` / `node-evidence-unverifiable`); every line states what this run did, and a write that stops partway names what landed. The legacy-ADR instruct never withholds it. **The adoption state this seed leaves behind is reported, never assumed:** the Recommendations section (step 4 / 8) carries the `spec-adoption` item — `not adopted` (the store is absent: the seed offered, the decline the agent records), `adopting` (a store with no live contract: the decline offered), silenced by a recorded decline (`ack-write --lane spec-adoption`), and a store the probe cannot read is a stated skip — and `status` prints the same state on its own line.
|
|
51
51
|
|
|
52
52
|
**`gates-migration` — legacy gates.json migration (consented preview — D8).** An EXISTING declaration may still carry the retired review-ledger / fold-completeness checks. Run the preview (dry-run — writes NOTHING), show the user the exact plan, and only on an explicit yes re-run it with `--apply`: canonical legacy entries (matched by their documented single-invocation cmd forms) are REMOVED, the canonical `unit-tests` cmd gains the built-in lcov reporters, and the coverage-check gate is ADDED last — atomic and COMPLETE, so the migrated declaration satisfies `run-gates --final`. **The checker rides a PRODUCER or is not declared at all** (`${CLAUDE_SKILL_DIR}/references/modes/gates.md`): with no gate producing the lcov it reads, the migration does NOT add it, an already-declared one is reported INERT, the result is not called final-run-capable, and the preview prints the paste-ready suite cmd to declare by hand — nothing is ever removed for you. CUSTOMIZED entries are NEVER auto-touched: the preview names each with a paste-ready recovery, and the commit guard must NOT be installed until they are resolved. Of step 3's run-list, only this migration and `tools/checker-gates.mjs` under the `tier` ask change an existing gates.json (the FILL preview runs at init).
|
|
53
53
|
|
package/tools/commands.mjs
CHANGED
|
@@ -194,7 +194,7 @@ const CATALOG = [
|
|
|
194
194
|
invocation: invocationOf('mcp'),
|
|
195
195
|
group: 'Configure',
|
|
196
196
|
kind: GUARDED,
|
|
197
|
-
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
|
|
197
|
+
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 writes nothing and names the advisor’s mcp-channel item, which applies it by the consent route.',
|
|
198
198
|
},
|
|
199
199
|
{
|
|
200
200
|
key: 'recipes',
|
|
@@ -311,7 +311,7 @@ const classifyDir = (abs, io) => {
|
|
|
311
311
|
return { state: STATE.FOREIGN, className: describeNonRegular(st) };
|
|
312
312
|
};
|
|
313
313
|
|
|
314
|
-
// ── the merges (ONE definition, shared by the writer's bodies
|
|
314
|
+
// ── the merges (ONE definition, shared by the writer's bodies) ──
|
|
315
315
|
// Merge-don't-clobber in both: every foreign server, key and allow rule is carried over untouched,
|
|
316
316
|
// and our own entry/rules are added idempotently.
|
|
317
317
|
|
|
@@ -335,19 +335,6 @@ export const mergeSettings = (registration) => {
|
|
|
335
335
|
|
|
336
336
|
export const formatJson = (data, eol) => `${JSON.stringify(data, null, JSON_INDENT).replaceAll(LF, eol)}${eol}`;
|
|
337
337
|
|
|
338
|
-
// The two hand-apply bodies — the whole output of the MASKED arm.
|
|
339
|
-
//
|
|
340
|
-
// They are deliberately DIFFERENT shapes, because what the kit knows about each file differs. The
|
|
341
|
-
// settings file was OBSERVABLE — and read where present — so its body is a real merge: it already
|
|
342
|
-
// carries every foreign key it had. The masked `.mcp.json` was not observable at all, so a
|
|
343
|
-
// whole-file body would name only our server and, pasted as
|
|
344
|
-
// instructed, would delete every foreign server the mask hid. `mcpEntry` is therefore the ENTRY
|
|
345
|
-
// ALONE, to be merged under `mcpServers` by a human who can see what is actually in that file.
|
|
346
|
-
export const renderFragments = (registration) => ({
|
|
347
|
-
mcpEntry: formatJson(registration.entry, registration.mcpJson.eol),
|
|
348
|
-
settings: formatJson(mergeSettings(registration), registration.settings.eol),
|
|
349
|
-
});
|
|
350
|
-
|
|
351
338
|
// A LIBRARY module the mode doc names by path — so someone can try to run it. A no-op on import;
|
|
352
339
|
// on a direct run it points at the command that acts on what this module only reports.
|
|
353
340
|
refuseDirectRun(import.meta.url);
|
package/tools/mcp.mjs
CHANGED
|
@@ -22,21 +22,21 @@
|
|
|
22
22
|
// • the preflight is READ-ONLY: an absent `.claude/` is a NAMED state, and the dir is created on
|
|
23
23
|
// `--apply` only (assertCreatableDirSafe mkdirs, so it may not run in a preview);
|
|
24
24
|
// • a MASKED target (an OS sandbox injects a character device where `.mcp.json` would be — this
|
|
25
|
-
// repo is exactly that case) is not a failure: the kit
|
|
26
|
-
//
|
|
25
|
+
// repo is exactly that case) is not a failure: the kit writes nothing, exits 0 and names the
|
|
26
|
+
// advisor's mcp-channel item, whose check line re-judges outside the sandbox;
|
|
27
27
|
// • never `settings.local.json`; never commits.
|
|
28
28
|
//
|
|
29
29
|
// The read half lives in mcp-registration.mjs, which the advisor and `uninstall` use — so what is
|
|
30
30
|
// there and what would be written are computed by one module, never by two that can disagree.
|
|
31
31
|
//
|
|
32
|
-
// Exit codes: 0 done / dry-run (incl. the
|
|
32
|
+
// Exit codes: 0 done / dry-run (incl. the masked state); 1 precondition STOP; 2 usage.
|
|
33
33
|
// Dependency-free beyond the kit's own exports, Node >= 22. No side effects on import.
|
|
34
34
|
|
|
35
35
|
import {
|
|
36
36
|
lstatSync, openSync, closeSync, fstatSync, writeFileSync, ftruncateSync, fsyncSync,
|
|
37
37
|
renameSync, constants,
|
|
38
38
|
} from 'node:fs';
|
|
39
|
-
import { join, resolve } from 'node:path';
|
|
39
|
+
import { dirname, join, resolve } from 'node:path';
|
|
40
40
|
import { fileURLToPath } from 'node:url';
|
|
41
41
|
import { assertCreatableDirSafe, writeContainedFileAtomic } from './atomic-write.mjs';
|
|
42
42
|
import { isDirectRun } from './direct-run.mjs';
|
|
@@ -46,7 +46,6 @@ import {
|
|
|
46
46
|
CLAUDE_DIR_REL,
|
|
47
47
|
ENABLED_KEY,
|
|
48
48
|
MCP_JSON_REL,
|
|
49
|
-
SERVERS_KEY,
|
|
50
49
|
SERVER_NAME,
|
|
51
50
|
SETTINGS_REL,
|
|
52
51
|
STATE,
|
|
@@ -54,9 +53,9 @@ import {
|
|
|
54
53
|
mergeMcpJson,
|
|
55
54
|
mergeSettings,
|
|
56
55
|
readRegistration,
|
|
57
|
-
renderFragments,
|
|
58
56
|
} from './mcp-registration.mjs';
|
|
59
57
|
import { shellQuoteArg } from './review-state.mjs';
|
|
58
|
+
import { applySlot } from './write-lanes.mjs';
|
|
60
59
|
|
|
61
60
|
const q = shellQuoteArg;
|
|
62
61
|
|
|
@@ -83,7 +82,8 @@ Default is --dry-run (a preview that prints the exact entry and writes nothing).
|
|
|
83
82
|
|
|
84
83
|
Without --replace, an existing "${SERVER_NAME}" entry that STRUCTURALLY DIFFERS is refused unwritten (key
|
|
85
84
|
order is ignored). Where ${MCP_JSON_REL} is a device node, FIFO or socket — an OS sandbox
|
|
86
|
-
mask is the usual cause —
|
|
85
|
+
mask is the usual cause — nothing is written, and the report names the advisor's mcp-channel
|
|
86
|
+
item, which applies the registration by the consent route.
|
|
87
87
|
Never writes settings.local.json; never commits.`;
|
|
88
88
|
|
|
89
89
|
export const fail = (exitCode, message) => Object.assign(new Error(message), { exitCode });
|
|
@@ -105,9 +105,9 @@ const lstatNoFollow = (path, lstat = lstatSync) => {
|
|
|
105
105
|
// A target we could not read or parse is never overwritten: a merge over a file whose current
|
|
106
106
|
// content is unknown is a clobber wearing a merge's name.
|
|
107
107
|
//
|
|
108
|
-
// `maskedAllowed` is TRUE for `.mcp.json` alone. That file has a sanctioned
|
|
109
|
-
//
|
|
110
|
-
// mask there is an ordinary refusal rather than a second, unplanned success path.
|
|
108
|
+
// `maskedAllowed` is TRUE for `.mcp.json` alone. That file has a sanctioned route — the advisor's
|
|
109
|
+
// mcp-channel item, whose check line re-judges outside the sandbox — and `settings.json` has none, so
|
|
110
|
+
// a mask there is an ordinary refusal rather than a second, unplanned success path.
|
|
111
111
|
const assertTargetUsable = (target, { maskedAllowed = false } = {}) => {
|
|
112
112
|
if (target.state === STATE.MASKED) {
|
|
113
113
|
if (maskedAllowed) return;
|
|
@@ -168,7 +168,7 @@ export const preflightMcp = ({ cwd, replace = false }, deps = {}) => {
|
|
|
168
168
|
};
|
|
169
169
|
|
|
170
170
|
// The plan is pure over the registration: what is missing, and the exact body each file would get.
|
|
171
|
-
// Both bodies are built even when nothing is written — they ARE the preview
|
|
171
|
+
// Both bodies are built even when nothing is written — they ARE the preview.
|
|
172
172
|
export const planMcp = (registration) => ({
|
|
173
173
|
writeMcpJson: !registration.mcpJson.matches,
|
|
174
174
|
writeSettings: !registration.settings.complete,
|
|
@@ -225,9 +225,8 @@ const writeTarget = (root, target, body, deps, stop) => {
|
|
|
225
225
|
|
|
226
226
|
export const writeMcp = ({ cwd, dryRun = true, replace = false } = {}, deps = {}) => {
|
|
227
227
|
const preflight = preflightMcp({ cwd: cwd ?? deps.cwd ?? process.cwd(), replace }, deps);
|
|
228
|
-
const base = { ...preflight, dryRun, wrote: false };
|
|
229
|
-
if (preflight.masked) return
|
|
230
|
-
if (dryRun) return base;
|
|
228
|
+
const base = { ...preflight, dryRun, replace, wrote: false };
|
|
229
|
+
if (preflight.masked || dryRun) return base;
|
|
231
230
|
|
|
232
231
|
const { root, registration, plan } = preflight;
|
|
233
232
|
const stop = (message) => makeMcpError(MCP_SYMLINK, message);
|
|
@@ -286,19 +285,19 @@ const settingsLine = (result) => {
|
|
|
286
285
|
return ` - ${SETTINGS_REL}: ${dryRun ? 'would set' : 'set'} ${parts.join(' · ')}`;
|
|
287
286
|
};
|
|
288
287
|
|
|
289
|
-
// The
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
// whole-file body pasted as instructed would delete every server it could not see.
|
|
288
|
+
// The masked report names the item that applies the registration by the consent route: its check
|
|
289
|
+
// line, run once more outside the sandbox, reads the real file this mode cannot see through the mask.
|
|
290
|
+
// A run under --replace routes through the differing arm, whose check and apply carry the replace.
|
|
293
291
|
const maskedReport = (result) => {
|
|
294
292
|
const target = result.registration.mcpJson;
|
|
293
|
+
const variant = result.replace ? 'mcp-channel.differing' : 'mcp-channel';
|
|
294
|
+
const { check, apply } = applySlot(variant, { toolsDir: dirname(MCP_TOOL), root: result.root });
|
|
295
295
|
return [
|
|
296
296
|
// The CLASS is what was observed; the sandbox mask is the usual CAUSE but is not established here.
|
|
297
|
-
`agent-workflow MCP registration —
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
`
|
|
301
|
-
indented(result.fragments.settings),
|
|
297
|
+
`agent-workflow MCP registration — nothing was written: ${MCP_JSON_REL} is a ${target.className} (an OS sandbox device mask is the usual cause).`,
|
|
298
|
+
" the advisor's mcp-channel item applies the registration by the consent route — its check line runs once more outside the sandbox and the apply line runs on the yes given on that result:",
|
|
299
|
+
` check: ${check}`,
|
|
300
|
+
` apply: ${apply}`,
|
|
302
301
|
POSTURE_LINE,
|
|
303
302
|
HIDDEN_MODE_LINE,
|
|
304
303
|
].join(LF);
|
|
@@ -55,7 +55,7 @@ import { surveyFamily, surveyGateHook, surveyAdrLayoutStrict } from './family-re
|
|
|
55
55
|
import { probeSandboxMasks, needsMasksApply, patternToRel } from './sandbox-masks.mjs';
|
|
56
56
|
import { shellQuoteArg } from './review-state.mjs';
|
|
57
57
|
import { isFinalCapableDeclaration } from './run-gates.mjs';
|
|
58
|
-
import { loadDeclaration, canonicalCheckerGates, coverageProducerPrecedes, isKitOwnedCheckerGate,
|
|
58
|
+
import { loadDeclaration, canonicalCheckerGates, coverageProducerPrecedes, isKitOwnedCheckerGate, LCOV_PRODUCER_KEY } from './gates-declaration.mjs';
|
|
59
59
|
import { readRegularFileNoFollow } from './fs-read-nofollow.mjs';
|
|
60
60
|
// The typed channel's READ-ONLY leaf only — never tools/mcp.mjs, which reaches the write core.
|
|
61
61
|
import {
|
|
@@ -119,6 +119,8 @@ export { ACKS_FILE, ACKS_LANE_KEY, ACKS_WORKTREES_DIR_KEY, ACKS_COVERAGE_DOMAIN_
|
|
|
119
119
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
120
120
|
const toolPath = (rel) => join(HERE, rel);
|
|
121
121
|
const q = shellQuoteArg;
|
|
122
|
+
// A user variant's own step, as the lane leaf words it: the rendered apply of every user item.
|
|
123
|
+
const userStepOf = (variant, root) => applySlot(variant, { toolsDir: HERE, root }).apply;
|
|
122
124
|
const ABSENT_CODE = 'ENOENT';
|
|
123
125
|
const ENFORCEMENT_KIT_ROOT = resolve(HERE, '..');
|
|
124
126
|
const ENFORCEMENT_BUNDLE_ROOT = join(ENFORCEMENT_KIT_ROOT, 'references', 'scripts');
|
|
@@ -163,6 +165,7 @@ export const SEVERITIES = Object.freeze({
|
|
|
163
165
|
'review-recipe': SEVERITY_ATTENTION,
|
|
164
166
|
'gates-declaration': SEVERITY_OPTIONAL,
|
|
165
167
|
'gates-inert': SEVERITY_ATTENTION,
|
|
168
|
+
'gates-inert.unfillable': SEVERITY_ATTENTION,
|
|
166
169
|
'gates-inert.no-verification': SEVERITY_ATTENTION,
|
|
167
170
|
// Both third outcomes are attention, and both therefore block the flow-optimal line MECHANICALLY:
|
|
168
171
|
// an added item makes the verdict non-null. One reports a pair that is dead on a tree the closed
|
|
@@ -255,6 +258,7 @@ export const composeVerdict = ({ attention, optional, skipped }) => {
|
|
|
255
258
|
// banned tokens), never a fixture-coverage gamble. A pure-placeholder template marks a WHAT whose
|
|
256
259
|
// content is fully dynamic (capped at composition by truncation-with-count) — except `profile-gap`, whose
|
|
257
260
|
// WHAT is never truncated: the leaf's suite pins every composed WHAT under the cap.
|
|
261
|
+
const GATES_INERT_WHAT = 'the declared coverage checker ({id}) has no producer before it — it certifies nothing this run, or reads a stale lcov';
|
|
258
262
|
export const WHATS = Object.freeze({
|
|
259
263
|
'velocity-core': 'routine read-only commands still prompt — {n} audited read-only allowlist entr(ies) not seeded',
|
|
260
264
|
'velocity-core.rule': 'auto mode refuses a harness apply, so work cannot stay in auto mode — the apply ask rule is not seeded',
|
|
@@ -267,7 +271,8 @@ export const WHATS = Object.freeze({
|
|
|
267
271
|
'sandbox-provision.installable': 'the OS sandbox is unavailable: {reason} — installable via the doctor (consent tuple {tuple})',
|
|
268
272
|
'review-recipe': '{degraded}',
|
|
269
273
|
'gates-declaration': 'no declared gate matrix (docs/ai/gates.json absent or empty) — gates prompt one by one; the apply PREVIEWS its --apply line, writes nothing',
|
|
270
|
-
'gates-inert':
|
|
274
|
+
'gates-inert': GATES_INERT_WHAT,
|
|
275
|
+
'gates-inert.unfillable': GATES_INERT_WHAT,
|
|
271
276
|
'gates-inert.no-verification': "all {n} declared gate(s) are the kit's own checkers — the matrix runs no project-verification command",
|
|
272
277
|
'gates-inert.producer-unrecognized': 'the coverage checker ({id}) has NO producer declared and none offerable, on a tree the changed-line domain barely reaches',
|
|
273
278
|
'gates-inert.coverage-domain-narrow': 'certification covers the assessable minority only — {ext} dominate(s) the tracked tree, and the changed-line domain excludes them',
|
|
@@ -588,7 +593,8 @@ const probeReviewRecipe = ({ root, deps, add, skip }) => {
|
|
|
588
593
|
}
|
|
589
594
|
}
|
|
590
595
|
if (degraded.length > 0) {
|
|
591
|
-
add('review-recipe', fillTemplate(WHATS['review-recipe'], { degraded: capList(degraded, templateBudget(WHATS['review-recipe'])) }),
|
|
596
|
+
add('review-recipe', fillTemplate(WHATS['review-recipe'], { degraded: capList(degraded, templateBudget(WHATS['review-recipe'])) }),
|
|
597
|
+
userStepOf('review-recipe', root));
|
|
592
598
|
}
|
|
593
599
|
} catch (err) {
|
|
594
600
|
skip('review-recipe', err);
|
|
@@ -651,8 +657,8 @@ const probeGates = ({ root, deps, add, skip, shared }) => {
|
|
|
651
657
|
// gate is not a kit checker. Cause A's apply follows what the FILL can actually do, which the
|
|
652
658
|
// D-8 placement rule changed: when the checker is the declaration's LAST gate and the project's own
|
|
653
659
|
// scripts yield an offerable producer, the fill now PLACES that producer before the checker, so the
|
|
654
|
-
// remedy is the ordinary consent-gated preview.
|
|
655
|
-
//
|
|
660
|
+
// remedy is the ordinary consent-gated preview. Where no offerable producer exists, or the checker
|
|
661
|
+
// is not last, the arm is the user item `gates-inert.unfillable` — the edit is the project's own,
|
|
656
662
|
// because the fill never reorders entries it did not write.
|
|
657
663
|
//
|
|
658
664
|
// An ABSENT or EMPTY declaration belongs to the gates-declaration item; a malformed one throws out
|
|
@@ -765,8 +771,9 @@ export const probeGatesInert = ({ root, deps, add, skip }) => {
|
|
|
765
771
|
add(
|
|
766
772
|
'gates-inert',
|
|
767
773
|
fillTemplate(WHATS['gates-inert.producer-unrecognized'], { id }),
|
|
768
|
-
|
|
774
|
+
userStepOf('gates-inert.producer-unrecognized', root),
|
|
769
775
|
'gates-inert.producer-unrecognized',
|
|
776
|
+
`${id} has no producer declared and none offerable — the fill offers only terminating-class script names, so a recognized body under another name (a "ci" script running node --test) is declared as its own gate entry, never marked`,
|
|
770
777
|
);
|
|
771
778
|
return;
|
|
772
779
|
}
|
|
@@ -788,13 +795,12 @@ export const probeGatesInert = ({ root, deps, add, skip }) => {
|
|
|
788
795
|
: colliding !== undefined
|
|
789
796
|
? `a producer IS offerable here, but its id "${colliding.id}" is already declared, so the fill refuses it as a collision — rename that gate, or repoint it at the producer form`
|
|
790
797
|
: `no offerable producer exists here, and the fill never reorders entries it did not write`;
|
|
791
|
-
|
|
792
|
-
'gates-inert',
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
);
|
|
798
|
+
if (checkerIsLast && producer) {
|
|
799
|
+
add('gates-inert', fillTemplate(WHATS['gates-inert'], { id }), fillPreviewFor(root, [producer.id]));
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
add('gates-inert', fillTemplate(WHATS['gates-inert.unfillable'], { id }), userStepOf('gates-inert.unfillable', root),
|
|
803
|
+
'gates-inert.unfillable', blocked);
|
|
798
804
|
return;
|
|
799
805
|
}
|
|
800
806
|
// The source-size checker is one of the kit's OWN checkers, and it is deliberately NOT
|
|
@@ -887,15 +893,11 @@ const probeSourceSize = ({ root, deps, add, skip }) => {
|
|
|
887
893
|
}
|
|
888
894
|
// The id SQUATTER — the id says the practice is declared, the cmd is not this checker under any
|
|
889
895
|
// reading. `--adopt` here mints the record and is then refused by the fill on the id collision:
|
|
890
|
-
// correct against a squatter, and useless as a RENDERED remedy, so this arm
|
|
891
|
-
// edits that actually resolve it
|
|
896
|
+
// correct against a squatter, and useless as a RENDERED remedy, so this arm is a user item whose
|
|
897
|
+
// step names the edits that actually resolve it.
|
|
892
898
|
if (gates.some((gate) => gate.id === SOURCE_SIZE_GATE_ID)) {
|
|
893
|
-
add(
|
|
894
|
-
'source-size'
|
|
895
|
-
fillTemplate(WHATS['source-size.id-squatter'], {}),
|
|
896
|
-
`HAND-APPLY: in ${GATES_REL}, either rename the "${SOURCE_SIZE_GATE_ID}" gate to an id of its own, or repoint its cmd at node ${q(tool)} --check — then re-run recommendations`,
|
|
897
|
-
'source-size.id-squatter',
|
|
898
|
-
);
|
|
899
|
+
add('source-size', fillTemplate(WHATS['source-size.id-squatter'], {}), userStepOf('source-size.id-squatter', root),
|
|
900
|
+
'source-size.id-squatter');
|
|
899
901
|
return;
|
|
900
902
|
}
|
|
901
903
|
add('source-size', fillTemplate(WHATS['source-size'], {}), adoptLine);
|
|
@@ -1166,7 +1168,7 @@ const renderBrokenExecutor = ({ root, deps, add, skip }) => {
|
|
|
1166
1168
|
const blocked = writerBlock(root, deps);
|
|
1167
1169
|
if (blocked) preconditions.push(`${blocked} — fix that`);
|
|
1168
1170
|
const room = templateBudget(WHATS['executor-vehicle']) - String(configured.length).length - survey.state.length - 2;
|
|
1169
|
-
|
|
1171
|
+
add(
|
|
1170
1172
|
'executor-vehicle',
|
|
1171
1173
|
fillTemplate(WHATS['executor-vehicle'], {
|
|
1172
1174
|
n: configured.length,
|
|
@@ -1179,9 +1181,10 @@ const renderBrokenExecutor = ({ root, deps, add, skip }) => {
|
|
|
1179
1181
|
'executor-vehicle',
|
|
1180
1182
|
`${preconditions.length ? `precondition: ${preconditions.join('; ')}; ` : ''}${HIDDEN_AGENTS_FOLLOW_UP(root)}`,
|
|
1181
1183
|
);
|
|
1184
|
+
return true;
|
|
1182
1185
|
} catch (err) {
|
|
1183
1186
|
skip('executor-vehicle', err);
|
|
1184
|
-
return
|
|
1187
|
+
return true;
|
|
1185
1188
|
}
|
|
1186
1189
|
};
|
|
1187
1190
|
|
|
@@ -1342,6 +1345,8 @@ export const RISK_NOTED_KEYS = Object.freeze(['bridge-tier', 'read-lane', 'workt
|
|
|
1342
1345
|
// spec-check's own census; a recorded decline (the `spec-adoption` ack lane) is the fact that
|
|
1343
1346
|
// silences the item, and an unreadable store is a stated skip so the flow-optimal line never renders
|
|
1344
1347
|
// over it. The not-adopted apply is the spec-layer ensure; the decline preview rides the recipe line.
|
|
1348
|
+
// The one opening of every offer's decline alternative: a chat write the agent runs on the user's decline.
|
|
1349
|
+
export const DECLINE_ALTERNATIVE = 'decline alternative (asked apart, before the confirmation; on your decline the agent records it instead of the apply, never after it): ';
|
|
1345
1350
|
export const probeSpecAdoption = ({ root, deps, add, skip }) => {
|
|
1346
1351
|
try {
|
|
1347
1352
|
const survey = surveySpecAdoption(root, deps);
|
|
@@ -1357,7 +1362,7 @@ export const probeSpecAdoption = ({ root, deps, add, skip }) => {
|
|
|
1357
1362
|
fillTemplate(WHATS['spec-adoption'], {}),
|
|
1358
1363
|
`node ${q(toolPath('ensure-configs.mjs'))} --reconcile --only ${SPEC_LAYER_ENSURE} --cwd ${q(root)}`,
|
|
1359
1364
|
'spec-adoption',
|
|
1360
|
-
|
|
1365
|
+
`${DECLINE_ALTERNATIVE}${decline}`,
|
|
1361
1366
|
);
|
|
1362
1367
|
return;
|
|
1363
1368
|
}
|
|
@@ -1477,13 +1482,12 @@ export const probeAdrStore = ({ root, deps, add, skip }) => {
|
|
|
1477
1482
|
try {
|
|
1478
1483
|
const shape = ADR_LAYOUT_SHAPES[surveyAdrLayoutStrict(root, deps)];
|
|
1479
1484
|
if (!shape) return; // migrated, or no ADR substrate at all — nothing to offer
|
|
1480
|
-
//
|
|
1481
|
-
//
|
|
1482
|
-
// dry-run. A runnable one-liner here would auto-run a tree-mutating migration on stale consent.
|
|
1485
|
+
// The apply is the bare dry run: consent takes two steps, as profile-gap's does — the first yes
|
|
1486
|
+
// runs it and the plan is relayed, the same line with --apply runs only on a second yes over it.
|
|
1483
1487
|
add(
|
|
1484
1488
|
'adr-store-migration',
|
|
1485
1489
|
fillTemplate(WHATS['adr-store-migration'], { shape }),
|
|
1486
|
-
`
|
|
1490
|
+
`node ${q(toolPath('migrate-adr-store.mjs'))} --dry-run --cwd ${q(root)}`,
|
|
1487
1491
|
);
|
|
1488
1492
|
} catch (err) {
|
|
1489
1493
|
skip('adr-store-migration', err);
|
|
@@ -1534,7 +1538,7 @@ export const probeJevConnect = ({ root, deps, add, skip }) => {
|
|
|
1534
1538
|
const place = placeOf({ env, platform: host.platform, hostname: host.hostname, container: host.container });
|
|
1535
1539
|
const decline = `node ${q(toolPath('ack-write.mjs'))} --lane jev-connect --fingerprint ${JEV_CONNECT_DECLINE} --cwd ${q(root)}`;
|
|
1536
1540
|
add('jev-connect', WHATS['jev-connect'], applySlot('jev-connect', { toolsDir: HERE }).apply, 'jev-connect',
|
|
1537
|
-
`${place ? `run the apply in ${place}; ` : ''}
|
|
1541
|
+
`${place ? `run the apply in ${place}; ` : ''}${DECLINE_ALTERNATIVE}${decline}`);
|
|
1538
1542
|
} catch (err) {
|
|
1539
1543
|
skip('jev-connect', err);
|
|
1540
1544
|
}
|
|
@@ -1568,7 +1572,7 @@ export const probeJevSkill = ({ root, deps, add, skip }) => {
|
|
|
1568
1572
|
.map(({ path, reason }) => `left untouched: ${printable(path)} — ${printable(reason)}; `).join('');
|
|
1569
1573
|
const record = `node ${q(toolPath('ack-write.mjs'))} --lane jev-skill --fingerprint ${decline} --cwd ${q(root)}`;
|
|
1570
1574
|
add('jev-skill', what, line, behind.length ? 'jev-skill.earlier' : 'jev-skill',
|
|
1571
|
-
`${place ? `run the apply in ${place}; ` : ''}${untouched}${note ? `${note}; ` : ''}
|
|
1575
|
+
`${place ? `run the apply in ${place}; ` : ''}${untouched}${note ? `${note}; ` : ''}${DECLINE_ALTERNATIVE}${record}`);
|
|
1572
1576
|
} catch (err) {
|
|
1573
1577
|
skip('jev-skill', err);
|
|
1574
1578
|
}
|
|
@@ -1664,10 +1668,9 @@ export const buildRecommendations = ({ cwd, deps = {} } = {}) => {
|
|
|
1664
1668
|
// which item fired, and only this field says which ARM of it did — so a consumer (the pre-publish
|
|
1665
1669
|
// smoke asserting a specific false-green never returns) can assert the exact outcome instead of
|
|
1666
1670
|
// pattern-matching prose that is free to be reworded.
|
|
1667
|
-
// `detail` (optional) is an extra rendered `recipe:` line —
|
|
1668
|
-
// the capped WHAT and
|
|
1669
|
-
// line
|
|
1670
|
-
// agents hidden-mode reconcile follow-up). Single-line like apply; absent for every other item.
|
|
1671
|
+
// `detail` (optional) is an extra rendered `recipe:` line — an item's own facts or its alternative,
|
|
1672
|
+
// TOO LONG for the capped WHAT and outside the pure-command apply (such as the bridge-tier
|
|
1673
|
+
// missing-entries line or the worktrees-dir ack fallback). Single-line like apply; absent otherwise.
|
|
1671
1674
|
// Returns whether the item really RENDERED. One probe's disposition depends on another's having
|
|
1672
1675
|
// spoken (the marker-stale ⟷ read-lane.stale precedence), and "the conditions still look right"
|
|
1673
1676
|
// is not the same fact as "an item exists" — the shape backstop can refuse, and a condition read
|
|
@@ -1757,9 +1760,9 @@ Usage:
|
|
|
1757
1760
|
|
|
1758
1761
|
Computes the deterministic Recommendations section every kit upgrade ends with — VERDICT-FIRST:
|
|
1759
1762
|
one composed verdict line opens every non-optimal render, then per item {severity · what is
|
|
1760
|
-
sub-optimal · the benefit in one plain line · the lane · an optional \`recipe:\` line (
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
+
sub-optimal · the benefit in one plain line · the lane · an optional \`recipe:\` line (an item's own
|
|
1764
|
+
facts or its alternative, such as the bridge-tier missing-entries line or
|
|
1765
|
+
the worktrees-dir ack fallback) · the harness check line · the exact consent-gated apply one-liner}. --cwd is
|
|
1763
1766
|
REQUIRED (the target project is explicit, never inferred from the shell's current directory). The
|
|
1764
1767
|
section renders present-even-when-empty ("${RECOMMENDATIONS_EMPTY_LINE}"); a probe failure is a
|
|
1765
1768
|
stated skipped-item line. Apply lines are cwd-independent (absolute tool paths, a pinned --cwd;
|
|
@@ -1770,8 +1773,8 @@ allow rules, <wrapper> * exclusions (codex-exec * where an execute slot delegate
|
|
|
1770
1773
|
sandbox.network.allowedDomains and state dirs in sandbox.filesystem.allowWrite. Where the host
|
|
1771
1774
|
honors the settings sandbox keys, an excluded wrapper runs outside the sandbox, every sandboxed
|
|
1772
1775
|
command of the project can reach each host, and every sandboxed command can write each state dir;
|
|
1773
|
-
plan/diff review modes and delegated execution keep their prompt.
|
|
1774
|
-
the user's own step.
|
|
1776
|
+
plan/diff review modes and delegated execution keep their prompt.
|
|
1777
|
+
A user item stays the user's own step.
|
|
1775
1778
|
|
|
1776
1779
|
Read-only: never writes, never commits, never runs a subscription CLI. Exit codes: 0 report
|
|
1777
1780
|
rendered (items or empty); 1 error; 2 usage.`;
|
package/tools/write-lanes.mjs
CHANGED
|
@@ -3,6 +3,10 @@ import { dirCovers } from './declared-paths.mjs';
|
|
|
3
3
|
import { claudeDirOf, quoteArg, skillTargets } from './jev-facts.mjs';
|
|
4
4
|
import { targetsFor } from './jev-connect.mjs';
|
|
5
5
|
import { BUNDLED_SCRIPT_NAMES, INSTALLER_REL, SCRIPTS_REL } from './git-hooks.mjs';
|
|
6
|
+
import { dqUnsafePath } from './checker-claim.mjs';
|
|
7
|
+
import { isLineUnsafe } from './source-size-refusal.mjs';
|
|
8
|
+
import { SOURCE_SIZE_GATE_ID } from './source-size-gate-cmd.mjs';
|
|
9
|
+
import { GATES_REL } from './gates-declaration.mjs';
|
|
6
10
|
|
|
7
11
|
export const LANE_CHAT = 'chat';
|
|
8
12
|
export const LANE_HARNESS = 'harness';
|
|
@@ -16,16 +20,17 @@ export const VARIANT_LANES = Object.freeze({
|
|
|
16
20
|
'autonomy-render': LANE_HARNESS,
|
|
17
21
|
'sandbox-provision': LANE_USER,
|
|
18
22
|
'sandbox-provision.installable': LANE_USER,
|
|
19
|
-
'review-recipe':
|
|
23
|
+
'review-recipe': LANE_USER,
|
|
20
24
|
'gates-declaration': LANE_CHAT,
|
|
21
25
|
'gates-inert': LANE_CHAT,
|
|
26
|
+
'gates-inert.unfillable': LANE_USER,
|
|
22
27
|
'gates-inert.no-verification': LANE_CHAT,
|
|
23
|
-
'gates-inert.producer-unrecognized':
|
|
28
|
+
'gates-inert.producer-unrecognized': LANE_USER,
|
|
24
29
|
'gates-inert.coverage-domain-narrow': LANE_CHAT,
|
|
25
30
|
'source-size': LANE_CHAT,
|
|
26
31
|
'source-size.unminted': LANE_CHAT,
|
|
27
32
|
'source-size.adopted-elsewhere': LANE_CHAT,
|
|
28
|
-
'source-size.id-squatter':
|
|
33
|
+
'source-size.id-squatter': LANE_USER,
|
|
29
34
|
'gate-hook': LANE_HARNESS,
|
|
30
35
|
'gate-hook.marker-stale': LANE_HARNESS,
|
|
31
36
|
'commit-guard': LANE_HARNESS,
|
|
@@ -109,6 +114,15 @@ const getGuardPaths = (facts) => [preCommitOf(facts), join(facts.root, INSTALLER
|
|
|
109
114
|
|
|
110
115
|
const formatCommand = (argv) => argv.map(quoteArg).join(' ');
|
|
111
116
|
const TERMINAL_STEP = 'run in a terminal of your own: ';
|
|
117
|
+
const OWN_STEP = 'your own step — ';
|
|
118
|
+
const RERUN_ADVISOR = ', then run /agent-workflow-kit recommendations';
|
|
119
|
+
|
|
120
|
+
const squatterStep = (facts) => {
|
|
121
|
+
const tool = join(facts.toolsDir, 'source-size-check.mjs');
|
|
122
|
+
const rename = `${OWN_STEP}in ${GATES_REL} rename the "${SOURCE_SIZE_GATE_ID}" gate to an id of its own`;
|
|
123
|
+
if (dqUnsafePath(tool) || isLineUnsafe(tool)) return `${rename}${RERUN_ADVISOR}`;
|
|
124
|
+
return `${rename}, or repoint its cmd at node "${tool}" --check${RERUN_ADVISOR}`;
|
|
125
|
+
};
|
|
112
126
|
|
|
113
127
|
// rejudged: the written value derives from state init's record does not watch, so init re-runs its check before the apply.
|
|
114
128
|
const createForm = (file, preview, apply, writes, envDependent = false, userStep = null, rejudged = false) => Object.freeze({
|
|
@@ -155,13 +169,13 @@ const FORMS = Object.freeze({
|
|
|
155
169
|
'jev-skill': SKILL_FORM,
|
|
156
170
|
'jev-skill.earlier': SKILL_FORM,
|
|
157
171
|
'jev-connect': createForm('jev-connect.mjs', null, [], getStartupPaths, true,
|
|
158
|
-
(facts, argv) =>
|
|
172
|
+
(facts, argv) => `${OWN_STEP}it asks for the key in your terminal and the key never goes into the chat — `
|
|
159
173
|
+ `${TERMINAL_STEP}${formatCommand(argv.apply)}`),
|
|
160
174
|
'family-freshness': Object.freeze({
|
|
161
|
-
userStep: () =>
|
|
175
|
+
userStep: () => `${OWN_STEP}${TERMINAL_STEP}${formatCommand(['npx', '@sabaiway/agent-workflow-kit@latest', 'init'])}`,
|
|
162
176
|
}),
|
|
163
177
|
'sandbox-provision': Object.freeze({
|
|
164
|
-
userStep: (facts) =>
|
|
178
|
+
userStep: (facts) => `${OWN_STEP}the doctor has no install it can run for you here (the reason is this item's text) — `
|
|
165
179
|
+ `${TERMINAL_STEP}cd ${quoteArg(facts.root)} && ${formatCommand(['node', join(facts.toolsDir, 'autonomy-doctor.mjs')])}`,
|
|
166
180
|
}),
|
|
167
181
|
enforcement: createForm('git-hooks.mjs',
|
|
@@ -171,7 +185,21 @@ const FORMS = Object.freeze({
|
|
|
171
185
|
'worktrees-dir': createForm('velocity-profile.mjs',
|
|
172
186
|
['--worktrees', '--cwd', '<root>'], ['--apply', '--worktrees', '--cwd', '<root>'], LOCAL_SETTINGS_WRITES, false, null, true),
|
|
173
187
|
'worktrees-dir.home': Object.freeze({
|
|
174
|
-
userStep: () =>
|
|
188
|
+
userStep: () => `${OWN_STEP}point docs/ai/worktrees.json parentDir at a dedicated directory${RERUN_ADVISOR}`,
|
|
189
|
+
}),
|
|
190
|
+
'gates-inert.unfillable': Object.freeze({
|
|
191
|
+
userStep: () => `${OWN_STEP}in ${GATES_REL} declare or move a suite gate carrying the coverage reporters before the coverage checker`
|
|
192
|
+
+ ` (references/modes/gates.md names the exact form), or drop that checker${RERUN_ADVISOR}`,
|
|
193
|
+
}),
|
|
194
|
+
'gates-inert.producer-unrecognized': Object.freeze({
|
|
195
|
+
userStep: () => `${OWN_STEP}in ${GATES_REL} mark the gate that writes the lcov with "lcovProducer": true`
|
|
196
|
+
+ ` (references/modes/gates.md names the key), or drop the coverage checker${RERUN_ADVISOR}`,
|
|
197
|
+
}),
|
|
198
|
+
'source-size.id-squatter': Object.freeze({ userStep: squatterStep }),
|
|
199
|
+
'review-recipe': Object.freeze({
|
|
200
|
+
userStep: () => `${OWN_STEP}set up the backend the recipe names with /agent-workflow-kit setup (it places the bridge skill`
|
|
201
|
+
+ ' and links its wrappers, and names the CLI install and sign-in that stay yours; /agent-workflow-kit backends shows what is'
|
|
202
|
+
+ ` missing), or re-pick the recipe with /agent-workflow-kit set-recipe${RERUN_ADVISOR}`,
|
|
175
203
|
}),
|
|
176
204
|
});
|
|
177
205
|
|