@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.
- package/README.md +14 -10
- package/bin/cli.js +46 -15
- package/obj/template/.claude/commands/init-project-doctor.md +74 -0
- package/obj/template/.claude/hooks/lib/resume_writer.py +14 -1
- package/obj/template/.claude/hooks/track_guard.sh +11 -1
- package/obj/template/.claude/manifest.json +848 -230
- package/obj/template/.claude/schemas/workflow-track.v1.json +64 -0
- package/obj/template/.claude/skills/audit-baseline/audit.sh +6 -3
- package/obj/template/.claude/skills/chore/SKILL.md +2 -2
- package/obj/template/.claude/skills/harness/SKILL.md +15 -6
- package/obj/template/.claude/skills/intake/SKILL.md +1 -1
- package/obj/template/.claude/skills/swarm-plan/SKILL.md +2 -0
- package/obj/template/.claude/skills/tdd/SKILL.md +2 -2
- package/obj/template/.claude/skills/triage/SKILL.md +29 -6
- package/obj/template/.claude/skills/triage/seed-tasklist.mjs +107 -0
- package/obj/template/.claude/skills/upgrade-project/SKILL.md +121 -0
- package/obj/template/.claude/workflows.jsonl +6 -0
- package/obj/template/CLAUDE.md +14 -19
- package/obj/template/docs/init/seed.md +152 -7
- package/package.json +1 -1
- package/src/.claude/workflows.template.jsonl +6 -0
- package/src/CLAUDE.template.md +14 -19
- package/src/cli/diff-render.js +54 -0
- package/src/cli/install.js +38 -3
- package/src/cli/manifest.js +7 -3
- package/src/cli/merge.js +107 -13
- package/src/cli/track-tasklist-materializer.js +223 -0
- package/src/cli/tui/upgrade.js +130 -27
- package/src/cli/upgrade-tiers.js +256 -0
- package/src/cli/workflow-migrator.js +40 -0
- package/src/cli/workflows-validator-invariants.js +417 -0
- package/src/cli/workflows-validator-predicates.js +19 -0
- package/src/cli/workflows-validator.js +156 -0
- package/src/seed.template.md +152 -7
- package/obj/template/.claude/skills/google-analytics/SKILL.md +0 -129
- package/obj/template/.claude/skills/google-analytics/references/audiences.md +0 -389
- package/obj/template/.claude/skills/google-analytics/references/bigquery.md +0 -470
- package/obj/template/.claude/skills/google-analytics/references/custom-dimensions.md +0 -355
- package/obj/template/.claude/skills/google-analytics/references/custom-events.md +0 -383
- package/obj/template/.claude/skills/google-analytics/references/data-management.md +0 -416
- package/obj/template/.claude/skills/google-analytics/references/debugview.md +0 -364
- package/obj/template/.claude/skills/google-analytics/references/events-fundamentals.md +0 -398
- package/obj/template/.claude/skills/google-analytics/references/gtag.md +0 -502
- package/obj/template/.claude/skills/google-analytics/references/gtm-integration.md +0 -483
- package/obj/template/.claude/skills/google-analytics/references/measurement-protocol.md +0 -519
- package/obj/template/.claude/skills/google-analytics/references/privacy.md +0 -441
- package/obj/template/.claude/skills/google-analytics/references/recommended-events.md +0 -464
- package/obj/template/.claude/skills/google-analytics/references/reporting.md +0 -397
- package/obj/template/.claude/skills/google-analytics/references/setup.md +0 -344
- package/obj/template/.claude/skills/google-analytics/references/user-tracking.md +0 -417
- package/obj/template/.claude/skills/optimize-seo/SKILL.md +0 -313
- package/obj/template/.claude/skills/optimize-seo/scripts/pagespeed.mjs +0 -197
- package/obj/template/.claude/skills/pagespeed-insights/LICENSE.md +0 -37
- package/obj/template/.claude/skills/pagespeed-insights/SKILL.md +0 -446
- package/obj/template/.claude/skills/pagespeed-insights/reference.md +0 -50
package/obj/template/CLAUDE.md
CHANGED
|
@@ -40,7 +40,7 @@ On every new session, before any work, you SHALL:
|
|
|
40
40
|
|
|
41
41
|
1. **Read** `.claude/project.json` and check the `configured` field.
|
|
42
42
|
2. **If `configured: false`** — `/init-project` has not run. The repository is in a sanctioned operating state called **project-agnostic mode**: hooks are active but `test_runner` and `lint_runner` run in guide mode and nothing is tailored to the user's stack. You SHALL greet the user with this exact framing:
|
|
43
|
-
> "This repo has the Claude Code baseline installed (22 hooks, 1 subagent,
|
|
43
|
+
> "This repo has the Claude Code baseline installed (22 hooks, 1 subagent, 38 skills). It's in **project-agnostic mode** — `test_runner` and `lint_runner` are in guide mode and nothing is tailored to your stack. Run **`/init-project`** to scout the codebase, run the recommender, and generate a config. Skip it if you want baseline-only behavior, but you'll miss stack-specific tailoring."
|
|
44
44
|
You SHALL then proceed with whatever the user asks. Project-agnostic mode is **allowed** — the user is not required to run `/init-project` to use the baseline. The `setup_guard` hook surfaces a one-shot reminder on Write/Edit/MultiEdit (rate-limited to 10 minutes); it does **not** block writes. Other guards (commit, env, spec-approval, verify-pass, track, swarm-boundary) remain hard regardless of `configured` state.
|
|
45
45
|
3. **If `configured: true`** — read `docs/init/seed.md` §16 if present so you know what was added. Tell the user:
|
|
46
46
|
> "Configured for `<stack>`. Run `/triage \"<request>\"` to start a workflow, or `/harness` for the full pipeline."
|
|
@@ -69,7 +69,8 @@ The 11-phase workflow is the only sanctioned path from request to commit. Phase
|
|
|
69
69
|
| 10 | Document | `/document` | docs |
|
|
70
70
|
| 10.5 | Archive | `/archive` | bundle at `docs/archive/<date>/<slug>/` |
|
|
71
71
|
| 10.6 | Memory flush | `/memory-flush` | curated canonical memory + reset `_pending.md` |
|
|
72
|
-
| 11 | **Grant commit** (gate C) + commit | user runs **`/grant-commit`**, then `/commit` (skill) | commit |
|
|
72
|
+
| 11 | **Grant commit** (gate C) + changelog + commit | user runs **`/grant-commit`**, then `/changelog` (skill, sub-step 11.5), then `/commit` (skill) | commit |
|
|
73
|
+
| 11.5 | Changelog (Phase 11 sub-step) | `/changelog` (skill); harness auto-invokes between gate C and `/commit` | `CHANGELOG.md` `## [Unreleased]` section grows + `.claude/state/changelog/<slug>.json` |
|
|
73
74
|
|
|
74
75
|
**Mandatory rules:**
|
|
75
76
|
|
|
@@ -90,25 +91,19 @@ The 11-phase workflow is the only sanctioned path from request to commit. Phase
|
|
|
90
91
|
|
|
91
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`.
|
|
92
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
|
+
|
|
93
96
|
## Article V — Harness orchestration (MANDATORY SOP)
|
|
94
97
|
|
|
95
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.
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
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.
|
|
100
|
-
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.
|
|
101
|
-
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**.
|
|
102
|
-
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.
|
|
103
|
-
5. **Log every transition** to `.claude/state/harness/<slug>.log`.
|
|
104
|
-
|
|
105
|
-
**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.
|
|
106
|
-
|
|
107
|
-
**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.
|
|
108
|
-
|
|
109
|
-
**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:
|
|
110
101
|
|
|
111
|
-
|
|
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.
|
|
112
107
|
|
|
113
108
|
**Integrate-failure decision tree.** When `/integrate` fails inside the loop, you SHALL classify:
|
|
114
109
|
|
|
@@ -292,7 +287,7 @@ Cryptographic supply-chain attestation, signed lock files, and per-skill aggrega
|
|
|
292
287
|
|---|---|
|
|
293
288
|
| `.claude/hooks/` | 22 hook scripts (17 write/run-boundary + 4 lifecycle + 1 input-boundary). Bash + python3, no jq. |
|
|
294
289
|
| `.claude/agents/` | 1 baseline subagent: `swarm-worker` (rendered from `src/agents/swarm-worker.template.md`) |
|
|
295
|
-
| `.claude/skills/` |
|
|
290
|
+
| `.claude/skills/` | 38 skills: artifact (4) + phases (11) + workers (5) + spec helpers (4) + orchestration (3) + memory (1) + shared globals (7) + audit (1) + alt tracks (1) + maintenance (1) |
|
|
296
291
|
| `.claude/commands/` | 5 consent/bootstrap gates: `approve-spec`, `approve-swarm`, `grant-commit`, `grant-push`, `init-project` |
|
|
297
292
|
| `.claude/memory/` | 7 canonical knowledge files + `_pending.md` (staging) + `_resume.md` (continuity snapshot) + `README.md` |
|
|
298
293
|
| `.claude/project.json` | per-project config (test/lint cmd, TDD globs, destructive patterns, swarm config, additions). Populated by `/init-project`. |
|
|
@@ -307,8 +302,8 @@ Cryptographic supply-chain attestation, signed lock files, and per-skill aggrega
|
|
|
307
302
|
**Artifact drafting (4)** — each ships a `template.md`:
|
|
308
303
|
- `intake` (Phase 1), `brd` (cross-functional pre-spec), `spec` (Phase 4, diagram-driven), `rca` (out-of-band postmortem)
|
|
309
304
|
|
|
310
|
-
**Workflow phases (
|
|
311
|
-
- `triage`, `scout`, `research`, `tdd`, `simplify`, `security`, `integrate`, `document`, `archive`, `commit`
|
|
305
|
+
**Workflow phases (11)** — auto-invocable; orchestrator chains them:
|
|
306
|
+
- `triage`, `scout`, `research`, `tdd`, `simplify`, `security`, `integrate`, `document`, `archive`, `changelog` (Phase 11.5), `commit`
|
|
312
307
|
|
|
313
308
|
**Phase workers (5)** — execute pre-decided recipes; mandatorily invoke a sub-skill:
|
|
314
309
|
- `scenario`, `implement`, `verify`, `prose`, `design-ui`
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
**Mandatory binding language.** Each numbered section (§) below specifies a binding requirement for the baseline. Implementations SHALL conform; `CLAUDE.md` Articles SHALL reference the corresponding §; project amendments (per `CLAUDE.md` Art. X) SHALL NOT contradict any § here.
|
|
13
13
|
|
|
14
|
-
The baseline turns soft engineering rules (no unauthorized commits, no stubs, no mocks of internal code, no self-approved specs) into structural guarantees enforced by write-boundary hooks. Eleven workflow phases plus one stripped-down chore track (skips TDD; runs verify + archive mandatorily, simplify/integrate/document conditionally), seventeen write/run-boundary guards plus four lifecycle hooks plus one input-boundary hook (twenty-two hook scripts total — twenty `.sh` + two `.mjs` after the JS-port pilot), thirty-
|
|
14
|
+
The baseline turns soft engineering rules (no unauthorized commits, no stubs, no mocks of internal code, no self-approved specs) into structural guarantees enforced by write-boundary hooks. Eleven workflow phases plus one stripped-down chore track (skips TDD; runs verify + archive mandatorily, simplify/integrate/document conditionally), seventeen write/run-boundary guards plus four lifecycle hooks plus one input-boundary hook (twenty-two hook scripts total — twenty `.sh` + two `.mjs` after the JS-port pilot), thirty-eight skills, one subagent, and four consent gates. Decisions live in main context; the lone subagent (`swarm-worker`) executes pre-decided recipes in parallel worktrees during `/swarm-dispatch`. Every artifact is archived; every third-party API is looked up against live docs. Project memory accumulates across sessions in `.claude/memory/` — auto-extracted by a Stop hook, curated in main context via `/memory-flush`, self-healing via re-verification.
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -110,7 +110,7 @@ Applies to every language. Mappings for TSX, Node, Python, Go, Rust ship inside
|
|
|
110
110
|
│ │ └── lib/common.sh # shared helpers
|
|
111
111
|
│ ├── agents/ # 1 subagent: swarm-worker (rendered from src/agents/swarm-worker.template.md)
|
|
112
112
|
│ ├── commands/ # 5 consent/bootstrap gates (user-only — structurally)
|
|
113
|
-
│ ├── skills/ #
|
|
113
|
+
│ ├── skills/ # 38 skills: artifact (4) + phases (11) + workers (5) + spec helpers (4) + orchestration (3) + memory (1) + shared globals (7) + audit (1) + alt tracks (1) + maintenance (1)
|
|
114
114
|
│ ├── memory/ # project memory: 7 canonical files + _pending.md (gitignored body) + README.md
|
|
115
115
|
│ └── state/ # runtime: workflow.json, approvals, swarm plans, verdicts, logs
|
|
116
116
|
├── src/ # pristine ship-time templates (overlay source for `npx @friedbotstudio/create-baseline`)
|
|
@@ -181,7 +181,7 @@ The baseline ships exactly one subagent. The architectural reason: subagents los
|
|
|
181
181
|
|
|
182
182
|
**Automated re-rendering by `/init-project`.** Step 6.4 re-renders `swarm-worker.md` from the template, driven by the recommender's `additions.swarm_worker_skills`. The recommender does **not** propose new subagent types — only stack-skill additions for the existing worker. Specialization happens via skills loaded into the worker's context, not via parallel agent personas; new decision-making roles belong in skills, which run in main context.
|
|
183
183
|
|
|
184
|
-
### §4.3 Skills (
|
|
184
|
+
### §4.3 Skills (38)
|
|
185
185
|
|
|
186
186
|
Each at `.claude/skills/<name>/SKILL.md`, frontmatter `name` + `description`, plus optional `template.md` (artifact skills) or helper scripts.
|
|
187
187
|
|
|
@@ -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 (
|
|
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
|
|
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
|
|
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
|
|---|---|---|
|
|
@@ -516,7 +517,7 @@ Seed-level requirement: no stale workflow artifacts in the working tree after co
|
|
|
516
517
|
|
|
517
518
|
**Step 4:** Write `src/agents/swarm-worker.template.md` (canonical-body store, per §4.2) — the only subagent template. Then render `.claude/agents/swarm-worker.md` from it with default tokens. The template carries four tokens — `{{NAME}}`, `{{DESCRIPTION}}`, `{{SKILLS}}`, `{{ROLE_LINE}}`. Default `SKILLS` is the YAML list block ` - scenario\n - implement` (the worker's two mandatory sub-skills). Render-parity holds at this stage. `/init-project` later re-renders the worker with stack-aware tokens when the recommender flags stack-specific skills to preload via `additions.swarm_worker_skills`.
|
|
518
519
|
|
|
519
|
-
**Step 5:** Write `.claude/skills/` for the
|
|
520
|
+
**Step 5:** Write `.claude/skills/` for the 38 skills (§4.3) — 29 workflow/worker/orchestration/memory/alt-track skills you author (the +1 over 28 is the `changelog` Phase 11.5 skill) plus 7 shared globals plus 1 audit skill plus 1 maintenance skill. The breakdown: artifact drafting (4) + workflow phases (10) + phase workers (5: `scenario`, `implement`, `verify`, `prose`, `design-ui`) + spec helpers (4: `spec-lint`, `spec-render`, `spec-diagram-review`, `spec-traceability-review`) + orchestration (3: `harness`, `swarm-plan`, `swarm-dispatch`) + memory (1: `memory-flush`) + shared globals (7: `claude-automation-recommender`, `code-structure`, `humanizer`, `documentation`, `technical-tutorials`, `copywriting`, `impeccable`) + drift defender (1: `audit-baseline`) + alternate tracks (1: `chore`) + maintenance (1: `upgrade-project`). The vendored `claude-automation-recommender` (Apache 2.0, from `claude-code-setup`), the writing/quality globals, and the design global ship unchanged with their licenses intact. Artifact skills (intake, brd, spec, rca) each ship a `template.md`. Helper scripts: swarm-plan gets `validate.sh`, swarm-dispatch gets `swarm_merge.sh`, spec-render gets `render.sh`, spec-lint gets `lint.sh`, archive gets `archive.sh`, audit-baseline gets `audit.sh`. All helper scripts `chmod +x`.
|
|
520
521
|
|
|
521
522
|
**Step 6:** Write `.claude/commands/*.md` for the 4 gates (§4.4). All carry `disable-model-invocation: true` as belt-and-braces; structural user-only is enforced by their directory.
|
|
522
523
|
|
|
@@ -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.
|
|
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": {
|
|
@@ -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"}}]}
|
package/src/CLAUDE.template.md
CHANGED
|
@@ -40,7 +40,7 @@ On every new session, before any work, you SHALL:
|
|
|
40
40
|
|
|
41
41
|
1. **Read** `.claude/project.json` and check the `configured` field.
|
|
42
42
|
2. **If `configured: false`** — `/init-project` has not run. The repository is in a sanctioned operating state called **project-agnostic mode**: hooks are active but `test_runner` and `lint_runner` run in guide mode and nothing is tailored to the user's stack. You SHALL greet the user with this exact framing:
|
|
43
|
-
> "This repo has the Claude Code baseline installed (22 hooks, 1 subagent,
|
|
43
|
+
> "This repo has the Claude Code baseline installed (22 hooks, 1 subagent, 38 skills). It's in **project-agnostic mode** — `test_runner` and `lint_runner` are in guide mode and nothing is tailored to your stack. Run **`/init-project`** to scout the codebase, run the recommender, and generate a config. Skip it if you want baseline-only behavior, but you'll miss stack-specific tailoring."
|
|
44
44
|
You SHALL then proceed with whatever the user asks. Project-agnostic mode is **allowed** — the user is not required to run `/init-project` to use the baseline. The `setup_guard` hook surfaces a one-shot reminder on Write/Edit/MultiEdit (rate-limited to 10 minutes); it does **not** block writes. Other guards (commit, env, spec-approval, verify-pass, track, swarm-boundary) remain hard regardless of `configured` state.
|
|
45
45
|
3. **If `configured: true`** — read `docs/init/seed.md` §16 if present so you know what was added. Tell the user:
|
|
46
46
|
> "Configured for `<stack>`. Run `/triage \"<request>\"` to start a workflow, or `/harness` for the full pipeline."
|
|
@@ -69,7 +69,8 @@ The 11-phase workflow is the only sanctioned path from request to commit. Phase
|
|
|
69
69
|
| 10 | Document | `/document` | docs |
|
|
70
70
|
| 10.5 | Archive | `/archive` | bundle at `docs/archive/<date>/<slug>/` |
|
|
71
71
|
| 10.6 | Memory flush | `/memory-flush` | curated canonical memory + reset `_pending.md` |
|
|
72
|
-
| 11 | **Grant commit** (gate C) + commit | user runs **`/grant-commit`**, then `/commit` (skill) | commit |
|
|
72
|
+
| 11 | **Grant commit** (gate C) + changelog + commit | user runs **`/grant-commit`**, then `/changelog` (skill, sub-step 11.5), then `/commit` (skill) | commit |
|
|
73
|
+
| 11.5 | Changelog (Phase 11 sub-step) | `/changelog` (skill); harness auto-invokes between gate C and `/commit` | `CHANGELOG.md` `## [Unreleased]` section grows + `.claude/state/changelog/<slug>.json` |
|
|
73
74
|
|
|
74
75
|
**Mandatory rules:**
|
|
75
76
|
|
|
@@ -90,25 +91,19 @@ The 11-phase workflow is the only sanctioned path from request to commit. Phase
|
|
|
90
91
|
|
|
91
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`.
|
|
92
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
|
+
|
|
93
96
|
## Article V — Harness orchestration (MANDATORY SOP)
|
|
94
97
|
|
|
95
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.
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
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.
|
|
100
|
-
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.
|
|
101
|
-
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**.
|
|
102
|
-
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.
|
|
103
|
-
5. **Log every transition** to `.claude/state/harness/<slug>.log`.
|
|
104
|
-
|
|
105
|
-
**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.
|
|
106
|
-
|
|
107
|
-
**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.
|
|
108
|
-
|
|
109
|
-
**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:
|
|
110
101
|
|
|
111
|
-
|
|
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.
|
|
112
107
|
|
|
113
108
|
**Integrate-failure decision tree.** When `/integrate` fails inside the loop, you SHALL classify:
|
|
114
109
|
|
|
@@ -292,7 +287,7 @@ Cryptographic supply-chain attestation, signed lock files, and per-skill aggrega
|
|
|
292
287
|
|---|---|
|
|
293
288
|
| `.claude/hooks/` | 22 hook scripts (17 write/run-boundary + 4 lifecycle + 1 input-boundary). Bash + python3, no jq. |
|
|
294
289
|
| `.claude/agents/` | 1 baseline subagent: `swarm-worker` (rendered from `src/agents/swarm-worker.template.md`) |
|
|
295
|
-
| `.claude/skills/` |
|
|
290
|
+
| `.claude/skills/` | 38 skills: artifact (4) + phases (11) + workers (5) + spec helpers (4) + orchestration (3) + memory (1) + shared globals (7) + audit (1) + alt tracks (1) + maintenance (1) |
|
|
296
291
|
| `.claude/commands/` | 5 consent/bootstrap gates: `approve-spec`, `approve-swarm`, `grant-commit`, `grant-push`, `init-project` |
|
|
297
292
|
| `.claude/memory/` | 7 canonical knowledge files + `_pending.md` (staging) + `_resume.md` (continuity snapshot) + `README.md` |
|
|
298
293
|
| `.claude/project.json` | per-project config (test/lint cmd, TDD globs, destructive patterns, swarm config, additions). Populated by `/init-project`. |
|
|
@@ -307,8 +302,8 @@ Cryptographic supply-chain attestation, signed lock files, and per-skill aggrega
|
|
|
307
302
|
**Artifact drafting (4)** — each ships a `template.md`:
|
|
308
303
|
- `intake` (Phase 1), `brd` (cross-functional pre-spec), `spec` (Phase 4, diagram-driven), `rca` (out-of-band postmortem)
|
|
309
304
|
|
|
310
|
-
**Workflow phases (
|
|
311
|
-
- `triage`, `scout`, `research`, `tdd`, `simplify`, `security`, `integrate`, `document`, `archive`, `commit`
|
|
305
|
+
**Workflow phases (11)** — auto-invocable; orchestrator chains them:
|
|
306
|
+
- `triage`, `scout`, `research`, `tdd`, `simplify`, `security`, `integrate`, `document`, `archive`, `changelog` (Phase 11.5), `commit`
|
|
312
307
|
|
|
313
308
|
**Phase workers (5)** — execute pre-decided recipes; mandatorily invoke a sub-skill:
|
|
314
309
|
- `scenario`, `implement`, `verify`, `prose`, `design-ui`
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Foundation — line-level unified-diff renderer used by the upgrade TUI's
|
|
2
|
+
// "Show diff" prompt. Pure function; no IO, no side effects.
|
|
3
|
+
|
|
4
|
+
const ANSI_RED = '\x1b[31m';
|
|
5
|
+
const ANSI_GREEN = '\x1b[32m';
|
|
6
|
+
const ANSI_RESET = '\x1b[0m';
|
|
7
|
+
|
|
8
|
+
export function renderUnifiedDiff(localText, incomingText, opts = {}) {
|
|
9
|
+
const colorize = opts.colorize === true;
|
|
10
|
+
const ops = diffLines(splitLines(localText), splitLines(incomingText));
|
|
11
|
+
return ops.map((op) => renderOp(op, colorize)).join('\n');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function splitLines(text) {
|
|
15
|
+
return String(text).split('\n');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function renderOp(op, colorize) {
|
|
19
|
+
if (op.kind === 'context') return ' ' + op.line;
|
|
20
|
+
const marker = op.kind === 'remove' ? '-' : '+';
|
|
21
|
+
if (!colorize) return marker + op.line;
|
|
22
|
+
const color = op.kind === 'remove' ? ANSI_RED : ANSI_GREEN;
|
|
23
|
+
return color + marker + op.line + ANSI_RESET;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function diffLines(a, b) {
|
|
27
|
+
const m = a.length;
|
|
28
|
+
const n = b.length;
|
|
29
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
30
|
+
for (let i = 1; i <= m; i++) {
|
|
31
|
+
for (let j = 1; j <= n; j++) {
|
|
32
|
+
if (a[i - 1] === b[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
33
|
+
else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const ops = [];
|
|
37
|
+
let i = m;
|
|
38
|
+
let j = n;
|
|
39
|
+
while (i > 0 && j > 0) {
|
|
40
|
+
if (a[i - 1] === b[j - 1]) {
|
|
41
|
+
ops.push({ kind: 'context', line: a[i - 1] });
|
|
42
|
+
i--; j--;
|
|
43
|
+
} else if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
44
|
+
ops.push({ kind: 'remove', line: a[i - 1] });
|
|
45
|
+
i--;
|
|
46
|
+
} else {
|
|
47
|
+
ops.push({ kind: 'add', line: b[j - 1] });
|
|
48
|
+
j--;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
while (i > 0) { ops.push({ kind: 'remove', line: a[i - 1] }); i--; }
|
|
52
|
+
while (j > 0) { ops.push({ kind: 'add', line: b[j - 1] }); j--; }
|
|
53
|
+
return ops.reverse();
|
|
54
|
+
}
|
package/src/cli/install.js
CHANGED
|
@@ -10,7 +10,11 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
10
10
|
const PACKAGE_ROOT = resolve(__dirname, '../..');
|
|
11
11
|
const NPMRC_TEMPLATE_PATH = join(PACKAGE_ROOT, 'src/.npmrc.template');
|
|
12
12
|
|
|
13
|
-
export const NEVER_TOUCH = Object.freeze([
|
|
13
|
+
export const NEVER_TOUCH = Object.freeze([
|
|
14
|
+
'.claude/project.json',
|
|
15
|
+
'.claude/workflows.jsonl',
|
|
16
|
+
'.claude/schemas/workflow-track.v1.json',
|
|
17
|
+
]);
|
|
14
18
|
export const SPECIAL_MERGE = Object.freeze(['.mcp.json']);
|
|
15
19
|
// The shipped manifest now lives at `.claude/manifest.json` (inside the
|
|
16
20
|
// template's .claude/ subtree), so the recursive cp drops it at the correct
|
|
@@ -32,14 +36,43 @@ async function listFiles(root, base = root, acc = []) {
|
|
|
32
36
|
return acc;
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
async function readPackageVersion() {
|
|
40
|
+
try {
|
|
41
|
+
const pkgPath = join(PACKAGE_ROOT, 'package.json');
|
|
42
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'));
|
|
43
|
+
return typeof pkg.version === 'string' && pkg.version.length > 0 ? pkg.version : '0.0.0';
|
|
44
|
+
} catch {
|
|
45
|
+
return '0.0.0';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
async function writeBaselineManifest(target) {
|
|
36
50
|
const files = await listFiles(target);
|
|
37
|
-
const filtered = files.filter((p) =>
|
|
38
|
-
|
|
51
|
+
const filtered = files.filter((p) =>
|
|
52
|
+
p !== '.claude/.baseline-manifest.json' && !p.startsWith('.claude/.baseline-prior/')
|
|
53
|
+
);
|
|
54
|
+
const baseline_version = await readPackageVersion();
|
|
55
|
+
const m = await buildManifestFromDir(target, filtered, { baseline_version });
|
|
39
56
|
await mkdir(join(target, '.claude'), { recursive: true });
|
|
40
57
|
await saveManifest(join(target, '.claude/.baseline-manifest.json'), m);
|
|
41
58
|
}
|
|
42
59
|
|
|
60
|
+
async function writeBaselinePriorMirror(templateDir, target) {
|
|
61
|
+
const priorRoot = join(target, '.claude/.baseline-prior');
|
|
62
|
+
await mkdir(priorRoot, { recursive: true });
|
|
63
|
+
await cp(templateDir, priorRoot, {
|
|
64
|
+
recursive: true,
|
|
65
|
+
force: true,
|
|
66
|
+
filter: (src) => {
|
|
67
|
+
const rel = relative(templateDir, src).split(sep).join('/');
|
|
68
|
+
if (rel === '') return true;
|
|
69
|
+
if (COPY_EXCLUDE.includes(rel)) return false;
|
|
70
|
+
return true;
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
await writeFile(join(priorRoot, '.gitignore'), '*\n');
|
|
74
|
+
}
|
|
75
|
+
|
|
43
76
|
function makeFilter(opts) {
|
|
44
77
|
return (src, _dest) => {
|
|
45
78
|
const rel = relative(opts.templateRoot, src).split(sep).join('/');
|
|
@@ -89,6 +122,7 @@ export async function freshInstall(templateDir, target, opts = {}) {
|
|
|
89
122
|
await cp(templateDir, target, { recursive: true, force: false, filter });
|
|
90
123
|
await applySpecialAndNeverTouch(templateDir, target);
|
|
91
124
|
if (opts.withNpmrc === true) await materializeNpmrc(target);
|
|
125
|
+
await writeBaselinePriorMirror(templateDir, target);
|
|
92
126
|
await writeBaselineManifest(target);
|
|
93
127
|
}
|
|
94
128
|
|
|
@@ -97,5 +131,6 @@ export async function forceInstall(templateDir, target, opts = {}) {
|
|
|
97
131
|
await cp(templateDir, target, { recursive: true, force: true, filter });
|
|
98
132
|
await applySpecialAndNeverTouch(templateDir, target);
|
|
99
133
|
if (opts.withNpmrc === true) await materializeNpmrc(target);
|
|
134
|
+
await writeBaselinePriorMirror(templateDir, target);
|
|
100
135
|
await writeBaselineManifest(target);
|
|
101
136
|
}
|
package/src/cli/manifest.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
|
|
5
|
-
export const MANIFEST_VERSION =
|
|
5
|
+
export const MANIFEST_VERSION = 2;
|
|
6
6
|
|
|
7
7
|
export async function hashFile(path) {
|
|
8
8
|
const buf = await readFile(path);
|
|
@@ -24,15 +24,19 @@ export async function saveManifest(path, m) {
|
|
|
24
24
|
await writeFile(path, JSON.stringify(m, null, 2) + '\n');
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export async function buildManifestFromDir(rootDir, fileList) {
|
|
27
|
+
export async function buildManifestFromDir(rootDir, fileList, opts = {}) {
|
|
28
28
|
const files = {};
|
|
29
29
|
const sorted = [...fileList].sort();
|
|
30
30
|
for (const rel of sorted) {
|
|
31
31
|
files[rel] = await hashFile(join(rootDir, rel));
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
const manifest = {
|
|
34
34
|
manifest_version: MANIFEST_VERSION,
|
|
35
35
|
generated_at: new Date().toISOString(),
|
|
36
36
|
files,
|
|
37
37
|
};
|
|
38
|
+
if (typeof opts.baseline_version === 'string' && opts.baseline_version.length > 0) {
|
|
39
|
+
manifest.baseline_version = opts.baseline_version;
|
|
40
|
+
}
|
|
41
|
+
return manifest;
|
|
38
42
|
}
|