test(delegation): 把標記與判準正本雙向綁住
沒有這條斷言,兩邊會慢慢漂開,而漂開時不會有任何東西報錯:正本上多標一步不會壞,
判準表少列一項也不會壞,只是下一個讀的人會以為自己讀到的是全部。
十三條,重點在三處:正本上被標記的集合等於判準表列出的集合;會問使用者的步驟一律
未被標記(判準第二條的迴歸保護,四個點名的步驟加上一次全域掃描);被標記的步驟碰得到
寫入時要寫明哪一半不委派(第四條)。
掃描類的斷言都補上自我檢查,因為這種測試最常見的壞法是「一條都沒掃到」而它照樣是綠的:
提問語至少要認出四個步驟、至少要掃過四十個步驟。標記位置那一條原本把三種合法位置寫成
`/^#{2,3} /`,那條會把「### 沒編號的標題 〔可委派〕」也放過去,改成三條互斥的規則。
helpers 新增 promptSteps(名字、有沒有標記、內文),promptStep 改建在它上面,
順帶讓步驟標題容得下後綴。
議題 #58
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,20 +66,51 @@ export function assertNeutralPrompt(prompt, command) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 可委派的標記。寫成標題後綴,不是 emoji 也不是 HTML 註解——那兩種模型讀不穩。 */
|
||||
export const DELEGATABLE = '〔可委派〕';
|
||||
|
||||
/**
|
||||
* 正本裡的每一個編號步驟:名字、有沒有被標成可委派、以及它的內文。
|
||||
*
|
||||
* 步驟的界線是下一個 `##` 或 `###` 標題;段落標題(`## 第二段…`)不算步驟,
|
||||
* 但會把前一步收尾,否則一段的最後一步會把整個段落的收場白都吃進來。
|
||||
* @param {string} prompt 正本內容
|
||||
* @returns {{name: string, marked: boolean, body: string}[]}
|
||||
*/
|
||||
export function promptSteps(prompt) {
|
||||
const lines = prompt.split('\n');
|
||||
// 先收齊所有標題的行號,每一步的結尾就是它後面最近的那一個
|
||||
const 標題行 = [];
|
||||
lines.forEach((line, at) => {
|
||||
if (/^#{2,3} /.test(line)) 標題行.push(at);
|
||||
});
|
||||
|
||||
return 標題行
|
||||
.map((at, i) => ({ at, 到: 標題行[i + 1] ?? lines.length }))
|
||||
.filter(({ at }) => /^### \d+\. /.test(lines[at]))
|
||||
.map(({ at, 到 }) => {
|
||||
const raw = /^### \d+\. (.+)$/.exec(lines[at])[1].trim();
|
||||
const marked = raw.endsWith(DELEGATABLE);
|
||||
return {
|
||||
name: (marked ? raw.slice(0, -DELEGATABLE.length) : raw).trim(),
|
||||
marked,
|
||||
body: lines.slice(at, 到).join('\n'),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出正本裡某一個編號步驟的內容,**以名字取而不是以編號取**。
|
||||
* 步驟會增刪、編號會整批位移,名字不會;用編號寫的測試會在別人插一步時無聲地
|
||||
* 框到另一段內容上,而那種失敗看起來像是正本掉了東西。
|
||||
* @param {string} prompt 正本內容
|
||||
* @param {string} name 步驟名,例如 '產生圖解版總覽'
|
||||
* @returns {string} 該步驟的標題與內文,到下一個 `### ` 為止
|
||||
* @param {string} name 步驟名,不含可委派後綴
|
||||
* @returns {string} 該步驟的標題與內文,到下一個 `##` 或 `###` 標題為止
|
||||
*/
|
||||
export function promptStep(prompt, name) {
|
||||
const start = prompt.search(new RegExp(`^### \\d+\\. ${name}$`, 'm'));
|
||||
assert.ok(start >= 0, `正本裡找不到「${name}」這一步`);
|
||||
const rest = prompt.slice(start);
|
||||
const end = rest.slice(1).search(/^### /m);
|
||||
return end === -1 ? rest : rest.slice(0, end + 1);
|
||||
const step = promptSteps(prompt).find((one) => one.name === name);
|
||||
assert.ok(step, `正本裡找不到「${name}」這一步`);
|
||||
return step.body;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user