Backlog Item Lifecycle
STATUS: DRAFT — This document was derived from reading source files on 2026-02-27.
Claims marked with [VERIFY] require confirmation against live script execution before
this document is promoted to canonical reference.
SOURCE: Derived from reading .claude/skills/backlog/SKILL.md,
.claude/skills/backlog/references/state-machine.md,
.claude/skills/backlog/references/item-schema.md,
.claude/skills/backlog/scripts/backlog.py (lines 1–100),
.claude/skills/work-backlog-item/SKILL.md,
.claude/skills/create-backlog-item/SKILL.md,
.claude/skills/groom-backlog-item/SKILL.md (accessed 2026-02-27).
1. Data Architecture
Three-layer system. Each layer has a distinct role; they are not interchangeable.
Layer 1 — GitHub Issues (canonical source of truth)
GitHub Issues are authoritative for item status and priority. The label set on the issue is the canonical status. When a local file and its linked issue disagree, the GitHub issue wins.
- Status is encoded as
status:*labels (one per item at any time) - Priority is encoded as
priority:*labels (P0/P1/P2/Ideas) - Issue body mirrors the item file body after each sync
- Milestone field mirrors
metadata.milestone
Layer 2 — .claude/backlog/*.md (local cache for agent consumption)
Per-item files exist to avoid GitHub API saturation during agent sessions. Agents read these files instead of hitting the API for every lookup.
- Named
{priority}-{slug}.md— e.g.,p1-error-recovery.md - Contain YAML frontmatter + body sections
- Written by
backlog.py; never edited directly by skills or agents - Stale when their linked issue has changed state without a
backlog pull[VERIFY: pull subcommand behavior]
Layer 3 — backlog.py (sole interface)
All reads and writes to both layers go through this script. No direct edits to
.claude/backlog/*.md files. No direct gh issue edit calls. The script handles:
- Creating per-item files with correct frontmatter
- Creating and closing GitHub Issues
- Syncing groomed content to issue bodies
- Writing label transitions
- Writing
metadata.issueback to the local file after issue creation
uv run .claude/skills/backlog/scripts/backlog.py <subcommand> [options]
2. Item Lifecycle Flow
State 1: Created (local only)
Trigger: /create-backlog-item collects fields and invokes backlog add.
Command:
uv run .claude/skills/backlog/scripts/backlog.py add \
--title "Item title" \
--priority P1 \
--description "Description" \
--source "Session observation" \
--type Feature \
[--research-first "questions"] \
[--no-create-issue]
What gets created:
- Per-item file:
.claude/backlog/{priority}-{slug}.md - Frontmatter fields set:
name,description,metadata.topic,metadata.source,metadata.added,metadata.priority,metadata.type,metadata.status: needs-grooming - Body sections written: Description, optionally Acceptance Criteria, Research First, Suggested Location
Where data lives: Local file only. No GitHub issue unless P0/P1 and --create-issue was
passed (or the user confirmed in guided/quick mode).
GitHub behavior:
- P0 or P1 with
--create-issue: GitHub Issue created immediately;metadata.issue: '#N'written to file - P0 or P1 without
--create-issue: local file only; issue created later bybacklog syncorbacklog update --create-issue - P2 or Ideas: always local only at creation;
--no-create-issueis set automatically
State 2: Synced to GitHub
Trigger: backlog sync (also triggered by GitHub Action on .claude/backlog/ changes).
Command:
uv run .claude/skills/backlog/scripts/backlog.py sync [--dry-run]
What happens:
- Script scans
.claude/backlog/for P0/P1 items without ametadata.issuefield - Creates a GitHub Issue for each such item
- Writes
metadata.issue: '#N'back to the per-item file frontmatter - Applies
status:needs-groomingandpriority:P*labels to the issue
Where data lives: Both local file and GitHub Issue. The local file is now linked.
GitHub Action integration: [VERIFY] The GitHub Action triggers backlog sync when
.claude/backlog/ files are changed in a push. This means committing a new P0/P1 item
file triggers issue creation automatically without a manual sync call.
State 3: Groomed
Trigger: /groom-backlog-item <title> orchestrates fact-checking, RT-ICA, and the
backlog-item-groomer agent, then writes sections via backlog groom.
Commands (incremental, preferred):
# After fact-check step
uv run .claude/skills/backlog/scripts/backlog.py groom "{title}" \
--section "Fact-Check" --content "{fact-check summary}"
# After RT-ICA step
uv run .claude/skills/backlog/scripts/backlog.py groom "{title}" \
--section "RT-ICA" --content "{rt-ica summary}"
# After groomer agent output
uv run .claude/skills/backlog/scripts/backlog.py groom "{title}" \
--section "Reproducibility" --content "{content}"
# ... repeated for each of the 7 required subsections
Alternative (full body at once):
uv run .claude/skills/backlog/scripts/backlog.py groom "{title}" \
--groomed-content "{full groomed body}"
# Or from file:
uv run .claude/skills/backlog/scripts/backlog.py groom "{title}" \
--groomed-file path/to/groomed.md
What gets written to the item file:
metadata.groomed: YYYY-MM-DD(set only after ALL 7 required sections are present)- Body sections appended: Fact-Check, RT-ICA, then groomed subsections (Reproducibility, Priority, Impact, Scope, Output / Evidence, Dependencies, Research, Skills, Agents, Prior Work, Files, Decision)
Where data lives: Local file updated with all sections. If the item has a linked GitHub
Issue, backlog groom syncs the groomed content to the issue body. [VERIFY: whether the
script pushes body to GH on every groom call or only on explicit sync]
GitHub label transition: status:needs-grooming removed, status:groomed added.
Item is considered fully groomed only when all 7 canonical sections are present
(Fact-Check, RT-ICA, Reproducibility, Dependencies, Skills, Agents, Prior Work) AND
metadata.groomed is set. Partial grooming (some sections present) is not groomed —
/groom-backlog-item resumes from the first missing section on re-run.
State 4: In Milestone
Trigger: /group-items-to-milestone assigns the item to a GitHub milestone.
What happens:
metadata.milestone: {N}written to per-item file- GitHub Issue milestone field updated
- GitHub label:
status:groomedremoved,status:in-milestoneadded
Where data lives: Both local and GitHub, now with milestone linkage.
Note: This state is managed by /group-items-to-milestone, which is not documented in
the source files read for this draft. [VERIFY: exact command invoked by that skill]
State 5: In Progress
Trigger: /work-backlog-item <title> — after RT-ICA returns APPROVED and the SAM plan
file is created.
Command:
uv run .claude/skills/backlog/scripts/backlog.py update "{title}" \
--plan "plan/tasks-{N}-{slug}.md" \
--status in-progress \
-R Jamie-BitFlight/claude_skills
What gets written:
metadata.plan: plan/tasks-{N}-{slug}.mdin per-item file frontmattermetadata.status: in-progressin frontmatter- GitHub label: current status label removed,
status:in-progressadded
Critical constraint: status:in-progress MUST NOT be set before RT-ICA returns APPROVED
and the plan file exists. Setting it during grooming or RT-ICA checking is incorrect.
Where data lives: Both local and GitHub. Plan file exists at plan/tasks-{N}-{slug}.md.
State 6: Done / Closed
Trigger: /work-backlog-item close <title> — after plan checklist is 100% complete and
acceptance criteria verification passes.
Command:
uv run .claude/skills/backlog/scripts/backlog.py close "{title}" \
--plan "plan/tasks-{N}-{slug}.md" \
--checklist-pass \
[-R Jamie-BitFlight/claude_skills]
Preconditions enforced before this command is called:
- Plan file checklist: all
- [x](zero unchecked items) - Acceptance criteria verification agent returns PASS (per-criterion, not overall guess)
What happens:
metadata.status: donewritten to per-item file- GitHub label:
status:in-progressremoved,status:doneadded - GitHub Issue closed
Optional cleanup flag (--cleanup) [VERIFY: whether this flag exists on close or only
on resolve]: removes the local per-item file after closing. Without --cleanup, the file
remains but has status: done.
Terminal state (closed): done → closed transition happens only when
/complete-milestone archives the milestone. At that point metadata.status: closed is set
and a milestone completion archive is written to .claude/milestones/v{N}-completion.md.
State 7: Resolved (won't-fix / obsolete)
Trigger: Any skill or /work-backlog-item resolve <title> when an item is invalid,
obsolete, or superseded.
Command:
uv run .claude/skills/backlog/scripts/backlog.py resolve "{title or #N}" \
--reason "reason text" \
[-R Jamie-BitFlight/claude_skills]
What happens:
metadata.status: resolvedwritten to per-item file- GitHub label: current status label removed,
status:resolvedadded - GitHub Issue closed (with resolution comment if
--reasonprovided) --cleanupremoves the local file [VERIFY: cleanup flag presence on resolve]
Can be triggered from any state — not just in-progress. An item can be resolved from
needs-grooming, groomed, in-milestone, or in-progress.
State 8: Pulling from GitHub
Trigger: Manual backlog pull call to sync GitHub state back to local files.
Command:
uv run .claude/skills/backlog/scripts/backlog.py pull [--dry-run] [--force]
What happens [VERIFY: exact pull behavior — inferred from docstring]:
- Fetches current issue state from GitHub for each item with a
metadata.issuefield - Overwrites or merges local file body with current issue body
- Updates
metadata.statusto match GitHub label state --dry-run: shows what would change without writing--force: overwrites local changes without prompting [VERIFY]
Use case: When a GitHub Issue is updated externally (e.g., via web UI or another agent),
backlog pull brings those changes into the local cache.
3. Data Flow Diagram
flowchart TD
subgraph local["Local: .claude/backlog/"]
LF["Per-item file\n{priority}-{slug}.md\n(frontmatter + body)"]
end
subgraph github["GitHub Issues"]
GH["Issue #N\n(title + body + labels + milestone)"]
end
subgraph script["backlog.py (sole interface)"]
ADD["backlog add"]
SYNC["backlog sync"]
GROOM["backlog groom"]
UPDATE["backlog update"]
CLOSE["backlog close"]
RESOLVE["backlog resolve"]
PULL["backlog pull"]
end
Create(["/create-backlog-item"]) --> ADD
ADD -->|"Creates file\n.claude/backlog/{priority}-{slug}.md"| LF
ADD -->|"P0/P1 + --create-issue\nCreates issue + writes metadata.issue"| GH
LF -->|"P0/P1 items missing issue"| SYNC
SYNC -->|"Creates GH issue\nwrites metadata.issue back"| LF
SYNC --> GH
Groom(["/groom-backlog-item"]) --> GROOM
GROOM -->|"Writes sections to file\nSets metadata.groomed"| LF
GROOM -->|"If item has issue:\nsyncs body to GH"| GH
Work(["/work-backlog-item"]) --> UPDATE
UPDATE -->|"Sets metadata.plan\nmetadata.status=in-progress"| LF
UPDATE -->|"Updates labels"| GH
CloseCmd(["/work-backlog-item close"]) --> CLOSE
CLOSE -->|"Sets metadata.status=done"| LF
CLOSE -->|"Closes issue"| GH
ResolveCmd(["/work-backlog-item resolve"]) --> RESOLVE
RESOLVE -->|"Sets metadata.status=resolved\n[--cleanup deletes local file]"| LF
RESOLVE -->|"Closes issue + resolution comment"| GH
GH -->|"External changes or drift"| PULL
PULL -->|"Overwrites local with GH state"| LF
classDef localonly fill:#e8f4e8,stroke:#4a9e4a
classDef synced fill:#e8eaf4,stroke:#4a4a9e
classDef terminal fill:#f4e8e8,stroke:#9e4a4a
LF:::synced
GH:::synced
When items are local-only (no GitHub issue)
- P2 and Ideas items at creation —
backlog addwith--no-create-issue - P0/P1 items before first
backlog sync— if created without--create-issue - Any item between creation and the next GitHub Action trigger [VERIFY: GH Action timing]
When items get pushed to GitHub
- P0/P1 at creation if
--create-issuepassed or user confirms in guided mode - On
backlog sync— creates issues for all P0/P1 items missing them - On
backlog groom— syncs groomed body to existing issue - On
backlog update --status in-progress— updates labels - On
backlog close— closes issue - On
backlog resolve— closes issue with comment
When local files get removed
backlog close --cleanup: file deleted after issue is closed [VERIFY: flag exists]backlog resolve --cleanup: file deleted after issue is resolved [VERIFY: flag exists]- After
/complete-milestone: milestone archive written; [VERIFY: whether local files are deleted or only status is set toclosed]
Sync direction
- Push (local → GitHub):
backlog add,backlog sync,backlog groom,backlog update,backlog close,backlog resolve - Pull (GitHub → local):
backlog pull - Write-back on create:
backlog add --create-issueandbacklog syncwritemetadata.issueback to the local file after creating the GH issue (bidirectional in a single operation)
4. Label and Priority Mapping
Status Labels (1:1 with states)
| Status | GitHub Label | Set by |
|---|---|---|
needs-grooming |
status:needs-grooming |
backlog add |
groomed |
status:groomed |
backlog groom (after all 7 sections present) |
blocked |
status:blocked |
backlog groom (RT-ICA BLOCKED) or backlog update |
in-milestone |
status:in-milestone |
/group-items-to-milestone |
in-progress |
status:in-progress |
backlog update --status in-progress |
done |
status:done |
backlog close |
resolved |
status:resolved |
backlog resolve |
closed |
status:closed |
/complete-milestone only |
Labels are exclusive — exactly one status:* label per issue at any time. The backlog script
manages label transitions; no direct gh label calls are permitted.
Priority Labels (orthogonal to status)
| Priority | GitHub Label | Local file naming |
|---|---|---|
| P0 | priority:P0 |
.claude/backlog/p0-{slug}.md |
| P1 | priority:P1 |
.claude/backlog/p1-{slug}.md |
| P2 | priority:P2 |
.claude/backlog/p2-{slug}.md |
| Ideas | priority:Ideas |
.claude/backlog/ideas-{slug}.md |
Priority labels are set at creation and do not change unless the item is explicitly
re-prioritized. [VERIFY: whether backlog update supports --priority flag]
Type Labels
| Type | GitHub Label |
|---|---|
| Feature | type:feature |
| Bug | type:bug |
| Refactor | type:refactor |
| Docs | type:docs |
| Chore | type:chore |
5. What Happens at Each Point
After backlog add
.claude/backlog/{priority}-{slug}.md — CREATED
frontmatter:
name: "Item title"
description: "one-sentence summary"
metadata:
topic: "item-title-slug"
source: "Session observation"
added: 2026-02-27
priority: P1
type: Feature
status: needs-grooming
body:
## Description
{prose}
## Acceptance Criteria
{bullet list — if provided}
## Research First
{questions — if provided}
GitHub Issue: NOT YET CREATED (unless P0/P1 + --create-issue)
After backlog sync (P0/P1 item gets issue)
.claude/backlog/p1-{slug}.md — MODIFIED
frontmatter:
metadata.issue: '#42' ← written back by script
GitHub Issue #42:
title: "Item title"
body: {mirrored from local file body}
labels: [priority:P1, status:needs-grooming]
state: open
After backlog groom (all sections written)
.claude/backlog/p1-{slug}.md — MODIFIED
frontmatter:
metadata.groomed: 2026-02-27 ← set after all 7 sections present
metadata.status: groomed ← [VERIFY: whether groom sets status or only update does]
body additions:
## Fact-Check
## RT-ICA
## Groomed
### Reproducibility
### Dependencies
### Skills
### Agents
### Prior Work
(+ others)
GitHub Issue #42:
body: {updated to include groomed sections}
labels: [priority:P1, status:groomed] ← status:needs-grooming replaced
After backlog update --status in-progress
.claude/backlog/p1-{slug}.md — MODIFIED
frontmatter:
metadata.plan: "plan/tasks-3-{slug}.md"
metadata.status: in-progress
GitHub Issue #42:
labels: [priority:P1, status:in-progress] ← previous status label replaced
After backlog close --checklist-pass
.claude/backlog/p1-{slug}.md — MODIFIED (or DELETED if --cleanup)
frontmatter:
metadata.status: done
GitHub Issue #42:
state: closed
labels: [priority:P1, status:done]
After backlog resolve --reason "..."
.claude/backlog/p1-{slug}.md — MODIFIED (or DELETED if --cleanup)
frontmatter:
metadata.status: resolved
GitHub Issue #42:
state: closed
labels: [priority:P1, status:resolved]
comment: resolution reason text appended
After backlog close --cleanup (or equivalent with cleanup)
.claude/backlog/p1-{slug}.md — DELETED
GitHub Issue #42: (state unchanged — already closed by backlog close)
After PR merge + next backlog sync
Already-closed issues (state: closed on GitHub) are skipped by backlog sync.
The sync command only creates issues for P0/P1 items that have no issue yet.
Closed issues are not re-opened or re-synced. [VERIFY: exact skip logic in sync]
6. Item File Structure (Annotated)
---
name: "Fix error recovery in rollback" # title; matches GH issue title
description: "Rollback does not handle partial…" # one-sentence summary
metadata:
topic: "fix-error-recovery-in-rollback" # kebab-case slug, max 60 chars
source: "Code review 2026-02-20" # trigger
added: 2026-02-20 # creation date
priority: P1 # P0|P1|P2|Ideas
type: Bug # Feature|Bug|Refactor|Docs|Chore
status: in-progress # current state (see state machine)
groomed: 2026-02-21 # set by groom-backlog-item
issue: '#42' # GitHub issue number
milestone: 3 # GitHub milestone number
plan: plan/tasks-5-fix-error-recovery.md # SAM task file path
---
## Description
{prose — what the problem is, not how to fix it}
## Acceptance Criteria
- Running `command X` produces `output Y`
- File `.claude/backlog/p1-{slug}.md` exists with correct frontmatter
## Research First
{questions to answer before grooming}
## Suggested Location
`plugins/plugin-creator/scripts/`
## Fact-Check
Claims checked: 3
VERIFIED: 2 | REFUTED: 1 | INCONCLUSIVE: 0
Refuted claims: [...]
Citations: [...]
## RT-ICA
Goal: {one sentence}
Conditions:
1. {condition} | Status: AVAILABLE | Info needed: —
Decision: APPROVED
Missing: None
## Groomed
### Reproducibility
### Dependencies
### Skills
### Agents
### Prior Work
### Files
### Decision
## Acceptance Criteria Verification
[PASS] Running `command X` produces `output Y` — verified at src/main.py:42
[PASS] File exists with correct frontmatter — confirmed at .claude/backlog/p1-…
Overall: PASS (2/2 criteria met)
7. Testing Checklist
Items to verify in the next session by running the commands and observing actual output.
Data Architecture
- Confirm
backlog addwith--no-create-issueproduces no GitHub API call (check with--dry-runif available, or observe GH issue list before/after) - Confirm
backlog addwith--create-issuewritesmetadata.issue: '#N'to the local file (read file after command) - Confirm
backlog syncskips items that already havemetadata.issueset - Confirm
backlog syncwith--dry-runshows what would be created without writing
State Transitions
- Confirm
backlog groom "{title}" --section "Fact-Check" --content "..."appends without overwriting existing sections - Confirm
metadata.groomedis set only after all 7 required sections are present (try with 6 sections and verify field is absent) - Confirm
backlog update --status in-progressrequires a prior--planto be set, or can set status independently - Confirm
backlog closewithout--checklist-passflag is rejected (expected: error or warning) - Confirm
backlog resolvewithout--reasonis rejected
Cleanup Behavior
- Verify whether
--cleanupflag exists onbacklog close(checkbacklog close --help) - Verify whether
--cleanupflag exists onbacklog resolve(checkbacklog resolve --help) - Confirm whether
backlog close --cleanupdeletes the local file or only marks it deleted - Confirm what
/complete-milestonedoes to local per-item files (deletes or setsstatus: closed)
Pull / Bidirectional Sync
- Run
backlog pull --dry-runand confirm it shows current GH state vs local state - Confirm
backlog pulloverwritesmetadata.statusto match GH label - Confirm
backlog pulldoes not overwritemetadata.planormetadata.groomedwith empty values from GH
Priority and Label Mapping
- Confirm P2 and Ideas items never get GH issues via
backlog sync(even if present in backlog dir) - Confirm label transitions are atomic: old label removed and new label added in same API call [VERIFY with
gh issue view N --json labels] - Verify
backlog update --priorityexists or confirm re-prioritization path
GitHub Action Integration
- Confirm GitHub Action triggers on
.claude/backlog/file changes (read.github/workflows/for trigger definition) - Confirm Action calls
backlog sync(not a different command) - Confirm Action runs with GITHUB_TOKEN available
Post-Merge Behavior
- After merging a PR that fixes
#N, confirm issue is closed on GH - Run
backlog syncafter merge and confirm closed items are skipped - Confirm
backlog listexcludes or marks closed/resolved items