CLAUDE.md@scripts · git:20261030.f452f25 · 2026-10-30 · sha256 5a3eb63864f8b586

CLAUDE.md@scripts git:20261030.f452f25A

Immutable. This exact content never changes and is served at /api/v1/blob/5a3eb63864f8b586.

# scripts/ — 輔助腳本

部署到 `~/.claude/scripts/` 的輔助腳本層(部署清單由 `install-dd-pipeline.sh`
頂部的 `DD_SCRIPTS` 陣列決定),加上本 repo 自用的 git hooks。

## 關鍵檔案

- `check-claude-md.sh` — CLAUDE.md pre-commit gate(block 版)。規則:staged
  變更含程式碼檔(副檔名見腳本內 `CODE_EXT`)的目錄,必須存在 CLAUDE.md 且
  同批 staged;逃生口 `SKIP_DOC_CHECK=1`(僅供迴圈檢查點 commit)。由
  `/dd-init` 掛進各專案的 `.git/hooks/pre-commit`。
  - **記憶檔名可設**:預設 `CLAUDE.md`,`git config dd.docfile GEMINI.md`
    改成別的(寫在 `.git/config`,不進版控)。用途是**只准放某個固定檔名的 repo**
    (例如規定記憶檔一律叫 `GEMINI.md`、不得出現 `CLAUDE.md` 的團隊)—— 檔名寫死
    就等於那些 repo 完全用不到 gate。
    - **只讀 `--local`**:設成 global 的話,一次設定會把這台機器上
      **所有** repo 的 gate 改認那個檔名,個人 repo 的 CLAUDE.md 全部變成不算。一個 repo 一個決定。
      ⚠️ **連 `-c dd.docfile=…` 與 `GIT_CONFIG_PARAMETERS` 也不生效**(那是 command 層、不是 local)——
      想在別的 repo 乾跑就真的 `git config --local dd.docfile <檔名>`,驗完 `--unset`
    - **只接受單純檔名**(`[A-Za-z0-9._-]`,不是 `.` / `..`,**也不以 `-` 開頭**):帶路徑或空白一律 `exit 1`。
      這個字元集同時擋掉 regex 特殊字元,所以組 awk 樣式時只需要跳脫 `.`(`DOC_RE`)
    - ⚠️ **`-` 開頭要另外擋**:字元集本身放行 `-`,而 `md_of "."` 是 `echo "$DOC_FILE"` ——
      bash 內建 echo 把 `-n` / `-e` 當選項吃掉,根目錄的記憶檔路徑會變成**空字串**
      (`[ -f "" ]` 恆假、訊息也印不出檔名);用 `od -c` 驗過,`echo "-n"` 真的印零位元組
    - **檔名在三處各自出現**,改動時三處都要顧:`md_of()`(組路徑)、訊息字串(`${DOC_FILE}`)、
      `owed_dirs` 的 awk 樣式(`ENVIRON["DOC_RE"]`,決定欠帳**銷不銷**)
    - 變異測試(記「哪個變異 → 哪幾則紅」):

      | 變異 | 應該變紅的情境 |
      |---|---|
      | `md_of()` 改回寫死 `CLAUDE.md` | ⑫ 的這六則:①「只有 GEMINI.md 也過關」/②「補上 b/GEMINI.md 後過關」與它的 `said b/GEMINI.md`/③「補上 c/GEMINI.md:同批 staged 放行」「c 的欠帳已銷」與 `said c/GEMINI.md`。⚠️ **不是「①②③ 全部」**:②的「只有 CLAUDE.md 要擋」在變異版照樣 block(擋下的理由變成別的目錄),它的 `said "缺少 GEMINI.md 的目錄"` 也照樣綠(**訊息字串走 `${DOC_FILE}`、不經 `md_of()`**)—— 綠燈不代表那兩則釘住了這個功能 |
      | awk 樣式改回寫死 `/(^\|\/)CLAUDE\.md$/` | **只有**⑫③ 的「c 的欠帳已銷:只動 d 的 commit 放行」一則 |
      | 把 `case "$DOC_FILE" in …` 整段驗證拿掉 | ⑫④ 的「帶路徑」「含空白」兩則 expect + `said "必須是單純檔名"` |
      | `--local` 改回 `--get`(吃得到 global) | ⑫⑤ 的 expect 與 `said "c/CLAUDE.md"` |
      | 驗證的 `case` 拿掉 `-*` 那一條 | ⑫④ 的「以 - 開頭」expect 與 `said "不以 - 開頭"` |

      ⚠️ **第二個變異第一版是 0 紅** —— 當時 ③ 的最後一則是「補上 `c/GEMINI.md` 後放行」。
      `owed_dirs` 其實跑了、也確實把 `c` 算成欠帳,**是 `[ -f "$md" ] && grep -qxF "$md" && continue`
      這條「同批 staged 就過關」的短路把結果蓋掉**。要測銷帳必須**再加一個完全不碰該目錄的 commit**,
      詳見 `tests/CLAUDE.md`
    - **上游文件已同步**(2026-10-16 同日推上游):專案 CLAUDE.md 把「動到 gate 行為」列為
      要手動巡一遍的觸發時機,巡完動到的是**兩份 README 的「CLAUDE.md 維護規則」**、
      **`DD_PIPELINE_ARCHITECTURE.md` 的 gate 段**、**CHANGELOG 的 `## 未發布`**。
      `commands/dd-init.md` **刻意不動**,理由分兩半:
      - **區塊外**(Phase 3,`grep -n '^### Phase 3' commands/dd-init.md`):它寫進 hook 的那一行
        只是轉呼叫 gate、**不判檔名**,所以行為正確。改它**不需要**跳 rev(蓋章區塊是
        ``grep -n '^```' commands/dd-init.md | head -2`` 夾出來的那段,Phase 3 在它之後)
      - **區塊內**:rev 12 以前步驟 8 的範圍指令寫死在蓋章區塊裡,要跟著檔名走就得跳 `dd-loop-rev`。
        rev 13(2026-10-30)起區塊只寫「指令見 `dd-loop`」,指令在 `skills/dd-loop/SKILL.md`(一樣寫死 `grep 'CLAUDE\.md$'`),
        要跟著檔名走改那裡就好、不必跳 rev;已蓋 rev 12 以前的專案區塊裡仍是舊指令
      ⚠️ **已知限制(不是「使用者看不到」)**:Phase 3 產生的訊息字串寫死 `CLAUDE.md`
      (hook 的註解、gate 不在時的 fallback 警告、pre-commit/lefthook 的設定名 ——
      `grep -n 'CLAUDE.md gate' commands/dd-init.md`)。設了 `dd.docfile` 的 repo 裡,
      協作者打開 `.git/hooks/pre-commit` 看到的仍是「需有 CLAUDE.md」,**而 gate 實際認的是
      `dd.docfile`**。要修就改 Phase 3 的措辭(不必跳 rev),但依專案 CLAUDE.md 動 commands
      要另外在沙盒 repo 乾跑一遍,所以沒跟這批一起做
  - **擋不到 `git commit --no-verify`/`-n`**:git 直接跳過 pre-commit,gate 整個不跑,
    SKIP 那種「沒起點就自動記」也不會發生。已經記過段落起點時,之後的正常 commit 會在欠帳裡補抓到;
    沒記起點、或繞過的就是最後一個 commit,就完全漏查。被擋時的訊息結尾會提醒別這樣繞;
    Claude 端由 `skills/gate-guard` 的 PreToolUse hook 在執行前 deny
  - **`--start-segment` 有兩個擋點,`reviews_missing()` 是第二個**(2026-10-07,S6):
    專案**有 `docs/reviews/` 目錄時**(= opt-in),檢查「上一段的 commit 範圍內有沒有新增
    `docs/reviews/S*.md`」—— 沒有就 `exit 1`。擋點放在這裡而不是 pre-commit 的理由:
    **pre-commit 收到 0 個參數、拿不到 commit 訊息**,而「翻 `DONE`」那個 commit 不一定存在;
    `--start-segment` 是每段必經、而且天然就在「上一段剛結束」那個時點。
    - **樣式是 `(^|/)S[0-9][^/]*\.md$`**(`check-claude-md.sh` 的 `reviews_missing` 裡,理由寫在它上面的註解;位置用 `grep -n -F 'S[0-9][^/]*' scripts/check-claude-md.sh` 找,不寫行號 —— 這裡原本寫 `:186`/`:183`,2026-10-19 合進 `dd.docfile` 後就指歪了)。兩個設計都是刻意的:
      `S[0-9]` 而不是 `S[^/]*`(後者讓 `SUMMARY.md` / `SPEC.md` 也算報告);
      `(^|/)` 讓**子目錄**下的 `…/S1-a.md` 一樣命中(多張進度表時報告要分子目錄,段落 ID 會跨表重複)。
      ⚠️ **連帶的盲點**:段落 ID 用別的字母開頭(例如 `T1`–`T10`)時**一個都不命中**,
      於是 gate 判定「上一段沒有附 report」而 `exit 1`,**即使報告確實存在且已 commit**。
      已實際踩到;當下的處置是「起點改用進度表 commits 欄的值」,
      **不要為了讓 gate 過而改檔名**(那會讓檔名與段落 ID、commit 訊息三者不一致)。
    - **`docs/reviews/CLAUDE.md` 不算報告**(不是 `S` 開頭),改它不會放行。
    - 擋下時印的「兩條路」第 1 條是主對話直接派審查者的報告(每個審查者一份 `-a/-b/-c…`,加查證 `-judge.md`)。**判定樣式沒變** —— `-judge.md`、`-c.md` 本來就命中 `S[0-9]`。
      第 2 條(SKIP)多印一行「trailer 緊接在 `Co-Authored-By` 前面、中間不能空行」:隔了空行 git 不認它是 trailer,`--format='%(trailers…)'` 查不到、commit 照樣成功
    - **剛採用這個慣例時第一次 `--start-segment` 通常會被擋**(上一段本來就沒報告)——
      預期行為,照它印的出路補一份 `S<N>-SKIP.md` 並 commit(只建檔不 commit 還是擋)。
      起點檔不存在、或上一段從空樹開始時不擋(`reviews_missing` 的 `EMPTY_TREE` 那行,視為第一段;2026-10-30 乾跑實測)。
    - **要退出:刪掉 `docs/reviews/` 目錄**,本檢查就完全不啟用。
    - ⚠️⚠️ **已知的三條繞過路徑 —— 文件寫明,不要把它當閉環**:
      ①**跑完這一段但永遠不開下一段**(擋點只在「開下一段」那個時點);
      ②**漏跑一次 `--start-segment`** —— 起點沒搬動,`base..HEAD` 橫跨兩段,
      **上一段的報告會把這一段的要求頂掉**(實跑確認);
      ③**`rm` 掉起點檔** —— 兩個擋點一起關掉,而且 git 裡零痕跡
      (gate 的欠帳訊息本來就在教這招,所以那行後面補了警告)。
      ⇒ 它買到的是「**要繼續往前走就得先結清上一段**」,不是閉環(`skills/dd-loop/SKILL.md`「報告一律落地」那條也寫明了這點)。
      **三條都有特性測試**(2026-10-19):`tests/test-gate.sh` 裡說明以「繞過①」「繞過②」「繞過③」開頭的那幾則,
      各配一則對照(同一個狀態、只差那個繞過動作,結果相反)。**變紅時先分清是哪一種**:
      繞過①③變紅 = gate 堵上了那條路,是刻意的就先改上面那句、再照新行為改測試。
      ⚠️ **繞過②以 gate 現在看得到的東西堵不掉**:它跟「報告 commit 之後還有步驟 6、7、8 的 commit」這個正常流程,在 gate 眼裡是同一個形狀
      (gate 只看起點..HEAD 有沒有新增報告,沒有「段落」的概念)。所以②變紅時先看區塊開頭的「正常流程」那則 ——
      它也紅了就是回歸,改回 gate,不要改文件(`docs/reviews/2026-10-18-pstack-gaps/S1-a.md` 的 A1)。
      變異實測(哪個變異 → 哪幾則變紅,刻意不記紅數):

      | 變異(改了哪一行) | 應該變紅的情境 |
      |---|---|
      | pre-commit 也查報告:`staged=` 那行下面加 `reviews_missing && exit 1` | 繞過①;另外從「建立 docs/reviews/ 的那個 commit」起一路連鎖變紅(前面被擋、後面的狀態跟著跑掉)。⚠️ 要確認繞過①那則自己有牙,得**只在它那個 commit 打開這個變異**(gate 副本改成 `[ -f "$(git rev-parse --git-path m1-on)" ] && reviews_missing && exit 1`,測試副本只在那一則前後 `touch` / `rm` 那個檔):只有它變紅。改在起點之後的**第一個** commit 打開則 0 紅 —— 那時 HEAD 就是起點、範圍是空的,`reviews_missing` 本來就回「不缺」(`S1-b.md` 的 B1) |
      | 試著堵繞過②:只算 HEAD 那個 commit 新增的報告(`reviews_missing` 裡 `"$range_base" "$head_sha"` 改成 `"$head_sha^" "$head_sha"`) | 繞過②**與**「正常流程」那則一起紅(外加它們後面的 `base_is`)—— 這就是「堵不掉」的證據 |
      | `--start-segment` 有 `docs/reviews/` 而沒有起點檔就拒絕:它那一支的 `head_or_empty > "$BASE_FILE"` 上面加 `[ -d docs/reviews ] && ! read_base && exit 1` | 只有繞過③的「rm 掉起點檔,開下一段放行」 |
      | `--start-segment` 沒有起點檔時從空樹記起:它那一支的 `head_or_empty > "$BASE_FILE"` 改成 `if read_base; then head_or_empty > "$BASE_FILE"; else echo "$EMPTY_TREE" > "$BASE_FILE"; fi` | 只有繞過③的「by/ 的欠帳被洗掉」 |
    - ⚠️ **`[ -d docs/reviews ] || return 1` 這行是 opt-in 的全部** —— 動它會讓
      **所有**沒採用這個慣例的專案(含 `test-gate.sh` 前 61 項用的拋棄式 repo)一起被擋。
    - ⚠️ **`--diff-filter=A`(只算「新增」)是刻意的**:gate 自己的訊息與五份文件都寫
      「有沒有**新增**」,而第一版用 `ACMR` —— 於是**上一段的 `S1-a.md` 改個錯字,
      這一段零報告也能開下一段**(reviewer 實跑抓到)。代價:報告的「處置」節如果要等
      下一段才寫得出來,那次補寫不算這一段的證據 —— 本專案的流程是在步驟 4 當場寫完,所以不衝突。
  - **SKIP 不是豁免**:段落起點記在 `git rev-parse --git-path dd-segment-base`
    (`--start-segment` 記下;忘了記時第一個 SKIP commit 自動記)。之後的正常 commit
    沿著 commit 的祖先關係結算起點以來的欠帳:改程式碼記帳,要由看得到那段程式碼的後代
    commit(含 merge commit 自己)更新該目錄 CLAUDE.md 才銷帳。所以舊起點不會讓 gate 變寬鬆,
    平行分支上的 CLAUDE.md 更新也抵不掉。
    目錄在 index 裡已經沒有程式碼就不追討;還有欠帳時 `--start-segment` 拒絕重記
  - ⚠️⚠️ **「記下的起點 → 這次實際要用的起點」有三處在解析,而退化政策刻意不同**
    (2026-10-07,S4 把機制抽成 `resolve_fork_base`):

    | 處 | 產出 | 起點不是祖先 | 連共同祖先都沒有 |
    |---|---|---|---|
    | `owed_dirs` | range(`A..B`,空樹時是裸 `head`) | 退到共同祖先 | **放行,但印警告**(只檢查 staged) |
    | `reviews_missing` | base sha | 退到共同祖先 | **放行,但印警告**(跳過 review 檢查) |
    | `--segment-base` | base sha | **硬失敗** | **硬失敗** |

    ⚠️ **`--segment-base` 有一個例外沒寫在表上**:
    **起點是空樹時它印出空樹 sha 並 exit 0**(`[ "$base" = "$EMPTY_TREE" ] ||` 那一段)——
    因為「段落從第一個 commit 之前開始」是合法狀態,步驟 3、4、8 拿空樹 sha 去 `git diff` 是對的。
    實跑確認:全新 repo `--start-segment` → `--segment-base` 印 `4b825dc…`、exit 0。
    ⚠️ **而「空樹起點 + `--segment-base`」目前沒有任何測試覆蓋**(既有的 `--segment-base` 斷言,base 都是真 commit 或不存在)。

    **前兩處「往寬退」是刻意的** —— 退窄會把欠帳 / review 要求洗掉,而那正是這兩個檢查要擋的事。
    **`--segment-base` 必須硬失敗**:它的輸出要餵 `git diff`,印空字串或錯的 base 是文件點名的地雷。
    ⚠️ **所以 `resolve_fork_base` 只抽「機制」不抽「政策」** —— 共用的是
    `merge-base --is-ancestor` + fork-point + 那句警告(**S6 的 review 抓到這兩處已經漂移**:
    一處退到共同祖先、另一處直接放行;根目錄 `docs/reviews/S6-a.md` 的 A2 / `docs/reviews/S6-b.md` 的 B2 —— `2026-10-23-裁決審查/` 底下也有同名檔,那裡的 A2/B2 是別的事);「連共同祖先都沒有」的後續處置留在呼叫端,
    因為兩處的後果不同。`--segment-base` **刻意不用這支**:硬套進來就得加一個關掉 fallback 的
    mode 參數,那只是把同一個決策搬進函式裡(S4 規劃時的預判,S4 實作時確認了)。
    ⚠️ **改這三處任何一處,先跑變異測試**:`bash tests/test-gate.sh <改壞的 gate>`。
    ⚠️⚠️ **下面記的是「哪個變異 → 哪幾個情境變紅」,刻意不記紅數**:
    紅數同時綁「斷言集合」與「變異的具體寫法」兩個會變的東西,而文件通常只記得住數字 ——
    加任何一條碰到該路徑的斷言,紅數就過期。這與本 repo `tests/CLAUDE.md` 禁止寫「N 項」是同一類,
    **而且更糟:變異的定義本身沒被記下來**。實例:「`--segment-base` 改成軟退化」有兩種合理讀法
    (只退到共同祖先 / 再加上「沒祖先時仍印記下的 base」),review 實測各給 **3 紅與 4 紅** ——
    光看數字分不出是 gate 壞了還是變異寫法不同。

    | 變異(要寫清楚改了哪幾行) | 應該變紅的情境 |
    |---|---|
    | `--segment-base` 不再硬失敗(退到共同祖先) | 既有「起點失效時 --segment-base 要失敗」+ ⑩ 的「必須硬失敗」 |
    | `resolve_fork_base` 的 fork-point 整支拿掉 | 情境 7 的三則(`owed_dirs`)**與** ⑩ 的三則(`reviews_missing`)**各自獨立** —— 這才是「共用函式對兩個呼叫端都承重」的證據(3 + 3,不是「總共 N 紅」) |
    | 只拿掉 `resolve_fork_base` 共用那句警告 | 情境 7 與 ⑩ 的 `said` 各一則 ⚠️ **它只證明共用那句被釘住,不證明各呼叫端自己那句被釘住** |
    | 只打 `reviews_missing` 的「沒有共同祖先」那一支(改成擋 / 拿掉它的警告) | ⑪ 的兩則。⚠️ **修正前這兩個變異各 0 紅** —— ⑪ 把 `docs/reviews` 連目錄刪了,opt-in 判準不成立、那一支根本沒執行到;而 `said "找不到共同祖先"` 是被 `owed_dirs` 的訊息頂掉的 |
    ⚠️ 修法與三個空洞斷言陷阱見 `tests/CLAUDE.md`。
  - ⚠️ **`shellcheck -S warning` clean 不代表變數有局部化**:
    曾有函式宣告了**死的** `local`,真正用的變數卻是**漏宣告的全域** —— shellcheck 乾淨,一個字都沒說。
    要查局部化只能自己對 `local` 清單與函式內實際用到的變數名。
  - 列檔案的 git 指令都經過 `gitq`(`core.quotePath=false`):git 預設把非 ASCII 路徑加引號跳脫,
    副檔名比對不到,中文目錄等於完全不檢查
  - 行為的回歸測試是 `tests/test-gate.sh`(CI 在 ubuntu 與 macOS `/bin/bash` 3.2 各跑一次)。本機跑:

    ```bash
    /bin/bash tests/test-gate.sh              # 測 repo 裡這份 gate
    /bin/bash tests/test-gate.sh /tmp/gate.sh # 測改壞的副本(變異測試)
    ```
- `dd-init-upgrade.sh` — `/dd-init` 升級舊蓋章區塊時的分類器(2026-10-30,S2)。把專案根目錄 CLAUDE.md 裡帶
  `dd-loop-version` 標記的區塊逐行分成「範本原文」(去前後空白後逐字等於腳本內 heredoc 清單的某一行)與「在地內容」,
  `--plan` 只印分類、`--apply <檔> <輸出>` 寫合併結果到另一個檔、`--list` 印清單;三個模式都不改原檔。用法、exit 碼與清單的
  產生指令寫在檔頭。
  - **清單是歷代蓋章區塊的行**:改了 `commands/dd-init.md` 的蓋章區塊就要把新的行加進 heredoc(產生指令在檔頭,要用 bash 跑)
  - **分類核心是一支 awk、一律 `LC_ALL=C`**:中文比對與「共同前綴 60 bytes」按位元組算,macOS 的 BWK awk、Debian 的 mawk、
    gawk 結果一樣(三種都實跑過)。別用 gawk 專屬函式(`gensub`、`asort`)—— ubuntu runner 的 `awk` 不保證是 gawk
  - **找區塊**:起點是標記上方最近的標題;往下切單位(空行跟著前一個、圍欄整塊一個),遇到「同級或更高、不在清單裡」的標題停;
    終點是停下之前**最後一個範本單位** —— 它之後的在地內容原地不動。在地單位跟著前面最近的範本錨點(編號步驟行、`驗證不過`、
    `CLAUDE.md 維護` 標題)插回新區塊同一處。專案改寫過的步驟行(第 0 欄的 `N. `)本身算在地,但它底下的子項仍錨到步驟 N
  - **標題比對不看 `#` 的個數**(`inL()`):區塊掛在在地標題底下時,`--apply` 會把新區塊的標題降級(`## CLAUDE.md 維護` → `####`);
    逐字比對的話下一次升級會把降級後的標題當在地行留下,多一個空標題(S2 步驟 4 實測)
  - 情境測試 `tests/test-dd-init-upgrade.sh`(變異對照見 `tests/CLAUDE.md`)
- `githooks/pre-commit` — 本 repo 自用(dogfood),轉呼叫上面的 gate。
  **不部署**到 `~/.claude/`;啟用方式:`git config core.hooksPath scripts/githooks`。
  注意 `core.hooksPath` 設定後 git 會完全忽略 `.git/hooks/`,與 `/dd-init` 的
  預設掛載點互斥(dd-init Phase 3 會偵測並改掛到 hooksPath 目錄)。

## 此層慣例

- 新增要部署的腳本:檔案放這裡 + 加入 `DD_SCRIPTS` 陣列 + `--force` 重新部署
- 腳本必須通過 `shellcheck -S warning` 且可在 macOS bash 3.2 執行。CI 的 ShellCheck step
  是逐檔列出的(`.github/workflows/ci.yml`),新增腳本要自己加進清單,否則 CI 根本不會檢查它。
  3.2 相容性由 CI 的 macOS job(`bash32`)用 `/bin/bash` 實跑行為測試來驗 —— 它只抓得到行為測試碰得到的程式碼,
  新腳本要有行為測試並加進那個 job,才算有驗 3.2
- 變數後面緊接全形字(`)`、`:`)一律寫 `${var}`。`$var)` 在 macOS bash 3.2 會把全形字吃掉一半:
  變數值不見、只剩亂碼。實測 `zh_TW.UTF-8`、`en_US.UTF-8` 都會,macOS 的 `C.UTF-8` 與 Linux bash 5.2
  (`C.UTF-8`)不會;不報錯,shellcheck 連 style 級都不警告(2026-09-11,gate 的 `rm ${BASE_FILE})` 踩過)
- gate 的檢查邏輯或輸出改動時,同步檢視全域模板 §3.9 與 `skills/dd-loop/SKILL.md` 對 gate 行為的描述,以及 `skills/task-planner/SKILL.md`
  進度表規則區塊依賴的 gate 行為:`--start-segment` 印出的起點(完整 SHA,還沒有 commit 時是空樹)、有欠帳時拒絕重記、
  SKIP 記帳、只查 CLAUDE.md 有沒有一起 staged 而不查內容
- 改了 gate 或它的情境測試,要故意把 gate 改壞一行(例如拿掉 `grep -qxF "$md"` 的 `-x`)存成副本,
  用上面的第二種跑法確認會出現 ❌。全綠不代表有在檢查:2026-09-11 拿掉 `-x`、讓 staged 清單被空白拆開,這兩種改壞法
  在當時的 57 個情境下照樣全過,補到 61 個才抓到

## 與上層的關係

安裝腳本的 `create_scripts()` 負責部署(覆蓋前有差異備份);gate 的使用情境
定義在 `templates/global/CLAUDE.md` §3.9 與根目錄 CLAUDE.md「核心工作法」。