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-artifactrecord_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 交付失败披露它继续流程绝不改动评审工件或裁决。顶层参数参数类型说明levellow \| medium \| high本次评审投入档位低投入低努力度。仅在客户端渲染时用于标注见 REPORT_FINDINGS_LEVELSfindingsReportFindingsFindingParams[]完整 findings 列表最多50条REPORT_FINDINGS_MAX空数组是合法的“无发现”报告单条 finding 字段与工件逐字对齐设计文档明确字段名与枚举拼写必须与 findings 工件完全一致让模型从工件中“复制”值而不是“翻译”值。实现中的 schema 定义于 FINDING_ITEM_SCHEMA字段约束语义id字符串≤64 字符全列表唯一工件 id如R1-2outcome 回执按它归位severityCritical \| Suggestion \| Nice to have必填该数组顺序即排序顺序最严重在前confidencehigh \| low未验证的低投入轮次可省略sourcereview \| build \| test \| probe \| lint发现来源默认reviewfile≤4096 字符REPORT_FINDINGS_FILE_MAX仓库相对路径或评审的(body)占位符必填。上限对齐文件系统 PATH_MAX避免截断路径导致定位错位line整数 ≥1须在 JS 安全整数范围内可选summary不限长度一句话陈述缺陷必填trim 后不得为空shortSummary≤60 字符供紧凑列表 UI 使用的压缩标签缺省时由summary推导failureScenario≤4000 字符具体触发条件与错误结果finding 的证据必填category≤64 字符自由格式 kebab-case 标签correctness、security、test-coverage…directioncertifies-falsely \| fails-closedCritical 的两条决策轴之一#10291伪造正确结果 vs. 拒绝/卡死/降级。工件没有则不填baselineregression \| new-surfaceCritical 的另一决策轴合并基线本可正确处理回归vs. 失败路径在基线不存在新面outcomefixed \| skipped \| no_change_needed仅在修复后的回执调用中携带outcomeNote≤1000 字符修复者理由skipped时必填——读者有权知道未完成工作的原因必填字段只有四个severity、file、summary、failureScenario。注意summary在工具 schema 中刻意不设 maxLength测试用例钉死了这一点因为工件不限制它收紧上限会拒绝工件本身能容纳的列表。排序规则严重度 → 置信度 → 位置实现中sortReportedFindingsreport-findings.ts与工件自身的sortFindings排序逻辑对齐严重度按FINDING_SEVERITIES数组下标Critical 最前置信度high排在缺省未验证之前缺省排在low之前——这是对工件排序的唯一扩展工件要求confidence必填而本契约允许低投入轮次省略位置file按代码单元code-unit比较行号缺失的排在行锚定之前?? 0最后用id打破同文件同行平局使排序完全确定。测试用“缺失行优先 id 按代码单元”以及“混合大小写路径”两组用例锁死了这些细节report-findings.test.ts并特意避免localeCompare——ICU 排序与代码单元顺序在a与B这类大小写混合场景下不一致。shortSummary 压缩compressFindingSummaryreport-findings.ts把超长标签压到 60 字符以内先把换行等空白折叠为单空格避免源码散文里的换行进入单行列表单元格截断点尽量落在词边界在接近上限的合理位置找空格使标签读起来像从句而非被切断的单词省略号用单个字符…U2026而非三个点硬截断落在代理对surrogate pair中间时回退一个单元——未配对的 high surrogate 不是字符终端消毒与显示压缩等其它截断路径同样按码点边界切割。优先级为调用方提供的shortSummary若合法→ 压缩后的summary两者都会再走压缩normalizeFinding。校验拒绝规则validateToolParamValuesreport-findings.ts在构建期拒绝控制字符id、file、summary、shortSummary、failureScenario、category、outcomeNote均不得含控制字符其中三个散文字段summary/failureScenario/outcomeNote放行换行行内空白合法而file、shortSummary等单行字段连换行都拒绝空必填字段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 的注释核心定义packages/core/src/tools/report-findings.ts 中的FINDING_SEVERITIES、FINDING_CONFIDENCES、FINDING_OUTCOMES、FINDING_SOURCES、FINDING_DIRECTIONS、FINDING_BASELINES——这是权威源CLI 历史名称重导出findings.ts 以历史名称SEVERITIES、CONFIDENCES、OUTCOMES、SOURCES、DIRECTIONS、BASELINES重导出核心常量供工件命令使用Web Shell 渲染器CodeReviewArtifactDetail.tsx是浏览器 bundle不能导入 Node 侧包保留自己的副本并且对未知值失败关闭fail closed——在核心新增一个枚举值必须同步更新渲染器副本否则已保存工件中携带该值会导致渲染中断。direction/baseline两条轴只有核心与 CLI 两份副本——渲染器刻意忽略这两个字段不参与词汇快照。两次调用模型报告 结果回执设计文档把工具的使用模式定义为两次调用而“第二次调用是第一次值得信任的原因”首次调用Step 6在写入 findings 工件之后立即调用一次level取本次评审投入档位每条 finding 的字段逐字复制自刚写出的工件不要重新推导或改述——工件是 oracle。低投入low effort轮次没有工件用level: low上报未验证清单且confidence: low只打在保留Confidence: low标记的候选上其余省略——因为low档位本身已给整份列表打上“未验证”标签见 SKILL.md 与 SKILL.md 的 Step 6 规则。结果回执Step 6B 及之后--fix运行后重新调用每条 finding 携带outcomeskipped的还要带outcomeNote。这条规则比 Step 6B 更长寿会话中任何时刻某条已上报 finding 的处置发生变化用户说fix these issues、某 finding 被证明有误、修复在对话中途落地都要把 outcome 记回工件并重新发起调用——SKILL.md 明令“客户端逐条状态只信任携带 outcome 的调用”否则“树已关闭的 finding 在所有客户端上仍渲染为打开”。两次调用的共性后一次调用整体替换清单绝不追加。失败时披露并继续不影响工件与裁决。身份门activeReportIds为避免 outcome 回执悄悄改写清单工具实例维护一个活进程契约——activeReportIdsreport-findings.ts当且仅当最近一次已交付的报告所有 finding 都带 id 时才记录该 id 集合带 outcome 的替换调用必须完整匹配该身份不得丢任何一条drops N finding(s) from the active report也不得引入清单外的新 idcarries 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、--outcomesJSON 数组{id, outcome, note?}必须覆盖每条 finding、--test-delta、--to-anchors、--print。设计文档特别指出--input也接受已保存的评审工件或先前的--out报告任何以findings键携带数组的对象因为 Step 9 清理会删除findings-in.json侧文件而后置会话的 outcome 路径需要能从不被删除的幸存文件中恢复实现见 validateFindings 的包装解包逻辑。validateOutcomes与applyOutcomesfindings.ts在命令侧执行同一“全有或全无”纪律未覆盖的 finding 与未知 id 都是硬错误——前者是修复者悄悄缩短清单后者意味着台账建立在别的清单上合并会挂错行。渲染端TUI、Web Shell 与转录压缩TUIFindingsDisplay终端 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.ts、save-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),仅供参考