Introduction

This is a small, opinionated convention for how a software project keeps track of:

  • Why non-obvious choices were made
  • What's next and in what order
  • What's been tried that didn't pan out
  • What's in flight right now that someone else (or future-you) needs to pick up

Four document kinds, four homes, one inviolable rule: every load-bearing piece of information has exactly one canonical home, and only one of the four — the handoff — gets deleted.

Who this is for

  • Solo developers using Claude Code who want their project to stay coherent across many sessions, without context drift, without re-discovering decisions, and without scrolling back through transcripts.
  • Small teams who want a low-overhead alternative to Notion / Confluence / Linear sprawl. Markdown in docs/, version-controlled, reviewable in PRs.
  • Maintainers of long-running projects who've felt the pain of "what was the rationale for X?" being unanswerable.

What this isn't

  • Not a project management framework. No tickets, no statuses, no burn-downs.
  • Not a documentation site generator. It is rendered as one (mdBook), but the discipline is fundamentally a habit, not a tool.
  • Not specific to Claude Code. Claude Code happens to be where this discipline crystallised, and there are tooling integrations described later, but a human team using only git and vim could follow this verbatim.

How to read this book

  1. Read Overview: four memory horizons first — it sketches the whole shape in one page.
  2. Read Handoff documents — this is the most distinctive part of the discipline and the most easily abused without it.
  3. Skim the per-kind chapters (ADRs, plans, spikes) as you need them.
  4. Use Adopting this in a new project as the actionable checklist.

A note on style

These docs are short on purpose. Every word is load-bearing. If a sentence reads as filler, file an issue or a PR — it's a bug.

Overview: four memory horizons

A project carries four kinds of memory. Each has a different lifetime, a different audience, and a different failure mode if it ends up in the wrong place.

Memory kindLifetimeLives inFailure mode if mis-stored
Decision ("why we chose X")Foreverdocs/adr/NNNN-*.md (Architecture Decision Records, ADRs)Buried in commit messages or chat history; future contributors re-litigate
Plan ("what's next, in what order")Long-lived; updated in placedocs/plans/<topic>.mdScattered across TODOs, draft Pull Requests (PRs), latest handoff — nobody knows the truth
Spike ("did this idea work?")Forever, marked Resolveddocs/spikes/<topic>.mdQuietly forgotten; ideas re-attempted years later
Handoff ("what's in flight RIGHT NOW")Ephemeral — folded then deleteddocs/handoffs/<topic>-handoff.mdSurvives past resolution; "STATUS: RESOLVED" piles up; new contributors mistake it for current

The term Architecture Decision Record (ADR) comes from Michael Nygard's 2011 post; the format used in this discipline is a lightly evolved descendant. Used throughout this book, "ADR" always refers to a numbered file in docs/adr/.

Plus three supporting kinds that already have well-established homes:

Memory kindLives in
How something works ("design")Free-form docs/<topic>.md
What shipped, whenCHANGELOG.md + git tags
Working source of truth on code shapeThe code itself

The migration pattern

The discipline lives or dies by one rule:

Every load-bearing piece of a handoff migrates into its proper home (ADR / plan / spike / design doc / CHANGELOG) before the handoff file is deleted.

This forces every handoff to ask the question "where does this actually belong?" at resolution time. The answer is almost always one of the other four kinds. The handoff was just the temporary workspace.

Why the strict separation

Decisions go stale slowly; plans go stale fast; handoffs go stale instantly. Mixing them creates a class of bug where a doc says one thing and the code says another, and the only way to know which is current is by reading commit dates.

By giving each kind its own directory and lifetime rule, you can answer "is this still true?" by looking at the kind, not by reading the contents.

You're asking…Look at…Confidence in currency
"Why does the system work this way?"The relevant ADRHigh — ADRs are amended in place
"What's the project doing this quarter?"The active plan docHigh — plans are kept current
"Did anyone try X already?"docs/spikes/High — spikes are immortal but resolved
"What's the next concrete action?"The single live handoff in docs/handoffs/High — there's only ever one

If docs/handoffs/ has more than one file, the discipline has been broken. That's the smell test.

Concrete shape on disk

your-project/
├── CLAUDE.md                          ← project conventions, points at this discipline
├── CHANGELOG.md
├── docs/
│   ├── adr/
│   │   ├── README.md                  ← ADR conventions
│   │   ├── 0001-some-decision.md
│   │   ├── 0002-another-decision.md
│   │   └── ...
│   ├── plans/
│   │   ├── phase-0-foo.md
│   │   ├── phase-1-bar-roadmap.md
│   │   └── ...
│   ├── spikes/
│   │   ├── opentimer-sky130.md
│   │   └── ...
│   ├── handoffs/
│   │   └── current-work-handoff.md    ← exactly zero or one file at a time
│   ├── handoff-discipline.md          ← copy of the migration rule
│   ├── release-process.md             ← optional: how releases get cut
│   └── <design docs>.md               ← architecture, subsystem walkthroughs, etc.
└── ...

The next chapters cover each kind in detail.

Architecture Decision Records (ADRs)

An Architecture Decision Record ("ADR") is a short, numbered markdown file capturing the why behind a non-obvious choice so future contributors don't re-litigate it. The term and the basic structure (Context / Decision / Consequences) come from Michael Nygard's 2011 post; the discipline here adds an explicit Status lifecycle, in-place amendments, and the rule that ADRs are never deleted.

When to write one

Write an ADR whenever you make a non-trivial choice that future-you (or a future contributor, or a future Claude session) would want to understand the reasoning behind. Examples that qualify:

  • Picking one tool / library / format over another
  • Choosing a particular architectural boundary
  • Adopting a multi-piece convention that spans several files
  • Deciding not to do something obvious-looking

Skip ADRs for:

  • Mechanical changes (renames, typo fixes, dependency bumps)
  • Choices the code itself makes self-evident (a small refactor, a chosen variable name)
  • Things adequately captured by a commit message

A useful test: if someone asked "why is it like this?" six months from now, would the answer be in the code, the commit, or this ADR? If the answer is "this ADR", write it.

Format

Numbered (0001-...md, 0002-...md), structured as:

# ADR NNNN — Short title

**Status:** Accepted (YYYY-MM-DD).

## Context

What was the situation? What constraints forced a decision?
Be concrete — name the systems, the data formats, the friction.

## Decision

What we chose, with enough specificity to act on.
Numbered sub-points if multiple commitments.

## Consequences

What this buys us, what it costs us, and what would trigger a walk-back.
Be honest about the costs.

## Walk-back options

(Optional, but strongly encouraged.) Conditions under which this
decision should be revisited, and how to revisit it cheaply.

## Links

Cross-references to related files, design docs, related ADRs.

Status lifecycle

ADRs go through these states:

  • Proposed — written, under review.
  • Accepted (YYYY-MM-DD) — agreed upon. The date matters.
  • Superseded by ADR NNNN (YYYY-MM-DD) — replaced. The old ADR stays in place; the new one references it.
  • Withdrawn (YYYY-MM-DD) — abandoned without replacement (rare).

When scope expands, amend the Status: line in place rather than silently rewriting the body:

**Status:** Accepted (2026-05-05). Scope expanded 2026-09-01 — see Decision §3 below.

The amendment goes inline as a clearly-marked section near the top:

## Amendment (2026-09-01)

The original Decision <X> turned out to be too restrictive given <Y>.
This amendment relaxes it as follows: ...

The original Context, Decision, and Walk-back sections below are
retained for historical record. Where they conflict with this
amendment, this amendment wins.

This pattern preserves the historical reasoning while making the current bright lines easy to find.

Numbering and the index

Use sequential zero-padded numbers: 0001-foo.md, 0002-bar.md. Don't reuse numbers, don't renumber.

Maintain docs/adr/README.md as the index:

| ADR  | Title                | Status                |
| ---- | -------------------- | --------------------- |
| 0001 | [OpenSTA as oracle](0001-opensta-as-oracle.md) | Accepted 2026-04-15 |
| 0002 | [Timing IR](0002-timing-ir.md) | Accepted 2026-04-20 |
| 0003 | [OpenTimer primary STA](0003-opentimer-primary-sta.md) | **Superseded** by spike (2026-05-01) |

Never delete an ADR

If superseded, add a new ADR that references the old one and update the old one's Status: to Superseded by ADR NNNN (YYYY-MM-DD). The old ADR stays in the repo. Future readers tracing the history of a decision need both.

This is uncomfortable when the old ADR is wrong; that's the point. Wrong-ADR-with-Superseded-marker is more useful than a clean repo with no record of the wrong turn.

A worked example

The cleanest ADR sequence is one where ADR M proposes a path, a spike investigates, the spike fails, ADR N supersedes ADR M, and the supersession is recorded inline:

  • ADR 0001: "Use external tool X as our oracle." Accepted.
  • ADR 0003: "Add tool Y as our in-process reference." Accepted (proposed).
  • Spike: time-boxed validation of Y on real workloads. Resolved — Y unfit for our inputs.
  • ADR 0003 Status updated to: Superseded by spike outcome (2026-05-01).
  • ADR 0001 amended: scope expanded — X is now both oracle and sole STA path. Inline ## Amendment block records the rationale.

A reader chasing "why are we using X out of process?" reads ADR 0001's amendment and the spike, and gets the full story.

Template

A copy-paste-ready template lives at starter/docs/adr/0000-template.md in this repo. Drop it into your project's docs/adr/ and rename to the next available number.

Plan documents

Plan documents schedule the work an Architecture Decision Record (ADR) or a set of ADRs has committed to. They answer the question "what's next, in what order?" — and they answer it durably, in a place that doesn't rot the way a TODO list or a draft Pull Request (PR) description does.

What a plan is

A plan doc is a scheduling doc, not a design doc. The design lives in ADRs and in free-form docs/<subsystem>.md design docs. The plan organises the implementation of those decisions into:

  • Phases with entry and exit criteria
  • Workstreams (often abbreviated WS with a numeric or alphabetic suffix, e.g. WS1, WS-P1.1) that can sometimes run in parallel
  • A status section that's updated in place as work lands

The shape varies. A small project might have a single docs/plans/roadmap.md. A larger one accumulates per-phase plans and a top-level roadmap that orders them.

What a plan is not

  • Not an ADR. "We chose X over Y because Z" → an ADR. The plan consumes that decision and slices it into work.
  • Not a handoff. "What needs to happen tomorrow morning, with verification commands" → a handoff. The plan is the persistent layer the handoff folds into at resolution.
  • Not a design doc. "How the X subsystem works internally" → a design doc. The plan refers to the design doc; it doesn't reproduce it.

Anatomy of a good plan

Plans tend to settle into this shape:

# Plan — <Phase / topic>: <one-line goal>

**Status:** <Proposed | Active | Closed>. <Latest dated update.>

## Goal

What this phase / topic delivers, in 1–3 sentences.

## Prerequisites

What must be true / accepted / merged before this can start. ADRs
that need to be Accepted, fixtures that need to be in place.

## Where things stand (YYYY-MM-DD)

The single most-recent status snapshot. Updated in place.
Past states are in `git log`, not stacked here.

## Workstreams

### WS1 — <Name>

**Status:** <In flight | Shipped commit `abc1234` | Blocked on X>

<Concrete scope. Reference ADRs and design docs; don't duplicate them.>

**Deliverables:**
- Specific artefacts: a binary, a schema, a CI gate, a test fixture.

**Exit criteria:**
- Verifiable conditions for declaring this workstream done.

### WS2 — <Name>

...

## Phase exit criteria

A short checklist of bullets. When all are true, the phase closes.

Why entry/exit criteria matter

Plans without exit criteria become forever documents. Someone always thinks of one more thing to add. The exit criteria are the contract: when these are met, the phase closes and a new plan (or follow-up roadmap) takes over.

A useful pattern: the next plan's prerequisites and the current plan's exit criteria mirror each other. That makes the handoff between phases explicit.

Workstream sizing

The right workstream size is "a contributor can pick this up cold and start within an hour." Anything larger and it's really a phase. Anything smaller and it's really a task that belongs in a handoff.

A workstream typically has:

  • A short name (WS-P1.1, WS2.4, WS-RH.1 — whatever scheme fits)
  • 1–3 deliverables
  • An exit criterion you can test for
  • An effort estimate ("~2 days", "~1 week")

Updating a plan

Plans are updated in place as work lands. A common pattern:

### WS-P1.1 — Structured timing output

- **WS-P1.1.a — Symbolic violation messages.** **Shipped 2026-05-02 in commit `0432d9a`.** New `WordSymbolMap` ...
- **WS-P1.1.b — `--timing-report <path.json>`.** **Shipped 2026-05-02 in commit `58a7a04`.** ...
- **WS-P1.1.c — `--timing-summary` text output.** **Shipped 2026-05-02 in commit `44e70a0`.** ...
- **WS-P1.1.d — Per-DFF worst-slack ranking.** Partially shipped in `58a7a04`. Remaining: ...

The pattern: bold the status, name the commit, say what shipped. The plan becomes a readable history once enough of it is filled in.

Scheduling vs. design split

A subtle but load-bearing rule: plans schedule; ADRs decide.

When you find yourself writing "we chose X over Y because Z" inside a plan, that paragraph belongs in an ADR. When you find yourself writing "the system works by …" inside a plan, that belongs in a design doc.

The plan should reference both — "per ADR 0007, Pillar B Stage 3 lands only if measurement justifies it" — without reproducing them.

Multiple plans, one roadmap

Once a project has more than two or three active plans, add a roadmap.md that orders them. The roadmap doesn't replace the per-phase plans; it sequences them. See Jacquard's post-phase-0-roadmap.md as a real example.

Template

A copy-paste-ready plan template lives at starter/docs/plans/_template.md.

Spike documents

A spike is a time-boxed experiment with a binary outcome — "did this idea work?" — written down before it's answered, resolved with a clear yes/no, and kept forever as the historical answer. The term comes from XP / agile usage and just means a short, sharp investigation; here it's formalised into its own document kind so the question and the answer are findable later.

Spikes pair naturally with Architecture Decision Records (ADRs): an ADR proposes a direction, a spike validates the foundational assumption, and depending on the outcome the ADR proceeds, gets amended, or gets superseded.

When to write one

Write a spike when you're about to invest non-trivial effort in validating an assumption that the project's direction depends on. Examples:

  • "Will library X parse our actual production inputs without re-engineering?"
  • "Can we hit the throughput target on the available hardware?"
  • "Does the proposed schema handle the edge cases we know about?"

The signal: the answer determines whether an ADR proceeds, gets superseded, or pivots. If you don't have an ADR (or proposed ADR) the spike informs, you might be doing exploratory research instead — that's fine, but it doesn't need a spike doc.

Anatomy

# Spike — <One-line question this spike answers>

**Status:** <Open | Resolved (YYYY-MM-DD) — <one-line outcome>>

**Time-box:** <e.g. "≤ 3 days; abort if Q1 hasn't passed by EOD day 1">

## Question

The single binary question this spike answers. If you find yourself
writing two questions, that's two spikes.

## Why this is in question

What we don't know, that we'd need to know to commit to ADR NNNN
(or to pivot ADR NNNN, or to write a new one).

## Approach

The plan for the time-box. Be specific:
- Q1 (sub-question 1, the cheapest first):
  - Step 1: <thing>
  - Step 2: <thing>
- Q2 (only attempted if Q1 passes):
  - ...

## Decision matrix

What we'll do under each outcome:

| Outcome | Means | Action |
|---|---|---|
| Q1 fails | Approach unworkable on our inputs | Spike resolves NO; ADR NNNN superseded |
| Q1 passes, Q2 fails | Partially viable, but blocking gap | Spike resolves PARTIAL; ADR NNNN amended scope |
| Both pass | Fully viable | Spike resolves YES; ADR NNNN unchanged; proceed |

## Findings

(Filled in as the spike runs. Written terse — these are evidence, not narrative.)

## Outcome

(Single short paragraph at resolution. The conclusion sentence
becomes the Status line.)

Time-boxing

A spike that runs longer than its box has lost the discipline. When the box expires:

  • If the answer is clear → resolve.
  • If the answer needs another N days → write that down, extend the box once, and proceed. Don't extend twice; if it needs that much investigation, it's no longer a spike — it's a research thread that probably needs its own ADR or design doc.
  • If you've gotten side-tracked → resolve as inconclusive, capture the side-track as a follow-up.

Resolving a spike

A resolved spike is never deleted. The Status line gets updated, the Outcome section is filled in, and any ADRs the spike informed get updated to reference the resolution.

**Status:** Resolved (2026-05-01) — Q2 fail; ADR 0003 superseded.

The spike file becomes a permanent part of the repo's "we tried this" record. Future contributors who look at a Superseded ADR follow the link to the spike to understand why.

Spike vs. ADR walk-back

These look similar but serve different purposes:

  • An ADR walk-back is a prediction — "if X happens, we'd revisit." Written before the fact.
  • A spike is an experiment — "we don't yet know if X works; here's how we'll find out." Written before the fact, resolved after.

A spike is cheap insurance against committing to an ADR whose foundational assumption hasn't been tested.

Template

A copy-paste-ready template lives at starter/docs/spikes/_template.md.

Handoff documents

A handoff is an ephemeral working-memory document that bridges a single session boundary — when you stop working and someone else (Claude or human) picks up — and is deleted once the work it describes is resolved. The name is literal: it's what you hand to the next session.

This is the most distinctive part of the discipline, and the one most easily abused without it.

Why this discipline exists

Decision rationale, technical context, and project state all already have natural homes:

  • Architecture Decision Records (ADRs) capture architectural decisions and their why.
  • Design docs capture how things work.
  • Plan docs capture what's left and the next workstream slices.

When that content lives in a handoff instead, two things go wrong:

  1. It's not where contributors look. A new contributor reading the README → ADR chain shouldn't have to dig through a stack of resolved handoff docs to find load-bearing decisions.
  2. It rots out of sync with reality. Handoffs are point-in-time snapshots. A "STATUS: RESOLVED" banner doesn't help when the thing referenced has moved or changed; the canonical doc is what should hold the current truth.

The discipline closes this gap by forcing migration before deletion. Every load-bearing piece of a handoff lands in its proper home (ADR / plan / spike / design doc / CHANGELOG) before the handoff file is removed.

What a handoff IS

A single markdown file at docs/handoffs/<topic>-handoff.md containing exactly what the next session needs to pick up where you left off:

  • Goal & next-up — what this session was trying to do, and what the very next concrete action is.
  • Done this session — commits landed, with one-line summaries.
  • Open follow-ups — the work that wasn't done, with enough scope detail to start cold.
  • Critical context — gotchas, surprising findings, environment specifics that aren't obvious from the code or docs yet.
  • Verification — the command(s) the next session runs to confirm the work is in the state you say it is.

Exactly one handoff exists at a time. There's no chain of resolved predecessors to wade through. If you find yourself with two, that's the smell test failing.

What a handoff IS NOT

  • Not a decision log. Decisions go in ADRs. If you find yourself writing "we chose X over Y because Z" in a handoff, that paragraph belongs in an ADR (or an existing ADR's Consequences / Walk-back section).
  • Not a design doc. "How subsystem A works" is a design topic; it lives in a free-form docs/<topic>.md, not in a handoff's "Critical context" section.
  • Not a status dashboard for the project. Workstream status lives in plan docs. A handoff cites a plan; it doesn't reproduce one.
  • Not a historical record. git log is the historical record. Handoffs that survive past their resolution turn into noise that misleads new contributors.

When to write one

Write a handoff at the end of any session that:

  1. Leaves work in a partial state that someone else might pick up cold.
  2. Captures non-obvious context the next session needs (e.g. "the find_timing proc rejects -full_update; use ::sta::find_timing_cmd 1 directly").
  3. Documents the next concrete step with enough scope to start without re-discovering it.

If the session ended at a clean stopping point (everything merged, all decisions documented in ADRs/plans, nothing surprising), don't write a handoff. The plan doc already says what's next.

Resolution: fold, then delete

The two-location split is deliberate: handoffs live at docs/handoffs/<topic>-handoff.md while in flight; their content migrates into the persistent docs (docs/adr/, docs/plans/, design docs under docs/) at resolution. The handoff file then gets removed; nothing about the work is lost because everything load-bearing has a permanent home elsewhere.

When a handoff's work is done — whether in the next session or several sessions later — every load-bearing piece of it must be migrated before the handoff file is deleted:

If the handoff says...It belongs in...
"We chose approach X over Y because Z"The relevant ADR's Decision/Consequences section, or a new ADR if no fit exists
"Future scope for WS-N: do A then B then C"The plan doc's WS-N section
"Gotcha: tool Z's API X behaves Y"A code comment near the call site, or a design doc if the gotcha cuts across files
"Build dep Z is required on Linux"The build script's apt-suggestion / Brewfile / README install section
"Subsystem A doesn't yet do B"Plan doc as a new open item, or an ADR-tracked walk-back if it's a deferred design choice
"Run cargo test --feature foo to verify"The verification block in the relevant plan doc, or a test-running section in CLAUDE.md

After migration, the handoff file is removed in the same commit as the migration:

git rm docs/handoffs/<topic>-handoff.md
git add <files-receiving-the-migrated-content>
git commit -m "docs: resolve <topic> handoff — fold into <where-it-went>"

The commit message records what migrated where — that's the audit trail. git log -- docs/handoffs/ then shows the project's handoff history (one add, one delete per session) without needing the files themselves to live forever.

Template

When you do need to write one, use this skeleton. Replace placeholders inline; delete sections that don't apply (better to omit a section than fill it with "N/A").

# Handoff — <Topic> (one-line summary of what this session left open)

**Created:** YYYY-MM-DD
**Working tree:** clean | <state if not clean>
**Branch:** main | <branch>

## Goal & next-up

**Goal of this session:** <what you were trying to do, in 1–3 sentences>

**Next session should pick up:** <the very next concrete action, by name. Reference the plan doc section if applicable.>

**Verification command:**
\`\`\`sh
<commands the next session runs to confirm this handoff's claimed state>
# Expect: <what success looks like>
\`\`\`

## Done this session

| Commit | Subject | Notes |
|---|---|---|
| `<sha>` | <subject> | <one-line note> |

## Open follow-ups (priority-ordered)

### 1. <Item name> (<rough size>)

<Concrete scope. Enough detail to start cold. Link to existing plan/ADR/design-doc sections rather than reproducing them.>

### 2. ...

## Critical context

<Things the next session needs to know that aren't yet in the code/docs.
Be honest about what's truly load-bearing — anything obvious from
`git log` or a quick `grep` doesn't belong here.>

## References

- [`<predecessor-handoff if any>`](<path>) — predecessor (if relevant)
- [`<plan doc>`](<path>) — current workstream state
- [`<ADR>`](<path>) — relevant decision

## Migration note

<When this resolves, what migrates where. Helps the next session
do the fold-and-delete cleanly.>

Tooling

The create_handoff and resume_handoff skills (from various Claude Code orchestration toolkits) generate and consume handoffs. They're optional — the discipline above is the load-bearing artefact. A handoff written by hand following the template is just as valid.

If you use one of those skills, expect it to default to YAML format under thoughts/shared/handoffs/ with database indexing. That doesn't apply here. Override it: produce markdown at docs/handoffs/<topic>-handoff.md and skip the database step. The skill activation is informational; the project's convention takes precedence.

The CLAUDE.md snippet in this repo's starter/CLAUDE.md.snippet includes the override note so Claude Code picks it up automatically.

A worked example

A real handoff resolution from Jacquard:

  1. Session 1: deep-investigation work on a vendored dependency revealed a license-tagging bug upstream. The session ended with the bug filed but not yet fixed, plus a list of follow-ups blocked on different self-hosted runners coming back online. A handoff pre-release-runners-handoff.md was written.
  2. Session 2 (days later): one of the open follow-ups was unblocked. The fix was implemented, and the Critical context gotcha (an OpenSTA Tcl quirk) was migrated into a code comment near the Tcl call site. The HIP/CUDA routing notes from the handoff's open follow-ups migrated into the plan doc as a new "WS-RH.2" workstream.
  3. At resolution: git rm docs/handoffs/pre-release-runners-handoff.md in the same commit as the plan-doc edit. The commit message: "docs: resolve pre-release-runners handoff — fold HIP/CUDA routing into post-phase-0-roadmap WS-RH.2."

git log -- docs/handoffs/ now shows: one add, one delete. The audit trail is intact; the handoff itself is gone.

Release process

A lightweight release process complements the four-document discipline. It doesn't have to live alongside Architecture Decision Records (ADRs), plans, and handoffs, but the same instincts apply: keep it short, version-control the steps, make the punch-list visible.

Shape

Two distinct documents are useful:

  1. CHANGELOG.md at the repo root, in Keep a Changelog format. The historical record of what shipped when.
  2. docs/release-process.md describing how releases are cut. Short, prescriptive, version-controlled. Includes a pre-release punch list of one-time items that block the first numbered release.

Why a pre-release punch list

Most release processes on the internet assume v1.0 has already shipped. The trickiest moment is before that, when "we should release at some point" turns into "what's actually blocking us?"

A visible, version-controlled punch list makes that explicit:

## Pre-release checklist (one-time, before the first numbered release)

- [x] Phase 1 closed (per `docs/plans/phase-1.md`).
- [x] License posture confirmed; `NOTICE` file in place.
- [ ] All CI jobs green on `main`.
- [ ] End-to-end test for the headline feature.
- [ ] User-facing docs cover the install path.

Each item is a check-box. As they land, they're checked off in the same PR that landed them. When all are checked, you cut a release.

When to release

Cut a release when:

  • A user-visible feature or fix lands that you want consumers to be able to pin against.
  • Schema or CLI changes happened and consumers need a stable reference point.
  • A meaningful chunk of work in docs/plans/ is closed (e.g. a phase exits all criteria).

There is no fixed cadence. Time-based releases force releases when there's nothing user-visible to release; demand-driven releases avoid the noise.

Versioning

SemVer, starting once the first numbered release ships. Pre-1.0 versions (0.x.0) carry the standard SemVer caveat: minor bumps may include breaking changes; specific stable contracts (a JSON schema, a binary format) are documented in their own ADRs and follow stricter rules.

A useful pattern is to declare the stable contracts explicitly:

Stable contracts (additive-only, breaking changes require a major
bump and a deprecation window):

- `--report` JSON schema — `src/report.rs::SCHEMA_VERSION`,
  governed by ADR 0008.
- IR FlatBuffers schema — `crates/ir/schemas/ir.fbs`,
  governed by ADR 0002.

Everything not listed is fair game to evolve.

Steps for cutting a release

1. **Verify CI is green** on `main` for all jobs. If a job is offline, hold the release until it's restored.

2. **Roll the `[Unreleased]` section in `CHANGELOG.md` into a numbered version block.** Update the link references at the bottom. Leave a fresh empty `[Unreleased]` section.

3. **Bump version** in your project's manifest (`Cargo.toml`, `package.json`, etc.). Re-build to update the lockfile.

4. **Commit:** `chore: release v<X.Y.Z>` with whatever attribution conventions your project uses.

5. **Tag:** `git tag -a v<X.Y.Z> -m "v<X.Y.Z>"` then `git push --tags`.

6. **Create a release** on the platform of your choice from the tag. Body = the CHANGELOG section for that version.

What does NOT need to change at release time

Be explicit about this — it stops well-meaning maintainers from sweeping in changes that should land in their own PRs:

  • Vendored / submodule pins (unless deliberately bumping a vendored dep).
  • License files (unless re-licensing).
  • Long-lived design docs (unless the release changes the design they describe).

Template

A copy-paste-ready release-process.md with placeholders lives at starter/docs/release-process.md.

Adopting this in a new project

Three steps. Five minutes if your project is fresh; longer for an existing project where you'll need to backfill a few Architecture Decision Records (ADRs).

Step 1 — Drop in the templates

# from this repo's root
cp -r starter/docs/* /path/to/your-project/docs/

That gives your project:

docs/
├── adr/
│   ├── README.md          ← ADR conventions + index
│   └── 0000-template.md   ← copy this for each new ADR
├── plans/
│   └── _template.md
├── spikes/
│   └── _template.md
├── handoffs/
│   └── _template.md
├── handoff-discipline.md  ← the migration rule, in full
└── release-process.md     ← optional companion

The empty directories (adr/, plans/, spikes/, handoffs/) signal intent. Even if they're empty for a while, the structure is there.

Step 2 — Append the CLAUDE.md snippet

cat starter/CLAUDE.md.snippet >> /path/to/your-project/CLAUDE.md

This adds a section to your project's CLAUDE.md that:

  • Points Claude at docs/handoff-discipline.md for the migration rule.
  • Overrides the default thoughts/shared/handoffs/ location used by some Claude Code skill toolkits.
  • Lists the three "where does X go?" rules so Claude doesn't have to re-derive them.

If your project doesn't have a CLAUDE.md yet, the snippet works as a complete starter.

Step 3 — Backfill (existing projects only)

If you're adopting this on a project that already has months of decisions baked into the code:

  1. Skim recent commits and PRs for "we decided to …" / "we chose …" rationales. Each becomes a candidate ADR.
  2. Don't try to backfill everything. Backfill only the decisions that are currently load-bearing — the ones a new contributor needs to know.
  3. Backdate the Status: line when you do backfill: Accepted (2025-08-15, retroactive). Be honest about the retro nature.
  4. Write the first plan doc capturing what's actually in flight right now. Often this is just promoting the team's existing tracking into a doc.

A reasonable backfill is 3–5 ADRs and 1 plan doc. If you find yourself writing 30 ADRs, you're treating ADRs as documentation rather than load-bearing decisions.

Optional — wire up a docs site

If you want the docs rendered as a navigable site (not just files in docs/), the simplest path is mdBook (matches how this very repo is built):

cd /path/to/your-project
cargo install mdbook              # or: brew install mdbook
mdbook init --title "<Project>"   # creates book.toml + src/

Then move (or symlink) docs/ content under mdBook's src/, write a SUMMARY.md, and you're done. See this repo's book.toml and src/SUMMARY.md for a working example.

For projects that prefer Python-flavoured tooling, MkDocs (with the Material theme) is an equally good choice and renders the same content with no changes.

What "good adoption" looks like a month later

  • 2–8 ADRs in docs/adr/, each with a clear Status line. At least one Superseded by a later ADR or by a spike.
  • 1–3 plan docs in docs/plans/. The active one updated in place; older ones with Status: Closed.
  • A handful of resolved spikes in docs/spikes/. Each Resolved with a one-line outcome.
  • docs/handoffs/ has either zero files (clean stopping point) or one file (work in flight).
  • git log -- docs/handoffs/ shows a clean rhythm of one-add-one-delete commits.

What "bad adoption" looks like

  • docs/handoffs/ has 4+ files, each with STATUS: RESOLVED banners. → The fold-then-delete rule isn't being followed.
  • Every commit gets a new ADR. → ADRs are being written for trivial decisions.
  • Plans are written once and never updated. → Plans are being treated as ADRs.
  • ADRs include "step 1: do X, step 2: do Y" implementation steps. → ADRs are being treated as plans.

If you see these patterns, re-read the Overview and the migration table in Handoff documents. The four kinds want to bleed into each other; the discipline is the dam.

Working with Claude Code

This discipline emerged from many months of human-and-Claude-Code collaboration. A few patterns are worth calling out specifically because they're load-bearing to how the discipline survives long sessions.

What Claude Code does well with this convention

  • Reading Architecture Decision Records (ADRs) before suggesting changes. Once CLAUDE.md points at docs/adr/, Claude reads them. ADRs become guard rails that prevent re-litigating decided questions.
  • Updating plan docs in place. "Mark WS-P1.1.b as shipped in commit <sha>" is a low-friction request that produces a clean diff.
  • Writing handoffs at session end. When asked, Claude produces a handoff that follows the template. The discipline of "what's in flight, what's blocked" is a natural fit for end-of-session reflection.
  • Folding handoffs at session start. "Resume from docs/handoffs/foo-handoff.md" can be combined with "and migrate any resolved items into the plan doc, then delete the handoff if everything's resolved."

What Claude Code needs explicit help with

  • Skill toolkit defaults. Various Claude Code skill toolkits (the create_handoff / resume_handoff family) default to YAML under thoughts/shared/handoffs/ with database indexing. These overrides need to be in CLAUDE.md so Claude picks the project convention over the skill default. The starter snippet handles this.
  • Knowing when not to write a handoff. Without a hint, Claude tends to write one at every session end. The CLAUDE.md snippet says "skip the handoff if the session ended at a clean stopping point."
  • Distinguishing ADR-worthy from commit-message-worthy. Without a hint, Claude tends to over-write ADRs for trivial decisions. The CLAUDE.md snippet includes the test: would someone six months from now wonder why this was chosen?

The CLAUDE.md snippet

The full text is in starter/CLAUDE.md.snippet in this repo. Append it verbatim to your project's CLAUDE.md.

The snippet covers:

  1. Where each kind of doc lives.
  2. The three smell tests (handoff overflow, ADR overflow, plan stagnation).
  3. Override notes for the YAML/database default of various skill toolkits.
  4. A pointer back to this repo for the long-form discipline.

Useful one-shot prompts

A few prompts that work well with this convention:

At session start (resuming work):

Read docs/handoffs/ — there's a single handoff there. Confirm the verification command's expectation, then proceed with the next-up item.

At session end (work in progress, partial state):

Write a handoff at docs/handoffs/{topic}-handoff.md. Use the template at docs/handoffs/_template.md. Goal: {X}. Open follow-ups: {list}. Critical context: {gotchas}.

At session end (work fully resolved):

Migrate the contents of docs/handoffs/{topic}-handoff.md into docs/plans/{plan}.md and docs/adr/{NNNN}-{…}.md, then git rm the handoff. Combine into a single commit.

Mid-session (decision point):

Before we decide on {X} vs {Y}, draft an ADR proposing {X}. Use the template at docs/adr/0000-template.md. Status: Proposed. Number it next available.

Mid-session (validating an assumption):

Write a spike at docs/spikes/{topic}.md with the question {Q}, time-box of 2 days, and a decision matrix for the three plausible outcomes. Status: Open.

Anti-patterns to watch for

Claude has a few biases that this discipline mostly resists, but watch for:

  • Reaching for handoffs as the universal output format. A handoff is for ephemeral working memory. If Claude proposes writing a "handoff" that contains Why we chose X over Y, redirect to an ADR.
  • Reproducing plan content inside ADRs. "ADR 0007 — Roadmap for the Q3 work" is a plan doc, not an ADR. Redirect.
  • Mixing decision and rationale into commit messages. Commit messages should reference the ADR, not embed it. "Per ADR 0006, …" is the right shape.

Pairing this with other Claude Code conventions

Nothing here conflicts with the broader Claude Code conventions you might already use:

  • CLAUDE.md as the primary instructions file. Yes — this discipline lives in CLAUDE.md.
  • Memory systems (the ~/.claude/memory/ user-memory pattern). Memory and ADRs cover different things: memory is cross-project, about the user and their preferences; ADRs are project-specific, about the code and its design. They don't compete.
  • Skill toolkits (create_handoff etc.). These work fine with the convention as long as the directory and format overrides are in CLAUDE.md.

FAQ

Why four kinds of doc? Isn't that a lot?

The four kinds correspond to four genuinely different lifetimes:

  • Forever, immortal (Architecture Decision Record, ADR)
  • Long-lived, kept current (plan)
  • Forever, marked Resolved (spike)
  • Ephemeral, deleted at resolution (handoff)

Collapsing any pair leads to a class of bug. ADR + plan collapsed → "decisions" that get rewritten as scope changes. Plan + handoff collapsed → can't tell what's currently committed vs. what was just last week's brainstorm. Handoff + ADR collapsed → handoffs survive forever and pile up.

The cost of four kinds is low (each has a clear template). The cost of fewer kinds is high (the ones that remain become overloaded).

Why mdBook over MkDocs / Docusaurus / VitePress?

No strong reason. mdBook is what this repo uses because (a) it's Rust-native and matches the Jacquard project the discipline was extracted from, (b) it has zero JavaScript and a fast build, and (c) the default theme is good enough out of the box. MkDocs (especially with the Material theme) is an equally fine choice and the underlying Markdown content needs no changes.

The discipline itself doesn't care. Plain docs/ files in your repo, viewed on GitHub, work fine.

What if my project genuinely doesn't have any non-obvious decisions yet?

Then don't write any ADRs yet. The discipline doesn't require backfilling decisions that aren't there.

That said, "we deliberately chose not to use X" is itself an ADR-worthy decision. If your project's shape was influenced by an explicit choice — even a "we'll keep it simple and not bring in framework Y" — write that down.

How do I handle a decision that was made before this discipline was adopted?

Backfill an ADR with Status: Accepted (YYYY-MM-DD, retroactive). Be honest about the retroactive nature. Don't invent commitments that weren't actually made; just record what's currently true.

Can a handoff reference a previous handoff?

Yes, in the ## References section. But it's a smell — handoffs aren't supposed to chain. If you find yourself referencing a previous handoff, ask whether the previous one's content should have been folded into a plan or ADR instead of carried forward.

What if the next session needs context from three previous handoffs?

That context belongs in the plan doc, not in a chain of handoffs. Migrate it.

Do I need a release-process.md?

Only if your project produces releases (libraries, binaries, services with versions consumers can pin against). For internal services with continuous deployment, you probably don't.

Does the same person who creates a handoff have to resolve it?

No. The whole point of the handoff template is that it captures enough context for someone else (Claude, a colleague, future-you) to pick up cold. The verification command at the top is the safety net — it confirms the handoff's claimed state matches reality before the new session starts work.

Does this work for non-software projects?

Probably. The four memory horizons (decision, plan, spike, handoff) are general enough. The templates would need light adjustment (verification commands wouldn't be cargo test). If you try it for, say, a writing project or a research project, file an issue with what worked and what didn't.

What's the relationship between this and the arc42 / diátaxis / C4 / RFC frameworks?

Adjacent but different scopes:

  • arc42 is a software architecture documentation template. It would fit alongside this as the structure of your docs/<subsystem>.md design docs. It doesn't address the lifecycle question.
  • Diátaxis classifies user-facing documentation (tutorials, how-tos, references, explanations). This discipline is about internal project memory (decisions, plans, spikes, handoffs); the two are complementary.
  • C4 model describes diagram conventions for software architecture. Orthogonal — use C4 inside your design docs if you want.
  • IETF RFCs are similar in spirit to ADRs but for cross-team standards. ADRs are RFCs scoped to a single project.

Nothing here conflicts with any of those; they fill in different parts of the documentation surface area.

Is this proprietary to Claude Code?

No. The discipline is plain-Markdown, plain-git, and tool-agnostic. The CLAUDE.md snippet is Claude-specific because Claude Code happens to read that file by convention; an analogous snippet for any other AI coding assistant (or none at all) would work equally well. The core convention is older than AI coding assistants.

Glossary

Terms and acronyms used throughout this book.

ADR — Architecture Decision Record

A short, numbered markdown file capturing a non-obvious design choice and the rationale behind it. Lives at docs/adr/NNNN-<short-name>.md. Never deleted; amended in place when scope expands; superseded (with the old one preserved) when replaced.

The term and the basic three-section structure (Context / Decision / Consequences) come from Michael Nygard's 2011 post. The discipline in this book extends that with an explicit Status lifecycle and amendment rules.

See: Architecture Decision Records.

Plan doc

A scheduling document at docs/plans/<topic>.md that orders the work an ADR (or set of ADRs) has committed to. Organised into phases and workstreams with explicit entry/exit criteria. Updated in place as work lands.

See: Plan documents.

Spike

A time-boxed experiment, written up at docs/spikes/<topic>.md, that answers a single binary question — "did this approach work?" — before the project commits to it. The term comes from XP / agile usage. Resolved with a clear yes/no/partial outcome and kept forever as the historical answer.

See: Spike documents.

Handoff

An ephemeral working-memory document at docs/handoffs/<topic>-handoff.md that bridges a single session boundary. Folded into ADRs / plans / spikes / design docs at resolution and then deleted in the same commit as the migration. Exactly one handoff exists at a time.

See: Handoff documents.

Workstream (WS)

A unit of scoped work within a plan doc, typically named with a numeric or alphabetic suffix (WS1, WS-P1.1, WS-RH.2). Sized roughly so a contributor can pick it up cold and start within an hour. Each workstream has 1–3 deliverables and an exit criterion.

Phase

A coarse-grained grouping of workstreams within a plan, with explicit entry and exit criteria. A plan typically has one or more phases; phases close when their exit criteria are met.

Fold-then-delete

The migration rule for handoffs: every load-bearing piece of a handoff must be migrated to its proper home (ADR / plan / spike / design doc / CHANGELOG.md) before the handoff file is git rm'd. The migration and deletion go in the same commit so the audit trail is intact.

Walk-back

A section in an ADR that names the conditions under which the decision should be revisited, and how to revisit it cheaply. Distinct from a spike: walk-backs are predictions about future re-evaluation; spikes are experiments run before committing.

CI — Continuous Integration

The automated build/test pipeline that runs on every push or pull request. Used in the release process as a gating signal ("don't release if CI is red").

PR — Pull Request

A proposed change to a repository, reviewed before merge. Used loosely throughout this book to mean "a unit of reviewed change," regardless of platform (GitHub PR, GitLab MR, Gerrit changeset, etc.).

SemVer — Semantic Versioning

The SemVer 2.0.0 spec for MAJOR.MINOR.PATCH version numbers, where breaking changes bump MAJOR, additive changes bump MINOR, and bugfixes bump PATCH. Pre-1.0 (0.x.y) versions carry the standard caveat that minor bumps may include breaking changes.

CLI — Command-Line Interface

The set of subcommands, flags, and arguments a binary exposes. Distinct from internal APIs: a project's CLI is a user-facing contract and gets versioned accordingly.

Status (ADR / spike)

A line near the top of an ADR or spike file that names its lifecycle state. ADR statuses: ProposedAccepted (YYYY-MM-DD)Superseded by ADR NNNN (YYYY-MM-DD) or Withdrawn (YYYY-MM-DD). Spike statuses: OpenResolved (YYYY-MM-DD) — <one-line outcome>.

Migration table

The table inside the handoff chapter (and in docs/handoff-discipline.md in adopting projects) mapping each kind of handoff content to its destination home. The single most important reference for resolving a handoff.