# Codex Prompt: T1c Extract Pipeline 升级 — Round 1 (Plan Only)

> 这是一个**只产实现计划，不写代码、不调 Figma API**的任务。30-45 分钟工作量。
>
> 目标：让 figma-sync extract pipeline 能递归抽取子节点视觉数据（fills / strokes / text / dimensions），并按 axis 归属，让下游 audit / Code Connect / generator 可以机械验证 Slider track / Rating star / Progress fill bar / Notification icon 等子节点级 token 一致性。
>
> **本轮不修脚本、不安装依赖、不写代码、不调 Figma API、不 commit。** 只产出一份计划 `.md` + 一份真源 schema 草稿 `.md`。

---

## 必读前置

1. **`AGENTS.md`** — 项目级 AI 协作入口（含"同步 Figma 库"安全模式约束）
2. **`docs/meta-rules.md`** — 元规则真源（必读，反模式清单 + Audit 子规则 + 触发器 G "schema 设计 vs 实例填充" 分工）
3. **`docs/internal/long-term-plan-v2-2026-04-29.md`** — 路线图（本任务在 T1b 完成与 T2 启动之间，新增 T1c 轨道）
4. **`figma-sync/extract.mjs`** — 当前 extract 主体（282 行）。重点看：
   - `extractNodeFrame(node)` line 134-162 — 只抽顶层 paint
   - `collectVectorNodeIds(node, result)` line 104-111 — 唯一的递归，但只收 ID
   - `buildComponentEntry()` line 164-202 — variant 构造，子节点完全丢
5. **`figma-sync/normalize-component-tokens.mjs`** — 下游 normalize 层
6. **`figma-sync/audit-component-token-fidelity.mjs`** line 1530-1560 — figma-data-missing 触发逻辑
7. **`docs/internal/component-token-fidelity-report.md`** Summary — 当前 171 条 figma-data-missing 基线
8. **`figma-data/raw/components/slider__4934_7206.json`** — Slider 顶层 placeholder fill 实证
9. **`figma-data/raw/components/rating__4935_7294.json`** — Rating 顶层 placeholder fill + 5 vectorNodeIds 实证
10. **`src/components/Slider/Slider.vue`** + **`src/components/Rating/Rating.vue`** — code 端实现，看 track / handle / star 用什么 token

---

## 上下文：当前 extract pipeline 的结构性盲区

主 session 已确认：

### 盲区 1：extract 只抽顶层 paint，子节点全丢

`extractNodeFrame(node)` 只对传入 node 抽 fills/strokes/text。COMPONENT_SET / COMPONENT 顶层往往是 auto-layout 容器，本身无视觉，真实 paint 在子节点（track / handle / fill bar / star icon / status indicator）。

实证：
- Slider 顶层 fill = `[{opacity: 0, hex: #000000}]`（透明占位）
- Rating 顶层 fill 同上 + 5 个 vectorNodeIds（star ID 收了，但无 fill）
- Progress 顶层同样是容器
- 8 组件共 171 条 figma-data-missing 都是这个根因

### 盲区 2：没有 axis 归属

即使抽了子节点 paint，audit 也不知道"这个 fill 对应哪个 axis 值"。例如 Slider 的 progress 段：在 `state=hover` variant 下颜色是 `--brand-hover`，在 `state=disabled` 下是 `--neutral`。如果 extract 只把 paint 扁平塞进 variant，audit 无法机械区分。

### 盲区 3：没有 raw response 缓存

`figma-sync/extract.mjs:248` 调 `getFile()` 后立即 walk document tree，不存 raw API response。要改 extract 规则就必须重调 Figma API（需要 `FIGMA_TOKEN` + 走 `pnpm sync:figma-library --with-extract` 安全模式）。开发循环慢且依赖网络。

### 盲区 4：figma-data/ 体积无控制

无脑递归抽所有子节点会让 figma-data/raw/ 体积爆炸（4787 个 component 文件已 153MB）。需要"抽哪些"的过滤规则真源。

---

## 设计原则（必须遵守，违反 = plan 重写）

按 [`docs/meta-rules.md`](../../meta-rules.md) 反模式清单 + 触发器：

- **#1 不在脚本里硬编码"项目级规则"**——"什么是 track / handle / fill"必须登记到 `.md` 真源，extract 读真源
- **#2 不打补丁**——不能 `if (componentName === 'Slider') { 抽 track }`；必须抽象成"按子节点角色匹配规则"机制
- **#3 产出契约思维**——extract output schema 必须让下游机械区分"顶层 paint / 子节点 paint / 占位 paint"，含 evidenceLevel 等价字段
- **#4 想清下游消费**——audit-component-token-fidelity / normalize / Code Connect / generator 怎么用这份扩展数据
- **#5 扩展只改一处**——加新 child 角色（如 future Tooltip arrow / Dialog header）只改真源 `.md`
- **触发器 G**：schema 设计 = plan owner 框定 + executor 写草稿 + plan owner 复审；实例填充 = executor 扫 figma-data/raw 列实例

---

## 你的任务（两个产出）

### 产出 1：plan 文档

**`docs/internal/_plans/t1c-extract-recurse-plan.md`**

按下面 10 节写。每节落到**具体可执行**而非抽象描述。

### 产出 2：真源 schema 草稿

**`src/design-system/translation/figma-extract-rules.draft.md`**

**只是草稿**——plan owner 复审后定稿才 rename 去 `.draft`。包含字段定义 + 5 条示范实例（Slider / Rating / Progress / Notification / Switch 各 1）。

不要扫全库填实例（那是 Round 2 executor 任务）。

---

## plan 文档章节要求

### 章节 0：真源机制设计（先于一切）

按反模式 #1 #2 #5，本升级核心**不是改 extract 算法**，而是**建立"哪些子节点抽 / 抽什么 / axis 归属怎么算" 真源 .md + extract 读真源**。

**0.1 设计 `figma-extract-rules.md` schema**（产出 2 草稿）

字段必须能描述：

```
对每个 component，登记：
  - childRoles: [
      { roleName: 'track' | 'handle' | 'fill-bar' | 'icon' | 'label' | ...,
        nodeNameMatch: 正则 / 字面量 / Figma name pattern,
        nodeTypeMatch: 'VECTOR' | 'FRAME' | 'TEXT' | ... 可选,
        depthLimit: 数字（防深度爆炸）,
        captureKinds: ['fills', 'strokes', 'text', 'dimensions'],
        axisAttribution: 'inherit-from-variant' | 'pattern-match' | 'manual'
      }
  ]
  - extractionMode: 'recursive-with-rules' | 'top-level-only'（默认 top-level-only，向后兼容）
```

**关键设计决策**（plan 文档章节 0 必须答）：

- a) `roleName` 是不是闭集？如果是，列举（track / handle / fill-bar / icon / label / arrow / divider / ...）
- b) `nodeNameMatch` 用正则还是字面量？给 Figma 实际命名样本支撑（看 raw JSON 里子节点 name）
- c) `axisAttribution = inherit-from-variant` 含义：子节点继承 variant 的 axis tuple（最常见）。`pattern-match` 含义：子节点 name 自带 axis 信息（如 `track--disabled`）。`manual` 含义：开发者必须在真源里手写映射
- d) 子节点又有子节点（嵌套 instance）怎么办？（depthLimit 之外的 fallback 策略）

**0.2 设计 extract 输出 schema 升级**

extract 输出 JSON 现在每个 variant 有顶层 `fills/strokes/text`，升级后增加：

```json
{
  "fills": [...],  // 顶层（保留，向后兼容）
  "extractedChildren": [
    {
      "roleName": "track",
      "nodePath": "Slider/State=Default/Track",
      "nodeId": "...",
      "depth": 2,
      "fills": [...],
      "strokes": [...],
      "text": null,
      "dimensions": { "h": 4, "w": 200, "r": 2 },
      "evidenceLevel": "rule-matched",
      "evidenceSource": ["figma-extract-rules.md/Slider/track"]
    }
  ]
}
```

`evidenceLevel` 取值（plan 章节 0 必须定义）：
- `rule-matched` — 命中 figma-extract-rules.md 规则
- `unmatched-but-captured` — 没命中规则但有视觉 paint（启发式抓取，提示登记）
- `placeholder-only` — 只有占位 fill（opacity 0 / 不可见），不应进 audit

**0.3 设计下游 audit / normalize 怎么消费**

audit-component-token-fidelity.mjs 在 line 1530-1560 当前对 `refs.length === 0` 直接判 `figma-data-missing`。升级后必须：

- 先读 variant 顶层 paint
- 再读 `extractedChildren` 里 `evidenceLevel = rule-matched` 的 paint
- 仍然空才判 `figma-data-missing`

audit 需要新增一个 axis-attribution 通道：哪个 child role 的 paint 对应 code 端哪个 selector / palette key。这部分**在 axis-implementation-map.md 已有的 schema 里扩展**还是新增 schema？plan 章节 0 必须答。

**0.4 plan 阶段产出物**

plan 文档章节 0 必须包含：
- a) `figma-extract-rules.md` 的完整字段定义（与产出 2 草稿一致）
- b) extract output schema 升级的 JSON shape 示例
- c) 5 条草稿实例（在产出 2 文件里）的设计依据：扫 `figma-data/raw/components/{slider|rating|progress|notification|switch}__*.json` 里子节点 name，列出至少 3 个真实子节点 name 用作 `nodeNameMatch` 样本
- d) 后续扩展场景：T2 generator 出现新子节点角色（如 Tooltip arrow）→ 只改真源 .md，extract / audit / Code Connect 自动支持

**违反检查**：
- 如果你的 0.1 设计要求 extract.mjs 里 `if (componentName === 'Slider') { ... }` → 重写
- 如果 0.2 schema 没溯源字段 → 重写（违反反模式 #3）
- 如果 0.3 audit 升级要求"在 audit 脚本里 hardcode 'Slider track 对应 .slider__track'" → 重写（应在真源 .md，audit 读它）

---

### 章节 1：问题诊断（你自己复述）

用你自己的话说清 4 个盲区，每个给 1-2 个具体行号 / JSON 字段引用。这一节是 sanity check——诊断写错后面整份 plan 跑偏。

### 章节 2：受影响组件清单 + 真源数据核查

跑 JSON 真源核查（按触发器 I 三层核对的"层 1"）：

```bash
# 列出顶层 fill 是占位（opacity 0）的所有组件
node -e '
const fs=require("fs"), path=require("path");
const dir="figma-data/raw/components";
for (const f of fs.readdirSync(dir)) {
  const d=JSON.parse(fs.readFileSync(path.join(dir,f),"utf8"));
  for (const v of d.variants ?? []) {
    const placeholderOnly = v.fills?.length === 1
      && v.fills[0].opacity === 0;
    if (placeholderOnly) {
      console.log(d.figmaName, v.name ?? "(default)", "vectorIds:", v.vectorNodeIds?.length ?? 0);
      break; // 一个 variant 命中就够说明问题
    }
  }
}
'
```

把命中清单 ∩ component-token-fidelity-report 里 figma-data-missing 分布表对齐——预期对得上 8 组件 171 条。如果对不上说明根因诊断不完整。

为每个命中组件填表：

| 组件 | figma-data-missing 数 | 顶层 fill 状态 | 子节点视觉关键 name 样本（3-5 个） | 预期 childRoles |
|---|---:|---|---|---|

### 章节 3：extract.mjs 算法升级设计

具体说明：

- 递归 walk 算法伪代码（DFS / BFS / depth-limited）
- `figma-extract-rules.md` 在 extract.mjs 启动时怎么加载（fs.read + 解析为 JSON-able 结构）
- 每个 child node 怎么 match role：name 正则 + type 过滤 + depth 限制
- match 后调 `extractNodeFrame()` 抽 paint，放进 `extractedChildren[]`
- 没 match 但有 visible paint 的子节点怎么处理（`unmatched-but-captured` 标记，让 plan owner 后续登记）
- 输入输出契约：`extractWithChildren(node, rulesForComponent) → { fills, extractedChildren: [...] }`

### 章节 4：raw Figma response 缓存设计

按盲区 3，本轮必须设计**缓存机制**（不实现，只设计）：

- 缓存路径建议：`figma-data/raw-cache/figma-file-response.json`（约 100MB+，要进 .gitignore）
- 缓存策略：第一次 `--with-extract` 时存；后续 extract 默认读缓存；显式 `--refresh-cache` 才重调 API
- 缓存失效条件：用户手动 --refresh-cache / Figma file 版本号变化（如能从 API 拿到）

这让 Round 2 之后改 extract 规则不用反复网络调 API，开发循环加速。

### 章节 5：normalize 层接口契约

`figma-sync/normalize-component-tokens.mjs` 当前怎么消费 raw extract 数据？升级后：

- variant.fills 仍主流（向后兼容）
- 新增遍历 `variant.extractedChildren` 把每个 child paint 拍平进 normalize 输出，但**保留 child role + nodePath**作为字段，让 audit / Code Connect 可以按 role 查
- normalize 输出 schema 升级最小化——给具体 JSON shape 示例

### 章节 6：audit 脚本升级要点

audit-component-token-fidelity.mjs 升级幅度（不实现，只列要点）：

- `collectFigmaRefs(variant)` line 1537 当前抽什么？升级后要抽 `extractedChildren`
- `figma-data-missing` 触发条件升级（章节 0.3 已说，这里写具体修改的代码段行号）
- 是否需要新 verdict（如 `child-role-unmapped`）？

如果 axis-implementation-map.md 需要扩展（让 axis-branch-missing 算法能查 child role），plan 章节 6 要明确扩展什么字段。

### 章节 7：Figma sync 流程影响 + 安全约束

按 AGENTS.md "同步 Figma 库"段：

- 升级后 Round 2 执行序列：(1) 改 extract.mjs (2) 改 figma-extract-rules.md (3) 跑 `pnpm sync:figma-library --with-extract`（**安全模式，dry-run cleanup**）(4) 看 figma-data/ diff (5) 重跑 audit
- figma-data/raw-cache/ 要不要进 .gitignore？给具体路径
- figma-data/normalized/ 体积预期增长（给数量级估算，不要乱填精确数）
- cleanup 报告会不会误标新增的 children 文件为"待删除"？要不要改 cleanup 规则？

### 章节 8：边界 case 列表（≥ 8 个）

每个给应对策略：

1. 子节点是 nested COMPONENT instance（引用其它组件）
2. 子节点是 BOOLEAN_OPERATION（合并形状，无独立 fills）
3. 子节点 visible: false（隐藏分支，algorithmically present）
4. 子节点 name 含 emoji / 特殊字符
5. variant 之间子节点结构不同（如 size=large 多一个 label child）
6. 子节点 fills 是 IMAGE / GRADIENT 而非 SOLID
7. axis tuple 在 variant name 里没穷举（如 Slider 只有 default + disabled，没有 hover）
8. 同一个 child role 出现 N 次（如 Steps 多个 step item）

### 章节 9：自验 sanity check（带预期数字）

跑完 v2 后报告应满足：

- ❌ figma-data-missing 应从 171 降到 < N（你估算）
- 5 条草稿实例对应组件的 figma-data-missing 应清零（Slider 8 → 0、Rating 20 → 0、Progress 32 → ?、Notification 10 → ?、Switch 14 → ?）
- 总 finding 数 vs v1 (36081) 的预期变化（应**增加**——子节点 paint 现在也会进 audit，会触发新 token-match / token-mismatch）
- direct vs heuristic 分布的新预期
- figma-data/ 体积增长 < 30%（如果超过，Round 1 设计有问题）

如果你估不出，写"主 session 审 plan 时给参考"——不要乱填。

### 章节 10：工程量分解 + 风险点

| sub-task | 估时 | 风险 | 降级方案 |
|---|---:|---|---|
| 写 figma-extract-rules.md schema + 5 实例（plan owner 已起草，executor 复审） | 30min | 低 | — |
| 实现 raw response 缓存 | 30min | 低 | — |
| 改 extract.mjs 加 `extractChildrenByRules()` | 1.5h | 中（Figma name 正则边界） | 不能 match 的 child 标 unmatched-but-captured |
| 改 normalize-component-tokens.mjs | 1h | 中 | — |
| 改 audit-component-token-fidelity.mjs（消费 extractedChildren） | 1.5h | 中（不能影响现有 verdict 逻辑） | 用 feature flag，旧逻辑保留 |
| 跑 `pnpm sync:figma-library --with-extract` 拉新数据 | 30min | 低（只看 diff） | — |
| 重跑 audit + 对照预期数字 | 30min | 低 | — |

总估时应在 **6-8h** 区间。high 风险任务必须给降级。

---

## 不要做的事

- ❌ 不写任何 `.mjs` 代码
- ❌ 不改 `figma-sync/extract.mjs` / `normalize-component-tokens.mjs` / `audit-component-token-fidelity.mjs`
- ❌ 不调 Figma API（不跑 `pnpm sync:figma-library` 任何变体）
- ❌ 不安装依赖（`pnpm install` 留到 Round 2）
- ❌ 不改 src/ / figma-data/ / 任何报告
- ❌ 不 commit / push
- ❌ 不让 executor 自己设计 `figma-extract-rules.md` 全部实例——只设计 schema + 5 草稿实例（按触发器 G 分工）

允许的：

- ✅ 读项目内任何文件
- ✅ 跑 grep / find / `node -e "JSON.parse(...)"` 查 raw JSON 真源（按触发器 I 三层核对）
- ✅ 新增 2 个文件：
  - `docs/internal/_plans/t1c-extract-recurse-plan.md`
  - `src/design-system/translation/figma-extract-rules.draft.md`

---

## 自验

```bash
# 唯一新增 2 个文件
ls -la docs/internal/_plans/t1c-extract-recurse-plan.md
ls -la src/design-system/translation/figma-extract-rules.draft.md

# 行数预期
wc -l docs/internal/_plans/t1c-extract-recurse-plan.md         # 期望 ~250-400 行
wc -l src/design-system/translation/figma-extract-rules.draft.md  # 期望 ~80-150 行（schema + 5 实例）

# 工作区只增 2 个文件
git status --short | grep -v '^??' | wc -l   # 应为 0（无 modified）
git status --short | grep '??' | wc -l        # 应增加 2

# 没 commit
git log --oneline -1   # HEAD 应仍是 f5941f2
```

---

## 完成后 STOP

输出给主 session：

```
plan 路径：docs/internal/_plans/t1c-extract-recurse-plan.md
plan 行数：N
schema 草稿路径：src/design-system/translation/figma-extract-rules.draft.md
草稿行数：N
草稿实例数：5（Slider / Rating / Progress / Notification / Switch）

章节 2 受影响组件清单核查结果：N 组件命中（预期 8）
章节 9 figma-data-missing 预期降幅：171 → N
章节 10 总估时：N 小时
high 风险 sub-task 数：N

工作区状态：2 untracked，0 modified，0 commits
```

主 session 会审两份文件，找：
- schema 字段是否够（章节 0）
- audit / normalize / Code Connect 下游契约是否清楚（章节 0.3 / 5 / 6）
- 5 条草稿实例的 nodeNameMatch 是否对齐 Figma 实际 name（章节 2 核查样本）
- 边界 case 是否覆盖
- 估时是否合理

**plan 通过 + schema 草稿定稿后才会有 Round 2 implementation prompt。**

**STOP。不要写代码。不要进 Round 2。**
