dd-loop · git:20261030.92509fc · 2026-10-30 · sha256 b23c9d02094c3092

dd-loop git:20261030.92509fcA

Immutable. This exact content never changes and is served at /api/v1/blob/b23c9d02094c3092.

---
name: dd-loop
description: 8 步開發迴圈(全域 CLAUDE.md §3.9)各步的做法細則:段落起點與 `<起點>` 怎麼算、步驟 4 code-review 怎麼派工、查證與把報告落地、步驟 8 怎麼算要審的 CLAUDE.md、步驟 7 與 8 的關係。全域 §3.9 規定段落開始時、以及步驟 3、4、8 開始前呼叫。不適用於迴圈外的單次修改或純問答。
---

# DD Loop — 8 步開發迴圈的做法細則

全域 CLAUDE.md §3.9 列出 8 步與不能跳的關卡;這份寫各步怎麼做。兩邊講法不同時,以全域 §3.9 的關卡為準。

## 段落起點與 `<起點>`

段落開始前先跑 `~/.claude/scripts/check-claude-md.sh --start-segment`,印出「段落起點:…」才算記好。
⚠️ **這個指令有兩個擋點**:①起點以來有 `SKIP_DOC_CHECK=1` 欠下、還沒補的 CLAUDE.md
②**專案有 `docs/reviews/` 目錄時**(= 採用了 review 留痕慣例),上一段的 commit 範圍內
沒有新增 `docs/reviews/S*.md`。兩者都會 `exit 1` 並印出該怎麼做 —— 照它印的做,不要繞過。
剛採用第二個慣例、而且上一段的起點檔存在又不是空樹時,第一次會被擋(上一段本來就沒報告;從沒記過起點、或上一段從空樹開始的專案不會被擋)。
出路是補一份 `docs/reviews/S<上一段>-SKIP.md`(沒有段落編號就用 `S1`,理由寫「採用 review 留痕之前的段落」)並**連同 trailer 一起 commit** —— 只建檔不 commit 還是會被擋。
沒印出這行就是沒記好。舊版 gate 的徵兆是**什麼都不印、exit 0**(沒有 staged 時)或印「commit 已擋下」(有 staged 時);
用 `grep -q -- --start-segment ~/.claude/scripts/check-claude-md.sh || echo 舊版` 確認(少了 `--` 會被當成 grep 的選項)。
是舊版就不要照它印的補檔或 commit:到本機的 claude-dd clone(沒有就 `git clone https://github.com/kopp0510/claude-dd`)跑 `git pull && ./install-dd-pipeline.sh --force`,再重跑一次 `--start-segment`。
一段常有好幾個 commit,
步驟 3、4、8 都要看「起點到現在」的整段,下文的 `<起點>` = `~/.claude/scripts/check-claude-md.sh --segment-base`
的輸出(exit 非 0 或輸出是空的 = 範圍沒算出來,不是範圍為空)。只看最後一個 commit 會漏(實測)。
忘了記下一段的起點時,範圍會連上一段一起算 —— **SKIP 欠下、還沒補的 CLAUDE.md 只會多審,不會漏;
但「上一段有沒有附 review 報告」那個檢查會漏一整段**(上一段的報告會把這一段的要求頂掉,見下方「報告一律落地」那條)。
**但這只在「起點檔已經存在」時成立**:從來沒跑過 `--start-segment` 的專案(例如剛照 UPGRADING 改用段落起點的舊專案),
要等第一個 `SKIP_DOC_CHECK=1` commit 才會自動補記,起點就落在段落中間,它前面的 commit 步驟 3、4、8 全部看不到。
這時 gate 印的是「記下段落起點 …」、`--segment-base` 也正常 exit 0,兩個訊號都看不出範圍已經縮水。所以段落開始前一定要自己跑一次。
**有進度表時 `--segment-base` 不是最終答案**:專案有 `task-planner` 產的進度表
(`docs/designs/…` 的「進度表」)時,`<起點>` 以**表上這段 commits 欄的起點**為準,**只取 `..` 前面那段**(`4b71e3b..` 填 `4b71e3b`)。
連 `..` 一起代入會靜靜出錯:`git diff 4b71e3b..` 等於 `4b71e3b..HEAD`,漏掉工作目錄還沒 commit 的改動且 exit 0;
`git diff 4b71e3b.. HEAD` 回 exit 129,放在管線裡整條仍 exit 0。
兩者在「回頭做 `BLOCKED` 過的段落」時會不一樣 —— 中間插做別段會把 `--segment-base` 往後推,
用它就漏掉這段先前已經寫好的部分;表上那個較舊、較寬,只會多審。
**半號段落**(`S2.5` 這種插在兩段之間的 hotfix)有自己的 commit 且改了人工邏輯 → 算新段落,要走步驟 3–8;
只是把前一段漏掉的檔案補進同一個 commit、或純文件/資料改動,才算前一段的小任務(只走 1、2)。不管有沒有進度表、用哪種 task 工具都一樣

## 步驟 3、4:code-simplifier 與 code-review

步驟 3 跑 code-simplifier 時只針對該段新增/修改的程式碼:`git diff <起點>`。步驟 4 每段全量跑,修掉 Critical/Important 級發現才續行;細則:

- 範圍要明講:依序跑時 `git diff <起點>` 加上 `git ls-files --others --exclude-standard`(步驟 3 新增、
  還沒 commit 的檔案 `git diff` 看不到),並行時 `git diff <起點> HEAD`。沒講的話,本地 code-reviewer agent
  預設只看還沒 staged 的 `git diff`,已 commit 的整段都審不到
- **這一段沒有「人工寫的程式碼」就不要派 agent**:diff 全是資料、設定、註解或文件,
  或改動已被專案的機械檢查完全覆蓋(渲染等價比對、round-trip 檢查這類)時,改成自己逐條核對、
  在回報附證據(可用 `verification-gate` skill),並在設計文件「段落與小任務」該段底下寫明跳過的理由(沒有設計文件就寫在回報裡);
  有 `docs/reviews/` 時另建 `docs/reviews/S<段落>-SKIP.md`(見下方「報告一律落地」那條)。
  判準是**「這段有沒有正確性還沒被機械檢查證明的人工判斷」,不是 diff 多長**。
- ⚠️⚠️ **派工的 prompt 交事實(行號、數字、實測輸出),不要交「我判斷元凶是 X」** ——
  自己的錯誤診斷寫進去,reviewer 會跟著錯(實證:兩個都接受了錯的框架,只有第三方去查才發現)。要寫就標明是推測。
- **派工前先準備一個 diff 檔**(放 scratchpad),內容:①「範圍要明講」那條算出的整段 diff;
  ②步驟 3 自己改了什麼(依序跑時 `git diff HEAD`,分開附 —— simplifier 改了行為正是要抓的,
  混在整段裡審查者看不出是誰改的);③**派工當下**被改到檔案的整檔內容(依序跑時取工作目錄版本,
  就是 diff 描述的那一版,另把未追蹤的新檔放整檔;與步驟 3 並行時取 `HEAD` 版本,因為 simplifier 正在改工作目錄);
  ④步驟 1(重跑時加步驟 5)已經跑過的驗證輸出,**連同預期值一起寫**,派工 prompt 標明這些已確認、不要重跑(不寫的話它會把測試、build、渲染指令整組再跑一遍)——「跑過」不代表結果對(實測)。
  被改到的檔,審查者與主對話一律讀 diff 檔裡那一份、不讀工作目錄;沒被改到的檔照常讀。
- **diff 檔用 Read 的 `offset`/`limit` 每次 300 行讀到最後一行**,這句也寫進給審查者的 prompt:
  Read 一次讀太多會**靜靜截斷、不報錯**(約 47 KB 就停;實測 3024 行的 diff 檔只回 544 行)
- **主對話直接派審查者**:用 Agent 工具**在同一則訊息裡平行**派審查者
  (`subagent_type: "code-reviewer"`,不帶 model 參數 —— agent 定義是 opus,不換 haiku)**最少 3 個**,
  數量是設定的下限,實際數量自己定,理由寫進 `-judge.md` 的「派工」節。**先派工、派完自己讀 diff 檔**(跟審查者同時進行)。
  一發只會挖到一部分(實測同一段 diff 五發,每發都只命中 3 個已知錯誤中的 2 個、子集還不同;兩發聯集平均 2.3/3 對單發 1.8/3)。派工 prompt 寫明審查者的職責:找漏洞、每條附證據、沒證據不算。
  某個審查者失敗、沒回來或報告不完整 → 補派,補不了就照實寫進報告(哪個、為什麼),不能當作它沒有發現;
  收回的完整報告少於 3 份 → 不算跑過步驟 4,照實說,不續行
- 不另派裁決者:2026-10-24 實測主對話(Opus)直接派平均 269s、5.13 美元、命中 4/6,另派 Opus 裁決者 350s、3.83 美元、命中 3/6(直接派比較貴;n 小、同時改了三件事,只是方向)
- **等全部審查者回來才查證**:派工 prompt 派出時、每個審查者的完整報告(含 Not reported、What I ran)一回來,
  都逐字存進 scratchpad 檔(之後照它落地成報告;context 被壓縮後也還在)。逐條讀程式碼查證(預設「這條可能是錯的」),分三類:採納 / 駁回 / 需使用者決定。
  採納的項目沿用審查者標的級別,調級要寫理由;採納的 Critical / Important 修掉才續行,
  Smells 採納了照改、不擋。審查者列的外部查證項,能查就查,查不了另列「需要外部查證」。
  ⚠️ 查證的人就是寫這段 diff 的人 —— 「預設這條可能是錯的」反過來也要用在自己的辯護上:要駁回就讀程式碼證明,不憑記憶
- 派工 prompt 寫明「只用手上的資料判斷,需要外部查證的列出來交回」;不設審查者的工具次數上限(砍上限會連查證一起砍)。
  **時間花在哪**:實測派送管線只佔 ~25s,時間由它跑幾次工具決定(3–5 次 = 2–4 分、21/26 次 = 13–17 分)
- **審查者對同一處講不一樣時,讀程式碼判定,不表決**:實測有一發看到真錯誤卻判成
  「既有問題、不是這次 diff 引入的」而丟進 Not reported —— 實際上這次 diff 把同一個錯誤寫法
  複製進了新的程式碼路徑。被判成既有問題的項目,要回去看那幾行
- 發現若是「行為變了,但**無從判定哪個才是預期**」(程式碼、測試、文件都沒寫明語意),
  **用 AskUserQuestion 把選擇交回使用者**,不要自己選一邊也不要讓 reviewer 選 ——
  查證時把它標成「需使用者決定」(附選項與建議答案)再問。
  金額、權限、資料保留期這類決定選錯了不會報錯,只會靜靜地錯下去
- ⚠️⚠️ **報告一律落地成 committed 檔,跳過要申報。** 步驟 4 是 8 步裡最容易**靜默**掉的,三個條件疊在一起:
  **①它不留痕**(步驟 2、6 有 commit 賴不掉;步驟 4 跑完什麼都不產生,進度表的 `DONE` 是同一個 agent 自己填的,等於自證)
  **②它最貴**(至少 3 個審查者,是 context 快滿時帳面上最划算的犧牲品)**③跳過條款就寫在旁邊幾行**。
  ⚠️ **而跳過會自我延續**:步驟 7 是迴圈唯一固化教訓的格子,它的輸入來自步驟 4 的發現 —— **剪斷這條回授線不會報錯**。
  - 每個審查者一份 `docs/reviews/S<段落>-a.md`、`-b.md`、`-c.md`…,加一份查證 `S<段落>-judge.md`
    (檔名是歷史沿用,內容是主對話的查證);各三節:**派工 / 發現 / 處置**。
    審查者那幾份:「派工」照給它的派工 prompt 寫、「發現」放 scratchpad 裡的逐字原文、「處置」寫怎麼處理;
    審查者之間的重疊項與獨有項寫進 `-judge.md`。
    第三節是唯一只有自己寫得出來、也唯一看得出「發現有沒有變成修正」的部分。
    **多張進度表時放子目錄**(段落 ID 會跨表重複);**寫之前先 `ls`**,
    因為覆蓋掉別人的報告時 `git status` 顯示的是 `M` 而不是 `??`,沒有任何警告。
    報告檔的命名慣例:見專案自己的 `docs/reviews/CLAUDE.md`。
  - 跳過時建 `docs/reviews/S<段落>-SKIP.md` 寫明理由(有 `docs/reviews/` 時 gate 的 `--start-segment` 要範圍裡新增一份
    `S<數字>…md`,只加 trailer 開下一段時會被擋),commit 訊息加 trailer `SKIP-REVIEW: <段落> <理由>`(放在 `Co-Authored-By` 那一段裡、緊接在它前一行,**中間不能空行** —— 隔了空行 git 就不認它是 trailer,下面的查法回空值、commit 照樣成功),
    同時寫進設計文件該段底下。查的時候用
    `git log --format='%h %(trailers:key=SKIP-REVIEW,valueonly)'`,**不要用 `--grep`**
    (描述這條規則的 commit 自己就會命中)。
  - ⚠️ **目的不是讓說謊變不可能**(報告內容真偽驗不出來),而是把**「靜默省略」這個選項拿掉**。
    `.md` 不在 gate 的 `CODE_EXT` 裡,pre-commit 不會強制它存在(只在開下一段時查,而且只查「有沒有新增任何一份」)—— 不要把它寫成閉環。
  - ⚠️ **證據不可以放 `.git/` 底下**(段落起點檔每個 worktree 各一份、不進版控)。
- ⚠️ **「程式碼只有一行 / 輸入定義域全被測試覆蓋」不是合法的跳過理由** ——
  那個理由**衡量錯了對象**:review 真正抓的不是運算式對不對(那被機械檢查蓋住了),
  而是**註解與文件裡的宣稱**,而端到端的驗證在結構上看不到那一類東西。
  實測某段只跑 simplifier 就抓到一句「作者自稱查證過卻仍然錯」的宣稱;下一段同等規模的改動,兩個 reviewer 抓出 2 個 Critical。
  ⚠️ **判準用「有沒有引別處」而不是「程式碼幾行」**:diff 裡只要有
  「引別處行號 / 函式 / UI 字串 / commit 範圍」的敘述,就不是「只有一個運算式」—— 補審在那段的「修正版」裡
  又抓到一個新的錯宣稱,而 playwright 攔 request body 看不到這一類東西。
  **hotfix 性質的段落通常正是人工判斷最密的地方。**

## 步驟 8:先算範圍

下面這段是**沒有進度表**時的寫法;**有進度表時 `base` 改填表上這段 commits 欄的起點**(只取 `..` 前面那段,見上方「段落起點與 `<起點>`」;
連 `..` 一起填時第一條 `git diff` 回 exit 129,整條管線卻 exit 0,只列出還沒 commit 的那幾份,看起來就像範圍正常):

```bash
base=$(~/.claude/scripts/check-claude-md.sh --segment-base) && [ -n "$base" ] &&
{ git -c core.quotePath=false diff --name-only "$base" HEAD
  git -c core.quotePath=false diff --name-only HEAD
  git -c core.quotePath=false ls-files --others --exclude-standard; } \
  | grep 'CLAUDE\.md$' | sort -u
```

後兩條是「還沒 commit 的」(已追蹤的改動 + 未追蹤的新檔)。**不要改用
`status --porcelain -uall | awk '{print $NF}'`**:git 對含空白的路徑會加引號
(`?? "api server/CLAUDE.md"`,`core.quotePath=false` 只解決非 ASCII),`$NF` 從空白切開
就只剩 `server/CLAUDE.md"`,`grep 'CLAUDE\.md$'` 比對不到而丟棄 —— 少列一份卻照樣 exit 0、
不印任何錯誤。步驟 7 剛寫出、或被 gate 逼著補出來的 CLAUDE.md 正是「還沒 commit」這個狀態

算出幾份就只審那幾份,用 `claude-md-management:claude-md-improver`。
**該 skill 的 Phase 1 寫的是「find 全部」,不先算範圍就會全 repo 掃** —
產出跟本輪無關的長報告,看兩次就會開始跳過、規則等於沒有。範圍算出來是空的才跳過這一步(指令 exit 非 0 是範圍沒算出來,不算空)

## 步驟 7、8 的關係

- 步驟 7 處理「本輪學到什麼」,與全域 §3.9「功能落地後,受影響目錄的巢狀 CLAUDE.md 逐層堆疊更新」那條(程式碼改了所以文件要同步)是兩件事
- **步驟 7 跳過不代表步驟 8 也跳過**:步驟 2、6 commit 時 pre-commit gate 會逼著更新
  改碼目錄的 CLAUDE.md,那些改動不是步驟 7 加的,但一樣要審 — gate 只確認「有寫」、
  **不確認「寫得對」**,這一格正是步驟 8 存在的理由
- **順序不可反**:7 是加、8 是整理。反過來的話 8 剛整理完 7 又塞新東西進去,白做
- **步驟 8 的發現分兩類,不要把選擇丟回給使用者**(使用者未必知道該修什麼):
  實跑驗證得出來的錯(指令、檔名、數量、路徑與實際不符)→ **直接修,不問**,結尾報一行;
  主觀建議(太囉嗦、結構可更好)→ **提一次就好,不動手**。判準是「能不能實跑驗證」
- **步驟 7、8 動到檔案就要 commit**:`.md` 不在 gate 的程式碼副檔名清單裡,不會被擋,
  但留著未提交的改動會混進下一輪