marketplace/bundles/plan-marshall/skills/manage-status/SKILL.md
Manage status.json files with phase tracking, metadata, and lifecycle operations
npx skillsauth add cuioss/plan-marshall manage-statusInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Manage status.json files with phase tracking, metadata, and lifecycle operations. Handles plan status storage (JSON), phase operations, metadata management, plan discovery, phase transitions, archiving, and routing.
Base contract: See manage-contract.md for shared enforcement rules, TOON output format, and error response patterns.
Skill-specific constraints:
pending, in_progress, doneset-phase, update-phase, or transition commands--get or --set flagsStandards: See status-lifecycle.md for the phase state machine, plan lifecycle, and metadata conventions.
get-routing-context for combined stateStatus is stored in the plan directory:
.plan/plans/{plan_id}/status.json
JSON format for storage:
{
"title": "Plan Title",
"current_phase": "1-init",
"phases": [
{"name": "1-init", "status": "in_progress"},
{"name": "2-refine", "status": "pending"},
{"name": "3-outline", "status": "pending"},
{"name": "4-plan", "status": "pending"},
{"name": "5-execute", "status": "pending"},
{"name": "6-finalize", "status": "pending"}
],
"metadata": {
"change_type": "feature",
"use_worktree": true,
"worktree_path": "/abs/path/.plan/local/worktrees/my-feature",
"worktree_branch": "feature/my-feature"
},
"created": "2025-01-15T10:00:00Z",
"updated": "2025-01-15T14:30:00Z"
}
| Field | Type | Description |
|-------|------|-------------|
| title | string | Plan title |
| current_phase | string | Current active phase |
| phases | list | Phase objects with name and status |
| metadata | table | Key-value metadata (common fields: change_type, confidence, domain, use_worktree, worktree_path, worktree_branch) |
| created | string | ISO timestamp of creation |
| updated | string | ISO timestamp of last update |
| Status | Description |
|--------|-------------|
| pending | Phase not yet started |
| in_progress | Phase currently active |
| done | Phase completed |
status.metadata is the canonical source of truth for whether a plan
runs in an isolated git worktree. create seeds only use_worktree;
worktree_branch and worktree_path are persisted at phase-5
materialization, and get-worktree-path reads all three:
| Field | Type | When set | Description |
|-------|------|----------|-------------|
| use_worktree | bool | Always (seeded by create) | true when the plan runs in an isolated worktree, false when it runs against the main checkout. Never absent on plans created via create. |
| worktree_path | string | Persisted at phase-5-execute Step 2.5 (absent until then) | Absolute path to the worktree root. Used by get-worktree-path, build wrappers (--plan-id resolution), and phase-entry assertions. Phases 1-4 record no path; phase-5-execute Step 2.5 persists the resolved path once git worktree add runs. |
| worktree_branch | string | Persisted at phase-5-execute Step 2.5 (absent until then) | Feature branch ref (feature/{plan_id}) derived and checked out at materialization. Recorded for the audit trail and consumed by workflow-integration-git worktree subcommands. |
Downstream consumers MUST read these fields via get-worktree-path
rather than re-deriving the path from filesystem layout. Re-derivation
breaks if the platform-neutral worktree root constant ever changes
again, and it duplicates logic that manage-status already owns.
Script: plan-marshall:manage-status:manage-status
Create status.json with initial phases. Optionally records the
worktree intent (use_worktree) into status.metadata; the branch and
the resolved worktree_path are derived and persisted later, at phase-5
materialization.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status create \
--plan-id {plan_id} \
--title {title} \
--phases {comma-separated-phases} \
[--force] \
[--use-worktree]
Parameters:
--plan-id (required): Plan identifier (kebab-case)--title (required): Plan title--phases (required): Comma-separated phase names in execution order (e.g., 1-init,2-refine,3-outline,4-plan,5-execute,6-finalize). Order matters — it determines progress calculation and transition sequence.--force: Overwrite existing status.json--use-worktree (optional): Mark the plan as running in an isolated git worktree. Seeds only status.metadata.use_worktree=true; the feature branch (feature/{plan_id}) and the resolved worktree_path are derived and persisted later by phase-5-execute Step 2.5 once git worktree add runs. When --use-worktree is omitted, status.metadata.use_worktree=false is seeded explicitly so downstream resolvers never have to treat absence-of-metadata as "main-checkout".Output — main-checkout (TOON):
status: success
plan_id: my-feature
file: status.json
created: true
plan:
title: My Feature
current_phase: 1-init
use_worktree: false
Output — worktree (intent recorded) (TOON):
status: success
plan_id: my-feature
file: status.json
created: true
plan:
title: My Feature
current_phase: 1-init
use_worktree: true
The branch and worktree_path are absent here — phase-5-execute Step 2.5 derives feature/{plan_id} and persists both at materialization.
Read entire status.json content.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status read \
--plan-id {plan_id}
Output (TOON):
status: success
plan_id: my-feature
plan:
title: My Feature
current_phase: 2-refine
phases: [...]
metadata: {...}
Set current phase (marks phase as in_progress).
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status set-phase \
--plan-id {plan_id} \
--phase {phase_name}
Output (TOON):
status: success
plan_id: my-feature
current_phase: 2-refine
previous_phase: 1-init
Update a specific phase's status.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status update-phase \
--plan-id {plan_id} \
--phase {phase_name} \
--status {pending|in_progress|done}
Output (TOON):
status: success
plan_id: my-feature
phase: 1-init
phase_status: done
Calculate plan progress percentage.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status progress \
--plan-id {plan_id}
Output (TOON):
status: success
plan_id: my-feature
progress:
total_phases: 6
completed_phases: 3
current_phase: 4-plan
percent: 50
Progress formula: percent = floor(completed_phases / total_phases * 100). A phase counts as "completed" only when its status is done. Phases with status in_progress or pending are not counted.
Get or set metadata fields.
Set metadata:
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status metadata \
--plan-id {plan_id} \
--set \
--field {field_name} \
--value {value}
Get metadata:
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status metadata \
--plan-id {plan_id} \
--get \
--field {field_name}
Output (set) (TOON):
status: success
plan_id: my-feature
field: change_type
value: feature
previous_value: bug_fix
Output (get) (TOON):
status: success
plan_id: my-feature
field: change_type
value: feature
Record the outcome of a phase step inside status.metadata.phase_steps. Phase skills use this to persist intra-phase progress (e.g., discovery, drift-detection) so that resuming a phase can skip completed steps. Outcomes are done, skipped, loop_back, or failed. An optional --display-detail one-line string is persisted alongside the outcome so downstream renderers (phase-6-finalize vertical-steps block, etc.) can surface user-facing step summaries. Loop-back outcomes carry a mandatory --loop-back-target granularity classifier (see "Loop-back target classification" below). Steps that may inline-edit the worktree carry a dirty-worktree invariant on the done outcome (see "Dirty-worktree invariant on done" below).
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status mark-step-done \
--plan-id {plan_id} \
--phase {phase_name} \
--step {step_id} \
--outcome {done|skipped|loop_back|failed} \
[--display-detail "one-line user-facing detail"] \
[--head-at-completion <sha>] \
[--loop-back-target {5-execute|6-finalize}] \
[--force]
Parameters:
--plan-id (required): Plan identifier--phase (required): Phase name (e.g., 5-execute)--step (required): Step identifier within the phase (free-form string chosen by the phase skill)--outcome (required): done, skipped, loop_back, or failed--display-detail (optional at CLI level, required-by-convention for phase-6-finalize steps per the phase-6-finalize interface contract): One-line user-facing detail string. Persisted as null when omitted.--head-at-completion (optional): Git SHA captured at step completion. Persisted alongside outcome and consulted by resumable phase dispatchers (e.g., phase-6-finalize pre-push-quality-gate) to detect HEAD advancement.--loop-back-target (REQUIRED when --outcome=loop_back, FORBIDDEN otherwise): Loop-back target phase. Must be one of 5-execute (full phase rollback for fix-task-required dispositions) or 6-finalize (inline replay for inline-fixable dispositions). See "Loop-back target classification" below.--force (optional): Overwrite an existing differing outcomeLoop-back target classification:
The --loop-back-target flag encodes the granularity invariant from the phase-6-finalize "Loop-back Target Contract" section. Two legal values:
5-execute — full phase rollback for fix-task-required dispositions. Use when triage allocated one or more fix tasks (fix_tasks_created > 0) or deferred any findings to overflow (overflow_deferred > 0). The continuation hook re-dispatches phase-5-execute against the freshly-allocated fix tasks before re-entering the finalize FOR loop.6-finalize — inline replay for inline-fixable dispositions. Use when triage resolved every finding via SUPPRESS, narrow-rationale ACCEPT, or single-annotation FIX (no fix-task allocation, no overflow). The continuation hook stays in 6-finalize, does NOT call set-phase, and re-fires the loop-back-marked step from the resumable re-entry check.The flag is REQUIRED on every loop_back outcome (returns error: missing_loop_back_target when absent) and FORBIDDEN on every other outcome (returns error: unexpected_loop_back_target). The argparse choices enforce the two-value enumeration at parse time. There is no backwards-compat fallback — every loop-back-emitting call site MUST classify the disposition before persisting the outcome.
Dirty-worktree invariant on done:
Some finalize steps may legitimately mutate the worktree during their body — applying review fixes, re-running formatters, or regenerating artifacts. These steps are enumerated in the MAY_MUTATE_WORKTREE_STEPS set: automated-review, sonar-roundtrip, and finalize-step-simplify. When such a step reports --outcome done, the resolved worktree MUST be clean. Marking one of these steps done while uncommitted changes sit in the tree would let the finalize dispatcher advance past commit-push (HEAD unchanged + outcome done → skip commit) and silently drop the mutation.
The guard fires only when --outcome=done AND --step is in MAY_MUTATE_WORKTREE_STEPS. It resolves the worktree path from status.metadata (tri-state: use_worktree==false or empty worktree_path → main checkout .; non-empty → that path) and runs git -C {path} status --porcelain. When the porcelain output is non-empty (dirty), the command returns error: dirty_worktree_done_refused and refuses to persist the outcome. All other outcomes (skipped, failed, loop_back) and all steps outside the set fall through unchanged — the guard is additive to every call site that marks a clean tree done.
Two escape paths resolve the refusal, both re-issuing the same step as a loop-back:
--outcome loop_back --loop-back-target 6-finalize — replay the step inline so commit-push re-fires and the mutation is committed (inline-fixable disposition).--outcome loop_back --loop-back-target 5-execute — roll the change into a fix task (fix-task-required disposition).Storage shape (breaking — replaces the old bare-string shape):
status.metadata.phase_steps[{phase}][{step}] = {
"outcome": "done" | "skipped" | "loop_back" | "failed",
"display_detail": <string> | null,
"head_at_completion": <sha> | absent,
"loop_back_target": "5-execute" | "6-finalize" | absent
}
Both the metadata and phase_steps containers are created on demand. Bare-string entries from prior versions are treated as drift — see conflict semantics below. The head_at_completion and loop_back_target keys are only present when the corresponding flag was supplied (per the _build_entry helper); loop_back_target is structurally guaranteed to be present iff outcome == "loop_back".
Semantics:
changed: false is returned.display_detail, head_at_completion, or loop_back_target differ, the command updates the entry in place and returns changed: true.--force is not supplied, the command returns error: conflict with the existing outcome surfaced in the response. Supplying --force overwrites the existing value (and detail / head / loop_back_target).error: legacy_string_entry and refuses to write. The caller must migrate status.metadata.phase_steps to the dict shape before retrying — there is no automatic migration.Forward reference —
phase_steps_completeinvariant: Downstream phase skills and verification helpers treatstatus.metadata.phase_steps[{phase}]as the authoritative record of which intra-phase steps have been markeddoneorskipped. A phase is consideredphase_steps_completewhen every step in the phase's declared step list has a dict entry withoutcome == 'done'. The invariant reader rejects bare-string entries as legacy drift. Consumers must not fabricate entries by other means — always go throughmark-step-done.
Output — idempotent no-op (TOON):
status: success
plan_id: my-feature
phase: 5-execute
step: discovery
outcome: done
display_detail: null
changed: false
Output — state changed (TOON):
status: success
plan_id: my-feature
phase: 5-execute
step: discovery
outcome: done
display_detail: Discovered 3 drift candidates across deliverables 2 and 4
changed: true
previous_outcome: null
previous_display_detail: null
Output — conflict (TOON):
status: error
plan_id: my-feature
error: conflict
phase: 5-execute
step: discovery
existing_outcome: skipped
requested_outcome: done
message: Step 'discovery' in phase '5-execute' already marked as 'skipped'; use --force to overwrite with 'done'
Output — legacy drift (TOON):
status: error
plan_id: my-feature
error: legacy_string_entry
phase: 5-execute
step: discovery
existing_outcome: done
requested_outcome: done
message: Step 'discovery' in phase '5-execute' has legacy bare-string storage ('done'); migrate status.metadata.phase_steps to the dict shape {"outcome": ..., "display_detail": ...} before retrying.
Output — dirty-worktree refusal (TOON):
status: error
plan_id: my-feature
error: dirty_worktree_done_refused
phase: 6-finalize
step: automated-review
dirty: true
message: Step 'automated-review' in phase '6-finalize' may mutate the worktree, but the working tree is dirty — refusing to mark it done. Re-issue with --outcome loop_back --loop-back-target 6-finalize to replay the step inline (commit-push the change), or --outcome loop_back --loop-back-target 5-execute to roll the change into a fix task.
Read-only verdict over status.metadata.phase_steps[{phase}][{step}]: does a terminal step record exist? The phase-6-finalize dispatcher calls this after every dispatched (Task-agent) step returns, to detect the silent gap where a step returns status: success but skips its mandated mark-step-done side-effect — the omission that otherwise stays invisible until the phase_steps_complete handshake deadlocks the phase transition with no per-step attribution. The verb performs zero writes to status.json.
A record counts as recorded iff a dict entry with a terminal outcome in {done, skipped, loop_back, failed} is present (a loop_back record counts as terminal for guard purposes — the dispatcher re-fires it via the resumable re-entry check). Bare-string legacy entries and missing entries both report recorded: false.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status assert-step-recorded \
--plan-id {plan_id} \
--phase {phase_name} \
--step {step_id} \
[--require-terminal]
Parameters:
--plan-id (required): Plan identifier--phase (required): Phase name (e.g., 6-finalize)--step (required): Step identifier within the phase--require-terminal (optional): Escalate a missing terminal record to status: error, error: step_record_missing instead of returning recorded: false. The post-dispatch guard passes this flag so a missing record is a branchable failure verdict rather than a soft boolean.Output — recorded (TOON):
status: success
plan_id: my-feature
phase: 6-finalize
step: ci-verify
recorded: true
outcome: done
Output — not recorded (no --require-terminal) (TOON):
status: success
plan_id: my-feature
phase: 6-finalize
step: ci-verify
recorded: false
outcome: null
Output — missing record under --require-terminal (TOON):
status: error
plan_id: my-feature
error: step_record_missing
phase: 6-finalize
step: ci-verify
recorded: false
outcome: null
message: No terminal record for step 'ci-verify' in phase '6-finalize': the dispatched step returned without recording a mark-step-done outcome (expected one of ['done', 'skipped', 'loop_back', 'failed']).
Get combined status context (phase, progress, metadata) in one call.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status get-context \
--plan-id {plan_id}
Output (TOON):
status: success
plan_id: my-feature
title: My Feature
current_phase: 2-refine
total_phases: 6
completed_phases: 1
change_type: feature
Note: All metadata fields are promoted to top level for convenience (flattened from metadata object). The fields shown depend on what has been set via metadata --set.
Resolve the persisted worktree path for a plan from status.metadata.
Allows callers (build wrappers, git-workflow, phase-entry assertions)
to look up the active worktree by --plan-id alone — without
re-deriving the path from filesystem layout, and without taking a
--project-dir argument.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status get-worktree-path \
--plan-id {plan_id}
Parameters:
--plan-id (required): Plan identifierBehavior (tri-state, discriminated by worktree_state):
metadata.use_worktree == false (or metadata is absent) → worktree_state: disabled, worktree_path: ''. Callers interpret this as "plan runs against the main checkout".metadata.use_worktree == true and metadata.worktree_path is set → worktree_state: materialized, worktree_path: <abs>. The worktree directory has been created.metadata.use_worktree == true and metadata.worktree_path is missing/empty → worktree_state: pending, worktree_path: '', not_yet_materialized: true. The plan opted into worktree mode but the directory has not been materialized yet (pre-materialization). Callers MUST fall back to the main checkout cwd.The worktree_unresolved error path is owned by phase_handshake verify, which validates filesystem-resolvability of a non-empty worktree_path. This subcommand never returns that error; it returns pending for the pre-materialization state instead.
Output — disabled (main checkout) (TOON):
status: success
plan_id: my-feature
use_worktree: false
worktree_state: disabled
worktree_path: ""
Output — materialized (TOON):
status: success
plan_id: my-feature
use_worktree: true
worktree_state: materialized
worktree_path: /abs/path/.plan/local/worktrees/my-feature
worktree_branch: feature/my-feature
Output — pending (pre-materialization) (TOON):
status: success
plan_id: my-feature
use_worktree: true
worktree_state: pending
worktree_path: ""
not_yet_materialized: true
Discover all plans, optionally filtered by current phase.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status list \
[--filter PHASE]
Parameters:
--filter (optional): Comma-separated phase names to filter byOutput (TOON):
status: success
total: 2
plans[2]{id,current_phase,status,location}:
my-feature,3-outline,in_progress,current
bugfix-123,5-execute,in_progress,worktree
Each entry carries a location field: current (the plan directory lives on the cwd checkout) or worktree (the plan directory was moved into its worktree at phase-5 entry, ADR-002). The merged list is deduped by plan id (a moved-in plan appears exactly once) and sorted by id.
Concurrent-session visibility: per ADR-002, a plan's non-git-controlled runtime state (its plan directory) MOVES into the plan's own worktree at phase-5 entry and moves back to main at finalize. While a plan is executing (phase-5+), its plan directory therefore lives in its worktree, not on main. cmd_list discovers both sources: it enumerates the main checkout's plans (location: current) AND scans get_worktree_root()'s child worktrees for moved-in plans (location: worktree), so a list run from the main checkout DOES surface a plan that is mid-flight in its worktree — the ADR-002 move-in is exactly why the worktree scan is necessary. See marketplace/bundles/plan-marshall/skills/workflow-integration-git/standards/worktree-handling.md for the worktree lifecycle that produces this property.
Mark a phase as done and advance to next phase. Validates phase ordering.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status transition \
--plan-id {plan_id} \
--completed {phase_name}
Output (TOON):
status: success
plan_id: my-feature
completed_phase: 3-outline
next_phase: 4-plan
Archive a completed plan (moves to .plan/archived-plans/YYYY-MM-DD-{plan_id}).
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status archive \
--plan-id {plan_id} \
[--dry-run] \
[--reason REASON]
--reason REASON persists a human-readable explanation to
status.metadata.archived_reason on the archived plan. The field is additive
(omitted when the flag is absent — no schema migration). Used by plan-doctor
rule stuck-low-confidence-archive as the canonical remediation flag so a
retrospective audit can distinguish intentional abandonment from neglect.
Example values: low_confidence, scope_changed, superseded_by_<plan_id>.
Output (TOON):
status: success
plan_id: my-feature
archived_to: .plan/archived-plans/2026-04-02-my-feature
Delete an entire plan directory. Used when user selects "Replace" for an existing plan during plan-init.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status delete-plan \
--plan-id {plan_id}
Output (TOON format):
On success:
status: success
plan_id: my-feature
action: deleted
path: /path/to/.plan/plans/my-feature
files_removed: 5
On error (plan not found):
status: error
plan_id: my-feature
error: plan_not_found
message: Plan directory does not exist: /path/to/.plan/plans/my-feature
Use case: Called by plan-init when user selects "Replace" to delete existing plan before creating new one. See plan-marshall:phase-1-init/standards/plan-overwrite.md for the full workflow.
Warning: This recursively deletes the entire plan directory including all subdirectories (logs, tasks, work artifacts). There is no undo.
Get skill name for a phase.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status route \
--phase {phase_name}
Output (TOON):
status: success
phase: 3-outline
skill: solution-outline
description: Create solution outline with deliverables
Get combined routing context (phase + skill + progress in one call).
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status get-routing-context \
--plan-id {plan_id}
Output (TOON):
status: success
plan_id: my-feature
title: Add caching layer
current_phase: 3-outline
skill: solution-outline
skill_description: Create solution outline with deliverables
total_phases: 6
completed_phases: 2
Cross-plan merge-coordination mutex with three sub-verbs (acquire / check / release). Serializes the rebase/merge-to-main critical section across concurrently-finalizing plans so two plans can never race on the merge-to-main step. The lock is a cooperative marker stored under status.metadata of the acquiring plan:
| Marker field | Type | Description |
|--------------|------|-------------|
| merging_on_main | bool | true while the plan holds the lock |
| merge_lock_acquired_at | string (ISO-8601 UTC) | Acquisition timestamp, so contention diagnostics can identify how long the holder has held the marker |
All marker reads/writes flow through the _status_core helpers — there is no direct .plan/ file access in the handler.
Lifecycle states: acquired (this plan holds the marker), released/absent (no marker), blocked (poll window elapsed while another plan held it).
acquire — BLOCKING. Scans every OTHER plan's status.json (same discovery path as list) for an existing holder. If free, writes the marker and returns status: acquired. If held, enters a time.sleep poll loop (fixed module-level interval, total window 5 minutes / 300s), re-checking each interval; on elapse returns status: blocked with blocking_plan_id. The poll lives entirely inside the Python handler — there is NEVER a Bash poll loop. AskUserQuestion is never issued from this verb: the timeout path only returns the structured blocked payload, and the orchestrator (branch-cleanup.md Pre-Merge Gate) owns the user-prompt escalation naming blocking_plan_id.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status merge-lock acquire \
--plan-id {plan_id}
Output — acquired (TOON):
status: acquired
plan_id: my-feature
acquired_at: 2026-05-29T13:50:00Z
Output — blocked (TOON):
status: blocked
plan_id: my-feature
blocking_plan_id: other-plan
poll_window_seconds: 300.0
check — non-blocking read of the current holder (including this plan's own self-held marker).
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status merge-lock check \
--plan-id {plan_id}
Output — free (TOON):
status: free
plan_id: my-feature
Output — held (TOON):
status: held
plan_id: my-feature
holder_plan_id: other-plan
release — idempotently clears this plan's marker (no-op when not held). released is true only when a marker was actually present and cleared.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status merge-lock release \
--plan-id {plan_id}
Output (TOON):
status: success
plan_id: my-feature
released: true
Verify manage-status health (checks imports, phase routing table, directory access).
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status self-test
Output (TOON):
status: success
passed: 4
failed: 0
Phase set, transition rules, and phase-to-skill routing are defined in standards/status-lifecycle.md. The standard 6-phase model (1-init through 6-finalize) is sequential — the transition command enforces ordering.
Script: plan-marshall:manage-status:manage-status
| Command | Parameters | Description |
|---------|------------|-------------|
| create | --plan-id --title --phases [--force] [--use-worktree] | Create status.json (records use_worktree intent when --use-worktree is present; the branch and worktree_path are derived at phase-5-execute Step 2.5) |
| read | --plan-id | Read full status |
| set-phase | --plan-id --phase | Set current phase (marks as in_progress) |
| update-phase | --plan-id --phase --status | Update specific phase status |
| progress | --plan-id | Calculate progress percentage |
| metadata | --plan-id --get/--set --field [--value] | Get/set metadata fields |
| mark-step-done | --plan-id --phase --step --outcome [--display-detail] [--head-at-completion] [--loop-back-target] [--force] | Record phase step outcome (+ optional display detail / HEAD SHA / loop-back target) in metadata.phase_steps |
| assert-step-recorded | --plan-id --phase --step [--require-terminal] | Read-only verdict: reports recorded: true iff a terminal metadata.phase_steps[phase][step] outcome exists. The phase-6-finalize post-dispatch guard. With --require-terminal, a missing record returns error: step_record_missing. Zero writes. |
| get-context | --plan-id | Get combined status context |
| get-worktree-path | --plan-id | Resolve persisted worktree path (returns empty string when use_worktree==false) |
| list | [--filter PHASE] | Discover all plans across the main checkout and its worktrees (each entry tagged location: current/worktree), optionally filtered by phase |
| transition | --plan-id --completed | Mark phase done, advance to next |
| archive | --plan-id [--dry-run] [--reason REASON] | Archive completed plan; --reason persists to status.metadata.archived_reason (used by plan-doctor stuck-low-confidence-archive rule) |
| delete-plan | --plan-id | Delete entire plan directory |
| route | --phase | Get skill name for phase |
| get-routing-context | --plan-id | Get combined routing context |
| change-type-heuristic | --plan-id [--persist] | Deterministic change-type classifier for phase-3-outline Step 4. Reads the clarified-request narrative (falling back to original_input) and scores it against a fixed keyword table — returns one of feature, bug_fix, tech_debt, enhancement, verification, analysis, or ambiguous=true when no keyword fires / two change types tie / confidence < 0.7. With --persist, writes the resolved change_type to status.metadata.change_type (skipped in the ambiguous branch so the LLM detect-change-type workflow is the single writer there). |
| aggregate-confidence | --plan-id [--scores-file PATH] [--correctness N] [--completeness N] [--consistency N] [--non-duplication N] [--ambiguity N] [--module-mapping N] [--persist] | Weighted-math confidence aggregator for phase-2-refine Step 10. Computes the overall confidence from per-dimension scores (0..100) using the fixed weights correctness 20% / completeness 20% / consistency 20% / non-duplication 10% / ambiguity 20% / module-mapping 10%. Missing dimensions default to 0 and are recorded in missing_dimensions. Scores can be supplied via --scores-file (JSON object keyed by dimension) and / or individual CLI flags; flags take precedence on conflict. With --persist, the overall confidence is written to status.metadata.confidence. |
| merge-lock acquire | --plan-id | BLOCKING acquire of the cross-plan merge-lock. Writes the merging_on_main marker when free; polls (Python time.sleep, 5-minute window) when held, then returns status: blocked with blocking_plan_id on timeout. Never issues AskUserQuestion — orchestrator owns the escalation. |
| merge-lock check | --plan-id | Non-blocking read of the current merge-lock holder (status: free or held with holder_plan_id). |
| merge-lock release | --plan-id | Idempotently clear this plan's merge-lock marker; released: true only when a marker was present. |
| self-test | (none) | Verify manage-status health |
The canonical argparse surface for manage-status.py. The D4 plugin-doctor analyzer
(_analyze_manage_invocation.py) reads this section as source-of-truth for markdown
notation occurrences across the marketplace. Consuming skills xref this section by
name (e.g., "see manage-status Canonical invocations → transition") instead of
restating the command inline.
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status create \
--plan-id PLAN_ID --title TEXT --phases CSV \
[--force] \
[--use-worktree]
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status read \
--plan-id PLAN_ID
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status set-phase \
--plan-id PLAN_ID --phase PHASE
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status update-phase \
--plan-id PLAN_ID --phase PHASE --status {pending|in_progress|done}
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status progress \
--plan-id PLAN_ID
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status metadata \
--plan-id PLAN_ID --field FIELD \
(--get | --set --value VALUE)
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status get-context \
--plan-id PLAN_ID
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status get-worktree-path \
--plan-id PLAN_ID
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status list \
[--filter PHASES_CSV]
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status transition \
--plan-id PLAN_ID --completed PHASE
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status archive \
--plan-id PLAN_ID [--dry-run] [--reason REASON]
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status route \
--phase PHASE
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status get-routing-context \
--plan-id PLAN_ID
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status delete-plan \
--plan-id PLAN_ID
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status mark-step-done \
--plan-id PLAN_ID --phase PHASE --step STEP_ID \
--outcome {done|skipped|loop_back|failed} \
[--force] [--display-detail TEXT] [--head-at-completion SHA]
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status assert-step-recorded \
--plan-id PLAN_ID --phase PHASE --step STEP_ID \
[--require-terminal]
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status change-type-heuristic \
--plan-id PLAN_ID [--persist]
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status aggregate-confidence \
--plan-id PLAN_ID \
[--scores-file PATH] \
[--correctness N] [--completeness N] [--consistency N] \
[--non-duplication N] [--ambiguity N] [--module-mapping N] \
[--persist]
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status merge-lock acquire \
--plan-id PLAN_ID
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status merge-lock check \
--plan-id PLAN_ID
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status merge-lock release \
--plan-id PLAN_ID
python3 .plan/execute-script.py plan-marshall:manage-status:manage-status self-test
See manage-contract.md for the standard error response format.
| Error Code | Exit Code | Cause |
|------------|-----------|-------|
| invalid_plan_id | 1 | Plan ID not in kebab-case format |
| file_not_found | 1 | status.json doesn't exist |
| file_exists | 1 | status.json already exists (use --force) |
| invalid_phase | 1 | Phase name not in the phases list (set-phase, update-phase, transition) |
| phase_not_found | 1 | Phase doesn't exist in this plan's status.json phases array |
| unknown_phase | 1 | Phase name not in the static valid phases set (1-init through 6-finalize); only used by route command |
| plan_not_found | 1 | Plan directory does not exist (delete-plan command) |
| not_found | 1 | Plan directory not found (archive command) |
| not_found | 0 | Metadata field doesn't exist — valid query result (returns value: null), not an error |
| conflict | 1 | mark-step-done: step already has a different outcome and --force was not supplied |
| legacy_string_entry | 1 | mark-step-done: existing entry uses the pre-migration bare-string shape; caller must migrate to dict shape before retrying |
| invalid_outcome | 1 | mark-step-done: outcome not in done/skipped/loop_back/failed |
| invalid_argument | 1 | mark-step-done: empty --phase or --step |
| missing_loop_back_target | 1 | mark-step-done: --outcome=loop_back supplied without --loop-back-target. The flag is REQUIRED on every loop_back outcome (no backwards-compat fallback). |
| invalid_loop_back_target | 1 | mark-step-done: --loop-back-target value not in 5-execute/6-finalize. (Argparse choices normally catches this at parse time; this error fires only when the validation is bypassed at the API layer.) |
| unexpected_loop_back_target | 1 | mark-step-done: --loop-back-target supplied alongside an outcome other than loop_back. The flag is FORBIDDEN on done/skipped/failed outcomes. |
| dirty_worktree_done_refused | 1 | mark-step-done: --outcome=done for a step in MAY_MUTATE_WORKTREE_STEPS (automated-review/sonar-roundtrip/finalize-step-simplify) while the resolved worktree is dirty (git status --porcelain non-empty). Re-issue as --outcome loop_back --loop-back-target 6-finalize (inline replay) or --loop-back-target 5-execute (fix-task rollback). |
| step_record_missing | 0 | assert-step-recorded --require-terminal: no terminal record exists for the named phase step (the dispatched step returned without recording a mark-step-done outcome). Exit code is 0 — the post-dispatch guard branches on the TOON error field, not the process exit code. |
| worktree_unresolved | 1 | phase_handshake verify: metadata.use_worktree==true and metadata.worktree_path is non-empty but does not resolve on the filesystem. get-worktree-path does not emit this error — it returns worktree_state: pending for the pre-materialization state. |
Called by: plan-marshall:plan-marshall orchestrator for phase transitions, phase-1-init for initial status creation, and phase-6-finalize for archiving.
Phase skills read/update status through manage-status:
createset-phase, metadata, get-context, transitionarchive for completed plansAgents use metadata to store change_type and other classification data.
plan-marshall — Orchestrator that drives phase transitionsphase-1-init through phase-6-finalize — Phase-specific skills routed to by manage-statusmanage-metrics — Augments phase tracking with timing and token datamanage-config — System configuration consumed by status operationstools
Plan-marshall-domain implementor of the ext-self-review-{domain} extension point. Surfaces deterministic candidates (regexes, user-facing strings, markdown sections, symmetric-pair functions, flag-guard pairs, contract sources, schema-bearing files) for pre-submission structural self-review.
development
The single shared contract every untrusted-external-content ingestion surface loads — reader/orchestrator/writer isolation, the deterministic validator script as the containment boundary, and the output-schema discipline for candidate structs parsed from web pages, GitHub issue/PR/comment bodies, and Sonar issue messages
development
Domain-invariant recipe for deliberate wide-scope simplification campaigns across a scope x thoroughness cell, with a T4+ relation-graph pre-deliverable
testing
A test skill for README generation