---
# Front matter only so Jekyll renders the {{ site.data.counts.* }} tags below
# (TRA-263). Stripped from the served file.
layout: null
---
{%- comment -%}
GENERATED by scripts/gen-llms-full.mjs — do not edit by hand. Edit the docs page.
{%- endcomment -%}
# trace-mcp — Full documentation

> Concatenated full text of all trace-mcp docs pages, for AI systems that ingest one file instead of following links. See /llms.txt for the linked index.

---

# Tools reference

Source: https://trace-mcp.com/tools-reference.html


trace-mcp exposes {{ site.data.counts.tools }} MCP tools and {{ site.data.counts.resources }} resources.

This page groups the ones you reach for by hand. For the complete list —
every registered tool with its one-line description, generated from the
registrations themselves — see the [tool index](tools-index.md).

Tools are registered dynamically based on detected frameworks — you only see tools relevant to your project.

---

## Project

| Tool | What it does |
|---|---|
| `get_project_map` | Project overview — detected frameworks, directory structure, entry points |
| `get_index_health` | Index stats — file count, symbol count, edge count, errors |
| `reindex` | Trigger full or incremental re-indexing |
| `get_env_vars` | List environment variable keys from `.env` files with inferred value types |
| `get_plugin_registry` | List all registered indexer plugins and the edge types they emit |

## Navigation

| Tool | What it does |
|---|---|
| `search` | Full-text search (FTS5 + BM25) with kind / language / file pattern filters |
| `get_symbol` | Look up a symbol by ID or FQN — returns source code |
| `get_outline` | All symbols in a file — signatures only, no bodies |
| `find_usages` | Find all places that reference a symbol or file (imports, calls, renders, dispatches) |

## Framework intelligence

| Tool | What it does | When available |
|---|---|---|
| `get_component_tree` | Build component render tree from a root file | Vue, Nuxt, Inertia |
| `get_change_impact` | Reverse dependency graph — what depends on this file or symbol. Each dependent symbol includes `hasTestReach` (whether any test that covers the file also references that specific symbol) | Always |
| `get_task_context` | **Graph-aware context engine** — describe a dev task, get the optimal code subgraph (execution paths, tests, types) adapted to task type (bugfix/feature/refactor) | Always |
| `get_feature_context` | NLP-driven context assembly — describe a feature, get relevant code within a token budget | Always |
| `get_request_flow` | Trace request flow for a URL+method: route → middleware → controller → service | Express, NestJS, Laravel, FastAPI, Flask, DRF, Spring, Rails, Fastify, Hono, tRPC |
| `get_middleware_chain` | Trace middleware chain for a route URL | Express, NestJS, FastAPI, Flask |
| `get_event_graph` | Event/signal/task dispatch graph | Laravel, NestJS, Django, Celery, Socket.io |
| `get_model_context` | Full model context: relationships, schema, metadata | Eloquent, Prisma, TypeORM, Drizzle, Mongoose, Sequelize, SQLAlchemy |
| `get_schema` | Database schema reconstructed from migrations or ORM definitions | Eloquent, Prisma, TypeORM, Drizzle, Mongoose, Sequelize, SQLAlchemy |
| `get_livewire_context` | Full Livewire component context: properties, actions, events, view, children | Laravel |
| `get_nova_resource` | Full Laravel Nova resource context: model, fields, actions, filters, lenses, metrics | Laravel |
| `get_state_stores` | List stores/slices with state, actions, and dispatch sites | Zustand, Redux |

## NestJS

| Tool | What it does |
|---|---|
| `get_module_graph` | Build module dependency graph (modules → imports → controllers → providers → exports) |
| `get_di_tree` | Trace dependency injection tree (what a service injects + who injects it) |

## React Native

| Tool | What it does |
|---|---|
| `get_navigation_graph` | Build navigation tree from screens, navigators, and deep links |
| `get_screen_context` | Full screen context: navigator, navigation edges, deep link, platform variants, native modules |

## Code analysis

| Tool | What it does |
|---|---|
| `get_import_graph` | File-level dependency graph: what a file imports and what imports it |
| `get_call_graph` | Bidirectional call graph centered on a symbol (who it calls + who calls it) |
| `get_tests_for` | Find test files and test functions that cover a given symbol or file |
| `get_implementations` | Find all classes that implement or extend a given interface/base class |
| `get_type_hierarchy` | Walk TypeScript class/interface hierarchy: ancestors and descendants |
| `get_api_surface` | List all exported symbols (public API) of a file or matching files |
| `get_untested_symbols` | Find ALL symbols (not just exports) lacking test coverage. Returns the "unreached" tier (no test imports the source) by default; `level: "imported_not_called"` / `"all"` opt into the weaker tier, where transitively-exercised symbols also land. Pass `scope: "exports_only"` for the fast exports-only scan |
| `self_audit` | One-shot project health: dead exports, untested code, dependency hotspots, heritage metrics |

## Quality & security

| Tool | What it does |
|---|---|
| `scan_security` | OWASP Top-10 vulnerability scan: SQL injection, XSS, command injection, path traversal, hardcoded secrets, insecure crypto, open redirects, SSRF |
| `taint_analysis` | Track untrusted data from sources (HTTP params, env vars, file reads) to dangerous sinks (SQL, exec, innerHTML). Framework-aware, cross-file |
| `scan_code_smells` | Find TODO/FIXME/HACK comments, empty functions, hardcoded values, magic numbers |
| `detect_antipatterns` | Performance antipattern detection |
| `check_quality_gates` | Quality gate validation against configurable thresholds |
| `export_security_context` | Export security context for MCP server analysis — enrichment JSON for [skill-scan](https://github.com/kkdub/skill-scan): tool registrations with annotations, transitive call graphs classified by security category, sensitive data flows, capability maps |

## Topology & subprojects

Enabled by default (`topology.enabled: true`). See [Configuration](configuration.md#topology--subprojects).

### Service topology

| Tool | What it does |
|---|---|
| `get_service_map` | Map of all services, their APIs, and inter-service dependencies (auto-detects from Docker Compose) |
| `get_cross_service_impact` | Impact of changing an endpoint or event — which services are affected |
| `get_api_contract` | API contract (OpenAPI/gRPC/GraphQL) for a service |
| `get_service_deps` | External service dependencies: outgoing and incoming |
| `get_contract_drift` | Mismatches between API spec and implementation |

### Subprojects

A subproject is any working repository that is part of your project's ecosystem: microservices, frontends, backends, shared libraries, CLI tools, etc. A project auto-detects its subprojects on indexing, or you can add external ones manually.

| Tool | What it does |
|---|---|
| `get_subproject_graph` | All subprojects, cross-subproject connections, and stats |
| `get_subproject_impact` | Cross-subproject impact: find all client code that would break if an endpoint changes. Resolves to symbol level when per-subproject indexes exist |
| `get_subproject_clients` | Find all client calls across subprojects that call a specific endpoint |
| `subproject_add_repo` | Add a subproject, bound to the current project (or specify `project` param for external subprojects) |
| `subproject_sync` | Re-scan all subprojects: contracts, client calls, and re-link |

### Cross-project

Every session is attached to one project, but these two tools reach across to any OTHER project already registered with trace-mcp (`~/.trace/registry.json`) — see [Configuration](configuration.md#cross-project-tools).

| Tool | What it does |
|---|---|
| `list_projects` | List registered project roots (name, type, last-indexed), plus known subprojects |
| `call_project_tool` | Run any other trace-mcp tool against a DIFFERENT registered project's already-indexed data; returns that tool's response verbatim |

## Decision memory

See [Decision memory](decision-memory.md) for full documentation.

| Tool | What it does |
|---|---|
| `mine_sessions` | Extract decisions from Claude Code / Claw Code session logs (pattern-based, 0 LLM calls) |
| `add_decision` | Manually record a decision with code linkage + service scoping |
| `query_decisions` | Query by type/service/symbol/file/tag + FTS5 search + temporal filtering |
| `invalidate_decision` | Mark a decision as superseded (preserved for historical queries) |
| `get_decision_timeline` | Chronological history of decisions for a project/symbol/file |
| `get_decision_stats` | Knowledge graph overview: counts by type, source, sessions mined/indexed |
| `index_sessions` | Index conversation content for cross-session search |
| `search_sessions` | FTS5 search across all past session conversations |
| `get_wake_up` | Compact orientation (~300 tokens): project + active decisions + stats. Auto-mines on first call |

Decisions auto-enrich code intelligence: `get_change_impact` shows `linked_decisions`, `plan_turn` shows `related_decisions`, `get_wake_up` shows `active_decisions`.

## Session Analytics

See [Analytics](analytics.md) for full documentation.

| Tool | What it does |
|---|---|
| `get_session_analytics` | Token usage, cost breakdown by tool/server, top files, models used |
| `get_optimization_report` | Detect token waste patterns (8 rules) with savings estimates |
| `get_real_savings` | Analyze actual sessions: how much trace-mcp saves vs raw file reads |
| `benchmark_project` | Synthetic benchmark: raw reads vs trace-mcp compact responses (5 scenarios) |
| `get_coverage_report` | Technology profile: deps from manifests, coverage by trace-mcp plugins, gaps |
| `get_startup_context_audit` | What every session pays for before the first message, by source, what it costs, what the logs prove went unused, and where it says the same thing twice |
| `apply_startup_recommendations` | Apply (or preview) a `get_startup_context_audit` recommendation, backed up first |
| `rollback_startup_recommendations` | Undo one `apply_startup_recommendations` call, byte-for-byte, in one action |
| `get_usage_trends` | Daily token usage trends over time |
| `get_session_stats` | Real-time token savings for the current session |
| `audit_config` | Audit AI agent config files for stale refs, dead paths, bloat, scope leaks |

Supports **Claude Code** and **Claw Code** session logs (auto-detected).

## CI/PR reports (CLI)

Not an MCP tool — a CLI command for CI pipelines:

```bash
trace-mcp ci-report --base main --head HEAD --format markdown --output report.md
trace-mcp ci-report --base main --head HEAD --fail-on high
```

Generates a change impact report with blast radius, risk scores, test coverage gaps, architecture violations, and dead code. See [README](https://github.com/nikolai-vysotskyi/trace-mcp/blob/master/README.md#cipr-change-impact-reports) for GitHub Action setup.

## Security context export (CLI)

Export security context for MCP server analysis — generates enrichment JSON for [skill-scan](https://github.com/kkdub/skill-scan):

```bash
# Export to file
trace-mcp export-security-context -o enrichment.json

# Limit scope and call graph depth
trace-mcp export-security-context --scope src/tools --depth 4

# Re-index before export
trace-mcp export-security-context --index -o enrichment.json

# Use with skill-scan
trace-mcp export-security-context -o ctx.json && skill-scan scan . --enrich ctx.json
```

Output contains: MCP tool registrations with annotations, transitive call graphs classified by security category (`file_read`, `file_write`, `network_outbound`, `env_read`, `shell_exec`, `crypto`, `serialization`), sensitive data flows, and per-file capability maps.

## AI-powered (optional)

Requires `ai.enabled: true` in config. See [Configuration](configuration.md#ai-configuration).

| Tool | What it does |
|---|---|
| `explain_symbol` | AI-generated explanation of a symbol's purpose and behavior |
| `suggest_tests` | AI-generated test case suggestions for a symbol |
| `review_change` | AI-powered review of a file change |
| `find_similar` | Find semantically similar symbols using vector search + AI reranking |
| `explain_architecture` | AI-powered architecture analysis of a module or feature area |

---

## Resources

| Resource | URI | Description |
|---|---|---|
| Project map | `project://map` | JSON project overview |
| Index health | `project://health` | Index status |

---

## Usage examples

| Scenario | Tool to use |
|---|---|
| "Add a new field to the User model" | `get_change_impact` — shows all dependents: model, migration, request validation, Vue props |
| "What components does this page use?" | `get_component_tree` — full render tree with props/slots |
| "Refactor the auth flow" | `get_task_context("refactor the auth flow")` — intent-aware context with full execution paths |
| "Quick keyword context" | `get_feature_context("authentication")` — assembles relevant code in one call |
| "Does the Vue page match the controller response?" | Prop mismatch detection flags drift automatically at index time |
| "What's the DB schema?" | `get_schema` — reconstructed from migrations, no DB needed |
| "Trace a request end-to-end" | `get_request_flow("/api/users", "GET")` — full chain |
| "What NestJS modules does this depend on?" | `get_module_graph` — full dependency tree |
| "Find untested code" | `get_untested_symbols` — deep analysis with "unreached"/"imported_not_called" classification. Or lighter: `get_untested_symbols { scope: "exports_only" }` + `self_audit` |
| "Explain this complex service" | `explain_symbol` — AI-generated explanation with context |
| "What repos call this endpoint?" | `get_subproject_clients("/api/users")` — all client calls across repos |
| "Will this API change break anything?" | `get_subproject_impact` — cross-repo impact with symbol resolution |
| "Show me all service connections" | `get_subproject_graph` — repos, edges, stats |
| "Starting work on a task" | `get_task_context("fix the login bug")` — full execution context adapted to bugfix/feature/refactor |
| "PR impact report" | `trace-mcp ci-report --base main --head HEAD` — blast radius, risk score, test gaps |
| "How much am I spending on tokens?" | `get_session_analytics` — full breakdown by tool, file, model |
| "Where am I wasting tokens?" | `get_optimization_report` — detects repeated reads, bash-grep, large files |
| "How much would trace-mcp save?" | `get_real_savings` — compares actual reads vs compact alternatives |
| "Quick efficiency benchmark" | `benchmark_project` — synthetic per-category estimate of the structured-task ceiling, not measured savings (use `get_real_savings` for those) |
| "What am I paying before I even type?" | `get_startup_context_audit` — the startup block by source, its share of the bill, what rebuilds it, and a delete-only diff for the text it repeats |
| "What tech isn't covered?" | `get_coverage_report` — gaps in plugin coverage for your deps |

---

## Migrating from 1.x — retired tools

Seven tools were retired in 2.0. Each had been a deprecated alias for a
superset tool that already covered it; every call is expressible in the
replacement without loss of behaviour or response shape.

| Retired tool (1.x) | Replacement (2.0) |
|---|---|
| `pin_symbol { symbol_id }` | `pin { symbol_id }` |
| `pin_file { file_path }` | `pin { file_path }` |
| `search_with_mode { query, mode }` | `search { query, retriever: mode }` |
| `get_dead_exports { file_pattern }` | `get_dead_code { file_pattern, mode: "exports_only" }` |
| `get_untested_exports { file_pattern }` | `get_untested_symbols { file_pattern, scope: "exports_only" }` |
| `get_session_resume { max_sessions }` | `get_wake_up { scope: "resume", max_sessions }` |
| `get_project_memo { include_history, limit }` | `get_wake_up { scope: "project", include_history, history_limit }` |

`pin` accepts `symbol_id` and `file_path` together, pinning both at the same
weight in one call.

One deliberate difference: both retired export-scanning aliases were
TOON-enabled, but only `get_untested_symbols` inherited `output_format`.
Re-measuring the payloads through their replacements put
`get_untested_symbols { scope: "exports_only" }` at **+21.1%** (table mode —
it keeps TOON), while `get_dead_code { mode: "exports_only" }` came in at
**-17.3%** (list mode, because its rows are not uniform). That is well under
the +15% cutoff the TOON allowlist is built on, so wiring it would have cost
tokens rather than saved them. See [TOON savings](toon-savings.md).

Two rarely-used `search` tuning parameters were also removed. Per-channel
fusion weights now come from `~/.trace/tuning.jsonc` (written by
`tune_weights`) instead of `fusion_weights` on every call, and `fusion_debug`
is gone. The nested `fusion_weights` object was the single most expensive
structure in the whole tool schema, paid by every client on every session.

Together these changes cut the always-on tool surface from 148 to 141
registrations and the serialized schema every MCP client without lazy tool
loading pays at session start from 90,579 to 86,217 characters.

Calling a retired name no longer fails with a bare "not found": the server
answers with the replacement call, so a stale `CLAUDE.md` is a one-line fix
rather than a dead end.

### Policy: consolidations retire the old name, they don't alias it forever

This is settled, so future consolidations don't re-litigate it (TRA-205,
folding in the cancelled TRA-212).

**A tool that is consolidated into a superset tool is removed at the next
major, not kept as a permanent alias.** The alias layer TRA-193 shipped
additively was measured (TRA-239: 171 → 172 tools, schema tax up) and retired
outright in 2.0 (TRA-240). Trimming an alias's prose is not enough — the
registration itself is what every client without deferred tool loading pays
for on connect, and token cost is the product.

**The old name gets a call-time hint instead of a registration.** MCP has no
per-tool deprecation signal — a tool is either in `tools/list` or it is a hard
error — so a removed name would otherwise surface as a bare "not found".
`src/server/retired-tools.ts` rewrites that one message to name the
replacement call. It costs nothing on `tools/list`, which is the whole point:
the migration hint lives on the error path, not in the schema payload.

**Renaming purely for clarity is not worth a registration.** Two similarly
named tools that do different things (`tune_decision_weights` vs
`tune_weights`) are disambiguated in their descriptions, not split into new
names with the old ones aliased — a sentence of prose is free, a second
registration is not.

Reopening this needs evidence a retired name is still costing users more than
its removal saved. `src/tools/register/__tests__/tool-schema-budget.test.ts`
is the gate on any change that grows the always-on surface.

### Migrating to 3.0 — Node 22

3.0 raised the Node floor: Node 20 and 21 are no longer supported, and
`node >= 22` is required. No tool signature or response shape changed. If
`npx -y trace-mcp@latest serve` started failing at startup rather than at a
tool call, check `node --version` first.

Both majors landed within a day of each other (2.0.0 on 2026-08-28, 3.0.0 on
2026-08-29), so an install floating on `latest` may have taken both at once.
Pin a major in your MCP client config (`trace-mcp@3`) if you would rather
adopt them deliberately.

---

# Tool index

Source: https://trace-mcp.com/tools-index.html



Every tool the server registers, alphabetically, with the first line of the
description a client receives in `tools/list`. It is generated from the
registrations themselves, so a tool cannot ship without appearing here.

For the tools grouped by what you are trying to do — plus resources, usage
examples and the migration notes — see the [tools reference](tools-reference.md).
The AI-backed tools (`explain_symbol`, `suggest_tests`, `review_change`,
`find_similar`, `explain_architecture`) register from a different module and
need `ai.enabled: true`; they are described
[there](tools-reference.md#ai-powered-optional).

The rows below are registrations, not a count of what you are served. The
{{ site.data.counts.tools }} figure quoted elsewhere counts what any repo gets,
which excludes the framework-specific rows.

| Availability | Meaning |
| --- | --- |
| `always` | Registered on every repo. |
| `framework` | Only when its framework is detected — see [supported frameworks](supported-frameworks.md). |
| `opt-in` | Behind a config flag (`topology.enabled`, `runtime.enabled`) — see [configuration](configuration.md). |

| Tool | What it does | Availability |
| --- | --- | --- |
| `add_decision` | Manually record an architectural decision, tech choice, preference, or convention. | always |
| `analyze_perf` | Per-tool latency telemetry: p50/p95/max, count, error_rate. | always |
| `apply_codemod` | Structural (AST-aware) or regex find-and-replace across files. | always |
| `apply_move` | Move a symbol to a different file or rename/move a file, updating all import paths across the codebase. | always |
| `apply_rename` | Rename a symbol across all usages (definition + all importing files). | always |
| `apply_startup_recommendations` | Apply or preview a get_startup_context_audit recommendation: disable an unused MCP server, move an unused skill aside, or delete duplicated instruction lines. | always |
| `approve_decision` | Approve a decision currently in the memoir-style review queue (review_status="pending"). | always |
| `assess_change_risk` | Before modifying a file or symbol, predict risk level (low/medium/high/critical) with contributing factors and recommended mitigations. | always |
| `audit_config` | Scan AI agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) for stale references, dead paths, token bloat, and (when include_drift is set) drift be… | always |
| `batch` | Execute multiple trace-mcp tools in a single MCP request. | always |
| `benchmark_project` | Synthetic token efficiency benchmark: compare raw file reads vs trace-mcp compact responses across symbol lookup, file exploration, search, and impact analys… | always |
| `build_corpus` | Pack a slice of project context into a persistent corpus on disk so future query_corpus calls can prime an LLM with the same snapshot without re-running the… | always |
| `build_decision_clusters` | Recompute the L2 thematic cluster overlay over the decision store using the configured LLM. | always |
| `call_project_tool` | Relay a trace-mcp tool call to a DIFFERENT registered project than this session's own (cross-project dispatch). | always |
| `change_signature` | Change a function/method signature (add/remove/rename/reorder parameters) and update all call sites. | always |
| `check_architecture` | Check architectural layer rules: detect forbidden imports between layers (e.g. | always |
| `check_claudemd_drift` | Detect drift between AI agent config files (CLAUDE.md, AGENTS.md, .cursorrules) and the live tool/skill/command surface: dead path references, references to… | always |
| `check_duplication` | Check if a function/class name already exists before creating it. | always |
| `check_edit_safe` | Edit-safety preflight: before modifying a symbol or file, get one verdict for "is this safe to edit and what must I preserve". | always |
| `check_embedding_drift` | Pin and re-check a 16-string canary against the active embedding provider. | always |
| `check_quality_gates` | Run configurable quality gate checks (complexity, coupling, circular imports, dead exports, tech debt, security, antipatterns, code smells). | always |
| `check_rename` | Pre-rename collision detection: checks the symbol's own file and all importing files for existing symbols with the target name. | always |
| `compare_branches` | Compare two branches at symbol level: what was added, modified, removed. | always |
| `consolidate_decisions` | LLM-driven semantic dedup of the decision store. | always |
| `delete_corpus` | Remove a saved corpus (manifest + packed body). | always |
| `detect_antipatterns` | Detect performance & design antipatterns: N+1 queries, missing eager loading, unbounded queries, event listener leaks, circular ORM association cycles, missi… | always |
| `detect_ast_clones` | Find Type-2 AST clones: functions/methods with identical structure after normalizing identifiers and literals (tree-sitter parse + AST subtree hash). | always |
| `detect_communities` | Run Leiden community detection on the file dependency graph. | always |
| `detect_drift` | Detect architectural drift: cross-module co-change anomalies (files in different modules that always change together) and shotgun surgery patterns (commits t… | always |
| `detect_topic_tunnels` | Cross-project topic tunnels: links between registered subprojects sharing canonical entities — manifest package names, top-level declared dependencies, and g… | opt-in |
| `diff_graph_snapshots` | Compare two named graph snapshots and report deltas in counts, communities, and top in-degree files. | always |
| `discover_claude_sessions` | Scan ~/.claude/projects for projects Claude Code has touched on this machine, decode each directory name back to its absolute path, and report which ones sti… | opt-in |
| `discover_hermes_sessions` | List Hermes Agent (NousResearch) sessions visible on this machine. | always |
| `embed_repo` | Precompute and cache symbol embeddings for semantic / hybrid search. | always |
| `export_decisions` | Export decisions to JSONL or Markdown. | always |
| `export_graph` | Export the dependency graph in formats external tools understand. | always |
| `export_security_context` | Export security context for MCP server analysis. | always |
| `extract_function` | Extract a line range out of an enclosing function into a new named helper (AST-aware, TypeScript/JavaScript). | always |
| `find_usages` | Find all places that reference a symbol or file (imports, calls, renders, dispatches). | always |
| `generate_docs` | Auto-generate project documentation from the code graph. | always |
| `generate_insights_report` | Single-call narrative health snapshot: god files (PageRank), architectural bridges (edge bottlenecks), risk hotspots (complexity × churn), edge resolution-ti… | always |
| `generate_sbom` | Generate a Software Bill of Materials (SBOM) from package manifests and lockfiles. | always |
| `get_api_contract` | Get API contract (OpenAPI/gRPC/GraphQL) for a service. | opt-in |
| `get_api_surface` | List all exported symbols (public API) of a file or matching files. | always |
| `get_artifacts` | Surface non-code knowledge from the index: DB schemas (migrations, ORM models), API specs (routes, OpenAPI endpoints), infrastructure (docker-compose service… | always |
| `get_call_graph` | Build a bidirectional call graph centered on a symbol (who calls it + what it calls). | always |
| `get_change_impact` | Full change impact report: risk score + mitigations, breaking change detection, enriched dependents (complexity, coverage, exports), module groups, affected… | always |
| `get_changed_symbols` | Map a git diff to affected symbols (functions, classes, methods). | always |
| `get_circular_imports` | Find circular dependency chains in the import graph (Kosaraju SCC algorithm). | always |
| `get_cluster_decisions` | Return the member decisions of a cluster, plus the cluster header. | always |
| `get_co_changes` | Find files that frequently change together in git history (temporal coupling). | always |
| `get_code_owners` | Git-based code ownership: who contributed most to specific files (git shortlog). | always |
| `get_communities` | Get previously detected communities (file clusters). | always |
| `get_community` | Get details for a specific community: files, inter-community dependencies. | always |
| `get_complexity_report` | Get complexity metrics (cyclomatic, max nesting, param count) for symbols in a file or across the project. | always |
| `get_complexity_trend` | File complexity over git history: cyclomatic complexity at past commits. | always |
| `get_component_tree` | Build a component render tree starting from a given .vue file. | framework |
| `get_context_bundle` | Get a symbol's source code + its import dependencies + optional callers, packed within a token budget. | always |
| `get_contract_drift` | Detect mismatches between API spec and implementation: endpoints in spec but not in code, or in code but not in spec. | opt-in |
| `get_contract_versions` | Show version history for a service API contract with breaking change detection between versions. | opt-in |
| `get_control_flow` | Build a Control Flow Graph (CFG) for a function/method: if/else branches, loops, try/catch, returns, throws. | always |
| `get_coupling` | Coupling analysis: afferent (Ca), efferent (Ce), instability index per file. | always |
| `get_coupling_trend` | File coupling over git history: Ca/Ce/instability at past commits. | always |
| `get_coverage_report` | Technology profile of the project: detected frameworks/ORMs/UI libs from manifests (package.json, composer.json, etc.), which are covered by trace-mcp plugin… | always |
| `get_cross_domain_deps` | Show which business domains depend on which. | always |
| `get_cross_service_impact` | Analyze cross-service impact of changing an endpoint or event. | opt-in |
| `get_cross_workspace_impact` | Show which workspaces are affected by changes in a given workspace. | always |
| `get_dataflow` | Intra-function dataflow analysis: track how each parameter flows through the function body — into which calls, where it gets mutated, and what is returned. | always |
| `get_dead_code` | Dead code detection. | always |
| `get_decision` | Fetch a single decision by id, including its full `content`. | always |
| `get_decision_clusters` | List decision clusters with optional full-text filter. | always |
| `get_decision_stats` | Overview of the decision knowledge graph: total decisions, active/invalidated counts, breakdown by type and source. | always |
| `get_decision_timeline` | Chronological timeline of decisions for a project, symbol, or file. | always |
| `get_dependency_diagram` | Render dependency diagram for a file/directory path as Mermaid or DOT. | always |
| `get_di_tree` | Trace NestJS dependency injection tree (what a service injects + who injects it). | framework |
| `get_domain_context` | Get all code related to a specific business domain. | always |
| `get_domain_map` | Get hierarchical map of business domains with key symbols per domain. | always |
| `get_edge_bottlenecks` | Find architectural bottleneck edges in the import graph: edges on many shortest paths (betweenness), edges whose removal would disconnect the graph (bridges)… | always |
| `get_endpoint_analytics` | Per-route analytics: request count, error rate, latency, caller services. | opt-in |
| `get_env_vars` | List environment variable keys from .env files with inferred value types/formats. | always |
| `get_event_graph` | Get event/signal/task dispatch graph (Laravel events, Django signals, NestJS events, Celery tasks, Socket.io events). | framework |
| `get_feature_context` | Search code by keyword/topic → returns ranked source snippets within a token budget. | always |
| `get_federation_impact` | Aggregates cross-repo impact into ONE call: if you change an endpoint, service, or symbol, this combines subproject client-call impact (which repos/files cal… | opt-in |
| `get_file_health_timeline` | Aggregates get_complexity_trend, get_coupling_trend, and get_git_churn into one per-file time series: complexity, coupling, and a lightweight risk_score per… | always |
| `get_git_churn` | Per-file git churn: commits, unique authors, frequency, volatility assessment. | always |
| `get_graph_timeline` | Graph-evolution timeline: samples evenly-spaced historical commits (via git log) and reports file-count + commit churn per period, with a short narrative dif… | always |
| `get_health_trends` | Time-series health metrics for a file or module: bug score, complexity, coupling, churn over time. | always |
| `get_implementations` | Find all classes that implement or extend a given interface or base class. | always |
| `get_import_graph` | Show file-level dependency graph: what a file imports and what imports it (requires reindex for ESM edge resolution). | always |
| `get_index_health` | Get index status, statistics, health, and pipeline progress (indexing, summarization, embedding). | always |
| `get_livewire_context` | Get full context for a Livewire component: properties, actions, events, view, child components. | framework |
| `get_middleware_chain` | Trace middleware chain for a route URL (Express/NestJS/FastAPI/Flask). | framework |
| `get_minimal_context` | Single-call orientation context (~150 tokens). | always |
| `get_model_context` | Get full model context: relationships, schema, and metadata (Eloquent/Mongoose/Sequelize/SQLAlchemy/Prisma/TypeORM/Drizzle). | framework |
| `get_module_graph` | Build NestJS module dependency graph (module -> imports -> controllers -> providers -> exports). | framework |
| `get_navigation_graph` | Build React Native navigation tree from screens, navigators, and deep links. | framework |
| `get_nova_resource` | Get full context for a Laravel Nova resource: model, fields, actions, filters, lenses, metrics. | framework |
| `get_optimization_report` | Detect token waste patterns in AI agent sessions: repeated file reads, Bash grep instead of search, large file reads, unused trace-mcp tools. | always |
| `get_outline` | Get all symbols for a file (signatures only, no bodies) — cheaper than Read for understanding a file before editing. | always |
| `get_package_deps` | Cross-repo package dependency analysis: find which registered projects depend on a package, or what packages a project publishes. | always |
| `get_pagerank` | File importance ranking via PageRank on the import graph. | always |
| `get_plugin_registry` | List all registered indexer plugins and the edge types they emit. | always |
| `get_preset_info` | Show active tool preset, available presets, which tools are registered in this session, and which are deferred (loadable via load_tools). | always |
| `get_project_health` | Structural health: coupling instability, dependency cycles, PageRank rankings, refactor candidates. | always |
| `get_project_map` | Get project overview: detected frameworks, languages, file counts, structure. | always |
| `get_real_savings` | A/B comparison: how many tokens could be saved by using trace-mcp instead of raw Read/Bash file reads. | always |
| `get_refactor_candidates` | Find functions with high complexity called from many files — candidates for extraction to shared modules. | always |
| `get_related_symbols` | Find symbols related via co-location (same file), shared importers, and name similarity. | always |
| `get_request_flow` | Trace request flow for a URL+method: route → middleware → controller → service (Laravel/Express/NestJS/Fastify/Hono/tRPC/FastAPI/Flask/DRF). | framework |
| `get_risk_hotspots` | Code hotspots: files with both high complexity AND high git churn (Adam Tornhill methodology). | always |
| `get_runtime_call_graph` | Actual call graph from runtime traces (vs static analysis). | opt-in |
| `get_runtime_deps` | Which external services (databases, caches, APIs, queues) does this code actually call at runtime. | opt-in |
| `get_runtime_profile` | Runtime profile for a symbol or route: call count, latency percentiles (p50/p95/p99), error rate, calls per hour. | opt-in |
| `get_schema` | Get database schema reconstructed from migrations or ORM model definitions. | framework |
| `get_screen_context` | Get full context for a React Native screen: navigator, navigation edges, deep link, platform variants, native modules. | framework |
| `get_service_deps` | Get external service dependencies: which services this one calls (outgoing) and which call it (incoming). | opt-in |
| `get_service_map` | Get map of all services, their APIs, and inter-service dependencies. | opt-in |
| `get_session_analytics` | Analyze AI agent session logs: token usage, cost breakdown by tool/server, top files, models used. | always |
| `get_session_journal` | Session history: all tool calls made, files read, zero-result searches, and duplicate queries. | always |
| `get_session_snapshot` | Compact session snapshot (~200 tokens) for context recovery after compaction. | always |
| `get_session_stats` | Token savings stats for this session: per-tool call counts, estimated token savings, reduction percentage, dedup savings, and per-tool latency (p50/p95/max/e… | always |
| `get_startup_context_audit` | What every session pays before your first message, what it costs, and what went unused: source decomposition, cache-rebuild prices, and removals proven unuse… | always |
| `get_state_stores` | List all Zustand stores and Redux Toolkit slices with their state fields, actions/reducers, and dispatch sites. | framework |
| `get_subproject_clients` | Find all client calls across subprojects that call a specific endpoint. | opt-in |
| `get_subproject_graph` | Show all subprojects and their cross-repo connections. | opt-in |
| `get_subproject_impact` | Cross-repo impact analysis: find all client code across subprojects that would break if an endpoint changes. | opt-in |
| `get_suggested_questions` | Auto-generated, prioritized review questions derived from the analyses we already cache (untested framework entry points, circular imports, ast-clone cluster… | always |
| `get_surprises` | Rank cross-module file edges by how unexpected they look (deep folder distance + popular target + few edges = high surprise). | always |
| `get_symbol` | Look up a symbol by symbol_id or FQN and return its source code. | always |
| `get_symbol_complexity_trend` | Single symbol complexity over git history: cyclomatic, nesting, params, lines at past commits. | always |
| `get_symbol_owners` | Git blame-based symbol ownership: who wrote which lines of a specific symbol. | always |
| `get_task_context` | All-in-one context for starting a dev task: execution paths, tests, entry points, adapted by task type. | always |
| `get_tech_debt` | Per-module tech debt score (A–F grade) combining: complexity, coupling instability, test coverage gaps, and git churn. | always |
| `get_tests_for` | Find test files/functions covering a given symbol or file. | always |
| `get_type_hierarchy` | Walk TypeScript class/interface hierarchy: ancestors (what it extends/implements) and descendants (what extends/implements it). | always |
| `get_untested_symbols` | Find symbols lacking test coverage. | always |
| `get_usage_trends` | Daily token usage time-series: sessions, tokens, estimated cost, tool calls per day. | always |
| `get_wake_up` | Compact orientation context (~300 tokens) for session start. | always |
| `get_workspace_map` | List all detected monorepo workspaces with file counts, symbol counts, and languages. | always |
| `graph_query` | Trace how named symbols relate in the dependency graph → returns subgraph + Mermaid diagram. | always |
| `index_sessions` | Index conversation content from Claude Code / Claw Code sessions for cross-session search. | always |
| `invalidate_decision` | Mark a decision as no longer valid. | always |
| `list_bundles` | List installed pre-indexed bundles for dependency libraries. | always |
| `list_corpora` | List every corpus saved on disk with its manifest (scope, project_root, sizes, timestamps). | always |
| `list_graph_snapshots` | List previously captured graph snapshots, most recent first. | always |
| `list_pins` | List all active ranking pins with weight, scope, target, expiry, and creator. | always |
| `list_projects` | List projects registered with trace-mcp (~/.trace/registry.json) — the roots call_project_tool accepts. | always |
| `load_tools` | Load tools this session's preset deferred, by preset name and/or explicit tool names. | always |
| `mine_sessions` | Mine Claude Code / Claw Code session logs for architectural decisions, tech choices, bug root causes, and preferences. | always |
| `pack_context` | Pack project context into a single document for external LLMs. | always |
| `pin` | Boost (or demote) a symbol and/or file in PageRank-driven ranking by setting a multiplicative weight. | always |
| `plan_batch_change` | Analyze the impact of updating a package/dependency. | always |
| `plan_refactoring` | Preview any refactoring (rename, move, extract, signature) without applying. | always |
| `plan_turn` | Opening-move router for new tasks. | always |
| `predict_bugs` | Heuristic bug-risk triage: ranks files by git churn, fix-commit ratio, complexity, coupling, PageRank, and author count — a prioritization heuristic, NOT a v… | always |
| `query_by_intent` | Map a business question to domain taxonomy → returns domain ownership and relevance scores (no source code). | always |
| `query_corpus` | Answer a natural-language question against a saved corpus. | always |
| `query_decisions` | Query the decision knowledge graph. | always |
| `refresh_co_changes` | Rebuild co-change index from git history. | always |
| `regenerate_project_memo` | Synthesise (or refresh) the project memo — a 250-400 word LLM-written orientation digest over the decision store. | always |
| `register_edit` | Notify trace-mcp that a file was edited. | always |
| `reindex` | Trigger (re)indexing of the project or a subdirectory. | always |
| `reject_decision` | Reject a decision currently in the memoir-style review queue (review_status="pending"). | always |
| `remember_decision` | Live agent write into the decision knowledge graph. | always |
| `remove_dead_code` | Safely remove a dead symbol from its file. | always |
| `repair_index` | Apply a targeted repair to the local SQLite index. | always |
| `rollback_startup_recommendations` | Undo one apply_startup_recommendations(dry_run:false) call byte-for-byte: restores files and moved skills. | always |
| `scan_code_smells` | Find deferred work and shortcuts: TODO/FIXME/HACK/XXX comments, empty functions & stubs, hardcoded values (IPs, URLs, credentials, magic numbers), and per-la… | always |
| `scan_security` | Scan project files for OWASP Top-10 security vulnerabilities using pattern matching. | always |
| `search` | Search symbols by name, kind, or text. | always |
| `search_bundles` | Search pre-indexed bundles for symbols from popular libraries (React, Express, etc.). | always |
| `search_sessions` | Search across all past session conversations. | always |
| `search_text` | Full-text search across all indexed files. | always |
| `self_audit` | Dead code & coverage audit: dead exports, untested public symbols, heritage debt. | always |
| `snapshot_graph` | Capture the current graph shape (file/symbol counts, edges by type, top in-degree files, communities, exported symbols) under a named label. | always |
| `subproject_add_repo` | Add a repository as a subproject of the current project. | opt-in |
| `subproject_sync` | Re-scan all subprojects: re-discover services, re-parse contracts, re-scan client calls, and re-link everything. | opt-in |
| `suggest_queries` | Onboarding helper: shows top imported files, most connected symbols (PageRank), language stats, and example tool calls. | always |
| `taint_analysis` | Track flow of untrusted data from sources (HTTP params, env vars, file reads) to dangerous sinks (SQL queries, exec, innerHTML, redirects). | always |
| `trace_state_add_dead_end` | Shortcut to record a failed approach or dead end into task state without a full patch. | always |
| `trace_state_checkpoint` | Save a named state checkpoint snapshot for safe rollback if a future exploration path fails. | always |
| `trace_state_get` | Retrieve current execution state for a task in compact markdown (~150 tokens) or full JSON. | always |
| `trace_state_init` | Initialize structured execution state for a task (arXiv:2608.26263). | always |
| `trace_state_list` | List recent agent execution task states and their status in storage. | always |
| `trace_state_patch` | Apply an RFC 7396 JSON Merge Patch to update state atomically. | always |
| `trace_state_rollback` | Rollback task execution state to a previously saved checkpoint snapshot by label or ID. | always |
| `traverse_graph` | Walk the dependency graph from a starting symbol or file using BFS/DFS, with a hard token budget on the response. | always |
| `tune_decision_weights` | Decision memory, not retrieval ranking (that is `tune_weights`): re-fit decision confidence weights from accumulated review feedback (approve/reject events). | always |
| `tune_weights` | Retrieval fusion ranking for `search`, not decision memory (that is `tune_decision_weights`): read the persistent ranking ledger and learn per-repo signal-fu… | always |
| `unpin` | Remove a ranking pin by target. | always |
| `verify_index` | Read-only structural check of the local SQLite index: SQLite integrity_check, foreign-key violations, required-table presence, FTS5 integrity-check, embeddin… | always |
| `visualize_graph` | Open interactive HTML graph in browser showing file/symbol dependencies. | always |
| `visualize_subproject_topology` | Open interactive HTML visualization of the subproject topology: services as nodes, API calls as edges, health/risk indicators per service. | opt-in |

---

# Serena, Repomix and 20+ code graph MCP servers compared

Source: https://trace-mcp.com/comparisons.html


Most MCP servers in this space do one of three things: pack a repository into a prompt (Repomix), proxy a live language server (Serena), or build a persistent code graph (codegraph, codebase-memory-mcp, trace-mcp). trace-mcp is in the third group, and adds framework-aware edges and code-linked memory on top of the graph. This page is the whole field, tool by tool, with the evidence for each row and the date it was checked.

**Before the tables: a capability table is the cheapest kind of evidence.** Every ✓ below, ours included, is a feature claim. The one claim on this page that was measured rather than asserted is the token cost: [a median {{ site.data.pr_context_bench.median_savings_pct }}% fewer input tokens to assemble code-review context](/pr-context-benchmark.html), over {{ site.data.pr_context_bench.pr_count }} merged pull requests in {{ site.data.pr_context_bench.repo_count }} open-source repositories nobody here maintains. Two peers below publish token benchmarks of their own — codebase-memory-mcp's arXiv preprint (10× fewer tokens across 31 repos, which we have not reproduced) and codegraph's self-run August 2026 measurement (62% fewer tokens across seven repos, the most transparent self-benchmark in this field). What separates ours is not that it exists: it is that the base and head SHAs, the losing cases, and the single command that re-runs the whole thing all ship inside the repository.

## trace-mcp vs the main alternatives

The tables further down cover the whole field. This section covers the projects people actually evaluate against trace-mcp, in enough depth to decide from. The first five each also have a dedicated page with a focused table, an honest "when to pick theirs" section, and an FAQ; the sixth, CodeGraphContext, is covered here only.

_Every star count, licence and tool-surface figure in this section was re-read from the GitHub API and from each project's own source on **September 3, 2026** (CodeGraphContext on **September 4, 2026**), not carried over from the head-to-head pages. These summaries are written for the hub and rewritten on each pass — never pasted from a spoke, so the hub and its deep-dive pages stay distinct documents rather than drifting into near-duplicates._

### trace-mcp vs Repomix — packing a repository vs indexing it

[Repomix](https://github.com/yamadashy/repomix) (yamadashy/repomix, {{ site.data.competitors.repomix.stars }} stars, TypeScript, MIT, v1.18.0) turns a repository into one prompt-shaped file. It ships an official MCP server (`--mcp`) and a tree-sitter `--compress` mode that keeps imports, classes, functions and signatures and drops the implementation bodies. It is the shortest path from "here is a repository" to "the model has read it": no index build, no daemon, no config, and `repomix --remote owner/name` does the same for a codebase you do not own in seconds. For a small repo, a one-shot question, or pasting context into a web chat with no MCP client at all, that is the right shape of tool, and it is roughly 275× more popular than trace-mcp, which means far more third-party recipes when you need an answer at 2am.

What a pack cannot contain is an edge. `--compress` is lossy summarisation *per file*, so no compression level yields "who calls this" or "what breaks if I change this signature" — a pack holds the bytes that would answer those questions but computes nothing. A pack is also a fixed cost re-paid on every regeneration, and stale from the first edit after it is written (`--watch` re-packs the entire output after a 300 ms debounce, local directories only). trace-mcp pays the indexing cost once and amortises it across every query in the session, reindexes incrementally per changed file, resolves framework edges — route → handler, controller → template, model → table — through {{ site.data.counts.frameworks }} integrations, and has a write path Repomix does not: rename across files, symbol moves, signature changes, AST codemods, verified dead-code removal. Pick Repomix when the repo fits in context; pick a graph when it does not, or when the question is structural. → [trace-mcp vs Repomix](/vs/repomix.html)

### trace-mcp vs Serena — a live LSP proxy vs a precomputed graph

[Serena](https://github.com/oraios/serena) (oraios/serena, {{ site.data.competitors.serena.stars }} stars, Python, MIT) drives real language servers rather than parsing code itself: its README claims support for over 40 programming languages through LSP, plus an optional bridge into a running JetBrains IDE. That is a genuine precision lead in the cases AST heuristics get wrong — overloads, re-exports, generics, dynamic dispatch through interfaces — and it is on by default for Serena where it is opt-in for us. It is also the only peer that shapes its tool surface along two axes at once: fifteen client-specific contexts (Claude Code, Codex, Cursor-style IDE, ChatGPT, JetBrains and more) crossed with nine modes (`planning`, `editing`, `one-shot`, `no-memories`, …). Counted from source today, 50 tool classes are defined, 22 of them optional, leaving **28 enabled by default** — exactly level with trace-mcp's 28-tool `minimal` preset, so the honest comparison there is "the same size", not "half".

The structural difference is edges and durability. Serena is not stateless — `SolidLanguageServer` persists two pickled per-file document-symbol caches under `.serena/cache/`, so warm symbol lookups survive a restart — but it keeps no graph: no import graph, no call graph, no impact traversal. `callHierarchy/incomingCalls` exists in its LSP client layer and no tool class exposes it, so an agent cannot ask Serena for a call graph at all. Its memories are markdown files under `.serena/memories` with topic namespacing and cross-reference integrity checking, which is more than "notes" — but nothing ties a memory to a symbol and nothing rechecks it against the code at recall time, where trace-mcp's decisions bind to symbol IDs, are verified as non-stale before they are served, and surface inside `get_change_impact`. Pick Serena for one mainstream language with a first-class language server, or if you need agent-driven debugging in JetBrains (something we deliberately do not chase — a static graph is the wrong tool for a running process). Pick trace-mcp for polyglot repositories, framework edges no language server models, transitive impact analysis, and the work that comes after navigation. → [trace-mcp vs Serena](/vs/serena.html)

### trace-mcp vs codebase-memory-mcp — the closest peer, opposite bet

[codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp) (DeusData/codebase-memory-mcp, {{ site.data.competitors.codebase_memory_mcp.stars }} stars, C, MIT) is the peer with the same premise as ours — a persistent tree-sitter knowledge graph of functions, classes, call chains, HTTP routes and cross-service links, served over MCP, fully local, no API key — and the opposite bet on how to spend the effort. It goes wide: **162 vendored grammars** compiled into a single binary (against trace-mcp's {{ site.data.counts.languages }}), with Hybrid LSP semantic type resolution layered on about a dozen of them, and **15 MCP tools at roughly 7K tokens of schema** against our ~11.6K — trimmed further by positive allowlists, `--tool-profile scout` exposing seven tools and `analysis` eleven. That ~1.7× advertised-surface gap is the clearest place any competitor beats us, and this page is not going to pretend otherwise. Its supply-chain posture — SLSA Level 3 provenance, VirusTotal-scanned reproducible release candidates, OpenSSF Scorecard — is stronger than ours and can be the whole decision in a regulated shop. Its authors also published a benchmark preprint (arXiv 2603.27277: 83% answer quality, ~10× fewer tokens, 2.1× fewer tool calls across 31 repositories) that we have not reproduced.

Where it stops is depth and writes. A language-agnostic parser produces no framework edges: it knows `UserController` exists, not that it renders `Users/Show.vue` through Inertia. It is read-only — no rename with import rewriting, no symbol moves, no signature changes, no AST codemods, no dead-code removal. Its memory primitive, `manage_adr`, writes flat markdown documents rather than code-linked decisions, so nothing stops a decision about a deleted function from being replayed as if it still held. And it has no code-security analysis — no taint analysis, no SARIF output, no CI quality gates. trace-mcp's OWASP taint analysis with type-aware pruning, configurable quality gates and SARIF 2.1.0 output make its graph a participant in CI rather than a chat aid. (Their supply-chain posture, above, is a different axis and a stronger one than ours.) One thing it has that we do not: `ingest_traces` folds observed runtime caller/callee counts into the graph — dynamic edges static analysis cannot see. → [trace-mcp vs codebase-memory-mcp](/vs/codebase-memory-mcp.html)

### trace-mcp vs codegraph — one advertised tool, tuned for orientation

[codegraph](https://github.com/colbymchenry/codegraph) (colbymchenry/codegraph, {{ site.data.competitors.codegraph.stars }} stars, MIT, v1.6.0 — TypeScript over a native Rust extraction kernel with vendored C grammars) is the largest peer by stars and the sharpest single idea in this field. It defines eight MCP tools — search, callers, callees, impact, node, explore, status, files — and by default **advertises exactly one**. Re-verified in its source at the current commit: `DEFAULT_MCP_TOOLS` is the single-element set `{explore}`, the other seven stay fully implemented and re-enablable through a `CODEGRAPH_MCP_TOOLS` allowlist, and the stated reason in their own comment is that every other tool is a narrower slice of `explore` and *presence itself steers mis-picks*. The whole advertised surface costs roughly **1.9K tokens** against our ~11.6K — about 6× cheaper, real money paid on every session. Its self-run August 2026 benchmark reports 88% fewer tool calls, 62% fewer tokens, 44% lower cost and 53% faster across seven repositories, disclosing the model, the queries, four runs per arm, and a correction to an earlier version of its own harness. It is not third-party reproduced, and it is still the most transparent self-benchmark here.

Two things it is honest about, which we repeat rather than quietly exploit: its README reports that its answers leave roughly 80% *more* retrieval context resident at the end of a multi-turn session than a file-reading agent's do (67K tokens against 18K on VS Code) — fewer tokens processed and a larger persistent footprint are both true at once. And its scope stops at navigation by its own README's account: no rename, no move, no codemod, no taint analysis, no SARIF, no dead-code removal, and no session memory of any kind. Its framework work links URL patterns to handlers across 17 frameworks and does it well, but does not model controller → template or model → table; it parses 34 languages against our {{ site.data.counts.languages }}. Pick codegraph when orientation in an unfamiliar codebase is the whole job and the advertised-surface budget is tight. Pick trace-mcp when the job continues past reading. → [trace-mcp vs codegraph](/vs/codegraph.html)

### trace-mcp vs Context Mode — the adjacent lane, not a rival

[Context Mode](https://github.com/mksglu/context-mode) (mksglu/context-mode, {{ site.data.competitors.context_mode.stars }} stars, TypeScript, v1.0.169) is the one entry here that is regularly mistaken for a competitor and is not one. Read at its source: its eleven advertised tools (`ctx_execute`, `ctx_execute_file`, `ctx_index`, `ctx_search`, `ctx_fetch_and_index`, `ctx_batch_execute`, `ctx_stats`, `ctx_doctor`, `ctx_upgrade`, `ctx_purge`, `ctx_insight`) sit on eight runtime dependencies that contain **no code parser at all** — no tree-sitter, and nothing that resolves a symbol, an import edge or a call edge. Its "12 languages" are subprocess runtimes for *executing* scripts the agent writes, not grammars for parsing your code; reading that number as comparable to our {{ site.data.counts.languages }} is the single easiest mistake to make about these two tools. Its headline 98% figure measures tool-output bytes across committed fixtures — Context7 docs, Playwright snapshots, vitest and tsc output, an nginx log, a 500-row CSV — not task success, and its `BENCHMARK.md` ships those fixtures, which is more than most claims in this field do.

So the two products solve different halves of the same budget. Context Mode compresses what tools *return* — browser automation, logs, big API responses — and routes the agent toward computing over data instead of reading it; trace-mcp makes questions about *code* cheap to ask. Neither substitutes for the other, and running both is a reasonable setup, with the honest caveat that you then pay both advertised surfaces at session start (its eleven tools are always fully loaded; there is no preset system and no deferred loading). Two practical notes before you adopt it: it ships under the **Elastic License 2.0**, not an OSI licence, which is a policy question at many companies before anyone reads a feature table; and its session-continuity coverage is uneven across hosts — full on Claude Code, OpenCode and KiloCode, partial on Cursor, Codex CLI and Kiro, absent on Antigravity IDE and Zed. → [trace-mcp vs Context Mode](/vs/context-mode.html)

### trace-mcp vs CodeGraphContext — the peer that drives the most SCIP indexers
{: #vs-codegraphcontext}

[CodeGraphContext](https://github.com/CodeGraphContext/CodeGraphContext) (CodeGraphContext/CodeGraphContext, started by Shashankss1205, 4,155 stars, Python, MIT, v0.6.10 — self-classified `Development Status :: 3 - Alpha`, last pushed September 2, 2026) goes further on compiler-grade references than anyone else here, ourselves included. Read at source today: tree-sitter parsers for the 23 languages its README lists, plus an opt-in SCIP path (`SCIP_INDEXER=true` in `~/.codegraphcontext/.env`) that shells out to **nine** Sourcegraph indexer families — scip-python, scip-typescript, scip-go, scip-java, scip-clang, scip-dotnet, scip-php, scip-ruby and scip-ctags, covering more than twenty file extensions — folds their symbol data into the same property graph, and supplements with tree-sitter for the files SCIP did not cover. We ship SCIP too and the table below says so, but our bridge auto-runs **three** (scip-typescript, scip-python, rust-analyzer) and otherwise ingests a `.scip` index you produced yourself, so their coverage of the driven-indexer path is three times ours. Both sides are opt-in and both cost the same thing: every language needs its indexer binary installed first, and C/C++ additionally need a `compile_commands.json` compilation database or CodeGraphContext logs a warning and falls back to tree-sitter for that repo. It also ships two things nobody else here has. **Bundles** are portable `.cgc` pre-indexed graph snapshots — exportable, importable, and downloadable from a public registry hosted as a Hugging Face dataset (`cgc bundle load flask`) — so you can skip indexing a dependency by fetching someone else's graph of it; no other peer, ours included, offers that. And its datasource ingesters pull **MySQL, Redis and Cassandra** structure into the graph alongside the code.

Where it differs from us is surface, storage and scope. Its `tool_definitions.py` defines **29 tools and advertises all 29** — about 3.7K tokens of schema against our ~9.8K of schema, ~11.6K once the server-instructions block we also pay at session start is counted — with an `mcp.json` `disabledTools` **denylist** as the only trimming knob, the exact opposite of codegraph's one-tool opt-in allowlist and of our presets. Storage is a decision you have to make rather than a detail you never see: six backend options (FalkorDB Lite, FalkorDB Remote, KuzuDB, LadybugDB, Neo4j, Nornic DB), defaulting to FalkorDB Lite on Unix with Python 3.12+ and documented with unusual honesty — their own dependency comments record that Kùzu was archived upstream in October 2025 and that redis-py is pinned to 5.x to keep FalkorDB Lite's Unix-socket path working. trace-mcp ships one embedded SQLite+FTS5 store and no backend question at all. Framework awareness stops at two hardcoded Java Spring tools (`find_java_spring_endpoints`, `find_java_spring_beans`) rather than {{ site.data.counts.frameworks }} integrations resolving route → handler, controller → template and model → table. And it reads and analyses rather than writes: `find_dead_code`, cyclomatic complexity and `simulate_architectural_change` all report — no refactoring, codemods, taint analysis, SARIF output or code-linked decision memory. One thing it has that our fixed tool set does not: `execute_cypher_query`, a raw graph query escape hatch, if you are willing to write Cypher.

Pick CodeGraphContext when compiler-grade references on a mainstream language justify installing an indexer toolchain, or when a pre-built bundle saves you an index you would otherwise build. If you are looking for a CodeGraphContext alternative, pick trace-mcp when you want one embedded store, framework edges and a write path. _(Covered here on the hub only — this peer has no dedicated page.)_

### If you are comparing two of the alternatives to each other

Not every reader arrives having already picked us. [Repomix vs codegraph](/vs/repomix-vs-codegraph.html) puts those two head-to-head on their own terms — packing versus indexing — with where each is honestly weak (Repomix computes nothing; codegraph leaves a larger context footprint and says so; neither one writes code; neither benchmark is third-party) and where trace-mcp sits between them.

## Verification methodology

How the rows below are checked, and when. Star counts render from
`docs/_data/competitors.yml`, re-read from the live GitHub API rather than
carried forward; a project not in that file keeps the figure from the pass that
last checked it, written with a `~`. Rows not named in a pass were not
re-checked in it.

### Pass of September 5, 2026 — current

- **First source read of LeanKG** (FreePeak, Rust, Apache-2.0), the largest remaining table entry whose architecture had never been read. Read through the GitHub API at `main`, no clone: repository metadata, `src/mcp/tools.rs`, `src/mcp/toon.rs`, `src/mcp/token_budget.rs`, `src/budget.rs`, and the per-client packaging directories. Findings and the take-or-pass on each are in the profiling depth tracker at the end of this page.
- **First source read of Roam-Code** (Cranot/roam-code, Python, Apache-2.0), the largest table entry whose architecture had never been read. Read through the GitHub API at `main` plus the published release artefacts, no clone and nothing executed: repository metadata, `src/roam/mcp_server.py` (the preset tables and the tool registrar), `docs/mcp-tools.md`, `README.md`, and the two benchmark trees under `benchmarks/`. Three table cells moved and the one method gap it exposed was closed the same day; findings and the take-or-pass on each are in the profiling depth tracker at the end of this page.
- **First source read of Repomix**, the largest entry in the table and the last one still standing on a README. Read through the GitHub API at `main`, no clone: the MCP server and its eight tool modules, the token-budget guard, the tree-sitter compression path, and the skill generator. The row understated it — findings and the take-or-pass on each are in the profiling depth tracker at the end of this page.
- **First source read of marm-memory**, and it corrected the row: the project delegates code indexing to another project's MCP server rather than doing it, which changes what it is in this table. Details and the decision are in the profiling depth tracker at the end of this page.
- Rows for other projects were not re-checked in this pass and carry the September 3 figures.

### Pass of September 3, 2026

- **All five head-to-head peers re-read at source** for the per-competitor summaries above; star counts, licences and versions from the GitHub API.
- **codegraph** — its single-advertised-tool default, eight tool names, 34 languages and 17 routing frameworks, from its own source and README.
- **Serena** — tool registry recounted: 50 tool classes, 22 optional, 28 enabled by default, down from the 52 / 23 / 29 read on August 30, plus fifteen client contexts and nine modes.
- **Context Mode** — eleven `ctx_*` tools, eight runtime dependencies with no code parser among them, the Elastic-2.0 licence and the per-host session-support table.
- **codebase-memory-mcp** — 162 languages, 15 tools, and the `--tool-profile scout` / `analysis` allowlists at seven and eleven tools.
- **Repomix** — v1.18.0, the `--watch` 300 ms debounce and its local-only restriction.
- **One figure dropped rather than carried forward.** Repomix's "~70% reduction" is no longer published in its README or its code-compress guide, so this page quotes no percentage for `--compress`.

### Pass of September 2, 2026

- **First source read of the incremental-graph peer.** code-review-graph, cloned at commit `b58668751ab0`. Findings are in the fifth mechanism under "Deep dive"; the profiling depth tracker at the end of this page records what was read.
- **Re-read of the largest peer's tool surface** at its current commit, to confirm a claim this page had been carrying from an older reading. It held.
- **Star figures re-checked against the GitHub API** for both, and both had moved.

### Pass of August 30, 2026

- **Second, deeper source read of Serena**, the largest LSP-native peer. It corrected four rows in Serena's favour — see [trace-mcp vs Serena](/vs/serena.html) for the detail.
- **Star re-check against the API**, each figure re-read rather than carried forward: trace-mcp 100 → 102, Repomix corrected to 28.1K (one table still carried a stale ~26.7K, contradicting the other), codebase-memory-mcp 41.1K → 41.2K, mem0 ~53K → ~64.3K, ConPort 761 → 765, Graphify 110.6K → 112.4K, Headroom 67.6K → 68.1K, codegraph → 68.7K.
- **Deep-dive on the two 60K+-star entrants flagged in a previous revision.** **Graphify** (Python, deterministic AST-to-knowledge-graph skill/MCP server, no vector store): its edge provenance tagging (`EXTRACTED`/`INFERRED`/`AMBIGUOUS`) is a 3-tier scheme trace-mcp's 4-tier `resolution_tier` already exceeds, and its Cypher/GraphML export is a feature trace-mcp already ships (`export_graph`). **Headroom** (Python, reversible tool-output/JSON/log compression layer — library, HTTP proxy or MCP server): compresses arbitrary tool output generically rather than understanding code structure, orthogonal to a code-graph server. Neither closes a real gap; see their rows and footnotes below.
- **"Honest assessment" rewritten** after six of seven identified gaps shipped and went through an adversarial deep-validation pass.

### Standing caveats

- **Star counts are a snapshot, not a ranking.** Counts in this space can jump 3–4× on a trending spike and reverse just as fast.
- **Download counts are not used, ours or theirs.** npm totals are heavily inflated by registry mirrors that re-crawl the whole version history on every publish: for trace-mcp on 2026-08-29, 104 published versions each showed a near-uniform 136–198 weekly downloads while the median version sat at 2 — real users pull `latest`, crawlers enumerate. Stripping publish days leaves an organic baseline of ~20–50/day against a headline of 4,551/28d. The same mechanism applies to every peer here, so a competitor's self-reported download number is not comparable evidence in either direction. Stars and GitHub traffic uniques are the adoption metrics used on this page.
- **Everything else is from public documentation and public repositories.** If you maintain one of these projects and see an inaccuracy, [open an issue](https://github.com/nikolai-vysotskyi/trace-mcp/issues) and we will fix it.

## vs. token-efficient code exploration

Tools that help AI agents read code with fewer tokens — AST parsing, outlines, context packing.

| Capability | trace-mcp | Repomix | Context Mode | code-review-graph | jCodeMunch | codebase-memory-mcp | cymbal |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **GitHub stars** | {{ site.data.competitors.trace_mcp.stars }} | {{ site.data.competitors.repomix.stars }} | {{ site.data.competitors.context_mode.stars }} | {{ site.data.competitors.code_review_graph.stars }} | 2.6K | {{ site.data.competitors.codebase_memory_mcp.stars }} | 165 |
| Tree-sitter AST parsing | ✓ {{ site.data.counts.languages }} languages | ✓ compress only (~20) | ✗ no code parsing | ✓ 23 langs + Jupyter | ✓ 70+ languages | ✓ 162 languages | ✓ 22 languages |
| Token-efficient symbol lookup | ✓ outlines, symbols, bundles | ✗ packs entire files | ✗ no symbol index — compresses tool *output* instead | ✓ | ✓ core focus (~95% reduction) | ✓ | ✓ outline/show/context |
| Cross-file dependency graph | ✓ directed edge graph | ✗ | ✗ | ✓ incremental knowledge graph | ✓ import graph | ✓ knowledge graph | ✓ refs/importers |
| Framework-aware edges | ✓ {{ site.data.counts.frameworks }} integrations | ✗ | ✗ | ✗ | ✓ 21 frameworks (route/middleware) | partial (REST routes) | ✗ |
| Impact analysis | ✓ reverse dep traversal + decorator filter | ✗ | ✗ | ✓ blast-radius + Leiden communities | ✓ blast radius + decorator filter | ✓ detect_changes | ✓ impact command |
| Call graph | ✓ bidirectional, graph-based | ✗ | ✗ | ✓ graph-based | ✓ AST-based, bidirectional | ✓ trace_call_path | ✓ refs/importers |
| Refactoring tools | ✓ rename, extract, dead code, codemod | ✗ | ✗ | ✗ | ✗ (dead code detect only) | ✗ | ✗ |
| Security scanning | ✓ OWASP Top-10, taint | ✓ Secretlint | ✗ | ✗ | ✗ | ✗ | ✗ |
| Multi-repo subprojects | ✓ cross-repo API linking | ✓ remote repos | ✗ | ✓ multi-repo daemon | ✓ GitHub repos | ✓ cross-service HTTP linking | ✗ |
| IaC as graph nodes | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ K8s/Kustomize/HCL/Docker | ✗ |
| Session memory | ✓ built-in | ✗ | ✓ SQLite FTS5 journal | ✗ | ✓ index persistence | ✓ persistent graph | ✗ |
| Written in | TypeScript | TypeScript | TypeScript | Python | Python | C | Go |

_New entrants since April 2026 (local code-graph / packing lane, worth tracking): **Repomix** ships an official MCP server (`--mcp`) + tree-sitter `--compress` (signatures kept, bodies dropped); **tokensave** (601 stars, 40+ tools, 30+ langs, pre-indexed semantic KG); **codegraph** (colbymchenry — function-level dep graph, tree-sitter→SQLite, auto-sync; went viral this cycle, now {{ site.data.competitors.codegraph.stars }} stars; its August 2026 re-measurement claims 62% fewer tokens / 88% fewer tool calls / 44% lower cost across seven repos, with model, queries, run count and a correction to its own earlier harness disclosed — self-run, not third-party reproduced, and the most transparent self-benchmark in this field; see [vs codegraph](/vs/codegraph.html)); **Headroom** (68.1K stars — not a code-graph tool, a generic reversible compression layer for tool outputs/JSON/logs/RAG chunks, deployable as library/proxy/MCP server; its `CodeCompressor` is AST-aware for 7 languages but purely for shrinking output bytes, with no graph, no symbol index, no cross-file edges — complements rather than competes with token-efficient *symbol* lookup); **repo-context-mcp** (nduc99911, 103 stars, TypeScript — three tools: `repo_map` directory tree + entrypoint detection, `search_code` substring grep, `pack_context` token-budgeted markdown pack; no AST parsing, no symbol index, no dependency graph — a lighter-weight cousin of Repomix, not a code-graph competitor). `cymbal` could not be re-verified in June 2026 — possibly renamed or inactive. `Context Mode` **is** active and got its source deep-dive on August 30, 2026 (mksglu/context-mode, 20,245 stars, commit `8a35367` = v1.0.169): eleven MCP tools, all advertised, and **no code parser at all** — its eight runtime dependencies contain no tree-sitter, and none of its tools resolves a symbol, an import edge or a call edge. Its "12 languages" are subprocess runtimes for *executing* agent-written scripts, not grammars for parsing your code, and its 98% figure measures tool-output bytes on 14 committed fixtures rather than task success. It also ships under the Elastic License 2.0 rather than an OSI licence. It compresses what tools return; we make questions about code cheap to ask — adjacent lanes that compose rather than compete. See [vs Context Mode](/vs/context-mode.html). The June "could not verify" note was wrong and is retracted here._

**codebase-memory-mcp's stars more than doubled over two revisions (18.1K → 41.0K verified via GitHub API) — the fastest single-project jump we've tracked in this doc.** Its authors also published a benchmark preprint (arXiv 2603.27277: "Codebase-Memory: Tree-Sitter-Based Knowledge Graphs for LLM Code Exploration via MCP") reporting 83% answer quality, 10× fewer tokens, and 2.1× fewer tool calls vs. file-by-file exploration across 31 real-world repos — the first published third-party-style benchmark from a direct code-graph peer (vs. the self-reported numbers most others cite). We have not independently reproduced it. This doesn't change our positioning (see "Honest assessment" below) but is worth flagging: a fast-growing peer with a real benchmark paper is a sharper competitive signal than a star count alone.

## vs. AI session memory

Tools that persist context across AI agent sessions — activity logs, knowledge graphs, memory compression.

| Capability | trace-mcp | Kage | MemPalace | claude-mem | mem0 / OpenMemory | engram | ConPort |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **GitHub stars** | {{ site.data.competitors.trace_mcp.stars }} | new (2026) | ~56.7K | 91.8K | ~64.3K | 2.7K | 765 |
| Cross-session context carryover | ✓ `get_wake_up { scope: "resume" }` + decisions | ✓ git-committed packets | ✓ wings/rooms | ✓ core focus | ✓ multi-level (User/Session/Agent) | ✓ branch-scoped handoffs | ✓ |
| Cross-session content search | ✓ `search_sessions` FTS5 | partial (JSON packets) | ✓ vector+keyword+temporal (+optional rerank), 96.6% R@5 LongMemEval | ✓ SQLite + Chroma hybrid | ✓ hierarchical, ≤7K tok/retrieval (94.4 LongMemEval) | ✓ local ONNX embeddings | ✓ vector semantic |
| Decision knowledge graph | ✓ temporal, code-linked | ✓ temporal, code-linked | ✓ temporal + "Closets" storage | ✗ | ✓ temporal + state-key supersession | ✗ | ✓ project-level |
| Code-graph-aware memory | ✓ decisions → symbols & files | ✓ **+ citation verification (staleness check)** | ✗ text-only | ✗ text-only | ✗ text-only | ✗ text-only | ✗ text-only |
| Auto-extraction from sessions | ✓ pattern-based (0 LLM calls); hybrid LLM opt-in | ✗ agent-written | ✗ verbatim, zero extraction | ✓ AI-compressed + citations | ✓ single-pass hierarchical LLM | ✗ | ✗ |
| Wake-up context | ✓ ~300 tok (code-linked decisions) | — | ✓ ~170 tok (AAAK) | ✓ progressive disclosure (~10×) + Endless Mode | ✗ | ✗ | ✗ |
| Decision enrichment in tools | ✓ impact/plan_turn/resume | ✗ | ✗ standalone | ✗ | ✗ | ✗ | ✗ |
| Service/subproject scoping | ✓ decisions per service | ✗ | ✓ wings per project | ✗ | ✗ | ✓ per branch | ✓ per workspace |
| Published retrieval benchmark | ✗ | ✗ | ✓ LongMemEval / LoCoMo / MemBench | ✗ | ✓ LoCoMo / LongMemEval / BEAM | ✗ | ✗ |
| Code intelligence included | ✓ {{ site.data.counts.tools }} tools, 180+ edge types | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Works as standalone memory | ✗ code-focused | ✓ git-native, code-focused | ✓ general-purpose | ✗ Claude-specific | ✓ agent-agnostic | ✓ agent-agnostic | ✓ project-scoped |
| Written in | TypeScript | — | Python | TypeScript | TS + Python | Go / Rust | Python |

> **Key difference:** MemPalace stores "decided to use PostgreSQL" as text in ChromaDB. trace-mcp stores the same decision **linked to `src/db/connection.ts::Pool#class`** — and when you run `get_change_impact` on that symbol, the decision shows up in `linked_decisions`. General-purpose memory tools remember *what you said*. trace-mcp remembers *what you said* AND *which code it's about*.
>
> **Where the field moved (April → June 2026):** (1) Retrieval became a *published number* — mem0 (94.4 LongMemEval, ≤7K tok/retrieval) and MemPalace (96.6% R@5) both ship benchmarks; trace-mcp's decision recall is still FTS5-only with no published figure. (2) **Kage** is the first peer to share trace-mcp's code-linked-memory premise *and* add what trace-mcp lacks: it verifies each memory's cited code at recall and diff time, withholding decisions whose code was renamed/deleted (claimed 0% stale-served). (3) mem0 added search-time temporal decay (1.5× recency / 0.3× stale) and state-key supersession — close analogs to trace-mcp's `order_by:"heat"` and `invalidate_decision`, but automatic.

## vs. documentation generation & RAG

Tools that generate docs from code or provide embedding-based code search for AI retrieval.

| Capability | trace-mcp | Repomix | DeepContext | smart-coding-mcp | mcp-local-rag¹ | knowledge-rag¹ |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| **GitHub stars** | {{ site.data.competitors.trace_mcp.stars }} | {{ site.data.competitors.repomix.stars }} | ~300 | ~200 | ~200 | ~60 |
| Real-time code understanding | ✓ live graph, always current | ✗ snapshot at pack time | ✗ manual reindex | partial (opt-in watcher) | ✗ | partial (file watcher) |
| Auto-generated project docs | ✓ `generate_docs` from graph | ✗ raw file dump | ✗ | ✗ | ✗ | ✗ |
| Semantic code search | ✓ `search` + `query_by_intent` | ✗ no search | ✓ Jina embeddings | ✓ nomic embeddings | ✓ vector search | ✓ hybrid + reranking |
| Framework-aware context | ✓ routes, models, components | ✗ | ✗ | ✗ | ✗ | ✗ |
| Task-focused context | ✓ `get_task_context` — code subgraph | ✗ packs everything | ✗ | ✗ | ✗ | ✗ |
| No doc maintenance needed | ✓ derived from code | ✓ repacks on demand | ✗ manual reindex | partial (auto on startup) | ✗ manual ingest | partial (auto-reindex) |
| Works offline, no API keys | ✓ graph + FTS5 + bundled ONNX embeddings | ✓ | ✗ requires cloud API | ✗ requires local embeddings | ✗ requires local embeddings | ✗ requires local embeddings |
| Incremental updates | ✓ file watcher, content hash | ✗ full repack | ✓ SHA-256 hashing | ✓ file hash + opt-in watcher | ✗ | ✓ mtime + dedup |
| Written in | TypeScript | TypeScript | TypeScript | JavaScript | TypeScript | Python |

_¹ mcp-local-rag and knowledge-rag are document RAG tools (PDF, DOCX, Markdown) — not code-specific. Included for comparison as they occupy adjacent mindshare._

> **Key difference:** RAG tools answer "find code similar to this query." trace-mcp answers "show me the execution path, the dependencies, and the tests for this feature." Graph traversal finds structurally relevant code that embedding similarity misses — and never returns stale results because the graph updates incrementally with every file save. (Independent evidence: the *CodeCompass* study, arXiv 2602.20048, reports +23.2 pp on hidden-dependency tasks from graph navigation over grep-style retrieval.)

## vs. code graph MCP servers

| Capability | trace-mcp | Serena | code-review-graph | codebase-memory-mcp | SocratiCode | Narsil-MCP | Roam-Code |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **GitHub stars** | {{ site.data.competitors.trace_mcp.stars }} | {{ site.data.competitors.serena.stars }} | {{ site.data.competitors.code_review_graph.stars }} | {{ site.data.competitors.codebase_memory_mcp.stars }} | ~900 | ~100 | ~510 |
| Languages | {{ site.data.counts.languages }} | 40+ (73 LSP backends) | 23 + Jupyter | 161 | 19 | 32 | 28 |
| Framework integrations | {{ site.data.counts.frameworks }} | ✗ | ✗ (Python entry points only) | ✗ | ✗ | ✗ | ~15 (ORM N+1 / API drift only) |
| Cross-language edges | ✓ | ✗ | ✗ | ✓ cross-service HTTP | ✓ polyglot dep graph | ✗ | ✓ PHP↔TS API drift |
| MCP tools advertised (default) | 28 `minimal` (~11.6K tok², default); 60 `standard` (~20.5K); {{ site.data.counts.tools }} `full` (~52K) | 28 default (50 defined) | ~28 | 15 all / 11 `analysis` / 7 `scout` (~7K tok, schema only) | 21 | 90 | 246 defined / 17 default `core` (8 presets) |
| Session memory | ✓ | ✓ (notes, not code-linked) | ✗ | ✓ | ✗ | ✗ | ✗ |
| CI/PR reports | ✓ | ✗ | ✓ blast-radius GitHub Action | ✗ | ✗ | ✗ | ✓ SARIF 2.1.0 + GH/GL/Azure |
| Multi-repo subprojects | ✓ | partial (`query_project`, optional, no cross-repo edges) | ✓ multi-repo daemon | ✓ cross-service | ✓ cross-project search | ✗ | ✗ |
| Control-flow / data-flow | ✓ CFG w/ basic blocks + loop back-edges + dataflow | ✗ | ✗ | ✗ | ✗ | ✓ CFG w/ basic blocks + loop edges; type-aware taint | ✗ |
| Security scanning | ✓ OWASP/taint, type-aware pruning | ✗ | ✗ | ✗ | ✗ | ✓ 147 rules (taint/OWASP/CWE) + SBOM + OSV/supply-chain | ✗ |
| IaC as graph nodes | ✓ K8s/Kustomize/HCL/Docker, cross-file resolved to real nodes | ✗ | ✗ | ✓ K8s/Kustomize/HCL/Docker | ✗ | ✗ | ✗ |
| Compiler-grade precision | ✓ opt-in LSP + offline SCIP ingestion (`scip_resolved` tier) | ✓ live LSP (rename/refs/diagnostics) | ✗ | ✗ | ✗ | ✗ | ✗ |
| SARIF / CI-scanning output | ✓ 2.1.0, OASIS-schema-validated | ✗ | ✓ blast-radius GitHub Action | ✗ | ✗ | ✗ | ✓ SARIF 2.1.0 + GH/GL/Azure |
| Graph visualization | ✓ desktop app (cosmos.gl) | ✗ | ✗ | ✓ 3D web UI | ✓ interactive HTML | ✓ SPA frontend | ✗ |
| Knowledge graph queries | ✓ `graph_query` | ✗ | ✗ | ✓ Cypher-like | ✗ | ✓ SPARQL / RDF | ✗ |
| Refactoring tools | ✓ rename/move/signature/codemod/extract¹ | ✓ rename/safe-delete; move/inline via JetBrains bridge only | ✗ | ✗ | ✗ | ✗ | ✗ |
| Antipatterns / clone detection | ✓ 11 antipatterns + 4 code smells (debug artifacts across 10 langs) + AST Type-2 subtree hashing + name/signature duplication | ✗ | ✗ | ✓ MinHash near-clone + Louvain communities | ✗ | ✗ | ✓ 23 patterns + AST Type-2 subtree hashing |
| Architecture governance | ✓ | ✗ | ✓ Leiden communities | ✓ Louvain communities | ✗ | ✗ | ✓ change-safety gates |
| Token savings tracking | ✓ | ✗ | ✓ (~65× median claimed, whole-corpus baseline) | ✓ | ✓ (~61% claimed) | ✗ | ✓ (−80% input tokens claimed on a 41-cell A/B, labelled measured-at-an-older-kernel) |
| Written in | TypeScript | Python | Python | C | TypeScript | Rust | Python |

_² Our token figures are the **whole session-start cost**: `tools/list` schema plus the server-instructions block, because that is what a client actually pays before asking anything. The schema-only split is in the measurement table under "Deep dive" below (`minimal` is ~9.8K schema + ~1.75K instructions). Peer figures are quoted on whatever basis their own source supports and labelled where it differs._

_¹ `apply_codemod` now rewrites on `@ast-grep/napi` (true AST pattern matching, metavariable substitution, no false matches in strings/comments) with automatic regex fallback for non-AST languages; the native binding loads lazily and degrades to regex instead of crashing if missing. `extract_function` is re-enabled with AST free-variable analysis — it detects genuine multi-return-value slices and rejects them with a structured error rather than silently dropping a binding, and lowers `confidence` on shadowed-variable cases instead of misreporting them as clean (see "where competitors lead" below for what deep validation found)._

_New entrants since April 2026 (direct code-graph MCP peers): **grafel** (Rust, multi-repo daemon, cross-repo + IaC topology, watcher-driven, FlatBuffer in-memory graph); **GitNexus** (MCP-native KG, Leiden communities with cohesion scores); **Code Pathfinder** (5-pass AST indexing, NL queries, dataflow); **CodeGraphContext** (tree-sitter **+ optional SCIP indexers** → property graph — profiled at source on September 4, 2026; see [its summary above](#vs-codegraphcontext)); **Graphify** (Python, 112.4K stars — deterministic tree-sitter AST → knowledge graph over 13 languages plus docs/SQL schemas/configs/PDFs/images, no vector store, `/graphify` Claude Code skill or standalone `--mcp` server, `--neo4j` Cypher export, `--wiki` crawlable markdown output; 3-tier edge provenance (`EXTRACTED`/`INFERRED`/`AMBIGUOUS`) is coarser than trace-mcp's 4-tier `resolution_tier`, and it has no refactoring tools, security scanning, or framework-aware edges). Serena's "debugger tool", noted here in an earlier revision as a native feature, is on a re-read of the source an **optional beta bridge into a running JetBrains IDE** (`JetBrainsDebugTool`, alongside twelve other `JetBrains*` tools that proxy to their plugin) — it is not a debugger Serena implements, and it is off by default._

> **Why framework awareness matters:** A graph that knows `UserController` exists but doesn't know it renders `Users/Show.vue` via Inertia is missing the edges that matter most. Framework integrations turn a syntax graph into a **semantic** graph — the agent sees the same connections a developer sees.

## Deep dive: how the two largest peers shape their tool surface

Both of the biggest projects in this space (by stars) made the same product call, independently, and it is the one place they are clearly ahead of trace-mcp today. Verified by reading their source on August 28, 2026, not their READMEs.

**The {{ site.data.competitors.codegraph.stars }}-star entrant (colbymchenry/codegraph, v1.6.0, TypeScript with a Rust kernel)** defines eight MCP tools — search, callers, callees, impact, node, explore, status, files — and by default **advertises exactly one of them**. `DEFAULT_MCP_TOOLS` is the single-element set `{explore}`; the rest stay fully implemented and re-enablable through a `CODEGRAPH_MCP_TOOLS` allowlist env var, but are not listed to agents. The stated reason, in a source comment: every other tool is a narrower slice of what `explore` already does, and *presence itself steers mis-picks*. Their whole advertised surface costs roughly **1.9K tokens** (~390 tokens of schema plus a ~5.8K-character server-instructions block). Two further mechanisms are worth noting: (1) `explore` carries a per-project **adaptive output budget** — total output cap, default file count, per-file cap and clustering threshold all tier on indexed file count, explicitly kept under the host's ~25K-char inline tool-result cap so the result is never externalised to a file the agent has to read back; (2) their file-reading tool deliberately mirrors the host's native Read contract byte-for-byte (`offset`/`limit`, `<n>\t<line>` output, "safe to Edit from") so it can be substituted for Read rather than competing with it.

**The {{ site.data.competitors.codebase_memory_mcp.stars }}-star entrant (DeusData/codebase-memory-mcp, pure C)** ships 15 MCP tools (~7K tokens of schema) and adds **tool profiles**: `--tool-profile=scout` exposes 7, `--tool-profile=analysis` exposes 11, default exposes all 15. Re-verified September 3, 2026: 162 languages (up from 161), Hybrid LSP semantic type resolution across 12 languages, and two tools we had not catalogued — `manage_adr` (create/replace an Architecture Decision Record document) and `ingest_traces` (ingest runtime caller/callee counts to enrich the graph). Supply-chain posture is a deliberate selling point: SLSA Level 3, VirusTotal scanning of three behaviourally identical release candidates, OpenSSF Scorecard.

**Take:** `manage_adr` is a flat markdown document with get/update/sections modes — not code-linked memory, and no reason to copy it; trace-mcp's decisions already bind to symbol IDs and surface inside `get_change_impact`. `ingest_traces` is a genuinely missing capability (runtime-observed dynamic call edges that static analysis cannot see) but is a three-field payload — a thin veneer, worth revisiting only if users ask. The 161-language race stays out of lane, as before.

**What we took — and where it landed.** The August 2026 pass recorded a *default* tool surface small enough to be honest about as the thing to fix, and named a specific bug: the preset gate was silently bypassed on the default daemon-backed path, pinning every session at the full surface. **That is now shipped and closed.** Re-measured on August 30, 2026 with a real `initialize` + `tools/list` round-trip against the built server, reading the wire payload rather than counting names:

| Configuration | Tools | `tools/list` wire | Server instructions |
|---|---:|---:|---:|
| `preset: "minimal"` (shipped default) | 28 | ~9.8K tok | ~1.75K tok |
| `preset: "standard"` | 55 | ~18.8K tok | ~1.75K tok |
| `preset: "full"` (explicit opt-in) | 166 | ~49.9K tok | ~2.1K tok |
| `standard` + `description_verbosity: "none"` | 55 | ~8.4K tok | 0 |

The tool counts in that table are what *this* repo serves, not the preset's ceiling: registration is gated on detected frameworks, so `minimal` (28 tools) hits its ceiling here while `standard` (60 tools) serves 55 of its 60 and `full` ({{ site.data.counts.tools }} tools) serves 166. Quote the ceilings when comparing on paper and the live numbers when comparing session cost — the comparison table above quotes ceilings, so it stays checkable in CI. Measure the live surface on a cold index and you will read low: framework-gated registration only settles once the first index pass completes (a cold run measured 24 / 54 / 165).

So the honest default is **~11.6K tokens, not the ~51K this page used to quote** — a 2.5× correction in our own favour, caused by four landed changes (preset honoured on the daemon path, seven deprecated aliases retired, `compact_schemas` extended to the whole surface, and the default preset moved to `minimal` once `load_tools` made everything outside it one call away) that this page had not caught up with. The `minimal` row's ~9.8K is derived, not re-measured: the preset grew 25 → 28 tools and 30,540 → 34,041 serialized chars when it absorbed the always-load set, +11.5% on the ~8.8K that was measured live.

**A third mechanism worth reading, from the budget-policy peer (GlitterKill/SDL-MCP, 467 stars, TypeScript; source read August 29, 2026, not its README).** It solves the same problem *losslessly* rather than by dropping tools. `src/gateway/index.ts` registers **four** namespace tools — `sdl.query`, `sdl.code`, `sdl.repo`, `sdl.agent` — each of whose wire schema is a `oneOf` over per-action envelopes (`buildGatewayWireSchema` in `src/gateway/thin-schemas.ts`), with the 29 flat tool names kept only as deprecated aliases behind `emitLegacyTools`. `src/gateway/compact-schema.ts` then flattens the union and deduplicates repeated sub-schemas into `$defs`/`$ref` before the schema ever reaches `tools/list`. Two further pieces sit on top: `src/mcp/response-projection/budgets.ts` quantises every tool *result* into eight fixed budget classes (120 / 200 / 500 / 1K / 2K / 8K tokens) rather than accepting an arbitrary caller number, with `Math.min(class, callerCap, 8K)` as the rule; and a result that overflows its class is returned as an opaque **handle** (`responseMode: "handle"`, recovered in 8 KiB pages) instead of being truncated, so nothing is silently lost. Their stated reason for fixed classes is that a size that varies per call makes responses prompt-cache-unstable — the same reasoning drives an explicit ban on timestamps, durations, session IDs, counters and machine paths in default responses.

**Take, with numbers rather than admiration:**
- **Namespace projection: not now.** It would take our 55-tool `standard` surface toward a handful of advertised entries, but it is a breaking change to every tool name, and it moves tool selection from the model's native tool-picker into a `oneOf` discriminator — a real accuracy risk we would be trading blind. Revisit only if the default surface stops shrinking by other means.
- **`$defs`/`$ref` deduplication: measured and rejected.** MCP gives every tool its own `inputSchema`, so `$ref` cannot be shared across tools — only within one. Measured on our own full surface, *all* duplicated property definitions across 165 tools total ~7.5 KB (~2.1K tokens, 4% of the wire), and the top offenders are already small (`output_format` ×10 = 2.6 KB, `detail_level` ×5 = 1.2 KB). Deduplication only pays after gateway consolidation puts many actions under one schema. Filed nothing; this closes the idea.
- **Handles instead of truncation: worth taking**, and independent of the two above.
- **Fixed budget classes: worth taking**, same reason.

**A fourth mechanism, from the largest LSP-native peer (oraios/serena, {{ site.data.competitors.serena.stars }} stars, Python; source read August 30, 2026 at commit `7fcbca7e`, not its README).** It is the only peer that shapes its tool surface along *two* axes instead of one, and the axes are not the one we use.

Serena has no persistent code *graph*: its `solidlsp` layer drives 73 language-server backends live (the README's "over 40 languages" counts languages, not backends), so there is no incremental index, no impact analysis, no PageRank, no co-change. It is not stateless, though — an earlier revision of this page said "every symbol query is an LSP round-trip", and a second source read (August 30, 2026, commit `43ae021`) found two pickled per-file document-symbol caches under `.serena/cache/<language>/`, versioned and keyed by content hash, loaded at startup. Warm symbol lookups survive a restart; what is never stored is edges. The rest of the comparison is about tool-surface shaping, which is where it is genuinely ahead.

The two axes are **context** (who is calling) and **mode** (what phase the work is in). Sixteen context files under `src/serena/resources/config/contexts/` — `claude-code.yml`, `codex.yml`, `vscode.yml`, `chatgpt.yml`, `desktop-app.yml` and so on — each carry an `excluded_tools` list and a per-client `prompt`. Ten mode files (`planning`, `editing`, `one-shot`, `no-memories`, …) carry a second `excluded_tools` list plus phase-specific instructions. `ToolSet.apply()` in `src/serena/agent.py` composes them as ordered set operations over a default-enabled registry, with `included_optional_tools` re-adding, `fixed_tools` overriding wholesale, and a `LEGACY_TOOL_NAME_MAPPING` so renames don't break configs.

Three details are worth recording precisely, because they are the reasoning and not just the shape:

1. **A host's native tools are treated as capability already present, not as competition.** The `claude-code` context excludes `read_file`, `create_text_file`, `find_file`, `list_dir`, `search_for_pattern` and `execute_shell_command` — six tools, deleted from the wire, on the stated grounds that a CLI agent already has them. The `codex` context excludes a slightly different set (it keeps `search_for_pattern`, drops `replace_content`). The suppression list is a per-host claim, and it is data in a YAML file rather than a branch in code.
2. **The prompt names the agent's rationalizations and pre-refutes them.** The `claude-code` prompt does not stop at a routing table; it carries an explicit "Disallowed reasoning" block listing the three excuses agents use for falling back to a native read — "I already know the path", "one Read call is faster than three Serena calls", "the built-in tool description says to use Read for known paths" — and instructs that catching yourself on one of them *is* the signal to switch. This is the same enforcement goal the budget-policy peer pursues by generating client-side hook files, solved in-band for the cost of a few hundred tokens and no files written to the user's repo.
3. **Per-host wire quirks are configuration too.** `structured_tool_output: false` in the `claude-code` context exists because that host does not unpack structured tool output and re-escapes it; `single_project: true` drops project-switching tools entirely whenever a project is given at startup, rather than advertising a switcher that cannot be used.

**Take, with the boundary drawn:**
- **Client-aware tool suppression: taking it.** We read `clientInfo` at `initialize` and currently discard it. Our preset answers "how much capability", which is the right coarse knob and stays; what it cannot express is "this host already has a file reader". The two compose — profile filters after preset — and unlike every other remaining lever on advertised cost, this one gives up no capability, because a tool the host already provides was never ours to lose. Filed as a scoped issue with a per-profile wire measurement required before merge.
- **Rationalization-refuting instructions: taking it,** and separately, because it is a prompt change that can land on its own. Our block routes but does not persuade; the evidence that routing alone is insufficient is in our own repo, where the development instructions needed a pre-tool hook to stop the fallback the routing table had already forbidden. Budget is the constraint: net-neutral or cheaper against the current ~1.75K, measured on the wire, with the behavioural ratio measured rather than assumed.
- **Modes (phase-scoped tool sets): passing for now.** Contexts are inferable — the host tells us who it is. A mode is not: it needs the user to declare "I am planning" versus "I am editing", and a knob nobody sets is a knob that costs documentation and delivers nothing. Revisit only if a host ever surfaces its own phase.
- **`fixed_tools` / wholesale override: already covered** by `load_tools` plus explicit preset config; adding a third override path would be strictly more surface for the same outcome.

**A fifth mechanism, from the incremental-graph peer (tirth8205/code-review-graph, {{ site.data.competitors.code_review_graph.stars }} stars, Python; source read September 2, 2026 at commit `b58668751ab0`, not its README).** It is not a tool-surface idea at all — on that axis this peer is the furthest behind, registering 29 `@mcp.tool()` handlers in `code_review_graph/main.py` and advertising every one of them by default; its own docstring puts the cost at "~8k description tokens per LLM turn" and offers only a manual opt-in allowlist (`serve --tools ...` / `CRG_TOOLS=`) to trim it. The idea worth taking is somewhere else entirely: `code_review_graph/uncertainty.py` treats **an empty result as a first-class answer that owes the caller a reason**.

The reasoning in that file's own module docstring is the part that generalises. A bare `result_count: 0` is ambiguous between "the code really has no such relationship" and "this graph cannot see that relationship" — the target was never indexed, the graph is behind the working tree, or the language has a known static-analysis blind spot. An agent reads the first meaning, then either draws a wrong conclusion or abandons the graph and scans the whole repository by hand. So a single capped sentence on the empty case is a token *saving*, not a cost: roughly thirty tokens of honesty in place of a multi-thousand-token fallback. The implementation keeps that trade honest — a hard `MAX_CONFIDENCE_CHARS = 140` budget, attached only when the result list is empty, so every response that carries results stays byte-identical.

What makes it more than a nice message is that the blind spots are **data keyed by (language, query pattern)**, not scattered conditionals: a `LanguageGap` table pairs a language set with the exact query patterns the gap can affect, so a container-resolution caveat lands on `callers_of` and `tests_for` and never on a file summary, whose empty result has nothing to do with call resolution.

**Take, with the boundary drawn:**
- **Empty-result confidence markers: taking it.** Zero-result answers on our navigation and impact tools should say whether the zero is real or a known limit, under a hard character cap, and only when the result set is empty.
- **A hand-maintained gap table: passing, and we can do better.** Their table is written by hand and has to be pruned by hand as gaps get fixed (the docstring says exactly that). We already store a `resolution_tier` on every edge (`scip_resolved` > `lsp_resolved` > `ast_resolved` > `ast_inferred` > `text_matched`), so the same note can be *derived* from the actual resolved-edge share for that language in the indexed repository, and it goes stale on its own when the numbers move. Their edge model carries only two values here — `EXTRACTED` and `INFERRED`, with `INFERRED` written by the scoped resolver on rewrite — which is why they cannot derive it and we can.
- **Their headline token figure: passing on the framing.** The current README claims ~65× median per-question reduction across six repositories (36×–376×, re-captured 2026-08-02), against a whole-corpus baseline that the same README concedes "no real agent pays". Our own comparable figure comes from real merged pull requests rather than a synthetic upper bound, and we should keep it that way.

Their storage model is otherwise conventional and holds no surprise for us: a single SQLite file with `nodes` / `edges` / `metadata` tables, qualified-name strings rather than ids as edge endpoints, tree-sitter via `tree-sitter-language-pack`, networkx for traversal, igraph only as an optional extra for communities.

## Honest assessment: where competitors lead

No tool is uniformly ahead. trace-mcp is the only one combining framework-aware code intelligence + a refactoring engine + code-linked session memory in a single local MCP server — but on individual axes, specialists go deeper. As of July 2026, six of the seven gaps identified in the June re-verification have shipped and gone through an adversarial deep-validation pass (not just unit tests — a second pass that tried specifically to break each feature). That pass surfaced real bugs, which is itself worth being transparent about:

**Shipped and adversarially validated:**

- **AST-based rewrite engine.** `apply_codemod` now runs on `@ast-grep/napi` (true AST pattern matching, metavariable substitution, no false matches in strings/comments), auto-falling back to the regex engine for non-AST languages. `extract_function` is re-enabled with AST free-variable analysis. Deep validation found and fixed a real crash risk: the native `.node` binding can be silently dropped by npm's known optional-dependency bug (npm/cli#4828), and the codemod/extract modules did static top-level imports of it — meaning a missing binding **crashed the whole MCP server at startup**. Fixed with lazy loading and graceful degradation (verified via a real fresh `npm install` reproducing the drop). Also found and fixed: a shadowed-variable slice could reference the wrong out-of-scope binding in the generated `return`; a genuine multi-return-value slice silently dropped the second binding instead of being rejected; a zero-match codemod returned a hard tool-call error for the normal "nothing to change" outcome.
- **Compiler-grade reference precision via SCIP.** A new `scip_resolved` edge tier (above `lsp_resolved`) ingests precomputed `.scip` indexes (scip-typescript / scip-python / rust-analyzer→SCIP) offline — no live language-server process needed. Deep validation ran a **real `scip-typescript` indexer** end-to-end (not just synthetic protobuf bytes) and found the subsystem produced **zero `scip_resolved` edges on any real input, ever** — two decoder bugs (a length-field evaluation-order bug that corrupted every subsequent read; range fields decoded as zig-zag instead of plain varint, corrupting every position) had passed the original synthetic tests only because the hand-written test fixture shared the same wrong assumptions as the buggy decoder. Both fixed and locked in with a permanent captured-`.scip` regression fixture.
- **Staleness verification of code-linked memory.** `query_decisions`/`get_wake_up` now verify a decision's linked `symbol_id` still resolves and its source is unchanged since `created_at` before serving it — the Kage-style guarantee. Deep validation found the "fail open" contract (never hide a decision just because verification itself errored) was not actually enforced — an internal Store error propagated uncaught, and the recall-timeout fallback then silently returned an *empty* list, i.e. fail-closed data loss disguised as fail-open. Fixed. Also found and fixed a performance issue: verifying 100 decisions could take ~3.1s (synchronous git subprocess spawns per decision); memoized to ~95ms for the common case of decisions clustered on a handful of files (the fully-scattered worst case is unchanged and remains open, see below).
- **Decision-retrieval quality + a tracked benchmark.** `query_decisions` now fuses FTS5 + embedding similarity (reusing the existing Signal Fusion engine) with FTS5 as the zero-dependency fallback, plus a tracked recall@k/MRR benchmark. Deep validation found the benchmark *script* itself was broken (pointed at a build path that doesn't exist under this repo's bundled output) and had silently drifted from the tracked fixture it was supposed to measure. Fixed to run against the real fixture; corrected numbers: recall@1=0.594, recall@3=recall@5=0.969, MRR=0.823.
- **SARIF 2.1.0 output.** `scan_security` / `detect_antipatterns` / `check_quality_gates` now support `output_format: "sarif"`. Deep validation installed a JSON-schema validator and checked real generated payloads against the actual OASIS SARIF 2.1.0 schema (not just eyeballed the shape) — all required fields validated across all three finding shapes. Also found and fixed: the embedded `$schema` URL pointed at a moved/dead (404) location; corrected to the canonical OASIS URL.
- **Real CFG with loop back-edges.** `get_control_flow` now emits loop back-edges, loop-exit edges, and try/catch/finally merge nodes instead of a branch-only tree. Deep validation found and fixed a real pre-existing bug independent of the back-edge feature: the do-while detector matched *any* identifier starting with "do" (`doOuter()`, `document.write()`, `download(x)`) as a loop, injecting phantom back-edges and corrupting cyclomatic complexity on ordinary code. Verified separately: nested loops get correctly independent back-edges, `break` doesn't create a false back-edge, `continue` is modeled distinctly, switch/case fallthrough is branches not a flattened block.
- **Type-aware SAST.** `taint_analysis` now prunes flows that provably terminate at a non-string value (numeric/boolean coercion). Deep validation focused on the highest-risk failure mode for a security tool — silent false negatives from over-pruning — and found two real ones: a variable narrowed to numeric at one point but reassigned to attacker-controlled string input *before* the sink was still (wrongly) pruned; string concatenation and template-literal taint propagation (`s = '' + id`, `` `p-${id}` ``) wasn't tracked at all, producing zero flows for classic injection shapes. Both fixed; verified the fixes don't regress the pruning itself (`String(x)` casts and non-sanitizing look-alike wrappers still flag correctly).
- **IaC as first-class graph nodes.** K8s manifests, Kustomize overlays, and docker-compose→Dockerfile links are now `Resource`/`Module` graph nodes with `imports` edges, not just `get_artifacts` discoveries. Deep validation found the cross-file resolution was worse than "not wired": Kustomize/compose import edges were persisting as **useless source→source self-loops**, and a second bug meant multiple resource references (`resources: [a.yaml, b.yaml]`) silently collapsed into a single edge because they shared one SQLite `INSERT OR IGNORE` key. Fixed with a real post-pass resolver (modeled on the existing wikilink resolver) that now correctly traverses Kustomize Module → Resource and compose service → Dockerfile. Also found Terraform/HCL module→source edges were fully implemented but silently dropped at persist time for lack of a source symbol — fixed.

**Closed the same day it was found (September 5, 2026).** The September 5 profiling pass found one project in this table doing something none of the rest do, us included: it commits a preregistration *before* it measures — question, metric formula, frozen corpus, a pass bar declared unadjustable after seeing data, a recorded prediction — then publishes a **MISS** against its own bar together with a control showing the bar had been calibrated against a corpus nobody can obtain. Our numbers were measured but neither preregistered nor stamped with the build they came from. Both figures on public surfaces now carry a preregistration ([PR review context](/perf/prereg-pr-context/), [aggregate response tokens](/perf/prereg-response-tokens/)), each labelled retrospective because the bars were written after the results were known, and each carries the version and commit it was measured at. The response-token figure publishes as **MISSED**, twice and on opposite halves of its bar. The first run cleared 25% at 29.3% but covered only 88.4% of recorded call volume against a declared 90%. Measuring the tail took coverage to 97.2% and the figure down to 21.1% — the unmeasured calls held the worst per-call ratios in the product, plus 1,731 calls to mutating tools that had been credited a `Read`/`Grep` baseline they never had. So the second run clears coverage and misses the headline. The bar was declared unadjustable and was not adjusted; the figure stays on the storefront at 21% with the miss stated next to it. `tests/docs/savings-claims.test.ts` fails CI on a published figure without a stamp or a preregistration.

**Still genuinely open (honest, not closed by the validation pass):**

- **Advertised tool-surface cost — narrowed ~4.4×, still behind.** Re-measured August 30, 2026: the shipped default is the `minimal` preset at 28 tools / ~9.8K tokens of `tools/list` plus ~1.75K tokens of server instructions — ~11.6K in total, down from the ~51K this page quoted in August. (`standard`, an explicit opt-in, has a 60-tool ceiling and serves 55 of them here, for ~18.8K.) The preset-bypass bug behind that number is fixed and closed. The remaining gap is real but no longer embarrassing: the two largest peers advertise ~1.9K and ~7K tokens, i.e. ~6× and ~1.7× cheaper than our default. Both buy that with a smaller *capability* surface (one peer advertises a single tool); we buy ours by deferring 141 tools out of the default and keeping them one `load_tools` call away. What is still ours to fix is that ~54% of the wire is *descriptions* — measured on the 55-tool `standard` surface, 37.1 KB of the 67.2 KB payload, of which 22.4 KB is tool descriptions and 14.7 KB parameter descriptions. (The earlier "~57% / 22.5 KB of 67.7 KB" reading on this page mixed the two: the KB figure was tool descriptions alone, the percentage was both.) `description_verbosity: "none"` cuts that same surface to 29.4 KB — roughly halving whichever preset you run — but at a cost to tool-selection accuracy nobody has measured. A middle setting that keeps the first sentence and drops the rest is the obvious unexplored win.



- **Validated code-health metric.** A temporal-holdout calibration script now correlates `predict_bugs`/`get_risk_hotspots` against real future-fix commits on this repo (churn Spearman ≈0.34, precision@20 ≈2.1–2.4× over random) and the tool descriptions were reworded to honest "heuristic triage" language. This is evidence, not CodeScene-grade external validation — the gap to a peer-reviewed, cross-repo-validated metric remains.
- **Worst-case decision-verification latency.** The memoization fix above only helps when decisions cluster on a handful of files; a batch fully scattered across N distinct files is still O(N) git subprocess spawns. An async/batched redesign would be needed to bound the worst case.
- **CFG is line-based, not AST-based**, and taint analysis remains lexical/regex, not a real dataflow engine — both are known architectural ceilings, not just untested edge cases; a full AST/dataflow rewrite of either is out of scope for now.

**Deliberately NOT chasing (out of lane or vanity):** live runtime debugging (Serena reaches it by proxying to a JetBrains IDE — runtime, not static graph, and not something a standalone local server can offer); counterfactual architecture simulation / multi-agent swarm (Roam-Code — unverified, speculative); the 161-language count race (codebase-memory-mcp — trace-mcp's {{ site.data.counts.languages }} already covers the real-world long tail); the tool-count arms race for its own sake (Roam 246, Narsil 90 — quality of edges beats tool count; note this is a claim about which tools to *build*, not about how many to advertise by default, where we are currently behind — see above); verbatim chat storage and 20× "Endless Mode" (MemPalace / claude-mem — trace-mcp's extract-then-store model is deliberate, and Endless Mode adds 60–90s latency per tool).

## FAQ

### What is the best MCP server for giving an AI agent codebase context?

It depends on the shape of the question you want the agent to ask. To hand the agent a whole small repository in one prompt, Repomix packs it into a single file. To resolve a symbol with compiler-grade precision in a language you already run a language server for, Serena proxies that server. To ask cheap structural questions repeatedly across a large codebase — who calls this, what breaks if I change it, which route reaches this handler — a precomputed graph is the fit, which is where codegraph, codebase-memory-mcp and trace-mcp sit. The tables on this page compare them row by row.

### What is the difference between packing a repository and indexing it?

Packing serialises the source into one artifact the agent reads: cost scales with repository size and every question re-reads the same text. Indexing parses the source once into a queryable structure — symbols, imports, call edges — and the agent queries it: cost scales with the answer, not with the repository, and the index has to be kept fresh. [Repomix vs codegraph](/vs/repomix-vs-codegraph.html) is that trade-off head-to-head between two tools that each pick one side.

### Do any of these need a language server or a compiler installed?

Serena does by design — it is a language-server proxy, so its coverage is whatever server you have installed for that stack. trace-mcp parses with tree-sitter across {{ site.data.counts.languages }} languages and needs no toolchain; LSP enrichment is opt-in and only raises the resolution tier of edges it already has. That is the core precision-versus-breadth trade in [trace-mcp vs Serena](/vs/serena.html).

### How many tools does each server advertise, and why does it matter?

Every tool a server advertises costs tokens in `tools/list` before the agent asks anything. On the shipped default preset trace-mcp advertises 28 tools at roughly 11.6K tokens; codegraph advertises one, at roughly 1.9K; codebase-memory-mcp ships 15 at roughly 7K. We are behind the cheapest peers on that axis and say so in the deep dive above rather than leaving it to the tables.

### How current is this comparison?

Each figure carries the date of the pass that checked it. Star counts render from a single data file re-read from the GitHub API rather than copied between tables, and the verification methodology section lists what each pass actually read — source, README, or API only. The profiling depth tracker at the end marks which entries have had a real source read and which are still README-level.

## Profiling depth tracker

Which entries above got a real read of their architecture/code and a concrete take-or-pass decision, vs. which are still table rows filled from README/star-count checks only. Used to pick where the next competitor-intel pass digs deeper instead of re-scanning the same surface facts.

**Profiled deep (architecture/code read, explicit take-or-pass with reasoning):** Graphify, Headroom, Kage, mem0/OpenMemory, MemPalace, codebase-memory-mcp, codegraph, SDL-MCP, Serena, code-review-graph, CodeGraphContext, LeanKG, Roam-Code, Repomix, marm-memory.

**Tracked, still surface-level only (README + stars, no code/architecture read yet):** SocratiCode, Narsil-MCP, tokensave, jCodeMunch, cymbal, DeepContext, smart-coding-mcp, mcp-local-rag, knowledge-rag, ConPort, engram, claude-mem, repo-context-mcp, grafel, GitNexus, Code Pathfinder, CodeGraph (codegraph-ai).

Still unprofiled from the previous pass's "newly spotted" list: **CodeGraph** (codegraph-ai, 74 stars, C — 42 MCP tools, 38 languages, VS Code extension). marm-memory has since been read at source — see below.

**SDL-MCP profiled this pass** (August 29, 2026 — repo cloned and read: `src/gateway/`, `src/mcp/response-projection/`, `docs/architecture.md`, `docs/tool-output-contract.md`, `docs/tool-enforcement.md`). Findings and the take-or-pass on each are in the tool-surface deep dive above. Three things beyond the budget layer are worth recording here rather than re-discovering: it runs on an embedded **graph** database (LadybugDB / Kuzu engine) rather than SQLite+FTS5; it ships **client-side enforcement generation** (`sdl-mcp init --client claude-code --enforce-agent-tools` writes `.claude/settings.json` hooks, a subagent, and repo-local instruction files whose job is to stop the agent falling back to native Read/Bash) — a distribution idea, not a code idea, and the one place a peer is doing something we are not; and it treats native-tool substitution as a design goal, mirroring the host's Read contract byte-for-byte, which is the same move the largest peer makes.

**Serena profiled this pass** (August 30, 2026 — repo cloned at commit `7fcbca7e` and read: `src/serena/agent.py`, `src/serena/tools/`, `src/serena/config/`, all sixteen context and ten mode YAMLs, `src/solidlsp/`, `CHANGELOG.md`). Findings and the take-or-pass on each are in the fourth mechanism under "Deep dive" above; it also corrected the debugger claim this page carried. A second, deeper read followed on August 30 at commit `43ae021`, and it corrected this page as much as it corrected theirs — see the [vs Serena](/vs/serena.html) page for the four rows that moved in Serena's favour. Two facts worth recording here rather than re-deriving: it keeps **no persistent graph**, but it does persist per-file document-symbol caches (an earlier revision of this page wrongly said every symbol query is a live LSP round-trip), and its "memory" is markdown files under `.serena/memories` — topic-namespaced and cross-referenced, but neither code-linked nor staleness-verified, which is why our row reads "✓ (notes, not code-linked)".

**Context Mode profiled this pass** (August 30, 2026 — read via the GitHub API at commit `8a35367`, no clone: `package.json`, `src/server.ts`, `src/executor.ts`, `BENCHMARK.md`, `LICENSE`). Findings are on the [vs Context Mode](/vs/context-mode.html) page: eleven MCP tools, no code parser among its eight runtime dependencies, "12 languages" that are subprocess runtimes rather than grammars, a 98% figure that measures tool-output bytes on committed fixtures, and Elastic License 2.0 rather than an OSI licence.

**code-review-graph profiled this pass** (September 2, 2026 — repo cloned at commit `b58668751ab0` and read: `pyproject.toml`, `code_review_graph/graph.py`, `main.py`, `uncertainty.py`, `scoped_resolver.py`, `parser.py`, `README.md`). Findings and the take-or-pass are in the fifth mechanism under "Deep dive" above. Three facts worth recording here rather than re-deriving: its stars have moved from the ~19K this page carried to **31.1K** (GitHub API, this pass), current release v2.3.8 (2026-08-21), MIT, Python; its graph is a plain SQLite `nodes`/`edges` schema keyed on qualified-name strings, with an edge confidence model of only two values (`EXTRACTED` / `INFERRED`) against our five resolution tiers; and its advertised surface is 29 tools with no default trimming at all, the opposite end of the same axis from the largest peer.

**codegraph re-verified this pass** (September 2, 2026 at commit `b9ca4b7981116909900368cc1686a1074cd4d4c1`, source re-read rather than carried forward from the August 28 reading this page had been quoting). The claim holds exactly: `src/mcp/tools.ts` defines eight tools and `DEFAULT_MCP_TOOLS` is literally `new Set(['explore'])`, with the other seven reachable only through `CODEGRAPH_MCP_TOOLS`. One caveat for anyone re-checking: a comment inside `getTools()` says "the default 4-tool surface" and is stale prose — the set it points at contains one entry. Stars 69.2K at that reading, re-checked at 69.4K on September 3, 2026.

**CodeGraphContext profiled this pass** (September 4, 2026 — read through the GitHub API at `main`, no clone: repository metadata, `pyproject.toml`, `README.md`, `src/codegraphcontext/tool_definitions.py`, `server.py`, `tools/scip_indexer.py`, `tools/indexing/scip_pipeline.py`, `core/bundle_registry.py`). It had been a table row since April on README and stars alone; this pass exists because `/comparisons.html` ranks first for `codegraphcontext` in Search Console on two passing mentions, which is a poor reason for a reader to arrive and find nothing (measured by the SEO Agent during review of PR #842). Facts worth recording rather than re-deriving: the repo moved from `Shashankss1205/CodeGraphContext` to its own `CodeGraphContext` org, 4,155 stars at this reading, v0.6.10 and still self-classified alpha; 29 tools defined and all 29 advertised, trimmable only by an `mcp.json` `disabledTools` denylist; the SCIP path is off by default and needs a per-language Sourcegraph indexer binary, with C/C++ additionally requiring `compile_commands.json`; and its `.cgc` bundle registry is a Hugging Face dataset (`codegraphcontext/registry`), the only shareable pre-built graph distribution anyone in this field ships. Deliberately no `/vs/` spoke — six existing spokes have earned zero impressions in 28 days, so the hub is where coverage pays.

**All five head-to-head peers re-verified this pass** (September 3, 2026 — read through the GitHub API, no clones: each project's repository metadata, `package.json` or equivalent, README, and the specific source files behind the claims this page makes about tool surfaces). This pass exists because the per-competitor summaries above were written for the hub rather than copied from the head-to-head pages, and every figure in them had to hold at source today. Four numbers moved and are corrected throughout: codegraph 69.4K stars, codebase-memory-mcp 42.1K and 162 languages, Serena 28.8K, Context Mode 20.3K. One claim moved against a previous reading: Serena's registry now defines 50 tool classes with 22 optional, so its default surface is 28 tools rather than the 29 read on August 30 — exactly level with ours. One number was retired rather than re-quoted: Repomix no longer publishes a percentage for `--compress` in its README or its code-compress guide, so this page no longer quotes one.

**LeanKG profiled this pass** (September 5, 2026 — read through the GitHub API at `main`, no clone: repository metadata, `src/mcp/tools.rs`, `src/mcp/toon.rs`, `src/mcp/token_budget.rs`, `src/budget.rs`, and the eight per-client packaging directories). Facts worth recording rather than re-deriving: 216 stars, Rust, Apache-2.0, v0.26.1 (2026-08-22) and pushed to daily; `ToolRegistry::list_tools()` returns **76 tools with no gating of any kind** — the opposite end of the surface axis from the peer that advertises one, and roughly 2.7× our shipped default; it ships install packages for eight separate hosts (Claude Code, Cursor, OpenCode, Kilo, Antigravity, Gemini, CommandCode, plus an `instructions/` rules tree), which is the widest per-client distribution anyone in this field ships.

Four mechanisms were read at source and each got a decision:

- **A tabular encoding for homogeneous object arrays — already ours, and ours is better founded.** Their `toon.rs` hoists the shared key set of an object array into a header row so field names appear once instead of *n* times. That is the same structural win trace-mcp ships as `output_format: "toon"` on 13 tools, against the published TOON spec rather than a homegrown encoder, and losslessly — theirs emits strings unquoted unless they contain a special character, and its own comment claims "~40%" with no measurement behind it. Nothing to take.
- **A fixed per-tool token ceiling with truncation: passing, we already do better.** `TokenBudget::max_tokens_for_tool` is a hardcoded `match` from tool name to a number (800–6000, default 1000), applied by truncating the payload and appending `_token_budget` metadata. trace-mcp's `src/server/budget-defaults.ts` instead caps the *parameters* that generate the payload, at session-budget thresholds, and only when the caller did not set them explicitly — shaping the query rather than cutting the answer in half. A static per-tool ceiling would be a regression on that.
- **A compute budget around heavy graph work: taking it.** `budget.rs` wraps every long-running algorithm in wall-clock, resident-memory and iteration ceilings, checked from inside the hot loops, so a heavy call aborts rather than "running for hours against a 600k-element graph and OOM-killing the host". We have no equivalent: our budgets are all token budgets, and `src/daemon/vitals-log.ts` exists only to leave evidence *after* an OS-level kill, not to prevent one. Filed as TRA-841, tied to the two open daemon-death issues.
- **Enforced cross-tool routing hints: taking the enforcement, not the form.** Their overlapping search tools carry an explicit `Prefer-order (search): …` clause in the description, and a unit test asserts every member of that family still carries one. Our descriptions already carry equivalent pairwise pointers and read better than theirs, but nothing keeps them from drifting as tools are added or descriptions trimmed — and the description is the only routing mechanism that reaches clients where our hook and IDE rules do not. Filed as TRA-842, scoped to a drift test at zero net description cost.

Two things deliberately passed without filing: `report_query_outcome`, a tool the agent calls to report whether a query answered its question (our session mining reconstructs the same signal without asking the agent to spend a call on it), and their unguarded 76-tool surface, which is the problem the preset work already solved for us.

**Roam-Code profiled this pass** (September 5, 2026 — read through the GitHub API at `main`, no clone and nothing executed: repository metadata, `src/roam/mcp_server.py` — the preset tables and the tool registrar — `docs/mcp-tools.md`, `README.md`, and both benchmark trees under `benchmarks/`). Facts worth recording rather than re-deriving: 514 stars (GitHub API, this pass), Python, Apache-2.0, v14.0.2 released the day of this reading and pushed to daily; the surface is **246 tools defined but 17 advertised by default**, not the flat 224 this page carried — `_PRESETS` in `mcp_server.py` names eight selectable sets (`core`, `review`, `refactor`, `debug`, `architecture`, `compliance`, `compile-curated`, `full`) chosen by a `ROAM_MCP_PRESET` env var, with one always-registered meta-tool that reports what the other presets hold. Their headline savings figure is no longer the "~92%" this page quoted; the README now leads with −83% turns / −80% input tokens from a 41-cell A/B and labels the row with the kernel version it was measured at.

Three mechanisms were read at source and each got a decision:

- **Preregistering a benchmark before running it, and publishing the miss: taking it.** `benchmarks/cross-repo-l1/PREREGISTRATION.md` fixes the question, the metric's formula, a frozen 60-prompt corpus, a pass bar declared unadjustable after seeing data, and a recorded prediction — then `RESULTS.md` reports **MISSED** on all three targets, and a home-repo control on the same corpus shows the bar had been calibrated against a corpus that is not in the tree, so no repo could have cleared it. Publishing the negative result *and* the reason the test was mis-calibrated is a stronger credibility move than any number on the page, and it is the one place a peer is clearly ahead of us on method rather than on features. Adopted the same day against our own published figures: both now carry a retrospective preregistration and the build they were measured at, and the aggregate response-token figure publishes as MISSED on its coverage half — see "Closed the same day it was found" above.
- **Default surface chosen from measured tool-firing data, not from curation: taking the method, not the list.** Their `_CORE_TOOLS` set carries per-tool comments recording which tools fired in an A/B and which lost over 25+ runs, and two tools are deleted from the default with the loss recorded in-source next to the deletion. Our `minimal` preset was composed by judgement; `/perf/response-tokens/` now gives us the per-tool measurement that would let it be composed the same way. This belongs with the open preset work rather than in a new issue of its own.
- **A meta-tool as the escape hatch from a small default: passing, ours is already better.** `roam_expand_toolset` is always registered and lists what the other presets contain, but it cannot change the running server — the docs are explicit that acting on it means restarting with a different env var. `load_tools` re-sends the full list in the live session, so the same problem is solved without a restart. Nothing to take.

**Repomix profiled this pass** (September 5, 2026 — read through the GitHub API at `main`, no clone and nothing executed: repository metadata, `src/mcp/mcpServer.ts` and the eight files under `src/mcp/tools/`, `src/cli/cliTokenBudget.ts`, `src/core/treeSitter/` including the parse strategies and the sixteen per-language query files, and the `src/core/skill/` tree). It had been a table row on README and stars alone since this page's first revision, which was wrong for the largest entry we track. 28.2K stars, MIT, TypeScript, v1.18.0 (2026-08-08), pushed daily.

What the row was missing: it is no longer only a packer. It ships an MCP server of **eight tools** — pack local, pack remote, read and grep the packed output, attach an existing pack, generate an Agent Skill, and two raw filesystem tools — and `--compress` is tree-sitter query captures, keeping signature, comment and import lines and dropping bodies, with five per-language strategies over a shared stateless base and a deduplicating chunk set.

Three mechanisms were read at source and each got a decision:

- **A sandbox mode that shrinks the tool set *and* the instructions text together: taking the idea, filing our own half of it.** Under `--sandbox` it registers the raw filesystem tools and does *not* register the ones that reach outside the root (remote fetch, skill writing, arbitrary-path attach) — with a source comment saying the disabled ones must "leave no trace for the agent" — and it swaps in a second instructions string that names only the tools that mode can actually serve. A second comment states plainly that their secret scan is a content heuristic and not an access boundary. Our own instructions block does the opposite: `buildInstructions` takes no preset argument, so on the default surface it routes the agent to 26 tools that are not registered. Filed as an issue with the list and a drift test.
- **A token budget that fails the run: taking it, narrowly.** `validateTokenBudget` throws after the output exists, so a pack over `--token-budget` exits non-zero with a message naming the three ways to get under it. That is a CI gate, not a clamp — it makes "this repository no longer fits the context window" a build failure someone has to answer. Our budgets all shape a response downward instead, which is right for a live agent call and gives nobody a signal in CI.
- **Generating an Agent Skill from a repository: passing on the feature, noting the channel.** `src/core/skill/` writes a Claude Agent Skill — sections, statistics, detected tech stack — as an output format. We already ship skills as a first-class surface, so the feature is not a gap; what is worth recording is that a packer treats the skill format as an output target, which is a distribution surface rather than a code idea.

**marm-memory profiled this pass** (September 5, 2026 — read through the GitHub API at `MARM-main`, no clone and nothing executed: repository metadata, `marm_graph/core/cbm_client.py`, `marm_graph/core/tool_router.py`, `marm_graph/config/settings.py`, `marm_mcp_server/server_stdio.py` and `marm_mcp_server/services/stdio_graph_tools.py`). 350 stars, Apache-2.0, Python, v2.47.0 (2026-09-02). The row this page carried described it as session history, codebase index and a concept graph in one SQLite layer. Two thirds of that is right and the middle third is not: **it does not index code itself.**

`CbmClient` spawns another project's code-graph MCP server as a long-lived stdio child, pins its version in a constant, and re-publishes its answers as five of its own fourteen tools. `tool_router.py` calls exactly six tools on that child by name — `list_projects`, `index_status`, `search_code`, `search_graph`, `get_graph_schema`, `detect_changes` — and reshapes each response for its own surface. The child command is an environment variable, so the binary is swappable; the six names and their response shapes are not.

The decision that matters is not about a feature:

- **The code-graph slot under a memory layer is a real socket with a written contract, and it is the second one we have found.** A memory-first project would rather delegate indexing than build it. That makes "which code-graph server gets plugged in" a distribution question — one this page can record but not answer. Filed internally with the six-tool contract, the two known instances, and a costed recommendation; nothing shipped on the strength of one reading.
- **Their concept graph and compaction are the half they do build**, and they are notes-over-sessions rather than code-linked — the same distinction this page already draws for the largest LSP-native peer's markdown memories. Nothing to take: our decisions bind to symbol IDs and are staleness-verified.

Priority for next deep-dive: **CodeGraph** (codegraph-ai, 74 stars, C — 42 MCP tools, 38 languages, VS Code extension), the last entry from the "newly spotted" list that has never been read at source.

**Bottom line:** trace-mcp's moat — framework-aware graph + refactoring + code-linked memory in one local MCP — is intact and unmatched as a *combination*. Six of seven gaps identified in the June 2026 re-verification are now shipped; the adversarial validation pass that followed found and fixed 15+ real bugs (several of them "the feature silently didn't work at all," not cosmetic) rather than taking the initial implementation on faith. The one deliberately-open gap (a peer-reviewed validated health metric) is honestly labeled as such rather than oversold.

## Next steps

- See the full [tools reference](/tools-reference.html) for every MCP tool trace-mcp exposes, grouped by framework.
- Read the [architecture](/architecture.html) page for how the indexing pipeline, storage, and LSP enrichment fit together.
- Check [supported frameworks & languages](/supported-frameworks.html) to confirm your stack is covered.
- [Get started](/#install) — trace-mcp works out of the box, no configuration required.

---

# Repomix alternative: trace-mcp vs Repomix

Source: https://trace-mcp.com/vs/repomix.html



**TL;DR.** Repomix answers "put my repository into a prompt." trace-mcp answers "let the agent look things up in my repository." Repomix is a packer: it concatenates files into one artifact, optionally stripping function bodies with tree-sitter to save bytes. trace-mcp is an index: it parses the repo into a dependency graph with full-text search and serves it over MCP, so an agent asks for the outline of one file or the callers of one symbol instead of loading a snapshot of everything.

If you are picking between them, the question is not "which is better" — it is **how many turns your agent spends in the same repo**.

## Head-to-head

| Capability | trace-mcp | Repomix |
|---|:---:|:---:|
| **GitHub stars** | {{ site.data.competitors.trace_mcp.stars }} | {{ site.data.competitors.repomix.stars }} |
| Model | live index (SQLite + FTS5) | one-shot pack |
| Tree-sitter AST parsing | ✓ {{ site.data.counts.languages }} languages | ✓ `--compress` only (~20) |
| Token-efficient symbol lookup | ✓ outlines, symbols, bundles | ✗ packs entire files |
| Cross-file dependency graph | ✓ directed edge graph | ✗ |
| Framework-aware edges | ✓ {{ site.data.counts.frameworks }} integrations | ✗ |
| Call graph | ✓ bidirectional, graph-based | ✗ |
| Impact analysis | ✓ reverse dependency traversal | ✗ |
| Search | ✓ FTS5 + embeddings + graph | ✓ regex over the pack (`grep_repomix_output`) |
| Refactoring tools | ✓ rename, move, signature, codemod, extract | ✗ |
| Security scanning | ✓ OWASP Top-10, taint analysis | ✓ Secretlint (secrets only) |
| Freshness | ✓ incremental, file-watcher, content hash | ✓ `--watch`, but a full re-pack, local dirs only |
| Remote repositories | ✓ multi-repo subprojects | ✓ packs remote repos directly |
| Official MCP server | ✓ core product | ✓ `--mcp` |
| Setup cost | index build, then instant queries | none, but re-pack on every change |
| Works offline, no API keys | ✓ | ✓ |
| Written in | TypeScript | TypeScript |

## When to pick Repomix

Honest version, and it is a real list:

- **One-shot questions.** "Read this whole repo and tell me what it does" is exactly what a pack is for. No index build, no daemon, no config.
- **Repositories you do not own.** `repomix --remote owner/name` gives you a third-party codebase in a prompt in seconds. trace-mcp can index other repos as subprojects, but that is a heavier setup for a one-time look.
- **Small repositories that fit in context.** If the whole thing fits, a graph buys you nothing — the model already has every file.
- **Zero-install-in-the-loop workflows.** Pasting a pack into a web chat needs no MCP client at all.
- **Ecosystem.** Repomix is roughly 280× more popular by stars, with far more third-party recipes and integrations. That matters when you need an answer from a search engine at 2am.

## When to pick trace-mcp

- **The repo does not fit in context**, so a pack is either truncated or ruinously expensive.
- **Multi-turn sessions.** A pack is a fixed cost re-paid every time it is regenerated; an index is a fixed cost paid once and amortised across every query in the session.
- **The question is structural.** "What breaks if I change this function", "who calls this", "which route handler renders this component" — a pack contains the bytes that would answer this, but nothing that computes it. trace-mcp resolves it as one tool call over the graph.
- **Framework semantics matter.** A graph that knows `UserController` exists but not that it renders `Users/Show.vue` via Inertia is missing the edges a developer actually reasons about. trace-mcp ships {{ site.data.counts.frameworks }} framework integrations for exactly those edges.
- **You want the agent to change code, not just read it.** Rename across files, move a symbol, change a signature, apply an AST codemod, remove verified-dead code — Repomix has no write path.
- **Freshness.** trace-mcp reindexes incrementally on file change, per file. Repomix's `--watch` re-packs the whole output after a 300 ms debounce and only works on local directories; without it, a pack is stale from the first edit after it was written.

## The honest caveat on token cost

trace-mcp's per-query cost is small, but its *advertised* cost is not free: on the shipped default path, `tools/list` is 28 tools and roughly 11.6K tokens, paid by every client that does not support deferred tool loading. That is down from ~50K, once the preset bypass on the daemon-backed path was fixed and the default preset moved to `minimal`; everything outside it is one `load_tools` call away. A short session on a small repo can still genuinely cost less through Repomix. We would rather say that here than have you discover it yourself.

**Our security scanning has a ceiling, and it is stated on the [comparisons page](/comparisons.html) rather than only here.** The control-flow graph is line-based, not AST-based, and taint analysis is lexical/regex, not a real dataflow engine. Type-aware pruning cuts false positives; it does not turn this into a dataflow analyser. A full AST/dataflow rewrite is out of scope for now.

## FAQ

**Is trace-mcp a drop-in replacement for Repomix?**
No. Repomix produces one packed file you paste or attach; trace-mcp exposes MCP tools an agent calls during a session. If your workflow is "paste my repo into a chat", Repomix is the closer fit. If your agent runs many turns against the same repo, trace-mcp replaces the pack entirely, because the agent queries the index instead of re-reading a snapshot.

**Does Repomix have an MCP server?**
Yes — an official one, via `--mcp`, including packing remote repositories. It is still packing rather than indexing: the tools return file content, not a symbol graph.

**Repomix has a `--compress` flag. Isn't that the same as a code graph?**
No. `--compress` uses tree-sitter to strip function bodies and keep signatures, cutting roughly 70% of a pack's bytes. That is lossy summarisation *per file*. There are no cross-file edges, no call graph, and no impact analysis.

**Which one is cheaper in tokens?**
It depends on session length. A pack is a fixed up-front cost, re-paid on every refresh. trace-mcp pays an up-front schema cost (see the caveat above) and then small scoped per-query costs. One-shot question on a small repo: Repomix. Multi-turn session on a repo that does not fit in context: trace-mcp.

**Can I use both?**
Yes, and it is a sensible setup — Repomix for handing a whole small or third-party repo to a model in one shot, trace-mcp for navigation, impact analysis and refactoring in the repo you work in daily.

## Next steps

- Full field: [how trace-mcp compares](/comparisons.html) against 20+ code-graph and memory MCP servers.
- The other head-to-heads: [vs Serena](/vs/serena.html) · [vs codebase-memory-mcp](/vs/codebase-memory-mcp.html) · [vs codegraph](/vs/codegraph.html) · [vs Context Mode](/vs/context-mode.html)
- Comparing Repomix against something other than us: [Repomix vs codegraph](/vs/repomix-vs-codegraph.html) — packing against indexing, on their own terms.
- [Cut Claude Code token usage](/reduce-claude-code-token-usage.html) — the measured tactics, including the ones that have nothing to do with us.
- [Get started](/#install) — no configuration required.

---

# Serena MCP alternative: trace-mcp vs Serena

Source: https://trace-mcp.com/vs/serena.html



**TL;DR.** Serena and trace-mcp both give an agent structured code navigation instead of raw file reads, and they get there from opposite directions. Serena is a **live LSP proxy**: it asks a real language server your question, so its references and renames are compiler-grade, but its world is exactly what the language server knows. trace-mcp is a **precomputed graph**: tree-sitter parsing across {{ site.data.counts.languages }} languages into SQLite, with framework-aware edges, refactoring, security scanning and code-linked memory on top — and LSP or offline SCIP as an *optional* precision upgrade rather than a hard dependency.

Pick Serena if precision on one well-supported language is the whole job. Pick trace-mcp if breadth, framework semantics, or anything beyond navigation is.

## Head-to-head

| Capability | trace-mcp | Serena |
|---|:---:|:---:|
| **GitHub stars** | {{ site.data.competitors.trace_mcp.stars }} | {{ site.data.competitors.serena.stars }} |
| Languages | {{ site.data.counts.languages }} (tree-sitter) | 40+ (73 LSP backends) |
| Requires a language server | ✗ optional enrichment | ✓ core premise |
| Reference precision by default | AST-resolved (tiered) | compiler-grade (LSP) |
| Compiler-grade path | ✓ opt-in LSP + offline SCIP ingestion | ✓ live LSP |
| Framework integrations | ✓ {{ site.data.counts.frameworks }} | ✗ |
| Cross-language edges | ✓ | ✗ |
| Persistent state across restarts | ✓ SQLite + FTS5 graph | partial — on-disk symbol cache, no graph |
| Impact analysis | ✓ reverse dependency traversal + decorator filter | ✗ |
| Call graph | ✓ bidirectional, graph-based | ✗ not exposed as a tool |
| Refactoring tools | ✓ rename, move, signature, AST codemod, extract | ✓ rename, safe-delete; move and inline only via the JetBrains bridge |
| Live debugger | ✗ deliberately out of lane | ✓ via a JetBrains IDE bridge (optional, beta) |
| Session memory | ✓ code-linked decision graph, staleness-checked at recall | ✓ markdown notes, cross-referenced but not code-linked |
| Security scanning | ✓ OWASP Top-10, type-aware taint | ✗ |
| Control-flow / data-flow | ✓ CFG with basic blocks and loop back-edges | ✗ |
| SARIF / CI output | ✓ 2.1.0, schema-validated | ✗ |
| Multi-repo subprojects | ✓ cross-repo API linking | partial — query another project, no cross-repo edges |
| Graph visualization | ✓ desktop app | ✗ |
| MCP tools advertised (default) | 28 (~11.6K tok); {{ site.data.counts.tools }} on `full` | 29; 52 defined |
| Written in | TypeScript | Python |

## When to pick Serena

- **You work in one language with a first-class language server.** For Python or TypeScript, "find all references" and "rename symbol" from a real language server are correct in cases AST heuristics get wrong: overloads, re-exports, generics, dynamic dispatch through interfaces. That is a genuine precision lead, and it is on by default for Serena while it is opt-in for us.
- **You want no index build step.** There is no graph to construct and nothing to re-index after a pull. Serena is not fully stateless, though — `SolidLanguageServer` keeps two pickled per-file symbol caches under `.serena/cache/<language>/` (`raw_document_symbols.pkl`, `document_symbols.pkl`), loaded on start and keyed by file content hash, so warm queries survive a restart. What it does not keep is edges: no import graph, no call graph, no impact traversal.
- **You already work inside a JetBrains IDE.** Serena can bridge into it for debugging — breakpoints, stepping and variable inspection driven by the agent (an optional beta tool, and it needs the IDE plus their plugin running). trace-mcp deliberately does not do this; a static graph is not the right tool for a running process, and we are not planning to chase it.
- **Popularity.** Serena is roughly 280× larger by stars, with correspondingly more community answers and integrations.

## When to pick trace-mcp

- **Your stack is polyglot or unusual.** {{ site.data.counts.languages }} tree-sitter grammars beat 40+ language servers when part of your repo is Terraform, Kotlin, SQL, Dockerfiles, or a language whose LSP is a maintenance liability.
- **The edges you care about are framework edges.** Route → handler, controller → template, model → table, component → component. No language server models these. trace-mcp ships {{ site.data.counts.frameworks }} integrations that do.
- **You want impact analysis, not just references.** "Everything transitively affected by changing this function, filtered to route handlers" is a graph traversal; it is not an LSP request.
- **The job goes past navigation.** Security scanning with type-aware taint pruning, quality gates, dead-code removal, AST codemods, SARIF output for CI, complexity and churn hotspots — Serena's scope stops well before these.
- **Memory should survive the session and be tied to code.** Serena's memories are notes the agent writes. trace-mcp's decisions are linked to symbol IDs, verified as non-stale at recall time, and surface inside `get_change_impact`.

## Where we are not being smug

Two honest points.

First, **the table above used to be built from Serena's README, and reading the source moved four rows in Serena's favour.** We cloned it at commit `43ae021` (version 1.7.1.dev0, MIT) and read its tool package, its memory package, its project server and its `solidlsp` language-server layer. What changed:

- **It is not stateless.** We claimed "no persistent state, per-session". It persists two pickled document-symbol caches per language and loads them at startup (see the bullet above). Warm symbol lookups do survive a restart; only the edges do not exist.
- **It can reach other repositories.** We claimed a flat ✗. `query_project` and `list_queryable_projects` run any read-only Serena tool against another registered project, through a small Flask project server. Both are optional tools, off unless enabled, and there are still no edges between repositories — but "cannot" was wrong.
- **Its memories are more than notes.** We called them manual notes. They are markdown files under `.serena/memories` with topic namespacing, a global scope beside the project scope, and `mem:` cross-references with referential-integrity checking and autofix. What they are not is code-linked: nothing ties a memory to a symbol, and nothing rechecks it against the code when it is recalled. That narrower difference is the real one.
- **Its default surface is 29 tools, not ~55.** The registry marks 52 tool classes, 23 of them optional (13 are the JetBrains bridge), leaving 29 enabled by default — so the honest comparison against our 28 is "the same size", not "half".

Two rows moved the other way, and we state the evidence rather than the verdict. **Call graph**: `callHierarchy/incomingCalls` and `outgoingCalls` are implemented in the LSP client layer, but no tool class calls them, so an agent cannot ask Serena for a call graph. **Move and inline refactoring**: `JetBrainsMoveTool` and `JetBrainsInlineSymbol` proxy to a running JetBrains IDE; both are optional and beta. Native and always-on are `rename_symbol` and `safe_delete_symbol`.

If you maintain Serena and something here is wrong, [open an issue](https://github.com/nikolai-vysotskyi/trace-mcp/issues) and we will fix it.

Second, **our default tool surface is expensive.** trace-mcp advertises 28 tools, roughly 11.6K tokens, on the shipped default path as of August 29, 2026 — down from ~50K, once the preset bypass on the daemon-backed path was fixed and the default preset moved to `minimal`. That is level with Serena's 29 default tools rather than an order of magnitude above it, and anything outside the default is one `load_tools` call away.

**Our security scanning has a ceiling, and it is stated on the [comparisons page](/comparisons.html) rather than only here.** The control-flow graph is line-based, not AST-based, and taint analysis is lexical/regex, not a real dataflow engine. Type-aware pruning cuts false positives; it does not turn this into a dataflow analyser. A full AST/dataflow rewrite is out of scope for now.

## FAQ

**What is the core difference between Serena and trace-mcp?**
Serena proxies a live language server per request: compiler-grade, but only as broad as the language server. trace-mcp precomputes a persistent graph from tree-sitter, then optionally upgrades edges with LSP or offline SCIP. Precision-first and stateless versus breadth-first and persistent.

**Is Serena more accurate than trace-mcp?**
For plain references and renames in a well-served language, yes by default. trace-mcp closes that gap only with opt-in LSP enrichment or a SCIP index, which raise edges to the `lsp_resolved` / `scip_resolved` tier. For cross-language and framework edges, no language server has the answer at all.

**Does trace-mcp need a language server installed?**
No. tree-sitter alone covers {{ site.data.counts.languages }} languages with no toolchain and no compile step. LSP is opt-in; SCIP ingestion is offline. Serena's premise is the opposite — no language server for your stack means no answers.

**Can Serena do impact analysis or framework-aware navigation?**
Not as a graph. "References to this symbol" is an LSP request; "this controller renders that template via Inertia" is not something a language server models.

**Which has a lower startup cost?**
Serena on a cold repo — no index build, though the language server still has to warm up, which on a large TypeScript or Java project is not free. Its pickled symbol caches make the second start cheaper than the first. trace-mcp pays a one-time index build, then serves from SQLite; both survive restarts, but only trace-mcp's stored state includes edges.

## Next steps

- Full field: [how trace-mcp compares](/comparisons.html) against 20+ code-graph and memory MCP servers.
- The other head-to-heads: [vs Repomix](/vs/repomix.html) · [vs codebase-memory-mcp](/vs/codebase-memory-mcp.html) · [vs codegraph](/vs/codegraph.html) · [vs Context Mode](/vs/context-mode.html)
- [Architecture](/architecture.html) — how the indexing pipeline, storage and LSP enrichment fit together.
- [Get started](/#install) — no configuration required.

---

# codebase-memory-mcp alternative: trace-mcp vs codebase-memory-mcp

Source: https://trace-mcp.com/vs/codebase-memory-mcp.html



**TL;DR.** codebase-memory-mcp (DeusData) is the peer closest to trace-mcp's premise: parse a repository into a persistent knowledge graph and serve it to agents over MCP, instead of letting them re-read files. Both do impact analysis, call-path tracing, cross-service linking and infrastructure-as-code as graph nodes.

The split is depth versus breadth, in both directions. codebase-memory-mcp is broader on languages (161 vs {{ site.data.counts.languages }}) and dramatically leaner on advertised tool cost. trace-mcp is deeper per repository: {{ site.data.counts.frameworks }} framework integrations produce edges a language-agnostic parser cannot, and it can act on the graph — rename, move, codemod, remove dead code, scan for vulnerabilities — rather than only describe it.

## Head-to-head

| Capability | trace-mcp | codebase-memory-mcp |
|---|:---:|:---:|
| **GitHub stars** | {{ site.data.competitors.trace_mcp.stars }} | {{ site.data.competitors.codebase_memory_mcp.stars }} |
| Languages | {{ site.data.counts.languages }} | 161 |
| Framework integrations | ✓ {{ site.data.counts.frameworks }} | ✗ (partial REST routes) |
| Persistent knowledge graph | ✓ SQLite + FTS5 | ✓ |
| Knowledge-graph queries | ✓ `graph_query` | ✓ Cypher-like |
| Impact analysis | ✓ reverse dependency traversal + decorator filter | ✓ `detect_changes` |
| Call graph | ✓ bidirectional | ✓ `trace_call_path` |
| Cross-service / multi-repo | ✓ cross-repo API linking | ✓ cross-service HTTP linking |
| IaC as graph nodes | ✓ K8s/Kustomize/HCL/Docker, cross-file resolved | ✓ K8s/Kustomize/HCL/Docker |
| Clone / community detection | ✓ AST Type-2 subtree hashing, 11 antipatterns | ✓ MinHash near-clone, Louvain communities |
| Refactoring tools | ✓ rename, move, signature, AST codemod, extract | ✗ |
| Security scanning | ✓ OWASP Top-10, type-aware taint, SARIF 2.1.0 | ✗ |
| Control-flow graph | ✓ basic blocks, loop back-edges, try/catch merges | ✗ |
| Quality gates in CI | ✓ complexity / security / coverage thresholds | ✗ |
| Code-linked decision memory | ✓ decisions bound to symbol IDs, staleness-verified | partial (`manage_adr` markdown documents) |
| Runtime trace ingestion | ✗ | ✓ `ingest_traces` |
| Graph visualization | ✓ desktop app | ✓ 3D web UI |
| MCP tools advertised (default) | 28 (~11.6K tok); {{ site.data.counts.tools }} on `full` | 15 (~7K tok); profiles: 11 / 7 |
| Supply-chain posture | OpenSSF Scorecard, CodeQL, Semgrep | SLSA L3, VirusTotal, OpenSSF Scorecard |
| Published benchmark | ✓ [PR review context](/pr-context-benchmark.html), one task, losses published | ✓ preprint, 31 repos, not independently reproduced |
| Written in | TypeScript | C |

## When to pick codebase-memory-mcp

This is the peer where the honest list is longest, so here it is in full:

- **Your advertised tool budget is tight.** 15 tools at roughly 7K tokens — or 7 with `--tool-profile=scout` — against trace-mcp's ~11.6K on the shipped default path (down from ~50K; the preset-bypass bug is fixed and the default is now `minimal`). If you run several MCP servers in one client and every one of them is competing for the same context window, a ~1.7× difference still matters. This is the clearest place any competitor beats us, and we are not going to pretend otherwise on our own comparison page.
- **You need a language we do not parse.** 161 grammars against {{ site.data.counts.languages }}. If your repo has one in the gap, none of trace-mcp's depth helps you.
- **You want evidence across a broad workload.** Its authors published a benchmark preprint (arXiv 2603.27277: 83% answer quality, ~10× fewer tokens, 2.1× fewer tool calls across 31 repositories). We have not reproduced it and it is not peer-reviewed. Ours — the [PR review context benchmark](/pr-context-benchmark.html), median {{ site.data.pr_context_bench.median_savings_pct }}% over {{ site.data.pr_context_bench.pr_count }} merged pull requests in {{ site.data.pr_context_bench.repo_count }} repositories we do not maintain — is the more auditable of the two — it ships the exact revisions, the losing cases and the command that re-runs it — but it measures one task, not general exploration, and theirs runs on real open-source repositories too. If breadth of workload is what you need evidence for, theirs is the wider claim.
- **Supply-chain requirements are strict.** SLSA Level 3 provenance plus VirusTotal-scanned reproducible release candidates is a stronger posture than ours, and in a regulated environment that can be the whole decision.
- **You have runtime traces to fold in.** `ingest_traces` enriches the graph with observed caller/callee counts — dynamic edges static analysis cannot see. trace-mcp has no equivalent.

## When to pick trace-mcp

- **Framework semantics.** A graph that knows `UserController` exists but not that it renders `Users/Show.vue` via Inertia is missing the edges developers actually reason about. {{ site.data.counts.frameworks }} integrations produce route → handler, controller → template, model → table and component edges; a language-agnostic parser produces none of them.
- **You want the agent to change code.** Rename across files with import rewriting, symbol and file moves, signature changes that update call sites, AST-based codemods with metavariable substitution, dead-code removal with orphan-import detection. codebase-memory-mcp is read-only.
- **Security is part of the job.** OWASP Top-10 rules, taint analysis with type-aware pruning, and OASIS-schema-validated SARIF 2.1.0 for CI ingestion.
- **Memory should be code-linked and verified.** `manage_adr` writes flat markdown documents. trace-mcp binds decisions to symbol IDs, checks at recall time that the linked code still resolves and is unchanged, and surfaces them inside `get_change_impact` — so a decision about code that was deleted stops being served.
- **Gates, not just reports.** Quality gates with configurable complexity, security and tech-debt thresholds, plus SARIF output, make the graph a CI participant rather than a chat aid.

## The honest caveat on security scanning

**Our security scanning has a ceiling, and it is stated on the [comparisons page](/comparisons.html) rather than only here.** The control-flow graph is line-based, not AST-based, and taint analysis is lexical/regex, not a real dataflow engine. Type-aware pruning cuts false positives; it does not turn this into a dataflow analyser. A full AST/dataflow rewrite is out of scope for now.

## FAQ

**How similar are trace-mcp and codebase-memory-mcp?**
The closest pair in this space. Both parse with tree-sitter into a persistent knowledge graph, both do impact analysis and call-path tracing, both model IaC as nodes. The divergence is what sits on top: framework-aware edges, refactoring, security and code-linked memory for trace-mcp; raw language breadth and a much leaner advertised surface for codebase-memory-mcp.

**It supports 161 languages and trace-mcp supports {{ site.data.counts.languages }}. Does that matter?**
Only if your repository contains a language in the gap. trace-mcp's coverage targets the real-world long tail rather than a count; the depth goes into per-framework semantics instead.

**Is codebase-memory-mcp cheaper in tokens?**
On advertised surface, yes — ~7K against our ~11.6K on the shipped default path as of August 29, 2026. The preset-bypass bug that put us at ~50K is fixed and the default preset is now `minimal`; the remaining ~1.7× gap is real and is the clearest place a competitor leads.

**Does it have a published benchmark?**
Yes — a self-published preprint (arXiv 2603.27277) across 31 repositories, not independently reproduced and not peer-reviewed. So does trace-mcp, narrower: the [PR review context benchmark](/pr-context-benchmark.html), median {{ site.data.pr_context_bench.median_savings_pct }}% over {{ site.data.pr_context_bench.pr_count }} merged pull requests in {{ site.data.pr_context_bench.repo_count }} repositories nobody here maintains, with the losing cases and the re-run command shipped. Theirs is the wider workload; ours is the more auditable.

**Can either one refactor code, not just read it?**
Only trace-mcp. codebase-memory-mcp is read-only analysis.

## Next steps

- Full field: [how trace-mcp compares](/comparisons.html) against 20+ code-graph and memory MCP servers.
- The other head-to-heads: [vs Repomix](/vs/repomix.html) · [vs Serena](/vs/serena.html) · [vs codegraph](/vs/codegraph.html) · [vs Context Mode](/vs/context-mode.html)
- [Decision memory](/decision-memory.html) — how code-linked decisions differ from a notes file.
- [Get started](/#install) — no configuration required.

---

# CodeGraph MCP alternative: trace-mcp vs codegraph

Source: https://trace-mcp.com/vs/codegraph.html



**TL;DR.** codegraph is the largest project in this field by stars, and it made one design decision that is worth understanding before you compare anything else: it defines eight MCP tools and **advertises exactly one of them**. Everything an agent can ask it goes through `codegraph_explore`. The reasoning, stated in its own source, is that the other seven are narrower slices of `explore` and that the mere presence of a tool steers agents into mis-picking it.

trace-mcp bets the other way. It advertises 28 tools on its default preset — navigation, impact analysis, refactoring, security scanning, code-linked memory — because those are genuinely different operations, not slices of one.

Pick codegraph if the job is *orient an agent in a repository it has never seen*, and you want that to cost almost nothing in advertised schema. Pick trace-mcp if the job continues after orientation.

## Head-to-head

| Capability | trace-mcp | codegraph |
|---|:---:|:---:|
| **GitHub stars** | {{ site.data.competitors.trace_mcp.stars }} | {{ site.data.competitors.codegraph.stars }} |
| License | MIT | MIT |
| Languages | {{ site.data.counts.languages }} (tree-sitter) | 34 (tree-sitter, Rust kernel + WASM fallback) |
| Framework integrations | ✓ {{ site.data.counts.frameworks }} | ✓ 17 (route → handler) |
| Framework edges beyond routing | ✓ controller → template, model → table, component → component | partial — route → handler, plus React Native `component`/`property` nodes |
| Cross-language edges | ✓ | ✓ Swift ↔ ObjC, RN bridge / TurboModules / Expo / Fabric |
| MCP tools defined | {{ site.data.counts.tools }} | 8 |
| MCP tools advertised by default | 28 (~11.6K tok) | **1** (`codegraph_explore`) |
| Rest of the surface reachable | ✓ `load_tools`, one call | ✓ `CODEGRAPH_MCP_TOOLS` env allowlist, restart |
| Persistent graph across restarts | ✓ SQLite + FTS5 | ✓ SQLite |
| Runs fully local, no API key | ✓ | ✓ |
| Incremental re-index on save | ✓ | ✓ debounced file watcher |
| Impact analysis | ✓ reverse traversal + decorator filter | ✓ `codegraph_impact` (behind the allowlist) |
| Refactoring tools | ✓ rename, move, signature, AST codemod, extract | ✗ |
| Security scanning | ✓ OWASP Top-10, type-aware taint | ✗ |
| Control-flow / data-flow | ✓ CFG with basic blocks and loop back-edges | ✗ |
| SARIF / CI output | ✓ 2.1.0, schema-validated | ✗ |
| Session memory | ✓ code-linked decision graph | ✗ |
| Multi-repo | ✓ cross-repo API linking into one graph | partial — queries other separately-indexed projects by path |
| Graph visualization | ✓ desktop app | ✗ |
| Published A/B token benchmark | ✗ per-repo `get_real_savings` instead | ✓ 7 repos, methodology disclosed |
| Written in | TypeScript | TypeScript + Rust kernel |

Verified on August 29, 2026 against codegraph's source and README at commit `6a056ec` — the `main` head, shipped after the `v1.6.0` tag. Tool-surface claims come from the source; language, framework and bridging counts come from the README's own tables, which the source directory layout corroborates.

## When to pick codegraph

- **Your agent's expensive problem is orientation, not editing.** codegraph's own benchmark shows its largest wins exactly where an agent would otherwise burn a big slice of budget on find/grep/read before touching the right file — 2 tool calls against 28 on VS Code, 2 against 43 on Excalidraw, zero file reads in both. If most of your sessions are "understand this unfamiliar codebase", that is the shape of win you are buying.
- **You want the cheapest possible advertised surface.** One tool. Nothing else is listed to the model at all. Our 28-tool default is real money next to that, paid on every session by every client that does not defer tool loading.
- **You want a published benchmark you can argue with.** codegraph reports 88% fewer tool calls, 62% fewer tokens, 44% lower cost and 53% faster across seven repositories, and it discloses the model, the queries, four runs per arm, and a correction to an earlier version of its own harness that had let the control arm reach CodeGraph through the shell. It is self-run, not independently reproduced — but it is the most transparent self-benchmark in this field, and it is more than we publish.
- **Indexing speed on very large trees matters.** A native Rust extraction kernel with per-language tree-sitter grammars, a WASM fallback for unbuilt platforms, and cgroup-aware resource scaling for small VPS boxes is a different engineering investment than a TypeScript-only parser, and their reported numbers on the Swift compiler and the Linux kernel reflect it.
- **Popularity.** codegraph is roughly 670× larger by stars, with the community answers and integrations that follow from that.

## When to pick trace-mcp

- **The job goes past navigation.** Rename across a repo, move a symbol with its imports, an AST codemod, a taint scan with type-aware pruning, quality gates, SARIF for CI, dead-code removal. codegraph has none of these, by design and by its own README's scope.
- **The edges you care about are framework edges beyond routing.** codegraph links URL patterns to their handlers across 17 frameworks, and does it well; its React Native work also emits `component` and `property` nodes. It does not model controller → template or model → table. trace-mcp's {{ site.data.counts.frameworks }} integrations do, and traverses them.
- **Your stack is polyglot.** {{ site.data.counts.languages }} grammars against 34.
- **You want memory that outlives the session and is tied to code.** trace-mcp's decisions link to symbol IDs, are verified as non-stale before recall, and surface inside `get_change_impact`. codegraph has no session memory at all.
- **You want the other tools without an env var and a restart.** codegraph's seven unlisted tools are fully implemented and re-enablable through `CODEGRAPH_MCP_TOOLS`; that is a config change, not something the agent can decide mid-task. trace-mcp's deferred surface is one `load_tools` call away inside the session.

## Where we are not being smug

Four honest points.

**Their default surface is cheaper than ours and it is not close.** One advertised tool against our 28 and ~11.6K tokens. Our number is down from ~50K after the preset bypass on the daemon path was fixed and the default preset moved to `minimal`, and everything outside it is one call away — but "much better than we were" is not "as cheap as theirs."

**Their benchmark covers more of the job than ours does.** Both projects now publish an A/B across named repositories with a disclosed harness — theirs across seven repos and a broad slice of session work, ours the [PR review context benchmark](/pr-context-benchmark.html): a median {{ site.data.pr_context_bench.median_savings_pct }}% input-token reduction over {{ site.data.pr_context_bench.pr_count }} merged pull requests in {{ site.data.pr_context_bench.repo_count }} repositories nobody here maintains, with the losing cases and the re-run command shipped in the repo. Theirs covers the wider workload; ours publishes the revisions it ran on and the five cases where it lost. Both run on repositories their authors do not maintain — that is not a point of difference, and this page said otherwise until September 5, 2026. One task type measured well is not the same as a general claim. For everything else we now publish {{ site.data.response_tokens.reduction_pct }}%, measured on {{ site.data.response_tokens.calls_weighted }} real tool calls from one machine against a baseline that is still an estimate — it replaced an aggregate that turned out to be arithmetic on a constant, and [why it moved](/reduce-claude-code-token-usage.html#the-number-moved-on-5-september-2026-and-here-is-why) is published too.

**They publish their own downside, so we will repeat it rather than quietly use it.** codegraph's README states that its responses leave roughly 80% more retrieval context resident at the end of a multi-turn session than a file-reading agent's do — 67K tokens against 18K on VS Code. That is a genuine cost of returning rich graph answers, it is a cost trace-mcp pays in its own form, and the fact that they printed it is a point in their favour.

**Our security scanning has a ceiling, and it is stated on the [comparisons page](/comparisons.html) rather than only here.** The control-flow graph is line-based, not AST-based, and taint analysis is lexical/regex, not a real dataflow engine. Type-aware pruning cuts false positives; it does not turn this into a dataflow analyser. A full AST/dataflow rewrite is out of scope for now.

If you maintain codegraph and something here is wrong, [open an issue](https://github.com/nikolai-vysotskyi/trace-mcp/issues) and we will fix it.

## FAQ

**What is the core difference between codegraph and trace-mcp?**
codegraph defines eight tools and advertises one, on the reasoning that the rest are narrower slices of `explore` and that presence itself steers mis-picks. trace-mcp advertises 28 and keeps ~140 more one `load_tools` call away, because refactoring, security scanning and memory are not slices of navigation.

**Does codegraph have a smaller tool surface than trace-mcp?**
Yes, substantially — one advertised tool against 28 and ~11.6K tokens. That is a cost we pay every session and they do not. What it buys is a much wider set of operations addressable without an env-var opt-in and a restart.

**Can codegraph do refactoring or security scanning?**
No. Reading its source on August 29, 2026 found no rename/move/codemod, no taint analysis, no control-flow graph, no SARIF and no cross-session memory. It is a navigation and discovery tool, and scopes itself that way.

**Whose token-savings numbers are better supported?**
Both publish one. Theirs: seven repositories, model and queries named, four runs per arm, a documented fix to a flaw in their own earlier harness — 62% fewer tokens, 44% lower cost. Ours: the [PR review context benchmark](/pr-context-benchmark.html), a median {{ site.data.pr_context_bench.median_savings_pct }}% input-token reduction across {{ site.data.pr_context_bench.pr_count }} merged pull requests in {{ site.data.pr_context_bench.repo_count }} repositories we do not maintain, with base and head SHAs, the five cases where it lost, and one command to re-run it. Theirs covers more of a session; ours is the more auditable. Both run on code their authors do not maintain, and neither is third-party reproduced.

**Do either of them send code to a cloud service?**
No. Both index locally into SQLite, need no API key, and survive restarts.

## Next steps

- Full field: [how trace-mcp compares](/comparisons.html) against 20+ code-graph and memory MCP servers.
- The other head-to-heads: [vs Repomix](/vs/repomix.html) · [vs Serena](/vs/serena.html) · [vs codebase-memory-mcp](/vs/codebase-memory-mcp.html) · [vs Context Mode](/vs/context-mode.html)
- Comparing codegraph against something other than us: [Repomix vs codegraph](/vs/repomix-vs-codegraph.html) — indexing against packing, on their own terms.
- [Architecture](/architecture.html) — how the indexing pipeline, storage and LSP enrichment fit together.
- [Get started](/#install) — no configuration required.

---

# trace-mcp vs Context Mode

Source: https://trace-mcp.com/vs/context-mode.html



**TL;DR.** These two tools are not really rivals, and a page that pretended otherwise would be wrong in a way you would catch in ten minutes.

Context Mode attacks the cost of what tool calls *return*. A Playwright snapshot, twenty GitHub issues, an nginx access log — its answer is that the agent should write a script, run it in a subprocess, and put only the printed result into the conversation. Everything long gets indexed into SQLite FTS5 and retrieved by BM25 instead of pasted.

trace-mcp attacks the cost of *asking about code*. It parses your repository into a symbol graph so "what calls this", "what breaks if I change this", "where is this route handled" are single cheap lookups rather than a sequence of reads that need compressing afterwards.

If your agent is drowning in tool output, Context Mode is aimed at your problem and trace-mcp is not. If your agent is drowning in `Read` calls trying to understand a codebase, it is the other way round. Both connect to the same client at the same time.

## Head-to-head

| Capability | trace-mcp | Context Mode |
|---|:---:|:---:|
| **GitHub stars** | {{ site.data.competitors.trace_mcp.stars }} | {{ site.data.competitors.context_mode.stars }} |
| Licence | MIT | Elastic License 2.0 (source-available, not OSI) |
| Written in | TypeScript | TypeScript |
| MCP tools defined | {{ site.data.counts.tools }} | 11 |
| MCP tools advertised (default) | 28 (~11.6K tok) | 11 — all of them |
| Parses source code (AST) | ✓ tree-sitter, {{ site.data.counts.languages }} languages | ✗ no parser in its dependency tree |
| Symbol index | ✓ | ✗ |
| Cross-file dependency graph | ✓ directed edge graph | ✗ |
| Call graph | ✓ bidirectional | ✗ |
| Impact analysis | ✓ reverse traversal + decorator filter | ✗ |
| Framework-aware edges | ✓ {{ site.data.counts.frameworks }} integrations | ✗ |
| Refactoring tools | ✓ rename, move, signature, codemod, dead code | ✗ |
| Security scanning | ✓ OWASP Top-10, type-aware taint | ✗ |
| Runs agent-written code | ✗ by design | ✓ 12 runtimes, subprocess |
| Compresses other tools' output | ✗ | ✓ core premise |
| Full-text search over arbitrary content | ✓ FTS5 over the indexed repo | ✓ FTS5 + BM25 over docs, logs, fetched pages |
| Fetches and indexes web pages | ✗ | ✓ `ctx_fetch_and_index` |
| Survives context compaction | ✓ decision memory, code-linked | ✓ session snapshot replayed after compaction |
| Cross-session memory | ✓ decisions bound to symbol IDs | ✓ session events, deleted unless `--continue` |
| Client coverage | any MCP client | 17 hosts, but session continuity varies by host |
| Runs fully local, no API key | ✓ | ✓ |
| Published measurement | per-repo `get_real_savings` | ✓ 21 fixtures, harness in repo |

Verified on August 30, 2026 against Context Mode's source at commit `8a35367`, matching `package.json` version `1.0.169`. Tool, parser and storage claims come from the source; host-coverage and self-stated limits come from its README, quoted below.

## What Context Mode actually is, from its source

Four things are worth stating precisely, because the marketing word "context" covers both products and hides how differently they work.

**Eleven tools, all advertised.** `ctx_execute`, `ctx_execute_file`, `ctx_index`, `ctx_search`, `ctx_fetch_and_index`, `ctx_batch_execute`, `ctx_stats`, `ctx_doctor`, `ctx_upgrade`, `ctx_purge`, `ctx_insight`. There is no preset system and no deferred loading; the one mode that hides them is the embedded-plugin path, where the host registers the same eleven natively instead.

**"12 languages" is not the same axis as our language count.** Context Mode's list — JavaScript, TypeScript, Python, shell, Ruby, Go, Rust, PHP, Perl, R, Elixir, C# — is the set of runtimes it can *shell out to* in order to run a script you wrote. trace-mcp's {{ site.data.counts.languages }} is the set of grammars it can *parse* to build a graph. Reading the two numbers as comparable is the single easiest mistake to make about these tools, and it is worth not making it in either direction.

**The "sandbox" is hardening, not isolation.** A scratch temp directory, a timeout, and a large denylist of environment variables that enable injection (`LD_PRELOAD`, `NODE_OPTIONS`, `PYTHONSTARTUP`, `RUBYOPT`, `BASH_ENV` and roughly seventy in all, each annotated with its rationale). No container, no seccomp, no VM. The project says this itself rather than leaving you to find it: its README states that the execution tools "run arbitrary code and still inherit the process's filesystem access, so the boundary guard is a defense-in-depth layer for the *file-read* tool, not a full OS sandbox — treat approving any execution tool as approving arbitrary code, and keep host-level sandboxing enabled." Its MCP annotations agree: `ctx_execute` is marked `destructiveHint: true` and `openWorldHint: true`.

**The benchmark is real, narrow, and honestly bounded.** `BENCHMARK.md` reports 21 scenarios over fixtures captured from actual tool output — Context7 docs, Playwright page snapshots, GitHub issue lists, vitest and tsc output, an nginx log, a 500-row analytics CSV — with the fixtures and the harness committed. The headline "315 KB becomes 5.4 KB, 98% reduction" is the subtotal of the 14 `ctx_execute_file` scenarios (its own table says 5.5 KB). The same file reports the FTS5 retrieval path at 44-93%, and labels the overall figure 96%. It measures output bytes, not task success — but it names its fixtures and ships them, which is more than most claims in this field do.

## When to pick Context Mode

- **Your context is being eaten by tool output, not by code reading.** Browser automation, log analysis, big API responses, CI output. That is exactly the problem it was built for, and trace-mcp does nothing about it.
- **You want the agent to compute rather than read.** "Write a script that counts, print the count" is a genuinely good instinct, and Context Mode makes it a first-class routed path instead of a habit you have to nag the model into.
- **You need continuity across compaction on a supported host.** Its session snapshot is rebuilt and re-injected after the conversation compacts. Check its host table first, because coverage is uneven: it calls session support *full* on Claude Code, OpenCode and KiloCode; *high* on Gemini CLI, VS Code and JetBrains Copilot, GitHub Copilot CLI, OpenClaw, Pi and OMP (tool events captured, user decisions mostly not); *partial* on Cursor, Codex CLI, Antigravity CLI and Kiro; and Antigravity IDE and Zed have no hook support and get no session tracking at all.
- **You are not shipping it as a service.** For ordinary internal use, ELv2 is unlikely to bother you.

## When to pick trace-mcp

- **The expensive question is structural.** "What breaks if I change this signature", "who calls this", "which template does this controller render". Those are graph queries. Context Mode has no graph to query — it would help your agent write a script that greps, and a grep is not a resolved edge.
- **You want the work that comes after understanding.** Rename across a repo, move a symbol with its imports, an AST codemod, taint analysis, SARIF for CI, verified dead-code removal. None of that exists in Context Mode's eleven tools.
- **Your memory needs to be about code, not about the session.** trace-mcp's decisions bind to symbol IDs and are checked for staleness before recall, so a decision about a function that has since changed does not get replayed as if it still held. Context Mode's session store is an event journal about the conversation, and it is wiped on a fresh start unless you pass `--continue`.
- **Licence class matters where you work.** MIT against ELv2 is a policy question at many companies, and it gets decided before anyone reads a feature table.

## Where we are not being smug

**Their advertised surface is smaller than ours and always fully loaded.** Eleven tools against our 28 at roughly 11.6K tokens at session start. If you run both servers, you pay both — that is a real cost of the "run them together" recommendation on this page, and you should weigh it rather than take the recommendation on faith.

**Their measurement covers the session; ours covers one task in it.** We now have a benchmark with named fixtures — the [PR review context benchmark](/pr-context-benchmark.html), a median {{ site.data.pr_context_bench.median_savings_pct }}% input-token reduction over {{ site.data.pr_context_bench.pr_count }} merged pull requests in {{ site.data.pr_context_bench.repo_count }} open-source repositories nobody here maintains, re-runnable from the repo. It measures assembling code-review context, not a whole working session, so it does not answer the question Context Mode's numbers answer. For general use we quote {{ site.data.response_tokens.reduction_pct }}%, measured on {{ site.data.response_tokens.calls_weighted }} real tool calls from one machine against an estimated baseline — still our own instrumentation, not a benchmark, and it replaced an earlier aggregate we [disproved ourselves](/reduce-claude-code-token-usage.html#the-number-moved-on-5-september-2026-and-here-is-why).

**They are 200 times our size, and it is not only marketing.** A tool that reaches 20K stars in six months has found something people wanted. The "think in code" framing is a genuinely good idea that we do not have an equivalent of.

**Our security scanning has a ceiling, and it is stated on the [comparisons page](/comparisons.html) rather than only here.** The control-flow graph is line-based, not AST-based, and taint analysis is lexical/regex, not a real dataflow engine. Type-aware pruning cuts false positives; it does not turn this into a dataflow analyser. A full AST/dataflow rewrite is out of scope for now.

If you maintain Context Mode and something here is wrong, [open an issue](https://github.com/nikolai-vysotskyi/trace-mcp/issues) and we will fix it.

## FAQ

**Is Context Mode a competitor to trace-mcp?**
Mostly no. It compresses what tools return; we make questions about code cheap to ask. Different halves of the same bill, and they compose in one client.

**Does Context Mode build a code graph or symbol index?**
No. At commit `8a35367` its eight runtime dependencies contain no tree-sitter and no other parser, and none of its eleven tools resolves a symbol, an import edge or a call edge.

**What does the 98% reduction number actually measure?**
Raw output bytes against summary bytes, over 14 committed fixtures, on the `ctx_execute_file` path only. Its own file reports 44-93% for FTS5 retrieval and 96% overall. It is not a task-success benchmark, and it does not claim to be.

**Is Context Mode open source?**
Source-available under the Elastic License 2.0, which GitHub reports as NOASSERTION. ELv2 forbids offering the software as a managed service. trace-mcp is MIT.

**Is its sandbox an OS-level sandbox?**
No — a scratch directory, a timeout and an environment denylist. Its README says outright that approving an execution tool means approving arbitrary code and that host-level sandboxing should stay on.

**Can I run both?**
Yes, and for a codebase-heavy agent that also drives browsers or CI, that is probably the right setup. Budget for both tool surfaces at session start.

## Next steps

- Full field: [how trace-mcp compares](/comparisons.html) against 20+ code-graph and memory MCP servers.
- The other head-to-heads: [vs Repomix](/vs/repomix.html) · [vs Serena](/vs/serena.html) · [vs codebase-memory-mcp](/vs/codebase-memory-mcp.html) · [vs codegraph](/vs/codegraph.html)
- [Cut Claude Code token usage](/reduce-claude-code-token-usage.html) — seven tactics, ordered by measured impact.
- [Get started](/#install) — no configuration required.

---

# Repomix vs codegraph

Source: https://trace-mcp.com/vs/repomix-vs-codegraph.html



**TL;DR.** Repomix and codegraph are both answers to "my agent does not know my codebase", and they are opposite answers. Repomix **packs**: it concatenates the repository into one consolidated file the model reads. codegraph **indexes**: it parses the repository into a symbol and call graph in SQLite the agent queries. Repomix hands over content. codegraph answers questions about structure.

Pick Repomix when the repository is small enough to hand over whole, or when it is somebody else's and you want to look at it once. Pick codegraph when the agent will spend many turns in a repository too large to hand over at all.

Everything below was verified against both projects' public README and docs on **September 2, 2026**. We build [trace-mcp](/), which is in the same lane as codegraph; the last section says plainly where we differ and where each of these beats us.

## Head-to-head

| | Repomix | codegraph |
|---|:---:|:---:|
| **GitHub stars** | {{ site.data.competitors.repomix.stars }} | {{ site.data.competitors.codegraph.stars }} |
| License | MIT | MIT |
| Model | one-shot pack | live index (SQLite) |
| What the agent receives | file content | resolved symbols, call paths, blast radius |
| Parses code | ✓ tree-sitter, `--compress` only (experimental) | ✓ tree-sitter, always |
| Languages | not enumerated for `--compress`; 19 for comment stripping | 34 |
| Cross-file dependency edges | ✗ | ✓ |
| Call graph | ✗ | ✓ including dynamic-dispatch hops |
| Impact analysis | ✗ | ✓ blast radius inline in `explore` |
| Framework-aware route edges | ✗ | ✓ 15 route-shape families |
| Navigation (`navigates`) edges | ✗ | ✓ 7 routers (Next.js, Expo, React Router, TanStack, Vue/Nuxt, SvelteKit) |
| Cross-language bridging | ✗ | ✓ Swift ↔ Obj-C, React Native bridge, Expo, Fabric |
| Search | ✓ regex over the pack (`grep_repomix_output`) | ✓ graph query |
| Remote repositories | ✓ `--remote owner/name` | ✗ index a local checkout |
| Freshness | `--watch`, 300 ms debounce, full re-pack, local dirs only | auto-sync per file + staleness banner on pending files |
| MCP server | ✓ `--mcp`, 5 tools (+2 in `--sandbox`) | ✓ **1** advertised tool, 7 more behind an env allowlist |
| Refactoring / write path | ✗ | ✗ |
| Security scanning | ✓ Secretlint (secrets only) | ✗ |
| Published A/B benchmark | ✗ token counting, no A/B | ✓ 7 repos, model/queries/run count disclosed |
| Runs local, no API key | ✓ | ✓ |

Sources: Repomix's README and CLI reference; codegraph's README at the `main` head, and its source at commit `b9ca4b79` for the tool-surface row. Star counts read from the GitHub API the same day — they move fast in this space, so treat them as a snapshot rather than a ranking.

## When to pick Repomix

- **The repository is not yours.** `repomix --remote owner/name` gives you a third-party codebase in a prompt in seconds, with `--remote-branch` for a tag or a commit. codegraph indexes a local checkout; there is no remote mode.
- **The whole thing fits in context.** If the model can hold every file, a graph buys you nothing — it already has the answer to every structural question, in full.
- **You want no MCP client in the loop.** A pack is a file. Paste it into a web chat, attach it to a ticket, hand it to a colleague. codegraph is only useful through a tool call.
- **You need the pack itself as an artifact.** `--token-count-tree` shows where the tokens are, and `--token-budget` fails a CI job when the packed output exceeds a threshold. That is a build-pipeline primitive; a graph is not.
- **Secrets scanning on the way out.** Repomix runs Secretlint over what it packs, so a key in a `.env` does not silently land in a prompt.

## When to pick codegraph

- **The repository does not fit, and re-packing is the cost you are trying to avoid.** codegraph's own benchmark is exactly this shape: on questions where a file-reading agent needed 28–43 tool calls and up to 19 file reads, the agent with the graph answered from one to four `codegraph_explore` calls and read zero files.
- **The question is structural.** "What breaks if I change this", "who calls this", "how does the request reach this handler". A pack contains the bytes that would answer it, but nothing that computes the answer — and `grep_repomix_output` finds the string, not the edge.
- **Your stack has boundaries a parser normally stops at.** Route to handler across 15 framework families, `navigates` edges across 7 routers, and Swift ↔ Objective-C and React Native bridge hops.
- **You care about the advertised tool surface.** codegraph defines eight MCP tools and lists **one**. Its stated reasoning is that the other seven are narrower slices of `explore` and that presence itself steers agents into mis-picking. The whole surface costs roughly 1.9K tokens. Repomix's MCP server lists five.
- **Staleness has to be visible, not assumed.** During the debounce window, codegraph prepends a banner naming pending files and tells the agent to read them directly. A pack has no equivalent: it is silently stale from the first edit after it was written.

## Where each of them is honestly weak

**Repomix cannot compute anything.** Its `--compress` mode is tree-sitter, and it is genuinely good at what it does — signatures kept, bodies dropped, roughly 70% of the bytes gone — but it is lossy summarisation *per file*. There is no cross-file edge in a pack, at any compression level, and no amount of grep over one recovers a call graph.

**codegraph leaves more context resident, and says so.** Its README reports that across the same seven repositories, its responses leave about **80% more retrieval context** in the window at the end of a multi-turn session than a file-reading agent's do — 67K tokens against 18K on VS Code. Fewer tokens *processed* and a larger persistent *footprint* are both true at once. Publishing that is a point in its favour, and it is a real cost in a small context window.

**Neither one writes code.** No rename across files, no move, no signature change, no codemod. Both are read paths.

**Neither benchmark is third-party.** codegraph's is self-run — well documented, with the model, the queries, four runs per arm and a disclosed correction to its own earlier harness, but self-run. Repomix publishes token counts, not an A/B at all.

## Where trace-mcp fits

We are in codegraph's lane, not Repomix's, and it would be dishonest to pretend the comparison flatters us on every axis.

[trace-mcp](/) is a precomputed graph like codegraph's, with two differences that matter and one that costs us. It ships {{ site.data.counts.frameworks }} framework integrations that model edges beyond routing — controller → template, model → table, component → component — across {{ site.data.counts.languages }} languages. And it has a write path: rename, move, signature change, AST codemod, dead-code removal, plus OWASP taint scanning and SARIF for CI, none of which either tool above has.

What it costs: our default preset advertises 28 tools at roughly 11.6K tokens, against codegraph's one tool at ~1.9K. That gap is the honest reason to pick codegraph if orientation is the whole job. It is real money paid every session, and what it buys is the write path above plus everything outside the preset one `load_tools` call away.

On measurement we can offer one thing neither of them does: the [PR review context benchmark](/pr-context-benchmark.html) is a median {{ site.data.pr_context_bench.median_savings_pct }}% input-token reduction over {{ site.data.pr_context_bench.pr_count }} merged pull requests in {{ site.data.pr_context_bench.repo_count }} open-source repositories nobody here maintains, with the base and head SHAs, the losing cases, and the command that re-runs it all shipped in the repository.

Head-to-head, one at a time: [trace-mcp vs Repomix](/vs/repomix.html) · [trace-mcp vs codegraph](/vs/codegraph.html).

## FAQ

**What is the difference between Repomix and codegraph?**
Repomix packs a repository into one file a model reads. codegraph parses it into a symbol and call graph an agent queries. Content versus computed structure — that one difference explains almost every other row above.

**Is Repomix or codegraph cheaper in tokens?**
It depends on session length. A pack is a fixed up-front cost, re-paid on every repack; `--compress` cuts roughly 70% of it. codegraph advertises one tool at ~1.9K tokens and charges per query, and reports 62% fewer tokens and 44% lower cost than a file-reading agent across seven repositories. One question, small repo: Repomix. Long session, large repo: codegraph.

**Do they both stay up to date as I edit?**
Both watch, but refresh different things. Repomix's `--watch` re-packs the whole output after a 300 ms debounce and works on local directories only. codegraph syncs the graph per changed file and flags pending files in the response so the agent reads them directly.

**Can Repomix answer "who calls this function"?**
Not as a computed answer. `grep_repomix_output` runs a JavaScript regex over the packed text, so it finds the string — not the call. It cannot tell a call from a comment, follow a re-export, or resolve dynamic dispatch.

**Can I use both?**
Yes. Repomix for a remote repository you want to look at once; codegraph for the one you work in daily. No overlap, no API key on either side.

## Next steps

- Full field: [how trace-mcp compares](/comparisons.html) against 20+ code-graph and memory MCP servers, with the same sourcing discipline.
- The head-to-heads: [vs Repomix](/vs/repomix.html) · [vs codegraph](/vs/codegraph.html) · [vs Serena](/vs/serena.html) · [vs codebase-memory-mcp](/vs/codebase-memory-mcp.html) · [vs Context Mode](/vs/context-mode.html)
- [Cut Claude Code token usage](/reduce-claude-code-token-usage.html) — the measured tactics, including the ones that have nothing to do with any of these tools.

---

# How to reduce Claude Code token usage

Source: https://trace-mcp.com/reduce-claude-code-token-usage.html



Token cost in Claude Code is not mostly the code you show it. It is the code it reads **looking for** the code it needs, plus everything it re-reads on later turns because the earlier result scrolled out of reach.

Below are seven tactics ordered by how much they moved the number in our own measurements, with the numbers we actually have and honest gaps where we do not have any. Several of them have nothing to do with trace-mcp; those come first, because they are free.

## 1. Stop paying for exploration twice

The dominant cost on a repository too large to fit in context is *search*, not reading. An agent asked "where is rate limiting handled" will open a dozen candidate files before it finds the one that matters, and every one of those reads stays in the transcript for the rest of the session.

Two cheap habits fix most of it:

- **Name the file when you know it.** "Fix the retry backoff in `src/http/client.ts`" costs one read. "Fix the retry backoff" costs an exploration.
- **Ask for structure before content.** Signatures and line ranges are a fraction of a file's tokens, and they are usually enough to pick the one symbol worth reading in full.

## 2. Read symbols, not files

Reading a 500-line file to change five lines pays for 495 lines you did not need — and pays again on the next turn if the result gets re-read. The pattern that works is outline → one symbol → edit.

This is what trace-mcp's `get_outline` and `get_symbol` exist for: the first returns signatures with line numbers, the second returns exactly one function or class. Claude Code's native `Read` supports `offset`/`limit` and will do the same job once you know the range, which is precisely what the outline gives you.

## 3. Audit your MCP tool surface — including ours

This is the tactic most people never check, and it can dominate everything else.

Every MCP server you connect injects its tool schemas into the context **at session start, before you ask anything**. Three servers with large surfaces can cost tens of thousands of tokens on every single session, whether or not you call any of them.

Measured on our own server, August 29, 2026: trace-mcp's shipped default is the `minimal` (28 tools) preset — ~9.8K tokens of `tools/list` plus ~1.75K tokens of server instructions, **~11.6K in total**, which is the number to budget against because a client pays both. (`full` ({{ site.data.counts.tools }} tools) is ~49.9K + ~2.1K if you opt into it.) That is still not cheap, and we say so on our own [comparisons page](/comparisons.html) — the leanest peers in this category advertise ~1.9K and ~7K tokens by shipping a small default surface with the rest opt-in.

What to do about it:

- Run `tools/list` against each server you have connected and count the tokens. Most people have never looked.
- Disconnect servers you are not using in this project. A server connected "just in case" is a fixed tax.
- Use a preset or allowlist where the server offers one. trace-mcp ships `minimal` (28 tools), `standard` (60 tools) and `full` ({{ site.data.counts.tools }} tools), plus `tools.include` / `tools.exclude` in config.
- **Previously noted here as broken, now fixed:** presets used to take effect only when the daemon was bypassed (`TRACE_MCP_NO_DAEMON=1`) and were silently ignored on the default daemon-backed path. That bug is shipped and closed — the preset is honoured on both paths, and `TRACE_MCP_NO_DAEMON=1` is no longer needed as a workaround. Measured on the default path: `standard` serves ~18.8K tokens of `tools/list` plus ~1.75K of server instructions (~20.5K), against ~49.9K + ~2.1K for `full`.
- **One caveat that is still live:** set these in the global `~/.trace/.config.json`. `tools.preset` is honoured from a project-local `.trace/.config.json` too, but `tools.description_verbosity` / `tools.instructions_verbosity` are not — set those globally until that is fixed.

## 4. Pick the output format per tool, not globally

Encoding matters, but not uniformly. Our measurements (`scripts/bench-toon.ts`, `gpt-tokenizer` with the cl100k_base encoding, against a snapshot of this repo's own index — 1,501 files, 9,467 symbols):

| Payload shape | Format change | Measured |
|---|---|---:|
| Flat, same scalar fields per row (`query_decisions`) | JSON → TOON | **+31.4%** |
| Flat symbol records (`get_outline`) | JSON → TOON | **+28.8%** |
| Flat item records (`search`) | JSON → TOON | **+16.4%** |
| Nested object per row (`find_usages`) | JSON → TOON | **−17.5%** |
| Inner array per row (`search_text`) | JSON → TOON | **−25.5%** |
| Repeated long paths (`search_text`) | flat → grouped by file | **+20.8%** |

The rule underneath: compact tabular encodings win when every row has the same scalar columns, and lose the moment a row contains a nested object or an inner array. Full method and the breakeven curve are on the [TOON savings page](/toon-savings.html).

Every number in this section is measured by trace-mcp on trace-mcp. The one measurement taken on code we do not own is the [PR review context benchmark](/pr-context-benchmark.html) — {{ site.data.pr_context_bench.pr_count }} merged pull requests across {{ site.data.pr_context_bench.repo_count }} open-source repositories — and to see what any of this is worth on your own sessions rather than on ours, [session analytics](/analytics.html) reports the same figures from your local agent logs.

## 5. Prefer one structural query over many reads

"What breaks if I change this function" answered by reading files is an open-ended crawl: find the definition, grep for the name, open each hit, follow each of those. Answered from a dependency graph it is one call that returns the affected symbols and the tests covering them.

The same applies to "who calls this", "which tests cover this", and "what does this module import". These are graph traversals. If your tooling can compute them, the token cost is the answer's size rather than the search's size — and the search is the expensive part.

## 6. Compact, don't clear

Clearing the context feels like saving tokens and usually is not: the agent re-derives what it lost, and re-derivation costs more than the transcript did. Compaction — or simply writing a short summary of conclusions and starting fresh from it — keeps the findings while dropping the raw tool output that produced them.

## 7. Index once, query many times

The reason a code index pays off is amortisation. Building it costs something once; every query afterwards is cheap and scoped. A packing tool that concatenates your repository into a prompt pays its full cost on **every** refresh, which is fine for a one-shot question and expensive across a long session.

That is the trade in one line: if your session is a single question about a small repository, pack it. If it is many turns against a repository too large to fit in context, index it. We wrote up the specifics against the main packing tool in [trace-mcp vs Repomix](/vs/repomix.html).

## What we claim, and what we have measured

### The number moved on 5 September 2026, and here is why

Until that date this site and the README claimed **~40–50% fewer tokens on average**. That figure was not a measurement. It descended from a counter in `src/savings.ts` that scored every tool call *before the tool ran*: `RAW_COST_ESTIMATES[tool] × 0.15`, a constant. Thousands of calls, zero variance — 5,123 `search_text` calls each booked exactly 2,550 tokens saved. We found it ourselves, in [TRA-880](https://github.com/nikolai-vysotskyi/trace-mcp/pull/915), and the counter now measures the real response instead.

The figure we publish in its place is **{{ site.data.response_tokens.reduction_pct }}%**, over {{ site.data.response_tokens.calls_weighted }} recorded calls of {{ site.data.response_tokens.tools_with_baseline }} tools — 97.2% of everything the store has recorded — with each tool's response counted in `o200k_base` tokens on the wire. Read the caveats before quoting it:

Measured at trace-mcp **{{ site.data.response_tokens.measured_build.version }} (`{{ site.data.response_tokens.measured_build.commit }}`)** on {{ site.data.response_tokens.measured_at | date: "%-d %B %Y" }}{% if site.data.measurements.response_tokens.historical %} — a result from that build, not a claim about the current one{% endif %}. Its [preregistration](/perf/prereg-response-tokens/) publishes it as a **miss** — and, since the tail was measured (TRA-945), on the headline half: the coverage bar is now cleared, the 25% reduction bar is not. The bar was declared unadjustable before the data and has not been moved.

- **The measured half is the response. The baseline half is still an estimate.** "What a `Read`/`Grep` would have cost instead" is hand-written in `RAW_COST_ESTIMATES` and has never been validated. Until it is, this is a measured numerator over an estimated denominator.
- **One machine's usage mix**, not the field — the call weighting comes from a single maintainer store (`benchmarks/response-tokens/call-volume.json`, with provenance).
- **{{ site.data.response_tokens.tools_costing_more }} of the {{ site.data.response_tokens.tools_with_baseline }} tools with a baseline return *more* tokens than it credits them** — `list_projects` at 10.5x is the worst, then `get_dead_code`, `get_call_graph`, `get_complexity_report`, `check_claudemd_drift`, `get_feature_context`, `search`, `get_outline`, `find_usages`, `get_changed_symbols`. The old counter booked a positive number for them anyway. The per-tool table is published in full: [tool response token cost](/perf/response-tokens/).
- **Two more replace no file read at all** (`register_edit`, `reindex`), so they are credited nothing and counted as overhead: {{ site.data.response_tokens.overhead_calls }} calls, {{ site.data.response_tokens.overhead_tokens }} tokens. With them on the spend side the all-in figure is **{{ site.data.response_tokens.reduction_pct_incl_overhead }}%**, which is the one to plan a session budget against.
- It still varies enormously with repository size and session shape; on a small repo that fits in context it is roughly zero.

The one figure here that is neither ours nor an estimate is the [PR review context benchmark](/pr-context-benchmark.html) — {{ site.data.pr_context_bench.median_savings_pct }}% median over {{ site.data.pr_context_bench.pr_count }} merged pull requests in {{ site.data.pr_context_bench.repo_count }} repositories we do not maintain, SHAs pinned and losing cases published. The numbers on this page that come with a script and a tokenizer are the ones in section 4; those you can reproduce.

We still do not have a published, independently reproducible *end-to-end session* benchmark, and at least one competitor does. We would rather write that here than quietly imply otherwise.

## FAQ

**What actually uses the most tokens in Claude Code?**
Repeated full-file reads during exploration, the MCP tool schemas advertised at session start, and long transcripts carrying every earlier tool result forward — in that order on large repositories.

**Do MCP servers increase or decrease token usage?**
Both. Each pays a fixed up-front schema cost and then saves per query if its answers are narrower than the reads they replace. A large surface with few calls per session is a net loss. Measure it with `tools/list`.

**Does asking for an outline instead of reading the file really help?**
Yes, when you only need structure. Outline first, then read the specific symbol or line range — instead of reading 500 lines to edit five.

**Does output format affect token cost?**
Measurably, per payload shape. TOON beat JSON by 31.4% on `query_decisions` and 28.8% on `get_outline`, and lost by 17.5% and 25.5% on `find_usages` and `search_text`. It is a per-tool decision.

**Is clearing context the same as saving tokens?**
No — clearing forces re-derivation, which usually costs more. Compact, or hand off a short written summary.

## Next steps

- [Tools reference](/tools-reference.html) — every trace-mcp tool, including the outline/symbol/impact ones above.
- [TOON savings](/toon-savings.html) — the full measurement method behind section 4.
- [Configuration](/configuration.html) — presets and `tools.include` / `tools.exclude`.
- [Get started](/#install) — no configuration required.

---

# Configuration

Source: https://trace-mcp.com/configuration.html


Configuration is optional — trace-mcp works out of the box for standard projects.

This page is the reference for the file itself: where it lives, how the layers
merge, and what the keys you reach for actually do. Several sections of it are
big enough to have their own page — the [quality gates](quality-gates.md)
thresholds, the [MCP tracing](telemetry.md) span exporter, the memory knobs that
bound the [daemon](daemon-memory.md), and the [tweakcc](tweakcc.md) enforcement
tier that `trace-mcp init` writes here on your behalf.

For the exhaustive list — every key the schema accepts, with its type, allowed
values and real default, generated from the schema itself — see the
[config index](config-index.md).

---

## How config works

All trace state lives in `~/.trace/` (with automatic backwards compatibility and fallback from legacy `~/.trace-mcp/`):

```
~/.trace/
  .config.json              # global config + per-project sections
  registry.json             # registered projects
  index/
    my-app-a1b2c3d4e5f6.db  # per-project databases
```

### Config merge order

1. **Global defaults** — `~/.trace/.config.json` (or `~/.trace-mcp/.config.json` fallback)
2. **Per-project section** — `~/.trace/.config.json → projects["/path/to/project"]` (created by `trace add`)
3. **Local override** — `.trace.json` (or legacy `.trace-mcp.json`) in the project directory
4. **Zod schema defaults** — fallback values

### Global config example

`~/.trace/.config.json`:
```jsonc
{
  // Global defaults (apply to all projects)
  "ai": {
    "enabled": true
    // provider defaults to "onnx" — local embeddings, no API keys
  },
  "security": {
    "max_file_size_bytes": 524288
  },

  // Per-project settings (created by `trace add`)
  "projects": {
    "/Users/me/projects/my-app": {
      "root": ".",
      "include": ["app/**/*.php", "routes/**/*.php", "src/**/*.{ts,vue}"],
      "exclude": ["vendor/**", "node_modules/**"]
    },
    "/Users/me/projects/api": {
      "root": ".",
      "include": ["src/**/*.ts"],
      "exclude": ["node_modules/**", "dist/**"]
    }
  }
}
```

### Per-project config file (optional)

You can place a config file at `.trace/.config.json` in your project root to override settings without editing the global config:

```jsonc
// /path/to/project/.trace/.config.json
{
  "include": ["src/**/*.ts", "lib/**/*.ts"],
  "exclude": ["node_modules/**", "dist/**", "coverage/**"],
  "ignore": {
    "directories": ["generated", "proto"],
    "patterns": ["**/fixtures/**", "**/*.generated.ts"],
    // Respect the project's root .gitignore when walking (default: true).
    // Set false to index git-ignored trees too — vendored and generated code
    // then competes with your own in every search result.
    "gitignore": true
  }
}
```

Alternative locations (checked in order): `.trace/.config.json`, `.trace.json`, `.trace-mcp/.config.json`, `.trace-mcp.json`, `.trace-mcp`, `.config/trace.json`, `.config/trace-mcp.json`, `package.json` (under `"trace-mcp"` key). The `trace-mcp`-spelled names keep working permanently — nothing is removed.

---

## Migration from trace-mcp to trace

**The project is `trace-mcp`; the command is `trace`.** The rename lives at exactly that boundary — the CLI verb, the MCP server key written into client configs, and the local state directory shorten to `trace`, and nothing else does: not the npm package, not this repo, not the domain, not the registry entry. The reason is ergonomics, not efficiency — the same shape as `rg` for ripgrep or `kubectl` for kubernetes. The measured token saving from the shorter tool prefix (`mcp__trace__<tool>` vs. `mcp__trace-mcp__<tool>`) is real but small: 66–366 tokens per turn depending on tokenizer and preset, 0.74–1.23% of a tool list that already costs 8k–45k tokens.

**The npm package keeps its name.** It is `trace-mcp` and stays `trace-mcp`: `trace` on npm is an unrelated package by another author, so `npm install -g trace` would install someone else's code. Install with `npm install -g trace-mcp` or `npx -y trace-mcp@latest`.

| | Before | After |
|---|---|---|
| npm package | `trace-mcp` | `trace-mcp` (unchanged) |
| Command | `trace-mcp <cmd>` | `trace <cmd>` — `trace-mcp` kept as an alias |
| MCP server key in client configs | `trace-mcp` | `trace` |
| State directory | `~/.trace-mcp/` | `~/.trace/`, falling back to `~/.trace-mcp/` |
| Project config | `.trace-mcp.json` | `.trace.json`, then `.trace-mcp.json` |
| Plugin / registry ids | `trace-mcp` | `trace-mcp` (unchanged) |

`trace init` and `trace upgrade` rename an existing `mcpServers["trace-mcp"]` entry to `mcpServers["trace"]` in every client they can write to, preserving whatever else you configured on that entry. Nothing is deleted, and an entry you leave spelled `trace-mcp` keeps connecting — it just costs the longer tool prefix. Existing indexes are not rebuilt.

---

## .traceignore

Place a `.traceignore` file in your project root to exclude files and directories from indexing. It uses the same syntax as `.gitignore`:

```gitignore
# Skip generated code
generated/
**/generated/**

# Skip protobuf definitions
proto/

# Skip test fixtures
tests/fixtures/

# Skip specific file patterns
*.generated.ts
*.pb.go

# Negation — re-include something
!proto/important.proto
```

### Difference from .gitignore

| | `.gitignore` | `.traceignore` |
|---|---|---|
| **Effect** | Files are indexed for the dependency graph, but source content is hidden from AI output | Files are **completely skipped** — not indexed at all |
| **Use case** | Secrets, credentials, env files | Generated code, vendored deps, large data files |

### Built-in skip directories

These directories are always skipped (no configuration needed):

`node_modules`, `.git`, `dist`, `build`, `.next`, `__pycache__`, `.venv`, `vendor`, `.trace-mcp`, `coverage`, `.turbo`

You can add **more** directory names to skip via `.traceignore` or the `ignore.directories` config key — both only *add* to the skip list, they don't remove anything from it.

`trace-mcp add` / `trace-mcp index` print a "Skipped top-level folders" line after indexing listing every top-level directory that got skipped this way, so a folder missing from the index isn't a silent surprise.

### Getting a skipped folder indexed

Two different things can leave a folder out of the index — check which one applies:

1. **The folder name collides with a built-in skip dir** (e.g. you have your own `vendor/` or `build/` with real source in it). There's currently no per-project way to un-skip a built-in name — rename the folder, or [open an issue](https://github.com/nikolai-vysotskyi/trace-mcp/issues) if this collision is common enough to warrant a config override.
2. **The folder just isn't a built-in skip dir, but nothing in `include` matches its files.** The default `include` covers every extension a registered language plugin claims, *except* the pure data formats — JSON, XML (`.xml`, `.svg`, `.csproj`, ...) and INI (`.ini`, `.conf`, `.properties`, ...) — which are left out because lockfiles and fixtures would swamp the index. Add an explicit pattern for those:

   ```jsonc
   // .trace/.config.json
   {
     "include": ["schemas/**/*.json"]
   }
   ```

   `include` in a per-project config file **replaces** the built-in list rather than adding to it (config merge is shallow) — copy the defaults from [`src/config.ts`](https://github.com/nikolai-vysotskyi/trace-mcp/blob/master/src/config.ts) alongside your addition if you still want the rest of the project indexed.

---

## Options

| Option | Type | Default | Description |
|---|---|---|---|
| `root` | `string` | `"."` | Project root directory |
| `include` | `string[]` | Auto-detected | Glob patterns for files to index |
| `exclude` | `string[]` | Common exclusions | Glob patterns to skip |
| `follow_symlinks` | `boolean` | `false` | Follow directory symlinks during file discovery. Leave off unless you know the tree is free of symlink cycles — enabling it on a tree with a cycle (e.g. Ansible Molecule's `roles/<role>/molecule/<scenario>/roles/<role> -> ../../../` layout) can silently truncate traversal. Symlinked *files* are always skipped regardless of this setting. |
| `ignore.directories` | `string[]` | `[]` | Extra directory names to skip (added to built-in list) |
| `ignore.patterns` | `string[]` | `[]` | Extra gitignore-style patterns to exclude from indexing |
| `plugins` | `string[]` | `[]` | Paths to custom plugins — see [development](development.md#adding-a-new-integration-plugin) for the plugin interface |
| `security.secret_patterns` | `string[]` | Common patterns | Regex patterns for secret filtering |
| `security.max_file_size_bytes` | `number` | `524288` | Max file size to index (bytes) |

### Framework-specific options

```jsonc
{
  "frameworks": {
    "laravel": {
      "artisan": {
        "enabled": true,    // Enable artisan integration
        "timeout": 10000    // Command timeout in ms
      },
      "graceful_degradation": true  // Continue if artisan fails
    }
  }
}
```

---

## AI configuration

AI features enable semantic search (vector embeddings) and optional LLM-powered summarization. trace-mcp supports three embedding providers, with a **zero-config local option** as the default.

### Provider overview

| Provider | Embeddings | LLM (summarization) | Requires | Setup |
|---|---|---|---|---|
| **`onnx`** (default) | ✓ local, offline | ✗ | `@huggingface/transformers` (optional dep) | Zero-config — model auto-downloads (~23 MB) on first use |
| **`ollama`** | ✓ via Ollama | ✓ via Ollama | Running Ollama instance | Install Ollama + pull models |
| **`lmstudio`** | ✓ via LM Studio | ✓ via LM Studio | LM Studio server running | OpenAI-compatible, no API key |
| **`openai`** | ✓ | ✓ | API key | `api_key` or `OPENAI_API_KEY` env |
| **`anthropic`** | ✗ (no embeddings API) | ✓ | API key | `api_key` or `ANTHROPIC_API_KEY` env |
| **`gemini`** | ✓ | ✓ | API key | Google Generative Language API (consumer) — `api_key` (AIza…) or `GEMINI_API_KEY` env |
| **`vertex`** | ✓ | ✓ | OAuth token + GCP project | Google Vertex AI (GCP) — `api_key` = access token, plus `vertex_project` + `vertex_location` |
| **`voyage`** | ✓ (code-tuned) | ✗ | API key | Voyage AI embeddings only — pair with another provider for inference |
| **`mistral`** / **`groq`** / **`together`** / **`deepseek`** / **`xai`** | ✓ | ✓ | API key | OpenAI-compatible endpoints — per-provider `*_API_KEY` env |

### Minimal setup — local embeddings (no API keys)

```jsonc
{
  "ai": {
    "enabled": true
    // provider defaults to "onnx"
    // model defaults to Xenova/all-MiniLM-L6-v2 (384 dims, Apache 2.0)
    // auto-downloads ~23 MB on first embed_repo or semantic search
  }
}
```

This enables semantic/hybrid `search` and `query_by_intent` with zero configuration. No API keys, no external services, works fully offline after first model download.

### Full setup — Ollama (embeddings + LLM summarization)

```jsonc
{
  "ai": {
    "enabled": true,
    "provider": "ollama",
    "base_url": "http://localhost:11434",
    "inference_model": "gemma4:e4b",
    "fast_model": "gemma4:e4b",
    "embedding_model": "qwen3-embedding:0.6b",
    "embedding_dimensions": 1024,
    "summarize_on_index": true,
    "summarize_batch_size": 20,
    "summarize_kinds": ["class", "function", "method", "interface", "trait", "enum", "type"],
    "concurrency": 4
  }
}
```

> **Ollama embedding dimensions — match your model.** Ollama embedding models
> vary in output dimensionality (`nomic-embed-text` → 768, `qwen3-embedding:0.6b`
> → 1024, `mxbai-embed-large` → 1024, etc.). When `ai.embedding_dimensions` is
> **omitted**, trace-mcp auto-detects the real dimension by probing the model on
> first use, so you don't have to set it. When you **do** set it, the value MUST
> equal the model's real dimension — a wrong value makes every vector insert fail
> with a dimension mismatch, and `embed_repo` then returns `status: "error"`
> (`dimension_mismatch`) rather than a silent 0-coverage "completed". If you
> switch to a model with a different dimension, either update
> `embedding_dimensions` to match (or remove it) and re-run
> `embed_repo({ force: true })`.

### Full setup — OpenAI

```jsonc
{
  "ai": {
    "enabled": true,
    "provider": "openai",
    "api_key": "sk-...",
    "inference_model": "gpt-4o-mini",
    "embedding_model": "text-embedding-3-small",
    "embedding_dimensions": 1536,
    "summarize_on_index": true
  }
}
```

### Full setup — Google Gemini (consumer API)

Uses the Google Generative Language API (`generativelanguage.googleapis.com`) with a simple `AIza…` API key from [ai.google.dev](https://ai.google.dev). For GCP-governed workloads, use the `vertex` provider instead.

```jsonc
{
  "ai": {
    "enabled": true,
    "provider": "gemini",
    "api_key": "AIza...",
    "inference_model": "gemini-2.5-flash",
    "embedding_model": "text-embedding-004",
    "embedding_dimensions": 768
  }
}
```

### Full setup — Google Vertex AI (GCP)

Uses Vertex AI with a short-lived OAuth2 access token (~1h TTL). Generate via `gcloud auth print-access-token` — you're responsible for refreshing it.

```jsonc
{
  "ai": {
    "enabled": true,
    "provider": "vertex",
    "api_key": "ya29....",                // `gcloud auth print-access-token`
    "vertex_project": "my-gcp-project",
    "vertex_location": "us-central1",
    "inference_model": "gemini-2.5-flash",
    "embedding_model": "text-embedding-005",
    "embedding_dimensions": 768
  }
}
```

Environment variables: `GOOGLE_ACCESS_TOKEN`, `GOOGLE_CLOUD_PROJECT`, `GOOGLE_CLOUD_LOCATION` are honored when the config fields are unset.

### Full setup — Voyage AI (embeddings only)

Voyage specializes in retrieval-grade embeddings. `voyage-code-3` is tuned for source code and is the recommended default for this project. Voyage has no inference API — keep `features.inference` disabled, or layer Voyage embeddings on top of Anthropic/OpenAI/Ollama for summarization by switching providers per-capability in your own setup.

```jsonc
{
  "ai": {
    "enabled": true,
    "provider": "voyage",
    "api_key": "pa-...",                   // or VOYAGE_API_KEY env
    "embedding_model": "voyage-code-3",
    "embedding_dimensions": 1024,
    "features": { "embedding": true, "inference": false, "fast_inference": false }
  }
}
```

### All options

| Option | Default | Description |
|---|---|---|
| `ai.enabled` | `false` | Enable AI features |
| `ai.provider` | `"onnx"` | `onnx`, `ollama`, `lmstudio`, `openai`, `anthropic`, `gemini`, `vertex`, `voyage`, `mistral`, `groq`, `together`, `deepseek`, `xai` |
| `ai.base_url` | — | Custom API endpoint (providers that honor it) |
| `ai.api_key` | — | API key, or OAuth access token for `vertex`. Env fallbacks: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `GOOGLE_ACCESS_TOKEN`, `VOYAGE_API_KEY`, etc. |
| `ai.vertex_project` | — | Vertex only — GCP project ID (or `GOOGLE_CLOUD_PROJECT` env) |
| `ai.vertex_location` | `us-central1` | Vertex only — GCP region (or `GOOGLE_CLOUD_LOCATION` env) |
| `ai.inference_model` | — | LLM for explanations and reviews (ollama/openai only) |
| `ai.fast_model` | — | Faster LLM for lightweight tasks (ollama/openai only) |
| `ai.embedding_model` | auto per provider | `"Xenova/all-MiniLM-L6-v2"` (onnx), `"qwen3-embedding:0.6b"` (ollama), `"text-embedding-3-small"` (openai) |
| `ai.embedding_dimensions` | auto per provider | `384` (onnx), `768` (ollama), `1536` (openai) |
| `ai.summarize_on_index` | `false` | Auto-summarize symbols after indexing (requires ollama/openai with LLM model) |
| `ai.summarize_batch_size` | `20` | Symbols per summarization batch |
| `ai.summarize_kinds` | `["class", "function", ...]` | Symbol kinds to summarize |
| `ai.concurrency` | `1` | Max parallel requests to AI provider (1–32) |
| `ai.reranker_model` | — | Model for search result reranking (ollama/openai only) |

> **When the embedding endpoint is unreachable:** background embedding trips a
> circuit breaker after 2 consecutive failed batches and then pauses for 10
> minutes. The pause is stored in the project DB, so it survives a daemon
> restart instead of re-attempting the whole backlog on every start. While it is
> open, `get_index_health` reports `embedding: { queued, pausedUntil, lastError }`
> and adds a warning — semantic and hybrid search results are incomplete until
> the provider is reachable. `embed_repo` ignores the pause and retries
> immediately.

> **`ai.enabled` takes effect per project start.** A project already open in the
> daemon keeps the config it was started with, so flipping `ai.enabled` off stops
> embedding for that project only after it restarts.

> **ONNX provider details:** Uses `@huggingface/transformers` (installed as optional dependency). The default model `Xenova/all-MiniLM-L6-v2` is Apache 2.0 licensed, produces 384-dimensional L2-normalized mean-pooled vectors, and weighs ~23 MB. The model is cached locally after first download. You can use any ONNX-compatible model from HuggingFace by setting `embedding_model`.

> **Ollama parallelism:** When setting `concurrency` > 1, you must also configure Ollama to handle parallel requests. The desktop app UI does not expose this setting — use one of these methods:
>
> **Option 1 — Environment variable for the desktop app (macOS):**
> ```bash
> launchctl setenv OLLAMA_NUM_PARALLEL 4
> ```
> Then quit and reopen the Ollama app. The variable persists until logout.
>
> **Option 2 — Run from terminal instead of the desktop app:**
> ```bash
> OLLAMA_NUM_PARALLEL=4 ollama serve
> ```
>
> **Option 3 — Persist via shell profile** (add to `~/.zshrc`):
> ```bash
> export OLLAMA_NUM_PARALLEL=4
> ```
> Then `source ~/.zshrc` and restart Ollama.
>
> Set `OLLAMA_NUM_PARALLEL` to match your `ai.concurrency` value. Higher parallelism uses more VRAM/RAM — start with 2–4 and increase if your hardware allows.

---

## LSP enrichment

trace-mcp can optionally use Language Server Protocol (LSP) servers to enrich call graph edges with **compiler-grade type resolution**. This resolves dynamic dispatch, interface polymorphism, generics, and other cases that tree-sitter AST analysis alone cannot handle.

**Disabled by default** — opt-in via configuration. When enabled, LSP runs as a post-indexing enrichment pass (Pass 3) after the standard tree-sitter indexing completes. If an LSP server is not installed or fails to start, indexing continues normally without LSP edges.

```jsonc
{
  "lsp": {
    "enabled": true,              // default: false — must opt-in
    "auto_detect": true,          // default: true — auto-detect available LSP servers
    "max_concurrent_servers": 2,  // default: 2 — limit parallel LSP processes
    "enrichment_timeout_ms": 120000, // default: 120000 — overall enrichment timeout
    "batch_size": 100,            // default: 100 — symbols per batch
    "servers": {                  // optional: override auto-detected server commands
      "typescript": {
        "command": "npx",
        "args": ["typescript-language-server", "--stdio"],
        "timeout_ms": 30000
      }
    }
  }
}
```

| Option | Default | Description |
|---|---|---|
| `lsp.enabled` | `false` | Enable LSP enrichment pass |
| `lsp.auto_detect` | `true` | Auto-detect available LSP servers based on project files |
| `lsp.max_concurrent_servers` | `2` | Maximum number of LSP servers running simultaneously |
| `lsp.enrichment_timeout_ms` | `120000` | Overall timeout for the entire LSP enrichment pass |
| `lsp.batch_size` | `100` | Number of symbols to process per batch |
| `lsp.servers.<lang>.command` | — | Override the LSP server command for a language |
| `lsp.servers.<lang>.args` | `[]` | Arguments for the LSP server command |
| `lsp.servers.<lang>.timeout_ms` | `30000` | Per-request timeout for this server |
| `lsp.servers.<lang>.initializationOptions` | — | Custom LSP initialization options |

### Auto-detected servers

| Language | Server | Detection |
|---|---|---|
| TypeScript/JavaScript | `typescript-language-server` | `tsconfig.json` or `package.json` exists |
| Python | `pyright-langserver` | `pyproject.toml`, `requirements.txt`, or `setup.py` exists |
| Go | `gopls` | `go.mod` exists |
| Rust | `rust-analyzer` | `Cargo.toml` exists |

Servers are only started if the corresponding language has files in the index AND the server binary is available on PATH.

---

## Tool exposure & agent behavior

The `tools.*` section controls what the MCP server injects into every session — tool set, instruction verbosity, and optional agent behavior rules.

```jsonc
{
  "tools": {
    "preset": "standard",                // "full" | "standard" | "minimal" | "review" | "architecture" | "dev" | "security" | "design" | "perf" | "router"
    "description_verbosity": "full",     // "full" | "minimal" | "none"
    "instructions_verbosity": "full",    // "full" | "minimal" | "none" — controls the tool-routing block
    "client_profile": "auto",            // "auto" | "off" | "claude-code" | "codex" | "cursor" | "vscode" | "generic"
    "agent_behavior": "off",             // "strict" | "minimal" | "off" — see below
    "meta_fields": true,                 // true | false | ["_hints", "_budget_warning", ...]
    "compact_schemas": false             // strip advanced params from tool schemas (saves tokens)
  }
}
```

| Option | Default | Description |
|---|---|---|
| `tools.preset` | `"minimal"` | Tool preset — the number is the upper bound on the tool surface; framework-gated tools only appear when the framework is detected. `minimal` (28 tools, default), `standard` (60 tools — covers >99% of real-world tool calls per session-log mining), `review` (32 tools), `architecture` (41 tools), `dev` (42 tools), `security` (35 tools), `design` (26 tools), `perf` (34 tools), `router` (10 tools — see [The router preset](#the-router-preset)), or `full` (every registered tool, opt-in). A preset is a *deferral*, not a restriction: everything outside it is registered but hidden, and `load_tools` pulls any of it in mid-session. `tools.exclude` remains a hard restriction that `load_tools` cannot undo. |
| `tools.include` | — | Whitelist specific tools by name |
| `tools.exclude` | — | Blacklist specific tools by name |
| `tools.description_verbosity` | `"full"` | Per-tool description length. `minimal` = first sentence. `none` = empty |
| `tools.instructions_verbosity` | `"full"` | Server-level instructions (the tool-routing block). `full` ~2K tokens, `minimal` ~200 |
| `tools.client_profile` | `"auto"` | Tailors the advertised surface to the connected host — see [Client profiles](#client-profiles). `auto` detects it from the `initialize` handshake, a profile name pins it, `off` disables the layer. Env override: `TRACE_MCP_CLIENT_PROFILE` |
| `tools.agent_behavior` | `"off"` | Behavior rules appended to instructions — see [Agent behavior rules](#agent-behavior-rules) |
| `tools.meta_fields` | `true` | Meta fields in responses (`_hints`, `_budget_warning`, etc.). Set `false` or list to narrow |
| `tools.compact_schemas` | `false` | Strip advanced/optional params from tool schemas. Cuts schema size ~42% (measured 2026-08-29) |

### Progressive tool disclosure

A preset used to be permanent: a tool outside it was never registered, so the
session that saved schema tokens also lost the tool for the rest of its life.
That made the small presets a bad trade, and most sessions ran `full` and paid
the whole surface up front.

Since v3.3 a preset is a *deferral*. Tools outside it are registered but
disabled — absent from `tools/list`, so you don't pay their schemas — and the
always-available `load_tools` pulls any of them in mid-session:

```
load_tools()                                  # list what this session deferred
load_tools({ tools: ["taint_analysis"] })     # load one
load_tools({ preset: "architecture" })        # load a preset's worth
load_tools({ preset: "full" })                # load everything deferred
```

Configs written before v3.3 pinned `"preset": "full"` — that was the default at
the time, not a choice, and it kept those installs paying for the whole surface
long after the default moved. Upgrading rewrites that one value to the shipped
default, once and without asking, and records that it did so; set `full` (or any
other preset) yourself afterwards and no later upgrade will touch it. To go back
to the old behaviour, set `"preset": "full"` or run `load_tools({ preset: "full" })`.

Loading emits `notifications/tools/list_changed`, and clients that honour it
(Claude Code among them) re-read the larger surface and can call the new tools
directly. Clients that ignore the notification are not stuck: `load_tools`
returns each loaded tool's full JSON schema in its response, and the loaded tool
is immediately reachable through `batch` — `batch({ calls: [{ tool, args }] })`
— which is in every preset.

`batch` dispatches by name against the whole registry, deferred tools included,
so a deferred tool is callable through it *without* loading it first. That is
deliberate (it is what the `router` preset below is built on), and the price is
one round-trip's worth of schema you never see: you have to know the tool's
arguments, or read them from `load_tools`. `tools.exclude` is not reachable this
way — the exclusion is checked on the inner call names too, on both the local
and the daemon-backed path.

What escalation cannot do is widen `tools.exclude`. Exclusion stays a hard
restriction; `load_tools` reports those names under `blocked` and leaves them
off. If you want a tool gone, exclude it — don't rely on the preset.

A preset name that doesn't resolve — a typo, or a preset added in a version
newer than the one installed — falls back to `minimal` and logs a warning naming
the available presets. Before v3.12 it fell back to `full`, which turned a typo
in a flag set to save tokens into a 36.3k-token surface instead of a 7.8k one.
Failing toward the cheap surface costs at most one `load_tools` round-trip.

Measured `tools/list` cost of each preset on this repo (serialized chars, then
o200k tokens, 2026-09-01; `router` added 2026-09-02): `router` 7.1k / 1.6k,
`design` 21.9k / 5.0k, `perf` 32.3k / 7.5k, `minimal`
34.0k / 7.8k, `review` 37.3k / 8.6k, `security` 41.5k / 9.6k, `architecture`
44.3k / 10.2k, `dev` 51.3k / 11.9k, `standard` 64.6k / 14.9k, `full` 157.7k /
36.3k. Against `full`, that is a 67% cut on the widest role preset (`dev`) and
86% on the narrowest (`design`) — framework-gated tools are excluded, so a
project that detects the matching framework pays more.
`load_tools` itself is 0.9k of that — the price of making the other 123k optional.

### The router preset

`"preset": "router"` advertises **no** code-intelligence tools at all — only the
session meta-tools that are never gated, `load_tools` and `batch` among them. Ten
tools, 1.6k tokens, 95.6% below `full` and 79.2% below the `minimal` default.

It is usable rather than crippled because the two halves cover each other:
`load_tools()` names the ~150 deferred tools (names only — the schemas are what
you are not paying for), and `batch` calls any of them directly. So the session
pays for a catalog instead of a surface, and there is no escalation round-trip
unless you want the schemas.

```jsonc
{ "tools": { "preset": "router" } }      // or: trace-mcp --preset router
```

It is **opt-in and will not become the default.** On a host that already defers
tool schemas itself — Claude Code's ToolSearch, which keeps only the 15
`ALWAYS_LOAD_TOOLS` eagerly loaded — `router` saves ~2.9k tokens and takes away
exactly the first-five-minutes tools that stamp exists to protect. On a host
without such a mechanism it saves ~6.2k per session, every session. Take it if
your client has no tool deferral of its own, or if you are running many short
sessions where the surface is most of what you pay.

### Client profiles

The preset decides *how much* capability a session advertises. The client
profile decides what that particular host does not need to be told about.

The connected client names itself in the `initialize` handshake, so trace-mcp
knows whether it is talking to Claude Code, Codex, Cursor, VS Code, or something
it has never seen. Two things follow from that:

- **Tools the host already has are not advertised.** A CLI coding agent arrives
  with its own content search; offering ours alongside it costs schema tokens
  and gives the model two ways to do one thing. Suppression lists are short and
  deliberately conservative — `search_text` and `discover_hermes_sessions` today.
- **The instructions name the host's own tools.** The routing block is written
  for a host it cannot see, so it says "`read`, `content-match`, `glob` mean
  whatever yours are called". Once the handshake identifies the host it says
  `Read`/`Grep`/`Glob` on Claude Code and `shell` (cat/rg/find) with
  `apply_patch` on Codex.

The profile composes *after* the preset — it only removes names from whatever
the preset already advertised — and it never removes capability. A suppressed
tool stays callable by name, `load_tools({ tools: ["search_text"] })` puts it
back on `tools/list`, and `"client_profile": "off"` (or
`TRACE_MCP_CLIENT_PROFILE=off`) disables the layer entirely. An unrecognised
host resolves to `generic`, which suppresses nothing.

Measured on a live `initialize` + `tools/list` round-trip against the built
server, default `minimal` preset (serialized chars, 2026-08-30):

| Client | instructions | `tools/list` | advertised | handshake total |
|---|---|---|---|---|
| `generic` (or `client_profile: "off"`) | 7,669 | 36,523 | 28 | 44,192 |
| `claude-code` | 7,933 | 34,621 | 27 | 42,554 (−3.7%) |
| `codex` | 7,927 | 34,621 | 27 | 42,548 (−3.7%) |
| `cursor` | 7,928 | 34,621 | 27 | 42,549 (−3.7%) |

The instructions grow slightly because the profile appends one line naming what
it hid and how to get it back — without it a suppressed tool is invisible, since
`load_tools()` lists what the *preset* deferred and a suppressed tool is inside
the preset.

Every `tools.*` option works from a project-local config file (`.trace/.config.json`) as well as the global one — none of them are global-only. The tool surface is built once per MCP session, so a change takes effect on the next session (restart the MCP client); the daemon does not need restarting.

### Agent behavior rules

`tools.agent_behavior` appends generic discipline rules (anti-sycophancy, anti-fabrication, goal-driven execution, 2-strike session hygiene, no drive-by refactors) to the server instructions. These are client-agnostic — every MCP-compatible client (Claude Code, Cursor, Codex, Windsurf, …) receives them.

| Value | What ships | When to use |
|---|---|---|
| `"off"` *(default)* | Nothing | Default — you already manage agent behavior elsewhere (CLAUDE.md, [tweakcc](tweakcc.md)), or don't want opinionated rules |
| `"minimal"` | One rule: never fabricate paths/symbols/APIs — call `search`/`get_symbol`/run the command | Minimal nudge tied to trace-mcp tool use, no personality prescription |
| `"strict"` | 8 rules: no flattery, disagree on wrong premises, never fabricate, stop when confused, goal-driven execution, verify before reporting "done", 2-strike rule, surgical changes only | Max-tier default — aligns agent behavior across a team |

**Auto-set by `trace-mcp init`:** picking the **Max** enforcement level writes `"agent_behavior": "strict"` to your global config. Picking Base/Standard writes `"off"`. Re-run `init` to change tiers — the value updates idempotently.

**Why it lives in MCP instructions (not CLAUDE.md or tweakcc):**
- Cross-client — Cursor/Codex/Windsurf users get the same behavior without CC-specific setup.
- Auto-updates on `npm upgrade trace-mcp` — no re-init required to pull new rule wording.
- Single source of truth alongside the tool-routing block.

If you want to override in one project without affecting others, put `"agent_behavior": "off"` (or any other value) in that project's `.trace/.config.json` — per-project config takes precedence over global.

### 4-tier resolution system

Every edge in the call graph carries a `resolution_tier` indicating how it was resolved:

| Tier | Source | Confidence |
|---|---|---|
| `scip_resolved` | Offline SCIP index ingestion (opt-in) | Compiler-grade (highest) |
| `lsp_resolved` | LSP call hierarchy | Compiler-grade |
| `ast_resolved` | Tree-sitter + module resolution | Static AST (default) |
| `ast_inferred` | Heuristic inference from imports | Medium |
| `text_matched` | Name/text similarity matching | Lowest |

The `get_call_graph` tool reports a `resolution_tiers` summary showing the distribution across all edges, so you can see how much of the graph has compiler-grade confidence.

---

## Topology & subprojects

trace-mcp includes a **topology layer** for cross-service analysis and a **subproject layer** for linking dependency graphs across subprojects within a project.

A **subproject** is any working repository that is part of your project's ecosystem: microservices, frontends, backends, shared libraries, CLI tools, etc. Each directory with its own root marker (`package.json`, `composer.json`, `go.mod`, etc.) is a subproject. A project contains one or more subprojects; the project itself is not a subproject. Subprojects can live inside the project directory (e.g. `project/frontend/`) or outside it (added manually via `subproject add`).

Both topology and subprojects are **enabled by default** — every indexed project auto-detects its subprojects.

```jsonc
{
  "topology": {
    "enabled": true,           // default: true — enable topology + subproject tools
    "auto_discover": true,     // default: true — auto-detect and register subprojects on indexing
    "auto_detect": true,       // default: true — auto-detect from Docker Compose
    "repos": [],               // additional repo paths to include in topology
    "contract_globs": []       // explicit contract file patterns (e.g. ["api/openapi.yaml"])
  }
}
```

| Option | Default | Description |
|---|---|---|
| `topology.enabled` | `true` | Enable topology and subproject tools |
| `topology.auto_discover` | `true` | Auto-detect and register subprojects on every index |
| `topology.auto_detect` | `true` | Auto-detect subprojects from Docker Compose / workspace structure |
| `topology.repos` | `[]` | Additional repo paths to include in the topology graph |
| `topology.contract_globs` | — | Explicit paths to API contract files (relative to project root) |

### Auto-discovery flow

When a project is indexed (via `serve`, `serve-http`, or `index`):

1. **Subprojects are detected** within the project root using these strategies (in order):
   - **Docker Compose** — parses `docker-compose.yml` / `compose.yml` for service definitions
   - **Flat workspace** — scans first-level subdirectories for root markers (`package.json`, `composer.json`, `go.mod`, etc.). Requires ≥2 found (e.g. `project/frontend/` + `project/backend/`)
   - **Grouped workspace** — scans two levels deep (`root/group/service/`). Requires ≥2 found (e.g. `project/org/service-a/` + `project/org/service-b/`)
   - **Monolith fallback** — treats the project root as a single subproject
2. Each detected subproject is **registered** and bound to the project in `~/.trace/topology.db`
3. **API contracts** are parsed (OpenAPI, GraphQL SDL, Protobuf) for each subproject
4. Code is **scanned** for HTTP/gRPC client calls (fetch, axios, Http::, requests, etc.)
5. Client calls are **matched** to known endpoints from other subprojects
6. **Cross-subproject edges** are created

This is non-blocking — the server starts immediately, and subproject syncs in the background.

### Disabling

To disable auto-discovery while keeping topology tools:
```jsonc
{ "topology": { "enabled": true, "auto_discover": false } }
```

To disable everything:
```jsonc
{ "topology": { "enabled": false } }
```

### Subproject CLI

```bash
# Add a subproject (can be inside or outside project dir)
trace-mcp subproject add --repo=../service-b --project=. [--contract=openapi.yaml] [--name=my-service]
trace-mcp subproject remove <name-or-path>
trace-mcp subproject list [--project=.] [--json]
trace-mcp subproject sync
trace-mcp subproject impact --endpoint=/api/users [--method=GET] [--service=user-svc]
```

### Supported contract formats

| Format | Auto-detected files |
|---|---|
| **OpenAPI / Swagger** | `openapi.yml`, `openapi.yaml`, `openapi.json`, `swagger.yml`, `swagger.yaml`, `swagger.json`, `api-spec.yml`, `api-spec.yaml`, `api-spec.json` |
| **GraphQL SDL** | `schema.graphql`, `schema.gql` |
| **Protobuf / gRPC** | `*.proto` |

### Supported client call patterns

The scanner detects HTTP/gRPC/GraphQL calls in 12+ patterns across all supported languages:

| Pattern | Languages | Example |
|---|---|---|
| `fetch()` | JS/TS | `fetch('/api/users')` |
| `axios.*()` | JS/TS | `axios.get('/api/users')` |
| `Http::*()` | PHP/Laravel | `Http::post('/api/orders')` |
| `requests.*()` | Python | `requests.get('https://api.example.com/users')` |
| `http.Get/Post()` | Go | `http.Get("http://svc/api/users")` |
| `RestTemplate.*()` | Java/Kotlin | `.getForObject("/api/users")` |
| gRPC stubs | All | `client.GetUser()` |
| GraphQL operations | All | `query GetUser { ... }` |

### Cross-project tools

Every MCP session is still attached to exactly one project (see [stdio vs HTTP](#stdio-vs-http--choosing-your-setup)) — but two tools let an agent reach across to any OTHER project already registered with trace-mcp, without opening a second MCP connection:

- **`list_projects`** — lists every project in `~/.trace/registry.json` (root, name, type, last-indexed timestamp), plus known subprojects when topology is enabled for the current session.
- **`call_project_tool { project, tool, args }`** — runs any of the {{ site.data.counts.tools }} normal trace-mcp tools against a DIFFERENT registered project's already-indexed data and returns that tool's response verbatim. `project` must be a root from `list_projects`; an unregistered root or an unknown `tool` name returns a structured `{ error: { code, message, data } }` payload instead of throwing.

This is read-only relay wiring — it never starts indexing or a file watcher for the target project. In the HTTP daemon, a target project already warm in memory is served directly; a cold one is opened on demand via the same read-mostly path used for on-demand subprojects. Under `stdio` (no daemon), the target project's existing index database is opened directly; a project that has never been indexed cannot be relayed to.

No existing tool's schema changes because of this — `call_project_tool` dispatches to the target project's own registered handler for `tool`, so that tool's contract is exactly what it is when called directly.

---

## Supported MCP clients

`trace init` (or `trace-mcp init`) detects installed MCP clients and writes a `trace` server entry into each one's native config format (with `trace-mcp` legacy compatibility preserved). Pick clients interactively, or pass `--mcp-client <name>` for non-interactive runs.

| Client | Config path | Format | Top-level key | Notes |
|---|---|---|---|---|
| Claude Code | `~/.claude.json`, `<project>/.mcp.json` | JSON | `mcpServers` | Supports Base / Standard / Max enforcement tiers (hooks, tweakcc) |
| Claw Code | `~/.claw/settings.json`, `<project>/.claw.json` | JSON | `mcpServers` | Same enforcement tiers as Claude Code |
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) | JSON | `mcpServers` | Quit Claude.app before init — it overwrites foreign keys on preference flush |
| Cursor | `~/.cursor/mcp.json`, `<project>/.cursor/mcp.json` | JSON | `mcpServers` | Also writes `.cursor/rules/trace.mdc` |
| Windsurf | `~/.windsurf/mcp.json`, `<project>/.windsurf/mcp.json` | JSON | `mcpServers` | Also writes `.windsurfrules` |
| Continue | `~/.continue/mcpServers/mcp.json` | JSON | `mcpServers` | |
| Junie | `~/.junie/mcp/mcp.json` | JSON | `mcpServers` | |
| JetBrains AI Assistant | IDE-internal XML | — | — | Manual: Settings → Tools → AI Assistant → MCP. Use "Import from Claude" if Claude Desktop is configured |
| Codex | `~/.codex/config.toml` | TOML | `[mcp_servers.trace]` | A legacy `[mcp_servers.trace-mcp]` table is detected and renamed on `init` |
| Hermes Agent | `$HERMES_HOME/config.yaml` (default `~/.hermes/config.yaml`) | YAML | `mcp_servers` | Always global; also writes `AGENTS.md` and pre-allowlists hooks |
| **AMP** (Sourcegraph) | `~/.config/amp/settings.json[c]`, `<project>/.amp/settings.json[c]` | JSON / JSONC | `amp.mcpServers` (literal dot in key) | Comments and formatting preserved via `jsonc-parser`. Also writes `AGENTS.md` |
| **Warp** | Cloud-synced storage (no writable file) | — | — | Manual: Settings → Agents → MCP servers → + Add → paste JSON. If Claude Code is also configured, enable "File-based MCP servers" so Warp inherits trace from `~/.claude.json`. Also writes `AGENTS.md` |
| **Factory Droid** | `~/.factory/mcp.json`, `<project>/.factory/mcp.json` | JSON | `mcpServers` (entries need `type: "stdio"`) | Also writes `AGENTS.md` |
| **Cline** | `<VS Code User>/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` | JSON | `mcpServers` | VS Code extension; global-only (globalStorage has no per-project variant). Detected only when the extension's settings dir exists |
| **Kilo Code** | `<VS Code User>/globalStorage/kilocode.kilo-code/settings/mcp_settings.json` | JSON | `mcpServers` | Legacy VS Code extension config. The newer Kilo CLI (≥ v7) uses a non-standard `~/.config/kilo/kilo.jsonc` shape (`mcp` key, `command` as array) that trace does not write — configure that manually if you use the CLI |
| **Antigravity** (Google) | `~/.gemini/config/mcp_config.json` | JSON | `mcpServers` | Global-only (no documented per-project config as of mid-2026) |
| **Kimi Code CLI** (Moonshot) | `~/.kimi/mcp.json` | JSON | `mcpServers` | Global-only; format is compatible with other MCP clients |

> `<VS Code User>` is `~/Library/Application Support/Code/User` (macOS), `%APPDATA%\Code\User` (Windows), or `~/.config/Code/User` (Linux).

**Enforcement tiers (Claude Code / Claw / Desktop only):**
- **Base** — `CLAUDE.md` block with tool routing rules.
- **Standard** — Base + PreToolUse guard hooks that intercept built-in Read/Grep/Glob.
- **Max** — Standard + tweakcc system-prompt patches and strict agent-behavior rules.

For all other clients only the Base tier applies — there is no equivalent of Claude Code hooks or tweakcc in those tools.

### Renaming `trace-mcp` → `trace`: what `init` can and can't reach

MCP clients advertise every tool prefixed with the server key, so the rename
also renames the tool prefix a client-side config may reference in full:
`mcp__trace-mcp__search` becomes `mcp__trace__search`. `trace init` (and the
post-update migration that runs automatically when the daemon starts after
an upgrade) rewrites this everywhere it owns the file:

- The `mcpServers` entry itself, in every supported client above.
- Claude Code / Claw Code **permission allowlist entries**
  (`permissions.allow` / `permissions.deny`) and **hook `matcher` strings**,
  in both the global `settings.json` and the project-scoped
  `settings.local.json`.

**What stays manual.** Anything you wrote yourself outside those specific
fields is not touched — most commonly, tool names spelled out in your own
prose inside `CLAUDE.md` / `AGENTS.md` (`init` only rewrites the routing
block it generated, not arbitrary text you added), or a permission/hook
config in a file trace-mcp doesn't manage the shape of. If a tool call stops
matching a hook, or an allowlisted tool starts re-prompting for approval
after upgrading, grep your own config for `mcp__trace-mcp__` and replace it
with `mcp__trace__`.

### Multica workspace agents

Multica agents don't run `trace-mcp init` — each agent's MCP wiring is set directly with `multica agent update --mcp-config-file <json>` (or `agent create --mcp-config-file` on first setup), scoped to that one agent. An agent with no `mcp_config` inherits the machine's own MCP setup, so its preset is whatever `tools.preset` says in `~/.trace/.config.json` (`minimal` unless you changed it).

Setting `mcp_config` does **not** override the inherited `trace-mcp` entry by name — Multica launches that agent with `--strict-mcp-config`, so it gets *only* the servers you list. Everything else the machine provides (user-scope servers, plugin servers, hosted connectors) disappears for that agent. List every server the role still needs, not just `trace-mcp`:

```json
{
  "mcpServers": {
    "trace-mcp": { "command": "trace-mcp", "args": ["serve", "--preset", "dev"] },
    "context7": { "command": "context7-mcp", "args": [] }
  }
}
```

`--preset` is an option of the `serve` subcommand — `args` without `serve` starts nothing.

```bash
multica agent update <agent-id> --mcp-config-file ./trace-mcp-dev.json
```

> **Check that the agent's installed `trace-mcp` supports `--preset` before rolling this out.** The flag shipped after 3.11.0; an older binary exits immediately with `unknown option '--preset'`, and because a dead MCP server is silent, the agent simply runs with **zero** trace-mcp tools instead of a smaller set. Verify with `trace-mcp --help | grep -- --preset` on the machine the agent runs on. Same trap applies to a `command` pointing at a build inside a per-task working directory — those get cleaned up.

The role → preset matrix used in the trace-mcp workspace itself (adjust to your own roles):

| Agent role | Preset | Why |
|---|---|---|
| Independent code review | `review` | rename-safety, quality gates, risk/impact assessment — no refactor or design tools |
| Security audit | `security` | `scan_security`, `taint_analysis`, SBOM, config audit — no refactor/design tools |
| Design/UX review | `design` | component tree, screens, navigation, state — no security/perf tools |
| Implementation & bugfixing | `dev` | refactor + codemod tools (`apply_rename`, `extract_function`, `change_signature`) |
| Performance analysis | `perf` | `analyze_perf`, complexity/coupling trends, risk hotspots |

`multica agent update --mcp-config*` **replaces** the agent's entire `mcp_config` — it does not merge. If the agent already has other private MCP servers configured, read them back first (only a workspace owner/admin can; `mcp_config` reads redacted for agent actors) and include them in the new payload, or the update will silently remove them.

If you can't read it back (agent actors can't), you can still recover the *set* of servers a past run had from Claude Code's own connection logs on the machine that ran it — `~/Library/Caches/claude-cli-nodejs/<encoded-workdir>/mcp-logs-<server>/` has one directory per connected server. Under `--strict-mcp-config` that listing is exactly the config's server set; only each server's argv is still unknown.

---

## stdio vs HTTP — choosing your setup

trace speaks MCP over two transports. Which one to pick depends on whether you want a process per repo or one long-lived daemon serving many projects.

### stdio — recommended for per-repo agent sessions

`trace serve` runs the server over stdio. Your MCP client launches one process per session with the working directory set to the repo you opened, so the project is auto-detected from there — no URL, no `?project=`, and each session is isolated to its own repo. No daemon is required: it runs in-process, and if a daemon happens to be running it transparently reuses the warm index.

Wire it up without touching a committed file:

```bash
# Across all your projects (stored in ~/.claude.json, not committed):
claude mcp add --scope user trace -- npx -y trace-mcp@latest serve

# Just one project, not committed (local scope):
claude mcp add --scope local trace -- npx -y trace-mcp@latest serve
```

To share one config with the whole team, commit a portable stdio entry to `.mcp.json` — it carries no machine-specific URL or path:

```json
{
  "mcpServers": {
    "trace": { "command": "npx", "args": ["-y", "trace-mcp@latest", "serve"] }
  }
}
```

Each developer's session spawns its own per-repo process; nothing is shared or hardcoded. Avoid committing an absolute HTTP URL such as `http://127.0.0.1:3741/mcp?project=/Users/you/...` — that path only exists on your machine and would break for everyone else.

### HTTP daemon — one warm index shared across many projects

`trace serve-http` runs a long-lived daemon (default `127.0.0.1:3741`) that holds warm indexes for several registered projects and backs the desktop app. MCP clients connect with the target project in the URL:

```
http://127.0.0.1:3741/mcp?project=/absolute/path/to/repo
```

Holding several indexes warm is what the daemon costs you in RAM: [daemon memory](daemon-memory.md) breaks the resident set down region by region and names the knob that bounds each one.

The daemon multiplexes projects — one process serves all of them — but each MCP registration is bound to a single project via `?project=` (or, for clients that cannot append a query string, the `X-Trace-Project` header or `params._meta["traceMcp/projectRoot"]`). Pick this when you want one warm index reused across sessions and tools and you're comfortable managing the per-registration URL. For one-session-per-repo workflows, stdio is simpler.

> **The daemon's trust boundary is loopback.** `serve-http` has no authentication: every `/api` route and `/mcp` itself trust the caller, and `?project=` / `X-Trace-Project` / `params._meta["traceMcp/projectRoot"]` can name any directory on the machine — the daemon will index and serve it. On `127.0.0.1` that grants nothing extra, because anything able to reach the port already runs as you and can read those files directly. Binding elsewhere hands that power to the network, so a non-loopback `--host` is refused unless you also pass `--allow-remote`, and even then you are expected to put your own authentication (SSH tunnel, reverse proxy, VPN) in front of the port.

> **One project per session.** Both transports resolve exactly one project per MCP session — stdio from the working directory, HTTP from `?project=`. A single session cannot query across repositories today; to work with several repos, register each (`trace add <path>`) and add one MCP entry per repo (HTTP) or open one session per repo (stdio). Cross-repo queries inside a single session — useful for multi-repo/pseudo-monorepo setups — are tracked as an enhancement in [#199](https://github.com/nikolai-vysotskyi/trace-mcp/issues/199).

| | stdio | HTTP daemon |
|---|---|---|
| Process model | one per session | one shared daemon |
| Project scope | auto-detected from cwd | `?project=` per registration |
| Config | command, no URL | URL with absolute path |
| Warm-index reuse | per session (shared if a daemon is running) | always shared |
| Best for | per-repo agent sessions, committed team config | desktop app, many projects, one shared index |

---

## Hermes Agent sessions

Hermes Agent (NousResearch) stores conversations in a SQLite database at `$HERMES_HOME/state.db` (default `~/.hermes/state.db`) plus one DB per profile under `<home>/profiles/<name>/state.db`. trace reads these read-only and exposes them through:

- `discover_hermes_sessions` — MCP tool that lists sessions without mining or indexing them.
- `mine_sessions` — if you pass a `project_root`, the decision miner also walks every Hermes session it can see and records any decisions it finds under that project. When `project_root` is absent Hermes is skipped entirely — global conversations are deliberately not attributed to a guessed project.

Hermes sessions are global (no per-project binding in the upstream schema). Do not expect project scoping on the provider side.

```jsonc
{
  "hermes": {
    "enabled": "auto",       // "auto" (default) | true | false
    "home_override": null,   // override $HERMES_HOME / ~/.hermes resolution
    "profile": null          // scope discovery to <home>/profiles/<name>/
  }
}
```

With `enabled: "auto"` the provider is registered at boot; discovery returns an empty list when no `state.db` exists, so there is no penalty on machines that don't use Hermes.

---

## Environment variables

| Variable | Description |
|---|---|
| `TRACE_MCP_LOG_LEVEL` | Log level (debug, info, warn, error) |
| `HERMES_HOME` | Override for Hermes Agent storage root (default `~/.hermes`). Read by `discover_hermes_sessions` and the Hermes session provider. |
| `TRACE_MCP_COMPUTE_TIMEOUT_MS` | Wall-clock ceiling for one heavy graph traversal (default `15000`). |
| `TRACE_MCP_COMPUTE_RSS_MB` | Resident-memory ceiling checked during a traversal (default `3000`). |
| `TRACE_MCP_COMPUTE_MAX_ITERATIONS` | Iteration ceiling for one traversal (default `2000000`). |
| `TRACE_MCP_NO_COMPUTE_GUARD` | Set to `1` to disable all compute ceilings (debugging). |

### Compute ceilings

`get_call_graph`, `get_change_impact`, `get_dependency_diagram`,
`get_circular_imports`, `find_usages` and `get_pagerank` tick a per-call guard
inside their traversal loops. When a ceiling is hit the tool returns the
**partial** result with a `_budget_exceeded` field naming the ceiling, the
limit, and the elapsed time — never a tool-call error — and the daemon log
gets one `Compute budget exceeded` line with the tool name.

The defaults come from `scripts/bench-compute-guard.ts`: a deliberately heavy
`get_call_graph` (37,449 symbols, depth 5) consumes 79,577 ticks in ~85 ms, so
every ceiling sits one to two orders of magnitude above anything a real query
reaches. Measured tick overhead on that same query is within run-to-run noise
(−2.7% … +2.1% across five runs).

---

## CLI

*(All commands support both `trace <command>` and legacy `trace-mcp <command>` aliases.)*

```bash
# Setup
trace init                 # One-time global setup (MCP clients, hooks, CLAUDE.md)
trace add [dir]            # Register a project for indexing
trace list                 # List all registered projects
trace upgrade [dir]        # Upgrade all projects (or specific one) — migrations + reindex

# Server
trace serve                # Start MCP server (stdio transport)
trace serve-http           # Start HTTP/SSE server (default: 127.0.0.1:3741)
  -p, --port <port>        # Custom port
  --host <host>            # Custom host (loopback only unless --allow-remote)
  --allow-remote           # Permit a non-loopback --host (see the trust boundary note)

# Manual indexing
trace index <dir>          # Index a project directory
  -f, --force              # Force reindex all files

# Subprojects (= services bound to projects)
trace subproject add       # Add a subproject to a project
  --repo <path>            # Subproject/service path (required)
  --project <path>         # Project this subproject belongs to (required)
  --contract <paths...>    # Explicit contract file paths
  --name <name>            # Display name
trace subproject remove <name-or-path>   # Remove a subproject
trace subproject list                    # List subprojects
  --project <path>         # Filter to a specific project
  --json                   # Output as JSON
trace subproject sync                    # Re-scan all subprojects
trace subproject impact                  # Cross-subproject impact analysis
  --endpoint <path>        # Endpoint path pattern
  --method <method>        # HTTP method filter
  --service <name>         # Service name filter
  --json                   # Output as JSON

# Hooks
trace setup-hooks          # Install guard hook (blocks Read/Grep/Glob/Bash on code + Agent(Explore))
  --global                 # Install globally
  --uninstall              # Remove hook

# Analytics (see docs/analytics.md)
trace analytics sync       # Parse session logs into analytics DB
  --full                   # Force full rescan
trace analytics report     # Token usage report
  --period <p>             # today, week, month, all (default: week)
trace analytics optimize   # Optimization recommendations
trace analytics savings    # Real savings analysis
trace analytics benchmark  # Synthetic token efficiency benchmark
  --queries <n>            # Queries per scenario (default: 10)
  --format <fmt>           # text, json, markdown
trace analytics coverage   # Technology coverage report
trace analytics trends     # Daily usage trends
  --days <n>               # Number of days (default: 30)
```

---

## Security

- **Path traversal protection** — all file access validated against project root
- **Symlink detection** — prevents escape from project boundary
- **Secret pattern filtering** — configurable regex patterns filter out secrets from tool output
- **File size limits** — per-file byte cap prevents OOM on large files
- **Artisan whitelist** — only safe artisan commands allowed (when Laravel integration is enabled)
- **HTTP rate limiting** — 60 req/min per IP on HTTP/SSE transport

What counts as a failing security finding *in your own code* is a separate
setting — `quality_gates.rules.max_security_critical_findings`, documented with
this project's own calibrated numbers under [quality gates](quality-gates.md).

---

# Config index

Source: https://trace-mcp.com/config-index.html



Every key the config schema accepts, in schema order, with its type and the
default that applies when you leave it out. It is generated from the schema
itself, so an option cannot ship without appearing here.

This page answers "does this key exist, and what is it set to if I say nothing".
For what a key is *for* — and for the sections large enough to have earned their
own page — start at [configuration](configuration.md), which links on to
[quality gates](quality-gates.md), [telemetry](telemetry.md),
[daemon memory](daemon-memory.md) and [decision memory](decision-memory.md).

Everything is optional: trace-mcp indexes a standard project with no config file
at all. A default of `—` means the key is a section rather than a value;
`_unset_` means there is no default and the feature reads the key only when you
supply it.

| Key | Type | Default |
| --- | --- | --- |
| `db` | object | — |
| `db.path` | string | _unset_ |
| `include` | string[] | _built-in list_ |
| `exclude` | string[] | _built-in list_ |
| `follow_symlinks` | boolean | `false` |
| `ignore` | object | `{}` |
| `ignore.directories` | string[] | `[]` |
| `ignore.patterns` | string[] | `[]` |
| `ignore.gitignore` | boolean | `true` |
| `frameworks` | object | — |
| `frameworks.laravel` | object | — |
| `frameworks.laravel.artisan` | object | — |
| `frameworks.laravel.artisan.enabled` | boolean | `true` |
| `frameworks.laravel.artisan.timeout` | number (> 0) | `10000` |
| `frameworks.laravel.graceful_degradation` | boolean | `true` |
| `ai` | object | — |
| `ai.enabled` | boolean | `false` |
| `ai.provider` | `onnx` \| `ollama` \| `openai` \| `anthropic` \| `lmstudio` \| `gemini` \| `vertex` \| `voyage` \| `mistral` \| `deepseek` \| `groq` \| `together` \| `xai` | `"onnx"` |
| `ai.features` | object | `{}` |
| `ai.features.embedding` | boolean | `true` |
| `ai.features.inference` | boolean | `true` |
| `ai.features.fast_inference` | boolean | `true` |
| `ai.base_url` | string | _unset_ |
| `ai.api_key` | string | _unset_ |
| `ai.inference_model` | string | _unset_ |
| `ai.fast_model` | string | _unset_ |
| `ai.embedding_model` | string | _unset_ |
| `ai.embedding_dimensions` | number | _unset_ |
| `ai.summarize_on_index` | boolean | `false` |
| `ai.summarize_batch_size` | number (> 0) | `20` |
| `ai.summarize_kinds` | string[] | `["class","function","method","interface","trait","enum","…` |
| `ai.summarizeFromDocstrings` | boolean | `true` |
| `ai.openaiExtraBody` | object | `{}` |
| `ai.concurrency` | number (≥ 1, ≤ 32) | `1` |
| `ai.reranker_model` | string | _unset_ |
| `ai.vertex_project` | string | _unset_ |
| `ai.vertex_location` | string | _unset_ |
| `ai.autoRebuildOnProviderMismatch` | boolean | `true` |
| `plugins` | string[] | `[]` |
| `security` | object | — |
| `security.secret_patterns` | string[] | _unset_ |
| `security.max_file_size_bytes` | number (> 0) | _unset_ |
| `security.max_files` | number (> 0) | _unset_ |
| `predictive` | object | — |
| `predictive.enabled` | boolean | `true` |
| `predictive.weights` | object | `{}` |
| `predictive.weights.bug` | object | `{}` |
| `predictive.weights.bug.churn` | number | `0.2` |
| `predictive.weights.bug.fix_ratio` | number | `0.2` |
| `predictive.weights.bug.complexity` | number | `0.2` |
| `predictive.weights.bug.coupling` | number | `0.15` |
| `predictive.weights.bug.pagerank` | number | `0.1` |
| `predictive.weights.bug.authors` | number | `0.15` |
| `predictive.weights.tech_debt` | object | `{}` |
| `predictive.weights.tech_debt.complexity` | number | `0.3` |
| `predictive.weights.tech_debt.coupling` | number | `0.25` |
| `predictive.weights.tech_debt.test_gap` | number | `0.25` |
| `predictive.weights.tech_debt.churn` | number | `0.2` |
| `predictive.weights.change_risk` | object | `{}` |
| `predictive.weights.change_risk.blast_radius` | number | `0.25` |
| `predictive.weights.change_risk.complexity` | number | `0.2` |
| `predictive.weights.change_risk.churn` | number | `0.2` |
| `predictive.weights.change_risk.test_gap` | number | `0.2` |
| `predictive.weights.change_risk.coupling` | number | `0.15` |
| `predictive.cache_ttl_minutes` | number | `60` |
| `predictive.git_since_days` | number | `180` |
| `predictive.module_depth` | number | `2` |
| `intent` | object | — |
| `intent.enabled` | boolean | `false` |
| `intent.domain_hints` | object | _unset_ |
| `intent.custom_domains` | object[] | _unset_ |
| `intent.auto_classify_on_index` | boolean | `true` |
| `intent.classify_batch_size` | number (> 0) | `100` |
| `runtime` | object | — |
| `runtime.enabled` | boolean | `false` |
| `runtime.otlp` | object | `{}` |
| `runtime.otlp.port` | number (≥ 0, ≤ 65535) | `4318` |
| `runtime.otlp.host` | string | `"127.0.0.1"` |
| `runtime.otlp.max_body_bytes` | number (> 0) | `4194304` |
| `runtime.retention` | object | `{}` |
| `runtime.retention.max_span_age_days` | number (> 0) | `7` |
| `runtime.retention.max_aggregate_age_days` | number (> 0) | `90` |
| `runtime.retention.prune_interval` | number (≥ 0) | `100` |
| `runtime.mapping` | object | `{}` |
| `runtime.mapping.fqn_attributes` | string[] | `["code.function","code.namespace","code.filepath"]` |
| `runtime.mapping.route_patterns` | string[] | `["^(?:GET\|POST\|PUT\|PATCH\|DELETE\|HEAD\|OPTIONS)\\s+(.+)$"]` |
| `lsp` | object | — |
| `lsp.enabled` | boolean | `false` |
| `lsp.servers` | object | `{}` |
| `lsp.auto_detect` | boolean | `true` |
| `lsp.max_concurrent_servers` | number (≥ 1, ≤ 4) | `2` |
| `lsp.enrichment_timeout_ms` | number (≥ 5000, ≤ 600000) | `120000` |
| `lsp.batch_size` | number (≥ 10, ≤ 1000) | `100` |
| `scip` | object | — |
| `scip.enabled` | boolean | `false` |
| `scip.auto_detect` | boolean | `true` |
| `scip.index_path` | string | _unset_ |
| `scip.indexers` | object | `{}` |
| `scip.ingestion_timeout_ms` | number (≥ 5000, ≤ 600000) | `120000` |
| `topology` | object | — |
| `topology.enabled` | boolean | `true` |
| `topology.repos` | string[] | `[]` |
| `topology.auto_detect` | boolean | `true` |
| `topology.auto_discover` | boolean | `true` |
| `topology.contract_globs` | string[] | _unset_ |
| `indexer` | object | — |
| `indexer.workers` | number (≥ 1, ≤ 32) | _unset_ |
| `indexer.parallel_initial_index` | number (≥ 1, ≤ 16) | _unset_ |
| `pipeline` | object | `{}` |
| `pipeline.task_cache_ttl_days` | number (≥ 1, ≤ 365) | `30` |
| `vault` | object | `{}` |
| `vault.enabled` | boolean | `true` |
| `vault.roots` | string[] | `[]` |
| `vault.extra_globs` | string[] | `["**/*.md","**/*.mdx","**/*.markdown"]` |
| `decisions` | object | `{}` |
| `decisions.review_threshold` | number (≥ 0, ≤ 1) | `0.75` |
| `decisions.reject_threshold` | number (≥ 0, ≤ 1) | `0.45` |
| `memory` | object | `{}` |
| `memory.recall` | object | `{}` |
| `memory.recall.timeoutMs` | number (≥ 100, ≤ 60000) | `5000` |
| `memory.heat` | object | `{}` |
| `memory.heat.enabled` | boolean | `true` |
| `memory.heat.halfLifeDays` | number (≥ 0.5, ≤ 365) | `14` |
| `memory.heat.freshnessDays` | number (≥ 0.5, ≤ 365) | `7` |
| `memory.mining` | object | `{}` |
| `memory.mining.strategy` | `regex` \| `llm` \| `hybrid` | `"regex"` |
| `memory.mining.llm` | object | `{}` |
| `memory.mining.llm.maxTokensPerSession` | number (≥ 500, ≤ 50000) | `8000` |
| `memory.mining.llm.minSessionLength` | number (≥ 0) | `500` |
| `memory.mining.llm.maxSessions` | number (≥ 1) | `50` |
| `memory.mining.incrementalCursor` | boolean | `true` |
| `memory.memo` | object | `{}` |
| `memory.memo.enabled` | boolean | `true` |
| `memory.memo.regenerateEveryN` | number (≥ 5, ≤ 500) | `50` |
| `memory.memo.targetTokens` | number (≥ 100, ≤ 2000) | `350` |
| `memory.memo.maxBudgetTokens` | number (≥ 100, ≤ 4000) | `400` |
| `memory.memo.historyLimit` | number (≥ 1, ≤ 100) | `10` |
| `memory.memo.autoRegenerate` | boolean | `true` |
| `memory.memo.minTriggerIntervalSec` | number (≥ 60, ≤ 86400) | `600` |
| `memory.consolidation` | object | — |
| `memory.consolidation.defaultMinTitleSimilarity` | number (≥ 0, ≤ 1) | `0.4` |
| `memory.consolidation.defaultMaxDecisions` | number (≥ 1, ≤ 500) | `50` |
| `memory.consolidation.defaultSameTypeOnly` | boolean | `false` |
| `memory.audit_log` | object | — |
| `memory.audit_log.enabled` | boolean | `false` |
| `memory.audit_log.dir` | string | _unset_ |
| `memory.audit_log.retentionDays` | number (≥ 0, ≤ 3650) | `0` |
| `memory.weight_tuning` | object | — |
| `memory.weight_tuning.enabled` | boolean | `true` |
| `memory.weight_tuning.min_events` | number (≥ 10, ≤ 10000) | `25` |
| `memory.background` | object | `{}` |
| `memory.background.enabled` | boolean | `false` |
| `memory.background.tickIntervalSec` | number (≥ 10, ≤ 3600) | `60` |
| `memory.background.activityDebounceSec` | number (≥ 0, ≤ 3600) | `120` |
| `memory.background.idleWindowSec` | number (≥ 60, ≤ 86400) | `3600` |
| `memory.background.coldThresholdSec` | number (≥ 3600, ≤ 604800) | `86400` |
| `memory.background.mineMinIntervalSec` | number (≥ 60, ≤ 86400) | `1800` |
| `memory.background.clusterEveryNDecisions` | number (≥ 5, ≤ 500) | `25` |
| `memory.background.failureBackoffSec` | number (≥ 60, ≤ 86400) | `3600` |
| `memory.background.tuneCooldownSec` | number (≥ 3600, ≤ 2592000) | `86400` |
| `memory.background.tuneEveryNNewEvents` | number (≥ 5, ≤ 1000) | `25` |
| `quality_gates` | object | — |
| `quality_gates.enabled` | boolean | `true` |
| `quality_gates.fail_on` | `error` \| `warning` \| `none` | `"error"` |
| `quality_gates.rules` | object | `{}` |
| `quality_gates.rules.max_cyclomatic_complexity` | object | — |
| `quality_gates.rules.max_cyclomatic_complexity.threshold` | number \| string | _unset_ |
| `quality_gates.rules.max_cyclomatic_complexity.severity` | `error` \| `warning` | `"error"` |
| `quality_gates.rules.max_cyclomatic_complexity.scope` | `all` \| `new_symbols` \| `changed_symbols` | _unset_ |
| `quality_gates.rules.max_cyclomatic_complexity.message` | string | _unset_ |
| `quality_gates.rules.max_coupling_instability` | object | — |
| `quality_gates.rules.max_coupling_instability.threshold` | number \| string | _unset_ |
| `quality_gates.rules.max_coupling_instability.severity` | `error` \| `warning` | `"error"` |
| `quality_gates.rules.max_coupling_instability.scope` | `all` \| `new_symbols` \| `changed_symbols` | _unset_ |
| `quality_gates.rules.max_coupling_instability.message` | string | _unset_ |
| `quality_gates.rules.max_circular_import_chains` | object | — |
| `quality_gates.rules.max_circular_import_chains.threshold` | number \| string | _unset_ |
| `quality_gates.rules.max_circular_import_chains.severity` | `error` \| `warning` | `"error"` |
| `quality_gates.rules.max_circular_import_chains.scope` | `all` \| `new_symbols` \| `changed_symbols` | _unset_ |
| `quality_gates.rules.max_circular_import_chains.message` | string | _unset_ |
| `quality_gates.rules.max_dead_exports_percent` | object | — |
| `quality_gates.rules.max_dead_exports_percent.threshold` | number \| string | _unset_ |
| `quality_gates.rules.max_dead_exports_percent.severity` | `error` \| `warning` | `"error"` |
| `quality_gates.rules.max_dead_exports_percent.scope` | `all` \| `new_symbols` \| `changed_symbols` | _unset_ |
| `quality_gates.rules.max_dead_exports_percent.message` | string | _unset_ |
| `quality_gates.rules.max_tech_debt_grade` | object | — |
| `quality_gates.rules.max_tech_debt_grade.threshold` | number \| string | _unset_ |
| `quality_gates.rules.max_tech_debt_grade.severity` | `error` \| `warning` | `"error"` |
| `quality_gates.rules.max_tech_debt_grade.scope` | `all` \| `new_symbols` \| `changed_symbols` | _unset_ |
| `quality_gates.rules.max_tech_debt_grade.message` | string | _unset_ |
| `quality_gates.rules.max_security_critical_findings` | object | — |
| `quality_gates.rules.max_security_critical_findings.threshold` | number \| string | _unset_ |
| `quality_gates.rules.max_security_critical_findings.severity` | `error` \| `warning` | `"error"` |
| `quality_gates.rules.max_security_critical_findings.scope` | `all` \| `new_symbols` \| `changed_symbols` | _unset_ |
| `quality_gates.rules.max_security_critical_findings.message` | string | _unset_ |
| `quality_gates.rules.max_antipattern_count` | object | — |
| `quality_gates.rules.max_antipattern_count.threshold` | number \| string | _unset_ |
| `quality_gates.rules.max_antipattern_count.severity` | `error` \| `warning` | `"error"` |
| `quality_gates.rules.max_antipattern_count.scope` | `all` \| `new_symbols` \| `changed_symbols` | _unset_ |
| `quality_gates.rules.max_antipattern_count.message` | string | _unset_ |
| `quality_gates.rules.max_code_smell_count` | object | — |
| `quality_gates.rules.max_code_smell_count.threshold` | number \| string | _unset_ |
| `quality_gates.rules.max_code_smell_count.severity` | `error` \| `warning` | `"error"` |
| `quality_gates.rules.max_code_smell_count.scope` | `all` \| `new_symbols` \| `changed_symbols` | _unset_ |
| `quality_gates.rules.max_code_smell_count.message` | string | _unset_ |
| `telemetry` | object | — |
| `telemetry.enabled` | boolean | `false` |
| `telemetry.usage_ping` | boolean | `true` |
| `telemetry.max_rows` | number (≥ 0, ≤ 10000000) | `500000` |
| `telemetry.observability` | object | `{}` |
| `telemetry.observability.enabled` | boolean | `false` |
| `telemetry.observability.sink` | `noop` \| `otlp` \| `langfuse` \| `multi` | `"noop"` |
| `telemetry.observability.sampleRate` | number (≥ 0, ≤ 1) | `1` |
| `telemetry.observability.otlp` | object | `{}` |
| `telemetry.observability.otlp.endpoint` | string | `"http://localhost:4318/v1/traces"` |
| `telemetry.observability.otlp.headers` | object | `{}` |
| `telemetry.observability.otlp.serviceName` | string | `"trace-mcp"` |
| `telemetry.observability.otlp.maxQueuedSpans` | number (≥ 1, ≤ 1000000) | `5000` |
| `telemetry.observability.otlp.requestTimeoutMs` | number (≥ 0, ≤ 600000) | `10000` |
| `telemetry.observability.langfuse` | object | `{}` |
| `telemetry.observability.langfuse.endpoint` | string | `"https://cloud.langfuse.com"` |
| `telemetry.observability.langfuse.publicKey` | string | _unset_ |
| `telemetry.observability.langfuse.secretKey` | string | _unset_ |
| `telemetry.observability.langfuse.maxQueuedEvents` | number (≥ 2, ≤ 1000000) | `10000` |
| `telemetry.observability.langfuse.requestTimeoutMs` | number (≥ 0, ≤ 600000) | `10000` |
| `tools` | object | — |
| `tools.preset` | string | `"minimal"` |
| `tools.include` | string[] | _unset_ |
| `tools.exclude` | string[] | _unset_ |
| `tools.descriptions` | object | _unset_ |
| `tools.description_verbosity` | `full` \| `minimal` \| `none` | `"full"` |
| `tools.instructions_verbosity` | `full` \| `minimal` \| `none` | `"full"` |
| `tools.client_profile` | `auto` \| `off` \| `claude-code` \| `codex` \| `cursor` \| `vscode` \| `generic` | `"auto"` |
| `tools.agent_behavior` | `strict` \| `minimal` \| `off` | `"off"` |
| `tools.meta_fields` | boolean \| (`_hints` \| `_budget_warning` \| `_budget_level` \| `_duplicate_warning` \| `_dedup` \| `_optimization_hint` \| `_meta` \| `_duplication_warnings` \| `_methodology` \| `_warnings`)[] | `true` |
| `tools.compact_schemas` | boolean | `false` |
| `tools.default_format` | `json` \| `compact` \| `auto` | `"json"` |
| `tools.default_detail_level` | `minimal` \| `default` \| `full` | _unset_ |
| `watch` | object | `{}` |
| `watch.enabled` | boolean | `true` |
| `watch.debounceMs` | number (≥ 500, ≤ 30000) | `2000` |
| `logging` | object | `{}` |
| `logging.file` | boolean | `false` |
| `logging.path` | string | `"~/.trace-mcp/run.log"` |
| `logging.level` | `trace` \| `debug` \| `info` \| `warn` \| `error` \| `fatal` | `"info"` |
| `logging.max_size_mb` | number (> 0, ≤ 500) | `10` |
| `git` | object | `{}` |
| `git.defaultBaseBranch` | string | _unset_ |
| `idle_timeout_minutes` | number (≥ 0, ≤ 1440) | `30` |
| `daemon_stability_seconds` | number (≥ 0, ≤ 600) | `30` |
| `backend_swap_drain_ms` | number (≥ 0, ≤ 60000) | `5000` |
| `auto_spawn_daemon` | boolean | `true` |
| `daemon_spawn_timeout_seconds` | number (≥ 1, ≤ 60) | `20` |
| `daemon_idle_exit_minutes` | number (≥ 0, ≤ 1440) | `15` |
| `project_idle_unload_minutes` | number (≥ 0, ≤ 1440) | `30` |
| `daemon_eager_load_projects` | number (≥ 0, ≤ 1000) | `8` |
| `index_cache_mb` | number (≥ 1, ≤ 1024) | `16` |
| `index_mmap_mb` | number (≥ 0, ≤ 4096) | `64` |
| `hermes` | object | `{}` |
| `hermes.enabled` | `auto` \| boolean | `"auto"` |
| `hermes.home_override` | string | _unset_ |
| `hermes.profile` | string | _unset_ |

One key is worth reading with care: `db.path` is vestigial. Nothing reads it.
The index location is not configurable — `getDbPath()` in `src/global.ts` puts
every project's database under `~/.trace/index/`. The key is still accepted so
existing config files keep validating, and will be dropped in the next major.

---

# Architecture

Source: https://trace-mcp.com/architecture.html


## Indexing pipeline

This page describes how the index is built. What it exposes once built is the
[tools reference](tools-reference.md); which languages reach which depth of the
pipeline below is the [language capability matrix](language-matrix.md).

trace-mcp uses a two-pass indexing pipeline:

```
Source files (PHP, TS, Vue, Python, Go, Java, Kotlin, Ruby, HTML, CSS, Blade)
    │
    ▼
┌──────────────────────────────────────────┐
│  Pass 1 — Per-file extraction            │
│  Language plugins (tree-sitter) →        │
│    symbols (functions, classes, etc.)     │
│  Integration plugins →                   │
│    routes, components, migrations,       │
│    events, models, schemas, variants     │
└────────────────────┬─────────────────────┘
                     │
                     ▼
┌──────────────────────────────────────────┐
│  Pass 2 — Cross-file resolution          │
│  Module resolvers:                       │
│    PSR-4 · ES modules · Python modules  │
│  Integration plugins resolveEdges():     │
│    Vue component references              │
│    Inertia render → page mapping         │
│    Blade template inheritance            │
│    ORM relationship resolution           │
│    Route → controller binding            │
│  → unified directed edge graph           │
└────────────────────┬─────────────────────┘
                     │
                     ▼
┌──────────────────────────────────────────┐
│  Per-project SQLite (WAL mode) + FTS5   │
│  nodes · edges · symbols · routes       │
│  + optional: embeddings · summaries     │
└────────────────────┬─────────────────────┘
                     │
                     ▼
┌──────────────────────────────────────────┐
│  Subprojects (auto, post-index)         │
│  Topology DB (~/.trace/topology.db)     │
│  Auto-detect services per project       │
│  Contracts · Endpoints · Client calls   │
│  Cross-service impact edges             │
└──────────────────────────────────────────┘
```

**Incremental by default** — files are content-hashed; unchanged files are skipped on re-index.

When AI is enabled, a background pipeline runs after indexing to generate summaries and embeddings for key symbols.

### Storage

All state is centralized in `~/.trace/`, and what goes in it is set by
[configuration](configuration.md#how-config-works):

```
~/.trace/
  .config.json              # global config + per-project settings
  registry.json             # project registry (all added projects)
  topology.db               # cross-service topology + subproject graph
  analytics.db              # session analytics (cross-project)
  savings.json              # cumulative token savings tracker
  index/
    my-app-a1b2c3d4e5f6.db  # per-project SQLite databases
    api-server-b2c3d4e5.db
```

Each project gets its own SQLite database, named `<project-basename>-<sha256-hash-of-path>.db`. The project registry tracks which projects are registered, their root paths, and last index time. Nothing is stored in the project directory itself.

The **topology database** (`topology.db`) is shared across all projects. It stores:
- **Subprojects** (= services) — bound to projects, auto-detected or manually added
- **API contracts** — parsed OpenAPI, GraphQL SDL, Protobuf specs
- **Endpoints** — normalized API endpoints extracted from contracts
- **Client calls** — HTTP/gRPC/GraphQL calls discovered in code
- **Cross-subproject edges** — links between client calls and endpoints

Each subproject is bound to a project via `project_root`. A project can have multiple subprojects (frontend, backend, etc.), and the same subproject can belong to multiple projects.

The **decision memory database** (`decisions.db`) is also shared across all projects. It stores:
- **Decisions** — architectural decisions, tech choices, bug root causes, preferences, etc., each with temporal validity (`valid_from`/`valid_until`) and optional code linkage (`symbol_id`, `file_path`, `service_name`)
- **Session chunks** — chunked conversation content from AI session logs, FTS5-indexed for cross-session search
- **Mined sessions tracker** — prevents re-processing already-mined session files

Decisions are auto-enriched into code intelligence tool responses (`get_change_impact`, `plan_turn`, `get_wake_up`) via the enrichment layer in `src/memory/enrichment.ts`.

---

## Plugin system

Plugins are the core extensibility mechanism. There are two types:

### Language plugins

Located in `src/indexer/plugins/language/`. Each plugin handles symbol extraction for one language using tree-sitter. Depth varies by plugin — see the [language capability matrix](language-matrix.md) for which ones resolve import, call and type edges, and [supported frameworks](supported-frameworks.md) for the integration layer on top.

Registered plugins: PHP, TypeScript/JavaScript, Vue, Python, Go, Java, Kotlin, Ruby, HTML, CSS.

### Integration plugins

Located in `src/indexer/plugins/integration/`, organized by category:

| Category | Plugins | What they do |
|---|---|---|
| `framework/` | Laravel, Django, Rails, Spring, NestJS, Express, FastAPI, Flask, Hono, Fastify, Nuxt, Next.js | Route, controller, middleware extraction |
| `orm/` | Prisma, TypeORM, Sequelize, Mongoose, SQLAlchemy, Drizzle | Model, relationship, migration extraction |
| `view/` | React, Vue, React Native, Blade, Inertia, shadcn, MUI, Ant Design, Headless UI, Nuxt UI | Component tree, prop, render analysis |
| `api/` | GraphQL, tRPC, DRF | Schema, endpoint, resolver extraction |
| `validation/` | Zod, Pydantic | Schema definition extraction |
| `state/` | Zustand | Store, action, selector extraction |
| `realtime/` | Socket.io | Event handler, namespace extraction |
| `testing/` | Testing | Test suite, fixture, coverage analysis |
| `tooling/` | Celery, n8n, data-fetching | Task, workflow, query hook extraction |

### Plugin interface

Every integration plugin implements `FrameworkPlugin`:

```typescript
interface FrameworkPlugin {
  manifest: PluginManifest;           // name, version, priority, dependencies
  detect(ctx: ProjectContext): boolean; // returns true if framework detected
  registerSchema(): NodeTypes & EdgeTypes; // declares symbol/edge types
  extractNodes?(filePath, content, language): FileParseResult; // extract symbols
  resolveEdges?(ctx: ResolveContext): RawEdge[]; // resolve inter-symbol edges
}
```

Detection runs once on startup. Only plugins whose `detect()` returns `true` participate in indexing.

Plugins are loaded in topological order (respecting dependencies) and by priority (lower = earlier).

---

## Module resolution

Three module resolvers handle cross-file imports:

| Resolver | Languages | What it resolves |
|---|---|---|
| **ES modules** | TypeScript, JavaScript, Vue | `import` / `require` with tsconfig paths, barrel exports |
| **PSR-4** | PHP | Namespace-based autoloading per `composer.json` |
| **Python modules** | Python | Relative/absolute imports, `__init__.py` packages |

---

## Scoring & ranking

`src/scoring/` contains algorithms for ranking search results and context assembly:

- **BM25** — full-text relevance via FTS5
- **PageRank** — symbol importance based on the dependency graph
- **Hybrid scoring** — combines BM25 + graph signals
- **Structured assembly** — assembles context within a token budget, maximizing coverage

Alongside the code graph, trace-mcp keeps a second store of *why* the code is
shaped this way — see [decision memory](decision-memory.md). Writing a new
plugin for either pass is covered in [development](development.md#adding-a-new-language-plugin).

---

## Tech stack

| Component | Technology |
|---|---|
| Parsing | [tree-sitter](https://tree-sitter.github.io/) (PHP, TS, Python, Go, Java, Kotlin, Ruby, HTML, CSS), [@vue/compiler-sfc](https://github.com/vuejs/core) |
| Database | [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) — WAL mode, FTS5, vector storage |
| Module resolution | [oxc-resolver](https://github.com/nicolo-ribaudo/oxc-resolver) (ESM/CJS), PSR-4, Python modules |
| AI | Ollama / OpenAI — embeddings, summarization, reranking, inference caching |
| Validation | [Zod](https://zod.dev) — config + input validation |
| Error handling | [neverthrow](https://github.com/supermacro/neverthrow) — Rust-style `Result<T, E>` |
| Logging | [pino](https://getpino.io/) — structured JSON logging |
| MCP | [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk) |
| Build | tsup · vitest · TypeScript 5.7 |

---

## Project structure

```
src/
├── ai/                     # Embeddings, reranker, summarization, vector store, inference caching
├── db/                     # SQLite schema, store, FTS5
├── subproject/             # Subproject layer (subprojects = services, bound to projects)
│   ├── manager.ts          #   Add/remove/sync subprojects, auto-discover projects, cross-subproject impact
│   └── scanner.ts          #   HTTP/gRPC/GraphQL client call scanner
├── topology/               # Cross-service topology layer
│   ├── topology-db.ts      #   Topology + subproject SQLite store (subprojects bound to projects via project_root)
│   ├── contract-parser.ts  #   OpenAPI, GraphQL SDL, Protobuf parsers
│   └── service-detector.ts #   Subproject discovery (Docker Compose, flat/grouped workspace, monolith fallback)
├── indexer/
│   ├── plugins/
│   │   ├── language/       # {{ site.data.counts.languages }} languages — PHP, TS, Vue, Python, Go, Java, Kotlin, Ruby, Rust,
│   │   │                   #   C/C++/C#, Swift, Dart, Scala, Zig, OCaml, Clojure, F#, Elm,
│   │   │                   #   CUDA, COBOL, Verilog, GLSL, Svelte, MATLAB, Lean, Wolfram, …
│   │   └── integration/    # 85 plugins organized by category:
│   │       ├── framework/  #   Laravel, Django, Rails, Spring, NestJS, Express, FastAPI,
│   │       │               #   Flask, Hono, Fastify, Nuxt, Next.js
│   │       ├── orm/        #   Prisma, TypeORM, Sequelize, Mongoose, SQLAlchemy, Drizzle
│   │       ├── view/       #   React, Vue, React Native, Blade, Inertia, shadcn, MUI,
│   │       │               #   Ant Design, Headless UI, Nuxt UI
│   │       ├── api/        #   GraphQL, tRPC, DRF
│   │       ├── validation/ #   Zod, Pydantic
│   │       ├── state/      #   Zustand
│   │       ├── realtime/   #   Socket.io
│   │       ├── testing/    #   Playwright, Cypress, Jest, Vitest, Mocha
│   │       └── tooling/    #   Celery, n8n, data-fetching
│   ├── resolvers/          # PSR-4, ES module, Python module resolution
│   ├── pipeline.ts         # Two-pass indexing engine
│   ├── watcher.ts          # File change watcher
│   └── monorepo.ts         # Monorepo workspace detection
├── memory/                 # Decision memory (cross-session knowledge graph)
│   ├── decision-store.ts   #   SQLite store: decisions + session chunks + FTS5
│   ├── conversation-miner.ts # Pattern-based decision extraction from JSONL logs
│   ├── session-indexer.ts  #   Chunked session content indexer for search
│   ├── wake-up.ts          #   L0/L1/L2 wake-up context assembler
│   ├── enrichment.ts       #   Decision injection into code intelligence results
│   └── index.ts            #   Barrel export
├── analytics/              # Session analytics engine
│   ├── log-parser.ts       #   JSONL parser (Claude Code + Claw Code)
│   ├── analytics-store.ts  #   SQLite storage for parsed sessions
│   ├── sync.ts             #   Incremental session log sync
│   ├── session-analytics.ts #  Analytics query facade
│   ├── rules.ts            #   8 optimization rules (repeated reads, bash-grep, etc.)
│   ├── real-savings.ts     #   Real savings analysis (Read vs get_symbol)
│   ├── benchmark.ts        #   Synthetic benchmark (5 scenarios)
│   ├── tech-detector.ts    #   Manifest parser + coverage assessment
│   └── known-packages.ts   #   Catalog of ~200 known packages
├── tools/                  # 170 MCP tool implementations
├── scoring/                # PageRank, BM25, hybrid scoring, structured assembly
├── plugin-api/             # Plugin registry, loader, executor, test harness
├── init/                   # Setup & detection (Claude Code, Claw Code, Cursor, Windsurf, Continue)
├── utils/                  # Env parser, hasher, security, source reader, token counter
├── server.ts               # MCP server factory
├── config.ts               # Cosmiconfig + Zod validation
├── errors.ts               # Error types (neverthrow)
├── logger.ts               # Pino logger setup
├── cli.ts                  # Commander CLI (serve, serve-http, index, subproject, analytics)
├── cli-analytics.ts        # Analytics CLI subcommands (sync, report, optimize, benchmark, coverage, savings, trends)
└── cli-subproject.ts       # Subproject CLI subcommands (add --project, list --project, etc.)
```

---

# Supported frameworks & languages

Source: https://trace-mcp.com/supported-frameworks.html


## Languages ({{ site.data.counts.languages }})

The list below says which languages are supported. It does not say how deep
each one goes, and the spread is wide — some get a full AST and a call graph,
others get symbol extraction only. The [language capability
matrix](language-matrix.md) has the per-language breakdown: parser, import
edges, call edges, type edges, and whether a test covers the plugin. Detection
is automatic, but a language whose files sit outside the default globs needs an
`include` entry — see [configuration](configuration.md#options). The tools a
detected framework unlocks are marked framework-gated in the [tools
reference](tools-reference.md).

### Tree-sitter (full AST parsing)

| Language | What's extracted |
|---|---|
| **PHP** | Classes, interfaces, traits, enums, functions, methods, properties, constants, namespaces |
| **TypeScript / JavaScript** | Functions, classes, variables, types, interfaces, enums, exports, JSX/TSX |
| **Python** | Functions, classes, decorators, attributes, module variables |
| **Go** | Functions, methods, types (structs, interfaces), constants, variables, packages |
| **Java** | Classes, interfaces, enums, annotation types, methods, fields |
| **Kotlin** | Classes, functions, properties |
| **Ruby** | Classes, modules, methods, constants |
| **Rust** | Functions, structs, enums, traits, impl blocks, macros, constants, modules |
| **C** | Functions, structs, enums, unions, typedefs, macros, global variables |
| **C++** | Classes, structs, namespaces, enums, functions, methods, templates, type aliases |
| **C#** | Namespaces, classes, interfaces, structs, enums, records, delegates, methods, properties |
| **Scala** | Classes, objects, traits, enums, case classes, methods, vals, type aliases, given instances |
| **Vue SFC** | Components, script setup symbols, template analysis |
| **HTML** | Script/link references, meta tags, form elements, custom elements |
| **Astro** | Components, frontmatter script block, props |
| **Prisma schema** | Models, fields, relations, enums |
| **GraphQL** | Types, queries, mutations, resolvers, fragments |
| **Solidity** | Contracts, functions, modifiers, events, structs |

### Regex-based (symbol extraction)

| Language | What's extracted |
|---|---|
| **CSS / SCSS / SASS / LESS** | Custom properties, classes, IDs, mixins, keyframes, font-face |
| **Swift** | Classes, structs, enums, protocols, functions, properties, typealiases |
| **Dart** | Classes, mixins, enums, functions, getters/setters, factory constructors |
| **Objective-C** | Classes, protocols, methods (full selectors), properties, C functions |
| **Elixir** | Modules, functions, macros, guards, type specs, callbacks |
| **Erlang** | Modules, exported functions, records, macros, type specs |
| **Haskell** | Modules, data types, type classes, type signatures, instances |
| **Gleam** | Functions, types, constants |
| **Scala** | Packages, case classes, objects, traits, enums, defs, vals |
| **Groovy** | Classes, interfaces, enums, traits, methods |
| **Bash** | Functions, readonly/exported constants |
| **Lua** | Functions, module methods, local variables |
| **Perl** | Subroutines, packages |
| **GDScript** | Functions, classes, enums, signals, constants, variables |
| **R** | Functions, S4 classes/generics/methods |
| **Julia** | Functions, structs, modules, macros, constants |
| **Nix** | Attribute bindings, function definitions |
| **SQL** | Tables, views, functions, procedures, triggers, CTEs, schemas, types |
| **HCL / Terraform** | Resources, data sources, modules, variables, outputs, providers |
| **Protocol Buffers** | Messages, enums, services, RPCs |
| **XML / XUL / XSD** | Root element, id/name attributes, namespaces, XSD types, XSLT templates |
| **YAML** | Top-level keys |
| **JSON** | First-level keys |
| **TOML** | Tables, array-of-tables, key-value bindings |
| **Assembly** | Labels, procedures, macros, equates, sections, directives |
| **Fortran** | Subroutines, functions, modules, programs, types |
| **AutoHotkey** | Functions, classes, static methods |
| **Verse (UEFN)** | Classes, methods, properties, variables |
| **AL (Business Central)** | Tables, pages, codeunits, enums, procedures, triggers |
| **Blade (Laravel)** | Sections, components, slots, includes, extends |
| **EJS** | Functions, constants (from scriptlet blocks) |
| **Zig** | Functions, structs, enums, unions, constants, variables, test declarations |
| **OCaml** | Let/val bindings, type definitions, modules, classes, exceptions |
| **Clojure** | defn, def, defmacro, defprotocol, defrecord, deftype, ns declarations |
| **F#** | Let bindings, members, type definitions, modules, exceptions |
| **Elm** | Functions, type aliases, custom types, ports, modules |
| **CUDA** | Kernels (__global__), device/host functions, structs, __constant__/__shared__ vars |
| **COBOL** | PROGRAM-ID, divisions, sections, paragraphs, data items (01–77 levels), conditions (88) |
| **Verilog / SystemVerilog** | Modules, interfaces, packages, classes, functions, tasks, parameters |
| **GLSL** | Functions, structs, uniforms, varyings, layout qualifiers, constants |
| **Meson** | Projects, executables, libraries, dependencies, custom targets, subdirs |
| **Vim Script** | Functions, commands, variables (g:/s:/b:), augroups |
| **Common Lisp** | defun, defmacro, defclass, defstruct, defpackage, defvar, defconstant |
| **Emacs Lisp** | defun, defmacro, defvar, defcustom, defconst, define-*-mode, defgroup |
| **Dockerfile** | FROM stages, ARG/ENV declarations, EXPOSE ports, ENTRYPOINT/CMD |
| **Makefile** | Targets, variable definitions, define blocks, .PHONY |
| **CMake** | Functions, macros, projects, add_executable/add_library, options |
| **INI / Config** | Sections, key-value pairs (.ini, .cfg, .conf, .properties, .editorconfig) |
| **Svelte** | Props (export let / $props), reactive declarations, runes ($state/$derived), snippets |
| **Markdown** | Headings (h1–h4), link definitions, code block languages |
| **MATLAB / Octave** | Functions, classdef, properties blocks, global/persistent variables |
| **Lean 4** | def, theorem, lemma, structure, class, inductive, abbrev, namespace |
| **FORM** | Symbols, indices, vectors, functions, tables, procedures, modules |
| **Magma** | Functions, procedures, intrinsics, types, records |
| **Wolfram / Mathematica** | Function definitions (SetDelayed), packages, usage strings, options |
| **Pascal / Delphi** | Units, classes, interfaces, procedures, functions |
| **Ada** | Packages, procedures, functions, types, tasks |
| **PowerShell** | Functions, cmdlets, modules, parameters |
| **Apex** | Classes, interfaces, triggers, methods |
| **PL/SQL** | Packages, procedures, functions, triggers, types |
| **Nim** | Procs, types, modules, templates |
| **Tcl** | Procs, namespaces, variables |
| **D** | Modules, classes, structs, functions |

---

## Backend frameworks

| Framework | What's extracted |
|---|---|
| **Laravel** | Routes, controllers, Eloquent relations, migrations, FormRequests, events/listeners, middleware, broadcasting |
| **Laravel Livewire** | Components, properties, actions, events, views, child components |
| **Laravel Nova** | Resources, fields, actions, filters, lenses, metrics |
| **Filament** | Resources, relation managers, panels, widgets |
| **Spatie Laravel Data** | Data objects, transformations |
| **Laravel Pennant** | Feature flag definitions |
| **Django** | Models, URL patterns, views (CBV + FBV), admin registrations, signals, forms |
| **Django REST Framework** | Serializers, ViewSets, API endpoints |
| **FastAPI** | Route definitions, path/query parameters, request models |
| **Flask** | Routes, blueprints, request handlers |
| **Express** | Routes, middleware, error handlers, param handlers |
| **NestJS** | Controllers, modules, services, decorators, DI tree |
| **Fastify** | Routes, hooks, plugins |
| **Hono** | Routes, middleware |
| **Next.js** | API routes, pages, `getServerSideProps`, `getStaticProps` |
| **Rails** | Routes, controllers, models, migrations, associations |
| **Spring** | Beans, controllers, services, JPA entities |
| **tRPC** | Routers, procedures, type definitions |

---

## Frontend frameworks

| Framework | What's extracted |
|---|---|
| **Vue** | Components (Options + Composition API), `defineProps`, `defineEmits`, composables, render trees |
| **Nuxt** | File-based routing, auto-imports, `useFetch` / `useAsyncData`, server API routes, layouts, middleware |
| **React** | Components (functional + class), hooks, props |
| **React Native** | Native components, navigation patterns, screens, deep links, platform variants |
| **Blade** | `@extends`, `@include`, `@component`, `<x-*>` directives, template inheritance |
| **Inertia.js** | `Inertia::render()` calls, controller ↔ Vue page mapping, prop extraction & validation |

---

## UI component libraries

| Library | What's extracted |
|---|---|
| **shadcn/ui** | Component registry, CVA/TV variant definitions, Radix primitive composition, sub-component exports |
| **Nuxt UI** | Theme overrides from `app.config.ts`, UForm schemas, Tailwind Variants (`tv()`) definitions, color mode |
| **Material-UI** | Component usage, theme customization |
| **Ant Design** | Component usage patterns |
| **Headless UI** | Unstyled component composition |

---

## Data & ORM

| Library | What's extracted |
|---|---|
| **Eloquent** (Laravel) | Models, relationships, scopes, casts, schema from migrations |
| **Prisma** | Data models from `schema.prisma`, relations |
| **TypeORM** | Entities, relations, repositories |
| **Drizzle** | Schema definitions, table relations |
| **Sequelize** | Models, associations, migrations |
| **Mongoose** | Schemas, models, middleware |
| **SQLAlchemy** | ORM models, relationships, columns, constraints |

---

## Validation & schema

| Library | What's extracted |
|---|---|
| **Zod** | Schema definitions, type inference |
| **Pydantic** | BaseModel subclasses, field types, ORM mode references |

---

## API & realtime

| Library | What's extracted |
|---|---|
| **GraphQL** | Schemas, resolvers, type definitions |
| **Socket.io** | Event handlers, namespaces, rooms |

---

## State management

| Library | What's extracted |
|---|---|
| **Zustand** | Store definitions, actions, selectors |

---

## Tooling & automation

| Plugin | What's extracted |
|---|---|
| **Celery** | Task definitions, routing, schedules |
| **n8n** | Workflow nodes, connections, parameters, credentials |
| **Data fetching** | React Query, SWR — query hooks, mutations, cache config |
| **Testing** | Playwright, Cypress, Jest, Vitest, Mocha — test suites, fixtures |

---

# Session Analytics & Coverage Intelligence

Source: https://trace-mcp.com/analytics.html


trace-mcp includes a built-in analytics engine that parses AI agent session logs, tracks token savings, detects wasteful patterns, and assesses technology coverage. It measures what your sessions actually cost; [cutting Claude Code token usage](reduce-claude-code-token-usage.md) is the list of levers to pull once the report names the waste, and the [PR review context benchmark](pr-context-benchmark.md) is the same measurement run on somebody else's repositories.

---

## How it works

```
Session logs (JSONL)                   Project manifests
  Claude Code: ~/.claude/projects/       package.json, composer.json,
  Claw Code: <project>/.claw/sessions/  requirements.txt, go.mod, ...
         │                                        │
         ▼                                        ▼
┌─────────────────────────┐         ┌──────────────────────────┐
│  Log Parser             │         │  Tech Detector           │
│  Extracts:              │         │  Parses manifests,       │
│  - tool calls + results │         │  classifies deps,        │
│  - token usage          │         │  matches against         │
│  - model info           │         │  known-packages catalog  │
│  - target files         │         │  (~200 packages)         │
└──────────┬──────────────┘         └──────────┬───────────────┘
           │                                   │
           ▼                                   ▼
┌─────────────────────────┐         ┌──────────────────────────┐
│  Analytics DB (SQLite)  │         │  Coverage Report         │
│  ~/.trace-mcp/          │         │  covered / gaps /        │
│    analytics.db         │         │  unknown deps            │
│  Tables:                │         └──────────────────────────┘
│  - sessions             │
│  - tool_calls           │
│  - sync_state           │
└──────────┬──────────────┘
           │
     ┌─────┴──────┬──────────────┬─────────────────┐
     ▼            ▼              ▼                  ▼
 Analytics    Optimization    Real Savings       Benchmark
 Report       Report          Analysis           Engine
 (per tool,   (8 rules,       (Read vs           (synthetic,
  per file,    savings est.)   get_symbol)        5 scenarios)
  per model)
```

### Supported clients

| Client | Session log location | Config files |
|--------|---------------------|--------------|
| **Claude Code** | `~/.claude/projects/<encoded-path>/<session-id>.jsonl` | `CLAUDE.md`, `.claude/settings.json` |
| **Claw Code** | `<project>/.claw/sessions/<session-id>.jsonl` | `.claw.json`, `.claw/settings.json` |

Both formats are auto-detected during sync. No configuration needed.

### Local-machine scoping

`get_session_analytics`, `get_optimization_report`, `get_real_savings`, and `analyze_perf` (for `window` other than `"session"`) only see session logs that physically exist on the machine running the MCP server — they read `~/.claude/projects/<encoded-path>/` and `<project>/.claw/sessions/` directly, they do not fetch data from any other machine. If you invoke them from a fresh checkout, a remote/cloud agent runtime, or a CI sandbox that never ran a local Claude Code / Claw Code session for this project, there is nothing to find and the tools report that. `get_session_analytics`/`get_optimization_report`/`get_real_savings` distinguish this from "checked, nothing to report" by adding a `_warnings` field when both the discoverable log files and the aggregated result are empty. `analyze_perf` with a persistent `window` additionally requires `telemetry.enabled: true` in [config](configuration.md) (off by default) and returns an explicit `error` when it's off — that flag is the same span emitter documented under [MCP tracing](telemetry.md).

### JSONL format differences

| | Claude Code | Claw Code |
|--|-------------|-----------|
| Record types | `{type: "assistant"}`, `{type: "user"}` | `{type: "message"}` with `message.role` |
| Tool result delivery | Embedded in `user` message | Separate `tool` role message |
| Tool input format | JSON object | JSON string (parsed automatically) |
| Session metadata | `timestamp`, `sessionId` on each record | `{type: "session_meta"}` header record |

---

## MCP Tools

### `get_session_analytics`

Token usage, cost breakdown by tool/server, top files, models used. Auto-syncs logs before querying.

```
get_session_analytics({ period?: "today" | "week" | "month" | "all" })
```

Returns: session count, total tokens (input/output/cache), estimated cost, breakdown by tool server (builtin, trace-mcp, jcodemunch, phpstorm, ...), top tools by token output, top files by read tokens, models used.

### `get_optimization_report`

Detects wasteful tool call patterns and recommends trace-mcp alternatives.

```
get_optimization_report({ period?: "today" | "week" | "month" | "all" })
```

**8 built-in rules:**

| Rule | Severity | Detects | Recommends |
|------|----------|---------|------------|
| `repeated-file-read` | high | Same file Read 3+ times per session | `get_outline` + `get_symbol` |
| `bash-grep` | high | `Bash` with grep/rg/ack commands | `search` tool |
| `bash-cat` | medium | `Bash` with cat/head/tail commands | `get_symbol` or `Read` |
| `large-file-read` | medium | `Read` with output > 5000 chars | `get_outline` → `get_symbol` |
| `phpstorm-read-indexed` | medium | PhpStorm file read on indexed files | `get_symbol` |
| `phpstorm-search-indexed` | medium | PhpStorm text search on indexed project | `search` |
| `unused-trace-tools` | low | Sessions without trace-mcp but with Read/Grep | Enable trace-mcp tools |
| `agent-for-indexed` | medium | Agent subagent calls (~50K tokens each) | `get_feature_context` / `get_task_context` |

### `get_real_savings`

Analyzes actual session logs to compute how much could be saved by using trace-mcp instead of raw file reads. For each `Read`/`Bash cat`/PhpStorm read, finds the file in the index and estimates the compact alternative cost.

```
get_real_savings({ period?: "today" | "week" | "month" | "all" })
```

Returns: per-file breakdown (reads, current tokens, alternative tokens, savings %), tool replacement stats, and A/B comparison (sessions with vs without trace-mcp).

### `benchmark_project`

Synthetic benchmark comparing raw file reads vs trace-mcp compact responses.

```
benchmark_project({ queries?: number, seed?: number, format?: "json" | "markdown" })
```

**5 scenarios:** symbol lookup, file exploration, search, impact analysis, call graph. Uses actual index data with seeded randomness for reproducibility.

### `get_startup_context_audit`

What the session startup block is made of and what it costs — the context every session pays for before your first message: the harness system prompt, tool schemas, MCP servers, the skill and agent listings, and SessionStart hook output.

```
get_startup_context_audit()
```

No parameters: the look-back window is fixed at 30 days. A parameter here would have to survive `compact_schemas`, which strips non-core params from the schema and so freezes them at their default without saying so.

Returns: the block's size distribution across fresh sessions, a decomposition by source (hooks are named individually), the block's share of the input-side bill, the mid-session cache rebuilds that make it get paid twice and what each cost, MCP servers present at startup alongside how often they were actually called, the instruction files on disk, and `recommendations` — suggestions with a per-start token price and a cost over the window.

Every recommendation rests on **evidence of non-use over a stated observation window**, never on size: an MCP server whose instructions loaded into N startups and whose tools were never called, a skill listed at every start and never invoked, text duplicated between the global and project instruction files. A tool that is missing from the startup block is a tool the agent will not call, so a suggestion made because something is *big* can cost its reader far more than it saves. SessionStart hooks are deliberately excluded from suggestions for the same reason — nothing in the log says whether the model used a hook's output, so there is no evidence of non-use to stand on. They stay in the decomposition, where the reader sees the cost and decides.

Everything is computed locally from `~/.claude/projects/*.jsonl`; nothing leaves the machine. The system prompt, tool schemas and CLAUDE.md are never written to the session log, so they are reported together as one residual row rather than split apart — the payload's `notes` says so too.

#### `textCompression` — where the block says the same thing twice

The audit answers "what does the block cost and what in it went unused". The `textCompression` field on the same payload answers the other half — of the text that is *needed* and stays, how much of it is a rule you already receive from somewhere else?

It rides along on this tool rather than being one of its own: it is the same question, and a second parameterless tool would add schema chars to every session that lists tools while diluting the `compact_schemas` reduction documented in [configuration](configuration.html).

It compares your own `CLAUDE.md`, `AGENTS.md` and `MEMORY.md` against the instruction text that MCP servers, the skill listing and SessionStart hooks *actually sent* at startup — read from the most recent session log of the project you are in — and proposes deletions with a unified diff and a per-session token delta.

**Nothing is written, and nothing is reworded.** The invariant, which the payload states and the tests enforce:

> a line is only proposed for removal when **every sentence on it** is still delivered by another source in the same startup block, and each removal cites that source per sentence. A heading goes only once its whole body has.

The word doing the work is *every*. An earlier version removed a line once 60% of its characters were matched and validated that with a "some sentence matched" check — which deletes the other 40%, text nothing else says. Two independent reviews reproduced it. Any threshold below "every unit" reintroduces it, so the rule is universal and the report proposes less rather than guessing.

That is also why this is not an LLM rewrite. On real instruction files the compressible mass is not verbose prose, it is restatement across sources: a `CLAUDE.md` section that repeats, in the author's own words, a rule an MCP server already sends. Dropping the second copy leaves the instruction present, verbatim, in the block — which is what makes "the meaning survived" checkable instead of a matter of taste.

Matching is on sentences and word overlap, with three guards:

- **Polarity.** "Do not run tests in parallel" and "Run tests in parallel" share every content word and are opposite instructions. A prohibition is never removed on the evidence of a permission.
- **Values.** Digits are kept and compared, so `Node 22` does not prove `Node 18`.
- **Short sentences.** Below five content words, partial overlap means nothing — `Never push to main` and `Never push to prod` are mostly alike — so a short rule must be near-identical to its evidence.

Evidence comes from **one** startup block, not a union across sessions: a server configured last month and removed since must not prove that today's file repeats it. It is scoped to the project, because another project's servers and hooks are not evidence about this one's session.

Text it does not own is never edited — a third party's skill descriptions, another server's instructions, a plugin hook's output. Those are the reference corpus: read to prove duplication, reported in `notCompressible` with their size and the reason.

Measured payload on real projects: 200–1400 tokens, with the diff capped at 200 lines; every removal is listed in `removals` regardless.

Applying a `textCompression` proposal — with a backup and one-action rollback — is separate work; this only shows you the diff. (`recommendations[]`, below, is a different payload field and does have an apply path.)

### `apply_startup_recommendations` / `rollback_startup_recommendations`

The apply half of `recommendations[]` (TRA-769). Every recommendation kind maps to configuration the user could edit themselves:

| `kind` | What applying it does |
|---|---|
| `unusedMcpServer` | Removes the server's entry from `mcpServers` in `~/.claude/settings.json`, `~/.claude.json`, or the project's `.mcp.json` — whichever actually has it |
| `unusedSkill` | Moves the skill's directory out of `~/.claude/skills` (or the project's `.claude/skills`) into a `.trace-mcp-disabled-skills` sibling of `skills/`, so it stops being discovered |
| `duplicateInstructions` | Deletes the lines the project instruction file shares with the global one, leaving the global file untouched |

A plugin-namespaced skill (its name contains `:`) is always skipped — one unused skill is not evidence for disabling the plugin that ships it.

```
apply_startup_recommendations({
  requests: [{ kind: "unusedMcpServer", target: "idle-server" }],
  dry_run?: boolean, // default true
})
```

`requests` takes the recommendation's own `kind` and `target` verbatim — one entry per recommendation you want acted on, never "apply all". With `dry_run` at its default (`true`), nothing is written: each outcome reports `status: "wouldApply"` or `"skipped"` (with why), and `duplicateInstructions` includes the same kind of deletion-only diff `textCompression` uses, plus the token count it would remove. Pass `dry_run: false` to actually write — every file is backed up before it's touched, bundled into one backup per call.

```
rollback_startup_recommendations({ backup_id?: string })
```

Restores everything one `apply_startup_recommendations(dry_run: false)` call wrote, byte-for-byte, in a single action: files go back to their exact prior bytes, moved skill directories move back. Defaults to the most recent backup. Backups live under `~/.trace/startup-backups/`, one directory per apply call, and are never deleted automatically — rolling back twice is a safe no-op, not a second undo.

Re-run `get_startup_context_audit` afterward to see the new size rather than trusting the delta.

### `get_coverage_report`

Technology profile — which dependencies are covered by trace-mcp plugins and which are not.

```
get_coverage_report()
```

Parses: `package.json`, `composer.json`, `requirements.txt`, `pyproject.toml`, `go.mod`, `Gemfile`. Classifies each dependency by category (framework/orm/ui/testing/infra/utility) and priority (high/medium/low/none). Reports covered deps, gaps, and unknowns.

### `get_usage_trends`

Daily token usage trends over time.

```
get_usage_trends({ days?: number })
```

Returns daily breakdown: sessions, tokens, estimated cost, tool calls. Good for spotting cost spikes and tracking optimization progress.

### `get_session_stats`

Real-time token savings of the current trace-mcp session (in-memory tracker, no log parsing).

```
get_session_stats()
```

### `audit_config`

Audit AI agent config files (CLAUDE.md, .cursorrules, .claw.json, etc.) for stale references, dead paths, token bloat, scope leaks, and redundancy.

```
audit_config()
```

---

## CLI Commands

All analytics commands are under `trace analytics` (supports `trace-mcp analytics` alias):

```bash
# Sync session logs into analytics DB
trace analytics sync [--full]

# Token usage report
trace analytics report [--period today|week|month|all] [--format text|json]

# Optimization recommendations
trace analytics optimize [--period today|week|month|all] [--format text|json]

# Real savings analysis
trace analytics savings [--period today|week|month|all] [--format text|json]

# Synthetic benchmark
trace analytics benchmark [--queries 10] [--seed 42] [--format text|json|markdown]

# Technology coverage
trace analytics coverage [--format text|json]

# Usage trends
trace analytics trends [--days 30] [--format text|json]
```

---

## Storage

Analytics data lives in `~/.trace/analytics.db` (or `~/.trace-mcp/analytics.db` fallback, separate from project indexes):

```sql
sessions       — one row per parsed session (tokens, model, timestamps)
tool_calls     — one row per tool call (name, server, output size, target file)
sync_state     — file paths + mtime for incremental sync
```

Session savings (in-memory tracker) persist to `~/.trace/savings.json` (or `~/.trace-mcp/savings.json`).

### Incremental sync

`analytics sync` only re-parses files whose mtime has changed since last sync. Use `--full` to force a complete rescan. Sync runs automatically before every analytics tool call.

---

## Example output

### `trace-mcp analytics report`

```
📊 Session Analytics (week)

Sessions: 24
Tool calls: 1203
Input tokens: 346,523
Output tokens: 1,200,000
Cache read: 8,500,000
Estimated cost: $61.86

Top tools:
  Read: 380 calls (~350,000 tokens)
  Bash: 290 calls (~95,000 tokens)
  Edit: 180 calls (~12,000 tokens)
  mcp__trace-mcp__search: 45 calls (~8,000 tokens)

Top files:
  src/server.ts: 35 reads (~45,000 tokens)
  src/db/store.ts: 22 reads (~32,000 tokens)
```

### `trace-mcp analytics optimize`

```
🔍 Optimization Report (week)

Current usage: 1,200,000 tokens (~$6.00)

[high] repeated-file-read: 85 occurrences
  Current: 350,000 tokens → Potential: 70,000 tokens
  Savings: 280,000 tokens (80%)
  Use get_outline + get_symbol instead of reading the full file repeatedly.

[high] bash-grep: 42 occurrences
  Current: 95,000 tokens → Potential: 19,000 tokens
  Savings: 76,000 tokens (80%)
  Use trace-mcp search tool instead of Bash grep/rg.

Total potential savings: 400,000 tokens (~$2.00, 33%)
```

### `trace-mcp analytics benchmark`

```
⚡ Token Efficiency Benchmark

Project: /Users/me/my-app
Index: 651 files, 3342 symbols

symbol_lookup: 41,211 → 2,098 tokens (94.9% reduction)
file_exploration: 16,366 → 762 tokens (95.3% reduction)
search: 22,860 → 8,000 tokens (65.0% reduction)
impact_analysis: 96,717 → 4,841 tokens (95.0% reduction)
call_graph: 178,661 → 10,723 tokens (94.0% reduction)
composite_task: 71,076 → 2,033 tokens (97.1% reduction)

Total: 426,891 → 28,457 (93.3% reduction)
```

---

# PR Review Context Benchmark

Source: https://trace-mcp.com/pr-context-benchmark.html



Every claim about token reduction on this site used to rest on trace-mcp's own
internal estimators — the [session analytics](analytics.md) numbers, measured by
the tool on itself. That is not good enough for anyone outside the project.
This page is the measurement on somebody else's code: **{{ site.data.pr_context_bench.pr_count }}
real merged pull requests** across **{{ site.data.pr_context_bench.repo_count }}**
open-source repositories, with the PR numbers and commit SHAs pinned in the
repo so the run reproduces.

## TL;DR

Assembling review context for a pull request with trace-mcp costs a median
**{{ site.data.pr_context_bench.median_savings_pct }}% fewer input tokens** than
loading the diff plus every file it touches — while making *more* of the code
the change can break visible, not less.

| | naive file loading | trace-mcp |
|---|---:|---:|
| input tokens, median | {{ site.data.pr_context_bench.baseline_median_tokens }} | **{{ site.data.pr_context_bench.trace_median_tokens }}** |
| input tokens, p90 | {{ site.data.pr_context_bench.baseline_p90_tokens }} | **{{ site.data.pr_context_bench.trace_p90_tokens }}** |
| input tokens, worst case | {{ site.data.pr_context_bench.baseline_max_tokens }} | **{{ site.data.pr_context_bench.trace_max_tokens }}** |
| cost per PR, median | ${{ site.data.pr_context_bench.baseline_median_cost }} | **${{ site.data.pr_context_bench.trace_median_cost }}** |
| cost per PR, p90 | ${{ site.data.pr_context_bench.baseline_p90_cost }} | **${{ site.data.pr_context_bench.trace_p90_cost }}** |
| changed symbols readable | {{ site.data.pr_context_bench.baseline_changed_symbol_readable }} | {{ site.data.pr_context_bench.trace_changed_symbol_readable }} |
| affected call sites readable | {{ site.data.pr_context_bench.baseline_dependent_readable }} | **{{ site.data.pr_context_bench.trace_dependent_readable }}** |
| affected call sites at least located | {{ site.data.pr_context_bench.baseline_dependent_pointed }} | **{{ site.data.pr_context_bench.trace_dependent_pointed }}** |

Measured at trace-mcp **{{ site.data.pr_context_bench.measured_build.version }}
(`{{ site.data.pr_context_bench.measured_build.commit }}`)** on
{{ site.data.pr_context_bench.generated_at | date: "%-d %B %Y" }}{% if site.data.measurements.pr_context.historical %} — published as a result from that
build, not as a claim about the current one{% endif %}. What this run set out to
measure, the bar it had to clear and the verdict against that bar:
[preregistration]({{ '/perf/prereg-pr-context/' | relative_url }}).

Dollar figures are input tokens priced at `{{ site.data.pr_context_bench.model }}`,
${{ site.data.pr_context_bench.input_usd_per_mtok }} per million input tokens.
Indexing a repository costs a median {{ site.data.pr_context_bench.median_index_ms }} ms
per PR once the initial index exists, and is amortised across every query
against that repo.

## What was measured

The carrier task is **AI code review of a real pull request** — the most
token-hungry production pipeline in the code-agent market, and the one where
the entire cost is context assembly.

Two arms, same pull requests, same tokenizer (`gpt-tokenizer`, exact counts —
not a characters-over-four estimate), same prompt skeleton:

- **Naive file loading** — the review instructions, the unified diff, and the
  complete text of every source file the diff touches. This is what an agent
  without an index does.
- **trace-mcp** — the review instructions, the unified diff, then
  `get_changed_symbols` to resolve which indexed symbols the diff actually
  touched, `get_context_bundle` for those symbols with their dependencies and
  callers, and `get_change_impact` for the call sites the change can break.

Both contexts are assembled against the same commit — the PR head — because
that is the state a review agent has in front of it.

### Dataset

{{ site.data.pr_context_bench.pr_count }} merged, bug-fix-titled pull requests
from `honojs/hono`, `axios/axios`, `expressjs/express`, `psf/requests`,
`pallets/flask` and `sindresorhus/got` — TypeScript, JavaScript and Python.
Selection criteria, applied before any measurement:

- merged, with `fix` in the title (a review has something to look for);
- between 1 and 20 changed files (below that there is nothing to review; above
  it, no agent would attempt the naive arm and the pair stops being comparable);
- base and head SHAs resolvable, pinned in
  [`benchmarks/pr-context/dataset.json`](https://github.com/nikolai-vysotskyi/trace-mcp/blob/main/benchmarks/pr-context/dataset.json).

A further {{ site.data.pr_context_bench.skipped_count }} PRs were mined but
excluded at run time because the diff touched no indexed symbol at all —
documentation, lockfiles, CI config. Including them would have inflated the
headline: the trace-mcp arm for such a PR is nothing but the diff, so the
"saving" would be an artifact of there being no code to load.

### Reproducing it

```bash
git clone https://github.com/nikolai-vysotskyi/trace-mcp && cd trace-mcp
pnpm install
npx tsx scripts/bench-pr-context.ts        # writes benchmarks/pr-context/results.json
```

The script clones each upstream repo into `node_modules/.cache/pr-context/`,
checks out the pinned SHA, indexes it, and writes every per-PR row alongside
the aggregates. Every number on this page is rendered from
`docs/_data/pr_context_bench.json`, which that script generates — none of them
is typed by hand.

## Where trace-mcp did not pay off

A benchmark without this section is marketing. On this dataset
**{{ site.data.pr_context_bench.loss_count }} of {{ site.data.pr_context_bench.pr_count }} PRs**
were cases where the index barely earned its keep:

| PR | files | changed symbols | naive | trace-mcp | saved |
|---|---:|---:|---:|---:|---:|
{% for l in site.data.pr_context_bench.losses -%}
| [{{ l.url | split: "/" | slice: -3, 3 | join: "/" }}]({{ l.url }}) | {{ l.changed_files }} | {{ l.changed_symbols }} | {{ l.baseline_tokens }} | {{ l.trace_tokens }} | {{ l.savings_pct }}% |
{% endfor %}

They share a shape: a small change to one or two small files. When the whole
file is 200 lines, loading it outright is already cheap, and the symbol bodies
plus the impact list come to nearly the same size. `got#2379` is the extreme —
{{ site.data.pr_context_bench.losses[0].savings_pct }}% saved, which is noise.
**If your repository is small, or your PRs touch only small files, this index
does not solve a problem you have.** The saving scales with how much of a file
a reviewer does not need.

Two further limits worth stating plainly:

- **The truncation failure mode did not fire here.** The trace-mcp arm is
  capped at an 8,000-token context bundle; on this dataset no PR was large
  enough for that cap to drop a changed symbol, so changed-symbol readability
  is {{ site.data.pr_context_bench.trace_changed_symbol_readable }} in both
  arms. On a substantially larger PR it would bite, and the benchmark reports
  it as a `truncated` loss when it does. We have not measured that regime.
- **Call-site coverage is structural, not semantic.** "Readable" means the
  symbol's body is in the context; "located" means it is named with its file
  and line. It does not mean a model used it correctly.

## What this does not measure

**Review quality is not measured here.** The metrics on this page are
structural coverage of the code a reviewer needs, not an LLM's judgement about
whether it found the bug. Measuring that requires running a model over both
arms on all {{ site.data.pr_context_bench.pr_count }} PRs and scoring the
findings, which is a separate, paid experiment.

So the honest reading of this page is narrow and it is deliberately narrow:
**for the same review task on the same PRs, trace-mcp's context costs about a
tenth of the tokens and puts strictly more of the affected call graph in front
of the reviewer.** Whether that translates into catching more bugs is an open
question, and this benchmark is the harness a future run would extend to answer
it. For the levers that produce that difference — presets, compact schemas and
the TOON encoding — see [cutting Claude Code token usage](reduce-claude-code-token-usage.md).

## See also

- [TOON output format — measured token savings](toon-savings.html)
- [Cut Claude Code token usage](reduce-claude-code-token-usage.html)
- [Tools reference](tools-reference.html)

---

# TOON Token Savings — Measured

Source: https://trace-mcp.com/toon-savings.html


This document captures real-world token measurements for the TOON output
format wired across trace-mcp tools, plus the independent `search_text`
`grouping: "by_file"` reshape. Encoding is one of several levers on response
size — presets, `compact_schemas` and the reshapes below are collected in [how
to cut Claude Code token usage](reduce-claude-code-token-usage.md), and the
end-to-end cost of a real task is measured on somebody else's repositories in
the [PR review context benchmark](pr-context-benchmark.md).

## TL;DR — which tools support TOON

After benchmarking, TOON is wired only on the five tools where it is a clear
net win on representative payloads:

| tool                  | measured savings | why                                                     |
|-----------------------|-----------------:|---------------------------------------------------------|
| `query_decisions`     | **+31.4%**       | Homogeneous row-shaped decisions; pure table mode.      |
| `get_outline`         | **+28.8%**       | Flat symbol records; pure table mode.                   |
| `get_changed_symbols` | **+21.5%**       | Flat change records; pure table mode.                   |
| `search`              | **+16.4%**       | Flat item records; mild table-mode amortization.        |
| `get_feature_context` |  **+7.9%**       | Mostly flat items; modest gain.                         |

For every other tool TOON is off by default and the parameter is not
accepted in the schema — the per-tool `toon` parameter is listed with each
tool in the [tools reference](tools-reference.md). To see what the encoding is
worth on your own traffic rather than on this corpus, `get_real_savings` and
`get_optimization_report` report it per session ([analytics](analytics.md)).

## Why we removed TOON from 4 tools

| tool                | measured | reason                                                                                          |
|---------------------|---------:|-------------------------------------------------------------------------------------------------|
| `find_usages`       |  -17.5%  | Each reference carries a nested `symbol{}` block → list mode → header overhead per row.         |
| `search_text`       |  -25.5%  | Multi-line `context[]` arrays per match → list mode. Use `grouping: "by_file"` for +20.8% instead. |
| `get_artifacts`     |  -15.2%  | Artifact kinds are heterogeneous; field schemas diverge between rows → list mode.               |
| `get_context_bundle`|  -10.1%  | Nested `primary`/`imports` structure plus small typical size — fixed TOON preamble dominates.   |

`@toon-format/toon` remains a project dependency because the five keepers
above still rely on it.

## Internal mechanism — table mode vs list mode

TOON has two output shapes (driven entirely by the encoder, not by the
caller):

- **Table mode** (`[N]{col1, col2, col3}:`) — emitted only when every row in
  an array contains the **same scalar-only fields** (string, number, bool,
  null). One header amortizes over all rows; each row collapses to a single
  CSV-like line. This is where TOON wins.
- **List mode** (YAML-style nested keys) — emitted when any row has a
  nested object, an inner array, or differs in field set. Each row pays its
  own field labels; the header amortization disappears. TOON loses here vs
  JSON because JSON's `{"k":` is already terse.

The `scripts/toon-diagnostic-2.ts` script reproduces the breakeven curve.
Sample output (n = rows per array, Δ% = TOON savings over JSON):

```
N  | scalar-only (table)   | with array tags         | with nested obj
   | json   toon  Δ%   mode| json   toon  Δ%    mode | json   toon  Δ%    mode
---|----------------------|------------------------|------------------------
 10|  185    130  +29.7  T |  255   333  -30.6   L  |  354   443  -25.1   L
 20|  365    250  +31.5  T |  505   663  -31.3   L  |  704   883  -25.4   L
 50|  905    610  +32.6  T | 1255  1653  -31.7   L  | 1754  2203  -25.6   L
100| 1805   1210  +33.0  T | 2505  3303  -31.9   L  | 3504  4403  -25.7   L
```

Key observations:

- A **single inner `tags: [...]` array per row collapses the win into a
  ~30% regression** — the encoder falls out of table mode.
- A **single nested `symbol: {...}` object per row** is similarly fatal
  (~25% regression).
- Table-mode wins grow with column count: for 20 scalar columns × 20 rows
  the encoder reaches **+47.4%** vs JSON. This matches what `query_decisions`
  hits in production.

The four loser tools all land in list mode by construction:
`find_usages` has nested `symbol{}`, `search_text` has `context[]`,
`get_artifacts` row shapes vary per kind, `get_context_bundle` payloads
are deeply nested and small.

## Methodology

- Script: [`scripts/bench-toon.ts`](https://github.com/nikolai-vysotskyi/trace-mcp/blob/master/scripts/bench-toon.ts) for the
  per-tool numbers; [`scripts/toon-diagnostic-2.ts`](https://github.com/nikolai-vysotskyi/trace-mcp/blob/master/scripts/toon-diagnostic-2.ts)
  for the table-vs-list-mode curve.
- Invocation pattern: each registered MCP tool's closure is captured via a
  fake `server.tool(...)`. This bypasses the MCP transport but exercises the
  identical handler code that ships in production.
- Corpus for live-DB scenarios: a snapshot copy of the trace-mcp self-index
  (1,501 indexed files, 9,467 symbols).
- Corpus for fixture-only scenarios (`query_decisions`,
  `get_changed_symbols`): in-memory stores seeded with realistic shapes.
- Tokenizer: [`gpt-tokenizer`](https://www.npmjs.com/package/gpt-tokenizer)
  with the **cl100k_base** encoding. This is a GPT-4 / Claude family proxy.
  Anthropic's tokenizer is not publicly released; cl100k_base is the
  closest open approximation.
- Roundtrip assertion: every TOON output is decoded via
  `@toon-format/toon`'s `decode()` and structurally compared to the JSON
  payload, with a ~1e-6 relative tolerance for float precision.

Run it:

```bash
pnpm exec tsx scripts/bench-toon.ts
pnpm exec tsx scripts/toon-diagnostic-2.ts
```

## Overall — JSON vs TOON (original 9-tool sweep)

| scenario | json_tokens | toon_tokens | savings_pct | json_bytes | toon_bytes | bytes_savings_pct | notes |
|---|---:|---:|---:|---:|---:|---:|---|
| search query=register limit=30 | 3319 | 2776 | **16.4%** | 12052 | 9729 | 19.3% | KEEP — 25 items |
| get_outline store.ts | 5330 | 3793 | **28.8%** | 19520 | 13216 | 32.3% | KEEP — 92 symbols |
| find_usages fqn=Store | 46662 | 54840 | -17.5% | 190771 | 206704 | -8.4% | REMOVED — 567 references |
| get_feature_context token_budget=2000 | 2965 | 2730 | **7.9%** | 10810 | 9787 | 9.5% | KEEP — 11 items |
| get_context_bundle encodeResponse | 129 | 142 | -10.1% | 470 | 465 | 1.1% | REMOVED — small payload |
| query_decisions limit=20 | 3129 | 2146 | **31.4%** | 11062 | 6960 | 37.1% | KEEP — 20 decisions |
| get_artifacts limit=50 | 1391 | 1602 | -15.2% | 4636 | 5233 | -12.9% | REMOVED — 30 artifacts |
| get_changed_symbols since=HEAD~1 | 288 | 226 | **21.5%** | 892 | 557 | 37.6% | KEEP — 0 changes |
| search_text flat (toon) | 2160 | 2710 | -25.5% | 7850 | 8394 | -6.9% | REMOVED — 50 hits |

## search_text — flat vs by_file (TOON removed, grouping kept)

All percentages are computed against the **flat-json baseline** (2160 tokens
/ 7850 bytes for the same 50-hit corpus). `output_format: "toon"` is no
longer accepted on this tool; `grouping: "by_file"` stays and, as of
TRA-711, is the **default** — `grouping: "flat"` is the opt-out.

| scenario | json_tokens | savings_pct | json_bytes | bytes_savings_pct | notes |
|---|---:|---:|---:|---:|---|
| search_text flat | 2160 | 0% | 7850 | 0% | baseline |
| search_text by_file | 1710 | **+20.8%** | 5826 | +25.8% | 8 files, 50 hits |

`grouping: "by_file"` is a pure structural reshape — it deduplicates file
paths under nested `files[].hits[]` buckets. It is lossless and independent
of `output_format`.

## Caveats

1. **Tokenizer**: cl100k_base is a *proxy* for Anthropic's tokenizer. Real
   Claude-side savings can differ by ±2-3 percentage points.
2. **Float precision**: TOON rounds high-precision floats below ~8
   significant digits (e.g. PageRank scores). For human-readable outputs
   this is invisible; for callers that hash JSON output, it is a real
   semantic difference.
3. **Single-sample bench**: each scenario runs once. The retrieval tools are
   deterministic given a fixed DB, so this is fine for token counts.
4. **Corpus is one repo**: trace-mcp's self-index. Results will shift on
   other codebases — wider repos with longer file paths boost `by_file`
   more; repos with sparser fields and shorter strings boost TOON more.

## Recommendation

- TOON is enabled (and worth selecting via `output_format: "toon"`) on
  exactly five tools: `query_decisions`, `get_outline`, `get_changed_symbols`,
  `search`, `get_feature_context`.
- For everything else, JSON is the default and the only accepted output
  format. The encoder regression on heterogeneous / nested / small payloads
  outweighs any benefit.
- For `search_text`, `grouping: "by_file"` is the default (TRA-711) — a
  clean +20.8% lossless win whenever paths repeat across hits, unrelated to
  TOON. `grouping: "flat"` restores the old `matches[]` shape.
- Document the float-precision caveat for callers that hash or diff
  responses.

## Wave 2 candidates — measured

Forecasted savings if the same `output_format: "toon"` switch were wired into
each of the candidates below. **These tools are NOT wired** — this is a
measurement-only pass to decide which to wire in a follow-up.

Each row encodes the JSON payload returned by the production handler, then
re-encodes the same payload via `encodeResponse(..., "toon")`. Tokenised
with cl100k_base, roundtripped via `@toon-format/toon`'s `decode()` against
the parsed JSON with the loose-float comparator. Sorted by `savings_pct`
descending.

| scenario | items | json_tokens | toon_tokens | savings_pct | mode | notes |
|---|---:|---:|---:|---:|:---:|---|
| get_risk_hotspots limit=30 | 30 | 1338 | 758 | **+43.3%** | table | flat scalar rows |
| analyze_perf top=30 | 15 | 530 | 301 | **+43.2%** | table | seeded latency stats, 7 scalar columns |
| get_git_churn limit=50 | 50 | 2634 | 1604 | **+39.1%** | table | flat scalar rows |
| get_coupling limit=50 | 50 | 1733 | 1096 | **+36.8%** | table | flat scalar rows |
| get_pagerank limit=50 | 50 | 1634 | 1045 | **+36.0%** | table | only 2 columns but very repetitive |
| get_complexity_report limit=50 | 50 | 3057 | 2075 | **+32.1%** | table | flat scalar rows |
| get_refactor_candidates limit=40 | 40 | 1932 | 1387 | **+28.2%** | table | flat scalar rows |
| predict_bugs limit=40 | 40 | 8877 | 6965 | **+21.5%** | table | risky-control beat the prediction — see Surprises |
| get_untested_symbols scope=exports_only (project-wide) | 907 | 51903 | 40937 | **+21.1%** | table | flat scalar rows |
| list_pins (2 seeded) | 2 | 88 | 71 | +19.3% | table | tiny payload — fixed preamble dominates |
| get_untested_symbols max_results=80 | 80 | 14280 | 13098 | +8.3% | table | signature strings dilute the win |
| get_tests_for output-format.ts | 1 | 44 | 44 | 0% | table | single-row payload — no amortization |
| get_dead_code mode=exports_only (project-wide) | 100 | 5716 | 6707 | **-17.3%** | list | re-measured via the replacement call — row shapes are not uniform |
| get_dead_code limit=30 | 30 | 2425 | 2822 | **-16.4%** | list | nested `signals{}` object per row → list mode |
| get_implementations name=LanguagePlugin | 33 | 2292 | 2683 | **-17.1%** | list | `via: string \| string[]` → list mode |

Skipped on this corpus (no bundles installed locally):
- `list_bundles` — empty payload
- `search_bundles` — empty payload

## Wave 2 recommendation

**Wire `output_format: "toon"` into these 10 tools** — every one is in table
mode with the loose-float roundtrip clean, and savings cross the **+15%
cutoff** we established for the original five keepers:

| tool | measured savings | mode |
|---|---:|:---:|
| `get_risk_hotspots` | +43.3% | table |
| `analyze_perf` | +43.2% | table |
| `get_git_churn` | +39.1% | table |
| `get_coupling` | +36.8% | table |
| `get_pagerank` | +36.0% | table |
| `get_complexity_report` | +32.1% | table |
| `get_refactor_candidates` | +28.2% | table |
| `predict_bugs` | +21.5% | table |
| `get_untested_symbols` (`scope: exports_only`) | +21.1% | table |

**Do not wire** these — savings below the +15% cutoff or list-mode regression:

| tool | measured | reason |
|---|---:|---|
| `list_pins` | +19.3% above cutoff, but typical payload is ~2-10 rows × 88-300 tokens — TOON's fixed preamble dominates at this scale and a single pin payload is already small. Wire only if usage tests show consistent ≥10-pin payloads in practice. |
| `get_untested_symbols` | +8.3% | Per-row `signature` and `level` strings drown out the column-header amortization. |
| `get_tests_for` | 0% | Single-row payloads are typical; no amortization possible. |
| `get_dead_code` | -16.4% / -17.3% | Nested `signals{ import_graph, call_graph, barrel_exports }` per row forces list mode in the default mode. The `exports_only` payload was carried by the retired `get_dead_exports` alias and had measured +21.2%; re-measured through `get_dead_code { mode: "exports_only" }` in 2026-08 it lands at -17.3%, so it was deliberately *not* moved onto the allowlist (TRA-240). |
| `get_implementations` | -17.1% | `via: string \| string[]` makes row shapes heterogeneous → list mode. |

## Wave 2 surprises

1. **`predict_bugs` landed in table mode at +21.5%.** Prediction said the
   per-row `signals: string[]` array would force list mode (-25%-ish, like
   `find_usages`). What actually happens: in this corpus most predictions
   have an *empty* `signals` array, and the TOON encoder keeps table mode
   when every row's array field has the same length. The win is real but
   could degrade on a repo where `signals` is densely populated — re-measure
   before wiring on a high-churn repo.
2. **`list_pins` (2 seeded rows) only saves 19.3%.** The fixed TOON preamble
   is ~10-15 tokens; on a 2-row payload it eats most of the column-header
   amortization. The savings curve crosses +30% somewhere around 8-10 pins.
3. **`get_pagerank` saves +36.0% on just two columns (`file`, `score`).**
   The win is driven entirely by the repetitive `file` paths — TOON strips
   the per-row key labels, JSON repeats `"file":` for every row.
4. **`get_implementations` regressed even though no row's `via` was an
   array** in this run (33 implementors of LanguagePlugin, all with single
   `extends`). The encoder still went list mode — likely because of the
   union-typed field shape across encoder probing, or because some rows have
   `signature: null`. Investigate before wiring.
5. **`analyze_perf` is tied for first place at +43.2%** — that is the
   strongest predicted candidate by a wide margin, despite being seeded with
   synthetic latency stats. A real persistent-telemetry payload (24h/7d
   windows) would benefit even more.

---

# Decision memory

Source: https://trace-mcp.com/decision-memory.html


trace-mcp includes a persistent decision knowledge graph that captures architectural decisions, tech choices, bug root causes, preferences, and conventions — linked to the code they're about, on the same symbol and file nodes the indexer builds ([architecture](architecture.md)).

## Why

Every conversation with an AI agent produces decisions that disappear when the session ends. Six months of daily AI use = thousands of decisions lost. General-purpose memory tools (MemPalace, OpenMemory, Mem0) store these as text. trace-mcp stores them **linked to code symbols and files** — so when you ask "what breaks if I change this?", you also see *why it was built that way*.

## What "why" actually looks like

The system tries to capture the kinds of reasoning that disappear from the diff but matter when someone returns to the code months later:

- **The alternative that was rejected and the reason** — "went with Postgres JSONB over a separate document store because transactional updates had to span relational and semi-structured data in the same write." Without the rejected branch, future readers re-litigate the same choice.
- **The constraint that forced the hand** — legal, performance budget, deadline, an upstream dependency we don't control. Captured as `tradeoff` decisions with the constraint named, so when the constraint disappears the decision is flagged as revisitable.
- **The failure mode behind a fix** — for `bug_root_cause`, what actually went wrong, not what got patched. "Request body parser ran before auth middleware, so unauthenticated payloads hit the DB," not "added auth check." The fix is in the diff; the failure mode isn't.
- **The thing that was tried first and didn't work** — recovered from session logs by `mine_sessions`, because agents rarely volunteer their own dead ends. This is the highest-value content and the easiest to lose without dedicated capture.
- **The local convention being established** — "all new endpoints go through `withAuth` even if the route looks public." Stops the next agent from reinventing or violating it.

Each of these is linked to the symbol or file it's about, so the next agent who touches that code sees the reasoning surface automatically through `get_change_impact` or `plan_turn` — they don't have to know the decision exists to find it. Both of those, and the `*_decision` / `mine_sessions` tools below, are listed in the [tools reference](tools-reference.md).

## Architecture

```
              ┌──────────────────────────────────────────┐
              │              CAPTURE PATHS                │
              │                                            │
  add_decision│  remember_decision   │   mine_sessions     │
  (manual,    │  (live agent write,  │   (post-hoc, scans  │
   conf=1.0)  │   confidence-scored) │    JSONL logs,      │
              │                      │    pattern-matched) │
              └────────┬──────────────┬──────────┬─────────┘
                       │              │          │
                       ▼              ▼          ▼
              ┌──────────────────────────────────────────┐
              │  Memoir-style review queue                │
              │   confidence ≥ 0.75 → active (default)    │
              │   0.45 ≤ conf < 0.75 → pending review     │
              │   confidence < 0.45  → dropped            │
              │   (approve_decision / reject_decision)    │
              └────────────────┬──────────────────────────┘
                               │
                               ▼
              ┌──────────────────────────────────────────┐
              │  Decision Store (decisions.db)            │
              │  SQLite + FTS5 (porter stemming)          │
              │  ┌────────────┐  ┌────────────────────┐   │
              │  │ decisions  │  │  session_chunks    │   │
              │  │ code-linked│  │  cross-session     │   │
              │  │ temporal   │  │  content search    │   │
              │  │ branch-aware│  │                    │   │
              │  └────────────┘  └────────────────────┘   │
              └────────────────┬──────────────────────────┘
                               │
                               ▼
              ┌──────────────────────────────────────────┐
              │  Enrichment Layer                         │
              │  get_change_impact  → linked_decisions    │
              │  plan_turn          → related_decisions   │
              │  get_wake_up(resume) → active_decisions   │
              │  get_wake_up        → orientation context │
              └──────────────────────────────────────────┘
```

## Decision types

| Type | What it captures | Example |
|---|---|---|
| `architecture_decision` | Structural choices | "Migrating from REST to GraphQL" |
| `tech_choice` | Technology selections | "PostgreSQL over MySQL for JSONB support" |
| `bug_root_cause` | Why bugs happened | "Missing null check in auth middleware" |
| `preference` | Team/personal preferences | "Always use named exports" |
| `tradeoff` | Acknowledged tradeoffs | "Accept higher latency for stronger consistency" |
| `discovery` | New learnings | "Discovered that Prisma doesn't support CTEs" |
| `convention` | Coding conventions | "From now on, use snake_case for DB columns" |

## Memoir-style review queue

Not everything an agent observes is worth keeping. The capture pipeline routes every write through a three-tier confidence gate so high-signal decisions enter the graph immediately while borderline ones queue for a human, and noise is dropped:

| Confidence | Routing | `review_status` |
|---|---|---|
| ≥ `review_threshold` (default **0.75**) | Active — visible in `query_decisions` by default | `null` (auto-approved) |
| `[reject_threshold, review_threshold)` | Queued for human approval | `'pending'` |
| < `reject_threshold` (default **0.45**) | Dropped, not persisted | n/a |

Both thresholds are configurable via the `decisions.review_threshold` and `decisions.reject_threshold` keys in config, or per-call on `mine_sessions` and `remember_decision`.

Manage the queue with `approve_decision` / `reject_decision`; inspect it with `query_decisions { include_pending: true }` or `query_decisions { review_status: "pending" }`. `add_decision` bypasses the gate (it's the explicit, human-curated path — confidence is pinned to 1.0).

## Confidence scoring

The score that drives the review queue combines a base prior with multiplicative boosts for signals that correlate with usefulness:

| Signal | Effect |
|---|---|
| Decision is linked to a `symbol_id` or `file_path` | + code-ref boost |
| `content` length ≥ 200 chars | + length boost |
| At least one tag attached | + tags boost |
| Type is high-signal (`architecture_decision`, `bug_root_cause`, `tradeoff`) | + type boost |
| Decision is scoped to a `service_name` | + service boost |

For mined decisions, the pattern's intrinsic confidence (see [Extraction patterns](#extraction-patterns)) is additionally multiplied by `1 + 0.05 × n` where `n` is the number of context boosters (`because`, `reason`, `pros and cons`, `alternative`, `architecture`, `design decision`) found in the surrounding turn.

The implementation lives in [`src/memory/decision-confidence.ts`](https://github.com/nikolai-vysotskyi/trace-mcp/blob/master/src/memory/decision-confidence.ts).

## MCP tools

### Capture

| Tool | Description |
|---|---|
| `add_decision` | Manually record a decision. Bypasses the review queue (confidence = 1.0). Accepts `title`, `content`, `type`, `service_name`, `symbol_id`, `file_path`, `tags`, `git_branch`. |
| `remember_decision` | Live agent-write path. Confidence-scores the input and routes through the review queue. Per-session dedup + rate-limit so the agent can't spam the store. Use during a session to capture decisions in real time. |
| `mine_sessions` | Scan Claude Code / Claw Code JSONL logs and extract decisions via pattern matching (no LLM calls). Skips already-processed sessions. Results also flow through the review queue. |
| `index_sessions` | Index conversation content (chunked) for cross-session search. Enables `search_sessions`. |

### Review queue

| Tool | Description |
|---|---|
| `approve_decision` | Promote a `pending` decision to active. |
| `reject_decision` | Mark a `pending` decision as rejected (kept for audit, hidden by default). |

### Read & query

| Tool | Description |
|---|---|
| `query_decisions` | Query with filters: `type`, `service_name`, `symbol_id`, `file_path`, `tag`, `search` (FTS5), `as_of` (temporal), `git_branch`, `include_pending`, `review_status`. |
| `invalidate_decision` | Mark a decision as superseded. It remains in the graph for historical queries. |
| `get_decision_timeline` | Chronological view of decisions for a project, symbol, or file. |
| `get_decision_stats` | Overview: total/active/invalidated, by type, by source, mined/indexed session counts. |

### Search & orientation

| Tool | Description |
|---|---|
| `search_sessions` | Full-text search across all past session conversations. "What did we discuss about auth last month?" |
| `get_wake_up` | Compact orientation (~300 tokens) at session start: project identity + active decisions + memory stats. Auto-mines on first call if the store is empty. |
| `get_wake_up { scope: "resume" }` | Cross-session context carryover: focus files, key searches, and dead-end queries from recent past sessions, alongside active decisions. |

## Code linkage

Decisions can be linked to:

- **Symbols** — `symbol_id: "src/auth/provider.ts::AuthProvider#class"` — any symbol in the code graph
- **Files** — `file_path: "src/auth/provider.ts"` — a specific file
- **Services** — `service_name: "auth-api"` — a service/subproject within the project

When linked, decisions automatically surface in code intelligence tools:

```
get_change_impact(symbol_id="src/auth/provider.ts::AuthProvider#class")
→ {
    ...impact analysis...,
    linked_decisions: [
      { id: 42, title: "Use Clerk for auth", type: "tech_choice",
        symbol: "src/auth/provider.ts::AuthProvider#class", when: "2025-06-01" }
    ]
  }
```

## Temporal validity

Every decision has a `valid_from` timestamp (when it was made) and an optional `valid_until` (when it was superseded):

- **Active decisions** — `valid_until IS NULL` — currently in effect
- **Invalidated decisions** — `valid_until IS NOT NULL` — superseded but preserved for history

Query modes:

```
query_decisions()                              # active only (default)
query_decisions(as_of="2025-01-15T00:00:00Z")  # what was active on Jan 15
query_decisions(include_invalidated=true)       # full history
```

## Service scoping

In projects with multiple services (subprojects), decisions can be scoped to a specific service:

```
add_decision(
  title="Use JWT for service-to-service auth",
  service_name="auth-api",
  type="tech_choice"
)

query_decisions(service_name="auth-api")     # only auth-api decisions
query_decisions()                            # all project decisions
get_decision_stats()                         # shows available_services
```

## Extraction patterns

The conversation miner uses 8 regex-based patterns to extract decisions from assistant messages:

| Pattern | Matches | Type | Confidence |
|---|---|---|---|
| "decided to", "going with", "chose X" | Architecture choices | `architecture_decision` | 0.85 |
| "using X because", "picked X for" | Technology selections | `tech_choice` | 0.80 |
| "X instead of Y", "X over Y" | Comparisons | `tech_choice` | 0.75 |
| "the bug was", "root cause", "caused by" | Bug analysis | `bug_root_cause` | 0.85 |
| "prefer", "always use", "never use" | Preferences | `preference` | 0.70 |
| "tradeoff", "downside is" | Tradeoffs | `tradeoff` | 0.75 |
| "discovered that", "turns out" | Learnings | `discovery` | 0.80 |
| "from now on", "the rule is" | Conventions | `convention` | 0.80 |

Context boosters ("because", "reasoning", "pros and cons", "architecture") increase confidence by 5% each.

Auto-tagging detects topics: auth, database, api, testing, performance, security, devops, typescript, refactoring, migration.

## What we deliberately don't record

Decision memory is for content that disappears when the chat log is gone. It explicitly avoids storing things that can be recovered from the code or git history:

- **The diff itself** — `git log -p` is authoritative.
- **Who touched what** — `git blame` and `git shortlog` answer this.
- **Current code state** — read the file; the index has an outline.

The mining pipeline also filters non-user content before it reaches the store. Block-tagged regions stripped during ingestion:

| Tag | Reason |
|---|---|
| `<private>…</private>` | User-curated "do not remember this" |
| `<persisted-output>…</persisted-output>` | Tool-output capture, often hundreds of KB of file contents |
| `<system-reminder>…</system-reminder>` | Runtime nudges, not user-authored |
| `<ide_selection>…</ide_selection>` | IDE selection echo (may contain sensitive code) |
| `<task-notification>…</task-notification>` | Autonomous protocol payloads from background agents |
| `<local-command-stdout>…</local-command-stdout>` | Captured shell output (may contain secrets) |

`<command-message>` and `<command-name>` are kept — those wrap real user slash-commands and are part of the conversation. Implementation: `stripPrivacyTags` in [`src/memory/conversation-miner.ts`](https://github.com/nikolai-vysotskyi/trace-mcp/blob/master/src/memory/conversation-miner.ts).

## CLI

*(Commands support both `trace` and `trace-mcp` aliases)*

```bash
trace memory mine [--project=.] [--force] [--min-confidence=0.6]
trace memory index [--project=.] [--force]
trace memory search "query" [--project=.] [--limit=20]
trace memory decisions [--project=.] [--type=tech_choice] [--search="query"] [--json]
trace memory stats [--project=.] [--json]
trace memory timeline [--project=.] [--file=path] [--symbol=id]
```

## Storage

All decision memory is stored in `~/.trace/decisions.db` (or `~/.trace-mcp/decisions.db` fallback, SQLite, WAL mode). Tables:

- `decisions` — decision records with code linkage, temporal validity, service scoping
- `decisions_fts` — FTS5 virtual table for full-text search over decisions
- `session_chunks` — chunked conversation content from session logs
- `session_chunks_fts` — FTS5 virtual table for cross-session content search
- `mined_sessions` — tracking which sessions have been processed

Key columns on `decisions`:

| Column | Purpose |
|---|---|
| `title`, `content`, `type` | The decision itself |
| `project_root`, `service_name` | Where it applies (project + optional subproject) |
| `symbol_id`, `file_path` | What code it's about (drives auto-surfacing in impact tools) |
| `tags` | JSON array for categorization and filtering |
| `valid_from`, `valid_until` | Temporal validity (`valid_until = NULL` means active) |
| `git_branch` | Branch scoping (`NULL` = branch-agnostic, visible everywhere) |
| `source` | `'manual'` (added via `add_decision`), `'mined'` (extracted from logs), or `'auto'` (live agent write) |
| `confidence` | `0..1` score driving the review queue (always `1.0` for `'manual'`) |
| `review_status` | `NULL` = auto-approved, `'pending'` = awaiting review, `'approved'`, `'rejected'` |
| `session_id` | Provenance — which session produced this decision |

---

# Language capability matrix

Source: https://trace-mcp.com/language-matrix.html



trace-mcp ships 81 language plugins. They are not equally deep, and this
page says how deep each one is. Every language in the list gets symbol
extraction; call graphs and type edges are a much smaller set. For what each
language's framework integrations understand on top of that, see [supported
frameworks](supported-frameworks.md); for the `include` globs referenced
below, see [configuration](configuration.md). The edge columns are what makes
the graph queryable: a language with call edges answers `get_call_graph` and
call-aware `find_usages` ([tools reference](tools-reference.md)), one without
them still answers `search` and `get_outline`. How those edges are produced
is the two-pass pipeline in [architecture](architecture.md#indexing-pipeline).

- **indexed with the default config:** 78 (the rest need an `include` entry — see below)
- **tree-sitter parser:** 29 · **regex parser:** 46 · **custom parser:** 6
- **import edges:** 16
- **call edges (`get_call_graph`, call-aware `find_usages`):** 3
- **type / inheritance edges:** 2
- **covered by a plugin test:** 68

## What the columns mean

| Column | Meaning |
| --- | --- |
| Parser | `tree-sitter` — real grammar-based AST. `regex` — pattern extraction, no AST. `custom` — hand-written parser for a structured format. |
| Default | The shipped default `include` globs reach files with this extension. Where this is empty the plugin only runs once you add the extension to `include` in `.trace.json`. |
| Imports | Import statements are resolved into graph edges. Plugins outside this set still parse imports, but nothing turns them into edges yet. |
| Calls | A call-graph resolver exists, so "who calls this" is answerable for this language. |
| Types | Type-annotation or inheritance edges are resolved. |
| Tests | At least one test exercises this plugin directly. |

**Default config vs. plugin count.** The default `include` is one global glob
over every extension the plugins below claim, so 78 of
the 81 plugins run wherever their files live in the repo — no
directory anchoring. The remaining 3
are pure data formats, left out because lockfiles, fixtures and `.svg` would
swamp the index for little symbol value. Ask for them explicitly if you want
them:

```json
{ "include": ["schemas/**/*.json", "k8s/**/*.xml"] }
```

Note that `include` in a project config **replaces** the built-in list rather
than adding to it, so copy the default glob alongside your addition if you
still want the rest of the repo indexed.

A regex plugin still gives you working `search`, `get_outline` and symbol
navigation — that covers most "find it and read it" work. What it does not give
you is a call graph: that needs an AST plus a per-language resolver. Where the
Calls column is empty, `get_call_graph` returns nothing for that language
unless you enable LSP enrichment (`lsp.enabled: true`) or ingest a SCIP index.

**Imports means edges, not parsing.** Most plugins extract import statements;
far fewer have a pipeline pass that resolves the specifier to a target node, and
without one the extracted import never becomes an edge. This column counts the
second thing, so it is much shorter than the language list — see
`src/indexer/edge-resolvers/import-capable-languages.ts`.

## Matrix

| Language | Extensions | Parser | Default | Imports | Calls | Types | Tests |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ada | .adb .ads .ada | regex (multi-pass) | yes | — | — | — | yes |
| al | .al | regex | yes | — | — | — | yes |
| apex | .cls .trigger .apex | regex (multi-pass) | yes | — | — | — | yes |
| assembly | .asm .s .S | regex | yes | — | — | — | yes |
| astro | .astro | tree-sitter | yes | — | — | — | yes |
| autohotkey | .ahk .ah2 | regex | yes | — | — | — | yes |
| bash | .sh .bash .zsh | tree-sitter | yes | — | — | — | yes |
| blade | .blade.php | regex | yes | — | — | — | yes |
| c | .c .h | tree-sitter | yes | yes | — | — | yes |
| clojure | .clj .cljs .cljc .edn | regex | yes | — | — | — | — |
| cmake | .cmake CMakeLists.txt | regex | yes | — | — | — | yes |
| cobol | .cob .cbl .cpy .cobol | regex (multi-pass) | yes | — | — | — | yes |
| common-lisp | .lisp .lsp .cl .asd | regex (multi-pass) | yes | — | — | — | yes |
| cpp | .cpp .cxx .cc .hpp | tree-sitter | yes | yes | — | — | yes |
| csharp | .cs | tree-sitter | yes | — | — | — | yes |
| css | .css .scss .sass .less | tree-sitter | yes | yes | — | — | yes |
| cuda | .cu .cuh | regex | yes | — | — | — | — |
| d | .d .di | regex (multi-pass) | yes | — | — | — | yes |
| dart | .dart | tree-sitter | yes | — | — | — | yes |
| dockerfile | Dockerfile .dockerfile | regex | yes | — | — | — | yes |
| ejs | .ejs | regex | yes | — | — | — | yes |
| elisp | .el .elc | tree-sitter | yes | — | — | — | — |
| elixir | .ex .exs | tree-sitter | yes | — | — | — | yes |
| elm | .elm | tree-sitter | yes | — | — | — | — |
| erlang | .erl .hrl | regex | yes | — | — | — | yes |
| form | .frm .prc | regex | yes | — | — | — | — |
| fortran | .f .f90 .f95 .f03 | regex | yes | — | — | — | yes |
| fsharp | .fs .fsi .fsx | regex (multi-pass) | yes | — | — | — | — |
| gdscript | .gd | regex | yes | — | — | — | yes |
| gleam | .gleam | regex | yes | — | — | — | yes |
| glsl | .glsl .vert .frag .geom | regex | yes | — | — | — | — |
| go | .go | tree-sitter | yes | yes | — | — | yes |
| graphql | .graphql .gql | custom | yes | — | — | — | yes |
| groovy | .groovy .gradle .gvy | regex | yes | — | — | — | yes |
| haskell | .hs .lhs | regex | yes | — | — | — | yes |
| hcl | .tf .hcl .tfvars | custom | yes | yes | — | — | yes |
| html | .html .htm | tree-sitter | yes | yes | — | — | yes |
| ini | .ini .cfg .conf .properties | regex | — | — | — | — | — |
| java | .java | tree-sitter | yes | yes | — | — | yes |
| json | .json .jsonc .json5 | tree-sitter | — | — | — | — | yes |
| julia | .jl | regex | yes | — | — | — | yes |
| kotlin | .kt .kts | tree-sitter | yes | — | — | — | yes |
| lean | .lean | regex | yes | — | — | — | — |
| lua | .lua .luau | tree-sitter | yes | — | — | — | yes |
| magma | .m .mag .magma | regex | yes | — | — | — | — |
| makefile | Makefile makefile .mk GNUmakefile | regex | yes | — | — | — | yes |
| markdown | .md .mdx .markdown .qmd | custom | yes | yes | — | — | yes |
| matlab | .m .mlx .mat | regex (multi-pass) | yes | — | — | — | yes |
| meson | meson.build meson_options.txt | regex | yes | — | — | — | — |
| nim | .nim .nims .nimble | regex | yes | — | — | — | yes |
| nix | .nix | regex | yes | — | — | — | yes |
| objc | .m .mm | tree-sitter | yes | — | — | — | yes |
| ocaml | .ml .mli | tree-sitter | yes | — | — | — | yes |
| pascal | .pas .dpr .dpk .lpr | regex (multi-pass) | yes | — | — | — | yes |
| perl | .pl .pm .t | regex | yes | — | — | — | yes |
| php | .php | tree-sitter | yes | yes | yes | — | yes |
| plsql | .pls .plb .pck .pkb | regex (multi-pass) | yes | — | — | — | yes |
| powershell | .ps1 .psm1 .psd1 | regex (multi-pass) | yes | — | — | — | yes |
| prisma | .prisma | custom | yes | — | — | — | yes |
| protobuf | .proto | regex | yes | — | — | — | yes |
| python | .py .pyi | tree-sitter | yes | yes | yes | yes | yes |
| r | .r .R .Rmd | regex | yes | — | — | — | yes |
| ruby | .rb .rake | tree-sitter | yes | yes | — | — | yes |
| rust | .rs | tree-sitter | yes | yes | — | — | yes |
| scala | .scala .sc | tree-sitter | yes | — | — | — | yes |
| solidity | .sol | tree-sitter | yes | — | — | — | yes |
| sql | .sql | regex | yes | — | — | — | yes |
| svelte | .svelte | regex | yes | — | — | — | yes |
| swift | .swift | tree-sitter | yes | — | — | — | yes |
| tcl | .tcl .tk .itcl .itk | regex (multi-pass) | yes | — | — | — | yes |
| toml | .toml | tree-sitter | yes | — | — | — | yes |
| typescript | .ts .tsx .mts .cts | tree-sitter | yes | yes | yes | yes | yes |
| verilog | .v .sv .svh .vh | regex | yes | — | — | — | yes |
| verse | .verse | regex | yes | — | — | — | yes |
| vhdl | .vhd .vhdl .vho .vhs | regex | yes | — | — | — | yes |
| vimscript | .vim .vimrc | regex | yes | — | — | — | — |
| vue | .vue | tree-sitter | yes | yes | — | — | yes |
| wolfram | .wl .wls .m .nb | regex | yes | — | — | — | — |
| xml | .xml .xul .xsl .xslt | custom | — | yes | — | — | yes |
| yaml | .yaml .yml | custom | yes | yes | — | — | yes |
| zig | .zig .zon | tree-sitter | yes | — | — | — | yes |

---

# Daemon memory: what it costs and what caps it

Source: https://trace-mcp.com/daemon-memory.html



The HTTP daemon is a background process — the shared-index deployment described
under [configuration](configuration.md#stdio-vs-http--choosing-your-setup). If
it outweighs the user's browser it has failed regardless of how fast queries
are. This page records what the
resident set is actually made of, measured rather than estimated, and which
knob bounds each part.

## Measured attribution (TRA-422)

macOS 26.5, Apple Silicon, daemon v3.2.0 at rest, 11 projects loaded, no query
traffic. `ps -o rss=` reported 1.55 GB; `vmmap -summary` splits it:

| Region | Resident | What it is | Bounded by |
|---|---:|---|---|
| Memory Tag 255 | 714 MB | V8 heap + Node allocations. `heap_used` was 370–515 MB at the same moment, so V8 holds roughly 2× its live set in committed pages it has not returned to the OS. | project count |
| `mapped file` | 380 MB | SQLite `mmap` of each project's `index.db`. | `index_mmap_mb` × project count |
| `MALLOC_SMALL` | 258 MB (206 MB dirty) | Native allocations: SQLite page cache, tree-sitter trees. | `index_cache_mb` × connections |
| `MALLOC_SMALL (empty)` | 104 MB (22 MB dirty) | Freed, not returned to the OS. | — |
| `__TEXT` / `__LINKEDIT` / `__OBJC_RO` / `__DATA*` | ~330 MB | Node binary and native modules. Clean, file-backed, shared with every other Node process on the machine. | fixed |
| page table in kernel | 55 MB | Cost of the ~112 GB of virtual address space V8 reserves. | fixed |

The `mapped file` rows are per-project and worth reading directly:

```
64.0M  63.6M  .trace/index/thewed-2f9565b74fb5.db
64.0M  63.7M  .trace/index/general-e8778b435c05.db
64.0M  63.3M  .trace/index/assetfeed-76da1a753b39.db
34.1M  33.4M  .trace/index/workdir-45190de3e39c.db
...
```

Three DBs are larger than the 64 MB `index_mmap_mb` window and sit at the cap
with essentially every mapped page resident — a full-table scan touches the
whole window. Smaller DBs are mapped in their entirety. Average across the 11:
**34.5 MB per project from mmap alone.**

**Marginal cost of one loaded project: ~100 MB resident** (~35 MB mmap, ~23 MB
native page cache and tree-sitter, the balance V8 heap). Fixed floor with zero
projects loaded is ~165 MB RSS, of which ~330 MB of library text is shared.

A JS heap snapshot was not the right instrument here: the V8 heap is only ~28 %
of RSS, so it cannot attribute the other 72 %.

## The ceiling

`daemon_eager_load_projects` (default **8**) is the number of projects the
daemon keeps resident. 8 × ~100 MB ≈ 800 MB marginal on top of the ~350 MB
fixed footprint. Raise it on a big machine with few repos; lower it if the
daemon is competing for memory.

Two rules enforce it, both in the sweep armed by `startIdleUnloadSweep`:

- **TTL** — `project_idle_unload_minutes` (default 30) unloads any project not
  touched in that long.
- **LRU ceiling** — anything above `daemon_eager_load_projects` is unloaded
  least-recently-accessed first, regardless of TTL.

Before TRA-422 only the TTL existed and the eager cap applied at startup only,
so lazy loads (`/mcp` auto-register, `/api/projects/reindex-file`) drifted past
it unchecked: a daemon that booted with `eager: 8, deferred: 34` was holding 11
projects three minutes later, with nothing to bring it back down.

Both rules skip projects that are `starting`/`indexing` or have live clients
(`resourcePool.getRefCount > 0`). The ceiling is therefore best-effort: a
daemon with 11 busy projects stays at 11 rather than tearing down work in
flight. Set either knob to 0 to disable that rule; with both at 0 no timer is
armed.

Unloading is in-memory only. The project stays in the registry and reloads
lazily on its next request (503 + `Retry-After` while it warms, the same path a
cold-start project takes).

## Reproducing the measurement

```bash
PID=$(curl -s http://127.0.0.1:3741/health | python3 -c 'import sys,json;print(json.load(sys.stdin)["pid"])')
ps -o rss= -p "$PID"                 # total RSS in KB
vmmap -summary "$PID"                # region breakdown
vmmap "$PID" | grep 'mapped file'    # per-project index.db mmap residency
```

`Daemon vitals` lines in `~/.trace/daemon.log` carry `rss_mb`,
`heap_used_mb` and `projects_loaded` every 60 s, which is the cheap way to
watch the floor over time.

## Known caveat

The sweep runs every 5 minutes, so a burst of lazy loads can sit above the
ceiling for up to one interval before it is enforced. Enforcing on each
`addProject` instead would close that window; it was not worth the coupling
until the delay is shown to matter.

The sweep also cannot help a daemon that does not live long enough to run it —
see TRA-421 on daemon restarts.

Every knob named above — `daemon_eager_load_projects`,
`project_idle_unload_minutes`, `index_mmap_mb`, `index_cache_mb` — is a
[configuration](configuration.md) key, and what the mapped `index.db` files
actually hold is the storage layer described in
[architecture](architecture.md#storage).

---

# MCP Tracing & Telemetry

Source: https://trace-mcp.com/telemetry.html


Tracing an MCP server means seeing which tool the agent called, how long it
took and what it threw. trace-mcp ships a pluggable observability bridge (P13)
that emits OpenTelemetry-compatible spans for every AI provider call and every
MCP tool invocation, so MCP tool traffic shows up in the same tracing backend
as the rest of your stack. The default sink is `noop` — opt-in only, and
switched on with the `telemetry.*` keys described in
[configuration](configuration.md). Turning it on
also unlocks the persistent `window` values of `analyze_perf`, which is
otherwise limited to the current session ([analytics](analytics.md)).

> **This page is about spans you export to your own collector.** It is not the
> anonymous daily install ping, which is a separate subsystem reporting to the
> maintainer. That one's published field list — everything it sends, both ways
> to turn it off, and how to delete the state it keeps — is on the
> [privacy page](privacy.md), with the source in `src/telemetry/usage-ping.ts`.
> Nothing on this page phones home.

## Quickstart (3 commands)

```bash
# 1. Stand up a local OTLP collector (Jaeger all-in-one).
cd ops/telemetry && docker compose up -d && cd -

# 2. Enable the bridge for this project (.trace.json is gitignored).
cat > .trace.json <<'JSON'
{
  "telemetry": {
    "observability": {
      "enabled": true,
      "sink": "otlp",
      "otlp": { "endpoint": "http://localhost:4318/v1/traces" }
    }
  }
}
JSON

# 3. Run anything — spans land in Jaeger UI at http://localhost:16686.
pnpm run build && node dist/cli.js eval run --dataset default
```

## Where the traces appear

Open <http://localhost:16686>, pick `trace-mcp` from the **Service** dropdown,
click **Find Traces**.

## Span topology

| Span name              | Origin                                | Key attributes                                                                                |
| ---------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------- |
| `tool.<name>`          | `src/server/tool-gate.ts`             | `tool.name`, `duration_ms`, `tool.is_error`                                                   |
| `ai.embed`             | `TrackedEmbeddingService.embed`       | `ai.provider`, `ai.model`, `ai.url`, `ai.input_size`, `ai.output_size`, `duration_ms`         |
| `ai.embed_batch`       | `TrackedEmbeddingService.embedBatch`  | as above + batch counts                                                                       |
| `ai.generate`          | `TrackedInferenceService.generate`    | `ai.provider`, `ai.model`, `ai.max_tokens`, `ai.temperature`, `ai.input_size`, `duration_ms`  |
| `ai.generate_stream`   | `TrackedInferenceService.generateStream` | as above                                                                                   |

Errors thrown by the wrapped call surface as a span `exception` event
(`exception.type`, `exception.message`, `exception.stacktrace`) with span
status `ERROR`. The error is rethrown so caller control flow is unchanged.

## Switching sinks

In `.trace.json` (or `~/.trace/.config.json` for a global default):

| Goal                         | Config                                                                                              |
| ---------------------------- | --------------------------------------------------------------------------------------------------- |
| Disabled (default)           | `telemetry.observability.enabled: false` or omit the block                                          |
| Local Jaeger / collector     | `sink: "otlp"`, `otlp.endpoint: "http://localhost:4318/v1/traces"`                                  |
| Hosted OTLP (e.g. Honeycomb) | `sink: "otlp"`, `otlp.endpoint: "https://api.honeycomb.io/v1/traces"`, `otlp.headers: { "x-honeycomb-team": "…" }` |
| Langfuse cloud               | `sink: "langfuse"`, `langfuse.publicKey: "pk-…"`, `langfuse.secretKey: "sk-…"`                      |
| Fan-out to both              | `sink: "multi"` + both `otlp` and `langfuse` blocks                                                 |
| Probabilistic sampling       | `sampleRate: 0.1` (10% of spans kept)                                                               |

All sinks lazy-load — paying for `noop` is free, paying for `otlp` only loads
when `sink === "otlp"`.

## Adding a custom span from your own code

The public API lives in `src/telemetry/index.ts`:

```ts
import {
  getGlobalTelemetrySink,
  instrumentAsync,
} from './telemetry/index.js';

// Time an async function and auto-record exceptions.
await instrumentAsync(
  getGlobalTelemetrySink(),
  'my.operation',
  { 'my.attr': 42 },
  async (span) => {
    // ... your work ...
    span.setAttribute('result.count', items.length);
    return items;
  },
);

// Manual span lifecycle.
const span = getGlobalTelemetrySink().startSpan('my.op', { foo: 'bar' });
try {
  // ...
  span.setStatus('ok');
} catch (err) {
  span.recordError(err);
  throw err;
} finally {
  span.end();
}
```

`instrumentAiCall` and `instrumentToolCall` convenience wrappers exist for
the two standardised attribute schemas — prefer them when emitting AI or
tool-flavoured spans so dashboards stay consistent.

## Cleanup

```bash
cd ops/telemetry && docker compose down -v
rm .trace.json   # if you don't want telemetry enabled going forward
```

## Performance

- `noop` sink: a class allocation per span + 4 no-op method calls. Measured
  overhead is below the per-call jitter floor (~sub-microsecond).
- `otlp` sink: buffers spans in memory; flushes when 50 spans accumulate or
  every 5 s. Each flush is a single POST to `/v1/traces`. The export is
  fire-and-forget — `onError` logs at `warn` but never blocks the caller.
- `sampleRate < 1`: a `SamplingSink` wraps the real sink and rolls a
  `Math.random()` per `startSpan` / `emit`. Kept spans cost the full export
  path; dropped spans are noop.

## Troubleshooting

- **No spans in Jaeger.** Confirm `telemetry.observability.enabled` resolves
  to `true` (`node dist/cli.js config show`). Confirm the endpoint matches
  the Jaeger OTLP HTTP port (`4318` by default). Watch the trace-mcp log for
  `telemetry.otlp_export_failed` — most often a 404 on the path or a
  hostname typo.
- **Spans appear once then never refresh.** Jaeger memory store evicts after
  ~10k traces. Restart the container or switch to `badger` storage.
- **Container can't reach trace-mcp.** This direction never happens — the
  SDK is the client; Jaeger is the server. Always `http://localhost:4318`
  from the trace-mcp side.

---

# Privacy

Source: https://trace-mcp.com/privacy.html



trace-mcp runs entirely on your machine. Indexing is local, the index lives in `~/.trace/`, and semantic search uses bundled ONNX embeddings with no API keys and no outbound calls. There is no account and no server of ours in the path — see [Architecture](architecture.md) for what runs where.

Exactly one thing leaves your machine on its own: an anonymous daily ping that counts active installs. This page is the complete description of it.

---

## The daily ping

At most one per day, per install. Sent from `src/telemetry/usage-ping.ts` at server startup, over [GA4's Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/ga4) — a single HTTP POST, not a custom backend or an SDK.

### Everything it sends

- A random install id — a UUID generated locally, stored in `~/.trace/telemetry-state.json`. This is the only per-install identifier.
- The trace-mcp version, and the version the previous ping came from.
- Whether this run is a first install, an upgrade, a downgrade, or another day on the same version.
- Node major version and OS platform (`darwin`, `linux`, `win32`).
- The country your machine's timezone belongs to — `DE`, not a city and not an IP.
- The name of the MCP client that connected (`claude-code`, `cursor`), and the model it mostly drove (`claude-opus-4-6`).
- How many repositories you have indexed — the number, never their names or paths.
- Your machine's class: CPU architecture, core count, RAM in whole gigabytes, OS kernel version.
- The tool preset the session ran with (`minimal`, `dev`, `full`, …) and how many tools it advertised — the count, never which ones.
- Two aggregate counters since the previous ping: how many tool calls you made and the estimated tokens they saved. These are the same totals `trace-mcp analytics savings` prints locally.
- Two counters for background-daemon reliability: how many times the daemon started, and how many of those starts followed a run that died without shutting down. Counts only — no exit codes, no timestamps, no reasons, and nothing about what was running.

### What it never sends

No IP address — `ip_override` is deliberately left unset, so Google derives nothing about your network from the request. No device fingerprint, no demographics, no account, email, hostname or username. No repository name, no file path, no query content, and no code. No per-tool or per-project breakdown.

It is also suppressed entirely when `CI` is set, so build jobs never count as installs.

### Its credentials are public by design

The GA4 measurement id and its write-only `api_secret` are compiled into the published bundle as plaintext, so anyone can read exactly where the ping goes and verify this page against the wire. The trade is deliberate and its consequence is stated in [SECURITY.md](https://github.com/nikolai-vysotskyi/trace-mcp/blob/master/SECURITY.md#telemetry-credentials--public-by-design): the counts are unauthenticated and therefore inflatable.

---

## Turning it off

Either one disables the ping completely:

```bash
# Environment variable — also accepts 0 and false.
export TRACE_MCP_TELEMETRY=off
```

```jsonc
// ~/.trace/.config.json
{
  "telemetry": { "usage_ping": false }
}
```

**`telemetry.usage_ping` is not `telemetry.enabled`.** The `enabled` key next to it switches on a *local* latency database in `~/.trace/telemetry.db` that never leaves your machine, and `telemetry.observability.*` exports spans to a collector *you* configure — see [MCP tracing](telemetry.md). Neither of those has anything to do with the ping. Every key is listed in the [config index](config-index.md).

The first time trace-mcp runs, it prints one line to stderr naming the ping and both opt-outs, then records that it has done so and never prints it again.

## Deleting local state

`~/.trace/` is the whole footprint — index, telemetry state file, savings totals, logs. Deleting it removes everything trace-mcp has stored about you, including the install id, which means a later ping counts as a new install rather than as you.

```bash
rm -rf ~/.trace          # or ~/.trace-mcp on an install that hasn't migrated
```

To remove a single project's index instead, use `trace-mcp remove <path>` — details in [Configuration](configuration.md).

## Your AI client is a separate question

trace-mcp returns graph results over MCP. What your client (Claude Code, Cursor, Codex, Windsurf) then forwards to a model, and under whose privacy policy, is governed by that client, not by us.

Outbound calls to a remote LLM provider — used by a few optional features — require explicit consent first: `trace-mcp consent grant <provider>`. Nothing calls a remote provider without it.

---

# Quality gates — configuring thresholds

Source: https://trace-mcp.com/quality-gates.html


`check_quality_gates` reads the `quality_gates` section of
[`.trace-mcp.json`](configuration.md). Configure nothing and three rules still
run; name a rule and your threshold replaces the built-in one for that rule
only. The second half of this page is this repo's own configuration as a worked
example of how the numbers get chosen.

## What runs when you configure nothing

Three rules ship on by default:

| Rule | Threshold | Severity |
|---|---|---|
| `max_cyclomatic_complexity` | 30 | error |
| `max_circular_import_chains` | 0 | error |
| `max_coupling_instability` | 0.9 | warning |

The other five are only checked once you name them — an absent rule is not a
zero threshold, it is no check at all. There is no coverage rule: coverage is
not one of the signals this gate reads.

## Configuring your own

```json
{
  "quality_gates": {
    "enabled": true,
    "fail_on": "error",
    "rules": {
      "max_cyclomatic_complexity": { "threshold": 60, "severity": "warning" },
      "max_dead_exports_percent": { "threshold": 10, "severity": "warning" },
      "max_security_critical_findings": { "threshold": 0, "severity": "error" }
    }
  }
}
```

The eight rule keys:

| Key | Threshold means |
|---|---|
| `max_cyclomatic_complexity` | highest cyclomatic complexity of any indexed symbol |
| `max_coupling_instability` | highest instability (0–1) of any module |
| `max_circular_import_chains` | number of circular import chains |
| `max_dead_exports_percent` | share of exports nothing imports |
| `max_tech_debt_grade` | worst module grade allowed, `A`–`F` |
| `max_security_critical_findings` | critical findings from `scan_security` |
| `max_antipattern_count` | findings from `detect_antipatterns` |
| `max_code_smell_count` | findings from the code-smell scan |

Every rule takes the same fields: `threshold` (a number, or a letter for the
grade), `severity` (`error` or `warning`) and an optional `message` shown when
it trips. `fail_on` decides what turns into a failing run — `error` (default), `warning`
to fail on both, or `none` to report without failing. `enabled: false` turns the
whole gate off.

Start by running `check_quality_gates` with the defaults, then raise only the
rules your codebase legitimately trips — the worked example below is that
process on this repo.

## Worked example: this repo's own thresholds

The values below live in this repository's `.trace-mcp.json`. They are
calibrated against the codebase's actual, reviewed state — re-derive them after
any large refactor round rather than treating them as permanent.

### max_cyclomatic_complexity: 130 (warning)

The generic default of 30 fires on nearly every language/framework plugin in
this repo — dispatch tables for {{ site.data.counts.languages }} languages and {{ site.data.counts.frameworks }} frameworks are
inherently branchy. After decomposing every class that was a genuine
god-object (`DecisionStore` 262→96, `TopologyStore` 125→49,
`IndexingPipeline` 137→115, plus tool-gate/api-routes/memory-tools splits),
the remaining top offenders are all language/framework plugins whose
complexity is domain-necessary, not accidental: `DartLanguagePlugin` (123),
`FastAPIPlugin` (119), `CppLanguagePlugin` (117), `LaravelPlugin` (105),
`ObjCLanguagePlugin` / `OcamlLanguagePlugin` (103). 130 sits just above that
accepted ceiling — today's classes pass silently, but a genuinely new outlier
above it still trips the warning.

### max_security_critical_findings: 2 (error)

`scan_security` is a regex-based scanner with no data-flow analysis (a full
taint/dataflow rewrite was explicitly scoped out — see [comparisons](comparisons.md)).
It cannot see past a runtime-validated identifier or a value's closed
provenance. Two findings are reviewed and accepted as false positives, not
suppressed silently:

- `src/ai/vec-extension.ts:134` — `this.table` is a single hardcoded literal
  set once at field declaration, additionally guarded by
  `assertSafeSqliteIdentifier()` right before the `DROP TABLE` call
  (defense in depth against future code changes, not because this path is
  reachable today).
- `src/daemon/project-manager.ts:704` — `name` is drawn from
  `sqlite_master`'s own table-name catalog (a fixed, developer-controlled
  enumeration of ~30 literal `CREATE TABLE` names), never from project/user
  input.

The threshold is 2, not 0, so a **third** critical finding — new or
otherwise — still fails the gate and forces review. If either of these two
findings' code changes such that the value could become externally
influenced, re-audit before assuming the finding is still a false positive.

### max_circular_import_chains: 0 (error), max_tech_debt_grade: D (warning)

The circular-import rule is left at its default of 0 — no evidence-based reason
to relax it. The tech-debt grade is not on by default at all; `D` is this repo's
own ceiling, set so a module sliding to `F` fails the run.

The gates are read by `check_quality_gates`, `get_tech_debt` and
`get_circular_imports` ([tools reference](tools-reference.md)); where the file
itself lives and how per-project overrides merge is in
[configuration](configuration.md).

---

# System Prompt Routing via tweakcc

Source: https://trace-mcp.com/tweakcc.html


> **Requires:** [tweakcc](https://github.com/Piebald-AI/tweakcc) — a tool that patches Claude Code's system prompts directly.

This is the Claude-Code-specific half of tool routing. The cross-client half —
which tools are advertised at all, and how much of each schema — is
`tools.preset` and friends in [configuration](configuration.md#tool-exposure--agent-behavior); what that
is worth in tokens is measured in [cutting Claude Code token
usage](reduce-claude-code-token-usage.md).

---

## Why system prompt routing?

The common failure mode with trace-mcp routing isn't forgetting — it's **skipping**. Claude sees the CLAUDE.md policy and reaches for `Read` or `Grep` anyway because native tools feel faster under pressure or in long sessions — especially after context compression drops the CLAUDE.md block.

trace-mcp has three enforcement layers:

| Layer | Mechanism | Strength |
|-------|-----------|----------|
| **Base** — CLAUDE.md policy | Soft rules in project instructions | Weakest — ignored under cognitive load or after context compression |
| **Standard** — hooks | PreToolUse guards intercept tool calls at runtime | Medium — stderr warnings, allows Read for edits, catches violations |
| **Max** — system prompt rewrites (this doc) + agent behavior rules | Patch Claude's core tool descriptions via tweakcc; inject anti-sycophancy + goal-driven discipline rules via MCP instructions | Strongest — model internalizes the preference from the start, behaves like a senior engineer by default |

Each layer **adds on top** of the previous ones — nothing gets removed. tweakcc is an optional amplifier, not a replacement.

Picking **Max** during `trace-mcp init` does two things beyond Standard:
1. Installs tweakcc system-prompt rewrites (the 8 files below).
2. Writes `tools.agent_behavior: "strict"` to your global config — this is delivered via MCP instructions to every client (Claude Code, Cursor, Codex, Windsurf), not just CC. See [Agent behavior rules](configuration.md#agent-behavior-rules).

---

## Architecture

### Base (CLAUDE.md only)

```
CLAUDE.md (soft)
  routing policy tables
```

### Standard (current default after `trace-mcp init`)

```
CLAUDE.md (soft)  →  PreToolUse guard (hard)  →  PostToolUse reindex (auto)
  routing policy      blocks Read/Grep/Glob/     index-file after Edit/Write
                      Bash on code files +
                      Agent(Explore) subagents

                  →  PreCompact hook (auto)   →  Worktree hook (auto)
                      injects session snapshot    registers new worktrees
```

### Max (Standard + tweakcc + agent_behavior rules)

```
System prompts (deep)   →  MCP instructions (every session)  →  CLAUDE.md (soft)  →  PreToolUse guard (hard)  →  PostToolUse/PreCompact/Worktree (auto)
  routing built into        tool routing + agent_behavior=       full policy          catches remaining 5%        unchanged
  core tool descriptions    "strict" (anti-sycophancy,            (reinforcement)
                            goal-driven, 2-strike rule)
```

All existing hooks stay. tweakcc adds the deepest layer — the model never sees "use Grep for code search" in its tool descriptions; it sees "use trace-mcp search" from the start. `agent_behavior: "strict"` runs in parallel via MCP instructions, making the agent behave like a senior engineer by default: no flattery, disagreement on wrong premises, no fabrication, no drive-by refactors, verification before reporting "done".

---

## Prompt Rewrites (8 files)

All files are tweakcc prompt fragments. Only the content below the YAML frontmatter is replaced.

### 1. Read files (`system-prompt-tool-usage-read-files.md`)

```
Before reading any source code file, call trace-mcp get_outline to see its
structure first. To read specific symbols, use get_symbol (by symbol_id or fqn)
or get_context_bundle (symbol + its imports, or batch multiple symbol_ids) instead
of reading the whole file. Use Read for non-code files (.md, .json, .yaml, .toml,
.env, .txt, .html, images, PDFs) and when you need complete file content before
editing with Edit/Write. Never use cat, head, tail, or sed to read any file.
```

### 2. Search content (`system-prompt-tool-usage-search-content.md`)

```
To search code by symbol name (function, class, method, variable), use trace-mcp
search — narrow with kind=, language=, file_pattern=, implements=, extends=.
To search for strings, comments, TODOs, or patterns in source code, use trace-mcp
search_text (supports regex, context_lines for surrounding code). For semantic
usages (imports, calls, renders, dispatches), use find_usages. Use Grep only for
searching non-code file content (.md, .json, .yaml, .txt, .env, config files).
Never invoke grep or rg via Bash.
```

### 3. Search files (`system-prompt-tool-usage-search-files.md`)

```
To browse project structure, use trace-mcp get_project_map (summary_only for
overview, or full for detailed structure). To find symbols in specific paths, use
search with file_pattern= filter. To see what's in a specific file, use
get_outline. Use Glob only when finding non-code files by name pattern. Never use
find or ls via Bash for file discovery.
```

### 4. Reserve Bash (`system-prompt-tool-usage-reserve-bash.md`)

```
Reserve Bash exclusively for system commands and terminal operations: builds
(pnpm run build), tests (pnpm test, vitest, pytest), git commands, package managers,
docker, kubectl, and similar. Never use Bash for code exploration — do not run
grep, rg, find, cat, head, or tail on source code files through it. Use trace-mcp
MCP tools for all code reading and searching. If unsure whether a dedicated tool
exists, default to the dedicated tool.
```

### 5. Direct search (`system-prompt-tool-usage-direct-search.md`)

```
For directed codebase searches (finding a specific function, class, or method),
use trace-mcp search directly — it is faster and more precise than text search.
Narrow results with kind= (function, class, method, interface, type, variable),
language=, file_pattern=, implements=, extends=. For text pattern searches in
code, use trace-mcp search_text. Use native search tools only for non-code files.
```

### 6. Delegate exploration (`system-prompt-tool-usage-delegate-exploration.md`)

```
For broader codebase exploration, start with trace-mcp: get_project_map for
project overview, get_task_context for all-in-one task context (replaces manual
chaining of search → get_symbol → Read). When the project is unfamiliar, call
suggest_queries for orientation. Never spawn Agent(Explore) subagents for code
exploration — use get_task_context or get_feature_context instead (50x cheaper).
Agent subagents are only for: writing code in parallel, running tests, web research.
```

### 7. Subagent guidance (`system-prompt-tool-usage-subagent-guidance.md`)

```
Use subagents only for tasks that require actual execution: writing code in
parallel (background workers), running tests, web/external research, or Plan mode.
Never use Agent(Explore) or Agent(general-purpose) for code exploration, review,
or analysis — each subprocess costs ~50K tokens in overhead. Instead use trace-mcp:
get_task_context (all-in-one task context), get_feature_context (NL query),
batch (multiple lookups in one call), find_usages, get_call_graph.
```

### 8. Read first (`system-prompt-doing-tasks-read-first.md`)

```
Do not propose changes to code you haven't understood. Before modifying code, use
trace-mcp to build context: get_outline to see the file's structure, get_symbol
or get_context_bundle to read the relevant symbols, and get_change_impact to
understand the blast radius. For complete task context in one call, use
get_task_context with a natural language description of your task.

Use batch to combine multiple independent trace-mcp calls into a single request
(e.g., get_outline for 3 files + search for a symbol).

For non-code files (.md, .json, .yaml, .toml, .env, .txt, .html), use Read
directly.
```

---

## Verification

After installing the tweakcc rewrites, verify with these test prompts:

| Test prompt | Expected behavior |
|---|---|
| "Find the main function in this project" | Uses trace-mcp `search`, not `Grep` |
| "What does UserService do?" | Uses `get_outline` + `get_symbol`, not `Read` |
| "Show me the project structure" | Uses `get_project_map`, not `Glob` or `ls` |
| "Search for TODO comments" | Uses `search_text`, not `Grep` |
| "Read the README" | Uses `Read` (non-code file — correct) |
| "Search package.json for the version" | Uses `Grep` or `Read` (non-code file — correct) |
| Edit a `.ts` file | PostToolUse hook fires, re-indexes automatically |
| "What breaks if I change this function?" | Uses `get_change_impact`, not guessing |
| "Explore the plugin architecture" | Uses `get_task_context`, not Agent(Explore) |
| "Analyze the indexing pipeline" | Uses `get_task_context`/`get_feature_context`, not Agent |

---

## Combining with hooks (recommended)

System prompt routing and hooks are **complementary**, not exclusive. Run both for maximum enforcement:

- **tweakcc prompts** handle the 95% case — Claude reaches for trace-mcp by default
- **PreToolUse guard** catches the remaining 5% under cognitive load or in very long sessions
- **PostToolUse reindex** keeps the index fresh (zero model overhead)
- **PreCompact hook** injects session snapshot to prevent compaction amnesia
- **Worktree hook** auto-registers new worktrees

This layered approach gives the strongest enforcement with the least friction.

---

## Rollback

To revert: restore original tweakcc prompt files. No changes to CLAUDE.md, hooks, or settings are needed — the existing Standard setup continues to work independently.

---

# Development

Source: https://trace-mcp.com/development.html


## Setup

Read [architecture](architecture.md) first — the two-pass pipeline, the plugin
interface and the storage layout are what most of the code below is arranged
around.

```bash
git clone https://github.com/nikolai-vysotskyi/trace-mcp.git
cd trace-mcp
pnpm install
pnpm run build
```

## Scripts

| Script | What it does |
|---|---|
| `pnpm run build` | TypeScript compilation via tsup |
| `pnpm run dev` | Watch mode (tsup --watch) |
| `pnpm run test` | Run all tests (vitest) |
| `pnpm run test:watch` | Watch mode for tests |
| `pnpm run typecheck` | TypeScript type checking (`tsc --noEmit`) |
| `pnpm run lint` | Same as `typecheck` (legacy alias) |
| `pnpm run format` | Auto-format the repo with Biome |
| `pnpm run format:check` | Check formatting without writing |
| `pnpm run biome:ci` | Full Biome check (formatter + linter) — same as CI |
| `pnpm run serve` | Start MCP server (dev) |
| `node scripts/capture-screenshots.mjs` | Regenerate every docs/site screenshot from a seeded demo state |
| `pnpm --filter trace-mcp-app run check:i18n` | Fail on a user-facing string left inline in an extracted surface |

## Code style — Biome

Formatter and linter are unified under [Biome](https://biomejs.dev). Config lives in `biome.jsonc` at the repo root.

- **Formatter**: 2-space indent, single quotes, semicolons, trailing comma all, 100-col line width. Runs across `src/`, `tests/`, and `packages/app/`.
- **Linter**: only a hand-picked subset is enabled as errors today (correctness + style + selected complexity rules). `recommended: false` — we ramp rules in incrementally rather than turning them all on at once. See `biome.jsonc` for the current set.
- **Pre-commit hook**: `simple-git-hooks` + `lint-staged` run `biome check --write` only on staged files. Set `SKIP_SIMPLE_GIT_HOOKS=1` to bypass for emergencies.
- **CI**: a fast `biome` job runs `biome ci --diagnostic-level=error` and gates the heavier `impact-report` and `app-typecheck` jobs.
- **Editor**: `.vscode/extensions.json` recommends the official `biomejs.biome` extension. JetBrains users can install the [Biome plugin](https://plugins.jetbrains.com/plugin/22761-biome).
- **`git blame`**: `.git-blame-ignore-revs` lists the formatter mass-pass commit. Enable locally with `git config blame.ignoreRevsFile .git-blame-ignore-revs`. GitHub honors it on the web.

### Ramping new lint rules

When promoting a new rule:

1. Add it to `biome.jsonc` at severity `warn` first to see the blast radius (`pnpm exec biome lint --reporter=summary`).
2. If the rule has a safe auto-fix, run `pnpm exec biome lint --write --only=<rule-id>`. Review the diff.
3. For unsafe fixes (e.g. `useExhaustiveDependencies` removing deps, `useButtonType` guessing `type="button"`): hand-fix or scope via `overrides` in `biome.jsonc`.
4. Once violations hit zero, promote severity to `error`.
5. Mass-fix commits should be added to `.git-blame-ignore-revs`.

### Remaining warning burndown

`pnpm run biome:ci` exits clean (**0 errors**). The remaining warnings are the
`noExplicitAny` backlog (~170, scoped to `src/` and `packages/app/` — tests are
overridden to `off` because mocks and AST fixtures intentionally use `any`).

These should be fixed incrementally as files are touched, and require real
domain types — not blanket replacement with `unknown`:

- **Python parsers** (`src/indexer/plugins/integration/{framework/fastapi,framework/flask,orm/sqlalchemy}/index.ts`) — tree-sitter `TSNode` shape varies per language; the existing `any` casts should become discriminated unions over node `type`.
- **CLI surface** (`src/cli.ts`) — Commander.js untyped `opts` objects; should be replaced with per-command `interface CliOpts`.
- **Analytics store** (`src/analytics/`) — `better-sqlite3` row callbacks; `Row` types should be defined per query.
- **Doc/refactoring tools** (`src/tools/{project,refactoring,framework,analysis,quality}/*`) — generic graph visitor patterns; need per-visitor type unions.

Promote `suspicious/noExplicitAny` from warn to error once the backlog is gone.

## Tests

```bash
pnpm run test                       # All tests (1668 tests, ~2s)
pnpm run test --run <pattern>  # Run specific test files
pnpm run test:watch             # Watch mode
```

CI additionally runs the repo's own [quality gates](quality-gates.md) against
this codebase, with thresholds set in `.trace.json` — see
[configuration](configuration.md) for how that file merges with global
settings.

Test files live alongside source or in `tests/`:

```
tests/
├── ai/              # AI pipeline tests
├── ci/              # CI report generator and formatter tests
├── frameworks/      # Framework plugin tests (per-framework)
├── tools/           # MCP tool integration tests
├── integration/     # End-to-end indexing tests
├── e2e/             # CLI and protocol tests
├── db/              # Database layer tests
├── indexer/         # Indexing pipeline tests
├── parsers/         # Language parser tests
├── resolvers/       # Module resolver tests
├── scoring/         # Scoring algorithm tests
└── fixtures/        # Test fixtures (sample projects)
```

---

## Desktop app strings and languages

The app is translated (TRA-379). Every user-facing string lives in a catalogue, not in
the component that renders it, and English is the source language.

```
packages/app/src/shared/i18n/
  locales.ts              # which languages ship, their names, the localStorage key
  catalog/en/<surface>.ts # the strings, one file per surface (= one i18next namespace)
  catalog/ru/<surface>.ts # a translation, same keys — one such directory per language
packages/app/src/renderer/i18n/
  index.ts                # i18next init, setLocale, useLocale, t
  format.ts               # Intl wrappers: relativeTime, formatDate, formatNumber
packages/app/src/main/
  i18n.ts                 # the main process's own i18next instance, and its t
  locale.ts               # the choice mirrored to userData, so main can read it
```

**Why i18next.** Plurals. Russian needs four forms where English needs two, and the
only correct way to choose one is `Intl.PluralRules` — which i18next drives, along
with interpolation and a runtime language switch. We install the resolver and none of
its optional backends or detectors, because the catalogues are compiled in: a desktop
app should not wait on a fetch to paint its first label.

**Adding a string.** Put it in `catalog/en/<surface>.ts` (create the file and add one
line to `catalog/en/index.ts` if the surface is new — one file per surface is what
keeps two extraction slices from editing the same catalogue), add the same key to
every other language, then read it in the component:

```tsx
const { t } = useTranslation('settings');   // components: re-renders on a switch
t('title');
t('projectCount', { count });                // plurals: one key, never a ternary
```

Module-level helpers that are not components import `t` from `renderer/i18n` instead.
Never concatenate a sentence, and never format a date or a number by hand — use
`renderer/i18n/format.ts`.

**Which languages ship.** Ten: `en · de · es · fr · hi · ja · ko · pt-BR · ru · zh`.
English is first because it is the source language and the `fallbackLng`; the rest are
ordered by code. An order that encodes importance only invites the argument about the
order.

The set is weighted to a developer audience rather than to general speaker counts —
that is why Chinese, Japanese and Korean are in it. English stays the source because
every issue and discussion this repo has is in English.

**What the evidence actually supported, and where it ran out (TRA-389).** Worth keeping,
because the next person to ask "who are our users" will otherwise re-run these searches:

- npm exposes no per-country download data for a package. Nothing to read. Don't go
  looking again.
- GitHub traffic gives referrers, not geography — `Google`, `reddit.com`,
  `trace-mcp.com`, one `yandex.ru`. Useless for this question.
- Self-reported profile locations were all that was left: of 102 stargazers, 31 disclose
  one. Largest cluster Russian-speaking (Moscow ×3, Kiev, Bishkek), then China ×2 (plus
  a fork by `iflow-mcp`, a Chinese MCP tooling account), then a Spanish-speaking scatter
  (Tijuana, plus the forks `computo-experto`, `cerebrotecnologico`, `felipecordero`).
  German: one stargazer, one fork.

That signal is thin, and on its own it supported exactly the four languages TRA-389
shipped. The set is ten because #594 chose to weight the developer audience instead of
waiting for evidence this project cannot collect — a judgement call, made knowingly,
not a reading of the data above. Note the cost it accepted: **every language
is a permanent commitment on every future string**, and `catalog-parity.test.ts` will
enforce it on ten catalogues from here on.

**Adding a language.** Add it to `LOCALES` in `shared/i18n/locales.ts`, copy
`catalog/en/` to `catalog/<code>/` and translate it. `catalog-parity.test.ts` then
fails until every key exists and every `{{placeholder}}` survived; nothing else needs
wiring, and the Language control picks the new entry up from `LOCALES`.

Two things a copy-and-translate pass gets wrong. **Plurals are per-language**: write the
forms the language actually has, not a mirror of English's `_one`/`_other`. Chinese has
one (`_other` alone), Russian has four. The parity test compares base keys precisely so
that it cannot force a language into English's shape. And **length**: German and Spanish
run longer than English, so check the workspace table headers, the bulk actions bar and
the segmented controls at the 640×420 window minimum.

**The checks.**

```bash
pnpm --filter trace-mcp-app run check:i18n   # no inline strings in extracted surfaces
pnpm --filter trace-mcp-app run test         # catalogue parity, plurals, Intl output
```

`check-i18n.mjs` scans an allowlist, not the whole tree: string extraction lands
surface by surface, and the `CHECKED` array at the top of the script is how a finished
slice records that it is finished. Extract a surface → add its path there.

**The main process** (the application menu, the tray, dialogs) has no React and
cannot read the renderer's `localStorage`, so the language is mirrored to a one-line
file in `userData` — exactly the arrangement `main/appearance.ts` uses for the theme.
The renderer's `setLocale` sends `set-locale` over IPC, and `main/menu.ts` writes the
file, switches its instance and rebuilds both surfaces: `Menu.setApplicationMenu`
replaces the menu wholesale, there is no per-item relabel. Main-process code calls
`t('menu:file')` from `main/i18n`. Standard macOS items stay on their Electron
`role` — the OS supplies those labels already translated, and hand-translating one
is how a menu ends up half in each language.

## Desktop app update channels

`packages/app/src/main/update-channel.ts` is the single place that decides which
mechanism a platform gets. There is one mechanism now; there used to be two.

| Platform | Mechanism | Notes |
| --- | --- | --- |
| macOS | `electron-updater` + Squirrel.Mac | Driven by `latest-mac.yml`. Squirrel.Mac validates the replacement bundle's code signature, so this only became possible once builds were Developer ID signed and notarized (TRA-436). |
| Windows | `electron-updater` + NSIS | Driven by `latest.yml`. |
| Linux | none | No packaged target today (`linux.target: []`). |

Both channel files come from the top-level `publish` block in
`packages/app/electron-builder.yml` and are uploaded to the GitHub release by
`.github/workflows/release.yml`, which fails the release if either is missing —
an install polling a 404 is never offered another update and says nothing.

Consequences worth knowing before touching this:

- The macOS build packages **both architectures in one job**. electron-builder
  writes one `latest-mac.yml` per invocation listing only that invocation's
  files, so a build matrix would have each leg clobber the other's feed and
  strand one architecture.
- `electron-updater` is the app's only production `dependency`. Everything the
  renderer imports is bundled by Vite and therefore belongs in
  `devDependencies` — that is what keeps the packaged `node_modules` small.

### The staged-zip updater, and the bridge off it (TRA-437)

macOS used to run a second mechanism: the npm postinstall downloaded the release
zip and replaced the `.app` itself, staging the zip beside the bundle when the
app was running so a helper could swap it on exit. It failed fourteen
consecutive times without a single success (TRA-431) and is gone — along with
`scripts/apply-pending-update.mjs`, the pending marker files, and
`~/.trace/app-update-state.json`.

Builds up to and including 3.8.0 are ad-hoc signed and cannot self-update, so
`scripts/postinstall-app.mjs` still swaps **those** bundles — and only those. It
recognises them by the presence of
`Contents/Resources/scripts/apply-pending-update.mjs`, which shipped for exactly
as long as the old updater existed. A bundle without it owns its own updates and
is never written from outside; a version constant would have to be kept in sync
with whatever release-please picks, and this cannot drift.

Once no legacy bundle is left in the field, everything in that script below
`stopRunningDaemon()` can be deleted.

That script keeps one invariant worth knowing before touching it: **only an
installed bundle may become the update target.** An `electron-builder` output
under `release/mac-arm64/` is a real, correctly signed-looking bundle, so plist
validation alone accepts it; `isPlausibleInstallPath` (duplicated in
`scripts/locate-app.mjs` and `packages/app/src/main/install-path.ts`, kept honest
by `install-path.test.ts`) is what rejects build trees and checkouts. Recording
one in `~/.trace/app-location.json` froze a user's install for three major
versions.

`app-location.json` and `app-update-state.json` both live in the CLI state
directory, which `src/global.ts` renamed from `~/.trace-mcp` to `~/.trace` in
TRA-611 — by *renaming* the old directory on first import, so on a migrated
machine the old path is gone. The plain-Node scripts cannot import that module (it is TypeScript, and importing it
would perform the rename as a side effect), so they resolve the directory
through `scripts/trace-home.mjs`, which mirrors the app's
`packages/app/src/main/trace-home.ts::getLauncherDir`: prefer `~/.trace` only
when it is actually on disk, otherwise stay on `~/.trace-mcp`.

---

## Adding a new integration plugin

1. Create a directory under the appropriate category in `src/indexer/plugins/integration/`:

```
src/indexer/plugins/integration/framework/my-framework/
├── index.ts
└── helpers.ts (optional)
```

2. Implement `FrameworkPlugin`:

```typescript
import { FrameworkPlugin, PluginManifest } from '../../../../plugin-api/types.js';

const manifest: PluginManifest = {
  name: 'my-framework',
  version: '1.0.0',
  languages: ['typescript'],
  priority: 20,
};

export const MyFrameworkPlugin: FrameworkPlugin = {
  manifest,

  detect(ctx) {
    // Check package.json, config files, etc.
    return ctx.hasDependency('my-framework');
  },

  registerSchema() {
    return {
      nodeTypes: ['my_framework_route'],
      edgeTypes: ['my_framework_handles'],
    };
  },

  extractNodes(filePath, content, language) {
    // Parse file and return symbols
    return { symbols: [], edges: [] };
  },

  resolveEdges(ctx) {
    // Resolve cross-file relationships
    return [];
  },
};
```

3. Register the plugin in `src/indexer/plugins/integration/framework/index.ts` (or the appropriate category index).

4. Write tests in `tests/frameworks/my-framework.test.ts`.

---

## Adding a new language plugin

1. Create files in `src/indexer/plugins/language/my-lang/`:

```
src/indexer/plugins/language/my-lang/
├── index.ts
└── helpers.ts
```

2. Use tree-sitter for parsing. See existing plugins for patterns (e.g., `typescript/index.ts`).

3. Register in `src/indexer/plugins/language/index.ts`.

---

## Plugin test harness

The `src/plugin-api/test-harness.ts` module provides utilities for testing plugins in isolation:

```typescript
import { createTestHarness } from '../src/plugin-api/test-harness.js';

const harness = createTestHarness(MyPlugin);
const result = await harness.indexFile('test.ts', sourceCode);
expect(result.symbols).toContainEqual(expect.objectContaining({ name: 'myFunction' }));
```

## Screenshots — one script, one seeded state

Every screenshot in `README.md` and on trace-mcp.com is produced by
`scripts/capture-screenshots.mjs`. Do not take them by hand: hand-taken shots
carry whatever happened to be on the machine — a developer's own project list,
a `Daemon unreachable` banner, half-loaded skeletons — and nothing records what
version of the app they show.

```bash
pnpm run build                       # the CLI bundle the demo daemon runs from
pnpm --dir packages/app run build    # the renderer being photographed
node scripts/capture-screenshots.mjs             # regenerate everything
node scripts/capture-screenshots.mjs app-graph   # just one (marker left alone)
node scripts/capture-screenshots.mjs --now       # …without waiting for an idle machine
node scripts/capture-screenshots.mjs --check     # are the committed ones stale?
```

The run launches the real Electron window against a seeded demo state and
writes WebP files into `docs/images/`. It does not touch the daemon you already
have running, your `~/.trace`, or your project registry: the demo daemon
gets its own port and its own `TRACE_MCP_DATA_DIR`, the demo projects are
`git archive` extracts of this repo at HEAD placed under `/tmp/trace-mcp-demo`,
and Electron gets a throwaway Chromium profile. Nothing in the frame identifies
a machine or a person.

**The frame is a photograph of the window, not of the web contents.** macOS
draws the traffic lights, the rounded corners and the sidebar's vibrancy
outside the renderer, so `Page.captureScreenshot` — the obvious way to do this —
returns something indistinguishable from a browser tab, and that is what got
published once (TRA-390). Instead the script asks the main process for the
window's CGWindowID over its Node inspector and hands it to
`screencapture -o -l<id>`: the real window, no drop shadow, rounded corners
returned as alpha. This makes the script macOS-only, and it steals focus for
the length of the run — the window has to be key, or the buttons photograph
grey.

**So it waits until nobody is at the machine.** Owning the screen is not
optional here and both ways out were measured and rejected:
`webContents.capturePage()` on an unshown window returns square opaque corners
and no buttons, and `showInactive()` plus `screencapture` returns the corners
but grey buttons — both are frames `checkWindowChrome` refuses. What the run
can do is not take the screen from somebody: it reads `HIDIdleTime` and defers
(exit code 75, nothing written) unless the machine has been untouched for five
minutes, and it activates the app once per run rather than once per shot. Pass
`--now` when you are the one asking for it and are willing to lose the front
for a couple of minutes.

**Every frame is inspected before it becomes a file.** `checkWindowChrome`
looks for the two things a capture of the web contents can never have —
transparent rounded corners, and the three buttons in colour in the top-left
strip — and throws with the reason when either is missing. A chrome-less
capture fails the run instead of quietly replacing a good image.

**Adding a screenshot is a data change.** Append an entry to
`scripts/screenshots.manifest.json` — the surface to open, which controls to
click, the appearance, and the `alt` text — and re-run the script. The `alt` in
the manifest is the same string that belongs in `README.md` and
`docs/index.html`; keep them equal when a screenshot's content changes, because
stale alt text is both an accessibility bug and an SEO one.

**Freshness.** `docs/images/screenshots.json` records the app version and the
commit of the last change under `packages/app/src/renderer` / `src/main`.
`--check` compares that against HEAD and exits non-zero with a reason when the
UI has moved on — that is the signal the docs and SEO autopilots read, so they
never have to eyeball an image to know whether it is current.

---

# Tool response token cost

Source: https://trace-mcp.com/perf/response-tokens.html



Measured 2026-09-05 on darwin 25.5.0 / arm64, trace-mcp
{{ site.data.response_tokens.measured_build.version }}
(`{{ site.data.response_tokens.measured_build.commit }}`) — the build stamp
travels with the figure to every surface that quotes it, and the
[preregistration](./prereg-response-tokens.md) states the bar and the verdict
(this run publishes as a **miss**, on the reduction half of the bar). Against
trace-mcp's own repo (2 159 files, 11 134 symbols) over a real stdio
`tools/call` round-trip. TRA-880, extended to the tail by TRA-945. Reproduce
with:

```
pnpm run build && npx tsx scripts/bench-response-tokens.ts [repoPath]
```

Token column is the median of three runs, a real `o200k_base` count of the
response text, not an estimate. Call volume is this machine's
`~/.trace/savings.json` ({{ site.data.response_tokens.calls_store_total }} calls
since the store was created) — real usage, one machine, never an average user.

## What was wrong

The *advertised surface* side of the token story has been measured and guarded
for weeks (`preset-surface-budget.test.ts`). The *response* side never was.

`src/savings.ts` scored a call before the tool ran: `recordCall(name)` took a
hand-written `RAW_COST_ESTIMATES[name]`, multiplied it by a flat
`COMPRESSION_RATIO = 0.15`, and booked the difference as saved. The gate
(`src/server/tool-gate-helpers.ts`) was the only caller and never passed a real
count. So `tokens_saved` was **`calls x constant`** — arithmetically confirmable
in the store: 5 123 `search_text` calls, 13 063 650 saved, exactly 2 550 each.

That number is not internal. It is the counter on the homepage and in the README
(`docs/_data/savings.yml`), and `calls`/`tokens_saved` ride the usage ping.

## The measurement

{{ site.data.response_tokens.tools_measured }} tools, covering **97.2%** of
recorded call volume. Ratio is measured response ÷ the raw `Read`/`Grep` the
tool is credited with replacing; **above 1.00 means the tool costs more than
what it stands in for.**

| tool | calls (real) | raw baseline | measured response | measured/baseline |
|---|---|---|---|---|
| `search_text` | 5,125 | 3,000 | **1,722** | 0.57 |
| `get_outline` | 4,461 | 1,200 | **1,427** | 1.19 |
| `search` | 4,441 | 600 | **924** | 1.54 |
| `get_symbol` | 2,687 | 800 | **294** | 0.37 |
| `find_usages` | 440 | 1,000 | **975** | 0.97 |
| `get_project_map` | 351 | 1,500 | **568** | 0.38 |
| `get_index_health` | 211 | 500 | **297** | 0.59 |
| `get_tests_for` | 96 | 800 | **90** | 0.11 |
| `get_complexity_report` | 80 | 800 | **1,820** | 2.27 |
| `get_feature_context` | 73 | 4,000 | **7,443** | 1.86 |
| `get_env_vars` | 66 | 500 | **13** | 0.03 |
| `get_dead_code` | 53 | 1,200 | **2,725** | 2.27 |
| `get_context_bundle` | 39 | 6,000 | **127** | 0.02 |
| `get_changed_symbols` | 38 | 500 | **1,224** | 2.45 |
| `get_task_context` | 32 | 8,000 | **5,383** | 0.67 |
| `check_quality_gates` | 24 | 500 | **121** | 0.24 |
| `get_circular_imports` | 21 | 500 | **76** | 0.15 |
| `check_duplication` | 19 | 500 | **272** | 0.54 |
| `check_claudemd_drift` | 17 | 500 | **1,099** | 2.20 |
| `scan_security` | 16 | 500 | **38** | 0.08 |
| `list_projects` | 15 | 500 | **901** | 1.80 |
| `get_call_graph` | 14 | 1,500 | **1,421** | 0.95 |

Those {{ site.data.response_tokens.calls_weighted }} calls cost
{{ site.data.response_tokens.measured_tokens }} measured tokens against a
{{ site.data.response_tokens.baseline_tokens }}-token baseline —
**{{ site.data.response_tokens.reduction_pct }}% fewer**, or
{{ site.data.response_tokens.credited_reduction_pct }}% if you floor the losing
tools at zero the way the corrected counter does. That is the figure the
homepage and the README quote in place of the old "~40–50% on average"
(TRA-904). It is generated into `docs/_data/response_tokens.json` by
`npx tsx scripts/gen-response-tokens-data.ts` from this table's two inputs, so
no surface can retype it. The baseline half is still an estimate — see the last
section.

Three things the table says:

1. **0.15 is wrong on every tool that matters.** The four busiest (88% of all
   calls) measure 0.37–1.54. The assumption is off by 2.5x on the best of them.
2. **{{ site.data.response_tokens.tools_costing_more }} of the
   {{ site.data.response_tokens.tools_with_baseline }} cost more than the
   baseline they replace** — and, before the counter was corrected, were still
   booking a positive number on every call. It was ten of twenty-two until
   TRA-952 reshaped the three worst (below); the ones left are led by
   `get_complexity_report` and `get_dead_code` at 2.27x. Each is a
   response-shaping defect: a default `depth`/`limit` too generous for what the
   caller asked.
3. **A few are far better than claimed** — `get_context_bundle` at 0.02 and
   `get_env_vars` at 0.03 were being under-credited by an order of magnitude.

## The tail, and the tools with nothing to compare against

TRA-880 measured twelve tools (88.4% of calls) and published as a miss on
coverage. Measuring the remaining twelve found something the head could not
show: **some tools have no baseline at all.**

A savings figure is "what a `Read`/`Grep` would have cost, minus what we
returned". `register_edit` is a notification that a file changed; `reindex`
rebuilds an index. There is no file read an agent could have run instead, so
that subtraction has no left-hand side. `DEFAULT_RAW_COST = 500` was supplying
one anyway — and `register_edit` is the **fourth busiest tool on this machine**,
1 289 calls. Across the whole store, 1 731 calls to mutating tools had booked
**~736 000 tokens of savings that never existed**, 3.0% of everything the
counter had ever claimed.

Fixed in `src/savings.ts`: `NO_BASELINE_TOOLS` credits zero. The response is
still counted on the spend side, because the agent still paid for it:

| tool | calls | baseline | measured response | tokens spent, credited zero |
|---|---|---|---|---|
| `register_edit` | 1,289 | — | **345** | 444,705 |
| `reindex` | 184 | — | **59** | 10,856 |

That is {{ site.data.response_tokens.overhead_calls }} calls and
{{ site.data.response_tokens.overhead_tokens }} tokens of pure overhead — real
cost with no counterfactual. Counting it on the spend side and nothing on the
baseline side gives the all-in number:
**{{ site.data.response_tokens.reduction_pct_incl_overhead }}%**. That is what a
session costs; the {{ site.data.response_tokens.reduction_pct }}% above is what
a lookup costs. Neither is wrong; they answer different questions, and the lower
one is the one to plan a budget against.

`src/tools/register/__tests__/no-baseline-tools.test.ts` fails CI if a tool that
describes itself as mutating is left out of the set, so the next one cannot
quietly start booking savings again.

### What closing the tail did to the headline

The two left-hand columns are frozen literals: they record what those runs
measured, so a later re-measurement cannot rewrite them. Only the TRA-952 column
is live.

| | TRA-880 (12 tools) | TRA-945 (24 tools) | TRA-952 (shaped) |
|---|---|---|---|
| coverage of recorded calls | 88.4% | 97.2% | **97.2%** |
| net `reduction_pct` | 29.3% | 21.1% | **{{ site.data.response_tokens.reduction_pct }}%** |
| credited | 35.2% | 32.6% | {{ site.data.response_tokens.credited_reduction_pct }}% |
| all-in, incl. no-baseline overhead | not computed | 19.5% | {{ site.data.response_tokens.reduction_pct_incl_overhead }}% |
| tools costing more than their baseline | 4 of 12 | 10 of 22 | **{{ site.data.response_tokens.tools_costing_more }} of {{ site.data.response_tokens.tools_with_baseline }}** |

The tail was more expensive than the head, in both directions: it contained the
worst per-call ratios in the product and the calls that should never have been
scored. Fixing the coverage miss produced a reduction miss.

## The fix

`SavingsTracker.recordActualTokens(tool, tokens)` corrects the pre-call guess
once the response exists. `recordCall` stays where it is, before the tool runs,
because budget clamping and dedup both read the session totals first — this is a
two-phase estimate-then-reconcile, not a move.

Four things the correction has to get right, all found in review and guarded in
`tests/tools/savings.test.ts`:

- **A response bigger than its baseline credits zero**, not a fat positive.
- **A failed call credits zero** (`recordFailedCall`). Scored as payload, a
  4-token error from `get_task_context` would have booked 7 996 saved — more
  than any real answer to the same call. Applies to error responses and to
  throws alike.
- **`batch` is corrected too.** It dispatches handlers directly and never goes
  through the gate, so every batched call would otherwise have kept the guess.
- **Measured last, on the wire bytes.** `enrichResponse` adds fields and
  `applyWireFormat` can re-encode into a denser format; measuring before either
  books a number the client never receives. An empty response is a measured
  zero, not a missing one.

## How the numbers are collected

Three runs per tool, median published, min and max printed. That is not
ceremony: a single sample recorded `get_task_context` at 5 383 tokens and then
at 8 357 minutes later on the same commit. The spread turned out not to be
variance but a **degraded surface** — when a daemon is already running, the
stdio session proxies to it and the session's own `--preset` is ignored, so
twelve of the twenty-four tools answer `Tool X disabled` and the bench was
about to publish those error strings as measurements. The harness now aborts on
any errored call rather than writing it to the artifact.

Within one healthy session the responses are near-deterministic: every tool
above has a min–max spread of 0–3 tokens.

## What is still an estimate, and what to do next

`RAW_COST_ESTIMATES` — "what a `Read`/`Grep` would have cost instead" — is still
hand-written and unvalidated, so the savings *baseline* remains a guess even
though the response side is now measured. That is the next measurement, not this
one: it needs a real counterfactual (the same question answered with
`Read`/`Grep`, tokens counted), which is what `benchmarks/pr-context-benchmark`
does for PR context and nothing does for tool calls.

One caveat on `get_outline`: it read 1 427 tokens against 1 056 in
TRA-880, on the same target file, because the change that added
`NO_BASELINE_TOOLS` grew `src/savings.ts` by ~70 lines. The bench measures a
live repository, so its own commits move its numbers. That is a property of the
corpus, not noise, and it is why the corpus size is stated at the top.

## What shaping the three worst tools did (TRA-952)

The first three tools on that follow-up list have been reshaped, and the table
above is the after. What each one was returning that nobody asked for:

| tool | before | after | what came out |
|---|---|---|---|
| `list_projects` | 5,240 (10.48x) | **901 (1.80x)** | 98 subprojects, three absolute paths each. `call_project_tool` only accepts registered roots, so a subproject was never a valid next call. Now `include_subprojects`, default off. |
| `get_call_graph` | 5,263 (3.51x) | **1,421 (0.95x)** | Both directions expanded at every level, so depth 2 answered "what else does my caller call" — 64 of 75 nodes. Each branch now keeps its own direction. |
| `get_dead_code` | 4,819 (4.02x) | **2,725 (2.27x)** | 50 of 341 candidates in one page, for a list the caller verifies entry by entry. Default is 25; `total_dead` is unchanged, so nothing is hidden. |

No response field was dropped and no schema changed: the subprojects list is
still available on request, the call graph still reaches the same depth, and a
deeper dead-code page is still one `limit` away.

**And it moved the headline by 0.1 points.** Those three tools are 82 of 18,319
recorded calls. The weighted figure is decided by `search_text`, `get_outline`
and `search`, which are 78% of call volume between them — `search` at 1.54x and
`get_outline` at 1.19x are now the whole of the negative block that matters.
Worst-ratio-first was the right order for finding defects and the wrong one for
moving the number; the next response-shaping issue should be `search`, on
volume.

`get_dead_code` is left at 2.27x on purpose. Its baseline is 1,200 tokens —
"what a `Read`/`Grep` would have cost instead" for a whole-repo dead-code sweep
over 3,412 exports, which is not a credible 1,200 tokens. Cutting the tool
further would buy the ratio by answering less; the honest correction there is on
the baseline half, which is still an estimate.

### Three rows moved for reasons that are not this change

`get_changed_symbols` (521 → 1,224), `find_usages` (1,122 → 975) and
`search_text` (1,659 → 1,722) were not touched. `get_changed_symbols` reports the
diff of whatever working tree it runs on, so the two runs asked it different
questions and its row is not comparable between them at all. The other two track the corpus: the repo gained files and symbols between
the two measurements. Same caveat as `get_outline` above, and the reason the
per-tool ratios are the durable part of this page and the aggregate is not.
