Intelligence du code
Control Center donne à un agent quatre façons de comprendre le code au-delà de la simple lecture. Elles répondent à des questions réellement différentes, et s’il y en a quatre plutôt qu’une, c’est qu’aucun mécanisme pris isolément ne répond à toutes.
| Mécanisme | Répond à | Coût |
|---|---|---|
| Graphe de code | Ce qui se trouve ici, et ce qui en dépend | Une passe d’indexation ; des requêtes peu coûteuses |
| Serveur de langage | Est-ce que ceci compile vraiment, et quel est ce type | Un processus de serveur par projet |
| Recherche structurelle | Où cette FORME apparaît-elle | Une analyse syntaxique par fichier |
| Débogueur | Ce qui était vrai à cet instant, dans ce cadre | Un processus arrêté |
La lacune que comble le serveur de langage
Section intitulée « La lacune que comble le serveur de langage »Le graphe de code est construit à partir de tree-sitter : des symboles, des arêtes d’appel, un rayon d’impact. Il est rapide, il est peu coûteux, et il ne sait rien des types. Il ne peut pas vous dire si l’appel que vous venez d’écrire compile, si ce champ existe sur l’objet, ni si votre renommage a manqué un ré-export.
Sans serveur de langage, un agent l’apprend au moment des tests — ou jamais. Il écrit quelque chose de plausible, passe à la suite, et l’échec refait surface trois tours plus tard, rattaché à une autre modification.
Le gain ne vient donc pas vraiment de l’outil lsp. Il vient de ce que write, edit et apply_patch sont enveloppés : après chaque écriture, le fichier passe par le serveur de langage et les diagnostics nouvellement introduits sont intégrés au résultat de l’outil. L’agent apprend qu’il a cassé la compilation au moment même où il l’a cassée, qu’il ait pensé à poser la question ou non.
Uniquement les nouveaux diagnostics
Section intitulée « Uniquement les nouveaux diagnostics »Répéter à l’agent ce qu’il a déjà lu est pire que de ne rien dire : cela remplit le résultat d’un bruit qu’il apprend à survoler, et la seule erreur réellement nouvelle disparaît avec le reste. Un registre par exécution garde la trace de ce qui a été signalé, indexé sur la gravité, le code et le message — pas sur la position, si bien qu’un diagnostic qui a simplement descendu de quatre lignes n’est pas signalé de nouveau comme nouveau.
Paresseux, et honnête sur son absence
Section intitulée « Paresseux, et honnête sur son absence »Aucun serveur ne démarre au lancement. L’un d’eux démarre à la première requête qui en a besoin — presque toujours la première modification d’un fichier dont il a la charge — et les serveurs inactifs sont balayés. Un hôte qui ne touche jamais à Dart ne démarre jamais d’analyseur.
La détection est une intersection : un marqueur racine doit exister dans la copie de travail et le binaire doit être résolvable. Un marqueur seul dit « ce projet est du Python », et rien sur le fait que le serveur soit installé ; un binaire résolvable seul dit que l’hôte dispose d’une chaîne d’outils, et rien sur cette copie de travail. Seuls les deux réunis signifient qu’une requête peut aboutir, et un outil proposé qui échoue systématiquement est pire qu’un outil absent.
Pourquoi la recherche structurelle, alors que grep existe
Section intitulée « Pourquoi la recherche structurelle, alors que grep existe »grep fait correspondre des caractères. C’est le bon outil pour un nom, et le mauvais pour une forme.
Un motif structurel est analysé par la même grammaire que le fichier, si bien que dispose($X) trouve l’appel quelle que soit la façon dont il est espacé, découpé ou réparti sur plusieurs lignes, et ne correspond jamais aux mêmes caractères à l’intérieur d’un littéral de chaîne ou d’un commentaire.
Ce qui en fait plus qu’une expression régulière avec des étapes en plus, c’est qu’une métavariable répétée dans un même motif doit capturer le même texte :
if ($X != null) $X.dispose()correspond à une garde qui libère ce qu’elle a testé, et ne correspond pas à une garde qui libère autre chose. C’est une classe de bugs qu’aucune expression textuelle ne permet d’exprimer.
Il tourne sur le tree-sitter déjà lié
Section intitulée « Il tourne sur le tree-sitter déjà lié »Il n’y a pas de second moteur. cc_natives charge déjà tree-sitter et ses grammaires pour le graphe de code, avec un cache de requêtes compilées et une table de langages. Le moteur de correspondance est du Dart pur, sur des arbres qui étaient de toute façon analysés. Un second moteur structurel impliquerait un second analyseur, un second jeu de grammaires à compiler et à déployer sur chaque plateforme, et deux réponses à la question « qu’est-ce qu’une expression d’appel ».
Un fragment a besoin d’un endroit où vivre
Section intitulée « Un fragment a besoin d’un endroit où vivre »Une grammaire ne définit que ce qu’est un fichier entier. Analysé tel quel au niveau supérieur de Dart, dispose(x) est une signature de fonction parfaitement valide — une déclaration nommée dispose prenant un paramètre x — sans la moindre erreur pour le trahir. Un moteur de correspondance bâti sur cette analyse cherche des déclarations, ne trouve aucun appel et annonce « aucun résultat » pour un motif qui n’a jamais été lu comme il en a l’air.
Ainsi, un motif est essayé dans une échelle d’échafaudages — instruction, expression, membre de classe, niveau supérieur — et le premier qui l’accepte l’emporte. Un fragment qu’aucun contexte n’accepte est signalé comme non analysable, plutôt que d’être recherché et silencieusement introuvable.
Pourquoi une réécriture passe par une préparation plutôt que d’écrire
Section intitulée « Pourquoi une réécriture passe par une préparation plutôt que d’écrire »Une réécriture structurelle est la modification la plus large qu’un agent puisse produire : un motif, quarante fichiers, et personne ne lit le résultat ligne par ligne — ni le modèle, ni la personne qui l’a demandée. L’écrire puis annoncer « terminé » signifie que le premier regard honnête sur la modification est un git diff après coup.
Ainsi, ast_edit annonce ce qu’il ferait — le nombre, les fichiers, les emplacements correspondants — et la modification n’est appliquée que lorsque resolve la valide. C’est aussi ce qui permet de présenter un diff et un choix Accepter/Abandonner à un humain avant que quoi que ce soit n’atteigne le disque.
La validation est tout ou rien, et chaque fichier est comparé au contenu capturé au moment de la préparation. Entre la préparation et la validation, l’agent a pu modifier l’un de ces fichiers à la main, une passe de diagnostics a pu le réécrire, ou un formateur a pu y toucher : valider malgré tout effacerait ce travail, et le diff paraîtrait intentionnel. La moindre divergence fait échouer la modification dans son ensemble, car une réécriture structurelle partiellement appliquée laisse un arbre qui ne compile sous aucune des deux formes.
Ce à quoi un débogueur répond, et qu’une instruction print ne peut pas
Section intitulée « Ce à quoi un débogueur répond, et qu’une instruction print ne peut pas »La boucle de l’instruction print est toujours la même : ajouter un print, tout relancer, lire la sortie, supprimer le print. Elle coûte une exécution complète des tests par question, et ne répond qu’à la question que vous avez pensé à poser avant de lancer.
Un cadre arrêté répond d’un coup à toutes les questions sur cet instant — chaque variable locale, chaque appelant, et une expression évaluée dans la portée propre du cadre. Pour un processus bloqué ou une défaillance qui ne se reproduit qu’une fois sur dix, c’est la différence entre un après-midi et une minute.
Borné comme une enceinte, et pour la même raison
Section intitulée « Borné comme une enceinte, et pour la même raison »Un adaptateur de débogage possède un processus arrêté qui retient tout ce que ce processus retient — un port, un verrou, une connexion à une base de données. Une session que personne ne pilote est une fuite qui survit au tour qui l’a lancée. Il existe donc une durée de vie stricte, exactement une session par conversation, et un second launch est refusé plutôt que de remplacer silencieusement le premier : un agent qui en démarre un second sans terminer le premier a presque certainement perdu la trace du premier.
Un transport, deux protocoles
Section intitulée « Un transport, deux protocoles »Le Debug Adapter Protocol partage le cadrage Content-Length du Language Server Protocol, et rien au-dessus — des clés de corrélation différentes, une forme d’échec différente, et un troisième type de message (les événements) sans équivalent dans LSP. Le cadrage vit à un seul endroit ; les protocoles s’y appuient séparément.
Écrire le cadrage deux fois signifierait deux réponses à « un message s’étale sur deux segments », « un segment contient trois messages » et « le serveur a écrit une bannière sur stdout avant sa première trame », et une seule de ces réponses recevrait le correctif.
Voir aussi
Section intitulée « Voir aussi »- Rechercher du code avec le graphe de code — le volet symboles et impact
- Déboguer un test en échec — le débogueur, de bout en bout
- Refactoriser structurellement —
ast_grepetast_edit - Outils d’agent intégrés — chaque opération et chaque argument