Backlog Lifecycle
The file-backed backlog under .oat/repo/pjm/backlog/ tracks work as one Markdown file per item. This page describes the lifecycle those items move through, the atomic close-out command that keeps the pieces in sync, and the diagnostics that flag drift when a close-out is done by hand and something is missed.
For the flag-by-flag command reference, see oat backlog archive. For the two-layer PJM surface that hosts the backlog, see Tool Packs.
Where a backlog item lives
items/<id>.md- active, file-backed records. Each carries frontmatter with astatusand anupdatedtimestamp.archived/<id>.md- the resting place for closed-out items, preserving the full item file as history.completed.md- a newest-first summary log of completed work, one line per entry (YYYY-MM-DD — BL-YYMMDD-slug — Title — one-line outcome).index.md- the human-facing curated overview plus a managed, generated index table rebuilt from item frontmatter.
Item statuses
An item's status frontmatter field is one of exactly four values:
open- captured, not yet started.in_progress- actively being worked.closed- completed.wont_do- abandoned or intentionally declined.
closed and wont_do are the two terminal statuses. These are the only valid values — never invent variants like done. A terminal item belongs in archived/, not items/.
Closing out an item
When an item reaches a terminal state, close it out with the atomic command rather than editing files by hand:
# completed work
oat backlog archive BL-260705-example --summary "shipped the thing"
# abandoned work
oat backlog archive BL-260705-example --wont-do --summary "superseded by BL-260706-other"A single oat backlog archive run performs the whole close-out so its parts cannot drift apart:
- Sets the terminal
status(closedby default,wont_dowith--wont-do) and stampsupdated. - Appends a canonical newest-first entry to
completed.md.closeditems always get an entry (with a visibleTODO: summarize outcomeplaceholder when--summaryis omitted, so the gap stays visible);wont_doitems get one only when you pass--summary. - Moves
items/<id>.mdintoarchived/— withgit mvinside a work tree, or a plain rename outside git. - Regenerates the managed backlog index.
The command is safe to re-run: an item already in archived/ produces a no-op warning with no writes. An out-of-enum current status (for example a hand-set done) is a hard error that names the file, lists the valid statuses, and tells you how to recover. See the command reference for exit codes and the --json payload.
Catching lifecycle drift
The manual close-out this command replaces is exactly where the two motivating repos drifted — an item was marked closed in frontmatter and summarized in completed.md but never moved to archived/, and one shipped with the invalid status done. oat pjm doctor (and therefore oat doctor, which aggregates the pjm:* checks) now surfaces that drift:
pjm:backlog_terminal_in_items(fail) - aclosedorwont_doitem is still sitting initems/. Fix: runoat backlog archive <id>to finish the move.pjm:backlog_invalid_status(fail) - an item carries an out-of-enum or missingstatus. The message lists the offending file paths and the valid statuses.pjm:backlog_archived_open(warn) - anopenorin_progressitem is inarchived/, which usually means it was archived prematurely.pjm:backlog_completed_unarchived(warn) -completed.mdreferences an item whose file still lives initems/.pjm:backlog_duplicate_id(fail) - the same<id>.mdexists in bothitems/andarchived/.oat backlog archiverefuses to auto-resolve this (it would clobber the archived record), so the duplicate must be reconciled by hand.
Doctor reports drift; it never auto-fixes. A human or agent runs oat backlog archive (or corrects the status) and re-runs doctor to confirm the backlog is clean again.