Автоматические комментарии к проверке PR
Чтение различий PR и публикация встроенных комментариев проверки через API.
«Автоматические комментарии к проверке PR» — бесплатный урок AI Agents на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Agents, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Agents содержит 4 уроков всего.
Что такое автоматическая проверка PR
Агент автоматической проверки PR читает изменённые файлы, анализирует различия и публикует целевые комментарии к проверке — подобно человеку-проверяющему, но быстрее и стабильнее. Варианты применения: проверка безопасности, контроль стиля, обнаружение распространённых ошибок, проверка покрытия тестами или составление сводки о назначении 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}')Получение открытых PR
Используйте repo.get_pulls(), чтобы получить список PR. Фильтруйте их по состоянию (open, closed), базовой ветке или порядку сортировки. Обычно бот проверки обрабатывает открытые PR, которые ещё не проверялись.
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}')Получение изменённых файлов
pr.get_files() возвращает список объектов File, каждый из которых представляет файл, изменённый в PR. Основные свойства: filename, status (добавлен/изменён/удалён), additions, deletions, patch (текст объединённых различий).
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'
]Чтение изменений файла (различий)
file.patch — это строка с объединёнными различиями. Строки, начинающиеся с +, были добавлены, а строки, начинающиеся с -, удалены. Заголовок @@ показывает, с каких номеров строк начинается фрагмент. Проанализируйте его, чтобы точно определить, где находятся проблемы в новом коде.
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
pr.get_commits() возвращает коммиты PR. Каждый коммит содержит сообщение, автора и список изменённых файлов. Анализ сообщений коммитов помогает боту проверки составить сводку о назначении PR и оценить качество коммитов.
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
Используйте pr.create_review(), чтобы отправить официальную проверку. Параметр event определяет тип проверки: 'APPROVE', 'REQUEST_CHANGES' или 'COMMENT'. Проверка может содержать общий текст и список встроенных комментариев к конкретным строкам.
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'
)Публикация комментариев к строкам проверки
Встроенные комментарии отображаются непосредственно у конкретных строк в различиях. Каждый комментарий указывает path (путь к файлу), line (номер строки в новом файле) и body. Передайте список комментариев в 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
Передайте различия файла в LLM вместе с запросом на проверку безопасности и качества. Попросите вернуть структурированный ответ JSON со списком проблем, указав для каждой путь к файлу, номер строки, серьёзность и описание. Используйте эти данные для создания списка встроенных комментариев.
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
Объедините все компоненты в полный процесс проверки PR: получите файлы, оставьте только подходящие для проверки, проанализируйте каждый с помощью LLM, соберите комментарии и отправьте проверку одним вызовом API. Ограничьте обработку первыми 10 изменёнными файлами, чтобы контролировать расходы на 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')Проверка существующих проверок агента
Перед публикацией проверки убедитесь, что бот-агент уже не проверял этот PR в текущем состоянии. Избегайте дублирования: проверьте список существующих проверок и найдите имя пользователя Вашего бота.
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
Распространённый шаблон автоматизированной проверки — поиск небезопасных шаблонов в добавленных строках: секретов, записанных непосредственно в коде, устаревших функций, конкатенации SQL-строк или вызовов eval(). Проверьте каждую добавленную строку на соответствие набору шаблонов и отметьте найденные проблемы встроенными комментариями.
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)
Быстрая проверка: события проверки
Проверьте, насколько хорошо Вы понимаете возможности проверки PR.
Повторение: автоматизированная проверка PR
Теперь Ваш агент может программно проверять PR:
- repo.get_pull(number) — получить объект PR с метаданными
- pr.get_files() — перечислить изменённые файлы с полями
filename,patch,additions - pr.get_commits() — изучить сообщения коммитов и статистику
- pr.create_review(body, event, comments) — отправить проверку с необязательными встроенными комментариями
- Формат встроенного комментария:
{'path': filename, 'line': N, 'body': markdown} - Используйте
'COMMENT'для замечаний и'REQUEST_CHANGES'для блокирующих проблем - Перед повторной проверкой изучите уже существующие проверки, чтобы избежать дубликатов
- Ограничьте анализ 10 файлами и 25 встроенными комментариями, чтобы не превысить ограничения API
Часто задаваемые вопросы
Урок «Автоматические комментарии к проверке PR» бесплатный?
Да — полный текст урока «Автоматические комментарии к проверке PR» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Agents, подпишись на CoddyKit PRO. Курс AI Agents содержит 4 уроков всего.
Чему я научусь в уроке «Автоматические комментарии к проверке PR»?
Чтение различий PR и публикация встроенных комментариев проверки через API. Ты практикуешь AI Agents с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Agents?
Предыдущий опыт не требуется. AI Agents на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Автоматические комментарии к проверке PR»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Agents?
Да. Каждый урок AI Agents включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Обзор GitHub REST API
- Просмотр и управление задачами
- Автоматические комментарии к проверке PR
- История коммитов и анализ различий