Purpose: define the format, fields, states, and lifecycle of an individual work unit — the
BL-###item. Items are the smallest atom of tracked work in this methodology. Every concrete change to the product corresponds to one or more items.
Items live inside epics (see 03_epics.md). Each item has a unique project-wide ID, a frontmatter table of structured fields, and a body of acceptance criteria and notes. They progress through a defined lifecycle, gated at the end by the Definition of Done.
A backlog item is:
BL-### where ### is a monotonically increasing integer assigned at filing.BACKLOG.md until it is done or rejected, at which point it moves to that epic’s ARCHIVE.md.Item IDs are monotonic and project-wide.
BL-001 is the first item ever filed. BL-1247 is the 1247th.grep -roh 'BL-[0-9]\+' backlog/ | sort -t- -k2 -n | tail -1) and incrementing.rg "BL-0428" backlog/ finds the item regardless of which folder it now lives in.BL-0428 always points at the same item, even if the item has been re-routed.BL-005 is older than BL-503. Useful for at-a-glance triage.ls-able. Easier to scan in a long file.Use three to four digits with zero padding (BL-0001, BL-0042, BL-1247). Keeps lexical sort matching numerical sort up to a sensible project size. Once a project exceeds 9999 items, widen the padding.
Items in FUTURE.md (deferred to a later phase) can use one of two ID schemes. Pick one per project and stay consistent.
BACKLOG.mdItems in FUTURE.md use the same project-wide monotonic numbering as active items. Example: BL-0428 lives in FUTURE.md until it’s promoted to BACKLOG.md, at which point it keeps the same ID.
Items in FUTURE.md use scoped IDs like BL-E02-F01, BL-E02-F04. When promoted to BACKLOG.md, the item is renumbered to a real BL-### from the monotonic counter.
Either is fine. The project picks one in its instruction file or backlog README and stays consistent. Record the choice explicitly — a one-line entry in CLAUDE.md / AGENTS.md / the backlog README.md saves every future contributor the cost of grepping FUTURE.md and guessing from the IDs they see. Mixing schemes inside the same project produces confusion.
The item begins with a level-3 heading:
### BL-### — <Short imperative title>
###). Higher levels are reserved for BACKLOG.md section dividers (e.g., “Open” vs. “In progress”).—) separates ID and title. A regular hyphen also works; pick one and stay consistent across the project.Imperative titles read like commits and PR titles. The reader sees what the change does. A noun-phrase title (“CSV export”) leaves the reader unclear whether it is a feature ask, a bug, a refactor, or a doc edit.
Immediately below the heading, every item carries a frontmatter table with the following fields:
| Field | Value |
|----------|----------------------------------------|
| Epic | E<NN>-<slug> |
| Pillar | P<#> |
| Priority | P0 \| P1 \| P2 \| P3 |
| Effort | XS \| S \| M \| L \| XL |
| Status | backlog \| ready \| in-progress \| under-review \| to-be-tested \| done \| blocked \| rejected |
| Test | not-tested \| pending \| manual-verified \| partial \| pass \| fail: <detail> \| regression-needed \| n/a |
| Deps | BL-### or — |
| Lock | — or <holder>@<ISO-8601-timestamp> |
The table comes before any body text. It is the at-a-glance summary of the item’s state and is parsed by automation and humans alike.
— (em dash) for “not applicable” rather than omitting the row.| as the markdown separator. Inside values that need a literal pipe, escape with \| or use a different character.Status, Test, and the body — except an approved goal and Done means:, which the lock does not grant rights over (see Frozen intent). Other fields (Pillar, Epic) can be edited by anyone but should not be edited without reason. For subagent flows (05 “Subagent delegation”), the orchestrator is the lock-holder — subagents inherit edit rights through the orchestrator and do not acquire their own lock.The assurance profile is selected before implementation. A0/A1 items may inherit it and their human owner from the project/epic without extra rows. A2/A3 items require both rows, even when inherited:
| Assurance | A2 — material |
| Accountable owner | <named human or human-held role with current holder recorded in the repo> |
In the body, record required reviewers/evidence and the human acceptance decision or durable reference, including date and accepted residual risks. The profile rules govern readiness, escalation, and completion; an absent row never downgrades risk. These fields add no Status or Test values.
P0 | P1 | P2 | P3
| Value | Meaning |
|---|---|
| P0 | Ship-blocker or outage. Drop everything; fix this first. Used sparingly. |
| P1 | Required for the current phase. The phase cannot complete without this item. |
| P2 | Planned for the current phase but not blocking. Most items live here. |
| P3 | Nice-to-have. Future. Typically moved to FUTURE.md rather than worked. |
BACKLOG.md is a smell. P3 items belong in FUTURE.md. If a P3 item is in BACKLOG.md, ask why it is being kept warm.Re-prioritization is a deliberate event, not a passive drift.
XS | S | M | L | XL
| Value | Rough time bound | Typical shape |
|---|---|---|
| XS | Under 2 hours | A tiny fix, a config change, a one-line correction with a single test. |
| S | Half a day | A small feature or fix touching 1–3 files. |
| M | 1–2 days | Most items. A focused feature, a meaningful fix, a small refactor. |
| L | 3–5 days | A larger feature spanning multiple modules; a non-trivial refactor. |
| XL | 1–2 weeks | The upper bound. Anything beyond is a candidate for splitting into multiple items. |
An item that genuinely takes more than two weeks of focused work should be split. The split units are usually:
Splitting before starting is cheaper than splitting mid-flight.
Effort and priority are independent. A P1 item can be XS; a P3 item can be XL. The two fields answer different questions:
Together they help prioritize the picking queue: P1-XS items are usually picked first because they unblock big phase outcomes cheaply; P2-L items are scheduled as capacity allows.
The default rule for picking the next item to work on: highest impact per unit of effort. With Priority: (P0–P3) and Effort: (XS–XL) both set on every active item, the picking order falls out naturally.
| Priority | Effort | What to do |
|---|---|---|
| P0 | any | Drop everything. Work this. |
| P1 | XS, S | Pick first among non-P0 work. Cheap items that unblock big outcomes. |
| P1 | M, L, XL | Schedule deliberately. Don’t grab opportunistically. |
| P2 | XS, S | Good for sprint slack and gap-filling. |
| P2 | M+ | Defer unless explicitly scheduled. |
| P3 | any | Likely belongs in FUTURE.md, not in active BACKLOG.md. |
This is the default heuristic, not a contract. The user (or epic owner) can override when product reasons require it — but the override is explicit, not silent.
Without a rule, contributors (especially AI agents in autonomous mode) tend to pick whichever item caught their attention. The results: many partially-touched items, no compounding wins, cheap-and-impactful items perpetually skipped for whatever felt interesting.
The ROI heuristic biases toward shipping the highest-value work per unit of time invested. Over many items, the small high-value wins land daily, while larger work gets scheduled with full awareness of cost.
When a contributor (human or AI) picks a non-ROI-optimal item, the commit message or the item body should say why. This prevents future-you (or another contributor) from re-deriving the reasoning, and it’s the kind of decision that often becomes a memory entry (see 08_lessons_and_memory.md).
The autonomous-loop prompt template explicitly uses this heuristic. The loop picks the highest-impact-per-effort ready item, executes through the DoD, archives, and picks again. The heuristic is what keeps the loop converging on milestone-level outcomes rather than meandering through interesting-but-low-impact work.
backlog | ready | in-progress | under-review | to-be-tested | done | blocked | rejected
| Status | Meaning |
|---|---|
backlog |
Filed but not yet ready for work. Missing detail, blocked on a dependency, awaiting refinement. |
ready |
All prerequisites met — no unresolved Deps:, no open Needs clarification marker. The item can be picked up immediately by any contributor with the right context. |
in-progress |
A contributor is actively working this item. Lock: is set. |
under-review |
Code complete. Awaiting review, CI, or merge. |
to-be-tested |
Review passed, merged or ready to merge, awaiting UI verification per Definition of Done. |
done |
All gates passed. The item is closed. Requires Test: pass. |
blocked |
Cannot proceed. A Blocker: body line names what is in the way. |
rejected |
A decision was made not to do this. The item stays as a record. |
The canonical names above are the methodology’s defaults. Projects sometimes use shorthand variants that are recognized as equivalent:
| Canonical | Common alias | Notes |
|---|---|---|
backlog |
todo |
Some teams prefer todo; treat as equivalent to backlog. Pick one per project and stay consistent. |
| (no canonical) | future |
Used specifically for items living in FUTURE.md. Not a transition state — once promoted, the item flips to backlog or ready. |
| (no canonical) | parked |
An item set aside indefinitely (priority shifted, not enough info). Project may use this when blocked doesn’t fit (nothing concrete blocks it; just deprioritized). Document the choice. |
Adding aliases is fine; inventing new lifecycle states is not. Stick to the eight canonical states’ semantics; if a project needs a new state, that’s a methodology-change candidate (T2/T3 per the tier matrix in templates/AUTONOMOUS_LOOP.md).
backlog ──> ready ──> in-progress ──> under-review ──> to-be-tested ──> done
backlog | ready | in-progress | under-review | to-be-tested ──> blocked ──> ready (when unblocked)
backlog | ready ──> rejected
in-progress should have its Lock: field set in the same commit.to-be-tested to done must have Test: pass. There is no other path to done.blocked items must carry a Blocker: body line. An item that says blocked without saying what is blocking it is not actionable.rejected items remain in ARCHIVE.md with a brief note explaining why they were rejected. Do not delete; the record helps future contributors avoid relitigating.Items occasionally move backward — a review uncovers a bigger problem than expected, a test fails, the user reports the change is wrong. Move the status back to the appropriate stage (in-progress if more work is needed, under-review if a re-review is needed) and continue. Backward transitions are normal; covering up an issue to keep the status forward is not.
not-tested | pending | manual-verified | partial | pass | fail: <detail> | regression-needed | n/a
| Value | Meaning |
|---|---|
not-tested |
No verification has been attempted yet. Default for new items. |
pending |
Tests exist (written or scaffolded) but not yet executed against this item. Often the intermediate state right before pass or fail:. |
manual-verified |
Verified by a human (UI walkthrough, manual reproduction of the bug fix) but no automated test exists. Acceptable for Status: done when automation is impractical (e.g., third-party API quirks); flag for follow-up regression-test item. |
partial |
Some test surface exists and passes; some is missing or pending. Example: “happy path tested; error-path test not yet written.” Cannot transition to Status: done; explicit limitation. |
pass |
All gates of Definition of Done passed. Required for Status: done. |
fail: <detail> |
A gate failed. The <detail> names what failed and how. |
regression-needed |
A regression test is required (typically for a bug fix) and has not yet been added. |
n/a |
The item has no testable behavior (e.g., a pure docs change, a folder-creation chore). Acceptable for Status: done when genuinely applicable; using n/a to dodge testing is the anti-pattern. |
The Test field may also carry artifact references — a file path, PR number, or short note pointing to where the verification evidence lives. These pair with an enum-style value when more traceability helps:
Test: pass — apps/api/src/test/admin.test.ts (19 tests)
Test: pass — manual UI walkthrough; PR #160 review notes
Test: partial — Google branch unit-tested; e2e pending real client ID
Test: pending — apps/web/src/hooks/__tests__/useEntitlements.test.ts (scaffolded)
The enum value at the start makes the field greppable + machine-readable; the free-form tail makes it humanly useful. Prefer this two-part form for non-trivial items where the verification evidence isn’t obvious from the item body.
Status is about workflow position (where in the flow this item is). Test is about verification truth (does this work). They are not the same thing.
A common failure pattern is to flip Status: done while Test: still reads not-tested. The separate Test field makes this impossible to do accidentally — the hard rule below makes it explicit.
When a gate fails, capture what specifically failed in the value:
fail: UI verification — dark theme contrast broken on the detail page.fail: unit tests — three new tests fail after schema migration.fail: regression — bug reappears under offline state.Vague fail values do not help the next contributor. Specifics do.
Used specifically when a bug fix has been written but the test that proves the bug is fixed (and won’t return) has not been added yet. This is a meaningful in-between state — the fix works, but the safety net is missing. Move to pass once the regression test exists and passes.
manual-verified and n/a are acceptable for Status: doneThe hard rule below requires Test: pass for Status: done. The two extensions:
manual-verified → done IS allowed when automated testing is genuinely impractical for the change and a follow-up regression-needed item exists to backfill automation when feasible. The item body must document why automation was skipped. Manual-verified without a follow-up regression item is the cheating-agent anti-pattern; don’t do it.n/a → done IS allowed for items with no testable behavior (folder creation, README edits, repo-state chores). The item body must note why it’s n/a; “I didn’t feel like writing tests” is not a valid reason.For everything else, the canonical path remains not-tested → pending → pass → done.
Status: donerequiresTest: pass,manual-verified(with a regression-needed follow-up), orn/a(with a body-documented reason).
There is no other path. Not from not-tested. Not from pending. Not from partial. Not from fail:. Not from regression-needed (without a backing pass).
This rule is the single most important enforcement point in the methodology. See 07_definition_of_done.md for the gates that produce Test: pass.
If the gates cannot be passed for some reason, the item stays at to-be-tested, under-review, or blocked. Not done.
The two narrow extensions (manual-verified, n/a) exist to keep the methodology honest about real-world cases where automated testing is genuinely impractical or doesn’t apply — without becoming an escape hatch. Both require explicit body documentation; both are auditable in the next eval cycle.
Deps: BL-### or —
Deps: BL-0428.Deps: BL-0428, BL-0517.—.Deps: field.Lock: — or <holder>@<ISO-8601-timestamp>
Documented in full in 05_locks_and_parallel_work.md. Short summary:
— — no lock. Free to take.<holder>@<timestamp> — locked until the timestamp expires.Acquiring an item to work on it requires setting this field. Releasing it requires clearing the field back to —.
Lock + Status + TestThe three state-bearing fields are not independent — they move together. Never edit one without checking the others. The table below shows valid combinations for typical outcomes:
| Outcome | Lock |
Status |
Test |
|---|---|---|---|
| Item just picked up; work starting | <id>@<now+TTL> |
in-progress |
not-tested |
| Code complete; PR open; awaiting review | <id>@<refreshed TTL> |
under-review |
not-tested |
| Reviewer requested changes; back to work | <id>@<refreshed TTL> |
in-progress |
(unchanged) |
| Code merged; automated tests green; no UI to verify | — |
done |
pass |
| Code merged; manual UI verification pending | — |
to-be-tested |
not-tested |
| Code merged; UI verified per DoD | — |
done |
pass |
| Tests revealed a bug; fix coming in same PR | <id>@<refreshed TTL> |
in-progress |
fail: <one-line> |
| Bug-fix shipped; regression test not yet written | <id>@<refreshed TTL> |
in-progress |
regression-needed |
| External blocker discovered | — (release the lock) |
blocked |
(unchanged) |
| Abandoning the work entirely | — |
backlog or rejected |
(unchanged) |
Status: done + Test: not-tested is a half-truth. The hard rule forbids it.Status: in-progress + Lock: — means no one is actually holding it. Acquire the lock or revert the status.Status: blocked + Lock: <future-timestamp> is broken. Blocked work shouldn’t hold a lock because the wait time will exceed the TTL — release it so other items aren’t starved.Status: under-review + Lock: — means the reviewer is in limbo. Refresh the lock during review so it’s clear who’s responsible for the next move.When changing any one of these three fields, ask: which of the other two should change with it?
Below the frontmatter table, the item body has these sections, in this order:
**Why / Description:** <one paragraph — problem, approach, what "done" looks like>
**Approach:** (optional, multi-step)
1. <Step one>
2. <Step two>
3. ...
**Done means:**
- [ ] <Acceptance criterion 1>
- [ ] <Acceptance criterion 2>
- [ ] <Acceptance criterion 3>
**Files (probable):** (at Effort M+ this becomes a **Code Map** — each path
annotated with its role, the utilities to reuse, the non-obvious constraints)
- `<path/to/likely/file/1>`
- `<path/to/likely/file/2>`
**Blocker:** (only if Status: blocked)
<One-line reason. Reference to the blocker — another item, an external dependency, a decision pending.>
**Notes:** (optional)
<Any additional context, design decisions, or links worth preserving.>
Why / Description. One paragraph. Three to seven sentences. Must answer: What is the problem? What is the approach? What does done look like? The reader should be able to decide if this item is worth picking up from the description alone.
Approach. Optional. For items with non-obvious sequence — a refactor, a multi-step rollout, a coordinated change. Numbered list. Each step is actionable.
Done means. Required. Acceptance criteria as checkboxes. These are the item-level exit criteria. They are not the Definition of Done gates (those are universal). They are the specific conditions for this item.
Examples:
[ ] Export button visible on the report page.[ ] Clicking the export button downloads a CSV with the right columns.[ ] Tests cover the success and the empty-data cases.Files (probable). A best-guess list of files the change will likely touch. Not authoritative — the real diff may differ — but useful as a starting point for the contributor and as a way to surface conflicts (“two items will probably touch the same file, mind the merge”). At Effort M and above, this section is upgraded to a Code Map — see The Code Map below.
Blocker. Required if and only if Status: blocked. A one-line statement of what is blocking and what needs to happen to unblock. “Blocked on API spec from team X — expected by Y.” Without this, a blocked item is a black hole.
Notes. Optional. For context that does not fit elsewhere: design rationale, links to discussions, partial findings from research, alternatives considered.
Vague criteria (“works correctly,” “handles errors gracefully”) are the most common reason an item’s done gets disputed after the fact. EARS (Easy Approach to Requirements Syntax) is a lightweight writing convention — no tooling, just five sentence shapes — that forces each criterion to name a trigger and an observable response, which is exactly what makes it testable:
| Pattern | Shape | Example |
|---|---|---|
| Ubiquitous | THE SYSTEM SHALL <response> |
The export SHALL be UTF-8 encoded. |
| Event-driven | WHEN <trigger>, THE SYSTEM SHALL <response> |
WHEN the user clicks Download, the system SHALL stream a CSV of the filtered rows. |
| State-driven | WHILE <state>, THE SYSTEM SHALL <response> |
WHILE a filter is active, the export SHALL contain only matching rows. |
| Unwanted behavior | IF <condition>, THEN THE SYSTEM SHALL <response> |
IF the result set is empty, THEN the system SHALL download a header-only file, not an error. |
| Optional feature | WHERE <feature present>, THE SYSTEM SHALL <response> |
WHERE SSO is enabled, the system SHALL record the exporter’s identity. |
A Done means: checkbox written this way doubles as its own test plan: the trigger tells the verifier what to do, the response tells them what to observe — and it maps cleanly onto the Test: field (each criterion resolves to pass or fail: <which clause broke>).
Apply it where ambiguity is expensive — anything touching money, authentication, data integrity, or an external contract. For a one-line doc fix, a plain checkbox is fine; don’t ceremonialize trivial work.
For XS and S items, **Files (probable):** is enough. At Effort M and above, upgrade it to a Code Map: the same list, annotated — each path carries its role in this change, the existing functions or utilities to reuse, and the constraints that aren’t obvious from the code:
**Code Map:** (drained from planning — not re-derived at implementation time)
- `apps/api/src/routes/reports.ts` — add the CSV endpoint here; reuse
`buildActivityQuery()` (already filter-aware).
- `apps/web/src/pages/Reports/ActivityReport.tsx` — toolbar gets the
button; follow the existing `ExportPdfButton` pattern.
- Constraint: report queries must stream — the 50k-row case times out
buffered (see memory entry `streaming-reports`).
The rule that makes it work: whoever plans the item drains their investigation into the Code Map instead of carrying it in their session. Codebase knowledge gathered while planning — which files, which helpers already exist, which approach was rejected and why — dies with the planning session unless it lands in the item body.
The cold-handoff test: could a fresh session, holding this item and its repository links, start implementing without repeating the planner’s investigation? For an M+ item the answer must be yes. Rotation readiness adds purpose, constraints, human ownership, verification commands/procedures, and relevant decisions/rejected alternatives to that check. A new human or AI must be able to plan or verify from those artifacts without private chats or personal prompts. Fix missing context before handoff; record an actual cold walkthrough only when one occurred.
The dispatch rule: when the item is handed off — to a subagent, to tomorrow’s session, to another contributor — the instruction is “work BL-####,” pointing at the item. Don’t paste a summary of the item into the handoff prompt: the summary drifts from the item, and now two versions of the truth exist. If the item body isn’t good enough to work from, fix the item body. (A subagent handed a slice of the item — research this, test that — is still told what its slice is; the rule is that the item’s own goal and constraints are read from the item rather than restated around it. See 05 — Subagent delegation.)
Verify the Code Map before implementing, not after. A Code Map makes factual claims about the codebase — these files exist, that helper does this, this constraint holds. If any of them is wrong, everything derived from it is wrong, and the review that catches it comes after the code is written. For M+ items, have a fresh session check those claims against the actual code first: see spec-verification, the cheapest point at which a wrong plan can be caught.
The Code Map is durable item knowledge, distinct from the volatile session state in the active-context file: the Code Map says “reuse buildActivityQuery()”; active context says “halfway through step 3.” One survives the item’s whole life; the other is wiped at handoff.
Once a human approves an item’s Why / Description: (the goal) and its Done means: criteria — or an epic charter’s Outcome and Exit criteria — those sections become frozen intent: human-owned, agent-immutable. Every other section (Approach, Code Map / Files (probable), Blocker, Notes) stays editable as the work teaches; the definition of success does not.
The two frozen sections are not adjacent — Approach sits between them — so the marker goes once, directly below the frontmatter table, and names what it covers rather than fencing a contiguous block:
> **Frozen intent** — `Why / Description:` and `Done means:` approved by
> <who> on <YYYY-MM-DD>. Agents do not edit them; halt and renegotiate instead.
One line, greppable (rg "Frozen intent" backlog/), and unambiguous about scope without needing the frozen text to be contiguous.
Why this exists. Without the boundary, the drift is invisible: an agent that rewords an acceptance criterion to match what it actually built produces a diff that looks like editing, not scope change — and the item then “passes” a spec-compliance review against criteria the implementation wrote for itself. Freezing the intent leaves exactly one path: halt and renegotiate — surface what’s wrong with the approved goal, get explicit re-approval from the human who approved it (or higher authority), and land the change as a visible edit with a one-line reason next to the badge. The executing agent never thaws its own item.
This is the third member of a family: protected regions bound the code an agent may touch, the tier matrix bounds authoritative docs, and frozen intent bounds the approved definition of the work itself.
readyAn item can be filed with a question its author could not answer; without somewhere to put it, the question gets guessed at implementation time. Epic charters already carry open questions and a closing gate (03); items get the same in one line, reusing the frozen-intent marker shape — below the frontmatter table, one per question:
> **Needs clarification** — <the specific unanswered question>
Greppable like the other (rg "Needs clarification" backlog/). Three rules keep it a gate rather than a note:
Status: ready — the same block as an unresolved Deps: entry. A question that doesn’t stop pickup gets answered by whoever picks up.HUMAN_NEEDED.md and picks the next. An agent that answers its own clarifying question has quietly converted a decision into an assumption, with nobody watching.Distinct from Principle 1: that governs an implementer stating assumptions before coding an item they can work; this governs an author who could not finish specifying one. One happens after pickup; the other prevents it.
Every artifact an agent loads routinely costs context even when it’s correct. 08 already budgets the two it owns — the instruction file and memory entries. Generalized across the artifacts a contributor loads to do an item: each has a target size, and “too big” is a signal with a defined response, not a style complaint.
| Artifact | Target | Too big means |
|---|---|---|
| Item body | ~1–2 pages | It’s more than one item. Split it — the beyond-XL splitting shapes apply below XL too; mid-task, use scope-creep recovery. |
| Epic charter | ~2–3 pages | It’s absorbing design content. Move that to docs/planning/ or a pillar (03); the charter keeps decisions and links. |
| Instruction file | see 08 | Demote entries to memory (08 demotion loop). |
| Memory entry | see 08 | It’s a doc, not a memory. Promote to a pillar or design doc; the entry keeps the pointer. |
The bottom two rows carry links, not numbers, on purpose: 08 owns those figures, and a second copy here would drift from it.
Defaults, not laws — a project re-tunes them in its instruction file, the same pattern as the per-project DoD overlay. The rationale travels with the numbers: a context artifact is a liability that must earn its length. An 800-line item body doesn’t make the work clearer; it makes every session that loads it duller on what matters.
When an item is moved to rejected — or to done for a reason that isn’t obvious from the diff — add a **Resolution:** block to the body explaining why.
**Resolution:** <one or two sentences explaining what happened and why.>
<hash>,” “decided against; see [decision doc].”ARCHIVE.md can see not just that an item was rejected, but why.### BL-0517 — Add per-place language preference override
| Field | Value |
|----------|-----------|
| Status | rejected |
| ... | ... |
**Why / Description:** [Original problem description...]
**Resolution:** Covered by the broader Phase 3 i18n epic; per-place
override is now derived from the user's account-level language
preference set during onboarding. No standalone feature needed.
See E11 — Internationalization.
The next person who notices “we should let users pick per-place languages” can rg BL-0517 and find the resolution before re-filing.
stateDiagram-v2
[*] --> backlog : filed
backlog --> ready : refined
ready --> in_progress : picked up + locked
in_progress --> under_review : code complete
under_review --> to_be_tested : review passed
to_be_tested --> done : DoD passed; Test: pass
in_progress --> blocked : blocker discovered
blocked --> ready : unblocked
ready --> rejected : not pursuing
backlog --> rejected : not pursuing
done --> [*] : moved to ARCHIVE.md
rejected --> [*] : moved to ARCHIVE.md
Diagram-vs-enum note: Mermaid state names cannot contain hyphens, so the diagram uses
in_progress,under_review,to_be_testedwith underscores. The correspondingStatus:enum values in actual items use hyphens (in-progress,under-review,to-be-tested). Same states, different rendering — match the enum form, not the diagram form, when filling in aStatus:field.
BACKLOG.md with Status: backlog, Test: not-tested, Lock: —.Needs clarification marker left standing). Move to Status: ready.Lock: <holder>@<now + TTL>. Sets Status: in-progress. Both changes in the same commit.Status: under-review.Status: to-be-tested.Test: pass and Status: done. Release the lock (Lock: —).BACKLOG.md to ARCHIVE.md (preserving everything). Update the epic’s rollup counts in EPICS.md.If a blocker is discovered at any stage, move to Status: blocked and add a Blocker: body line. When the blocker resolves, move back to whatever status is appropriate (usually ready if no code has been written yet, or back to in-progress to continue).
If the item is decided against, set Status: rejected, add a one-line note explaining why, move to ARCHIVE.md. Do not delete. The rejection record prevents the same proposal from being relitigated from scratch.
The happy path is backlog → ready → in-progress → under-review → to-be-tested → done. Real items deviate. Unified recovery guide:
Status: in-progress and Test: fail: <one-line detail>. Be specific: not “tests failed” but “fail: trip-summary card throws on empty trips array.”done on the shrunk version, file follow-up items for the rest per the splitting rules below. If the goal or Done means: was approved, shrinking it is a frozen-intent renegotiation — surface it and get re-approval, exactly as in the scope-creep flow below.You hit something you can’t unblock yourself: missing credential, pending decision, upstream PR, legal review.
Lock: —). Blocked work shouldn’t hold a lock because the wait will exceed the TTL — and you don’t want to starve other contributors who could be doing unrelated work.Status: blocked.**Blocker:** line to the body — one sentence saying what’s blocking and what resolves it. “Blocked on API spec from team X — expected by Y.”ready and re-acquire the lock fresh.You started implementing and realized the item is bigger than Effort implied (or touches things you didn’t expect).
**Why / Description:** body to describe what shipped, not what was originally proposed.Done means: was approved (frozen intent), the narrowing is a renegotiation — surface it and get re-approval per Frozen intent. Silently shrinking the goal to match the reduced work is the same violation as growing it.done until its narrowed scope is actually verified.You held longer than the TTL and forgot to refresh, or you crashed and resumed later.
Lock: field. If it’s still your ID with an expired timestamp, just refresh and continue.—, the previous-holder protocol applies: git blame the item to see what partial work exists before continuing. (See 05 — Expired locks.)done item turns out to not actually be donePost-merge, someone discovers the item didn’t really work.
Status: done → Status: in-progress and Test: pass → Test: fail: <detail>.**Resolution:** note (see Resolution notes) explaining what was missed the first time — useful for future contributors who might wonder why the item appears twice in the audit trail.In all these recovery flows, the three coupled fields (Lock, Status, Test) move together. Never change one in isolation without considering the others. The “Coupled fields” table above shows the valid joint states.
Always into a specific epic’s BACKLOG.md. Never into:
BACKLOG.md at the repo root (legacy from before epics were structured).TODO.md or ROADMAP.md.Two cases:
The friction of “must create an epic first” is intentional. It prevents random items from accumulating without a home, and it forces the team to think about scope before working.
HUMAN_NEEDED.md — work blocked on human agencySome items can’t be completed by an AI agent alone:
These items shouldn’t sit Status: in-progress consuming a lock — the lock TTL would expire while the agent waits indefinitely, and the unfinished work would block other contributors. They also shouldn’t be deferred to FUTURE.md — the work is real and current; it’s just gated on a human.
The methodology uses a dedicated backlog/HUMAN_NEEDED.md file at the backlog root (sibling of EPICS.md) listing every item currently blocked on human agency.
When an AI agent (or human contributor) encounters human-blocked work:
Status: blockedLock: — (released)**Blocker:** line in the body naming what the human needs to do and what unblocks it.HUMAN_NEEDED.md linking back to the BL item.The human, when they have time, scans HUMAN_NEEDED.md, completes the actions, and updates the BL items (Status: ready or Status: in-progress) so an agent can pick them up. The corresponding line in HUMAN_NEEDED.md moves from the “Active” section to “Recently unblocked” (then gets pruned after ~30 days — the BL item’s ARCHIVE.md entry remains the permanent record).
Blocker: across N epic folders.# HUMAN_NEEDED.md
Tasks blocked on human action. Each entry links to the originating
BL item. Add an entry when you set `Status: blocked` and the blocker
is human-only (not waiting on another agent, not waiting on a
dependency that an agent can resolve).
## Active
- [BL-0123](epics/E02-payments/BACKLOG.md#bl-0123) — set up Stripe
production webhook; need account owner access. Added 2026-05-12.
- [BL-0211](epics/E03-launch/BACKLOG.md#bl-0211) — review legal
disclaimer wording with counsel. Added 2026-05-18.
- [BL-0289](epics/E04-billing/BACKLOG.md#bl-0289) — physical card
needed for test charge in production. Added 2026-05-21.
## Recently unblocked (last 30 days)
- ~~[BL-0099]~~ — sign contractor NDA. Done 2026-05-09. Item resumed.
- ~~[BL-0142]~~ — capture screenshots from on-site demo. Done 2026-05-15.
_(Older unblocked items live in their epic's `ARCHIVE.md`.)_
HUMAN_NEEDED.mdDeps: field and the lock system.Status: under-review).FUTURE.md (or stays out of the system entirely).HUMAN_NEEDED.md is where that released-lock work becomes visible.HUMAN_NEEDED.md entries.The file is a passive registry, not an actively-managed surface — items land in it via the blocked-item protocol above, and humans scan it when they check in. The autonomous loop does not interact with HUMAN_NEEDED.md directly; blocked items simply leave the ready set via the lock release, so the loop stops touching them.
BACKLOG.md structure (the per-epic active-items file)Each epic’s BACKLOG.md opens with a summary table — one line per item — followed by the detailed item blocks. The summary is a navigation index that lets a reader see “what’s open in this epic” without scrolling.
# E<NN> — <Epic name> — Active Backlog
_Items currently in scope for this epic. See [charter](README.md) for exit criteria._
## Summary
| ID | Title | Priority | Effort | Status |
|---------|----------------------------------------------------------|----------|--------|--------------|
| BL-0428 | Add CSV export to the activity report | P2 | M | done/pass |
| BL-0517 | Fix off-by-one in date parser | P1 | S | in-progress |
| BL-0518 | Document the export feature in the user guide | P3 | XS | backlog |
| BL-0519 | Stale-data audit on the activity report | P2 | M | rejected (covered by BL-0428) |
---
### BL-0428 — Add CSV export to the activity report
<full item block — frontmatter table + body sections>
### BL-0517 — Fix off-by-one in date parser
<full item block>
### BL-0518 — Document the export feature in the user guide
<full item block>
### BL-0519 — Stale-data audit on the activity report
<full item block, including the `**Resolution:**` note>
Status/Test when convenient — e.g., done/pass for a fully-closed item. Shorthand only; the canonical state lives in the per-item frontmatter table.rejected (reason)) or move directly to ARCHIVE.md — project choice. Keeping them visible briefly helps prevent the same idea from being re-filed.A BACKLOG.md with 30 items and no summary table requires scrolling and Ctrl+F. With the summary at the top, the at-a-glance scan is one screen. For epics that accumulate items quickly, the summary is the difference between a usable backlog and an opaque scroll.
Paste this to start a new item:
### BL-### — <Short imperative title>
| Field | Value |
|----------|-----------|
| Epic | E<NN>-<slug> |
| Pillar | P<#> |
| Priority | P2 |
| Effort | M |
| Status | backlog |
| Test | not-tested |
| Deps | — |
| Lock | — |
<!-- Once a human approves the goal + Done means, add the frozen-intent marker here:
> **Frozen intent** — `Why / Description:` and `Done means:` approved by <who>
> on <YYYY-MM-DD>. Agents do not edit them; halt and renegotiate instead. -->
**Why / Description:** <One paragraph: problem, approach, done.>
**Approach:** (optional)
1. <Step>
2. <Step>
**Done means:**
- [ ] <Criterion>
- [ ] <Criterion>
**Code Map:** (Effort M+; at XS/S a plain `**Files (probable):**` list is enough)
- `<path>` — <role in this change>; reuse `<existing helper>`
- Constraint: <what isn't obvious from reading the code>
**Notes:** (optional)
<Context, links, design rationale.>
### BL-0428 — Add CSV export to the activity report
| Field | Value |
|----------|------------------------------------|
| Epic | E04-reporting |
| Pillar | P9 |
| Priority | P2 |
| Effort | M |
| Status | in-progress |
| Test | not-tested |
| Deps | — |
| Lock | alice-laptop@2026-04-18T16:30Z |
**Why / Description:** Operators currently view the activity report only
in the web UI and re-key totals into a spreadsheet for monthly billing.
Add a "Download CSV" button that exports the same data the table displays,
applying the user's current filter selection. Done when the button
appears, the CSV downloads on click, and the contents match the on-screen
table including filters.
**Approach:**
1. Add a GET `/reports/activity.csv` endpoint that re-uses the existing
activity-report query, accepting the same filter parameters.
2. Stream the result as CSV rather than buffering — the report can be
large.
3. Add a "Download CSV" button to the report toolbar that calls the
endpoint with the current filter state.
4. Tests: a backend test that asserts the endpoint returns the right
columns for a known dataset, plus a UI test that asserts the button
triggers a download.
**Done means:**
- [ ] `GET /reports/activity.csv` returns CSV matching the table query.
- [ ] CSV streams (does not buffer) and works for large result sets.
- [ ] Filter parameters in the URL match the UI's current filter state.
- [ ] Backend test covers the success and empty-data cases.
- [ ] UI test asserts the button triggers a download.
- [ ] Verified manually in actual UI: button appears, downloads work,
filters are respected.
**Code Map:** (Effort M — annotated per "The Code Map" above)
- `apps/api/src/routes/reports.ts` — add `GET /reports/activity.csv`;
reuse `buildActivityQuery()`, already filter-aware and already shared
with the email-summary job.
- `apps/api/src/test/reports.test.ts` — endpoint tests (success + empty).
- `apps/web/src/pages/Reports/ActivityReport.tsx` — toolbar gets the
button; reuse the page's existing filter state, don't re-derive it.
- `apps/web/src/pages/Reports/ActivityReport.test.tsx` — download test.
- Constraint: the response must stream. Tested locally at 50k rows —
buffered hit the default response timeout.
**Notes:** Reusing the shared query keeps the table view, the email-
summary job, and the export on one code path.
Every field is filled. Every section is concrete. A contributor who has never seen this item can pick it up cold and know what to do — which is the cold-handoff test passing.
One item may produce multiple commits and one PR — that is normal. One PR may close multiple items — also normal, when the items are tightly coupled and the work is naturally bundled. What is not normal:
A healthy ratio is roughly one item to one PR, plus or minus a small constant.
The frontmatter table is designed to grep cleanly. Common queries:
# All items advancing a given pillar (cross-epic)
rg "^\| Pillar +\| P7" backlog/
# All P0 (ship-blocking) items
rg "^\| Priority +\| P0" backlog/
# All currently free (unlocked) items
rg "^\| Lock +\| —" backlog/
# All currently in-progress items
rg "^\| Status +\| in-progress" backlog/
# All items held by a specific agent (find stale locks)
rg "^\| Lock +\| claude-session" backlog/
# All blocked items, with 12 lines of context (to read the Blocker line)
rg "^\| Status +\| blocked" -A 12 backlog/
# All items rejected with a specific resolution pattern
rg "^\*\*Resolution:\*\*.*covered by" backlog/
# Find the next BL-ID to use (largest current + 1)
rg -oN "^### BL-\d+" backlog/ | grep -oE "[0-9]+" | sort -n | tail -1
# All items in a specific effort bucket
rg "^\| Effort +\| L" backlog/
When designing a new field or value, design it to grep cleanly: predictable position in the frontmatter, predictable value format, simple regex anchors. Avoid free-form values where grep would need to parse English.
If two contributors file new items in parallel and both compute the same “next” BL-ID, the second to merge bumps to the next number in their PR (single-file edit, cheap to fix). The first contributor to push wins the ID; the second renumbers. Never reuse a previously-rejected ID — even if the rejected item’s ID becomes “gappy” in the sequence, the gap stays. Rejected IDs remain in the audit trail.
EPICS.md reflect item state.rg "Pillar: P<#>" backlog/) surfaces items across all epics.Lock: field records who is currently working the item — authoritative only where it is visible.to-be-tested and done. It is also why the Test: field is separate from Status:.| Mistake | Fix |
|---|---|
| Title is a noun (“CSV export”). | Rewrite imperative: “Add CSV export to the activity report.” |
| Description is one sentence with no acceptance criteria. | Flesh out: problem, approach, what done looks like. Add the Done means: checklist. |
Status is done but Test: reads not-tested. |
Roll back to to-be-tested. Run the gates. Set Test: pass only when truthful. |
| Effort is XL and never started. | Split. Two M items finish; one XL item rots. |
Deps: references an item that does not exist. |
Either typo (fix) or stale (clear). Broken refs erode trust. |
Item is in-progress but Lock: is —. |
Inconsistent state. Either someone is silently working it (acquire the lock retroactively, in their name) or no one is (drop back to ready). |
Item is blocked with no Blocker: line. |
Add the blocker line, or drop back to ready if it is no longer blocked. |
Item lingers in backlog for many months with no refinement. |
Either refine it to ready, or move it to FUTURE.md, or reject it. Limbo helps no one. |
Item is in two epics’ BACKLOG.md. |
An item lives in exactly one epic. Pick one; delete the duplicate (preserving the ID in the chosen one). |
Item IDs are not monotonic (someone reused BL-0042). |
Find the duplicate. Renumber the newer one. Reuse breaks every grep-based query going forward. |
| Acceptance criteria are vague (“works correctly”). | Replace with specifics, or use an EARS trigger→response shape (“WHEN the result set is empty, the system SHALL download a header-only file”). |
| M+ item handed off, and the receiver re-investigated the codebase from scratch. | The planning session didn’t drain its findings. Add the Code Map; apply the cold-handoff test before dispatch. |
| Acceptance criteria quietly reworded mid-task to match what was built. | Frozen-intent violation. Restore the approved text, then renegotiate visibly — or accept the finding that the work missed the goal. |
Open the epic’s
BACKLOG.md. Scan the summary table at the top. Find an item withStatus: backlogorreadyANDLock: —(or an expired-timestamp lock). Acquire the lock in one commit: setLock: <your-id>@<now+TTL>andStatus: in-progresstogether. Read the item body for the goal. Use plan mode for anything non-trivial (see 06 — Plan before executing). Do the work. Pass every gate in the Definition of Done. Release the lock in the merging PR: setLock: —,Status: done,Test: passtogether. Move the item block fromBACKLOG.mdtoARCHIVE.md. Bump the epic’sOpen/Donecount inEPICS.md. If blocked:Status: blocked,Lock: —, add a one-line**Blocker:**to the body. If scope creeps mid-task: stop, split, file follow-up items.
Items are bound by their epic charter. An item whose work would clearly fall outside the epic’s exit criteria does not belong in that epic. Move it (rare, requires careful migration) or close it as rejected with a pointer to where the work actually goes.
Items are bound by the Definition of Done. No shortcuts to done. No done without Test: pass.
Items are bound by the Working Principles. The principles apply at item-execution time: the contributor working an item must think before coding, keep it simple, stay surgical, and execute toward a verifiable goal.