Compare commits

..

11 Commits

Author SHA1 Message Date
jared 53a3c4c2c2 Document ADR-015: memsearch episodic sync and resolved commingling design
memsearch episodic memory (Step 4) is now live and synced via git. Record
the final sync design: dedicated private Forgejo repo at
forgejo.swansoncloud.com/jared/memsearch, auto-commit+push via cc-os
session-end hook, and the resolved decision to use a single global
cross-client store (not per-client repos). Commingling is an explicit
design choice, not a tolerated risk. Update build plan to split Step 5 into
5a (episodic sync, done) and 5b (vault sync, remaining). Update CLAUDE.md
implementation status, USER-GUIDE to reflect memsearch now operational, and
add full ADR-015 decision record with rationale and alternatives.
2026-06-09 15:11:12 -04:00
jared accae31fba Archive memsearch-episodic-layer change
- Sync memsearch-episodic delta spec to openspec/specs/memsearch-episodic/spec.md
- Update README.md: memsearch now active alongside Graphify
- Move change to openspec/changes/archive/2026-06-09-memsearch-episodic-layer/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:35:50 -04:00
jared e6d925df29 Clarify memsearch spec: install-first, audit-defaults, deviate only if needed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 13:38:10 -04:00
jared eb2cdc8871 Sharpen memsearch spec: memory_dir target, skill default, scope boundary
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 13:29:55 -04:00
jared 98cc49c063 Add memsearch episodic layer design spec (Step 4)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 13:28:49 -04:00
jared 61a33f137b Document vault conventions completion and migration scaffolding seeding
Step 1 (vault conventions initialization) is now complete: vault-
conventions.md exists at ~/Documents/SecondBrain/ (canonical naming
resolved), all 6 fixture notes are seeded for Graphify extraction
testing, and 14 handbook + design notes have been migrated to the
vault as first-class migration scaffolding per the empirical finding
locked 2026-06-05. Update CLAUDE.md and build-plan.md to reflect
completion and implementation status.
2026-06-09 09:41:27 -04:00
jared 5834036370 Document memory-project skill completion and vault/project query refactor
Complete the memory-project skill implementation. Rename memory-query to
memory-vault, split project graph queries into memory-project skill, mark
memory-project DONE in build plan, and clarify session-context.sh scope
(now project graph path only — vault injection removed). Update user guide
with examples of both vault and project graph query workflows.
2026-06-08 13:24:53 -04:00
jared ece3469889 Archive memory-project-graph change with stable spec
Completes design and implementation of vault/project graph skill split:
memory-vault (renamed from memory-query, no god-node injection),
memory-project (new, covers full project-graph lifecycle), routing rule
in ~/.claude/CLAUDE.md, and trimmed session-context.sh (project graph
pointer only). All three AI assistant directories (.claude/, .codex/,
.pi/) remain content-identical after mirroring. Spec locked and moved to
openspec/specs/project-graph-lifecycle/.
2026-06-08 13:13:55 -04:00
jared 2317a087ce Update build plan status and CLAUDE.md with implementation progress
- Mark Steps 2a, 2b, 3, 6 complete; add implementation status notes
- Add "Implemented Components" section with progressive disclosure to CLAUDE.md
- Document installed plugin, Graphify, and vault setup as working baseline
- Update last-updated date to 2026-06-08
- Add standing instruction to keep CLAUDE.md current as implementation progresses

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 09:42:04 -04:00
jared b3730d871b Add user-facing documentation for cc-os memory system
Introduces README.md as the main entry point and docs/USER-GUIDE.md to guide users through the memory system design and build plan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 16:56:40 -04:00
jared f914ab4878 Complete memory plugin implementation — hooks, skills, and graph-connectivity findings
- Memory plugin installed globally at ~/.claude/plugins/memory/ with three hooks:
  session-start.sh (background graphify rebuild), session-context.sh (UserPromptSubmit
  vault injection), and post-tool-use-write.sh (session log + dirty-file tracking)
- Root cause fixed: SessionStart has last-write-wins semantics (undocumented); moved
  vault context injection to UserPromptSubmit which concatenates multiple hooks
- Verified working: vault graph map (366 nodes, 346 edges) injected into every session
- ADR-014: Graphify is a structure extractor, not a topic clusterer — hub notes +
  wikilinks must be author-provided; migration scaffolding is now a first-class deliverable
- Graph connectivity findings documented in 07-graph-connectivity-findings.md
- Archive 2026-06-05-add-memory-plugin change; sync memory-plugin-hooks and
  memory-plugin-skills to stable specs
- Add graphify-out/ to .gitignore (build artifact)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 16:39:49 -04:00
29 changed files with 1986 additions and 24 deletions

7
.gitignore vendored
View File

@ -2,3 +2,10 @@
.claude/
.codex/
.pi/
.DS_Store
*.swp
*.log
/.idea/
/.vscode/
# Graphify build artifacts
graphify-out/

View File

@ -4,14 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## What this repository is
`cc-os` is a **documentation- and design-only repository** — there is no application code,
build, lint, or test step yet. It captures the design of a **personal, cross-project memory
system for Claude Code** (for a multi-client freelancer) plus the research that informs it.
Implementation has **not started**; the deliverables here are markdown specs, ADRs, and a
build outline that a future session turns into a real implementation.
`cc-os` is a **design + implementation repository** — it captures the design of a **personal,
cross-project memory system for Claude Code** (for a multi-client freelancer) plus the research
that informs it. The global memory plugin is now partially implemented; markdown specs, ADRs,
and the build plan remain the source of truth for what is being built and what remains.
Everything is markdown-as-truth. When asked to "build," the next step is to convert the
existing build outline into a staged implementation plan, not to start coding ad hoc.
Everything is markdown-as-truth. When asked to "build," work from the staged tasks in
`docs/memory-system/04-build-plan.md`, not ad hoc.
## Directory layout
@ -60,6 +59,35 @@ to those two and fix the stale doc.
**Decisions locked (2026-06-04):** Six-facet tag taxonomy + `scope/` (ADR-011); reuse `~/Documents/SecondBrain` vault rather than creating a new one (ADR-012); build-first / migrate-incrementally — build full system against a fixture set first, defer bulk vault migration to last, onboard projects one at a time (ADR-013).
**Empirical finding locked (2026-06-05):** Graphify is a structure extractor, not a topic clusterer — no emergent hub nodes appear even at `--mode deep`; hub notes + wikilinks must be author-provided during migration (not deferred). Migration scaffolding is now a first-class deliverable. Open question: do facet tags create graph edges? (ADR-014; findings: `docs/memory-system/07-graph-connectivity-findings.md`).
**Implementation status (2026-06-09):** The global Claude Code plugin is live (`~/.claude/plugins/memory/`). Steps 1, 2a, 2b, 3, and 6 of the build plan are complete (including the `memory-project` skill, previously TODO under Step 6/Part D). Step 1 completed 2026-06-09: vault-conventions.md exists, all 6 fixture notes seeded, and 14 Graphify handbook + memory-system design notes migrated to the vault as migration scaffolding. Step 4 (memsearch) completed 2026-06-09: plugin installed via marketplace, MEMSEARCH_DIR set global (~/.memsearch), Stop hook verified producing daily memory files, search confirmed working. Step 5a (memsearch episodic git sync) completed 2026-06-09: dedicated private Forgejo repo (`forgejo.swansoncloud.com/jared/memsearch`), whitelist `.gitignore` (memory/*.md only), auto-commit+push via cc-os `session-end.sh` hook (ADR-015). Step 5b (Obsidian vault → VPS sync) remains. See `docs/memory-system/04-build-plan.md` for full step status.
**Decision (2026-06-09):** Single global memsearch store for ALL clients — cross-client commingling is the accepted, intended design (ADR-015 resolved). One private Forgejo repo (`forgejo.swansoncloud.com/jared/memsearch`), not per-client repos. Forward direction: minimize/eliminate dedicated per-client working directories; work projects locally or use a single general `clients/` dir for non-local client work — memory is captured globally regardless of cwd. No `clients/` directory structure designed yet; open item.
## Implemented Components
**Global memory plugin** — `~/.claude/plugins/memory/`
- Hooks: `hooks/` — session-start.sh, session-context.sh (project graph path only), post-tool-use-write.sh, session-end.sh
- Skills: `skills/` — memory-vault, memory-write, memory-reorganize, memory-project
- Config: `config.yaml` — vault path, Ollama model (qwen25-coder-7b-16k), env vars
- Hook wiring: `~/.claude/settings.json`
**Graphify** — v0.8.31 at `/home/jared/.local/bin/graphify`
- Vault graph: `~/Documents/SecondBrain/graphify-out/` — disposable, rebuilt by SessionStart hook
- Project graph: `<project-root>/graphify-out/` — same pattern; gitignore it in each project repo
- Vault conventions: `~/Documents/SecondBrain/vault-conventions.md` — frontmatter contract + tag taxonomy (canonical name decided 2026-06-09; formerly referred to as CONVENTIONS.md)
**memsearch** — v0.4.6 via Claude Code plugin marketplace (`memsearch@memsearch-plugins`)
- Hooks: Stop, SessionStart, UserPromptSubmit, SessionEnd (ship with plugin)
- Memory store: `~/.memsearch/memory/YYYY-MM-DD.md` daily files (global, cross-project, all clients — one store by design)
- Index: `~/.memsearch/milvus.db` (Milvus Lite, local); embeddings via ONNX bge-m3
- Config: `MEMSEARCH_DIR=~/.memsearch` in `~/.zshrc` for global scope
- 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 `session-end.sh` hook (see ADR-015)
**Not yet implemented:** Obsidian vault → VPS sync (Step 5b). Memsearch episodic git sync is done (Step 5a, 2026-06-09).
## OpenSpec workflow
Changes are managed spec-driven via OpenSpec. Use the matching skills rather than editing spec
@ -83,6 +111,7 @@ project context for OpenSpec artifacts comes from `docs/` and this file.
- **Decisions live in ADRs.** Don't silently reverse a locked decision; add or amend an ADR in
`03-architecture-decisions.md` with the reasoning.
- The package on PyPI is `graphifyy` (double-y) but the command is `graphify`.
- **Keep this file current:** When a build step completes, (a) mark it done in `docs/memory-system/04-build-plan.md`, (b) add or update a component pointer in the Implemented Components section above, and (c) update the current design paragraph if the design changed. This file is the AI's orientation entry point — accuracy matters more than brevity.
## Session Orchestration

41
README.md Normal file
View File

@ -0,0 +1,41 @@
# cc-os — Cross-Project Memory for Claude Code
A personal memory system for Claude Code, designed for a multi-client freelancer.
The system captures knowledge across projects and surfaces it automatically at the
start of each Claude Code session.
## Status
The memory plugin is **live and working.** Design documentation lives in `docs/`.
Both episodic memory (memsearch) and semantic/knowledge memory (Graphify + Obsidian vault)
are active.
## What it does for you
- **Injects vault context at session start** — Claude sees your SecondBrain graph
summary, your conventions, and today's journal path before you type the first message.
- **Surfaces project structure on demand** — run one command to build a graph of any
project; Claude can then query it for patterns, architecture, and conventions.
- **Remembers conventions automatically**`memory-write` stores evergreen knowledge
(coding patterns, client rules, recurring decisions) in the correct vault location
with proper tags.
- **Stays fresh without a daemon** — the vault graph rebuilds in the background when
stale; vault writes immediately invalidate the timestamp so the next session picks
up changes.
- **Keeps project repos clean** — all indexes are build artifacts (`graphify-out/`);
nothing is committed to project repos.
## User guide
See **[docs/USER-GUIDE.md](docs/USER-GUIDE.md)** for setup, onboarding new projects,
using the skills, and known limitations.
## Directory layout
| Path | Contents |
|------|----------|
| `docs/memory-system/` | Architecture, ADRs, build plan, Graphify evaluation |
| `docs/graphify/` | Verified Graphify command handbook |
| `docs/USER-GUIDE.md` | Practical usage guide |
| `openspec/` | Spec-driven change management (changes, specs) |
| `graphify-interview`, `memory-systems-compared060326` | Raw source transcripts (do not cite as fact) |

212
docs/USER-GUIDE.md Normal file
View File

@ -0,0 +1,212 @@
# Memory Plugin — User Guide
_Last updated: 2026-06-08_
---
## 1. What you get automatically
Every Claude Code session, three hooks fire without any action on your part:
| Hook | What it does |
|------|-------------|
| `SessionStart` | Rebuilds vault graph in background if >7 days stale |
| `UserPromptSubmit` (first message only) | Injects vault context into Claude's context window |
| `PostToolUse` (Write/Edit on vault files) | Invalidates rebuild stamp so next session picks up changes |
**What Claude sees at the start of every session:**
- Project graph: path to `<project>/graphify-out/graph.json` if one exists in the current
project root (path only — use `memory-project` to actually query it)
The session-context hook no longer injects vault graph map, conventions, or a journal pointer.
Use `memory-vault` to query cross-project knowledge on demand.
---
## 2. Onboarding a new project
Run these three commands once per project, from the project root.
**Step 1 — Extract the graph:**
```bash
graphify extract . --backend ollama --model qwen2.5-coder:7b
```
You must pass `--backend ollama`. Without it, graphify auto-selects Gemini because
`GEMINI_API_KEY` is set in your environment, which costs money and uses the wrong model.
**Step 2 — Generate the report:**
```bash
graphify cluster-only <project-dir>
```
This produces `graphify-out/GRAPH_REPORT.md` with named communities. Claude uses this
file to understand project structure.
**Step 3 — Add to .gitignore:**
```bash
echo "graphify-out/" >> .gitignore
```
`graphify-out/` is a build artifact. Do not commit it.
After these steps, the next Claude Code session in that project will automatically inject
the project graph path into context.
---
## 3. Updating a project graph
Project graphs are **not updated automatically** — run these manually when the project
has evolved significantly.
**Incremental update** (cheaper; skips unchanged files):
```bash
graphify update .
```
**Full rebuild** (after significant structural changes):
```bash
graphify extract . --backend ollama --model qwen2.5-coder:7b --force
```
After either command, re-run `graphify cluster-only <project-dir>` to refresh the report.
A good rule of thumb: update after adding a major new module, refactoring a core abstraction,
or onboarding a new team member who needs current structure.
---
## 4. Using the skills
**`memory-vault` — query cross-project knowledge from the vault**
```
/memory-vault what are the conventions for naming hooks?
/memory-vault how does the auth pattern work across projects?
```
Use `memory-vault` any time you want Claude to explicitly search the Obsidian vault rather
than relying on what was injected at session start. Good for conventions, tool behavior,
decisions, and any evergreen knowledge that spans projects.
**`memory-project` — per-project graph lifecycle (onboard, update, remove, query)**
```
/memory-project query how is auth structured?
/memory-project onboard
/memory-project update
/memory-project remove
```
Use `memory-project` to query the current project's graph or to manage its lifecycle.
Required for project graph queries — the session start injection gives Claude the path but
not the content. `onboard` runs the full extract + cluster sequence; `update` does an
incremental refresh; `remove` drops the graph and cleans up `.gitignore`.
**`memory-write` — write evergreen knowledge to vault**
```
/memory-write write a note about the convention we just decided: all hooks must be idempotent
```
Claude will produce the note with correct frontmatter (summary, scope, type, facet tags) and
ask you to confirm before writing. Never write vault notes by hand if you can avoid it —
getting frontmatter wrong breaks tag queries.
**`memory-reorganize` — restructure the vault**
```
/memory-reorganize the convention notes are scattered, suggest a hub structure
```
This skill operates in **plan-mode only** — it will propose a reorganization and stop. You
must approve and direct execution. It will not move or rename files without explicit confirmation.
---
## 5. Writing vault notes
When writing notes manually or reviewing `memory-write` output, the frontmatter must include:
```yaml
---
summary: one-line router hint (used by graphify query and grep fallback)
tags:
- scope/global # global (applies everywhere) or scope/project (project-specific)
- type/procedure # one of: procedure, reference, log, hub, concept, decision
- client/hyperthrive # at least one facet tag (see below)
---
```
**Required facet tags** — use at least one from any of these namespaces:
| Namespace | Examples |
|-----------|---------|
| `client/` | `client/hyperthrive`, `client/acme` |
| `project/` | `project/cc-os`, `project/api-v2` |
| `domain/` | `domain/auth`, `domain/billing` |
| `tool/` | `tool/graphify`, `tool/rails` |
| `convention/` | `convention/naming`, `convention/git` |
**The evergreen rule:** vault notes should capture knowledge that is true across sessions and
projects — decisions, patterns, conventions, reference facts. Ephemeral session notes belong
in the journal, not the vault.
Note the contrast with episodic memory: while vault notes use `client/` and `scope/` facets to
scope individual notes, memsearch episodic memory is a **single global store spanning all
clients and projects** — there is no per-client partitioning at the store level (by design).
Hub notes (type/hub) tie a domain together with wikilinks. If you find a cluster of notes
that share a theme but have no hub, create one — Graphify does not create hub nodes
automatically (see Known limitations).
---
## 6. Vault graph auto-refresh
You do not need to manage vault graph freshness manually.
- **At session start:** if the vault graph is >7 days stale, `SessionStart` triggers a
background rebuild. No output is shown; it runs quietly.
- **After vault writes:** `PostToolUse` fires on every Write/Edit to a vault file and
immediately invalidates the rebuild stamp. The next session start will trigger a rebuild,
ensuring changes made in one session are visible in the next.
The vault graph lives at `~/Documents/SecondBrain/graphify-out/`. It is a build artifact —
do not commit it to the SecondBrain vault's git repo if you track one.
---
## 7. Known limitations
**Facet tags do not create graph edges.**
Tags like `tool/graphify` or `client/hyperthrive` appear in frontmatter but Graphify does
not convert them into graph edges. Hub notes and wikilinks are the only way to create
connections in the graph — and they must be author-provided. This is documented in ADR-014
(`docs/memory-system/03-architecture-decisions.md`). Consequence: if you are relying on
tag-based clustering in Graphify, it will not work; use hub notes and explicit wikilinks instead.
**Project graph injection is path-only.**
At session start, Claude receives the path to `<project>/graphify-out/graph.json` — not
the content. To actually query the project graph, invoke `memory-project query <question>`.
Claude will not automatically read the project graph without being asked.
**memsearch (episodic memory) is live as of 2026-06-09.**
The second memory tier — episodic memory of what happened across sessions, stored in Milvus
Lite — is installed and operational. Memory is captured globally across all clients and
projects in one store (`~/.memsearch`), synced to a private self-hosted Forgejo repo
(`forgejo.swansoncloud.com/jared/memsearch`). This is the intended design: a single
commingled global store, not per-client stores. Use `/memory-recall` to search episodic memory;
use `/memory-config` to inspect configuration. See ADR-015 for the rationale and sync details.
**Vault graph rebuild is fire-and-forget.**
The `SessionStart` rebuild runs in the background with no progress indicator. If the rebuild
fails silently (e.g., Ollama is not running), the stale graph continues to be used. Check
`~/Documents/SecondBrain/graphify-out/` timestamps if context seems outdated.

View File

@ -1,6 +1,6 @@
# Architecture Decision Records
_Last updated: 2026-06-04_
_Last updated: 2026-06-09_
A running log of decisions and *why*. Format per entry: Context · Decision · Rationale ·
Alternatives rejected · Status. Newest decisions extend the log; supersede rather than delete.
@ -295,6 +295,140 @@ _Date: 2026-06-04_
ADR-012 (SecondBrain vault reuse — the migration steps this order defers).
- **Status**: Accepted (updates `05-implementation-process.md` build order).
## ADR-014 — Graph connectivity comes from authored structure; migration scaffolding is a first-class prerequisite
_Date: 2026-06-05_
- **Context**: ADR-011 specified hub notes + wikilinks + Graphify graph edges as the mechanism
for expressing hierarchy and cross-note relationships, with ADR-013 deferring bulk vault
migration to the final stage. Before build began, a discriminating empirical test compared a
cached-replay graph (per-fixture isolated extractions) against a clean single-pass deep
extraction (`graphify extract ~/Documents/SecondBrain --backend ollama --model
qwen25-coder-7b-16k --max-concurrency 1 --token-budget 8000 --mode deep --exclude
.obsidian`) against the real `~/Documents/SecondBrain` vault under Graphify 0.8.31 +
qwen2.5-coder:7b. See `07-graph-connectivity-findings.md` for the full data and methodology.
[primary/measured — 2026-06-05 session]
- **Decision**: **The connective spine of the knowledge graph must be author-provided.** Hub
notes and wikilinks are not optional scaffolding to add "someday" — they are the mechanism
by which Graphify connects thematically related notes, and they must be authored **as part of
the migration step**, not deferred to bulk import. Migration scaffolding (hub notes +
wikilinks for key concepts) is treated as a **first-class build deliverable** in the
migrate-incrementally phase of ADR-013.
- **Rationale**: The empirical test found that Graphify is a **structure extractor, not a topic
clusterer**. Even at `--mode deep --token-budget 8000`, no emergent shared-topic hub nodes
appeared (no "Pest Control" node, no "Niche Prospecting" node). All cross-note edges observed
came from explicit references, wikilinks, or document-level semantic similarity — not from
shared thematic identity. A practical test query ("how do we do niche prospecting outreach for
pest control?") returned 3 starting notes and traversal could not reach the email templates /
ACV data / business-model notes (separate communities, no connecting edges). This confirms
that useful retrieval is gated on migration scaffolding, not on Graphify's extraction power.
The clean single-pass run also showed the cached graph was partially a build artifact (cross-
note edges rose from 41% to 78% in a single-pass run), but the structural finding — no
emergent hub nodes — held in both runs.
- **Relationship to ADR-011**: Validates the hub-notes + wikilinks half of ADR-011 empirically.
The facet-tag half is **not yet validated**: no edge was observed to arise from shared
frontmatter facet tags alone. Whether `client/X` or `tool/Y` tags create graph connectivity
is an **open question** — see "Deferred" below. Do not assume facet tags contribute to graph
traversal retrieval until tested.
- **Relationship to ADR-013**: Refines the migrate-incrementally stage. "Migration" must be
defined to include hub note authoring and wikilink addition for key concepts, not just
frontmatter schema migration (adding `summary:` and namespaced facet tags). The build plan
(`04-build-plan.md`) should be updated to name this deliverable explicitly.
- **Alternatives rejected**: Relying on the SLM to auto-cluster topics and synthesize hub
entities — **empirically does not happen** at 7B model size with `--mode deep`. The design
already intended human-authored hub notes for this; the test confirms that intent was correct
and the fallback assumption ("maybe the LLM will do it") is false.
- **Deferred**:
1. **Facet-tag-to-graph-edge question**: Do shared frontmatter facet tags (`client/`,
`tool/`, `domain/`, etc.) cause Graphify to create edges between notes, or does graph
connectivity come only from explicit wikilinks/references and semantic similarity? This was
NOT tested. Resolve before designing graph-traversal retrieval skills.
2. **Larger extraction model**: Whether a substantially larger SLM (14B, 30B) would
synthesize emergent topic-hub nodes is untested. Secondary — the design does not depend on
it — but worth one test run before the build ships.
3. **`reasoning_effort:"none"` patch**: The clean run required a local patch to `graphify/
llm.py`. Track the upstream Graphify issue tracker for an official fix; treat the
installed version as pinned until resolved.
- **Status**: Accepted. Refines ADR-013 (migrate-incrementally phase scope) and empirically
validates the hub-notes/wikilinks mechanism of ADR-011 while flagging its facet-tag half as
an open question.
## ADR-015 — memsearch episodic memory version-controlled in a dedicated private repo, auto-synced via cc-os SessionEnd hook
_Date: 2026-06-09_
- **Context**: memsearch's memory store at `~/.memsearch` accumulates daily session-summary
files (`memory/YYYY-MM-DD.md`) that are the only irreplaceable data in the episodic layer —
the Milvus Lite index (`milvus.db`) and the bge-m3 ONNX embeddings model are derived/
disposable and can be rebuilt at any time via `memsearch index`. With Step 4 (episodic layer)
now live, preserving episodic memory across machines and protecting against local disk loss
required a sync strategy. The question was: what venue, what scope, and who owns the sync?
memsearch's own stated design philosophy is "markdown files are the canonical data store; the
vector database is a derived index" and notes that markdown is "git-friendly" and the index
rebuildable from markdown — making git a natural fit for the markdown layer.
- **Decision**: `~/.memsearch` is a **dedicated git repo** on branch `main` with remote `origin`
pointing to a **private self-hosted Forgejo repo**
(`ssh://git@forgejo.swansoncloud.com:2222/jared/memsearch.git`;
web: `https://forgejo.swansoncloud.com/jared/memsearch`). A whitelist `.gitignore` tracks
**only** `memory/*.md` (the daily session files) and `.gitignore` itself. Excluded as
derived/disposable: `milvus.db` (Milvus Lite index — rebuildable any time via
`memsearch index`), `config.toml`, and the bge-m3 ONNX embeddings model (lives in
`~/.cache/huggingface`, not in the store). Auto-commit and push are wired into the **cc-os
memory plugin's own `session-end.sh`** hook, not the marketplace plugin. The appended block
guards on `~/.memsearch/.git` existing, runs `git add -A` (whitelist makes it safe), commits
only when something is staged (message: `memsearch: session memory <date>`), and pushes with
`timeout 30 ... || true`. The entire block is wrapped in a subshell with a trailing `|| true`
so it can **never fail session shutdown**.
- **Rationale**:
- **Dedicated repo (not folded into the vault or a project repo):** ADR-001 established
episodic and semantic/knowledge as separate systems by design, and `~/.memsearch` is a
global, cross-project store with no natural home in any single project repo. Committing
episodic session logs into the Obsidian vault repo would conflate the two systems and
violate the separation of concerns that ADR-001 is built on.
- **Whitelist — commit markdown, exclude rebuildable index/model/config:** consistent with
memsearch's own design philosophy ("markdown is canonical; index is derived") and with the
cc-os principle from ADR-008 ("sync the vault, not the indexes"). The 544 MB bge-m3 ONNX
model is not even in the store; the Milvus Lite DB rebuilds in one command. Committing them
would bloat the repo for zero durability gain.
- **Sync lives in cc-os hook, not the marketplace plugin:** the marketplace plugin's hook
scripts (`stop.sh`, `session-start.sh`, `user-prompt-submit.sh`, `session-end.sh`) perform
no git operations — only a read-only `git rev-parse --show-toplevel` for scoping. Adding
sync to a marketplace plugin that can be clobbered by an upstream update is fragile; owning
it in the cc-os `session-end.sh` keeps the sync logic under our control and version-tracked
in this repo.
- **Fail-safe contract:** the `|| true` wrap and `timeout 30` on push ensure that a network
outage, an unreachable Forgejo instance, or any git error cannot prevent a session from
closing cleanly. The SessionEnd hook's harness timeout is ~10 s, so push is effectively
capped; commits land locally first, and any unpushed commit is carried forward by the next
session's push (daily files are append-only, so no conflict risk).
- **Commingling — resolved 2026-06-09**: `~/.memsearch` aggregates session summaries across
**all clients** into one global store. This is an **explicit design choice**, not a tolerated
risk: the user deliberately accepts a single commingled global store across all clients.
Private self-hosted Forgejo (user-owned infrastructure) is the chosen sync venue; client-
agreement compliance is the user's knowing responsibility. The concern was previously recorded
as user-owned and unresolved; that is now closed: single global store is the intended design.
This also aligns with a forward direction of minimizing dedicated per-client working
directories — since memsearch captures memory globally regardless of cwd, a single
`clients/` area for non-project client work becomes viable (direction stated; not yet designed).
- **Alternatives rejected**:
- **Fold into the Obsidian vault repo:** violates ADR-001's episodic/semantic separation.
Episodic logs have different lifecycle (accrete and decay), different write patterns
(auto-captured every session), and would clutter a vault intended for curated durable
knowledge.
- **Auto-commit in the marketplace plugin:** the marketplace plugin can be overwritten on
update, losing the sync logic silently. Out-of-band ownership in cc-os is safer.
- **Commit the Milvus Lite index or the embeddings model:** both are large binaries, derived
from the markdown source, and rebuildable. Committing them wastes space and provides no
additional durability. The markdown files are the canonical source; the index follows from
them.
- **Syncthing or rsync instead of git:** git provides both version history and conflict-free
daily-append semantics; Syncthing is continuous-async (suitable for the vault where changes
are sparse); git's push-on-session-end cadence matches how memsearch produces data (one
daily file per day, append-only). Git was already chosen for ADR-008's vault sync rationale;
applying the same mechanism to the episodic store is consistent.
- **Status**: Accepted (commingling resolved 2026-06-09). Initial commit `106cebc` in the
Forgejo repo; git repo initialized 2026-06-09 via the `git-context:repo-init` skill.
## Rejected tools (summary)
| Tool | Why rejected for our use |

View File

@ -1,6 +1,6 @@
# Build Plan
_Last updated: 2026-06-04_
_Last updated: 2026-06-09_
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
@ -37,21 +37,33 @@ plugin that packages it.
`summary` (one-line, author-written — this is the human-readable router hint Graphify
does not generate) and `scope` (`scope/global` or `scope/project`). Tags are now
supplementary, not the primary query mechanism.
**Naming resolved (2026-06-09):** `vault-conventions.md` is the canonical name (not
`CONVENTIONS.md`). The file exists at `~/Documents/SecondBrain/vault-conventions.md`.
- Seed a few real notes (e.g. `tool/semrush`, `client/<x>`) to use as extraction test cases
in Step 2.
**Status: COMPLETE (2026-06-09)**
- `vault-conventions.md` (renamed 2026-06-09 from `CONVENTIONS.md`) exists in the vault at
`~/Documents/SecondBrain/vault-conventions.md` — frontmatter contract + tag taxonomy.
- All 6 fixture notes exist in the vault (seeded for extraction testing in Step 2c).
- 14 Graphify handbook + memory-system design notes migrated to the vault (2026-06-09) as
part of migration scaffolding — first-class deliverable per the empirical finding locked
2026-06-05.
### Step 2 — Graphify + Ollama setup (the knowledge graph layer)
This replaces the Ruby tag-index CLI. Graphify builds a knowledge graph over vault docs via a
local SLM, and a free code graph over project files via AST.
#### 2a — Install Graphify
#### 2a — Install Graphify **DONE (2026-06-08)**
```
pip install graphify # or follow current install docs
```
Verify: `graphify --version`
#### 2b — Configure Ollama for extraction
**Status:** `graphify` v0.8.31 installed at `/home/jared/.local/bin/graphify`.
#### 2b — Configure Ollama for extraction **DONE (2026-06-08)**
The 2024 approach (Modelfile with `PARAMETER num_ctx`) still works but the preferred
programmatic method is now per-request via the API (Graphify supports this via
`GRAPHIFY_OLLAMA_NUM_CTX`):
@ -65,6 +77,11 @@ GRAPHIFY_OLLAMA_NUM_CTX=8192 # 8K is sufficient for vault notes (200-2000 w
To verify context is being used: `ollama ps` shows allocated context after first call.
**Status:** All env vars set in `~/.claude/plugins/memory/config.yaml`:
`OLLAMA_FLASH_ATTENTION=1`, `GRAPHIFY_OLLAMA_NUM_CTX=8192`, `GRAPHIFY_OLLAMA_KEEP_ALIVE=5`.
Model locked to `qwen25-coder-7b-16k` (per ADR and memory note — this is Graphify's default
and the confirmed working extraction model).
Available models (as of 2026-06-03, in order of interest):
- `gemma4:e2b` — 7.2 GB, ~2B effective params, 128K native window, fast; **start here**
- `qwen3.5:2b` — 2.7 GB, smallest, good fallback if VRAM is constrained
@ -122,6 +139,12 @@ Tune `--token-budget` (semantic chunk size) and `--max-concurrency` based on VRA
Review `GRAPH_REPORT.md` — check god nodes make sense (they should be your most-connected
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.
**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
until the system is verified working on the fixture set and pilot project.
@ -141,7 +164,13 @@ subsequent runs; use `--force` when you've deleted files (to clear stale nodes).
Store each project's `graphify-out/` alongside the project, or in a configurable cache dir.
Keep per-project graphs separate — do not merge client projects into one graph.
### Step 3 — Hooks (maintenance + retrieval)
**Note (2026-06-08):** A `graphify-out/` for the `cc-os` project exists (built 2026-06-05),
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)
### Step 3 — Hooks (maintenance + retrieval) **DONE (2026-06-08)**
Hooks now call Graphify instead of the Ruby CLI.
@ -150,15 +179,15 @@ Hooks now call Graphify instead of the Ruby CLI.
nodes — a periodic `--force` rebuild (e.g. weekly, or triggered by SessionStart stale
check) is needed to clear ghost nodes.
- **SessionStart**: reconcile (check vault graph mtime vs last rebuild; trigger `--force` if
stale threshold exceeded) + inject context (query MCP server for god nodes + inject
`convention/*` note summaries + journal pointer).
stale threshold exceeded) + inject context (emits project graph path if a
`graphify-out/graph.json` exists at the project root; vault context injection was removed).
- **SessionEnd**: append daily journal note with pointers to vault notes touched.
**Stale-node mitigation strategy:** track last `--force` rebuild time in a stamp file
(e.g. `~/.cache/graphify/vault-rebuild.stamp`). SessionStart hook triggers `--force` if
older than N days (7 is a reasonable starting point).
### Step 4 — Episodic layer (memsearch)
### Step 4 — Episodic layer (memsearch) — DONE
- Install memsearch (`/plugin marketplace add zilliztech/memsearch`, then `plugin install
memsearch`), local to start. Verify daily memory files appear after a few conversations.
- Decide whether memsearch indexes our session-end journal notes or its own capture (likely its
@ -166,15 +195,40 @@ older than N days (7 is a reasonable starting point).
- Graphify does **not** replace memsearch here. Time-anchored semantic queries ("what was I
working on last Tuesday?") are better served by vector similarity over session logs.
### Step 5 — Sync
### Step 5 — Sync — PARTIAL
Step 5 covers two distinct sync lines; they are at different statuses:
#### 5a — memsearch episodic memory git sync — **DONE (2026-06-09)**
`~/.memsearch` is now a git repo on branch `main` with remote `origin` pointing to a private
self-hosted Forgejo repo (`ssh://git@forgejo.swansoncloud.com:2222/jared/memsearch.git`;
web: `https://forgejo.swansoncloud.com/jared/memsearch`). Initial commit `106cebc`.
A whitelist `.gitignore` tracks **only** `memory/*.md` (the daily session files) and
`.gitignore`. Excluded as derived/disposable: `milvus.db` (Milvus Lite index — rebuildable via
`memsearch index`), `config.toml`, and the bge-m3 ONNX model (lives in `~/.cache/huggingface`).
Auto-commit and push are wired into the cc-os memory plugin's **own `session-end.sh`** hook
(not the marketplace plugin, which can be clobbered on update). The block guards on
`~/.memsearch/.git` existing, commits only when staged content exists (message:
`memsearch: session memory <date>`), and pushes with `timeout 30 ... || true`. Wrapped in a
subshell with trailing `|| true` — cannot fail session shutdown. See **ADR-015** for rationale
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).
### Step 6 — Package as a global plugin (Part D)
### 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.
**Status:** Global plugin installed at `~/.claude/plugins/memory/` with all hook scripts and
4 skills (`memory-vault`, `memory-write`, `memory-reorganize`, `memory-project`). Plugin enabled in
`~/.claude/settings.json`.
### Step 7 — (SKIPPED) QMD semantic layer
Covered by Graphify. The knowledge-graph approach provides structured semantic retrieval
without vectors. Only revisit if Graphify's graph queries prove insufficient for a use case
@ -211,9 +265,10 @@ project-ephemeral state (that's the episodic layer / project files). Concretely:
- Add `--budget N` to cap answer size (default ~2000 tokens); `--dfs --budget N` for bounded traversal
- Add `--graph <path>` to query a specific project's `graph.json` instead of the vault graph
**At session start**: god-node summary (injected by hook from `GRAPH_REPORT.md`) gives the
architecture spine — what are the highest-connectivity concepts in the vault. The AI then
queries specific nodes on demand rather than reading everything.
**At session start**: the session-context.sh hook emits the project graph path (pointing at
`<project-root>/graphify-out/graph.json`) if one exists — vault context injection (god-node
summary, convention summaries, journal pointer) was removed. The AI queries the vault or
project graph on demand rather than having it injected.
**On demand, during a task**: run `graphify query` when the task touches a tool/client/domain.
The graph returns relevant node clusters; the AI opens only the vault notes whose `summary`
@ -234,7 +289,7 @@ SEMrush concept, regardless of which client project they came from. Graph edges
| **Read / query** | AI needs knowledge | AI runs `graphify query` / `graphify explain` via Bash (no hook; on-demand) |
| **Update** | AI/user edits a note | AI edit → **PostToolUse**`graphify update --file`; user edit → **SessionStart** reconcile |
| **Delete / rename** | note removed/renamed | `graphify update --file` prunes AI-deleted paths; **SessionStart** triggers `--force` rebuild if stale (catches manual deletes) |
| **Inject** | new session starts | **SessionStart** hook: god-node summary + `convention/*` summaries + journal pointer |
| **Inject** | new session starts | **SessionStart** hook: emits project graph path (vault context injection removed) |
| **Journal** | session ends | **SessionEnd** hook appends a dated journal note with pointers |
| **Reorganize** | periodic, user-invoked | `reorganize memory` in **plan mode**: dedupe, merge, split, re-scope, then `graphify --force` to rebuild clean graph — human approves |
| **Stale graph rebuild** | SessionStart stale check | If rebuild stamp > N days old: `graphify --force` on vault; resets stamp |
@ -242,11 +297,14 @@ SEMrush concept, regardless of which client project they came from. Graph edges
Hooks are thin shell wrappers; the logic lives in Graphify.
**Hooks summary:**
- **SessionStart** — stale check + `--force` if needed, then inject (god nodes + conventions + journal pointer).
- **SessionStart** — stale check + `--force` if needed, then inject project graph path (vault context injection was removed; session-context.sh now only emits the project graph path).
- **PostToolUse** (on `Write`/`Edit` of vault `.md`) — `graphify update --file`.
- **SessionEnd** — append daily journal note.
- (memsearch brings its own hooks for the episodic layer.)
**Status:** All 4 hooks wired in `~/.claude/settings.json`: SessionStart, UserPromptSubmit
(context injection), PostToolUse (vault writes), SessionEnd (journal).
---
## Part D — Claude Code plugin with skills (global install)
@ -263,11 +321,16 @@ Graphify config.
- **Skills** (these carry the *know-how* to the model):
- `memory-write` — when to record evergreen knowledge, the frontmatter contract (summary
required, scope rule), "vault not repo."
- `memory-query` — how/when to use `graphify query` vs memsearch; god-node discipline;
- `memory-vault` — how/when to use `graphify query` vs memsearch; god-node discipline;
`--budget`/`--dfs` usage; cross-client lookups via `--graph`; progressive-disclosure
via summary field.
- `memory-reorganize` — the plan-mode consolidation/promotion procedure + guardrails;
when to trigger `graphify --force` rebuild.
- `memory-project` **DONE (2026-06-08)** — full lifecycle skill for per-project Graphify graphs:
- **Onboard**: `graphify extract . --backend ollama --model qwen2.5-coder:7b`, then `graphify cluster-only .`, add `graphify-out/` to `.gitignore`
- **Query**: `graphify query ... --graph <path>` for project-context queries (wraps `memory-vault`)
- **Update**: `graphify update .` for incremental updates (no API cost) or `--force` rebuild after structural changes
- **Remove**: delete `graphify-out/` when no longer needed
- **Config**: vault path, Graphify output dir, Ollama model name, `num_ctx`, rebuild-stale
threshold in days — set once at user level.
- **Env vars** set by plugin: `OLLAMA_FLASH_ATTENTION=1`, `GRAPHIFY_OLLAMA_NUM_CTX=8192`,

View File

@ -338,7 +338,7 @@ Graphify config.
`--graph <project-root>/graphify-out/graph.json`.
- **Skills** (carry the know-how to the model):
- `memory-write` — when to record evergreen knowledge, frontmatter contract, scope rule, vault not repo.
- `memory-query` — `graphify query` vs memsearch; god-node discipline; `--budget`/`--dfs`; cross-client lookups; progressive disclosure via `summary`.
- `memory-vault` — `graphify query` vs memsearch; god-node discipline; `--budget`/`--dfs`; cross-client lookups; progressive disclosure via `summary`.
- `memory-reorganize` — plan-mode consolidation/promotion procedure; when to trigger `--force` rebuild; human-approval guardrails.
- **Env vars** baked in: `OLLAMA_FLASH_ATTENTION=1`, `GRAPHIFY_OLLAMA_KEEP_ALIVE=5`,
`OLLAMA_BASE_URL=http://127.0.0.1:11434/v1`, `OLLAMA_API_KEY=<any-non-empty>`. Note:

View File

@ -0,0 +1,166 @@
# Graph Connectivity Findings: Structure Extractor vs. Topic Clusterer
_Last updated: 2026-06-05_
_Status: Empirical findings — primary/measured data from 2026-06-05 session against the real `~/Documents/SecondBrain` vault (Graphify 0.8.31, qwen2.5-coder:7b)._
---
## Context
We regenerated the vault knowledge graph to inspect it visually, then ran a discriminating test
to determine why the graph appeared so fragmented in the initial screenshot — specifically,
whether the fragmentation was a build artifact or a genuine property of Graphify's extraction
behavior.
Two runs were compared:
1. **Cached run** — a replay of per-fixture isolated extractions (each fixture extracted in its
own temp directory, results assembled after the fact). This was the source of the initial
visual screenshot.
2. **Clean deep run** — a genuine single-pass extraction against the full vault, run from
scratch.
---
## Methodology
### Cached run (baseline)
Replayed previously-generated per-fixture outputs. Each note was extracted in isolation in a
separate temp directory (`/tmp/graphify-bench/vault-graph-viz/graphify-out/`). No single
invocation saw the full corpus.
### Clean deep run
Single invocation against the full vault:
```
graphify extract ~/Documents/SecondBrain \
--backend ollama \
--model qwen25-coder-7b-16k \
--max-concurrency 1 \
--token-budget 8000 \
--mode deep \
--exclude .obsidian
graphify cluster-only <out> --backend ollama
```
Output: `/tmp/graphify-bench/vault-graph-clean/graphify-out/`
Note: this run required the `reasoning_effort:"none"` patch applied to the installed
`graphify/llm.py` — a fragile workaround that is lost on `pip upgrade`. Track Graphify issue
tracker for an official fix before the build step.
---
## Comparison Table
| Metric | Cached run (replay, isolated) | Clean deep run (single-pass) |
|--------|-------------------------------|------------------------------|
| Nodes | 51 | 30 |
| Edges | 34 | 27 |
| Cross-note edges | 14 (41%) | 21 (78%) |
| Communities | 18 | 9 |
| Shared topic/category hub entity (e.g. "Pest Control") | none | none |
| Token cost | 0 (cache replay) | 38,590 in / 8,296 out (genuine) |
The clean run's project-config node (`niche-automation-prospecting`) went from 0 cross-note
links to 6 — it now references the cold-email research, 10DLC setup, RingCentral analysis, and
VoIP research notes.
---
## Conclusions
### 1. Part of the extreme fragmentation was a build artifact [primary/measured]
The cached graph replayed per-fixture extractions done in isolated temp directories. A clean
single-pass extraction nearly doubles the **proportion** of cross-note edges (41% → 78%), and
cuts community count from 18 to 9. A real production graph will be meaningfully more connected
than the initial screenshot suggested.
### 2. Graphify is a structure extractor, not a topic clusterer [primary/measured]
This is the load-bearing finding. Graphify creates edges where one note's text
references/cites another, or where two notes are semantically very close
(document-to-document). It does **not** invent emergent shared-category entity nodes.
In both runs — including the clean deep run at `--mode deep --token-budget 8000` — **no**
shared topic hub node appeared. No "Pest Control" node, no "Niche Prospecting" node, no
"Speed-to-Lead" node. The LLM found many intra-note entities and doc-to-doc edges, but did not
synthesize a thematic category node spanning unrelated notes on the same topic.
This means: the connective "spine" a human reader imagines must be an **actual hub note**,
authored with wikilinks. This is exactly the mechanism ADR-011 already specifies. The
empirical data confirms the design choice rather than opening a gap in it.
### 3. Migration scaffolding is a first-class build prerequisite [primary/measured, strategic]
**Practical consequence of conclusion 2.** Running `graphify extract` is the easy part. A real
practical query — "how do we do niche prospecting outreach for pest control?" — returned only 3
starting notes in **both** runs, and traversal could **not** reach the email templates / ACV
data / business-model notes. Those are in separate communities with no connecting edges.
Therefore: authoring hub notes + wikilinks for key concepts **during migration** is what makes
retrieval actually work. This is a **first-class build deliverable and a prerequisite for
useful retrieval** — not a "bulk import later" afterthought. This refines and reinforces
ADR-013 (build-first / migrate-incrementally): the migrate-incrementally phase must include hub
note authoring as a named deliverable, not just frontmatter schema migration.
See ADR-014 for the locked decision.
### 4. OPEN QUESTION: Do facet tags create graph edges? [explicitly UNRESOLVED — untested]
Every edge observed in both runs came from explicit references, wikilinks, or semantic
similarity between documents. No edge was observably produced by shared frontmatter facet tags
alone (e.g., two notes both tagged `client/X` or `tool/Y`).
**If facet tags do not create graph connectivity**, then:
- Tag-scoped retrieval ("all notes tagged `client/acme` + `tool/semrush`") is a **separate
tag-query path**, not graph traversal.
- The graph provides connection-based recall; the tag facets provide attribute-filtered recall.
These are complementary, not interchangeable.
- The design of graph-traversal retrieval skills must account for this separation.
This is the **key thing to verify before designing retrieval skills**. It was not tested in
either run. Do not assume facet tags create edges.
### 5. Caveats [primary/measured — honestly recorded]
**(a) Coarser granularity is a tradeoff, not strictly "better."** The clean run produced 30
nodes (one per document, roughly) because batching 45 notes per LLM call shifted extraction
toward document-level nodes + doc-to-doc edges rather than fine-grained intra-note sub-entities.
More cross-note edges, but less intra-note entity resolution. Whether this is better depends on
the query type — a behavioral tradeoff, not a dominance result.
**(b) A substantially larger extraction model may produce topic-hub entities — untested.** We
did not test a 14B or 30B model. However, this is **secondary**: the design already intends
human-authored hub notes as the connective spine (ADR-011). If a larger SLM does synthesize
hub entities, that's additive; the design doesn't depend on it.
**(c) The `reasoning_effort:"none"` patch is fragile.** This local patch to the installed
`graphify/llm.py` was required to complete the extraction run. It is lost on `pip upgrade`.
Track upstream for an official flag or mitigation before the system build step. Until then,
treat the installed version as pinned.
---
## Pointers to Disposable Outputs
- **Cached run output**: `/tmp/graphify-bench/vault-graph-viz/graphify-out/` — replay of
per-fixture isolated extractions; **not** representative of production behavior.
- **Clean deep run output**: `/tmp/graphify-bench/vault-graph-clean/graphify-out/` — genuine
single-pass extraction; the reference for production behavior.
Both are disposable (per ADR-008: indexes are rebuildable, never synced).
---
## Cross-References
- **ADR-011** — Six-facet tag taxonomy + hub notes + wikilinks as the relationship mechanism
(empirically validated for the hub-notes/wikilinks half; open question on the facet-tag half)
- **ADR-013** — Build-first / migrate-incrementally (refined: migration phase must include hub
note authoring as a named deliverable)
- **ADR-014** — Decision locked from this finding: graph connectivity comes from authored
structure; migration scaffolding is a first-class prerequisite
- `docs/memory-system/06-graphify-evaluation.md` — earlier Graphify fit assessment (pre-empirical)

View File

@ -0,0 +1,97 @@
# Step 4 — Memsearch Episodic Layer Design
_Last updated: 2026-06-09_
_Status: Approved, pending implementation_
## Goal
Add time-anchored episodic memory to the personal Claude Code memory system. Enable queries like "what was I working on last Tuesday?" or "did we discuss X recently?" across all projects.
## Guiding Principle
Install memsearch as a standard global plugin. Audit its default behavior before adding anything custom. Every deviation from defaults requires a concrete reason observed after install — not assumed in advance. The expected outcome is a working episodic layer with zero custom code.
## Architecture Fit
The system has two memory types kept as separate systems:
- **Episodic** ("what happened, when") → **memsearch** (this step)
- **Semantic/knowledge** ("how do we…") → **Obsidian vault + Graphify** (already live)
memsearch is the purpose-built episodic layer. It is not a replacement for Graphify and does not index the vault.
## Components
### 1. memsearch Plugin
Installed via Claude Code plugin marketplace:
```
/plugin marketplace add zilliztech/memsearch
/plugin install memsearch
```
Ships with its own Stop hook (fires after each response turn), its own Claude guidance, and tool definitions. No custom code needed.
### 2. Configuration
After install, run `memsearch config list` to audit actual defaults before changing anything.
The one setting to confirm: **memory_dir** must be global (`~/.memsearch/memory/`) so all projects share one episodic store — essential for cross-project recall. Run `memsearch config list` after install; if memory_dir is not already global, set it explicitly in `~/.memsearch/config.toml`. memsearch's Milvus database already defaults to a global path (`~/.memsearch/milvus.db`), so memory_dir may match — verify rather than assume.
All other defaults are kept:
- Embedding: ONNX bge-m3-onnx-int8 (CPU, no API key, ~558MB one-time download)
- Storage: Milvus Lite at `~/.memsearch/milvus.db`
### 3. Built-in Guidance Audit
memsearch ships as a full Claude Code plugin with its own system prompt instructions and tool guidance. After install, observe what it adds to Claude's context before adding anything.
Expected outcome: no custom skill needed. A `memory-search` skill is only warranted if — after seeing the default install — memsearch's built-in guidance fails to handle cross-project episodic recall, or creates routing confusion with `memory-vault`/`memory-project`. This will be evaluated at install time.
## Data Flow
```
Each response turn:
memsearch Stop hook fires (ships with plugin)
→ extracts last user question + Claude response from transcript
→ summarizes via Haiku (2-10 bullet points, third-person)
→ appends to ~/.memsearch/memory/YYYY-MM-DD.md
→ runs `memsearch index` immediately (incremental, SHA-256 dedup)
→ stored in Milvus Lite at ~/.memsearch/milvus.db
On-demand recall:
memsearch search "<query>"
→ hybrid search (dense bge-m3 vectors + BM25)
→ L1 snippets by default; L2/L3 drill-down available
```
## Hook Integration
**session-end.sh: unchanged.**
The existing hook writes vault provenance (which vault notes were touched, project, end reason) to `~/Documents/SecondBrain/journal/YYYY-MM-DD.md`. This is complementary to memsearch, not redundant:
| System | Captures | Answers |
|--------|----------|---------|
| memsearch Stop hook | Per-turn Q&A summaries | "What were we discussing last Tuesday?" |
| session-end.sh | Vault notes touched per session | "Which notes did I update during that sprint?" |
## What This Step Does Not Do
- Does not index the vault (`~/Documents/SecondBrain/`) — Graphify owns that
- Does not replace or modify session-end.sh
- Does not add custom hook code
- Does not configure Milvus Server or Zilliz Cloud (Milvus Lite is correct for personal use)
- Does not assume SessionStart hook injection is needed — evaluate after seeing what the default global install provides
- Step 4 is complete when: plugin is installed, memory_dir is confirmed global, and at least one session has produced a daily memory file at `~/.memsearch/memory/YYYY-MM-DD.md`. SessionStart injection is a distinct future step.
## Installation Checklist
1. Install plugin via marketplace commands above
2. Run `memsearch config list` — record actual defaults
3. If memory_dir is project-local, set `memory_dir = "~/.memsearch/memory/"` in `~/.memsearch/config.toml`
4. Verify Stop hook is registered (`~/.claude/settings.json` or plugin config)
5. Run one test session — confirm `~/.memsearch/memory/YYYY-MM-DD.md` is created after a turn
6. Audit Claude plugin instructions — note what guidance memsearch adds
7. Add custom `memory-search` skill only if gaps exist in built-in guidance

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-05

View File

@ -0,0 +1,103 @@
## Context
The vault knowledge graph (Graphify over `~/Documents/SecondBrain`), the extraction model (`qwen2.5-coder:7b`), and the episodic layer (memsearch) are built and validated. What does not yet exist is the runtime integration layer: Claude Code sessions do not automatically know the vault exists, do not inject context at startup, do not update the graph when vault notes are edited, and do not record activity at session end.
This design specifies the global Claude Code plugin that wires all of that together. It is installed once at the user level (`~/.claude/`) and inherited by every project. The design rests on ADR-007 (lazy freshness — event-driven hooks, no daemon), ADR-008 (markdown-as-truth, graphs are disposable), ADR-009 (global plugin), ADR-013 (build-first / validate against fixture set before full vault), and ADR-014 (graph connectivity comes from authored hub notes + wikilinks, not auto-clustering; facet-tag-to-edge question is open).
Current constraints:
- Graphify v0.8.31 anchored; the `reasoning_effort:"none"` patch to the installed `llm.py` is required for extraction and is lost on `pip upgrade` — fragile.
- `GRAPHIFY_OLLAMA_NUM_CTX` does NOT propagate through Graphify's `/v1` endpoint; context must be baked via a Modelfile variant (finding from `graphify-ollama-setup` change).
- Vault location: `~/Documents/SecondBrain` (ADR-012). Project graph convention: `<project-root>/graphify-out/graph.json`.
- Rebuild stamp: `~/.cache/graphify/vault-rebuild.stamp`; default threshold 7 days.
## Goals / Non-Goals
**Goals:**
- Register three lifecycle hooks in the global Claude Code settings: SessionStart (staleness check + context injection), PostToolUse (graph update on vault write/edit), SessionEnd (episodic journal).
- Deliver three skills that carry behavioral conventions to the model: `memory-query`, `memory-write`, `memory-reorganize`.
- Provide a configurable plugin config block (vault path, Graphify output dir, Ollama model, `num_ctx`, stale threshold, env vars).
- Validate the end-to-end hook-and-skills path against the fixture set (ADR-013) before the full vault is live.
**Non-Goals:**
- Vault migration scaffolding (hub notes + wikilinks) — a first-class prerequisite per ADR-014, but not a deliverable of this change. The plugin is designed assuming this scaffolding will exist; without it, retrieval is degraded but the plugin still functions.
- Per-project code-graph indexing (Step 2e) — separate, later change.
- Bulk vault migration (Step 5) — separate, later change.
- Memsearch installation or configuration — already installed; this plugin adds hooks that complement it.
- Vault sync mechanism (git / Syncthing) — separate, later concern.
## Decisions
**Hook input/output mechanism: stdin JSON in, `additionalContext` out.**
Hooks receive input as JSON on stdin (not as env vars or CLI args). The hook reads fields with `jq`: `jq -r '.session_id'`, `jq -r '.tool_input.file_path'`, `jq -r '.cwd'`, `jq -r '.source'`, etc. SessionStart injects context by writing JSON to stdout with an `additionalContext` field and exiting 0 — this is the confirmed Claude Code mechanism for hook-to-model context injection. (Source: Claude Code hooks documentation.)
**Hook implementation: thin shell wrappers over Graphify CLI.**
The hooks are shell scripts registered in `~/.claude/settings.json`. They call Graphify CLI directly via the Bash tool path (not the MCP server) — simpler, no process to manage, all logic in the tool we already have. The MCP server remains an optional query path the AI can use during a session; it is not required for hooks.
*Alternative considered:* Python wrapper scripts with richer logic (retry, logging). Rejected — the hooks need to stay thin and debuggable. If a hook fails it should fail visibly, not silently. A one-line `graphify update --file "$FILE"` is auditable; a 50-line Python script is not.
**SessionStart injects god-nodes via `GRAPH_REPORT.md`-equivalent query, not by reading `graph.json` raw.**
The hook reads the pre-computed god-node list (top N most-connected nodes by degree) via `graphify query --budget N` or by reading `GRAPH_REPORT.md` if it exists at the output dir. It does NOT dump `graph.json` into context — that would blow the token budget. The god-node list is a compact, human-readable "map of what's known."
*Alternative considered:* Injecting the full vault listing from `ls`. Rejected — too much noise, no semantic structure, does not scale.
**SessionStart also injects `convention/*` note summaries (not full content).**
The hook runs `graphify query "convention"` (or equivalent tag-filtered query) to pull `convention/*` node summaries. Only the `summary` frontmatter field is included in injection, not the full note text. This respects progressive-disclosure and keeps context lean.
*Tag-query note:* Whether `convention/` tags create graph edges is an open question (ADR-014). The hook uses the `graphify query "convention"` semantic path as the primary mechanism; if that proves insufficient, a fallback `grep -r "^tags:.*convention" ~/Documents/SecondBrain` to collect frontmatter summaries is viable and simpler. The design does NOT assume tag-based graph traversal works — both paths are planned.
**PostToolUse hook targets vault writes only.**
The hook fires on `Write` and `Edit` tool calls, but ONLY when the target file path matches `~/Documents/SecondBrain/**/*.md`. Project-repo writes are ignored. This constraint is enforced in the hook's guard clause before any Graphify call.
**SessionEnd hook appends to a daily journal note (vault, not project repo).**
The journal note lives at `~/Documents/SecondBrain/journal/YYYY-MM-DD.md` and is append-only. The hook collects vault notes touched in the session (from the PostToolUse hook's side-effect log or from Claude's session memory) and writes a timestamped entry: project name, notes touched, brief session intent. This is the input memsearch indexes for episodic recall.
**Stale-rebuild stamp approach — rebuild is DETACHED, not inline.**
The staleness check reads `~/.cache/graphify/vault-rebuild.stamp` (a file whose mtime or content is the last `--force` rebuild timestamp). If older than N days (default 7, configurable), SessionStart spawns `graphify extract <vault-path> --backend ollama --model <configured-model> --force` as a **detached background process** (e.g. `nohup graphify ... >> <logfile> 2>&1 &` or `setsid`) and returns immediately. The hook MUST NOT block waiting for the rebuild — SessionStart is a blocking hook (the session does not start until the hook returns), so a multi-minute inline rebuild would freeze the user. (Source: Claude Code hooks documentation — SessionStart has no async option and must return promptly; target sub-second for the synchronous path.)
A lock file (e.g. `~/.cache/graphify/vault-rebuild.lock`) prevents concurrent sessions from spawning duplicate rebuilds. The background process touches the stamp on successful completion. The rebuilt graph is not visible to the session that spawned the rebuild; it is available to the next session. The current session proceeds with the existing (possibly stale) graph — this is an accepted tradeoff.
*Ghost-node note:* `graphify update --file` merges but does not prune deleted nodes. The periodic `--force` rebuild (triggered by the stamp check) is the only way to clear ghost nodes. 7-day default balances freshness against extraction cost (~38K tokens per full vault run at current vault size).
**Three skills, not one.**
`memory-query`, `memory-write`, `memory-reorganize` are separate skills loaded on demand rather than a single monolithic `memory` skill. This keeps each skill's context footprint small — a session doing only writes doesn't pull in reorganize guidance, and vice versa.
**memory-query skill must NOT assume tag-based graph traversal (ADR-014 open question).**
The skill teaches two distinct retrieval paths: (1) graph traversal via `graphify query` / `path` / `explain` (connection-based, follows edges from explicit references and wikilinks), and (2) tag-attribute filtering (treat as a separate query mechanism, not graph traversal). The skill notes explicitly that whether shared frontmatter facet tags create graph edges is unverified — use both paths but do not conflate them.
**Config lives in the plugin's settings block, not in CLAUDE.md.**
Vault path, output dir, model, num_ctx, stale threshold, env vars — all go in the plugin's `settings.json` contribution (or a `.env`-style config file the hooks source). Per-project CLAUDE.md holds only pointers/tags, not the system config.
## Risks / Trade-offs
- **`reasoning_effort:"none"` patch lost on `pip upgrade`** → Treat installed Graphify as pinned; document the patch and the pinned version in the plugin README. Track upstream issue tracker for an official flag. Mitigation: the plugin's setup instructions include a post-install verification step that confirms the patch is present.
- **`GRAPHIFY_OLLAMA_NUM_CTX` env-var does not propagate** → Bake context into the Ollama model variant via Modelfile (as discovered in `graphify-ollama-setup`). The plugin config should reference the Modelfile-baked variant name (e.g. `qwen25-coder-7b-16k`), not the base model name.
- **SessionStart hook latency — RESOLVED** → SessionStart is a blocking hook with no async option (Claude Code hooks documentation). The heavy `--force` rebuild is now **detached as a background process** (lock-guarded to prevent duplication). The synchronous path (read stamp, spawn background if stale, inject context) must be sub-second. Tradeoff: the session that triggers a rebuild sees the existing graph; next session sees the rebuilt one. This is accepted; the 7-day threshold makes rebuilds rare.
- **Convention-injection path uncertainty** → Whether `graphify query "convention"` reliably surfaces `convention/*` notes depends on the facet-tag-to-edge question (ADR-014 open). Fallback: shell grep over vault frontmatter for `convention/` tags + read `summary:` field directly. Design both paths; switch based on empirical testing during fixture validation.
- **Journal note accumulation** → Daily journal notes grow over time; memsearch handles indexing, but old notes are not pruned. Mitigation: note in the plugin README that journal notes are part of the vault and subject to normal vault governance (reorg skill can consolidate old journals).
- **Vault migration scaffolding is a prerequisite, not guaranteed** → If hub notes + wikilinks are not yet authored for the fixture set, retrieval quality will be degraded (ADR-014: no authored structure → no graph connectivity). The plugin is still installable and hooks still fire; only retrieval utility is reduced. This is the expected state at initial install; the migration scaffolding change is the unlock.
## Migration Plan
All steps are local and non-destructive:
1. **Create plugin directory structure**: `~/.claude/plugins/memory/` with hooks/ and skills/ subdirectories.
2. **Write hook scripts**: `session-start.sh`, `post-tool-use-write.sh`, `session-end.sh`. Each is a thin shell wrapper; test each script standalone before registering.
3. **Register hooks** in `~/.claude/settings.json` global config: SessionStart → `session-start.sh`, PostToolUse (Write + Edit) → `post-tool-use-write.sh`, SessionEnd → `session-end.sh`.
4. **Write skill files**: `memory-query.md`, `memory-write.md`, `memory-reorganize.md` in `~/.claude/plugins/memory/skills/`. Register in plugin manifest.
5. **Write plugin config** (`~/.claude/plugins/memory/config.yaml` or equivalent): vault path, output dir, model, num_ctx, stale threshold, env vars.
6. **Validate against fixture set** (ADR-013): start a session with the fixture project; confirm SessionStart injects god-nodes + conventions; write a test vault note and confirm PostToolUse fires `graphify update`; end session and confirm journal note appended. Use the fixture set, not the full vault, for this first pass.
Rollback: deregister hooks from `settings.json`; plugin directory can remain. No vault content is mutated by the hooks (extraction is read-only over notes; journal is append-only to a new file).
## Open Questions
1. ~~**Does Claude Code support async/backgrounded SessionStart hooks?**~~ **RESOLVED (2026-06-05):** SessionStart is a blocking hook with no async option (Claude Code hooks documentation). The rebuild is now detached as a background process. See the stale-rebuild decision above.
2. **Convention-injection path**: which mechanism surfaces `convention/*` note summaries most reliably — `graphify query "convention"` or frontmatter grep + `summary:` field read? Decide by testing both against the fixture set during step 6.
3. **Journal note format**: what level of detail is useful for memsearch episodic recall? Minimum: project name + note paths touched + date. Optional: session intent, decisions made. Settle during fixture validation.
4. **Project graph path injection**: SessionStart should inject `--graph <project-root>/graphify-out/graph.json` if a project graph exists at that path. The hook reads `.cwd` from stdin JSON (confirmed available — Claude Code hooks documentation), which provides the working directory. A `git rev-parse --show-toplevel` from that cwd or a `$CLAUDE_PROJECT_DIR` env var convention can determine project root. Mechanism available; exact convention to settle during implementation.
5. **Facet-tag-to-edge question** (ADR-014 deferred): before designing `memory-query`'s tag-filter path, verify empirically whether shared facet tags create graph edges in the fixture graph. One test: build a fixture graph with two notes sharing `tool/graphify`, inspect `graph.json` for an edge between them.
**RESOLVED (2026-06-05) — hook input/output mechanisms:**
- **Session ID availability**: `session_id` is available in hook stdin JSON (`jq -r '.session_id'`) for all hooks that receive it (PostToolUse, SessionEnd). Use for per-session temp file naming. No session-id env var exists; stdin is the only source. (Claude Code hooks documentation.)
- **PostToolUse file-path access**: The hook reads `.tool_input.file_path` from stdin JSON (`jq -r '.tool_input.file_path'`). Also available: `.tool_name` for filtering by tool type. The vault-path guard is done inside the hook after reading this field.
- **Context injection mechanism**: SessionStart injects text into the model context by writing `{"additionalContext": "..."}` JSON to stdout and exiting 0. All prior references to "inject via context" or hand-waved mechanisms now refer to this confirmed mechanism. (Claude Code hooks documentation.)

View File

@ -0,0 +1,38 @@
## Why
The memory system's knowledge graph (vault + Graphify) and episodic layer (memsearch) are built and validated, but Claude Code has no runtime awareness of them — hooks are not registered, skills do not exist, and there is no mechanism by which a session injects vault context or records what was touched. This change delivers the global Claude Code plugin that makes the system live: hooks that inject knowledge automatically and update the graph on writes, plus three on-demand skills that teach Claude when and how to read from and write to the vault.
## What Changes
- **New: `~/.claude/plugins/memory/` global plugin** — installs hooks (SessionStart, PostToolUse, SessionEnd) and the three skills at the user level; all projects inherit without per-project setup.
- **New: SessionStart hook** — staleness check (rebuild stamp age vs. threshold) + forced rebuild if stale; injects vault god-nodes, current-project `convention/*` summaries, episodic journal pointer, and project graph path if a project graph exists.
- **New: PostToolUse hook** (Write/Edit on vault `.md`) — runs `graphify update --file <path>` to merge changed notes into the vault graph event-driven, no polling.
- **New: SessionEnd hook** — appends a dated episodic journal note with pointers to vault notes touched in the session.
- **New: `memory-query` skill** — when/how to use `graphify query` / `path` / `explain` vs. memsearch; god-node discipline; `--budget` / `--dfs` usage; cross-client lookups via `--graph`; tag-scoped retrieval treated as a separate path from graph traversal (open question per ADR-014).
- **New: `memory-write` skill** — when knowledge is evergreen vs. project-ephemeral; required frontmatter contract (`summary` + `scope/`); vault-not-repo rule.
- **New: `memory-reorganize` skill** — plan-mode consolidation/promotion procedure; when to trigger `--force` rebuild; human-review guardrails.
- **New: plugin config block** — vault path, Graphify output dir, Ollama model name, `num_ctx`, rebuild-stale threshold (days), env vars (`OLLAMA_FLASH_ATTENTION`, `GRAPHIFY_OLLAMA_NUM_CTX`, `GRAPHIFY_OLLAMA_KEEP_ALIVE`).
**Out of scope / prerequisite (not tasks here):** Vault migration scaffolding — authoring hub notes + wikilinks — is a prerequisite for useful retrieval (ADR-014: Graphify is a structure extractor, not a topic clusterer; connectivity comes from authored hub notes + wikilinks). This change assumes that scaffolding either exists for the fixture set or will be done as a parallel prerequisite.
## Capabilities
### New Capabilities
- `memory-plugin-hooks`: The three lifecycle hooks (SessionStart / PostToolUse / SessionEnd) that make graph freshness and context injection automatic. Covers the shell wrapper logic, staleness check, stamp file, `graphify update --file` invocation, and journal note append.
- `memory-plugin-skills`: The three skills (`memory-query`, `memory-write`, `memory-reorganize`) that teach Claude the behavioral conventions for the memory system. Covers query-pattern guidance, frontmatter contract, write-path rules, and reorganize procedure.
### Modified Capabilities
<!-- None. This change adds the plugin runtime layer. The underlying specs (vault-graph-build,
local-model-selection, incremental-migration, reference-extraction-benchmark) are consumed
as prerequisites; their requirements do not change. -->
## Impact
- **New global plugin** at `~/.claude/plugins/memory/` (or equivalent user-level plugin install path); no per-project files created.
- **Dependencies:** Graphify (`graphifyy`, anchored to v0.8.31), Ollama with `qwen2.5-coder:7b` (or the selected model from `local-model-selection`), memsearch plugin already installed.
- **Known fragile runtime dependency:** `reasoning_effort:"none"` patch to Graphify's installed `llm.py` — lost on `pip upgrade`; tracked as a risk. Treat installed version as pinned until upstream resolves.
- **Env vars** set in plugin config: `OLLAMA_FLASH_ATTENTION=1`, `GRAPHIFY_OLLAMA_NUM_CTX=8192`, `GRAPHIFY_OLLAMA_KEEP_ALIVE=5`.
- **Consumes:** built vault graph (`vault-graph-build`), selected extraction model (`local-model-selection`), ADR-007 (lazy freshness), ADR-009 (global plugin), ADR-011 (six-facet taxonomy), ADR-013 (build-first/migrate-incrementally), ADR-014 (graph connectivity from authored structure).
- **Unblocks:** the per-project code-graph onboarding phase (Step 2e, separate change) and bulk vault migration (Step 5, separate change).

View File

@ -0,0 +1,76 @@
## ADDED Requirements
### Requirement: SessionStart hook — staleness check and detached background rebuild
The plugin SHALL register a SessionStart hook that reads the rebuild stamp file at `~/.cache/graphify/vault-rebuild.stamp`. If the stamp is absent or its mtime is older than the configured threshold (default: 7 days), the hook SHALL spawn `graphify extract <vault-path> --backend ollama --model <configured-model> --force` as a **detached background process** (nohup/setsid, output redirected to a log file) and SHALL return immediately without waiting for it. The background process SHALL update the stamp file upon successful completion and remove the lock file. The hook SHALL NOT block on the rebuild — SessionStart blocks the entire session until the hook returns, so the synchronous path must be sub-second. (Source: Claude Code hooks documentation — SessionStart has no async option.) The hook SHALL write a lock file (`~/.cache/graphify/vault-rebuild.lock`) before spawning, so concurrent sessions do not launch duplicate rebuilds.
#### Scenario: Stamp is fresh (within threshold)
- **WHEN** the SessionStart hook fires and the rebuild stamp mtime is within the configured threshold
- **THEN** no extraction run is triggered and the hook proceeds to context injection
#### Scenario: Stamp is stale or absent
- **WHEN** the SessionStart hook fires and the rebuild stamp is absent or older than the configured threshold
- **THEN** the hook checks for a lock file; if absent, spawns a detached background rebuild and writes the lock; in all cases the hook continues immediately to context injection using the existing graph; the rebuilt graph becomes available to the next session
#### Scenario: Rebuild already in progress (lock file present)
- **WHEN** the SessionStart hook fires and the lock file already exists (rebuild spawned by a concurrent session)
- **THEN** the hook does NOT spawn another rebuild; it logs that a rebuild is in progress and proceeds to context injection
#### Scenario: Extraction fails during stale rebuild
- **WHEN** the background rebuild process exits non-zero
- **THEN** the background process logs the error to its log file and removes the lock file without touching the stamp; the current session is unaffected (already past injection); the next session will attempt the rebuild again
### Requirement: SessionStart hook — vault context injection
The plugin SHALL register a SessionStart hook that injects the following into Claude's context at the start of each session: (a) the vault graph's god-node summary (top N most-connected nodes by degree, sourced from `GRAPH_REPORT.md` or equivalent query), (b) the summaries of `convention/*` vault notes (sourced via `graphify query "convention"` or, as a fallback, by grepping vault frontmatter for `convention/` tags and reading the `summary:` field), and (c) a pointer to the current day's episodic journal note path. The hook SHALL deliver this content by writing `{"additionalContext": "<assembled-text>"}` JSON to stdout and exiting 0 — this is the confirmed Claude Code mechanism for hook-to-model context injection. (Source: Claude Code hooks documentation.) The hook reads its input from stdin JSON; relevant fields include `.cwd` (working directory) and `.source` (startup|resume|clear|compact).
#### Scenario: Vault graph and convention notes exist
- **WHEN** SessionStart fires and the vault graph and at least one `convention/*` note exist
- **THEN** the injected context contains the god-node list, all `convention/*` summaries, and the journal note path
#### Scenario: No convention notes exist yet
- **WHEN** SessionStart fires and no `convention/*` notes exist in the vault
- **THEN** the injected context contains the god-node list and journal pointer; the conventions section is omitted without error
#### Scenario: Project graph exists for the current project
- **WHEN** SessionStart fires and a file exists at `<project-root>/graphify-out/graph.json`
- **THEN** the injected context also includes the project graph path, so Claude can query it via `graphify query --graph <path>`
### Requirement: PostToolUse hook — event-driven vault graph update
The plugin SHALL register a PostToolUse hook that fires after any `Write` or `Edit` tool call. The hook SHALL check whether the target file path is a `.md` file under the configured vault path (`~/Documents/SecondBrain`). If it is, the hook SHALL run `graphify update --file <path>` to merge the changed note into the vault graph. If the target path is outside the vault, the hook SHALL do nothing.
#### Scenario: A vault note is written or edited
- **WHEN** a Write or Edit tool call completes and the target path is under the vault directory
- **THEN** the hook runs `graphify update --file <target-path>` synchronously before Claude proceeds
#### Scenario: A non-vault file is written or edited
- **WHEN** a Write or Edit tool call completes and the target path is outside the vault directory
- **THEN** the hook does nothing; no graphify call is made
#### Scenario: graphify update fails for a vault write
- **WHEN** `graphify update --file <path>` exits non-zero
- **THEN** the hook logs the error but does NOT block the tool call result from being returned; the graph may be transiently stale
### Requirement: SessionEnd hook — episodic journal append
The plugin SHALL register a SessionEnd hook that appends a dated entry to the daily journal note at `<vault-path>/journal/YYYY-MM-DD.md` (creating the file if it does not exist). The entry SHALL include at minimum: the current project name (or path), the vault note paths touched during the session, and a UTC timestamp. The journal file is append-only; existing content is never overwritten.
#### Scenario: Session ends with vault notes touched
- **WHEN** the SessionEnd hook fires and at least one vault note was written or edited during the session
- **THEN** the hook appends a timestamped entry to the daily journal file listing the touched note paths and the project context
#### Scenario: Session ends with no vault writes
- **WHEN** the SessionEnd hook fires and no vault notes were touched
- **THEN** the hook still appends a minimal entry (timestamp + project) so the session is recorded for episodic recall
#### Scenario: Journal file does not exist yet for the day
- **WHEN** the SessionEnd hook fires and `<vault-path>/journal/YYYY-MM-DD.md` does not exist
- **THEN** the hook creates the file with appropriate frontmatter (summary, scope/global, type/log) and appends the session entry
### Requirement: Plugin configuration block
The plugin SHALL expose a configuration file at `~/.claude/plugins/memory/config.yaml` that allows setting: vault path, Graphify output directory, Ollama model name (Modelfile-baked variant), num_ctx, rebuild-stale threshold in days, and the env vars `OLLAMA_FLASH_ATTENTION`, `GRAPHIFY_OLLAMA_NUM_CTX`, `GRAPHIFY_OLLAMA_KEEP_ALIVE`. All hooks SHALL read from this config rather than hardcoding paths or values.
#### Scenario: Default config is used
- **WHEN** the config file exists with no overrides for a setting
- **THEN** the hook uses the default value for that setting (vault: `~/Documents/SecondBrain`, threshold: 7 days, model: `qwen25-coder-7b-16k`)
#### Scenario: User overrides vault path
- **WHEN** the config file specifies a non-default vault path
- **THEN** all hooks use the configured path for vault detection, graph paths, and journal writes

View File

@ -0,0 +1,62 @@
## ADDED Requirements
### Requirement: memory-query skill — graph retrieval guidance
The plugin SHALL provide a `memory-query` skill that teaches Claude when and how to query the vault knowledge graph versus the episodic memsearch layer. The skill SHALL specify: (a) use `graphify query "<topic>"` for evergreen knowledge questions ("how do we…", "what do we know about…"), (b) use `graphify path "<node-a>" "<node-b>"` to find how two concepts relate, (c) use `graphify explain "<node>"` for a deep-dive with neighbors, (d) add `--budget N` to cap answer size (default ~2000 tokens), (e) add `--dfs --budget N` for bounded depth-first traversal, (f) add `--graph <path>` to query a specific project graph instead of the vault graph. The skill SHALL explicitly state that god-nodes are the entry point — query god-nodes first, then scalpel to specific nodes. The skill SHALL treat tag-scoped retrieval (e.g. "all notes tagged `client/acme`") as a SEPARATE query path from graph traversal, noting that whether shared frontmatter facet tags create graph edges is unverified (ADR-014 open question); Claude SHALL NOT assume facet tags enable graph traversal. For "what happened when" questions, the skill SHALL direct Claude to memsearch in natural language rather than the vault graph.
#### Scenario: Claude needs to answer a knowledge question during a task
- **WHEN** a task touches a tool, client, or domain the vault may know about
- **THEN** Claude runs `graphify query "<topic>"` before relying on training knowledge, using `--budget` to keep context lean
#### Scenario: Claude needs to find how two concepts relate
- **WHEN** a task requires understanding the relationship between two vault entities (e.g. client and a tool they use)
- **THEN** Claude runs `graphify path "<entity-a>" "<entity-b>"` to traverse the graph path between them
#### Scenario: Cross-client knowledge lookup
- **WHEN** Claude needs to find all vault knowledge about a tool or domain regardless of which client it came from
- **THEN** Claude runs `graphify query "<tool-or-domain>"` against the vault graph (not `--graph <project-path>`) to get cross-client results
#### Scenario: Project-specific knowledge lookup
- **WHEN** Claude needs knowledge specific to the current project's code graph
- **THEN** Claude runs `graphify query "<topic>" --graph <project-root>/graphify-out/graph.json` using the project graph path injected by SessionStart
#### Scenario: Episodic question ("what was I working on last week?")
- **WHEN** a question is time-anchored ("what did we decide last Tuesday", "what was I working on yesterday")
- **THEN** Claude queries memsearch in natural language, NOT the vault graph
### Requirement: memory-write skill — when and how to write vault knowledge
The plugin SHALL provide a `memory-write` skill that teaches Claude when knowledge should be written to the vault and what the required frontmatter contract is. The skill SHALL specify: write to the vault when knowledge is evergreen and reusable across projects (tool/API behavior, conventions, client-specific facts worth reusing); do NOT write project-ephemeral state to the vault (that goes to project files or the episodic layer). The skill SHALL specify the required frontmatter fields: `summary` (one-line, written at note creation — not deferred — this is the human-authored router hint Graphify does not generate), `scope/global` or `scope/project` (default new tool/domain knowledge to `scope/global`; use `scope/project` when specific to how a client uses something), and at least one facet tag from the six-facet taxonomy (`type/`, `client/`, `project/`, `domain/`, `tool/`, `convention/`). The skill SHALL state the vault-not-repo rule: write knowledge ONLY to `~/Documents/SecondBrain`, never silently into a project repo. The skill SHALL note that writing a vault note automatically triggers the PostToolUse hook to update the graph — no manual graphify call is needed after writing.
#### Scenario: Claude discovers how a tool's API behaves
- **WHEN** Claude learns something about a tool's authentication, rate limits, or endpoint quirks that could apply to future sessions or projects
- **THEN** Claude writes a note to the vault with the required frontmatter (summary, scope/global, type/reference, tool/<name>) rather than embedding it in the project's CLAUDE.md
#### Scenario: Claude establishes a convention that should apply beyond this task
- **WHEN** Claude and the user settle on a convention (coding style, workflow, decision rule) that should propagate to other projects
- **THEN** Claude writes a `convention/<name>` note to the vault with the required frontmatter (summary, scope, type/convention)
#### Scenario: Knowledge is project-specific (how this client uses a tool)
- **WHEN** the knowledge describes behavior specific to one client's setup, not the tool generally
- **THEN** Claude writes the note with `scope/project` and a `client/<name>` facet tag rather than `scope/global`
#### Scenario: Claude tries to record project-ephemeral state
- **WHEN** the information is specific to the current task and not reusable in future sessions or projects
- **THEN** Claude does NOT write it to the vault; it stays in the project repo or episodic session notes
### Requirement: memory-reorganize skill — consolidation and plan-mode promotion
The plugin SHALL provide a `memory-reorganize` skill that teaches Claude the procedure for consolidating, deduplicating, promoting, and reorganizing vault notes. The skill SHALL specify: (a) reorganization MUST happen in plan mode — Claude proposes changes for human review before executing; (b) triggers include: duplicate coverage noticed across notes, a `scope/project` fact observed to recur across 2+ clients (promotion candidate), a note grown beyond the L1 "one concept, ~200 lines" discipline; (c) the consolidation procedure is: identify candidate notes, propose a merge/split/promote/rename plan, await human approval, execute edits, then run `graphify extract <vault-path> --force` to rebuild the graph clean; (d) a `--force` rebuild is required after any reorganization that removes, renames, or restructures notes (to clear ghost nodes); (e) `--force` rebuild is ALSO triggered when the SessionStart stale check fires, so reorganize skill's rebuild invocation is consistent with the staleness-check mechanism. The skill SHALL warn that `graphify update --file` does NOT prune deleted or renamed nodes — only `--force` rebuilds clear ghost nodes.
#### Scenario: Claude notices duplicate coverage across two vault notes
- **WHEN** Claude identifies two notes that cover the same concept with overlapping content
- **THEN** Claude enters plan mode: proposes which note to keep, what to merge, and what to discard — and awaits human approval before making any edits
#### Scenario: A project-scoped fact has recurred across multiple clients
- **WHEN** Claude observes that a fact tagged `scope/project` has appeared in 2+ client contexts, suggesting it is actually global knowledge
- **THEN** Claude proposes promoting the note to `scope/global` (updating frontmatter, merging client-specific variants if needed) in plan mode before executing
#### Scenario: Reorganization is complete and graph needs to be rebuilt
- **WHEN** any reorganization edit has been approved and executed (notes deleted, renamed, or restructured)
- **THEN** Claude runs `graphify extract <vault-path> --backend ollama --model <configured-model> --force` to clear ghost nodes and rebuild a clean graph; updates the rebuild stamp
#### Scenario: Note has grown beyond single-concept discipline
- **WHEN** Claude observes a vault note has grown beyond ~200 lines or covers more than one distinct concept
- **THEN** Claude proposes a split into two or more focused notes in plan mode, with proposed filenames, frontmatter, and the wikilinks that would connect them — before executing

View File

@ -0,0 +1,50 @@
## 1. Plugin directory and config
- [x] 1.1 Create plugin directory structure: `~/.claude/plugins/memory/` with `hooks/` and `skills/` subdirectories
- [x] 1.2 Write `~/.claude/plugins/memory/config.yaml` with configurable fields: vault path (`~/Documents/SecondBrain`), Graphify output dir, Ollama model (`qwen25-coder-7b-16k`), num_ctx (8192), stale threshold (7 days), and env vars (`OLLAMA_FLASH_ATTENTION=1`, `GRAPHIFY_OLLAMA_NUM_CTX=8192`, `GRAPHIFY_OLLAMA_KEEP_ALIVE=5`)
- [x] 1.3 Write a `README.md` for the plugin documenting: the `reasoning_effort:"none"` patch requirement, the pinned Graphify version (v0.8.31), the Modelfile-baked context approach (`qwen25-coder-7b-16k`), and how to verify the patch is present after any `pip upgrade`
## 2. SessionStart hook
- [x] 2.1 Write `~/.claude/plugins/memory/hooks/session-start.sh`: read stdin JSON via `jq` (fields: `.cwd`, `.source`); source config; check `~/.cache/graphify/vault-rebuild.stamp` mtime vs. threshold — if stale or absent AND no lock file at `~/.cache/graphify/vault-rebuild.lock`, spawn `nohup graphify extract <vault-path> --backend ollama --model <model> --force >> <logfile> 2>&1 &` as a **detached background process**, write the lock file (background process removes it on completion), and continue immediately; do NOT wait for the rebuild (SessionStart blocks the session — it must return sub-second on the synchronous path). Log that a background rebuild was triggered.
- [x] 2.2 Extend `session-start.sh`: inject vault god-node summary — read from `GRAPH_REPORT.md` or equivalent at the configured output dir; assemble context string (compact "map of what's known" block, not raw `graph.json`)
- [x] 2.3 Extend `session-start.sh`: inject `convention/*` note summaries — attempt `graphify query "convention" --budget 500`; fall back to `grep -rl "convention/" <vault-path> | xargs grep -h "^summary:"` if graph query returns empty; omit section silently if no convention notes found
- [x] 2.4 Extend `session-start.sh`: inject episodic journal pointer — compute today's journal path (`<vault-path>/journal/YYYY-MM-DD.md`) and add as a one-line pointer; inject project graph path (`<project-root>/graphify-out/graph.json`) if that file exists (read `.cwd` from stdin JSON as working directory base; use `git rev-parse --show-toplevel` from there or `$CLAUDE_PROJECT_DIR` convention)
- [x] 2.5 Emit all injected content to stdout as `{"additionalContext": "<assembled-text>"}` JSON and exit 0 — this is the confirmed Claude Code mechanism for hook-to-model context injection (Claude Code hooks documentation)
- [x] 2.6 Test `session-start.sh` standalone (not via Claude Code harness): supply mock stdin JSON (`echo '{"cwd":"/tmp","source":"startup"}' | ./session-start.sh`); run with a clean stamp, a stale stamp (confirm background spawn, no block), a lock file already present (confirm no duplicate spawn), and a missing vault — confirm each path behaves as specified in `memory-plugin-hooks/spec.md`
## 3. PostToolUse hook
- [x] 3.1 Write `~/.claude/plugins/memory/hooks/post-tool-use-write.sh`: source config; read stdin JSON via `jq` — extract `FILE_PATH=$(jq -r '.tool_input.file_path')` and `TOOL_NAME=$(jq -r '.tool_name')`; guard clause — skip if `$FILE_PATH` does not match `<vault-path>/**/*.md`; if it matches, run `graphify update --file "$FILE_PATH"`; log error but do NOT block on non-zero exit (exit 0 always); also append `$FILE_PATH` to per-session temp log (see task 4.2)
> **Deviation (2026-06-05):** `graphify update --file` does not exist in Graphify v0.8.31. The correct incremental update for markdown/doc files is `graphify extract <vault-path> --update --backend ollama --model <model>` which uses SHA-256 content hashing to skip unchanged files. Running this synchronously in PostToolUse would block on Ollama. **Adopted approach:** PostToolUse deletes `~/.cache/graphify/vault-rebuild.stamp` (invalidates freshness). Next SessionStart detects the missing stamp and spawns `graphify extract --update` as a detached background process, refreshing only changed files. Graph freshness is deferred by at most one session start, not immediate.
- [x] 3.2 Test `post-tool-use-write.sh` standalone: supply mock stdin JSON (`echo '{"tool_name":"Write","tool_input":{"file_path":"<vault-path>/test.md"}}' | ./post-tool-use-write.sh`); test with a vault `.md` path (confirm graphify update fires), a non-vault path (confirm nothing fires), and a path where graphify returns non-zero (confirm error is logged, script exits 0)
## 4. SessionEnd hook
- [x] 4.1 Write `~/.claude/plugins/memory/hooks/session-end.sh`: read stdin JSON via `jq` — extract `SESSION_ID=$(jq -r '.session_id')` and `REASON=$(jq -r '.reason')`; source config; compute today's journal path; if file does not exist, create it with minimal frontmatter (`summary:`, `scope/global`, `type/log`); append a timestamped entry with: current project (from `$CLAUDE_PROJECT_DIR` or `git rev-parse --show-toplevel`), vault notes touched this session (read from temp log at `/tmp/claude-vault-touched-$SESSION_ID` — see task 4.2), UTC timestamp. Note: SessionEnd has no decision control (exit code ignored); use for cleanup/logging only.
- [x] 4.2 Extend `post-tool-use-write.sh` to append successfully-updated vault note paths to a per-session temp file `/tmp/claude-vault-touched-$SESSION_ID` where `SESSION_ID` is read from stdin JSON via `jq -r '.session_id'`; `session-end.sh` reads this same file using its own stdin `session_id`. (Confirmed available: `session_id` is a field on stdin JSON for both PostToolUse and SessionEnd — Claude Code hooks documentation.)
- [x] 4.3 Test `session-end.sh` standalone: supply mock stdin JSON (`echo '{"session_id":"test-123","reason":"exit"}' | ./session-end.sh`); run with notes in the temp file at `/tmp/claude-vault-touched-test-123` (confirm journal entry created with paths), run with no temp file (confirm minimal entry is still appended), run twice in one day (confirm append-only, no overwrite)
## 5. Skills authoring
- [x] 5.1 Write `~/.claude/plugins/memory/skills/memory-query.md`: cover all patterns from `memory-plugin-skills/spec.md` — god-node-first discipline, `graphify query`/`path`/`explain` with `--budget`/`--dfs`/`--graph`, cross-client lookups, explicit call-out that tag-based graph traversal is unverified (ADR-014), episodic questions → memsearch
- [x] 5.2 Write `~/.claude/plugins/memory/skills/memory-write.md`: cover all rules from the spec — evergreen vs. ephemeral test, required frontmatter contract (`summary` written now, `scope/`, at least one facet tag), vault-not-repo rule, note that PostToolUse hook fires automatically on vault writes
- [x] 5.3 Write `~/.claude/plugins/memory/skills/memory-reorganize.md`: cover the spec — plan-mode-only rule, three triggers (duplicate, recurrence-promotion, oversized note), consolidation procedure, `--force` rebuild requirement after reorganization, ghost-node explanation
- [x] 5.4 Register the three skills in the plugin manifest (or `~/.claude/settings.json`) so they are available as on-demand skills in Claude Code sessions
- **Note (2026-06-05):** Skills in `~/.claude/plugins/memory/skills/` require a marketplace entry to be auto-discovered via `enabledPlugins`. The `.claude-plugin/plugin.json` manifest is in place; a marketplace registration in `~/.claude/settings.json` `extraKnownMarketplaces` pointing to `~/.claude/plugins/` is needed to complete skill auto-loading. Hooks fire independently of this.
## 6. Hook registration
- [x] 6.1 Register `session-start.sh` as a SessionStart hook in `~/.claude/settings.json`
- [x] 6.2 Register `post-tool-use-write.sh` as a PostToolUse hook for `Write` and `Edit` tool calls in `~/.claude/settings.json`
- [x] 6.3 Register `session-end.sh` as a SessionEnd hook in `~/.claude/settings.json`
## 7. End-to-end validation against fixture set (ADR-013)
- [x] 7.1 Start a Claude Code session in the fixture project — confirm SessionStart injects god-node list, at least one convention summary (if present), journal pointer, and project graph path
- [x] 7.2 Write a test vault note during the session — confirm PostToolUse fires `graphify update --file` (verify from hook log or graph.json mtime)
> **Clarification (2026-06-05):** PostToolUse fires stamp invalidation (deletes `~/.cache/graphify/vault-rebuild.stamp`), triggering deferred incremental update on next SessionStart, per deviation note in task 3.1.
- [x] 7.3 End the session — confirm SessionEnd appended a journal entry to `<vault-path>/journal/YYYY-MM-DD.md` with the note paths touched
- [x] 7.4 Trigger a stale rebuild: backdate the stamp file (or delete it) and restart the session — confirm SessionStart returns immediately (no freeze), background rebuild process spawns (check process list / logfile), lock file appears then clears, stamp is updated when rebuild completes; start a second concurrent session during rebuild and confirm the lock prevents a duplicate spawn
- [x] 7.5 Test the convention-injection fallback: temporarily remove the vault graph and restart the session — confirm the grep fallback surfaces convention summaries (or the section is cleanly omitted if none exist)
- [x] 7.6 Run each of the three skills (`/memory-query`, `/memory-write`, `/memory-reorganize`) in a session and confirm the guidance loads and is coherent with the fixture set context

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-08

View File

@ -0,0 +1,119 @@
_Last updated: 2026-06-08_
## Context
The memory plugin ships at `~/.claude/plugins/memory/` with three skills: `memory-query`, `memory-write`, `memory-reorganize`. `memory-query` covers vault retrieval; the other two are write-path skills. No project-graph skill exists.
Two problems compound:
1. `memory-query` has a god-node dependency: it instructs Claude to start from a hub note surfaced in the session's `additionalContext`. That requires `session-context.sh` to emit a vault graph report on every session start — cost paid regardless of whether vault knowledge is needed.
2. There is no routing rule. Claude has to infer when to query the vault versus a project graph, with no explicit guidance. The two question types are structurally distinct and need different tools.
Constraints (unchanged):
- Ollama model: `qwen2.5-coder:7b` — established by `local-model-selection`, used for all graphify extraction.
- `graphify-out/` must never be committed — local, disposable, rebuildable.
- Skills are mirrored identically across `~/.claude/`, `.codex/`, `.pi/`.
## Goals / Non-Goals
**Goals:**
- Two focused skills that align with the two query types.
- Routing rule that Claude can apply without ambiguity, persisted in a file that's globally visible.
- Lean session injection — emit only what is used on every session start.
- Remove the god-node injection dependency without breaking vault retrieval quality.
**Non-Goals:**
- Memsearch / episodic layer (Step 4 of build plan — not in scope here).
- Vault migration scaffolding (Step 1 — separate concern).
- Automated graph freshness (no daemon; updates remain on-demand).
- Bulk project onboarding.
## Decisions
### Decision: Two skills, not one with internal routing
`memory-vault` and `memory-project` are separate skills, not branches of a single skill.
The question types are structurally different: vault queries need no project context; project queries need a graph path and a codebase in scope. Skills have asymmetric capabilities — vault writes are meaningful; "project writes" are not a concept. A model routing naturally between two named skills is more reliable than a model routing internally within one skill with conditional logic.
Alternative considered: single `memory` skill with a routing section at the top. Rejected — the conditional logic would need to cover both lifecycle operations (onboard/update/remove) and query routing, making the skill unwieldy and ambiguous.
### Decision: Drop god-node injection dependency from memory-vault
The current `memory-query` skill instructs Claude to start at a hub note surfaced in `additionalContext`. Graphify's traversal already handles graph navigation internally — the `--budget` flag bounds token cost, and `graphify query` returns relevant subgraph regardless of entry node. The god-node instruction was a guard against unconstrained traversal, not a technical requirement.
Removing it means `session-context.sh` no longer needs to emit a vault graph report. The skill retains all query/path/explain patterns and the `--budget` flag, which together bound traversal cost.
Alternative considered: keep god-node but make injection opt-in. Rejected — optional injection is harder to reason about than no injection. The skill works without it.
### Decision: Routing rule lives in ~/.claude/CLAUDE.md
The routing heuristic ("vault vs project graph") must be globally visible — it applies in every project session, not just sessions in this repo. `~/.claude/CLAUDE.md` is the global user instructions file loaded automatically in every project. It is a single file to update, and changes take effect everywhere immediately.
Alternative considered: per-project CLAUDE.md. Rejected — would require maintaining the rule in every project.
Alternative considered: inside the skills themselves. Rejected — a model reading `memory-vault` might not read `memory-project` first, and vice versa. The routing rule needs a neutral home both skills can point to.
### Decision: session-context.sh emits only project graph pointer
Current `session-context.sh` emits: vault graph report, live convention query, journal pointer, project graph path. The vault graph report and convention query are the ones that motivated the god-node dependency — they pre-populate a hub node for the skill to start from. With god-node removed, they are overhead.
The project graph path pointer is the only injection that pays per-session: it tells Claude whether a project graph exists for the current repo without requiring Claude to probe the filesystem. This one conditional line remains.
Alternative considered: emit nothing; let skills query on demand. Rejected — the graph path check is cheap and eliminates a filesystem probe in every session that uses memory-project.
### Decision: memory-project delegates query mechanics to memory-vault
`memory-project`'s query section states the `--graph ./graphify-out/graph.json` routing rule and then explicitly says "see memory-vault for query mechanics (graphify query, graphify path, graphify explain, --budget, --dfs)." This avoids duplication and ensures the two skills stay consistent as graphify evolves.
### Decision: AST-only extraction for code repos; --backend ollama specified uniformly
Code repos use the free tree-sitter AST pass — no LLM cost. The `--backend ollama --model qwen2.5-coder:7b` flags are always specified on the onboard command because the single invocation handles both pure-code and mixed (code + docs) repos without requiring Claude to detect repo type first.
### Decision: cluster-only as a separate onboarding step
`graphify cluster-only .` runs after initial extraction to build the community structure required for `graphify query`. No combined command exists in the Graphify CLI at v0.8.30, so the two-step sequence is the correct protocol.
## memory-vault Skill
**Scope:** vault at `~/Documents/SecondBrain`. Evergreen, cross-project knowledge: conventions, tool behavior, client facts, patterns that generalize beyond one repo.
**Query patterns (unchanged from memory-query):**
- `graphify query "<topic>" --budget 2000` — explore
- `graphify path "<a>" "<b>"` — trace relationships
- `graphify explain "<node>"` — deep-dive
- `graphify query "<topic>" --dfs --budget 2000` — bounded depth-first
No `--graph` flag = vault default. No god-node injection needed.
**Tag-scoped caution note retained:** facet-tag graph edges not yet verified (ADR-014 open).
**Write guidance:** use memory-write when knowledge generalizes beyond the current repo. If in doubt: "Would this be useful outside this repo, next year?" If yes → vault.
## memory-project Skill
**Scope:** per-project graph at `./graphify-out/graph.json`. Codebase structure and module relationships for the current repo only.
**Lifecycle operations:**
| Operation | Command |
|-----------|---------|
| Onboard (extract) | `graphify extract . --backend ollama --model qwen2.5-coder:7b` |
| Onboard (cluster) | `graphify cluster-only .` |
| Onboard (gitignore) | ensure `graphify-out/` in `.gitignore` |
| Incremental update | `graphify update .` |
| Structural update | `graphify update . --force` (renames, deletions, moves) |
| Remove | delete `graphify-out/`; leave gitignore entry |
**Query:** `graphify query "<question>" --graph ./graphify-out/graph.json` — then see memory-vault for traversal mechanics.
**Graph path discovery:** `session-context.sh` injects the path if the graph exists. If no path is injected, suggest onboarding before knowledge queries.
**Hard constraints:** never commit `graphify-out/`; gitignore entry survives even if graph is deleted; model is `qwen2.5-coder:7b`.
## Risks / Trade-offs
- [Risk] god-node removal degrades vault retrieval quality → `--budget 2000` still bounds token cost; graphify's internal traversal handles routing from any entry. Risk is low; can be revisited if retrieval quality drops.
- [Risk] routing rule in `~/.claude/CLAUDE.md` is invisible to models that don't read it → mitigated by both skills referencing the routing rule location.
- [Risk] `graphify-out/` accidentally committed → `.gitignore` entry is step 3 of onboard; skill states "never commit" explicitly.
- [Trade-off] No automatic freshness — graphs stale after significant refactors → accepted (ADR-013 laziness principle); `graphify update .` is cheap and Claude can suggest it.

View File

@ -0,0 +1,38 @@
## Why
The current memory-query skill has a god-node dependency — it assumes a hub note is surfaced in the session's `additionalContext` as an entry point. That injection relies on heavy session context output (vault graph report, convention query, journal pointer) that pays a cost every session regardless of whether vault knowledge is needed. There is also no routing rule: nothing tells Claude when to reach for the vault versus when to reach for a project graph.
On the project side, the `memory-project-graph` skill was never implemented. Per-project code graphs exist (any repo can be onboarded to Graphify), but Claude has no protocol for creating, updating, or querying them.
## What Changes
1. **Rename `memory-query` → `memory-vault`** with two changes to the SKILL.md: drop the god-node section (graphify's internal traversal handles this; no injected entry point needed), and add an explicit scope statement — this skill is for cross-project evergreen knowledge, not codebase structure.
2. **Create `memory-project`** — a new skill covering the full lifecycle of per-project Graphify graphs: onboard (extract + cluster + gitignore), incremental update, structural update (--force), remove, and project-scoped query routing. Query mechanics delegate to `memory-vault` rather than being duplicated.
3. **Add a routing rule block to `~/.claude/CLAUDE.md`** — vault for anything that generalizes beyond one repo; project graph for codebase structure and module relationships. Decision heuristic: "Would this be useful outside this repo, next year?"
4. **Trim `session-context.sh`** to emit only the project graph path pointer when `graphify-out/graph.json` exists. Drop: vault graph report, live convention query, journal pointer.
5. **Mirror** both skill renames and additions to `.codex/skills/` and `.pi/skills/` — the three AI assistant directories stay identical.
## Capabilities
### New Capabilities
- `memory-vault`: Cross-project vault querying without god-node injection dependency.
- `memory-project`: Per-project graph lifecycle (onboard, update, remove) and project-scoped queries.
- `memory-routing`: Routing rule in `~/.claude/CLAUDE.md` — globally visible, single place to maintain.
### Modified Capabilities
- `session-context.sh`: Narrowed to project graph pointer only — less injection noise, faster hook execution.
## Impact
- Rename: `~/.claude/plugins/memory/skills/memory-query/``memory-vault/`
- New file: `~/.claude/plugins/memory/skills/memory-project/SKILL.md`
- Edit: `session-context.sh` (trim to graph pointer)
- Edit: `~/.claude/CLAUDE.md` (add routing rule block)
- Mirror all the above to `.codex/skills/` and `.pi/skills/`
- No new runtime dependencies — same `graphify` CLI and `qwen2.5-coder:7b` Ollama model.

View File

@ -0,0 +1,145 @@
_Last updated: 2026-06-08_
## Scope
This spec covers the refactored memory skill layout: `memory-vault` (renamed from `memory-query`), `memory-project` (new), the routing rule in `~/.claude/CLAUDE.md`, and the trimmed `session-context.sh`. The original spec name (project-graph-lifecycle) is retained for path stability.
---
## Requirement 1: Routing rule in ~/.claude/CLAUDE.md
A routing rule block SHALL be added to `~/.claude/CLAUDE.md` that tells Claude:
- Use `memory-vault` for: conventions, tool behavior, client facts, patterns that generalize beyond one repo.
- Use `memory-project` for: codebase structure, module relationships, what exists where in the current repo.
- Decision heuristic: "Would this be useful outside this repo, next year?" — if yes, vault; if no, project graph.
#### Scenario: Claude chooses between vault and project graph
- **WHEN** Claude needs to answer a question about tool conventions or client context
- **THEN** Claude uses `memory-vault` (vault query, no `--graph` flag)
- **WHEN** Claude needs to answer a question about what modules exist in the current codebase
- **THEN** Claude uses `memory-project` with `--graph ./graphify-out/graph.json`
---
## Requirement 2: memory-vault skill
The `memory-vault` SKILL.md SHALL:
2.1 State explicitly that this skill is for cross-project evergreen knowledge — not codebase structure.
2.2 Provide query patterns:
- `graphify query "<topic>" --budget 2000` — explore a topic
- `graphify path "<a>" "<b>"` — trace a relationship
- `graphify explain "<node>"` — deep-dive a specific node
- `graphify query "<topic>" --dfs --budget 2000` — bounded depth-first
2.3 NOT contain a god-node section or any instruction to start from an injected hub note. Graph traversal entry point is handled internally by graphify.
2.4 Retain the tag-scoped caution note: facet-tag graph edges are not yet verified (ADR-014 open); treat tag-based filtering as experimental.
2.5 Include write guidance pointing to `memory-write` with the routing heuristic.
#### Scenario: Claude queries the vault without injected context
- **WHEN** no vault graph report is present in session context
- **THEN** Claude runs `graphify query "<topic>" --budget 2000` directly
- **AND** Claude does NOT stall waiting for a god-node entry point
#### Scenario: Claude stays in vault scope for cross-project questions
- **WHEN** the question concerns client conventions or tool behavior
- **THEN** Claude queries the vault (no `--graph` flag)
- **AND** Claude does NOT use `--graph ./graphify-out/graph.json` for this question
---
## Requirement 3: memory-project skill
The `memory-project` SKILL.md SHALL:
3.1 Cover the **onboard** lifecycle:
- `graphify extract . --backend ollama --model qwen2.5-coder:7b` (from project root)
- `graphify cluster-only .` (build community structure for query)
- Ensure `graphify-out/` is present in `.gitignore`
- Confirm `graphify-out/graph.json` exists before reporting complete
- State that code-only repos use the free AST pass; LLM cost only applies when `.md` docs are present
3.2 Cover **incremental update**:
- `graphify update .` for routine edits (no `--force`)
- `graphify update . --force` for structural changes (renames, deletions, directory moves)
- Suggest update after sessions involving significant refactoring
3.3 Cover **remove**:
- Delete `graphify-out/` when project is inactive or graph is too stale
- Confirm the graph is always rebuildable via the onboard sequence
- Leave the `.gitignore` entry even after removal
3.4 Cover **query routing**:
- Use `graphify query "<question>" --graph ./graphify-out/graph.json`
- Delegate all query mechanics (traversal, --budget, --dfs, graphify path, graphify explain) to `memory-vault` — do not duplicate
3.5 Cover **graph path discovery**:
- `session-context.sh` injects the graph path when `graphify-out/graph.json` exists
- If no graph path is injected and Claude is in a project context, suggest running the onboard sequence before proceeding with knowledge queries
3.6 State hard constraints:
- Never commit `graphify-out/`
- Model is `qwen2.5-coder:7b`
- `.gitignore` entry for `graphify-out/` must survive even if the directory is deleted
#### Scenario: Claude onboards a new project
- **WHEN** a user asks to onboard a project to the memory system
- **THEN** Claude runs extract, then cluster-only, then adds the gitignore entry
- **AND** Claude confirms graph.json exists before reporting done
- **AND** Claude does NOT commit graphify-out/ under any circumstances
#### Scenario: Claude updates the graph after routine edits
- **WHEN** files have been modified but not renamed, deleted, or moved
- **THEN** Claude runs `graphify update .` (no `--force`)
#### Scenario: Claude updates the graph after structural changes
- **WHEN** files have been renamed, deleted, or directories reorganized
- **THEN** Claude runs `graphify update . --force` to clear ghost nodes
#### Scenario: Claude starts a session in a project with no graph
- **WHEN** no graph path is injected by session-context.sh
- **AND** Claude is working in a project context
- **THEN** Claude suggests the onboard sequence before knowledge queries
- **AND** Claude does NOT silently fall back to vault-only retrieval without noting the gap
---
## Requirement 4: session-context.sh trimmed to project graph pointer
`session-context.sh` SHALL emit only:
- The absolute path to `<project-root>/graphify-out/graph.json` — if and only if that file exists
`session-context.sh` SHALL NOT emit:
- Vault graph reports
- Live convention queries
- Journal pointers
#### Scenario: session-context.sh runs in an onboarded project
- **WHEN** `<project-root>/graphify-out/graph.json` exists
- **THEN** the script outputs the absolute path to the graph file
- **AND** no other memory context is emitted
#### Scenario: session-context.sh runs in a project with no graph
- **WHEN** `<project-root>/graphify-out/graph.json` does not exist
- **THEN** the script outputs nothing (or a minimal "no project graph" note)
---
## Requirement 5: Mirror to .codex/ and .pi/
5.1 `.codex/skills/memory-query/` SHALL be renamed to `.codex/skills/memory-vault/` with the same SKILL.md content as `~/.claude/plugins/memory/skills/memory-vault/`.
5.2 `.codex/skills/memory-project/SKILL.md` SHALL be created with the same content as `~/.claude/plugins/memory/skills/memory-project/SKILL.md`.
5.3 Same renames and additions SHALL be applied to `.pi/skills/`.
5.4 All three directories (`~/.claude/plugins/memory/skills/`, `.codex/skills/`, `.pi/skills/`) SHALL remain content-identical after this change.
#### Scenario: Verifying mirror consistency
- **WHEN** any of the three skill directories is read
- **THEN** the content of `memory-vault/SKILL.md` is identical across all three
- **AND** the content of `memory-project/SKILL.md` is identical across all three

View File

@ -0,0 +1,49 @@
## 1. Write routing rule to ~/.claude/CLAUDE.md
- [x] 1.1 Add a "Memory routing" section to `~/.claude/CLAUDE.md` with:
- Vault (`memory-vault`) for: conventions, tool behavior, client facts, patterns that generalize beyond one repo
- Project graph (`memory-project`) for: codebase structure, module relationships, what exists where in the current repo
- Decision heuristic: "Would this be useful outside this repo, next year?" — yes → vault; no → project graph
## 2. Rename memory-query → memory-vault and update SKILL.md
- [x] 2.1 Rename `~/.claude/plugins/memory/skills/memory-query/` to `memory-vault/`
- [x] 2.2 Rewrite `SKILL.md`:
- Replace description frontmatter to reflect vault scope
- Add explicit scope statement: "cross-project evergreen knowledge — not codebase structure"
- Remove the god-node section entirely (no injected hub note required)
- Keep: query/path/explain patterns with `--budget 2000`, tag-scoped caution note (ADR-014), episodic → memsearch redirect
- Add write guidance: use `memory-write` for knowledge that generalizes; decision heuristic points to `~/.claude/CLAUDE.md` routing rule
## 3. Create memory-project skill
- [x] 3.1 Create directory `~/.claude/plugins/memory/skills/memory-project/`
- [x] 3.2 Write `SKILL.md` with sections:
- **Scope** — project graph at `./graphify-out/graph.json`; codebase structure only
- **Onboard**`graphify extract . --backend ollama --model qwen2.5-coder:7b`, then `graphify cluster-only .`, then add `graphify-out/` to `.gitignore`; note AST-only cost for code repos; confirm graph.json exists before reporting done
- **Incremental update**`graphify update .` for routine edits; `graphify update . --force` for structural changes (renames, deletions, moves); suggest update after major refactors
- **Remove** — delete `graphify-out/`; graph is rebuildable; leave gitignore entry
- **Query**`graphify query "<question>" --graph ./graphify-out/graph.json`; delegate traversal mechanics to `memory-vault` (no duplication)
- **Graph path discovery**`session-context.sh` injects path if graph exists; if no path injected in project context, suggest onboard
- **Constraints** — never commit `graphify-out/`; model is `qwen2.5-coder:7b`; gitignore entry survives deletion
## 4. Trim session-context.sh
- [x] 4.1 Edit `~/.claude/plugins/memory/hooks/session-context.sh`:
- Remove: vault graph report output, live convention query, journal pointer
- Keep: conditional check for `<project-root>/graphify-out/graph.json`; if exists, emit the absolute path; else emit nothing
## 5. Mirror to .codex/ and .pi/
- [x] 5.1 Rename `.codex/skills/memory-query/``memory-vault/`; replace SKILL.md with the updated content from step 2
- [x] 5.2 Create `.codex/skills/memory-project/SKILL.md` with the content from step 3
- [x] 5.3 Rename `.pi/skills/memory-query/``memory-vault/`; replace SKILL.md with the updated content from step 2
- [x] 5.4 Create `.pi/skills/memory-project/SKILL.md` with the content from step 3
## 6. Verify against spec
- [x] 6.1 Check Requirement 1: routing rule exists in `~/.claude/CLAUDE.md` with vault/project distinction and decision heuristic
- [x] 6.2 Check Requirement 2: `memory-vault` SKILL.md has no god-node section; has scope statement; query patterns present; tag-caution retained
- [x] 6.3 Check Requirement 3: `memory-project` SKILL.md covers all lifecycle operations; query delegates to memory-vault; constraints stated; gitignore contract explicit
- [x] 6.4 Check Requirement 4: `session-context.sh` emits only the graph path pointer; vault report, convention query, journal pointer are gone
- [x] 6.5 Check Requirement 5: content of `memory-vault/SKILL.md` and `memory-project/SKILL.md` is identical across `~/.claude/plugins/memory/skills/`, `.codex/skills/`, `.pi/skills/`

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-09

View File

@ -0,0 +1,66 @@
## Context
The personal Claude Code memory system currently has two intended layers: episodic (per-turn Q&A history across all projects) and semantic/knowledge (the Obsidian vault + Graphify). The semantic layer is live. The episodic layer does not exist yet — there is no durable record of what was discussed across sessions.
memsearch is a purpose-built episodic memory plugin for Claude Code, developed by Zilliz (the company behind Milvus). It ships with its own Stop hook, Claude guidance, and tool definitions. The design principle for this step is: install first, audit defaults, deviate only if evidence demands it.
**Current state:** No episodic memory. No memsearch installed.
**Constraints:**
- Must be a global install (all projects share one episodic store)
- No external API keys — embeddings must run locally
- No daemon or cron — freshness is lazy (write-time + incremental index)
- No custom code unless audit reveals concrete gaps
## Goals / Non-Goals
**Goals:**
- Install memsearch via the Claude Code plugin marketplace
- Confirm `memory_dir` points to a global path (`~/.memsearch/memory/`)
- Verify the memsearch Stop hook fires and produces `~/.memsearch/memory/YYYY-MM-DD.md` after a session
- Audit the built-in plugin guidance to check for routing conflicts with `memory-vault`/`memory-project`
**Non-Goals:**
- Does not index the Obsidian vault — Graphify owns semantic search
- Does not add a SessionStart hook injection (evaluate after seeing what the default install provides)
- Does not configure Milvus Server or Zilliz Cloud (Milvus Lite is correct for personal use)
- Does not write a custom `memory-search` skill unless the audit reveals gaps
- Does not modify `session-end.sh` or any existing hooks
## Decisions
**Use the plugin marketplace, not manual install**
memsearch ships as a complete Claude Code plugin. Installing via `/plugin marketplace add` + `/plugin install` wires the Stop hook, tool definitions, and Claude guidance automatically. Manual installation would require replicating that work without benefit.
*Alternative considered: clone repo and wire manually* — rejected; more work with no gain given a working plugin exists.
**Milvus Lite over Milvus Server**
Personal use case: one developer, one machine, low write volume. Milvus Lite runs in-process with no daemon. Milvus Server adds operational overhead that isn't warranted here.
*Alternative considered: Zilliz Cloud* — rejected; requires account, network dependency, and cost for a personal tool.
**ONNX bge-m3 embeddings over API-based embeddings**
Ships with the plugin; runs on CPU; ~558MB one-time download; no API key required. Consistent with the system's principle of local-first tooling (same reason Ollama is used for Graphify extraction).
**Audit-first policy**
memsearch's built-in guidance may already handle cross-project episodic recall correctly. Adding a custom skill before seeing what the default install provides would be premature. Evaluate after one real session.
## Risks / Trade-offs
- **`memory_dir` defaults to project-local path** → Mitigation: run `memsearch config list` immediately after install; set global path in `~/.memsearch/config.toml` if needed. Spec requires this verification step.
- **Plugin marketplace unavailability** → Mitigation: if `/plugin marketplace add` fails, check memsearch GitHub for alternative install path.
- **558MB embedding model download** → Mitigation: one-time cost; happens on first use. No action needed, just awareness.
- **Built-in guidance conflicts with `memory-vault`/`memory-project` routing** → Mitigation: audit after install; add a minimal `memory-search` skill only if routing confusion is observed in practice.
- **Stop hook not auto-registering** → Mitigation: verification step in the checklist confirms hook is wired; manual registration in `~/.claude/settings.json` is fallback.
## Migration Plan
1. Install plugin via marketplace commands
2. Run `memsearch config list` — record actual defaults (do not assume)
3. If `memory_dir` is project-local, set `memory_dir = "~/.memsearch/memory/"` in `~/.memsearch/config.toml`
4. Verify Stop hook is registered (check `~/.claude/settings.json` or plugin config)
5. Run one test session — confirm `~/.memsearch/memory/YYYY-MM-DD.md` is created
6. Audit Claude plugin instructions — note what guidance memsearch adds to context
7. If routing gaps exist, write a minimal `memory-search` skill
**Rollback:** Uninstall the plugin (`/plugin uninstall memsearch`). The Milvus database and daily memory files are inert without the plugin. No changes to existing hooks or skills, so nothing to revert there.

View File

@ -0,0 +1,29 @@
## Why
The memory system has two layers — episodic ("what happened, when") and semantic/knowledge ("how do we…"). The semantic layer is live (Obsidian vault + Graphify). The episodic layer is missing: there is no way to answer "what were we working on last Tuesday?" or "did we discuss X recently?" across projects. memsearch is the purpose-built tool for this and fills that gap with zero custom code.
## What Changes
- Install memsearch as a global Claude Code plugin via the marketplace
- Confirm `memory_dir` is set to a global path (`~/.memsearch/memory/`) so all projects share one episodic store
- Verify the memsearch Stop hook is registering per-turn Q&A summaries correctly
- Audit memsearch's built-in Claude guidance after install before adding anything custom
## Capabilities
### New Capabilities
- `memsearch-episodic`: Episodic memory layer — install memsearch plugin, confirm global memory_dir, verify Stop hook produces daily memory files, and audit built-in guidance for routing gaps
### Modified Capabilities
_(none — existing hooks and skills are unchanged; memsearch ships with its own Stop hook)_
## Impact
- New tool available in all Claude Code sessions: `memsearch search "<query>"`
- memsearch Stop hook fires after every response turn (ships with plugin; no custom code)
- `~/.memsearch/memory/YYYY-MM-DD.md` daily files accumulate across all projects
- `~/.memsearch/milvus.db` is the persistent vector store (Milvus Lite, local)
- No changes to `session-end.sh`, `memory-vault`, `memory-project`, or Graphify
- A `memory-search` skill is conditional: only warranted if built-in guidance proves insufficient after install

View File

@ -0,0 +1,49 @@
## ADDED Requirements
### Requirement: Plugin installed globally
The system SHALL install memsearch as a Claude Code plugin via the marketplace so that episodic memory is available in all sessions.
#### Scenario: Successful marketplace install
- **WHEN** the user runs `/plugin marketplace add zilliztech/memsearch` followed by `/plugin install memsearch`
- **THEN** the memsearch plugin is active, its Stop hook is registered, and `memsearch` tool definitions are available in the session
### Requirement: Global memory directory
The system SHALL store all episodic memory files at a global path (`~/.memsearch/memory/`) so that memories from all projects accumulate in one place and are queryable cross-project.
#### Scenario: memory_dir is already global after install
- **WHEN** the user runs `memsearch config list` after install
- **THEN** `memory_dir` resolves to `~/.memsearch/memory/` (or equivalent global home-relative path)
#### Scenario: memory_dir requires explicit configuration
- **WHEN** `memsearch config list` shows a project-local `memory_dir`
- **THEN** the user sets `memory_dir = "~/.memsearch/memory/"` in `~/.memsearch/config.toml` and re-verifies with `memsearch config list`
### Requirement: Stop hook produces daily memory files
The system SHALL produce a daily memory file at `~/.memsearch/memory/YYYY-MM-DD.md` after each session turn so that episodic history accumulates automatically without manual action.
#### Scenario: Memory file created after first session turn
- **WHEN** a session turn completes (user message + Claude response) with memsearch installed and configured
- **THEN** a file exists at `~/.memsearch/memory/<today-date>.md` containing a summary of that turn
#### Scenario: Stop hook fires on every turn
- **WHEN** multiple turns occur in a session
- **THEN** the daily memory file grows with entries for each turn (SHA-256 dedup prevents duplicate entries)
### Requirement: Built-in guidance audited before customization
The system SHALL audit memsearch's built-in Claude plugin instructions after install before adding any custom skill or configuration, so that custom code is only added when a concrete gap is identified.
#### Scenario: No routing conflict found
- **WHEN** the user reviews memsearch's built-in guidance and runs a test session
- **THEN** episodic recall queries are handled by memsearch's built-in tools without conflicting with `memory-vault` or `memory-project` routing
- **THEN** no custom `memory-search` skill is created
#### Scenario: Routing conflict or gap identified
- **WHEN** memsearch's built-in guidance fails to distinguish episodic from semantic queries, or does not handle cross-project recall correctly
- **THEN** a minimal `memory-search` skill is authored that supplements (not replaces) the built-in guidance
### Requirement: Cross-project episodic recall available
The system SHALL enable the user to query episodic memory across all projects via `memsearch search "<query>"` so that questions like "what were we working on last Tuesday?" are answerable.
#### Scenario: Successful cross-project query
- **WHEN** the user runs `memsearch search "what was I working on last week?"` after at least one session has produced memory files
- **THEN** memsearch returns relevant summaries from `~/.memsearch/memory/` ranked by semantic similarity

View File

@ -0,0 +1,29 @@
## 1. Install Plugin
- [x] 1.1 Run `/plugin marketplace add zilliztech/memsearch` in a Claude Code session
- [x] 1.2 Run `/plugin install memsearch` to activate the plugin
- [x] 1.3 Confirm the plugin is listed as active (no error messages)
## 2. Audit and Configure
- [x] 2.1 Run `memsearch config list` and record the actual defaults (especially `memory_dir` and `milvus_db` paths)
- [x] 2.2 If `memory_dir` is project-local, set `memory_dir = "~/.memsearch/memory/"` in `~/.memsearch/config.toml`
- [x] 2.3 Re-run `memsearch config list` to confirm `memory_dir` resolves to a global path
## 3. Verify Stop Hook
- [x] 3.1 Check `~/.claude/settings.json` (or plugin config) to confirm the memsearch Stop hook is registered
- [x] 3.2 Run a short test session (a few turns) and exit
- [x] 3.3 Confirm `~/.memsearch/memory/YYYY-MM-DD.md` exists with content from the test session
## 4. Audit Built-in Guidance
- [x] 4.1 Review what memsearch adds to Claude's context (system prompt / tool definitions)
- [x] 4.2 Run a test query: `memsearch search "what were we working on in the test session?"`
- [x] 4.3 Assess whether built-in guidance handles cross-project episodic recall without conflicting with `memory-vault`/`memory-project`
- [x] 4.4 If routing gaps are found, author a minimal `memory-search` skill; otherwise skip
## 5. Mark Complete
- [x] 5.1 Update `docs/memory-system/04-build-plan.md` — mark Step 4 complete
- [x] 5.2 Update `CLAUDE.md` — add memsearch to Implemented Components, update design paragraph

View File

@ -0,0 +1,84 @@
# Spec: memory-plugin-hooks
_Purpose: Define the Claude Code hook behaviors for the memory plugin — SessionStart context injection and staleness-driven graph rebuild, PostToolUse vault graph update, SessionEnd episodic journal append, and plugin configuration._
_Last updated: 2026-06-05_
---
## Requirements
### Requirement: SessionStart hook — staleness check and detached background rebuild
The plugin SHALL register a SessionStart hook that reads the rebuild stamp file at `~/.cache/graphify/vault-rebuild.stamp`. If the stamp is absent or its mtime is older than the configured threshold (default: 7 days), the hook SHALL spawn `graphify extract <vault-path> --backend ollama --model <configured-model> --force` as a **detached background process** (nohup/setsid, output redirected to a log file) and SHALL return immediately without waiting for it. The background process SHALL update the stamp file upon successful completion and remove the lock file. The hook SHALL NOT block on the rebuild — SessionStart blocks the entire session until the hook returns, so the synchronous path must be sub-second. (Source: Claude Code hooks documentation — SessionStart has no async option.) The hook SHALL write a lock file (`~/.cache/graphify/vault-rebuild.lock`) before spawning, so concurrent sessions do not launch duplicate rebuilds.
#### Scenario: Stamp is fresh (within threshold)
- **WHEN** the SessionStart hook fires and the rebuild stamp mtime is within the configured threshold
- **THEN** no extraction run is triggered and the hook proceeds to context injection
#### Scenario: Stamp is stale or absent
- **WHEN** the SessionStart hook fires and the rebuild stamp is absent or older than the configured threshold
- **THEN** the hook checks for a lock file; if absent, spawns a detached background rebuild and writes the lock; in all cases the hook continues immediately to context injection using the existing graph; the rebuilt graph becomes available to the next session
#### Scenario: Rebuild already in progress (lock file present)
- **WHEN** the SessionStart hook fires and the lock file already exists (rebuild spawned by a concurrent session)
- **THEN** the hook does NOT spawn another rebuild; it logs that a rebuild is in progress and proceeds to context injection
#### Scenario: Extraction fails during stale rebuild
- **WHEN** the background rebuild process exits non-zero
- **THEN** the background process logs the error to its log file and removes the lock file without touching the stamp; the current session is unaffected (already past injection); the next session will attempt the rebuild again
### Requirement: SessionStart hook — vault context injection
The plugin SHALL register a SessionStart hook that injects the following into Claude's context at the start of each session: (a) the vault graph's god-node summary (top N most-connected nodes by degree, sourced from `GRAPH_REPORT.md` or equivalent query), (b) the summaries of `convention/*` vault notes (sourced via `graphify query "convention"` or, as a fallback, by grepping vault frontmatter for `convention/` tags and reading the `summary:` field), and (c) a pointer to the current day's episodic journal note path. The hook SHALL deliver this content by writing `{"additionalContext": "<assembled-text>"}` JSON to stdout and exiting 0 — this is the confirmed Claude Code mechanism for hook-to-model context injection. (Source: Claude Code hooks documentation.) The hook reads its input from stdin JSON; relevant fields include `.cwd` (working directory) and `.source` (startup|resume|clear|compact).
#### Scenario: Vault graph and convention notes exist
- **WHEN** SessionStart fires and the vault graph and at least one `convention/*` note exist
- **THEN** the injected context contains the god-node list, all `convention/*` summaries, and the journal note path
#### Scenario: No convention notes exist yet
- **WHEN** SessionStart fires and no `convention/*` notes exist in the vault
- **THEN** the injected context contains the god-node list and journal pointer; the conventions section is omitted without error
#### Scenario: Project graph exists for the current project
- **WHEN** SessionStart fires and a file exists at `<project-root>/graphify-out/graph.json`
- **THEN** the injected context also includes the project graph path, so Claude can query it via `graphify query --graph <path>`
### Requirement: PostToolUse hook — event-driven vault graph update
The plugin SHALL register a PostToolUse hook that fires after any `Write` or `Edit` tool call. The hook SHALL check whether the target file path is a `.md` file under the configured vault path (`~/Documents/SecondBrain`). If it is, the hook SHALL run `graphify update --file <path>` to merge the changed note into the vault graph. If the target path is outside the vault, the hook SHALL do nothing.
#### Scenario: A vault note is written or edited
- **WHEN** a Write or Edit tool call completes and the target path is under the vault directory
- **THEN** the hook runs `graphify update --file <target-path>` synchronously before Claude proceeds
#### Scenario: A non-vault file is written or edited
- **WHEN** a Write or Edit tool call completes and the target path is outside the vault directory
- **THEN** the hook does nothing; no graphify call is made
#### Scenario: graphify update fails for a vault write
- **WHEN** `graphify update --file <path>` exits non-zero
- **THEN** the hook logs the error but does NOT block the tool call result from being returned; the graph may be transiently stale
### Requirement: SessionEnd hook — episodic journal append
The plugin SHALL register a SessionEnd hook that appends a dated entry to the daily journal note at `<vault-path>/journal/YYYY-MM-DD.md` (creating the file if it does not exist). The entry SHALL include at minimum: the current project name (or path), the vault note paths touched during the session, and a UTC timestamp. The journal file is append-only; existing content is never overwritten.
#### Scenario: Session ends with vault notes touched
- **WHEN** the SessionEnd hook fires and at least one vault note was written or edited during the session
- **THEN** the hook appends a timestamped entry to the daily journal file listing the touched note paths and the project context
#### Scenario: Session ends with no vault writes
- **WHEN** the SessionEnd hook fires and no vault notes were touched
- **THEN** the hook still appends a minimal entry (timestamp + project) so the session is recorded for episodic recall
#### Scenario: Journal file does not exist yet for the day
- **WHEN** the SessionEnd hook fires and `<vault-path>/journal/YYYY-MM-DD.md` does not exist
- **THEN** the hook creates the file with appropriate frontmatter (summary, scope/global, type/log) and appends the session entry
### Requirement: Plugin configuration block
The plugin SHALL expose a configuration file at `~/.claude/plugins/memory/config.yaml` that allows setting: vault path, Graphify output directory, Ollama model name (Modelfile-baked variant), num_ctx, rebuild-stale threshold in days, and the env vars `OLLAMA_FLASH_ATTENTION`, `GRAPHIFY_OLLAMA_NUM_CTX`, `GRAPHIFY_OLLAMA_KEEP_ALIVE`. All hooks SHALL read from this config rather than hardcoding paths or values.
#### Scenario: Default config is used
- **WHEN** the config file exists with no overrides for a setting
- **THEN** the hook uses the default value for that setting (vault: `~/Documents/SecondBrain`, threshold: 7 days, model: `qwen25-coder-7b-16k`)
#### Scenario: User overrides vault path
- **WHEN** the config file specifies a non-default vault path
- **THEN** all hooks use the configured path for vault detection, graph paths, and journal writes

View File

@ -0,0 +1,70 @@
# Spec: memory-plugin-skills
_Purpose: Define the Claude Code skill behaviors for the memory plugin — when and how to query the vault graph, write vault knowledge, and reorganize vault notes._
_Last updated: 2026-06-05_
---
## Requirements
### Requirement: memory-query skill — graph retrieval guidance
The plugin SHALL provide a `memory-query` skill that teaches Claude when and how to query the vault knowledge graph versus the episodic memsearch layer. The skill SHALL specify: (a) use `graphify query "<topic>"` for evergreen knowledge questions ("how do we…", "what do we know about…"), (b) use `graphify path "<node-a>" "<node-b>"` to find how two concepts relate, (c) use `graphify explain "<node>"` for a deep-dive with neighbors, (d) add `--budget N` to cap answer size (default ~2000 tokens), (e) add `--dfs --budget N` for bounded depth-first traversal, (f) add `--graph <path>` to query a specific project graph instead of the vault graph. The skill SHALL explicitly state that god-nodes are the entry point — query god-nodes first, then scalpel to specific nodes. The skill SHALL treat tag-scoped retrieval (e.g. "all notes tagged `client/acme`") as a SEPARATE query path from graph traversal, noting that whether shared frontmatter facet tags create graph edges is unverified (ADR-014 open question); Claude SHALL NOT assume facet tags enable graph traversal. For "what happened when" questions, the skill SHALL direct Claude to memsearch in natural language rather than the vault graph.
#### Scenario: Claude needs to answer a knowledge question during a task
- **WHEN** a task touches a tool, client, or domain the vault may know about
- **THEN** Claude runs `graphify query "<topic>"` before relying on training knowledge, using `--budget` to keep context lean
#### Scenario: Claude needs to find how two concepts relate
- **WHEN** a task requires understanding the relationship between two vault entities (e.g. client and a tool they use)
- **THEN** Claude runs `graphify path "<entity-a>" "<entity-b>"` to traverse the graph path between them
#### Scenario: Cross-client knowledge lookup
- **WHEN** Claude needs to find all vault knowledge about a tool or domain regardless of which client it came from
- **THEN** Claude runs `graphify query "<tool-or-domain>"` against the vault graph (not `--graph <project-path>`) to get cross-client results
#### Scenario: Project-specific knowledge lookup
- **WHEN** Claude needs knowledge specific to the current project's code graph
- **THEN** Claude runs `graphify query "<topic>" --graph <project-root>/graphify-out/graph.json` using the project graph path injected by SessionStart
#### Scenario: Episodic question ("what was I working on last week?")
- **WHEN** a question is time-anchored ("what did we decide last Tuesday", "what was I working on yesterday")
- **THEN** Claude queries memsearch in natural language, NOT the vault graph
### Requirement: memory-write skill — when and how to write vault knowledge
The plugin SHALL provide a `memory-write` skill that teaches Claude when knowledge should be written to the vault and what the required frontmatter contract is. The skill SHALL specify: write to the vault when knowledge is evergreen and reusable across projects (tool/API behavior, conventions, client-specific facts worth reusing); do NOT write project-ephemeral state to the vault (that goes to project files or the episodic layer). The skill SHALL specify the required frontmatter fields: `summary` (one-line, written at note creation — not deferred — this is the human-authored router hint Graphify does not generate), `scope/global` or `scope/project` (default new tool/domain knowledge to `scope/global`; use `scope/project` when specific to how a client uses something), and at least one facet tag from the six-facet taxonomy (`type/`, `client/`, `project/`, `domain/`, `tool/`, `convention/`). The skill SHALL state the vault-not-repo rule: write knowledge ONLY to `~/Documents/SecondBrain`, never silently into a project repo. The skill SHALL note that writing a vault note automatically triggers the PostToolUse hook to update the graph — no manual graphify call is needed after writing.
#### Scenario: Claude discovers how a tool's API behaves
- **WHEN** Claude learns something about a tool's authentication, rate limits, or endpoint quirks that could apply to future sessions or projects
- **THEN** Claude writes a note to the vault with the required frontmatter (summary, scope/global, type/reference, tool/<name>) rather than embedding it in the project's CLAUDE.md
#### Scenario: Claude establishes a convention that should apply beyond this task
- **WHEN** Claude and the user settle on a convention (coding style, workflow, decision rule) that should propagate to other projects
- **THEN** Claude writes a `convention/<name>` note to the vault with the required frontmatter (summary, scope, type/convention)
#### Scenario: Knowledge is project-specific (how this client uses a tool)
- **WHEN** the knowledge describes behavior specific to one client's setup, not the tool generally
- **THEN** Claude writes the note with `scope/project` and a `client/<name>` facet tag rather than `scope/global`
#### Scenario: Claude tries to record project-ephemeral state
- **WHEN** the information is specific to the current task and not reusable in future sessions or projects
- **THEN** Claude does NOT write it to the vault; it stays in the project repo or episodic session notes
### Requirement: memory-reorganize skill — consolidation and plan-mode promotion
The plugin SHALL provide a `memory-reorganize` skill that teaches Claude the procedure for consolidating, deduplicating, promoting, and reorganizing vault notes. The skill SHALL specify: (a) reorganization MUST happen in plan mode — Claude proposes changes for human review before executing; (b) triggers include: duplicate coverage noticed across notes, a `scope/project` fact observed to recur across 2+ clients (promotion candidate), a note grown beyond the L1 "one concept, ~200 lines" discipline; (c) the consolidation procedure is: identify candidate notes, propose a merge/split/promote/rename plan, await human approval, execute edits, then run `graphify extract <vault-path> --force` to rebuild the graph clean; (d) a `--force` rebuild is required after any reorganization that removes, renames, or restructures notes (to clear ghost nodes); (e) `--force` rebuild is ALSO triggered when the SessionStart stale check fires, so reorganize skill's rebuild invocation is consistent with the staleness-check mechanism. The skill SHALL warn that `graphify update --file` does NOT prune deleted or renamed nodes — only `--force` rebuilds clear ghost nodes.
#### Scenario: Claude notices duplicate coverage across two vault notes
- **WHEN** Claude identifies two notes that cover the same concept with overlapping content
- **THEN** Claude enters plan mode: proposes which note to keep, what to merge, and what to discard — and awaits human approval before making any edits
#### Scenario: A project-scoped fact has recurred across multiple clients
- **WHEN** Claude observes that a fact tagged `scope/project` has appeared in 2+ client contexts, suggesting it is actually global knowledge
- **THEN** Claude proposes promoting the note to `scope/global` (updating frontmatter, merging client-specific variants if needed) in plan mode before executing
#### Scenario: Reorganization is complete and graph needs to be rebuilt
- **WHEN** any reorganization edit has been approved and executed (notes deleted, renamed, or restructured)
- **THEN** Claude runs `graphify extract <vault-path> --backend ollama --model <configured-model> --force` to clear ghost nodes and rebuild a clean graph; updates the rebuild stamp
#### Scenario: Note has grown beyond single-concept discipline
- **WHEN** Claude observes a vault note has grown beyond ~200 lines or covers more than one distinct concept
- **THEN** Claude proposes a split into two or more focused notes in plan mode, with proposed filenames, frontmatter, and the wikilinks that would connect them — before executing

View File

@ -0,0 +1,55 @@
# Spec: memsearch-episodic
## Purpose
Defines the episodic memory layer backed by the memsearch Claude Code plugin. Episodic memory captures what happened and when — session summaries stored as daily markdown files, queryable by semantic similarity across all projects.
## Requirements
### Requirement: Plugin installed globally
The system SHALL install memsearch as a Claude Code plugin via the marketplace so that episodic memory is available in all sessions.
#### Scenario: Successful marketplace install
- **WHEN** the user runs `/plugin marketplace add zilliztech/memsearch` followed by `/plugin install memsearch`
- **THEN** the memsearch plugin is active, its Stop hook is registered, and `memsearch` tool definitions are available in the session
### Requirement: Global memory directory
The system SHALL store all episodic memory files at a global path (`~/.memsearch/memory/`) so that memories from all projects accumulate in one place and are queryable cross-project.
#### Scenario: memory_dir is already global after install
- **WHEN** the user runs `memsearch config list` after install
- **THEN** `memory_dir` resolves to `~/.memsearch/memory/` (or equivalent global home-relative path)
#### Scenario: memory_dir requires explicit configuration
- **WHEN** `memsearch config list` shows a project-local `memory_dir`
- **THEN** the user sets `memory_dir = "~/.memsearch/memory/"` in `~/.memsearch/config.toml` and re-verifies with `memsearch config list`
### Requirement: Stop hook produces daily memory files
The system SHALL produce a daily memory file at `~/.memsearch/memory/YYYY-MM-DD.md` after each session turn so that episodic history accumulates automatically without manual action.
#### Scenario: Memory file created after first session turn
- **WHEN** a session turn completes (user message + Claude response) with memsearch installed and configured
- **THEN** a file exists at `~/.memsearch/memory/<today-date>.md` containing a summary of that turn
#### Scenario: Stop hook fires on every turn
- **WHEN** multiple turns occur in a session
- **THEN** the daily memory file grows with entries for each turn (SHA-256 dedup prevents duplicate entries)
### Requirement: Built-in guidance audited before customization
The system SHALL audit memsearch's built-in Claude plugin instructions after install before adding any custom skill or configuration, so that custom code is only added when a concrete gap is identified.
#### Scenario: No routing conflict found
- **WHEN** the user reviews memsearch's built-in guidance and runs a test session
- **THEN** episodic recall queries are handled by memsearch's built-in tools without conflicting with `memory-vault` or `memory-project` routing
- **THEN** no custom `memory-search` skill is created
#### Scenario: Routing conflict or gap identified
- **WHEN** memsearch's built-in guidance fails to distinguish episodic from semantic queries, or does not handle cross-project recall correctly
- **THEN** a minimal `memory-search` skill is authored that supplements (not replaces) the built-in guidance
### Requirement: Cross-project episodic recall available
The system SHALL enable the user to query episodic memory across all projects via `memsearch search "<query>"` so that questions like "what were we working on last Tuesday?" are answerable.
#### Scenario: Successful cross-project query
- **WHEN** the user runs `memsearch search "what was I working on last week?"` after at least one session has produced memory files
- **THEN** memsearch returns relevant summaries from `~/.memsearch/memory/` ranked by semantic similarity

View File

@ -0,0 +1,145 @@
_Last updated: 2026-06-08_
## Scope
This spec covers the refactored memory skill layout: `memory-vault` (renamed from `memory-query`), `memory-project` (new), the routing rule in `~/.claude/CLAUDE.md`, and the trimmed `session-context.sh`. The original spec name (project-graph-lifecycle) is retained for path stability.
---
## Requirement 1: Routing rule in ~/.claude/CLAUDE.md
A routing rule block SHALL be added to `~/.claude/CLAUDE.md` that tells Claude:
- Use `memory-vault` for: conventions, tool behavior, client facts, patterns that generalize beyond one repo.
- Use `memory-project` for: codebase structure, module relationships, what exists where in the current repo.
- Decision heuristic: "Would this be useful outside this repo, next year?" — if yes, vault; if no, project graph.
#### Scenario: Claude chooses between vault and project graph
- **WHEN** Claude needs to answer a question about tool conventions or client context
- **THEN** Claude uses `memory-vault` (vault query, no `--graph` flag)
- **WHEN** Claude needs to answer a question about what modules exist in the current codebase
- **THEN** Claude uses `memory-project` with `--graph ./graphify-out/graph.json`
---
## Requirement 2: memory-vault skill
The `memory-vault` SKILL.md SHALL:
2.1 State explicitly that this skill is for cross-project evergreen knowledge — not codebase structure.
2.2 Provide query patterns:
- `graphify query "<topic>" --budget 2000` — explore a topic
- `graphify path "<a>" "<b>"` — trace a relationship
- `graphify explain "<node>"` — deep-dive a specific node
- `graphify query "<topic>" --dfs --budget 2000` — bounded depth-first
2.3 NOT contain a god-node section or any instruction to start from an injected hub note. Graph traversal entry point is handled internally by graphify.
2.4 Retain the tag-scoped caution note: facet-tag graph edges are not yet verified (ADR-014 open); treat tag-based filtering as experimental.
2.5 Include write guidance pointing to `memory-write` with the routing heuristic.
#### Scenario: Claude queries the vault without injected context
- **WHEN** no vault graph report is present in session context
- **THEN** Claude runs `graphify query "<topic>" --budget 2000` directly
- **AND** Claude does NOT stall waiting for a god-node entry point
#### Scenario: Claude stays in vault scope for cross-project questions
- **WHEN** the question concerns client conventions or tool behavior
- **THEN** Claude queries the vault (no `--graph` flag)
- **AND** Claude does NOT use `--graph ./graphify-out/graph.json` for this question
---
## Requirement 3: memory-project skill
The `memory-project` SKILL.md SHALL:
3.1 Cover the **onboard** lifecycle:
- `graphify extract . --backend ollama --model qwen2.5-coder:7b` (from project root)
- `graphify cluster-only .` (build community structure for query)
- Ensure `graphify-out/` is present in `.gitignore`
- Confirm `graphify-out/graph.json` exists before reporting complete
- State that code-only repos use the free AST pass; LLM cost only applies when `.md` docs are present
3.2 Cover **incremental update**:
- `graphify update .` for routine edits (no `--force`)
- `graphify update . --force` for structural changes (renames, deletions, directory moves)
- Suggest update after sessions involving significant refactoring
3.3 Cover **remove**:
- Delete `graphify-out/` when project is inactive or graph is too stale
- Confirm the graph is always rebuildable via the onboard sequence
- Leave the `.gitignore` entry even after removal
3.4 Cover **query routing**:
- Use `graphify query "<question>" --graph ./graphify-out/graph.json`
- Delegate all query mechanics (traversal, --budget, --dfs, graphify path, graphify explain) to `memory-vault` — do not duplicate
3.5 Cover **graph path discovery**:
- `session-context.sh` injects the graph path when `graphify-out/graph.json` exists
- If no graph path is injected and Claude is in a project context, suggest running the onboard sequence before proceeding with knowledge queries
3.6 State hard constraints:
- Never commit `graphify-out/`
- Model is `qwen2.5-coder:7b`
- `.gitignore` entry for `graphify-out/` must survive even if the directory is deleted
#### Scenario: Claude onboards a new project
- **WHEN** a user asks to onboard a project to the memory system
- **THEN** Claude runs extract, then cluster-only, then adds the gitignore entry
- **AND** Claude confirms graph.json exists before reporting done
- **AND** Claude does NOT commit graphify-out/ under any circumstances
#### Scenario: Claude updates the graph after routine edits
- **WHEN** files have been modified but not renamed, deleted, or moved
- **THEN** Claude runs `graphify update .` (no `--force`)
#### Scenario: Claude updates the graph after structural changes
- **WHEN** files have been renamed, deleted, or directories reorganized
- **THEN** Claude runs `graphify update . --force` to clear ghost nodes
#### Scenario: Claude starts a session in a project with no graph
- **WHEN** no graph path is injected by session-context.sh
- **AND** Claude is working in a project context
- **THEN** Claude suggests the onboard sequence before knowledge queries
- **AND** Claude does NOT silently fall back to vault-only retrieval without noting the gap
---
## Requirement 4: session-context.sh trimmed to project graph pointer
`session-context.sh` SHALL emit only:
- The absolute path to `<project-root>/graphify-out/graph.json` — if and only if that file exists
`session-context.sh` SHALL NOT emit:
- Vault graph reports
- Live convention queries
- Journal pointers
#### Scenario: session-context.sh runs in an onboarded project
- **WHEN** `<project-root>/graphify-out/graph.json` exists
- **THEN** the script outputs the absolute path to the graph file
- **AND** no other memory context is emitted
#### Scenario: session-context.sh runs in a project with no graph
- **WHEN** `<project-root>/graphify-out/graph.json` does not exist
- **THEN** the script outputs nothing (or a minimal "no project graph" note)
---
## Requirement 5: Mirror to .codex/ and .pi/
5.1 `.codex/skills/memory-query/` SHALL be renamed to `.codex/skills/memory-vault/` with the same SKILL.md content as `~/.claude/plugins/memory/skills/memory-vault/`.
5.2 `.codex/skills/memory-project/SKILL.md` SHALL be created with the same content as `~/.claude/plugins/memory/skills/memory-project/SKILL.md`.
5.3 Same renames and additions SHALL be applied to `.pi/skills/`.
5.4 All three directories (`~/.claude/plugins/memory/skills/`, `.codex/skills/`, `.pi/skills/`) SHALL remain content-identical after this change.
#### Scenario: Verifying mirror consistency
- **WHEN** any of the three skill directories is read
- **THEN** the content of `memory-vault/SKILL.md` is identical across all three
- **AND** the content of `memory-project/SKILL.md` is identical across all three