# R1 — Token L1↔L2 等价类登记 + audit 三分判定升级

> **触发方式**：用户对 executor 说 `请按 docs/internal/_prompts/r1-token-equivalence-and-audit-upgrade.prompt.md 执行`
>
> **角色**：executor (Codex)
> **Plan owner**：Claude Code
> **Mode**：仍 verification mode — 只动 `src/design-system/translation/token-aliases.ts`（translation SoT 加 metadata，不改既有 mapping）+ `figma-sync/audit-component-attributes.mjs` + 报告产物。**禁动 src/canonical / src/components / src/tokens / figma-data/extract**。
> **STOP**：完成后报告新 baseline 数字（exact / theme-asymmetric / non-equivalent 三桶）+ 列 top non-equivalent 给 plan owner，等下一步指令。

---

## 1. 背景（前提）

- 双层 token 抽象问题完整分析在 [`docs/internal/token-layer-strategy.md`](../token-layer-strategy.md) §1-3。**先读这份**。
- 上一波 audit baseline（[`docs/internal/component-attributes-audit.md`](../component-attributes-audit.md)）：38 component / 4037 variant / 6515 finding（2626 non-allowlisted）；color-token-mismatch 占 758 条。
- 实证表明 R1 等价类约可吸收 20-30%（theme-asymmetric 那部分）；70-80% 是真不等价的 wrong-token bug —— **本 prompt 目标是把这两类自动分桶**，不是去修 code。

## 2. 任务 — 三件事

### 2.1 在 `src/design-system/translation/token-aliases.ts` 末尾追加 equivalence metadata

```ts
/**
 * SEMANTIC_TO_PRIMITIVE_EQUIVALENCE
 *
 * 登记每个 L2 semantic token 在 dark / light 两 scope 下等价的 L1 primitive。
 * Audit 用这份 map 区分：
 *  - exact：Figma 绑 X，code 用 X
 *  - theme-asymmetric：light 等价但 dark 不等价（或反之）—— 需 plan owner 拍板
 *  - non-equivalent：两 theme 都不等价 —— 真 wrong-token bug
 *
 * SoT 仍是 src/tokens/variables.css；本表是 audit 用的"等价见证"，
 * 必须跟 variables.css 当前值一致（脚本会校验）。
 *
 * 不要在这里新建 token；只登记已存在的 L1/L2 关系。
 */
export const SEMANTIC_TO_PRIMITIVE_EQUIVALENCE: Record<
  string,
  { dark: string | null; light: string | null }
> = {
  // 由 executor 按 variables.css 完整解析 + 填充
}
```

**填充方法**（mandatory）：脚本解析 `src/tokens/variables.css`，对每个 `--xxx: var(--yyy);` 形式的 alias 行，登记 `'--xxx': { dark/light: '--yyy' }`。对每个 `--xxx: #hex;` 形式的非 alias 行（自己就是 L1 primitive），跳过（不登记）。

如果一个 L2 在 dark 是 alias、light 是 hex（或反之），登记 alias 那侧 + 另一侧用反查 hex 找到等值 L1 token：

- 例：`--bg-layer3: #262626;`（dark scope）+ `--bg-layer3: #f0f0f0;`（light scope）
  - dark scope `#262626` 反查 → `--color-grey-11`
  - light scope `#f0f0f0` 反查 → `--color-grey-3`
  - 登记：`'--bg-layer3': { dark: '--color-grey-11', light: '--color-grey-3' }`

如果反查不到（hex 不是任何 L1 primitive 的值），登记 `null` + console.warn。

### 2.2 升级 `figma-sync/audit-component-attributes.mjs` 三分判定

当前 audit 对 color-token-mismatch 是二分（match / mismatch）。改成三分：

```js
function classifyColorMatch(figmaCssVar, codeCssVar, equivalenceMap) {
  if (figmaCssVar === codeCssVar) return { match: 'exact', themes: 'both' }

  const eq = equivalenceMap[codeCssVar]
  if (!eq) return { match: 'non-equivalent', themes: 'none' }

  const lightOk = eq.light === figmaCssVar
  const darkOk = eq.dark === figmaCssVar
  if (lightOk && darkOk) return { match: 'exact', themes: 'both' }       // L2 双 theme 都映射到这个 L1
  if (lightOk || darkOk) return { match: 'theme-asymmetric', themes: lightOk ? 'light' : 'dark' }
  return { match: 'non-equivalent', themes: 'none' }
}
```

Finding record 加字段：

```jsonc
{
  // 原字段不变
  "category": "color-token-mismatch",
  // 新增 ↓
  "equivalence": "exact" | "theme-asymmetric" | "non-equivalent",
  "equivalentThemes": "both" | "light" | "dark" | "none",
  "severity": "info"          // exact —— 不该出现（如果出现说明 audit 自身 bug，留 sanity check）
              | "drift"       // theme-asymmetric —— 需拍板，暂不 blocker
              | "blocker"     // non-equivalent —— 真 wrong-token，重点 fix 目标
}
```

**注意**：原 audit 输出 `severity: blocker/drift` 是按属性类别给的（color-token-mismatch=blocker, padding 差 1-2px=drift 等）。本改动只覆盖 color-token-mismatch 这一类的 severity：从恒定 blocker → 按 equivalence 细分。其他类别（size/padding/gap/opacity）severity 规则不变。

### 2.3 在 markdown 报告里加新章节 + 重生成

`docs/internal/component-attributes-audit.md` 末尾追加：

```markdown
## Color Token Mismatch — by equivalence (R1)

| Equivalence | Count | Severity |
|---|---:|---|
| exact (audit sanity, expect 0) | N | info |
| theme-asymmetric | N | drift |
| non-equivalent | N | blocker |

### Top 30 non-equivalent (R1 真 fix 目标清单)
| Component | Variant | Selector | Figma | Code | Resolved (dark/light) |
| ... |

### Theme-asymmetric (需要拍板：Figma 漏绑某 theme，或 code 引用过宽)
| ... |
```

JSON 报告 `summary.byCategory` 里 color-token-mismatch 改成嵌套 object：

```json
"color-token-mismatch": {
  "total": 758,
  "exact": 0,
  "themeAsymmetric": NNN,
  "nonEquivalent": NNN
}
```

## 3. 校验脚本（mandatory 在 audit 启动时自检）

`audit-component-attributes.mjs` 启动时跑一个 sanity：解析 `variables.css` 当前值，对 equivalence map 里每条 entry 做反查：

- `'--bg-layer3': { light: '--color-grey-3' }` → 校验 light scope `--bg-layer3` 的值 == light scope `--color-grey-3` 的值
- 任一不一致 → audit 退出非 0，报错 "equivalence map drifted from variables.css; rebuild map" —— 防止 SoT 自己漂移

## 4. STOP 协议

跑完报告：

1. equivalence map 总 entry 数 + 哪些 L2 token 反查 `null`（hex 不是任何 L1）
2. color-token-mismatch 三桶新数字（exact / theme-asymmetric / non-equivalent）
3. Top 20 non-equivalent finding（按出现频次降序，附 figma → code pair + 出现次数）
4. Top 10 theme-asymmetric finding（同上 + 标 light/dark 哪一侧 OK）
5. `pnpm audit:component-attributes` 退出码 + 新 baseline 总数

**禁止做的事**：
- ❌ 改 `src/canonical/*` / `src/components/*` / `src/tokens/variables.css`
- ❌ 改既有 `FIGMA_TOKEN_TO_CODE_TOKEN` map（只新增 SEMANTIC_TO_PRIMITIVE_EQUIVALENCE）
- ❌ 自己往 `divergences-decisions.json` 加 allowlist 来 silence finding
- ❌ commit / push（plan owner 复审后由用户决定）
- ❌ 新建 token / 改 token 值

## 5. 输出物清单

- `src/design-system/translation/token-aliases.ts` 追加 SEMANTIC_TO_PRIMITIVE_EQUIVALENCE 块
- `figma-sync/audit-component-attributes.mjs` 改 color-token-mismatch 判定 + 加 sanity check
- `figma-data/normalized/component-attributes.audit.json` 重新生成（含 equivalence 字段）
- `docs/internal/component-attributes-audit.md` 重新生成（含 R1 章节）
- STOP 报告含 §4 五点

## 6. 后续

R1 跑完，plan owner 看新报告：
- non-equivalent baseline → 决定 P2（form-input 族 code fix）的 prompt 范围，需要用户授权退出 verification mode
- theme-asymmetric finding → 设计师 review 决定 Figma 是漏绑还是 code 引用过宽

---

**完成后 STOP，等 plan owner 决定下一步**。
