/** * 流程正本與規則正本的共用檢查。 * * 這些檔案是文件不是程式,但它們是各指令實際交付的東西:正本的平台中立性決定 * 轉接檔能不能一份寫到底,段落結構決定下游解析得到什麼。靠人記不牢,用測試釘住。 */ 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}`); } }