Come dò davvero istruzioni a un'AI per costruire software
- ai-workflow
- prompting
- developer-tools
- field-notes
La mossa chiave non è chiedere all'AI di scrivere codice. È avere una vera conversazione con un modello pensante sull'idea, farlo scrivere la specifica, e poi passare quella specifica a un agente di coding. Due fasi, due lavori completamente diversi.
La maggior parte degli sviluppatori usa gli strumenti AI di coding nello stesso modo sbagliato. Aprono una chat, digitano una frase vaga e si aspettano che dall'altra parte esca software funzionante. Quando non succede, danno la colpa al modello. Il modello non è il problema. Il flusso di lavoro lo è. Uso lo sviluppo assistito da AI in produzione da abbastanza tempo da avere un sistema, e il punto centrale è semplice: il prompting ha due fasi distinte, e quasi nessuno le separa.
La fase uno è una conversazione con un modello pensante (Claude Opus 4.8, GPT-5.5) sull'idea. Non sul codice. Sui vincoli, i casi limite, il tuo stack, le tue convenzioni, cosa questa funzionalità dovrebbe e non dovrebbe fare. Alla fine di quella conversazione, chiedi al modello di scrivere una specifica di build precisa per un agente di coding. La fase due è consegnare quella specifica all'agente (Claude Code, Cursor) e lasciarlo pianificare ed eseguire. Il modello pensante è l'architetto. L'agente di coding è il costruttore.

Perché un prompt vago produce poltiglia plausibile
Un modello linguistico produrrà sempre qualcosa. Questo è il problema. Chiedigli di "aggiungere una funzionalità di bookmark" e scriverà codice, con sicurezza, basandosi su qualunque assunzione abbia fatto con i suoi dati di addestramento. Non sa che sei su Next.js 15 App Router, che usi Prisma con MySQL, che la tua auth è NextAuth v5, che distribuisci su Hetzner tramite Ploi, o che hai bandito i class component sei mesi fa. Fa supposizioni ragionevoli e produce poltiglia plausibile: codice sintatticamente corretto che non si adatta al tuo progetto.
La densità del segnale è la variabile. Ogni vincolo che ometti è una decisione che il modello prende per te senza che tu lo sappia. L'unico rimedio è codificare il contesto rilevante prima che venga scritto qualsiasi codice, e il modo più affidabile per farlo è una conversazione strutturata seguita da una specifica scritta.
Fase uno: conversazione con il modello pensante
Apro Claude o ChatGPT e descrivo l'idea in linguaggio semplice. Non un prompt. Una descrizione. Cosa voglio costruire, perché, chi lo usa, quali sono i vincoli. Chiedo al modello di farmi domande a sua volta: cosa potrebbe andare storto? Quali casi limite mi sto perdendo? Cosa questa funzionalità dovrebbe esplicitamente non fare? Questa conversazione porta alla luce assunzioni che non sapevo di stare facendo.
Poi do al modello un meta-prompt: un'istruzione strutturata che gli dice di produrre una specifica di build per un agente di coding, codificando il mio stack e le mie convenzioni complete. Ecco il meta-prompt che uso:
You are a senior software architect and technical writer.
I need you to turn the following idea into a precise build specification
for an AI coding agent (Claude Code / Cursor).
MY STACK:
- Next.js 15 (App Router, TypeScript, Tailwind CSS 4)
- Database: MySQL via Prisma 6. Schema in prisma/schema.prisma.
- Auth: NextAuth v5 with JWT strategy. Session helpers in src/lib/auth.ts.
- Deploy: Hetzner VPS managed by Ploi, Cloudflare CDN in front.
- No class components. No pages/ directory. No getServerSideProps.
IDEA:
[describe your feature here in plain language]
CONSTRAINTS:
- [any non-obvious edge cases, business rules, or perf requirements]
PRODUCE:
1. A one-paragraph summary of what will be built and why.
2. A list of every file that will be created or modified, with a one-line
description of the change.
3. Numbered acceptance criteria the agent can verify by running
'npx tsc --noEmit' and 'npm test'.
4. Any migration steps needed (Prisma schema changes, seed data, env vars).
5. Explicit out-of-scope items so the agent does not overshoot.
Output the spec in markdown. Do NOT write any code yet.Il vincolo chiave in quel meta-prompt è "Do NOT write any code yet." Vuoi che il modello pensante faccia lavoro architetturale, non implementativo. Se inizia a scrivere codice, è passato nella modalità sbagliata. Tienilo sulla specifica.
Fase due: la specifica dell'agente
Ciò che esce dal modello pensante è un documento markdown su cui l'agente di coding può effettivamente agire. Nomina ogni file che cambierà, elenca criteri di accettazione verificabili e indica cosa è fuori scope. L'agente non ha più bisogno di indovinare. Ecco come appare una vera specifica per una funzionalità bookmark:
# Build Spec: Bookmark Feature
## Summary
Add a per-user bookmark system so authenticated users can save posts.
Bookmarks are stored in MySQL, surfaced in a new /bookmarks page,
and toggled from individual post pages via a server action.
## Files
- prisma/schema.prisma add Bookmark model (userId, postSlug, createdAt)
- prisma/migrations/... generated migration, run before deploy
- src/app/bookmarks/page.tsx new page, server component, lists user bookmarks
- src/app/actions/bookmarks.ts server actions: toggleBookmark, getUserBookmarks
- src/components/BookmarkButton.tsx client component, optimistic UI
- src/app/blog/[slug]/page.tsx import and render BookmarkButton
## Acceptance Criteria
1. 'npx prisma migrate dev' runs without error on a clean dev database.
2. 'npx tsc --noEmit' passes with zero errors.
3. 'npm test' passes: unit tests for toggleBookmark cover unauthenticated
call (must throw), duplicate toggle (idempotent remove), and success path.
4. Visiting /bookmarks as an unauthenticated user redirects to /login.
5. Clicking BookmarkButton on a post toggles the filled/outline icon
without a full page reload (optimistic update via useOptimistic).
## Out of Scope
- Bookmark folders or tags.
- Public bookmark lists.
- Any change to the existing post rendering pipeline.Nota come i criteri di accettazione siano verificabili dalla macchina. L'agente può eseguire npx tsc --noEmit e npm test e ottenere un pass o un fail. Non ha bisogno di fare valutazioni soggettive su se la funzionalità è finita. La specifica definisce la fine.
- Ogni file elencato dà all'agente un confine di scope. Sa cosa toccare e cosa lasciare in pace.
- La sezione fuori scope è importante quanto quella in scope. Impedisce all'agente di aggiungere funzionalità non richieste.
- Il passaggio di migrazione è esplicito. L'agente non indovinerà le modifiche al database.
- I criteri di accettazione che fanno riferimento a comandi reali danno all'agente un ciclo di auto-verifica.
Memoria markdown: il file AGENTS.md
Ecco un fatto sugli agenti di coding AI che la maggior parte delle persone impara a proprie spese: non hanno memoria tra le sessioni. L'agente che ha costruito il tuo middleware di autenticazione la settimana scorsa oggi non sa che esiste. Senza contesto persistente, inventerà un pattern diverso, violerà le tue convenzioni di denominazione, o importerà una libreria che hai bandito.
La soluzione è un file AGENTS.md alla radice del progetto. Non è documentazione per gli esseri umani. È memoria di lavoro per l'agente, caricata all'inizio di ogni sessione. Codifica il tuo stack, le tue convenzioni, i comandi da eseguire e i pattern da evitare. Poiché vive nel repository ed è versionata, rimane aggiornata man mano che il progetto evolve.

Ecco un AGENTS.md minimale per il mio tipico stack Next.js:
# AGENTS.md - Stack and Conventions
## Stack
- Next.js 15, App Router only. No pages/ directory.
- TypeScript strict mode. No 'any'. Use 'unknown' and narrow.
- Tailwind CSS 4 for styling. No inline style props.
- Prisma 6 with MySQL. Schema: prisma/schema.prisma.
- NextAuth v5 - session helpers in src/lib/auth.ts. Do not re-implement auth.
- Deploy: Ploi on Hetzner, Cloudflare in front.
## Conventions
- Server components by default. Add 'use client' only when necessary.
- Server actions in src/app/actions/. One file per domain (bookmarks.ts, etc.).
- Shared UI in src/components/. Page-specific UI co-located with the page.
- Tests in __tests__/ next to the file under test.
## Commands
npm run dev start dev server
npm run build production build
npm test run vitest
npx tsc --noEmit type-check only
## Do NOT
- Use getServerSideProps, getStaticProps, or pages/api routes.
- Import server-only modules in client components.
- Use process.env directly in components - use src/lib/config.ts.Una volta che quel file esiste, non ridigi mai il tuo stack all'agente. Il meta-prompt che uso per generare le specifiche vi fa riferimento direttamente: quando descrivo il mio stack nella conversazione con il modello pensante, aggiorno l'AGENTS.md allo stesso tempo. I due documenti restano sincronizzati. Il modello pensante usa lo stack per scrivere una specifica corretta. L'agente di coding legge AGENTS.md per eseguirla correttamente.
Verificare il risultato in un passaggio separato
Dopo che l'agente consegna l'implementazione, non chiedere alla stessa sessione di verificarla. Il contesto è di parte: ha un modello di ciò che intendeva scrivere, e quel modello lo farà trascurare ciò che ha effettivamente scritto. Avvia una sessione fresca, carica AGENTS.md, carica il diff, e dai all'agente un prompt di verifica: rivedi le modifiche rispetto ad AGENTS.md, riporta le violazioni, i test mancanti, gli errori di tipo e i bug logici, non suggerire miglioramenti.
Il contesto fresco legge ciò che è effettivamente lì. Non ha alcun attaccamento all'intento della sessione precedente. Combinato con i criteri di accettazione verificabili dalla macchina della specifica, questo approccio a due passaggi cattura la maggior parte dei problemi prima che raggiungano la revisione.
Il ciclo completo
Metti tutto insieme: hai una conversazione con un modello pensante sull'idea, produci una specifica di build usando il meta-prompt, passi la specifica all'agente di coding insieme ad AGENTS.md, l'agente costruisce e si auto-verifica rispetto ai criteri di accettazione, poi esegui un passaggio di verifica separato in una sessione fresca. Cinque passaggi, ognuno con un input e un output chiari.
Questo è più lento che digitare una singola riga in una finestra di chat. È più veloce che fare debug del codice che l'agente ha scritto basandosi su assunzioni sbagliate, combattere errori di tipo che la specifica avrebbe catturato, e spiegare al tuo team perché la funzionalità bookmark memorizza lo stato in un Context provider invece di una server action.
- 01Conversazione con il modello pensante: descrivi l'idea, porta alla luce vincoli e casi limite.
- 02Meta-prompt: chiedi al modello pensante di produrre una specifica di build per l'agente di coding.
- 03Revisione della specifica: itera sulla specifica finché i criteri di accettazione sono verificabili dalla macchina.
- 04Esecuzione dell'agente: consegna la specifica e AGENTS.md all'agente di coding.
- 05Passaggio di verifica separato: sessione fresca, AGENTS.md, diff, prompt di verifica.
Il modello pensante fa lavoro architetturale. L'agente di coding fa lavoro implementativo. Tu fai lavoro di giudizio: decidere quando la specifica è buona, quando l'implementazione è accettabile, quando iterare e quando spedire. Quella divisione del lavoro è ciò che rende questo sistema affidabile.