@friedbotstudio/create-baseline 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +14 -10
  2. package/bin/cli.js +16 -12
  3. package/obj/template/.claude/commands/init-project-doctor.md +74 -0
  4. package/obj/template/.claude/hooks/lib/resume_writer.py +14 -1
  5. package/obj/template/.claude/hooks/track_guard.sh +11 -1
  6. package/obj/template/.claude/manifest.json +29 -97
  7. package/obj/template/.claude/schemas/workflow-track.v1.json +64 -0
  8. package/obj/template/.claude/skills/audit-baseline/audit.sh +2 -2
  9. package/obj/template/.claude/skills/chore/SKILL.md +2 -2
  10. package/obj/template/.claude/skills/harness/SKILL.md +15 -6
  11. package/obj/template/.claude/skills/intake/SKILL.md +1 -1
  12. package/obj/template/.claude/skills/swarm-plan/SKILL.md +2 -0
  13. package/obj/template/.claude/skills/tdd/SKILL.md +2 -2
  14. package/obj/template/.claude/skills/triage/SKILL.md +29 -6
  15. package/obj/template/.claude/skills/triage/seed-tasklist.mjs +107 -0
  16. package/obj/template/.claude/workflows.jsonl +6 -0
  17. package/obj/template/CLAUDE.md +8 -14
  18. package/obj/template/docs/init/seed.md +148 -3
  19. package/package.json +1 -1
  20. package/src/.claude/workflows.template.jsonl +6 -0
  21. package/src/CLAUDE.template.md +8 -14
  22. package/src/cli/install.js +5 -1
  23. package/src/cli/merge.js +28 -1
  24. package/src/cli/track-tasklist-materializer.js +223 -0
  25. package/src/cli/tui/upgrade.js +14 -8
  26. package/src/cli/upgrade-tiers.js +22 -0
  27. package/src/cli/workflow-migrator.js +40 -0
  28. package/src/cli/workflows-validator-invariants.js +417 -0
  29. package/src/cli/workflows-validator-predicates.js +19 -0
  30. package/src/cli/workflows-validator.js +156 -0
  31. package/src/seed.template.md +148 -3
  32. package/obj/template/.claude/skills/google-analytics/SKILL.md +0 -129
  33. package/obj/template/.claude/skills/google-analytics/references/audiences.md +0 -389
  34. package/obj/template/.claude/skills/google-analytics/references/bigquery.md +0 -470
  35. package/obj/template/.claude/skills/google-analytics/references/custom-dimensions.md +0 -355
  36. package/obj/template/.claude/skills/google-analytics/references/custom-events.md +0 -383
  37. package/obj/template/.claude/skills/google-analytics/references/data-management.md +0 -416
  38. package/obj/template/.claude/skills/google-analytics/references/debugview.md +0 -364
  39. package/obj/template/.claude/skills/google-analytics/references/events-fundamentals.md +0 -398
  40. package/obj/template/.claude/skills/google-analytics/references/gtag.md +0 -502
  41. package/obj/template/.claude/skills/google-analytics/references/gtm-integration.md +0 -483
  42. package/obj/template/.claude/skills/google-analytics/references/measurement-protocol.md +0 -519
  43. package/obj/template/.claude/skills/google-analytics/references/privacy.md +0 -441
  44. package/obj/template/.claude/skills/google-analytics/references/recommended-events.md +0 -464
  45. package/obj/template/.claude/skills/google-analytics/references/reporting.md +0 -397
  46. package/obj/template/.claude/skills/google-analytics/references/setup.md +0 -344
  47. package/obj/template/.claude/skills/google-analytics/references/user-tracking.md +0 -417
  48. package/obj/template/.claude/skills/optimize-seo/SKILL.md +0 -313
  49. package/obj/template/.claude/skills/optimize-seo/scripts/pagespeed.mjs +0 -197
  50. package/obj/template/.claude/skills/pagespeed-insights/LICENSE.md +0 -37
  51. package/obj/template/.claude/skills/pagespeed-insights/SKILL.md +0 -446
  52. package/obj/template/.claude/skills/pagespeed-insights/reference.md +0 -50
@@ -56,6 +56,7 @@ THEN write `harness_state`. The marker is the session-scoped "in the loop" signa
56
56
  3. **Fresh start or resume?**
57
57
  - `.claude/state/workflow.json` exists → **resume**.
58
58
  - Absent → **fresh start**. The argument (or surrounding conversation) is the request; proceed to Pillar 1.
59
+ 3a. **Pre-§18 workflow.json migrator (post-§18 baseline).** If `workflow.json` carries the pre-§18 shape (has `entry_phase`, no `track_id`), run a one-shot migrator before continuing: `node -e "import('./src/cli/workflow-migrator.js').then(m => m.migrateWorkflowJsonInPlace('.claude/state/workflow.json'))"`. The migrator derives `track_id` from `entry_phase` via the canonical map (intake → intake-full, spec → spec-entry, tdd → tdd-quickfix, chore → chore), remaps `completed[]` phase-names to node-ids, initializes `skipped_alternates: []`, refreshes `updated_at`, and removes `entry_phase`. Idempotent: already-post-§18 input is a no-op. Unmapped `entry_phase` throws; halt with the migrator's error message and tell the user to re-run `/triage` to restart this workflow.
59
60
  4. **Ground the user before acting.** When `_resume.md` is present, open with one sentence summarizing where things stood. Grounding only — do not invent state not in `workflow.json`.
60
61
  5. **Detect divergence.** If `_resume.md`'s recent prompts contradict `workflow.json` (e.g., the user said "actually skip security" mid-session and `exceptions` doesn't reflect it), do **not** auto-proceed. Surface as a clarifying question. Memory accelerates triage; it never authorizes a skip.
61
62
  6. **Arm the safety net.** Marker FIRST: `echo "<slug>" > .claude/state/.harness_active`. Then write `harness_state` with `{state: "continue", slug, reason: "loop armed; preflight passed"}`. This pair stays in place for the entire loop; mid-loop crashes are now covered by the Stop hook.
@@ -66,8 +67,8 @@ Log every transition to `.claude/state/harness/<slug>.log` with timestamp + `ent
66
67
 
67
68
  Inside each iteration:
68
69
 
69
- 1. **TaskList.** If empty (first invocation in a fresh session, or session-bound state was reset), **re-seed**: read `workflow.json → completed + exceptions + entry_phase` and recreate tasks for every remaining phase using the canonical templates from `triage`'s SKILL.md. Skip phases already in `completed`. Wire `addBlockedBy` so the dependency chain is preserved.
70
- 2. **Pick the next action.** Find the lowest-id `pending` task whose `blockedBy` list is empty.
70
+ 1. **TaskList.** If empty (first invocation in a fresh session, or session-bound state was reset), **re-seed** from `workflow.json → track_id` (post-§18) via the materializer: run `node .claude/skills/triage/seed-tasklist.mjs <track_id> <slug>` to emit the canonical TaskList JSON for the track. Skip nodes whose `metadata.phase` is in `workflow.json completed` or in `exceptions`. Wire `addBlockedBy` from each emitted entry's `blockedBy` ordinals (translated to session task_ids of predecessors). Pre-§18 workflow.json files (`entry_phase` set, no `track_id`) SHALL have been migrated by preflight Step 3a before re-seed runs; if `track_id` is still absent here, fall back to the canonical templates documented in `triage/SKILL.md` Step 5's "Reference: canonical track shapes" subsection.
71
+ 2. **Pick the next action.** Find the lowest-id `pending` task whose `blockedBy` list is empty. Then check for a **parallel cluster** (SP-002 / Article IV invariant supporting `can_parallel: true`): if the picked task carries `can_parallel: true` in its metadata AND one or more SIBLING pending tasks share both (a) identical `blockedBy` lists AND (b) `can_parallel: true`, group the picked task + every such sibling into a single cluster for this iteration. If no siblings share both conditions, proceed with the single task as today.
71
72
  3. **If no pending task remains** (workflow complete), **EXIT LOOP with DONE**:
72
73
  - Marker FIRST: `rm -f .claude/state/.harness_active`.
73
74
  - Write `harness_state` with `{state: "done", slug, reason: "workflow complete"}`.
@@ -76,10 +77,18 @@ Inside each iteration:
76
77
  - Marker FIRST: `rm -f .claude/state/.harness_active`.
77
78
  - Write `harness_state` with `{state: "yielded", slug, reason: "yielded at /<gate>"}` — exactly three fields.
78
79
  - Break out of the loop; the terminal message names the consent command for the user to run.
79
- 5. **Otherwise INVOKE the phase skill:**
80
- - `TaskUpdate` to `in_progress` (set `activeForm` to the imperative-progressive form, e.g. "Running scout").
81
- - Log `entered <phase>` to `.claude/state/harness/<slug>.log`.
82
- - Invoke the matching phase skill via the **`Skill` tool — one invocation per loop iteration**.
80
+ 5. **Otherwise INVOKE the phase skill(s):**
81
+ - **Single-task path** (no parallel cluster detected at step 2):
82
+ - `TaskUpdate` to `in_progress` (set `activeForm` to the imperative-progressive form, e.g. "Running scout").
83
+ - Log `entered <phase>` to `.claude/state/harness/<slug>.log`.
84
+ - Invoke the matching phase skill via the **`Skill` tool — one invocation per loop iteration**.
85
+ - **Parallel-cluster path** (cluster of ≥2 tasks all with `can_parallel: true` + identical blockedBy detected at step 2):
86
+ - `TaskUpdate` every cluster task to `in_progress`.
87
+ - Log `entered cluster: <ids>` to the harness log.
88
+ - Dispatch the cluster via the `Task` tool, one `Task` invocation per cluster member — typically `swarm-worker` with a recipe per node, but the `skill:` field on each Node decides the worker target. All `Task` invocations go in a SINGLE assistant message so the runtime dispatches them concurrently.
89
+ - Wait for all cluster members to return.
90
+ - On all-success: mark each cluster task `completed`; refresh the marker + state once (NOT per-cluster-member); continue loop.
91
+ - On any cluster member's failure: EXIT LOOP with YIELD; `reason: "cluster <ids>: <failed-id> failed: <summary>"`. Leave succeeded members `completed` and the failed member `in_progress` for inspection.
83
92
  - On phase-skill success:
84
93
  - `TaskUpdate` to `completed`.
85
94
  - Append the phase name to `workflow.json → completed`; update `updated_at`.
@@ -10,7 +10,7 @@ You are drafting an **intake document** — the earliest structured artifact in
10
10
 
11
11
  ## Prerequisite
12
12
 
13
- `.claude/state/workflow.json` exists (written by `/triage`) with `entry_phase` either `intake` or a later phase that lists `intake` in `exceptions`. If neither is true, stop and instruct the user to run `/triage` first.
13
+ `.claude/state/workflow.json` exists (written by `/triage`) with EITHER `track_id == "intake-full"` (post-§18 canonical) OR `entry_phase == "intake"` (legacy pre-§18 — accepted for in-flight workflows the harness preflight migrator hasn't run on yet) — OR a later phase that lists `intake` in `exceptions`. If neither is true, stop and instruct the user to run `/triage` first.
14
14
 
15
15
  ## Inputs
16
16
 
@@ -8,6 +8,8 @@ description: Decompose an approved spec into a dependency-ordered swarm plan —
8
8
 
9
9
  Invoked after `/approve-spec` and before `/tdd` on any spec that has ≥3 independent components worth parallelizing (per `project.json → swarm.min_tasks_worth_swarming`). For smaller specs, skip swarm and go straight to `/tdd` solo.
10
10
 
11
+ **Post-§18 amendment.** Beyond the canonical `.claude/state/swarm/<slug>.json` plan, swarm-plan ALSO emits a runtime sub-track overlay at `.claude/state/swarm/<slug>.jsonl` (JSONL) when the chosen workflow track contains a selector node whose `swarm-implementation` alternate is chosen. The overlay's single line is a transient Track record with `selectable: false`, `track_id: "swarm-runtime-<slug>"`, and `nodes[]` enumerating the swarm-worker dispatches as `can_parallel: true` peers. The harness reloads runtime overlays from `.claude/state/swarm/*.jsonl` at the same time it reads `.claude/workflows.jsonl`, so the runtime Track set is the union. The overlay is deleted by `/archive` along with the rest of the workflow's swarm state.
12
+
11
13
  ## Prereqs
12
14
 
13
15
  1. `.claude/state/spec_approvals/<slug>.approval` exists (spec is human-approved).
@@ -13,7 +13,7 @@ Main-context decisions live here. Worker execution happens in harness ticks.
13
13
 
14
14
  # Prereq
15
15
 
16
- Either an approved spec exists (`.claude/state/spec_approvals/<slug>.approval`) OR `entry_phase` in `workflow.json` is `tdd` (quickfix/bugfix direct-to-TDD).
16
+ Either an approved spec exists (`.claude/state/spec_approvals/<slug>.approval`) OR `track_id` in `workflow.json` is `tdd-quickfix` (post-§18; legacy `entry_phase == "tdd"` accepted on pre-§18 workflow.json files the harness preflight migrator hasn't run on yet — quickfix/bugfix direct-to-TDD route).
17
17
 
18
18
  If neither, stop and direct the user to `/triage` first.
19
19
 
@@ -21,7 +21,7 @@ If neither, stop and direct the user to `/triage` first.
21
21
 
22
22
  ## 1. Verify prereq
23
23
 
24
- Read `.claude/state/workflow.json`. Confirm `tdd` is the current phase to run (entry_phase is `tdd` for quickfix, OR `spec` is in `completed` AND the spec approval token exists for spec-track).
24
+ Read `.claude/state/workflow.json`. Confirm `tdd` is the current phase to run: `track_id == "tdd-quickfix"` for quickfix (post-§18; legacy `entry_phase == "tdd"` also accepted), OR `spec` is in `completed` AND the spec approval token exists for spec-entry / intake-full tracks.
25
25
 
26
26
  ## 2. Decide the scenario recipe (in main context)
27
27
 
@@ -19,24 +19,47 @@ Triage the user's request and set up `.claude/state/workflow.json` so downstream
19
19
  1. Restate the request back to the user in 1-2 sentences, and name the entry phase you've chosen and why.
20
20
  2. **Git-repo detection (mandatory).** Run `git rev-parse --is-inside-work-tree 2>/dev/null` at the project root. If the exit status is non-zero, the project is not a git repository: gate C / `commit` are inapplicable AND the swarm path is unavailable because worktree isolation (the swarm contract's physical safety mechanism) requires git (CLAUDE.md Article IV "Phase 6c and Phase 11 are git-conditional", Article VII). Append `"swarm-plan"`, `"approve-swarm"`, `"swarm-dispatch"`, `"grant-commit"`, `"changelog"`, and `"commit"` to the exceptions array you'll write in step 4. `"changelog"` is auto-excepted alongside `"commit"` because Phase 11.5 is a pre-commit curator with no purpose outside a commit-bearing workflow. Tell the user: "Non-git project detected — `swarm-plan`, `approve-swarm`, `swarm-dispatch`, `grant-commit`, `changelog`, and `commit` auto-excepted. Phase 6 routes to solo `/tdd`. Workflow ends after `/archive`. Persistence outside git is your responsibility."
21
21
  3. If the user has not confirmed yet, ask: "Entry phase = <X>. Exceptions = <Y>. Proceed? (or tell me a different entry)"
22
- 4. On confirmation, write `.claude/state/workflow.json`:
22
+ 4. 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):
23
23
  ```json
24
24
  {
25
25
  "request": "<the request>",
26
26
  "slug": "<workflow slug>",
27
- "entry_phase": "<intake|spec|tdd|chore>",
27
+ "track_id": "<intake-full|spec-entry|tdd-quickfix|chore>",
28
28
  "exceptions": ["<phase>", ...],
29
29
  "completed": [],
30
+ "skipped_alternates": [],
30
31
  "source_backlog_keys": ["<backlog stable key>", ...],
31
32
  "created_at": <epoch>,
32
33
  "updated_at": <epoch>
33
34
  }
34
35
  ```
35
36
 
37
+ 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`, 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 `src/cli/workflow-migrator.js`.
38
+
36
39
  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.py --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).
37
- 5. **Seed the workflow tasklist.** Use the `TaskCreate` tool to register one task per non-excepted phase plus each applicable consent gate. The tasks are the running checklist that `/harness` (or any direct phase invocation) reads to decide the next action; consent-gate tasks block the workflow until the user runs the corresponding command. **When `grant-commit` and `commit` are in exceptions (non-git project), do NOT seed those two tasks** — the workflow ends after `/archive`. Use these canonical templates:
40
+ 5. **Seed the workflow tasklist** workflows.jsonl-driven (post-§18; per CLAUDE.md Article IV amendment + seed.md §18).
41
+
42
+ **Source of truth.** `.claude/workflows.jsonl` declares every Track this project can execute, one Track per JSONL line. The four canonical tracks (intake-full, spec-entry, tdd-quickfix, chore) 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.
43
+
44
+ **Procedure:**
45
+
46
+ 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.
47
+
48
+ 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.
49
+
50
+ 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.
51
+
52
+ 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 `src/cli/track-tasklist-materializer.js`). 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.
53
+
54
+ 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.
55
+
56
+ **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.
57
+
58
+ **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`, `changelog`, `commit` nodes — the materializer's runtime context (passed by triage) carries an `excluded_node_ids` set; the helper skips those nodes during TaskCreate emission.
59
+
60
+ **Reference: canonical track shapes (mirrored in workflows.jsonl).** The four selectable tracks shipped in the pristine template are byte-equivalent to these pre-§18 ordering descriptions:
38
61
 
39
- **For `chore` track** (single phase + memory-flush + changelog + commit gate):
62
+ **For `chore` track** (single phase + memory-flush + commit gate):
40
63
  - `Run /chore for <slug>` — activeForm: "Running chore", metadata: `{"phase": "chore"}`
41
64
  - `Run /memory-flush for <slug>` — activeForm: "Running memory-flush", metadata: `{"phase": "memory-flush"}`, addBlockedBy previous
42
65
  - `Wait for /grant-commit` — metadata: `{"phase": "grant-commit", "needs_user": true}`, addBlockedBy previous
@@ -48,7 +71,7 @@ Triage the user's request and set up `.claude/state/workflow.json` so downstream
48
71
 
49
72
  **For `spec`-entry track** (skip upstream): `Run /spec`, `Wait for /approve-spec <path>` (`needs_user`), `Run /tdd`, `Run /simplify`, `Run /security` (unless excepted), `Run /integrate`, `Run /document`, `Run /archive`, `Run /memory-flush`, `Wait for /grant-commit` (`needs_user`), `Run /changelog`, `Run /commit` — each with `addBlockedBy` set to the previous task's id.
50
73
 
51
- **For `intake`-entry full track**: `Run /intake`, `Run /brd` (only if stakeholder-heavy), `Run /scout`, `Run /research`, `Run /spec`, `Wait for /approve-spec <path>` (`needs_user`), `Run /tdd` OR (`Run /swarm-plan`, `Wait for /approve-swarm <slug>` (`needs_user`), `Run /swarm-dispatch`), `Run /simplify`, `Run /security` (unless excepted), `Run /integrate`, `Run /document`, `Run /archive`, `Run /memory-flush`, `Wait for /grant-commit` (`needs_user`), `Run /changelog`, `Run /commit`. **On non-git projects the swarm branch SHALL NOT be seeded** — only `Run /tdd` goes in the list, regardless of expected component count. Swarm-vs-solo is a Phase-6 main-context decision (per CLAUDE.md Article V) only on git projects; non-git workflows resolve to solo at triage time because `swarm-plan`, `approve-swarm`, and `swarm-dispatch` are already in `exceptions`. On non-git projects `changelog` is also auto-excepted alongside `commit` (Phase 11.5 only has purpose with a downstream commit).
74
+ **For `intake`-entry full track**: `Run /intake`, `Run /brd` (only if stakeholder-heavy), `Run /scout`, `Run /research`, `Run /spec`, `Wait for /approve-spec <path>` (`needs_user`), `Run /tdd` OR (`Run /swarm-plan`, `Wait for /approve-swarm <slug>` (`needs_user`), `Run /swarm-dispatch`), `Run /simplify`, `Run /security` (unless excepted), `Run /integrate`, `Run /document`, `Run /archive`, `Run /memory-flush`, `Wait for /grant-commit` (`needs_user`), `Run /changelog`, `Run /commit`. **On non-git projects the swarm branch SHALL NOT be seeded** — only `Run /tdd` goes in the list. Swarm-vs-solo is a Phase-6 main-context decision (per CLAUDE.md Article V) only on git projects; non-git workflows resolve to solo at triage time because `swarm-plan`, `approve-swarm`, and `swarm-dispatch` are already in `exceptions`. On non-git projects `changelog` is also auto-excepted alongside `commit`.
52
75
 
53
76
  For every task: `subject` is imperative ("Run /scout for <slug>" / "Wait for /approve-spec <path>"); `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.
54
77
 
@@ -57,5 +80,5 @@ Triage the user's request and set up `.claude/state/workflow.json` so downstream
57
80
  # Constraints
58
81
 
59
82
  - NEVER skip triage by guessing from filename or diff alone. The user's natural-language framing is the primary signal.
60
- - The Track Guard reads `entry_phase` and `exceptions`. If the user wants to skip an optional phase (e.g. `security`), add it to `exceptions` — do not silently re-order `workflow.phases` in project.json.
83
+ - The Track Guard reads `track_id` (post-§18) OR legacy `entry_phase`, plus `exceptions`. If the user wants to skip an optional phase (e.g. `security`), add it to `exceptions` — do not silently re-order `workflow.phases` in project.json.
61
84
  - If a workflow.json already exists for an open request, ask whether to replace it (starts a fresh track) or add to `completed` (continuing the same track).
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ // Foundation helper for the triage skill (post-§18). Two modes:
3
+ //
4
+ // node .claude/skills/triage/seed-tasklist.mjs --validate-only
5
+ // Loads .claude/workflows.jsonl, validates against the §18 schema +
6
+ // invariants I1..I11, exits 0 on success or non-zero on failure (with
7
+ // a named error printed to stderr citing the offending track / node).
8
+ //
9
+ // node .claude/skills/triage/seed-tasklist.mjs <track_id> <slug> [--exclude-nodes id1,id2,...]
10
+ // Loads .claude/workflows.jsonl, validates, finds the chosen Track,
11
+ // materializes its DAG into a canonical TaskList shape, and prints the
12
+ // resulting JSON array to stdout. The optional --exclude-nodes flag
13
+ // skips the named nodes during emission (used to drop git-conditional
14
+ // consent gates on non-git projects).
15
+ //
16
+ // The triage skill body invokes this helper as a subprocess; for each entry
17
+ // in the printed TaskList it calls TaskCreate, then TaskUpdate to wire
18
+ // blockedBy by translating ordinal references to session-assigned task_ids.
19
+
20
+ import { resolve, dirname } from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+ import { validateWorkflowsJsonl } from '../../../src/cli/workflows-validator.js';
23
+ import { materializeTaskList } from '../../../src/cli/track-tasklist-materializer.js';
24
+
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const REPO_ROOT = resolve(dirname(__filename), '../../..');
27
+ const WORKFLOWS_PATH = resolve(REPO_ROOT, '.claude/workflows.jsonl');
28
+
29
+ async function main(argv) {
30
+ const args = argv.slice(2);
31
+ const validateOnly = args.includes('--validate-only');
32
+ if (validateOnly) {
33
+ return runValidate();
34
+ }
35
+ const positional = args.filter((a) => !a.startsWith('--'));
36
+ const excludeFlag = args.find((a) => a.startsWith('--exclude-nodes='));
37
+ const excludedNodeIds = excludeFlag
38
+ ? new Set(excludeFlag.slice('--exclude-nodes='.length).split(',').filter(Boolean))
39
+ : new Set();
40
+ if (positional.length < 2) {
41
+ printUsageAndExit(2);
42
+ }
43
+ return runMaterialize(positional[0], positional[1], excludedNodeIds);
44
+ }
45
+
46
+ async function runValidate() {
47
+ const result = await validateWorkflowsJsonl(WORKFLOWS_PATH);
48
+ if (!result.ok) {
49
+ printValidationErrors(result.errors);
50
+ process.exit(1);
51
+ }
52
+ process.stderr.write(`validated ${result.tracks.length} tracks in ${WORKFLOWS_PATH}\n`);
53
+ process.exit(0);
54
+ }
55
+
56
+ async function runMaterialize(trackId, slug, excludedNodeIds) {
57
+ const result = await validateWorkflowsJsonl(WORKFLOWS_PATH);
58
+ if (!result.ok) {
59
+ printValidationErrors(result.errors);
60
+ process.exit(1);
61
+ }
62
+ const track = result.tracks.find((t) => t.track_id === trackId);
63
+ if (!track) {
64
+ process.stderr.write(`track_id '${trackId}' not found in ${WORKFLOWS_PATH}\n`);
65
+ process.exit(1);
66
+ }
67
+ if (track.selectable !== true) {
68
+ process.stderr.write(`track '${trackId}' is selectable=false (sub-track); only selectable tracks may be materialized at workflow seed time.\n`);
69
+ process.exit(1);
70
+ }
71
+ const tasks = materializeTaskList(track, { slug });
72
+ const filtered = excludedNodeIds.size > 0
73
+ ? tasks.filter((t) => !excludedTask(t, excludedNodeIds))
74
+ : tasks;
75
+ process.stdout.write(JSON.stringify(filtered, null, 2) + '\n');
76
+ process.exit(0);
77
+ }
78
+
79
+ function excludedTask(task, excludedNodeIds) {
80
+ if (excludedNodeIds.has(task.metadata?.phase)) return true;
81
+ return false;
82
+ }
83
+
84
+ function printValidationErrors(errors) {
85
+ process.stderr.write(`validation failed (${errors.length} error(s)):\n`);
86
+ for (const err of errors) {
87
+ const parts = [err.kind];
88
+ if (err.line) parts.push(`line=${err.line}`);
89
+ if (err.track_id) parts.push(`track_id=${err.track_id}`);
90
+ if (err.node_id) parts.push(`node_id=${err.node_id}`);
91
+ process.stderr.write(` - ${parts.join(' ')}: ${err.message}\n`);
92
+ }
93
+ }
94
+
95
+ function printUsageAndExit(code) {
96
+ process.stderr.write(
97
+ 'usage:\n' +
98
+ ' node .claude/skills/triage/seed-tasklist.mjs --validate-only\n' +
99
+ ' node .claude/skills/triage/seed-tasklist.mjs <track_id> <slug> [--exclude-nodes id1,id2,...]\n'
100
+ );
101
+ process.exit(code);
102
+ }
103
+
104
+ main(process.argv).catch((err) => {
105
+ process.stderr.write(`fatal: ${err.message}\n`);
106
+ process.exit(2);
107
+ });
@@ -0,0 +1,6 @@
1
+ {"$schema":"./schemas/workflow-track.v1.json","track_id":"intake-full","name":"Intake-entry full pipeline","description":"Canonical 11-phase pipeline for new features that need full design upfront (intake → scout → research → spec → approval → tdd/swarm → simplify → security → integrate → document → archive → memory-flush → grant-commit → changelog → commit).","selectable":true,"selector_hints":["build a new feature with full design upfront","add new functionality requiring a written spec","constitutional change or architectural refactor"],"preconditions":[],"invariants":["commits","requires_spec"],"nodes":[{"id":"intake","type":"task","skill":"intake","depends_on":[],"blocks":["scout"],"can_parallel":false,"needs_user":false,"activeForm":"Running intake","metadata":{"phase":"intake"}},{"id":"scout","type":"task","skill":"scout","depends_on":["intake"],"blocks":["research"],"can_parallel":false,"needs_user":false,"activeForm":"Running scout","metadata":{"phase":"scout"}},{"id":"research","type":"task","skill":"research","depends_on":["scout"],"blocks":["spec"],"can_parallel":false,"needs_user":false,"activeForm":"Running research","metadata":{"phase":"research"}},{"id":"spec","type":"task","skill":"spec","depends_on":["research"],"blocks":["approve-spec"],"can_parallel":false,"needs_user":false,"activeForm":"Running spec","metadata":{"phase":"spec"}},{"id":"approve-spec","type":"task","skill":"approve-spec","depends_on":["spec"],"blocks":["implementation"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting spec approval","metadata":{"phase":"approve-spec","needs_user":true}},{"id":"implementation","type":"selector","alternates":[{"sub_track":"swarm-implementation","preconditions":[{"name":"requires_git"},{"name":"requires_min_components","argument":"3"}],"description":"Swarm path for git projects with 3+ independent components"},{"sub_track":"tdd-worker-chain","preconditions":[],"description":"Solo TDD fallback (default)"}],"depends_on":["approve-spec"],"blocks":["simplify"],"can_parallel":false,"needs_user":false},{"id":"simplify","type":"task","skill":"simplify","depends_on":["implementation"],"blocks":["security"],"can_parallel":false,"needs_user":false,"activeForm":"Running simplify","metadata":{"phase":"simplify"}},{"id":"security","type":"task","skill":"security","depends_on":["simplify"],"blocks":["integrate"],"can_parallel":false,"needs_user":false,"activeForm":"Running security","metadata":{"phase":"security"}},{"id":"integrate","type":"task","skill":"integrate","depends_on":["security"],"blocks":["document"],"can_parallel":false,"needs_user":false,"activeForm":"Running integrate","metadata":{"phase":"integrate"}},{"id":"document","type":"task","skill":"document","depends_on":["integrate"],"blocks":["archive"],"can_parallel":false,"needs_user":false,"activeForm":"Running document","metadata":{"phase":"document"}},{"id":"archive","type":"task","skill":"archive","depends_on":["document"],"blocks":["memory-flush"],"can_parallel":false,"needs_user":false,"activeForm":"Running archive","metadata":{"phase":"archive"}},{"id":"memory-flush","type":"task","skill":"memory-flush","depends_on":["archive"],"blocks":["grant-commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running memory-flush","metadata":{"phase":"memory-flush"}},{"id":"grant-commit","type":"task","skill":"grant-commit","depends_on":["memory-flush"],"blocks":["changelog"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting commit consent","metadata":{"phase":"grant-commit","needs_user":true}},{"id":"changelog","type":"task","skill":"changelog","depends_on":["grant-commit"],"blocks":["commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running changelog","metadata":{"phase":"changelog"}},{"id":"commit","type":"task","skill":"commit","depends_on":["changelog"],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running commit","metadata":{"phase":"commit"}}]}
2
+ {"$schema":"./schemas/workflow-track.v1.json","track_id":"spec-entry","name":"Spec-entry bugfix pipeline","description":"Bugfix workflow that starts at /spec; skips intake/scout/research. For known-behavior bugs needing a contract-level fix.","selectable":true,"selector_hints":["bug needs a spec","contract-level bugfix","behaviour change requires written design"],"preconditions":[],"invariants":["commits","requires_spec"],"nodes":[{"id":"spec","type":"task","skill":"spec","depends_on":[],"blocks":["approve-spec"],"can_parallel":false,"needs_user":false,"activeForm":"Running spec","metadata":{"phase":"spec"}},{"id":"approve-spec","type":"task","skill":"approve-spec","depends_on":["spec"],"blocks":["tdd"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting spec approval","metadata":{"phase":"approve-spec","needs_user":true}},{"id":"tdd","type":"task","skill":"tdd","depends_on":["approve-spec"],"blocks":["simplify"],"can_parallel":false,"needs_user":false,"activeForm":"Running TDD","metadata":{"phase":"tdd"}},{"id":"simplify","type":"task","skill":"simplify","depends_on":["tdd"],"blocks":["security"],"can_parallel":false,"needs_user":false,"activeForm":"Running simplify","metadata":{"phase":"simplify"}},{"id":"security","type":"task","skill":"security","depends_on":["simplify"],"blocks":["integrate"],"can_parallel":false,"needs_user":false,"activeForm":"Running security","metadata":{"phase":"security"}},{"id":"integrate","type":"task","skill":"integrate","depends_on":["security"],"blocks":["document"],"can_parallel":false,"needs_user":false,"activeForm":"Running integrate","metadata":{"phase":"integrate"}},{"id":"document","type":"task","skill":"document","depends_on":["integrate"],"blocks":["archive"],"can_parallel":false,"needs_user":false,"activeForm":"Running document","metadata":{"phase":"document"}},{"id":"archive","type":"task","skill":"archive","depends_on":["document"],"blocks":["memory-flush"],"can_parallel":false,"needs_user":false,"activeForm":"Running archive","metadata":{"phase":"archive"}},{"id":"memory-flush","type":"task","skill":"memory-flush","depends_on":["archive"],"blocks":["grant-commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running memory-flush","metadata":{"phase":"memory-flush"}},{"id":"grant-commit","type":"task","skill":"grant-commit","depends_on":["memory-flush"],"blocks":["changelog"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting commit consent","metadata":{"phase":"grant-commit","needs_user":true}},{"id":"changelog","type":"task","skill":"changelog","depends_on":["grant-commit"],"blocks":["commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running changelog","metadata":{"phase":"changelog"}},{"id":"commit","type":"task","skill":"commit","depends_on":["changelog"],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running commit","metadata":{"phase":"commit"}}]}
3
+ {"$schema":"./schemas/workflow-track.v1.json","track_id":"tdd-quickfix","name":"TDD-entry quickfix pipeline","description":"Quickfix workflow that starts directly at /tdd. For localized misbehaviour with a known failing case; no spec needed.","selectable":true,"selector_hints":["quickfix with known failing test","localized bug; no spec needed","small bundled patch with a clear failing case"],"preconditions":[],"invariants":["commits"],"nodes":[{"id":"tdd","type":"task","skill":"tdd","depends_on":[],"blocks":["simplify"],"can_parallel":false,"needs_user":false,"activeForm":"Running TDD","metadata":{"phase":"tdd"}},{"id":"simplify","type":"task","skill":"simplify","depends_on":["tdd"],"blocks":["security"],"can_parallel":false,"needs_user":false,"activeForm":"Running simplify","metadata":{"phase":"simplify"}},{"id":"security","type":"task","skill":"security","depends_on":["simplify"],"blocks":["integrate"],"can_parallel":false,"needs_user":false,"activeForm":"Running security","metadata":{"phase":"security"}},{"id":"integrate","type":"task","skill":"integrate","depends_on":["security"],"blocks":["document"],"can_parallel":false,"needs_user":false,"activeForm":"Running integrate","metadata":{"phase":"integrate"}},{"id":"document","type":"task","skill":"document","depends_on":["integrate"],"blocks":["archive"],"can_parallel":false,"needs_user":false,"activeForm":"Running document","metadata":{"phase":"document"}},{"id":"archive","type":"task","skill":"archive","depends_on":["document"],"blocks":["memory-flush"],"can_parallel":false,"needs_user":false,"activeForm":"Running archive","metadata":{"phase":"archive"}},{"id":"memory-flush","type":"task","skill":"memory-flush","depends_on":["archive"],"blocks":["grant-commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running memory-flush","metadata":{"phase":"memory-flush"}},{"id":"grant-commit","type":"task","skill":"grant-commit","depends_on":["memory-flush"],"blocks":["changelog"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting commit consent","metadata":{"phase":"grant-commit","needs_user":true}},{"id":"changelog","type":"task","skill":"changelog","depends_on":["grant-commit"],"blocks":["commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running changelog","metadata":{"phase":"changelog"}},{"id":"commit","type":"task","skill":"commit","depends_on":["changelog"],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running commit","metadata":{"phase":"commit"}}]}
4
+ {"$schema":"./schemas/workflow-track.v1.json","track_id":"chore","name":"Chore track","description":"Stripped-down pipeline for changes that need no failing-test-driven code change: documentation edits, configuration tweaks, formatting, dependency bumps with no project code change.","selectable":true,"selector_hints":["documentation edit","governance count refresh","configuration tweak","formatting or typo fix","skill consolidation move"],"preconditions":[],"invariants":["commits","chore"],"nodes":[{"id":"chore","type":"task","skill":"chore","depends_on":[],"blocks":["memory-flush"],"can_parallel":false,"needs_user":false,"activeForm":"Running chore","metadata":{"phase":"chore"}},{"id":"memory-flush","type":"task","skill":"memory-flush","depends_on":["chore"],"blocks":["grant-commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running memory-flush","metadata":{"phase":"memory-flush"}},{"id":"grant-commit","type":"task","skill":"grant-commit","depends_on":["memory-flush"],"blocks":["changelog"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting commit consent","metadata":{"phase":"grant-commit","needs_user":true}},{"id":"changelog","type":"task","skill":"changelog","depends_on":["grant-commit"],"blocks":["commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running changelog","metadata":{"phase":"changelog"}},{"id":"commit","type":"task","skill":"commit","depends_on":["changelog"],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running commit","metadata":{"phase":"commit"}}]}
5
+ {"$schema":"./schemas/workflow-track.v1.json","track_id":"swarm-implementation","name":"Swarm implementation sub-track","description":"Parallel implementation via swarm-plan + approve-swarm + swarm-dispatch. Selected by intake-full's selector node when requires_git and requires_min_components:3 preconditions pass.","selectable":false,"selector_hints":[],"preconditions":[{"name":"requires_git"}],"invariants":["requires_swarm","git_only"],"nodes":[{"id":"swarm-plan","type":"task","skill":"swarm-plan","depends_on":[],"blocks":["approve-swarm"],"can_parallel":false,"needs_user":false,"activeForm":"Running swarm-plan","metadata":{"phase":"swarm-plan"}},{"id":"approve-swarm","type":"task","skill":"approve-swarm","depends_on":["swarm-plan"],"blocks":["swarm-dispatch"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting swarm approval","metadata":{"phase":"approve-swarm","needs_user":true}},{"id":"swarm-dispatch","type":"task","skill":"swarm-dispatch","depends_on":["approve-swarm"],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running swarm-dispatch","metadata":{"phase":"swarm-dispatch"}}]}
6
+ {"$schema":"./schemas/workflow-track.v1.json","track_id":"tdd-worker-chain","name":"TDD worker-chain sub-track","description":"Solo TDD path. Fallback alternate for intake-full when swarm preconditions fail or the user picks solo explicitly.","selectable":false,"selector_hints":[],"preconditions":[],"invariants":[],"nodes":[{"id":"tdd","type":"task","skill":"tdd","depends_on":[],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running TDD","metadata":{"phase":"tdd"}}]}
@@ -91,25 +91,19 @@ The 11-phase workflow is the only sanctioned path from request to commit. Phase
91
91
 
92
92
  **Swarm vs solo at Phase 6.** When the approved spec has fewer than `project.json → swarm.min_tasks_worth_swarming` (default 3) independent components **OR** the project is not a git repository, run `/tdd` solo. Otherwise route through `/swarm-plan` → `/approve-swarm` → `/swarm-dispatch`. In non-git projects the swarm phases are excepted at triage time (see the "Phase 6c and Phase 11 are git-conditional" bullet above), so this decision always resolves to solo — the rule's first clause never fires on a non-git tree, and a user "use swarm" override SHALL be refused with the reason `swarm requires git`.
93
93
 
94
+ **Post-§18 amendment (2026-05-21).** Workflow track definitions live in `.claude/workflows.jsonl` per `docs/init/seed.md §18`. The phase-ordering rules and entry-point classifications above remain binding; every Track declared in `workflows.jsonl` SHALL satisfy them plus the additional invariants in seed.md §18.3 (I1..I11). `/triage` reads `workflows.jsonl`, validates each Track against §18, classifies the user's request via LLM reasoning over `name + description + selector_hints`, confirms via `AskUserQuestion`, and materializes the chosen Track's DAG into the TaskList (via `src/cli/track-tasklist-materializer.js`). The 4 canonical tracks shipped in the pristine template are byte-equivalent to this Article's hardcoded templates per spec AC-016. The harness migrates pre-§18 `workflow.json` files (carrying `entry_phase` + no `track_id`) one-shot at preflight via `src/cli/workflow-migrator.js`. `/init-project doctor` (sub-command) detects schema / invariant / mirror drift and offers interactive fixes.
95
+
94
96
  ## Article V — Harness orchestration (MANDATORY SOP)
95
97
 
96
98
  `/harness` is invokable by both the user (via the slash command) and the model (via `Skill(harness)`). A single `Skill(harness)` invocation **loops internally through every non-gated phase boundary** until the loop hits one of four exit conditions: consent gate, phase-skill failure, integrate-failure-needs-spec-change, or workflow done. The user invokes `/harness` to start a fresh workflow or to resume after a yield. You SHALL suggest `/harness` when a concrete engineering ask crystallizes in conversation; the user decides when to invoke it.
97
99
 
98
- When `/harness` is invoked, you SHALL:
99
-
100
- 1. **Preflight (once per invocation).** Read `.claude/state/workflow.json` and `.claude/state/harness_state` (if present); read `.claude/state/spec_approvals/`, `swarm_approvals/`, and `commit_consent` to reconcile state.
101
- 2. **Arm the safety net.** Marker FIRST: `echo "<slug>" > .claude/state/.harness_active`. Then write `harness_state` with `{state: "continue", slug, reason: "loop armed; preflight passed"}`. This pair stays in place for the entire loop.
102
- 3. **Enter the loop body.** Each iteration: pick the lowest-id `pending` task whose `blockedBy` list is empty. If no task remains → **EXIT LOOP with DONE**. If the task carries `metadata.needs_user: true` → **EXIT LOOP with YIELD** (surface the gate; do NOT self-approve, simulate approval, or write approval tokens directly). Otherwise invoke the matching phase skill via the Skill tool, mark `completed` on success, append to `workflow.json → completed`, refresh marker+state, and **continue to the next iteration**.
103
- 4. **Exit the loop** on yield/failure/done. Write the matching `harness_state` (`yielded` or `done`) with marker-first ordering, then emit a single terminal message naming what just happened.
104
- 5. **Log every transition** to `.claude/state/harness/<slug>.log`.
105
-
106
- **Internal loop atomicity.** Inside the loop, every iteration is one Skill(`<phase>`) invocation plus one marker refresh plus one `harness_state` refresh — all happening within the same `Skill(harness)` call, **without emitting an intermediate terminal message**. Intermediate terminal messages would invite the model to stop and trigger the safety net unnecessarily. The marker op is FIRST (`echo "<slug>" > .claude/state/.harness_active` on `continue`-refresh, `rm -f .claude/state/.harness_active` on exit with `yielded`/`done`), then `harness_state` is written, then (only on loop exit) the terminal message is emitted.
107
-
108
- **The safety net.** The `harness_continuation` Stop hook (Article VIII) re-fires `Skill(harness)` only when the loop exited mid-flow — i.e., on-disk state is `{state: "continue"}` with the marker present, and `stop_hook_active` is absent on the Stop payload. In normal operation (loop runs to gate/failure/done), the hook sees `state != continue` or marker absent and stays silent. The hook is a defense-in-depth signal, not the primary driver: a healthy `Skill(harness)` invocation never depends on it. The hook is also bounded to one block per turn by Claude Code's `stop_hook_active` semantics, so it cannot itself drive multi-phase chaining.
109
-
110
- **Resume after yield (auto).** After yielding at a consent gate, the harness skill writes `state: yielded` and removes `.harness_active`. The user runs the consent slash command in their next prompt; `consent_gate_grant` writes the gate marker (outside Claude's tool boundary), the command body writes the consent token, and the `harness_continuation` Stop hook detects fresh consent (rung 4: `workflow.json` present + `state=yielded` + a consent-token mtime newer than `harness_state`). The hook emits `{decision:"block"}`, and `Skill(harness)` is re-invoked in the same turn. The next invocation re-enters preflight, finds the gate token on disk, marks the gate task `completed`, and re-enters the loop. The user does not type `/harness` to resume.
100
+ **Operational SOP lives in `.claude/skills/harness/SKILL.md`** — preflight, marker-first state writes, loop body iteration, safety-net interaction with `harness_continuation`, resume-after-yield mechanics, and task discipline. This Article declares the constitutional invariants the SOP must satisfy:
111
101
 
112
- **Task discipline (autonomous progression).** `/triage` seeds a `TaskCreate`-backed checklist covering every non-excepted phase plus consent-gate placeholders (the placeholders carry `metadata.needs_user: true`). Inside the loop you SHALL: (a) call `TaskList` first; if empty, re-seed from `workflow.json → completed + exceptions + entry_phase` using the canonical templates in `triage`'s SKILL.md; (b) `TaskUpdate` the next pending non-blocked task to `in_progress` before invoking its phase skill; (c) `TaskUpdate` to `completed` only after the phase skill returns success; (d) when the next pending task carries `needs_user: true`, EXIT LOOP with YIELD — never invoke a skill for that task. The TaskList is session-bound; `workflow.json → completed` is the durable truth, and re-seeding always reconciles to it.
102
+ - The loop SHALL exit on one of four conditions: consent gate (yield), phase-skill failure (yield), integrate-failure-needs-spec-change (yield), or workflow done.
103
+ - You SHALL NOT self-approve at any consent gate. You SHALL NOT simulate approval. You SHALL NOT write approval tokens directly.
104
+ - Every successful phase invocation SHALL `TaskUpdate` to `completed`, append the phase name to `workflow.json → completed`, and refresh marker + `harness_state` (marker FIRST) before continuing.
105
+ - `workflow.json → completed` is the durable truth across sessions; the TaskList is session-bound. When they disagree, trust `workflow.json` and re-seed.
106
+ - The `harness_continuation` Stop hook is a safety net, not the primary driver. A healthy `Skill(harness)` invocation runs to a clean exit on its own; the hook re-fires only when the loop was interrupted mid-flow with `state: "continue"` + marker present.
113
107
 
114
108
  **Integrate-failure decision tree.** When `/integrate` fails inside the loop, you SHALL classify:
115
109
 
@@ -247,7 +247,7 @@ Each vendored shared global ships with its own `LICENSE` + `NOTICE` alongside th
247
247
 
248
248
  - `chore` — for tasks with no failing-test-driven code change (documentation, governance counts, vendored-skill content updates, configuration, formatting, typo fixes, dependency bumps, skill consolidations). Skips `/scenario` and `/implement` — there is nothing to drive with a failing test. Runs the edits directly, then conditionally invokes `simplify` / `integrate` / `document` based on what the diff touches (each has explicit triggers in the chore skill body). `verify`, `archive`, and `/grant-commit` + `/commit` always run. Chore is a stripped-down pipeline, **not** a bypass — silent skips of triggered conditional phases are forbidden; the end-of-chore summary documents every skip rationale. Tasks that need a real failing test route to `/tdd` or higher instead.
249
249
 
250
- ### §4.4 Commands (4) — structurally user-only
250
+ ### §4.4 Commands (6) — structurally user-only
251
251
 
252
252
  Files at `.claude/commands/<name>.md`. Commands differ from skills in exactly one way: **Claude cannot invoke them via the Skill tool.** A command is a button only a human can press.
253
253
 
@@ -258,8 +258,9 @@ Files at `.claude/commands/<name>.md`. Commands differ from skills in exactly on
258
258
  | `grant-commit` | Opens a 5-minute consent window for `git commit` on a protected branch. Writes `.claude/state/commit_consent`. Enforced by `git_commit_guard`. |
259
259
  | `grant-push` | Opens a 5-minute consent window for `git push` on a protected branch. Writes `.claude/state/push_consent`. Enforced by `git_commit_guard`. Not a workflow-phase gate — a runtime consent for the branch-aware policy (§11). |
260
260
  | `init-project` | One-time bootstrap. Detects stack, proposes `.claude/project.json` (test cmd, lint cmd, TDD globs, destructive patterns, artifact required sections, swarm config). Flips `configured: true`. |
261
+ | `init-project-doctor` | Detects baseline drift — missing/invalid `.claude/workflows.jsonl`, schema/invariant violations, four-way Article IV / §18 mirror drift, and (advisory) shipped-tooling files placed outside `.claude/`. Interactive: presents each violation via `AskUserQuestion` and applies the named fix on confirmation. |
261
262
 
262
- **Adding a sixth command requires answering yes to both:** "does a human need to press this?" and "is 'user-only via frontmatter flag' too weak a guarantee?" Otherwise make it a skill.
263
+ **Adding a seventh command requires answering yes to both:** "does a human need to press this?" and "is 'user-only via frontmatter flag' too weak a guarantee?" Otherwise make it a skill.
263
264
 
264
265
  ### §4.5 MCP servers (3)
265
266
 
@@ -338,7 +339,7 @@ Phases are fixed ordering; `/triage` picks the entry and may mark phases as exce
338
339
 
339
340
  ## §6 — Consent model
340
341
 
341
- **Four consent gates + one bootstrap gate.** All are slash commands, not skills. Commands live in `.claude/commands/`; Claude cannot invoke them via the Skill tool. The guarantee is structural (file location), not flag-based. Three of the four gates are workflow-phase gates (A: `/approve-spec`, B: `/approve-swarm`, C: `/grant-commit`); the fourth (`/grant-push`) is a Bash-time consent for the branch-aware push policy in §11.
342
+ **Four consent gates + one bootstrap + one doctor.** All are slash commands, not skills. Commands live in `.claude/commands/`; Claude cannot invoke them via the Skill tool. The guarantee is structural (file location), not flag-based. Three of the four gates are workflow-phase gates (A: `/approve-spec`, B: `/approve-swarm`, C: `/grant-commit`); the fourth (`/grant-push`) is a Bash-time consent for the branch-aware push policy in §11. The bootstrap is `/init-project`; the doctor is `/init-project doctor` (drift detector + repairer for `.claude/workflows.jsonl` + the §18 / Article IV four-way mirror; see §18.7).
342
343
 
343
344
  | Gate | When it fires | Unlocks |
344
345
  |---|---|---|
@@ -598,3 +599,147 @@ The audit at `.claude/skills/audit-baseline/audit.sh` consumes `manifest.owners.
598
599
  The audit also verifies constitutional citation: CLAUDE.md SHALL contain the literal string "Article XI" and a reference to the manifest, and `docs/init/seed.md` SHALL contain "§17" and a manifest reference. Missing citations trigger FAIL with `CLAUDE.md missing Article XI citation` or `seed.md missing §17 citation`.
599
600
 
600
601
  This provenance system is intentionally minimal: the manifest tracks shipped-file hashes; the frontmatter declares per-skill ownership; the audit reconciles the two against on-disk reality. Cryptographic supply-chain attestation, signed lock files, and per-skill aggregate merkle hashes are non-goals; the per-file `manifest.files` map already covers every file in every skill directory. A future `npx @friedbotstudio/create-baseline upgrade` subcommand will consume `manifest.owners.skills` + `manifest.files` to safely re-overlay baseline-owned files while leaving user-added skills and locally-customized baseline skills untouched — that subcommand is out of scope here.
602
+
603
+ ---
604
+
605
+ ## §18 — Workflow definitions and Article IV invariants
606
+
607
+ ### 18.1 Source of truth
608
+
609
+ `.claude/workflows.jsonl` is the canonical source for every workflow this baseline can execute. The file holds one Track record per line (JSONL). It is project-owned and `NEVER_TOUCH` (declared in `src/cli/install.js:NEVER_TOUCH` and `scripts/build-manifest.mjs:NEVER_TOUCH_PATHS`); baseline upgrades preserve user customizations verbatim via `NEVER_TOUCH_PRESERVE`. The shipped baseline overlays the pristine 6-track set from `src/.claude/workflows.template.jsonl` onto fresh installs via `scripts/build-template.sh` Stage 2; existing installs are not touched. The JSON Schema document at `.claude/schemas/workflow-track.v1.json` is referenced by `Track.$schema` and is itself `NEVER_TOUCH`.
610
+
611
+ `workflows.jsonl` supersedes the hardcoded triage templates (intake-full / spec-entry / tdd-quickfix / chore). Triage reads `workflows.jsonl` at seed time, validates each Track, classifies the user's request, and materializes the chosen Track's DAG into the TaskList. The canonical four tracks shipped in the pristine template are byte-equivalent to the pre-§18 hardcoded templates per spec AC-016 (`tests/byte-equivalent-migration.test.mjs`).
612
+
613
+ ### 18.2 Track schema
614
+
615
+ A **Track** record has this shape (full definition in `.claude/schemas/workflow-track.v1.json`):
616
+
617
+ ```jsonc
618
+ {
619
+ "$schema": "./schemas/workflow-track.v1.json",
620
+ "track_id": "<unique-across-file>",
621
+ "name": "<short label>",
622
+ "description": "<paragraph; read by the LLM classifier>",
623
+ "selectable": true, // false = sub-track only (referenced via sub_track)
624
+ "selector_hints": ["<descriptive phrase>", ...],
625
+ "preconditions": [{"name": "<predicate>", "argument": "<opt>"}, ...],
626
+ "invariants": ["commits", "requires_spec", ...],
627
+ "nodes": [Node, ...]
628
+ }
629
+ ```
630
+
631
+ A **Node** is either a `task` (skill invocation or sub-track expansion) or a `selector` (picks one of multiple alternates at runtime):
632
+
633
+ ```jsonc
634
+ {
635
+ "id": "<unique-within-track>",
636
+ "type": "task" | "selector",
637
+ // type=task → exactly one of:
638
+ "skill": "<skill-or-command-name>",
639
+ "sub_track": "<another-track_id>",
640
+ // type=selector → required:
641
+ "alternates": [Alternate, ...],
642
+ // shared:
643
+ "input": "<opt; passed to the skill at invocation>",
644
+ "invocation_prompt": "<opt; declared-now/used-later — v2 Handlebars+LLM>",
645
+ "output": "<opt; informational artifact path>",
646
+ "output_formatter_prompt": "<opt; declared-now/used-later>",
647
+ "depends_on": ["<predecessor node id>", ...],
648
+ "blocks": ["<successor node id>", ...],
649
+ "can_parallel": false, // true: peers at same dep level dispatch concurrently
650
+ "needs_user": false, // true: consent gate; harness yields
651
+ "activeForm": "<TaskList spinner text>",
652
+ "metadata": {"phase": "<...>"}
653
+ }
654
+ ```
655
+
656
+ An **Alternate** (inside a selector node):
657
+
658
+ ```jsonc
659
+ {
660
+ "skill": "<skill-name>", // XOR with sub_track
661
+ "sub_track": "<track_id>", // XOR with skill
662
+ "preconditions": [Predicate, ...],
663
+ "description": "<rationale>"
664
+ }
665
+ ```
666
+
667
+ A **Predicate** (track-level and alternate-level):
668
+
669
+ ```jsonc
670
+ {
671
+ "name": "<v1-vocabulary>",
672
+ "argument": "<opt; e.g., '3' for min_components>"
673
+ }
674
+ ```
675
+
676
+ ### 18.3 Article IV invariants (I1..I11)
677
+
678
+ Every Track in `workflows.jsonl` SHALL satisfy these invariants. Validation runs at three points: install/upgrade time (audit-baseline), triage time (LLM-driven selector), and harness time (per-node before dispatch).
679
+
680
+ - **I1.** Unique `track_id` across the file.
681
+ - **I2.** Unique `node.id` within a track.
682
+ - **I3.** `type=task` nodes carry exactly one of `{skill, sub_track}`. `type=selector` nodes carry non-empty `alternates[]`.
683
+ - **I4.** Every `depends_on` and `blocks` reference resolves to a `node.id` in the same track.
684
+ - **I5.** The dependency DAG is acyclic.
685
+ - **I6.** Tracks declaring the `commits` invariant SHALL include a `needs_user: true` `grant-commit` node ordered before the node with `skill: "commit"`.
686
+ - **I7.** Every `sub_track` reference resolves to a Track with `selectable: false`.
687
+ - **I8.** Every `skill:` reference resolves to a known invokable — skill in `EXPECTED_SKILLS ∪ project.json additions.skills`, OR consent-gate command in `.claude/commands/` (e.g., `approve-spec`, `grant-commit`, `approve-swarm`).
688
+ - **I9.** `needs_user: true` nodes appear in dependency order before any node that depends on their consent.
689
+ - **I10.** A selector node's alternates SHALL share the same shape (all skill, or all sub_track) — they're interchangeable in the DAG.
690
+ - **I11.** Every `Predicate.name` resolves to a known v1 predicate (see §18.4).
691
+
692
+ ### 18.4 Predicate vocabulary (v1)
693
+
694
+ The closed set of declarative predicates that may appear in Track or Alternate `preconditions[]`:
695
+
696
+ | Predicate | Argument | Evaluates true when |
697
+ |---|---|---|
698
+ | `requires_git` | — | `git rev-parse --is-inside-work-tree` exits 0 at the project root. |
699
+ | `requires_user_override` | `<value>` | The user explicitly named this alternate in conversation (e.g., "use solo"). |
700
+ | `requires_min_components` | `<int>` | The approved spec has at least N C4 Components. |
701
+ | `requires_phase_completed` | `<phase>` | The named phase appears in `workflow.json → completed`. |
702
+ | `requires_skill_present` | `<skill_id>` | The named skill exists in `EXPECTED_SKILLS ∪ additions.skills`. |
703
+
704
+ Adding a new predicate is a constitutional change: update this section, update `src/cli/workflows-validator-predicates.js`, and update the corresponding seed.template.md mirror.
705
+
706
+ ### 18.5 `invocation_prompt` / `output_formatter_prompt` — declared, deferred
707
+
708
+ Both fields are part of the v1 Node schema and validated at parse time. They are **not actuated in v1** — the harness ignores them. They are declared now to lock the schema shape so future Track records can carry them without a schema bump. The v2 actuation plan: Handlebars-style templates with LLM interpolation, allowing per-track UX customization of the invocation phrasing and the post-skill output formatting. Until v2 ships, populating these fields is allowed but inert.
709
+
710
+ ### 18.6 Migration from pre-§18 workflow.json
711
+
712
+ An in-flight `.claude/state/workflow.json` written by a pre-§18 baseline (carries `entry_phase` field, no `track_id`) is one-shot-migrated by the harness preflight before the workflow loads. The canonical map:
713
+
714
+ | `entry_phase` (pre-§18) | `track_id` (post-§18) |
715
+ |---|---|
716
+ | `intake` | `intake-full` |
717
+ | `spec` | `spec-entry` |
718
+ | `tdd` | `tdd-quickfix` |
719
+ | `chore` | `chore` |
720
+
721
+ `completed[]` is remapped from phase names to node ids; the canonical tracks are designed so most phase names equal the corresponding node id (identity remap), with the exception of selector wrappers (e.g., `implementation` in intake-full wraps the swarm-vs-tdd selection). The migrator initializes `skipped_alternates: []` and refreshes `updated_at`. Idempotent: re-running on an already-migrated workflow.json is a no-op. Unmapped `entry_phase` halts with a named error; the user restarts via `/triage`.
722
+
723
+ Migrator implementation: `src/cli/workflow-migrator.js` exports `migrateWorkflowJsonInPlace(filePath)`.
724
+
725
+ ### 18.7 Lifecycle: install, upgrade, doctor
726
+
727
+ - **Fresh install.** `scripts/build-template.sh` overlays `src/.claude/workflows.template.jsonl` → `obj/template/.claude/workflows.jsonl` at Stage 2, and the pristine schemas/ directory bulk-rsyncs at Stage 1. The CLI install copies both into the consumer target. Result: every fresh install has `<target>/.claude/workflows.jsonl` with the canonical 4 selectable + 2 sub-track set.
728
+
729
+ - **Upgrade.** Both `.claude/workflows.jsonl` and `.claude/schemas/workflow-track.v1.json` are `NEVER_TOUCH`. The merge flow returns `NEVER_TOUCH_PRESERVE` for them on every upgrade; user customizations (added tracks, modified nodes, per-project additions like `cli-copy-review`) survive verbatim.
730
+
731
+ - **Doctor.** `/init-project doctor` (new sub-command) detects drift: missing `workflows.jsonl`, schema/invariant violations, four-way mirror drift between seed.md §18 / src/seed.template.md §18 / CLAUDE.md Article IV / src/CLAUDE.template.md Article IV, and (advisory) shipped-tooling files placed outside `.claude/` per the convention codified at §3.
732
+
733
+ ### 18.8 Cross-references
734
+
735
+ - `CLAUDE.md Article IV` — phase-ordering rules; binding on every commit-producing track.
736
+ - `CLAUDE.md Article VII` — git rules; relevant to the `requires_git` precondition.
737
+ - `seed.md §3` — directory structure convention (tooling lives under `.claude/`).
738
+ - `seed.md §17` — skill provenance (separate concern; workflows.jsonl is project-owned, not baseline-owned).
739
+ - `.claude/workflows.jsonl` — this project's live tracks.
740
+ - `.claude/schemas/workflow-track.v1.json` — JSON Schema referenced by `Track.$schema`.
741
+ - `src/cli/workflows-validator.js` — validator orchestration.
742
+ - `src/cli/workflows-validator-invariants.js` — invariant checks I1–I11.
743
+ - `src/cli/workflows-validator-predicates.js` — predicate vocabulary.
744
+ - `src/cli/workflow-migrator.js` — pre-§18 → post-§18 migrator.
745
+ - `src/cli/track-tasklist-materializer.js` — Track → TaskList shape.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@friedbotstudio/create-baseline",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Node CLI scaffolder that materializes the Claude Code baseline (hooks, skills, commands, MCP servers, governance docs) into a target project, with branded interactive install / upgrade / doctor flows. Run via `npx @friedbotstudio/create-baseline <target>`.",
5
5
  "type": "module",
6
6
  "bin": {