Step 5 was binary on upgrade (append whole template / leave alone), so projects bootstrapped before v1.2.0 silently missed new canonical triggers (`check across all projects`, `we're on Windows`) on re-run. Now upgrade reads existing CLAUDE.md, substring-diffs vs template, preserves a deliberately-pinned platform line, and appends only missing lines after explicit confirm. Re-runs are no-ops. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.2 KiB
title, type, updated
| title | type | updated |
|---|---|---|
| project-bootstrap@1.3.0 — idempotent CLAUDE.md merge on upgrade | concept | 2026-04-30 |
project-bootstrap@1.3.0 — idempotent CLAUDE.md merge on upgrade
2026-04-30.
Problem
Step 5 of project-bootstrap (≤1.2.0) treated CLAUDE.md as binary:
- File missing → write template.
- File exists → ask "append the whole template, or leave as is?"
Both branches fail the upgrade case. Append-the-whole-template duplicates trigger lines; leave-as-is leaves the file behind whenever the canonical template gains new lines. Concretely: the v1.2.0 release added check across all projects (→ using-projects-meta) to the template — but every project bootstrapped before v1.2.0 silently kept its old CLAUDE.md and never got the new trigger when re-run on the upgrade path. Same gap for we're on Windows (→ active-platform).
Decision
Step 5 in v1.3.0 splits along the existing fork:
- Init (file missing) — unchanged. Write the template, substitute the platform line on non-Windows hosts.
- Upgrade (file exists) — read existing → diff against template → confirm → append-only-missing.
Diff rules:
- Trigger lines (everything except the platform line): present iff any existing line, after
trim+tolower, contains the template line's text. Substring match, not equality — tolerates user rewording or trailing punctuation. Avoids false-positive duplicates while preserving canonical wording for missing ones. - Platform line: present iff any existing line matches
we're on (windows|linux|macos)case-insensitively. If the user pinned a different platform on purpose, leave it. Only append the host-appropriate platform line when none of the three is present.
After diff: if zero missing lines → no-op. Otherwise show the diff to the user, wait for explicit confirm, then append (don't rewrite — file order preserved). Re-runs become no-ops once canon is reached.
Why merge, not overwrite
User-edited CLAUDE.md often carries project-specific lines beyond the template (custom triggers, comments, ordering preferences). Overwrite would clobber them; equality-match would miss substring rephrasings. Substring + append-only is the minimum-violence reconcile that keeps canon current without trampling user intent. The confirmation gate stays — append is still a write.
Composition with [bootstrap-recommend-projects-meta]
[bootstrap-recommend-projects-meta] (⚪ Ready, Step 5.7 future work) covers the machine-level dependency: detect missing projects-meta-mcp MCP server and recommend setup-projects-meta. This task covers the project-level trigger merge. They compose cleanly:
- 5.7 makes the trigger functional on the host.
- 1.3.0 ensures the trigger is actually present in the project's CLAUDE.md.
Either alone is partial; together they close the loop for the check across all projects line specifically and for any future canonical trigger generally.
Pattern: idempotent reconcile of canonical config
Same pattern other setup-skills can borrow when they own a canonical file shape on disk: read existing → diff against canon → confirm → append-only-missing. Avoids the binary "create from template / leave alone" trap that's good for greenfield and bad for upgrade.