关于 review-tickets
Review Tickets 将 review-deep、review-full 或 review-diff 的运行结果整理为经过检查的工单文件。它依据被审查的提交重新核对发现,按可执行修复合并问题,并生成供审核或发布到 Jira 的内容。
主要功能
- 01按文件将发现重新拆分
- 02依据审查代码判断发现
- 03合并可由同一修复解决的单元
- 04核验代码引文与工单位置
- 05导出 Markdown 和 JSON 工单
适用场景
- 01从代码审查结果整理可执行工单
- 02筛除与被审查提交不符的发现
- 03在发布到 Jira 前检查工单
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jimweller/ clanker-code-review-plugin --skill review-tickets安装后在 Claude Code 中输入/review-tickets确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
prompts/judge.txtsibling.txtscripts/apply.pyassemble.pybundle.pychecks.pycommon.pydupscreen.pyevidence.pyjudge.pyplan.pypool.pyreadback.pyrecut.pysiblings.pywiki.py详细介绍
STARTER_CHARACTER = 🧾
审查工单
将一次 review-deep、review-full 或 review-diff 运行转换为工单文件,让人或模型无需重新阅读审查内容即可采取行动。工单中的每句话都来自审查者本人,经过阅读代码的裁决者保留、收窄或删除。唯一新增的文字是摘要。
输入是一个 review-deep、review-full 或 review-diff 运行目录。该目录包含 run.json、findings.jsonl、issues.jsonl、verify/results.jsonl、verify/second.jsonl 和 checkout/,后者是被审查提交的干净检出副本。此技能写入的所有内容都放在 <run>/tickets/ 下。脚本位于与此文件相邻的 scripts/ 目录中,并使用 Python 标准库,但 dupscreen.py 除外,它通过 uv 运行。
参数
review-deep、review-full 或 review-diff 打印的运行目录。以下每条命令都将其用作 $RUN。如果调用时未提供运行目录,且恰好只有一个候选目录,步骤 1 会自动发现它;不会暂停询问。
要求
PATH中有claude,并已登录到某个提供方。每个裁决者都是一个claude -p进程,会继承此环境,因此opus和fable之类的模型别名会通过与发起会话相同的提供方设置解析。- 有
git,且run.json中的repo指向的仓库包含被审查的提交。 - 重复项筛查需要
uv。
run.json 在 models 下以 [alias, effort] 对的形式设置模型。默认值为:裁决者使用 xhigh 的 Opus,批判者使用 high 的 Fable,同类检查者使用 xhigh 的 Opus。concurrency 默认值为 50。
流程
将每个模型步骤作为后台 Bash 调用运行。所有调用完成后,线程池会返回,完成通知就是继续执行的信号。切勿使用 sleep 轮询正在运行的步骤。直接将步骤命令作为后台调用运行。切勿使用 nohup 或尾随的 & 将其分离,也不要用 tail -f | grep -m1 等待日志行。tail 只有在下一次写入时才会退出,因此日志写入最后一行后,该调用会一直挂起到超时。
步骤 1:预检与计划
S="${CLAUDE_SKILL_DIR}/scripts" # replace ${CLAUDE_SKILL_DIR} with this SKILL.md's own directory on a non-Claude harness
[ -f "$S/plan.py" ] || { echo "scripts not found at $S"; exit 1; }
PF="${CLAUDE_SKILL_DIR}/../review-deep/scripts" # preflight.py has one copy, in review-deep
[ -f "$PF/preflight.py" ] || { echo "scripts not found at $PF"; exit 1; }
RUN="<user-provided run directory, or empty>"
if [ -z "$RUN" ]; then
cache="${XDG_CACHE_HOME:-$HOME/.cache}"
candidates=$(find "$cache/review-deep" "$cache/review-full" "$cache/review-diff" -mindepth 1 -maxdepth 1 -type d 2>/dev/null \
| while read -r d; do [ -f "$d/run.json" ] && echo "$d"; done)
n=$(printf '%s\n' "$candidates" | grep -c .)
if [ "$n" -eq 1 ]; then
RUN="$candidates"
echo "no run directory given, one candidate exists, using $RUN"
else
echo "no run directory given and $n candidates exist, name one explicitly:"
printf '%s\n' "$candidates"
exit 1
fi
fi
python3 "$PF/preflight.py" --tier tickets --run "$RUN" || exit 1
python3 $S/plan.py "$RUN"
preflight.py --tier tickets 会检查 run.json 是否存在、仓库和被审查的提交是否仍然存在(根据 review-full 和 review-diff 的 init_run.py,快照运行两周后,提交可能会被 git gc 清理),以及 checkout/、issues.jsonl 和 verify/results.jsonl 是否全部存在,因为任何一种输入都必须先完成 review-deep 的步骤 5 链路,才能开始生成工单。$S 防护会捕获不完整或损坏的插件或技能安装。
当某个问题的审查评级达到或超过门槛(默认是 High),且验证器已确认该问题时,就会为其制定计划。工单严重程度取验证器和二次检查评级中较低者,即使低于门槛也如此。tickets/decisions.json 可以列出 skip_issues,用于跳过已在其他地方跟踪的问题;也可以列出 include_issues,用于纳入首轮判定不可用的问题,例如占位答案。裁决者会自行验证纳入的问题。
步骤 2:重新切分为单元
python3 $S/recut.py $RUN
一个单元是某个文件中属于同一问题的发现。某个主题型问题合并了分布在 24 个文件中的 77 条发现,生成的工单所引用的位置无法支撑其主张。按文件切分为单元解决了这个问题,而步骤 4 只会在同一项更改能够修复多个单元时才将它们重新合并。
步骤 3:裁决并编辑
python3 $S/judge.py $RUN # every unit, background
python3 $S/apply.py $RUN
python3 $S/judge.py $RUN --critical # second model on units still Critical, background
python3 $S/apply.py $RUN
裁决者会阅读代码,尝试反驳每项主张,并将每条发现标记为保留、编辑或删除。保留的发现必须指出支撑每项主张的代码。编辑只能修改与代码矛盾的部分。每项编辑和删除都要引用代码,脚本会在接受答案前,针对被审查提交中的 Git 内容检查每条引文。apply.py 会写入 tickets/edits.json。如果裁决者反驳某个单元、第二个模型不同意裁决结果,或引文并非来自代码,该单元就会被排除。
judge.py --ids FILE 会重新运行 JSON 列表中的单元 ID,并保留其他所有答案。
步骤 4:合并由同一修复解决的同级项
python3 $S/siblings.py $RUN # background
python3 $S/judge.py $RUN --merged # background
python3 $S/apply.py $RUN --merged
按文件拆分也会把调用方和被调用方中出现的同一缺陷拆开。对于每个包含两个或更多就绪单元的问题,裁定者会将缺陷可通过在同一位置进行一次更改来解决的单元分组。在两个独立位置出现的相同模式仍保留为两个工单。随后会再次对合并后的工单进行整体裁定,以检查其新摘要并编辑其发现项。合并后的工单会将每个发现项保留为单独的项目符号,因此调用方和被调用方都会列出。
步骤 5:汇总并检查
python3 $S/assemble.py $RUN
python3 $S/checks.py $RUN
assemble.py 还会在运行目录中与 report.md 并排写入 tickets.md:内容与这些工单相同,采用纯 Markdown 格式,开头统计各视角和优先级的工单数量,随后按优先级列出每个工单及其发现项和代码。在步骤 7 之后重新运行步骤 5 会重写该文件。
在发布任何内容之前,checks.py 必须打印 problems 0。它会逐字节对比每个代码块与 git 中的内容,检查每个位置,并标记 Jira 无法渲染的标记。
步骤 6:筛查重复项
使用任意 Jira 客户端将目标项目的问题导出为 JSON,每个问题对应一个对象,包含 key、summary 和 description,这些字段可以位于顶层,也可以位于 fields 下。然后:
uv run --with scikit-learn --with numpy python $S/dupscreen.py $RUN existing.json
筛查会按共同词汇打印成对的问题,供人工阅读。大多数问题对共享的是主题,而非缺陷。
步骤 7:记录决定
将操作员的决定写入 tickets/decisions.json,然后重新运行步骤 5。
| 键 | 效果 |
|---|---|
drop | {ID: reason}。移除该工单,例如因为它与现有问题重复。 |
defer_priorities | 一个列表,例如 ["Low"]。这些工单仍会汇总并通过检查,但不会出现在 tickets/postable.json 中。 |
skip_issues、include_issues | 由步骤 1 读取。 |
步骤 8:发布
只有在操作员明确同意后才能发布。使用可用的任意 Jira 客户端。以下规则在 385 个工单上经过验证。
- 按优先级逐个发布工单。将 Critical 映射为 Highest,High 映射为 High,Medium 映射为 Medium,Low 映射为 Low。以 Jira wiki 标记语法发送
summary、type、映射后的优先级、labels和description。 - 创建调用返回后,立即将工单 ID 和新键追加到日志中,然后再执行其他操作。
- 使用 ADF 格式的描述读回每个工单,并运行
python3 $S/readback.py TICKET_JSON FIELDS_JSON。首次出现不匹配时立即停止。有一次运行中,两个由 en dash 连接的代码跨度被渲染成一个跨度。检查器在接下来的 300 个工单重复出现该问题之前发现了它。 - 首次出错时立即停止。仅在收到 429 时,等待 Retry-After 指定的时间后重试。超时或 5xx 错误可能已经创建了问题,因此重试前必须先核对。
- 每次恢复发布前,都要根据本次运行的工单标签(从
run.json读取review-deep、review-full或review-diff)搜索并核对日志。Jira 搜索不会返回已归档的问题,因此有意归档的问题不会被视为意外问题。 - 单独发布第一个工单,逐字段读回,并确认匹配后再继续。
步骤 9:打包延后处理的工单
python3 $S/bundle.py $RUN
tickets/bundle/ticket.json 描述一个涵盖所有延后工作、包含每个类别标签的工单;tickets/bundle/<name>-deferred-defects.md 则包含每个延后工单的摘要、类别和发现项。发布该工单,附加该文件,并将下载的附件与本地文件逐字节比较。
Jira wiki 标记
组装器会处理这些情况,checks.py 会标记任何遗漏项。每项都经过发布后再读回验证。
- 在
{{...}}内,Jira 会移除{ } [ ] * | !前的转义反斜杠,并保留其他字符前的反斜杠,因此{{"a\"b"}}会显示为"a\"b"。 - 只包含一个反斜杠的代码片段没有可用的编码方式。
{{\}}会丢失等宽格式,{{\\}}会显示两个反斜杠,而\不会被解码。组装器会写入单词 backslash。 - Jira 只会在特定字符旁关闭
{{...}}。已确认的字符包括其后的空格、, / . ' - ; ),以及换行符;其前的空格、/、(,以及换行符。组装器会在代码片段与任何其他相邻内容之间插入空格。 - Markdown 反引号序列只会与长度相同的序列配对。问题描述中直接出现的
```json会保留为文本。 - 对于
javascript json yaml bash python sql csharp以外的代码语言,会写成none,因为未观察到其他语言名称能正确渲染。
设置依据
| 设置 | 依据 |
|---|---|
| 由 Opus 以 xhigh 担任评审 | 对于包含 10 句已知错误句子的 10 个单元,Opus xhigh 以 $3.84 移除了 10 句,Fable medium 以 $6.93 移除了 10 句,Fable high 以 $9.65 移除了 10 句,而 Opus high 以 $1.72 移除了 9 句。完整运行中,618 个单元在 50 个并发任务下耗时 17 分钟,按标价计费 $188.59。 |
| 每个保留项都要有理由 | 所有通过评审的错误句子都是理由为空的保留项。按此规则重新评审这类保留项所在的 291 个单元,费用为 $98.49。 |
使用独立的 claude -p --bare 进程 | 工作流子 Agent 会收到操作员的 CLAUDE.md、技能列表、MCP 指令和钩子。Serena 钩子拒绝了它们的读取操作,而 CLAUDE.md 中的行文规则改写了引号内代码的标点。裸进程的上下文约为 3,000 个 token,而非 77,000 个。 |
在 dontAsk 下使用 --tools Bash,Read | --bare 不包含 Grep 或 Glob,请求它们只会得到 Read,这导致整次运行都无法搜索。搜索通过 Bash 执行,只运行只读命令,并拒绝写入。实际使用中,cd、awk、xargs、cut、node、变量赋值和 $(...) 都被拒绝,因此提示词会明确列出这些内容。 |
| 使用 5 分钟的提示词缓存 | 设置 ENABLE_PROMPT_CACHING_1H 后,每次缓存写入都会按 1 小时费率计费,输入价格翻倍。进程池会移除该设置,并设置 CLAUDE_CODE_PROMPT_CACHE_TTL=5m。 |
| 检查每个回答的内容 | JSON schema 会检查格式。模型对 schema 拒绝的回答会使用占位内容,例如 reason: "test" 和证据 a:1,并会漏掉引用代码的类型注解、改写其中的标点。 |
| 每个工单使用一个进程,而非一个长会话 | 一个评审多个工单的会话每轮都会重新读取不断增长的全部上下文。每工单会话结束时约有 26,000 个 token。 |
| 使用提交内容的干净检出 | 在实时树中搜索的评审进程读到了其他评审者在评审状态目录中的发现。检出还会移除 run.json 中 judge_exclude 列出的文件,例如 CLAUDE.md 和 .claude/。 |
| 取两个评分中的较低者 | 这是操作员的规则。评审者给出的评分低于审核者;在一次包含 618 个单元的生产运行中,有 459 个单元的评分下调。 |
报告计数
每个工单都会为提出过任何发现的每个视角标注一个类别标签,大多数工单会有多个标签。在按类别和优先级统计工单的表格中,类别行统计的是标签,因此总数会超过工单数。请将行标题注明为“包含该类别的工单”,并另列去重后的工单总数。
文件
位于 $RUN/tickets/ 下:
plan.json、units.jsonl,已规划的问题及其按文件划分的单元judge/results.json、judge/merged.json,每个评审器回答及其尝试次数和成本,以及包含各进程事件流的judge/calls/edits.json、merges.json、merge_edits.json、decisions.jsonfinal/<ID>.json,每个工单一份发布载荷,final/manifest.json和postable.jsondup_pairs.json、bundle/
在运行目录本身,和 report.md 并列:
tickets.md,按优先级视角表排列的所有工单,格式为纯 Markdown
测试
针对 review-deep 和 review-tickets 的测试与评测,以及覆盖全部四个技能的保护测试和预检测试,都位于此插件仓库根目录下的 evals/ 中,在已安装技能之外。
| 检查 | 命令(在 evals/ 中运行) | 成本 |
|---|---|---|
| 单元测试,每个技能一个目录,另加预检和保护测试 | uv run --with pytest pytest -q unit/review_deep、unit/review_tickets、unit/preflight 和 unit/guard | 无 |
| 在 harbor fixture 上端到端重放,涵盖全部三种类型 | uv run --with pytest pytest -q test_end_to_end.py | 无 |
对照 fixture/truth.json 进行实时质量评测 | python3 live_eval.py WORK_DIR --run | 按标价约 $3 |
修改 scripts/ 后,运行单元测试和重放。如果提示词发生变化,重放会以 "prompt changed" 停止。使用 python3 pipeline.py WORK_DIR --mode record 重新记录,使用 live_eval.py 检查运行结果,并使用 python3 golden.py save RUN_DIR 保存。更改模型、工作量级别或提示词后,按需运行实时评测。
规则
- 未经操作员明确同意,绝不发布;且在回读第一张工单之前,绝不批量发布。
- 绝不让评审器读取实时工作树。评审器在
run.json的checkout中运行。 - 绝不接受未经内容检查的模型回答;绝不接受并非来自所审查提交代码的证据引文。
- 将提示词保存在
prompts/中,并使用.txt格式。Markdown 格式化工具曾重写一个.md提示词并改变其含义。 - 按实测结果报告数量。工单数、标签数和发现数是三个不同的数字。