From 7ede69ec85b53b85da364799a06adcfa3271581e Mon Sep 17 00:00:00 2001 From: jared Date: Mon, 15 Jun 2026 15:23:32 -0400 Subject: [PATCH] Add vault_sync.py hook; complete Step 5b and 2e pilot Step 5b (Obsidian vault -> VPS git sync) is now fully done: the vault is a private Forgejo repo (SecondBrain) and vault_sync.py auto-commits+pushes it on SessionEnd, mirroring memsearch_sync.py. Step 2e pilot done (llf-schema project graph onboarded). Step 2d closed (live vault graph is the baseline). Docs and implementation status updated. Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 11 +++++-- docs/memory-system/04-build-plan.md | 41 +++++++++++++++++------ plugins/memory/hooks/vault_sync.py | 50 +++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 12 deletions(-) create mode 100755 plugins/memory/hooks/vault_sync.py diff --git a/CLAUDE.md b/CLAUDE.md index 5a59d05..10bb310 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,10 +67,12 @@ to those two and fix the stale doc. **Implementation status (2026-06-12):** Memory plugin source moved into git at `cc-os/plugins/memory/` and bash hooks ported to Python (deep-module architecture: shared `config.py`, `hook_io.py`, `session_state.py`; thin entry-point scripts). Cutover via symlink `~/.claude/plugins/memory → cc-os/plugins/memory/` and settings.json hook rewrite. memsearch sync split into dedicated `memsearch_sync.py` SessionEnd hook (relocation of ADR-015 behavior, not reversal). Fresh-session test passed 2026-06-12. See ADR-016. +**Implementation status (2026-06-15):** Step 5b done and automated — vault (`~/Documents/SecondBrain`) initialized as git repo, pushed to private Forgejo (`ssh://git@forgejo.swansoncloud.com:2222/jared/SecondBrain.git`); 52 files, git chosen over Syncthing. Auto-commit+push wired via `vault_sync.py` SessionEnd hook (third SessionEnd hook, runs after `session_end.py` so the daily journal note is included; push-only — SessionStart pull is an optional future item for multi-machine). Step 2e pilot done — llf-schema (`/home/jared/dev/llf-schema`, PHP 8.2 WordPress plugin) onboarded via `memory-project` skill: 605 nodes / 930 edges / 52 communities; Ollama doc pass lossy on WordPress docs but AST pass solid. Step 2d closed — live vault `graphify-out/` is the baseline; fixture-only build superseded. + ## Implemented Components **Global memory plugin** — `cc-os/plugins/memory/` (git-tracked, 2026-06-12); symlinked into `~/.claude/plugins/memory` -- Hooks: `hooks/` — `session_start.py`, `session_context.py` (project graph path only), `post_tool_use_write.py`, `session_end.py` (vault journal), `memsearch_sync.py` (second SessionEnd hook; memsearch auto-commit+push, 30s timeout) +- Hooks: `hooks/` — `session_start.py`, `session_context.py` (project graph path only), `post_tool_use_write.py`, `session_end.py` (vault journal), `memsearch_sync.py` (second SessionEnd hook; memsearch auto-commit+push, 30s timeout), `vault_sync.py` (third SessionEnd hook; vault auto-commit+push to forgejo.swansoncloud.com/jared/SecondBrain, 30s timeout; mirrors memsearch_sync.py) - Shared modules: `config.py` (load_config → frozen Config dataclass), `hook_io.py` (read_input → HookInput dataclass), `session_state.py` (record_touch/read_touches; encapsulates `/tmp/claude-vault-touched-$SESSION_ID` contract) - Skills: `skills/` — memory-vault, memory-write, memory-reorganize, memory-project - Config: `config.yaml` — vault path, Ollama model (qwen25-coder-7b-16k), env vars @@ -89,7 +91,12 @@ to those two and fix the stale doc. - Skills: `/memory-recall`, `/memory-config` (ship with plugin) - Git sync: `~/.memsearch` is a dedicated private Forgejo repo (`forgejo.swansoncloud.com/jared/memsearch`); whitelist `.gitignore` commits only `memory/*.md` (excludes rebuildable `milvus.db`, model, config); auto-commit+push wired into the cc-os memory plugin's own `memsearch_sync.py` SessionEnd hook (relocated from `session-end.sh` by ADR-016, 2026-06-12; behavior preserved — see ADR-015) -**Not yet implemented:** Obsidian vault → VPS sync (Step 5b). Memsearch episodic git sync is done (Step 5a, 2026-06-09). +**Obsidian vault git sync** — `~/Documents/SecondBrain` (2026-06-15) +- Remote: `ssh://git@forgejo.swansoncloud.com:2222/jared/SecondBrain.git` (private Forgejo; web: `https://forgejo.swansoncloud.com/jared/SecondBrain`) +- 52 files tracked (notes, journal, templates, vault-conventions.md, CLAUDE.md, .obsidian config); `graphify-out/` excluded via `.gitignore` +- Auto-commit+push via `vault_sync.py` SessionEnd hook in cc-os memory plugin (mirrors `memsearch_sync.py`; push-only — SessionStart pull is optional future item for multi-machine) + +**Remaining optional items:** SessionStart vault pull (multi-machine sync; push-only is the current design); additional project onboarding (one at a time, per ADR-013); bulk vault migration. All required build steps complete as of 2026-06-15. ## OpenSpec workflow diff --git a/docs/memory-system/04-build-plan.md b/docs/memory-system/04-build-plan.md index a7a91ee..f101204 100644 --- a/docs/memory-system/04-build-plan.md +++ b/docs/memory-system/04-build-plan.md @@ -1,6 +1,6 @@ # Build Plan -_Last updated: 2026-06-12_ +_Last updated: 2026-06-15_ How a human builds this system, step by step, and answers to the operational questions: which scripts and hooks, how the AI knows when to write and what conventions to follow, how and @@ -142,8 +142,10 @@ tools, clients, and domain concepts). **Note (2026-06-08):** The `~/Documents/SecondBrain` vault already has a live `graphify-out/` graph (32 files, built 2026-06-05). The fixture-first approach from ADR-013 may therefore be superseded — the vault graph exists before Step 1 (conventions/fixtures) is formally complete. -Determine whether to treat the existing vault graph as the fixture baseline or run a clean -fixture-only build once Step 1 is done. This is an open question before 2d can be marked done. + +**Resolved (2026-06-15): DONE/superseded.** The live vault `graphify-out/` over the full vault +IS the baseline graph. A fixture-only build is moot now that the vault holds real scaffolding +notes (not just fixtures). No separate fixture-only build will be run. **Full vault migration** (the `~/Documents/SecondBrain` build above run over all notes) is the final step — deferred to after end-to-end validation on the pilot project. Do not bulk-migrate the vault @@ -168,7 +170,13 @@ Keep per-project graphs separate — do not merge client projects into one graph but `cc-os` is a docs-only repo, not a pilot project in the ADR-013 sense. A pilot project with real code has not yet been onboarded. -**Status: NOT STARTED** (pilot project onboard pending) +**Status: PILOT DONE (2026-06-15).** The llf-schema project (`/home/jared/dev/llf-schema`, +PHP 8.2 WordPress plugin) was onboarded via the `memory-project` skill: `graphify extract` +(tree-sitter AST on 90 code files + Ollama doc pass on 9 doc files, ~3.5 min) + `cluster-only` +→ 605 nodes / 930 edges / 52 communities (~524K `graph.json`). `graphify-out/` added to that +repo's `.gitignore`. **Finding:** Ollama doc pass was lossy on structured WordPress docs (101 +warnings, recursive bisection) but the AST pass carried the code structure — acceptable for +code-heavy repos. **Remaining under 2e:** onboard additional projects incrementally. ### Step 3 — Hooks (maintenance + retrieval) **DONE (2026-06-08)** @@ -216,11 +224,24 @@ subshell with trailing `|| true` — cannot fail session shutdown. See **ADR-015 and the cross-client commingling, which is an accepted design choice (single global store across all clients by intent — see ADR-015, resolved 2026-06-09). -#### 5b — Obsidian vault → VPS sync — **NOT STARTED** -- Pick **git** (versioned, hourly push/pull) or **Syncthing** (continuous, zero-thought) for - the vault → VPS. Configure on each machine. -- **Do not sync** the Graphify `graphify-out/` directories, Milvus caches, or the Ollama - models. Graphs are rebuilt per machine from the vault (the single source of truth). +#### 5b — Obsidian vault → VPS sync — **DONE (2026-06-15)** +The vault at `~/Documents/SecondBrain` was initialized as a git repo and pushed to a new +private Forgejo repo (`ssh://git@forgejo.swansoncloud.com:2222/jared/SecondBrain.git`; +web: `https://forgejo.swansoncloud.com/jared/SecondBrain`), mirroring the memsearch sync +pattern. 52 files committed on 2026-06-15 (notes, journal, templates, vault-conventions.md, +CLAUDE.md, .obsidian config); `graphify-out/` correctly excluded via the existing `.gitignore`. +**Decision:** git sync chosen over Syncthing. + +**Auto-sync hook — DONE (2026-06-15):** `vault_sync.py` added as the third SessionEnd hook +(after `session_end.py` and `memsearch_sync.py`, timeout 30). Auto-commits `git add -A` and +pushes the vault to `forgejo.swansoncloud.com/jared/SecondBrain` on every session end, so the +daily journal note from `session_end.py` is included in the commit. Mirrors the +`memsearch_sync.py` pattern; uses `cfg.vault_path`. Smoke-tested (exit 0) 2026-06-15. + +**Remaining (optional):** SessionStart `git pull` for multi-machine freshness — push-only today. + +**Do not sync** the Graphify `graphify-out/` directories, Milvus caches, or the Ollama +models. Graphs are rebuilt per machine from the vault (the single source of truth). ### Step 6 — Package as a global plugin (Part D) **DONE (2026-06-08)** - Wrap Steps 2–3 into a Claude Code plugin with skills; install at user level. @@ -358,7 +379,7 @@ global install keeps conventions a single source of truth. 1. **Vault location** — Settled by ADR-012: `~/Documents/SecondBrain`. Symlink into `~/.claude/memory` only if a tool requires it. -2. **Sync mechanism** — git (versioned, hourly) vs Syncthing (continuous)? +2. **Sync mechanism** — ~~git (versioned, hourly) vs Syncthing (continuous)?~~ **RESOLVED (2026-06-15): git.** 3. **Stale rebuild threshold** — how many days before SessionStart triggers `--force`? 7 days is the starting guess; tune after observing drift in practice. 4. **Per-project graph path** — how does the AI know which `graph.json` to pass to diff --git a/plugins/memory/hooks/vault_sync.py b/plugins/memory/hooks/vault_sync.py new file mode 100755 index 0000000..3ef1b34 --- /dev/null +++ b/plugins/memory/hooks/vault_sync.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""SessionEnd hook — auto-commit + push the SecondBrain vault. + +Step 5b automation: commit vault content and push to +forgejo.swansoncloud.com/jared/SecondBrain. Mirrors memsearch_sync.py. +Must run AFTER session_end.py so the daily journal note that hook writes is +included in the commit. The disposable graphify-out/ index is excluded via the +vault's .gitignore. Fail-open; must never disrupt session shutdown. +""" +import os +import sys +import subprocess +from datetime import datetime + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import config as config_mod + + +def main(): + cfg = config_mod.load_config() + vault_dir = cfg.vault_path + + if not os.path.isdir(os.path.join(vault_dir, ".git")): + return + + devnull = subprocess.DEVNULL + subprocess.run(["git", "-C", vault_dir, "add", "-A"], stdout=devnull, stderr=devnull) + staged = subprocess.run( + ["git", "-C", vault_dir, "diff", "--cached", "--quiet"], + stdout=devnull, stderr=devnull, + ) + if staged.returncode != 0: + date_str = datetime.now().strftime("%Y-%m-%d") + subprocess.run( + ["git", "-C", vault_dir, "commit", "-m", f"vault: session notes {date_str}"], + stdout=devnull, stderr=devnull, + ) + print("[session-end] vault committed") + subprocess.run( + ["timeout", "30", "git", "-C", vault_dir, "push", "--quiet", "origin", "main"], + stdout=devnull, stderr=devnull, + ) + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"[vault-sync] error: {e}", file=sys.stderr) + sys.exit(0)