{
  "markdown": "<!-- mcp-name: io.github.cdeust/ai-architect-mcp-codebase -->\n\n<p align=\"center\">\n  <img src=\"assets/banner.svg\" alt=\"ai-architect-mcp-codebase — codebase intelligence as an MCP server\" width=\"100%\"/>\n</p>\n\n<p align=\"center\">\n  <a href=\"LICENSE\"><img src=\"https://img.shields.io/badge/License-MIT-blue.svg\" alt=\"MIT License\"></a>\n  <img src=\"https://img.shields.io/badge/Rust-1.95.0_pinned-dea584.svg\" alt=\"Rust 1.95.0, pinned by rust-toolchain.toml\">\n  <img src=\"https://img.shields.io/badge/Tools-26-orange\" alt=\"26 MCP tools\">\n  <img src=\"https://img.shields.io/badge/Tests-1500+_passing-brightgreen\" alt=\"1500+ tests\">\n  <img src=\"https://img.shields.io/badge/Coverage-91%25-brightgreen\" alt=\"91% line coverage\">\n  <a href=\"https://www.bestpractices.dev/projects/13845\"><img src=\"https://www.bestpractices.dev/projects/13845/badge\" alt=\"OpenSSF Best Practices\"></a>\n  <img src=\"https://img.shields.io/badge/Languages-11-blueviolet\" alt=\"11 languages\">\n  <img src=\"https://img.shields.io/badge/Stages-0_through_9-8A2BE2\" alt=\"Stages\">\n</p>\n\n<p align=\"center\">\n  <strong>Cross-platform codebase intelligence for Codex, Gemini CLI, Claude Code, Cursor, VS Code, Zed, and any stdio MCP host.</strong><br>\n  One read-only Rust server, host-specific installation packages, and the same evidence-graded graph answers everywhere.\n</p>\n\n<p align=\"center\">\n  <a href=\"#what-an-agent-can-ask-it\">What An Agent Can Ask</a> · <a href=\"#getting-started\">Getting Started</a> · <a href=\"#the-pipeline\">Pipeline</a> · <a href=\"#26-mcp-tools\">Tools</a> · <a href=\"#architecture\">Architecture</a> · <a href=\"#the-zetetic-standard\">Zetetic Standard</a>\n</p>\n\n<p align=\"center\">\n  <strong>Companion projects:</strong><br>\n  <a href=\"https://github.com/cdeust/Cortex\">Hypermnesia MCP</a> — persistent memory that consolidates and reconsolidates across sessions<br>\n  <a href=\"https://github.com/cdeust/zetetic-team-subagents\">zetetic-team-subagents</a> — 97 genius reasoning agents + 18 team specialists<br>\n  <a href=\"https://github.com/cdeust/ai-architect-mcp-spec\">AI Architect Spec</a> — TypeScript PRD generator that consumes our graph intelligence\n</p>\n\n---\n\nEvery AI coding assistant hits the same wall: you ask it to change `handle_tool_call`, and it either hallucinates a function that was renamed last week, edits something in the wrong community of the codebase, or silently breaks a call chain three modules away. Agents operate on strings; codebases have structure. The gap is where bugs live.\n\n**ai-architect-mcp-codebase** is a cross-platform Rust MCP server for Codex, Gemini CLI, Claude Code, Cursor, VS Code, Zed, and other stdio MCP hosts. It indexes any Rust, Python, TypeScript, Java, Kotlin, Swift, Objective-C, C, C++, or Go codebase into a LadybugDB property graph (Ruby is dispatched on the shallow path — node-kind rows, no deep extraction — for 11 languages in total), resolves imports and call chains across files, detects functional communities via Leiden-class community detection, traces available call-graph paths from detected entry points, builds a hybrid BM25 + sparse TF-IDF + RRF search index, and exposes all of it through 26 MCP tools.\n\nIt is the **codebase intelligence layer** that sits between a finding (\"this bug exists\") and a PRD (\"here is the fix, here is what it affects, here is what it must never break\"). It is **read-only intelligence** — it never writes code, opens PRs, or runs CI. It supplies source-linked structural evidence and analysis limitations for the next stage to inspect.\n\n**One pipeline stage = one MCP tool. 10 stages. 26 tools. 12,000+ lines of Rust. 1500+ tests. Compiler and Clippy checks are part of CI.**\n\n---\n\n## What an agent can ask it\n\nFor inferred Rust receiver calls, pass `lsp: true` to `analyze_codebase` and\ninstall rust-analyzer. The response's `lsp_status.state` distinguishes\n`disabled`, `completed`, and `failed`; failures retain their error and analysis\ncontinues on the available graph, which may contain partial LSP results.\n`lsp_resolve` retains the pass's counts; `resolve.phase = \"static\"` identifies\nthe separate static-resolution receipt. Completion does not mean every call\nwas resolved.\n\nAnalysis persists its coverage report for `query_graph(graph=\"missed\")` and\nreturns the same summary. Rust processes use explicit `#[test]` and\n`#[kani::proof]` attributes, with separate `test` and `proof` entry kinds.\nThese are source declarations, not evidence of execution or successful proof.\n([Rust testing attributes](https://doc.rust-lang.org/reference/attributes/testing.html),\n[Kani proof attributes](https://model-checking.github.io/kani/reference/attributes.html).)\nGraphs created before entry metadata was stored require a full reindex:\n`analyze_codebase` rebuilds them, and `index_codebase` automatically falls back\nto a full index when its incremental compatibility check detects the old schema.\n\n```\nanalyze_codebase(path: \"/path/to/project\", output_dir: \"/tmp/run\")\n  → index + resolve + cluster + build search index in one call\n  → 430 nodes, 400 edges, 216 communities, 35 processes on our own codebase\n\nsearch_codebase(graph_path, query: \"process incoming tool requests\")\n  → hybrid ranked results: BM25 lexical + sparse TF-IDF semantic + RRF fusion\n  → returns: handle_tool_call (score 0.021), dispatch_request (0.020), ...\n\nget_context(graph_path, qualified_name: \"src/main.rs::handle_tool_call\")\n  → 360° view: community membership, process participation,\n    incoming calls, outgoing calls, types used, types that use it\n  → did-you-mean suggestions when the symbol isn't found exactly\n\nget_impact(graph_path, qualified_name)\n  → candidate impact: callers, communities and processes in the available graph\n  → evidence for choosing what to inspect and recheck after a change\n\ndetect_changes(graph_path, diff_text OR base_ref+head_ref)\n  → git diff → affected symbols → impacted communities → touched processes\n  → risk score for the change\n\nvalidate_prd_against_graph(prd_path, graph_path)\n  → does the PRD reference real symbols? (symbol hallucination check)\n  → does \"scoped to X\" match the actual community count?\n  → does \"doesn't affect main\" hold against the call graph?\n\ncheck_security_gates(graph_path, changed_symbols)\n  → auth-critical community touch · unsafe symbol · public API change ·\n    unresolved imports · test coverage gap\n\nverify_semantic_diff(before_graph_path, after_graph_path)\n  → what nodes/edges appeared, what disappeared, what dangles,\n    new cycles via Tarjan SCC, regression score with verdict\n```\n\n\nThese tools establish different kinds of evidence. Stage 2's `verified` receipt\nmeans schema checks, clarification completeness and caller acknowledgement passed;\nits transcript digest binds the recorded bytes, not the truth of the finding.\n`gates_passed` means no critical flag was emitted by the available security checks.\nInspect `report.assessment_complete` as well: it is false for an empty symbol list, skipped\nchecks, or changed symbols that could not be resolved. Review those items and warnings even when\n`gates_passed` is true. The unresolved-import gate reports unresolved imports in\na changed symbol's file; a single graph snapshot cannot establish when they were\nintroduced.\nA semantic-diff `clean` verdict requires a structural regression score below\nthe configured threshold and no positive unresolved-import delta. Any increase\nin unresolved imports produces at least `concerning`, even below that threshold.\nA clean result does not establish behavioral equivalence. Tests, compiler checks\nor formal proofs must establish that separate property.\n\nImpact and process results depend on the relationships the graph captured.\nProcess traversal stops at depth 20; it is graph reachability, not an observed\nruntime trace or an exhaustive account of effects. Preserve coverage and\nresolution qualifiers, and confirm important absence claims against source even\nwhen the coverage report contains no flagged files.\n\n---\n\n## Getting started\n\n### Prerequisites\n\n- Rust 1.95.0 — pinned by [`rust-toolchain.toml`](rust-toolchain.toml), so `rustup` installs and selects it for you; the same compiler builds CI and the releases\n- CMake (LadybugDB builds its C++ core from source — ~5 minutes first build, cached after)\n\n### Clone + build\n\n```bash\ngit clone https://github.com/cdeust/ai-architect-mcp-codebase.git\ncd ai-architect-mcp-codebase\ncargo build --release\n# First build: ~5 minutes (compiles LadybugDB C++ core)\n# Subsequent builds: <1 second incremental\n```\n\n### Register the MCP server\n\nThe repo ships a `.mcp.json` that Claude Code picks up automatically when you open the directory:\n\n```json\n{\n  \"mcpServers\": {\n    \"ai-architect\": {\n      \"command\": \"cargo\",\n      \"args\": [\"run\", \"--quiet\", \"--release\", \"--manifest-path\", \"Cargo.toml\", \"--\", \"--profile\", \"core\"]\n    }\n  }\n}\n```\n\nOr register globally (recommended agent setup — the `core` profile):\n\n```bash\nclaude mcp add ai-architect -- /absolute/path/to/target/release/ai-architect-mcp-codebase --profile core\n```\n\n### Tool profiles\n\nThe server registers one of two tool sets, chosen once at startup:\n\n| Profile | Tools | Who it's for |\n|---|---|---|\n| `core` | 8 — `health_check` · `analyze_codebase` · `search_codebase` · `get_context` · `get_symbol` · `get_impact` · `query_graph` · `detect_changes` | **Recommended for agents.** The read-only code-intelligence surface: analyze once, then search, inspect symbols, and measure blast radius. |\n| `full` | all 26 | The ai-architect pipeline orchestrator — adds the internal finding → PRD stages (1/2/4/6/8/9) and the manual graph passes (`index_codebase`, `resolve_graph`, `cluster_graph`, `lsp_resolve`, `get_processes`, `index_history`). |\n\nSelect with the `--profile` flag or the `AP_PROFILE` environment variable (the flag wins):\n\n```bash\nai-architect-mcp-codebase --profile core   # agent-facing 8\nAP_PROFILE=core ai-architect-mcp-codebase  # same, via env\nai-architect-mcp-codebase                  # default: full (all 26)\n```\n\nThe default stays `full` until the next major version — shrinking the default tool surface is a breaking change. New agent installations should opt into `core`: `analyze_codebase` already runs index + resolve + cluster in one call, so the 18 hidden tools are pipeline plumbing an agent never needs, and hiding them keeps the tool prompt small.\n\n### First run\n\n```bash\n# Run the binary directly to verify the handshake\n./target/release/ai-architect-mcp-codebase\n\n# Or exercise it via stdio JSON-RPC:\nprintf '%s\\n' \\\n  '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}' \\\n  '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\"}' \\\n  '{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"health_check\",\"arguments\":{}}}' \\\n  | ./target/release/ai-architect-mcp-codebase\n```\n\n### Use with other MCP hosts\n\nThe server is a self-contained stdio binary — any MCP host can launch it. Install once:\n\n```bash\ncargo install ai-architect-mcp-codebase   # installs the `ai-architect-mcp-codebase` binary into ~/.cargo/bin\n```\n\n### Install into your agent host (auto-config)\n\nOne command detects your installed hosts and writes the right MCP config for each — **never clobbering** the rest of the file:\n\n```bash\nai-architect-mcp-codebase install\n```\n\nIt configures the top six hosts it detects: **Claude Code** (`~/.claude.json`), **Codex CLI** (`~/.codex/config.toml`), **Gemini CLI** (`~/.gemini/settings.json`), **Cursor** (`~/.cursor/mcp.json`), **VS Code** (`Code/User/mcp.json`), and **Zed** (`~/.config/zed/settings.json`).\n\n- **Never clobbers.** The existing config is parsed; only our `ai-architect` entry is added or updated; every other server survives. A file it cannot safely parse is **never overwritten** — it prints the exact entry to paste by hand.\n- **Zed JSONC.** Zed's `settings.json` allows comments, which strict JSON editing would destroy, so `install` **refuses to edit it in place** and prints the snippet + instructions instead (your comments stay byte-for-byte).\n- **Codex TOML** is edited comment- and format-preserving (via `toml_edit`).\n- **Flags:** `--dry-run` (print planned changes, write nothing), `--only <host>` / `--skip <host>` (filter; `--only` forces a host even if undetected), `--with-hooks` (also register the Grep/Glob PreToolUse hook, see below). Re-running is **idempotent** (a second run reports \"no change\").\n- **Uninstall:** `ai-architect-mcp-codebase uninstall` removes exactly our entries (and the hook), leaving everything else intact.\n\n```bash\nai-architect-mcp-codebase install --dry-run                 # preview\nai-architect-mcp-codebase install --only cursor --only zed  # just these\nai-architect-mcp-codebase install --with-hooks              # + the grep→graph hook\nai-architect-mcp-codebase uninstall                         # remove our entries\n```\n\n**Binary → first query.** Measured on this machine (2026-07): `install` completes in **~1.3 s** (dominated by process/DB startup; the config write itself is sub-second); `analyze_codebase` on this repo's own `src/` (114 files → 16.5k nodes, 16.3k edges — index + resolve + cluster) takes **~12 s wall**; the first `search_codebase` returns instantly. So once the binary exists, **install → analyze → first graph query is ~15 s — well under the 2-minute target.** The one-time `cargo build --release` (~5 min, compiling the LadybugDB C++ core) is a separate, before-the-clock step.\n\n#### Fail-open grep→graph hook\n\n`ai-architect-mcp-codebase install --with-hooks` registers a Claude Code `PreToolUse` hook (matcher `Grep|Glob`) that runs `ai-architect-mcp-codebase hook-augment`. Before a Grep/Glob in a project that has an ai-architect graph, it injects a one-line suggestion to consider `search_codebase`/`query_graph` first. **Cardinal rule: it never blocks the tool call** — no graph, an unparseable payload, or any error → it prints nothing and exits 0. Hook registration is **opt-in** (the `--with-hooks` flag), never default.\n\n### Or configure a host by hand\n\nThe CLI commands below assume `~/.cargo/bin` is on your `PATH`. GUI hosts (Cursor, Windsurf, VS Code) may not inherit your shell `PATH` — in the JSON configs, replace `ai-architect-mcp-codebase` with the output of `which ai-architect-mcp-codebase`. Use the `core` profile (8 read-only tools) for agent hosts.\n\n**Gemini CLI**\n\n```bash\ngemini mcp add -e AP_PROFILE=core ai-architect ai-architect-mcp-codebase\n```\n\nOr install as an extension (this repo ships a `gemini-extension.json`):\n\n```bash\ngemini extensions install https://github.com/cdeust/ai-architect-mcp-codebase\n```\n\nThe extension also exposes three host-native workflows from `skills/`:\n`understand-codebase`, `impact-analysis`, and `validate-change-plan`.\nThey use only the eight tools in the `core` profile and explicitly surface\nindex coverage gaps before accepting negative graph results.\n\n**Claude Code plugin** (primary interface)\n\n```bash\nclaude plugin marketplace add cdeust/ai-architect-mcp-codebase\nclaude plugin install ai-architect-mcp-codebase@ai-architect-mcp-codebase-marketplace\n```\n\nFresh marketplace installs require GitHub CLI 2.68 or newer. The bootstrap\nverifies the release's attached Sigstore bundle against the fixed\n`cdeust/ai-architect-mcp-codebase/.github/workflows/release.yml` signer before\ninstalling any executable; it never accepts a manifest-provided trust anchor.\nThe bundle avoids a Rekor transparency-log lookup, but `gh` can still need the\nnetwork to refresh Sigstore's TUF trust root on a cold cache.\nThis protects the official package and makes a minimal-diff fork that changes\nonly metadata fail closed; it cannot make arbitrary code from a hostile fork\ntrustworthy, because such a fork can also replace the bootstrap itself. Verify\nthat the marketplace slug is exactly `cdeust/ai-architect-mcp-codebase`.\n\n#### Developer escape hatch: running a local dev build in place of the release\n\n`bin/ensure-binary.sh` pins the installed binary to a verified release digest\n(see [Security](#security)) — that pin rejects any binary it did not download\nand verify itself, including one you legitimately rebuilt from source. Set\n`AI_ARCHITECT_SOURCE_CHECKOUT=1` to opt out of the pin for a local dev build.\nThe bootstrap accepts two shapes under this flag, both requiring the explicit\nopt-in — it is never inferred from metadata:\n\n- **Plain source checkout** — `$CLAUDE_PLUGIN_ROOT` itself contains `.git`\n  (you registered a clone directly as the plugin root).\n- **Live-mount montage** — the installed binary at\n  `target/release/ai-architect-mcp-codebase` is a symlink whose fully\n  resolved target lies outside `$CLAUDE_PLUGIN_ROOT` and sits inside its own\n  `.git`-bearing checkout (e.g. a marketplace cache whose binary was replaced\n  with a symlink into a separate dev clone, so you can iterate without\n  reinstalling the plugin after every rebuild). Added in\n  [#208](https://github.com/cdeust/ai-architect-mcp-codebase/pull/208) —\n  a plain `.git`-at-root check cannot see this shape, because a marketplace\n  cache has no `.git` of its own.\n\n**What the flag skips, precisely:** only the release-binary digest\nverification (`sha256sum` against the cached/pinned digest) and, for a fresh\ninstall, the download + Sigstore provenance check — for that one launch. It\ndoes **not** skip the `Cargo.toml` / `plugin.json` presence checks (still\n`fatal` if either file is missing), and for a plain source checkout it still\nruns the freshness rebuild (`cargo build --release` when `src/` is newer than\nthe binary). For the montage shape specifically, nothing rebuilds the\nbinary — the bootstrap trusts the already-built binary the symlink resolves\nto, as-is.\n\n**Threat model.** This is an explicit, user-set opt-in, never something\npackaged metadata can trigger. An attacker who can already write to your\nplugin cache — replacing the installed binary with a symlink to force this\npath — can just as easily replace `bin/ensure-binary.sh` or\n`bin/launch-plugin.sh` themselves, so the digest pin was never a defense\nagainst that attacker; it defends the *default* path (flag unset) where the\nbootstrap is the thing standing between a marketplace download and your\nshell. The default path is unchanged by this hatch and remains a hard\n`fatal` on any digest mismatch. Every accepted bypass is announced on\n`stderr` even in quiet mode:\n\n```\nai-architect-mcp-codebase: bootstrap verification skipped (source-checkout mode)\nai-architect-mcp-codebase: live-mounted dev symlink: <plugin-cache>/target/release/ai-architect-mcp-codebase -> <resolved dev path> (source checkout at <resolved .git root>)\n```\n\n**Diagnosing the failure mode without the flag.** If a marketplace-cache\nbinary is replaced by a montage symlink and `AI_ARCHITECT_SOURCE_CHECKOUT` is\nnot set, the plugin dies silently from Claude Code's point of view — you only\nsee `MCP error -32000: Connection closed`. The real cause is on stderr, which\nClaude Code does not surface for a failed MCP launch; run the launcher by\nhand with `CLAUDE_PLUGIN_ROOT` set to the plugin cache directory to see it:\n\n```bash\nCLAUDE_PLUGIN_ROOT=/path/to/plugin/cache bin/launch-plugin.sh\n# ai-architect-mcp-codebase: FATAL: cached binary digest mismatch; reinstall the plugin\n```\n\n**Operational gotcha:** `export AI_ARCHITECT_SOURCE_CHECKOUT=1` in\n`~/.zshrc` alone is not enough. `~/.zshrc` is read only by *interactive*\nshells; the Claude Code plugin launcher and its hooks run in non-interactive\nones and never see it. Put the export in `~/.zshenv` (or your shell's\nequivalent non-interactive startup file) instead.\n\nIf the former Automatised Pipeline plugin is installed, remove it before\ninstalling the canonical package:\n\n```bash\nclaude plugin uninstall automatised-pipeline@automatised-pipeline-marketplace\nclaude plugin marketplace remove automatised-pipeline-marketplace\n```\n\nClaude MCP allowlists and permissions must also replace every prefix listed in\n`revoked_claude_tool_prefixes` in the contract with\n`mcp__plugin_ai-architect-mcp-codebase_ai-architect__<tool>`. The final\n`ai-architect` segment is intentionally stable: it is the MCP server key, not\nthe plugin's distribution name. The machine-readable source of truth is\n[`mcp-contract.json`](mcp-contract.json); consumer repositories validate their\nallowlists against its derived `claude_tool_prefix` instead of maintaining an\nindependent spelling.\n\nContract schema 1 requires `distribution`, `claude_plugin`,\n`claude_marketplace`, `mcp_server`, `claude_tool_prefix`, and\n`revoked_claude_tool_prefixes`. Consumers must pin the raw contract URL to the\nfull commit SHA (tags can be moved), validate that the prefix equals\n`mcp__plugin_<claude_plugin>_<mcp_server>__`, and remove revoked prefixes from\nallowlists rather than retaining them as aliases. Consumer PRs record the full\nproducer commit in their contract URL; the v0.11.1 release must not be assumed\navailable until its verified-release workflow completes.\nThe same contract is included in the crate, MCPB, and signed release assets.\n\n**OpenAI Codex CLI** (also picked up by the ChatGPT desktop app and Codex IDE extension — they share `~/.codex/config.toml`)\n\n```bash\ncodex mcp add ai-architect -- ai-architect-mcp-codebase --profile core\n```\n\nOr in `~/.codex/config.toml`:\n\n```toml\n[mcp_servers.ai-architect]\ncommand = \"ai-architect-mcp-codebase\"\nargs = [\"--profile\", \"core\"]\n```\n\nOr install the packaged Codex plugin and its three matching skills from this\nrepository's marketplace:\n\n```bash\ncargo install ai-architect-mcp-codebase\ncodex plugin marketplace add cdeust/ai-architect-mcp-codebase\ncodex plugin add ai-architect-mcp-codebase@ai-architect-mcp-codebase\n```\n\nThe Codex package lives under `plugins/ai-architect-mcp-codebase/`, with its own\n`.mcp.json` fixed to `--profile core`. This isolation is intentional: the\nroot `.mcp.json` remains the existing Claude project configuration and keeps\nthe server's backward-compatible `full` default.\n\nFor Gemini CLI, uninstall the former extension identity before reinstalling\nfrom the renamed repository:\n\n```bash\ngemini extensions uninstall ai-architect\ngemini extensions install https://github.com/cdeust/ai-architect-mcp-codebase\n```\n\n**Cursor** — `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):\n\n```json\n{\n  \"mcpServers\": {\n    \"ai-architect\": {\n      \"command\": \"ai-architect-mcp-codebase\",\n      \"args\": [\"--profile\", \"core\"]\n    }\n  }\n}\n```\n\n**Windsurf** — `~/.codeium/windsurf/mcp_config.json`: same `mcpServers` block as Cursor.\n\n**VS Code** — `.vscode/mcp.json`:\n\n```json\n{\n  \"servers\": {\n    \"ai-architect\": {\n      \"type\": \"stdio\",\n      \"command\": \"ai-architect-mcp-codebase\",\n      \"args\": [\"--profile\", \"core\"]\n    }\n  }\n}\n```\n\n**OpenAI Agents SDK (Python)**\n\n```python\nfrom agents.mcp import MCPServerStdio\n\nasync with MCPServerStdio(\n    name=\"ai-architect\",\n    params={\"command\": \"ai-architect-mcp-codebase\", \"args\": [\"--profile\", \"core\"]},\n) as server:\n    agent = Agent(name=\"Assistant\", mcp_servers=[server])\n```\n\n---\n\n## The pipeline\n\nEvery stage is a tool. Stages build on each other but are independently callable. The pipeline is serial in logical order but MCP calls are stateless — you can re-run stages 3a-3d on a fresh codebase without re-running stages 1-2.\n\n| # | Tool(s) | What it does |\n|---|---|---|\n| **0** | `health_check` | Handshake + protocol + tool count |\n| **1** | `extract_finding`, `refine_finding` | Deterministic finding extraction + orchestrator-aware prompt refinement |\n| **2** | `start_verification`, `append_clarification`, `finalize_verification`, `abort_verification` | Human-gated clarification loop with SHA-256 transcript digest, atomic single-file session state |\n| **3a** | `index_codebase`, `query_graph`, `get_symbol` | tree-sitter AST → LadybugDB graph (16 node labels, 36+ relationship tables); user-configurable `exclude_dirs` and graceful skip of unreadable directories (issue #249) |\n| **3b** | `resolve_graph`, `lsp_resolve` | Import/call/impl resolution with confidence scoring + optional LSP deep resolution (rust-analyzer / pyright / typescript-language-server) |\n| **3c** | `cluster_graph`, `get_processes`, `get_impact` | Leiden-class community detection (Louvain + C2 repair) + BFS execution-flow tracing from entry points |\n| **3d** | `search_codebase`, `get_context`, `analyze_codebase`, `detect_changes` | Hybrid BM25 + sparse TF-IDF + RRF search · 360° symbol view · all-in-one analysis · git-diff impact |\n| **4** | `prepare_prd_input` | Bundle verified finding + graph intel → artifact for ai-architect-mcp-spec |\n| **6** | `validate_prd_against_graph` | Symbol hallucination · community consistency · process-impact contradiction |\n| **8** | `check_security_gates` | Auth-critical community · unsafe symbol · public-API change · unresolved-import presence · test-coverage gap |\n| **9** | `verify_semantic_diff` | Before/after graph diff with Tarjan SCC cycle detection and regression scoring |\n\n> Stages 5 (PRD generation), 7 (implementation), 10 (benchmark), 11 (deployment), 12 (PR) belong to other systems in the pipeline: [ai-architect-mcp-spec](https://github.com/cdeust/ai-architect-mcp-spec), the coding agent, CI, and `gh`. This project is the **read-only intelligence** half.\n\n---\n\n## 26 MCP Tools\n\nEvery tool takes structured JSON arguments via the MCP protocol and returns a structured JSON response. No LLM is called from inside any tool — intelligence is the agent's job; the tool's job is safe, fast data movement with invariants.\n\n```\nStage 0:  health_check\nStage 1:  extract_finding · refine_finding\nStage 2:  start_verification · append_clarification · finalize_verification · abort_verification\nStage 3:  ingest_traces\nStage 3a: index_codebase · index_status · query_graph · get_symbol\nStage 3b: resolve_graph · lsp_resolve\nStage 3c: cluster_graph · get_processes · get_impact\nStage 3d: search_codebase · get_context · analyze_codebase · detect_changes\nStage 3e: index_history\nStage 4:  prepare_prd_input\nStage 6:  validate_prd_against_graph\nStage 8:  check_security_gates\nStage 9:  verify_semantic_diff\n```\n\nEach tool has a JSON Schema enforced at the wire, reason codes on error (no cryptic protocol errors), and a receipt-style response with timing and counts.\n\n> Agent installs rarely need all 26 — the `core` profile (see [Tool profiles](#tool-profiles)) registers just the 8 code-intelligence tools.\n\n### Team-shared graph artifact (optional)\n\n`index_codebase` can commit a compressed snapshot of the graph so teammates who\nclone the repo never have to cold-index it.\n\n- `index_codebase` with `\"export_artifact\": true` writes, after a successful\n  index, a `tar → zstd` snapshot to `<path>/.ai-architect-mcp-codebase/graph.zst`\n  plus a `graph.meta.json` sidecar (schema version, git sha, tool version,\n  node/edge counts). It also appends a `.gitattributes` entry\n  (`.ai-architect-mcp-codebase/graph.zst binary merge=ours`) so the committed\n  binary never produces merge conflicts across branches. Commit both files.\n  A repo indexed before the project rename (issue #195) carries this snapshot\n  under the old `.automatised-pipeline/` directory name; the first touch of\n  the artifact (export, bootstrap, or even a `hook-augment` Grep/Glob check)\n  migrates it to the current name in place — a one-shot rename, not a\n  permanent dual-path read.\n- `index_codebase` with `\"bootstrap\": true` — when there is no local graph at\n  `<output_dir>/graph` but a committed artifact is present — decompresses the\n  snapshot instead of cold-indexing. **Staleness is checked first** by comparing\n  the artifact's git sha with the repo's current HEAD:\n  - shas equal → import as-is (nothing to fill), response `source='artifact_bootstrap'`,\n    `graph_state='fresh'`;\n  - shas differ → by **default the snapshot is imported, then incrementally\n    filled** up to the working tree (only the artifact→HEAD diff is\n    re-parsed), response `source='artifact_bootstrap_fill'`,\n    `graph_state='filled_to_working_tree'`, carrying `fill_method` and\n    `{changed, added, deleted, renamed, unchanged}` counts;\n  - `\"accept_stale\": true` → import the stale snapshot anyway and **skip the\n    fill**, and the response carries a `stale_artifact`\n    `{artifact_sha, head_sha, commits_behind}` report so a stale graph is\n    never mistaken for a fresh one.\n\n  A fill that fails (no git diff and no bundled manifest) falls back to a\n  full index, as does an import failure — both explicit (logged to stderr,\n  never a silent partial graph) and reported via a `bootstrap_skipped` note.\n\n### Excluding directories from the walk (issue #249)\n\nBoth `index_codebase` and `analyze_codebase` accept `\"exclude_dirs\"`\n(default `[]`) — directory names or paths to prune from the walk in\naddition to the built-in build/dependency skip list (`node_modules`,\n`.venv`, `vendor`, `target`, …). This is for directories that must never be\nread (a secrets folder, a credentials mount), not a performance prune:\n\n- An entry **without** a path separator (e.g. `\"secrets\"`) is a bare name,\n  matched anywhere in the tree — like the built-in list.\n- An entry **with** a path separator (e.g. `\"config/secrets\"`) is a path\n  relative to `path`, matched as exactly one subtree. No glob support.\n- Exclusion **wins over every `dependency_scope` tier**, including `full` —\n  it is checked before, and independently of, dependency-directory descent.\n- Pruned directories are never silently dropped: each appears in the\n  coverage sidecar as `skipped` with reason `user_excluded`, and the\n  response's `coverage.skipped.user_excluded_count` carries the exact count.\n- Changing `exclude_dirs` on an existing graph requires `\"full\": true` — like\n  `dependency_scope`, the incremental-index manifest does not capture it.\n\nIndependent of `exclude_dirs`, a directory the OS refuses to read\n(`EACCES`/`PermissionDenied`) no longer aborts the whole index: it is\nrecorded in the coverage sidecar with reason `unreadable` and the walk\ncontinues past it, so one locked-down subdirectory can no longer discard an\notherwise-successful index.\n\nAll three flags default to `false`, so existing behavior and the `core`/`core8`\nprofiles are unchanged. The artifact is entirely optional: without it,\n`index_codebase` cold-indexes exactly as before.\n\n> Post-import *incremental fill* (re-index only the `artifact_commit..HEAD` diff\n> instead of a full re-index) is tracked in\n> [#62](https://github.com/cdeust/ai-architect-mcp-codebase/issues/62) — it needs a\n> changed-files-only indexer, which AP does not yet have.\n\n---\n\n## Architecture\n\nRust MCP server, hand-rolled stdio JSON-RPC 2.0 (no SDK — we own the wire). Clean Architecture with module boundaries.\n\n```\ntransport (stdio, JSON-RPC framing)\n      ↓\nserver/main.rs  (request dispatch, tool registry)\n      ↓\nhandlers (do_* functions, one per tool)\n      ↓\ncore modules:\n    graph_store        — LadybugDB port (Cypher + UNWIND + prepared statements)\n    parser/{rust,python,typescript,mod}  — tree-sitter AST extractors\n    indexer            — walk + parse + persist pipeline\n    resolver           — cross-file import/call/impl resolution\n    lsp_{client,resolver}  — optional LSP deep resolution\n    clustering         — inline Louvain + C2 repair + process tracing\n    search/{bm25,vector,rrf,mod}  — hybrid search (Tantivy + sparse TF-IDF + RRF)\n    prd_input          — stage 4: bundle for ai-architect-mcp-spec\n    prd_validator      — stage 6: validate PRD claims against graph\n    security_gates     — stage 8: auth/unsafe/API/imports/coverage checks\n    semantic_diff      — stage 9: before/after graph regression scoring\n    git_diff           — diff parser + symbol mapping\n```\n\n### Dependencies\n\nSixteen crates. Nothing speculative; everything justified.\n\n| Crate | Purpose | License | Why |\n|---|---|---|---|\n| `serde` + `serde_json` | Wire serialization | MIT | JSON-RPC, artifact persistence |\n| `sha2` | Stage-2 transcript digest | MIT | Tamper detection |\n| `lbug` (LadybugDB) | Embedded property graph + Cypher | MIT | Native Cypher, FTS-ready, the Kùzu successor |\n| `tree-sitter` | Incremental parser runtime | MIT | First-class Rust bindings |\n| `tree-sitter-rust` · `-python` · `-typescript` · `-java` · `-kotlin-ng` · `-swift` · `-objc` · `-c` · `-cpp` · `-go` | Language grammars (10) | MIT / Apache-2.0 | Semantic structure without a compiler |\n| `tantivy` | Lucene-grade BM25 | MIT | Real ranked text search, <10ms startup |\n\nDeliberately **not** included: async runtime (we're stdio-blocking), HTTP client, LLM SDK, embedding model runtime (sparse TF-IDF replaces it at zero dep cost).\n\n### Storage\n\nGraphs are per-finding by design (Lamport's isolation invariant): each finding gets its own LadybugDB instance at `<output_dir>/runs/<run_id>/findings/<finding_id>/graph/`. Zero-coordination concurrency, trivial cleanup, no cross-finding state leakage. Redundant indexing for shared codebases is acknowledged and mitigated in a later optional cache layer — not shoehorned into the core.\n\n### Configuration — `max_db_size`\n\nEvery LadybugDB `Database` this crate opens reserves virtual address space up front, sized by `max_db_size`. lbug's own default (`SystemConfig::default()`) is `1 << 43` = 8 TiB per instance; with `graph_cache`'s `MAX_CACHED_GRAPHS = 8` entries live in the read-path cache at once, that is a 64 TiB worst case (issue #25). `src/graph_store.rs::system_config()` is the single choke point every `GraphStore::open_or_create` call resolves through, in this precedence order:\n\n1. **`AP_LBUG_TEST_MAX_DB_SIZE`** — test-only, set for every `cargo test` process via `.cargo/config.toml`'s `[env]` table (512 MiB / `2^29`, issue #21). Always wins when present, so `cargo test` behavior is independent of the production knob below.\n2. **`AP_LBUG_MAX_DB_SIZE`** — production override, unset by default. Bytes, must be a power of two and at least 8 MiB (lbug's own `BufferManager::verifySizeParams` floor). An invalid value is rejected with an actionable error at `GraphStore::open_or_create` time — never a silent fallback.\n3. **Default: 8 TiB (`1 << 43` bytes)** when neither var is set — lbug's own `DEFAULT_VM_REGION_MAX_SIZE`, the engine's per-database VM-region ceiling on every 64-bit desktop/server platform (`lbug-0.19.1/lbug-src/src/include/common/constants.h`). This is an address-space **reservation**, not an allocation: disk and memory grow only with the data actually written. An earlier release capped the default at 8 GiB (issue #25, sized from the measurement table below); that cap **aborted any ingestion whose graph outgrew it** and was repealed on 2026-08-14 — an index must complete regardless of corpus size, multi-TiB included.\n\nSet `AP_LBUG_MAX_DB_SIZE` to bound the reservation in address-space-constrained environments (e.g. containers with a low `RLIMIT_AS`); the historical measurement table below documents typical graph sizes.\n\n**Measured graph sizes (2026-07-15, `du -k` on every `graph` file found under `~/.cache/cortex/code-graphs/*/graph`, `~/.cortex/ap_graph/graph`, and `**/.prd-gen/graphs/*/graph`), top 10 of 75:**\n\n| Graph | Size |\n|---|---|\n| `repro-cortex-viz-deps` (cortex-viz + `node_modules`) | 473 MiB |\n| `bench-c2-viz-deps` (cortex-viz + deps) | 472 MiB |\n| `bench-c3-viz-pubapi` (cortex-viz, public API surface) | 460 MiB |\n| `wt-windows-launcher-96-97-*` (Cortex worktree) | 147 MiB |\n| `wt-homeostatic-*` (Cortex worktree) | 144 MiB |\n| `wt-tools-drift-*` (Cortex worktree) | 143 MiB |\n| `Cortex-wt-wiki-titles-*` | 142 MiB |\n| `wt-findings-provenance-*` | 126 MiB |\n| `anthropic-partnership-Cortex` | 126 MiB |\n| `wt-ingest-provenance-*` | 124 MiB |\n\nTotal across all 75 measured graphs: ~4.87 GiB. Every graph other than the top 3 (which include `node_modules`) is under 150 MiB — the `node_modules`-inclusive runs are the actual worst case driving the sizing rule above.\n\n---\n\n## The zetetic standard\n\nInherited from [zetetic-team-subagents](https://github.com/cdeust/zetetic-team-subagents). Not a prompt suggestion — an enforcement rule that holds in code.\n\n| Pillar | Question |\n|---|---|\n| **Logical** | *Is it consistent?* |\n| **Critical** | *Is it true?* |\n| **Rational** | *Is it useful?* |\n| **Essential** | *Is it necessary?* |\n\n**In this codebase it concretely means:**\n\n1. Every algorithm traces to a source. Louvain → *Blondel et al. 2008*. Leiden C2 repair → *Traag et al. 2019*. RRF → *Cormack, Clarke, Büttcher 2009*. SCC → *Tarjan 1972*. BM25 via Tantivy → *Robertson et al. 1994*.\n2. Named constants should record their source or measured rationale. `RRF_K = 60` cites Cormack 2009. `BULK_BATCH_SIZE = 500` cites Kùzu/LadybugDB tuning. `PARSE_TIMEOUT_MICROS = 5_000_000` is justified in the block above it.\n3. No invented numbers. Where a value was chosen by judgment, the comment says so (\"heuristic, not paper-backed\") and cites its operational justification.\n4. Tool responses cite the spec that governs each error reason. `unsafe finding_id (spec §5.1.4, §9.3 Q4): must match [A-Za-z0-9._-]+` — callers see which rule they violated.\n5. When a capability can't be proved at spec time, the tool degrades gracefully and says so in plain language. Example: `lsp_resolve` on a stub binary returns `lsp_probe_failed: found on PATH but didn't respond as an LSP server (stdout closed immediately; likely a stub, proxy, or non-LSP binary)` — not a cryptic protocol error.\n\n---\n\n## Security\n\nFour CRITICAL, four HIGH, three MEDIUM findings were surfaced by a `security-auditor` agent pass and fixed in commit [`512d683`](https://github.com/cdeust/ai-architect-mcp-codebase/commit/512d683):\n\n- Cypher injection via `insert_edge` → centralized `cypher_str()` escaping (`\\` first, then `'`)\n- Git argument injection → `validate_git_ref` rejects `--`, newlines, NUL; `--` separator before refs\n- Arbitrary binary execution via `lsp_command` → strict allowlist (`rust-analyzer`, `pyright`, `pyright-langserver`, `typescript-language-server`)\n- Symlink traversal → `fs::symlink_metadata` + `MAX_DEPTH`\n- Resource exhaustion → `MAX_FILES=100_000`, `MAX_FILE_BYTES=10 MB`, `MAX_TOTAL_BYTES=2 GB`, `MAX_DEPTH=64`\n- Tree-sitter pathological input → `set_timeout_micros(5_000_000)` + `MAX_PARSE_BYTES=1 MB`\n- `query_graph` read-only → two layers over disjoint statement families (see below)\n- `graph_path` filesystem safety → `validate_graph_path_safe()` before any `remove_dir_all`\n- LSP `rootUri` → RFC 3986 percent-encoding\n- Diff line overflow → `DIFF_LINE_MAX = u64::MAX / 2` guard\n\nEach fix has a test that asserts the exploit is now rejected. Run `cargo test` to see 1500+ tests pass including the exploit-regression suite.\n\n### How `query_graph` is kept read-only\n\nTwo layers, covering **disjoint** statement families. Neither subsumes the other.\n\n| Layer | Refuses | Mechanism |\n|---|---|---|\n| Engine (`GraphStore::execute_read_only_query`) | every database write and DDL — `CREATE`, `MERGE`, `SET`, `DELETE`/`DETACH DELETE`, `DROP`, `ALTER`, however spelled | `PreparedStatement::is_read_only()`: the verdict comes from the compiled plan, so a mutation written in syntax no keyword scan enumerates is still refused |\n| Lexical (`FORBIDDEN_CYPHER_KEYWORDS`) | filesystem and database movement — `COPY … TO`, `EXPORT`/`IMPORT DATABASE`, `ATTACH`, `DETACH`, `USE`, `LOAD FROM` | whole-word, case-insensitive scan of the query with string literals, backticked identifiers and comments masked out first |\n| Lexical (`READ_ONLY_PROCEDURES`) | every `CALL` naming anything but `TABLE_INFO` / `SHOW_TABLES` — including the `CALL <setting> = <value>` configuration form | per-PROCEDURE classification of the identifier after each `CALL` token |\n\nThe lexical layer is **load-bearing, not defence in depth**. lbug's\n`StatementReadWriteAnalyzer` overrides `visitCopyFrom` but leaves **six**\nstatements at the base visitor's no-op — `visitCopyTo`, `visitExportDatabase`,\n`visitImportDatabase`, `visitAttachDatabase`, `visitDetachDatabase` and\n`visitUseDatabase` (`parsed_statement_visitor.h`:51, 57-61 on lbug 0.19.1) — so all\nsix are classified read-only. `DETACH`/`USE` were added to the denylist on\n2026-08-25 after a mechanical re-audit against those headers; before it, both\npassed the lexical filter AND the engine filter. Measured 2026-08-24 against\nlbug 0.19.1 on **both** available engine gates — `is_read_only()` and a database\nopened with `SystemConfig::read_only(true)`, which reaches the same predicate via\n`ClientContext::validateTransaction` — `COPY (…) TO 'f.csv'` and\n`EXPORT DATABASE 'd'` execute and write the filesystem, while both correctly refuse\n`CREATE NODE TABLE`. Pinned by `engine_gate_does_not_cover_filesystem_writes` and,\nfor the whole family, `engine_classifies_every_filesystem_statement_as_read_only`.\n\n`CALL` is classified **per procedure** rather than refused wholesale, so schema\nintrospection (`CALL table_info('Function') RETURN *`) is reachable. That\ndistinction is load-bearing too: the same analyzer returns `readOnly = true` from\n`visitStandaloneCall`, so `CALL threads = 8` — a configuration write — is\nengine-read-only, and this lexical layer is the only barrier that exists against\nit. Relaxing the KEYWORD rather than allowlisting the PROCEDURE would remove that\nbarrier entirely.\n\nA keyword introduced by `:` or `.` is an identifier, not a clause, so queries over\nthis schema's own `Import` node table work unchanged:\n`MATCH (f:File)-[:Defines_File_Import]->(n:Import) WHERE n.is_resolved = false RETURN n.path`.\n\nThe gate does **not** extend that exemption to an alias (`AS <keyword>`), where the\nclause detectors do. The asymmetry is deliberate: on the gate an exemption can only\never let a keyword through, so it fails closed and a bare `use`/`create` pattern\nvariable is refused (backtick it); on the clause detectors the expensive direction\nis reversed, because reading `AS limit` as a clause would suppress the `LIMIT`\ninjection. A masked literal or backticked identifier is treated as a TOKEN, never\nas whitespace, so no look-back can walk across one.\n\n`query_graph` executes **one statement per call**. A trailing `;` is accepted; a\n`;`-chained request is refused with reason `multi_statement_not_supported`, because\nthe read-only classification, `LIMIT` injection, `ORDER BY` detection and the offset\ncursor are all properties of a single statement.\n\nThe full security argument — threat model, trust boundaries, what each claim\nrests on, and where it stops — is in\n[docs/ASSURANCE-CASE.md](docs/ASSURANCE-CASE.md). Reporting process and response\nSLA: [SECURITY.md](SECURITY.md). How the project is run and what happens if the\nmaintainer stops: [GOVERNANCE.md](GOVERNANCE.md). Where it is going:\n[docs/ROADMAP.md](docs/ROADMAP.md). OpenSSF Best Practices answers, criterion by\ncriterion: [.bestpractices.json](.bestpractices.json).\n\n---\n\n## Scale\n\nRe-measured 2026-07-28 on the current dependency (`lbug 0.18`, rustc 1.95.0,\nmacOS 26.5.1 arm64) by re-running the `dba` agent's nine compile-and-run probes\n— `cargo test --release --test lbug_bulk_investigation -- --nocapture`, 199\nedges per strategy. The ranking is the same one the original 0.15.3 run found;\nthe absolute figures are not comparable across the two runs, because both the\nengine version and the machine changed.\n\n| Strategy | ms/edge |\n|---|---|\n| Raw string per edge (naive) | 9.658 |\n| Prepared statement, no transaction | 6.924 |\n| `BEGIN TRANSACTION` + prepared + `COMMIT` | 0.328 |\n| **UNWIND + typed `LogicalType::Struct`** | **0.127** |\n\nThe chosen path is **76× faster than the naive one** on this measurement.\n\nThe bulk-insert path uses UNWIND with a typed struct schema (the engineer who wrote the first version used `LogicalType::Any` which fails the binder — the typed struct form works). Prepared statements are cached in a `RefCell<HashMap<query, PreparedStatement>>` on the `GraphStore`. Sparse TF-IDF replaces the dense `N × V × 4B` matrix — **30.5× smaller** on our own codebase (108 KB vs 3.2 MB) and scales linearly with non-zero terms rather than vocab size. Clustering eliminated `probe_node_label_for_process` (per-node Cypher round-trip) in favor of a single in-memory `HashMap<id, label>` population pass.\n\n500-file synthetic Rust fixture indexes in **~38 seconds** end-to-end (parse + resolve + cluster + search index), down from the pre-audit implied \"5 min – 1 hour\" bracket.\n\n---\n\n## Falsifiable evidence — graph tools vs a Grep/Glob/Read baseline\n\nThis offline retrieval evaluation compares graph queries with a fixed\nsubstring-search/full-file-read protocol on an authored 4-language corpus\n(Python, TypeScript, Go, Rust): 20 questions across five capability dimensions.\n`benchmarks/eval_headtohead/PRE_REGISTRATION.md` records the hypotheses and\nprotocol. The current results below are the post-#92 run in\n`benchmarks/eval_headtohead/results.json`; earlier runs remain separately saved.\nSee that folder's `MANIFEST.md` for provenance and `reproduce.sh` for the command.\nThe deterministic evaluation needs no API key or external corpus; building it\nrequires the Rust toolchain and dependencies to be available.\n\n| metric (mean ± sample stdev, n=20) | AP graph tools | Grep/Glob/Read baseline | source field |\n|---|---:|---:|---|\n| retrieval precision | **1.00 ± 0.00** | 0.65 ± 0.33 | `aggregate.{graph,explorer}.precision` |\n| retrieval recall | **1.00 ± 0.00** | 1.00 ± 0.00 | `aggregate.*.recall` |\n| payload token proxy | **43.14 ± 17.26** | 550.36 ± 330.28 | `aggregate.*.tokens` |\n| modeled tool calls | **1.00 ± 0.00** | 5.20 ± 1.64 | `aggregate.*.tool_calls` |\n| mean per-question token ratio (baseline / graph) | **14.26×** | — | `aggregate.token_ratio_explorer_over_graph.mean` |\n| mean per-question tool-call ratio | **5.20×** | — | `aggregate.toolcall_ratio_explorer_over_graph.mean` |\n\nAll four hypotheses H1–H4 are **SUPPORTED** in the current run under this\nprotocol. The original run **FALSIFIED H4**: graph recall was 0.825 against 1.00.\nIts five losses (`go-D3`, `go-D4`, `rs-D2`, `rs-D4`, `ts-D4`) remain in\n`raw_results.2026-07-26-pre-fix-87.json`; fixes #87 and #92 closed these gaps.\nThe corpus informed those fixes, so the current result is a regression benchmark,\nnot an unseen generalization test.\n\nCosts are modeled, not observed AI-client bills or tool traces. The graph leg\nserializes a benchmark-specific compact envelope of symbol identities; the\nbaseline counts its substring-hit transcript plus full matching files. Both use\na payload-size / 4 token proxy. Graph calls are assigned one per question;\nbaseline calls are assigned two plus the number of matching files. Indexing,\nclient prompts, actual MCP response envelopes and model reasoning are excluded.\nThe 14.26× figure is a mean of per-question ratios; dividing aggregate payload\nvolumes gives 12.76×, a different statistic. The optional answer-quality judge\n(`AP_EVAL_JUDGE_CMD`) did not run. These measurements establish file-retrieval\nresults and protocol costs, not AI-agent success, hallucination reduction or\nreal-world token savings.\n\n---\n\n## Integration with the rest of the stack\n\n```\n                 ┌─────────────────────────────────────────┐\n                 │           Claude Code agent             │\n                 └────────────┬────────────────────────────┘\n                              │ MCP (stdio JSON-RPC)\n                              ↓\n      ┌──────────────────────────────────────────────────┐\n      │             ai-architect-mcp-codebase                 │  ← this repo\n      │  stage 0 · 1 · 2 · 3a-e · 4 · 6 · 8 · 9          │\n      │  Rust · LadybugDB · tree-sitter · Tantivy        │\n      └──────┬──────────────────┬────────────────────────┘\n             │                  │\n             │                  └────→  stage 5 (PRD gen)\n             │                         [ai-architect-mcp-spec]\n             ↓                          TypeScript / Node\n     ┌─────────────────┐                    │\n     │     Cortex      │                    │\n     │  memory engine  │ ←──────────────────┘\n     │  PostgreSQL +   │\n     │    pgvector     │\n     └─────────────────┘\n             ↑\n             │  cross-session memory for findings,\n             │  decisions, lessons learned\n             │\n     ┌─────────────────────────────┐\n     │  zetetic-team-subagents     │\n     │  97 genius + 18 specialists │\n     │  problem-shape routing      │\n     └─────────────────────────────┘\n```\n\n- **Cortex** — every architectural decision made during a pipeline run gets remembered. When the next finding touches a similar area, Cortex surfaces the prior reasoning before you re-derive it.\n- **zetetic-team-subagents** — the genius agents (Shannon, Lamport, Simon, Popper, Feynman, Fermi, dba, architect, security-auditor, engineer) designed this project stage by stage. Every major decision in `stages/*.md` traces to an agent dispatch.\n- **ai-architect-mcp-spec** — consumes our `stage-4.prd_input.json` artifact via disk or MCP-to-MCP query of `search_codebase` / `get_context` / `get_impact`. Each in its ideal language: our performance-critical graph work in Rust, their document generation in TypeScript.\n\n---\n\n## Testing\n\n```bash\ncargo test                                          # 1500+ tests, full suite\ncargo test --release --test scalability_bench       # 500-file synthetic fixture\ncargo test --release --test lbug_bulk_investigation # dba's 9 UNWIND probes\ncargo test --release --test stage3a_integration     # end-to-end per sub-stage\ncargo test --release --test stage9_integration      # before/after diff\ncargo check                                         # zero warnings required\ncargo build --release                               # release binary\n```\n\nEvery stage has an integration test with fixture data. The `lbug_bulk_investigation` test is intentionally preserved — it's the compile-and-run proof that dba's UNWIND pattern works, kept for regression protection and documentation.\n\n---\n\n## Repository layout\n\n```\nai-architect-mcp-codebase/\n├── src/\n│   ├── main.rs                    ← MCP server entry point\n│   ├── cli.rs                     ← argument parsing + startup wiring\n│   ├── tool_schemas.rs            ← JSON Schemas for every tool\n│   ├── tool_profile.rs            ← core/full profile selection\n│   ├── lib.rs                     ← re-exports for integration tests\n│   ├── analyze_handlers.rs        ← one file per tool-handler group\n│   ├── indexing_handlers.rs · query_handlers.rs · symbol_handlers.rs\n│   ├── search_context_handlers.rs · process_impact_handlers.rs\n│   ├── history_handlers.rs · prd_handlers.rs\n│   ├── verification_core.rs · verification_ops.rs\n│   ├── graph_store/               ← LadybugDB port (UNWIND + prepared + cached)\n│   │   ├── mod.rs · config.rs · ddl.rs · schema.rs · serialize.rs · membership.rs\n│   ├── parser/\n│   │   ├── mod.rs                 ← language dispatch\n│   │   ├── language.rs            ← the Language enum — 11 variants\n│   │   └── spec/                  ← per-language specs + shared walkers/\n│   ├── indexer/                   ← walk + parse + persist (+ iac/, persist/)\n│   ├── resolver/                  ← cross-file resolution\n│   │   ├── imports.rs · calls.rs · extends.rs · implements.rs · uses.rs · phases.rs\n│   ├── resolver_layers.rs · lsp_client.rs · lsp_resolver.rs\n│   ├── clustering/                ← Louvain + C2 repair + BFS process tracing\n│   │   ├── community.rs · process.rs · impact.rs\n│   ├── search/\n│   │   ├── mod.rs                 ← public types, index path, search_graph\n│   │   ├── hybrid.rs · substring.rs   ← the two ranking paths\n│   │   ├── context.rs · name_lookup.rs · enrichment.rs · grouping.rs\n│   │   ├── bm25.rs · vector.rs · vector_format.rs · rrf.rs\n│   │   ├── qualified_name.rs · impact_target.rs\n│   ├── prd_input/                 ← stage 4\n│   ├── prd_validator/             ← stage 6\n│   ├── security_gates/            ← stage 8\n│   ├── semantic_diff.rs           ← stage 9\n│   ├── history/ · cochange.rs     ← stage 3e\n│   ├── macro_expansion/ · stdlib_index/ · language_provider/\n│   └── git_diff.rs                ← diff parsing + symbol mapping\n├── stages/                        ← locked spec per stage (Shannon, then engineer implements)\n│   ├── stage-1.md · stage-2.md · stage-3.md · stage-3b.md · stage-3c.md\n│   ├── stage-6.md · stage-8.md\n│   ├── stage-1.review.md · stage-3-db-evaluation.md · stage-3-research.md\n│   └── decisions/                 ← Popper / Lamport / Simon verdicts per decision\n├── tests/\n│   ├── stage3a_integration.rs · stage3b_integration.rs\n│   ├── stage3c_integration.rs · stage3d_integration.rs\n│   ├── stage4_integration.rs · stage6_integration.rs\n│   ├── stage8_integration.rs · stage9_integration.rs\n│   ├── multilang_integration.rs · graph_accuracy.rs\n│   ├── stage3d_hybrid_search.rs\n│   ├── scalability_bench.rs\n│   ├── lbug_bulk_investigation.rs\n│   ├── tfidf_size_report.rs\n│   └── fixtures/multilang/        ← sample.rs · sample.py · sample.ts\n├── scripts/                       ← doc-claim and pin gates, both CI-enforced\n│   ├── check_doc_claims.py · check_marketplace_pins.py\n│   └── tests/\n├── .claude/\n│   ├── agents/                    ← 18 specialists + 97 genius agents\n│   ├── skills/ · commands/ · tools/ · hooks/\n│   └── scripts/\n├── .mcp.json\n├── NOTES.md                       ← stages table + growth rule\n├── Cargo.toml\n└── README.md\n```\n\n---\n\n## The zetetic decisions behind the build\n\nEvery major architectural decision was made by a genius agent with a specific problem shape. Stored in `stages/decisions/*.md` and in Cortex.\n\n| Decision | Agent | Verdict |\n|---|---|---|\n| Rust vs C/C++ for the glue layer | **Popper** | Conjecture \"Rust is the right language\" is unfalsified. `lbug` + `tree-sitter` already run native C/C++; Rust is the glue where the borrow checker pays the most. |\n| Graph-per-finding vs graph-per-codebase | **Lamport** | Per-finding. Isolation holds by construction with zero coordination; the redundant-indexing cost is mitigable in an optional cache layer later. |\n| Stage 3a decomposition | **Simon** | Five steps, satisficed against the growth rule; first useful query at step 4. |\n| DB backend choice | **dba** | LadybugDB (evaluated at `lbug 0.15.3`, now on `0.18`) — only option simultaneously maintained, native Cypher, embedded, with FTS + vector + algo extensions. |\n| Stage 2 clarification loop shape | **Shannon** | Four-tool state machine with atomic single-file session (no crash window between separate files), unconditional one-round-minimum before finalize. |\n| lbug UNWIND pattern | **dba** | `LogicalType::Struct { fields }` works; `LogicalType::Any` fails the binder — 38× speedup verified by compile-and-run probes. |\n\nAgents are spawned via [zetetic-team-subagents](https://github.com/cdeust/zetetic-team-subagents); each genius is a reasoning pattern (not a persona) with canonical moves and primary-source citations.\n\n---\n\n## Status\n\nPublic repo, MIT licensed. Security audit fixes are in, correctness fixes are in, scale fixes are in, stages 4/6/8/9 are live, but every capability marked \"live\" above has been verified end-to-end on this machine, not yet in a production context.\n\n**What works today**: indexing Rust, Python, TypeScript, Java, Kotlin, Swift, Objective-C, C, C++, and Go codebases end-to-end, resolving cross-file relationships, clustering into communities, tracing processes from entry points, hybrid search, PRD input preparation, PRD claim validation, security gate checking, before/after regression detection.\n\n**What's deferred**:\n- Cross-file indexer batching to unlock the full 38× UNWIND win (currently 1.17× aggregate; per-edge rate is already 0.143 ms)\n- `is_unsafe` extraction in the Rust parser (stage 8 S2 runs in `info`-skip mode pending this)\n- LSP-based deep method resolution on inferred types\n- Multi-repo / workgroup operations (GitNexus `group_*`)\n- Rename / refactor tools (we are read-only by design)\n\n---\n\n## Registry\n\nPublished on crates.io as [`ai-architect-mcp-codebase`](https://crates.io/crates/ai-architect-mcp-codebase) and listed in the [MCP Registry](https://registry.modelcontextprotocol.io) under the name below (this line doubles as the registry's package-ownership proof):\n\nmcp-name: io.github.cdeust/ai-architect-mcp-codebase\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n\nThis software is the independent work of Clément Deust. It was developed\noutside any employment relationship and is not affiliated with, endorsed by,\nor owned by any past or present employer. It is part of the ai-architect\necosystem ([Cortex](https://github.com/cdeust/Cortex),\n[zetetic-team-subagents](https://github.com/cdeust/zetetic-team-subagents),\n[AI Architect Spec](https://github.com/cdeust/ai-architect-mcp-spec)).\n\nThe graph-theoretic and information-retrieval algorithms used here (Louvain\ncommunity detection with C2 repair, BM25, RRF rank fusion, tree-sitter AST\nparsing, Tarjan strongly-connected-components) are sourced from published\nresearch; citations are documented inline via `// source:` annotations and in\n`docs/`. The MIT license covers this implementation; it does not assert\nownership over the underlying algorithms, which remain attributable to their\noriginal authors.\n\n---\n\n<p align=\"center\"><sub>Built by <a href=\"https://github.com/cdeust\">cdeust</a>. Every stage designed by a genius agent. Every constant sourced.</sub></p>\n",
  "bytes": 57139,
  "sha": "2963876d05ac56af704bbc8944d94dfbba570746e3867b5d5f37213a4afda8cb",
  "repo_slug": "cdeust/ai-architect-mcp-codebase",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_cdeust_ai_architect_mcp_codeba_3cefe9e3/readme"
}