@papi-ai/skills 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/bin/install.mjs +104 -0
- package/lib/manifest.d.ts +33 -0
- package/lib/manifest.mjs +102 -0
- package/manifest.json +52 -0
- package/package.json +47 -0
- package/skills/check-mcp/SKILL.md +40 -0
- package/skills/deployment-completeness-audit/SKILL.md +255 -0
- package/skills/papi-advanced/SKILL.md +28 -0
- package/skills/papi-build/SKILL.md +52 -0
- package/skills/papi-idea/SKILL.md +37 -0
- package/skills/papi-plan/SKILL.md +164 -0
- package/skills/papi-strategy/SKILL.md +28 -0
- package/skills/playwright-skill/API_REFERENCE.md +653 -0
- package/skills/playwright-skill/EXAMPLES.md +166 -0
- package/skills/playwright-skill/SKILL.md +147 -0
- package/skills/playwright-skill/lib/helpers.js +441 -0
- package/skills/playwright-skill/package.json +26 -0
- package/skills/playwright-skill/run.js +228 -0
- package/skills/pr-reviewer/SKILL.md +443 -0
- package/skills/pr-reviewer/references/gh_cli_guide.md +368 -0
- package/skills/pr-reviewer/references/review_criteria.md +345 -0
- package/skills/pr-reviewer/references/scenarios.md +71 -0
- package/skills/pr-reviewer/references/troubleshooting.md +55 -0
- package/skills/pr-reviewer/scripts/add_inline_comment.py +163 -0
- package/skills/pr-reviewer/scripts/fetch_pr_data.py +327 -0
- package/skills/pr-reviewer/scripts/generate_review_files.py +480 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generate structured review files from PR analysis.
|
|
4
|
+
|
|
5
|
+
Creates three review files:
|
|
6
|
+
- pr/review.md: Detailed review for internal use
|
|
7
|
+
- pr/human.md: Short, clean review for posting (no emojis, em-dashes, line numbers)
|
|
8
|
+
- pr/inline.md: List of inline comments with code snippets
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python generate_review_files.py <pr_review_dir> --findings <findings_json>
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
python generate_review_files.py /tmp/PRs/myrepo/123 --findings findings.json
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Dict, List, Any
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_pr_directory(pr_review_dir: Path) -> Path:
|
|
26
|
+
"""Create the pr/ subdirectory for review files."""
|
|
27
|
+
pr_dir = pr_review_dir / "pr"
|
|
28
|
+
pr_dir.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
return pr_dir
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_findings(findings_file: str) -> Dict[str, Any]:
|
|
33
|
+
"""
|
|
34
|
+
Load review findings from JSON file.
|
|
35
|
+
|
|
36
|
+
Expected structure:
|
|
37
|
+
{
|
|
38
|
+
"summary": "Overall assessment...",
|
|
39
|
+
"blockers": [{
|
|
40
|
+
"category": "Security",
|
|
41
|
+
"issue": "SQL injection vulnerability",
|
|
42
|
+
"file": "src/db/queries.py",
|
|
43
|
+
"line": 45,
|
|
44
|
+
"details": "Using string concatenation...",
|
|
45
|
+
"fix": "Use parameterized queries",
|
|
46
|
+
"code_snippet": "result = db.execute(...)"
|
|
47
|
+
}],
|
|
48
|
+
"important": [...],
|
|
49
|
+
"nits": [...],
|
|
50
|
+
"suggestions": [...],
|
|
51
|
+
"questions": [...],
|
|
52
|
+
"praise": [...],
|
|
53
|
+
"inline_comments": [{
|
|
54
|
+
"file": "src/app.py",
|
|
55
|
+
"line": 42,
|
|
56
|
+
"comment": "Consider edge case handling",
|
|
57
|
+
"code_snippet": "def process(data):\n return data.strip()",
|
|
58
|
+
"start_line": 41,
|
|
59
|
+
"end_line": 43
|
|
60
|
+
}]
|
|
61
|
+
}
|
|
62
|
+
"""
|
|
63
|
+
with open(findings_file, 'r') as f:
|
|
64
|
+
return json.load(f)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def generate_detailed_review(findings: Dict[str, Any], metadata: Dict[str, Any]) -> str:
|
|
68
|
+
"""Generate detailed review.md with full analysis."""
|
|
69
|
+
|
|
70
|
+
review = f"""# Pull Request Review - Detailed Analysis
|
|
71
|
+
|
|
72
|
+
## PR Information
|
|
73
|
+
|
|
74
|
+
**Repository**: {metadata.get('repository', 'N/A')}
|
|
75
|
+
**PR Number**: #{metadata.get('number', 'N/A')}
|
|
76
|
+
**Title**: {metadata.get('title', 'N/A')}
|
|
77
|
+
**Author**: {metadata.get('author', 'N/A')}
|
|
78
|
+
**Branch**: {metadata.get('head_branch', 'N/A')} → {metadata.get('base_branch', 'N/A')}
|
|
79
|
+
|
|
80
|
+
## Summary
|
|
81
|
+
|
|
82
|
+
{findings.get('summary', 'No summary provided')}
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
# Add blockers
|
|
87
|
+
blockers = findings.get('blockers', [])
|
|
88
|
+
if blockers:
|
|
89
|
+
review += "## 🔴 Critical Issues (Blockers)\n\n"
|
|
90
|
+
review += "**These MUST be fixed before merging.**\n\n"
|
|
91
|
+
for i, blocker in enumerate(blockers, 1):
|
|
92
|
+
review += f"### {i}. {blocker.get('category', 'Issue')}: {blocker.get('issue', 'Unknown')}\n\n"
|
|
93
|
+
if blocker.get('file'):
|
|
94
|
+
review += f"**File**: `{blocker['file']}"
|
|
95
|
+
if blocker.get('line'):
|
|
96
|
+
review += f":{blocker['line']}"
|
|
97
|
+
review += "`\n\n"
|
|
98
|
+
review += f"**Problem**: {blocker.get('details', 'No details')}\n\n"
|
|
99
|
+
if blocker.get('fix'):
|
|
100
|
+
review += f"**Solution**: {blocker['fix']}\n\n"
|
|
101
|
+
if blocker.get('code_snippet'):
|
|
102
|
+
review += f"**Current Code**:\n```\n{blocker['code_snippet']}\n```\n\n"
|
|
103
|
+
review += "---\n\n"
|
|
104
|
+
|
|
105
|
+
# Add important issues
|
|
106
|
+
important = findings.get('important', [])
|
|
107
|
+
if important:
|
|
108
|
+
review += "## 🟡 Important Issues\n\n"
|
|
109
|
+
review += "**Should be addressed before merging.**\n\n"
|
|
110
|
+
for i, issue in enumerate(important, 1):
|
|
111
|
+
review += f"### {i}. {issue.get('category', 'Issue')}: {issue.get('issue', 'Unknown')}\n\n"
|
|
112
|
+
if issue.get('file'):
|
|
113
|
+
review += f"**File**: `{issue['file']}"
|
|
114
|
+
if issue.get('line'):
|
|
115
|
+
review += f":{issue['line']}"
|
|
116
|
+
review += "`\n\n"
|
|
117
|
+
review += f"**Impact**: {issue.get('details', 'No details')}\n\n"
|
|
118
|
+
if issue.get('fix'):
|
|
119
|
+
review += f"**Suggestion**: {issue['fix']}\n\n"
|
|
120
|
+
if issue.get('code_snippet'):
|
|
121
|
+
review += f"**Code**:\n```\n{issue['code_snippet']}\n```\n\n"
|
|
122
|
+
review += "---\n\n"
|
|
123
|
+
|
|
124
|
+
# Add nits
|
|
125
|
+
nits = findings.get('nits', [])
|
|
126
|
+
if nits:
|
|
127
|
+
review += "## 🟢 Minor Issues (Nits)\n\n"
|
|
128
|
+
review += "**Nice to have, but not blocking.**\n\n"
|
|
129
|
+
for i, nit in enumerate(nits, 1):
|
|
130
|
+
review += f"{i}. **{nit.get('category', 'Style')}**: {nit.get('issue', 'Unknown')}\n"
|
|
131
|
+
if nit.get('file'):
|
|
132
|
+
review += f" - File: `{nit['file']}`\n"
|
|
133
|
+
if nit.get('details'):
|
|
134
|
+
review += f" - {nit['details']}\n"
|
|
135
|
+
review += "\n"
|
|
136
|
+
|
|
137
|
+
# Add suggestions
|
|
138
|
+
suggestions = findings.get('suggestions', [])
|
|
139
|
+
if suggestions:
|
|
140
|
+
review += "## 💡 Suggestions for Future\n\n"
|
|
141
|
+
for i, suggestion in enumerate(suggestions, 1):
|
|
142
|
+
review += f"{i}. {suggestion}\n"
|
|
143
|
+
review += "\n"
|
|
144
|
+
|
|
145
|
+
# Add questions
|
|
146
|
+
questions = findings.get('questions', [])
|
|
147
|
+
if questions:
|
|
148
|
+
review += "## ❓ Questions / Clarifications Needed\n\n"
|
|
149
|
+
for i, question in enumerate(questions, 1):
|
|
150
|
+
review += f"{i}. {question}\n"
|
|
151
|
+
review += "\n"
|
|
152
|
+
|
|
153
|
+
# Add praise
|
|
154
|
+
praise = findings.get('praise', [])
|
|
155
|
+
if praise:
|
|
156
|
+
review += "## ✅ Positive Notes\n\n"
|
|
157
|
+
for item in praise:
|
|
158
|
+
review += f"- {item}\n"
|
|
159
|
+
review += "\n"
|
|
160
|
+
|
|
161
|
+
# Add overall recommendation
|
|
162
|
+
review += "## Overall Recommendation\n\n"
|
|
163
|
+
if blockers:
|
|
164
|
+
review += "**Request Changes** - Critical issues must be addressed.\n"
|
|
165
|
+
elif important:
|
|
166
|
+
review += "**Request Changes** - Important issues should be fixed.\n"
|
|
167
|
+
else:
|
|
168
|
+
review += "**Approve** - Looks good! Minor nits can be addressed optionally.\n"
|
|
169
|
+
|
|
170
|
+
return review
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def generate_human_review(findings: Dict[str, Any], metadata: Dict[str, Any]) -> str:
|
|
174
|
+
"""
|
|
175
|
+
Generate short, clean human.md for posting.
|
|
176
|
+
|
|
177
|
+
Rules:
|
|
178
|
+
- No emojis
|
|
179
|
+
- No em dashes (use regular hyphens)
|
|
180
|
+
- No code line numbers
|
|
181
|
+
- Concise and professional
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def clean_text(text: str) -> str:
|
|
185
|
+
"""Remove em-dashes and replace with regular hyphens."""
|
|
186
|
+
if not text:
|
|
187
|
+
return text
|
|
188
|
+
# Replace em dash (—) with regular hyphen (-)
|
|
189
|
+
# Also replace en dash (–) with regular hyphen
|
|
190
|
+
return text.replace('—', '-').replace('–', '-')
|
|
191
|
+
|
|
192
|
+
title = clean_text(metadata.get('title', 'N/A'))
|
|
193
|
+
summary = clean_text(findings.get('summary', 'No summary provided'))
|
|
194
|
+
|
|
195
|
+
review = f"""# Code Review
|
|
196
|
+
|
|
197
|
+
**PR #{metadata.get('number', 'N/A')}**: {title}
|
|
198
|
+
|
|
199
|
+
## Summary
|
|
200
|
+
|
|
201
|
+
{summary}
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
# Add blockers - no emojis
|
|
206
|
+
blockers = findings.get('blockers', [])
|
|
207
|
+
if blockers:
|
|
208
|
+
review += "## Critical Issues - Must Fix\n\n"
|
|
209
|
+
for i, blocker in enumerate(blockers, 1):
|
|
210
|
+
# No emojis, no em dashes, no line numbers
|
|
211
|
+
issue = clean_text(blocker.get('issue', 'Issue'))
|
|
212
|
+
details = clean_text(blocker.get('details', 'No details'))
|
|
213
|
+
fix = clean_text(blocker.get('fix', ''))
|
|
214
|
+
|
|
215
|
+
review += f"{i}. **{issue}**\n"
|
|
216
|
+
if blocker.get('file'):
|
|
217
|
+
# File path without line number
|
|
218
|
+
review += f" - File: `{blocker['file']}`\n"
|
|
219
|
+
review += f" - {details}\n"
|
|
220
|
+
if fix:
|
|
221
|
+
review += f" - Fix: {fix}\n"
|
|
222
|
+
review += "\n"
|
|
223
|
+
|
|
224
|
+
# Add important issues
|
|
225
|
+
important = findings.get('important', [])
|
|
226
|
+
if important:
|
|
227
|
+
review += "## Important Issues - Should Fix\n\n"
|
|
228
|
+
for i, issue_item in enumerate(important, 1):
|
|
229
|
+
issue = clean_text(issue_item.get('issue', 'Issue'))
|
|
230
|
+
details = clean_text(issue_item.get('details', 'No details'))
|
|
231
|
+
fix = clean_text(issue_item.get('fix', ''))
|
|
232
|
+
|
|
233
|
+
review += f"{i}. **{issue}**\n"
|
|
234
|
+
if issue_item.get('file'):
|
|
235
|
+
review += f" - File: `{issue_item['file']}`\n"
|
|
236
|
+
review += f" - {details}\n"
|
|
237
|
+
if fix:
|
|
238
|
+
review += f" - Suggestion: {fix}\n"
|
|
239
|
+
review += "\n"
|
|
240
|
+
|
|
241
|
+
# Add nits - keep brief
|
|
242
|
+
nits = findings.get('nits', [])
|
|
243
|
+
if nits and len(nits) <= 3: # Only include if few
|
|
244
|
+
review += "## Minor Issues\n\n"
|
|
245
|
+
for i, nit in enumerate(nits, 1):
|
|
246
|
+
issue = clean_text(nit.get('issue', 'Issue'))
|
|
247
|
+
review += f"{i}. {issue}"
|
|
248
|
+
if nit.get('file'):
|
|
249
|
+
review += f" in `{nit['file']}`"
|
|
250
|
+
review += "\n"
|
|
251
|
+
review += "\n"
|
|
252
|
+
|
|
253
|
+
# Add praise
|
|
254
|
+
praise = findings.get('praise', [])
|
|
255
|
+
if praise:
|
|
256
|
+
review += "## Positive Notes\n\n"
|
|
257
|
+
for item in praise:
|
|
258
|
+
clean_item = clean_text(item)
|
|
259
|
+
review += f"- {clean_item}\n"
|
|
260
|
+
review += "\n"
|
|
261
|
+
|
|
262
|
+
# Add overall recommendation - no emojis
|
|
263
|
+
if blockers:
|
|
264
|
+
review += "## Recommendation\n\nRequest changes - critical issues need to be addressed before merging.\n"
|
|
265
|
+
elif important:
|
|
266
|
+
review += "## Recommendation\n\nRequest changes - please address the important issues listed above.\n"
|
|
267
|
+
else:
|
|
268
|
+
review += "## Recommendation\n\nApprove - the code looks good. Minor items can be addressed optionally.\n"
|
|
269
|
+
|
|
270
|
+
return review
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def generate_inline_comments_file(findings: Dict[str, Any]) -> str:
|
|
274
|
+
"""
|
|
275
|
+
Generate inline.md with list of proposed inline comments.
|
|
276
|
+
|
|
277
|
+
Includes code snippets with line number headers.
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
inline_comments = findings.get('inline_comments', [])
|
|
281
|
+
|
|
282
|
+
if not inline_comments:
|
|
283
|
+
return "# Inline Comments\n\nNo inline comments proposed.\n"
|
|
284
|
+
|
|
285
|
+
content = "# Proposed Inline Comments\n\n"
|
|
286
|
+
content += f"**Total Comments**: {len(inline_comments)}\n\n"
|
|
287
|
+
content += "Review these before posting. Edit as needed.\n\n"
|
|
288
|
+
content += "---\n\n"
|
|
289
|
+
|
|
290
|
+
for i, comment in enumerate(inline_comments, 1):
|
|
291
|
+
content += f"## Comment {i}\n\n"
|
|
292
|
+
content += f"**File**: `{comment.get('file', 'unknown')}`\n"
|
|
293
|
+
content += f"**Line**: {comment.get('line', 'N/A')}\n"
|
|
294
|
+
|
|
295
|
+
if comment.get('start_line') and comment.get('end_line'):
|
|
296
|
+
content += f"**Range**: Lines {comment['start_line']}-{comment['end_line']}\n"
|
|
297
|
+
|
|
298
|
+
content += f"\n**Comment**:\n{comment.get('comment', 'No comment')}\n\n"
|
|
299
|
+
|
|
300
|
+
if comment.get('code_snippet'):
|
|
301
|
+
# Add line numbers in header
|
|
302
|
+
start = comment.get('start_line', comment.get('line', 1))
|
|
303
|
+
end = comment.get('end_line', comment.get('line', 1))
|
|
304
|
+
|
|
305
|
+
if start == end:
|
|
306
|
+
content += f"**Code (Line {start})**:\n"
|
|
307
|
+
else:
|
|
308
|
+
content += f"**Code (Lines {start}-{end})**:\n"
|
|
309
|
+
|
|
310
|
+
content += f"```\n{comment['code_snippet']}\n```\n\n"
|
|
311
|
+
|
|
312
|
+
# Add command to post this comment
|
|
313
|
+
owner = comment.get('owner', 'OWNER')
|
|
314
|
+
repo = comment.get('repo', 'REPO')
|
|
315
|
+
pr_num = comment.get('pr_number', 'PR_NUM')
|
|
316
|
+
|
|
317
|
+
content += "**Command to post**:\n```bash\n"
|
|
318
|
+
content += f"python scripts/add_inline_comment.py {owner} {repo} {pr_num} latest \\\n"
|
|
319
|
+
content += f" \"{comment.get('file', 'file.py')}\" {comment.get('line', 42)} \\\n"
|
|
320
|
+
content += f" \"{comment.get('comment', 'comment')}\"\n"
|
|
321
|
+
content += "```\n\n"
|
|
322
|
+
content += "---\n\n"
|
|
323
|
+
|
|
324
|
+
return content
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def generate_claude_commands(pr_review_dir: Path, metadata: Dict[str, Any]):
|
|
328
|
+
"""Generate .claude directory with custom slash commands."""
|
|
329
|
+
|
|
330
|
+
claude_dir = pr_review_dir / ".claude" / "commands"
|
|
331
|
+
claude_dir.mkdir(parents=True, exist_ok=True)
|
|
332
|
+
|
|
333
|
+
owner = metadata.get('owner', 'owner')
|
|
334
|
+
repo = metadata.get('repo', 'repo')
|
|
335
|
+
pr_number = metadata.get('number', '123')
|
|
336
|
+
|
|
337
|
+
# /send command - approve and post human.md
|
|
338
|
+
send_cmd = f"""Post the human-friendly review and approve the PR.
|
|
339
|
+
|
|
340
|
+
Steps:
|
|
341
|
+
1. Read the file `pr/human.md` in the current directory
|
|
342
|
+
2. Post the review content as a PR comment using:
|
|
343
|
+
`gh pr comment {pr_number} --repo {owner}/{repo} --body-file pr/human.md`
|
|
344
|
+
3. Approve the PR using:
|
|
345
|
+
`gh pr review {pr_number} --repo {owner}/{repo} --approve`
|
|
346
|
+
4. Confirm to the user that the review was posted and PR was approved
|
|
347
|
+
"""
|
|
348
|
+
|
|
349
|
+
with open(claude_dir / "send.md", 'w') as f:
|
|
350
|
+
f.write(send_cmd)
|
|
351
|
+
|
|
352
|
+
# /send-decline command - request changes and post human.md
|
|
353
|
+
send_decline_cmd = f"""Post the human-friendly review and request changes on the PR.
|
|
354
|
+
|
|
355
|
+
Steps:
|
|
356
|
+
1. Read the file `pr/human.md` in the current directory
|
|
357
|
+
2. Post the review content as a PR comment using:
|
|
358
|
+
`gh pr comment {pr_number} --repo {owner}/{repo} --body-file pr/human.md`
|
|
359
|
+
3. Request changes on the PR using:
|
|
360
|
+
`gh pr review {pr_number} --repo {owner}/{repo} --request-changes`
|
|
361
|
+
4. Confirm to the user that the review was posted and changes were requested
|
|
362
|
+
"""
|
|
363
|
+
|
|
364
|
+
with open(claude_dir / "send-decline.md", 'w') as f:
|
|
365
|
+
f.write(send_decline_cmd)
|
|
366
|
+
|
|
367
|
+
# /show command - open in VS Code
|
|
368
|
+
show_cmd = f"""Open the PR review directory in VS Code for editing.
|
|
369
|
+
|
|
370
|
+
Steps:
|
|
371
|
+
1. Run `code .` to open the current directory in VS Code
|
|
372
|
+
2. Tell the user they can now edit the review files:
|
|
373
|
+
- pr/review.md (detailed review)
|
|
374
|
+
- pr/human.md (short review for posting)
|
|
375
|
+
- pr/inline.md (inline comments)
|
|
376
|
+
3. Remind them to use /send or /send-decline when ready to post
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
with open(claude_dir / "show.md", 'w') as f:
|
|
380
|
+
f.write(show_cmd)
|
|
381
|
+
|
|
382
|
+
print(f"✅ Created slash commands in {claude_dir}")
|
|
383
|
+
print(" - /send (approve and post)")
|
|
384
|
+
print(" - /send-decline (request changes and post)")
|
|
385
|
+
print(" - /show (open in VS Code)")
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def main():
|
|
389
|
+
parser = argparse.ArgumentParser(
|
|
390
|
+
description='Generate structured review files from PR analysis',
|
|
391
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
392
|
+
epilog=__doc__
|
|
393
|
+
)
|
|
394
|
+
parser.add_argument('pr_review_dir', help='PR review directory path')
|
|
395
|
+
parser.add_argument('--findings', required=True, help='JSON file with review findings')
|
|
396
|
+
parser.add_argument('--metadata', help='JSON file with PR metadata (optional)')
|
|
397
|
+
|
|
398
|
+
args = parser.parse_args()
|
|
399
|
+
|
|
400
|
+
try:
|
|
401
|
+
# Load findings
|
|
402
|
+
findings = load_findings(args.findings)
|
|
403
|
+
|
|
404
|
+
# Load metadata if provided
|
|
405
|
+
metadata = {}
|
|
406
|
+
if args.metadata and os.path.exists(args.metadata):
|
|
407
|
+
with open(args.metadata, 'r') as f:
|
|
408
|
+
metadata = json.load(f)
|
|
409
|
+
|
|
410
|
+
# Extract metadata from findings if not provided
|
|
411
|
+
if not metadata:
|
|
412
|
+
metadata = findings.get('metadata', {})
|
|
413
|
+
|
|
414
|
+
# Create pr directory
|
|
415
|
+
pr_review_dir = Path(args.pr_review_dir)
|
|
416
|
+
pr_dir = create_pr_directory(pr_review_dir)
|
|
417
|
+
|
|
418
|
+
print(f"📝 Generating review files in {pr_dir}...")
|
|
419
|
+
|
|
420
|
+
# Generate detailed review
|
|
421
|
+
detailed_review = generate_detailed_review(findings, metadata)
|
|
422
|
+
review_file = pr_dir / "review.md"
|
|
423
|
+
with open(review_file, 'w') as f:
|
|
424
|
+
f.write(detailed_review)
|
|
425
|
+
print(f"✅ Created detailed review: {review_file}")
|
|
426
|
+
|
|
427
|
+
# Generate human-friendly review
|
|
428
|
+
human_review = generate_human_review(findings, metadata)
|
|
429
|
+
human_file = pr_dir / "human.md"
|
|
430
|
+
with open(human_file, 'w') as f:
|
|
431
|
+
f.write(human_review)
|
|
432
|
+
print(f"✅ Created human review: {human_file}")
|
|
433
|
+
|
|
434
|
+
# Generate inline comments file
|
|
435
|
+
inline_comments = generate_inline_comments_file(findings)
|
|
436
|
+
inline_file = pr_dir / "inline.md"
|
|
437
|
+
with open(inline_file, 'w') as f:
|
|
438
|
+
f.write(inline_comments)
|
|
439
|
+
print(f"✅ Created inline comments: {inline_file}")
|
|
440
|
+
|
|
441
|
+
# Generate Claude slash commands
|
|
442
|
+
generate_claude_commands(pr_review_dir, metadata)
|
|
443
|
+
|
|
444
|
+
# Create summary file
|
|
445
|
+
summary = f"""PR Review Files Generated
|
|
446
|
+
========================
|
|
447
|
+
|
|
448
|
+
Directory: {pr_review_dir}
|
|
449
|
+
|
|
450
|
+
Files created:
|
|
451
|
+
- pr/review.md - Detailed analysis for your review
|
|
452
|
+
- pr/human.md - Clean version for posting (no emojis, no line numbers)
|
|
453
|
+
- pr/inline.md - Proposed inline comments with code snippets
|
|
454
|
+
|
|
455
|
+
Slash commands available:
|
|
456
|
+
- /send - Post human.md and approve PR
|
|
457
|
+
- /send-decline - Post human.md and request changes
|
|
458
|
+
- /show - Open directory in VS Code
|
|
459
|
+
|
|
460
|
+
Next steps:
|
|
461
|
+
1. Review the files (use /show to open in VS Code)
|
|
462
|
+
2. Edit as needed
|
|
463
|
+
3. Use /send or /send-decline when ready to post
|
|
464
|
+
|
|
465
|
+
IMPORTANT: Nothing will be posted until you run /send or /send-decline
|
|
466
|
+
"""
|
|
467
|
+
|
|
468
|
+
summary_file = pr_review_dir / "REVIEW_READY.txt"
|
|
469
|
+
with open(summary_file, 'w') as f:
|
|
470
|
+
f.write(summary)
|
|
471
|
+
|
|
472
|
+
print(f"\n{summary}")
|
|
473
|
+
|
|
474
|
+
except Exception as e:
|
|
475
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
476
|
+
sys.exit(1)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
if __name__ == '__main__':
|
|
480
|
+
main()
|