Conventions: every task lists Deliverables, Notes, and Accept (commands that must pass). Do tasks in order; do not start a task with a prior task’s Accept red.
M0.1 Monorepo skeleton. Deliverables: pnpm workspace with packages/{core,index,mcp,hooks,redact,distill,cli}, shared tsconfig (strict, ESM, NodeNext), vitest, eslint+prettier, pnpm build|test|lint|bench wired, GitHub Actions CI running all four on Node 20/22, empty docs/DEVLOG.md.
Accept: pnpm build && pnpm test && pnpm lint green in CI on both Node versions.
M1.1 Schemas & IDs. zod schemas for Memory front-matter (C1), brain.yaml config, session events (C2); ULID generation; slug util; markdown+front-matter parse/serialize with byte-exact round-trip.
Accept: pnpm --filter core test — includes round-trip property test over fixture corpus testdata/memories/* (create ≥12 fixtures covering all classes, retired, TTL, supersedes).
M1.2 tb lint. Validates schema, body ≤400 words, title ≤80, evidence presence for distilled memories, and injection heuristics: reject bodies matching agent-instruction patterns (case-insensitive: “ignore (all )?previous”, “disregard .*(instruction|rule)”, “you must now”, tool-invocation syntax like mcp__, raw <system>-style tags, “fetch|curl http” imperatives). Heuristics table lives in packages/core/src/injection-patterns.ts with one test per pattern + negative tests (legit bodies mentioning e.g. “the previous migration” must pass).
Accept: tb lint testdata/brains/valid exits 0; tb lint testdata/brains/poisoned exits 3 listing each violation.
M1.3 Logger + errors. Structured logger (debug|info|warn|error) writing to ~/.teambrain/logs/ with 7-day rotation; typed error hierarchy mapping to CLI exit codes (C6). Guardrail: logger redacts fields named body|content|prompt at info+.
tb init importer (2 days)M2.1 Scanner+importer. Detect and parse CLAUDE.md, .cursorrules, .cursor/rules/, AGENTS.md, docs/adr/; convert each into candidate memories (class inferred: ADR→decision, rules→convention, README arch sections→map) preserving ≥90% of source text into bodies (split >400 words into linked memories).
M2.2 Interview. ≤10 generated questions from gaps (no map memories? ask for service list; conflicting rules? ask which wins). Plain readline; every question skippable.
M2.3 Output as PR-ready branch. Writes .teambrain/, commits on branch teambrain/init, prints next-step instructions; never touches main.
Accept: integration test — run against 3 fixture repos (testdata/repos/{claude-md-only,cursor-heavy,adr-rich}); assert memory counts, class mapping, and ≥90% text preservation (Jaccard token overlap assertion); git status on fixture main is clean.
M3.1 Store & schema. better-sqlite3 db at ~/.teambrain/index.db: memories table, FTS5 mirror, vec0 virtual table (sqlite-vec) for embeddings; checksum of brain tree stored; auto-reindex on mismatch.
M3.2 Embeddings. fastembed bge-small, lazy model download to ~/.teambrain/models/ with checksum pin; embed on index, batch 64. Offline guard: if model absent and download impossible, degrade to lexical-only with a debug log (principle 2).
M3.3 Hybrid search (C4). BM25 top-40 ∪ vector top-40 → RRF(k=60) → filters → required force-include → token-budget trim (est. 4 chars/token).
M3.4 Bench. Generator for a synthetic 5k-memory brain; pnpm bench asserts search p95 < 300ms and index rebuild < 60s on CI hardware; recall@8 ≥ 0.85 on the golden query set testdata/golden-queries.yaml (write 25 query→expected-id pairs).
Accept: pnpm --filter index test && pnpm bench green.
M4.1 Daemon. tb serve: long-lived process; watches .teambrain/ (fs events) + git fetch timer (60s) on the brain; incremental reindex on change; unix socket at ~/.teambrain/daemon.sock for hook events; pidfile + tb doctor heartbeat.
M4.2 MCP server (C3). stdio MCP server via official SDK exposing the 4 tools; memory rendering per C3 injection-mitigation rule; memory_context respects the 2000-token budget with required-first ordering.
M4.3 tb install claude-code. Idempotently writes MCP registration and hook config into project .claude/settings.json (show diff, ask confirm; --yes for CI). Registers a SessionStart hook that calls the daemon and emits {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"<memory_context bundle>"}} on stdout (≤10k chars — trim to budget), and never blocks: on any error, exit 0 with empty output.
Accept: integration test with a scripted MCP client — call all 4 tools against a fixture brain; retire a memory on a branch, merge, assert it disappears from memory_search within one watcher cycle (the R5 negative test); tb install run twice produces zero diff the second time.
M5.1 Redaction engine. Detectors: vendored gitleaks-compatible regex set; Shannon-entropy scanner (>4.5 bits/char on tokens ≥20 chars); PII (email/phone/IP) per brain.yaml redaction level; deny-glob path filter honoring .gitignore. Typed replacements «REDACTED:type». Public corpus at packages/redact/corpus/ (≥120 cases: true positives per detector, tricky negatives like UUIDs and git SHAs which must NOT redact). Corpus is a release gate: CI fails on any regression.
M5.2 Claude Code hook set. Thin scripts (no deps beyond node) for SessionStart (context inject, M4.3), PostToolUse (map tool_name/tool_input → C2 tool_use events; capture paths and exit codes only — never content fields), Stop/SessionEnd (close record: outcome heuristic = commits made during session; emit candidate prompt “2 candidate memories — propose? [y/N]” only if candidates exist). All hooks: read stdin JSON → ≤20ms handling → fire-and-forget to daemon socket → exit 0 unconditionally. Use "async": true in hook config where supported.
M5.3 Spool + sessions branch. Daemon persists redacted events to ~/.teambrain/spool/<sid>.jsonl; on session_end, commit the record to local branch teambrain/sessions and push opportunistically (failure = keep local, cap spool at 200MB oldest-first with warn log).
M5.4 tb audit. Pretty-print last session’s record exactly as stored, with a redaction summary line (“3 replacements: 2 aws_key, 1 email”).
Accept: pnpm --filter redact test (corpus green); end-to-end test: replay a recorded fixture session (testdata/sessions/raw-claude.jsonl) through the hook handlers → assert produced JSONL validates against C2, contains zero un-redacted corpus strings, and hook handler bench < 20ms p95; assert no event ever contains keys named content|old_string|new_string.
M6.1 Collect+cluster. Read new records on teambrain/sessions since last watermark (stored in .teambrain/brain.yaml state block written by CI commit) + merged PR metadata via gh pr list --json (GitLab deferred, note in docs). Cluster signals: same-path struggles across ≥2 sessions; repeated failing commands; no-hit memory_search queries; agent candidates.
M6.2 Draft. One Provider call per cluster using versioned prompt prompts/distill-v1.md; zod-validate structured output into C1 candidates with evidence populated; invalid → discard + count.
M6.3 Dedup+conflict. Embed candidate; cosine ≥0.85 vs existing → drop or mark amendment; pairwise contradiction check vs top-3 neighbors (Provider call, fake-provider fixtures in tests) → set supersedes and flag.
M6.4 Gate+PR. Score = evidence_count × novelty(1−max_sim); top ≤10; write one file per candidate on branch teambrain/proposals-<date>; open PR via gh with a summary table body; run tb lint as the PR check (ship ci-templates/lint.yml).
Accept: golden pipeline test — testdata/sessions/week-fixture/ (write it: 12 synthetic sessions engineered to contain exactly 3 legitimate clusters, 1 duplicate of an existing memory, 1 contradiction) → FakeProvider → assert: exactly 3 proposals, duplicate dropped, contradiction carries supersedes + PR-body flag, all proposals pass tb lint. tb distill --dry-run prints the would-be PR without git side effects.
M7.1 tb digest. Aggregates (proposed/approved/retired counts, top-retrieved, no-hit queries, stale ≥90d, rules-file drift hash check) → Slack webhook JSON. Structural guardrail: the aggregation module imports a projection of events that excludes any author/user field; add a test asserting the digest output contains no per-person data even when fed authored fixtures.
M7.2 tb doctor [--json] per Tech Brief §6. M7.3 ci-templates/: GitHub Actions for distill (weekly cron), digest (weekly), lint (on PR touching .teambrain/), sessions-branch rotation (monthly squash+prune). README for each.
Accept: unit tests + tb doctor --json schema test; templates pass actionlint.
M8.1 Full-loop integration test (the release test): fixture repo → tb init → merge init PR → tb serve → replay sessions → tb distill → merge proposal PR → assert new memory served by memory_search → tb retire → assert absence. Runs in CI nightly.
M8.2 Packaging. npm publish workflow (--provenance), standalone binaries via bun compile for mac-arm64/mac-x64/linux-x64, tb doctor self-check post-install.
M8.3 Docs. README (quick start <5 min), FORMAT.md (C1 spec), SECURITY.md (threat model summary incl. memory-poisoning stance), per-command help.
Accept: nightly loop test green 3 consecutive runs; npm pack installs clean on a bare container and tb init && tb doctor succeeds.
Deferred (do NOT build in V1, even if tempting): Cursor hooks (pending OQ-1 spike — stub the adapter interface only), cloud sync, web UI, GitLab distill driver, LLM reranking, org scope enforcement beyond schema.
docs/; if something doesn’t match the installed Claude Code version, report the discrepancy rather than guessing (hook behavior changes between versions).