跳到內容

期末作業:交付 todo CLI v1.0.0

一句話:五章的東西全部用上,把練習 repo 的 todo CLI 出成 v1.0.0——這次工具檔要自己寫,做完有驗收腳本告訴你過了沒。

乾淨的main自己寫四樣工具hook · skill · agent · workflow第 1–4 章 · SPEC.md 一起 commit--worktree featremove 指令 + 測試第 5 章 · 同時進行--worktree docsREADME 的用法merge --no-ff回主 checkout ×2之後全部在主 checkout 的 claude 裡做@agent-doc-checker第 2 章 · 對照程式與文件/ship-check → go第 3 章 · 驗收產線/release 1.0.0第 1 章 · CHANGELOG + taggit push ✗第 4 章 · hook 擋下CAPSTONE_OKmake capstone產出:v1.0.0 tag、CHANGELOG.md、多一個 remove 指令、git log --graph 上兩個 merge 節點
一次真正的交付:前五章的工具各就各位,最後由驗收腳本告訴你過了沒。

前五章每章給你一份檔案貼、看一個現象。這一章反過來:不給檔案,只給規格。你自己寫四樣工具(hook 是兩個檔,共五個檔),再用它們走完一次真正的交付流程:

  • 一條規則(第 4 章 hook):這個 repo 不從 Claude session push。push_gate.py 讓它做不到,而不是請它不要。
  • 一份說明書(第 1 章 skill):/release <version> 把「測試、CHANGELOG、commit、tag」一次做完,每次都一樣。
  • 一個對照員(第 2 章 subagent):doc-checker 只讀不改,抓程式與 README 的落差——這正是平行開發最常出的問題。
  • 一條產線(第 3 章 workflow):/ship-check 平行對照程式與文件,給 go/no-go。
  • 兩個 worktree(第 5 章):feat 寫新指令、docs 寫文件,同時進行,回主 checkout merge。

做完你會拿到看得見的東西:git log --graph 上兩個 merge 節點加一個 v1.0.0 tag、CHANGELOG.md 第一段是你的版本、todo CLI 多了一個 remove 指令;然後 make capstone 印出 CAPSTONE_OK。

release 是最常見、也最需要規則的流程:測試要先過、changelog 要有、tag 要打、而且不能手滑 push。它剛好把五章的工具各放到最合理的位置——規則進 hook、程序進 skill、對照派 subagent、驗收寫成 workflow、不相干的工作開 worktree。首頁那句口訣「事實進 CLAUDE.md、程序進 skill、隔離用 subagent、編排用 workflow、強制用 hook、平行開 session」,這一章就是它的實戰版。

第 1 到 5 章 期末作業
工具檔 給你貼 給規格,你自己寫
做完看什麼 一個現象(被擋、有進度列、有摘要) 一個版本(tag、CHANGELOG、新指令)
怎麼知道對了 對照「預期看到」 make capstone 逐項驗收,印 CAPSTONE_OK
時間 各 20 到 40 分鐘 60 到 90 分鐘,五章都做過再來

驗收腳本 capstone/check.py 逐項看這些(在練習 repo 裡,make capstone 就是它;它可以隨時跑,做到哪一組就看哪一組,其他組先 ✗ 沒關係):

交付物 來自 驗收看什麼
.claude/settings.json + .claude/hooks/push_gate.py 第 4 章 matcher 是 Bash;git push 各種寫法 → exit 2 且 stderr 含「已擋下」;其他指令與壞 JSON → exit 0
.claude/skills/release/SKILL.md 第 1 章 frontmatter 四個欄位;正文有 $ARGUMENTS、先跑 unittest、寫 CHANGELOG.md、git tag、不 push
.claude/agents/doc-checker.md 第 2 章 tools 只有 Read/Grep/Glob;正文對照 src/todo.py 與 README.md,回報附行號
.claude/workflows/ship-check.js 第 3 章 meta、≥2 站、parallel 收 thunk、≥2 個 schema、.filter(Boolean)、log、return;用 node stub 跑得完
remove 指令+測試+README 第 5 章 行為符合 SPEC.md;main 上 ≥2 個 merge 節點;worktree 收乾淨
CHANGELOG.md+tag v1.0.0 出版 第一段 ## 1.0.0 (日期) 且列到 remove;tag 在 main 上;工作樹乾淨

驗收看的是形狀與行為,不是逐位元組比對:你的四份檔案跟練習 repo 的 solutions/capstone/ 長得不一樣沒關係,過驗收就是過。

一份給你貼的 SPEC.md,加上你自己寫的四樣工具(hook 是 settings.json+push_gate.py 兩個檔,所以總共五個檔)。每一節最後是自測——寫完先自己試,不要等到最後。提醒:claude 開著的時候,提示符前打 ! 就能直接跑 shell 指令(首頁「四件先講清楚的事」),本章會一直用到。

feat 與 docs 兩個 session 都照這份做,所以它要跟工具檔一起進 git。在終端機貼(與練習 repo 的 solutions/capstone/SPEC.md 相同):

Terminal window
cat > SPEC.md <<'EOF'
# SPEC:todo CLI v1.0.0
## 新指令 remove
- `python3 src/todo.py remove <n>`:移除第 n 筆(編號與 `list` 顯示的相同,從 1 起算)。
- 成功:印 `已移除 #<n>: <text>`,exit 0;剩下的項目編號往前補(`list` 重新從 1 排)。
- 編號不存在(0、超出範圍、不是整數):stderr 印 `錯誤:編號不存在:<原輸入>`,exit 1,清單不變。
- `USAGE` 改成 `用法:todo.py add <text> | list | done <n> | remove <n>`。
## 測試
- `tests/test_todo.py` 至少加兩個測試(測試函式名稱要含 `remove`):移除成功(清單少一筆、編號重排)、錯誤編號(exit 1、清單不變)。
## 文件
- `README.md` 的「怎麼跑」加上 `remove` 的範例,並用一句話說明移除後編號會重排。
## 出版
- `CHANGELOG.md` 最上面一段是 `## 1.0.0 (YYYY-MM-DD)`,條目從 git log 產生。
- 打 tag `v1.0.0`。不 push。
EOF

兩個檔,形狀跟第 4 章一樣,只換 matcher 與規則:

  • .claude/settings.json:PreToolUse,matcher 是 Bash(push 是 Bash 工具跑的,Edit|Write 攔不到),command 用 $CLAUDE_PROJECT_DIR 指到 .claude/hooks/push_gate.py。同一個檔再加一個跟 hook 無關的鍵 worktree.baseRef——第 5 章講過 --worktree 預設從 origin/main 長,設成 head 就改從你本機的 HEAD 長,步驟 6 開的 worktree 才會帶著你沒 push 的工具鏈與 SPEC.md(官方文件)。這個檔沒有要你設計的部分,跟第 4 章那份只差 matcher、檔名與多一個 worktree 鍵,可以直接貼(\" 是 JSON 字串裡放引號的寫法,第 4 章講過):

    {
    "hooks": {
    "PreToolUse": [
    {
    "matcher": "Bash",
    "hooks": [
    { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/push_gate.py\"" }
    ]
    }
    ]
    },
    "worktree": { "baseRef": "head" }
    }
  • .claude/hooks/push_gate.py:讀 stdin JSON 的 tool_input.command。同一段指令裡同時出現 git 與 push(git push origin main、git push --tags、cd x && git push 都算)→ 往 stderr 印一句含「已擋下」的理由,exit 2。其他情況 exit 0,並在 stderr 留一行 [push-gate] skip: 原因;stdin 不是 JSON、沒有 command 欄位也是 exit 0。

起手式:不用從第 4 章那支 70 行的 spec_gate.py 刪(要對照的話它在 solutions/ch4-hooks/.claude/hooks/),直接用這個骨架——只有 BLOCK_MSG 的內容與 TODO 那一行是你要寫的:

#!/usr/bin/env python3
"""PreToolUse hook:不准從 Claude session 執行 git push。"""
import json
import sys
BLOCK_MSG = "已擋下:……" # 你的理由:要含「已擋下」,並叫它停下來回報、不要換指令再試
def skip(reason):
print("[push-gate] skip: " + reason, file=sys.stderr)
return 0
def main():
try:
payload = json.load(sys.stdin) # Claude Code 從 stdin 餵進來的 JSON
except (ValueError, OSError):
return skip("stdin 不是合法 JSON")
command = (payload.get("tool_input") or {}).get("command") if isinstance(payload, dict) else None
if not command:
return skip("tool_input 沒有 command")
if ...: # TODO:規則判斷——同一段指令裡同時出現 git 與 push
print(BLOCK_MSG, file=sys.stderr)
return 2
return skip("不是 git push")
if __name__ == "__main__":
sys.exit(main())

規則本身不用正則,"git" in command and "push" in command 這種最簡單的寫法就能過驗收(代價是 git commit -m "push fix" 也會被擋——規格說了寧可誤擋)。

兩個常見的坑:只找字串 push 會誤擋 echo push;只擋開頭是 git push 會漏掉 cd x && git push。寧可誤擋,不要漏擋——但驗收會兩邊都試。

自測(在終端機,不在 Claude 裡)。| 把左邊那段 JSON 當右邊程式的 stdin——就是 Claude Code 餵 hook 的方式;$? 是上一個指令的 exit code:

Terminal window
echo '{"tool_name":"Bash","tool_input":{"command":"git push --tags"}}' | python3 .claude/hooks/push_gate.py; echo "exit=$?"
echo '{"tool_name":"Bash","tool_input":{"command":"echo push"}}' | python3 .claude/hooks/push_gate.py; echo "exit=$?"

預期:第一行 stderr 有「已擋下」且 exit=2;第二行是 [push-gate] skip: … 且 exit=0。

.claude/skills/release/SKILL.md。frontmatter 四個欄位:

欄位 值 為什麼
name release 指令名以目錄名為準,兩者一致
description 做什麼、只在使用者說要出版本時用 前幾個字最重要
argument-hint <version> 打 /release 時提示要帶版本號
disable-model-invocation true 它會 commit、打 tag——有副作用的步驟不讓 Claude 自己決定要不要跑(第 1 章那張表)

正文照這個順序寫成編號清單(順序就是安全網):

  1. 版本號從 $ARGUMENTS 拿(例如 1.0.0);沒給就停下來問使用者,不要自己猜。
  2. 確認在 main 且工作樹乾淨:git status --porcelain 沒有輸出(版本要建立在全部 commit 的狀態上);不是就停下來回報。
  3. 跑 python3 -m unittest -v,最後一行要是 OK 或 OK (skipped=N)(第 5 章解釋過那幾個跳過);不是就停下來回報、不要出版。
  4. 用 git describe --tags --abbrev=0(從目前的 commit 往回找最近的一個 tag)找上一個 tag:有就只看 <tag>..HEAD 的 git log --oneline,沒有就看全部。
  5. 依 commit 訊息開頭的 type 分組(第 1 章 changelog 的格式),只列有內容的分組。
  6. 用 date +%F 取今天日期,把 ## <version> (YYYY-MM-DD) 那段插到 CHANGELOG.md 最上面(沒有就建、有就插在第一行前,不動舊內容)。
  7. git add CHANGELOG.md && git commit -m "chore: release <version>"。
  8. git tag v<version>。
  9. 印出那段 changelog 與 tag 名,並提醒:沒有 push,要推請使用者自己在終端機做。

限制:只動 CHANGELOG.md;絕不 push。驗收會在正文找這五個字串,請原樣寫:$ARGUMENTS、unittest、CHANGELOG.md、git tag、push。

自測:重開 claude 後輸入 /,清單有 release,旁邊提示 <version>。

.claude/agents/doc-checker.md。frontmatter:name: doc-checker;description 寫觸發時機(「合併多個 session 的成果之後、出版之前」)而不是類別名;tools: Read, Grep, Glob——唯讀靠這一行,不靠「請不要改檔」。

正文:讀 src/todo.py 的 USAGE 字串與 main() 實際接受的每個指令(名稱、參數、成功與失敗時印什麼);讀 README.md 的「怎麼跑」與範例;逐一比對「程式有、文件沒提」與「文件寫了、程式沒有或行為不同」;每筆一行 檔名:行號 — 差異與建議;全部一致就只回一句「文件與程式一致」。驗收會在正文找 src/todo.py、README.md、行號 三個字串,請原樣寫。

自測:重開 claude 後輸入 @,選單有 agent-doc-checker。

.claude/workflows/ship-check.js。先拆站(第 3 章的四個問題),再寫:

站 輸入 輸出 失敗怎麼辦
Check:parallel 兩個 agent(thunk 形式) 一個讀 SPEC.md 對照 src/todo.py 與 tests/test_todo.py;另一個讀 SPEC.md 對照 README.md 各回 findings(第 3 章那份 FINDINGS schema 可以直接沿用) 回 null 的用 .filter(Boolean) 濾掉,log 一行筆數
Verdict:一個 agent 上一站合併後的 findings JSON schema:verdict 只能是 go 或 no-go(用 enum,第 3 章那張「七個寫法」表的最後一列),加 reasons 字串陣列 return 它,對話裡就看得到

規格:meta.name 是 'ship-check'、有 description、phases 列出兩站;prompt 要自足(subagent 看不到主對話,要讀哪些檔、對照什麼、回什麼都寫進去);每個 agent 都不改檔。

做法:cp solutions/ch3-workflow/.claude/workflows/review-flow.js .claude/workflows/ship-check.js(第 3 章貼的那份已被重置洗掉,solutions/ 有留),然後改五處——① meta 的 name/description/phases;② reviewPrompt 改成「先讀 SPEC.md 再對照指定檔案」的 prompt;③ parallel 裡兩個 agent 的檔案與 label;④ 加一個 VERDICT schema,最後那個 agent() 的選項加 schema: VERDICT;⑤ return summary 改成 return verdict。Verdict 的 schema 長這樣:

const VERDICT = {
type: 'object',
required: ['verdict', 'reasons'],
properties: {
verdict: { type: 'string', enum: ['go', 'no-go'] },
reasons: { type: 'array', items: { type: 'string' } },
},
}

驗收是用文字比對腳本的形狀,這幾個字請照第 3 章的寫法:export const meta = {、() => agent(、.filter(Boolean)、log(、schema:——空格多少沒關係,但 () => 這個包法與頂層的 return 不能省。

自測:這支腳本要 Claude Code 提供的 agent() 才跑得起來,所以直接用驗收腳本的靜態檢查與 node stub:python3 capstone/check.py,只看 [4/6] 那一組是不是全 ✓。

5. 兩個 worktree 的提示詞(第 5 章)

Section titled “5. 兩個 worktree 的提示詞(第 5 章)”

自足、寫清楚邊界、只 commit 不 push、commit 訊息指定好(release 會照它產 changelog)。

feat(整段貼):

讀 SPEC.md,只改 src/todo.py 與 tests/test_todo.py,完成「新指令 remove」與「測試」兩節,不要動 README;python3 -m unittest tests.test_todo -v 全過之後,用訊息「feat: add remove command」commit,不要 push

docs(整段貼):

讀 SPEC.md,只改 README.md 完成「文件」那一節,不要動 src/ 與 tests/;用訊息「docs: document remove command」commit,不要 push

主 checkout 的工作(裝工具鏈、收斂、出版、驗收)都在系統終端機直接開 claude 做——Orca 路線也一樣,跟第 5 章的收斂相同;只有 feat、docs 兩個 worktree 依路線不同。

練習交付 v1.0.0:從閘門到 CAPSTONE_OK

  1. 起點:

    第一次做就 clone;已 clone 過、或要重做,用第二段把上一次的 worktree、分支、tag 與 merge 結果一起清掉(可以重複跑)。已經在 repo 目錄裡的人不要再貼 clone 這段。

    Terminal window
    git clone https://github.com/AngusLu0731/claude-code-practice.git
    cd claude-code-practice

    已 clone/重跑(在 repo 根目錄執行):

    Terminal window
    for n in feat docs; do
    git worktree remove --force ".claude/worktrees/$n" 2>/dev/null
    git branch -D "worktree-$n" 2>/dev/null
    done
    git tag -d v1.0.0 2>/dev/null
    git merge --abort 2>/dev/null
    git checkout -f main && git reset --hard origin/main
    git clean -fd && git worktree prune

    白話:這段=把 repo 恢復成剛 clone 完的乾淨狀態。它會丟掉未 commit 的改動與未追蹤檔(含你自己貼進.claude/ 的檔案),但不會動被 gitignore 的.todo.json(只影響 list 顯示的項目,想清就 rm -f .todo.json)與.claude/settings.local.json(存的是你核准過的權限,留著沒關係)。origin/main 是 GitHub 上那份 main 在你電腦裡的複本,所以reset --hard origin/main=回到跟 GitHub 一模一樣。最後的 git worktree prune 是第 5 章才用得到的清理,現在跑了沒作用也無害。

  2. 信任資料夾:這個目錄還沒接受過信任對話的話,先 claude 接受再 /exit。

  3. 貼 SPEC.md、寫四樣工具:照上面「規格」的 0 到 4 節,在終端機貼 SPEC.md,然後寫 settings.json+push_gate.py、release/SKILL.md、doc-checker.md、ship-check.js。release 是唯一要自己建目錄的:先 mkdir -p .claude/skills/release(其他三個資料夾練習 repo 已預建,直接 cat > 或用編輯器存檔)。每寫完一份就做那一節的自測;hook 與 workflow 的自測不用開 claude,現在就能做。四個檔都寫完先 ls -R .claude 對一次路徑——驗收每一組的第一項都是「檔案存不存在」,路徑打錯是最便宜的失敗。

  4. 先 commit 工具鏈(有了 baseRef: head,worktree 會從你本機的 HEAD 長出來,但沒 commit 的檔案還是帶不過去;SPEC.md 兩個 session 都要讀):

    Terminal window
    git add -A && git commit -m "chore: release 工具鏈與 remove 規格" && git status --porcelain

    git status --porcelain 應該沒有輸出。

  5. 開 claude,確認四樣都在:

    Terminal window
    claude

    /hooks 的 PreToolUse 底下有 Bash → push_gate.py;輸入 / 清單有 release(提示 <version>)與 ship-check;輸入 @ 有 agent-doc-checker。都在就 /exit。少一樣回步驟 3 對照那一節的規格。

  6. session feat:

    有了 settings.json 的 worktree.baseRef: head,--worktree 會從你本機的 HEAD(也就是剛 commit 的 main)長出來,工具鏈與 SPEC.md 都在裡面。開一個終端機,在 repo 根目錄:

    Terminal window
    claude --worktree feat

    開好先在提示符打 !ls SPEC.md .claude/hooks/(! 開頭=直接跑 shell),兩樣都在才往下;少了就是 baseRef 沒設好,看故障排除。

    開好後整段貼「規格」第 5 節的 feat 提示詞。

  7. session docs:

    再開一個終端機,cd 到同一個 repo:

    Terminal window
    claude --worktree docs

    開好後整段貼「規格」第 5 節的 docs 提示詞。兩個 session 同時跑;等兩邊都回報「已 commit」。

  8. 收斂(主 checkout):

    第三個終端機,在 repo 根目錄(不是 .claude/worktrees/ 底下):

    Terminal window
    git merge --no-ff --no-edit worktree-feat && git merge --no-ff --no-edit worktree-docs && python3 -m unittest tests.test_todo -v

    預期 Ran 8 tests(或更多——SPEC.md 只要求至少加兩個測試,Claude 多寫幾個也算)與 OK——tests.test_todo 只跑 tests/test_todo.py 這一個檔(main 上 6 個,feat 至少加 2 個);第 5 章的 python3 -m unittest -v 跑的是全部三個檔的 31 個。然後跑 git log --oneline --graph | head -8,看到兩行 Merge branch 再進步驟 9——步驟 9 會把分支刪掉,之後想補 merge 節點就得重做。

  9. 收掉兩個 worktree:CLI:回 feat、docs 兩個終端機各打 /exit,選 remove(已 merge,安全;沒問的話自己收:git worktree remove .claude/worktrees/feat && git branch -d worktree-feat,docs 同理)。Orca:各打 /exit,側欄刪掉 feat、docs。然後在主 checkout:

    Terminal window
    git worktree prune && git worktree list

    只剩一行。

  10. 對照、驗收、出版、閘門——主 checkout 開 claude,依序四段提示詞:

    @agent-doc-checker 對照 src/todo.py 與 README.md,找出 remove 相關的不一致

    回報「文件與程式一致」就往下;有列出 remove 的差異就請主對話照它修 README.md,並用訊息 docs: sync README with CLI commit(/release 要求工作樹乾淨)。它多半還會順手列出 README 本來就沒寫的細節(add 空字串、用法錯誤的訊息……),那些跟這次交付無關,可以不理。

    /ship-check

    第一次會出現核准提示(列出 Check、Verdict 兩站),選 Yes, run it;/workflows 可以看進度。要拿到 verdict 是 go;no-go 就照 reasons 的 檔名:行號 修、commit、再跑。

    /release 1.0.0

    它會跑測試、寫 CHANGELOG.md、commit、打 tag,最後提醒你沒有 push。

    把 main 和 tag v1.0.0 推到 origin

    這一句是故意的:看 hook 把 push 擋下來。你對練習 repo 的 GitHub 沒有 push 權限,所以就算 hook 沒生效也推不上去——但要分清楚:看到含「已擋下」的訊息才是 hook 擋的;看到 GitHub 的 Permission denied 代表 hook 沒生效,回故障排除「push 沒被擋」。看到回報之後 /exit。

  11. 驗收:

    Terminal window
    make capstone

    沒有 make 的話 python3 capstone/check.py。

❯ /release 1.0.0
(release skill v1)把目前的 main 出成一個版本。版本號:1.0.0
⏺ Bash(python3 -m unittest -v)
⏺ Write(CHANGELOG.md)
⏺ Bash(git add CHANGELOG.md && git commit -m "chore: release 1.0.0" && git tag v1.0.0)
已出版 1.0.0:CHANGELOG.md 第一段如下,tag v1.0.0 已打。沒有 push——要推請你自己在終端機做。
❯ 把 main 和 tag v1.0.0 推到 origin
⏺ Bash(git push origin main --tags)
  ⎿ Blocked by PreToolUse hook
    已擋下:這個 repo 不從 Claude session push。請停下來回報使用者,不要換別的指令再試。
push 被 hook 擋下了,main 與 v1.0.0 都還在本機。
示意圖

橘色那行是你的 SKILL.md 正文被展開的證據(第一句寫什麼就看到什麼);紅色那兩行是你在 push_gate.py 裡寫的理由。

❯ make capstone
[1/6] push-gate hook(第 4 章)
  ✓ PreToolUse 有 matcher 含 Bash、command 指到 push_gate.py
  ✓ git push/git push --tags/cd … && git push 都被擋(exit 2,stderr 含「已擋下」)
……
[5/6] 平行開發與收斂(第 5 章)
  ✓ main 的歷史有 2 個 merge 節點
  ✓ remove 1 印「已移除 #1: A」,list 剩「[ ] 1. B」(編號重排)
[6/6] 出版
  ✓ CHANGELOG.md 第一段是「## 1.0.0 (2026-09-03)」,有 9 條
  ✓ tag v1.0.0 存在且在 main 的歷史上
════════════════════════════════════════════
 CAPSTONE_OK  todo CLI v1.0.0 已交付
 五章的工具都是你自己寫的,也都跑過了:
   第 1 章 skill      /release
   第 2 章 subagent   doc-checker
   第 3 章 workflow   /ship-check
   第 4 章 hook       push-gate
   第 5 章 worktree   feat + docs 兩個 merge 節點
 v1.0.0 → 3f2a9c1(2026-09-03)
════════════════════════════════════════════
示意圖

每一組的 ✓ 都對應「交付物」那張表;哪一項 ✗ 就會印原因與提示,修好再跑一次。hash、日期與條數是你的。

  • 步驟 5:/hooks、/、@ 三個地方都看得到你寫的東西。
  • 步驟 6:兩個 worktree 裡 ls SPEC.md .claude/hooks/ 都有東西——起點是你本機的 main,不是 origin/main。
  • 步驟 8:兩次 merge 沒衝突,Ran 8 tests(或更多)與 OK;git log --oneline --graph 最上面兩個 merge 節點。
  • 步驟 10:doc-checker 的回報帶 檔名:行號(或一句「文件與程式一致」;實跑時它常會自己加標題、一筆寫成多行,跟第 2 章一樣,行號有就好),而且 git status 沒有任何檔被它改;/workflows 看得到 Check 兩個 agent 與 Verdict 一個 agent,回到對話的 verdict 是 go;/release 1.0.0 之後 git tag 有 v1.0.0、CHANGELOG.md 第一行是 ## 1.0.0 (今天日期)(條目會包含 clone 來的那幾個 commit,這是正常的——它們也還沒出過版本);push 被擋,訊息含「已擋下」,git status --porcelain 沒有輸出。
  • 步驟 11:六組全 ✓,最後是 CAPSTONE_OK 那塊;python3 src/todo.py remove 1 真的能用。
  • worktree 裡沒有 .claude/ 或 SPEC.md:兩種原因。步驟 4 沒 commit;或是 settings.json 少了 worktree.baseRef: head(JSON 打錯也算)——--worktree 就照預設從 origin/main 長,你沒 push 的東西當然不在。救法:回主 checkout 確認 commit 了、settings.json 修好,然後在那個 session 的提示符打 !git merge main(或另開終端機 cd .claude/worktrees/feat && git merge main)。備案:自己用 git 從本機 main 開 worktree,再用同名進去——git worktree add .claude/worktrees/feat -b worktree-feat main,然後 claude --worktree feat(同名會直接進既有的 worktree)。
  • feat 或 docs 動了不該動的檔、merge 衝突:git merge --abort,回那個 session 說「只改指定檔案,重做一次」——邊界要寫進提示詞,這是第 5 章的重點。
  • doc-checker 改了檔:tools 那行少了或多了東西。唯讀是 tools: Read, Grep, Glob 給的,不是提示詞。
  • /ship-check 一直 no-go:看 reasons。真的是程式或文件跟 SPEC 對不上就修;如果只是驗收 agent 太嚴(列的都是不影響使用者的小事),把 Verdict 站的 prompt 寫清楚「小問題給 go」。
  • /release 說工作樹不乾淨或測試沒過:git status、python3 -m unittest -v 自己看一次——skill 的順序就是為了在這裡停下來。
  • push 沒被擋:/hooks 看 PreToolUse 有沒有 Bash 那條;matcher 寫成 Edit|Write 是最常見的錯。擋是擋了但 Claude 換了指令再試:你的理由那句要寫「不要換別的指令再試」。
  • merge 時畫面變成一堆 #、打字沒反應:git 開了 vim 要你確認合併訊息(指令少了 --no-edit)。打 :wq 再 Enter。
  • 驗收說 merge 節點不夠:收斂時少了 --no-ff,git 用 fast-forward 把 main 直接挪到分支尾端、沒留合併 commit。重來:git log --oneline 找到訊息是「chore: release 工具鏈…」那行前面的 7 碼 hash → git tag -d v1.0.0 → git reset --hard <那個 hash> → 重做步驟 8(這次記得 --no-ff --no-edit;分支還在就不用重做 worktree)→ 重做步驟 10 的 /release。如果步驟 9 已經 remove(分支已刪),feat/docs 的成果只剩 git 的 reflog 裡有:最簡單是回步驟 6 重開兩個 worktree 重做;進階做法是 git reflog 找到「feat: add remove command」那個 commit 的 hash,git branch worktree-feat <hash> 把分支接回來(docs 同理)再 merge。
  • 驗收說還有 worktree、或 remove 說 locked:git worktree list 看是哪個;locked 就先 git worktree unlock .claude/worktrees/<name>,再 git worktree remove 那個路徑、git branch -d 它的分支、git worktree prune。
  • 驗收的 [4/6] 說找不到 node:那組只做靜態檢查、stub 執行標示跳過,不算失敗。
  • 把第 4 章的 spec-gate 也掛上:settings.json 的 PreToolUse 陣列放兩個項目(Edit|Write → spec_gate.py、Bash → push_gate.py)。SPEC.md 已經在了,它會放行——兩條規則、各管各的工具。順帶一提,hook 在 worktree session 裡也生效:settings.json 與 push_gate.py 都已 commit,feat/docs 兩邊都有。
  • 給 /release 加前置條件:在 SKILL.md 步驟 0 要求使用者先貼上 /ship-check 的 verdict,不是 go 就停。程序(skill)與驗收(workflow)就串起來了。

自檢(勾選會存在你的瀏覽器)0/8

以 Claude Code 2.1.246(2026-08-26)為準