Agentic Coding

The Feature-Docs Hierarchy

A skill-driven workflow for spec, build, and ship — four explicit reference tiers, one branch per implementation, and a status model that doesn't leak

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.

  1. Repo initialized. .git exists. Absent → the human runs git init and makes a first commit.
  2. Working tree clean. git status --porcelain is empty. A dirty tree aborts with the specific reason, telling the human to stash or commit first. HEAD can be on any branch — as of v8 this no longer requires already being on main.
  3. Remote configured and reachable. origin is set and git ls-remote origin succeeds — merging only ever happens via pull request, which needs a remote to mean anything.
  4. Baseline .gitignore exists. 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 versionWhat happens
Matches the skill's current versionNo-op. Reports "already up to date" and stops — the structural diff/overwrite logic never runs.
Lower than current, or section missingA 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.

RuleDetail
Every iteration, first actiongit fetch origin && git merge origin/main — merge only, never rebase.
Why never rebaseRebase 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 conflictStop 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 mergeRe-run the test suite before doing any new work — main moving may mean something this implementation relied on has changed underneath it.
Merging outExclusively 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

  1. Create the feature folder by hand. mkdir -p feature-docs/<feature> — manual, on purpose, after the git gate holds.
  2. Scaffold/scaffold-implementation-docs creates the implementation folder and the impl/<feature>/<implementation> branch together. Implementation status starts 🔄 in-progress. Recomputes the root Features table.
  3. Sync from main, every time — fetch and merge origin/main before anything else this iteration, even on iteration 1.
  4. Reference doc — capture findings in iter-N/references-iter/<topic>.md.
  5. Grill/grill-me, mandatory. Never fires on its own; must be invoked explicitly, every iteration, before a spec gets written.
  6. Spec/spec-creator writes to iter-N/specs/spec.md, self-reviews with senior-code-review.
  7. Implement/tdd works from the reviewed spec; final act tags iter-N-done and writes the report. Does not open a PR, does not touch implementation status.
  8. Repeat 3–7 for as many iterations as the implementation needs, all on the same branch.
  9. Open the PR — when iterations look implementation-ready: push the branch, open the PR, set implementation status to 👀 in-review with the PR link recorded.
  10. 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.

FileCarries
feature-docs/README.mdFeatures table (computed rollup), Changelog/Versioning section, link to §end-to-end-flow / §git-baseline
<feature>/README.mdImplementations list, link to §shared-tier-rule
<impl>/README.mdImplementation status + current iteration, the one next action, links to §flow-grill / §flow-spec / §pr-workflow

Skills Catalogue

SkillScopeInvocationPurpose
/feature-docs-hierarchyGlobalAutoBootstrap or upgrade the system, gated by §git-baseline; upgrades are version-gated
/scaffold-implementation-docsProjectAutoPopulate an implementation dir and create its impl/ branch
/start-new-iterationProjectAutoCreate iter-N/; manual or auto-run by /pr-rejected
/grill-meGlobalManual onlyMandatory stress-test before /spec-creator
/spec-creatorGlobalAutoBuild and self-review the spec — terminal planning stage
/tddGlobalAutoTest-first implementation; tags iter-N-done, flips iteration status only
/senior-code-reviewGlobalAutoAudit for bugs, security, edge cases
/pr-mergedGlobalManual, human, input: branchImplementation → complete, recompute rollup, delete branch
/pr-rejectedGlobalManual, human, input: branchImplementation → 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.