Come integrare Spec Driven Development con AI Code Agents

ai-integration - 07/01/2026

8 min read

Indietro

Gli agenti AI sono potenti, ma senza direzione producono codice generico. La differenza tra un output mediocre e uno production-ready sta quasi sempre nell'input che gli dai.

In questo articolo ti mostro come uso le specifiche strutturate per guidare agenti come Claude Code, GitHub Copilot e Gemini CLI. Non teoria: workflow reali che applico nei miei progetti.


Il problema: AI senza contesto

Hai mai chiesto a un LLM "scrivi una API di autenticazione" e ricevuto qualcosa di generico, pieno di placeholder, che non si integra con nulla del tuo progetto?

Il problema non è l'AI. È che le hai dato un task senza contesto:

  • Nessun vincolo architetturale
  • Nessun pattern esistente da seguire
  • Nessun criterio di accettazione
  • Nessuna idea di cosa NON fare

Una specifica risolve tutto questo. Diventa il prompt di sistema per il tuo agente.


Architettura del workflow

Ecco il flusso che uso:

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   SPECIFICA │ ──► │  PIANO TASK  │ ──► │  AI AGENT   │
│  (markdown) │     │  (breakdown) │     │  (codegen)  │
└─────────────┘     └──────────────┘     └─────────────┘
                                                │
                                                ▼
                                         ┌─────────────┐
                                         │  VALIDAZIONE│
                                         │  (test/review)│
                                         └─────────────┘

Ogni step alimenta il successivo. La specifica genera il piano, il piano guida l'agente, l'output viene validato contro la specifica originale.


Step 1: Scrivere una specifica AI-friendly

Non tutte le specifiche sono uguali. Per funzionare bene con AI, una specifica deve essere:

  • Strutturata: sezioni chiare, non prose infinite
  • Esplicita: niente assunzioni implicite
  • Testabile: ogni comportamento ha un criterio verificabile
  • Contestualizzata: riferimenti al codebase esistente

Template che uso

# Feature: [Nome]

## Contesto
[Dove si inserisce nel sistema? Quali moduli tocca?]

## Obiettivo
[Perché esiste questa feature? Che problema risolve?]

## Comportamenti

### Scenario 1: [Nome scenario]
- Given: [stato iniziale]
- When: [azione utente/sistema]
- Then: [risultato atteso]

### Scenario 2: [Nome scenario]
...

## Vincoli tecnici
- [Vincolo 1]
- [Vincolo 2]

## Interfacce

### Input
[Formato dati in ingresso]

### Output
[Formato dati in uscita]

## Criteri di accettazione
- [ ] Criterio 1
- [ ] Criterio 2

## File coinvolti
- `src/services/auth.ts` - logica autenticazione
- `src/routes/api/login.ts` - endpoint esistente
- `src/models/user.ts` - modello utente

## Non-goal
- [Cosa NON implementare]

La sezione File coinvolti è cruciale: dice all'agente dove guardare e cosa rispettare.


Step 2: Feeding the Agent

Con Claude Code

Claude Code legge il contesto del progetto. Il mio prompt tipico:

Leggi la specifica in docs/specs/reset-password.md

Implementa la feature seguendo:
- I pattern esistenti in src/services/
- I vincoli elencati nella specifica
- I criteri di accettazione come test

Genera prima i test, poi l'implementazione.

Claude Code esplora il codebase, trova i pattern, e produce codice coerente.

Con GitHub Copilot

Copilot lavora meglio con contesto inline. Apro la specifica in un tab, e nel file di implementazione scrivo:

/**
 * Reset Password Service
 *
 * Spec: docs/specs/reset-password.md
 *
 * Behaviors:
 * - Generate time-limited reset token (1h expiry)
 * - Rate limit: 3 requests per email per 15 min
 * - Invalidate token after use
 */

Copilot usa questi commenti come guida per i suggerimenti.

Con Gemini CLI

Gemini CLI accetta file come contesto:

gemini --context docs/specs/reset-password.md \
       --context src/services/auth.ts \
       "Implementa il servizio di reset password seguendo la specifica"

Step 3: Iterazione guidata dalla spec

L'output dell'AI raramente è perfetto al primo colpo. Ma con una specifica, l'iterazione è strutturata:

  1. Genera codice dalla specifica
  2. Esegui i test derivati dai criteri di accettazione
  3. Identifica cosa non passa
  4. Raffina chiedendo all'AI di fixare, citando il criterio specifico

Esempio di prompt iterativo:

Il test "link scaduto mostra errore chiaro" fallisce.
Il criterio nella specifica dice: "Link scaduto mostra errore chiaro".

Attualmente il codice ritorna 401 senza body.
Modifica per ritornare { error: "reset_link_expired", message: "..." }

L'AI ha contesto preciso su cosa fixare e perché.


Caso pratico: API di prenotazione

Ti mostro un esempio reale. Devo implementare un sistema di prenotazione slot.

Specifica (riassunta)

# Feature: Booking Slots API

## Comportamenti
- GET /slots: ritorna slot disponibili per data
- POST /bookings: prenota uno slot
- La prenotazione fallisce se lo slot è già preso (race condition handling)

## Vincoli
- Lock ottimistico su slot
- Max 1 prenotazione per utente per giorno
- Slot disponibili solo per i prossimi 14 giorni

## File coinvolti
- src/services/booking.ts (da creare)
- src/routes/api/slots.ts (da creare)
- src/models/slot.ts (esiste, schema Prisma)

Prompt a Claude Code

Implementa la Booking Slots API secondo docs/specs/booking-slots.md

Segui i pattern di:
- src/services/auth.ts per la struttura service
- src/routes/api/users.ts per la struttura route

Usa transazioni Prisma per il lock ottimistico.
Genera test con vitest.

Output (estratto)

// src/services/booking.ts
export async function bookSlot(userId: string, slotId: string) {
  return prisma.$transaction(async (tx) => {
    // Check slot availability with lock
    const slot = await tx.slot.findUnique({
      where: { id: slotId },
      select: { id: true, bookedBy: true, date: true }
    });

    if (!slot) throw new BookingError('SLOT_NOT_FOUND');
    if (slot.bookedBy) throw new BookingError('SLOT_ALREADY_BOOKED');

    // Check user daily limit
    const today = startOfDay(slot.date);
    const existing = await tx.booking.findFirst({
      where: { userId, date: today }
    });

    if (existing) throw new BookingError('DAILY_LIMIT_REACHED');

    // Book the slot
    return tx.slot.update({
      where: { id: slotId, bookedBy: null }, // Optimistic lock
      data: { bookedBy: userId }
    });
  });
}

Il codice rispetta i vincoli della specifica: transazione, lock ottimistico, limite giornaliero.


Tips per massimizzare la qualità

1. Includi sempre esempi di codice esistente

Gli agenti AI imitano pattern. Se mostri come è scritto il resto del codebase, l'output sarà coerente.

2. Specifica i non-goal

Evita che l'AI aggiunga feature non richieste. "Non implementare notifiche email" previene scope creep.

3. Chiedi test prima del codice

Genera prima i test basati sui criteri di accettazione,
poi l'implementazione che li fa passare.

Questo forza l'AI a pensare ai casi edge prima di scrivere codice.

4. Usa checkpoint di validazione

Ogni 2-3 task, verifica manualmente. Non accumulare troppo codice AI-generated senza review.


Conclusione

Gli agenti AI sono moltiplicatori di produttività, ma amplificano anche l'ambiguità. Una specifica strutturata trasforma l'AI da "generatore di codice generico" a "implementatore di requisiti precisi".

Il workflow è semplice:

  1. Scrivi la specifica
  2. Alimenta l'agente
  3. Valida contro la specifica
  4. Itera

La specifica è il contratto. L'AI è l'esecutore. Tu sei l'architetto.


Vuoi implementare questo workflow?

Se vuoi integrare agenti AI nel tuo processo di sviluppo in modo strutturato, posso aiutarti a definire specifiche, configurare tool e ottimizzare il workflow.

Contattami per una consulenza su AI-assisted development.