# CLAUDE.md

> 🚨 **本檔的體例：判準／規則／索引，不放敘事。**
> 它**每個對話全文載入一次** ⇒ 多一行的成本要乘以每一次對話：
> **一條規則的正文長度是它的成本，不是它的重要程度。**
>
> **寫任何東西進來之前先問「它有沒有別的家」**，由上往下問，接不住才往下掉一層：
> 指令樣式／檔案事件攔得住的 → `tools/hooks/`｜做完了也不會知道出錯的 → `tools/*.py` 守衛｜
> 事故的來龍去脈 → `POSTMORTEMS.md`｜怎麼做、點哪顆按鈕 → `task/todo.txt`｜
> 東西在哪、哪份文件有權威 → `<架構索引>`｜系統現在長什麼樣 → `<規格目錄>`｜
> 還要花多少時間 → `task/work.txt`。
>
> **留在這裡的，只有「機器接不住、而且每次動手都要用到」的判斷。**
> 上限 `<N>` 行，由 `tools/spec_check.py` 檢查。

---

## 專案概觀

`<一句話：這是什麼>`
**技術棧**＝`<...>`｜**進入點**＝`<...>`｜**資料庫**＝`<本機 X / 正式 Y>`

**開工前先讀兩份**：`<架構索引>`、`task/work.txt`（排程、目前在做什麼、剩餘工時）。

---

## 🧭 寫任何一句話之前，先問「它在回答哪個問題」（HARD RULE）

寫錯地方的代價不是亂，是**兩份日後一定會漂移**。

| 這句話在回答 | 它的家 |
|---|---|
| 接下來做什麼、還要花多少時間 | `task/work.txt`（排程唯一權威，**一列一行**） |
| 那天發生什麼、當下為什麼那樣選 | `task/journal.txt`（append-only） |
| 做完了嗎、怎麼證明 | `task/evidence.txt`（結論＋實測數字） |
| 點哪顆按鈕、手把手怎麼做 | `task/todo.txt`（消耗品） |
| 設計上為什麼是這個形狀 | `<規格目錄>` |
| 事故的來龍去脈與教訓 | `POSTMORTEMS.md` |
| 被否決／被取代的決策 | `<記憶目錄>/obsolete-log.md`（**提新方案前先查**，防重議） |

🚨 **新資訊沒有家的時候，正確動作是回答上表那句話，不是開新檔。**

---

## Source of Truth

`<規格目錄>` 是權威規格 — **先改規格再改 code**。

🚨 **規格與 code 衝突 → 以 code 為準**，並回頭更新規格。
理由：使用者看到的是 code 跑出來的畫面，不是散文。

---

## 🔁 實作前強制流程（HARD RULE）

**問題背景一句話**：`<主要檔案>` 有 `<N>` 行 —— 沒有人（含 AI）能整檔讀進上下文，
而**對模型而言，沒寫進檔案的預留鉤子＝不存在** ⇒ 同一個能力被重寫第二遍，兩邊還漂移。

**任何新增功能／新檔案／新函式之前，必須依序做完：**

1. 讀 `<擴充點索引>`（含「新增時怎麼接」）。
2. `grep` 既有實作 — **至少 3 個同義詞**（中英文都試）。
3. **在回覆開頭輸出查詢結果**，二選一，不可省略：
   - `既有可複用項：<ID> <名稱>（<怎麼接>）` — 然後接上去，不新寫。
   - `查無可複用項。已查：<關鍵字>。新寫理由：<為什麼既有機制接不上>`

**跳過 1–3 直接寫 code 視為錯誤，需重做。**
重點是把「有沒有查」從不可觀測的內部行為，變成使用者一眼能驗收的產出 — **沒印出來就是沒查。**

**兩個配套習慣**：
- **先要計畫、不要直接要 code**：計畫階段的錯誤改一行字，實作階段的錯誤改三個檔案。
- **交付後回頭更新索引**：**索引一旦腐爛，比沒有索引更糟（會誤導）。**

---

## 🟢 綠燈的可信度（HARD RULE）

> 完整事故敘事＝`POSTMORTEMS.md`。這裡只留下次動手時要用的判準。

1. **綠燈必須附「我到底驗到了什麼」的數字。** 驗 0 個 ⇒ **一律 FAIL**。
   沒有數字的 PASS 只是「沒去看」的同義詞。
2. **覆蓋率是守衛的價值本身。** 沒走到的地方，綠燈只是沒去看。
3. **守衛卡住 ≠ 守衛沒發現**，兩者在終端機上長得一模一樣。等外部回應要有上限，等不到要大聲失敗。
4. **改了比對邏輯就要重跑故障演練**（注入 → 確認會紅 → 逐位元組還原）。還原一律 `try/finally`。
5. **靜態掃描回答不了「輸出的那一刻有沒有做對」** —— 要用注入實測。
6. **同一件事有 N 種寫法 ⇒ 沒有人數得出來誰有保護。**
   解法是「**唯一入口 ＋ 機器守衛**」，不是「把規則寫清楚一點」。
7. **過時的狀態行是雙向有害的** —— 看到就當場改。
8. **守衛的「前置條件不滿足」必須是 FAIL，不可以是 `return`。**
   看到 `if not X: return` 出現在守衛裡，先問「**X 不在的時候，誰會知道**」。
9. **「原始碼檢查全過」也是一種會騙人的綠燈** —— **數行為**才是綠燈的內容物。
   「我測過了」要接一句「**我是在哪一種狀態下測的**」。
10. **`cmd | tail` 會把 exit code 換成 `tail` 的。** 守衛一律單獨執行並讀 exit code。

---

## 🌍 本機綠 ≠ 可以部署（HARD RULE）

1. **部署前必須對真的 `<正式環境同型資料庫>` 跑一輪全套。**
   本機綠只代表「邏輯與回歸沒壞」，**不代表 schema、型別、外鍵在正式環境成立**。
2. **驗證要在兩種資料狀態下各跑一次：空庫與有資料。**
   ⚠️ 推論：**「再跑一次看有沒有過」不是驗證。** 要問「我剛才是在哪一種狀態下驗的」。
3. **測試永不寫入正式庫，而且是 fail-closed** —— 印警告會被忽略。

🚨 同一個道理適用於任何「只在一種環境驗過」的東西：
**靜態檢查抓不到「東西放在對的形狀、錯的地方」。**

---

## 🪝 第 1 層：hook

守衛要「記得跑」，**hook 由執行環境執行 —— 忘不掉，而且不觸發時 0 上下文成本**。
**清單與加新規則的規矩＝`tools/hooks/README.md`**（本檔不重抄）。

```bash
python tools/hooks/drill.py        # 正向 ＋ 反向演練
```

🚨 **誤報比漏報更危險** —— 誤報會讓人把 hook 關掉。
加規則必附**反向案例**，否則「全部擋掉」也會拿滿分。
⚠️ 設定檔不進版控 ⇒ **換機器要重接**（安裝腳本在 `tools/install_hooks.py`）。

---

## 🔒 Commit 前自查（HARD RULE）

**任一段有輸出就停下來處理，不要先 commit 再說。**

**改到什麼就跑什麼：**

| 改到 | 跑 | 它擋的那種**安靜**失敗 |
|---|---|---|
| `<模組 A>` | `tools/<guard_a>.py` | `<它擋的那次事故，一句話>` |
| `<模組 B>` | `tools/<guard_b>.py` | `<...>` |
| `<資料模型>` | `tools/<pg_check>.py` | **本機全綠、上雲當場死** |

**永遠不進版控**：`.env` 及任何 `.env.*`／DB 檔／憑證金鑰／`node_modules/`、`.venv/`。
已寫進 `.gitignore`，但 **`.gitignore` 只擋新檔 —— 已經 tracked 的檔案改再多次都照樣提交。**

🚨 **不可輪替的東西＝最高等級，永遠不准接近版控**（簽章金鑰外洩後無法撤換）。

---

## Commands

```bash
<每天真的會打的那幾行，含它們的坑>
```

---

## Conventions

- `<編碼／路徑習慣>`
- **不新開規劃／總結 `.md`** —— 更新 `task/work.txt` 或對應規格。
  例外只有「有外部讀者」的檔（事故記錄、對外講稿）。
- `<commit 訊息慣例>`
