Scrum4Me/docs/manual/02-statuses-and-transitions.md
Janpeter Visser bd7478861b
PBI-58: Developer manual + in-app /manual page (#148)
* docs(PBI-58): add developer manual chapters under docs/manual/

Adds a 7-file English-language manual targeted at new human contributors:
index, overview, statuses & transitions (with mermaid state diagrams),
git workflow, MCP integration, docker, and troubleshooting. The manual
is the *map* — it cross-references existing runbooks/ADRs/architecture
docs rather than duplicating their content.

Regenerates docs/INDEX.md and validates with check-doc-links.mjs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(PBI-58): add markdown rendering deps + manual:build script

Adds mermaid, rehype-slug, rehype-autolink-headings for the in-app
/manual page. Wires manual:build into prebuild so production builds
always regenerate the chapter TOC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-58): codegen script for in-app manual TOC

scripts/build-manual.mjs walks docs/manual/, parses YAML front-matter,
strips it from the body, and emits lib/manual.generated.ts with a typed
ManualEntry[] containing slug, title, description, filePath, and the
embedded markdown body. Pure Node 20, mirrors generate-docs-index.mjs.

Inlining the markdown at build time keeps runtime serverless functions
free of filesystem reads, which avoids whole-project NFT tracing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-58): /manual route renders developer manual chapters in-app

Catch-all route at app/(app)/manual/[[...slug]]/page.tsx with
generateStaticParams covering every TOC entry. Server-side
MarkdownView uses react-markdown with remark-gfm, rehype-slug, and
rehype-autolink-headings; mermaid code blocks are routed to a
client-only MermaidBlock that dynamic-imports mermaid on mount.

ManualSidebar (client) reads the typed TOC and highlights the active
chapter via usePathname.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(PBI-58): add Manual link to main nav bar

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:00:10 +02:00

10 KiB

title status audience language last_updated when_to_read
Statuses & Transitions active
contributor
en 2026-05-07 Whenever an entity's status changes unexpectedly or you need to know what status comes next.

02 — Statuses & Transitions

Every persistent entity in Scrum4Me has an explicit status enum. This chapter documents them all, with state-machine diagrams showing allowed transitions, the trigger for each transition (user action vs system / job-driven), and the side effects.

Hardstop: the database stores enums in UPPER_SNAKE; the REST API exposes them in lowercase. Conversion happens only through lib/task-status.ts — never call .toLowerCase() or .toUpperCase() directly. See the DB vs API mapping section at the end.

Quick reference

Entity Source enum Statuses
PBI PbiStatus READY, BLOCKED, DONE, FAILED
Story StoryStatus OPEN, IN_SPRINT, DONE, FAILED
Task TaskStatus TO_DO, IN_PROGRESS, REVIEW, DONE, FAILED
Sprint SprintStatus ACTIVE, COMPLETED, FAILED
SprintRun SprintRunStatus QUEUED, RUNNING, PAUSED, DONE, FAILED, CANCELLED
ClaudeJob ClaudeJobStatus QUEUED, CLAIMED, RUNNING, DONE, FAILED, CANCELLED, SKIPPED
Idea IdeaStatus DRAFT, GRILLING, GRILL_FAILED, GRILLED, PLANNING, PLAN_FAILED, PLAN_READY, PLANNED

PBI

A Product Backlog Item holds one or more stories. Its status reflects whether the PBI as a whole is ready to be picked up, blocked on something external, finished, or written off.

stateDiagram-v2
    [*] --> READY: create_pbi
    READY --> BLOCKED: user marks blocked
    BLOCKED --> READY: user unblocks
    READY --> DONE: all stories DONE
    READY --> FAILED: user gives up
    BLOCKED --> FAILED: user gives up
    DONE --> [*]
    FAILED --> [*]
Transition Trigger Side effect
* → READY create_pbi MCP tool or PBI dialog New PBI lands in priority group, sort_order = last + 1
READY ↔ BLOCKED User toggles via PBI dialog None besides log entry
READY → DONE All child stories reach DONE Auto-promotion (see ST-1109 plan)
* → FAILED User gives up on the PBI Stories may remain OPEN; PBI is filtered out of active boards

Story

A Story sits under a PBI. It moves out of the backlog when added to a Sprint, and reaches DONE when its tasks are complete and the implementation is verified.

stateDiagram-v2
    [*] --> OPEN: create_story
    OPEN --> IN_SPRINT: added to sprint
    IN_SPRINT --> OPEN: removed from sprint
    IN_SPRINT --> DONE: all tasks DONE + verify passes
    IN_SPRINT --> FAILED: verify fails / abandoned
    DONE --> [*]
    FAILED --> [*]
Transition Trigger Side effect
* → OPEN create_story MCP tool or Story dialog Lives in product backlog
OPEN ↔ IN_SPRINT Drag onto Sprint board, or sprint-removal Tasks denormalise sprint_id
IN_SPRINT → DONE Story completion via MCP / UI; auto-PR flow may trigger Auto-PR flow (runbooks/auto-pr-flow.md) may run; PBI is re-evaluated for READY → DONE
IN_SPRINT → FAILED Verification failure or manual abandon Logged in story log

Task

A Task is the smallest unit. The Claude worker mainly reads implementation_plan and writes status transitions through MCP tools.

stateDiagram-v2
    [*] --> TO_DO: create_task
    TO_DO --> IN_PROGRESS: agent claims / user starts
    IN_PROGRESS --> REVIEW: implementation done, awaiting verify
    REVIEW --> DONE: verify passes
    REVIEW --> IN_PROGRESS: verify fails, retry
    IN_PROGRESS --> FAILED: unrecoverable error
    REVIEW --> FAILED: gives up after retries
    DONE --> [*]
    FAILED --> [*]
Transition Trigger Side effect
* → TO_DO create_task MCP tool / Task dialog Inherits sprint_id from parent story
TO_DO → IN_PROGRESS Worker claim or user starts Story may auto-promote to IN_SPRINT
IN_PROGRESS → REVIEW Implementation logged Optional verify_task_against_plan runs
REVIEW → DONE Verify passes / human accepts When all sibling tasks are DONE, the parent story is eligible for DONE
* → FAILED Unrecoverable error or human marks failed Story may auto-promote to FAILED

The MCP tool is update_task_status({ task_id, status }) accepting lowercase API values: todo | in_progress | review | done | failed.

Sprint

A Sprint is the cross-cutting time-box. Its status tracks the overall sprint container, not the agent execution.

stateDiagram-v2
    [*] --> ACTIVE: create sprint
    ACTIVE --> COMPLETED: user closes sprint
    ACTIVE --> FAILED: user abandons sprint
    COMPLETED --> [*]
    FAILED --> [*]

For execution semantics (PER_TASK vs SPRINT_BATCH) see docs/architecture/sprint-execution-modes.md.

SprintRun

A SprintRun is one execution attempt of a sprint by the agent worker. Multiple runs may exist over a sprint's lifetime (if a run is cancelled or paused and restarted).

stateDiagram-v2
    [*] --> QUEUED: trigger sprint run
    QUEUED --> RUNNING: worker claims
    RUNNING --> PAUSED: pause requested
    PAUSED --> RUNNING: resume
    RUNNING --> DONE: all tasks done
    RUNNING --> FAILED: unrecoverable
    QUEUED --> CANCELLED: user cancels
    RUNNING --> CANCELLED: user cancels
    PAUSED --> CANCELLED: user cancels
    DONE --> [*]
    FAILED --> [*]
    CANCELLED --> [*]

The cascade rules (which task transitions automatically promote the SprintRun) are described in docs/plans/sprint-pr-worktree-state-machines.md. When calling update_task_status from inside a sprint run, pass the optional sprint_run_id so the server can validate ownership and propagate cascades.

ClaudeJob

The agent job queue (M13). Each enqueued unit of work is a ClaudeJob with a kind (TASK_IMPLEMENTATION, IDEA_GRILL, IDEA_MAKE_PLAN, PLAN_CHAT, SPRINT_IMPLEMENTATION).

stateDiagram-v2
    [*] --> QUEUED: enqueue
    QUEUED --> CLAIMED: wait_for_job (FOR UPDATE SKIP LOCKED)
    CLAIMED --> RUNNING: worker starts
    RUNNING --> DONE: update_job_status('done')
    RUNNING --> FAILED: update_job_status('failed')
    QUEUED --> CANCELLED: user cancels
    CLAIMED --> QUEUED: stale (>30min)
    QUEUED --> SKIPPED: superseded
    DONE --> [*]
    FAILED --> [*]
    CANCELLED --> [*]
    SKIPPED --> [*]
Transition Trigger Side effect
QUEUED → CLAIMED wait_for_job atomically claims Bearer token is bound to the job (claimed_by_token_id)
CLAIMED → QUEUED Stale claim (>30 min) Auto-requeue on next wait_for_job
RUNNING → DONE update_job_status('done') Optional token-cost telemetry stored on the row
RUNNING → FAILED update_job_status('failed') For IDEA_GRILL/IDEA_MAKE_PLAN, idea status auto-rolls to GRILL_FAILED / PLAN_FAILED

For idempotency rules and recovery procedures see docs/runbooks/worker-idempotency.md.

Idea

The Idea entity (M12) is a pre-PBI staging area. It goes through two AI-driven phases: a grill (Q&A loop with the user to clarify the idea) and a plan (single-pass output of a structured PBI tree). Failures are explicit terminal-ish states that allow retry.

stateDiagram-v2
    [*] --> DRAFT: create idea
    DRAFT --> GRILLING: enqueue IDEA_GRILL
    GRILLING --> GRILLED: update_idea_grill_md
    GRILLING --> GRILL_FAILED: job failed
    GRILL_FAILED --> GRILLING: retry
    GRILLED --> PLANNING: enqueue IDEA_MAKE_PLAN
    PLANNING --> PLAN_READY: update_idea_plan_md (parse ok)
    PLANNING --> PLAN_FAILED: parsePlanMd rejected
    PLAN_FAILED --> PLANNING: retry
    PLAN_READY --> PLANNED: PBI tree created
    PLANNED --> [*]
Transition Trigger Side effect
DRAFT → GRILLING User clicks "Grill" Enqueues IDEA_GRILL job; worker reads prompt_text + idea.grill_md
GRILLING → GRILLED update_idea_grill_md Logs IdeaLog{GRILL_RESULT}
* → GRILL_FAILED update_job_status('failed') for IDEA_GRILL Idea remains usable; user can retry
GRILLED → PLANNING User clicks "Make plan" Enqueues IDEA_MAKE_PLAN; worker outputs strict YAML-frontmatter
PLANNING → PLAN_READY update_idea_plan_md parse ok Logs IdeaLog{PLAN_RESULT}
PLANNING → PLAN_FAILED parsePlanMd rejected Logs IdeaLog{JOB_EVENT, errors}
PLAN_READY → PLANNED PBI tree generated from plan Idea is archived; PBI/Story/Task tree appears in the backlog

For the full Idea workflow, prompts, and prompt_text contents, see docs/plans/M12-ideas.md.

DB vs API mapping

Hardstop: never bypass lib/task-status.ts.

The database stores enums in UPPER_SNAKE (TO_DO, IN_PROGRESS, IN_SPRINT, …) because Prisma + PostgreSQL prefer that convention. The REST API exposes them in lowercase (todo, in_progress, in_sprint, …) because that's the convention HTTP consumers expect.

The two are mapped only through the helpers in lib/task-status.ts:

taskStatusToApi(status)      // DB → API
taskStatusFromApi(input)     // API → DB (returns null on bad input)
storyStatusToApi(status)
storyStatusFromApi(input)
pbiStatusToApi(status)
pbiStatusFromApi(input)
sprintStatusToApi(status)
sprintStatusFromApi(input)
sprintRunStatusToApi(status)
sprintRunStatusFromApi(input)

Bad input on the inbound side (*FromApi) returns null — the route handler converts that to a 422 Zod-style error. See docs/adr/0004-status-enum-mapping.md for the rationale.

What's next

03 — Git Workflow covers branching, commits, and the cost-driven PR rules.