# 把 Q4 那 8 个 DIM 格接进 `ds-sweep` —— 口径预注册

> 现状：8 格 2026-09-07 全部立在 DS 的 `docs/internal/ds-health-dimensions.md` §3，
> 每格都带**活源算法**与**当日实测基线** —— 但**没有任何东西每天去算它们**。
> ⛔ 口径先钉死再动手（B 档的附带义务）。

---

## 0 上游硬约束（DS 侧 §0 契约，逐字）

| # | 规则 | 对本次接线意味着什么 |
|---|---|---|
| **G4** | **所有数字从活源现算，⛔ 不引用任何既有报告的数** | ⛔ 采集器**不许**把 §3 里那些「2026-09-07 实测基线」当输入或当阈值。每轮从活源重算，⛔ 不读任何 md |
| **G5** | **绝对数必须配分母** | ⇒ **趋势比较一律用比例，⛔ 不用绝对数**。项目变大 ⇒ 绝对数涨，看着像退步 |
| G1 | grow-only：维度只能加不能悄悄删 | ⇒ 采集器发现某格**取不到数**时，⛔ 不许静默跳过（那等于悄悄删了一个维度）—— 见 §3 的 D3 |
| G3 | 每次评估必须逐条应答本表，**沉默 = 违约** | ⇒ 8 格每轮**逐条印**，取不到数就印 `null` + 原因 |
| G7 | 不新增采集，只分析团队已有产物 | ✅ 8 格的活源算法全部只读 DS 仓与它自己的闸，无新采集 |

---

## 1 🔴 最要紧的口径决定：**默认不叫人**

`ds-sweep` 的退出码是 `0 全清 / 1 被测对象要人看 / 2 装置自己坏了`。
⇒ 「一个维度的读数是多少」**本身不是「要人看」** —— 这条教训是本仓付过学费的：

> Q3 那条「人多久没写新一波」断了 102 天，每轮让采集器 `EXIT=1`，
> 于是 autofix 每天被唤起、无事可做、退出。⇒ 换成产物年龄后空转才停。

⇒ **8 格默认全部标 `〔读数·⛔ 不叫人〕`**，与 Q3 那条已有的形态一致。

### 1.1 那什么情况才叫人 —— 判据是「**有没有别的闸已经在守它**」

⛔ 接一个已被别的闸守住的维度进报警 = **重复报警**，而重复报警会训练人忽略报警。
⇒ 逐格实测（2026-09-08，⛔ 非印象）：

| 格 | 有闸在守这个数吗（实测） | 进 needsHuman？ |
|---|---|---|
| **DIM-Q12** Acceptance 三态比 | ✅ 有 —— `audit:acceptance-gate-coverage` 的 `[S2]` 棘轮守 `unclassified` 不许涨、`[S3]` 守豁免表仍命中，且它在 **pre-commit 无条件段**上 | ❌ 只记读数 |
| **DIM-Q13** 工具耦合度 | ❌ **无** —— 元闸 `audit:gate-mount-declaration` 守的是「有没有挂载声明」，**⛔ 不守「耦合度高不高」**（一条闸声明「挂在 `.claude/hooks`」完全合法） | ✅ **叫人** |
| **DIM-Q14** 跨组件 API 自洽性 | ✅ 有 —— `audit:prop-naming` / `audit:framework-api-floor` / `audit:binding-config-parity` 三条自己会红 | ❌ 只记读数 |
| **DIM-S9** a11y 棘轮 | ✅ 有 —— `tests/a11y-non-text-contrast-ratchet.test.ts`（跑在 `pnpm test` ⇒ pre-commit 无条件段 + Gitea） | ❌ 只记读数 |
| **DIM-U16** 文档站三视图就绪度 | ❌ **无** —— 🔴 实测：`audit:docs-site` **在 `gate-chain` 里**（阻塞面），但单跑 `EXIT=0` 而 `threeViewPending` 非零 ⇒ **闸在跑，它不守这个数**。与 Q2 那条「报 20 条 fail 却 `EXIT=0`」完全同族 | ✅ **叫人** |
| **DIM-X17** pattern 覆盖率 | ❌ **无** —— 与 U16 同一个量具、同一个理由 | ✅ **叫人** |
| **DIM-X18** 溯源悬空引用 | ❌ **无** —— 实测 `scripts/audit-*.mjs` + `figma-sync/*.mjs` **零个文件提到 `llms.txt`** | ✅ **叫人** |
| **DIM-X19** 基础标度缺口 | ❌ **无** —— 实测零个 audit 脚本提到 `--z-` / `--duration` / `--ease` / `--density` | ✅ **叫人** |

⇒ **5 格叫人**（`Q13` / `U16` / `X17` / `X18` / `X19`，其中 U16 与 X17 共用量具）
**· 3 格只记读数**（`Q12` / `Q14` / `S9`）。5 + 3 = 8 ✅

⚠️ **「有闸在守」这句话本身是本次实测出来的，⛔ 不是从文档抄的**。
其中 U16/X17 那一条**推翻了「它在 gate-chain 里所以有人守」的自然假设** ——
在链上 ≠ 守这个数。⇒ 这个区分要写进采集器注释，否则下一个人会照「在链上」重新豁免它。

---

## 2 🔴 第二个口径决定：叫人的**触发条件是退步，不是绝对值**

⛔ **不许**用「pending > 0 就叫人」这类绝对阈值 —— 那会**恒红**（U16 此刻 pending 就是非零），
而恒红 = 零信息，且它长得像 fail-closed（`AGENTS.md` 第 25 条）。

⇒ 触发条件 = **相对上一轮，比例在更差方向变化**。逐格方向（⛔ 现在定死，不许事后调）：

| 格 | 比例定义（分子/分母，G5） | 更差方向 |
|---|---|---|
| DIM-Q13 闸级 | 唯一挂载在 `.claude/hooks/` 的闸数 / 有 D1 声明的闸数 | **升高** |
| DIM-Q13 规则级 | `mockup-rules/index.mjs` 注册模块数 / 同上分母 | **升高**（更多规则押在同一个工具上）|
| DIM-U16 × 3 | `threeViewPending.{statusMatrix,useCases,designSpec}` / `componentPages` | **升高** ⛔ 三个视图分别读，不合成一个数 |
| DIM-X17 | `designSpec` **已就绪**页数 / `componentPages` | **降低** |
| DIM-X18 | 悬空引用条数 / `llms.txt` 引用总数 | **升高** |
| DIM-X19 × 3 | motion 族 / `--density` / `--z-*` 各自的 token 数（⚠️ **没有自然分母**）| **降低** |

### 2.1 ⛔ X19 是一个必须如实登记的例外：它**没有分母**

`--duration`/`--ease`/`--motion` 是「有几个」，不是「多少分之几」。G5 要求绝对数配分母，
而这一格**结构上给不出分母**（不存在「应该有几个 motion token」这个数）。
⇒ 本次的落法：**X19 三族按绝对数比，并在采集器里显式标注它是 G5 的例外**。
⛔ 不许发明一个假分母（例如「/ 全部 token 数」——那会让「加了一个颜色 token」看起来像 motion 退步）。
⚠️ 这一格的比较因此对「重命名」敏感：把 `--duration-fast` 改名成 `--motion-fast` 族计数不变，
但把它挪出 `variables.css` 就会被读成退步。**那个红是想要的**（token 从真源消失确实是退步）。

---

## 3 判据（⛔ 跑之前写死；每条要两侧）

### D1 · 8 格每轮都印，取不到数印 `null` 而**不是** 0
`node metrics/ds-sweep.mjs` 的人读输出里出现全部 8 个 DIM id。
**造故障**：把 `audit-acceptance-gate-coverage.mjs` 临时改名 ⇒ DIM-Q12 印 `null` + 原因，
⛔ 不许印 `0`（**不知道** ≠ 0，与 Q9 探针 `canCommit: null` 同范式）。

### D2 · 取不到数 ⇒ **exit 2（装置故障）**，⛔ 不是 exit 1
G4 要求数从活源现算 ⇒ 算不出来是**量具与活源脱节**，不是被测对象有问题。
**造故障**：同 D1 ⇒ 进程 `EXIT=2`，且输出里明说「装置故障 ⛔ 不得读作『DS 没问题』」。

### D3 · 少算一格 ⇒ 红（G1 grow-only 的机械化）
采集器持有 8 个 id 的闭集；**产出的格数 ≠ 8 ⇒ exit 2**。
**造故障**：从闭集里删掉一个 id ⇒ `EXIT=2` 并点名「登记表有 N 格、本轮只算了 N-1 格」。
⛔ 这一条是必须的：没有它，「悄悄少算一个维度」正是本表立表要治的那个病。

### D4 · 🔴 趋势必须对着**上一轮写的那一行**比，⛔ 不是本轮刚写的
这是 Q3 那个坑的同族（第一版把报警接在序列最后一行的年龄上，而那一行是本轮自己写的
⇒ 恒等于今天 ⇒ 永不红）。
**造故障**：造一份只有**本轮**那一行的序列 ⇒ 必须报「无上一轮可比 ⇒ 趋势不可用」，
⛔ 不许报「无退步」（那是把「不知道」说成「好」）。

### D5 · 真退步时它红**并点名哪一格哪个方向**
**造故障**：往序列里注入一行「上一轮」，使 U16 的 `useCases` 比例更好 ⇒ 本轮相对它退步
⇒ `EXIT=1`，逐字点名 `DIM-U16.useCases` + 两个比例值。

### D6 · 只记读数的 3 格**即使退步也不叫人**（但照样印）
**造故障**：让 DIM-Q12 相对上一轮退步 ⇒ 输出里印出退步，但 `needsHuman` 不含它、
`EXIT` 不因它变 1。⛔ 这一条要有正向对照，否则「默认不叫人」可能是「压根没算」。

### D7 · 第一轮零 Δ ⇒ `EXIT=0`
序列为空时跑一次 ⇒ 8 格都有值、`EXIT=0`、并印「本轮是第 1 个点 ⇒ 趋势不可用」。

### D8 · 序列⛔ 不落任何 git 仓
落点 `~/.claude/ds-metrics/ds-dimensions.jsonl`（同 Q3 的 `iteration-ledger.jsonl` 范式）。
**判据**：跑完后 DS 与 lab 两仓 `git status --porcelain` 与跑之前**逐字一致**。

### D9 · 采集器自证零回归
`node metrics/ds-sweep.mjs --self-test` 全绿，新增 must-hit / must-not-hit 覆盖 D1–D7。

---

## 4 ✅ 落地读数（2026-09-08）—— 7 格接线 · 1 格明确不接

```
DS 健康度维度（8 格闭集 · 数从活源现算）：
  · DIM-Q12 Acceptance 三态比   gated 42/133 = 31.6%              〔读数·⛔ 不叫人〕
  · DIM-Q13 工具耦合度          唯一挂 .claude/hooks 的闸 1/43(D1) = 2.3% · 规则级 13
  · DIM-S9  a11y 棘轮规模       非文本 20 对 + 文本 69 条〔G5 例外〕 〔读数·⛔ 不叫人〕
  · DIM-U16 三视图待办          statusMatrix 15/25 · useCases 25/25 · designSpec 24/25
  · DIM-X17 pattern 覆盖率      designSpec 就绪 1/25 = 4.0%
  · DIM-X18 溯源悬空引用        2/5 = 40.0%（逐条点名两个悬空文件）
  · DIM-X19 基础标度缺口        motion 0 · density 0 · z-index 4〔G5 例外〕
  ⊘ DIM-Q14 本轮未接（印 null + 理由 + 重开条件）
  ⇒ 序列 1 点 · **趋势不可用**（⛔ 不是「无退步」）· EXIT=0
```

**7 格全部从活源复现了 §3 登记的当日基线**（X17 `1/25` · X18 `2/5` · X19 `0/0/4` ·
U16 三视图取补数后与「10/25 · 0/25 · 1/25 已就绪」逐字对上 · Q13 闸级 1 / 规则级 13）。
⚠️ Q12 现在是 42/133 而基线是 35/133 —— 那是同日 A0 + B1 两批上闸的结果，**不是漂移**。
⚠️ Q13 分母 43 而基线写 40 —— 那是 2026-09-07 元闸窄扩面补了 3 条 D1 声明后的现值。

### 4.1 🔴 G4 的价值在 DIM-Q13 上兑现了 —— **第一版两个算式都算出 0**

登记基线是「闸级 1 · 规则级 13」，我的第一版实测得到 **0 / 0**。两个都是真错：

| 算式 | 第一版 | 为什么错 |
|---|---|---|
| 分子（唯一挂 `.claude/hooks/` 的闸） | 「头 45 行提到 `.claude/hooks/` 且**不提** `.husky/`」 | 🔴 `audit-mockup-conformance.mjs` 头注释第 7 行逐字写着「⛔ 刻意**不**进 prepublishOnly、也不挂 `.husky/pre-commit`」—— 那是一句**否定陈述**，而「提到 .husky」的词法判据分不清正反 ⇒ **假阴性**。改判**挂载声明行本身**（`L<数字> =` 形态）：有 ≥1 条且**每一条**都指向 `.claude/hooks/` |
| 规则级 | 数 `{ id:` 形态的对象字面量 | `export const RULES = [...]` 里是**import 进来的裸标识符**，不是对象字面量 ⇒ 正则恒得 0 |

⇒ **如果我照 §3 的基线抄那两个数，这两个错会被完整掩盖。**
G4「数从活源现算，⛔ 不引用任何既有报告的数」在这里不是形式主义 —— 它是唯一抓到这两个错的机制。
⚠️ 教训①与本仓已登记的病同族：**否定句会污染词法判据**（豁免表 `#M21.3` 那句站不住的理由同型）。

⇒ 同批补了两条**地板**（⛔ 塌了就别出数）：规则注册表解析出 0 ⇒ 抛；
引擎文件在而分子算出 0 ⇒ 抛并逐字说「⛔ 别把它读成『耦合度 0』—— 那正是这一格要测的病，恒 0 = 零信息」。

### 4.2 🔴 D6 造故障抓到「默认不叫人」退化成「**压根没算**」

第一版的 `DIM_WORSE_WHEN` 方向表**漏了 Q12 / S9** ⇒ 它们的退步根本没进 `regressions`
⇒ 造了 Q12 退步之后输出里一个字都没印。

⚠️ **这正是预注册 D6 那句话抓到的**：逐字「⛔ 这一条要有正向对照，否则『默认不叫人』
可能是『压根没算』」。⇒ 判据先钉死才抓得到；补 Q12/S9 进方向表后：
造 3 处退步 ⇒ 三处**全印**，Q12 那行标 `·` +〔不叫人：已有闸在守〕，
而「要人看」只算 **2 处**并点名 `DIM-U16.useCases · DIM-X18`。

### 4.3 两侧读数汇总（⛔ 绿灯不算证据）

| # | 造什么 | 红侧读数 |
|---|---|---|
| **D2a** | 把 `llms.txt` 挪走 | `EXIT=2` · 逐字「DIM-X18 取不到数：llms.txt 不在 ⇒ 这一格的输入面消失了」+「⛔ 退出码 2 = 装置故障，不得读作『DS 没问题』」 |
| **D2b** | 拆掉 Q13 的分子判据 | `EXIT=2` · 分子地板红并逐字「⛔ 别把它读成『耦合度 0』…恒 0 = 零信息」 |
| **D3** | 闭集少一格 / 多一个未登记 id | 纯函数 `dimClosedSetMismatch` 自证覆盖（含「多出未登记 id 也必须红」的阴性对照）|
| **D5** | 注入一行「上一轮」使两格退步 | `EXIT=1` · 逐字点名 `DIM-U16.useCases 0.8 → 1` · `DIM-X18 0.2 → 0.4` |
| **D6** | 让只记读数的 Q12 退步 | 印出来（标 `·`〔不叫人〕），`needsHuman` **不含它** |
| **自证** | 把一条断言改成必假 | `EXIT=2` 并逐字点名 `MH-D1` ⇒ 证明自证真在跑 |
| **D8** | 全程 | DS 仓 `git status --porcelain` 与造故障前**逐字一致**（序列落 `~/.claude/ds-metrics/`）|

还原后 `cmp EXIT=0`；干净一轮 `EXIT=0`。

### 4.4 ⚠️ 两条如实登记（都不在预注册里）

1. **真序列的第一行被污染过，已删并留证**：Q13 修好**之前**跑过一次，写进了 `Q13=0 / Q13.rule=0`。
   留着它 ⇒ 下一轮会读出一个**假退步**（0 → 0.023 在 `up` 方向上）。
   ⇒ 删掉后重起第一个点，被删那行备份在 scratchpad 作证。
   ⚠️ 一般性教训：**序列类量具的「第一个点」要在算式验对之后才写** ——
   否则那个点会变成一条永久的假基线。
2. **`audit-gate-mount-declaration.mjs` 是第三例裸 `main()`**：它 `export function classifyHead`
   （表示意图被 import），但实测 `import` 它会**跑完整条闸并 `process.exit`** ⇒ 拿不到导出。
   ⇒ 本批因此没走 import，改走「跑它 + 解析它自印的 `扫描面 N` / `D1 N` + 对账」。
   修它要把 ~100 行顶层代码包进 `isCliEntry` 守卫（仓里已有 `scripts/lib/is-cli-entry.mjs`），
   ⇒ **独立提案，⛔ 不在本批**（它守着 94 个闸，重排 100 行的回归风险不该由本批承担）。

---

## 5 ⛔ 这次不能说的话

- ⛔ 不许说「8 个维度现在有人守了」—— **3 格只记读数**（它们各自的闸在守，本次不重复报警）。
- ⛔ 不许说「趋势已经可用」—— 第一轮只有 1 个点，与 Q3 那条登记同样的话：
  **「趋势」这个词要等序列有第二个点。**
- ⛔ 不许说 DIM-Q12 延续了 `prose:gate≈73:1` —— 那条原算法不可复现（DS §1 更正 ① 已钉）。
- ⛔ 不许说 X19 满足 G5 —— 它是**显式登记的例外**（结构上没有分母）。
- ⛔ 不许把「`audit:docs-site` 在 gate-chain 里」读成「U16 有人守」——
  实测它 `EXIT=0` 而 pending 非零。

---

## ✅ 2026-09-09 · 第二个点到了 —— 序列的第一个 Δ（⛔ 本轮只是读到它，不是我跑的）

`~/.claude/ds-metrics/ds-dimensions.jsonl` 现取 **2 行**：

| # | `measuredAt` | `dsHead` |
|---|---|---|
| 1 | 2026-09-08 | `70344ccc`（Q1 B1 批那次） |
| 2 | **2026-09-09** | `b091088e` |

**逐格 Δ（只有 3 格动了，其余 9 格逐字不变）**：

| 格 | #1 | #2 | Δ |
|---|---:|---:|---|
| `Q12` | 0.315789 | 0.345865 | **+3.0pp** |
| `Q13.rule` | 0.302326 | 0.340909 | **+3.9pp** |
| `Q13` | 0.023256 | 0.022727 | −0.05pp（**几乎不动**） |

⛔ **这一段能说与不能说的，逐条钉住**：

- ✅ 可以说「**第一个 Δ 已存在**」⇒ 上方 §不能说的话里那条「趋势要等序列有第二个点」的
  **前件已满足**。
- ⛔ **仍不许说「趋势」** —— 2 个点只给出**一个差值**，而「趋势」要的是**方向的稳定性**。
  ⇒ 准确的说法是「第一个 Δ 是 +3.0pp / +3.9pp / −0.05pp」，⛔ 不是「在上升」。
- ⛔ **不许把这个 Δ 读成本轮三次上闸的效果** —— #2 的 `dsHead` 是 `b091088e`，
  它落在本轮第一个提交（`40dc3aca`）之后、但在 `af89f008` / `495212dd` **之前**
  ⇒ 后两次上闸**不在这个 Δ 里**。
- ⚠️ `Q13` 那格几乎不动而 `Q13.rule` 涨 3.9pp ⇒ **两个分子的口径不同**（一个是闸级、
  一个是规则级），⛔ 别把它们当同一条曲线的两个读数。
