From c14ef8a7af59aecbe3d3111fcb446535cdc221ba Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 10:58:12 +0800 Subject: [PATCH] docs: document the MCP server Add docs/mcp.md (setup for Claude Code, Cursor, opencode and Codex, tools, freshness, AI and privacy model, troubleshooting) and docs/guide/commands/09-mcp.md (implementation notes). Update README, commands.md, architecture.md (front-ends), generated-files.md (analyze.lock), the guide index, CLAUDE.md and CHANGELOG (Unreleased). Record progress, test steps and one debugging incident in docs/for-me-personal. --- CHANGELOG.md | 26 ++ CLAUDE.md | 20 +- README.md | 38 +++ docs/architecture.md | 38 +++ docs/commands.md | 51 +++- docs/for-me-personal/DEBUG.md | 43 +++- docs/for-me-personal/PROGRESS.md | 60 ++++- docs/for-me-personal/TEST.md | 42 ++++ docs/generated-files.md | 28 +++ docs/guide/README.md | 2 +- docs/guide/commands/09-mcp.md | 164 ++++++++++++ docs/guide/index.md | 9 + docs/mcp.md | 415 +++++++++++++++++++++++++++++++ 13 files changed, 929 insertions(+), 7 deletions(-) create mode 100644 docs/guide/commands/09-mcp.md create mode 100644 docs/mcp.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c11fe..8a16410 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,32 @@ All notable changes to DevMap are documented in this file. ## [Unreleased] +### Added + +- `devmap mcp`: run DevMap as an MCP server over stdio so AI agents can query the project map + with tool calls (`--root`, `--auto-refresh`, `--allow-ai`) + - Tools: `devmap_overview`, `devmap_map`, `devmap_flow`, `devmap_routes`, `devmap_impact` + (direct and transitive dependents, affected features, critical dependents), `devmap_find`, + `devmap_analyze` and, only with `--allow-ai`, `devmap_explain` + - Resources `devmap://snapshot`, `devmap://devmap-md`, `devmap://feature/{id}` and prompts + `devmap_onboard`, `devmap_plan_change` + - Read-only and static by default: no AI provider is contacted unless `--allow-ai` is set; + every result reports whether the snapshot is stale; results are size-capped and say when + they are truncated + - `devmap_analyze` refreshes the snapshot (single analysis shared by concurrent calls, + progress notifications, `running` status past the timeout, `aiDataDropped` when a static + rebuild loses AI-derived data); `--auto-refresh` does it before answering and falls back to + the older snapshot when it fails + - `.devmap/analyze.lock` prevents two MCP servers from writing the snapshot at once +- `mcpName` in `package.json` for the MCP Registry +- `docs/mcp.md` and a landing-site docs page for the MCP server + +### Changed + +- `runMap`, `runFlow`, `runAnalyze` and `runExplain` are exported; `map` and `flow` accept `write` + and `loaded`, `flow` accepts `narrate`, and `analyze` accepts `offline`. CLI behavior is unchanged +- The CI and publish workflows run `test/mcp-smoke.mjs` against the build before publishing + ### Planned - Go, Java, C# and PHP (PSR-4) import resolution, after JS/TS is mature diff --git a/CLAUDE.md b/CLAUDE.md index 2e9cd5d..9030e59 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,10 +25,11 @@ devmap/ │ │ │ └── registry/ ← Feature signal vocabulary (18 files) │ │ ├── ai/ ← Provider abstraction (Groq, OpenRouter, Custom) │ │ ├── cache/ ← Snapshot, file hash, agent navigation files -│ │ ├── commands/ ← Command handlers (init, analyze, map, flow, explain, onboarding, config, doctor) +│ │ ├── commands/ ← Command handlers (init, analyze, map, flow, explain, onboarding, config, doctor, mcp) +│ │ ├── mcp/ ← MCP server: tools, resources, prompts, snapshot provider, refresher + lock │ │ ├── onboarding/ ← Onboarding model and builder │ │ └── utils/ ← Config, errors, output, prompts, markdown renderer -│ ├── test/ ← 42 test files (`*.test.ts`, auto-discovered by `run-tests.ts`) +│ ├── test/ ← 48 test files (`*.test.ts`, auto-discovered by `run-tests.ts`) + `mcp-smoke.mjs`, `package-e2e.mjs` │ └── package.json ├── apps/web/ ← Astro marketing site (unrelated to CLI) ├── docs/ ← Public documentation @@ -72,6 +73,10 @@ Everything else (files, graph, routes, features, flows, critical files) is 100% | Domain inference | `src/analyzers/inference/domainInference.ts` | AI call — structured metadata only | | Snapshot enrichment | `src/ai/snapshotEnrichment.ts` | AI call — file purposes + feature terms | | Agent navigation | `src/cache/agentNavigation.ts` | `writeAgentNavigationFiles()` — index.json + features/*.json | +| MCP server | `src/mcp/server.ts` | `createDevmapServer()` — tools (`devmap_overview/map/flow/routes/impact/find/analyze`, `devmap_explain` only with `--allow-ai`), resources, prompts | +| MCP queries | `src/mcp/queries.ts` | Pure read-only queries over a `ProjectMap` (overview, routes, impact, find) | +| MCP snapshot provider | `src/mcp/snapshotProvider.ts` | mtime-cached snapshot, TTL-cached staleness, opt-in auto-refresh | +| MCP refresher | `src/mcp/refresh.ts` | Single-flight analysis + `.devmap/analyze.lock` | ## Data Model @@ -124,12 +129,13 @@ Order for a non-relative specifier: `#subpath` (package.json `imports`) → tsco ## Testing -42 test files in `packages/cli/test/`. Key covered areas: +48 test files in `packages/cli/test/`. Key covered areas: - Analyzers, AI clients, agent navigation, entity extractors - Dependency graph: aliases, workspace packages, `#imports`, Nuxt auto-imports/aliases, entry points (`graph-resolution`, `dependency-graph-alias`, `js-ts-ecosystem`, `js-ts-frameworks`) - Feature similarity/merge, frontend features, framework routes (`framework-routes`; Remix/React Router/TanStack/Expo/Hono and bundler aliases in `js-ts-frameworks`; client-side routers, Hono factories, Next normalization and critical-file ranking in `js-ts-gaps`; SolidStart, Qwik City, Angular, Elysia, Koa, AdonisJS and constant-resolved paths in `js-ts-more-frameworks`), detector fixes - Heuristic language readers (`heuristic-languages`), scanner robustness, project scope/metadata - Commands (init, analyze, map, flow, explain, onboarding, config, doctor) +- MCP server (`core-api`, `mcp-server`, `mcp-freshness`, `mcp-explain`): tools, resources, prompts, stale/missing snapshots, auto-refresh and lock, offline-never-touches-AI, stdio process with a clean protocol stream. `test/mcp-smoke.mjs` runs the compiled `dist/` as a server (CI and publish workflow) - Utils (markdown terminal, JSON output, prompts, errors) **No dedicated unit tests**: capability detector (exercised through feature/onboarding tests), cache layer internals. Domain inference is covered by `analyze-ai`; the pipeline orchestrator, service detector, filter engine, dependency graph builder and entry point detector are covered by `analyzers`, `graph-resolution` and `js-ts-ecosystem`. @@ -159,6 +165,8 @@ pnpm dev pnpm devmap ``` +MCP server (after `pnpm build:cli`): `node packages/cli/test/mcp-smoke.mjs`. + ## Important Constraints - No source files should be modified during reverse-engineering tasks @@ -167,6 +175,8 @@ pnpm devmap - Snapshot is regenerated on every `devmap analyze` run - `AGENTS.md` is never overwritten by init (append-only) - `.devmap/` is always gitignored +- `devmap mcp` owns stdout: it is the JSON-RPC stream. Never `console.log` from code the MCP server can reach; use `output.*` inside `withJsonOutput()` (silent) or stderr +- The MCP server is read-only and static by default. Only `devmap_analyze` / `--auto-refresh` write, and only inside `.devmap/`. AI is reachable only with `--allow-ai`; `devmap_explain` is registered only then ## Known Limitations @@ -179,12 +189,16 @@ pnpm devmap - Elysia, Koa and Adonis detectors follow literal call chains and mounts; routers built inside factory functions, `.use()` of computed values and runtime-assembled paths are not followed. Angular reads literal `Routes` arrays and `loadChildren` imports - Feature similarity threshold (0.35) is tuned but may cause false merges - Domain inference is single-shot — no conversation context +- `.devmap/analyze.lock` is taken by MCP-driven analysis only; `devmap analyze` does not take or check it +- The MCP staleness check re-scans project files (cached for 10 s), so it is not free on very large repositories ## Engineering Notes - All commands take a Dependencies object for testability - `runSafely` wrapper handles errors and sets process.exitCode - `AsyncLocalStorage` used for output mode (human/json) +- `runMap`, `runFlow`, `runAnalyze` and `runExplain` are exported and shared by the CLI and the MCP server; options `write:false`, `narrate:false`, `offline:true` and `loaded` make them safe for a long-lived process +- MCP tests copy a fixture to a temp dir and analyze it there; analyzing in place and saving the snapshot elsewhere changes the fingerprint and every result reports `stale: true` - p-limit(50) concurrency for file scanning - MD5 content hashing for fingerprinting - `.js` → `.ts` extension swap in import resolution (shared in `graph/localPaths.ts`) diff --git a/README.md b/README.md index a514ae6..1051a9e 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,43 @@ Reusable across sessions, tools, and agents. --- +## MCP Server + +Run DevMap as an [MCP](https://modelcontextprotocol.io) server and agents can +query the project map with tool calls instead of reading a dumped context file. + +```bash +# Claude Code +claude mcp add devmap -- npx -y @flaid/devmap mcp +``` + +```json +{ + "mcpServers": { + "devmap": { "command": "npx", "args": ["-y", "@flaid/devmap", "mcp"] } + } +} +``` + +The server is read-only and static by default (no AI provider is contacted unless +you start it with `--allow-ai`). + +| Tool | Answers | +| ---- | ------- | +| `devmap_overview` | What is this project? Entry points, critical files, features, flows | +| `devmap_map` | How is this feature or file wired? | +| `devmap_flow` | How does this request or feature work end to end? | +| `devmap_routes` | Which pages and API routes exist? | +| `devmap_impact` | What breaks if I change this file? | +| `devmap_find` | Where is the code for this concept? | +| `devmap_analyze` | Refresh the snapshot when it is stale | +| `devmap_explain` | Explain this file, feature or function (needs `--allow-ai`) | + +Every answer says whether the snapshot is stale. Setup for Claude Code, Cursor, +opencode and Codex, plus the privacy model, is in [docs/mcp.md](./docs/mcp.md). + +--- + ## Vision DevMap is not an AI coding assistant. @@ -313,6 +350,7 @@ Node.js 22.12+ ## Documentation - [docs/commands.md](./docs/commands.md) +- [docs/mcp.md](./docs/mcp.md) - [docs/architecture.md](./docs/architecture.md) - [docs/generated-files.md](./docs/generated-files.md) - [docs/design.md](./docs/design.md) diff --git a/docs/architecture.md b/docs/architecture.md index 4f5e681..f9b5683 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -534,3 +534,41 @@ aggregation, and fine-grained incremental derived-feature computation. required tests, work packages, acceptance criteria, verification commands, and repository delivery rules. Implementation must follow it incrementally; do not attempt this architecture as a single rewrite. + +## 15. Front-ends: CLI and MCP + +The analyzer has two front-ends over the same snapshot: + +```txt +createProjectMap() ──▶ .devmap/snapshot.json ──┬──▶ CLI commands (init, analyze, map, flow, explain, ...) + └──▶ devmap mcp (tools, resources, prompts) +``` + +`devmap mcp` adds no analysis of its own. The command modules export their core +(`runAnalyze`, `runMap`, `runFlow`, `runExplain`), and the MCP server calls them +with options that make them safe for a long-lived process: `write: false`, +`narrate: false`, `offline: true` and a preloaded snapshot. Reads are served by +pure query functions over the `ProjectMap` (`mcp/queries.ts`). + +Consequences for the architecture: + +- **The AI boundary does not move.** The three call sites in section 9 stay the + only ones. The MCP server is static by default and reaches them only when the + user starts it with `--allow-ai`, because the caller is an agent and not the + person who owns the API key. +- **stdout becomes a protocol.** Commands must never assume `console.log` is + free. All run functions already route output through `output.*`, which + `withJsonOutput()` silences; the MCP process also redirects `console.log` to stderr. +- **Freshness is part of every answer.** A long-lived server cannot rely on the + user having just run `analyze`, so each result carries `snapshot.stale`, and the + snapshot can be refreshed on request (`devmap_analyze`) or automatically + (`--auto-refresh`). +- **Concurrency now exists.** Two agents, or an agent and a terminal, can ask for + analysis at once. In-process calls are single-flight; MCP-driven analysis also + takes `.devmap/analyze.lock`. The CLI `analyze` does not take the lock yet. + +Deferred: a remote (HTTP) transport, serving several projects from one server, and +watching files for changes instead of checking staleness per request. + +Implementation notes are in [guide/commands/09-mcp.md](./guide/commands/09-mcp.md); +usage is in [mcp.md](./mcp.md). diff --git a/docs/commands.md b/docs/commands.md index b0de28f..99b659f 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -4,7 +4,8 @@ ## Overview -DevMap ships the following core project commands and one configuration command: +DevMap ships the following core project commands, a server command, and one +configuration command: * `devmap init` * `devmap analyze` @@ -12,6 +13,7 @@ DevMap ships the following core project commands and one configuration command: * `devmap map` * `devmap flow` * `devmap explain` +* `devmap mcp` * `devmap doctor` * `devmap config model` @@ -441,6 +443,51 @@ The `` argument is required. --- +## `devmap mcp` + +Run DevMap as an MCP (Model Context Protocol) server over stdio, so AI agents +can query the project map with tool calls. Full reference: [mcp.md](./mcp.md). + +### Purpose + +> Let an agent ask "what does this feature touch?" or "what breaks if I change +> this file?" without reading the whole repository first. + +### Usage + +```bash +devmap mcp # serve the project in the current folder +devmap mcp --root # serve another project +devmap mcp --auto-refresh # rebuild a stale or missing snapshot before answering +devmap mcp --allow-ai # let analysis use the AI provider; adds devmap_explain +``` + +Clients start it for you; see [mcp.md](./mcp.md#client-setup) for Claude Code, +Cursor, opencode and Codex. + +### Responsibilities + +* Serve tools over stdio: `devmap_overview`, `devmap_map`, `devmap_flow`, + `devmap_routes`, `devmap_impact`, `devmap_find`, `devmap_analyze`, and + `devmap_explain` (only with `--allow-ai`) +* Serve resources (`devmap://snapshot`, `devmap://devmap-md`, + `devmap://feature/{id}`) and prompts (`devmap_onboard`, `devmap_plan_change`) +* Answer from `.devmap/snapshot.json`; every result reports `snapshot.stale` +* Never print to stdout except the protocol stream; logs go to stderr + +### Rules + +* Read-only, except `devmap_analyze` (and `--auto-refresh`), which write only + inside `.devmap/` +* Static by default: no AI provider is contacted, and the AI config is not read, + unless `--allow-ai` is set +* `--auto-refresh` is off by default: a question never writes to disk +* Analysis takes `.devmap/analyze.lock` so two servers do not write the snapshot + at once +* `devmap mcp` has no `--json` flag; its output is the protocol + +--- + ## `devmap doctor` Run diagnostics for DevMap setup. @@ -630,6 +677,8 @@ Contract: * progress sections and Markdown rendering are omitted * AI responses are buffered instead of streamed * runtime failures return a JSON object with `status`, `error`, and optional `hint` +* `devmap mcp` is the exception: it has no `--json` flag because stdout is the + MCP protocol stream * `init --json` never prompts and therefore requires `GROQ_API_KEY`, `OPENROUTER_API_KEY`, or an existing API key * package-manager wrapper warnings may appear on stderr and are not part of the diff --git a/docs/for-me-personal/DEBUG.md b/docs/for-me-personal/DEBUG.md index 87f2202..221cdbd 100644 --- a/docs/for-me-personal/DEBUG.md +++ b/docs/for-me-personal/DEBUG.md @@ -1,6 +1,6 @@ # Catatan Debugging DevMap -Terakhir diperbarui: 2026-08-05 +Terakhir diperbarui: 2026-10-30 Dokumen ini menyimpan masalah teknis yang pernah ditemukan selama development DevMap. Tujuannya supaya penyebab, solusi, dan cara verifikasinya tidak perlu @@ -1596,3 +1596,44 @@ Test ketiga (bahasa `id` via prompt palsu) sudah hermetic dan tidak diubah. lingkungan runner. - Durasi test yang melonjak (detik → menit) adalah sinyal test menunggu input interaktif, bukan test yang lambat. +--- + +## 30. Semua Hasil MCP Melaporkan `stale: true` pada Snapshot yang Baru Dibuat + +**Tanggal:** 2026-10-29 + +### Gejala + +Tes `devmap_overview` untuk server MCP gagal: `snapshot.stale` bernilai `true` +padahal snapshot baru saja dibuat dari fixture yang sama dan tidak ada file yang +diubah. + +### Akar Masalah + +Helper tes menganalisis fixture di tempatnya +(`createProjectMap(test/fixtures/nextjs-project)`) lalu menyimpan snapshot ke +direktori temp lain (`saveSnapshot(tempRoot, snapshot)`). Pemeriksaan stale +memindai file di `projectRoot` yang diberikan ke server (direktori temp, yang +hanya berisi `.devmap/`), sehingga fingerprint-nya tidak pernah sama dengan +fingerprint di snapshot. Kodenya benar; fixture-nya yang tidak mewakili project +yang nyata. + +### Solusi + +- Salin fixture ke direktori temp dengan `cp(..., { recursive: true })`, lalu + jalankan `createProjectMap` dan `saveSnapshot` pada salinan itu. +- Tes baru yang butuh perubahan file (`addFile`) memakai project mini yang dibuat + langsung di direktori temp. + +### Verifikasi + +- `node --import tsx --test test/mcp-server.test.ts` → 13 pass / 0 fail. +- Tes `results flag a stale snapshot after the project changes` membuktikan arah + sebaliknya: `stale` berubah menjadi `true` hanya setelah file ditambahkan. + +### Pelajaran + +- Fingerprint snapshot terikat pada isi `projectRoot`. Tes yang membandingkan + snapshot dengan project harus menganalisis dan menyimpan di direktori yang sama. +- Jika semua hasil tiba-tiba `stale: true`, periksa dulu apakah snapshot dan project + yang dibandingkan memang folder yang sama sebelum mencurigai logika stale. diff --git a/docs/for-me-personal/PROGRESS.md b/docs/for-me-personal/PROGRESS.md index 48be6e3..bbaee12 100644 --- a/docs/for-me-personal/PROGRESS.md +++ b/docs/for-me-personal/PROGRESS.md @@ -1,6 +1,64 @@ # Progress DevMap -Terakhir diperbarui: 2026-10-03 +Terakhir diperbarui: 2026-10-30 + +## Update 2026-10-30 + +### MCP Server (`devmap mcp`) — PR #130 s.d. #133, docs di branch `docs/mcp-server` + +Tujuan: DevMap terasa "for AI agent" — agent bertanya ke peta proyek lewat tool +call, bukan membaca file context yang di-dump. Satu package, satu analyzer, +MCP hanya lapisan tipis di atas snapshot. + +1. **Fase 0 (#130)** — `runMap`/`runFlow`/`runExplain` di-export; opsi `write`, + `narrate`; helper `loadSnapshot()` (`{ snapshot, stale }`). +2. **Fase 1 (#131)** — command `devmap mcp` (stdio, read-only): tool + `devmap_overview/map/flow/routes/impact/find`, resource + `devmap://snapshot|devmap-md|feature/{id}`. Dependency baru + `@modelcontextprotocol/sdk` dan `zod`. +3. **Fase 2 (#132)** — `devmap_analyze` (single-flight, lock + `.devmap/analyze.lock`, progress notification, status `running` setelah + timeout), `--auto-refresh` (default off), `devmap_impact` transitive + (`depth` 1–5), prompt `devmap_onboard` / `devmap_plan_change`, + `runAnalyze` di-export dengan opsi `offline`. +4. **Fase 3 (#133)** — `devmap_explain` (hanya terdaftar dengan `--allow-ai`), + `test/mcp-smoke.mjs` dijalankan di `ci.yml` dan `publish.yml` sebelum + `npm publish`, `mcpName: io.github.itsflaid/devmap` di `package.json`. +5. **Fase 4 (sedang jalan)** — `docs/mcp.md`, bagian MCP di README, `commands.md`, + `architecture.md` (bagian 15), `generated-files.md`, `docs/guide/commands/09-mcp.md`, + `CLAUDE.md`, `CHANGELOG.md` (Unreleased); lalu landing page. + +**Keputusan penting** +- Nama command `devmap mcp`. `devmap_analyze` baru di Fase 2 (Fase 1 murni read-only). +- Auto-refresh default **off**. +- AI di MCP default **mati**, walaupun API key sudah di-set; hanya hidup dengan + `--allow-ai`. Alasan: yang memanggil tool adalah agent, bukan pemilik API key + (kuota dan metadata kode ke provider). `devmap_analyze` statis melaporkan + `aiDataDropped` kalau snapshot lama punya data AI. +- `server.json` dan bump versi **tidak** dimasukkan ke PR Fase 3: versi di + `server.json` harus sama dengan versi yang benar-benar terbit. Bump hanya + setelah konfirmasi eksplisit (aturan `AGENTS.md`). + +**Verifikasi (Fase 3)**: `pnpm test:cli` → 482 pass / 0 fail, `pnpm build:cli`, +`node packages/cli/test/mcp-smoke.mjs`, `pnpm test:package-e2e`. + +**Belum / sisa** +- Landing page (`apps/web`) + halaman Docs MCP. +- PR rilis: bump versi (tunggu konfirmasi level bump), `server.json`, + `packages/cli/README.md` (ikut tarball npm), tag + publish manual via OIDC, + lalu `mcp-publisher publish`. +- `devmap analyze` di terminal belum memakai `.devmap/analyze.lock`. +- Tes Windows/macOS untuk tes yang memakai proses sungguhan (stdio, lock, + smoke) baru akan tervalidasi oleh matriks CI; run CI terakhir yang terlihat + dari GitHub API bertanggal 2026-06 dan gagal (`Ubuntu / Node 20`), jadi + status CI perlu dicek langsung di tab Actions. + +**Catatan proses**: commit PR #130–#133 memakai format `feat(cli): ...` dan trailer +co-author, padahal `AGENTS.md` meminta `: ` tanpa scope dan +tanpa trailer bila `.agents/config.local.md` tidak ada. Sudah ter-merge, tidak +diubah; PR berikutnya mengikuti aturan. + +--- ## Update 2026-10-03 diff --git a/docs/for-me-personal/TEST.md b/docs/for-me-personal/TEST.md index 9a72cb8..035474a 100644 --- a/docs/for-me-personal/TEST.md +++ b/docs/for-me-personal/TEST.md @@ -15,6 +15,48 @@ Ada beberapa versi DevMap yang dapat diuji: | npm link | CLI global sementara | Menguji command `devmap` dari folder mana pun | | CI/runtime | OS dan versi Node berbeda | Verifikasi lintas platform sebelum release | +## MCP server (2026-10-30) + +Prasyarat: `pnpm install --frozen-lockfile`, Node 22.12+. + +```powershell +# 1. Tes terarah (in-memory + proses stdio sungguhan) +cd packages/cli +node --import tsx --test test/core-api.test.ts test/mcp-server.test.ts test/mcp-freshness.test.ts test/mcp-explain.test.ts + +# 2. Seluruh suite + typecheck +pnpm test:cli + +# 3. Build lalu smoke test hasil compile (server stdio sungguhan) +pnpm build:cli +node packages/cli/test/mcp-smoke.mjs + +# 4. Tarball: server MCP harus jalan dari package hasil pack +pnpm test:package-e2e +``` + +Hasil yang diharapkan: semua hijau; smoke test mencetak +`MCP smoke test passed (7 tools).` (tanpa `--allow-ai` tidak ada `devmap_explain`). + +Alur manual (belum dijalankan dengan client sungguhan, hanya JSON-RPC mentah): + +1. `devmap analyze` di project nyata, lalu daftarkan server + (`claude mcp add devmap -- node /path/ke/packages/cli/dist/index.js mcp`). +2. Minta agent: "pakai DevMap, jelaskan alur auth dan dampak mengubah file X". + Harapan: `devmap_overview` → `devmap_flow` → `devmap_impact`. +3. Ubah satu file, tanya lagi: hasil harus berisi `snapshot.stale: true`; panggil + `devmap_analyze` → `refreshed`, lalu `stale: false`. +4. Start dengan `--auto-refresh`: hasil pertama setelah perubahan membawa + `refreshed: true`. +5. Start dengan `--allow-ai` + provider terkonfigurasi: `devmap_explain` muncul. +6. Opsional: MCP Inspector (`npx @modelcontextprotocol/inspector node dist/index.js mcp`). + +Timeout client: Codex `tool_timeout_sec` default 60 s dan opencode `timeout` +default 5 s; `devmap_analyze` default menunggu 120 s. Uji di project besar. + +Lintas platform: tes yang memakai proses sungguhan (stdio, lock, smoke) belum +dijalankan di Windows/macOS dari sini — andalkan matriks `ci.yml`. + ## Onboarding TTY flake (2026-10-03) Dua test onboarding (`renders a snapshot-based guide`, diff --git a/docs/generated-files.md b/docs/generated-files.md index ed8c882..bbdade9 100644 --- a/docs/generated-files.md +++ b/docs/generated-files.md @@ -238,6 +238,34 @@ Purpose: --- +## .devmap/analyze.lock + +Created while `devmap mcp` runs an analysis (`devmap_analyze` or +`--auto-refresh`) and removed when it finishes. + +```json +{ "pid": 12345, "startedAt": "2026-10-30T02:11:09.000Z" } +``` + +Purpose: + +- Stop two MCP servers (or editor windows) from writing the snapshot at once + +A lock whose process is gone, or that is older than 15 minutes, is taken over +automatically. If no analysis is running and the file is still there, it is safe +to delete. `devmap analyze` does not create or check this file. + +Like the rest of `.devmap/`, it is gitignored. + +## What the MCP server writes + +`devmap mcp` answers from the files above and writes nothing unless an analysis +runs. `devmap_map`, `devmap_flow` and `devmap_explain` never write +`.devmap/maps/`, `.devmap/flows/` or `.devmap/explain/`; those folders are only +created by the CLI commands. See [mcp.md](./mcp.md). + +--- + # Next Phase — Not Generated Yet > The following files are planned for future phases. diff --git a/docs/guide/README.md b/docs/guide/README.md index 7d2225c..ae1a040 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -63,7 +63,7 @@ Read them in order for a first pass; use them as reference after that. registered CLI command, focused on implementation (dependency injection pattern, JSON-mode branching, error handling), not user-facing usage: -- [`init`](./commands/01-init.md) · [`analyze`](./commands/02-analyze.md) · [`onboarding`](./commands/03-onboarding.md) · [`map`](./commands/04-map.md) · [`flow`](./commands/05-flow.md) · [`explain`](./commands/06-explain.md) · [`config`](./commands/07-config.md) · [`doctor`](./commands/08-doctor.md) +- [`init`](./commands/01-init.md) · [`analyze`](./commands/02-analyze.md) · [`onboarding`](./commands/03-onboarding.md) · [`map`](./commands/04-map.md) · [`flow`](./commands/05-flow.md) · [`explain`](./commands/06-explain.md) · [`config`](./commands/07-config.md) · [`doctor`](./commands/08-doctor.md) · [`mcp`](./commands/09-mcp.md) ## A running theme diff --git a/docs/guide/commands/09-mcp.md b/docs/guide/commands/09-mcp.md new file mode 100644 index 0000000..3792f76 --- /dev/null +++ b/docs/guide/commands/09-mcp.md @@ -0,0 +1,164 @@ +# `devmap mcp` + +**Source:** `packages/cli/src/commands/mcp.ts` (entry) and `packages/cli/src/mcp/` +(the server). For usage, see [`docs/mcp.md`](../../mcp.md). + +Every other command is a one-shot: it runs, prints, exits. `devmap mcp` is the +first long-lived front-end, which changes three things that the rest of the CLI +never had to care about: **stdout is a protocol**, **state outlives a single +call**, and **the caller is not a person**. Most of the design follows from those. + +## Layout + +| File | Role | +|---|---| +| `commands/mcp.ts` | Process wiring: redirect `console.*` to stderr, create the server, connect stdio, shut down on SIGINT/SIGTERM/stdin end | +| `mcp/server.ts` | `createDevmapServer()`: registers tools, resources and prompts | +| `mcp/queries.ts` | Pure functions over a `ProjectMap` (`buildOverview`, `queryRoutes`, `queryImpact`, `queryFind`); no I/O | +| `mcp/snapshotProvider.ts` | Cached snapshot + staleness, optional auto-refresh | +| `mcp/refresh.ts` | `createRefresher()` (single-flight analysis) and the cross-process lock | +| `mcp/prompts.ts` | `devmap_onboard`, `devmap_plan_change` | + +`createDevmapServer()` takes `provider`, `refresher`, `analyze` and +`explainDependencies` as options. That is the same "every command takes a +Dependencies object" rule from `CLAUDE.md`, and it is what lets the tests run the +whole server in memory with a fake analyzer and a fake AI client. + +## Reuse, not a second analyzer + +`map`, `flow`, `analyze` and `explain` each export a `run*` function +(`runMap`, `runFlow`, `runAnalyze`, `runExplain`) that the CLI command wraps. The +MCP tools call the same functions with options that make them safe for a server: + +| Option | Where | Effect | +|---|---|---| +| `write: false` | `runMap`, `runFlow` | Compute the result without writing `.devmap/maps` or `.devmap/flows` | +| `narrate: false` | `runFlow` | Skip AI narration and do not even load the config | +| `offline: true` | `runAnalyze` | No domain inference, no snapshot enrichment, no architecture interpretation | +| `loaded` | `runMap`, `runFlow`, `runExplain` | Use a snapshot the caller already holds instead of re-reading and re-scanning | + +`offline` is the guarantee behind "static by default": with it set, +`buildCallAI` is never called, so the AI config is never read. A test installs an +AI client that throws when created, with an API key configured, and asserts it +stays untouched. + +## stdout is the protocol + +The stdio transport reads JSON-RPC from stdin and writes it to stdout. Anything +else printed to stdout corrupts the stream, and the client reports a parse error +or drops the connection. + +Two mechanisms keep it clean: + +1. `mcpCommand()` replaces `console.log`, `console.info` and `console.debug` with + `console.error` before the server starts. +2. Every call into a `run*` function is wrapped in `withJsonOutput()` + (`AsyncLocalStorage` set to `"json"`), which turns `output.section`, + `output.note`, `output.spinner` and the rest into no-ops. This is the same switch + `--json` uses, so no command needed a new "quiet" mode. + +The stdio tests start a real child process and fail on any non-JSON-RPC line, +because the SDK's transport reports them through `onerror`. + +## The snapshot provider + +A tool call must be cheap, but the CLI's `loadSnapshot()` reads and parses +`snapshot.json` and re-scans the project to compute staleness. `createSnapshotProvider()` +adds two caches: + +- The parsed snapshot is kept until the **mtime** of `snapshot.json` changes. A + snapshot rewritten by `devmap analyze` in another terminal is picked up on the + next call. +- The staleness result is kept for `staleTtlMs` (10 s), and dropped whenever the + snapshot is reloaded. + +With `autoRefresh` set, `get()` rebuilds when the snapshot is missing or stale +and then reloads. If the rebuild throws and a snapshot exists, the old one is +returned with `refreshError`, and further attempts are suppressed for +`retryCooldownMs` (30 s) so a persistent failure does not turn every query into a +slow failed analysis. If there is no snapshot at all, the failure is the real +error and is thrown. + +`queryImpact()` also memoizes a reverse import graph per snapshot in a +`WeakMap`, so the cache disappears with the snapshot object. + +## Refreshing: single flight plus a lock + +`createRefresher().run()` has two layers of exclusion: + +- **In-process**: concurrent calls get the same promise (`inflight`). Two agent + tool calls cause one analysis. +- **Cross-process**: `acquireLock()` creates `.devmap/analyze.lock` with + `open(path, "wx")` (atomic create-or-fail) and writes `{ pid, startedAt }`. + If the file exists, the lock is taken over when the pid is no longer alive + (`process.kill(pid, 0)`; `EPERM` counts as alive) or when it is older than + `LOCK_STALE_MS` (15 min); otherwise it throws a `DevmapError` with a retry hint. + +Before taking the lock, `run()` checks staleness. When nothing changed it returns +`unchanged` without touching the lock or the analyzer, so a call that has nothing +to do is also never blocked by another process's analysis. + +`devmap analyze` in the terminal does **not** take this lock. The window for a +collision is small, but it exists; if it matters, `runAnalyze` is the place to add it. + +### `aiDataDropped` + +A static rebuild produces a snapshot without `domain` or `ai`. The refresher +compares the previous snapshot with the new one and reports `aiDataDropped` when +the old one had them and the new one does not, so a rebuild never silently +discards what `devmap analyze` paid an AI provider for. If `analyze` decides the +project is unchanged, it reuses the previous snapshot (and its AI data); the +refresher detects that by `generatedAt` and reports `unchanged`. + +## `devmap_analyze` and time + +Clients have their own tool timeouts, so the tool does not hold the request open +indefinitely: + +- `waitFor(run, timeoutSeconds)` races the shared analysis against a timer. On + timeout the tool answers `status: "running"` and the analysis **keeps going**; the + next call joins it through `inflight`. +- While waiting, a heartbeat sends `notifications/progress` every 5 s, but only if + the request carried a `progressToken`. +- When the run settles, `provider.invalidate()` drops the cached snapshot, even + if the request that started it already stopped waiting. + +## Tool results + +`ok()` and `fail()` wrap everything. `fail()` turns a `DevmapError` into +`{ error, hint }` with `isError: true`, so an agent gets the same actionable text a +person would see in the terminal ("Run devmap analyze first."). Unexpected errors +get a plain `{ error }`. + +Sizes are bounded in `server.ts`: `truncateText()` caps text at 30,000 +characters (8,000 per flow, 20 flows), and list queries clamp `limit` through +`clampLimit()` (1–100). The result says `truncated` instead of silently cutting. + +## `devmap_explain` is registered conditionally + +It is added only `if (options.allowAi)`. An unregistered tool does not exist for +the client: calling it returns "Tool devmap_explain not found". This is a +stronger guarantee than checking a flag inside the handler, because the agent is +not even told the tool exists. `runExplain` still requires a configured provider, +so a missing key is the same `devmap init` error as on the command line. + +## Resources + +`devmap://feature/{id}` is a `ResourceTemplate` whose `list` reads +`.devmap/features/`. The `id` is checked against `^[a-z0-9][a-z0-9-]*$` before it +is joined into a path, which rules out `..` and path separators. + +## Testing + +| File | Covers | +|---|---| +| `test/core-api.test.ts` | `runMap`/`runFlow`/`runAnalyze` options: silent, write-free, offline | +| `test/mcp-server.test.ts` | Tools, resources, stale and missing snapshots, provider caching, stdio process | +| `test/mcp-freshness.test.ts` | `devmap_analyze`, auto-refresh and back-off, lock, transitive impact, prompts, stdio with a real analysis | +| `test/mcp-explain.test.ts` | `devmap_explain` gating, errors, truncation, registry metadata | +| `test/mcp-smoke.mjs` | The compiled `dist/` as a stdio server; run by CI and before `npm publish` | +| `test/package-e2e.mjs` | The packed tarball starts as an MCP server in a fresh project | + +Fixtures are copied to a temp directory and analyzed **there**. Analyzing the +fixture in place and saving the snapshot elsewhere makes the fingerprint differ +and every result report `stale: true`. diff --git a/docs/guide/index.md b/docs/guide/index.md index 4c85567..42f9a8b 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -227,3 +227,12 @@ whatever's cached in `.devmap/snapshot.json` (so a stale snapshot becomes detectable rather than silently trusted), the live network check that confirms a stored API key/model still actually work today, and what populates the final `issues[]` list. + +### `09-mcp.md` +Covers `commands/mcp.ts` and the `mcp/` folder — DevMap's first long-lived +front-end. Explains why stdout becomes a protocol (the `console.*` redirect plus +`withJsonOutput`), how the MCP tools reuse `runMap`/`runFlow`/`runAnalyze`/ +`runExplain` through `write`, `narrate`, `offline` and `loaded` options instead of +a second analyzer, the snapshot provider's mtime and TTL caches, the single-flight +refresher with a cross-process lock, why `devmap_explain` is registered only with +`--allow-ai`, and what `aiDataDropped` protects. diff --git a/docs/mcp.md b/docs/mcp.md new file mode 100644 index 0000000..18a313b --- /dev/null +++ b/docs/mcp.md @@ -0,0 +1,415 @@ +# DevMap as an MCP Server + +`devmap mcp` runs DevMap as a [Model Context Protocol](https://modelcontextprotocol.io) +server over stdio. An AI agent can ask the project map questions with tool calls +instead of reading a dumped context file or exploring the repository from scratch. + +```txt +Agent ──tool call──▶ devmap mcp ──reads──▶ .devmap/snapshot.json + ◀─structured JSON─ +``` + +The CLI and the MCP server share one analyzer and one snapshot. The server is a +thin layer: it never has its own copy of the analysis. + +## When to use which + +| | CLI + generated files | MCP server | +|---|---|---| +| Who calls it | You, or an agent through the shell | The agent, directly | +| Context | `DEVMAP.md` / `AGENTS.md` read at the start | Specific questions answered when needed (fewer tokens) | +| Output | Text, Markdown, files | Structured JSON | +| Freshness | Stale until you run `devmap analyze` | Every answer says whether it is stale; can refresh | +| Works with | Any agent that can read files | Clients that support MCP | + +They complement each other. Keep `devmap init` for the files, add the MCP server +for live queries. + +--- + +## Quick start + +**1. Analyze the project once** + +```bash +cd your-project +npx @flaid/devmap analyze +``` + +This writes `.devmap/snapshot.json`. (The MCP server can also build it for you, see +[Freshness](#freshness-and-the-snapshot).) + +**2. Register the server in your client** (see [Client setup](#client-setup)) + +```bash +claude mcp add devmap -- npx -y @flaid/devmap mcp +``` + +**3. Ask the agent** + +> "Use DevMap to explain how authentication works, then tell me what I would break by changing `lib/session.ts`." + +The agent starts with `devmap_overview`, traces flows with `devmap_flow`, and +checks `devmap_impact` before touching a shared file. + +--- + +## Client setup + +Every client launches the same command: `npx -y @flaid/devmap mcp`. Most clients +start the server in the folder you opened. If yours does not, add +`--root /absolute/path/to/project`. + +> **Tip: install globally for faster starts.** `npx` downloads the package the +> first time, which can exceed a client's startup timeout. After +> `npm install -g @flaid/devmap`, use `devmap` as the command with `mcp` as the +> argument. The same applies if your client cannot launch `npx` on Windows. + +### Claude Code + +```bash +# only you, this project +claude mcp add devmap -- npx -y @flaid/devmap mcp + +# shared with the team through .mcp.json (checked into the repo) +claude mcp add --scope project devmap -- npx -y @flaid/devmap mcp +``` + +The project file looks like this: + +```json +{ + "mcpServers": { + "devmap": { + "command": "npx", + "args": ["-y", "@flaid/devmap", "mcp"] + } + } +} +``` + +Use `/mcp` inside Claude Code to check that the server is connected. + +### Cursor + +`.cursor/mcp.json` in the project (or `~/.cursor/mcp.json` for every project): + +```json +{ + "mcpServers": { + "devmap": { + "command": "npx", + "args": ["-y", "@flaid/devmap", "mcp", "--root", "${workspaceFolder}"] + } + } +} +``` + +### opencode + +`opencode.json`: + +```json +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "devmap": { + "type": "local", + "command": ["npx", "-y", "@flaid/devmap", "mcp"], + "enabled": true, + "timeout": 30000 + } + } +} +``` + +`timeout` is how long opencode waits for the tool list. Its default is 5 seconds, +which is too short for the first `npx` download. + +### Codex + +`~/.codex/config.toml`: + +```toml +[mcp_servers.devmap] +command = "npx" +args = ["-y", "@flaid/devmap", "mcp"] +startup_timeout_sec = 60 +tool_timeout_sec = 180 +``` + +Codex's default tool timeout is 60 seconds. `devmap_analyze` can take longer on a +large project, so raise `tool_timeout_sec` or have the agent pass a smaller +`timeoutSeconds` (see [`devmap_analyze`](#devmap_analyze)). + +### Any other MCP client + +Configure a **stdio** server with command `npx` and arguments +`-y @flaid/devmap mcp`. + +--- + +## Command + +```bash +devmap mcp [--root ] [--auto-refresh] [--allow-ai] +``` + +| Flag | Default | Effect | +|---|---|---| +| `--root ` | current folder | Project root that contains `.devmap/snapshot.json` | +| `--auto-refresh` | off | Re-analyze automatically when the snapshot is stale or missing, before answering | +| `--allow-ai` | off | Let analysis use your configured AI provider, and expose `devmap_explain` | + +stdout carries the JSON-RPC stream only. Logs go to stderr. + +--- + +## Tools + +All tools return one JSON document. Every result that comes from the snapshot +includes `snapshot: { generatedAt, stale }`. + +| Tool | Purpose | Writes? | +|---|---|---| +| [`devmap_overview`](#devmap_overview) | Start here: framework, entry points, critical files, features, flows | No | +| [`devmap_map`](#devmap_map) | Dependency map of the project, one feature, or one file | No | +| [`devmap_flow`](#devmap_flow) | Trace a feature or API request end to end | No | +| [`devmap_routes`](#devmap_routes) | List page and API routes | No | +| [`devmap_impact`](#devmap_impact) | What is affected if a file changes | No | +| [`devmap_find`](#devmap_find) | Find features, files and routes by keyword | No | +| [`devmap_analyze`](#devmap_analyze) | Re-analyze and refresh the snapshot | `.devmap/` only | +| [`devmap_explain`](#devmap_explain) | AI explanation of a file, feature or function | No (needs `--allow-ai`) | + +### `devmap_overview` + +No input. Returns `project`, `stats`, `entryPoints`, `criticalFiles`, `features` +(name, purpose, confidence, entry point, file count), `flows`, `routeCounts`, +`database`, `externalServices`, `recommendedPath` and `nextSteps`. + +### `devmap_map` + +| Input | Type | Notes | +|---|---|---| +| `target` | string, optional | A feature name or a project-relative file path. Omit for the whole project | +| `depth` | 1–6, optional | Traversal depth for file and feature maps | +| `includeMermaid` | boolean, optional | Also return the Mermaid source (default false) | + +Returns `mode` (`project`, `feature` or `file`), `target`, `markdown`, `truncated`. + +### `devmap_flow` + +| Input | Type | Notes | +|---|---|---| +| `target` | string, optional | Flow name; case-insensitive, partial match allowed when unique. Omit for the curated top flows | +| `all` | boolean, optional | Include lower-confidence features and non-API routes | + +Static analysis only: no AI narration, nothing written. Returns `total`, +`truncated` and up to 20 `flows`, each with `name`, `type`, `purpose` and `markdown`. + +### `devmap_routes` + +| Input | Type | Notes | +|---|---|---| +| `kind` | `page` \| `api` \| `all` | Default `all` | +| `prefix` | string | Only paths that start with this, e.g. `/api/users` | +| `method` | string | HTTP method, e.g. `POST` | +| `limit` | 1–100 | Default 50 | + +Returns `total`, `returned`, `truncated` and `routes` (`path`, `file`, `kind`, `methods`). + +### `devmap_impact` + +Call this before editing a shared file. + +| Input | Type | Notes | +|---|---|---| +| `file` | string | Project-relative path. A unique path suffix also works | +| `depth` | 1–5 | Import hops to follow for dependents. Default 1 | +| `limit` | 1–100 | Max entries per list. Default 50 | + +Returns, among others: + +| Field | Meaning | +|---|---| +| `dependents`, `dependentCount` | Files that import this file directly | +| `transitive` | Everything that reaches the file within `depth` hops (`count`, `files` with their hop distance) | +| `criticalDependents` | Critical files among the transitive dependents | +| `affectedFeatures` | Features that contain the file or any dependent | +| `isCritical`, `criticalReasons` | Whether the file itself is ranked as critical | +| `impacts` | Areas DevMap expects to be affected | +| `imports` | Local files this file imports | + +Import cycles are handled. + +### `devmap_find` + +| Input | Type | Notes | +|---|---|---| +| `query` | string | One or more keywords, e.g. `auth login` or `InvoiceService` | +| `limit` | 1–100 | Default 15 | + +Searches paths, exported symbols, function names, search terms and purposes. Hits +are ranked and typed (`feature`, `file`, `route`). + +### `devmap_analyze` + +Re-analyzes the project and rewrites `.devmap/snapshot.json` and the navigation +files. It writes only inside `.devmap/`. + +| Input | Type | Notes | +|---|---|---| +| `fresh` | boolean | Ignore the cache and rebuild from scratch | +| `timeoutSeconds` | 1–600 | How long to wait. Default 120 | + +| `status` | Meaning | +|---|---| +| `unchanged` | No file changed; nothing was re-analyzed (unless `fresh: true`) | +| `refreshed` | A new snapshot was written | +| `running` | Still going after `timeoutSeconds`. It continues in the background; call again to wait for it | + +Other fields: `generatedAt`, `durationMs`, `stats`, `mode` (`static` or +`ai-enabled`) and `aiDataDropped`. Concurrent calls share one analysis, and +clients that ask for progress receive notifications while it runs. + +### `devmap_explain` + +Only available when the server runs with `--allow-ai`. See +[AI and privacy](#ai-and-privacy). + +| Input | Type | Notes | +|---|---|---| +| `target` | string | A project-relative file path, a feature name, or a top-level function name | + +Returns `mode`, `target`, `answer`, `model`, `contextFiles` and `truncated`. It +writes nothing to disk. + +--- + +## Resources and prompts + +**Resources** + +| URI | Content | +|---|---| +| `devmap://snapshot` | The full `.devmap/snapshot.json`. Large; prefer the tools | +| `devmap://devmap-md` | The project's `DEVMAP.md` | +| `devmap://feature/{id}` | Per-feature navigation data from `.devmap/features/{id}.json` | + +**Prompts** (ready-made workflows; how a client shows them varies) + +| Prompt | Input | What it asks the agent to do | +|---|---|---| +| `devmap_onboard` | `focus` (optional) | Build an onboarding guide using overview, flows and feature maps before opening files | +| `devmap_plan_change` | `change` | Find the right files, check `devmap_impact` for each, and propose a plan without editing code | + +--- + +## Freshness and the snapshot + +Tools answer from `.devmap/snapshot.json`. DevMap compares the project's files +with the snapshot to decide whether it is **stale** (the check is cached for +10 seconds). When it is, every result says so: + +```json +"snapshot": { + "generatedAt": "2026-10-30T02:11:09.000Z", + "stale": true, + "note": "The code changed after this snapshot was generated. Results may be outdated; call devmap_analyze to refresh." +} +``` + +There are three ways to refresh, from most to least hands-on: + +1. **Run `devmap analyze --fresh`** yourself. This is the only way to keep + AI-derived data (see below). +2. **The agent calls `devmap_analyze`** when it sees `stale: true`. +3. **`--auto-refresh`** rebuilds before answering, without the agent asking. + Results then carry `refreshed: true`. If the rebuild fails, the older snapshot + is served with `refreshError`, and DevMap waits 30 seconds before trying again. + +Without `--auto-refresh`, a question never writes to disk. + +Analysis takes a lock (`.devmap/analyze.lock`) so two servers or editor windows +cannot write the snapshot at once. A lock left by a dead process, or older than +15 minutes, is taken over. If another analysis is running, `devmap_analyze` +returns an error telling the agent to retry. + +--- + +## AI and privacy + +The MCP server is **static by default**: no AI provider is contacted, even when +you configured an API key with `devmap init`. The reason is that an agent, not +you, decides when to call a tool, so it should not be able to spend your AI quota +or send project metadata to a provider on its own. + +| | Without `--allow-ai` (default) | With `--allow-ai` | +|---|---|---| +| `devmap_analyze` | Static analysis only | May use your provider for domain inference and file purposes | +| `devmap_explain` | Not registered | Registered; sends source excerpts to your provider | +| Provider config | Never read | Read from `devmap init` (BYOK) | + +When a static rebuild replaces a snapshot that had AI-derived data (`domain`, +architecture), the result reports `aiDataDropped: true`. Run `devmap analyze` in a +terminal, or start the server with `--allow-ai`, to get it back. If nothing +changed, the existing snapshot is reused and its AI data stays. + +`devmap_explain` only reads files that are in the snapshot's index and inside the +project root. The scan skips `.env*` files and anything your `.gitignore` ignores, +so those are not indexed and cannot be sent. DevMap does **not** inspect file +contents for secrets: if a source file contains a hard-coded key, an excerpt of +that file can be sent to your provider. + +--- + +## Responses and errors + +- **Staleness**: see above. `snapshot.stale` is always present. +- **Truncation**: large text is capped (30,000 characters, 8,000 per flow) and the + result says `truncated: true`. Lists have a `limit` and report `truncated` too. +- **Errors** use the protocol's error flag and a structured body: + +```json +{ + "error": "No DevMap snapshot was found.", + "hint": "Run devmap analyze first." +} +``` + + Missing, corrupt and unsupported snapshots, unknown files or features, and a + held analysis lock return an `error`, and a `hint` when there is a next step. + +## Safety model + +- Every tool except `devmap_analyze` is read-only and annotated as such. + `devmap_analyze` is not destructive and is idempotent; it only writes inside `.devmap/`. +- `devmap_explain` is marked as reaching outside the machine (it contacts your AI provider). +- Feature resource ids are validated, so `devmap://feature/…` cannot read + outside `.devmap/features/`. +- File arguments are looked up in the snapshot's file index; they are not used as + filesystem paths. + +## Troubleshooting + +| Symptom | Fix | +|---|---| +| Client shows the server as failed or times out at startup | Install globally (`npm i -g @flaid/devmap`) and use `devmap mcp`, or raise the client's startup timeout | +| `No DevMap snapshot was found` | Run `devmap analyze`, or have the agent call `devmap_analyze`, or start with `--auto-refresh` | +| Results keep saying `stale: true` | Call `devmap_analyze`; check that a build step is not rewriting tracked files constantly | +| `devmap_analyze` times out in the client | Raise the client's tool timeout, or pass a smaller `timeoutSeconds` and call again; the analysis keeps running | +| `Another DevMap analysis is already running` | Wait and retry. If no analysis is running, delete `.devmap/analyze.lock` | +| `devmap_explain` is missing | Start the server with `--allow-ai` and make sure `devmap init` configured a provider | +| The server cannot find the project | Pass `--root /absolute/path/to/project` | + +## Limits + +- Analysis depth follows the rest of DevMap: JS/TS ecosystems first. Other languages + have fewer graph edges, so `devmap_impact` is less complete for them. +- The stale check re-scans the project's files, so on a very large repository it is + not free (hence the 10 second cache). +- The server serves one project (`--root`). Register one server per project. +- Only the stdio transport exists; there is no remote (HTTP) mode yet. + +See also: [commands.md](./commands.md), [generated-files.md](./generated-files.md), +[architecture.md](./architecture.md).