Projects · code intelligence

A graph instead of a guess: AI Architect Codebase, traced through its source

An agent asked who calls this function has two options: grep and hope, or read a graph. This server builds the graph. It parses a repository with tree-sitter, resolves relationships across files, and answers structural questions from an in-process property store, without ever writing code, opening a pull request or calling a language model.231 It is version 0.11.1, MIT-licensed, and pinned to one Rust toolchain.1 What follows is its architecture, the constants it computes with, and the places where its own documentation and its own code disagree.

Runsone process, one thread, a blocking loop over standard input; no network port and no async runtime34
Parseseleven languages through tree-sitter, ten of them with a deep specification1516
Storesan in-process graph of 24 node labels and 98 relationship tables, plus a search index and four sidecars1719
Exposes26 tools under the full profile, 8 under core, resolved once at startup897
Measured14.26× fewer tokens and 5.20× fewer tool calls than grep-and-read, over 20 pre-registered questions36
Its own verdictthe project's evaluation harness last scored 0.694 against a target of 0.85, and returned NOT PRODUCTION-GRADE34
Exhibit 01 · the process

One process, one loop, and two different products

There is no daemon, no port and no SDK. The host writes one JSON line to standard input, a single thread dispatches it against a short list of implemented methods, and the answer goes back on standard output.345 The protocol layer is hand-written on purpose, and the file says why: so that the agents know exactly what is happening.3 Two endpoints answer politely for capabilities the server never declares, because some clients probe them on connect and read the resulting error as a broken connection.6

The more consequential decision is the tool profile. Resolved once at startup, it decides whether the host sees twenty-six tools or eight.789 The same binary is therefore two different products depending on who launched it, and the launchers disagree: Codex and Gemini are started with the core profile, Claude Code and the desktop bundle pass no flag at all and get everything, while all three bundled skills tell the agent to use the eight-tool profile.12

MCP host Claude Code · Codex Gemini · desktop bundle one JSON line per call ONE PROCESS, ONE THREAD stdio JSON-RPC 2.0 hand-rolled: no MCP SDK, no network port, no async runtime, no LLM call initialize · tools/list · tools/call prompts/list · prompts/get resources/* answer empty on purpose PROFILE, RESOLVED ONCE --profile then AP_PROFILE, then full the flag wins full 26 tools the default core 8 tools 18 hidden WHO GETS WHICH Claude Code · no flag → 26 desktop bundle · no flag → 26 Codex · core → 8 Gemini · core → 8 All three skills tell the agent to use the eight-tool core profile. stdin stdout TWO DECISIONS WORTH KNOWING A tool hidden by the active profile returns exactly the envelope of a tool that does not exist, because the caller must not be able to tell the two apart. The tool count reported by the health check is derived from the registry rather than written down, after a bug where a hardcoded count silently lied as soon as a tool was added. The resource endpoints answer with empty lists although the capability is not declared, because some clients probe them on connect.
A hidden tool and a missing tool answer identically. That is written down as a requirement rather than an accident: the caller must not be able to tell them apart.10 The health check's tool count is derived from the registry rather than typed in, after a bug where a hardcoded number silently lied as soon as a tool was added.11 Both are small decisions, and both are the kind that only get made when someone has been bitten.
Exhibit 02 · the pipeline

From a source tree to something you can query

The walk is bounded before anything is parsed: a hundred thousand files, ten mebibytes each, two gibibytes in total, sixty-four levels deep, five seconds of parsing per file.1314 Those are not round numbers picked for comfort; the first one records the trees it was sized against.13 Eleven languages are recognised, ten with a deep specification and Ruby with a shallow one, and the ambiguous extensions are resolved by a fixed table so that a mis-detection is observable rather than silent.1516

What comes out is a property graph of twenty-four node labels and ninety-eight relationship tables, written as a directory rather than a file, with a search index and four sidecars beside it.1719 Search fuses a lexical and a vector ranking by reciprocal rank at K = 60.20 Communities come from Louvain, followed by a repair pass, because phase one alone leaves the graph over-fragmented — and the file records the measurement that showed it.21 Impact walks the graph to depth twenty.22

1 · READ, PARSE, STORE Bounded walk MAX_FILES 100 000 10 MiB per file, 2 GiB total MAX_DEPTH 64 sized on the Linux and Chromium trees tree-sitter 11 languages 10 with a deep spec; Ruby is served by a shallow one 5 s parse timeout per file LadybugDB graph 24 node labels 98 relationship tables in-process, a directory and not a single file Written beside it search_index/ · bm25 + vector meta.json · file_manifest.json index_coverage.json · cochange.json plus a shareable graph.zst 2 · ANSWER Hybrid search RRF, K = 60 overfetch × 3 BM25 and vector, fused by rank Communities Louvain, gamma 1.0 phase one alone over-fragments, so a repair pass splits by component Processes and impact MAX_BFS_DEPTH 20 entry rules carry a confidence, Go main scoring 0.9 and not 1.0 query_graph · READ-ONLY GATE 14 blacklisted keywords, 2 procedures admitted, LIMIT 500 injected, 30 s timeout, literals masked first WHY THE GATE IS LEXICAL AND NOT LEFT TO THE ENGINE Measured against the engine on 2026-08-24: both of its own gates classify a statement that copies a table to a file, and one that exports the whole database, as read-only, while refusing to create a table. A re-audit the next day added two more keywords, and the note records how the gap was found: by re-deriving the list from the engine headers rather than from the comment's own prose. Literals and comments are masked before the scan, after a query filtering on the word “load” was refused six times in one bench.
The read-only gate is lexical because the engine's own gate is not enough. Measured against the store on 2026-08-24, both of its internal gates classify a statement that copies a table to a file, and one that exports the whole database, as read-only.24 So this server keeps its own blacklist of fourteen keywords and admits exactly two procedures.23 Literals and comments are masked before the scan, after a legitimate query filtering on the word “load” was refused six times in a single bench, and an unterminated literal fails closed.25 A query with no limit gets one.26
Exhibit 03 · the caveats

What each answer admits about itself

Most tools answer a question. This one also answers how much of the question it could actually settle. A completeness marker reading exact or lower-bound rides on four answers.27 A freshness block saying whether the graph still matches your working tree rides on exactly three tools, and no more.28

The interesting part is how that block is attached. An earlier revision set it at each named return, which covered the exits its author could see and silently missed every early failure that propagated before them; it is now attached at the single exit, and the file explains that history rather than hiding it.28 The same instinct runs through the coverage report, which carries a caveat stating it is a best-effort signal and not a guarantee,29 and through the indexer, where a file whose parser panics is quarantined and counted rather than dropped.30

ON FOUR ANSWERS epistemic "exact" | "lower-bound" a count that could not be completed says so, rather ON THREE TOOLS ONLY graph_freshness fresh | stale | unknown get_symbol, search_codebase and get_impact. Not the others. ON EVERY COVERAGE FIGURE The caveat travels “best-effort signal, NOT a completeness guarantee”, with the advice to grep there instead ON WHAT WAS NOT READ Nothing is dropped silently a parser panic quarantines one file an unreadable directory is recorded your own exclusions are counted attached at the single exit, because an earlier revision missed every early failure WHAT IT STATES IT DOES NOT DO It never writes code, opens a pull request or runs your CI, and no tool in the registry modifies a source file. It calls no language model from inside any tool: the intelligence is the agent's job, the tool's job is moving data with invariants. It publishes no energy or CO₂ figure, because the repository measures no joules and a token proxy is not a watt-hour. It has no formal verification, no adversarial review and no second reviewer: there is one maintainer, and the assurance case says so.
The non-goals are as explicit as the features. It never writes code and no tool in the registry can.2 It calls no language model from inside a tool.31 It publishes no energy or carbon figure, on the stated grounds that the repository measures no joules and a token proxy is not a watt-hour.32 And the assurance case says plainly that with one maintainer, no change is reviewed by a second person.33
Exhibit 04 · the numbers

Measured, chosen, or merely claimed

The repository sets itself a rule: a named constant records its source or its measured rationale, and where a value was chosen by judgment the comment says so.44 It largely keeps it. The change-risk score is labelled in its own file as heuristic and not paper-backed, with arbitrary weights.43 The response budget is the opposite case, derived step by step from the host's own cap, extracted from the binary and checked against a real rejected response.42

The measured column is genuinely measured: a pre-registered head-to-head over twenty questions, an incremental index timed against a full one, a coverage figure with its tool and its date.363738 The head-to-head even states its own weakness: the corpus informed the fixes it measures, so it is a regression benchmark and not a generalisation test.36 The claimed column is where a reader should slow down. “1500+ tests” appears on the badge and three more times; counting test attributes in the tree gives 1 127, and the true figure cannot be settled without running the suite.39 The 91 % coverage badge has no committed report behind it.40 A 38× speedup sits in the same document as a measured 76×, unreconciled.41 And the graph-schema line in the README undercounts the code by a factor of one and a half to nearly three.18

MEASURED · PROTOCOL AND DATE 14.26× fewer tokens than grep and read σ 11.28, n = 20, pre-registered 2026-07-26 5.20× fewer tool calls, σ 1.64 incremental index 265 ms vs 2 920 ms 260 files, min of 5 trials, arm64 macOS bulk edge writes 76× faster, re-measured 2026-07-28, 199 edges per strategy line coverage 81.59 %, 947 tests cargo-llvm-cov 0.8.7, 2026-07-27 CHOSEN · A NUMBER SOMEONE PICKED RRF K = 60 · overfetch × 3 Louvain gamma 1.0 · BFS depth 20 graph cache 8 · row limit 500 response budget 100 000 chars The risk score is labelled in its own file: “heuristic, NOT paper-backed”, “the weights are arbitrary engineering judgment”. The repository's rule is that a constant records its source, and where it was judged, the comment says so. CLAIMED · NO PROTOCOL FOUND “1500+ tests” — 1 127 test attributes are counted in the tree at this revision “Coverage 91%” — no report is committed, so it cannot be reproduced from the repo “16 node labels, 36+ relationship tables” — the code declares 24 and 98 “38× speedup” — printed beside the measured 76×, never reconciled “<10 ms startup” — no measurement cited THE PROJECT'S OWN HARNESS, RUN 2026-08-08 Aggregate score 0.694 against a target of 0.85 and a per-language floor of 0.75, over two corpora: TypeScript 0.595, Rust 0.793. Verdict, in the run file: NOT PRODUCTION-GRADE. The weakest questions are “what classes implement interface X” at 0.250 and “what does X call” at 0.451. A second run is committed beside it and agrees to the third decimal. The falsified head-to-head runs are kept rather than deleted, and the README says the corpus informed the fixes: a regression benchmark, not a generalisation test.
Three registers, kept apart on purpose. A number with a protocol and a date, a number someone chose, and a number that appears in prose with neither. Most projects publish the three mixed together; this dossier separates them because the separation is the only thing that makes the first column worth anything.44

The verdict the project gives itself

The repository ships its own evaluation harness, and keeps its runs. The most recent one, dated 2026-08-08, scores 0.694 against a target of 0.85 and a per-language floor of 0.75, and prints the verdict NOT PRODUCTION-GRADE.34 The weakest questions are the ones a code-intelligence tool exists to answer: which classes implement an interface, at 0.250, and what a function calls, at 0.451. A second run is committed beside it and agrees, to the third decimal.35 None of this appears in the README. It is in the repository because the runs were kept rather than deleted, which is the reason this paragraph can be written at all.

1Cargo.toml:21:21 — version 0.11.1; MIT at :24, and a toolchain pinned to 1.95.0 in rust-toolchain.toml
2README.md:19:19-21 — “Stop your coding agent from guessing at your codebase… Runs entirely on your machine. Read-only: it never writes code, opens PRs, or runs CI.”
3src/main.rs:1:1-4 — “Transport: stdio JSON-RPC 2.0, hand-rolled (no MCP SDK — we own the protocol wire layer so the agents know exactly what's happening).”
4src/main.rs:367:367-385 — one locked stdin, one JSON line per request, synchronous dispatch; a blank line is ignored and a parse error is logged to stderr without a reply
5src/main.rs:295:295-330 — the implemented JSON-RPC methods; everything else answers -32601
6src/main.rs:323:323-326 — the resource endpoints answer with empty lists although the capability is not declared, because some clients probe them on connect and surface the error as a failed connection
7src/tool_profile.rs:66:66-74 — the flag wins over the environment variable, which wins over a default of full
8src/tool_profile.rs:132:132-159 — the twenty-six names of the full profile, as a fixed-size array
9src/tool_profile.rs:30:30-39 — the eight names of the core profile; :24-29 calls the eighteen it hides internal pipeline plumbing
10src/main.rs:197:197-206 — a tool hidden by the profile returns the envelope of a tool that does not exist: “One shape for both, because the caller must not be able to tell them apart”
11src/main.rs:231:231-251 — the tool count is derived from the registry, after a hardcoded count “silently lied if a new tool was added without bumping it”
12plugins/ai-architect-mcp-codebase/.mcp.json:5:4-5 — Codex is launched with the core profile, as is Gemini; the Claude Code and desktop launchers pass no flag and therefore serve all twenty-six
13src/indexer/mod.rs:44:44-58 — 100 000 files, 10 MiB per file, 2 GiB in total, depth 64; :41-43 sizes the first bound against the Linux and Chromium trees
14src/parser/mod.rs:19:19 — five seconds per file, and :248 keeps at most 64 error ranges
15src/parser/language.rs:9:9-23 — eleven language variants; :28-64 fixes the ambiguous extensions so that “a mis-detection is observable rather than silent”
16src/parser/spec/registry.rs:144:144 — Ruby is the only language served by a shallow specification; the other ten have a deep one at :30-46
17src/graph_store/schema.rs:98:98-125 — twenty-four node labels; :132-342 — ninety-eight relationship tables, named one by one because the engine's Rust crate has no table groups
18README.md:463:463 announces “16 node labels, 36+ relationship tables”. The code declares 24 and 98
19src/query_handlers/graph_paths.rs:151:151 — meta.json is written atomically beside the graph, with a manifest, a coverage file and a co-change file as further sidecars
20src/search/rrf.rs:14:14 — the rank-fusion constant K = 60, with the reciprocal formula at :37
21src/clustering/community_louvain.rs:14:14-16 — phase one alone leaves 1 262 communities for 2 567 symbols on the project's own corpus, which is why a repair pass splits disconnected communities; :17 is the resolution
22src/clustering/process.rs:265:265 — process tracing stops at depth 20; :47-80 gives each entry rule a confidence, and Go's Main is 0.9 rather than 1.0
23src/query_handlers/read_only_gate.rs:50:50-53 — fourteen blacklisted keywords; :70 admits exactly two procedures out of the twenty-six the engine declares
24src/query_handlers/read_only_gate.rs:25:25-34 — measured 2026-08-24 against the engine: both of its own gates let a copy-to-file and an export-database statement through as read-only while refusing to create a table
25src/query_handlers/read_only_gate.rs:93:93-111 — literals and comments are masked before the scan, after a query filtering on the word “load” was refused six times in one ingestion bench; an unterminated literal fails closed
26src/query_handlers.rs:287:287 — a row limit of 500 is injected when the query carries none, and the read timeout is 30 s
27src/epistemic.rs:33:33-52 — a completeness marker reading exact or lower-bound, emitted on four answers
28src/graph_freshness.rs:105:105 — the freshness block, attached to the single exit of three tools; :140-141 records why, an earlier revision attached it at each named return and “silently missed every ?-propagated failure before them”
29src/indexing_handlers.rs:308:308-313 — “Best-effort signal, NOT a completeness guarantee… ‘skipped’/‘quarantined’ files are NOT in the graph at all.”
30src/indexer/coverage.rs:50:50-51 — a file whose parser panics is quarantined rather than allowed to kill the index
31README.md:478:478 — “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.”
32README.md:50:50 — it publishes “no energy or CO₂ figure, because this repository measures no joules and a token proxy is not a watt-hour”
33docs/ASSURANCE-CASE.md:222:222-223 — “No multi-party review. With one maintainer, no change is reviewed by a second person”
34benches/runs/1786184136.md:5:3-5 and :11-12 — run of 2026-08-08: verdict NOT PRODUCTION-GRADE, aggregate 0.694 against a target of 0.85 and a per-language floor of 0.75, TypeScript 0.595 and Rust 0.793; :21-29 gives the weakest questions
35benches/runs/1786184112.md:5:5 — the second committed run returns the same verdict and the same aggregate, differing only in indexing time. These two are the only runs kept in the tree at this revision
36benchmarks/eval_headtohead/results.json:47:47-54 — 14.26× fewer tokens (σ 11.28) and 5.20× fewer tool calls (σ 1.64) over 20 questions, pre-registered 2026-07-26 and reproducible from the repository. README.md:790-795 adds the caveat that the corpus informed the fixes, so this is a regression benchmark rather than a generalisation test
37benchmarks/incremental_speed/results.json:3:3-15 — an incremental index at 265 ms against 2 920 ms for a full one, on a 260-file fixture, minimum of five trials
38docs/ASSURANCE-CASE.md:218:218-219 — line coverage 81.59 % over 947 passing tests, measured 2026-07-27
39README.md:52:52 — “1500+ tests”. Counting test attributes in the tree at this revision gives 1 127; the exact figure cannot be settled without running the suite
40README.md:12:12 — a coverage badge reading 91 %. No coverage report is committed, so the number cannot be reproduced from the repository alone
41README.md:1007:1007 and :1020 — a 38× speedup, printed in the same document as the measured 76× at :747-761 and never reconciled with it
42src/response_budget.rs:66:66 — a 100 000 character response budget; :8-19 derives it from the host's own cap, extracted from the binary on 2026-06-10 and checked against a 324 429-character response the host rejected
43src/git_diff.rs:265:265-266 and :277 — the change risk score is labelled “heuristic, NOT paper-backed” and “the weights are arbitrary engineering judgment”
44README.md:661:661-662 — the repository's own rule: “Named constants should record their source or measured rationale… No invented numbers.”