The pre-frontal cortex for your codebase.
An agentic substrate — a knowledge graph of decisions, code, and the why behind both. Cortex lets agents and humans collaborate from shared understanding instead of constant re-explanation, with a native structural indexer, decision tracking on a unified SQLite graph, and a 2D canvas (Cortex) that renders code as semantic frames.
Answers the question agents can't today: "why was this built this way?" — not just "what does this code do."
The indexer is a prebuilt native binary — maintained as the separate cortex-indexer project and fetched at install — that writes directly to Cortex's SQLite database (a single shared file, no separate cache). Cortex is the substrate underneath Mesh — the IDE built to harness it.
The canonical install. One command, and every Claude Code session in every repo on the machine picks up cortex's MCP server, skills, hooks, and slash commands.
claude plugin add github:ruevu/cortexThis single install registers, in one go:
- 30 MCP tools routed per-call (
search_graph,get_code_snippet,trace_path,context_pack,why_was_this_built,create_decision,query_graph,index_repository,decision_candidates,check_contracts, the four PR tools, etc.).list_projectsanddelete_projectare cross-repo; everything else takes arepo_pathargument so a single server can serve work across many repos in one session. - The SessionStart hook (
hooks/check-index.sh) — prints the active repo, its index state, and routing reminders into every new conversation. - The skill library under
skills/—seed-decisions(cold-start bootstrap),capture-decision,search-decisions,explain-architecture. - The decision-capture flow under
bin/—cortex decision create / link / why / list / rehome / propose / supersede / update / delete / show / candidates, pluscortex code find / show / where / why.
After the install, /mcp lists cortex as connected and cortex decision --help works from any indexed repo. There is no per-project .mcp.json to edit, no environment variable to set.
Every routed MCP tool (everything except list_projects / delete_project) requires an absolute repo_path argument naming the git root the call is about. The session-start banner prints this path for the active session; agents working across multiple repos pass the explicit path of the repo each call concerns. Calls without repo_path return a structured MissingRepoPathError payload listing every indexed repo the resolver knows about — agents can paste the right value back without a second tool call.
The full contract — routing modes, the repo_path requirement, and the error shapes — is documented in docs/mcp-tools.md.
For hacking on cortex itself, or for MCP clients that aren't Claude Code (Cursor, custom agents, etc.):
git clone git@github.com:ruevu/cortex.git
cd cortex
npm installThen point your MCP client at the launcher script:
{
"mcpServers": {
"cortex": {
"command": "bash",
"args": ["/absolute/path/to/cortex/bin/cortex-mcp.sh"]
}
}
}The bin/cortex-mcp.sh wrapper resolves its own install path via BASH_SOURCE and cds into it before exec'ing npx tsx src/index.ts. This is what the plugin install wires up automatically — manual users are doing the same thing by hand.
Note. If you maintain a project-scoped
.mcp.jsonand Claude Code's trust prompt setsenabledMcpjsonServers: ["cortex"]in.claude/settings.local.json, deleting the.mcp.jsonlater (intentionally or via a stash/branch switch) leaves the setting dangling and/mcpthen fails withMCP error -32000: Connection closed. The plugin install avoids this entire failure class because there is no project-level file to lose track of.
npm run devStarts the MCP server (stdio) and the 2D frames viewer at http://localhost:3334/viewer. Port 3333 is reserved for the MCP plugin instance.
/mcp shows cortex as ✘ failed with -32000 connection closed
Most common cause: a project-scoped .mcp.json is referenced by enabledMcpjsonServers in .claude/settings.local.json but the file itself is missing. Either restore the file or remove "cortex" from enabledMcpjsonServers and rely on the plugin install instead.
If you're on the plugin path and still see this, the plugin cache may have an unbuilt native addon — see the better-sqlite3 entry below.
/mcp displays which config file it loaded under Config location. Three locations are searched in order — the first match wins:
<your-project>/.mcp.json— project-level override~/.claude.jsonunderprojects["<your-project>"].mcpServers— per-project user config (added byclaude mcp add)~/.claude/plugins/cache/cortex-local/cortex/<ver>/.mcp.json— the plugin's own config
The plugin runs an old version of cortex
The plugin cache at ~/.claude/plugins/cache/cortex-local/cortex/<ver>/ is a snapshot taken at install time. Bumping cortex's plugin.json version (and marketplace.json version) forces a re-sync on the next Claude Code session. For a force-refresh today:
rm -rf ~/.claude/plugins/cache/cortex-local/cortex/<old-ver>
# restart Claude Code; the marketplace re-syncs from /Users/<you>/.../cortex on next sessionError: Could not locate the bindings file (better-sqlite3)
The native addon wasn't compiled in the plugin cache. Rebuild it in place:
cd ~/.claude/plugins/cache/cortex-local/cortex/<ver>
npm rebuild better-sqlite3┌──────────────────────────────────────────────────────────┐
│ MCP Server (stdio, main thread) │
│ │
│ Code (13) Decisions (11) PRs (4) Promotion (1) │
│ ────────── ───────────── ─────── ────────── │
│ index_* create, get, open_pr promote │
│ search_* update, delete, add_pr_ _decision │
│ trace_path search, touch, │
│ query_graph, why_was_this_ merge_pr, │
│ get_* built, link, get_pr │
│ propose, │
│ supersede │
├──────────────────────────────────────────────────────────┤
│ GraphStore │ DecisionService │ PRService │ EventBus │
├──────────────────────────────────────────────────────────┤
│ .cortex/db .cortex/decisions.db .cortex/ │
│ (graph: nodes, (sidecar — durable, events.db │
│ edges, ctx_* outlives reindex) (append log, │
│ bookkeeping) worker-owned) │
└──────────────────────────────────────────────────────────┘
│ │
│ ┌────────────────────┘
▼ ▼
┌─────────────────────────────────────────┐
│ HTTP :3333 (plugin) / :3334 (dev) │
│ /viewer 2D frames viewer │
│ /api/graph unified nodes+edges │
│ /api/projects list indexed projects │
│ /api/decisions adapted decision payload │
│ /api/aggregates auxiliary-path groups │
│ /ws event stream + mutations │
└─────────────────────────────────────────┘
Three SQLite files, three lifecycles. .cortex/db is the indexer-owned graph (derived, can be wiped and rebuilt). .cortex/decisions.db is the user-authored sidecar (durable, survives every reindex). .cortex/events.db is the append-only event log (worker-owned, drives the WebSocket stream). The three are coupled at query time by stable string keys (qualified names, file paths, PR numbers) — never by graph node IDs, which the indexer regenerates per run.
Two threads. MCP tool handlers run on the main thread and write to .cortex/db / .cortex/decisions.db. A worker thread owns .cortex/events.db and the WebSocket fan-out: each DecisionService / PRService write emits an Event on the bus, the worker persists it, derives GraphMutations, and broadcasts both over /ws. See docs/architecture/graph-ui.md for the full event pipeline.
Tech stack: TypeScript, Node.js 20+, better-sqlite3, @modelcontextprotocol/sdk, zod, ulid, ws, chokidar (git watcher), Canvas 2D (viewer). Frame extraction adds a Python 3.9+ venv with scikit-learn + hdbscan.
These query the unified nodes/edges tables directly (SQL, no subprocess):
| Tool | Description |
|---|---|
search_graph |
Find code entities by name, label, or qualified name pattern |
trace_path |
Trace call chains via recursive CTE (mode: calls or callers) |
get_code_snippet |
Read source code for a fully qualified name |
context_pack |
One-call symbol bundle: snippet + callers + callees + governing decisions + recent commits |
get_graph_schema |
List node labels and edge types with counts |
search_code |
Grep with graph enrichment — annotates matches with enclosing function/class |
query_graph |
Run a Cypher-flavoured query against the unified graph |
get_architecture |
One-shot architectural histogram (label/edge counts) |
check_contracts |
Report cross-language RPC contract mismatches (arg-key diffs) + coverage, from persisted BINDS_KEY edges |
list_projects |
List all indexed projects |
index_status |
Check if the current repository is indexed |
ingest_traces |
Bulk-ingest runtime traces (experimental) |
These spawn bin/cortex-indexer (write operations):
| Tool | Description |
|---|---|
index_repository |
Run the 7-pass indexing pipeline |
detect_changes |
Map git diff to affected symbols |
delete_project |
Remove a project from the index |
| Tool | Description |
|---|---|
create_decision |
Create a decision with rationale, alternatives, and governed code links |
decision_candidates |
Read-only: frame cold-start decision candidates from git history + docs. |
propose_decision |
Create a proposed-status decision pending review |
supersede_decision |
Mark one decision as superseded by another |
update_decision |
Update decision fields (title, description, rationale, status) |
delete_decision |
Delete a decision and cascade-delete its links |
get_decision |
Get a decision with resolved GOVERNS and REFERENCES links |
search_decisions |
FTS5 search over decision content, optionally scoped to a code path |
why_was_this_built |
Find decisions governing a code entity — walks up file/directory hierarchy |
link_decision |
Attach GOVERNS, REFERENCES, or SUPERSEDES links to an existing decision |
promote_decision |
Promote a decision to team or public visibility tier |
| Tool | Description |
|---|---|
open_pr |
Create a PR entity in the graph (state: draft/open/merged/closed) |
add_pr_touch |
Record that a PR adds or modifies a file inside a frame |
merge_pr |
Transition a PR to merged state |
get_pr |
Get a PR with its decision links and touches |
The viewer at /viewer renders the codebase as semantic frames — clusters of files that belong together by topic and co-change behaviour. It's derived from the visual prototype at docs/specs/cortex-v0.3/cortex-frames-prototype-v5.html, wired to live data, and reduced to a static-fetch model (no WebSocket consumption in this iteration).
- Frames come from cluster output (
data.frame_id/data.frame_labelon file nodes, written byscripts/frame-extraction/inject-frames.ts). - Decisions come from
.cortex/decisions.dbvia/api/decisions, surfaced as governance pills attached to the focused frame. - Edges are real CALLS edges from the indexer, filtered to intra- and inter-frame pairs.
- Aggregates (auxiliary content like
locales/,vendored/,__snapshots__/) are positioned server-side at a gravity centroid near the frames they relate to (edge→path→margin tie cascade) and rendered as bare dots — present but visually de-emphasised. - Project switcher in the toolbar reads
/api/projectsand re-fetches/api/graph?project=<name>on change.
Pure modules (adapters.js, layout.js, data-fetch.js) are unit-tested in vitest. The render loop in viewer.js is hand-verified against the running dev server.
The simulation features in the prototype (multi-agent demo, synapse animations, PR floating nodes, presence avatars, merge animation, cursor traversal) are explicit non-goals in this iteration. See docs/architecture/graph-ui.md#frames-viewer for the module layout and extension recipes.
Cortex consumes a prebuilt structural indexer at bin/cortex-indexer. The indexer is maintained as a separate project, cortex-indexer (MIT-licensed). npm install runs scripts/fetch-indexer.mjs (postinstall), which downloads the platform binary from the cortex-indexer GitHub release pinned in src/indexer/version.ts (checksum-verified and cached). A runtime guard (ensureIndexer) asserts the binary's --version matches the pin; set CORTEX_INDEXER_PATH to point at a locally built binary for development.
The indexer and Cortex's TypeScript layer share a single SQLite file (.cortex/db by default; override via CORTEX_DB_PATH). The indexer writes code entities into Cortex's nodes/edges tables directly (with 'ctx-<int>' text IDs and lowercase kind values like function, class, method); PRs use the same tables with their own kinds. Indexer-internal bookkeeping (project metadata, file hashes, FTS5 over names, semantic vectors) lives in ctx_*-prefixed tables alongside.
- Single-file architecture: no SQLite ATTACH, no separate cache file. The indexer and TS layer operate on the same DB with WAL concurrency.
- Bulk-write fast path: the indexer's
extract/sqlite_writer.c(in the cortex-indexer repo) constructs the SQLite file via raw B-tree page writes for full-index runs. Linear extrapolation: ~3 minutes for a Linux-scale (~180k LOC) repo. - Subprocess invocation: Cortex spawns
bin/cortex-indexer cli index_repository …withCORTEX_DBpointing at the same SQLite file.
There is no decision data in .cortex/db — decisions live in the sidecar .cortex/decisions.db and are never overwritten by reindexing. See docs/architecture/decisions-storage.md for the rationale.
The C indexer has two open issues that affect multi-project workflows: the dump pass replaces the entire nodes/edges tables (not project-scoped), and IDs collide across DBs because they restart at ctx-1 for each indexed repo. See docs/architecture/known-limitations.md for the canonical multi-project workflow using scripts/frame-extraction/merge-indexed-db.ts.
A post-index extraction pass walks the RPC seam between languages and records
contract edges (Anchor nodes + BINDS_KEY edges) linking a caller's
argument keys to the handler that consumes them across a language boundary —
for example a TypeScript consumer calling into the C indexer. The pass runs
automatically as part of index_repository (and the cortex index CLI); no
extra step is needed.
Once a repo is indexed, the check_contracts MCP tool reports any
argument-key mismatches between the two sides of each RPC seam, plus a
coverage figure for how much of the seam is bound. This catches a class of
bug that single-language tooling can't see — a caller passing repoPath to a
handler that reads repo_path, say — without running the code.
check_contracts(repo_path="/abs/path/to/repo")
→ { mismatches: [...], coverage: 0.92, ... }
A multi-phase pipeline that derives frames (semantic file clusters) from an indexed repo and writes them back into nodes.data for the viewer. Lives under scripts/frame-extraction/ (TS orchestrators) and scripts/frame-extraction/python/ (Python ML).
Pipeline stages:
indexed repo (.cortex/db)
│
├──► co-change.ts — 180-day git log → file-pair counts (JSONL)
│
├──► text-blob.ts — per-file path tokens + symbol names → blob JSONL
│
├──► tfidf_hdbscan.py — TF-IDF + HDBSCAN with combined topical + co-change
│ distance (γ-weighted). Emits cluster JSON +
│ silhouette + top tokens per cluster.
│
├──► eval.ts — co-change agreement, import agreement, cluster
│ count, noise rate → markdown report
│
└──► inject-frames.ts — writes frame_id / frame_label / frame_confidence
into nodes.data for the viewer
NPM scripts:
| Script | What it runs |
|---|---|
npm run survey:phase1 |
Phase 1 corpus survey: clone N repos, index, collect index-size stats |
npm run survey:report |
Generate phase-1-results.md from the survey JSONL |
npm run co-change |
Build co-change JSONL from the local repo's git log |
npm run cluster:tfidf |
TF-IDF + HDBSCAN clustering (optionally --gamma <0..1> to mix in co-change) |
npm run eval:phase2 |
Evaluate a cluster output against co-change + CALLS edges |
npm run setup-python |
Bootstrap the Python venv (scripts/frame-extraction/python/.venv/) |
Cluster outputs land in .tmp/frame-extraction/clusters/<repo-slug>.json; eval reports in docs/specs/cortex-v0.3/phase-2-eval/<repo-slug>.md. See docs/architecture/frame-extraction.md for the full data flow and design rationale.
A separate eval harness lives under evals/ and is invoked via npm run eval. Unlike the frame-extraction eval (which scores cluster quality), this harness scores Cortex's tool surface against real-world target repos defined in evals/targets.json (currently Nuxt UI, NuxtHub starter, anthill-cloud).
The harness produces a scorecard per target: nodes_by_label + edges_by_type + a fixed list of "killer queries" exercising the queries that the field assessment showed Cortex falling short on (high-degree functions in Vue/Nuxt repos, HTTP_CALLS edges, composables called, Nitro handlers, etc.). Each query has a baseline_expected pass/fail and the harness reports anything surprising relative to the baseline.
The harness is scaffolded; the CLI entry point (evals/src/cli.ts) is still a stub. See docs/architecture/eval-harness.md for the design.
| Skill | Description |
|---|---|
/search-decisions |
Find existing architectural decisions before making changes |
/capture-decision |
Guided workflow for recording new decisions with rationale and alternatives |
/explain-architecture |
Narrative explanation combining decisions, call chains, and code structure |
/seed-decisions |
Bootstrap decisions for a freshly-indexed repo from git + docs (cold-start seeding) |
- Cold-start decision seeding — fresh repos auto-detect zero decisions
and offer to bootstrap them from git + docs via the
seed-decisionsskill; see the decisions-storage architecture doc.
| Hook | Trigger | What it does |
|---|---|---|
| Grep → search_code nudge | PreToolUse on Grep (code files only) | Suggests using search_code for graph-enriched results |
| Suggest capture | PostToolUse on git commit | Reminds agents to capture architectural decisions |
| Check index | SessionStart | Prints Repo, Index state so the agent knows whether to reindex |
npm test # full vitest suite
npm run test:watch # watch mode
npx vitest run tests/graph/code-queries.test.ts # single fileMajor suites:
| Suite | Covers |
|---|---|
tests/graph/ |
Schema, node/edge CRUD, annotations, FTS, code queries |
tests/decisions/ |
Decision CRUD + GOVERNS/REFERENCES, search, promotion, sidecar migration |
tests/prs/ |
PR open/touch/merge with decision link side-effects |
tests/events/ |
EventBus, persister, mutation deriver, git log parser, ULID monotonicity |
tests/ws/ |
Client registry, protocol encode/decode |
tests/db/ |
Path resolution, cache helpers |
tests/api/ |
HTTP routes (/api/graph, /api/projects, /api/decisions, /api/aggregates) |
tests/viewer/ |
Layout, projection, adapter pure functions |
tests/integration/ |
Worker thread, git watcher, full WS server roundtrip |
tests/mcp-contract/ |
MCP tool-input/-output contracts for every registered tool |
tests/frame-extraction/ |
Path tokenisation, co-change, TF-IDF orchestrator, inject, eval metrics |
tests/evals/ |
Scorecard + assertion runner |
| Variable | Default | Description |
|---|---|---|
CORTEX_DB_PATH |
<git-root>/.cortex/db |
Graph DB path (TS connection string and indexer target) |
CORTEX_DECISIONS_DB |
<git-root>/.cortex/decisions.db |
Sidecar decisions DB path |
CORTEX_EVENTS_DB_PATH |
.cortex/events.db |
Event log path (worker-owned) |
CORTEX_VIEWER_PORT |
3333 (plugin), 3334 (dev) |
HTTP viewer port |
CORTEX_BIND_HOST |
127.0.0.1 |
HTTP bind interface; 0.0.0.0 to deliberately expose on the LAN |
CORTEX_API_TOKEN |
(unset) | When set, every /api/* path except /api/health requires Authorization: Bearer <token> |
CORTEX_CORS_ORIGINS |
(unset) | Comma-separated browser-origin allowlist for /api/* (CORS) |
CORTEX_API_STRICT |
(unset) | 1 → HTTP response-validation failures return 500 in production too |
CORTEX_INDEXER_PATH |
bin/cortex-indexer |
Path to the indexer binary |
CORTEX_DB |
(set by Cortex) | Same as CORTEX_DB_PATH, passed to the indexer subprocess |
npx tsx scripts/seed.tsSeeds a small set of code entities + decisions for development.
Cortex is proprietary, all rights reserved (see the root LICENSE) —
its TypeScript code, viewer, MCP server, decision tooling, build scripts,
plugin manifest, and documentation.
The native structural indexer Cortex consumes at runtime is maintained as a
separate project, cortex-indexer (MIT-licensed), with its own third-party
attributions (vendored C libraries: mimalloc, SQLite, TRE, xxHash, yyjson,
tree-sitter runtime + grammars, LZ4, simplecpp, nomic embedding vocabulary).
Cortex downloads its prebuilt binary at install time; see
scripts/fetch-indexer.mjs.