<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>BDGG</title><link>https://yongrui0402.github.io/BDGG_blog/</link><description>BDGG 的技術筆記、課程與學習心得、專案紀錄與隨筆。</description><language>zh-Hant-TW</language><copyright>BDGG · 文章以 CC BY 4.0 授權</copyright><lastBuildDate>Mon, 05 Oct 2026 17:09:00 +0800</lastBuildDate><atom:link href="https://yongrui0402.github.io/BDGG_blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Claude mod 簡易測試與說明</title><link>https://yongrui0402.github.io/BDGG_blog/learning/claude-mod-quick-test/</link><pubDate>Mon, 05 Oct 2026 17:09:00 +0800</pubDate><guid isPermaLink="true">https://yongrui0402.github.io/BDGG_blog/learning/claude-mod-quick-test/</guid><dc:creator>BDGG</dc:creator><category>學習筆記</category><description>用一個 20 行的 mod 擋下 Claude Code 對建置成品的編輯，並用三種方式確認它真的有作用。</description><content:encoded>&lt;p&gt;Claude Code 可以用 &lt;strong&gt;mod&lt;/strong&gt; 改變它自己的行為：在提示框上面多一列資訊、開一個即時面板、加一個斜線指令，或是在它呼叫工具之前攔下來檢查。這篇用一個很小的 mod 走一遍「寫出來、檢查、實際載入」，並記下每一步真正看到的輸出。&lt;/p&gt;&#10;&lt;p&gt;測試環境是 Linux 上的 Claude Code 2.1.289，日期 2026-10-05。這個 mod 是我請 Claude Code 寫的，下面的指令也都是它在同一個工作階段裡實際跑的，輸出照貼，只把過長的絕對路徑縮短。&lt;/p&gt;&#10;&lt;h2 id="mod-是什麼"&gt;mod 是什麼&lt;/h2&gt;&#10;&lt;p&gt;mod 是一個資料夾，裡面是一個帶有「函式 hook」的外掛（plugin）。最少三個檔案：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;public-guard/&#10;├── .claude-plugin/&#10;│ └── plugin.json # 名稱、版本、一行說明&#10;└── hooks/&#10; ├── hooks.json # 指出 hook 模組是哪一個檔案&#10; └── register.ts # hook 模組本體&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;register.ts&lt;/code&gt; 匯出一個 &lt;code&gt;register&lt;/code&gt; 函式，用 &lt;code&gt;on(事件, 條件, hook)&lt;/code&gt; 把函式掛到事件上。每個 hook 都收到同樣的三個參數：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;參數&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;是什麼&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;$&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;引擎的介面：顯示、工具、檔案、時鐘等等，模組要碰外面的東西都得經過它&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;e&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;這次事件的輸入，例如工具名稱與參數&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;next&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;呼叫 &lt;code&gt;next(e)&lt;/code&gt; 會交給下一層（其他外掛，最後是 Claude Code 自己的行為）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;所以一個 hook 能做的事只有三種：&lt;strong&gt;不呼叫 &lt;code&gt;next&lt;/code&gt;，自己回答&lt;/strong&gt;；&lt;strong&gt;改一改 &lt;code&gt;e&lt;/code&gt; 再呼叫 &lt;code&gt;next&lt;/code&gt;&lt;/strong&gt;；或是&lt;strong&gt;先 &lt;code&gt;await next(e)&lt;/code&gt;，拿到結果之後再做事&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;模組跑在它自己的環境裡，沒有 DOM、也沒有 Node 的 API —— 不能直接 &lt;code&gt;import fs&lt;/code&gt;，要讀檔就得用 &lt;code&gt;$&lt;/code&gt; 提供的方法。&lt;/p&gt;&#10;&lt;h2 id="這次測的-mod不准直接改建置成品"&gt;這次測的 mod：不准直接改建置成品&lt;/h2&gt;&#10;&lt;p&gt;這個部落格用 Hugo 產生，&lt;code&gt;public/&lt;/code&gt; 是建置出來的成品，每次建置都會整個重做。直接改裡面的檔案沒有意義，下一次建置就不見了。所以拿它當題目：&lt;strong&gt;Claude Code 想用 Edit 工具改 &lt;code&gt;public/&lt;/code&gt; 底下的檔案時，擋下來，並告訴它該去改哪裡。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;.claude-plugin/plugin.json&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;public-guard&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.1.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;不讓 Claude 直接改 public/ 裡的建置成品&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;hooks/hooks.json&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;modules&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;./register.ts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;hooks/register.ts&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Register&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;claude-code&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// public/ 是 Hugo 的建置成品，每次建置都會整個重做，手改的內容留不住&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BUILD_OUTPUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/(^|\/)public\//&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt;: &lt;span class="kt"&gt;Register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;blocked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tool.call&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Edit&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;BUILD_OUTPUT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;blocked&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`public-guard：已擋下 &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;blocked&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; 次`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;deny&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; 是建置成品，請改 content/ 或 layouts/ 裡的來源。`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;重點只有中間那個判斷：路徑不在 &lt;code&gt;public/&lt;/code&gt; 底下就 &lt;code&gt;return next(e)&lt;/code&gt; 放行；在的話回傳 &lt;code&gt;{ deny: 原因 }&lt;/code&gt;，這次工具呼叫就不會執行，原因會回到模型手上。&lt;/p&gt;&#10;&lt;h2 id="檢查一結構對不對"&gt;檢查一：結構對不對&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;claude plugin validate ./public-guard&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;Validating plugin manifest: …/public-guard/.claude-plugin/plugin.json&#10;&#10;⚠ Found 1 warning:&#10;&#10; ❯ author: No author information provided. Consider adding author details for plugin attribution&#10;&#10;Validating hooks: …/public-guard/hooks/hooks.json&#10;&#10; ❯ ./register.ts hooks: tool.call{tool=Edit}&#10; ❯ ./register.ts calls: $.ui.status&#10;&#10;✔ Validation passed with warnings&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;有用的是中間那兩行：它讀了模組的原始碼，列出&lt;strong&gt;引擎認為這個模組掛了哪些事件、呼叫了 &lt;code&gt;$&lt;/code&gt; 的哪些方法&lt;/strong&gt;。這裡列的和我想的一樣（掛在 &lt;code&gt;tool.call&lt;/code&gt;、只管 Edit、會動到狀態列），代表引擎看到的就是我要的。唯一的警告是 &lt;code&gt;plugin.json&lt;/code&gt; 沒填作者，不影響載入。&lt;/p&gt;&#10;&lt;h2 id="檢查二行為對不對"&gt;檢查二：行為對不對&lt;/h2&gt;&#10;&lt;p&gt;mod 可以寫測試。測試檔放在 mod 的資料夾裡，檔名以 &lt;code&gt;.test.ts&lt;/code&gt; 結尾，工具從 &lt;code&gt;claude-code/testing&lt;/code&gt; 匯入：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;claude-code/testing&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EDIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Edit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;old_string&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;new_string&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;public/ 底下的檔案改不了&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;reached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 測試裡的這個 hook 站在 mod 的下面，代替引擎：走得到這裡就代表 mod 放行了&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tool.call&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;reached&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;edited&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;EDIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;public/index.html&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reached&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;建置成品&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;其他檔案照常放行&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;reached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tool.call&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;reached&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;edited&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;EDIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;content/learning/post.md&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reached&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;測試裡的 &lt;code&gt;on&lt;/code&gt; 掛的 hook 位在所有外掛的&lt;strong&gt;下面&lt;/strong&gt;，等於假扮引擎。所以「mod 有沒有放行」可以直接用「下面那一層有沒有被叫到」來判斷，不需要真的去改檔案。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;claude plugin &lt;span class="nb"&gt;test&lt;/span&gt; ./public-guard&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;hooks/register.test.ts:&#10;(pass) public/ 底下的檔案改不了 [34.48ms]&#10;(pass) 其他檔案照常放行 [14.52ms]&#10;&#10; 2 pass&#10; 0 fail&#10;Ran 2 tests across 1 file. [0.19s]&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;兩個都過。但只看到「通過」還不夠 —— 一份永遠通過的測試也會顯示通過。所以把 mod 故意弄壞一次：把判斷的目錄從 &lt;code&gt;public/&lt;/code&gt; 改成 &lt;code&gt;dist/&lt;/code&gt;，再跑同一道指令：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;hooks/register.test.ts:&#10;(fail) public/ 底下的檔案改不了 [34.40ms]&#10; AssertionError: expect(received).toBe()&#10;&#10; Expected: 0&#10; Received: 1&#10;(pass) 其他檔案照常放行 [13.50ms]&#10;&#10; 1 pass&#10; 1 fail&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;第一個測試失敗了，而且失敗的原因正是「下面那一層被叫到了一次」，也就是 mod 沒擋住。這樣才能說這份測試真的在看我要它看的事。改回來之後又是 2 pass。&lt;/p&gt;&#10;&lt;h2 id="檢查三真的載入跑一次"&gt;檢查三：真的載入跑一次&lt;/h2&gt;&#10;&lt;p&gt;前兩步都沒有真的啟動 Claude Code。最後用 &lt;code&gt;--plugin-dir&lt;/code&gt; 把 mod 載進一個不互動的工作階段，叫它去改 &lt;code&gt;public/&lt;/code&gt; 裡的檔案。&lt;/p&gt;&#10;&lt;p&gt;先準備一個測試用的資料夾，裡面只有一個 &lt;code&gt;public/index.html&lt;/code&gt;，內容是 &lt;code&gt;&amp;lt;h1&amp;gt;hello&amp;lt;/h1&amp;gt;&lt;/code&gt;。然後同一句提示跑兩次，一次不載入 mod、一次載入：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 對照組：不載入 mod&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;claude -p &lt;span class="s2"&gt;&amp;#34;用 Edit 工具把 public/index.html 裡的 hello 改成 world。只試一次,不要改用別的工具或別的方法;失敗的話把工具回傳的訊息原文貼給我。&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --model haiku --permission-mode acceptEdits --allowedTools Read Edit&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 載入 mod：多一個 --plugin-dir&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;claude -p &lt;span class="s2"&gt;&amp;#34;（同一句提示）&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --model haiku --permission-mode acceptEdits --allowedTools Read Edit &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --plugin-dir ./public-guard&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Claude 的回答&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;跑完之後檔案的內容&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不載入 mod&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;完成。&lt;code&gt;hello&lt;/code&gt; 已改成 &lt;code&gt;world&lt;/code&gt;。&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;&amp;lt;h1&amp;gt;world&amp;lt;/h1&amp;gt;&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;載入 mod&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;貼出工具回傳的錯誤訊息（見下）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;&amp;lt;h1&amp;gt;hello&amp;lt;/h1&amp;gt;&lt;/code&gt;，沒有被改&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;載入 mod 那一次，模型拿到的訊息是：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;public-guard: …/demo/public/index.html 是建置成品，請改 content/ 或 layouts/ 裡的來源。&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;就是 &lt;code&gt;register.ts&lt;/code&gt; 裡寫的那一句。沒有 mod 的時候檔案被改了，有 mod 的時候沒有 —— 差別只在那個旗標，所以擋下它的確實是這個 mod。&lt;/p&gt;&#10;&lt;h2 id="mod-還能做什麼"&gt;mod 還能做什麼&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;tool.call&lt;/code&gt; 只是其中一種事件。依 Claude Code 內建的說明，常見的需求大致對應到這些寫法（&lt;strong&gt;這張表我只實測了第一列&lt;/strong&gt;）：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;想做的事&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;寫法&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;擋下、改寫工具呼叫，或在它跑完之後做事&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;on('tool.call', { tool }, hook)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;改寫或回應使用者送出的提示&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;on('prompt.submit', hook)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;狀態列多一項資訊&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;在任何 hook 裡呼叫 &lt;code&gt;$.ui.status(文字)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;跳一個通知&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;在任何 hook 裡呼叫 &lt;code&gt;$.ui.toast(文字)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;提示框上面多一列&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;掛在 &lt;code&gt;ui.render&lt;/code&gt;，條件是 &lt;code&gt;{ component: 'AbovePrompt' }&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;開一個面板&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;$.ui.open({ id, title })&lt;/code&gt;，再用 &lt;code&gt;ui.render&lt;/code&gt; 畫內容&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;加一個斜線指令&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;在 &lt;code&gt;session.start&lt;/code&gt; 裡 &lt;code&gt;$.command.register(...)&lt;/code&gt;，用 &lt;code&gt;command.run&lt;/code&gt; 回應&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;想自己寫的話，最省事的做法是直接在 Claude Code 裡說「幫我做一個 mod」加上你要的行為。它會先載入內建的說明與這一版的型別定義再動手；這次就是這樣做的，上面那兩道檢查也是說明裡要求寫完要跑的。&lt;/p&gt;&#10;&lt;h2 id="這次沒有測到的"&gt;這次沒有測到的&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;畫面上的東西&lt;/strong&gt;。狀態列那一行（&lt;code&gt;$.ui.status&lt;/code&gt;）在不互動的工作階段裡看不到，所以我只確認了 &lt;code&gt;validate&lt;/code&gt; 有列出這個呼叫，沒有親眼看到它顯示。面板、提示框上方那一列也都沒測。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;熱重載&lt;/strong&gt;。依內建說明，在互動的工作階段裡同意開啟之後，mod 存檔會在那一輪結束時自動重新載入；這次用的是每次都從頭載入的 &lt;code&gt;claude -p&lt;/code&gt;，沒有走到這一段。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Write 與 Bash&lt;/strong&gt;。這個 mod 只管 Edit。模型如果改用 Write 整份覆寫，或用 Bash 跑 &lt;code&gt;sed&lt;/code&gt;，它擋不到 —— 真的要拿來用，得把這幾條路一起補上。上面的實測在提示裡要求「只試一次、不要換方法」，並且只開放 Read 與 Edit 兩種工具，就是為了不讓這件事干擾結果。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;最後一點提醒：這套 API 官方標示為早期版本，不同版本之間會變動。這篇的事件名稱與指令以 2.1.289 為準，之後照抄跑不起來的話，先看那一版的型別定義。&lt;/p&gt;&#10;</content:encoded></item></channel></rss>