# Prompt — INFRA-F21 v1: Pre-commit hook (husky 三 🔴 block gate)

> **角色**：executor
> **范围**：装 husky + `.husky/pre-commit` script，跑 3 个 🔴 block gate（vue-tsc / vitest / figma-data staged write）。**不**装 lint-staged / prettier / eslint（v3 follow-up）；**不**装 alignment audit gate（v2 follow-up，等 F25 baseline）；**不**改 Claude Code `~/.claude/settings.json` L0 hook（v4 follow-up，update-config skill 负责）。
>
> ⚠️ **本任务需要 commit**——hook 文件是 source of truth，必须提交进仓库。
> ⚠️ **不扩范围**：只改/创建 §0 文件清单内的文件；不动 figma-sync / src / docs（除 §0.5 README 加一段）/ backlog.md / retrospection。
> ⚠️ §0 所有设计决策已由 plan owner 写定，**不要自行重设计**（不要加 lint-staged / 不要换 hook framework / 不要扩 gate）。
> ⚠️ 完成后按 §4 格式回报。

---

## §0 — Plan owner 已定裁定

### 背景

2026-05-13 session 发现 CANONICAL-002/006/008 三个 commit 在 page/component 重构时漂走了 vitest fixture，commit 时没跑 `pnpm test` → 10 failures 静默进 master。详见 [retrospection/2026-05-13-vitest-fixture-drift.md](../retrospection/2026-05-13-vitest-fixture-drift.md)。

F21 装 git pre-commit hook，把 type-check + vitest + figma-data 写入检测强制成 commit 前置——再忘跑就 block。

### §0.1 — 范围切割（critical — 不要扩）

**v1（本任务）**：只 3 个 🔴 gate（vue-tsc / vitest / figma-data path）+ husky 装配
**v2（follow-up，等 F25 baseline）**：🟠 alignment audit error 数不升 gate
**v3（follow-up，单独决定 code style）**：lint-staged + prettier + eslint
**v4（follow-up，update-config skill 负责）**：Claude Code session-level L0 PostToolUse hook（hex literal warn / figma-data warn）

如果 Codex 觉得 v2/v3/v4 "顺手"，停下来在 §4 报告问，不要自行扩。

### §0.2 — 创建文件清单（共 3 个新/改文件）

| 文件 | 操作 |
|---|---|
| `package.json` | 改：加 `husky` devDependency；加 `prepare` script |
| `.husky/pre-commit` | 新建：shell 脚本含 3 个 🔴 gate |
| `GETTING_STARTED.md`（如已存在） | 改：加一小段说明 pre-commit hook 行为 |

`.husky/_/`（husky 自动生成的目录，含 `husky.sh`）通常**不进 git**——确认 `.gitignore` 包含 `.husky/_/` 或类似，如不含则 `.husky/_/` 不要 commit。

### §0.3 — 装 husky（v9+，无 `husky install` 子命令的现代版）

```bash
pnpm add -D husky
```

`package.json` `scripts` 段加 `prepare`：

```json
"prepare": "husky || true"
```

注：现有 `prepare` 已经做 `vue-tsc --noEmit && vite build && node figma-sync/build-icon-dist.mjs`——把 `husky || true` **追加在前**（`&&` 链）：

```json
"prepare": "husky || true && vue-tsc --noEmit && vite build && node figma-sync/build-icon-dist.mjs"
```

`|| true` 兜底是因为 `husky` 在 CI / npm install --production 环境下可能没 git init，失败也不该 fail prepare 整链。

### §0.4 — `.husky/pre-commit` 脚本

```bash
#!/usr/bin/env sh

set -e

echo "▶ pre-commit: vue-tsc --noEmit"
pnpm exec vue-tsc --noEmit

echo "▶ pre-commit: pnpm test (vitest)"
pnpm test

echo "▶ pre-commit: check no figma-data/ writes"
if git diff --cached --name-only --diff-filter=AM | grep -q '^figma-data/'; then
  echo ""
  echo "❌ figma-data/ 写入被 pre-commit hook 阻断"
  echo "   (AGENTS.md 硬规则 #1：figma-data/ 是 figma 真源镜像，写入需 dry-run + 用户授权)"
  echo "   如确认要 commit，使用 git commit --no-verify 显式绕过"
  exit 1
fi

echo "✅ pre-commit gates passed"
```

**关键细节**：
- `set -e` 让任何子命令 exit≠0 都立即 fail hook
- `--diff-filter=AM` 只 catch 新增 (A) + 修改 (M)，不 catch 删除（删除 figma-data/ 文件是清理动作，应允许）
- `grep -q` quiet mode + exit code = found
- 不写 `|| true` 兜底——这是 block gate，本来就该 fail
- 三段都有 echo 进度提示，方便 user 看到卡哪一步

**chmod**：husky v9 应该自动给 `.husky/pre-commit` 加 +x；如发现没加，手动 `chmod +x .husky/pre-commit`。

### §0.5 — GETTING_STARTED.md 加段

如 [`GETTING_STARTED.md`](../../../GETTING_STARTED.md) 存在，找一个合适位置（如 "Development" / "Local Setup" 段后）加：

```markdown
## Pre-commit hook (INFRA-F21)

`pnpm install` 会自动通过 `prepare` 脚本装 husky pre-commit hook。每次 `git commit` 前会跑：

1. `pnpm exec vue-tsc --noEmit` — TypeScript 0 错容忍
2. `pnpm test` — vitest 全 pass
3. staged path 含 `figma-data/` → 阻断（AGENTS.md 硬规则 #1）

如需紧急绕过（仅在确认安全时）：`git commit --no-verify`。
```

如 `GETTING_STARTED.md` 不存在或没合适位置，跳过此步，在 §4 报告说明。

---

## §1 — 必读输入

1. [`package.json`](../../../package.json) — 现有 `prepare` script + devDependencies
2. [`AGENTS.md`](../../../AGENTS.md) 硬规则 #1（figma-data/ 写入 governance）
3. [`docs/internal/retrospection/2026-05-13-vitest-fixture-drift.md`](../retrospection/2026-05-13-vitest-fixture-drift.md) — fixture drift 教训 + 为什么需要 pnpm test gate
4. [`.gitignore`](../../../.gitignore) — 确认是否含 `.husky/_/`

---

## §2 — 任务清单（按顺序）

### 任务 2.1 — 装 husky

```bash
pnpm add -D husky
```

确认 `package.json` `devDependencies` 出现 `"husky": "^9.x.x"` 之类。

### 任务 2.2 — 改 package.json prepare script

把现有 `"prepare": "vue-tsc --noEmit && vite build && node figma-sync/build-icon-dist.mjs"` 改为：

```json
"prepare": "husky || true && vue-tsc --noEmit && vite build && node figma-sync/build-icon-dist.mjs"
```

### 任务 2.3 — 初始化 husky

```bash
pnpm exec husky init
```

会自动生成 `.husky/pre-commit`（带默认内容）和 `.gitignore` 加 `.husky/_/`。

### 任务 2.4 — 写 `.husky/pre-commit`

按 §0.4 内容覆盖 `.husky/pre-commit`（init 生成的默认内容替换掉）。

确认 `.husky/pre-commit` 有执行权限：

```bash
ls -la .husky/pre-commit
# 应显示 -rwxr-xr-x ...
```

如没 +x：`chmod +x .husky/pre-commit`

### 任务 2.5 — Smoke test

跑一次 hook（不实际 commit，单独跑 hook 脚本）：

```bash
sh .husky/pre-commit
```

预期：
- 三个 echo 步骤都跑通
- 最后输出 `✅ pre-commit gates passed`
- 全程 ~5-8s（vue-tsc ~3s + vitest ~2s + grep ~0.01s）

### 任务 2.6 — 验证 figma-data block

人造一个 staged figma-data/ 写入测试：

```bash
echo "test" > figma-data/test-f21-block.json
git add figma-data/test-f21-block.json
sh .husky/pre-commit  # 应 exit 1，输出 "❌ figma-data/ 写入被 pre-commit hook 阻断"
git reset HEAD figma-data/test-f21-block.json
rm figma-data/test-f21-block.json
```

预期：hook 在第三步 exit 1，前两步 vue-tsc / vitest 仍然能跑过（注：因为 set -e，前两步如失败会更早 exit；只要前两步 pass，第三步会正确触发 figma-data block）。

### 任务 2.7 — 验证不破 build

```bash
pnpm build
```

预期：✅ 无错（确认 husky 改 prepare 没影响 build）。

### 任务 2.8 — GETTING_STARTED.md 加段（如存在）

按 §0.5。如文件不存在或无合适位置，跳过 + §4 说明。

### 任务 2.9 — commit

```bash
git add package.json pnpm-lock.yaml .husky/ .gitignore
# 如 GETTING_STARTED.md 改了：
git add GETTING_STARTED.md

git commit -m "feat(INFRA-F21 v1): husky pre-commit hook with 3 red-block gates"
```

**注意**：commit 自身会触发 hook，所以这次 commit 必须 pass 三个 gate。如卡某个 gate→ 修该 gate 触发的真问题（不要绕过）。

---

## §3 — 验收清单

- [ ] `package.json` `husky` 在 devDependencies；`prepare` script 改为 `husky || true && <existing chain>`
- [ ] `.husky/pre-commit` 按 §0.4 内容创建，可执行（+x）
- [ ] `sh .husky/pre-commit` smoke test pass，输出"✅ pre-commit gates passed"
- [ ] figma-data block test：人造 figma-data/ 写入 → hook exit 1 → 提示阻断
- [ ] `pnpm build` ✅ 无错
- [ ] commit 触发的 hook 自身 pass 三 gate
- [ ] `.husky/_/` 在 `.gitignore` 里（husky init 应自动加）
- [ ] **不动**：lint-staged / prettier / eslint（v3 scope）/ alignment audit gate（v2 scope）/ Claude Code `~/.claude/settings.json`（v4 scope）/ canonical / src / figma-data

---

## §4 — 完成报告

```
## INFRA-F21 v1 pre-commit hook 完成报告

### 装配
- husky 版本：[e.g., ^9.1.0]
- prepare script：✅ 改为 husky || true && <existing>
- .husky/pre-commit：✅ 创建 + 可执行
- .husky/_/ 在 .gitignore：✅ / ❌（如否则补）

### Smoke test
- sh .husky/pre-commit：[全 pass / 卡第 N 步原因]
- 总耗时：[~Xs]

### figma-data block test
- 人造 figma-data/test-f21-block.json staged
- hook 触发：✅ exit 1 + 阻断提示
- 清理：git reset + rm done ✅

### 回归
- pnpm build：✅
- commit 本任务时 hook 自触发：[pass / 卡哪步]

### GETTING_STARTED.md
- 加段：[✅ / 跳过原因]

### commit hash
[hash]

### 未解决项 / blocker
- [无 / 描述]

### 范围澄清确认
- 没动 lint-staged / prettier / eslint：✅
- 没动 alignment audit gate：✅
- 没动 ~/.claude/settings.json：✅

DONE — pre-commit hook 已上岗，未来 commit 三个 🔴 gate 兜底；今日 fixture drift 教训机械落地。
```
