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