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:
parent
fa639cfad7
commit
7ede69ec85
11
CLAUDE.md
11
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-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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 2–3 into a Claude Code plugin with skills; install at user level.
|
- 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
|
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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
Loading…
Reference in New Issue