Projets · intelligence de code

Un graphe plutôt qu’une supposition : AI Architect Codebase, suivi dans son code

Un agent à qui l’on demande qui appelle cette fonction a deux options : grep et espérer, ou lire un graphe. Ce serveur construit le graphe. Il analyse un dépôt avec tree-sitter, résout les relations entre fichiers, et répond aux questions de structure depuis un store de propriétés en processus, sans jamais écrire de code, ouvrir une pull request ni appeler un modèle de langage.231 Il est en version 0.11.1, sous licence MIT, épinglé à une chaîne d’outils Rust.1 Ce qui suit est son architecture, les constantes avec lesquelles il calcule, et les endroits où sa propre documentation et son propre code se contredisent.

Tourneun processus, un fil, une boucle bloquante sur l’entrée standard ; ni port réseau ni runtime asynchrone34
Analyseonze langages via tree-sitter, dix avec une spécification profonde1516
Stockeun graphe en processus de 24 étiquettes de nœuds et 98 tables de relations, plus un index de recherche et quatre annexes1719
Expose26 outils en profil full, 8 en core, résolus une fois au démarrage897
Mesuré14,26× moins de tokens et 5,20× moins d’appels d’outils que grep-et-read, sur 20 questions pré-enregistrées36
Son propre verdictle harnais d’évaluation du projet a rendu 0,694 contre une cible de 0,85, et le verdict NOT PRODUCTION-GRADE34
Pièce 01 · le processus

Un processus, une boucle, et deux produits différents

Il n’y a ni démon, ni port, ni SDK. L’hôte écrit une ligne JSON sur l’entrée standard, un seul fil la dispatche contre une courte liste de méthodes implémentées, et la réponse repart sur la sortie standard.345 La couche protocole est écrite à la main à dessein, et le fichier dit pourquoi : pour que les agents sachent exactement ce qui se passe.3 Deux points d’entrée répondent poliment pour des capacités que le serveur ne déclare pas, parce que certains clients les sondent à la connexion et lisent l’erreur comme une connexion cassée.6

La décision la plus lourde de conséquences est le profil d’outils. Résolu une fois au démarrage, il décide si l’hôte voit vingt-six outils ou huit.789 Le même binaire est donc deux produits différents selon qui l’a lancé, et les lanceurs ne s’accordent pas : Codex et Gemini démarrent avec le profil core, Claude Code et le bundle desktop ne passent aucun drapeau et obtiennent tout, tandis que les trois skills livrées disent toutes à l’agent d’utiliser le profil à huit outils.12

Hôte MCP Claude Code · Codex Gemini · bundle desktop une ligne JSON par appel UN PROCESSUS, UN FIL stdio JSON-RPC 2.0 écrit à la main : pas de SDK MCP, pas de port réseau, pas d’async, aucun appel de LLM initialize · tools/list · tools/call prompts/list · prompts/get resources/* répond vide à dessein PROFIL, RÉSOLU UNE FOIS --profile puis AP_PROFILE, puis full le drapeau l’emporte full 26 outils le défaut core 8 outils 18 masqués QUI REÇOIT QUOI Claude Code · sans drapeau → 26 bundle desktop · sans drapeau → 26 Codex · core → 8 Gemini · core → 8 Les trois skills disent toutes à l’agent d’utiliser le profil core, à huit outils. stdin stdout DEUX DÉCISIONS À CONNAÎTRE Un outil masqué par le profil actif rend exactement l’enveloppe d’un outil inexistant, parce que l’appelant ne doit pas pouvoir les distinguer. Le nombre d’outils du contrôle de santé est dérivé du registre plutôt qu’écrit en dur, après un défaut où un compte codé en dur mentait en silence dès qu’un outil était ajouté. Les points d’entrée de ressources répondent par des listes vides bien que la capacité ne soit pas déclarée : des clients les sondent à la connexion.
Un outil masqué et un outil inexistant répondent à l’identique. C’est écrit comme une exigence et non comme un hasard : l’appelant ne doit pas pouvoir les distinguer.10 Le nombre d’outils du contrôle de santé est dérivé du registre plutôt que saisi, après un défaut où un nombre codé en dur mentait en silence dès qu’un outil était ajouté.11 Deux petites décisions, et deux décisions qu’on ne prend qu’après s’être fait mordre.
Pièce 02 · la chaîne

D’un arbre source à quelque chose d’interrogeable

Le parcours est borné avant toute analyse : cent mille fichiers, dix mébioctets chacun, deux gibioctets au total, soixante-quatre niveaux de profondeur, cinq secondes d’analyse par fichier.1314 Ce ne sont pas des chiffres ronds choisis par confort ; le premier consigne les arbres contre lesquels il a été dimensionné.13 Onze langages sont reconnus, dix avec une spécification profonde et Ruby avec une spécification de surface, et les extensions ambiguës sont tranchées par une table fixe pour qu’une erreur de détection soit observable plutôt que silencieuse.1516

Il en sort un graphe de propriétés de vingt-quatre étiquettes de nœuds et quatre-vingt-dix-huit tables de relations, écrit comme un répertoire et non comme un fichier, avec un index de recherche et quatre fichiers annexes à côté.1719 La recherche fusionne un classement lexical et un classement vectoriel par rang réciproque à K = 60.20 Les communautés viennent de Louvain, suivi d’une passe de réparation, parce que la phase un seule laisse le graphe sur-fragmenté — et le fichier consigne la mesure qui l’a montré.21 L’impact parcourt le graphe jusqu’à la profondeur vingt.22

1 · LIRE, ANALYSER, STOCKER Parcours borné MAX_FILES 100 000 10 Mio par fichier, 2 Gio au total MAX_DEPTH 64 dimensionné sur les arbres Linux et Chromium tree-sitter 11 langages 10 avec une spec profonde ; Ruby est servi par une spec de surface délai d’analyse 5 s par fichier Graphe LadybugDB 24 étiquettes de nœuds 98 tables de relations en processus, un répertoire et non un fichier unique Écrit à côté search_index/ · bm25 + vecteur meta.json · file_manifest.json index_coverage.json · cochange.json plus un graph.zst partageable 2 · RÉPONDRE Recherche hybride RRF, K = 60 surextraction × 3 BM25 et vecteur, fusionnés par rang Communities Louvain, gamma 1,0 la phase un seule sur-fragmente, une passe de réparation scinde alors Processus et impact MAX_BFS_DEPTH 20 les règles d’entrée ont une confiance, le main de Go valant 0,9 et non 1,0 query_graph · GARDE LECTURE SEULE 14 mots-clés interdits, 2 procédures admises, LIMIT 500 injecté, délai 30 s, littéraux masqués d’abord POURQUOI LA GARDE EST LEXICALE ET NON LAISSÉE AU MOTEUR Mesuré contre le moteur le 2026-08-24 : ses deux gardes internes classent en lecture seule une instruction qui copie une table vers un fichier, et une autre qui exporte toute la base, tout en refusant de créer une table. Un ré-audit le lendemain a ajouté deux mots-clés, et la note consigne comment l’écart a été trouvé : en redérivant la liste des en-têtes du moteur plutôt que de la prose du commentaire. Littéraux et commentaires sont masqués avant l’analyse, après qu’une requête filtrant sur le mot « load » a été refusée six fois dans un bench.
La garde lecture seule est lexicale parce que celle du moteur ne suffit pas. Mesuré contre le store le 2026-08-24, ses deux gardes internes classent en lecture seule une instruction qui copie une table vers un fichier et une autre qui exporte la base entière.24 Ce serveur tient donc sa propre liste noire de quatorze mots-clés et n’admet que deux procédures.23 Littéraux et commentaires sont masqués avant l’analyse, après qu’une requête légitime filtrant sur le mot « load » a été refusée six fois dans un seul bench, et un littéral non terminé échoue fermé.25 Une requête sans limite en reçoit une.26
Pièce 03 · les réserves

Ce que chaque réponse avoue d’elle-même

La plupart des outils répondent à une question. Celui-ci répond aussi de la part de la question qu’il a réellement pu trancher. Un marqueur de complétude valant exact ou lower-bound accompagne quatre réponses.27 Un bloc de fraîcheur, disant si le graphe correspond encore à votre copie de travail, accompagne exactement trois outils, et pas un de plus.28

L’intéressant est la façon dont ce bloc est attaché. Une révision antérieure le posait à chaque return nommé, ce qui couvrait les sorties que son auteur voyait et ratait en silence tout échec précoce propagé avant elles ; il est désormais attaché à la sortie unique, et le fichier explique cette histoire plutôt que de la masquer.28 Le même instinct traverse le rapport de couverture, qui porte une réserve disant qu’il est un signal au mieux et non une garantie,29 et l’indexeur, où un fichier dont l’analyseur panique est mis en quarantaine et compté plutôt qu’abandonné.30

SUR QUATRE RÉPONSES epistemic "exact" | "lower-bound" un décompte qui n’a pas pu aboutir le dit, plutôt que SUR TROIS OUTILS SEULEMENT graph_freshness fresh | stale | unknown get_symbol, search_codebase et get_impact. Pas les autres. SUR CHAQUE CHIFFRE DE COUVERTURE La réserve voyage avec « best-effort signal, NOT a completeness guarantee », avec le conseil d’y faire un grep SUR CE QUI N’A PAS ÉTÉ LU Rien n’est perdu en silence panique d’analyse → quarantaine un répertoire illisible est consigné vos propres exclusions sont comptées attaché à la sortie unique, parce qu’une révision antérieure ratait tout échec précoce CE QU’IL DÉCLARE NE PAS FAIRE Il n’écrit jamais de code, n’ouvre pas de pull request et ne lance pas votre CI ; aucun outil du registre ne modifie un fichier source. Il n’appelle aucun modèle de langage depuis un outil : l’intelligence est le travail de l’agent, celui de l’outil est de déplacer des données sous invariants. Il ne publie aucun chiffre d’énergie ni de CO₂, parce que le dépôt ne mesure aucun joule et qu’un proxy de tokens n’est pas un wattheure. Il n’a ni vérification formelle, ni revue adverse, ni second relecteur : il y a un seul mainteneur, et le dossier d’assurance le dit.
Les non-objectifs sont aussi explicites que les fonctions. Il n’écrit jamais de code et aucun outil du registre ne le peut.2 Il n’appelle aucun modèle de langage depuis un outil.31 Il ne publie aucun chiffre d’énergie ou de carbone, au motif déclaré que le dépôt ne mesure aucun joule et qu’un proxy de tokens n’est pas un wattheure.32 Et le dossier d’assurance dit franchement qu’avec un seul mainteneur, aucun changement n’est relu par une seconde personne.33
Pièce 04 · les nombres

Mesuré, choisi, ou simplement affirmé

Le dépôt se donne une règle : une constante nommée consigne sa source ou sa justification mesurée, et là où une valeur a été choisie au jugé, le commentaire le dit.44 Il la tient largement. Le score de risque de changement est étiqueté dans son propre fichier comme heuristique et non adossé à un article, avec des poids arbitraires.43 Le budget de réponse est le cas inverse, dérivé pas à pas du plafond de l’hôte, extrait du binaire et vérifié contre une vraie réponse rejetée.42

La colonne mesurée l’est vraiment : un face-à-face pré-enregistré sur vingt questions, un index incrémental chronométré contre un index complet, une couverture avec son outil et sa date.363738 Le face-à-face énonce même sa propre faiblesse : le corpus a informé les correctifs qu’il mesure, c’est donc un bench de régression et non un test de généralisation.36 La colonne affirmée est celle où il faut ralentir. « 1500+ tests » figure sur le badge et trois fois ailleurs ; le comptage des attributs de test dans l’arbre donne 1 127, et le vrai chiffre ne peut pas être tranché sans exécuter la suite.39 Le badge de couverture à 91 % n’a aucun rapport commité derrière lui.40 Une accélération de 38× siège dans le même document qu’un 76× mesuré, sans réconciliation.41 Et la ligne du README sur le schéma du graphe sous-compte le code d’un facteur un et demi à près de trois.18

MESURÉ · PROTOCOLE ET DATE 14,26× moins de tokens que grep et read σ 11,28, n = 20, pré-enregistré 2026-07-26 5,20× moins d’appels d’outils, σ 1,64 index incrémental 265 ms contre 2 920 ms 260 fichiers, min de 5 essais, arm64 macOS écritures d’arêtes en masse 76× plus vite, remesuré 2026-07-28, 199 arêtes par stratégie couverture de lignes 81,59 %, 947 tests cargo-llvm-cov 0.8.7, 2026-07-27 CHOISI · UN NOMBRE QUE QUELQU’UN A PRIS RRF K = 60 · surextraction × 3 Louvain gamma 1,0 · profondeur BFS 20 cache de graphes 8 · limite 500 lignes budget de réponse 100 000 caractères Le score de risque est étiqueté dans son fichier : « heuristic, NOT paper- backed », « the weights are arbitrary engineering judgment ». La règle du dépôt est qu’une constante consigne sa source, et si elle est jugée, le dit. AFFIRMÉ · AUCUN PROTOCOLE TROUVÉ « 1500+ tests » — on compte 1 127 attributs de test dans l’arbre ici « Coverage 91% » — aucun rapport commité, donc irreproductible depuis le dépôt « 16 node labels, 36+ relationship tables » — le code en déclare 24 et 98 « 38× speedup » — imprimé à côté du 76× mesuré, jamais réconcilié « <10 ms startup » — aucune mesure citée LE PROPRE HARNAIS DU PROJET, RUN 2026-08-08 Score agrégé 0,694 contre une cible de 0,85 et un plancher par langage de 0,75, sur deux corpus : TypeScript 0,595, Rust 0,793. Verdict, dans le fichier de run : NOT PRODUCTION-GRADE. Les questions les plus faibles sont « what classes implement interface X » à 0,250 et « what does X call » à 0,451. Un second run est commité à côté et concorde à la troisième décimale. Les runs falsifiés du face-à-face sont gardés plutôt que supprimés, et le README dit que le corpus a informé les correctifs : un bench de régression, pas un test de généralisation.
Trois registres, tenus séparés à dessein. Un nombre avec un protocole et une date, un nombre que quelqu’un a choisi, et un nombre qui apparaît dans la prose sans ni l’un ni l’autre. La plupart des projets publient les trois mélangés ; ce dossier les sépare parce que cette séparation est la seule chose qui donne sa valeur à la première colonne.44

Le verdict que le projet se donne à lui-même

Le dépôt livre son propre harnais d’évaluation, et garde ses runs. Le plus récent, daté du 2026-08-08, donne 0,694 contre une cible de 0,85 et un plancher par langage de 0,75, et imprime le verdict NOT PRODUCTION-GRADE.34 Les questions les plus faibles sont précisément celles pour lesquelles un outil d’intelligence de code existe : quelles classes implémentent une interface, à 0,250, et ce qu’une fonction appelle, à 0,451. Un second run est commité à côté et concorde à la troisième décimale.35 Rien de tout cela n’apparaît dans le README. C’est dans le dépôt parce que les runs ont été gardés plutôt que supprimés, et c’est la raison pour laquelle ce paragraphe peut être écrit.

1Cargo.toml:21 :21 — version 0.11.1 ; MIT en :24, et une chaîne d’outils épinglée à 1.95.0 dans 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 — une entrée standard verrouillée, une ligne JSON par requête, dispatch synchrone ; une ligne vide est ignorée et une erreur d’analyse part sur stderr sans réponse
5src/main.rs:295 :295-330 — les méthodes JSON-RPC implémentées ; tout le reste répond -32601
6src/main.rs:323 :323-326 — les points d’entrée de ressources répondent par des listes vides bien que la capacité ne soit pas déclarée, parce que certains clients les sondent à la connexion et lisent l’erreur comme une connexion échouée
7src/tool_profile.rs:66 :66-74 — le drapeau l’emporte sur la variable d’environnement, qui l’emporte sur un défaut à full
8src/tool_profile.rs:132 :132-159 — les vingt-six noms du profil full, dans un tableau de taille fixe
9src/tool_profile.rs:30 :30-39 — les huit noms du profil core ; :24-29 qualifie les dix-huit masqués de plomberie interne de pipeline
10src/main.rs:197 :197-206 — un outil masqué par le profil rend l’enveloppe d’un outil inexistant : « One shape for both, because the caller must not be able to tell them apart »
11src/main.rs:231 :231-251 — le nombre d’outils est dérivé du registre, après qu’un compte codé en dur « silently lied if a new tool was added without bumping it »
12plugins/ai-architect-mcp-codebase/.mcp.json:5 :4-5 — Codex est lancé avec le profil core, Gemini aussi ; les lanceurs de Claude Code et du bundle desktop ne passent aucun drapeau et servent donc les vingt-six
13src/indexer/mod.rs:44 :44-58 — 100 000 fichiers, 10 Mio par fichier, 2 Gio au total, profondeur 64 ; :41-43 dimensionne la première borne sur les arbres Linux et Chromium
14src/parser/mod.rs:19 :19 — cinq secondes par fichier, et :248 ne garde que 64 plages d’erreur
15src/parser/language.rs:9 :9-23 — onze variantes de langage ; :28-64 fixe les extensions ambiguës pour qu’« a mis-detection is observable rather than silent »
16src/parser/spec/registry.rs:144 :144 — Ruby est le seul langage servi par une spécification de surface ; les dix autres en ont une profonde en :30-46
17src/graph_store/schema.rs:98 :98-125 — vingt-quatre étiquettes de nœuds ; :132-342 — quatre-vingt-dix-huit tables de relations, nommées une à une parce que le crate Rust du moteur n’a pas de groupes de tables
18README.md:463 :463 annonce « 16 node labels, 36+ relationship tables ». Le code en déclare 24 et 98
19src/query_handlers/graph_paths.rs:151 :151 — meta.json est écrit atomiquement à côté du graphe, avec un manifeste, un fichier de couverture et un fichier de co-changement comme autres annexes
20src/search/rrf.rs:14 :14 — la constante de fusion de rangs K = 60, avec la formule réciproque en :37
21src/clustering/community_louvain.rs:14 :14-16 — la phase un seule laisse 1 262 communautés pour 2 567 symboles sur le corpus du projet, d’où une passe de réparation qui scinde les communautés déconnectées ; :17 est la résolution
22src/clustering/process.rs:265 :265 — le traçage de processus s’arrête à la profondeur 20 ; :47-80 donne une confiance à chaque règle d’entrée, et le Main de Go vaut 0,9 et non 1,0
23src/query_handlers/read_only_gate.rs:50 :50-53 — quatorze mots-clés interdits ; :70 n’admet que deux procédures sur les vingt-six que déclare le moteur
24src/query_handlers/read_only_gate.rs:25 :25-34 — mesuré le 2026-08-24 contre le moteur : ses deux gardes internes laissent passer en lecture seule une copie vers fichier et un export de base entière, tout en refusant de créer une table
25src/query_handlers/read_only_gate.rs:93 :93-111 — littéraux et commentaires sont masqués avant l’analyse, après qu’une requête légitime filtrant sur le mot « load » a été refusée six fois dans un seul bench ; un littéral non terminé échoue fermé
26src/query_handlers.rs:287 :287 — une limite de 500 lignes est injectée quand la requête n’en porte pas, et le délai de lecture est de 30 s
27src/epistemic.rs:33 :33-52 — un marqueur de complétude valant exact ou lower-bound, émis sur quatre réponses
28src/graph_freshness.rs:105 :105 — le bloc de fraîcheur, attaché à la sortie unique de trois outils ; :140-141 consigne pourquoi : une révision antérieure l’attachait à chaque return nommé et « 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 — un fichier dont l’analyseur panique est mis en quarantaine plutôt que de tuer l’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 — il ne publie « 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 et :11-12 — run du 2026-08-08 : verdict NOT PRODUCTION-GRADE, agrégat 0,694 contre une cible de 0,85 et un plancher par langage de 0,75, TypeScript 0,595 et Rust 0,793 ; :21-29 donne les questions les plus faibles
35benches/runs/1786184112.md:5 :5 — le second run commité rend le même verdict et le même agrégat, ne différant que par le temps d’indexation. Ce sont les deux seuls runs gardés dans l’arbre à cette révision
36benchmarks/eval_headtohead/results.json:47 :47-54 — 14,26× moins de tokens (σ 11,28) et 5,20× moins d’appels d’outils (σ 1,64) sur 20 questions, pré-enregistré le 2026-07-26 et reproductible depuis le dépôt. README.md:790-795 ajoute la réserve que le corpus a informé les correctifs : c’est donc un bench de régression et non un test de généralisation
37benchmarks/incremental_speed/results.json:3 :3-15 — un index incrémental à 265 ms contre 2 920 ms pour un index complet, sur une fixture de 260 fichiers, minimum de cinq essais
38docs/ASSURANCE-CASE.md:218 :218-219 — couverture de lignes 81,59 % sur 947 tests passants, mesurée le 2026-07-27
39README.md:52 :52 — « 1500+ tests ». Le comptage des attributs de test dans l’arbre à cette révision donne 1 127 ; le chiffre exact ne peut pas être tranché sans exécuter la suite
40README.md:12 :12 — un badge de couverture à 91 %. Aucun rapport de couverture n’est commité, donc le nombre n’est pas reproductible depuis le seul dépôt
41README.md:1007 :1007 et :1020 — une accélération de 38×, imprimée dans le même document que le 76× mesuré en :747-761 et jamais réconciliée avec lui
42src/response_budget.rs:66 :66 — un budget de réponse de 100 000 caractères ; :8-19 le dérive du plafond de l’hôte, extrait du binaire le 2026-06-10 et vérifié contre une réponse de 324 429 caractères que l’hôte a rejetée
43src/git_diff.rs:265 :265-266 et :277 — le score de risque de changement est étiqueté « heuristic, NOT paper-backed » et « the weights are arbitrary engineering judgment »
44README.md:661 :661-662 — la règle que le dépôt se donne : « Named constants should record their source or measured rationale… No invented numbers. »