Skip to content

Commit 4cfcd93

Browse files
authored
CI: improve PR review plan comment format (#338)
* ci: improve PR review plan comment format * ci: use github comment review plan format * ci: skip full tests for governance-only changes
1 parent 4163ce9 commit 4cfcd93

4 files changed

Lines changed: 214 additions & 8 deletions

File tree

.github/workflows/pr_review_plan_comment.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
4242
- name: Generate review plan
4343
run: |
44-
python scripts/ci/pr_review_plan.py --stdin < changed-files.txt > pr-review-plan.md
44+
python scripts/ci/pr_review_plan.py --stdin --format github-comment < changed-files.txt > pr-review-plan.md
4545
4646
- name: Comment review plan
4747
uses: actions/github-script@v7
@@ -53,6 +53,7 @@ jobs:
5353
const body = [
5454
marker,
5555
"This advisory review plan was generated from changed file names using trusted base-branch code.",
56+
"此审查计划由受信任的 base 分支代码根据变更文件名生成,仅作为维护者辅助。",
5657
"",
5758
plan,
5859
].join("\n");

.github/workflows/unit_test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ on:
44
pull_request:
55
paths-ignore:
66
- 'docs/**'
7+
- '.github/workflows/pr_review_plan.yml'
8+
- '.github/workflows/pr_review_plan_comment.yml'
9+
- '.github/workflows/repository_hygiene.yml'
10+
- 'scripts/ci/**'
711

812
jobs:
913
build:

docs/maintenance/pr_review_plan.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ git diff --name-only main...HEAD | python scripts/ci/pr_review_plan.py --stdin
2020

2121
The output is Markdown and can be pasted into a PR's AI Assistance or Merge Decision notes.
2222

23+
For a shorter PR-page comment format, use:
24+
25+
```bash
26+
git diff --name-only main...HEAD | python scripts/ci/pr_review_plan.py --stdin --format github-comment
27+
```
28+
29+
The default CLI format is intentionally detailed. The GitHub comment format is optimized for maintainer scanning: the visible summary is bilingual, while detailed file lists, local commands, and hold conditions are kept in collapsible sections.
30+
2331
## GitHub Automation
2432

2533
DeePTB has two advisory workflows for this plan:
@@ -29,16 +37,18 @@ DeePTB has two advisory workflows for this plan:
2937

3038
The comment workflow is designed for fork PRs. It checks out the trusted base commit from the main repository, reads changed file names through the GitHub API, then runs the base-branch copy of `scripts/ci/pr_review_plan.py`. It must not checkout or execute code from the fork branch.
3139

40+
The comment workflow may also add an Evidence section based on GitHub check status. That evidence is owned by the workflow, not by `pr_review_plan.py`, because the script only knows changed file names.
41+
3242
Required permissions for the comment workflow are intentionally narrow:
3343

3444
```yaml
3545
permissions:
3646
contents: read
37-
pull-requests: read
47+
pull-requests: write
3848
issues: write
3949
```
4050
41-
`issues: write` is needed because PR timeline comments use the issues comments API. The workflow updates the existing bot comment using a hidden marker instead of creating a new comment on every PR update.
51+
`issues: write` is needed because PR timeline comments use the issues comments API. `pull-requests: write` is needed for repositories where GitHub requires both issue-comment and pull-request write scopes for PR timeline comments. The workflow updates the existing bot comment using a hidden marker instead of creating a new comment on every PR update.
4252

4353
## How To Read The Output
4454

@@ -48,6 +58,7 @@ permissions:
4858
- **Suggested AI review** points to the minimum recommended prompts.
4959
- **Suggested checks** lists likely local commands for this PR.
5060
- **Hold conditions** names situations that should stop merge until resolved or explicitly waived.
61+
- **GitHub comment summary** shows bilingual risk, reason, and review focus, with details folded by default.
5162

5263
The plan is advisory. When the PR body, maintainer judgment, or scientific impact suggests higher risk than the file map, use the higher risk level.
5364

@@ -64,3 +75,9 @@ Docs-only changes should normally stay low risk:
6475
```bash
6576
printf "docs/index.rst\n" | python scripts/ci/pr_review_plan.py --stdin
6677
```
78+
79+
GitHub comment preview:
80+
81+
```bash
82+
printf "dptb/utils/argcheck.py\n" | python scripts/ci/pr_review_plan.py --stdin --format github-comment
83+
```

scripts/ci/pr_review_plan.py

Lines changed: 189 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,34 @@ class Risk(IntEnum):
2121
Risk.HIGH: "High",
2222
}
2323

24+
RISK_LABELS_ZH = {
25+
Risk.LOW: "低",
26+
Risk.MEDIUM: "中",
27+
Risk.HIGH: "高",
28+
}
29+
30+
AREA_NAMES_ZH = {
31+
"AtomicDataDict contract": "AtomicDataDict 契约",
32+
"Orbital indexing": "轨道索引",
33+
"Hamiltonian expansion": "Hamiltonian 展开",
34+
"Model assembly": "模型构建",
35+
"Config schema": "配置 schema",
36+
"Training behavior": "训练行为",
37+
"Loss behavior": "loss 行为",
38+
"Embedding and prediction": "embedding 与 prediction",
39+
"Dataset backends": "数据集后端",
40+
"CLI entrypoints": "CLI 入口",
41+
"Postprocess and export": "后处理与导出",
42+
"Plugins": "插件",
43+
"GitHub Actions and CI": "GitHub Actions 与 CI",
44+
"Tests": "测试",
45+
"Examples and configs": "示例与配置",
46+
"Documentation": "文档",
47+
"Issue templates": "issue 模板",
48+
"Packaging metadata": "打包元数据",
49+
"Maintenance governance": "维护治理",
50+
}
51+
2452

2553
@dataclass(frozen=True)
2654
class AreaRule:
@@ -262,6 +290,18 @@ def ai_prompt_suggestions(risk: Risk) -> list[str]:
262290
]
263291

264292

293+
def ai_prompt_suggestions_bilingual(risk: Risk) -> list[str]:
294+
if risk == Risk.LOW:
295+
return [
296+
"Optional: run the Test Gap Review Prompt if examples, docs commands, or test markers changed. / "
297+
"可选:如果示例、文档命令或测试 marker 有变更,运行测试缺口审查 prompt。",
298+
]
299+
return [
300+
"Run the Maintainer Review Prompt. / 运行维护者审查 prompt。",
301+
"Run the Test Gap Review Prompt. / 运行测试缺口审查 prompt。",
302+
]
303+
304+
265305
def maintainer_focus(risk: Risk, matched: list[tuple[AreaRule, list[str]]], paths: list[str]) -> list[str]:
266306
focus = [f"{rule.name}: {rule.focus}" for rule, _ in matched]
267307
if any(path.startswith("dptb/data/") for path in paths) and any(
@@ -292,6 +332,52 @@ def hold_conditions(risk: Risk) -> list[str]:
292332
return holds
293333

294334

335+
def format_area(rule: AreaRule, files: list[str], limit: int = 5) -> str:
336+
preview = ", ".join(files[:limit])
337+
suffix = "" if len(files) <= limit else f", ... ({len(files)} files)"
338+
return f"- **{rule.name}** ({RISK_LABELS[rule.risk]}): {preview}{suffix}"
339+
340+
341+
def risk_reasons(
342+
risk: Risk,
343+
matched: list[tuple[AreaRule, list[str]]],
344+
unmatched: list[str],
345+
paths: list[str],
346+
) -> list[str]:
347+
reasons: list[str] = []
348+
for rule, _ in matched:
349+
if rule.risk == Risk.HIGH:
350+
zh_name = AREA_NAMES_ZH.get(rule.name, rule.name)
351+
reasons.append(f"{rule.name} changed. / {zh_name} 有变更。")
352+
353+
if risk == Risk.HIGH and not reasons:
354+
reasons.append("High-risk areas changed. / 高风险区域有变更。")
355+
356+
for rule, _ in matched:
357+
if rule.risk == Risk.MEDIUM and len(reasons) < 4:
358+
zh_name = AREA_NAMES_ZH.get(rule.name, rule.name)
359+
reasons.append(f"{rule.name} changed. / {zh_name} 有变更。")
360+
361+
if has_any(paths, prefixes=("examples/",)):
362+
reasons.append("Examples and configs changed. / 示例和配置有变更。")
363+
if has_any(paths, prefixes=("docs/",), exact=("README.md",)):
364+
reasons.append("Documentation changed. / 文档有变更。")
365+
if unmatched:
366+
reasons.append(
367+
"Some files are unclassified by current `risk_map.md`. / "
368+
"部分文件尚未被当前风险地图覆盖。"
369+
)
370+
371+
if not reasons:
372+
reasons.append("No mapped high-risk area was detected. / 未发现已映射的高风险区域。")
373+
374+
deduped: list[str] = []
375+
for reason in reasons:
376+
if reason not in deduped:
377+
deduped.append(reason)
378+
return deduped[:5]
379+
380+
295381
def render_plan(paths: list[str]) -> str:
296382
risk, matched, unmatched = classify(paths)
297383
lines: list[str] = [
@@ -305,16 +391,14 @@ def render_plan(paths: list[str]) -> str:
305391

306392
if matched:
307393
for rule, files in matched:
308-
preview = ", ".join(files[:5])
309-
suffix = "" if len(files) <= 5 else f", ... ({len(files)} files)"
310-
lines.append(f"- **{rule.name}** ({RISK_LABELS[rule.risk]}): {preview}{suffix}")
394+
lines.append(format_area(rule, files))
311395
else:
312396
lines.append("- No mapped DeePTB risk area matched. Review scope manually.")
313397

314398
if unmatched:
315399
preview = ", ".join(unmatched[:8])
316400
suffix = "" if len(unmatched) <= 8 else f", ... ({len(unmatched)} files)"
317-
lines.append(f"- **Unmapped files**: {preview}{suffix}")
401+
lines.append(f"- **Unclassified by current risk_map.md**: {preview}{suffix}")
318402

319403
sections = (
320404
("Required Maintainer Focus", maintainer_focus(risk, matched, paths)),
@@ -337,6 +421,97 @@ def render_plan(paths: list[str]) -> str:
337421
return "\n".join(lines) + "\n"
338422

339423

424+
def render_github_comment_plan(paths: list[str]) -> str:
425+
risk, matched, unmatched = classify(paths)
426+
reasons = risk_reasons(risk, matched, unmatched, paths)
427+
focus = maintainer_focus(risk, matched, paths)
428+
checks = suggested_checks(risk, paths)
429+
holds = hold_conditions(risk)
430+
431+
lines: list[str] = [
432+
"## DeePTB PR Review Plan / DeePTB PR 审查计划",
433+
"",
434+
f"**Risk / 风险等级: {RISK_LABELS[risk]} ({RISK_LABELS_ZH[risk]})** · "
435+
f"**Changed files / 变更文件: {len(paths)}**",
436+
"",
437+
"## Why / 风险来源",
438+
"",
439+
]
440+
lines.extend(f"- {reason}" for reason in reasons)
441+
442+
lines.extend(
443+
[
444+
"",
445+
"## Recommended Review / 建议审查重点",
446+
"",
447+
]
448+
)
449+
lines.extend(f"- {item}" for item in ai_prompt_suggestions_bilingual(risk))
450+
if risk == Risk.HIGH:
451+
lines.append(
452+
"- Focus human review on config/API/data/checkpoint compatibility. / "
453+
"人工重点看配置、API、数据和 checkpoint 兼容性。"
454+
)
455+
else:
456+
lines.append(
457+
"- Confirm the changed behavior matches the PR scope. / "
458+
"确认变更行为和 PR 范围一致。"
459+
)
460+
461+
lines.extend(
462+
[
463+
"",
464+
"<details>",
465+
"<summary>Detailed risk areas</summary>",
466+
"",
467+
]
468+
)
469+
if matched:
470+
lines.extend(format_area(rule, files) for rule, files in matched)
471+
else:
472+
lines.append("- No mapped DeePTB risk area matched. Review scope manually.")
473+
if unmatched:
474+
preview = ", ".join(unmatched[:20])
475+
suffix = "" if len(unmatched) <= 20 else f", ... ({len(unmatched)} files)"
476+
lines.append(f"- **Unclassified by current risk_map.md**: {preview}{suffix}")
477+
lines.extend(["", "</details>"])
478+
479+
lines.extend(
480+
[
481+
"",
482+
"<details>",
483+
"<summary>Human review focus</summary>",
484+
"",
485+
]
486+
)
487+
lines.extend(f"- {item}" for item in focus)
488+
lines.extend(["", "</details>"])
489+
490+
lines.extend(
491+
[
492+
"",
493+
"<details>",
494+
"<summary>Local commands and hold conditions</summary>",
495+
"",
496+
"Suggested local commands:",
497+
"",
498+
]
499+
)
500+
lines.extend(f"- `{check}`" for check in checks)
501+
lines.extend(["", "Hold conditions:", ""])
502+
lines.extend(f"- {item}" for item in holds)
503+
lines.extend(
504+
[
505+
"",
506+
"</details>",
507+
"",
508+
"_Advisory only. / 仅作为审查辅助。_",
509+
]
510+
)
511+
512+
return "\n".join(lines) + "\n"
513+
514+
340515
def parse_args() -> argparse.Namespace:
341516
parser = argparse.ArgumentParser(
342517
description="Generate an advisory DeePTB PR review plan from changed files."
@@ -352,6 +527,12 @@ def parse_args() -> argparse.Namespace:
352527
action="store_true",
353528
help="Read newline-separated changed files from stdin.",
354529
)
530+
parser.add_argument(
531+
"--format",
532+
choices=("cli", "github-comment"),
533+
default="cli",
534+
help="Output format. Default keeps the detailed CLI-style Markdown.",
535+
)
355536
return parser.parse_args()
356537

357538

@@ -365,7 +546,10 @@ def main() -> int:
365546
)
366547
return 2
367548

368-
print(render_plan(paths), end="")
549+
if args.format == "github-comment":
550+
print(render_github_comment_plan(paths), end="")
551+
else:
552+
print(render_plan(paths), end="")
369553
return 0
370554

371555

0 commit comments

Comments
 (0)