Skip to content

Self-managing setup: wiring manifest, doctor, session autocheck - #127

Merged
emp3thy merged 21 commits into
mainfrom
feat/self-managing-setup
Aug 31, 2026
Merged

Self-managing setup: wiring manifest, doctor, session autocheck#127
emp3thy merged 21 commits into
mainfrom
feat/self-managing-setup

Conversation

@emp3thy

@emp3thy emp3thy commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Summary

better-memory now owns its entire Claude Code wiring. One declarative manifest drives install, drift detection, and automatic repair — no manual setup steps remain.

  • better_memory/setup/: manifest.py (declarative managed surface: 8 hooks, env, MCP entry, CLAUDE.md managed block, 3 skills), engine.py (render/inspect/diff/apply with timestamped backups, atomic writes, apply lock, re-read+retry on ~/.claude.json, legacy-section absorption/dedup for CLAUDE.md, Windows junction fallback for skill links), autocheck.py (session-start drift check with mtime+fingerprint cache, warnings-never-cached rule, BETTER_MEMORY_WIRING_AUTOCHECK kill switch), repo_hook.py (worktree-safe per-repo post-commit installer honoring core.hooksPath, self-re-pointing after repo moves).
  • New hook modules pre_compact, commit_checkpoint, stop_sweep replace the loose script and echo hooks.
  • CLI: better-memory setup and better-memory doctor [--fix] [--json]; install_hooks reduced to a deprecation shim.
  • Bootstrap scripts: scripts/setup.ps1 (installs uv if missing), scripts/setup.sh slimmed to three steps; Ollama removed from the setup path.
  • Golden parity tests pin the manifest against privacy-sanitized fixtures captured from the reference machine.
  • Docs: README, website (configuration/architecture/index), and hooks-setup.md rewritten for the managed flow; interactive and scripted paths documented separately.

Live migration executed and verified on the reference machine: doctor reports wiring clean; duplicated CLAUDE.md healed (224 -> 121 lines).

Test plan

  • Full suite: 1804 passed, 30 skipped.
  • Golden parity: manifest reproduces the reference machine wiring with zero semantic diff.
  • Live verification transcript in the task reports (setup -> doctor --fix -> doctor clean).

Follow-ups (tracked, not in this PR)

  • Unmocked subprocess e2e of better-memory setup under an isolated HOME; rewrite or delete the skip-marked test_setup_sh.py.
  • RATE_MEMORIES session-id mismatch fix (directive to print its session id; rating tools to accept it).
  • Pre-existing E501 in tests/hooks/test_post_commit.py:169.

🤖 Generated with Claude Code

emp3thy and others added 21 commits August 30, 2026 19:45
Approach A: one declarative wiring manifest drives install, drift
detection, and auto-repair across ~/.claude/settings.json, ~/.claude.json,
CLAUDE.md managed block, skills, and per-repo post-commit hooks.
Windows-first installer, ollama removed from setup path, backend-agnostic
wiring, ralph local covered by inheritance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Repo-move heal via one doctor command, core.hooksPath-aware post-commit
install, uv bootstrap in setup.ps1; verified-safe and minor/accepted
assumption buckets carried into the spec.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ten TDD tasks: manifest, three new hook modules, engine
(render/inspect/diff/apply), golden parity fixture, per-repo post-commit
installer, CLI setup+doctor, bootstrap autocheck with kill switch,
bootstrap scripts, docs sweep. Per-task confidence 90-95% with embedded
mitigations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pt gaps

Task 8: never cache the wiring fingerprint when apply() returns warnings,
otherwise persistent drift is reported once then silenced. Task 5: repo is
public, so parity fixtures are trimmed to the managed subset and
username-sanitized. Task 4: apply() takes home and creates backup/lock
dirs itself. Task 9: setup.ps1 cds to the repo root. Confidence now 92-95%
across all tasks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eError

pre_compact.py and commit_checkpoint.py did not validate that parsed JSON was
a dict before calling .get() methods. Valid-but-non-dict JSON (null, [], 42)
raised uncaught AttributeError and violated "hooks never raise" constraint.

Also added isinstance() check in commit_checkpoint for tool_input not being a
dict, preventing {"tool_input": null} from raising on second .get() call.

Changes:
- pre_compact.py: coerce non-dict parsed JSON to {}
- commit_checkpoint.py: coerce non-dict parsed JSON and tool_input to {}
- test_pre_compact.py: added tests for null and array JSON
- test_commit_checkpoint.py: added tests for null, array, and null tool_input

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port merge_settings_json and merge_claude_json from cli/install_hooks.py
into better_memory/setup/engine.py, generalized to all 8 MANAGED_HOOKS
specs (incl. if_filter groups) and extended with env merge. Adds
render/patch_mcp_entry/splice_managed_block/extract_managed_block/
diff/fingerprint — the pure render/inspect/diff half of the
self-managing setup engine; apply() (I/O) lands in a later task.

Adds a narrowly-scoped ruff per-file-ignore (F401) for
tests/setup/test_engine.py: the brief-mandated verbatim test imports
render for API-surface documentation but exercises it only indirectly
via fingerprint().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review findings on engine.py:
1. diff() raised TypeError on valid-but-non-dict settings.json/.claude.json
   content (5, null, [1,2,3], "text") because merge_settings/patch_mcp_entry
   both do dict(existing). _load_json_or_drift now validates isinstance(...,
   dict) and treats non-dict JSON the same as malformed JSON: one drift
   line, no exception.
2. render() and merge_settings()'s ADD pass built the same per-event
   matcher-group structure twice. Factored into _canonical_hook_groups(),
   called from both, so they cannot drift apart from each other.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the I/O half of better_memory/setup/engine.py: apply() writes the
managed subset of settings.json, .claude.json, CLAUDE.md, and skill
symlinks, backing up any existing file before each write. Serializes
concurrent apply() calls via a lock file (60s stale timeout) and retries
once if ~/.claude.json is rewritten by Claude Code itself between the
pre-write read and the atomic write (spec concern 3). Malformed JSON in
a target file produces a warning and leaves that file untouched.

_backup/_atomic_write are duplicated from cli/install_hooks.py rather
than imported, since that module becomes a shim over this engine in a
later task and must not become a dependency of it. install_skills()
ports install_skill_symlinks, parameterized by target dir + repo root
and extended to loop MANAGED_SKILLS.

Also vendors the start-better-memory-ui skill into .claude/skills/ (it
previously lived only in ~/.claude/skills/) and rewrites its
machine-absolute paths to repo-relative wording so it can serve as the
symlink source alongside the other two managed skills.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve hooks directory via git rev-parse --git-path for worktrees,
which store .git as a file (gitdir:) not a directory. Maintains
core.hooksPath precedence and handles bare repos (resolve to None).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds `better-memory setup`/`doctor` subcommands (better_memory/cli/setup_cmd.py)
wired into the main CLI dispatcher, and guts cli/install_hooks.py into a thin
deprecation shim that delegates to the setup engine (Tasks 3-4 already ported
its merge/backup/symlink logic there).

install_hooks fallout: deleted unit tests exercising removed internals
(tests/cli/test_install_hooks.py, test_install_skill_symlink.py) and the e2e
subprocess suite for the old flag-driven CLI contract
(tests/e2e/test_install_hooks.py) - their merge-logic coverage already lives
in tests/setup/test_engine*.py. Ported the previously-uncovered symlink
replacement-ladder/OSError-fallback/junction-recognition scenarios into
tests/setup/test_engine_apply.py against engine.install_skills() directly.
tests/e2e/test_setup_sh.py is skip-marked (not deleted): scripts/setup.sh is
unchanged here and still drives the old install_hooks flag contract until a
later task migrates it to call `better-memory setup` directly.
tests/e2e_meta/test_canary_home.py's REDUCED_DEFAULT scope updated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… migration

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… skills

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…steps

README/website/hooks-setup now describe the two bootstrap scripts, the
setup/doctor CLI (flags copied from cli/setup_cmd.py's argparse), the
BETTER_MEMORY_WIRING_AUTOCHECK env var, the new state/install-backups
layout, and the automatic per-repo post-commit install. Removes the
retired CLAUDE.md drift sentinel and the manual-paste/install_hooks
framing; corrects a pre-existing async/pythonw mismatch in the hook
registration reference JSON.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The manual-setup pythonw.exe generalization was removed in the prior
commit but survived in Troubleshooting's "Windows console flashes"
entry, which would break stdout-dependent hooks (all but observer) if
followed literally. Scopes the advice to observer only and points at
`better-memory doctor --fix` to restore correct interpreter
assignments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…it hooks, record autocheck failures, guard junction fallback

Final-review fix wave for feat/self-managing-setup:

- cli/setup_cmd.py: handle_setup/handle_doctor now resolve home via
  _common.resolve_home() before calling detect_machine_params(), fixing
  a split-brain where setup/doctor wrote wiring under the real home
  while hooks operated against a custom BETTER_MEMORY_HOME.
- setup/repo_hook.py: ensure_post_commit now re-points the managed
  invocation line when it drifts from the current machine params (e.g.
  after a repo/venv move), instead of treating any sentinel match as
  permanently installed and silently invoking a dead interpreter
  forever.
- hooks/session_bootstrap.py: a failing wiring autocheck is now
  recorded via record_hook_error(hook_name="wiring_autocheck", ...)
  instead of vanishing into a bare except-pass, so persistent drift
  failures leave a diagnostic trail.
- setup/engine.py: the Windows junction-fallback subprocess.run call
  is now guarded against SubprocessError/OSError (e.g. TimeoutExpired)
  so it degrades to a warning instead of crashing apply().
- docs/hooks-setup.md: moved the commit_checkpoint example's "if" key
  from the matcher group onto the hook entry, matching what
  manifest.py::hook_entry actually renders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Claude BugBot Analysis

Found 1 potential bug in this PR.

medium: 1

One confirmed bug: the session-level wiring autocheck's mtime+fingerprint cache omits the skills directory, so it silently fails to detect and repair a deleted/broken skill symlink after the first successful run, even though the full engine.diff() check (used by better-memory doctor) correctly flags it.

Comment thread better_memory/setup/autocheck.py
@emp3thy
emp3thy merged commit da8e01f into main Aug 31, 2026
3 checks passed
@emp3thy
emp3thy deleted the feat/self-managing-setup branch August 31, 2026 12:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant