昨天把權限邊界寫進 settings.json,理由是「自律有狀態,設定檔沒有」。今天要處理的是同一個病的另一半:那些不危險、但你每天要重複講三次的話。
重複講的問題不是麻煩,是每講一次都會少講一點。第一次你會完整說「檢查 schema 有沒有用浮點數存金額,而且 deadline 要是 UTC」,第五次你會說「檢查一下 schema」,然後它就只檢查了一半。
先把名詞講清楚。這件事在 Claude Code 裡有兩種寫法:一個檔(.claude/commands/x.md)或一個目錄(.claude/skills/x/SKILL.md),兩種都會變成 /x。官方文件現在把它們統稱 skill,一個檔的是舊格式,繼續能用;目錄那種多的是補充檔和 paths。frontmatter、被 Claude 自動叫,兩種都有 —— 這件事文件寫得含糊,明天有實測。這篇兩種都會出現:一個檔的先講,因為它最小;目錄那種留到最後一條。明天講的是目錄那種多出來的東西怎麼影響它活不活得下來。
不是所有重複的話都該做成指令。我的判準是三個條件同時成立:
| 條件 | 說明 |
|---|---|
| 重複 | 一週講兩次以上 |
| 會退化 | 講快了就會漏掉細節(= 它有一個完整版本,而你記不住) |
| 輸出可判定 | 它跑完之後,你看得出來過或沒過 |
第三條最容易被忽略,所以直接拿八條常看到的指令來篩:
| 指令 | 重複 | 會退化 | 輸出可判定 |
|---|---|---|---|
/commit 產 Conventional Commits 訊息 |
✅ | ✅ | ✅ 格式有規範,commitlint 就是裁判 |
/i18n 抽出寫死的中文字串換成 t('key') |
✅ | ✅ | ✅ 跑完 grep 還剩幾個 = 0 |
/doc 產 OpenAPI YAML |
✅ | ✅ | ✅ 有 schema,spectral lint 驗得了 |
/test 補單元測試 |
✅ | ✅ | ⚠️ 測試跑得過是一回事,「有沒有涵蓋邊界條件」沒有裁判 |
/explain 帶新人看架構 |
❌ 只做一次 | — | ❌ 一段意見 |
/review 挑效能與安全問題 |
✅ | ✅ | ❌ 一段意見,沒有過或沒過 |
/refactor 降圈複雜度、拆函式 |
✅ | ✅ | ❌ 改完對不對,只有它自己的報告 |
/audit 掃 SQL Injection / XSS |
✅ | ✅ | ❌ 同上,而且更糟 |
八條裡只有三條乾淨過關。而落選的五條,除了 /explain 是死在第一條(新人只接手一次,做成指令沒有意義),其餘四條全部卡在第三條。
/review 最單純:輸出是一段意見,每次跑出來都不一樣,你反而更花時間。
而 /refactor 和 /audit 死在同一句話上:它們的 prompt 結尾都掛著「並提供修復程式碼」。/i18n 也會改檔,但改完有 grep 當裁判;這兩條改完只剩它自己的報告 —— 你沒辦法從「它沒報錯」推論出「東西是對的」,因為也可能是它改完才沒報錯。裁判和選手要分開,這是這個系列從第一天講到現在的同一件事。
/audit 還多一層危險:/review 至少給你一堆意見,資安掃描回你「沒發現問題」,你會以為你安全了。但那只代表它沒找到。
給不出紅燈的檢查,它的綠燈就不是資訊。
還有一個是順手可以修的:/review 那條裡「檢查是否符合 ESLint/Prettier」的半段,不該交給 AI。eslint . && prettier --check . 兩秒跑完而且每次結果一樣,塞進 prompt 反而變成隨機的 —— Day 6 立過的規矩,這裡剛好撞見一個現成的違例。
順帶一提,前面那八條裡有兩條寫「請讀取目前選取或開啟的檔案」—— 別這樣寫。指令檔的輸入只有 $ARGUMENTS/$1…$9(你打在指令後面的第 1 到第 9 個參數,/mike humanizer 的 $1 就是 humanizer)、@檔案 與 !指令;「目前開啟的」換個終端、換台機器、進 CI 就沒有了。把路徑當參數傳進去,指令才帶得走。
先按一次 /help。內建指令比你以為的多 —— 我這版(2.1.269)的官方清單超過七十條 —— 而且有幾條剛好長在「每天講三次」的那個位置上:你本來要自己寫的,它已經有了。前面那張表裡的 /review,如果你要的其實只是「看一下這一輪改了什麼」,那是 /diff,不用寫。
下面分兩區。左邊兩欄照官方文件,右邊是我按它的時機。
| 指令 | 做什麼 | 我什麼時候按 |
|---|---|---|
/status |
印出目前 session 的狀態 | 換了 repo、換了模型之後,先確認自己站在哪 |
/config |
開設定介面:主題、模型、輸出風格 | 想改行為、又不想手編 settings.json |
/context |
用色塊畫出 context 被什麼佔掉 | 回答開始變短、變笨之前 |
/compact 指示 |
把對話壓成摘要,可以帶指示 | 長對話換題目時。帶指示(例如「留下檔案路徑和決定」),要的東西才不會一起被壓掉 |
/rewind |
把 code 和對話一起退回某個 checkpoint | 它改壞了,而且改的不只一個檔 |
/diff |
看工作樹的改動,含它這一輪動的 | commit 前 |
/permissions |
管 allow / ask / deny 規則 | 昨天那份 settings.json 的互動版 |
/memory |
編 CLAUDE.md、看自動記憶 |
Day 6 那份檔案的入口 |
| 指令 | 做什麼 | 我什麼時候按 |
|---|---|---|
/clear |
開新對話,context 清空 | 換題目、而且舊的一點都不需要帶過去。跟 /compact 的差別:壓是留摘要,清是全丟 |
/resume |
回到先前的對話 | 昨天做到一半的那件事。它配 /clear 用:清掉不代表回不去 |
/cost(= /usage) |
顯示 token 用量與費用 | 一段長工作收尾時看一眼,知道這件事花了多少 |
/model |
換模型,並存成之後的預設 | 大改動前換強的,機械性小改換快的 |
/effort |
設推理強度:low 到 max,或 auto |
跟 /model 是一組:先決定誰做,再決定花多少力氣 |
/mcp |
管 MCP 連線與 OAuth | 某個工具突然叫不到的時候,先來這裡看它是不是斷線。Day 14、15 會再回來 |
/hooks |
看 hook 設定 | 確認「某個事件觸發時會跑什麼」。注意這是 Claude Code 的 hook,不是昨天那個 git 的 commit-msg hook |
/doctor |
環境健檢,能診斷也能修 | 裝好第一天按一次;之後只在它行為怪、又說不出哪裡怪的時候按 |
/init |
產一份 CLAUDE.md 起手式 |
新 repo 第一次開。Day 6 那份檔案的第一版通常從這裡來 |
第二區的共通點是:你不會每天用,所以真的需要的時候你會忘記它存在,然後去 Google 一個它早就內建的功能。這一區值得抄一份貼在看得到的地方。
這兩區跟前面那張表的判準不是同一回事 —— 它們不是「一段話變指令」,是產品功能。但它們和自訂指令得的是同一種病:你會忘記它們存在,然後自己寫一條比較差的。 /help 會把它們列出來,但列出來不等於記得,這是下面那條 /mike 要處理的事。
/commit先補一條換誰都能用的 —— 而且它在前面那張表裡三項都過關:重複(一天好幾次)、會退化(寫到第五則就剩「更新文章」)、輸出可判定。
表裡我把裁判寫成 commitlint,但我自己的文章 repo 不用 Conventional Commits,用的是這個系列自己的格式。裁判因此換成別的東西 —— 換成什麼,等一下就看得到。
---
description: 依這個 repo 的系列格式產生 commit message,停住讓我確認
allowed-tools: Bash(git diff:*), Bash(git log:*), Bash(git status:*)
---
暫存區的檔案清單:
!`git diff --cached --stat`
暫存區的內容:
!`git diff --cached`
這個 repo 最近的 commit 格式:
!`git log --pretty=format:'%s' -8`
依照上面 `git log` 看得到的格式,產生**一則** commit message,
然後停住讓我確認,**不要自己 `git commit`**。規則:
1. **一次 commit 只放一篇文章。** 暫存區裡出現兩篇以上的 `story/` 資料夾,
先告訴我是哪幾篇、建議怎麼拆,不要硬寫成一則。
2. 標題寫**做了什麼**,不寫改了哪些檔 —— 檔案清單 `git show --stat` 就有了,
寫進訊息裡是重複。
3. 繁體中文,**半形**標點。
4. 提到具體數字(幾個檔、幾行)時,只用上面 `git diff` 裡看得到的,
**不要推算**。數不出來就不要寫。
輸出只給訊息本文,前後不要加說明。
那三行 !`…` 是把指令輸出塞進 prompt 的寫法,要在 frontmatter 的 allowed-tools 裡放行,展開時才會直接跑 —— 沒放行的,它不會跑,而是換成一句「先照原文跑這條」交給 Claude,走一般的權限詢問。
三個地方值得說。
第一,「不要自己 git commit」。 它產訊息,我按下去。少了這句,它就從裁判旁邊的助手變成選手。
第二,這裡面沒有「不要加 AI 署名」這條,而且是刻意的。昨天已經在 settings.json 用 attribution.commit: "" 關掉,還寫了一支 commit-msg hook 驗過(最後沒裝,設定就夠了)。同一件事不要在 prompt 裡再求一次 —— 設定擋得住的東西寫進 prompt,等於把一個確定的檢查換成一個機率的檢查。Day 6 那條規矩,最容易被自己違反的地方就在這裡。
第三,第 4 條「不要推算數字」。 沒有這句,它會很自然地寫出「新增約 200 行」—— 一個你既反駁不了也驗證不了的數字。
驗法:git reset --soft HEAD~1,把上一則 commit 的改動退回暫存區,讓 /commit 對著同一份 diff 重寫一次,再跟我當初手寫的那則比。
| 訊息 | |
|---|---|
| 我寫的 | md-single-html 多一份對外公開的 Gist,README 與 CLAUDE.md 記下三邊同步 |
| 第一次跑 | md-single-html 補上 Gist 那份:三份腳本要同步 |
| 第二次跑 | md-single-html 補記 Gist 公開版:三份來源要同步,漂移不會有提醒 |
前綴、句式、長度都落在同一個模子裡。而這個模子不是我寫在 prompt 裡的 —— 是 git log 那八行示範出來的。要它學格式,給它看八個例子,比給它寫八條規則短。 那八行就是這條指令的格式裁判。
不過兩次都漏了同一件事:那個 commit 同時改了 CLAUDE.md,兩個版本都只講 md-single-html。git diff --cached --stat 就印在它眼前,CLAUDE.md | 1 + 那一行它看得到,它是判斷這件事不值得進標題。「哪件事重要到要寫進標題」剛好是這條指令唯一委外不掉的部分。
我另外 clone 一份,把 0911 和 0918 兩篇文章一起塞進暫存區(30 個檔),再跑一次:
暫存區裡有兩篇文章,依規則不能寫成一則:1.
story/2026-09-11_…/— cover、gen.sh、中英文、4 組表格圖,共 12 個檔案2.story/2026-09-18_…/— cover、gen.sh、中英文、4 張截圖、5 組表格圖,共 18 個檔案建議拆法(先把 0918 那篇退出暫存):
git restore --staged story/2026-09-18_…/
12 + 18 = 30,跟 --stat 那行對得起來。它沒有硬寫成一則,也沒有自作主張去跑 git restore —— 它把指令印出來,停在那裡等我。
/commit 我放在專案的 .claude/commands/,然後 git add 的時候被擋下來:
The following paths are ignored by one of your .gitignore files:
.claude
所以這條指令根本沒進版控。 它只是我這台機器上的東西 —— 換台機器、或別人 clone 這個 repo,它不存在。專案層指令要成為專案的一部分,得先確認它真的被追蹤;否則它跟貼在螢幕邊的便利貼沒有差別,而便利貼至少你看得到它掉了。
我最後沒解開 .gitignore:這個 repo 的 .claude/ 裡還有本機設定,不該公開。所以 /commit 到今天仍是我的工具,不是這個專案的規則 —— 這兩件事的差別,就是要不要進 Git。
最後補一條跟前面性質不同的。
指令建好之後真正的問題是你會忘記你有它 —— 內建的有 /help 幫你列,自己寫的沒有。放兩個月不用,你連自己寫過什麼都想不起來,於是又乖乖手打一次那段話。
我寫了一條 /mike(就是我的名字,好記比好聽重要)。這一條用目錄格式,~/.claude/skills/mike/SKILL.md:
---
name: mike
description: 列出我自己的 slash command 與 skill,並標出可能已經腐爛的。
我說「我有哪些指令」「列一下 skill」時用。
argument-hint: [關鍵字]
allowed-tools: Bash(ls:*), Bash(find:*), Bash(awk:*), Bash(head:*), Bash(date:*)
---
<這裡用 ! 反引號跑 find,把兩處 commands 目錄與 skills 目錄
連同最後修改時間一起倒出來>
請做三件事,不要讀其他檔案、不要修改任何東西:
1. **能用的**:一張表 —— 名稱 / 一句話用途 / 上次改動距今幾天。
2. **可能腐爛的**:超過 90 天沒動的獨立列在最後,標 ⚠️。
我不記得的東西通常就在這一區。
3. **最後問我一句**:「要用哪一個?」然後停住等我回答。
$ARGUMENTS 有值的話,只顯示名稱或用途含這個關鍵字的。
/mike 全部列出,/mike humanizer 只列相關的。跟 /commit 那個檔比,多的是三行 frontmatter:name 是目錄格式要的;argument-hint 讓 / 選單提示你可以帶關鍵字;description 後半句「我說『我有哪些指令』時用」是寫給 Claude 看的 —— 目錄格式的 skill 平常只有這一行會被 Claude 看到,它靠這行決定要不要自己叫。所以我講「我有哪些指令」的時候,不用打 /mike 它也會跑。
反過來,如果你不要它自己跑,frontmatter 加一行 disable-model-invocation: true:它就從 Claude 的清單上消失,只有你打 /名稱 才會動。官方給的用途是有副作用的 skill —— 部署、寄信、改檔。/mike 只讀不寫、結尾還會停下來問你,所以我沒加;但一條會動檔案的 skill 沒有這一行,等於讓 Claude 可以在你沒開口的時候決定跑它。明天核對我自己那五個 skill 的時候,這一行標對標錯的剛好各有一個。
它符合前面那三個條件嗎 —— 重複 ✅、會退化 ✅、輸出可判定 ✅(列出來的每一行對不對,一眼看得出來;第一次跑就是這樣抓到 bug 的)。
第一次跑就抓到自己一個 bug:有兩個 skill 的 description 是 YAML 多行格式,我的 grep 只印出一個 |;修法是 awk 看到 | 就多讀一行。寫檢查工具的人自己也會寫出要被檢查的東西。
第二個 bug 更值得記。修好第一個之後我測了兩次都正常,隔天不帶參數按 /mike,說明欄整排空白。原因是那行 shell 裡有一段 sh -c '… "$1"' —— 那個 $1 是給 sh 看的,但 slash command 先把 $1…$9 當位置參數換掉了,沒帶參數就換成空字串,sh 拿到的是 ""。之前兩次測試剛好都帶了參數,所以剛好沒踩到。
指令檔裡不能出現
$1,連 shell 腳本裡的都不行 —— 替換發生在 shell 看到它之前。
改法是拿掉 sh -c,讓 awk 直接吃 find 的結果、用 FILENAME 取名字;$0 順手寫成 $(0),不賭它哪天也被換。
還有一個順帶的好處。指令不會告訴你它過期了,但那個「超過 90 天沒動」的清單看的是另一件事 —— 你自己多久沒碰它。這個數字比「它還對不對」好查得多,而且通常先壞的是這一個。
這件事後面會再回來 —— Day 12 要清點我寫過的五個 skill,看看有幾個還活著。答案不太好看。
這一篇留下的心法:
每天要講三次的話,就值得變成一個檔;但檔案不會告訴你它過期了,要有東西回頭數。指令檔裡別出現
$1,連 shell 腳本裡的都不行 —— 替換發生在 shell 看到它之前。
明天:寫完的 skill 要回來數,裝來的 skill 要先隔離。我數了自己的五個,剩一個。
.claude/commands/ 舊格式相容性寫在這頁)、內建指令一覽(本文「做什麼」欄照它翻,CLI 2.1.269);2026-09-12 查