From 930270f545ebbf31690dfaa74c6aa8a98b343dc4 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Thu, 17 Sep 2026 05:11:06 +0000 Subject: [PATCH] =?UTF-8?q?refactor(=E6=B8=AC=E8=A9=A6):=20=E6=AD=A3?= =?UTF-8?q?=E6=9C=AC=E7=9A=84=E5=85=B1=E7=94=A8=E6=AA=A2=E6=9F=A5=E6=8A=BD?= =?UTF-8?q?=E5=88=B0=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit description 前綴、沒有 frontmatter、不出現平台專屬字樣——這三件事每一份流程 正本都要驗,第二份正本出現時就該抽出來,而不是再抄一次。順帶把讀正本、讀規則 正本、讀模板三個路徑組合也收在同一處。 Co-Authored-By: Claude Opus 5 (1M context) --- test/helpers/prompt-doc.js | 62 +++++++++++++++++++++++++++++++++++ test/sdlc-plan-assets.test.js | 39 +++------------------- 2 files changed, 67 insertions(+), 34 deletions(-) create mode 100644 test/helpers/prompt-doc.js diff --git a/test/helpers/prompt-doc.js b/test/helpers/prompt-doc.js new file mode 100644 index 0000000..292bd54 --- /dev/null +++ b/test/helpers/prompt-doc.js @@ -0,0 +1,62 @@ +/** + * 流程正本與規則正本的共用檢查。 + * + * 這些檔案是文件不是程式,但它們是各指令實際交付的東西:正本的平台中立性決定 + * 轉接檔能不能一份寫到底,段落結構決定下游解析得到什麼。靠人記不牢,用測試釘住。 + */ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { repoRoot } from './run-script.js'; + +/** 讀一份流程正本 */ +export function readPrompt(name) { + return readFileSync(join(repoRoot, 'prompts', `${name}.md`), 'utf8'); +} + +/** 讀一份規則正本 */ +export function readReference(name) { + return readFileSync(join(repoRoot, 'references', `${name}.md`), 'utf8'); +} + +/** 讀一份輸出模板 */ +export function readTemplate(name) { + return readFileSync(join(repoRoot, 'templates', `${name}.md`), 'utf8'); +} + +/** + * 正本裡不該出現的字樣:任何一家助理的工具名、目錄名或呼叫語法。 + * 出現任何一個,就代表這份正本已經綁死在某個平台上。 + */ +export const PLATFORM_SPECIFIC = [ + 'AskUserQuestion', + 'Claude', + 'Codex', + 'Antigravity', + 'Copilot', + 'Kiro', + 'OpenCode', + 'oh-my-pi', + '.claude', + '.codex', +]; + +/** + * 斷言一份正本是平台中立的,且 description 帶上指定前綴。 + * @param {string} prompt 正本內容 + * @param {string} command 指令名,例如 sdlc-plan + */ +export function assertNeutralPrompt(prompt, command) { + const description = prompt.match(/^description:\s*(.+)$/m)?.[1]; + assert.ok(description, '正本需要一行 description 供轉接檔取用'); + assert.ok( + description.startsWith(`僅由 /${command} 指令叫用。`), + `description 前綴不符:${description}`, + ); + + assert.equal(prompt.startsWith('---'), false, '正本不該有 YAML frontmatter,那是轉接檔的事'); + + for (const token of [...PLATFORM_SPECIFIC, `$${command}`]) { + assert.equal(prompt.includes(token), false, `正本不該出現平台專屬字樣:${token}`); + } +} diff --git a/test/sdlc-plan-assets.test.js b/test/sdlc-plan-assets.test.js index f281f55..c3add9f 100644 --- a/test/sdlc-plan-assets.test.js +++ b/test/sdlc-plan-assets.test.js @@ -7,12 +7,10 @@ */ import test from 'node:test'; import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; -import { join } from 'node:path'; -import { repoRoot } from './helpers/run-script.js'; +import { assertNeutralPrompt, readPrompt, readTemplate } from './helpers/prompt-doc.js'; -const template = readFileSync(join(repoRoot, 'templates', 'requirement-issue.md'), 'utf8'); -const prompt = readFileSync(join(repoRoot, 'prompts', 'sdlc-plan.md'), 'utf8'); +const template = readTemplate('requirement-issue'); +const prompt = readPrompt('sdlc-plan'); /** 需求議題的九個段落,順序即議題裡的順序 */ const SECTIONS = [ @@ -50,35 +48,8 @@ test('總覽段落預留了總覽網頁的連結佔位', () => { // ── 流程正本 ─────────────────────────────────────────────────────── -test('正本的 description 以「僅由 /sdlc-plan 指令叫用。」起頭', () => { - const description = prompt.match(/^description:\s*(.+)$/m)?.[1]; - assert.ok(description, '正本需要一行 description 供轉接檔取用'); - assert.ok( - description.startsWith('僅由 /sdlc-plan 指令叫用。'), - `description 前綴不符:${description}`, - ); -}); - -test('正本是平台中立的:不出現任何特定助理的語法或名稱', () => { - const platformSpecific = [ - 'AskUserQuestion', - 'Claude', - 'Codex', - 'Antigravity', - 'Copilot', - 'Kiro', - 'OpenCode', - 'oh-my-pi', - '.claude', - '$sdlc-plan', - ]; - for (const token of platformSpecific) { - assert.equal(prompt.includes(token), false, `正本不該出現平台專屬字樣:${token}`); - } -}); - -test('正本沒有 YAML frontmatter:那是各平台轉接檔的事', () => { - assert.equal(prompt.startsWith('---'), false); +test('正本平台中立,description 前綴正確', () => { + assertNeutralPrompt(prompt, 'sdlc-plan'); }); test('正本交代了三種輸入都要能吃', () => {