Companion to the Plan storage fleet rule in template/CLAUDE.md. The inline rule is one sentence. This doc carries the rationale, the migration guidance for legacy docs/plans/ content, and the per-repo extension pattern.
A design / implementation / migration document that captures state about work in progress or work about to start:
- Multi-step refactor breakdowns (which files, in what order, how many LOC).
- Cross-package migration playbooks.
- Feature-design docs that enumerate JS surface + C++ binding signatures.
- "Where did we leave off" notes a future session needs to resume.
- LOC estimates, step boundaries, commit-split proposals.
What is not a plan (and belongs elsewhere):
- Permanent architecture docs:
docs/architecture/or a top-level<topic>.md(tracked). - API reference: JSDoc / TSDoc / Rustdoc / README.
- Onboarding / contributor docs:
CONTRIBUTING.md(tracked). - Incident post-mortems: if the lesson is worth keeping, it goes into CLAUDE.md as a rule with a
**Why:**line per the Compound lessons rule. The post-mortem itself can stay in.claude/plans/as scratch.
<repo-root>/.claude/plans/<lowercase-hyphenated>.md.
One location per repo. Never:
docs/plans/: tracked; defeats the rule.<pkg>/docs/plans/: tracked + duplicates the convention per-package.<pkg>/.claude/plans/: sub-package.claude/is a fleet-convention smell; CLAUDE itself reads the repo-root.claude/for the operator's current session.
The path is shared across parallel Claude sessions in the same checkout, so
multiple plans coexist comfortably. Worktrees get their own .claude/plans/ that disappears when the worktree is removed. That's by design.
The fleet template/.gitignore already excludes /.claude/* with an
explicit allowlist:
/.claude/*
!/.claude/agents/
!/.claude/commands/
!/.claude/hooks/
!/.claude/ops/
!/.claude/settings.json
!/.claude/skills/plans/ is intentionally absent from the allowlist. A freshly-written plan
is therefore untracked by default.
Do NOT:
- Add
!/.claude/plans/to the gitignore allowlist. git add .claude/plans/<file>.md.- Use
git add -A/git add .(which would sweep the plan in; the fleet rule already forbids those flags for unrelated reasons).
Plans capture state: what we're about to do, what we've ruled out, what the LOC estimates are. State decays the moment a commit lands. A plan tracked in git rots into "this file describes what main looked like 4 months ago" lies that future-you trusts. Keeping plans local-only forces the work to live in:
- The code (the actual implementation is the source of truth).
- Commit messages (capture the why at the moment the change ships).
- CHANGELOG (capture the consumer-visible diff at release time).
These are the surfaces that actually stay accurate, because they're written at the moment of the change rather than weeks before it.
Past incident: socket-btm grew three parallel plans/ directories (docs/plans/, packages/*/docs/plans/, .claude/plans/). Same content type, three locations, all tracked, all drifting. The rule is one location, untracked.
If you find a tracked plan in docs/plans/ or <pkg>/docs/plans/:
- Stop and ask the user before relocating. Moving the file requires rewriting every reference (test files, READMEs, source comments, Dockerfiles, build scripts) that cites the old path. Silent migration is a recipe for broken links.
- If the user approves migration:
- Inventory references first:
rg -l "docs/plans/<filename>"andrg -l "<pkg>/docs/plans/<filename>". - If the plan is still active (work isn't done): move to
.claude/plans/<same-name>.md(the destination is untracked, so the move requiresgit rm <old>+cp <old> .claude/plans/+ plain filesystem cp, notgit mv). Rewrite every reference. - If the plan is finished (work shipped): the plan has served its purpose.
git rmthe tracked copy + delete references that say "see plan X." Don't preserve dead plans as documentation; that turns them back into the rot the rule prevents.
- Inventory references first:
- Either way, the cleanup is its own commit / PR; don't bundle it with the work the plan describes.
Downstream repos can add their own plan-storage rules in their own CLAUDE.md (outside the fleet block). Common extensions:
- A per-repo
.claude/plans/README.mdlisting currently-active plans with a one-line description. That README is also untracked (under/.claude/*) but operators in a fresh worktree won't have it; the list is regenerable fromls -1 .claude/plans/. - Naming conventions for active vs archived plans (e.g.
wip-<name>.md/done-<name>.md). - A repo-specific plans index that the operator maintains by hand.
These all sit inside the same gitignored /.claude/plans/ directory and
don't change the fleet rule.
markdown-filename-guard: the hook accepts lowercase-hyphenated.mdfiles under eitherdocs/or.claude/(any depth). It will NOT block adocs/plans/<name>.mdwrite; the guard is filename-only, not content-aware. The plan-storage convention is enforced by this rule, not by the filename guard.- No fleet fork: this doc is fleet-canonical (lives under
template/docs/agents.md/fleet/). Downstream copies are read-only. Edit here and cascade. - Drift watch: if you find a downstream repo carrying its own diverged copy of this doc, reconcile back to fleet-canonical.