Blog
寫下來,然後呢?
想存筆記的時候,發現早就存過了
踩了一個坑。修完之後想「這個值得記下來」,打開筆記庫準備新增。
搜尋標題,跳出一篇一模一樣的。自己寫的,兩週前。
這件事付了兩次錢:第一次是踩坑,第二次是把它寫下來。而第二次完全沒有省下第一次。
筆記庫裡有 325 篇。那 325 篇裡,有多少是已經忘了自己寫過的?
筆記是被動的
一篇筆記要幫到你,需要三件事同時成立:
- 你寫過它
- 在需要的當下,你想起來你可能寫過
- 你去查了
第 1 項已經做得很好。問題全部出在第 2 項。
而第 2 項有一個殘酷的性質:你想不起來的時候,正是最需要它的時候。
記得「我寫過這個坑」的人,多半也記得怎麼避開,根本不需要查。真正需要筆記的場合, 是你完全沒意識到自己正走進一個舊坑——那個狀態下,不會有人想到要搜尋。
筆記能解決「查得到」的問題,解決不了「不知道要查」的問題。
以為已經處理過了
這個問題三個月前就想過,也寫了一支程式來解決。
它掛在 AI 助手的「使用者送出訊息」這個時機:每次送出,抓出句子裡的關鍵詞, 去筆記庫的索引比對,把最相關的幾篇塞進 AI 的上下文。
就算沒想到要查,相關的舊筆記也會自己浮上來。
設計是對的。它也真的在跑。
實測一句話:「確認一下 merge 有沒有成功」
精準命中那篇兩週前的筆記,排第一。
但它在工作的地方是關掉的
同一句話,換個位置再測一次。
工作目錄在家目錄底下——命中。
工作目錄在 ~/Projects/某個專案 底下——完全沒有反應。
原因在程式的第 23 行:
SKIP_DIRS = [os.path.expanduser("~/Projects")]
工作目錄在 ~/Projects 底下時,整個召回機制直接跳過。
而所有專案都在 ~/Projects 底下。
筆記召回機制
家目錄、筆記庫 → ✅ 會召回
~/Projects/* → ❌ 跳過
└── 99% 的工作時間都在這裡
一個解決「想不起來」的機制,被關在唯一會想不起來的地方之外。
當初關掉它是有道理的
發現的當下很想罵人,直到去看了成本。
那支程式塞進上下文的,是筆記索引裡的整段摘要。量一下索引檔:
| 字元數 | |
|---|---|
| 每筆索引平均長度 | 489 |
| 最長的一筆 | 2,238 |
| 一次注入 5 筆 | 約 2,445 |
每一次送出訊息都要付 2,445 字元。一場三小時的開發對話送出五十次訊息, 就是十二萬字元,塞的還多半跟當下無關。
所以那個決定完全合理:專注寫程式的時候,不需要每句話都被灌一堆舊筆記。
問題出在關掉的範圍。 為了省資源,最省事的做法是整片關掉,而那片剛好是全部。
修法:把兩件事拆開
卡住的地方在於,「用來比對的文字」和「塞進上下文的文字」被當成同一份東西。
它們的需求剛好相反:
- 比對要越多字越好——字越多,越容易命中
- 顯示要越少字越好——每次都要付錢
拆開之後就沒有取捨了:用完整的索引行去比對,只把標題、一小段摘要和檔名塞進上下文。
索引行(489 字元)
│
├──→ 拿去比對關鍵詞 ← 用完整版,命中率不變
│
└──→ 縮成「標題|80 字摘要 → 檔名.md」(約 150 字元)
└──→ 這個才塞進上下文
改完的數字:
| 每個訊息注入 | |
|---|---|
| 改之前 | 約 2,445 字元 |
| 改之後 | 約 430 字元 |
省了八成,命中的是同一批筆記。省下的空間讓 SKIP_DIRS 可以清空,
召回在所有地方都打開了。
改完不宣稱成功,拿真的會講的話再測一次:
- 「確認一下 merge 有沒有成功」→ 428 字元,正確那篇排第一 ✅
- 「用 git log 看一下最近的 commit」→ 550 字元,三篇相關的 ✅
剩下的缺口
同一批測試裡,有一句話完全沒有召回:
「幫我 push 上去」 —— 太短,沒有足夠特別的詞可以比對。
這句話暴露了這個機制的天花板:它綁在使用者說的話上面。而危險發生的時刻, 常常在說完話之後很多步——當工具真的要執行那個指令的時候。
說「幫我 push 上去」,AI 接下來會跑十個指令,坑在第七個。而召回只在第 0 步發生過一次。
所以再加一層:在指令執行前攔一下。這一層跟使用者說了什麼無關,它看的是即將發生的動作。
以那個 git log 的坑為例(那台機器上的 git log 會被代理工具改寫,預設濾掉
merge commit,所以拿它判斷「合併成功了沒」會得到錯的答案):
#!/usr/bin/env bash
input=$(cat)
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty')
# 比對前先把引號內容挖掉,否則 `grep "git log"` 會被誤判
bare=$(printf '%s' "$cmd" | sed -e 's/"[^"]*"//g' -e "s/'[^']*'//g")
# 同一段裡出現 git … log 才算([^;&|]* 擋住跨管線的誤判)
printf '%s' "$bare" | grep -qE '\bgit\b[^;&|]*\blog\b' || exit 0
# 已經明確處理過的就別囉唆
printf '%s' "$bare" | grep -qE '(--(no-)?merges|rev-parse|ls-remote)' && exit 0
# 一個對話只提醒一次
sid=$(printf '%s' "$input" | jq -r '.session_id // empty')
marker="${TMPDIR:-/tmp}/.gitlog-reminder-${sid}"
[ -e "$marker" ] && exit 0
: > "$marker"
jq -nc '{systemMessage: "提醒:git log 預設濾掉 merge commit——判斷「有沒有發生」請改用 rev-parse / ls-remote"}'
三個設計決定,每一個都是為了讓它活得久:
不擋,只提醒。 git log 絕大多數用途是正當的。會擋住正常工作的提醒,最後一定被關掉。
一個對話只講一次。 同樣的理由。提醒一次足以建立警覺,第五次只會讓人麻木。
先挖掉引號再比對。 第一版沒做這件事,cat notes.md | grep "git log" 也會觸發。
假陽性是這類提醒最大的敵人——它讓人覺得這東西不準,然後關掉它。
三層,以及什麼時候該往下搬
寫下來的知識可以放三個地方,差別在誰負責在對的時刻把它拿出來:
| 層 | 誰執行 | 什麼時候失效 |
|---|---|---|
| 筆記 | 未來的你 | 得先想到要查 |
| 自動召回 | 程式,在你說話的時候 | 你的話裡沒有可比對的詞 |
| 指令攔截 | 工具,在動作發生前 | 只有寫錯規則才失效 |
由上往下,可靠度遞增,寫起來也遞增麻煩。所以不必每一課都做到最底層。
判準只有一句:同一個坑踩第二次,就往下搬一層。
第一次踩,寫筆記就好,它可能再也不會發生。 第二次踩,代表這件事超出記得住的範圍,該交給機器了。
這個判準有一個漏洞
「踩第二次」聽起來很清楚。但要知道這是第二次,你得先記得第一次—— 而這篇文章的前提正是你不會記得。
老實講,這次之所以知道是第二次,是因為想存筆記時發現早就存過了。那是撞上的, 不是判準在運作。
所以它比較像一個事後的分類原則,不是事前的觸發條件。
真正有機會自動告訴你「這是第二次」的,只有中間那層——它比對的是你現在說的話 和你以前寫過的東西。所以召回層不是備援,它是三層裡唯一會主動指出「你踩過這個」的一層。
這一切假設你動得了工具鏈
上面說的 hook、常駐規則、lint 設定,前提是你有權改它們。
在公司裡,多數人不能動 CI、不能裝 hook、不能改共用的設定檔。那種情況下能做的比較有限: 把規則寫進團隊的 code review 檢查表、寫進 PR 模板、或是在自己的編輯器裡加一條 lint。
這些都比 hook 弱,因為它們仍然要靠某個人在對的時刻看一眼——而那正是第一層失效的原因。
這是個真實的限制,這篇文章沒有解法。
最諷刺的部分
診斷這整件事的時候,翻到筆記庫裡的一篇。三個月前寫的,標題是 〈guardrail 要檢查「有沒有做過決定」〉。
裡面有這麼一段:
指令層與保證層是兩種東西:前者由模型自己權衡,會被長對話稀釋、被當下任務壓過、 單純漏看;後者由工具執行,只有寫錯才失效,不會「忘記」。
還有一句:
規則寫在上下文裡了,仍然直接做出了錯的事。
這篇文章的結論,三個月前就寫完了。
那篇解釋「為什麼筆記救不了你」的筆記,本身就是一篇沒有救到我的筆記。
需要一個證明來說明為什麼知識要往下層搬的話,這就是了。
寫下來的那一刻,你確實卸下了什麼
寫筆記讓人有一種踏實感——這件事處理過了,記下來了。
那份踏實感是真的,而且量得出來。
有一組睡眠實驗室的研究,讓受試者在睡前寫一份清單,再用儀器量他們多久睡著。 寫「接下來幾天要做的事」那組,平均 15.8 分鐘入睡;寫「前幾天已經做完的事」 那組,25.1 分鐘。
兩組都在寫清單。差別只有一個:一組寫的是還沒處理的事,另一組寫的是已經處理完的。 腦袋只對前者鬆手。
還有一個細節:待辦清單上的項目寫得越多,入睡越快。而已完成清單剛好相反, 寫得越多反而睡得越慢。
所以讓人放鬆的是「還沒處理的事被安排出去了」這件事。
但那份放鬆是預支來的
腦袋願意鬆手,條件是它相信這件事會回來找你。
寫進行事曆有用,因為行事曆附帶一個東西:時間到了它會響。 你之所以能安心 忘記下週二的門診,是因為手機會在對的時候把它端到你面前。
筆記沒有這個部分。它只有前半段(把東西從腦袋搬到硬碟),缺的是後半段 (在對的時刻自己出現)。
於是你付出了信任,卻沒有拿到對應的東西。 腦袋以為這件事已經被安排好, 所以停止提醒你——而實際上沒有任何機制會在你需要的那一刻把它拿出來。
風險一點都沒有降低。坑還在原地,下次照樣會踩。改變的只有那份不安, 而那份不安本來是唯一會讓你警覺的東西。
所以真正的問題在於:寫完之後,誰負責在對的時刻讓它響。
兩條真的有用的路
一篇筆記要改變什麼,只有兩條路走得通。
一、內化成你的預設動作。 下次遇到類似情境,手會自動先做那個檢查—— 這比「記得有這回事」深一層。它需要刻意回頭讀、刻意練習。 而多數人(包括我)寫完就不會再翻,所以這條路的失敗率很高。
二、搬進工具裡。 hook、CLAUDE.md、型別系統、測試、lint 規則——
任何「不靠你記得也會發生」的東西。
第二條路的門檻其實比第一條低。寫一支二十行的 hook,比養成一個新習慣容易得多, 而且它不會有心情不好的一天。
最後
現在寫完一篇踩坑筆記,會多問一句:
這一課,我打算靠誰在對的時刻把它拿出來?
答案如果是「靠我自己記得」,那它高機率會再發生一次。