@friedbotstudio/create-baseline 0.5.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 (55) hide show
  1. package/README.md +14 -10
  2. package/bin/cli.js +46 -15
  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 +848 -230
  7. package/obj/template/.claude/schemas/workflow-track.v1.json +64 -0
  8. package/obj/template/.claude/skills/audit-baseline/audit.sh +6 -3
  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/skills/upgrade-project/SKILL.md +121 -0
  17. package/obj/template/.claude/workflows.jsonl +6 -0
  18. package/obj/template/CLAUDE.md +14 -19
  19. package/obj/template/docs/init/seed.md +152 -7
  20. package/package.json +1 -1
  21. package/src/.claude/workflows.template.jsonl +6 -0
  22. package/src/CLAUDE.template.md +14 -19
  23. package/src/cli/diff-render.js +54 -0
  24. package/src/cli/install.js +38 -3
  25. package/src/cli/manifest.js +7 -3
  26. package/src/cli/merge.js +107 -13
  27. package/src/cli/track-tasklist-materializer.js +223 -0
  28. package/src/cli/tui/upgrade.js +130 -27
  29. package/src/cli/upgrade-tiers.js +256 -0
  30. package/src/cli/workflow-migrator.js +40 -0
  31. package/src/cli/workflows-validator-invariants.js +417 -0
  32. package/src/cli/workflows-validator-predicates.js +19 -0
  33. package/src/cli/workflows-validator.js +156 -0
  34. package/src/seed.template.md +152 -7
  35. package/obj/template/.claude/skills/google-analytics/SKILL.md +0 -129
  36. package/obj/template/.claude/skills/google-analytics/references/audiences.md +0 -389
  37. package/obj/template/.claude/skills/google-analytics/references/bigquery.md +0 -470
  38. package/obj/template/.claude/skills/google-analytics/references/custom-dimensions.md +0 -355
  39. package/obj/template/.claude/skills/google-analytics/references/custom-events.md +0 -383
  40. package/obj/template/.claude/skills/google-analytics/references/data-management.md +0 -416
  41. package/obj/template/.claude/skills/google-analytics/references/debugview.md +0 -364
  42. package/obj/template/.claude/skills/google-analytics/references/events-fundamentals.md +0 -398
  43. package/obj/template/.claude/skills/google-analytics/references/gtag.md +0 -502
  44. package/obj/template/.claude/skills/google-analytics/references/gtm-integration.md +0 -483
  45. package/obj/template/.claude/skills/google-analytics/references/measurement-protocol.md +0 -519
  46. package/obj/template/.claude/skills/google-analytics/references/privacy.md +0 -441
  47. package/obj/template/.claude/skills/google-analytics/references/recommended-events.md +0 -464
  48. package/obj/template/.claude/skills/google-analytics/references/reporting.md +0 -397
  49. package/obj/template/.claude/skills/google-analytics/references/setup.md +0 -344
  50. package/obj/template/.claude/skills/google-analytics/references/user-tracking.md +0 -417
  51. package/obj/template/.claude/skills/optimize-seo/SKILL.md +0 -313
  52. package/obj/template/.claude/skills/optimize-seo/scripts/pagespeed.mjs +0 -197
  53. package/obj/template/.claude/skills/pagespeed-insights/LICENSE.md +0 -37
  54. package/obj/template/.claude/skills/pagespeed-insights/SKILL.md +0 -446
  55. package/obj/template/.claude/skills/pagespeed-insights/reference.md +0 -50
package/README.md CHANGED
@@ -41,7 +41,7 @@ A discipline layer for Claude Code. Hooks at every tool boundary, a workflow tha
41
41
 
42
42
  ## What this is
43
43
 
44
- The Claude Code Baseline is a repository overlay shipped via `npx @friedbotstudio/create-baseline ./target`. It installs **22 hooks** at Claude's tool boundaries, **37 skills** organised into nine categories, **1 subagent** for parallel work in isolated worktrees, an **11-phase workflow** from intake to commit, and **3 user-typed consent gates** that Claude cannot forge.
44
+ The Claude Code Baseline is a repository overlay shipped via `npx @friedbotstudio/create-baseline ./target`. It installs **22 hooks** at Claude's tool boundaries, **38 skills** organised into ten categories, **1 subagent** for parallel work in isolated worktrees, **4 canonical workflow tracks** declared in `.claude/workflows.jsonl` (where `intake-full` runs 11 nodes from intake to commit), and **3 user-typed consent gates** that Claude cannot forge.
45
45
 
46
46
  Every soft engineering rule a team usually repeats every session — *don't push, don't `--amend`, don't self-approve specs, don't skip phases, don't mock internal modules* — becomes a structural guarantee because the hooks run **outside Claude's tool boundary**. Claude cannot disable a hook with a flag, cannot write a consent marker, cannot reorder the phases without an explicit exception that triage records on disk.
47
47
 
@@ -62,9 +62,9 @@ A team that installs the baseline stops typing *"don't push, don't `--amend`, do
62
62
  | What | Count | Where it lives |
63
63
  |---|---:|---|
64
64
  | **Hooks** at PreToolUse / PostToolUse / SessionStart / Stop / PreCompact / UserPromptSubmit | 22 | `.claude/hooks/` |
65
- | **Skills** across artifact drafting, workflow phases, phase workers, spec helpers, orchestration, memory, audit, alternate tracks, and shared globals | 36 | `.claude/skills/` |
65
+ | **Skills** across artifact drafting, workflow phases, phase workers, spec helpers, orchestration, memory, audit, alternate tracks, shared globals, and maintenance | 38 | `.claude/skills/` |
66
66
  | **Subagent** — `swarm-worker`, executes pre-decided recipes inside isolated git worktrees | 1 | `.claude/agents/` |
67
- | **Workflow phases** intake scout research spec tdd simplify security integrate document archive memory-flush commit | 11 | enforced by `track_guard` |
67
+ | **Workflow tracks** declared in `.claude/workflows.jsonl`. Canonical set: `intake-full` (11 nodes), `spec-entry`, `tdd-quickfix`, `chore`. Two sub-tracks (`swarm-implementation`, `tdd-worker-chain`) are referenced by selector nodes inside the canonical set. | 4 selectable + 2 sub | `.claude/workflows.jsonl`, enforced by `track_guard` |
68
68
  | **Consent gates** — `/approve-spec`, `/approve-swarm`, `/grant-commit`. User-typed; structurally un-invokable by Claude | 3 | `consent_gate_grant` UserPromptSubmit hook |
69
69
  | **MCP servers** declared in `.mcp.json` — `context7` (third-party API docs), `plantuml` (diagram render), `playwright` (cross-engine smoke) | 3 | `.mcp.json` |
70
70
 
@@ -92,15 +92,19 @@ npx @friedbotstudio/create-baseline ./your-project
92
92
  npx @friedbotstudio/create-baseline ./your-project
93
93
 
94
94
  # Force-overwrite an existing install (interactive — type 'overwrite')
95
- npx @friedbotstudio/create-baseline ./your-project --overwrite
95
+ npx @friedbotstudio/create-baseline ./your-project --force
96
96
 
97
97
  # Upgrade an existing install against a newer baseline version.
98
- # In a TTY, each customised file becomes a keep-mine / take-theirs / abort
99
- # prompt. In CI / piped stdout, reproduces the prior --merge behaviour:
98
+ # In a TTY, each tier-1 customised file becomes a keep-mine / take-theirs / abort
99
+ # prompt; tier-2 files auto-merge via `git merge-file --diff3`; tier-3 files
100
+ # stage for the /upgrade-project Claude Code skill to reconcile. In CI / piped
101
+ # stdout, every per-file action is reported with a user-facing label:
100
102
  # - adds new baseline files
101
103
  # - refreshes baseline files the user has not touched
102
- # - preserves user-customised files (exit 3 if any)
103
- # - removes baseline files the upstream removed (only if untouched locally)
104
+ # - keeps customised files (exit 3 if any preserved)
105
+ # - removes baseline files removed upstream that the user had not touched
106
+ # - exit 4 if a mechanical merge produced conflict markers
107
+ # - exit 5 if any tier-3 file was staged for /upgrade-project
104
108
  npx @friedbotstudio/create-baseline upgrade ./your-project
105
109
 
106
110
  # Preview without writing anything
@@ -158,7 +162,7 @@ The three workflow-phase consent gates pause the workflow until you type the cor
158
162
 
159
163
  - **`/approve-spec <slug>`** — after the spec phase, before any code is written
160
164
  - **`/approve-swarm <slug>`** — after `/swarm-plan`, before parallel dispatch
161
- - **`/grant-commit`** — after `/archive`, before the commit lands
165
+ - **`/grant-commit`** — after `/archive` and `/memory-flush`, before the commit lands
162
166
 
163
167
  A fourth consent gate sits outside the phase pipeline:
164
168
 
@@ -172,7 +176,7 @@ The 22 hooks declared in `.claude/settings.json` fire at every Claude tool bound
172
176
 
173
177
  The architectural rule is simple: **decisions live in main context; subagents only execute pre-decided recipes**. The baseline ships exactly one subagent — `swarm-worker` — and its only sanctioned use is parallel dispatch of fully-specified recipes inside isolated git worktrees during `/swarm-dispatch`. Every other capability that might have been a subagent (code authoring, scenario design, scouting, security review, prose writing, UI design) lives instead as a **skill** that runs in main context with full conversation visibility.
174
178
 
175
- The 11-phase workflow is enforced at the write boundary by `track_guard`. Phase ordering is binding; the only mechanism to bypass a phase is the `exceptions` array in `.claude/state/workflow.json`, written by `/triage` at workflow creation time. The chore track is a stripped-down ordering of the same gates, with the test-first phases removed because there is nothing to test-first.
179
+ Tracks declared in `.claude/workflows.jsonl` are enforced at the write boundary by `track_guard`. Node ordering inside each track is binding; the only mechanism to bypass a node is the `exceptions` array in `.claude/state/workflow.json`, written by `/triage` at workflow creation time. The `chore` track is a stripped-down ordering of the same gates, with the test-first nodes removed because nothing needs testing first. Projects declare their own tracks (or add nodes to the canonical ones) by editing their `.claude/workflows.jsonl`. Article IV's invariants (I1..I11) bind every track regardless of who wrote it; a track that omits `/grant-commit` before a `commit` node, or whose dependency graph contains a cycle, is rejected at triage time with a named error.
176
180
 
177
181
  The constitution at `CLAUDE.md` is the source of truth for in-session behaviour; `docs/init/seed.md` is the source of truth for the baseline's shape. When the constitution and the implementation conflict, the constitution governs and the implementation gets corrected. When `seed.md` and the constitution conflict, `seed.md` governs and you stop and surface the drift before acting.
178
182
 
package/bin/cli.js CHANGED
@@ -8,8 +8,8 @@ import { existsSync } from 'node:fs';
8
8
  import * as io from '../src/cli/io.js';
9
9
  import { scanSentinels } from '../src/cli/conflict.js';
10
10
  import { freshInstall, forceInstall, COPY_EXCLUDE } from '../src/cli/install.js';
11
- import { threeWayMerge } from '../src/cli/merge.js';
12
- import { loadManifest, buildManifestFromDir } from '../src/cli/manifest.js';
11
+ import { threeWayMerge, ACTION_LABELS, ACTION_LABEL_WIDTH } from '../src/cli/merge.js';
12
+ import { loadManifest, buildManifestFromDir, MANIFEST_VERSION } from '../src/cli/manifest.js';
13
13
  import { fetchPlantumlIfMissing, FETCH_OUTCOMES } from '../src/cli/plantuml.js';
14
14
  import { runDoctor, formatReport } from '../src/cli/doctor.js';
15
15
 
@@ -18,7 +18,7 @@ const PKG_ROOT = resolve(__dirname, '..');
18
18
 
19
19
  const HELP_TEXT = `Usage:
20
20
  create-baseline <target> [options] install the baseline
21
- create-baseline upgrade [target] three-way merge against an installed baseline
21
+ create-baseline upgrade [target] three-tier merge (mechanical / semantic / binary-prompt)
22
22
  create-baseline doctor [target] report drift in an installed target
23
23
 
24
24
  Materializes the Claude Code baseline (.claude/, CLAUDE.md, .mcp.json,
@@ -31,10 +31,15 @@ Install modes:
31
31
 
32
32
  Upgrade:
33
33
  Replaces the prior --merge flag. Reads <target>/.claude/.baseline-manifest.json
34
- and runs a three-way merge against the shipped template. Prunes baseline files
35
- removed upstream that the user hadn't touched; customized-stale files are
36
- preserved (exit 3) or interactively resolved when stdout is a TTY (keep
37
- mine / take theirs / abort).
34
+ and runs a three-tier merge against the shipped template:
35
+ - tier 1 (binary prompt): customized files prompt "Keep your version / Use
36
+ new baseline / Show diff" in TTY mode (exit 3 on any skipped).
37
+ - tier 2 (mechanical): files routed through git merge-file --diff3 with
38
+ BASE recovered from .claude/.baseline-prior/ cache or npm fallback;
39
+ clean merges land silently, conflicts surface with markers (exit 4).
40
+ - tier 3 (semantic): staged at .claude/state/upgrade/<ts>/ for the
41
+ /upgrade-project Claude Code skill to reconcile (exit 5).
42
+ Prunes baseline files removed upstream that the user hadn't touched.
38
43
  --dry-run Print intended actions without writing.
39
44
 
40
45
  Doctor:
@@ -61,11 +66,13 @@ Misc:
61
66
  --version Print version.
62
67
 
63
68
  Exit codes:
64
- 0 success / clean doctor
69
+ 0 success (or clean doctor)
65
70
  1 user abort, conflict-without-force, doctor reports missing files, or upgrade aborted
66
- 2 argv error, non-TTY where TTY required, doctor finds no manifest, or --merge passed
67
- 3 upgrade had skipped customizations (or stale-customized prunes)
68
- 4 --require-plantuml fetch failure
71
+ 2 bad command line, prompted input needed but no terminal, doctor finds no manifest,
72
+ or deprecated --merge passed
73
+ 3 upgrade kept your customized files (or preserved files removed upstream)
74
+ 4 --require-plantuml jar fetch failed, OR mechanical merge produced conflicts to resolve
75
+ 5 semantic merge staged for /upgrade-project to reconcile in Claude Code
69
76
  `;
70
77
 
71
78
  const OPTIONS = {
@@ -198,8 +205,9 @@ async function runPlainInstall(target, values, templateDir) {
198
205
  if (!dryRun) {
199
206
  const plantumlExit = await fetchPlantumlPlain(target, values);
200
207
  if (plantumlExit !== 0) return plantumlExit;
201
- io.log(`Installed manifest version 1 to ${target}.`);
202
- io.log(`Pin via "@friedbotstudio/create-baseline@<exact-version>" in your bootstrap docs.`);
208
+ const version = await readPackageVersion();
209
+ io.log(`Baseline installed at ${target} (manifest v${MANIFEST_VERSION}).`);
210
+ io.log(`For reproducible installs, pin the exact version: @friedbotstudio/create-baseline@${version}`);
203
211
  }
204
212
  return 0;
205
213
  }
@@ -227,6 +235,13 @@ async function dispatchUpgrade(target, values, templateDir) {
227
235
  await usageError(`No baseline manifest at ${manifestPath}. Run a fresh install first.`);
228
236
  return 2;
229
237
  }
238
+ const { findPendingStage, formatStageTimestamp } = await import('../src/cli/upgrade-tiers.js');
239
+ const pending = await findPendingStage(target);
240
+ if (pending) {
241
+ const fileLines = pending.files.map((f) => ` - ${f}`).join('\n');
242
+ io.log(`A previous upgrade staged ${pending.files.length} file(s) for Claude Code review (staged ${formatStageTimestamp(pending.stage_ts)}):\n${fileLines}\nOpen Claude Code and run /upgrade-project to reconcile.`);
243
+ return 5;
244
+ }
230
245
  if (process.stdout.isTTY) {
231
246
  const tui = await import('../src/cli/tui/upgrade.js');
232
247
  return tui.run({
@@ -241,17 +256,33 @@ async function runPlainUpgrade(target, values, templateDir, manifestPath) {
241
256
  const oldManifest = await loadManifest(manifestPath);
242
257
  const tplFiles = await listShippedFiles(templateDir);
243
258
  const newManifest = await buildManifestFromDir(templateDir, tplFiles);
259
+ await overlayShippedTiers(templateDir, newManifest);
244
260
  if (values['dry-run']) {
245
261
  io.log(`Would upgrade ${tplFiles.length} files into ${target}`);
246
262
  return 0;
247
263
  }
248
264
  const report = await threeWayMerge(templateDir, target, oldManifest, newManifest);
249
265
  for (const action of report.actions) {
250
- io.log(`${action.kind.padEnd(24)} ${action.path}`);
266
+ const label = ACTION_LABELS[action.kind] ?? action.kind;
267
+ io.log(`${label.padEnd(ACTION_LABEL_WIDTH)} ${action.path}`);
251
268
  }
252
269
  return report.exitCode;
253
270
  }
254
271
 
272
+ async function overlayShippedTiers(templateDir, newManifest) {
273
+ const shippedPath = join(templateDir, '.claude/manifest.json');
274
+ if (!existsSync(shippedPath)) return;
275
+ const { readFile: rf } = await import('node:fs/promises');
276
+ const shipped = JSON.parse(await rf(shippedPath, 'utf8'));
277
+ if (!shipped?.files) return;
278
+ for (const rel of Object.keys(newManifest.files)) {
279
+ const shippedEntry = shipped.files[rel];
280
+ if (shippedEntry && typeof shippedEntry === 'object' && typeof shippedEntry.tier === 'string') {
281
+ newManifest.files[rel] = { sha256: newManifest.files[rel], tier: shippedEntry.tier };
282
+ }
283
+ }
284
+ }
285
+
255
286
  async function dispatchDoctor(positionals, values) {
256
287
  const target = resolve(positionals[1] ?? '.');
257
288
  const report = await runDoctor(target, { strict: !!values.strict });
@@ -326,7 +357,7 @@ async function main(argv) {
326
357
  }
327
358
 
328
359
  if (values['no-plantuml'] && values['require-plantuml']) {
329
- await usageError('--no-plantuml and --require-plantuml are mutually exclusive');
360
+ await usageError('--no-plantuml and --require-plantuml conflict; pick one or omit both.');
330
361
  return 2;
331
362
  }
332
363
  if (positionals.length === 0) {
@@ -0,0 +1,74 @@
1
+ ---
2
+ description: Detect and repair baseline drift — missing or invalid `.claude/workflows.jsonl`, schema violations, four-way Article IV / §18 mirror drift, and (advisory) shipped-tooling files placed outside `.claude/` against the convention codified at seed.md §3. Interactive: presents each detected violation via AskUserQuestion and applies the named fix on confirmation.
3
+ argument-hint: ""
4
+ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion, Glob, Grep
5
+ disable-model-invocation: true
6
+ ---
7
+
8
+ # `/init-project doctor` — baseline drift detector + repairer
9
+
10
+ User-only command. Run after a `create-baseline upgrade` cycle, after manually editing `workflows.jsonl`, or any time the baseline feels out of sync. Different from `create-baseline doctor` (the npm CLI manifest-drift checker) — this is a project-internal interactive repair tool.
11
+
12
+ ## Step 1 — Pre-flight
13
+
14
+ - Print: "`/init-project doctor` — checking baseline integrity. Each detected violation is presented separately; you confirm each fix via AskUserQuestion."
15
+ - Verify the project is configured: `.claude/project.json → configured == true`. If false, halt with: "Run `/init-project` first; the project is in agnostic mode."
16
+ - Verify `python3` and `node` available; halt with one-line missing-dep message if not.
17
+
18
+ ## Step 2 — Check `.claude/workflows.jsonl` presence
19
+
20
+ - If `.claude/workflows.jsonl` is missing on disk:
21
+ - AskUserQuestion: "`.claude/workflows.jsonl` missing. Restore from pristine template (`src/.claude/workflows.template.jsonl`)?"
22
+ - Options: `Restore` | `Skip` | `Show diff`
23
+ - On `Restore`: `cp src/.claude/workflows.template.jsonl .claude/workflows.jsonl`. Log to `.claude/state/init/doctor-<timestamp>.log`.
24
+ - On `Skip`: note in report; proceed.
25
+
26
+ ## Step 3 — Check `.claude/schemas/` presence
27
+
28
+ - If `.claude/schemas/workflow-track.v1.json` is missing:
29
+ - AskUserQuestion: "`.claude/schemas/workflow-track.v1.json` missing. Restore from `src/.claude/schemas/`?"
30
+ - Apply on `Restore` (recursive cp).
31
+
32
+ ## Step 4 — Validate workflows.jsonl against §18 schema + invariants
33
+
34
+ - Run `node .claude/skills/triage/seed-tasklist.mjs --validate-only`. The helper exits 0 on success or non-zero with a named-error report on failure.
35
+ - On validation failure, parse the helper's stderr for each error. For each:
36
+ - AskUserQuestion: "Violation: `<kind>` in track `<id>`: `<message>`. Options: `Show context`, `Skip` (mark as known), `Edit manually` (open file at line)."
37
+ - The doctor does NOT auto-fix schema/invariant violations — manual user judgment is required. Surface the violation context (track + node ids) and pause.
38
+
39
+ ## Step 5 — Four-way mirror check (Article IV / §18)
40
+
41
+ Extract the §18 sections from `docs/init/seed.md` and `src/seed.template.md`. Extract Article IV sections from `CLAUDE.md` and `src/CLAUDE.template.md`. Byte-compare each pair.
42
+
43
+ - On §18 mirror drift:
44
+ - AskUserQuestion: "`docs/init/seed.md §18` differs from `src/seed.template.md §18`. Options: `Re-mirror docs→src`, `Re-mirror src→docs`, `Show diff`, `Skip`."
45
+ - Apply the chosen overwrite.
46
+
47
+ - Same pattern for Article IV mirror.
48
+
49
+ ## Step 6 — `.claude/` tooling convention check (advisory)
50
+
51
+ Per seed.md §3 + `conventions.md → user-shipped-tooling-lives-in-claude-directory`, user-shipped baseline tooling lives under `.claude/`. The only project-root exceptions are `CLAUDE.md` and `.mcp.json`.
52
+
53
+ - Scan for shipped tooling at the project root that should live under `.claude/`. Heuristic: files matching `*.skill.md`, `*.workflow.json`, `*.hook.sh`, `*.command.md` at the project root.
54
+ - For each match:
55
+ - AskUserQuestion: "Convention violation: `<path>` at project root. Suggested target: `.claude/<subdir>/<filename>`. Move?"
56
+ - The doctor offers the move; the user confirms.
57
+
58
+ ## Step 7 — Report + log
59
+
60
+ - Print a summary table:
61
+ - Checks run: N
62
+ - Fixes applied: M
63
+ - Skipped (user declined or manual fix required): K
64
+ - Remaining manual: L
65
+ - Write the full session record to `.claude/state/init/doctor-<UTC-timestamp>.log` (one line per check + action).
66
+ - If any check returned a manual-fix path (e.g., schema violation in workflows.jsonl), exit with code 1 so caller workflows treat the result as "drift remains". Otherwise exit 0.
67
+
68
+ ## Constraints
69
+
70
+ - **Read-only by default; writes only on user confirmation.** Every Edit/Write happens after an AskUserQuestion `Apply` response. No silent fixes.
71
+ - **No commits.** The doctor's fixes land on the working tree. The user commits via the normal `/grant-commit` + `/commit` flow.
72
+ - **No new dependencies.** Validation reuses `src/cli/workflows-validator.js` via the triage helper.
73
+ - **Schema/invariant violations require manual fixes.** Auto-fixing structurally violates the user's intent (the violation might be intentional during development). Doctor surfaces; user fixes.
74
+ - **Mirror drift CAN be auto-fixed** (one-direction overwrite on confirmation). The reverse direction (which file is canonical?) is the user's call.
@@ -191,7 +191,20 @@ def compose_snapshot(transcript: Path, project_dir: Path, trigger: str) -> str:
191
191
  workflow = _read_workflow(project_dir)
192
192
 
193
193
  slug = workflow.get("slug") or "(none)"
194
- entry_phase = workflow.get("entry_phase") or "(unknown)"
194
+ # Post-§18: workflow.json carries `track_id`; legacy pre-§18 files carry
195
+ # `entry_phase`. Accept both. Map reverses
196
+ # `src/cli/workflow-migrator.js → ENTRY_PHASE_TO_TRACK_ID`.
197
+ _TRACK_ID_TO_ENTRY_PHASE = {
198
+ "intake-full": "intake",
199
+ "spec-entry": "spec",
200
+ "tdd-quickfix": "tdd",
201
+ "chore": "chore",
202
+ }
203
+ entry_phase = (
204
+ workflow.get("entry_phase")
205
+ or _TRACK_ID_TO_ENTRY_PHASE.get(workflow.get("track_id"))
206
+ or "(unknown)"
207
+ )
195
208
  completed = workflow.get("completed") or []
196
209
  exceptions = workflow.get("exceptions") or []
197
210
  phases = workflow.get("phases") or []
@@ -54,7 +54,17 @@ except Exception:
54
54
  phases = pj.get("workflow", {}).get("phases") or []
55
55
  artifacts = pj.get("workflow", {}).get("artifacts") or {}
56
56
  exceptions = set(ws.get("exceptions") or [])
57
- entry = ws.get("entry_phase")
57
+ # Post-§18: workflow.json carries `track_id`; legacy pre-§18 files carry
58
+ # `entry_phase`. Accept both. The canonical map below mirrors
59
+ # `src/cli/workflow-migrator.js → ENTRY_PHASE_TO_TRACK_ID` in reverse so
60
+ # track_guard's phase-ordering enforcement keeps working on both shapes.
61
+ _TRACK_ID_TO_ENTRY_PHASE = {
62
+ "intake-full": "intake",
63
+ "spec-entry": "spec",
64
+ "tdd-quickfix": "tdd",
65
+ "chore": "chore",
66
+ }
67
+ entry = ws.get("entry_phase") or _TRACK_ID_TO_ENTRY_PHASE.get(ws.get("track_id"))
58
68
 
59
69
  # Find which phase this file belongs to.
60
70
  file_phase = None