Inteligencja kodu
Control Center daje agentowi cztery sposoby rozumienia kodu poza jego czytaniem. Odpowiadają one na naprawdę różne pytania, a powód, dla którego są cztery, a nie jeden, jest taki, że żaden pojedynczy mechanizm nie odpowiada na wszystkie.
| Mechanizm | Odpowiada na | Koszty |
|---|---|---|
| Graf kodu | Co tu jest i co od tego zależy | Przebieg indeksowania; tanie zapytania |
| Serwer językowy | Czy to się w ogóle kompiluje i czym jest ten typ | Proces serwera na projekt |
| Wyszukiwanie strukturalne | Gdzie występuje ten KSZTAŁT | Parsowanie na plik |
| Debugger | Co było prawdą w tym momencie, w tej ramce | Zatrzymany proces |
Luka, którą zamyka serwer językowy
Dział zatytułowany „Luka, którą zamyka serwer językowy”Graf kodu jest budowany na podstawie tree-sittera: symbole, krawędzie wywołań, promień wpływu. Jest szybki, jest tani i nie wie nic o typach. Nie powie, czy wywołanie, które właśnie napisano, się kompiluje, czy dane pole istnieje w obiekcie ani czy zmiana nazwy pominęła jakiś reeksport.
Bez serwera językowego agent dowiaduje się o tym na etapie testów — albo nigdy. Pisze coś wiarygodnie wyglądającego, idzie dalej, a błąd wypływa trzy tury później, przypisany do innej zmiany.
Zysk nie polega więc tak naprawdę na narzędziu lsp. Polega na tym, że write, edit i apply_patch są opakowane: po każdym zapisie plik przechodzi przez serwer językowy, a nowo wprowadzone komunikaty diagnostyczne są dołączane do wyniku narzędzia. Agent dowiaduje się, że zepsuł kompilację, w momencie, w którym ją zepsuł — niezależnie od tego, czy wpadł na to, żeby o to zapytać.
Tylko nowe komunikaty diagnostyczne
Dział zatytułowany „Tylko nowe komunikaty diagnostyczne”Powtarzanie tego, co agent już usłyszał, jest gorsze niż milczenie: wypełnia wynik szumem, który agent uczy się pomijać wzrokiem, a razem z nim ucieka ten jedyny nowy błąd. Rejestr prowadzony dla każdego uruchomienia śledzi, co zostało zgłoszone, z kluczem opartym na wadze, kodzie i komunikacie — nie na pozycji, więc komunikat, który jedynie przesunął się o cztery linie w dół, nie zostaje ponownie zgłoszony jako nowy.
Leniwy i uczciwy co do swojej nieobecności
Dział zatytułowany „Leniwy i uczciwy co do swojej nieobecności”Żaden serwer nie startuje przy uruchomieniu. Serwer startuje przy pierwszym żądaniu, które go potrzebuje — czyli prawie zawsze przy pierwszej edycji pliku, który obsługuje — a bezczynne są zamykane. Host, który nigdy nie dotyka Darta, nigdy nie uruchamia analizatora.
Wykrywanie to część wspólna: znacznik korzenia musi istnieć w kopii roboczej i plik binarny musi dać się odnaleźć. Sam znacznik mówi „to projekt w Pythonie” i nic o tym, czy serwer jest zainstalowany; sam odnajdywalny plik binarny mówi, że host ma zestaw narzędzi, i nic o tej kopii roboczej. Dopiero oba razem oznaczają, że zapytanie może się udać, a narzędzie oferowane i zawsze zawodzące jest gorsze od nieobecnego.
Po co wyszukiwanie strukturalne, skoro istnieje grep
Dział zatytułowany „Po co wyszukiwanie strukturalne, skoro istnieje grep”grep dopasowuje znaki. To właściwe narzędzie do nazwy i niewłaściwe do kształtu.
Wzorzec strukturalny jest parsowany przez tę samą gramatykę co plik, więc dispose($X) znajduje wywołanie niezależnie od tego, jak jest rozstawione, zawinięte czy łamane, i nigdy nie dopasowuje tych samych znaków wewnątrz literału tekstowego lub komentarza.
Cechą, która czyni go czymś więcej niż wyrażeniem regularnym z dodatkowymi krokami, jest to, że metazmienna powtórzona w jednym wzorcu musi przechwycić ten sam tekst:
if ($X != null) $X.dispose()pasuje do warunku ochronnego, który zwalnia to, co sprawdził, a nie pasuje do takiego, który zwalnia coś innego. To klasa błędów, której nie da się w ogóle wyrazić tekstowo.
Korzysta z tree-sittera, który już jest podłączony
Dział zatytułowany „Korzysta z tree-sittera, który już jest podłączony”Nie ma drugiego silnika. cc_natives już ładuje tree-sittera i jego gramatyki na potrzeby grafu kodu, z pamięcią podręczną skompilowanych zapytań i tabelą języków. Dopasowywacz to czysty Dart operujący na drzewach, które i tak były parsowane. Drugi silnik strukturalny oznaczałby drugi parser, drugi zestaw gramatyk do zbudowania i wdrożenia na każdej platformie oraz dwie odpowiedzi na pytanie „czym jest wyrażenie wywołania”.
Fragment musi mieć gdzie zamieszkać
Dział zatytułowany „Fragment musi mieć gdzie zamieszkać”Gramatyka definiuje tylko to, czym jest cały plik. Sparsowany samodzielnie na najwyższym poziomie Darta, dispose(x) jest całkowicie poprawną sygnaturą funkcji — deklaracją o nazwie dispose przyjmującą parametr x — i żaden błąd nigdzie tego nie zdradza. Dopasowywacz zbudowany na takim parsowaniu szuka deklaracji, nie znajduje wywołań i zgłasza „brak dopasowań” dla wzorca, który nigdy nie był czytany tak, jak wygląda.
Dlatego wzorzec jest próbowany w drabinie rusztowań — instrukcja, wyrażenie, składowa klasy, najwyższy poziom — i wygrywa pierwsze, które go przyjmie. Fragment, którego nie przyjmuje żaden kontekst, jest zgłaszany jako nieparsowalny, a nie przeszukiwany i po cichu nieznaleziony.
Dlaczego przepisanie strukturalne najpierw trafia do poczekalni, a nie jest od razu zapisywane
Dział zatytułowany „Dlaczego przepisanie strukturalne najpierw trafia do poczekalni, a nie jest od razu zapisywane”Przepisanie strukturalne to najszersza edycja, jakiej może dokonać agent: jeden wzorzec, czterdzieści plików, a nikt nie czyta wyniku linia po linii — ani model, ani osoba, która o to poprosiła. Zapisanie go i zaraportowanie „gotowe” oznacza, że pierwszym uczciwym spojrzeniem na zmianę jest późniejszy git diff.
Dlatego ast_edit raportuje, co zrobiłoby — liczbę, pliki, dopasowane miejsca — a zmiana trafia na miejsce dopiero wtedy, gdy zatwierdzi ją resolve. To także pozwala postawić przed człowiekiem diff i wybór Zaakceptuj/Odrzuć, zanim cokolwiek dotrze na dysk.
Zatwierdzenie jest typu wszystko albo nic, a każdy plik jest porównywany z treścią zapisaną w momencie przygotowania. Między przygotowaniem a zatwierdzeniem agent mógł ręcznie edytować jeden z tych plików, przebieg diagnostyki mógł go przepisać albo formater mógł go dotknąć — zatwierdzenie mimo to odrzuciłoby tę pracę, a diff wyglądałby na zamierzony. Każda niezgodność odrzuca całą zmianę, bo częściowo zastosowane przepisanie strukturalne zostawia drzewo, które nie kompiluje się w żadnym z tych dwóch kształtów.
Na co odpowiada debugger, czego nie potrafi instrukcja print
Dział zatytułowany „Na co odpowiada debugger, czego nie potrafi instrukcja print”Pętla z instrukcją print wygląda tak: dodaj print, uruchom wszystko jeszcze raz, przeczytaj wynik, usuń print. Kosztuje pełne uruchomienie testów na każde pytanie i odpowiada tylko na pytanie, które przyszło do głowy przed uruchomieniem.
Zatrzymana ramka odpowiada naraz na każde pytanie o ten moment — o każdą zmienną lokalną, każdego wywołującego i o wyrażenie obliczone w zasięgu samej ramki. W przypadku zawieszonego procesu albo błędu, który powtarza się raz na dziesięć uruchomień, to różnica między całym popołudniem a minutą.
Ograniczona jak wybieg — i z tego samego powodu
Dział zatytułowany „Ograniczona jak wybieg — i z tego samego powodu”Adapter debuggera posiada zatrzymany proces, który trzyma wszystko, co trzyma ten proces — port, blokadę, połączenie z bazą danych. Sesja, której nikt nie prowadzi, to wyciek, który przeżywa turę, w której ją rozpoczęto. Dlatego obowiązuje twardy TTL, dokładnie jedna sesja na rozmowę, a drugie launch jest odrzucane, zamiast po cichu zastępować pierwsze: agent, który rozpoczyna drugie bez zakończenia pierwszego, prawie na pewno zgubił pierwsze z oczu.
Jeden transport, dwa protokoły
Dział zatytułowany „Jeden transport, dwa protokoły”Debug Adapter Protocol dzieli z Language Server Protocol ramkowanie Content-Length i nic ponadto — inne klucze korelacji, inny kształt błędów i trzeci typ komunikatu (zdarzenia), który nie ma odpowiednika w LSP. Ramkowanie mieszka w jednym miejscu, a protokoły leżą na nim osobno.
Napisanie ramkowania dwa razy oznaczałoby dwie odpowiedzi na pytania „komunikat rozciąga się na dwa fragmenty”, „fragment zawiera trzy komunikaty” i „serwer wypisał baner na stdout przed pierwszą ramką”, a poprawkę dostałaby tylko jedna z nich.
Zobacz też
Dział zatytułowany „Zobacz też”- Szukaj kodu w grafie kodu — strona symboli i wpływu
- Debuguj padający test z agentem — debugger od początku do końca
- Refaktoryzuj kod strukturalnie —
ast_grepiast_edit - Wbudowane narzędzia agentów — każda operacja i argument