Tech Lead's Club - Spec, Lean
凍結義務。解放計畫。由非建置者證明。衍生自 tlc-spec-driven 3.3.0(Felipe Rodrigues)、tlc-plan 與 tlc-implement。
┌──────┐ ┌────────┐ ┌───────┐ ┌────────┐
│ 規劃 │ → │ 檢查 │ → │ 建置 │ → │ 驗證 │
└──────┘ └────────┘ └───────┘ └────────┘
閱讀它 義務清單 你的工作 總是
四個步驟,先有兩個產出物,然後建置程式碼,最後才驗證。一份文件中,由人類確認什麼必須為真以及如何建置,之後才將其轉化為附有證明的義務。沒有任務分解,計畫也不包含元件目錄:難以逆轉的決定會以單向門的形式呈現其具體樣貌,而所有可逆轉的決定則在開發過程中決定,並在 diff 中進行審查。
為何如此設計
程式碼代理主要的失敗原因並非推理能力不佳,而是需求被讀取後,從未成為有效的義務,隨後又宣稱完成。有效的緩解措施是一組小型、凍結、外部的義務清單,加上非建置者的驗證者;自行檢查只會重現建置者自身的盲點。因此,此技能將預算投入在這兩件事上,拒絕將其花費在編排模型如何工作上。
有兩個後果值得先說明,因為它們正是此流程與傳統規格驅動流程的不同之處:
- 細粒度不等於品質。 將一個功能拆成十五個單一檔案的任務,買到的是順序而非正確性,而且每次任務都要重新閱讀流程。證明覆蓋率才能買到正確性。
- 模型必須遵守的計畫會與義務爭搶注意力。 像
Where、Tools、Depends on這些欄位是模型的工作,因此不需要寫下來。
關鍵規則
固定規則集。即使未讀取任何參考文件,這些規則仍然有效,而且它們是唯一不會隨著設定檔縮減的規則。
- 每個檢查都是一個帶有具體值的可觀察斷言,加上其證明——也就是透過退出代碼來判定結果的測試或指令。沒有證明,就沒有檢查。
- 測試應斷言檢查所說的內容,絕不應該斷言程式碼剛好做了什麼。絕不透過閱讀實作來撰寫測試。
- 絕不為了讓測試套件通過而削弱斷言、刪除測試或跳過測試。真正錯誤的檢查應該停下來詢問,而不是直接編輯。
- 檢查和
測試政策的行一旦被批准,就固定不變。在設計中,Landing、Relations和Surface是可新增的——在開發過程中發現的門會在關閉它的程式碼之前新增一行,而使用者批准過的行絕不重寫。Flow和Impact則兩者都不是:它們是保持為真,因此不同的路徑只會改變該路徑提交中的跳躍點。 - 驗證者是一個全新的子代理,絕非建置者本身,也絕非選項,更不需等待被要求。負責整個功能的人在該功能的最後一次提交落地後,立即派遣驗證者——針對
<feature base>..HEAD範圍,並包含每個檢查。絕不由建置者派遣,也絕不作為建置者的子任務。建置者完成工作、回報、然後停止。工作不是在最後一次提交時完成;而是當驗證者的報告說明每一個檢查時才完成。 - 設定檔是一個基準,而且不是秘密。 驗證報告必須指明所使用的設定檔,否則「未注入故障」這樣的陳述讀起來就像忘記注入它們。
- 完成的關卡是一個指令碼,而不是感覺:
validate_verification.py必須以退出碼 0 結束。 - 影響範圍: 已批准的規格授權本機編輯和本機提交。
git push、部署和生產資料變更需要針對該動作的明確許可。
設定檔
專案在 AGENTS.md 或等效文件中宣告要執行的強度。如果沒有宣告,則預設為:light。與 tlc-implement 相同的三個層級,以及相同的門檻,因此在兩種技能之間切換不需學習第二套術語。
## tlc-spec-lean
profile: light
budget: 150k
| 設定檔 | 新增功能 | 無法捕捉的錯誤 |
|---|---|---|
light(預設) | 證明在 HEAD 執行,顯示每個具名測試都存在且已執行,每個檢查有一個定位的斷言,有層級與取樣間隙,重新讀取 Swept existing | 一個沒有證明的集合成員;一個在錯誤的實作下也會通過的測試 |
standard | 重新計算 Coverage 聯集,Test policy 行各有判定,每個斷言表面一個故障 | 一個與約束來源矛盾的檢查;一個沒人建置的畫面 |
ui | 開啟並比較約束來源,逐畫面列舉文案和佈局 | 僅限間距、顏色與字重,逐畫面列舉 |
每個步驟都會增加一類可偵測的失敗,因此便宜的設定檔並不代表相同產品打折——請先閱讀右側欄位再做選擇。關於 light 有兩件事值得大聲說出來,因為它自己的那一行已經說明,但很容易被忽略:它不會注意到一個已列舉但無人證明的集合成員,也不會注意到一個在錯誤實作下也會通過的測試。standard 正是為了解決這兩個問題而存在。
ui 在不涉及介面的工作上不會增加成本:每個畫面步驟都取決於畫面是否存在。輸入為空的步驟只會增加一行,而不是一次通過(例如「無設定行」、「無約束來源」)。
在所有設定檔中,Coverage 聯集都會寫入 checks.md——這個聯集使得遺漏具有結構性,而且在撰寫時不會增加成本。standard 買到的是驗證者從每個集合的權威來源重新計算聯集,而不是讀取作者自己的表格。
設定檔是一個固定值,而不是偏好,而且與 tlc-implement 不同,這是強制執行而非請求:validate_verification.py 會拒絕報告設定檔與 checks.md 核准時不同的驗證報告,並且拒絕缺少故障行或未重新計算覆蓋率的 standard 報告,以及缺少約束來源章節的 ui 報告。因此,未執行的步驟與被遺忘的步驟仍然可以區分,這正是宣告基準的全部原因。
如果設定檔對於目前的功能來說似乎太薄弱,請用一句話說明,並讓使用者自行提出。默默執行超出設定檔範圍的工作,會損害宣告設定檔所帶來的可預測性。
產出物
.specs/
├── STATE.md # 決策記錄(AD-NNN)+ 交接快照
├── LESSONS.md # 由 scripts/lessons.py 產生——切勿手動編輯
├── lessons.json # 機器管理
└── features/<feature>/
├── plan.md # 問題、流程、影響,然後其餘樣貌,然後是準則;最後是稽核
├── checks.md # 斷言 + 證明、覆蓋率聯集、測試政策、已掃描事項
└── verification.md # 驗證者的報告
當每個階段產出內容時,才建立對應檔案。對於大約三個檔案以下且沒有單向門的變更,只需撰寫包含 ## Intent 段落的 checks.md,並跳過 plan.md——這是一個有限的例外,而非需要一個複雜的尺寸矩陣。
理解與義務是分開的產出物
plan.md 的存在是因為人類必須能夠在任何事物變成帶有測試選擇器的斷言之前進行規劃和提出異議。閱讀四十個檢查來重建正在建置的內容並非規劃,而在決定樣貌的同一趟流程中撰寫檢查,會產生那些只是確認已預設假設的檢查。
兩個部分都放在一個檔案中,因為它們是一次審查。 檔案邊界是語意上的邊界:這邊是人類確認的內容;另一邊是附有證明的義務。將計畫拆分成規格和設計,會在一個兩邊都不符合的地方切割,而且會為一個功能增加兩個強制停止點。檔案的順序是為了閱讀——問題、流程、影響,然後是其餘樣貌,然後是準則,然後是稽核表。撰寫順序則不同:先寫問題,走過表面,撰寫準則,然後填寫樣貌。這樣才能避免為了證明某個元件的存在而憑空發明準則。關閉閘門仍然要求每個準則必須落在 Flow、Relations 或 Surface 中的某處,否則就是超出範圍或樣貌中的缺口。
衍生出 checks.md 是承重部分,而不是文書工作。Surface 中的每個路由都對應一個 Coverage 集合行,其成員是其狀態;Landing 中的每個門都對應一個檢查;Relations 中的每個實體也都對應一個檢查。checks.md 中沒有對應指向的樣貌章節,要不是無效的,就是未經證明的,而 validate_checks.py 會對常見情況發出警告。
設計部分存在;但元件目錄不存在。 設計文件之所以會腐爛,從來不是因為圖表,而是因為每個類別的「目的 / 位置 / 介面 / 依賴」——這些可逆轉的細節在幾週內就會過時,然後以書面文件的權威性誤導下一位讀者。這些欄位在這裡都不存在。有五個有限的章節:
| 章節 | 審查內容 | 排除內容 |
|---|---|---|
Flow | 路徑,每個跳躍點一行 | 任何既不存在也不是由門建立的模組——那是部署位置 |
Impact | 底層變化的內容:術語和既有資料 | 風險登記簿 |
Relations | 實體、基數、單向約束 | 資料行和型別 |
Surface | 路由、輸入、輸出、狀態 | 請求主體規範,以及檢查 ID——那些尚未存在 |
Landing | 單向門,包含具體樣貌和被拒絕的替代方案 | 任何重構可以逆轉的內容 |
哪個資料夾、多少個類別、私有方法叫什麼:看 diff。 這些都是可逆轉的,由專案的慣例決定,而且永遠不值得寫成一個產出物。這是刻意的取捨,而且是唯一的取捨。
如果你走到這裡還有一個懸而未決的賭注——兩種架構都有可行的替代方案,而且每種都需要針對這個儲存庫進行成本評估——這並不適合放在 Landing 的行中,而行是這個產出物唯有的形式。無論專案使用何種方式來解決這個問題(ADR、RFC、spike),都應該先進行;然後計畫記錄最終勝出的樣貌,使其可審查。
流程
規劃 - 首先定義問題,然後是路徑,接著是變更會影響的內容,然後是其餘樣貌,最後是準則。撰寫順序仍然是問題 → 表面 → 準則 → 樣貌。事實靠查詢;決策靠詢問——當詢問時,提供具體選項與你的建議,每次最多兩個。兩個枚舉幫忙找出問題,因為「考慮邊際情況」找不到任何東西:此功能暴露的表面,每個都附帶每次出現時相同的一系列決策,以及九個隱含需求的維度。兩者都必須使用 n/a - <reason>,所以空白代表無人決定,而非不適用。一個產出物,人類先閱讀並提出異議,然後才產生任何檢查。完整流程、如何詢問、每個樣貌章節的規則、範本和關閉閘門:plan.md。
檢查 - 從計畫中衍生出附有證明的斷言,將每個枚舉集合成員都對應到一個檢查,並記錄每個已掃描維度最終的落點。以 ## Handoff 和算術結果結尾;如果估算超過預算,則在建置前停止,詢問使用者需要的機制。這是所有下游工作根據檢查編號引用的產出物:checks.md。
建置 - 由你決定如何進行,但必須先完成 ## Handoff 的算術,如果估算超過預算,則須獲得使用者對機制的選擇。從檢查撰寫測試、實作功能、執行每個證明,並以連貫的片段提交。沒有任務清單,也沒有每個任務的審查表。關於邊界、Landing 時機、交接和範圍護欄的說明:build.md。
驗證 - 當功能的最後一次提交落地時,在同一回合中派遣一個全新的驗證者。不要詢問。不要等待「verify work」。程序、報告格式和有範圍的重新驗證:verify.md。
持久記憶 - 專案決策、工作階段交接和經驗教訓層級:memory.md。
指令碼
將 <skill-dir> 解析為包含此 SKILL.md 的目錄,並執行 python3 <skill-dir>/scripts/<name>.py。專案資料在 .specs/ 下,相對於專案根目錄;當目前工作目錄不同時,請使用 --root。非零的退出碼表示停止並修正。
| 時機 | 指令 |
|---|---|
| 呈現計畫前 | validate_plan.py <feature> |
| 開始建置前 | validate_checks.py <feature> |
| 每次提交前 | check_commit.py --message "<msg>" |
| 宣告完成前 | validate_verification.py <feature> |
| 萃取經驗時 | lessons.py add ... |
| 編輯驗證器或範本後 | selftest.py |
validate_plan.py 執行關閉閘門,並防止樣貌部分變成它所取代的內容:它會拒絕沒有 SHALL 的準則、沒有預設選擇的假設、落點為空白或 n/a 未附理由的 Observable 行、沒有具體樣貌或沒有被拒絕替代方案的 Landing 行、Relations 內的資料行與型別、沒有狀態的 Surface 路由,以及任何空白而非使用 None - <why> 回答的章節。validate_checks.py 是遺漏捕捉器:它會拒絕沒有證明的檢查、宣告大小超過實際分配成員的覆蓋率行、遺漏的已掃描維度,以及遺漏的設定檔行,並對計畫中審查過但沒有檢查提及的路由發出警告。validate_verification.py 會拒絕未引用 file:line 的 PASS 報告、記錄到存活突變的報告、留下 Unproven 成員的報告,或是由建置者撰寫的報告。
只有在沒有程式碼執行工具時才跳過指令碼;屆時透過閱讀產出物來執行相同的檢查,並在對話中明確說明你正在使用降級路徑。
selftest.py 將其自身紀律應用於此技能的閘門:它會對一個已填寫的 fixture 集合進行一次變異(每個規則一次),並要求每個突變體都應被消滅;執行負面控制,證明設定檔範圍的閘門不會在其設定檔以下觸發;並且對在此處提供的每個指令碼進行冒煙測試——未經測試的指令碼是隱藏錯誤的地方,而完成閘門曾因 n 而退出 0
…
[翻譯截斷]