AI Agents · Lezione

Commenti automatizzati nelle PR

Lettura dei diff delle PR e pubblicazione di commenti inline di revisione tramite API

Lezione 3 di 413 passaggi

Commenti automatizzati nelle PR è una lezione AI Agents gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Agents, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Agents include 4 lezioni in totale.

Che cos'è la revisione automatizzata delle PR

Un agente per la revisione automatizzata delle PR legge i file modificati, analizza i diff e pubblica commenti mirati di revisione, proprio come un revisore umano, ma più rapidamente e in modo coerente. Casi d'uso: analisi della sicurezza, applicazione dello stile, rilevamento di bug comuni, verifica della copertura dei test o riepilogo del contenuto di una PR.

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

# Get a specific pull request by number
pr = repo.get_pull(42)
print(f'PR #{pr.number}: {pr.title}')
print(f'Author: {pr.user.login}')
print(f'Base branch: {pr.base.ref} <- Head: {pr.head.ref}')
print(f'State: {pr.state}')
print(f'Changed files: {pr.changed_files}')
print(f'Additions: +{pr.additions}')
print(f'Deletions: -{pr.deletions}')

Elencare le pull request aperte

Utilizzi repo.get_pulls() per elencare le PR. Filtri per stato (open, closed), branch di destinazione o ordine di ordinamento. Per un bot di revisione, in genere si elaborano le PR open che non sono ancora state sottoposte a revisione.

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

# All open PRs targeting main
open_prs = repo.get_pulls(
    state='open',
    base='main',
    sort='created',
    direction='desc'
)

for pr in open_prs:
    # Check if agent has already reviewed it
    reviews = list(pr.get_reviews())
    agent_reviewed = any(
        r.user.login == 'my-agent-bot[bot]'
        for r in reviews
    )
    if not agent_reviewed:
        print(f'Needs review: PR #{pr.number} - {pr.title}')

Ottenere i file modificati

pr.get_files() restituisce un elenco di oggetti File, ciascuno dei quali rappresenta un file modificato nella PR. Proprietà principali: filename, status (added/modified/removed), additions, deletions, patch (il testo del diff unificato).

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')
pr = repo.get_pull(42)

changed_files = list(pr.get_files())
print(f'Changed files: {len(changed_files)}')

for file in changed_files:
    print(f'\n{file.status.upper()}: {file.filename}')
    print(f'  +{file.additions} -{file.deletions}')

    # The patch is the unified diff
    if file.patch:
        print(f'  Patch preview: {file.patch[:200]}')

# Filter to only Python files
python_files = [
    f for f in changed_files
    if f.filename.endswith('.py') and f.status != 'removed'
]

Leggere la patch del file (diff)

file.patch è una stringa contenente un diff unificato. Le righe che iniziano con + sono state aggiunte, mentre quelle che iniziano con - sono state rimosse. L'intestazione @@ indica da quali numeri di riga inizia il blocco. Analizzi questi dati per individuare esattamente la posizione dei problemi nel nuovo codice.

def parse_patch_hunks(patch):
    if not patch:
        return []

    hunks = []
    current_hunk = None
    new_line_num = 0

    for line in patch.split('\n'):
        if line.startswith('@@'):
            # Parse: @@ -old_start,old_count +new_start,new_count @@
            import re
            match = re.search(r'\+([0-9]+)', line)
            if match:
                new_line_num = int(match.group(1))
            current_hunk = {'header': line, 'lines': [], 'start_line': new_line_num}
            hunks.append(current_hunk)
        elif current_hunk is not None:
            if line.startswith('+'):
                current_hunk['lines'].append({'type': 'add', 'content': line[1:], 'line': new_line_num})
                new_line_num += 1
            elif line.startswith('-'):
                current_hunk['lines'].append({'type': 'remove', 'content': line[1:], 'line': None})
            else:
                new_line_num += 1

    return hunks

# --- demo ---
patch = (
    '@@ -10,3 +10,4 @@\n'
    ' def add(a, b):\n'
    '-    return a - b\n'
    '+    return a + b\n'
    '+    # fixed subtraction bug\n'
)
for hunk in parse_patch_hunks(patch):
    print(hunk['header'])
    for line in hunk['lines']:
        print(' ', line)

Ottenere i commit di una PR

pr.get_commits() restituisce i commit inclusi nella PR. Ogni commit contiene un messaggio, un autore e un elenco di file modificati. Analizzare i messaggi dei commit aiuta il bot di revisione a riepilogare il contenuto della PR e a verificare il rispetto delle buone pratiche nei commit.

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')
pr = repo.get_pull(42)

commits = list(pr.get_commits())
print(f'Commits in PR: {len(commits)}')

for commit in commits:
    print(f'\nCommit: {commit.sha[:8]}')
    print(f'Author: {commit.commit.author.name}')
    print(f'Message: {commit.commit.message[:80]}')
    print(f'Changes: +{commit.stats.additions} -{commit.stats.deletions}')

# Check commit message quality
poor_messages = [
    c for c in commits
    if len(c.commit.message.split()[0] if c.commit.message else '') < 3
    or c.commit.message.lower().startswith(('wip', 'fix', 'test'))
]
if poor_messages:
    print(f'{len(poor_messages)} commits have poor message quality')

Pubblicare una revisione di una PR

Utilizzi pr.create_review() per inviare una revisione formale. Il parametro event controlla il tipo di revisione: 'APPROVE', 'REQUEST_CHANGES' o 'COMMENT'. Una revisione può includere un corpo generale e un elenco di commenti in linea su righe specifiche.

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')
pr = repo.get_pull(42)

# Submit a review with a general comment (no inline comments)
pr.create_review(
    body=(
        '**Automated Code Review**\n\n'
        'I analyzed this PR and found the following:\n'
        '- No security issues detected\n'
        '- Style conforms to project guidelines\n'
        '- Test coverage looks adequate\n\n'
        '_This review was generated automatically._'
    ),
    event='COMMENT'  # 'APPROVE', 'REQUEST_CHANGES', or 'COMMENT'
)

Pubblicare commenti di revisione in linea

I commenti in linea vengono visualizzati direttamente su righe specifiche del diff. Ogni commento specifica path (percorso del file), line (numero di riga nel nuovo file) e body. Passi un elenco di commenti a pr.create_review(comments=[...]).

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')
pr = repo.get_pull(42)

# Build inline comments from analysis results
inline_comments = [
    {
        'path': 'src/auth.py',
        'line': 47,
        'body': (
            ':warning: **Security Issue:** Using `md5` for password hashing.\n'
            'Use `bcrypt` or `argon2` instead.\n'
            'Example: `import bcrypt; bcrypt.hashpw(password.encode(), bcrypt.gensalt())`'
        )
    },
    {
        'path': 'src/utils.py',
        'line': 23,
        'body': (
            ':bulb: Consider using `pathlib.Path` instead of `os.path.join` '
            'for better cross-platform compatibility.'
        )
    }
]

pr.create_review(
    body='Security and style review complete. See inline comments.',
    event='REQUEST_CHANGES',
    comments=inline_comments
)
print(f'Review submitted with {len(inline_comments)} inline comments')

Analisi del codice basata su LLM

Fornisca il diff del file a un LLM insieme a un prompt per la revisione della sicurezza e della qualità. Richieda una risposta JSON strutturata che elenchi i problemi con percorso del file, numero di riga, gravità e descrizione. Utilizzi il risultato per creare l'elenco dei commenti in linea.

import anthropic
import json
import os

client = anthropic.Anthropic(api_key=os.environ['ANTHROPIC_API_KEY'])

def analyze_file_with_llm(filename, patch):
    prompt = (
        f'Review this Python code diff for issues.\n'
        f'File: {filename}\n\n'
        f'Return JSON array: [{{"line": N, "severity": "error|warning|info", '
        f'"message": "..."}}]\n'
        f'Focus on: security vulnerabilities, bugs, and anti-patterns.\n'
        f'Diff:\n{patch[:3000]}'
    )

    response = client.messages.create(
        model='claude-opus-4-5',
        max_tokens=1000,
        messages=[{'role': 'user', 'content': prompt}]
    )

    try:
        issues = json.loads(response.content[0].text)
        return [
            {'path': filename, 'line': i.get('line', 1),
             'body': f'[{i.get("severity", "info").upper()}] {i.get("message", "")}'}
            for i in issues if isinstance(i, dict)
        ]
    except json.JSONDecodeError:
        return []

Flusso completo dell'agente per la revisione delle PR

Colleghi tutti i componenti in un flusso completo di revisione delle PR: recuperi i file, filtri quelli sottoponibili a revisione, analizzi ciascuno con l'LLM, raccolga i commenti e invii la revisione con un'unica chiamata API. Limiti l'analisi ai primi 10 file modificati per contenere i costi dell'LLM.

def run_pr_review(g, repo_name, pr_number):
    repo = g.get_repo(repo_name)
    pr = repo.get_pull(pr_number)

    print(f'Reviewing PR #{pr_number}: {pr.title}')

    # Get changed files (limit to 10)
    files = [
        f for f in list(pr.get_files())[:10]
        if f.filename.endswith(('.py', '.js', '.ts'))
        and f.patch
    ]

    all_comments = []
    for file in files:
        print(f'  Analyzing: {file.filename}')
        comments = analyze_file_with_llm(file.filename, file.patch)
        all_comments.extend(comments)

    # Determine review event based on findings
    critical = [c for c in all_comments if '[ERROR]' in c['body']]
    event = 'REQUEST_CHANGES' if critical else 'COMMENT'

    summary = (
        f'Analyzed {len(files)} files. '
        f'Found {len(all_comments)} issues '
        f'({len(critical)} critical).'
    )

    pr.create_review(body=summary, event=event, comments=all_comments[:25])
    print(f'Review submitted: {event} with {len(all_comments)} comments')

Verificare le revisioni esistenti dell'agente

Prima di pubblicare una revisione, verifichi se il bot dell'agente ha già revisionato questa PR nello stato corrente. Eviti revisioni duplicate controllando nell'elenco delle revisioni esistenti il nome utente del Suo bot.

def should_review_pr(pr, bot_username):
    reviews = list(pr.get_reviews())

    # Check if bot already reviewed this PR
    bot_reviews = [
        r for r in reviews
        if r.user and r.user.login == bot_username
    ]

    if not bot_reviews:
        return True  # No previous review, proceed

    # Check if new commits were pushed since last review
    last_review_time = max(r.submitted_at for r in bot_reviews)
    commits = list(pr.get_commits())
    latest_commit_time = commits[-1].commit.author.date if commits else None

    if latest_commit_time and latest_commit_time > last_review_time:
        print(f'New commits since last review, re-reviewing...')
        return True

    print(f'PR #{pr.number} already reviewed by {bot_username}')
    return False

# --- demo: minimal stand-ins for PyGithub-style PR/review/commit objects ---
import datetime
from types import SimpleNamespace

class _FakeCommit:
    def __init__(self, date):
        self.commit = SimpleNamespace(author=SimpleNamespace(date=date))

class _FakeReview:
    def __init__(self, login, submitted_at):
        self.user = SimpleNamespace(login=login)
        self.submitted_at = submitted_at

class _FakePR:
    def __init__(self, number, reviews, commits):
        self.number = number
        self._reviews = reviews
        self._commits = commits
    def get_reviews(self):
        return self._reviews
    def get_commits(self):
        return self._commits

pr_never_reviewed = _FakePR(101, [], [_FakeCommit(datetime.datetime(2026, 8, 1))])
print('PR #101 needs review:', should_review_pr(pr_never_reviewed, 'coddy-bot'))

review_time = datetime.datetime(2026, 8, 1, 10, 0)
pr_up_to_date = _FakePR(102, [_FakeReview('coddy-bot', review_time)],
                        [_FakeCommit(review_time - datetime.timedelta(hours=1))])
print('PR #102 needs review:', should_review_pr(pr_up_to_date, 'coddy-bot'))

Pattern di scansione di sicurezza per la revisione delle PR

Un pattern comune per la revisione automatizzata consiste nella ricerca di anti-pattern di sicurezza nelle righe aggiunte: segreti codificati direttamente nel codice, utilizzo di funzioni deprecate, concatenazione di stringhe SQL o chiamate a eval(). Verifichi ogni riga aggiunta rispetto a un insieme di pattern e segnali i risultati come commenti inline.

import re

SECURITY_PATTERNS = [
    (r'eval\s*\(', 'Dangerous eval() call — code injection risk'),
    (r'os\.system\s*\(', 'os.system() is unsafe; use subprocess with a list'),
    (r'pickle\.loads?\s*\(', 'pickle.load is unsafe with untrusted data'),
    (r'execute\(.*%\s*', 'Possible SQL injection — use parameterized queries'),
    (r'md5\s*\(|hashlib\.md5', 'MD5 is cryptographically broken for passwords; use bcrypt'),
]

def scan_patch_for_security(filename, patch):
    if not patch:
        return []
    comments = []
    new_line = 0
    for line in patch.split('\n'):
        if line.startswith('@@'):
            import re as _re
            m = _re.search(r'\+([0-9]+)', line)
            if m:
                new_line = int(m.group(1))
        elif line.startswith('+') and not line.startswith('+++'):
            content = line[1:]
            for pattern, message in SECURITY_PATTERNS:
                if re.search(pattern, content):
                    comments.append({'path': filename, 'line': new_line,
                                     'body': f':rotating_light: **Security:** {message}'})
            new_line += 1
        elif not line.startswith('-'):
            new_line += 1
    return comments

# --- demo ---
patch = (
    '@@ -1,2 +1,3 @@\n'
    ' def run(cmd):\n'
    '+    os.system(cmd)\n'
    '+    hashlib.md5(cmd.encode())\n'
)
for c in scan_patch_for_security('run.py', patch):
    print(c)

Verifica rapida: eventi di revisione

Verifichi la Sua comprensione delle opzioni di revisione delle PR.

Riepilogo della revisione automatizzata delle PR

Ora il Suo agente è in grado di esaminare programmaticamente le pull request:

  • repo.get_pull(number) — recupera un oggetto PR con i metadati
  • pr.get_files() — elenca i file modificati con filename, patch, additions
  • pr.get_commits() — esamina i messaggi dei commit e le relative statistiche
  • pr.create_review(body, event, comments) — invia una revisione con commenti inline opzionali
  • Formato del commento inline: {'path': filename, 'line': N, 'body': markdown}
  • Utilizzi 'COMMENT' per le osservazioni e 'REQUEST_CHANGES' per i problemi bloccanti
  • Controlli le revisioni esistenti prima di ripetere la revisione, per evitare duplicati
  • Limiti l'analisi dei file a 10 file e a 25 commenti inline per rispettare i limiti dell'API
Gratis per iniziare

Impara AI Agents con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
60
Lezioni
239

Domande Frequenti

La lezione «Commenti automatizzati nelle PR» è gratuita?

Sì — il testo completo di «Commenti automatizzati nelle PR» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Agents, passa a CoddyKit PRO. Il corso AI Agents include 4 lezioni in totale.

Cosa imparerò in «Commenti automatizzati nelle PR»?

Lettura dei diff delle PR e pubblicazione di commenti inline di revisione tramite API Eserciti AI Agents con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare AI Agents?

Non è richiesta alcuna esperienza precedente. AI Agents su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Commenti automatizzati nelle PR»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione AI Agents?

Sì. Ogni lezione AI Agents include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Panoramica della GitHub REST API
  2. Elenco e gestione delle issue
  3. Commenti automatizzati nelle PR
  4. Cronologia dei commit e analisi dei diff
← Torna a AI Agents