今天要解的問題
Day29 調整的是 vault 的內容流程,今天往回看一層:給 Claude 的那些指令本身。
這個 vault 的設定是一個月裡一點一點長出來的:根目錄的 CLAUDE.md、.claude/skills/ 底下 16 個 skill、Day26 開始的 navi mod、幾支在 session 開始與寫檔前後執行的 hook。中間模型換過一輪,但沒有任何東西會去問:這些寫給舊模型的指令,現在還合用嗎?
我一開始丟給 Claude 的需求是這樣的:
我想要一個 skill 或 mod 定期檢查 claude.md、skill/mod/hook 的狀態,是否符合新的模型,若是需要修改請告訴我,與給我一個新的使用說明。不知道 claude 的 doctor 功能可否
今天做了兩件事:
- 主線:
/claude-doctor,一個每月跑一次、換模型時會提醒的設定健檢 - 支線:第一輪稽核的結果,CLAUDE.md 瘦身、把 10 個 skill 的強調語氣降下來
同一個 session 後來還做了全 vault 的 source 欄位統一和藝評頁標題格式,留到 Day32 再寫。
想法與取捨
用內建的 /doctor,還是自己做一個
先查內建工具能做到哪裡:
claude doctor:在終端機跑、不開 session,看的是安裝狀態和設定檔能不能讀- session 裡的
/doctor:會看無效的設定、沒在用的 skill/plugin/MCP、CLAUDE.md 裡 Claude 自己就推得出來的贅述 /doctor prompt-audit:找過時或互相矛盾的指令、指向不存在檔案的參照。要 v2.1.283 以上,我的版本是 2.1.286,可以用/skill-doctor:看每個 skill 佔多少 context、多常被叫到
沒有一個會回答「這些指令適不適合目前的模型」,也不知道這個 vault 自己的規則:CLAUDE.md 的 skill 表要跟 .claude/skills/ 資料夾一致、README 要跟實際的 hook 對得上。
所以做法是兩個都要:claude doctor 當成稽核的其中一個輸入,vault 自己的規則交給新的 skill。/doctor prompt-audit 和 /skill-doctor 是互動指令,skill 裡沒辦法代跑,就在報告最後提醒我自己去跑一次交叉比對。
先給計畫,還是先做研究
Claude 第一版直接交了設計:寫一支稽核腳本、加一個 SessionStart 提醒、每月排程。我沒有批准,回了這句:
我想要你先做個 research,或是每次都做個 research,來了解大家的使用,或是提出我可以修改的地方
理由很直接:「適不適合新模型」不是我或 Claude 憑印象能判斷的,標準會跟著官方文件和社群做法一起變。所以研究不是設計前的一次性步驟,而是 skill 每次執行的第 0 步。
第一輪研究派了兩個 subagent,一個查官方文件,一個查社群做法。跟這個 vault 直接相關的結論有幾條:
- CLAUDE.md 官方建議每份 200 行以內,越長遵循度越低;我的有 251 行
- 新模型對 system prompt 更敏感,為了防止「漏做」而寫的 CRITICAL、MUST、粗體,現在反而會讓它過度套用
- 只在某些檔案才需要的規則,可以放進
.claude/rules/,用paths:限定,碰到對應檔案時才載入 <!-- -->註解進 context 前會被拿掉,適合放給人看的歷史理由
為了讓「每次研究」不變成每次重講一樣的事,研究結果寫進 research-notes.md,下次只回報跟上一版比有變化的。裡面有一區叫「評估過不採用」,只增不減,例如社群有人主張 CLAUDE.md 要壓到 60 行以內,我判斷這份同時是 vault 的路由規則,砍到 60 行查找成本太高,這條就記進去,下個月不會再被提一次。
稽核完要不要直接改
稽核會找出很多該改的地方,但 skill 本身只報告、不改設定。會被它改寫的只有兩個地方:README 裡由腳本產生的那一段、研究摘要。
這是 Claude 在設計裡就放進去的原則,我沒有異議。設定檔是我跟 Claude 之間的約定,改哪一條、改成什麼語氣,應該由我挑。今天後面降強調、瘦身 CLAUDE.md,都是我看完報告之後一項一項點頭才做的。
換模型的提醒:放 hook,還是放 mod
第一版的提醒是兩支 hook。Claude 原本打算讓 SessionStart 從輸入裡讀模型名稱,查文件才發現 SessionStart 的輸入沒有模型欄位;有另一個事件 PostModelSwitch,會帶 from_model 和 to_model。所以變成 PostModelSwitch 負責換模型、SessionStart 負責「超過 35 天沒稽核」。
做完我問了一句:為什麼這個是用 hook,而不是用 mod?
Claude 的回答承認它是照著 vault 既有的守門 hook 寫,沒有拿 mod 來比過。比下來差別在訊息給誰看:
- hook 的輸出進的是 Claude 的 context。要不要轉告我,是 Claude 決定的,我可能根本看不到;每個 session 還多佔一點 context
- navi 的狀態列畫在我的畫面上,本來就只列「要處理的事」(Inbox 待分類數、raw 沒消化的域),多一項「設定 N 天未稽核」剛好
hook 唯一的優勢是不需要畫面也會跑,但沒人在場的情況已經由每月排程處理了,這個優勢用不到。
我接著問:那是不是 routine 跑的時候全部換成 mod、hook 都變成 mod?Claude 補清楚:守門 hook 不動。擋下「在主 checkout 寫檔」「推到 main」這種事,必須在任何環境都可靠生效,包括沒有畫面的排程,那就只能是 hook。最後的分工變成:

- skill 做事:研究、稽核、報告、重生 README,最後在 README 尾端蓋一個戳記:哪個模型、哪一天稽核的
- routine 觸發:每月 1 號 09:00 自動跑一次,開 PR 等我看
- mod 提醒:navi 讀那個戳記,模型不同或超過 35 天就在狀態列顯示
每 30 秒檢查一次,還是 turn 開始時檢查
改成 navi 之後,第一版是掛在 navi 原本就有的 30 秒刷新上。我問:30 秒是必要的嗎?模型應該不會很常更新。
確實不必要。會這樣寫只是因為那裡剛好有一個現成的計時器。模型只會在兩個 turn 之間用 /model 切換,戳記一個月才變一次,每 30 秒讀一次 README 是白做工。
改成三個時間點:session 開始時算一次、每個 turn 開始時重算、手動 /navi refresh 時重算。換模型後送出的第一句話就會反映,結果沒變就不重畫狀態列。Inbox 數那些項目照舊每 30 秒更新,兩件事拆開。
強調語氣要降到多少
第一輪稽核在 11 個檔案標出「強調密度偏高」,最高的是 vault-maintain:147 行裡有 81 個粗體、禁止、一律、絕不。
我先讓 Claude 改了 vault-maintain,改完再請它講清楚強調的好處和壞處,才決定其他 skill 要不要跟進。好處是標出真正不能錯的事、衝突時有優先順序、人掃讀方便;壞處是稀釋(81 個裡真正危險的兩條,看起來跟「回傳結構化結果」一樣重要)、新模型會過度套用、只有命令沒有理由時遇到例外無法判斷。
改 vault-maintain 時 Claude 用的標準是:只有做錯會丟資料或無法復原的規則留強調,旁邊附理由;其他改成「不做 X,因為 Y」。vault-maintain 最後只剩兩條:清 worktree 時不加 --force(會連未 commit 的工作一起刪)、I7 永不自動修(那些分支是 Inbox 擷取的唯一副本)。
Claude 自己也講了兩個限制,我照錄:門檻「每 100 行 8 個」是它依「只留少數幾條」的原則定的,不是官方數字;降強調之後表現會不會更好,這個 vault 還沒量過,目前只是照官方建議的預期。
實作
/claude-doctor 的流程
.claude/skills/claude-doctor/SKILL.md 分成六步:
| 步驟 | 做什麼 |
|---|---|
| Phase 0 | 兩個 subagent 研究官方與社群做法,只回報跟上一版的差異 |
| Phase 1 | claude doctor+稽核腳本 config_audit.py |
| Phase 2 | subagent 逐個讀 CLAUDE.md 與 SKILL.md,找語氣與過時步驟 |
| Phase 3 | 報告:新做法/應改/建議/通過 |
| Phase 4 | 重生 README 的自動產生區塊,蓋稽核戳記 |
| Phase 5 | 寫 log、開 PR |
能用規則判斷的全部交給腳本,C1 到 C8:skill 表跟資料夾一致、frontmatter 欄位合法、檔案行數、寫死的模型名稱、hook 有沒有接好(--smoke 會實際餵一筆假輸入給 hook,確認寫根目錄 log.md 會被擋下)、強調密度、README 是否過時、權限規則指向的路徑還在不在。
驗證方式是在 scratchpad 複製一份 vault,故意弄壞四個地方:刪掉 skill 表的一列、加一個不存在的 frontmatter 欄位、讓 hook 指向不存在的腳本、把戳記改成 67 天前。四個都被對應的檢查抓到。
navi 的稽核提醒
判斷邏輯獨立成 hooks/audit.ts,方便寫測試:
export function auditNote(readme: string, model: string, nowMs: number): string | undefined {
const m = readme.match(STAMP)
if (!m) return '設定未稽核(/claude-doctor)'
const [, audited, date] = m
// 只有別名(opus/sonnet)沒有版本號時無法判斷是否換過,略過模型比對
const cur = normalizeModel(model)
if (/\d/.test(cur) && cur !== normalizeModel(audited)) {
return `模型已換 ${cur},設定未稽核(/claude-doctor)`
}
const days = Math.floor((nowMs - Date.parse(`${date}T00:00:00`)) / 86_400_000)
if (days > AUDIT_MAX_DAYS) return `設定 ${days} 天未稽核(/claude-doctor)`
return undefined
}
STAMP 比對的是 README 尾端 <!-- claude-doctor: model=... date=... --> 這行。normalizeModel 會把 claude-opus-5-5[1m]、帶日期的快照版本、顯示名稱「Opus 5.5」都轉成同一個 opus-5-5,同一個模型換個 context 長度不算換模型。
掛到 navi 上的部分:
// 換模型(/model)後的第一個 turn 就會反映;不 await,不拖慢 turn
on('turn.start', async ($, e, next) => {
void checkAudit($)
return next(e)
})
async function checkAudit($: $) {
const note = await auditCheck($, await $.clock.now())
if (note !== auditMsg) {
auditMsg = note
await refresh($)
}
}
checkAudit 不 await,turn 不會因為讀 README 而卡住;結果跟上次一樣就不重畫。目前模型由 $.session.model() 取得,這是 mod API 本來就有的方法,不需要另外接事件。claude plugin test 跑 6 個測試全過:換模型、[1m] 後綴、只有別名、67 天前、沒有戳記。
CLAUDE.md 瘦身
照研究結果搬了三類東西出去:
- 多個 session 共用主 checkout 時的 fallback 協定(8 條)和 hook 的細節,搬進
.claude/README.md,CLAUDE.md 只留一行指引 - wiki 頁的 frontmatter 規格、書頁連結的路徑限定、圖片放置,搬進
.claude/rules/wiki-format.md,加上paths:讓它只在編輯 wiki、raw、logs、hubs 時載入 - 「為什麼 log 要切成月檔」這類歷史理由,改成 HTML 註解,人看得到、不佔 context
結果是 251 行、19,278 字變成 179 行、11,313 字。
10 個 skill 降強調
vault-maintain 由 Claude 直接改,另外 9 個分給 5 個 subagent 平行改。這種批次改寫最怕「順手」改到內容,所以收回來之後用腳本逐檔比對:frontmatter、行數、所有 code span、所有數字,都要跟改之前一模一樣。全部通過,只有兩處差異,一處是新加的理由裡多了一個 `TODO.md`,一處是原本藏在粗體裡的數字現在被算到了,都不是內容變動。

合計 476 個強調標記降到 41 個。剩下的多半在 skill 的輸出範本裡,那是 skill 要印出來的格式本身,拿掉會改變輸出,就留著。
踩到的坑
強調密度把 glob 當成粗體
剛搬出去的 .claude/rules/wiki-format.md 被標成強調密度偏高:36 行裡 3 個標記。可是這個檔裡根本沒有粗體。
🟡 C6 .claude/rules/wiki-format.md emphasis density 8.3/100 lines (3 markers in 36 lines)
一查,三個標記是 frontmatter 裡的 paths:"**/wiki/**"、"**/raw/**"、"**/logs/**"。兩個星號夾著文字,正好符合粗體的正規式。
第一次修,是在計算前把 frontmatter 和程式碼區塊整段刪掉。重跑之後反而多冒出三個警告:CLAUDE.md、claude-doctor、medium-cover 都超標了。原因是刪掉程式碼區塊也刪掉了那些行,分母變小,密度就變大。
第二次改成「把內容換成同樣數量的空行」:
blank = lambda m: "\n" * m.group(0).count("\n")
visible = re.sub(r"<!--.*?-->", "", text, flags=re.S)
visible = re.sub(r"\A---\n.*?\n---\n", blank, visible, flags=re.S)
visible = re.sub(r"```.*?```", blank, visible, flags=re.S)
visible = re.sub(r"`[^`\n]*`", "", visible)
這樣排除的東西不算進分子,行數還是照原檔算。之後的做法:要從分子排除的東西,不要連帶改到分母。
subagent 回報的「問題」,本身判斷錯
降強調時 Claude 請每個 subagent 順便列出「看起來過時或矛盾、但沒動」的內容。收回來二十幾條,大部分是真的,例如 book-review 寫「status → 2」,但欄位定義的值是「閱讀完畢」這種文字。
其中一條說 art-review 用裸名 [[{展名}]] 有歧義,因為 art/raw 和 art/wiki 有同名檔,應該像書一樣加路徑。這條是錯的:wiki-format.md 寫明了只有 books 的 raw 和 wiki 都是有效的連結目標,其他域的 raw 是被 wiki 取代的沉澱,裸名照慣例指向 wiki 頁。
不過這條錯誤的回報並非沒價值。它讓 Claude 回頭發現 vault-maintain 的 E4 寫的是「裸名命中多個同名檔就是違規」,跟 wiki-format 的慣例確實矛盾,之後跑 vault-maintain 可能會誤報 art 頁。subagent 給的是線索,不是結論;採用之前要回到規則本身對一次。
小結
- 新增
/claude-doctor:每次先研究、再用腳本稽核 C1–C8、報告只建議不改設定、重生 README 並蓋稽核戳記 - 每月 1 號 09:00 的 routine 自動跑,第一次在 11/1
- 換模型與 35 天未稽核的提醒放在 navi 狀態列,在 session 與 turn 開始時檢查;原本的兩支提醒 hook 移除,守門 hook 不動
- CLAUDE.md 從 251 行瘦到 179 行,wiki 寫作格式拆成 path-scoped rules;5 個手動才用的 skill 加了
disable-model-invocation,不再出現在每輪的 skill 清單裡 - vault-maintain 強調標記 81 → 2,另外 9 個 skill 395 → 39
已經知道、還沒做的有兩件:book-review 抓書封寫死了一個這個 session 沒有的瀏覽器工具(已記進 TODO.md);vault-maintain 的 E4 跟 wiki-format 的同名慣例矛盾(還沒決定怎麼改)。降強調的效果,要等之後用一段時間才知道。
