Become a member!

Delphi AI Skills 0.3.0: il linguaggio, e l'audit del codice

🌐
Questo articolo è disponibile anche in altre lingue:
🇬🇧 English  •  🇪🇸 Español  •  🇩🇪 Deutsch  •  🇧🇷 Português

La 0.3.0 di delphi-ai-skills porta le skill Delphi generiche annunciate al lancio: dieci skill open source che insegnano a Claude Code, Codex, Cursor e Gemini il linguaggio e l'RTL, e come fare l'audit di una unit che nessuno guarda da otto anni.

Logo del progetto open source delphi-ai-skills: skill per agenti AI di coding dedicate a Delphi e DelphiMVCFramework.

Nell’ultimo paragrafo del primo annuncio, a luglio, c’era scritto che le sette skill di allora erano volutamente verticali su DelphiMVCFramework, che quello era il punto di partenza e non l’arrivo, e che le successive avrebbero riguardato Delphi come piattaforma. La 0.3.0 è la prima parte di quel pezzo mancante.

Le skill adesso sono dieci. Due delle tre nuove non sono relazionate a DMVCFramework: valgono per qualunque codice Delphi: una form VCL, un servizio Windows, una libreria, una unit che nessuno tocca dal 2004.

In breve

  • delphi: il linguaggio e l’RTL, senza presupporre framework né layout di progetto: version gating, memoria e lifetime, stringhe, eccezioni, generics, threading.
  • delphi-code-smells: la passata di audit sul codice che avete già. Warning del compilatore, memory leak, access violation, doppie free, e per ogni difetto il modo per trovarlo.
  • dmvcframework-jsonrpc: JSON-RPC 2.0, dalla pubblicazione della classe al client IMVCJSONRPCExecutor.
  • Sempre Apache-2.0, sempre Markdown puro, sempre su Claude Code, Codex, Cursor, Gemini CLI e qualunque agente che sappia leggere un file.
  • Repository: github.com/danieleteti/delphi-ai-skills

La skill delphi: quella che non presuppone niente

Un agente AI ha letto molto più C# e TypeScript che Object Pascal. Il risultato non è codice sbagliato in modo evidente: è codice che sembra Delphi. Ha i begin al posto giusto, le maiuscole giuste, ma dentro ha s[0] per leggere il primo carattere, un try ... except ... finally ... end in un blocco solo, una stringa multiriga che su Delphi 11 non esiste, e altro rumore che arriva da altri linguaggi.

La skill delphi copre esattamente questo strato, e la parte che ripaga per prima è il version gating. Il modello non sa su quale versione compilate, e nella stessa unit mescola epoche diverse senza accorgersene: una sintassi arrivata con Florence accanto a un idioma che si scriveva nel 2004.

// Delphi 12 Athens e successivi. Su 11 Alexandria non compila.
var lSql := '''
  select * from customers
  ''';

// Delphi 13 Florence e successivi: if-then-else come espressione.
X := if Left < 100 then 22 else 45;

La skill porta la tabella delle CompilerVersion release per release, e prima ancora la regola che la rende utile: la versione di target si accerta, non si suppone. L’agente lo chiede al compilatore, perché dcc32.exe --version stampa esattamente la CompilerVersion che serve; se non ci arriva, guarda quali Studio sono installati; e se ne trova più di uno, o nessuno, fa una domanda sola: 11 Alexandria, 12 Athens o 13 Florence? Il .dproj non vale come risposta, perché <ProjectVersion> è la versione del formato del file di progetto, non del prodotto, e un progetto salvato l’ultima volta da un’IDE vecchia si apre tale e quale in una nuova.

Da lì in poi si adatta. Su un 13 Florence confermato l’if come espressione si usa e basta, e avvolgerlo in un {$IF} è rumore. Su 11 non deve comparire. E quando la risposta non arriva, la skill scrive per 11 Alexandria (il minimo che assume), mette la guardia esplicita e ve lo dichiara, invece di lasciarvi scoprire la scelta in fase di compilazione.

{$IF CompilerVersion >= 36}    // Delphi 12 Athens e successivi
  ...
{$ENDIF}

Poi c’è il catalogo degli errori che un LLM commette in Delphi con una regolarità quasi commovente, ciascuno con la forma giusta accanto e il motivo. Qualche riga, per dare l’idea:

Sbagliato Giusto Perché
s[0] per il primo carattere s[1], oppure s.Chars[0] string è a base 1, ma TStringHelper (Chars, IndexOf, Substring) è compilato a base 0. Due basi di indicizzazione nello stesso tipo.
return X; Result := X;, o Exit(X) Result è una variabile implicita, non un’istruzione. Letta prima di essere assegnata, restituisce spazzatura.
try ... except ... finally ... end annidarli Un blocco solo non può avere entrambi. È un errore di sintassi, non una preferenza.
with lObj do ... una variabile locale with maschera gli identificatori: un campo aggiunto a lObj sei mesi dopo si prende un nome del contesto esterno, in silenzio, e compila.
procedure Foo(AText: string) procedure Foo(const AText: string) Su un tipo gestito, const evita refcount e copia.
TStringList.Create sperando che liberi gli Objects[] TStringList.Create(True) I due contenitori hanno default opposti: TObjectList<T> possiede, TStringList no.

Il grosso del materiale sta in sei file reference/ che l’agente apre solo quando il compito lo richiede: memoria e lifetime, stringhe ed encoding, eccezioni, generics e RTTI, concorrenza, stile. La finestra di contesto resta libera finché non serve davvero, che è il motivo per cui una skill è un file su disco e non un blocco incollato in cima alla chat.

Tutto è stato copiato dai sorgenti RTL/VCL installati su disco, o confermato sulla docwiki. La skill inoltre sa di poter essere incompleta, e quando le serve una firma che non ha, ha l’ordine delle operazioni scritto dentro: prima legge il sorgente, poi la docwiki, e se ancora non è sicura lo dice. Non sono certo che TFoo.Bar esista, controlla in System.Classes.pas è una risposta utile. Una risposta sicura e sbagliata no.

E legge l’albero giusto: se compilate con 12 Athens, la verifica va fatta in Studio\23.0\source\rtl, non nella copia dello stesso file che sta sotto 37.0. La RTL cresce release dopo release, e un tipo o un overload che c’è in Florence può semplicemente non esistere in Athens. Vale anche per quello che le skill portano con sé: il container di DUnitX con cui si fa fallire una build su un memory leak si chiama TDUnitXServiceLocator nella versione distribuita con 13 Florence e TDUnitXIoC in quella distribuita con 12 Athens, con la stessa identica chiamata sotto. Un dettaglio del genere non lo si ricorda: lo si va a leggere.

Una dichiarazione, però, è solo metà della risposta. Dice quanti parametri ci vogliono e di che tipo, e tace su tutto il resto: chi libera cosa, in che ordine vanno chiamate le cose, qual è la forma idiomatica. Per quello serve un punto di chiamata vero, e le skill lo cercano in quest’ordine: prima il vostro codice, che porta con sé anche le convenzioni di casa da rispettare, poi i sorgenti stessi (l’RTL usa di continuo le proprie API, quindi una grep restituisce esempi funzionanti e non prosa di documentazione), poi i samples del framework o la docwiki.

E se l’agente non sa dove sono quei sorgenti, la regola è chiedere invece di tirare a indovinare. La risposta però non si perde a fine sessione: dopo aver verificato che il percorso esista, l’agente vi propone di scriverlo nel file di istruzioni che già legge, il CLAUDE.md del progetto o l’AGENTS.md, in un blocco suo:

<!-- delphi-local-sources -->
Delphi RTL/VCL source: C:\Program Files (x86)\Embarcadero\Studio\23.0\source   (12 Athens, CompilerVersion 36.0)
DelphiMVCFramework checkout: C:\DEV\dmvcframework   (sources/ + samples/)
<!-- /delphi-local-sources -->

Quel blocco è la prima cosa che l’agente guarda quando la sessione parte, e la domanda si fa una volta per progetto invece di una volta al giorno. Se un percorso non esiste più, o cambiate versione di Delphi, ve lo dice e richiede: preferisce ammettere di non sapere piuttosto che ripescare qualcosa dalla memoria.

La skill delphi-code-smells: prima la macchina, poi l’opinione

L’altra metà del mestiere non è scrivere codice, è guardare quello che c’è già. È qui che gli agenti si comportano peggio, e non perché sbaglino: perché sono educati. Chiedete una review e vi arriva una pagina di osservazioni sui nomi delle variabili, l’ordine delle uses e la lunghezza dei metodi. Zero leak. Una review che restituisce quindici note di stile e nessun problema di lifetime non è una review, è un parere.

delphi-code-smells impone un ordine di attacco, e i primi due punti non prevedono alcun giudizio umano:

  1. Compilare con warning e hint attivi, e leggere ogni riga dell’output. Gratis, oggettivo, e con la resa più alta di qualunque altra cosa.
  2. Eseguire con ReportMemoryLeaksOnShutdown := True, e se esiste una suite di test, eseguire quella.
  3. Ownership a mano: ogni Create nell’unit, chi lo libera, su quali percorsi, incluso quello che solleva.
  4. Gestione delle eccezioni: ogni except senza on, ogni handler vuoto, ogni try/except che voleva essere un try/finally.
  5. Concorrenza: tutto ciò che è raggiungibile da un TThread.Execute o dal corpo di un TTask.Run.
  6. Il resto. Naming, with, metodi lunghi, numeri magici.

La regola che tiene insieme la lista è scritta nella skill in modo poco diplomatico: un leak che il compilatore non vede batte una convenzione di naming, sempre. E ogni difetto va riportato con quello che costa a runtime, non con quanto è brutto da leggere.

Il difetto numero uno della classifica è questo, e continuo a vederlo tutte le settimane durante le mie consulenze:

// SBAGLIATO: il costruttore sta DENTRO il try
try
  lList := TStringList.Create;
  ...
finally
  lList.Free;   // se Create solleva, qui si chiama Destroy su memoria non inizializzata
end;

// GIUSTO
lList := TStringList.Create;
try
  ...
finally
  lList.Free;   // .Free è nil-safe: "if x <> nil then x.Free" è rumore
end;

Due righe scambiate di posto. La prima versione produce un’access violation solo quando il costruttore fallisce, cioè quasi mai, cioè il martedì mattina dal cliente che il server non lo riavvia da otto mesi.

La skill porta anche la passata da cinque minuti: una manciata di grep messe in ordine di resa, dal costruttore dentro il try all’eccezione ingoiata da un except end, dalla FreeAndNil su una locale al FreeOnTerminate. Sono i colpi che un umano non ha voglia di dare e che una macchina dà in tre secondi.

rg -n -U 'try\b[^;]*?\n\s*\w+\s*:=\s*T\w+\.Create'   # costruttore DENTRO il try
rg -n -P '(?s)except\s*(//[^\n]*\n\s*)*end'          # eccezione ingoiata
rg -n 'FreeOnTerminate'                              # lifetime dei thread

C’è poi la parte che rende l’audit ripetibile invece che episodico: come si legge davvero il report del memory manager, quando serve RegisterExpectedMemoryLeak, e come si configura DUnitX perché un leak faccia fallire la build. Un leak trovato una volta è una giornata di lavoro. Un leak che da domani rompe la pipeline è una classe di bug chiusa.

Un’ultima regola, che è la mia preferita perché vale anche per le persone: non riportare mai uno smell di cui non sai dire come si trova. Se non c’è un codice di warning, uno strumento o una grep che lo dimostri, è una preferenza, e nessuno l’aveva chiesta.

E poi JSON-RPC

La terza skill nuova, dmvcframework-jsonrpc, torna dentro il perimetro del framework: un endpoint JSON-RPC 2.0 in DelphiMVCFramework è una normale classe Delphi pubblicata su un segmento di URL, senza attributi di routing per metodo. La skill copre cosa è chiamabile e cosa no, la distinzione tra function (request) e procedure (notification), i parametri nominali e posizionali, le tre regole di ownership su chi libera cosa, i codici di errore, gli hook, e il client IMVCJSONRPCExecutor per chiamare quell’endpoint da Delphi.

Le dieci skill, in una tabella

Skill Cosa copre
delphi Il linguaggio e l’RTL: version gating, inline var, memoria e lifetime, stringhe ed encoding, eccezioni, System.Generics.Collections, RTTI, threading, convenzioni.
delphi-code-smells La review: warning e hint che indicano un bug vero, warnings-as-errors, ricerca dei leak, come far fallire una build su un leak, analisi statica, catalogo di smell con il modo di trovarli.
dmvcframework Il nucleo del framework: bootstrap ed engine, controller e functional action, routing, IMVCResponse, ownership, ORM ActiveRecord, Repository, container DI, validazione, middleware, JWT, SSE, dotEnv.
dmvcframework-minimal-api Route come metodi anonimi: gruppi di route (Prefix, MapGet, MapPost), binding guidato dai tipi, upload, endpoint filter e HTTP filter, .AsWeb.
dmvcframework-webapp Web app server-side: TemplatePro, ereditarietà dei template, fragment, ViewData, login con cookie/JWT, file statici, helper HTMX lato Delphi.
dmvcframework-ui Il layer di presentazione del wizard: Bootstrap 5.3, baselayout.html, i token di style.css, dark mode, toast.
dmvcframework-security Secure coding lato server: access control e IDOR, mass assignment, SQL injection, XSS in TemplatePro, CSRF, path traversal, upload, SSRF, header, hardening del JWT, segreti.
dmvcframework-jsonrpc JSON-RPC 2.0: pubblicazione, request e notification, parametri, ownership, errori, hook, client.
dmvcframework-testing DUnitX, IMVCServer in-process, IMVCRESTClient, test CRUD, di autenticazione e di autorizzazione, fixture di database.
htmx-skill L’indice di ogni pagina della documentazione ufficiale di htmx.org, così l’agente legge la pagina giusta invece di ricordarsi htmx a modo suo.

Le due skill Delphi non hanno requisiti: nessun layout di progetto, nessun framework. Le sette DMVCFramework partono da un progetto creato con il wizard dell’IDE, e dmvcframework-security viene tirata dentro d’ufficio da qualunque endpoint riceva input da un client.

Come si installano e come si usano

git clone https://github.com/danieleteti/delphi-ai-skills.git
cd delphi-ai-skills
install_in_claude.bat

Per Claude Code finisce qui: le skill vengono scoperte da sole. Per gli altri agenti ci sono install_in_codex.bat, install_in_cursor.bat e install_in_gemini.bat, che copiano le skill e scrivono il puntatore nel file di istruzioni giusto (AGENTS.md, .cursor/rules/*.mdc, GEMINI.md), perché quegli agenti non le scoprono da soli. Ogni script accetta un percorso di progetto, se preferite versionare le skill nel repository e darle a tutto il team:

install_in_claude.bat C:\DEV\mio-progetto

Le skill non si “chiamano”: si descrive il compito e l’agente sceglie. Il trigger più forte è nominare la tecnologia. “Trova il leak” è ambiguo, “trova il leak in questa unit Delphi no.

Questa unit compila su Delphi 11, o sto usando sintassi che esiste solo da Athens in poi? Questo servizio perde memoria nel giro di qualche giorno: trova il punto. Fai una review di questa unit e dimmi cosa c’è che non va davvero, non lo stile. Quali warning del compilatore sto ignorando che nascondono un bug vero? Fai fallire la build quando c’è un memory leak. Questo codice gira in un thread e tocca una label VCL: cosa c’è di sbagliato? TObjectList<T> con OwnsObjects o TList<T> semplice?

Quando volete la garanzia, la skill la si nomina e basta: su Claude Code con /delphi o /delphi-code-smells, sugli altri citando il percorso (“leggi skills/delphi-code-smells/SKILL.md, poi rivedi questa unit”). Funziona ovunque, perché è un file di testo, non una funzione.

Un consiglio che vale i dieci secondi che costa: se avete i sorgenti di DelphiMVCFramework su disco, ditelo all’agente all’inizio della sessione (“i sorgenti DMVCFramework sono in C:\DEV\dmvcframework”). Le skill sono istruite a verificare invece di indovinare, e con i sorgenti a portata di lettura verificano molto meglio.

Perché le skill non possono contenere un nome inventato

C’è un problema di fondo, in un progetto che ha come unico scopo impedire a un agente di inventare nomi di API: se a inventarne uno è la skill, il danno è più grave di prima, perché adesso l’errore ha l’aria autorevole della documentazione.

Per questo le skill non escono se non passano da una pipeline che le confronta con il codice vero: i sorgenti di DelphiMVCFramework e la RTL di tutte le versioni di Delphi supportate. Ogni nome citato dev’essere un nome che esiste lì dentro. Se anche uno solo non c’è, la release si ferma, e non è un parere che si può discutere in review: o il nome sta nei sorgenti o non sta nelle skill.

È il controllo che ha fatto emergere il difetto del DUnitX di 12 Athens raccontato più sopra, e l’ha fatto emergere qui invece che a casa vostra, che è esattamente il punto.

Quello che una verifica del genere non può dirvi è se quell’API è usata bene: per quello serve un compilatore, e per un errore di ownership serve eseguire il programma. È il primo dei tre livelli con cui le skill vengono controllate, ed è quello che costa così poco da poter restare acceso sempre.

Materiali e video su Patreon

Le skill dicono all’agente cosa è vero. Restano fuori il come: com’è fatta una sessione di lavoro vera, dove conviene fermarsi, cosa chiedere e in che ordine, quando l’agente sta prendendo una strada che vi costerà due ore.

Nelle prossime settimane pubblicherò diverso materiale, scritto e in video, sulla pagina Patreon di DelphiMVCFramework: sessioni intere su Delphi e su DMVCFramework con le skill al lavoro, con i punti in cui va storto qualcosa lasciati dentro, perché sono la parte che si impara.

Il repository resta Apache-2.0 e completo: nulla di quello che serve per usare le skill sta dietro un abbonamento. Patreon è il posto in cui trovate il materiale esplicativo, ed è anche il modo con cui chi vuole sostiene il lavoro su DMVCFramework e su tutto quello che ci gira intorno. Se vi è utile, il canale è quello. Se preferite prendere le skill e andare per la vostra strada, va benissimo lo stesso: è esattamente per questo che sono su GitHub.

Domande frequenti su delphi-ai-skills 0.3.0

Cosa c’è di nuovo nella 0.3.0 di delphi-ai-skills? Tre skill in più rispetto alla prima release, per un totale di dieci: delphi (il linguaggio e l’RTL, senza alcun framework), delphi-code-smells (la review del codice esistente) e dmvcframework-jsonrpc (JSON-RPC 2.0). La 0.3.0 corregge anche due affermazioni sbagliate nella reference sulla memoria della skill delphi.

Devo usare DelphiMVCFramework per usare queste skill? No, ed era previsto fin dal primo annuncio. delphi e delphi-code-smells non presuppongono alcun framework né alcun layout di progetto: valgono per una form VCL, un servizio Windows, una libreria, una unit legacy. Le altre sette restano specifiche di DelphiMVCFramework.

Cosa fa la skill di audit sul codice Delphi? delphi-code-smells mette la review nell’ordine giusto: prima il compilatore con warning e hint attivi, poi l’esecuzione con ReportMemoryLeaksOnShutdown := True, poi l’ownership a mano, le eccezioni, la concorrenza e solo alla fine lo stile. Copre i warning che indicano un bug vero (con il codice, per esempio W1035), $WARN e i warnings-as-errors, come si legge un report di leak, come si fa fallire una build su un leak con DUnitX, gli analizzatori statici di terze parti, e un catalogo di difetti in cui ogni voce dice cosa costa a runtime e come si trova.

Le skill si occupano di sicurezza? Sul versante server sì, ed è dmvcframework-security: una dipendenza obbligatoria di tutte le skill DMVCFramework, applicata a qualunque endpoint riceva input da un client. Copre access control e IDOR, mass assignment, SQL injection, XSS in TemplatePro, CSRF, path traversal, upload, SSRF e open redirect, header di sicurezza, hardening del JWT e gestione dei segreti. delphi-code-smells è invece un audit di difetti, non un audit OWASP: si occupa di leak, access violation, doppie free e risultati silenziosamente sbagliati.

Su quale versione di Delphi funzionano? Su 11 Alexandria, 12 Athens e 13 Florence. La skill accerta quale avete: interroga il compilatore con dcc32.exe --version, in alternativa guarda quali Studio sono installati, e se resta ambiguo vi chiede quale usate, invece di tirare a indovinare. Con la versione nota, ci si adatta. Quando la risposta non arriva, il target è Delphi 11 Alexandria, con una guardia {$IF CompilerVersion >= ...} intorno a tutto ciò che dipende dalla versione. Il contenuto della skill è verificato sui sorgenti RTL/VCL di Delphi 13 Florence. Le skill DMVCFramework puntano alla 3.5.0 (silicon).

Con quali agenti AI funzionano? Claude Code, Codex, Cursor, Gemini CLI, Windsurf, Continue e qualunque agente in grado di leggere istruzioni in Markdown. Il repository include lo script di installazione per i primi quattro. Sugli agenti diversi da Claude Code il caricamento dipende da quanto quell’agente rispetta il proprio file di istruzioni, quindi conviene nominare la tecnologia nel prompt, oppure la skill stessa.

Come faccio a sapere che le skill non contengono nomi di API inventati? Perché non possono. Prima di ogni rilascio, una pipeline confronta ogni nome citato nelle skill con il codice vero: i sorgenti di DelphiMVCFramework e la RTL delle versioni di Delphi supportate. Se un solo identificatore non esiste lì dentro, quella release non esce. Non è una rilettura attenta fatta da qualcuno di bravo, è un cancello: la stessa disciplina che le skill impongono all’agente, applicata alle skill. In pratica, quando una skill afferma qualcosa su una API, quell’affermazione è già stata messa a confronto con il codice invece che con la memoria di qualcuno. Quello che il controllo non prova è che l’API sia usata bene: per quello servono il compilatore e l’esecuzione, che sono i due livelli sopra.

La strada davanti

Siamo ancora in 0.x, e la forma dell’insieme è da considerarsi instabile: le skill potranno essere divise, unite, rinominate o rimosse man mano che l’uso reale mostra cosa serve davvero. La 1.0.0 arriverà quando l’insieme si sarà dimostrato su abbastanza progetti veri. Nel frattempo, le segnalazioni valgono più di qualunque roadmap: se una skill vi ha fatto scrivere codice sbagliato, quello è il difetto che voglio vedere per primo.

Il criterio per contribuire resta uno solo: ogni affermazione dev’essere verificabile sui sorgenti, citando il file. Nessun nome di API scritto a memoria. È la disciplina che chiediamo agli agenti, e sarebbe curioso non applicarla a noi.

Il progetto è qui: github.com/danieleteti/delphi-ai-skills. Issue e pull request sono benvenute.


Un agente che scrive Delphi senza sapere il Delphi produce codice che compila male e invecchia peggio. Un agente istruito produce codice che potete leggere fra due anni senza chiedervi chi lo ha scritto. La differenza, per ora, sono dieci file Markdown.

Comments

comments powered by Disqus