# DS 健康度维度接线计划 —— 剩余 34 格

> 立于 2026-09-16。owner 拍板「34 格都接上」后的执行计划。
> **这是计划文件，⛔ 不是冻结清单的一部分** —— `docs/frozen-worklist.md` 那条线仍是剩 4 条阻塞、建议停轮，两者互不相干。

## 0. 起手必读（⛔ 别跳）

| 读什么 | 为什么 |
|---|---|
| 本文件 §2 热点表 | 决定能不能并行 —— 这是**唯一**的硬约束 |
| 本文件 §4 分档表 | 每格的数据源 + 实测读数（2026-09-16 取，⛔ 引用前先重取） |
| `metrics/ds-sweep.mjs` 的 `dimU1` / `dimU17` | 现成范式：共用调用 · fail-closed · 分组不合成 |
| `AGENTS.md` §2.22 五行块 | 动手前每格必跑 |

**当前状态**（2026-09-16 收尾更新）：**闭集 31 格 = 在跑 30 + 明确不接 1（Q14）**。
~~闭集 12 格 = 在跑 11 + 明确不接 1~~ —— 第 1 轮已做完，见 §3 第 1 轮那节的「已完成」块。
读数入口 `node metrics/ds-sweep.mjs --ds <被测仓>`。

---

## 1. 现状与目标

- DS §3 登记表共 **64 格**，其中 **45 格**写明「能自动算」。
- 已接 **11 格**：Q12 · Q13 · S9 · U1 · U2 · U5 · U16 · X17 · X18 · X19 + **U17**（新立，⛔ 不在 64 格里）。
- Q14 **明确不接**（三条闸没有 `--json`，理由与重开条件已落在 `ds-sweep.mjs` 的 `notWired`）。
- **待接 34 格** ⇒ 本计划的射程。

---

## 2. 🔴 硬约束：5 处「注册表」（这决定了怎么分批）

每接一格都必须同时改这 5 处，**其中 4 处是同一行上的数组字面量**：

| # | 位置（2026-09-16 行号，⛔ 会漂，按内容找） | 形态 |
|:-:|---|---|
| 1 | `const DIM_IDS = Object.freeze([...])` | 一行数组 |
| 2 | `const DIM_WORSE_WHEN = Object.freeze({...})` | 对象字面量 |
| 3 | `export function flattenDimMetrics(cells)` | 逐格 `put()` |
| 4 | `runDimensions()` 里的 `wire('X', () => ...)` | 逐格一行 |
| 5 | 自证里 `MH-D1` / `MH-D1′` / `MN-D1` / `MN-D1″` | **四处写死的数组 + 一个写死的格数** |

⇒ **采集函数可以并行写**（各是独立代码块）；**这 5 处必须串行改**。
⇒ 所以分批的理由是**注册表**，⛔ 不是 A/B/C 的档次。

⚠️ 第 5 处是**刻意**写死的（`AGENTS` 的「⛔ 不许悄悄加格子」），改它时那几条控制会当场炸 —— **那是想要的**，⛔ 别把它当障碍绕过去。

---

## 3. 分轮方案

### 第 1 轮 · A 档 19 格，一次做完 —— ✅ **2026-09-16 已完成**（`2855438` + `7406769`）

**结果**：闭集 **12 → 31**（在跑 30 + 不接 1）。新增 spawn **只有 1 次**（face-inventory），
与计划一致。ratchet-watch before/after 均 🟢（红 0 · 绿 4 · 跳过 1，@`388d2737`）。

🔴 **动手前逐格跑五行块，3 处与下方 §5 的读数不符 —— 以 DS 登记表为准，§5 那三行已就地划掉**：

| 格 | §5 记的 | 实测 / 真口径 | 性质 |
|---|---|---|---|
| **X5** | icon 类 key **7** 个 | 登记表逐字是 `audit:icon-*` ⇒ **4** 个（在链 3） | §5 用了宽口径 `/icon/i`，把 `sync:icons` 这类**非闸**算了进来 |
| **X8** | `rollback\|回滚` 命中 **2** | **标题级 0**；那 2 处行级命中逐条看全是 npm registry 的备用端点 | **关键词假阳**（AGENTS §2.21 推论二②）⇒ 主判据改标题级，行级作上界 |
| **X7** | publint/attw 依赖 `[]` | 依赖确实 `[]`，但**漏了自研的 `audit:exports-types-contract`** | §5 只查了依赖一种找法 ⇒ 把「有自研 gate」读成「什么都没有」 |

⚠️ 另有 **2 处漂移**（⛔ 不是错，是 §5 取数之后被测仓变了）：
`E5` 的 `grep '^### '` 26 → **25**；`countOpenEntries` 3 → **2**。

🔴 **接线中现场踩到、值得下一轮避开的两个坑**：

1. **`countOpenEntries` 收的是 `sliceActiveBody()` 的结果，⛔ 不是全文。**
   传全文时它返回 **25** —— 恰好等于 `E5` 的另一个口径 ⇒ **两个本该差一个数量级的读数读出了同一个数**，
   而 25 在那个位置「看起来完全合理」。靠「两个该不同的数相等」这条线索抓到（AGENTS §2.19 推论四）。
   ⇒ 现走 `metrics/lib/ds-backlog-bridge.mjs`，桥自带交叉验证锚。
2. **渲染区此前是写死的 `console.log` ⇒ 一处已存在的 fail-open。**
   实测 **U1 / U2 / U5 / U17 四格早就算出来了却一行都没印**（前两轮改了 `wire()` 没改渲染，
   而**漏改不报错**）。⇒ 已改为由 `DIM_IDS` 驱动 + `MN-W11` 断言渲染表覆盖整个闭集。

**造故障六臂**（对照臂 exit 0 / 故障臂 exit 2，报的都是期望的那条控制）：
`F1` 闭集少一格→`MH-D1` · `F2` 渲染表删一格→`MN-W11` · `F3` 方向表删 key→`MN-D5″` ·
`F4` U15 加顶层合成率→`MN-W5` · `F5` X13 方向照抄 U16→`MH-W7` · `F6` E5 把全文当 Active 段→桥的锚。
⚠️ **`F6` 首版是假绿**：它确实 exit 2，但那个 2 来自**模块加载失败**（副本目录名破坏了 `LAB` 路径解析），
⛔ 不是控制拦的。补了阳性对照（同样的副本布局在**零注入**下必须 exit 0）才发现，已重做。

---

### 第 1 轮原始计划（存档）

**为什么一次做完**：注册表要改 5 处，分三次改比一次改贵；而 A 档只需 **4 次新增调用**，采集函数并不多。

闭集 **12 → 31**。共用分组（决定了只加 4 次 spawn）：

| 组 | 一次调用 / 一次读 | 覆盖 |
|---|---|---|
| **G0** | **零新增** —— 已在跑的 `docsSiteTotals()` | X13 · X11(一半) |
| **G1** | 已接的 `ds-health-scan-consumers --json` | S2（取 `pinned`/`dsVersion` + 一次 `git tag` 日期） |
| **G2** | 已接的 `ITERATION_ARTIFACTS` 两份 report | S6（多读 1 个 manifest） |
| **G3** | 已接的 ledger `ds-health-history.jsonl` | U12（现在只测新鲜度，改成顺带解析末行） |
| **G4** | 🆕 `gate-regression-face-inventory --json`（1 次 spawn） | Q6 · Q8 · Q10(一半) |
| **G5** | 🆕 `product-pattern-index.json`（纯文件读） | U15 · X16 · X10(一半) |
| **G6** | 🆕 `package.json`（已在 resolveDsRoot 读过） | X1(一半) · X5 · X6(一半) · X7 |
| **G7** | 🆕 `git ls-files` 1 次 + 一组 `existsSync` | X6 · X8 · X10 · X11 · X12 · X15 |
| **G8** | fs 遍历 docs/ + dist/ | E6 · X1(一半) |
| **G9** | import `lib/backlog-open-entries.mjs` | E5 |

### 第 2 轮 · B 档 13 格

**⛔ 别和第 1 轮混** —— 失败模式不同：A 档是源没了会 fail-closed，B 档是**口径写错会静默出错数**。
每格动手前必须先定口径并写进 cell。

`U7 · U9 · S7 · Q5 · Q7 · Q10 · E1 · E2 · E4 · X2 · X3 · X4 · X9`

### 不做 · C 档 2 格

| id | 卡在什么前件 |
|---|---|
| **E3** | 卡 `DIM-Q1`（M-rule 真守率）—— 它自己标「⚠️ 需 rule→gate 映射」，该映射不存在且 Q1 不在这 34 格内 ⇒ Δ(Q1) 取不到 |
| **S1** | 卡**一次真实的 DS 升级还没发生** —— 消费仓 69 个 commit 里版本串只改过 1 次（2026-08-07 首次引入）⇒ 分母恒 0。⚠️ 这是「前件未到」⛔ 不是「源不存在」：第二次版本跳动一落地，它当场变 A 档 |

---

## 4. 🔴 接线时必踩的三个坑（全部实测过）

### 4.1 闸的分母有 **6 个口径**（83 ↔ 105）

`Q5 / Q6 / Q8 / Q10` 四格全踩这个：

| 口径 | 读数 | 取法 |
|---|---:|---|
| `scripts/audit-*.mjs` 文件 | 83 | `ls scripts/audit-*.mjs \| wc -l` |
| lab `context-cost` gates.count | 88 | `ctx.json .totals.gates.count` |
| face-inventory `totalGates` | **93** | `gate-regression-face-inventory --json` |
| `package.json` 的 `audit:*` key | 94 | `Object.keys(scripts)` |
| scripts + figma-sync | 97 | 83 + 14 |
| mount-declaration 扫描面 | 105 | 该闸散文自印 |

⇒ **必须把口径名写进 cell**，否则四个比率互相不可比（`AGENTS §2.4`）。

### 4.2 ⛔ 别跑 `ds-health-scan-mockups.mjs`

它**不认 `--json`** 且**默认就写文件**（会改 `product-pattern-index.json` 与 `foreign-library-cleanup.md`），还要 **>120 s**（打 Figma API）。
⇒ **U12 / U15 / X16 一律改读 `_generated/` 下的现成产物**，⛔ 不跑活体扫描。

### 4.3 判定面为空要 fail-closed，且必须**逐面**断言

2026-09-16 实测踩过两次：
- 消费仓扫描在 git worktree 里跑**必然**扫出 0 个仓（`REPO_ROOT` 是 `.claude/worktrees/<name>`）⇒ 那个 `0` 看起来完全合理。
- `U17` 首版只扫一层目录 ⇒ 消费面全塌，而控制只断言「总用法 > 0」⇒ 示范面有数就通过了，**缺陷溜了过去**。

⇒ **口径：每个面各自断言非空，⛔ 不合成一个总数断言。**

---

## 5. 34 格分档表（2026-09-16 实测，⛔ 引用前先重取）

`$DS` = tvu-design-system · `$M` = `.../VS_Code/MicroApps`

| id | 维度名(简) | 数据源 | 实测读数（2026-09-16） | 结构化 | 档 |
|---|---|---|---|:-:|:-:|
| U12 | 库归属错误率 | `ds-health-history.jsonl` | `wrongLibraryRate 0.2071`（2676/12920）· `bySource` 已按来源拆 | ✅ | A |
| U15 | 复用被结构性阻断 | `product-pattern-index.json` | orphanMasters **28** · orphanInstances **728** · localInstances **13264** | ✅ | A |
| S2 | 版本滞后天数 | consumer scan + `git tag` | 两仓均 `^1.2.0` · dsVersion 1.2.0 · v1.2.0 发布 2026-07-31 ⇒ 滞后 **0** | ✅ | A |
| S6 | render 验证覆盖 | manifest + 两份 report | manifest **952** 条；vue `pass 262/fail 106`；react `total 946`。锚：manifest.length === vue.summary.total | ✅ | A |
| Q6 | report-only 闸占比 | face-inventory | `!blocking` = **8/93** | ✅ | A |
| Q8 | 闸自测覆盖 | face-inventory | `testFaceCount 267` · `{E-fx:49,E:1,I:22,IL:15,M:3,NONE:3}` · 零测试面 **3/93** | ✅ | A |
| E5 | backlog Active 条数 | `lib/backlog-open-entries.mjs` | 已漂：`grep -c '^### '` 26 → **25** · `countOpenEntries` 3 → **2**。⚠️ 两口径必须标，且 `countOpenEntries` 收的是 `sliceActiveBody()` 的结果 ⛔ 不是全文 | ✅ | A |
| E6 | docs 总体量 | fs | tracked **392 份 / 12,012,471 B**；磁盘全量 **437 份 / 13,305,108 B** | ✅ | A |
| X1 | bundle 体积 + sideEffects | `dist/` + package.json | `du -sk dist` **14776 KB**；`sideEffects` = `["**/*.css","./dist-wc/**"]`；dist js/css **63** 个 | ✅ | A |
| X5 | 图标闸挂载 | package.json | 🔴 ~~icon 类 key **7** 个~~ **口径错，见上方已完成块** ⇒ 真值 `audit:icon-*` **4** 个，在链 **3**，`audit:icon-canonical-names` 不在链上 | ✅ | A |
| X6 | 供应链 | 仓根 + package.json | dependabot/renovate/sbom/.snyk **零命中**（两种找法）；相关 deps `[]` | ✅ | A |
| X7 | 发布产物完整性 | package.json | `publint`/`attw` 依赖 `[]`（对）；🔴 ~~相关脚本 key `[]`~~ **漏了自研的 `audit:exports-types-contract`** ⇒ 信号 **1** 个 | ✅ | A |
| X8 | 回滚 runbook | `docs/RELEASING.md` | 🔴 ~~命中 **2**~~ **关键词假阳** ⇒ 标题级 **0**（那 2 处全是 npm registry 的备用端点）；行级 2 只作上界 | ✅ | A |
| X10 | 采用度量 / blast-radius | `_generated/` | 无 adoption/blast-radius 命名产物；但 `ds-health-history.jsonl` + `product-pattern-index.json` **都在** | ✅ | A |
| X11 | 组件成熟度分级 | manifest + docs-site | manifest **无 maturity 字段**；docs-site 侧 `approved 17 / inReview 8 / blocked 0` | ✅ | A |
| X12 | 治理文档 | 仓根 + `.github/` | CODEOWNERS/SUPPORT/ACCESSIBILITY/SECURITY/ISSUE_TEMPLATE **全 absent**，仅 `CONTRIBUTING.md` 在 | ✅ | A |
| X13 | 三视图覆盖 | docs-site-readiness | `threeViewPending {statusMatrix:15, useCases:25, designSpec:24}` · `componentPages 25` | ✅ | A |
| X15 | 微文案中央层 | docs 树 | microcopy/voice/tone/writing-guide **零命中**；内容 grep 命中 3 份**全是讨论该缺口的评估报告** | ✅ | A |
| X16 | 跨产品 pattern 索引 | `product-pattern-index.json` | 存在（82449 B）· **7 file / 6 distinct product**。⚠️ 登记表 base 写的「无跨产品索引」**已过期** | ✅ | A |
| U7 | 首次集成跨度 | `$M` git log | 朴素判据给**负跨度 −65 天**（`TvuIcon` 是消费仓自有组件不是 DS 的）⇒ 要写「DS import 溯源」判据 | ✅git | B |
| U9 | 每场景开工必读 KB | `STATUS.md` | 场景→文件链只在 md 表里（L-core 4 行 + L-ref 7 行）⇒ 要写表解析 | ❌md | B |
| S7 | 破坏性变更频次 | `CHANGELOG.md` | `^## ` **20** 个版本；`breaking` 命中 **15** ⚠️ 那是**提及次数**不是条目数 | ❌散文 | B |
| Q5 | 闸挂载自声明率 | mount-declaration | `--json` **被静默忽略**（该文件 0 命中），数只在散文：扫描面 **105** · 已声明 **67** · 沉默 **38** | ❌散文 | B |
| Q7 | 豁免表条目总数 | 39 个文件内联 | JSON 型只有 2 份；其余是各闸文件内的内联字面量 ⇒ 要写跨文件枚举器。⚠️ 「模式漏收 ⇒ 假绿」风险最高的一格 | ❌ | B |
| Q10 | 零挂载孤儿闸数 | face-inventory + Q5 | `mounts:[]` = **14/93**（这半是 A）；「头注释无为什么不挂」那半只在 Q5 散文里，且分母 93↔105 不同 | 半✅ | B |
| E1 | L-core 体量 | `STATUS.md` L-core 表 | 固定指针臂 = **94676 B (92.5 KB)**；但第 4 份有两种读法（「最新一份」vs 表里写死）⇒ 先定口径 | ❌md | B |
| E2 | 规则文件总行数 | lab `context-cost` | 量具给 **14 份 / 12322 行**，登记表要**五份** ⇒ 那 5 份的名单仓里无真源，要先定 | ✅ | B |
| E4 | mockup 链读法差 | `mockup-conventions.md` | 14 个 `##` 段**全是具名标题、无 7–11 编号** ⇒ AGENTS 说的「§必读链路 7-11」机械解析不出，scoped 臂要先定义 | ❌md | B |
| X2 | i18n 可本地化率 | no-hardcoded-ui-strings | EXIT=0，仅 74 B 散文：`72 .vue scanned, 14 locale keys` ⇒ 与 Q14 **同形态**，照 Q14 先例处理 | ❌散文 | B |
| X3 | motion token 化率 | `src/` | 硬编码 transition **14** 处（13 文件）；`--duration\|--ease\|--motion` = **0** ⇒ 与已接的 X19 同源，可并进去 | ✅ | B |
| X4 | 主题扩展点 | `variables.css` | 13 个 `:root` + 1 个 `[data-theme="light"]` ⇒ 主题 **2** 套；`useTheme\|ThemeProvider` **3** 命中 ⇒ 「`:root`=dark 默认」这条约定要写死进判据 | ✅ | B |
| X9 | 响应式 scope 声明 | `src/` | `@media` **2** 处（这半是 A）；「有无书面 desktop-first 声明」那半要先定位真源 | 半✅ | B |
| E3 | 规则增速 vs 闸覆盖增速 | Δ(E2) vs Δ(Q1) | **前件 Q1 不可自动且不在本批** | — | C |
| S1 | 升级适配成本 | `$M` git log | 版本串**只被改过 1 次**（首次引入）⇒ 升级事件 0 次、分母 0 | ✅git | C |

---

## 6. 复核入口（零写入）

```bash
DS=/Users/nancy/Documents/AICoding/VS_Code/tvu-design-system
cd "$DS" && node scripts/gate-regression-face-inventory.mjs --json > /tmp/fi.json; echo "EXIT=$?"
cd "$DS" && node figma-sync/audit-docs-site-readiness.mjs > /tmp/ds.out; echo "EXIT=$?"
cd /Users/nancy/Documents/AICoding/VS_Code/ai-ds-lab && node metrics/ds-sweep.mjs --ds "$DS" --out /tmp/sweep.json
```

⛔ **别跑 `ds-health-scan-mockups.mjs`**（见 §4.2）。
