news 2026/9/13 8:23:14

qwen-code 的 report_findings 类型化契约:让代码评审发现以结构化数据直达所有客户端

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
qwen-code 的 report_findings 类型化契约:让代码评审发现以结构化数据直达所有客户端

qwen-code 的 report_findings 类型化契约:让代码评审发现以结构化数据直达所有客户端

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

导读

在 qwen-code(一个运行于终端中的开源 AI 编码代理)的/review评审流程中,评审产出的 findings(发现项)此前只有两种存在形态:写入磁盘的 JSON 工件(.qwen/tmp/下的qwen review findings产物)与最终渲染出来的 Markdown 复述。本篇文章以设计文档 report-findings-typed-contract.md 为骨架,结合核心实现 report-findings.ts、CLI 侧工件命令 findings.ts 与 TUI 渲染组件,深入讲解report_findings这个带内(in-band)类型化契约工具:它如何把 findings 以结构化数据直接送达终端 UI、Web Shell 与 ACP 宿主,如何在--fix之后回执每条 finding 的结果(outcome),以及“后一次调用替换整个列表”这一契约如何在转录层被严格执行。读完你将掌握该契约的完整字段规范、两次调用模型、身份校验门与渲染/压缩规则,并能在自己的宿主或客户端上正确消费findings_list结构。

背景:评审发现的两次“转录”问题

设计文档开篇指出现状:/review其实已经两次把 findings 规范化成数据——qwen review findings把类型化工件写到.qwen/tmp/,Step 8 的save-artifact+record_artifact再把一份持久副本发布给 Web Shell 渲染(CodeReviewArtifactDetail)。但这两者都是事后注册的文件

问题在于:所有实时渲染会话的客户端——终端 UI、Web Shell 转录、ACP 宿主、daemon TUI——收到的只是同一份列表的Markdown 复述。而在--fix(或之后的fix these issues)执行完毕后,没有任何带内(in-band)信号告诉客户端哪些 finding 已被关闭。客户端只能看到两段文字,无法可靠地知道清单中每一项的处置状态。

report_findings就是这条缺失的带内半边:一次工具调用,{level, findings[]},由宿主 UI 按逐条 finding 渲染。核心实现文件头部注释点明了设计动机(report-findings.ts):

评审的 findings 已经作为数据存在过一次——qwen review findings工件——但那个文件在磁盘上,事后才通过record_artifact注册。每个实时渲染会话的客户端看到的只是散文式复述,而这正是工件想要消除的转录面。

工具契约:字段与枚举

report_findings是一次性交付型工具,不持久化任何内容,也不裁决结论——它的返回值是一个结构化returnDisplay,类型为findings_list。执行失败只是 UI 交付失败:披露它,继续流程,绝不改动评审工件或裁决。

顶层参数

参数类型说明
level'low' \| 'medium' \| 'high'本次评审投入档位(低投入低努力度)。仅在客户端渲染时用于标注,见 REPORT_FINDINGS_LEVELS
findingsReportFindingsFindingParams[]完整 findings 列表,最多50条(REPORT_FINDINGS_MAX);空数组是合法的“无发现”报告

单条 finding 字段(与工件逐字对齐)

设计文档明确:字段名与枚举拼写必须与 findings 工件完全一致,让模型从工件中“复制”值而不是“翻译”值。实现中的 schema 定义于 FINDING_ITEM_SCHEMA:

字段约束语义
id字符串,≤64 字符,全列表唯一工件 id(如"R1-2"),outcome 回执按它归位
severity'Critical' \| 'Suggestion' \| 'Nice to have'必填;该数组顺序即排序顺序(最严重在前)
confidence'high' \| 'low'未验证的低投入轮次可省略
source'review' \| 'build' \| 'test' \| 'probe' \| 'lint'发现来源,默认review
file≤4096 字符(REPORT_FINDINGS_FILE_MAX仓库相对路径,或评审的"(body)"占位符;必填。上限对齐文件系统 PATH_MAX,避免截断路径导致定位错位
line整数 ≥1,须在 JS 安全整数范围内可选
summary不限长度一句话陈述缺陷;必填,trim 后不得为空
shortSummary≤60 字符供紧凑列表 UI 使用的压缩标签;缺省时由summary推导
failureScenario≤4000 字符具体触发条件与错误结果(finding 的证据);必填
category≤64 字符自由格式 kebab-case 标签(correctnesssecuritytest-coverage…)
direction'certifies-falsely' \| 'fails-closed'Critical 的两条决策轴之一(#10291):伪造正确结果 vs. 拒绝/卡死/降级。工件没有则不填
baseline'regression' \| 'new-surface'Critical 的另一决策轴:合并基线本可正确处理(回归)vs. 失败路径在基线不存在(新面)
outcome'fixed' \| 'skipped' \| 'no_change_needed'在修复后的回执调用中携带
outcomeNote≤1000 字符修复者理由;skipped必填——读者有权知道未完成工作的原因

必填字段只有四个:severityfilesummaryfailureScenario。注意summary在工具 schema 中刻意不设 maxLength(测试用例钉死了这一点),因为工件不限制它,收紧上限会拒绝工件本身能容纳的列表。

排序规则:严重度 → 置信度 → 位置

实现中sortReportedFindings(report-findings.ts)与工件自身的sortFindings排序逻辑对齐:

  1. 严重度:按FINDING_SEVERITIES数组下标(Critical 最前);
  2. 置信度high排在缺省(未验证)之前,缺省排在low之前——这是对工件排序的唯一扩展:工件要求confidence必填,而本契约允许低投入轮次省略;
  3. 位置file按代码单元(code-unit)比较,行号缺失的排在行锚定之前(?? 0),最后用id打破同文件同行平局,使排序完全确定。

测试用“缺失行优先 + id 按代码单元”以及“混合大小写路径”两组用例锁死了这些细节(report-findings.test.ts),并特意避免localeCompare——ICU 排序与代码单元顺序在aB这类大小写混合场景下不一致。

shortSummary 压缩

compressFindingSummary(report-findings.ts)把超长标签压到 60 字符以内:

  • 先把换行等空白折叠为单空格(避免源码散文里的换行进入单行列表单元格);
  • 截断点尽量落在词边界(在接近上限的合理位置找空格),使标签读起来像从句而非被切断的单词;
  • 省略号用单个字符(U+2026)而非三个点;
  • 硬截断落在代理对(surrogate pair)中间时回退一个单元——未配对的 high surrogate 不是字符,终端消毒与显示压缩等其它截断路径同样按码点边界切割。

优先级为:调用方提供的shortSummary(若合法)→ 压缩后的summary;两者都会再走压缩(normalizeFinding)。

校验拒绝规则

validateToolParamValues(report-findings.ts)在构建期拒绝:

  • 控制字符idfilesummaryshortSummaryfailureScenariocategoryoutcomeNote均不得含控制字符;其中三个散文字段(summary/failureScenario/outcomeNote)放行换行(行内空白合法),而fileshortSummary等单行字段连换行都拒绝;
  • 空必填字段file/summary/failureScenariotrim 后为空即报错,并带出索引;
  • 非安全整数行号line必须是 JS 安全整数(Number.MAX_SAFE_INTEGER + 1这类 JSON 可表示的舍入值也会被拦下);
  • 重复 idid全局唯一,否则报duplicate id "…"
  • 部分 outcomewithOutcome > 0 && withOutcome < length时报错——outcome 要么全有,要么全无。这正是设计文档强调的“部分覆盖是失败模式”:修复了 9 条中的 6 条只回报 6 条,并没有对任何一条撒谎,但它悄悄缩短了清单,读者无法得知消失的 3 条。

全部校验逻辑都有对应测试用例(report-findings.test.ts)。

枚举的“三副本”结构与职责边界

一个值得注意的实现约束:这些枚举拼写在仓库里存在三处刻意保留的副本(见 report-findings.ts 的注释):

  1. 核心定义:packages/core/src/tools/report-findings.ts 中的FINDING_SEVERITIESFINDING_CONFIDENCESFINDING_OUTCOMESFINDING_SOURCESFINDING_DIRECTIONSFINDING_BASELINES——这是权威源;
  2. CLI 历史名称重导出:findings.ts 以历史名称SEVERITIESCONFIDENCESOUTCOMESSOURCESDIRECTIONSBASELINES重导出核心常量,供工件命令使用;
  3. Web Shell 渲染器CodeReviewArtifactDetail.tsx是浏览器 bundle,不能导入 Node 侧包,保留自己的副本,并且对未知值失败关闭(fail closed)——在核心新增一个枚举值必须同步更新渲染器副本,否则已保存工件中携带该值会导致渲染中断。

direction/baseline两条轴只有核心与 CLI 两份副本——渲染器刻意忽略这两个字段,不参与词汇快照。

两次调用模型:报告 + 结果回执

设计文档把工具的使用模式定义为两次调用,而“第二次调用是第一次值得信任的原因”:

  1. 首次调用(Step 6):在写入 findings 工件之后立即调用一次,level取本次评审投入档位,每条 finding 的字段逐字复制自刚写出的工件(不要重新推导或改述——工件是 oracle)。低投入(low effort)轮次没有工件,用level: "low"上报未验证清单,且confidence: "low"只打在保留Confidence: low标记的候选上,其余省略——因为low档位本身已给整份列表打上“未验证”标签(见 SKILL.md 与 SKILL.md 的 Step 6 规则)。
  2. 结果回执(Step 6B 及之后)--fix运行后,重新调用,每条 finding 携带outcomeskipped的还要带outcomeNote)。这条规则比 Step 6B 更长寿:会话中任何时刻某条已上报 finding 的处置发生变化(用户说fix these issues、某 finding 被证明有误、修复在对话中途落地),都要把 outcome 记回工件并重新发起调用——SKILL.md 明令“客户端逐条状态只信任携带 outcome 的调用”,否则“树已关闭的 finding 在所有客户端上仍渲染为打开”。

两次调用的共性:后一次调用整体替换清单,绝不追加。失败时披露并继续,不影响工件与裁决。

身份门:activeReportIds

为避免 outcome 回执悄悄改写清单,工具实例维护一个活进程契约——activeReportIds(report-findings.ts):

  • 当且仅当最近一次已交付的报告所有 finding 都带 id 时,才记录该 id 集合;
  • 带 outcome 的替换调用必须完整匹配该身份:不得丢任何一条(drops N finding(s) from the active report),也不得引入清单外的新 id(carries finding(s) the active report does not have)——因为 outcome 调用替换整个清单,必须把每条活跃 finding 连同 outcome 一起重报;
  • 身份在交付成功时提交,而非构建时(report-findings.ts):调度器会预先构建一批调用并可能在执行前丢弃(预校验取消、中止信号),一个构建了但从未交付的报告绝不能顶替客户端实际收到的身份。测试 report-findings.test.ts 专门钉死了“未交付不阻塞”这一行为;
  • 冷会话恢复没有身份--resume或进程重启会构造全新实例,其 outcome 调用按自身条件(全有或全无)校验,而不是对照重启前的报告。跨重启持久化身份被刻意排除在范围之外——转录层的替换机制不依赖它(测试 report-findings.test.ts 验证了同样的子集在旧实例被拒、在新实例被接受)。

与 CLI 工件命令的衔接

qwen review findings命令(findings.ts)是工件的权威写入方,参数包括--input--out--outcomes(JSON 数组{id, outcome, note?},必须覆盖每条 finding)、--test-delta--to-anchors--print。设计文档特别指出:--input也接受已保存的评审工件或先前的--out报告(任何以findings键携带数组的对象),因为 Step 9 清理会删除findings-in.json侧文件,而后置会话的 outcome 路径需要能从不被删除的幸存文件中恢复(实现见 validateFindings 的包装解包逻辑)。validateOutcomesapplyOutcomes(findings.ts)在命令侧执行同一“全有或全无”纪律:未覆盖的 finding 与未知 id 都是硬错误——前者是修复者悄悄缩短清单,后者意味着台账建立在别的清单上,合并会挂错行。

渲染端:TUI、Web Shell 与转录压缩

TUI:FindingsDisplay

终端 UI 用 ink 组件 FindingsDisplay.tsx 渲染findings_list

  • 顶部:level: "low"时先渲染灰色横幅(low-effort pass — findings are unverified),且优先于空态分支——空列表依然是该轮次的产物,不带标记渲染会被误读为“已核验的干净报告”;
  • 每行:严重度着色(Critical 红、Suggestion 黄、Nice to have 灰)、id、file:line(青色)、short summary、低置信度标记(low confidence)、outcome 徽标;fixed/no_change_needed视为已解决(绿色勾 + 删除线),skipped保持未解决并内联outcomeNote理由;
  • 所有插值都过terminalSafe(剥离 C0/C1 控制字符)——outcomeNote合法携带行内空白,任何漏网的控制字符都可能伪造或覆盖被当作可信 findings 的行;
  • 压缩截断:历史/录制压缩对自由文本字段截断,并对整份列表应用聚合保留显示预算,保留最严重前缀、统计被逐出的尾部为omittedFindings,渲染为(+N more findings removed by history compaction)

转录层的“最后一次胜出”

“后一次调用替换整个列表”不止是校验,还要被渲染。模块 findings-coalescing.ts 实现了转录侧的同一规则:

  • 每个转录面——实时历史、恢复的历史、录制/续播、daemon 投影——只保留最后交付的findings_list,更早的每一条都被折叠为一行替换标记(findings replaced by a later report_findings call)
  • 被折叠的显示仍保留在工具上(supersededFindingsDisplay),以便回卷(rewind)越过替换调用时恢复;
  • 截断/回卷修复时会先恢复被替换的显示、再对幸存者重新合并(recoalesceFindingsHistoryItems),保证“只保留最后一份清单”的不变式在截断后的转录上依然成立。

这样,初次报告与其 outcome 回执永远不会并排出现两份清单。daemon TUI 适配器则直接透传findings_list,由 Web Shell 侧(i18n.tsx、toolFormatting.ts 等处引用)与 ACP 宿主按同一结构消费。

验证体系

设计文档列出的验证面在仓库中均有落点:

  • 核心工具单测:report-findings.test.ts 覆盖排序、shortSummary 推导/压缩、空列表、outcome 计数、部分 outcome 拒绝、重复 id、控制字符、schema 违规、字段 trim 与安全整数行号;
  • 压缩单测:自由文本字段截断、类型化字段幸存(omittedFindings计数);
  • FindingsDisplay 渲染测试:FindingsDisplay.test.tsx 覆盖行渲染、带跳过理由的 outcome、空态;
  • 回归保障findings.tssave-artifact、ToolMessage、daemon 适配器、config 注册、SKILL 一致性(SKILL.test.ts)与 review-digest 测试套件保持绿色。

小结

report_findings解决的是一个具体而普遍的问题:结构化数据已经存在于磁盘工件中,却在客户端渲染面上退化成散文,进而在修复后失去处置状态。它的答案是把契约搬进带内:一次调用、逐字复制工件、findings_list结构化返回、全有或全无的 outcome 回执、以 id 集合为身份的活动进程校验门,以及“最后一次胜出”的转录替换。无论你是在终端里跑/review、在 Web Shell 里回看评审,还是以 ACP 宿主身份消费会话,这份契约都是客户端理解“哪些 finding 仍待处理、哪些已被关闭”的唯一可靠来源。

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 8:22:41

PS2022/PS2021神经滤镜离线安装包完整指南:版本匹配与避坑步骤

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 8:21:20

Workflow与Agent本质区别:AI应用开发的范式选择指南

1. 什么是 Workflow 与 Agent&#xff1a;不是概念炒作&#xff0c;而是开发路径的分水岭“Workflow 与 Agent&#xff1a;AI 应用的两大范式”——这句话最近在技术社区刷屏&#xff0c;但很多人点开文章后发现&#xff0c;要么堆砌术语讲不清区别&#xff0c;要么拿大模型API…

作者头像 李华
网站建设 2026/9/13 8:20:27

冷启动工具产品设计:从信息断点缝合到协作语义网络

1. 项目概述&#xff1a;一个“冷启动”工具产品的现实主义突围路径“WorkBuddy起于无人问津处&#xff0c;怀着生态‘人声鼎沸’的野心”——这句话不是口号&#xff0c;而是我去年接手一个内部孵化项目时&#xff0c;贴在工位白板上的第一行字。它精准概括了所有从零起步的生…

作者头像 李华