* 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>
222 lines
10 KiB
Markdown
222 lines
10 KiB
Markdown
---
|
|
title: "Statuses & Transitions"
|
|
status: active
|
|
audience: [contributor]
|
|
language: en
|
|
last_updated: 2026-05-07
|
|
when_to_read: "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`](../../lib/task-status.ts) — never call `.toLowerCase()` or `.toUpperCase()` directly. See the [DB vs API mapping](#db-vs-api-mapping) section at the end.
|
|
|
|
## Quick reference
|
|
|
|
| Entity | Source enum | Statuses |
|
|
|---|---|---|
|
|
| [PBI](#pbi) | `PbiStatus` | `READY`, `BLOCKED`, `DONE`, `FAILED` |
|
|
| [Story](#story) | `StoryStatus` | `OPEN`, `IN_SPRINT`, `DONE`, `FAILED` |
|
|
| [Task](#task) | `TaskStatus` | `TO_DO`, `IN_PROGRESS`, `REVIEW`, `DONE`, `FAILED` |
|
|
| [Sprint](#sprint) | `SprintStatus` | `ACTIVE`, `COMPLETED`, `FAILED` |
|
|
| [SprintRun](#sprintrun) | `SprintRunStatus` | `QUEUED`, `RUNNING`, `PAUSED`, `DONE`, `FAILED`, `CANCELLED` |
|
|
| [ClaudeJob](#claudejob) | `ClaudeJobStatus` | `QUEUED`, `CLAIMED`, `RUNNING`, `DONE`, `FAILED`, `CANCELLED`, `SKIPPED` |
|
|
| [Idea](#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.
|
|
|
|
```mermaid
|
|
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](../plans/ST-1109-pbi-status.md)) |
|
|
| `* → 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.
|
|
|
|
```mermaid
|
|
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`](../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.
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
```mermaid
|
|
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`](../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).
|
|
|
|
```mermaid
|
|
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`](../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`).
|
|
|
|
```mermaid
|
|
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`](../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.
|
|
|
|
```mermaid
|
|
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`](../plans/M12-ideas.md).
|
|
|
|
## DB vs API mapping
|
|
|
|
> **Hardstop:** never bypass [`lib/task-status.ts`](../../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`](../../lib/task-status.ts):
|
|
|
|
```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`](../adr/0004-status-enum-mapping.md) for the rationale.
|
|
|
|
## What's next
|
|
|
|
→ [03 — Git Workflow](./03-git-workflow.md) covers branching, commits, and the cost-driven PR rules.
|