@sabaiway/agent-workflow-kit 14.28.0 → 14.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,35 @@ 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.29.0 — in auto mode the chat yes applies a harness item through the harness's own dialog: one seeded ask rule (AD-179)
8
+
9
+ **In a session the harness runs in its auto mode, once the apply ask rule is seeded (every velocity-core, kit-tools-tier or bridge-tier apply seeds it), the
10
+ chat yes of `/agent-workflow-kit upgrade` reaches a harness item's apply through the harness's own permission dialog
11
+ instead of a classifier refusal.** Before, auto mode's classifier judged the
12
+ outside-sandbox apply of a harness item and could refuse it, so the item stayed pending with no way forward in the chat.
13
+
14
+ - **One ask rule.** Every `velocity-profile` apply (flagless, `--kit-tools`, `--bridge-tier`) appends
15
+ `Bash(node *apply-danger-check.mjs*--apply*)` to `permissions.ask` in `.claude/settings.json` once, beside any ask
16
+ entries already there. The harness then asks before every harness apply (the `apply:` line of each harness item the
17
+ advisor renders). The rule is a
18
+ question added: it allows nothing and removes no prompt. `--autonomy` leaves it in place. The dry run lists it after
19
+ the read-only core: `would add permissions.ask entries: N`, the entry, `already present (ask): M`.
20
+ - **The advisor names it.** The `velocity-core` item now fires while the read-only core or the rule is missing, with a
21
+ text for each case: `in auto mode a harness apply is judged instead of asked — the apply ask rule is not seeded` when
22
+ only the rule is missing, both clauses when both are.
23
+ - **The danger check admits exactly it.** For `velocity-core`, `kit-tools-tier` and `bridge-tier` the check's planned
24
+ change carries the rule under `permissions.ask` and admits that one added entry as `nothing widened`; any other added,
25
+ removed or altered `permissions.ask` entry from those items refuses, naming the key.
26
+ - **The route** (`references/modes/recommendations.md`): the apply line runs once, outside the sandbox at once, so the
27
+ harness asks at the point of action; a velocity pick is checked and applied before any other, so it seeds the rule
28
+ first; when auto mode still refuses the check or the apply, the agent gives one plain step — leave auto mode for that
29
+ one action (Shift+Tab in the terminal, the mode indicator in an editor), or run
30
+ `npx @sabaiway/agent-workflow-kit@latest init` in a terminal of your own — then runs the same line once more, or the
31
+ advisor again after `init`.
32
+ - This release seeds no new allow entry anywhere (none for the check line); console `init` is unchanged except that its
33
+ velocity preview lists the rule, and its one `y` seeds it. Known limit: a project whose settings carry `permissions.defaultMode: "auto"` still stops the velocity
34
+ preflight, so it gets no rule and takes the plain step (recorded debt).
35
+
7
36
  ## 14.28.0 — `ready` means wired: the status line and `/agent-workflow-kit recipes` say when the project sandbox settings lack a bridge's entries (AD-178)
8
37
 
9
38
  **A bridge reads `ready` on the status line only when the project settings cover its sandbox entries, where they
package/README.md CHANGED
@@ -261,7 +261,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
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 degrades to printed maintainer-pasted commands, and the one-time host consent that makes it promptless surfaces via `recommendations`. Never commits, never pushes, never runs a subscription CLI. |
263
263
  | `/agent-workflow-kit uninstall` | opt-in, any time | **guarded teardown** — the inverse of `init` / `setup`. Removes only what's **provably ours** (managed skill dirs + bridge wrappers; in a project, the hidden-mode git-ignore block it added + the pre-commit hook it installed); **never deletes** your `docs/ai` / `AGENTS.md` (prints the exact `rm` to run by hand) or your `.claude/settings.json` (prints an **edit** — remove the attribution key, review any velocity `permissions.*` — never an `rm`). Always `--dry-run` first; preflight-then-mutate; never commits. |
264
- | `/agent-workflow-kit velocity` | Claude Code · opt-in | **onboarding velocity profile** — seeds a fixed, audited **read-only** allowlist into `.claude/settings.json` so routine read-only commands stop idling on approval prompts while you're away; opt-in `acceptEdits`; plus a **read-only advisory** of likely project gate commands to add by hand. Writes **only** `.claude/settings.json` — **never** allowlists commit/push/publish, never writes `settings.local.json`, never commits. A seeded entry is a **trust posture, not a sandbox** (a runtime residual remains at the settings level — its guard ships as the opt-in `hook` command); a direct commit/push/publish still asks. `--dry-run` first. |
264
+ | `/agent-workflow-kit velocity` | Claude Code · opt-in | **onboarding velocity profile** — seeds a fixed, audited **read-only** allowlist into `.claude/settings.json` so routine read-only commands stop idling on approval prompts while you're away; every allowlist apply (flagless, `--kit-tools`, `--bridge-tier`) also seeds one `permissions.ask` rule, so the harness asks before every harness apply (the `apply:` line of each harness item the upgrade advisor renders) instead of judging it in its auto mode — a question added, it removes no prompt; opt-in `acceptEdits`; plus a **read-only advisory** of likely project gate commands to add by hand. Writes **only** `.claude/settings.json` — **never** allowlists commit/push/publish, never writes `settings.local.json`, never commits. A seeded entry is a **trust posture, not a sandbox** (a runtime residual remains at the settings level — its guard ships as the opt-in `hook` command); a direct commit/push/publish still asks. `--dry-run` first. |
265
265
  | `/agent-workflow-kit agents` | Claude Code · opt-in | **subagent vehicles** — places the five bundled subagent definitions into `.claude/agents/`: **four read-only vehicles + one full-tool `executor`**. **No read-only vehicle gets `Bash`**, and that is the load-bearing property: a read-only fan-out on a full-tool subagent shells out for facts it could have read, and every shelled command is an approval prompt you never needed to see. Three of the four ride a **cheap model** (haiku, low effort) for mechanical work — extraction sweeps, changelog fact-skeletons, gate-failure triage; the fourth, **`review-lens`**, is an ADDITIONAL independent read-only review opinion on code your configured backends have already seen (never a replacement for your plan or diff review recipe, advisory like every review; an epic is reviewed by the lens alone, whatever `epic.review` resolves to). The fifth, **`executor`** (all tools; model and effort from `docs/ai/vehicles.json`), is the ONE vehicle with a shell and the instrument behind the **Subagent** recipe: dispatched only for a bounded, file-disjoint execution / authoring / routine slice you verify afterwards — never for read-only work, never as a review backend, and it never commits. Its state (`placed` / `customized` / `unusable` / `missing`) is what a `subagent` slot's readiness resolves from. Writing code and running gates stay off the four read-only vehicles; the executor carries a bounded slice you re-verify on your main lane, and every vehicle's output is verified. Preview by default (`--apply` writes); an existing customized read-only vehicle or lens is **preserved, never overwritten**; the executor's body is re-derived from `docs/ai/vehicles.json` (a hand edit is replaced); never touches `settings*.json`, never commits. |
266
266
  | `/agent-workflow-kit hook` | Claude Code · opt-in | **gate-approval hook** — places a self-contained PreToolUse hook (`.claude/hooks/`) and wires it into `.claude/settings.json`: a Bash command **byte-identical** to a gate you declared in `docs/ai/gates.json` (run from the project root) is auto-approved — no prompt, no idle; a seeded read-only command carrying a runtime residual (output redirection, command substitution, `--output` writes) now **asks** even where an allow rule would have silently passed it (proven live — hook `ask` overrides an allow rule). Detection is a conservative string scan, so it over-asks on a byte that is ordinary text — searching for `=>`, or a read wearing `2>/dev/null`, prompts. That is a documented open limit: 4.1.0 built three mechanisms to narrow it and removed all three in review, each counterexample kept as a test (see Mode: hook). Exact matches only, never patterns; never `deny`; a broken `gates.json` only disables auto-approval, never the guard. An opt-in **read-lane** (`--read-lane` → `docs/ai/lanes.json`, a **separate** file from `gates.json`) additionally auto-approves *compounds* of seeded read-only commands carrying no shell metaprogramming; `--apply --read-lane` verifies the placed hook is current first (delete-to-reseed otherwise). Preview by default; never writes `settings.local.json`; never commits. |
267
267
  | `/agent-workflow-kit state-block-guard` | Claude Code · opt-in | **closing-block detector** — a `Stop` hook that reads the turn's final assistant message and warns when the closing state block is defective: the «what I need from you» slot answering *nothing* (a turn that ENDS always needs a resume, so the answer is false, not merely unhelpful), or a first-person promise of imminent work in a turn that is over. Judged on the slot's first clause, so a real ask followed by a clause break and "nothing else" passes, and a promise gated on something named passes; matching is word-bounded and Unicode-aware, quoted and fenced examples are stripped, and the last STARTED block decides — an incomplete tail counts as no block rather than falling back to an earlier one. A message carrying no block at all is silent unless you pass `--require-block` — this kit does not mandate the block, and a hook that runs every turn must not warn every turn. The judgement is lexical and the mode doc names every residual it leaves. **Detection, never prevention** — a `Stop` hook cannot un-send the message it judges; what it buys is that a silent recurrence becomes a visible one. Warns via `systemMessage` and **exits 0 on every path**: never `deny`, never blocks the stop, never re-enters the model. Reads nothing but the message, makes no network call, approves nothing. This mode is **read-only and has no writer** — it ships the self-contained hook and a paste-ready wiring block you merge into `.claude/settings.json` yourself. |
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.28.0'
6
+ version: '14.29.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-kit
package/capability.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "agent-workflow-kit",
5
5
  "kind": "composition-root",
6
- "version": "14.28.0",
6
+ "version": "14.29.0",
7
7
  "provides": [],
8
8
  "roles": {},
9
9
  "detect": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sabaiway/agent-workflow-kit",
3
- "version": "14.28.0",
3
+ "version": "14.29.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",
@@ -20,7 +20,7 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/recommendations.mjs --cwd <project-root> [--
20
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, and any apply slot that opens with HAND-APPLY whatever its lane, is the user's own step, handed over as worded. 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`. 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). An item marked **HAND-APPLY** (e.g. `enforcement` or `read-lane.stale`) is **the user's own step** — hand the user its rendered line together with its posture note; HAND-APPLY is **maintainer territory wherever it renders** — the apply slot or the `recipe:` line. For `worktrees-dir` the two classes COMPOSE in one item and the ORDER is part of the contract: its `recipe:` line is the HAND-APPLY grant advice, its apply one-liner is the dry-run ack preview, and you **wait for the maintainer to confirm the grant is applied (or the terminal fallback chosen)** before running that preview and the exact `--apply` command it prints — the ack RECORDS a choice that must already have been made, and against a trusted host NO the item renders no ack at all.
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
- The **consent route** for a harness item: run its `check:` line, show its result, take the user's yes on that result and run its `apply:` line with that result's digest, in the sandbox first. On exactly a write the OS refuses on a protected file, a writer's report that the sandbox masks its target and nothing was written (the mcp masked report), or the check's `masked:` line, say so and run that same line once more outside the sandbox, so the harness asks at the point of action and the check re-judges in the apply's own environment; never another line, never unseen. After a `masked:` check, the yes is taken on the outside result. 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.
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.
24
24
 
25
25
  **The `executor-vehicle` item (the carrier the project already CONFIGURED — beside the `agents` offer, never the same item).** It fires on ONE pair of facts: a slot in `docs/ai/orchestration.json` names `subagent` while the executor vehicle (`.claude/agents/executor.md`) is `missing` or `unusable` — so every such slot silently degrades to solo. That is a configured declaration that is broken, hence **`attention`**, not an offer; its text names the state, its reason and how many configured slots are affected across the six activities (`plan-authoring`, `plan-execution`, `routine`, `feedback-triage`, `epic`, `task`), covering the subagent-capable slots `plan-authoring.author`, `plan-authoring.fold`, `plan-execution.execute`, `routine.carrier`, `epic.author`, `task.author` and `task.execute`. **`placed` and `customized` render NOTHING** (a working vehicle is not a recommendation), a config naming no `subagent` slot renders nothing, and a config the validated reader refuses is a stated **skip**, never an item computed over a shape nothing accepted. It is deliberately DISTINCT from its two neighbours, neither of which can observe this state: the `agents` offer converges the moment nothing is left to PLACE — which a present-but-unusable executor also satisfies — and the review-recipe probe skips a slot configured `subagent` by construction. The agent applies it on the chat yes after the check; `init` previews the agents writer's own `cheap-agents.mjs` and applies it with `--apply` on the terminal yes as a route the user may take instead. Its `recipe:` line carries each precondition the advisor observes beside the apply slot, followed by the hidden-mode reconcile follow-up (`hide-footprint --reconcile`), for a hidden deployment only. Honest limit on convergence: the apply places an ABSENT vehicle, so it clears `missing`; it STOPs on a symlink, so on `unusable` the `recipe:` line names each refusal as a precondition; an executor whose frontmatter is invalid is re-derived by the apply itself. Risk profile: writes under `.claude/agents/` only — the executor's body re-derived from `docs/ai/vehicles.json`, every other vehicle create-only — never `settings.json`, never a commit.
26
26
 
@@ -14,11 +14,11 @@ The opt-in onboarding **velocity profile** — it seeds a fixed, audited **read-
14
14
 
15
15
  Run `node ${CLAUDE_SKILL_DIR}/tools/velocity-profile.mjs [--dry-run | --apply] [--kit-tools] [--bridge-tier] [--accept-edits] [--cwd <dir>]`:
16
16
 
17
- 1. **`--dry-run` first, always** (the default — changes nothing). It prints: the fixed read-only core it would add; a **read-only advisory** that lists your `package.json` `scripts` as **unaudited candidates you may add BY HAND** (inspect each first) to `.claude/settings.json` / `settings.local.json` — the tool **never** writes them and flags obviously-mutating names as "do not add"; any **pre-existing non-read-only `Bash(...)` entries** to consider removing by hand; and the honest residual notice (below). It STOPs (zero writes) on a symlinked `.claude` / non-regular `settings.json`, malformed settings JSON, or an unsafe `permissions.defaultMode` — `bypassPermissions` or anything outside `default`/`acceptEdits`/`plan`, present in **either** `settings.json` or `settings.local.json`.
17
+ 1. **`--dry-run` first, always** (the default — changes nothing). It prints: the fixed read-only core it would add; the apply ask rule it would add (step 3); a **read-only advisory** that lists your `package.json` `scripts` as **unaudited candidates you may add BY HAND** (inspect each first) to `.claude/settings.json` / `settings.local.json` — the tool **never** writes them and flags obviously-mutating names as "do not add"; any **pre-existing non-read-only `Bash(...)` entries** to consider removing by hand; and the honest residual notice (below). It STOPs (zero writes) on a symlinked `.claude` / non-regular `settings.json`, malformed settings JSON, or an unsafe `permissions.defaultMode` — `bypassPermissions` or anything outside `default`/`acceptEdits`/`plan`, present in **either** `settings.json` or `settings.local.json`.
18
18
  2. **Ask the opt-ins** via **`AskUserQuestion` where supported** — two separate choices, the safe option FIRST in each. First the **`--kit-tools` tier** (separate from `acceptEdits`; **"core only" remains the default answer** — seed the tier only on an explicit yes, then add `--kit-tools` to the `--apply` re-run). Then the `acceptEdits` opt-in:
19
19
  - **"Keep per-edit approval prompts (recommended)"** — seed only the read-only allowlist; file edits still prompt.
20
20
  - **"Auto-accept file edits (`defaultMode: acceptEdits`)"** — present the honest FULL posture: it auto-applies Edit/Write AND auto-runs `mkdir`/`touch`/`mv`/`cp` in the working dir, is paired with the read-only allowlist, and — stated plainly — a settings-level allow rule is a **trust posture, not a sandbox**: a read-only entry can still write a file via output redirection, and (Claude Code's allow rules do not inspect command substitution) could in principle run another command via `cmd $(…)`. velocity **never adds `commit`/`push`/`publish` as allow rules** — so a direct `git push` still ASKs — and the residual guard for the seeded core ships as the opt-in PreToolUse hook (`${CLAUDE_SKILL_DIR}/references/modes/hook.md`; probe-proven on the `--output` family — current engine builds already intercept redirection/substitution upstream). The approval **floor** (never auto-approved, with or without the tier): every writer `--apply`/`--write`/`--yes` still prompts; clobber-protection STOPs still stop; the three release asks (commit/push/publish) stay maintainer-owned. Note also that a `defaultMode` in `settings.local.json` would override this project-level write (local > project), since velocity writes only `.claude/settings.json`.
21
- 3. **Only on an explicit yes**, re-run with `--apply` (add `--accept-edits` only if they chose the second option). It merges-don't-clobber (preserves `includeCoAuthoredBy`, every key, and existing allow entries) and writes **only** `.claude/settings.json`.
21
+ 3. **Only on an explicit yes**, re-run with `--apply` (add `--accept-edits` only if they chose the second option). It merges-don't-clobber (preserves `includeCoAuthoredBy`, every key, and existing allow entries) and writes **only** `.claude/settings.json`. Every apply — flagless, `--kit-tools`, `--bridge-tier` — also appends one ask rule, `Bash(node *apply-danger-check.mjs*--apply*)`, to `permissions.ask` once, beside any ask entries already there: the harness then asks before every harness apply (the `apply:` line of each harness item the advisor renders) instead of judging it in its auto mode. It is a question added, never an allow, and it removes no prompt; `--autonomy` leaves it in place.
22
22
  4. **Surface delegation-readiness, read-only.** If they want a step run Delegated, set it with `/agent-workflow-kit set-recipe --set plan-execution.execute=delegated` (`${CLAUDE_SKILL_DIR}/references/modes/set-recipe.md`) or by hand-editing `docs/ai/orchestration.json`; **velocity itself never writes the orchestration config.**
23
23
 
24
24
  **The `--kit-tools` tier (opt-in).** On top of the read-only core, `--kit-tools` seeds an audited kit-tool tier — every path **resolved absolute from the running skill at seed time**, so the posture is **fail-safe**: a moved or reinstalled skill leaves a stale rule that simply **prompts again** (re-run velocity to reseed; nothing widens silently). A seeded entry equals its covered dispatch line below with `${CLAUDE_SKILL_DIR}` replaced by the resolved skill dir and `${PROJECT_ROOT}` by this project's resolved root, **UNQUOTED** — a quoted path is a different byte string and will still prompt (a dead rule, not a hole). The covered dispatch lines (this list IS the documented-invocation source):
@@ -6,7 +6,7 @@ import { homedir } from 'node:os';
6
6
  import { basename, dirname, isAbsolute, join, relative, resolve, win32 } from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import {
9
- CLAUDE_DIR, RENDER_OWNED_REDLINE_RULES,
9
+ APPLY_ASK_RULE, CLAUDE_DIR, RENDER_OWNED_REDLINE_RULES,
10
10
  SAFE_DEFAULT_MODES, SETTINGS_FILE, SETTINGS_LOCAL_FILE, UNIVERSAL_READONLY_ALLOWLIST,
11
11
  deriveKitToolsAllowlist, detectAtRoot, mergeAutonomySettings,
12
12
  writeAutonomyProfile, writeVelocityProfile,
@@ -165,6 +165,7 @@ export const scopeOf = (variant, facts, deps = {}) => {
165
165
  set('sandbox.filesystem.allowWrite', stateDirsOf(recipe.dirEntries, facts));
166
166
  }
167
167
  set('permissions.allow', allow);
168
+ set('permissions.ask', [APPLY_ASK_RULE]);
168
169
  } else if (variant === 'autonomy-render') {
169
170
  set('permissions.defaultMode', SAFE_DEFAULT_MODES);
170
171
  set('sandbox.enabled', [true]);
@@ -193,7 +194,8 @@ export const scopeOf = (variant, facts, deps = {}) => {
193
194
  return {
194
195
  admits: ({ path, key, change, value }) => {
195
196
  const correctFile = key.startsWith(`${SERVERS_KEY}.`) ? path === MCP_JSON_REL : path === SETTINGS_FILE;
196
- const gains = variant === 'bridge-tier' || key === 'hooks.PreToolUse' || key === ENABLED_KEY || key === CREDENTIALS;
197
+ const gains = variant === 'bridge-tier' || key === 'hooks.PreToolUse' || key === ENABLED_KEY || key === CREDENTIALS
198
+ || (VELOCITY_VARIANTS.includes(variant) && key === 'permissions.ask');
197
199
  return correctFile && (!gains || change === 'added') && Boolean(allowed[key]?.(value));
198
200
  },
199
201
  admitsPath: (path, directory = false) => directory ? directories.includes(path)
@@ -227,6 +229,7 @@ export const plannedChange = (variant, facts, deps = {}) => {
227
229
  let after = structuredClone(before);
228
230
  after.permissions = { ...before.permissions,
229
231
  allow: [...(before.permissions?.allow ?? []), ...plan.toAdd, ...plan.tierToAdd, ...plan.bridgeToAdd] };
232
+ if (plan.askToAdd.length) after.permissions.ask = [...(before.permissions?.ask ?? []), ...plan.askToAdd];
230
233
  if (plan.excludedToAdd.length || plan.hostsToAdd.length) {
231
234
  after.sandbox = { ...before.sandbox };
232
235
  if (plan.excludedToAdd.length) after.sandbox.excludedCommands = [...(before.sandbox?.excludedCommands ?? []), ...plan.excludedToAdd];
@@ -244,6 +244,8 @@ export const composeVerdict = ({ attention, optional, skipped }) => {
244
244
  // WHAT is never truncated: the leaf's suite pins every composed WHAT under the cap.
245
245
  export const WHATS = Object.freeze({
246
246
  'velocity-core': 'routine read-only commands still prompt — {n} audited read-only allowlist entr(ies) not seeded',
247
+ 'velocity-core.rule': 'in auto mode a harness apply is judged instead of asked — the apply ask rule is not seeded',
248
+ 'velocity-core.both': 'routine read-only commands still prompt, and in auto mode a harness apply is judged instead of asked — {n} entr(ies) not seeded',
247
249
  'kit-tools-tier': "the kit's own read-only tools still prompt — {n} kit-tools tier entr(ies) not seeded",
248
250
  'bridge-tier': 'bridge-wrappers tier incomplete — {n} entr(ies) missing (allow rules, exclusions, hosts or state dirs of used bridges)',
249
251
  'autonomy-policy': 'no {path} — the computed defaults apply implicitly (red-lines ask/deny; every activity floors at prompt)',
@@ -317,7 +319,7 @@ const templateBudget = (template) => ITEM_LINE_CAP - template.replace(/\{[A-Za-z
317
319
 
318
320
  // ── the frozen benefit registry (fact-true; pinned by tests) ────────────────────────────────────
319
321
  export const BENEFITS = Object.freeze({
320
- 'velocity-core': 'velocity — routine read-only commands stop prompting while the maintainer is away',
322
+ 'velocity-core': 'velocity — routine read-only commands stop prompting while the maintainer is away; the harness asks before each harness apply',
321
323
  'kit-tools-tier': "velocity — the kit's own read-only tools run promptless (audited, resolved-absolute tier)",
322
324
  'bridge-tier':
323
325
  'velocity — used bridges get <wrapper> * exclusions (codex-exec * if delegated), hosts, state dirs (allowWrite); reviews: code-mode allows',
@@ -442,8 +444,10 @@ const probeVelocityItems = ({ root, deps, add, skip }) => {
442
444
  // deliberately carries NO defensive catch (a dead branch is not honesty; the preflight catch
443
445
  // above owns the real failure modes).
444
446
  const core = planVelocityProfile(preflight, {});
445
- if (core.toAdd.length > 0) {
446
- add('velocity-core', fillTemplate(WHATS['velocity-core'], { n: core.toAdd.length }), applyLine(''));
447
+ if (core.toAdd.length > 0 || core.askToAdd.length > 0) {
448
+ const template = core.toAdd.length === 0 ? WHATS['velocity-core.rule']
449
+ : core.askToAdd.length === 0 ? WHATS['velocity-core'] : WHATS['velocity-core.both'];
450
+ add('velocity-core', fillTemplate(template, { n: core.toAdd.length + core.askToAdd.length }), applyLine(''));
447
451
  }
448
452
  try {
449
453
  const kt = planVelocityProfile(preflight, { kitTools: true });
@@ -39,6 +39,8 @@ export const WORKFLOW_STAMP = 'docs/ai/.workflow-version';
39
39
  export const SAFE_DEFAULT_MODES = Object.freeze(['default', 'acceptEdits', 'plan']);
40
40
  export const UNSAFE_BYPASS_MODE = 'bypassPermissions';
41
41
  export const ACCEPT_EDITS_MODE = 'acceptEdits';
42
+ // The harness asks before every harness apply; an ask rule allows nothing.
43
+ export const APPLY_ASK_RULE = 'Bash(node *apply-danger-check.mjs*--apply*)';
42
44
 
43
45
  // The audited read-only core. Every entry is a Claude Code Bash allow pattern whose command runs NO
44
46
  // mutating operation and NO inline arbitrary code execution through its OWN flags (verified
@@ -335,6 +337,8 @@ absolute path (args wildcard), run-gates.mjs as ONE exact project-root-pinned by
335
337
  (project-exec - it runs YOUR declared gates.json), source-size-check.mjs as ONE exact --check
336
338
  byte-string (its read-only mode only - its writers keep prompting), and the writers' exact arg-free
337
339
  dry-run preview byte-strings. Never touches settings.local.json.
340
+ Every apply also appends the apply ask rule ${APPLY_ASK_RULE} to permissions.ask
341
+ once, so the harness asks before every harness apply; it allows nothing.
338
342
  --bridge-tier (own consent) derives each entry for a used bridge from the effective project recipes:
339
343
  review wrappers' CODE mode (codex-review code, agy-review code), the quoted grounding pre-step rule,
340
344
  and <wrapper> * exclusions in sandbox.excludedCommands. An execute slot delegating to codex adds
@@ -408,8 +412,8 @@ const validateSettingsShape = (data, relPath) => {
408
412
  }
409
413
  // The --autonomy render merges into permissions.ask / permissions.deny (arrays) and the top-level
410
414
  // `sandbox` object — a malformed one of these is a STOP with ZERO writes, never a merge-through-clobber
411
- // (the same precondition posture as permissions.allow above). The allowlist mode never touches these,
412
- // so this only tightens the autonomy path.
415
+ // (the same precondition posture as permissions.allow above). The allowlist mode also appends the
416
+ // apply ask rule to permissions.ask, so its shape is a precondition for both paths.
413
417
  if (data.permissions?.ask !== undefined && !Array.isArray(data.permissions.ask)) {
414
418
  throw makeVelocityProfileError(VELOCITY_MALFORMED, `${relPath}: permissions.ask must be an array`);
415
419
  }
@@ -587,14 +591,17 @@ const assertTargetWritable = (absPath, deps = {}) => {
587
591
 
588
592
  const formatJson = (data, eol) => `${JSON.stringify(data, null, SETTINGS_JSON_INDENT).replace(JSON_NEWLINE_PATTERN, eol)}${eol}`;
589
593
 
590
- const mergeProjectSettings = (projectData, toAdd, acceptEdits, excludedToAdd = [], hostsToAdd = []) => {
594
+ const mergeProjectSettings = (projectData, toAdd, acceptEdits, excludedToAdd = [], hostsToAdd = [], askToAdd = []) => {
591
595
  const base = projectData ?? {};
592
596
  const permissions = getPermissions(base);
593
597
  const allow = getAllowEntries(base);
594
598
  const mergedAllow = [...allow, ...toAdd.filter((entry) => !allow.includes(entry))];
599
+ const ask = permissions.ask ?? [];
600
+ const newAsk = askToAdd.filter((entry) => !ask.includes(entry));
595
601
  const mergedPermissions = {
596
602
  ...permissions,
597
603
  allow: mergedAllow,
604
+ ...(newAsk.length ? { ask: [...ask, ...newAsk] } : {}),
598
605
  ...(acceptEdits === true ? { defaultMode: ACCEPT_EDITS_MODE } : {}),
599
606
  };
600
607
  // The bridge tier's second and third surfaces: sandbox.excludedCommands and
@@ -626,6 +633,9 @@ const formatAllowlist = (result) => [
626
633
  `${result.wrote ? 'added' : 'would add'} read-only core entries: ${result.toAdd.length}`,
627
634
  ...formatEntryList(result.toAdd),
628
635
  `already present: ${result.alreadyPresent.length}`,
636
+ `${result.wrote ? 'added' : 'would add'} permissions.ask entries: ${result.askToAdd.length}`,
637
+ ...formatEntryList(result.askToAdd),
638
+ `already present (ask): ${result.askAlreadyPresent.length}`,
629
639
  ];
630
640
 
631
641
  // The tier's honest posture, printed on every --kit-tools run: run-gates is project-exec (never
@@ -979,6 +989,9 @@ const deriveUsedBridgeTier = (root, deps = {}) => {
979
989
  export const planVelocityProfile = (preflight, opts = {}) => {
980
990
  const { acceptEdits, kitTools, bridgeTier } = opts;
981
991
  const projectAllow = getAllowEntries(preflight.projectSettings?.data);
992
+ const projectAsk = getPermissions(preflight.projectSettings?.data).ask ?? [];
993
+ const askToAdd = projectAsk.includes(APPLY_ASK_RULE) ? [] : [APPLY_ASK_RULE];
994
+ const askAlreadyPresent = projectAsk.includes(APPLY_ASK_RULE) ? [APPLY_ASK_RULE] : [];
982
995
  const toAdd = UNIVERSAL_READONLY_ALLOWLIST.filter((entry) => !projectAllow.includes(entry));
983
996
  const alreadyPresent = UNIVERSAL_READONLY_ALLOWLIST.filter((entry) => projectAllow.includes(entry));
984
997
  const tier = kitTools === true ? deriveKitToolsAllowlist({ projectDir: preflight.cwd }) : [];
@@ -1011,6 +1024,8 @@ export const planVelocityProfile = (preflight, opts = {}) => {
1011
1024
  return {
1012
1025
  toAdd,
1013
1026
  alreadyPresent,
1027
+ askToAdd,
1028
+ askAlreadyPresent,
1014
1029
  tierToAdd,
1015
1030
  tierAlreadyPresent,
1016
1031
  kitTools: kitTools === true,
@@ -1074,6 +1089,7 @@ export const writeVelocityProfile = ({ cwd, acceptEdits = false, dryRun = true,
1074
1089
  acceptEdits,
1075
1090
  plan.excludedToAdd,
1076
1091
  plan.hostsToAdd,
1092
+ plan.askToAdd,
1077
1093
  ), plan.dirsToAdd);
1078
1094
  fs.writeFile(settingsPath, formatJson(merged, preflight.projectSettings.eol ?? LF), UTF8);
1079
1095
  return { wrote: true, dryRun: false, settingsPath, ...resultBase };