feat/commit-split-and-pr/main #45

Merged
admin merged 15 commits from feat/commit-split-and-pr/main into master 2026-09-17 08:25:36 +00:00
Showing only changes of commit 513dad7f6a - Show all commits
+76 -1
View File
@@ -11,7 +11,8 @@ import { assertNeutralPrompt, readPrompt } from './helpers/prompt-doc.js';
const prompt = readPrompt('sdlc-feat');
/** 各段的內容分開切,避免把別段的字樣誤認成這一段的規則 */
const phase1 = prompt.slice(prompt.indexOf('## 第一段'), prompt.indexOf('## 第二段'));
const phase2 = prompt.slice(prompt.indexOf('## 第二段'), prompt.indexOf('## 邊界'));
const phase2 = prompt.slice(prompt.indexOf('## 第二段'), prompt.indexOf('## 第三段'));
const phase3 = prompt.slice(prompt.indexOf('## 第三段'), prompt.indexOf('## 邊界'));
test('正本平台中立,description 前綴正確', () => {
assertNeutralPrompt(prompt, 'sdlc-feat');
@@ -180,3 +181,77 @@ test('邊界把第二段不做的事也列出來', () => {
assert.match(boundary, /不提交、不開 PR、不停錶/);
assert.match(boundary, /不改與待辦無關的程式碼/);
});
// ── 第三段:提交與開立 PR ─────────────────────────────────────────
test('第三段指名兩支腳本,順序為先提交再開 PR', () => {
const order = ['commit-split.js', 'pr-create.js'];
const positions = order.map((name) => phase3.indexOf(name));
assert.equal(positions.every((p) => p >= 0), true, '兩支腳本都要被指名');
assert.deepEqual([...positions].sort((a, b) => a - b), positions);
});
test('要等待辦全部勾完才進第三段', () => {
assert.match(phase3, /全部待辦都勾完之後才進這一段/);
});
test('--type 是程式碼那一批的類型,其餘由腳本自己認', () => {
assert.match(phase3, /測試、文件與設定檔\s*\n?由腳本自己認出來|由腳本自己認出來/);
assert.match(phase3, /不必也不能指定/);
});
test('--scope 什麼時候要給寫清楚了', () => {
assert.match(phase3, /只在某一批有多個檔案時才需要/);
assert.match(phase3, /單檔那批的 scope 就是檔名/);
});
test('commit 描述要用繁體中文,並交代夾雜英文的處理', () => {
assert.match(phase3, /描述用繁體中文/);
assert.match(phase3, /保留原文/);
});
test('跨兩個功能時要分兩次跑,且指名用哪個旗標做得到', () => {
assert.match(phase3, /分兩次跑/);
assert.match(phase3, /--files/, '光說「分兩次跑」而不說怎麼分,等於沒說');
assert.match(phase3, /失去了分批的意義/);
});
test('PR 描述的八個段落都列出來,且標明順序不能換', () => {
for (const section of [
'摘要', '需求議題', '工作包議題', '變更內容',
'設計重點', '解決的問題', '影響的功能', '測試結果',
]) {
assert.match(phase3, new RegExp(`\\*\\*${section}\\*\\*`), `缺少段落說明:${section}`);
}
assert.match(phase3, /順序不能換/);
});
test('測試結果要放實際輸出,並交代沒有自動化測試時怎麼辦', () => {
assert.match(phase3, /放實際跑過的輸出/);
assert.match(phase3, /已測試通過/, '要指名這句被禁止的寫法');
assert.match(phase3, /手動\s*\n?驗證步驟|手動驗證步驟/);
assert.match(phase3, /補真的內容/);
});
test('標題由腳本設為分支名,不另外指定', () => {
assert.match(phase3, /標題由腳本設為分支名/);
assert.match(phase3, /不必也不能另外指定/);
});
test('先開 PR 再停錶,且 PR 沒開成就不停錶', () => {
assert.match(phase3, /先開 PR 再停錶/);
assert.match(phase3, /沒開成就不停錶/);
assert.match(phase3, /工時要記在真的有做事的那段時間上/);
});
test('兩支腳本都要求先試跑', () => {
const dryRuns = phase3.match(/--dry-run/g) ?? [];
assert.ok(dryRuns.length >= 2, `兩支寫入型腳本各要先試跑,只找到 ${dryRuns.length} 處`);
});
test('邊界把第三段不做的事也列出來', () => {
const boundary = prompt.slice(prompt.indexOf('## 邊界'));
assert.match(boundary, /第三段不改任何一行程式碼/);
assert.match(boundary, /不把「已測試通過」這種空話/);
assert.match(boundary, /不代替使用者決定 commit 的類型與描述/);
});