-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(cli): detect overlapping review comment ranges #9801
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,6 +50,7 @@ import { | |
| interface FindingAnchor { | ||
| path: string; | ||
| line: number; | ||
| startLine?: number; | ||
| /** | ||
| * Ledger id (`R<round>-<n>`) — carried-forward findings ONLY. The | ||
| * orchestrator omits it on fresh findings of the current round: a fresh id | ||
|
|
@@ -126,6 +127,7 @@ interface RawComment { | |
| body?: string; | ||
| path?: string; | ||
| line?: number; | ||
| start_line?: number; | ||
| commit_id?: string; | ||
| in_reply_to_id?: number; | ||
| user?: { login?: string }; | ||
|
|
@@ -238,7 +240,12 @@ export function parseFindingsFile(path: string): FindingAnchor[] | null { | |
| ) { | ||
| return null; | ||
| } | ||
| const e = entry as { path: string; line?: unknown; id?: unknown }; | ||
| const e = entry as { | ||
| path: string; | ||
| line?: unknown; | ||
| start_line?: unknown; | ||
| id?: unknown; | ||
| }; | ||
| // Same fail-safe as `path`: an `id` of the wrong type or shape is a | ||
| // malformed file, and silently ignoring it would let a carried re-post | ||
| // read as a fresh duplicate at the very location it belongs (a typo'd | ||
|
|
@@ -256,6 +263,7 @@ export function parseFindingsFile(path: string): FindingAnchor[] | null { | |
| out.push({ | ||
| path: e.path, | ||
| line: typeof e.line === 'number' ? e.line : 0, | ||
| ...(typeof e.start_line === 'number' ? { startLine: e.start_line } : {}), | ||
| ...(typeof e.id === 'string' ? { id: e.id } : {}), | ||
| }); | ||
| } | ||
|
|
@@ -641,7 +649,6 @@ function classifyExistingComments( | |
| CommentSummary[] | ||
| > = { stale: [], resolved: [], overlap: [], repost: [], noConflict: [] }; | ||
|
|
||
| const newFindingKeys = new Set(newFindings.map((f) => `${f.path}:${f.line}`)); | ||
| // Location → carried ids of the findings anchored there. Only findings with | ||
| // an id participate, and the orchestrator writes ids ONLY on carried | ||
| // findings (SKILL.md — the findings file): a fresh `R<this-round>-<n>` | ||
|
|
@@ -688,6 +695,20 @@ function classifyExistingComments( | |
| } | ||
|
|
||
| for (const c of qwenComments) { | ||
| const commentLine = c.line ?? 0; | ||
| const commentStartLine = c.start_line ?? commentLine; | ||
| const commentRangeStart = Math.min(commentStartLine, commentLine); | ||
|
Comment on lines
+698
to
+700
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] R1-1: No test discriminates the comment-side The regression that could then ship: an existing multi-line comment One test closes it, and it flips the mutant as required (fails under the mutant with 中文说明此处评论侧的 由此可能溜进发布的回归:既有多行评论 补一个测试即可闭合,且它能按预期翻转变异体(在变异体下以 — qwen3.8-max via Qwen Code /review (v0.22.0) |
||
| const commentRangeEnd = Math.max(commentStartLine, commentLine); | ||
| const overlapsNewFinding = newFindings.some((finding) => { | ||
| if (finding.path !== (c.path ?? '')) return false; | ||
|
Comment on lines
+702
to
+703
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] R1-2: The overlap gate became range-based while the comment set it runs over is still recognized partly by an ungated, any-account shape match: the qwenComments filter accepts any comment whose body contains the short footer substring ( Measured on this commit: a comment from an account named Suggested fix: restrict the new range-intersection branch to account-gated comments (the marker/severity disjuncts, where provenance is the posting account) and keep the any-account footer match exact-line as before; or explicitly document that an ungated footer match now grants range-wide suppression. 中文说明overlap 门变为基于区间,而它作用的评论集合仍部分依赖一个不设账户门槛的形状匹配来识别:qwenComments 过滤器接受任何正文包含短尾注子串( 在本提交上实测:一个名为 建议修复:将新的区间相交分支限制为有账户门槛的评论(marker/severity 两个分支,其来源是发布账号),任一账户的尾注匹配保持原有的精确行语义;或明确注明:无账户门槛的尾注匹配现在授予区间级压制。 — qwen3.8-max via Qwen Code /review (v0.22.0) |
||
| const findingStartLine = finding.startLine ?? finding.line; | ||
| const findingRangeStart = Math.min(findingStartLine, finding.line); | ||
| const findingRangeEnd = Math.max(findingStartLine, finding.line); | ||
| return ( | ||
| findingRangeStart <= commentRangeEnd && | ||
| commentRangeStart <= findingRangeEnd | ||
| ); | ||
| }); | ||
| const summary: CommentSummary = { | ||
| id: c.id, | ||
| path: c.path ?? '', | ||
|
|
@@ -701,13 +722,13 @@ function classifyExistingComments( | |
| buckets.stale.push(summary); | ||
| } else if (repliedToIds.has(c.id)) { | ||
| buckets.resolved.push(summary); | ||
| } else if (newFindingKeys.has(`${c.path}:${c.line}`)) { | ||
| // Overlap stays location-based: a same-line finding with a DIFFERENT | ||
| // claim is still dropped (the drop log now names this comment so the | ||
| // false positive is visible — #9208). Repost is the additional, id-based | ||
| // bucket: a Step 6 ledger re-post lands on the original thread's line by | ||
| // construction and carries the original id in its prefix, so an id match | ||
| // marks the re-post target and exempts that finding from the drop. | ||
| } else if (overlapsNewFinding) { | ||
| // Overlap stays location-based: an intersecting same-file finding with | ||
| // a DIFFERENT claim is still dropped (the drop log names this comment so | ||
| // the false positive is visible — #9208). Repost remains exact-line and | ||
| // id-based: a Step 6 ledger re-post lands on the original thread's line | ||
| // by construction and carries the original id in its prefix, so an id | ||
| // match marks the re-post target and exempts that finding from the drop. | ||
| buckets.overlap.push(summary); | ||
| const wantedIds = carriedIdsByLocation.get(`${c.path}:${c.line}`); | ||
| // Ledger ids are per-account — two reviewers of the same PR keep two | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Suggestion] R1-3: The new
start_lineparse arm validates nothing beyondtypeof === 'number', and both directions of that leniency silently corrupt the new range-overlap dedup whilefindingsFileInvalidstays false (the report reads as a clean pass).Two shapes, both probed on this commit:
[{"path":"a.ts","start_line":"12","line":18}]parses to the point[18,18](the key is silently dropped), so an existing comment spanning lines 12–17 no longer intersects →noConflict, and a duplicate posts. The numeric control arm correctly reportsoverlap.start_line: -3yields[-3,18], flipping an unrelated same-file comment at line 5 tooverlapandblockOnExistingCommentsto true — a wrong premise handed to the deterministic drop rule; fractional2.5misses dedup at line 2. Pre-diffstart_linewas not parsed at all, so both shapes were inert.Probe output (unmodified PR code):
The file's own fail-safe rejects the WHOLE file for a misshapen
idbecause a corrupt id would actively corrupt a match — a corrupt range does the same here, andsubmit.ts'sisDiffLine(Number.isSafeInteger(n) && n > 0) is the existing domain gate for exactly this value class on the posting side. Suggested fix: mirror theidfail-safe on the new arm — whenstart_lineis present (and not null) but not a positive safe integer, reject the whole file (return null), e.g. by reusing/exportingisDiffLine; or, if the lenient drop is intentional, a comment saying so keeps the next reader from re-deriving this.中文说明
新的
start_line解析分支只校验typeof === 'number',两个方向的宽松都会静默破坏新的区间去重,而findingsFileInvalid保持 false(报告读起来像一次干净的通过)。两种形态,均在本提交上以探针实测:
[{"path":"a.ts","start_line":"12","line":18}]被解析为点[18,18](该键被静默丢弃),横跨 12–17 行的既有评论不再与之相交 → 判为noConflict,重复评论被发出;数字对照组正确给出overlap。start_line: -3得到[-3,18],同文件第 5 行一条无关评论被翻转为overlap,blockOnExistingComments置 true——向确定性丢弃规则提供了错误前提;小数2.5则漏掉第 2 行的去重。diff 之前start_line根本不被解析,两种形态都是惰性的。本文件自身的 fail-safe 先例是:畸形
id拒绝整个文件,因为损坏的 id 会主动破坏匹配——损坏的区间在这里同理;submit.ts的isDiffLine(Number.isSafeInteger(n) && n > 0)正是发布侧针对这一数值类型的既有域校验。建议修复:对新分支镜像id的 fail-safe——start_line存在(且非 null)但不是正安全整数时拒绝整个文件(return null),例如复用/导出isDiffLine;若宽松丢弃是有意为之,加一条注释说明,避免后续读者重新推导。— qwen3.8-max via Qwen Code /review (v0.22.0)