avatar
蕭富云行動應用開發者 · Android & Flutter

把個人設定當產品養 — 不是因為它有測試,是因為它會壞

2026-08-31 · 13 min read

說明: 本文由 AI(Claude)根據我的初始筆記與想法完成。

前兩篇談的是一輪工作怎麼跑、以及怎麼量它有沒有守規矩。跑那個迴圈、量那個分數的東西,其實是一個 git repo:依它自己的 README 數,一百二十來個 hook、五十三個技能、十九份規則文件。

它已經大到會壞了。常聽到用來形容這種設定的說法是:hooks 有測試、規則有 lint、一份來源渲染兩個 runtime。 我派了一個唯讀的研究 agent 去查了一遍——三句話,每一句都只對了一半。

先講結論:把它當產品養,不是因為這三句宣稱多準,是因為我把它會怎麼壞,寫在它壞掉之前。這篇先驗證這三句,再談真正撐住它的東西。

宣稱一:hooks 有測試

這句最接近事實。hook 的測試沒有框架,就是一個手寫的 check 函式,數通過與失敗,印一行 ── N passed, M failed ──,測試檔放在 hook 旁邊。抽樣十二個 hook,七個有測試。

有測試的那些,釘住的都是真的出過事的東西:

寫這篇的時候有個小插曲:我派去讀 repo 的研究 agent,被 repo 自己的守衛擋在門外——站在別的專案裡的 session 連列目錄都不行,只能一個檔案一個檔案地讀。守衛有沒有用,這算一次意外的驗證。

但「有測試」離「有關卡」還有一段距離:沒有 CI。 測試是真的、寫得也好,但沒有東西在 push 或 merge 時跑它們。「hook 有測試」目前的意思是「有人會跑」,不是「沒過就進不去」。

宣稱二:規則有 lint

這句對了一半。渲染器對技能檔的 front matter 要求嚴格到只准兩個欄位,多一個就安裝失敗——這是真的 lint,而且是每次安裝都會跑的那種。hook 本身也有一道 shellcheck 的關卡。

但範圍很窄:lint 只看 shell 語法,而且只看改過的檔案。 腳本裡自己承認:有一百來個 hook 是 shellcheck 進來之前寫的,全掃會被舊發現淹沒。

真正對不上的是「規則」這兩個字。規則的散文本身沒有 lint。 主設定裡明文禁止權限清單出現絕對路徑,靠的是審查時的判斷,不是腳本。一條「靠判斷執行的規則」,本身就是一條沒被執行的規則。

宣稱三:一份來源渲染兩個 runtime

這句是驗證下來最紮實的一句。同一棵來源樹會渲染成兩個 runtime 的設定:一個給 Claude Code,一個給 Codex。渲染不是複製,是改寫:

/name               → $name
AskUserQuestion     → the structured user-input tool
model: sonnet       → the appropriate current capability tier
~/.claude-personal  → ~/.codex-personal

還有一層轉接:Codex 的 patch 呼叫被翻成一個個檔案的寫入,好讓同一批守衛 hook 原封不動地在兩邊跑

我一開始只是想讓兩邊都能用。做完才發現它的真正價值:移植的時候會壞掉的東西,就是不小心長成某個 runtime 形狀的東西。 一條規則若只在一邊成立,它多半不是規則,是習慣。

順帶一個副產品:兩個 runtime 的記憶互相看不見,所以工作流程的狀態——路由、偏好、游標——住在一個中立的目錄裡,兩邊共用,但不共用登入和對話歷史。而指向記憶備份倉庫的那個指標,刻意放在記憶系統之外——它負責引導記憶,所以不能住在它要引導的東西裡面。

不過兩邊還是不對等:Codex 那邊比較薄。 它的指令升級策略只有三條,Claude 那邊的權限清單約一百五十條。

三句宣稱之外,真正撐住它的東西

三句話查完,真正讓它像產品的,其實是宣稱裡都沒提到的幾個動作。

第一個最無聊,也最重要:repo 是唯一的來源,每一個活著的設定目錄都是它的複本。 安裝腳本把同一份檔案逐位元組複製進每一個根目錄——所以你在複本裡讀到的規則,跟在來源裡讀到的一模一樣,一模一樣到你分不出自己站在哪裡。

分不出來就會出事:改了複本,下一次安裝就被靜靜蓋掉。所以規則裡有一張表,寫明誰可以動什麼:

目標一個 session 可以改嗎
專案的記憶檔隨意——記憶就是拿來寫的
規則文件、技能先提案,等人點頭
主設定、權限、hooks提案之外,還要說出波及範圍——這些會載進每一台機器的每一個 session
活著的複本永遠不改。下一次安裝就被蓋掉

「說出波及範圍」那一格是產品思維的起點。一個只有你自己用的檔案,也分得出哪一行改了會炸到所有地方。

第二個是教訓怎麼進規則書。每次出事就加一條規則,是設定檔腐爛最常見的路。所以教訓有它自己的升遷流程:先落在記憶裡——便宜、立刻、不用誰同意;只有在同一個錯誤被它糾正了兩次、或者它在別的專案也適用的時候,才升進規則文件,升遷本身是提案;升上去之後,原本的記憶縮成一個指標。而且要落在擁有那個關切的檔案裡——風格規則進風格文件,委派規則進委派文件——不是塞成某個技能裡的一條特例。容器本身就是重點。

第三個是把壞法先寫下來。維護文件裡有一節叫「已知的退化模式」。我覺得這一節是整個 repo 最像產品的地方,因為它承認這東西會壞,而且知道會怎麼壞

  1. 規則堆積——每次事故加一條,載入的份量脹到 session 開始略讀。
  2. 兩本密碼本各自漂移——主設定、規則文件、外掛技能都在講同一道閘,慢慢講成三個版本。防法:每條規則只住在一個地方,其他地方指過去。
  3. 散文與 hook 分家——某一節描述一個行為,執行它的 hook 改了,那一節開始說謊。防法:有 hook 在做的事,散文就不再重複;散文只寫 hook 沒帶的部分,並點名那個 hook。
  4. 名冊過期——模型名、旗標、技能名靜靜腐爛。防法:名冊型的內容一律打上日期,引用前先驗證。
  5. 儀式性合規——估量、驗收、審查都渲染了,形式在、內容空。防法:不可能失敗的東西不是檢查,刪掉或磨利。

第五條有一個很具體的推論:每回合重新注入的那個審查提醒,點名的是「要派一個 agent」,不是「要寫下那行判決」。因為如果提醒只催那一行字,模型學會的就是把字打出來——提醒永遠不能讓指標比它代表的行為更容易達成。

第四個是一個拒絕被服從的門檻。載入的份量該有個上限。舊版寫的是三萬五千字。後來維護文件自己把它拆穿了:那個數字不是推導出來的,它是寫下那一天的檔案大小(2026-07-08,實際 35,532)——一個現狀穿著預算的衣服。新的四萬五千字被明白地標成「聞味道的門檻,不是上限」:超過了就去讀檔案,問哪幾條規則不再值得它的份量;真的撞到了,重新量,不要服從。

修剪要看證據,不看字數。證據來自上一篇那個儀表板:份量從 29,008 漲到 43,196 的那段時間,整體合規率從 52% 升到 73%。文件把這個讀成「預期的傷害沒有出現」——並且明白拒絕讀成「長大有幫助」,因為同一段時間裡分母被修了三次,而分母的修正本來就會抬高比率。

同一節還記了一次失手:一次修剪預測省 1,600 字,實際省了 371,因為它量的是那一節的大小,不是節裡面重複的那一塊。量那個真的會被刪掉的東西。

誠實的帳

把三句宣稱和它們各自的洞,收成一張表:

項目現況
CI沒有——測試是人的紀律,不是關卡的紀律
lint 範圍只到 shell 語法、只掃改過的檔案;規則的散文本身沒有 lint,絕對路徑禁令靠審查判斷
兩個 runtime渲染是真的,但 Codex 的指令升級策略只有三條,Claude 的權限清單約一百五十條
repo 內的 session全域指令與專案指令逐位元組相同,被收兩次費——文件的原話是「記錄下來,尚未解決」

有已知問題清單的還是產品;沒有的,是嗜好。

一句話收尾

你不需要一份這麼大的設定。但有三件事不用帶著 repo 也能帶走:來源只有一個,複本永遠不改;在它壞掉之前,先寫下它會怎麼壞;一個數字如果只是寫下那一天的大小,就老實說它是。