From 3da822adc65d4d68afedf64d906ca8f2600a3e96 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 18 Sep 2026 09:12:08 +0800 Subject: [PATCH 1/3] =?UTF-8?q?docs(references):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=A7=94=E6=B4=BE=E5=88=A4=E6=BA=96=EF=BC=8C=E5=9B=9B=E6=A2=9D?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E6=88=90=E7=AB=8B=E6=89=8D=E5=8F=AF=E5=A7=94?= =?UTF-8?q?=E6=B4=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 只在意結果的步驟——翻譯分支名、算截止日、逐條比對可行性清單、產生 HTML 總覽—— 它們的中間產物目前全部留在主脈絡裡,把後面真正需要判斷力的步驟愈擠愈窄。要把這些 交出去,得先說清楚「哪些交得出去」,否則下一個人只能憑感覺標。 四條判準裡有兩條是硬排除,各自寫上理由,因為沒有理由的規則遲早會被繞過去: 第二條(步驟中不會詢問使用者)是因為子代理問不到使用者,一旦卡在提問就只能自行決定; 第四條(不直接寫入 Gitea 或 git)不是因為子代理做不好,而是**它的失敗沒有人看著** ——備妥工作樹失敗會中止整個領取、實際提交失敗會留下半套 git 歷史。 委派一律以**能力描述**表達,不指名任何平台的工具:子代理是平台專屬能力,而流程正本 必須保持平台中立(#4 的驗收標準)。能力描述對不支援的平台是自然降級,同一份正本兩邊 都讀得通,不必維護兩份。這份正本明寫「不要把它改寫成工具名」,因為那是讀到它的人 最可能動手改的地方。 那張「目前標記為〔可委派〕的步驟」表與正本上的標記互為正本,由資產測試雙向綁住。 議題 #58 Co-Authored-By: Claude Opus 5 (1M context) --- references/delegation.md | 57 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 references/delegation.md diff --git a/references/delegation.md b/references/delegation.md new file mode 100644 index 0000000..ec9fca3 --- /dev/null +++ b/references/delegation.md @@ -0,0 +1,57 @@ +# 委派判準 + +流程正本裡標著〔可委派〕的步驟,是照這份判準挑出來的。**四條全部成立才可委派**; +任何一條不成立就不標,那一步一律自己做。 + +這份是規則正本,流程正本指名讀它,不把規則抄過去——抄過去就會有兩份各自演化的判準。 + +## 判準 + +1. **產出是可驗證的成品** — 交回來的東西呼叫端看得出對不對:一個英文 kebab 字串、 + 一份日期表、一份疑點清單、一份 HTML。說不出「拿回來的該長什麼樣」的步驟不可委派, + 因為呼叫端沒有辦法判斷它做完了沒有。 +2. **步驟中不會詢問使用者** — **硬排除**。子代理問不到使用者,一旦卡在提問就只能自行 + 決定,而它決定的那件事使用者從頭到尾不會知道。逐題問到共識、問來源分支、認不出語言 + 就停下來問——這類步驟一律不標,無論它們看起來多像例行公事。 +3. **失敗能被呼叫端偵測** — 交不出東西、交回來的形狀不對,主流程當場看得出來並接手。 + 失敗只會表現成「結果怪怪的」而不會表現成「失敗」的步驟不可委派。 +4. **只產出草稿或唯讀結果,不直接寫入 Gitea 或 git** — **硬排除**。理由不是子代理做不好, + 而是**它的失敗沒有人看著**:備妥工作樹失敗會中止整個領取,實際提交失敗會留下半套 + git 歷史,這兩種都需要當場有人判斷下一步。 + +## 怎麼委派 + +標記寫成標題後綴 `〔可委派〕`,並以**能力描述**說明怎麼做,不指名任何平台的工具: + +> 這一步只在意結果;你的環境若能把工作交給子代理,就交出去,只把結果帶回來;不能就自己做。 + +子代理是平台專屬能力,而流程正本必須保持平台中立。能力描述對不支援的平台是自然降級, +同一份正本兩邊都讀得通,不需要維護兩份。**不要把它改寫成工具名**——那會讓正本綁死在 +某一個助理上。 + +委派與否不改變產出:兩條路的結果必須一樣,差別只在中間產物留不留在主脈絡裡。 + +## 部分委派 + +一個步驟裡只有一半合判準時,**標記照下,並在該步寫明哪一半不委派**。這比整步不標好—— +不標的話那一半的中間產物照樣塞滿主脈絡;也比整步委派安全,因為第四條是硬排除。 + +目前有三步是這個形狀:兩份圖解總覽(產出 HTML 可委派,寫回議題的 `issue-update` 不委派) +與分批提交(方案計算可委派,實際跑 `commit-split.js` 不委派)。 + +## 目前標記為〔可委派〕的步驟 + +這張表與正本上的標記互為正本,**兩邊由資產測試雙向綁住**。改一邊就要改另一邊, +否則測試會擋下來——沒有這條斷言,兩邊會漂開,而漂開時不會有任何東西報錯。 + +| 正本 | 步驟 | 委派範圍 | +| --- | --- | --- | +| `sdlc-plan` | 產生圖解版總覽 | 產出 HTML;寫回議題不委派 | +| `sdlc-analyze` | 對四份清單列出疑點 | 全步 | +| `sdlc-analyze` | 算出截止日 | 全步 | +| `sdlc-analyze` | 產生分析版的圖解總覽 | 產出 HTML;寫回議題不委派 | +| `sdlc-feat` | 把議題標題翻成英文 | 全步 | +| `sdlc-feat` | 分批提交 | 方案計算;實際提交不委派 | + +`sdlc-sync`、`sdlc-fix` 與 `sdlc-report` 目前沒有可委派的步驟:前兩者每一步都在問使用者 +或寫入 Gitea,後者只有一支唯讀腳本,委派出去省不到什麼。 From 84cd8fc41f625a965e1036012038f7ec2359e943 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 18 Sep 2026 09:12:08 +0800 Subject: [PATCH 2/3] =?UTF-8?q?docs(prompts):=20=E5=85=AD=E5=80=8B?= =?UTF-8?q?=E5=8F=AA=E5=9C=A8=E6=84=8F=E7=B5=90=E6=9E=9C=E7=9A=84=E6=AD=A5?= =?UTF-8?q?=E9=A9=9F=E6=A8=99=E4=B8=8A=E3=80=94=E5=8F=AF=E5=A7=94=E6=B4=BE?= =?UTF-8?q?=E3=80=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三份正本各加一節說明這個後綴是什麼意思,只留能力描述那一句與指回判準正本的指路, 判準本身不複寫——抄過去就會有兩份各自演化的規則(AGENTS.md 的 references/ 那一列)。 標的是六個:sdlc-plan 的產生圖解版總覽;sdlc-analyze 的對四份清單列出疑點、算出截止日、 產生分析版的圖解總覽;sdlc-feat 的把議題標題翻成英文、分批提交。 其中三步是部分委派,各自在該步寫明哪一半留給主流程:兩份圖解總覽委派的是產出 HTML, 寫回議題的 issue-update 不委派;分批提交委派的是方案計算,實際跑 commit-split.js 不委派。 理由不在這裡複述,指回判準第四條。 **「認出語言,讀規則正本」不標**,儘管議題的列舉點了它。那一步的核心動作含「認不出語言 就停下來問、不要猜」,正是判準第二條硬排除的事;排掉它之後剛好是議題所寫的六個。 這一條與 repo 擁有者確認過。 AGENTS.md 的 references/ 那一列補上「委派判準」,否則那串括號裡的列舉會漏掉新的一份。 議題 #58 Co-Authored-By: Claude Opus 5 (1M context) --- AGENTS.md | 2 +- prompts/sdlc-analyze.md | 15 ++++++++++++--- prompts/sdlc-feat.md | 16 +++++++++++++--- prompts/sdlc-plan.md | 11 ++++++++++- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 835e386..79f60fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,7 @@ | `prompts/` | 流程正本(`sdlc-{plan,analyze,feat,fix,sync,report}.md`),唯一的事實來源 | 平台中立 markdown,不含任何平台專屬語法 | | `scripts/` | 所有副作用(Gitea API、git、檔案系統)的唯一出口 | Node、零外部套件,僅用內建 `fetch` / `child_process` / `fs` | | `templates/` | 所有產出格式(議題、PR、報表、總覽網頁) | 以 `{{變數}}` 佔位,不含邏輯。唯一例外是 `overview-artifact.html`:它是一份要在瀏覽器裡開的網頁,需要一段把 mermaid 圖畫出來的腳本 | -| `references/` | 規則正本(實作規範、註解格式對照表、可行性檢查清單) | 由流程正本指名讀取,不自行散落於 prompts | +| `references/` | 規則正本(實作規範、註解格式對照表、可行性檢查清單、委派判準) | 由流程正本指名讀取,不自行散落於 prompts | | `bin/tea-sdlc.js` | 指令入口:取走子指令,其餘 argv 原樣交出去 | 不含任何平台目錄知識,也不自己動手做事 | | `scripts/install.js` | 平台偵測與轉接檔產生/移除 | 唯一知道各平台目錄結構的地方 | | `scripts/install-verify.js` | 安裝後走一遍叫用鏈(轉接檔 → PATH 上的 tea-sdlc → 流程正本) | 只認拿到的轉接檔路徑,不自己推導平台目錄;不碰網路 | diff --git a/prompts/sdlc-analyze.md b/prompts/sdlc-analyze.md index 15e2e8a..771d015 100644 --- a/prompts/sdlc-analyze.md +++ b/prompts/sdlc-analyze.md @@ -26,6 +26,13 @@ Milestone、看板與人天估算補上。 錶已經跑在同一顆議題上時起錶什麼都不做,所以 plan 接著跑 analyze 不會把累積的時間 切成兩段。**錶不跨階段跑**,也**不碰別顆議題上的錶**。 +## 〔可委派〕的意思 + +標題後綴 `〔可委派〕` 的步驟只在意結果:**你的環境若能把工作交給子代理,就交出去, +只把結果帶回來;不能就自己做。** 沒有這個後綴的步驟一律自己做。 + +怎麼挑、為什麼這樣挑,見 `references/delegation.md`——判準只有那一份,這裡不複述。 + ## 第一段:可行性分析 ### 1. 讀議題 @@ -59,7 +66,7 @@ node scripts/timer.js --repo --index <需求議題編號> --dry-run 請他自己去停**,不要代勞:那一段時間該記在哪顆議題上只有他知道。順帶說明停錶不會動到 任何既有的工作樹——碼錶只管時間、工作樹只管檔案。 -### 3. 對四份清單列出疑點 +### 3. 對四份清單列出疑點 〔可委派〕 依序讀這四份規則正本,逐條對照議題內容: @@ -165,7 +172,7 @@ no-op」。確認無誤後拿掉該旗標再跑一次。 工作包建好之後,把它們之間的關係與時程補上。做完這一段,看板上呈現的才是真實的 開發順序,而不是一堆平鋪的議題。 -### 10. 算出截止日 +### 10. 算出截止日 〔可委派〕 把每顆工作包的編號、人天估算與先決關係寫成一份計畫檔: @@ -207,7 +214,7 @@ node scripts/project-add.js --repo --index <編號> --project "<看 看板名稱靠掃最近 50 筆議題反查 id,反查不到就會請你直接貼專案網址(結尾即 id)。 本流程不建立 Milestone,也不建立專案。 -### 12. 產生分析版的圖解總覽 +### 12. 產生分析版的圖解總覽 〔可委派〕 用同一份 `templates/overview-artifact.html` 再產一份,但這一份要多出**工作包全景**: 把工作包之間的相依與截止日畫成一張圖,讓開發者看得出自己這一項在整體中的位置。 @@ -224,6 +231,8 @@ node scripts/project-add.js --repo --index <編號> --project "<看 節點寫工作包標題與截止日,箭頭方向是「先決 → 後續」。節點一樣以 12 個為上限, 超過就只畫相依鏈最長路徑上的那幾顆,其餘在頁尾列成文字。 +委派的是**產出那份 HTML**;拿到網址之後寫回議題那一步**不委派**(判準第四條)。 + 網址一樣寫回需求議題: ``` diff --git a/prompts/sdlc-feat.md b/prompts/sdlc-feat.md index 8b4f9d6..57865dd 100644 --- a/prompts/sdlc-feat.md +++ b/prompts/sdlc-feat.md @@ -19,6 +19,13 @@ description: 僅由 /sdlc-feat 指令叫用。領取一顆工作包、備妥工 一個工作包議題編號。使用者直接給的,或 `/sdlc-fix` 收到議題後交棒過來的—— 兩者一樣處理,**不要因為是交棒來的就要求他再打一次指令**。 +## 〔可委派〕的意思 + +標題後綴 `〔可委派〕` 的步驟只在意結果:**你的環境若能把工作交給子代理,就交出去, +只把結果帶回來;不能就自己做。** 沒有這個後綴的步驟一律自己做。 + +怎麼挑、為什麼這樣挑,見 `references/delegation.md`——判準只有那一份,這裡不複述。 + ## 第一段:領取與開工準備 ### 1. 讀工作包 @@ -83,7 +90,7 @@ node scripts/claim.js --repo --index <編號> --dry-run 不論哪一種,來源分支都必須**已經在遠端上**:工作樹的起點一律取自 `origin/{來源分支}`。 -### 4. 把議題標題翻成英文 +### 4. 把議題標題翻成英文 〔可委派〕 分支名的中段要用英文,中文會讓 CI 與 URL 出問題。把工作包議題的標題翻成 **小寫英文 kebab、40 字元以內**,例如「建立工作包的抽取契約」→ `wp-extract-contract`。 @@ -231,9 +238,12 @@ reviewer 得從一堆「已完成第 N 項」裡找真正的討論。 ## 第三段:提交與開立 PR -### 13. 分批提交 +### 13. 分批提交 〔可委派〕 -全部待辦都勾完之後才進這一段。變更依類型分批: +全部待辦都勾完之後才進這一段。變更依類型分批。 + +委派的是**方案計算**:變更分成哪幾批、每一批收哪些檔案、各自的 `--type` 與描述怎麼寫。 +**實際提交不委派**——底下那支 `commit-split.js` 由主流程執行(判準第四條)。 ``` node scripts/commit-split.js --path <工作樹路徑> --type feat \ diff --git a/prompts/sdlc-plan.md b/prompts/sdlc-plan.md index fc84b5c..bf8d0b3 100644 --- a/prompts/sdlc-plan.md +++ b/prompts/sdlc-plan.md @@ -30,6 +30,13 @@ description: 僅由 /sdlc-plan 指令叫用。把一段口語需求轉成結構 起與停都寫在這一份裡,**錶不跨階段跑**:跑完就去開會而錶跑一整天,報表當場失真。 反過來,別顆議題上的錶一律不碰——那一段時間該記在哪顆議題上只有使用者知道。 +## 〔可委派〕的意思 + +標題後綴 `〔可委派〕` 的步驟只在意結果:**你的環境若能把工作交給子代理,就交出去, +只把結果帶回來;不能就自己做。** 沒有這個後綴的步驟一律自己做。 + +怎麼挑、為什麼這樣挑,見 `references/delegation.md`——判準只有那一份,這裡不複述。 + ## 步驟 ### 1. 記下開始時間 @@ -125,7 +132,7 @@ node scripts/timer.js --repo --index <編號> --dry-run 是哪一顆,請他自己去停**,不要代勞:那一段時間該記在哪顆議題上只有他知道。順帶說明 停錶不會動到任何既有的工作樹——碼錶只管時間、工作樹只管檔案。 -### 8. 產生圖解版總覽 +### 8. 產生圖解版總覽 〔可委派〕 套用 `templates/overview-artifact.html`,把議題的總覽、目標與流程圖填成一份可以直接投影的 網頁。這一份是給**非技術的利害關係人**看的:他們不必讀完技術細節就知道這件事在做什麼。 @@ -142,6 +149,8 @@ node scripts/timer.js --repo --index <編號> --dry-run 若執行環境能把 HTML 發佈成可分享的網址,就發佈;不能的話存成檔案,把路徑當成網址用。 +委派的是**產出那份 HTML**;拿到網址之後寫回議題那一步**不委派**(判準第四條)。 + 拿到網址後寫回議題: ``` From f61ef3fa78ebbaecf73ef0e93302ae459c7fc3aa Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 18 Sep 2026 09:12:08 +0800 Subject: [PATCH 3/3] =?UTF-8?q?test(delegation):=20=E6=8A=8A=E6=A8=99?= =?UTF-8?q?=E8=A8=98=E8=88=87=E5=88=A4=E6=BA=96=E6=AD=A3=E6=9C=AC=E9=9B=99?= =?UTF-8?q?=E5=90=91=E7=B6=81=E4=BD=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 沒有這條斷言,兩邊會慢慢漂開,而漂開時不會有任何東西報錯:正本上多標一步不會壞, 判準表少列一項也不會壞,只是下一個讀的人會以為自己讀到的是全部。 十三條,重點在三處:正本上被標記的集合等於判準表列出的集合;會問使用者的步驟一律 未被標記(判準第二條的迴歸保護,四個點名的步驟加上一次全域掃描);被標記的步驟碰得到 寫入時要寫明哪一半不委派(第四條)。 掃描類的斷言都補上自我檢查,因為這種測試最常見的壞法是「一條都沒掃到」而它照樣是綠的: 提問語至少要認出四個步驟、至少要掃過四十個步驟。標記位置那一條原本把三種合法位置寫成 `/^#{2,3} /`,那條會把「### 沒編號的標題 〔可委派〕」也放過去,改成三條互斥的規則。 helpers 新增 promptSteps(名字、有沒有標記、內文),promptStep 改建在它上面, 順帶讓步驟標題容得下後綴。 議題 #58 Co-Authored-By: Claude Opus 5 (1M context) --- test/delegation-assets.test.js | 220 +++++++++++++++++++++++++++++++++ test/helpers/prompt-doc.js | 45 +++++-- 2 files changed, 258 insertions(+), 7 deletions(-) create mode 100644 test/delegation-assets.test.js diff --git a/test/delegation-assets.test.js b/test/delegation-assets.test.js new file mode 100644 index 0000000..8262677 --- /dev/null +++ b/test/delegation-assets.test.js @@ -0,0 +1,220 @@ +/** + * 委派:判準正本、正本上的標記,以及兩者之間那條雙向斷言。 + * + * 判準與標記分住兩個檔案,而它們講的是同一件事。沒有雙向斷言,兩邊會慢慢漂開—— + * 而漂開的時候不會有任何東西報錯:正本上多標一步不會壞,判準表少列一項也不會壞, + * 只是下一個讀的人會以為自己讀到的是全部。 + */ +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('