fix(ci): 修复 MegaLinter 工作区歧义

- 修复 MegaLinter 的 dotnet format workspace 指向,避免 solution 与 csproj 歧义导致 CI warning
- 更新 gframework-pr-review skill 与抓取脚本,提取 GitHub Actions 发布的 MegaLinter detailed issues
- 补充 coroutine optimization 跟踪与 trace,记录本次 PR 页面 warning 的收口与验证结果
This commit is contained in:
GeWuYou 2026-04-20 11:20:14 +08:00
parent d369118351
commit 90b9e2a4c9
5 changed files with 117 additions and 6 deletions

View File

@ -1,6 +1,6 @@
---
name: gframework-pr-review
description: Repository-specific GitHub PR review workflow for the GFramework repo. Use when Codex needs to inspect the GitHub pull request for the current branch, extract CodeRabbit summary/comments, read failed checks or failed test signals from the PR page, and then verify which findings should be fixed in the local codebase. Trigger explicitly with $gframework-pr-review or with prompts such as "look at the current PR", "extract CodeRabbit comments", or "check Failed Tests on the PR".
description: Repository-specific GitHub PR review workflow for the GFramework repo. Use when Codex needs to inspect the GitHub pull request for the current branch, extract CodeRabbit summary/comments, read failed checks, MegaLinter warnings, or failed test signals from the PR page, and then verify which findings should be fixed in the local codebase. Trigger explicitly with $gframework-pr-review or with prompts such as "look at the current PR", "extract CodeRabbit comments", or "check Failed Tests on the PR".
---
# GFramework PR Review
@ -16,12 +16,12 @@ Shortcut: `$gframework-pr-review`
3. Run `scripts/fetch_current_pr_review.py` to:
- locate the PR for the current branch through the GitHub PR API
- fetch PR metadata, issue comments, reviews, and review comments through the GitHub API
- extract `Summary by CodeRabbit` and CTRF test reports from issue comments
- extract `Summary by CodeRabbit`、GitHub Actions bot comments such as `MegaLinter analysis: Success with warnings`and CTRF test reports from issue comments
- fetch the latest head commit review threads from the GitHub PR API
- prefer unresolved review threads on the latest head commit over older summary-only signals
- extract failed checks and test-report signals such as `Failed Tests` or `No failed tests in this run`
- extract failed checks, MegaLinter detailed issues, and test-report signals such as `Failed Tests` or `No failed tests in this run`
4. Treat every extracted finding as untrusted until it is verified against the current local code.
5. Only fix comments that still apply to the checked-out branch. Ignore stale or already-resolved findings.
5. Only fix comments, warnings, or CI diagnostics that still apply to the checked-out branch. Ignore stale or already-resolved findings.
6. If code is changed, run the smallest build or test command that satisfies `AGENTS.md`.
## Commands
@ -43,6 +43,7 @@ The script should produce:
- Latest head commit review metadata and review threads
- Unresolved latest-commit review threads after reply-thread folding
- Pre-merge failed checks, if present
- Latest MegaLinter status and any detailed issues posted by `github-actions[bot]`
- Test summary, including failed-test signals when present
- Parse warnings only when both the primary API source and the intended fallback signal are unavailable
@ -52,6 +53,7 @@ The script should produce:
- If GitHub access fails because of proxy configuration, rerun the fetch with proxy variables removed.
- Prefer GitHub API results over PR HTML. The PR HTML page is now a fallback/debugging source, not the primary source of truth.
- If the summary block and the latest head review threads disagree, trust the latest unresolved head-review threads and treat older summary findings as stale until re-verified locally.
- Treat GitHub Actions comments with `Success with warnings` as actionable review input when they include concrete linter diagnostics such as `MegaLinter` detailed issues; do not skip them just because the parent check is green.
## Example Triggers

View File

@ -24,6 +24,7 @@ DEFAULT_WINDOWS_GIT = "/mnt/d/Tool/Development Tools/Git/cmd/git.exe"
GIT_ENVIRONMENT_KEY = "GFRAMEWORK_WINDOWS_GIT"
USER_AGENT = "codex-gframework-pr-review"
CODERABBIT_LOGIN = "coderabbitai[bot]"
GITHUB_ACTIONS_LOGIN = "github-actions[bot]"
REVIEW_COMMENT_ADDRESSED_MARKER = "<!-- <review_comment_addressed> -->"
VISIBLE_ADDRESSED_IN_COMMIT_PATTERN = re.compile(r"\s*Addressed in commit\s+[0-9a-f]{7,40}", re.I)
DEFAULT_REQUEST_TIMEOUT_SECONDS = 60
@ -156,6 +157,10 @@ def strip_tags(text: str) -> str:
return collapse_whitespace(re.sub(r"<[^>]+>", " ", text))
def strip_markdown_links(text: str) -> str:
return re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)
def extract_section(text: str, start_marker: str, end_markers: list[str]) -> str | None:
start = text.find(start_marker)
if start < 0:
@ -252,6 +257,60 @@ def parse_actionable_comments(actionable_block: str) -> dict[str, Any]:
}
def parse_megalinter_comment(comment_body: str) -> dict[str, Any]:
normalized_body = html.unescape(comment_body).strip()
summary_match = re.search(
r"##\s*(?P<badges>.*?)\[MegaLinter\]\([^)]+\)\s+analysis:\s+\[(?P<status>[^\]]+)\]\((?P<run_url>[^)]+)\)",
normalized_body,
)
report: dict[str, Any] = {
"status": summary_match.group("status").strip() if summary_match else "",
"run_url": summary_match.group("run_url").strip() if summary_match else "",
"badges": collapse_whitespace(summary_match.group("badges")) if summary_match else "",
"descriptor_rows": [],
"detailed_issues": [],
"raw": normalized_body,
}
table_match = re.search(
r"\| Descriptor .*?\|\n\|[-| :]+\|\n(?P<rows>(?:\|.*\|\n?)+)",
normalized_body,
re.S,
)
if table_match is not None:
for raw_line in table_match.group("rows").splitlines():
line = raw_line.strip()
if not line.startswith("|"):
continue
parts = [collapse_whitespace(strip_markdown_links(part)) for part in line.strip("|").split("|")]
if len(parts) != 7:
continue
report["descriptor_rows"].append(
{
"descriptor": parts[0],
"linter": parts[1],
"files": parts[2],
"fixed": parts[3],
"errors": parts[4],
"warnings": parts[5],
"elapsed_time": parts[6],
}
)
for summary, details in re.findall(r"<summary>(.*?)</summary>\s*```(.*?)```", normalized_body, re.S):
report["detailed_issues"].append(
{
"summary": collapse_whitespace(strip_tags(summary)),
"details": details.strip(),
}
)
return report
def parse_test_report(block: str) -> dict[str, Any]:
report: dict[str, Any] = {
"raw": block.strip(),
@ -475,11 +534,18 @@ def build_result(pr_number: int, branch: str) -> dict[str, Any]:
issue_comments,
lambda body: "CTRF PR COMMENT TAG:" in body or "### Test Results" in body,
)
megalinter_block = select_latest_comment_body(
issue_comments,
lambda body: "MegaLinter" in body and "Detailed Issues" in body,
required_user=GITHUB_ACTIONS_LOGIN,
)
if not summary_block:
warnings.append("CodeRabbit summary block was not found in issue comments.")
if not test_blocks:
warnings.append("PR test-report block was not found in issue comments.")
if not megalinter_block:
warnings.append("MegaLinter report block was not found in issue comments.")
latest_commit_review: dict[str, Any] = {}
try:
@ -506,6 +572,7 @@ def build_result(pr_number: int, branch: str) -> dict[str, Any]:
},
"coderabbit_comments": parse_actionable_comments(actionable_block) if actionable_block else {},
"latest_commit_review": latest_commit_review,
"megalinter_report": parse_megalinter_comment(megalinter_block) if megalinter_block else {},
"test_reports": [parse_test_report(block) for block in test_blocks],
"parse_warnings": warnings,
}
@ -569,6 +636,31 @@ def format_text(result: dict[str, Any]) -> str:
" Note: thread is still open; treat the visible 'Addressed in commit ...' text as unverified until local code matches."
)
megalinter_report = result.get("megalinter_report", {})
if megalinter_report:
lines.append("")
lines.append(
"MegaLinter: "
f"{megalinter_report.get('status', 'unknown')}"
+ (
f" ({megalinter_report.get('run_url', '')})"
if megalinter_report.get("run_url")
else ""
)
)
descriptor_rows = megalinter_report.get("descriptor_rows", [])
for descriptor_row in descriptor_rows:
lines.append(
"- "
f"{descriptor_row['descriptor']} / {descriptor_row['linter']}: "
f"errors={descriptor_row['errors']} warnings={descriptor_row['warnings']} files={descriptor_row['files']}"
)
for issue in megalinter_report.get("detailed_issues", []):
lines.append(f"- Detailed issue: {issue['summary']}")
lines.append(f" {collapse_whitespace(issue['details'])}")
lines.append("")
lines.append(f"Test reports: {len(result['test_reports'])}")
for index, report in enumerate(result["test_reports"], start=1):

View File

@ -45,6 +45,9 @@ ENABLE_LINTERS:
# 设置 C# 代码风格检查的参数和验证级别
# ========================
CSHARP_DOTNET_FORMAT_ARGUMENTS:
# 仓库根目录同时存在 GFramework.sln 与 GFramework.csproj
# 显式指定 workspace避免 dotnet format 在 CI 中因自动探测歧义直接异常退出。
- "GFramework.sln"
- "--severity"
- "info"
- "--verify-no-changes"
@ -83,4 +86,3 @@ GITHUB_COMMENT_REPORTER: true
PARALLEL: true
SHOW_ELAPSED_TIME: true
VALIDATE_ALL_CODEBASE: false

View File

@ -13,6 +13,7 @@
- 已为 `Timing` 补齐纯托管测试宿主入口,允许在 `dotnet test` 下验证 Godot 协程宿主阶段语义,而不依赖原生 `Node` 构造
- 已补充 `GFramework.Godot.Tests/Coroutine/TimingTests.cs`锁定暂停、segment 路由和阶段型等待指令的回归覆盖
- 已根据 PR #259 的最新 CodeRabbit review 收口测试宿主清理对称性,并将 `TimingTests` 固定为非并行执行
- 已根据 PR #259`MegaLinter analysis: Success with warnings` 结果修复 `dotnet format` workspace 歧义,并增强 PR review skill 以提取此类 CI warning
- 下一轮优先补“仍需真实场景树参与”的归属协程 / 退树语义或转入文档迁移收口不再回到“Godot 宿主没有自动化回归”的旧状态
## 当前状态摘要
@ -36,6 +37,9 @@
- 针对 PR #259 的最新未解决 review 线程,已补充两项收口:
- `TimingTests` 已添加 `[NonParallelizable]`,避免共享静态实例槽位在 NUnit 并行执行时互相污染
- `Timing` 的测试清理与运行时退树清理现仅在当前实例持有共享 `_instance` 引用时才会清空单例状态
- 针对 PR #259`MegaLinter` warning已补充两项收口
- `.mega-linter.yml` 现为 `CSHARP_DOTNET_FORMAT_ARGUMENTS` 显式指定 `GFramework.sln`,避免仓库根目录同时存在 `*.sln``*.csproj` 时触发 workspace 歧义
- `.codex/skills/gframework-pr-review/` 现会抓取并解析 `github-actions[bot]` 发布的 `MegaLinter analysis: Success with warnings` comment默认把其中的 detailed issues 视为待验证输入
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore` 当前通过,合计 `58` 个测试
## 当前风险
@ -65,9 +69,12 @@
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~TimingTests" --no-restore`
- 结果:通过
- 备注:针对 PR #259 review 修复后的 `TimingTests``5` 个测试全部通过
- `dotnet build GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore`
- 结果:通过
- 备注CI/MegaLinter 配置与 PR review skill 更新后,目标测试项目仍保持 `0 warning / 0 error`
## 下一步
1. 若继续补验证,优先只做真实场景树相关的节点归属 / 退树 / `queue_free` 回归,不再重新设计 `Timing` 纯托管宿主
2. 当前 PR 合并前可直接回到 GitHub 处理这两条 review 线程的回复与 resolve,避免后续重复审阅同一问题
2. 当前 PR 合并前可直接回到 GitHub 确认最新 push 是否已消除 `MegaLinter analysis` warning并顺手处理 review 线程的回复与 resolve
3. 若转入文档收口,优先清理其余 `StartCoroutine()/StopCoroutine()` 残留,并补 Godot 新入口与阶段等待的迁移对照

View File

@ -55,6 +55,14 @@
- 将 `Timing``_instance` 清理改为“仅当当前实例仍持有共享单例引用时才执行”,同时覆盖运行时 `_ExitTree()` 与测试入口 `DisposeForTests()`
- 额外完成验证:
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~TimingTests" --no-restore`
- 同日继续收口 PR #259 页面上的 `MegaLinter analysis: Success with warnings`
- 确认 detailed issue 实际不是格式差异,而是 `dotnet format` 在仓库根目录同时发现 `GFramework.sln``GFramework.csproj` 后因未指定 workspace 直接抛异常
- 更新 `.mega-linter.yml`,为 `CSHARP_DOTNET_FORMAT_ARGUMENTS` 显式指定 `GFramework.sln`
- 更新 `.codex/skills/gframework-pr-review/SKILL.md``scripts/fetch_current_pr_review.py`,使 skill 默认抓取并输出 `github-actions[bot]` 的 MegaLinter comments 和 detailed issues
- 额外完成验证:
- `python3 -c "from pathlib import Path; compile(Path('.codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py').read_text(encoding='utf-8'), '.codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py', 'exec'); print('syntax-ok')"`
- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --branch feat/coroutine-optimization --format json`
- `dotnet build GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore`
### 下一步