On confirmation, write .claude/state/workflow.json (post-§18 shape — uses track_id from the chosen Track in .claude/workflows.jsonl, NOT the old entry_phase field):
{
"request": "<the request>",
"slug": "<workflow slug>",
"track_id": "<intake-full|spec-entry|tdd-quickfix|chore|freeform>",
"exceptions": ["<phase>", ...],
"completed": [],
"skipped_alternates": [],
"source_backlog_keys": ["<backlog stable key>", ...],
"created_at": <epoch>,
"updated_at": <epoch>
}
The track_id value is the track_id field of the Track you picked in step 5c above (one of intake-full, spec-entry, tdd-quickfix, chore, freeform, OR a project-declared selectable Track from .claude/workflows.jsonl). The legacy pre-§18 field entry_phase is NOT written — downstream skills (intake / tdd / chore / harness) read track_id directly. Pre-§18 workflow.json files (those that still carry entry_phase) are auto-migrated by harness preflight Step 3a via the shipped .claude/skills/harness/workflow-migrator.js mirror (synced from src/cli/workflow-migrator.js at build time by scripts/build-template.sh Stage 0b).
The source_backlog_keys field is optional. When the user's request explicitly names one or more backlog entries this workflow picks up (the common framing is a Source: line listing backlog keys), populate the array with those keys. /commit (Phase 11) reads this field and invokes sweep.mjs --mode stamp-closure after the commit lands, stamping each named entry with status: picked-up + superseded-at: <today> so the next /memory-flush Step 0a auto-closes them. Absent / empty array → /commit skips the stamp step entirely (backward-compatible for any workflow that pre-dates the field). /triage does NOT auto-detect backlog keys from free-form prose — the user populates the field (or names them in the triage prompt and you populate it during step 4).
Seed the workflow tasklist — workflows.jsonl-driven (post-§18; per CLAUDE.md Article IV amendment + seed.md §18).
Source of truth. .claude/workflows.jsonl declares every Track this project can execute, one Track per JSONL line. The five canonical selectable tracks (intake-full, spec-entry, tdd-quickfix, chore, freeform) plus any per-project additions live there. Sub-tracks (selectable=false; e.g., swarm-implementation, tdd-worker-chain) are referenced by sub_track: in selector-node alternates.
Procedure:
a. Load + validate. Run node .claude/skills/triage/seed-tasklist.mjs --validate-only to parse .claude/workflows.jsonl and verify every Track against the §18 invariants (I1..I11). On validation failure, the helper exits non-zero and prints a named error citing the offending track / node / line. Halt triage; tell the user to fix workflows.jsonl or run /init-project doctor to repair drift.
b. Classify (LLM-driven). Read each selectable Track's name, description, and selector_hints from workflows.jsonl. Match against the user's request using natural-language reasoning — selector_hints are descriptive aids, NOT match tokens. Rank the tracks by plausibility for the request. Selectable Tracks whose track-level preconditions[] evaluate false in this project (e.g., requires_git on a non-git project) are excluded from the candidate set BEFORE ranking — they cannot be picked.
c. Confirm (AskUserQuestion, always). Present the picked Track plus the top 2-3 alternates via AskUserQuestion. Confidence thresholds are not used; the user picks. On ambiguity (e.g., chore vs intake-full for a documentation refactor), surface both and let the user decide.
d. Materialize TaskList. Run node .claude/skills/triage/seed-tasklist.mjs <track_id> <slug> to emit the canonical TaskList JSON for the chosen Track (subjects, activeForms, metadata.phase, needs_user, blockedBy by ordinal — driven by the shipped .claude/skills/triage/track-tasklist-materializer.js mirror, synced from src/cli/track-tasklist-materializer.js at build time). For each entry, call TaskCreate to register the task; capture the returned task_id. For each entry's blockedBy ordinals, call TaskUpdate addBlockedBy mapping ordinals to the captured task_ids of the predecessor entries.
e. source_backlog_keys (optional). If the user's request names backlog entries (typical framing: a Source: line listing backlog keys), populate workflow.json → source_backlog_keys with those keys. /commit reads this and stamps closure on the named entries after the commit lands.
Fallback for missing workflows.jsonl. A baseline install always ships .claude/workflows.jsonl (pristine template overlaid by scripts/build-template.sh Stage 2; CLI install copies it). If the file is missing on disk, the install is broken — halt triage with a named error and tell the user to run /init-project doctor to regenerate the file from the pristine template.
Non-git projects. Tracks declaring git_only invariant (e.g., swarm-implementation) are excluded from the candidate set on non-git projects. The commit-bearing tracks (intake-full, spec-entry, tdd-quickfix, chore) auto-except their grant-commit, commit nodes — the materializer's runtime context (passed by triage) carries an excluded_node_ids set; the helper skips those nodes during TaskCreate emission.
Reference: canonical track shapes. The selectable tracks (chore, tdd-quickfix, spec-entry, intake-full, freeform) and the two sub-tracks are declared authoritatively in .claude/workflows.jsonl — one Track per line, each with its node DAG (nodes[] with id, depends_on, metadata.phase, needs_user). That file is the single source of the canonical track shapes; read it directly rather than relying on a prose copy here (the prior duplicated templates were removed in WF-5 to prevent drift). The materializer (track-tasklist-materializer.js) renders the same DAGs into the TaskList. Non-git projects: commit-bearing tracks auto-except grant-commit, commit (and intake-full's swarm branch) per the Non-git note above.
For every task: subject is imperative ("Run /scout for " / "Wait for /approve-spec "); description names the phase + the slug; metadata.phase carries the phase name; consent-gate tasks set metadata.needs_user: true. Wire addBlockedBy so each task blocks until its predecessor completes — this surfaces the workflow's true dependency graph and prevents /harness from racing past a gate.