Migrate ADR log to docs/adr/, one file per decision

The monolithic docs/memory-system/03-architecture-decisions.md had
grown to 27 entries and was becoming hard to navigate and diff. Run
/os-adr:migrate to split it into docs/adr/ (one file per ADR plus a
generated README index and migration report), promoting the cc-os
retrofit ahead of pilot projects per the ADR-020 amendment recorded
in the monolith.

The monolith is retained as the historical source (tagged with
migration_source frontmatter) but new decisions now go in docs/adr/.
Update CLAUDE.md's layout and conventions sections and
docs/implementation-status.md's timeline/rollout notes to point at
the new location.

Closes out the /os-status:fix run; refs ADR-020.
This commit is contained in:
jared 2026-07-12 18:10:31 -04:00
parent cf14bb33f9
commit 537d6215b1
32 changed files with 1335 additions and 10 deletions

View File

@ -24,9 +24,14 @@ hoc.
Numbered files within a directory are not a required reading order — read the one whose Numbered files within a directory are not a required reading order — read the one whose
topic you need. topic you need.
- **`docs/adr/`** — the ADR system (one file per decision + generated `README.md` index),
managed via `/os-adr:*` skills. Migrated 2026-07-12 from the monolithic
`docs/memory-system/03-architecture-decisions.md`, which is retained as the historical
source (provenance: `migration_source` frontmatter) but is no longer where new decisions go.
- **`docs/memory-system/`** — the design of the memory system itself. `02-system-design.md` - **`docs/memory-system/`** — the design of the memory system itself. `02-system-design.md`
is the architecture, `03-architecture-decisions.md` is the ADR log, `04-build-plan.md` the is the architecture, `03-architecture-decisions.md` is the historical ADR log (see
build outline with step status, `06-graphify-evaluation.md` justifies the Graphify pivot. `docs/adr/` above), `04-build-plan.md` the build outline with step status,
`06-graphify-evaluation.md` justifies the Graphify pivot.
- **`docs/implementation-status.md`** — the status timeline, full per-component detail for - **`docs/implementation-status.md`** — the status timeline, full per-component detail for
every shipped plugin (os-vault, os-orchestration, os-status, os-doc-hygiene, os-adr), every shipped plugin (os-vault, os-orchestration, os-status, os-doc-hygiene, os-adr),
eval-harness records, and plugin rename/cache-refresh procedures. eval-harness records, and plugin rename/cache-refresh procedures.
@ -79,8 +84,9 @@ before modifying any of these:
- **os-doc-hygiene** (`plugins/os-doc-hygiene/`) — stale/bloated-doc monitoring; - **os-doc-hygiene** (`plugins/os-doc-hygiene/`) — stale/bloated-doc monitoring;
check/clean/status/sweep skills. check/clean/status/sweep skills.
- **os-adr** (`plugins/os-adr/`) — ADR system: Ruby `lib/adr/` + CLIs, create/init/migrate/ - **os-adr** (`plugins/os-adr/`) — ADR system: Ruby `lib/adr/` + CLIs, create/init/migrate/
find skills; Eval A/B/C harnesses. Rollout order locked: pilot projects, then the cc-os find skills; Eval A/B/C harnesses. cc-os retrofit done 2026-07-12 (promoted ahead of
retrofit of `03-architecture-decisions.md`, then wider. pilots — ADR-020 amendment / `docs/adr/0020`); remaining rollout: pilot projects one at a
time, then wider.
- **Graphify** v0.8.31 (`~/.local/bin/graphify`; PyPI package is `graphifyy`, double-y) — - **Graphify** v0.8.31 (`~/.local/bin/graphify`; PyPI package is `graphifyy`, double-y) —
vault graph at `~/Documents/SecondBrain/graphify-out/`, per-project graphs at vault graph at `~/Documents/SecondBrain/graphify-out/`, per-project graphs at
`<project-root>/graphify-out/` (gitignored); both disposable/rebuildable. `<project-root>/graphify-out/` (gitignored); both disposable/rebuildable.
@ -117,8 +123,13 @@ OpenSpec artifacts comes from `docs/` and this file.
authority; anchored to Graphify v0.8.30). authority; anchored to Graphify v0.8.30).
- **Dates are absolute** (e.g. `2026-06-03`); design docs carry a `_Last updated:_` / status - **Dates are absolute** (e.g. `2026-06-03`); design docs carry a `_Last updated:_` / status
line — keep these current when editing. line — keep these current when editing.
- **Decisions live in ADRs.** Don't silently reverse a locked decision; add or amend an ADR - **Decisions live in ADRs** in `docs/adr/` (index: `docs/adr/README.md`). Don't silently
in `docs/memory-system/03-architecture-decisions.md` with the reasoning. reverse a locked decision; record the superseding decision via `/os-adr:create` with the
reasoning. **When** a task involves an architecture-level choice, or changes/replaces an
approach this codebase already uses (a library, a convention, a plugin structure) →
**first** run `/os-adr:find` to check whether a recorded decision covers it, and **when**
you make such a choice → record it with `/os-adr:create`. A task that reverses an Accepted
ADR is not complete until the superseding ADR exists.
- **Keep records current:** when a build step completes, (a) mark it done in - **Keep records current:** when a build step completes, (a) mark it done in
`docs/memory-system/04-build-plan.md`, (b) record it in `docs/memory-system/04-build-plan.md`, (b) record it in
`docs/implementation-status.md` (timeline entry + component pointer), and (c) update the `docs/implementation-status.md` (timeline entry + component pointer), and (c) update the

View File

@ -0,0 +1,36 @@
---
id: "0001"
date: 2026-06-03
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-001 — Two memory types, kept as separate systems"
---
# 0001 — Two memory types, kept as separate systems
## Context
Earlier attempts to make one tool serve both "what happened" and "how do we do
X" felt forced (e.g. trying to make memsearch filter knowledge by tags).
## Decision
Model **episodic** memory and **semantic/knowledge** memory as two separate
systems with different tools.
- **Rationale**: They have different lifecycles (episodic accretes and decays; knowledge is
deliberately maintained), different write paths (auto-captured vs curated with guardrails),
and different query patterns ("when did we…" vs "how do we…"). Separation dissolves the
earlier integration tension entirely.
## Consequences
Episodic and semantic/knowledge memory are built and maintained as two independent systems with different tools, lifecycles, and write paths, rather than one unified store. This avoids the awkward filtering that forcing a single tool (e.g. memsearch with tags) would require, at the cost of maintaining two separate systems instead of one.
## Alternatives rejected
One unified store (memsearch-for-everything, or OpenBrain's single
`thoughts` table) — conflates the two and forces awkward filtering.

View File

@ -0,0 +1,35 @@
---
id: "0002"
date: 2026-06-03
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-002 — memsearch for the episodic layer"
---
# 0002 — memsearch for the episodic layer
## Context
Need timeline/"what happened" memory (Goal 3) that's NL-queryable and lazy.
## Decision
Adopt **memsearch** (Zilliz) off-the-shelf for episodic memory.
- **Rationale**: It already implements the OpenClaw daily-notes + "dreaming" pattern and the
markdown-as-truth / disposable-shadow-index philosophy we'd otherwise build. Embedded
**Milvus Lite** (single file), hybrid BM25+vector+RRF search, local ONNX embeddings (no API
key/cost), a FileWatcher that handles deletions — **no Docker, no server**. Two-line install.
## Consequences
memsearch is adopted off-the-shelf for episodic memory instead of building a custom daily-notes/dreaming system, giving embedded Milvus Lite storage, hybrid BM25+vector+RRF search, and local ONNX embeddings with no Docker or API key required. This means the episodic layer depends on memsearch's design and behavior rather than a bespoke implementation.
## Alternatives rejected
claude-mem (MCP-based — Claude must actively call search; opaque
blobs vs readable markdown; overkill features). Hand-building daily notes + dreaming
ourselves (reinventing a solved tool).

View File

@ -0,0 +1,39 @@
---
id: "0003"
date: 2026-06-03
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-003 — Flat vault with namespaced tags, not folders"
---
# 0003 — Flat vault with namespaced tags, not folders
## Context
Connelly/Huryn organize by folders (`tools/`, `domain/`). User wants a flat
Obsidian vault with tags as virtual indexes, and cross-cutting filters (client × tool ×
convention).
## Decision
One **flat markdown vault**; organize via **namespaced, nested tags**
(`tool/`, `client/`, `domain/`, `convention/`, `scope/`). Slashes are valid Obsidian nested
tags, so `#tool` matches all children.
- **Rationale**: A note can carry several namespaces at once (`tool/semrush` +
`client/sesame3g` + `convention/react-ts`) — folders can't express that. Enables "filter by
client+tool to narrow the index." Enumerable virtual indexes ("what clients/tools exist").
## Consequences
The vault is organized as a single flat markdown store using namespaced, nested tags (tool/, client/, domain/, convention/, scope/) instead of folders, enabling a note to belong to multiple cross-cutting categories at once and supporting enumerable virtual indexes. The trade-off accepted is that while tags give humans/Obsidian free filtering, the AI gets nothing for free from tags alone — they must be materialized into a queryable index (addressed in ADR-004/010).
## Alternatives rejected
Folder hierarchy (single-axis; can't do cross-cutting filters).
Pure-prefix path filtering via memsearch `source_prefix` (would force directories back in).
- **Trade-off accepted**: Tags give the *human/Obsidian* free filtering, but the *AI* gets
nothing for free from tags — we must materialize them into a queryable index (see ADR-004).

View File

@ -0,0 +1,44 @@
---
id: "0004"
date: 2026-06-03
status: Superseded
supersedes:
superseded-by: "0010"
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-004 — SQLite + Sequel (Ruby) tag index as the knowledge-layer cache"
---
# 0004 — SQLite + Sequel (Ruby) tag index as the knowledge-layer cache
## Context
The AI can't use Obsidian tags directly; tag filtering needs a machine-queryable
index. A previous `~/Documents/SecondBrain/` tag database was lost track of.
## Decision
A small **Ruby program using the Sequel ORM over SQLite**, exposed as a **CLI**.
Schema: `files(path, mtime, summary, scope)`, `tags(name)`, `files_tags` join
(`many_to_many`). The summary is a **column on `files`** (an attribute), not a join.
- **Rationale**: Normalized `tags` table makes enumerating the vocabulary a first-class cheap
query (the "virtual index" goal). The `summary` column is what turns the index from a
*finder* into a *router* — the AI sees enough to pick a file without opening it (progressive
disclosure, low tokens). Ruby + Sequel + CLI keeps the contract clean and the DB swappable;
the AI never touches SQLite directly.
- **Failure-mode guard (the lost-SecondBrain lesson)**: **markdown is always authoritative; the
SQLite file is a disposable cache** that is never synced and can be rebuilt from frontmatter
anytime (`index update --rebuild`).
## Consequences
A Ruby/Sequel/SQLite CLI tag index was built as the machine-queryable cache over the vault, with markdown treated as always-authoritative and the SQLite file as a disposable, rebuildable cache — never synced. This bespoke index and its CLI were later dropped and superseded by ADR-010's Graphify graph, though the summary + namespaced-tag frontmatter it introduced was retained as note metadata.
## Alternatives rejected
Plain-markdown generated `INDEX.md` (must regenerate; grep-at-scale
is token-heavy). Frontmatter grep on demand (scales badly). Milvus/Postgres for knowledge
(overkill; QMD/memsearch prove SQLite is enough — see ADR-006/008).
- **Query output**: returns **path + summary + matched tags** (option C) — tags are cheap and
show *why* a result matched, useful for cross-client queries.

View File

@ -0,0 +1,34 @@
---
id: "0005"
date: 2026-06-03
status: Superseded
supersedes:
superseded-by: "0010"
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-005 — Structured-first; semantic search over the vault deferred"
---
# 0005 — Structured-first; semantic search over the vault deferred
## Context
Tag filtering ("client/sesame3g + tool/semrush") may miss notes whose wording
doesn't match the query ("how do we use semrush" vs a note titled "search analytics
integration").
## Decision
Ship the knowledge layer **structured-only** (tags + summaries). **Defer**
meaning-based search over the vault until it demonstrably bites.
- **Rationale**: Structured tagging is the lightweight/fast thing the user wants, and the
summary+tag design is built to make it work. Follow the video's "only level up when it bites."
## Consequences
The knowledge layer shipped structured-only (tags + summaries), deliberately deferring meaning-based/semantic search over the vault until it demonstrably became necessary. This premise was later superseded by ADR-010, since Graphify provides structured and connection-based recall together from day one rather than requiring a separate semantic layer to be bolted on later.
## Alternatives rejected
None stated in source.

View File

@ -0,0 +1,37 @@
---
id: "0006"
date: 2026-06-03
status: Superseded
supersedes:
superseded-by: "0010"
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-006 — QMD as the (deferred) semantic-over-knowledge layer"
---
# 0006 — QMD as the (deferred) semantic-over-knowledge layer
## Context
When ADR-005's structured-only proves insufficient, we want a set-and-forget
semantic layer over the vault, local and Docker-free.
## Decision
Earmark **QMD** (github.com/tobi/qmd) for that role; do **not** install yet.
- **Rationale**: Local markdown search using **SQLite + FTS5/BM25 + local vector embeddings
(EmbeddingGemma-300M GGUF) + LLM rerank**; CLI + optional **MCP server**; no Docker, no API
keys. Validates that SQLite + a local vector model suffices (no Milvus/Postgres for
knowledge). Complements the tag index (QMD filters by path/collection context, not
first-class frontmatter tags), so it adds semantic recall without replacing structured
filtering.
## Consequences
QMD was earmarked (but not installed) as the future semantic-over-knowledge layer to complement the tag index once structured-only search proved insufficient, validating that SQLite plus a local vector model would suffice without Milvus/Postgres. This earmarking was later superseded by ADR-010, since Graphify's knowledge graph fills the semantic-recall role via traversal/explain without needing a separate vector system.
## Alternatives rejected
Pointing memsearch at the vault (mixes episodic and knowledge
corpora; its filtering is path-prefix not tags). A bespoke embedding index (reinvents QMD).

View File

@ -0,0 +1,37 @@
---
id: "0007"
date: 2026-06-03
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-007 — Lazy freshness: write-hook + session-start reconcile, no daemon/cron"
---
# 0007 — Lazy freshness: write-hook + session-start reconcile, no daemon/cron
## Context
The cache must reflect new/edited/deleted/renamed notes without becoming a
resource hog or going stale on renames.
## Decision
**Option A (lazy).** A `PostToolUse` hook updates the index on **AI** writes
(single-file, prunes on delete). **Manual** edits are caught by a **session-start reconcile**
(`index update --since` + prune of vanished paths). **No daemon, no cron.**
- **Rationale**: The AI is the primary writer, so write-time hooks give event-driven freshness
with no polling. The user rarely edits the vault by hand, so a session-start reconcile is
enough; a continuous `inotify` daemon (the `listen` gem) would add an always-on process to
manage/sync for negligible benefit. Matches the user's "lazy sync is fine" stance.
## Consequences
Index freshness is handled lazily: a PostToolUse hook updates the index on AI writes, and a session-start reconcile catches manual edits and deletions, with no daemon or cron process running continuously. This avoids the overhead of an always-on inotify/listen process at the cost of freshness only being guaranteed at write-time or session-start rather than continuously.
## Alternatives rejected
`inotify`/`listen` daemon (live freshness, but always-on process
to manage — unnecessary). Cron reconcile ("seems silly" per user; session-start covers it).

View File

@ -0,0 +1,36 @@
---
id: "0008"
date: 2026-06-03
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-008 — Markdown-as-truth; sync the vault, not the indexes"
---
# 0008 — Markdown-as-truth; sync the vault, not the indexes
## Context
Must be accessible on a VPS / multiple machines but run local-fast (Goal 4).
## Decision
Sync the **markdown vault** to the VPS via **git or Syncthing** (choice deferred
to build time). **Graphs/indexes (Milvus Lite, Graphify `graphify-out/`) are rebuilt per
machine and never synced.**
- **Rationale**: Markdown is plain text — git/Syncthing sync it trivially; lazy (hourly or
continuous-async) is enough. Indexes are disposable caches; syncing binary DBs invites
conflicts for no gain. Local reads stay fast; ownership and portability stay with the user.
## Consequences
Only the markdown vault is synced across machines (via git or Syncthing); derived indexes like Milvus Lite and Graphify's graphify-out/ are rebuilt per machine and never synced, keeping local reads fast and avoiding binary-DB sync conflicts. This preserves user ownership and portability of the source data while requiring each machine to rebuild its own indexes.
## Alternatives rejected
**OpenBrain / Mem0** hosted DBs — always-remote, adds per-query
latency and monthly cost, conflicts with local-fast; ownership weaker (Mem0 especially).
Only worth it for real-time cross-tool memory, which the user called overkill.

View File

@ -0,0 +1,35 @@
---
id: "0009"
date: 2026-06-03
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-009 — Package as a global Claude Code plugin with skills"
---
# 0009 — Package as a global Claude Code plugin with skills
## Context
Every project, on every machine, should know how to use the vault — write
conventions, query patterns, the hooks, and the CLI — without per-project setup.
## Decision
Ship hooks + scripts + CRUD know-how as a **global Claude Code plugin with
skills**, installed at the user level.
- **Rationale**: Skills carry the "when to write / what conventions / how & when to query"
guidance to the model; the plugin registers the session-start / session-end / PostToolUse
hooks and wires up Graphify (extraction/update/query + MCP server). Global install =
consistent behavior everywhere; single source of truth for the conventions themselves.
## Consequences
The vault write/query conventions, hooks, and CLI knowledge are packaged as a global, user-level Claude Code plugin with skills, so every project on every machine gets consistent behavior without per-project setup. This centralizes the conventions in a single source of truth but was accepted as still to be built per the build plan.
## Alternatives rejected
None stated in source.

View File

@ -0,0 +1,52 @@
---
id: "0010"
date: 2026-06-03
status: Accepted
supersedes: "0004, 0005, 0006"
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-010 — Graphify knowledge graph as the knowledge layer (supersedes ADR-004/005/006)"
---
# 0010 — Graphify knowledge graph as the knowledge layer (supersedes ADR-004/005/006)
## Context
ADR-004 specced a hand-built Ruby/Sequel/SQLite tag index (+ CLI) as the
machine-queryable layer over the vault, with ADR-005/006 deferring meaning-based recall to a
future QMD vector layer. Before building any of it, we evaluated **Graphify** (`graphify`,
PyPI `graphifyy`) — a tool that turns a folder into a queryable knowledge graph (local
tree-sitter AST for code, local-SLM entity/relationship extraction for docs). See
`06-graphify-evaluation.md`.
## Decision
Use **Graphify as the knowledge-layer engine** over the vault, with a **local
Ollama** backend for doc extraction and free AST for per-project code graphs. **Drop** the
Ruby/SQLite tag-index CLI (ADR-004) and the earmarked QMD layer (ADR-006); **retain** the
`summary` + namespaced-tag frontmatter from ADR-003/004 as note metadata.
- **Rationale**: One off-the-shelf tool delivers both what the tag index was for (structured
retrieval) and what QMD was deferred for (connection/meaning-based recall via graph
traversal + `explain`) — without writing or maintaining a bespoke index, and without a vector
store. Code graphs come free. Keeps the markdown-as-truth, no-Docker, no-API-key, local-first
properties (extraction runs against local Ollama). Net scope reduction: the entire Ruby build
(old critical-path Step 2) and the QMD layer are removed.
- **What's retained / changed**: `summary` stays the human-written router hint Graphify does not
generate; namespaced tags stay useful for Obsidian filtering and as node attributes. How
tightly metadata should feed graph queries is a **build-time refinement**, not settled here.
- **Trade-off accepted**: Graphify's `--update` doesn't prune deleted nodes (stale-node drift) —
mitigated by a periodic `--force` rebuild on the session-start staleness check (ADR-007's lazy
model still applies). Graphify also moves fast (flags are version-dependent; anchored to
v0.8.30) and its headline token-savings numbers are corpus-dependent — benchmark our own.
## Consequences
Graphify becomes the knowledge-layer engine over the vault (local Ollama backend for docs, free tree-sitter AST for code), replacing the planned Ruby/SQLite tag index (ADR-004) and the earmarked QMD semantic layer (ADR-006), while retaining the summary + namespaced-tag frontmatter as note metadata. This reduces the scope of what must be built and maintained, but introduces reliance on Graphify's behavior, including a known trade-off that `--update` doesn't prune deleted nodes (mitigated by periodic `--force` rebuilds) and that its flags/behavior are version-dependent.
## Alternatives rejected
Building the Ruby/SQLite index as originally planned (more code to
own; no semantic recall); adding QMD as a second system on top (two stores where one graph
suffices).

View File

@ -0,0 +1,65 @@
---
id: "0011"
date: 2026-06-04
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-011 — Faceted tag taxonomy: six independent namespaces (refines ADR-003)"
---
# 0011 — Faceted tag taxonomy: six independent namespaces (refines ADR-003)
## Context
ADR-003 introduced five namespaces (`tool/`, `client/`, `domain/`,
`convention/`, `scope/`). During vault-reuse assessment (ADR-012) it became clear that (1)
the existing SecondBrain vault uses a de-facto first-tag convention for note kind
(research/plan/log/adr/howto) that should be made explicit and machine-queryable, and (2)
for a freelancer working many projects per client, project identity deserves a first-class
namespace rather than being implied by `client/` or `domain/`.
## Decision
Knowledge-vault notes are classified by **six independent, flat tag facets**
that sit side-by-side, never nested into one another:
- `type/` — note kind: `research`, `howto`, `adr`, `hub`, `plan`, `log`, `clip`, etc.
- `client/` — which client
- `project/` — which project (first-class; a freelancer's projects are the primary unit of
work)
- `domain/` — knowledge domain / topic area
- `tool/` — tool-specific knowledge
- `convention/` — conventions
- …plus `scope/global` or `scope/project` (retained from ADR-003)
Hierarchy and relationships are expressed via **hub notes** (`type/hub`), **wikilinks**, and
**Graphify knowledge-graph edges** — NOT via nested tag paths.
By convention `type/` is listed **first** in frontmatter, preserving the SecondBrain vault's
existing type-first ordering habit and making the note kind immediately visible.
- **Rationale**: The vault is flat — hierarchy is not expressed through folder paths or tag
nesting. The user's reality is many-to-many (many projects per client, knowledge domains
spanning clients), which a single-parent tree models badly and forces false hierarchy. A
project hub note links out to both its `client/` and relevant `domain/` tags rather than
being buried under either. Per-type `_templates` will be provided for **core types only**
(research, howto, adr, hub); the long tail stays freeform until a pattern earns a template.
Consistent per-type structure also improves Graphify's local-SLM extraction reliability.
## Consequences
Vault notes are classified using six independent, flat, side-by-side tag facets (type/, client/, project/, domain/, tool/, convention/, plus scope/), with type/ listed first in frontmatter, and hierarchy/relationships expressed only via hub notes, wikilinks, and Graphify graph edges rather than nested tag paths. This refines ADR-003's namespace list, adds project/ as a first-class facet for freelancer work, and improves Graphify's extraction reliability via consistent per-type structure, with templates provided only for core types initially.
## Alternatives rejected
Hierarchical nesting in the style of John Conneely's
`domain/{product}/{project}.md` folder structure (from the youngleaders.tech article "How I
finally sorted my Claude Code memory" — **secondary/interview-grade source, not verified
against primary implementation**). Rejected because: (1) the vault is flat — hierarchy is
not expressed through folder paths; (2) the user's many-to-many reality maps badly onto a
single-parent folder tree and forces false hierarchy; (3) nesting one facet through another
(e.g. `domain/client/project`) creates Law-of-Demeter-style traversal coupling. Conneely's
structure was the inspiration but diverges here on hierarchy-vs-facets. Faceted parallel tags
are the flat-vault analogue of what the Graphify graph already does with edges, so they
compose naturally with the chosen knowledge layer.

View File

@ -0,0 +1,53 @@
---
id: "0012"
date: 2026-06-04
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-012 — Reuse the existing SecondBrain vault as the knowledge vault"
---
# 0012 — Reuse the existing SecondBrain vault as the knowledge vault
## Context
The design called for a flat markdown vault as the semantic knowledge layer
(ADR-003/008/010). The question was whether to stand up a new `~/brain` vault from scratch
or adopt the existing `~/Documents/SecondBrain` vault.
## Decision
**Adopt `~/Documents/SecondBrain`** as the knowledge vault rather than
creating a new vault.
- **Rationale**: Assessment found the SecondBrain vault is already flat (all notes at root,
only a `_templates/` exception — exactly what the design permits), already articulates the
correct "durable knowledge, not working memory" role in its `CLAUDE.md` and
`vault-conventions.md`, and contains ~20 real notes. It also includes two patterns that
**improve on the current design** and should be adopted:
1. `vault-conventions.md`'s "act without being asked" section specifying *when* the AI
should proactively query the vault — a behavioral spec the cc-os docs lacked.
2. Project-config hub notes with a tag-inference table (auto-tag by path pattern) that
operationalizes *how* to tag a note from a given project.
- **Adaptations required (migration cost)**:
- Add `summary:` frontmatter to existing notes.
- Migrate flat unnamespaced tags to the six-facet namespaced form (per ADR-011).
- Add `scope/global` or `scope/project` to each note.
- Initialize git in the vault (no `.git` exists yet — required by ADR-008's sync strategy).
- Replace the vault's `~/.claude/scripts/vault_search.rb` reference (script does not exist)
with `graphify query` (ADR-010).
These are mechanical schema migrations, not structural rework.
## Consequences
The existing ~/Documents/SecondBrain vault is adopted as the knowledge vault instead of creating a new one, since it was already flat and already articulated the correct durable-knowledge role, and it contributes two design improvements (proactive-query behavior spec and a project-config tag-inference table) to be adopted. This requires mechanical migration work: adding summary frontmatter, migrating tags to the six-facet form, adding scope tags, initializing git, and replacing a nonexistent script reference with graphify query.
## Alternatives rejected
Starting fresh with a new `~/brain` vault. Rejected because the
hardest design decision — flat structure, durable-knowledge-only role, governance philosophy
— is already made and practiced in SecondBrain. The improved behavioral patterns
(proactive-query spec, tag-inference table) and the existing notes are worth preserving; the
remaining work is mechanical migration.

View File

@ -0,0 +1,56 @@
---
id: "0013"
date: 2026-06-04
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-013 — Build-first / migrate-incrementally (build-order inversion)"
---
# 0013 — Build-first / migrate-incrementally (build-order inversion)
## Context
The build runbook (`05-implementation-process.md`) originally front-loaded bulk
vault migration as Step 1 — migrating all ~20 existing SecondBrain notes and all projects to
the ADR-011 six-facet taxonomy before the system existed to validate them. This committed to
a schema and workflow (the tag taxonomy from ADR-011, the vault-reuse choice from ADR-012,
and Graphify extraction behavior) before any end-to-end path had been exercised. The risk:
locking in an approach that fails at scale, with no feedback loop until the entire vault has
been touched.
## Decision
**Invert the build order.** The full system is built and validated against a
small **510 note fixture set** first. Bulk vault migration is deferred to the final stage.
The first real-data validation uses **one small project that contains both code AND
documents**, exercising both the local-SLM doc-extraction path and the tree-sitter code path
in the same run. After that single project validates end-to-end, remaining projects are
onboarded **one at a time** with an observe-and-adjust step between each.
- **Rationale**: Validates the ADR-011 taxonomy and ADR-012 vault conventions against the real
Graphify extraction pipeline before the entire vault is committed. The first mixed code+docs
project surfaces both extraction paths (SLM for docs, tree-sitter for code) early, when
corrections are cheap. Per-project rollout keeps the blast radius of any schema or workflow
correction small; each project is an opportunity to observe and adjust rather than discover
problems across 20 notes at once. This is consistent with the "markdown-as-truth, indexes are
disposable" principle (ADR-008): the vault notes are durable, but the extraction schema should
be validated before it shapes all of them.
## Consequences
The build order was inverted so the full system is validated against a small 5-10 note fixture set first, with bulk vault migration deferred to the final stage; the first real-data validation uses one project mixing code and docs, then remaining projects are onboarded one at a time with an observe-and-adjust step between each. This reduces the blast radius of any taxonomy or workflow correction compared to migrating the whole vault before validating the pipeline.
## Alternatives rejected
- **Keep migration-first (status quo)**: Front-loads all ~20 notes and all projects before
any end-to-end validation exists. Commits to ADR-011's taxonomy and ADR-012's migration
steps against the full vault without a feedback loop — exactly the gap this decision closes.
- **Big-bang migrate everything after build**: Build against fixtures, then migrate all notes
and all projects in one batch at the end. Avoids the pre-build commitment problem but still
risks a single large irreversible migration with no observe-and-adjust loop between units.
Per-project rollout with intermediate checkpoints is strictly safer.
- **Cross-references**: ADR-011 (six-facet tag taxonomy — the schema being validated);
ADR-012 (SecondBrain vault reuse — the migration steps this order defers).

View File

@ -0,0 +1,76 @@
---
id: "0014"
date: 2026-06-05
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-014 — Graph connectivity comes from authored structure; migration scaffolding is a first-class prerequisite"
---
# 0014 — Graph connectivity comes from authored structure; migration scaffolding is a first-class prerequisite
## Context
ADR-011 specified hub notes + wikilinks + Graphify graph edges as the mechanism
for expressing hierarchy and cross-note relationships, with ADR-013 deferring bulk vault
migration to the final stage. Before build began, a discriminating empirical test compared a
cached-replay graph (per-fixture isolated extractions) against a clean single-pass deep
extraction (`graphify extract ~/Documents/SecondBrain --backend ollama --model
qwen25-coder-7b-16k --max-concurrency 1 --token-budget 8000 --mode deep --exclude
.obsidian`) against the real `~/Documents/SecondBrain` vault under Graphify 0.8.31 +
qwen2.5-coder:7b. See `07-graph-connectivity-findings.md` for the full data and methodology.
[primary/measured — 2026-06-05 session]
## Decision
**The connective spine of the knowledge graph must be author-provided.** Hub
notes and wikilinks are not optional scaffolding to add "someday" — they are the mechanism
by which Graphify connects thematically related notes, and they must be authored **as part of
the migration step**, not deferred to bulk import. Migration scaffolding (hub notes +
wikilinks for key concepts) is treated as a **first-class build deliverable** in the
migrate-incrementally phase of ADR-013.
- **Rationale**: The empirical test found that Graphify is a **structure extractor, not a topic
clusterer**. Even at `--mode deep --token-budget 8000`, no emergent shared-topic hub nodes
appeared (no "Pest Control" node, no "Niche Prospecting" node). All cross-note edges observed
came from explicit references, wikilinks, or document-level semantic similarity — not from
shared thematic identity. A practical test query ("how do we do niche prospecting outreach for
pest control?") returned 3 starting notes and traversal could not reach the email templates /
ACV data / business-model notes (separate communities, no connecting edges). This confirms
that useful retrieval is gated on migration scaffolding, not on Graphify's extraction power.
The clean single-pass run also showed the cached graph was partially a build artifact (cross-
note edges rose from 41% to 78% in a single-pass run), but the structural finding — no
emergent hub nodes — held in both runs.
- **Relationship to ADR-011**: Validates the hub-notes + wikilinks half of ADR-011 empirically.
The facet-tag half is **not yet validated**: no edge was observed to arise from shared
frontmatter facet tags alone. Whether `client/X` or `tool/Y` tags create graph connectivity
is an **open question** — see "Deferred" below. Do not assume facet tags contribute to graph
traversal retrieval until tested.
- **Relationship to ADR-013**: Refines the migrate-incrementally stage. "Migration" must be
defined to include hub note authoring and wikilink addition for key concepts, not just
frontmatter schema migration (adding `summary:` and namespaced facet tags). The build plan
(`04-build-plan.md`) should be updated to name this deliverable explicitly.
## Consequences
Empirical testing showed Graphify is a structure extractor, not a topic clusterer — no emergent shared-topic hub nodes appeared even in deep-mode extraction — so hub notes and wikilinks must be author-provided and are now treated as a first-class migration deliverable rather than optional scaffolding added later. This refines ADR-013's migrate-incrementally stage to explicitly include hub-note authoring and wikilinking, while leaving open whether shared frontmatter facet tags alone contribute to graph connectivity.
## Alternatives rejected
Relying on the SLM to auto-cluster topics and synthesize hub
entities — **empirically does not happen** at 7B model size with `--mode deep`. The design
already intended human-authored hub notes for this; the test confirms that intent was correct
and the fallback assumption ("maybe the LLM will do it") is false.
- **Deferred**:
1. **Facet-tag-to-graph-edge question**: Do shared frontmatter facet tags (`client/`,
`tool/`, `domain/`, etc.) cause Graphify to create edges between notes, or does graph
connectivity come only from explicit wikilinks/references and semantic similarity? This was
NOT tested. Resolve before designing graph-traversal retrieval skills.
2. **Larger extraction model**: Whether a substantially larger SLM (14B, 30B) would
synthesize emergent topic-hub nodes is untested. Secondary — the design does not depend on
it — but worth one test run before the build ships.
3. **`reasoning_effort:"none"` patch**: The clean run required a local patch to `graphify/
llm.py`. Track the upstream Graphify issue tracker for an official fix; treat the
installed version as pinned until resolved.

View File

@ -0,0 +1,94 @@
---
id: "0015"
date: 2026-06-09
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-015 — memsearch episodic memory version-controlled in a dedicated private repo, auto-synced via cc-os SessionEnd hook"
---
# 0015 — memsearch episodic memory version-controlled in a dedicated private repo, auto-synced via cc-os SessionEnd hook
## Context
memsearch's memory store at `~/.memsearch` accumulates daily session-summary
files (`memory/YYYY-MM-DD.md`) that are the only irreplaceable data in the episodic layer —
the Milvus Lite index (`milvus.db`) and the bge-m3 ONNX embeddings model are derived/
disposable and can be rebuilt at any time via `memsearch index`. With Step 4 (episodic layer)
now live, preserving episodic memory across machines and protecting against local disk loss
required a sync strategy. The question was: what venue, what scope, and who owns the sync?
memsearch's own stated design philosophy is "markdown files are the canonical data store; the
vector database is a derived index" and notes that markdown is "git-friendly" and the index
rebuildable from markdown — making git a natural fit for the markdown layer.
## Decision
`~/.memsearch` is a **dedicated git repo** on branch `main` with remote `origin`
pointing to a **private self-hosted Forgejo repo**
(`ssh://git@forgejo.swansoncloud.com:2222/jared/memsearch.git`;
web: `https://forgejo.swansoncloud.com/jared/memsearch`). A whitelist `.gitignore` tracks
**only** `memory/*.md` (the daily session files) and `.gitignore` itself. Excluded as
derived/disposable: `milvus.db` (Milvus Lite index — rebuildable any time via
`memsearch index`), `config.toml`, and the bge-m3 ONNX embeddings model (lives in
`~/.cache/huggingface`, not in the store). Auto-commit and push are wired into the **cc-os
memory plugin's own `session-end.sh`** hook, not the marketplace plugin. The appended block
guards on `~/.memsearch/.git` existing, runs `git add -A` (whitelist makes it safe), commits
only when something is staged (message: `memsearch: session memory <date>`), and pushes with
`timeout 30 ... || true`. The entire block is wrapped in a subshell with a trailing `|| true`
so it can **never fail session shutdown**.
- **Rationale**:
- **Dedicated repo (not folded into the vault or a project repo):** ADR-001 established
episodic and semantic/knowledge as separate systems by design, and `~/.memsearch` is a
global, cross-project store with no natural home in any single project repo. Committing
episodic session logs into the Obsidian vault repo would conflate the two systems and
violate the separation of concerns that ADR-001 is built on.
- **Whitelist — commit markdown, exclude rebuildable index/model/config:** consistent with
memsearch's own design philosophy ("markdown is canonical; index is derived") and with the
cc-os principle from ADR-008 ("sync the vault, not the indexes"). The 544 MB bge-m3 ONNX
model is not even in the store; the Milvus Lite DB rebuilds in one command. Committing them
would bloat the repo for zero durability gain.
- **Sync lives in cc-os hook, not the marketplace plugin:** the marketplace plugin's hook
scripts (`stop.sh`, `session-start.sh`, `user-prompt-submit.sh`, `session-end.sh`) perform
no git operations — only a read-only `git rev-parse --show-toplevel` for scoping. Adding
sync to a marketplace plugin that can be clobbered by an upstream update is fragile; owning
it in the cc-os `session-end.sh` keeps the sync logic under our control and version-tracked
in this repo.
- **Fail-safe contract:** the `|| true` wrap and `timeout 30` on push ensure that a network
outage, an unreachable Forgejo instance, or any git error cannot prevent a session from
closing cleanly. The SessionEnd hook's harness timeout is ~10 s, so push is effectively
capped; commits land locally first, and any unpushed commit is carried forward by the next
session's push (daily files are append-only, so no conflict risk).
- **Commingling — resolved 2026-06-09**: `~/.memsearch` aggregates session summaries across
**all clients** into one global store. This is an **explicit design choice**, not a tolerated
risk: the user deliberately accepts a single commingled global store across all clients.
Private self-hosted Forgejo (user-owned infrastructure) is the chosen sync venue; client-
agreement compliance is the user's knowing responsibility. The concern was previously recorded
as user-owned and unresolved; that is now closed: single global store is the intended design.
This also aligns with a forward direction of minimizing dedicated per-client working
directories — since memsearch captures memory globally regardless of cwd, a single
`clients/` area for non-project client work becomes viable (direction stated; not yet designed).
## Consequences
~/.memsearch becomes its own git repo synced to a private self-hosted Forgejo remote, tracking only the daily markdown session files (via a whitelist .gitignore) while excluding the derived/disposable Milvus index, config, and embeddings model; sync is wired into cc-os's own SessionEnd hook rather than the marketplace plugin, wrapped in a fail-safe subshell so it can never block session shutdown. The design explicitly accepts a single global store commingling all clients' episodic memory as an intentional choice, with client-agreement compliance left as the user's responsibility.
## Alternatives rejected
- **Fold into the Obsidian vault repo:** violates ADR-001's episodic/semantic separation.
Episodic logs have different lifecycle (accrete and decay), different write patterns
(auto-captured every session), and would clutter a vault intended for curated durable
knowledge.
- **Auto-commit in the marketplace plugin:** the marketplace plugin can be overwritten on
update, losing the sync logic silently. Out-of-band ownership in cc-os is safer.
- **Commit the Milvus Lite index or the embeddings model:** both are large binaries, derived
from the markdown source, and rebuildable. Committing them wastes space and provides no
additional durability. The markdown files are the canonical source; the index follows from
them.
- **Syncthing or rsync instead of git:** git provides both version history and conflict-free
daily-append semantics; Syncthing is continuous-async (suitable for the vault where changes
are sparse); git's push-on-session-end cadence matches how memsearch produces data (one
daily file per day, append-only). Git was already chosen for ADR-008's vault sync rationale;
applying the same mechanism to the episodic store is consistent.

View File

@ -0,0 +1,111 @@
---
id: "0016"
date: 2026-06-12
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: low
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-016 — Memory plugin sourced from cc-os git repo; bash→Python deep-module port; memsearch-sync split; symlink cutover"
---
# 0016 — Memory plugin sourced from cc-os git repo; bash→Python deep-module port; memsearch-sync split; symlink cutover
## 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
(`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
abstraction boundary. Additionally, the memsearch auto-commit+push block (ADR-015 behavior)
was embedded in `session_end.sh` alongside the vault journal logic. The plugin was untracked
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 (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:
- `config.py`: `load_config() -> Config` frozen dataclass — handles YAML parse, path
expansion, defaults, missing-file case. Replaces 3 separate inline YAML parsers.
- `hook_io.py`: `read_input() -> HookInput` dataclass (session_id, cwd, file_path, reason)
— replaces 4 separate inline stdin parsers and eliminates the jq-vs-python3 inconsistency.
- `session_state.py`: `record_touch(session_id, path)` / `read_touches(session_id) ->
list[str]` — encapsulates the `/tmp/claude-vault-touched-$SESSION_ID` temp-file contract.
The temp file still exists by necessity (separate process invocations); the win is an
explicit, named, testable contract.
- Thin entry-point scripts: `hooks/session_start.py`, `hooks/session_context.py`,
`hooks/post_tool_use_write.py`, `hooks/session_end.py`, `hooks/memsearch_sync.py`.
- Every `main()` is wrapped in `try/except`: uncaught exceptions log to stderr and `exit 0`
(fail-open invariant — a hook crash cannot block a session).
- **Decision (3) — memsearch-sync split**: The memsearch auto-commit+push block from ADR-015
was extracted out of `session_end.py` (vault journal) into its own dedicated SessionEnd hook
entry `memsearch_sync.py`, registered as a separate hook in `~/.claude/settings.json` with a
30-second timeout. This is a **relocation of ADR-015 behavior, not a reversal** — the
auto-commit+push logic, whitelist `.gitignore`, fail-safe `|| true` contract, and Forgejo
remote are byte-for-byte preserved. The benefit is separation of concerns: vault journal
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
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
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/`.
- **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.
- **Deep-module over OO hook-class hierarchy**: four hooks that run as separate processes
with no shared runtime have nothing to gain from a class hierarchy — inheritance is just
complexity. Shared modules with named functions and typed dataclasses give all the benefits
(single parse path, named contracts) with no class machinery. The three modules provide a
deep public surface (one function call = full parse + validation) behind a thin API, which
is the Ousterhout deep-module criterion.
- **memsearch-sync split**: the original monolithic `session_end.sh` mixed two distinct
concerns (vault journal vs external git push). Split entry points mean each has its own
timeout budget, its own failure domain, and its own log line.
- **Symlink + settings.json rewrite**: the symlink is the minimum viable mechanism consistent
with how Claude Code actually resolves plugins. Attempting to retool `known_marketplaces.json`
for per-plugin path overrides would have required reverse-engineering an undocumented format;
direct hook path rewriting in settings.json is explicit, transparent, and reversible.
## Decision
_not stated in source_
## Consequences
The os-vault plugin moved from an untracked in-place directory to git-tracked source in cc-os (with a symlink at the old location), its four bash hooks were ported to a deep-module Python architecture (shared config/hook_io/session_state modules plus thin entry scripts, each wrapped in try/except to fail open), and the memsearch auto-commit+push logic was split out of the vault journal hook into its own dedicated SessionEnd entry. Ongoing contracts include never replacing the plugin symlink with a plain directory and keeping settings.json's absolute hook paths current when filenames change; a fresh-session cutover test passed 2026-06-12.
## Alternatives rejected
- **OO hook-class hierarchy**: a base `Hook` class with subclasses per hook type. Rejected
because hooks run in separate processes — there is no shared runtime state to inherit.
Shared logic belongs in functions, not class trees.
- **Keep bash, just move into git**: bash lacks typed dataclasses, structured exceptions, and
unit-testable modules. The jq-vs-python3 inconsistency (pre-existing bug) would be carried
forward unchanged, and the temp-file contract would remain implicit.
- **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
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.
- The fail-open invariant (`exit 0` on any unhandled exception) is a hard contract: never
wrap a hook's `main()` in code that propagates exceptions to the harness.
- A fresh-session cutover test was run 2026-06-12 and passed: all five hooks fired from the
cc-os Python sources.
- **Cross-references**: ADR-009 (global plugin design); ADR-015 (memsearch sync — relocated,
not reversed).

View File

@ -0,0 +1,30 @@
---
id: "0017"
date: 2026-06-17
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-017 — Project graph onboarding assesses the repo and writes `.graphifyignore` before extracting"
---
# 0017 — Project graph onboarding assesses the repo and writes `.graphifyignore` before extracting
## 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.
## 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.
## Consequences
Project onboarding now surveys the repo and writes a per-project .graphifyignore before running graphify extract, since graphify does not honor .gitignore and previously walked huge directories like node_modules, routing every non-code file through the costly Ollama LLM pass and causing a run to be killed after an hour with no output. Borderline directories require human confirmation rather than being auto-excluded, and onboarding now also uses a 16k-context model build to reduce chunk counts.
## 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.

View File

@ -0,0 +1,58 @@
---
id: "0018"
date: 2026-07-03
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-018 — Plugin renames must update the marketplace manifest, not just the directory and settings.json"
---
# 0018 — Plugin renames must update the marketplace manifest, not just the directory and settings.json
## 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`.
## Consequences
Renaming a local plugin is now a documented three-file operation (directory/symlink, marketplace manifest entry, settings.json enabledPlugins) followed by marketplace update, install of the new record, and uninstall of the stale one, verified via `claude plugin details`. This was necessitated because the memory→os-vault rename left skills unavailable despite hooks still firing, since hooks are wired by absolute path and gave a false signal of full functionality while the marketplace manifest (the actual source of plugin identity) still pointed at the old name.
## 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.

View File

@ -0,0 +1,32 @@
---
id: "0019"
date: 2026-07-03
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-019 — Global os-orchestration plugin supersedes per-project orchestration text"
---
# 0019 — Global os-orchestration plugin supersedes per-project orchestration text
## 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.
## Consequences
cc-os adopts the os-orchestration plugin's permissive delegation rule (execute small single-file ops directly, delegate larger/cross-file work) as the sole global default, removing cc-os's own stricter per-project override that previously contradicted it. The plugin was migrated into cc-os git tracking following the os-vault pattern, giving one authoritative, version-controlled orchestration rule applied consistently across all projects.
## 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).

View File

@ -0,0 +1,33 @@
---
id: "0020"
date: 2026-07-03
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-020 — os-adr plugin built; implementation-time resolutions of the design's open questions"
---
# 0020 — os-adr plugin built; implementation-time resolutions of the design's open questions
## Context
The `os-adr` plugin (OpenSpec change `add-os-adr-plugin`, built from `docs/adr-system/04-plugin-requirements.md` + `05-plugin-prd.md`) was implemented. Its design deliberately left two questions open — the migration pilot's low-confidence flag-rate threshold, and whether ADR frontmatter needs a Graphify tag/edge convention for retrieval layer 3 — and the pilot forced several heuristic refinements within the design's declared mechanical/LLM boundary.
## Decision
(1) **Pilot gate threshold: 25%** low-confidence flags, recorded in `Adr::MigrationReport::FLAG_RATE_THRESHOLD` and printed in every migration report. Final pilot rates (sandboxed copies of the three surveyed projects): viking-warrior-training-log 0%, delta-refinery 0%, llf-schema 8.3% — gate passed. (2) **No Graphify tag/edge convention for ADRs**: retrieval layer 3 expands the *query* file paths one hop through `graphify-out/graph.json` node `source_file` attributes and matches the expanded set against `affected-paths` frontmatter; ADRs never need to exist as graph nodes. Verified against the real llf-schema graph. (3) **Heuristic-boundary refinements from the pilot**: heading-synonym fallback mappings (Why/Rationale/Problem/Background→Context, Impact/Implications/Trade-offs→Consequences) are mechanical copies but cap the file at `migration_confidence: medium`; status synonyms SETTLED/Confirmed→Accepted, OPEN→Proposed map mechanically; bold-label paragraphs (`**Context.** …`) and bulleted labels (`- **Status:** …`) parse as sections; multi-decision files *without* mechanically splittable unit headings classify `unrecognized` (report-only) rather than converting as one blob.
- **Rationale**: The threshold was set empirically at pilot time per the PRD ("do not ship on an unmeasured guess"); 25% sits well above the observed worst case (8.3%) while still failing a genuinely broken heuristic run (pre-tightening iterations hit 100% on delta-refinery and llf-schema). Query-path graph expansion keeps ADRs plain markdown with zero indexing obligations — retrieval reuses the existing project graph instead of imposing a new convention on it (and sidesteps the open ADR-014 facet-edge question entirely). Capping synonym-mapped files at medium keeps the mechanical-vs-judgment boundary honest: the copy is mechanical, the mapping is a judgment call, so it stays flagged.
## Consequences
The os-adr plugin was built and its two open design questions were resolved at implementation time: a 25% low-confidence migration flag-rate gate was set (validated against pilot data with a worst observed rate of 8.3%), and no Graphify tag/edge convention was created for ADRs since retrieval instead expands query file paths one hop through the existing project graph. Several heuristic refinements (heading/status synonym mappings, bold-label parsing) were capped at medium confidence to keep judgment calls flagged, and multi-decision files without splittable headings are classified unrecognized rather than auto-converted; the cc-os retrofit was later promoted ahead of pilot-project migrations.
## Alternatives rejected
- **Indexing ADRs as Graphify nodes with a tag/edge convention**: adds an extraction pass and a freshness obligation for no retrieval gain at current corpus sizes; revisit only if one-hop query expansion proves too shallow.
- **Treating heading synonyms as high-confidence mechanical fills**: hides an interpretive mapping from the flag mechanism.
- **Auto-splitting multi-Status files that lack unit headings**: the split points would themselves be guesses; `unrecognized` + manual handling is the non-destructive answer.
- **Cross-references**: `docs/adr-system/04-plugin-requirements.md` (locked requirements), `05-plugin-prd.md` (phases), `06-eval-scenarios.md` (held-out eval sketches — the eval itself stays deferred), ADR-013 (build-first/migrate-incrementally precedent), ADR-018 (three-place plugin registration, followed during install), ADR-019 (plugin conventions).

View File

@ -0,0 +1,33 @@
---
id: "0021"
date: 2026-07-03
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-021 — Model-tier skill-execution eval for os-adr (Eval A), run in-session via subagents"
---
# 0021 — Model-tier skill-execution eval for os-adr (Eval A), run in-session via subagents
## Context
The os-adr build validated every model-touching surface (skill-following, migrate fills, find judgment) only at the session model used to build it (Fable). Confidence that the skills execute correctly on the *weakest* tier (Haiku) would imply confidence on all stronger tiers. The locked eval methodology (`04-plugin-requirements.md`) covers only the deferred held-out unprompted-behavior eval (requirements 45, "Eval B") and says nothing about model tiers. Separately, headless `claude -p` runs consume more of the user's subscription credit than in-session work.
## Decision
(1) A second, non-held-out eval — **Eval A: prompted skill-execution across model tiers** — lives at `plugins/os-adr/eval/`: two fixture projects (generated with the plugin's own CLIs), six scenarios (S1 create, S2 create+supersede, S3 find/conflict, S4 find/distractor, S5 init, S6 migrate+fills), a deterministic Ruby checker (`bin/check`, structural invariants only — never prose quality), sandbox + headless-runner scripts, and a runner-prompt template. (2) **Primary run mode is in-session**: a driver session spawns Agent-tool subagents with `model:` pinned to the tier under test; each subagent reads the SKILL.md file directly (uniform across tiers; dispatch is deterministic plumbing). Headless `claude -p` is the fidelity fallback only. (3) The **`autoresearch` skill (Classic mode)** wraps the grid as its metric to iterate SKILL.md *wording only* — checker, fixtures, scenarios, and runner prompt are frozen during a loop, as the guard against metric-gaming. (4) **Eval B gains a model axis** when it eventually runs: scenario × model tier, pass rate per tier as the autoresearch metric.
- **Rationale**: The deterministic core (CLIs, hook, index) is model-independent and already tested; what varies by tier is instruction-following, so the cheap, high-signal eval is exactly that surface with machine-checkable pass criteria (the plugin's invariants double as scoring rules). In-session subagents preserve the user's credits and parallelize; the fidelity gap (no SessionStart context, no slash dispatch) is irrelevant for *explicitly invoked* skills. Eval A prompts are deliberately not held-out — only Eval B's are — so they can be iterated on freely without contaminating the deferred methodology.
## Consequences
A new non-held-out Eval A harness was built to test prompted skill-execution across model tiers (including the weakest, Haiku) using six scenarios and a deterministic structural checker, run primarily via in-session Agent-tool subagents rather than headless `claude -p` to preserve subscription credit and enable parallelism; headless runs remain a fidelity fallback. The autoresearch skill wraps this grid to iterate SKILL.md wording only, with checker/fixtures/scenarios frozen as a guard against metric-gaming; the grid itself had not yet been run against haiku/sonnet as of this ADR.
## Alternatives rejected
- **Headless-only harness**: higher subscription cost per run, no parallel driver, no benefit for prompted-execution scoring; kept as fallback for full-fidelity checks.
- **Folding tier-testing into Eval B**: conflates two questions (can the model *execute* a skill it was told to run vs. does it *recognize* when to run one) and would burn held-out scenarios on mechanical failures.
- **LLM-judged scoring**: reintroduces the model-capability variable into the scorer; structural invariants are sufficient and reproducible.
- **Cross-references**: `plugins/os-adr/eval/README.md` (procedure + autoresearch invocation), `docs/adr-system/06-eval-scenarios.md` (Eval B sketches, now noting the model axis), ADR-020 (pilot gate the S6 fixture mirrors).

View File

@ -0,0 +1,33 @@
---
id: "0022"
date: 2026-07-06
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-022 — os-status plugin: aggregated in-process SessionStart status checks"
---
# 0022 — os-status plugin: aggregated in-process SessionStart status checks
## Context
Two real incidents showed that cc-os plugins have per-project and per-environment preconditions nothing verifies: (1) `CLAUDE_CODE_SUBAGENT_MODEL=haiku` in `~/.claude/settings.json` silently downgraded all 23 verified subagent spawns found by the WS1 orchestration audit — and the E1 canary proved the downgrade is unobservable to the model at spawn time, so protection must be deterministic; (2) projects routinely lack artifacts plugins assume (ADR system, vault hub note), with each plugin inventing its own SessionStart reminder in isolation (os-adr, os-doc-hygiene) or having none. The WS3 plan (`docs/plans/ws3-status-convention-plugin.md`) proposed a per-plugin status-check convention plus a glue plugin; two perspective reviews (simplifier, implementer, 2026-07-06) pressure-tested where check code should live.
## Decision
New global plugin **`os-status`** (`plugins/os-status/`), one SessionStart hook running all checks **in-process**: plain Python functions in `hooks/checks.py` with the uniform signature `check(ctx) -> CheckResult(status, message)` and a single registry list. Three result states: `ok` (silent), `note` (near-zero-token line, never snoozed/suppressed — exists so os-adr's Eval-B-tuned usage note survives conversion verbatim), `warn` (aggregated into at most one banner; once-per-day snooze + permanent suppress in a gitignored per-project `.cc-os/` dir — state only, never code). Initial checks: `subagent-model-env-override` (env + settings.json `env` block; runs outside git projects too), `adr-system-present` (verbatim port of os-adr's hook, honoring legacy `.os-adr/suppress`), `vault-hub-note-present` (config-first slug in `.cc-os/config`, else `type/hub` + `project/<name>` facet-tag scan of the vault). os-adr's own SessionStart hook was removed atomically in the same change, with the cutover verified against the refreshed plugin cache.
## Consequences
A new os-status plugin runs all per-project precondition checks in-process from a single SessionStart hook using a uniform check(ctx) function signature and a shared registry, with three result states (ok, note, warn) and per-project snooze/suppress state stored only as data, never code. This consolidates checks that were previously invented ad hoc per plugin (or missing entirely), catches issues like the silent CLAUDE_CODE_SUBAGENT_MODEL downgrade deterministically, and required removing os-adr's own SessionStart hook in the same change.
## Alternatives rejected
- **Option A — per-project code copies** (`.cc-os/status.d/*.py`): copies drift; the 2026-07-04 stale-cache incident is exactly this failure class; adds a per-project install step.
- **Option B as sketched — per-plugin scripts + subprocess/JSON enumeration**: overbuilt for three trivially cheap probes with one author in one repo (spawn/parse/timeout/error-normalization protocol for three registrants); and enumeration from plugin caches would silently run stale code — os-vault is wired by absolute path to git source and its cache copy is already stale (implementer finding). The uniform function signature is kept as the seam so extraction to per-plugin checks later is mechanical.
- **Blanket silent-on-ok**: would regress os-adr's present-state usage note; hence the third `note` state.
- **Deferred**: `context` injection field (no consumer yet), consolidation of `.os-adr/`/`.dochygiene/` state dirs, conversion of os-doc-hygiene's reminder, Phase 2 cache-freshness "collection manager" duties, subprocess enumeration.
- **Cross-references**: OpenSpec change `add-os-status-plugin` (proposal/design/specs/tasks), `plugins/os-status/invariants.md`, WS1 findings (`docs/orchestration-audit/2026-07-06-findings.md`), ADR-018 (marketplace-manifest procedure followed for install).

View File

@ -0,0 +1,31 @@
---
id: "0023"
date: 2026-07-10
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-023 — Backlog process management ships as a new `os-backlog` plugin (not an os-vault extension)"
---
# 0023 — Backlog process management ships as a new `os-backlog` plugin (not an os-vault extension)
## Context
The Planka backlog pilot (vault note `vault-backlog-pilot-plan`) reached its plugin phase with one gated decision open: build the AI's process-management surface (skills for capture/list/tick/board ops, deterministic scripts over the `planka-api` gem, card-triage/board-audit named agents, hooks) as a new plugin or as an extension of os-vault. The decision also surfaced a broader framing question: what cc-os *is* relative to `~/dev/cc-plugins`.
## Decision
(1) New global plugin **`os-backlog`** (`plugins/os-backlog/`). os-vault is not extended. Naming per `cc-os-plugin-skill-naming-convention.md`: verb-first skills invoked as `/os-backlog:<verb>`; no `name:` frontmatter; no `commands/` dispatchers. (2) **Ecosystem role made explicit**: cc-os is the user's *personal operating layer* for Claude Code — the family of always-on, globally installed, mutually aware `os-*` plugins running on every machine; `~/dev/cc-plugins` is the shelf of optional, as-needed plugins. Cross-plugin awareness and cooperation (including notifications, ADR-024) is an explicit long-term goal of the cc-os family, not an accident.
- **Rationale**: Separation of concerns — os-vault is the memory/knowledge domain (what do I know), os-backlog is the workflow/process domain (what state is work in): different backing stores (SecondBrain vault vs. Planka Postgres via REST), different failure modes (local files vs. network service), different maturity (os-vault is eval-hardened and stable; backlog skills will churn while the process settles). Every comparably sized domain in the family already got its own plugin (os-adr, os-status, os-doc-hygiene). Hook hygiene: a broken/unreachable Planka must not degrade vault sync hooks.
## Consequences
Backlog process management (capture/list/tick/board skills over Planka) ships as a new, separate os-backlog plugin rather than extending os-vault, keeping the memory/knowledge domain and the workflow/process domain on different backing stores with different failure modes and maturity levels. This also makes explicit that cc-os is meant to be a family of mutually aware, cooperating always-on plugins, with cross-domain needs met by skills invoking each other rather than by merging plugins.
## Alternatives rejected
**Extending os-vault** — the only benefit is one fewer plugin to maintain, a cost already amortized by `bin/refresh-plugins` and the marketplace procedure (ADR-018); it would couple a churning surface to a frozen, eval-tuned one and blur the domain boundary the tag taxonomy itself encodes. Future skills needing both domains compose by invoking each other's skills, not by merging plugins.
- **Cross-references**: vault notes `vault-backlog-pilot-plan` (pilot plan + execution status), `cc-os-plugin-skill-naming-convention`; ADR-018 (install procedure); ADR-024 (notification direction the plugin will consume).

View File

@ -0,0 +1,31 @@
---
id: "0024"
date: 2026-07-10
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-024 — Apprise replaces Pushover as the notification transport (supersedes the Pushover-direct plan)"
---
# 0024 — Apprise replaces Pushover as the notification transport (supersedes the Pushover-direct plan)
## Context
The `planka-api` gem's tick/digest delivery was specified as Pushover-direct (`PUSHOVER_API_TOKEN`/`PUSHOVER_USER_KEY`, falls back to stdout when unset — see the gem's `docs/issues/README.md` and the P0 board card "Provision Pushover digest credentials"). Separately, Planka itself ships Apprise as a notification integration, and the user's real need is multi-service: Discord, Telegram, Bark, Home Assistant, Gnome desktop, Zoom, and Pushover are all in active use, and any AI agent (Claude Code, Hermes) on any machine should be able to notify on whichever service fits the moment.
## Decision
(1) **Apprise becomes the single notification transport**: an Apprise API server deployed as a docker-compose service on the OVH production server, following the established service pattern. (2) **This iteration's scope is deliberately minimal**: stand up the server and swap the gem's Pushover P0 for a minimal Apprise client (one `notify` call against the API) — the pilot's critical path does not grow. (3) The **expansion is captured as P2/P3 backlog cards, to be built soon**: an Apprise client gem + CLI wrapper, an `os-notify` cc-os plugin so any session can route notifications by service/need (per ADR-023's ecosystem framing, notifications are cross-plugin infrastructure and belong in cc-os), multi-service routing config, and wiring Planka's native Apprise integration to the same server.
- **Rationale**: One integration covers N services — adding a service becomes an Apprise URL/tag config change, not new code; service selection becomes a runtime parameter, which is exactly the "tell the AI where to notify" requirement. Planka speaking Apprise natively means board events and agent-initiated notifications converge on one server. The minimal client is *less* work than the Pushover gem would have been (Apprise absorbs per-service quirks). Notification-blindness policy (pull beats push, silent days stay silent) is unchanged — this ADR changes the transport, not the policy.
## Consequences
Apprise replaces the originally planned Pushover-direct integration as the single notification transport, deployed as an Apprise API server so any service (Discord, Telegram, Bark, Home Assistant, etc.) can be targeted via configuration rather than new code, with Planka's native Apprise support converging on the same server. This iteration's scope stays minimal (server + a minimal Apprise client swapped in for the planned Pushover gem code), with a fuller client gem, CLI wrapper, and an os-notify plugin captured as later backlog work; the underlying notification-blindness policy is unchanged.
## Alternatives rejected
**Pushover gem as planned** — single-service dead end; every future service would be a new integration. **Deferring Apprise entirely** — the server + minimal client is comparable effort to the Pushover path, so there is no cheaper interim.
- **Cross-references**: ADR-023 (os-backlog / ecosystem role); vault note `vault-backlog-pilot-plan` (notification policy v2); gem repo `~/dev/ruby-gems/planka` (tick delivery); Apprise docs https://appriseit.com/.

View File

@ -0,0 +1,36 @@
---
id: "0025"
date: 2026-07-10
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-025 — Standard Ruby structure for cc-os plugins (lib/ + single-dispatcher bin/, fail-soft, installed-gem-only dependencies)"
---
# 0025 — Standard Ruby structure for cc-os plugins (lib/ + single-dispatcher bin/, fail-soft, installed-gem-only dependencies)
## Context
Two plugins ship Ruby (os-adr, os-backlog) and more will follow (os-notify is queued). implementation-status.md informally calls os-backlog's layout "the os-adr `lib/`+`bin/` pattern", but no ADR pins it, and the two plugins already diverge: os-adr uses one `bin/` script per verb (`adr-new`, `adr-find`, …) invoked as `ruby ${CLAUDE_PLUGIN_ROOT}/bin/adr-new`, inline `abort` error handling, stdlib only; os-backlog uses a single dispatcher `bin/os-backlog <subcommand>` invoked without a `ruby` prefix, a centralized `fail_soft` helper, and a lazily `require`d installed gem (`planka-api`) guarded by `rescue LoadError`. Without a recorded standard, each new plugin re-decides and the surfaces drift.
## Decision
New cc-os plugin Ruby follows this standard (deviate only with a recorded reason):
1. **Library**: `lib/<domain>.rb` + `lib/<domain>/*.rb`, one `module <Domain>` namespace, loaded via `require_relative`, stdlib-first. No gemspec/Gemfile inside a plugin — plugins are source trees, not gems.
2. **CLI**: a single dispatcher `bin/os-<domain>` with subcommands (os-backlog style), executable with a `#!/usr/bin/env ruby` shebang; skills invoke `${CLAUDE_PLUGIN_ROOT}/bin/os-<domain> <subcommand>` with no `ruby` prefix. One entry point per plugin keeps skill docs uniform and namespaces the verbs.
3. **Errors**: every user-reachable failure goes through one fail-soft helper printing exactly one line, `os-<domain>: <message>`, exit 1. Never a raw stack trace from a skill-invoked path.
4. **External gems**: consumed as *installed* gems, `require`d lazily inside the method that needs them with `rescue LoadError` → fail-soft. Never vendored, never source-coupled to a sibling repo, never shelled out to another gem's binary when the client classes suffice. Each required gem is named in the plugin README and implementation-status.md (installation is an environment prerequisite, so it must be discoverable).
5. **Tests**: minitest, `tests/all.rb` + `tests/test_helper.rb`, with in-memory fakes for any network service so the suite runs on machines without the gem or credentials (os-backlog's `FakePlankaClient` is the template).
- **Rationale**: os-backlog is the newer, deliberate iteration of the pattern and its choices earned their place: the dispatcher scales without `bin/` sprawl, fail-soft is mandatory for network-touching plugins (ADR-023's hook-hygiene argument), and lazy installed-gem requires keep pure subcommands (e.g. `resolve`) working on machines where the gem is absent — verified in practice 2026-07-10.
## Consequences
New cc-os plugin Ruby code must follow a standard structure: a lib/ namespace with require_relative loading, a single dispatcher bin/os-<domain> script for all subcommands, one fail-soft error helper per plugin, external gems required lazily with LoadError rescue and no vendoring, and minitest tests using in-memory fakes for network services. os-adr's older per-verb-script pattern is grandfathered rather than retrofitted immediately, and packaging plugins as real gems was rejected as unnecessary release ceremony.
## Alternatives rejected
**Per-verb bin scripts (os-adr style)** — fine at 4 verbs, sprawl at 9; grandfathered in os-adr, not worth a retrofit until os-adr is next touched for other reasons. **Packaging plugins as real gems** — adds release/version ceremony for code that ships by symlink + cache refresh (ADR-018). **Vendoring external gems** — duplicates code and hides the dependency instead of documenting it.
- **Cross-references**: ADR-023 (plugin boundaries, hook hygiene); implementation-status.md os-adr/os-backlog component sections; `cc-os-plugin-skill-naming-convention.md` (naming layer above this structural layer).

View File

@ -0,0 +1,31 @@
---
id: "0026"
date: 2026-07-12
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-026 — Unified project setup/update command: `/os-status:fix` remediates what the os-status checks flag"
---
# 0026 — Unified project setup/update command: `/os-status:fix` remediates what the os-status checks flag
## Context
Bringing a project up to cc-os standards currently requires knowing and running each plugin's own onboarding surface separately (`/os-adr:init`, `/os-vault:onboard-project`, a manual hub-note write, the os-backlog routing skill — which is itself still unbuilt, issue #14, so the os-status tracker warning points at a skill that does not exist). The SessionStart warnings (ADR-022) name a different remediation per line, and only os-adr's names a real command. The user wants one command that brings any project up to date with the current cc-os approach, without per-plugin ceremony. A literal `/cc-os:init` is impossible under the naming convention (`os-[domain]` plugins only).
## Decision
(1) New skill **`/os-status:fix`** in the os-status plugin: it runs the same check registry the SessionStart hook runs, then for each non-`ok` result drives the owning plugin's remediation — invoking that plugin's existing skill (`/os-adr:init`, `/os-vault:onboard-project`, the routing skill once built) or performing the small direct action (hub-note creation via `/os-vault:write` conventions). Decision-bearing steps (tracker destination, ignore-list confirmation) keep their human gates; mechanical steps run autonomously. (2) **Idempotent by construction**: re-running `fix` on a configured project is the update path — no separate update command. (3) Each `Check` in `hooks/checks.py` gains a **remediation pointer** so check and fix stay co-located in one registry; SessionStart warn output suggests `/os-status:fix` as the single entry point (per-check detail remains for context). (4) `.cc-os/config` gains a **`version` key** stamped by `fix`; a future check can warn when a project was configured under an older approach.
- **Rationale**: os-status already owns the per-project precondition registry (ADR-022); the fix skill is the write-side of the same seam, so checks and remediations cannot drift apart. Idempotent fix collapses setup/update into one verb. Per-plugin init skills remain the implementation (composition per ADR-023's cooperation goal) — `fix` orchestrates, it does not reimplement.
## Consequences
A new /os-status:fix skill runs the same check registry as the SessionStart hook and drives each non-ok result's remediation by invoking the owning plugin's existing onboarding skill or a small direct action, functioning as both the initial setup path and, by being idempotent, the update path. Each check gains a co-located remediation pointer in the registry, and .cc-os/config gains a version key so future checks can detect projects configured under an older approach; implementation was queued as a Forgejo issue with the tracker remediation depending on issue #14.
## Alternatives rejected
**New `os-project` plugin with `/os-project:init`** — reads slightly more naturally but splits remediations from the check registry they mirror and adds a seventh plugin for glue. **`/os-status:configure`** — same home, weaker verb; `fix` reads as "fix what the status check flagged", which is exactly the contract. **Literal `/cc-os:init`** — violates the `os-[domain]` naming convention.
- **Cross-references**: ADR-022 (check registry), ADR-023 (cross-plugin cooperation), `cc-os-plugin-skill-naming-convention.md`; Forgejo issue #14 (routing skill — the tracker remediation `fix` will delegate to).

View File

@ -0,0 +1,31 @@
---
id: "0027"
date: 2026-07-12
status: Accepted
supersedes:
superseded-by:
affected-paths: []
affected-components: []
migration_confidence: medium
migration_source: "docs/memory-system/03-architecture-decisions.md### ADR-027 — All cc-os per-project state lives under `.cc-os/<plugin>/`"
---
# 0027 — All cc-os per-project state lives under `.cc-os/<plugin>/`
## Context
The credvault smoke-test of `/os-status:fix` (issue #21) surfaced per-project dotfile sprawl: `.cc-os/` (os-status config/snoozes/state, os-backlog tracker key), `.dochygiene/` (os-doc-hygiene state), a planned `.os-adr/`, plus non-cc-os dirs (`.claude/`, `graphify-out/`). Each cc-os plugin inventing its own root-level dotdir means N gitignore entries, N places to look for state, and ad-hoc gitignore hygiene per session (observed in the credvault audit, session cd1ae1cd).
## Decision
(1) All cc-os-owned per-project state lives under **`.cc-os/`**: the shared `config` file stays at `.cc-os/config` (cross-plugin key=value contract, ADR-026); each plugin's private state lives in **`.cc-os/<plugin-domain>/`** (e.g. `.cc-os/dochygiene/`, `.cc-os/status/`, `.cc-os/adr/`). No new root-level dotdirs. (2) Existing plugins migrate with a **backward-compat fallback read** of the old path (auto-migrate on first write, then remove the old dir): os-doc-hygiene `.dochygiene/``.cc-os/dochygiene/`; os-status's own `snooze-*`/`state.json` → `.cc-os/status/`. (3) **`graphify-out/` is exempt** — external-tool output, disposable/rebuildable, path baked into os-vault hooks; moving it is deferred as aesthetic-only churn. `.claude/` is Claude Code's own dir, out of scope. (4) One gitignore entry — `.cc-os/` — covers all cc-os state; ensuring it is present becomes an explicit step of `/os-status:fix` (issue #23), not improvised per session.
- **Rationale**: One predictable home for the operating layer's per-project state matches the cc-os family framing (ADR-023: mutually aware, cooperating plugins) and extends the shared-config seam ADR-026 established. Fallback-read migration makes rollout painless across already-onboarded projects.
## Consequences
All cc-os-owned per-project state consolidates under .cc-os/, with each plugin's private state in .cc-os/<plugin-domain>/ (e.g. .cc-os/dochygiene/, .cc-os/status/, .cc-os/adr/) instead of each plugin inventing its own root-level dotdir, reducing gitignore entries to one and giving a single predictable location for state. Existing plugins migrate via a backward-compatible fallback read of their old path (auto-migrating on first write), while graphify-out/ and .claude/ remain exempt as out of scope.
## Alternatives rejected
**Status quo (per-plugin root dotdirs)** — sprawl grows linearly with the plugin family. **Moving `graphify-out/` too** — touches the os-vault onboard skill, SessionStart reconcile, and docs for zero functional gain.
- **Cross-references**: ADR-023 (ecosystem framing), ADR-026 (`.cc-os/config` contract); Forgejo issues #23 (fix tune-up), #24 (this consolidation).

37
docs/adr/README.md Normal file
View File

@ -0,0 +1,37 @@
<!-- Generated by os-adr. Do not hand-edit the table: it is regenerated in full on every ADR write. -->
# Architecture Decision Records
One file per decision, `NNNN-kebab-title.md`, created via `/os-adr:create`.
<!-- adr-index:begin -->
| ID | Title | Status | Date |
| --- | --- | --- | --- |
| 0001 | [Two memory types, kept as separate systems](0001-two-memory-types-kept-as-separate-systems.md) | Accepted | 2026-06-03 |
| 0002 | [memsearch for the episodic layer](0002-memsearch-for-the-episodic-layer.md) | Accepted | 2026-06-03 |
| 0003 | [Flat vault with namespaced tags, not folders](0003-flat-vault-with-namespaced-tags-not-folders.md) | Accepted | 2026-06-03 |
| 0004 | [SQLite + Sequel (Ruby) tag index as the knowledge-layer cache](0004-sqlite-sequel-ruby-tag-index-as-the-knowledge-layer-cache.md) | Superseded | 2026-06-03 |
| 0005 | [Structured-first; semantic search over the vault deferred](0005-structured-first-semantic-search-over-the-vault-deferred.md) | Superseded | 2026-06-03 |
| 0006 | [QMD as the (deferred) semantic-over-knowledge layer](0006-qmd-as-the-deferred-semantic-over-knowledge-layer.md) | Superseded | 2026-06-03 |
| 0007 | [Lazy freshness: write-hook + session-start reconcile, no daemon/cron](0007-lazy-freshness-write-hook-session-start-reconcile-no-daemon-cron.md) | Accepted | 2026-06-03 |
| 0008 | [Markdown-as-truth; sync the vault, not the indexes](0008-markdown-as-truth-sync-the-vault-not-the-indexes.md) | Accepted | 2026-06-03 |
| 0009 | [Package as a global Claude Code plugin with skills](0009-package-as-a-global-claude-code-plugin-with-skills.md) | Accepted | 2026-06-03 |
| 0010 | [Graphify knowledge graph as the knowledge layer (supersedes ADR-004/005/006)](0010-graphify-knowledge-graph-as-the-knowledge-layer-supersedes-adr-004-005-006.md) | Accepted | 2026-06-03 |
| 0011 | [Faceted tag taxonomy: six independent namespaces (refines ADR-003)](0011-faceted-tag-taxonomy-six-independent-namespaces-refines-adr-003.md) | Accepted | 2026-06-04 |
| 0012 | [Reuse the existing SecondBrain vault as the knowledge vault](0012-reuse-the-existing-secondbrain-vault-as-the-knowledge-vault.md) | Accepted | 2026-06-04 |
| 0013 | [Build-first / migrate-incrementally (build-order inversion)](0013-build-first-migrate-incrementally-build-order-inversion.md) | Accepted | 2026-06-04 |
| 0014 | [Graph connectivity comes from authored structure; migration scaffolding is a first-class prerequisite](0014-graph-connectivity-comes-from-authored-structure-migration-scaffolding-is-a-first-class-prerequisite.md) | Accepted | 2026-06-05 |
| 0015 | [memsearch episodic memory version-controlled in a dedicated private repo, auto-synced via cc-os SessionEnd hook](0015-memsearch-episodic-memory-version-controlled-in-a-dedicated-private-repo-auto-synced-via-cc-os-sessionend-hook.md) | Accepted | 2026-06-09 |
| 0016 | [Memory plugin sourced from cc-os git repo; bash→Python deep-module port; memsearch-sync split; symlink cutover](0016-memory-plugin-sourced-from-cc-os-git-repo-bash-python-deep-module-port-memsearch-sync-split-symlink-cutover.md) | Accepted | 2026-06-12 |
| 0017 | [Project graph onboarding assesses the repo and writes `.graphifyignore` before extracting](0017-project-graph-onboarding-assesses-the-repo-and-writes-graphifyignore-before-extracting.md) | Accepted | 2026-06-17 |
| 0018 | [Plugin renames must update the marketplace manifest, not just the directory and settings.json](0018-plugin-renames-must-update-the-marketplace-manifest-not-just-the-directory-and-settings-json.md) | Accepted | 2026-07-03 |
| 0019 | [Global os-orchestration plugin supersedes per-project orchestration text](0019-global-os-orchestration-plugin-supersedes-per-project-orchestration-text.md) | Accepted | 2026-07-03 |
| 0020 | [os-adr plugin built; implementation-time resolutions of the design's open questions](0020-os-adr-plugin-built-implementation-time-resolutions-of-the-design-s-open-questions.md) | Accepted | 2026-07-03 |
| 0021 | [Model-tier skill-execution eval for os-adr (Eval A), run in-session via subagents](0021-model-tier-skill-execution-eval-for-os-adr-eval-a-run-in-session-via-subagents.md) | Accepted | 2026-07-03 |
| 0022 | [os-status plugin: aggregated in-process SessionStart status checks](0022-os-status-plugin-aggregated-in-process-sessionstart-status-checks.md) | Accepted | 2026-07-06 |
| 0023 | [Backlog process management ships as a new `os-backlog` plugin (not an os-vault extension)](0023-backlog-process-management-ships-as-a-new-os-backlog-plugin-not-an-os-vault-extension.md) | Accepted | 2026-07-10 |
| 0024 | [Apprise replaces Pushover as the notification transport (supersedes the Pushover-direct plan)](0024-apprise-replaces-pushover-as-the-notification-transport-supersedes-the-pushover-direct-plan.md) | Accepted | 2026-07-10 |
| 0025 | [Standard Ruby structure for cc-os plugins (lib/ + single-dispatcher bin/, fail-soft, installed-gem-only dependencies)](0025-standard-ruby-structure-for-cc-os-plugins-lib-single-dispatcher-bin-fail-soft-installed-gem-only-dependencies.md) | Accepted | 2026-07-10 |
| 0026 | [Unified project setup/update command: `/os-status:fix` remediates what the os-status checks flag](0026-unified-project-setup-update-command-os-status-fix-remediates-what-the-os-status-checks-flag.md) | Accepted | 2026-07-12 |
| 0027 | [All cc-os per-project state lives under `.cc-os/<plugin>/`](0027-all-cc-os-per-project-state-lives-under-cc-os-plugin.md) | Accepted | 2026-07-12 |
<!-- adr-index:end -->

View File

@ -0,0 +1,41 @@
<!-- Generated by os-adr migration. Regenerated on every migration pass. -->
# ADR Migration Report
Converted: 27 · Low-confidence flag rate: 3.7% (gate threshold: 25% — within gate)
| ID | Title | Source | Confidence | Open items |
| --- | --- | --- | --- | --- |
| 0001 | [Two memory types, kept as separate systems](0001-two-memory-types-kept-as-separate-systems.md) | docs/memory-system/03-architecture-decisions.md### ADR-001 — Two memory types, kept as separate systems | medium | |
| 0002 | [memsearch for the episodic layer](0002-memsearch-for-the-episodic-layer.md) | docs/memory-system/03-architecture-decisions.md### ADR-002 — memsearch for the episodic layer | medium | |
| 0003 | [Flat vault with namespaced tags, not folders](0003-flat-vault-with-namespaced-tags-not-folders.md) | docs/memory-system/03-architecture-decisions.md### ADR-003 — Flat vault with namespaced tags, not folders | medium | |
| 0004 | [SQLite + Sequel (Ruby) tag index as the knowledge-layer cache](0004-sqlite-sequel-ruby-tag-index-as-the-knowledge-layer-cache.md) | docs/memory-system/03-architecture-decisions.md### ADR-004 — SQLite + Sequel (Ruby) tag index as the knowledge-layer cache | medium | |
| 0005 | [Structured-first; semantic search over the vault deferred](0005-structured-first-semantic-search-over-the-vault-deferred.md) | docs/memory-system/03-architecture-decisions.md### ADR-005 — Structured-first; semantic search over the vault deferred | medium | |
| 0006 | [QMD as the (deferred) semantic-over-knowledge layer](0006-qmd-as-the-deferred-semantic-over-knowledge-layer.md) | docs/memory-system/03-architecture-decisions.md### ADR-006 — QMD as the (deferred) semantic-over-knowledge layer | medium | |
| 0007 | [Lazy freshness: write-hook + session-start reconcile, no daemon/cron](0007-lazy-freshness-write-hook-session-start-reconcile-no-daemon-cron.md) | docs/memory-system/03-architecture-decisions.md### ADR-007 — Lazy freshness: write-hook + session-start reconcile, no daemon/cron | medium | |
| 0008 | [Markdown-as-truth; sync the vault, not the indexes](0008-markdown-as-truth-sync-the-vault-not-the-indexes.md) | docs/memory-system/03-architecture-decisions.md### ADR-008 — Markdown-as-truth; sync the vault, not the indexes | medium | |
| 0009 | [Package as a global Claude Code plugin with skills](0009-package-as-a-global-claude-code-plugin-with-skills.md) | docs/memory-system/03-architecture-decisions.md### ADR-009 — Package as a global Claude Code plugin with skills | medium | |
| 0010 | [Graphify knowledge graph as the knowledge layer (supersedes ADR-004/005/006)](0010-graphify-knowledge-graph-as-the-knowledge-layer-supersedes-adr-004-005-006.md) | docs/memory-system/03-architecture-decisions.md### ADR-010 — Graphify knowledge graph as the knowledge layer (supersedes ADR-004/005/006) | medium | |
| 0011 | [Faceted tag taxonomy: six independent namespaces (refines ADR-003)](0011-faceted-tag-taxonomy-six-independent-namespaces-refines-adr-003.md) | docs/memory-system/03-architecture-decisions.md### ADR-011 — Faceted tag taxonomy: six independent namespaces (refines ADR-003) | medium | |
| 0012 | [Reuse the existing SecondBrain vault as the knowledge vault](0012-reuse-the-existing-secondbrain-vault-as-the-knowledge-vault.md) | docs/memory-system/03-architecture-decisions.md### ADR-012 — Reuse the existing SecondBrain vault as the knowledge vault | medium | |
| 0013 | [Build-first / migrate-incrementally (build-order inversion)](0013-build-first-migrate-incrementally-build-order-inversion.md) | docs/memory-system/03-architecture-decisions.md### ADR-013 — Build-first / migrate-incrementally (build-order inversion) | medium | |
| 0014 | [Graph connectivity comes from authored structure; migration scaffolding is a first-class prerequisite](0014-graph-connectivity-comes-from-authored-structure-migration-scaffolding-is-a-first-class-prerequisite.md) | docs/memory-system/03-architecture-decisions.md### ADR-014 — Graph connectivity comes from authored structure; migration scaffolding is a first-class prerequisite | medium | |
| 0015 | [memsearch episodic memory version-controlled in a dedicated private repo, auto-synced via cc-os SessionEnd hook](0015-memsearch-episodic-memory-version-controlled-in-a-dedicated-private-repo-auto-synced-via-cc-os-sessionend-hook.md) | docs/memory-system/03-architecture-decisions.md### ADR-015 — memsearch episodic memory version-controlled in a dedicated private repo, auto-synced via cc-os SessionEnd hook | medium | |
| 0016 | [Memory plugin sourced from cc-os git repo; bash→Python deep-module port; memsearch-sync split; symlink cutover](0016-memory-plugin-sourced-from-cc-os-git-repo-bash-python-deep-module-port-memsearch-sync-split-symlink-cutover.md) | docs/memory-system/03-architecture-decisions.md### ADR-016 — Memory plugin sourced from cc-os git repo; bash→Python deep-module port; memsearch-sync split; symlink cutover | low | Decision (not stated) |
| 0017 | [Project graph onboarding assesses the repo and writes `.graphifyignore` before extracting](0017-project-graph-onboarding-assesses-the-repo-and-writes-graphifyignore-before-extracting.md) | docs/memory-system/03-architecture-decisions.md### ADR-017 — Project graph onboarding assesses the repo and writes `.graphifyignore` before extracting | medium | |
| 0018 | [Plugin renames must update the marketplace manifest, not just the directory and settings.json](0018-plugin-renames-must-update-the-marketplace-manifest-not-just-the-directory-and-settings-json.md) | docs/memory-system/03-architecture-decisions.md### ADR-018 — Plugin renames must update the marketplace manifest, not just the directory and settings.json | medium | |
| 0019 | [Global os-orchestration plugin supersedes per-project orchestration text](0019-global-os-orchestration-plugin-supersedes-per-project-orchestration-text.md) | docs/memory-system/03-architecture-decisions.md### ADR-019 — Global os-orchestration plugin supersedes per-project orchestration text | medium | |
| 0020 | [os-adr plugin built; implementation-time resolutions of the design's open questions](0020-os-adr-plugin-built-implementation-time-resolutions-of-the-design-s-open-questions.md) | docs/memory-system/03-architecture-decisions.md### ADR-020 — os-adr plugin built; implementation-time resolutions of the design's open questions | medium | |
| 0021 | [Model-tier skill-execution eval for os-adr (Eval A), run in-session via subagents](0021-model-tier-skill-execution-eval-for-os-adr-eval-a-run-in-session-via-subagents.md) | docs/memory-system/03-architecture-decisions.md### ADR-021 — Model-tier skill-execution eval for os-adr (Eval A), run in-session via subagents | medium | |
| 0022 | [os-status plugin: aggregated in-process SessionStart status checks](0022-os-status-plugin-aggregated-in-process-sessionstart-status-checks.md) | docs/memory-system/03-architecture-decisions.md### ADR-022 — os-status plugin: aggregated in-process SessionStart status checks | medium | |
| 0023 | [Backlog process management ships as a new `os-backlog` plugin (not an os-vault extension)](0023-backlog-process-management-ships-as-a-new-os-backlog-plugin-not-an-os-vault-extension.md) | docs/memory-system/03-architecture-decisions.md### ADR-023 — Backlog process management ships as a new `os-backlog` plugin (not an os-vault extension) | medium | |
| 0024 | [Apprise replaces Pushover as the notification transport (supersedes the Pushover-direct plan)](0024-apprise-replaces-pushover-as-the-notification-transport-supersedes-the-pushover-direct-plan.md) | docs/memory-system/03-architecture-decisions.md### ADR-024 — Apprise replaces Pushover as the notification transport (supersedes the Pushover-direct plan) | medium | |
| 0025 | [Standard Ruby structure for cc-os plugins (lib/ + single-dispatcher bin/, fail-soft, installed-gem-only dependencies)](0025-standard-ruby-structure-for-cc-os-plugins-lib-single-dispatcher-bin-fail-soft-installed-gem-only-dependencies.md) | docs/memory-system/03-architecture-decisions.md### ADR-025 — Standard Ruby structure for cc-os plugins (lib/ + single-dispatcher bin/, fail-soft, installed-gem-only dependencies) | medium | |
| 0026 | [Unified project setup/update command: `/os-status:fix` remediates what the os-status checks flag](0026-unified-project-setup-update-command-os-status-fix-remediates-what-the-os-status-checks-flag.md) | docs/memory-system/03-architecture-decisions.md### ADR-026 — Unified project setup/update command: `/os-status:fix` remediates what the os-status checks flag | medium | |
| 0027 | [All cc-os per-project state lives under `.cc-os/<plugin>/`](0027-all-cc-os-per-project-state-lives-under-cc-os-plugin.md) | docs/memory-system/03-architecture-decisions.md### ADR-027 — All cc-os per-project state lives under `.cc-os/<plugin>/` | medium | |
## Unrecognized content (manual handling required)
_none_
_Sources are untouched. Old-system removal is a separate, explicitly user-approved step._

View File

@ -203,6 +203,22 @@ on first write; scanner self-excludes `.cc-os/`; all four SKILL.md files updated
passed. `graphify-out/` explicitly exempt from consolidation (ADR-027). Re-run of passed. `graphify-out/` explicitly exempt from consolidation (ADR-027). Re-run of
`/os-status:fix` in credvault is the pending IRL verification. `/os-status:fix` in credvault is the pending IRL verification.
**cc-os ADR retrofit (2026-07-12, via `/os-status:fix`):** the 27-ADR monolithic
`docs/memory-system/03-architecture-decisions.md` was migrated to `docs/adr/` with
`/os-adr:migrate`, promoted ahead of the pilot projects (ADR-020 amended in place: migrate
pilot had already passed its gate, migrate is non-destructive, and the monolith's context
cost was real and growing). Mechanics: the detector only scans `docs/{adr,decisions,decision}/`
+ `DECISIONS.md`, so the source was shimmed in via a format-normalized copy under
`docs/decisions/` (bullet-label `- **Context**: …` → line-start `**Context:** …` so the
bold-label parser could extract sections; Rationale/Cross-references left as bullets so they
ride inside Decision/Alternatives content). Result: 27/27 converted, flag rate 3.7% (one low:
0016), consequences filled per file from source material (sonnet-delegated), 0004/0005/0006
marked Superseded by 0010 with frontmatter links, `migration_source` repointed at the real
monolith path, shim removed. The monolith is retained untouched as the historical source; new
decisions go through `/os-adr:create`. CLAUDE.md updated (docs/adr/ layout entry,
trigger-phrased decisions convention, os-adr rollout line). Remaining os-adr rollout: pilot
projects one at a time, then wider.
**Remaining optional items:** additional project onboarding (one at a time, per ADR-013); bulk **Remaining optional items:** additional project onboarding (one at a time, per ADR-013); bulk
vault migration. All required build steps complete as of 2026-06-15. vault migration. All required build steps complete as of 2026-06-15.
@ -528,9 +544,10 @@ symlinked into `~/.claude/plugins/os-status`
IRL session audits), not another lab rung. Run-set is now the baseline; wording tuning IRL session audits), not another lab rung. Run-set is now the baseline; wording tuning
against it moves measurement to the reserve-set. Full results: vault note against it moves measurement to the reserve-set. Full results: vault note
[[os-adr-eval-c-frozen-grid-results]]. [[os-adr-eval-c-frozen-grid-results]].
- **Remaining (locked rollout order):** real-project migration/adoption one at a time via - **Remaining (rollout order, amended 2026-07-12):** the cc-os retrofit is done (promoted
`/os-adr:migrate` — pilot projects first, then the cc-os retrofit (its 19-ADR monolithic ahead of pilots — see the 2026-07-12 timeline entry and the ADR-020 amendment); remaining:
file was deliberately excluded from the pilot), then wider. When onboarding real projects, real-project migration/adoption one at a time via `/os-adr:migrate` — pilot projects, then
wider. When onboarding real projects,
add the trigger-phrased "Architecture decisions" CLAUDE.md section (copy from add the trigger-phrased "Architecture decisions" CLAUDE.md section (copy from
`eval-b/fixture/project/CLAUDE.md`; candidate: emit it from `/os-adr:init`/`migrate`). `eval-b/fixture/project/CLAUDE.md`; candidate: emit it from `/os-adr:init`/`migrate`).

View File

@ -601,7 +601,7 @@ _Date: 2026-07-03_
- **Treating heading synonyms as high-confidence mechanical fills**: hides an interpretive mapping from the flag mechanism. - **Treating heading synonyms as high-confidence mechanical fills**: hides an interpretive mapping from the flag mechanism.
- **Auto-splitting multi-Status files that lack unit headings**: the split points would themselves be guesses; `unrecognized` + manual handling is the non-destructive answer. - **Auto-splitting multi-Status files that lack unit headings**: the split points would themselves be guesses; `unrecognized` + manual handling is the non-destructive answer.
- **Cross-references**: `docs/adr-system/04-plugin-requirements.md` (locked requirements), `05-plugin-prd.md` (phases), `06-eval-scenarios.md` (held-out eval sketches — the eval itself stays deferred), ADR-013 (build-first/migrate-incrementally precedent), ADR-018 (three-place plugin registration, followed during install), ADR-019 (plugin conventions). - **Cross-references**: `docs/adr-system/04-plugin-requirements.md` (locked requirements), `05-plugin-prd.md` (phases), `06-eval-scenarios.md` (held-out eval sketches — the eval itself stays deferred), ADR-013 (build-first/migrate-incrementally precedent), ADR-018 (three-place plugin registration, followed during install), ADR-019 (plugin conventions).
- **Status**: Accepted 2026-07-03. Plugin live at `cc-os/plugins/os-adr/`, installed as `os-adr@local-plugins`. Rollout order locked: pilot projects' real migration (interactive, per project) → cc-os retrofit → wider. - **Status**: Accepted 2026-07-03. Plugin live at `cc-os/plugins/os-adr/`, installed as `os-adr@local-plugins`. Rollout order locked: pilot projects' real migration (interactive, per project) → cc-os retrofit → wider. **Amended 2026-07-12:** cc-os retrofit promoted ahead of the pilot projects' real migrations. Reasons: the migrate-skill pilot already passed its 25% gate on sandboxed copies, `/os-adr:migrate` is non-destructive (sources untouched, low-confidence conversions flagged), and this file's context cost is real and growing (~27 ADRs read wholesale into sessions). Pilot-then-wider ordering still applies to the remaining projects.
## ADR-021 — Model-tier skill-execution eval for os-adr (Eval A), run in-session via subagents ## ADR-021 — Model-tier skill-execution eval for os-adr (Eval A), run in-session via subagents