TeamBrain

Developing TeamBrain

Everything you need to go from git clone to a merged PR. For using TeamBrain in your own repo, see the README instead.

Setup (5 minutes)

Requirements: Node ≥ 20, pnpm ≥ 9 (corepack enable is enough), git. Optional: the gh CLI (authenticated) for the PR-opening paths (distiller, init/retire flows against a real remote).

git clone https://github.com/donatienmigue/TeamBrain.git
cd TeamBrain
pnpm install
pnpm build && pnpm test && pnpm lint && pnpm bench

All four must be green before and after your change — CI runs exactly these on Node 20 and 22, plus actionlint over the workflows and a nightly full-loop integration run.

Windows note. Everything works natively (no WSL needed for development): the daemon uses a named pipe where POSIX uses a unix socket, and tests fake ~/.teambrain via TEAMBRAIN_HOME. Avoid cloning under a OneDrive-synced folder if you can — file locking causes flaky deletes — and keep the checkout path short (deep paths break git checkout on fixture files).

The 60-second architecture

agent (Claude Code / Cursor / Codex / Gemini CLI — any MCP client)
  │  MCP tools: memory_context · memory_search · memory_propose · memory_feedback
  ▼
tb daemon (packages/mcp) ──reads── SQLite index (packages/index)
  │                                   ▲  rebuilt from
  │  capture hooks (packages/hooks)   │
  ▼  redacted on-device (packages/redact)
local spool → git branch `teambrain/sessions` → CI distiller (packages/distill)
                                                    │
                                                    ▼
                              memory PR → human merges → .teambrain/ on main

Two invariants explain most of the design (full list in CLAUDE.md):

Repo map

Package What it owns Start reading
packages/core Memory/event schemas (zod), byte-exact parse/serialize, lint + injection patterns, ULIDs, logger, typed errors → exit codes src/memory.ts, src/events.ts
packages/index SQLite + FTS5 + sqlite-vec hybrid retrieval behind RetrievalBackend; brain-tree + codemap checksum sync; bench src/store.ts, src/search-pipeline.ts
packages/mcp MCP server (4 tools), daemon (tb serve), session spool → teambrain/sessions branch, injection-safe rendering src/tools.ts, src/daemon.ts, src/spool.ts
packages/hooks Agent payloads → C2 events; the privacy contract (paths + exit codes only, never content); the CaptureAdapter registry — one adapter per vendor (claude-code, cursor, codex, gemini-cli), each = mapper + install plan + declared capabilities; the README capture matrix is generated from it src/adapter.ts, src/registry.ts, src/map.ts
packages/redact Secret/entropy/PII detectors, deny-globs, the public release-gating corpus src/engine.ts, corpus/
packages/distill Collect → cluster → LLM draft → dedup → gate → PR; the CodeMap generator (tb distill --codemap, opt-in); the only package allowed to call an LLM src/pipeline.ts, src/codemap/generate.ts
packages/cli Every tb command; thin wrappers over the packages above src/program.ts

Authoritative docs, in reading order: CLAUDE.md (principles — they’re tie-breakers, not suggestions) → docs/internal/CONTRACTS.md (frozen schemas; never change without asking) → docs/internal/BUILD_PLAN.md (tasks + acceptance criteria) → docs/internal/DEVLOG.md (what/why/tradeoffs of everything done so far).

Adding a capture adapter (new agent tool)

A vendor integration is three things — a payload mapper, an install plan, and an honest capabilities declaration — behind one interface (packages/hooks/src/adapter.ts). Everything else (envelope, redaction, deny-globs, transport, budgets) is shared; if you find yourself re-implementing any of it per vendor, the framework is being misused.

  1. Spike first (ADAPTERS_PLAN.md A1): against the installed tool, answer “does it expose a lifecycle/tool hook that can run a command?” Record a real session’s payloads to testdata/sessions/raw-<tool>.jsonl. No verified install surface → the vendor is BLOCKED and gets no adapter.
  2. Write packages/hooks/src/adapters/<tool>.ts implementing CaptureAdapter. Tier A (native hooks): map payloads to C2 events, structurally dropping content — read only a path and an exit code. Tier B (no hooks): mapEvent returns null and the install plan registers tb mcp --client <tool>, which turns on shared MCP-side session inference.
  3. Add it to ADAPTERS in packages/hooks/src/registry.ts — tb install, tb doctor, and the docs pick it up from there.
  4. Ship the per-adapter test set (ADAPTERS_PLAN.md §D): fixture replay, privacy negative, C2 validity, idempotent install, latency, doctor honesty.
  5. pnpm build && node scripts/update-capture-matrix.mjs — the README matrix is generated from declared capabilities, and matrix.test.ts fails CI if the docs claim more than the code declares.

Running tests

pnpm test                                     # everything (~500 tests, <1 min)
pnpm --filter @teambrain/redact test          # one package
pnpm --filter @teambrain/cli test -- --run reindex   # tests matching a pattern
pnpm bench                                    # perf budgets (fails red, like tests)

Rules that will bite you if you skip them (full list in CLAUDE.md):

Dogfooding: this repo runs TeamBrain on itself

The repo has its own brain (.teambrain/), MCP registration (.mcp.json, .cursor/), and capture hooks (.claude/settings.json). To experience what you’re building:

pnpm build
node packages/cli/dist/tb.js serve      # or `tb serve` if linked globally
# in another terminal:
node packages/cli/dist/tb.js doctor    # daemon, index, capture health
node packages/cli/dist/tb.js audit --last-session

A Claude Code session in this repo will then load the team memories at start and its activity is captured (metadata-only, redacted). tb audit shows you exactly what was recorded — use it whenever you touch the capture path.

A Stop hook runs pnpm test:changed at the end of every Claude Code turn, so an agent-assisted turn can’t end with failing tests.

Common tasks

Add a tb subcommand — implement run<X>Command() in packages/cli/src/<x>-command.ts returning { exitCode, output } (exit codes: 0 ok · 1 user error · 2 environment · 3 validation — throw the typed errors from @teambrain/core and map with exitCodeForError). Register it in program.ts with a helpGroup, add help copy to help-text.ts, write tests including at least one negative. Note tb propose/tb reindex as templates.

Add a redaction detector — add the rule in packages/redact/src/ and corpus cases in packages/redact/corpus/ (true positives and tricky negatives — UUIDs and git SHAs must not redact). The corpus is release-gating.

Add an injection-lint pattern — extend the table in packages/core/src/injection-patterns.ts; every entry needs one positive and one negative case in its test file.

Touch retrieval — the pure stages live in packages/index/src/search-pipeline.ts (unit-testable, no DB); the SQL halves in store.ts. Contract order is frozen (C4): BM25 ∪ vector → RRF → filters → required force-include → token trim. Run pnpm bench after.

Change a schema — stop. Schemas in CONTRACTS.md are frozen; write a proposal in the DEVLOG and ask first.

Pull requests

Troubleshooting

Symptom Fix
tb doctor says daemon unreachable Start tb serve in the repo that holds the brain; hooks degrade silently by design when it’s down.
Index looks stale or corrupt tb reindex — always safe, git is the truth.
better-sqlite3 ABI errors after a Node upgrade pnpm rebuild better-sqlite3
Bench fails only on your machine Budgets assume unloaded hardware; close the IDE indexer, rerun. If it still fails, treat it as real.
A fixture test fails with CRLF noise on Windows Fixture corpora are LF-forced via .gitattributes; if you added fixtures, cover them there too.
Tests wrote to your real ~/.teambrain Bug — they must use TEAMBRAIN_HOME/injected dirs. Report it.