@ranger1/dx 0.1.34 → 0.1.36
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/@opencode/agents/__pycache__/pr_review_aggregate.cpython-314.pyc +0 -0
- package/@opencode/agents/claude-reviewer.md +3 -2
- package/@opencode/agents/codex-reviewer.md +3 -2
- package/@opencode/agents/gemini-reviewer.md +3 -2
- package/@opencode/agents/pr-fix.md +4 -3
- package/@opencode/agents/pr-review-aggregate.md +7 -7
- package/@opencode/agents/pr_review_aggregate.py +95 -3
- package/@opencode/commands/pr-review-loop.md +58 -6
- package/package.json +1 -1
|
Binary file
|
|
@@ -14,20 +14,21 @@ tools:
|
|
|
14
14
|
|
|
15
15
|
- `PR #<number>`
|
|
16
16
|
- `round: <number>`
|
|
17
|
+
- `runId: <string>`(必须透传,禁止自行生成)
|
|
17
18
|
- `contextFile: <filename>`
|
|
18
19
|
|
|
19
20
|
## 输出(强制)
|
|
20
21
|
|
|
21
22
|
只输出一行:
|
|
22
23
|
|
|
23
|
-
`reviewFile:
|
|
24
|
+
`reviewFile: ./.cache/<file>.md`
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
## 规则
|
|
27
28
|
|
|
28
29
|
- 默认已在 PR head 分支(可直接读工作区代码)
|
|
29
30
|
- 可用 `git`/`gh` 只读命令获取 diff/上下文
|
|
30
|
-
- 写入 reviewFile
|
|
31
|
+
- 写入 reviewFile:`./.cache/review-CLD-pr<PR_NUMBER>-r<ROUND>-<RUN_ID>.md`
|
|
31
32
|
- findings id 必须以 `CLD-` 开头
|
|
32
33
|
|
|
33
34
|
## Cache 约定(强制)
|
|
@@ -15,20 +15,21 @@ tools:
|
|
|
15
15
|
|
|
16
16
|
- `PR #<number>`
|
|
17
17
|
- `round: <number>`
|
|
18
|
+
- `runId: <string>`(必须透传,禁止自行生成)
|
|
18
19
|
- `contextFile: <filename>`
|
|
19
20
|
|
|
20
21
|
## 输出(强制)
|
|
21
22
|
|
|
22
23
|
只输出一行:
|
|
23
24
|
|
|
24
|
-
`reviewFile:
|
|
25
|
+
`reviewFile: ./.cache/<file>.md`
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
## 规则
|
|
28
29
|
|
|
29
30
|
- 默认已在 PR head 分支(可直接读工作区代码)
|
|
30
31
|
- 可用 `git`/`gh` 只读命令获取 diff/上下文
|
|
31
|
-
- 写入 reviewFile
|
|
32
|
+
- 写入 reviewFile:`./.cache/review-CDX-pr<PR_NUMBER>-r<ROUND>-<RUN_ID>.md`
|
|
32
33
|
- findings id 必须以 `CDX-` 开头
|
|
33
34
|
|
|
34
35
|
## Cache 约定(强制)
|
|
@@ -14,20 +14,21 @@ tools:
|
|
|
14
14
|
|
|
15
15
|
- `PR #<number>`
|
|
16
16
|
- `round: <number>`
|
|
17
|
+
- `runId: <string>`(必须透传,禁止自行生成)
|
|
17
18
|
- `contextFile: <filename>`
|
|
18
19
|
|
|
19
20
|
## 输出(强制)
|
|
20
21
|
|
|
21
22
|
只输出一行:
|
|
22
23
|
|
|
23
|
-
`reviewFile:
|
|
24
|
+
`reviewFile: ./.cache/<file>.md`
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
## 规则
|
|
27
28
|
|
|
28
29
|
- 默认已在 PR head 分支(可直接读工作区代码)
|
|
29
30
|
- 可用 `git`/`gh` 只读命令获取 diff/上下文
|
|
30
|
-
- 写入 reviewFile
|
|
31
|
+
- 写入 reviewFile:`./.cache/review-GMN-pr<PR_NUMBER>-r<ROUND>-<RUN_ID>.md`
|
|
31
32
|
- findings id 必须以 `GMN-` 开头
|
|
32
33
|
|
|
33
34
|
## Cache 约定(强制)
|
|
@@ -32,6 +32,7 @@ tools:
|
|
|
32
32
|
### 必需输入
|
|
33
33
|
|
|
34
34
|
- **PR 编号**:调用者必须在 prompt 中明确提供(如:`请修复 PR #123`)
|
|
35
|
+
- **runId**:调用者必须在 prompt 中提供(必须透传,禁止自行生成)
|
|
35
36
|
- **fixFile**:调用者必须在 prompt 中提供问题清单文件路径(repo 相对路径,例:`./.cache/fix-...md`)(Structured Handoff)
|
|
36
37
|
|
|
37
38
|
### 失败快速退出
|
|
@@ -126,11 +127,11 @@ Round: 2
|
|
|
126
127
|
|
|
127
128
|
## 输出(强制)
|
|
128
129
|
|
|
129
|
-
|
|
130
|
+
写入:`./.cache/fix-report-pr<PR_NUMBER>-r<ROUND>-<RUN_ID>.md`
|
|
130
131
|
|
|
131
132
|
最终只输出一行:
|
|
132
133
|
|
|
133
|
-
`fixReportFile:
|
|
134
|
+
`fixReportFile: ./.cache/<file>.md`
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
## fixReportFile 内容格式(强制)
|
|
@@ -172,4 +173,4 @@ Rejected: <n>
|
|
|
172
173
|
## 输出有效性保证
|
|
173
174
|
|
|
174
175
|
- fixReportFile 必须成功写入
|
|
175
|
-
- stdout 只能输出一行 `fixReportFile:
|
|
176
|
+
- stdout 只能输出一行 `fixReportFile: ./.cache/<file>.md`
|
|
@@ -20,15 +20,15 @@ tools:
|
|
|
20
20
|
- `PR #<number>`
|
|
21
21
|
- `round: <number>`
|
|
22
22
|
- `runId: <string>`
|
|
23
|
-
- `contextFile: <
|
|
24
|
-
- `reviewFile: <
|
|
23
|
+
- `contextFile: <path>`(例如:`./.cache/pr-context-...md`)
|
|
24
|
+
- `reviewFile: <path>`(三行,分别对应 CDX/CLD/GMN,例如:`./.cache/review-...md`)
|
|
25
25
|
|
|
26
26
|
### 模式 B:发布修复评论(基于 fixReportFile)
|
|
27
27
|
|
|
28
28
|
- `PR #<number>`
|
|
29
29
|
- `round: <number>`
|
|
30
30
|
- `runId: <string>`
|
|
31
|
-
- `fixReportFile: <
|
|
31
|
+
- `fixReportFile: <path>`(例如:`./.cache/fix-report-...md`)
|
|
32
32
|
|
|
33
33
|
示例:
|
|
34
34
|
|
|
@@ -36,10 +36,10 @@ tools:
|
|
|
36
36
|
PR #123
|
|
37
37
|
round: 1
|
|
38
38
|
runId: abcdef123456
|
|
39
|
-
contextFile: pr-context-pr123-r1-abcdef123456.md
|
|
40
|
-
reviewFile: review-CDX-pr123-r1-abcdef123456.md
|
|
41
|
-
reviewFile: review-CLD-pr123-r1-abcdef123456.md
|
|
42
|
-
reviewFile: review-GMN-pr123-r1-abcdef123456.md
|
|
39
|
+
contextFile: ./.cache/pr-context-pr123-r1-abcdef123456.md
|
|
40
|
+
reviewFile: ./.cache/review-CDX-pr123-r1-abcdef123456.md
|
|
41
|
+
reviewFile: ./.cache/review-CLD-pr123-r1-abcdef123456.md
|
|
42
|
+
reviewFile: ./.cache/review-GMN-pr123-r1-abcdef123456.md
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
## 执行方式(强制)
|
|
@@ -318,13 +318,66 @@ def _counts(findings):
|
|
|
318
318
|
return c
|
|
319
319
|
|
|
320
320
|
|
|
321
|
-
def
|
|
321
|
+
def _check_existing_comment(pr_number, run_id, round_num, comment_type):
|
|
322
|
+
"""
|
|
323
|
+
Check if a comment with same runId/round/type already exists.
|
|
324
|
+
Returns True if duplicate exists (should skip posting).
|
|
325
|
+
|
|
326
|
+
comment_type: "review-summary" or "fix-report" or "final-report"
|
|
327
|
+
"""
|
|
328
|
+
try:
|
|
329
|
+
result = subprocess.run(
|
|
330
|
+
["gh", "api", f"repos/:owner/:repo/issues/{pr_number}/comments", "--paginate"],
|
|
331
|
+
stdout=subprocess.PIPE,
|
|
332
|
+
stderr=subprocess.DEVNULL,
|
|
333
|
+
text=True,
|
|
334
|
+
)
|
|
335
|
+
if result.returncode != 0:
|
|
336
|
+
return False
|
|
337
|
+
|
|
338
|
+
comments = json.loads(result.stdout or "[]")
|
|
339
|
+
|
|
340
|
+
if comment_type == "review-summary":
|
|
341
|
+
type_header = f"## Review Summary (Round {round_num})"
|
|
342
|
+
elif comment_type == "fix-report":
|
|
343
|
+
type_header = f"## Fix Report (Round {round_num})"
|
|
344
|
+
elif comment_type == "final-report":
|
|
345
|
+
type_header = "## Final Report"
|
|
346
|
+
else:
|
|
347
|
+
return False
|
|
348
|
+
|
|
349
|
+
run_id_pattern = f"RunId: {run_id}"
|
|
350
|
+
|
|
351
|
+
for comment in comments:
|
|
352
|
+
body = comment.get("body", "")
|
|
353
|
+
if MARKER in body and type_header in body and run_id_pattern in body:
|
|
354
|
+
return True
|
|
355
|
+
|
|
356
|
+
return False
|
|
357
|
+
except Exception:
|
|
358
|
+
return False
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _post_pr_comment(pr_number, body_ref, run_id=None, round_num=None, comment_type=None):
|
|
362
|
+
"""
|
|
363
|
+
Post a PR comment with idempotency check.
|
|
364
|
+
|
|
365
|
+
If run_id, round_num, and comment_type are provided, checks for existing
|
|
366
|
+
duplicate before posting and skips if already posted.
|
|
367
|
+
|
|
368
|
+
Returns: True if posted successfully or skipped (idempotent), False on error
|
|
369
|
+
"""
|
|
322
370
|
if isinstance(body_ref, Path):
|
|
323
371
|
p = body_ref
|
|
324
372
|
else:
|
|
325
373
|
p = _resolve_ref(REPO_ROOT, CACHE_DIR, body_ref)
|
|
326
374
|
if not p:
|
|
327
375
|
return False
|
|
376
|
+
|
|
377
|
+
if run_id and round_num and comment_type:
|
|
378
|
+
if _check_existing_comment(pr_number, run_id, round_num, comment_type):
|
|
379
|
+
return True
|
|
380
|
+
|
|
328
381
|
body_path = str(p)
|
|
329
382
|
rc = subprocess.run(
|
|
330
383
|
["gh", "pr", "comment", str(pr_number), "--body-file", body_path],
|
|
@@ -397,6 +450,32 @@ def _render_mode_b_comment(pr_number, round_num, run_id, fix_report_md):
|
|
|
397
450
|
return "\n".join(body)
|
|
398
451
|
|
|
399
452
|
|
|
453
|
+
def _render_final_comment(pr_number, round_num, run_id, status):
|
|
454
|
+
lines = []
|
|
455
|
+
lines.append(MARKER)
|
|
456
|
+
lines.append("")
|
|
457
|
+
lines.append("## Final Report")
|
|
458
|
+
lines.append("")
|
|
459
|
+
lines.append(f"- PR: #{pr_number}")
|
|
460
|
+
lines.append(f"- Total Rounds: {round_num}")
|
|
461
|
+
lines.append(f"- RunId: {run_id}")
|
|
462
|
+
lines.append("")
|
|
463
|
+
|
|
464
|
+
if status == "RESOLVED":
|
|
465
|
+
lines.append("### Status: ✅ All issues resolved")
|
|
466
|
+
lines.append("")
|
|
467
|
+
lines.append("All P0/P1/P2 issues from the automated review have been addressed.")
|
|
468
|
+
lines.append("The PR is ready for human review and merge.")
|
|
469
|
+
else:
|
|
470
|
+
lines.append("### Status: ⚠️ Max rounds reached")
|
|
471
|
+
lines.append("")
|
|
472
|
+
lines.append("The automated review loop has completed the maximum number of rounds (3).")
|
|
473
|
+
lines.append("Some issues may still remain. Please review the PR comments above for details.")
|
|
474
|
+
|
|
475
|
+
lines.append("")
|
|
476
|
+
return "\n".join(lines)
|
|
477
|
+
|
|
478
|
+
|
|
400
479
|
def main(argv):
|
|
401
480
|
class _ArgParser(argparse.ArgumentParser):
|
|
402
481
|
def error(self, message):
|
|
@@ -409,6 +488,7 @@ def main(argv):
|
|
|
409
488
|
parser.add_argument("--context-file")
|
|
410
489
|
parser.add_argument("--review-file", action="append", default=[])
|
|
411
490
|
parser.add_argument("--fix-report-file")
|
|
491
|
+
parser.add_argument("--final-report")
|
|
412
492
|
parser.add_argument("--duplicate-groups-json")
|
|
413
493
|
parser.add_argument("--duplicate-groups-b64")
|
|
414
494
|
|
|
@@ -422,6 +502,7 @@ def main(argv):
|
|
|
422
502
|
round_num = args.round
|
|
423
503
|
run_id = str(args.run_id)
|
|
424
504
|
|
|
505
|
+
final_report = (args.final_report or "").strip() or None
|
|
425
506
|
fix_report_file = (args.fix_report_file or "").strip() or None
|
|
426
507
|
context_file = (args.context_file or "").strip() or None
|
|
427
508
|
review_files = []
|
|
@@ -430,6 +511,17 @@ def main(argv):
|
|
|
430
511
|
if s:
|
|
431
512
|
review_files.append(s)
|
|
432
513
|
|
|
514
|
+
if final_report:
|
|
515
|
+
body = _render_final_comment(pr_number, round_num, run_id, final_report)
|
|
516
|
+
body_basename = f"review-aggregate-final-pr{pr_number}-{run_id}.md"
|
|
517
|
+
body_ref = _repo_relpath(REPO_ROOT, CACHE_DIR / body_basename)
|
|
518
|
+
_write_cache_text(body_ref, body)
|
|
519
|
+
if not _post_pr_comment(pr_number, body_ref, run_id=run_id, round_num=round_num, comment_type="final-report"):
|
|
520
|
+
_json_out({"error": "GH_PR_COMMENT_FAILED"})
|
|
521
|
+
return 1
|
|
522
|
+
_json_out({"ok": True, "final": True})
|
|
523
|
+
return 0
|
|
524
|
+
|
|
433
525
|
if fix_report_file:
|
|
434
526
|
fix_p = _resolve_ref(REPO_ROOT, CACHE_DIR, fix_report_file)
|
|
435
527
|
if not fix_p or not fix_p.exists():
|
|
@@ -440,7 +532,7 @@ def main(argv):
|
|
|
440
532
|
body_basename = f"review-aggregate-fix-comment-pr{pr_number}-r{round_num}-{run_id}.md"
|
|
441
533
|
body_ref = _repo_relpath(REPO_ROOT, CACHE_DIR / body_basename)
|
|
442
534
|
_write_cache_text(body_ref, body)
|
|
443
|
-
if not _post_pr_comment(pr_number, body_ref):
|
|
535
|
+
if not _post_pr_comment(pr_number, body_ref, run_id=run_id, round_num=round_num, comment_type="fix-report"):
|
|
444
536
|
_json_out({"error": "GH_PR_COMMENT_FAILED"})
|
|
445
537
|
return 1
|
|
446
538
|
_json_out({"ok": True})
|
|
@@ -488,7 +580,7 @@ def main(argv):
|
|
|
488
580
|
body_basename = f"review-aggregate-comment-pr{pr_number}-r{round_num}-{run_id}.md"
|
|
489
581
|
body_ref = _repo_relpath(REPO_ROOT, CACHE_DIR / body_basename)
|
|
490
582
|
_write_cache_text(body_ref, body)
|
|
491
|
-
if not _post_pr_comment(pr_number, body_ref):
|
|
583
|
+
if not _post_pr_comment(pr_number, body_ref, run_id=run_id, round_num=round_num, comment_type="review-summary"):
|
|
492
584
|
_json_out({"error": "GH_PR_COMMENT_FAILED"})
|
|
493
585
|
return 1
|
|
494
586
|
|
|
@@ -41,6 +41,13 @@ agent: sisyphus
|
|
|
41
41
|
|
|
42
42
|
## 循环(最多 3 轮)
|
|
43
43
|
|
|
44
|
+
**⚠️ 严格串行执行要求(Critical)**:
|
|
45
|
+
|
|
46
|
+
- 每个 Step 必须完成(收到返回值)后才能开始下一个 Step
|
|
47
|
+
- **禁止任何步骤并行执行**(除了 Step 2 的三个 reviewer 可并行)
|
|
48
|
+
- 如果任何步骤失败或超时,必须立即终止当前轮次,不能跳过或重试
|
|
49
|
+
- 每个步骤的 Task 调用必须 await 返回结果,不能 fire-and-forget
|
|
50
|
+
|
|
44
51
|
每轮按顺序执行:
|
|
45
52
|
|
|
46
53
|
1. Task: `pr-context` **(必须先完成,不可与 Step 2 并行)**
|
|
@@ -56,35 +63,80 @@ agent: sisyphus
|
|
|
56
63
|
- 每个 reviewer prompt 必须包含:
|
|
57
64
|
- `PR #{{PR_NUMBER}}`
|
|
58
65
|
- `round: <ROUND>`
|
|
59
|
-
- `
|
|
66
|
+
- `runId: <RUN_ID>`(来自 Step 1 的输出,必须透传,禁止自行生成)
|
|
67
|
+
- `contextFile: ./.cache/<file>.md`(来自 Step 1 的输出)
|
|
60
68
|
- reviewer 默认读 `contextFile`;必要时允许用 `git/gh` 只读命令拿 diff
|
|
61
69
|
- 忽略问题:1.格式化代码引起的噪音 2.已经lint检查以外的格式问题
|
|
62
70
|
- 特别关注: 逻辑、安全、性能、可维护性
|
|
63
71
|
- 同时要注意 pr 前面轮次的 修复和讨论,对于已经拒绝、已修复的问题不要反复的提出
|
|
64
72
|
- 同时也要注意fix的过程中有没有引入新的问题。
|
|
65
|
-
- 每个 reviewer 输出:`reviewFile:
|
|
73
|
+
- 每个 reviewer 输出:`reviewFile: ./.cache/<file>.md`(Markdown)
|
|
66
74
|
|
|
67
75
|
3. Task: `pr-review-aggregate`
|
|
68
76
|
|
|
69
|
-
- prompt 必须包含:`PR #{{PR_NUMBER}}`、`round: <ROUND>`、`runId: <RUN_ID>`、`contextFile:
|
|
77
|
+
- prompt 必须包含:`PR #{{PR_NUMBER}}`、`round: <ROUND>`、`runId: <RUN_ID>`、`contextFile: ./.cache/<file>.md`、三条 `reviewFile: ./.cache/<file>.md`
|
|
70
78
|
- 输出:`{"stop":true}` 或 `{"stop":false,"fixFile":"..."}`
|
|
71
79
|
- 若 `stop=true`:本轮结束并退出循环
|
|
80
|
+
- **唯一性约束**: 每轮只能发布一次 Review Summary;脚本内置幂等检查,重复调用不会重复发布
|
|
72
81
|
|
|
73
82
|
4. Task: `pr-fix`
|
|
74
83
|
|
|
75
84
|
- prompt 必须包含:
|
|
76
85
|
- `PR #{{PR_NUMBER}}`
|
|
77
86
|
- `round: <ROUND>`
|
|
78
|
-
- `
|
|
87
|
+
- `runId: <RUN_ID>`(来自 Step 1 的输出,必须透传,禁止自行生成)
|
|
88
|
+
- `fixFile: ./.cache/<file>.md`
|
|
79
89
|
- 约定:`pr-fix` 对每个 findingId 单独 commit + push(一个 findingId 一个 commit),结束后再 `git push` 兜底
|
|
80
90
|
|
|
81
|
-
- pr-fix 输出:`fixReportFile:
|
|
91
|
+
- pr-fix 输出:`fixReportFile: ./.cache/<file>.md`(Markdown)
|
|
82
92
|
|
|
83
93
|
5. Task: `pr-review-aggregate`(发布修复评论)
|
|
84
94
|
|
|
85
|
-
- prompt 必须包含:`PR #{{PR_NUMBER}}`、`round: <ROUND>`、`runId: <RUN_ID>`、`fixReportFile:
|
|
95
|
+
- prompt 必须包含:`PR #{{PR_NUMBER}}`、`round: <ROUND>`、`runId: <RUN_ID>`、`fixReportFile: ./.cache/<file>.md`
|
|
86
96
|
- 输出:`{"ok":true}`
|
|
97
|
+
- **唯一性约束**: 每轮只能发布一次 Fix Report;脚本内置幂等检查,重复调用不会重复发布
|
|
87
98
|
|
|
88
99
|
6. 下一轮
|
|
89
100
|
|
|
90
101
|
- 回到 1(进入下一轮 reviewers)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## 终止与收尾(强制)
|
|
105
|
+
|
|
106
|
+
循环结束时,必须发布一个最终评论到 PR,格式如下:
|
|
107
|
+
|
|
108
|
+
### 情况 A: 所有问题已解决(stop=true)
|
|
109
|
+
|
|
110
|
+
当 Step 3 返回 `{"stop":true}` 时,调用 `pr-review-aggregate` 发布收尾评论:
|
|
111
|
+
|
|
112
|
+
- prompt 必须包含:
|
|
113
|
+
- `PR #{{PR_NUMBER}}`
|
|
114
|
+
- `round: <ROUND>`
|
|
115
|
+
- `runId: <RUN_ID>`
|
|
116
|
+
- `--final-report "RESOLVED"`(新增参数,表示所有问题已解决)
|
|
117
|
+
|
|
118
|
+
### 情况 B: 达到最大轮次(3 轮后仍有问题)
|
|
119
|
+
|
|
120
|
+
当循环完成 3 轮后仍未 stop,调用 `pr-review-aggregate` 发布收尾评论:
|
|
121
|
+
|
|
122
|
+
- prompt 必须包含:
|
|
123
|
+
- `PR #{{PR_NUMBER}}`
|
|
124
|
+
- `round: 3`
|
|
125
|
+
- `runId: <RUN_ID>`
|
|
126
|
+
- `--final-report "MAX_ROUNDS_REACHED"`(新增参数,表示达到最大轮次)
|
|
127
|
+
|
|
128
|
+
### 最终评论格式(由脚本生成)
|
|
129
|
+
|
|
130
|
+
```markdown
|
|
131
|
+
<!-- pr-review-loop-marker -->
|
|
132
|
+
|
|
133
|
+
## Final Report
|
|
134
|
+
|
|
135
|
+
- PR: #<PR_NUMBER>
|
|
136
|
+
- Total Rounds: <N>
|
|
137
|
+
- Status: ✅ All issues resolved / ⚠️ Max rounds reached (some issues may remain)
|
|
138
|
+
|
|
139
|
+
### Summary
|
|
140
|
+
|
|
141
|
+
[自动生成的总结]
|
|
142
|
+
```
|