症狀(總管 2026-08-12 實撞):寫暫存腳本進 scratchpad
(/private/tmp/.../scratchpad/foo.py)被 sdd-guard.sh 攔下,訊息印出字面的
「/nonexistent/3-specs/ 下找不到任何 SDD」。
兩個洞:
- 洞 A:scratchpad 不在任何 git repo 裡,卻被當成「repo 裡的 code 變動」誤判
需要 SDD。改成先問 path_in_git_worktree()(見 hooks/lib/path-resolve.sh):
不在任何 git repo 裡 → SDD 天生管不到,直接放行,不必先猜專案根。
這個檢查放在 $_root 的 case 分岔之前、對兩邊都適用——第一版只放進「專案外」
分支,被本次新增的 hooks/tests/sdd-guard.test.sh 抓到一個不對稱漏洞(cwd 剛好
等於 scratchpad 祖先目錄時會漏判),改成統一檢查後修掉。
- 洞 B:舊版用內部 sentinel `/nonexistent/3-specs` 重用既有的擋下路徑,但這個
假路徑被直接印進使用者看到的訊息。改用 RESOLVED 旗標記解析成不成功,訊息
改用人話描述原因,不洩漏假路徑。
fail-closed / fail-open 的判準(票上明確要求回答,不能各憑運氣):
真的落在某個 git repo 裡、但那個 repo 沒有 3-specs(或沒有 active SDD)→
仍然 fail-closed(擋)。理由:這道閘存在的目的就是防止「沒有 SDD 卻能動
code」,把「判斷不出來」直接放行,等於把環境跑歪(cwd 被切走、
$CLAUDE_PROJECT_DIR 沒設)悄悄變成「這道閘關掉了、且沒人知道」——silent
bypass 的代價遠高於多打一次確認。#22 紅線亦明寫「不要把閘改成解析失敗就
放行」。
順手修的殘留 cwd 依賴:SPECS_DIR 的預設值原本是相對路徑
「system-dev/docs/3-specs」,專案內迴圈找不到時會被拿去跟 hook 執行當下的
cwd 兜;改成絕對路徑 $_root/system-dev/docs/3-specs。
同時修 ADR-0001(ISEP 自建 wiki):標題與內文原本會讓人誤解成「ISEP plugin
裝到哪個 repo,就會在那裡自建一份 wiki」,但實際查證(marketplace.json 只宣告
hooks/commands/skills、README 明文排除 wiki/docs、hooks 一律用
${CLAUDE_PLUGIN_ROOT} 讀自己不是寫別處)並非如此——那份 wiki 只是 ISEP 這個
repo自己的開發歷史,跟裝 plugin 無關。唯一真的會在某 repo 建 wiki 的
scripts/install.sh 是 system-dev-template 的獨立安裝器殘留,要手動執行,
作用對象是 cwd 不是「plugin 裝到的地方」——這多半是誤解的真正來源,已在
ADR 的「常見誤解」段說明。
驗證:
- 造出 08-12 原始事故情境(cwd=InkStoneCo、CLAUDE_PROJECT_DIR 未設、寫
scratchpad),修前擋(印 /nonexistent)、修後放行——實測輸出見票留言。
- 造出「真的在 git repo 裡但沒有 3-specs」情境,修後仍擋、訊息不含
/nonexistent。
- 新增 hooks/tests/sdd-guard.test.sh:8 案例全過(洞 A/洞 B/fail-open
陷阱/單一活性違反/恰好一份 active/改文件放行)。
- 既有六套 scripts/test-*.sh 全過,無退步。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.8 KiB
ADR-0001:ISEP 這個 repo 自己維護一份 wiki(記 ISEP 自己的事,跟「裝 plugin」無關)
- 狀態:已採納(決策未變,本次僅修訂標題與內文的誤導處,見文末「常見誤解」)
- 日期:2026-08-20
- 票:
inkstone/ISEP#3(原案)、inkstone/InkStoneCo#22(本次修訂)
先講結論,避免讀到一半就會錯意
本 ADR 談的「wiki」,是 inkstone/ISEP 這個 git repo 自己的開發歷史——
跟其他任何 repo(InkStoneCo、arcrun…)在自己 repo 底下放一份
system-dev/wiki/ 記自己的事,是同一種、完全獨立的東西。
🔴 這件事不會發生:把 ISEP 這個 Claude Code plugin「裝」到別的 repo(本機或雲端的
Claude Code session 啟用這個 plugin),不會在那個 repo 裡多寫出任何檔案,
更不會在那裡生出一份 system-dev/wiki/。「plugin 裝到哪、wiki 就跟著長在哪,
所以每個 repo 都會有兩份」是誤讀——見文末「常見誤解」段的查證。
背景
ISEP 是獨立 repo,對外扮演的角色是「環境」(hooks/commands/skills/scripts,
README.md「裝什麼」段列了清單,白紙黑字排除 wiki//docs//_archive/——
那些是「知識」不是「環境」)。但 ISEP自己也是一個在持續開發的 repo:它有自己的
決策(例如這份 ADR 本身)、踩過的坑、現在的狀態。過去要查「ISEP 這裡為什麼這樣設計、
之前討論到哪」,只能回頭 clone InkStoneCo 頂層知識庫,多一層跳轉,而且 ISEP 自己的
開發細節並不天然屬於 InkStoneCo 頂層(那裡管的是跨專案決策,不是單一 repo 的施工細節)。
決策
inkstone/ISEP 這個 repo 自己建立 system-dev/wiki/,骨架取自
inkstone/system-dev-template 的 wiki template(三層 + 標籤橫切:INDEX.md/
TAXONOMY.md/status.md/mistakes.md/principles.md/cards/<bucket>/),
照它的規約裝,不自創格式。
這份 wiki 只在 ISEP 這個 repo 的 git 歷史裡,跟著 git clone inkstone/ISEP 走;
它不是 plugin payload 的一部分(plugin.json/marketplace.json 只宣告
hooks//commands//skills/,任何 Claude Code session 啟用這個 plugin 時載入的
也只有這些),所以其他 repo 啟用 ISEP plugin 時,這份 wiki 不會、也無法出現在那裡。
紅線:這份 wiki 只記 ISEP 自己的事。不把 InkStoneCo 頂層 wiki 的內容複製過來——
複製即 fork,fork 即漂移,跟「真身薄殼合一」(見 cards/isep/真身薄殼合一.md)要解的病
是同一種結構性錯誤,只是對象從 hook 換成知識庫。
後果
- 好處:接手 ISEP 這個 repo 的 session,在它自己的 checkout 裡就查得到它自己的歷史, 不必先 clone 別的 repo。
- 代價:多一份骨架要維護(跟 InkStoneCo 頂層、以及其他裝了 template 的子 repo 一樣, 各自一份、各自維護,不互相複製)。
- 邊界:跨專案的決策、鐵律、部署架構全局,仍然只在 InkStoneCo 頂層記錄,ISEP 不重複。
常見誤解,與查證
誤解:「ISEP 這個 plugin 裝到哪個 repo,就會在那個 repo 裡自建一份 wiki, 於是每個裝了 ISEP 的 repo 都會多出兩份(自己的 + ISEP 幫它建的)。」
這不是實際行為。查證如下(2026-08-20 實查,不是抄口述):
.claude-plugin/marketplace.json把整個 repo 根目錄("source": "./")宣告成 plugin 來源,Claude Code 依慣例目錄(hooks/、commands/、skills/)載入內容;README.md「裝什麼」表列出的也正是這幾個目錄(外加scripts/供它們呼叫)—— 沒有任何一項是 wiki 或 docs。啟用這個 plugin,載入的是 hook 腳本的路徑 (${CLAUDE_PLUGIN_ROOT}/hooks/*.sh)、command/skill 的定義;這個載入過程本身 不涉及「往目前工作的 repo 寫入任何檔案」——它是讀,不是寫。README.md「裝什麼」段明文把wiki//docs//_archive/列在「不放」—— 這條界線本來就是刻意畫的(環境 vs 知識分離),不是本 ADR 才立的。- 全部 hooks 對「自己這支腳本」的路徑一律用
${CLAUDE_PLUGIN_ROOT}(不用$CLAUDE_PROJECT_DIR,見README.md「路徑規約」段)——這條規約本身就代表 hook 的邏輯設計上就是「讀 plugin 自己的檔案」,不是「往目前工作的 repo 寫東西」。 - 唯一一支「真的會在某個 repo 裡建出 wiki」的腳本是
scripts/install.sh—— 但它是system-dev-template的獨立安裝器(不是 ISEP 的功能),要人或 AI 手動執行 一次才會動作,且動作對象是執行當下的 cwd,不是「ISEP 被啟用的地方」。 它會混進這個 repo,是搬家時帶過來的殘留(docs/governance/DIVERGENCE-v0.5.0-to-v0.6.0.mdA6 節已標記它是待清理項,跟.claude-plugin宣告的 plugin 功能無關)。 這支腳本的存在,多半就是本誤解真正的來源——它看起來像「ISEP 會建 wiki」, 但觸發方式(手動跑一次)與作用對象(cwd,不是「plugin 裝到的地方」)都跟 「裝 plugin 就自動建」完全不同。
⇒ 結論:本 ADR 的「wiki」只指 ISEP 這個 repo 自己 checkout 裡的那一份,
跟其他任何 repo 有沒有、要不要各自裝一份 wiki(那是它們自己的 /wiki-init 決定),
兩件事互不影響、也不會因為裝了 ISEP plugin 而自動被牽動。
相關
cards/isep/真身薄殼合一.mdcards/isep/repo邊界與紅線.mdcards/isep/hook路徑規約.mdinkstone/InkStoneCo#22(本次修訂的來由:leo 讀完舊版誤解成「plugin 裝到哪、 wiki 就跟著建到哪」)