期末作業:交付 todo CLI v1.0.0
一句話:五章的東西全部用上,把練習 repo 的 todo CLI 出成 v1.0.0——這次工具檔要自己寫,做完有驗收腳本告訴你過了沒。
前五章每章給你一份檔案貼、看一個現象。這一章反過來:不給檔案,只給規格。你自己寫四樣工具(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。
為什麼是「出版本」
Section titled “為什麼是「出版本」”release 是最常見、也最需要規則的流程:測試要先過、changelog 要有、tag 要打、而且不能手滑 push。它剛好把五章的工具各放到最合理的位置——規則進 hook、程序進 skill、對照派 subagent、驗收寫成 workflow、不相干的工作開 worktree。首頁那句口訣「事實進 CLAUDE.md、程序進 skill、隔離用 subagent、編排用 workflow、強制用 hook、平行開 session」,這一章就是它的實戰版。
跟前五章不一樣的地方
Section titled “跟前五章不一樣的地方”| 第 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 指令(首頁「四件先講清楚的事」),本章會一直用到。
0. SPEC.md(給你的,直接貼)
Section titled “0. SPEC.md(給你的,直接貼)”feat 與 docs 兩個 session 都照這份做,所以它要跟工具檔一起進 git。在終端機貼(與練習 repo 的 solutions/capstone/SPEC.md 相同):
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。EOF1. push-gate hook(第 4 章)
Section titled “1. push-gate hook(第 4 章)”兩個檔,形狀跟第 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 jsonimport 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:
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。
2. release skill(第 1 章)
Section titled “2. release skill(第 1 章)”.claude/skills/release/SKILL.md。frontmatter 四個欄位:
| 欄位 | 值 | 為什麼 |
|---|---|---|
name |
release |
指令名以目錄名為準,兩者一致 |
description |
做什麼、只在使用者說要出版本時用 | 前幾個字最重要 |
argument-hint |
<version> |
打 /release 時提示要帶版本號 |
disable-model-invocation |
true |
它會 commit、打 tag——有副作用的步驟不讓 Claude 自己決定要不要跑(第 1 章那張表) |
正文照這個順序寫成編號清單(順序就是安全網):
- 版本號從
$ARGUMENTS拿(例如1.0.0);沒給就停下來問使用者,不要自己猜。 - 確認在
main且工作樹乾淨:git status --porcelain沒有輸出(版本要建立在全部 commit 的狀態上);不是就停下來回報。 - 跑
python3 -m unittest -v,最後一行要是OK或OK (skipped=N)(第 5 章解釋過那幾個跳過);不是就停下來回報、不要出版。 - 用
git describe --tags --abbrev=0(從目前的 commit 往回找最近的一個 tag)找上一個 tag:有就只看<tag>..HEAD的git log --oneline,沒有就看全部。 - 依 commit 訊息開頭的 type 分組(第 1 章 changelog 的格式),只列有內容的分組。
- 用
date +%F取今天日期,把## <version> (YYYY-MM-DD)那段插到CHANGELOG.md最上面(沒有就建、有就插在第一行前,不動舊內容)。 git add CHANGELOG.md && git commit -m "chore: release <version>"。git tag v<version>。- 印出那段 changelog 與 tag 名,並提醒:沒有 push,要推請使用者自己在終端機做。
限制:只動 CHANGELOG.md;絕不 push。驗收會在正文找這五個字串,請原樣寫:$ARGUMENTS、unittest、CHANGELOG.md、git tag、push。
自測:重開 claude 後輸入 /,清單有 release,旁邊提示 <version>。
3. doc-checker subagent(第 2 章)
Section titled “3. doc-checker subagent(第 2 章)”.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。
4. ship-check workflow(第 3 章)
Section titled “4. ship-check workflow(第 3 章)”.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,不要 pushdocs(整段貼):
讀 SPEC.md,只改 README.md 完成「文件」那一節,不要動 src/ 與 tests/;用訊息「docs: document remove command」commit,不要 push主 checkout 的工作(裝工具鏈、收斂、出版、驗收)都在系統終端機直接開 claude 做——Orca 路線也一樣,跟第 5 章的收斂相同;只有 feat、docs 兩個 worktree 依路線不同。
練習交付 v1.0.0:從閘門到 CAPSTONE_OK
-
起點:
第一次做就 clone;已 clone 過、或要重做,用第二段把上一次的 worktree、分支、tag 與 merge 結果一起清掉(可以重複跑)。已經在 repo 目錄裡的人不要再貼 clone 這段。
Terminal window git clone https://github.com/AngusLu0731/claude-code-practice.gitcd claude-code-practice已 clone/重跑(在 repo 根目錄執行):
Terminal window for n in feat docs; dogit worktree remove --force ".claude/worktrees/$n" 2>/dev/nullgit branch -D "worktree-$n" 2>/dev/nulldonegit tag -d v1.0.0 2>/dev/nullgit merge --abort 2>/dev/nullgit checkout -f main && git reset --hard origin/maingit 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 章才用得到的清理,現在跑了沒作用也無害。開一個系統終端機到你 clone 的主 checkout,跑這一段(刪 worktree 的迴圈對 Orca 無害;要重做的話再到側欄刪掉上次的
feat、docs兩個 worktree)。本章接下來除了步驟 6、7 之外,都在這個系統終端機做。Terminal window for n in feat docs; dogit worktree remove --force ".claude/worktrees/$n" 2>/dev/nullgit branch -D "worktree-$n" 2>/dev/nulldonegit tag -d v1.0.0 2>/dev/nullgit merge --abort 2>/dev/nullgit checkout -f main && git reset --hard origin/maingit clean -fd && git worktree prune -
信任資料夾:這個目錄還沒接受過信任對話的話,先
claude接受再/exit。 -
貼 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對一次路徑——驗收每一組的第一項都是「檔案存不存在」,路徑打錯是最便宜的失敗。 -
先 commit 工具鏈(有了
baseRef: head,worktree 會從你本機的 HEAD 長出來,但沒 commit 的檔案還是帶不過去;SPEC.md兩個 session 都要讀):Terminal window git add -A && git commit -m "chore: release 工具鏈與 remove 規格" && git status --porcelaingit status --porcelain應該沒有輸出。 -
開
claude,確認四樣都在:Terminal window claude/hooks的 PreToolUse 底下有Bash→push_gate.py;輸入/清單有release(提示<version>)與ship-check;輸入@有agent-doc-checker。都在就/exit。少一樣回步驟 3 對照那一節的規格。 -
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沒設好,看故障排除。按 repo 名旁的 +,任務名取
feat,起點選本機的main(工具鏈的 commit 在上面),agent 選 Claude Code;第一次會問信任,選 Yes。開好先在提示符打!ls SPEC.md .claude/hooks/——沒有就代表起點不是本機 main,在這個 worktree 按 Cmd-T 開終端機分頁跑git merge main補進來。開好後整段貼「規格」第 5 節的
feat提示詞。 -
session docs:
再開一個終端機,
cd到同一個 repo:Terminal window claude --worktree docs再按一次 +,任務名取
docs,agent 一樣選 Claude Code。把docs拖到feat旁邊並排。開好後整段貼「規格」第 5 節的
docs提示詞。兩個 session 同時跑;等兩邊都回報「已 commit」。 -
收斂(主 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用步驟 1 的系統終端機,在主 checkout(Orca 的分支名預設就是任務名;不確定先
git branch看):Terminal window git merge --no-ff --no-edit feat && git merge --no-ff --no-edit 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 節點就得重做。 -
收掉兩個 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只剩一行。
-
對照、驗收、出版、閘門——主 checkout 開
claude,依序四段提示詞:@agent-doc-checker 對照 src/todo.py 與 README.md,找出 remove 相關的不一致回報「文件與程式一致」就往下;有列出 remove 的差異就請主對話照它修
README.md,並用訊息docs: sync README with CLIcommit(/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。 -
驗收:
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)為準