dw-init — scaffold a repo for the solo lane
Everything the loop assumes a repo has, written in one gated pass: the .ai/ state the skills read
and write, the durable homes they promote into, the guardrail hooks, and a settings file whose
allow-list is derived from the project rather than guessed.
What it writes
| Path |
Tracked? |
Purpose |
.ai/work/ |
tracked |
one folder per change (dw-shape writes CHANGE.md) |
.ai/README.md |
tracked |
what .ai/ is and who owns it |
.ai/backlog/ + its README.md |
tracked |
one file per follow-up, between changes |
.ai/archive/ + its README.md |
tracked |
landed change docs — history, not guidance |
docs/decisions/ + its README.md |
tracked |
durable decision records (dw-land promotes here) |
CONTEXT.md |
tracked |
the project's glossary — terms only |
VERIFY.md |
tracked |
how to start this project and drive a feature by hand |
AGENTS.md |
tracked |
the one always-loaded file: rules, commands, Task Router |
CLAUDE.md |
tracked |
a symlink to AGENTS.md — never a second copy |
scripts/check-agents-docs.mjs |
tracked |
the gate on that file's budget, router and commands |
.claude/settings.json |
tracked |
permissions (ask + deny + derived allow) and hook wiring |
.claude/hooks/*.sh |
tracked |
the guardrail scripts those settings reference |
.worktreeinclude |
tracked |
gitignored files a fresh worktree should carry in |
.github/PULL_REQUEST_TEMPLATE.md |
tracked |
the PR body shape dw-land fills and GitHub prefills |
.gitignore |
tracked |
a managed marker block for the personal files |
.husky/ + .lintstagedrc.json |
tracked, optional |
the pre-commit twin of the hooks — only when opted in |
Deliberately absent: .ai/verify/ and .ai/handoffs/ — the solo lane has one thin closing pass
that writes no artifact, and no one to hand off to. The root VERIFY.md is not that folder: one
file saying how to drive the project, written once and read by every change, never a per-change
artifact. Also absent: CLAUDE.local.md. Agent memory
is tracked AGENTS.md, because a gitignored file survives neither a fresh clone nor a
git worktree, and a second always-loaded file forks the corpus in two — docs/decisions/ in a
scaffolded repo carries the record. Personal, cross-project notes belong in ~/.claude/CLAUDE.md.
.gitignore still names CLAUDE.local.md so a stray one is never committed, and the hooks still
read it when they find one, so a repo scaffolded before this keeps working untouched.
docs/agents/ ships with its README — the layer's contract, and the file declaring its per-file
budget — and no topic files. The Task Router ships with rows for what the scaffold actually creates,
and the topic layer grows only when dw-land promotes into it: a new topic file and its router row in
the same commit, with the corpus baseline re-recorded beside them.
Templates come from ${CLAUDE_PLUGIN_ROOT}/templates/ — this lane's own payload.
(${CLAUDE_PLUGIN_ROOT} is the env var Claude Code substitutes to this plugin's install dir.)
Workflow
1. Detect — never assume the stack
- Repo root (
git rev-parse --show-toplevel) and default branch
(git symbolic-ref --short refs/remotes/origin/HEAD, else init.defaultBranch, else main).
- Test / lint / typecheck commands from the manifests actually present —
package.json scripts,
Makefile, pyproject.toml, go.mod. Read the real commands; don't invent them. Keep this
list — it becomes the permissions.allow entries in step 4, and a command you didn't find here
must not appear there.
- Pre-commit signals for step 5: formatter and linter deps/configs, a
test/typecheck script —
the detection table in references/precommit.md.
- What already exists:
AGENTS.md, CLAUDE.md (a real file? a symlink? to what?), a legacy
CLAUDE.local.md, .claude/settings*, .gitignore, CONTEXT.md, docs/. This is rarely a
greenfield tree, and step 3 must diff against reality. A repo already carrying a real CLAUDE.md
with content is the case to slow down on — it becomes AGENTS.md plus a symlink, which is a
rename the user has to approve at the gate, not a clobber.
2. Pick the hooks
Six are always offered because they're stack-agnostic: bash-guard (the PreToolUse dispatcher —
one stdin parse, then it spawns each guard only when the command could trip it),
block-dangerous-commands, block-env-access, enforce-commit-hygiene, credential-leak-guard
and large-file-guard. Add the JS/TS ones only where that stack is actually present:
block-non-pnpm, lint-on-edit, typecheck-on-commit (the declared typecheck before a commit
that stages TS — its per-turn predecessor cost a measured 4-5 s at every turn end). On a stack
with no lint or typecheck hook, offer the six alone and say the rest are stack-specific rather
than silently writing nothing.
guard-plugin-canon is shape-specific, not stack-specific — offer it only where step 1 found a
plugins/ directory whose entries are symlinks back into the tree. It refuses an edit aimed through
one of those links and names the canon instead, so on a repo with no such layout it is a hook that
can never fire.
3. HARD STOP — show what you're about to write
List every path, marked tracked or ignored, with a diff for anything that already exists.
Add three things that aren't paths: the permissions.allow list derived in step 1, so what the
agent may run without asking is approved rather than assumed; the optional pre-commit offer
(step 5), so the one gate covers it; and, when the repo already has a real CLAUDE.md, the
git mv CLAUDE.md AGENTS.md rename it needs, spelled out — that one moves a file the user wrote and
must never be inferred from silence. Wait for explicit confirmation. Scaffolding mutates the repo
and a wrong clobber is expensive — this gate is not optional even though the rest of the lane is
light.
4. Write
mkdir -p .ai/work .ai/backlog .ai/archive docs/decisions docs/agents; seed .ai/work with
.gitkeep (the other four get READMEs). Remove a .gitkeep that a README now supersedes — an earlier version
seeded docs/decisions/.gitkeep, so a repo scaffolded then and re-run now keeps a redundant one
beside the README, and the next reader cannot tell which is the convention.
.ai/README.md — copy ${CLAUDE_PLUGIN_ROOT}/templates/work-README.md verbatim. It states the
lifecycle a reader gets wrong: a CHANGE.md leaves work/ at merge — archived, never deleted.
.ai/backlog/README.md, .ai/archive/README.md and docs/decisions/README.md — copy
${CLAUDE_PLUGIN_ROOT}/templates/backlog-README.md,
${CLAUDE_PLUGIN_ROOT}/templates/archive-README.md and
${CLAUDE_PLUGIN_ROOT}/templates/decisions-README.md verbatim.
docs/agents/README.md — copy ${CLAUDE_PLUGIN_ROOT}/templates/agents-docs-README.md verbatim; the
template's router row points at it, so the two arrive together or path sync fails on the first run.
Existing entries in any of the three dirs are left alone — they carry real content from
earlier changes.
A legacy single-file .ai/BACKLOG.md, if present, is named at the gate: offer to split it into
per-file entries, never clobber or silently keep it.
CONTEXT.md — if absent, create it with a one-line purpose statement (this project's glossary;
terms only, no implementation detail) and a Term budget: **90 lines / 7 KB**. line, which caps the
file and holds each term to one bullet of two lines. Nothing else. If it exists, leave it alone.
VERIFY.md — if absent, create it the same way, and write exactly this much: a one-line purpose
statement, the two invariants below, then the three headings with one line under each naming what
belongs there. It holds what a green suite does not — Launch (the command that starts this
project, on what port, with what disposable state), Doctor (one read-only check answering "is
this instance worth driving?"), Drive (the commands, each paired with the observable result it
should produce). The invariants go in before any of that is filled, both learned the hard way:
cleanup removes instances and scratch state but never the evidence, and kill what you started
rather than killing by process name. Filling the three sections is not this skill's job, and a
project with nothing to launch leaves those lines as written — that is a finished VERIFY.md, not a
stub owed to anyone. A library, a docs repo or a skills catalog has no instance to drive, and an
honest empty section beats an invented command every session pays for. If it exists, leave it alone.
${CLAUDE_PLUGIN_ROOT}/templates/settings.json → .claude/settings.json; prune the hook
entries not selected, add the permissions.allow list (below), then confirm the file still parses
as valid JSON.
AGENTS.md — if absent, render ${CLAUDE_PLUGIN_ROOT}/templates/AGENTS.md, substituting
{{PROJECT_NAME}} {{DEFAULT_BRANCH}} {{STACK}} {{TEST_COMMAND}} {{LINT_COMMAND}}
{{TYPECHECK_COMMAND}} {{COMMIT_PATTERN}} {{COMMIT_TRAILER}} {{BOOTSTRAP_COMMAND}}
{{HOOKS_INSTALLED}}
{{AGENTS_CHECK_COMMAND}}. Every placeholder gets
a value or the line goes — a {{…}} left in the file is read as content by the next session and
evaled as a command by the hooks, which is why they carry an explicit guard against exactly
these tokens.
- The commands come from step 1 verbatim;
none where the manifests had none. Write none,
not a plausible guess and not _(none detected)_: the hooks read none as "skip", and a command
that doesn't exist fails on every edit.
{{COMMIT_PATTERN}} and {{COMMIT_TRAILER}} are policy, not detected commands, so ask rather
than probe: derive the pattern from the log the repo already has (git log --format=%s -30) and
propose it, and write none for the trailer unless the user says every commit must carry one.
Both defaults live in enforce-commit-hygiene.sh — Conventional Commits for the pattern, none
for the trailer — so an honest none is always a safe answer here.
{{LINT_COMMAND}} is the per-file form (pnpm exec eslint --fix, ruff check --fix), since
lint-on-edit appends one path to it — not the whole-project script, which would re-lint the
tree on every edit. Where the project only has the whole-project form, say so at the gate and
write none rather than wiring a slow hook.
- Prune the Task Router row for anything this run didn't create. The template ships a row
pointing at
.claude/hooks/, and that directory only exists if step 2 selected a hook — so a
scaffold that declined them all fails its own agents:check on its first run, because path sync
requires every routed path to exist. Same rule for any row whose target you skipped. A row is a
promise that the file is there.
- Idempotency is per-section: if
AGENTS.md already exists, leave it alone and report which of the
template's sections it is missing. Never merge a rendered template into a file someone wrote.
scripts/check-agents-docs.mjs — copy ${CLAUDE_PLUGIN_ROOT}/templates/check-agents-docs.mjs
verbatim. Zero dependencies, Node built-ins only, and it finds the repo root by walking up from its
own location to the nearest AGENTS.md — so scripts/ is the conventional home, not a required
one. It checks the always-loaded contract — the declared budget, that no {{…}} placeholder
survived, Task Router coverage and path sync, that every pnpm <script> named in AGENTS.md exists,
that CLAUDE.md is a symlink — plus the size of the layers it routes to: a Ceiling: per decision
record, a Topic budget: per topic file, a Term budget: over CONTEXT.md, and a word ratchet over
docs/agents/ as a whole. The three declared caps are opt-in, and each switches on the one
shape rule a number cannot express for its layer — no dated gotcha bullet, no term over two lines;
a missing declaration is neither checked nor mentioned, because a gate that lights an existing
folder red on install day is one you switch off rather than meet. The ratchet is the opt-out one:
it records what a corpus already is, so it is green the day it is seeded, and a repo switches it off
by deleting the file.
- An existing
scripts/check-agents-docs.mjs is left alone, with its diff against the template
shown at the gate. A repo that patched the checker patched it for a reason, and the rule the
AGENTS.md bullet states holds here too: never overwrite a file someone edited.
- Seed the baseline last.
--update-baseline refuses to write while any other check fails, so
node scripts/check-agents-docs.mjs --update-baseline runs once every path above exists. A fresh
scaffold records zero words, and the first topic file dw-land promotes pays one visible
re-record — the bargain, not a bug.
{{AGENTS_CHECK_COMMAND}} is how AGENTS.md's own header names its enforcement. With a
package.json, add "agents:check": "node scripts/check-agents-docs.mjs" to scripts and render
pnpm agents:check; where the repo has an aggregate gate script (check, verify), add it to
that too. Without a package.json, render the bare node scripts/check-agents-docs.mjs — and say
at the gate that the checker needs Node even where the project does not.
CLAUDE.md — ln -s AGENTS.md CLAUDE.md. A symlink, never a copy: the harnesses load
CLAUDE.md, the file is AGENTS.md, and a materialized second copy is the fork this whole layout
exists to prevent. If a real CLAUDE.md with content is already there, it was approved at the gate
as a rename — git mv CLAUDE.md AGENTS.md, then link — and its content stays; reconcile it against
the template's sections afterwards rather than overwriting.
- The selected
${CLAUDE_PLUGIN_ROOT}/templates/hooks/*.sh → .claude/hooks/, chmod +x each.
.worktreeinclude — if absent, copy ${CLAUDE_PLUGIN_ROOT}/templates/worktreeinclude.txt
verbatim. If it exists, leave it alone. Every line ships commented out, so it copies nothing
until the user names a file; that is deliberate, because an uncommented guess would copy a secret
nobody asked for. Tracked, and it earns its keep twice: Claude Code reads it for claude -w
worktrees, worktree.sh create reads it for the loop's own. Say at the gate that it starts empty
and the user should add their .env line.
.github/PULL_REQUEST_TEMPLATE.md — if absent, mkdir -p .github and copy
${CLAUDE_PLUGIN_ROOT}/templates/PULL_REQUEST_TEMPLATE.md verbatim. If it exists, leave it
alone. It ships as guidance comments only, so it renders empty until someone writes a body.
- Append
${CLAUDE_PLUGIN_ROOT}/templates/gitignore-block.txt to .gitignore between its markers.
Idempotent: if the markers are already there, replace the block in place, never duplicate it.
The permissions.allow list — derived, never invented. The template ships ask and deny
only, so nothing is pre-approved and every check waits on a prompt unless a global setting happens
to cover it. This is the difference between a lane that runs and one that idles, so build the list
from what step 1 actually found:
- The project's own checks, exactly as detected. Match the wildcard style already used in the
template's
ask list — a bare entry plus an argument form, e.g. Bash(pnpm test) and
Bash(pnpm test *). Never allowlist a script that isn't in the manifest: an entry for a
command that doesn't exist is worse than no entry, because it reads as verified.
- The read-only git surface this lane uses —
git status, git diff, git log,
git rev-parse, git symbolic-ref, git branch --list, git worktree list, in the same two
forms.
Write nothing that overlaps ask or deny. Don't reason about which list wins — just never add
an entry that could match git commit, git push, anything in the template's ask list, or
anything touching .env. Adding write or network commands here is not a speed optimisation; it
removes the gate the rest of the lane is built around.
Seed .ai/backlog/ and .ai/archive/ with their READMEs and nothing else — no example entry, no
TODO, nothing standing in for one. On the next read a placeholder is indistinguishable from real
queued work, and a backlog you have to first decide isn't real is one you stop opening. The copied
backlog-README.md states the entry shape; entries arrive later, from dw-land.
5. Optional — wire the pre-commit
Only when opted in at the gate. pnpm add -D husky lint-staged, pnpm exec husky init, then write
.husky/pre-commit and .lintstagedrc.json from the shapes in references/precommit.md — globs
matched to the formatter and linter step 1 actually detected, never to a tool that isn't installed.
Typecheck and test lines are separate opt-ins: both run the whole project per commit. agents:check
is not an opt-in — it goes in uncommented and unguarded, because it reads a handful of files rather
than building the project, and the reference explains why a staged-paths guard on it leaks. On a repo
that's partly wired, fill the gaps and show diffs — never overwrite blind; the re-run rules are in
the reference. Worth having even solo: it catches the commits made outside a session, where no hook
fires.
Where the repo has no package.json and so no husky, say what that leaves uncovered rather than
inventing a gate: AGENTS.md can then drift past its budget until someone runs the checker by hand.
6. Reconcile tracking
The split is the whole point, so enforce it after writing: .ai/, docs/decisions/, CONTEXT.md,
AGENTS.md, CLAUDE.md, .claude/settings.json and .claude/hooks/ (plus .husky/ when written)
must not be ignored — remove any pre-existing rule that ignores them. CLAUDE.local.md and
.claude/settings.local.json must be ignored: nothing writes the first any more, and the rule
is what keeps a stray one from being committed.
git add the CLAUDE.md symlink and confirm git recorded it as one — git ls-files -s CLAUDE.md
must show mode 120000. A repo with core.symlinks=false stores the link as a text file holding the
target's name, which reads as a one-line CLAUDE.md and silently un-forks nothing; say so rather
than leaving it.
7. Report
List what was written and which paths to git add. If the pre-commit was declined, say what that
leaves uncovered: commits made outside a session run with no formatter and no guardrails. Name the
_(…)_ placeholders left in AGENTS.md for the user to fill — the template ships the sections, only
they know this project's invariants.
References
references/precommit.md — detection signals, glob→command mapping, the .husky/pre-commit and
.lintstagedrc.json shapes, and the idempotent re-run rules. Read it before step 5.
Next: dw-shape to open the first change, or dw-doctor to verify the scaffold actually fires.
$ARGUMENTS
1---2name: dw-init3description: Scaffold a private/solo repo for the solo loop — `.ai/` (work / backlog / archive), `docs/decisions/`, `CONTEXT.md`, a tracked `AGENTS.md` with a Task Router and a size budget, the guardrail hooks, settings with a derived allow-list, and an optional pre-commit. Explicit-invoke only.4---56# dw-init — scaffold a repo for the solo lane78Everything the loop assumes a repo has, written in one gated pass: the `.ai/` state the skills read9and write, the durable homes they promote into, the guardrail hooks, and a settings file whose10allow-list is **derived from the project rather than guessed**.1112## What it writes1314| Path | Tracked? | Purpose |15| ----------------------------------- | ----------------- | -------------------------------------------------------- |16| `.ai/work/` | **tracked** | one folder per change (`dw-shape` writes `CHANGE.md`) |17| `.ai/README.md` | **tracked** | what `.ai/` is and who owns it |18| `.ai/backlog/` + its `README.md` | **tracked** | one file per follow-up, between changes |19| `.ai/archive/` + its `README.md` | **tracked** | landed change docs — history, not guidance |20| `docs/decisions/` + its `README.md` | **tracked** | durable decision records (`dw-land` promotes here) |21| `CONTEXT.md` | **tracked** | the project's glossary — terms only |22| `VERIFY.md` | **tracked** | how to start this project and drive a feature by hand |23| `AGENTS.md` | **tracked** | the one always-loaded file: rules, commands, Task Router |24| `CLAUDE.md` | **tracked** | a symlink to `AGENTS.md` — never a second copy |25| `scripts/check-agents-docs.mjs` | **tracked** | the gate on that file's budget, router and commands |26| `.claude/settings.json` | **tracked** | permissions (ask + deny + derived allow) and hook wiring |27| `.claude/hooks/*.sh` | **tracked** | the guardrail scripts those settings reference |28| `.worktreeinclude` | **tracked** | gitignored files a fresh worktree should carry in |29| `.github/PULL_REQUEST_TEMPLATE.md` | **tracked** | the PR body shape `dw-land` fills and GitHub prefills |30| `.gitignore` | tracked | a managed marker block for the personal files |31| `.husky/` + `.lintstagedrc.json` | tracked, optional | the pre-commit twin of the hooks — only when opted in |3233Deliberately absent: `.ai/verify/` and `.ai/handoffs/` — the solo lane has one thin closing pass34that writes no artifact, and no one to hand off to. The root `VERIFY.md` is **not** that folder: one35file saying how to drive the project, written once and read by every change, never a per-change36artifact. Also absent: **`CLAUDE.local.md`**. Agent memory37is tracked `AGENTS.md`, because a gitignored file survives neither a fresh clone nor a38`git worktree`, and a second always-loaded file forks the corpus in two — `docs/decisions/` in a39scaffolded repo carries the record. Personal, cross-project notes belong in `~/.claude/CLAUDE.md`.40`.gitignore` still names `CLAUDE.local.md` so a stray one is never committed, and the hooks still41read it when they find one, so a repo scaffolded before this keeps working untouched.4243`docs/agents/` ships with its README — the layer's contract, and the file declaring its per-file44budget — and no topic files. The Task Router ships with rows for what the scaffold actually creates,45and the topic layer grows only when `dw-land` promotes into it: a new topic file and its router row in46the same commit, with the corpus baseline re-recorded beside them.4748Templates come from `${CLAUDE_PLUGIN_ROOT}/templates/` — this lane's own payload.49(`${CLAUDE_PLUGIN_ROOT}` is the env var Claude Code substitutes to this plugin's install dir.)5051## Workflow5253### 1. Detect — never assume the stack5455- Repo root (`git rev-parse --show-toplevel`) and default branch56 (`git symbolic-ref --short refs/remotes/origin/HEAD`, else `init.defaultBranch`, else `main`).57- Test / lint / typecheck commands from the manifests actually present — `package.json` scripts,58 `Makefile`, `pyproject.toml`, `go.mod`. Read the real commands; don't invent them. **Keep this59 list** — it becomes the `permissions.allow` entries in step 4, and a command you didn't find here60 must not appear there.61- Pre-commit signals for step 5: formatter and linter deps/configs, a `test`/`typecheck` script —62 the detection table in `references/precommit.md`.63- What already exists: `AGENTS.md`, `CLAUDE.md` (a real file? a symlink? to what?), a legacy64 `CLAUDE.local.md`, `.claude/settings*`, `.gitignore`, `CONTEXT.md`, `docs/`. This is rarely a65 greenfield tree, and step 3 must diff against reality. A repo already carrying a real `CLAUDE.md`66 with content is the case to slow down on — it becomes `AGENTS.md` plus a symlink, which is a67 rename the user has to approve at the gate, not a clobber.6869### 2. Pick the hooks7071Six are always offered because they're stack-agnostic: `bash-guard` (the PreToolUse dispatcher —72one stdin parse, then it spawns each guard only when the command could trip it),73`block-dangerous-commands`, `block-env-access`, `enforce-commit-hygiene`, `credential-leak-guard`74and `large-file-guard`. Add the JS/TS ones only where that stack is actually present:75`block-non-pnpm`, `lint-on-edit`, `typecheck-on-commit` (the declared typecheck before a commit76that stages TS — its per-turn predecessor cost a measured 4-5 s at every turn end). On a stack77with no lint or typecheck hook, offer the six alone and say the rest are stack-specific rather78than silently writing nothing.7980`guard-plugin-canon` is **shape-specific, not stack-specific** — offer it only where step 1 found a81`plugins/` directory whose entries are symlinks back into the tree. It refuses an edit aimed through82one of those links and names the canon instead, so on a repo with no such layout it is a hook that83can never fire.8485### 3. HARD STOP — show what you're about to write8687List every path, marked **tracked** or **ignored**, with a diff for anything that already exists.88Add three things that aren't paths: **the `permissions.allow` list derived in step 1**, so what the89agent may run without asking is approved rather than assumed; **the optional pre-commit offer**90(step 5), so the one gate covers it; and, when the repo already has a real `CLAUDE.md`, **the91`git mv CLAUDE.md AGENTS.md` rename** it needs, spelled out — that one moves a file the user wrote and92must never be inferred from silence. **Wait for explicit confirmation.** Scaffolding mutates the repo93and a wrong clobber is expensive — this gate is not optional even though the rest of the lane is94light.9596### 4. Write9798- `mkdir -p .ai/work .ai/backlog .ai/archive docs/decisions docs/agents`; seed `.ai/work` with99 `.gitkeep` (the other four get READMEs). **Remove a `.gitkeep` that a README now supersedes** — an earlier version100 seeded `docs/decisions/.gitkeep`, so a repo scaffolded then and re-run now keeps a redundant one101 beside the README, and the next reader cannot tell which is the convention.102- `.ai/README.md` — copy `${CLAUDE_PLUGIN_ROOT}/templates/work-README.md` verbatim. It states the103 lifecycle a reader gets wrong: a `CHANGE.md` leaves `work/` at merge — archived, never deleted.104- `.ai/backlog/README.md`, `.ai/archive/README.md` and `docs/decisions/README.md` — copy105 `${CLAUDE_PLUGIN_ROOT}/templates/backlog-README.md`,106 `${CLAUDE_PLUGIN_ROOT}/templates/archive-README.md` and107 `${CLAUDE_PLUGIN_ROOT}/templates/decisions-README.md` verbatim.108 `docs/agents/README.md` — copy `${CLAUDE_PLUGIN_ROOT}/templates/agents-docs-README.md` verbatim; the109 template's router row points at it, so the two arrive together or path sync fails on the first run.110 **Existing entries in any of the three dirs are left alone** — they carry real content from111 earlier changes.112 A legacy single-file `.ai/BACKLOG.md`, if present, is named at the gate: offer to split it into113 per-file entries, never clobber or silently keep it.114- `CONTEXT.md` — if absent, create it with a one-line purpose statement (this project's glossary;115 terms only, no implementation detail) and a `Term budget: **90 lines / 7 KB**.` line, which caps the116 file and holds each term to one bullet of two lines. Nothing else. If it exists, leave it alone.117- `VERIFY.md` — if absent, create it the same way, and write exactly this much: a one-line purpose118 statement, the two invariants below, then the three headings with **one line under each naming what119 belongs there**. It holds what a green suite does not — **Launch** (the command that starts this120 project, on what port, with what disposable state), **Doctor** (one read-only check answering "is121 this instance worth driving?"), **Drive** (the commands, each paired with the observable result it122 should produce). The invariants go in before any of that is filled, both learned the hard way:123 cleanup removes instances and scratch state but **never the evidence**, and **kill what you started**124 rather than killing by process name. **Filling the three sections is not this skill's job**, and a125 project with nothing to launch leaves those lines as written — that is a finished `VERIFY.md`, not a126 stub owed to anyone. A library, a docs repo or a skills catalog has no instance to drive, and an127 honest empty section beats an invented command every session pays for. If it exists, leave it alone.128- `${CLAUDE_PLUGIN_ROOT}/templates/settings.json` → `.claude/settings.json`; **prune** the hook129 entries not selected, add the `permissions.allow` list (below), then confirm the file still parses130 as valid JSON.131- `AGENTS.md` — if absent, render `${CLAUDE_PLUGIN_ROOT}/templates/AGENTS.md`, substituting132 `{{PROJECT_NAME}}` `{{DEFAULT_BRANCH}}` `{{STACK}}` `{{TEST_COMMAND}}` `{{LINT_COMMAND}}`133 `{{TYPECHECK_COMMAND}}` `{{COMMIT_PATTERN}}` `{{COMMIT_TRAILER}}` `{{BOOTSTRAP_COMMAND}}`134 `{{HOOKS_INSTALLED}}`135 `{{AGENTS_CHECK_COMMAND}}`. **Every placeholder gets136 a value or the line goes** — a `{{…}}` left in the file is read as content by the next session and137 `eval`ed as a command by the hooks, which is why they carry an explicit guard against exactly138 these tokens.139 - The commands come from step 1 **verbatim**; `none` where the manifests had none. Write `none`,140 not a plausible guess and not `_(none detected)_`: the hooks read `none` as "skip", and a command141 that doesn't exist fails on every edit.142 - `{{COMMIT_PATTERN}}` and `{{COMMIT_TRAILER}}` are policy, not detected commands, so ask rather143 than probe: derive the pattern from the log the repo already has (`git log --format=%s -30`) and144 propose it, and write `none` for the trailer unless the user says every commit must carry one.145 Both defaults live in `enforce-commit-hygiene.sh` — Conventional Commits for the pattern, `none`146 for the trailer — so an honest `none` is always a safe answer here.147 - `{{LINT_COMMAND}}` is the **per-file** form (`pnpm exec eslint --fix`, `ruff check --fix`), since148 `lint-on-edit` appends one path to it — not the whole-project script, which would re-lint the149 tree on every edit. Where the project only has the whole-project form, say so at the gate and150 write `none` rather than wiring a slow hook.151 - **Prune the Task Router row for anything this run didn't create.** The template ships a row152 pointing at `.claude/hooks/`, and that directory only exists if step 2 selected a hook — so a153 scaffold that declined them all fails its own `agents:check` on its first run, because path sync154 requires every routed path to exist. Same rule for any row whose target you skipped. A row is a155 promise that the file is there.156 - Idempotency is per-section: if `AGENTS.md` already exists, leave it alone and report which of the157 template's sections it is missing. Never merge a rendered template into a file someone wrote.158- `scripts/check-agents-docs.mjs` — copy `${CLAUDE_PLUGIN_ROOT}/templates/check-agents-docs.mjs`159 verbatim. Zero dependencies, Node built-ins only, and it finds the repo root by walking up from its160 own location to the nearest `AGENTS.md` — so `scripts/` is the conventional home, not a required161 one. It checks the always-loaded contract — the declared budget, that no `{{…}}` placeholder162 survived, Task Router coverage and path sync, that every `pnpm <script>` named in `AGENTS.md` exists,163 that `CLAUDE.md` is a symlink — plus the size of the layers it routes to: a `Ceiling:` per decision164 record, a `Topic budget:` per topic file, a `Term budget:` over `CONTEXT.md`, and a word ratchet over165 `docs/agents/` as a whole. The three declared caps are **opt-in**, and each switches on the one166 shape rule a number cannot express for its layer — no dated gotcha bullet, no term over two lines;167 a missing declaration is neither checked nor mentioned, because a gate that lights an existing168 folder red on install day is one you switch off rather than meet. The ratchet is the **opt-out** one:169 it records what a corpus already is, so it is green the day it is seeded, and a repo switches it off170 by deleting the file.171 - **An existing `scripts/check-agents-docs.mjs` is left alone**, with its diff against the template172 shown at the gate. A repo that patched the checker patched it for a reason, and the rule the173 `AGENTS.md` bullet states holds here too: never overwrite a file someone edited.174 - **Seed the baseline last.** `--update-baseline` refuses to write while any other check fails, so175 `node scripts/check-agents-docs.mjs --update-baseline` runs once every path above exists. A fresh176 scaffold records zero words, and the first topic file `dw-land` promotes pays one visible177 re-record — the bargain, not a bug.178 - `{{AGENTS_CHECK_COMMAND}}` is how `AGENTS.md`'s own header names its enforcement. With a179 `package.json`, add `"agents:check": "node scripts/check-agents-docs.mjs"` to `scripts` and render180 `pnpm agents:check`; where the repo has an aggregate gate script (`check`, `verify`), add it to181 that too. Without a `package.json`, render the bare `node scripts/check-agents-docs.mjs` — and say182 at the gate that the checker needs Node even where the project does not.183- `CLAUDE.md` — `ln -s AGENTS.md CLAUDE.md`. A **symlink**, never a copy: the harnesses load184 `CLAUDE.md`, the file is `AGENTS.md`, and a materialized second copy is the fork this whole layout185 exists to prevent. If a real `CLAUDE.md` with content is already there, it was approved at the gate186 as a rename — `git mv CLAUDE.md AGENTS.md`, then link — and its content stays; reconcile it against187 the template's sections afterwards rather than overwriting.188- The selected `${CLAUDE_PLUGIN_ROOT}/templates/hooks/*.sh` → `.claude/hooks/`, `chmod +x` each.189- `.worktreeinclude` — if absent, copy `${CLAUDE_PLUGIN_ROOT}/templates/worktreeinclude.txt`190 verbatim. **If it exists, leave it alone.** Every line ships commented out, so it copies nothing191 until the user names a file; that is deliberate, because an uncommented guess would copy a secret192 nobody asked for. Tracked, and it earns its keep twice: Claude Code reads it for `claude -w`193 worktrees, `worktree.sh create` reads it for the loop's own. Say at the gate that it starts empty194 and the user should add their `.env` line.195- `.github/PULL_REQUEST_TEMPLATE.md` — if absent, `mkdir -p .github` and copy196 `${CLAUDE_PLUGIN_ROOT}/templates/PULL_REQUEST_TEMPLATE.md` verbatim. **If it exists, leave it197 alone.** It ships as guidance comments only, so it renders empty until someone writes a body.198- Append `${CLAUDE_PLUGIN_ROOT}/templates/gitignore-block.txt` to `.gitignore` between its markers.199 **Idempotent**: if the markers are already there, replace the block in place, never duplicate it.200201**The `permissions.allow` list — derived, never invented.** The template ships `ask` and `deny`202only, so nothing is pre-approved and every check waits on a prompt unless a global setting happens203to cover it. This is the difference between a lane that runs and one that idles, so build the list204from what step 1 actually found:205206- **The project's own checks, exactly as detected.** Match the wildcard style already used in the207 template's `ask` list — a bare entry plus an argument form, e.g. `Bash(pnpm test)` and208 `Bash(pnpm test *)`. **Never allowlist a script that isn't in the manifest**: an entry for a209 command that doesn't exist is worse than no entry, because it reads as verified.210- **The read-only git surface this lane uses** — `git status`, `git diff`, `git log`,211 `git rev-parse`, `git symbolic-ref`, `git branch --list`, `git worktree list`, in the same two212 forms.213214**Write nothing that overlaps `ask` or `deny`.** Don't reason about which list wins — just never add215an entry that could match `git commit`, `git push`, anything in the template's `ask` list, or216anything touching `.env`. Adding write or network commands here is not a speed optimisation; it217removes the gate the rest of the lane is built around.218219**Seed `.ai/backlog/` and `.ai/archive/` with their READMEs and nothing else** — no example entry, no220`TODO`, nothing standing in for one. On the next read a placeholder is indistinguishable from real221queued work, and a backlog you have to first decide isn't real is one you stop opening. The copied222`backlog-README.md` states the entry shape; entries arrive later, from `dw-land`.223224### 5. Optional — wire the pre-commit225226Only when opted in at the gate. `pnpm add -D husky lint-staged`, `pnpm exec husky init`, then write227`.husky/pre-commit` and `.lintstagedrc.json` from the shapes in `references/precommit.md` — globs228matched to the formatter and linter step 1 actually detected, never to a tool that isn't installed.229Typecheck and test lines are separate opt-ins: both run the whole project per commit. **`agents:check`230is not an opt-in** — it goes in uncommented and unguarded, because it reads a handful of files rather231than building the project, and the reference explains why a staged-paths guard on it leaks. On a repo232that's partly wired, fill the gaps and show diffs — never overwrite blind; the re-run rules are in233the reference. Worth having even solo: it catches the commits made outside a session, where no hook234fires.235236Where the repo has no `package.json` and so no husky, say what that leaves uncovered rather than237inventing a gate: `AGENTS.md` can then drift past its budget until someone runs the checker by hand.238239### 6. Reconcile tracking240241The split is the whole point, so enforce it after writing: `.ai/`, `docs/decisions/`, `CONTEXT.md`,242`AGENTS.md`, `CLAUDE.md`, `.claude/settings.json` and `.claude/hooks/` (plus `.husky/` when written)243must **not** be ignored — remove any pre-existing rule that ignores them. `CLAUDE.local.md` and244`.claude/settings.local.json` must **be** ignored: nothing writes the first any more, and the rule245is what keeps a stray one from being committed.246247`git add` the `CLAUDE.md` symlink and confirm git recorded it as one — `git ls-files -s CLAUDE.md`248must show mode `120000`. A repo with `core.symlinks=false` stores the link as a text file holding the249target's name, which reads as a one-line `CLAUDE.md` and silently un-forks nothing; say so rather250than leaving it.251252### 7. Report253254List what was written and which paths to `git add`. If the pre-commit was declined, say what that255leaves uncovered: commits made outside a session run with no formatter and no guardrails. Name the256`_(…)_` placeholders left in `AGENTS.md` for the user to fill — the template ships the sections, only257they know this project's invariants.258259## References260261- `references/precommit.md` — detection signals, glob→command mapping, the `.husky/pre-commit` and262 `.lintstagedrc.json` shapes, and the idempotent re-run rules. Read it before step 5.263264**Next:** `dw-shape` to open the first change, or `dw-doctor` to verify the scaffold actually fires.265266$ARGUMENTS