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 <noreply@anthropic.com>
This commit is contained in:
jared 2026-06-15 15:23:32 -04:00
parent fa639cfad7
commit 7ede69ec85
3 changed files with 90 additions and 12 deletions

View File

@ -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-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 ## Implemented Components
**Global memory plugin** — `cc-os/plugins/memory/` (git-tracked, 2026-06-12); symlinked into `~/.claude/plugins/memory` **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) - 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 - Skills: `skills/` — memory-vault, memory-write, memory-reorganize, memory-project
- Config: `config.yaml` — vault path, Ollama model (qwen25-coder-7b-16k), env vars - 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) - 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) - 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 ## OpenSpec workflow

View File

@ -1,6 +1,6 @@
# Build Plan # 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: 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 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/` **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 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. 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 — **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 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 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. 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)** ### 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 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). across all clients by intent — see ADR-015, resolved 2026-06-09).
#### 5b — Obsidian vault → VPS sync — **NOT STARTED** #### 5b — Obsidian vault → VPS sync — **DONE (2026-06-15)**
- Pick **git** (versioned, hourly push/pull) or **Syncthing** (continuous, zero-thought) for The vault at `~/Documents/SecondBrain` was initialized as a git repo and pushed to a new
the vault → VPS. Configure on each machine. private Forgejo repo (`ssh://git@forgejo.swansoncloud.com:2222/jared/SecondBrain.git`;
- **Do not sync** the Graphify `graphify-out/` directories, Milvus caches, or the Ollama web: `https://forgejo.swansoncloud.com/jared/SecondBrain`), mirroring the memsearch sync
models. Graphs are rebuilt per machine from the vault (the single source of truth). 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)** ### Step 6 — Package as a global plugin (Part D) **DONE (2026-06-08)**
- Wrap Steps 23 into a Claude Code plugin with skills; install at user level. - Wrap Steps 23 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 1. **Vault location** — Settled by ADR-012: `~/Documents/SecondBrain`. Symlink into
`~/.claude/memory` only if a tool requires it. `~/.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`? 3. **Stale rebuild threshold** — how many days before SessionStart triggers `--force`?
7 days is the starting guess; tune after observing drift in practice. 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 4. **Per-project graph path** — how does the AI know which `graph.json` to pass to

View File

@ -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)