From b47129420a4630726390fa13ed3f2658d5cdc46f Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Thu, 6 Aug 2026 06:15:05 +0200 Subject: [PATCH 1/4] fix(issue-quality): treat media-only sections as empty so image-only goals cannot hide repeated prose (#1098) An HTML or markdown image in a section made it look non-empty, so repeated identical prose in the other sections escaped duplicate and title-repeat detection and the issue passed the quality gate. Strip media-only tokens in clean() so media-only sections participate in emptiness/duplicate checks like any other blank section. Closes the image-only-section bypass seen on #1098. --- .github/scripts/issue-quality.cjs | 51 ++++++++++++ .github/scripts/issue-quality.test.cjs | 111 +++++++++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/.github/scripts/issue-quality.cjs b/.github/scripts/issue-quality.cjs index 232789f8f1..a0fae2760c 100644 --- a/.github/scripts/issue-quality.cjs +++ b/.github/scripts/issue-quality.cjs @@ -51,12 +51,61 @@ function isPlaceholderOnlyValue(raw) { return PLACEHOLDER_ONLY_RE.test(value); } +/** + * Strip image/media-only content from a markdown or HTML fragment so that a + * section whose only content is a screenshot or media embed is treated as + * empty by the validators. + * + * Handles: + * - Markdown images: `![alt](url)`, `![alt](url "title")` + * - HTML and ... blocks + * - Common media embeds (video/audio) when they are the only content + * + * Text mixed with media (for example a caption or repro steps around an + * image) is preserved; only the media tokens themselves are removed. + */ +function stripMediaTokens(text) { + if (typeof text !== "string") return ""; + return text + // HTML media tags: (self-closing or not), ..., + // , (kept as whole blocks so a lone + // media embed does not leave stray tags behind). + .replace(//gi, " ") + .replace(//gi, " ") + .replace(//gi, " ") + .replace(/]*>/gi, " ") + // Markdown images, optionally with a title: ![alt](url "title"). Alt text + // may contain balanced brackets (for example ![Image [screenshot]](url)). + .replace(/!\[(?:[^\[\]]|\[[^\]]*\])*\]\([^)]*\)/g, " ") + // HTML comment tokens that may wrap media. + .replace(//g, " "); +} + +/** + * True when a section contains no substantive text after removing media + * tokens and whitespace. Used to decide whether a media-only section should + * count as empty for quality validation. + */ +function isMediaOnly(text) { + if (typeof text !== "string") return false; + const stripped = stripMediaTokens(text); + return stripped.replace(/\s+/g, "").length === 0; +} + /** * Strip HTML comments, placeholder-only values, and trim whitespace. */ function clean(raw) { if (typeof raw !== "string") return ""; let s = raw.replace(//g, ""); + // Media-only sections (a lone screenshot or embed) carry no reportable + // text. Strip the media tokens so the section participates in emptiness and + // duplicate detection like any other blank section. This closes the + // image-only-section bypass (see #1098: an ``-only goal hid repeated + // prose in the other sections from duplicate detection). + if (isMediaOnly(s)) { + s = stripMediaTokens(s).replace(/\s+/g, " ").trim(); + } // Whole-value placeholders first (including a single enclosing fence), so // line-by-line stripping cannot leave bare fence markers behind. if (isPlaceholderOnlyValue(s)) return ""; @@ -1290,6 +1339,8 @@ module.exports = { clean, normalise, canonicalise, + stripMediaTokens, + isMediaOnly, extractSection, resolveSection, detectIssueKind, diff --git a/.github/scripts/issue-quality.test.cjs b/.github/scripts/issue-quality.test.cjs index 8ecc4e7aa3..dcd44ad9aa 100644 --- a/.github/scripts/issue-quality.test.cjs +++ b/.github/scripts/issue-quality.test.cjs @@ -20,6 +20,8 @@ const { isPlaceholder, isRawPlaceholder, isUnusableVersion, + stripMediaTokens, + isMediaOnly, countWords, hasConcreteDetail, hasActionableReproductionDetail, @@ -235,6 +237,115 @@ describe("validateIssue - feature", () => { assert.ok(result.reasons.length > 0); }); + it("rejects an image-only goal section that hides repeated prose (#1098)", () => { + // Regression for #1098: an HTML in the goal section made the goal + // look non-empty, so the repeated identical sentences in the other three + // sections were not caught as duplicates and the issue passed validation. + const repeated = + "It is hoped that the usage query will support time-based queries and statistics, as well as key-based queries and statistics"; + const img = + 'Image'; + const body = [ + "### Area", + "CLI", + "### What are you trying to accomplish?", + img, + "### What prevents this today?", + repeated, + "### What should OpenCodex do?", + repeated, + "### Example usage or interface", + repeated, + ].join("\n"); + const result = validateIssue({ title: repeated, body, labels: ["enhancement"] }); + assert.equal(result.kind, "feature"); + assert.equal(result.valid, false); + assert.ok( + result.reasons.some((r) => /missing or empty/i.test(r)), + `Expected missing/empty reason, got: ${result.reasons.join("; ")}`, + ); + assert.ok( + result.reasons.some((r) => /same content/i.test(r)), + `Expected duplicate-content reason, got: ${result.reasons.join("; ")}`, + ); + }); + + it("rejects a markdown-image-only goal section with repeated prose (#1098)", () => { + const repeated = + "It is hoped that the usage query will support time-based queries and statistics, as well as key-based queries and statistics"; + const mdImg = "![Image](https://github.com/user-attachments/assets/17ea27a8-cec6-4591-aa09-a0ce36f1211f)"; + const body = [ + "### What are you trying to accomplish?", + mdImg, + "### What prevents this today?", + repeated, + "### What should OpenCodex do?", + repeated, + "### Example usage or interface", + repeated, + ].join("\n"); + const result = validateIssue({ title: repeated, body, labels: ["enhancement"] }); + assert.equal(result.kind, "feature"); + assert.equal(result.valid, false); + assert.ok( + result.reasons.some((r) => /missing or empty/i.test(r)), + `Expected missing/empty reason, got: ${result.reasons.join("; ")}`, + ); + }); + + it("rejects a markdown image with bracketed alt text in the goal (#1098)", () => { + const repeated = + "It is hoped that the usage query will support time-based queries and statistics, as well as key-based queries and statistics"; + // GitHub permits balanced brackets inside image alt text, e.g. + // ![Image [screenshot]](url). The stripper must still treat it as + // media-only so it cannot hide repeated prose. + const mdImg = "![Image [screenshot]](https://example.com/x.png)"; + const body = [ + "### What are you trying to accomplish?", + mdImg, + "### What prevents this today?", + repeated, + "### What should OpenCodex do?", + repeated, + "### Example usage or interface", + repeated, + ].join("\n"); + const result = validateIssue({ title: repeated, body, labels: ["enhancement"] }); + assert.equal(result.kind, "feature"); + assert.equal(result.valid, false); + assert.ok( + result.reasons.some((r) => /missing or empty/i.test(r)), + `Expected missing/empty reason, got: ${result.reasons.join("; ")}`, + ); + }); + + it("preserves a goal section that mixes an image with real text", () => { + const goal = [ + "![Screenshot](https://example.com/shot.png)", + "Route voice requests to a configured fallback provider when the primary quota is exhausted.", + ].join("\n"); + const result = validateIssue({ + title: "Voice fallback routing", + body: featureBodyWithGoal(goal), + labels: ["enhancement"], + }); + assert.equal(result.kind, "feature"); + assert.equal(result.valid, true); + }); + + it("treats image/media-only sections as empty via isMediaOnly", () => { + assert.equal(isMediaOnly(''), true); + assert.equal(isMediaOnly("![alt](https://example.com/x.png)"), true); + assert.equal(isMediaOnly("![alt [with bracket]](https://example.com/x.png)"), true); + assert.equal(isMediaOnly(''), true); + assert.equal(isMediaOnly(''), true); + assert.equal(isMediaOnly('\nCaption text'), false); + assert.equal(isMediaOnly("Some real description."), false); + assert.equal(stripMediaTokens('').trim(), ""); + assert.equal(stripMediaTokens('![alt](url "title")').trim(), ""); + assert.equal(stripMediaTokens('before ![alt](url) after').replace(/\s+/g, " ").trim(), "before after"); + }); + it("accepts a concise but actionable feature", () => { const body = [ "### Area", From 98de51002be3259b13040f57d9a4c6c1e90f8f84 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Thu, 6 Aug 2026 06:25:47 +0200 Subject: [PATCH 2/4] fix(issue-quality): scan markdown images with balanced delimiters (CodeRabbit) Replace the regex-only markdown-image matcher with a small balanced scanner so destinations containing balanced parentheses (e.g. ![diagram](https://example.com/image_(final).png)) are stripped as media-only instead of leaving a trailing fragment that evades the empty-section check. Also assert the repeated-title reason in the #1098 regression test and cover balanced-paren URLs and malformed destinations. --- .github/scripts/issue-quality.cjs | 100 +++++++++++++++++++++++-- .github/scripts/issue-quality.test.cjs | 34 +++++++++ 2 files changed, 127 insertions(+), 7 deletions(-) diff --git a/.github/scripts/issue-quality.cjs b/.github/scripts/issue-quality.cjs index a0fae2760c..0274022cc5 100644 --- a/.github/scripts/issue-quality.cjs +++ b/.github/scripts/issue-quality.cjs @@ -66,19 +66,105 @@ function isPlaceholderOnlyValue(raw) { */ function stripMediaTokens(text) { if (typeof text !== "string") return ""; - return text + const htmlStripped = text // HTML media tags: (self-closing or not), ..., // , (kept as whole blocks so a lone // media embed does not leave stray tags behind). .replace(//gi, " ") .replace(//gi, " ") .replace(//gi, " ") - .replace(/]*>/gi, " ") - // Markdown images, optionally with a title: ![alt](url "title"). Alt text - // may contain balanced brackets (for example ![Image [screenshot]](url)). - .replace(/!\[(?:[^\[\]]|\[[^\]]*\])*\]\([^)]*\)/g, " ") - // HTML comment tokens that may wrap media. - .replace(//g, " "); + .replace(/]*>/gi, " "); + return stripMarkdownImages(htmlStripped); +} + +/** + * Remove Markdown image tokens `![alt](dest "title")` using a small + * balanced scanner instead of a regex, because destinations may contain + * balanced parentheses (for example `image_(final).png`) and alt text may + * contain balanced brackets (`![Image [screenshot]](url)`). + * + * A token is matched only when: + * - it starts with `![` (not escaped); + * - the alt text is balanced with respect to `[` / `]`; + * - the destination is balanced with respect to `(`, `)` and `"` (an + * optional title may follow); and + * - the token closes with a `)`. + * + * Malformed tokens (unbalanced destination, e.g. `a)b.png)`) are left in + * place — they are not valid Markdown images and must not be silently + * dropped. + */ +function stripMarkdownImages(text) { + if (typeof text !== "string") return ""; + const out = []; + let i = 0; + while (i < text.length) { + // A backslash-escaped or code-fenced `![` is not an image token. We only + // guard the common `\!` escape here; fenced blocks are handled by the + // section extractor upstream, which does not include them in sections. + if (text[i] === "!" && text[i + 1] === "[") { + const end = scanMarkdownImage(text, i); + if (end !== -1) { + out.push(" "); + i = end; + continue; + } + } + out.push(text[i]); + i += 1; + } + return out.join(""); +} + +/** + * Scan a Markdown image token starting at `start` (which points at `!`). + * Returns the index just past the closing `)` on success, or -1 when the + * token is malformed. + */ +function scanMarkdownImage(text, start) { + // Alt text: `![` ... `]` with balanced nested brackets. + let i = start + 2; + let bracketDepth = 0; + for (; i < text.length; i += 1) { + const ch = text[i]; + if (ch === "\\") { + i += 1; // skip escaped character + continue; + } + if (ch === "[") { + bracketDepth += 1; + } else if (ch === "]") { + if (bracketDepth === 0) break; + bracketDepth -= 1; + } + } + if (i >= text.length || text[i] !== "]") return -1; + + // Destination: `(` ... `)` with balanced parentheses. An optional + // whitespace-separated `"title"` may follow the destination. + if (text[i + 1] !== "(") return -1; + i += 2; + let parenDepth = 1; + let inQuotes = false; + for (; i < text.length; i += 1) { + const ch = text[i]; + if (ch === "\\") { + i += 1; // skip escaped character + continue; + } + if (ch === '"') { + inQuotes = !inQuotes; + continue; + } + if (inQuotes) continue; + if (ch === "(") { + parenDepth += 1; + } else if (ch === ")") { + parenDepth -= 1; + if (parenDepth === 0) return i + 1; + } + } + return -1; } /** diff --git a/.github/scripts/issue-quality.test.cjs b/.github/scripts/issue-quality.test.cjs index dcd44ad9aa..7094025087 100644 --- a/.github/scripts/issue-quality.test.cjs +++ b/.github/scripts/issue-quality.test.cjs @@ -268,6 +268,10 @@ describe("validateIssue - feature", () => { result.reasons.some((r) => /same content/i.test(r)), `Expected duplicate-content reason, got: ${result.reasons.join("; ")}`, ); + assert.ok( + result.reasons.some((r) => /repeat the issue title/i.test(r)), + `Expected repeated-title reason, got: ${result.reasons.join("; ")}`, + ); }); it("rejects a markdown-image-only goal section with repeated prose (#1098)", () => { @@ -319,6 +323,32 @@ describe("validateIssue - feature", () => { ); }); + it("rejects a markdown image whose URL contains balanced parentheses (#1098)", () => { + const repeated = + "It is hoped that the usage query will support time-based queries and statistics, as well as key-based queries and statistics"; + // Markdown destinations may contain balanced parentheses, e.g. + // ![diagram](https://example.com/image_(final).png). The stripper must + // still treat it as media-only so it cannot hide repeated prose. + const mdImg = "![diagram](https://example.com/image_(final).png)"; + const body = [ + "### What are you trying to accomplish?", + mdImg, + "### What prevents this today?", + repeated, + "### What should OpenCodex do?", + repeated, + "### Example usage or interface", + repeated, + ].join("\n"); + const result = validateIssue({ title: repeated, body, labels: ["enhancement"] }); + assert.equal(result.kind, "feature"); + assert.equal(result.valid, false); + assert.ok( + result.reasons.some((r) => /missing or empty/i.test(r)), + `Expected missing/empty reason, got: ${result.reasons.join("; ")}`, + ); + }); + it("preserves a goal section that mixes an image with real text", () => { const goal = [ "![Screenshot](https://example.com/shot.png)", @@ -337,6 +367,10 @@ describe("validateIssue - feature", () => { assert.equal(isMediaOnly(''), true); assert.equal(isMediaOnly("![alt](https://example.com/x.png)"), true); assert.equal(isMediaOnly("![alt [with bracket]](https://example.com/x.png)"), true); + assert.equal(isMediaOnly("![diagram](https://example.com/image_(final).png)"), true); + assert.equal(isMediaOnly('![alt](https://example.com/image_(final).png "title")'), true); + assert.equal(isMediaOnly("![bad](https://example.com/a)b.png)"), false); + assert.equal(isMediaOnly("\\![escaped](url)"), false); assert.equal(isMediaOnly(''), true); assert.equal(isMediaOnly(''), true); assert.equal(isMediaOnly('\nCaption text'), false); From 718b57a3f099baec6e6daebd7eb0f7e050726c66 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Thu, 6 Aug 2026 06:28:01 +0200 Subject: [PATCH 3/4] fix(issue-quality): handle reference images, media fallback text, and indented code (Codex) - Strip reference-style markdown images (![alt][ref] plus [ref]: url) when the reference is used by an image, including implicit ![alt][] references. - Preserve fallback/caption prose inside