Tekoälyagentit · Oppitunti

Automaattiset PR-katselmointikommentit

PR-diffien lukeminen ja rivinsisäisten katselmointikommenttien julkaiseminen API:n kautta

Oppitunti 3/413 vaihetta

Automaattiset PR-katselmointikommentit on ilmainen Tekoälyagentit-oppitunti CoddyKitissä. Tämä on oppitunti 3/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Tekoälyagentit-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Tekoälyagentit-kurssilla on yhteensä 4 oppituntia.

Mitä on automaattinen PR-katselmointi?

Automaattinen PR-katselmointiin tarkoitettu agentti lukee muuttuneet tiedostot, analysoi diffit ja julkaisee kohdennettuja katselmuskommentteja — aivan kuten ihmiskatselmoija, mutta nopeammin ja johdonmukaisemmin. Käyttökohteita ovat tietoturvan tarkistus, tyylisääntöjen valvonta, yleisten virheiden havaitseminen, testikattavuuden tarkistaminen ja PR:n sisällön yhteenveto.

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}')

Avoimien pull requestien listaaminen

Käyttäkää komentoa repo.get_pulls() PR:ien listaamiseen. Suodattakaa niitä tilan (open, closed), kohdehaaran tai lajittelujärjestyksen perusteella. Katselmointibotti käsittelee yleensä avoimia PR:iä, joita ei ole vielä katselmoitu.

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}')

Muuttuneiden tiedostojen hakeminen

pr.get_files() palauttaa luettelon File-olioita — kukin niistä edustaa PR:ssä muuttunutta tiedostoa. Tärkeitä ominaisuuksia ovat filename, status (added/modified/removed), additions, deletions ja patch (yhtenäisen diffin teksti).

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'
]

Tiedoston patchin (diffin) lukeminen

file.patch on yhtenäinen diff-merkkijono. Merkillä + alkavat rivit on lisätty ja merkillä - alkavat rivit poistettu. Otsikko @@ näyttää, mistä rivinumerosta hunk alkaa. Jäsentäkää tämä, jotta tunnistatte täsmälleen, missä uuden koodin ongelmat ovat.

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)

PR:n komittien hakeminen

pr.get_commits() palauttaa PR:n komitit. Jokaisella komitilla on viesti, tekijä ja luettelo muuttuneista tiedostoista. Komittiviestien analysointi auttaa katselmointibottia tekemään yhteenvedon PR:n sisällöstä ja tarkistamaan komittikäytännöt.

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')

PR-katselmuksen lähettäminen

Käyttäkää komentoa pr.create_review() muodollisen katselmuksen lähettämiseen. Parametri event määrittää katselmuksen tyypin: 'APPROVE', 'REQUEST_CHANGES' tai 'COMMENT'. Katselmukseen voi sisältyä yleinen leipäteksti ja luettelo tiettyihin riveihin kohdistuvista kommenteista.

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'
)

Rivikohtaisten katselmuskommenttien lähettäminen

Rivikohtaiset kommentit näkyvät suoraan diffin tietyillä riveillä. Jokainen kommentti määrittää path-arvon (tiedostopolku), line-arvon (uuden tiedoston rivinumero) ja body-arvon. Välittäkää kommenttiluettelo kutsulle 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')

LLM-pohjainen koodianalyysi

Syöttäkää tiedoston diff LLM:lle tietoturva- ja laatuarviointiin tarkoitetun kehotteen kanssa. Pyytäkää jäsennelty JSON-vastaus, jossa luetellaan ongelmat sekä tiedostopolku, rivinumero, vakavuus ja kuvaus. Rakentakaa vastausten avulla rivikohtaisten kommenttien luettelo.

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 []

PR-katselmointiagentin täysi työnkulku

Yhdistäkää kaikki osat täydelliseksi PR-katselmoinnin työnkuluksi: hakekaa tiedostot, suodattakaa katselmoitavat tiedostot, analysoikaa kukin LLM:n avulla, kerätkää kommentit ja lähettäkää katselmus yhdellä API-kutsulla. Rajoittakaa käsittely kymmeneen ensimmäiseen muuttuneeseen tiedostoon LLM-kustannusten hallitsemiseksi.

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')

Olemassa olevien agenttikatselmusten tarkistaminen

Tarkistakaa ennen katselmuksen lähettämistä, onko agenttibottinne jo katselmoinut tämän PR:n nykyisessä tilassa. Välttäkää katselmusten kaksoiskappaleet tarkistamalla olemassa olevien katselmusten luettelosta bottinne käyttäjänimi.

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'))

PR-katselmoinnin tietoturvatarkistusmalli

Yleinen automaattisen katselmoinnin malli on etsiä lisätyiltä riveiltä tietoturvan vastaisia käytäntöjä: kovakoodattuja salaisuuksia, vanhentuneiden funktioiden käyttöä, SQL-merkkijonojen yhdistämistä tai eval()-kutsuja. Tarkista jokainen lisätty rivi mallijoukkoa vasten ja merkitse löydökset rivinsisäisiksi kommenteiksi.

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)

Pikatarkistus: katselmointitapahtumat

Testaa ymmärrystäsi PR-katselmoinnin vaihtoehdoista.

Automaattisen PR-katselmoinnin yhteenveto

Agenttisi osaa nyt katselmoida pull requesteja ohjelmallisesti:

  • repo.get_pull(number) — hae PR-objekti metatietoineen
  • pr.get_files() — luettele muuttuneet tiedostot, joiden tiedot ovat filename, patch ja additions
  • pr.get_commits() — tarkastele commit-viestejä ja tilastoja
  • pr.create_review(body, event, comments) — lähetä katselmointi valinnaisine rivinsisäisine kommentteineen
  • Rivinsisäisen kommentin muoto: {'path': filename, 'line': N, 'body': markdown}
  • Käytä 'COMMENT'-arvoa havainnoille ja 'REQUEST_CHANGES'-arvoa estäville ongelmille
  • Tarkista aiemmat katselmoinnit ennen uutta katselmointia päällekkäisyyksien välttämiseksi
  • Rajoita tiedostojen analyysi 10 tiedostoon ja rivinsisäiset kommentit 25 kommenttiin API-rajojen puitteissa pysymiseksi
Aloita maksutta

Opi Tekoälyagentit tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
60
Oppitunnit
239

Usein kysytyt kysymykset

Onko oppitunti ”Automaattiset PR-katselmointikommentit” ilmainen?

Kyllä – oppitunnin ”Automaattiset PR-katselmointikommentit” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Tekoälyagentit-kurssin, päivitä CoddyKit PROhon. Tekoälyagentit-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Automaattiset PR-katselmointikommentit”?

PR-diffien lukeminen ja rivinsisäisten katselmointikommenttien julkaiseminen API:n kautta Harjoittelet Tekoälyagentit-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni Tekoälyagentit-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin Tekoälyagentit-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 3/4.

Kuinka kauan ”Automaattiset PR-katselmointikommentit”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä Tekoälyagentit-oppitunnilla?

Kyllä. Jokainen Tekoälyagentit-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. GitHub REST API:n yleiskatsaus
  2. Issuejen luettelointi ja hallinta
  3. Automaattiset PR-katselmointikommentit
  4. Commit-historian ja diffien analyysi
← Takaisin: Tekoälyagentit