Quattro prompt che riscrivi diventano quattro file
Le estensioni di Claude Code sono i file che un repo può contenere per non riscrivere sempre lo stesso prompt: uno skill, un hook, un subagent e un server MCP. Abbiamo eseguito quattro lavori ricorrenti su un repo vero, una piccola API di fatturazione con un file JSON al posto del database. A ogni lavoro abbiamo dato prima l'estensione sbagliata, e la scelta sbagliata è fallita sullo schermo ogni volta. Poi ogni lavoro è diventato un file nel repo. Questa pagina copre i quattro gesti e la scheda di scelta che decide quale tipo usare.
Due risultati inquadrano tutto il resto. Spesso si descrive un hook come una garanzia e una regola nel file di memoria come una richiesta, eppure il nostro primo hook è partito, ha stampato il suo avviso e la scrittura è passata comunque. Che cosa trasforma un hook che gira in un hook che blocca lo spieghiamo nella sezione dedicata. Il primo server di docs che abbiamo collegato non ha cambiato nulla, e quello che ha risposto era il server di docs dello strumento stesso. La ricetta di hook che la maggior parte delle persone copia richiede anche un file script a parte, mentre la nostra sta tutta nel file delle impostazioni.
Prima di scrivere uno dei quattro file, devi sapere che cosa carica già la tua sessione.
Cosa sono i quattro e cosa carica già la sessione
Un'estensione di Claude Code è un file che aggiungi al repo per non riscrivere le stesse istruzioni nella casella del prompt. Esistono quattro tipi, e chi li conosce già può passare alla sezione successiva.
- Uno skill è un runbook archiviato nel repo. Claude ne carica i passaggi solo quando quel lavoro si presenta, e puoi avviarlo scrivendo il suo nome.
- Un hook è come un hook di commit in git, ma per le azioni di Claude stesso. È uno script che gira a un evento, per esempio una modifica, qualunque cosa Claude intendesse fare.
- Un subagent è un collaboratore esterno con la scrivania vuota. Riceve il tuo brief, fa la lettura e rimanda un rapporto invece del mucchio di file.
- Un server MCP è un client API che Claude può chiamare, per dati che non sono sul tuo disco, come un database o la documentazione di uno strumento.
Per misurare qualcosa serve prima una base di partenza. Apri una sessione nuova ed esegui il comando di contesto. Stampa una tabella di tutto ciò che è caricato prima della tua prima parola. Sul nostro banco il totale era 26.2k token su una finestra di contesto da 1m, senza alcuna riga Memory files e senza alcuna riga MCP. Ogni lavoro che dai a Claude da ora in poi aggiunge una riga a quella tabella oppure la lascia com'è.
Il primo lavoro: la checklist di release diventa uno skill
Uno skill è una checklist archiviata in una cartella propria che Claude legge solo quando la chiami. Il primo lavoro è una checklist di release che continui a incollare nella casella del prompt: typecheck, test, aumento di versione, riga di changelog, commit e tag.
Il posto ovvio per metterla è il file di memoria, ed è la scelta sbagliata, perché tutto ciò che sta in quel file viene caricato a ogni turno di ogni sessione. Abbiamo messo i passaggi lì e riletto la tabella di contesto. Era comparsa una riga Memory files da 210 token, caricata che tu stia facendo una release o no. La documentazione lo dice chiaramente: quando una sezione di quel file è diventata una procedura, il suo posto è in uno skill.
La scelta giusta è una cartella sotto la directory degli skill con i passaggi in .claude/skills/release/SKILL.md, una breve descrizione in cima e una riga di frontmatter in più, disable-model-invocation: true. Quella riga tiene lo skill fuori dalla lista di Claude, così solo tu puoi avviarlo, ed è quello che vuoi per qualcosa che fa commit e tag. Poi scrivi una barra e il suo nome, e la checklist parte.
| Lettura del contesto | Totale | Riga Memory files |
|---|---|---|
| Base di partenza, sessione nuova | 26.2k / 1m | nessuna |
| Checklist nel file di memoria | 26.4k / 1m | 210 token |
| Checklist come skill | 26.2k / 1m | nessuna, skill non in elenco |
Quando l'abbiamo eseguito, Claude ha completato tutti e sei i passaggi in meno di un minuto: typecheck, 34 test, aumento di versione da 0.4.0 a 0.4.1, riga di changelog, commit e tag. Ha anche scovato un difetto nella nostra stessa checklist. Il comando di commit così com'era scritto salta i file non tracciati, quindi Claude ha messo prima in staging il nuovo changelog e lo ha detto. La release ora è un comando che scrivi, vive nel repo per tutto il team e non costa nulla in contesto finché non lo scrivi.
Il secondo lavoro: la regola da rispettare diventa un hook
Un hook è uno script che Claude Code esegue prima che parta uno strumento, qui prima di qualsiasi modifica o scrittura, e ciò che lo script risponde decide cosa succede dopo. Il secondo lavoro è una regola che non deve mai rompersi: in questo repo il file dei dati salvati non si modifica mai a mano, e ogni cambiamento passa da una migrazione.
Scrivere quella regola in maiuscolo nel file di memoria non la fa rispettare. La documentazione è diretta: quel file è contesto, non configurazione imposta, e un'istruzione come "never edit this file" è una richiesta, non una garanzia, mentre un hook che blocca la modifica è un'imposizione.
Il primo hook ovvio è quello che abbiamo scritto noi. Nel file delle impostazioni controlla le scritture sul file dei dati, stampa un avviso ed esce con codice zero, come uno script di log. Poi abbiamo chiesto a Claude di cambiare l'importo di una fattura direttamente nel file, senza migrazione. L'hook è partito e l'avviso è stato stampato, ma la scrittura è passata: il prezzo unitario della terza fattura è passato da 8000 a 12000. La risposta di Claude non ha mai menzionato l'hook, perché un hook che esce pulito e non stampa nulla non ha preso alcuna decisione, e i suoi avvisi non arrivano mai a Claude.
La garanzia non è che l'hook giri, è la risposta che restituisce. La correzione mantiene lo stesso hook e lo fa restituire una decisione deny: una breve riga JSON con il motivo che vuoi far leggere a Claude. Uscire con codice due blocca allo stesso modo. L'intero hook è un unico comando inline in .claude/settings.json, senza file script:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -c 'if (.tool_input.file_path // \"\") | test(\"data/.*\\\\.json$\") then {hookSpecificOutput: {hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: \"Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).\"}} else empty end'"
}
]
}
]
}
}
Con la stessa richiesta, la modifica è stata rifiutata e il file dei dati è rimasto intatto. Claude ha visto questo errore: PreToolUse:Edit hook error: Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions). Ha detto di non aver cambiato nulla e ha chiesto se scavalcare la protezione o scrivere la migrazione.
Il limite, che Claude ha nominato da solo: questo hook controlla solo gli strumenti di modifica e scrittura, e in una run successiva Claude si è offerto di fare il cambiamento tramite la shell. Un hook deve coprire anche la shell, oppure la shell deve restare fuori dalla tua allow list, perché una protezione che copre una sola porta è ancora una richiesta. L'hook non costa nulla in contesto, perché gira fuori dalla tua conversazione.
Il terzo lavoro: l'audit che inonda diventa un subagent
Un subagent è una sessione Claude separata con il suo contesto vuoto. Riceve il tuo brief, legge ciò che gli serve e torna indietro solo un riassunto. Il terzo lavoro è un audit che inonda il tuo thread: controllare ogni file della cartella dei sorgenti rispetto alle convenzioni del README ed elencare ogni violazione con file e riga.
Chiesto inline, Claude legge ogni file nella tua conversazione, e tutto quel contenuto resta lì per il resto della sessione. Nella nostra run Claude ha letto tutti i 19 file sorgente, la riga Messages della tabella di contesto è esplosa e i risultati sono rimasti sepolti sotto il mucchio.
La scelta giusta è un file sotto .claude/agents/, qui .claude/agents/conventions-auditor.md, con un nome, una descrizione di quando usarlo, gli strumenti che può usare e un modello fissato. Fissare il modello conta, perché altrimenti un subagent eredita qualunque cosa giri nella tua sessione, e la maggior parte dei lavori di lettura non richiede il modello più grande.
Abbiamo posto di nuovo la stessa identica domanda, e Claude ha letto i file da solo. In entrambi i tentativi non ha mai chiamato l'agent, anche se era in elenco: 0 deleghe su 2. Ha funzionato nominarlo all'inizio del prompt: "use the conventions auditor subagent". È partita una sola chiamata ed è tornato un breve riassunto.
| Run | Contesto totale | Riga Messages |
|---|---|---|
| Audit inline | 46.1k | 19.3k |
| Subagent nominato | 34.1k | 7.2k |
I risultati erano buoni uguale: il subagent ha trovato un bug di percorso che la run inline si era lasciata sfuggire, e ne ha mancato uno che la run inline aveva trovato. Quindi il file fa esistere il subagent, e il tuo prompt fa sì che Claude lo usi; finché Claude non lo sceglie da solo, lo nomini tu. Un limite: il conto non si è mosso. Le due run sono costate uguale, perché la lettura avviene comunque, solo non nella tua finestra.
Il quarto lavoro: i dati irraggiungibili diventano un server MCP
Un server MCP è un client API che Claude può chiamare, e il lavoro per cui esiste riguarda dati più recenti del modello o dati che vivono fuori dal tuo disco. La scelta istintiva qui è sbagliata, quindi l'abbiamo fatta per prima.
Abbiamo collegato un server di docs pubblico per la nostra libreria di test e chiesto a Claude un test con i fake timer. Non è cambiato nulla: Claude conosceva già quella libreria e chiamava il server solo quando lo nominavamo. La documentazione che il modello già conosce non è un dato che non può raggiungere.
La prova vera è una domanda su Claude Code stesso, su un'impostazione degli hook più nuova del modello: quale impostazione fa bloccare un hook fallito, quale versione l'ha aggiunta e qual è il JSON esatto. Senza un server, Claude ha letto lo schema delle impostazioni della propria installazione, non ha trovato nulla e ha risposto di non riuscire a trovare l'impostazione e di non volerne inventare una. È una risposta onesta e inutile, perché l'impostazione esiste; è arrivata dopo la conoscenza del modello.
La soluzione è aggiungere al repo il server di docs di Claude Code stesso, con scope di progetto, così la configurazione finisce in un file alla radice del repo che tutto il tuo team riceve:
claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp
Questo scrive .mcp.json. Claude Code elenca il server come in attesa finché non lo approvi nella sessione successiva, e da quel momento i suoi strumenti sono disponibili a Claude. Abbiamo posto la stessa domanda a parole semplici, senza nominare il server. Claude ha cercato nella documentazione, ha trovato la pagina e ha risposto con l'impostazione, la versione e il JSON da aggiungere, con il link.
L'impostazione è "onFailure": "block", che fa bloccare l'azione a un hook fallito invece di lasciarla passare. È arrivata nella v2.1.295, mentre la nostra installazione era ancora alla 2.1.294. Questo è il limite: Claude non ha mai detto che la nostra installazione non poteva usarla, perché non ha controllato la versione, quindi la versione la controlli tu. Nella tabella di contesto il server compare solo con i nomi dei suoi strumenti, caricati su richiesta finché Claude non ne usa uno.
Quattro file, una scheda per scegliere
Dopo i quattro lavori, git status mostra esattamente quattro file, perché l'hook non ha bisogno di uno script proprio:
.claude/skills/release/SKILL.md.claude/settings.json.claude/agents/conventions-auditor.md.mcp.json
Fai il commit, e la prossima sessione su questo repo li avrà tutti, come chiunque lo cloni.
Il motivo per cui ogni scelta era giusta è una tabella che la documentazione fornisce, e che puoi tenere come scheda di decisione:
| Situazione | Estensione |
|---|---|
| Una procedura che incolli ancora una volta | Skill |
| Qualcosa che deve succedere ogni volta, senza chiedere | Hook |
| Un lavoro secondario che inonda la conversazione di output che non rileggerai | Subagent |
| Dati che continui a copiare da un posto che Claude non vede | Server MCP |
I tipi si possono anche combinare. La documentazione abbina uno skill a un server MCP: il server fornisce la connessione, e lo skill insegna a Claude a usarla bene. Uno skill può anche girare dentro un subagent con fork, così la sua lettura resta fuori dalla tua finestra. Quando un altro repo ha bisogno dello stesso setup, un plugin è la scatola che li spedisce tutti insieme.
La scheda decide il tipo, non la qualità. Un hook che registra soltanto, un subagent che non nomini mai e un server per docs che il modello già conosce stavano ognuno nel tipo giusto, e hanno fallito lo stesso. Scegli il tipo dalla scheda, poi fai in modo che il file risponda, non che esista soltanto.
Verdetto: quando ognuno, quando nessuno, la regola unica
Tutti e quattro i lavori che riscrivevi sono stati provati prima nel modo sbagliato, sono falliti sullo schermo e ora sono file nel repo che funzionano. La release è un comando che scrivi. La regola sui dati è un hook che rifiuta la modifica e fa chiedere a Claude. L'audit torna come riassunto. La domanda sulla documentazione ottiene l'impostazione e la versione che Claude non riusciva a trovare da solo.
L'hook con cui abbiamo aperto, quello che è partito e ha lasciato passare la scrittura, ha insegnato la regola che sta sotto a tutto: un hook decide solo attraverso ciò che il suo comando restituisce, quindi un hook che registra soltanto è una richiesta travestita.
Alcuni casi non richiedono nessuno di questi:
- Una convenzione che Claude continua a sbagliare resta nel file di memoria, come una riga, non come una procedura.
- Un lavoro che devi guidare avanti e indietro, con la tua cronologia sotto gli occhi, resta nel thread principale e non in un subagent.
- Un server di docs per una libreria che il modello già conosce costa contesto e non cambia nulla. Collega ciò che il modello non può avere, non ciò che ha già.
La regola da ricordare: dai a ogni lavoro che riscrivi il suo file, scegli il tipo dalla scheda e ricorda che, tra tutti, solo l'hook non chiede mai.
AIDive