diff --git a/scripts/features/lib/delivered.mjs b/scripts/features/lib/delivered.mjs new file mode 100644 index 0000000000..fbdf6b8c3f --- /dev/null +++ b/scripts/features/lib/delivered.mjs @@ -0,0 +1,33 @@ +/** + * Delivery detection: PR merged + CHANGELOG + git log, with confidence grading. + */ + +const VERSION_HEADER_RE = /^##\s+\[?(\d+\.\d+\.\d+)\]?/; + +export function parseChangelog(text, issueNumber) { + if (typeof text !== "string") return null; + const needle = `#${issueNumber}`; + const lines = text.split("\n"); + + let currentSection = null; + let currentVersion = null; + for (const line of lines) { + const headerMatch = line.match(VERSION_HEADER_RE); + if (headerMatch) { + currentSection = line.trim(); + currentVersion = headerMatch[1]; + continue; + } + if (line.includes(needle) && currentSection) { + const match = line.match(/\(#\d+\)/); + if (match && match[0] === `(${needle})`) { + return { + section: currentSection, + version: currentVersion, + line: line.trim(), + }; + } + } + } + return null; +} diff --git a/tests/unit/feature-triage/delivered.test.mjs b/tests/unit/feature-triage/delivered.test.mjs new file mode 100644 index 0000000000..77c12ae912 --- /dev/null +++ b/tests/unit/feature-triage/delivered.test.mjs @@ -0,0 +1,42 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { parseChangelog } from "../../../scripts/features/lib/delivered.mjs"; + +describe("parseChangelog", () => { + it("returns section header when #N found", () => { + const text = ` +# Changelog + +## [3.7.2] - 2026-03-15 + +- Fix something (#980) +- Other item + +## [3.7.1] - 2026-03-01 + +- Earlier change (#900) +`; + const r = parseChangelog(text, 980); + assert.deepEqual(r, { + section: "## [3.7.2] - 2026-03-15", + version: "3.7.2", + line: "- Fix something (#980)", + }); + }); + + it("returns null when #N not found", () => { + const text = `## [3.7.2]\n- Item #100\n`; + assert.equal(parseChangelog(text, 999), null); + }); + + it("ignores #N appearing without # prefix", () => { + const text = `## [3.7.2]\n- Issue 980 (no hash)\n`; + assert.equal(parseChangelog(text, 980), null); + }); + + it("handles plain version headers without brackets/date", () => { + const text = `## 3.7.2\n- Fix (#980)\n`; + const r = parseChangelog(text, 980); + assert.equal(r.version, "3.7.2"); + }); +});