Michele Polo

L'Intelligenza Artificiale come Co-Sviluppatore: Una Partnership Simbiotica

Noi siamo nati per darci aiuto come i piedi, le mani, come le due file di denti
— Marco Aurelio

La Complementarietà: L'Essenza della Collaborazione

Marco Aurelio, nel secondo secolo dopo Cristo, aveva già compreso una verità profonda sull'esistenza umana: siamo creature fatte per la collaborazione. I piedi, le mani, i denti lavorano in armonia perfetta, ognuno con il suo ruolo specifico, ognuno indispensabile all'altro.

Mentre collaboravo con un assistente AI per lo sviluppo software. Non come sostituto, ma come partner. Non come automa che esegue ordini, ma come collaboratore con competenze complementari alle mie.

Perché la vera rivoluzione dell'AI nello sviluppo software non sta nel sostituire il programmatore, ma nell'amplificarne le capacità. Come i piedi e le mani, come le due file di denti, uomo e AI possono lavorare in simbiosi, ognuno contribuendo con i propri punti di forza.

Questo post esplora due dimensioni di questa partnership:

  1. Come ho usato l'AI per sviluppare CodeIntel stesso
  2. Come CodeIntel integra l'AI come capability fondamentale del sistema

Due facce della stessa medaglia: l'intelligenza artificiale al servizio della comprensione del codice.

Parte I: Claude Code, Gemini, Perplexity come Co-Sviluppatore

Il Contesto: Sviluppare Software con un Assistente AI

Quando ho iniziato CodeIntel, avevo due opzioni:

  1. Sviluppare tutto manualmente, riga per riga
  2. Collaborare con un AI assistant

Ho scelto la seconda. Non per pigrizia, ma per curiosità e pragmatismo.

Volevo capire:

  • Fino a che punto un AI può contribuire a design architetturale?
  • Può scrivere test significativi, non solo boilerplate?
  • Può documentare in modo chiaro e utile?
  • Può debuggare problemi complessi?
  • Dove sono i limiti?

Spoiler: I risultati mi hanno sorpreso, in positivo e in negativo.

Cosa Fanno Benissimo

1. Boilerplate e Scaffolding

Scenario: Devo creare un nuovo agent (es. DocumentationAgent).

Senza AI: 30-40 minuti per scrivere struttura base, interfaccia, test skeleton, README.

Con Claude Code: 5 minuti.

// Prompt: "Create DocumentationAgent skeleton with:
// - BaseAgent extension
// - Constructor with logger + services
// - generateDoc() method
// - Unit test structure
// - README outline"

// Output (generato in 30 secondi):
export class DocumentationAgent extends BaseAgent {
  constructor(
    private astAnalyzer: IASTAnalyzer,
    private llm: ILLMService,
    logger: ILogger
  ) {
    super(logger);
  }

  async generateDoc(
    filePath: string,
    options?: DocOptions
  ): Promise<Documentation> {
    const traceId = this.generateTraceId();
    this.logger.info(`[${traceId}] DocumentationAgent start`);

    // Implementation here

    return documentation;
  }
}

// + test file structure
// + README template

Valore: Tempo risparmiato, consistenza strutturale, posso concentrarmi sulla logica business.

2. Test Writing

Scenario: Ho implementato una funzione complessa (es. semantic chunking). Servono test.

Senza AI: 1-2 ore per coprire edge cases, scrivere assertions, setup/teardown.

Con Claude Code: 20 minuti.

Esempio da Sprint 4 (CodebaseIndexAgent):

// Funzione da testare
private chunkSemantically(ast: AST, file: File): Chunk[] {
  // Strategy: functions > classes > imports > fallback
  // ~100 linee di logica complessa
}

// Test generati da Claude Code:
describe('chunkSemantically', () => {
  it('should prioritize function-level chunks', () => {
    // Test implementation
  });

  it('should fallback to class-level when no functions', () => {
    // Test implementation
  });

  it('should handle files with only imports', () => {
    // Test implementation
  });

  it('should fallback to file-level for unparseable code', () => {
    // Test implementation
  });

  it('should respect max chunk size', () => {
    // Test implementation
  });

  // + 10 altri edge cases che non avrei pensato
});

Risultato: 35/35 test passing, 100% chunker coverage.

Valore: Coverage completa, edge cases che avrei dimenticato, tempo risparmiato.

3. Documentazione Dettagliata

Scenario: Ho completato Sprint 3. Serve RETROSPECTIVE.md completa.

Senza AI: 2-3 ore per ricostruire timeline, metriche, lessons learned.

Con Claude Code: 30 minuti.

Claude analizza:

  • Commit history dello sprint
  • DAILY_NOTES.md entries
  • Test results
  • SPRINT_PLAN.md progress

E genera:

  • Executive Summary
  • Metrics & Statistics (velocity, coverage, ecc.)
  • What Went Well / What Didn't Go Well
  • Lessons Learned con categorizzazione
  • Action Items per sprint successivo

Esempio reale da Sprint 3:

## Executive Summary

Sprint 3 achieved **100% completion** (15/15 story points) in 5 days,
delivering critical infrastructure improvements and establishing
the foundation for Phase 2 agent implementation.

**Key Metrics:**
- Story Points: 15/15 (100%) ✅
- Velocity: 3.00 points/day
- Test Coverage: 363/363 tests (100%)

[... 490 righe di retrospettiva dettagliata ...]

Valore: Documentazione completa, consistente, con insights che emergono dall'analisi dei dati.

4. Debugging Complesso

Caso di studio reale: Sprint 3, Task 4 - CI Configuration

Problema: Integration tests passano localmente, falliscono in CI.

Debug session con Claude Code (8 iterazioni documentate):

Iterazione 1:

  • Claude: "Verifico port mapping. Sembra 8001:8000 invece di 8000:8000"
  • Fix: Correggo docker-compose.yml
  • Risultato: Ancora fallisce

Iterazione 2:

  • Claude: "ChromaDB API v1 deprecated, serve v2. Cambia endpoint da /api/v1 a /api/v2"
  • Fix: Aggiorno client
  • Risultato: Ancora fallisce

Iterazione 3:

  • Claude: "Health check endpoint non risponde. Timeout troppo breve?"
  • Fix: Aumento wait time
  • Risultato: Ancora fallisce

[... 5 iterazioni successive ...]

Iterazione 8:

  • Me (human intervention): "Authentication! ChromaDB server ha auth, client no"
  • Claude: "Giusto! Aggiungo token auth end-to-end"
  • Fix: Configuro CHROMA_SERVER_AUTH_CREDENTIALS + client token
  • Risultato: ✅ 363/363 tests passing in CI

Lezioni:

  • Claude ha proposto fix ragionevoli per 7/8 problemi
  • L'intervento umano è stato critico per l'insight finale (authentication)
  • Documentare ogni iterazione crea knowledge base prezioso

Cosa NON Fanno Bene

Essere onesti è importante. Claude Code ha limiti:

1. Decisioni Architetturali ad Alto Livello

Domanda: "Dovrei usare multi-tenant vector collections o database separati?"

Claude: Fornisce pro/cons generici, ma non può decidere senza contesto business profondo.

Decisione finale: Sempre umana. Claude è consulente, non decision-maker.

2. Trade-off Complessi

Domanda: "Implemento caching Redis o accetto latenza maggiore per semplicità?"

Claude: Analizza le opzioni, ma i trade-off dipendono da:

  • Budget infrastrutturale
  • Scala utenti prevista
  • Team maintenance capacity
  • Priorità business

Decision: Sempre umana.

3. "Code Smell" Sottili

Claude può rilevare code smell ovvi (funzioni troppo lunghe, duplicazione), ma non percepisce "smell architetturali" sottili che emergono da conoscenza del dominio.

Esempio: "Questo agent sta facendo troppo" richiede intuizione architetturale che va oltre analisi sintattica.

4. Creatività Disruptive

L'IA è eccellente nell'applicare pattern noti, mediocre nell'inventarne di nuovi.

Per innovazione architetturale, serve pensiero umano.

La Partnership Ideale: Divisione dei Ruoli

Dopo 8 sprint con Claude Code, ho identificato una divisione ottimale:

Attività AI Umano Collaborazione
Architettura high-level
Design pattern selection ⚠️
Boilerplate scaffolding
Implementazione algoritmi ⚠️
Test writing ⚠️
Code review ⚠️
Debugging ✅✅
Documentazione tecnica ⚠️
Retrospettive ⚠️

Leggenda: ✅ Forte, ⚠️ Medio, ❌ Debole

Pattern emergente: L'AI eccelle in compiti strutturati, l'umano eccelle in compiti creativi/strategici, collaborazione massimizza risultato.

Parte II: L'AI nel Sistema CodeIntel

Oltre ad usare AI per sviluppare, CodeIntel integra AI come capability core del sistema.

RAG: Retrieval-Augmented Generation

Il cuore di CodeIntel è un sistema RAG (Retrieval-Augmented Generation), pattern che combina:

  1. Vector search (retrieval) per trovare codice rilevante
  2. LLM (generation) per sintetizzare risposte contestuali

Pipeline completa:

User Question
    ↓
[1] Embed Question (384D/768D vector)
    ↓
[2] Vector Search (find similar code chunks)
    ↓
[3] Build Context (top-k chunks + metadata)
    ↓
[4] Prompt Engineering (question + context)
    ↓
[5] LLM Generation (answer with citations)
    ↓
Answer + Sources

Implementazione pratica:

export class CodeUnderstandingAgent extends BaseAgent {
  async ask(question: string, options: AskOptions): Promise<Answer> {
    const traceId = this.generateTraceId();

    // PHASE 1: RETRIEVAL (find relevant code)
    const queryEmbedding = await this.embedder.embed(question);

    const searchResults = await this.semanticSearch.search({
      embedding: queryEmbedding,
      limit: options.topK || 10,
      filters: options.filters, // language, file pattern, etc.
    });

    // PHASE 2: CONTEXT BUILDING
    const context = await this.contextBuilder.build({
      chunks: searchResults,
      maxTokens: 4000, // LLM context window
      prioritize: 'relevance', // or 'recency'
    });

    // PHASE 3: GENERATION (LLM with context)
    const prompt = this.buildPrompt(question, context);

    const answer = await this.llm.generate({
      prompt,
      model: 'llama3.2',
      temperature: 0.3, // Lower for factual answers
      stream: true,     // Streaming for UX
    });

    return {
      answer: answer.text,
      sources: context.chunks.map(c => c.metadata),
      confidence: this.calculateConfidence(searchResults),
      traceId,
    };
  }
}

Perché RAG > Pure LLM?

Without RAG (pure LLM):

Q: "How does authentication work in this codebase?"
A: [Generic response about OAuth/JWT, not specific to your code]

With RAG (context-aware):

Q: "How does authentication work in this codebase?"

[System retrieves actual auth code from codebase]

A: "This codebase uses JWT authentication implemented in
   src/auth/JWTAuthenticator.ts. The authenticator validates
   tokens using HS256 algorithm, with a 24h expiration.
   Refresh tokens are stored in Redis with 7-day TTL.

   Key files:
   - src/auth/JWTAuthenticator.ts (core logic)
   - src/middleware/authMiddleware.ts (Express integration)
   - src/auth/__tests__/JWTAuthenticator.test.ts (test suite)
  "

Differenza: LLM ha accesso al codice reale, non solo conoscenza generica.

Embeddings: Rappresentazione Semantica del Codice

Gli embeddings sono il cuore del RAG. Trasformano codice in vettori numerici che catturano significato semantico.

Configurazione CodeIntel:

Production:

  • Model: nomic-embed-text (768 dimensioni)
  • Dimensionality: 768D
  • Vantaggi: Alta precisione, semantic similarity accuracy
  • Costo: Embedding time ~100ms/chunk

Development:

  • Model: all-minilm (384 dimensioni)
  • Dimensionality: 384D
  • Vantaggi: Veloce (50ms/chunk), meno memoria
  • Trade-off: Precision leggermente inferiore

Perché 768D invece di OpenAI (1536D)?

  • Privacy: Ollama locale, no data sent to cloud
  • Cost: Zero API costs
  • Performance: Testing in Sprint 5 ha provato che 768D è sufficiente per codebase <100K LOC

Esempio embedding generation:

export class EmbeddingService {
  async embed(text: string): Promise<number[]> {
    // Cache hit?
    const cached = this.cache.get(this.hash(text));
    if (cached) return cached;

    // Generate embedding via Ollama
    const response = await this.ollama.embeddings({
      model: 'nomic-embed-text',
      prompt: text,
    });

    const embedding = response.embedding; // 768D vector

    // Cache for future
    this.cache.set(this.hash(text), embedding);

    return embedding;
  }

  async embedBatch(texts: string[]): Promise<number[][]> {
    // Parallel processing for performance (10-100x faster)
    return Promise.all(texts.map(t => this.embed(t)));
  }
}

Performance Sprint 4:

  • Sequential: 50 chunks × 100ms = 5 seconds
  • Parallel (batched): 50 chunks in ~500ms = 10x faster

LLM Locale: Privacy-First Approach

CodeIntel usa Ollama per eseguire LLM localmente. Nessun dato inviato a cloud.

Modelli supportati:

  • llama3.2: General-purpose, buon balance qualità/velocità
  • codellama: Specializzato per codice
  • deepseek-coder: Ottimizzato per generazione codice

Configurazione:

export class LLMService {
  async generate(prompt: string, options: LLMOptions): Promise<LLMResponse> {
    const response = await this.ollama.chat({
      model: options.model || 'llama3.2',
      messages: [
        {
          role: 'system',
          content: 'You are a code analysis expert...',
        },
        {
          role: 'user',
          content: prompt,
        },
      ],
      temperature: options.temperature || 0.3,
      stream: options.stream || false,
    });

    return {
      text: response.message.content,
      model: options.model,
      tokens: {
        prompt: response.prompt_eval_count,
        completion: response.eval_count,
        total: response.prompt_eval_count + response.eval_count,
      },
    };
  }
}

Streaming support per UX migliore:

async generateStreaming(prompt: string): AsyncGenerator<string> {
  const stream = await this.ollama.chat({
    model: 'llama3.2',
    messages: [...],
    stream: true,
  });

  for await (const chunk of stream) {
    yield chunk.message.content;
  }
}

// Usage in CLI:
for await (const token of agent.askStreaming(question)) {
  process.stdout.write(token); // Live output
}

Prompt Engineering: L'Arte del Controllo

LLM sono potenti ma imprevedibili. Prompt engineering è critico.

Esempio: DocumentationAgent:

buildPrompt(funcNode: FunctionNode, context: CodeContext): string {
  return `
You are a technical documentation expert.

TASK: Generate JSDoc documentation for this function.

CONTEXT:
- File: ${context.filePath}
- Language: ${context.language}
- Dependencies: ${context.imports.join(', ')}

FUNCTION:
\`\`\`${context.language}
${funcNode.text}
\`\`\`

REQUIREMENTS:
1. Brief description (1-2 sentences)
2. @param tags for each parameter with type and description
3. @returns tag with type and description
4. @throws if function can throw errors
5. @example with realistic usage

OUTPUT FORMAT: Valid JSDoc block starting with /**

IMPORTANT:
- Be concise but complete
- Use technical language
- Include edge cases in @example
- Do NOT hallucinate parameter names or types
`;
}

Risultato: Documentation coerente, accurata, formattata.

Lessons learned:

  • Specificity: Più dettagliato il prompt, migliore l'output
  • Examples: Few-shot learning migliora drasticamente qualità
  • Constraints: "Do NOT hallucinate" riduce allucinazioni
  • Format: Specificare formato output (JSON, Markdown, etc.)

Multi-Agent Orchestration con AI

Pattern avanzato: Agents che collaborano.

Esempio: ArchitectureAnalysisAgent:

export class ArchitectureAnalysisAgent extends BaseAgent {
  async analyze(projectPath: string): Promise<Report> {
    // PHASE 1: Parallel fact collection (no AI)
    const [deps, patterns, metrics] = await Promise.all([
      this.dependencyAnalyzer.analyze(projectPath), // Pure algorithm
      this.patternDetector.detect(projectPath),     // Pattern matching
      this.metricsCalculator.calculate(projectPath), // Complexity metrics
    ]);

    const facts = { deps, patterns, metrics }; // Verifiable

    // PHASE 2: AI synthesis (with facts)
    const prompt = this.buildAnalysisPrompt(facts);
    const insights = await this.llm.generate(prompt);

    return {
      facts,        // Measurable (can unit test)
      insights,     // AI-generated (needs human review)
      confidence: this.scoreConfidence(facts),
      diagrams: this.generateMermaidDiagrams(deps),
    };
  }
}

Separazione chiara:

  • Facts: Algoritmi deterministici (dependency graph, cyclomatic complexity)
  • Insights: AI synthesis (architectural recommendations)

Vantaggio: Facts sono testabili, insights sono "best effort".

Conclusione: Simbiosi, Non Sostituzione

Dopo 8 sprint sviluppando con gli agenti e costruendo un sistema AI-powered, la mia conclusione è chiara:

L'AI non sostituisce lo sviluppatore. Lo amplifica.

Come i piedi e le mani di Marco Aurelio, umano e AI hanno ruoli complementari:

Umano:

  • Visione architetturale
  • Trade-off business
  • Creatività disruptive
  • Intuizione di dominio
  • Decisioni etiche

AI:

  • Velocità implementativa
  • Coverage di edge cases
  • Documentazione dettagliata
  • Pattern recognition
  • Consistency enforcement

Insieme: Producono software migliore, più velocemente, con più qualità.

CodeIntel è prova di questa simbiosi:

  • 753+ test scritti in collaborazione umano-AI
  • 94% coverage raggiunta grazie a test completi
  • 7,000+ righe di documentazione generate efficientemente
  • 8 sprint completati in tempo record

Ma le decisioni critiche - architettura, tech stack, trade-off - sono sempre state umane.

L'AI è il miglior collaboratore che abbia mai avuto. Ma è esattamente questo: un collaboratore, non un sostituto.

Nel prossimo post, scendiamo nel dettaglio tecnico: Abstract Syntax Trees, Tree-sitter, e come CodeIntel "comprende" veramente il codice a livello semantico.


Nel prossimo post: AST, Tree-sitter e l'Arte di Comprendere il Codice


Serie "CodeIntel System: Dal Concetto al Codice"