spec-node-states¶
A node's status is a backend-DERIVED four-state value (pending/active/merged/drift), not hand-written.
A spec node's status is mostly derived in the backend from what git and the live worktrees
actually say — with one declared exception (pending todos, below). There are four states, evaluated
in this precedence (deriveStatus in source-of-truth's specs.ts):
- pending (declared todo) — a node whose frontmatter says
status: pendingand has no implementing code yet (empty/absentcode:, no drift) is a written-but-unbuilt spec. It reads pending regardless of how manyspec.mdcommits it has, and even while a worktree is merely adding its text (overlay). This is checked first so authoring a roadmap spec doesn't flip it toactive/mergedjust by existing in git. The arrival of governed code (a non-emptycode:list, or drift) graduates it — from then on it derives like any coded node. - active — an unmerged managed worktree has pending ops on a coded node (it carries a board overlay). Live, in-flight work. Only the board assembler knows the overlay, so this state can only be produced there.
- drift — the governed code has moved ahead of the spec's latest version (
drift > 0, by git ancestry). The spec may be stale. - merged — the node has committed version(s) and is in sync (no drift, no in-flight work).
A node with no committed version and no declared status also reads pending (the empty default).
Frontmatter status otherwise survives only as a fallback: if git is unreadable every node would
collapse to version 0, so a node that declared active/merged/drift still shows that intent.
For coded nodes the declared value is otherwise ignored — the derivation is authoritative.
Because active needs the overlay, the derivation runs in two places over one shared helper:
loadSpecs derives from git alone (so /api/specs reports pending/drift/merged), and buildBoard
(sessions) re-derives with the overlay so a node a worktree is touching reads active. A
ghost node (one a worktree is adding, not yet on main) therefore reads active, never pending.
The same end-to-end tracing covers the two other state vocabularies the board shows:
- overlay op-types —
added·edited·deleted·moved, computed per worktree vs main and stamped on the node as glyphs (+ ~ ✕ →) in the colour of the authoring session (see node-graph for the surfacing). - session states — the worktree state machine, traced HARD (explicit writes, then one liveness
check + one guarded inference — no text-sniffing the TUI). The agent's own writes are authoritative and
reconcilereturns each directly:awaiting's proposalsreview·done·close-pending, plusparked·error·asking.askingis captured deterministically the instant the agent invokes the AskUserQuestion tool — the singlePreToolUsemark-active hook readstool_namefrom the payload and writesasking(the question → note), elseactive— and is also self-declarable viaspex session ask. The only LIVE-derived values areworking·idle·offline, from one liveness check (a dead tmux or a bare-shell pane = offline) plus one guarded inference (idle, written active-only by theidle_promptNotification hook so it never clobbers a declaration). Each is carried straight to the session window's status dot (see sessions and session-console).
The dashboard surfaces the derived node status as the row's dot colour and label (green=merged, orange=active, yellow=drift, grey=pending), with the drift count still shown as its own badge — see node-graph.