Skip to content

vt-d-statusline-spec-phase

Spec/phase-aware statusline for Claude Code — renders the active SPEC-NNN, its workflow phase, context usage and session cost on one line, degrading to a generic display when no spec is active. Use this when you steer several worktrees at once and want to see at a glance which spec and phase the current session is on. The toolkit ships the script only; you opt in from your own ~/.claude/settings.json.

Plugin: vt-product-dev
Category: Other
Command: /vt-d-statusline-spec-phase


Statusline: spec + phase

Renders one line for the session you are in:

SPEC-167 · planned · 61% ctx · \$4.70

and, when no spec can be resolved — on main, outside a worktree, or in a repo with no specs/ directory — the generic form, with no invented id:

Opus 5 · my-project

Opting in

The toolkit does not install this for you, by design. It ships the script and nothing else. Add the entry to your own ~/.claude/settings.json:

"statusLine": {
  "type": "command",
  "command": "~/.claude/skills/vt-d-statusline-spec-phase/scripts/statusline.sh"
}

Remove that block to opt out. Nothing else changes; the toolkit never wrote it and never updates it.

What you are agreeing to. A statusLine command executes on every assistant message, as you, with no permission prompt and no hook inspection — it is not a Bash tool call. The path above is a symlink into the toolkit checkout, and the script sources its sibling spec-phase.sh with no integrity check. So enabling this means treating that checkout as trusted code: anyone who can write there — a merged PR, or an agent running with edits auto-approved — gets per-message execution as you. That is inherent to statusLine, not specific to this script, but it is worth knowing before you paste. Point it at your primary checkout, never a feature worktree: a removed worktree leaves a dead symlink and every render fails.

Why it is not shipped

Two independent reasons, either of which would be sufficient:

  1. SEC-4 removed the shipped statusLine key from configs/user-global/settings.json on the grounds that a statusline is personal preference, not a toolkit standard — colleagues should not inherit a display they never chose. See configs/security/SECURITY-CONFIG.md, "Retired exception (SEC-4)", which prescribes exactly the personal-config route above.
  2. It would not work anyway for the people most likely to want it. scripts/lib/merge_settings.py is marker-based and user-wins: a top-level key you already own is preserved verbatim. Anyone who had already set their own statusLine would never receive the toolkit's, and the opt-in would fail silently for them.

The same section of SECURITY-CONFIG.md also requires pinning any third-party statusline to an exact version. That constraint does not apply here — this script has no third-party dependency at all, runs no package runner, and makes no network call.

What it renders

Segment Source
SPEC-NNN .active-spec if present, else parsed from the payload's worktree.branch (AD-1)
phase the spec's committed state.yaml (AD-2) — see the table below
NN% ctx context_window.used_percentage from the payload
$N.NN cost.total_cost_usd from the payload

Phase labels

Phase comes from the committed state.yaml, never from gate-file presence: .review-gate.md and .test-gate.md are gitignored and none exist under specs/, so a gate-file reading would report "not built" for every spec ever built.

state.yaml says Label
status: completed done
status: rejected / parked rejected / parked
finalize_gate: reached finalize
review_gate: reached review
build_gate: reached build
plan_gate: reached planned
shape_gate: reached shaped
status: in_progress, no gate active
anything else spec

A terminal status outranks every gate, and a gate whose own status is PENDING is a placeholder rather than a gate reached — both learned from real spec data, where a rejected spec carrying four PENDING placeholders would otherwise have displayed as nearly finished.

Phases 0 (0-start) and 1 (1-bootstrap) are project-level, not spec-level. No per-spec artifact records them, so they are not derivable and are not rendered.

Behaviour and limits

  • One line, ANSI colour, no OSC-8 hyperlinks. Honours NO_COLOR.
  • Never fails a prompt. Malformed, empty or unexpected input degrades to the generic line and exits 0.
  • Single session only. It shows the session it runs in. Claude Code exposes no cross-session registry, so a multi-worktree view is not possible from a statusline — that was investigated and dropped (SPEC-167, FR-4). For a cross-task view use /vt-p-domain-dashboard, or git worktree list.
  • Hot path. Measured at ~87 ms per render against Claude Code's 300 ms debounce, almost entirely interpreter startup. It makes exactly one python3 call and never shells out to git — the branch arrives in the payload. Both constraints are enforced by tests/spec-167/test_statusline.bats, not merely documented here.

Scripts

Script Role
scripts/statusline.sh entrypoint — reads the payload on stdin, renders the line
scripts/spec-phase.sh sourceable helper — spec_phase <dir> and spec_id_from_branch <branch>

spec-phase.sh is usable on its own:

The two paths below have different roots, on purpose: the script comes from the plugin, the spec directory is relative to the project root you run it in. Mixing roots is why an earlier version of this example ran from nowhere, and a repo-relative plugins/… script path (BUG-347) exists only when that project is the toolkit:

bash "$CLAUDE_PLUGIN_ROOT/skills/statusline-spec-phase/scripts/spec-phase.sh" \
  specs/<NNN-slug>   # prints one phase label, e.g. planned

If $CLAUDE_PLUGIN_ROOT is not set, use scripts/spec-phase.sh relative to this skill file's directory, resolving the skill file's real path first (deployed skills are symlinks into the toolkit checkout).

Next actions

After reading this skill, the useful next steps are:

  • Enable it — paste the statusLine block above into your own ~/.claude/settings.json, then start a new Claude Code session. The line appears on the first render; there is nothing to restart or reload beyond the session itself. Deploy ~/.claude from the primary checkout rather than from a worktree, or removing that worktree dead-links the script and every render fails.
  • Check what it derives for any spec without enabling anything, by calling the helper directly: bash "$CLAUDE_PLUGIN_ROOT/skills/statusline-spec-phase/scripts/spec-phase.sh" specs/<NNN-slug> prints one phase label.
  • Read the parallel-work guardrails if the reason you want this is steering several worktrees at once — docs/solutions/patterns/v025-multi-worktree-workflow.md covers the procedure, the 4–6 ceiling, and the four hazards this repo has already paid for.
  • Use /vt-p-domain-dashboard instead if what you actually need is a view across several specs or projects. This skill is single-session by design and cannot see other worktrees.
  • Disable it by deleting the statusLine block from your own settings. The toolkit never wrote it and never updates it, so nothing else has to be undone.
  • docs/solutions/patterns/v025-multi-worktree-workflow.md — running several worktrees, and the hazards that come with it
  • configs/security/SECURITY-CONFIG.md — the SEC-4 retired exception this skill honours