# AITerms.tw 執行規範

> 任何工程師 / AI 在編輯本站前 MUST 讀完本檔。違反任一 = No-Go。
> 本檔是「主動 SOP」，不只是記錄問題；任何任務 MUST 對照本檔走完 preflight / runtime / postflight 三段。

最後更新：2026-05-09
維護者：Hans Lin / Claude

---

## 0. 開工前必讀順序

1. `CLAUDE.md`（44 條 quick rules + HARD RULES + 部署規則）
2. 本檔 `docs/EXECUTION-STANDARDS.md`（SOP）
3. `docs/LESSONS-LEARNED.md`（深度理解每條規則的來由，章節 A–BB）
4. `/content-quality/term-page-spec.md`（術語頁規範）
5. `/content-quality/source-policy.md`（來源規範）
6. `/content-quality/ai-generation-rules.md`（AI 生成規則）
7. `/content-quality/known-bad-patterns.json`（禁用詞 + 禁用 claim + 架構規則）
8. `/content-quality/today-2026-05-09.md`（最新事件 changelog）

跳過任一條 = 違反 SOP。

---

## 1. 編輯任務 SOP

### 1.1 編輯既有 .astro 頁面

**preflight**：
- `grep "prerender" <file>` 確認頁面類型（static / dynamic）
- 若是 4 個 static anchor（`/`, `/ipas`, `/ipas/L1`, `/ipas/L2`）NEVER 加 `prerender = false`（章節 M）
- 動態 state 用 vanilla JS client-side fetch + try/catch
- grep 該頁是否有 LAYOUT-SPEC class，沒有先補

**runtime**：
- 用 LAYOUT-SPEC class（`.page`, `.section-stack`, `.heading-row`, `.card-spec` 等）
- 卡片元素 padding ≥ 16px（章節 O）
- 不動 `src/components/static/TermCard.astro`（70+ 頁引用）
- 按鈕/卡片詞彙統一用 `.ref-btn-* / .ref-card`（2026-06-04 Phase 2 起；舊 `.info-card / .info-tag / .ait-btn-*` 已移除，`.ait-chip` 仍在），新排版規範用 `-spec` / `-layout` 後綴
- TypeScript no `any`，i18n zh + en 雙語
- 若用 `buildMetadata()`，MUST 完整透傳 title / description / canonicalURL / ogTitle / ogImage / ogImageAlt

**postflight**：
- `npx astro check` 0 errors
- `git diff --stat` 確認影響範圍
- grep `mb-8|overflow-x-auto|grid-cols-[0-9]|word-break: break-all` 危險 class 清乾淨
- 若 deploy，跑 Phase 5 Playwright（5 頁 × 8 viewport × 5 assertion = 200 cases）

### 1.2 編輯 src/data/catalog-content.ts

**核心規則**：NEVER 派多 fork 同時改（章節 L）。

**preflight**：
- 過 `known-bad-patterns.json` 的 banned_phrases + banned_claims 檢查
- 模型版本 NEVER 寫死「最新 X」「業界領先」「最強」，加「目前」「依官方說明」字樣
- Context window MUST 標明對應模型（NEVER 寫「全系列 1M」）
- 商用權利 / 隱私 / 地區限制 MUST 引官方原文，NEVER 寫 VPN 建議
- 同名 / 近似名產品 MUST 在 FAQ 加「這是 X 嗎？」區別題

**runtime**：
- `grep -n "slug: 'xxx'"` 定位 entry
- `Read offset/limit` 讀該 entry，不讀整檔
- 一次只一個 fork，不平行（章節 L）

**postflight**：
- 提醒 Hans 去 Supabase ai_tools 表手改 tagline / logoUrl / officialUrl（CLAUDE.md rule 20）
- `npx astro check` 0 errors
- 數字 MUST 對齊資料源 `.length`，NEVER 寫「80+」「500+」

### 1.3 編輯 src/styles/global.css

**核心規則**：multi-fork race 風險高（章節 L）。MUST 序列。

**preflight**：
- 卡片 padding clamp 最小值 ≥ 16px（章節 O）
- 按鈕/卡片詞彙用 .ref-btn-* / .ref-card（舊 .info-card / .info-tag / .ait-btn-* 已於 2026-06-04 移除）
- 新排版規範用 -spec / -layout 後綴避免衝突

**postflight**：
- 跑 Phase 5 Playwright 200 cases 確認 0 fail
- curl 線上 CSS 確認新 utility class 真的編譯到 _astro/*.css

### 1.4 編輯 src/data/* 內容

- 數字 MUST 對齊資料源 .length
- 絕對 claim（最強 / 第一 / 完全免費 / 保證可商用）MUST 含 evidence_url + last_checked_at（章節 S）
- iPAS 百分比 MUST 含 sample_size + exam_year_range + methodology_url（章節 X）

### 1.5 編輯 term_data（Supabase 內容）

**Hans 親手做** — Claude / 工程師 NEVER 用 CLI migration，NEVER 叫使用者去 Dashboard 執行 SQL。
Claude 只能：
- 寫 SQL 草稿到 `supabase/migrations/`
- 寫 backfill SQL（commented-out block）
- 列清單給 Hans

---

## 2. 新建任務 SOP

### 2.1 新建 dynamic SSR 頁面

- `export const prerender = false`
- 路徑 NEVER 在 4 個 static anchor 內（`/`, `/ipas`, `/ipas/L1`, `/ipas/L2`）
- Supabase query 全包 try/catch + safe fallback default（章節 U）
- 訪客模式 fallback 不破畫面
- LAYOUT-SPEC class
- 不要在靜態頁用 `client:load`

### 2.2 新建 .md / .json / .txt 公開 route

- `export const prerender = true`（章節 N）
- Content-Type = `text/markdown; charset=utf-8` 或 `application/json` 或 `text/plain`
- 多行輸出（≥20 行 + H1 + H2，每行用真實換行，不得壓成單行）
- 加 sanity check throw
- try/catch 包 Supabase 查詢，失敗時 fallback empty list

### 2.3 新建 lib（src/lib/）

- 純函數 inline types，NEVER `import type {…} from '@/lib/supabase/database.types'`（章節 T）
- vitest 覆蓋率 80%+
- no fetch / no global state
- no Supabase client（純函數）

### 2.4 新建 component（src/components/）

- MUST 跟 1 個實際使用例同 commit（章節 BB），不留僵屍檔
- 不撞既有元件名（避免影響 70+ 頁，例如 TermCard）
- vanilla JS only（no React island）
- LAYOUT-SPEC class
- 收合互動用 `<button aria-expanded>` + `<div hidden>` + JS toggle

### 2.5 新建 API route（src/pages/api/）

- `export const prerender = false`（rule 11）
- Supabase query 全包 try/catch + safe fallback（章節 U）
- auth 檢查用 `auth.uid()`，service-only 操作走 service_role + RLS deny user
- 回應 JSON shape 在 spec 中明確定義（避免前後端不一致）

---

## 3. 部署 SOP

### 3.1 Pre-deploy 強制檢查

- `npx astro check` 0 errors（NEVER 跳過）
- `npx vitest run` 全 pass
- 4 個 static anchor 仍是靜態
- `npx tsx scripts/validate-terms.ts` 通過（如可跑）
- `git status` 確認沒有意外的 staged 檔案

### 3.2 Deploy

- **MUST** `bash scripts/safe-deploy.sh`
- **NEVER** 手動 `npm run build && wrangler deploy`
- safe-deploy.sh 自動 `rm -rf dist`（防 CSS 快取汙染）
- 背景跑 deploy MUST 用 `run_in_background: true` + 等通知，不要短 sleep 輪詢

### 3.3 Post-deploy

- `curl -sIL https://aiterms.tw/<path>` 確認 HTTP 200 + Content-Type
- 改 5 截圖頁 MUST 多 viewport 截圖驗證（320 / 375 / 414 / 768）
- 改 OG 圖 MUST 換 URL（query 加 `?v=N`），LINE 才會重抓快取
- **NEVER** 靠本地 grep / LINE 預覽 / 截圖直覺判斷 deploy 是否生效（章節 AA）
- curl HTML 確認新 utility class 編譯到 production CSS

### 3.4 Deploy 失敗處理

- safe-deploy.sh 顯示「頁面不存在: dist/client/X」→ 4 個 static anchor 被改成 dynamic 了，revert prerender = false（章節 M）
- safe-deploy.sh 顯示「CSS 太小」→ Tailwind purge 異常，先 build 一次本地驗證
- safe-deploy.sh 顯示「術語頁面不足」→ Supabase build-time 連線失敗，檢查 service role key

---

## 4. AI 接手 SOP

任何 AI 編輯本站前 MUST 依序讀：

1. `CLAUDE.md`
2. `docs/EXECUTION-STANDARDS.md`（本檔）
3. `/content-quality/term-page-spec.md`
4. `/content-quality/source-policy.md`
5. `/content-quality/ai-generation-rules.md`
6. `/content-quality/known-bad-patterns.json`
7. `/content-quality/today-2026-05-09.md`

新增 / 修改內容前 MUST：

1. 過 known-bad-patterns 檢查（banned_phrases + banned_claims + architecture_rules）
2. 找官方來源（NEVER 憑記憶 / LLM 預設知識）
3. 無來源的數字 / 比例 / 授權 / 版本 NEVER 寫
4. 不確定時用保守描述（如「目前」「依官方說明」「主流方案」）
5. 跑 `validate-terms.ts`，沒過不准 ship

---

## 5. 多 fork 序列規則（章節 L 強化）

**同檔多工作 MUST 序列**，否則 fork 2 必 race blocked。以下檔案是高風險共用點：

- `src/data/catalog-content.ts`（catalog 內容）
- `src/styles/global.css`（全站 CSS）
- `docs/LESSONS-LEARNED.md`（教訓累積）
- `CLAUDE.md`（規則）
- `src/lib/queries-unified.ts`（query helper）
- `src/lib/markdown-renderer.ts`（renderer）
- `src/lib/supabase/database.types.ts`（DB types）
- `package.json`（scripts）

**不同檔 fork 可平行**。

實作要點：
- 派 fork 前先列出每 fork 會動的檔案路徑（精確）
- 不同 fork 的檔案路徑 MUST 不重疊
- 若必須同檔多工作 → 拆成單 fork 內部分批 + 序列處理

---

## 6. Decision Tree：何時用 fork / serial

```
要改的檔有衝突？
├─ Yes → 序列（單 fork 內部分批 / 等前一 fork 完成再派下一個）
└─ No → 可平行 fork

要 apply 破壞性變更（DB / RLS / migration）？
├─ Yes → MUST Hans 親手做（CLAUDE.md HARD RULE）
│       Claude 只能寫 SQL 草稿到 supabase/migrations/，不執行
└─ No → fork 可做

要 deploy？
├─ 即時（單一改動） → bash scripts/safe-deploy.sh
└─ 集合多 fork 結果 → 等所有 fork 完成 + astro check + vitest 後再 deploy

要 deploy 含 OG 圖更動？
├─ MUST 加 ?v=N 換 URL，LINE 才會重抓快取
└─ 不換 URL = 視覺上沒更新

要做 CSS / 排版修改？
├─ MUST 跑 Phase 5 Playwright 200 cases
└─ 沒跑 = 真 bug 會逃過去（如 stat-card 14px）
```

---

## 7. Validator 強制執行

build pipeline prebuild MUST 跑：

- `scripts/validate-terms.ts`（chapter L–BB 自查）
- `npx astro check`（0 errors）
- `npx vitest run`（全 pass）

任一 fail = 不准 deploy。

`validate-terms.ts` 包含 9 條 fail check：

1. failIfDuplicateCanonicalTerm（章節 W）
2. failIfActiveSlugEndsWithNumber（章節 W）
3. failIfMissingCanonical（章節 W）
4. failIfMarkdownOneLine（章節 Q）
5. failIfHtmlContainsRawMarkdown（章節 R）
6. failIfBannedGeneratedPhrase（章節 V）
7. failIfUnsupportedIpasPercentage（章節 X）
8. failIfEmptyHeading（章節 R）
9. failIfSourceTitleUrlMismatch（章節 Y，待 Step 1 schema 升級啟用）

---

## 8. 寫程式碼基本鐵律

- 靜態頁 JS = 0（React 只在 `/app/*`）
- TypeScript no `any`
- 所有 `src/pages/api/**/*.ts` MUST `export const prerender = false`
- 所有 `*.md.ts` / `*.json.ts` / `*.txt.ts` MUST `export const prerender = true`
- i18n MUST zh + en 雙語
- NEVER 全形破折號（用冒號取代）
- NEVER 左邊色條的卡片設計
- 新資料 `status = 'draft'`，禁止直接 'published'
- 所有資料查詢走 `src/lib/queries.ts` / `queries-unified.ts`
- Git commit: `feat:` / `fix:` / `refactor:` / `docs:` / `chore:` 描述

---

## 9. 違反處理

任一 SOP 違反 = No-Go。實際操作：

- preflight 沒做 → 退回重做
- runtime 違反 → 中斷修正
- postflight 沒過 → 不准 deploy
- 已 deploy 才發現 → 立即 revert + 寫進 LESSONS-LEARNED 新章節 + 加進 SOP

每次踩坑後 MUST：

1. 加章節到 `docs/LESSONS-LEARNED.md`
2. 加規則到 `CLAUDE.md`
3. 加 SOP 條目到本檔（加哪個 section / 加哪個 checklist）
4. 加 banned phrase / claim 到 `known-bad-patterns.json`
5. 寫 changelog 到 `/content-quality/today-YYYY-MM-DD.md`

---

## 10. 文件循環

```
踩坑 → 解決 → LESSONS-LEARNED 章節 → CLAUDE.md 規則 → SOP checklist → known-bad-patterns → today changelog
```

每一步 MUST 同步更新，不能只寫 1 個地方。下次新人 / AI 接手才能讀完整。
