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>
50 lines
3.2 KiB
Markdown
50 lines
3.2 KiB
Markdown
---
|
|
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.
|