# 发布说明 v0.4.2（质量门禁收口）

[← 返回 README](../README.zh.md) ｜ 相关：[v0.4.1 发布说明](v0.4.1.md)

**发布日期**：2026-09-16 ｜ **区间**：`v0.4.1 → v0.4.2`
**规模**：代码修复 1 commit + 锁文件元数据 1 commit + 发布 1 commit；
源码与测试 8 文件 +106 / −54，无新增模块，测试套件与用例数不变（6 套 101 例全绿）

v0.4.2 是一次**纯代码质量**版本：清零 PR #7 上 Sonar「New Code 可靠性评级 = D」
门禁的全部 15 项告警（12×S2871、1×S3776、1×S7778、1×S1199）。
**没有任何用户可见的行为变化**——每一处修改要么是把代码原本就在做的语义显式写出来，
要么是内部拆分。

---

## 修复

### 字符串排序全部改用显式比较器（12 项 CRITICAL，S2871）

`Array.prototype.sort()` 不带比较器的写法被静态分析判为可靠性缺陷（默认序随元素
`toString` 漂移）。新增 `src/model.ts` 导出的具名比较器：

```ts
export function byCodeUnit(a: string, b: string): number {
  if (a < b) return -1;
  if (a > b) return 1;
  return 0;
}
```

它就是 UTF-16 码元序——与 `sort()` 默认行为**逐字节等价**，所以全部排序结果的既有
顺序不变、测试零改动。刻意不用 `localeCompare`：诊断/快照的键集顺序、告警门控签名、
`list` 输出序都是 wire 可见的，不能交给宿主机的 locale 决定。落点：
`src/registry.ts` 6 处（canonicalConfig、两个签名函数、两路 watcher 键、blocked 集）、
`src/cli.ts` 1 处（profile 枚举）、五个测试文件 8 处（含未被标记的同类，一并对齐）。

### `scanProject` 认知复杂度 24 → 约 6（S3776）

两处无损拆分（`src/registry.ts`）：

- 同服务剔除告警 + 变更门控（签名比对、告警循环）抽为方法
  `warnIdentityShadows(key, projectRoot, identityShadows): boolean`，返回
  `shadowChanged` 供诊断条件使用；
- scan 诊断的「值不值得写」大 || 条件与载荷字段抽为纯函数
  `buildScanDiag(input): Record<string, unknown> | null`，返回 `null` 即
  「干净且无配置」——**零配置项目不留痕**的约定原样保持，写入的载荷逐字段等价。

### 其余两项 MINOR（S7778 / S1199）

- `src/cli.ts` `collectLayers` 末尾两次 `layers.push` 合并为单次调用（实参自左向右
  求值，六层影子优先序不变）。
- `test/test-model.mjs` 第 17 用例去掉冗余裸块。

---

## 依赖元数据

`pnpm-lock.yaml` 的 `@deepseek-ai/cordis` importer specifier 由 `^4.0.1` 同步为
`^4.0.2`——`package.json` 自 v0.4.1 起声明的就是 `^4.0.2`（解析版本一直是 `4.0.2`），
此前锁文件差一行元数据导致 pnpm 每次运行都要求重锁。不改依赖解析结果。

## 升级

无破坏性变更，无需任何操作：不写不改任何配置文件，装载、deny、诊断、CLI 输出与
v0.4.1 完全一致。
