Automatyczne komentarze w przeglądach PR
Odczytywanie diffów PR i publikowanie komentarzy inline w przeglądzie za pomocą API.
Automatyczne komentarze w przeglądach PR to bezpłatna lekcja AI Agents na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Agents, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Agents zawiera 4 lekcji w sumie.
Czym jest automatyczny przegląd PR
Agent do automatycznego przeglądu PR odczytuje zmienione pliki, analizuje różnice i publikuje ukierunkowane komentarze — podobnie jak ludzki recenzent, ale szybciej i w bardziej spójny sposób. Przykładowe zastosowania to skanowanie pod kątem bezpieczeństwa, egzekwowanie stylu, wykrywanie typowych błędów, sprawdzanie pokrycia testami oraz podsumowywanie zawartości 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}')Wyświetlanie otwartych pull requestów
Za pomocą repo.get_pulls() można wyświetlić listę PR-ów. Można je filtrować według stanu (open, closed), gałęzi docelowej lub kolejności sortowania. W przypadku bota do przeglądów zazwyczaj przetwarza się otwarte PR-y, które nie zostały jeszcze zrecenzowane.
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}')Pobieranie zmienionych plików
pr.get_files() zwraca listę obiektów File — każdy reprezentuje plik zmieniony w ramach PR-a. Najważniejsze właściwości to: filename, status (dodany/zmodyfikowany/usunięty), additions, deletions oraz patch (tekst różnicy w formacie unified diff).
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'
]Odczytywanie poprawki pliku (różnicy)
file.patch to ciąg znaków zawierający różnicę w formacie unified diff. Wiersze zaczynające się od + zostały dodane, a wiersze zaczynające się od - usunięte. Nagłówek @@ wskazuje numery wierszy, od których zaczyna się dany fragment. Analizując te dane, można dokładnie określić miejsca występowania problemów w nowym kodzie.
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)
Pobieranie commitów PR-a
pr.get_commits() zwraca commity należące do PR-a. Każdy commit zawiera komunikat, autora i listę zmienionych plików. Analiza komunikatów commitów pomaga botowi do przeglądów podsumować zawartość PR-a i sprawdzić, czy commity są poprawnie przygotowane.
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')Publikowanie przeglądu PR-a
Za pomocą pr.create_review() można przesłać formalny przegląd. Parametr event określa jego typ: 'APPROVE', 'REQUEST_CHANGES' lub 'COMMENT'. Przegląd może zawierać ogólną treść oraz listę komentarzy kontekstowych dotyczących konkretnych wierszy.
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'
)Publikowanie komentarzy do konkretnych wierszy
Komentarze kontekstowe pojawiają się bezpośrednio przy konkretnych wierszach różnicy. Każdy komentarz określa path (ścieżkę pliku), line (numer wiersza w nowym pliku) oraz body. Listę komentarzy należy przekazać do 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')Analiza kodu z użyciem LLM
Różnicę pliku należy przekazać do LLM wraz z poleceniem dotyczącym przeglądu bezpieczeństwa i jakości. Należy poprosić o uporządkowaną odpowiedź JSON zawierającą listę problemów wraz ze ścieżką pliku, numerem wiersza, poziomem ważności i opisem. Na tej podstawie można utworzyć listę komentarzy kontekstowych.
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 []Pełny przebieg działania agenta do przeglądu PR
Wszystkie elementy należy połączyć w kompletny proces przeglądu PR-a: pobrać pliki, ograniczyć je do tych podlegających przeglądowi, przeanalizować każdy z użyciem LLM, zebrać komentarze i przesłać przegląd w jednym wywołaniu API. Aby kontrolować koszty LLM, należy ograniczyć analizę do pierwszych 10 zmienionych plików.
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')Sprawdzanie istniejących przeglądów agenta
Przed opublikowaniem przeglądu należy sprawdzić, czy bot agenta nie zrecenzował już tego PR-a w jego bieżącym stanie. Aby uniknąć duplikowania przeglądów, należy sprawdzić na liście istniejących przeglądów nazwę użytkownika bota.
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'))
Wzorzec skanowania bezpieczeństwa podczas przeglądu PR
Typowym wzorcem automatycznego przeglądu jest skanowanie dodanych wierszy pod kątem antywzorców związanych z bezpieczeństwem: sekretów zapisanych na stałe, użycia przestarzałych funkcji, konkatenacji ciągów znaków w zapytaniach SQL lub wywołań eval(). Należy sprawdzić każdy dodany wiersz pod kątem zestawu wzorców i oznaczyć wykryte problemy komentarzami 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)
Szybki test: zdarzenia przeglądu
Proszę sprawdzić swoją wiedzę na temat opcji przeglądu PR.
Podsumowanie automatycznego przeglądu PR
Agent może teraz programistycznie przeglądać pull requesty:
- repo.get_pull(number) — pobieranie obiektu PR z metadanymi
- pr.get_files() — wyświetlanie listy zmienionych plików z właściwościami
filename,patch,additions - pr.get_commits() — sprawdzanie komunikatów commitów i statystyk
- pr.create_review(body, event, comments) — przesyłanie przeglądu z opcjonalnymi komentarzami inline
- Format komentarza inline:
{'path': filename, 'line': N, 'body': markdown} - Użyj
'COMMENT'w przypadku spostrzeżeń i'REQUEST_CHANGES'w przypadku problemów blokujących - Przed ponownym przeglądem sprawdź istniejące przeglądy, aby uniknąć duplikatów
- Ogranicz analizę plików do 10 plików i 25 komentarzy inline, aby zmieścić się w limitach API
Ucz się AI Agents dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 60
- Lekcje
- 239
Często zadawane pytania
Czy lekcja „Automatyczne komentarze w przeglądach PR” jest bezpłatna?
Tak — pełny tekst „Automatyczne komentarze w przeglądach PR” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Agents, przejdź na CoddyKit PRO. Kurs AI Agents zawiera 4 lekcji w sumie.
Co nauczysz się w „Automatyczne komentarze w przeglądach PR”?
Odczytywanie diffów PR i publikowanie komentarzy inline w przeglądzie za pomocą API. Ćwiczysz AI Agents z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AI Agents?
Nie wymagamy żadnego doświadczenia. AI Agents w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Automatyczne komentarze w przeglądach PR”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AI Agents?
Tak. Każda lekcja AI Agents zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Przegląd GitHub REST API
- Listowanie i zarządzanie zgłoszeniami
- Automatyczne komentarze w przeglądach PR
- Historia commitów i analiza diffów