15. Notes carry project provenance and are laid out per-project¶
- Status: Accepted
- Date: 2026-07-02
- Deciders: kristof (owner), Claude (orchestrator)
- Relates: ADR-0003, ADR-0005, ADR-0011, data-model, issue #90
Context¶
A single brain is shared across many projects: every repo under a registry prefix maps to
one brain (ADR-0011) — e.g. all antenna-* repos → antenna-brain. Until now notes were
organized only by kind (memory/, decisions/, work-state/, people/) with no record
of which project a note came from. You could not ask "show me everything from o2o-platform",
and the router (COMMONWEALTH.md) mixed every project together. The only project signal was
incidental text an extraction agent happened to put in a title.
Decision¶
- Provenance is first-class metadata. Add an optional
sourcefield to every note's base frontmatter — a stable project identity: the gitoriginremote reduced to anowner/reposlug, else the repo-root basename, else the cwd basename (resolveProjectSource). It is optional; pre-existing and non-project notes are "unattributed". - The on-disk layout is per-project:
<project>/<kind>/<id>.md. A note with asourcelives under a per-project subtree; an unattributed note stays at the kind root (<kind>/<id>.md), which is also the back-compat location. The project folder is a single filesystem-safe segment derived fromsource(separators flattened); the fullsourcestays in frontmatter. The owner chose project-first subtrees over flat-with-metadata so each project is a self-contained, browsable tree. - Kind is authoritative from frontmatter, not the folder.
listNoteswalks the tree and identifies a note by (parent folder is a kind folder) + (notINDEX.md), then filters byfrontmatter.kind. This decouples the layout from kind-detection, so the same code works for both flat and per-project paths and future layout tweaks don't ripple. - Capture stamps
sourceautomatically. The SessionEnd hook already passes the sessioncwd;curate captureresolves the project from it and stamps each candidate. MCPrememberresolves it from the MCP process cwd.approve()promotes a staged note into the same subtree. - Grouping/filtering is a derived/query concern (disposable, ADR-0005).
searchgains asourcefilter and returnssource;COMMONWEALTH.mdis regenerated grouped by project (a section per source, unattributed last); anINDEX.mdis written in every note-bearing directory. - The secret scrub stays layout-agnostic. A file is a note (and thus scanned) when its
immediate parent folder is a kind folder — covering both
<kind>/x.mdand<project>/<kind>/x.md— so per-project paths never slip past the pre-commit secret gate.
Consequences¶
- Concurrency is unaffected (ADR-0003): notes are still atomic one-fact-per-file with collision-proof ids; per-project folders union-merge exactly as flat folders did.
- Existing brains keep working: notes without
sourcestay flat and read/search/scrub normally; new captures get a project subtree. No migration is required (backfill is optional). - The derived index and
COMMONWEALTH.md/INDEX.mdare regenerated, never hand-merged, so the grouping is fully rebuildable from the note set. - Trade-off accepted: project-first subtrees required teaching
listNotes/derived generation and the secret scrub to be layout-agnostic (parent-folder-is-kind) rather than assuming a fixed set of top-level kind folders. Done once, centrally.