feat(project-bootstrap): v1.3.0 — idempotent CLAUDE.md merge on upgrade

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>
This commit is contained in:
2026-04-30 15:06:06 +03:00
parent 0b1b0555d1
commit 68fe9a8c1c
10 changed files with 156 additions and 8 deletions

View File

@@ -0,0 +1,49 @@
---
title: "project-bootstrap@1.3.0 — idempotent CLAUDE.md merge on upgrade"
type: concept
updated: 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.