Le mie due settimane con Cursor e Continue.dev

Negli ultimi giorni ho avuto l'opportunità di testare due strumenti, Cursor e Continue.dev, per valutarne l'impatto nel nostro workflow quotidiano da sviluppatori. L'obiettivo era capire se potessero migliorare le attività di code review, auto-generazione del codice e generazione automatizzata di commit message.

 

Primo test con Continue.dev

Il primo esperimento è stato l'integrazione di Continue.dev in Visual Studio Code, uno strumento open-source che permette di avere un copilota AI direttamente nell'editor, facilitando l'automazione di attività e suggerimenti intelligenti. Per questo test ho utilizzato Ollama come provider, una piattaforma che consente di eseguire modelli linguistici in locale, garantendo maggiore controllo e privacy. In particolare, ho scelto il modello Deepseek-r1:8B per creare comandi personalizzati.

Intanto un piccolo approfondimento su cosa stanno ad indicare questi valori 8B, 7B, 3B.

💡 Questi valori indicano il numero di parametri del modello di intelligenza artificiale. "B" sta per "billion" (miliardi), quindi un modello 7B ha 7 miliardi di parametri, mentre un 8B ne ha 8. Maggiore è il numero di parametri, più il modello è complesso e capace di gestire compiti avanzati, ma anche più esigente in termini di risorse hardware.

 

Primo tentativo: fallimento totale

La combinazione di un modello pesante da fare girare localmente, come quello scelto, e le limitazioni hardware del mio MacBook con chip M1 e 16GB di RAM ha reso l'esperienza frustrante. L'avvio di DeepSeek sembrava un'operazione interminabile, quasi come effettuare una ricerca su Internet Explorer.

Secondo tentativo: Qwen-Coder2.5:7B

Dopo il primo insuccesso, ho deciso di cambiare approccio provando il modello Qwen-Coder2.5:7B. Questa volta, l'esperienza è stata decisamente più fluida: il modello si attivava più rapidamente e l'interazione risultava scorrevole. Tuttavia, l'accuratezza dell'output non era sempre all'altezza delle aspettative.

 

Creazione dei comandi personalizzati

Dopo aver selezionato il modello, ho scritto i prompt per due comandi distinti, partendo da quello un pò più facile per la generazione automatica di commit message conformi alle best practice (es. Conventional Commits). Continue.dev permette di creare comandi personalizzati che automatizzano attività specifiche nel flusso di lavoro. Il prompt per il primo comando è stato relativamente semplice da strutturare, prendendo spunto dalla documentazione ufficiale di Continue/Commands, che guida nella creazione di questi comandi personalizzati.

 

Primo comando: Generazione automatica di commit

Il funzionamento del comando è piuttosto lineare: estrae il diff di Git, analizza le modifiche apportate e genera un messaggio di commit conciso, chiaro e in linea con le best practice. Potrebbe sembrare un'implementazione banale, ma qualsiasi strumento che aiuti ad evitare di dover interrompere il flusso di lavoro per pensare al messaggio più adeguato è sicuramente un'aggiunta ben accetta.

 

1export const CommitMessage = {
2  name: "commit-message-suggest",
3  description:"Analyze Git changes and generate a commit message compliant with Conventional Commits.",
4  run: async function* (sdk: ContinueSDK) {
5 
6    const diff = await sdk.ide.getDiff(false);
7    for await (const message of sdk.llm.streamComplete(
8      `${diff}\n\nWrite a commit message for the above changes. 
9      Use no more than 20 tokens to give a brief description in the imperative mood (e.g. 'Add feature' not 'Added feature').
10      Use the Conventional Commits format:
11      - feat: (for new features)
12      - fix: (for bug fixes)
13      - ref: (for code improvements without changing behavior)
14      - docs: (for documentation updates)
15      - test: (for adding or updating tests)
16      - chore: (for maintenance changes)
17      
18      Valid examples:
19      - feat: add OAuth authentication support
20      - fix: correct tax calculation bug
21      - refactor: optimize sorting algorithm` ,
22      new AbortController().signal,
23      {
24        maxTokens: 20,
25      },
26    )) {
27      yield message;
28    }
29  },
30};

 

Secondo comando: Code review automatizzata

Il secondo comando si è rivelato più complesso. Tutto parte da una sfida che ogni sviluppatore conosce bene: c'è sempre quel collega, il Giovanni Storti della code review, il pignolo del team, quello che si trova a dover esaminare decine di PR al giorno e segnalare sempre gli stessi errori. E mentre lui combatte con codice poco allineato agli standard, il resto del team sa già che alcune sue osservazioni saranno inevitabili.

Per ridurre il suo stress (e il nostro), il comando è stato pensato per eseguire una pre-review automatizzata, intercettando in anticipo quelle segnalazioni ripetitive. In questo modo, gli sviluppatori possono correggere da soli gli errori più comuni prima della revisione ufficiale, risparmiando tempo prezioso a tutti e, soprattutto, evitando di finire nel mirino del revisore più esigente del team.

Per questo comando ovviamente sono partito da quello che era stato creato precedentemente (commit-message). Il mio comando prenderà quello che è il diff Git, vedrà le modifiche e mi darà una code-review coi fiocchi. Beh, forse non proprio, ma non per colpa sua.

 

1export const CodeReview = {
2  name: "code-review",
3  description: "Analyze current Git changes",
4  run: async function* (sdk: ContinueSDK) { 
5    const diff = await sdk.ide.getDiff(false);
6    if (!diff) {
7      yield "No changes in staging.";
8      return;
9    }
10
11
12    const prompt = `
13    \`\`\`diff
14      ${diff.join("\n")}
15      \`\`\`
16
17    Analyze the code changes and provide a detailed review. 
18    Highlight potential issues, suggest improvements, and indicate best practices to follow. 
19    Also, adhere to the following Angular Coding Style Guide conventions:
20    - Verify the folder structure and adherence to modules (e.g., base, auth, dictionary).
21    - Ensure file names follow the format: 'feature.type.ts'.
22    - Use the correct separators in names (dashes for words, dots for types).
23    - Imports must be sorted by type and alphabetically.
24    - Ensure the sequence of class members is: properties, constructor, methods.
25    - Private members should come after protected and public ones.
26    - Getters/setters should be ordered before methods, with the getter preceding the setter.
27    `;
28
29      let fullAnalysis = "";
30      for await (const review of sdk.llm.streamComplete(
31        prompt,
32        new AbortController().signal,
33        {
34          temperature: 0.4,
35        },
36      )) {
37        fullAnalysis += review;
38        yield review;
39      }
40  }
41}

 

Tutto molto bello ma diamo contesto!

Una cosa di cui mi sono accorto (dopo aver fatto partire un po’ di volte il comando) e che nella code-review c’era sempre un problema di "contesto". Mi spiego meglio.

Il file che stavo passando al prompt era incompleto: nel diff Git che io passavo, c’erano solo le righe di codice che avevo modificato ed il path del file modificato. Questo tipo di approccio poteva andare bene per un commit message, ma per una code-review avevo bisogno di avere tutto il file (modificato e non), affinchè il modello potesse aiutarmi ed io potessi aiutarlo a capire perchè erano stato fatte quelle modifiche e cosa comportavano.


Quindi in una seconda versione, tramite un po' di fine tuning all’interno della funzione, sono riuscito a passare al prompt sia il gitDiff ed tutto il contenuto. Ecco qui il file di esempio:

 

1export const CodeReview = {
2  name: "code-review",
3  description: "Analyze the modified files in the repository.",
4  run: async function* (sdk: ContinueSDK) { 
5    const diff = await sdk.ide.getDiff(false);
6    
7    if (!diff || diff.length === 0) {
8      yield "No modifications found.";
9      return;
10    }
11
12    // Joins all the lines of the diff into a string
13    const diffContent = diff.join('\n');
14    const modifiedFiles = new Set<string>();
15
16    // Improved regex to extract file names
17    const diffBlocks = diffContent.split('diff --git');
18    
19    for (const block of diffBlocks) {
20      if (!block.trim()) continue;
21      
22      // Extracts the file path from the line "--- a/path/to/file"
23      const filePathMatch = block.match(/^--- a\/(.+?)\n/m);
24      if (filePathMatch) {
25        modifiedFiles.add(filePathMatch[1]);
26      }
27    }
28
29    if (modifiedFiles.size === 0) {
30      yield "No modified files found.";
31      return;
32    }
33
34    let fullAnalysis = "";
35    const dir = await sdk.ide.getWorkspaceDirs();
36    const baseDir = dir[0]; // Takes the first directory as the base
37
38    for (const filePath of modifiedFiles) {
39      try {
40        // Builds the full path
41        const fullPath = `${baseDir}/${filePath}`;
42        
43        // Reads the content of the modified file
44        const fileContent = await sdk.ide.readFile(fullPath);
45        
46        if (!fileContent) {
47          yield `Unable to read file: ${filePath}`;
48          continue;
49        }
50
51        // Extracts the specific diff for this file
52        const fileDiff = diffBlocks.find(block => block.includes(`--- a/${filePath}`)) || '';
53
54        const prompt = `**File Analysis Request**
55
56          **File Path:** ${filePath}
57
58          **Objective:** Provide a concise and fast analysis of the file changes. DON'T WRITE ANY CODE UNLESS ASKED.
59
60          **Instructions:**
61          - **Diff Overview:**
62            \`\`\`diff
63            ${fileDiff}
64            \`\`\`
65
66          - **Full Content:**
67            \`\`\`typescript
68            ${fileContent}
69            \`\`\`
70
71 // ... existing code ...

 

Ecco quindi un semplice prompt che non fa altro che andare a controllare eventuali errori all’interno del file, curare l’ordinamento di import e proprietà e, infine, offrire suggerimenti per lo sviluppatore per aiutarlo nella correzione del file. Questo è solo l'inizio, e ci sono tanti modi per migliorarlo.

(Se hai avuto altre esperienze con Continue.dev, mi piacerebbe avere feedback!)

 

Cursor: un'alternativa per la produttività

Dopo la prima esperienza con Continue.dev, ho deciso di provare anche Cursor e sono rimasto piacevolmente sorpreso.

Cursor è un editor di codice avanzato che integra funzionalità di intelligenza artificiale per ottimizzare l'autocompletamento e la code review, offrendo un'esperienza di sviluppo più fluida e personalizzata. Una delle caratteristiche che mi ha colpito è la possibilità di allegare una "rule" a un file, ovvero un set di istruzioni specifiche per guidare l'intelligenza artificiale nell'analisi del codice. Questa "rule" indica all'AI cosa controllare, come la conformità a determinati stili di codifica o la rilevazione di errori. In questo modo, l'AI sa esattamente come procedere, migliorando l'accuratezza dei suggerimenti.

 

unnamed (2).png

 

Ho testato, ad esempio, una rule specifica per Angular e l'ho personalizzata secondo le mie esigenze. Top! E ce ne sono molte altre già disponibili per diversi framework (Cursor Rules).

Ma come in tutto, ci sono anche i contro. L'unico “neo”? Per sostenere lo sviluppo e mantenere alta la qualità del servizio, Cursor offre un piano premium a pagamento. Il costo di questo piano varia tra 20$ e 40$ al mese, a seconda delle funzionalità e del livello di accesso desiderato. Questo investimento consente agli sviluppatori di accedere a strumenti avanzati, come l'integrazione con modelli AI premium come GPT-4 e Claude 3.5 Sonnet, che richiedono risorse computazionali significative.

Considerando l'evoluzione del panorama dello sviluppo software e l'importanza crescente dell'intelligenza artificiale nel migliorare la produttività e la qualità del codice, questa spesa può essere giustificata per professionisti e team che cercano di ottimizzare il loro flusso di lavoro e rimanere competitivi nel settore.

 

Punti di forza e debolezza

Continue.dev

Vantaggi:

  • Open-source e gratuito
  • Personalizzabile con prompt su misura
  • Integrabile in VS Code

Svantaggi:

  • Le performance dipendono dal modello scelto e dall'hardware
  • L'output non è sempre preciso
  • Configurazione iniziale non immediata

 

Cursor

Vantaggi:

  • Ottima integrazione con il codice
  • Code review più efficace
  • Maggiore reattività dell'autocompletamento

Svantaggi:

  • Strumento a pagamento
  • Dipendenza dalla connessione internet per le funzionalità AI avanzate.

     

Conclusioni

Mi sono divertito molto nel fare questi esperimenti e nel cercare di integrare questi strumenti nel mio flusso di lavoro quotidiano. Sono ancora all'inizio di questo percorso e credo ci siano molte possibilità di miglioramento, sia nella configurazione che nell'uso dei modelli disponibili. Le potenzialità sono enormi e, con un affinamento dei prompt e una maggiore esperienza nell'integrazione nel proprio workflow, potrebbero diventare strumenti ancora più efficienti.

Un aspetto particolarmente interessante è la possibilità di creare un flusso di lavoro condiviso, che permetta a un intero team di collaborare in modo strutturato, adattandosi alle esigenze di ogni membro. Questo approccio potrebbe migliorare non solo l'efficienza individuale, ma anche la coesione e la produttività dell'intero gruppo.

E tu hai provato queste soluzioni? Hai trovato metodi alternativi o ottimizzazioni particolari per massimizzarne il rendimento? Scrivici e parliamone! ;)

Hai un progetto in mente?

Vuoi implementare soluzioni AI per migliorare i tuoi prodotti digitali?

Contattaci

Autore

Alessandro Fabrizi

Programmatore, chitarrista, chimico mancato e capo scout per passione. Fin da piccolo diviso tra codice e pentagrammi, ha trovato il modo di unire logica e creatività sia nello sviluppo software che nella musica. Dopo aver studiato chimica all’università (e scoperto che i bug del codice sono meno esplosivi di quelli in laboratorio), si è dedicato alla programmazione, diventando un Frontend Developer.

Nerd nel DNA, si destreggia tra campagne di Dungeons & Dragons, strumenti musicali e tende da scout, cercando sempre di insegnare qualcosa ai più giovani senza perdere dadi né bussola. Se non è al computer, probabilmente sta accordando la chitarra, preparando un fuoco da campo o discutendo se sia meglio un critico a D&D o un codice che gira al primo colpo (spoiler: il critico vince sempre).

Devmy su linkedin

Ultimi Articoli