# 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 `cc-os/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
