codebase-memory-mcp Documentation

Local code intelligence for developers and AI agents. Connect code, docs and infrastructure in a persistent graph, with CBM Lens, MCP tools, agent...

RAW Doc

Technical Documentation: DeusData/codebase-memory-mcp

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (README.md)

codebase-memory-mcp: local code intelligence for AI agents via MCP















Understand your repository, from source code to the systems around it. codebase-memory-mcp is an MCP server that connects code, documentation, configuration, and infrastructure in a persistent local knowledge graph. Use it to find an unfamiliar feature, follow a call across services, trace a deployment back to its source, or assess which parts of a project a change could affect.

Give your coding agent structural context through Model Context Protocol (MCP), or query the graph directly from the CLI. The MCP server is the core of the project. Its optional visual companion, CBM Lens, is in open beta (v0.0.1), with a source reader, architecture maps, the Galaxy graph, coverage diagnostics, architecture decisions, and local code chat.

The native C backend exposes 17 MCP tools, parses languages and file formats with vendored grammars, and adds Hybrid LSP type resolution for supported languages. The installer supports 45 automatic or conditional client integrations, including client configuration, skills, agent profiles, and applicable hooks. Indexing and graph queries need no hosted service or API key. Focused graph queries can reduce repeated file reads and token use; see the measured results.

Quick start · Capabilities · Documentation · Infrastructure · Smart CI · Explore CBM Lens · Agent setup · MCP tools

Local processing: Source analysis and graph storage run on your machine. CBM Lens can optionally download a model for browser AI after you enable it; inference stays on the device. The installer writes agent configuration for the clients you select. See Security for release verification and SECURITY.md for reporting issues.

See the graph with CBM Lens (open beta v0.0.1). Folders become nested platforms, files and symbols become selectable blocks, and arrows show indexed relationships. The recording opens src/foundation in the map of this repository, selects arena.c and opens its source. Height can show source size; hotspot gravity reflects static fan-in. Still image.

Galaxy. Explore the code knowledge graph in 3D. The recording follows the outgoing relationships of cbm_arena_alloc one layer further, switches to the hierarchy and shows the two-hop call path to cbm_alloc. Edge pulses show relationship direction; they do not represent live execution. Still: two dependency layers around cbm_max_file_bytes.

Explore. Browse files in an IDE-style workspace, read syntax-highlighted source in Monaco, and keep the code graph alongside it. File impact shows what depends on the open file, and Code details shows the callers and calls of the symbol under the caret. Select code to ask the local chat about it.

More views: system structure and behavior

System structure groups symbols into components and lets you inspect their dependencies.

Behavior shows possible calls from a selected operation and the source evidence behind them.

The high-resolution GIFs show CBM Lens Beta on 20 October 2026; the still images were captured on 12–13 October. All show public source from this repository. See capture details for each capture's build, indexed scope and recording setup.

Why codebase-memory-mcp

  • Find the right code with context: combine symbol, text, and semantic search with callers, dependencies, source snippets, and file outlines. Targeted queries and compact, paged results can reduce token use and repeated file reads; see Performance for scoped measurements.
  • Connect documentation to implementation: index document sections, PDF text pages, architecture decisions, and supported references into code. Find the explanation alongside the symbol.
  • Follow the system beyond source files: trace supported relationships through Docker, Kubernetes, Helm, Kustomize, Terraform/OpenTofu, Terragrunt, Ansible, scripts, Make, and CI configuration.
  • Understand a change before running it: inspect affected symbols, compare indexed snapshots, and find related tests. Experimental Smart CI combines static relationships and per-test coverage for supported C/C++ test harnesses, falling back to broader runs when evidence is insufficient.
  • Explore visually with CBM Lens Beta: read source, follow call paths, inspect architecture and coverage gaps, manage architecture decisions, and ask a local model about selected code.
  • Equip your coding agents: install sets up supported client configuration, durable guidance, skills, Scout/Verify/Auditor profiles, and applicable context hooks. Support varies by client; see the integration matrix.
  • Keep context local and reusable: indexes persist between sessions, eligible active MCP sessions can keep them synchronized, and optional graph artifacts let a team share a baseline.
  • Run a native tool on your machine: standard releases include CBM Lens for macOS, Linux, and Windows. The native runtime needs no Docker or language runtime; parser grammars are included.

Quick Start

Install the latest release (macOS / Linux):

bash
curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bash

The standard native release includes CBM Lens. Start it after installation with codebase-memory-mcp daemon start --open --port=9749. The Nix packages and source builds also offer builds without the UI.

Windows (PowerShell):

powershell
# 1. Download the installer
Invoke-WebRequest -Uri https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.ps1 -OutFile install.ps1

# 2. (Optional but recommended) Inspect the script
notepad install.ps1

# 3. Unblock the downloaded file (removes Mark-of-the-Web restriction added by browsers/Invoke-WebRequest)
Unblock-File .\install.ps1

# 4. Run it
.\install.ps1

Note: If you see a script execution policy error, run Set-ExecutionPolicy -Scope Process Bypass first, or invoke with PowerShell -ExecutionPolicy Bypass -File .\install.ps1.

Options: --skip-config (binary only, no agent setup), --dir= (custom location).

Antivirus note: Microsoft Defender may flag a release binary as
Trojan:Script/Wacatac.B!ml. This is a known false positive; typically 61 of
~62 engines return clean, and the same detection family hits gh, llama.cpp,
Godot and Microsoft's own Go toolchain. See
Antivirus False Positives for the
evidence, how to verify the artifacts yourself, and how to report it if you
think we are wrong.

Restart your coding agent. Say "Index this project": done.

Manual install

  1. Download the archive for your platform from the latest release:

    • codebase-memory-mcp-<os>-<arch>.tar.gz (macOS/Linux) or .zip (Windows)
  2. Extract and install (each archive includes install.sh or install.ps1):

    macOS / Linux:

    bash
    tar xzf codebase-memory-mcp-*.tar.gz
    ./install.sh

    Windows (PowerShell):

    powershell
    Expand-Archive codebase-memory-mcp-windows-amd64.zip -DestinationPath .
    Unblock-File .\install.ps1
    .\install.ps1
  3. Restart your coding agent.

The install command automatically strips macOS quarantine attributes and ad-hoc signs the binary; no manual xattr/codesign needed.

Agent setup: skills, profiles, and hooks

Installation also equips supported coding agents to use the graph in daily work. The native install command detects clients and installs the integration pieces their documented formats support:

Component What it adds
MCP configuration Connects the client to the local server and its tools.
Instructions and skills Explains graph-first exploration, index freshness, coverage checks, and when to read source.
Scout, Verify, and Auditor profiles Offers fast provisional lookup, task-directed verification, or bounded full verification, where custom agents are supported.
Session and compaction hooks Restores graph context at supported lifecycle events.
Search and read hooks Adds relevant graph context to supported searches and flags recorded coverage gaps after supported file reads.

Hook augmentation is context-only and fail-open: it does not replace or block the requested tool call. Features vary by client and platform; some clients use durable instructions or a parent-agent handoff instead of hooks or direct subagent MCP access. Codex users review and trust installed hooks through /hooks. See Multi-Agent Support and Sessions, compaction, and subagents for exact support and activation requirements.

Session Coordination Daemon

CBM automatically shares one per-account coordination daemon across Claude Code, Codex, OpenCode, and every other configured client. There is no opt-in setting for MCP servers or hook clients: the first daemon-backed CBM session starts it, each session registers its own work, and a session-managed daemon stops after the final session. A daemon started with daemon start remains running until stopped. The daemon owns long-lived background services such as watchers, shared indexing jobs, and the optional UI. Closing one session cancels work owned only by that session, while work still needed by another session continues.

The detached daemon does not depend on an MCP frontend's stderr. It keeps owner-only durable records under the canonical ${CBM_CACHE_DIR}/logs directory (default ~/.cache/codebase-memory-mcp/logs):

File Contents
cbm-daemon.log Daemon lifecycle, watcher/indexing, UI, resource, and error events.
daemon-conflicts.ndjson Exact-build, coordination-ABI, and cache-root admission conflicts.
activation-events.ndjson Install/update/uninstall activation progress and outcomes.

Thin frontends still write immediate startup and session-specific errors to their own stderr; MCP JSON-RPC stdout remains clean.

All active CBM processes must run the exact same version, executable build, coordination ABI, and canonical cache root. Equivalent CBM_CACHE_DIR aliases resolve to the same root; a genuinely different root is rejected while any CBM process is active. MCP servers, hooks, one-shot CLI commands, temporary index workers, and the daemon share a crash-safe OS admission barrier; starting an ordinary conflicting process fails before doing work and records an explicit conflict in ${CBM_CACHE_DIR}/logs/daemon-conflicts.ndjson.

The native install and uninstall commands are the deliberate exception to that conflict rule. For installation, download, verification, and private same-filesystem staging finish before activation so a bad candidate never disrupts active work. The native update command prints the installer command that performs the update. Activation then publishes account-wide maintenance intent, asks the daemon and every temporary local operation to cancel, and waits to a finite deadline for all coordinated CBM processes to exit. It holds the admission and lifetime barriers exclusively while changing the active binary, configuration, PATH, or indexes. New CBM work cannot enter during this window. Activation progress and results are recorded in ${CBM_CACHE_DIR}/logs/activation-events.ndjson, and a successful command tells you to restart open coding-agent sessions so they launch the activated build.

Package-manager setup (npm, PyPI, or Go) verifies and caches the native executable for that package. It does not replace the active native installation and therefore does not stop running CBM sessions. When that cached binary is executed, it still enters the same exact-build admission barrier. The shell and PowerShell installers invoke the verified candidate's native install command, so they do receive the full account-wide activation guarantee.

The ordinary cli mode runs one command locally, but it is not disconnected from the daemon above: it connects to the shared coordination daemon (starting one if none is already running) for that same admission barrier plus per-project locks on graph mutations. Its connection is a cli_session: it does not register a project watcher or open a browser, and it holds the admission lease only for the command's lifetime. The shared daemon may serve Lens according to its UI configuration and available assets. If the command's own connection is what started the daemon, that daemon exits again once the command closes and no other session is attached; if a daemon was already running, the command simply joins and leaves it exactly as found. While the command is running, a temporary monitor separately lets activation cancel that operation and its supervised worker safely; the monitor exits with the command and never becomes a standing daemon. See CLI Mode for details.

CBM Lens Beta: code visualization and source navigation

CBM Lens is an optional visual companion to the MCP server, currently in open beta (v0.0.1). Standard native releases include its browser interface, so you can launch Lens directly after installation; UI-enabled source builds include it too. It builds on CodeAtlasWeb by Bernhard Jackiewicz (design and tracking in #1964). Lens is served on loopback. Browser AI downloads its optional model only after you enable it; inference stays on-device. Each panel names where its facts come from, and what the index did not record is shown as a gap. Start the local server:

bash
codebase-memory-mcp daemon start --open --port=9749

The browser opens at http://127.0.0.1:9749. A permanent daemon keeps the UI available after the command exits. Use daemon status to inspect it and daemon stop when you are finished. For an MCP session, --ui=true --port=9749 enables the same shared UI. Active clients must use the same executable build and cache directory.

Open your first repository

  1. Open the project menu and choose Add project index. You can also use Alt+P.
  2. Choose a local repository folder and name the index. Files stay in place.
  3. Start indexing and follow its reported status. You can close the dialog while it runs and reopen its activity from the project menu.
  4. Open the indexed project. Start with Architecture for an overview or Explore to read a file.

To bookmark a view, use ?project=<name>&workspace=architecture. Other workspace values are explore, galaxy, adr, coverage, and system.

Workspace What you can do
Explore Navigate the indexed file tree and read source in Monaco. File impact shows what depends on the open file; Code details shows the callers, calls, references and related tests of the symbol under the caret.
Galaxy Search and focus graph nodes, load their neighbors layer by layer, switch to hierarchy, follow a path, and open source. Back and Forward return to earlier views. The coverage shadow marks known gaps around the loaded graph.
Architecture Explore the folder map, entry points, routes, hotspots, system structure, and behavior paths. Select a node or connection to inspect its evidence.
ADR Read and edit the project's architecture decision record. Save explicitly; the document is stored with the index and shared with manage_adr.
Coverage Check indexed, partially parsed, skipped, and excluded paths, together with available freshness information.
System Inspect daemon activity, indexes, and logs. Filter logs by project, severity, or text.

Architecture views

  • Overview: move through source folders and files on a 3D map. Choose source-size or uniform height, language or kind colors, and relationship filters. Switch to Entry points to follow a static call graph from a starting symbol.
  • Routes: switch between the service map and endpoint connections. Deployment metadata and indexed route evidence are shown separately; unresolved registrations remain visible.
  • Hotspots: inspect symbols using measured signals such as fan-in and complexity. Gravity wells help locate frequently referenced areas. They are not a code-quality score.
  • System structure: inspect component dependencies, expand groups, isolate cycles, and follow connections back to source evidence.
  • Behavior: choose a source and a reachable destination to inspect static call paths and their source statements. These paths show possible relationships, not a recorded execution order. Runtime observations require separately ingested traces.

Drag a 3D view to orbit, scroll to zoom, and select a node or edge to inspect it. Edge colors distinguish relationships, and pulses indicate their direction. Reduced-motion preferences pause the pulses. Large views show their limits and omitted counts. A missing edge can reflect an indexing or display limit, so check Coverage and the source before drawing conclusions.

Chat with your codebase locally

Open Chat with the panel button at the top right to ask about the code you are looking at. In Explore it uses the open file or the exact text you have selected; in Galaxy it uses the selected node and its loaded neighbors. You can also attach graph evidence through Add context.

  1. Click the robot icon at the top right to open the agent configuration.
  2. Choose a local model. The default Qwen2.5 Coder 0.5B downloads about 567 MB; Qwen3 0.6B, LFM2.5 1.2B and Qwen3.5 2B are larger options. All need WebGPU with shader-f16 support.
  3. Click Download & load. Model files come from Hugging Face; inference runs in your browser, with no hosted chat API or API key. A model that is already in the browser cache loads with Load model (cached, no download).
  4. Ask a question. The composer shows the file or selection that will accompany it. Select a smaller range when you want to focus the answer.

Questions about callers and callees, such as "Who calls cbm_pipeline_run?", are answered from the loaded graph without the model, and the answer says so. When you select a symbol, the chat lists its indexed facts and the lines of its code, and the model adds one sentence; that sentence is left out when it claims something the source does not show. Text written by the model is labelled as such.

The recording asks who calls arena_grow and what it calls, with both answers listed directly from the indexed graph beside the selected symbol. Still image of a model explanation and graph answer.

The chat history is kept for 24 hours in this browser, per project. Each new message uses the source currently open in the reader; earlier source snapshots are not silently accumulated, and messages about another file or selection are not sent along. If a prompt is too long for the model, older messages are left out first; if it still does not fit, CBM Lens asks you to select less code instead of cutting the code. This chat explains the context you give it; it cannot edit files or run tools. Check its answers against the source. The small default model words things loosely, especially in languages other than English.

Cmd+K or Ctrl+K opens the Galaxy search. It finds symbols, files and folders by name or path in the whole index, also nodes that are not loaded yet. For callers and callees, ask in the chat; the answer is listed from the graph.

Architecture decisions

The ADR workspace keeps an unsaved draft per project in this browser tab. It checks whether the stored document changed before saving, but the API does not provide an atomic document lock. The editor accepts up to 8,000 UTF-8 bytes. Saving changes the indexed decision document, not a file in your repository.

Experimental agent activity

The Agents workspace and activity overlays are hidden in normal builds. To include them in a source build:

bash
VITE_EXPERIMENTAL_AGENTS=true scripts/build.sh --with-ui

Activity comes from explicitly configured tool hooks. The bundled adapter supports Claude Code PostToolUse; other clients need their own adapter. Recorded events go to the local daemon, and the reader must enable activity before the browser polls them. This does not automatically observe every agent. See the frontend setup guide.

For troubleshooting, open System logs. Frontend errors and failed requests are recorded locally. GET /api/ui-log reads frontend entries from the daemon's SQLite journal; ${CBM_CACHE_DIR}/logs/ui.log is also retained as a rotating JSONL export. See the frontend README for log scope, model setup, and development details.

Auto-Index

Enable automatic indexing on MCP session start:

bash
codebase-memory-mcp config set auto_index true

auto_index is off by default. When enabled, new projects are indexed automatically on first connection. The configurable file limit is config set auto_index_limit 50000. Previously indexed projects can be watched independently through auto_watch (on by default), subject to the session rules below.

The watcher follows MCP sessions: it watches the project each open MCP session is rooted in (the client's working directory) and stops when the last session for that project closes. A repository indexed with cli index_repository, or indexed from a session rooted somewhere else, is not watched; re-run index_repository after changing it. index_status reports this in its watch object: watched, and either the poll cadence and last scan time or the reason it is not watched.

Watcher registration is controlled separately by auto_watch (default true). Set config set auto_watch false to keep a session from registering its project with the background watcher; useful when working across many projects and you want each session contained to explicit indexing.

To turn the watcher off entirely, set config set watcher_enabled false (default true): the background poll thread never starts and no project is registered, while auto_index and manual index_repository keep working. Unlike auto_watch, which is consulted per session, watcher_enabled is read once when the background daemon starts, so run codebase-memory-mcp daemon stop after changing it; reconnecting your MCP client alone will not restart the daemon. See docs/CONFIGURATION.md.

The watcher follows git projects only by default: a project indexed from a directory that is not a git repository is not watched and stays at its last index until you run index_repository again. To have such roots polled too, set config set watch_non_git true (default false, read when the daemon starts): they are then checked on the same cadence with a file-tree scan that uses the indexer's own ignore rules.

Keeping Up to Date

Updates run from the install script on every platform, not from inside the running binary. codebase-memory-mcp update validates your flags and then prints the exact command to run:

bash
# macOS / Linux
bash "<install-dir>/install.sh"
powershell
# Windows
powershell -File "<install-dir>\install.ps1"

The install script is placed next to the binary at install time, so the printed path resolves beside the executable. It is idempotent, so re-running it is the update: it stops the daemon, retires the running binary, installs the new one, and cleans up.

Why it works this way. On Windows it is a hard requirement; a running executable cannot replace its own image, so the swap has to happen from a process that is not the binary being replaced. On macOS and Linux it is a deliberate choice: an in-process updater is structurally a downloader (fetch an archive, verify it, unpack it, mark a file executable, run it), and shipping that composite in every binary to serve a command most people run a handful of times is a poor trade. The release archives now carry no download URLs at all, and cbm makes no network request of its own accord: it does not check for new versions in the background, and nothing phones home. You find out about releases from the install script, your package manager, or GitHub.

If PowerShell refuses to run the script because the file came from the internet, Unblock-File it first.

Installed through npm or pip? Update with your package manager on every platform (npm install codebase-memory-mcp@latest / pip install -U codebase-memory-mcp).

Uninstall

bash
codebase-memory-mcp uninstall

Removes owned agent config entries, skills, hooks, instructions, and the installed binary. Existing graph indexes are listed and kept by default. -y/--yes, -n/--no, and noninteractive input keep them unless --delete-indexes is explicitly given. An interactive terminal without that flag is asked separately; the default answer is to keep them.

bash
codebase-memory-mcp uninstall -y --delete-indexes   # also delete every project index
codebase-memory-mcp uninstall --dry-run --delete-indexes   # preview; change nothing

--delete-indexes is explicit consent and takes precedence over --no; --dry-run always preserves the files. When indexes are kept, uninstall prints their cache directory (${CBM_CACHE_DIR:-~/.cache/codebase-memory-mcp}) and how to remove them.

The install script placed beside the binary is reported, not deleted: uninstall prints its path and the rm command for it. It is left alone on purpose: it may be your own copy, a symlink into a checkout, or managed by a package manager, and an uninstaller should not delete a file it cannot prove it owns.

Features

Graph & analysis

  • Architecture overview: get_architecture returns a compact summary by default. Request specific aspects for routes, hotspots, dependencies, clusters, or cycles. system_structure provides the projection used by CBM Lens and must be requested on its own.
  • Architecture Decision Records: manage_adr persists the project's decision record across sessions; indexing also extracts supported ADR metadata and supersession from documentation files
  • Louvain community detection: Discovers functional modules by clustering call edges
  • Change impact: detect_changes maps uncommitted changes to affected symbols with risk classification and can report test impact with scope: "tests"; compare_graphs compares indexed snapshots
  • Runtime observations: ingest_traces adds observed call relationships from separately collected traces alongside static analysis; static call paths alone do not show what ran
  • Call graph: Resolves function calls across files and packages (import-aware, type-inferred)
  • Dead code candidates: find functions with no indexed callers, excluding entry points. Check dynamic dispatch, coverage gaps, and framework conventions before removing code.
  • Cypher-like queries: MATCH (f:Function)-[:CALLS]->(g) WHERE f.name = 'main' RETURN g.name
  • Semantic search (the semantic_query parameter of search_graph, not a separate tool): vector search across the entire graph, powered by bundled Nomic nomic-embed-code embeddings (40K tokens, 768d int8) compiled into the binary; no API key, no Ollama, no Docker. 11-signal combined scoring (TF-IDF, RRI, API/Type/Decorator signatures, AST profiles, data flow, Halstead-lite, MinHash, module proximity, graph diffusion).
  • BM25 full-text search via SQLite FTS5 with cbm_camel_split tokenizer (camelCase / snake_case aware)
  • Structural search (search_graph): regex name patterns, label filters, min/max degree, file scoping
  • Code search (search_code): graph-augmented grep over indexed files only

Documentation connected to code

Find the explanation behind a symbol and follow supported references back to its implementation. Documentation participates in the graph alongside source files:

  • Markdown, reStructuredText, and AsciiDoc become navigable document sections; PDFs with text layers become searchable text pages. PDF extraction does not perform OCR.
  • Document-to-code references and supported language doc-comment references create MENTIONS relationships when the target can be resolved.
  • Architecture decision records contribute metadata such as status, deciders, and supersession, so decisions can be explored with the code they describe.
  • Supported diagrams and OpenAPI descriptions contribute structured entities. Extraction and reference resolution differ by format; not every textual mention becomes a graph edge.

Use graph and text search to locate the relevant section, follow its recorded relationships, and open the source evidence. Check check_index_coverage before treating a missing result as proof that the repository has no such documentation or reference.

Infrastructure and deployment

Ask "What deploys this service?", "Which image runs it?", or "Where does this configuration reach code?" CBM connects supported infrastructure forms to the source they build, configure, and run:

  • Containers and clusters: Dockerfiles, Compose services, Kubernetes resources, Helm charts and releases, and Kustomize overlays and patches.
  • Provisioning and configuration: Terraform/OpenTofu resources and modules, tfvars overrides, Terragrunt dependencies, Ansible tasks and handlers, and supported configuration keys and environment-variable access.
  • Build and delivery: shell commands, Make targets, and supported CI steps that build images, invoke repository scripts, or deploy resources.

For resolvable inputs, a trace can follow Service → workload → image → Dockerfile → program, or follow deployment and configuration relationships. Use trace_path modes for topology, deployment, and run paths. These are static relationships from supported source forms; dynamic values and unresolved templates can leave gaps. See Edge types for the exact relationship rules and limitations.

Cross-service linking

  • HTTP route ↔ call-site matching with confidence scoring
  • gRPC, GraphQL, tRPC service detection with protobuf Route extraction
  • Channel detection (EMITS / LISTENS_ON) for Socket.IO, EventEmitter, and generic pub-sub patterns across 8 languages with constant resolution

Cross-repo intelligence

  • CROSS_* edges link nodes across multiple repos indexed under the same store
  • Cross-repo architecture summary combining services, routes, and dependencies across the indexed fleet
  • Run status: index_status (and the UI layout next to linked_projects) carries a cross_repo object, {"status":"never_run"} until index_repository(mode="cross-repo-intelligence") has run with that project as the source, then "ran" with last_run_at, outcome, targets, projects_scanned, total_cross_edges and skipped_projects. That is how you tell "never linked" apart from "linked, nothing matched". The status is stored in the project's own index next to its CROSS_* edges, so a reindex that rebuilds the index drops both at once and the status goes back to never_run.
  • Skipped stores: target_projects: ["*"] skips an index built before the #768 schema change rather than failing the whole run. The response lists each one as skipped_projects: [{"name", "reason": "pre_768_schema", "hint": "reindex this project"}].

Smart CI and test impact

Use the graph to explain which tests a change reaches, and use that evidence to focus supported CI runs. Smart CI is experimental and has two layers:

Layer What it does What you supply
CI job selection Chooses jobs from the changed-file list. Repository-specific path and job rules; this repository's selector is an example.
Test selection Combines static reachability, changed tests, and admitted per-test coverage to select suites or tests. A supported C/C++ test harness, configuration, a coverage map, and a runner that accepts named selections.

codebase-memory-mcp test-impact select produces a selection with reasons; test-impact publish produces the graph-and-coverage bundle used as its baseline. For agent-assisted investigation, detect_changes with scope: "tests" exposes test impact through MCP.

Narrowing currently supports C/C++ family code with this repository's test conventions or declared equivalents. It is not general test selection for every language CBM indexes. Missing, stale, incompatible, or incomplete evidence broadens the selection or returns run_all; static reachability without admitted coverage never omits a suite. Begin with report-only shadow mode and validate your harness before enabling gating.

In this repository, test narrowing is wired to the Linux x86_64 test leg when TEST_IMPACT_MODE=gate; release and dry-run workflows retain full execution. See the Smart CI guide for supported conventions, coverage admission, setup, version requirements, and fallback behavior.

Edge types (selected)

  • CALLS: a callable is invoked at the source site
  • CALL_REFERENCE: a callable is used at a supported reference site (for example, a direct value argument) and resolves to one exact target
  • USAGE: an identifier is used, but a unique callable target is not proven (including ambiguous or complex expressions)
  • IMPORTS, DEFINES, IMPLEMENTS, INHERITS
  • HTTP_CALLS, ASYNC_CALLS (cross-service); a shell script, Make recipe or CI step running curl / wget on a URL it states (constant words resolved, unknown parts {}) to its Route with the method its flags give: a Kubernetes Service of the repository by the path, as code clients do, any other host (a download, localhost) with the host kept; the edge keeps via, method, url_path, host, never the user info or the query
  • SPAWNS: the caller starts another program (subprocess.run, exec.Command, posix_spawn, Process.Start, a shell command or Make recipe running a program by path, ...); the target is a Process node named after the program, or <dynamic> when the program is not a literal. When a shell script, Make recipe, GitHub Actions step or Prow job runs a script of the repository (./hack/x.sh, bash hack/x.sh), the target is that script's Module (else its File); make TARGET (also $(MAKE), -C, -f) targets the rule of that target, followed through included makefiles; edges from CI carry ci and job. A Dockerfile's last stage ENTRYPOINT (else CMD) spawns what the container runs (api: dockerfile): the repository script or source copied into the image, the Go main of a binary a stage builds with go build -o, else a Process
  • INFRA_MAPS: a Kubernetes Service to each workload whose pod labels carry every key: value of the Service's selector, within one namespace
  • CONNECTS_TO: a Kubernetes workload or compose service to the Service its containers' environment addresses (PRODUCT_CATALOG_SERVICE_ADDR: productcatalogservice:3550, a URL's host, <svc>.<ns>.svc..., a bare name on an *_ADDR / *_HOST / *_URL / ... key; compose: the project's .env interpolated): a Kubernetes Service of the same bundle and namespace, or a compose service of the same file. The edge keeps via: env, key and container, never the value
  • USAGE with ref: image: a Kubernetes workload (any pod template: containers, initContainers, CronJobs, custom resources) or a docker compose service to the Image it runs
  • Helm templates are rendered for their images: with the chart's values (the Chart DEPLOYS them, default_of: the values keys read) and with each release's (a Flux HelmRelease, a kustomize helmCharts entry, a Terraform helm_release: override_of). An image the render cannot know is left out, never guessed: after a change of values it does not run exactly, a tpl or include it cannot look into, a release value it cannot read, or a work budget it runs past. Known limit: each template file renders on its own, while Helm renders a chart's files with one shared .Values (a subchart's is its parent's .Values.<alias>), so a chart that changes .Values in one file and reads it in another can still show a wrong image
  • DEPLOYS: what puts manifests, kustomizations or charts on a cluster, to what it puts there: a Flux Kustomization or Argo Application to the kustomizations / manifests / chart of its path, a Flux HelmRelease to its chart (spec.chart or chartRef → OCIRepository), a shell command or Make recipe (kubectl apply -f/-k, kustomize build | kubectl apply, helm install/upgrade/template), a Go installer loading a chart with the Helm SDK, a Terraform helm_release, a Terraform kubernetes_* resource the object it declares (a Resource like a manifest's, ecosystem: terraform, when its name is a literal), a kustomization inflating a chart (helmCharts; its values file an IMPORTS). A chart outside the repository is an external Chart, keyed by its publisher when its repository URL is stated (__chart__<repository>/<name>, the same node in every project, the URL in repository), else one per project and name; the edge says via (flux, argo, helmrelease, kubectl, kustomize, helm, helm-sdk, terraform, helmcharts, ...)
  • DEPENDS_ON: a Helm chart to the charts it declares (the local chart for a resolvable file:// repository, otherwise an external Chart; the edge keeps repository), and a go.mod / requirements.txt file to an external Package; a Dockerfile to the Images it builds on (via: from, copy-from); an Image to the Dockerfile it is built from (via: build; tool: skaffold, compose, docker-build for docker / podman / nerdctl / buildah build commands in scripts, Make and CI); a compose service to the services its depends_on / links name
  • CONFIGURES: code reading an environment variable to the shared EnvVar of that name (strategy: env_access); a Kubernetes container, compose service or Dockerfile (ENV of the image) setting one to that container's own EnvVar (strategy: env_set, with source: value, configMapKeyRef, secretKeyRef, fieldRef, ..., compose, dockerfile; values are never stored); and that container's EnvVar to the code reading the name inside the files its image is built from (strategy: env_read, with dockerfile): the image's files are the sources its Dockerfile's COPY / ADD put into it, less the context's .dockerignore
  • CONFIGURES with strategy: key_symbol: a code symbol to the config key it names (config_key). A YAML, JSON, HCL (Terraform, OpenTofu, tfvars), TOML or INI key links by exact name (maxRetryCount = max_retry_count) and only to code inside the config file's component: its directory, or the one around a config/, configs/, conf/, settings/ or etc/ directory. A .env or .properties key also links by substring (confidence 0.75)
  • OVERRIDE with via: patch: a Kustomize patch to each object of its kustomization's build it changes (child → base): a patch file's documents to the object of the same kind and name; an inline patch (from the kustomization) or a JSON6902 operation file to what its target selects (group, version, kind, name, namespace, label selector); a component's in the build of each kustomization including it. The edge keeps kustomization, patch and line; a patch file is an IMPORTS (local_name: patches) of its kustomization, never a member of the build
  • OVERRIDE with via: namePrefix / nameSuffix: an object a kustomization renames, from its variant: a Resource named with the deployed name (capi- + controller-manager; the prefixes of nested kustomizations outermost first, suffixes innermost first) in the namespace that build gives it, ecosystem: kustomize, which the kustomization's file DEFINES. CRDs, Namespaces and API services keep their names, as kustomize does. The edge keeps kustomization and line
  • USAGE with via: terraform: a Terraform / OpenTofu reference (var.X, local.X, module.X, data.T.N, T.N) from the top-level block holding it to the block of its module (the .tf / .tofu files of its directory) that declares it (a locals entry: its locals block); an entry of the block's depends_on list is a DEPENDS_ON instead. The edge keeps ref (var, local, module, data, resource) and line; a name only mentioned in string text is no reference. A module call with a local source (module "net" { source = "./modules/net" }) IMPORTS (local_name: the module's name) the Folder of that directory (the root directory: the Project), and module.net.id (also module.net[0].id, module.net[*].id) USAGEs (ref: output) the output "id" there, beside module.net
  • OVERRIDE with via: tfvars: a .tfvars assignment to a variable the module of its own directory declares, from the file's Variable to that variable block (the edge keeps line); a .tfvars file whose own directory declares none of its names sets the nearest ancestor module's that does, the edge marked inferred (such a file is applied by a -var-file the repository does not state)
  • Terragrunt (via: terragrunt): a unit (terragrunt.hcl), evaluated in its own context: an include IMPORTS (local_name) the file its path names; a read_terragrunt_config IMPORTS (read: the file holding the call) the file it names, from the block holding the call or, for a call in an included file, from the unit's include block that brings it in; a dependency DEPENDS_ON the other unit; a local terraform source IMPORTS its Folder. Paths may use find_in_parent_folders, dirname and get_terragrunt_dir; anything else is unknown and links nothing
  • Ansible (via: ansible): a playbook or task file (a YAML file whose top level is a sequence) IMPORTS (local_name: the directive) what its directives name: import_playbook a playbook, include_tasks / import_tasks / include a task file (from its directory, else its role's tasks/), roles: items and include_role / import_role a role's Folder (roles/<name> at or above its directory). A templated target links nothing. Each task and handler is a Function (named by its name:, else its module; source: ansible, kind, module) that its file DEFINES: in a playbook's plays, a role's tasks/ and handlers/ files, and the files those include; a block's tasks, not the block. A notify CALLS (via: notify) each handler of that name or listen topic in the task's own role (else its file). A kube (kubespray's module) or kubernetes.core.k8s task DEPLOYS (via: ansible, module) the repository file its play rendered the applied manifest from: the template (in the role's templates/) or copy (its files/) task whose dest is the task's filename, item by item through register / with_items / loop lists the play states (a literal list, a set_fact, the role's defaults / vars), or a k8s template: argument
  • EMITS, LISTENS_ON (channels)
  • DATA_FLOWS with arg-to-param mapping + field access chains
  • SIMILAR_TO (MinHash + LSH near-clone detection, Jaccard scored)
  • SEMANTICALLY_RELATED (vocabulary-mismatch, same-language, score ≥ 0.80)

Indexing pipeline

  • 162 vendored tree-sitter grammars compiled into the binary
  • Generic package / module resolution: bare specifiers like @myorg/pkg, github.com/foo/bar, use my_crate::foo resolved via manifest scanning (package.json, go.mod, Cargo.toml, pyproject.toml, composer.json, pubspec.yaml, pom.xml, build.gradle, mix.exs, *.gemspec)
  • Infrastructure-as-code indexing: Dockerfiles (Dockerfile.*, *.Dockerfile, Containerfile: bases, what the container runs, ENV), container images (Image: the normalised repository, tag and digest on the edges; a repository built from several Dockerfiles, one per component tag, gets an Image per component), docker compose services (Resource), Kubernetes manifests (Resource, with group, version, kind, namespace, labels as k=v,k2=v2), Kustomize overlays (IMPORTS to their entries and patch files, OVERRIDE from their patches), Helm charts (Chart, DEPENDS_ON), Terraform / OpenTofu (.tf, .tofu, .tfvars) as graph nodes. A Service walks to the code it runs: Service → workload → Image → Dockerfile → main
  • Hybrid LSP semantic type resolution for Python, TypeScript / JavaScript / JSX / TSX, PHP, C#, Go, C, C++, Java, Kotlin, Rust, and Perl; a lightweight C implementation of language type-resolution algorithms, structurally inspired by and compatible with major language servers including tsserver / typescript-go, pyright, gopls, Roslyn, Eclipse JDT, and rust-analyzer (parameter binding, return-type inference, generic substitution, JSX component dispatch, JSDoc inference for plain JS files, namespace + trait + late-static-binding resolution for PHP, file-scoped namespaces + records + LINQ method syntax for C#, class-hierarchy + overload + lambda resolution for Java, extension-function + scope-function resolution for Kotlin, trait-method + UFCS resolution for Rust)
  • RAM-first pipeline: LZ4 compression, in-memory SQLite, single dump at end. Memory released after.

Distribution & operation

  • Native runtime set, zero infrastructure services: SQLite-backed, persists to ~/.cache/codebase-memory-mcp/
  • Auto-sync: Background watcher detects file changes in the project an MCP session is working in and re-indexes it automatically (other indexed repos: re-run index_repository)
  • Route nodes: REST endpoints are first-class graph entities
  • CLI mode: codebase-memory-mcp cli search_graph --project my-project --name-pattern ".*Handler.*"
  • Available on: npm, PyPI, Homebrew, Scoop, AUR, go install

Indexing modes

Mode Use it for
full Full indexing with semantic analysis. This is the default.
moderate A filtered index with semantic analysis.
fast A filtered index without semantic analysis.
cross-repo-intelligence Link services across indexed projects, optionally limited by target_projects.

Team-Shared Graph Artifact

Share a compressed graph snapshot so teammates can reuse an indexed baseline.

.codebase-memory/graph.db.zst is a zstd-compressed snapshot of the knowledge graph that lives next to your source. The artifact is opt-in: index_repository writes it only with persistence: true (the default is false), and once it exists later indexes refresh it. Local indexes persist regardless. When a teammate indexes a clone with an existing artifact, CBM imports that baseline and applies their local changes incrementally.

  • Format: SQLite database, indexes stripped, VACUUM INTO compacted, then zstd 1.5.7 compressed (8 to 13:1 ratio typical)
  • Two tiers:
    • Best (zstd -9 + index strip + VACUUM INTO); written by index_repository with persistence: true
    • Fast (zstd -3); refreshes an existing artifact on every other index, including the watcher's low-latency incremental updates
  • Bootstrap: when no local DB exists but the artifact is present, index_repository imports the artifact first, then runs incremental indexing, reusing the shared baseline where possible
  • Merge policy: the first export creates .codebase-memory/.gitattributes with merge=ours. Check your repository's Git merge-driver setup before relying on it for concurrent artifact updates.
  • Commit it deliberately: the artifact is rewritten on every index, including the watcher's Fast tier, and git stores each rewrite as a full new blob. Committing every refresh is what turns a 20 MB file into gigabytes of history; one team reached ~6 GB across ~350 commits of this single path. Pick a cadence (a release, a milestone, a nightly job) rather than committing every save.
  • Git LFS, if it must move on every commit: track it from the repo-root .gitattributes and leave the auto-created .codebase-memory/.gitattributes in place; the nearer file goes on supplying merge=ours, and only filter comes from the root:
    gitattributes
    .codebase-memory/graph.db.zst filter=lfs diff=lfs merge=lfs -text
    Track only the .zst; artifact.json is small and carries the schema version. The attribute applies to future commits only. Removing existing blobs requires a separate history migration. Two costs to weigh before adopting it: GitHub meters LFS storage and bandwidth, and its objects cannot be pruned without contacting support; and every teammate needs git lfs install; without it their checkout leaves a pointer file where the artifact should be, the integrity-checked import refuses it, and they fall back to a full reindex.
  • Optional: shared export is opt-in, and Git commits remain your choice. Add .codebase-memory/ to .gitignore if you prefer everyone to reindex from scratch.

The artifact provides a shared baseline while each developer keeps a local query database.

How It Works

codebase-memory-mcp is a structural analysis backend: it builds and queries the knowledge graph. The backend does not require a generative model. It relies on your MCP client (Claude Code, or any MCP-compatible agent) to be the intelligence layer.

text
You: "what calls ProcessOrder?"

Agent calls: trace_path(function_name="ProcessOrder", direction="inbound")

codebase-memory-mcp: executes graph query, returns structured results

Agent: presents the call chain in plain English

With MCP, your coding agent translates a question into tool calls. CBM Lens can also use an optional browser model for explanations; this does not change how the backend indexes or queries code.

Performance

Published backend benchmarks on Apple M3 Pro. These are separate from the CBM Lens checks:

Operation Time Notes
Linux kernel full index 3 min 28M LOC, 75K files → 4.81M nodes, 7.72M edges
Linux kernel fast index 1m 12s 1.88M nodes
Django full index ~6s 49K nodes, 196K edges
Cypher query <1ms Relationship traversal
Name search (regex) <10ms SQL LIKE pre-filtering
Dead code detection ~150ms Full graph scan with degree filtering
Trace call path (depth=5) <10ms BFS traversal

Memory-aware pipeline: Indexing keeps hot data in memory and can spill extraction results to disk when needed. Memory is released back to the OS after indexing completes.

More focused context, fewer tokens: The project's 31-repository preprint reports 10× lower token use and 2.1× fewer tool calls compared with file-by-file exploration. These March 2026 backend measurements cover the study's workloads and agent setup; the paper documents the full evaluation. An earlier five-query comparison reported ~3,400 versus ~412,000 tokens for structural queries. The study's system-performance measurements include the Linux indexing and Cypher timings above.

Explore the measurement guide to evaluate quality, latency and token use on your own workload.

Troubleshooting & Diagnostics

codebase-memory-mcp indexes and queries locally and collects no telemetry. When you hit something we can't reproduce on our side (a slow memory climb over hours, a performance regression, a leak that only appears after days of real use), we have no diagnostic data unless you choose to send it. Here is how to capture it yourself.

Capture a diagnostics log

Set CBM_DIAGNOSTICS=1 before the first daemon-backed MCP session starts, then reproduce the problem (let it run as long as it takes; a slow leak needs time to show in the trend). The shared daemon captures this setting from the session that starts it. If it is already running, close all daemon-backed sessions so it exits before changing the setting. The daemon creates a fresh owner-private cbm-diagnostics--<random> directory below the system temp directory ($TMPDIR or /tmp on macOS/Linux, %TEMP% on Windows). The exact paths are recorded by the diagnostics.start event in ${CBM_CACHE_DIR}/logs/cbm-daemon.log:

File What it is
trajectory.ndjson The memory trajectory: one JSON line every 5 s with rss, committed (Windows commit charge), peak_*, page_faults, fd, and queries. This is the file we need for memory/leak reports: the trend over time is what pinpoints a leak. It is kept on disk after the server exits (so you can grab it post-mortem) and rotates to trajectory.ndjson.1 past ~8 MB.
snapshot.json The latest snapshot only; handy for a quick live check. Removed on clean exit.

The private randomized directory prevents another local account from pre-placing a link or special file at a predictable diagnostics path. Its `` component is the shared daemon's process ID, also recorded by the daemon.start event. Set the variable consistently in the env block of each agent's MCP server config, or export it before launching the first session.

What to share

When you open a memory/performance issue, attach the .ndjson trajectory: it contains no source code or query text, only resource counters. If you'd rather not attach a file, paste it (or an agent's summary of it) into the issue: your assistant can read the NDJSON directly and report whether rss/committed grow monotonically, how fast, and relative to query count; which is exactly what we need to find the cause.

Installation

Pre-built Binaries

Platform Archive
macOS (Apple Silicon) codebase-memory-mcp-darwin-arm64.tar.gz
macOS (Intel) codebase-memory-mcp-darwin-amd64.tar.gz
Linux (x86_64) codebase-memory-mcp-linux-amd64.tar.gz
Linux (ARM64) codebase-memory-mcp-linux-arm64.tar.gz
Windows (x86_64) codebase-memory-mcp-windows-amd64.zip

Every release includes checksums.txt with SHA-256 hashes. The executable is self-contained; no adjacent data file is required. Linux -portable archives contain the fully static builds; ordinary platform archives use their native system ABI.

Windows note: SmartScreen may show a warning for unsigned software. Click "More info" → "Run anyway". Verify integrity with checksums.txt.

Setup Scripts

Automated download + install

macOS / Linux:

bash
curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/scripts/setup.sh | bash

Windows (PowerShell):

powershell
irm https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/scripts/setup-windows.ps1 | iex

AUR (Arch Linux)

bash
yay -S codebase-memory-mcp-bin
bash
paru -S codebase-memory-mcp-bin

The codebase-memory-mcp-bin package is available at: https://aur.archlinux.org/packages/codebase-memory-mcp-bin

Nix (flake)

The flake exposes two server packages plus the standalone frontend:

Package Contents
default (codebase-memory-mcp) Standard server, no UI
codebase-memory-mcp-ui Server with CBM Lens embedded (--ui=true works)
graph-ui Just the built CBM Lens assets (dist/)

Run directly without installing:

bash
# Standard server
nix run github:DeusData/codebase-memory-mcp

# Server with CBM Lens embedded
nix run github:DeusData/codebase-memory-mcp#codebase-memory-mcp-ui -- --ui=true --port=9749
# then open http://127.0.0.1:9749

Or build a binary into ./result/bin/codebase-memory-mcp:

bash
nix build github:DeusData/codebase-memory-mcp                          # standard
nix build github:DeusData/codebase-memory-mcp#codebase-memory-mcp-ui   # with UI

Working in a clone? Use . in place of the flake URL, e.g. nix run .#codebase-memory-mcp-ui -- --ui=true, or drop into a shell that puts the binary on PATH with nix shell .#codebase-memory-mcp-ui.

Note: launched by hand (not from an MCP client) the server exits as soon as stdin closes; that's normal MCP behaviour. Keep stdin open while testing the UI, e.g. sleep infinity | codebase-memory-mcp --ui=true --port=9749. The codebase-memory-mcp-ui package embeds the UI at build time; nix run'ing the standard default package with --ui=true will refuse to start the HTTP server.

Install via Claude Code

text
You: "Install this MCP server: https://github.com/DeusData/codebase-memory-mcp"

Build from Source

Prerequisites: C/C++ compiler, zlib, and Node.js for CBM Lens

Requirement Check Install
C compiler (gcc or clang) gcc --version or clang --version macOS: xcode-select --install, Linux: apt install build-essential
C++ compiler g++ --version or clang++ --version Same as above
zlib n/a macOS: included, Linux: apt install zlib1g-dev
Git git --version Pre-installed on most systems
Node.js 22+ and npm node --version and npm --version Required for --with-ui
bash
git clone https://github.com/DeusData/codebase-memory-mcp.git
cd codebase-memory-mcp
scripts/build.sh --with-ui          # the shipped composition (CBM Lens embedded, needs Node.js)
scripts/build.sh                    # without the UI (development only)
# Binary at: build/c/codebase-memory-mcp   (codebase-memory-mcp.exe on Windows)

The --with-ui build embeds CBM Lens and the agent integration templates into the native executable. You do not need Node.js to run that executable. Optional browser model weights are downloaded separately after opt-in.

Tests

Counts on main at 4d9592b5, where CBM Lens arrived. Native and Go counts use the macOS configuration; platform-specific registrations differ.

Suite Count Checked for this README
Native C 9,388 tests across 189 suites 9,376 passed, 11 skipped, 1 failed on macOS (regex_compile_refuses_overlong_pattern)
UI unit tests (Vitest) 3,970 tests in 310 files All passed
Go package wrapper 30 top-level tests Counted in the source
npm package wrapper 24 tests Counted in the source
Python package wrapper 43 tests Counted in the source

Run the native test suite:

bash
scripts/test.sh                     # full: clean sanitizer build + all suites + guards
scripts/test.sh --suites <name>     # one suite, incremental, seconds
build/c/test-runner --list-suites   # what is available

Frontend development and validation:

bash
cd graph-ui
npm ci
npm run test:unit
npm run build
npm run dev

The dev server listens on port 5173 and proxies /api and /rpc to port 9749. Start the matching CBM backend first.

Package wrappers have their own test commands: go test ./... in pkg/go, npm test in pkg/npm, and python3 -m unittest discover -s tests -p 'test_*.py' in pkg/pypi. The Go wrapper requires Go 1.26.1 or newer.

scripts/test.sh is the C test entry point used by CI. The canonical local artifact-flow check builds both stripped/unstripped candidates, defaults to the stripped candidate for this explicitly unscanned local run, packages those exact bytes, extracts the archive, and smokes it:

bash
scripts/ci/smoke-artifact.sh <linux|darwin|windows> <amd64|arm64>

scripts/package-release.sh is intentionally a lower-level immutable boundary:
it accepts only an already-final --selected-binary plus its
--expected-sha256; it never builds, strips, signs, or relinks the executable.

Manual MCP Configuration

If you prefer not to use the install command

Add to ~/.claude.json (user scope) or project .mcp.json:

json
{
  "mcpServers": {
    "codebase-memory-mcp": {
      "command": "/path/to/codebase-memory-mcp",
      "args": []
    }
  }
}

Restart your agent. Verify with /mcp; you should see codebase-memory-mcp with 17 tools.

Multi-Agent Support

install configures 45 supported automatic/conditional client surfaces: 39 detected automatically and 6
conditional or explicit. “Conditional” means the installer writes only when the
documented platform or an explicit, already-existing config path proves the
target is active. It never flips experimental feature flags, enables plugins,
YOLO modes, global permission bypasses, or third-party instruction trust.

Where a client has a documented custom-agent format, the installer creates three
exact-owned definitions from one canonical contract:

  • Scout (Tier 1): about 3 to 4 narrow calls for fast positive, provisional discovery; no absence, exhaustive-impact, or dead-code claims.
  • Verify (Tier 2, default): task-directed graph evidence, exact source checks, path coverage for every cited file, and scope coverage before negative claims.
  • Auditor (Tier 3): bounded scope, current index generation, complete relevant pagination, broader relationship checks, and explicit unresolved limitations.

Every direct tier batches check_index_coverage for its evidence paths and reads
flagged ranges or skipped/excluded files directly. A clean coverage result means
only “no recorded gap,” never proof of completeness. Clients without safe child
MCP access receive the same three tiers as parent-handoff agents; the parent must
supply project, generation, pagination state, graph evidence, and coverage
results. Updates migrate only byte-identical prior Verify definitions and never
overwrite user-modified agents.

Agent Activation MCP config Durable context / augmentation
Claude Code Detected ~/.claude.json Skill + three exact-tool graph agents; SessionStart, SubagentStart, non-blocking PreToolUse for Grep/Glob/Bash, and post-Read coverage
Codex CLI Detected $CODEX_HOME/config.toml Managed AGENTS.md activation pointer, skill, three read-only agents; SessionStart + SubagentStart
Gemini CLI Detected .gemini/settings.json GEMINI.md, three explicit read/graph-tool subagents; BeforeTool, AfterTool read_file coverage, and SessionStart
Zed Detected platform settings.json (JSONC) AGENTS.md + shared skill
OpenCode Detected $OPENCODE_CONFIG or resolved global config AGENTS.md, skill, three deny-by-default read-only agents; plugin adds grep/glob graph lookup, post-read coverage, first-tool-result session context, and post-compaction reinjection
Antigravity Detected .gemini/config/mcp_config.json .gemini/GEMINI.md
Aider Detected none CONVENTIONS.md via .aider.conf.yml
KiloCode Detected .config/kilo/kilo.jsonc Rule + three graph-tool subagents with deny-by-default permissions
VS Code Detected platform Code/User/mcp.json ~/.copilot/skills, three read-only agents, sessionStart + subagentStart
Cursor Detected .cursor/mcp.json Skill + three read-only parent-handoff agents; context hooks withheld because session injection races and readonly blocks MCP
Windsurf Detected ~/.codeium/windsurf/mcp_config.json Always-on global_rules.md
Augment / Auggie Detected ~/.augment/settings.json Rule, three read-only handoff subagents, SessionStart + post-view coverage
OpenClaw Detected $OPENCLAW_CONFIG_PATH or state openclaw.json Active-workspace AGENTS.md + TOOLS.md; compaction reinjection
Kiro Detected $KIRO_HOME/settings/mcp.json Steering, skill, three JSON agents with isolated Scout/Analysis-profile MCP and explicit graph-tool selectors (includeMcpJson: false)
Junie Detected .junie/mcp/mcp.json Skill + three graph subagents for EAP-capable builds; Scout and Analysis server aliases hard-limit the tier tool surfaces; no ineffective EAP SessionStart hook
Hermes Detected $HERMES_HOME/config.yaml Skill + fail-open pre_llm_call context augmentation
OpenHands Detected .openhands/mcp.json Shared .agents/skills/codebase-memory/SKILL.md
Cline Detected ~/.cline/mcp.json + ${CLINE_DATA_DIR:-~/.cline/data}/settings/cline_mcp_settings.json Rule + skill; automatic file hooks withheld because they auto-activate and their output is not reliably consumed; child agents cannot use MCP
Warp Detected, skill only UI, Warp Drive, or per invocation (manual) Shared ~/.agents/skills/codebase-memory/SKILL.md
Qwen Code Detected .qwen/settings.json QWEN.md, skill, three explicit read/graph-tool agents; SessionStart, SubagentStart, and post-ReadFile coverage
GitHub Copilot CLI Detected $COPILOT_HOME/mcp-config.json Instructions, skill, three read-only agents; sessionStart + subagentStart
Factory Droid Detected .factory/mcp.json AGENTS.md, skill, three droids with exact per-tier graph-tool lists (without additive whole-server exposure); SessionStart + post-Read coverage on macOS/Linux, withheld on Windows
Crush Detected .config/crush/crush.json Managed context path with explicit parent-to-child handoff
Goose Detected .config/goose/config.yaml .goosehints
Mistral Vibe Detected $VIBE_HOME/config.toml AGENTS.md, skill, and three matched agent/prompt pairs with explicit read-only graph-tool allowlists
Grok Build Detected $GROK_HOME/config.toml Owned rules/codebase-memory.md, skill, three graph agents with named-server mcpInheritance and exact server__tool dispatcher ids; context hooks withheld because its passive hook events discard stdout
Qoder CLI Detected ~/.qoder/settings.json Skill, three directly MCP-attached agents with named-server scoping and exact per-tier graph-tool lists; SessionStart, SubagentStart, and post-Read coverage, including documented PowerShell execution on Windows
Kimi Code CLI Detected $KIMI_CODE_HOME/mcp.json (default ~/.kimi-code) Same-root AGENTS.md + skill; fail-open UserPromptSubmit hook in config.toml
GitLab Duo CLI Detected $GLAB_CONFIG_DIR/duo/mcp.json or platform fallback Fail-open user SessionStart on macOS/Linux; hook withheld on Windows; no experimental global skill enablement
Rovo Dev CLI Detected configured override or ~/.rovodev/mcp.json Global AGENTS.md, skill + three read-only handoff subagents; no undocumented hook
Amp Detected ~/.config/agents/skills/codebase-memory/mcp.json Colocated skill + ~/.config/amp/AGENTS.md; no plugin
Devin CLI / Local Detected ~/.config/devin/config.json (platform app-data path on Windows) Same-root AGENTS.md + skill; macOS/Linux UserPromptSubmit + PostCompaction, and SessionStart only when Claude does not already provide it; hooks withheld on Windows
Tabnine Detected ~/.tabnine/mcp_servers.json MCP only; no experimental/YOLO setting
Continue / cn Conditional Existing ~/.continue/config.yaml or $CBM_CONTINUE_CONFIG_PATH MCP only
Visual Studio Conditional, Windows ~/.mcp.json MCP only
TRAE Conditional Existing $CBM_TRAE_CONFIG_PATH MCP only
Roo Code Conditional Existing $CBM_ROO_CONFIG_PATH MCP only
Amazon Q Developer IDE Detected ~/.aws/amazonq/default.json (preserves an existing agents/default.json or legacy mcp.json) MCP only
CodeBuddy Code CLI Detected ~/.codebuddy/.mcp.json (preserves an active deprecated/legacy file) CODEBUDDY.md, skill, three read-only graph agents; beta hooks are not auto-installed
IBM Bob Shell Detected by bob ~/.bob/mcp_settings.json Shared rule; no invented hook or agent
Pochi Detected ~/.pochi/config.jsonc (mcp) README.pochi.md, skill, and three readFile-only parent-handoff agents
Pi Detected none ~/.pi/agent/AGENTS.md + skill; MCP/subagents require an explicit reviewed extension
IBM Bob IDE Conditional Existing ~/.bob/mcp.json Shared rule + IDE skill; no invented hook or agent
Oh My Pi (omp) Detected Effective agent directory (OMP_PROFILE / PI_CODING_AGENT_DIR; default ~/.omp/agent/mcp.json) Skill and three direct-MCP graph-tool subagents (Scout/Verify/Auditor); preserves user AGENTS.md
Sourcegraph Cody Explicit opt-in Existing $CBM_CODY_CONFIG_PATH MCP only

For Codex, install keeps only a tiny managed activation pointer in global
$CODEX_HOME/AGENTS.md; all detailed behavior lives in the installed codebase-memory skill.
Fresh installs create the pointer, upgrades replace the legacy full managed block while preserving
all user-owned bytes, and uninstall removes only the managed pointer.

Sessions, compaction, and subagents

Hooks installed by this project are fail-open and context-only. Claude Code's
PreToolUse observes Grep/Glob/Bash and injects matching graph symbols as
additionalContext; PostToolUse on Read adds targeted coverage context when
the graph could not fully parse or index that file. It never denies or replaces
the requested tool call.

Claude Code, Codex CLI, Qwen Code, GitHub Copilot CLI, and VS Code's Copilot
runtime receive paired session/subagent context where the vendor exposes a
documented context-output contract. Codex users must review and trust installed
hooks through /hooks; changing a hook definition changes its trust hash, so an
update can require re-trust. Qoder uses SessionStart, SubagentStart, and
post-Read coverage, including its documented PowerShell executor on Windows.
Kimi uses UserPromptSubmit, while Hermes uses pre_llm_call; both retain their
documented Windows execution paths. Devin installs
UserPromptSubmit and PostCompaction on macOS/Linux and adds SessionStart
only when Claude's equivalent managed hook is not present. GitLab Duo gets a
narrowly scoped macOS/Linux user SessionStart entry on its experimental hook
surface. GitLab Duo, Devin, and Factory hooks are withheld on Windows
because those vendors do not document a deterministic shell/executor contract
there. Gemini CLI, Factory Droid, and Augment also add documented post-read/view
coverage context but expose no equivalent documented child-start context.

For runtimes without a stable context-producing lifecycle event, durable files
carry the contract across fresh sessions and compaction: verify the graph project
and index freshness, query structural facts in the parent, then pass the project,
qualified symbols, paths, and call-chain evidence in every delegated task.
Claude, Codex, Gemini, Kiro, Qwen, Copilot, CodeBuddy, OpenCode, Kilo, Vibe,
Qoder, Junie, Factory, and Grok Build receive Scout, Verify, and Auditor graph profiles.
Kiro embeds this MCP server with --tool-profile scout for Scout and
--tool-profile analysis for Verify/Auditor. Junie registers equivalent named
server aliases because its subagent schema filters by server rather than by
individual tool. Both process profiles use positive allowlists: Scout exposes
seven fast inspection tools, Analysis exposes eleven, and future or mutating
tools remain unavailable until explicitly reviewed. If either Junie alias
collides with user configuration, the installer preserves it and installs
parent-handoff profiles instead. Qoder combines its documented named-server
selection with exact tier-specific MCP tool IDs. Factory uses exact registered
MCP tool IDs without its additive mcpServers field, which would expose the
whole server. Codex, Kilo, Vibe, and other capable formats likewise enumerate
the narrowest supported tool set. Rovo, Cursor, Augment, Pochi, and Cline use parent handoff where direct
child MCP is unavailable or unsafe; Pochi is limited to readFile, and Cline
child agents cannot use MCP.

Cline's file hooks auto-activate when present, and current Cline does not
reliably consume their context output, so automatic adapters are withheld and
older owned adapters are cleaned up. CodeBuddy's beta, version-gated hooks are
not auto-installed. Junie's EAP
SessionStart output is documented as ignored, so no context hook is installed.
Junie custom agents remain EAP-dependent. Qoder can resolve higher-priority
project or plugin agents before user agents with the same name; reload the
client after installation or profile changes.
Cursor context
hooks are withheld: session context injection has a known race, subagentStart
is control-only, and read-only subagents cannot safely receive MCP access. Grok Build's
passive hook events (SessionStart, SubagentStart, PostToolUse) discard
stdout and PreToolUse honors only deny/rewrite decisions, so its context hooks
are withheld; Grok also reads Claude and Cursor MCP, skill, and hook files
through its compat layer, and the native config.toml entry shadows that copy
by name. Rovo
has no documented session context-output hook, and Bob
documents neither a suitable hook nor a custom-agent surface. Those surfaces are
not approximated with invented augmentation. Kimi plugins, Amp plugins, and
GitLab experimental global skills remain opt-in.

OpenClaw reinjects the Codebase Knowledge Graph (codebase-memory-mcp) AGENTS
section after compaction and places the same guidance in TOOLS.md, the bootstrap
files inherited by its subagents. Automatic augmentation covers the active/default
workspace. Separate agents.list[].workspace directories require making that
workspace active for installation or copying the managed block there.

The installed Claude shim is named cbm-code-discovery-gate for backward
compatibility; despite the legacy name, it never gates or blocks.

Manual or UI-managed integrations

These are intentionally not counted as automatic installs: Qodo MCP is added
through its UI and may be governed by enterprise allowlists; Warp MCP is managed
through Warp Drive/UI or per invocation (only the shared skill is automatic);
JetBrains AI Assistant / ACP is IDE-managed; GitHub Copilot coding agent, Jules,
and CodeRabbit are cloud/repository-managed; Replit exposes a remote/service
integration rather than a stable local user-global client; BLACKBOX AI does not
document a stable arbitrary user-global MCP/instruction/agent schema; Plandex has
no stable global registry safe to mutate; and SWE-agent uses explicit YAML and is
no longer a suitable automatic global target.

CLI Mode

Every MCP tool can be invoked as a local, one-shot command. CLI tools connect to the shared coordination daemon (starting one if none is already running) to get a crash-safe exact-build admission lease for the command's lifetime; they never register with the background watcher, and a daemon a command had to start itself exits again once the command's connection closes and no other session is using it. index_repository is the only exception internally: it starts a temporary, exact-build supervised worker for the index, then stops that worker before the CLI command exits; the worker holds its own lease until exit.

Commands that mutate graph data use shared OS-backed, per-project locks. This serializes conflicting work from CLI and MCP sessions on the same project while allowing unrelated projects to proceed independently.

When stderr is an interactive terminal, the CLI automatically shows lifecycle and indexing progress. Pass --progress to force the same feedback when stderr is redirected or the command is run non-interactively. Pass --quiet to disable automatic terminal progress and ordinary diagnostics while retaining errors; it cannot be combined with --progress or outer cli --verbose. Routine informational logs are quiet by default; pass outer cli --verbose to include them. Progress and logs use stderr while stdout remains reserved for the command result. Read tools return a compact tree by default; pass a tool's --format json for machine-readable payload JSON, or outer --json for the full MCP envelope.

Large compact-tree tables may start with a response-local <section>_refs directory and an explicit <section>_ref_rule. A cell such as @0+handler.go reconstructs to ref 0's prefix plus handler.go. References are local to that sibling <section> table and expansion is non-recursive: entries inside <section>_refs are always literal prefixes. This is limited to declared path and qualified-name columns and activates only when the exact rendered table is at least 15% and 64 bytes smaller and a conservative model-neutral token-shape proxy also improves by at least 1%. Search and trace likewise render direct and prefix-grouped tree shapes and keep the smaller complete representation, so singleton or scattered answers do not pay directory overhead. Keys are declared once per table but never cryptically abbreviated, and --format json keeps stable literal strings for machine consumers. Both gates are deterministic; exact token counts still depend on the caller's tokenizer.

Lean responses truncate semantically, never by cutting arbitrary bytes from code or identifiers. Ranked graph rows are retained ahead of raw grep rows and diagnostic summaries; omitted rows/sections report totals, has_more, and a strictly advancing continuation offset or cursor. If even the first whole row cannot fit, CBM asks for a higher budget and emits no self-looping cursor. max_output_tokens is model-neutral sizing guidance: CBM enforces a deterministic ceiling of four UTF-8 bytes per requested token, so it is not a tokenizer-exact count. Detail flags such as diagnostics, source_mode, and detail opt into heavier fields. search_code pages ranked rows with result_limit/result_offset (limit remains a compatibility alias), raw rows with raw_limit/raw_offset, and directory summaries with directory_limit/directory_offset. Raw lines default to a UTF-8-safe match-centered preview; each row reports content_start_byte, returned/total byte counts, match byte bounds when known, and a content continuation offset. Pass raw_content_offset to page the original line without moving the raw-row cursor. match_limit and source_max_lines bound per-result details, with exact omission metadata. detect_changes pages changed files, impacted symbols, and module summaries independently; prefer its snapshot-bound *_cursor continuations, which reject changed commits, worktree bytes, graph generation, or semantic arguments instead of silently skipping or duplicating rows.

Every response is standard UTF-8. Identifiers, paths, and raw search previews preserve POSIX byte-string identities: a preserved value containing malformed UTF-8 is emitted reversibly as @bytes:<lowercase hex of every original byte>. A valid preserved value that literally begins with the reserved @bytes: or @utf8: prefix is emitted as @utf8:<original value>, so decoding is unambiguous: strip one @utf8: prefix for literal UTF-8, or hex-decode one @bytes: prefix for original bytes. Ordinary valid UTF-8 is unchanged and pays no output-token overhead. To keep code readable, source bodies replace malformed UTF-8 with U+FFFD; use the pageable raw search preview when byte-exact source inspection is required.

Use cli <tool> --help to see the flags generated from that tool's input schema:

bash
codebase-memory-mcp cli index_repository --repo-path /path/to/repo
codebase-memory-mcp cli list_projects

# Use the "name" returned by list_projects as the project value.
codebase-memory-mcp cli search_graph --project my-project --name-pattern '.*Handler.*' --label Function
codebase-memory-mcp cli trace_path --project my-project --function-name Search --direction both
codebase-memory-mcp cli query_graph --project my-project --query 'MATCH (f:Function) RETURN f.name LIMIT 5'

# Force human-readable progress without contaminating stdout.
codebase-memory-mcp cli --progress index_repository --repo-path /path/to/repo
# Suppress automatic terminal progress and non-error diagnostics.
codebase-memory-mcp cli --quiet list_projects --format json
codebase-memory-mcp cli search_graph --project my-project --label Function --format json
codebase-memory-mcp cli list_projects --format json --detail stats | jq '.projects[].name'

Tools requiring arguments accept JSON piped on stdin. Tools with only optional arguments, such as list_projects, accept flags, --args-file, or redirected JSON files without waiting on an inherited pipe. Inline JSON remains accepted for backward compatibility but is deprecated in favor of flags, --args-file, or stdin.

MCP Tools

Indexing

Tool Description
index_repository Index a repository into the graph. Auto-sync keeps it fresh only while it is the project of an open MCP session; repos indexed from the CLI or from another project's session need a re-run (index_status shows watch). Waits for the whole index by default; pass async: true to start it in the daemon and return at once, then poll with status: true (see below).
list_projects List indexed projects with paging; use detail: "stats" for node/edge counts and database sizes.
delete_project Remove a project and all its graph data.
index_status Check indexing status of a project.
check_index_coverage Check whether exact paths or a scope are indexed and fresh. A clean result means no recorded gap, not proof of completeness.

Long indexes and client call deadlines. A synchronous index_repository on a large
repository can take longer than an MCP client allows one tool call (some IDE clients give up
after a fixed deadline). When a client cancels or disconnects, the daemon cancels an index that
nobody else is waiting for, so retrying the same blocking call never finishes. Use the async
mode instead:

  1. index_repository(repo_path="/abs/path", async: true) starts the index in the daemon (or
    joins the one already running for that project) and returns immediately with
    state (queued/running). The job keeps running even if the client cancels, times out or
    disconnects; only stopping the daemon ends it.
  2. index_repository(repo_path="/abs/path", status: true) reports state
    (queued, running, cancelling, succeeded, failed, cancelled), started_at,
    finished_at and an error summary. Poll it until the state is succeeded, failed or
    cancelled. Pass the same repo_path (and name, if the index call used one).

async and status are exclusive; async does not apply to cross-repo-intelligence. Both
need the daemon-backed server (the default codebase-memory-mcp entry point). A temporary
daemon (started on demand rather than by codebase-memory-mcp daemon start) stops, and
cancels its jobs, when its last client disconnects. A connected MCP session keeps it alive, so
async from your editor works. A one-shot codebase-memory-mcp cli index_repository --async
that is the daemon's only client is refused with a clear error, because the job would die the
moment the command exits: run codebase-memory-mcp daemon start first, keep an MCP session
open, or call without --async. status works everywhere. When a synchronous call was cut
short, the next
index_repository or status call for that project carries a notice suggesting the async
mode. index_status keeps describing the published graph and its freshness.

Querying

Tool Description
search_graph Structural, BM25, and semantic search. Page structural rows with offset/limit and ranked semantic rows independently with semantic_offset/semantic_limit.
trace_path Trace callers, callees, data flow, or cross-service paths (alias: trace_call_path). Depth 1 to 15; tests are excluded by default. mode: calls (default), data_flow, cross_service, topology (Kubernetes objects, routes, controllers and what they deploy; start at a Resource or Route), deploy (what a CI job, script or Make rule runs and deploys; outbound from the job, inbound from a chart), run (what a Service or workload runs: workload → Image → Dockerfile → the program its container starts, with the environment each container is given; outbound from a Service, workload or compose service).
detect_changes Map git diff to affected symbols + blast radius with risk classification.
query_graph Execute Cypher-like graph queries (read-only).
get_graph_schema Node/edge counts, relationship patterns, property definitions per label. Run this first.
compare_graphs Compare two indexed snapshots: node/edge additions and removals between a base and a target.
get_code_snippet Read source code for a function by qualified name.
get_file_outline Declaration outline of one repository-relative file in source order, with optional label filter and paging.
get_architecture Codebase overview: languages, packages, routes, hotspots, clusters, ADR.
search_code Grep-like text search within indexed project files.
manage_adr CRUD for Architecture Decision Records (get reads, update replaces the whole document, set_sections rewrites only the named sections and leaves every other byte untouched, sections lists headings). Query modes do not wait behind a same-project reindex; writes remain serialized.
ingest_traces Store observed call paths or caller/callee pairs under a run label. Both endpoints must resolve to indexed symbols; unresolved names are reported.

manage_adr(mode='set_sections') writes one or more sections by name and splices them into the stored document, so text outside the named sections (including a preamble, code fences and section ordering) is preserved byte-for-byte. Any ## Heading works, not just the conventional PURPOSE / STACK / ARCHITECTURE / PATTERNS / TRADEOFFS / PHILOSOPHY set; names match exactly, including case. Writing the same section twice is a no-op, so a retry after a lost response cannot duplicate content.

manage_adr query modes (get and sections) use the server's cached query store so they can proceed while a same-project reindex is running. If another process publishes a replacement store during reindexing, they can return the pre-publication ADR until idle eviction refreshes that cache. Updates remain serialized through the project mutation guard.

Graph Data Model

Node Labels

Project, Package, Folder, File, Module, Section, Class, Struct, Interface, Trait, Enum, Type, Function, Method, Field, Variable, Constant, Macro, Decorator, Route, Channel, Table, Process, EnvVar, Resource, Chart, Image, Branch

Edge Types

CONTAINS_FOLDER, CONTAINS_FILE, DEFINES, DEFINES_METHOD, IMPORTS, CALLS, CALL_REFERENCE, USAGE, INHERITS, IMPLEMENTS, OVERRIDE, DECORATES, RAISES, THROWS, WRITES, DATA_FLOWS, HTTP_CALLS, GRPC_CALLS, ASYNC_CALLS, EMITS, LISTENS_ON, HANDLES, SPAWNS, CONFIGURES, INFRA_MAPS, CONNECTS_TO, DEPLOYS, DEPENDS_ON, REFERENCES_FILE, MENTIONS, TESTS, TESTS_FILE, SIMILAR_TO, SEMANTICALLY_RELATED, FILE_CHANGES_WITH, HAS_BRANCH, and the cross-repo CROSS_HTTP_CALLS, CROSS_GRPC_CALLS, CROSS_GRAPHQL_CALLS, CROSS_TRPC_CALLS, CROSS_ASYNC_CALLS, CROSS_CHANNEL

MENTIONS links a documented definition to the code its doc comment references (C# <see cref>, <seealso cref>, <exception cref>, ``), one edge per pair with via, syntax, tier (exact or unique), first line and count. A reference is bound only when it names exactly one definition under the language's own lookup rules; the rest are counted by reason in index_status under doc_links (samples with diagnostics='full').

Qualified Names

get_code_snippet uses qualified names: ..<name>. Use search_graph to discover them first.

Supported Cypher (openCypher read subset)

query_graph is a read-only openCypher subset:

  • Clauses: MATCH, OPTIONAL MATCH, multiple MATCH, WHERE, WITH (+ WITH … WHERE), RETURN, ORDER BY, SKIP, LIMIT, DISTINCT, UNWIND, UNION / UNION ALL, CASE.
  • Patterns: labelled nodes, label alternation (n:A|B), relationship types/direction, variable-length paths [*1..3], inline property maps.
  • WHERE: = <> < <= > >=, AND/OR/XOR/NOT, IN, CONTAINS, STARTS WITH, ENDS WITH, IS [NOT] NULL, regex =~, label test n:Label, and EXISTS { (n)-[:TYPE]->() } (single-hop existence; great for dead-code, e.g. WHERE NOT EXISTS { (f)<-[:CALLS]-() }).
  • Aggregates: count (+DISTINCT), sum, avg, min, max, collect.
  • Functions: labels, type, id, keys, properties; toLower/toUpper/toString/toInteger/toFloat/toBoolean; size, length, trim/ltrim/rtrim, reverse; coalesce, substring, replace, left, right.

Anything outside this subset (write/MERGE/CALL clauses, unsupported functions, list/map literals, comprehensions, path functions, parameters) fails with a clear unsupported … error rather than returning empty results.

Ignoring Files

Layered: hardcoded patterns (.git, node_modules, etc.) → .gitignore hierarchy → .cbmignore (project-specific, gitignore syntax). Symlinks are always skipped.

See docs/cbmignore.md for the full .cbmignore how-to: syntax, precedence across the ignore layers, and negation semantics.

Configuration

bash
codebase-memory-mcp config list                          # show all settings
codebase-memory-mcp config set auto_index true           # auto-index on session start
codebase-memory-mcp config set auto_index_limit 50000    # max files for auto-index
codebase-memory-mcp config set auto_watch false          # don't register background git watcher (default: true)
codebase-memory-mcp config set doc_candidates false      # skip doc<->code candidate scoring on huge repos (default: auto = on for full, off for moderate/fast)
codebase-memory-mcp config set watcher_enabled false     # stop the watcher thread entirely (default: true)
codebase-memory-mcp config set watch_non_git true        # also poll non-git project roots (default: false)
codebase-memory-mcp config set index_max_files 250000    # optional per-index source-file limit
codebase-memory-mcp config set index_max_source_mb 16384 # optional per-index source-size limit
codebase-memory-mcp config reset auto_index              # reset to default

The two index_max_* settings default to off. Exceeding one fails the complete
index attempt rather than publishing a partial graph; an existing serving index
is preserved. See Index resource limits.

Environment Variables

Variable Default Description
CBM_ALLOWED_ROOT (unset) Confine index_repository to paths within this directory. When set, a repo_path that resolves (after symlink / .. resolution) outside this root is refused, and the same check now applies to the graph UI's POST /api/index route rather than only to the MCP tool. Unset imposes no containment restriction; but see the always-on limits below, which apply whether or not this is set. Useful when the server may be driven by an untrusted caller, e.g. agentic or multi-tenant deployments.
CBM_CACHE_DIR ~/.cache/codebase-memory-mcp Override the database storage directory. All project indexes and config are stored here. One account can use only one canonical cache root at a time; close active CBM sessions/commands before switching it.
CBM_DIAGNOSTICS false Set to 1 or true to enable the shared daemon's periodic snapshot.json and retained trajectory.ndjson below a fresh owner-private directory in the system temp directory. Exact paths are logged by diagnostics.start.
CBM_DOWNLOAD_URL (GitHub releases) Override the download URL for updates. Used for testing or self-hosted deployments.
CBM_LOG_LEVEL role-aware Set the minimum log level. Thin MCP/CLI/hook frontends default to warn; the detached daemon and its supervised index workers default to info so lifecycle and liveness records remain available. Accepted values (case-insensitive): debug, info, warn, error, none; or their numeric equivalents 0 to 4. A physical worker retains INFO liveness records even under a stricter override because its private log drives the supervisor's no-progress timeout. Frontend messages go to that session's stderr; detached daemon events go to ${CBM_CACHE_DIR}/logs/cbm-daemon.log. Stdout is reserved for MCP JSON-RPC.
CBM_WORKERS (detected) Override the parallel-indexing worker count returned by cbm_default_worker_count. Useful inside containers where sysconf(_SC_NPROCESSORS_ONLN) reports host CPUs rather than the cgroup's effective quota. Range 1 to 256; invalid values are ignored with a warning.
CBM_MEM_BUDGET_MB (detected) Override the in-memory graph budget with an explicit cap in MiB, taking precedence over the derived default (ram_fraction × total_RAM, adjusted for available memory and daemon worker caps). Useful on bare-metal hosts without a cgroup limit, or to pin a budget below the cgroup limit so headroom is left for sibling processes. Must be a positive integer; it is clamped to detected total RAM (logged as mem.budget.clamped), and non-numeric or non-positive values are ignored with a warning (mem.budget.env.invalid).
CBM_DUMP_VERIFY_MIN_RATIO 0.5 After indexing, compare persisted SQLite node count to the in-memory dump count. When persisted nodes fall below this fraction of committed nodes (and committed > 50), index_repository returns status:"degraded" instead of silent indexed. Range 0 to 1; set 0 to disable. Invalid values are ignored with a warning.

Environment used by daemon-owned components (such as diagnostics, daemon logging, and process-wide indexing resource limits) is captured from the first daemon-backed session that starts the daemon. Later sessions join that process and cannot replace those values. To change them, close all daemon-backed sessions, update the relevant agent configurations consistently, and restart a session. CBM_ALLOWED_ROOT remains session-specific, a conflicting CBM_CACHE_DIR is rejected, and a one-shot CLI command is not exempt from the rule above: it connects to the coordination daemon like any other session, starting one if none is running. Its own environment becomes the captured daemon-owned environment only when its invocation is the one that starts the daemon; joining an already-running daemon, it inherits that daemon's already-captured values instead.

bash
# Store indexes in a custom directory
export CBM_CACHE_DIR=~/my-projects/cbm-data

Custom File Extensions

The JSON config files support a single key, extra_extensions, which maps additional file extensions to supported languages. Useful for framework-specific extensions like .blade.php (Laravel) or .mjs (ES modules). (For other tunables, see Environment Variables and the config subcommand above.)

Need the full config-file reference? See docs/CONFIGURATION.md.

Per-project (in your repo root):

json
// .codebase-memory.json
{"extra_extensions": {".blade.php": "php", ".mjs": "javascript"}}

Global (applies to all projects):

json
// ~/.config/codebase-memory-mcp/config.json  (or $XDG_CONFIG_HOME/...)
{"extra_extensions": {".twig": "html", ".phtml": "php"}}

Each entry maps an extension (which must start with .) to a language name. Language names are matched case-insensitively. Accepted values (aliases in parentheses) are:

arkts, asciidoc, bash (sh), c, c++ (cpp), c# (csharp), chialisp, clojure, cmake, cobol, common lisp (commonlisp, lisp), css, cuda, dart, diagram, dockerfile, elixir, elm, emacs lisp (emacslisp), erlang, f# (fsharp), form, fortran, glsl, go, graphql, groovy, haskell, hcl (terraform), html, ini, java, javascript, jinja2 (jinja), json, julia, kotlin, lean, lua, magma, makefile, markdown, matlab, meson, mojo, nix, objective-c (objc), ocaml, openapi, pdf, perl, php, plsql, protobuf, python, r, ruby, rust, scala, scss, sql, svelte, swift, toml, tsx, typescript, verilog, vimscript, vue, wolfram, xml, yaml, zig.

Project config overrides global for conflicting extensions. An entry whose language name is unknown, or whose extension does not start with ., is skipped and logged as a warning. Missing config files are ignored.

Persistence

SQLite databases stored at ~/.cache/codebase-memory-mcp/. Persists across restarts (WAL mode, ACID-safe). To reset: rm -rf ~/.cache/codebase-memory-mcp/.

Troubleshooting

Problem Fix
/mcp doesn't show the server Check .mcp.json path is absolute. Restart agent. Test: echo '{}' | /path/to/binary should output JSON.
index_repository fails Pass absolute path: index_repository(repo_path="/absolute/path")
index_repository times out in the client Start it with async: true, then poll with status: true (see Indexing).
trace_path returns 0 results Use search_graph(name_pattern=".*PartialName.*") first to find the exact name.
Queries return wrong project results Add project="name" parameter. Use list_projects to see names.
Binary not found after install Add to PATH: export PATH="$HOME/.local/bin:$PATH"
UI not loading Ensure you ran --ui=true. Check http://localhost:9749.
CBM Lens shows "server unreachable" or "Architecture is not available yet." Check that ?project= names an indexed project exactly; pick it from the project menu, or see list_projects. Then look at System, tab Logs, or ${CBM_CACHE_DIR}/logs/ui.log.

Hybrid LSP

Semantic type resolution beyond tree-sitter.

Tree-sitter alone gives a syntactic AST. That handles naming, structure, and call sites well, but it can't tell you that user.profile.display_name() resolves to Profile.display_name declared three modules away; tree-sitter doesn't track imports, generics, inheritance, or stdlib types.

codebase-memory-mcp ships a lightweight C implementation of language type-resolution algorithms, structurally inspired by and compatible with major language servers (tsserver / typescript-go, pyright, gopls, Roslyn, Eclipse JDT, rust-analyzer), embedded directly into the native executable. No language server process, no per-project setup, no API key. We call this layer Hybrid LSP: it runs alongside tree-sitter on every parse and refines invocation resolution (CALLS / RESOLVED_CALLS) and callable-value resolution (CALL_REFERENCE, with ambiguous values retained as USAGE) using type information, so the resulting graph mirrors what an IDE "Go to Definition" would resolve.

Languages with full Hybrid LSP:

Language What it handles
Python (new in v0.7.0) imports + dotted submodule walks, dataclasses, Self return types, generics, @property, match/case class patterns, SQLAlchemy 2.0 Mapped[T], Pydantic BaseModel, typing.Annotated / ClassVar / Final / InitVar, async/await, classmethod/staticmethod, narrowing (isinstance / is not None / walrus), typing.cast / assert_type, common stdlib (logging, pathlib, json, functools). Target ~95% resolution on idiomatic code.
TypeScript / JavaScript / JSX / TSX generics, JSX component dispatch, JSDoc inference for plain JS, .d.ts declarations, module re-exports, method chaining via return-type propagation, per-file overlay chained to a shared cross-file registry
PHP (new in v0.7.0) namespaces, traits, late-static-binding, PHPDoc inference, parameter binding, return-type inference
C# (new in v0.7.0) global usings, file-scoped namespaces, records (incl. C# 12 primary constructors), LINQ method syntax, async Task<T> / ValueTask<T> unwrap, generic methods, this / base dispatch, var inference, common BCL stdlib
Go (sharpened in v0.7.0) pre-built per-package cross-file registry, generics, embedded structs, interface satisfaction, package-aware import resolution
C / C++ (sharpened in v0.7.0) pre-built per-language cross-file registry shared across C and C++; C side handles macros + typedef chains + header-vs-source linking; C++ side handles templates, namespaces, auto inference, and method resolution via class hierarchy
Java (new in v0.8.0) imports (single-type, on-demand, static), class hierarchies with this / super dispatch, generics, annotations, overload matching by arity and parameter types, lambdas / method references bound to functional interfaces, field-type inference, common JDK stdlib
Kotlin (new in v0.8.0) imports + same-package resolution, classes / objects / companion objects, extension functions, data classes, nullable-type unwrapping, scope functions (let / apply / run / also / with), infix calls, common stdlib
Rust (new in v0.8.0) use declarations + module paths, impl blocks and trait methods, struct fields, generics with trait bounds, operator-trait desugaring, derive-macro method synthesis, UFCS static paths, common std prelude
Perl packages + @ISA / use parent / use base inheritance with method-resolution-order dispatch, SUPER:: calls, Exporter (use Foo qw(...)) import maps, bless / ref($class)||$class self-type inference, qualified Pkg::sub static calls, curated perlfunc + CPAN OOP stdlib; unresolved receivers emit no edge (zero-edge guarantee)

Two-layer architecture:

  1. Tree-sitter pass: fast, syntactic, runs for every one of the 162 languages. Extracts definitions, calls, imports.
  2. Hybrid LSP pass: type-aware, runs above the tree-sitter pass per-language. Refines call edges using the import graph plus a per-file or pre-built cross-file definition registry. Languages without a Hybrid LSP pass yet fall back to textual resolution, with coverage varying by language and construct.

The result is a knowledge graph accurate enough to drive trace_path across packages, inheritance hierarchies, and stdlib calls; without paying for a language server process per project.

Language Support

162 languages, counted by the vendored grammar directories in the parser manifest. Benchmarked against 64 real open-source repositories (78 to 49K nodes):

Tier Score Languages
Excellent (>= 90%) Lua, Kotlin, C++, Perl, Objective-C, Groovy, C, Bash, Zig, Swift, CSS, YAML, TOML, HTML, SCSS, HCL, Dockerfile
Good (75-89%) Python, TypeScript, TSX, Go, Rust, Java, R, Dart, JavaScript, Erlang, Elixir, Scala, Ruby, PHP, C#, SQL
Functional (< 75%) OCaml, Haskell

Also supported (not yet benchmarked): Ada, Agda, Apex, ArkTS, Assembly (NASM), Astro, AWK, Beancount, BibTeX, Bicep, Bitbake, Blade, Cairo, Cap'n Proto, CFML, CFScript, Chialisp, Clojure, CMake, COBOL, Common Lisp, Crystal, CSV, CUDA, D, Devicetree, Diff, .env, Elm, Emacs Lisp, F#, Fennel, Fish, FORM, Fortran, FunC, GDScript, .gitattributes, .gitignore, Gleam, GLSL, GN, Go module, Go template, GraphQL, Hare, HLSL, Hyprlang, INI, ISPC, Janet, Jinja2, JSDoc, JSON, JSON5, Jsonnet, Julia, Just, Kconfig, KDL, Lean 4, Linker Script, Liquid, LLVM IR, Luau, Magma, Makefile, Markdown, MATLAB, Mermaid, Meson, Mojo, Move, Nickel, Nix, ObjectScript (UDL and routines), Odin, Pascal, Pine Script, Pkl, PL/SQL, PO (gettext), Pony, PowerShell, Prisma, .properties, Protobuf, Puppet, PureScript, QML, Racket, Regex, requirements.txt, ReScript, RON, reStructuredText, Scheme, Slang, Smali, Smithy, Solidity, SOQL, SOSL, Squirrel, SSH config, Starlark, Svelte, Sway, SystemVerilog, TableGen, Tcl, Teal, Templ, Thrift, TLA+, Typst, Verilog, VHDL, Vim script, Vue, WGSL, WIT, Wolfram, XML, Zsh.

Architecture

text
src/
  main.c              Entry point (MCP stdio server + CLI + install/update/config)
  daemon/             Per-account session coordination, IPC, lifecycle, shared jobs/watchers
  mcp/                MCP server (17 tools, JSON-RPC 2.0, session detection, auto-index)
  cli/                Install/uninstall/update/config (45 client surfaces, hooks, instructions)
  store/              SQLite graph storage (nodes, edges, traversal, search, Louvain)
  pipeline/           Multi-pass indexing (structure → definitions → calls → HTTP links → config → tests)
  cypher/             Cypher query lexer, parser, planner, executor
  discover/           File discovery (.gitignore, .cbmignore, symlink handling)
  watcher/            Background auto-sync (git polling, adaptive intervals)
  traces/             Runtime trace ingestion
  ui/                 Local HTTP server + embedded CBM Lens frontend
  foundation/         Platform abstractions (threads, filesystem, logging, memory)
graph-ui/             CodeAtlasWeb: React, Monaco, and Three.js browser frontend
internal/cbm/         Vendored tree-sitter grammars (162 languages) + AST extraction engine

Security

Every release is verified through a multi-layer pipeline before publication:

  • VirusTotal: all 24 executable candidates (unstripped, debug-stripped and stripped) across the eight release products are scanned before smoke/soak (clean is preferred and only the single-Microsoft !ml tolerance documented in SECURITY.md may pass; the number of engines that returned a decisive result is recorded as evidence but is VirusTotal fleet availability, not a pass condition). The selected executable is packaged without changing its SHA-256, release notes link the verdict for the exact bytes shipped, and the full per-candidate evidence is published alongside the release as TSVs for anyone auditing the selection. Every distinct object extracted from the shipped containers; install.sh, install.ps1, LICENSE, THIRD_PARTY_NOTICES.md, the MCPB manifest.json and the unpacked UI assets; is then scanned as well, so the full published surface is covered, not just the executables.
  • SLSA Level 3: cryptographic build provenance generated by the trusted GitHub Actions build workflow; verify with gh attestation verify <file> --repo DeusData/codebase-memory-mcp --signer-workflow DeusData/codebase-memory-mcp/.github/workflows/_build.yml
  • Sigstore cosign: keyless signatures on all artifacts; bundles included in every release
  • SHA-256 checksums: checksums.txt published with every release; verified by both install scripts before extraction
  • CodeQL SAST: blocks release pipeline if any open alerts remain
  • No language-runtime dependency chain: the native backend needs no language-runtime environment; UI builds embed the frontend

VirusTotal release scans

The v0.11.0 release notes report clean scans for all eight shipped binaries. Each result is tied to the exact executable hash. The links below show the reports for those binaries; the displayed hashes are shortened for readability.

Binary SHA-256 prefix Reported verdict
linux-amd64 a831cdcafaed7b1f7c23... clean
linux-arm64 7812062c19030de15ae2... clean
linux-amd64-portable ce11c141431aeadd7885... clean
linux-arm64-portable 403d0fab6204e7129167... clean
darwin-amd64 69ca71b4b62fe2336778... clean
darwin-arm64 a67b7ccead5d2ca85205... clean
windows-amd64 7edcd3807ebcfd85ec19... clean
windows-arm64 5615aa31e3cdbe6155e7... clean

The release also publishes candidate scan results, binary provenance, and selection records. For subsequent versions, use the scan evidence linked from the latest release.

Help shape what comes next

The next idea could be yours. We invite the OSS community to bring ideas, tricky integration questions, and feedback to our Discord community. When a direct conversation works better, reach out to DeusData or the project's co-developers there. Let's build what comes next together.

License

MIT


Images README (`docs/images/README.md`)

CBM Lens Beta README captures

These are screenshots and browser recordings of the real application using public source from this repository. The application data and text were not retouched. The GIFs were refreshed on 20 October 2026; the still images preserve the earlier captures from 12–13 October.

High-resolution recordings: 20 October

  • Repository: DeusData/codebase-memory-mcp
  • Source commit: 4cf44147e639ae070ee44d9230244bfb9d9676e0
  • Build: make -f Makefile.cbm cbm-with-ui, with the standard frontend and experimental agent activity disabled
  • Capture: built-in browser screenshots on macOS, English locale, native viewport 1600 × 1000, 12 frames per second
  • Encoding: JPEG screenshot frames converted to GIF with one 256-color palette per recording, no dithering, infinite looping; no upscaling, sharpening or pointer overlays

The v0.0.1 label is the frontend package version, not the backend release version. Standard native releases include Lens; using a source build for these recordings is a capture-provenance detail, not an installation requirement.

Data and interaction

The project codebase-memory-mcp is a clean git archive export of the source commit, indexed in full mode into an isolated local cache. No private repository was used. Because the export has no .git folder, the interface reports "Git unavailable" where applicable.

The index reported 81,190 nodes and 411,072 edges. These counts describe this source scope, not a benchmark or proof of complete repository coverage. The interface retains its coverage and display-limit notices. The clips join recorded interaction segments with pauses shortened between them; their displayed application states and answers are unchanged. Directional pulses represent indexed relationships, not observed execution.

Recording Duration What it shows
lens-overview.gif 12.5 seconds Architecture overview, opening src/foundation, browsing its files, selecting arena.c and reading its source beside the map.
lens-galaxy.gif 12 seconds Expanding outgoing relationships around cbm_arena_alloc, switching to the hierarchy and tracing the two-hop call path cbm_arena_alloc → arena_grow → cbm_alloc.
lens-chat.gif 11.5 seconds arena_grow selected in Galaxy, with caller and callee questions answered directly from the indexed graph.

All three GIFs are 1600 × 1000. Their frames and playback duration were decoded and checked after export. README and website use these same files.

Local chat check

The optional Qwen2.5 Coder 0.5B model was downloaded and loaded through the application's own setup. Automatic explanations were disabled for this recording. Both displayed answers are deterministic graph answers, labelled "Listed from the indexed graph; not generated by the model." The clip shows one incoming CALLS relationship from cbm_arena_alloc and two outgoing CALLS relationships to cbm_alloc and cbm_free, alongside the other indexed relationship types. It does not demonstrate model-generated prose.

Original still images: 12 October

Bernhard Jackiewicz captured the PNG stills on 12 October 2026, from commit 4d9592b5 on main after PR #2068. The build used scripts/build.sh --with-ui; headless Chromium on macOS used English locale, a 1440 × 900 viewport and device scale factor 1. Experimental agent activity was disabled.

The indexed project was a public git archive export of that commit in full mode, with 75,037 nodes and 361,349 edges. The PNG stills are 1440 × 900, except the chat still at 1440 × 1200 so the explanation and answer fit together. These are historical application views, also used as reduced-motion alternatives on the website; they are not extracted from the new GIFs.

Still What it shows
lens-architecture.png The whole repository on the architecture map.
lens-chat.png A local model explanation of cbm_pipeline_pass_doclinks and a caller answer listed from the graph.
lens-system-structure.png Component groups and their dependencies in System structure, with src selected.
lens-behavior.png Possible direct calls of compileCards, with its declaration.

Refreshed still images: 13 October

lens-explore.jpg and lens-galaxy.jpg are unedited browser captures at 1280 × 720 from the locally served CBM Lens Beta UI. The running binary reports dev; its exact build commit is unavailable, so these captures do not certify an exact source revision. The local project is named cbm-triage-main-20260919; the screenshots show public CBM source and indexed relationships.

  • Explore: src/foundation/limits.c, its compact file impact summary and direct dependencies (24 nodes, 26 edges). The UI's freshness, sampling and coverage notices remain visible.
  • Galaxy: cbm_max_file_bytes, both directions, all edge types, two layers (160 nodes, 218 edges).

These counts describe the displayed index snapshot, not a benchmark or proof of complete repository coverage.


BENCHMARK (`docs/BENCHMARK.md`)

Codebase Memory MCP -- v0.3.0 Language Benchmark

Methodology

  • 63 languages (27 programming + 8 config/markup), 12 questions each (4 for config languages)
  • Up to 5 attempts per question with escalating retry strategies
  • Real open-source repos (medium to large: 78--49K nodes)
  • Grading: PASS (1.0) / PARTIAL (0.5) / FAIL (0.0), N/A excluded from denominator
  • Platform: Apple M3 Pro, macOS Darwin 25.3.0
  • Date: 2026-03-01

The 12 Questions

# Category Question Primary MCP Tool
Q1 Indexing Verify index stats (nodes, edges, schema) get_graph_schema
Q2 Discovery Find functions/methods search_graph(label="Function")
Q3 Discovery Find classes/structs search_graph(label="Class")
Q4 Pattern Find by name pattern search_graph(name_pattern="...")
Q5 Code Get code snippet get_code_snippet
Q6 Search Text search in code search_code
Q7 Trace Outbound call trace trace_call_path(direction="outbound")
Q8 Trace Inbound call trace trace_call_path(direction="inbound")
Q9 Graph Cypher CALLS query query_graph
Q10 Enrich Params/returns/properties query_graph
Q11 OOP Inheritance/implements query_graph
Q12 Files List files & directories list_directory

Config/markup languages (HTML, CSS, SCSS, YAML, TOML, HCL, SQL, Dockerfile) run Q1, Q4, Q6, Q12. Other questions marked N/A.


Summary Table

# Language Repo Nodes Edges Score Pct Tier
1 Lua neovim/neovim 23,955 90,247 12/12 100% 1
2 Kotlin ktorio/ktor 25,297 71,498 12/12 100% 1
3 C++ nlohmann/json 5,262 8,681 12/12 100% 1
4 Perl mojolicious/mojo 3,287 4,182 12/12 100% 1
5 Objective-C AFNetworking 1,087 1,348 12/12 100% 1
6 Groovy spockframework/spock 14,081 34,557 12/12 100% 1
7 C jqlang/jq 1,330 3,923 11/11 100% 1
8 Bash bats-core/bats-core 436 479 10/10 100% 1
9 Zig zigtools/zls 2,824 10,230 10/10 100% 1
10 Swift Alamofire/Alamofire 3,631 7,639 10.5/11 95% 1
11 CSS animate-css/animate.css 295 214 4/4 100% 1
12 YAML kubernetes/examples 2,110 1,844 4/4 100% 1
13 TOML rust-lang/cargo 16,773 51,403 4/4 100% 1
14 HTML twbs/bootstrap 2,726 4,135 4/4 100% 1
15 SCSS twbs/bootstrap 2,726 4,135 4/4 100% 1
16 HCL hashicorp/terraform 78 76 4/4 100% 1
17 Dockerfile docker-library/official-images 1,481 1,588 4/4 100% 1
18 Python django/django 49,398 196,022 10.5/12 87% 2
19 TypeScript nestjs/nest 9,063 15,772 10.5/12 87% 2
20 TSX shadcn-ui/ui 29,755 41,883 10.5/12 87% 2
21 Go codebase-memory-mcp (self) 2,259 6,561 10.5/12 87% 2
22 Rust BurntSushi/ripgrep 4,118 6,971 10.5/12 87% 2
23 Java spring-projects/spring-petclinic 660 1,080 10.5/12 87% 2
24 R tidyverse/dplyr 1,618 2,409 10.5/12 87% 2
25 Dart felangel/bloc 5,089 6,430 10.5/12 87% 2
26 JavaScript lodash/lodash 244 405 9.5/11 86% 2
27 Erlang ninenines/cowboy 3,270 9,815 9.5/11 86% 2
28 Elixir elixir-plug/plug 870 865 9.5/11 86% 2
29 Scala playframework/playframework 19,627 43,764 9/12 75% 2
30 Ruby sinatra/sinatra 1,377 1,893 9/12 75% 2
31 PHP laravel/framework 38,644 161,242 9/12 75% 2
32 C# jasontaylordev/CleanArchitecture 1,043 1,632 9/12 75% 2
33 SQL flyway/flyway 4.5/6 75% 4.5/6 75% 2
34 OCaml ocaml/dune 11,691 10,447 8/11 72% 3
35 Haskell PostgREST/postgrest 2,066 2,463 7.5/12 62% 3

Tier Classification

Tier Range Count Languages
Tier 1 -- Excellent >= 90% 17 Lua, Kotlin, C++, Perl, Objective-C, Groovy, C, Bash, Zig, Swift, CSS, YAML, TOML, HTML, SCSS, HCL, Dockerfile
Tier 2 -- Good 75--89% 16 Python, TypeScript, TSX, Go, Rust, Java, R, Dart, JavaScript, Erlang, Elixir, Scala, Ruby, PHP, C#, SQL
Tier 3 -- Functional < 75% 2 OCaml (72%), Haskell (62%)

Per-Language Results

Python (django/django)

Project: django-python | Repo: /tmp/lang-bench/django-python
Nodes: 49,398 | Edges: 196,022

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 49,398 nodes, 196,022 edges, 12 labels, 20 rel types
Q2 Find Functions PASS 1/5 search_graph(Function) render_to_string (1458 in), skipUnlessDBFeature, call_command. 1005 total
Q3 Find Classes PASS 1/5 search_graph(Class) ModelAdmin (511 in), ValidationError (415 in), ImproperlyConfigured. 1005 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) SimpleTestCase, TestCase, SchemaTests. 22,135 total
Q5 Code Snippet PASS 1/5 get_code_snippet render_to_string: 11 lines from django/template/loader.py:52-62
Q6 Text Search PASS 1/5 search_code(TODO|FIXME) 5 matches: FIXME in admin/checks.py, TODO in admin/options.py
Q7 Outbound Trace PASS 1/5 trace_call_path(outbound) 21 edges, 3 hops: render_to_string -> render, get_template, select_template
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) 3 callers: test_include, test_include_state, test_include_cache
Q9 Cypher CALLS PASS 3/5 query_graph with project param First 2 attempts needed project param adjustment
Q10 Properties PARTIAL 3/5 query_graph(properties) Functions returned but all properties null
Q11 Inheritance PASS 3/5 query_graph(INHERITS) 200 rows (capped): LazySettings, SimpleAdminConfig, ModelAdmin
Q12 List Directory PASS 1/5 list_directory 21 entries: django/, docs/, tests/, extras/, pyproject.toml

Score: 10.5/12 (87%)

JavaScript (lodash/lodash)

Project: lodash-js | Repo: /tmp/lang-bench/lodash-js
Nodes: 244 | Edges: 405

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 244 nodes, 405 edges, 7 labels
Q2 Find Functions PASS 1/5 search_graph(Function) build (7 variants), baseConvert, Hash. 36 total
Q3 Find Classes PASS 2/5 search_graph(Module) No Class nodes (functional lib). Module fallback: 39 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) browser-testing.yml, markdown-doctest-setup.js. 10 total
Q5 Code Snippet PASS 1/5 get_code_snippet build: 57 lines from lib/main/build-site.js
Q6 Text Search PASS 1/5 search_code(TODO) 1 match: TODO in .github/workflows/ci-bun.yml
Q7 Outbound Trace PASS 2/5 trace_call_path(baseConvert) 2 edges: baseAry, self-recursive
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) 1 caller: build-dist.js module
Q9 Cypher CALLS PASS 1/5 query_graph 5 rows: minify->minify, build-dist.js->build
Q10 Properties PARTIAL 1/5 query_graph(properties) Functions returned but properties null
Q11 Inheritance N/A -- -- No classes/inheritance (pure functional library)
Q12 List Directory PASS 1/5 list_directory 19 entries: dist/, fp/, lib/, test/, lodash.js

Score: 9.5/11 (86%)

TypeScript (nestjs/nest)

Project: nestjs-ts | Repo: /tmp/lang-bench/nestjs-ts
Nodes: 9,063 | Edges: 15,772

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 9,063 nodes, 15,772 edges; 14 labels
Q2 Find Functions PASS 1/5 search_graph(Function) Injectable (in=242), Module (in=209), Controller (in=117). 292 total
Q3 Find Classes PASS 1/5 search_graph(Class) Post (in=128), FastifyAdapter, Module, InstanceWrapper. 1005+ total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) TestingModuleBuilder, TestController, test folders. 142 results
Q5 Code Snippet PASS 1/5 get_code_snippet Injectable: 6 lines from common/decorators/core/injectable.decorator.ts
Q6 Text Search PASS 1/5 search_code(TODO) 2 matches in express.spec.ts, gulp/tasks/samples.ts
Q7 Outbound Trace PASS 2/5 trace_call_path(Module) First try Injectable had 0 outbound; Module: 4 edges
Q8 Inbound Trace PASS 1/5 trace_call_path(Injectable) 242 callers across services, interceptors, pipes, guards
Q9 Cypher CALLS PASS 1/5 query_graph 5 rows: gulpfile.js->register, bar.service->Injectable
Q10 Properties PARTIAL 1/5 query_graph(properties) Properties null
Q11 Inheritance PASS 1/5 query_graph(INHERITS) 200 rows: AuthGuard, BadGatewayException, ClientGrpcProxy
Q12 List Directory PASS 1/5 list_directory 22 entries: packages/, integration/, sample/, tools/

Score: 10.5/12 (87%)

TSX (shadcn-ui/ui)

Project: shadcn-tsx | Repo: /tmp/lang-bench/shadcn-tsx
Nodes: 29,755 | Edges: 41,883

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 29,755 nodes, 41,883 edges; 13 labels
Q2 Find Functions PASS 1/5 search_graph(Function) cn (in=558), IconPlaceholder (in=362). 7,424 total
Q3 Find Classes PASS 1/5 search_graph(Class) 18 classes: RegistryError, ComponentErrorBoundary
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 146 results: search.test.ts, registries.test.ts
Q5 Code Snippet PASS 1/5 get_code_snippet(cn) 3 lines from apps/v4/examples/base/lib/utils.ts:4-6
Q6 Text Search PASS 1/5 search_code(TODO) 5 matches in registry.ts, source.config.ts
Q7 Outbound Trace PASS 2/5 trace_call_path(Button) Button->cn (1 edge)
Q8 Inbound Trace PASS 1/5 trace_call_path(cn) 558 callers (result saved to file)
Q9 Cypher CALLS PASS 1/5 query_graph BlocksPage->getAllBlockIds, ChartPage->getActiveStyle
Q10 Properties PARTIAL 1/5 query_graph(properties) Properties null
Q11 Inheritance PASS 1/5 query_graph(INHERITS) 14 rows: RegistryNotFoundError, ConfigMissingError
Q12 List Directory PASS 1/5 list_directory 17 entries: apps/, packages/, templates/

Score: 10.5/12 (87%)

Go (codebase-memory-mcp)

Project: codebase-memory-mcp | Repo: self
Nodes: 2,259 | Edges: 6,561

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 2,259 nodes, 6,561 edges; 13 labels
Q2 Find Functions PASS 1/5 search_graph(Function) OpenMemory (in=136), NodeText (in=79), Parse (in=35). 918 total
Q3 Find Classes PASS 1/5 search_graph(Class) Node (in=243), LanguageSpec, FileInfo, Edge. 91 structs
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 413 results: cypher_test.go, langparity_test.go
Q5 Code Snippet PASS 1/5 get_code_snippet OpenMemory: 16 lines from internal/store/store.go:113-128
Q6 Text Search PASS 1/5 search_code(TODO) 4 matches in tools.go, langparity_test.go
Q7 Outbound Trace PASS 1/5 trace_call_path(outbound) 11 edges: Open, Close, initSchema, cacheDir
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) 68 callers: test files across store/pipeline/httplink
Q9 Cypher CALLS PASS 1/5 query_graph printAST->printAST, main->Parse, TestLexBasicQuery->Lex
Q10 Properties PARTIAL 1/5 query_graph(properties) Properties null
Q11 Inheritance PASS 1/5 query_graph(IMPLEMENTS) 3 rows: RelPattern, FilterWhere, fusedExpandMarker
Q12 List Directory PASS 1/5 list_directory 15 entries: cmd/, internal/, scripts/, go.mod

Score: 10.5/12 (87%)

Rust (BurntSushi/ripgrep)

Project: ripgrep-rust | Repo: /tmp/lang-bench/ripgrep-rust
Nodes: 4,118 | Edges: 6,971

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 689 Functions, 2039 Methods, 294 Classes, 70 Enums
Q2 Find Functions PASS 1/5 search_graph(Function) matcher (in=133), args (119), printer_contents (72). 689 total
Q3 Find Classes PASS 1/5 search_graph(Class) HiArgs (out=93), LowArgs (out=74), Debug (in=54). 294 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) test method (in=524), SearcherTester, TestCommand. 134 results
Q5 Code Snippet PASS 1/5 get_code_snippet matcher() from test_matcher.rs:8-10
Q6 Text Search PASS 1/5 search_code(TODO) 3 TODOs in globset, gitignore, overrides
Q7 Outbound Trace PASS 1/5 trace_call_path(search) 12 callees: quit_after_match, sort, haystack_builder, search_worker
Q8 Inbound Trace PASS 1/5 trace_call_path(search) Called by run, main, search_parallel, files_parallel
Q9 Cypher CALLS PASS 1/5 query_graph main->set_git_revision_hash, main->set_windows_exe_options
Q10 Properties PASS 1/5 query_graph(properties) 5 functions returned
Q11 Inheritance PARTIAL 1/5 query_graph(INHERITS) 1 result: NoCaptures. Rust traits not modeled as inheritance
Q12 List Directory PASS 1/5 list_directory 20 entries: crates/, tests/, benchsuite/, Cargo.toml

Score: 10.5/12 (87%)

Java (spring-projects/spring-petclinic)

Project: spring-petclinic-java | Repo: /tmp/lang-bench/spring-petclinic-java
Nodes: 660 | Edges: 1,080

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 167 Methods, 39 Classes, 30 Routes, 3 Interfaces
Q2 Find Functions PASS 1/5 search_graph(Method) getPet (in=12,out=6), testValidate (out=16). 167 total
Q3 Find Classes PASS 1/5 search_graph(Class) OwnerControllerTests, PetController, Owner. 39 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 97 test-related nodes
Q5 Code Snippet PASS 1/5 get_code_snippet getPet() from Owner.java:135-145
Q6 Text Search PARTIAL 2/5 search_code(error) TODO/FIXME returned 0; "error" found 3 matches
Q7 Outbound Trace PASS 1/5 trace_call_path(outbound) getPet calls getId, isNew, getName, getPets
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) Called by loadPetWithVisit, findPet, processCreationForm, test methods
Q9 Cypher CALLS PASS 1/5 query_graph toString->getName, addPet->isNew, getPet->getPets
Q10 Properties PASS 1/5 query_graph(properties) main, registerHints, getId, setId, isNew
Q11 Inheritance PASS 1/5 query_graph(INHERITS) 8 results: NamedEntity, Person, Owner, Pet, Vet
Q12 List Directory PASS 1/5 list_directory 13 entries: src/, gradle/, pom.xml, docker-compose.yml

Score: 10.5/12 (87%)

C++ (nlohmann/json)

Project: nlohmann-json-cpp | Repo: /tmp/lang-bench/nlohmann-json-cpp
Nodes: 5,262 | Edges: 8,681

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 2,456 Functions, 649 Methods, 603 Macros, 153 Classes
Q2 Find Functions PASS 1/5 search_graph(Function) result (in=99), push_back (in=42), CAPTURE (in=35)
Q3 Find Classes PASS 1/5 search_graph(Class) Fuzzer (out=54), MutationDispatcher (out=36). 153 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 234 test-related nodes
Q5 Code Snippet PASS 1/5 get_code_snippet result from unit-bjdata.cpp:2935-2936
Q6 Text Search PASS 1/5 search_code(TODO) 3 TODOs in binary_reader.hpp, json.hpp
Q7 Outbound Trace PASS 1/5 trace_call_path(push_back) Recursive call through json_pointer chain
Q8 Inbound Trace PASS 1/5 trace_call_path(push_back) 23 callers: main, sax_event_consumer, lexer, binary_reader
Q9 Cypher CALLS PASS 1/5 query_graph main->at, main->back
Q10 Properties PASS 1/5 query_graph(properties) 5 functions returned
Q11 Inheritance PASS 1/5 query_graph(INHERITS) 21 results: parse_error, invalid_iterator, type_error, out_of_range
Q12 List Directory PASS 1/5 list_directory 20 entries: include/, tests/, docs/, CMakeLists.txt

Score: 12/12 (100%)

C# (jasontaylordev/CleanArchitecture)

Project: clean-architecture-csharp | Repo: /tmp/lang-bench/clean-architecture-csharp
Nodes: 1,043 | Edges: 1,632

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 303 Methods, 123 Classes, 5 Interfaces
Q2 Find Functions PASS 1/5 search_graph(Method) SendAsync (in=17), PaginatedList (in=11). 303 total
Q3 Find Classes PASS 1/5 search_graph(Class) TodoItems (in=18), TodoLists (in=18), BaseTestFixture. 123 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 85 test-related nodes
Q5 Code Snippet PASS 1/5 get_code_snippet SendAsync() from Testing.cs:38-45
Q6 Text Search PARTIAL 2/5 search_code(error) TODO/FIXME=0; "error" found 3 matches
Q7 Outbound Trace PARTIAL 1/5 trace_call_path(outbound) SendAsync has 0 outbound (calls external ISender.Send)
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) 16 callers: test methods
Q9 Cypher CALLS PASS 1/5 query_graph NotEqualOperator->EqualOperator, GetHashCode->GetEqualityComponents
Q10 Properties PASS 1/5 query_graph(properties) AuthorizationBehaviour, Handle, LoggingBehaviour
Q11 Inheritance PASS 1/5 query_graph(INHERITS) 29 results: BaseAuditableEntity, TodoItem, TodoList
Q12 List Directory PASS 1/5 list_directory 16 entries: src/, tests/, infra/, templates/

Score: 9/12 (75%)

PHP (laravel/framework)

Project: laravel-framework-php | Repo: /tmp/lang-bench/laravel-framework-php
Nodes: 39,767 | Edges: 148,440 (post-Phase-4–5 PHP-LSP; was 196,979 baseline, –25%)
CALLS edges: 47,341 (was ~83k baseline — 43% reduction in name-fallback misroutes)
Tests: 278 PHP-LSP unit tests, all passing (total project: 3,091 / 0 failed)
LSP module size: ~3,700 lines C resolver + ~700 lines stdlib + ~5,500 lines tests = ~9,900 LoC

PHP runs through a Light Semantic Pass (internal/cbm/lsp/php_lsp.c,
~3,500 lines) that approaches phpactor-grade type resolution while
staying in-process and PHP-runtime-free. Phase 4 capabilities:

  • Receiver-type tracking with full ancestor chain walk (cycle-detected,
    bounded to 32 hops)
  • Namespace + use-clause resolution (class, function, const), incl.
    as aliasing in both wrapped and bare forms
  • PHPDoc @var, @param, @property, @method parsing — generics
    (Collection, array) supported
  • Type narrowing: instanceof, is_string/int/array/..., assert(...)
    sequential narrowing, negative narrowing on early return / throw
  • Property type tracking: typed declarations, constructor property
    promotion, constructor-body inference ($this->bar = $bar)
  • Trait flattening with as aliasing
  • Late static binding chain depth, self/parent/static distinction
  • Match / ternary / clone / cast result-type evaluation
  • Foreach element-type propagation through array<T> and Iterator
  • Magic methods (__call/__callStatic → facade dispatch)
  • Stdlib coverage: SPL, PSR, DateTime, Throwable, Closure, plus
    Eloquent Builder/Model/Collection chains, Symfony HttpFoundation,
    Carbon date methods, PSR-7 with-builders

Attribution correctness fix (the primary metric, see
docs/PHP_LSP_PRE_FLIGHT.md §6):

  • ConfiguresPrompts.configurePrompts → helpers.value misroute (3
    call sites): fixed — those typed-receiver $prompt->value() calls
    no longer route to the global helper. Verified: 0 misroute edges.
  • Roughly 27 500 fewer total edges, primarily from blocking
    name-fallback edges where the receiver was a vendor (un-indexed) type
    — better precision at the cost of some recall, see notes below.
Q# Question Grade Approach Notes
Q1 Index Stats PASS get_graph_schema unchanged
Q2 Find Functions PASS search_graph(Function) unchanged
Q3 Find Classes PASS search_graph(Class) unchanged
Q4 Pattern Search PASS search_graph(name_pattern=test) unchanged
Q5 Code Snippet PASS get_code_snippet unchanged
Q6 Text Search PARTIAL search_code search-backend issue, separate ticket — see PHP_LSP_PRE_FLIGHT §3
Q7 Outbound Trace PASS trace_call_path(outbound) unchanged
Q8 Inbound Trace PARTIAL trace_call_path(inbound) tool-side disambiguation, separate ticket — see PHP_LSP_PRE_FLIGHT §4.1
Q9 Cypher CALLS PASS query_graph unchanged
Q10 Properties PASS query_graph(properties) unchanged
Q11 Inheritance PASS query_graph(INHERITS) unchanged
Q12 List Directory PASS list_directory unchanged

Score: 10/12 (83%) — the two remaining PARTIALs are non-LSP issues
tracked separately. PHP-LSP delivered the underlying graph-correctness
win (collide-set attribution); benchmark Tier 1 (≥ 90 %) requires the
search-recall and trace-disambiguation tickets to ship as well.

Note on edge-count drop: when the LSP knows the receiver is typed
but the receiver class is not indexed (e.g., Composer vendor types like
Laravel\\Prompts\\Prompt), it emits a synthetic
php_method_typed_unindexed resolution that the bridge uses to
suppress the unified extractor's name-based fallback. This trades
recall (no edge instead of a guess) for precision (no wrong edge). The
pre-flight argued this is the right trade for graph correctness.

Test coverage: 100 unit tests in tests/test_php_lsp.c, all
passing. Total project tests: 2913 / 0 failed.

Lua (neovim/neovim)

Project: neovim-lua | Repo: /tmp/lang-bench/neovim-lua
Nodes: 23,955 | Edges: 90,247

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 12,311 Functions, 7,587 Variables, 1,157 Files, 399 Methods, 196 Classes
Q2 Find Functions PASS 1/5 search_graph(Function) assert (in=825), vim.fn.bufnr (in=390), vim.fn.count (in=228)
Q3 Find Classes PASS 1/5 search_graph(Class) cmdmod (in=84), file_buffer (out=80), TUIData (out=59). 196 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 198 test-related nodes
Q5 Code Snippet PASS 1/5 get_code_snippet vim.validate: 21 lines from runtime/lua/vim/_core/shared.lua
Q6 Text Search PASS 1/5 search_code(TODO) Found across build.zig, test.yml
Q7 Outbound Trace PASS 1/5 trace_call_path(outbound) 5 callees: vim.deprecate, is_valid, validate_spec. Depth 2: 15 more
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) 129K chars result (saved to file). Massive inbound
Q9 Cypher CALLS PASS 1/5 query_graph f_rpcstart->check_secure, f_termopen->f_jobstart
Q10 Properties PASS 1/5 search_graph(include_connected) assert: in=825/out=2, connected to M.error, main
Q11 Inheritance PASS 1/5 query_graph(INHERITS) UGridPrinter inherits object
Q12 List Directory PASS 1/5 list_directory 22 entries: src/, runtime/, test/, cmake/, deps/

Score: 12/12 (100%)

Scala (playframework/playframework)

Project: playframework-scala | Repo: /tmp/lang-bench/playframework-scala
Nodes: 19,627 | Edges: 43,764

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 8,018 Methods, 2,924 Variables, 2,007 Classes, 309 Interfaces
Q2 Find Functions PASS 1/5 search_graph(Function) app (in=48), queryString (in=14), json (in=9). 71 total
Q3 Find Classes PASS 1/5 search_graph(Class) Http (out=135), AbstractController (in=81), Controller (in=68)
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 636 test-related nodes
Q5 Code Snippet PASS 1/5 get_code_snippet app: 1-line abstract def from play/api/test/package.scala
Q6 Text Search PASS 1/5 search_code(TODO) FIXME in CachedSpec, TODO in CryptoComponents
Q7 Outbound Trace PARTIAL 1/5 trace_call_path(outbound) queryString: abstract method, no outbound calls
Q8 Inbound Trace PARTIAL 1/5 trace_call_path(inbound) queryString: no inbound calls detected
Q9 Cypher CALLS PASS 1/5 query_graph fromContent->fromString, waitForCancel->waitForKey
Q10 Properties PASS 1/5 search_graph(include_connected) ok: in=133/out=6, data: in=131/out=1
Q11 Inheritance PASS 1/5 query_graph(INHERITS) 200 rows (capped): DefaultApplication, Controller, many *Spec
Q12 List Directory PASS 1/5 list_directory 14 entries: core/, dev-mode/, documentation/, testkit/

Score: 9/12 (75%)

Kotlin (ktorio/ktor)

Project: ktor-kotlin | Repo: /tmp/lang-bench/ktor-kotlin
Nodes: 25,297 | Edges: 71,498

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 8,485 Methods, 2,865 Functions, 2,572 Classes, 1,921 Routes
Q2 Find Functions PASS 1/5 search_graph(Function) request (in=216), content (in=186), http (in=171). 1010+ total
Q3 Find Classes PASS 1/5 search_graph(Class) ContentType (in=224), HttpRequestBuilder (in=208). 1010+ total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) testApplication (in=1504), runTest (in=359). 5,015 total
Q5 Code Snippet PASS 1/5 get_code_snippet request: 2 lines from ktor-client-core
Q6 Text Search PASS 1/5 search_code(TODO) "TODO: Remove when PR fixing this file will be merged"
Q7 Outbound Trace PASS 1/5 trace_call_path(HttpClient) 4 callees: create, apply, invokeOnCompletion
Q8 Inbound Trace PASS 1/5 trace_call_path(HttpClient) 3 callers: config, testPluginInstalledTwice
Q9 Cypher CALLS PASS 1/5 query_graph getNative->error, matches->toLowerCasePreservingASCIIRules
Q10 Properties PASS 1/5 search_graph(include_connected) request: in=216/out=10, content: in=186/out=6
Q11 Inheritance PASS 1/5 query_graph(INHERITS) 200 rows (capped): HttpClientEngineBase, many *Engine
Q12 List Directory PASS 1/5 list_directory 39 entries: ktor-client/, ktor-server/, ktor-http/

Score: 12/12 (100%)

Ruby (sinatra/sinatra)

Project: sinatra-ruby | Repo: /tmp/lang-bench/sinatra-ruby
Nodes: 1,377 | Edges: 1,893

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 786 Methods, 127 Classes, 37 Functions
Q2 Find Functions PASS 1/5 search_graph(Function) setup (in=11), route_def, write_app_file. 37 total
Q3 Find Classes PASS 1/5 search_graph(Class) Sinatra (out=172), OkJson (out=31). 127 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 183 test-related nodes
Q5 Code Snippet PASS 1/5 get_code_snippet setup: 4 lines from test/markdown_test.rb
Q6 Text Search PASS 1/5 search_code(TODO) 2 matches: base.rb, content_for_spec.rb
Q7 Outbound Trace PARTIAL 1/5 trace_call_path(outbound) setup in Minitest: no outbound calls (block-based)
Q8 Inbound Trace PARTIAL 1/5 trace_call_path(inbound) No inbound calls for this method
Q9 Cypher CALLS PASS 1/5 query_graph status_app->mock_app, mock_app->new, setup_blocks->each
Q10 Properties PASS 1/5 search_graph(include_connected) it: in=38/out=3, get: in=23/out=1
Q11 Inheritance PASS 1/5 query_graph(INHERITS) 2 results: BError->AError, ErubiTest->ERBTest
Q12 List Directory PASS 1/5 list_directory 18 entries: lib/, test/, examples/

Score: 9/12 (75%)

C (jqlang/jq)

Project: jq-c | Repo: /tmp/lang-bench/jq-c
Nodes: 1,330 | Edges: 3,923

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 764 Functions, 159 Fields, 75 Variables, 45 Classes (structs)
Q2 Find Functions PASS 1/5 search_graph(Function) jv_free (in=163), jv_get_kind (in=97), yyparse (out=51). 764 total
Q3 Find Classes PASS 1/5 search_graph(Class) jq_state (out=25), yyguts_t (out=24), jv_parser (out=21). 45 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 25 test-related nodes: run_jq_tests, jv_test, LLVMFuzzerTestOneInput
Q5 Code Snippet PASS 1/5 get_code_snippet jv_free: 1-line declaration from src/jv.h:55
Q6 Text Search PASS 1/5 search_code(TODO) 7 matches: build_manpage.py, execute.c, jv_parse.c
Q7 Outbound Trace PASS 1/5 trace_call_path(outbound) 5 callees: jvp_invalid/number/array/string/object_free. Depth 2: jvp_refcnt_dec
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) 87K chars (saved to file). Massive caller tree
Q9 Cypher CALLS PASS 1/5 query_graph f_negate->type_error, f_json_parse->type_error, f_length->type_error
Q10 Properties PASS 1/5 search_graph(include_connected) jv_free: in=163/out=6, jv_get_kind: in=97/out=1
Q11 Inheritance N/A -- -- C has no class inheritance
Q12 List Directory PASS 1/5 list_directory 23 entries: src/, tests/, docs/, build/

Score: 11/11 (100%)

Bash (bats-core/bats-core)

Project: bats-bash | Repo: /tmp/lang-bench/bats-bash
Nodes: 436 | Edges: 479

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 156 Functions, 65 Variables, 66 Modules
Q2 Find Functions PASS 1/5 search_graph(Function) bats_print_stack_trace, main, run, bats_generate_warning. 156 total
Q3 Find Classes N/A -- -- Bash has no classes
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=run) 6 matches: run, bats_semaphore_run, reentrant_run
Q5 Code Snippet PASS 1/5 get_code_snippet run: 142-line function source (310-451)
Q6 Text Search PASS 1/5 search_code(TODO) 2 TODOs in semaphore.bash, tracing.bash
Q7 Outbound Trace PASS 1/5 trace_call_path(outbound) 6 callees: bats_quote_code, bats_format_file_line_reference
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) 3 callers across 2 hops
Q9 Cypher CALLS PASS 1/5 query_graph 10 rows of function call pairs
Q10 Properties PASS 1/5 search_graph(Variable) 65 variables
Q11 Inheritance N/A -- -- Bash has no class hierarchy
Q12 List Directory PASS 1/5 list_directory 66 modules found

Score: 10/10 (100%)

Zig (zigtools/zls)

Project: zls-zig | Repo: /tmp/lang-bench/zls-zig
Nodes: 2,824 | Edges: 10,230

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 1,010+ Functions, 1,005 Variables, 109 Modules
Q2 Find Functions PASS 1/5 search_graph(Function) std.ArrayList, trace, empty, resolveTypeOfNodeUncached. 1010+ total
Q3 Find Classes N/A -- -- Zig uses structs, not classes
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=resolve) 57 matches: resolve, resolveTypeOfNodeUncached
Q5 Code Snippet PASS 1/5 get_code_snippet trace: 19-line function showing tracy integration
Q6 Text Search PASS 1/5 search_code(TODO) 3+ TODOs in DiagnosticsCollection.zig, DocumentStore.zig
Q7 Outbound Trace PASS 1/5 trace_call_path(outbound) 2 callees: ___tracy_emit_zone_begin, _callstack
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) 94 callers: processMessage, create, handleConfiguration
Q9 Cypher CALLS PASS 1/5 query_graph main->createRequestBody, build->getVersion
Q10 Properties PASS 1/5 search_graph(Variable) 1,005 variables
Q11 Inheritance N/A -- -- Zig has no inheritance
Q12 List Directory PASS 1/5 list_directory 109 modules

Score: 10/10 (100%)

Elixir (elixir-plug/plug)

Project: plug-elixir | Repo: /tmp/lang-bench/plug-elixir
Nodes: 870 | Edges: 865

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 554 Functions, 129 Classes, 80 Modules
Q2 Find Functions PASS 1/5 search_graph(Function) parse_before_param, parse_hd_before_value. 554 total
Q3 Find Classes PASS 1/5 search_graph(Class) Plug.Conn, Plug.Debugger, Plug.CSRFProtection. 129 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=parse) 29 matches
Q5 Code Snippet PASS 1/5 get_code_snippet match: 3-line macro source
Q6 Text Search PASS 1/5 search_code(TODO) 3 TODOs in adapters/test/conn.ex, ssl.ex
Q7 Outbound Trace PASS 1/5 trace_call_path(outbound) 6 edges across 2 hops
Q8 Inbound Trace PARTIAL 1/5 trace_call_path(inbound) 0 callers (entry point function)
Q9 Cypher CALLS PASS 1/5 query_graph parse_headers->match, skip_preamble->before_parse_headers
Q10 Properties PASS 1/5 search_graph(Variable) 6 variables
Q11 Inheritance N/A -- -- Elixir uses protocols/behaviours
Q12 List Directory PASS 1/5 list_directory 80 modules

Score: 9.5/11 (86%)

Haskell (PostgREST/postgrest)

Project: postgrest-haskell | Repo: /tmp/lang-bench/postgrest-haskell
Nodes: 2,066 | Edges: 2,463

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 746 Functions, 148 Classes, 655 Variables
Q2 Find Functions PASS 1/5 search_graph(Function) defaultenv, jwtauthheader, headers. 746 total
Q3 Find Classes PASS 1/5 search_graph(Class) Thread, PostgrestTimedOut, ErrorBody. 148 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=action) 4 matches: actionResult, actionPlan, actionResponse
Q5 Code Snippet PASS 1/5 get_code_snippet actionPlan: 7-line Haskell source with pattern matching
Q6 Text Search PASS 1/5 search_code(TODO) 3+ TODOs in workflow files
Q7 Outbound Trace PARTIAL 1/5 trace_call_path(outbound) 0 callees (function composition not traced as CALLS)
Q8 Inbound Trace PARTIAL 1/5 trace_call_path(inbound) 0 callers (Haskell call patterns not fully traced)
Q9 Cypher CALLS PASS 1/5 query_graph generate_jwt->encode, run_command->run, main->generate_jwt
Q10 Properties PASS 1/5 search_graph(Variable) 655 variables
Q11 Inheritance PARTIAL 1/5 query_graph(INHERITS) 1 result: Thread (Python test helper). Typeclasses not modeled
Q12 List Directory PASS 1/5 list_directory 184 modules

Score: 7.5/12 (62%)

OCaml (ocaml/dune)

Project: dune-ocaml | Repo: /tmp/lang-bench/dune-ocaml
Nodes: 11,691 | Edges: 10,447

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 1,010+ Functions, 12 Classes, 1,005 Modules
Q2 Find Functions PASS 1/5 search_graph(Function) value, unit, append_files_in_dir_if_not_empty. 1010+ total
Q3 Find Classes PASS 1/5 search_graph(Class) fsenv, watch, CramLexer, DuneLexer. 12 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=build) 111 matches
Q5 Code Snippet PASS 1/5 get_code_snippet value: 4-line OCaml source with match expression
Q6 Text Search PASS 1/5 search_code(TODO) 3+ TODOs in workflow.yml, client.ml
Q7 Outbound Trace PARTIAL 1/5 trace_call_path(outbound) 0 callees (pure function)
Q8 Inbound Trace PARTIAL 1/5 trace_call_path(inbound) 0 callers (OCaml call analysis limited)
Q9 Cypher CALLS PASS 1/5 query_graph dune_trace_write->write, add_watch->isprefix
Q10 Properties PASS 1/5 search_graph(Variable) 103 variables
Q11 Inheritance N/A -- -- OCaml uses modules/functors
Q12 List Directory PASS 1/5 list_directory 1,005 modules

Score: 8/11 (72%)

Erlang (ninenines/cowboy)

Project: cowboy-erlang | Repo: /tmp/lang-bench/cowboy-erlang
Nodes: 3,270 | Edges: 9,815

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 3,270 nodes, 9,815 edges; 8 labels
Q2 Find Functions PASS 1/5 search_graph(Function) send (in=379), gun_open (in=278), do_get (in=103). 2,739 total
Q3 Find Classes PASS 2/5 search_graph(Module) No Class nodes; Module fallback: 193 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 29 results
Q5 Code Snippet PASS 1/5 get_code_snippet send: 2 lines from src/cowboy_http3.erl
Q6 Text Search PASS 2/5 search_code(error) TODO/FIXME=0; "error" found 5 matches
Q7 Outbound Trace PASS 1/5 trace_call_path(outbound) send->send (self-recursive)
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) 379 callers (saved to file)
Q9 Cypher CALLS PASS 1/5 query_graph start->compile, start->start_clear, stop->stop_listener
Q10 Properties PARTIAL 1/5 query_graph(properties) Properties null
Q11 Inheritance N/A -- -- Erlang has no class inheritance
Q12 List Directory PASS 1/5 list_directory 12 entries: src/, test/, examples/

Score: 9.5/11 (86%)

R (tidyverse/dplyr)

Project: dplyr-r | Repo: /tmp/lang-bench/dplyr-r
Nodes: 1,618 | Edges: 2,409

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 1,027 Functions, 13 Classes, 168 test nodes
Q2 Find Functions PASS 1/5 search_graph(Function) arrange.data.frame, group_by, filter, mutate, select. 1,027 total
Q3 Find Classes PASS 1/5 search_graph(Class) rlang_api_ptrs_t, Expander, FactorExpander. 13 total (C++ structs)
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 168 results: testthat.R, test-across.R
Q5 Code Snippet PASS 1/5 get_code_snippet arrange.data.frame: 10 lines from R/arrange.R:88-97
Q6 Text Search PASS 1/5 search_code(TODO) 5 matches in R/across.R about deprecation
Q7 Outbound Trace PASS 3/5 trace_call_path(outbound) C++ func found 2 edges (grouped/ungrouped)
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) 3 callers: test files
Q9 Cypher CALLS PASS 1/5 query_graph context.R->new_environment, starwars.R->get_all/lookup
Q10 Properties PARTIAL 1/5 query_graph(properties) Properties null
Q11 Inheritance PASS 1/5 query_graph(INHERITS) 3 rows: FactorExpander, VectorExpander, LeafExpander inherit Expander
Q12 List Directory PASS 1/5 list_directory 21 entries: R/, src/, tests/, vignettes/

Score: 10.5/12 (87%)

Objective-C (AFNetworking/AFNetworking)

Project: afnetworking-objc | Repo: /tmp/lang-bench/afnetworking-objc
Nodes: 1,087 | Edges: 1,348

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 search_graph(File) 89 files indexed
Q2 Find Functions PASS 1/5 search_graph(Function) AFNetworkReachabilityCallback, data, destination. 114 total
Q3 Find Classes PASS 1/5 search_graph(Class) AFSecurityPolicyTests, AFHTTPRequestSerializer. 80 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=Session) 26 results
Q5 Code Snippet PASS 1/5 get_code_snippet AFNetworkReachabilityCallback: 3-line function
Q6 Text Search PASS 1/5 search_code(TODO) 1 match in AFImageDownloader.m
Q7 Outbound Trace PASS 1/5 trace_call_path(outbound) 2 edges: Callback->PostReachabilityStatusChange->StatusForFlags
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) Callers traced
Q9 Cypher CALLS PASS 1/5 query_graph Function->Function edges present
Q10 Properties PASS 1/5 search_graph(Variable) 32 variables
Q11 Inheritance PASS 1/5 search_graph(Method) 632 methods extracted
Q12 List Directory PASS 1/5 list_directory 17 entries: AFNetworking/, Tests/, UIKit+AFNetworking/

Score: 12/12 (100%)

Swift (Alamofire/Alamofire)

Project: alamofire-swift | Repo: /tmp/lang-bench/alamofire-swift
Nodes: 3,631 | Edges: 7,639

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 search_graph(File) 470 files indexed
Q2 Find Functions PASS 1/5 search_graph(Function) write, read, responseStream, download. 117 total
Q3 Find Classes PASS 1/5 search_graph(Class) Session, Result, URLEncodedFormEncoder, AFError. 371 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=Request) 480 results
Q5 Code Snippet PASS 1/5 get_code_snippet write: 3-line function
Q6 Text Search PASS 1/5 search_code(TODO) 3 matches in Session.swift, SessionDelegate.swift
Q7 Outbound Trace PASS 1/5 trace_call_path(write) 70KB+ result: 58+ callers
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) Rich caller chain
Q9 Cypher CALLS PASS 1/5 query_graph didCreateURLRequest->task, finish->finish
Q10 Properties PARTIAL 1/5 search_graph(Variable) 0 Variables, 817 Fields (Swift properties as Fields)
Q11 Inheritance PASS 1/5 search_graph(Module) 449 modules
Q12 List Directory PASS 1/5 list_directory 19 entries: Source/, Tests/, Documentation/

Score: 10.5/11 (95%)

Dart (felangel/bloc)

Project: bloc-dart | Repo: /tmp/lang-bench/bloc-dart
Nodes: 5,089 | Edges: 6,430

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 search_graph(File) 900 files indexed
Q2 Find Functions PASS 1/5 search_graph(Function) identical, downloadFile, analyzeDependencies. 479 total
Q3 Find Classes PASS 1/5 search_graph(Class) Bloc, Cubit, BlocObserver, HydratedCubit, MockBloc. 846 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=Bloc) 268 results
Q5 Code Snippet PASS 1/5 get_code_snippet(Bloc) 278-line class source
Q6 Text Search PASS 1/5 search_code(TODO) 3 matches in gradle, kotlin, dart
Q7 Outbound Trace PARTIAL 1/5 trace_call_path(identical) 0 edges (built-in function reference, leaf node)
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) Callers traced
Q9 Cypher CALLS PASS 1/5 query_graph generateCubitCode->get, getCubitTemplate->getFreezedCubitTemplate
Q10 Properties PASS 1/5 search_graph(Variable) 592 variables
Q11 Inheritance PASS 1/5 search_graph(Method) 873 methods, 14 INHERITS edges
Q12 List Directory PASS 1/5 list_directory 11 entries: packages/, examples/, extensions/

Score: 10.5/12 (87%)

Perl (mojolicious/mojo)

Project: mojo-perl | Repo: /tmp/lang-bench/mojo-perl
Nodes: 3,287 | Edges: 4,182

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 search_graph(File) 192 files indexed
Q2 Find Functions PASS 1/5 search_graph(Function) join, split, warn, decode, encode, render. 1,005 total
Q3 Find Classes PASS 1/5 search_graph(Class) 0 classes (expected -- Perl uses packages)
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=render) 26 results
Q5 Code Snippet PASS 1/5 get_code_snippet(join) 3-line sub returned
Q6 Text Search PASS 1/5 search_code(TODO) 1 match in bundled highlight.js
Q7 Outbound Trace PASS 1/5 trace_call_path(join) 114 results, rich 2-hop graph
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) 40+ callers: websocket, camelize, tablify
Q9 Cypher CALLS PASS 1/5 query_graph 491 CALLS edges: run->getopt, remote_address->split
Q10 Properties PASS 1/5 search_graph(Variable) 1,003+ variables
Q11 Inheritance PASS 1/5 search_graph(Method) 0 methods (expected -- Perl subs are Functions)
Q12 List Directory PASS 1/5 list_directory 9 entries: lib/, t/, examples/, script/

Score: 12/12 (100%)

Groovy (spockframework/spock)

Project: spock-groovy | Repo: /tmp/lang-bench/spock-groovy
Nodes: 14,081 | Edges: 34,557

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 search_graph(File) 1,001 files (+ 284 unlisted)
Q2 Find Functions PASS 1/5 search_graph(Function) cleanup, setup, println. 185 total
Q3 Find Classes PASS 1/5 search_graph(Class) Specification, Issue, FailsWith. 1,005 total
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=Spec) 802 results
Q5 Code Snippet PASS 1/5 get_code_snippet isSatisfiedBy: interface method signature
Q6 Text Search PASS 1/5 search_code(TODO) 3+ matches in BuilderHelper.java, PojoBuilder.java
Q7 Outbound Trace PASS 1/5 trace_call_path(isSatisfiedBy) 4 edges: matches, computeSimilarityScore
Q8 Inbound Trace PASS 1/5 trace_call_path(inbound) match, describeMismatch callers
Q9 Cypher CALLS PASS 1/5 query_graph 5,876 Method->Method CALLS
Q10 Properties PASS 1/5 search_graph(Variable) 1,003+ variables (1,278 total)
Q11 Inheritance PASS 1/5 search_graph(Method) 1,003+ methods (6,788 total)
Q12 List Directory PASS 1/5 list_directory 27 entries: spock-core/, spock-specs/, build-logic/

Score: 12/12 (100%)

SQL (flyway/flyway)

Project: flyway-sql | Repo: /tmp/lang-bench/flyway-sql
Nodes: 10,149 | Edges: 23,222

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 search_graph(File) 811 files indexed
Q2 Find Functions PASS 1/5 search_graph(Function) 47 functions (Python/Bash helpers, not SQL functions)
Q3 Find Classes PASS 1/5 search_graph(Class) 576 classes (Java: ClassicConfiguration, ConfigUtils)
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=Migration) 456 results
Q6 Text Search PARTIAL 1/5 search_code(CREATE TABLE) Found in docs but no actual SQL DDL indexed
Q12 List Directory PASS 1/5 list_directory 22 entries: flyway-core/, flyway-docker/

Score: 4.5/6 (75%)

Dockerfile (docker-library/official-images)

Project: official-images-dockerfile | Repo: /tmp/lang-bench/official-images-dockerfile
Nodes: 1,481 | Edges: 1,588

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 search_graph(File) 206 files indexed
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=docker) 12 results
Q6 Text Search PASS 1/5 search_code(FROM) 3+ matches in workflow scripts
Q12 List Directory PASS 1/5 list_directory 17 entries: library/, test/, Dockerfile

Score: 4/4 (100%)

CSS (animate-css/animate.css)

Project: animate-css | Repo: /tmp/lang-bench/animate-css
Nodes: 295 | Edges: 214

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 117 Files, 115 Modules, 37 Variables, 3 Functions
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 0 results (no tests in CSS lib)
Q6 Text Search PASS 1/5 search_code(TODO) 0 matches (clean codebase)
Q12 List Directory PASS 1/5 list_directory 14 entries: source/, docs/

Score: 4/4 (100%)

YAML (kubernetes/examples)

Project: k8s-yaml | Repo: /tmp/lang-bench/k8s-yaml
Nodes: 2,110 | Edges: 1,844

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 1,235 Variables, 335 Files, 309 Modules
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 12 test-related nodes
Q6 Text Search PASS 1/5 search_code(TODO) 9 matches across demo scripts
Q12 List Directory PASS 1/5 list_directory 12 entries: AI/, databases/, web/

Score: 4/4 (100%)

TOML (rust-lang/cargo)

Project: cargo-toml | Repo: /tmp/lang-bench/cargo-toml
Nodes: 16,773 | Edges: 51,403

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 search_graph(Variable) 305 variables
Q4 Pattern Search PASS 1/5 search_code(TODO) 3+ TODOs in workflows, Cargo.toml
Q6 Text Search PASS 1/5 list_directory(src/cargo) 8 entries: core/, lib.rs, lints/, ops/
Q12 List Directory PASS 1/5 search_graph(Module) 1,005+ modules

Score: 4/4 (100%)

HCL (hashicorp/terraform)

Project: terraform-hcl | Repo: /tmp/lang-bench/terraform-hcl
Nodes: 78 | Edges: 76

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 search_graph(Variable) 50 variables (version, constraints, hashes, region)
Q4 Pattern Search PASS 1/5 search_code(TODO) 0 TODOs (clean config)
Q6 Text Search PASS 1/5 list_directory 6 entries: main.tf, outputs.tf, variables.tf
Q12 List Directory PASS 1/5 search_graph(Module) 5 modules: main.tf, .terraform.lock.hcl

Score: 4/4 (100%)

HTML (twbs/bootstrap)

Project: bootstrap-scss (shared) | Repo: /tmp/lang-bench/bootstrap-scss
Nodes: 2,726 | Edges: 4,135

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 1,525 Variables, 267 Methods, 195 Functions, 22 Classes
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 18 test-related nodes
Q6 Text Search PASS 1/5 search_code(TODO) 3 TODO/FIXME matches in js/src/carousel.js
Q12 List Directory PASS 1/5 list_directory 15 entries: scss/, js/, dist/, build/

Score: 4/4 (100%)

SCSS (twbs/bootstrap)

Project: bootstrap-scss (shared) | Repo: /tmp/lang-bench/bootstrap-scss
Nodes: 2,726 | Edges: 4,135

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 1,525 Variables, 267 Methods, 195 Functions, 22 Classes
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=test) 18 SCSS test files
Q6 Text Search PASS 1/5 search_code(TODO) 3 matches
Q12 List Directory PASS 1/5 list_directory 15 entries: scss/, js/, dist/

Score: 4/4 (100%)


Linux Kernel Stress Test

Project: linux-kernel | Subset: drivers/net/ethernet/intel/
Files: 387 C/H files | Nodes: 19,993 | Edges: 67,305

Index Breakdown

Label Count
Function 11,546
Class (structs) 1,851
Method 1,803
Macro 1,346
Field 1,095
Community 734
Enum 480
Variable 461

Results

Q# Question Grade Attempts Approach Notes
Q1 Index Stats PASS 1/5 get_graph_schema 14 node labels, 11 edge types, 20K nodes, 67K edges
Q2 Find Functions PASS 1/5 search_graph(Function) e1e_rphy (in=64), e1e_wphy (in=53). 11,546 total
Q3 Find Classes PASS 1/5 search_graph(Class) nic (in=86), params (in=59). 1,851 C structs
Q4 Pattern Search PASS 1/5 search_graph(name_pattern=init|probe) 715 matches: i40e_probe (out=59), ixgbe_probe (out=43)
Q5 Code Snippet PASS 1/5 get_code_snippet e1e_rphy: 4 lines from e1000.h:543-546
Q6 Text Search PASS 1/5 search_code(BUG_ON|WARN_ON|pr_err) Found across amt.c, e100.c, e1000_ethtool.c
Q7 Outbound Trace (depth=5) PASS 1/5 trace_call_path(outbound) i40e_probe: 129K chars result. Massive call tree, no timeout
Q8 Inbound Trace (depth=5) PASS 1/5 trace_call_path(inbound) e1e_rphy: 67.6K chars. 64 direct callers, deep chains
Q9 Cypher CALLS PASS 1/5 query_graph 10 CALLS edges: netdev_boot_setup, amt functions
Q10 Properties PARTIAL 2/5 query_graph(Cypher) IS NOT NULL not supported; retried with adjusted query
Q11 Inheritance N/A -- -- C language
Q12 List Directory PASS 1/5 list_directory 17 entries: e100.c, i40e/, ice/, ixgbe/, igb/

Score: 11/11 (100%) (Q11 N/A)

Stress Metrics

Test Result Size Timeout?
Deep trace (depth=5, outbound, i40e_probe) PASS 129,026 chars No
Deep trace (depth=5, inbound, e1e_rphy) PASS 67,600 chars No
Deep trace (depth=5, both, e1000e_reset) PASS moderate No
Multi-hop Cypher (3-hop CALLS chain) PASS 10 valid chains No

The tool handles 20K-node kernel subsystems without timeouts. Deep traces on well-connected probe functions (out_degree=59) produce massive but complete results. Community detection identified 734 clusters -- useful for understanding driver module boundaries.


Cross-Cutting Findings

Strengths

  1. Zero indexing failures across 63 repos of all sizes (78 to 49K nodes)
  2. 100% on 17 languages -- half the languages achieved perfect scores
  3. No language below 62% -- even the weakest performs core operations
  4. Massive codebases handled: Django (49K nodes), Laravel (38K nodes), neovim (24K nodes) -- no performance issues
  5. Kernel-scale stress test passed: 20K nodes, 67K edges, 129K-char traces, zero timeouts
  6. "Removed" languages still strong: Obj-C (100%), Perl (100%), Groovy (100%), Swift (95%), Dart (87%)

Common PARTIAL Patterns

  • Q10 (Properties): Most languages return functions with properties=null. Parameter/return extraction is incomplete -- this is the single most common deduction
  • Q7/Q8 (Trace): Abstract methods, entry-point functions, and built-in references correctly return 0 edges (no false positives), but are graded PARTIAL when no alternative function has edges
  • Q11 (Inheritance): Languages using alternative paradigms (traits, protocols, typeclasses, modules) have limited INHERITS edges

Known Limitations

  1. Function properties: Parameter types and return types are not extracted for most languages, resulting in properties=null
  2. Haskell call tracing: Function composition (f . g . h) is not modeled as CALLS edges. Only explicit function applications are traced
  3. OCaml call analysis: Limited call resolution due to module functor indirection
  4. Cypher IS NOT NULL: Not supported in the Cypher parser -- use alternative query patterns
  5. Scala/Ruby trace: Abstract methods and block-based patterns can have 0 trace edges
  6. query_graph 200-row cap: Aggregation queries (COUNT) silently undercount on large codebases

Aggregate Statistics

Metric Value
Languages tested 35
Total questions asked 370
PASS 327 (88%)
PARTIAL 25 (7%)
FAIL 18 (5%)
N/A (excluded) 41
Weighted score 339.5 / 370
Overall percentage 91.8%
Perfect scores (100%) 17 languages
Tier 1 (>=90%) 17 languages
Tier 2 (75-89%) 16 languages
Tier 3 (<75%) 2 languages

CONFIGURATION (`docs/CONFIGURATION.md`)

Configuration Reference

This page documents the configuration files that codebase-memory-mcp reads or writes today.

At a Glance

Purpose Path Format Notes
Global custom extension mapping $XDG_CONFIG_HOME/codebase-memory-mcp/config.json JSON Falls back to ~/.config/codebase-memory-mcp/config.json when XDG_CONFIG_HOME is unset.
Per-project custom extension mapping {repo_root}/.codebase-memory.json JSON Overrides conflicting global extra_extensions entries.
CLI-managed runtime settings ${CBM_CACHE_DIR:-~/.cache/codebase-memory-mcp}/_config.db SQLite Written by codebase-memory-mcp config set/reset.
UI settings ${CBM_CACHE_DIR:-~/.cache/codebase-memory-mcp}/config.json JSON Stores ui_enabled and ui_port.
Daemon operation log ${CBM_CACHE_DIR:-~/.cache/codebase-memory-mcp}/logs/cbm-daemon.log Structured log Durable daemon lifecycle, watcher/indexing, UI, resource, and error events.
Admission conflict log ${CBM_CACHE_DIR:-~/.cache/codebase-memory-mcp}/logs/daemon-conflicts.ndjson NDJSON Exact-build, ABI, and canonical-cache conflicts.
Activation log ${CBM_CACHE_DIR:-~/.cache/codebase-memory-mcp}/logs/activation-events.ndjson NDJSON Install/update/uninstall activation progress and outcomes.

CBM resolves CBM_CACHE_DIR to a canonical per-account path before using any of these locations. The log directory and files are private to the account.

1. Custom File Extension Mapping

Two optional JSON files let you map additional file extensions to built-in languages.

Global config

Default path:

text
$XDG_CONFIG_HOME/codebase-memory-mcp/config.json

Fallback when XDG_CONFIG_HOME is unset:

text
~/.config/codebase-memory-mcp/config.json

Per-project config

Place this file in the repository root:

text
.codebase-memory.json

Format

json
{
  "extra_extensions": {
    ".blade.php": "php",
    ".mjs": "javascript",
    ".twig": "html"
  }
}

Notes:

  • Extension keys must include the leading dot.
  • Language names are case-insensitive.
  • Unknown language names are skipped.
  • Missing files are ignored.
  • If the same extension appears in both files, the per-project file wins.

2. CLI-Managed Runtime Settings

The config subcommand stores runtime settings in a small SQLite database:

text
${CBM_CACHE_DIR:-~/.cache/codebase-memory-mcp}/_config.db

Inspect or change values with the CLI:

bash
codebase-memory-mcp config list
codebase-memory-mcp config get auto_index
codebase-memory-mcp config set auto_index true
codebase-memory-mcp config set auto_index_limit 50000
codebase-memory-mcp config set watcher_enabled false
codebase-memory-mcp config reset auto_index

Current keys:

Key Default Meaning
auto_index false Automatically index new projects when an MCP session starts.
auto_index_limit 50000 Maximum file count allowed for automatic indexing of a new project.
auto_watch true Register the session's project with the background git watcher on connect. Set false to keep a session from registering its project (the watcher still runs for other projects).
doc_candidates auto Score documentation sections against code to store doc↔code candidates (the possible targets of a section, and the sections that may describe a function or file, each with a judged probability). Unset (auto), the index mode decides: on for full indexes (the default mode), off for moderate and fast. Set true or false to choose for every mode; config reset doc_candidates returns to auto. The scoring grows with sections × functions: under a second up to ~100k functions, ~15 s at ~400k, ~3 minutes on the Linux kernel. Off stores no candidates and leaves the rest of the graph unchanged; on in fast mode stores candidates without adding fast's skipped SIMILAR_TO / SEMANTICALLY_RELATED edges. index_repository also takes doc_candidates for a single index, which wins over this setting. Applies from the next index.
watcher_enabled true Master switch for the background watcher subsystem. Set false to stop the watcher from starting at all — no poll thread and no project registration. Reindex manually with index_repository when disabled. Only an open MCP session's own project is ever watched; index_status reports the current state in its watch object.
watch_non_git false Also poll project roots that are not git repositories. By default the watcher only follows git projects, so a project indexed from a plain directory is never refreshed after its first index — reindex it manually with index_repository. Set true to poll such roots on the same adaptive cadence with a file-tree scan: the indexer's own discovery walk (same skip lists, .gitignore and .cbmignore rules) hashed over each file's path, size and mtime. Any change reindexes once; paths the indexer skips (including cbm's own .codebase-memory/ output) never trigger. The first poll after the daemon starts reindexes each such project once, since nothing records which tree state the index holds. The scan walks the whole tree every poll, so it costs more than git polling on very large trees. Read once when the daemon starts, like watcher_enabled.
index_max_files off Optional maximum number of accepted source files in one discovery run.
index_max_source_mb off Optional maximum accepted source size in MiB in one discovery run.

For a watched project, index_status names the polling method in watch.strategy:
git (HEAD and dirty-state polling), tree (a non-git root polled by the
watch_non_git file-tree scan), none (a non-git root that is registered but never
polled, the default for a plain directory) or pending (registered; the first poll
has not run yet).

watcher_enabled vs auto_watch. watcher_enabled controls whether the
watcher subsystem starts at all (the background poll thread). auto_watch is
narrower: it only controls whether a connecting session registers its own
project with an already-running watcher. When watcher_enabled=false,
auto_watch has no effect — there is no watcher to register with.

They also differ in when they are read, which matters because the watcher
lives in the background daemon, not in your MCP client:

  • auto_watch is consulted each time a session would register its project, so
    a change applies to sessions that connect afterwards.
  • watcher_enabled is read once, when the daemon starts, because it decides
    whether the watcher is built at all. The daemon is long-lived and outlives
    individual MCP sessions, so reconnecting your client is not enough — retire
    the daemon so the next one picks the new value up:
bash
codebase-memory-mcp config set watcher_enabled false
codebase-memory-mcp daemon stop     # next session starts a daemon without the watcher
codebase-memory-mcp daemon status   # confirm

Disabling the watcher does not disable anything else: the daemon still starts,
auto_index still runs, and index_repository stays available for manual
reindexing.

The two index_max_* settings are independent and disabled by default. They
apply to explicit indexing, automatic indexing, and watcher re-indexing, but not
to cross-repo-intelligence, which does not scan repository source files.
Equality is allowed; exceeding either setting fails the complete index request
and preserves any previously serving database. See
Index resource limits for counting, validation, and
error-response details.

3. UI Settings

The optional built-in graph UI stores its settings in:

text
${CBM_CACHE_DIR:-~/.cache/codebase-memory-mcp}/config.json

Current format:

json
{
  "ui_enabled": false,
  "ui_port": 9749
}

Notes:

  • If a UI-enabled binary finds its verified external asset pack and no UI config file exists yet, the UI auto-enables on first run. Missing or invalid assets leave the MCP/daemon service available and keep the UI disabled.
  • CBM_CACHE_DIR changes both the UI config location and the runtime settings database location.
  • CBM resolves CBM_CACHE_DIR to one canonical per-account cache root. A process configured with a different root fails while any CBM session or command is active; close them before switching roots.

4. Environment Variables

These environment variables affect runtime behavior:

Variable Default Description
CBM_ALLOWED_ROOT (unset) Confine index_repository to paths within this directory. When set, a repo_path that resolves (after symlink / .. resolution) outside this root is refused, and the same check now applies to the graph UI's POST /api/index route rather than only to the MCP tool. Unset imposes no containment restriction — but see the always-on limits below, which apply whether or not this is set. Useful when the server may be driven by an untrusted caller, e.g. agentic or multi-tenant deployments.
CBM_CACHE_DIR ~/.cache/codebase-memory-mcp Override the cache directory used for indexes, _config.db, and UI config.json.
CBM_DIAGNOSTICS false Enable periodic snapshot.json and retained trajectory.ndjson below a fresh owner-private directory in the system temp directory. The daemon records the randomized paths in the diagnostics.start discovery record (a single JSON line) in ${CBM_CACHE_DIR}/logs/cbm-daemon.log; that one record is emitted even when CBM_LOG_LEVEL suppresses ordinary logging, so the paths always remain discoverable.
CBM_DOWNLOAD_URL GitHub releases Override the update download URL.
CBM_LOG_LEVEL role-aware Set the log level to debug, info, warn, error, or none (or 0-4). Thin MCP/CLI/hook frontends default to warn; the detached daemon and supervised index workers default to info. Physical workers retain INFO liveness records because their private logs drive the supervisor's no-progress timeout. Frontend messages use that session's stderr; detached daemon events use ${CBM_CACHE_DIR}/logs/cbm-daemon.log.
CBM_RUNTIME_DIR %LOCALAPPDATA% (Windows), /private/tmp (macOS), /tmp (other) Parent directory for the daemon/CLI rendezvous directory, which CBM creates inside it as cbm-daemon- (cbm-daemon-<key> on Windows). Set it when the default ancestry cannot pass the private-directory check — see below. CBM_CACHE_DIR does not move the rendezvous.
CBM_WORKERS auto-detected Override the indexing worker count.

Relocating the daemon rendezvous directory

Before it is used, the rendezvous directory and every ancestor of it are checked:
each ancestor must be owned by you or by root, must not be world-writable (unless
it is the standard root-owned sticky directory such as /tmp), and must carry no
allow-ACL — on Windows, no ACE granting mutation rights to another identity. The
rendezvous directory itself is then forced to owner-only (0700, no extended ACL
/ an owner-only DACL).

That ancestry is not always acceptable in the default location. A Windows profile
that has acquired a capability-SID ACE with WRITE_DAC / WRITE_OWNER / DELETE
on %LOCALAPPDATA% — something an installed packaged app can add — fails the walk,
and so can an unusual /tmp or home directory on POSIX. When that happens every
command fails, config list included, so the settings surface cannot be reached
either:

text
codebase-memory-mcp: secure daemon endpoint could not be created

CBM_RUNTIME_DIR points the rendezvous at an ancestry you choose:

bash
export CBM_RUNTIME_DIR="$HOME/cbm-runtime"   # any directory you own
powershell
$env:CBM_RUNTIME_DIR = "D:\cbm-runtime"

The check is not relaxed for the directory you name: it goes through exactly the
same validation as the default, and a value that fails it is refused rather than
silently ignored. Because the rendezvous is how sessions find each other, every
process that should share one daemon must see the same value — set it in the
environment of your MCP client and your shell alike, or a CLI invocation without
it will coordinate through the default location instead.

WSL2 and Windows drives (/mnt/c, /mnt/e, ...). CBM_CACHE_DIR goes through
the same private-directory check. With WSL's default automount options, DrvFs
reports every directory as 0777 and ignores chmod, so a cache under
/mnt/<drive> is refused, and the refusal names this remedy. Either turn on
permission metadata in /etc/wsl.conf, then run wsl --shutdown and reopen WSL:

ini
[automount]
options = "metadata,umask=22,fmask=11"

or keep the cache on the Linux filesystem (the default ~/.cache/codebase-memory-mcp),
which is also much faster than a 9p-mounted Windows drive.

Environment used by daemon-owned components—such as diagnostics, daemon logging, and process-wide indexing resource limits—is captured from the first daemon-backed session that starts the daemon. Later sessions join the existing process and cannot replace those values. To change them, close every daemon-backed session, update the relevant agent configurations consistently, and restart a session. CBM_ALLOWED_ROOT remains session-specific, a conflicting CBM_CACHE_DIR is rejected, and a one-shot CLI command is not exempt from the rule above: it connects to the coordination daemon like any other session, starting one if none is running, so its own environment becomes the captured daemon-owned environment only when its invocation is the one that starts the daemon — joining an already-running daemon, it inherits that daemon's already-captured values instead.

Roots that are always refused

Independently of CBM_ALLOWED_ROOT, some directories are refused as an indexing
root because they are too broad or too sensitive to index as a unit:

  • a filesystem root, a Windows drive root, or a UNC share root;
  • a top-level system tree — /etc, /var, /usr, /home, /Users, and on
    Windows C:\Windows, C:\Users, C:\ProgramData, C:\Program Files;
  • your home directory itself (directories below it are fine);
  • a credential directory at any depth — .ssh, .aws, .gnupg, .kube,
    .docker, .netrc, .git-credentials, .password-store, macOS Keychains.

Two limits are worth stating plainly. This constrains scope, not
sensitivity: inside a root that is allowed, every file the process can read may
be indexed and later returned. And the credential list is a denylist, so it
raises the cost of a mistake rather than closing the class — a directory it does
not name is permitted.

Shared / HPC filesystems

On clusters and other shared machines, home directories often live on a network
filesystem (NFS, Lustre, GPFS, SMB/CIFS), and ~/.cache is sometimes a symlink
into a shared tree. Three rules keep CBM working there:

  • Put the cache and the rendezvous on local storage. Set CBM_CACHE_DIR (indexes,
    _config.db) and, if needed, CBM_RUNTIME_DIR (the daemon rendezvous) to a
    local disk you own, for example /local/scratch/$USER/cbm or /tmp/$USER-cbm.
    Set the same values in your MCP client's environment and your shell.

  • Do not route the cache into another account's tree. A symlinked ~/.cache is
    resolved and allowed, but every directory on the resolved path must be owned
    by you or by root. If a parent such as /shared/fs belongs to another account,
    that account can rename or replace anything below it, so CBM refuses the path
    and names the directory and its owner:

    text
    ... (cache-private) - '/shared/fs' is owned by uid 1234 (alice), not by you (uid 1000) or root; ...

    This check has no override. Choose a location with a safe ancestry instead.

  • Avoid network filesystems for the cache. The indexes are SQLite databases in
    WAL mode, which needs shared-memory locking that network filesystems do not
    provide reliably. On Linux, CBM logs one daemon.cache_root_network_fs warning
    at startup when the cache root is on NFS, Lustre, GPFS, CIFS or SMB2. The cache
    still works there, but it can be slow, and a database can be corrupted.

5. Agent and Editor Integration Files

The install command can also write MCP entries and instruction blocks into agent/editor config files such as Claude Code, Codex, Gemini, VS Code, Cursor, Zed, and others.

Those target paths vary by tool and platform, so the easiest way to inspect the exact files for your machine is:

bash
codebase-memory-mcp install --dry-run

That prints the specific config files the installer would modify without writing anything.


INDEX RESOURCE LIMITS (`docs/INDEX_RESOURCE_LIMITS.md`)

Index resource limits

Index resource limits are optional operator controls for repositories whose
discovery breadth is not known in advance. They are disabled by default so
existing large-repository workloads retain their current behavior.

Discovery settings

Key Default Accepted value Protects
index_max_files off off or 1..10000000 Accepted source-file count
index_max_source_mb off off or 1..1048576 Accepted source-file bytes

Set or reset them with the normal configuration command:

bash
codebase-memory-mcp config set index_max_files 250000
codebase-memory-mcp config set index_max_source_mb 16384
codebase-memory-mcp config reset index_max_files

Values use base-10 integers. MiB means 1,048,576 bytes. Empty values, zero,
negative values, suffixes, trailing characters, and values outside the stated
ranges are rejected without changing the stored value.

Counting and failure semantics

index_max_files counts a file only after it passes directory pruning, ignore
rules, filename and suffix filters, language detection, and the existing
per-file size rule. index_max_source_mb sums the filesystem sizes of that same
accepted set.

Equality is allowed. The first file or byte that makes an observed value greater
than its limit stops discovery. CBM discards the partial file list and does not
publish a partial graph as a complete index.

For an explicit MCP request the error payload contains:

json
{
  "status": "error",
  "code": "resource_limit_exceeded",
  "stage": "discovery",
  "resource": "files",
  "observed": 250001,
  "limit": 250000,
  "unit": "files",
  "retryable": true,
  "serving_index_preserved": true,
  "message": "Index discovery exceeded index_max_files"
}

The previous database remains available because publication occurs only after a
complete discovery and successful staged build. If no previous database exists,
serving_index_preserved is false.

Trust and compatibility

Limits are read from the CLI-managed _config.db; they are not MCP request
arguments. A supervised parent replaces any caller-supplied internal policy
before spawning its worker, and the worker rejects a missing or incomplete
parent policy.

These settings do not replace or increase auto_index_limit, change the 512 MiB
single-file cap, alter workspace-root authorization, or affect
cross-repo-intelligence. With both settings off, discovery follows the
existing unbounded path.


MEASURING SAVINGS (`docs/MEASURING_SAVINGS.md`)

Measuring quality, latency, and agent savings

This guide describes how to measure codebase-memory-mcp (CBM) on your own
repository without mixing three different questions:

  1. Does the agent give a better answer?
  2. Is CBM fast and stable under the workload?
  3. Does graph-assisted exploration use fewer model tokens or tool calls than
    file-by-file exploration?

Measure and report those classes separately. A fast graph query does not prove
that the final answer is correct, and CBM's query counters do not reveal the
agent's model-token or total tool-call consumption. The README's published
performance figures, the
language benchmark and the more detailed
evaluation plan provide the grading concepts and the
larger comparative methodology behind this smaller recipe.

Freeze the experiment

The shell examples in this guide require a POSIX-compatible shell on macOS or
Linux, or Git Bash on Windows. They are not native PowerShell syntax.
Run the command blocks in one dedicated shell: the setup and preflight blocks
enable fail-fast handling, and they capture Git command output before testing it
so a failed inner command cannot be mistaken for a clean worktree.

Before either condition runs, record:

  • the repository URL or local identity and exact Git commit SHA;
  • the question set and expected scope of each answer;
  • the CBM version, index mode, operating system, and machine details;
  • the agent model/version, system prompt, tool instructions, context limits,
    and per-question budget;
  • which condition runs first, plus any warm-up policy;
  • the client or evaluation harness used to record tokens and tool calls.

Use the same repository SHA, questions, model, prompt, budget, and clean-session
policy for both conditions. Do not let the second condition see the first
condition's answers or tool results. If runs are repeated, choose the repeat
count and aggregation method before looking at results and retain every run,
including failures and zero-result queries.

Do not run either condition in a merely clean-looking working copy. A frozen
experiment includes untracked files, so create separate clean detached
worktrees for the two conditions:

bash
set -eu
SOURCE_REPO=/absolute/path/to/source-repository
COMMITISH=main
SHA=$(git -C "$SOURCE_REPO" rev-parse "$COMMITISH^{commit}") || exit 1
MODE=full
RUN_ROOT=$(mktemp -d) || exit 1
GRAPH_REPO="$RUN_ROOT/graph"
BASELINE_REPO="$RUN_ROOT/baseline"
ARTIFACT_ROOT="$RUN_ROOT/artifacts"

git -C "$SOURCE_REPO" worktree add --detach "$GRAPH_REPO" "$SHA" || exit 1
git -C "$SOURCE_REPO" worktree add --detach "$BASELINE_REPO" "$SHA" || exit 1
mkdir "$ARTIFACT_ROOT" || exit 1

GRAPH_SHA=$(git -C "$GRAPH_REPO" rev-parse HEAD) || exit 1
GRAPH_STATUS=$(git -C "$GRAPH_REPO" status --porcelain=v1 --untracked-files=all) || exit 1
BASELINE_SHA=$(git -C "$BASELINE_REPO" rev-parse HEAD) || exit 1
BASELINE_STATUS=$(git -C "$BASELINE_REPO" status --porcelain=v1 --untracked-files=all) || exit 1
test "$GRAPH_SHA" = "$SHA" || exit 1
test -z "$GRAPH_STATUS" || exit 1
test "$BASELINE_SHA" = "$SHA" || exit 1
test -z "$BASELINE_STATUS" || exit 1

Repeat the matching SHA and cleanliness assertions immediately before indexing
and immediately before each condition. If either assertion fails, stop and
create a new detached worktree; do not erase unknown files to make a reused
checkout appear clean. Write run artifacts under ARTIFACT_ROOT, outside both
condition worktrees.

Preflight the graph

Immediately before every Graph-condition measurement, repeat its clean-worktree
assertions and require a successful fresh index of that exact worktree and mode:

bash
set -eu
GRAPH_SHA=$(git -C "$GRAPH_REPO" rev-parse HEAD) || exit 1
GRAPH_STATUS=$(git -C "$GRAPH_REPO" status --porcelain=v1 --untracked-files=all) || exit 1
test "$GRAPH_SHA" = "$SHA" || exit 1
test -z "$GRAPH_STATUS" || exit 1
codebase-memory-mcp cli index_repository \
  --repo-path "$GRAPH_REPO" \
  --mode "$MODE" || exit 1
GRAPH_SHA_AFTER=$(git -C "$GRAPH_REPO" rev-parse HEAD) || exit 1
GRAPH_STATUS_AFTER=$(git -C "$GRAPH_REPO" status --porcelain=v1 --untracked-files=all) || exit 1
test "$GRAPH_SHA_AFTER" = "$SHA" || exit 1
test -z "$GRAPH_STATUS_AFTER" || exit 1

Take PROJECT_NAME from the successful indexing response. A verbose status call
is useful only as a root/current-HEAD cross-check:

bash
codebase-memory-mcp cli list_projects
codebase-memory-mcp cli index_status --project PROJECT_NAME --verbose

Confirm that root_path is GRAPH_REPO, git.head_sha is SHA, and the Git
context is detached. index_status describes the project root and its current
filesystem Git context; it does not by itself prove that indexed records
were produced from that revision. The immediately preceding successful fresh
index_repository call is the freshness requirement.

Finally, run one or more representative queries whose expected symbols you
have verified directly at the recorded SHA:

bash
codebase-memory-mcp cli search_graph \
  --project PROJECT_NAME \
  --name-pattern 'KNOWN_SYMBOL_PATTERN' \
  --limit 10

Do not start measurement until the fresh index succeeds, the status cross-check
names the intended detached checkout and current SHA, and the representative
queries return the expected symbols. Save the indexing response, status, and
query outputs with the run artifacts. Immediately before the file-by-file
condition, repeat the corresponding SHA and cleanliness assertions for
BASELINE_REPO.

1. Measure answer quality

Create a fixed set of real developer questions. Include a mix of definition
discovery, relationships or call paths, targeted source retrieval,
architecture, and cross-cutting questions where those dimensions apply. Record
the expected scope or an independently derived ground truth for each question.

Run two isolated conditions:

Condition Allowed exploration tools
Graph CBM graph tools such as search_graph, trace_path, query_graph, get_code_snippet, get_architecture, and search_code
File-by-file baseline File listing, text search, and targeted file reads only

Grade the final answers against the source at the frozen SHA, not against how
plausible they sound. The compact rubric in BENCHMARK.md uses
PASS (1.0), PARTIAL (0.5), and FAIL (0.0), excluding truly inapplicable
questions from the denominator. For a finer comparison, follow
EVALUATION_PLAN.md: score correctness, completeness, and
specificity separately, blind the grader to which condition produced each
answer, randomize A/B order, and require source evidence for the grade.

Keep quality scores beside, but separate from, efficiency metrics. A token
reduction is only useful when the answer still meets the chosen quality bar.

2. Measure latency and stability

Record indexing time separately from query latency, and classify the index run
as full-source, artifact-assisted, or incremental. When no local project
database exists, index_repository can import a compatible
.codebase-memory/graph.db.zst and then take the incremental-manifest route.
Before timing, record whether that artifact exists in GRAPH_REPO, retain the
index log, and require the later successful artifact.import record containing
db and size_mb before classifying the run as artifact-assisted. A bootstrap
attempt alone, or an artifact.import record containing skip or err, does
not prove that the artifact was used. For queries, use the same fixed workload
in the same order, identify warm-up calls in advance, and retain per-call
durations and exit status rather than only a single average. Report the machine,
OS, CBM version, repository SHA, index mode, question set, index-run class, and
whether query results are cold or warm.

Built-in diagnostics

For a daemon-backed run, enable diagnostics before the first session starts.
The daemon captures its environment at startup; if it is already running,
close all daemon-backed sessions before changing the setting. See
Configuration for the complete
environment contract.

bash
export CBM_DIAGNOSTICS=1

CBM creates a fresh randomized, owner-private diagnostics directory below the
system temporary directory. Do not assume or construct an old predictable
/tmp filename. Discover the exact snapshot and trajectory paths from the
diagnostics.start JSON record in
${CBM_CACHE_DIR}/logs/cbm-daemon.log (the default cache directory is
~/.cache/codebase-memory-mcp). The record is emitted even when the configured
log level suppresses ordinary logging. The README's
diagnostics section explains the
files and retention behavior.

The live snapshot.json includes CBM-side query_count, query_errors,
query_total_us, query_avg_us, and query_max_us, along with process resource
counters. The retained trajectory.ndjson provides the resource and query-count
trend over time. These are useful for CBM latency and stability analysis, but
they are not a record of an agent's model usage or of non-CBM tools.
Because a daemon can be shared by multiple sessions, use an otherwise idle
daemon and record before/after values (or start a dedicated run) when attributing
its counters to one workload.

Canonical soak workload

From a source checkout, the canonical endurance entry point is:

bash
scripts/soak-legs.sh build/c/codebase-memory-mcp 10

It runs the quick mixed workload and the read-only query-leak workload, checks
for a valid completion summary, and writes per-call latency plus resource
results. The quick leg writes soak-results/; the query-leak leg writes
soak-results-query-leak/. Each directory contains latency.csv, metrics.csv,
and summary.txt. The two CSV files begin with these exact headers:

text
timestamp,tool,duration_ms,exit_code
timestamp,uptime_s,rss_bytes,heap_committed,fd_count,query_count,query_max_us

Do not invoke scripts/soak-test.sh directly; soak-legs.sh owns the
release-gating sequence. Treat these artifacts as stability and CBM latency
evidence, not as answer-quality or model-token evidence.

3. Measure token and tool-call savings

Run the Graph and file-by-file conditions on the same frozen inputs described
above. The MCP client or evaluation harness must capture agent usage because
CBM cannot know the final model input/output token count or the agent's total
tool-call consumption.

Define each run_id as one paired experimental replicate. Each
(run_id, condition) is exactly one isolated client session that answers one
question. Capture one row for every usage window the client directly measures:

text
run_id,condition,repo_sha,question_id,window,input_tokens,output_tokens,total_tokens,tool_calls,wall_time_ms,answer_artifact,quality_score

In each row, tool_calls is the count of every client tool invocation inside
that same window, including orchestration calls and the graph or file tools
permitted for that condition, plus retries, errors, and zero-result calls. Do
not count only CBM calls. Candidate windows are:

  • Answering tokens: input plus output tokens between fixed markers around
    the question-answering phase.
  • Full-session tokens: the entire isolated session, including orientation,
    initial probes, dead ends, and answer formatting.

The full-session value best represents an adopter's total cost; the answering
window helps explain where a difference arose. Do not substitute CBM's
query_count for either tool_calls value: it cannot see file searches, file
reads, or other client tools.

For each Graph/baseline pair, compare only a window that both clients report
directly, with at most one row per (run_id, condition, window). If either
client cannot expose a window, omit that window or record its usage fields as
N/A and exclude it from the comparison. Never infer a missing window, partition
a session total across questions, or duplicate one session total into multiple
question rows.

Select one window W and calculate from the Graph and baseline rows for that
same window only:

text
token reduction (%)     = 100 * (baseline tokens - Graph tokens) / baseline tokens
tool-call reduction (%) = 100 * (baseline calls  - Graph calls)  / baseline calls
token ratio              = baseline tokens / Graph tokens
tool-call ratio          = baseline calls  / Graph calls

Calculate and label every common measured window separately; never mix their
numerators or denominators. If baseline tokens or calls are zero, the
corresponding reduction percentage is N/A. If Graph tokens or calls are zero,
the corresponding ratio is N/A. Do not add pseudocounts. Publish the raw paired
counts alongside ratios and percentages. Also publish the quality result for
each pair, the number of runs, aggregation method, failures, and experimental
controls. Never turn a result from one repository, question set, model, or
machine into a universal savings claim.

Reproducibility checklist

  • Repository identity and exact SHA are recorded; each condition uses a clean,
    detached worktree with no tracked or untracked changes.
  • CBM version, index mode, project name, and preflight outputs are retained.
  • Every Graph run follows a successful fresh index of the frozen worktree and
    mode; verbose status is only a root/current-HEAD cross-check.
  • Questions, ground truth, prompts, budgets, and condition order are frozen.
  • Graph and file-by-file runs use isolated sessions and the same controls.
  • Quality, CBM latency/stability, and agent savings are reported separately.
  • Usage counts come directly from the client or evaluation harness; each
    condition run is one isolated question/session, common windows use separate
    rows, and unsupported windows are omitted or N/A rather than inferred.
  • Diagnostics paths come from diagnostics.start, not a guessed temp path.
  • Raw outputs, errors, zero results, and calculation inputs are retained.
  • Claims identify their repository, SHA, question set, model, machine, and run
    count, with no invented or extrapolated benchmark numbers.

RUNTIME TRACE MODEL (`docs/RUNTIME_TRACE_MODEL.md`)

Runtime Trace Observation Model

Status and recommendation

This document is an architecture contract, not an implementation. It recommends a
durable, versioned runtime-observation sidecar whose publications can be read alongside
a pinned static index generation. Runtime observations MUST NOT patch static CALLS
edges, fabricate static nodes, or appear as persistent edges in the default graph.

The model intentionally separates facts observed at runtime from facts derived from
source. A caller may explicitly request a RUNTIME_CALL overlay; existing static
search, architecture, and default trace behavior remain unchanged.

This document-only change does not alter the runtime wire version, runtime schema
version, runtime semantic version, runtime artifact version, static semantic index
version, or artifact schema version. In particular, the static semantic index version
remains 3.

Scope

The model defines:

  • stable identities for spans, producer contributions, observations, endpoints, and
    composite publications;
  • deterministic ingestion, aggregation, publication, and replay semantics;
  • generation-scoped resolution from runtime endpoints to static symbols;
  • an opt-in runtime call overlay without changing the stored static graph;
  • concurrency, retry, conflict, recovery, privacy, and cardinality boundaries; and
  • six bounded implementation packages that can be reviewed and shipped separately.

Non-goals

This design does not:

  • infer new static CALLS edges or change confidence on existing ones;
  • create placeholder nodes in the static graph for runtime-only identities;
  • persist runtime edges in the generic graph edge table;
  • make runtime data visible to existing graph tools unless an overlay is requested;
  • define distributed tracing transport, sampling policy, or collector deployment;
  • promise complete traces, causal proof from timing alone, or source-line attribution;
  • store arbitrary span payloads, headers, request bodies, query strings, or secrets;
  • average percentile summaries or treat a producer's p99 as a mergeable percentile;
  • provide a compatibility promise for a future mutation API before its versioned
    contract exists; or
  • change any runtime or static version merely by adding this document.

Current surface

The current ingest_traces surface requires project and traces[]. Each trace item
permits caller, callee, and count, but those item fields are not currently
individually required. Its handler counts the supplied records and returns
status="accepted", traces_received, and an unimplemented note; this is not an
accepted durable mutation, and the handler does not open or mutate a store.

The existing trace helper surface can extract service.name, HTTP method, HTTP path,
HTTP status, span kind, duration, URL path, and p99. Verified span-kind values are:

  • 1: internal;
  • 2: server; and
  • 3: client.

Generic graph edges are unique by source, target, type, and the local-name
discriminator. Generic edge upsert merges properties with json_patch. Those rules are
appropriate for the static graph but are not the contribution or aggregation rules in
this document. The store already exposes BEGIN IMMEDIATE, COMMIT, and ROLLBACK.
Static publication already uses opaque generations and request-scoped reads.

Normative terms

MUST, MUST NOT, SHOULD, and MAY are normative. "Canonical JSON" means UTF-8
JSON with a fixed schema, lexicographically sorted object keys, arrays in their
specified order, no insignificant whitespace, and integers rather than locale- or
platform-dependent numeric strings. Hashes are computed over those canonical bytes.

Identity layers

The following identities are different and MUST NOT be substituted for one another.

Trace and span identity

A span is identified within one producer namespace by:

text
(project, producer_id, producer_epoch, trace_id, span_id)

trace_id and span_id are opaque byte strings rendered in one canonical lowercase
hex form at the boundary. producer_epoch is part of span identity so a producer can
reuse trace or span identifiers in a later epoch without collision. The tuple identifies
an input span for diagnostics and deduplication; it does not identify a static node or
an aggregate.

Producer contribution identity

Every accepted batch has a producer-stable identifier:

text
contribution_id = H(canonical JSON {
  producer_id,
  producer_epoch,
  source_batch_id
})

producer_id names the authenticated logical producer, not a transient process.
producer_epoch changes only when that producer intentionally starts a new identity
domain. source_batch_id is stable across delivery retries. The resulting
contribution_id is scoped by project.

An accepted contribution is immutable. Retrying the same (project, producer_id, contribution_id) with the same canonical payload hash is an idempotent success and
returns the original disposition. Retrying it with different canonical bytes is a
conflict and MUST be rejected without mutation. A producer MUST NOT reuse another
producer's namespace, and the service MUST NOT silently mint a replacement ID to turn
a conflict into a second contribution.

The payload hash covers the canonical normalized allowlisted contribution map together
with its pinned wire and runtime semantic version fields. It does not cover original
envelope ordering, unsupported fields, discarded fields, or raw payload bytes.

Endpoint identity

Every endpoint has an explicit tag and a canonical key.

  1. symbol: a source-derived symbol reference (project, qualified_name). This stable
    endpoint identity deliberately excludes a static generation. A separate resolution
    record maps it within one named static generation and resolver version. A later
    generation MUST resolve again and MUST NOT inherit a node identifier from an older
    generation.
  2. runtime: a runtime-only endpoint derived from an allowlisted protocol identity,
    such as canonical service name plus normalized HTTP route and method. It is stored
    only in the sidecar and never creates a static node.
  3. unknown: an opaque, scoped endpoint used when the allowlisted evidence cannot
    identify either a symbol or a runtime service endpoint. Unknown keys MUST be stable
    only inside their documented scope (for example, producer plus trace) and MUST NOT
    collapse unrelated missing values into one global endpoint.

Endpoint canonicalization is versioned by runtime_semantic_version. All strings are
Unicode-normalized, methods and protocol tokens use their specified canonical case,
and absent values are distinct from empty values. Raw URL queries, fragments, and
credentials are excluded.

Observation identity

An observation describes one directed runtime perspective, not one delivery. Its
canonical key is:

text
observation_key = H(canonical JSON {
  runtime_semantic_version,
  project,
  protocol,
  perspective,
  source_endpoint_key,
  target_endpoint_key,
  operation_key
})

The key excludes counters, timestamps, duration summaries, status summaries,
contribution IDs, and static database row IDs. operation_key contains only the
versioned allowlisted operation identity, such as canonical HTTP method and normalized
route template. Consequently, identical observations from multiple producers converge
while semantically different directions or operations remain distinct.

Composite publication identity

A reader-visible snapshot is identified by the atomic pair:

text
{ static_generation, runtime_generation }

The pair is the head identity, but its halves are independent immutable publications.
A runtime generation identifies runtime contributions and aggregates without embedding
a static generation. Generation-scoped symbol resolutions and overlay projections are
keyed by the pair plus resolver version. Neither half may be silently advanced while a
request is using the pair.

Protocol and perspective mapping

Ingestion parses and normalizes only versioned, allowlisted span fields into the
contribution, then derives a perspective from that evidence. The raw span payload is
not durably preserved. The HTTP mapper uses the existing service-name, HTTP method,
HTTP path or URL path, status, span-kind, and duration helpers. The route field is
normalized as a route template when the source provides one; a raw high-cardinality URL
path MUST NOT be promoted to a template by guesswork.

For the verified span kinds:

  • client (3) represents an outbound attempt. The source is the resolved local
    symbol when available, otherwise a tagged local runtime or scoped unknown endpoint.
    The target is the allowlisted remote service/HTTP endpoint or a scoped unknown
    endpoint. The perspective token is outbound.
  • server (2) represents an inbound handling attempt. The source is an allowlisted
    peer/runtime endpoint or a scoped unknown endpoint. The target is the resolved local
    handler when available, otherwise the local service/HTTP runtime endpoint. The
    perspective token is inbound.
  • internal (1) represents local work. A directed call projection is produced only
    when explicit parent/child evidence supplies two endpoints. Otherwise the
    observation remains queryable as internal activity but does not become a
    RUNTIME_CALL. The perspective token is internal.

Unsupported or absent span kinds are retained only as normalized, allowlisted non-call
observation metadata if policy allows; unsupported fields and raw payload bytes are
discarded. They MUST NOT be guessed into one of the three perspectives. Status and
duration contribute to summaries but never to observation identity.

Symbol resolution is best-effort and generation-scoped. Ambiguous matches remain
tagged runtime or unknown endpoints. Resolution MUST fail closed: it must not choose a
symbol by filename substring, short-name coincidence, timing, or insertion order.

Sidecar model

The durable sidecar is conceptually separate from the static graph store even if an
implementation initially shares the same database file. It contains these logical
relations:

`runtime_endpoints`

Stores the endpoint tag, canonical endpoint key, and canonical allowlisted identity.
For a symbol endpoint, the durable identity contains project plus qualified name but
no static generation. Generation and static database row IDs belong only to resolution
records or caches, not durable endpoint identity.

`runtime_contributions`

Stores project, producer ID, contribution ID, canonical payload hash, ingestion state,
and immutable canonical contribution metadata. States include at least staged and
finalized; an aborted transaction leaves neither state visible.

`runtime_observation_contributions`

Maps (contribution_id, observation_key) to that contribution's canonical summary.
The map entry is immutable after finalization. Replacing it requires a new contribution
identity.

`runtime_aggregates`

Stores the deterministic fold of finalized contribution-map entries for each
observation key and runtime generation. It is derived state and can be rebuilt from
finalized contributions.

`runtime_resolutions`

Maps a stable runtime or unknown endpoint key to an optional stable SymbolEndpoint
(project, qualified_name) plus its current static node/cache result, unresolved
result, or ambiguity result. Each record is keyed by project, runtime generation,
static generation, stable endpoint key, and resolver version. Resolution records are
derived caches, not endpoint or observation identity, and a record from one static
generation MUST NOT be reused for another.

`runtime_publications`

Stores immutable runtime generations independently of static generations. A separate
atomic per-project composite head pairs one current runtime generation with one current
static generation. Publication metadata includes the relevant runtime versions; the
composite head records the static index/artifact versions used for compatibility
checks.

No table above is a static graph node or edge table. The implementation MUST NOT reuse
generic json_patch edge upsert for contribution merging.

Deterministic aggregation

Before immutable summaries are stored, one contribution is normalized deterministically:

  1. canonicalize only allowlisted fields and compute the exact span identity
    (project, producer_id, producer_epoch, trace_id, span_id);
  2. deduplicate records with the same span identity and identical normalized bytes;
  3. reject the entire contribution if one span identity has different normalized bytes;
  4. group the remaining unique normalized records by observation_key;
  5. after keyed exact-span deduplication/set union supplies idempotence, fold integer
    counts and duration sums, fixed status buckets, and fixed-schema histogram bins over
    the unique normalized span map using associative and commutative exact addition; and
  6. sort keys and canonical-serialize the contribution map before hashing or storage.

The normalized contribution map and its immutable summaries MUST be byte-identical for
every input ordering and worker partitioning of the same unique normalized span set.
Discarded or unsupported fields cannot change the payload hash.

For each observation, aggregation is a map union keyed by contribution identity:

text
aggregate(observation) = fold(sorted union {
  contribution_id -> canonical contribution summary
})

Union of identical key/value entries is idempotent. A differing value under the same
key is a conflict, not a winner selection. Map union is associative and commutative;
sorting by contribution ID before serialization makes the resulting bytes independent
of producer, retry, transaction, or worker order.

Canonical contribution summaries MAY contain integer count, integer duration sum in a
fixed unit, fixed-schema status buckets, and a mergeable duration distribution whose
encoding is pinned by runtime_semantic_version. Aggregate totals are exact integer
sums over the contribution map. Derived display values are computed after the fold.

Percentiles are not additive. A batch p99 MUST NOT be averaged, weighted-averaged, or
selected as the aggregate p99. The existing p99 helper may summarize a single raw batch
for diagnostics, but a published cross-contribution p99 requires a versioned mergeable
distribution (for example fixed histogram boundaries). Without such a distribution,
the aggregate p99 is unavailable.

Canonical aggregate JSON MUST be byte-identical for every permutation and
parenthesization of the same finalized contribution set.

Ingestion and publication transaction

Full wire parsing, canonicalization, payload hashing, and every validation that does
not depend on mutable project state happen before acquiring the publication lock.
Publication is then serialized by one shared per-project publication lock used by both
static and runtime publishers. An implementation MUST NOT introduce an independent
runtime lock that permits lost updates to either half of the composite head.

Within that lock, a runtime publisher:

  1. begins a write transaction with BEGIN IMMEDIATE;
  2. reads and pins the current composite head and validates compatibility versions;
  3. performs state-dependent authorization, quota, producer identity, deduplication,
    contribution conflict, and expected-head checks;
  4. stages all endpoints, immutable contribution-map entries, and rebuilt aggregates;
  5. allocates an opaque runtime generation independent of the pinned static generation;
  6. durably finalizes every contribution in the publication;
  7. writes the immutable runtime publication and atomically changes only the runtime
    half of the composite head, preserving its pinned static generation;
  8. commits all changes with COMMIT; and
  9. releases the per-project publication lock.

Every validation or write failure before commit uses ROLLBACK. There is no visible
partial contribution, aggregate, generation, or head. Finalization and head movement
are in the same transaction, so a success response is sent only after durable commit.

Static publication follows the same lock discipline and atomically changes only the
static half of the composite head, preserving its pinned runtime generation. It does
not create a new runtime generation merely because source was reindexed. Resolution
records for the new {static_generation, runtime_generation} pair are recomputed or
materialized under their pair-and-resolver-version key; records from the old static
generation are never treated as resolutions for the new one.

Reader contract

A request that opts into runtime data pins one composite head at request start. Every
page, endpoint resolution, aggregate, and overlay edge in that request is read from
that pair. Default requests pin only their existing static request scope and do not pay
for or expose the overlay.

Pagination cursors encode the project, composite head, query shape, overlay mode, and
runtime semantic version. If the current head differs, a subsequent request with that
cursor returns a stale-cursor error; it never resumes against mixed generations.
Readers MAY explicitly request a still-retained historical composite head. Missing or
garbage-collected heads fail explicitly.

`RUNTIME_CALL` overlay

Runtime call projection is an explicit read option. It presents directed overlay edges
derived from one pinned runtime generation. These edges:

  • use the distinct type RUNTIME_CALL;
  • carry endpoint tags so callers can distinguish symbol, runtime, and unknown ends;
  • include deterministic aggregate summaries and the composite head identity;
  • never mutate, replace, increase confidence on, or suppress a static CALLS edge;
  • are omitted from existing search, architecture, and default trace responses unless
    the caller requests the overlay; and
  • are regenerated when either half of the composite publication changes.

If a projected runtime call has symbol endpoints, it references their qualified-name
identity within the pinned static generation. Runtime-only and unknown endpoints remain
sidecar objects and are not fabricated as graph nodes.

Version boundaries

Compatibility is divided deliberately:

  • wire_version: request and response envelope, field encodings, and retry result;
  • runtime_schema_version: sidecar tables, indexes, and migration compatibility;
  • runtime_semantic_version: canonical endpoint/observation keys, mappings, and
    aggregation meaning;
  • runtime_artifact_version: exported/imported sidecar artifact layout;
  • static_semantic_index_version: source-derived graph semantics; and
  • static_artifact_schema_version: static artifact representation.

A change bumps only the boundary it changes. A runtime semantic change can invalidate
runtime generations without changing static semantic index version. A storage-only
migration can change runtime schema version without changing observation identity.
Import rejects incompatible artifact versions before mutation. This document changes
none of these versions.

Security, privacy, and cardinality

Ingestion is project-authorized and producer-authenticated. Producer IDs, contribution
IDs, and project scope are server-validated; caller-provided project or producer fields
do not grant access. Reads apply the same project authorization to runtime and static
data.

Only allowlisted attributes enter canonical identities or summaries. Credentials,
authorization data, cookies, request/response bodies, query strings, fragments,
arbitrary baggage, and raw exception text are rejected or discarded before durable
storage. Logs and errors use opaque IDs and counts, not rejected payloads.

The service enforces bounded request bytes, records, endpoints, observations, attribute
lengths, route-template segments, status buckets, and contribution-map growth. Raw URL
paths are not accepted as unbounded route labels. Unknown endpoints are scoped to avoid
both accidental global collapse and attacker-controlled global cardinality. Quotas and
retention apply per project and producer. Limit failure is all-or-nothing and does not
publish a truncated contribution unless a future wire version explicitly defines that
behavior.

Failure and recovery matrix

Condition Required disposition Visible state
Same contribution ID, same payload hash Idempotent success; return prior result Unchanged
Same contribution ID, different payload hash Conflict; reject entire request Unchanged
Unauthorized project or producer Reject before staging Unchanged
Invalid/unsupported wire or semantic version Reject before staging Unchanged
Invalid endpoint, attribute, or cardinality limit Reject entire contribution Unchanged
Ambiguous symbol resolution Keep runtime/unknown tag; do not guess Valid sidecar observation only
Client-supplied expected composite head differs from the head read under lock Reject as stale; retry from the newly read head Unchanged
Failure while staging or aggregating ROLLBACK No partial state
Crash before commit Database recovery rolls back the transaction Prior head remains
Commit succeeds but response is lost Producer retry resolves idempotently One finalized contribution
Derived aggregate missing or corrupt Rebuild from finalized contribution map before publish Prior valid head remains
Cursor references a different or removed head Return stale/missing cursor error No mixed-generation read
Unsupported span kind Retain only normalized, allowlisted non-call observation metadata; discard unsupported fields and raw payload bytes No guessed call overlay
No mergeable duration distribution Publish counts/sums; report p99 unavailable No averaged p99

Alternatives considered

Patch static `CALLS` edges

Benefit: existing graph queries would see runtime evidence with no overlay option.

Trade-offs: it conflates observed and source-derived semantics, lets sampled or hostile
telemetry alter the static graph, cannot represent runtime-only endpoints honestly,
couples runtime retention to static indexing, and makes rollback and provenance
ambiguous. It is rejected.

Fabricate static nodes for runtime services and unknown peers

Benefit: all endpoints would fit the existing graph node/edge shape.

Trade-offs: fabricated nodes look source-derived, create unstable identities and high
cardinality, collide with future symbol resolution, and pollute architecture/search
results. It is rejected.

Store persistent runtime edges in the default graph

Benefit: generic edge uniqueness and json_patch upsert are already available.

Trade-offs: generic uniqueness has no immutable producer-contribution dimension;
json_patch is neither the required commutative map union nor a conflict detector;
default readers would unexpectedly mix generations and data classes. It is rejected.

Keep runtime observations entirely ephemeral

Benefit: minimal schema, retention, and migration burden; no durable sensitive data.

Trade-offs: retries cannot be made idempotent across restarts, results are not
reproducible or pageable, multi-producer aggregation is unstable, and a reader cannot
pin a coherent composite publication. It is rejected as the primary model, though
ephemeral pre-validation remains useful.

Durable versioned sidecar with an opt-in overlay (selected)

Benefits: preserves static semantics, gives runtime-only endpoints an honest identity,
supports deterministic replay and aggregation, isolates retention and authorization,
and provides atomic cross-generation reads.

Trade-offs: adds schema and version boundaries, a shared publication lock, endpoint
resolution, aggregate rebuilds, overlay-aware APIs, and explicit operational limits.
These costs are accepted because they make provenance, concurrency, failure recovery,
and compatibility testable rather than implicit.

Bounded implementation packages

Implementation remains deferred. The following packages are intentionally ordered and
bounded; each requires its own reproduce-first tests and review.

  1. Versioned wire validation and canonicalization. Define authenticated producer
    identity and epoch, contribution ID, exact span-identity deduplication, conflicting
    duplicate rejection, canonical contribution maps and payload hashes, allowlisted
    HTTP/span mapping, limits, and dry-run validation. No store mutation.
  2. Sidecar schema and immutable contributions. Add runtime schema migrations,
    endpoints, contribution records, observation-contribution maps, idempotent retry,
    conflict rejection, and transactional rollback. No publication head or overlay.
  3. Deterministic aggregation. Add canonical intra-contribution grouping, keyed
    exact-span idempotent set union/deduplication, associative and commutative exact
    integer/status/histogram addition, cross-contribution map-union folds, fixed
    mergeable duration distributions, input/worker/merge-order permutation tests,
    rebuild verification, and explicit unavailable p99 behavior.
  4. Generation-scoped resolution and publication. Add the shared per-project lock,
    independent runtime generation allocation, durable finalization, pair-and-version
    resolution records, atomic composite head updates that preserve the other half, and
    crash/retry recovery tests.
  5. Pinned read and RUNTIME_CALL overlay. Add opt-in queries, tagged endpoints,
    request-scoped composite pins, stale cursors, historical-head handling, and proofs
    that default static tools remain byte-for-byte unchanged.
  6. Artifact, retention, and operational hardening. Add versioned runtime artifact
    import/export, quota/retention policy, authorization audits, corruption rebuild,
    migration/rollback tests, metrics, and multi-producer concurrency tests.

No package may silently absorb a later package's public surface or persistence model.

Acceptance invariants

An implementation of this contract is acceptable only if all of these hold:

  1. Static nodes and CALLS edges are byte-identical with runtime ingestion enabled or
    disabled for the same source input.
  2. Existing search, architecture, and default trace responses exclude runtime data.
  3. Runtime data appears only through an explicit, versioned overlay request.
  4. Same producer contribution plus same payload is idempotent across retries and
    restarts; a different payload under that identity is a no-mutation conflict.
  5. Normalized contribution and aggregate bytes are identical for every input, delivery,
    worker, and merge order of the same unique normalized span set, including identical
    duplicate replay; a conflicting duplicate span identity rejects the whole
    contribution without mutation.
  6. Aggregate p99 is derived only from a pinned mergeable distribution and is otherwise
    unavailable; producer p99 values are never averaged.
  7. Symbol resolution is qualified-name based, generation-scoped, and fail-closed on
    ambiguity; runtime and unknown endpoints never fabricate static nodes.
  8. Runtime generation state contains all of its finalized contributions, endpoints,
    and aggregates, and reader visibility requires the separate atomic composite-head
    update; the transaction exposes both the complete generation and head update, or
    neither.
  9. Every overlay read and page uses one immutable
    {static_generation, runtime_generation} pair; stale cursors fail explicitly.
  10. Static and runtime publication serialize through the same per-project lock; each
    publisher changes only its own half of the composite head and preserves the other,
    and symbol-resolution records are never reused across static generations or
    resolver versions.
  11. Unsupported span kinds, missing identities, and limit violations never become
    guessed calls or partial publications.
  12. Authorization, privacy filtering, quotas, and retention are enforced before data
    becomes durable or reader-visible.
  13. Wire, runtime schema, runtime semantic, runtime artifact, static semantic, and
    static artifact versions can change independently according to their boundaries.
  14. Documentation-only adoption leaves all six version values and all runtime behavior
    unchanged.

SECURITY DISCLOSURE (`docs/SECURITY-DISCLOSURE.md`)

Security Disclosure & Handling Process

This document explains how security reports are handled for
codebase-memory-mcp — what happens after you report a vulnerability, what you
can expect from us, and how disclosure and credit work.

For how to report a vulnerability and what is in scope, see
SECURITY.md. This document covers the process after a
report arrives.

This is a solo, volunteer-maintained project. Everything below is handled
on a good-faith, best-effort basis. The timeframes are honest targets we aim
to beat — not contractual guarantees. If something will take longer, we will
tell you and keep you updated rather than go silent.

Principles

We follow coordinated disclosure:

  1. Fix privately, disclose publicly. Details of an unfixed vulnerability are
    never discussed in the open. We develop and validate the fix in private, ship
    a release, and only then disclose.
  2. Patch before publicity. A fixed release is always available before the
    vulnerability is described publicly, so users can update immediately.
  3. Credit the researcher. Public credit by default; anonymity on request.
  4. A bug fixed once should stay fixed. Every fix ships with a regression
    test or guard so the same class of issue cannot silently return.

What happens after you report

Step What we do Target (best-effort)
1. Acknowledge Confirm we received your report and are looking at it. within 7 days (usually much sooner)
2. Triage & severity Reproduce the issue and assign a severity (CVSS). within 14 days
3. Fix privately Develop the fix in a private environment, with a regression guard, and validate it across all supported platforms (Linux, macOS, Windows) under full CI. severity-dependent
4. You verify We invite you (read-only) to confirm the fix resolves the issue and that the guard prevents regression. Your sign-off is welcomed; an unresponsive reporter will not indefinitely block a release. —
5. Release Merge the fix and cut a patched release promptly. as fast as severity warrants
6. Disclose Publish a GitHub Security Advisory, request a CVE (GitHub is a CNA), and credit you. after a short upgrade window

Overall fix timeline: we aim to resolve and release a fix within 90 days
of triage, and much faster for high-severity issues. Critical, actively
exploitable issues are handled with the highest priority.

Severity

We assess severity using CVSS and prioritise accordingly. Roughly:

  • Critical / High — remote code execution, sandbox/scope escape, supply-chain
    compromise. Prioritised; expedited release.
  • Medium — issues requiring local access, non-default configuration, or
    significant user interaction.
  • Low — defense-in-depth gaps, hardening, information exposure with limited
    impact.

Credit & CVE

  • You are credited by name/handle in the published advisory unless you ask to
    remain anonymous.
  • A CVE identifier is requested for each distinct vulnerability via the
    GitHub Security Advisory (one CVE per vulnerability, not per report — a single
    report may yield several).
  • The advisory lists the affected and patched version ranges so downstream
    tooling (e.g. Dependabot) can alert users automatically.

Safe harbor

We will not pursue or support legal action against researchers who act in
good faith, meaning you:

  • only access, modify, or store data in your own test environment;
  • avoid privacy violations, data destruction, and degradation of service for
    others;
  • give us a reasonable opportunity to fix the issue before disclosing it
    publicly;
  • do not exploit the issue beyond the minimum necessary to demonstrate it.

Good-faith research conducted under this policy is considered authorised, and we
will work with you, not against you.

What we ask of you

  • Report privately (see SECURITY.md) — not as a public
    issue, PR, or social-media post.
  • Give us reasonable time to fix before any public write-up.
  • Provide enough detail to reproduce (affected version, steps, impact).

Thank you for helping keep a tool used by developers worldwide safe. 🙏


SMART CI (`docs/SMART_CI.md`)

Smart CI: pull-request test selection on GitHub Actions

Smart CI runs, for each pull request, only the CI jobs and tests that the
change can reach. This page explains how this repository does it and how
another repository on GitHub Actions could do the same with the released
codebase-memory-mcp binary.

Status: experimental. Every answer carries "experimental": true. The
selection narrows real test runs only while the repository variable
TEST_IMPACT_MODE is gate; otherwise it runs in report-only (shadow) mode.

Read the scope section before you start. The lane layer is a pattern
any repository can copy. The test layer narrows only C/C++ code whose tests
follow this repository's test-framework conventions (or declared
equivalents), and only with a per-test coverage map that you must produce
yourself. In any other repository the engine answers "run everything". That
is safe, but it saves nothing.

The guarantee

Smart CI never runs fewer tests unless it can show that the omitted tests
cannot be affected. When it is unsure, it runs everything:

  • a path that no rule knows, an empty or truncated file list, or a change to
    the selection's own configuration, tooling or workflows;
  • a shallow clone, a diff that cannot be read in full, or an invalid config;
  • no coverage map, or a coverage map that is not admitted (wrong commit, more
    than 7 days old, made for another platform or harness);
  • a change that reaches the test runner's main, or a test-runner role
    (below) that no test names;
  • a role table entry the engine cannot read, or a macro naming a role's
    literal that it cannot follow;
  • any failure of the selection job itself.

Each of these makes the run cost more CI minutes. None of them loses coverage.

Two layers

text
changed files ──► Layer 1: lane selector ──► which CI jobs run
                  (scripts/ci/select-lanes.sh, your own rules)
                                   │
                                   ▼  (the Linux x86_64 test leg only, in this repo)
merge base + PR ─► Layer 2: test-impact engine ─► which suites / tests run
                  (codebase-memory-mcp test-impact select
                   + team-artifact bundle from main)
Layer 1: lanes Layer 2: tests
Question Which jobs (lint, test legs, smoke, sanitizers) does this PR need? Inside a test job, which suites and tests?
Input The PR's changed-file list Git history, the code graph, a per-test coverage map
Implementation scripts/ci/select-lanes.sh (repository-specific rules) The binary's test-impact select subcommand
Unknown input Every lane runs Every test runs (run_all with reasons)
Portable? As a pattern: you write your own rules C/C++ with supported test conventions only (see below)

Can your repository use it?

Piece General? What you provide
Lane selection (Layer 1) Pattern only Your own path → category → lanes rules. select-lanes.sh encodes this repository's paths.
Engine (test-impact select) Ships in the binary Nothing to build.
Source languages it can narrow C family only: .c .h .cc .cpp .hpp .m .mm .cxx .hh Every other changed file must match a config rule (ignore, referencing_tests, ...). Otherwise the answer is run_all (UNMAPPED_FILE).
Test model C family only The c-cbm preset (this repository's TEST/SUITE/RUN_TEST/RUN_SELECTED_SUITE macros, see tests/test_framework.h), or custom tests.conventions records for equivalent C macros.
Coverage map Required to narrow at all Format-1 files (functions.tsv, tests.tsv, meta.json). This repository produces them with an LLVM-instrumented runner and scripts/test-impact/coverage-map.py, which are both specific to its harness.
Runner filter Pattern A test runner that can run a suite / suite:test list and fails when a listed test does not exist.

The other presets (pytest, go, junit, gtest, rust, jest) are
accepted switches, but the engine does not model them. An enabled gtest
blocks narrowing for C++ files. Custom tests.conventions are covered by the
model's unit tests. The engine's end-to-end tests use c-cbm only. Treat
custom conventions as unproven until you have shadow evidence.

Without admitted coverage the engine marks every suite whole, and the lane
reports run_all. Static reachability alone never omits a suite.

Prerequisites

  1. The binary, pinned. Use the same release in the producer and the PR
    jobs. Download that release's Linux archive and verify it against the
    release's checksums.txt, as this repository's shadow job does
    (scripts/ci/test-impact-shadow.sh --fetch-release):

    bash
    CBM_VERSION=vX.Y.Z   # a release that contains the features you use (see below)
    asset=codebase-memory-mcp-linux-amd64.tar.gz
    gh release download "$CBM_VERSION" --repo DeusData/codebase-memory-mcp \
      --pattern "$asset" --pattern checksums.txt --dir "$RUNNER_TEMP/cbm"
    (cd "$RUNNER_TEMP/cbm" && grep " $asset\$" checksums.txt | sha256sum -c - &&
      tar -xzf "$asset" codebase-memory-mcp)
    "$RUNNER_TEMP/cbm/codebase-memory-mcp" --version

    For a quick start, install.sh installs the latest release with the same
    checks (HTTPS only, SHA-256 against checksums.txt, the archive's member
    list, --version); --skip-config skips agent configuration, which a CI
    runner does not need. It has no version flag, so it is not a pin.

    Version note. v0.12.0 and v0.12.1 have test-impact select/publish.
    They do not have key: "path", unreferenced, --vetoed-out or
    run_all_rules; those ship in a later release. With either version, a config
    that uses those keys is invalid, and every answer is run_all. Until that
    release, build the binary from source (make -f Makefile.cbm cbm), as this
    repository's producer and select-tests jobs do.

  2. Full git history. Check out with fetch-depth: 0 in every job that
    runs the engine. The engine verifies the history it diffs and refuses a
    shallow clone (GIT_UNAVAILABLE, everything runs).

  3. A runner that runs a named subset. See "The test job" below.

  4. A coverage producer on the default branch that writes the format-1 map
    for every test, child processes included. A test whose coverage is
    uncertain must be marked incomplete. An incomplete test is always
    selected.

  5. gh, python3, and GH_TOKEN with actions: read in the PR job, to
    fetch the bundle. GitHub-hosted runners include gh and python3.

Recipe

Copy these files from this repository and adapt the marked lines:

File Adapt
scripts/ci/test-impact-bundle.sh WORKFLOW= (producer file name) and the hard-coded main branch filter.
scripts/ci/test-impact-select.sh --lane unit (your lane name) and the PLATFORM default (must equal the producer's).
scripts/test-impact/selection.py Nothing, if your runner accepts suite / suite:test lines.

(a) Producer: publish a bundle for every commit on main

The bundle holds the commit's frozen graph, its coverage map and a
receipt.json that binds both by SHA-256. It is uploaded as the Actions
artifact test-impact-<sha>. test-impact-bundle.sh accepts a bundle only
from a successful push, schedule or workflow_dispatch run of the
producer workflow, in the same repository, on main, whose head is exactly
the requested commit.

text
/* Detailed source-code truncated for AI context efficiency. */

This repository's producer (.github/workflows/test-impact-artifact.yml with
scripts/ci/test-impact-publish.sh) adds an incremental mode. A push re-runs
coverage only for the suites that its own selection picks and merges them into
the previous map (coverage-merge.py). The graph is built incrementally from
the previous bundle (--previous). The weekly run rebuilds everything and
fails if the incremental graph topology differs from the full one. The
incremental mode is an optimisation. A full coverage run on every push is
simpler and just as correct.

The receipt keeps the time of the oldest coverage observation. An incremental
publish carries it forward unchanged. A map whose oldest observation is more
than 7 days old is refused (TOO_OLD), so a full refresh has to run more often
than that.

(b) PR job, shadow mode (report only)

yaml
test-impact-shadow:
    runs-on: ubuntu-latest
    continue-on-error: true          # can never block or pass a merge
    permissions:
      contents: read
      actions: read                  # the merge base's bundle
    steps:
      - uses: actions/checkout@<pinned-sha>
        with:
          fetch-depth: 0
          persist-credentials: false
      - name: Install codebase-memory-mcp
        run: <the pinned install from Prerequisites>
      - name: Select with the team artifact (shadow)
        env:
          GH_TOKEN: ${{ github.token }}
        run: scripts/ci/test-impact-select.sh --binary "$RUNNER_TEMP/cbm/codebase-memory-mcp" --out "$RUNNER_TEMP/test-impact"
      - uses: actions/upload-artifact@<pinned-sha>
        if: ${{ always() }}
        with:
          name: test-impact-prediction
          path: |
            ${{ runner.temp }}/test-impact/selection-engine.json
            ${{ runner.temp }}/test-impact/summary-engine.md

Leave this job out of your aggregate required check. What
test-impact-select.sh does:

  1. Takes the base side of the PR merge commit (HEAD^1) and fetches that
    commit's bundle.
  2. If the producer skipped that commit (merge bursts: GitHub keeps one pending
    run per concurrency group), it uses the nearest first-parent ancestor that
    has a bundle, up to 20 commits back, and diffs against that ancestor. If the
    PR reverts a change that main made after the ancestor, the ancestor's map
    cannot speak for that file, so the script selects without coverage (the
    revert guard).
  3. Passes --artifact-verified only for a bundle that it fetched and checked
    itself. Nothing inside a bundle can assert where the bundle came from.
  4. Runs test-impact select and writes selection-engine.json. It also
    writes a Markdown summary to the job's step summary.

The engine reads .codebase-memory.json from the selection base (the merge
base, or the ancestor whose bundle was admitted), not from the PR. A PR cannot
loosen its own selection. A PR that changes the config runs
everything (builtin:test-impact-config).

(c) Gate job and the test job

yaml
select-tests:
    if: ${{ vars.TEST_IMPACT_MODE == 'gate' }}
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      actions: read
    outputs:
      selection: ${{ steps.select.outputs.selection }}
    steps:
      - uses: actions/checkout@<pinned-sha>
        with:
          fetch-depth: 0
          persist-credentials: false
      - name: Install codebase-memory-mcp
        run: <the pinned install from Prerequisites>
      - id: select
        env:
          GH_TOKEN: ${{ github.token }}
        run: scripts/ci/test-impact-select.sh --binary "$RUNNER_TEMP/cbm/codebase-memory-mcp" --out "$RUNNER_TEMP/test-impact" --gate "$RUNNER_TEMP/test-selection"
      - if: ${{ steps.select.outputs.selection == 'narrowed' }}
        uses: actions/upload-artifact@<pinned-sha>
        with:
          name: test-selection
          path: ${{ runner.temp }}/test-selection
          if-no-files-found: error

  test:
    needs: [select-tests]
    if: ${{ !cancelled() }}          # a skipped or failed select-tests must NOT skip the tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<pinned-sha>
      - if: ${{ needs.select-tests.result == 'success' && needs.select-tests.outputs.selection == 'narrowed' }}
        uses: actions/download-artifact@<pinned-sha>
        with:
          name: test-selection
          path: ${{ runner.temp }}/test-selection
      - run: ./ci/run-tests.sh          # YOURS: narrows only if test-only.txt is present

With --gate DIR, the script runs selection.py filter … --lane unit and
writes selection=narrowed or selection=all to $GITHUB_OUTPUT. A narrowed
answer writes:

  • DIR/test-only.txt: one token per line, either suite (run the whole
    suite) or suite:test;
  • DIR/optional-suites.txt: selected suites that are registered only under a
    preprocessor condition. A build may lack them.

Everything else means all: a run_all answer, an unknown schema or filter
encoding, a filter that disagrees with the lane's suite list, a crash, and a
lane that selects nothing. The test job then gets no selection and runs every
test.

The test job's runner has to keep the same contract as this repository's
runner (scripts/test.sh → scripts/run-tests-parallel.sh →
CBM_TEST_ONLY_FILE, read by tests/test_main.c):

  • launch only the suites that the tokens name, and only the named tests inside
    a suite:test suite;
  • fail when the selection names a suite the build does not register,
    unless optional-suites.txt lists it;
  • fail when a selected test cannot be found;
  • fail when a selection was promised but the file is missing or empty.

A selection that is silently ignored, or partly ignored, would turn a narrowing
into missed coverage.

A runner that starts itself again. Tests often spawn the test binary as a
child (<runner> __probe_x ...) and its main hands over to a probe. A change
that reaches main reaches every test, so the engine runs everything
(RUNNER_REACHED). List the probes instead in a table in the runner's source,
one entry per line, as tests/test_main.c does:

c
TF_ROLE(TF_PHASE_SEAMED, "__cbm_sockprobe", tf_maybe_run_socket_probe),
TF_ROLE(TF_PHASE_WORKER, "--index-worker", tf_maybe_run_index_worker),
TF_ROLE(TF_PHASE_SPAWNER, "CBM_INDEX_SUPERVISOR", tf_maybe_run_index_worker),

The engine reads the table: a change that only a probe's handler reaches
selects the tests whose source names one of that handler's literals (the argv
the child gets, or a switch that lets production code start it), also through
a #define that names the literal. The runner's own use of a literal names no
spawner. A reached handler that no test names, a table entry the engine cannot
read, and a change reaching main any other way still run everything; the
engine note names the call chain into main.

Only the legs the coverage speaks for should narrow. Here the coverage
comes from one Linux x86_64 clang-21 producer. Only the Linux x86_64 test leg
downloads the selection (runner.os == 'Linux' && runner.arch == 'X64' in
_test.yml). The arm64, macOS and Windows legs always run everything,
because code under architecture or platform #ifdefs has static evidence
only there. That leg builds with gcc, not clang: code under a compiler #if
has static evidence only too, and --platform names the producer's platform,
so the engine's platform check admits the map by construction.

(d) Flip the variable

Shadow is the default: the variable is unset, select-tests is skipped, and
every test runs. When the evidence is in, set the repository variable:

bash
gh variable set TEST_IMPACT_MODE --body gate      # gate
gh variable delete TEST_IMPACT_MODE               # back to shadow

Do not add select-tests to your required checks or to the aggregate gate.
When it breaks, PRs cost more minutes. They do not lose coverage.

Optional: Layer 1 (lanes)

scripts/ci/select-lanes.sh is this repository's lane rule set. A changes
job lists the PR's files (the pulls/{n}/files API, with a git fallback),
runs the selector, and writes tier, full, lanes (a JSON list) and
reasons to $GITHUB_OUTPUT. Each lane job then runs
if: contains(fromJSON(needs.changes.outputs.lanes), '<lane>'). A single
aggregate job (ci-ok, scripts/ci/require-all-green.sh) is the only required
check. It fails when a selected lane was skipped, cancelled or missing.

For your own version, keep these properties:

  • An unknown path, an empty list, workflow and harness changes, and a list
    truncated at the API's 3000-file cap all select every lane.
  • The selector runs from a checkout without credentials, after the step that
    holds the token.
  • Replay your PR history through the selector and check that every real
    failure had its catching lane selected. This repository's equivalent is
    scripts/test-impact/replay-selector.sh.

Configuration: `.codebase-memory.json` → `test_impact`

Key Values Default
version 1 (required) —
tests.presets {"c-cbm": true, ...}. Unknown names are an error. c-cbm off, the six others on
tests.conventions C-family macro records: case, suite, registration, suite_registration none
rules ordered list, see below none (built-ins only)
lanes list of {name, suites, exclude_suites?, default: "run"|"skip", narrow?} one lane unit: all suites except @perf, narrow: true
unmapped_files "run_all" (the only accepted value) run_all
coverage.compatibility_paths repo-relative files that the coverage map depends on (harness, runner, build flags) none

Rules. Each rule is {id, paths, action, ...}. paths are gitignore-style
globs with {a,b} alternatives. A pattern with a / is anchored at the
repository root. Without a /, it matches at any depth. ** matches across
directories. Project rules come first, then the built-ins. For each changed
path, the first matching rule wins, so order matters.

action Effect
run_all Any match runs everything. The summary names the rule (run_all_rules).
ignore The path selects nothing.
referencing_tests Selects the tests whose source contains the path as text. The engine tries the path itself, then its parent directories down to the key, each with and without its first segment. key: "path_segment:N" stops at segment N (zero-based). key: "path" tries the exact path only. If no test names the path, everything runs, unless unreferenced: "ignore" is set.
suites Runs the named suites whole.
lanes Activates the named lanes (for lanes with default: "skip").

Built-in rules, applied after yours:

id paths action
builtin:test-impact-config .codebase-memory.json run_all
builtin:build Makefile*, CMakeLists.txt, *.cmake, meson.build, BUILD*, WORKSPACE, package*.json, go.mod, go.sum, Cargo.*, pom.xml, build.gradle*, pyproject.toml, setup.*, requirements*.txt run_all
builtin:ci .github/**, .gitlab-ci.yml, .circleci/**, Jenkinsfile*, .buildkite/**, azure-pipelines*.yml run_all
builtin:vendor vendor*/**, third_party/** run_all
builtin:conftest conftest.py run_all
builtin:documentation docs/**, **/*.md, LICENSE* ignore

Lanes. A lane that you declare yourself does not narrow unless you set
"narrow": true. signals is accepted but has no effect yet.

This repository's file, explained:

Rule Why
harness → run_all The test framework and the runner's main touch every suite.
engine → run_all The selection's own code and config. A change to them could loosen what is selected.
build → run_all Build and runner scripts: the file list cannot tell a flag change from a harmless one. /.gitattributes is anchored at the root.
grammars → run_all Vendored parsers are not modelled per test.
fixtures → referencing_tests, path_segment:2 tests/fixtures/<name>/... selects the tests that mention the fixture, down to tests/fixtures/<name>.
contracts → ignore Shell, Python and data contract tests run in every leg's contract step, which is never narrowed.
notices → ignore Release notices.
repo-data → referencing_tests, path, unreferenced: ignore Scripts, docs and packaging. A C test whose source names one of these files (its path, as text) is selected. A file that no test names selects nothing.

coverage.compatibility_paths lists the harness and coverage tools. Changing
the bytes of any of them, or the --platform label, makes the existing coverage
map incompatible until the producer publishes a new one.

Rollout and evidence

  1. Shadow. Run (a) and (b). Leave TEST_IMPACT_MODE unset.

  2. Measure. For each PR run, compare selection-engine.json with what
    failed in that run's full test legs.

    • A miss is a test that failed in the full run but was not selected.
      One unexplained miss blocks the flip.
    • Savings are the suites and tests that were not selected.

    Nothing in this repository computes misses for Layer 2 yet. Layer 1's
    history replay (replay-selector.sh) is the model to follow.

  3. Gate. Set TEST_IMPACT_MODE=gate. Keep the shadow job running, so that
    the evidence keeps coming.

Reading the step summary (### test-impact engine):

Line Meaning
decision: \selected`/`run_all`/`nothing`, then run-all reasons [...]` The top-level answer and why everything runs. decided by rule(s) [...] names the matched run_all rule ids.
team artifact: fetched (ancestor …) / none; coverage \admitted`/`rejected` [reasons]/`unavailable`` Whether a bundle was used, and whether its coverage was admitted.
engine note: … The engine's first diagnostic line, such as the path that caused UNMAPPED_FILE, or the call chain into the runner's main for RUNNER_REACHED.
lane \unit` (selected): N whole [REASON n, …], M narrowed (K tests)` Per lane: suites that run whole (with a histogram of reasons), and suites narrowed to K tests.
the runner-main veto (RUNNER_REACHED) discarded this selection: The change reached the runner's main, so everything runs. The lines below show what would have been selected otherwise.
PR CI runs N selection tokens / PR CI runs every test (selection exit N) Gate mode only: what the test legs received.

Common run-all reasons:

Reason Cause
RULE_RUN_ALL A run_all rule matched (see run_all_rules).
UNMAPPED_FILE A changed file matched no rule and is not C-family source.
RULE_TARGET_UNKNOWN A referencing_tests path that no test names (without unreferenced: "ignore").
RUNNER_REACHED The change reaches the test runner's main (or a child role none of whose literals a test names).
GIT_UNAVAILABLE No git, a shallow clone, or a diff that cannot be read in full.
CONFIG_INVALID / POLICY_UNAVAILABLE The merge base's config or rules do not parse.

Reasons per suite: ARTIFACT_REJECTED (no admitted coverage), SETUP_HIT /
SETUP_UNKNOWN (the suite's between-test setup ran changed code, or its
coverage is incomplete), INVENTORY_UNKNOWN (the suite cannot be read with
certainty), CONDITIONAL (#if-registered), STATIC / CHANGED / COVERAGE
/ RULE (the evidence that selected it).

Failure modes

Every row below costs CI minutes, not coverage.

What happens Result
No bundle for the merge base Nearest bundled ancestor, up to 20 first-parent commits back. Otherwise no coverage, and everything runs.
Bundle fetch fails (token, API) Warning, no coverage, and everything runs.
The PR reverts a change that main made after the ancestor Revert guard: no coverage, and everything runs.
Coverage older than 7 days, or a platform/compatibility mismatch Map refused: every suite runs whole.
Shallow checkout GIT_UNAVAILABLE: everything runs.
Engine deadline (default 1800 s) or crash The job fails. The test job gets no selection, so everything runs.
select-tests skipped (shadow) or failed Everything runs (!cancelled() on the test job).
The selection names a suite or test that the build lacks The test run fails: a plumbing fault, reported, never skipped.

Known limits

  • Narrowing covers C-family source and C test conventions only. Everything
    else is decided by rules or runs everything.
  • The coverage map format and its producer come from this repository's LLVM
    toolchain and harness. You need your own equivalent.
  • One producer platform. The selection narrows only the leg that matches the
    producer's platform; every other leg runs everything. Narrowing more legs
    needs a producer (and a bundle) per platform.
  • The weekly full refresh and the 7-day age limit are the same length. Each
    week, when the refresh runs late, there can be a short window in which maps
    are refused and every test runs.
  • In merge bursts the producer skips commits. The ancestor fallback covers up
    to 20 of them.
  • The answer schema is cbm.test_impact.v0 and is marked experimental. It may
    change between releases. Pin the binary.

Cbmignore (`docs/cbmignore.md`)

`.cbmignore` — Excluding Files from Indexing

.cbmignore is a project-specific ignore file that controls which files the
indexer sees. It uses gitignore-style syntax and is read from the root of
the indexed directory
(<repo>/.cbmignore). Nested .cbmignore files in
subdirectories are not read.

It applies at file discovery time — the directory walk that selects files
for parsing. Every indexing path uses the same discovery: the initial
index_repository, manual re-indexing, and background auto-sync. A path
matched by .cbmignore never enters the graph. Changes to .cbmignore take
effect on the next (re-)index.

Unlike .gitignore, it has no effect on git itself — it only shapes what the
indexer sees. Commit it to share indexing excludes with your team, or list it
in .gitignore to keep personal excludes untracked.

To verify it works: directory subtrees skipped during discovery are reported
in the index_repository response under excluded
({"dirs": [up to 25 paths], "count": <total>, "truncated": }).

Syntax

One pattern per line. Blank lines are ignored, lines starting with # are
comments, and trailing whitespace is trimmed.

Feature Meaning
* matches any run of characters, except /
? matches exactly one character, except /
** matches across directory boundaries (**/name, dir/**, a/**/b)
[abc], [a-z] character classes; [!a-z] / [^a-z] negate the class
trailing / pattern matches directories only
/ anywhere else anchors the pattern to the repo root
no / in pattern matches the file/directory name at any depth
leading ! negation — re-includes a previously matched path; the last matching pattern wins

Examples:

gitignore
# Generated protobuf output, anywhere in the tree
*.pb.go

# A specific top-level directory (leading / anchors to the repo root)
/third_party/

# Any directory named "snapshots", at any depth (trailing / = directories only)
snapshots/

# Everything under any fixtures directory
**/fixtures/**

# Anchored glob: generated clients for any single-character API version
/api/v?/generated/

# Character class: yearly log folders 2020-2029
/logs/202[0-9]/

# Ignore all YAML, but keep CI configs (negation — last match wins)
*.yaml
!ci.yaml

Precedence

Discovery applies its filters in a fixed order. In general, the first layer
that rejects a path wins, except .cbmignore negations (layer 4) can un-skip
ordinary built-in skip directories from layer 1 and rescue paths from layer 5.
For directories:

  1. Built-in skip list — .git, node_modules, dist, target,
    vendor, tool caches, etc. (60+ names; the fast/moderate index modes add
    more, e.g. docs, examples, testdata). Generated output whose
    folder name alone is too common to skip everywhere is matched by path
    suffix instead: Laravel's compiled Blade view cache
    storage/framework/views/ (at any depth) is skipped even without
    Laravel's stock .gitignore there, while other views/ folders stay
    indexed. A .cbmignore negation
    (for example !target/) can un-skip these directories, except the
    non-negatable safety core: .git, node_modules, .worktrees, and
    .claude-worktrees.
  2. Repo .gitignore — <repo>/.gitignore merged with
    <git-common-dir>/info/exclude (worktree-aware); later patterns win on
    conflict. Honored even when the indexed directory is not a git repo root.
  3. Nested .gitignore files — picked up during the walk and matched
    relative to their own directory.
  4. .cbmignore — a positive match skips the path; a negated match can
    un-skip built-in skip dirs from layer 1 (except the safety core) and can
    also rescue paths from layer 5.
  5. Git global excludes — core.excludesFile from ~/.gitconfig or the
    XDG git config (default $XDG_CONFIG_HOME/git/ignore); consulted only
    when the project is a git repo with a config.

For files, built-in suffix filters (.png, .o, .db, …; fast modes add
archives, media, lockfiles, .min.js, …) and fast-mode filename/substring
filters run before the ignore files, and a maximum-file-size cap runs
after them; none of these are overridable from .cbmignore. Symlinks are
always skipped.

Negation (`!`) — current behavior

  • Within .cbmignore: standard gitignore semantics. Patterns are
    evaluated top to bottom and the last matching pattern wins, so
    !pattern re-includes something an earlier line excluded.
  • Parent pruning (same caveat as git): when a directory is excluded, the
    walk never descends into it — you cannot re-include a file whose parent
    directory is excluded. Negate the directory itself if you need its
    contents.
  • Across layers: a .cbmignore negation overrides the git global
    excludes
    layer and can also un-skip ordinary built-in skip directories.
    Example: your ~/.config/git/ignore ignores *.sql, but this project's
    SQL should be indexed — add !*.sql to .cbmignore. Negation still cannot
    override the safety core built-in dirs (.git, node_modules,
    .worktrees, .claude-worktrees), the repo .gitignore/info/exclude,
    nested .gitignore files, the built-in suffix/filename filters, or the
    size cap.

Planned follow-up

  • Auxiliary filesystem walkers will honor the same ignore predicate as
    discovery, so every code path sees an identical ignore decision
    (unification tracked in a follow-up issue).

Llms (`docs/llms.txt`)

codebase-memory-mcp

Codebase Memory is an open-source local MCP server for AI coding agents. It connects code, documentation, configuration, and infrastructure in a persistent knowledge graph, with 17 MCP tools and native CLI access. Search for a feature, follow callers and dependencies, trace services and deployments, and inspect change impact. Indexing, graph queries, and semantic embeddings run locally without a hosted service or API key. CBM Lens is the optional visual companion, currently in open beta (v0.0.1); its optional browser model supports on-device code chat after an opt-in download.

Capabilities

  • Code exploration: symbol and structural search, BM25 text search, semantic search, source snippets and file outlines, call paths, data flow, architecture summaries, hotspots, cycles, and coverage diagnostics. The server registers 17 MCP tools.
  • Semantic and similarity relationships: bundled embeddings support natural-language discovery; SEMANTICALLY_RELATED edges connect vocabulary-mismatch matches, and SIMILAR_TO edges expose near-clone candidates.
  • Languages and formats: vendored parsers cover source, documentation, and configuration formats. Hybrid LSP adds semantic type resolution for Python, TypeScript/JavaScript/JSX/TSX, PHP, C#, Go, C/C++, Java, Kotlin, Rust, and Perl. Support and resolution depth differ by language; use the README language matrix and check_index_coverage for the indexed project.
  • Documentation connected to code: Markdown, reStructuredText, and AsciiDoc sections; PDF text pages (no OCR); supported document and language doc-comment references to code; ADR metadata and supersession. Supported diagrams and OpenAPI descriptions contribute entities, with format-specific reference-resolution limits.
  • Infrastructure and deployment: Docker/Compose, Kubernetes, Helm, Kustomize, Terraform/OpenTofu, Terragrunt, Ansible, shell, Make, and supported CI relationships. Trace deployments, images, entrypoints, configuration keys, and environment-variable access back to source when inputs can be resolved statically.
  • Cross-service and cross-repository relationships: supported HTTP, asynchronous messaging, gRPC, GraphQL, and tRPC links. Cross-repo analysis explicitly links projects indexed in the same store; missing links do not prove that no runtime relationship exists.
  • Change impact: detect_changes maps diffs to affected symbols and investigates related tests with scope: "tests"; compare_graphs compares indexed snapshots.
  • Smart CI (experimental): repository-specific changed-file rules select CI jobs; test-impact select combines static reachability, changed tests, and admitted per-test coverage to select tests. Narrowing supports C/C++ family code with supported test-harness conventions and an explicitly configured runner. Missing or doubtful evidence broadens the selection or runs all tests. Other indexed languages are not automatically supported for test narrowing. test-impact publish creates the baseline graph-and-coverage bundle. Start in shadow mode; consult the guide for version requirements.
  • Architecture decisions and runtime evidence: manage_adr stores the project's decision record; ingest_traces adds separately collected observed call relationships alongside static analysis.
  • CBM Lens open beta (v0.0.1): optional visual companion to the MCP server, with a local browser interface with source exploration, Galaxy graph navigation, architecture and behavior views, ADR editing, index-coverage diagnostics, daemon/system information, and optional local code chat. Static graph animations are not live execution traces. Activity overlays are experimental source-build features, not a standard-release promise.
  • Agent integrations: 45 automatic or conditional client surfaces. Installation configures supported MCP entries, durable instructions, skills, Scout/Verify/Auditor profiles, and applicable session, compaction, search, and read hooks. Coverage and activation vary by client and platform. Hooks provide context without blocking or replacing the user's tool call; some clients require trust or use parent-agent handoff instead.
  • Persistent operation: a per-account daemon coordinates sessions and indexing. Existing indexes can stay synchronized during eligible active MCP sessions; initial auto-indexing is separately opt-in. Optional team graph artifacts share a baseline while each user keeps a local query database.
  • Token-efficient retrieval: targeted graph queries, focused source snippets, and compact, paged results can reduce token use and repeated file reads during code exploration.
  • Distribution: native C backend, MIT license, standard native releases for macOS, Linux, and Windows include CBM Lens; Linux portable archives provide static builds. Nix and development source builds can omit the UI. Package-manager options and release verification are documented in the README.

Client coverage

  • Supported agents: 45 automatic/conditional client surfaces (39 automatically detected + 6 conditional/explicit): Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode, Antigravity, Aider, KiloCode, VS Code, Cursor, Windsurf, Augment / Auggie, OpenClaw, Kiro, Junie, Hermes, OpenHands, Cline, Warp, Qwen Code, GitHub Copilot CLI, Factory Droid, Crush, Goose, Mistral Vibe, Grok Build, Qoder CLI, Kimi Code CLI, GitLab Duo CLI, Rovo Dev CLI, Amp, Devin CLI / Local, Tabnine, Continue / cn, Visual Studio, TRAE, Roo Code, Amazon Q Developer IDE, CodeBuddy Code CLI, IBM Bob IDE, IBM Bob Shell, Pochi, Pi, Oh My Pi (omp), and Sourcegraph Cody.

Measurement and limits

A March 2026 preprint across 31 real-world repositories reports 10× lower token use and 2.1× fewer tool calls compared with file-by-file exploration. These historical backend measurements cover the study’s workloads and agent setup. The paper documents the full evaluation; the README Performance section links to the research and earlier measurements.

Graph evidence depends on index generation, freshness, and exact path/scope coverage. Partial, skipped, excluded, stale, or unknown coverage requires source verification, especially for absence claims. Infrastructure relationships are static, and test-selection support is narrower than language-indexing support.


Robots (`docs/robots.txt`)

codebase-memory-mcp — all crawlers welcome, including AI answer engines.

User-agent: *
Allow: /

Explicitly allow AI / LLM crawlers used by answer engines.

User-agent: GPTBot
Allow: /

User-agent: OAI-SearchBot
Allow: /

User-agent: ChatGPT-User
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: Claude-Web
Allow: /

User-agent: PerplexityBot
Allow: /

User-agent: Google-Extended
Allow: /

User-agent: Applebot-Extended
Allow: /

User-agent: Bingbot
Allow: /

Sitemap: https://deusdata.github.io/codebase-memory-mcp/sitemap.xml


Graph Ui Docs Adr 0001 Modellwahl (`graph-ui/docs/adr/0001-modellwahl.md`)

ADR 0001: Optional local inference and model evidence

Decision

CBM Lens offers optional browser inference. Weights are downloaded only after the
user opts in; they are not embedded in the executable. Model selection remains a
user choice. Browser model IDs, fixed revisions, allowed files and licenses live in
src/browser-ai/model-policy.ts; THIRD_PARTY.md documents them.

The older GGUF sidecar catalogue remains supported by local scripts and its recorded
evaluation. It is separate from the browser ONNX catalogue. Its measurements do not
predict browser performance, another machine's performance or a model's general rank.

Sidecar model sources

The six Q4_K_M candidates below were recorded in August 2026. Class A used a 3072-token
context and class B used 8192 tokens. Exact download sizes refer to those evaluated
files, not runtime memory. Checksums are retained in models/SHA256SUMS.

Model Class Download source Recorded size License recorded for the base model
Qwen3.5-2B A unsloth/Qwen3.5-2B-GGUF 1280835840 Bytes Apache 2.0
LFM2.5-1.2B-Instruct A LiquidAI/LFM2.5-1.2B-Instruct-GGUF 730895168 Bytes LFM Open License v1.0; commercial terms apply at $10M annual revenue
MiniCPM5-1B A openbmb/MiniCPM5-1B-GGUF 688065920 Bytes Apache 2.0
Qwen2.5-Coder-1.5B-Instruct A Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF 1117320768 Bytes Apache 2.0
Qwen3.5-4B B unsloth/Qwen3.5-4B-MTP-GGUF 2834975040 Bytes Apache 2.0
Gemma 4 E4B-it B unsloth/gemma-4-E4B-it-GGUF 4977171584 Bytes Apache 2.0

Review current model-card terms before downloading or redistribution. The recorded
LFM license allows free commercial use below $10M annual revenue; use beyond that
threshold requires separate consideration. No evaluation result overrides licensing.

Evaluation limits

verification/w5/eval.json records 44 fixture questions, including 23 in German,
with temperature 0 and seed 42. The figures below summarize that saved run. Citation
compliance measures the evaluation's citation rule, not factual correctness.

Model Class Pass rate Citation compliance Mean tokens/s, rounded
Qwen3.5-2B A 0.682 0.932 86
LFM2.5-1.2B A 0.295 0.432 110
MiniCPM5-1B A 0.250 0.545 171
Qwen2.5-Coder-1.5B A 0.227 0.705 84
Qwen3.5-4B B 0.818 0.955 38
gemma-4-E4B B 0.841 1.000 32

The recorded class recommendations were Qwen3.5-2B and gemma-4-E4B, with pass-rate
and citation thresholds of 0.6 and 0.9. They are recommendations, not installed
defaults. A reported repeat run has no separate saved artifact and is not independent
reproduction evidence. The original record does not measure unscorable citations;
missing measurements must not be displayed as zero.

Engine boundary

The sidecar evaluation used llama.cpp on Apple Silicon with Metal and GGUF files.
Its source/build record is in runtime provenance.
llm/start.sh also supports a local model cache and OpenAI-compatible transport;
the class-specific script options reproduce the recorded evaluation.

Browser inference uses a different runtime and format. Compare actual device and
workload behavior before drawing performance conclusions, especially prompt prefill,
GPU memory limits and large-file handling. Relevant engine sources retained from
the original investigation are llama.cpp,
wllama, WebLLM,
Transformers.js,
WebGPU limits,
and the ggml WebGPU study.
These references explain the formats and tradeoffs; the sidecar timings above are
not browser benchmarks.


--- METRICS ---