diff --git a/scripts/issue-body.js b/scripts/issue-body.js index 280e745..15ef9a3 100644 --- a/scripts/issue-body.js +++ b/scripts/issue-body.js @@ -182,6 +182,19 @@ export function referencedIndex(sections, name, label) { return null; } +/** + * 工作包掛在哪一顆需求議題底下:關聯段落的 `需求議題:#7`。 + * + * 這是「這顆議題是不是工作包、屬於誰」的唯一判準,抽取(wp-extract)與清單(wp-list) + * 共用同一個函式。兩邊各寫一次也跑得起來,但歸屬規則一旦有兩份,某天只會有一邊被改到, + * 而分岔的樣子是「清單裡看得到、抽取卻說不是」——那種不一致沒有人看得懂。 + * @param {Map} sections + * @returns {number|null} 沒填或不是工作包時為 null + */ +export function requirementIndex(sections) { + return referencedIndex(sections, '關聯', '需求議題'); +} + /** * 在段落裡找出「標籤:數字」那一行的數字,例如關聯段落的 `估算人天:3`。 * 與 referencedIndex 同形狀,差別只在這裡要的是數量而非議題編號,所以認小數。 diff --git a/scripts/wp-extract.js b/scripts/wp-extract.js index d96a0fc..5486510 100644 --- a/scripts/wp-extract.js +++ b/scripts/wp-extract.js @@ -29,7 +29,7 @@ import { checklistInSection, listSection, parseSections, - referencedIndex, + requirementIndex, tableRows, textSection, } from './issue-body.js'; @@ -77,7 +77,7 @@ main(async () => { index: issue.number, url: issue.html_url, title: issue.title, - 需求議題: referencedIndex(sections, '關聯', '需求議題'), + 需求議題: requirementIndex(sections), 描述: textSection(sections, '描述'), 架構圖: textSection(sections, '架構圖'), 範圍邊界: listSection(sections, '範圍邊界'), diff --git a/scripts/wp-list.js b/scripts/wp-list.js index 88cf842..53b67e6 100644 --- a/scripts/wp-list.js +++ b/scripts/wp-list.js @@ -6,9 +6,9 @@ * 單位是工作包。這一支把「哪幾顆工作包掛在這顆需求底下」答出來,讓他從清單裡挑一顆, * 而不是自己去 Gitea 網頁上翻。 * - * **判準沿用工作包抽取那一套**:關聯段落裡的 `需求議題:#<編號>`(見 wp-extract 的 - * `需求議題` 欄位,解析同樣走 issue-body 的 referencedIndex)。不另發明判準——標籤、 - * 標題前綴、相依關係都各有各的用途,拿它們當歸屬會與抽取契約分岔。 + * **判準沿用工作包抽取那一套**:關聯段落裡的 `需求議題:#<編號>`。判準與 wp-extract 的 + * `需求議題` 欄位共用 issue-body 的 requirementIndex,不是各寫一份長得像的解析—— + * 標籤、標題前綴、相依關係都當得了歸屬判準,但各發明一套就會與抽取契約分岔。 * * **PR 不算工作包。** 每個 PR 都是議題,而 pr-create 產出的 PR 描述本來就有 * 「需求議題:#N」那一行,只看 body 會把 PR 混進清單裡。 @@ -28,7 +28,7 @@ import { preflight, resolveLogin, } from './lib.js'; -import { parseSections, referencedIndex } from './issue-body.js'; +import { parseSections, requirementIndex } from './issue-body.js'; main(async () => { const flags = parseFlags(process.argv.slice(2), { @@ -83,5 +83,5 @@ main(async () => { */ function belongsTo(issue, requirement) { if (issue.pull_request != null) return false; - return referencedIndex(parseSections(issue.body), '關聯', '需求議題') === requirement; + return requirementIndex(parseSections(issue.body)) === requirement; } diff --git a/test/sdlc-fix-assets.test.js b/test/sdlc-fix-assets.test.js index 86f819a..ec93c03 100644 --- a/test/sdlc-fix-assets.test.js +++ b/test/sdlc-fix-assets.test.js @@ -216,7 +216,7 @@ test('一次問一題,選項含手動輸入', () => { }); test('該問的情況有列舉,不是一句「不確定就問」', () => { - const section = steps.slice(steps.indexOf('## 6.'), steps.indexOf('## 7.')); + const section = step('## 6.', '## 7.'); const bullets = section.match(/^- /gm) ?? []; assert.ok(bullets.length >= 3, `該問的情況要列得出來,只找到 ${bullets.length} 條`); assert.match(section, /推了新 commit/, '位置對不上是最常見的一種,要點名'); @@ -257,7 +257,7 @@ test('寫入前要求先試跑', () => { // ── 摘要 ─────────────────────────────────────────────────────────── test('摘要要逐則列出,並單獨點出沒有記號的那幾則', () => { - const section = steps.slice(steps.indexOf('## 8.')); + const section = steps.slice(steps.indexOf('## 8.')); // 最後一步,到結尾為止 assert.match(section, /逐則一行/); assert.match(section, /沒有留下記號的那幾則/); assert.match(section, /以為它們被跳過/, '要說出為什麼得單獨列'); diff --git a/test/wp-list.test.js b/test/wp-list.test.js index cce5c74..aec4c7e 100644 --- a/test/wp-list.test.js +++ b/test/wp-list.test.js @@ -54,11 +54,11 @@ function routes(issues, overrides = {}) { }); } -async function run(t, issues, args = []) { +async function run(t, issues) { const stub = await withStubGitea(t, routes(issues)); const result = await runScript( 'wp-list.js', - ['--repo', REPO, '--requirement', String(REQUIREMENT), ...args], + ['--repo', REPO, '--requirement', String(REQUIREMENT)], { env: envFor(stub) }, ); return { stub, ...result };