CLAUDE.md · git:20260905.7257442 · 2026-09-05 · sha256 073d81d35ae67890

CLAUDE.md git:20260905.7257442A

Immutable. This exact content is served forever at /api/v1/blob/073d81d35ae67890.

# trace-mcp Development Guide

## What this project is

trace-mcp is a framework-aware code intelligence MCP server. It indexes source code into a dependency graph with full-text search, understanding 87 framework integrations across 81 languages. It exposes MCP tools for navigation, impact analysis, and framework-specific queries.

## Agent Behavior — read before every task

These rules override tool routing and workflow checklists when in conflict.

### No flattery, no filler
Skip openers: "Great question", "You're absolutely right", "Excellent idea", "I'd be happy to", "Absolutely!". Start the response with the answer or the action. No ceremonial closings.

### Disagree when the premise is wrong
If the user's request rests on a false assumption, say so before doing the work. Agreeing with wrong premises to be polite is worse than pushback. Example: user asks to optimize a function that's not on the hot path — say that, then ask whether to proceed anyway.

### Never fabricate
Not file paths, not symbol names, not API signatures, not test output, not commit hashes. If unsure, call `search` / `get_symbol` / run the command. "I don't know, let me check" beats a plausible-sounding guess.

### Stop when confused
If the task has two plausible interpretations and the choice materially changes the diff, ask. Do not pick silently and proceed. Trivial/reversible tasks (typo, rename local var) — proceed.

### Goal-driven execution
Rewrite vague asks into verifiable goals before writing code:
- "Add validation" → "Write failing tests for invalid inputs (empty, malformed, oversized), make them pass."
- "Fix the bug" → "Write a failing test reproducing the symptom, make it pass."
- "Refactor X" → "Ensure existing test suite passes before and after, no public API changes."
- "Make it faster" → "Benchmark current hot path, identify bottleneck with profiling, show benchmark improved."

Never report "done" based on a plausible-looking diff. Run the test/build/typecheck. Plausibility is not correctness.

### 2-strike session rule
After two failed attempts at the same issue, stop. Summarize what was tried, what was learned, and ask the user to reset the session with a sharper prompt. Do not grind through a third attempt in a polluted context — fresh session + better prompt beats long session + accumulated failures.

### English only in code and content
All text committed to the repository — UI strings, button labels, tooltips, error messages, log output, code comments, docstrings, README/docs, commit messages — MUST be in English. This is a hard rule even when the user is chatting with you in another language. If the user writes in Russian, answer them in Russian, but every string you put in a file is English. No exceptions, including ad-hoc test fixtures or TODO comments. If unsure whether a fixture counts, default to English.

## Build & Test

```bash
pnpm run build          # TypeScript compilation
pnpm run test               # Vitest (all tests)
pnpm run test --run <pattern> # Run specific test
```

## trace Tool Routing — MANDATORY (for AI agents working ON this codebase)

**HARD RULE: NEVER use Read, Grep, Glob, or Bash (ls, find, cat, head, tail) to explore or navigate source code (.ts, .js, .py, etc.). ALWAYS use trace tools instead. This is not a suggestion — it is a requirement. Violations waste tokens and produce worse results.**

Since trace is its own MCP server, when developing it you MUST use trace tools to navigate the codebase. This guidance is split into focused subsections below: Navigation & Search, Refactoring & Codemods, Token & Output Optimization, and Plugin & LSP Architecture.

## Navigation & Search

### Decision Matrix — USE THESE, NOT native tools

| Task | trace tool | NEVER use |
|------|------------|-----------|
| Find a function/class/method | `search` | ~~Grep~~ ~~Glob~~ |
| Understand a file before editing | `get_outline` | ~~Read (full file)~~ |
| Read one symbol's source | `get_symbol` | ~~Read (full file)~~ |
| What breaks if I change X | `get_change_impact` | ~~guessing~~ |
| Who calls this / what does it call | `get_call_graph` | ~~Grep~~ |
| Find all usages of a symbol | `find_usages` | ~~Grep~~ |
| Get context for a task | `get_feature_context` | ~~reading 15 files with Read~~ |
| Find tests for a symbol | `get_tests_for` | ~~Glob + Grep~~ |
| Find untested symbols (deep) | `get_untested_symbols` (classifies "unreached" vs "imported_not_called") | ~~manual audit~~ |
| Project overview | `get_project_map` (summary_only=true) | ~~Bash ls~~ |
| List files in a directory | `get_outline` or `search` | ~~Bash ls/find~~ ~~Glob~~ |
| Find where something is defined | `search` | ~~Grep~~ |
| Understand project structure | `get_project_map` | ~~Bash find~~ |
| Move a symbol between files | `apply_move` { symbol_id, target_file } | ~~manual cut/paste + Edit~~ |
| Move/rename a file | `apply_move` { source_file, new_path } | ~~manual mv + Edit imports~~ |
| Change function signature | `change_signature` { symbol_id, changes } | ~~manual Edit across files~~ |
| Preview any refactoring | `plan_refactoring` { type, ... } | ~~guessing~~ |

## Token & Output Optimization

### Token optimization — MANDATORY

**Use `batch` for multiple independent queries.** When you need 2+ independent tool calls, combine them into a single `batch` call. This saves round-trips and reduces context overhead.

```
batch({ calls: [
  { tool: "get_outline", args: { path: "src/server/server.ts" } },
  { tool: "get_outline", args: { path: "src/config.ts" } },
  { tool: "search", args: { query: "registerTool", kind: "method" } }
] })
```

**Use `get_context_bundle` instead of get_symbol chains.** When you need a symbol + its imports, or multiple symbols at once:
- Single: `get_context_bundle({ symbol_id: "..." })` — returns symbol + imports
- Batch: `get_context_bundle({ symbol_ids: ["...", "..."] })` — deduplicates shared imports

**NEVER read the same file twice.** Use `get_outline` once → then `get_symbol` for specific symbols.

**NEVER use Agent(Explore) or Agent(general-purpose) for code exploration/understanding/review.** This is the single largest source of token waste — each Agent subprocess loads full system prompt + CLAUDE.md + memory (~50K tokens overhead) before doing anything. Use trace-mcp tools instead:

| Instead of Agent… | Use this |
|---|---|
| "Explore codebase structure" | `get_project_map` (summary_only=true) |
| "Explore/analyze/review module X" | `get_task_context` { task: "understand module X" } |
| "Find where X is used" | `find_usages` or `get_call_graph` |
| "Understand how feature Y works" | `get_feature_context` { query: "feature Y" } |
| "Check architecture of Z" | `get_task_context` { task: "architecture of Z" } |
| Multiple independent lookups | `batch` with multiple tool calls |

Agent is ONLY acceptable for: writing code in parallel (background workers), running tests, web research, or Plan mode.

**Monitor waste:** the trace-mcp guard hook (PreToolUse) tracks repeated reads, Bash grep on source paths, and missed trace-mcp opportunities, and replies inline with corrective hints. Honour those hints.

**After editing a file:** Call `register_edit` { file_path: "path/to/file" } to reindex that single file and invalidate caches. Much lighter than full `reindex`. Do this after every Edit/Write to keep the index fresh. If `_duplication_warnings` appears in the response, review the referenced symbols — you may be duplicating existing logic.

**Before creating new functions/classes:** Call `check_duplication` { name: "functionName", kind: "function" } to verify no similar symbol exists. Prevents reinventing existing logic.

## Refactoring & Codemods

### The ONLY cases where native tools are allowed

- **Read**: ONLY for non-code files (.md, .json, .yaml, .env, config), OR immediately before using Edit on a file you need to modify
- **Grep**: ONLY for searching non-code file content (config, markdown, yaml)
- **Glob**: ONLY for finding non-code files by name pattern
- **Bash**: ONLY for running builds (`pnpm run build`), tests (`pnpm run test`), git commands, or other CLI operations — NEVER for code exploration
- **Edit**: ONLY for unique, one-off changes. If you are about to make the same kind of change (same pattern/intent) **2 or more times** — whether in one file or across files — STOP and use `apply_codemod` instead. This includes: adding async/await, updating function signatures, fixing import paths, adding/removing keywords, wrapping calls. **No exceptions. No "it's just a few edits". Use apply_codemod.**

### Read-before-Edit optimization (saves ~80K tokens/day)

When you need to Edit a file, minimize what you Read:
1. **Use `get_outline` first** to find the line range of the symbol you need to edit
2. **Read only the relevant range**: `Read { file_path, offset: startLine, limit: endLine - startLine + 10 }` — not the whole file
3. **Never re-read a file you already read** in this session unless it was modified since. If you need a reminder of structure, use `get_outline`
4. **For files >200 lines**: ALWAYS use offset/limit. Reading a 500-line file to edit 5 lines wastes ~400 lines of tokens
5. **After Edit**: call `register_edit` to reindex — do NOT re-read the file to verify (Edit tool confirms success)

### TOON output format — when to use

13 tools support `output_format: "toon"` for lossless Token-Oriented Object Notation. Pass it whenever the response will be consumed by an LLM. Lossless is a mathematical property of the encoding; the structural win on tabular payloads is a property of the format spec. Actual token savings depend on the consumer's tokenizer and payload shape — see [docs/toon-savings.md](docs/toon-savings.md) for measurements on this repo's self-index. Default remains JSON; TOON is strictly opt-in.

Allowlist:

- `analyze_perf`
- `get_changed_symbols`
- `get_complexity_report`
- `get_coupling`
- `get_feature_context`
- `get_git_churn`
- `get_outline`
- `get_pagerank`
- `get_refactor_candidates`
- `get_risk_hotspots`
- `get_untested_symbols`
- `query_decisions`
- `search`

For `search_text`, `grouping: "by_file"` is the default (lossless path-dedup in JSON) and beats TOON — the two don't stack and grouping is the cheaper win. Pass `grouping: "flat"` only if you need the ungrouped `matches[]` shape.

Other tools are NOT TOON-enabled — they regressed in measurements (heterogeneous payloads with nested objects or arrays land in TOON's list mode, which costs more tokens than JSON). Don't pass `output_format: "toon"` to a tool not on this list; it will be rejected by schema validation. Drift guardrail: `src/tools/register/__tests__/toon-drift.test.ts` keeps the schema, description, and allowlist in sync.

### Cross-tool routing hints

Tools that overlap (`search` / `search_text` / `find_usages` / `get_feature_context`, the context family, the impact family) point at each other in their descriptions. That prose is the only routing mechanism reaching every MCP client — the PreToolUse guard hook is Claude Code only, `src/init/ide-rules.ts` covers Cursor/Windsurf. The families are declared in `src/tools/tool-families.ts`, enforced by `src/tools/register/__tests__/family-routing-drift.test.ts`, and protected from the `description_verbosity: minimal` collapse. Adding a tool to one of those families means adding a sibling pointer to its description — and trimming elsewhere to keep net description bytes flat.

## Plugin & LSP Architecture

### Plugin architecture

- Language plugins: `src/indexer/plugins/lang/` — one per language (ts, python, go, etc.)
- Integration plugins: `src/indexer/plugins/integration/` — framework-specific (api/, framework/, orm/, etc.)
- Each plugin implements `LanguagePlugin` or `FrameworkPlugin` interface from `src/plugin-api/types.ts`
- Plugin registry: `src/plugin-api/registry.ts`

### LSP enrichment subsystem

- `src/lsp/` — separate subsystem (not a plugin) for compiler-grade call graph enrichment
- **Opt-in** via `lsp.enabled: true` in config. Disabled by default, zero overhead when off.
- Pipeline integration: Pass 3 (after tree-sitter indexing + edge resolution, before env indexing)
- Key files:
  - `src/lsp/bridge.ts` — orchestrator (entry point)
  - `src/lsp/client.ts` — JSON-RPC client over stdio (Content-Length framing)
  - `src/lsp/lifecycle.ts` — LSP server process management (lazy start, concurrent limits, shutdown)
  - `src/lsp/enrichment.ts` — core algorithm: callHierarchy/prepare → outgoingCalls → edge upgrade/insert
  - `src/lsp/mappers.ts` — symbol ↔ LSP position mapping
  - `src/lsp/config.ts` — auto-detection of available servers (tsserver, pyright, gopls, rust-analyzer)
  - `src/lsp/protocol.ts` — hand-written LSP type definitions (zero external deps)
- Edges have a `resolution_tier` column (a field value, not a tool) with possible values ranked `scip_resolved` > `lsp_resolved` > `ast_resolved` > `ast_inferred` > `text_matched`
- Config schema: `lsp` section in `TraceMcpConfigSchema` (`src/config.ts`)

## Workflow Checklists — MANDATORY

These workflows define which trace-mcp tools MUST be used at each stage. Follow them — they are not optional.

### Starting any task
1. `get_project_map` (summary_only=true) — orient yourself
2. `get_task_context` { task: "description of what you're doing" } — get all relevant code in one call
3. Do NOT read files one by one. `get_task_context` replaces manual chaining of search → get_symbol → Read.

### Before refactoring
1. `assess_change_risk` { file_path / symbol_id } — understand risk level before touching anything
2. `get_refactor_candidates` — find what actually needs refactoring (don't guess)
3. `get_change_impact` — know what breaks before you break it
4. `get_complexity_report` { file_path } — quantify current complexity

### Renaming a symbol
1. `check_rename` { symbol_id, target_name } — collision detection FIRST
2. `apply_rename` { symbol_id, new_name } — renames across ALL files (definition + imports). Also scans non-code files (YAML/JSON/env) for mentions.
3. Review the result's `non_code_suggestions` field (not a separate tool — a property of `apply_rename`'s response) — apply manually if relevant
4. NEVER rename manually via Edit with replace_all — it misses import sites and cross-file references

### Moving a symbol or file
1. `plan_refactoring` { type: "move", symbol_id, target_file } — preview all edits FIRST
2. Review the edit plan: import rewrites, barrel updates, sibling dependency imports
3. `apply_move` { symbol_id, target_file, dry_run: false } — apply the move
4. Or for file moves: `apply_move` { source_file, new_path, dry_run: false } — moves file + rewrites all import paths
5. `register_edit` for all modified files after the move

### Changing a function signature
1. `plan_refactoring` { type: "signature", symbol_id, changes } — preview all edits FIRST
2. Review: definition changes + all call site updates
3. `change_signature` { symbol_id, changes, dry_run: false } — apply changes
4. The `changes` array passed to `change_signature` accepts these change-object keys (not standalone tools): `add_param`, `remove_param`, `rename_param`, `reorder_params`
5. Params added via `add_param` with a `default_value` field set don't require call site changes; params without one get an `undefined` placeholder

### Previewing any refactoring
- `plan_refactoring` { type: "rename"|"move"|"extract"|"signature", ... } — returns all {old_text, new_text} pairs without applying
- Always use this before destructive refactoring to review the blast radius

### Bulk mechanical changes (add async/await, update patterns, fix imports across many files)
1. `apply_codemod` { pattern, replacement, file_pattern, dry_run: true } — preview changes first (dry_run is default)
2. Review the preview — check matched files, context lines, replacement correctness
3. `apply_codemod` { pattern, replacement, file_pattern, dry_run: false } — apply changes
4. If >20 files affected, must add `confirm_large: true`
5. Use the `filter_content` parameter (on `apply_codemod`, not a separate tool) to narrow scope (e.g. only files containing "extractNodes")
6. Use `multiline: true` for patterns spanning multiple lines
7. NEVER use Edit for the same mechanical change 2+ times — use apply_codemod. This is a HARD RULE, not a guideline. Even "just 3 edits" is a violation.

### Deleting code
1. `get_dead_code` { file_pattern } — verify code is actually dead (multi-signal detection)
2. `get_dead_code` { file_pattern, mode: "exports_only" } — find unused exports
3. `remove_dead_code` { symbol_id } — safe removal with orphan import detection
4. NEVER delete code without verifying it's dead first

### Before PR / commit
1. `scan_security` { rules: ["all"] } — OWASP Top-10 vulnerability scan
2. `check_quality_gates` { scope: "changed" } — quality gate validation
3. `detect_antipatterns` {} — performance antipattern scan
4. `compare_branches` { branch: "current" } — symbol-level diff for PR description
5. Fix any critical/high findings before committing
6. Commit with the repo's configured git identity — never `git config user.email` to an
   agent address. It resolves to no GitHub login, so `license/cla` hangs at `pending`
   forever and the PR reads red. Attribute the agent with an `Agent:` message trailer
   (see CONTRIBUTING.md).

### Bug fixing
1. `predict_bugs` {} — prioritize which files to investigate
2. `get_risk_hotspots` {} — high complexity + high churn = likely bug location
3. `get_task_context` { task: "fix the bug description" } — get relevant code
4. `taint_analysis` {} — if security-related, trace untrusted data flows

### Upgrading dependencies
1. `plan_batch_change` { package: "name", from_version, to_version } — impact analysis
2. Review all affected files and import references
3. `check_quality_gates` { scope: "changed" } — verify no degradation after upgrade

### Periodic health checks (once per session)
1. `audit_config` {} — check for stale references in CLAUDE.md/settings
2. `self_audit` {} — dead exports, untested code, hotspots
3. `get_tech_debt` {} — per-module tech debt grades

## External listings and distribution

`ops/distribution.md` is the ledger of every directory and registry that lists
trace-mcp: what each one currently shows, how it can be changed, what it costs,
and what previous runs already ruled out. **Read it before touching any listing,
directory submission, or registry, and update it in the same change.** It exists
because that work is invisible from the code — without it each run re-discovers
the same closed doors and sometimes decides the opposite of the last one.

Numbers quoted to the outside world come from `docs/_data/counts.yml`. Never
hand-type a tool/language/framework count into a listing, a form, or `server.json`.

**The other half of that ledger is not in this repo.** Whether a listing ever
sent a visitor, what the outside world says about us channel by channel, and the
unposted launch drafts live in the private
[`trace-mcp-private`](https://github.com/nikolai-vysotskyi/trace-mcp-private)
repo (`ops/arrivals.md`, `ops/user-signal.md`, `ops/launch-*.md`), moved there on
2026-09-05 because that material names people and analyses other communities'
moderation rules. `ops/distribution.md` stayed here on purpose, so the read
before a listing change still costs one file in the repo you already have. If a
task is about *whether distribution is worth doing* rather than *how to submit*,
read `ops/arrivals.md` there first — the answer has held across four windows and
it should change what you decide.

`ops/index-coverage.md` is the companion ledger for the site itself: per-URL
Search Console coverage and last-crawl dates. **Read it before any SEO or docs
work aimed at Google, and update it in the same change** — it is what tells a run
whether Google has even fetched the pages it is about to change.

## Client start-block token costs

`ops/context-block-levers.md` is the measured ledger of what a user can switch
off in Claude Code / the Agent SDK to shrink the tokens sent before the first
question: per-tool schema prices, the system-prompt options, and the traps
(`--tools ""` costs *more*, not less). **Read it before promising, building or
documenting any start-block optimisation, and update it in the same change.**
Every number there carries the harness and the date it was measured.

## What we say the product does

`ops/positioning.md` holds the one-sentence claim trace-mcp leads with, the
honesty boundary that travels with it, and the list of surfaces obliged to match.
**Read it before writing or editing any public-facing description** — the
homepage hero, `README.md`'s first screen, `server.json`'s `description`, a
directory listing, a `/vs/` page. Update it in the same change that moves the
claim.

It exists because the product grew four mechanisms that are not the code graph —
decision memory, the `Read`/`Bash` mirrors, the startup-block audit with its
apply button, and `trace_state_*` — and two of them do not ship as MCP tools at
all. Deciding each one's public framing separately produces two products by
accident, which is the failure the file is there to prevent.