feat: 移除計時並停用報表預覽

This commit is contained in:
2026-09-21 17:41:44 +08:00
parent 5a259a95af
commit 7f723adff8
34 changed files with 100 additions and 6113 deletions
-220
View File
@@ -1,220 +0,0 @@
/**
* 委派:判準正本、正本上的標記,以及兩者之間那條雙向斷言。
*
* 判準與標記分住兩個檔案,而它們講的是同一件事。沒有雙向斷言,兩邊會慢慢漂開——
* 而漂開的時候不會有任何東西報錯:正本上多標一步不會壞,判準表少列一項也不會壞,
* 只是下一個讀的人會以為自己讀到的是全部。
*/
import test from 'node:test';
import assert from 'node:assert/strict';
import {
DELEGATABLE,
PLATFORM_SPECIFIC,
promptSteps,
readPrompt,
readReference,
} from './helpers/prompt-doc.js';
const PROMPTS = ['sdlc-plan', 'sdlc-analyze', 'sdlc-feat', 'sdlc-fix', 'sdlc-sync', 'sdlc-report'];
const reference = readReference('delegation');
/** 帶標記的三份正本;另外三份目前沒有可委派的步驟 */
const 有標記的正本 = ['sdlc-plan', 'sdlc-analyze', 'sdlc-feat'];
/** `正本/步驟` 這種好讀的鍵,比對失敗時看得出差在哪一步 */
const 鍵 = ({ prompt, name }) => `${prompt}/${name}`;
/** delegation.md 那張表列出來的步驟 */
const 表上的 = () =>
[...reference.matchAll(/^\| `(sdlc-[a-z]+)` \| (.+?) \| (.+?) \|$/gm)].map((m) => ({
prompt: m[1],
name: m[2].trim(),
範圍: m[3].trim(),
}));
/** 正本上實際被標記的步驟 */
const 正本上的 = () =>
PROMPTS.flatMap((prompt) =>
promptSteps(readPrompt(prompt))
.filter((step) => step.marked)
.map((step) => ({ prompt, name: step.name, body: step.body })),
);
// ── 判準正本 ───────────────────────────────────────────────────────
test('delegation.md 的開頭形狀與既有規則正本一致', () => {
assert.equal(reference.startsWith('# '), true, '規則正本一律以 H1 起頭,不放 frontmatter');
assert.match(reference.split('\n')[0], /委派/);
});
/** 判準那一節的四條,各自含標題與理由 */
function 判準逐條() {
const 節 = reference.slice(reference.indexOf('## 判準'), reference.indexOf('## 怎麼委派'));
return 節.split(/^(?=\d+\. \*\*)/m).filter((one) => /^\d+\. \*\*/.test(one));
}
test('四條判準逐條載明,一條不多一條不少', () => {
const 判準 = reference.slice(reference.indexOf('## 判準'), reference.indexOf('## 怎麼委派'));
const 條 = 判準逐條().map((one) => /^\d+\. \*\*(.+?)\*\*/.exec(one)[1]);
assert.equal(條.length, 4, `判準應為四條,目前 ${條.length} 條:${條.join('、')}`);
assert.match(判準, /可驗證的成品/);
assert.match(判準, /不會詢問使用者/);
assert.match(判準, /失敗能被呼叫端偵測/);
assert.match(判準, /不直接寫入 Gitea 或 git/);
});
test('第二條與第四條各自標明是硬排除,並各自寫出理由', () => {
// 數「硬排除」出現幾次的話,別處多提一句就會失敗;要問的是「那兩條上面有沒有」
const [, 二, , 四] = 判準逐條();
assert.match(二, /硬排除/, '第二條是硬排除,不是建議');
assert.match(二, /子代理問不到使用者/, '沒寫理由的話,下一個人會把它當成建議而繞過去');
assert.match(四, /硬排除/, '第四條是硬排除,不是建議');
assert.match(四, /失敗沒有人看著/, '理由不是子代理做不好,要寫清楚,否則會被當成不信任');
});
// ── 雙向斷言 ───────────────────────────────────────────────────────
test('正本上被標記的集合,等於 delegation.md 列出的集合', () => {
const 表 = 表上的().map(鍵).sort();
const 正本 = 正本上的().map(鍵).sort();
assert.deepEqual(正本, 表, '改一邊就要改另一邊,否則兩份說法會漂開');
});
// 這一條與上一條刻意重複:雙向斷言只保證兩邊一致,兩邊一起改就一起漂走。
// 把議題點名的那六個逐字釘在這裡,改動才需要有人明確地改掉這份清單。
test('被標記的正好是議題點名的那六個', () => {
assert.deepEqual(正本上的().map(鍵).sort(), [
'sdlc-analyze/算出截止日',
'sdlc-analyze/對四份清單列出疑點',
'sdlc-analyze/產生分析版的圖解總覽',
'sdlc-feat/把議題標題翻成英文',
'sdlc-feat/分批提交',
'sdlc-plan/產生圖解版總覽',
].sort());
});
// ── 判準第二條的迴歸保護 ───────────────────────────────────────────
/** 會問使用者的步驟。子代理問不到人,這些永遠不該被標上可委派。 */
const 會問使用者 = [
['sdlc-plan', '逐項詢問'],
['sdlc-analyze', '逐題問到共識'],
['sdlc-feat', '問來源分支'],
['sdlc-feat', '認出語言,讀規則正本'],
];
test('點名的問到共識類步驟一律未被標記', () => {
for (const [prompt, name] of 會問使用者) {
const step = promptSteps(readPrompt(prompt)).find((one) => one.name === name);
assert.ok(step, `${prompt} 少了「${name}」這一步;步驟改名的話這份清單要跟著改`);
assert.equal(step.marked, false, `${prompt}/${name} 會問使用者,判準第二條硬排除`);
}
});
test('任何看得出在問使用者的步驟都沒有被標記', () => {
// 只認明確的提問語,不認「不要拿去問使用者」那種否定句——那句正好出現在可委派的步驟裡
const 提問語 = /一次問一題|停下來問|等使用者回答|問到共識|問過使用者/;
let 掃過 = 0;
let 認出 = 0;
for (const prompt of PROMPTS) {
for (const step of promptSteps(readPrompt(prompt))) {
掃過 += 1;
if (!提問語.test(step.body)) continue;
認出 += 1;
assert.equal(step.marked, false, `${prompt}/${step.name} 在問使用者,不該標可委派`);
}
}
// 兩道自我檢查:這種掃描最常見的壞法是「一條都沒掃到」,而那時它照樣是綠的。
// 目前有編號步驟的是 plan/analyze/feat/report 四份,sdlc-fix 與 sdlc-sync 沒有編號步驟
assert.ok(掃過 >= 40, `只掃到 ${掃過} 個步驟,正本的步驟標題格式可能變了`);
assert.ok(認出 >= 4, `提問語一個步驟都沒認出來(${認出}),這道保護已經形同虛設`);
});
// ── 判準第四條:不直接寫入 ─────────────────────────────────────────
/** 會寫入 Gitea 或 git 的腳本。被標記的步驟碰到它們,就要寫明哪一半不委派。 */
const 寫入型 = [
'issue-create',
'issue-update',
'issue-link',
'project-add',
'pr-create',
'claim.js',
'branch-prep',
'worktree-ensure',
'worktree-remove',
'timer.js',
'time-log.js',
'commit-split',
];
test('被標記的步驟若碰得到寫入,就要寫明哪一半不委派', () => {
for (const step of 正本上的()) {
const 碰到 = 寫入型.filter((script) => step.body.includes(script));
if (碰到.length === 0) continue;
assert.match(
step.body,
/不委派/,
`${鍵(step)} 用到 ${碰到.join('、')},要寫明那一半留給主流程`,
);
}
});
test('三步部分委派的範圍,判準表上也說得出來', () => {
const 部分 = 表上的().filter((one) => one.範圍 !== '全步');
assert.equal(部分.length, 3, '兩份圖解總覽與分批提交是部分委派');
for (const one of 部分) {
assert.match(one.範圍, /不委派/, `${鍵(one)} 的範圍要說出哪一半不委派`);
}
});
test('另外三份正本一個標記都沒有,與判準正本結尾那句話一致', () => {
for (const prompt of PROMPTS.filter((one) => !有標記的正本.includes(one))) {
assert.equal(
readPrompt(prompt).includes(DELEGATABLE),
false,
`${prompt} 出現了標記,但 delegation.md 結尾說它沒有可委派的步驟`,
);
}
assert.match(reference, /`sdlc-sync`、`sdlc-fix` 與 `sdlc-report` 目前沒有可委派的步驟/);
});
// ── 平台中立 ───────────────────────────────────────────────────────
test('判準正本與標記都不指名任何平台的工具', () => {
for (const token of PLATFORM_SPECIFIC) {
assert.equal(reference.includes(token), false, `delegation.md 不該出現平台專屬字樣:${token}`);
}
// 子代理是平台專屬能力,正本只能以能力描述帶過
assert.match(reference, /能力描述/);
assert.match(reference, /不能就自己做/);
});
test('三份帶標記的正本各自說明了這個後綴是什麼意思,並指名判準正本', () => {
for (const prompt of 有標記的正本) {
const text = readPrompt(prompt);
assert.match(text, new RegExp(`## ${DELEGATABLE}的意思`), `${prompt} 要解釋這個後綴`);
assert.match(text, /references\/delegation\.md/, `${prompt} 要指名判準正本,不要把判準抄過去`);
assert.match(text, /不能就自己做/, `${prompt} 要寫成能力描述,讓不支援的平台自然降級`);
}
});
test('標記是標題後綴,不用 emoji 也不用 HTML 註解', () => {
for (const prompt of PROMPTS) {
const text = readPrompt(prompt);
assert.equal(text.includes('<!--'), false, `${prompt}:HTML 註解模型讀不穩,不拿它當標記`);
// 標記只出現在標題後綴與那一節的說明裡,不會單獨浮在內文中間
for (const line of text.split('\n')) {
if (!line.includes(DELEGATABLE)) continue;
// 三種合法位置,寫死成互斥的三條。曾經第二條寫成 /^#{2,3} /,把第一條整個
// 吃掉了——那時候「### 工作包議題 〔可委派〕」這種沒編號的標題也會通過
const 合法 =
new RegExp(`^### \\d+\\. .+ ${DELEGATABLE}$`).test(line) ||
line === `## ${DELEGATABLE}的意思` ||
line.includes(`\`${DELEGATABLE}\``);
assert.ok(合法, `${prompt}:標記出現在不該出現的位置:${line}`);
}
}
});