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:
installsets 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):
curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bashThe 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):
# 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.ps1Note: If you see a script execution policy error, run
Set-ExecutionPolicy -Scope Process Bypassfirst, or invoke withPowerShell -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 hitsgh, 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
Download the archive for your platform from the latest release:
codebase-memory-mcp-<os>-<arch>.tar.gz(macOS/Linux) or.zip(Windows)
Extract and install (each archive includes
install.shorinstall.ps1):macOS / Linux:
bashtar xzf codebase-memory-mcp-*.tar.gz ./install.shWindows (PowerShell):
powershellExpand-Archive codebase-memory-mcp-windows-amd64.zip -DestinationPath . Unblock-File .\install.ps1 .\install.ps1Restart 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:
codebase-memory-mcp daemon start --open --port=9749The 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
- Open the project menu and choose Add project index. You can also use
Alt+P. - Choose a local repository folder and name the index. Files stay in place.
- Start indexing and follow its reported status. You can close the dialog while it runs and reopen its activity from the project menu.
- 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.
- Click the robot icon at the top right to open the agent configuration.
- 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-f16support. - 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).
- 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.
Search
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:
VITE_EXPERIMENTAL_AGENTS=true scripts/build.sh --with-uiActivity 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:
codebase-memory-mcp config set auto_index trueauto_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:
# macOS / Linux
bash "<install-dir>/install.sh"# 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
codebase-memory-mcp uninstallRemoves 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.
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_architecturereturns a compact summary by default. Request specificaspectsfor routes, hotspots, dependencies, clusters, or cycles.system_structureprovides the projection used by CBM Lens and must be requested on its own. - Architecture Decision Records:
manage_adrpersists 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_changesmaps uncommitted changes to affected symbols with risk classification and can report test impact withscope: "tests";compare_graphscompares indexed snapshots - Runtime observations:
ingest_tracesadds 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
Search
- Semantic search (the
semantic_queryparameter ofsearch_graph, not a separate tool): vector search across the entire graph, powered by bundled Nomicnomic-embed-codeembeddings (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_splittokenizer (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
MENTIONSrelationships 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 tolinked_projects) carries across_repoobject,{"status":"never_run"}untilindex_repository(mode="cross-repo-intelligence")has run with that project as the source, then"ran"withlast_run_at,outcome,targets,projects_scanned,total_cross_edgesandskipped_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 itsCROSS_*edges, so a reindex that rebuilds the index drops both at once and the status goes back tonever_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 asskipped_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 siteCALL_REFERENCE: a callable is used at a supported reference site (for example, a direct value argument) and resolves to one exact targetUSAGE: an identifier is used, but a unique callable target is not proven (including ambiguous or complex expressions)IMPORTS,DEFINES,IMPLEMENTS,INHERITSHTTP_CALLS,ASYNC_CALLS(cross-service); a shell script, Make recipe or CI step runningcurl/wgeton a URL it states (constant words resolved, unknown parts{}) to itsRoutewith 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 keepsvia,method,url_path,host, never the user info or the querySPAWNS: 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 aProcessnode 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'sModule(else itsFile);make TARGET(also$(MAKE),-C,-f) targets the rule of that target, followed through included makefiles; edges from CI carryciandjob. A Dockerfile's last stageENTRYPOINT(elseCMD) spawns what the container runs (api: dockerfile): the repository script or source copied into the image, the Gomainof a binary a stage builds withgo build -o, else aProcessINFRA_MAPS: a Kubernetes Service to each workload whose pod labels carry everykey: valueof the Service's selector, within one namespaceCONNECTS_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.envinterpolated): a Kubernetes Service of the same bundle and namespace, or a compose service of the same file. The edge keepsvia: env,keyandcontainer, never the valueUSAGEwithref: image: a Kubernetes workload (any pod template: containers, initContainers, CronJobs, custom resources) or a docker compose service to theImageit runs- Helm templates are rendered for their images: with the chart's values (the
ChartDEPLOYSthem,default_of: the values keys read) and with each release's (a Flux HelmRelease, a kustomizehelmChartsentry, a Terraformhelm_release:override_of). An image the render cannot know is left out, never guessed: after a change of values it does not run exactly, atplor 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.Valuesin 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.chartorchartRef→ 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 Terraformhelm_release, a Terraformkubernetes_*resource the object it declares (aResourcelike a manifest's,ecosystem: terraform, when its name is a literal), a kustomization inflating a chart (helmCharts; its values file anIMPORTS). A chart outside the repository is an externalChart, keyed by its publisher when its repository URL is stated (__chart__<repository>/<name>, the same node in every project, the URL inrepository), else one per project and name; the edge saysvia(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 resolvablefile://repository, otherwise an externalChart; the edge keepsrepository), and ago.mod/requirements.txtfile to an externalPackage; a Dockerfile to theImages it builds on (via: from,copy-from); anImageto the Dockerfile it is built from (via: build;tool:skaffold,compose,docker-buildfordocker/podman/nerdctl/buildahbuild commands in scripts, Make and CI); a compose service to the services itsdepends_on/linksnameCONFIGURES: code reading an environment variable to the sharedEnvVarof that name (strategy: env_access); a Kubernetes container, compose service or Dockerfile (ENVof the image) setting one to that container's ownEnvVar(strategy: env_set, withsource:value,configMapKeyRef,secretKeyRef,fieldRef, ...,compose,dockerfile; values are never stored); and that container'sEnvVarto the code reading the name inside the files its image is built from (strategy: env_read, withdockerfile): the image's files are the sources its Dockerfile'sCOPY/ADDput into it, less the context's.dockerignoreCONFIGURESwithstrategy: 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 aconfig/,configs/,conf/,settings/oretc/directory. A.envor.propertieskey also links by substring (confidence 0.75)OVERRIDEwithvia: 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 itstargetselects (group, version, kind, name, namespace, label selector); a component's in the build of each kustomization including it. The edge keepskustomization,patchandline; a patch file is anIMPORTS(local_name: patches) of its kustomization, never a member of the buildOVERRIDEwithvia: namePrefix/nameSuffix: an object a kustomization renames, from its variant: aResourcenamed 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 fileDEFINES. CRDs, Namespaces and API services keep their names, as kustomize does. The edge keepskustomizationandlineUSAGEwithvia: 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/.tofufiles of its directory) that declares it (alocalsentry: itslocalsblock); an entry of the block'sdepends_onlist is aDEPENDS_ONinstead. The edge keepsref(var,local,module,data,resource) andline; 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) theFolderof that directory (the root directory: theProject), andmodule.net.id(alsomodule.net[0].id,module.net[*].id)USAGEs (ref: output) theoutput "id"there, besidemodule.netOVERRIDEwithvia: tfvars: a.tfvarsassignment to a variable the module of its own directory declares, from the file'sVariableto thatvariableblock (the edge keepsline); a.tfvarsfile whose own directory declares none of its names sets the nearest ancestor module's that does, the edge markedinferred(such a file is applied by a-var-filethe repository does not state)- Terragrunt (
via: terragrunt): a unit (terragrunt.hcl), evaluated in its own context: anincludeIMPORTS(local_name) the file itspathnames; aread_terragrunt_configIMPORTS(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'sincludeblock that brings it in; adependencyDEPENDS_ONthe other unit; a localterraformsourceIMPORTSitsFolder. Paths may usefind_in_parent_folders,dirnameandget_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_playbooka playbook,include_tasks/import_tasks/includea task file (from its directory, else its role'stasks/),roles:items andinclude_role/import_rolea role'sFolder(roles/<name>at or above its directory). A templated target links nothing. Each task and handler is aFunction(named by itsname:, else its module;source: ansible,kind,module) that its fileDEFINES: in a playbook's plays, a role'stasks/andhandlers/files, and the files those include; a block's tasks, not the block. AnotifyCALLS(via: notify) each handler of that name orlistentopic in the task's own role (else its file). Akube(kubespray's module) orkubernetes.core.k8staskDEPLOYS(via: ansible,module) the repository file its play rendered the applied manifest from: thetemplate(in the role'stemplates/) orcopy(itsfiles/) task whosedestis the task'sfilename, item by item throughregister/with_items/looplists the play states (a literal list, aset_fact, the role'sdefaults/vars), or ak8stemplate:argument EMITS,LISTENS_ON(channels)DATA_FLOWSwith arg-to-param mapping + field access chainsSIMILAR_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::fooresolved 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 anImageper component), docker compose services (Resource), Kubernetes manifests (Resource, withgroup,version,kind,namespace,labelsask=v,k2=v2), Kustomize overlays (IMPORTSto their entries and patch files,OVERRIDEfrom 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 INTOcompacted, then zstd 1.5.7 compressed (8 to 13:1 ratio typical) - Two tiers:
- Best (
zstd -9+ index strip +VACUUM INTO); written byindex_repositorywithpersistence: true - Fast (
zstd -3); refreshes an existing artifact on every other index, including the watcher's low-latency incremental updates
- Best (
- Bootstrap: when no local DB exists but the artifact is present,
index_repositoryimports the artifact first, then runs incremental indexing, reusing the shared baseline where possible - Merge policy: the first export creates
.codebase-memory/.gitattributeswithmerge=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
.gitattributesand leave the auto-created.codebase-memory/.gitattributesin place; the nearer file goes on supplyingmerge=ours, and onlyfiltercomes from the root:Track only thegitattributes.codebase-memory/graph.db.zst filter=lfs diff=lfs merge=lfs -text.zst;artifact.jsonis 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 needsgit 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.gitignoreif 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.
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 EnglishWith 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:
curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/scripts/setup.sh | bashWindows (PowerShell):
irm https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/scripts/setup-windows.ps1 | iexAUR (Arch Linux)
yay -S codebase-memory-mcp-binparu -S codebase-memory-mcp-binThe 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:
# 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:9749Or build a binary into ./result/bin/codebase-memory-mcp:
nix build github:DeusData/codebase-memory-mcp # standard
nix build github:DeusData/codebase-memory-mcp#codebase-memory-mcp-ui # with UIWorking 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
stdincloses; that's normal MCP behaviour. Keepstdinopen while testing the UI, e.g.sleep infinity | codebase-memory-mcp --ui=true --port=9749. Thecodebase-memory-mcp-uipackage embeds the UI at build time;nix run'ing the standarddefaultpackage with--ui=truewill refuse to start the HTTP server.
Install via Claude Code
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 |
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:
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 availableFrontend development and validation:
cd graph-ui
npm ci
npm run test:unit
npm run build
npm run devThe 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:
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:
{
"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'sPreToolUse observes Grep/Glob/Bash and injects matching graph symbols asadditionalContext; 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 installsUserPromptSubmit 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 EAPSessionStart 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:
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:
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 withstate(queued/running). The job keeps running even if the client cancels, times out or
disconnects; only stopping the daemon ends it.index_repository(repo_path="/abs/path", status: true)reportsstate
(queued,running,cancelling,succeeded,failed,cancelled),started_at,finished_atand anerrorsummary. Poll it until the state issucceeded,failedorcancelled. Pass the samerepo_path(andname, 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 nextindex_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, multipleMATCH,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 testn:Label, andEXISTS { (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
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 defaultThe 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.
# Store indexes in a custom directory
export CBM_CACHE_DIR=~/my-projects/cbm-dataCustom 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):
// .codebase-memory.json
{"extra_extensions": {".blade.php": "php", ".mjs": "javascript"}}Global (applies to all projects):
// ~/.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:
- Tree-sitter pass: fast, syntactic, runs for every one of the 162 languages. Extracts definitions, calls, imports.
- 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
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 engineSecurity
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
!mltolerance 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 MCPBmanifest.jsonand 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.txtpublished 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.asaliasing in both wrapped and bare forms - PHPDoc
@var,@param,@property,@methodparsing — 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
asaliasing - 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, seedocs/PHP_LSP_PRE_FLIGHT.md §6):
ConfiguresPrompts.configurePrompts → helpers.valuemisroute (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 likeLaravel\\Prompts\\Prompt), it emits a syntheticphp_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
- Zero indexing failures across 63 repos of all sizes (78 to 49K nodes)
- 100% on 17 languages -- half the languages achieved perfect scores
- No language below 62% -- even the weakest performs core operations
- Massive codebases handled: Django (49K nodes), Laravel (38K nodes), neovim (24K nodes) -- no performance issues
- Kernel-scale stress test passed: 20K nodes, 67K edges, 129K-char traces, zero timeouts
- "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
- Function properties: Parameter types and return types are not extracted for most languages, resulting in
properties=null - Haskell call tracing: Function composition (
f . g . h) is not modeled as CALLS edges. Only explicit function applications are traced - OCaml call analysis: Limited call resolution due to module functor indirection
- Cypher
IS NOT NULL: Not supported in the Cypher parser -- use alternative query patterns - Scala/Ruby trace: Abstract methods and block-based patterns can have 0 trace edges
- 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:
$XDG_CONFIG_HOME/codebase-memory-mcp/config.jsonFallback when XDG_CONFIG_HOME is unset:
~/.config/codebase-memory-mcp/config.jsonPer-project config
Place this file in the repository root:
.codebase-memory.jsonFormat
{
"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:
${CBM_CACHE_DIR:-~/.cache/codebase-memory-mcp}/_config.dbInspect or change values with the CLI:
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_indexCurrent 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 thewatch_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_enabledvsauto_watch.watcher_enabledcontrols whether the
watcher subsystem starts at all (the background poll thread).auto_watchis
narrower: it only controls whether a connecting session registers its own
project with an already-running watcher. Whenwatcher_enabled=false,auto_watchhas 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_watchis consulted each time a session would register its project, so
a change applies to sessions that connect afterwards.watcher_enabledis 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:bashcodebase-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 # confirmDisabling the watcher does not disable anything else: the daemon still starts,
auto_indexstill runs, andindex_repositorystays 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:
${CBM_CACHE_DIR:-~/.cache/codebase-memory-mcp}/config.jsonCurrent format:
{
"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_DIRchanges both the UI config location and the runtime settings database location.- CBM resolves
CBM_CACHE_DIRto 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:
codebase-memory-mcp: secure daemon endpoint could not be createdCBM_RUNTIME_DIR points the rendezvous at an ancestry you choose:
export CBM_RUNTIME_DIR="$HOME/cbm-runtime" # any directory you own$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:
[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
WindowsC:\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, macOSKeychains.
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/cbmor/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
~/.cacheis
resolved and allowed, but every directory on the resolved path must be owned
by you or by root. If a parent such as/shared/fsbelongs 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 onedaemon.cache_root_network_fswarning
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:
codebase-memory-mcp install --dry-runThat 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:
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_filesValues 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:
{
"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 affectcross-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:
- Does the agent give a better answer?
- Is CBM fast and stable under the workload?
- 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:
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 1Repeat 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:
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 1Take PROJECT_NAME from the successful indexing response. A verbose status call
is useful only as a root/current-HEAD cross-check:
codebase-memory-mcp cli list_projects
codebase-memory-mcp cli index_status --project PROJECT_NAME --verboseConfirm 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 freshindex_repository call is the freshness requirement.
Finally, run one or more representative queries whose expected symbols you
have verified directly at the recorded SHA:
codebase-memory-mcp cli search_graph \
--project PROJECT_NAME \
--name-pattern 'KNOWN_SYMBOL_PATTERN' \
--limit 10Do 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 forBASELINE_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 containingdb 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.
export CBM_DIAGNOSTICS=1CBM 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 thediagnostics.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:
scripts/soak-legs.sh build/c/codebase-memory-mcp 10It 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 writessoak-results-query-leak/. Each directory contains latency.csv, metrics.csv,
and summary.txt. The two CSV files begin with these exact headers:
timestamp,tool,duration_ms,exit_code
timestamp,uptime_s,rss_bytes,heap_committed,fd_count,query_count,query_max_usDo 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:
run_id,condition,repo_sha,question_id,window,input_tokens,output_tokens,total_tokens,tool_calls,wall_time_ms,answer_artifact,quality_scoreIn 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'squery_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:
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 callsCalculate 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
CALLSedges 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 returnsstatus="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; and3: 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:
(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:
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 resultingcontribution_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.
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.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.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:
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:
{ 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 isoutbound.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 isinbound.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 aRUNTIME_CALL. The perspective token isinternal.
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 andfinalized; 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:
- canonicalize only allowlisted fields and compute the exact span identity
(project, producer_id, producer_epoch, trace_id, span_id); - deduplicate records with the same span identity and identical normalized bytes;
- reject the entire contribution if one span identity has different normalized bytes;
- group the remaining unique normalized records by
observation_key; - 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 - 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:
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:
- begins a write transaction with
BEGIN IMMEDIATE; - reads and pins the current composite head and validates compatibility versions;
- performs state-dependent authorization, quota, producer identity, deduplication,
contribution conflict, and expected-head checks; - stages all endpoints, immutable contribution-map entries, and rebuilt aggregates;
- allocates an opaque runtime generation independent of the pinned static generation;
- durably finalizes every contribution in the publication;
- writes the immutable runtime publication and atomically changes only the runtime
half of the composite head, preserving its pinned static generation; - commits all changes with
COMMIT; and - 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
CALLSedge; - 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; andstatic_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.
- 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. - 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. - 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. - 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. - Pinned read and
RUNTIME_CALLoverlay. 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. - 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:
- Static nodes and
CALLSedges are byte-identical with runtime ingestion enabled or
disabled for the same source input. - Existing search, architecture, and default trace responses exclude runtime data.
- Runtime data appears only through an explicit, versioned overlay request.
- Same producer contribution plus same payload is idempotent across retries and
restarts; a different payload under that identity is a no-mutation conflict. - 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. - Aggregate p99 is derived only from a pinned mergeable distribution and is otherwise
unavailable; producer p99 values are never averaged. - Symbol resolution is qualified-name based, generation-scoped, and fail-closed on
ambiguity; runtime and unknown endpoints never fabricate static nodes. - 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. - Every overlay read and page uses one immutable
{static_generation, runtime_generation}pair; stale cursors fail explicitly. - 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. - Unsupported span kinds, missing identities, and limit violations never become
guessed calls or partial publications. - Authorization, privacy filtering, quotas, and retention are enforced before data
becomes durable or reader-visible. - Wire, runtime schema, runtime semantic, runtime artifact, static semantic, and
static artifact versions can change independently according to their boundaries. - 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, seeSECURITY.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:
- 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. - Patch before publicity. A fixed release is always available before the
vulnerability is described publicly, so users can update immediately. - Credit the researcher. Public credit by default; anonymity on request.
- 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 releasedcodebase-memory-mcp binary.
Status: experimental. Every answer carries "experimental": true. The
selection narrows real test runs only while the repository variableTEST_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
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
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'schecksums.txt, as this repository's shadow job does
(scripts/ci/test-impact-shadow.sh --fetch-release):bashCBM_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" --versionFor a quick start,
install.shinstalls the latest release with the same
checks (HTTPS only, SHA-256 againstchecksums.txt, the archive's member
list,--version);--skip-configskips agent configuration, which a CI
runner does not need. It has no version flag, so it is not a pin.Version note.
v0.12.0andv0.12.1havetest-impact select/publish.
They do not havekey: "path",unreferenced,--vetoed-outorrun_all_rules; those ship in a later release. With either version, a config
that uses those keys is invalid, and every answer isrun_all. Until that
release, build the binary from source (make -f Makefile.cbm cbm), as this
repository's producer andselect-testsjobs do.Full git history. Check out with
fetch-depth: 0in every job that
runs the engine. The engine verifies the history it diffs and refuses a
shallow clone (GIT_UNAVAILABLE, everything runs).A runner that runs a named subset. See "The test job" below.
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 markedincomplete. An incomplete test is always
selected.gh,python3, andGH_TOKENwithactions: readin the PR job, to
fetch the bundle. GitHub-hosted runners includeghandpython3.
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 areceipt.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.
/* Detailed source-code truncated for AI context efficiency. */This repository's producer (.github/workflows/test-impact-artifact.yml withscripts/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)
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.mdLeave this job out of your aggregate required check. Whattest-impact-select.sh does:
- Takes the base side of the PR merge commit (
HEAD^1) and fetches that
commit's bundle. - 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). - Passes
--artifact-verifiedonly for a bundle that it fetched and checked
itself. Nothing inside a bundle can assert where the bundle came from. - Runs
test-impact selectand writesselection-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
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 presentWith --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, eithersuite(run the whole
suite) orsuite: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
asuite:testsuite; - fail when the selection names a suite the build does not register,
unlessoptional-suites.txtlists 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:
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:
gh variable set TEST_IMPACT_MODE --body gate # gate
gh variable delete TEST_IMPACT_MODE # back to shadowDo 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) andreasons to $GITHUB_OUTPUT. Each lane job then runsif: 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 isscripts/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
Shadow. Run (a) and (b). Leave
TEST_IMPACT_MODEunset.Measure. For each PR run, compare
selection-engine.jsonwith 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.- A miss is a test that failed in the full run but was not selected.
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.v0and 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 initialindex_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:
# 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.yamlPrecedence
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:
- 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.gitignorethere, while otherviews/folders stay
indexed. A.cbmignorenegation
(for example!target/) can un-skip these directories, except the
non-negatable safety core:.git,node_modules,.worktrees, and
.claude-worktrees. - Repo
.gitignore—<repo>/.gitignoremerged 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. - Nested
.gitignorefiles — picked up during the walk and matched
relative to their own directory. .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.- Git global excludes —
core.excludesFilefrom~/.gitconfigor 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!patternre-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
.cbmignorenegation overrides the git global
excludes layer and can also un-skip ordinary built-in skip directories.
Example: your~/.config/git/ignoreignores*.sql, but this project's
SQL should be indexed — add!*.sqlto.cbmignore. Negation still cannot
override the safety core built-in dirs (.git,node_modules,.worktrees,.claude-worktrees), the repo.gitignore/info/exclude,
nested.gitignorefiles, 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.
Links
- Homepage: https://deusdata.github.io/codebase-memory-mcp/
- Source and feature reference: https://github.com/DeusData/codebase-memory-mcp
- Latest release: https://github.com/DeusData/codebase-memory-mcp/releases/latest
- Quick start and installation: https://github.com/DeusData/codebase-memory-mcp#quick-start
- Documentation indexing: https://github.com/DeusData/codebase-memory-mcp#documentation-connected-to-code
- Infrastructure and deployment: https://github.com/DeusData/codebase-memory-mcp#infrastructure-and-deployment
- CBM Lens: https://github.com/DeusData/codebase-memory-mcp#cbm-lens-beta-code-visualization-and-source-navigation
- Agent integrations: https://github.com/DeusData/codebase-memory-mcp#multi-agent-support
- Smart CI guide: https://github.com/DeusData/codebase-memory-mcp/blob/main/docs/SMART_CI.md
- Configuration: https://github.com/DeusData/codebase-memory-mcp/blob/main/docs/CONFIGURATION.md
- MCP tools: https://github.com/DeusData/codebase-memory-mcp#mcp-tools
- Token savings and evidence: https://deusdata.github.io/codebase-memory-mcp/#token-savings
- Performance and research: https://github.com/DeusData/codebase-memory-mcp#performance
- Benchmark report: https://github.com/DeusData/codebase-memory-mcp/blob/main/docs/BENCHMARK.md
- Research paper: https://arxiv.org/abs/2603.27277
- Security and release verification: https://github.com/DeusData/codebase-memory-mcp#security
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 insrc/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 ---
- Files Extracted: 13
- Estimated Token Budget: ~71732 tokens
- Canonical Reference: https://codewiki.google/github.com/DeusData/codebase-memory-mcp