-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(ci): render the queued-acknowledgement comment #8726
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
254b8e8
5d29984
e781f47
aff47ff
ab4af89
798cb71
0259892
9bb5a8f
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 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,6 +26,10 @@ const workflow = readFileSync( | |||||||||
| '.github/workflows/qwen-code-pr-review.yml', | ||||||||||
| 'utf8', | ||||||||||
| ); | ||||||||||
| const workflowsDir = '.github/workflows'; | ||||||||||
| const workflowFiles = readdirSync(workflowsDir).filter((f) => | ||||||||||
| /\.ya?ml$/.test(f), | ||||||||||
| ); | ||||||||||
|
|
||||||||||
| function runReviewStep() { | ||||||||||
| const doc = parse(workflow); | ||||||||||
|
|
@@ -2175,14 +2179,12 @@ describe('workflow expression length', () => { | |||||||||
| // length 21000` (e.g. run 31239579253). CI stayed green the whole time — no | ||||||||||
| // test covered this, which is why it is covered here. | ||||||||||
| const LIMIT = 21000; | ||||||||||
| const dir = '.github/workflows'; | ||||||||||
| const files = readdirSync(dir).filter((f) => /\.ya?ml$/.test(f)); | ||||||||||
|
|
||||||||||
| it('keeps every templated run block under the limit', () => { | ||||||||||
| expect(files.length).toBeGreaterThan(0); | ||||||||||
| expect(workflowFiles.length).toBeGreaterThan(0); | ||||||||||
| const over = []; | ||||||||||
| for (const file of files) { | ||||||||||
| const doc = parse(readFileSync(join(dir, file), 'utf8')); | ||||||||||
| for (const file of workflowFiles) { | ||||||||||
| const doc = parse(readFileSync(join(workflowsDir, file), 'utf8')); | ||||||||||
| for (const [jobId, job] of Object.entries(doc?.jobs ?? {})) { | ||||||||||
| for (const step of job?.steps ?? []) { | ||||||||||
| const body = step?.run; | ||||||||||
|
|
@@ -2274,3 +2276,129 @@ describe('command shape matching', () => { | |||||||||
| expect(stripCr).toBeGreaterThan(firstLine); | ||||||||||
| }); | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| describe('bot comment markers', () => { | ||||||||||
| // A line that opens with `<!--` starts an HTML block, and that block runs to | ||||||||||
| // the line holding the closing delimiter INCLUSIVE — the rest of that line | ||||||||||
| // stays inside it and is never parsed as Markdown. The queued-ack comment | ||||||||||
| // glued its prose straight onto the marker and reached every PR as raw | ||||||||||
| // source with a dead link. Measured through GitHub's own renderer | ||||||||||
| // (POST /markdown, mode=gfm): marker+text -> 0 <a>/0 <em>; marker+"\n"+text | ||||||||||
| // and marker+"\n\n"+text -> 1 <a>/1 <em>. | ||||||||||
| // | ||||||||||
| // The rule keys off ONE thing: a marker that opens a string literal, and | ||||||||||
|
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] The "marker opens a string literal" rule misses the same bug class when the marker sits mid-literal but at the start of a rendered line. Failure scenario: 中文说明"标记打开字符串字面量"规则会漏掉同一缺陷类:标记位于字面量中间、却落在渲染行的行首。失败场景: — qwen3.8-max via Qwen Code /review (v0.21.7) |
||||||||||
| // what remains inside that literal after it. That is what distinguishes | ||||||||||
| // BUILDING a comment body from merely REFERENCING a marker — `jq | ||||||||||
| // contains("<!-- m -->")` and `printf '<!-- m -->' "$VAR"` leave nothing | ||||||||||
| // after the marker and are fine, while `="<!-- m -->prose"` does not. The | ||||||||||
| // scan is bounded to the marker's physical line: a glued body is by | ||||||||||
| // definition on that line, and searching past it would couple the guard to | ||||||||||
| // unrelated quotes elsewhere in the file — a doc example quoting an unclosed | ||||||||||
| // `--body "<!-- m -->` would break the moment any later line gained a `"`. | ||||||||||
| // | ||||||||||
| // Known gaps, stated rather than papered over: bodies split across printf | ||||||||||
| // arguments (`printf '%s%s' '<!-- m -->' 'prose'`) — detecting them means | ||||||||||
| // modelling which literal is the format string, and every cheap | ||||||||||
| // approximation flagged the legitimate `printf '<!-- m -->' "$VAR"` form; | ||||||||||
| // bodies assembled across statements or files (one `echo` per line into a | ||||||||||
| // `--body-file`); markers at physical line start (heredocs, YAML block | ||||||||||
| // scalars); markers mid-literal that only land at a rendered line start | ||||||||||
| // after `\n` expansion (`printf 'x\n<!-- m -->prose'`); multi-line literals | ||||||||||
| // whose closing quote sits on a later line; a line-wrapped printf whose | ||||||||||
| // format opens with the marker (the `\n` exemption reads one physical | ||||||||||
| // line); continuations after a marker-ending literal other than an | ||||||||||
| // adjacent quoted literal or bare `$(…)` — `$VAR` expansion, unquoted | ||||||||||
| // words, `$'…'` literals, backtick substitution, backslash-newline, and | ||||||||||
| // text after the closing `)` of a wrapping subshell assignment | ||||||||||
| // (`BODY="$(printf '<!-- m -->')prose"`); closing that shape needs a | ||||||||||
| // subshell discriminator that false-positives on legitimate jq | ||||||||||
| // `contains("<!-- … -->"))` references inside `$(…)` assignments; | ||||||||||
| // trailing end-of-line comments — the `#` skip only fires when the | ||||||||||
| // comment OPENS the physical line, so a glued marker quoted in a | ||||||||||
| // trailing comment is still flagged; YAML double-quoted scalars | ||||||||||
| // (`body: "<!-- m -->\nprose"`) — YAML expands `\n`, but the scanner | ||||||||||
| // cannot cheaply tell a YAML scalar from a shell literal where `\n` | ||||||||||
| // stays literal; and marker-headed bodies built outside | ||||||||||
| // `.github/workflows` — the `.github/scripts/*.mjs` comment builders | ||||||||||
| // (template literals and pushed marker lines) are not scanned. All are | ||||||||||
|
Comment on lines
+2321
to
+2323
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] The enumeration scopes builder-pattern gaps to bodies built outside 中文说明[建议] 枚举把"拼装器模式"的缺口限定在 — qwen3.8-max via Qwen Code /review (v0.21.8) |
||||||||||
| // latent — nothing glues a marker today. | ||||||||||
|
|
||||||||||
| it('never glues prose onto a comment marker, in any workflow', () => { | ||||||||||
| expect(workflowFiles.length).toBeGreaterThan(0); | ||||||||||
| const offenders = []; | ||||||||||
| for (const file of workflowFiles) { | ||||||||||
| const text = readFileSync(join(workflowsDir, file), 'utf8'); | ||||||||||
| // The class excludes `\n` so a `<!--` inside a nearby comment cannot | ||||||||||
| // let one match span lines, swallow the real marker, and get discarded | ||||||||||
| // by the comment skip below — the guard would then pass with the very | ||||||||||
| // regression present. `>` stays allowed inside a marker (lazy match to | ||||||||||
| // the first `-->` on the line) so arrow-style markers are covered too. | ||||||||||
| const re = /<!--[^\n]*?-->/g; | ||||||||||
| let m; | ||||||||||
| while ((m = re.exec(text)) !== null) { | ||||||||||
| const lineStart = text.lastIndexOf('\n', m.index) + 1; | ||||||||||
| const prefix = text.slice(lineStart, m.index); | ||||||||||
| // Prose in a YAML or shell comment never reaches a comment body. | ||||||||||
| if (/^\s*#/.test(prefix)) continue; | ||||||||||
|
Comment on lines
+2341
to
+2342
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] The comment skip only fires for comments that OPEN a physical line; an end-of-line YAML or shell comment containing a quoted marker with glued prose is still scanned and flagged, contradicting the rationale comment above it. Probe-verified against the verbatim scan logic: Fix: declare the limit in the known-gaps comment alongside its siblings, or strip trailing-comment regions before scanning (a 中文说明注释跳过只对"以注释开头"的物理行生效;行尾的 YAML 或 shell 注释中若包含带粘接正文的引号标记,仍会被扫描并标记,与上方的理由注释相矛盾。已用逐字提取的扫描逻辑验证: — qwen3.8-max via Qwen Code /review (v0.21.8) |
||||||||||
| const quote = m.index === lineStart ? null : text[m.index - 1]; | ||||||||||
|
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] Markers at physical line start are skipped unconditionally ( 中文说明物理行首的标记被无条件跳过( — qwen3.8-max via Qwen Code /review (v0.21.7) |
||||||||||
| // Only a marker that OPENS a string literal can be building a body. | ||||||||||
| if (quote !== "'" && quote !== '"') continue; | ||||||||||
|
Comment on lines
+2343
to
+2345
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] Whitespace between the opening quote and the marker evades this check ( 中文说明[建议] 开引号与标记之间的空白可以绕过这一检查(此时 — qwen3.8-max via Qwen Code /review (v0.21.8) |
||||||||||
| const rest = text.slice(m.index + m[0].length); | ||||||||||
| const lineEnd = rest.indexOf('\n'); | ||||||||||
| const line = lineEnd === -1 ? rest : rest.slice(0, lineEnd); | ||||||||||
| const end = line.indexOf(quote); | ||||||||||
| // No closing quote on this line means either the marker ends the line | ||||||||||
| // inside a multi-line literal (a real newline separates the body, | ||||||||||
| // which renders) or the quote is prose in a doc example — neither | ||||||||||
| // glues anything ON the marker's line. | ||||||||||
| if (end === -1) continue; | ||||||||||
| let glued = line.slice(0, end); | ||||||||||
| if (glued === '') { | ||||||||||
| // The literal ended at the marker — but an adjacent literal on the | ||||||||||
| // same line concatenates onto it at runtime, and so does an | ||||||||||
| // unquoted `$(…)` (its output is invisible to this scan). | ||||||||||
| const after = line.slice(end + 1); | ||||||||||
| if (after[0] === "'" || after[0] === '"') { | ||||||||||
| const q2 = after[0]; | ||||||||||
| const e2 = after.slice(1).indexOf(q2); | ||||||||||
| glued = e2 === -1 ? '' : after.slice(1, 1 + e2); | ||||||||||
| } else if (after[0] === '$' && after[1] === '(') { | ||||||||||
| glued = after; | ||||||||||
| } | ||||||||||
|
Comment on lines
+2365
to
+2367
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] The same-line continuation model after an empty marker literal only recognizes a following quoted literal or Fix options: declare these shapes in known-gaps, or treat any non-whitespace continuation outside the marker literal as suspect. Caution if closing the hole: a naive "any text after 中文说明空标记字面量之后的同行续接模型只识别紧随的引号字面量或 — qwen3.8-max via Qwen Code /review (v0.21.8) |
||||||||||
| } | ||||||||||
| if (glued === '') continue; | ||||||||||
| // The two-character `\n` escape separates only where the shell | ||||||||||
| // expands it: a printf format string or ANSI-C `$'…'` opened at the | ||||||||||
| // END of the prefix — an unrelated printf earlier on the same line | ||||||||||
| // must not bless a plain double-quoted assignment, where `\n` stays | ||||||||||
| // a literal backslash-n and the prose stays on the marker's line. | ||||||||||
| if ( | ||||||||||
| glued.startsWith('\\n') && | ||||||||||
| /printf\s+(?:-\S+\s+(?:\S+\s+)?|--\s+)?['"]$|\$'$/.test(prefix) | ||||||||||
| ) { | ||||||||||
| continue; | ||||||||||
|
Comment on lines
+2375
to
+2379
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] The 中文说明[建议] — qwen3.8-max via Qwen Code /review (v0.21.8) |
||||||||||
| } | ||||||||||
| offenders.push( | ||||||||||
| `${file}: ${text.slice(m.index, m.index + 56).split('\n')[0]}`, | ||||||||||
| ); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| expect(offenders).toEqual([]); | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| it('pins the workflow-run URL into the ack printf', () => { | ||||||||||
| // bash printf with a leftover argument and no conversion spec exits 0 | ||||||||||
| // under `set -euo pipefail` and emits `[workflow run]()`, so nothing | ||||||||||
| // else catches a dropped `%s` or `"$RUN_URL"` on the ack line. Assert | ||||||||||
| // the link shape, not bare co-existence: a `%s` displaced out of the | ||||||||||
| // parens keeps both pieces on the line and re-ships the dead link. | ||||||||||
| const ackLine = workflow | ||||||||||
| .split('\n') | ||||||||||
| .find( | ||||||||||
| (l) => l.includes('printf') && l.includes('<!-- qwen-review-ack -->'), | ||||||||||
| ); | ||||||||||
| expect(ackLine).toBeDefined(); | ||||||||||
| expect(ackLine).toContain('[workflow run](%s)'); | ||||||||||
| expect(ackLine).toContain('"$RUN_URL"'); | ||||||||||
|
Comment on lines
+2400
to
+2402
Collaborator
Author
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] The URL-pin test asserts Failure scenario: a future edit rewrites the ack line to
Suggested change
中文说明URL 固定测试只断言 — DeepSeek/deepseek-v4-flash via Qwen Code /review (v0.21.8) |
||||||||||
| }); | ||||||||||
| }); | ||||||||||
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] No test pins the
%s→"$RUN_URL"weaving on this changed line — the new regression test constrains only the marker/body separator. Failure scenario: a future edit drops%s(or the argument) → bash printf with a leftover argument and no conversion spec exits 0 underset -euo pipefailand emits[workflow run]()→ the ack comment ships with an empty workflow-run link while the whole suite stays green (verified in bash at this commit). Becauseissue_comment-triggered reviews never appear in the PR's checks list, the author silently loses the only pointer to their review run — the exact defect this PR fixes, reintroduced with no red test. Suggested fix: assert in this file's source-level style that the printf carrying the marker contains%sand is passed"$RUN_URL"(stronger: execute the ack script with a stubbedghand assert the captured body contains the marker, a blank line, and the run URL).中文说明
本行改动中
%s→"$RUN_URL"的编织没有任何测试固定——新增的回归测试只约束了标记与正文之间的分隔。失败场景:未来的编辑删掉%s(或参数)→ bash printf 带着多余参数且无转换说明符,在set -euo pipefail下仍以 0 退出并输出[workflow run]()→ ack 评论带着空的 workflow-run 链接发出,而整个套件保持全绿(已在本提交的 bash 中验证)。由于issue_comment触发的评审从不出现在 PR 的 checks 列表中,作者会悄然失去指向其评审 run 的唯一入口——这正是本 PR 修复的缺陷,且无测试变红地被重新引入。建议修复:以本文件现有的源码级断言风格,断言承载标记的 printf 含有%s且传入了"$RUN_URL"(更强:用打桩的gh执行 ack 脚本,断言捕获的 body 含标记、空行与 run URL)。— qwen3.8-max via Qwen Code /review (v0.21.7)