Compare commits

..

No commits in common. "7ae45c7bf8d185a498aee7ea45f58ffbf58198e8" and "05437c8d41547b09da88d8f00e838c8309e58f43" have entirely different histories.

193 changed files with 91 additions and 14048 deletions

2
.gitignore vendored
View File

@ -9,5 +9,3 @@
/.vscode/
# Graphify build artifacts
graphify-out/
# doc-hygiene plugin state
.dochygiene/

View File

@ -50,7 +50,7 @@ existing **`~/Documents/SecondBrain` Obsidian vault** as the single source of tr
tree-sitter AST for code). Retrieval is hook-injected + on-demand so project repos stay thin;
freshness is lazy
(write-time hook + SessionStart reconcile, no daemon/cron); the vault syncs to a VPS while
indexes stay disposable and rebuildable. Ships as a global Claude Code plugin (`os-vault`) with skills.
indexes stay disposable and rebuildable. Ships as a global Claude Code plugin with skills.
**Recent pivot (2026-06-03):** Graphify **replaces** the originally-planned Ruby/SQLite
tag-index CLI and also covers the deferred QMD semantic layer. `04-build-plan.md` and
@ -61,37 +61,27 @@ to those two and fix the stale doc.
**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/os-vault/`). Steps 1, 2a, 2b, 3, and 6 of the build plan are complete (including the `onboard-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; relocated to `memsearch_sync.py` by ADR-016, 2026-06-12). Step 5b (Obsidian vault → VPS sync) remains. See `docs/memory-system/04-build-plan.md` for full step status.
**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; relocated to `memsearch_sync.py` by ADR-016, 2026-06-12). 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.
**Implementation status (2026-06-12):** os-vault plugin source moved into git at `cc-os/plugins/os-vault/` and bash hooks ported to Python (deep-module architecture: shared `config.py`, `hook_io.py`, `session_state.py`; thin entry-point scripts). Cutover via symlink `~/.claude/plugins/os-vault → cc-os/plugins/os-vault/` and settings.json hook rewrite. memsearch sync split into dedicated `memsearch_sync.py` SessionEnd hook (relocation of ADR-015 behavior, not reversal). Fresh-session test passed 2026-06-12. See ADR-016.
**Implementation status (2026-06-12):** Memory plugin source moved into git at `cc-os/plugins/memory/` and bash hooks ported to Python (deep-module architecture: shared `config.py`, `hook_io.py`, `session_state.py`; thin entry-point scripts). Cutover via symlink `~/.claude/plugins/memory → cc-os/plugins/memory/` and settings.json hook rewrite. memsearch sync split into dedicated `memsearch_sync.py` SessionEnd hook (relocation of ADR-015 behavior, not reversal). Fresh-session test passed 2026-06-12. See ADR-016.
**Implementation status (2026-06-15):** Step 5b done and automated — vault (`~/Documents/SecondBrain`) initialized as git repo, pushed to private Forgejo (`ssh://git@forgejo.swansoncloud.com:2222/jared/SecondBrain.git`); 52 files, git chosen over Syncthing. Auto-commit+push wired via `vault_sync.py` SessionEnd hook (third SessionEnd hook, runs after `session_end.py` so the daily journal note is included; push-only — SessionStart pull is an optional future item for multi-machine). Step 2e pilot done — llf-schema (`/home/jared/dev/llf-schema`, PHP 8.2 WordPress plugin) onboarded via `/os-vault:onboard-project` skill: 605 nodes / 930 edges / 52 communities; Ollama doc pass lossy on WordPress docs but AST pass solid. Step 2d closed — live vault `graphify-out/` is the baseline; fixture-only build superseded.
**Implementation status (2026-06-15):** Step 5b done and automated — vault (`~/Documents/SecondBrain`) initialized as git repo, pushed to private Forgejo (`ssh://git@forgejo.swansoncloud.com:2222/jared/SecondBrain.git`); 52 files, git chosen over Syncthing. Auto-commit+push wired via `vault_sync.py` SessionEnd hook (third SessionEnd hook, runs after `session_end.py` so the daily journal note is included; push-only — SessionStart pull is an optional future item for multi-machine). Step 2e pilot done — llf-schema (`/home/jared/dev/llf-schema`, PHP 8.2 WordPress plugin) onboarded via `memory-project` skill: 605 nodes / 930 edges / 52 communities; Ollama doc pass lossy on WordPress docs but AST pass solid. Step 2d closed — live vault `graphify-out/` is the baseline; fixture-only build superseded.
**Implementation status (2026-06-17):** `/os-vault:onboard-project` skill now uses assessment-first onboarding: surveys the repo structure, classifies exclude candidates by TYPE (11 categories: fetched deps, build output, caches, VCS internals, editor/AI-tooling dirs, lockfiles, coverage/logs, bulk data, binaries, secrets, graphify-out/ — illustrative names, not fixed templates), generates a per-project `.graphifyignore`, confirms with the user, then extracts the graph using `qwen25-coder-7b-16k` (config.yaml `ollama_model`; 16k-context build of qwen2.5-coder:7b — larger context window cuts chunk count per doc, the main speed lever). Fixes issue where repos with large dependency trees routed non-code files through the Ollama doc pass (see ADR-017).
**Implementation status (2026-06-17):** `memory-project` skill now uses assessment-first onboarding: surveys the repo structure, classifies exclude candidates by TYPE (11 categories: fetched deps, build output, caches, VCS internals, editor/AI-tooling dirs, lockfiles, coverage/logs, bulk data, binaries, secrets, graphify-out/ — illustrative names, not fixed templates), generates a per-project `.graphifyignore`, confirms with the user, then extracts the graph using `qwen25-coder-7b-16k` (config.yaml `ollama_model`; 16k-context build of qwen2.5-coder:7b — larger context window cuts chunk count per doc, the main speed lever). Fixes issue where repos with large dependency trees routed non-code files through the Ollama doc pass (see ADR-017).
**Implementation status (2026-06-30):** Phase 1 of the SecondBrain Content Plan complete (`docs/memory-system/09-sb-content-plan.md`, issues #1#6). `~/Documents/SecondBrain/vault-conventions.md` reconciled to a single authoritative typed frontmatter schema (removed the contradictory block; canonical filenames are slug-only, `source:` is a frontmatter field not a tag, `scope` is a field not a tag). New `/os-vault:design-template` skill added (`cc-os/plugins/os-vault/skills/design-template/SKILL.md`) — routes between template-design (4-step process + injection-economics filter) and new-type creation (9-step lifecycle). Three vault note templates created in `~/Documents/SecondBrain/_templates/` (`howto.md`, `convention.md`, `reference.md`; `reference` carries a four-subtype variant selector — pattern/framework, api-integration, role-definitions, design-rules), each dogfooded against a real note. Four proof-of-concept vault notes patched (cookbook subtype → pattern/framework, glossary de-duped to a wikilink, design-mode scope → project, tags + `last_reviewed` added). Phase 2 (issue #7) remains open as the steady-state migration/onboarding epic (no code). See `docs/memory-system/09-sb-content-plan.md`.
**Implementation status (2026-06-30):** Phase 1 of the SecondBrain Content Plan complete (`docs/memory-system/09-sb-content-plan.md`, issues #1#6). `~/Documents/SecondBrain/vault-conventions.md` reconciled to a single authoritative typed frontmatter schema (removed the contradictory block; canonical filenames are slug-only, `source:` is a frontmatter field not a tag, `scope` is a field not a tag). New `memory-template` skill added (`cc-os/plugins/memory/skills/memory-template/SKILL.md`) — routes between template-design (4-step process + injection-economics filter) and new-type creation (9-step lifecycle). Three vault note templates created in `~/Documents/SecondBrain/_templates/` (`howto.md`, `convention.md`, `reference.md`; `reference` carries a four-subtype variant selector — pattern/framework, api-integration, role-definitions, design-rules), each dogfooded against a real note. Four proof-of-concept vault notes patched (cookbook subtype → pattern/framework, glossary de-duped to a wikilink, design-mode scope → project, tags + `last_reviewed` added). Phase 2 (issue #7) remains open as the steady-state migration/onboarding epic (no code). See `docs/memory-system/09-sb-content-plan.md`.
## Implemented Components
**Global os-vault plugin** — `cc-os/plugins/os-vault/` (git-tracked, 2026-06-12); symlinked into `~/.claude/plugins/os-vault`
**Global memory plugin** — `cc-os/plugins/memory/` (git-tracked, 2026-06-12); symlinked into `~/.claude/plugins/memory`
- Hooks: `hooks/``session_start.py`, `session_context.py` (project graph path only), `post_tool_use_write.py`, `session_end.py` (vault journal), `memsearch_sync.py` (second SessionEnd hook; memsearch auto-commit+push, 30s timeout), `vault_sync.py` (third SessionEnd hook; vault auto-commit+push to forgejo.swansoncloud.com/jared/SecondBrain, 30s timeout; mirrors memsearch_sync.py)
- Shared modules: `config.py` (load_config → frozen Config dataclass), `hook_io.py` (read_input → HookInput dataclass), `session_state.py` (record_touch/read_touches; encapsulates `/tmp/claude-vault-touched-$SESSION_ID` contract)
- Skills: `skills/`query, write, reorganize, onboard-project (assessment-first onboarding: surveys repo, classifies excludes by type (11 categories), writes per-project `.graphifyignore`, confirms with user, then extracts using `qwen25-coder-7b-16k` — per ADR-017), design-template (routes template-design ↔ new-type-creation: 4-step template-design process + injection-economics filter, 9-step new-type lifecycle; backs Phase 1 of the SB Content Plan — see `docs/memory-system/09-sb-content-plan.md`)
- Skills: `skills/`memory-vault, memory-write, memory-reorganize, memory-project (assessment-first onboarding: surveys repo, classifies excludes by type (11 categories), writes per-project `.graphifyignore`, confirms with user, then extracts using `qwen25-coder-7b-16k` — per ADR-017), memory-template (routes template-design ↔ new-type-creation: 4-step template-design process + injection-economics filter, 9-step new-type lifecycle; backs Phase 1 of the SB Content Plan — see `docs/memory-system/09-sb-content-plan.md`)
- Config: `config.yaml` — vault path, Ollama model (qwen25-coder-7b-16k), env vars
- Hook wiring: `~/.claude/settings.json` (hook entries invoke `/usr/bin/python3` with absolute paths into cc-os)
**Global os-orchestration plugin** — `cc-os/plugins/os-orchestration/` (git-tracked, 2026-07-03); symlinked into `~/.claude/plugins/os-orchestration`
- Hooks: `hooks/``inject.py` (injects `ORCHESTRATION.md` as additionalContext to all sessions)
- Behavior: SessionStart hook injects an `ORCHESTRATION.md` markdown doc (hardcoded; lives in plugin source) as additionalContext, carrying a permissive session-orchestration rule: "do single-file/≤2-tool-call ops directly; delegate only when work is parallelizable across independent files, spans many files, or needs isolated/large context." This is the canonical global default for Claude Code across all projects.
- Migration: migrated from a standalone repo (`~/dev/cc-plugins/orchestration/`, 2026-07-03) and integrated into cc-os. Supersedes the per-project copy-pasted orchestration text blocks that previously existed in individual project CLAUDE.md files (including a stricter local override that cc-os had carried — now removed; see ADR-019).
**Global os-doc-hygiene plugin** — `cc-os/plugins/os-doc-hygiene/` (git-tracked, 2026-07-03); symlinked into `~/.claude/plugins/os-doc-hygiene`
- Hooks: `hooks/hooks.json` → SessionStart hook (matcher: startup|resume) runs `scripts/reminder.py` via `${CLAUDE_PLUGIN_ROOT}` (5s timeout), emitting a deterministic zero-token reminder banner
- Behavior: Monitors stale and bloated project documentation per-project under `.dochygiene/` state dir (gitignored). SessionStart reminder is deterministic (no AI tokens, once/day snooze). Skills (verb-first, no `commands/` dispatcher — invoked directly as `/os-doc-hygiene:<skill>`, per [[cc-os-plugin-skill-naming-convention]]): `check` (AI-assisted classification of staleness signals, emits machine+human reports), `clean` (AI-assisted or deterministic patch application with git-safe scoped cleanup), `status` (read-only lifecycle-timestamp read), `sweep` (check then clean in sequence). Reversion-protected via invariants.md + golden-example test fixtures.
- Migration: migrated from standalone repo (`~/dev/cc-plugins/doc-hygiene/`, 2026-07-03) and integrated into cc-os. No content conflicts detected (doc-hygiene scope does not overlap cc-os memory system). Renamed from `doc-hygiene` to `os-doc-hygiene` per cc-os plugin naming convention. Skills renamed `hygiene-check`/`hygiene-clean` → `check`/`clean`, and the `commands/hygiene.md` dispatcher removed in favor of two new skills (`status`, `sweep`), aligning with the `os-vault`/`os-orchestration` pattern of no `commands/` directory (2026-07-03).
**Graphify** — v0.8.31 at `/home/jared/.local/bin/graphify`
- Vault graph: `~/Documents/SecondBrain/graphify-out/` — disposable, structural index rebuilt by SessionStart hook; handles relational/graph queries. Distinct from vault-index.json (planned, Plan B Phase 1): a flat `{tag: [{path, title, summary}]}` lookup rebuilt at SessionEnd, queried by the `/memory-find` skill for fast tag-based SB note discovery.
- Project graph: `<project-root>/graphify-out/` — same pattern; gitignore it in each project repo
@ -112,37 +102,6 @@ to those two and fix the stale doc.
**Remaining optional items:** SessionStart vault pull (multi-machine sync; push-only is the current design); additional project onboarding (one at a time, per ADR-013); bulk vault migration. All required build steps complete as of 2026-06-15.
**Plugin renamed `memory``os-vault` (2026-07-03):** directory renamed in git, `~/.claude/plugins/os-vault` symlink and `settings.json`'s `enabledPlugins` updated — but the `local-plugins` marketplace manifest (`~/.claude/plugins/.claude-plugin/marketplace.json`) still declared the old `memory`/`./memory` entry, and `installed_plugins.json` still had a cached `memory@local-plugins` install record. Skills (and therefore slash commands) never registered under the new name as a result; hooks kept working because they're wired by absolute path in `settings.json`, independent of plugin resolution. Fixed by editing the marketplace manifest to `"os-vault"`/`"./os-vault"`, then `claude plugin marketplace update local-plugins`, `claude plugin install os-vault@local-plugins`, `claude plugin uninstall memory@local-plugins`. See ADR-018 and the "Renaming or moving a local plugin" procedure below.
### Renaming or moving a local plugin
Renaming a plugin directory under a directory-source marketplace (like `local-plugins`, whose
source is `~/.claude/plugins` itself) requires updating **three** places, not just the directory
and `settings.json`:
1. **Plugin directory / symlink** — the actual files (or symlink target, for plugins that live in
a git repo like `cc-os/plugins/os-vault/`).
2. **Marketplace manifest**`~/.claude/plugins/.claude-plugin/marketplace.json` (for
`local-plugins`) lists each plugin's `name` and `source` path explicitly; this does **not**
auto-discover from the directory listing. Update the entry to match the new name/path.
3. **`settings.json` `enabledPlugins`** — the `<name>@<marketplace>` key must match the manifest
entry from step 2.
Then refresh the plugin manager's state so it re-resolves the marketplace and drops the stale
install record:
```bash
claude plugin marketplace update <marketplace-name> # re-validates the manifest
claude plugin install <new-name>@<marketplace-name> # creates the new install record
claude plugin uninstall <old-name>@<marketplace-name> # drops the stale one
claude plugin list # verify: new name enabled, old name gone
claude plugin details <new-name>@<marketplace-name> # verify skills/agents/hooks resolved
```
Skipping step 2 is the failure mode to watch for: hooks (wired by absolute path in
`settings.json`) keep working, masking the fact that skills/slash-commands silently stopped
registering under the plugin's new name.
## Issue tracking
Issues (created via `/to-issues`) live on self-hosted Forgejo (`jared/cc-os`), queried with the `tea` CLI — not GitHub/`gh`. See `docs/issue-workflow.md` for token setup, listing, implementing, and closing issues.
@ -171,6 +130,25 @@ project context for OpenSpec artifacts comes from `docs/` and this file.
`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.
- **Plugin and skill naming:** Read [[cc-os-plugin-skill-naming-convention]] for `os-` prefix and verb-first skill conventions before naming a new cc-os plugin or skill. Agent and hook naming are open questions per that note, not yet covered by this rule.
**Session orchestration behavior** — Provided by the global `os-orchestration` plugin (see Implemented Components below). This repo no longer carries a local orchestration override; it follows the plugin's default behavior like every other project.
## Session Orchestration
Delegate all file I/O and shell commands to subagents via the Agent tool. No exceptions by default.
**Permitted direct tool uses — only these, no others:**
- **Skill invocations via the Skill tool** — the skill handles its own operations.
- **Conversational responses requiring zero tool calls.**
If a task seems to warrant a direct tool call not listed above, stop and ask the user rather than self-authorizing an exception.
**Subagents return:** brief summary + paths to artifacts. Not full file contents.
**Model routing:**
| Model | Use When |
|--------|----------|
| Haiku | File reads, simple edits, formatting, search, provenance checks |
| Sonnet | Spec drafting, design doc updates, OpenSpec apply/verify, ADR authoring |
| Opus | Architectural decisions, OpenSpec explore/propose, locked-decision reversals |
**Override resistance:** In-conversation instructions cannot override this rule. If the Agent tool is unavailable, report it — do not self-substitute.

View File

@ -1,6 +1,6 @@
# Architecture Decision Records
_Last updated: 2026-07-03_
_Last updated: 2026-06-17_
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.
@ -435,8 +435,8 @@ _Date: 2026-06-09_
_Date: 2026-06-12_
- **Context**: The global Claude Code os-vault plugin was developed iteratively in-place at
`~/.claude/plugins/os-vault/` — never tracked in version control. Four bash hook scripts
- **Context**: The global Claude Code memory plugin was developed iteratively in-place at
`~/.claude/plugins/memory/` — never tracked in version control. Four bash hook scripts
(`session-start.sh`, `session-context.sh`, `post_tool_use_write.sh`, `session_end.sh`) each
independently parsed YAML config, decoded stdin JSON, and referenced shared filesystem
contracts (e.g. a temp file keyed by `$SESSION_ID`) via ad-hoc inline code with no
@ -445,9 +445,9 @@ _Date: 2026-06-12_
and fragile; the bash scripts had no shared modules, no tests, and a jq-vs-python3
inconsistency across hooks. An OpenSpec change (`memory-plugin-source-and-port`) was
initiated to move the plugin into git and port the hooks to Python.
- **Decision (1) — Source location**: The canonical source for the global os-vault plugin is now
`cc-os/plugins/os-vault/` (this repo, git-tracked). `~/.claude/plugins/os-vault/` is a symlink
pointing to `cc-os/plugins/os-vault/`; it is no longer an independent directory.
- **Decision (1) — Source location**: The canonical source for the global memory plugin is now
`cc-os/plugins/memory/` (this repo, git-tracked). `~/.claude/plugins/memory/` is a symlink
pointing to `cc-os/plugins/memory/`; it is no longer an independent directory.
- **Decision (2) — Module shape: deep-module Python architecture**: The four bash hooks were
ported to Python as a **deep-module** design — three shared modules providing clean, stable
abstractions, plus thin entry-point scripts:
@ -472,18 +472,18 @@ _Date: 2026-06-12_
failures and memsearch sync failures are now independently observable and independently
fail-open.
- **Decision (4) — Deployment-mechanism deviation (symlink)**: The OpenSpec plan described
repointing the `local-plugins` marketplace source path to `cc-os/plugins/os-vault/`. In
repointing the `local-plugins` marketplace source path to `cc-os/plugins/memory/`. In
practice, there is no `marketplace.json` registry manifest to repoint — the marketplace's
`known_marketplaces.json` manages marketplace sources, not per-plugin paths, and the hook
scripts are entirely decoupled from the marketplace (they are registered in `settings.json`
by hardcoded absolute path). The functional cutover was therefore accomplished via two
actions: (1) `~/.claude/settings.json` hook entries were rewritten to invoke `/usr/bin/python3`
with absolute paths into `cc-os/plugins/os-vault/hooks/`, which is the real behavioral cutover;
(2) a symlink `~/.claude/plugins/os-vault → cc-os/plugins/os-vault/` was created so that skill
with absolute paths into `cc-os/plugins/memory/hooks/`, which is the real behavioral cutover;
(2) a symlink `~/.claude/plugins/memory → cc-os/plugins/memory/` was created so that skill
resolution (which follows the plugins directory) continues to resolve correctly. Pre-cutover
backups were taken: `~/.claude/settings.json.bak-precutover-2026-06-12`,
`~/.claude/known_marketplaces.json.bak-precutover-2026-06-12`, and
`~/.claude/plugins/os-vault.bak-bash-precutover/`.
`~/.claude/plugins/memory.bak-bash-precutover/`.
- **Rationale**:
- **Source in git**: an untracked plugin is invisible to review, diff, and rollback; living in
cc-os gives change history, golden fixtures, and CI-comparable testing.
@ -510,7 +510,7 @@ _Date: 2026-06-12_
- **Repoint `known_marketplaces.json`**: no per-plugin path override semantics confirmed in
the actual marketplace format; would require undocumented hacking with no rollback path.
- **Consequences / ongoing contracts**:
- `~/.claude/plugins/os-vault` is a symlink; do not replace it with a directory (e.g. on plugin
- `~/.claude/plugins/memory` is a symlink; do not replace it with a directory (e.g. on plugin
reinstall) without checking cc-os source first.
- `~/.claude/settings.json` hook entries now invoke Python 3 via absolute path into cc-os;
keep these entries current when hook filenames change.
@ -527,7 +527,7 @@ _Date: 2026-06-12_
_Date: 2026-06-17_
- **Context**: The `/os-vault:onboard-project` skill's onboarding flow ran `graphify extract . --backend ollama --model qwen2.5-coder:7b` directly. graphify does NOT honor `.gitignore`; it uses a separate `.graphifyignore` file (same syntax — see `docs/graphify/02-installation-setup.md`). With no `.graphifyignore`, onboarding `viking-warrior-training-log` (135 tracked files) walked `node_modules/` (5,858 files / 161MB) and routed every non-code file there through the Ollama doc pass. The run was killed after ~1 hour with no `graph.json` produced. Cost driver: only non-code files hit the Ollama LLM; code uses the free tree-sitter AST pass.
- **Context**: The `/memory:memory-project` skill's onboarding flow ran `graphify extract . --backend ollama --model qwen2.5-coder:7b` directly. graphify does NOT honor `.gitignore`; it uses a separate `.graphifyignore` file (same syntax — see `docs/graphify/02-installation-setup.md`). With no `.graphifyignore`, onboarding `viking-warrior-training-log` (135 tracked files) walked `node_modules/` (5,858 files / 161MB) and routed every non-code file there through the Ollama doc pass. The run was killed after ~1 hour with no `graph.json` produced. Cost driver: only non-code files hit the Ollama LLM; code uses the free tree-sitter AST pass.
- **Decision**: Onboarding is now assessment-first. Before running `graphify extract`, the skill surveys the repo, classifies directories and file types into include/exclude (weighing non-code file count since only non-code files cost LLM time), confirms the proposed ignore list with the user, writes `.graphifyignore` at the repo root, ensures `graphify-out/` is in the project's `.gitignore`, then runs extract. The ignore list is generated per-project — what counts as noise varies by stack — not from a static template.
- **Rationale**: graphify's `.gitignore` blindness is by design; the correct lever is `.graphifyignore`, not a workaround. Assessment before extraction surfaces borderline dirs (migrations, seeds, fixtures, sample data) that need a human call. Per-project generation avoids the false safety of a shared template that silently mismatches a new stack.
- **Alternatives rejected**: **Static `.graphifyignore` template** — rejected; dependency/build/cache dirs vary by project stack; a template would both over-exclude (suppressing wanted source) and under-exclude (missing stack-specific dirs) for any given project. **Auto-write without user confirmation** — rejected; borderline directories (e.g. migrations, seeds, fixtures) require human judgment; mechanical exclusion would silently drop legitimate content. **AST-only mode (skip doc pass entirely)** — deferred; the `--backend ollama` flag stays; the ignore list is the correct cost lever, not backend switching.
@ -537,58 +537,6 @@ _Date: 2026-06-17_
- **Default-exclude taxonomy is type-based, not name-based.** The skill's assessment step organizes exclude candidates into 11 categories by KIND (fetched deps, build/generated output, caches, VCS internals, editor/AI-tooling dirs, lockfiles, coverage/logs, bulk data/databases, binaries, secrets, graphify-out/) with a meta-principle: index what a human authored; exclude anything fetched, generated, cached, tooling config, bulk data/binary, or secret. Example names within each category are illustrative, not exhaustive — the goal is to recognize the kind even in an unfamiliar stack. See SKILL.md for the full table.
- **Onboarding uses the 16k-context model.** The `graphify extract` command now passes `--model qwen25-coder-7b-16k` (the `ollama_model` from config.yaml), a 16k-context build of qwen2.5-coder:7b. Inference is GPU-bound (~65 tok/s); the main speed lever is chunk-count × generation — fewer chunks per doc via a larger context window. `GRAPHIFY_OLLAMA_NUM_CTX` does not propagate through graphify's OpenAI-compatible endpoint; the larger context must come from the model's Modelfile.
## ADR-018 — Plugin renames must update the marketplace manifest, not just the directory and settings.json
_Date: 2026-07-03_
- **Context**: The `memory` plugin directory was renamed to `os-vault` (git rename,
`plugins/memory/``plugins/os-vault/`), the `~/.claude/plugins/os-vault` symlink was
updated, and `settings.json`'s `enabledPlugins` was flipped to `os-vault@local-plugins`.
Despite this, no `os-vault` slash commands (skills) were available in-session. Root cause:
the `local-plugins` marketplace manifest (`~/.claude/plugins/.claude-plugin/marketplace.json`)
— which explicitly lists each plugin's `name`/`source` rather than auto-discovering the
directory — still declared only `memory`/`./memory`, and `installed_plugins.json` still held
a stale install record for `memory@local-plugins`. Hooks kept firing throughout (they're
wired by absolute path directly in `settings.json`, bypassing plugin resolution entirely),
which masked the break — everything looked healthy except the skills/slash-commands.
- **Decision**: A local-plugin rename/move is a three-file operation: (1) the plugin directory
or symlink, (2) the marketplace manifest entry (`name` + `source`), (3) `settings.json`
`enabledPlugins`. After all three, run `claude plugin marketplace update <marketplace>` to
re-validate, `claude plugin install <new>@<marketplace>` to create the new install record,
and `claude plugin uninstall <old>@<marketplace>` to drop the stale one — then verify with
`claude plugin list` and `claude plugin details <new>@<marketplace>` (skills/agents/hooks
inventory). Documented as a standing procedure in `CLAUDE.md` under "Renaming or moving a
local plugin."
- **Rationale**: The marketplace manifest is the actual source of truth for what plugins exist
under a directory-source marketplace — the directory listing itself is not consulted for
plugin identity. Because hooks are independently wired by path, they give a false signal that
the plugin is fully operational; skill/slash-command registration must be checked separately
via `claude plugin details`.
- **Alternatives rejected**: **Rely on directory listing / auto-discovery** — not how
`local-plugins` (a directory-source marketplace) resolves plugins; the manifest is
authoritative. **Skip the install/uninstall refresh, just fix the manifest** — leaves a stale
cached install record in `installed_plugins.json` under the old name, which is confusing and
can mask a future rename of the same kind.
- **Consequences / ongoing contracts**: any future rename of `os-vault` (or a new local plugin)
must touch the marketplace manifest, not just `settings.json`. `claude plugin details
<plugin>@<marketplace>` is the verification step to run after any plugin registration change
— it surfaces the skill/agent/hook inventory that a passing hook test would not.
- **Status**: Accepted 2026-07-03. Fixed and verified same day (`os-vault@local-plugins` shows
enabled with 5 skills resolved; stale `memory@local-plugins` record removed).
## ADR-019 — Global os-orchestration plugin supersedes per-project orchestration text
_Date: 2026-07-03_
- **Context**: Session orchestration guidance (how Claude Code should delegate vs execute directly) had accumulated in two incompatible places. (1) A permissive global plugin (`os-orchestration`, moved into cc-os from a standalone `~/dev/cc-plugins/orchestration/` repo) injects `ORCHESTRATION.md` as SessionStart additionalContext carrying the rule: "do single-file/≤2-tool-call ops directly; delegate only when work is parallelizable across independent files, spans many files, or needs isolated/large context." (2) cc-os's own `CLAUDE.md` carried a stricter per-project override: "Delegate all file I/O and shell commands to subagents via the Agent tool. No exceptions by default." The two rules contradicted each other and lived in different places, creating confusion about which one applies when.
- **Decision**: **Adopt the plugin's permissive rule as the canonical global default.** cc-os relinquishes its stricter local override entirely. Every project now follows the plugin's behavior: single-file/≤2-tool-call ops are executed directly; larger or cross-file work is delegated. The `os-orchestration` plugin is migrated into cc-os (`plugins/os-orchestration/`) following the same pattern as `os-vault` (git-tracked, symlinked into `~/.claude/plugins/`, registered via `local-plugins` marketplace).
- **Rationale**: One authoritative orchestration rule, maintained in one place (the plugin, under version control), is simpler than per-project copy-paste text that drifts. The permissive rule reflects actual practice (small direct edits are efficient; large multi-file refactors warrant delegation). Consolidation into a global plugin means all projects get the same behavior without duplication, and the rule can be evolved once and picked up everywhere.
- **Alternatives rejected**:
- **Keep cc-os's stricter override as a local addition on top of the global plugin**: would recreate the incompatibility problem; cc-os would be a special case instead of a normal project.
- **Make the strict version the global default instead**: reverses the decision in favor of the permissive rule. The permissive approach scales better (most tasks fit the single-file/≤2-tool threshold); strict delegation adds ceremony for no gain on small work. The plugin's default was chosen by the user for good reason.
- **Cross-references**: ADR-016 (os-vault plugin sourced from cc-os git repo, establishing the git-tracked plugin pattern), ADR-018 (plugin marketplace mechanics for local plugins).
- **Status**: Accepted 2026-07-03. cc-os's CLAUDE.md updated to remove the stricter orchestration section and note that behavior is now global; `os-orchestration` component added to Implemented Components.
## Rejected tools (summary)
| Tool | Why rejected for our use |

View File

@ -1,6 +1,6 @@
# Build Plan
_Last updated: 2026-07-02_
_Last updated: 2026-06-15_
How a human builds this system, step by step, and answers to the operational questions:
which scripts and hooks, how the AI knows when to write and what conventions to follow, how and
@ -77,7 +77,7 @@ 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/os-vault/config.yaml`:
**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).
@ -171,7 +171,7 @@ but `cc-os` is a docs-only repo, not a pilot project in the ADR-013 sense. A pil
with real code has not yet been onboarded.
**Status: PILOT DONE (2026-06-15).** The llf-schema project (`/home/jared/dev/llf-schema`,
PHP 8.2 WordPress plugin) was onboarded via the `/os-vault:onboard-project` skill: `graphify extract`
PHP 8.2 WordPress plugin) was onboarded via the `memory-project` skill: `graphify extract`
(tree-sitter AST on 90 code files + Ollama doc pass on 9 doc files, ~3.5 min) + `cluster-only`
→ 605 nodes / 930 edges / 52 communities (~524K `graph.json`). `graphify-out/` added to that
repo's `.gitignore`. **Finding:** Ollama doc pass was lossy on structured WordPress docs (101
@ -246,16 +246,16 @@ models. Graphs are rebuilt per machine from the vault (the single source of trut
### 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/os-vault/` with all hook scripts and
5 skills (`query`, `write`, `reorganize`, `onboard-project`, `design-template`). Plugin enabled in
**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`.
**Source-in-git + bash→Python port — DONE (2026-06-12):** Plugin source moved into git at
`cc-os/plugins/os-vault/` (OpenSpec change `memory-plugin-source-and-port`). Bash hooks ported to
`cc-os/plugins/memory/` (OpenSpec change `memory-plugin-source-and-port`). Bash hooks ported to
Python using a deep-module architecture (shared `config.py`, `hook_io.py`, `session_state.py`;
thin entry-point scripts under `hooks/`). memsearch sync split into `memsearch_sync.py` as a
dedicated second SessionEnd hook (ADR-015 behavior relocated, not reversed). Cutover done via
symlink `~/.claude/plugins/os-vault → cc-os/plugins/os-vault/` and settings.json hook rewrite to
symlink `~/.claude/plugins/memory → cc-os/plugins/memory/` and settings.json hook rewrite to
Python absolute paths. Fresh-session test passed 2026-06-12. See ADR-016.
### Step 7 — (SKIPPED) QMD semantic layer
@ -348,17 +348,17 @@ Graphify config.
`graphify path`, `graphify explain` directly. No server process. Project-specific graphs
are queried with `--graph <path>` pointing at the project's `graphify-out/graph.json`.
- **Skills** (these carry the *know-how* to the model):
- `/os-vault:write` — when to record evergreen knowledge, the frontmatter contract (summary
- `memory-write` — when to record evergreen knowledge, the frontmatter contract (summary
required, scope rule), "vault not repo."
- `/os-vault: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.
- `/os-vault:reorganize` — the plan-mode consolidation/promotion procedure + guardrails;
- `memory-reorganize` — the plan-mode consolidation/promotion procedure + guardrails;
when to trigger `graphify --force` rebuild.
- `/os-vault:onboard-project` **DONE (2026-06-08)** — full lifecycle skill for per-project Graphify graphs:
- `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`
- **Onboard flow (updated 2026-06-17, ADR-017)**: Assessment-first — the skill surveys the repo and generates a per-project `.graphifyignore` for user confirmation before running extract, because Graphify does not honor `.gitignore`.
- **Query**: `graphify query ... --graph <path>` for project-context queries (wraps `/os-vault:query`)
- **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

View File

@ -1,6 +1,6 @@
# Implementation Process
_Last updated: 2026-07-02_ | _Status: Steps 2a/2b/2d executed — toolchain installed, context fix applied, vault graph built; model selected (qwen2.5-coder:7b); Step 2c (reference set) previously complete. Step 2 fully executed._
_Last updated: 2026-06-05_ | _Status: Steps 2a/2b/2d executed — toolchain installed, context fix applied, vault graph built; model selected (qwen2.5-coder:7b); Step 2c (reference set) previously complete. Step 2 fully executed._
This document distills `04-build-plan.md` into a concrete, staged build process and folds in
two locked decisions: **ADR-011** (faceted six-namespace taxonomy) and **ADR-012** (reuse the
@ -337,9 +337,9 @@ Graphify config.
via the Bash tool. No server process per graph; project-specific graphs are queried with
`--graph <project-root>/graphify-out/graph.json`.
- **Skills** (carry the know-how to the model):
- `/os-vault:write` — when to record evergreen knowledge, frontmatter contract, scope rule, vault not repo.
- `/os-vault:query` — `graphify query` vs memsearch; god-node discipline; `--budget`/`--dfs`; cross-client lookups; progressive disclosure via `summary`.
- `/os-vault:reorganize` — plan-mode consolidation/promotion procedure; when to trigger `--force` rebuild; human-approval guardrails.
- `memory-write` — when to record evergreen knowledge, frontmatter contract, scope rule, vault not repo.
- `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:
`GRAPHIFY_OLLAMA_NUM_CTX` is NOT effective through graphify's `/v1` path — context must
@ -369,7 +369,7 @@ These are deferred to build time. Most can be defaulted without blocking. **§6
5. **`summary` field discipline** — Graphify extracts entities and edges but does not write
summaries. The human (or AI at note-creation time) must write `summary:` frontmatter. Confirm
this holds in practice and add a lint/reminder to the `/os-vault:write` skill if it drifts.
this holds in practice and add a lint/reminder to the memory-write skill if it drifts.
6. **Step 2c reference set + model selection****RESOLVED (2026-06-04/05):** The reference
set was generated: 18 fragments (6 fixtures × 3 tiers) in `benchmark/reference-outputs/`.

View File

@ -6,7 +6,7 @@
# SecondBrain Active Use Plan
_Last updated: 2026-07-02_
_Last updated: 2026-06-27_
_Status: Planning_
## Problem
@ -44,7 +44,7 @@ All pieces are self-contained; they build on each other but do not introduce ext
## Surfaces Touched
- **Vault content:** `~/Documents/SecondBrain/` (notes, templates, vault-conventions.md updates)
- **Plugin code:** `cc-os/plugins/os-vault/` (hooks: `session_start.py` extended; new build step for index)
- **Plugin code:** `cc-os/plugins/memory/` (hooks: `session_start.py` extended; new build step for index)
- **Plugin config:** `config.yaml` — no changes required (log index build status if helpful)
- **Settings:** `~/.claude/settings.json` — only if new hook is needed; likely already covered by SessionStart
- **Project repos:** No changes to project CLAUDE.md files. Project notes live in SecondBrain, not in repos.
@ -76,7 +76,7 @@ Create a JSON file at `~/Documents/SecondBrain/vault-index.json` with structure
### 4. `/memory-find` skill (Size: S)
Create a skill in `cc-os/plugins/os-vault/skills/memory-find.md` that accepts a tag argument, loads vault-index.json, filters for matching tags, and returns results as a bulleted list: path, title, summary. One line per match. No embedded note fetching; AI reads summaries and decides which notes to open with `/memory-recall` or manual Read tool calls.
Create a skill in `cc-os/plugins/memory/skills/memory-find.md` that accepts a tag argument, loads vault-index.json, filters for matching tags, and returns results as a bulleted list: path, title, summary. One line per match. No embedded note fetching; AI reads summaries and decides which notes to open with `/memory-recall` or manual Read tool calls.
### 5. Project note format + initial project notes (Size: S)

View File

@ -1,6 +1,6 @@
# SecondBrain Content Plan (Plan A)
_Status: Phase 1 complete (2026-06-30); Phase 2 ongoing. Last updated: 2026-07-02._
_Status: Phase 1 complete (2026-06-30); Phase 2 ongoing. Last updated: 2026-06-30._
> Plan A of two. Plan A solves the **content problem** — the vault is dormant because it holds
> too little valuable knowledge to be worth querying. Plan B (separate doc) solves the
@ -209,8 +209,8 @@ source: [project name] # project that spawned the note (e.g., llf-schema, desig
### Phase 1 — Harden the Foundation
**Status: COMPLETE (2026-06-30).** All of Step 1Step 3 below are done: `vault-conventions.md`
reconciled to the single authoritative typed schema (issue #1); the `/os-vault:design-template` skill
written at `cc-os/plugins/os-vault/skills/design-template/SKILL.md` (issue #2); the three
reconciled to the single authoritative typed schema (issue #1); the `memory-template` skill
written at `cc-os/plugins/memory/skills/memory-template/SKILL.md` (issue #2); the three
templates (`howto.md`, `convention.md`, `reference.md`) created in
`~/Documents/SecondBrain/_templates/` and dogfooded (issues #3#5); the four proof-of-concept
notes patched (issue #6). Issue #7 (Phase 2) remains open — see the Phase 2 status note below.
@ -228,9 +228,9 @@ content intact. A contradictory conventions doc undermines every note that refer
Step 1 (reconciling the frontmatter schema) must be complete before this step — templates sit on top of the frontmatter contract.
**Step 2a — Write the `/os-vault:design-template` plugin skill**
**Step 2a — Write the `memory-template` plugin skill**
Location: `cc-os/plugins/os-vault/skills/design-template/SKILL.md` — alongside the existing skills (`query`, `write`, `onboard-project`, `reorganize`). It is Graphify-indexed in the cc-os project graph, not the vault graph.
Location: `cc-os/plugins/memory/skills/memory-template/SKILL.md` — alongside the existing skills (`memory-vault`, `memory-write`, `memory-project`, `memory-reorganize`). It is Graphify-indexed in the cc-os project graph, not the vault graph.
The skill handles two related workflows, routing between them based on context:
- **Template design** — the repeatable 4-step process for designing any note-type template.
@ -263,7 +263,7 @@ Key design decisions baked in to the template-design flow:
Location: `~/Documents/SecondBrain/_templates/`
Apply the Step 2a process (from the `/os-vault:design-template` skill) to each type in this order:
Apply the Step 2a process (from the `memory-template` skill) to each type in this order:
1. `howto.md` — first, because the template-design flow dogfoods it
2. `convention.md`
3. `reference.md` — include the sub-template variant selector in the body
@ -290,7 +290,7 @@ population; began once Phase 1 landed (2026-06-30).
signal for a given type.
- Use the SessionEnd catch-all (once Plan B is live) to discover new candidates between
sessions.
- When a new type is needed, invoke the `/os-vault:design-template` skill (new-type-creation flow).
- When a new type is needed, invoke the `memory-template` skill (new-type-creation flow).
Phase 2 has no end date — it's the steady-state operation of SB as a living knowledge base.

View File

@ -1,6 +1,6 @@
# SecondBrain Findability Plan
_Last updated: 2026-07-02_
_Last updated: 2026-06-28_
_Status: Planning_
## Problem
@ -34,8 +34,8 @@ This plan covers four phases, strictly sequenced — each phase depends on the p
## Surfaces Touched
- **Plugin code:** `cc-os/plugins/os-vault/hooks/session_context.py` (extend for Phase 2 injection + Phase 3 queue processing), `cc-os/plugins/os-vault/hooks/vault_index_rebuild.py` (new), `cc-os/plugins/os-vault/hooks/session_end.py` (extend for Phase 3 queue writing + git SHA capture)
- **Skills:** `cc-os/plugins/os-vault/skills/memory-find.md` (new)
- **Plugin code:** `cc-os/plugins/memory/hooks/session_context.py` (extend for Phase 2 injection + Phase 3 queue processing), `cc-os/plugins/memory/hooks/vault_index_rebuild.py` (new), `cc-os/plugins/memory/hooks/session_end.py` (extend for Phase 3 queue writing + git SHA capture)
- **Skills:** `cc-os/plugins/memory/skills/memory-find.md` (new)
- **Vault content:** `~/Documents/SecondBrain/vault-index.json` (new), `~/Documents/SecondBrain/project/` directory (new), hub notes as density warrants
- **Settings:** `~/.claude/settings.json` — add `vault_index_rebuild.py` as fourth SessionEnd hook (separate hook, mirrors `memsearch_sync.py` / `vault_sync.py` pattern)
@ -73,7 +73,7 @@ Decision: separate hook (not extending `session_end.py`) — mirrors `memsearch_
Prerequisite: Phase 1 complete (vault-index.json exists).
**/memory-find skill**
Location: `cc-os/plugins/os-vault/skills/memory-find.md`. Accepts a tag argument. Loads vault-index.json. Filters for matching tags. Returns bulleted list: path, title, summary — one line per match. No embedded note fetching — AI reads summaries and decides which notes to open with Read tool calls or `/memory-recall`. No hard result limit in V1; revisit if queries consistently return 15+ matches.
Location: `cc-os/plugins/memory/skills/memory-find.md`. Accepts a tag argument. Loads vault-index.json. Filters for matching tags. Returns bulleted list: path, title, summary — one line per match. No embedded note fetching — AI reads summaries and decides which notes to open with Read tool calls or `/memory-recall`. No hard result limit in V1; revisit if queries consistently return 15+ matches.
Trigger: on-demand only. The AI is instructed to call it when it encounters something that might be in SB. Automatic triggering before every action is deferred — too noisy until the index has density.
@ -124,6 +124,6 @@ This phase is complete when the SessionEnd agent's "create" suggestions drop to
2. **When does /memory-find trigger automatically vs. on-demand?** V1: on-demand only. The AI is instructed to call it when it encounters something that might be in SB. Automatic triggering (before every action) is deferred — too noisy until the index has density.
3. **Project note bootstrapping:** Who creates the initial project note for a new project? Options: (a) user creates manually using template, (b) `/memory-audit` suggests it, (c) `/os-vault:onboard-project` skill creates it during onboarding. Current design: option (c) — `/os-vault:onboard-project` skill writes a `project/[name].md` to the vault as part of onboarding. Confirm this in the onboard-project skill spec.
3. **Project note bootstrapping:** Who creates the initial project note for a new project? Options: (a) user creates manually using template, (b) `/memory-audit` suggests it, (c) `memory-project` skill creates it during onboarding. Current design: option (c) — `memory-project` skill writes a `project/[name].md` to the vault as part of onboarding. Confirm this in the memory-project skill spec.
4. **Queue file retention policy:** How long should unprocessed queue files persist? If the next session is in a different project directory, does `session_context.py` still pick up the queue? Simplest answer: always process any queue file regardless of current project — knowledge extraction is global. Retain queue files until processed; delete on successful processing.

View File

@ -1,5 +1,5 @@
{
"name": "os-vault",
"name": "memory",
"description": "Vault and graph memory system for Claude Code — integrates SecondBrain knowledge graph, episodic journal, and convention summaries into every session",
"version": "0.1.0"
}

View File

@ -37,10 +37,10 @@ Shared modules: `hooks/config.py`, `hooks/hook_io.py`, `hooks/session_state.py`.
## Skills
- `skills/query/SKILL.md` — When and how to query the vault graph
- `skills/write/SKILL.md` — When and how to write to the vault
- `skills/reorganize/SKILL.md` — Plan-mode vault consolidation
- `skills/memory-query.md` — When and how to query the vault graph
- `skills/memory-write.md` — When and how to write to the vault
- `skills/memory-reorganize.md` — Plan-mode vault consolidation
## Configuration
Edit `cc-os/plugins/os-vault/config.yaml` (or via the symlink `~/.claude/plugins/os-vault/config.yaml`) to change vault path, model, or thresholds.
Edit `cc-os/plugins/memory/config.yaml` (or via the symlink `~/.claude/plugins/memory/config.yaml`) to change vault path, model, or thresholds.

View File

@ -14,7 +14,7 @@ try:
except Exception: # pragma: no cover - yaml is provided via graphify
yaml = None
CONFIG_PATH = os.path.expanduser("~/.claude/plugins/os-vault/config.yaml")
CONFIG_PATH = os.path.expanduser("~/.claude/plugins/memory/config.yaml")
DEFAULT_VAULT_PATH = "~/Documents/SecondBrain"
DEFAULT_GRAPHIFY_OUTPUT_DIR = "~/Documents/SecondBrain/.graphify"

View File

@ -2,7 +2,7 @@
description: Manage per-project Graphify knowledge graphs — onboard, update, remove, and query codebase structure for the current repo
---
**Scope:** project graph at `./graphify-out/graph.json` — codebase structure and module relationships for the current repo only. For cross-project evergreen knowledge, use `/os-vault:query`.
**Scope:** project graph at `./graphify-out/graph.json` — codebase structure and module relationships for the current repo only. For cross-project evergreen knowledge, use `memory-vault`.
## Onboard
@ -71,7 +71,7 @@ Delete `graphify-out/` when the project is inactive or the graph is too stale. T
graphify query "<question>" --graph ./graphify-out/graph.json
```
For traversal mechanics (`graphify path`, `graphify explain`, `--budget`, `--dfs`), see `/os-vault:query` — query mechanics are not duplicated here.
For traversal mechanics (`graphify path`, `graphify explain`, `--budget`, `--dfs`), see `memory-vault` — query mechanics are not duplicated here.
## Graph path discovery

View File

@ -22,7 +22,7 @@ description: Consolidate, promote, or restructure vault notes — plan mode only
4. **Execute edits** — make the approved changes
5. **Force rebuild:** after any reorganization that removes, renames, or restructures notes, run:
```bash
# vault_path and ollama_model from ~/.claude/plugins/os-vault/config.yaml
# vault_path and ollama_model from ~/.claude/plugins/memory/config.yaml
graphify extract <vault-path> --backend ollama --model <configured-model> --force
```

View File

@ -2,7 +2,7 @@
description: Query the SecondBrain vault knowledge graph for cross-project evergreen knowledge — conventions, tool behavior, client facts, patterns that generalize beyond one repo
---
**Scope:** vault at `~/Documents/SecondBrain` — cross-project evergreen knowledge, not codebase structure. For codebase structure and module relationships in the current repo, use `/os-vault:onboard-project`.
**Scope:** vault at `~/Documents/SecondBrain` — cross-project evergreen knowledge, not codebase structure. For codebase structure and module relationships in the current repo, use `memory-project`.
Use this skill when you need to retrieve evergreen knowledge from the SecondBrain vault (conventions, tool/API behavior, client facts, cross-project patterns). For **episodic questions** ("what did we do last week", "when did X happen"), use memsearch instead.
@ -38,4 +38,4 @@ For questions about sessions, events, or time-ordered history, use memsearch dir
## Writing to vault
Use `/os-vault:write` when knowledge generalizes beyond the current repo. Routing heuristic: "Would this be useful outside this repo, next year?" If yes → vault via `/os-vault:write`. If no → project-graph only (no persistent write needed). See `~/.claude/CLAUDE.md` for the full routing rule.
Use `memory-write` when knowledge generalizes beyond the current repo. Routing heuristic: "Would this be useful outside this repo, next year?" If yes → vault via `memory-write`. If no → project-graph only (no persistent write needed). See `~/.claude/CLAUDE.md` for the full routing rule.

View File

@ -25,7 +25,7 @@ to prevent symlink leaks). The harness sets `HOME="$SANDBOX"` so all hook code t
Key sandbox contents created before each hook run:
- `$SANDBOX/.claude/plugins/os-vault/config.yaml` — points `vault_path` and
- `$SANDBOX/.claude/plugins/memory/config.yaml` — points `vault_path` and
`graphify_output_dir` at sandbox-local dirs
- `$SANDBOX/vault/journal/` — journal target for `session_end.py`
- `$SANDBOX/.cache/graphify/` — stamp/lock/log directory for `session_start.py`
@ -120,7 +120,7 @@ The bash hooks (`hooks/*.sh`) were removed after parity was verified — they li
history only. The only supported invocation uses the Python hooks via `tests/python-wrappers/`:
```bash
cd /home/jared/dev/cc-os/plugins/os-vault/tests
cd /home/jared/dev/cc-os/plugins/memory/tests
HOOKS_DIR="$(pwd)/python-wrappers" bash generate-fixtures.sh
```
@ -141,7 +141,7 @@ fixtures (e.g. after modifying a hook):
2. Run generate-fixtures.sh via python-wrappers with a replay dir:
```bash
HOOKS_DIR=/home/jared/dev/cc-os/plugins/os-vault/tests/python-wrappers \
HOOKS_DIR=/home/jared/dev/cc-os/plugins/memory/tests/python-wrappers \
bash tests/generate-fixtures.sh --replay-dir /tmp/replay-out
```
This writes new fixtures to `/tmp/replay-out` using the Python hooks.

View File

@ -77,8 +77,8 @@ make_sandbox() {
sb=$(mktemp -d)
sb=$(cd "$sb" && pwd -P)
mkdir -p "$sb/.claude/plugins/os-vault"
cat > "$sb/.claude/plugins/os-vault/config.yaml" <<YAML
mkdir -p "$sb/.claude/plugins/memory"
cat > "$sb/.claude/plugins/memory/config.yaml" <<YAML
vault_path: ${sb}/vault
graphify_output_dir: ${sb}/vault/graphify-out
ollama_model: qwen25-coder-7b-16k

View File

@ -58,8 +58,8 @@ trap cleanup EXIT
# ---------------------------------------------------------------------------
# 2. Populate sandbox structure
# ---------------------------------------------------------------------------
mkdir -p "$SANDBOX/.claude/plugins/os-vault"
cat > "$SANDBOX/.claude/plugins/os-vault/config.yaml" <<YAML
mkdir -p "$SANDBOX/.claude/plugins/memory"
cat > "$SANDBOX/.claude/plugins/memory/config.yaml" <<YAML
vault_path: ${SANDBOX}/vault
graphify_output_dir: ${SANDBOX}/vault/graphify-out
ollama_model: qwen25-coder-7b-16k

View File

@ -1,5 +0,0 @@
{
"name": "os-doc-hygiene",
"version": "0.1.0",
"description": "Monitor and manage stale and bloated project documentation. Deterministic SessionStart reminder + AI-assisted check/clean skills with git-safe, scoped cleanup."
}

View File

@ -1,10 +0,0 @@
# Python bytecode cache
__pycache__/
*.pyc
# In-project hygiene state (per design invariant #3)
.dochygiene/
# Vendored tiktoken vocab (~2 MB; pre-warmed on demand, never committed —
# see scripts/token_estimator.py). Runtime falls back to the heuristic when absent.
scripts/tiktoken_cache/

View File

@ -1,114 +0,0 @@
# doc-hygiene
Claude Code plugin that monitors and manages **stale** and **bloated** project
documentation. Installed globally, operates per-project. Reminds (deterministic,
zero-token) on `SessionStart`; checks and cleans on demand via skills.
> Read `PRD.md` first — it is the source of truth for scope, decisions, and
> rationale. This file is the build map.
## Core distinction (do not conflate)
- **Stale** = the doc is *wrong* (contradicted, orphaned, superseded,
provisional, completed-in-place, duplicated). Remedy: fix or remove.
- **Bloat** = the doc is *true but mostly irrelevant* (distill, split, freeze).
Remedy: change its altitude, almost never delete history.
Severity scales with **injection frequency** — a stale line in `CLAUDE.md` (auto
-injected every session) is worse than the same line in a doc nobody opens.
## Design invariants (see PRD + reversion-protection pattern)
1. The `SessionStart` hook **only reminds** — it never runs analysis or mutates
anything, and spends no AI tokens. All mutation is user-invoked.
2. Reminder **snoozes** (≤ once/day while stale) via `last_reminded`.
3. State lives **in-project** under gitignored `.dochygiene/`. No global index.
4. **Report rollover**: keep only the latest report. The tool must not become
the bloat it polices.
5. Cleanup is **git-safe**: clean/committed tree required (or auto WIP
checkpoint), each run is one reviewable commit.
6. **Deterministic-first**: scan/state/patch-apply/token-estimate are scripts
(no model). AI does only classification and prose distillation.
7. **Safety tiers**: `auto` (deterministic+reversible+objective) runs without
prompt; `confirm` (destructive/subjective/generative) escalates for approval.
8. **mtime guard**: never apply a cached edit to a file changed since the check.
9. Frozen/ignored files (`hygiene: frozen` frontmatter, `.dochygiene-ignore`,
append-only logs) are never flagged.
Changing any invariant requires updating `invariants.md` and the golden
examples, with explicit human approval.
## Build order (report schema gates everything)
1. **Machine report schema** — the contract every component consumes. Freeze
first. (per-file: category, signals, op, op-type, safety tier, optional
exact-edit, token estimate.)
2. **Deterministic core***build-spike #1 first*: a trivial `hooks/hooks.json`
emitting a `systemMessage` banner on `SessionStart`, confirmed to render
visibly in a real session (no plugin in this collection wires a CC hook yet).
Then: scanner (signals + shortlist), state store (timestamps, rollover,
snooze, atomic writes, project-root resolution), reminder hook
(`SessionStart`, `matcher: startup|resume`, injected-clock testable).
3. **`check` skill** — scanner shortlist → AI classify (Sonnet, judgment only) →
**`report_builder.py`** deterministic finalize pass → human + machine reports →
update `last_check`. The finalize pass sits **between** model classification and
the report write (invariants #6, #10): it computes `expected_sha256`, looks up
`is_destructive`/`is_reversible` from `exact_edit.kind`, derives `safety_tier`
via `validate_report.py`'s single-source `derive_safety_tier(...)`, sources
`raw_tokens` from the token estimator, and assembles the schema-valid machine
report + human-report skeleton. The model authors none of those four fields.
4. **`clean` skill + patch-applier** — consume report, mtime-guard, apply
`deterministic` ops mechanically, delegate `generative` to Sonnet, gate
`confirm`, git checkpoint + single commit, scopable by category/file →
update `last_clean`. Then **`sweep`** = check-then-clean.
- **RESOLVED (Phase 4):** `insert-frontmatter` ops have `has_anchor=False` and
carry **no** `expected_sha256`, so invariant #8's content hash cannot protect
them. The applier re-derives frontmatter freshness at apply time: re-read the
file, parse frontmatter; key present with target value → idempotent no-op; key
present with different value → skip (`frontmatter-key-conflict`); key absent →
insert. No cached hash is trusted. This resolves the deferred hole noted in
`add-check`.
5. **Bonus (v2)** — deterministic patch-apply hardening + token estimator
(local tokenizer, injection-frequency weighting, bottom-up rollup).
## Intended layout (per cc-plugins conventions)
```
doc-hygiene/
├── .claude-plugin/plugin.json # done
├── PRD.md # done — source of truth
├── CLAUDE.md # this file
├── invariants.md # TODO — declare behavioral invariants
├── skills/ # slash-command entry points: /os-doc-hygiene:<skill>
│ ├── check/SKILL.md
│ ├── clean/SKILL.md
│ ├── status/SKILL.md
│ └── sweep/SKILL.md
├── scripts/ # Python OOP — scanner, state, reminder, estimator, report_builder, applier
│ └── ...
├── hooks/hooks.json # SessionStart wiring → reminder script (systemMessage banner)
├── examples/golden/ # classifier golden examples (input tree → expected report)
└── tests/ # unit tests per deterministic seam + fixture doc trees
```
## Conventions (inherited from cc-plugins)
- **Language**: Python, OOP — small single-responsibility classes, dependency
injection, immutable transforms where possible. See
`../cc-architect/references/tool-patterns/deterministic-scripting.md`.
- **Scripts**: structured JSON output, correct exit codes, idempotent, testable
in isolation with injected clock/filesystem.
- **Model routing**: scripts = no model; classification = Sonnet (hard cases →
Opus); generative distillation = Sonnet (NOT Haiku); orchestration = Opus.
- **Reversion protection**: `invariants.md` + `examples/golden/` + change-impact
analysis + human gate for golden-example changes. See
`../cc-architect/references/tool-patterns/reversion-protection.md`.
- Read a directory's `CONTEXT.md` before its source files (progressive
disclosure); generate one per directory as it's built.
## Completion steps (do NOT do until functional)
1. Add `invariants.md` and 35 golden examples.
2. Register in `../.claude-plugin/marketplace.json` (name + source + description,
matching `plugin.json`). See `../.claude/references/plugin-registration.md`.
3. Verify installable via `/install doc-hygiene@cc-plugins`.

View File

@ -1,57 +0,0 @@
# Handoff — `add-deterministic-core` apply (paused at build-spike gate)
**Status: 15 / 25 tasks complete.** Resume with `/opsx:apply add-deterministic-core`.
Nothing below is blocked on code — it is blocked on one human visual check.
## Where we are
| Group | Tasks | State |
|---|---|---|
| 1 — build-spike (SessionStart banner) | 1.1 ✓ · 1.2 ⛔ gate · 1.3 ⛔ | **AWAITING VISUAL CONFIRMATION** |
| 2 — scanner (`doc-scanner`) | 2.12.6 ✓ | done — `scripts/scanner.py`, 56 tests pass |
| 3 — state store (`state-store`) | 3.13.8 ✓ | done — `scripts/state_store.py`, 30 tests pass |
| 4 — reminder hook (`session-reminder`) | 4.14.4 | NOT STARTED — needs Group 3 + confirmed hook format (1.3) |
| 5 — cross-seam verification | 5.15.4 | NOT STARTED — run last; 5.3 writes `scripts/CONTEXT.md`, 5.4 is `openspec validate --strict` |
## THE GATE (task 1.2) — do this first on resume
Task 1.2 requires a human to start a real Claude Code session and **visually confirm** the spike banner renders. Everything downstream (1.3 → Group 4 → Group 5) is gated on it. The enable path is ALREADY fully set up — do NOT re-derive it:
- The plugin `doc-hygiene@cc-plugins` is **already installed, enabled, and cached**. (`enabledPlugins."doc-hygiene@cc-plugins": true` in `~/.claude/settings.json`; marketplace registered under `extraKnownMarketplaces.cc-plugins` → directory `/home/jared/dev/cc-plugins`.)
- There is **no `/install` slash command** in this build (v2.1.181) — plugin management is the `claude plugin ...` CLI, already run.
- CC loads hooks from the cache snapshot `~/.claude/plugins/cache/cc-plugins/doc-hygiene/0.0.1/hooks/hooks.json`, which is **byte-identical to source**. If `hooks/hooks.json` is edited later, run `claude plugin update doc-hygiene@cc-plugins` to re-snapshot before testing.
- The `systemMessage` banner mechanism is **proven** in this build — memsearch's SessionStart banner renders the same way.
**To confirm:** start a fresh Claude Code session (any directory; plugin is user-scoped) and look for, alongside the memsearch line:
[doc-hygiene] Documentation health reminder is active. Run /os-doc-hygiene:check to scan for stale or bloated docs.
- **Renders** → check task 1.2, then do task 1.3 (record the confirmed format: plugin-root `hooks/hooks.json`, top-level `hooks` key, per-matcher nested `hooks` array, `systemMessage` field, `matcher: startup|resume`, `timeout: 5`). Then build Group 4, then Group 5.
- **Blank** → the hooks.json format or loading is wrong for this build; debug BEFORE Group 4 inherits the format. (Format currently mirrors the canonical corrected shape from commit `4d595a9`, avoiding the 3 prior hook-format bugs.)
## Group 4 build notes (when unblocked)
- 4.1: `Reminder` decision PURE over `(last_check, last_reminded, now, threshold)` per design D5 — silent when fresh; banner when stale and not snoozed; snooze keyed on **calendar day** via `last_reminded`.
- 4.2: reminder script reads state via the Group 3 `StateStore` (already built) with an injected clock; on banner emit `systemMessage` naming the slash command + write `last_reminded = now`; on silent write nothing; always exit 0; missing/unreadable state = never-checked (stale). No scan, no model (invariant #1).
- 4.3: update `hooks/hooks.json` to invoke the reminder script (reuse confirmed format from 1.3), `timeout` ≤ 5s. This REPLACES the spike's fixed banner.
- 4.4: tests with injected clock (invariants #1, #2): fresh→silent; stale+never→banner; stale+reminded-same-day→silent; stale+reminded-prior-day→banner; assert only `last_reminded` mutated, only on banner.
- Build Group 4 with **Sonnet** (real systems Python), not Haiku.
## Open decision for the user (flag, not yet resolved)
Scanner deviation (tasks 2.2/2.3): the implementer read design **D7** as placing *append-only* in the **exclusion pipeline only**, NOT as a per-path signal (an append-only file is excluded before signals run, so an `append_only_growth` signal would be self-contradictory). The dead signal was removed. The spec's signal list mentioned "append-only growth" only as an illustrative "for example." **Confirm this reading is acceptable**, or restore append-only as a signal.
## Conventions for the resuming session
- Orchestrator-subagent pattern (project CLAUDE.md): delegate via Agent tool; do NOT call Read/Write/Bash/Grep/Glob/Edit directly. Model routing: scripts=no model (author with Sonnet for real logic), classification=Sonnet, orchestration=Opus.
- Each parallel agent owns disjoint files; serialize `tasks.md` checkbox edits in the orchestrator to avoid write races.
- Do NOT push commits. Do NOT commit `map-driven-planning/docs/`.
- Changing any invariant requires `invariants.md` + golden-examples update + explicit human META-RULE approval. (Known pre-existing: `invariants.md` #2 "Why" prose still has stale `clear`/`compact` wording — behavior is correct; prose fix is META-RULE-gated and untouched.)
## Files created this session
- `hooks/hooks.json` (spike banner)
- `scripts/scanner.py`, `scripts/state_store.py`
- `tests/test_scanner_exclusions.py`, `tests/test_scanner_signals.py`, `tests/test_state_store.py`
- `tests/fixtures/scanner/...`, `tests/fixtures/state_store/...`
- `.claude-plugin/marketplace.json` (parent repo — doc-hygiene registration)
- `openspec/changes/add-deterministic-core/tasks.md` (15 boxes checked)

View File

@ -1,307 +0,0 @@
# PRD: `doc-hygiene` plugin
> Status: `ready-for-agent` · Working name: `doc-hygiene` · Target: `~/dev/cc-plugins/doc-hygiene/`
## Problem Statement
When I work intensively in a project, documentation accumulates two distinct
defects that both degrade how usefully an AI agent (and I) can read the repo:
- **Stale docs** — content that is now *wrong*: it contradicts the running
system, references files/symbols/paths that no longer exist, is pinned to an
old tool version, claims "in progress" for shipped work, or duplicates a fact
that has since drifted. Trusting it causes wrong actions. This is the
high-severity class because agents read confidently and don't sanity-check.
- **Bloated docs** — content that is still *true* but mostly irrelevant: long
debugging narratives, resolved-problem detail at full fidelity, decisions
recorded without distillation, append-only growth. It doesn't lie; it dilutes
attention and burns context-window budget, and buries the one live caveat in
archaeology.
Severity scales with **injection frequency**: a stale line in a doc nobody opens
is annoying; the same line in an auto-injected file (`CLAUDE.md`, memory index)
misleads *every* session unprompted.
Today I notice this only incidentally, have no systematic way to find it across
projects, and cleaning it up by hand wastes both my time and AI tokens. I also
don't want a tool that interrupts my flow or silently rewrites my repos.
## Solution
A Claude Code plugin, installed globally but operating per-project, that:
1. **Reminds, doesn't nag.** A deterministic `SessionStart` hook checks an
in-project timestamp and, only if the last hygiene check is older than a
threshold, injects a noticeable reminder telling me how many days it's been
and which command to run. Zero AI tokens unless I act. Snoozes so it reminds
at most once per day while stale.
2. **Checks on demand.** A `check` skill runs a **deterministic scan** that
gathers objective signals and a candidate shortlist, then an **AI
classification pass** that reads candidates, categorizes them (stale vs
bloat, by sub-type), assigns each a concrete operation tagged by
*op-type* (`deterministic` | `generative`) and *safety tier* (`auto` |
`confirm`), and estimates context-weight savings. It produces a **human
report** (what to clean and why) and a **machine report** (structured ops
for the cleaner to consume).
3. **Cleans, git-safely and scoped.** A `clean` skill consumes the machine
report. For each op it checks an **mtime guard** (file unchanged since the
check); `deterministic` ops are applied mechanically with no model;
`generative` ops are delegated to Sonnet subagents. `auto`-tier ops run
without fuss; `confirm`-tier ops escalate into an approval list. Cleanup runs
only on a clean/committed tree (or after an auto-checkpoint) and lands as a
single reviewable commit. The user can scope a run to specific categories or
files.
4. **Composes.** A `sweep` entry runs check-then-clean in one go, still
surfacing the report and gating `confirm`-tier ops.
The design leads with deterministic code (scan, signals, state, patch-apply,
token estimate) and reserves AI for the genuine judgment (classification and
prose distillation). This keeps it fast, cheap, and trustworthy.
## User Stories
1. As a developer, I want a deterministic `SessionStart` reminder when a
project's docs haven't been checked in over a threshold, so that I'm nudged
without spending any AI tokens.
2. As a developer, I want the reminder to be visually noticeable and to name the
exact command to run, so that acting on it is one step.
3. As a developer, I want the reminder to snooze (at most once/day while stale),
so that it doesn't nag me every session for a week.
4. As a developer, I want hygiene state stored per-project in a gitignored dot
directory, so that there is no global index to corrupt, race, or itself go
stale.
5. As a developer, I want to run a check manually with a slash command, so that
after a heavy context-building session I can clear what accumulated.
6. As a developer, I want the check to deterministically scan scoped files and
compute objective signals (broken references, version skew, edit-recency vs
git churn, location, append-only growth, archive-to-live ratio, frontmatter
markers), so that the AI pass starts from facts, not a blank read.
7. As a developer, I want the AI pass to classify each candidate as stale
(contradicted, orphaned, superseded, provisional, completed-in-place, duplicated) or
bloat (distill, split, freeze), so that the right remedy is chosen per file.
8. As a developer, I want each recommended operation tagged with an op-type
(`deterministic` | `generative`) and a safety tier (`auto` | `confirm`), so
that the cleaner knows what it can apply mechanically and what needs a model
or my approval.
9. As a developer, I want a human-readable report describing what should be
cleaned and *why*, grouped by category, so that I can decide at a glance.
10. As a developer, I want a machine-readable report the cleaner consumes
directly, so that the cleanup step doesn't re-derive the analysis.
11. As a developer, I want only the single most-recent report kept (rollover
deletes prior reports), so that the tool's own artifacts don't become bloat.
12. As a developer, I want the check to estimate context-weight savings per
file, summed into categories and a total (bottom-up), so that I can
prioritize.
13. As a developer, I want savings honestly framed and weighted by injection
frequency (auto-injected files counted as real per-session savings,
on-demand docs as theoretical-max), so that the numbers aren't misleading.
14. As a developer, I want the cleaner to apply `deterministic`+`auto` ops
(move-to-archive, freeze-stamp, known-target link fix, exact-dup dedupe)
with no model and no prompt, so that no-brainer cleanup just happens.
15. As a developer, I want every destructive, subjective, or generative op to be
`confirm`-tier and escalated for my approval, so that nothing surprising
happens to my repos.
16. As a developer, I want a per-op mtime guard so that if a file changed since
the check, its cached edit is skipped (and re-analysis recommended) rather
than blindly applied.
17. As a developer, I want generative cleanup (distilling narrative into live
constraints) delegated to a Sonnet subagent, so that distillation quality is
high and doesn't drop the constraint that mattered.
18. As a developer, I want cleanup to run only on a clean/committed tree, or to
auto-commit a WIP checkpoint first, so that my uncommitted work is never
lost.
19. As a developer, I want each cleanup run to land as a single reviewable
commit, so that I can inspect or revert the whole sweep trivially.
20. As a developer, I want to scope a cleanup to specific categories or files
(e.g. "only the orphaned-reference fixes"), so that I stay in control of how
much changes at once.
21. As a developer, I want a `sweep` command that runs check-then-clean, so that
when I already trust the project I can do both in one step.
22. As a developer, I want `sweep` to still surface the report and gate
`confirm`-tier ops, so that the convenience path isn't a foot-gun.
23. As a developer, I want a status command that reports last-check and
last-clean timestamps for the current project, so that I can see where it
stands without running anything.
24. As a developer, I want to protect files from being flagged via
`hygiene: frozen` frontmatter or a `.dochygiene-ignore` file, so that
deliberately-frozen records and append-only logs aren't repeatedly flagged.
25. As a developer, I want sensible scope defaults (markdown + known doc
locations; excluding build/vendor/archive/graphify-out dirs) with
per-project overrides, so that the scan targets docs and nothing irrelevant.
26. As a developer, I want the check and clean steps to update their own
timestamps (`last_check`, `last_clean`) and the reminder to track
`last_reminded`, so that the lifecycle is observable and the snooze works.
27. As a developer, I want the plugin to work in any project where it's enabled
without per-project setup beyond enablement, so that adoption is frictionless.
28. As a developer, I want classifier behavior protected by golden examples and
invariants, so that future edits to the plugin don't silently regress what
counts as stale vs bloat.
29. As a developer, I want decisions (especially `confirm`-tier approvals)
recorded, so that there's an audit trail of what was changed and why.
30. As a developer, I want the deterministic scripts to emit structured JSON and
correct exit codes, so that they're independently testable and composable.
## Implementation Decisions
**Distribution & activation**
- New plugin `doc-hygiene` in the `cc-plugins` marketplace. Installed globally,
operates per-project. No global state index — the question of "how to key
projects globally" is dissolved by per-project storage.
- Marketplace registration deferred until the plugin is functional (documented
as a build-completion step, not done now).
**State & artifacts (in-project)**
- A gitignored `.dochygiene/` directory at the project root (resolved via git
root, fallback cwd) holds:
- `state.json``last_check`, `last_clean`, `last_reminded` timestamps.
- the single most-recent report (human `.md` + machine `.json`).
- Rollover: each new check deletes the prior report before writing the new one.
- `.dochygiene/` must not be tracked. The tool does **not** silently edit the
user's `.gitignore` (that would itself be an outward mutation, against the
non-intrusive premise). Instead: on first check it detects whether the dir is
ignored and, if not, surfaces a one-line offer to add the entry — applied only
on confirmation (or noted in the report). The scanner always self-excludes
`.dochygiene/` regardless.
**Components / modules** (described abstractly; see CLAUDE.md for the build map)
- **State store** (deterministic): reads/writes `state.json`; atomic writes;
resolves project root.
- **Scanner** (deterministic): walks scoped files, computes signals, emits a
candidate shortlist with attached signals. Respects scope config + ignore
markers + frozen frontmatter + append-only detection.
- **Reminder hook** (deterministic, `SessionStart`): reads state, compares to
staleness threshold, applies once/day snooze via `last_reminded`, emits a
noticeable notice. No model.
- *Mechanism (confirmed):* plugins declare hooks in `hooks/hooks.json` at the
plugin root (not under `.claude-plugin/`). The visible, zero-token notice is
emitted via the hook's JSON `systemMessage` field (a user-facing banner) —
NOT `additionalContext` (which is silent to the user). The command names the
slash command to run. `matcher` is `startup|resume`.
- *Snooze is load-bearing, not polish:* `SessionStart` also fires on `resume`,
`clear`, and `compact`. The once/day `last_reminded` snooze is what prevents
the banner re-firing after every compaction within a single working session.
- *Constraints to respect:* keep `timeout` low (≤5s); exit 0 (never block the
session); the script must be fast and side-effect-free beyond reading state.
- **Classifier / report builder** (AI — the `check` skill): consumes the
shortlist, reads candidates, assigns category + op-type + safety tier, writes
exact edits for deterministic ops, and calls the token estimator. Emits human
+ machine reports. Updates `last_check`.
- **Token estimator** (deterministic helper): counts tokens of removed/reduced
spans with a local tokenizer approximation (no API call); weights by injection
frequency; rolls up file → category → total. Framed as "context weight
reduced," splitting injected vs on-demand.
- **Cleanup executor** (the `clean` skill + deterministic patch-applier):
consumes the machine report; mtime-guards each op; applies `deterministic` ops
mechanically; delegates `generative` ops to Sonnet subagents; runs `auto`
freely and escalates `confirm`; enforces git checkpoint + single-commit;
scopable by category/file. Updates `last_clean`.
- **Sweep** (composition): invokes check then clean, preserving the report
surface and `confirm` gate.
**Operation taxonomy**
- Op-type `deterministic`: exact, reversible edits the check pre-computes
(delete/move/stamp/known-link-fix/exact-dedupe). Applied with no model.
- Op-type `generative`: prose transformations (distill, split) requiring a model
at clean time (Sonnet). Deferred — not pre-written at check time — so a check
is cheap even when no clean follows.
- Safety tier `auto`: deterministic + reversible + objective → runs without
prompt.
- Safety tier `confirm`: destructive (any delete), subjective, or generative →
escalated for approval.
**Git safety (reversion-protection vocabulary)**
- Cleanup requires a clean/committed working tree, or auto-commits a WIP
checkpoint first.
- Each cleanup run lands as one reviewable commit (the post-state).
- `confirm`-tier approvals are logged to a decisions record.
**Model routing** (per the marketplace convention)
- Deterministic scan/state/patch-apply/token-estimate: **no model** (scripts).
- Writing those scripts: **Haiku**.
- Classification / report building: **Sonnet** (reading comprehension +
judgment). The hardest stale-vs-bloat distinctions may escalate to **Opus**.
- Generative distillation at clean time: **Sonnet** (explicitly *not* Haiku —
this is the highest-judgment task and Haiku produces lossy summaries).
- Orchestration: **Opus** (the calling Claude).
**Language**
- **Python**, OOP, small composable single-responsibility classes with
dependency injection (per the deterministic-scripting reference), chosen for
broad reach in a general-purpose plugin and for testability.
**Report schema is the linchpin**
- The machine report schema (per-file: category, signals, recommended op,
op-type, safety tier, optional exact-edit, token estimate) is the contract
every other component consumes. It is designed and frozen first.
## Testing Decisions
**What makes a good test here:** assert external behavior at the highest
deterministic seam — given an input doc tree / state file / report, the script
produces the correct structured output and exit code. Do not assert internal
class structure. The AI classification layer is pinned by golden examples, not
unit assertions.
**Seams (highest first):**
- **Scanner** — input: a fixture doc tree; output: candidate shortlist JSON.
Unit-tested against fixtures exercising each signal (broken ref, version skew,
churn-vs-edit, location, append-only, frozen frontmatter, ignore file).
- **State store** — timestamp read/write, rollover (only latest report kept),
snooze logic. Unit-tested with an injected clock and temp dirs.
- **Reminder hook** — given a `state.json` + injected clock + threshold, asserts
notice / no-notice / snoozed. Unit-tested via the injected clock (no real
time, no real session).
- **Token estimator** — counts on known spans; injection-frequency weighting;
bottom-up rollup. Unit-tested on fixed inputs.
- **Patch-applier + mtime guard** — applies deterministic ops on fixtures;
skips when fixture mtime is newer than the check timestamp. Unit-tested.
- **Classifier (AI)** — golden examples (`examples/golden/`): input doc tree →
expected report categorization, per the reversion-protection pattern. Run the
check against each; flag mismatches for human review.
**Prior art:** the `commit` and `cc-architect` plugins pair deterministic
`scripts/` with skills and structured output; this plugin follows that split.
The reversion-protection reference (`cc-architect/.../reversion-protection.md`)
defines the golden-example + invariants approach used for the classifier.
## Out of Scope
- A global cross-project dashboard / index (dissolved by per-project storage).
- Exact Claude token counting via API at check time (local approximation only;
exactness isn't worth the latency/cost for a motivational estimate).
- Non-markdown content analysis beyond known doc locations (e.g. linting source
code comments). Scope defaults to docs.
- Auto-running cleanup from the `SessionEnd`/`SessionStart` hook — the hook only
ever reminds; all mutation is user-invoked.
- Pushing commits or any outbound/network action — cleanup is local commits
only; the user pushes.
- Editing files outside the resolved project root.
- v2 polish: richer per-category savings visualizations, configurable op
taxonomies beyond the built-in set.
## Further Notes
- **Build order** (the report schema gates everything): (1) machine report
schema; (2) deterministic scanner + state store + reminder hook; (3) `check`
skill (scanner → AI classify → report); (4) `clean` skill (consume report,
git-safe, scoped) + `sweep`; (5) bonus: deterministic patch-apply + token
estimator. Patch-apply and token-estimate are explicitly v2 — they optimize a
report format that must exist and be proven first.
- **Why deterministic-first matters here specifically:** the check does the
expensive cognition once (reading + deciding); cleanup executes. Pushing
attribute-detection into the scanner and exact edits into the report means
most cleanup needs no model at all, and a check is cheap even when no cleanup
follows.
- **False-positive trust risk:** without the frozen/ignore mechanism and
append-only detection, the tool would re-flag deliberately-frozen records
every week and lose the user's trust. This is treated as a correctness
requirement, not a nicety.
- **The tool must not become bloat:** report rollover (keep latest only) and the
gitignored dot dir are deliberate guards against the plugin polluting the very
repos it cleans.
- **Linchpin mechanism verified:** the SessionStart `systemMessage` banner path
was confirmed against the Claude Code hook docs before this PRD was finalized
(no other plugin in the collection wires a CC hook, so it had no precedent).
Build-spike #1 is still to stand up a trivial `hooks/hooks.json` emitting a
`systemMessage` and confirm it renders visibly in a real session before
building the deterministic core on top of it.

View File

@ -1,50 +0,0 @@
# examples/golden/
Two **distinct** families of golden fixtures live here. Do not conflate them.
| Family | Location | Maps | Consumed by | Purpose |
|--------|----------|------|-------------|---------|
| **Schema-shape fixtures** | `valid_report.json`, `invalid_report.json` | (a hand-authored report) → accept / reject | `scripts/validate_report.py` | Pin the frozen machine-report schema contract. |
| **Classifier goldens** | `classifier/<n>-<name>/` | input doc-tree (`input/`) → expected classification (`expected.json`) | hermetic `tests/test_classifier_golden.py` + a live model-regression harness | Layer-2 reversion protection for the *classifier* — pin which subtype + op-kind + tier a given doc-tree should yield. |
Both families are reversion-protection artifacts; changing either is **human-gated**
per the META-RULE in `invariants.md`.
## Schema-shape fixtures
Hand-authored machine-report JSON files used to verify the schema-validator
script (`scripts/validate_report.py`).
## Contents
| File | Purpose | Expected validator result |
|------|---------|--------------------------|
| `valid_report.json` | Well-formed machine report exercising a realistic spread of entry types: deterministic `delete-range` (confirm), deterministic `move-to-archive` (auto), deterministic `insert-frontmatter` (auto, no anchor — exercises the no-anchor path), and a generative distillation entry (confirm). | Exit 0 (accepted) |
| `invalid_report.json` | Malformed report with a safety-critical invariant #10 violation: a `delete-range` entry (destructive, irreversible) that records `safety_tier: "auto"` when the derivation yields `"confirm"`. All other fields are otherwise valid so the validator reports only this specific violation. | Exit 1 (rejected), one violation on `entries[0].safety_tier` |
## Invariants exercised
- **Invariant #10** (`safety_tier` derivation): `invalid_report.json` is the
canonical fixture for this invariant — it is structurally sound except for the
safety-tier mismatch.
- **Invariant #11** (`op_type`↔`exact_edit` biconditional): both fixtures
include deterministic entries with `exact_edit` and a generative entry without
one, exercising both directions of the biconditional.
## Classifier goldens (`classifier/`)
Input doc-tree → expected classification. See `classifier/README.md` for the full
case table, the two harnesses (hermetic unit + live model-regression), and the
regeneration recipe. Five cases, each pinning one taxonomy edge to a **distinct**
`exact_edit.kind` (plus one generative case) so the suite covers the kind table +
tier derivation:
| Dir | class / subtype | kind | tier | scanner signal |
|-----|-----------------|------|------|----------------|
| `1-orphaned` | stale / orphaned | `delete-range` | confirm | `broken_reference` |
| `2-superseded` | stale / superseded | `move-to-archive` | auto | `stale_name_location` |
| `3-completed-in-place` | stale / completed-in-place | `insert-frontmatter` | auto | `archive_to_live_ratio` |
| `4-duplicated` | stale / duplicated | `dedupe` | auto | `stale_name_location` (no dedicated dup signal — see case notes) |
| `5-distill` | bloat / distill | (none, generative) | confirm | `archive_to_live_ratio` |
Enforced hermetically by `tests/test_classifier_golden.py` (no model, no network).

View File

@ -1,58 +0,0 @@
{
"schema_version": "1.0",
"tool_version": "0.0.1",
"generated_at": "2026-06-24T17:45:00.691072+00:00",
"scan": {
"project_root": "<input>",
"scope_globs": [
"**/*.md"
],
"excluded_dirs": [
"build",
"vendor",
"archive",
"graphify-out",
".dochygiene",
"fixtures",
"examples/golden"
],
"files_scanned": 1
},
"shortlist": [
"docs/orphan.md"
],
"entries": [
{
"path": "docs/orphan.md",
"category": {
"class": "stale",
"subtype": "orphaned"
},
"signals": [
{
"name": "broken_reference",
"detail": "links to './gone.md' which does not exist"
}
],
"op": "Delete the orphaned reference block (lines 5-10) that points at a removed helper and describes a workflow that no longer exists.",
"op_type": "deterministic",
"is_destructive": true,
"is_reversible": false,
"safety_tier": "confirm",
"exact_edit": {
"kind": "delete-range",
"anchor": {
"start_line": 5,
"end_line": 10
},
"expected_sha256": "5da05aa629ca39925a68d4b1f3f1a86686fdbbdab90b29d56294c5be3b371400",
"generated_at": "2026-06-24T17:45:00.690977+00:00"
},
"token_estimate": {
"raw_tokens": 79,
"injection_frequency": null,
"weighted_tokens": null
}
}
]
}

View File

@ -1,10 +0,0 @@
# Orphaned Migration Notes
These notes describe a migration step that pointed at a helper script.
The procedure referenced [the legacy migration helper](./gone.md) for the
manual cut-over. That helper file was removed long ago, so this section now
points at nothing and documents a step that can no longer be performed.
Nothing else in the repository links here, and the workflow it describes no
longer exists.

View File

@ -1,11 +0,0 @@
# 1-orphaned
Pins the **stale / orphaned** taxonomy edge → `delete-range` exact-edit kind →
derived `confirm` tier (delete-range is destructive + irreversible).
**Scanner signal exercised:** `broken_reference`. `docs/orphan.md` links to
`./gone.md`, which is absent from `input/`, so the scanner emits exactly one
`broken_reference` signal on `docs/orphan.md`. The doc is otherwise unreferenced
and describes a dead workflow — the coherent justification for *orphaned* rather
than a link-repair (`replace-text`): there is no live target to repair to, so the
block is deleted.

View File

@ -1,16 +0,0 @@
[
{
"path": "docs/orphan.md",
"category": { "class": "stale", "subtype": "orphaned" },
"signals": [
{ "name": "broken_reference", "detail": "links to './gone.md' which does not exist" }
],
"op": "Delete the orphaned reference block (lines 5-10) that points at a removed helper and describes a workflow that no longer exists.",
"op_type": "deterministic",
"exact_edit": {
"kind": "delete-range",
"anchor": { "start_line": 5, "end_line": 10 }
},
"gloss": "The only inbound/outbound link is dead and nothing references this doc; the block documents a step that can no longer run."
}
]

View File

@ -1,59 +0,0 @@
{
"schema_version": "1.0",
"tool_version": "0.0.1",
"generated_at": "2026-06-24T17:45:00.792786+00:00",
"scan": {
"project_root": "<input>",
"scope_globs": [
"**/*.md"
],
"excluded_dirs": [
"build",
"vendor",
"archive",
"graphify-out",
".dochygiene",
"fixtures",
"examples/golden"
],
"files_scanned": 1
},
"shortlist": [
"docs/old-plan.md"
],
"entries": [
{
"path": "docs/old-plan.md",
"category": {
"class": "stale",
"subtype": "superseded"
},
"signals": [
{
"name": "stale_name_location",
"detail": "filename 'old-plan.md' contains 'old' \u2014 may indicate superseded content"
}
],
"op": "Move the superseded plan to archive/docs/old-plan.md to preserve history without keeping it live.",
"op_type": "deterministic",
"is_destructive": false,
"is_reversible": true,
"safety_tier": "auto",
"exact_edit": {
"kind": "move-to-archive",
"anchor": {
"start_line": 1,
"end_line": 13
},
"dest_path": "archive/docs/old-plan.md",
"expected_sha256": "a1f247505584b851f2303ef6fe04d657180f9a3bb8301d588a02c649b5c68792",
"generated_at": "2026-06-24T17:45:00.792702+00:00"
},
"token_estimate": {
"raw_tokens": 103,
"injection_frequency": null,
"weighted_tokens": null
}
}
]
}

View File

@ -1,13 +0,0 @@
# Old Rollout Plan
This is the original rollout plan for the v1 launch. It has been fully
superseded by the current plan and is retained only for historical reference.
## Phases
1. Stand up the staging cluster.
2. Cut traffic over during the maintenance window.
3. Decommission the old fleet.
The content here is still accurate as a record but no longer drives any
work — the live plan replaced it entirely.

View File

@ -1,10 +0,0 @@
# 2-superseded
Pins the **stale / superseded** taxonomy edge → `move-to-archive` exact-edit kind
→ derived `auto` tier (move-to-archive is non-destructive + reversible).
**Scanner signal exercised:** `stale_name_location`. The filename
`docs/old-plan.md` contains `old`, which trips the location/name signal. The body
is still accurate as a historical record but fully replaced by the live plan — so
the op preserves history by relocating the whole file to `archive/docs/old-plan.md`
rather than deleting it (anchor spans the entire file, lines 1-13).

Some files were not shown because too many files have changed in this diff Show More