Skip to content

T-T5RB-consolidate-task-status-enum

Status: closed/done · Impact: high · Complexity: large

The task status enum has accumulated semantically duplicated values (done vs closed/done, superseded vs closed/superseded, bare closed paired with legacy resolution_* fields, etc.) that exist only because the schema grew incrementally. Collapse the enum to a single canonical shape organized under four top-level majors — planning/, open/, in-progress, closed/ — and migrate every existing instance. This is also the first schema-version bump (v1 → v2), so it establishes the per-version transform pattern that future schema changes will follow.

plugin/entities/task/schema.json exposes 21 values in the status enum, of which only 5 are in use across the 28 task files in docs/planning/tasks/: draft (10), ready (3), in-progress (2), closed/done (8), closed/superseded (1). The remaining 16 are a mix of legacy bare forms (done, superseded, closed, open, partially-superseded, relocated-upstream, investigated-no-repro) and never-used closed reasons (closed/partially-superseded, closed/obsoleted, closed/relocated, closed/no-repro, closed/wontdo, closed/needs-definition, etc.). The duplicate shapes force every reader (skill, audit, human) to think about whether done and closed/done mean the same thing — they do, and they shouldn’t both exist.

The allOf block carries a bare-closed-requires-resolution_* branch purely to support files that nobody is writing anymore. schema_version is at 1; no schema has ever bumped, so the audit_entities.py / migrate_entities.py per-version handler chain exists only in concept (see plugin/skills/entities-audit/SKILL.md notes on stale-version drift being manual today).

Schema version bumps to 2. The status enum contains exactly the following values, in four majors:

planning/ — spec still being shaped, not pickable

  • planning/draft — captured idea; body still being written
  • planning/proposed — body filled in; awaiting refinement / triage
  • planning/needs-definition — refinement blocked on a missing piece (gap recorded in definition_gap)
  • planning/backlog — triaged; on the list but not next

open/ — available to be picked up right now

  • open/ready — implementation-ready; passes task-ensure-ready

in-progress — someone has it (bare; /blocked qualifier when applicable)

  • in-progress
  • in-progress/blocked — work started, paused on external dependency

closed/ — terminal

  • closed/done — shipped
  • closed/superseded — fully replaced by another task
  • closed/partially-superseded — partially replaced
  • closed/obsoleted — no longer relevant
  • closed/relocated — moved to another tracker / repo
  • closed/no-repro — investigated, couldn’t reproduce
  • closed/wontdo — decided not to do

Every existing task file in this repo has been rewritten to use the new shape. Every plugin script and skill that references status values has been updated. The migrate skill now dispatches stale schema_version drift through a per-type version chain, with plugin/entities/task/migrations/v1-to-v2.py as the first transform.

  1. Schema v2 — In plugin/entities/task/schema.json: bump version to 2, replace the enum with the 14-entry list above, remove the bare-closed-requires-resolution_* branch from allOf (the bare closed status no longer exists), keep the closed/* requires completion_note branch. Update the schema description to drop the “legacy shape” language.

  2. Template — Update plugin/entities/task/template.md: change the initial status: to planning/draft, bump schema_version: "2", update the HTML-comment workflow line to walk planning/draft → planning/proposed → planning/backlog → open/ready.

  3. Creation script defaults — In plugin/scripts/new_task.py: change STATUS_CHOICES to ("planning/draft", "planning/proposed", "planning/backlog", "open/ready"), default to planning/draft. Update the CLI help text.

  4. Skill references — Audit every plugin/skills/*/SKILL.md that hardcodes status values. Known callers: task-work (flips status on pickup; current expects ready, moves to open/ready), task-ensure-ready (downshifts to proposed/needs-definition; moves to planning/needs-definition), task-review, task-define, task-new, backlog-triage. Each gets a search-and-replace for the literal status strings plus a re-read for any prose that mentions the old shape.

  5. v1→v2 migration transform — Create plugin/entities/task/migrations/v1-to-v2.py as a pure function that takes a parsed-frontmatter dict and returns the migrated dict. Mapping table is in the Migration mapping section below. For the bare-closed-with-resolution case, infer the target stage from resolution, copy any completion_note already present, and drop the legacy resolution, resolution_date, resolution_commit keys. Raise a clear MigrationError if a bare-closed file has no resolution field (shouldn’t happen given the v1 schema’s allOf branch, but defensive).

  6. Audit + migrate wiring — In audit_entities.py: when a file’s schema_version is older than the schema’s current version, check whether a transform exists at <entities_dir>/<type>/migrations/v<file_ver>-to-v<curr_ver>.py; if yes, tag the drift auto_fixable: true. In migrate_entities.py: register a handler for the schema_version/older than drift kind that loads and applies the transform chain (today only one hop, but the structure supports v1→v2→v3 chains later). The handler updates the schema_version field after applying so the file is now stamped at the new version.

  7. Apply to this repo — Run /sdlc:entities-migrate against this repo. 13 task files should migrate (10 draft, 3 ready). Closed and in-progress files are unaffected by the status remapping but still need their schema_version bumped, so all 24 active task files will be touched in some way. Re-run the audit; confirm 0 drift.

  8. Tests — Add evals to plugin/skills/entities-migrate/tests/ covering the v1-to-v2 transform: one fixture per legacy status that requires migration (draft, ready, proposed, backlog, done, superseded, closed with each resolution variant). Add evals to plugin/skills/entities-audit/tests/ confirming stale-version drift is now auto_fixable: true when a transform exists.

This is the canonical mapping the v1-to-v2 transform implements.

old statusnew statusother field changes
draftplanning/draft
proposedplanning/proposed
proposed/needs-definitionplanning/needs-definition
backlogplanning/backlog
readyopen/ready
in-progressin-progressunchanged
in-progress/blockedin-progress/blockedunchanged
closed/doneclosed/doneunchanged
closed/supersededclosed/supersededunchanged
closed/partially-supersededclosed/partially-supersededunchanged
closed/obsoletedclosed/obsoletedunchanged
closed/relocatedclosed/relocatedunchanged
closed/no-reproclosed/no-reprounchanged
closed/wontdoclosed/wontdounchanged
closed (bare; resolution: fixed)closed/donedrop resolution, resolution_date, resolution_commit; require completion_note (synthesize from resolution_commit if missing: “Resolved in “)
closed (bare; resolution: wontfix)closed/wontdosame field cleanup
closed (bare; resolution: superseded)closed/supersededsame
closed (bare; resolution: obsoleted)closed/obsoletedsame
open (bare legacy)planning/proposedbest-guess default; no other field changes
done (bare legacy)closed/donerequire completion_note (synthesize “Migrated from bare done status” if missing)
superseded (bare legacy)closed/supersededsame completion_note synthesis
partially-superseded (legacy)closed/partially-supersededsame
relocated-upstream (legacy)closed/relocatedsame
investigated-no-repro (legacy)closed/no-reprosame
  • plugin/entities/task/schema.json — bump version to 2, replace status enum, remove bare-closed allOf branch
  • plugin/entities/task/template.mdschema_version: "2", initial status: planning/draft, comment block update
  • plugin/entities/task/migrations/v1-to-v2.py (new) — pure-function transform, importable by the migrate handler
  • plugin/scripts/new_task.pySTATUS_CHOICES update, default to planning/draft, help text
  • plugin/scripts/audit_entities.py — detect available transforms; flip stale-version drift to auto_fixable: true when a chain exists
  • plugin/scripts/migrate_entities.py — register handler for stale-version drift; load and apply transform chain; update schema_version on success
  • plugin/skills/task-work/SKILL.md — replace readyopen/ready, in-progress references audited, status-flip language
  • plugin/skills/task-ensure-ready/SKILL.md — downshift target becomes planning/needs-definition
  • plugin/skills/task-new/SKILL.md — status choices in interview match new enum
  • plugin/skills/task-define/SKILL.md — any proposed/needs-definition references
  • plugin/skills/task-review/SKILL.md — status references in triage prose
  • plugin/skills/backlog-triage/SKILL.md — promotion creates tasks at planning/draft (or whatever the new equivalent is)
  • plugin/skills/entities-audit/SKILL.md — drift kinds table now lists schema_version older than current as auto-fixable when transform exists
  • plugin/skills/entities-migrate/SKILL.md — document the v1-to-v2 case; note that the apply path also bumps schema_version to current
  • plugin/skills/entities-migrate/tests/run_evals.py — new fixture cases for v1-to-v2 transform
  • plugin/skills/entities-migrate/tests/fixtures/v1-to-v2-*/ (new) — one fixture per legacy status that requires migration
  • plugin/skills/entities-audit/tests/run_evals.py — update the stale-schema-version case to assert auto_fixable: true once a real transform exists
  • docs/planning/tasks/*.md — every active task file migrated by /sdlc:entities-migrate at the end (13 status updates + schema_version stamp on all 24 active files)
  • AC-1: plugin/entities/task/schema.json validates as Draft 2020-12 with version: 2 and exactly the 14-value enum listed above; bare-closed allOf branch is gone.
  • AC-2: plugin/scripts/new_task.py defaults to planning/draft; running it against a fresh project produces a file that validates cleanly under the v2 schema.
  • AC-3: plugin/entities/task/migrations/v1-to-v2.py exists, is a pure function (no I/O), and every row of the migration table above has a unit-style fixture asserting the mapping.
  • AC-4: Running /sdlc:entities-migrate against this repo’s main branch in a fresh worktree produces a diff that updates every active task file to v2 with the right status remapping; /sdlc:entities-audit afterwards exits 0 with no structural drift.
  • AC-5: plugin/skills/entities-audit/tests/run_evals.py and plugin/skills/entities-migrate/tests/run_evals.py both pass, including new cases for stale-version-with-transform and each migration row.
  • AC-6: .claude/skills/project-check/check_entities.py reports no drift across all entity directories.
  • AC-7: Every plugin/skills/*/SKILL.md that references task statuses uses only values from the v2 enum; a grep for the removed values (\bdraft\b, \bready\b in status context, proposed/needs-definition, etc.) outside of the migration transform and this task file returns no results.
  • LLM-assisted prose migration. The H2-section drift still gets reported as manual after this lands; the section-content rewriter is a separate task.
  • Milestone or backlog status reform. Those entities have their own state shapes; this task changes only the task enum. If similar duplication exists for milestones, it gets its own ticket.
  • Cross-project migration tooling (e.g., a hosted migrator for projects that adopt the plugin later). Once this lands, any v1 project that runs /sdlc:entities-migrate gets the v2 transform applied locally; that’s sufficient.
  • Renaming definition_gap, resolution_* fields, or any frontmatter key beyond the enum cleanup. The bare-closed migration removes resolution_* from migrated files; it does not remove the field definitions from the schema (deferred to a follow-up so an audit can confirm no instance still carries them first).
  • none

Raised during the SDLC:Mini session on 2026-05-19, after /sdlc:setup ran a fresh audit and surfaced the schema_version drift on this repo’s two pre-versioning tasks. The conversation about semantic duplicates in the status enum (done vs closed/done, etc.) led directly to this consolidation. Establishes the v1→v2 transform pattern for future schema changes.

Captured by /sdlc:task-work on 2026-05-19. PR: pending.

  • AC-1: auto — uv run --with jsonschema Draft 2020-12 check + JSON read of plugin/entities/task/schema.json; version: 2, 14-value enum, single allOf branch confirmed.
  • AC-2: auto — plugin/scripts/new_task.py invoked against a fresh project root; output validated with plugin/validators/validate_frontmatter.py.
  • AC-3: auto — plugin/entities/task/migrations/test_v1_to_v2.py exercises every mapping table row plus error paths; 32/32 passing.
  • AC-4: auto — migration ran against this repo’s tasks + the M0001 milestone (30 files migrated); plugin/scripts/audit_entities.py exits 0 afterwards.
  • AC-5: auto — plugin/skills/entities-audit/tests/run_evals.py 8/8 and plugin/skills/entities-migrate/tests/run_evals.py 8/8, including the new stale-version-with-transform cases.
  • AC-6: auto — .claude/skills/project-check/check_entities.py --entities-dir plugin/entities clean across 4 entity dirs; .claude/skills/project-check/tests/run_evals.py 7/7.
  • AC-7: auto — grep -rEn '\(draft|ready|proposed|backlog)`|\bproposed/needs-definition\b’ plugin/skills/*/SKILL.mdreturns no live status references (only_LEGACY_BARE_CLOSED_STATUSES` backstop set inside the audit script, which is the v1-tolerance code path itself).
  • The sub-agent produced 7 atomic, logically-scoped commits — schema/template, transform module, audit+migrate wiring, SKILL.md sweep, pre-versioned-file fix, repo migration, follow-up cleanup. Each leaves the worktree buildable.
  • Fixture-based eval pattern scaled cleanly from 4/4 to 8/8 cases on both audit and migrate suites without breaking the existing cases — the test layout supports incremental drift coverage.
  • validate_frontmatter.py + audit_entities.py + migrate_entities.py formed a tight feedback loop during migration; structural problems surfaced as exit-1 from the audit before they could compound.
  • PyYAML auto-parses unquoted ISO 8601 datetime strings into Python datetime objects, which then round-trip as 2026-05-19T21:02:01+00:00 and fail the schema’s Z-only pattern — task-ensure-ready hit this on first stamp attempt and the bad value landed in a commit before validation caught up. Two automation gaps fall out: (a) task-ensure-ready should write the timestamp quoted (or pass it through the same _coerce_for_schema path the validator uses) → T-DPO2-ensure-ready-stamps-quoted-iso-z, and (b) the validator’s pipe-to-tail in skill examples masks the exit code; skills that gate on the validator should invoke it without piping → T-XPG7-validator-skill-examples-no-pipe-tail.
  • The task spec implicitly assumed every pre-v2 file had schema_version: 1 set. In practice, two pre-existing tasks in this repo had no schema_version field at all (they predated that field), and the original migrate flow’s stamp path would have stamped schema_version: "2" onto a v1-shaped file, leaving it invalid against the v2 schema. The sub-agent had to add a generalization (commit c050d0b): if a file is missing schema_version and a transform chain exists from v1, route it through the chain rather than the stamp path. Future schema bumps should specify this case upfront in the task spec rather than relying on implementer judgment. → T-U1GD-schema-bump-tasks-handle-missing-version
  • yaml.safe_dump re-emits files with keys in insertion order. The stamp path inserts schema_version right after type:; the transform-chain path inserts it at the bottom of the dict. Functional but visually inconsistent across migrated files. A small reorder_frontmatter_keys(...) helper called by both paths would close this. → T-NIPN-migrate-reorders-frontmatter-keys
  • The implementation-ready contract requires an Out of scope section “only when the surrounding scope is non-obvious.” The task-ensure-ready evaluator currently treats it as effectively-required (it caught this task because the spec includes one). Two reasonable readings of the contract — worth disambiguating in implementation-ready.md. → T-E4ZB-clarify-out-of-scope-requirement
  • The ## Migration mapping table sits between Approach and Files-to-touch in the task body, but task-ensure-ready looks for the canonical contract sections by name. A task that introduces a domain-specific section breaks the linear flow assumption. Not blocking here, but if more tasks start carrying “reference tables” they’ll need a convention (subsection under Approach? appendix-style at the end?). → T-BFKL-ensure-ready-tolerates-domain-sections

← Back to Tasks