From ceb723c63e5c6e383046f1018623b29e94a90b61 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Thu, 17 Sep 2026 09:19:11 +0000 Subject: [PATCH] =?UTF-8?q?test(=E6=B5=81=E7=A8=8B=E6=AD=A3=E6=9C=AC):=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20sdlc-sync=EF=BC=8C=E4=B8=A6=E8=AE=93?= =?UTF-8?q?=E5=8F=A6=E5=A4=96=E5=85=A9=E4=BB=BD=E6=AD=A3=E6=9C=AC=E6=95=B4?= =?UTF-8?q?=E4=BD=B5=E5=AE=8C=E8=87=AA=E5=8B=95=E6=8E=A5=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) --- test/sdlc-sync-assets.test.js | 151 ++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 test/sdlc-sync-assets.test.js diff --git a/test/sdlc-sync-assets.test.js b/test/sdlc-sync-assets.test.js new file mode 100644 index 0000000..e71711b --- /dev/null +++ b/test/sdlc-sync-assets.test.js @@ -0,0 +1,151 @@ +/** + * /sdlc-sync 的流程正本,以及另外兩份正本的「接回」那一段。 + * + * 這個指令的價值在於「描述不再騙人」,而三件關鍵事只有正本做得到:挑出哪些留言真的是 + * 決策、寫回去之前讓使用者點頭、以及略過的那幾則要保持未標記。寫漏任何一件, + * 描述就會繼續過期,或者有決策被靜靜吞掉。 + */ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { assertNeutralPrompt, readPrompt } from './helpers/prompt-doc.js'; + +const prompt = readPrompt('sdlc-sync'); +const steps = prompt.slice(prompt.indexOf('## 1.'), prompt.indexOf('## 邊界')); + +test('正本平台中立,description 前綴正確', () => { + assertNeutralPrompt(prompt, 'sdlc-sync'); +}); + +test('兩種議題各自指名對應的抽取腳本', () => { + assert.match(steps, /issue-extract/); + assert.match(steps, /wp-extract/); + assert.match(steps, /需求議題.*工作包議題|工作包議題.*需求議題/s); +}); + +test('留言內容另外拿,並說明為什麼抽取契約不夠', () => { + assert.match(steps, /pr-comments/); + assert.match(steps, /只給數字不給內容/); +}); + +test('說明那支腳本議題與 PR 都收得下,不再宣稱「PR 也是 issue」', () => { + assert.match(steps, /議題與 PR 都收得下/); + assert.equal(steps.includes('Gitea 的 PR 也是 issue'), false, '反過來說才對:每個 PR 都是議題'); +}); + +test('已處理認的是自己打的 +1', () => { + assert.match(steps, /自己打的/); + assert.match(steps, /我同意/); +}); + +test('略過會讓計數停在大於 0,這件事要講明白', () => { + const section = steps.slice(steps.indexOf('## 5.')); + assert.match(section, /停在大於 0/); + assert.match(section, /每次開始時都會再停一次/); + assert.match(section, /他選了略過/, '要讓使用者分得出「沒整併乾淨」與「選了略過」'); +}); + +test('已標記過的留言跳過', () => { + assert.match(steps, /已經標記過的留言不再處理|已處理.*跳過/s); +}); + +// ── 挑決策 ───────────────────────────────────────────────────────── + +test('要整併與不整併各有判斷依據,不是只給兩個詞', () => { + assert.match(steps, /\*\*要整併\*\* — .{10,}/); + assert.match(steps, /\*\*不整併\*\* — .{10,}/); +}); + +test('判斷不了時當成要整併,並說出漏掉的代價', () => { + assert.match(steps, /判斷不了的\*\*當成要整併\*\*/); + assert.match(steps, /描述就會繼續騙後面的人/); +}); + +// ── 先點頭再寫 ───────────────────────────────────────────────────── + +test('寫回去之前要列出方案給使用者看', () => { + assert.match(steps, /先列出來再動手/); + assert.match(steps, /哪一段/); + assert.match(steps, /改完長什麼樣/); +}); + +test('一次問一題,且略過的會保持未標記', () => { + assert.match(steps, /一次問一題/); + assert.match(steps, /略過的留言保持未標記/); + assert.match(steps, /下次執行還會被提出來/); +}); + +test('說明了這一步是描述的最後一道關卡', () => { + assert.match(steps, /最後一道關卡/); + assert.match(steps, /拿它當事實/); +}); + +// ── 寫回 ─────────────────────────────────────────────────────────── + +test('指名 comments-merge,並要求先試跑', () => { + assert.match(steps, /comments-merge\.js/); + assert.match(steps, /--dry-run/); +}); + +test('--content-file 的內容是整段,且說明標題那一行不含在內', () => { + assert.match(steps, /那一段改完的完整內容/); + assert.match(steps, /不含 `## 標題` 那一行/); +}); + +test('--merged 只放真的併進去的,並說出放錯的後果', () => { + assert.match(steps, /只放\*\*真的被併進這一段\*\*/); + assert.match(steps, /再也\n?不會被提出來|再也不會被提出來/); +}); + +test('SECTION_NOT_FOUND 的處置是回頭確認,不是硬塞別的段落', () => { + assert.match(steps, /SECTION_NOT_FOUND/); + assert.match(steps, /不要改用別的段落硬塞/); +}); + +// ── 回報 ─────────────────────────────────────────────────────────── + +test('回報要列出略過的那幾則', () => { + const section = steps.slice(steps.indexOf('## 5.')); + assert.match(section, /略過的那幾則是哪些/); + assert.match(section, /下次還會出現/); +}); + +// ── 接回原本的指令 ───────────────────────────────────────────────── + +test('整併完自動接回,不要求使用者重打指令', () => { + const section = prompt.slice(prompt.indexOf('## 接回原本的指令')); + assert.match(section, /直接接回去/); + assert.match(section, /不要要求使用者重打一次/); +}); + +test('接回之前要重新抽取,並說明為什麼', () => { + const section = prompt.slice(prompt.indexOf('## 接回原本的指令')); + assert.match(section, /先重跑一次抽取/); + assert.match(section, /這一整段就白做了/); +}); + +// ── 邊界 ─────────────────────────────────────────────────────────── + +test('邊界列出不做的事,含不刪改留言', () => { + const boundary = prompt.slice(prompt.indexOf('## 邊界')); + assert.match(boundary, /不自行決定要不要整併/); + assert.match(boundary, /不整份重寫描述/); + assert.match(boundary, /不標記略過的留言/); + assert.match(boundary, /不刪除、不編輯任何留言/); + assert.match(boundary, /誰說過什麼的紀錄/, '要說出為什麼留言不能動'); +}); + +// ── 另外兩份正本的接回 ───────────────────────────────────────────── + +for (const name of ['sdlc-analyze', 'sdlc-feat']) { + test(`${name} 偵測到未整併留言時會轉去整併,並自動接回`, () => { + const other = readPrompt(name); + assert.match(other, /未處理留言數/); + assert.match(other, /直接走 `\/sdlc-sync` 的流程/, `${name} 要真的轉過去,不是叫人自己跑`); + assert.match(other, /自動接回這裡/); + assert.match(other, /不要要求使用者重打指令/); + }); + + test(`${name} 接回前要重新抽取一次`, () => { + assert.match(readPrompt(name), /重新抽取一次拿到更新後的描述/); + }); +}