Why a Hierarchy, Not Just a Folder
Most "docs live next to the code" setups start well and rot fast: a docs/ folder that nobody updates, a spec that drifts from what actually shipped, a status field nobody trusts because three different people update it three different ways.
The feature-docs hierarchy is an attempt to make that rot structurally hard. Every planning artifact — references, specs, implementation reports — lives under feature-docs/ at the repo root, organized into four tiers that mirror how long a decision stays true, one Git branch per shippable increment, and a status model split into two fields that are never allowed to be conflated.
It's implemented as a set of Claude Code skills (/feature-docs-hierarchy, /scaffold-implementation-docs, /grill-me, /spec-creator, /tdd, /pr-merged, /pr-rejected) that read and write a canonical CLAUDE.md section, addressed by §anchor rather than by paragraph number — so the process itself is diffable and versionable, the same way the code is.
The Gate Before Anything Runs
Before /feature-docs-hierarchy does anything — bootstrap a fresh project or upgrade an existing one — it checks four preconditions. All four must hold, or it names the failure and stops. It never fixes one on its own; each is a deliberate human action.
- Repo initialized.
.gitexists. Absent → the human runsgit initand makes a first commit. - Working tree clean.
git status --porcelainis empty. A dirty tree aborts with the specific reason, telling the human to stash or commit first.HEADcan be on any branch — as of v8 this no longer requires already being onmain. - Remote configured and reachable.
originis set andgit ls-remote originsucceeds — merging only ever happens via pull request, which needs a remote to mean anything. - Baseline
.gitignoreexists. Created if absent, but only in bootstrap mode — upgrade mode checks, never invents one for an established project.
⚠️ Why the gate is hard, not soft
Bootstrapping or upgrading on top of uncommitted work is exactly how feature-docs/README.md silently corrupts — a Features table computed against a working tree that later gets reset or force-pushed over. The gate exists so that failure mode simply can't happen.
The trunk requirement used to be stricter: HEAD had to already be on main before the skill would run at all. That turned out to be pure friction with no matching safety benefit — so as of v8 the skill handles trunk itself. It fetches, checks out main, applies the template update, commits it there locally (never pushes — that's still the human's call), and checks back out whatever branch you started on. The one thing that still has to hold is a clean tree, because an uncommitted diff silently riding along into a trunk-side commit is a real risk the branch requirement never actually prevented.
Upgrades Are Version-Gated
feature-docs/README.md carries a Changelog/Versioning section: a single integer template version. Upgrade mode reads it first, before touching anything else in the tree.
| Recorded version | What happens |
|---|---|
| Matches the skill's current version | No-op. Reports "already up to date" and stops — the structural diff/overwrite logic never runs. |
| Lower than current, or section missing | A missing section is treated as version 0 — this covers installs that predate the versioning mechanism, so there's no separate legacy heuristic to maintain. Falls through to the structural update, then stamps the new version with a changelog entry. |
The section is skill-managed, the same way the Features table is — nobody hand-edits either. As of v8, the structural update pass also locates any pre-v8 shared/ folder still sitting around and migrates its contents automatically — see below.
Four Tiers, One Folder Each
Every document lives at the tier where it stops being true. Through v7 this was a shared/ wrapper directory split into references/, documentation/, scripts/, and fixes/ subfolders at each tier. As of v8 that split is gone: each tier is one explicitly-named folder, and everything that used to be scattered across those four subfolders goes into it.
feature-docs/
README.md # root signpost — Features table + Changelog/Versioning
references-root/ # root tier — true regardless of feature
<feature-name>/
README.md # feature signpost
references-feat/ # true for every implementation of this feature — auto
<implementation-name>/ # ↔ branch impl/<feature>/<implementation>
README.md # implementation signpost
ITERATIONS.md # implementation status + per-iteration rows
references-impl/ # true for every iteration of this implementation
iter-1/ # ↔ commit(s) + tag iter-1-done
references-iter/ specs/ implementation-reports/
iter-2/ ...
Choosing the right tier for a reference document is a recursive test, applied outward:
Figure 1 — Which reference tier does this content belong in?
💡 Key Insight
The four tiers aren't four separate rules — they're one test (would this still be true one level up?) applied recursively. That's what keeps the hierarchy from needing a fifth tier, a sixth tier, or an "it depends" carve-out every time a new kind of document shows up. Each tier's folder carries its tier suffix (-root/-feat/-impl/-iter) precisely because "references" alone would collide at every level.
All three of root-, feature-, and implementation-tier are auto-scaffolded. Root-tier feature-docs/references-root/ is created by /feature-docs-hierarchy in both bootstrap and upgrade mode. Feature-tier <feature>/references-feat/ and implementation-tier <feature>/<impl>/references-impl/ are created by /scaffold-implementation-docs at scaffold time, and backfilled by /feature-docs-hierarchy's upgrade mode for any pre-existing feature or implementation whose folder is entirely missing (folder creation only — an already-existing folder, even partially populated, is never touched). In every case, files placed inside a reference folder are project data — the skills only ever manage the folders' existence, never their contents.
💡 Why the backfill matters
Without it, a project that adopted the hierarchy before these folders existed would carry silently inconsistent features and implementations forever — some with the folder, some without, depending only on when each one happened to be scaffolded. Upgrade mode closes that gap the same way it closes every other structural drift: on the next version-gated run, not by waiting for someone to notice.
⚠️ Migrating from pre-v8
Upgrade mode moves old shared/references/, shared/documentation/, shared/scripts/, and shared/fixes/ content into the new references-*/ folder automatically — whole files relocated with git mv, never opened or edited. A folder is deleted only once it's confirmed empty. If two source files would collide on the same target filename, that one shared/ folder's migration is skipped entirely and flagged in the report — a human renames one file, then the next upgrade run completes it. Silently merging file contents on someone's behalf is a worse failure mode than asking.
One Branch Per Implementation
The implementation folder and its branch, impl/<feature>/<implementation>, are created together by /scaffold-implementation-docs — never one without the other. An iteration is not a branch: it's a commit closed out with an annotated tag, iter-N-done, on that same branch.
| Rule | Detail |
|---|---|
| Every iteration, first action | git fetch origin && git merge origin/main — merge only, never rebase. |
| Why never rebase | Rebase rewrites history from the point of divergence forward, orphaning every existing iter-N-done tag — they'd point at commits no longer reachable from the branch head. |
| On conflict | Stop and surface to a human — an agent guessing at conflict resolution is a worse failure mode than the drift the sync step exists to prevent. |
| On clean merge | Re-run the test suite before doing any new work — main moving may mean something this implementation relied on has changed underneath it. |
| Merging out | Exclusively via pull request, exclusively manual, for feature work. No skill ever pushes to trunk. |
All iterations on one branch surface as a single PR — review is QA-style over the whole implementation, not diff-style per iteration.
There's exactly one carve-out from "no skill pushes to trunk": /feature-docs-hierarchy committing its own governance files — feature-docs/README.md, the CLAUDE.md section, the installed skill files — directly to local main, per the gate section above. It still never pushes, and it never touches feature code. Everything else stays PR-only.
Two Layers of Status, Never Conflated
Iteration status is local and informational. Implementation status is what actually drives the feature rollup. Keeping them as two separate fields — rather than inferring one from the other — is what makes the rollup trustworthy.
Figure 2 — Iteration status: local, informational, never drives a merge
Figure 3 — Implementation status: the only field the rollup reads
Note there's no fourth "rejected" state — rejection isn't terminal, it's just this implementation still being unfinished. /pr-rejected logs the QA feedback and automatically starts the next iteration on the same branch.
The Rollup Reads Implementation Status Only
feature = AND( impl-status₁, impl-status₂, … impl-statusₙ )
All implementations ✅ complete → the feature reads ✅ complete. One holdout, anywhere, and the feature reads 🔄 in-progress — recomputed by /pr-merged every time it fires, never copied forward.
⚠️ The layers don't leak into each other
Three iterations sitting at ✅ done on a branch that's never been opened as a PR still roll up as 🔄 in-progress at the feature level — correctly. Iteration status can't paper over an implementation that was never actually reviewed. This is the entire reason the two fields exist separately instead of one "progress" field that tries to do both jobs.
The Cycle, End to End
- Create the feature folder by hand.
mkdir -p feature-docs/<feature>— manual, on purpose, after the git gate holds. - Scaffold —
/scaffold-implementation-docscreates the implementation folder and theimpl/<feature>/<implementation>branch together. Implementation status starts🔄 in-progress. Recomputes the root Features table. - Sync from main, every time — fetch and merge
origin/mainbefore anything else this iteration, even on iteration 1. - Reference doc — capture findings in
iter-N/references-iter/<topic>.md. - Grill —
/grill-me, mandatory. Never fires on its own; must be invoked explicitly, every iteration, before a spec gets written. - Spec —
/spec-creatorwrites toiter-N/specs/spec.md, self-reviews withsenior-code-review. - Implement —
/tddworks from the reviewed spec; final act tagsiter-N-doneand writes the report. Does not open a PR, does not touch implementation status. - Repeat 3–7 for as many iterations as the implementation needs, all on the same branch.
- Open the PR — when iterations look implementation-ready: push the branch, open the PR, set implementation status to
👀 in-reviewwith the PR link recorded. - QA, then record the outcome. QA happens outside this hierarchy. Merged →
/pr-merged <branch>. Rejected →/pr-rejected <branch>, which auto-starts the next iteration on the same branch.
🔑 The Core Principle
Every automated step in this cycle is reversible and touches nothing shared — syncing main, writing a reference doc, running /grill-me, writing a spec, tagging an iteration. The moment something crosses a Git or PR boundary — opening a PR, confirming a merge, confirming a rejection — a human has to say so explicitly. No skill infers "this is ready" on its own.
Signposts, Not Copies
The process lives in exactly one place — the ## Feature Documentation Workflow section of CLAUDE.md, addressed by §anchor. Every README in the tree is a signpost, not a copy: where you are, current status, the one next action, and a link to the specific anchor that governs this moment.
| File | Carries |
|---|---|
feature-docs/README.md | Features table (computed rollup), Changelog/Versioning section, link to §end-to-end-flow / §git-baseline |
<feature>/README.md | Implementations list, link to §shared-tier-rule |
<impl>/README.md | Implementation status + current iteration, the one next action, links to §flow-grill / §flow-spec / §pr-workflow |
Skills Catalogue
| Skill | Scope | Invocation | Purpose |
|---|---|---|---|
/feature-docs-hierarchy | Global | Auto | Bootstrap or upgrade the system, gated by §git-baseline; upgrades are version-gated |
/scaffold-implementation-docs | Project | Auto | Populate an implementation dir and create its impl/ branch |
/start-new-iteration | Project | Auto | Create iter-N/; manual or auto-run by /pr-rejected |
/grill-me | Global | Manual only | Mandatory stress-test before /spec-creator |
/spec-creator | Global | Auto | Build and self-review the spec — terminal planning stage |
/tdd | Global | Auto | Test-first implementation; tags iter-N-done, flips iteration status only |
/senior-code-review | Global | Auto | Audit for bugs, security, edge cases |
/pr-merged | Global | Manual, human, input: branch | Implementation → complete, recompute rollup, delete branch |
/pr-rejected | Global | Manual, human, input: branch | Implementation → in-progress, log feedback, auto-start next iteration |
The hierarchy doesn't try to automate the parts that are genuinely a human's call — opening a PR, confirming what happened to it. It automates everything reversible in between, and gets out of the way at every boundary that isn't.