@friedbotstudio/create-baseline 0.4.0 → 0.6.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 +1 -1
- package/bin/cli.js +97 -23
- package/obj/template/.claude/manifest.json +961 -0
- package/obj/template/.claude/skills/audit-baseline/audit.sh +20 -10
- package/obj/template/.claude/skills/upgrade-project/SKILL.md +121 -0
- package/obj/template/CLAUDE.md +9 -8
- package/obj/template/docs/init/seed.md +6 -6
- package/package.json +1 -1
- package/src/CLAUDE.template.md +9 -8
- package/src/cli/diff-render.js +54 -0
- package/src/cli/install.js +40 -11
- package/src/cli/manifest.js +7 -3
- package/src/cli/merge.js +80 -13
- package/src/cli/tui/install.js +3 -1
- package/src/cli/tui/meta.js +51 -18
- package/src/cli/tui/splash.js +111 -0
- package/src/cli/tui/tokens.js +15 -8
- package/src/cli/tui/upgrade.js +138 -22
- package/src/cli/upgrade-tiers.js +234 -0
- package/src/seed.template.md +6 -6
- package/obj/template/manifest.json +0 -275
|
@@ -49,18 +49,25 @@ EXPECTED_AGENTS = {
|
|
|
49
49
|
# from main context; they never make decisions.
|
|
50
50
|
"swarm-worker",
|
|
51
51
|
}
|
|
52
|
-
# Skill provenance comes from the shipped manifest
|
|
52
|
+
# Skill provenance comes from the shipped manifest. Two possible locations,
|
|
53
|
+
# tried in order:
|
|
54
|
+
# 1. <root>/.claude/manifest.json — present in consumer projects after the
|
|
55
|
+
# CLI installs the baseline (the recursive cp puts it there directly).
|
|
56
|
+
# 2. <root>/obj/template/.claude/manifest.json — present in the baseline
|
|
57
|
+
# dev repo, where `npm run build` writes the manifest before publishing.
|
|
53
58
|
# The build (scripts/build-manifest.mjs) reads owner: frontmatter from every
|
|
54
59
|
# .claude/skills/<slug>/SKILL.md and emits the canonical baseline-skill set as
|
|
55
60
|
# manifest.owners.skills. See CLAUDE.md Article XI and seed.md §17.
|
|
56
61
|
def load_manifest():
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
for rel in (".claude/manifest.json", "obj/template/.claude/manifest.json"):
|
|
63
|
+
path = root / rel
|
|
64
|
+
if not path.exists():
|
|
65
|
+
continue
|
|
66
|
+
try:
|
|
67
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
68
|
+
except Exception:
|
|
69
|
+
return None
|
|
70
|
+
return None
|
|
64
71
|
|
|
65
72
|
def read_skill_owner(slug):
|
|
66
73
|
p = root / f".claude/skills/{slug}/SKILL.md"
|
|
@@ -245,7 +252,7 @@ def check_skill_ownership():
|
|
|
245
252
|
# Manifest-driven baseline-skill presence + per-file hash check.
|
|
246
253
|
manifest = load_manifest()
|
|
247
254
|
if manifest is None:
|
|
248
|
-
add("skill ownership: manifest", "WARN", "obj/template/manifest.json missing — run npm run build")
|
|
255
|
+
add("skill ownership: manifest", "WARN", ".claude/manifest.json (or obj/template/.claude/manifest.json) missing — run npm run build")
|
|
249
256
|
return
|
|
250
257
|
owners_skills = (manifest.get("owners") or {}).get("skills", {}) or {}
|
|
251
258
|
files_map = manifest.get("files") or {}
|
|
@@ -254,13 +261,16 @@ def check_skill_ownership():
|
|
|
254
261
|
if not skill_dir.is_dir():
|
|
255
262
|
add(f"skill ownership: {slug}", "FAIL", "baseline skill missing")
|
|
256
263
|
continue
|
|
257
|
-
for path,
|
|
264
|
+
for path, entry in files_map.items():
|
|
258
265
|
if not path.startswith(f".claude/skills/{slug}/"):
|
|
259
266
|
continue
|
|
260
267
|
disk_file = root / path
|
|
261
268
|
if not disk_file.exists():
|
|
262
269
|
add(f"skill ownership: {slug}", "FAIL", f"baseline skill missing: {path}")
|
|
263
270
|
continue
|
|
271
|
+
# Manifest v3+ wraps each entry as {sha256, tier}; v2 ships bare
|
|
272
|
+
# sha strings. Both shapes are accepted at read time.
|
|
273
|
+
expected_hash = entry if isinstance(entry, str) else (entry or {}).get("sha256")
|
|
264
274
|
actual = hashlib.sha256(disk_file.read_bytes()).hexdigest()
|
|
265
275
|
if actual != expected_hash:
|
|
266
276
|
add(f"skill ownership: {slug}", "FAIL", f"hash mismatch at {path}")
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upgrade-project
|
|
3
|
+
owner: baseline
|
|
4
|
+
description: Reconcile baseline-versioned files that the `create-baseline upgrade` CLI staged for LLM-assisted semantic merge. Use when the CLI prints "Open Claude Code and run /upgrade-project to reconcile". Reads the stage manifest at `.claude/state/upgrade/<ts>/manifest.json`, reasons through each pending file's three-way delta in main context, writes a reconciled LOCAL, then deletes the stage when every file lands. Supports `--dry-run` (preview the reconciled diff without writing) and a structured "needs-user-input" fallback when the conflict cannot be disambiguated automatically.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /upgrade-project — semantic-merge reconciliation for baseline files
|
|
8
|
+
|
|
9
|
+
You are reconciling files that `create-baseline upgrade` decided required **semantic merge** rather than mechanical merge. The CLI has already detected per-file customization, classified each file as tier 3 (SEMANTIC) at build time, and staged the three states (BASE / INCOMING / LOCAL) for you to reason about in main context. This skill is the only sanctioned way to drive that staged state to RECONCILED.
|
|
10
|
+
|
|
11
|
+
This skill is **maintenance work**, not a workflow phase. It is invoked reactively whenever the upgrade CLI prints the "run /upgrade-project to reconcile" pointer. It does not appear in `.claude/state/workflow.json`, does not require `/triage`, and does not trigger consent gates.
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
- The user just ran `npx @friedbotstudio/create-baseline upgrade <target>` and the CLI exited 5 with a "Pending semantic-merge stage at <ts>" message.
|
|
16
|
+
- The user types `/upgrade-project` or asks "reconcile the staged files".
|
|
17
|
+
- A previous `/upgrade-project` invocation hit a `NEEDS_USER_INPUT` fallback, the user provided direction, and you re-invoke to pick up where you left off.
|
|
18
|
+
|
|
19
|
+
## Inputs (read from disk)
|
|
20
|
+
|
|
21
|
+
For each stage directory under `.claude/state/upgrade/`:
|
|
22
|
+
|
|
23
|
+
- `manifest.json` — the **stage manifest** the CLI wrote. Schema:
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"stage_version": 1,
|
|
27
|
+
"slug": "upgrade-flow-rework",
|
|
28
|
+
"created_at": "2026-05-20T14:49:00.000Z",
|
|
29
|
+
"baseline_version_from": "0.4.0",
|
|
30
|
+
"baseline_version_to": "0.5.0",
|
|
31
|
+
"files": [
|
|
32
|
+
{
|
|
33
|
+
"rel": "docs/init/seed.md",
|
|
34
|
+
"base_sha256": "<hex>",
|
|
35
|
+
"incoming_sha256": "<hex>",
|
|
36
|
+
"local_sha256": "<hex>",
|
|
37
|
+
"status": "PENDING"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
- For each entry in `files`, three artifacts are present:
|
|
43
|
+
- `<rel>.baseline-base` — the **BASE** content (the file as it was when the user last installed the baseline).
|
|
44
|
+
- `<rel>.baseline-incoming` — the **INCOMING** content (the file as it ships in the new baseline; INCOMING and REMOTE are the same thing).
|
|
45
|
+
- The LOCAL file remains at its real path inside the target tree (untouched by the CLI).
|
|
46
|
+
|
|
47
|
+
## Procedure
|
|
48
|
+
|
|
49
|
+
1. **Discover the stage.** Read `.claude/state/upgrade/` and pick the most-recent stage directory whose manifest has at least one file with `status: PENDING` or `status: NEEDS_USER_INPUT`. If no such stage exists, tell the user "No pending stage to reconcile" and exit.
|
|
50
|
+
2. **Per file**, in the order they appear in the stage manifest:
|
|
51
|
+
- Read BASE, INCOMING, and LOCAL.
|
|
52
|
+
- Reason about the three-way delta. Identify what changed between BASE → INCOMING (the upstream edit), what changed between BASE → LOCAL (the user edit), and where they conflict.
|
|
53
|
+
- If both edits are textually non-overlapping, the CLI would have routed the file to tier 2 (mechanical merge). The fact that the file is in tier 3 means structural reconciliation is needed — most commonly: both sides inserted content at the same structural anchor (a new section, a new numbered article, a new TOC entry).
|
|
54
|
+
- Apply the **zero-drift renumbering rule** below.
|
|
55
|
+
- Write the reconciled bytes to the LOCAL path.
|
|
56
|
+
- Update the stage manifest entry's `status` to `RECONCILED`.
|
|
57
|
+
3. **Finalize the stage.** When every entry's status is `RECONCILED`, delete the stage directory (`rm -rf .claude/state/upgrade/<ts>/`). Report per-file status to the user.
|
|
58
|
+
|
|
59
|
+
## The zero-drift renumbering rule (binding)
|
|
60
|
+
|
|
61
|
+
When BASE → INCOMING adds a new structural entry (a new Article, a new section, a new numbered item) at position N, and BASE → LOCAL added the user's own entry at the same position N, you SHALL renumber the user's entry to the **next available** slot (N+1) — you SHALL **never fold** the user's entry into an existing baseline section.
|
|
62
|
+
|
|
63
|
+
Concrete example (the Article-XI reproducer):
|
|
64
|
+
- BASE `seed.md` ends at Article X.
|
|
65
|
+
- LOCAL has added a project-specific `## Article XI (user content)`.
|
|
66
|
+
- INCOMING ships a new baseline `## Article XI (Skill provenance and the baseline manifest)`.
|
|
67
|
+
|
|
68
|
+
The reconciled `seed.md` SHALL contain:
|
|
69
|
+
- `## Article XI` — the baseline's content (verbatim).
|
|
70
|
+
- `## Article XII` — the user's prior content, renumbered.
|
|
71
|
+
- Every cross-reference in the document that pointed to "Article XI" SHALL be updated to point to either Article XI (when the reference was always to the new baseline content) or Article XII (when the reference was to what was previously Article XI). Surface ambiguous references as `NEEDS_USER_INPUT` per the fallback below.
|
|
72
|
+
|
|
73
|
+
The reason **shift, never fold**: the next baseline upgrade SHALL produce zero new staging entries for this file. If the user's content were folded into an existing baseline section, the next upgrade would re-detect a customization and re-stage. The renumbering preserves both bodies as independent structural units, so subsequent upgrades see exactly the baseline-owned portion (Articles I–XI) as unchanged.
|
|
74
|
+
|
|
75
|
+
The same principle applies recursively. If a later baseline ships Article XII and the user's content has been at Article XII since the prior upgrade, shift the user's content to Article XIII. Always shift to the next available slot.
|
|
76
|
+
|
|
77
|
+
## `--dry-run` mode
|
|
78
|
+
|
|
79
|
+
When invoked with `args=dry-run` (e.g., `/upgrade-project dry-run`):
|
|
80
|
+
|
|
81
|
+
- Per file, produce the reconciled bytes in your reasoning, then emit a colorized unified diff (LOCAL vs reconciled) to the skill's terminal output rather than writing.
|
|
82
|
+
- DO NOT modify any LOCAL file.
|
|
83
|
+
- DO NOT update the stage manifest (statuses stay PENDING / NEEDS_USER_INPUT).
|
|
84
|
+
- DO NOT delete the stage directory.
|
|
85
|
+
- Tell the user: "Dry-run complete. Re-run without `dry-run` to apply."
|
|
86
|
+
|
|
87
|
+
Dry-run mode is for building trust in early use. After the first few successful reconciliations, the user typically stops dry-running.
|
|
88
|
+
|
|
89
|
+
## Fallback — NEEDS_USER_INPUT
|
|
90
|
+
|
|
91
|
+
When you genuinely cannot disambiguate intent — the conflict has multiple plausible reconciliations and you cannot pick one without guessing the user's preference — apply the **NEEDS_USER_INPUT** fallback rather than picking arbitrarily:
|
|
92
|
+
|
|
93
|
+
1. Update the stage manifest entry's `status` to `NEEDS_USER_INPUT`.
|
|
94
|
+
2. Leave BASE, INCOMING, and LOCAL artifacts in place (do NOT delete the stage).
|
|
95
|
+
3. Surface a targeted question to the user that names the file, summarizes the conflict in one sentence, and offers concrete options. Example: "Cannot disambiguate `docs/init/seed.md` Article XI: the baseline's new Article XI heading shares the user's chosen heading text. Should I (a) treat them as the same article and merge bodies, or (b) renumber the user's article to XII?"
|
|
96
|
+
4. Exit clean. The user provides direction in their next prompt. A subsequent `/upgrade-project` invocation re-reads the stage manifest, finds the `NEEDS_USER_INPUT` entry, and re-attempts with the user's direction.
|
|
97
|
+
|
|
98
|
+
Use this fallback sparingly. The rework's whole point is that LLM judgment exceeds `git merge-file` for structural conflicts; if you punt to NEEDS_USER_INPUT for trivial reconciliations, you defeat the purpose.
|
|
99
|
+
|
|
100
|
+
## Constraints
|
|
101
|
+
|
|
102
|
+
- **Validate `rel` before writing.** Before writing reconciled bytes to LOCAL, you SHALL verify that the resolved absolute path of `<target>/<rel>` is a descendant of `target`. A `rel` value that escapes the target tree (`../`, absolute path, symlink-resolved escape) SHALL be rejected as a `NEEDS_USER_INPUT` fallback with the reason `path-traversal-rejected`. The CLI's stage writer never produces escaping `rel` values, so this catches only tampered stage manifests from a local attacker with `.claude/state/` write access — defense in depth.
|
|
103
|
+
- **No write outside the stage directory and the LOCAL path.** You SHALL NOT touch `.claude/.baseline-prior/`, the installed `.baseline-manifest.json`, or any other CLI state.
|
|
104
|
+
- **No partial writes per file.** The reconciled LOCAL must be the complete final content. If you cannot produce a complete reconciliation, use the NEEDS_USER_INPUT fallback and leave LOCAL unmodified.
|
|
105
|
+
- **Honor Article XI of CLAUDE.md.** This skill only touches files explicitly staged by the CLI — which, by construction, are baseline-owned. User-added files at colliding paths are never staged.
|
|
106
|
+
- **No commits.** Reconciled files land on the working tree; the user inspects via `git diff` and commits when satisfied.
|
|
107
|
+
- **No re-fetching from npm.** BASE is already on disk in the stage; no network round-trip needed.
|
|
108
|
+
|
|
109
|
+
## Output
|
|
110
|
+
|
|
111
|
+
After running, report per file:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
# /upgrade-project — <stage_ts>
|
|
115
|
+
|
|
116
|
+
- <rel>: RECONCILED (N lines changed)
|
|
117
|
+
- <rel>: NEEDS_USER_INPUT — <one-sentence question>
|
|
118
|
+
- <rel>: SKIPPED (dry-run)
|
|
119
|
+
|
|
120
|
+
Stage deleted: yes | no (NEEDS_USER_INPUT pending)
|
|
121
|
+
```
|
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
|
|
|
@@ -272,13 +273,13 @@ The vendored `impeccable` skill stays untouched (Article IX). `design-ui` is the
|
|
|
272
273
|
|
|
273
274
|
## Article XI — Skill provenance and the baseline manifest
|
|
274
275
|
|
|
275
|
-
A skill at `.claude/skills/<slug>/SKILL.md` is **baseline-owned** iff its YAML frontmatter declares `owner: baseline`. Every other skill on disk — those without an `owner:` field, or those declaring `owner: user` — is user/third-party and out-of-scope of baseline audit checks. Absence-of-`owner` is the deliberate default so a project with pre-existing skills can install the baseline without annotating any of its own files. The build script `scripts/build-manifest.mjs` reads each `owner:` value and emits the canonical baseline-skill set into `obj/template/manifest.json` under `owners.skills` (a JSON object mapping slug → `"baseline"`). The
|
|
276
|
+
A skill at `.claude/skills/<slug>/SKILL.md` is **baseline-owned** iff its YAML frontmatter declares `owner: baseline`. Every other skill on disk — those without an `owner:` field, or those declaring `owner: user` — is user/third-party and out-of-scope of baseline audit checks. Absence-of-`owner` is the deliberate default so a project with pre-existing skills can install the baseline without annotating any of its own files. The build script `scripts/build-manifest.mjs` reads each `owner:` value and emits the canonical baseline-skill set into the shipped manifest at `obj/template/.claude/manifest.json` under `owners.skills` (a JSON object mapping slug → `"baseline"`). The recursive install copies the manifest straight to `<target>/.claude/manifest.json` (same path inside the `.claude/` subtree, no special-case). The CLI separately writes `<target>/.claude/.baseline-manifest.json` post-install as a runtime sha256 table of the target's actual on-disk contents (used by `doctor` and `upgrade`). The audit at `.claude/skills/audit-baseline/audit.sh` consumes `manifest.owners.skills` from the shipped `.claude/manifest.json` as the canonical baseline-skill enumeration — the previous hard-coded `EXPECTED_SKILLS` set is removed.
|
|
276
277
|
|
|
277
278
|
You SHALL:
|
|
278
279
|
|
|
279
280
|
1. **Declare baseline ownership only.** A SKILL.md that ships in the baseline SHALL declare `owner: baseline` in its frontmatter directly after `name:`. Authoring a user/third-party skill does NOT require any `owner:` annotation — absence is the default. Explicit `owner: user` is permitted but never required. The only frontmatter-related FAIL the audit emits is `invalid owner=<value>` (a present-but-malformed `owner:` field, e.g. typo). Missing-`owner:` is silently skipped.
|
|
280
|
-
2. **Trust the manifest.** The shipped `obj/template/manifest.json` (
|
|
281
|
-
3. **Re-derive on drift.** The audit re-derives sha256 hashes from `manifest.files` for every path under `.claude/skills/<slug>/` whose slug appears in `owners.skills`, and compares against on-disk content. Mismatches surface as `hash mismatch at <path>`. A baseline-listed slug missing from disk surfaces as `baseline skill missing`. These are hard FAIL — drift detection has no opt-out.
|
|
281
|
+
2. **Trust the manifest.** The shipped manifest at `obj/template/.claude/manifest.json` (delivered to `<target>/.claude/manifest.json` by the recursive install copy) is the canonical record of baseline-owned skills and their content hashes. The runtime `<target>/.claude/.baseline-manifest.json` written by the CLI post-install is a separate file that captures the target's actual on-disk hashes for `doctor`/`upgrade` — do not conflate the two. You SHALL NOT maintain a separate hard-coded list of baseline-skill slugs anywhere in the codebase.
|
|
282
|
+
3. **Re-derive on drift.** The audit reads the manifest from `<root>/.claude/manifest.json` (consumer projects) with a fallback to `<root>/obj/template/.claude/manifest.json` (the baseline dev repo). It re-derives sha256 hashes from `manifest.files` for every path under `.claude/skills/<slug>/` whose slug appears in `owners.skills`, and compares against on-disk content. Mismatches surface as `hash mismatch at <path>`. A baseline-listed slug missing from disk surfaces as `baseline skill missing`. These are hard FAIL — drift detection has no opt-out.
|
|
282
283
|
4. **Preserve constitutional citation.** This Article XI SHALL remain in CLAUDE.md AND in `src/CLAUDE.template.md` (byte-equal mirror). The genesis §17 in `docs/init/seed.md` SHALL remain present, with `src/seed.template.md` mirroring it. The audit verifies both citations and reports `CLAUDE.md missing Article XI citation` or `seed.md missing §17 citation` on absence.
|
|
283
284
|
5. **Out-of-scope skills don't break the audit.** Any skill on disk that doesn't declare `owner: baseline` is out-of-scope: excluded from the baseline count, the names-match check, and the hash-drift check. Installing the baseline into a project that already has its own skills is zero-friction — no per-file annotation required. Maintenance of those skills is the user's responsibility.
|
|
284
285
|
|
|
@@ -292,7 +293,7 @@ Cryptographic supply-chain attestation, signed lock files, and per-skill aggrega
|
|
|
292
293
|
|---|---|
|
|
293
294
|
| `.claude/hooks/` | 22 hook scripts (17 write/run-boundary + 4 lifecycle + 1 input-boundary). Bash + python3, no jq. |
|
|
294
295
|
| `.claude/agents/` | 1 baseline subagent: `swarm-worker` (rendered from `src/agents/swarm-worker.template.md`) |
|
|
295
|
-
| `.claude/skills/` |
|
|
296
|
+
| `.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
297
|
| `.claude/commands/` | 5 consent/bootstrap gates: `approve-spec`, `approve-swarm`, `grant-commit`, `grant-push`, `init-project` |
|
|
297
298
|
| `.claude/memory/` | 7 canonical knowledge files + `_pending.md` (staging) + `_resume.md` (continuity snapshot) + `README.md` |
|
|
298
299
|
| `.claude/project.json` | per-project config (test/lint cmd, TDD globs, destructive patterns, swarm config, additions). Populated by `/init-project`. |
|
|
@@ -307,8 +308,8 @@ Cryptographic supply-chain attestation, signed lock files, and per-skill aggrega
|
|
|
307
308
|
**Artifact drafting (4)** — each ships a `template.md`:
|
|
308
309
|
- `intake` (Phase 1), `brd` (cross-functional pre-spec), `spec` (Phase 4, diagram-driven), `rca` (out-of-band postmortem)
|
|
309
310
|
|
|
310
|
-
**Workflow phases (
|
|
311
|
-
- `triage`, `scout`, `research`, `tdd`, `simplify`, `security`, `integrate`, `document`, `archive`, `commit`
|
|
311
|
+
**Workflow phases (11)** — auto-invocable; orchestrator chains them:
|
|
312
|
+
- `triage`, `scout`, `research`, `tdd`, `simplify`, `security`, `integrate`, `document`, `archive`, `changelog` (Phase 11.5), `commit`
|
|
312
313
|
|
|
313
314
|
**Phase workers (5)** — execute pre-decided recipes; mandatorily invoke a sub-skill:
|
|
314
315
|
- `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
|
|
|
@@ -516,7 +516,7 @@ Seed-level requirement: no stale workflow artifacts in the working tree after co
|
|
|
516
516
|
|
|
517
517
|
**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
518
|
|
|
519
|
-
**Step 5:** Write `.claude/skills/` for the
|
|
519
|
+
**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
520
|
|
|
521
521
|
**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
522
|
|
|
@@ -591,9 +591,9 @@ Until `/init-project` runs, this section stays empty. Once populated, every fiel
|
|
|
591
591
|
|
|
592
592
|
## §17 — Skill provenance and the baseline manifest
|
|
593
593
|
|
|
594
|
-
A skill at `.claude/skills/<slug>/SKILL.md` is **baseline-owned** iff its YAML frontmatter declares `owner: baseline`. Baseline-owned skills are those that ship with the baseline; every other skill on disk — those without an `owner:` field, or those declaring `owner: user` — is user/third-party and out-of-scope of baseline audit checks. Absence-of-`owner` is the deliberate default so a project that already has its own skills can install the baseline without annotating any of those files. The build script `scripts/build-manifest.mjs` reads each `owner:` value at release time and emits the canonical baseline-skill set into `obj/template/manifest.json` under `owners.skills` (a JSON object mapping slug → `"baseline"`). The
|
|
594
|
+
A skill at `.claude/skills/<slug>/SKILL.md` is **baseline-owned** iff its YAML frontmatter declares `owner: baseline`. Baseline-owned skills are those that ship with the baseline; every other skill on disk — those without an `owner:` field, or those declaring `owner: user` — is user/third-party and out-of-scope of baseline audit checks. Absence-of-`owner` is the deliberate default so a project that already has its own skills can install the baseline without annotating any of those files. The build script `scripts/build-manifest.mjs` reads each `owner:` value at release time and emits the canonical baseline-skill set into the shipped manifest at `obj/template/.claude/manifest.json` under `owners.skills` (a JSON object mapping slug → `"baseline"`). The recursive install copies the manifest into the consumer target at `<target>/.claude/manifest.json` (same in-tree path, no special-case). The CLI separately writes `<target>/.claude/.baseline-manifest.json` post-install on `freshInstall`/`forceInstall`/`merge` — that file is the runtime snapshot of the target's actual on-disk hashes, consumed by `doctor` and `upgrade`. The two files coexist by design: the shipped manifest is frozen at release time and carries `owners.skills`; the runtime manifest is generated at install time and is hash-only.
|
|
595
595
|
|
|
596
|
-
The audit at `.claude/skills/audit-baseline/audit.sh` consumes `manifest.owners.skills` as the canonical baseline-skill enumeration (replacing the previous hard-coded `EXPECTED_SKILLS` set). For every baseline-owned skill, the audit re-derives sha256 hashes from `manifest.files` and compares against on-disk content; a mismatch is reported as `hash mismatch at <path>` against the named slug. A baseline skill present in the manifest but absent from disk is reported as `baseline skill missing`. A SKILL.md whose `owner:` field is present but carries an invalid value (anything other than `baseline` or `user`) is reported as `invalid owner=<value>`. SKILL.md files without an `owner:` field are treated as user/third-party and silently skipped — they are excluded from the baseline count, the names-match check, and the hash-drift check, so installing the baseline into a project that already has its own skills never breaks the audit.
|
|
596
|
+
The audit at `.claude/skills/audit-baseline/audit.sh` consumes `manifest.owners.skills` as the canonical baseline-skill enumeration (replacing the previous hard-coded `EXPECTED_SKILLS` set). It reads the manifest from `<root>/.claude/manifest.json` first (consumer projects) and falls back to `<root>/obj/template/.claude/manifest.json` (the baseline dev repo where `npm run build` writes the manifest). For every baseline-owned skill, the audit re-derives sha256 hashes from `manifest.files` and compares against on-disk content; a mismatch is reported as `hash mismatch at <path>` against the named slug. A baseline skill present in the manifest but absent from disk is reported as `baseline skill missing`. A SKILL.md whose `owner:` field is present but carries an invalid value (anything other than `baseline` or `user`) is reported as `invalid owner=<value>`. SKILL.md files without an `owner:` field are treated as user/third-party and silently skipped — they are excluded from the baseline count, the names-match check, and the hash-drift check, so installing the baseline into a project that already has its own skills never breaks the audit.
|
|
597
597
|
|
|
598
598
|
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
599
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friedbotstudio/create-baseline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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": {
|
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
|
|
|
@@ -272,13 +273,13 @@ The vendored `impeccable` skill stays untouched (Article IX). `design-ui` is the
|
|
|
272
273
|
|
|
273
274
|
## Article XI — Skill provenance and the baseline manifest
|
|
274
275
|
|
|
275
|
-
A skill at `.claude/skills/<slug>/SKILL.md` is **baseline-owned** iff its YAML frontmatter declares `owner: baseline`. Every other skill on disk — those without an `owner:` field, or those declaring `owner: user` — is user/third-party and out-of-scope of baseline audit checks. Absence-of-`owner` is the deliberate default so a project with pre-existing skills can install the baseline without annotating any of its own files. The build script `scripts/build-manifest.mjs` reads each `owner:` value and emits the canonical baseline-skill set into `obj/template/manifest.json` under `owners.skills` (a JSON object mapping slug → `"baseline"`). The
|
|
276
|
+
A skill at `.claude/skills/<slug>/SKILL.md` is **baseline-owned** iff its YAML frontmatter declares `owner: baseline`. Every other skill on disk — those without an `owner:` field, or those declaring `owner: user` — is user/third-party and out-of-scope of baseline audit checks. Absence-of-`owner` is the deliberate default so a project with pre-existing skills can install the baseline without annotating any of its own files. The build script `scripts/build-manifest.mjs` reads each `owner:` value and emits the canonical baseline-skill set into the shipped manifest at `obj/template/.claude/manifest.json` under `owners.skills` (a JSON object mapping slug → `"baseline"`). The recursive install copies the manifest straight to `<target>/.claude/manifest.json` (same path inside the `.claude/` subtree, no special-case). The CLI separately writes `<target>/.claude/.baseline-manifest.json` post-install as a runtime sha256 table of the target's actual on-disk contents (used by `doctor` and `upgrade`). The audit at `.claude/skills/audit-baseline/audit.sh` consumes `manifest.owners.skills` from the shipped `.claude/manifest.json` as the canonical baseline-skill enumeration — the previous hard-coded `EXPECTED_SKILLS` set is removed.
|
|
276
277
|
|
|
277
278
|
You SHALL:
|
|
278
279
|
|
|
279
280
|
1. **Declare baseline ownership only.** A SKILL.md that ships in the baseline SHALL declare `owner: baseline` in its frontmatter directly after `name:`. Authoring a user/third-party skill does NOT require any `owner:` annotation — absence is the default. Explicit `owner: user` is permitted but never required. The only frontmatter-related FAIL the audit emits is `invalid owner=<value>` (a present-but-malformed `owner:` field, e.g. typo). Missing-`owner:` is silently skipped.
|
|
280
|
-
2. **Trust the manifest.** The shipped `obj/template/manifest.json` (
|
|
281
|
-
3. **Re-derive on drift.** The audit re-derives sha256 hashes from `manifest.files` for every path under `.claude/skills/<slug>/` whose slug appears in `owners.skills`, and compares against on-disk content. Mismatches surface as `hash mismatch at <path>`. A baseline-listed slug missing from disk surfaces as `baseline skill missing`. These are hard FAIL — drift detection has no opt-out.
|
|
281
|
+
2. **Trust the manifest.** The shipped manifest at `obj/template/.claude/manifest.json` (delivered to `<target>/.claude/manifest.json` by the recursive install copy) is the canonical record of baseline-owned skills and their content hashes. The runtime `<target>/.claude/.baseline-manifest.json` written by the CLI post-install is a separate file that captures the target's actual on-disk hashes for `doctor`/`upgrade` — do not conflate the two. You SHALL NOT maintain a separate hard-coded list of baseline-skill slugs anywhere in the codebase.
|
|
282
|
+
3. **Re-derive on drift.** The audit reads the manifest from `<root>/.claude/manifest.json` (consumer projects) with a fallback to `<root>/obj/template/.claude/manifest.json` (the baseline dev repo). It re-derives sha256 hashes from `manifest.files` for every path under `.claude/skills/<slug>/` whose slug appears in `owners.skills`, and compares against on-disk content. Mismatches surface as `hash mismatch at <path>`. A baseline-listed slug missing from disk surfaces as `baseline skill missing`. These are hard FAIL — drift detection has no opt-out.
|
|
282
283
|
4. **Preserve constitutional citation.** This Article XI SHALL remain in CLAUDE.md AND in `src/CLAUDE.template.md` (byte-equal mirror). The genesis §17 in `docs/init/seed.md` SHALL remain present, with `src/seed.template.md` mirroring it. The audit verifies both citations and reports `CLAUDE.md missing Article XI citation` or `seed.md missing §17 citation` on absence.
|
|
283
284
|
5. **Out-of-scope skills don't break the audit.** Any skill on disk that doesn't declare `owner: baseline` is out-of-scope: excluded from the baseline count, the names-match check, and the hash-drift check. Installing the baseline into a project that already has its own skills is zero-friction — no per-file annotation required. Maintenance of those skills is the user's responsibility.
|
|
284
285
|
|
|
@@ -292,7 +293,7 @@ Cryptographic supply-chain attestation, signed lock files, and per-skill aggrega
|
|
|
292
293
|
|---|---|
|
|
293
294
|
| `.claude/hooks/` | 22 hook scripts (17 write/run-boundary + 4 lifecycle + 1 input-boundary). Bash + python3, no jq. |
|
|
294
295
|
| `.claude/agents/` | 1 baseline subagent: `swarm-worker` (rendered from `src/agents/swarm-worker.template.md`) |
|
|
295
|
-
| `.claude/skills/` |
|
|
296
|
+
| `.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
297
|
| `.claude/commands/` | 5 consent/bootstrap gates: `approve-spec`, `approve-swarm`, `grant-commit`, `grant-push`, `init-project` |
|
|
297
298
|
| `.claude/memory/` | 7 canonical knowledge files + `_pending.md` (staging) + `_resume.md` (continuity snapshot) + `README.md` |
|
|
298
299
|
| `.claude/project.json` | per-project config (test/lint cmd, TDD globs, destructive patterns, swarm config, additions). Populated by `/init-project`. |
|
|
@@ -307,8 +308,8 @@ Cryptographic supply-chain attestation, signed lock files, and per-skill aggrega
|
|
|
307
308
|
**Artifact drafting (4)** — each ships a `template.md`:
|
|
308
309
|
- `intake` (Phase 1), `brd` (cross-functional pre-spec), `spec` (Phase 4, diagram-driven), `rca` (out-of-band postmortem)
|
|
309
310
|
|
|
310
|
-
**Workflow phases (
|
|
311
|
-
- `triage`, `scout`, `research`, `tdd`, `simplify`, `security`, `integrate`, `document`, `archive`, `commit`
|
|
311
|
+
**Workflow phases (11)** — auto-invocable; orchestrator chains them:
|
|
312
|
+
- `triage`, `scout`, `research`, `tdd`, `simplify`, `security`, `integrate`, `document`, `archive`, `changelog` (Phase 11.5), `commit`
|
|
312
313
|
|
|
313
314
|
**Phase workers (5)** — execute pre-decided recipes; mandatorily invoke a sub-skill:
|
|
314
315
|
- `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
|
@@ -12,15 +12,13 @@ const NPMRC_TEMPLATE_PATH = join(PACKAGE_ROOT, 'src/.npmrc.template');
|
|
|
12
12
|
|
|
13
13
|
export const NEVER_TOUCH = Object.freeze(['.claude/project.json']);
|
|
14
14
|
export const SPECIAL_MERGE = Object.freeze(['.mcp.json']);
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// `
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
// see what shipped, but exclude it from the fresh/force install copy.
|
|
23
|
-
export const COPY_EXCLUDE = Object.freeze(['manifest.json']);
|
|
15
|
+
// The shipped manifest now lives at `.claude/manifest.json` (inside the
|
|
16
|
+
// template's .claude/ subtree), so the recursive cp drops it at the correct
|
|
17
|
+
// consumer path without any special-case filtering. The consumer-side audit
|
|
18
|
+
// (`.claude/skills/audit-baseline/audit.sh`) reads it from there for
|
|
19
|
+
// hash-drift detection. COPY_EXCLUDE stays as a list (currently empty) so
|
|
20
|
+
// future never-copy artifacts can be added without API churn at the callers.
|
|
21
|
+
export const COPY_EXCLUDE = Object.freeze([]);
|
|
24
22
|
|
|
25
23
|
async function listFiles(root, base = root, acc = []) {
|
|
26
24
|
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
@@ -34,14 +32,43 @@ async function listFiles(root, base = root, acc = []) {
|
|
|
34
32
|
return acc;
|
|
35
33
|
}
|
|
36
34
|
|
|
35
|
+
async function readPackageVersion() {
|
|
36
|
+
try {
|
|
37
|
+
const pkgPath = join(PACKAGE_ROOT, 'package.json');
|
|
38
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'));
|
|
39
|
+
return typeof pkg.version === 'string' && pkg.version.length > 0 ? pkg.version : '0.0.0';
|
|
40
|
+
} catch {
|
|
41
|
+
return '0.0.0';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
async function writeBaselineManifest(target) {
|
|
38
46
|
const files = await listFiles(target);
|
|
39
|
-
const filtered = files.filter((p) =>
|
|
40
|
-
|
|
47
|
+
const filtered = files.filter((p) =>
|
|
48
|
+
p !== '.claude/.baseline-manifest.json' && !p.startsWith('.claude/.baseline-prior/')
|
|
49
|
+
);
|
|
50
|
+
const baseline_version = await readPackageVersion();
|
|
51
|
+
const m = await buildManifestFromDir(target, filtered, { baseline_version });
|
|
41
52
|
await mkdir(join(target, '.claude'), { recursive: true });
|
|
42
53
|
await saveManifest(join(target, '.claude/.baseline-manifest.json'), m);
|
|
43
54
|
}
|
|
44
55
|
|
|
56
|
+
async function writeBaselinePriorMirror(templateDir, target) {
|
|
57
|
+
const priorRoot = join(target, '.claude/.baseline-prior');
|
|
58
|
+
await mkdir(priorRoot, { recursive: true });
|
|
59
|
+
await cp(templateDir, priorRoot, {
|
|
60
|
+
recursive: true,
|
|
61
|
+
force: true,
|
|
62
|
+
filter: (src) => {
|
|
63
|
+
const rel = relative(templateDir, src).split(sep).join('/');
|
|
64
|
+
if (rel === '') return true;
|
|
65
|
+
if (COPY_EXCLUDE.includes(rel)) return false;
|
|
66
|
+
return true;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
await writeFile(join(priorRoot, '.gitignore'), '*\n');
|
|
70
|
+
}
|
|
71
|
+
|
|
45
72
|
function makeFilter(opts) {
|
|
46
73
|
return (src, _dest) => {
|
|
47
74
|
const rel = relative(opts.templateRoot, src).split(sep).join('/');
|
|
@@ -91,6 +118,7 @@ export async function freshInstall(templateDir, target, opts = {}) {
|
|
|
91
118
|
await cp(templateDir, target, { recursive: true, force: false, filter });
|
|
92
119
|
await applySpecialAndNeverTouch(templateDir, target);
|
|
93
120
|
if (opts.withNpmrc === true) await materializeNpmrc(target);
|
|
121
|
+
await writeBaselinePriorMirror(templateDir, target);
|
|
94
122
|
await writeBaselineManifest(target);
|
|
95
123
|
}
|
|
96
124
|
|
|
@@ -99,5 +127,6 @@ export async function forceInstall(templateDir, target, opts = {}) {
|
|
|
99
127
|
await cp(templateDir, target, { recursive: true, force: true, filter });
|
|
100
128
|
await applySpecialAndNeverTouch(templateDir, target);
|
|
101
129
|
if (opts.withNpmrc === true) await materializeNpmrc(target);
|
|
130
|
+
await writeBaselinePriorMirror(templateDir, target);
|
|
102
131
|
await writeBaselineManifest(target);
|
|
103
132
|
}
|
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
|
}
|