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

@@ -1,6 +1,14 @@
# Task Board # Task Board
_Updated: 2026-04-30_ _Updated: 2026-04-30_
## 🟢 [bootstrap-claude-md-merge] — project-bootstrap upgrade: idempotent merge of canonical CLAUDE.md triggers
**Status:** done
**Where I stopped:** Step 5 in `skills/project-bootstrap/SKILL.md` split into Init / Upgrade-merge — upgrade reads existing CLAUDE.md, substring-diffs vs template (case-insensitive trim), platform line preserved if user pinned a non-host platform, asks confirm with diff, appends only missing canonical lines; version bump 1.2.0 → 1.3.0; skill row in `skills/project-bootstrap/README.md` updated to note idempotent merge; root `README.md` + `README.ru.md` upgrade-mode paragraph extended; reinstalled to `~/.claude/skills/project-bootstrap/`, `dist/project-bootstrap.skill` rebuilt; concept page `.wiki/concepts/bootstrap-claude-md-merge.md` written; index + log entries added
**Next action:** (none — kept until merged)
**Branch:** master
---
<!-- <!--
Canonical layout. One block per task. Per-task deep context lives in Canonical layout. One block per task. Per-task deep context lives in
.tasks/<task-slug>.md (created by using-tasks when a task becomes active .tasks/<task-slug>.md (created by using-tasks when a task becomes active

View File

@@ -0,0 +1,53 @@
# bootstrap-claude-md-merge
## Goal
project-bootstrap upgrade mode currently treats `CLAUDE.md` as binary: either append
the entire template (causing duplicate trigger lines) or leave it untouched. Result:
projects bootstrapped before v1.2.0 silently miss new canonical triggers like
`check across all projects` (→ `using-projects-meta`) and `we're on Windows`
(→ `active-platform`) when re-run on the upgrade path. Fix: turn Step 5 into an
idempotent merge — read existing CLAUDE.md, diff against the template, append only
missing canonical lines after user confirm. Re-runs become no-ops.
## Key files
- `skills/project-bootstrap/SKILL.md:220-249` — Step 5 (the broken binary choice)
- `skills/project-bootstrap/assets/CLAUDE.md.template` — canonical trigger set
- `skills/project-bootstrap/README.md` — public surface, mention idempotency
- `dist/project-bootstrap.skill` — rebuild after install
- `~/.claude/skills/project-bootstrap/` — reinstall target
## Decisions log
- 2026-04-30: Diff strategy — substring match per template line against existing file
(trim + lowercase). A trigger is "present" if any existing line contains the template
trigger text. Avoids false-positive duplicates when user has rephrased a trigger but
preserves canonical wording for missing ones. Rejected: full line-equality match
(too brittle — user comments / case differences would re-add).
- 2026-04-30: Confirmation gate stays — show user the diff (N lines to append) before
touching the file. Idempotent run with no missing lines = silent no-op.
- 2026-04-30: Version bump 1.2.0 → 1.3.0. Behavior change visible to users on every
upgrade — minor bump fits semver intent.
## Open questions
- [ ] Should the platform line (`we're on Windows`) be substituted to match the host
during merge, the same way init does it? Decision: yes — if existing file has no
`we're on (Windows|Linux|macOS)` line, append the host-appropriate one. If it has
any of the three, leave it (user may have intentionally pinned a different platform).
## Completed steps
- [x] open task
- [x] patch Step 5 in SKILL.md (read → diff → confirm → append) — split Init / Upgrade-merge
- [x] bump version 1.2.0 → 1.3.0 in SKILL.md frontmatter
- [x] update `skills/project-bootstrap/README.md` `CLAUDE.md` row to note idempotent merge
- [x] update root `README.md` + `README.ru.md` upgrade-mode paragraph
- [x] reinstall + rebuild dist (`bash scripts/install.sh project-bootstrap && bash scripts/build.sh project-bootstrap`); verified `version: 1.3.0` on disk + Step 5 split
- [x] wiki: concept page `.wiki/concepts/bootstrap-claude-md-merge.md`, index entry, log entry
- [x] commit
- [x] STATUS.md: 🔴 → 🟢
- [x] decision on platform line — preserve if user pinned non-host one (case-insensitive `we're on (windows|linux|macos)` match)
## Notes
Existing related task `[bootstrap-recommend-projects-meta]` (Step 5.7) is **machine-level**
detection (recommend `setup-projects-meta` if MCP server not registered). This task is
**project-level** merge of the trigger line into the project's CLAUDE.md. They compose
cleanly: 5.7 makes the trigger functional on the host, this task ensures the trigger
is actually present in the file.

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.

View File

@@ -13,6 +13,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever
## Concepts ## Concepts
- [active-platform-decision.md](concepts/active-platform-decision.md) — why `active-platform` is a skill (not a memory entry); why default = Windows; how it's wired into `project-bootstrap` - [active-platform-decision.md](concepts/active-platform-decision.md) — why `active-platform` is a skill (not a memory entry); why default = Windows; how it's wired into `project-bootstrap`
- [bootstrap-claude-md-merge.md](concepts/bootstrap-claude-md-merge.md) — project-bootstrap@1.3.0 — Step 5 upgrade path becomes idempotent merge (read → diff vs template → confirm → append missing); fixes silent gap where pre-1.2.0 projects never picked up new canonical triggers (`check across all projects`, `we're on Windows`)
- [bootstrap-manifest.md](concepts/bootstrap-manifest.md) — record of which `project-bootstrap` / `setup-wiki` / `setup-tasks` versions initialized this project's `.wiki/` and `.tasks/` layout (overwritten on re-bootstrap; history in git) - [bootstrap-manifest.md](concepts/bootstrap-manifest.md) — record of which `project-bootstrap` / `setup-wiki` / `setup-tasks` versions initialized this project's `.wiki/` and `.tasks/` layout (overwritten on re-bootstrap; history in git)
- [build-notes.md](concepts/build-notes.md) — why `build.ps1` exists alongside `build.sh`; PS 5.1 backslash-in-zip gotcha; how to extract a `.skill` - [build-notes.md](concepts/build-notes.md) — why `build.ps1` exists alongside `build.sh`; PS 5.1 backslash-in-zip gotcha; how to extract a `.skill`
- [install-portability.md](concepts/install-portability.md) — `install.sh` / `build.sh` rewritten to drop `mapfile` (bash 4+) and `find -printf` (GNU only) so stock macOS (bash 3.2 + BSD find) works - [install-portability.md](concepts/install-portability.md) — `install.sh` / `build.sh` rewritten to drop `mapfile` (bash 4+) and `find -printf` (GNU only) so stock macOS (bash 3.2 + BSD find) works

View File

@@ -36,3 +36,4 @@ Parseable: `grep "^## \[" .wiki/log.md | tail -20`.
## [2026-04-30] refactor | project-bootstrap re-run on this repo (upgrade mode) — setup-wiki noop, setup-tasks noop, CLAUDE.md unchanged (matches template), bootstrap-manifest.md written: project-bootstrap@1.1.0 / setup-wiki@1.0.0 / setup-tasks@1.0.0 ## [2026-04-30] refactor | project-bootstrap re-run on this repo (upgrade mode) — setup-wiki noop, setup-tasks noop, CLAUDE.md unchanged (matches template), bootstrap-manifest.md written: project-bootstrap@1.1.0 / setup-wiki@1.0.0 / setup-tasks@1.0.0
## [2026-04-30] decision | project-bootstrap@1.2.0 — CLAUDE.md template gains `check across all projects` (verbatim trigger from using-projects-meta description); installs auto-load cross-project tasks + shared-wiki access in every bootstrapped repo; no Step 5.7 dependency-check mirror — Prerequisites pointer in using-projects-meta is self-correcting; local CLAUDE.md, both READMEs, dist/.skill, projects-meta-skills concept page synced ## [2026-04-30] decision | project-bootstrap@1.2.0 — CLAUDE.md template gains `check across all projects` (verbatim trigger from using-projects-meta description); installs auto-load cross-project tasks + shared-wiki access in every bootstrapped repo; no Step 5.7 dependency-check mirror — Prerequisites pointer in using-projects-meta is self-correcting; local CLAUDE.md, both READMEs, dist/.skill, projects-meta-skills concept page synced
## [2026-04-30] decision | Step 5.7 mirror of Step 5.6 (projects-meta-mcp dependency detector / `setup-projects-meta` recommendation) accepted as future work; tracked as ⚪ Ready task `[bootstrap-recommend-projects-meta]`; deferred until first observed fresh-machine miss so detector signal is informed by real failure mode; concept page `projects-meta-skills.md` updated to reflect new stance ## [2026-04-30] decision | Step 5.7 mirror of Step 5.6 (projects-meta-mcp dependency detector / `setup-projects-meta` recommendation) accepted as future work; tracked as ⚪ Ready task `[bootstrap-recommend-projects-meta]`; deferred until first observed fresh-machine miss so detector signal is informed by real failure mode; concept page `projects-meta-skills.md` updated to reflect new stance
## [2026-04-30] decision | project-bootstrap@1.3.0 — Step 5 upgrade path turned idempotent: read existing CLAUDE.md → substring-diff vs template → confirm → append-only-missing; closes silent gap where pre-1.2.0 projects never picked up new canonical triggers (`check across all projects`, `we're on Windows`); platform line preserved if user pinned a non-host one; concept page `bootstrap-claude-md-merge.md` written; README CLAUDE.md row updated to note idempotent merge

View File

@@ -58,7 +58,10 @@ project's folder and it will, in one pass:
Two modes, picked automatically: **init** for an empty folder, **upgrade** Two modes, picked automatically: **init** for an empty folder, **upgrade**
for an existing project (the skill only fills the gaps and never overwrites for an existing project (the skill only fills the gaps and never overwrites
without explicit confirmation). without explicit confirmation). On upgrade, `CLAUDE.md` is merged
idempotently — only canonical trigger lines that aren't already present are
appended after explicit confirm, so re-running `project-bootstrap` after a
template change picks up the new triggers without duplicating the old ones.
### Edit a skill ### Edit a skill

View File

@@ -44,7 +44,11 @@ bash scripts/install.sh caveman wiki-maintainer
Два режима, выбирается автоматически: **init** для пустой папки и **upgrade** Два режима, выбирается автоматически: **init** для пустой папки и **upgrade**
для существующего проекта (скилл только дозаполняет пробелы и ничего не для существующего проекта (скилл только дозаполняет пробелы и ничего не
перезаписывает без явного подтверждения). перезаписывает без явного подтверждения). В режиме upgrade `CLAUDE.md`
сливается идемпотентно — только канонические триггер-строки, которых ещё
нет в файле, дописываются после явного подтверждения, поэтому повторный
запуск `project-bootstrap` после обновления шаблона подтягивает новые
триггеры, не дублируя старые.
### Отредактировать скилл ### Отредактировать скилл

Binary file not shown.

View File

@@ -44,7 +44,7 @@ projects bootstrapped at different times debuggable.
| `README.md` | minimal stub | Skipped if file exists. | | `README.md` | minimal stub | Skipped if file exists. |
| `.wiki/` | delegated to `setup-wiki` | Karpathy LLM Wiki layout — `CLAUDE.md`, `index.md`, `log.md`, `overview.md`, `raw/`, `entities/`, `concepts/`, `packages/`, `sources/`. | | `.wiki/` | delegated to `setup-wiki` | Karpathy LLM Wiki layout — `CLAUDE.md`, `index.md`, `log.md`, `overview.md`, `raw/`, `entities/`, `concepts/`, `packages/`, `sources/`. |
| `.tasks/` | delegated to `setup-tasks` | Canonical board — `STATUS.md` plus per-task `<task-slug>.md` files. | | `.tasks/` | delegated to `setup-tasks` | Canonical board — `STATUS.md` plus per-task `<task-slug>.md` files. |
| `CLAUDE.md` | `assets/CLAUDE.md.template` | Skill triggers (`use superpowers`, `use project wiki`, etc.). On non-Windows hosts, swap the `we're on Windows` line for `we're on Linux` / `we're on macOS`. | | `CLAUDE.md` | `assets/CLAUDE.md.template` | Skill triggers (`use superpowers`, `use project wiki`, etc.). On non-Windows hosts, swap the `we're on Windows` line for `we're on Linux` / `we're on macOS`. On upgrade, the template is treated as a canonical set and merged idempotently — only missing trigger lines are appended after user confirm. Re-runs are no-ops. |
| `.wiki/concepts/bootstrap-manifest.md` | generated | Records which skill versions initialized the project, so cross-project layout drift is debuggable. | | `.wiki/concepts/bootstrap-manifest.md` | generated | Records which skill versions initialized the project, so cross-project layout drift is debuggable. |
## Workflow ## Workflow

View File

@@ -1,6 +1,6 @@
--- ---
name: project-bootstrap name: project-bootstrap
version: 1.2.0 version: 1.3.0
description: > description: >
Initializes or upgrades a project in the current folder: git, .gitignore, README.md, Initializes or upgrades a project in the current folder: git, .gitignore, README.md,
.wiki/ using Karpathy's method, .tasks/ for task tracking, CLAUDE.md with skill triggers. .wiki/ using Karpathy's method, .tasks/ for task tracking, CLAUDE.md with skill triggers.
@@ -219,12 +219,41 @@ If `setup-tasks` is not installed, **stop** and tell the user — same rule as S
## Step 5 — CLAUDE.md ## Step 5 — CLAUDE.md
If `CLAUDE.md` already exists — show its contents and ask: Two paths, picked by file presence:
"CLAUDE.md already exists. Append skill triggers to the end, or leave as is?"
If it does not exist — create from template `assets/CLAUDE.md.template`. ### Init (file does not exist)
Create `CLAUDE.md` from `assets/CLAUDE.md.template`. Substitute the platform line
on non-Windows hosts (`we're on Linux` / `we're on macOS` instead of
`we're on Windows`).
### Upgrade (file exists) — idempotent merge
Treat the template as the canonical trigger set and reconcile the existing file
against it. Re-runs are no-ops once the file is in canon.
1. Read the existing `CLAUDE.md`.
2. For each non-empty, non-comment line in the template, decide whether it's
already present:
- **Trigger lines** (everything except the platform line) — present iff any
existing line, after `trim` + `tolower`, contains the template line's
trigger text. Substring match, not equality — tolerates user rewording or
trailing punctuation.
- **Platform line** (`we're on Windows`) — 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 alone**. Only append
the host-appropriate platform line when none of the three is present.
3. Collect missing lines. If none → print `CLAUDE.md already canon — no changes`
and skip to Step 5.5.
4. Show the user the diff (N lines, exact text to append) and ask one question:
"Append these N missing canonical triggers to the end of CLAUDE.md?" Wait
for explicit confirmation before writing.
5. On confirm: append a single newline (if the file doesn't end with one) and
then the missing lines, one per line. Don't rewrite the file — only append.
Don't reorder existing lines. Don't dedupe within the existing file.
Template contents (`assets/CLAUDE.md.template` — source of truth):
Template contents:
```markdown ```markdown
# CLAUDE.md # CLAUDE.md
# Agent instructions. Each line is a trigger for an installed skill. # Agent instructions. Each line is a trigger for an installed skill.