---
title: F55 支柱① 残余「组合契约随包发」可行性 scoping
date: 2026-07-10
backlog: INFRA-F55-支柱①
status: draft
---

# F55 支柱① 残余「组合契约随包发」可行性 scoping

> 只读分析，未改任何源文件。目标：给 owner 一份可拍板的薄/厚路线对比 + 技术骨架。

## 0. 结论先行

**推荐：先发「薄」（发布现有 `component-affordances.json` 原样数据），厚数据（布局/间距规范）留作 owner 拍板后的 step 2。** 理由见 §5。

---

## 1. `component-affordances.json` 现状与缺口

**结构**（`docs/internal/component-affordances.json`，37 个 canonical 组件全覆盖，`_meta.is_source_of_truth: true`）：

每条组件记录字段：`code_name` / `npm_export` / `figma_name` / `category` / `summary` / `synonyms_en/zh` / `when_to_use` / `built_in_features`（内置能力 + controlled_by prop）/ `do_not_hand_compose`（反模式警告）/ `composition: { contained_by[], contains[] }` / `code_props`（实证自 defineProps）/ `key_events` / `code_import` / `related` / `status`。

**统计**（脚本核实）：
- 37 个组件，25 个有非空 `contained_by`，11 个有非空 `contains`
- 这是**布尔级组合关系**（"谁通常包谁" / "内部用了谁"），例如 `FormItem.contains = [InputBoxLine, SelectBoxLine, InputNumber, ...]`、`Table.contains = [Pagination, Badge, Switch, CheckBox, Rating]`

**T4b 要的是什么**（`docs/PROJECT_GOAL.md` §T4b，逐字）：
> `compositionSupport` — 不只描述单组件，还含组件组合规则（如 FormItem 包 Input、Table 包 Pagination）**+ 布局/间距规范**，让 AI 能合成**页面级**输出

**缺口判定**：组合规则（谁包谁）已有，且已经过 audit 校验（`audit:component-affordances.mjs` 的 B 项强制双向覆盖）；**布局/间距规范完全没有**。全文件唯一沾边的是 `FormItem.labelWidth` / `FormItem.layout` 两个 prop——但那是单组件自身的 API（"这个 FormItem 内部 label 多宽"），不是"多个组件排布在一起时用什么 gap/间距 token"的组合级 spacing spec（比如"纵向堆叠的 FormItem 之间用 `--sp-m`"、"Table 下方到 Pagination 的 margin 用 `--sp-l`"这类数据，全无）。

结论：**组合规则=有（薄但存在）；布局/间距规范=无（真空）**。T4b 的目标是两者都要，现状只满足一半。

---

## 2. 「随包发」具体要做什么

### 2.1 现有 json 是否直接可发

可以——现有 37 条记录本身就是干净的 AI 消费数据（`_meta` 已声明是给 AI 用的 SoT，非设计师/开发内部笔记）。唯一要做的是把它从 `docs/internal/`（不随包）搬/镜像出一份到 `dist/`（随包）。

### 2.2 参照 token 出口范式（本 session 刚 ship 的 `figma-sync/generate-token-exports.mjs`）

Token 出口范式是：单一真源（`src/tokens/variables.css`）→ 四个纯函数（`parseCss → classify → buildTree → emit*`）→ 三个 dist 产物（`tokens.json` DTCG / `tokens.js` resolved runtime / `tokens.d.ts` 类型）→ `package.json` `exports` 各开一个入口 → `audit:token-exports` drift gate 挂 `prepublishOnly`。

组合契约可复用**同一形态**，但源头不同：

- 源头不是 CSS，是已经结构化的 JSON（`component-affordances.json`），所以**不需要 parse 步骤**——直接 `readFileSync + JSON.parse` 即可，emitter 比 token 出口简单得多（token 出口的复杂度全在 CSS 文本解析 + var() 引用消解；组合契约没有这一层）。
- 三个产物类比：
  - `dist/composition/composition.json`（原样透传，供任意语言消费）
  - `dist/composition/composition.js`（resolved 运行时对象，供 JS/TS 消费方直接 import）
  - `dist/composition/composition.d.ts`（TS 类型：`ComponentName` union + 每条记录的字段类型）
- `package.json` 加两个 exports 条目：`"./composition": "./dist/composition/composition.json"`、`"./composition/js": { types, import }`（照抄 `./tokens` / `./tokens/js` 的写法）
- 挂 `pnpm build`（vite build 之后同一行）+ 新增 `audit:composition-exports` drift gate（校验 dist 与源 json 一致），挂 `prepublishOnly`

预估工作量：**远小于 token 出口**（token 出口是 7-task SDD，因为 CSS 解析+DTCG树构建+双主题解析有真实复杂度；组合契约是结构化 JSON 直接搬运，emitter 本身是几十行）。合理估算 0.5-1 天（脚本 + exports + 一个 drift audit + 挂 build/prepublishOnly + 文档一句话）。

### 2.3 要不要 DTCG 类结构 / TS 类型

- **DTCG 不适用**：DTCG（Design Tokens Community Group）规范是给「颜色/尺寸/字体」这类原子 token 设计的（`$type`/`$value`/`$extensions`），组合契约的数据形状是"组件关系图 + 组件元信息"，不是 token，硬套 DTCG 除了增加认知负担没有实际收益。保持现有的自定义 schema（已经是 icon affordance 层的姊妹结构，格式已验证可用）即可。
- **TS 类型有必要**：给 `code_name` 生成一个 union type、给每条记录的 `composition.contains`/`contained_by` 生成正确类型，能让消费方（尤其是另一个 AI agent 或另一个 repo 的构建脚本）在编译期发现拼写错误的组件名，这个收益是真实的，值得做（就是 `composition.d.ts`）。

---

## 3. 消费者拿到组合契约能做什么（真实用例）

**现状（薄，只有 contained_by/contains）能支持的**：
- "生成一个表单页面"时，AI 查到 `FormItem.contains` 包含 `InputBoxLine/SelectBoxLine/InputNumber` → 知道该把这些控件塞进 FormItem 而不是裸放；查到 `do_not_hand_compose` → 知道不能拿 `<input>` + 按钮自拼 InputNumber。**这本身已经是 T4b 想要的"组件组合规则"部分**（防止 AI 拼错组件层级），价值不小。
- 反例（真实触发本层诞生的 case）：AI 不知道 InputNumber 已内置 step，拿 InputBox + 两个 Button 自拼步进器——现有数据已经能防止这类错误。

**厚数据（补布局/间距）之后能新增的**：
- 真正的"页面级排布"：多个 FormItem 纵向堆叠时用哪个间距 token、Table+Pagination 之间留多少 margin、PopupBox footer 按钮之间的 gap——这些数字目前完全没有登记处，AI 合成页面时只能瞎猜或抄现有 mockup 的像素值（不保证是"规范"还是"偶然如此"）。
- 没有这层，AI 能拼对"该用哪个组件"，但拼不出"排列出来间距是否符合设计系统规范"——**页面级合成的最后一公里仍然靠人工过审**。

---

## 4. 工作量与 owner 决策点

| 路线 | 工作量 | 是否需要 owner 拍板 |
|---|---|---|
| 薄（发布现有 37 条） | ~0.5-1 天（emitter 仿 token 范式 + exports + drift audit + 挂 build） | 不需要——数据已存在且已过 review（`_meta.authored_by` + 已用于内部 AI 消费一个月），只是搬运/暴露，不新增内容判断 |
| 厚（补布局/间距规范） | 未知量级，取决于覆盖粒度；至少要：① 新字段 schema 设计（如 `spacing_rules: [{context, token}]`）② 逐组件盘点真实间距惯例（不是编造，需要从 `variables.css` 的 `--sp-*` token + 现有 mockup 实测）③ 新 audit 校验规则 | **需要**——按 memory `[[figma-write-scope]]`：reusable pattern 入库需 owner 拍板；这里的"哪些间距是稳定可复用规则、哪些只是某个 mockup 的偶然值"是判断题，AI 不能单方面认定 |

---

## 5. 推荐：薄路线优先，理由

1. **风险不对称**：薄路线是"把已审核数据搬到 dist"，无新内容判断，可回退成本低；厚路线一旦发布就是公开契约，consumer（其他 AI agent / 未来消费产品）可能依赖这些间距数字，若后续发现是编造/不成熟的规则要改就是 breaking change。在没有 owner 判断哪些是"真实可复用 pattern"之前抢发布布局规则，属于 CLAUDE.md 明确警惕的"无确凿依据编造契约/约定"（本机 `[[feedback_no-fabrication-on-uncertain-tool-output]]`）。
2. **T4b 拆解开看，薄已经交付一半价值**：组合规则（谁包谁 + 反模式）现状齐全且已过 audit，直接发布即可让外部消费方受益，不需要等厚数据齐全才动手——两件事不是强耦合的必须打包发布。
3. **对齐本 session 已验证过的 emitter 范式**：token 出口的经验是"先把简单直接的部分做成 zero-dep emitter + drift gate 挂上，复杂部分留给下一轮"，组合契约可以照做——先把现有 37 条做成三产物 + exports + gate，验证消费方真的会用、用得顺不顺，再决定厚数据往哪个方向补（而不是先猜 owner 要什么粒度的间距规则，编一版出来）。
4. **owner 决策点收窄到最小**：薄路线不需要 owner 现在拍任何板；厚路线需要的"哪些间距是 reusable pattern"判断题可以留到有真实消费反馈（比如某个 AI agent 真的因为缺间距规则拼错页面）之后再拍，避免空对空猜测。

---

## 6. 若 owner 选厚路线，起步锚点

- 新字段建议挂在每条组件记录的 `composition` 下（不是新增顶层）：`composition.layout_hints: [{ when: string, spacing_token: string }]`，例如 `FormItem: { layout_hints: [{ when: "同一 Form 内纵向堆叠多个 FormItem", spacing_token: "--sp-m" }] }`
- 间距 token 必须引用 `src/tokens/variables.css` 里已存在的 `--sp-*` 真源（不能发明新数值），与「Token 架构」章节的"Figma Variable = 原子真源"原则一致
- 覆盖顺序建议从已有 `contains` 非空的 11 个组件开始（FormItem / Table / Steps / TabList / Breadcrumb / TopBar / UserMenu 等），而不是全 37 个一次铺开

---

## 附：文件清单（本次分析读取，均未修改）

- `docs/internal/component-affordances.json`（1116 行，37 组件）
- `src/design-system/translation/affordance-layer.md`
- `scripts/generate-component-affordances.mjs` / `scripts/audit-component-affordances.mjs`
- `docs/PROJECT_GOAL.md` §T4b / §Token 架构
- `docs/internal/backlog.md`（INFRA-F55 支柱① 残余登记，line 34）
- `figma-sync/generate-token-exports.mjs`（范式参照）
- `package.json`（exports / scripts / prepublishOnly gate 链）
- `figma-data/normalized/components.manifest.json`（685 组件的 Figma 变体清单，确认其顶层无 `compositionSupport` 字段、当前未随包发）
- `docs/internal/retrospection/2026-06-01-component-affordances-layer.md`（本层原始设计意图，已预见"未来喂进 AI manifest T4b"）
