Files
tea-sdlc/test/helpers/prompt-doc.js
T
jiantw83andClaude Opus 5 0aa0439061 test(總覽網頁): 釘住模板結構與兩份正本的規則
模板的檢查重點是「樣式與內容分離」與「全景段落能整段消失」,兩者壞掉時規劃版會
留下空標題或行內樣式散落各處,肉眼不容易發現。

順帶把 work-package 測試的 phase2 切法收斂到第三段之前——原本切到檔尾,第三段
新增的編號清單會混進段落順序的斷言裡。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 06:22:29 +00:00

98 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 流程正本與規則正本的共用檢查。
*
* 這些檔案是文件不是程式,但它們是各指令實際交付的東西:正本的平台中立性決定
* 轉接檔能不能一份寫到底,段落結構決定下游解析得到什麼。靠人記不牢,用測試釘住。
*/
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');
}
/** 讀一份輸出模板(markdown) */
export function readTemplate(name) {
return readTemplateFile(`${name}.md`);
}
/** 讀 templates/ 底下任一個檔案,含副檔名 */
export function readTemplateFile(filename) {
return readFileSync(join(repoRoot, 'templates', filename), '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}`);
}
}
/**
* 斷言模板的 `## 標題` 就是這組段落,順序一致。
* 段落順序即下游抽取契約的解析依據,兩邊必須一起改。
*/
export function assertTemplateSections(template, sections) {
const headings = [...template.matchAll(/^## (.+)$/gm)].map((m) => m[1].trim());
assert.deepEqual(headings, sections);
}
/**
* 斷言正本的編號清單逐一交代了這組段落,順序與模板一致。
* @param {string} text 正本內容,或其中相關的一段
*/
export function assertPromptListsSections(text, sections) {
const listed = [...text.matchAll(/^\d+\.\s+\*\*(.+?)\*\*/gm)].map((m) => m[1].trim());
assert.deepEqual(listed, sections);
}
/**
* 斷言圖表段落只有佔位、沒有寫死的 mermaid 圍欄。
* 正本允許「乾脆不畫」,圍欄寫死在模板裡的話,不畫時會在議題頁留下一塊渲染失敗的空區塊。
* @param {string} heading 圖表段落的標題,例如 '流程圖'
* @param {string} nextHeading 其後一個段落的標題,用來框出範圍
*/
export function assertDiagramPlaceholderOnly(template, heading, nextHeading) {
assert.equal(template.includes('```mermaid'), false);
const section = template.slice(template.indexOf(`## ${heading}`), template.indexOf(`## ${nextHeading}`));
assert.match(section.trim(), new RegExp(`^## ${heading}\\s+\\{\\{${heading}\\}\\}$`));
}