feat(install): bootstrap per-operator CLAUDE.md from .example template#398
feat(install): bootstrap per-operator CLAUDE.md from .example template#398
Conversation
Untrack home/.claude/CLAUDE.md and home/IDENTITY.md, ship a tracked home/.claude/CLAUDE.md.example as the canonical universal-content template, and extend make install to seed home/IDENTITY.md from the example on fresh clones and reconcile the CLAUDE.md symlink on every run. Idempotent: re-running install when both files are already in place is a quiet no-op. Refresh home/.claude/MEMORY.md.example with starter section headings and a brief role description so it works as a seed for new operators or as the migration source when memory is enabled. Refs #396, fixes #397.
Review by KaiThe installed version at Review: PR #398 feat(install): bootstrap per-operator CLAUDE.md from .example templateOverall this is a clean, well-tested change. The three-branch Step 1 logic is correct, idempotency guarantees are sound, and tests cover the key paths. Warning
In the dry_run path the copy of if ws_claude_src.is_dir():
print(f"[DRY RUN] Would copy: {ws_claude_src} -> {ws_claude_dst}")But the real-run path calls Suggestions1. Dry-run warning test missing symlink assertion (
assert not (install / "home" / ".claude" / "CLAUDE.md").exists()The early return prevents symlink creation in both modes, but the dry_run test doesn't verify it. Minor coverage gap. 2. Meta-content placement in The paragraph starting "This file is the bootstrap template. Operators copy it to 3. Step 1 copies No issues found in:
|
- Move bootstrap-template explanation in CLAUDE.md.example out of the "Who You Are" persona section into a dedicated "About This File" section at the top, so operators customizing voice/personality do not encounter file-lifecycle instructions mid-paragraph. - Clarify _bootstrap_home_identity docstring: source IDENTITY.md is the authoritative copy when present; direct edits to the install copy are not protected against overwrite by the source-copy branch. - Pin the early-return contract in the dry-run warning test by also asserting the symlink is not created and no symlink-creation message is logged. Mirrors the assertion already in the non-dry-run counterpart so future regressions cannot leak in.
Review by KaiThis is a clean PR. All three suggestions from the prior review were addressed - the dry-run test now asserts the symlink is absent, the "About This File" meta-content was moved to a top-level section, and the docstring now explicitly states that direct edits to the install copy are not protected. I traced through the One minor note: Suggestion - Nothing else to flag. |
The trailing comment block on _HOME_CLAUDE_EXCLUDES described why history and MEMORY.md remain in the excludes list (stale-file protection after their migration to DATA_DIR), but ambiguously read as if it also applied to CLAUDE.md. CLAUDE.md is excluded for an unrelated reason: its bootstrap path owns the symlink, not because of any DATA_DIR migration. Rephrase the trailing comment to scope it explicitly to the migrated entries and point at the per-entry comment for CLAUDE.md.
Review by KaiThe The rest is clean. Tracing through the three-branch logic in
Symlink reconciliation uses
The One minor observation, not a bug: in branch 2, This is clean. No new issues. |
Address feedback from a parallel review pass. CLAUDE.md.example "Memory System" section: gate the API documentation behind a short preamble that points operators back to the Memory Write Routing rule when their context says memory is disabled. Without the gate, the section described Qdrant, the extraction pass, and the API endpoints as if always available, which is wrong in disabled mode where none of that infrastructure runs. CLAUDE.md.example MEMORY.md description: "for stable identity and rules" inverted the new architecture (rules now live in this file, not in MEMORY.md). Rephrase to mode-neutral: MEMORY.md holds operator notes and project state; in enabled mode the vector store is the active fact surface. install.py _bootstrap_home_identity: emit a positive "already bootstrapped" log line in the steady-state path where the install copy exists, the source has no IDENTITY.md, and the symlink target is already correct. Spec calls for this explicitly; previously the path was silent, which made it impossible to distinguish a successful no-op reinstall from a skipped/broken one. New test pins the full-idempotent path: asserts the "already bootstrapped" line appears and no copy/seed/symlink-create lines do.
Review by KaiThis PR is clean. The three prior review rounds addressed all meaningful issues, and the final state is sound. I traced through the logic independently:
Symlink reconciliation: Uses
Test counts in
No bugs, security issues, or convention violations found. |
Summary
Untrack
home/.claude/CLAUDE.mdandhome/IDENTITY.mdso they become per-operator local files, shiphome/.claude/CLAUDE.md.exampleas the tracked universal-content template, and extendmake installto bootstrap the operator's localhome/IDENTITY.mdfrom the example on a fresh clone (and reconcile thehome/.claude/CLAUDE.mdsymlink) on every run. Re-running install when both files are already in place performs no copy or symlink work and emits a single "already bootstrapped" log line; the symlink reconciliation specifically is a quiet short-circuit when the target is already correct.This is the first piece of #396; subsequent PRs gate the memory subsystem on
MEMORY_ENABLED, drive write routing from a runtime context flag, and migrate existing MEMORY.md content to Qdrant.What changed
home/.claude/CLAUDE.md.example(new, tracked). Universal content only: hard rules (no source modification, noEnterPlanMode, symlink target convention), public-facing content rules, conditional memory write rule (routes new facts to either the API or MEMORY.md based on a runtime[Memory subsystem: ...]line, with explicit handling of the absent-line and absent-block cases), behavioral rules, and the full API reference block (scheduling, send-message, send-file, memory, external services). The Memory System section is gated on enabled mode at the top so disabled-mode operators do not read about API endpoints that are unavailable to them.home/.claude/CLAUDE.mdandhome/IDENTITY.mdremoved from tracking; both kept out via.gitignore(existing unblock-by-negation pattern updated). Operators' local copies persist on disk and are not affected.src/kai/install.py:_bootstrap_home_identity()helper. Picks the best available seed forhome/IDENTITY.md(operator's source copy when present,.exampletemplate otherwise) and reconciles thehome/.claude/CLAUDE.mdsymlink. Returns early with a warning when neither seed is available so the install does not produce a symlink to a nonexistent target. Logs a single "already bootstrapped" line when both files are in place and the symlink target is correct._HOME_CLAUDE_EXCLUDESnow also excludesCLAUDE.mdso the operator's local untracked symlink is not copied during thehome/.claude/tree walk; the bootstrap helper is the single source of truth for the symlink._apply_source()rewired to call the helper for both dry-run and real installs.home/.claude/MEMORY.md.example: refreshed with a brief role description (its file's role shifts: it is now the seed file for new operators on the disabled-mode path, and the migration source on the enabled-mode path) plus starter section headings.Note for existing operators
Existing operators with their own
home/IDENTITY.mdwill keep using it across reinstalls (source-IDENTITY.md wins over.examplein branch 1). That means the new universal baseline shipped here (Memory Write Routing rule, gated Memory System section, refined Public-Facing Content Rules) will not be picked up automatically. To adopt the new baseline, diff your localhome/IDENTITY.mdagainsthome/.claude/CLAUDE.md.exampleand merge in the universal pieces by hand. This is intentional per the broader Phase 2 triage step.Tests
TestBootstrapHomeIdentitycovering: seed-from-example on fresh clone, prefer-source over example when both present, no-op when install copy exists and no source IDENTITY.md, symlink reconciliation (skip when correct, recreate when wrong target, create when missing), dry-run does not mutate the filesystem, warn-and-return-early when no seed is available, and the "already bootstrapped" log line in the full-idempotent path.TestApplySourcetests updated to match the new contract:mock_own.call_countadjusted from 2 to 3 to account for the symlink lchown via_set_ownership; the two "warns when IDENTITY.md missing" tests renamed to assert the new "neither source nor example present" warning behavior.Test plan
make check(ruff check + format)pytest tests/test_install.py::TestBootstrapHomeIdentity tests/test_install.py::TestApplySourcepytestsuitemake installproduces a writablehome/IDENTITY.mdand a workinghome/.claude/CLAUDE.mdsymlink at the install location.make installimmediately after; verify the second run prints the "already bootstrapped" log line and no "Created symlink" / "Bootstrapped" / "Copied" lines for the identity surface.fixes #397
refs #396