mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-13 22:25:37 +08:00
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:
parent
d369118351
commit
90b9e2a4c9
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: gframework-pr-review
|
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
|
# GFramework PR Review
|
||||||
@ -16,12 +16,12 @@ Shortcut: `$gframework-pr-review`
|
|||||||
3. Run `scripts/fetch_current_pr_review.py` to:
|
3. Run `scripts/fetch_current_pr_review.py` to:
|
||||||
- locate the PR for the current branch through the GitHub PR API
|
- 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
|
- 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
|
- 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
|
- 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.
|
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`.
|
6. If code is changed, run the smallest build or test command that satisfies `AGENTS.md`.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
@ -43,6 +43,7 @@ The script should produce:
|
|||||||
- Latest head commit review metadata and review threads
|
- Latest head commit review metadata and review threads
|
||||||
- Unresolved latest-commit review threads after reply-thread folding
|
- Unresolved latest-commit review threads after reply-thread folding
|
||||||
- Pre-merge failed checks, if present
|
- 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
|
- Test summary, including failed-test signals when present
|
||||||
- Parse warnings only when both the primary API source and the intended fallback signal are unavailable
|
- 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.
|
- 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.
|
- 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.
|
- 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
|
## Example Triggers
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ DEFAULT_WINDOWS_GIT = "/mnt/d/Tool/Development Tools/Git/cmd/git.exe"
|
|||||||
GIT_ENVIRONMENT_KEY = "GFRAMEWORK_WINDOWS_GIT"
|
GIT_ENVIRONMENT_KEY = "GFRAMEWORK_WINDOWS_GIT"
|
||||||
USER_AGENT = "codex-gframework-pr-review"
|
USER_AGENT = "codex-gframework-pr-review"
|
||||||
CODERABBIT_LOGIN = "coderabbitai[bot]"
|
CODERABBIT_LOGIN = "coderabbitai[bot]"
|
||||||
|
GITHUB_ACTIONS_LOGIN = "github-actions[bot]"
|
||||||
REVIEW_COMMENT_ADDRESSED_MARKER = "<!-- <review_comment_addressed> -->"
|
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)
|
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
|
DEFAULT_REQUEST_TIMEOUT_SECONDS = 60
|
||||||
@ -156,6 +157,10 @@ def strip_tags(text: str) -> str:
|
|||||||
return collapse_whitespace(re.sub(r"<[^>]+>", " ", text))
|
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:
|
def extract_section(text: str, start_marker: str, end_markers: list[str]) -> str | None:
|
||||||
start = text.find(start_marker)
|
start = text.find(start_marker)
|
||||||
if start < 0:
|
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]:
|
def parse_test_report(block: str) -> dict[str, Any]:
|
||||||
report: dict[str, Any] = {
|
report: dict[str, Any] = {
|
||||||
"raw": block.strip(),
|
"raw": block.strip(),
|
||||||
@ -475,11 +534,18 @@ def build_result(pr_number: int, branch: str) -> dict[str, Any]:
|
|||||||
issue_comments,
|
issue_comments,
|
||||||
lambda body: "CTRF PR COMMENT TAG:" in body or "### Test Results" in body,
|
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:
|
if not summary_block:
|
||||||
warnings.append("CodeRabbit summary block was not found in issue comments.")
|
warnings.append("CodeRabbit summary block was not found in issue comments.")
|
||||||
if not test_blocks:
|
if not test_blocks:
|
||||||
warnings.append("PR test-report block was not found in issue comments.")
|
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] = {}
|
latest_commit_review: dict[str, Any] = {}
|
||||||
try:
|
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 {},
|
"coderabbit_comments": parse_actionable_comments(actionable_block) if actionable_block else {},
|
||||||
"latest_commit_review": latest_commit_review,
|
"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],
|
"test_reports": [parse_test_report(block) for block in test_blocks],
|
||||||
"parse_warnings": warnings,
|
"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."
|
" 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("")
|
||||||
lines.append(f"Test reports: {len(result['test_reports'])}")
|
lines.append(f"Test reports: {len(result['test_reports'])}")
|
||||||
for index, report in enumerate(result["test_reports"], start=1):
|
for index, report in enumerate(result["test_reports"], start=1):
|
||||||
|
|||||||
@ -45,6 +45,9 @@ ENABLE_LINTERS:
|
|||||||
# 设置 C# 代码风格检查的参数和验证级别
|
# 设置 C# 代码风格检查的参数和验证级别
|
||||||
# ========================
|
# ========================
|
||||||
CSHARP_DOTNET_FORMAT_ARGUMENTS:
|
CSHARP_DOTNET_FORMAT_ARGUMENTS:
|
||||||
|
# 仓库根目录同时存在 GFramework.sln 与 GFramework.csproj;
|
||||||
|
# 显式指定 workspace,避免 dotnet format 在 CI 中因自动探测歧义直接异常退出。
|
||||||
|
- "GFramework.sln"
|
||||||
- "--severity"
|
- "--severity"
|
||||||
- "info"
|
- "info"
|
||||||
- "--verify-no-changes"
|
- "--verify-no-changes"
|
||||||
@ -83,4 +86,3 @@ GITHUB_COMMENT_REPORTER: true
|
|||||||
PARALLEL: true
|
PARALLEL: true
|
||||||
SHOW_ELAPSED_TIME: true
|
SHOW_ELAPSED_TIME: true
|
||||||
VALIDATE_ALL_CODEBASE: false
|
VALIDATE_ALL_CODEBASE: false
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
- 已为 `Timing` 补齐纯托管测试宿主入口,允许在 `dotnet test` 下验证 Godot 协程宿主阶段语义,而不依赖原生 `Node` 构造
|
- 已为 `Timing` 补齐纯托管测试宿主入口,允许在 `dotnet test` 下验证 Godot 协程宿主阶段语义,而不依赖原生 `Node` 构造
|
||||||
- 已补充 `GFramework.Godot.Tests/Coroutine/TimingTests.cs`,锁定暂停、segment 路由和阶段型等待指令的回归覆盖
|
- 已补充 `GFramework.Godot.Tests/Coroutine/TimingTests.cs`,锁定暂停、segment 路由和阶段型等待指令的回归覆盖
|
||||||
- 已根据 PR #259 的最新 CodeRabbit review 收口测试宿主清理对称性,并将 `TimingTests` 固定为非并行执行
|
- 已根据 PR #259 的最新 CodeRabbit review 收口测试宿主清理对称性,并将 `TimingTests` 固定为非并行执行
|
||||||
|
- 已根据 PR #259 的 `MegaLinter analysis: Success with warnings` 结果修复 `dotnet format` workspace 歧义,并增强 PR review skill 以提取此类 CI warning
|
||||||
- 下一轮优先补“仍需真实场景树参与”的归属协程 / 退树语义,或转入文档迁移收口,不再回到“Godot 宿主没有自动化回归”的旧状态
|
- 下一轮优先补“仍需真实场景树参与”的归属协程 / 退树语义,或转入文档迁移收口,不再回到“Godot 宿主没有自动化回归”的旧状态
|
||||||
|
|
||||||
## 当前状态摘要
|
## 当前状态摘要
|
||||||
@ -36,6 +37,9 @@
|
|||||||
- 针对 PR #259 的最新未解决 review 线程,已补充两项收口:
|
- 针对 PR #259 的最新未解决 review 线程,已补充两项收口:
|
||||||
- `TimingTests` 已添加 `[NonParallelizable]`,避免共享静态实例槽位在 NUnit 并行执行时互相污染
|
- `TimingTests` 已添加 `[NonParallelizable]`,避免共享静态实例槽位在 NUnit 并行执行时互相污染
|
||||||
- `Timing` 的测试清理与运行时退树清理现仅在当前实例持有共享 `_instance` 引用时才会清空单例状态
|
- `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` 个测试
|
- `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`
|
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~TimingTests" --no-restore`
|
||||||
- 结果:通过
|
- 结果:通过
|
||||||
- 备注:针对 PR #259 review 修复后的 `TimingTests` 共 `5` 个测试全部通过
|
- 备注:针对 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` 纯托管宿主
|
1. 若继续补验证,优先只做真实场景树相关的节点归属 / 退树 / `queue_free` 回归,不再重新设计 `Timing` 纯托管宿主
|
||||||
2. 当前 PR 合并前可直接回到 GitHub 处理这两条 review 线程的回复与 resolve,避免后续重复审阅同一问题
|
2. 当前 PR 合并前可直接回到 GitHub 确认最新 push 是否已消除 `MegaLinter analysis` warning,并顺手处理 review 线程的回复与 resolve
|
||||||
3. 若转入文档收口,优先清理其余 `StartCoroutine()/StopCoroutine()` 残留,并补 Godot 新入口与阶段等待的迁移对照
|
3. 若转入文档收口,优先清理其余 `StartCoroutine()/StopCoroutine()` 残留,并补 Godot 新入口与阶段等待的迁移对照
|
||||||
|
|||||||
@ -55,6 +55,14 @@
|
|||||||
- 将 `Timing` 的 `_instance` 清理改为“仅当当前实例仍持有共享单例引用时才执行”,同时覆盖运行时 `_ExitTree()` 与测试入口 `DisposeForTests()`
|
- 将 `Timing` 的 `_instance` 清理改为“仅当当前实例仍持有共享单例引用时才执行”,同时覆盖运行时 `_ExitTree()` 与测试入口 `DisposeForTests()`
|
||||||
- 额外完成验证:
|
- 额外完成验证:
|
||||||
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~TimingTests" --no-restore`
|
- `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`
|
||||||
|
|
||||||
### 下一步
|
### 下一步
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user