评论转规则 (Comments to Rules)
该技能将仓库的 PR 审查评论历史转化为按语言区分的代码风格规则文件。它还会添加从本地代码库中推断出的规则,最后提供使用这些规则进行审查的功能。
请针对目标仓库运行此技能:即用户当前工作的目录,或他们指定的路径或远程仓库。该技能存在于一个独立的仓库中。您只需按需运行一次,它不会安装到目标仓库中。
何时使用此技能
- 用户要求从 PR 审查历史中生成、构建或更新编码风格规则。
- 用户希望用过去的代码审查反馈来填充
.claude/rules/目录,而不是手动编写。 - 用户想知道他们的审查者实际执行了哪些约定,并希望将其转化为代理可以遵循的规则。
不要用此技能来审查某个特定的 PR 或差异(diff)——请使用 code-review 技能来实现。不要用它来编写通用的仓库文档——请使用 init 技能。
前提条件
在开始之前,请检查以下内容。如果任何检查失败,请停止并给出清晰的消息。
gh已安装并已通过身份验证 (gh auth status)。jq已安装。- 目标目录是一个带有 GitHub 远程仓库的 Git 仓库 (
gh repo view在该目录下执行成功)。
如果 gh auth status 失败,请不要自行运行 gh auth login 或 gh auth refresh。请告知用户运行它,然后停止。这些命令可能会打开交互式浏览器流程或设备代码提示——只有用户应该启动这些流程。
操作步骤
第一步 — 获取已合并 PR 的审查评论
针对目标仓库运行 scripts/fetch_comments.sh。它会运行 scripts/lib/ 下的一系列独立脚本:列出 PR、获取评论、过滤、格式化、分组,然后写入结果。运行该脚本时加上 --help 参数可查看其标志(PR 状态、PR 数量上限、并发数、超时时间)。完整规范:references/fetch-plan.md。
- 该脚本默认将评论转储(dump)以 Markdown 格式写入
~/.cache/comments-to-rules/{owner}-{repo}/。 - 使用
--output参数可以选择不同的位置。如果指定一个目录,则每个语言会得到一个块文件。如果指定其他路径,则得到一个合并文件。 - 在目录模式下,脚本已经按语言对转储进行了分组:
<language>/comments-NNN.md,每块 250 条评论。这可以确保第二步中的每次Read调用成本较低。 - 脚本会在你看到之前丢弃长度少于 10 个字符的评论(使用
--min-length参数可更改此值)。它仅衡量纯文本部分,排除任何```suggestion块。这是一种针对纯粹噪声——如lgtm、nit、+1等——的机械过滤。它不能替代第二步中基于判断的过滤。 - 包含
```suggestion块的评论将完全跳过长度过滤器,无论其文本有多短。建议本身就是一个信号,即使旁边没有文本——参见第二步。该评论还会在其上方获得一个原始代码(Original code):块,显示 GitHub 附加到该评论的周围代码,即建议所替换的内容。 - 脚本会报告其自身进度:检测到的仓库、输出位置、每个获取批次以及写入的每个文件。请向用户展示此进度。不要静默运行脚本。
- 默认的 PR 限制是 1000 个,按最近优先顺序排列。传递
--all可不设上限。如果 PR 数量超过默认限制,脚本将以状态码 2 退出并显示NEEDS_INPUT消息。询问用户是要使用最近的 1000 个 PR 还是全部,然后根据选择重新运行。 - 如果用户在大型获取过程中中断脚本(Ctrl-C),脚本将在当前批次完成后停止。它将写入已获取的内容,并打印未获取到的 PR 范围。告知用户这一点,并提供稍后从该范围继续的选项。
- 如果缺少
gh、jq或timeout,或者gh未通过身份验证,请停止并告知用户如何修复。不要自行运行gh auth login或gh auth refresh——请参见前提条件。 - 如果仓库没有已合并的 PR,请告知用户,然后跳至第三步(仅本地文件检查)。
第二步 — 过滤评论并将其分组为规则
转储已按语言分组:来自第一步的每个语言一个子目录或部分。它也已完成长度过滤。请自行阅读每种语言的评论——不要将此步骤脚本化。
- 丢弃长度过滤器遗漏的噪声:带有额外词语的批准(“LGTM” 加上评论)、机器人评论、以及没有可操作指导的讨论。此步骤需要判断力。长度过滤器则不需要。
- 保留陈述或暗示编码约定的评论——例如,“这里用
const而不是let”,“始终处理此错误”,或“倾向于提前返回”。 - 当两个或更多评论陈述了相同的规则时,将它们合并为一条规则。
- 包含
```suggestion块以及其上方原始代码(Original code):块的评论,包含来自该仓库自身历史的真实前后对比对。阅读差异(diff),而不只是旁边的文本——差异本身可以陈述一个规则,即使文本说得很少或什么都没说。将一个if/else转换为卫语句(guard clause)并返回的建议,本身就陈述了“倾向于提前返回”,无论是否有评论如此说明。当你以这种方式保留规则时,请将差异视为来源,而不仅仅是某个单独陈述规则的示例。 - 如果一条规则确实需要代码示例,请参阅
rule-template.md了解何时值得添加。优先使用真实的原始代码(Original code):和建议(suggestion)对,而不是编造的——它本身就是一个真实的notLikeThis/ratherLikeThis对。 - 陈述规则,然后将该对作为代码展示。不要叙述它的来源——不要写“一位审查者在 PR #531 上建议了这一点”,也不要写“一位审查者的真实建议改变了……”。规则和代码就是内容。PR 编号和审查者不是。
一种语言可能只有很少的可操作评论,不足以支持建立一条规则。一个粗略的指导原则是,将少于三条幸存规则视为太少。这种情况可能出现在 PR 历史稀疏、仓库较小或较新、或者是审查者很少触及的语言中。在这种情况下,不要强行用仅有的少量内容生成规则文件。记录哪些语言内容稀少。保留少数幸存规则。让第三步来编写该语言文件的其余部分。这包括极端情况:一种语言没有任何幸存的评论,但仍然会得到一个文件,完全由第三步编写。
在编写某个语言的规则文件之前,请检查目标仓库的 .claude/rules/ 目录中是否存在任何已经覆盖该语言的现有文件。不要只检查确切的 code-style-{language}.md 名称——请阅读每个现有文件的 paths: 前置元数据(frontmatter),并将其与该语言的文件扩展名进行比较。
- 如果某个现有文件的
paths:已经覆盖了该语言,请询问用户(使用AskUserQuestion):更新该文件,还是在其旁边创建一个新的code-style-{language}.md?对于每个匹配的语言询问一次,而不是每个文件询问一次。 - 如果你更新一个现有文件,请先阅读它。将你的新发现合并进去。保留仍然有效的现有规则。如果新发现与现有规则冲突,请停止并询问用户保留哪一个——永远不要静默覆盖。
- 如果没有现有文件覆盖该语言,则创建
.claude/rules/code-style-{language}.md。
无论哪种方式,请遵循 references/rule-template.md 中的结构:一个 paths: 前置元数据 glob、一个可选的上下文注释、以及规则列表项。默认情况下,用纯文本陈述一条规则即可——只有在规则没有示例会不清楚时才添加包含代码块的“好/坏”示例。如果真实的 PR 评论提供了一个规则确实需要的清晰示例,请逐字引用它,而不是改写。以符合 ASD-STE100 规范的文本编写每个规则文件。如果已安装,则调用 asd-ste100 技能进行此检查。如果未安装,请在文件中注明你跳过了此检查。
第三步 — 本地文件检查
扫描目标仓库的文件,尊重 .gitignore,并按语言分组。扫描代码库中存在的每种语言,而不仅仅是第二步已覆盖的语言。从代码本身推断约定:命名风格、导入顺序、格式、测试结构、错误处理以及类似模式。
- 对于第二步已充分覆盖的语言,在“本地推断(Locally Inferred)”标题下添加这些发现。保持此部分与上方的 PR 评论规则在视觉上分离,以便每条规则的来源保持清晰。
- 对于第二步标记为内容稀少或跳过的语言,此检查是它们的主要来源——而非补充。这涵盖了没有 PR 评论的语言,以及完全没有合并 PR 的仓库(参见第一步)。仅根据本地推断来编写文件。在文件的上下文注释中明确说明这一点——参见
rule-template.md——例如:“这些规则来自本地代码约定。该语言的 PR 审查历史过于稀疏,无法从中提取规则。”不要将这些规则标记为来自 PR。不要将本地发现呈现为像是审查者所说的。 - 如果本地代码没有遵循一致的约定,不要编造一条规则。而是将问题添加到“未决问题(Open Questions)”部分——参见
rule-template.md——并询问用户应采用哪种约定。 - 不要为没有任何可操作规则可陈述的语言编写规则文件——无论是来自第二步还是这一步。一个规则文件,如果其唯一内容就是解释为什么没有什么可说的,那它就不是一个规则文件。这种情况最常见于格式化工具完全控制的语言,没有 PR 历史,也没有可推断的手写约定——例如,仅由 Prettier 格式化的配置或锁定文件,如 JSON。在这种情况下,跳过该文件,并在第四步的总结中用一行理由列出该语言。
如果第一步没有找到任何合并的 PR,那么每种语言的规则文件都完全来自此步骤。将此视为新仓库或审查较少的仓库的正常流程,而不是备选方案。
对于一个拥有多种语言的仓库,将某种语言的第二步和第三步工作委托给子代理是一个合理的选择。 这样,每种语言就能获得自己独立的上下文来阅读评论和扫描文件。如果你这样做,请告诉子代理其最终报告必须包含的内容:
- 语言,以及其写入或更新的文件路径。
- 总规则数量。
- 它使用了多少条评论来创建规则——一个持续更新的总数,每次保留或丢弃评论时更新。不要事后通过重新读取文件来重建此计数。
第四步仅根据这些报告的数字构建总结。不要重新读取已完成的规则文件来统计其规则或确认其内容。编写该文件的子代理已经知道这两个数字。
保持这种委托是扁平化的——只有一层,绝不嵌套。 直接从主会话中为每种语言分派最多一个子代理。不要让语言子代理生成自己的子代理来分割大量评论,也不要要求它这样做。有一次审计发现了一个嵌套树——主会话,然后是语言代理,然后是块读取器子代理,大约有 20 个代理深度。管理它花费了该次运行实际 token 成本的 16%,纯粹用于协调:轮询代理状态、发送保活心跳、逐个追踪掉队者。这个数字甚至还不包括主会话在检查之间自身叙述的成本。一种语言拥有的评论数量超过一个子代理上下文舒适容纳量,并不是增加一层树的理由。这是一个理由,让那个子代理在其自身上下文中顺序读取其块文件。那个上下文可以容纳的内容远多于主会话在任何时候需要跟踪的内容。
第四步 — 确认规则文件已完成
在你提供任何进一步的操作之前,请明确告知用户规则文件写入已完成。这与第五步中提供的审查邀请是分开的事件,而不是其引导部分。将其作为独立消息陈述,并附带包含以下内容的总结:
- 文件。 你在此次运行中写入或更新的每个
.claude/rules/code-style-{language}.md。 - 覆盖的语言。 这些文件中包含的不同语言。
- 获取的 PR。 第一步自己打印输出中的 PR 数量。读取此数字——不要自己重新计算。
- 获取的评论。 第一步自己打印输出中的总评论数量——经过长度过滤后、语义过滤前的数量。读取脚本的“在 M 个语言组中写入了 N 条评论”这一行。不要通过读取评论转储来推导此数字。
- 使用的评论。 经过第二步语义过滤并形成规则的评论数量。此数字小于“获取的评论”——大多数获取的评论最终是噪声、讨论或你已经捕获的规则的重复。从你过滤时按语言维护的运行总数中获取此数字——参见第三步中关于委托给子代理的说明。不要通过重新读取已完成的规则文件并统计来获取。
- 创建的规则。 此次运行中你写入的所有文件中规则的总数,包括来自 PR 和本地推断的。从你按语言跟踪的计数或从每个子代理报告的计数中求和。应用与“使用的评论”相同的规则:在工作时计数,永远不要通过重新读取文件来重新计数。
- 使用的 Tokens。 将此作为估计值陈述,并说明这一点。你无法可靠地读取会话中的确切 token 使用量。将估计值基于一个粗略的代理:将从评论转储和本地文件中读取的字符数加上写入规则文件的字符数除以大约 4。告知用户这是粗略估计,不是计费数字,并建议他们运行
/cost以获取精确数字。 - 跳过的语言。 第三步选择不为其实施规则文件的任何语言——从任一步骤都没有可操作规则,或仓库中文件太少不值得为其创建专用文件——每个语言附上一行理由。
示例:
规则文件已完成。
- 写入了
.claude/rules/code-style-python.md,更新了.claude/rules/code-style-shell.md(共 2 个文件)- 语言:python, shell
- 获取了 342 个已合并 PR,118 条评论通过了过滤,其中 23 条用于创建规则
- 共创建了 19 条规则
- 本次运行估计使用了约 28K Tokens(粗略估计——运行
/cost获取精确数字)- 跳过了 json——仅为配置文件
要让我用这些新的规则文件对仓库进行审查吗?