### Mode: velocity

<!-- opt-in-capability: velocity-core -->
<!-- opt-in-capability: kit-tools-tier -->
<!-- opt-in-capability: bridge-tier -->
<!-- opt-in-capability: autonomy-render -->
<!-- opt-in-capability: sandbox-lane -->
<!-- opt-in-capability: accept-edits -->

Requires: ${CLAUDE_SKILL_DIR}/references/shared/command-shapes.md

The opt-in onboarding **velocity profile** — it seeds a fixed, audited **read-only** Claude Code allowlist into `.claude/settings.json` so an agent stops idling on approval prompts for routine read-only commands while the maintainer is away. It is the family's **first programmatic `.claude/settings.json` writer** (attribution stayed an agent-driven prose merge). **In-agent, opt-in, writes only `.claude/settings.json`**, on one hard rule: **it never allowlists `commit`/`push`/`publish`** — so a direct commit/push/publish still ASKs; the only caveat is the trust-posture residual (below) — its closure is **shipped, opt-in: `${CLAUDE_SKILL_DIR}/references/modes/hook.md`**.

**Version-status routing (like the other writer modes):** read `docs/ai/.workflow-version` first — not-deployed → bootstrap; stamp < `3.0.0` → `upgrade`; stamp > head / unparseable → STOP. The tool enforces this in code too (`--apply` STOPs unless the stamp is the lineage head).

Run `node ${CLAUDE_SKILL_DIR}/tools/velocity-profile.mjs [--dry-run | --apply] [--kit-tools] [--bridge-tier] [--accept-edits] [--cwd <dir>]`:

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`.
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:
   - **"Keep per-edit approval prompts (recommended)"** — seed only the read-only allowlist; file edits still prompt.
   - **"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`.
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`.
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.**

**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):

- `node ${CLAUDE_SKILL_DIR}/tools/recipes.mjs --active-line` — and any other args (wildcard)
- `node ${CLAUDE_SKILL_DIR}/tools/procedures.mjs <activity>` (wildcard)
- `node ${CLAUDE_SKILL_DIR}/tools/family-registry.mjs --json` (wildcard)
- `node ${CLAUDE_SKILL_DIR}/tools/detect-backends.mjs` (wildcard)
- `node ${CLAUDE_SKILL_DIR}/tools/commands.mjs` (wildcard)
- `node ${CLAUDE_SKILL_DIR}/tools/review-state.mjs --check` (wildcard)
- `node ${CLAUDE_SKILL_DIR}/tools/recommendations.mjs --cwd ${PROJECT_ROOT}` (wildcard — the read-only deployment advisor; its rendered apply one-liners are writers and still prompt)
- `node ${CLAUDE_SKILL_DIR}/tools/manifest/validate.mjs --strict <skill-dir>` (wildcard)
- `node ${CLAUDE_SKILL_DIR}/tools/release-scan.mjs <path>` (wildcard)
- `node ${CLAUDE_SKILL_DIR}/tools/repo-search.mjs --pattern <literal> --path <p>` (wildcard — the LITERAL search lane; for a pattern carrying a shell-significant byte use `--pattern-file <p>` instead, and for a TARGET PATH carrying one use `--paths-file <p>` — either way the bytes never enter the command string. one of the TWO tier tools that carry hook residual coverage — see the honesty note below)
- `node ${CLAUDE_SKILL_DIR}/tools/path-inventory.mjs --path <p>` (wildcard — the read-only inventory lane: exists / type / size / `wc -l` lines / one-level listing / small-file contents for N paths in ONE call, so small path questions never need a composed shell. `--paths-file <p>` is its out-of-band target lane; hook residual coverage as with repo-search)
- `node ${CLAUDE_SKILL_DIR}/tools/run-gates.mjs --cwd ${PROJECT_ROOT}` — **EXACT byte-string only**, and honestly **project-exec, not read-only**: it runs YOUR declared `docs/ai/gates.json` commands — the same trust boundary the opt-in hook grants byte-exact per-cmd. A wildcard would be BROADER than that boundary (`--cwd <dir>` executes another project's declared gates), so the bare cwd-defaulting form, any other `--cwd`, `--only`, and **`--final`** forms all still prompt (`--final` WRITES the final-run receipt into the core-evidence store — a recording run is never auto-approved).
- `node ${CLAUDE_SKILL_DIR}/tools/source-size-check.mjs --check` — **EXACT byte-string only**, and only this READ-ONLY mode: the tool's `--write-baseline` and `--adopt` modes WRITE (the recorded ratchet, and a gate declaration) and are deliberately uncovered — they still prompt, always. Every other form, `--cwd` included, keeps prompting. **What it covers, precisely:** the agent's own direct invocation. It is **not** the byte-string a DECLARED gate carries — the fill emits that path double-quoted (a kit path with a space must survive), and a seedable allow rule may carry no quotes at all, so the two spellings cannot be one string; a declared gate is auto-approved by the opt-in hook instead, byte-exact against `docs/ai/gates.json`.
- Writer previews, **exact arg-free dry-run byte-strings only** (the SEEDED tier byte-string is the arg-free preview of each): `node ${CLAUDE_SKILL_DIR}/tools/velocity-profile.mjs` · `node ${CLAUDE_SKILL_DIR}/tools/cheap-agents.mjs` · `node ${CLAUDE_SKILL_DIR}/tools/gate-hook.mjs` — every `--apply`/`--write`/`--yes` still prompts, always. (`gate-hook` also has a **`--read-lane`** flagged preview — the opt-in read-only compound lane, `${CLAUDE_SKILL_DIR}/references/modes/hook.md`; that flagged form is NOT the seeded arg-free byte-string, so it may **prompt once** — it IS a consent flow, stated, no silent cap.)

Honesty notes: tier entries get **NO PreToolUse-hook residual coverage — with TWO deliberate exceptions, `repo-search.mjs` and `path-inventory.mjs`**, whose prefixes are in the hook's scanned list because they take caller-supplied argument bytes: a redirection or substitution on THEIR invocation raises the ask, whose reason names that tool's out-of-band lane (`--pattern-file` / `--paths-file`) — context for the HUMAN deciding, not a message to the caller. Neither is in the seeded core, so neither inherits the read-lane allow. Every other tier entry: the opt-in hook's residual ask-net guards only the seeded read-only CORE prefixes, so the tier rides the same settings-level residual posture as the core (redirection / command substitution are not inspectable at the settings layer; see the residual notice). A skill or project path that cannot survive UNQUOTED in a byte-exact rule (spaces, metacharacters, non-POSIX) **STOPs the tier up front with a clear error** — nothing is seeded. Anything you want covered beyond the tier — such paths, this repo's own relative-path spellings, other tools — stays a **BY-HAND add** to your settings, with the path your project actually reaches the kit by. Pre-existing `node …` allow entries that do NOT match the seeded tier byte-forms stay flagged by the advisory for hand review.

**Invariants:** creates `.claude/` if absent and writes **only** `.claude/settings.json` (no other file); **never** allowlists commit/push/publish; **never** writes `settings.local.json`; never commits; opt-in `acceptEdits`, never silent.

**Every settings-derived RUNTIME claim on this mode's surfaces is host-conditional.** Whether a host applies the `sandbox.*` settings keys is **not knowable from here** — a settings-native host honors them; an IDE/session-imposed sandbox was observed ignoring hand-applied `sandbox.network.allowedDomains` / `sandbox.filesystem.allowWrite` in BOTH scopes. So every statement about what a settings key DOES at run time is stated **`where the host honors the settings sandbox keys`**, never promised — the bridge tier's exclusion routing; the autonomy render's `allowedDomains` / `allowWrite` / `allowUnsandboxedCommands` / `excludedCommands` degrade lines over FOREIGN keys; and equally the render's claims about the keys it writes ITSELF (the `sandbox:` line, the `fs_outside_repo` confinement note, the network prompt-on-egress and credentials-coverage degrades), which is where a clean deployment reads a promise. The qualifier rides every such line, and one notice naming the unknown closes **every** `--autonomy` render, clean settings included. A malformed `allowWrite` (not an array, or an entry that is not a non-empty string) is reported as unreadable rather than assumed empty — as UNVERIFIABLE, never as a weakening: nothing may be claimed about what a value the tool cannot read would widen.

**The boundary of that qualifier, stated so it is not guessed.** It covers claims about `sandbox.*` keys — the block whose honouring was observed to vary by host. It deliberately does NOT cover `permissions.*` semantics (an allow rule auto-approving a command, `local > project` precedence for a non-sandbox key): that is the harness's own documented permission model, the layer this whole mode rests on, and hedging it would say nothing true. **Stated residual:** which sentences count as settings-derived runtime claims is decided by review, not by a checker — deciding whether a sentence asserts a runtime effect is a question about prose meaning, and this family deliberately has no mechanism that judges that (the same limit that retired the byte-scanning deny rung). A new claim-bearing line therefore has to carry the qualifier by authorship; the acceptance criterion is that every line stating what a `sandbox.*` key DOES carries it, and the tests pin the surfaces that exist today. What does NOT change is the CLASSIFICATION: which red-line a key would weaken, and that a proven bridge-tier exclusion is a note rather than a weakening, are properties of the declaration and stay flat statements.

**The `--bridge-tier` (own opt-in, AD-044).** Seeds what a promptless council review run needs — BOTH surfaces: `permissions.allow` prefix rules AND the wrapper names in `sandbox.excludedCommands` (where the host honors the settings sandbox keys the harness runs an excluded command OUTSIDE the sandbox — the wrappers need network — so a plain allowlisted invocation triggers no sandbox-bypass approval; where it does not, the wrapper starts sandboxed and simply prompts or fails loudly — fail-safe, never a silent widening). Both land in the **project** `.claude/settings.json` — the file this writer owns; an exclusion placed only in `settings.local.json` was live-observed NOT to route the command outside the sandbox (2026-07-11: the wrapper then starts sandboxed and dies on a read-only HOME + a network prompt), so hand-wiring the local file is not a working substitute for this tier. **Honesty note:** a session whose sandbox is imposed by the harness runtime itself (e.g. an IDE-managed session sandbox) may ignore settings-level exclusions entirely — there the wrappers need the session-level sandbox config (or a per-run consented bypass); the tier's seeded posture is correct for the settings-native sandbox and simply prompts again elsewhere (fail-safe, never a silent widening). Each bridge's observed egress hosts are declared in its `capability.json` `networkHosts` — the single documentation source (observed-minimal — a blocked host names itself at run time; read the manifests, this doc deliberately retypes no host list). The kit **never seeds** `sandbox.network.allowedDomains` or `sandbox.filesystem.allowWrite` (bridge council 2026-07-11, both backends concur): a network pre-allow widens egress for EVERY sandboxed command, and a write allowance on CLI state dirs (`~/.codex`, `~/.gemini/…`) would make credential dirs writable to every sandboxed command. Per-bridge picture under a harness-managed sandbox (live-observed 2026-07-11/12): an IDE-managed session sandbox ignores hand-applied `sandbox.network.allowedDomains` / `sandbox.filesystem.allowWrite` in BOTH settings scopes — its own per-host network consents govern egress; the durable zero-prompt lanes there are the session/host sandbox config (hosts from `networkHosts` + the CLI state-dir writes) or the per-run consented bypass. codex additionally needs a writable HOME (EROFS `~/.codex`); note the apex-vs-wildcard nuance — an apex domain is NOT covered by its `*.`-wildcard form, so the manifests carry both forms where observed (the blocked host names itself at run time). The upgrade Recommendations advisor surfaces exactly this recipe (hosts ∪ resolved `writableDirs`) as the `sandbox-lane` discoverability item — a consent-gated **ack writer** converging on a neutral fingerprint acknowledgement recorded into the family-owned `docs/ai/acks.json` (the kit still never seeds the security keys — those stay hand-apply), with the posture notes at the consent moment (`${CLAUDE_SKILL_DIR}/references/modes/recommendations.md`). Membership is the FROZEN review-wrapper constant, **never** the execution/probe wrappers (`codex-exec`, `agy-run` keep their human prompt — delegated execution is not covered by this consent; codex-exec's nested-sandbox recovery is the canon's observed-failure lane, not a preemptive tier seed), and only the **`code` review mode** — a `plan`/`diff` invocation takes a file argument that can point OUTSIDE the repo, so those modes keep their prompt; each wrapper entry derives ONLY when its bridge is **placed on PATH** (an absent bridge is a stated skip). The seeded byte-forms (this list IS the documented-invocation source for the bridge tier):

- `Bash(codex-review code:*)` — the code-mode prefix, args wildcard; plus `codex-review` in `sandbox.excludedCommands`
- `Bash(agy-review code:*)` — the code-mode prefix, args wildcard; plus `agy-review` in `sandbox.excludedCommands`
- `Bash(node "${CLAUDE_SKILL_DIR}/tools/grounding.mjs":*)` — the grounding pre-step, **double-quoted absolute path**: exactly the byte-form the `procedures` advisor renders, so the pasted pre-step matches the rule (seeded↔rendered byte-parity). It derives ONLY when `agy-review` is placed (the pre-step exists for agy's grounded reviews; codex grounds natively) — a codex-only install never auto-allows this writer. The quoted form is accepted for THIS one tool only — never a general quote allowance; an unseedable kit path (spaces/metacharacters) is a stated skip. Its writer surface is closed by the tool's own guard: `--out` accepts only system-temp scratch (rewritable) or a FRESH gitignored in-repo path (create-only, exclusive write) — tracked, in-repo-not-ignored, EXISTING in-repo (even gitignored), outside-repo non-temp, and symlink/non-regular destinations are all refused.

**Consented posture (printed on every `--bridge-tier` run, stated not pretended away):** an auto-allowed review wrapper runs **UNATTENDED** — it reads any repo file it is pointed at and sends the assembled payload to its **subscription backend**; prefix rules cannot inspect arguments, so a code-mode argument that names a readable file (agy's `--facts`/`--decided`) rides the same consent. That is the tier's purpose (unattended council review runs) and its residual. Tier entries get no PreToolUse-hook coverage — the same documented residual class as the autonomy red-line rules. The grounding entry's writer surface is bounded by `grounding.mjs`'s own scratch-destination guard (a tracked or in-repo-not-ignored `--out` is refused by the tool itself).

**Invocation shape (why a run still prompts):** a prefix allow rule matches only a **PLAIN invocation starting with the wrapper name** — `codex-review code`, `agy-review code --facts @f`. An env-var prefix (`AGY_PROBE=1 agy-review …`) or a compound chain (`agy-review … && …`; `;`-joined statements) can NEVER match a prefix rule (observed live); output redirects are fine. Drive the wrappers as plain single commands.

**Read-side invocation shape (why routine reads prompt, and the fix).** The same rule binds EVERY prefix allow entry, seeded read-only core included: a **compound chain** (`grep … && …`, `;`/`|`-joined reads), an **env-var prefix** (`FOO=bar grep …`), a **`$`/`${…}`/`$(…)` expansion**, or an **inline `node -e '…'`** never matches a prefix rule — so read *compounds* keep prompting at the settings layer even when each command is individually seeded. The mechanism that clears them is the opt-in **read-lane** in the PreToolUse hook (`${CLAUDE_SKILL_DIR}/references/modes/hook.md`): it auto-approves a compound whose every separator-split segment is a plain seeded read-only core command with **zero shell metaprogramming**. A scripted probe stays allow-listable by being a **WRITTEN scratch file run as `node <file>`** (a byte-exact declared gate, or a kit-tool tier entry) — **never** `node -e '…'`, which is unclassifiable and always prompts.

**Exit codes:** `0` done / dry-run; `1` a precondition STOP (stamp not current, unsafe mode, malformed settings, symlinked `.claude` / non-regular target); `2` bad arguments.

---

## The `--autonomy` render (separate mode — AD-044)

`--autonomy` is a **separate mode** from the allowlist seeding above: it renders the per-project autonomy policy (`docs/ai/autonomy.json`, written by `set-autonomy` — `${CLAUDE_SKILL_DIR}/references/modes/set-autonomy.md`) into the `.claude/settings.json` blocks it **owns** — the `sandbox` block, `permissions.ask`/`permissions.deny` red-lines, and `permissions.defaultMode`. It is **policy-only**: it never seeds the read-only allowlist and leaves `permissions.allow` untouched as a value. Run **`node ${CLAUDE_SKILL_DIR}/tools/velocity-profile.mjs --autonomy [--apply] [--cwd <dir>]`**:

1. **Preview by default** (writes nothing); **`--apply`** merges the render-owned blocks into `.claude/settings.json` (merge-don't-clobber — foreign top-level keys, existing `permissions.allow`, and foreign ask/deny entries preserved; a policy flip MOVES a red-line rule between ask/deny rather than duplicating it). It **refuses an absent policy** loudly (seed one first with `set-autonomy`). It **never writes `settings.local.json`**; a `settings.local.json` `defaultMode` that would mask the render is reported (local > project).
2. **`--autonomy --check`** is a **read-only drift gate**: it recomputes the render and compares against the live render-owned blocks — exit `0` in sync, exit `1` on drift (naming the exact key). A hand-edit OUTSIDE those blocks never flags.
3. **Version-PROBED, never version-pinned.** The render reads the INSTALLED harness version at run time (`probeHarnessVersion` — a read-only PATH resolution, never a spawn) and names only what it observed. Sandbox keys are `sandbox.enabled` + `sandbox.autoAllowBashIfSandboxed`; `sandbox` level ⇒ auto-allow + `defaultMode: acceptEdits`, `prompt` level ⇒ auto-allow OFF + `defaultMode: default` (the sandbox stays enabled as a confine floor either way). Red-lines use the argument-matching `:*` wildcard (`Bash(git commit:*)` / `Bash(git push:*)` / `Bash(npm publish:*)`). Three branches, all loud:
   - **Observed and supported** — `sandbox.credentials` IS rendered (and merged into settings.json, and drift-checked). Coverage is stated PARTIAL: env vars only. File credentials (`~/.ssh`) are **not** rendered, because that entry shape was never verified against an installed build — declare `sandbox.credentials.files` yourself if you need it. A `credentials=ask` policy degrades loudly too: the schema offers deny/mask and no ask mode, so `ask` is never quietly upgraded.
   - **Observed but older than the capability** — no key rendered, degrade names **the observed version**.
   - **Version unresolvable** — no key rendered, degrade STATES the unknown and its cause. Never a guess in either direction.
   A **network hard-block** and a **prompt-on-outside-write** are not expressed by this render at all (stated as such, without claiming a platform limit it did not observe). A missing Linux dependency (`socat`/`bwrap`) or an unsupported platform degrades the WHOLE sandbox to unsandboxed — the render still lands the red-lines + `defaultMode` and caveats that ad-hoc scripts will still prompt.
4. **`--autonomy` cannot combine** with `--accept-edits`, `--kit-tools`, or `--bridge-tier` (allowlist-mode flags) — a loud usage error.

**Autonomy invariants:** policy-only (never seeds the allowlist, leaves `permissions.allow` untouched) · writes only `.claude/settings.json` (never `settings.local.json`) · refuses an absent policy · merge-don't-clobber · **names only the version it observed, and states the unknown when it observed none** · degrades loudly wherever the render cannot express a red-line · previews by default · never commits.
