Slang 自动代码审查发布:slang-review-post-github 从候选 Markdown 到单条 GitHub PR Review 的完整实现
2026/9/17 13:01:18 网站建设 项目流程

Slang 自动代码审查发布:slang-review-post-github 从候选 Markdown 到单条 GitHub PR Review 的完整实现

【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang

Slang(shader-slang/slang)仓库内置了一套面向 AI Agent 的清晰度审查(clarity review)流水线,.claude/skills/slang-review-post-github/是这条流水线的最后一个环节:它把经过整合与范围过滤后的候选评论(candidate markdown),一次性提交为一条规范的 GitHub PR Review,而不是散落的线程评论。本文完整讲解该 skill 的运行方式、候选文件的格式契约、Agent 署名策略与发布失败策略,并结合仓库中 post_github_review.py 的源码与 test_post_github_review.py 的回归测试,说明底层如何做 diff 行号校验、payload 构造与"失败即中止"的行为保证。读完本文,你可以独立复现这条发布链路,并理解 Slang 仓库 PR 审查协议中自动化评论是如何被安全、可审计地投递到 GitHub 的。

一、这个 skill 在整个审查流程中的位置

Slang 仓库的 REVIEW.md 定义了一套"PR Review Protocol",其中专门有一节Clarity Review Candidate Pass:与常规的高置信度 bug/gap/question 过滤不同,清晰度审查专门处理"变更代码不清晰、内部不一致或解释不足"这类更大量的反馈。它规定了一条由多个仓库本地 skill 串联而成的流水线(位于 .claude/skills/ 目录):

  1. slang-review-clarity:生成高层算法/注释/模型层面的候选;
  2. slang-review-fine-grained-clarity:生成逐行的命名/注释/类型一致性候选;
  3. slang-review-consolidate-candidates:合并候选、消解重复与重叠;
  4. slang-review-scope-filter:按"PR 作者是否应负责"过滤候选;
  5. slang-review-resolve-judgment-calls:对标记为需要判断的候选做聚焦分析;
  6. slang-review-post-github:将幸存候选发布为一条正式 GitHub PR Review(交互式会话场景;harness 场景则并入 Step 5 的单条 pending review)。

其中 slang-review-clarity-workflow/SKILL.md 定义了各阶段的中间产物文件布局:PR 的 diff 与文件清单缓存在tmp/pr-diff.patchtmp/pr-files.txt,原始候选输出在tmp/review-candidates/pr-<number>-clarity.mdpr-<number>-fine-grained-clarity.md,整合后的规范文件(canonical file)是tmp/review-candidates/pr-<number>-clarity-workflow.md。发布 skill 的输入正是这个规范文件——所以 REVIEW.md 强调"一条 PR 只能发一条 review",且所有候选评论都必须挂到 diff 行上,而不是逐条发线程评论。

二、基本用法:一条命令发布

SKILL.md 给出的核心命令如下(以仓库根目录为工作目录):

python .claude/skills/slang-review-post-github/scripts/post_github_review.py \ --repo shader-slang/slang \ --pr <number> \ --candidates tmp/review-candidates/pr-<number>-clarity-workflow.md

该脚本刻意只依赖 Python 标准库,实际的 GitHub API 调用全部通过 shell 调出(shells out)GitHub CLI(gh)完成。文档特别指出:在本仓库的 Windows 或 WSL 环境里应使用gh.exe,脚本会按此默认值查找;也可以显式传--gh <path>或设置环境变量GH_CLI=<path>来指定可执行文件。

用户没有明确要求发布时,先加--dry-run跑一遍。脚本完整的命令行参数(由 parse_args 定义)包括:

参数说明
--repo必填,GitHub 仓库,如shader-slang/slang
--pr必填,PR 编号(整数)
--candidates必填,候选 markdown 文件路径
--gh显式指定gh/gh.exe路径,默认回退到GH_CLI或平台默认
--eventGitHub review 事件,仅允许COMMENT/REQUEST_CHANGES,默认COMMENT
--body/--body-file二选一,指定 review 正文文本或正文文件
--acting-as-bot-user允许正文不带 Agent 署名前缀(仅当 GitHub 账号本身已标识为 bot/agent)
--exclude-judgment-calls(别名--exclude-needs-human-judgment排除Status: Needs judgment call的候选
--dry-run校验并打印最终 payload,但不实际发布

关于gh可执行文件的查找逻辑,find_gh 的优先级是:显式--gh参数 >GH_CLI环境变量 > 平台默认名(Windows 或 WSL 下为gh.exe,通过读/proc/version判断 WSL,见 is_wsl)。任何一步找不到可执行文件都会直接以致命错误退出,这与后文"失败即中止"的策略一致。

三、前置条件:可发布候选必须长什么样

SKILL.md 明确规定,发布前必须先跑slang-review-consolidate-candidatesslang-review-scope-filter;如果文件里还有标记为Needs judgment call的候选,还应先跑slang-review-resolve-judgment-calls。这些前置 skill 各自负责写入不同的元数据字段(参见 slang-review-scope-filter/SKILL.md 与 slang-review-consolidate-candidates/SKILL.md):

  • scope filter 负责写入Scope decision: Direct | Contextual | Out-of-scopeScope rationale
  • consolidation 负责写入Overlap decision: Keep | Duplicate of <id> | Superseded by <id> | Merged into <id> | Needs judgment callOverlap rationale,并把重复/被取代/已合并的候选置为Status: Drop

每一条可发布候选必须同时具备

  • Status: KeepStatus: ReviseStatus: Needs judgment call(源码中常量集合POSTABLE_STATUSES还包含别名"Needs human judgment",见 post_github_review.py L24);
  • Scope decision: DirectScope decision: Contextual,外加非空的Scope rationale
  • Overlap decision: KeepOverlap decision: Needs judgment call,外加非空的Overlap rationale
  • Location: path:lineLocation: path:start-end,指向 PR 版本(right side)中真实存在于 GitHub diff 里的行;
  • 一个Proposed comment:严格引用块(strict blockquote)。

Status: Drop的候选会被直接忽略;但任何缺失、含糊、未过滤的状态(例如仍停留在生成阶段的Status: Proposed)都会让脚本在创建任何 GitHub review之前就整体失败。这一点在 parse_candidates 中实现:每个候选独立收集错误信息,最后汇总为一条candidate file is not postable的致命错误一次性报出;若过滤后没有任何可发布候选,也会报no postable candidates found

此外 parse_candidates 还会做两道文件级检查:候选标题必须符合### <ID>: <title>形式(HEADING_RE要求 ID 为大写字母+数字,如C001FG014),且候选 ID 不允许重复——重复 ID 会以duplicate candidate ID(s)直接致命退出。

3.1 Location 的解析规则

Location值由 parse_location 用正则^?(.+?):([0-9]+)(?:-([0-9]+))??$解析,要点:

  • 路径部分可以带反引号包裹,行号部分是 PR 版本文件中的右侧行号
  • 省略end时起止行相同(单行评论);
  • 行号必须从 1 开始且end >= start,否则报invalid Location line range。测试用例 test_invalid_location_fails_before_posting 验证了`source/file.cpp:0`这类值会在解析期就被拒绝。

3.2 Proposed comment 的严格引用块格式

候选的评论正文必须以Proposed comment:行开头,随后是严格引用块。parse_proposed_comment 的解析规则相当严格,这解释了文档里"必须用>写空行、不要用 lazy continuation"的要求:

  • 每一行必须以>开头,>后可选一个空格;
  • 第一个非空行必须是>开头,否则报has Proposed comment but no blockquote body
  • 引用块开始后遇到第一个空行即终止该段;空行之后只允许出现Notes:段(见 validate_proposed_comment_tail),出现任何其他文本都会报non-note content——这是为了防止本应属于评论的句子被静默丢弃;
  • 最终正文去引号后不能为空,否则报empty Proposed comment body

对应地,test_stray_text_after_proposed_comment_terminator_fails 专门回归了"终止空行后混入游离文本"这一陷阱;test_lazy_proposed_comment_continuation_fails 则确认 Markdown 的 lazy blockquote 续行会被拒绝。

四、Review Body:整体评论正文的格式与 Agent 署名

4.1## Review Body小节

若未通过--body/--body-file提供正文,脚本会回退到候选文件中的## Review Body小节。SKILL.md 给出的示例格式是:

## Review Body > Codex-authored clarity review: This PR needs more work before it is easy to review. > > ## Main Concerns > > - The central invariant is not stated. > - The test comments do not explain the intended behavior. ## Kept

由 extract_review_body 实现,规则为:

  • 小节内容必须是严格引用块:去掉首尾空行后,到下一个顶层章节(任何##开头行或候选标题### XXX:)为止的每一行都必须以>开头;
  • 空行写作>,小节内的 Markdown 标题写成> ## Details这样的被引用行;
  • 小节为空时致命报错;解析出的正文去引号后即可包含正常 Markdown(测试 test_parse_candidates_extracts_review_body_and_comment 验证了去引号后## Details等标题得以还原)。

4.2 Agent 署名(Authorship Label)策略

这是本 skill 的一个核心政策:当 review 经由人类用户的 GitHub 账号发出时,review 正文必须在开头声明该 review 由 Agent 生成,并且由 Agent 自己写进正文——发布脚本只负责校验,不会替你追加署名文本。

要求的格式为(大小写不敏感):

<agent name>-authored <optional review type> review:

细节规则(与 AGENT_REVIEW_ATTRIBUTION_RE 正则一一对应):

  • authored之前的分隔符可以是-,也可以是空白(如Claude 3.7 Sonnet authored review:);
  • review type 可省略,省略时authoredreview之间是单个空格(如Codex-authored review:);
  • Agent 名可以是任意不含换行的文本,长度上限为 50 个 Unicode 标量值(常量MAX_AGENT_IDENTITY_SCALARS),可以带标点,例如OpenAI/GPT-4.1 (reviewer)-authored clarity review:

文档给出的三个合法示例:

GPT-4.1-authored clarity review: Claude 3.7 Sonnet authored review: OpenAI/GPT-4.1 (reviewer)-authored clarity review:

测试文件里有一组针对性用例:test_prepare_review_body_accepts_*系列验证各种合法写法被接受,而 test_prepare_review_body_rejects_overlong_agent_identity 用 51 个字符的 Agent 名验证了长度上限、test_prepare_review_body_rejects_multiline_agent_attribution 验证了换行会被拒绝(has_agent_review_attribution 中 Agent 名正则限定为[^\r\n]+?)。

--acting-as-bot-user仅在初始用户提示或环境明确表明 GitHub 会把 review 归属到 bot/agent 账号时才使用;该选项会禁用正文署名要求。此时若正文缺失,default_review_body 会生成一段兜底文本Clarity review.,并在存在Needs judgment call候选时自动附加一句说明(说明这些候选因被认为可信而保留)。

五、Review 事件选择:COMMENT / REQUEST_CHANGES / APPROVE

SKILL.md 的Review Result一节给出了明确的事件策略:

  • 默认使用COMMENT。本仓库的自动化审查政策把 bot 生成的 review 视为非阻塞的咨询性(advisory)review,可能直接拒绝或撤销自动化的REQUEST_CHANGES
  • 只有当初始用户提示明确要求"阻塞式清晰度 review"、所用账号的本地项目政策允许自动化阻塞 review、且没有使用--acting-as-bot-user时,才使用REQUEST_CHANGES
  • 本工作流禁止使用APPROVE(slang-review-clarity-workflow/SKILL.md 的Posting Defaults同样重申了这条)。

这条策略在代码里有硬性约束:VALID_EVENTS = {"REQUEST_CHANGES", "COMMENT"}parse_argschoices限制),并且 main 在最前面就检查--acting-as-bot-user--event COMMENT的组合关系——bot 账号只能发COMMENT,否则直接失败。测试 test_main_rejects_bot_user_request_changes_before_gh_lookup 更进一步证明:这条策略校验发生在查找gh可执行文件之前,即不触碰任何外部命令。

关于Needs judgment call候选,默认行为是包含在 review 中,只有用户明确要求时才用--exclude-judgment-calls排除。文档给出的理由是:整条工作流是自动化的,保留一条可信的、带有不确定性的评论,好过把它静默丢掉。

六、发布到 GitHub 的内容与行定位

6.1 只发评论正文本身

Posted Comment Text一节规定:发布的内容只有拟议的评论正文,不包含候选 ID、status、confidence、scope、notes、source context 或任何其他流程元数据。也不要用 GitHub 链接或代码块替代真正的 diff 行评论——脚本必须通过 GitHub review API 把每条评论挂到对应的 diff 行/行区间上。若某条候选关注的是 diff 之外的代码或契约,其Location应指向最近或最合理的可评论 diff 行,而拟议评论本身应说明真正的目标;脚本只负责校验 GitHub 能否把评论挂到给定行上。

6.2 用 PR diff 校验 Location:脚本的核心安全机制

发布前,validate_locations 会完成三件事:

  1. 确定 head commit:调gh api repos/<repo>/pulls/<pr>取 PR 元数据,从head.sha拿 head commit id;拿不到就致命退出(review API 的 payload 必须绑定 head commit)。
  2. 拉取各文件的 patch:调gh api --paginate --slurp repos/<repo>/pulls/<pr>/files?per_page=100(分页响应由 flatten_pages 归一化为文件条目列表),对每个文件的patch文本解析出"右侧可评论行号集合"。
  3. 逐候选核对行号:候选的Location路径必须出现在 diff 数据中,且start_lineend_line的每一行都必须在可评论集合里,否则报missing line(s): ...并整体失败。

右侧行号的解析规则在 right_lines_from_patch:从每个 hunk 头@@ -l,s +l,s @@HUNK_RE)拿到右侧起始行,随后+行(新增)与 行(上下文)计入可评论集合并使行号递增,-行(删除,属于左侧行号)不计数,\ No newline at end of file忽略。这解释了文档中"Location 必须指 PR 版本中存在、且在 GitHub diff 里出现的行"的约束——GitHub 不允许评论被删除的左侧行。

大 diff 回退路径:GitHub 的 files API 对过大的文件会省略patch字段。此时脚本把缺 patch 的文件路径收集起来,回退到gh api -H "Accept: application/vnd.github.v3.diff" repos/<repo>/pulls/<pr>拉取整份 unified diff(run_full_pr_diff),再由 right_lines_from_full_diff 按diff --git/+++头切分文件并解析行号。若整份 diff 端点返回 HTTP 406(diff 过大),脚本会给出一条针对性的可操作错误:缩小候选集、拆分 PR,或先本地校验。对应的回归测试包括 test_validate_locations_uses_full_diff_when_file_patch_is_missing 与 test_validate_locations_reports_full_diff_size_limit。

测试 test_full_diff_parser_keeps_added_lines_that_look_like_headers 揭示了一个有意思的边界:新增的代码行本身可能以+++开头(例如拼接 diff 头字符串的代码),解析器只在尚未处于某文件 hunk 内时才把+++视为文件切换头,避免把这类行误判为新文件边界。

6.3 Payload 构造与提交

校验通过后,build_payload 组装 GitHub PR review API 的 JSON payload:

  • commit_id:head commit SHA;
  • eventCOMMENTREQUEST_CHANGES
  • body:通过署名策略校验后的 review 正文;
  • comments:每个候选一条,字段为pathbodyline(= end_line)、side: "RIGHT";多行评论(start != end)额外带start_linestart_side: "RIGHT"

post_review 则把 payload 写入临时 JSON 文件,通过gh api --method POST --input <temp.json> repos/<repo>/pulls/<pr>/reviews提交,随后清理临时文件。--dry-run模式在 main 中直接打印格式化后的 payload 并返回 0,不发起任何发布调用——这也是 SKILL.md 建议"未明确要求发布时先 dry-run"的原因:dry-run 会走完包括 diff 校验在内的全部校验路径,是零副作用的完整彩排。测试 test_build_payload_preserves_review_body_and_maps_range 精确断言了多行区间到line/start_line的映射。

七、失败策略(Failure Policy)与工程实现细节

SKILL.md 的Failure Policy要求脚本在以下任一情况下大声失败且不做任何动作:无法解析候选文件、无法将可发布候选的 Location 对到 PR diff、无法确定 PR head commit、找不到或无法执行 GitHub CLI 可执行文件。源码层面这由FatalError异常统一承载(post_github_review.py L33-L35),main捕获后以error: <message>打印到 stderr 并以退出码 2结束(L794-L799),任何失败的校验都发生在post_review之前,从而保证"要么零副作用,要么完整发布"。

其他值得注意的实现细节:

  • UTF-8 解码:run_command 对所有子进程统一text=True, encoding="utf-8",避免依赖进程 locale 导致gh输出的 JSON 解码错乱;test_run_command_uses_utf8_text_decoding 专门锁定了这个行为;
  • 启动失败包装:子进程启动抛OSError(如可执行文件不可启动)会被包装成FatalError,而不是裸露的 traceback(test_run_json_wraps_command_startup_failure);
  • 元数据边界:候选元数据只从Context:/Proposed comment:/Notes:之前解析(CANDIDATE_METADATA_BOUNDARIES),Notes:里写- Status: Drop之类的行不会覆盖头部元数据(test_notes_metadata_does_not_override_candidate_metadata)。

八、修改脚本后的回归测试

SKILL.md 最后一条硬性要求:改动发布脚本之后必须运行

python .claude/skills/slang-review-post-github/scripts/test_post_github_review.py

该测试基于标准库unittest,通过importlib从同目录直接加载post_github_review.py(见 测试文件 L13-L20),不依赖 pytest。测试覆盖了本文所述的几乎所有契约:严格引用块解析、lazy 续行拒绝、## Review Body的章节终止边界、缺失元数据/非法 Location 的前置失败、署名前缀的各种合法/非法形态、--event默认值为COMMENT、bot 账号 +REQUEST_CHANGES的组合拒绝、diff 右侧行号解析(含+++伪头边界)、files API 缺 patch 时的整份 diff 回退、HTTP 406 大 diff 报错,以及不可评论行号的missing line(s)失败路径。由于测试对gh api调用做了打桩(fakerun_json/run_command),整套回归不需要真实凭据即可离线运行。

九、小结:一套可审计的自动化审查发布链路

把 SKILL.md 的要求与 post_github_review.py 的实现对照来看,这条链路的设计目标是:让 Agent 生成的清晰度评论在格式契约(严格引用块、完整元数据、可映射的 Location)、署名合规(正文显式声明 Agent 作者身份)、事件策略(非阻塞 COMMENT 优先,bot 账号禁止阻塞)三个维度上都先于网络调用被本地强制校验,任何不满足都整体失败、零副作用。配合前置 skill 写入的 Scope/Overlap 判断字段与 REVIEW.md 中"每 PR 一条 review、评论必须挂在 diff 行上"的协议,Slang 仓库从而把"AI 审查"从随手发评论的形态,约束成了一条可复现、可测试、可人工审计的发布流程——这也是该 skill 对任何希望在自己的仓库中落地 Agent 代码审查的团队最有参考价值的部分:先用窄格式 + 强校验把候选评论结构化,再用一个只依赖标准库的小脚本完成与平台 API 的对接。

【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询