Hoppa till innehåll

Kodintelligens

Control Center ger en agent fyra sätt att förstå kod utöver att bara läsa den. De svarar på genuint olika frågor, och anledningen till att det är fyra i stället för ett är att ingen enskild mekanism svarar på alla.

Mekanism Svarar på Kostar
Kodgraf Vad som finns här och vad som beror på det En indexeringsomgång; billig att fråga
Språkserver Om det här faktiskt kompilerar, och vad den här typen är En serverprocess per projekt
Strukturell sökning Var det här MÖNSTRET förekommer En parsning per fil
Felsökare Vad som var sant i detta ögonblick, i denna stackram En stoppad process

Kodgrafen byggs från tree-sitter: symboler, anropskanter, en påverkansradie. Den är snabb, den är billig, och den vet ingenting om typer. Den kan inte säga dig om anropet du just skrev kompilerar, om fältet finns på objektet, eller om namnändringen missade en återexport.

Utan en språkserver får en agent reda på det vid testtillfället – eller aldrig. Den skriver något som verkar rimligt, går vidare, och felet dyker upp tre turer senare kopplat till en annan ändring.

Så vinsten är egentligen inte verktyget lsp. Den är att write, edit och apply_patch är omslutna: efter varje skrivning går filen genom språkservern och nyintroducerad diagnostik vävs in i verktygets resultat. Agenten får veta att den bröt bygget i samma stund som den bröt det, oavsett om den kom på att fråga.

Att upprepa det agenten redan har fått veta är sämre än att inget säga: det fyller resultatet med brus som agenten lär sig att skumma förbi, och det enda nya felet försvinner med det. En liggare per körning håller reda på vad som har rapporterats, nycklad på allvarlighetsgrad, kod och meddelande – inte position, så en diagnostik som bara flyttade ned fyra rader rapporteras inte om som ny.

Ingen server startar vid uppstart. En startar vid den första begäran som behöver den, vilket nästan alltid är den första redigeringen av en fil den gör anspråk på, och inaktiva servrar sveps bort. En värd som aldrig rör Dart startar aldrig en analyzer.

Detektering är en skärning: en rotmarkör måste finnas i utcheckningen och binärfilen måste kunna slås upp. En markör ensam säger “det här projektet är Python” och ingenting om huruvida servern är installerad; en binärfil som kan slås upp säger ensam att värden har en verktygskedja och ingenting om just den här utcheckningen. Först båda tillsammans betyder att en fråga kan lyckas, och ett verktyg som erbjuds men alltid misslyckas är sämre än ett som inte finns.

grep matchar tecken. Det är rätt verktyg för ett namn och fel verktyg för en form.

Ett strukturellt mönster parsas med samma grammatik som filen, så dispose($X) hittar anropet hur det än är blankstegsindelat, omslutet eller radbrytet, och matchar aldrig samma tecken inne i en strängliteral eller en kommentar.

Egenskapen som gör det mer än ett regex med extra steg är att en metavariabel som upprepas i ett mönster måste fånga samma text:

if ($X != null) $X.dispose()

matchar ett skydd som frigör det det testade, och matchar inte ett som frigör något annat. Det är en felklass som du inte kan uttrycka textuellt alls.

Det finns ingen andra motor. cc_natives laddar redan tree-sitter och dess grammatiker för kodgrafen, med en cache för kompilerade frågor och en språkstabell. Matcharen är ren Dart över träd som ändå parsades. En andra strukturell motor skulle innebära en andra parser, en andra uppsättning grammatiker att bygga och paketera på varje plattform, och två svar på “vad är ett anropsuttryck”.

En grammatik definierar bara vad en hel fil är. Parsat bart på toppnivå i Dart är dispose(x) en fullt giltig funktionssignatur – en deklaration med namnet dispose som tar en parameter x – utan något fel någonstans som avslöjar det. En matchare byggd på den parsningen letar efter deklarationer, hittar inga anrop och rapporterar “inga träffar” för ett mönster som aldrig lästes så som det ser ut.

Så ett mönster provas i en stege av skal – sats, uttryck, klassmedlem, toppnivå – och det första som accepterar det vinner. Ett fragment som ingen kontext accepterar rapporteras som oparsbart i stället för att sökas efter och tyst inte hittas.

Varför en omskrivning mellanlagras i stället för att skrivas

Avsnitt med rubriken “Varför en omskrivning mellanlagras i stället för att skrivas”

En strukturell omskrivning är den vidaste redigeringen en agent kan göra: ett mönster, fyrtio filer, och ingen läser resultatet rad för rad – varken modellen eller personen som bad om det. Att skriva den och rapportera “klart” innebär att den första ärliga titten på ändringen är en git diff efteråt.

Så ast_edit rapporterar vad den skulle göra – antalet, filerna, de matchade ställena – och ändringen genomförs först när resolve committar den. Det är också det som gör att en diff och ett Acceptera/Kasta kan ligga framför en människa innan något når disken.

Commiten är allt-eller-inget och varje fil kontrolleras mot innehållet som fångades vid mellanlagringen. Mellan mellanlagringen och commiten kan agenten ha handredigerat en av filerna, en diagnostikkörning kan ha skrivit om den, eller en formatterare kan ha rört den – att committa ändå skulle kasta det arbetet och diffen skulle se avsiktlig ut. Varje avvikelse avvisar hela ändringen, eftersom en delvis tillämpad strukturell omskrivning lämnar ett träd som inte kompilerar i någon av formerna.

Vad en felsökare kan svara på som en print-sats inte kan

Avsnitt med rubriken “Vad en felsökare kan svara på som en print-sats inte kan”

Loopen med print-satser är: lägg till en print, kör om allt, läs utskriften, ta bort printen. Den kostar en hel testkörning per fråga, och den svarar bara på frågan du kom på att ställa innan du körde.

En stoppad stackram svarar på varje fråga om det ögonblicket på en gång – varje lokal, varje anropare och ett uttryck som utvärderas i rammens eget omfång. För en process som har hakat upp sig eller ett fel som reproduceras en gång på tio körningar är det skillnaden mellan en eftermiddag och en minut.

En felsökningsadapter äger en stoppad process som håller vad den processen än håller – en port, ett lås, en databasanslutning. En session som ingen kör är en läcka som överlever turen som startade den. Därför finns det en hård TTL, exakt en session per samtal, och en andra launch avvisas i stället för att tyst ersätta den första: en agent som startar en andra utan att avsluta den första har nästan säkert tappat bort den första.

Debug Adapter Protocol använder samma Content-Length-inramning som Language Server Protocol, och ingenting ovanför den – olika korrelationsnycklar, en annan felform och en tredje meddelandetyp (händelser) utan LSP-motsvarighet. Inramningen bor på ett ställe; protokollen vilar på den var för sig.

Att skriva inramningen två gånger skulle innebära två svar på “ett meddelande sträcker sig över två delar”, “en del innehåller tre meddelanden” och “servern skrev en banner till stdout före sin första ram”, och bara ett av dem skulle få rättningen.