# 更新日志

本项目的所有重要变更均记录于此。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。

## [0.63.1] - 2026-11-05

### 变更
- **ADR 0033 P2 交付（页面内联 + 关联契约）+ ADR 0034 P2 交付（task-manifest / contrib / 单写者汇聚器）—— 2026-11-05**。
  **0033 P2**：`index.md` 不再渲染 `## 架构分层` / `## 结构分层`（搬进 `architecture.md`：前者早就是
  `## 语义分层` 超集，后者并进 `## 结构层诊断` 的摘要行），L1 由此 1,997 → **1,854 B**，
  才装得下 80 B 入口指针；物料开启时 L1 挂 `> 物料包（agent 入口）：[AGENTS.md] · [map.json]`（**1,934 B**），
  L2 每域挂「本域关系」块（最差页 `ontology.md` **1,482 B** ≤ 1,536）；新增 L3 `domains/<slug>/files.md`
  （`file:` id + `isEntry`/`isTest` 标记，按 L3 5,120 B 逐行试装）；新增 `domains/<slug>/relations.md`（14 份）
  与 `knowledge/<slug>.md`（14 份）两份 P2 物料产物；`knowledge.md` 的 §三/§四 迁走后 **83,025 → 13,718 B**，
  从 `DOCS_BUDGET_PENDING_PAGES` **转正**并恢复 SUMMARY 硬闸门。`CONTEXT_DOCS_VERSION` 0.7.1 → **0.8.0**。
  **一处口径重算**：`L2_RELATION_BLOCK` 150 → **112** —— 150 是 P0 时点按「1,536 − 1,373 = 163 余量」推的，
  P2 给每张域页加「[文件]」入口后最差基页 1,395 B、余量 141，留 20% 得 112；**1,395 + 150 = 1,545 > 1,536
  物理装不下**，按「数字是测量时点现场值」的纪律重算，不是把档位抬到 1,600。同时把 L2 的五个 L3 链接标签
  压成两字（`组件清单`→`组件` 等，省 30 B）——省的是字节不是信息，链接文字只做导航。
  **一处假违例修正**：`docsBudgetContract` 的 L2 逐页算式原按「产出页 + 增量」判，关系块内联后等于同一笔账
  算两次（1,460 + 150 恒超），改为先剥掉页尾 `## 本域关系` 还原**基页**再判「基页 + 增量 ≤ 上限」。
  **0034 P2**：新增 `src/portfolio/materialPortfolio.js`（**汇聚层唯一写入口**，只写
  `<portfolio-root>/.nice-aos/portfolio-material/`）+ `portfolio material manifest|build` 两个子命令
  （挂在既有 `portfolio` 命令族下，不新增顶层命令族）。`task-manifest.json` = 项目 × 域 × 关注面
  (graph/context/knowledge) 切片，内嵌 `budgets{maxReadKB:15,maxQueries:4}` 与
  `acceptance{tierRequired,evidenceCoordsRequired}`，本仓 dogfooding **42 切片 / 14 域 × 3**；
  contrib 草稿区 `<project>/.nice-aos/material-contrib/{domain}-{concern}.json`（Q2 拍板），校验器**复用
  `src/material/schema.js`** 的 id 文法与前缀白名单（不写第二份），`tier` 非 `inferred` 判**越权整文件拒收**
  ——worker 只能产 `inferred 0.7`（ADR 0021/0034 D4），`structural`/`reference` 归确定性派生；汇聚器逐项目
  转述 `material.json#sources` 两轴四态、**degraded 照列不弃**、串行写盘、`filesHash` 按 path 码点序复算
  （两次运行 `changed=0`）。配套 `test/portfolioMaterial.test.mjs` 9 条。
  **计数回填（P2 后实测，2026-11-05）**：`mdFiles` 23 → **37**（+14 `files.md`）、`treeFiles` 37、
  manifest 39、物料自有文件 **43**（15 骨架 + 14 relations + 14 knowledge）；`npm test` **1,760 例 / 0 fail**。
  **订正计划旧值**：计划 §5 记的 P1「mdFiles=35 / 磁盘 45」与实测（23 / 25）不符 —— 旧值是更早语料的现场值，
  按计划 §8「不得沿用本文数字」的纪律改记新测值。
  **另修一处基线缺陷**：本机 `snapshot.json` 停在 P1 入库前，导致 `structureRules.test.mjs` 三例红灯
  （`material.ca=0` / depth 多一档 / 共享邻居 56≠58），`refreshRepo` + `storage rebuild --kind code` 后归零
  —— 这也解释了「计划记 0 fail、复跑 3 fail」的差异：它是快照陈旧，不是代码回归。

- **ADR 0033 Q11 / Q12 拍板（2026-11-01）：两条待拍板的决策落定，且都落成代码常量 —— 知识域页的归属规则与上限、汇总层的字节档**。
  **Q11（知识按域切片的归属与上限）**：四口径实测（主域 72,822 B / 复制 179,115 B / 边归属 81,786 B / **边归属+枚举列折叠 56,354 B**，最大页 `ontology` **19,961 B**）
  ⇒ 定「**边归属 + 枚举列折叠优先 + 24,576 B/域页**」。**16 KB 被实测否决**：ontology 的「§三 36 行 + §四索引 42 行」固定部分折叠后仍有 19,961 B ⇒
  16 KB 档下 §四 的 179 行明细一行都放不下。**主域口径被否决的理由不是体积而是纪律**：「多域文档归第一个域」是生成器**自造**的归属规则 ⇒ 违反「物料不新增事实」。
  「折叠」这一步比 Top-N 丢行强：§三 反查行的「文档」列是一串路径枚举（`snapshot.js` 一行 29 份文档）、§四 索引行的「标题」列同理，
  **行的���实是「被 N 份文档引用 / 命中 M 个对象」，枚举可由命令重取** ⇒ 折成「共 N 份 · 回查 `knowledge doc-impact <id>`」后**行一条不丢**，
  合计反而降到 56,354 B（比现状 `knowledge.md` 的 §三+§四 74,249 B 还小）。落成：`MATERIAL_BUDGET_BYTES.KNOWLEDGE_DOMAIN_MD: 24576`（原 `null`）、
  `MATERIAL_BUDGET_PENDING` 清空（⑫ 的「未定」保护解除）、新增 `MATERIAL_KNOWLEDGE_ATTRIBUTION`（**归属规则写成常量而不是 ADR 里的一句话** —— 否则生成器会自己发明规则）。
  **Q12（汇总层三页要不要字节档）**：定「**给汇总层单列一档 `SUMMARY = 16,384 B`**」—— 三页此前「无档」（`docsBudgetTierFor` 返回 null），
  而「无档 ≠ 不限」，且「L1 <2KB」被读成「顶层全部 <2KB」正是这个口子。实测 1,688 / 2,598 / 85,054 B；P2 把 §三/§四 迁走后 `knowledge.md` 残留
  = **10,805 B** ⇒ 装得下。**迁移前列 `DOCS_BUDGET_PENDING_PAGES`**（**未验收**态：不是通过、不是违例，ADR 0028 三态），实仓闸门写成**会自己翻面**的形式：
  仍渲染 `## 四、文档决策索引` ⇒ 必须在 pending 名单里；已改成索引页 ⇒ 必须移除并恢复硬闸门。附带给 P2 的前置动作（结构层小节搬进 `architecture.md`）定了落点
  （1,688 + 266 ≈ 1,954 B ≤ 16,384）。档位阶梯补上 `L2 < L1 < L3 < SUMMARY` 单调性；汇总层不挂任何增量块，也不在「启动必读」路径上。

- **项目集 MCP server（`nice-aos portfolio mcp`）与 serve 项目集端点（ADR 0034 P1）—— 一个 server 路由 N 个项目，既有工具一字不改**。新增
  `src/portfolio/mcpPortfolio.js`（**零写副作用**：只读快照与物料产物，一个字节都不写）、`portfolio mcp` 子命令、
  `serve /api/portfolio/projects` 与 `/api/portfolio/facts` 两个只读端点（未配置项目集时返回 404 + `E_PORTFOLIO_NOT_CONFIGURED`，
  **不返回空清单** —— 空清单会被读成「项目集里没有跨项目事实」）。工具面 = **既有 16 个（各多一个可选 `projectId`）+ 3 个项目集工具**
  （`project_list` / `cross_impact` / `shared_deps`）；单项目清单 ⇒ 不带 `projectId` 自动路由，**同一调用与 `nice-aos mcp` 返回逐字节一致**（退化兼容）。
  resources 按项目分命名空间（`aos://material/{project}/AGENTS.md|map.json|material.json|graph/*.json`，**已存在的才列**），与知识卡的 `aos://cards/*` 隔离；
  未知 uri 归 `InvalidParams/-32602`（SDK 1.32.2 无专用 NotFound 码，plain `throw` 会被包成 -32603 丢语义），路径穿越被白名单挡住，一次读失败不拖垮 server。
  **一处口径订正（实现期实测）**：跨项目影响面**不合并成一张图再算** —— 多仓 id 相撞（每仓都有 `file:src/index.js`）⇒ 合并前要加项目前缀 ⇒
  而 `computeLocalImpact` 种子展开里的 `containsParents.get(id).startsWith("file:")` 判定**恒为假** ⇒ 「改一个方法 ⇒ 连带它所在文件」这条种子被悄悄丢掉 ⇒
  跨项目口径与单项目口径（`graph impact --local`，0033 D6/Q8 的唯一口径）不再等价 —— 正是 M11 要防的分叉，只是出现在「合并」那一步。
  故改为：**逐项目按原生 id 算本地影响面**（测试直接与 `computeLocalImpact(buildLinkGraph(snap))` 对拍 `affected` 与 `summary`）**+ 跨项目关系单独由 `portfolio review` 的匹配器给出**
  （同路径/同路由/同名接口/AST 指纹重复/共享依赖，逐条对应 XP-SHARED-01 / XP-CALL-* / XP-CONTRACT / XP-DUP-01 / XP-DEP-01），不混进影响面；
  `mergeLinkGraphs` 留给 P2 汇聚器做项目集层图事实。**降级显式**：缺快照的项目照列不弃（`state` = ready/stale/absent/broken + `reasons`），不阻塞其它项目；
  两轴四态**不重算** —— 转述物料产物自己记的 `material.json#sources`（0033 D4 的落点）。配套 `test/portfolioMcp.test.mjs`（13 条）+ `test/servePortfolio.test.mjs`（3 条）
  + `test/fixtures/portfolio-mcp-server.mjs`（真实 stdio 子进程：往返逐字节一致、stdout 零污染、未知 uri 错误码确为 -32602）。
- **物料层 schema v1 + `src/material/` 骨架（P0 契约冻结第三项）：契约落成代码，不是再写一份散文**。新增
  `src/material/schema.js`（**唯一事实声明处**：字节档 / 必填字段 / 枚举 / id 文法 / 排序 / 截断 / 五个校验器）、
  `src/material/index.js`（默认 off 的开关解析 + **文件计划**）、`test/materialSchema.test.mjs`（**25 条契约断言 + 2 条 P1 占位 todo**）。
  P0 只交付契约：这里没有 render、没有写盘、不碰 `.nice-aos/`，且测试把这条钉住（断言 `src/material/generate.js` **今天确实不存在**）。
  四件值得单独记的形状：**① 字节档的 `null` 只表示「契约未定」**（Q11 未拍板 ⇒ `KNOWLEDGE_DOMAIN_MD: null` + `MATERIAL_BUDGET_PENDING` 双向对齐，
  文件计划给它 `pending:true`，写盘器与门禁遇到 pending 只能报「未验收」），**预算键集 ↔ 路径绑定表一一对应且每档被真实文件吃到**
  （`DOCS_BUDGET_BYTES.L2 = 1024` 那类死常量这次前置成断言）；**② id 体系不新造 = 可执行**——`ALL_ID_PREFIXES` 由
  `OBJECT_TYPES` + `KNOWLEDGE_OBJECT_TYPES` 运行时反查（实得 **47** 个），物料白名单是其**真子集**（6 个），`MATERIAL_ID_RE` 与白名单**同源生成**，
  「改了表忘了改正则」当场红（实测一个坑：`u` 模式下 `\:` 是**非法转义**，blanket escape 直接 `SyntaxError`）；
  **③ `graph.source{snapshotId,commit,state,derive}` 与 `material.json` 的双源 `sources` 字段集不同**（差集钉住，`derive` 必须落在
  `GRAPH_DERIVE_ALLOWED` 里 —— D6 的「复用既有算法」从此由校验器消费），且 `MATERIAL_BUDGET_BYTES` 与 `DOCS_BUDGET_BYTES` **并列不合并**（ADR 0033 C5 推论 3）；
  **④ 确定性需要「有方向」的比较器**：`compareMaterialRows`（tier 按 confidence 降 → 数值降 → 其余码点升）+ `isSortedBy` 由写盘器与 `material verify` **共用**，
  否则 `weight` 按码点比会得出 `10 < 2`、`filesHash` 假抖。文件计划的算术与 §4.1 闭合：P1 自有文件 **15 = 8 md + 7 json**，
  P2 按域展开时 slug 去重 + 码点序（乱序输入 ⇒ 同一份计划）；`isMaterialArtifactPath()` 对 docs 树既有文件一律返回 false（含 Windows 反斜杠路径），
  「清理只删 `owned:"material"`」从此可反查。**订正一条本计划的既有验收措辞**：Node 22.23 实测**带 body 且失败的 `{todo:true}` 仍令 `node --test` 退出码 = 1**，
  所以 P1 的「先行失败测试」不能写成会红的断言（会破 D5「旧测试零翻红」，且这种红几乎一定在落地前被整条删掉）——
  改为 `test.todo()` 占位（报告可见、不改退出码）+ 今天就能绿的入口契约断言，§6 P0 行与 ADR 0033 ⑯ 已同步。
- **闸门当场抓到一处真违例，并暴露两条被写歪的口径（`CONTEXT_DOCS_VERSION` 0.7.0 → 0.7.1）**：新增 `src/material/` 让本仓多了一个域（18 → 19），
  `domains/_index.md` 随之 **1,511 → 1,565 B，顶穿 L2 档 1,536 B** —— 而 P0 第一项刚把这一页收进字节闸门，
  所以这次不是「产物又漂了」，是**闸门第一次咬人**。根因在生成侧：这一页是 L2 里唯一按域数线性变长且**没有上限机制**的页
  （L1 有 `renderCoverageSection`、域页有 `renderDomainKnowledgeSection` 兜底，只有域目录漏了）。
  修法是**按预算逐行装配**而不是放宽档位、也不是另配一个 TopN 常数：装不下的域整行不出现，页尾必须写
  「共 X 个域，本表显示前 N 个（按文件数降序）；其余见 `domains/` 目录与 tree.json，或 `serve` 后访问 /docs」（P6 截断注记），
  本仓实测停在 17/19、页 1,484 B。配套加一条**纯契约**测试（40 域夹具逼出截断 + 确定性 + 注记三件事），
  实仓那条只负责盯「截断有没有生效」——60 域的仓库不会出现在本仓闸门里，但会出现在夹具里。
  **同时订正两条写歪的口径**：① 实仓看门狗过去只认 `## 结构分层`，可本仓产出的是 `## 架构分层`（实测 191 B）——
  坐标并不缺（27 个模块节点、三档深度），是 `structureLayerGate` 判 `archLayer-usable`（`shared` 21/27 = 77.8% 未过 80% 塌陷阈值）
  ⇒ 按设计**不采用**结构层，那一节自然不出现，
  「小节根本不存在」被读成「P2 前置已做完」，于是 L1 顶到位时报的是**误导性的红**（喊"前置被绕过"，实际"根本没得搬"）——
  现在两种块名都认，并多断一条**前置算式**：搬完可搬块 + 80 B 指针必须回到档内，否则说明「搬分层块」不足以解锁 P2；
  ② L1 的「余量」不是资产 —— `renderCoverageSection` 贴顶装配，语料一涨就长到离 2,048 只差几个字节
  （本仓 2,023 B 余 25 B → **2,045 B 余 3 B**），所以 P2 的入口指针只能靠**搬内容**换空间，
  把「先腾地方再挂指针」写成「趁余量挂上」是错的。计划里那条「实测该小节 266 B」同样随快照变（本仓现值 191 B），已在 ADR 与 guide 就地订正。
  **骨架进了本体图，评审侧三条现场值跟着变**（ADR 0033 ⑱）：`src/material/` 让模块 26 → **27**、`shared` 占比 21/27 = **77.8%** —— 恰好跌破 `ARCH_LAYER_COLLAPSE_RATIO = 0.8`，
  `structureLayerGate` 从「采用结构层」翻成 `archLayer-usable`，L1 的 `## 结构分层` 那一节从此不产出（上面那条误导性红的真正成因）；
  `check matrix --level module` 的 **ARCH-06 命中集从 `{cli/commands}` 变 `{cli/commands, material}`**（`material` 今天 `ce=3 / ca=0`，只有测试引用它 ⇒ 真阳性，P3 接通消费面后 `ca` 才会出现），
  深度档从 `[0, 1]` 变 **`[0, 1, 2]`**（`material` 独自占 `depth=0` 的纯入口档，把入口层与叶子层拉开；**反证做过**：内存里摘掉 `mod:src/material` 重算，档位回到 `[0, 1]`）。
  `ARCH-03 / COUPLE-03`（方向反转）结论没变（**0 条**），但**理由变了**：现在不命中是因为方向真的对，不是因为区分度低 ——
  三条一起同步进 `test/structureRules.test.mjs` 与 `skills/nice-aos-code-review-skill/spec/review-rules.md` 的真阳性表（ARCH-06 的 `ce/ca` 也从手抄的 `196/50` 更正为 `213/25`）。
  **顺带统一一个端点口径**：`DOCS_BUDGET_BYTES` 这些数字是**含端点的上限**（`bytes ≤ 档` 算合规），生成侧 `fitToBudget` 一直按 `<=` 装配，
  实仓闸门却写 `<` —— 差一个字节。L1 与 `_index.md` 都是贴顶装配，下一次语料前移就可能正好落在档顶，那时闸门会报「顶穿」而产物其实合规：
  **假违例也是违例**，两边一起按 `<=`，口径写进常量注释。
- **本轮复跑（schema v1 之后按 0032/0033 的交付纪律走一遍：`doc-ingest → export docs → check docs --gate → eval → npm test → test:isolation`）**：
  `doc-ingest` 55 份 / **776** 块（775 → 776，本轮新增的 0.7.1 说明段进语料）/ 标题 **1,168** / 源 1,386,783 B；
  候选 **1,092 → 1,109**、可建链 **519 → 532**、标题档拒收 387、未解析 190 ⇒ 命中率 **47.5% → 48.0%**
  （**这一轮比率没降**：可建链涨了 13 条，因为 0.7.1 的截断注记与「本域知识」小节补出了可解析的引用 —— 上一轮「候选涨、可建链不涨 ⇒ 比率降一格」不是必然形态）；
  写盘 SQLite **11,894 对象 / 65,958 边**。`export docs`：**28 md / 109,356 B / `mdFiles == treeFiles == 28`**、`meta.files` 30、磁盘 31；
  `index.md` **2,045 B**（L1 档顶余 3 B）、`domains/_index.md` **1,484 B（共 19 个域显示前 17 个）**、最差域页 `ontology.md` 1,373 B、
  汇总层 `architecture.md` 1,688 B / `modules.md` 2,598 B / `knowledge.md` **83,973 B**；**幂等由第二次导出证明（`changed: 0 / unchanged: 30`）**。
  `check docs --gate` **exit 0、0 条警告**（coverage **68.4% = 13/19 域**、grounding 违例 0、staleness 0；真缺口 6 个域明示零知识），
  `ask eval` **35/35**（10/11/8/6 四组全绿，`strictMaxResults:25` 未翻面），
  `npm test` **1,726 tests / 1,716 pass / 0 fail / 8 skipped / 2 todo**，`npm run test:isolation` ✓ `aos.sqlite` 前后逐字节一致（末次 **69,615,616 B / `fee27055f2217f13`**）。
  **隔离读数订正一条**：镜像库哈希**不是常量** —— `knowledge doc-ingest` 会把文档边写进 `aos.sqlite`（本轮 65,958 → 65,961 边），
  同一次收口里连续三次复跑得到 `4f31610291872de0` → `3da019514ed613df` → `fee27055f2217f13` 三个不同哈希；
  该测试守的是**同一次 `npm test` 前后逐字节一致**，不是「等于某个历史值」—— P1 的物料生成器若写库，验收也只能按前者。
  **口径照旧**：上面那组 ingest 数是「本条目与 ADR ⑰ 末、计划 §C v1.6 三处补写**之前**一次复跑」的现场值；补写完再跑，
  候选 **1,109 → 1,115**、可建链 **532 → 535**、源 1,386,783 → 1,392,641 → 1,393,661 B，比率仍 **48.0%**，而 **docs 产物字节逐项不变**
  （`index.md` 2,045 / `_index.md` 1,484 / 最差域页 1,373 / `knowledge.md` 83,973），第二次导出 `changed: 0 / unchanged: 30`，`check docs --gate` **exit 0 / 0 警告**。
  本仓的**最终读数**以 [docs/guide.md](./docs/guide.md) 的「最新读数」行为唯一口径（已按末次复跑对齐）。
- **MCP resources 协议 spike（P0 契约冻结第二项）：8/8 通过 ⇒ P3 走 `resources/list` + `resources/read`，不启用「退回文件/CLI」分支**。
  证据落成会红的测试而非散文：新 `test/mcpResourcesContract.test.mjs` **9 条** + 夹具 `test/fixtures/mcpResourcesMaterial.js`
  （纯内存合成文本，**不读快照、不写文件**）与 `test/fixtures/mcp-resources-server.mjs`（仅作子进程 spawn 的 stdio 运行体）。
  实测 `@modelcontextprotocol/sdk@1.32.2` 的**低层 `Server`** 就能声明 `capabilities.resources`（不必切 `McpServer`/zod，
  与 `src/cli/commands/mcp.js` 现有取向兼容），`resources/list` / `read` / `templates/list` / `subscribe`+`sendResourceUpdated`
  全可用，**5.9KB markdown 与 5.8KB JSON 单次 `contents[0].text` 逐字节往返** ⇒ 物料自有文件（4KB/64KB/128KB 上限）不需要分片协议；
  **真实 stdio 子进程 stdout 零污染行**（现有 stderr-only 日志纪律继续够用）。三条实现期约束进 P3 契约：① 该 SDK **无专用 ResourceNotFound 码**，
  未知 uri 只能报 `InvalidParams/-32602`（plain `throw` 会被包成 -32603 丢语义，探测时真发生一次）；② `resources/subscribe` 必须 server 自注册 handler，否则 -32601；
  ③ `resources/list` 分页未压测（清单 ~40 条，P4a 复核）。**spike 没证的**：真实客户端（Claude Code / Cursor）是否消费 resources —— P3 现场验一次。
- **又一处「手抄派生量」被守卫（与 R9 / ADR 0033 C7 同族）**：`mcp.js` 的 `MCP_PROTOCOL_VERSION = '2024-11-05'` 只当默认值/文档用，
  真实协商发生在 SDK 内部（其 `LATEST_PROTOCOL_VERSION` 已是 **2025-11-25**），而原断言只校验「形如 `YYYY-MM-DD`」—— 常量漂了测试仍全绿。
  本轮**不改行为**（避免与 P3 的 resources 实现混在一处），把断言钉成 `MCP_PROTOCOL_VERSION ∈ SUPPORTED_PROTOCOL_VERSIONS`；
  **待办**：P3 让该常量改由 SDK 取值或删除，并在启动日志打印协商结果。
- **本轮复跑（spike 之后按 0032/0033 的交付纪律走一遍：`doc-ingest → export docs → check docs --gate → eval → npm test → test:isolation`）**：
  语料 55 份 / **775** 块（774 → 775；增量对应本轮新增小节，**未逐块核对归属**），候选 **1,074 → 1,087 → 1,092**、可建链恒为 **519** ⇒ 命中率 **48.4% → 47.7% → 47.5%**
  （**语料每扩写一次，候选涨、可建链不涨 ⇒ 比率降一格；本轮为收口而改的 `docs/guide.md`「最新读数」那一行本身又是一次扩写** —— 门禁不设命中率阈值正因如此）；
  docs 产物字节**逐项不变**（27 md / 110,247 B / `index.md` 2,023 B / 最差域页 1,373 B / `knowledge.md` 83,278 B），
  但首轮 `export docs` 报 **`changed: 21 / unchanged: 8`** —— 每页 frontmatter 的 `knowledgeSnapshot` 哈希**换值不换长度**，
  **第二次复跑才是幂等证明（`changed: 0 / unchanged: 29`）**；门禁 **exit 0**（唯一告警仍是 `coordinate-resolvable`：HEAD `80a9f68` ≠ 快照 `5c8bfb7` —— **P1 开工前复跑 `refreshRepo`**），
  评测 **35/35**，全量 **1,698 tests / 1,690 pass / 0 fail / 8 skipped**（+9 全在本轮新增的 `test/mcpResourcesContract.test.mjs`），
  `test:isolation` ✓ `aos.sqlite` 逐字节一致（`52500a1eb62455cb`）。
- **评测用例 r7 阈值第二次显式改期望（`test/eval/knowledge-search.jsonl`）**：ADR 0033 转 v0.3 + 计划转 v1.2 后按纪律重跑
  `doc-ingest → export docs → ask eval`，r7「grounding 四检」的严格(AND)候选 16 → **21**，`strictMaxResults:20` 变红；
  按 D4 立的规矩**改期望并在文件头留痕**（20 → 25，留 4 条余量）。**关键观测**：这次**文档行数没变（仍 828）**，
  只有检索块 729 → 773 与正文出现次数变了 —— 候选数由**正文内容**驱动，不由规模驱动；所以「锁一个语料规模数字」不够，
  A/B 必须锁知识指纹 `kfp:`（ADR 0033 Q9 / 计划 §7.2 据此改写）。
- **ADR 0033 转 v0.3（重基线，含 `refreshRepo` 二次复算）**：上游 ADR 0032 的 D0–D4 已随 v0.63.0 入库，本文全部基线数字重测
  （27 md / **110,247B** / 18 Domain / 24 Module / **2,133 对象** / 39 类型；L1 **2,023B** 余 **25B**；L2 现行上限 **1,536B**、
  最差页 `ontology.md` 1,373B；`knowledge.md` **83,278B**）。据此重写 C1–C5 与 C7、D2 文件数（P2 页面 **26 → 36**、
  `mdFiles` 27→35→71）、D6 修复清单（六处 → **两处已交付 + 四处仍开 + 新增 `link`/`IMPACT_REL_TYPES` 词表分叉**）、
  D8 执行面（**本仓无 CI**）、Q4/Q5/Q6/Q7/Q9 建议，新增 **Q10**（词表收口）与 **Q11**（切片上限）。
  **Q11 定稿时按实测改口**：`knowledge.md` 86% 在 §三/§四 两表，最大单域页按主域归属 **`cli` 22,923B**、
  按复制归属 **`ontology` 50,742B**（展开 70,441B → 169,332B，**2.40×**）—— 初稿的「ontology ≈17.5KB / 12,196B，
  建议上限 16KB → ~24KB」是**口径混算 + 漏算 §四 每文档明细块**（36,465B，远大于索引行 7,737B）的结果，已订正为
  「**先定归属规则与截断口径，再谈上限**；只调上限的解法作废」。
  并把「字节预算必须引用 `src/ontology/contextDocs.js:29` 的 `DOCS_BUDGET_BYTES`」写为前置（v0.2 全文未引用，是缺口）。
- **计划 v1.2（`docs/plan/project-material-iteration-2026-10-31.md`）前瞻契约同步**：§1.2 四条前置标为已满足、
  §4.1/§4.5/§6/§7.1/§8.1/§8.3/§9-C 按 v0.3 重算，新增 **P2 两件前置**（L1 结构层小节实测 266B 移入 `architecture.md`、
  `CONTEXT_DOCS_VERSION` 0.7.0 → 0.8.0 —— 0.7.0 已被 `12a7d05` 用掉）与 **P0 待裁定项**
  （`mdFiles == treeFiles` 是同一常量的硬断言，json 入 tree 必打破）；§8.3 决策表整体换成与 ADR 0033 同编号的 **Q1–Q11**
  （原文是另一套 Q1–Q8，编号冲突已消除并留痕）；§9-C 增补 v1.1 → v1.2 修订实录。
- **门禁报出的第三次落后现场复现（不是 bug，是它该干的活）**：ADR 0033 定稿时 `check docs --gate` 报
  `coordinate-resolvable` warn（HEAD `b84d830` ≠ 代码快照 `661acbd`；`git diff --name-only | grep -c "^src/"` 实测 **41** 个源文件差异），
  补跑 `action refreshRepo`（1,541 ms）后对象 **2,070 → 2,133**、`graph impact --local` **38 → 40**、`knowledge.md` 81,831 → **83,278B**，
  门禁回到 **exit 0 / 0 告警**、评测 **35/35**、导出幂等（`changed: 0 / unchanged: 29`）。
  **散文里的现场值随复跑前移，不是常量**；`§1.1/§1.3/§1.3.1/§3/§9-A` 的审计现场数字按纪律**保留不覆写**，
  计划文件头新增「⚠ v1.2 口径声明」划清现场值与契约的边界。

- **快照刷新把两条测试打红，并按现场纪律同步判据与 spec**：`refreshRepo` 后 `npm test` 出现 2 条失败（全量 1,680 例）——
  `test/structureRules.test.mjs` 的 **ARCH-02 兜底命中集**（`knowledge` 出边 15→20 但 11 条指向 `ontology`，
  `outConcentration` **0.467 → 0.550** 跨过 0.5 阈值，按判据退出「面宽且发散」）与 **COUPLE-06 共享邻居 55 → 56**。
  两条都不是判据变了，是结构变了：**判据一行未动**，只把断言与 `skills/nice-aos-code-review-skill/spec/review-rules.md`
  的实测表一起更新（`ontology 45/0.333`、`cli/commands 213/0.216` 豁免、`themes` 仍 `ca=28`/7 个依赖方、
  `archLayer` 分布 15/16 → **20/24 = shared**）并写明「数字随快照前移，复跑即修复」。复跑后 **1,672 pass / 0 fail / 8 skipped**，
  `npm run test:isolation` 确认 `.nice-aos/data/aos.sqlite` 前后**逐字节一致**（`9516b8ef…`）。
  ADR 0033 C7 与计划 §8.1 新增 **R9** 把这条记为「快照派生量被手抄进断言与文档」的同族证据 —— **D7 的生成式引用不止适用于技能层**。
- **本轮收口复跑（`doc-ingest → export docs → check docs --gate → eval → npm test`）**：docs 树终值
  **27 md / 110,247 B**、`index.md 2,023 B`（余 25 B）、`knowledge.md 83,278 B`、最差 L2 `ontology.md 1,373 B`；
  导出幂等（`changed: 0 / unchanged: 29`）、门禁 **exit 0 / 0 告警**（coverage 66.7%、violations 0、staleness 0）、
  评测 **35/35**。建链闸门读数同步：候选 906 → **1,061**、可建链 447 → **520**、命中率 49.3% → **49.0%**
  （`docs/guide.md` 已把新读数并列标注，避免读者拿一个当另一个）。

- **P0 契约冻结第一项：docs 预算常量与文件数三口径（ADR 0033 v0.3 → v1.3，`src/ontology/contextDocs.js`）**
  —— 这是 ADR 0033 要求的「字节预算必须引用唯一声明处」那条前置的落地：
  ① **删死常量 `L2: 1024`**（自 D2 起零引用点，却把「L2 <1KB」的说法带在 ADR 0032、`contextDocs.js` 头注释、
  context-builder 技能三处；本轮一并订正，L2 的上限从来是 **1,536**）；
  ② 新增**增量预算** `L1_POINTER: 80` / `L2_RELATION_BLOCK: 150`，并加 `DOCS_BUDGET_ROLES` 角色登记 ——
  **没登记角色的键进不来这份常量**（防死常量长回来的闸门），`CONTEXT_DOCS_VERSION` 保持 **0.7.0**（产物零字节变化不 bump，0.8.0 归 P2）；
  ③ 判档拆成两个函数，因为**上限档位与增量适用范围不是一件事**：`docsBudgetTierFor(path)`（与产物 frontmatter 的
  `layer:` 一一对应）给出 `index.md`→L1、`domains/*/*.md`→L2_WITH_KNOWLEDGE、L3→L3，
  而 `architecture.md` / `modules.md` / `knowledge.md` 是 **null = 无档**；`docsIncrementTierFor(path)` 决定挂不挂块，
  **`domains/_index.md` 与域页同档但不挂块**（1,511B，距档顶只剩 25B，按档位一刀切第一个顶穿的就是它）；
  ④ **三口径裁定**：`mdFiles == treeFiles` 是不许破的结构恒等式（`buildTree(files)` 的输入就是那批 md），
  物料 json **不进 `files`**、单独记账为 `dataFiles`（tree.json 侧栏字段），`meta.json.files` = md + `tree.json`（+ `docs.html`）、
  磁盘 = manifest + 1 ⇒ P1/P2 加 json 时两个计数纹丝不动，**默认 off 下旧断言零改动由全量绿证明**；
  ⑤ 新测试 `test/docsBudgetContract.test.mjs` **7 条**（6 条纯契约 + 1 条读 `.nice-aos/context/` 的实仓闸门，缺产物时 `skip`，
  与 `structureRules` 同族随仓库演进）；`test/outputDocs.test.mjs` 加 2 条三口径恒等式并把 `MD_FILES + 2` 手抄换成 `DOCS_STRUCTURAL_FILES`。
  **P0 暴露的两条新事实**：「L1 <2KB」管的是 `index.md` 这一页，不是顶层全部（实测 `architecture.md` **3,475B** /
  `modules.md` **3,109B** 从没受管）⇒ 新增待拍板 **Q12（汇总层要不要字节档）**；P2 的前置动作会把 `architecture.md` 推到 3,741B。
  实仓逐页闸门现场：`ontology.md` 1,373 + 150 = 1,523 ≤ 1,536（**余 13B**）、L1 2,023 + 80 > 2,048 ⇒ 断言写成二选一
  （装得下放行；装不下则 `index.md` 里必须还有 `## 结构分层`，否则说明是第三种东西把 L1 顶大了）。
  收口复跑：`refreshRepo`（快照 `5c8bfb7`，对象 2,133 → **2,134**）→ `doc-ingest`（55 份 / **774** 块 / `KnowledgeDoc` **829** 行，
  候选 1,074 → 可建链 520 = **48.4%**）→ `export docs`（27 md / **110,247B**，产物字节与上一轮逐项相同）→
  `check docs --gate` **exit 0** → eval **35/35**（`strictMaxResults:25` 本轮没翻面）→ `npm test` **1,689 / 1,681 pass / 0 fail / 8 skipped**，
  `npm run test:isolation` 镜像前后**逐字节一致**（`b941e236…`）。

### 修复
- **Express 路由抽取两处缺陷 —— 聚合路由整族丢失、无类型标注的 Router 实例不被识别（2026-11-05）**。
  在实仓 `asdm-agentlink` 上做项目集组合分析时暴露：快照里 `Route` 只有 73 条、express 42 条，
  而 `routes/managed-agent/` 整个主 API 面 **一条都没有**，`unmountedExpressRoutesCount` 自报 **177**。
  两处根因，都在抽取侧：

  ① **`collectRouterMounts` 只认 `use('/path', router)` 两实参形态**（`src/analyzers/tsAnalyzer.js`）。
  Express 的 `use` 允许「路径 + 任意个中间件 + router」，于是
  `app.use('/api/managed-agent', managedAgentDualAuth, managedAgentRouter)` 把 **arg[1] 的中间件当成了
  router**，真正的 `managedAgentRouter` 拿不到前缀；同时聚合路由的裸挂载
  `managedAgentRouter.use(registerRouter)`（无路径实参）**完全不被记录**。
  二者叠加，「前缀挂载聚合 router → 裸挂载子路由」这一常见结构整族失效。
  修法：`.use` 的**全部标识符实参**都登记为候选 router（由 builder 侧按「该文件是否真有路由」自然筛掉
  中间件），并记下 `.use` 的**接收者**；builder 改为沿挂载链递归合成前缀
  （`app.use('/api/x', A)` + `A.use(B)` ⇒ B 前缀 `/api/x`），带环保护。

  ② **`routerFactoryIsServer` 依赖 `facts.importMap`，而该映射由 `visit()` 在路由识别之后才填充**（同函数内
  时序错位）。后果：`const router = express.Router()`（**不带类型标注**）被判成非服务端 Router ⇒
  **整文件 0 条路由**；而 `const router: express.Router = express.Router()` 走类型标注分支不受影响。
  证据是完全重合的分类：`routes/managed-agent/` 下 22 个带标注文件全部入选、9 个不带标注文件
  （`tenants` `users` `workspaces` `digital-employees` `de-templates` `resources` `auth-service-config`
  `asdm-platform-integration` `auth`）全部落选，一个不差。
  修法：预扫顶层 import 构建 `preImportMap` 供该判定使用（import 本就具有提升语义）。

  **实测（asdm-agentlink 573 文件）**：`Route` 73 → **278**（express 42 → **247**，其中
  `/api/managed-agent/*` **0 → 213**）；`unmountedExpressRoutesCount` 177 → **49**；对象 5,383 → **5,588**；
  Neo4j 图库节点 125,398 → **131,836**。配套回归测试 **2 条**（`test/expressRoutes.test.mjs`
  「use(路径,中间件,router)」与「前缀挂载聚合 router + 裸挂载子路由 + 无类型标注」）——
  **回退源码时这 2 条恰好红、新代码 7/7 绿**，确认为真守卫。`npm test` **1,762 例 / 1,754 pass / 0 fail / 8 skipped**。
  **顺手记一条踩坑**：`facts` 缓存会让修复「看不出效果」（重扫耗时减半而路由数纹丝不动），
  验证必须加 `--no-incremental`。

  **未修的边界（诚实标注）**：就此案而言跨项目调用链仍为 0。目标侧（路由）已补齐，卡在调用侧 ——
  CLI 的 36 条请求全经 `this.request(method, path)` 通用助手 + `` `${this.baseUrl}/api/managed-agent${path}` ``
  模板拼接，路径是运行期参数，静态抽取一个都拿不到（该 CLI 快照只产出 6 个调用点，且全是
  `http://127.0.0.1:${port}/...` 回环）。这属静态分析对通用 HTTP 客户端封装的固有边界，需跨函数数据流分析，
  成本远高于本次修复；判断线协议一致性应走「路由逐条核对 + 契约字段对比」，不要依赖 `callChains` 维度。

## [0.63.0] - 2026-11-01

**v0.63 = ADR 0032 的五段全部落地（D0 信任地基 → D1 存量语义资产入库 → D2 docs 导出有内容 → D3 门禁与锚定 → D4 评测基线）**。
主线是把「事实 → 人可读且可门禁的项目说明书」这条**确定性**路径走完：仓内 ADR/计划/skill 成为可检索可建链的知识对象，
`export docs` 与 `knowledge export` 真带出「改这里要读哪几份决策、守哪几条约束」，`check docs --gate` 把覆盖率、证据与
新鲜度变成 CI 能判定的东西，`ask eval --mode search` 给检索/注入一条可复跑的基线。D0 不新增能力，只把 K0–K3 已有能力
变成可依赖的（计划：`docs/plan/knowledge-docs-export-iteration-2026-10-31.md`；决策：ADR 0031 与 ADR 0032）。
本轮**不写 wiki 叙事层、不接 LLM 生产链**，也不做向量检索（按 ADR 0031 D5b 不做）。

> **发布状态**：已发版 —— `package.json` = **0.63.0**，发版 commit `b84d830`（本地；未 push）。
> 本仓自 v0.60 起不再为每个版本打 tag（现有最新 tag 仍是 `v0.59.0`），版本号以 `package.json` 为准。

### 新增


- **镜像内容身份**（schema **v5，只追加**）：`aos_mirror_state` 加 `mirrored_json_size` / `mirrored_json_hash`。
  `mirrorFreshness()` 依次判 mtime（`MIRROR_FRESH_TOLERANCE_MS = 100`）与字节数 —— 「`cp -p`/回滚保住 mtime
  但换了内容」由第二步兜住，且**不读文件内容**；逐字节确证是新函数 `verifyMirrorIdentity()`，
  只在显式要求时付一次 sha256（`storage status --verify`）。v1–v4 的 `up` 源码一字未动
  （`checksumOf` 绑 `sha256(version:upSource)`，改旧迁移会硬错）
- **水位路径按 kind 解析**（`src/storage/snapshotPathRegistry.js`）：过去固定读 `dirname(getSqlitePath())/默认文件名`，
  既忽略 `saveSnapshot({snapshotDir})` 也忽略各 kind 自己的 kit —— 自定义快照目录会被误判成「永远新鲜/永远落后」。
  现在走注册表解析器（代码走 `src/ontology/snapshot.js`，六类领域快照走 `snapshotFileKit.js`），
  注册表本身是为了绕开 `overviewSnapshot → storage/index → sqliteSnapshot` 的导入环
- **检索面显式降级**（`src/knowledge/search.js` 的 `knowledgeMirrorState()` / `warnDegraded()`）：
  `search` / `show` / `trace` / `stats` 统一带 `source`（`sqlite`|`json`）+ `stale` + `reason`，
  **禁止静默空集** —— 「镜像落后」与「库里真没这条知识」分开说，`reason` 直接给修复命令；
  `trace` 另有机器码档（`KNOWLEDGE_MIRROR_STALE` / `KNOWLEDGE_NOT_MIRRORED` / `SQLITE_UNAVAILABLE`）
- **knowledge 全族接 `--envelope`**：12 个子命令（build/export/lint/compile/card new|list|show/search/show/trace/stats/query）
  逐个 `emitData(data, legacy)` + `failEnvelope/failWith(E_*, …)`，新增 13 个 `E_KNOWLEDGE_*` + `E_SQLITE_UNAVAILABLE`
  错误码与可执行 hint（`src/common/errors.js`）。**默认档逐字节不变**（44 次 CLI 调用改造前后对拍：
  stdout 只差 `durationMs`、退出码全同、stderr 失败输出纯增量），失败只允许追加 `errorCode`/`hint` 字段
- **`npm run test:isolation`**（`scripts/test-isolation-check.mjs`）：套件前后给 `.nice-aos/data/aos.sqlite{,-wal,-shm}`
  打指纹并比对 —— 主库逐字节一致才算通过

**存量语义资产入库（D1，ADR 0032 D1/D2）**

- **新对象类型 `KnowledgeDoc`**（`aos_types` 46→47，前缀 `kd:`）：把仓内白名单文档
  （`docs/adr` / `docs/plan` / `skills/**/SKILL.md` / `docs/guide.md`）扫成可检索、可建链的知识对象。
  **文件级行 + 块级行**两层：文件行带大纲/计数/`contentHash`，块行带脱敏 `indexText`。
  块按标题层级自适应下钻——整节 ≤ `splitBytes(4000)` **合块并携带子树正文**（早期版本在合块分支只取本节导语，
  小文档被切成空壳、正文凭空消失），超了才下钻；容器导语只由一个块携带；围栏内的 `#` 不算标题。
  实测本仓：**53 份 → 729 块 / 1,117 标题 / 原文 1,163,666 B → 索引 730,184 字符**（9 块截断、丢弃 10,483 字符，计数外显不静默）
- **`nice-aos knowledge doc-ingest`**（新子命令，`src/knowledge/docIngest.js`）：纯函数摄取内核 +
  `--dry-run` 出全套基线数字（不写快照与镜像）。**同一份 .md 两次摄取逐字节一致**（行内不落时间戳，
  CRLF/BOM 归一后同 hash）。脱敏边界不倒退：真实凭据打 `[REDACTED:assigned-secret]`，
  **含 `/` 的串按「文件路径引用」放行**（掩掉就把「文档点到代码对象」这条召回通路一起掩没）；
  正文仍绝不过 `OMITTED_FIELDS` 那道整行拒绝的闸（实测 `leakedFieldRows = 0`、凭据命中 1 处）
- **文档 → 代码对象建 `documents` 边，只收两档强度**：与卡片共用同一个解析器（`src/ontology/targetResolver.js`），
  但 **标题模糊档（inferred 0.7）在文档侧不接受**（`DOC_EDGE_CONFIDENCE_FLOOR = reference`），
  且目标类型必须落在 `documents` 的 `tgt` 公理范围内（容器类 Domain/Project 拒收并计数）。
  闸门放投影侧而非摄取侧：docIngest 只抽候选串，命中与强度由**当时的代码快照**决定
  - 实测：引用候选 906 / 命中 447 / 解析不出 209 / 标题档跳过 250 → **命中率 49.3%**；
    拒收计数 `docTitleTierRejected 238`、`docTargetTypeRejected 6`（后者全部来自迭代计划里显式写的 `dom:`）
  - 投影后知识→代码对象边 **445 条**（structural 22 / reference 415 / inferred 8；按来源 卡片 7 + 文档 430）
- **检索面接住第三类对象**：`knowledge search --type KnowledgeDoc`、`show kd:<路径>`（返回文件级行：大纲 + 分块数 + hash，
  无正文字段）、`stats`、本体目录、FTS 行数同步认得 `KnowledgeDoc`——漏一处的表现是「进了索引却查不出来」这种静默缺口
- **ask 注入白名单从「只卡片」扩为「卡片优先 + 文档补位」**：卡片仍占前位（决策 > 叙述）

**docs 导出有内容（D2，ADR 0032 D3/D4/D5/D6）**

- **`export docs` 注入知识**（`src/ontology/contextDocs.js`，`CONTEXT_DOCS_VERSION` 0.5.0 → **0.6.0**）：
  L1 `index.md` 加「知识覆盖」小节（卡片/文档/边数/域覆盖一行 + 指向全域页）、L2 `domains/<slug>.md` 加
  「本域知识」（本域卡片至多 Top 5 + 溢出折叠 + 回链）、新增全域页 **`knowledge.md`** 承载明细
  （卡片↔代码对象边表、按 `origin` 分组的文档决策索引、「改哪个代码对象要复核哪些知识」反查表、强度档口径）。
  L3 组件页**不改**（避免预算连锁反应）。实测：docs 树 **27 md**，`index.md 1,911 B`（<2,048）、
  最差 L2 `ontology.md 1,260 B`（<1,536）、`knowledge.md 77,562 B`；18 个域**全部**带「本域知识」小节，
  其中 12 个有知识资产（覆盖率 **66.7%**）、6 个明示「暂无知识卡片」；同一快照重复运行 **`changed: 0 / unchanged: 29`**
- **不谎报缺失**（与 `vocabulary --kind` 同一纪律）：无知识快照 / 读取失败 / 本域无卡片，三种情况小节**都仍要出现**
  并写明原因与补齐命令（「知识资产：**未接入**（缺知识快照）——补齐后本节自动出现内容，不省略」），
  `knowledge.md` 也照样存在并如实为空。省略小节会被 agent 读成「这个域不存在」
- **渲染器抽出为单一真源**（新 `src/knowledge/markdownRenderer.js`）：`knowledge export --format markdown`
  与 docs 树 `knowledge.md` **共用同一份渲染**（此前前者是 2,543 字符的统计壳，两者各渲染各的）。
  现 markdown 导出 **77,478 B / 7 节**，含卡片清单（要点 + 证据 + status/confidence + 源路径）、反查表、
  文档决策索引、聚合枢纽与**摄取口径与闸门拒收计数**（被拒多少才说明留下的可信）、强度档口径。
  内容纪律：只渲染事实与计数；**`indexText` 与正文一律不下发**；资产台账标识（含主机名/IP）与来源绝对路径
  **只出计数**；「敏感凭据」条目只进数字（实跑 991 条）
- **字节预算集中声明 + 渲染层自己折叠**：`DOCS_BUDGET_BYTES = { L1: 2048, L2: 1024, L2_WITH_KNOWLEDGE: 1536, L3: 5120 }`
  落在 `contextDocs.js` 顶部，测试从这里取值（散落在断言里的数字早晚和渲染器不同源）；
  装不下时**整条丢可选行**，连必选都顶不住退 compact 一行——不留给调用方检查，否则契约写在两个地方
- **新连接层 `src/knowledge/docsKnowledgeIndex.js`**（把知识边折成功能域事实清单，纯函数）：
  三条口径由测试锁死——归组只走 `documents`/`constrains`（枢纽边是统计口径不是耦合）；
  代码对象折域两条腿（按边解析 → 按名字兜底）；强度档由 `confidence` 反查不另存第二套事实。
  输出全显式排序（docs 树增量写盘的前提：不稳定顺序会让每次运行全量重写并狂广播 serve）
- **新只读命令 `nice-aos knowledge doc-impact <id>`**（`src/knowledge/docImpact.js`）：知识节点的**正向牵动视图**
  ——这张卡/这份文档落在哪些代码对象上（带档位与所属模块/域）、取代链上下游、`docCites` 互引。
  四种输入形态（`kc:` 卡片 id / `kd:` 文档 id（块级按文件归并）/ **已摄取文档**的裸文件名或相对路径（带不带 `.md` 都收）
  / 代码对象 id → **反向视图**并如实标注，且仅对**被知识边牵连过**的对象生效）；
  「查不到」按成因分开给提示（空 id / 聚合枢纽 / 未摄取 / 路径歧义 / 快照缺失），**绝不返回空集合**让 agent 误读成「没有影响」；
  `--limit` 折叠时标注总量不静默截断
- **影响面起点/终点口径收口**（实测 `graph impact "kc:analyzers.analyzer-contract" --local` → `affected: []` 是设计使然）：
  **不改 `IMPACT_REL_TYPES`**（`documents` 入白名单会把「一张卡文档化 20 个文件」读成「改一个文件牵动 20 处」）；
  卡片/文档作**终点**进影响面，作**起点**走 `knowledge doc-impact` / `knowledge trace`。
  口径已订正三处：`docs/plan/knowledge-index-roadmap.md`（G8 行 + K3 段）、[ADR 0031](docs/adr/0031-project-knowledge-index.md) D7、本节
- **serve `/docs` 与 `tree.json` 自动跟随**（`renderDocsHtml` 内嵌 md 原文，无需新增端点）：
  实跑 `/docs/` 页面内 18 处「本域知识」、`tree.json` `version 0.6.0 / totalFiles 27` 且含 `knowledge.md`、
  `/context/knowledge.md` 静态别名 200 / 77,562 B

**门禁与锚定（D3，ADR 0032「下一轮口径」落地）**

- **新 `src/knowledge/grounding.js`：确定性四检 + 新鲜度守卫**（纯函数、结构化违例，命名与 ADR 逐字对齐，全文不写「六检/七检」）：
  **符号存在**（卡片/文档里的 `src/...` 与显式 id 必须命中当前快照 `idSet`）· **快照锚定**（产物 frontmatter 的
  `snapshotId`/`commit` 与声明一致）· **凭据**（复用 `redact.js`，命中即 error）· **坐标可回**（`file:line` 按断言所属快照解析；
  HEAD≠快照 → `stale` 告警不直接 fail）。明确**不开**表名检（代码快照无 `Table` 类型）与调用链检（JS/TS 本无方法调用链），
  也不为它们预留代码位；「双读者一致」本轮退化为一条机检：docs 页里出现的每个 `kc:`/`kd:` id 必须存在于快照。
  实跑 `cardsChecked 4 · refsChecked 7 · refsSkipped 0 · refsUnverifiable 0 · credentialRowsScanned 832 · artifactsChecked 27 ·
  artifactIdsChecked 10`，**`coordinatesChecked 0` 如实标注**：当前卡片无一条 `file:line` 坐标，第四检处于「无可检对象」状态，
  `groundingViolations = 0` 只对前三检负责
- **新命令 `nice-aos check docs`**（`src/cli/commands/checkDocs.js`，`check.js` 只做组合）：三条知识指标
  `knowledgeCoverage`（现测 **66.7%** = 12/18 域）/ `groundingViolations`（现测 **0**）/ `knowledgeStaleness`（现测 **0**），
  规则读 `nice-aos.gates.json` 新增的 `knowledge` 段（现 **2 条**：`grounding-clean`、`knowledge-fresh`；
  coverage 按 ADR 口径**首轮只观测不设阈值**，配置里留 `knowledgeCoverageNote` 说明为什么没有第三条）；
  **退出码三态** 0 通过 / 1 知识没达标 / **2 门禁配置有误**（配错时一条门禁都不执行，且与「不达标」严格区分）；
  `--gate` 三种形态：不写 = 自动探测（缺失则不参与判定并打印原因，**exit 0**）/ 裸写 = 强制用默认文件（缺失 exit 2）/ 带路径 = 用该文件；
  `check diff` 的 8 条规则**一字未动**
- **归域是借来的事实，所以分开报**：`coverage` 同时给 `noKnowledge[]` 与 `unattributed[]` 两个桶 ——
  现测 6 个真缺口（`blueprint-ai-agent(2) / nice-aos-canvas-skill(4) / overview(5) / planning(6) / project-blueprint-ai-agent(2) / skills(5)`），
  `unattributed` 为空数组；哪天 `unattributed` 非空，覆盖率数字就不能再当成「这个域没知识」读
- **产物锚定**：`contextDocs.js` 的 `frontmatter()` 增 `knowledgeSnapshot` / `knowledgeScannedAt` / `cardsHash`
  三字段（27 份产物与 `meta.json` 同步，交付时点 `kfp:882273f8c17144fb` / `87f24a8a619b2b99`；收尾复跑后为
  `kfp:cbd9f2a1f4d44d3c` —— 文档本身在 ingest 白名单内，写一次就前移一次，属预期，以 `check docs --gate` 现值为准），
  `export docs` 的 `summary.stale` 从「只比代码 commit」扩为**同时比知识快照水位**；
  `check docs` 另有 `corpus` 段直接说出「产物锚定的语料 ≠ 当前白名单文件」（现测 `{fresh:false, changed:1, checked:55}` + 下一步命令），
  本轮此前只能靠人发现「ADR 写完语料从 53 份变 55 份而快照没跟上」
- **非法筛选值不再静默空集**（`src/knowledge/search.js`，与 D0「不许静默空集」同一条纪律）：
  `knowledge search --type doc` 从「0 条且无提示」改为 `E_KNOWLEDGE_SEARCH_TYPE_UNKNOWN` + `suggestions:["KnowledgeDoc"]`
  + `validValues` + hint；同族 `--status` / `--tag` / `--entity` 一并处理，并说明枢纽类型
  （Tag/Category/Source/Asset）不作检索结果、改用 `knowledge query tags|categories|sources|assets`

**评测基线（D4，ADR 0032「先测基线、不定终点目标」）**

- **新 harness `src/knowledge/searchEval.js`**：断言键走 `EXPECT_KEYS` 白名单 + `EXPECT_JUDGES` 判定表（未知键加载期即报错）；
  两类硬纪律——①用例 schema 有误时**短路**返回「用例集配置有误（未执行任何用例）」并逐条指名，一条都不跑、不落半截报告；
  ②单条用例抛错只标该条 `passed:false + error`，不炸整个套件；`buildSearchEvalDeps` 让真实检索可注入、测试可用假 deps 覆盖
  （`matchExpr` 形态、any→and 回退、降级模式用完恢复存储模式、无快照时 `model()` 给 null）
- **首轮评测集 `test/eval/knowledge-search.jsonl`：35 条 / 四类**（`relaxed-vs-and 10 · card-vs-doc 11 · ask-injection 8 · degraded 6`，
  口径下限 ≥30 且每类都覆盖）。首轮实跑 **35/35 PASS、`passRate 1.0`、exit 0** —— 但阈值全部来自本仓自测值（作者即被测方），
  **它是「别退化」的地板，不是「好用」的结论**
- **入口 `nice-aos ask eval --mode search`**（`askEval.js` 的 `--mode ask|search`，默认 `ask` 且原模式一字未改；
  非法 `--mode` 在执行前直接报错，因为拼错的模式不会有任何用例可跑）+ `npm run eval:knowledge`；
  首轮报告 `reports/knowledge-search-baseline-2026-10-31.json`（`reports/` 不入库，结论数字进 ADR 0032 as-built）
- **人评协议按口径落进报告结构**：双人二值判定（①答案正确 ②证据成立，各判是/否，**不做 Likert 打分**），
  标定样本 **10 条**（`r1/c1/c5/c6/a1/a4/a6/a8/d1/d4`，四类都有）量两人分歧率后再扩样本；
  报告里 `verdicts` 全为 `null` —— 「还没人评」是机器可读形状，首轮**不承诺「人评 ≥90%」**
- **CI 边界**：真实检索**不进 `npm test`**（外部 CLI 有成本，只进 manual）；`test/searchEval.test.mjs` 15 条只测 harness 行为 +
  用例集形状守卫（≥30 条、每类 ≥5、id 唯一、0 schema 错、标定恰好 10 且覆盖四类、期望里不许 `#NNN` 块锚点、
  `maxResults` 只允许 0、`budgetMaxChars` 必须是 100 的倍数）。**回归守卫 ≠ 评测基线**，两者分开计数、分开复跑
- **四条实测发现**（比通过率有用，全部写进 ADR 0032）：①混合 Top-K 里**文档排前面**（828 行文档对象对 4 张卡），
  「卡片优先」靠 ask 注入**两次独立检索 + 各给预算**实现，不是排序；②`storage off` 回退走**整句匹配**，
  自然语言中文问句召回 **0**、关键词问句召回 **35–284**；③**结构化筛选 + 中文整句会 0 命中**（结构化词法通道按空白切词），
  用例 `c11` 是它的常驻探针兼内容缺口探针（卡片补齐后要**显式改期望**，不许悄悄放宽）；④首轮 100% 是自设阈值的地板
- **收尾复跑（改源不改产物：重跑 `doc-ingest` → `export docs` → `check docs` → eval）**：语料 **55 份 / 773 块 / 文档对象 828 行**，
  命中率 **46.9% → 46.8%**（候选 1,037→1,039 / 可建链 486，ADR 0032 D8 第四版），知识快照水位 `kfp:cbd9f2a1f4d44d3c`；
  docs 树 `index.md 2,024 B`（< 2,048，**余量只剩 24 B**）、最差 L2 `ontology.md 1,373 B`、`knowledge.md 81,261 B`、
  markdown 说明书 81,064 B / 7 节；`check docs --gate` **exit 0**（`corpus.fresh:true`）。
  （这几个是**发版当次的现场值**；ADR 0033 定稿时因编辑白名单文档 + 补跑 `refreshRepo` 已前移到
  `index.md 2,023 B` / `knowledge.md 83,278 B`，见本文 Unreleased 段）
  评测这边**按计划咬了一次**：`r7`（问句「grounding 四检」）的严格 AND 候选随语料增长 15 → 16，`strictMaxResults:15` 变红、
  套件 34/35、exit 1。处置是**显式重定这一条阈值到 20 并在用例文件与 ADR 0032 D8 留痕**，不批量放宽、不静默重跑 ——
  这正是「与回归守卫的边界：评测基线可以变差，变差要在报告里看见」的那条纪律第一次真的被用到

### 修复

- **测试污染工作区镜像**（D0-4 根因）：`test/knowledgeScan.test.mjs` 等 spawn CLI 的用例只隔离了
  `--knowledge-snapshot-dir`，没隔离 `--sqlite-path`，`knowledge build` 成功后无条件写默认镜像，
  把开发机的知识分区刷成 fixture。现全部改为指向临时库，并由上面的守卫脚本长期兜住
- **`graph deps` 的卡片标题恒为 `null`**：`computeModuleKnowledgeCoupling()` 取的是 `srcNode.name`，
  而知识卡片节点带的是 `title` —— `buildLinkGraph()` 的节点投影补上 `title`，耦合表按 `title ?? name` 取
- **`knowledge card show`（redacted）恒打「(空结果)」**：单对象被交给只认数组的 `outputPretty`。
  现改为字段表（id/标题/模块/文件/摘要/标签/实体/引用/置信 + 正文提示）
- **redacted 档泄漏 `indexText`**：`card show --body redacted` 把编译产物整份发出，而 `indexText` 含正文 ——
  envelope 的 `data` 现在与 `card list` / `query cards` / serve 走同一条边界（要原文只能 `--body raw`）
- **投影面 `type` 口径**：`search`/`show`/`trace` 的 `type` 过去被卡片 `props.type` 顶掉，
  「按 `--type KnowledgeCard` 筛出来全是 `syntax-rule`」，agent 复核就恒为 0 条。现 `type` 恒为本体类型，
  OKF 卡片类型改由 `cardType` 承载；ask 注入段落显示的是 `cardType`（对人更有信息量的那一层）
- **两条 hint 指向不存在的路径**：`skills/nice-aos-knowledge/SKILL.md`（实际目录名带 `-skill`，改为引用 skill 名）、
  `knowledge/profile.yaml`（实际是项目根 `knowledge.profile.yml`）
- **`knowledge doc-impact` 的文档互引恒为空**（D2 测试逼出的内核 bug）：`docCites` 边建在**块**上
  （`kd:…#001`），而 `citeChains()` 拿文件级 id 逐字比对，于是「这份 ADR 明明引了卡，查询却说没引用」。
  现按 `#` 前截断折到文件级，与代码边同一套折叠口径（`src/knowledge/docImpact.js`）
- **`doc-impact` 的「裸文件名」解析不出省略扩展名的写法**：`kd:` 的 id 刻意丢 `.md`（`path` 保留），而
  `resolveSubject()` 只按 `d.path` 逐字比对，于是「顺着 id 复制出来的串」和文档承诺的「裸文件名或相对路径」
  对不上 —— 实测 `doc-impact knowledge-index-roadmap` 返回 `not-found`，带 `.md` 才命中。现补一次 `.md` 候选
  （带/省略都收，唯一性判定与歧义提示不变），并把两条**前提**写进四处文档与 `not-found` 提示：
  裸名只对**已摄取的文档**生效（代码文件不在白名单），反向视图只对**被知识边牵连过**的代码对象生效
- **文档把非法筛选值写成了命令示例**（补 ADR 0032 的验收清单时逐条实跑发现）：guide / 知识 skill / CHANGELOG /
  计划 §5 四处写 `knowledge search --type doc`，而 `--type` 的合法取值是本体类型**全名**
  （`KnowledgeEntry / KnowledgeCard / KnowledgeDoc`，help 与 `serveOpenApi` 的 enum 同源）—— 短名逐字比对
  `aos_objects.type` 必然落空，实测 `--type doc` 返回 0 条**且不给任何提示**。四处示例已改成全名；
  「筛选值非法不静默空集」（`E_KNOWLEDGE_SEARCH_TYPE_UNKNOWN` + 候选，先例见 `E_PANORAMA_FILTER_INVALID`）
  的口径连同同族的 `--status` / `--tag` / `--entity` 一起冻结进 ADR 0032「下一轮口径」，**随 D3 实现闭环**（见上面 D3 段）
- **既有镜像的本体目录不回填新增类型/边**（D1 实测缺口，`src/storage/sqliteSnapshot.js` 的 `ensureSeed()`）：
  过去只在 `aos_types` **为 0 行**时播种，版本升级新增的目录项永远进不了老镜像 —— 本仓现场是
  `aos_objects` 已有 782 行 `KnowledgeDoc`、`aos_links` 1,181 条文档边，而 `aos_types` 停在 46 行（不认得
  `KnowledgeDoc`）、`aos_link_types` 停在 72 行（不认得 `docHasChunk` / `docCites`）。`vocabulary` 读的是
  内存目录所以看不出，直读目录表的消费方（SQL 审计、按 `prefix` 反解 id）会把这些行当脏数据。
  现新增 `findMissingCatalogEntries()`：**只报「缺」不报「多」**（多出的行可能是外部合法扩展；据此重播会把
  no-op 写成真实页改动，而 `npm run test:isolation` 要求跑完套件主库逐字节一致），缺失非空时补播种一次，
  补齐后 `ensureSeed` 转为纯读。开发机镜像现已是 **47 类型 / 74 边**
- **`check docs --gate` 不接受裸写法**（D3 的新测试逼出）：选项声明没开 optional value，
  `nice-aos check docs --gate` 直接被 commander 拒掉 —— 而 ADR 口径要的就是「裸写 = 强制使用默认文件」这一档。
  现为 `--gate [path]`：不带值走默认文件且**缺失即报错**（不能静默当成「没有门禁」），带值用该文件，不写则自动探测
- **没有门禁配置的仓库里 `check docs` 文本档崩溃，并把「没配门禁」显示成「门禁没过」**（D3 的新测试逼出）：
  仓里没有 `nice-aos.gates.json` 时，payload 的 `gate` 是 `{status:'none'}` 占位对象，而 `renderGates()` 无条件
  `gate.errors.map(...)` —— 实跑抛 `Cannot read properties of undefined (reading 'map')`，命令因此 **exit 1**。
  一个纯展示层的崩溃伪装成了质量结论。现两处都修：CLI 按结果形状分支（`Array.isArray(payload.gate?.results)` 才走
  `renderGates`，否则打印 `note`），`renderGates` 自己也加占位守卫（**渲染器不信任调用方传进来的是什么**）；
  同时删掉 payload 里重复的 `gateNote` 键（同一信息两个键迟早漂移）。修复后该场景 **exit 0** 并打印「门禁：未找到知识门禁配置」

### 文档校准

- **ADR 0031 状态行**：「D7 的卡片↔代码建链仍属 K3 Proposed」→ as-built（K3 已于 2026-10-30 交付，commit 30f9dcf / v0.62.0）；
  §Trigger 的「不进 ask、不可全文检索」补 as-built 注；Consequences 同步
- `docs/guide.md`：新增「信任地基与契约统一（v0.63，D0）」小节；统一输出契约一节补已接入命令族与
  `E_KNOWLEDGE_*` 错误码清单
- 设计文档加 as-built 头注（列出 D0 收口的六项，2026-10-30 的实测数字保留为审计现场）；
  `docs/plan/knowledge-index-roadmap.md` 状态行指向 D0；`docs/plan/knowledge-docs-export-iteration-2026-10-31.md`
  状态改为「D0 已实施」，评审 §6.4 的 R1–R5 复核结论写明「五条均已修正」
- 知识 skill 的「context-builder 只会产出 0 Domain 的空骨架」补输入限定（只对知识投影目录成立，代码快照实测 18 Domain / 27 md）
- **新增 [ADR 0032](docs/adr/0032-docs-export-knowledge-injection.md)**「存量文档入库与 docs 导出接知识 —— 确定性项目说明书」：
  D1（`KnowledgeDoc` 对象 + 分块与 indexText 契约）/ D2（建边两档闸门 + 投影侧解析）/ D3（L1 计数、L2 链接、
  内容集中 `knowledge.md` + 不谎报缺失）/ D4（单一渲染器 + 隐私边界）/ D5（`doc-impact` 与起点/终点口径）/
  D6（连接层三条口径）逐项定案；Consequences 如实记两条代价 —— 引用命中率只有 **49.3%**（无阈值判定，留给 D3 门禁）、
  全域页 **77.5 KB** 破「目录页也应该小」的口头预期（它按任务加载，不进 L1/L2 预算链条）
- `docs/guide.md`：新增「存量文档入库与 docs 导出接知识（v0.63，D1–D2 · ADR 0032）」小节（`doc-ingest` /
  `doc-impact` / `export docs` 三条命令链、`kd:` id 去 `.md` 而 `path` 保留后缀的坑、两道建边闸门、
  字节预算与不谎报口径、起点/终点边界）
- `docs/plan/knowledge-docs-export-iteration-2026-10-31.md`：状态行改为「D0 + D1 + D2 已实施」并补 **D2 as-built**
  段（docs 树账本 / markdown 说明书 / `doc-impact` 四种输入逐形实跑 / serve 跟随 / 兼容冲击点四处核对 / 一处内核 bug
  + 收尾两处修复 / 六条偏差如实标注）；
  §5 量化指标表加 **「D0–D2 实测」列**（✅/⚠️/❌ 逐维度，门禁 D3 与评测 D4 当时标未做 ——
  本轮新增的 28 + 17 用例是**回归守卫**，不是检索评测基线；**D3/D4 交付后该列已扩为「D0–D4 实测」并把这两行改 ✅**）
- `docs/plan/knowledge-index-roadmap.md`：状态行从「只到 D0」改为「D0 + D1 + D2 于 2026-10-31 实施并验收」并指向 ADR 0032；
  G8 行的起点/终点口径与 K3 段同步订正（**D3/D4 交付轮**同文件状态行再进一步，见下条）
- `skills/nice-aos-context-builder/SKILL.md`：三明治结构补 `knowledge.md` 全域页与 `DOCS_BUDGET_BYTES` 四个预算值
  （L1 <2KB / L2 <1KB / 带知识 L2 <1.5KB / L3 <5KB），并写明「无知识时小节仍出现并说明原因」
- `skills/nice-aos-knowledge-skill/SKILL.md`：补 `doc-ingest`（白名单/分块/脱敏/基线数字）与 `doc-impact`
  （四种输入形态、错误档分因、只读不写图）两条工作流，markdown 导出从「统计壳」改为「七节说明书 + 单一真源」口径
- **`doc-impact` 输入契约的两条前提写进四处**（收尾实测逼出：裸名起初只有带 `.md` 才命中，代码文件喂进来直接
  `not-found`）：guide / CHANGELOG / ADR 0032 D5 / 知识 skill 的措辞都改成「裸名或相对路径**只对已摄取的文档**生效、
  反向视图**只对被知识边牵连过的代码对象**生效」，内核同步补 `.md` 候选与第三条 hint —— 文档承诺与实跑一致，
  而不是把承诺改窄
- `README.md`：能力速查新增「仓内文档成为知识、说明书真的有内容（v0.63，D0–D2）」条；快速上手补 `doc-ingest --dry-run`
  与 `doc-impact` 两行（用本仓真实卡片 id）；场景表补「这份决策落在哪些代码上」、产物表补 `knowledge.md` 与
  「知识说明书」行；安全边界补仓内文档摄取的同款脱敏口径；ADR 索引范围 0001–0031 → **0001–0032**
- **ADR 0032 补全到计划要求的完整度**（原稿只有 D1–D6 + Consequences + 兼容表）：加 **D7 非目标**、**D8「实测数字是审计现场」**
  （同日三版漂移：53→55→55 份 / 729→759→**767** 块 / 边 445→458→**461** / 命中率 49.3→48.7→**48.3%**，复核命令给出）、
  **验证**（12 行逐条复跑记录：`export docs` 当场重跑得 `mdFiles 27 / changed 0 / unchanged 29`，markdown 说明书 77,478 B / 7 节
  与 `knowledge.md` 标题逐行对拍，`--type KnowledgeDoc` 10 命中 vs `--type doc` 静默 0 条）、**回滚**、
  **决策请求定案 Q1–Q5**、**下一轮口径**（计划 §D4 点名要写进本文的**人评协议**在此落档）、**与既有 ADR 的关系**；
  补全过程本身给出两个新事实：①**编辑本文就让语料又涨**（759→767 块），而持久化快照仍停在 53 份 / 729 块、
  `export docs` 照旧打 `51/729` —— 没有任何命令报告 docs 树落后于语料，这正是 D3 产物锚定的存在理由；
  ②ADR 0033 的字节预算算术测于本 ADR 落地前（最差 L2 页 725 B、`knowledge.md` 预计 20–40 KB），实测已是 1,260 B / 77,562 B，
  已在关系表里记为「它需按新基线重算」，**没有替别的会话改它的文档**
- **ADR 0032 到 v1.3（D3/D4 交付）**：状态从「D3/D4 口径待实现」改为**四段全部 as-built**，新增
  **「D3/D4 as-built」实录节**（口径与实现分两节写是刻意的：口径不因实现而改，实现不达口径的地方当场认账 ——
  `coordinatesChecked 0`、首轮 35/35 是**地板不是结论**、混合榜文档主导、`storage off` 整句匹配召回 0 等四条实测发现全在此节）；
  「下一轮口径」标题去掉「实现仍属 Proposed」并加注「口径保持冻结原样」；验证表补 `check docs --gate` 与
  `ask eval --mode search` 两行、订正 `--type doc` 一行
- `docs/plan/knowledge-docs-export-iteration-2026-10-31.md`：状态行升到 **1.4 / 五段全部实施完毕**，新增
  **D3 as-built**（四检账本 + 三条指标现值 + 门禁 2 条规则的理由 + 反例清单 + 两处被逼出的缺陷 + 相对原稿的偏差）
  与 **D4 as-built**（交付面 / 35 条首轮结果 / 两条硬纪律 / 人评协议落点 / 四条实测发现 / CI 边界 / 与回归守卫的分界）；
  §5 表头改「D0–D4 实测」，门禁与评测两行从 ❌ 改为 ✅（并保留「命中率只是基线读数」的限定），
  D2 as-built 里「`check docs --gate` 属 D3 本轮未做」那条偏差加删除线并指向新段落
- `docs/guide.md`：新增「知识门禁与评测基线（v0.63，D3–D4 · ADR 0032）」小节 —— `check docs` 三态退出码与 `--gate` 三形态、
  三条指标各自能不能当阈值用、`npm run eval:knowledge` 的 manual 定位与人评协议，以及「正文不下发、诊断不回显凭据」的边界
- `skills/nice-aos-knowledge-skill/SKILL.md`：命令参考补 `check docs` / `ask eval --mode search` / `npm run eval:knowledge`，
  并写明两条使用纪律 —— **覆盖率与命中率不是门禁阈值**（`knowledgeCoverage` 首轮只观测）、
  **别用混合榜验卡片优先**（卡片优先来自两次独立检索 + 各给预算）

### 测试

1680 tests / 1672 pass / 0 fail / 8 skipped；`npm run test:isolation` ✓ 工作区 `aos.sqlite` 前后逐字节一致
（`65814528:a9f0a129faae8a62`，收尾连跑两次同值）。D0 阶段口径：1581 / 1573；D2 收尾：1627 / 1619；D3+D4 交付：**1680 / 1672**。
指纹与 D2 收尾时记的 `972ff2d8da3a940c` / `bdf93d93844ce2a6`、D3/D4 首次复测的 `086fa54dfeb81f9c` 不同是**目录回填补播种**与**本轮重跑 `doc-ingest`**
（语料随文档编辑增长，快照与镜像内容跟着变）—— 跑完套件前后依旧逐字节一致。本轮新增与同步的守卫：

- `test/knowledge-grounding.test.mjs`（D3 四检 + 新鲜度守卫）：正例全过、口径点名的三条反例逐条成对
  （不存在的 `src/foo.js` / 显式 id 不在 `idSet` / 手改 frontmatter 的 commit 与 `cardsHash` / 正文塞假 token 且
  **诊断不回显凭据值**）、坐标超行数 → error 而行数未知 → 跳过（**null ≠ 0**）、
  HEAD≠快照 → warn 不 fail、docs 页 `kc:`/`kd:` 机检、双快照缺失走未知告警不误报、两次运行顺序确定
- `test/checkDocsCli.test.mjs`（**14 条**，D3 门禁与 CLI）：三条指标出数与「文本档 = JSON 档同结论、两次运行逐字节相同」、
  口径点名的三条反例在 CLI 档各复现一遍（含「没配门禁也否决」vs「只告警 exit 0」的分级）、`--gate` 三种形态、
  退出码三态（0 / 1 不达标 / **2 配置有误且一条门禁都不执行**）、无门禁配置时 exit 0 + 「门禁：」说明（对应上面的崩溃修复）、
  `null = 未知不是 0`（缺知识快照 → coverage `null`，配了规则按未通过计）、**归域是借来的事实**
  （无代码成员的域进 `unattributed`，不与 `noKnowledge` 混报）、语料漂移只提示不擅自否决、
  `validateKnowledgeGates` 每条配置错误都指名道姓
- `test/searchEval.test.mjs`（**15 条**，D4）：harness 行为（假 deps 覆盖 `matchExpr` 形态、any→and 回退、
  降级模式用完恢复存储模式、无快照时 `model()` 给 null、筛选值非法抛错、单条用例抛错不炸套件、schema 错短路不执行）
  + 用例集形状守卫（≥30 条、每类 ≥5、id 唯一、0 schema 错、标定恰好 10 且覆盖四类、期望里不许 `#NNN` 块锚点、
  `maxResults` 只允许 0、`budgetMaxChars` 是 100 的倍数）—— **不在 CI 跑真实检索**
- `test/eval/knowledge-search.jsonl`（**35 条**，评测基线本体，manual：`npm run eval:knowledge`）；
  它与上面的回归守卫**分开计数**，报告 `reports/knowledge-search-baseline-2026-10-31.json`（`reports/` 不入库）
- `test/knowledge-doc-ingest.test.mjs`（**28 用例**，D1）：分块与合块（小文档不丢正文的回归）、围栏内不认标题、
  容器导语只由一个块携带、两次摄取逐字节一致、脱敏与「含 `/` 放行」边界、两档闸门与类型档展开
  （`CodeUnit` 按目录展开，防 K3 卡片边被静默误杀）、检索面多处同步、CLI 隔离与幂等
- `test/knowledge-docs-export.test.mjs`（**17 用例**，D2）：连接层三条口径（归组只走 `documents`/`constrains`、
  折域两条腿、档位由 `confidence` 反查）、不谎报三档、`fitToBudget` 折叠步长（387 → 211 → 102 B 实测值）、
  单一渲染器对拍（前 7 个 `## ` 标题与三档表逐行相同）、`doc-impact` 四入参与错误档（含**省略 `.md`** 的裸名/相对路径）、CLI 幂等与快照逐字节不变
- `test/outputDocs.test.mjs`：文件数与预算常量改从 `contextDocs.js` 取值（`MD_FILES` / `DOCS_BUDGET_BYTES`），
  新增 `knowledge.md` 存在性与「未接入也写明原因」断言
- `test/contextDocsStructure.test.mjs`：汇总文件 4 → **5**（`knowledge.md` 与结构层出场无关，永远产出）
- `test/storage/sqliteSnapshot.test.mjs`：`ensureSeed` 目录落后时补播种、补齐后纯读不写、
  「只报缺不报多」三条断言（对应上面的镜像目录回填修复）

---

## [0.62.0] - 2026-10-30

**知识与代码接成一张图**（ADR 0031 / roadmap K3）：卡片能连到代码对象，改代码能算出「哪些卡片要复核」，
问答能带上项目沉淀的规范，全景矩阵能反查「缺哪类知识、建议补哪张卡片」。

> **发布状态**：本版**尚未发布到 npm registry**，tag 与发布动作另行执行。

### 新增

- **共享目标解析器**（`src/ontology/targetResolver.js`）：`buildTargetIndex` / `resolveReference(s)` /
  `extractTargetsFromQuestion` / `collectExplicitIds`。ask 图上下文与卡片建链**共用同一套索引与档位判据**
  —— 原来的 `EXPLICIT_ID_RE` 字符类 `[A-Za-z0-9_\-./#@]+` 既不含非 ASCII 也不含 `kc:` 前缀，
  已扩为「代码 39 个 prefix + 知识 8 个 prefix」；非 ASCII 知识 id（`cat:开发与架构`）另走 CJK 通道
  并**按 idSet 逐字回退截断**（贪婪匹配会把句尾中文一起吞掉）
- **卡片 → 代码对象建链**（`src/knowledge/dataMapAdapter.js`）：`references[]` → `documents`，
  `entities[]` → `constrains`。**命中才建边**（解析不出来不建 —— 臆造的边会污染影响面遍历）；
  单个引用串最多建 5 条边（`CODE_REF_MATCH_LIMIT`），否则一个泛化实体名会连到几十个对象
- **逐边强度档**（schema v4）：`aos_links` 新增 `confidence REAL` 列（迁移 v4 **只追加**，
  既有迁移源码不动；ALTER 幂等 —— schema.sql 已含该列的全新库要先查列再改）。
  `aos_link_types` 的 confidence 是**按边类型**的，放不下逐边差异。同一对端点被多通道命中时取**最强档**
  （structural 1.0 > reference 0.9 > inferred 0.7）—— 强度只由事实决定，不由遍历顺序决定
- **混合图与合并**（`src/ontology/linkGraph.js` 的 `mergeLinkGraphs`）：代码图与知识图合并成一张，
  「改这个模块，哪些卡片要复核」直接可答。合并策略是「各自建图 → 合并图」而不是「合并 dataMap」——
  合并 dataMap 会让链接抽取走混合图全量分支（29 个 linkImpl × 1.3 万对象 ≈ 36 万次抽取），
  而两侧的边本来不相交，合并图得到的边集合完全一致却快得多
- **`graph impact --local`**：不连 Neo4j 的本地混合图路径；**未配 Neo4j 时自动落到本地**
  （影响面不需要图数据库）。**`graph deps --local`** 输出「模块 × 规范」耦合
  （`src/knowledge/knowledgeGraph.js` 的 `computeModuleKnowledgeCoupling`）
- **ask 注入项目卡片**（`src/cli/commands/askKnowledgeContext.js`）：RRF Top-K 卡片片段追加在基础上下文之后，
  默认开启（`--no-knowledge` 关闭，`--knowledge-limit` 调条数）。每条带卡片 id、源文件路径与要点摘要，
  按字符预算裁剪（放不下的整条丢弃，不截半）。**只注入 `KnowledgeCard`** —— 外部条目库有自己的检索面，
  混进来会把 Top-K 被上万条外部条目淹没
- **`/api/ask/context?knowledge=1`** 增 `knowledge` 段（默认不注入，既有消费方的响应体量与形状不变）
- **panorama 卡片 facet 与缺口建议**：卡片数进矩阵 `facets`（`kind:'cards'`）；
  `latent` / `no-channel` 缺口的 `action.cardAdvice` 给出「该补哪类知识卡片」的建议
  （由现有卡片类型分布 + 类别×阶段**推导**，不是静态句）
- **蓝图二部视图**：`knowledge export --format html` 增「卡片 × 代码对象」页签（左卡片右代码对象）；
  `kindOf` 补 `kc:` 前缀（此前卡片被当 entry 渲染）
- **项目自带卡片**：nice-aos 仓库新增 `knowledge/cards/`（4 张：快照双层契约 / 卡片→代码建链 /
  影响面白名单 / 分析器契约）—— K3 的 dogfooding，也是验收脚本的可复现输入

### 行为变更与修复

- **影响面白名单加 `CONSTRAINS`、不加 `DOCUMENTS`**：卡片「约束」代码对象 ⇒ 卡片依赖它 ⇒ 改代码要复核卡片；
  卡片「文档化」某对象不构成依赖，加进去会把「有文档的代码对象」全变成受影响对象（噪声远大于信号）
- **FTS 通道两处检索质量修复**（`src/knowledge/search.js`）：
  ① 结构化筛选原先在 JS 侧做、而 FTS 先取 500 条再过滤 —— 宽召回时目标卡片被 LIMIT 截断在外面；
  改成把同一份筛选条件下推成子查询（`buildFilterSql` 共用），**过滤先于 LIMIT**。
  ② FTS 行原先按插入顺序返回，RRF 的 rank 用的是插入序而非相关度 —— 改成 `ORDER BY score`（bm25 越小越相关）
- **问句要用宽松召回**：新增 `segmentQueryAny`（词内 AND、词间 OR）+ 复用 `askFocus.extractQuestionTokens`
  取词。`knowledge search` 的 AND 语义不动（关键词场景更准），ask 注入走宽松模式 ——
  自然语言问句里一半是虚词（「怎么定」「要遵守哪些」），整句 AND 必然落空。**索引侧分词完全不变**
- **panorama 卡片 facet 是附加项**：**不进 FACETS 表、不进 `cell.count`** ——
  前者会让「数据源不全」时把本该 `no-channel` 的格误判成 `absent`；
  后者会让 `count>0` 把 `latent` 格变成 `covered`（缺口凭空少一截）。实测 64 格
  status / count / evaluableRatio / density 逐格不变
- **卡片 facet 被排除在「空渠道」诊断之外**：卡片不是取证渠道，混进空渠道列表会误导 agent 去补它补不了的东西
- **`adviseGap` 拆成 `adviseGapCore` + 外壳**：外壳只挂 `action.cardAdvice`，**不改 cause 链、不改 headline**
  ——「事实缺没缺」与「知识写没写」是两件事
- **卡片快照带源文件相对路径**（`path`，如 `knowledge/cards/<module>/<topic>.md`）：
  ask / serve 注入时要能回溯原文。相对路径而非绝对路径 —— 绝对路径会把开发机目录结构写进快照
- **serve 的全景缓存指纹纳入知识快照**：否则新增卡片后 `/api/panorama` 一直返回旧缓存
- **MCP `panorama_matrix` 惰性读卡片**（`createToolRegistry({ snap, knowledgeCards })` 可显式注入）：
  注册表在每个 MCP 会话都会建，不该让无关工具为一份知识快照付解析代价
- **测试隔离补 `NICE_AOS_KNOWLEDGE_SNAPSHOT_DIR`**（`test/panoramaCli.test.mjs`）：
  panorama 现在会读项目卡片，不隔离的话「某类不可评估」的断言会随开发机状态随机翻车
- **修掉一个与本版无关的既有假红**（`test/overviewWatcher.test.mjs`「单文件 target：watchFile 跨平台工作」）：
  Node 的 `fs.watchFile` **首次基线 stat 是异步的** —— 用例在 `start()` 后立刻写文件，基线 stat 一旦晚于这次写入，
  基线就把新 size 记成初始值，之后每次 poll 都看到 size 未变 → 回调**永不触发**（实测隔离跑 5/5 通过、
  全量并行跑必挂：线程池繁忙时基线 stat 被拖迟）。修法是写入前先等基线落定（对齐同文件其它用例的 250~350ms 节奏），
  并把等待改成「没到就换个长度重写」的有界重试。只改测试，不动 `src/overview/fileWatcher.js` 的产品行为。

### 实测（nice-aos 仓库自身 + 真实知识库 `C:\knowledge-md`）

`knowledge compile`（4 张卡片）→ **11063 对象 / 64649 条边**（卡片→代码边 14 条：structural 5 / reference 0 /
inferred 9），投影与 `aos_links` **逐条一致**，`danglingEdges=0 / selfLoopEdges=0 / duplicateEdges=0 /
leakedFieldRows=0`；`storage rebuild --kind knowledge` 连跑两次结果一致。

```bash
nice-aos graph impact "mod:src/analyzers" --local --json
#   → affected[].type = ["KnowledgeCard","SourceFile"]
#   → kc:analyzers.analyzer-contract（hops=1，viaRels=[CONSTRAINS]）
nice-aos serve &
curl -s "http://127.0.0.1:8420/api/ask/context?q=分析器注册表扩展点怎么定&knowledge=1"
#   → knowledge.hits[0].id = "kc:analyzers.analyzer-contract"
#   → knowledge.hits[0].path = "knowledge/cards/analyzers/analyzer-contract.md"
```

全量回归 **1557 tests / 1549 pass / 0 fail / 8 skipped**（新增 19 项 K3 用例）；
`check diff --gate nice-aos.gates.json` 8 条规则通过（`no-link-removal` 保持既有的 72 条警告）。

### 验证状态（诚实边界）

- **真实 Neo4j 写入仍未验证**（K2 遗留）：本机与共享开发库都没有可用 Neo4j。
  `graph push --kind knowledge` 走的是本地投影，边的 Cypher 写入路径本身已由
  `test/knowledgeNeo4jSink.test.mjs` 用假 driver 覆盖，但**接真实库后仍需复跑一次做端到端确认**
- `card review/approve/supersede` 写工作流与知识子命令统一 `--envelope` 未实施
- 正文级大规模语料的构建体积/耗时仍需用真实卡片库补基线

### 文档

- 更新 [ADR 0031 项目知识索引](docs/adr/0031-project-knowledge-index.md) 与
  [本地路线 K0–K4](docs/plan/knowledge-index-roadmap.md)（K3 as-built 记录、验收脚本与命令面校准）

## [0.61.0] - 2026-10-30

**知识资产图谱升级为项目知识索引**：`knowledge-snapshot.json` 成为第七类快照（ADR 0031 / roadmap K0），
项目内 OKF-lite 卡片成为可校验、可重建的知识 SoT（K1），FTS5 + 结构化筛选 + 图扩散的 RRF 检索
贯通 CLI、MCP 与 HTTP（K2）。知识条目、卡片及其关系与代码图谱共用存储边界，但代码↔知识影响面和
ask 注入仍属于后续 K3。

> **发布状态**：本版**尚未发布到 npm registry**，tag 与发布动作另行执行。

### 新增

- **知识本体目录独立成模块**（`src/knowledge/knowledgeOntology.js`）：7 个对象类型
  （`KnowledgeEntry` / `KnowledgeCard` / `KnowledgeCategory` / `KnowledgeTag` / `KnowledgeSource` /
  `KnowledgeAsset` / `KnowledgeAssetKind`）、8 个 linkType（5 聚合 + 3 归属）、端点公理、
  prefixMap（含 `ent:`/`doc:` 双前缀反解）、能力发现载荷。
  **刻意不进 `blueprint.js`**：代码快照的顶层键集合被 hubContracts 冻结、`ONTOLOGY_META.categories`
  被 panoramaCli 断言为 8 类，塞进去要么破契约要么破断言。两份目录只在 `seedOntologyCatalog()`
  一处合并播种，合并时**前缀冲突即硬错**。`aos_types` 因此 39→46、`aos_link_types` 58→66
- **DataMap 适配器**（`src/knowledge/dataMapAdapter.js`）：快照容器键→本体类型名、kebab 边名→
  camelCase linkType、从 `entries[].category/tags[]/sourceKind` **派生归属边**，输出顶层 `Link`
  显式边载体键与 `_meta.knowledge` 投影诊断（`aggregateEdges / membershipEdges / rowsWithoutId /
  leakedFieldRows / unknownSnapshotEdges / danglingEdges / duplicateEdges / selfLoopEdges`）。
  同输入两次投影字节级一致（确定性承诺延伸到底）
- **`Link` 是显式边载体键**（`sqliteSnapshot.js` / `linkGraph.js`）：代码快照没有该顶层键，且 `Link`
  行没有 `id`，所以对象展开循环天然跳过——**知识复用了代码的整条存储链路，没有第二套写入**。
  `saveSnapshot` 在对象事务内追加 `Link`→`aos_links` 物化并返回 `links`；`loadSnapshot` 只在分区
  真有边时挂回 `Link`（代码镜像继续与 JSON 同形）；`extractLinks` 合并显式边，使知识边与代码边
  **同源产出**，`graph *` 与本地影响面无需为知识另写遍历
- **命令面**：`query --kind code|knowledge`、`vocabulary --kind knowledge`（类型可达 / 边 active /
  快照缺失不谎报）、`storage rebuild --kind knowledge`、`graph push --kind knowledge`；
  `knowledge build` 成功后自动落 SQLite 镜像，失败降级不阻断并提示可事后 rebuild
- **K1 项目卡片与编译门禁**：新增 `profile.js` / `cardSchema.js` / `redact.js` / `compiler.js`，
  支持 Profile 驱动的 OKF-lite 字段与章节校验、断链和 `supersedes` 环检测、凭据/高熵串脱敏、
  源文件 SHA-256 `contentHash` 与确定性索引。CLI 新增 `knowledge lint [--gate]`、`compile`、
  `card new|list|show`、`query cards`；`knowledge build` 重建外部条目时保留项目卡片
- **K1 卡片图边**：新增 `cardInCategory` / `cardTaggedWith` / `cardCites` / `supersedes`，
  目标存在才物化。卡片与外部条目进入同一 knowledge 分区，不另造存储
- **K2 FTS5 与 RRF 检索**：schema 只追加 v3，FTS 使用 `unicode61`；索引写中文单字与相邻 bigram，
  查询用 bigram AND 抑制单字噪声。BM25、结构化筛选与知识对象之间的 1–2 跳图扩散经 RRF(k=60) 融合；
  `saveSnapshot` 同事务重建 FTS，删除卡片不留幽灵命中。新增 `knowledge search/show/trace/stats`
- **K2 MCP / HTTP**：工具总数 12→16，新增 `knowledge_search` / `knowledge_show` / `knowledge_trace` /
  `knowledge_stats`；serve 新增 `/api/knowledge/search` / `/api/knowledge/card/{id}` /
  `/api/knowledge/stats`，继承现有 `/api/*` 鉴权与角色语义
- **显式降级与可观测性**：SQLite/FTS 不可用时搜索返回 `degraded:true, ranked:false` 并在 stderr
  提示无相关性排序；`storage status --json` 增加 FTS 可用性/行数，快照保存返回 FTS 行数与构建耗时

### 行为变更与修复

- **`looksLikeKnowledgeModel` 不再把投影误判为模型**：投影的 `_meta.kind` 同样是 `knowledge-asset`，
  初版据此判形会让「已投影」再投影一次。现先判 `isKnowledgeDataMap` 并返回 false（单测抓出的真实缺陷）
- **自环边剔除上移到适配器**：原先在 `extractLinks` 剔，会让 `aos_links` 与图抽取的边数差一条。
  现在适配器剔除并计入 `_meta.knowledge.selfLoopEdges`，两侧从此一致
- **`aos_links` 的 `src_type/tgt_type` 是 NOT NULL**：初版写 `edge.srcType ?? null` 会顶掉整个
  saveSnapshot 事务。改为缺端点类型即跳过该行
- **纯知识投影不再空跑 29 个 linkImpl**：`extractLinks` 加 `hasCodeObjects` 短路，实测省 ~300ms /
  30 万次扫描；混合图仍全扫，不漏边
- **投影层二次守门正文类字段**：`OMITTED_FIELDS` 从 `scan.js` 导出，适配器遇到携带它们的条目
  **整行拒绝**（不是剥字段后写入——剥字段会让「快照里有正文」静默消失），`leakedFieldRows > 0` 时
  `knowledge build` 在 stderr 告警并提示这是扫描器回归信号
- `DEFAULT_SNAPSHOT_FILE` 补 `knowledge` 项：镜像水位的 `jsonMtimeMs` 依赖这张表，漏 kind 会静默失效
- **raw 不污染 SoT 与对象镜像**：卡片快照与 `aos_objects` 始终保存 redacted `indexText`；
  `compile --index-body raw` 只重建当次 FTS，`storage rebuild --kind knowledge` 后恢复 redacted 检索。
  普通 `show` / 搜索 / MCP / HTTP 不返回 `indexText`；`card show --body raw` 显式读取本地原文并打印风险警告
- **搜索热路径不扫全库**：FTS 命中后按候选 id 回表，结构化筛选下推 SQL JSON 表达式；不在每次搜索里
  对 UNINDEXED `snapshot_id` 做 FTS COUNT。图扩散只走知识对象之间的边，避免标签/分类枢纽灌入弱候选

### 实测（真实知识库 `C:\knowledge-md`）

`knowledge build` → 10822 条目 / 23 分类 / 100 标签 / 15 来源 / 76 资产 / 587 关系，~560ms；
投影 **11047 个对象 + 64621 条边**（归属边 64034 + 聚合边 587），
悬空 0 / 重复 0 / 自环 0 / 无 id 行 0 / 禁字段泄露 0；`storage rebuild --kind knowledge` 连跑两次一致。
**归属边规模比 ADR 起草时按「平均 2.8 标签」估的 3.3 万高出一倍**（实际平均 3.9），已在文档校正。
K2 rebuild 后为 **10822 FTS 行 / schema v3**；「口令」返回 3 个精确候选，热查询约 47–51ms，
首次含 SQLite 打开约 116ms。临时卡片项目已验证 `card new → compile → search → rebuild → search`
结果逐字段一致、删除后无幽灵命中、raw 在 rebuild 后不再命中、storage off 显式降级。
真实库「SQL 注入」在当前元数据下没有满足 bigram AND 的条目，返回空集；没有用临时卡片结果冒充真实库命中。

### 验证状态（诚实边界）

- `graph push --kind knowledge` 的**真实 Neo4j 写入未验证**：本机与共享开发库都没有可用 Neo4j。
  CLI 侧验到连接阶段（bogus URI → `ECONNREFUSED`，说明 kind 白名单、文件加载、归一化都生效）；
  写入路径由新增的 `test/knowledgeNeo4jSink.test.mjs` 用假 driver 断言生成的 Cypher 事实
  （6 个类型 label、`Link` 不成节点、UPPER_SNAKE rel 命名、约束先于写入、`snapshot_kind='knowledge'`、
  >500 自动切批且批次总量守恒、节点 `props_json` 不含禁字段）。**接真实库后仍需复跑 `graph push` 确认**
- 三项知识指标尚未接入 `nice-aos.gates.json`，本轮 CI 入口为 `knowledge lint --gate`；
  `card review/approve/supersede` 与知识子命令统一 `--envelope` 未实现。`graph impact/deps`、
  ask 知识注入、卡片→代码对象建链属于 K3，不在本版承诺内
- `check diff` 的 `no-link-removal` 警告（linksRemoved=72）**与本版无关**：用 HEAD 干净副本对同一份
  `snapshot.json` 跑 A/B，改动前后 `extractLinks` 均为 2631 条边、`scanned` 45443、差集 0/0；
  差异来自基线快照与当前快照之间的仓库自身演进（ScriptFunction −28 等）
- 最终全量回归：1538 tests / 1530 pass / 0 fail / 8 skipped；`gates.json` 8 条规则通过（`no-link-removal` 为上述既有警告）

### 文档

- 新增 [ADR 0031 项目知识索引](docs/adr/0031-project-knowledge-index.md)（D1–D10，含 K0–K2 as-built 校正）
  与 [落地路线 K0–K4](docs/plan/knowledge-index-roadmap.md)（K0–K2 已交付并回填实测、降级与未完成边界）
- `docs/guide.md` 写明**投影边界的两套命名**约定（快照/渲染用小写容器键 + kebab 边名，
  DataMap/`aos_links`/Neo4j/`graph *` 用 PascalCase 类型名 + camelCase linkType，
  转换只在边界发生一次），以及 `query --kind` 与 `knowledge query` 的分工

## [0.60.1] - 2026-10-29

**0.60.0 发布后的修复轮**：本版修掉自审报告发现的缺陷，并让"依赖漏洞清零"的结论对**实际发布物**成立。
0.60.0 的功能与 API 不变，本版只含修复、文档校准与依赖同步。

> **版本号必须进位**：0.60.0 已发布到 npm registry（stage `a2bf332b`），以 0.60.0 重发会被 registry 拒绝，
> 故 `package.json` / `package-lock.json` 同步提升到 0.60.1（代码里 `src/common/format.js`、
> `src/common/snapshotAccess.js` 的注释本轮就写的是 v0.60.1，此前与 package.json 对不上）。
>
> **发布状态**：本版**尚未发布到 npm registry**，tag 与发布动作另行执行。

### 审核发现问题的处置（自审报告 → 修复闭环）

- **【P0】发布物内嵌的仍是旧版传递依赖**：`bundleDependencies` 让 `npm pack` 打包**磁盘实际内容**，
  而磁盘 `node_modules` 没有随 lock 同步 —— 磁盘上的 `hono 4.13.5` / `zod 4.4.3` / `qs 6.15.3` /
  `ip-address 10.5.0` / `@hono/node-server 2.1.1` / `body-parser 2.3.0` / `proxy-addr 2.0.7` /
  `fast-uri 3.1.6` 全都低于 lock 解析出的版本，即"审计读的是 lock、发布的是磁盘"。
  已执行 `npm ci` 重建 `node_modules`，复核后磁盘与 lock 逐项一致，`npm audit` 0 漏洞。
  **教训**：锁文件只描述"应该装什么"，`bundleDependencies` 打包的是"现在装了什么"——两者必须分别核对。
  - **补充**：`npm ci` 还会**静默跳过装不上的 optionalDependencies**——本机 `better-sqlite3`（原生模块）
    在 ci 后消失，导致 16 项存储相关测试集体变红，且 npm 不报错。
    发布前的检查清单要加一条：**ci 之后逐个验证 optionalDependencies 真的装上了**
    （本机补 `npm install better-sqlite3 --no-save` 拉取预编译二进制即可恢复，lock 不受影响）。
- **【P1】`orchestrator-fanout` 在 file 层级语义失真**：`isOrchestrationNode` 用 `x.lines` 当"文件数"，
  但 `lines` 在 module 层是 `Module.fileCount`、在 file 层是 `SourceFile.lineCount`。
  file 层因此把 `analyzerRegistry.js`（ce=15 / 142 行）、`builder.js`（15 / 199）、
  `builderScan.js`（9 / 392）、`viewerClient.js`（15 / 35）误判为编排容器并降级成 `info`，
  而 `evidence.files` 实际填的是**代码行数**——报告把行数读成文件数。
  现判据按 `level` 分支（编排容器是 module 层专属概念，file 层一律返回 false），
  evidence 区分 `files`（file 层恒为 1）与 `lineCount`，四个文件恢复为 `real-fragile`
  （`src/analyzers/structureCoords.js`）。
- **【P1】油猴脚本识别漏判超长元数据块**：`==UserScript==` 块要求整体落在前 4KB 内，
  而真实脚本的 `@match` / `@require` / `@resource` 可达数百行（实测 12KB / ~300 行）→ 被判成普通 js →
  facts 丢 imports → 再次触发"假死代码"链路。现改为先读 4KB 定位块首、命中后按需扩窗读到块尾
  （上限 256KB），块**首**仍锚定在文件头（防"正文提到该标记"被误判），块**长**不再受限
  （`src/analyzers/userScriptAnalyzer.js`）。
- **【P2】Windows 上 `cmd /c` 兜底是命令注入面**：`.cmd` shim 解析不出 Node 入口时回退 `cmd /c`，
  argv 会被拼回一条 cmd 命令行，prompt 里的 `&` / `|` / `>` 因此变成命令分隔符
  （实测 prompt 为 `hello & calc` 时 cmd 真的会去执行 `calc`）。
  处置分两层：
  - **首选路径彻底不经过 shell**：shim 解析升级为同时识别 `.js/.cjs/.mjs` 与 `.exe/.com` 入口并直调，
    prompt 逐字节原样送达。npm 全局 CLI 都是标准 shim → 实际全部走这条。
  - **cmd 兜底路径做安全转义**（手写 shim 仍需支持）：这层**不能靠 `^` 转义修好**——
    外层 cmd 解析一次、shim 批处理展开 `%*` 再解析一次，实测 `^` 转义、`""` 转义、
    整体命令行拼接三种方案都无法做到逐字节还原。最终形态是
    `""<shim>" <args>" & exit` 配 `windowsVerbatimArguments`：参数各自加引号（内部 `"` 写成 `""`），
    末尾 `& exit` 让最后一个引号不再位于命令行末尾（cmd 就不会把它吃掉），
    而 `exit` 不带参数会保留当前 errorlevel，所以退出码仍能透传。
    实测 `&` `|` `>` `"` 与多参数均逐字节精确送达，退出码正确。
  - **残余限制（cmd 的固有限制，非本实现可解）**：命令行按**行**解析 → 参数里的换行会被截断；
    phase-1 会展开 `%VAR%` → 形如 `%PATH%` 会被替换成环境变量值（仍在引号内，不会变成命令执行）。
    这也是为什么**能解析出入口的 shim 一律走无 shell 的直调路径**。
  （`src/cli/commands/agentRunner.js`）
- **【P2】Java 注解统计在 Windows 上恒为 0（静默假数据）**：`countJavaAnnotationsAsync` 的降级链只有
  `rg` → `find + grep`，而 `find` / `grep` 是 POSIX 命令——Windows 上 `find` 命中的是 `system32\find.exe`
  （文本搜索工具，不是 GNU find），`grep` 根本不存在。两级全败后直接返回 0，
  而这个 0 与"仓库里没有 Java 文件"**无法区分**。现补第三级 **JS 内置目录扫描**（口径与 `rg -l` 一致：按文件计），
  任何平台都能给出真实数字（`src/overview/overviewScanner.js`）。

### 技能包与代码事实对齐（14 个 SKILL.md）

- **新增 `nice-aos-knowledge-skill`**：`knowledge build / query / export` 的完整工作流。
  此前 ADR 0030 的知识资产图谱只有命令、没有 skill——agent 只知道它存在，不知道两种布局的识别规则、
  五类边的生成阈值（topTags 40 / minCategoryTag 3 / topCooc 100 / topCites 800，小库自动放宽）
  以及"快照只留瘦字段、正文与命令永不入库"的隐私边界。已在 8756 条目真实知识库上实跑校准：
  785ms 扫描、2.2MB 快照、566 条边（category-tag 175 / category-source 215 / tag-tag 100 / asset-kind 76）。
- **`nice-aos-skill` 补齐 0.60.0 发布版能力，修正版本标注**：`check diff` + 质量门禁
  （`nice-aos.gates.json`、`metric/op/valueFrom/severity`、退出码 0/1/2）、`propose` 提案包、
  `output --format table`（15 张表 + manifest 对账）、`knowledge` 命令速查、`serve` 的
  `--token` / `--rate-limit` / `--ws-interval` / `--docs-dir`。原先标为 `v0.61` / `v0.61.1` 的
  `portfolio` 与 `graph schema` 实为 0.60.0 发布内容，标注已改正（0.59.0 未发布，0.60.0 是首个 npm 版本）。
- **`nice-aos-context-builder` 增加"产物可信度自检"**：`output docs` 对任何目录都会 `ok:true`。
  新增 summary 字段全表（含 `structure.explainedVariance/fidelity/nodeCount`）与判读规则——
  `domains===0` 或 `explainedVariance===0` 即本体未建模该仓库（实测一份知识库产出 4 个骨架文件 / 0 Domain / nodeCount 1），
  此时不得把骨架当项目知识；并澄清结构层闸门 `structureLayerGate()` 是内部裁决、不是可配置开关。
- **`nice-aos-portfolio-skill`**：`graph schema` 的版本标注同步为 0.60.0。
- **`asdm-aos-skill` 的 `name` 规范化为 `asdm-aos`**：原值 `ASDM Ontology Skill` 含空格与大写，
  不合技能名规则（`^[a-z0-9]+(-[a-z0-9]+)*$`，≤64 字符），加载时只能退化成按目录名识别。
  现与 `registry_id: asdm-aos` 一致，装到技能目录后调用名为 `asdm-aos`。

### 修复

- `output docs` 的过期提示曾指向不存在的 `nice-aos analyze` 命令，改为 `nice-aos action refreshRepo`
  （`src/cli/commands/export.js`）。
- **`docs.html` 用 `file://` 双击打开是空页**：浏览器禁止 `file://` 页面 fetch 同目录文件，
  旧版运行时才去读 `./tree.json` 与 `./<path>.md`，失败后只显示"未找到 tree.json"。
  现在生成时把 tree 与全部 md 正文内嵌为 `<script id="docs-embed" type="application/json">`
  （`<` 转义为 `\u003c` 防止正文提前闭合脚本），前端内嵌优先、缺失才回退 fetch——
  双击可离线浏览，`serve /docs` 行为不变（`src/ontology/docsViewer.js`、`src/cli/commands/export.js`）。

### 自审修复（nice-aos-code-review 首轮）

- **油猴脚本识别改为文件头锚定**：`isUserScriptCandidate` 原先只检查文件是否"包含" `==UserScript==`，
  结果把源码注释里提到该标记的 `userScriptAnalyzer.js` 自己认成脚本，整个文件的导入边被丢弃，
  并连带产出 3 处死代码误报。现要求 `==UserScript==` 块位于文件头且块内含 `@name`；
  `.user.js` 后缀仍直接命中。重新扫描后 `UserScript` 归零，误报消失（`src/analyzers/userScriptAnalyzer.js`）。
  （块**首**锚定在文件头，但块**长**不限 —— 见上方 P1 条目：早期固定 4KB 窗口会漏判超长元数据块。）
- **新增 `orchestrator-fanout` 结构诊断**：`src/cli/commands` 这类"注册 N 个子命令模块"的编排容器，
  `radius` 恰好落在中位数另一侧时会被判成 `real-fragile`（0.3214 vs 中位数 0.3144 的刀锋翻转）。
  现按 `ce/文件数 ≤ 5` 且 `outConcentration < 0.5` 判为编排容器，严重度降为 info 并在证据里给出
  `files / cePerFile / outConcentration`，`real-fragile` 只保留给真正的脆弱强耦合
  （`src/analyzers/structureCoords.js`、`src/ontology/contextDocs.js`）。
- **结构诊断回归测试改为锁不变量**：`refreshRepo` 会改变对象总数与 radius 中位数，同一模块的 kind
  可能在 `hub-fanout` / `orchestrator-fanout` 之间漂移（本轮 0.321 → 0.050），共享邻居数也会变（46 → 54）。
  原先"必须是 orchestrator""必须命中 3 组"的断言在快照刷新后假红，现改为"不得 real-fragile"
  "至少一组平行模块"，具体数字写入注释（`test/structureRules.test.mjs`）。
- **Windows 可移植性**（`src/cli/commands/agentRunner.js`、6 个测试文件）：
  - 二进制探测改为直接按 `PATH` + `PATHEXT` 扫描，不再 spawn `where`——最小 PATH 下 `where.exe` 自身不可见，
    会导致"未检测到任何可用问答通道"；顺带省掉 8 次子进程。
  - npm `.cmd` shim 解析出 `nodeEntry`，绕开 CreateProcess 不能直接执行 `.cmd` 的限制。
  - 测试里的动态 `import()` 统一走 `pathToFileURL(...).href`（Windows 裸路径会被当作 `c:` 协议拒绝）；
    fake agent 在 Windows 下生成 `.cmd` + `.cjs`；monorepo / askSave 路径断言先统一分隔符；CRLF 检出先归一化。
- **依赖漏洞清零**：`@modelcontextprotocol/sdk` 升到 `^1.32.2`，并在其下写**嵌套** `overrides`
  （`hono` / `qs` / `zod` / `ip-address` / `fast-uri` 等）。平铺 `overrides` 在 `bundledDependencies`
  子树内不生效——包发布时要自带依赖，npm 会忽略外层改写；`npm audit` 由 9 项（1 critical / 4 high / 4 moderate）降为 0
  （`package.json`、`package-lock.json`）。
- **`bundleDependencies` 的动机与代价**（本轮新增声明，之前的 CHANGELOG 未说明）：
  - **动机**：CLI 依赖 MCP SDK，而 SDK 的传递链（`hono` / `zod` / `qs` …）漏洞多且修复频繁。
    自带依赖可以让 `npm audit` 的修复结果**随包分发**，用户 `npm i -g nice-aos` 后不再受上游传递依赖的旧版本影响；
    同时 `overrides` 必须写在 `@modelcontextprotocol/sdk` **下面**（嵌套），因为平铺 overrides 在被 bundle 的子树里会被 npm 忽略。
  - **代价**：安装布局变了——用户项目里会出现一份**冻结的依赖副本**，上游修漏洞不会自动生效，
    必须随 nice-aos 发新版本才能带上；`npm audit` 也不会再提示这些副本（它们已不在用户的依赖树里）。
    这是一次"用可控的冻结副本换上游漏洞免疫"的取舍，不是免费的。
  - **发布纪律**：`bundleDependencies` 打包的是**磁盘实际内容**而非 lock 的解析结果，
    所以每次发布前必须 `npm ci` 重建 `node_modules` 并复核 tarball 内嵌版本（见上方 P0 条目）。

- **死代码与重复实现清理**：删除 **12 处**确认无引用代码（`impact.js` 的 `shortLabel` / `impactCommand`、
  `linkGraph.js` 的 `computeLocalModuleCoupling`、`callChain.js` 的 `emptyCallChainResult`、
  `contextDocs.js` 的重复常量 `ARCH_LAYER_COLLAPSE_RATIO`，以及 contrib 两个脚本内 6 个死函数）；
  抽取 `src/common/snapshotAccess.js`（快照取数三别名）与 `src/common/format.js`（`fmtLocalTime`），
  消掉三处 viewer 的重复实现，`ARCH_LAYER_COLLAPSE_RATIO` 改为单一来源导出。
  - **保留 3 处未删**（自审报告列在"可删除"里，但本轮**故意保留**，此前 CHANGELOG 写成"删除 15 处"与事实不符）：
    `isGoCandidate` / `isRustCandidate`（已标 `@deprecated`，按 `docs/arch-review/architecture-review.md`
    的决议"先标记一个版本再删"）与 `getKnowledgeSnapshotPath`（与 `setKnowledgeSnapshotDir` 成对的公开导出）。
    **教训**：死代码清单里出现 `@deprecated` / 公开 API 时，要先问"是否真是死代码"，再决定删不删。
- **测试全绿**：`# tests 1509 / # pass 1501 / # fail 0 / # skipped 8`（Windows 本机实测；
  0.60.0 那条 `overview.test.mjs` 的 Windows 假红已随 Java 注解统计的 JS 兜底修复）；
  `check diff --gate nice-aos.gates.json` 8 条规则全部通过（`linksRemoved=72` 属删死代码的预期 warning）。
- **新增回归测试 9 条**（`test/agentRunner.test.mjs` 新建 + `test/structureRules.test.mjs` +
  `test/userScriptAnalyzer.test.mjs`）：覆盖 file 层编排判据、`evidence.files` / `lineCount` 区分、
  超长油猴元数据块、cmd 兜底的元字符原样送达 / 无注入副作用 / 退出码透传、`resolveOnPath` 四个边界。
  全部做过**反向验证**——把实现临时还原成旧逻辑，这 4 组用例确实变红（测试有效，不是摆设）。

### 技能包：nice-aos-code-review 增加可执行前置与复验闭环

- **Step 0 质量门禁**：先跑 `npm test` / `npm audit` / 版本核对，并把红灯按"产品缺陷 / 测试可移植性 /
  夹具过期"分类——不同类别的修法完全不同，混在一起会把测试环境问题误判成产品回归。
- **Step 0.5 平台可移植性体检**：Windows↔POSIX 六项对照（动态 import、路径分隔符、`.cmd` shim、
  `where`/`which`、CRLF 文本锚点、shell 脚本夹具）。
- **Step 4.3 死代码复核改为 grep 实证**：给出四类豁免（测试动态引用 / 冻结制品 / `@deprecated` 兼容导出 /
  生成物）与"自扫描把注释当代码"的坑。
- **§7.4 修复复验闭环**：单测 → 全量回归 → audit → `refreshRepo` + `check diff --gate` → `deadCandidate`
  复核的五步命令序列，以及"一次只修一个根因 / 反向验证 / 范围止于评审结论"三条纪律。

## [0.60.0] - 2026-10-28

**首个发布到 npm registry 的版本**：v0.59.0 只打了 Git 标签、从未发布到 npm（npm 上的上一版仍是 0.58.0），且该标签上的代码存在一处让 CLI 完全无法启动的缺陷。本版包含 0.59.0 的全部能力，并修掉该缺陷；同时完成 README 全面校准与补全。

### 发布阻断修复：migrate.js 重复导出（CLI 完全无法启动）

**症状**：任意命令（含 `nice-aos --help`、`nice-aos --version`）在进程启动阶段直接抛
`SyntaxError: Duplicate export of 'MIGRATIONS'`，退出码 1——整个包不可用。

**根因**：`src/storage/migrate.js` 原本的导出约定是「函数声明 + 文件末尾统一 re-export」。
v0.59.0 收尾提交为了让测试能构造**真实旧口径**的账本行，把 `MIGRATIONS` / `checksumOf` /
`legacyChecksumOf` 三个符号改成「声明处直接 `export`」，但文件末尾那条 re-export 列表
（`export { computeContentHash, computePkHash, checksumOf, legacyChecksumOf, MIGRATIONS }`）
没同步删掉这三个名字——同一符号被导出两次。ESM 在**静态解析阶段**就报错，因此任何经由
`src/storage/index.js` 导入该链路的命令都起不来，与运行环境、参数无关。

**修复**：末尾 re-export 只保留确实未在声明处导出的 `computeContentHash` / `computePkHash`。

**拦截点**：`test/storage/schema.test.mjs` 与 `test/storage/migrateChecksum.test.mjs` 直接
`import { MIGRATIONS, legacyChecksumOf } from '../../src/storage/migrate.js'`——发布前跑全量
`npm test` 即可命中（该文件解析失败 → 整个 storage 套件加载失败）。本次发布已跑全量测试。

### README 全面校准与补全

- **事实校准**：子命令数量由「18 个」更正为 **22 个**（另 4 个顶层兼容别名 `deadcode` /
  `duplicates` / `io` / `matrix` 在帮助里隐藏）；对象类型 39 种、链接类型 58 种、MCP 12 个
  tools、随包分发 13 个 SKILL.md 均与代码一致。
- **补齐 v0.59 能力**：`check impact` / `check diff` 的实际用法（`check impact <target>` 是位置参数，
  不是 `--src`）、`propose` 提案包、`output --format table` 的 15 张表 + manifest 对账清单、
  `portfolio` 项目集组合分析、`knowledge` 知识资产图谱、`vocabulary` 能力发现、`--envelope` 统一输出契约。
- **新增章节**：命令全景表（按核心 / 原子 / 体检 / 领域 / 治理 / 运维分组）、输出形态与产物契约表、
  集成方式（MCP tools 清单 / serve 端点 / Skills / Contrib）、数据与安全边界、环境要求与安装升级、
  许可证。
- **ADR 引用范围**由「0011–0019」更正为「0001–0030」。

## [0.59.0] - 2026-10-28

> **发布说明**：本版仅打了 Git 标签（`v0.59.0`），**从未发布到 npm registry**——
> npm 上的上一版是 0.58.0。包含下述全部能力且可用的版本是 **0.60.0**（含发布阻断修复）。

本版跨越 0.35~0.59 的全部未发布工作，主题：**知识资产图谱（ADR 0030）、导出可对账（表格 + manifest）、质量门禁与本体改进提案（ADR 0028/29）、项目集组合分析（ADR 0024/25）、Neo4j 远端图对账、坐标矩阵结构体检（check matrix / impact）**；另含若干扫描器与测试稳定性修复。

### 发布收尾修复（2026-10-28）

- **graph_* 错误码判定顺序修正**（`storage/neo4jSnapshot.js`）：`openDriver()` 把「无配置」与「driver 库缺失」都折叠成 null 后，`driverUnavailableResult()` 曾用 `loadDriverLib()` 分叉错误码——干净环境（无配置 + 无 driver）下会误导用户去 `npm i neo4j-driver`。现改为先按配置判定：无配置永远返回 `E_NEO4J_CONFIG_MISSING`（指引配连接），有配置才可能是 driver 不可用
- **结构坐标断言随仓库演进更新**：v0.59 新增公共底座使 deployment/planning/service 共享邻居数 40→46；`cli/commands` 因新命令模块互引在模块级真实构成 COUPLE-07 脆弱点（与命令循环依赖同源）——断言按事实更新


### 知识资产图谱 —— 扫描知识库并展示分类 / 标签 / 来源关系（ADR 0030）

**动机**：代码本体回答的是「谁调用谁」。知识资产库（例如一份带条目、分类、标签、来源的 Markdown/SQLite 资料库）没有调用链，人要看的是知识之间怎么连。已有的 `panorama` 融合的是代码仓五类快照，扫不了这种资料目录。

**实现要点**：

1. 新命令 `nice-aos knowledge build|export|query`。`build --root` 自动识别两种布局：`exports/entries.json` 或 `db/knowledge.db`（结构化），以及至少两份 Markdown（frontmatter、`[[双链]]`、相对链接）。
2. 图谱边：分类—标签、标签共现、分类—来源、文档互链、资产归类。大库裁掉弱边，避免上万条目画成一团。
3. 快照与 HTML 不写入正文、命令、绝对路径和资产备注，避免把明文凭据扫进去。带「敏感凭据」标签的条目只计数。
4. `knowledge export --format html` 产出自包含图谱页（分类×标签、标签共现、分类×来源、文档互链、资产归类）。

### 导出可对账 —— 单快照表格导出 + 统一 manifest + 流式写盘（P0）

**动机**：导出的三个既有形态（markdown / html / viewmodel）都是**给人看的报告**——
8.3 MB 快照压成 97 KB 的 markdown、984 KB 的视图模型，8249 个对象一行没有。
要做二次分析（透视、扇入扇出、依赖版本分歧）必须拿到**行**，而不是段落。
同时导出产物一旦离开本机就无法回答三件事：从哪个快照导的、用什么参数导的、文件有没有被改过。

**① `output --format table`**（单快照 → 15 张表，CSV / Excel）

复用项目集导出的三层表（`buildExportTables`：schema 字典 / 实例层 / 项目集维度），
表定义、列顺序、转义规则全部同一份——单项目表与项目集表不会对不上账。
边用 `extractLinks`（与 Neo4j 入库同一个函数），所以表格里的边与图库里的边同口径。

实测（nice-aos 自扫快照，8249 对象）：对象实例 8249 行 / 2.1 MB，边实例 17644 行 / 3.7 MB，
文件清单 904 行，重复实现 293 组，全局热点 324 个，定义实体&属性 241 行 —— 合计 27821 行。
单快照下 5 张跨项目维度表（共享依赖 / 隐式耦合 / 同名契约 / 同名文件 / 跨项目调用链）为空，
CLI 会明说「空是因为维度不适用」，而不是让人对着只有表头的 CSV 猜。

**② 统一 manifest（`schema: nice-aos/export-manifest/v1`）**

每个写文件的导出旁落一份清单：`source`（快照 sha256 + commit/branch + 分析时间）、
`params`（格式/主题/类型过滤/增量/合并/表选择）、`counts`（对象数 + 按类型 + 表行数）、
`files[]`（rows 或 lines + bytes + sha256）。stdout 模式不产 manifest（没有可对账的锚点）。
**`bytes` / `sha256` 与写盘同一次遍历算出，不是写完回读**——回读会在「写完到回读」之间留篡改窗口。

**③ 流式写盘（`src/common/fileSink.js` + `src/common/csvSink.js`）**

`writeFileSync` 要求整份产物先在内存拼成字符串：8 MB 快照 JSON、项目集 ×8、几十 MB 表格，
峰值内存是产物大小的两倍以上。改为按 chunk 写 + 背压（`drain`），峰值压到一个 chunk。
三个实现细节值得记：
- **跨 chunk 边界不能劈开代理对**：落单的 high surrogate 编码后是 U+FFFD，校验和照样对得上，
  坏的是内容（emoji / 生僻字变问号）——边界处多带一个 code unit。
- **CSV 攒批而不是逐行**：参考资料（属性图 CSV 导出）逐行 `await write()`，367K 行就是 367K 次
  await + 367K 次 hash.update，microtask 开销盖过 IO。这里 512 行一批，写入次数与行数无关。
- **打开阶段的错误翻译成人话**：文件被 Excel 占用时 Node 报 `EPERM ... open`，用户看不出是「文件开着」，
  提示改为「该文件可能正被 Excel 等程序占用」。

**④ 转义规则收敛到一份**

`csvEscape` / `toCsv` 原只在 `src/portfolio/portfolioExport.js`，单快照导出要复用。
规则上移到 `src/common/csvSink.js`，`portfolioExport` 改为 re-export（对外 API 不变，
测试用 `===` 钉住「必须是同一个函数引用」）。
`portfolio export` 的 CSV 落盘同时切到流式（`writeTablesCsvStreaming`），产物逐字节不变。

**测试**：`test/exportManifest.test.mjs` 16 项（emoji 跨块、空文件、CRLF/BOM/转义、
manifest 溯源与哈希对账、目录/文件落点、CLI 端到端的 rows 对账与 stdout 不产 manifest）。

### 质量门禁 + 本体改进提案包 —— Act 步的验收与可评审形态（ADR 0028 / ADR 0029）

**动机**：ADR 0027 的 `check diff` 给出了差异与改善/退化判定，但**判定只到人读**——
CI 需要的是「声明式、可版本化的判据 + 明确的通过/不通过 + 不通过时退出码非 0」。
文章里对应的一句是「查询、动作和**守卫检查**从不依赖 LLM——同样的输入永远得到同样的输出」。

同时，ADR 0026/0027/0028 三件各管一段（测量诊断 / 客观差异 / 验收），
提 PR 需要把它们**串成一份可评审的产物**：改了什么、为什么改、怎么证明改对了。

**① 质量门禁**（新增 `src/ontology/gates.js` + `nice-aos.gates.json`）：

```json
{ "id": "no-dangling-edges", "metric": "danglingEdges", "op": "<=", "value": 0, "severity": "error" }
{ "id": "resolution-not-regressed", "metric": "importResolutionRate", "op": ">=", "valueFrom": "base" }
```

门禁规则是**声明式数据**（不是代码）：可引用 9 项指标 + 7 项结构差异量，
全部是已存在、可从快照直接读出的客观量——**不为门禁而造新指标**。
门禁配置放**项目根** `<项目根>/nice-aos.gates.json`（`.nice-aos/` 已被 gitignore，
而门禁是项目自己的质量契约，必须进版本控制、走 PR 评审）。

三个关键判定：
- **配错必须报错，不能静默跳过**——引用不存在的指标 / 空规则集 → 配置错误，**一条门禁都不执行**。
  静默跳过会让 CI 显示绿灯而实际一条没跑，那比门禁配错本身危险得多。
- **退出码三态**：0 通过 / 1 门禁未通过（代码没达标）/ 2 门禁配置有误（门禁没跑起来）。
  不区分 1 与 2，CI 无法分辨该改代码还是该改门禁配置。
- **null = 未知，无法证明通过就不算通过**：旧快照缺 `quality` 块时该规则判 unknown，
  默认计入未通过（可 `--gate-unknown skip` 改为跳过）。

**② 本体改进提案包**（新增 `src/ontology/proposal.js` + `nice-aos propose`）：

ADR 0026 的 action 新增结构化 `target` 字段——原先「改哪里」只在一句人话里
（"修 archLayer 推断…（src/ontology/semantics.js inferFileArchLayer）"），机器读不出：

```js
target: { kind: 'code'|'config'|'data', file: 'src/ontology/semantics.js', symbol: 'inferFileArchLayer' }
```

`kind` 决定「谁来改」：代码面 / 配置面 / 数据面——三者工作量与归属完全不同。
提案按**改动面**排序（代码面优先：一次投入覆盖多格）。

**同根因的多个格子合并成一条提案**：本仓 `code.test`/`test.baseline`/`test.test`
三个格子都因「测试目录未纳入扫描根」而空，但**改一次全好**。逐条列出会让人以为要做三件事，
反而稀释可行动性。合并键 = 成因 + 标题 + 目标文件 + 目标符号（后两项必须在键里：
同根因但目标不同的就是两件事）。实测本仓 **9 缺口 → 4 可行动 → 2 条提案**。

**验收段三态**：无基线时**不能说「未通过」**——那会误导评审以为代码没达标，
实际是「没得比」。此时明确写「未做门禁判定」并指出需先存基线。

**闭环全貌**：
```
① 测  nice-aos panorama --section gaps   → 缺口 + 成因 + 依据 + 目标 + 下一步 + 验证命令
② 改  nice-aos action refreshRepo        → 按「目标」改源码或改扫描配置
③ 验  nice-aos check diff                → 客观差异 + 门禁判定（退出码 0/1/2）
④ 提  nice-aos propose                   → 把①②③串成一份可评审的提案（PR 描述）
```

**边界**：`propose` **只产出提案文档，不执行改动、不碰 git**——提 PR 需要的内容
（改哪里/为什么/怎么验）是工具能确定性给出的，实施与合并由 agent 或人进行。

**验证**：单测 31 例（test/gates.test.mjs 16 + test/proposal.test.mjs 15）；
**变异测试 11 项全部被用例抓住**（门禁侧 5 项 / 提案侧 6 项）；
**全量回归对拍**：基线 1385 例 21 失败 → 本改动 1466 例 20 失败，失败清单逐条一致。
实测：门禁通过退出码 0；造「悬空边 7 + 解析率 0.5」的退化 → 2 条未过、退出码 1；
门禁引用不存在的指标 → 报「未执行任何门禁」、退出码 2；空门禁 → 退出码 2。

### 本体版本 diff —— Act 步的客观验收凭据（ADR 0027）

**动机**：ADR 0026 落地后发现一个悬空处——`gapActions` 给每条缺口写的 `verify`
（「重扫后悬空边应归零」「该格应从 ○ 变 ✓」）**没有任何工具能验证**。
人肉比对两个 snapshot.json 既慢又不可靠。建议链断在最后一步，verify 本身成了空话。

对照物是 KE 的 `ke snapshot list`：「两个版本之间什么变了，**逐条确定，不靠推测**」。
文章给的判据也是同一个：闭环之所以转得起来，是因为每次改完蓝图/分析器，
「重新导入、**快照 diff**、覆盖率、查询结果，全部是分钟级可验证的客观反馈」。

**现状盘点**：`gitDiff.js` 是文件级 git 区间比对；`merge.js` 是多仓**合并**（不是差异）；
`panorama` 只有单份快照——**仓库里没有「同一本体 v1↔v2 的结构化差异」这一 primitive**。

**做法**：新增 `src/ontology/versionDiff.js`（纯函数、无 IO）+ `nice-aos check diff` 子命令：

```
总体判定：改善
  改善：悬空引用边
客观指标：
  ✔ 悬空引用边：5 → 0（-5）
  = 导入解析率：1 → 1（0）
对象：新增 1 / 删除 0 / 变化 0
  Method：+1 -0 ~0
链接：新增 1 / 删除 0
人工标注：0 → 1（新增 1 / 移除 0）
```

**六个关键判定**：

1. **差异落到具体字段**——`~ method:src/x.js#f 变化字段：astFingerprint, health.complexity.cyclomatic`，
   而不是「变了 300 个对象」（无法行动）。
2. **噪声字段排除**——`reviewed`/`notes`（人工标注层）、`line`/`pos`/`end`（位置信息）
   不算语义变化。否则改一行注释就让上百个 Method「变了」，报告变噪音，
   **人不再看报告比没有报告更糟**。
3. **链接按无向口径**——快照里没有独立 links 数组（链接在 `*Ids` 字段上），
   折叠成 `a↔b` 边集合比对：同一批边换个字段名表达（`calls` ↔ `calledBy`）
   不会被误判成删一条加一条，正反向也不重复计数。
   代价（诚实边界）：方向变化不可见。
4. **人工标注单列一类**——它是「人对本体的判断」，不是本体事实；
   但 OAC 的「全程审计」依赖它，所以既不混进对象变化，也不忽略。
5. **总体判定只看有方向的客观指标**——9 项指标全部取自 `_meta` 既有字段
   （**不为 diff 而造指标**），各声明方向。**对象数量变化不参与判定**：
   「新增 300 个对象」不构成改善证据。
6. **null = 未知，不是 0**——旧快照缺 `quality` 块时该指标判「无法判定」，
   并在总体判定里**点名**哪些项判不了，不让读者以为「没变化」就是「没问题」。

另：指标标记用 `✔`/`✘`/`=`/`?` 表示**好坏**而非数值方向——「未解析导入数 17→0」
是变好，但 `↑` 会被读成变差。

**闭环接上**：`gapActions` 新增 `action.verifyCommand` 字段，
「重扫后如何验证」类建议的 verify 统一指向 `nice-aos check diff`。
于是每一环都有工具：**测（panorama）→ 诊（gapActions）→ 改（action.refreshRepo）→ 验（check diff）**。

**落位**：`check diff`（纯本地快照、不依赖 Neo4j，与 check 族同源，ADR 0020）。
**不提供顶层同名别名**——理由同 `impact`：`check impact` 与 `graph impact` 是两条不同路径，
同名会让用户误判拿到的是哪一路结果。基线默认读 `<快照目录>/snapshot.base.json`，
缺失时给含 `cp` 命令的可执行提示。

**验证**：`test/versionDiff.test.mjs` 17 例；**变异测试 7 项全部被用例抓住**
（删噪声字段排除 → 3 红；链接改有向 → 1 红；指标方向判定改「一律变大即好」→ 4 红；
null 当已知值 → 2 红；删字段级定位 → 1 红；总体判定掺入对象数量 → 1 红；标注不单列 → 1 红）；
**全量回归对拍**：基线 1385 例 / 21 失败，本改动 1435 例 / 20 失败，失败清单逐条一致。

### 知识缺口行动推导（Act 步）—— 建议由证据推导，不查静态字符串表（ADR 0026）

**动机**：跨域知识全景（v0.57）已有 Plan→Do→Check 三步，但**没有 Act**——
缺口清单的实现是 `GAP_SUGGESTIONS` 这张**按格子位置查的静态字符串表**，
建议内容与「这一格为什么空」无关。同一句「补测试层文件」在两种完全不同的情形下都会出现：

- 项目确实没有测试 → 该人补测试
- **测试目录没进扫描根 → 该改配置重扫**（补测试是白干）

本仓自扫描实测正是后者：`roots:["src"]` 收窄 → 118 个 `test/*.test.mjs` 一个没进快照 →
`code/test` 格为空 → 建议「补测试层文件」。agent 照做会真的去写测试，
而真正的缺口在扫描配置——**建议错了，闭环再快也是空转**。

**做法**：新增 `src/panorama/gapActions.js`（纯函数、无 IO），每条 gap 增挂 `action`：

```json
{
  "cause": "missing-data",
  "headline": "测试目录未纳入扫描根——需扩大扫描范围，不是补测试",
  "why": ["快照共 178 个源文件，实际扫描根为 src", "扫描范围内 0 个测试特征文件…"],
  "next": "nice-aos action refreshRepo --params '{\"repoPath\":\".\",\"roots\":[\"src\",\".\"]}'",
  "verify": "重扫后 nice-aos panorama --section gaps 中「代码 / 测试」格应从 ○ 变 ✓",
  "actionable": true
}
```

**五类成因**（诊断单位是**取证渠道 facet**，不是知识类别——同属 code 类的
「页面路由」与「测试层文件」成因毫无关系）：

| cause | 含义 | 下一步 |
|---|---|---|
| `missing-data` | 事实存在但没采到 | 改扫描根 / 改分析器 |
| `not-modelled` | 该阶段没定义取证渠道 | 改本体（补 FACETS） |
| `absent-source` | 依赖的数据源没接进来 | 先产出快照 |
| `quality-risk` | 快照级缺陷（悬空边 / 重复 id） | 先修质量 |
| `no-issue` | 本就不该有内容，或「空 = 健康」 | **不动** |

**三个容易漏掉的判定**：

1. **负向语义**：`循环依赖`/`孤儿候选`/`未匹配调用`/`死导出候选`/`解析错误` 是「坏东西清单」，
   取到 0 条 = **没有这类问题**（健康信号），不是知识缺失。
   不区分就会把「本仓零循环依赖」报成「缺循环依赖知识」，建议变成「去制造循环依赖」。
2. **维度不适用**：纯 CLI 仓没有前端路由、没有前后端契约——这类渠道不该催用户去补。
   判据全来自快照事实（框架值 + 对象计数），且有反向守卫：
   框架是 react 时 0 个路由**不算**「不适用」，那是真缺口。
   领域维度按**数据源**判（db/deploy/service/planning），并严格区分
   「快照没接进来」（不可评估，absent）与「快照接进来但内容为空」（该维度不适用）：
   全五源接入但内容皆空时，可行动缺口从 **48 条降到 21 条**。
3. **质量风险排在渠道诊断之前**：`danglingEdges`/`duplicateIds` 是快照级缺陷，
   不对应任何渠道，会被「孤儿候选为空 → 健康」的结论盖住。
   `orphanNodes` 特意不计入——它与孤儿候选同一口径，同时报会自相矛盾。

**诚实边界**：null = 未知不是 0；分不清就说分不清（兜底明说「快照事实不足以区分
『未采集』与『确实没有』」）；看不到未扫描的目录，只给可证推论不给断言；
派生结论不进本体（不写快照、不新增 linkType）。

**对外**：`summary.actionableGaps` / `gapCauseByKind` 给出**真待办**数与成因分布
（`filterPanorama` 同步重算）；CLI 缺口渲染改用结论式 headline，
不再并列显示自相矛盾的静态建议；MCP `panorama_matrix` 与 `GET /api/panorama`
的工具描述补充 `action` 契约。

**验证**：`test/panoramaActions.test.mjs` 31 例；**变异测试 7 项全部被用例抓住**
（删负向语义规则 / 删维度不适用（按渠道）/ 删维度不适用（按数据源）/ 删扫描根收窄判定 /
删专项诊断 / `actionable` 恒 true / summary 不重算）；**全量回归对拍**：基线 1385 例 / 21 失败，
本改动 1416 例 / 20 失败，失败清单逐条一致（全部源于未装的可选依赖 `better-sqlite3` / `neo4j-driver`）。
真实仓实测：16 条 gap → **11 条可行动**，5 条判为「本无此类事实」不再当待办。

### 项目集代码评审 —— 变更集驱动的跨项目评审引擎与 skill 增强（ADR 0025）

**动机**：项目集扫描已有 8 个维度，但产出的是**扫描结论**，不是**评审报告**。
既有 `nice-aos-code-review-skill` 的 20 个维度全部落在**单仓快照**上，评的是仓库**现状**、
靠 agent 手工跑 20+ 条 CLI 填模板；而项目集扫描已把「谁调用谁 / 谁和谁重名 / 谁和谁共享依赖」
变成结构化事实，却**没人把这些事实投影到一次具体改动上**。缺的就是这一公里：
「**这次改动动了会不会砸到别的项目**」。

**实测数据（asdm-portal 真实变更，8 项目语境）**：

- 评审 3 个真实变更文件（`src/graph/api/graphApi.ts`、`src/lib/auth.ts`、`src/services/portalApi.ts`）：
  快照覆盖率 **3/3（100%）**、耗时 **11.9s**（含 8 个项目快照加载）
- 跨项目出边 **13** —— 与 ADR 0024 维度**独立算出**的「13 处跨项目命中」逐条一致
  （两处实现独立、匹配器同一）→ 交叉验证通过
- XP-API-01 接口缺口 **4 条**（全集内无任何实现）：`/api/auth/me`、`/api/auth/session`、
  `/_ai/portal/settings/installation-field`、`/_ai/portal/settings/feature-flags/public`
  —— 后两条大概率由仓外系统提供，而代码里没有任何地方声明这一点
- XP-API-03 证据偏弱 **9 条**（全部 tier3 通配，路径含前端模板串 `/api/graph/${*}/stats`）
- XP-CONTRACT-01 同名契约 **10 条**、XP-DUP-01 重复实现 **1 条**
- 工作区模式实测暴露口径问题：未跟踪区混着构建产物（`.tmp-portal/` **1853 个文件**），
  不排除会让变更集变成 1863 个文件、覆盖率归零

**实现要点**：

1. **新引擎** `src/portfolio/review.js`：`collectChangeSet`（git name-status + numstat + `--unified=0`
   解析新增行区间）/ `buildProjectIndex` / `runPortfolioReview` / `renderReviewMarkdown`。
2. **评审对象是变更集**，不是仓库现状 —— 变更集外的调用点一律不评。
3. **8 条 XP-* 规则**（新前缀，不动既有 20 维度）：XP-API-01/02/03、XP-CONTRACT-01、
   XP-SHARED-01、XP-DUP-01、XP-DEP-01、XP-COVER-01。
4. **匹配阶梯复用** `rpcMatch.matchApiRouteEx`（ADR 0012 D3 四档）—— 与单仓构建期、跨仓合并期、
   跨项目调用链维度**同一套**。
5. **只读本地快照，不依赖 Neo4j** —— 评审是高频动作，不能依赖要额外起服务的图库。
6. **分级压住噪音**：第一版把同名 Interface 全判 P1，在 asdm-portal 上一口气报 10 条
   （TS 里 `Props`/`Options` 满地都是）→ 改为同名 **Route** → P1、同名 Interface/Class → P2，
   同路径同名交给 XP-SHARED-01 不重复报。
7. **未跟踪文件要纳入**（刚写完没 add 的新文件最需要评审），但**产物目录要排除并披露**：
   已知产物目录名，或单个顶层目录未跟踪文件 ≥ 20 个（`.github/` 这类真实目录不受影响）。
8. **覆盖率强制披露**：未被快照覆盖的文件是「未评审」，不是「通过」（XP-COVER-01）。
9. **CLI**：`nice-aos portfolio review --project <name> [--diff <range>|--staged|--files <清单>]`
   [--output <report.md>] [--format md|json]`。
10. **skill 侧增强既有 code-review skill**（不新建第 13 个 skill）：加**模式 B（项目集合集评审）**
    + 新文件 `spec/portfolio-review-rules.md`；`spec/review-template.md` 加跨项目评审章节；
    portfolio skill 加评审入口与边界。

**测试**：

- 新增 `test/portfolioReview.test.mjs` **27 例**：规则表完整性 / hunk 新增行解析 / 行号命中新增区间 /
  文件清单模式 / 真 git 仓库采集（状态 + 增删行 + 新增行区间 + 未跟踪文件）/ 接口缺口新增行 P0、
  存量 P1、变更集外不评、跨项目已实现不报 / 调用方断裂 P0 / tier3 与歧义报证据不足 /
  同名 Route P1、同名 Interface P2、同路径不重复报 / 共享路径分级 / AST 指纹重复 /
  依赖版本分歧（一致时不报）/ 覆盖率披露 / 项目不存在明确报错 / 无语境项目时边界声明 /
  findings 排序与计数自洽 / Markdown 报告结构 / 项目索引完整性。
- asdm 8 项目实跑核对：出边 13 与 ADR 0024 逐条一致；4 条接口缺口逐条核对路径在全集中确无实现。

**诚实边界**：

- **快照时效是硬边界**：快照是扫描时刻的状态，diff 之后新引入的调用点不在快照里 →
  XP-API-01 对新代码天然有盲区。报告显式声明，未假装覆盖新代码。
- **未覆盖 ≠ 通过**：XP-COVER-01 + 覆盖率披露强制呈现。
- 跨项目结论一律 `requiresHumanConfirm`：路径 / 指纹 / 同名都是静态候选信号。
- 跨项目调用链只到**路由声明文件**这一层（8 个项目 `Route.componentId` 全为 null），
  拿不到方法级末端，不编造方法级链。
- 本地重算与图库查询是两份实现（与 ADR 0024 同一取舍），口径靠共享匹配器压住。
- 出网调用（外部 API）无事实来源，本维度不覆盖。

### 项目集蓝图新增「跨项目调用轨迹」维度 —— A 真的在调 B（ADR 0024）

**动机**：单项目的「调用轨迹」视图已在 ADR 0023 落地，但**项目集**蓝图（`portfolio report`）
的 7 个维度全是**结构相似**信号（共享依赖 / 同名契约 / 重复实现 / 同名文件 / 热点…）——
它们回答「项目之间有什么相同」，回答不了「**A 是不是真的在调 B 的接口**」。
前者是候选信号（同名 ≠ 同一份东西），后者是**运行期调用事实**（调用点路径命中了另一个项目的
服务端路由），信号强度高一档。这个维度缺了，项目集分析就只是在比长相。

**实测数据（asdm 8 项目，真实快照）**：

- 原料现成：`_meta.unmatchedFrontendCalls` **167 处**调用点在本仓内没找到实现；
  服务端路由池 **159 条**（asdm-agentorbit 116 express / asdm-agentorbit-agent 17 go /
  asdm-aos-server 23 express / asdm-ontology-research 3 express）
- 匹配后：**13 处跨项目命中 · 2 对项目** —
  `asdm-portal → asdm-aos-server` 11 处（`/api/graph/*` → `src/modules/graph/graph.controller.ts`，
  4 处字面量 + 7 处通配）、`asdm-portal → asdm-agentorbit` 2 处
  （`/api/auth/login|logout` → `server/src/routes/auth.ts`）
- 这两对项目在既有维度里只是「共享 react 等 8 个包」的隐式耦合 —— **调用链维度才第一次指出
  「portal 真的在调 aos-server 的接口」**

**实现要点**：

1. **共享匹配器**（`src/portfolio/callChain.js`，纯函数）：匹配阶梯直接复用
   `rpcMatch.matchApiRouteEx`（ADR 0012 D3 四档）—— 与单仓构建期、跨仓合并期
   （`merge.js`）**同一套**，不再有第三份匹配实现。tier → confidence 对齐 ADR 0021 三档。
2. **三个出口同源**：`portfolio analyze` / `portfolio report` / `portfolio export` 表格
   共用这一个模块 → 表格与页面逐项同源。（既有 7 个维度是「本地重算 vs 图库查询」两份实现，
   CHANGELOG 已自认漂移风险；本维度不重复这个债。）
3. **两类调用点来源分列**：`unmatched`（本仓无实现 → 委派调用 `delegated`）与
   `routeBound`（已在本仓建链 → 路径重叠 `overlap`，并记住本仓实现 `ownRoute`）。
   `overlap` 是**路径级**证据，比既有 `contracts` 维度的**名字级**聚合强一档。
4. **三条排除口径写死**（实测踩到，否则结论直接变假）：
   ① 本机地址（`http://127.0.0.1:${port}/status` 会误命中别的项目的 `/status`）→
      排除 loopback；② 测试/夹具目录（`analyzers/frontend/tests/fixtures/` 是分析器自带样例工程，
      假调用会命中真实路由）→ 维度内路径判定（**不改全局 `isTestFile` 口径** —— 那是死代码/
      健康度等维度的公共事实）；③ 歧义不武断：一条调用在多个项目都有实现 → 全部列为候选 + 标记。
5. **仓内命中不短路**：本仓有实现、别的项目也提供同一路径 → 仍出链（「两个项目提供同一路径」
   本身就是跨项目事实）。
6. **覆盖率强制披露**：调用点按分区记账（测试/本机/无法解析/仅仓内/跨项目/未匹配），
   不重不漏；断言锁死 `各分区之和 = 调用点总数`。
7. **蓝图新增板块**「跨项目调用轨迹」：项目→项目**有向**调用矩阵（与无向的隐式耦合矩阵语义不同）、
   轨迹条带图（与 ADR 0023 同构：调用方前端 → 被调方路由 → 路由声明文件）、链明细表。
8. **表格新增第 15 张**「跨项目调用链」（CSV/XLSX 同流程）：调用方/被调方/链类型/方法/调用路径/
   目标路由/目标路由文件/证据档/置信度/调用点数/调用点位置/是否歧义。

**测试**：

- 新增 `test/portfolioCallChain.test.mjs` **17 例**：四档阶梯与置信度映射 / host 与本机地址判定 /
  夹具路径判定 / 条带角色序列 / 两类来源抽取 / 非服务端路由不入池 / 跨项目委派命中 /
  仓内命中不短路 / 本机地址排除 / 夹具排除 / 歧义全列 / 通配落 tier3 / 路径重叠型 /
  覆盖率字段齐全与计数自洽 / 导出表口径 / 蓝图板块与诚实边界。
- asdm 8 项目实跑：13 处跨项目命中、2 对项目，逐条核对调用点文件行号与目标路由文件一致。

**诚实边界**：

- 该维度**只读本地快照** —— `_meta.unmatchedFrontendCalls` 不入图库（图库只存 `AosEntity`
  节点与关系），所以它与其它 7 个维度的「两源逐项一致」不同，图库源下拿不到 `unmatched` 来源。
  已在覆盖率、蓝图说明、表格说明三处披露。
- 链的末端只锚在**路由声明文件**：asdm 8 个项目的 `Route.componentId` 全为 null
  （express/go 的 handler 方法未绑定），拿不到方法级末端 —— 不编造。
- 路径匹配是**静态推断**，不等于运行时真的打了过去；一律 `requiresHumanConfirm`。
- `overlap`（路径重叠）要求另一项目**确实提供了该路径**；同名但不同路径不算。

### 安全：清除 git 历史里的明文凭据（并附一个测试竞态的真因）

**事件**：提交 `45065cb`（update otogly ask）把共享库 Neo4j 的**明文密码**写进了 `docs/guide.md`，
且已推送远端 —— 任何拿到仓库或历史的人都能读到。

**处置**（本次完成）：

1. **全量扫描定位**：明文只出现在 `docs/guide.md` **一个文件**、**24 个提交**
   （`45065cb` 引入 → `f4d2028` 移除）。`docs/guide.md` 里的其它 token 全是文档示例值
   （`s3cret-abc123` / `sk-xxx`），不是真凭据。
2. **改写**：`git filter-branch --index-filter` 把这 24 个提交里的明文替换为占位符 `<你的密码>`。
   - 改写器**本身不含任何秘密**：待清除的凭据是从 blob 里按模式自己提取的
     （脚本/命令行/临时文件里都不留明文）
   - 范围只取 `45065cb^..main` → **更早的提交 SHA 全部不变**（16 个旧标签无需改动）
   - 验证：改写前后 tip 树**完全一致**、提交信息/作者/日期逐条保持、提交数不变（109）
3. **强推**：`72edd97...858c12f main -> main (forced update)`。
4. **本地净化**：删 `refs/original/*`、过期 reflog、`gc --prune=now --aggressive`
   → 旧 tip 与 `45065cb` 都已是 unknown object。
5. **终检**：**全新克隆**一份远端仓库做**字节级扫描**（含 `.git/pack` 内所有对象，
   425 文件 / 9.7MB）→ 未发现明文；本地 `.git` 同样干净；`git fsck` 退出 0。

**仍需人工完成（代码侧无法代劳）**：

- **轮换共享库密码**。清历史只解决「仓库里不再有」；已经 clone 过的人、
  GitHub 对象库里未 GC 的旧 blob（仍可按 SHA 拉到）依然拿得到 —— **密码必须换掉**。
- 协作者需要**重新 clone**（历史被重写，旧 clone 直接 pull 会冲突）。
- 如需清掉 GitHub 的缓存视图，可用仓库设置里的 "Remove sensitive data"（或联系支持）。
- 共享库若还有别的系统引用同一密码（服务端配置 / CI 变量），那些地方要一起换。

**顺带修掉一个测试竞态的**真因**（不是负载问题）**：
文件监听「单文件」用例把内容从 `'1'` 改成 `'2'` —— **长度相同**。
`fs.watchFile` 只在 `mtime` 变化**或** `size` 变化时回调，两次写入若落在**同一毫秒**，
则 mtime 没变、size 也没变 → **回调永不触发**（实测隔离跑多数通过、并行跑必挂）。
改成不同长度内容（size 必然变化）+ 轮询等待（8s 上限）→ 并行跑 3/3 稳定。

**诚实边界**：

- 改写只覆盖 `main` 分支与该区间；16 个旧标签指向改写点之前的提交，本来就不含密码（已逐个核对）。
- `docs/guide.md` 里的**示例** token（`s3cret-abc123` 等）保留未动 —— 它们不是真凭据。
- 密码轮换后，本地脚本与 CI 里的 `NICE_AOS_NEO4J_PASSWORD` 需同步更新。

### 项目集扫描结果导出成表格（CSV / Excel）—— 给二次数据分析用

**动机**：扫描结果的归宿原本只有两个 —— 蓝图页（**给人看**）和 JSON（**给程序读**）。
但「二次分析」是第三类活：拿对象表做透视、拿边表算扇入扇出、拿依赖表查版本分歧、
拿重复实现表排治理优先级 —— 这些都要**表格**，不是一页 HTML。
参考形状来自 asdm 的 `aos_engine` schema 导出（本体三表：定义实体&属性 / 定义关系 / 定义关系属性）；
本实现在此之上补齐**实例层**与**项目集维度** —— 二次分析要的是数据，不只是字典。

**新增命令**：`nice-aos portfolio export`（`--format csv|xlsx|both` / `--source local|neo4j` /
`--tables` / `--projects`），产出 **14 张表**：

| 层 | 表 |
|---|---|
| 本体字典 | `定义实体&属性` / `定义关系` / `定义关系属性` |
| 实例数据 | `对象实例` / `边实例` / `文件清单` / `依赖清单` |
| 项目集维度 | `项目汇总` / `共享依赖` / `隐式耦合` / `同名契约` / `重复实现` / `同名文件` / `全局热点` |

**两条数据源，口径一致**（实测 8 个项目逐项相同）：
`17848 对象 / 43039 关系 / 2250 文件 / 326 依赖`，
共享依赖 50 · 隐式耦合 11 对 · 同名契约 69 · 跨项目重复 38 组 · 同名文件 40。

- `--source local`（默认）读本地快照，**不需要 Neo4j**；跨项目维度在本地用与
  `portfolio analyze` 相同的口径重算（运行时依赖才算真实耦合 / 契约候选只取
  Interface·Route / 重复实现按 AST 指纹分组 / 依赖热度按包名聚合）。
- `--source neo4j` 从图库拉 `AosEntity` + 关系。

**实现要点**：

1. **零新增依赖**：XLSX 是手写的最小实现（`src/portfolio/xlsxWriter.js` —— zip + OOXML，
   用 `node:zlib` 打包），不引 exceljs / xlsx 那种 10MB 依赖树。
   CSV 输出 UTF-8 **BOM + CRLF** → Excel 双击不乱码；XLSX 带表头加粗、首行冻结、列宽、自动筛选。
2. **边不另造轮子**：本地边用 `extractLinks(dataMap)` —— 与 Neo4j 入库**同一个函数**
   （`src/ontology/linkGraph.js`），所以本地边表与图库边表必然一致，不存在「本地少算几类边」的漂移。
3. **基础边 vs 派生边**用 `linkDefs` 的 `FORWARD_OF` 判（谁能作为某正向边的镜像）。
   ⚠️ 不能用「def 里有没有 `reverse` 字段」判 —— `passesProps` / `usesHook` / `registers` /
   `navigatesTo` 都是**正向**关系，只是没配 mirror，用错判据会把它们标成派生边。
4. **可空性**看「每个实例都有该字段」，不只看显式 null —— 字段在部分实例上**缺失**
   同样意味着可空。（这是写测试时被自己的假数据打脸才发现的口径缺陷。）
5. **monorepo 嵌套落点必须递归发现**（实测踩到）：`portfolio scan` 对容器型项目
   （`asdm-cli/asdm-bootstrapper-cli`）落盘是 `projects/<name>/<sub>/snapshot.json`，
   只扫一层会**只导到 6/8 个项目**（静默少两个）。现在递归找 `snapshot.json`，
   用相对路径还原项目名（与 `portfolio.json` 里的名字一致）。
6. **类型中文名单一事实源**：新增 `src/ontology/typeLabels.js`（39 个类型的名字 + 常见字段名），
   `askFocus.js` 原来私藏的那份改为复用它 —— 同一类型在终端叫「方法」、在表格里叫「函数」，
   做二次分析的人根本对不上号。

**测试**：

- 新增 `test/portfolioExport.test.mjs` **18 例**：CSV 转义与 BOM / 建表口径
  （运行时耦合、契约候选、重复实现、同名文件、热点阈值）/ 本体三表（基础边派生边、可空性）/
  monorepo 递归发现 / **XLSX 反向解包验证**（自写 zip 解包器读回条目与单元格，证明文件真能打开）/
  CLI 契约（产物落盘、非法表名报错）。
- 产物额外用**两套独立实现**复核：.NET `ZipFile` 能正常读出 14 个工作表，17 个 XML 部件全部通过
  XML 解析；CSV 用 `Import-Csv` 回读行数一致。

**诚实边界**：

- XLSX 写入器**只写不读**，不做公式 / 图表 / 合并单元格 / 样式定制；数值按原样写入，不做任何换算。
- 跨项目维度在本地是**重算**（口径对齐 `crossProject.js`，但不是同一段代码）——
  将来 `crossProject` 改口径，这里要同步改。
- 表格是**扫描当时**的快照口径，不含增量（与 JSON 蓝图同理）。
- 重复实现是 AST 指纹**精确匹配**：语义相同但写法不同的重复实现查不出来。

### 修掉让整套回归「冻结」的测试句柄泄漏 —— 从挂死到 137 秒跑完

**现象**：全量回归跑到 `overview.scanOverview` 之后长时间不动。不是慢，是**死锁**：
父 runner 与子进程在 25 秒采样窗口内 CPU 与 IO **全为 0**，父子进程互相等待。

**根因**：`test/overviewWatcher.test.mjs` 里每个用例的 `w.close()` 都写在**断言之后**。
而 `fs.watch` / `fs.watchFile` 是 persistent 句柄，只要还挂着就阻止事件循环退出：
断言一旦先失败（负载下时序类断言本就会抖），`close()` 被跳过 →
该用例的子进程**永不退出** → 父 runner 无限等待 → **整套回归冻结**（不是失败，是挂死）。
最脆弱的是「单文件 target」用例：`fs.watchFile` 轮询间隔 1000ms，测试只等 1500ms，
负载下轮询回调迟到就断言失败 → 直接命中泄漏路径。
（**后续实测**：修完泄漏后，全量并行跑时它确实假红过一次 —— 隔离复跑 3/3 通过、耗时 4s。
已把固定 `sleep(1500)` 改成**轮询等待回调**（有 8s 上限），不再赌时序；
这同时也验证了 `finally` 修复有效：同一处失败**没有**再把整套回归拖死。）

**两层修法**：

1. 每个用例把 `w.close()` 放进 `finally`，无条件执行 —— 断言失败照旧报，但不再拖住进程。
2. `npm test` 加 `--test-force-exit`（Node ≥22）：测试跑完即退出，残留句柄不再有资格冻结整套回归。
   该旗标会被 Node **传播到每个测试子进程**（实测子进程命令行含 `--test-force-exit`），
   兜底在子进程层面同样生效 —— 正是观察到的那个冻结点的对症药。

**用泄漏用例实测证明兜底有效**（临时用例：故意断言失败 + 故意不关 `fs.watch`）：

- 带 `--test-force-exit`：0s 失败并退出；
- 不带：15s 仍未退出（死锁），只能强杀。

**顺带修掉一个 Windows 上恒红的断言**：`collectFingerprint` 用例期望 `src/sub/c.ts`，
实际 `path.relative` 在 Windows 上返回 `src\sub\c.ts` → 该用例**每次必失败**。
改为按 `path.sep` 归一化后再断言（跨平台意图不变）。

**回归对比**（同环境、干净基线 worktree @833ecef vs 本次修复，同一条命令）：

| | tests | pass | fail | skipped |
|---|---|---|---|---|
| 基线（833ecef） | 1333 | 1267 | 58 | 8 |
| 修复后 | 1333 | 1268 | **57** | 8 |

逐条比对失败清单（72 条 vs 71 条 `not ok`）：**唯一差异是 `collectFingerprint` 已消失，零新增失败**。
Windows 既有失败基线随之由 58 降为 **57**（`docs/guide.md` 的「开发」段已同步）。
顺带证实：`fileWatcher` / `overviewWatcher` 两个文件自 `4ea7fd5` 起未被任何近期提交碰过 —— 这次冻结与本轮
8 项目扩容的代码改动无关。

**诚实边界**：

- 修的是**测试基础设施**的冻结，产品代码本轮零改动。
- `--test-force-exit` 是「不再挂死」，不是「不再泄漏」—— 句柄仍应被正确关闭，
  所以第 1 层（`finally` 无条件 close）才是真修复，第 2 层只是兜底。
- 本机同时有 4 个其它项目（`c:\asdm-tools\_tmp\patch-*.js`）的脚本在满载空转，8 核机器上会压时序类用例；
  本轮两次跑批中那个已知的 `mcp --dir`（8s 截止线）**两轮都通过**，未触发抖动。

### asdm 项目集扩容到 8 个 + 修掉两个让蓝图「说假话」的数据缺陷

**扩容**：新增 `asdm-agentorbit-agent`（Go 服务，module `github.com/leansoftx/asdm-agentorbit-agent`，
63 文件 / 927 对象 / 5724 关系）—— 19 个 internal 包 + `cmd/digiworker-sidecar` + Azure Pipelines ×5 + Dockerfile ×4。
顶层有 `go.mod` → 直接识别为真项目（非容器型）。项目集现为 8 个：
**2250 文件 / 17848 对象 / 扫描 43.6s / 组合分析 1.8s**。

**新项目的信号很典型**：共享依赖 0、隐式耦合 0、契约候选 0、跨项目重复 0 —— **代码层面与项目集零耦合**
（Go 生态与 npm 生态无交集，接口命名体系也不同）；但**同名文件 11 个**
（`scripts/export-release.sh`、`deploy/k8s/02-secret.yaml`、`deploy/k8s/01-namespace.yaml`、
`deploy/k8s/03-configmap.yaml`、`deploy/k8s/deploy-k8s.sh`、`templates/steps-swr-login.yml`、
`azure-pipelines*.yml` ×5）——
**运维/流水线层与项目集高度同源**。这是「公共层候选」维度才看得见的信号：
代码解耦了，运维资产还在各仓库各养一份。

**⚠ 修掉两个会让结论「说假话」的缺陷**（都是本轮实测发现的）：

1. **同名函数定义导致图里少节点**（`src/ontology/infraObjects.js`）
   `buildShellScriptObjects` 把函数 id 按**名字**索引（`fnIdMap`），而 bash 允许同名函数定义多次
   （asdm-admin 的 `deploy/scripts/deploy-ecs-arm-one-step.sh` 里 `log_info`/`log_warn`/`log_error`
   各定义了两次，行 298-300 与 339-341）→ 两次定义拿到**同一个 id** → 快照里两个同 id 对象 →
   入库 MERGE 折叠 → **远端图里凭空少 6 个 BashFunction 节点**。
   实测证据：修复前 asdm-admin 本地 6833 对象 / 远端 6827 节点；修复后两边**都是 6833**，
   8 个项目的「对象数 = 节点数」现已全部成立（零折叠）。
   修法：对象 id 按**定义**给（与 CMake 侧写法对齐），按名字索引只保留给**边解析**用
   （bash 后定义覆盖先定义 → 调用边仍指向后定义，运行时语义不变）；
   `usesBuiltin` / `readsCliParam` 边的来源是具体某个定义，也改为按下标取 id。

2. **跨项目重复实现维度被静默截断**（`src/portfolio/crossProject.js`）
   该维度原来「把全部 Method 拉回客户端再按指纹分组」，而查询封装默认 `limit = 2000` ——
   8 个项目的 Method 共 **7537 个** → **静默截断 73.5%**。
   后果：同一批数据两次分析给出 **3 组** 和 **0 组** 跨项目重复，全是「抽到哪 2000 条的运气」。
   真实值是 **38 组**（DB 侧按指纹聚合复核），里面有真信号：
   `repoId` 10 处 × 4 项目 · `sleep` 9 处 × 2 项目 · `clear` 5 处 × 2 项目 ·
   `normalizeTemplatePath` 4 处 × 3 项目 · `useToast` 4 处 × 3 项目 · `fmtDuration` 4 处 × 3 项目。
   修法：把分组**推到 DB 侧**（`WITH fp, collect(...) ... WHERE size(members) >= $minSize`），
   只取回「多成员组」——结果集小，不会截断；
   同时 `q()` 统一带 **truncated 标记**，各维度结果都带上，蓝图页面对被截断的维度**显式报警**
   （「不是全量」），绝不让截断静默发生。
   `hotspots` 的取数下限也从 `limit*3`（90）提到 2000 —— 依赖热度是按包名聚合这些行得到的，
   取样太少会漏掉分散在低扇入节点上的包。

3. **写入连接级失败自动重试**（`src/storage/neo4jSnapshot.js`）
   批量入库时偶发 `Connection was closed by server`（实测 asdm-admin 第一次推失败）→
   写入是批量任务，一次网络抖动不该让整个项目失败。
   现在识别连接级错误（连接被服务端关闭 / 连接池取不到 / 网络重置）→
   丢掉可能已失效的连接池、**换一条新连接重试一次**（写事务是原子的，失败已回滚，无残留）。
   业务级错误（约束冲突、语法错误）不重试 —— 重试没有意义。

**8 项目实测全景**：
- 规模矩阵：asdm-admin 6833 节点 / 16304 关系 · asdm-agentorbit 4089/7892 ·
  asdm-ontology-research/aos 3038/6691 · asdm-portal 1288/3416 ·
  asdm-cli/bootstrapper-cli 857/1545 · asdm-agentorbit-agent 927/5724 ·
  asdm-mcp-server-hosted 412/630 · asdm-aos-server 404/837
- **50 个被 ≥2 项目使用的依赖包（21 个构成运行时耦合）** · **11 对项目间隐式耦合**
  （admin↔portal 8 个运行时包 · agentorbit↔portal 6 个 · agentorbit↔aos-server 5 个：
  dotenv/cors/express/helmet/prom-client）
- **69 条跨项目同名契约候选** —— `ApiResponse` 横跨 **5 个项目**；
  `GraphNode`/`User`/`AuthContextType`/`ToastContextValue`/`LoginResponse`/`SessionState` 各横跨 3 个
- **38 组跨项目重复实现**（AST 指纹，精确匹配）
- **40 个跨项目同名文件** —— `scripts/export-release.sh` 出现在 **7 个项目** ·
  `deploy/k8s/02-secret.yaml` 5 个 · `azure-pipelines-ecs-arm.yml` 4 个
- 依赖热度：react 414 · `@leansoftx/asdm-aos-core` 143（内部共享库，项目集的脊柱）· vitest 115
- 代码热点：`asdm-admin-web/src/hooks/useI18n.ts` 87 · `icons/index.ts` 70 ·
  `services/apiClient.ts` 61 · `components/common/PageMeta.tsx` 61

**测试**：新增 `test/infraObjects.test.mjs` **5 例**（同名函数多次定义 → 每个定义一个唯一 id /
对象数 = 不同 id 数 / 调用边仍指向后定义（bash 运行时语义）/ builtin 边不因 id 覆盖而丢边 /
PowerShell 同样按定义给唯一 id）；
`test/portfolio.test.mjs` 补 **2 例**（维度被截断时蓝图必须显式报警 / 未截断时不误报）。
全量回归 **1333 例 / 1266 通过 / 59 失败** —— 其中 1 例是既有的 `mcp --dir` 启动超时抖动
（隔离复跑 7.4s 通过，贴着 8s 截止线，负载下偶发），其余 58 例与 Windows 既有基线逐条一致，**零回归**。

### 远端图 schema 对账：把「多快照同图」的约束冲突变成可诊断、可修复（`nice-aos graph schema`）

**发现于 asdm 项目集重扫**：多快照入库报 `already exists with label 'Project' and property 'name'`。
根因不是数据问题，是**共享库的 schema 与当前模型不匹配**：

| 模型 | 对象身份 | 约束 |
|---|---|---|
| 当前（v0.55+） | `(snapshot_id, id)` —— 一张图里放任意多个快照 | `UNIQUE AosEntity(snapshot_id, id)` + `UNIQUE AosSnapshot(snapshot_id)` |
| 旧（遗留） | 全局唯一 —— 假设全库只有一个快照 | `UNIQUE Project(name)` / `UNIQUE User(name)` / `UNIQUE AosGraph(aos_id)` |

当前模型写每个节点都带 `snapshot_id`，但**旧约束没有这个维度** → 任何新快照里出现同名对象，
CREATE 立刻被拦下。而 `Project.name` 就是项目名 → 同一项目写进第二个快照必撞。

**新增能力**：
- **`nice-aos graph schema`** —— 只读体检远端约束并分类。分类是**数据驱动**的：
  只有「该 label 上确实存在带 `snapshot_id` 的节点」才说明当前模型真会写它。
  - `model` 当前模型要求的 → 保留
  - `legacy` 旧模型遗留的全局唯一约束 → **会拦下多快照写入**（真冲突）
  - `inert` 旧图模型遗留（当前模型根本不写那个 label）→ **拦不住任何写入，可不管**
  - `other` 其它（非唯一性 / 带 `snapshot_id` 维度）
- **`nice-aos graph schema --drop-legacy`** —— 显式删除真冲突的那几条。
  改共享库 schema 是**显式操作**，绝不隐式发生；`inert` 的不动。
- **写入前预检**（加在 `saveSnapshot` 这个唯一写入咽喉，`graph push` / `portfolio scan` /
  自动双写全部受益）：发现会被遗留约束拦下的值时，不再让 Neo4j 抛原始错误，
  而是直接说清「哪条约束 / 哪个值 / 已被哪个快照占用 / 两种修法（删约束 or 改写进已有快照）」。

**实测修复**（共享库 `bolt+s://bolt.leansoftx.com:443` / db=neo4j）：
- 体检结果：约束 5 条 → 真冲突 **1 条**（`project_name UNIQUE Project(name)`）；
  `aos_id`（旧图 `AosGraph`）与 `user_name`（当前模型不写 `User` —— 7 个 asdm 快照的 `User` 实体均为 0，
  远端仅 3 个旧模型 User 节点）判为 `inert`，按「历史遗留旧图不管」的原则**保持原样**。
- 删除 `project_name` 后重扫 7 个 asdm 项目 → **7/7 成功 · 2186 文件 · 16916 对象 · 57.4s**，
  每个项目写进**独立的 `code:<项目名>` 快照**，零冲突。
- 清理被取代的旧快照 `asdm-aos-server:default`（同一项目留两版会让组合分析重复计数）
  → 远端「同项目多快照」降为 **0 组**。

**重扫后组合分析实测**（分析 1.5s）：
- 规模矩阵：asdm-admin 6827 节点 / 16304 关系 · asdm-agentorbit 4089/7892 ·
  asdm-ontology-research/aos 3038/6691 · asdm-portal 1283/3408 ·
  asdm-cli/bootstrapper-cli 857/1545 · asdm-mcp-server-hosted 412/630 · asdm-aos-server 404/837
- **49 个被 ≥2 项目使用的依赖包，其中 20 个构成运行时耦合** · **11 对项目间隐式耦合**
  （admin↔portal 8 个运行时包 · agentorbit↔aos-server 5 个：dotenv/cors/express/helmet/prom-client）
- **69 条跨项目同名契约候选** —— `ApiResponse` 横跨 **5 个项目**，`GraphNode`/`User`/`SessionState`/
  `AuthContextType` 各横跨 3 个（契约分叉风险最高的信号）
- **3 组跨项目重复实现**（AST 指纹）—— `clearStoredToken`（aos ↔ portal）·
  `useToast`（aos ↔ portal）· 另 1 组
- **36 个跨项目同名文件** —— `scripts/export-release.sh` 出现在 **6 个项目** ·
  `deploy/k8s/02-secret.yaml` 4 个 · `deploy/k8s/01-configmap.yaml` 3 个
- 依赖热度：react 414 · `@leansoftx/asdm-aos-core` 143（内部共享库，是项目集的真正脊柱）· vitest 115
- 代码热点：`asdm-admin-web/src/hooks/useI18n.ts` 87 · `icons/index.ts` 70 ·
  `components/common/PageMeta.tsx` 61 · `services/apiClient.ts` 61

**⚠ 安全修复**：`docs/guide.md` 的 Neo4j 凭据示例里**直接写着明文密码**（由提交 `45065cb` 引入，
已推送到远端）—— 已改为占位符 `<你的密码>`。**该密码仍留在 git 历史里，建议轮换。**

**测试**：新增 `test/neo4jSchema.test.mjs` **10 例** —— 约束分类口径 / 属性形态归一化
（`SHOW CONSTRAINTS` 的 `properties` 在社区版是纯字符串数组、企业版是对象数组，认错会导致
合法约束被误判成遗留冲突并被误删）/ 冲突键提取 / 诊断文本内容。
其中一条专门钉住 `formatCollisionDiagnostics` 里的越界变量 bug（真出冲突时该函数会直接抛异常）。

### 跨项目组合分析：项目集全家桶蓝图（`nice-aos portfolio`）

**单项目快照回答「这个项目怎么样」；项目集分析回答「这些项目合在一起有什么问题」**——
后者不是前者的加总，需要多个快照落在同一张图里才能算。新增命令族 `nice-aos portfolio`：

- **`portfolio scan`** —— 一次扫多个项目 → 各项目**独立**快照（落 `<out-dir>/projects/<name>/snapshot.json`，
  **不污染各项目仓库**）+ 可选推入 Neo4j（每项目独立 `snapshot_id`）。
  容器型项目自动探测真项目根（`asdm-cli` → `asdm-cli/asdm-bootstrapper-cli`、
  `asdm-ontology-research` → `asdm-ontology-research/aos`）。
  支持 `--projects a,b,c` 与 `--config portfolio.json` 两种清单写法。
- **`portfolio analyze`** —— 跨 snapshot 组合分析，6 个维度：
  `scale`（规模矩阵）/ `dependencies`（共享依赖 + 版本分歧 + **运行时 vs 开发时**）/
  `contracts`（跨项目同名接口路由 = **契约分叉候选**）/ `duplicates`（跨项目重复实现，AST 指纹）/
  `hotspots`（跨项目扇入；代码热点与依赖热度分开）/ `sharedFiles`（跨项目同名文件 = 公共层 or 分叉维护候选）。
- **`portfolio report`** —— 「项目集全家桶蓝图」单页自包含 HTML（无外链 / 无 `<script>` / 无 `src=`）：
  项目矩阵卡片 + 项目间隐式耦合热力矩阵 + 共享依赖表 + 契约候选 + 重复实现 + 公共层候选 + 全局热点条形图。
  另支持 `--format json | md`。
- **`portfolio summary`** —— 终端汇总。

**asdm 项目集实测**（7 个项目：asdm-admin / asdm-agentorbit / asdm-aos-server / asdm-cli /
asdm-ontology-research / asdm-mcp-server-hosted / asdm-portal）：
2173 文件 / 16815 对象 / 扫描 33s / 7 个快照全部入库；组合分析 1.8s。产出：
- **20 个构成运行时耦合的共享依赖** · **11 对项目间隐式耦合**
  （asdm-admin ↔ asdm-portal 共享 8 个运行时包；asdm-aos-server ↔ asdm-agentorbit 共享 express/helmet/cors/prom-client）
- **67 条跨项目同名契约候选** —— `ApiResponse` 横跨 **5 个项目**（契约分叉风险最高的信号）
- **1 组跨项目重复实现** —— `resetInstanceCache` 在 asdm-agentorbit（3 处）与 asdm-aos-server（1 处）同构
- **35 个跨项目同名文件** —— `scripts/export-release.sh` 出现在 **6 个项目**、`deploy/k8s/02-secret.yaml` 出现在 4 个
- 依赖热度：`@leansoftx/asdm-aos-core` 扇入 143（内部共享库，是项目集的真正脊柱）

**口径纪律**（写进 skill，不只是代码）：
1. **每个项目恰好一个快照** —— Neo4j `saveSnapshot` 对同 id 是「先删后写」，共用会互相覆盖；
   同一项目留两版又会让组合分析重复计数。`scan` 默认**复用**远端同名项目已有 `snapshot_id`
   （`--no-reuse-existing` 可关）。
2. **运行时依赖才算真实耦合** —— `devDependencies` 共享是常态（`@types/*` / `typescript` / `vitest`），
   不构成运行时耦合。实测未区分前 21 对耦合，区分后 11 对 —— **去掉的是噪声**。
3. **跨项目结论一律是候选信号** —— 同名接口 ≠ 同一份契约；同 AST 指纹 ≠ 同一份实现；
   同名文件 ≠ 该抽公共库。每条带 `requiresHumanConfirm`。
4. **报告的项目集规模要核对** —— `scan` 可能有项目失败，失败项不参与分析。
5. **不改别人项目的仓库** —— 快照落项目集目录，不写进各项目。

**已知边界**：跨项目重复实现是**精确匹配**（AST 指纹），语义相同但写法不同查不出来；
遗留旧模型约束（`Project.name` 全库唯一，无 snapshot_id 维度）会与「同一项目一个快照」冲突，
表现为 push 报 `already exists with label 'Project'` —— 本命令给出可读提示与 `--reuse-existing` 解法，
**不擅自改共享库 schema**。

**新增** `skills/nice-aos-portfolio-skill/`（含五条纪律与已知边界）· **测试** 新增 `test/portfolio.test.mjs` **9 例**
（项目清单解析 / 容器型探测 / 快照落点不污染项目仓库 / 单项目失败不阻断 / 判定口径 / 蓝图自包含 / 缺分析降级）。

### P0 修复：扫描器遵守 .gitignore（构建产物与敏感文件不再进入快照）

**发现于真实项目扫描测试**（desktop-pet：Electron 客户端 + Go 服务端 + Web 前端 + Helm/K8s，370 个源文件）。
该仓库的 `.gitignore` 明确排除了 `release.noindex/`、`out/`、`*update-private-key*` 等，
`git check-ignore` 确认规则生效 —— 但**扫描器从不读 .gitignore**，被排除的内容照样进快照。

- **实测污染规模**：1729 / 4416 = **39% 的快照对象是构建产物**
  （1714 个 Method 来自 electron-builder 打包后的压缩代码、7 个 Module、5 个 SourceFile、2 个 Class）。
- **下游代价**：
  1. 模块图出现 `mod:app/release.noindex` 幽灵模块；
  2. `check duplicates` 报出 **315 个重复组**，组 #1 的 14 个成员全是打包后的 `errorNN` 压缩函数；
  3. MDS 坐标投影被稀释，模块级解释方差被拉到 21.2%（同规模项目在 46% 量级）；
  4. 被 `.gitignore` 显式排除的敏感文件（`*update-private-key*`）处于可扫范围。
- **新增** `src/analyzers/gitignoreFilter.js`：按 git 语义解析 `.gitignore` ——
  `#` 注释 / 空行 / `!` 取反 / 尾部 `/` 仅目录 / 前导或中间 `/` 锚定到基准目录 /
  `*` `?` `[abc]` 通配 / `**` 跨目录；**嵌套 `.gitignore` 作用域为其所在目录且优先级高于父目录**
  （子目录后写的行优先）。
- **接入点** `src/analyzers/projectScanner.js`：`walk()` 递归时对目录做「整棵剪枝」、对文件做逐个排除，
  与 git 行为一致（不进入被忽略的目录）。`scanProject()` 返回值新增 `gitignore` 统计块
  （`enabled` / `ignoredFiles` / `ignoredDirs` / `ruleCount` / `scopeCount` / `fallback`），
  **「为什么少了这些文件」可解释**。
- **逃生舱**：`respectGitignore: false` 关闭过滤（`action refreshRepo --params '{"respectGitignore":false}'`）。
- **保守兜底**：若 `.gitignore` 规则写得过宽把整棵树排除（过滤后 0 个文件但确有命中），
  自动放弃过滤重扫并标记 `fallback: true` —— **宁可多扫，不可扫出空快照**。
- **修复效果实测**（desktop-pet 重扫）：源文件 370 → 365、快照对象 **4396 → 2667（-39.3%）**、
  **幽灵对象 1729 → 0**、幽灵模块 7 → 0、`Method` 3323 → 1609；
  模块级中位半径 0.130 → 0.180（不再被幽灵零度节点主导）。
- **测试**：新增 `test/gitignoreFilter.test.mjs` **10 例** —— pattern 编译单元测试（锚定/非锚定/仅目录/取反/
  通配/`**`）+ 端到端（构建产物与敏感文件排除、嵌套 `.gitignore` 与优先级、逃生舱、
  规则过宽兜底、无 `.gitignore` 时行为零变化）。
- **诚实边界**：本次修复消除的是**污染**，不是**投影质量问题**。
  该项目模块级解释方差修复后仍只有 23.2% —— 根因是模块图本身稀疏（66 模块仅 119 边），
  与构建产物无关。判据在稀疏图上的退化（半径中位数判别力不足）是**独立问题**，待单独处理。

### 坐标矩阵阶段 4：坐标作为代码图谱的「语义视图」附加层

让坐标在可视化侧可见。**明确这是"可见但不决策"的一层** —— 力导向负责「看」（视觉不挤、便于扫读），
MDS 坐标负责「量」（依赖深度 / 耦合半径 / Martin I）。默认仍用力导向，语义视图是前端上的一个开关。

- **新文件** `src/ontology/viewerCoordinates.js`：坐标适配层，把 `check matrix` 的坐标结果投影到 viewer 模型。
- **坐标块**：`codeGraph.moduleView.coordinates`，含 `embeddingMethod` / `solver`（**两件事分列**，
  混写会让下游无法判断 fidelity 口径）、`explainedVariance` / `fidelity`（**坐标是投影不是地图**，必须随图披露）、
  `confidence: {tier, source}`（对齐 ADR 0021 统一质量块口径）、`items: [{id, x, y, depth, balance, radius, instability, ca, ce}]`。
- **默认不注入**：`buildViewerModel(dataMap, { withCoordinates: true })` —— 默认 false，**连键都不出现**。
  未开启时 viewmodel 与旧版**逐字节一致**。
- **只在 `export --format html` 开启**：`--format viewmodel` 的产物由 **agent 直接消费**，
  坐标块对它没有决策价值（agent 要结构判据走 `check matrix` / code-review skill），
  却是实打实的 token 与体积开销（本仓 viewmodel 已 378KB）。HTML 才是「人看图」的场景。
- **与图节点集严格对齐**：模块图节点会被裁剪到 ≤90，坐标按图的实际节点集过滤 ——
  否则会出现「图上没有的坐标」，坐标语义与图不一致。
- **规模熔断**：模块数 > 200 直接不算 —— MDS 是 O(n³)，宁可没有语义视图，也不能让导出慢到不可用。
- **无区分度闸门**：投影退化成全同角色 / 全孤立 → 不出场（与 context-docs 的 no-signal 闸门同一条纪律）。
- **前端**：「语义视图」开关（**默认关闭**）；开启后按 MDS 坐标排布（y 轴翻转对齐屏幕坐标）；
  tooltip 显示 `依赖深度 / 净消费-基座 / 耦合半径 / Martin I`；图例随图披露投影质量 + 「坐标是投影不是地图」。
  **语义视图下拖拽不跑力模拟** —— 跑一次就会把 MDS 坐标布局冲掉（坐标是「量」，不能被弹簧重排）。
- **测试**：新增 `test/viewerCoordinates.test.mjs` **14 例** —— 字段契约 / 半径可由 x,y 复算 /
  与图节点集一一对应 / 默认不注入 / 规模熔断 / no-signal 闸门 / 客户端开关默认关闭 / 投影质量随图披露。

### 坐标矩阵阶段 3：影响面本地降级路径（`nice-aos check impact`）

`nice-aos graph impact` 走 Neo4j 远端图，没配图数据库就用不了。阶段 3 补一条**本地快照路径**：
快照里就带着全部对象与隐式关系，不需要图数据库也能回答「改这个会波及谁」。

- **新命令**：`nice-aos check impact <target> [--depth N] [--format table|json] [--output <file>]
  [--compare-neo4j]`。深度钳制 1~6，默认 3。
- **本地与远端同源同口径**：新增 `src/ontology/linkGraph.js` 抽出链接抽取（与 Neo4j push 同一实现）
  + 19 类 forward 依赖边白名单（唯一一份）；`src/storage/neo4jSnapshot.js` 删除本地重复的
  `relType` / `buildIdTypeIndex` / `IMPLEMENTED_LINK_TYPES` / `IMPACT_REL_TYPES` 定义，改为复用。
  种子展开（自身 + CONTAINS 成员 + 所属 file）、分层 BFS、访问集去重、`hops` 取最短跳、
  `viaRels` 取 min-hop 路径边去重 —— 逐条同构。
- **消掉双份算法漂移的隐患**：阶段 1 已实际发生过一次 radius 口径漂移（两处各算各的），
  本轮把抽取实现收敛到 `linkGraph.js` 一处，远端路径改为复用。
- **`ask --graph` 降级路径重写**：原先无 Neo4j 时只给一句提示；现在先抽目标 → 有 Neo4j 走远端 →
  否则走本地影响面路径，`meta.source` 标注 `'neo4j' | 'local'`，agent 能明确知道结论出自哪一路。
- **刻意不提供顶层别名**：`nice-aos graph impact` 已是远端图路径，同名不同实现会让人误判拿到的是哪一路。
- **测试**：新增 `test/localImpactParity.test.mjs` **15 例** —— 复用既有 `test/graphImpactParity.test.mjs`
  的路径枚举 oracle 与同一套夹具，**不依赖 Neo4j** 就能钉死本地路径语义
  （min-hop / viaRels / 截断 / 环 / 种子展开 / 深度钳制 / 错误码 / CLI 契约 / 链接抽取形态）。
- **刻意不做坐标剪枝**：实测 `check impact` 总耗时 47ms 中图构建占 46ms、BFS 仅 1ms，
  剪枝省不下 2 倍 → 按方案自定规则（「加速 <2× 就直接放弃」）放弃 `--radius`。

### 坐标矩阵阶段 2：code-review skill 的结构层判据

阶段 2 把结构坐标接进 `nice-aos-code-review-skill` —— 给依赖 `archLayer` 的规则补上不依赖层次声明的兜底判据。
**规则写在 skill 文档里（改的是给 agent 的提示词，不是运行时）**，但判据在测试里可执行化，防止文档与行为漂移。

- **`spec/review-rules.md` 新增 §14「结构层（Structure）」整节**：字段语义表（`depth` 大=基座、
  `balance = ce−ca > 0` = 净消费方、`radius` 越小越靠核心等）、`archLayer` 可用性判定、
  兜底判据表、4 条新规则、hub-fanout vs real-fragile 成对看表、COUPLE-06 误报豁免、本仓实测真阳性表、
  诚实边界声明。
- **ARCH-02 / ARCH-03 / COUPLE-03 三条既有规则**补「`archLayer` 不可用时走结构层兜底」引用。
- **4 条新规则**：`ARCH-06`（声明层与结构角色不符）、`ARCH-07`（事实基座未显式建模）、
  `COUPLE-06`（隐性耦合群 / 平行模块组）、`COUPLE-07`（真脆弱模块）。
- **`spec/review-template.md` 新增「结构层诊断」整节**；附录 B 加 Step 3.5 CLI 命令、附录 C 加数据源登记。
- **`SKILL.md` 流水线加 Step 3.5 结构层判据**（含完整 bash 命令与判据表 + 三条纪律）；
  维度表 ARCH 5→7、COUPLE 5→7；规则计数 80+→84+。
- **测试**：新增 `test/structureRules.test.mjs` **12 例** —— 把 §14 判据逐条翻译成代码对本仓快照实跑，
  断言命中对象与文档里写的「本仓实测真阳性」表**逐字一致**；另含 5 例纯边界测试（不依赖快照）。
- **实跑命中**：ARCH-06 命中 `cli/commands`（`archLayer=shared` 但 `ce=125 > 2×ca=42`）；
  ARCH-02 兜底命中 `ontology`（outEdges=38）与 `storage`（10）；ARCH-07 命中 `themes`
  （`ce=0`、`ca=24`、5 个平级模块单向依赖它 —— 事实基座但命名未体现，与方案验收示例逐字对上）；
  COUPLE-06 命中 3 组平行模块群。ARCH-03/COUPLE-03 兜底命中 0 条 —— 本仓 depth 只有 0/1 两档且
  depth=1 的模块 `ce=0`，不存在方向反转，文档已诚实说明「不命中不等于失效」。

### skills 完善：上下文生成 skill 接入结构层

- **`src/ontology/contextDocs.js`**：新增 `renderStructureSection()` / `structureRole()`；
  `buildContextDocs(dataMap, options)` 接受**可选注入** `options.structure`（保持纯函数，不自己算、不触盘）。
- **`src/cli/commands/export.js`**：`output docs` 在 `archLayer` 塌陷时自动计算模块级结构坐标并注入；
  失败自动降级为无结构层的老格式；`summary` 增 `structure` 块（level / embeddingMethod /
  explainedVariance / fidelity / nodeCount / diagnosticCount）或 `structureError`。
- **出场闸门（`structureLayerGate`）**：结构层是 `archLayer` 塌陷时的**替代分层**，不是并列的第二套视角。
  `archLayer` 可用 → **不出场**（前端等项目的文档行为零变化）；投影退化成全模块同标签 → **不出场**
  （噪声不是信息）。闸门判定前置于坐标计算，省掉无意义开销。
  塌陷阈值（主导层 ≥80%）收敛到 `src/ontology/semantics.js` 的 `detectArchLayerCollapse()` 单一事实源，
  与 code-review skill §14.1 同一口径 —— 两处不一致会出现「文档说别信 archLayer、评审说 archLayer 可用」。
- **`skills/nice-aos-context-builder/SKILL.md` / `README.md`**：同步结构层能力与出场闸门口径（版本 0.5.0）。
- **`skills/nice-aos-skill/SKILL.md`**：登记 `check matrix` / `check impact` 与结构层数据源。
- **测试**：新增 `test/contextDocsStructure.test.mjs` **12 例** —— 不注入 structure 时行为完全不变；
  注入后三处内容正确；层次分散时不出场；闸门四条分支逐条钉死；structure 节点对不上/为空时安全降级。

### 坐标矩阵阶段 1：`nice-aos check matrix` 结构体检器（已落地）

把代码依赖图的**邻接矩阵**转成可度量的二维坐标，正式接入 CLI：新增 `nice-aos check matrix`
（归入既有 `check` 体检族，只读本地快照、不依赖 Neo4j），并提供 hidden 顶层别名 `nice-aos matrix`。
设计稿见 `docs/arch-review/coordinate-matrix-plan-v2.md` 阶段 1。

- **新命令**：`nice-aos check matrix [--level module|file] [--format table|json] [--output <file>]
  [--method auto|shortest-path|coupling-similarity] [--detect-rules] [--html <file>]
  [--instability-high N] [--implicit-coupling N]`。
  输出含坐标、`depth` / `radius` / `balance` / Martin I / `ca` / `ce` / `sharedNeighbors` /
  `implicitCoupling` / `outConcentration`、结构诊断清单与架构规则候选。
- **输出契约不新造**：默认走 legacy 形状；`--envelope` 开启时走 `{ok:true,data:{...}}`
  （opt-in，与既有命令一致）；`--envelope --output <file>` 时**文件里也是 envelope 形状**。
- **诊断四类**：`hub-fanout`（高扇出但靠近耦合核心 → 结构枢纽，非脆弱）、`real-fragile`
  （高扇出且远离核心 → 真脆弱）、`pure-consumer`（Ca=0，Martin I=1 但可能完全无害）、
  `implicit-cluster`（互为近邻却无直接边的**平行模块群**，血缘口径完全看不见）。
- **规则候选是候选不是结论**：`--detect-rules` 产出的每条都带 `evidence` + `confidence`(≤0.7)
  + `requiresHumanConfirm: true`，定位是候选生成器，不自动改代码、不自动改断言。
- **落点**：新文件 `src/analyzers/structureCoords.js`（纯 JS 线性代数 + Tarjan SCC，零外部依赖）、
  `src/cli/commands/matrix.js`、`src/cli/commands/matrixHtml.js`；改 `src/cli/commands/check.js`
  与 `src/cli/index.js`（hidden 别名）。
- **HTML 报告自包含**：邻接矩阵热力 + MDS 坐标平面 + 力导向对照 + 诊断表 + 规则候选，
  **无外链 / 无 `<script>` / 无 `src=`**，双击即开、可邮件转发。
- **派生视图纪律**：坐标矩阵是**派生视图**，不进 blueprint、不新增 LINK_TYPES、
  不引入 Neo4j 依赖（ADR 0019 / 0020 / 0022 / 0023 口径）。

### 本轮修正：三个会让结论失效的实质缺陷

1. **`radius` 口径错位（直接推翻方案结论 A）**：MDS 的双中心化保证原点 = 质心，
   所以「到耦合谱中心的距离」必须按质心算。最初实现在**逐轴 min-max 归一化后的平面**上算半径，
   等于把原点从质心搬到包围盒角点 —— 实测 `cli/commands` 的真实核心半径 0.0999 被算成 0.5301，
   反而大于 `database` 的 0.2803，排序彻底打乱，「I 高 + 半径小 = 稳定枢纽」被直接推翻。
   现改为**质心居中 + 等比缩放到 [0,1]**，`radius = hypot(x−0.5, y−0.5)`，**可由 `x,y` 直接复算**，
   跨方法/跨规模可比。实测模块级 16 节点在两种口径下隐性耦合分组完全相同。
2. **CLI 默认值静默覆盖层级阈值**：`--implicit-coupling` 原硬编码默认值 `'2'`，
   把分析器按层级设定的文件级 `3` 覆盖掉 → 文件级口径被悄悄改掉。
   现该选项**不设默认值**，只有用户显式传参才覆盖。
3. **诊断阈值淹没噪声**：只看 Martin I 时，文件级 178 个文件有 67 个 I ≥ 0.5，
   但它们的 Ce 中位数只有 4（叶子文件的常态）。叠加**绝对扇出量下限 `minFanout=8`** 后，
   文件级诊断从 73 条收敛到 36 条 —— **I 是比例，Ce 才是量**，两者必须一起看。
   隐性耦合群改用 **Bron–Kerbosch 极大团**分组（连通分量会把平行模块组与外围一起吞进来，
   实测一次吞掉 12/16 模块），`minClusterSize` 由 2 提到 3（2 个不算「群」）。

### 阶段 1 实测（nice-aos 自身代码库，178 文件 / 16 模块，快照 @ `648be42`）

- **模块级**：16 节点 / 53 依赖边 / 11 环内节点，解释方差 45.6%、保真度 0.797、应力 0.401
- **文件级**：178 节点 / 395 依赖边，解释方差 43.6%、保真度 0.895，端到端 **1.7~2.0s**
- **Martin 误报消解**：`cli/commands` I=0.856（全仓最高）但半径 0.076（全图最小）→ 判为**结构枢纽**
  而非脆弱模块；`cli` I=0.534、半径 0.095 同理
- **隐性耦合群**：模块级 3 组（`{deployment, planning, service}` 等）；
  文件级 **23 组**，逐个人工核对全部成立 —— 平台分析器组、语言分析器组、ask 子命令组、
  只读命令组、四个 viewer 组、快照构建器组、viewerClient 数据源组……
  这批「同一生态位的平行模块组」在 fan-in/fan-out 血缘口径里**完全不可见**
- **测试**：新增 `test/matrixCommand.test.mjs` **34 例**（CLI 契约 / envelope / 错误码 / HTML 自包含 /
  Martin I 手算核对 / 确定性 / 三个工程坑回归 / 诊断语义 / 阈值层级口径）；
  `npm test` → **1240 例 / 1174 通过 / 58 失败 / 8 跳过** —— 58 例与 `docs/guide.md`「开发」段
  记录的 **Windows 既有基线完全一致**（`ERR_UNSUPPORTED_ESM_URL_SCHEME`），**零回归**
- **已知技术债**：`scripts/matrix-*.mjs` 仍是独立实现，未改为从 `src/analyzers/structureCoords.js`
  导入 → 存在双份算法漂移风险（本轮已实际发生一次 radius 口径漂移），收尾时应统一到单一真源

### 调用轨迹视图（ADR 0023 P0/P1/P2）

本体蓝图新增第 14 个 Tab「调用轨迹」：把「轨迹」重定义为**静态调用流 hop 链**（用跳数代替时间戳、
用证据等级徽标代替角色徽标、用起点→终点代替输入输出——快照无时序/无执行主体/无运行值，
照抄 agent 会话截图不成立）。设计稿见 `docs/adr/0023-call-trajectory-view-proposal.md`（评审修订版）。

- **四档能力，按事实探测启用**：L1 前端调用点 → API 路由 → handler 方法；L2 handler → 下游方法；
  L3 末端（SQL 表名 / 出网端点）；兜底 页面导航流 + 文件 import 依赖流。
  **覆盖率强制披露**：面板逐档给出 `active` / `no-facts`（本图无此事实）/ `not-enabled`（有事实但本版未启用）
  与原因文案，并附本图事实盘点（路由数、import 边数、方法调用链条数、SQL 表名条数、出网端点数、
  前端调用命中率）——**宁可显示覆盖率，不给臆造链**。
- **边的来源逐边标注 `origin`**：**本体链接**（已注册 linkType：`imports` / `navigatesTo` / `calls` /
  `callsApi` / `requestsTo` / `passesProps`）与**字段事实**（快照字段：前端调用匹配 `Route.frontendCalls`、
  handler 绑定 `Route.componentId`）。字段事实**不是本体链接**，UI 给独立徽标。
  **不新增 linkType**：不进词表、不改 `LINK_TYPES`、不碰 `graphImpact` 的 `IMPACT_REL_TYPES`，
  ADR 0019 T2 词表口径与 ask/graph 工具链零影响。
- **证据分档对齐 ADR 0021**：前端调用匹配 tier1→1.0 / tier2→0.9 / tier3-4→0.7，
  人工规则命中（`.nice-aos/api-routes.json`）→0.7，本体直接事实→1.0。
- **遍历纪律**：分层 BFS（frontier + visited，代价 ∝ 节点+边）+ 代表链派生（每层取一个代表节点），
  **不做路径枚举**（ADR 0020）；hop ≤ 3、每层前沿 ≤ 8、边 ≤ 400、入口 ≤ 60（api 30 / nav 15 / import 15），
  截断数在模型 `budgets.droppedEntries` 披露。
- **落点**：聚合层新文件 `src/ontology/viewerTrajectory.js`（viewerModel.js 已 63KB 不再内联）、
  客户端新文件 `src/ontology/viewerClient/trajectory.js`（覆盖率面板 / hop 条带图 / 跳明细表）、
  装配 6 处（`viewer.js` Tab+section、`viewerClient.js` 片段顺序、**INIT 必须最后**、`init.js` 隐藏与挂载、
  `viewerModel.js` 聚合与返回字段、`viewerStyles.js` 样式）。
- **顺带修复既有缺陷**：`Route.apiMethods` 口径不统一（Go/Next 是数组、Python 是裸字符串），
  视图模型层未归一，导致含 FastAPI 路由的仓在客户端路由地图渲染时 `apiMethods.join('/')` 抛错，
  **中断 INIT 后续所有 Tab 渲染**；已在 `viewerModel.js` 统一归一为数组。
- **测试**：`test/trajectoryView.test.mjs` 新增 5 例（前后端同仓 L1 链结构与 `origin` 标注 /
  纯前端仓 L1 关闭且兜底有链 / 无入口仓模型为 `null` / tier→confidence 映射 / 客户端渲染输出）；
  全部引用 viewer 的 19 个测试文件回归 180 通过、0 回归失败。

### 调用轨迹视图 P1/P2：L2 方法链 + L3 末端 + CommonJS require 解析

**P1**：L3 末端（SQL 表名弱节点 / 出网端点）+ L2/L3 独立入口 + 单链分层 SVG 图 + 跨 Tab 跳转。
**P2**：L2 方法链按 `Method.callIds` **事实探测**自动开启（不按语言名单开关），四档覆盖率状态
从 `not-enabled` 收敛为 `active` / `no-facts`（每档都写明原因，禁用即指名原因）。

- **L3 SQL 表名弱节点**：自建快照没有 `Table` 对象，故表名以 `sql:<table>` 呈现，附台账
  「被 N 个方法写 · M 条语句」——**数据枢纽表一眼可见**；`dynamic:true` 的动态表名标 0.7 证据级。
- **L2 方法链**：`Method.callIds` 分层 BFS 展开（仅 Dart/Go/Python/油猴有该事实），
  无方法级调用链的语言显式标注「不补不存在的边」（ADR 0022 实测一）。
- **单链分层 SVG**：左=起点、按跳数分列，实线=本体链接 / 虚线=字段事实；**跨 Tab 跳转**
  （route→路由地图、method/class→实体类图、file→代码图谱、net/us/fn→脚本蓝图、store/hook/svc→业务数据图）。
- **CommonJS `require()` 解析**（配套改动，P1 真实项目实测暴露）：`tsAnalyzer` 此前只认 ESM `import`，
  零依赖 CommonJS Node 项目的 import 边恒为 0 → 轨迹视图整体为空。现按
  `const x = require(...)` / `const { a, b } = require(...)` 两种绑定形态登记，
  与 ESM import 同构进 `facts.imports` + `facts.importMap`。实测某真实项目 import 边 **0 → 223**。
- **解析率修正**：`builtin`（node 内置模块）与 `asset`（静态资源）此前不计入已解析，
  `importResolutionRate` 被系统性拉低——现计入已解析。
- **真实项目验证**（零依赖 CommonJS Node 服务端，107 文件 / 1312 Method / 441 条 SQL）：
  轨迹模型由 `null`（Tab 隐藏）恢复为 **32 个入口**（16 方法 L3 + 16 文件兜底）；
  L1 显式标注「裸 http.createServer 的手写路由不产 Route，本档整体不适用」，
  L2 标注「方法级调用链为 0 条」，L3/兜底 active；导出 HTML 287 KB，客户端渲染冒烟通过。
- **测试**：`test/trajectoryView.test.mjs` 5 例 → **8 例**（新增 SQL 弱节点 + L3 激活、
  Python 方法链 + L2 激活、CommonJS require → import 边底座）；全仓 `npm test` 无新增失败。

### panorama 接入统一输出契约（v0.59 补齐）

`panorama` 是 v0.57 新增的跨域全景命令，落在 v0.59 引入的**统一输出契约**之前——全仓
`--envelope` 开启后它是唯一仍吐裸对象的结构化命令：agent 拿到 `{ok:true, matrix:{...}}`
而非约定的 `{ok:true, data:{...}}`，`data` 恒为 undefined，且裸对象顶层那个 `ok:true`
与真实字段平排，极易被误读成「已经符合契约」。

- **成功出口**：改走 `emitData`——默认模式输出**完全不变**（仍是裸 panorama 对象，
  历史消费方零影响）；`--envelope` / `NICE_AOS_ENVELOPE=1` 下统一为 `{ok:true,data:{...}}`。
  文本模式在 envelope 模式下同样出 envelope JSON（与 `vocabulary` 同语义）。
- **失败出口**：改走 `failWith(code, message, {hint})`——默认模式由「一句人话」升级为
  `{ok:false, error:"…", errorCode:"…", hint:"…"}`，envelope 模式下 `error` 升级为
  `{code, message, hint}` 三件套。
- **错误码映射**：`E_SNAPSHOT_NOT_FOUND`（缺代码快照）/ `E_SNAPSHOT_CORRUPT`（快照存在但
  解析失败）/ `E_PANORAMA_FILTER_INVALID`（category / stage 取值非法，附合法取值清单）/
  `E_INVALID_PARAM`（`--section` 取值非法）。新增 `E_PANORAMA_FILTER_INVALID` 登记进
  `src/common/errors.js` 的 `ERROR_CODES` 与 `CODE_HINTS`（此前只有 serve 端点用字面量）。
- **`readOptionalSnapshot` strict 模式**抛出的错误补 `code = 'E_SNAPSHOT_CORRUPT'`，
  供 CLI 结构化失败出口分流。
- **文案修正**：`--section sources` 的数据源清单改用 CJK 宽度对齐（`padEndWidth`），
  中文标签不再因 `padEnd` 按字符计数而错位。

**测试**：`test/panoramaCli.test.mjs` +7 例（默认模式零破坏 / `--envelope` 包装 /
文本模式在 envelope 下出 JSON / 环境变量开关 / 两种错误形态 / 三类错误码映射）；
`test/envelope.test.mjs` 新增第 7 组「panorama 接入统一契约」——该文件是
「哪些命令遵守 `--envelope`」的单一索引。

### vocabulary linkTypes 事实化（ADR 0019 T2）

`vocabulary.linkTypesActive` / `reverseLinkTypesActive` 的判定口径从**前缀可达性**（源/目标前缀有对象即 active，会把「有对象但零边」的 linkType 误标 active）升级为**本图实际边事实**——只列本图实际存在 ≥1 条边的 linkType：

- blueprint 可遍历的链接：对候选源对象真实调用 `link()` 探测，首个非空边即 active（早退）
- 边物化在 `_meta` 的链接（shellEdges / rosEdges / crossLangEdges）按实际边计数（unresolved `to:null` 占位不算）
- 纯声明链接（无实现也无物化边）标 inactive——本图不可能出现可遍历边
- 无快照时退化为全集（不作任何「不存在」承诺），行为不变

CLI `nice-aos vocabulary`、serve `/api/schema`、MCP `get_schema` 三入口同步生效（同一事实源）。原「未做精确边计数」的取舍说明作废：现在直接探测真实边，无需字段名映射表。

### 边 confidence 常量 + 统一质量块（ADR 0019 T5，见 [ADR 0021](docs/adr/0021-linkdef-confidence-and-unified-quality-block.md)）

ADR 0019 T2 解决了「这条 linkType 在本图**有没有边**」，本轮补上另一半——
「这条 linkType 靠什么证据成立」，以及「这张图整体质量如何」。

**一、linkDef 级 confidence（证据强度）**

`src/ontology/linkDefs.js` 的 58 条 def 全部带上 `confidence` + `reason`，三档取值
（沿用 asdm-aos 的 1 / 0.9 / 0.7）：**结构级 1.0**（源码或本体显式声明）/
**引用级 0.9**（import / 调用 / 渲染等显式引用）/ **推断级 0.7**（命名约定或启发式派生）。

- **分类默认值**：`hierarchy → 1.0`、`reference → 0.9`、`derive → 0.7`。
- **`derive` 分类逐条定档**：该分类内部性质并不一致——`extends` / `implements` /
  `overrides` / `usesTrait` 是源码关键字显式声明（关系必然成立，定 1.0），只有
  `mapsToTable` / `mappedFromCode` 真正靠命名约定猜（定 0.7）。一刀切给 0.7 会让
  agent 用「排除不确定边」时误杀类型体系里**最确定**的关系——那是误报，违反本库哲学。
- **与逐边 confidence 正交**：逐边 `{id, confidence, reason}`（`linkMeta.js`）回答
  「这一条**具体边**的运行时证据有多强」（解析降级：fallback / auto-import / 缺源）；
  def 级回答「这一**类关系**靠什么成立」。def 级**不参与**逐边计算——逐边缺省仍是
  `1.0 / direct`，既有输出与下游预期零变化。

**二、统一质量块**

新增 `src/ontology/quality.js`，产出 `ConvertQuality` 同形的
`{ danglingEdges, orphanNodes, externalNodes, duplicateIds }`：

| 字段 | 口径 |
|---|---|
| `danglingEdges` | 构建期被摘除的悬空引用值个数（与 `_meta.refIntegrity.danglingCount` 同源） |
| `orphanNodes` | 孤儿候选节点数（文件级口径，同 `_meta.orphanCandidates`） |
| `externalNodes` | 已物化但实现不在本仓的实体数（`dep:` 第三方包，排除 workspace 本地包；`ext:` 虚拟外部实体为 ADR 0019 T3 预留） |
| `duplicateIds` | 快照内重复 id 的额外出现次数（生成期不变量校验） |

- **构建期产物**：写入 `_meta.quality`。`danglingEdges` **只能**构建期算——悬空引用在
  `auditRefIntegrity` 阶段已被摘除，运行时无从得知当年摘了几条。
- **旧快照降级**：无 `_meta.quality` 时按快照现算其余三项，`danglingEdges` 置
  `null`——**null 表示「未知」，不是 0**，绝不谎报。
- **无快照时** `vocabulary.quality` 为 `null`（不可评估 ≠ 一切正常）。

**三、四个出口（同一事实源）**

| 出口 | 形态 |
|---|---|
| 快照 | `_meta.quality` |
| MCP `get_health` | `summary.quality` |
| serve `GET /api/stats` | `quality` |
| MCP `get_schema` / serve `/api/schema` / CLI `vocabulary` | `vocabulary.quality` + `vocabulary.linkTypeConfidence` |

`vocabulary.linkTypeConfidence` 覆盖**全部**已声明 linkType（不只 active 的）——
agent 需要在查询前就知道每类关系的证据强度；与 `linkTypesActive`（有没有边）正交。
`_vocabularyStats.linkTypeConfidenceTiers` 给出三档计数。

**测试**：新增 `test/quality.test.mjs`（14 例：三档覆盖 / 分类默认 / derive 逐条定档 /
逐边语义零破坏护栏 / 质量块四项口径 / 旧快照降级 / 构建期集成 / 三出口）；
`test/envelope.test.mjs` 第 5 组补契约断言；`test/serve.test.mjs` 补 `/api/stats` 质量块断言。

全量回归 1190 例 / 58 例失败——与改动前失败集合**逐项一致**（已用 HEAD 基线工作树对拍确认
零新增；58 例均为既有 Windows `ERR_UNSUPPORTED_ESM_URL_SCHEME` 环境问题）。
判定方法与基线数字见 `docs/guide.md` 的「全量测试的既有失败基线（Windows）」一节。

**明确不做**：不改逐边 confidence 的计算（会改变既有输出）；不给 `link` 命令加
`--where`（属独立特性，另开 ADR）；不做 T3 `ext:` 虚拟外部实体（本次仅预留口径）。

### 接口多态展开 —— 落点重定为「契约闭包」（ADR 0019 T4，见 [ADR 0022](docs/adr/0022-interface-polymorphism-contract-closure.md)）

ADR 0019 T4 原规格是照搬 asdm-aos 的「多态派发目标」展开（扩 `calls` / `calledBy`）。
**本轮先做前置条件实测，结论是照抄会得到一个「实现了但对主力语言零效果」的功能。**

**实测一：asdm 那套算法作用在「方法调用链」上，而 TS/JS 根本没有调用链。**
三组 fixture 走真实构建管线：

| 语言 | 接口/实现模型 | 方法调用链 | `calls`/`calledBy` 实测 |
|---|---|---|---|
| TS/JS | ✅ 完整 | ❌ 无 | `getUser` 函数体里明明调 `r.find(id)`，边仍全空 |
| Dart | ✅ 完整 | ⚠️ 解析不出边 | 块体与表达式体两种写法皆全空 |
| Go / Python | ❌ 无接口模型 | ✅ 有 | 不适用 |

根因：`Method.callIds` / `calledByIds` 只在 Dart / Go / Python / 油猴脚本路径填充，
**TS/JS 分析器不产出方法级调用事实**。asdm 的 `findImplementingMethods` /
`collectContractMethodIds` 输入都是调用链上的边 → 输入恒空 → 输出恒空。

**实测二：接口/实现结构在 TS 上完整可用 —— 真正的落点在这里。**
`Class.implementsIds`、`Interface.extendsIds`、`Method.overridesId`/`overriddenByIds`
在 TS fixture 上全部正确落盘，「改这个接口方法影响谁」是**答得准**的，只是**不完整**。

**实测三：原规格未覆盖的两处真实数据缺陷（TS 上可复现）。**

| 缺陷 | 复现 | 后果 |
|---|---|---|
| 接口之间从不建立方法覆盖链 | `interface ChildRepo extends Repo` → `ChildRepo.find.overridesId = null` | 「改 `Repo.find` 影响谁」漏掉整个子接口分支 |
| 多契约被单值覆盖静默丢弃 | `class AB implements A, B`（A/B 都有 `foo`）→ `AB.foo.overridesId` 只剩 `B.foo` | 「`AB.foo` 覆盖了哪些契约」答不全 |

**落地**

- **构建期修两处数据缺陷**（`typeEntities.js` 的 `linkMethodOverrides`）：
  `Method.overridesIds[]` 改多值（单值 `overridesId` 保留**首值**兼容既有消费方）；
  接口之间沿 `extendsIds` 向上按同名方法建立覆盖链。
- **查询期算契约闭包**（新增 `src/ontology/polymorphic.js` + `blueprint.linkDetail`）：
  快照只存一跳直接事实，闭包在 blueprint 层 BFS 算出——
  `overriddenBy(契约方法)` 含子接口分支与传递实现者，`overrides(实现方法)` 含父契约。
- **闭包边按跳数标证据强度**：1 跳 = 直接事实（`1.0/direct`，语义不变）；
  ≥2 跳 = 传递闭包（`0.9/contract-closure`）。MCP `traverse_links` 的 `edges`、
  CLI `link --depth>1` 的 `_confidence`/`_reason`、CLI `link --depth 1` 的闭包边均可见。
- **零破坏**：无接口继承的图，`link` 输出与改动前**完全一致**（闭包边才加标注字段）。

**测试**：新增 `test/polymorphic.test.mjs`（12 例：接口间覆盖链 / 多契约不丢 /
闭包双向 / 证据强度分级 / 无继承时零破坏 / 旧快照单值兼容 / CLI 端到端）。

**明确不做**：不改 `calls`/`calledBy` 现有语义（无调用链语言上本就恒空）；
不实现 asdm 的派发目标展开——其算法与前置条件已存档在 ADR 0022 D5，
待「TS/JS 方法调用链」或「Dart 调用边解析」落地后启用；
不处理「类继承链上的方法继承」（`Derived extends Base` 未重声明时无方法实体可返回）。

### 坐标矩阵方案验证（实验阶段，尚未接入 CLI）

把代码依赖图的**邻接矩阵**转成可度量的二维坐标（MDS），验证它能否比现有力导向图
多提供架构信息，并探索其在「规则生成 / 影响面预测」上的下游价值。

**当前状态：实验脚本与设计文档保留；CLI 接入见上方「坐标矩阵阶段 1」。**

### 新增：坐标矩阵实验脚本组（`scripts/matrix-*.mjs`）

| 脚本 | 作用 |
|------|------|
| `matrix-lab2.mjs` | 四种嵌入方法对比（最短路 MDS / hub 折扣 / 耦合相似度 / 拉普拉斯谱嵌入） |
| `matrix-eig.mjs` | 正交化幂迭代（top-2 特征向量）+ Jacobi 全谱（参考实现） |
| `matrix-depth.mjs` | SCC 感知的依赖深度（Tarjan 缩点 → DAG 最长路径） |
| `matrix-validate.mjs` | 坐标语义验证（层可分性 η² / 依赖深度相关 / 距离有效性，含置换检验） |
| `matrix-control.mjs` | 对照实验：MDS vs 现有力导向 vs 随机坐标 |
| `matrix-martin.mjs` | Martin 不稳定度 I 与坐标口径的诊断差集 |
| `matrix-temporal.mjs` | git 共变预测（显著性检验 / 组合预测器 / 多窗口稳健性 `--windows`） |
| `matrix-perf.mjs` | 幂迭代与 Jacobi 的等价性交叉校验 + 性能基准 + 端到端计时 |
| `matrix-viz.mjs` | 生成自包含 HTML 可视化（邻接矩阵 / 坐标平面 / 力导向对照） |

全部**零外部依赖**（纯 JS 线性代数 + `node:child_process` 调 git）。

**确定性修复**：坐标求解、置换检验、随机基线、力导向退化抖动原先各自使用未播种的 `Math.random()`，
同一份快照连跑两次 p 值会漂移（实测 y 轴 η² 的 p 在 0.070~0.139 间跳）。现统一改用
`matrix-eig.mjs` 导出的 `makeRng(seed)`（xorshift32），**同输入同输出**已核验：
`matrix-temporal` / `matrix-validate` / `matrix-control` / `matrix-viz` 支持 `--seed`（默认 42）；
`matrix-lab2` / `matrix-perf` 的特征求解固定用种子 42（性能数字需可比，不开放调整）。

**新增 `--windows` 稳健性**：`matrix-temporal.mjs --windows 30,45,87` 为每个提交窗口
**独立重算** churn 与共变真值（候选池与坐标固定），产出"组合预测器是否在所有窗口都最优"的实证表。
此前 plan-v2 文档里的 3 窗口表在仓库中**没有任何实现**。

**新增端到端计时**：`matrix-perf.mjs` 增补"全源最短路 + 特征分解"端到端口径——
此前只测特征分解，把端到端成本讲低了约一个数量级。实测 n=1200 时全源朴素 Dijkstra
独占 6.2s（O(n³)），是特征分解的 6 倍，才是真正的规模瓶颈。

**测试**：`npm test` → 1210 例 / 1144 通过 / 58 失败 / 8 跳过。58 例与 `docs/guide.md`「开发」段
记录的 **Windows 既有基线完全一致**（`ERR_UNSUPPORTED_ESM_URL_SCHEME`，集中在会 spawn 子进程 /
动态 import 的用例），本次改动只涉及 `scripts/`（无任何测试引用它）与文档，**零回归**。

### 实测结论（nice-aos 自身代码库，178 文件 / 16 模块）

- **坐标可度量**：平面上越近 = 耦合越紧（Spearman ρ=−0.471 文件级 / −0.690 模块级，p=0.005）；
  对照现有力导向图（ρ=−0.168 文件级）—— MDS 的可预测性是力导向的 2.8 倍
- **坐标承载架构语义**：x 轴 ~ 依赖深度 r=+0.554（文件级，p=0.005）/ +0.468（模块级，p=0.050）、
  x 轴 ~ 目录层 η²=0.420（p=0.005）；y 轴不显著（η²=0.156, p=0.139）→ **每轴须独立验证**
- **Martin 度量会误报**：`cli/commands` 的 I=0.856 是全仓最高，但坐标半径仅 0.076（CLI 口径，结构枢纽）。
  组合判据「I 高 + 半径大」才是真脆弱
- **坐标能发现隐性耦合群**：`deployment`/`planning`/`service` 共享 40 个邻居且互为近邻，
  fan-in/fan-out 口径完全看不见
- **时序预测**：距离单独 AUC=0.708（p=0.002）；与 churn 组合 AUC=**0.806**。
  距离**不能**替代 churn（P@50 仅 0.24 vs 0.62），但含独立于 churn 的结构信号
  （控制 churn 后 corr 由 −0.171 降至 −0.154，仅衰减 10%）
- **稳健性（3 窗口独立重算）**：30/45/87 提交的组合 AUC 分别为 0.823 / 0.825 / 0.799，
  **均优于任一单独信号**；且 churn 与距离的强弱在窗口间会翻转（87 窗口距离反超），
  这是"两者互补"的核心证据
- **规模化可行**：正交化幂迭代与 Jacobi 全谱**完全等价**（特征向量夹角 0.000°、stress 相对差
  ≤0.0002%），特征分解快 **25~1100 倍**（n=1200：348ms vs 384.4s）；但端到端 n=1200 为 **7.2s**
  （全源最短路占 6.2s，O(n³) 才是规模瓶颈）

### 设计文档

- `docs/arch-review/coordinate-matrix-analysis.md` —— 第一轮：可行性验证、三个工程坑
- `docs/arch-review/coordinate-matrix-plan-v2.md` —— 第二轮：外部对标（DSM / emerge / ArchUnit /
  CodeScene）、诊断差集、时序验证、规模化路径、五阶段落地方案。
  **2026-10-23 修订**：全部数值刷齐至 178 文件快照（此前正文停留在 174 文件，且与本 CHANGELOG 矛盾）；
  补齐稳健性窗口与端到端性能两项缺失实现；**按实现价值重排落地阶段**——主卖点由"影响面预测 /
  CodeScene 替代"改为"**给 code-review 规则补结构层证据**"（ARCH-02 / ARCH-03 / COUPLE-03 的判据
  依赖 `archLayer`，而本仓 170/178 文件 = `shared`，这三条规则在 CLI 工具类项目上本就失效）；
  原阶段 4（规则生成）并入阶段 1 的 `--detect-rules`，原阶段 5（archLayer 双输出）提级为 P1，
  原阶段 3（影响面）拆为"P1 本地降级路径（不依赖 MDS）"与"P2 坐标剪枝（可选）"；
  新增 §3.6 交代坐标矩阵与本仓 58 种血缘边的关系（当前仅用 `imports` 一种）。

### 尚未落地

`codeGraph` 坐标附加层、影响面预测器、code-review 规则表的结构层判据落地
均**未实现**——详见 v2 方案文档的阶段划分。现有行为零变化。

### 统一输出契约 + 能力发现 + 查询防呆（反哺自 data-hub 项目的借鉴笔记）

外部项目（`steam-userscripts / data-hub`）借鉴 nice-aos 时整理了一份对照表，指出若干可反哺的问题。
本轮把其中**成立且值得做**的 4 项落地，其余 2 项为对方自创（见文末「明确不做」）。

### 新增：统一输出 envelope（`--envelope` / `NICE_AOS_ENVELOPE=1`）

**问题**：AOS 各命令的成功输出顶层形状随动词漂移——`query` 出裸数组、`link` 出裸数组、
`count` 出 `{ok,type,total}`、`action` 出 `{ok,message,stats}`。消费方（agent / 油猴脚本 /
jq 管道）要各写一层适配。对方项目为此把契约定死为 `{ok:true,data:{...}}`。

**落地**：新增全局开关 `--envelope`（或环境变量 `NICE_AOS_ENVELOPE=1`），开启后统一为：

| 命令 | envelope 输出 |
|------|--------------|
| `query` | `{ok:true, data:{type, total, returned, objects:[...]}}` |
| `count` | `{ok:true, data:{type, where?, total}}` |
| `link` | `{ok:true, data:{linkType, src, depth, total, objects:[...], byDepth?}}` |
| `action` | `{ok:true, data:{message, ...}}` |

**默认模式行为零变化**——`query`/`link` 仍出裸数组，`count` 仍出单行 `{ok,type,total}`，
`action` 仍出 `{ok,message}`。contrib 油猴脚本（读 `error` 字符串）、jq 管道、serve 端点
都不受影响。`--envelope` 是 opt-in。

新增 `src/cli/envelope.js`（`emitData` / `emitFailure` / `resolveEnvelopeMode`）与
`src/cli/shared.js` 的 `failWith(code, message, {hint, ...extra})`。

### 新增：结构化错误 + 自纠正候选

失败输出在默认模式下也带上结构化信息（`error` 仍是字符串，旧消费方兼容）：

```json
{ "ok": false, "error": "未知对象类型: Compoennt。…", "errorCode": "E_UNKNOWN_OBJECT_TYPE",
  "hint": "用 `nice-aos vocabulary --json` 列出全部对象类型",
  "suggestions": [{ "value": "Component", "score": 0.311 }] }
```

envelope 模式下 `error` 升级为 `{code, message, hint}` 三件套。

- `ERROR_CODES` 从 8 条扩充到 16 条：新增 `E_UNKNOWN_OBJECT_TYPE` / `E_UNKNOWN_LINK_TYPE` /
  `E_UNKNOWN_ACTION` / `E_SNAPSHOT_NOT_FOUND` / `E_SNAPSHOT_CORRUPT` / `E_UNKNOWN_FIELD` /
  `E_INVALID_PARAM`，并新增 `CODE_HINTS` 映射（每个码配一条可执行修复提示）
- 未知对象类型 / 未知 linkType 附带 `suggestions`（编辑距离相似候选，前 5 条）
- `findSimilarStrings` / `levenshtein` 从 `toolRegistry.js` 私有实现抽到
  `src/common/similarity.js` 供 CLI 与 MCP 共用（toolRegistry 侧行为零变化）

### 新增：能力发现 `nice-aos vocabulary`

**问题**：此前「这张图里能查什么、能走什么关系」只有 MCP `get_schema` 一个入口；
serve `/api/schema` 只给全集（39 对象类型 + 58 链接类型），CLI 完全没有入口。
对方项目为此自建了 `hubq vocab`。

**落地**：新增 `nice-aos vocabulary` 命令，与 MCP `get_schema`、serve `/api/schema`
共用 `src/ontology/vocabulary.js` 同一事实源（`buildSchemaPayload({snap})`）：

```bash
nice-aos vocabulary                  # 文本摘要（★ 标记本图可达）
nice-aos vocabulary --json           # 完整 JSON（与 get_schema 同形态）
nice-aos vocabulary --active-only    # 只列本图可达的子集
nice-aos vocabulary --section links  # all / types / links / actions / stats
```

- 链接类型按**前缀可达性**收敛（`vocabulary.linkTypesActive` / `reverseLinkTypesActive`）——*口径后升级为边事实，见「未发布」*
- serve `/api/schema` 补上 `vocabulary` 块与 `_vocabularyStats`（此前只有全集）
- **无快照也能用**：类型 / 链接 / 动作名是静态的；只有「本图可达」需要快照，
  没有快照时可达性退化为全集
- MCP `get_schema` 改为调用同一函数——MCP 侧输出形态与行为零变化

### 新增：`link --depth` 多跳遍历

此前 CLI 侧只有单跳 `link`，递归影响面要 agent 手写循环（README 与 skill 教的都是手写递归）；
MCP `traverse_links` 有 depth 能力但 CLI 没有。

```bash
nice-aos link importedBy --src "file:src/services/ai.ts" --depth 3
nice-aos link importedBy --src "file:src/services/ai.ts" --depth 2 --limit 50
```

- 复用 MCP `traverse_links` 的同一套 BFS 实现（`linkBfsWithMeta`），证据/置信度语义一致
- 多跳结果每项带 `_depth` / `_confidence` / `_reason`（边的来源，如 `direct` /
  `vue-global-fallback` / `name-convention`）
- 深度上限 3（与 MCP 一致），`--limit` 默认 200 防爆炸
- `--depth 1` 与不传时结果完全一致（默认行为不变）

### 新增：`--where` 列名先校验

**问题**：`--where` 里拼错的列名过去会**静默返回空数组**——agent 无法区分
"该类型确实没有这个字段"与"字段名写错了"，这两种含义完全相反。这是查询纪律里长期承认的坑。

```bash
nice-aos query Method --where "deadCandidat=true"                # stderr 警告（仍返回结果）
nice-aos query Method --where "deadCandidat=true" --strict-where # 报错退出，E_UNKNOWN_FIELD
```

- 判定口径：字段名**根段**（点路径取第一段）须出现在该类型任一对象的字段并集里；
  允许"部分对象有、部分对象没有"的字段（如 `deadCandidate`）——那不是拼写错误；
  只有**所有**对象上都不存在时才判为未知列名
- 默认只警告不中断（零破坏），`--strict-where` 升级为报错退出
- `query` 与 `count` 同语义

### 测试

- 新增 `test/envelope.test.mjs`（21 例）：默认形状不变 / envelope 形状 / 环境变量开关 /
  未知类型与 linkType 的自纠正 / where 列名校验（警告与 strict）/ vocabulary 三形态 /
  link --depth 三形态
- `test/hubContracts.test.mjs`：两个枢纽的冻结导出清单显式扩到 v0.59.0 新增
  （`cli/shared.js` +failWith/collectFieldNames/findUnknownWhereFields；
  `ontology/snapshot.js` +loadSnapshotIfExists）——纯新增，无重命名/删减
- 全量回归：1131 → 1152 例（+21 新增），无新增失败

### 明确不做（对方的做法不适合 AOS）

- **`--field` 缺失字段输出 `null`**：AOS 维持"对象上不存在的字段不产生键"。
  AOS 面向 agent 与 jq 管道，省 token 优先；对方要 null 是因为其消费方需要稳定 schema——
  两种取舍各有道理，不盲从
- **"值走占位符"**：AOS 的 `--where` 是纯内存过滤，不拼接 SQL，无注入面，无需占位符

### 同时：文档与 Skill 对齐 v0.58.0

代码同步至 v0.58.0 后，把 v0.45 → v0.58 新增的能力补进 Skill 与使用说明，并修正一批陈旧描述：

- **`--where` 数值比较自相矛盾**：主 skill 与 `docs/guide.md` 曾在「query」段写"支持数值比较"、
  在「技术限制」段写"不支持数值比较"。以实现为准（`src/cli/shared.js` `parseWhere`）——**支持**四种形态：
  精确相等 / 模糊包含 / 数值比较 / **存在性（裸 key，v0.58.0 新增）**；全角逗号自动归一
- **服务端路由匹配池过时**：写作 `SERVER_API_ROUTE_TYPES 仅 go/python`，实为 `go / python / next-api / express`
  （v0.58.0 新增 express）
- **MCP 工具数**：主 skill 写"7 个能力"，实为 **12 个**（`panorama_matrix` + 4 个 `graph_*`）
- **对象类型计数**：主 skill 写"18 种"，代码本体域实为 19 种（全项目 39 种，含库/部署/服务/规划各域）
- **解析器数量**：主 skill 写"五个解析器"，v0.47 起已收敛为**统一注册表**（`analyzerRegistry.js`）下的 14 个分析器
- **`docs/guide.md` 的 Skills 表**：写"npm 包携带五个 SKILL.md"，实为 **12 个**
- **README 数据库审计数**：写"7 大审计"，实为 **9 大**（含实体边界 DDD 与代码↔数据库跨层审计）
- **context-builder README**：快照语义写"20+ 种对象、26 种链接"，实为 **39 种对象、58 种链接**
- **code-review 维度计数**：写"4 组共 11 个维度 + 50+ 规则"，实为 **5 组共 20 个维度 + 80+ 条规则**
  （原计数把"死代码 4 个"与"后端 5 个"排除在外）；已在 SKILL.md 写明计数口径
- **`docs/guide.md` 断链**：5 处相对链接用 `./` 指向仓库根（`./contrib/...` / `./CHANGELOG.md` / `./docs/adr/...`），
  在 `docs/` 目录下解析失败——已改为 `../`；另修复 `skills/nice-aos-context-builder/README.md` 的 `LICENSE` 相对路径
  与 `contrib/blueprint-ai-agent/README.md` 中指向作者本机 Windows 路径的死链

### 新增文档内容

- **主 skill**（`skills/nice-aos-skill/SKILL.md`）
  - 新增 `check` / `panorama` / `graph` 三节命令速查
  - 新增「前后端链路对账」节：恒等式 `sites = matched + unmatched + unexamined`、
    已覆盖的前端调用形态（Vue SFC / React / Flutter-Dart / Kotlin-Retrofit + 客户端封装层）与后端路由形态（Node / Go / Python / Next.js / Nuxt）
  - MCP 工具表补全至 12 项，并说明 withSnapshot 透明加载与 vocabulary envelope
  - 停止条件表新增「前端调用全落 unmatched 时先分清没匹配上 vs 没采集到」
- **`docs/guide.md`**
  - 新增「前后端链路对账（v0.58.0）」与「本体元模型与扩展点（v0.48.0 ~ v0.52.0）」两节
  - 新增 `check` 命令节，说明它与本体四级死代码的判定差异
  - Skills 表重写为 12 个 skill
  - Roadmap 节标注为历史存档，并指向 ADR 0011–0019
- **`docs/guide.md` / `README.md`**：`--where` 四形态、库表审计数、MCP 工具数等一并校正

### Skill 侧的跨域衔接

- `nice-aos-fullscan`：新增「跨域知识全景（v0.57.0）」节——`panorama` 覆盖单仓内跨域，
  `_architecture.py` 模板的价值收窄为**跨仓库**维度
- `nice-aos-database` / `nice-aos-deployment` / `nice-aos-service` / `nice-aos-planning`：
  各补一节说明本域快照如何进入 `panorama` 知识矩阵与 4 类跨域断链候选
- `nice-aos-deadcode`：新增 `check` 体检节，说明 `check deadcode`（入口可达性 BFS）与本体四级（零引用）
  两种判定的差异与取舍
- `nice-aos-code-review`：新增第 5 组维度「API Contract（前后端接口契约，API-01~05）」，
  同步 `spec/review-rules.md` 第 12 节，维度数 11 → 20、规则 50+ → 80+

## [0.58.0] — Vue 前端 API 调用点修复 + 前后端链路对账（ADR 0019 P0）

对标 asdm-aos 0.0.15「前端本体 + 前后端链路」盘点（ADR 0019）后落地 P0：修复一个**静默丢数据**缺陷，并补上让此类缺陷不可能再静默的对账机制。

### 修复：`.vue` 内的 API 调用点此前完全丢失

- **根因**：`vueAnalyzer` 本就把 `<script>` / `<script setup>` 块交给 `tsAnalyzer` 解析（imports / composables / stores / 导航调用全部继承），但构造 facts 时**从未透传 `sf.httpCalls`**。因此 Vue SFC 里的 `axios.get()` / `fetch()` / `request({url})` 对前后端链路完全不可见。
- **为什么是静默的**：不产出 `httpCalls` 就不进 `builderBackendRoutes` 7c-d 的匹配循环，连 `_meta.unmatchedFrontendCalls` 都不写，统计呈现「0 未命中 / 全匹配」的假象——Vue 项目的前后端链路实际等于不存在，但体检无任何告警。
- **修复**：`analyzeVueFile` 透出 `httpCalls`，行号按 script 块在 SFC 中的起始行校正回**真实文件行号**（`fileLine = scriptLine + (L - 1)`），使 `Route.frontendCalls` 的定位信息可直接跳转。
- 覆盖 Vue3 `<script setup>`、Vue2 Options API `methods`、template-only SFC（产出空数组而非抛错）。

### 新增：前后端链路对账，杜绝下一次「静默的 0」

`_meta.resolutionStats` 新增三项派生指标，建立恒等式 `sites = matched + unmatched + unexamined`：

| 字段 | 语义 |
|---|---|
| `frontendCallSitesCount` | 分析器采集到的前端 API 调用点总数（对账分母） |
| `frontendCallsAccounted` | 真正进入匹配并有结论（命中 / 未命中）的调用点数 |
| `frontendCallsUnexamined` | 采集到却从未进入匹配的调用点数，**正常恒为 0**；>0 即为静默丢失信号 |
| `frontendCallMatchRate` | 命中 / 采集总数，与 `importResolutionRate` 同风格，便于一眼看链路完整度 |

分母按全量 factsMap 统计（而非匹配器可见范围），因此**任何分析器新增 `httpCalls` 而匹配链路未覆盖该语言时，`unexamined` 会立刻大于 0**——把「漏了但看不出来」变成「一眼可见」。MCP `get_health` 与 serve `/api/stats` 透传 `resolutionStats`，无需额外改动即可消费。

### 修复：服务端路由声明被误判为「前端 API 调用」

以真实全栈仓（Node + Express 后端 + React 前端）作素材扫描时暴露：`router.get('/x', handler)` 与 `axios.get('/x')` 在 AST 上**完全同形**，初版把该仓 14 个后端路由全部记成「前端 API 调用」——`unmatchedFrontendCalls` 沦为噪声、`frontendCallMatchRate` 恒为 0，链路统计彻底失真。

- `SERVER_ROUTER_MODULE_RE` 识别 `express / fastify / koa / @hapi / restify / polka / connect / egg / @nestjs/*` 来源的 `Router()` 实例（含 `express.Router()` 与 `: Router` 类型标注），其 `.get/.post/.delete(...)` 判为**路由声明**并排除
- 素材仓复扫：误判 14 → 0

### 新增：API 客户端封装层解析（真实项目几乎都有一层）

裸调 `fetch` 的形态在真实前端里几乎不存在，通常是 `apiGet('/api/graph') → request(path, init) → fetch(path, init)`，整层 `src/api` 对链路不可见。

- **封装签名识别**：函数体把自身形参原样转发给 `fetch(...)` / `axios.get(...)` / `request({url, method})` 即登记为封装；支持**分层传递闭包**（定点迭代，`apiGet → request → fetch` 两层可解）
- **跨文件解析**：定义侧登记 `facts.httpWrappers`，调用侧登记 `facts.pendingHttpCalls`，由 builder 用导入图定性（`local → imported → 目标文件签名`）；定性失败的候选丢弃（那只是普通函数调用），故只有真调用点进分母
- **路径构建函数**：`const graphBase = (id) => \`/api/graph/${id}\`` 这类前缀工具会被识别，支持 `apiGet(\`${graphBase(id)}/stats\`)`（空 head 模板）与 `apiDelete(graphBase(id))`（实参是调用表达式）两种形态，动态段统一以 `${*}` 占位
- 素材仓复扫：识别到的前端调用点 **1 → 7**，与源码 7 处 API 调用精确对应

### 新增：Node 服务端路由抽取（Express / Fastify / Koa / Nest）

服务端 API 路由池此前只有 `go / python / next-api`——Node 全栈仓的后端路由完全不进快照，结果是前端 API 调用点 100% 落入 unmatched、`frontendCallMatchRate` 恒为 0。

- **路由声明抽出**：分析器已能识别 Router 实例（`SERVER_ROUTER_MODULE_RE`），其 `.get/.post/...` 此前是被排除掉的，现在改为抽出为 `facts.expressRoutes`，并生成 `routeType: 'express'` 的 Route 对象
- **挂载前缀跨文件拼接**：`app.use('/api/users', userRouter)` 拼出完整路径 `/api/users/:id`；`app.use(healthRoutes(cfg))` 视为根挂载。挂载点**无条件采集**——`app.ts` 这类"只 use 不定义 Router"的文件正是前缀信息的唯一来源
- **中间件过滤**：`helmet()` / `cors()` / `json()` 等不产生路由，不登记（否则给跨文件解析添噪声，且 facts 会进解析缓存）
- **路径不完整不生成 Route**：挂载点找不到的路由只计入 `_meta.resolutionStats.unmountedExpressRoutesCount`，宁可少也不生成半截路径造成错误匹配
- 素材仓（Node + Express + React）复扫：14 条路由全部带正确挂载前缀，前端调用点 **0/7 → 7/7 命中**

### 新增：Flutter / Android 前端 HTTP 客户端采集

与 Vue 同类的静默缺失：`.dart` / `.kt` 不产 `httpCalls`，且匹配循环显式跳过 `.dart`，两端叠加导致移动端链路整体不可见。

- **Dart / Flutter**：`http` / `dio` / `client` / `apiClient` 等客户端实例的 `.get/.post/...('/api/x')`，含 `Uri.parse('...')` 实参；限定接收者命名以避开 `Map.get` 之类同形调用
- **Kotlin / Android**：Retrofit 注解声明式路径（`@GET("api/users")`），要求注解后紧跟函数声明以避开 Dagger 同名注解；不带前导斜杠的路径自动补齐
- `.dart` 移出匹配循环的排除名单
- 新增 `test/flutterHttpCalls.test.mjs`（5 例，含端到端 Flutter↔Go 链路匹配与对账）

### CLI 查询增强

- **`--where` 存在性形态**（裸 key，`--where endpointInfo` 表示"有该字段"）：补齐四形态的第四种。判定口径对齐 asdm-aos `ontology/where.ts` 的 `isNonEmpty`——空数组 / 空串 / 空对象判为不存在，`0` 与 `false` 判为存在；非法片段（带空格、纯数字开头）仍静默跳过，避免写错的输入把结果滤空
- **`--where` 全角逗号归一**：中文输入法高频误输，`kind=page，name~User` 可直接用
- **`get_schema` 的链接词汇按本图可达性收敛**：新增 `vocabulary.linkTypesActive` / `reverseLinkTypesActive` 与 `_vocabularyStats.linkTypeActiveCount`。判定口径为**前缀可达性**（该 linkType 的源/目标前缀中只要有一种对象在本图存在即 active）——纯 Go 仓会看到 `passesProps` / `usesHook` 标 inactive，agent 不再浪费一轮去查本图不可能存在的链接类型。*「未做精确边计数」的取舍后被推翻：口径已升级为边事实（真实 `link()` 探测 + `_meta` 边计数），见「未发布」*

### 测试与文档

- 新增 `test/vueHttpCalls.test.mjs`（7 例）：事实层行号校正、Options API 封装实例形态、template-only 不报错、端到端 `.vue` 进 `Route.frontendCalls`、`.vue` ↔ `.js` 同路径回归对照、对账恒等式、纯前端仓不产生假「全匹配」
- 新增 `test/expressRoutes.test.mjs`（5 例）：路由声明抽出、`app.use` 挂载点采集（不含 Router 的文件也要采）、挂载前缀拼接 + 前后端命中、`:param` 通配匹配、未挂载路由计数可见而非生成错误 Route
- `test/whereClause.test.mjs`：更新裸 key 既有语义断言 + 新增存在性形态与全角逗号用例
- 新增 `docs/adr/0019-frontend-ontology-gap-vs-asdm-aos-0015.md`：对标 asdm-aos 0.0.15 的缺口盘点，以及基于 asdm-aos **真实源码**（53 个 TS 文件 + `AOS架构.md`）的补充盘点——A1 事实修正（`ApiEndpoint` / `apiToBackend` 由源码确证，且 `apiToBackend` confidence 仅 0.7）、A2 七项可借鉴（按 价值÷增重 排序）、A3 六项明确不借鉴（含 asdm 自身的两处缺陷：BFS 不限节点数、import 时写全局可变注册表）、A4 素材扫描暴露的缺陷清单

### 回归

新增测试 25 例（vueHttpCalls 7 + expressRoutes 5 + flutterHttpCalls 5 + whereClause 新增 8）。全量回归与 v0.57.0 基线在**同一 worktree 逐批次比对完全一致**（分析器批次 475/473/2、viewer/CLI 批次基线一致），既有失败均为环境问题（`better-sqlite3` 原生可选依赖未安装、子进程/git 依赖），与本次改动无关。

**素材仓端到端结果**（`asdm-aos-server`：Node + Express 后端 + React 图谱前端）：14 条 Express 路由全部带正确挂载前缀；7 个前端 API 调用点 **7/7 命中后端路由**，`frontendCallMatchRate = 1.0`，`frontendCallsUnexamined = 0`。

## [0.57.0] — ask 问题聚焦 + 油猴静态数据资源（DataResource）+ 跨域知识全景（panorama）

### ask 消费能力增强：问题聚焦上下文（`src/ontology/askFocus.js` 新模块）

- **从"固定骨架"升级为"骨架 + 问题检索"**：基础上下文（项目画像/架构分层/功能域/模块 Top 10/依赖/健康）之后，按问题关键词与意图从对象索引检索相关对象，最多 18 个对象 + 12 个数据资源，段首列出检索词与意图
- **中文检索**：整段 + 二元组切分并过滤虚词/量词（的/了/个/哪/项…），避免"个项/的架"这类无区分度 token；拉丁词支持 camelCase 拆分与显式对象 id（file:/comp:/dr: 等）
- **意图路由**：功能域/模块/路由/组件/Hook/Store/Service/接口/方法/脚本/数据/依赖/死代码/循环/风险/架构 16 类意图；无对象名命中时仍按意图带出代表对象
- **DataResource 感知**：问"缓存键/数据资源/存储/IndexedDB"时优先带出静态数据资源并附"静态数据资源（数据索引）"概览（按 storage 汇总 + 注册表键优先）
- **三条链路同源**：`ask`、`GET /api/ask/context`、`POST /api/ask` 共用同一构建器；无命中时返回 null 保持旧行为；`--json` 输出 `focus: {matched, selected, dataResourceCount, intents, durationMs}`
- **有界**：单一类型最多 5 个、总对象 18 个、单段 < 6KB；`--graph-only` 不追加聚焦段

### 油猴静态数据资源解析：DataResource（第 39 种对象 / 第 58 种链接）

- **`src/analyzers/userScriptDataResources.js` 新模块**：缓存注册表 `PLUGIN_CACHE_REGISTRY` 逐键展开（idb db/store、prefix、envelope、desc、digest、domain、per-key store）；localStorage/sessionStorage/GM_getValue/GM_setValue/GM_deleteValue/GM_listValues；IndexedDB open/transaction/objectStore/store.get/put/delete；内联数据集/映射表/配置表/图标表；JSON.parse 字面量与 `document.getElementById('viewer-data').textContent`；@resource；unsafeWindow/window 数据全局
- **常量折叠**：`const KEY = 'x'`、`CONFIG.SK.SETTINGS`、模板前缀、字符串拼接 → 静态键名；无法静态确定时归入动态键
- **对象与链接**：DataResource（`dr:`，category=DataAsset，L0）含 storage/key/prefix/dbName/storeName/envelope/registry/domain/access/callCount/fns/entryCount/fieldNames/bytes；新增 `accessesDataResource`（us: ↔ dr:）与 `usesDataResource`（fn: ↔ dr:）双向链接；`contains` 与 `belongsTo` 同步扩展
- **全链路落地**：单文件 analyzeFile / refreshRepo 快照 / `_meta.objectCounts` / SQLite 镜像 / Neo4j 图 sink / MCP get_schema / query 命令 / Markdown 导出 / 蓝图脚本页（数据资源卡片 + 详情表）/ ask 聚焦
- **蓝图客户端**：脚本蓝图卡片与业务数据图新增数据资源计数/清单；viewer-data 内嵌 `dataResource`（原始对象，上限 2000）与脚本级 `dataResources` 精简清单，供蓝图 AI 油猴脚本消费

### 蓝图 AI 油猴脚本 v1.1.0 / v1.1.1（`contrib/project-blueprint-ai-agent`）

- **声明式 BLUEPRINT_DATA_REGISTRY**：页面类型检测从 5 条 if 链改为注册表驱动；任意 `<script id="*-viewer-data">` 自动归入 generic 适配器，新蓝图零改代码接入
- **通用适配器 `_buildGenericIndex`**：不预设业务字段，把顶层数组（含 dataMap/model/data 包裹层）按小写键建索引，配 generic agent
- **`listDataResources` / `readDataResource` 只读工具**：读取 code 蓝图 DataResource 元数据 + 扫描页面所有 `<script id>` / `application/json` 内嵌 JSON 数据块（bytes/rootKeys/arrays），支持点路径下钻；只读、摘要裁剪、结构漂移降级，参考 steam-ai-agent 的 `PLUGIN_CACHE_REGISTRY` / `readPluginCache`
- **页面类型/数据块发现**：DataSource 上下文附带 `embeddedData`，跨页共享缓存同样带上；代码蓝图 queryObjects 支持 `dataresource`
- **v1.1.1 回补 snapshot.json 数据源**：设置新增「快照地址」，页面无内嵌数据时用 `GM_xmlhttpRequest` 拉取 snapshot.json 建索引；回退链为 页面解析 → snapshot URL → 共享缓存 → 报错；architecture/summary 仍走 DOM

### 旧版蓝图脚本 v1.1.1（`contrib/blueprint-ai-agent`，冻结）

- **标记 Deprecated**：README / 脚本头部明确后续不再更新，新用户迁移 `project-blueprint-ai-agent`
- **最小 DataResource 兼容**：`DATA_TYPES` 增加 `dataresource`，新增 `listDataResources` / `readDataResource` 两个只读工具，`queryObjects` / `getStats` / `getNodeDetails` / `listLinks` 可直接消费 v0.57 快照；不做其他功能对齐

### 测试与文档

- 新增 `test/dataResources.test.mjs`：注册表逐键展开 / 常量路径解析 / GM 读写合并 / IDB 表键 / 内联数据集 / @resource / 嵌入 JSON / 单文件对象与双向链接 / ask 聚焦（数据类命中、无关问题 null、中文虚词二元组过滤）
- 更新 `test/blueprintEngine` / `hubContracts` / `linkDefs` / `toolRegistry` 的 39 对象 / 58 链接 / 8 范畴断言
- 文档：README、docs/guide.md（对象/链接/ask 聚焦/脚本蓝图/油猴数据资源命令）、nice-aos / nice-aos-userscript skill、contrib v2 README
- ADR 0018：静态数据资源与 ask 问题聚焦设计

### 跨域知识全景（panorama）：五类领域快照 → 知识全景矩阵

代码 / 数据库 / 部署 / 后端服务 / 产品规划五类快照此前各自独立成文件、互不可见，跨域问题只能靠人脑拼。新增 `panorama` 子命令把它们融合成一张**知识全景矩阵**：

- **行 = 知识类别（8）**：代码 / 接口 / 数据 / 需求与业务 / 系统规格 / 部署与运维 / 测试 / 质量治理
- **列 = 生命周期阶段（8）**：基线 / 需求 / 设计 / 开发 / 测试 / 发布 / 运行 / 演进
- **每格** = 证据条数 + 置信度 + 取证渠道明细（`facets`）+ 证据样本

**格子状态四分（刻意区分「没有」「不知道」与「没建模」）**：

| 状态 | 标记 | 含义 |
|------|------|------|
| `covered` | `●N` | 取证渠道可用且取到 N 条证据 |
| `latent` | `○` | 渠道可用但无证据——知识仍处潜态，需人工挖掘 |
| `no-channel` | `∅` | 该类别在此阶段未定义取证渠道，且该类别的数据源都在——是**分析没建模**，不是数据没有 |
| `absent` | `·` | 该格声明的渠道因数据源缺失而全部不可用——不是「没有知识」，是「没看」 |

`no-channel` 与 `absent` 必须分开：混为一谈会让「数据源明明在、只是没定渠道」的格子被误标成「无数据源」，并使为这些格子手写的 `GAP_SUGGESTIONS` 变成永远渲染不到的死配置。现由 `test/panorama.test.mjs` 的结构性断言守住。

**置信度** = 可评估渠道占比 × (0.5 + 0.5 × 证据密度)，密度 = 该格条数相对本行最强阶段的比值。

**跨域断链检测（silos）**——4 类候选，启发式匹配一律标为 candidate 并附相似度（宁漏报不误报）：

- `db-orphan-table`：库表在代码实体（Interface/Class/Store/Service）中无对应项（≥5 列按高危）
- `code-unmatched-call`：前端调用未匹配到本仓服务端路由（也可能是外部 API）
- `deploy-unlinked-service`：部署服务名与代码模块/功能域无命名对应
- `planning-unlinked-feature`：规划特性与代码功能域/路由无命名对应

匹配逻辑：名称归一（剥离 `-service` / `_api` / `-gateway` 等基础设施后缀）+ 归一后包含关系 + 词元 Jaccard 相似度。候选池的归一名与词元集合**预计算一次**再进入两两比较循环（否则 400 表 × 1200 实体就是 48 万次重复归一）。命中扫描上限时，未评估条数写入 `summary.siloScan` 并在报告中显式提示——静默截断会让「已全覆盖」变成假象。

**新增文件**：`src/panorama/panoramaModel.js`（维度定义、取证渠道表、矩阵构建、置信度、缺口与断链检测、过滤与入参校验；纯函数无 IO）/ `panoramaViewer.js`（文本渲染，CJK 显示宽度对齐）/ `panoramaSources.js`（路径解析：显式 > 代码快照同目录 > 领域 kit 默认，三入口共用）/ `src/cli/commands/panorama.js`（CLI 子命令）。

**消费入口**：CLI `nice-aos panorama [--json] [--category] [--stage] [--section]`；HTTP `GET /api/panorama`（登记进 `serveOpenApi.ENDPOINTS`）；MCP 新增 `panorama_matrix` 工具（工具数 11 → 12）。三入口的入参校验统一：`category` / `stage` 取值非法一律报错（CLI exit 1 / HTTP `400 E_PANORAMA_FILTER_INVALID` / MCP `{ok:false,error}`），**不静默返回空矩阵**——「这个类别 0 格」与「类别名写错了」对调用方是完全相反的两种含义。

**修复**：

- 领域快照 kit 解析链不跟随全局 `--snapshot-dir`，与全局帮助承诺不一致：收敛到 `panoramaSources` 统一解析
- `include_silos=false` 只清 `silos` 而 `summary` 留旧值，消费方同时读到「0 条断链」与「1 条断链」：选项下沉到 `filterPanorama`，summary 一并重算
- `/api/panorama` 每请求重读重解析四份领域快照并全量重算：改为按五份文件的 `mtime+size` 指纹缓存，任一份变化即重建
- MCP 工具读了 `sources` 却不回传路径，调用方无法区分「路径写错」与「无数据源」
- `/api/panorama` 漏判快照损坏（`snapState === 'gone'`），会返回 200 + 全 absent 矩阵
- CLI 与 `panoramaSources` 各写一份快照读取且行为不一致：统一到 `readOptionalSnapshot` 并加 `strict`（CLI fail-fast，serve/MCP 降级）
- `_meta` 中应为数组的字段结构异常时退回空数组；`--limit` 非正数/非数字回落默认 20

**性能**：表↔代码实体匹配（全景最贵一步）由两处各算一次改为全局算一次（`ctx.tableMatch`）；断链匹配的名称归一与词元切分改为候选池预计算。

**测试**：`test/panorama.test.mjs`（36 例：四态语义、置信度单调性、缺口只收 latent/no-channel、`GAP_SUGGESTIONS` 无死配置的结构性断言、4 类断链、过滤与入参校验、`include_silos` 一致性、扫描上限、`ctx.tableMatch` 复用、脏数据容错、快照读取 strict/宽松两态、路径优先级、CJK 宽度）；`test/panoramaCli.test.mjs`（17 例，含 serve 侧 400 校验 / 500 损坏 / 缓存命中与领域快照新增后失效重建）；`toolRegistry` 断言同步到 12 工具。

## [0.56.0] — 图谱可视化切片（T56-1/2/3）

### storage 层：graphSlice / graphExpand（`src/storage/neo4jSnapshot.js`）

- **`graphSlice({snapshotId, label, limit})`**：分层子图切片。不带 label 返回类型骨架（各类型节点数 + 类型间边聚合 top-200）；带 label 返回该类型 top-N 节点（按度数排序，hub 优先）+ 切片内部边（端点都在切片内，防 hub 边爆炸）。limit 上限 500
- **`graphExpand({snapshotId, id, direction, relTypes, depth, limit})`**：邻域展开。以指定节点为中心沿边展开 1–3 跳（direction: out/in/both），relTypes 白名单校验（UPPER_SNAKE，防 Cypher 注入），返回节点+边局部子图；中心不存在返回 `E_TARGET_NOT_FOUND`
- **Neo4j LIMIT 参数化陷阱修复**：driver 把 JS number 当 float 传输，`LIMIT $param` 收到 `100.0` 报错——清洗后的整数直接插值进 Cypher（与 graphImpact 的 `*1..${maxDepth}` 同策略）

### serve 图端点（`/api/graph/stats` / `/api/graph/slice` / `/api/graph/expand`）

- 三个 GET 端点接入既有鉴权链（`/api/*` 默认 read 角色）与限流器；结果 JSON 原样透传，错误语义在 body（`{ok:false, code, hint}`，与 MCP graph_* 工具同契约），HTTP 恒 200
- 未配置 `NICE_AOS_NEO4J_URI` 时返回 `E_NEO4J_CONFIG_MISSING` + 修复提示，不抛 5xx
- 每次调用后 `closeDriver()`——与 CLI 共用驱动管理，连接池状态不跨请求泄漏
- ENDPOINTS 清单同步（`/api/status` 与 `/openapi.json` 自动带出）

### 蓝图 HTML「Neo4j 图谱」面板（viewer 第 13 个 Tab）

- **HTML 体积零增长**：图数据运行时 fetch serve 端点，不内嵌快照（1.2 万节点的 steam:default 内嵌会撑爆蓝图）
- 三级浏览：类型骨架（13 类型卡片 + 类型间连接表）→ 类型切片（SVG 同心圆布局，半径 ∝ 度数）→ 点击节点展开邻域（新节点紫色并入视图）
- 离线降级：file:// 直开或 serve 未起时显示配置指引（含 code/hint），不报红
- 切到 Tab 才懒加载（`gpInit` 钩子），不浪费离线请求
- 真实浏览器验证：骨架 13 卡片 → SourceFile 切片 165 节点/668 边 → 节点点击 +2 邻域节点

### 版本号

- `package.json` 0.52.0 → 0.56.0

## [0.55.0] — 图谱 MCP 工具化（T55-1/2/3/4）

### MCP graph_* 工具族（7 → 11 个工具，`src/ontology/toolRegistry.js`）

- **`graph_impact`**：影响面分析（复用 storage 层 `graphImpact`，target 不存在返回 `E_TARGET_NOT_FOUND`）
- **`graph_deps`**：模块耦合矩阵（复用 `moduleCoupling`，支持 module 聚焦与 min_weight）
- **`graph_query`**：只读 Cypher 直查（自动 LIMIT 防御；语法错误返回 `E_CYPHER_EXECUTION`）
- **`graph_status`**：连接健康探测 + 快照统计；连接失败时错误结构（code/message/hint）提升到顶层，调用方无需下钻 connection 就能判定失败
- 每个 handler `try/finally closeDriver()`——杜绝 neo4j-driver 连接池挂死 CLI/进程
- 降级语义：未配置 Neo4j 时返回 `{ok:false, code:'E_NEO4J_CONFIG_MISSING', hint}`，不抛异常

### 统一错误码（`src/common/errors.js` 新模块）

- `ERROR_CODES` 8 个常量 + `aosError(code, message?, hint?)` 工厂 + `HINTS`（setNeo4jUri / installDriver / checkConnection / pushFirst）
- `neo4jSnapshot.js` 全部 17 处失败路径迁移到结构化错误；`reason` 字符串保留（老消费方兼容）

### 测试

- `test/mcp.test.mjs`：7 → 11 工具断言 + graph_* 降级路径 4 个单测（无 Neo4j 配置 → `E_NEO4J_CONFIG_MISSING`）
- `test/mcpCli.test.mjs`：stderr 工具数断言 7 → 11
- `test/toolRegistry.test.mjs`：工具清单快照 7 → 11
- `src/cli/commands/mcp.js`：就绪日志工具数动态化（原硬编码 '7'）

### 修复

- **import 别名陷阱**：toolRegistry 直接 import `neo4jSnapshot.js` 模块，但 `closeNeo4jDriver`/`runNeo4jQuery` 只是 `storage/index.js` barrel 的 re-export 别名 → 改为模块内真实名 + `as` 别名（`closeDriver as closeNeo4jDriver`、`runQuery as runNeo4jQuery`）

## [Unreleased]

### Neo4j 远程图数据库 sink（`nice-aos graph`，可选 sink）

把本体图谱再写一份到 Neo4j，便于多仓库聚合查询、AI agent 通过 Cypher 直接消费、可视化面板共享。默认关闭，按需开启：

- **新增 `nice-aos graph` 子命令**：`push` / `status` / `query` / `delete`，单条命令触发推送或回填
- **自动双写（A）**：`action refreshRepo` 完成后静默推送到 Neo4j —— `--neo4j-auto` flag 或 `NICE_AOS_NEO4J_AUTO=1` 开启；`--no-neo4j-auto` 临时关闭；`--neo4j-no-links` 仅写节点
- **环境变量覆盖**：`NICE_AOS_NEO4J_URI` / `_USER` / `_PASSWORD` / `_DATABASE` / `_AUTO`
- **CLI flag 覆盖**：`--neo4j-uri` / `--neo4j-user` / `--neo4j-password` / `--neo4j-database`（flag > 环境变量）
- **图模型**：`(:AosSnapshot {snapshot_id, kind, project_name, ...})` 锚点 + `(:AosEntity {snapshot_id, id, type, props_json, path?, name?, ...})` 节点（按类型附加 `:Method` / `:SourceFile` / `:Module` ... label）+ `[:CONTAINS_OBJECT]` / `[:CALLS]` / `[:IMPORTS]` / `[:CONTAINS]` / `[:BELONGS_TO]` ... 关系（27 个具名 linkImpl 落地）
- **事务一致**：`session.executeWrite()` 一次性清旧 + 写锚点 + 写节点 + 写关系；幂等可重入
- **无需 APOC**：节点 label 按 type 分桶用 `SET n:\`TypeName\`` 静态模板（社区版 Neo4j 5.x 验证通过）
- **高频字段提升**：`path` / `name` / `archLayer` / `domainId` / `moduleId` 直接落到节点顶级属性，Cypher 直查不必 `json_extract`
- **失败降级**：未安装 `neo4j-driver` / 未配 URI / 网络/认证失败时，`graph` 子命令与 `--neo4j-auto` 静默降级为 no-op，**不影响 JSON / SQLite 主数据源**
- **依赖**：`neo4j-driver@^5.20.0` 加为 `optionalDependencies`

基准：161 文件 nice-aos 自扫 → 1157 节点 / 1975 关系，约 5–6s 全量重写。

### 入口收束（行为保持）

- README 收到一屏，场景 / 本体 / CLI / 解析说明移到 `docs/guide.md`
- `check deadcode|duplicates|io` 作为体检入口；顶层同名命令仍可用，只是不再出现在顶层帮助
- `ask --help` 选项表只留 `--agent`、`--json`、`--since`、`--save`；其余旗标仍可用，列在帮助末尾
- 五份蓝图 HTML 共用 `htmlShell`，渲染结果与拆分前一致
- 蓝图客户端脚本按 Tab 拆到 `src/ontology/viewerClient/`，拼回的脚本与拆分前一致
- `package.json` 含 `bin` 的 Node CLI，以及无路由的 cobra 项目，架构风格记为「命令行工具」
- PHP 分析器的行号计算改走 `textUtils`

## [0.52.0] - 2026-10-16

### MCP stdio server 接 withSnapshot 透明加载（候选三项收官）

候选三项的最后一项落地：在远端 v0.46 已用 `@modelcontextprotocol/sdk` 实现 MCP server 基础上，做两处增强：
1. **`withSnapshot` 透明加载**（v0.48.0 思路）：缺快照自动 build 不再 fail-fast
2. **`createMcpHandlers(snap)` 工厂函数抽取**：handler 逻辑可独立单测

详见 [ADR 0017](./docs/adr/0017-mcp-stdio-withsnapshot.md)。

#### MCP server 透明加载改造（v0.52.0）

旧 `src/cli/commands/mcp.js`（157 行 v0.46 实现）快照缺失时直接 `process.exit(1)`。改造后：

```js
// v0.52.0：withSnapshot 透明加载（v0.48.0 思路）
const { value: snap, rebuilt } = await withSnapshot(root, s => s, {
  silent: true,
  onRebuild: () => { rebuilt = true; },
});
if (rebuilt) process.stderr.write('[nice-aos mcp] 快照缺失，已自动 build（v0.48.0 透明加载）\n');
```

**对照 asdm `withGraph(gid, fn)` 设计哲学** —— "查询自动 load，无需先手动 load"。agent 启动 `nice-aos mcp` 不再需要"先跑 `action refreshRepo`"前提。

**JSON 损坏仍报错退出**：`withSnapshot` 透传非 NO_SNAPSHOT 错误（v0.48.0 决策），损坏 JSON → SyntaxError → CLI 退出。保护性 fail-fast 保留。

#### `createMcpHandlers(snap)` 工厂函数（v0.52.0 可维护性提升）

```js
export function createMcpHandlers(snap) {
  const registry = createToolRegistry({ snap });
  function listTools() { return { tools: registry.list().map(/* 形态映射 */) }; }
  async function callTool(request) { /* callTool */ }
  return { listTools, callTool };
}
```

**好处**：
- `listTools` / `callTool` 是纯函数，独立于 SDK 不依赖 stdio
- 8 个单测覆盖完整 handler 行为（无需 spawn 子进程）
- 协议层（`server.setRequestHandler`）与业务层（`createMcpHandlers`）解耦，未来换 HTTP transport 直接复用

#### 协议常量集中

```js
export const MCP_PROTOCOL_VERSION = '2024-11-05';  // SDK 期望
export const MCP_SERVER_NAME = 'nice-aos';
```

#### 新增测试（+8 测试，0 回归；-1 测试废除）

- `test/mcp.test.mjs` 新增 **8** 测试：
  - createMcpHandlers 返回双函数
  - listTools 返回 7 个 tool（name/description/inputSchema 字段齐全）
  - callTool 缺 name / 缺 params → isError=true + 错误 JSON 文本
  - callTool 合法 get_stats → isError=false + 业务结果 OK
  - callTool 未知 tool → isError=true（toolRegistry.call 兜底）
  - callTool 缺 arguments → 按 `{}` 处理（不报错）
  - MCP 常量导出测试稳定性

- `test/mcpCli.test.mjs` 更新：
  - 废除："快照不存在时报错并退出（fail-fast）" —— 行为故意变更（透明加载）
  - 更新："快照 JSON 损坏时报错并退出" 错误信匹配 → 现在报 "快照加载/构建失败"
  - 现有 "合法快照启动" 测试 仍通过

#### 已锁定的 4 个设计决策（详见 ADR 0017）

- **D1** `createMcpHandlers(snap)` 工厂函数（纯函数，与 SDK 解耦）
- **D2** withSnapshot 透明加载（复用 v0.48.0 设计，保留 JSON 损坏的 fail-fast）
- **D3** 保持 SDK + stdio transport（零新依赖；HTTP 留 v0.53+）
- **D4** 协议常量导出（`MCP_PROTOCOL_VERSION` / `MCP_SERVER_NAME`）

#### 不进入此 ADR（明确留给后续）

- ❌ HTTP MCP transport（v0.53+ 评估：bearer auth + Streamable HTTP SDK 复杂度）
- ❌ Multi-graph MCP server（单 server 启动场景已覆盖）
- ❌ converter 钩子接入 MCP request lifecycle（hook 化收益小）

#### 后续候选（v0.53+）

| 主题 | 内容 |
|------|------|
| MCP HTTP transport | Streamable HTTP SDK + bearer auth |
| linkImpls 27 函数 → lookup 表 | blueprint.js linkImpls 收敛为表 + hooks（converter 已留接口） |
| expandLinked 真实接入 | linkMeta.js 接到 converter.expandLinked（当前空实现） |

---

## [0.51.0] - 2026-10-15

### ontology converter 钩子下放（借鉴分析 §2-B 落地）

借鉴 asdm-aos-server `GraphConverter`（`asdm-aos-server/src/modules/graph/converter/aos-gitnexus/index.ts`）的"通用遍历与领域语义分离"思路，nice-aos 落地 3 个钩子的轻量版（保持零依赖、零行为变化）。

详见 [ADR 0016](./docs/adr/0016-converter-hooks.md) + 借鉴分析报告 `.nice-aos/analysis/asdm-aos-borrow-analysis.md` §3（本地分析产物，未入库）。

#### 新增 `src/ontology/converter.js`

单一文件承载 `niceAosConverter` 默认实现 + 注册接口：

- **钩子 1 `resolveLink(linkType)`**：正向 → `{base: 自身, direction: 'out'}`；反向别名 → `{base: forward, direction: 'in'}`；未知 → `null`
- **钩子 2 `expandLinked(ctx)`**：默认空实现（接口留位，未来多态扩展用）
- **钩子 3 `linkVocabulary()`**：返回 `{linkTypePairs, reverseLinkTypes}` 派生数据
- 注册表：`getConverter()` / `registerConverter(c, {default: true})` / `__resetConverterForTest()`

#### wire 一处：toolRegistry.get_schema.vocabulary

`get_schema.vocabulary.linkTypePairs` 与 `reverseLinkTypes` 字段从 `converter.linkVocabulary()` 派生（替代 v0.50.0 的直接 `LINK_TYPE_PAIRS` 导入）。行为对调用方**完全透明**：同样的字段、同样的数据形态。

**Viewer 端不动**：viewerModel.js 的 19 对 UI 词汇（`passesProps → propsFrom` 等不在 linkDefs 内的展示性反向名）保留，与 API 端 linkDefs 17 对形成 API/UI 双视图的合理分工。

#### 不动清单（保持轻量）

| 文件 | 是否改 | 原因 |
|------|--------|------|
| `blueprint.js` 27 个具名 linkImpls | ❌ | 后续 round 时机成熟再迁移；本 ADR 只是接口层 |
| `linkMeta.js` | ❌ | 工作良好；`expandLinked` 钩子留位待接入 |
| `viewerModel.js` | ❌ | UI 词汇独立语义（19 对 vs 17 对）合理 |
| `linkDefs.js` | ❌ | v0.50.0 稳定，被 converter 复用 |

#### 新增测试（+11 测试，0 回归）

- `test/converter.test.mjs` 新增 **11** 测试：
  - niceAosConverter 三钩子形态
  - resolveLink 正向 / 反向 / 未知（含 `''`/`null`/`undefined`）
  - expandLinked 默认空（无副作用，纯函数验证）
  - linkVocabulary 派生 linkTypePairs + reverseLinkTypes
  - getConverter 单例（两次调用同一实例）
  - registerConverter 默认不替换 / `{default: true}` 替换 / 拒绝缺钩子
  - hook 调用无副作用（不修改 ctx）

#### 已锁定的 4 个设计决策（详见 ADR 0016）

- **D1 converter 抽象层 3 钩子**：与 asdm 同构，但默认 singleton 实现（不引入多 converter 并存市场）
- **D2 wire 一处**：只换 toolRegistry.get_schema.vocabulary；不动 viewerModel / blueprint / linkMeta
- **D3 注册表接口保留**：用作未来多 converter 扩展口子（v0.52+ 评估）
- **D4 不动现有架构**：避免大爆炸式 refactor 的回归风险

#### 后续候选（v0.52+）

| 主题 | 内容 |
|------|------|
| **v0.52.0 MCP server 接入** | toolRegistry.call() 接 MCP protocol / withSnapshot 包装终态化（需 user 决策 transport） |
| linkImpls 27 函数 → lookup 表 | blueprint.js linkImpls 收敛 + converter 钩子接入（当前接口已留位） |
| expandLinked 真实接入 | linkMeta.js 接到 converter.expandLinked 钩子（接口已留位） |

---

## [0.50.0] - 2026-10-15

### linkDefs 元数据层（借鉴分析 §2-A 落地）

将 asdm-aos-server 借鉴分析报告中"LinkDef 数据驱动"思路落地——抽取 nice-aos 56 个 linkType 的**元数据单一事实源**，不改 linkImpls 行为（最安全的渐进式做法）。

详见 [ADR 0015](./docs/adr/0015-linkDefs-metadata-layer.md) + 借鉴分析报告 `.nice-aos/analysis/asdm-aos-borrow-analysis.md` §2-A（本地分析产物，未入库）。

#### 新增 `src/ontology/linkDefs.js`

nice-aos 56 个 linkType 的**元数据单一事实源**（name / reverse / sourcePrefixes / targetPrefixes / description / category / hasImpl）：

- 56 个 `LINK_DEFS` 数组（27 个有 blueprint.linkImpls 具名实现 + 29 个 shell/CMake/Nix/ROS/crossLang 等待实现）
- `LINK_TYPE_PAIRS`（17 个正反向对）、`REVERSE_OF` / `FORWARD_OF` / `REVERSE_LINK_TYPES` 派生
- `LINK_DEFS_BY_CATEGORY`：hierarchy / reference / derive 三类分组
- 工具函数：`getLinkDef()` / `acceptsSource()` / `hasLinkImpl()`
- fail-fast 一致性校验：name 唯一、reverse 互指、sourcePrefixes 非空、双声明防护

#### `get_schema.vocabulary` 切换到 linkDefs 派生

`v0.48.0` 在 `toolRegistry.js:get_schema` 手写 11 条 `LINK_TYPE_PAIRS` 常量移除，改为从 `linkDefs.LINK_TYPE_PAIRS` 派生。`viewerModel.blueprint.vocabulary.linkTypePairs` 在 v0.51+ 替换（保持单点改动）。

#### 质量护栏（fail-fast + 测试同步）

- **fail-fast**：模块加载时执行 `_validateLinkDefs(defs)`，数据错误立即抛错（不静默运行）
- **测试同步**：新增 `test/linkDefs.test.mjs`（11 测试）断言 `LINK_TYPE_NAMES` 与 `blueprint.js LINK_TYPES` 严格同步（56 ↔ 56）
- **行为零变化**：blueprint.js 27 个具名 linkImpls 函数原样运行；get_schema 字段向后兼容（旧工具入参不变）

#### 新增测试（+11 测试，0 回归）

- `test/linkDefs.test.mjs` 新增 **11** 测试：
  - name 集合唯一 / reverse 互指 / sourcePrefixes 非空
  - 与 blueprint.LINK_TYPES 完全同步（56 ↔ 56）
  - `REVERSE_OF` / `FORWARD_OF` 对称性
  - `getLinkDef` / `acceptsSource` / `hasLinkImpl` 行为
  - `LINK_DEFS_BY_CATEGORY` 完整性

#### 已锁定的 5 个设计决策（详见 ADR 0015）

- **D1 linkDefs 是元数据单一事实源**：linkType 描述性信息集中管理
- **D2 blueprint.js LINK_TYPES 仍是顺序/行为源**：避免顺序敏感的回归；linkDefs 测试断言同步
- **D3 vocabulary 从内联常量切到 linkDefs 派生**：消除 v0.48.0 留下的 11 条硬编码
- **D4 reverse 只在 forward def 上声明**：防双声明的 fail-fast 规则
- **D5 数据建模 5 原则**：sourcePrefixes 必填 / targetPrefixes 仅作 hint / category 三档

#### 不进入此 ADR（明确留给后续）

- ❌ linkImpls 27 个具名函数硬编码（仍在 blueprint.js）— v0.51+ converter 钩子下放阶段收敛
- ❌ viewerModel.js `LINK_TYPE_PAIRS` 硬编码（待 v0.51 同步切换）

#### 后续候选（v0.51+）

| 主题 | 内容 |
|------|------|
| **v0.51.0 converter 钩子下放** | linkImpls 27 个函数 → lookup 表 + `resolveLink` / `virtualEntity` / `expandLinked` / `linkVocabulary` 4 钩子（本 ADR 是前置） |
| viewerModel 同步 | `viewerModel.LINK_TYPE_PAIRS` 改 `import linkDefs.LINK_TYPE_PAIRS`（消除 v0.48.0 的硬编码残留） |

---

## [0.48.0] - 2026-10-15

### asdm-aos-server 借鉴 round 3：withSnapshot 透明加载 + vocabulary envelope

继 ADR 0012 (查询投影/RPC/SKILL契约)、ADR 0013 (RPC normalization/factsCache/refIntegrity) 之后，asdm-aos-server 借鉴的第三轮。本轮聚焦 **agent 友好度最后一公里**——独立、互不依赖、可单点回滚。

详见 [ADR 0014](./docs/adr/0014-asdm-aos-withsnapshot-vocabulary-envelope.md) + 借鉴分析报告 `.nice-aos/analysis/asdm-aos-borrow-analysis.md`（本地分析产物，未入库）。

#### 新增 `src/ontology/withSnapshot.js`

借鉴 asdm-aos-server 的 `withGraph(gid, fn)`（`mcp.server.ts:53-56`）的"透明按需加载"思路：

- `withSnapshot(projectRoot, fn, opts?)` — 快照存在时直接 `fn(snap)`；缺失或 `forceRefresh` 时先 `buildOntologyData` 再 `fn(snap)`。返回 `{value, rebuilt}` 便于观测
- `ensureSnapshot(projectRoot, opts?)` — 仅触发保证，不消费 fn
- 默认 `silent: true`（与 [ADR 0001](./docs/adr/0001-asdm-aos-borrowed-capabilities.md) 决策 4 一致）
- **透传非 `NO_SNAPSHOT` 错误**（损坏 JSON / IO 错误必须向上抛，不静默重 build）

#### `get_schema` 扩展：vocabulary 块（v0.48.0）

借鉴 asdm-aos-server `get_vocabulary`(`ontology.service.ts:176-196`) 的"实际出现 vs 蓝图声明"二层信息，但**走 cost-static 路径**——不调用 `createBlueprint`，让 `get_schema` 在大快照上也轻量。数据结构与 `viewerModel.blueprint.vocabulary` 共享形态，便于 agent 与 viewer 共用同一词汇源：

```js
{
  // ...原有字段（向后兼容）
  vocabulary: {
    objectTypes: [...],        // 按 count 倒序、含 active 标记
    linkTypes: [...],
    reverseLinkTypes: [...],    // 由 LINK_TYPE_PAIRS 推导
    linkTypePairs: { ... },     // 正反向对（如 contains → containedBy）
    actions: [...],
  },
  _vocabularyStats: {
    objectTypeActiveCount,
    objectTypeDeclaredCount,
    linkTypeDeclaredCount,
    reverseLinkTypeCount,
    generatedAt,
  },
}
```

#### 错误信 envelope 扩展：`_meta.suggestions`（v0.48.0）

借鉴 asdm-aos `AppError('ONT_UNKNOWN_LINK_TYPE', unknownLinkTypeMessage(...))` 模式，nice-aos 走返回值 envelope（保持错误约定）：

`traverse_links` 与 `query_objects` 遇到未知值时返回：

```js
{
  ok: false,
  error: '未知链接类型: rendesr',
  validValues: [...LINK_TYPES, 'links', 'all'],
  _meta: {
    epistemic: 'lower-bound',
    suggestions: [
      { value: 'renders', score: 0.286 },
      // ...（编辑距离打分）
    ],
  },
}
```

候选打分与 `findSimilarIds` 同算法（完全 > 前缀 > 子串 > Levenshtein），提取为 `findSimilarStrings(strings[], query, k)` 复用。

#### 新增测试（+10 测试，0 回归）

- `test/with-snapshot.test.mjs` 新增 **6** 测试：参数校验 / 损坏 JSON 透传 / 命中缓存路径不构建 / `ensureSnapshot` 存在 / 默认 `silent=true` 契约
- `test/toolRegistry.test.mjs` 25 → **29** 测试，新增 4 条：
  - `get_schema` 返回 vocabulary 块 + `_vocabularyStats`
  - `query_objects` 未知 type `_meta.suggestions`
  - `traverse_links` 未知 linkType `_meta.suggestions` + `validValues`
  - `traverse_links` "links" / "all" 是合法 linkType（不被误拒）

#### 已锁定的 3 个设计决策（详见 ADR 0014）

- **D1 withSnapshot 透明包装**：与 asdm `withGraph(gid, fn)` 同构，对应 nice-aos 本地 `loadSnapshot` + `buildOntologyData`；保留接口给未来 MCP server 接入
- **D2 vocabulary 与 viewer 共享形态**：cost-static 路径（不调 `createBlueprint`），让 `get_schema` 在大快照上仍轻量
- **D3 错误信 suggestions**：`_meta.ambiguity` 已有机制扩展（v0.35）；`_meta.suggestions` 是"未知值"场景的同类做法

#### 不借鉴（保持零依赖 + 单一可推理）

- ❌ asdm 的 zod / Express / Streamable HTTP MCP / OTel / pino / vitest
- ❌ GraphConverter 多 converter 并存（保留接口，默认实现单例，留 v0.49+）
- ❌ v1.3.6 Pending refs + 阶段屏障（规模不匹配）

#### 后续候选（v0.49+）

| 主题 | 内容 |
|------|------|
| MCP server 接入 | 把 `toolRegistry.call()` 接到 MCP protocol，让 `withSnapshot` 包装真正发挥价值（asdm `withGraph` 的镜像终态） |
| linkDefs 数据驱动 | `LINK_TYPES` + `builderLinks.js` + `linkMeta.js` 三处收敛为单一 def 表，新增 linkType 从 3 文件改 1 表行 |
| converter 钩子下放 | `traverse_links` 的 130 行集中逻辑拆 4 钩子（`resolveLink` / `virtualEntity` / `expandLinked` / `linkVocabulary`） |

---

## [0.47.0] - 2026-09-23

### 借鉴 asdm-aos 第二批能力：RPC 匹配增强、facts 解析缓存、跨仓补链、引用完整性、迁移校验和、查询谓词与分析器注册表

对照 asdm-ontology-research/aos 的系统性差距分析（ADR 0013），落地六个经上游实战验证的机制。
全程坚持"只借模式不借重量"：零构建、单管线、JSON-first 不变。

#### 1. RPC 匹配占位符归一 + tier 回执（P0）

- `apiPathSegments` 双侧归一：printf `%s`/`%1$s`、FastAPI `{id}`、Next `[id]`/`[...slug]`/`[[...slug]]`、
  `<id>`、模板表达式 `${expr}` 统一为 `:param`/`*all` 规范形（ADR 0009 遗留的 iDRAC
  `%s` 端点永不匹配从此可匹配）；参数名不参与匹配（aos toSegments 思想）
- 全大写环境变量模板前缀剥离（`${API_BASE_URL}/users` → `/users`），前缀差异仍交人工规则解释
- 尾段吞剩余扩展到 Next `:slug*` / `:slug?` catch-all
- 命中阶梯 tier（1 字面量+method → 4 通配）记入 `apiMatch.tier`、`frontendCalls[].tier`、
  `_meta.rpcChain.tierCounts`——命中可归因（matchedVia 同思路）
- **next-api 纳入服务端候选池**：7e 段前移至匹配点之前，Next `route.ts` 可被前端 httpCall /
  Python outbound 端点命中；7c-d（TS httpCalls）同步升级 matchApiRouteEx（method 消解 + 规则重试）
- php 仍默认排除，可经 `.nice-aos/api-routes.json` 的 `{"serverRouteTypes":["php"]}` 显式开启
- 未命中调用无条件进 `unmatchedFrontendCalls`（纯前端仓不再缺席——跨仓补链的原料）
- pythonAnalyzer：拼接表达式（`base + "/api/x" % id`）取首个含 `/` 的字符串字面量

#### 2. facts 内容寻址缓存（P0，二次构建 3.2× 提速）

- 新增 `analyzers/factsCache.js`：`<dataDir>/facts-cache.json` 按
  `relPath → { h: sha256 前 16, f: facts }` 缓存解析结果；**内容哈希为权威判定**，
  命中即跳过逐文件 parse（TS Compiler API / 状态机），后续相位照常全量运行——正确性等价
- 序列化：Set/Map 显式编码；`resolved`（import 解析每次重跑）、TS AST 节点（`node`/`statementNode`）
  与一切类实例不落盘；version / projectRoot / JSON 损坏任一不符 → 整体弃用回全量
- `refreshRepo` 默认启用（`nice-aos action refreshRepo --no-incremental` 关闭；
  params.incremental=false 同效）；缓存与快照同目录，跟随 `--snapshot-dir` 覆盖链
- `_meta.fileManifest`（relPath → `{s,m,h}`，aos snapshot manifest 的 nice-aos 版）+
  `_meta.factsCache`（hit/miss）随快照发布
- 自扫描实测：143 文件 1942ms → 598ms，产物对象计数一致

#### 3. 合并后跨仓 RPC 补链（P0）

- `output --merge` 合并后在总路由池上重跑阶梯匹配，补上"构建期因跨仓不可见而未命中"的
  前后端链路（unmatchedFrontendCalls / 未建链 outbound 端点 ↔ 他仓服务端路由）；
  命中记 `crossRepo: true` + tier/matchedVia，统计进 `_meta.merged.rpcChain`，stderr 汇报
- fileId 按合并 SourceFile path 重定位（rename 策略下原 fileId 可能已前缀化）

#### 4. 引用完整性审计（P0，实抓 2 个真 bug）

- 新增 `ontology/refIntegrity.js`：组装末尾全局 id 索引 → 批量存在性判定 → 悬空
  `*Id/*Ids` 摘除（宁摘除不悬挂），`_meta.refIntegrity` 上报计数与样本
- **修复 scriptObjects.js 同名函数 id 漂移**：`fnIdMap` 按名覆盖导致
  UserScript.functionIds 悬空 + ScriptFunction 重复对象 id（自扫描 19 处清零）
- **修复 PHP/Kotlin 外部导入不建 Dependency 对象**：`use Guzzle\Http\Client` 现在产出
  `dep:Guzzle` 对象（与 npm/Go 未声明依赖同路径），`query Dependency` 可查

#### 5. 迁移校验和（P1）

- migrate.js 新口径 `sha256(version:up源码)` 绑定实际执行的迁移实现；
  已应用迁移的 up 被改过 → 硬错并提示 `storage rebuild`（Flyway checksum 语义）；
  v0.31–v0.46 旧账本一次性采纳新口径（等价 repair）；导出
  `checksumOf/legacyChecksumOf/MIGRATIONS` 供核验

#### 6. --where 扩展（P1）

- 数值比较 `k>N` / `k>=N` / `k<N` / `k<=N`（两侧可数值化才参与，否则不命中）
- 点路径嵌套字段（`health.complexity.cyclomatic>5`、`apiMatch.methodMatches=true`）
- 既有 `=`/`:`/`~` 与数组语义零变化；CLI query / serve /api/objects / MCP query_objects 生效

#### 7. 分析器注册表（P1）

- 新增 `analyzers/analyzerRegistry.js`：分发链从 builderScan 与 buildSingleFileOntology
  两份重复实现收敛为单一注册表（顺序与拆分前逐一对应，行为零变化）
- `registerAnalyzer({name, match, fromDisk, single})` 进程内扩展点，返回注销函数——
  新增语言从"改 5 处核心"降为"注册 1 条"

#### 测试与修复

- 新增 7 个测试文件 33 个用例（rpcMatchV2 / factsCache / mergeCrossRepo / refIntegrity /
  migrateChecksum / whereClause / analyzerRegistry），全量 984 用例
- 与基线对比**零新增失败**；顺手修复存量 Windows bug：exportMerge.test.mjs 的
  `import(D:\...)` 裸路径（`pathToFileURL` 化），3 个存量失败转绿
- 存量 69 个失败均为环境项（better-sqlite3 未安装 + Windows ESM 动态导入路径类），与本次变更无关

## [0.46.0] - 2026-09-22

### 本体蓝图呈现增强：新增「本体概览」Tab + vocabulary 词汇接口

参考 `D:/workspace/asdm-new/asdm-aos-server/src/modules/ontology/ontology.service.ts` 的 `getVocabulary`（objectTypes + linkTypes + actions 三件套）
与 `modules/graph/graph.gitnexus-types.ts` 的 RelationshipType 双向词汇约定，把 nice-aos 原本藏在「总览」末尾的小段落，升级为独立的本体概览视图。

#### 1. `viewerModel.blueprint.vocabulary` —— Agent 端友好的词汇接口

借鉴 asdm-aos 的 `getVocabulary(graphId)` 返回结构（objectTypes 按 count 倒序 + linkTypes/linksActions 完整列表），
nice-aos 在 viewerModel 中新增：

- `vocabulary.objectTypes`：所有声明类型按实例数倒序，每项含 `type/prefix/category/level/count/active`
- `vocabulary.linkTypes` / `reverseLinkTypes`：链接类型 + 反向词汇
- `vocabulary.actions`：`refreshRepo / analyzeFile / markReviewed / addNote` 四个动作
- `activeTypeCount` / `totalInstanceCount` / `declaredTypeCount` / `declaredLinkTypeCount`：四个概览指标

数据契约稳定，Agent 可直接通过 `model.blueprint.vocabulary` 一次性发现全部可用类型与链接，
无需遍历 `model.dataMap` 各键再聚合。

#### 2. 「本体概览」Tab —— 把声明 vs 真实数据摆到一起

新增 Tab「本体概览」（位置：总览 与 领域蓝图 之间），四大区块：

1. **概览指标**：6 张 KPI 卡片（声明类型 / 活跃类型 / 范畴 / 层级 / 链接 / 实例总数）
2. **抽象层级条带**：L3 架构 → L2 结构 → L1 单元 → L0 事实，自顶向下展示；每行含活跃度进度条 + 实例总数
3. **概念范畴矩阵**：按 7 个概念范畴（Container / CodeUnit / EntryPoint / Script / Builtin / Environment / AuditFact）排成网格，
   每张卡片展示该范畴下所有类型（含 L3→L0 各层），按实例数倒序；类型行的活跃度（active = 当前快照有实例）一眼可辨
4. **链接词汇表**：正反向成对展示（如 `contains ↔ containedBy` / `calls ↔ calledBy`），三种状态点：
   - 🟢 绿点：已声明为正反向一对（双语义）
   - ⚪ 灰点：仅声明正向（反查靠所属字段）
   - 🟡 黄点：反向声明但未与正向配对

#### 3. 类型详情面板（v0.46.0 联动）

点击「概念范畴矩阵」中的任意类型行 → 下方出现详情面板：
- 类型名 / 层级标签 / 范畴标签 / 实例数 / id 前缀
- 类型描述
- 样本实例预览（前 5 个，含 name + id）

#### 4. 顶部「总览」Tab 引导

原总览末尾的「本体蓝图（概念分类体系）」小段落保留，并在末尾追加跳转提示，引导读者去专门的「本体概览」Tab。

#### 5. 实现要点（借鉴 asdm-aos）

| 维度 | asdm-aos 参考 | nice-aos 实现 |
| --- | --- | --- |
| vocabulary 接口 | `ontology.service.ts:getVocabulary` | `viewerModel.blueprint.vocabulary` |
| 链接正反向词汇 | `graph.gitnexus-types.ts` RelationshipType + `graph.types.ts` LINK_DEFS | `viewerModel.blueprint.linkTypePairs`（仅保留既有 LINK_TYPES 实际存在的双向对） |
| 双维组织（范畴×层级） | `graph.slice.ts` level 三档切分 | `viewerModel.blueprint.categoryMatrix` + `levelCards` |
| 视图层 | `graph.controller.ts` HTTP 端点 | 蓝图 HTML 内嵌 `renderOntology()` + 客户端 JS |

#### 6. 测试

新增 `test/ontologyView.test.mjs`（6 例）：
- vocabulary 暴露字段 + 倒序
- categoryMatrix / levelCards 结构 + 自洽
- linkTypePairs 验证 forward/reverse 都在 LINK_TYPES
- renderViewerHtml 嵌入新 Tab + renderOntology 渲染脚本
- activeTypeCount / totalInstanceCount 自洽校验

既有 178 个相关测试全部回归通过（`viewer / blueprintEngine / blueprintActions / viewerInteractive / hubContracts / dartEntities / goAnalyzer / goFrontendMap / vuePropsChain / propsChain / semantics / typeEntities / userScriptAnalyzer / themes`）。

## [0.45.0] - 2026-09-18

### aos 蓝图交互增强 + serve 强化 + 代码库自动监听

针对全景架构蓝图（aos 蓝图）三条主轴：**交互密度**（原本只有 Tab 切换，落后于 service/db/deploy 蓝图）、
**serve 暴露**（overview 文件未被监听，serve 与 overview watch 是两段独立链路）、
**自动化**（手动跑 `export` 才能触发通知）一次性补齐。

#### 1. `overview watch` —— 代码库变更后自动重扫 + 导出 + 通知 serve

新增子命令 `nice-aos overview watch --projects-dir <root>`：
- **文件监听核心** `src/overview/fileWatcher.js`：纯 Node 内置 `fs.watch` + `fs.watchFile`（项目保持零三方依赖），递归监听目录树，跳过 `node_modules / .git / dist / build / target` 等
- **防抖 + burst 检测**：默认 1500ms 窗口吸收 IDE / git pull 短时高频事件；burst 时延后 2× interval，避免连击
- **settling window**：start 后 `interval` 毫秒内忽略 fs.watch 伪事件（macOS kqueue 挂监听后立即 fire 一次的副作用）
- **baseline 去重**：start 时扫一遍目标 (mtime, size) 指纹，fire 时 statSync 对比，只把真变化的路径交给 onChange
- **优雅退出**：SIGINT / SIGTERM / SIGHUP 触发 watcher.close() 清 timer + 关闭所有 fs.watch handle + 关闭 SQLite
- **触发回调**：scanOverview → saveOverviewSnapshot → 导出（默认 html，可选 html / viewmodel / canvas / json 任意组合）→ notifyServe({ event: 'overview:changed', paths })
- **重入保护**：回调尚未 settle 时新事件只标记 dirty，结束后再补跑一次（避免扫描期间又被新事件打断）
- **一次性模式 `--once`**：CI 冒烟测试用，跑一次就退出不进入监听循环

CLI 用例：

```bash
nice-aos overview watch --projects-dir ../multi-projects \
  --interval 1500 \
  --export-formats html,canvas \
  --output-dir ../docs/overview
# 终端 Ctrl+C 退出，stderr 打印每次触发结果与累计统计
```

#### 2. serve 把 overview 也纳入监听与广播

- **`serveWebSocket.js` 重构为多文件监听**：`attachWebSocketUpgrade` 接受 `files: [{path, event}]` 数组，兼容旧 `snapPath + bpPath` 接口（自动转 files）
- **新增事件 `overview:changed`**：overview-snapshot.json / overview.html 任一 mtime/size 变化即广播，payload 加 `path` 字段方便客户端按路径分发
- **snapshot:changed 兼容旧 client**：payload 仍带 `kind: 'code'` 字段（v0.33 老 client 兼容）
- **hello 帧扩展**：除 `snapshot` / `blueprint` 外，新增 `frames: { snapshot, blueprint, overview }` map；老 client 仍能解析 `snapshot` / `blueprint` 字段
- **`serve.js` 加 overview 公共静态端点**：`/overview-snapshot.json` + `/overview.html`（404 时提示 `nice-aos overview scan + export`）
- **`PUBLIC_PATHS` 扩 2 个**：overview 端点与 snapshot.json / blueprint.html 同列公共静态（鉴权豁免）
- **banner + index html + `/api/status`**：overview 行加 '✓/✗' 状态显示，`/api/status` 返回 `overview: { snapReady, bpReady, snapPath, bpPath, wsEvent }`

#### 3. overview 蓝图交互密度对齐 service / db / deploy 蓝图

原 `overviewViewer.js` 仅有 1 处交互（Tab 切换），现在补齐：

- **服务清单（services tab）即时搜索** + Java 服务复选过滤
- **技术栈（tech tab）**：search 输入 + 框架 chips 多选过滤（chips 实时显示每框架项目数）
- **部署拓扑（deployment tab）**：search 输入 + 端口范围 min/max 数值过滤
- **卡片点击展开**：每张服务卡可点击展开查看完整技术栈 / 端口 / 依赖 / 路径
- **复制按钮 ⧉**：服务名 / 端口一键复制到剪贴板（`navigator.clipboard.writeText` + `execCommand('copy')` 兜底）
- **URL hash 同步**：Tab 切换写 `location.hash = '#tab=xxx'`，刷新可还原、可分享链接
- **回到顶部浮动按钮**：滚动 > 400px 显示，圆角按钮 smooth scroll 到顶部
- **Toast 提示**：复制成功 / 失败瞬时反馈
- **meta-info 实时统计**：每个 tab 工具栏右侧显示"共 X 项，显示 Y 项"

#### 4. 代码精简（overviewViewer.js）

- `renderTable(headers, rows, opts)` 辅助函数替换 6 处 `<table><thead><tbody>` 字符串模板拷贝（services / tech Java / integration crossMatrix / integration javaCrossDeps / deployment ports / compose / nginx 共 8 个表）
- `escapeHtml(s)` + `escapeAttr(s)` 两个工具取代原 `escape()` 单函数（属性上下文安全）
- `cardsHtml` 风格统计行用数组 + map 一次性产出（6 张卡片）
- JS 交互脚本独立 IIFE，单一 `DOMContentLoaded` 入口，注释分 7 段（tab / settling / search / filter / copy / back-top / toast）

#### 5. 测试

- `test/overviewWatcher.test.mjs`（8 例）：collectFingerprint 跳过规则、start() baseline 计数、防抖合并、settling 窗口过滤伪事件、删除 fireNow 报告、close() 资源清理、state() 字段、interval 下限 200ms、单文件 watchFile 跨平台
- `test/overviewViewer.test.mjs`（12 例）：buildOverviewViewerModel 字段聚合 + 缺失字段默认值、HTML 骨架 7 Tab + 6 卡片 + 7 panel、3 个 toolbar 属性锚点、交互脚本关键字（hash / copy / debounce / search target / port min-max / back-top / toast）、svc 卡片 data-name + copy-btn、人类知识空 / 满渲染、HTML / 属性转义、crossMatrix 行、nginx / port / compose 都渲染、totals 反映在卡片
- `test/serveAuth.test.mjs` 更新：`auth.public` 新增 `/overview-snapshot.json` / `/overview.html` 两个公共静态端点

#### 6. 兼容性

- `attachWebSocketUpgrade(server, opts)` 旧调用 `snapPath + bpPath` 仍可用（自动转 files 数组）
- WS hello 帧保留 `snapshot` / `blueprint` 字段，老 client 不需要升级
- snapshot:changed payload 保留 `kind: 'code'`，老 client 鉴权 / 路由逻辑零变化

## [0.44.0] - 2026-09-04

### 借鉴 asdm-aos：查询投影/计数、RPC 匹配强化、SKILL 行为契约

对上游 @leansoftx/asdm-aos v0.0.24 的第二轮定向借鉴（第一轮见 ADR 0001/0002），
吸收三个经对方实战验证的点。详见 `docs/adr/0012-aos-borrow-query-projection-rpc-rules-skill-contract.md`。

#### 查询投影与计数（agent 上下文友好）

- **`query --field id,name,filePath`** — 字段白名单投影（`id` 恒保留），在 where/limit 之后应用；
  同一公共件（`parseFields`/`projectObjects`）接入三处消费方：CLI query、serve `/api/objects?fields=`、
  MCP `query_objects.fields`
- **新命令 `count <type> [--where]`** — 单行紧凑 JSON `{"ok":true,"type":...,"total":N}`，
  供 agent 判断数量级后再决定 query 是否 `--all`（顶层命令 18 → 19）

#### RPC 匹配强化（method 消解 + 人工规则层，不改 ADR 0010 D3 软校验哲学）

- **`matchApiRouteEx`**：method 作同路径多路由的**消解优先级**（1 字面量+method → 2 字面量 →
  3 通配+method → 4 通配；`MIXED` 视为未定跳过 method 阶梯）；不做硬门——路径唯一命中时
  method 不一致仍建链，`methodMatches` 照旧如实记录；`matchApiRoute` 变薄包装，既有调用方行为零变化
- **人工路由规则文件 `<root>/.nice-aos/api-routes.json`**：段级前缀改写（`{"rules":[{"from":"/gw-api","to":"/v2/api"}]}`，
  裸数组亦接受），仅在自动阶梯全部未命中后重试（不劫持自动命中）；文件缺失零警告，非法条目跳过记
  `rulesWarnings` 不阻断构建（借鉴 asdm-aos 阶梯 5 / matchedVia 可审计设计）
- 数据形态：`NetworkEndpoint.apiMatch.matchedVia`（仅规则命中，`rule:/gw-api→/v2/api`）、
  `_meta.rpcChain.ruleMatched` / `rulesCount`（/`rulesWarnings`）

#### SKILL 行为契约（nice-aos-skill 试点）

- 新增**查询纪律**（先样本后过滤 / 投影降 token / 大输出先缩小）与**停止条件表**
  （importedBy 递归不假设层数、callsApi 未命中 ≠ 链路不存在、methodMatches=false 是如实记录、
  网关改写走规则文件等），命令参考补 `--field` 与 `count` 用法

#### 其他

- `test/hubContracts.test.mjs` 的 cli/shared.js 导出冻结清单登记 `parseFields`/`projectObjects`
- 新增测试：`test/queryProjection.test.mjs`（13 例）+ `test/rpcChain.test.mjs` +4 例

## [0.43.0] - 2026-08-31

### 画布作为 output 的第六种格式：架构图与蓝图产出的同构

v0.42 新增的 `nice-aos-canvas-skill` 只覆盖 Agent 视角的手动工作流（复制模板 + 替换数据块），
未在 CLI 暴露画布入口；v0.43 把画布做成 `output --format canvas` 的程序化产出，与
markdown / json / html / viewmodel 同列。详见 `docs/adr/0011-canvas-output-blueprint.md`。

#### 新增画布模块（src/canvas/）

- **`src/canvas/canvasPaths.js`** — 模板路径解析 + 占位符注入（含 `</script` 字符串转义）
- **`src/canvas/canvasBuilder.js`** — 4 个公开 API：
  - `buildCanvas({ kind, snapshot, title })` 通用入口
  - `buildDeployCanvas(model)` 部署画布快捷入口
  - `buildOverviewCanvas(model)` 全景画布快捷入口
  - `buildCanvasAuto({ deployModel, overviewModel, preferKind })` 自动检测
  - 错误前置：缺 `services` / `projects` 时抛明确错误，不产出半残 HTML

#### 模板增强（skills/nice-aos-canvas-skill/assets/）

- **`deploy-canvas-template.html`**：
  - 9 类风险徽标（latest / 单副本 / 无 HC / 未限额 / env 多 / env 兜底 / 孤儿 / 路由未解析 / 中间件无消费方）
  - 跨域边（Docker ↔ K8s）走点划线（8,3,2,3）
  - 运行时识别新增 **Rust**（已有 JVM/Node.js/Python/Nginx/PHP/.NET/Go）
  - 启动脚本检测占位符 token，未注入时降级到使用提示（不抛错）
  - 数据块 token 改为清晰的 `__CANVAS_DATA_JSON__`（之前是 `{"_placeholder": true}`）
- **`overview-canvas-template.html`**（新增）：
  - 项目列 × 分层行矩阵布局（接入 / 前端 / 应用 / 服务 / 数据 / 基础设施 / 工具 7 层）
  - 跨项目依赖长边（紫色虚线，从 `architecture.crossMatrix` 推导）
  - 按语言 / 按分层高亮过滤的图例
  - 同款零依赖 / 缩放 / 平移 / 节点详情抽屉

#### CLI 接入

- `nice-aos deploy export --format canvas --output x.html`  — 部署画布
- `nice-aos overview export --format canvas --output x.html` — 全景画布
- `nice-aos output --format canvas --output x.html`         — 三大核心命令 output 的 canvas 格式
  - 自动检测可用的 deploy → overview 快照，stderr 报告 `画布类型` 与 `数据源`
  - 无快照时给出可执行的 `nice-aos deploy scan / overview scan` 提示
  - `--format canvas` 必须配合 `--output`（避免大量 HTML 刷屏）
- `nice-aos --help` 顶部 `output` 命令描述同步更新
- `package.json` keywords 新增 `canvas` / `html-canvas`，description 更新
- `src/deployment/deploySnapshot.js` 补 `hasDeploySnapshot` 导出（自动检测需要）

#### 测试

- `test/canvasBuilder.test.mjs` — 19 个单测（模板加载、占位符注入、转义安全、3 种部署形态、overview 矩阵、auto 优先级、错误前置、SPA 路径资产完整性）
- `test/canvasExportCli.test.mjs` — 5 个端到端 CLI 测试（deploy/overview/output 三种入口、缺快照 fail、无 --output fail）
- 全量 `node --test`：在 v0.42.2 基线 882 个测试基础上新增 24 个画布测试，零回归

#### 已锁定的 8 个设计决策（详见 ADR 0011）

- D1 画布作为 output 的第 6 种格式
- D2 模板与数据解耦（模板只"画"，builder 只"装"）
- D3 占位符 `__CANVAS_DATA_JSON__` 是源码字面量（不用 `{{ var }}` 风格）
- D4 builder 暴露纯函数 API，CLI / Agent / 第三方工具共享
- D5 错误前置：缺关键字段时抛明确错误而非产出半残图
- D6 自动检测：output --format canvas 按 deploy → overview 顺序探测
- D7 模板启动脚本检测占位符，未注入时降级到使用提示
- D8 注入 JSON 时转义 `</script` 字符串，避免被浏览器提前关闭 data 块

#### 后续候选（v0.44+）

1. db / service / planning 三种蓝图画布（复用同一 builder 接口）
2. overview 画布增强：服务间通信矩阵 / 资源配额热力图
3. 画布对照模式（v1 / v2 diff）
4. 画布主题与现有 `--theme` 联动
5. 画布审计：直接叠加 `deploy audit` 红/黄/绿评级

## [0.42.2] - 2026-08-30

### viewer 封装提取阻断修复（48c19d6 遗留 3 个缺陷）

v0.42.1 的 viewer 模块拆分（viewer.js 3692 行 → viewerModel/viewerClient/viewerStyles/viewerInteractive 四模块）
在 48c19d6 提交中为半成品，本次补齐使其真正可用。

#### 修复 buildViewerModel 运行时崩溃（致命）

- `buildViewerModel` 迁到 viewerModel.js 时，其引用的 17 个模块级常量滞留 viewer.js 未随迁：
  `UNIT_CAP / USED_BY_CAP / HUB_CAP / MODULE_CAP`、5 个 `SCRIPT_*`、4 个 `ENTITY_*`、4 个图上限、`SCRIPT_ROLE_META`
- 后果：`ReferenceError: HUB_CAP is not defined`，所有 viewer / HTML 蓝图 / viewmodel / report 导出路径全部崩溃
- 修复：常量随迁 viewerModel.js（数据聚合层），viewer.js 同步清理

#### 修复 renderViewerHtml 脚本块拼接残缺

- 模板字符串被改为残缺拼接：`renderInteractiveScript()` 重复输出两次 + 游离 `</script>`
- 后果：交互操作脚本重复注入（监听器/提交动作翻倍），HTML 结构损坏
- 修复：`${CLIENT_BASE}` + 单次 `renderInteractiveScript()` 正确拼接；script 3 开 3 闭配对

#### 清理双侧死导入

- viewerModel.js：剔除仅渲染层使用的 `buildThemeCss / DEFAULT_THEMES / SHARED_CSS`
- viewer.js：剔除已随迁的 `ARCH_LAYERS / ONTOLOGY_META / OBJECT_TYPES / LINK_TYPES / ACTION_DEFS` 与全部常量定义

#### 验证

- viewer 族 + export 链路测试全过；全量 882 测试失败集与重构前基线（14dc55b / e243327）逐条一致（19 条均为 storage/ask/mcp 环境预存），零回归
- 同一快照在修复版与重构前基线渲染的蓝图 HTML 归一化后逐字节一致——蓝图特性零丢失
- 端到端实测：nice-today-2.0（1592 文件 / 831 组件大仓库）与 asdm-admin 部署目录、MySQL 迁移目录蓝图均正常产出并 DOM 执行验证通过

## [0.42.1] - 2026-08-29

### v0.42.0 review 闭环 —— 3 个真问题修复

详见 `docs/adr/0010-rpc-bidirectional-chain.md` 的"v0.42.1 闭环"章节。

#### 修复 matchApiRoute 字面量优先（ordering bug）

- 原实现按 `routeSegsList` 顺序遍历匹配，当通配路由（`/:id`）先于字面量（`/self`）声明时，
  客户端请求 `/self` 会错误命中 `:id`，导致 `serverRouteId` 错指
- 修复：matchApiRoute 加第一轮"全字面量命中"扫描，再退化到原通配逻辑；**路由声明顺序无关**
- 新增 test：`RPC 链 v0.42.1：字面量路由在通配之后声明时，self 仍命中字面量（不是 :id）`

#### 修复 urllib/requests.request method 推断（MIXED 假阳）

- 原实现对 `requests.request` / `urllib.request.urlopen` / `urllib.request.Request` 一律标 `MIXED`，
  导致 iDRAC 一类 urllib 仓库的 `_meta.rpcChain.methodMismatch` 统计虚高
- 修复：
  - `urllib.request.urlopen(url)` 默认按 `GET`（HTTP 事实标准）
  - `urllib.request.urlopen(url, data=...)` 按 `POST`
  - `urllib.request.Request(url, method='PUT')` 读 `method=` kwarg
  - `urllib.request.Request(url, data=...)` 没 `method=` 时按 `POST`；都没有仍为 `MIXED`
  - `requests.request(method='DELETE', url=...)` 读 `method=` kwarg
  - `requests.request('POST', 'url', ...)` 首位置参识别
- 新增 7 个 test 覆盖 method 推断各分支

#### 双向字段原子赋值（`linkRouteToEndpoint` helper）

- 7c-d2 段原先分散赋值 `ep.serverRouteId` / `ep.serverRoutePath` / `ep.apiMatch` /
  `route.clientEndpointIds` 4 个字段，未来重跑或加新匹配规则易出现单边赋值失败
- 抽 `linkRouteToEndpoint(route, ep, apiMatch)` helper 集中维护双向 invariant
- 新增 test 验证：所有匹配端点 ↔ 路由的 `clientEndpointIds` 必含其 id

#### 仍为 v0.43+ 候选

- 7c-d 段（TS httpCalls）扩到 Python 服务端路由
- `slugifyEndpointUrl` 在 id 中保留 `{ }` 占位符（breaking change，需重新生成 id）

## [0.42.0] - 2026-08-29

### 前后端 RPC 双向链 —— outbound 端点 ↔ 服务端 API 路由

详见 `docs/adr/0010-rpc-bidirectional-chain.md`。

#### 新增 Python 客户端 ↔ 服务端路由的匹配（7c-d2 段）

- v0.41.0 建出的 outbound 端点此前是孤立节点，现在与服务端路由双向建链
- 匹配目标限定 `go` / `python` 两类路由（routePath 具备真实 URL path 语义）
  - 不含 `php`：zentaopms 是 `/<module>-<method>` 的 query 式 URL，段匹配会误命中
  - 不含 `next-api`：构建于 7e 段晚于匹配点，且 Next 项目的 Python 客户端场景罕见
- 端点侧落 `serverRouteId` / `serverRoutePath` / `apiMatch`，路由侧落 `clientEndpointIds`
- 实测：客户端 `{uid}` ↔ 服务端 `{user_id}` 参数名不同仍能正确命中；跨语言 Go `:id` ↔ Python `{uid}` 同样命中

#### 匹配逻辑上提为公共函数（不重写）

- 7c-d 段原有的 `normPath` + 段级匹配抽成 `apiPathOf` / `apiPathSegments` / `matchApiRoute`
- 相比原实现扩展两点：支持 FastAPI 的 `{param}` 语法（原只认 Go 的 `:` 与 `*`）；
  host 含占位符时也能正确切出 path（`https://%s/redfish/v1/Systems` → `/redfish/v1/Systems`）
- 7c-d 段改为调用公共函数，行为不变

#### method 采用软校验

- 路径命中即建链，method 是否一致记在 `apiMatch.methodMatches`（`{ methodMatches, routeMethods, endpointMethod }`）
- 与 7c-d 既有的"method 不一致仍记录"哲学一致，并为候选 3（跨语言 API diff）预留数据

#### 新增 callsApi 边（LINK_TYPES 55 → 56）

- `net:` → 命中的服务端路由；`route:` → 调用该路由的全部客户端端点（双向可查）
- `OBJECT_TYPES` 仍 38

#### 覆盖度统计

- `_meta.rpcChain`：`{ serverRouteCount, endpointCount, matched, methodMismatch, unresolved }`
- 无服务端路由或无 Python 端点时为 `null`（维度不适用，区别于"匹配到 0 条"）

#### 修复既有缺陷：`Route.apiMethods` 口径不统一

- Go 路由是数组 `[method]`，Python 路由是裸字符串
- 直接 `includes` 会退化成子串匹配（`'POST'.includes('OST')` 误为 true），已归一为数组

## [0.41.0] - 2026-08-29

### 统一 NetworkEndpoint —— 还清 v0.40.0 的孤儿 fact 技术债

详见 `docs/adr/0009-unified-network-endpoint.md`。

#### 修复：v0.40.0 的 Python HTTP 客户端数据从未进本体图谱

- `facts.httpClientCalls`（v0.40.0 D3 新增）在 `src/` 内除声明与赋值外**零消费**，
  `dataMap.NetworkEndpoint` 的唯一来源始终是油猴分支
- 修：新增 `buildPythonOutboundEndpoints()`，按 `(method, url)` 全局跨文件聚合建 outbound 实体
- 实测 iDRAC-Redfish-Scripting（225 文件）：NetworkEndpoint **0 → 166**，其中 61 个为跨文件聚合端点
- 同时接入单文件分析路径（`analyzeFile xxx.py` 也能看到端点）

#### 修复：`%` 格式化 URL 截断错误（数据质量 bug）

- `requests.get("https://%s/redfish/v1/Systems" % idrac_ip)` 在 v0.40.0 产出
  `https://%s/redfish/v1/Systems" % idrac_ip`（`" % idrac_ip` 被误留在 url 内）
- 修：改为提取首个字符串字面量，前缀 `[a-zA-Z]*` 兼容 `f""` / `rf""` / `b""`
- iDRAC 166 个端点中 160 个含占位符，此修复直接决定数据可用性

#### NetworkEndpoint 实体模型统一

- 新增 `direction`（当前两侧均为 `outbound`，`inbound` 留待 v0.42.0 服务端 handler 接入）、`lang`、`lib` 三字段
- 油猴专有字段 `allowedByConnect` / `scriptId` / `scriptName` 在 Python 侧置 null，字段保留以维持两端同构
- 选"加字段"而非"拆新类型"，`OBJECT_TYPES` 仍 38 / `LINK_TYPES` 仍 55

#### 存储投影属性补齐

- `TYPE_PROPERTIES.NetworkEndpoint` 从 1 项扩到 10 项（新增 direction/lang/lib/kind/domain/url/callCount/hasAuth/filePath）
- `domain` 与 `url` 用 btree 索引提示，供 v0.42.0 RPC 链检索

#### 增量解析

- `incrementalParser` 的失效类型对 `.py` 追加 `NetworkEndpoint`（此前仅 `.user.js` 触发）
- `defaultTypesForFile()` 由 private 改为 export 以便单测覆盖

#### 消费方适配

- `exporter` 网络请求表格：来源列改为 `scriptName ?? filePath`（表头"脚本"→"来源"），kind 映射加 `http-client`，新增"方法"列（同 URL 的 GET/POST 是独立端点，缺此列会渲染成重复行）
- `viewer`：`scriptBlueprint.totalNetworkCount` 只统计 `scriptId` 非空的端点（与列表里的油猴端点口径一致）

#### 已知限制（留待 v0.42.0）

- 函数级归属 `fns` / `fnIds` 留空：pythonAnalyzer 的 function fact 只有 `pos` 没有 `end`，无法可靠做区间判定
- `callCount` 仅在跨文件时累加（analyzer 层按文件内 `(lib, method, url)` 去重只记首次）
- 160/166 端点的 URL 含占位符（`%s` / `{}`），需占位符归一化后才能做 RPC 链匹配

## [0.40.0] - 2026-08-29

### 多语言脚本架构增强 —— argparse / HTTP 客户端 / UTF-16 PowerShell / 跨语言同步

iDRAC-Redfish-Scripting（[github.com/dell/iDRAC-Redfish-Scripting](https://github.com/dell/iDRAC-Redfish-Scripting)，
146 .py + 79 .ps + 1 .psm1 共 200k+ 行）扫描驱动。Dell 的 Redfish API 脚本仓库跨 Python+PowerShell 双实现，是
混合脚本架构的典型场景。详见 `docs/adr/0008-idrac-multilang-scripts.md`。

#### 修复 PowerShell UTF-16 BOM 探测（数据质量 bug）

- iDRAC 79 个 PowerShell 文件全部 UTF-16 LE + BOM + CRLF，原 `fs.readFileSync(..., 'utf-8')` 直接读为乱码
- 修：新增 `readTextWithBom()` —— 头 2 字节 `0xFF 0xFE` → UTF-16 LE；头 3 字节 `0xEF 0xBB 0xBF` → UTF-8 BOM；其余 utf-8
- 修后 Invoke-CreateVirtualDiskREDFISH.psm1（55k 行 IdracRedfishSupport.psm1 同理）正确解析为 PowerShell，含 21 个 CLI 参数 + 1 个函数 + 真实 cmdlet 列表

#### 新增 argparse CLI 参数抽取（与 Bash/PS 的 cliParams 同构）

- 模式：`<var>.add_argument('-ip', help='...', required=False, dest='ip', type=int, default=0, action='store_true')`
- 抽取每个参数的 `flag / short / long / name / positional / type / required / default / action / help / line`
- flag 形式：`-ip` / `--ip` / `-ip, --ip-address` / `fqdd`（位置参数）
- name 解析：`dest` 优先 → 长写去 `--` → 短写去 `-`，并把 `-` 归 `_`（kebab→snake）
- 适用：iDRAC 单脚本平均 5-20 个 add_argument，整仓库累计数千 CLI 表面

#### 新增 Python HTTP 客户端端点抽取（requests / urllib / httpx / aiohttp）

- 4 个 lib 共 7+ 模式：`requests.get/post/patch/put/delete/head/options` + `urllib.request.urlopen/Request` + `httpx.*` + `aiohttp.{session,client}.*`
- 抽取 `lib / method / url / hasAuth / hasJson / hasData / line`
- 跨 if/else 自动去重（同一 lib+method+url 只记首次），避免 if/else 双分支模式产生双倍条目
- 适用：iDRAC 单脚本 51 个 calls（旧版 0 抽取）→ 18 unique；未来可接 NetworkEndpoint 实体补 outbound 边

#### 新增 PowerShell Verb-Noun 抽取

- 已有 Verb → role 映射（Get→read / Set→write / Invoke→exec / ...）增强：在 `PsFunction` 上加 `verbNoun: { verb, noun }` 与 `crossLangKey` 字段
- 适用：iDRAC `Get-IdracLifecycleLogsREDFISH` → verb=`Get` noun=`IdracLifecycleLogsREDFISH` crossLangKey=`IdracLifecycleLogsREDFISH`（120/120 函数全部识别）

#### 新增跨语言脚本匹配 + 边

- 匹配键归一化：Python `GetIdracLifecycleLogsREDFISH.py` ↔ PowerShell `Get-IdracLifecycleLogsREDFISH`
  - Python 端：去除 `Get / Set / Invoke / New / Remove / Reset / Add / Update / Delete / Enable / Disable / Test / Start / Stop / Restart / Mount / Dismount / Push / Pop / Register / Unregister / Show / Hide / Open / Close / Format / Out / Copy / Move / Rename / Convert / Import / Export / Connect / Disconnect / Read / Write / Send / Receive / Wait / Resolve / Use / Save / Backup / Restore / Sync / Trace / Assert` 47 个 PS Verb 前缀
  - PowerShell 端：用 Noun 部分（去 `Verb-` 前缀）
- 匹配后建 `_meta.crossLangEdges: [{ from, to, key, py, ps, unresolved? }]`
- iDRAC 实测：158 候选匹配中 36 命中 + 122 unresolved（unresolved 是 Python 单方实现或 PS 共享模块函数）

#### 混合脚本架构层

- `*.py` 下 `/redfish|sdk|api|client/...` 目录 → `integration`（API 客户端 / SDK 工具）
- `*.ps1 / *.psm1` 同理 → `integration`
- PS Verb 前缀粗判：`Set-* / New-* / Add-*` → `write` 层；其余 → `shared`

#### 项目级

- `Project.language` 现在拼接 `Python + PowerShell`（iDRAC 端到端验证）
- `Project.pyFileCount` 已在 v0.36 暴露，iDRAC 端 146；`pyDetected` 同步

#### 本体类型扩展

- `LINK_TYPES` 54 → 55：新增 `crossLangMatches`（SourceFile(py) ↔ SourceFile(ps) / PsFunction：同名工作流的多语言实现）
- `OBJECT_TYPES` 不变（38 个；复用 SourceFile / PsFunction）

### 测试

- `test/pythonAnalyzer.test.mjs` 新增 3 用例：
  - argparse CLI 参数抽取（短/长/dest/required/action/位置参数）
  - HTTP 客户端调用抽取（4 lib + dedupe + hasAuth/hasJson 标志）
  - crossLangKey 从 relPath 派生（去 .py + 含深层路径）
- `test/blueprintEngine.test.mjs` / `test/toolRegistry.test.mjs` 同步更新：`LINK_TYPES 54 → 55`
- 全套测试 831/831 通过（+3 新增；原 828 全保留零修改）

### 端到端验证（iDRAC-Redfish-Scripting）

| 维度 | 数量 |
| --- | --- |
| 文件总数 | 225 |
| Python 源文件 | 146 |
| PowerShell 脚本 | 79（PsScript） |
| PowerShell 函数 | 120（PsFunction，120/120 含 verbNoun） |
| Cmdlet 调用 | 763 |
| crossLangEdges | 158（36 命中 + 122 unresolved） |
| language | `Python + PowerShell` |

## [0.39.0] - 2026-08-29

### Python 解析增强：ROS 2 维度 + 字段误报修复 + 入口点扩充

openamr-platform-sw（65 个 .py + 14 个 .launch.py，ROS 2 rclpy + launch 真实生产项目）扫描驱动。详见 `docs/adr/0007-ros2-python-enhancement.md`。

#### 修复 parseClassFields 缩进感知（数据质量 bug）

- 旧版 `parseClassFields` 用跨行续行 + 字符串扫描两步法，但**不感知方法体 / 复合语句边界**，把方法体内的局部变量误判为类字段
- 真实复现：openamr `dock_trigger.py`（70 个方法、0 个真类级字段）旧版一度报 **341 个伪字段**（`cli` / `colmon` / `topic` / `req` / `future` / `t0` / `out` / `ranges` / ...）
- 修复：引入轻量 `blockStack`（`def / class / compound / pending`）—— 遇 `def / async def / class` 与 `if/elif/else/try/except/finally/with/for/while/match/case` 入栈，按缩进比较弹栈；**仅当 blockStack 为空时**才认为该语句在类直接体（class-body level）才走字段匹配
- 修复后：dock_trigger.py 字段从 341 → 0（真实），旧 17 个 pythonAnalyzer 单测零修改全部通过

#### 新增 ROS 2 节点类检测（rclpy Node / LifecycleNode / ComposableNode）

- `class X(Node)` / `class X(LifecycleNode)` / `class X(ComposableNode)` → 末段基类查表 → `cls.ormHints` 追加 `ros2-node` / `ros2-lifecycle-node` / `ros2-composable-node`
- 附加抽取 `super().__init__('node_name')` → `ormHints` 追加 `ros2-node-name:<name>`
- 非侵入：保留 `cls.bases` / `cls.extendsName` 形态，仅追加 `cls.rosHint` 字段与 `cls.ormHints` 元素
- 新增 `facts.ros2NodeClasses` 顶层索引（`[{ name, line, baseClass, bases, rosHint, channels, pos }]`）

#### 新增 ROS 2 通信通道抽取（publisher/subscriber/service/client/timer/action/parameter）

- 在 class body 整体（已剥离字符串的 cleanBody 通道）扫描 7 类模式：

  | pat.kind        | 来源模式                                              | 抽取参数                          |
  | --------------- | ----------------------------------------------------- | --------------------------------- |
  | `publisher`     | `self.create_publisher(Msg, topic, qos)`              | msgType / topic / qos             |
  | `subscription`  | `self.create_subscription(Msg, topic, cb, qos)`       | msgType / topic / callback / qos  |
  | `service`       | `self.create_service(Srv, name, cb)`                  | srvType / name / callback         |
  | `client`        | `self.create_client(Srv, name)`                       | srvType / name                    |
  | `timer`         | `self.create_timer(period, cb)`                       | period / callback                 |
  | `action-server` | `self.create_action_server(...)`                      | actionType / name / callback      |
  | `action-client` | `ActionClient(node, Action, name)`                    | actionType / name                 |
  | `parameter`     | `self.declare_parameter(name, default)`               | name / default                    |

- 括号深度匹配（`findMatchingParen`）兼容嵌套泛型 / 缺省 lambda；callback 保留原文本（不静态解析符号引用）
- topic / parameter default 不做值回填（保留符号化文本如 `topic: scan_in`）—— v0.40+ 候选

#### 新增 ROS 2 launch 文件检测（*.launch.py）

- 入口函数识别：`def generate_launch_description():`（兼容 `generate_launch_file`）
- 顶层抽取 5 类 launch 动作：
  - `Node(package=..., executable=..., name=..., namespace=...)` → 节点清单
  - `ExecuteProcess(cmd=...)` → 外部进程清单
  - `DeclareLaunchArgument(name=..., default_value=..., description=...)` → 启动参数清单
  - `IncludeLaunchDescription(PythonLaunchDescriptionSource(...))` → 嵌套 launch 清单
  - `GroupAction / OpaqueFunction / SetEnvironmentVariable / TimerAction / ComposableNodeContainer / ...` → 其它 actions
- 入口函数加入 `facts.pythonEntryPoints` 作为 `kind: 'launch'`
- launch 文件无通道抽取（launch 是组合描述，不直接持有 Node 状态）

#### 新增 def main() 入口点

- 顶层 `def main():`（无参数、无装饰器）→ entry point `kind: 'main'`
- 若已有 `if __name__ == "__main__": main()` 守卫 → 不重复添加
- 适用场景：ROS 2 `def main(): rclpy.init(); rclpy.spin(node)` 这类无守卫入口

#### 项目级维度扩充

- `Project.pyLaunchFileCount`：后缀 `.launch.py` 文件数（O(N) 文件名过滤）
- `Project.pyNodeClassCount`：实际解析得到的 ROS 2 节点类数（builder 阶段聚合）
- `Project.pyDetected`：与 `kotlinDetected` / `phpDetected` 对齐的 boolean 标识
- `Project.language` 在 ROS 2 launch 文件存在时追加 `+ROS2Launch` 标记

#### Python 架构层语义扩充（`inferFileArchLayer`）

- `*.launch.py` → `deployment`（部署编排）
- `*/<pkg>/routers/...` / `routes/...` / `controllers/...` / `api/...` / `views/...` / `endpoints/...` → `presentation`
- `*/<pkg>/services/...` / `use_cases/...` / `domain/...` / `biz/...` → `service`
- `*/<pkg>/models/...` / `schemas/...` / `entities/...` / `dto/...` → `service`
- `*/<pkg>/repositories/...` / `dal/...` / `dao/...` / `infra/...` / `adapters/...` / `gateways/...` → `integration`
- ROS 2 节点惯例：`*/scripts/...` 或 `*_node.py` → `service`

#### 本体类型扩展

- `OBJECT_TYPES` 35 → 38：新增 `RosNode`（`rosnode:`，CodeUnit/L1）、`RosChannel`（`roschan:`，CodeUnit/L1）、`RosLaunch`（`roslaunch:`，EntryPoint/L2）
- `LINK_TYPES` 49 → 54：新增 `declaresChannel`（RosNode → RosChannel）、`launchesNode`（RosLaunch → RosNode，含 unresolved 标记）、`launchesLaunch`（RosLaunch → RosLaunch，IncludeLaunch 嵌套）、`declaresLaunchArg` / `executesProcess`（预留）
- `ONTOLOGY_META.abstractionLevels` / `categories` 同步登记
- `_meta.rosEdges` 边集合导出

#### 真实项目端到端验证（openamr-platform-sw）

| 维度                | 数量 |
| ------------------- | ---- |
| Python 源文件       | 65   |
| ROS 2 launch 文件   | 14   |
| RosNode 节点类      | 16   |
| RosChannel 通道     | 150  |
| 　　publisher       | 14   |
| 　　subscription    | 29   |
| 　　service         | 1    |
| 　　client          | 4    |
| 　　timer           | 4    |
| 　　action          | 2    |
| 　　parameter       | 96   |
| RosLaunch launch    | 14   |
| declaresChannel 边  | 150  |
| launchesNode 边     | 18（全 unresolved —— launch 启动的多为第三方包节点如 rplidar_ros，本仓库无对应类） |
| launchesLaunch 边   | 5    |

### 测试

- `test/pythonAnalyzer.test.mjs` 新增 9 用例：
  - parseClassFields 缩进感知（局部变量不再误报）
  - ROS 2 Node 类基类识别（Node / LifecycleNode / ComposableNode）
  - ROS 2 通信通道抽取（pub/sub/service/client/timer/parameter 七通道）
  - launch 文件 Node/Argument/Process/Include 抽取
  - IncludeLaunchDescription 嵌套
  - 非 launch 文件 `pythonLaunch: null` 兜底
  - def main() 入口点
  - def main() 与 `__main__` 守卫并存去重
  - 带参数 / 装饰器的 main 不误报
- `test/blueprintEngine.test.mjs` 同步更新：`OBJECT_TYPES 35 → 38` / `LINK_TYPES 49 → 54`
- `test/toolRegistry.test.mjs` 同步更新：get_schema / list_types 计数
- 全套测试 828/828 通过（+9 新增；原 819 全保留零修改）

## [0.38.0] - 2026-08-28

### 新增 `output docs`：分层上下文文档树（context-builder skill 的 CLI 支撑）

- `nice-aos output docs` 把本体快照渲染为「三明治结构」md 文档树到 `.nice-aos/context/`：L1 `index.md`（顶层索引，<2KB：定位/技术栈/架构分层占比/功能域 Top10）、汇总层 `architecture.md`（语义分层/健康度/循环依赖/依赖 Top）与 `modules.md`（模块地图按 archLayer 分组）、L2 `domains/<slug>.md`（领域索引，<1KB：画像 + 子模块 Top5 + 单元构成 + L3 链接）、L3 `domains/<slug>/{components,routes,state,services}.md`（<5KB/文件：组件/路由/Store+Hook/Service+接口+类清单，接口类按领域 fileIds 圈定）
- 粒度预算由生成器保证：成员清单 TopN 截断 + "仅列出前 N，共 X"注明；仅非空内容生成；无 Domain 仓库/纯脚本仓库自动降级
- 每个 md 带轻 frontmatter（title/layer/generated）；slug 保留 CJK（`slugify`：非字母数字折叠 `-`，同名领域追加序号去重）
- `tree.json` 目录树索引（借鉴 display-web knowledge-graph 的"离线索引 + 按需 fetch"模式；path 统一相对 context 根，规避其前缀不一致 bug）
- 选项：`--format all|md`（默认 all = md 树 + tree.json + docs.html；md 为纯 agent 模式）、`--output <dir>`（默认 `.nice-aos/context`）；注意 commander 限制——子命令与父命令（export）同名选项时传参落在父命令 opts，docs action 从两级 opts 取有效值
- 写盘后 `notifyServe` 广播 `docs:changed`（与 export 主命令一致，serve 运行中才生效）
- 生成逻辑为纯函数 `src/ontology/contextDocs.js`（`buildContextDocs`/`buildTree`/`slugify` 可直接单测；千文件级项目实测约 40ms）

### 新增 serve `/docs` 在线文档浏览

- `GET /docs` → 302 `/docs/`（自包含 docs.html 浏览器：左侧目录树侧边栏 + 搜索 + 正文 md 渲染 + frontmatter 徽章卡 + 右侧 TOC scroll-spy + 站内 .md 链接无刷新跳转 + `?doc=` 可分享链接 + 暗色主题 + 移动端抽屉侧栏；借鉴 display-web doc-viewer，md 解析器强化表格对齐/嵌套列表/任务列表/代码围栏）
- `GET /docs/<path>` 与 `GET /context/<path>`：文档目录静态服务（.md → text/markdown、.json → application/json），逐段 decodeURIComponent + resolve 前缀校验双重路径穿越防护（段级 `..` 400、越界 403）
- 新选项 `serve --docs-dir <path>`（默认 `<root>/.nice-aos/context`）；`/docs`、`/docs/`、`/context/` 前缀与 blueprint.html 同为公共静态端点（豁免 Bearer 鉴权）
- `respond()` 支持透传自定义响应头（Location 等）；首页就绪表与 `/api/status`（`docs` 字段）同步；`serveOpenApi.ENDPOINTS` 登记 `/docs` 与 `/context/{path}`（/api/status 端点清单与 openapi.json 自动派生）
- 生成器 `src/ontology/docsViewer.js`（`renderDocsHtml` 零依赖；内置 `window.__docsParser` 测试钩子供 Node 直测 md 解析器）

### skill 与文档

- `skills/nice-aos-context-builder/`：README.md 从 ASDM Toolset 设计稿重写为通用代码上下文生成 skill 文档（三明治架构/粒度黄金数字理念保留，落点改为 nice-aos 快照 + output docs 闭环）；新增 SKILL.md（npm files 已含 `skills/**/SKILL.md`，可随包分发；渐进式披露阅读顺序 + agent 行为规范）
- 根 README 命令表（output/serve 行）与 export 示例区补 `output docs`；`nice-aos-skill/SKILL.md` 的 export 节与 serve 端点清单同步

### 修复

- `src/ontology/builder.js`：修复工作区未提交改动（Shell/CMake/PKGBUILD/Nix 分析器接入）三元链尾部多余括号导致的 SyntaxError（该错误使所有 CLI 命令无法启动）
- `src/analyzers/projectScanner.js`：文件遍历白名单补 `CMakeLists.txt` / `PKGBUILD`（无扩展名约定文件，与各 analyzer 候选检测严格同名）——此前这两类文件根本进不了 `scan.files`，`cmakeFiles` / `pkgbuildFiles` 恒为空，CMake / Arch 维度在全量扫描中恒为 0（单文件 analyzeFile 不受影响，故单测未暴露）
- `src/analyzers/nixAnalyzer.js`：flake `outputs.packages` 只认 `packages = { ... }` 嵌套形态，补齐更常见的属性路径形态 `packages.<system> = <expr>`（含 `let ... in { ... }`，取值域内最后一个顶层配平 attrset）与 `packages.<system>.<name> = expr`；`inputs` 同理补扁平写法 `inputs.<name>.url / inputs.<name>.flake = ...`（原只认 `inputs = { ... }` 嵌套块）；统一点路径与嵌块两分支的 `hasFlakeAttr` 语义（= 是否显式声明 flake 属性）并修 url/flake 键序导致的重复条目
- 本体元模型补登记：`ArchPackageFunction`（`archfn:`，CodeUnit/L1）与 `NixInput`（`nixin:`，Environment/L2）此前已有对象产出但未入 `OBJECT_TYPES` / `ONTOLOGY_META` 层级范畴清单（破坏"快照对象必在元模型声明"不变量）；全量模式 dataMap 补挂此前已构建但遗漏的 `ArchPackageFunction` 数组；全量 `_meta.objectCounts` 补齐 15 个新类型计数、单文件 objectCounts 补 `ArchPackageFunction` / `NixInput`（两种模式口径对齐）

### 审核：边界与数据质量清理

- `serve` docs 静态服务补第三重防护：`fs.realpathSync` 解析 symlink 后再做 docs 目录前缀校验（词法 `..` 校验拦不住目录内 symlink 指向目录外的逃逸；docsDir 本身也 realpath 以吸收 /tmp→/private/tmp 类别名），测试补 leak.md symlink 逃逸 → 403 用例
- `cmakeAnalyzer`：删除从未被调用的 `contentWithLineNoiseRemoved` 死代码；`findCallEdges` 从半成品（只建空 Set，builder 未消费）改为真实实现——按 `endfunction()/endmacro()` 行界圈定函数体范围、把范围内对同文件 function/macro 的调用归属为调用边（去重；顶层直接调用不产生边）；`detectRisks` 去掉未用的 targets/sets/filePath 参数
- `builder.buildCMakeObjects`：CMakeFunction 调用边接入 `_meta.shellEdges.callsFunction`（与 Shell 共享 `callsFunction` 链接类型，from/to 为 CMakeFunction 对象 ID）
- `builder.buildShellScriptObjects`：`usesBuiltin` / `readsCliParam` 边从"函数 × 脚本级全量 builtin/param"的笛卡尔积近似改为精确边——依据 `shellScriptAnalyzer` 新增的每函数 `builtinNames`/`cmdletNames`（函数体内实际出现的外部命令/cmdlet）与 `cliParamNames`（Bash 按 `--name` 文本关联、PS 按 `$Name` 引用关联）；顶层调用不建边。e2e 实测 3 函数 × 8 builtin 场景由 24 条降为 3 条真实调用
- `pkgbuildAnalyzer`：合并两处重叠的 `pkgdir-root` 检查（`$pkgdir`/`${pkgdir}`/`pkgdir` 三种写法统一走一条规则，单函数至多记一条，消除重复风险条目）；清理 `findFunctions` 未用变量
- `cmakeAnalyzer.findOptionsFromContent`：description 仅按引号形态识别，`option(X ON)` 的无引号布尔默认值不再被误作 description；`option(NAME)` 无 description 无默认值、省略 description 的写法均可解析
- 清理无人使用的导出/参数：`shellScriptAnalyzer.isPowerShellCandidate`（导出但全库零引用）、`analyzeShellScript` 未用 `ext` 变量、`buildShellScriptObjects` 未用 `fileObj` 参数

### 测试

- 新增 `test/outputDocs.test.mjs`（5 用例：全链路文件与内容契约 + L1<2KB/L2<1KB/L3<5KB 预算断言、--format md 降级、--output/export 别名、纯函数空 dataMap/slug 去重/CJK、buildTree 排序与 size）
- 新增 `test/serveDocs.test.mjs`（4 用例：/docs 302 + 静态资源 Content-Type 契约 + 嵌套/中文路径、路径穿越防护（node:http 原始路径直发——fetch/WHATWG URL 会客户端规范化 `..`）、docs 缺失 404 降级 + --docs-dir、/api/status 与首页同步）
- 新增 `test/nixAnalyzer.test.mjs` 扁平 inputs 用例（`inputs.<name>.url` 写法与嵌套等价 + hasFlakeAttr 声明语义）
- 同步既有断言：`test/serve.test.mjs` endpoints 清单、`test/serveAuth.test.mjs` auth.public 清单补 `/docs`、`/docs/`；`test/blueprintEngine.test.mjs` 与 `test/toolRegistry.test.mjs` 元模型契约数字 20→35 类型 / 26→49 链接 / 6→7 范畴（v0.37.0 +Shell/CMake/PKGBUILD/Nix 维度）
- 全量 841 用例全部通过（含多语言 e2e 冒烟：.sh/.ps1/CMakeLists.txt/PKGBUILD/flake.nix 混合仓库 refreshRepo → output docs → serve /docs 全链路；本轮新增 cmake 调用边/option 形态、pkgbuild 去重、serve symlink 逃逸用例）

## [0.37.0] - 2026-08-28

存储层 Schema 演进（Phase B）：从 8 张表 → 10 张表，借鉴 asdm-aos 16 表双层架构选择性引入 3-4 张关键表。详细方案与不借鉴项决策见 `docs/plan/storage-schema-v0.37.md`（待补 ADR，本节先合入变更）。

### 新增 aos_type_properties 表（投影层 DDL 输入）

- 借鉴 asdm-aos `aos_type_properties`：决定 viewmodel 字段裁剪 / HTTP API 字段过滤 / JSON schema 生成
- 新表 4 字段：`type_name` / `key` / `label` / `wire_type`（`string|number|boolean|object|array|ref`）+ `storage_hint`（`promoted|jsonb`）+ `index_hint`（`none|btree|fulltext|vector`）
- FK → aos_types(type_name) ON DELETE CASCADE
- 种子化覆盖 20 个核心类型：Project / Module / SourceFile / Component / Hook / Store / Method / Interface / Class / Trait / Dependency / Domain / Route / Service / PropEdge / GmApiUsage / InjectionPoint / NetworkEndpoint / UserScript / ScriptFunction，共 61 条属性
- 防御性过滤：只入 aos_types 已注册类型的属性（防幽灵属性 / FK 违规）

### 新增 aos_import_jobs 表（步进游标落表）

- 借鉴 asdm-aos `aos_import_jobs`：增量扫描的崩溃恢复基础
- 字段：`job_id` / `job_kind`（`refreshRepo|analyzeFile|export`）/ `snapshot_id` / `steps` / `current_step` / `cursor`（JSON）/ `status`（`running|paused|completed|failed`）/ `error` / `started_at` / `updated_at`
- FK → aos_snapshots CASCADE
- `idx_aos_import_jobs_status` 索引（status + updated_at）
- v0.37 占位表：schema 在位，应用层 incrementalParser 接入推迟到 v0.38

### aos_objects 加 content_hash + pk_hash 双字段（跨快照对象去重基础设施）

- `content_hash TEXT`：sha256(props_json) — 跨 snapshot 共享（同对象一致）
- `pk_hash TEXT`：sha256(`default|default|${type}|${id}|${content_hash}`) — 借鉴 asdm-aos 决策 2b；不含 snapshot_id 以支持跨 snapshot 同对象去重
- 2 索引：`idx_aos_objects_content_hash` + `idx_aos_objects_pk_hash`（**非 UNIQUE**：v0.37 表 PK 仍按 (snapshot_id,type,id)，UNIQUE 由 v0.38 PK 化时天然保证）
- saveSnapshot 写入时计算两 hash；loadSnapshot / loadType / loadObject / queryWhere 读路径不变（hash 字段不泄漏到 DataMap）
- v0.31 老库 backfill：SELECT NULL content_hash/pk_hash 行 → 应用层计算 → UPDATE（500 行/批 + 事务）

### aos_link_types 扩 4 字段（公理声明）

- 新增字段：`label` / `src_type` / `tgt_type` / `cardinality`（`1|0..1|*|1..*`）
- 种子化所有 26 种 link_type 的公理（contains / imports / calls / extends / ... 端点类型约束 + 基数）
- v0.37 暂不强制约束（按 Q4 决策：加字段存元数据，不接 linkMeta 校验）

### 迁移与初始化路径

- `db.js` 初始化流程重构：始终走 `applyPendingMigrations()` 路径（替代 v0.31 的"写 SCHEMA_VERSION 直接走"）
- `db.js` 以静态 import 引入 `migrate.js`（require(esm) 需 Node ≥ 20.19，与 engines >= 18 冲突；migrate.js 无反向依赖，无循环引用）
- `migrate.js` v2 块：ALTER TABLE aos_objects ADD COLUMN pk_hash + 4 索引 + 2 新表 + backfill
- v2 的 cardinality ALTER 带 CHECK 约束，与 schema.sql 新库口径一致（SQLite ADD COLUMN 支持 CHECK；老库已有行由 DEFAULT '*' 填充满足约束）
- `aos_link_types.src_type / tgt_type` 端点取值约定：类型名或 category 名（Container/CodeUnit/Script/AuditFact），已写入 schema.sql 与 seed.js 注释
- 日志走 stderr 不污染 stdout JSON（避免 ask --json 等命令被破坏）
- 全新安装：账本 v1 + v2 双行（v1 marker + v2 实际 schema 标记）
- v0.31 库升级：账本 v1 → 追加 v2 + 自动 backfill content_hash / pk_hash

### 文档

- storage 子命令 status 输出新增 `typeProperties` 计数
- storage init 显示 `本目录 X 个类型 / Y 个链接类型 / Z 个属性定义`
- `aos_link_types` 公理约定（`docs/adr/0007-link-types-axioms.md`）——待补

### 测试

- 新增 `test/storage/schema.test.mjs` 11 个测试（全新库 / v0.31 升级 / backfill 跨 snapshot 一致 / type_properties 种子化 / link_types 公理 / import_jobs CRUD / saveSnapshot hash 落地 / 二次写 hash 复用 / hash 公式顺序敏感 / getStatus 报告 / loadSnapshot 不泄漏 hash 字段）
- 现有 `test/storage/sqliteSnapshot.test.mjs` 调整：表数 8→10 + 账本 v1→v2 + 校验 10 张表名
- 修 `test/ask.test.mjs` 超时降级用例的时序抖动：600ms 预算在全量并发下连 opencode 应答也超时，提到 2000ms（codebuddy 挂起 5s 仍稳定触发降级路径）
- **811/811 全通过**（修复上述时序抖动后）

### 风险与回退

- v0.37 不破坏 v0.31 数据（只加列 + 索引 + 2 表；不回填会保留 NULL 字段；saveSnapshot 重写覆盖）
- 暂无 rollback 命令（v0.38 加 storage rollback --to v37）

## [0.36.1] - 2026-08-28

v0.36.0 后续候选高价值项闭环（候选 0 / 1 / 2 / 4）。

### 候选 0：`action analyzeFile` 单文件模式接入 `.kt/.kts/.php` analyzer 分发

- `buildSingleFileOntology` 分发链补 `.kt` / `.kts` → kotlinAnalyzer、`.php` → phpAnalyzer 分支（此前回落 tsAnalyzer 产生错误解析）
- 修复单文件模式 `cls.implementsNames.map` 空指针（Kotlin facts 无 implementsNames 字段；对齐全扫描的 `?? []` 空值安全写法）
- 单文件模式同步接入 PHP DAO 常量解析（本文件内 `define()` 可见；跨文件常量表在全仓库扫描解析）

### 候选 2：`phpDetected` / `kotlinDetected` 暴露

- `scanProject` 返回对象新增 `phpDetected` / `kotlinDetected`（与 `goDetected` / `flutterDetected` 对齐）
- Project 快照对象新增 `goDetected` / `phpDetected` / `kotlinDetected` 三字段（此前 flutter/tauri/electron 有而 go/php/kotlin 缺）

### 候选 1：PHP / Kotlin 内部 import 解析

- **新模块** `src/analyzers/phpKotlinImportResolver.js`（输出契约与 importResolver / goResolver 对齐：internal/external/unresolved）
- PHP：composer.json autoload **PSR-4 / PSR-0 前缀映射**（最长前缀优先，目录归一）→ 目标 `.php` 文件存在则 internal；无 composer 映射时按**全仓库声明限定名**兜底（namespace + class/interface/trait，小写归一匹配，覆盖 zentaopms 遗留库）；未命中按命名空间首段归并 external（`ecosystem: php`）
- Kotlin：**声明 package + 限定类名**精确匹配 → **源码路径后缀匹配**（任意源码根下 `com/example/Client.kt`，等效覆盖自定义 sourceSets srcDir，无需解析 build.gradle.kts）→ 未命中按首段归并 external（`ecosystem: kotlin`）
- 通配 `import a.b.*` 关联整包文件（imports 边多目标，与 Go package 导入同构；`file` 指向包内首个文件兼容单文件消费者）
- builder 导入边消费分支支持 `files` 数组（此前仅单 `file`）；`tf === relPath` 自引用跳过

### 候选 4：PHP DAO 链抽取（Phase-2 → 落地）

- `phpAnalyzer` 方法体语句级静态识别 zentaopms `dao` 链：`select(...)->from(X)` → SELECT、`update(X)` / `insert(X)` / `replace(X)`（内联表参）→ UPDATE/INSERT、`delete()->from(X)` → DELETE、`leftJoin/innerJoin/rightJoin(X)` → JOIN
- 表参三态：字符串字面量（静态）/ `TABLE_X` 常量（dynamic 占位）/ `$var`（dynamic 不参与链接）
- **常量解析**：全仓库 `define('TABLE_X', 'zt_x')` 值提取（兼容反引号值 `` '`zt_bug`' ``）→ builder 后置解析 pass 把 `Method.sqlQueries` 中 dynamic 常量改写为真实表名并清 dynamic（单文件模式仅本文件 define 可见）
- `Method.sqlQueries` 挂载扩展：class 方法 / trait 方法实体同步拷贝（此前仅模块级函数通道）；mapsToTable / mappedFromCode 代码↔表链接自动消费（含 JOIN 表）

### 测试

- `test/singleFileKotlinPhp.test.mjs` —— 4 tests（.php/.kt/.kts 单文件分发 + trait 单文件回填 + suspend/isDataModel 字段）
- `test/phpKotlinImportResolve.test.mjs` —— 4 tests（PSR-4 前缀归一 / PHP internal+external / Kotlin 限定名+通配+外部 / e2e 文件边）
- `test/phpDaoChain.test.mjs` —— 3 tests（DAO 链五形态提取 / defines 反引号剥离 / e2e 常量解析 + mapsToTable/mappedFromCode 链接）
- `test/kotlinPhpIntegration.test.mjs` 补 `phpDetected` / `kotlinDetected` / `goDetected` Project 字段断言
- **修复 v0.35.1 遗留的 10 个 viewer 测试失败**：`document` stub 缺 `addEventListener`（v0.35.1 邻接聚焦交互新增的顶层调用），按 dbViewer/serviceViewer 后写的 stub 样式补齐 5 个文件 8 个站点
- `test/mcpCli.test.mjs`：固定 1.5s 等待改轮询 stderr "就绪"（上限 8s），消除全量并行负载下的时序抖动

## [0.36.0] - 2026-08-27

### 新增 PHP / Kotlin 分析器 v0.36.0（ADR 0006）

完整方案对比与不借鉴项决策见 `docs/adr/0006-php-kotlin-analyzer.md`。

#### P0：新 PHP 分析器（zentaopms / Laravel / Symfony）

- **新模块** `src/analyzers/phpAnalyzer.js` —— 沿用 tsAnalyzer/rustAnalyzer 的"轻量状态机 + 等长噪声剥离"范式，零运行时依赖
- 实体映射：class（含 `extends model` → `isDataModel: true` / `extends control` → `isController: true` / abstract / final / readonly 修饰）、interface（含 extends 多继承）、trait（独立对象类型）、method（含 `__construct` 构造器、`static` 修饰）、property（`public $name` / `public string $name`）
- `use Trait1, Trait2;`（class 体内）→ `usesTraits` 数组
- `use ... as` 别名 + `use Baz\{ Qux, Quux as Q };` 群组导入解析
- `namespace Foo\Bar` → `moduleName: 'Foo.Bar'`（反斜杠归一点号）
- 字符串/注释噪声剥离（DAO 链中的 `'<类定义>'` 字符串不产生幽灵实体）
- `module/<x>/control.php` 内 public 非构造方法 → Route（`routeType: 'php'`，`path: '/<module>-<method>'`）
- API 边界：`sqlQueries` / `crossModuleImports` 在 v1 留空数组契约（DAO 链抽取 Phase-2 接入）

#### P0：新 Kotlin 分析器（Android / JVM / KMP）

- **新模块** `src/analyzers/kotlinAnalyzer.js` —— 与 PHP analyzer 同范式独立共存
- 实体映射：class（含 data_class / sealed_class / object 单例 / enum_class / companion_object 内嵌 5 种变体）、interface、fun（含 `suspend fun` → `isAsync: true` 标注 + `inline` / `operator` / `infix` 修饰）、val|var Property
- supertype 列表解析（含点号嵌套 `Call.Factory`）
- `import foo.Bar` / `import foo.Bar as Baz` / `import foo.*` 解析
- 噪声剥离覆盖三引号原始字符串（含 `${}` 插值）、字符字面量

#### P0：本体扩展：Trait 对象类型

- 新对象类型 `Trait`（`trait:` 前缀，L1/CodeUnit）—— 描述为"方法复用单元（PHP trait；同一命名空间，可被多个 Class `use` 注入方法）"
- 新链接类型 `usesTrait`（class → trait）与 `usedByTrait`（trait → class）—— 双向
- `dataMap.Trait` 数组、`Class.usesTraitIds` / `Class.usesTraits` 字段、`Trait.usedByIds` / `Trait.methodIds` 字段
- `OBJECT_TYPES` 19→20，`LINK_TYPES` 24→26
- `blueprint.js` 的 `contains(file:)` 同步包含 trait
- `viewModel` 已支持 `kind: 'trait'` 配色（cyan 描边，v0.34 之前已就绪）

#### P0：builder.js 集成

- analyzer 分发嵌套三元扩 `.kt` / `.kts` / `.php` 分支
- import 解析：PHP / Kotlin 按命名空间首段归并为 external（避免 `Foo\Bar` 被 TS resolver 误判为 npm 包）
- trait 链接双向回填：builder 阶段用 `usesTraits` 名字查全仓库 `traitByName` 表，写 `usesTraitIds` / `usedByIds`
- PHP 路由生成：handler Method 关联到 control 类同名方法
- 失败 `catch` 块补 `traits: []` / `routes: []` 兜底
- `objectCounts.Trait` 记账同步
- `buildSingleFileOntology` 镜像 trait 处理（单文件模式）

#### P0：项目扫描 & 框架检测

- `projectScanner.js` 扩 `.kt` / `.kts` / `.php` 进 `SOURCE_EXTENSIONS`
- `detectFramework` 新增 `php` / `kotlin` 早返回分支
- `phpDetected` 判定：`composer.json` 存在 + `.php` 文件数 > 0
- `kotlinDetected` 判定：`build.gradle.kts` 或 `settings.gradle.kts` 存在 + `.kt` + `.kts` 文件数 > 0
- `Project.kotlinFileCount` / `phpFileCount` 字段加（与 `pyFileCount` 平级）
- `Project.language` 拼接 +Kotlin +PHP

#### P1：架构层语义

`semantics.js#inferFileArchLayer` 新增 PHP / Kotlin 分层规则：

- PHP（zentaopms 惯例）：`control.php` → presentation；`(model|config).php` → service；`view|ui|lang/` → presentation；`dao|dal|repositories|services|models/` → service；`framework/` → shared
- Kotlin（Android 惯例）：`*(Activity|Fragment|Screen|Page).kt` → presentation；`*(Repository|UseCase|Service).kt` → service；`*ViewModel.kt` → presentation；`di|data|datasource|db|network|api/` → service

#### P0：测试

- `test/phpAnalyzer.test.mjs` —— 11 tests（实体/方法/属性/trait/namespace/use 群组/control 路由/接口/抽象类/死代码契约/DAO 链空契约）
- `test/kotlinAnalyzer.test.mjs` —— 12 tests（class 变体/supertype/fun+suspend/import/三引号字符串/死代码/enum/object 单例等）
- `test/kotlinPhpIntegration.test.mjs` —— 1 e2e test（fixture 项目 → buildOntologyData 全链路，含 trait 双向链接 / 路由 / 架构层 / objectCounts 验证）
- `test/blueprintEngine.test.mjs` —— 同步更新到 20 OBJECT_TYPES / 26 LINK_TYPES
- `test/toolRegistry.test.mjs` —— 计数断言收紧为精确 20/26（原 `>= 19`/`>= 20` 下界）

#### 文档与元数据同步

- README：语言清单与 intro、核心能力速览（20 对象 / 26 链接）、taxonomy L1 补 Trait、对象表新增 Trait 行（Class/Method/Route/Project 行补 PHP/Kotlin 字段枚举）、链接表补 usesTrait/usedByTrait 与 mapsToTable/mappedFromCode、新增「PHP 适配」「Kotlin 适配」章节、框架检测/导入解析补 composer.json 与 build.gradle.kts 判定、已知限制补 PHP/Kotlin 条目、MCP `list_types` 描述 19→20
- `toolRegistry.js`：get_schema / list_types / query_objects 工具描述计数 19→20、24+→26（MCP 客户端可见的运行时文案）
- `skills/nice-aos-skill/SKILL.md`：intro 语言清单、对象/关系计数（18→20、21→26）、taxonomy L1 补 Trait、MCP 工具表计数
- `package.json`：description 补 PHP/Kotlin 能力句，keywords 增 php / kotlin / zentaopms / android / trait

#### 后续候选（v0.36+）

0. `action analyzeFile` 单文件模式接入 `.kt/.kts/.php` analyzer 分发（当前回落 tsAnalyzer）

1. PHP/Kotlin PSR-4 内部 import 解析（读 composer.json autoload / build.gradle.kts sourceSets 区分内/外）
2. `phpDetected` / `kotlinDetected` 暴露到 scan 结果（与 `goDetected` / `flutterDetected` 对齐）
3. Kotlin receiver-constrained 调用图（借鉴 GitNexus P3）
4. PHP DAO 链抽取（Phase-2，复用 `pythonAnalyzer.sqlQueries` 通道）
5. PHP `lang/` 翻译资源实体（i18n 覆盖度图谱）

## [0.35.1] - 2026-08-27

### 借鉴 GitNexus：图谱协议层 v0.35.1（ADR 0005）

参考 `/Users/healer2027/workprojetcs/nice-aos/.nice-aos/analysis/gitnexus-graph-borrow-analysis.md`，4 项 GitNexus 范式增量落地。完整方案对比与不借鉴项决策见 `docs/adr/0005-gitnexus-borrowed-capabilities.md`。

#### P1：链接元数据（edge metadata）

- **新模块** `src/ontology/linkMeta.js` —— 把 `link()` 增强为 `linkWithMeta2(ctx, linkType, srcId) → [{id, confidence, reason}]`
- 元数据写入约定：源对象上 `<linkType>Meta` 数组与 `*Ids` 一一对应；缺省时降级为 `confidence: 1.0, reason: 'direct'`
- 模糊推导边的预设（与 builder.js 当前解析路径对齐）：
  - `vue-global-fallback` 0.6（renders 经 Vue.component 全局注册兜底）
  - `vue-same-file-fallback` 0.5（renders 经同文件兜底）
  - `auto-imported` 0.7（usesStore 经 unplugin-auto-import 隐式调用）
  - `missing-source` 0.4（prop 边源/目标组件缺失）
- 新增 `linkBfsWithMeta(ctx, linkType, srcId, depth)`：按 hop 分层 + `pathConfidence` 取路径最低值 + cycle 防爆
- 完全向后兼容：现有 `link(linkType, srcId)` 不变

#### P1：MCP epistemic 信封（epistemic envelope）

借鉴 GitNexus `src/mcp/tools.ts` 的 `epistemic: 'exact' | 'lower-bound'` 协议：

- **traverse_links 响应**：新增 `_meta: { epistemic, confidence, causes, count, at, ... }`；可选 `withMeta: true` 附加 `edges: [{id, confidence, reason}]`；`depth > 1` 时附加 `byDepth: [{depth, count, edges}]`
- **get_node 找不到**：返回 `_meta.ambiguity: { queried, candidates: [{id, name, _type, score}] }`（Levenshtein + prefix + 子串打分）
- **query_objects 歧义名（~ 多匹配）**：返回 `_meta.ambiguity: { queriedName, distinctNames, candidates: [{name, count, sampleId, relevance}] }`；唯一匹配时不附 `_meta`（与原行为一致）
- **get_health**：暴露 `_meta.resolutionStats`（向后兼容，无则 `null`）

#### P2：解析覆盖度记账（resolution-outcome）

借鉴 GitNexus `src/core/ingestion/scope-resolution/passes/resolution-outcome.ts`：

- builder.js 在 `_meta.resolutionStats` 写入：
  - `totalImportAttempts` / `totalResolvedImports` / `unresolvedImportsCount`
  - `unresolvedDynamicImportsCount`（defineAsyncComponent / React.lazy 解析失败）
  - `vueGlobalFallbackCount` / `vueSameFileFallbackCount`（renders 模糊兜底）
  - `autoImportedUsesStoreCount`（隐式 store 调用）
  - `matchedRouteCount` / `unmatchedFrontendCallsCount`（前后端路由匹配）
- 派生指标：`importResolutionRate`（解析成功率） + `fuzzyLinkCount`（所有模糊边计数）
- "已知限制"从文档 → 数据，agent 一眼看到图谱完整度

#### P2：蓝图邻接聚焦交互（reducer 范式）

借鉴 GitNexus WebGL Sigma 3 的邻接衰减着色，零依赖 SVG 实现：

- viewer.js CSS 增强：选中节点 → 邻接边 stroke-width ×1.5 + drop-shadow + 邻接节点描边 3.5px + 阴影；非邻接压暗至 0.18 alpha / 边 0.06 alpha
- 已有 `cgSetFocus` 函数 + Esc 键 + "清除聚焦"按钮三种解除方式
- viewmodel 暴露 `renderBudgets: { moduleGraphNodeCap, componentGraphNodeCap, storeGraphNodeCap, graphEdgeCap, entityNodeCap, entityTableCap }`；超 cap 时 UI 显示 `<span class="warn">节点已达渲染上限 N</span>`（借鉴 ~25K 节点 / ~50K 边悬崖阈值熔断）

### 测试

- 新增 `test/linkMeta.test.mjs` 16 tests：linkWithMeta2 直连边/模糊边/缺省降级/'links' 聚合/未知 linkType + linkBfsWithMeta byDepth 分层/cycle 防爆/pathConfidence + epistemic 信封 5 路径（traverse_links 成功/源不存在/withMeta + get_node 找不到 + query_objects 歧义/精确 + get_health 暴露 resolutionStats）
- 回归：114 tests pass（toolRegistry 24 + linkMeta 16 + blueprintActions 31 + blueprintEngine 14 + output 29）

## [0.35.0] - 2026-08-27

### 技术债清偿（ADR 0002 审核报告 P0/P1/P2 全量闭环，自扫描/审核驱动）

#### E-3（P1 核心）：refreshRepo / analyzeFile 真实实现 + serve /action 端点

- **引擎动作真实现**：`createBlueprintV2` 的 refreshRepo / analyzeFile 由"ok:false 指引 stub"升级为真实执行——refreshRepo 走项目根检测（detectProjectRoot，优先级与 action.js 一致）+ 全量重扫 + 快照落盘；analyzeFile 单文件本体分析并输出对象统计。核心逻辑收敛至新增 `src/ontology/actionOps.js`（builder 唯一静态加载点；落盘策略可注入 saveTo）；blueprint.js 动态 import 加载，规避 ontology ↔ builder 静态循环依赖
- **serve 新增 `POST /action`**：蓝图 UI 动作卡片提交端点打通（此前前端 `fetch('/action')` 直接 404）。markReviewed/addNote 写 JSON 快照 + SQLite overlay 双写（no-snapshot 回退镜像，语义同 action.js:149-195）；refreshRepo 重扫后直接落到本服务数据源目录（saveTo=snapPath），运行期实时探测立即生效；analyzeFile 纯只读。未知动作/缺参/对象不存在返回结构化 400；角色分级 `minRoleFor('/action')→write`；`ENDPOINTS` 登记，`/openapi.json` 与 `/api/status` 同步暴露
- **引擎异步契约**：`blueprintEngine.action()` 支持异步 actionImpl——同步 impl 返回值保持原样同步返回（完全向后兼容）；返回 thenable 时才收敛为 Promise 且 rejection 统一映射 `{ok:false}`

#### E-2：动作定义单源抽取

- 新增 `src/ontology/actionDefs.js` 纯定义模块（`ACTION_DEFS` + `getActionsForType`），按 roadmap 原方案完成"定义/渲染"分离；blueprintActions.js 保留 re-export 兼容既有导入与"同一引用"防漂移测试断言；blueprint.js / viewer.js 取数改走新单源

#### E-5 / E-4 收尾

- incrementalParser.js 头注释与类 JSDoc 残留 "LRU" 表述修正为"FIFO 容量淘汰简化实现（非严格 LRU）"，测试名同步更新
- viewer.js 前端 `esc()` 补齐单引号转义（与 blueprintActions escapeHtml 对齐）

#### P2 回归测试补位（E-6 深拷贝 / E-10 软链 / E-11 路径字段已在早前版本实现，本次补测试锁定行为）

- blueprintEngine.test.mjs +4：嵌套深拷贝防写穿（E-6）、同步/异步 actionImpl 契约、analyzeFile/refreshRepo 真实动作沙箱测试（临时目录快照重定向）
- projectRootDetector.test.mjs +2：祖先软链回环终止性、多层软链包裹解析（E-10）
- incrementalParser.test.mjs +1：sourceFile/file/source 扩展路径字段命中清除（E-11）
- serve.test.mjs +3：POST /action 集成（未知动作 400 / 写盘回读含 addNote 累加 / 只读分析）

### 测试

- 测试基线 739 → **749**，新增用例全部通过。剩余偶发失败均为外部 CLI / spawn 时序敏感用例（ask 端到端降级链、mcpCli 握手、duplicates 超时），与本节改动无关（未修改的 HEAD 上同样复现）

## [0.34.0] - 2026-08-27

### 新增（output 命令）

- **`--merge <paths...>` 多快照合并**（out-3）：monorepo / 多子项目各一份快照合并出总览；冲突策略 `--merge-strategy first-wins`（默认，计数上报）/ `rename`（后到冲突对象重前缀 `<source>:<id>` + `*Id/*Ids` 引用字段泛键回填，收录时统一应用映射）；非首源 Project 折叠进 `_meta.mergedProjects`；合并发生在 dataMap 层 → 四格式自然复用。新增 `src/ontology/merge.js`
- **`--include <types>` / `--exclude <types>` 类型过滤**（out-4）：作用于全部导出格式；未知类型 fail 并列出可用类型；过滤后 `_meta.objectCounts` 自动对齐
- **主题 API + CLI**（out-5）：`themes/index.js` 暴露 `registerTheme(name, def)` 运行时注册与用户目录懒加载（`NICE_AOS_THEMES_DIR` 可覆盖）；新增 `output theme add/list/remove` 子命令，主题落盘 `~/.nice-aos/themes/<name>.json`
- **`--format all`**（out-6）：一条命令产出 `<base>.md / <base>.html / <base>.viewmodel.json` 三件套（需 `--output`）

### 新增（serve 服务）

- **`GET /openapi.json`**（srv-3）：零依赖生成 OpenAPI 3.0.3 spec；端点清单单一事实源 `serveOpenApi.ENDPOINTS`（`/api/status.endpoints` 同源派生）。新增 `src/cli/commands/serveOpenApi.js`
- **`--rate-limit <max>` / `--window-ms <ms>`**（srv-4）：滑动窗口、IP 维度限流，超限 429 + `Retry-After`；每 IP 时间戳数组 + maxIps 容量驱逐防 OOM；先于鉴权执行防爆破绕过；观测端点 `/api/rate-limit`
- **`POST /api/ask`**（srv-5 / x-4）：serve 内直连已配置的 OpenAI 兼容模型服务回答问题（不依赖本地 AI CLI）；支持 `session` 续聊与 `save` 落盘；未配置模型时返回 503 配置指引；上下文两路取数与 GET /api/ask/context 同源
- **端点分级 read/write/admin**（srv-6）：token 支持 `secret:role` 形态与多值 `--token s1:read s2:write`；env `NICE_AOS_SERVE_TOKENS="s1,s2:read"` 优先；单 `--token` 默认 admin 向后兼容；角色校验表 `minRoleFor(method,url)`（POST /api/ask 与 /internal/broadcast 需 write）

### 新增（跨命令协同）

- **`ask --tool-call` / `--max-tool-steps`**（x-1）：自治工具循环正式版——prompt 注入 query/link/output 参数说明 → 模型输出 ```aos-tool``` fenced JSON 块 → 子进程执行真实 sub-command → 结果截断 ≤8KB 回填再生成，≤5 步收敛 + 上限强制收尾；仅模型服务通道（CLI agent 提示改用 `--tools`）。新增 `src/cli/commands/toolLoop.js`
- **output 完成后通知 serve 广播**（x-3）：serve 启动写 `<dataDir>/serve-runtime.json {pid,port}`（退出清理、陈旧 pid 按 ESRCH/EINVAL 判死）；export 写文件后经回环限定 `POST /internal/broadcast` 触发 WS 推送 `report:changed {paths,ts}`；serve 未运行静默跳过。新增 `src/cli/commands/notifyServe.js`

### 内部

- `serveAuth.js` 扩展 `ROLES / parseTokens / authorizeRole / minRoleFor / extractToken`；`serveWebSocket` 返回值暴露 `broadcast()`；CORS Allow-Methods 增加 POST
- 测试基线 699 → 739：新增 `exportUpgrade.test.mjs`(11) / `exportMerge.test.mjs`(4) / `serveUpgrade.test.mjs`(16) / `toolLoop.test.mjs`(9)；既有 serve 契约断言随端点清单/横幅新形态同步

## [0.33.1] - 2026-08-27

- **Agent 解析翻转 + 注册表扩展**（`agentRunner.js`）：`--agent auto` 时已配置的自定义模型服务成为首选通道（原为 CLI 优先、模型兜底），其后按注册表序探测 CLI；注册表从 2 项扩展至 8 项（codebuddy / opencode / **trae / qoder / claude / codex / qwen / aider**，后六者 experimental 标记）；报错信息给出可用注册名与接入指引
- **`--agent-cmd "<bin> [args...] {prompt}"`**：任意自定义 AI CLI 零代码接入——含 `{prompt}` 占位符按位注入（可多处），缺省追加末尾；未知名字不再死路一条
- **`--tools` 自治深查**（sub-tool P1）：隐含后台 serve，把 `query`/`link`/`export` CLI 用法与 HTTP 端点指引注入 prompt，AI 按需自行取证并要求引用对象 id
- **`--since <ref> [--staged]` 跨快照 diff 问答**（P2）：复用 gitDiff 增量解析，变更文件（≤40 行列示）与涉及本体对象（按类型分组 ≤15/类）折叠为 prompt 独立段落；与 export --since 同源同参（findGitRoot 收敛共享）
- **`--save [path]` 回答落盘**（P2）：自包含 Markdown 存档（元信息头含回答方/降级链/会话/增量范围/耗时），缺省写 `<snapshotDir>/answers/ask-<时间戳>.md`
- **`ask eval` 评测 harness**（P2）：JSONL 用例集（mustInclude 字符串或数组任选其一 / mustExclude）+ 纯函数评分，输出通过率与逐例报告；`--out` 落盘；存在失败 exitCode=1；doAsk 核心抽取复用（多用例共享一次上下文构建）

### 内部

- `gitDiff.findGitRoot` 上移共享（原 export.js 本地实现删除）；新增模块 `askSave.js` / `askDiffContext.js` / `askEval.js`
- 测试：新增 `test/askUpgrade.test.mjs` 15 例（注册表/模板编译/评分器/JSONL 解析/落盘/diff 上下文含临时 git 仓库 e2e）；既有 4 例按新语义更新

## [0.33.1] - 2026-08-27

### 修复（自扫描驱动：用 nice-aos 扫描自身仓库发现的设计缺陷）

- **Node CLI 形态识别**（`projectScanner.js`）：`package.json` 声明 `bin` 入口且无前端框架信号时判 `framework=node-cli`（标签"Node.js CLI 工具"），先于 userscript 判定——CLI 仓库混入油猴脚本（如 `contrib/`）不再劫持整个仓库画像；语言判定区分 TS/JS，纯 JS 工程不再误标 TypeScript
- **bin/main 入口识别**（`projectScanner.js` + `builder.js`）：`package.json` `bin`/`main` 指向且位于扫描根内的文件标 `isEntry`——自扫描中 CLI 真实入口 `src/cli/index.js` 此前被误判孤儿文件
- **外部测试引用豁免**（`projectScanner.collectExternalTestImports` + `builder.js`）：根级 `test/tests/__tests__/spec` 目录的 import 不在扫描范围，词法提取具名/文件级引用作为死代码判定证据（`relPath#name`）——孤儿候选、死导出、导出实体三类判定同时豁免；自扫描验证死导出候选 42 → 32
- **IO 扫描字符串打码**（`ioRegistry.maskStringsAndComments`）：匹配前把 `'...'` / `"..."` / 模板字面量内容与注释替换为等长空白（`${}` 插值段保留为代码），注册表 callee 名出现在字符串常量/代码生成模板中的误报消除——自扫描 3 个 fetch 高危误报清零；`callText`/`argsText` 回取原文保持展示保真
- **快照持久化收敛**（新增 `storage/snapshotFileKit.js`）：db / deploy / planning / service / overview 五个领域模块的 `getXxxSnapshotDir / load / save / has / fileHash` 模板复制（duplicates 检测 13 组中的 5 组）收敛为共享 kit，各模块保留原导出名薄封装

### 文档

- **README 介绍重写**：一句话定位语 + 「30 秒上手」前置 + 「四个入口」命令表精简 + 「核心能力速览」六要点；详细参考章节零改动，场景类子节收拢至「场景指南」

## [0.33.0] - 2026-08-26

三大核心命令升级（输入 / 输出 / 服务）+ code-graph-rag 范式落地（MCP / 重复代码 / 死代码 BFS / IO 注册表 / 模板），详见 `docs/plan/aos-three-core-roadmap.md` 与 `docs/adr/0004-code-graph-rag-reusable-patterns.md`。本节为 v0.32.0 → v0.33.0 的统一变更记录（原 [0.32.0] release commit `68070d2` 中未含 CHANGELOG 条目，本次合并为单节）。

### 新增

借鉴 [code-graph-rag](https://github.com/vitali87/code-graph-rag) 的 2 大工程模式（MCP server + AST fingerprint 重复代码检测），把 nice-aos 从"扫描器"扩展为"AI agent 友好的开发工具"。

#### ask 流式 + 多轮会话

- **`ask --stream`** 流式输出（仅 `--agent api`）：OpenAI 兼容 SSE token 透传；CLI agent（codebuddy / opencode）不支持流式自动降级到同步路径
- **`ask --session <id>` 多轮会话**：JSONL 存储于 `$NICE_AOS_CONFIG_DIR/sessions/<id>.jsonl`；`--session-max-turns N` 限制 prompt 携带的历史轮数
- 新增 `src/cli/commands/openaiCompat.js` 的 `invokeApiChatStream`（response.body.getReader + SSE `data:` 解析 + `[DONE]` 终止）
- 新增 `src/cli/commands/askSession.js`（loadSession / appendTurn / formatHistory；损坏行容错 + 路径穿越防护）

#### output 别名 + 增量 + 模板

- **`output`** 作为 `export` 的 commander 顶层别名；`src/cli/index.js` `exportCommand.alias('output')`
- **`export --since <ref>`** 增量导出（`src/cli/commands/export.js`）：git diff 解析 → 末尾追加"增量变更摘要"节（变更文件清单 + 按类型分组的涉及对象）
- **`export --since --staged`** 仅列已暂存变更（pre-commit 体检）
- **`export --template <path>`** 自定义 Markdown 模板（`src/ontology/template.js`）：`{{Project.name}}` / `{{stats.Component}}` / `{{ObjectCounts.Module}}` / `{{Health.complexity.avg}}` 占位符

#### serve 鉴权 + WebSocket 推送

- **`serve --token <secret>`** Bearer 鉴权（`src/cli/commands/serve.js`）：保护 `/api/*` + `/ws/snapshot`；`Authorization: Bearer` 头或 `?token=` query 都可；`timingSafeEqual` SHA-256 防 timing attack
- **`serve --ws-interval <ms>`** WebSocket 推送（`src/cli/commands/serveWebSocket.js`）：`/ws/snapshot` 单定时器 mtime 轮询；事件含 `hello` / `snapshot:changed` / `blueprint:changed` / `pong`
- **`NICE_AOS_SERVE_TOKEN` 环境变量覆盖 `--token`**（CI 场景）
- Auth 实现抽到 `src/cli/commands/serveAuth.js`（checkAuth + timingSafeEqualStr 单独可单测）

#### MCP server + tool 工具集（精简后 7 个）

- **`nice-aos mcp`** 子命令（`src/cli/commands/mcp.js`）：stdio 传输，启动 fail-fast（快照缺失立即报错）；优雅关闭 SIGTERM / SIGINT；启动日志走 stderr 不污染 JSON-RPC stdout
- **`nice-aos mcp --root /abs/path`** 接入 Claude Code：`claude mcp add nice-aos -- npx nice-aos mcp --root /abs/path`
- 工具注册表（`src/ontology/toolRegistry.js`）：`get_stats` / `get_schema` / `list_types` / `query_objects` / `get_node` / `traverse_links` / `get_health`（v0.33.0 精简 `detect_dead_code` / `query_graph`——前者与 deadcode CLI 子命令重叠，后者深度 ≤ 3 + 节点 ≤ 1000 价值密度低，由 traverse_links + deadcode 子命令替代）
- byId 索引（O(1) 查询）；handler try/catch 包装，错误用 `{ok: false, error}` 返回值表达
- 低层 `Server` + `setRequestHandler`（避开 McpServer.tool() 强制 zod 依赖；inputSchema 用 JSON Schema 形态）

#### 重复代码 + 死代码 + IO 注册表

- **`duplicates` 子命令**（`src/cli/commands/duplicates.js`）：text-based 规范化（去注释/字面量/标识符，保留关键字 + this/super/literal + JSX 组件 □C）→ SHA-256 → group-by（O(n)）
- **`deadcode` 子命令**（`src/cli/commands/deadcode.js`）：entry-point BFS 算法（roots = entry files / Component / test methods / 显式 --entry-point）+ 反向边（method → owner / file → 内含组件）；仅报 exported class（method / interface 留 v0.34.0）
- **`deadcode --write-back`** 把 dead 信息写回 snapshot 的 `deadCandidate` 字段（深拷贝保护 readonly JSON 结果）
- **`io` 子命令**（`src/cli/commands/io.js`）：数据驱动 IO_SINKS 注册表 + 静态文本匹配（regex）+ shadow check（local var 不污染全局）
- IO 注册表（`src/ontology/ioRegistry.js`）：30+ 油猴 GM_* / 10+ 浏览器 JS（fetch / localStorage / cookie / eval / innerHTML …）；5 种 ResourceKind（STORAGE/NETWORK/DOM/STDOUT/SCRIPT）+ 5 种 DANGER_LEVELS
- 边界保护：`myFetch(...)` / `consoleFetch(...)` 不误匹配 `fetch`

#### builder 集成

- **method entity 新增 `pos` / `end` 字段**（v0.33.0）：用于死代码 BFS 定位 + IO 扫描定位；向后兼容
- **method entity 挂 `astFingerprint` / `astFingerprintNodes`**（默认开启，性能门 `NICE_AOS_FINGERPRINT=0` 显式关闭以节省扫描时间）

### 依赖

- `@modelcontextprotocol/sdk@^1.30.0` 加入 `dependencies` 与 `bundledDependencies`（同时支持 `bundledDependencies`，已移除拼错的 `bundleDependencies` 字段）

### 测试

- **69 个新测试**（25 fingerprint + 4 duplicates + 24 toolRegistry + 4 mcpCli + 11 deadCode + 9 deadcodeCli + 16 ioRegistry + 7 ioCli - 5 个 query_graph 相关 + 6/6 session）
- **684 总测试**（既有 615 + 新 69，全部通过；当前 v0.33.0 精简后净删 5 个 query_graph 测试和 1 个过时 mcp 断言）

### 升级指引

```bash
npm install -g nice-aos@0.33.0
# 或:
npx nice-aos@0.33.0 action refreshRepo --root /abs/path/to/your/project
# 三核心增强：
npx nice-aos@0.33.0 ask "组件依赖" --stream --session r1
npx nice-aos@0.33.0 output --format markdown --since HEAD~1 --output diff.md
npx nice-aos@0.33.0 serve --token s3cret --ws-interval 5000
# 检测：
npx nice-aos@0.33.0 duplicates --dir /abs/path/to/data --min-size 20
npx nice-aos@0.33.0 deadcode --root /abs/path
npx nice-aos@0.33.0 io --root /abs/path --min-danger medium
# Claude Code 集成：
claude mcp add nice-aos -- npx nice-aos@0.33.0 mcp --root /abs/path
```

> 原 [0.32.0a] 节（合并入此）含：MCP tool 9 个（v0.33.0 减到 7 个）；builder 默认开启 fingerprint 但支持 `NICE_AOS_FINGERPRINT=0` 关闭；duplicate/deadcode/io 子命令原始描述。


## [0.31.0] - 2026-08-26

借鉴 asdm-aos 0.0.12 的 4 大工程模式：BlueprintRuntime + createEngine 元模型 / projectDetector 项目根自动检测 / ActionPanel 蓝图交互控件 / IncrementalParser 缓存式增量解析。详见 `docs/adr/0002-blueprint-engine-borrowed-from-aos.md`。

### 新增

- **蓝图引擎 V2**（`src/ontology/blueprintEngine.js`）—— 借鉴 aos BlueprintRuntime + createEngine 模式
  - `createBlueprintEngine({ objectTypes, linkTypes, actionDefs, linkImpls, actionImpls })` 工厂
  - 暴露 `find / where / link / action / snapshot / schema` 6 个方法
  - 写回不污染 seed（深拷贝 + 数组元素 `{...r}`，与 aos 同构）
  - 异常捕获：actionImpl 抛错 → 返回 `{ok:false, message}` 不抛到调用方
  - ParamDef 形态化（aos 4 种 + 扩展 number/boolean）：text / number / boolean / enum / objectRef / objectRefMulti
- **`createBlueprintV2(dataMap, opts)`**（`src/ontology/blueprint.js`）—— 兼容既有 linkImpls 闭包（24 个函数零重写），内置 markReviewed / addNote 动作，`extraActions` 参数允许 service/planning/db/deploy 蓝图扩展
- **`BLUEPRINT_SCHEMA`** 静态元数据导出 —— 聚合 OBJECT_TYPES（19）/ LINK_TYPES（24）/ ACTION_NAMES（4）/ ONTOLOGY_META（4 抽象层 + 6 分类）
- **项目根自动检测**（`src/analyzers/projectRootDetector.js`）—— 借鉴 aos projectDetector 思路
  - 多语言 marker 优先级表（Flutter pair 强信号 → Node → Rust → Go → Python → Java → Git 兜底）
  - Flutter pair 联合判定（`pubspec.yaml + lib/` 同时存在才算 Flutter）
  - 软链解析（realpath 防死循环）+ maxDepth=10 上限
  - monorepo 子包发现（命中 `package.json` 时自动探查 `apps/* / packages/*`）
  - `detectProjectRoot(inputPath, opts)` → `{ root, marker, description, fromPath, isFile, monorepoRoots }`
- **`action refreshRepo` 自动项目根检测**（`src/cli/commands/action.js`）—— 接受 `repoPath` 可选
  - 显式 `repoPath` 文件路径 → 向上找根；目录路径 → 用 detectProjectRoot
  - 不传 `repoPath` → 从 `process.cwd()` 向上找根
  - 输出 JSON 新增 `projectRoot: { path, source, marker, description }` 字段
- **蓝图交互操作**（`src/ontology/blueprintActions.js` + `src/ontology/viewer.js`）—— 借鉴 aos ActionPanel.tsx 设计
  - `buildActionCards({ selectedObjId, selectedObjType })` 结构化输出 4 张动作卡片
  - `renderActionCardHtml / renderActionCardsHtml` 按 ParamDef 自动渲染表单
  - 蓝图报告新增"交互操作"Tab：对象选择器（按 id/name/path 搜索 + 类型筛选）+ 动作卡片
  - 提交走 `fetch('/action', ...)` 调 `nice-aos serve` 端点（v0.32.0 集成）
- **增量解析器**（`src/analyzers/incrementalParser.js`）—— 借鉴 aos IncrementalParser 思路（无 tree-sitter）
  - `IncrementalParser` 类：LRU 缓存 `Map<filePath, {code, result}>`，默认容量 1000
  - `parse(filePath, code, analyzer)` 三态：缓存未命中 → 全量；命中且 code 未变 → 复用；code 变更 → 重算
  - 单例缓存（按 analyzer 类型隔离）：`getParser('ts') / 'vue' / ...`
  - `cachedAnalyze(analyzerName, analyzer)` 包装函数：零侵入集成
  - Git 集成：`listChangedFiles(repoRoot, since)` / `listStagedFiles` / `listUntrackedFiles`
  - 合并策略：`mergeSnapshotByFiles(oldSnapshot, changedFiles, newFileAnalyses)` by-id 替换

### 变更

- **`createBlueprint()` 兼容层保留** —— 既有 CLI（query / link / action）继续走 V1 路径，零迁移
- **`objectTypes` 增加 `kind: "object"|"interface"` 字段** —— 与 aos 对齐（nice-aos 现有 19 种类型均为 object）
- **现有 `/api/schema` 端点兼容新 schema** —— V2 engine 暴露 `engine.schema()` 形态一致

### 验证

- 全部 429 个测试通过（既有 322 + P0 30 + P1 26 + P2 23 + P3 28）
- 性能基准：1000 文件项目，10 个文件变更场景下，增量 < 全量 30%
- 既有 32 个测试文件（blueprintEngine / projectScanner / configAnalyzer / vueAnalyzer / tsAnalyzer / dartAnalyzer / goAnalyzer / rustAnalyzer / pythonAnalyzer / userScriptAnalyzer / sqlAnalyzer / propsChain / serviceBuilder / serviceAuditor / serviceViewer / dbBuilder / dbAuditor / dbViewer / deployBuilder / deployAuditor / deployViewer / planning / themes / serve / update / external-calls / routeMapView / data-model / nextRoutes / jsxRoutes / method-health / method-health）全通过

### 升级指引

```bash
npm install -g nice-aos@0.31.0
# 或:
npx nice-aos@0.31.0 action refreshRepo --params '{}'  # 自动从 cwd 找项目根
# 新增:蓝图报告交互操作 Tab（markReviewed / addNote / refreshRepo / analyzeFile）
nice-aos export --format html --output blueprint.html
```

## [0.30.0] - 2026-08-25

借鉴 asdm-aos 0.0.12 的 4 大能力：方法健康度 / 外部调用 / API 端点 / 数据模型 → 表链接 + 步骤化进度 + 元模型端点。详见 `docs/adr/0001-asdm-aos-borrowed-capabilities.md`。

### 新增

- **Method.health 子对象**：方法级代码健康度画像（圈复杂度 / 最大嵌套 / 分支数 / throw 数 / await 数 / 早期 return 数 / lambda 计数与 JSX 内联回调 / 测试方法识别 / `vi.mock` vs `jest.mock` 框架来源 / 派生 risk 评级 low/medium/high/critical）。统一承载 aos 的 complexity / isTest / lambdaCount / endpointInfo 4 个零散字段，单一 query 取全。接口方法无 body / Go 跨文件方法 / 非 ts 来源均使用 `placeholderHealth()` 兜底（向后兼容旧快照）
- **Method.externalCalls**：识别函数体里的 React Hooks（19 种）/ DOM API（14 种）/ 状态管理 API（11 种），输出 `[{ name, kind, framework, line }]`。不进入 `calls` 链接（旧 viewer 渲染契约不变），仅作 `query Method --where "externalCalls~useState"` 字段过滤
- **Method.endpointInfo**：API 端点装饰器级识别，覆盖 Next.js App Router（`app/api/.../route.ts` + `export async function GET/POST`）/ Next.js Pages Router（`pages/api/*.ts` + `handler(req,res)`）/ Nuxt 3（`server/api/*.get.ts` + `export default function`）
- **Method.sqlQueries**：从函数体字符串提取 SQL 表名（SELECT/INSERT/UPDATE/DELETE + 动态模板 `${name}`），4 个独立简单正则避免大字符串回溯爆炸；用作 mapsToTable 第二通道
- **Interface/Class.isDataModel + dataModelType**：类/接口名后缀启发式（DTO/Model/Entity/Schema/Request/Response/Params/Input/Output/Form/Payload → 对应类型）+ 装饰器识别（`@Entity/@ObjectType/@InputType/@Model/@ArgsType` → `orm-decorated`）
- **Interface/Class 链入 mapsToTable**：Class/Interface/Store/Service 作为 src 也能映射到 Table（之前仅 mappedTableIds 显式映射）
- **Dependency.isTypeDefinition**：标记 `@types/*` / `typescript` 类型定义包（仅 devDependency 时）。标记而非隐藏 —— 不破坏旧 query/link 行为
- **action 步骤化进度**：`refreshRepo --params '{"silent":false}'` 输出 5 步耗时（scan:start/done / parse:done / resolve:done / build:done）+ 人类可读汇总。默认 `silent=true` 保持 JSON 单一输出（向后兼容）
- **`/api/schema` 端点**：`nice-aos serve` 新增本体元模型端点，暴露 `OBJECT_TYPES`（19 个）/ `LINK_TYPES`（24 个）/ `ACTION_NAMES`（4 个）+ 概念元模型（abstractionLevels / categories）。借鉴 asdm-aos `ObjectTypeDef / LinkTypeDef / ActionDef` 设计，让外部 agent 自动发现能力

### 变更

- **mapsToTable 三通道匹配**（`src/ontology/blueprint.js`）：
  1. `mappedTableIds` 显式映射（dbModel.matchTablesToCodeEntities 设置）—— 兼容旧快照
  2. 自身 sqlQueries + 子方法聚合（Method/Class/Interface/Store/Service 都能映射）
  3. 命名约定：`UserEntity → users` / `Product → products` / `Class → classes`
- **mapsToTable ↔ mappedFromCode 共享** `collectTableIdsForEntity()` 私有函数，消除内联重复
- **asdm-aos 借鉴注释收敛**：24 处「借鉴 asdm-aos」注释统一指向 `docs/adr/0001-` ADR 文档（避免分散）

### 验证

- 全部 321 个测试通过（0.29.1 时 318 + methodHealth P1 修复回归 3 个）
- 端到端：`/api/schema` 返回 19/24/4 元模型；`silent:false` 输出 5 步耗时；`Method.health` 真实反映复杂度（renderPlanningMarkdown 17 圈复杂 → high）

### 升级指引

```bash
npm install -g nice-aos@0.30.0
# 或:
npx nice-aos@0.30.0 action refreshRepo --params '{"repoPath":"你的项目"}'
# 查看方法健康度:
nice-aos query Method --where "health.risk=critical"
# 查看本体元模型:
nice-aos serve  # 打开 http://127.0.0.1:8420/api/schema
```

## [0.29.1] - 2026-08-25

补 0.29.0 离线安装可用性：tgz 自带 `commander` / `typescript` / `yaml` 三个运行时依赖。

### 变更

- **`bundledDependencies` 加入 `commander` / `typescript` / `yaml`**：tgz 从 507 KB 增长到 4.8 MB，但安装时无需访问 npm registry
- **严格离线测试通过**：清空 npm cache + 阻断 registry 后 `npm install nice-aos-0.29.1.tgz` 880ms 完成，跑 `asdm-portal` 烟囱扫描正常

### 验证

- `npm install --offline` + `nice-aos --version` → 0.29.1
- asdm-portal 烟囱扫描：65 源文件 / 498ms / 0 错误

### 升级指引

```bash
# 已用 0.29.0 的用户：升级到 0.29.1 获得离线安装能力
npm install -g nice-aos@0.29.1
# 或用 tgz 离线装（无需网络）
scp nice-aos-0.29.1.tgz remote-host:~
ssh remote-host "npm install -g ./nice-aos-0.29.1.tgz"
```

## [0.29.0] - 2026-08-25

把"代码本体扫描"从 9 种主语言扩展为 **9 主语言 + 9 种配置/视图/SQL/部署文件**，并新增 `nice-aos-fullscan-skill` 一站式调度（代码 + 数据库 + 部署三套蓝图）。

### 新增

- **9 种配置/视图/SQL/部署文件纳入主本体扫描**（`src/analyzers/configAnalyzer.js`，200+ 行；与 `tsAnalyzer` / `vueAnalyzer` / `dartAnalyzer` / `goAnalyzer` / `rustAnalyzer` / `pythonAnalyzer` 平级）
  - **`.css`**：提取 `@import` URL / `@keyframes` 名 / CSS 自定义变量（`--xxx`）/ 顶层选择器（`.class` / `#id` / `tag`）
  - **`.html`**：提取 `<title>` / `<script src>` / `<link href>` / `<meta name|property>` / `id` 锚点
  - **`.sql`**：提取 `CREATE TABLE/VIEW/INDEX/FUNCTION/PROCEDURE/TRIGGER/SEQUENCE` 与 `DROP` 对象名（含 schema 限定名）；字符串字面量内的伪关键字不被误识别
  - **`.yml` / `.yaml`**：提取顶层 key + 二级 key + 强信号字段（`version` / `services` / `apiVersion` / `kind` / `image` / `metadata`）
  - **`.conf`**：支持 `[section]` + nginx 风格 `section { ... }` + nginx 指令（`key value;`）
  - **`.toml`**：`[section]` + `key = value`
  - **`.ini`**：`[section]` + `key = value`
  - **`.env` / `.env.development` / `.env.production` / `.env.local`**：仅提取 KEY 名（不取 value，敏感信息保护）；路径后追加 `#env` 标记供 builder 还原真实文件名
- **`projectScanner.SOURCE_EXTENSIONS` 扩展**：在原 9 种主语言上增加 `.css .html .sql .yml .yaml .conf .toml .ini .env`
- **`.env.*` 文件规范化**：`path.extname('.env.development')` 返回 `.development` 不在白名单 — 在 `walk` 阶段按文件名匹配并把扩展名规范化为 `env`，避免漏扫
- **`resolveRoots` 自动双根**：项目有 `src/` 时返回 `['src', '.']`（之前只有 `['src']`），让顶层 `.env*` / `index.html` / `*.conf` / `nginx.conf` 不被遗漏；`walk` 阶段按 relPath 去重（`seen` Set）避免重复
- **SourceFile 新增 3 字段**（蓝图展示用）：
  - `configKind`：css / html / sql / yaml / config
  - `configItems`：顶层 key / 标签 / 对象名 / 嵌套结构数组（最大 100 项 / 文件）
  - `configTruncated`：文件 > 2MB 时跳过详细解析仍算行数
- **`nice-aos-fullscan-skill`**（`skills/nice-aos-fullscan-skill/SKILL.md`）：一站式全栈扫描调度，标准工作流含 `_scan.sh` 批量模板

### 安全

- `.env` 文件分析**仅提取 KEY 名**（`parseEnv` 函数不取 value），避免把数据库密码 / API Key 写入 snapshot / 蓝图

### 验证

- 新增 `test/configAnalyzer.test.mjs`（11 个测试：CSS / HTML / SQL / YAML / INI / CONF / TOML / .env / 行数 / 大文件 / 磁盘 / 未识别 ext）
- 全套测试 266/266 通过（`node --test test/*.test.mjs`）

## [0.28.1] - 2026-08-25

补 0.28.0 周期内已存在但未发布的本地特性，并清理 release 副作用。

### 新增

- **Python 后端脚本分析子系统**（`src/analyzers/pythonAnalyzer.js`，1000+ 行，参照 `tsAnalyzer` / `vueAnalyzer` / `dartAnalyzer` / `goAnalyzer` / `rustAnalyzer` 平级设计、逻辑完全独立）
  - **实体映射**（对齐 TS/Rust/Dart 语义）：
    - `class X(Base, Mixin, metaclass=Meta): ...` → `Class`（kind: class；bases 抽末段类型名；`metaclass=` 抽到 `metaclassName`）
    - 含 `@abstractmethod` 或继承 `ABC` 且只有抽象方法的 class → `Interface`
    - `def f(...)` / `async def f(...)` → `Method`（`ownerKind=class`）；模块级 `def` → `moduleFunctions`
    - `@property` / `@staticmethod` / `@classmethod` / `@abstractmethod` / 自定义装饰器 → `methods[].decorators[]`（含 `kind` 标签）
    - `import X` / `from X import Y` / `from X import Y as Z` / `from X import *` → `imports`（`importMap` 含 local→specifier 与 from 子名映射）
    - `__init__` / `__str__` / `__repr__` / `__enter__`/`__exit__` 等 dunder → `methods[].isDunder=true + dunderCategory` 语义标签
    - `__all__ = [...]` → `pythonExports`（模块公开符号清单）
    - `if __name__ == "__main__":` → `pythonEntryPoints`（脚本入口）
    - `@app.command` / `@click.command` / `@typer.command` 装饰器 → `pythonEntryPoints`（CLI 入口）
    - 方法/函数体内调用 → `callEdges`（`self.method()` / `cls.method()` / `Class.method()` / `pkg.func()` / `new Class()`）
    - `@app.get/post/put/delete/patch` + `@app.route` / `@router.get` 系列 → `pythonRoutes`（method/path/handler/target）
  - **双通道噪声剥离**：`stripPythonNoise`（全剥离：f-string 插值 + 三引号/单引号/字节串 + 注释）供块状态机与调用提取；`stripCommentsOnly`（仅剥注释）供 import / 装饰器字符串 / 路由 path / `__all__` 列表 / docstring 提取
- **批量 Python AST 语法校验**（`checkPythonSyntaxBulk`）
  - 一次性 spawn `python3` 调 `ast.parse`，把失败文件登记到模块级 `pythonSyntaxErrors` Map
  - `analyzePythonFileFromDisk` 检测到当前文件失败时主动 throw（`code='PYTHON_SYNTAX_ERROR'`），由 `builder.js` 的 try/catch 写入 `Project.analysisErrors`
  - **关键价值**：`pythonAnalyzer` 是基于缩进的轻量级块结构匹配，原先对 `SyntaxError` 文件会"静默成功"。批量 AST 校验把这类问题暴露在 `analysisErrors`，不污染下游本体
  - 性能：TheAlgorithms/Python 1382 个 .py 文件批量校验 0.3 秒
- **`projectScanner` 识别 `.py` 文件**（`SOURCE_EXTENSIONS` 加入 `.py`，`scanProject` 输出 `pyFileCount`，`buildProject` 汇总 `pyFileCount`）
- **`action analyzeFile` 支持 `.py` 文件**（之前提示只支持 `.ts/.tsx/.js/.jsx/.mjs/.vue/.rs/.dart`）
- **`pythonAnalyzer.test.mjs` 测试套件**（`test/pythonAnalyzer.test.mjs`）—— 覆盖 class / 装饰器 / dunder / import / 路由 / 入口点 / 自定义 Analyzer 调用链聚合
- `package.json` `description` 与 `keywords` 同步纳入 Python（description 加入 "Python 后端脚本分析 CLI"，keywords 追加 `python`）
- `scanProject` 沿用 `pub` / `npm` 同级设计：识别 `.py` 后自动进入 `Project.pyFileCount` / `Project.language`（追加 "Python"）

### 修复

- **`package.json` self-dependency 残留**：dependencies 列表曾含 `"nice-aos": "^0.23.0"`（自我引用），会导致 `npm install nice-aos` 拉一个旧版副本。清理后 tarball 不再带 self-dep（`npm pack --dry-run` 验证 `total files` 由 68 → 67）

### 验证

- `npm test`：255/255 通过（含新增 `pythonAnalyzer.test.mjs`）
- `npm pack --dry-run`：67 files、494.9 kB、sha512 已记录
- TheAlgorithms/Python 端到端：1382 个 .py、308 Class、3844 Method、12 个 `analysisErrors`（全部为 Python 2 风格 `except X, Y:` 未加括号）
- nice-aos 自家项目（无 .py）：0 errors，行为不变

## [0.28.0] - 2026-08-24

### 新增

- **产品规划 / PRD 文档蓝图子系统**：扫描产品规划管理库（特性 PRD Markdown + Modules/ 目录 + 进展报告 + Roadmap 文档），产出**规划模型 `planning-snapshot.json`** 与自包含 **产品规划蓝图 HTML（7 Tab：总览 / 特性 / 模块 / 图谱 / 迭代与发布 / Roadmap 与战略 / 审计）**
  - 数据流：planning-snapshot.json（PlanningModel）→ `buildPlanningViewerModel()`（视图模型）→ `renderPlanningBlueprintHtml()`（HTML）
  - 实体类型（query 命令空间）：features / modules / releases / milestones / themes / dependencies
  - 状态归一化：emoji → key（🟢→done / 🟡→implementing / 🟠→designing / 🟣→clarifying / 🔴→blocked）+ 关键词 fallback（"阻塞/风险/有问题"→blocked，"实现中/开发中/进行中"→implementing，"澄清中/待确认/评审中"→clarifying …）；未命中归 `unknown`
  - 优先级归一化：P0/P1/P2/P3 + 高/中/低
  - 模块解析：合并特性表「分类」+ Modules/ 目录文档，模块标签去数字前缀（`01-` `02-`）
  - 迭代里程碑：扫描所有表，取首列形如 `v1.x` 的版本规划行
  - 四维健康审计：`coverage` 覆盖完整性 / `statusHealth` 状态健康 / `dependencyRisk` 依赖风险 / `releasePlanning` 版本规划
  - **CLI 命令组 `planning`**：`planning scan / snapshot / view / query / audit`
- **`skills/nice-aos-planning-skill/`** 产品规划 Skill：编排产品规划文档扫描 + 规划蓝图生成 + 四维审计 + 蓝图 AI 助手适配 `planning-viewer-data`
- **`contrib/blueprint-ai-agent` 适配产品规划蓝图**：页面类型检测新增 `planning-viewer-data` → 切换为"产品规划蓝图"智能体（10 个专属工具：概览/特性/模块/图谱/迭代与发布/Roadmap/审计/维度/状态分布/优先级分布），系统提示词、建议问题、FAB 与启动日志同步适配
- **图谱 2.0**（参考 `src/deployment/deployViewer.js` 的依赖图实现）。`src/planning/docsViewer.js` 重写图谱渲染块，提供：
  - **统一交互基础设施**：`graph-container` 包住 SVG 容器 + 右上 `graph-toolbar`（缩放 −/+/重置）+ 左下 `graph-legend`（状态色 dot + 边类型）+ 操作提示（"滚轮缩放 · 拖拽平移 · 点击节点高亮"）。所有交互通过修改 SVG `viewBox` 实现（缩放、平移、重置），无需第三方库
  - **节点结构升级**：从 `<circle>` 升级为 `<g class="g-node" data-id="...">` 包裹，绑定 `mousedown`/`click`/`wheel` 事件——点击节点高亮关联边（其它边 + 节点 `.dim` 透明度 0.08/0.18），再点空白或节点本身取消高亮
  - **`forceLayout` 改为确定性**：初始位置用 id FNV-1a 哈希替代 `Math.random()`——同输入恒出同位置，杜绝切 tab/重渲染时的"图谱抖动"
  - **`特性 × 模块` 改用分层分列布局**（借鉴 deployViewer 的 `LAYER_ORDER` 思路）：左列每个模块一格 + 右列特性汇总 + 列背景框 + 列标签，连接线带箭头 `marker-end`；不再抖动，节点密度可读
  - **图例 + 空状态**：`drawGraph` 支持 `legend: [{label,color,kind:'node'|'edge'}]` 与 `emptyMsg`，分情况渲染状态色 + 边类型图例
- `src/themes/index.js` 注册 `planning` 主题（沿用 `deep-blue` 配色）

### 修复

- 模块卡片 `X 特性` 标签：`docsScanner.buildModules` 产出对象无 `featureCount` 字段导致前端显示 "undefined 特性"。改为从 `m.featureIds` 兜底计算，**未识别到特性时整段标签隐藏**（不显示 `0 特性` 冗余、不显示 `undefined` 字面值）
- 发布卡片 `X 迭代 · Y 特性` 标签同步隐藏逻辑：迭代数与特性数都为 0 时整段 meta 标签隐藏

### 新增测试

- `图谱 HTML 模板含 toolbar / legend / viewBox 占位 + 脚本挂载交互`
- `特性依赖图节点为 g.g-node + 边为 path.g-edge，数据属性可定位高亮`
- `特性 × 模块图为分层分列布局：列背景 + 模块节点 + 特性节点`
- `图例渲染：状态色节点 + 边类型`
- `forceLayout 确定性：相同输入两次结果完全一致（同 id → 同坐标）`
- `test/planning.test.mjs` 新增 12 项产品规划子系统测试：`statusKey 状态归一化 / parseMarkdownTables 解析表格 / extractOverview 提取功能概述 / scanPlanningModel 解析特性/模块/依赖/里程碑/发布/主题 / snapshot 往返 / buildPlanningViewerModel 视图模型字段齐全 / renderPlanningBlueprintHtml 含 planning-viewer-data 且可解析 / auditHealth 输出四维审计 / 蓝图浏览器脚本运行时冒烟 / 图谱无内容时隐藏图谱 Tab 与子视图 / 仅特性依赖无模块归属时隐藏「特性 × 模块」子视图并切换默认激活 / 仅模块归属无依赖时隐藏「特性依赖」子视图并默认激活「特性 × 模块」`

## [0.27.0] - 2026-08-24

### 新增

- **nice-aos-code-review-skill**：综合代码评审 Skill（严格超集 `nice-aos-deadcode-skill`）。在四级死代码（文件/导出/类型/函数）清理工作流之上，整合 nice-aos 本体快照 + asdm-aos 服务模型（含五维健康审计 complexity/dataHealth/testCoverage/analysisQuality/dependencyHealth）+ 代码/服务蓝图（`export --format html|viewmodel`）+ 外部扫描 JSON（ESLint/Sonar/Checkstyle/Semgrep/Trivy/npm audit），编排七步流水线：**装配 → 通用维度 → 领域维度 → 死代码 → 后端服务 → 外部扫描融合 → 报告输出与 markReviewed 闭环**。规则按 ASDM code-review v2.0 的领域驱动模型扩展为 4 组共 **11 维度 + 50+ 规则 ID**（ARCH/TYPE/PERF/UX/SEC/STYLE 通用 + DOMAIN/REUSE/COUPLE/COHESION 领域 + DEAD-FILE/EXPORT/TYPE/FN 死代码 + BACK-CX/DATA/TEST/QA/DEP 后端）。报告模板 `spec/review-template.md` 含五维评分 + 死代码清单 + 外部扫描融合 + markReviewed 跨会话闭环；评级 P0~P3（CRITICAL/MAJOR/MINOR/INFO）。继承 nice-aos-deadcode-skill 全部规则矩阵（含判定豁免：命名空间导入 / `export *` / 动态 import / 接口方法永不判死 / 油猴 ScriptFunction 额外豁免），并明确边界——死代码**清理动作**（删除实体）仍由 deadcode skill 承载，本 Skill 仅输出评审报告
- `spec/review-rules.md` 通用+领域+死代码完整规则矩阵（含 PROJ-01~05 项目特定规则）
- `spec/backend-review-rules.md` 后端服务五维规则 + 蓝图评审 4 条
- `spec/review-template.md` 综合评审 Markdown 报告模板（11 维度小计表 + 死代码清单 + 外部扫描融合表 + markReviewed 闭环表）
- `reports/` 评审报告输出目录（gitkeep 占位）

### 变更

- `.gitignore` 新增 `.workbuddy/`（本地 memory 文件）与 `*-service-blueprint.html`（本地生成的服务蓝图）—— 避免误提交
- SKILL.md 系列继续保持 **`nice-aos-skill`（核心查询） / `nice-aos-deadcode-skill`（死代码清理） / `nice-aos-userscript-skill`（油猴审计） / `nice-aos-database-skill`（数据库） / `nice-aos-deployment-skill`（部署） / `nice-aos-service-skill`（后端服务查询） / `asdm-aos-skill`（Java 本体）** 七系生态 + **新增 `nice-aos-code-review-skill`（综合评审）**，职责边界明确

### 不变更

- nice-aos-deadcode-skill 的全部清理动作（删除 / `markReviewed` / `addNote`）保持不变——本 Skill 仅消费其四级候选清单，不接管清理

## [0.26.0] - 2026-08-24

### 新增

- **Java 后端服务蓝图**（基于 asdm-aos 本体快照）：新增 `service` 命令组，消费 asdm-aos 工具产出的 Java 后端本体快照（`snapshot.json`，含包/类/接口/方法/调用关系/DDL 表/依赖）构建后端服务模型，产出分析 JSON 与自包含服务蓝图 HTML（`service-blueprint.html`，9 Tab：总览/模块/分层/**图谱**/API 面/数据层/依赖与集成/代码质量/健康审计）
- **图谱 Tab**（力导向图，内联力模拟零依赖）：三种视图——**模块图谱**（节点=服务模块，大小 ∝ 类数；边=包依赖 `Package.dependsOnPackageIds`（蓝）+ 跨模块方法调用（绿），排除 other 噪声模块）、**分层调用流**（节点=架构分层，边=跨层方法调用，宽度 ∝ 调用次数，如 Controller→Service→Repository→Entity，实测 service→entity ×5310）、**模块×技术栈**（二分图：模块 + 技术分类（紫），边=模块使用该技术 `Package.dependencyIds`）；交互：拖拽节点/滚轮缩放/拖空白平移/点击聚焦高亮邻接
- **蓝图 AI 助手适配服务蓝图**（`contrib/blueprint-ai-agent`）：页面类型检测新增 `service-viewer-data` → 「服务蓝图」智能体（10 个专属工具：概览/模块/分层/端点/表/依赖技术栈/代码质量/健康审计/审计明细/图谱查询），系统提示词、建议问题、设置面板快照地址、FAB 与启动日志同步适配
- **模块动态推导**（`serviceModel.js` `deriveModuleRules`）：模块规则**不硬编码**——首次构建从快照包结构动态推导（基础包多数前缀 + 分层关键词守门员 + 单模块仓库坍缩），写入模块配置文件 `service-modules.json`（默认 `serviceSnapshotDir` 下），后续构建自动加载；`--module-prefix` 临时覆盖 / `--module-config` 指定配置文件，切换后端项目无需改代码
- **分层判定**（`serviceModel.js` `detectLayer`）：注解（`@RestController`/`@Service`/`@Repository`/`@Entity`/`@Configuration`/`@Mapper`）→ 类名后缀 → 包名关键词，覆盖 Controller/Service/Repository/Mapper/Entity/DTO/Config/Adapter/任务/工具 11 层
- **技术栈判定**（`serviceModel.js` `TECH_STACK_RULES`）：33 条依赖名正则，具体 starter（data-jpa/data-redis/security/webflux…）优先于泛型 spring-boot，识别 JPA/MyBatis/Spring Security/JJWT/ShedLock/SpringDoc/Redis/ES/S3/OBS/MinIO/OkHttp/MySQL 等
- **五维健康审计**（`serviceAuditor.js`）：代码复杂度 / 数据层健康 / 测试覆盖率 / 分析质量 / 依赖健康加权评分（含评分环 SVG）
- **CLI 命令**：`service build --snapshot <path>`（构建+保存模型）、`service export [--format json|html|viewmodel] [--snapshot <path>]`（支持直接指定快照 json 路径一步生成蓝图）、`service query <type>`（14 种对象）、`service audit health|all`
- 全局选项 `--service-snapshot-dir`；默认主题映射 `DEFAULT_THEMES.service = 'elegant-purple'`

### 变更

- `src/themes/index.js`：`DEFAULT_THEMES` 新增 `service` 条目
- 服务快照文件：`service-snapshot.json` / `service-modules.json`（与代码/数据库/部署快照分离，均在服务快照目录）
- **分层判定修复**：`domain` 包关键词从 `util` 改为 `entity`（DDD/MyBatis 项目的 POJO 位于 domain 包，leaniss-system-core 实测实体层 4→100 类）；接口补充 `layerKey` 判定（Mapper/Repository 接口归入对应分层，Mapper 接口 65 个、Repository 接口 3 个）
- **审计修正（代码审核发现）**：复杂度热点占比改为从全量方法重算（`complexityHotspots` 是 TOP50 展示截断列表，此前以截断列表计算占比，asdm-admin 实测热点数 50→159）；依赖多版本检测只比较具体版本号，忽略 Maven 属性占位符（`${xxx.version}`）与空版本（消除 asdm-admin 4 条误报 error，依赖健康 60→90，综合 77/C→82/B）
- **模块配置跨仓库防串用**：加载 `service-modules.json` 时校验 `repositoryName`，与快照仓库不符（切项目残留）则忽略并重新推导，CLI 输出警告并记入 `_meta.moduleConfigWarning`
- **口径统一与瘦身**：仓库级 `classCount` 与 `stats.classCount` 一致（不含枚举，asdm-admin 1878→1801）；服务蓝图 HTML 审计数据去重（`health.audits` 不再重复内嵌），热点标题显示实际条数（cc≥15，TOP n）

### 验证

- 221 个单元测试全过，零回归（新增 `serviceBuilder` / `serviceViewer` / `serviceAuditor` 25 个）
- 端到端实测：asdm-admin 本体快照（3583 文件/335 包/1878 类/189 接口/20408 方法/799 端点/107 表/55 依赖），模块动态推导与报告手写模块完全一致（core 960 类/adapter 282/integration 124/agentorbit 99/orgmapping 98/telemetry 81/file 49/sso 41/mcp 26/portal 118），热点 TOP3（圈复杂度 74/69/59）与测试统计（1154 单元/47 集成）均与报告一致；五维健康评分 82/B（复杂度 97：全量 159 个 cc≥15 热点；依赖 90：占位符误报清零）
- **leaniss-system-core 实测（引入 asdm-aos-skill 后）**：asdm-aos 扫描（577 Java 文件/464 类/157 接口/5581 方法/86 依赖/3316 DDL 表/63 Mapper）→ 后端服务蓝图（7 模块 common 126 类/system 120/manager 149/job 38/file 9/gateway 14/auth 8；技术栈 jjwt/quartz/springdoc/redis/minio/feign/mysql；448 端点；模块图谱 22 边）；另建前端（Vue2，163 组件/132 路由/67 领域）、数据库（Sprint 布局，80 迁移/106 表/6 库/11 领域）、部署（19 服务/15 路由/5 中间件）三类蓝图，四蓝图 HTML 内嵌数据与脚本均验证通过

## [0.25.0] - 2026-08-24

### 新增

- **代码↔数据库跨层审计**（借鉴 Java AOS 实体-表融合思路）：第 9 个审计场景 `db audit crosslayer`——孤儿表检测（无代码实体映射的表）、隐式外键识别（`*_id` 列无显式 FK 约束）、代码实体覆盖率、幽灵类型检测（代码有 Interface 但无对应数据库表）
- **跨层命名约定匹配**（`dbModel.js`）：`matchTablesToCodeEntities()` 三级匹配策略（精确→命名约定→子串），snake_case 表名 ↔ PascalCase 接口名自动关联（`user_roles` → `UserRole`）
- **前端数据模型类型识别**（`dbModel.js`）：`FRONTEND_DATA_MODEL_RULES` 5 种类型——API Response Type / API Request Type / Store Model / Entity Interface / Table Mirror
- **跨层链接类型**（`blueprint.js`）：新增 `mapsToTable`（Interface/Class/Store → Table）和 `mappedFromCode`（Table → Interface/Class/Store）两种链接，支持代码↔数据库双向遍历
- **CLI 命令**：`db audit crosslayer [--code-snapshot <path>]`；`db audit all` 新增 `--code-snapshot` 选项自动启用跨层审计

### 变更

- `dbModel.js` 导出新增：`tableNameToCandidateNames`、`codeNameToCandidateNames`、`matchTablesToCodeEntities`、`FRONTEND_DATA_MODEL_RULES`、`detectDataModelType`
- `LINK_TYPES` 从 22 种扩展为 24 种（+`mapsToTable` / `mappedFromCode`）
- 数据库 Skill SKILL.md 同步更新：9 大审计场景、跨层审计章节、触发场景、Agent 行为规则

### 验证

- 195 个单元测试全过，零回归
- 端到端实测：asdm-admin deploy 目录（141 文件/53 服务/35 路由/6 中间件），5 维审计评分（健康度 76/C、安全性 60/D、高可用 76/C、一致性 96/A、依赖 82/B）

## [0.24.0] - 2026-08-24

### 新增

- **数据统计 Tab**（数据库蓝图 dataoverview）：参考代码蓝图「代码统计」（9bbd034）为数据库脚本分析补齐规模画像——KPI 卡片（数据表 / 列总数 / 外键 / 索引 / 迁移版本 / 平均每表列数，视图/触发器/存储过程徽章）、领域数据量分布条形图（按列数排序，附表数/外键数/占比）、领域列数占比环形图（纯 SVG `stroke-dasharray`，r=15.9155 周长=100 直用百分比，领域沿用自身配色）、DDL/DML 操作分布（全部迁移 `operationSummary` 累计：CREATE TABLE/ALTER/INDEX/DROP/VIEW/TRIGGER/PROC/FUNC/INSERT/UPDATE）、列类型分布（归一化基础类型：`VARCHAR(255)`→VARCHAR、`INT UNSIGNED`→INT）、Top 20 宽表（按列数 + 领域/FK/索引/注释）、迁移版本操作量 Top 10 表格
- **数据图谱 Tab**（数据库蓝图 dataoverview）：表 / 领域两级关系网络的**力导向图**（内联力模拟零依赖：库仑斥力 + 弹簧引力 + 向心重力 + 速度阻尼，320 步冷却布局，与代码蓝图代码图谱同一套参数）——表图谱节点 = 表（大小 ∝ 列数，颜色 = 领域，节点携带服务端配色免客户端映射）、边 = 外键引用（同向 FK 聚合权重，粗细 ∝ FK 数）；领域图谱节点 = 领域（大小 ∝ 表数）、边 = 跨领域外键耦合；交互：拖拽节点实时重算、滚轮缩放、空白平移、点击聚焦邻接高亮（显示列/外键/索引/领域/首建版本/注释与邻接边数）、表/领域视图切换、重新布局、重置视图

### 变更

- `buildDbViewerModel` 视图模型新增 `stats` 与 `dataGraph` 两个聚合（纯函数，可供 AI agent 直接消费 `db export --format viewmodel`）；无表数据时置空并隐藏对应 Tab
- 数据图谱保护上限：表视图节点 ≤ 150（按 FK 度数 + 列数排序截断，`hiddenTableCount` 提示未展示小表）、领域节点 ≤ 24、边 ≤ 600；悬挂外键（指向不存在表）不建边；领域视图排除域内 FK 与自环
- 多数据库场景领域键对齐：统计与图谱统一使用 `db:domain` 复合键（与 `buildDomainsArray` 一致）

### 修复

- **演进分析图表空白**（数据库蓝图）：`auditEvolution` 读取 `m.operations` 数组 / `m.tableCount` 字段，而 `dbBuilder` 产出的迁移只携带 `operationSummary` 对象——导致真实扫描下表数增长曲线恒为 0、操作类型分布无柱条、演进趋势占比恒 0%；现兼容两种形态（`operationSummary` 主路径 + `operations` 旧形态），操作大类按 operationSummary 键映射（CREATE/ALTER/INDEX/DROP/DML/其他），增长曲线优先按当前表 `createdAt` 精确累计（终点 = 当前表数，重建表不重复计数），无 createdAt 时退回 CREATE-DROP 净增近似
- **顶部统计卡片窄屏错乱**（数据库蓝图）：flex 布局（`flex: 1 1 90px`）在约 780-800px 宽度下出现"第一行挤压 7 张窄卡 + 第二行 1 张孤卡被 grow 拉伸占满整行"；改为固定 8 项网格 `repeat(8, 1fr)`，≤1080px 切 4 列、≤560px 切 2 列，任意宽度下卡片等宽铺满
- **演进分析图表宽屏横向拉伸变形**（数据库蓝图）：图表渲染 IIFE 在页面加载时执行，而该 Tab 默认 `display:none`，`getBoundingClientRect()` 量宽为 0 后回退 480 假宽度，叠加 `preserveAspectRatio="none"` + `width:100%` 在 1500px 视口下被横向拉伸 3.1 倍——曲线压扁、X 轴版本号文字剪切变形、数据点变椭圆、堆叠柱挤成一片；重构为惰性渲染 `renderEvolution()`（与 ER 图同一模式）：隐藏时跳过图表绘制、Tab 激活时按实测宽度 1:1 绘制（viewBox 严格等于容器像素宽、显式 width/height、移除 preserveAspectRatio=none）、窗口 resize 防抖重绘、静态部分（图例/领域首版/趋势/里程碑）只渲染一次

### 验证

- 195 个单元测试全过（`test/dbViewer.test.mjs` 新增 6 个：stats/dataGraph 模型聚合（领域分布/操作累计/列类型归一化/FK 聚合权重/悬挂外键排除/跨域边）+ DOM stub 执行内嵌脚本渲染产物（KPI/环形图/力导向 SVG 无 NaN 且坐标在画布内 + 演进图表有曲线与柱条 + 卡片网格 CSS）+ 演进分析 operationSummary 聚合 + 演进图表惰性渲染（隐藏时不绘制 / Tab 激活按实测宽度绘制 / resize 重绘 / 无 preserveAspectRatio）+ 空数据容错）
- 端到端实测：sprint 布局 SQL 目录 `db scan` → `db export --format html`，5 表/19 列/5 领域全链路正确；真实浏览器验证两个新 Tab 渲染、表/领域视图切换、节点点击聚焦（user_info → 3 邻接高亮 + 元信息）
- 修复项实测（asdm-admin 真实库）：migrations（140 迁移/95 表）与 mysql-telemetry（8 迁移/6 表）演进分析均出数——telemetry 早期 CREATE 63% / 后期 ALTER 95%、11 根操作柱条、里程碑出现；顶部卡片 780px 下 4+4 两行等宽铺满（修复前 7 张 94px + 1 张 732px 孤卡）
- 图表变形修复几何级验证：1500px 视口下 Tab 激活前 viewBox 为空（隐藏不绘制），激活后 viewBox=1403=容器实测像素宽、preserveAspectRatio 已移除（修复前 viewBox=480 被拉伸 3.1 倍）；视觉复检 X 轴版本号字形端正无剪切、数据点为正圆、柱条宽度正常；窗口 resize 防抖重绘跟随新宽度

## [0.23.0] - 2026-08-23

### 新增

- **代码统计 Tab**（蓝图查看器）：行数 / 文件 / 单元规模画像——KPI 卡片（代码总行数 / 源文件总数 / 一级模块 / 代码单元 / 平均单文件行数 / 测试文件）、模块代码量分布条形图、代码分布占比环形图（纯 SVG `stroke-dasharray`，r=15.9155 周长=100 直用百分比）、语言分布（按行数）、架构层分布、Top 20 代码单元（组件/Hook/Store/服务混合按行数）、最大文件 Top 15 表格
- **代码图谱 Tab**（蓝图查看器）：模块 / 组件两级依赖网络的**力导向图**（内联力模拟零依赖：库仑斥力 + 弹簧引力 + 向心重力 + 速度阻尼，320 步冷却布局）；模块视图节点 = 二级以内模块（边 = 文件导入聚合权重）、组件视图节点 = 组件/Store（props 传递 / 文件导入 / useStore 三种边）；节点大小 ∝ 代码行数，Top 节点带标签；交互：拖拽节点实时重算、滚轮缩放、空白平移、点击聚焦邻接高亮、模块/组件视图切换、重新布局、重置视图
- **隐式 usesStore 边**：unplugin-auto-import 场景组件直接调用 `useXxxStore()` 而无 import 语句，静态导入图缺失该边——`vueAnalyzer` 组件 facts 新增 `storeCalls`（`useXxxStore` 经 useCalls + `globalStore` 等非 use 前缀形态经 setup storeVars）；builder post-pass 以**全局 Store 名单**匹配调用名解析为 `Component.storeIds`（零误报：不在名单内的 useXxx 不会误判）；blueprint `usesStore` 链接双向合并 import 与隐式使用；组件图谱 usesStore 边同步补齐

### 变更

- 模块统计粒度自动下钻：顶层分区过少（单 `src/` 根或融合仓库）时下钻一层，让粒度对齐真实代码分区（components/services 等）
- 代码图谱排除"全部代码聚合"的单一根模块巨型节点（与模块统计下钻逻辑对齐）；`hiddenModuleCount` 不再计入被排除的根
- 扩展名归一化：各分析器格式不一（ts: `tsx`；dart/rs: `.dart`），统一去点后再聚合语言分布

### 验证

- 189 个单元测试全过（viewer 新增 3 个：stats/codeGraph 模型聚合 + DOM stub 执行内嵌脚本渲染产物 + 隐式 usesStore 全链路；vueAnalyzer 新增 1 个：storeCalls 双命名形态）
- 四技术栈实测：nice-today-2.0（React+TS，1591 文件/55 万行）、nice-today-web-2.0（React+Tauri）、gameStore_flutter（Flutter+Riverpod，usesStore 11 条）、steam-stat（Vue3+Electron+Pinia，隐式边 0→43 条，31/207 组件解析出 storeIds 与源码 rg 结果一致）；力模拟坐标零越界零 NaN，产物自包含（无外部 CDN）

## [0.22.0] - 2026-08-23

### 新增

- **蓝图 CSS 主题化（`--theme`）**：三个蓝图查看器（部署 / 数据库 / 代码）的内嵌 CSS 拆分为「主题 token + 共享骨架 + 查看器专属布局」三段——布局骨架固定在 HTML 蓝图中，视觉风格按主题切换（参考 sglv-shared-css 的 CSS 变量 token 模式）
- **主题注册表**（`src/themes/index.js`）：`deep-blue`（深蓝暗色，GitHub dark 配色）、`fresh-green`（淡绿清新浅色主题：白卡片 + 深绿主色 `#1e7f5c`，语义色调深保证浅背景可读）与 `elegant-purple`（典雅紫暗色：深紫夜幕底 `#12101f` + 紫主色 `#a78bfa` + 粉紫点缀，玻璃质感）三个主题，`THEMES` 对象可无限扩展；`resolveTheme()` / `buildThemeCss()` / `listThemeNames()`
- **炫彩评分圆环**（`src/themes/ring.js`）：健康审计评分由普通 border 圆圈升级为 SVG 能量环（参考 steam-family-analysis 家庭健康分设计）——渐变弧（from → 分数插值色 → to）+ feGaussianBlur 辉光滤镜 + 圆头端点 + 顶部起始 + 1.1s 加载动画 + aria-label；客户端读取 `<html data-theme>` 按主题取环配色：deep-blue 紫→绿（分数越高越绿）、fresh-green 淡绿→深绿、elegant-purple 紫→粉
- **维度得分条渐变辉光**：健康审计维度进度条升级为渐变填充（`--bar-c` CSS 变量驱动 `color-mix` 渐变 + 辉光阴影）
- **共享骨架样式**（`src/themes/sharedCss.js`）：三个查看器完全一致的基础规则（body/header/统计卡片/标签页/面板/卡片网格/表格/搜索栏/健康仪表盘/响应式断点），透明色一律用 `color-mix()` 从主题变量派生，无硬编码 rgba
- **CLI `--theme` 选项**：`deploy export`（默认 deep-blue）、`db export`（默认 fresh-green）、`export`（默认 deep-blue）均支持主题切换，未知主题名报错并列出可选项
- **HTML 主题标记**：`<html data-theme="...">` + `:root[data-theme="..."]` 变量块，便于调试与未来 CSS 钩子
- **无障碍与原生控件适配**：主题块输出 `color-scheme: dark/light`（暗色主题下滚动条/输入框跟随）；圆环动画与卡片过渡在 `prefers-reduced-motion: reduce` 下自动禁用

### 变更

- **CSS 内硬编码透明色全部变量化**：`rgba(88,166,255,.4)` 之类改为 `color-mix(in srgb, var(--blue) 40%, transparent)`，主题切换时边框/悬停/热度色等透明变体自动跟随
- **代码蓝图（blueprint）语义色统一**：`--green` `#3fb950`→`#4ade80`、`--purple` `#bc8cff`→`#a78bfa`，与部署/数据库蓝图在 deep-blue 主题下完全一致（消除三个查看器间的历史色值漂移）；`--teal`/`--go` 作为代码蓝图私有变量经 `buildThemeCss` 追加
- **数据库蓝图（dataoverview）默认主题改为 fresh-green**：外观由深色变为淡绿浅色（用户场景示例"数据分析是淡绿色风格"），可通过 `--theme deep-blue` 切回深色
- 查看器专属布局样式（部署拓扑层 / ER 图 / UML 类图 / 路由树等）原样保留在各 viewer 中，DOM 结构与 class 名零改动——油猴脚本页面检测（`#deploy-viewer-data` / `#db-viewer-data` / `#viewer-data`）与 JS 渲染逻辑不受影响

### 验证

- 185 个单元测试全过（`test/themes.test.mjs` 13 个：注册表解析 / 变量块生成 / 私有变量追加 / 共享骨架完整性 / 三查看器默认主题与切换 / 圆环三主题配置 / 圆环客户端函数模拟 DOM 执行 / 产物注入）
- 产物逐份验证：deploy×deep-blue、db×fresh-green、db×elegant-purple、deploy×elegant-purple——每份含 `data-theme` 标记、圆环客户端代码、专属样式齐全
- 圆环端到端模拟：用产物内嵌真实健康数据（分数 90 / 等级 A）执行渲染，渐变端点、插值色、辉光滤镜、aria-label 全部正确
- CLI 错误路径：未知主题名 → 报错并列出 `deep-blue / fresh-green / elegant-purple`

## [0.21.0] - 2026-08-23

### 新增

- **部署配置目录分析（`nice-aos deploy`）**：扫描项目部署目录（如 `./deploy`），解析五类配置文件——docker-compose（`.yml`/`.yaml`）、K8s manifest（Deployment/Service/Ingress/ConfigMap/Secret/Job/CronJob）、Dockerfile（`Dockerfile*`）、nginx 配置（`nginx.conf`/`*.conf`）、环境文件（`.env`/`*.env`）与部署 shell 脚本，产出独立部署架构模型快照（`deploy-snapshot.json`，SHA-256 manifest 支持增量扫描）
- **服务归一化与跨文件合并**：compose 多文件（override/分环境）与 K8s manifest 定义的同名服务自动合并（名称归一化：小写 + 下划线转连字符；优先取首个非空值，数组字段取并集）；`${VAR:-default}` 镜像插值解引用取默认值，registry/版本 tag 提取兼容私有仓库长路径
- **服务类型识别与部署分层**：12 类服务类型（网关/前端/后端/适配器/任务/数据库/缓存/对象存储/搜索引擎/注册中心/可观测/CI-CD/工具，角色型模式优先于技术栈模式，`init`/`fixperms`/`flyway` 归任务层）+ 9 层部署分层（接入/前端/应用服务/适配器/任务/数据/可观测/CI-CD/工具）
- **nginx 路由与 upstream 解析**：`location → proxy_pass` 路由提取（含 upstream 名与服务名直连两种解析路径）、upstream server 列表解析、`auth_request`/websocket 标记；外部 https 目标识别
- **依赖推导**：三类依赖边——compose `depends_on`（启动依赖）、环境变量 URL 引用（`env_ref`，如 `SPRING_DATASOURCE_URL` 指向 mysql）、网关路由（`route`）；DFS 循环依赖检测与断链（引用不存在服务）检测
- **中间件识别**：MySQL/Redis/MinIO/Elasticsearch/Nacos/PostgreSQL 等 6+ 类中间件从镜像名识别（版本 tag 提取），自动推导消费方列表
- **5 大审计场景（`deploy audit`）**：安全审计（latest 镜像标签/明文敏感值/端口暴露/无鉴权路由）、高可用审计（健康检查缺失/探针/副本数/资源限额）、配置一致性审计（环境漂移——同名变量跨环境值不一致）、依赖审计（断链/循环依赖）→ 综合健康评分（加权四维，A-E 等级）
- **部署蓝图 deployoverview HTML（`deploy export --format html`）**：自包含 8 Tab——部署拓扑（分层卡片）/ 服务清单（搜索+类型过滤+详情展开：镜像/端口/探针/依赖/环境变量脱敏）/ 路由地图 / 依赖图谱（SVG 可缩放）/ 中间件矩阵（版本+消费方）/ 环境配置（变量统计+漂移对比）/ 文件清单 / 健康审计（评分仪表盘+Top 问题）
- **CLI**：`deploy scan`（全量/增量/`--exclude` 排除子目录）、`deploy query`（services/routes/upstreams/dependencies/middleware/environments/files/layers 8 类 + `--where` 过滤）、`deploy audit`（health/security/resilience/consistency/dependency/all）、`deploy export`（json/html/viewmodel）；全局 `--deploy-snapshot-dir` 选项
- **部署分析 Skill**（`skills/nice-aos-deployment-skill/SKILL.md`）与 **油猴 AI 助手部署智能体**：blueprint-ai-agent 自动检测 `#deploy-viewer-data` 页面并启用「部署蓝图」智能体（12 个专属工具：部署统计/服务查询/服务详情/路由/上游/依赖/中间件/环境/文件/分层/健康度/审计明细）

### 验证

- asdm-admin/deploy 冒烟：137 个部署文件 → 51 服务 / 33 路由 / 43 upstream / 52 依赖 / 6 中间件 / 9 分层 / 0 解析错误；健康评分 74（C），检出真实问题（compose 引用不存在服务、K8s latest 镜像标签、明文敏感值）
- leaniss-system-core/deploy 冒烟：29 个部署文件 → 19 服务 / 15 路由 / 5 中间件（MySQL/Redis/Milvus/Nacos 等）/ 0 解析错误；健康评分 85（B）；增量扫描复用验证通过
- 油猴智能体工具逻辑对导出 HTML 内嵌 JSON 全链路模拟验证：12 工具全部正确返回

### 增强（0.21.0 验证轮）

- **extra_hosts 外部目标识别**：compose `extra_hosts` 声明的主机名（如 `aise-smartanswer-web:%{{ADDR}}%`）视为网关静态映射的集群外服务，nginx 路由目标命中时标记 `externalHost`（原为未解析 null）
- **container_name 别名解析**：compose 服务的 `container_name`（容器网络 DNS 主机名，如服务 `jenkins` 的 `asdm-jenkins`）纳入名称解析表，nginx 路由 / upstream / 环境变量 URL 引用均可通过别名解析到所属服务
- 效果：leaniss 路由 15/15 全部有归属（12 服务 + 3 外部），依赖审计 88→100；asdm-admin `/_jenkins/` 路由与 `asdm-jenkins` upstream 正确解析到 jenkins 服务，未解析路由 2→1（仅剩真实未定义的 `asdm-portal`）
- **蓝图扫描时间本地化**：部署蓝图与数据库蓝图头部的"扫描于"时间由 ISO UTC 原文（`2026-08-23T00:30:48.328Z`）改为本地时间格式（`2026-08-23 08:30:48`），与代码蓝图的 `fmtLocalTime` 行为对齐
- **顶部统计栏宽屏布局**：部署蓝图与数据库蓝图头部的统计卡片由固定最小宽度左对齐改为 `flex: 1 1 90px` 弹性卡片（panel 背景 + 边框 + 圆角），任意宽度下均匀铺满整行，窄屏自动换行，与健康审计 Tab 的 metric-card 视觉语言统一

## [0.20.0] - 2026-08-23

### 新增

- **MySQL 迁移脚本目录分析（`nice-aos db`）**：Flyway 标准布局与 Sprint 非标准布局自动检测（`--layout`）、多数据库（`USE` 语句上下文跟踪、连字符库名 `[\w-]+`）、mysqldump 兼容（DROP+CREATE 表对按 DROP 先序处理防误删）、视图/触发器/存储过程解析、幂等 DDL 支持
- **数据库模型**：表/列/主键/外键/索引/迁移历史/领域分组/模式特征（软删除/审计字段/多租户/自引用/UUID 主键/复合主键/JSON 列），独立快照 `db-snapshot.json` 支持 SHA-256 增量扫描
- **7 大审计场景（`db audit`）**：健康度 / 迁移影响 / 领域依赖 / 索引优化 / 模型演进 / 外键链路 / 命名规范
- **数据蓝图 dataoverview HTML（`db export --format html`）**：8 Tab——SVG ER 关系图（领域分组+缩放平移）/ 表清单 / 外键关系 / 迁移时间线 / 建模特征 / 健康总览 / 演进分析 / 索引优化
- **数据库分析 Skill** 与 **油猴 AI 助手双智能体**（结构分析 + 数据概览，`#db-viewer-data` 页面自动检测）
- **CLI**：`db scan`（`--incremental` / `--all-files` / `--exclude`）、`db query`、`db audit`、`db export`；全局 `--db-snapshot-dir`

### 验证

- 120 迁移文件 / 82 表 / 33 外键 / 242 索引项目：健康评分 95，7 审计全过，HTML 8 Tab 正常
- leaniss-system-core/deploy/1.mysql：80 文件 / 106 表 / 6 数据库，非标准布局识别正确

## [0.18.2] - 2026-08-22

### 新增

- **数据路由 element 包装函数调用解析**：`element: withSuspense(X)` / `element: withPlatformGuard(X, 'platform')` / `createElement(Fn)` 等 HOC 包装形态——取第一个组件参数（Identifier 或 JSX）递归解析（steam-game-hub-2.0 惯例，43 条路由中 37 条使用）
- **数据路由 `<Navigate to>` 重定向提取**：element 内直接 `<Navigate to="/x" replace />`（index 兜底 / catch-all `*`）→ 路由导航边；`*` 相对 path 与父路径拼接为 `/*`
- **NavLink 常量成员引用解析**：数据驱动侧边栏 `{ path: ROUTES.DASHBOARD }` 形态——同文件 `const X = { KEY: '/value' }` 对象表解析 + named import 跨文件轻量解析（读模块文件 const 对象表，支持 .ts/.tsx/.js/.jsx/index.* 扩展探测）

### 验证

- 新增 `test/jsxRoutes.test.mjs` 用例（包装函数调用 / Navigate 重定向 / catch-all / 跨文件常量引用），总计 172 测试全过
- steam-game-hub-2.0（pnpm monorepo，Tauri + React）冒烟：494 文件 / 43 路由全部关联组件（41 组件 + 2 Navigate 重定向）/ 每路由 36 条侧边栏导航边（Steam + PS 导航组全集）；packages/* 6 个子包全部纳入；`../steam-client` workspace 根外条目正确不越界
- steam-game-library 回归：28/28/28（路由/关联/导航边）与基线一致

## [0.18.1] - 2026-08-22

### 新增

- **React Router 6.4+ 数据路由解析**（`createBrowserRouter` / `createHashRouter` / `createMemoryRouter`）：`[{ path, element, index, children }]` 对象树 → Route 实体，与既有 `<Routes>/<Route>` JSX 形态平级共存；`index: true` 以父路径产出，子级相对 path 与父路径拼接，有 `children` 的布局对象自身不产出（与 JSX 无 path 布局同语义）；`element` 解析支持 JSX / 括号包裹多行 JSX / 裸标识符，组件引用三级解析——import 引用 → `lazy(() => import('../pages/X'))` / `React.lazy` 包装变量（含 `.then((m) => ...)` 命名导出链，`import()` 经 ImportKeyword 识别）→ 本地包装函数（return JSX 最深组件递归展开）
- **`<NavLink to>` 导航边提取**（react-router）：字符串字面量与 `{ pathname: '/x' }` 对象形态计入 overlayOpens；**数据驱动侧边栏兜底**——`to={item.path}` 动态引用时提取同文件常量表（NAV_ITEMS 数组）中全部 `{ path: '/xxx' }` 字符串值为导航目标
- **布局外壳导航闭包**：数据路由布局对象的 componentFile 及其直接 import 的内部文件（如 Sidebar.tsx）的导航调用并入全部子路由的导航边——侧边栏导航对所有子页面可达

### 修复

- **数据路由 element 括号包裹**：`element: (\n <Suspense>...</Suspense>\n )` 多行 JSX 为 ParenthesizedExpression 包裹，此前组件解析返回 null——逐层剥离括号后解析

### 验证

- 新增 `test/jsxRoutes.test.mjs` 用例（数据路由端到端 + NavLink 字面量/动态兜底/非 react-router 排除），总计 172 测试全过
- steam-game-library 冒烟：Route 0 → 28（全部关联组件文件，`/` → LibraryPage.tsx 等 28 个页面全部建立路由地图条目与导航边，每页 27 个侧边栏可达目标）；既有 JSX 路由项目回归无变化

## [0.18.0] - 2026-08-22

### 新增

- **Vue 3 `<script setup>` 变量域解析**：setup 块内 `ref/shallowRef/reactive/shallowReactive/customRef/toRef` 声明 → state 键、`computed()` 调用 → computed 键、函数声明与 const 函数表达式 → method 键、`storeToRefs(...)` 解构名与 store 实例变量（`const userStore = useUserStore()`）→ store 键；store hook 命名兼容 `useXxxStore`（Pinia 官方惯例）与 `xxxStore`（snowy 的 globalStore/keepAliveStore 等）双形态，`storeToRefs(store)` 变量参数可溯源到真实 hook 名；变量域统一进入模板绑定 props 来源分类（此前 script setup 组件的 props 全被误判为 computed）与 vclass 类视图实体输入（stateCount 真实计数）
- **Store providerType 字段与展示**：Zustand（`zustand`）/ Pinia（`pinia`）/ Vuex（`vuex`）Store 实体携带 providerType；viewer「业务数据图」Store 卡片与单元清单、exporter Markdown 表格新增 provider 类型徽章/列
- **异步组件与路由 const 懒加载包装统一解析**：`const X = defineAsyncComponent(() => import('./x.vue'))`（Vue3 setup 惯用，unplugin-auto-import 场景无 import 记录不校验来源）与路由文件顶层 `const X = () => import(...)` 包装变量统一进组件解析索引——模板标签正常建立 renders 关系与 Props 传递边，路由 component 引用关联目标组件文件
- **Vite 动态注册死代码豁免**：`import.meta.glob(['/src/views/**.vue', '!/src/views/auth/**.vue'])` 模式采集（含 `!` 排除段、相对路径模式归一），命中文件豁免孤儿候选；vite.config.mjs 的 `unplugin-vue-components` `dirs`（自动注册组件目录）与 `unplugin-auto-import` `dirs` 目录内文件同样豁免（词法近似解析 vite 配置，`r('src/components')` 形式支持）
- **`<script setup name="X">` 组件命名**：vite-plugin-vue-setup-extend 的 name 属性优先于文件名派生（defineOptions 仍最高优先）
- **Ant Design Vue 排除**：`a-` 前缀标签（a-table/a-button 等）不进组件标签集与 props 传递链，与 element-ui `el-` 前缀同规则

### 修复

- **单行 SFC template 块丢失（定义性 bug）**：`splitSfc` 的 template 闭合配对正则误用行首锚定（`/^<(\/?)template\b/gm`），`<template>…</template>` 同行的单行 SFC 闭合标签不在行首导致 template 块整体丢失（无标签集、无传递链、无导航边）；改为非锚定配对（嵌套 template 深度计数不受影响）
- **路由组件关联断裂**：vue-router 路由文件中 `component: ClientLogin` 引用顶层 const 懒加载包装时无法定位目标 .vue 文件（此前仅支持内联 `() => import(...)`），路由地图出现无组件文件的路由条目
- **Go 解析五盲区（gin-vue-admin 形态对齐，参考 smartide-server / smartide-agent）**：
  - **子目录 go.mod 识别**：融合仓库（`server/go.mod` + `web/` 前端）根目录无 go.mod 时 framework 误判——从 `.go` 文件所在目录逐级向上发现全部 go.mod（多模块并存取源码最多者为主），Go 依赖并入、`goModule.dir` 记录所在目录
  - **`zap.Any` 等日志调用误报路由**：任意 `.Any("error", err)` 形态被误判为 gin 路由注册——引入 router 变量白名单（`gin.Engine`/`gin.RouterGroup` 类型变量与函数参数、`gin.New/Default` 产物），非 router 变量上的 HTTP 方法调用不产路由
  - **handler 字段链间接引用**：gin-vue-admin 惯例 `baseApi := v1.ApiGroupApp.SystemApiGroup.BaseApi` 后 `userRouter.POST("register", baseApi.Register)` 的 handler 无法关联 Method——字段链变量表展开为完整 `handlerChain`，链首 import 别名定位目标包子树内同名方法（纯分组目录如 `api/v1` 无直下文件时按子树搜索）
  - **前端 axios 配置对象形态调用丢失**：gin-vue-admin 前端 `service({ url: '/user/register', method: 'post', data })` 不产 httpCalls（此前仅支持 `service.get(...)` 方法形态）——封装实例（`@/utils/request` 导入的 axios 实例）配置对象字面量的 `url`/`method` 属性提取为前端调用并参与路由匹配
  - **深链调用链丢失**：`service.ServiceGroupApp.SystemServiceGroup.UserService.RegisterUser()` 形态的链长 ≥3 限定调用（链首为 import 包别名）不进调用链——`pkgchain` 边类型在目标包子树内按末段方法名搜索（词法近似）
- **项目根目录识别优化（定位任意目录均可分析）**：
  - **兄弟项目发现（siblingProjects）**：定位子项目目录（`web/`）或代码子目录（`web/src`）时，向上定位仓库根（`.git` / `go.work` / `pnpm-workspace.yaml` / `lerna.json` / `nx.json`，上限 4 层）后识别同级项目清单；普通目录（无仓库标记）不吸附邻居，只报告不并入扫描范围与依赖
  - **Go module 上级发现**：定位 Go module 子目录（如 `server/api`）时向上发现 go.mod（`goModule.dir` 以 `..` 相对形态表达），goResolver 以 `path.resolve` 折叠基准目录计算 import 目标——internal 文件边、路由 handler 关联、跨包调用链在子目录定位下全部正确
  - **依赖并入防误吸附**：无 `.git`、无根清单且子项目超过 4 个的「代码集合目录」（如工作区下并排放置的多个独立项目）只报告 subProjects 不并入依赖清单，避免框架误判与依赖污染

### 验证

- 新增 `test/vue3SetupFixes.test.mjs`（8 个用例）：script setup 变量域收集与七类来源分类（含 setup name 属性、storeToRefs 解构、vueOptions 扩展键集）、snowy 形态 store hook（无 use 前缀 + storeToRefs 变量参数溯源）、a- 前缀标签排除、路由顶层 const 懒加载包装组件关联、Pinia setup store（providerType + state/action 键）、Zustand providerType、defineAsyncComponent renders 关系与 Props 传递链、import.meta.glob + unplugin 目录孤儿豁免（含真孤儿保留）
- 新增 Go 盲区与根识别用例（`goAnalyzer.test.mjs` +1 / `goFrontendMap.test.mjs` +2 / `projectScanner.test.mjs` +4）：gin-vue-admin 形态单元测试（函数参数 router / 无前导斜杠 Group / handler 链展开 / 链式 Use / zap.Any 不误报）、融合仓库端到端（子目录 go.mod → framework=go + 依赖并入 + subProjects、handler 链解析到 Method、前端 service 配置对象匹配、深链调用链 Register→RegisterUser）、Go module 子目录定位端到端（`..` 折叠 → handler / 跨包调用链 / internal 文件边）、根识别四场景（定位 web/ 发现兄弟 server、定位 web/src 同样发现、定位仓库根不重复报告 + 代码集合目录防误吸附、定位 server/api 向上发现 go.mod）
- 总计 170 个测试全部通过（既有 162 无回归）；真实项目冒烟：
  - **snowy-admin-web**（Vue 3 + Vite + Pinia + Antdv + unplugin，597 文件）：206 条路由全部关联组件文件（const 包装修复生效）、67 条 PropEdge / 164 props（来源分布 computed×35 / handler×62 / state×41 / literal×23 / store×3——`kStore = keepAliveStore()` 无 use 前缀形态正确溯源）、9 个 Pinia store 全部携带 providerType=pinia（stateKeys 正确：globalStore 20 键等；纯 action store state=0 属实）、孤儿候选从误报收敛至 15 个（hooks/utils/403 页等真实候选）；端到端蓝图（snapshot.json 2.7MB + blueprint.html 746KB + blueprint.md 95KB），官方 CLI export 链路复核通过
  - **smartide-server**（gin-vue-admin 融合仓库，516 文件含 309 .go + web 前端）：`framework=go` 正确判定（子目录 `server/go.mod` 发现 + go 依赖并入 + subProjects 报告 server:go / web:npm）；96 条 gin 路由中 92 条 handler 解析到 Method（`baseApi.Register` 字段链展开生效；其余 4 条为 swagger/health/ws 内联闭包注册，如实无命名 handler）、`zap.Any` 无误报路由；**76 条路由命中前端调用**（web 前端 `service({url, method})` 配置对象形态提取生效）、40 条未匹配清单如实暴露；669 条调用边；定位 `web/` 前端子项目时 `framework=vue` + 兄弟项目 `server:go` 正确识别；端到端蓝图（snapshot.json 6.9MB + blueprint.html 563KB）
  - **回归**：aise-ui（Vue 2）198 PropEdge / 519 props / 159 vclass / 127 vue routes / 7 store 与 0.16.0 基线完全一致；leaniss-oneapi（Gin 融合仓库）124 路由 / 45 前端匹配 / 4 未匹配与 0.17.0 基线完全一致，定位 `router/` 子目录时 goModule 上级发现（dir=`..`）且 124 条路由完整解析；smartide-agent（Go CLI 代理）framework=go 正常；nice-today-2.0（React）831 Component 一致，PropEdge 414→429（+15 条为 React.lazy 包装组件此前无法解析、本期统一进解析索引的**预期增益**，lazy 页面的 props 传递补全）

## [0.17.0] - 2026-08-22

### 新增

- **Go 语言解析器（goAnalyzer，CLI / agent 代理 / Gin 后端）**：独立的轻量语法级解析器（深度状态机 + 双通道噪声剥离：全剥离通道供块状态机、保字符串通道供字面量提取，不依赖 gopls/tree-sitter），与 ts/vue/rust/dart 解析器平级共存，使 Go CLI、agent 代理类小程序与「Go 后端 + 前端」融合仓库可生成完整分析蓝图
  - **项目识别**：`go.mod` 存在且有 `.go` 源码 → `framework=go`（混合仓库前端文件仍各自解析）；`require` 段（分组块与单行形式，exclude/retract 块跳过）解析为 Dependency（`source=go`）；`vendor/`、`testdata/`、`bin/` 自动跳过
  - **实体映射**：`type X struct` → Class（`kind=struct`，字段含 `json/yaml` tag 与匿名内嵌——`*Base` 名取末段）、`type X interface` → Interface（嵌入接口 extends）；方法（值/指针接收者）与顶层函数 → Method（首字母大写判定 exported，`init/main` 恒为入口不判死）；package 目录 → Module；**Go 包 = 目录**：同包跨文件方法合并（接收者在另一文件声明的 `goOrphanMethods` 回填到声明文件）
  - **CLI 命令树（cobra）**：`var xxxCmd = &cobra.Command{Use/Short}` + `rootCmd.AddCommand(xxxCmd)` 边 → Route（`routeType=go-cli`，routePath 为 `smartide k8s init` 式命令链，多级树遍历 + 环防御）；`Flags()/PersistentFlags()` 注册的 flag 提取为 `-T/--type` 徽章；跨包限定子命令（`hostCmd.AddCommand(host.HostGetCmd)`）经 importMap 定位目标包目录归一；声明后从未注册的孤儿命令（如 smartide 被覆盖的 `help`）独立成根，天然暴露死代码
  - **HTTP 路由（Gin / 标准库）**：`router.Group("/api")` 前缀链累积 + `.GET/.POST/.PUT/.DELETE/.PATCH/Any("/path", ...)` → Route（`routeType=go`，apiMethods + `:param`/`*wildcard` 动态段标记）；handler 函数值（`controller.Register`）经 importMap 定位包目录关联到 Method（复用 componentId 机制，viewer 跳转方法签名）；组级 `apiRouter.Use(middleware.Auth())` 中间件按前缀链继承 + 内联中间件合并；`Handle("GET", ...)` 与 `http.HandleFunc` 标准库形式兜底；`NoRoute`/静态托管不产路由
  - **逻辑走向（调用链）**：包级函数跨文件互调（同包无需 import）+ `pkgAlias.Func()` 跨包调用（importMap 定位目标包目录）+ 方法体内调用（接收者变量/参数类型/构造字面量类型推断，`[]*User`/`map[string]T`/`*pkg.T` 归一为基类型）→ Method 的 `calls/calledBy`；Method 死代码候选按包级标识符引用判定
  - **前后端逻辑映射（融合仓库核心价值）**：tsAnalyzer 新增 httpCalls 提取——前端 `API.get/post/put/delete/patch('...')`、`axios.x()`、`fetch()`（含模板串 `` `/api/user/${id}` `` 与 options.method）→ 与 Go 路由路径分段匹配（`:param` 通配任意段、`*wildcard` 吞尾段、去 query、尾斜杠归一；method 不一致仍记录，详情可见）→ `Route.frontendCalls`（文件+行号+method 溯源）；未匹配调用进 `_meta.unmatchedFrontendCalls` 清单（路由地图「未匹配的前端调用」面板，用于发现死接口/路径漂移/外部 API 依赖）
  - **架构层（Go 目录信号）**：`main.go`/`cmd/` → entry，`router/controller/middleware/handler/api` → presentation，`model/dal/dao/repository/relay/service/biz/domain/monitor` → service，其余 → shared
  - **路由地图增强**：Go HTTP 路由（方法徽章 + 中间件链 + 前端调用数徽章与清单）与 Go CLI 命令（路径层级树按命令空格段嵌套，flags 见详情）统一进既有路由地图视图；域取首个业务段（跳过 `api`/`apis`/`v1` 网关前缀）；类型配色 go=天蓝、go-cli=灰蓝
- **README 对象表/框架检测/已知限制同步**：Project framework 枚举与 `goFileCount`、Interface/Class/Method/Route/Dependency 的 Go 语义说明、go.mod 框架检测、Go 已知限制（泛型不解析、调用链静态提取、Run 闭包不实体化、httpCalls 词法近似边界）

### 修复

- **蓝图实体类图语言配色缺 Go（Go 实体回退 TS/JS 蓝）**：`LANG_META` 缺 `go` 条目，Go struct/interface 的 UML 类框描边与头部底色回退为 TS/JS 蓝 `#58a6ff`，与图例语义冲突；语言分布条形图映射与图例均为硬编码四语言，Go 条同样回退蓝色且图例无 Go 说明。修复：`LANG_META` 新增 `go`（gopher 蓝 `#00add8`），CSS 新增 `--go` 变量与 `.bar.go` 类；图例改为**数据驱动**——按 `byLanguage` 实际语言从 `LANG_META` 取色生成，只显示项目内存在的语言（Go 项目不再显示无关的 TS/Vue/Rust 图例项），颜色与类图节点描边单一来源同源

### 验证

- 新增 `test/goAnalyzer.test.mjs` + `test/goFrontendMap.test.mjs`（13 个用例）：struct/interface/接收者方法提取（tag 字段/匿名内嵌/exported 判定）、调用链三类提取（pkg 跨包/local 同包/method 接收者推断）、cobra 命令树（Use/Short/flags/AddCommand 边）、gin 路由（Group 前缀累积/中间件/handler/动态段/标准库兜底）、go.mod 检测 + vendor 跳过、端到端实体入快照（Class/Method/Module/archLayer/依赖）、cobra→Route 命令链、gin→Route handler Method 关联、Go 项目 props 链为空不影响既有管线、tsAnalyzer httpCalls 提取、前端调用匹配（:param 通配/尾斜杠归一/未匹配清单）、viewer 路由地图视图模型（前端调用指标/go 类型分布/命令树字段）、viewer 实体类图 Go 配色（CSS 契约 + 内嵌脚本 mock DOM 渲染：图例 gopher 蓝圆点 / bar go 分布条 / 类框描边 `#00add8` / 不回退 TS 蓝）
- 总计 153 个测试全部通过（既有 140 无回归）；真实项目冒烟：
  - **smartide/cli**（cobra CLI）：161 个 .go 全扫（含根 `main.go` 入口识别），112 struct / 6 interface / 646 Method / 796 条调用边 / 114 个 go.mod 依赖；CLI 命令树 24 条命令链——`smartide` 根 + 16 个一级命令（init/start/new/stop/remove/version/list/get/host/reset/update/config/login/logout/connect/k8s）+ host×4 / k8s×2 子命令（最大深度 3：`smartide k8s init`）+ 孤儿 `help` 命令（声明后从未 AddCommand，真实死代码信号）
  - **leaniss-oneapi**（Gin + GORM + React 三主题融合仓库）：461 文件（222 .go + 239 .js）全扫，244 struct / 4 interface / 1224 Method / 994 条调用边 / 101 个 go 依赖；124 条 gin HTTP 路由（43 条 `:param`/`*target` 动态段、全部携带中间件链如 CriticalRateLimit/TurnstileCheck/AdminAuth）；**前后端映射 45 条路由命中前端调用**（三主题 default/air/berry 的 `API.get('/api/channel/...')` 等散落组件调用全部溯源到文件+行号）；未匹配前端调用 4 条（telegram oauth 登录 + 3 处 GitHub Releases 外部 API——如实暴露外部依赖）；`framework=go` 正确判定；端到端蓝图（snapshot.json 1.6MB + blueprint.html 561KB）
  - **回归**：nice-today-2.0（React）414 PropEdge / 831 Component 与 0.16.0 基线完全一致（tsAnalyzer 新增 httpCalls 不影响既有产出）

## [0.16.0] - 2026-08-22

### 新增

- **Vue2（Options API）props 传递链分析**：Vue SFC 模板绑定与 React JSX 属性同构接入 PropEdge 体系（`prop:A→B` 组件对聚合），「组件数据流」标签页对 Vue2 项目可用
  - **Options API 选项解析**：`export default {}` / `Vue.extend({})` / `defineComponent({})` 提取 `props`（对象/数组/混合形式，含 type）、`data`（对象/函数/方法形式）、`computed`/`methods` 键集、`components` 局部注册表；`mapState/mapGetters/mapActions/mapMutations` spread 展开提取 storeKeys（含 Vuex 模块名：双参 `mapState('app', [...])` 首参与箭头函数体 `state.app.x` 两种来源）
  - **模板绑定七类来源分类**：`:prop` / 静态属性 / 裸属性 / `v-model`（含 `v-model:arg`）/ `.sync` 修饰符剥离 / `@event`（事件回调记 handler，name 形如 `@save`）/ `v-bind="obj"` spread；表达式根标识符按 props → data → store → methods → computed 顺序判定，与 React 版语义对齐
  - **组件标签解析**（统一 helper）：局部 `components` 注册表 → import 索引（local 名 + PascalCase 双键，default 导入取目标文件 primary 组件）→ `main.js` 的 `Vue.component()` 全局注册兜底 → 同文件兜底；kebab-case 标签 / camelCase 导入名 / 文件派生名（`day.vue` ↔ `CrontabDay`）均可对齐
  - **排除规则**：element-ui `el-` 前缀、原生 HTML / Vue 内置标签（router-view/transition 等）不进传递链；`v-if/v-for/ref/key/class/style/slot` 等指令与 DOM 透传属性跳过
- **Vue 组件类视图实体（vclass）**：每个 `.vue` 文件 primary 组件合成为 `kind=component` 的 Class 实体（`vclass:X`，language=vue）——props 为字段（含 type）、computed + methods 为方法实体（`vmethod:X.key`）；组件 renders 组合关系回填为 vclass 间 rendersIds（目标同为 vclass 才成边，保持类图纯净），「实体类图」标签页对 Vue2 项目可用
- **类图 renders 组合边渲染**：UML 类图新增 renders 边（绿色实线箭头 + `arr-rnd` marker），区别于 implements（青色虚线）/ extends（紫色实线）；图例与统计 chips 同步展示 renders 边数；实体构造型标签新增 `«component»`
- **Vuex store 检测（Vue2）**：`/store/` 目录或导入 vuex 的文件，default export（对象字面量 / `new Vuex.Store({})` / RuoYi 风格 shorthand 引用顶层 const）提取 `stateKeys` + `actionKeys`（actions + mutations 合并）为 Store 实体（`providerType=vuex`），「业务数据图」对 Vuex 项目可用
- **router-link 静态导航边**：模板内 `<router-link to="/path">` 静态路径 → 文件所属路由 → 目标路由的 `navigatesToIds`（动态 `:to` 含表达式不可静态解析，跳过）；`this.$router.push('/path')` 调用同步支持
- **`@` 别名解析增强（projectScanner）**：vue.config.js 的 `configureWebpack.resolve.alias`（支持 `resolve('src')` / `path.resolve(__dirname, 'src')` / `'src'` 三种值形式）与 jsconfig.json paths 解析；存在 vue.config.js + `src/` 时兜底 `@/* → src/*`（vue-cli 惯例）
- **`build` 目录源码树豁免**：构建产物目录名 `build` 在 `src/` 源码树内不再跳过（如 RuoYi 的 `src/views/tool/build/` 业务源码目录）；`node_modules`/`.git` 等其余跳过项不受影响

### 验证

- 新增 `test/vuePropsChain.test.mjs`（8 个用例）：Options API 选项提取（props 定义/data/computed/methods/components/storeKeys 模块名）、模板绑定七类来源分类（forward/state/store/handler/computed/literal/spread + v-model/.sync/静态/裸属性 + router-link 导航）、@ 别名解析与 Vuex store 检测、PropEdge 聚合（局部注册 + 全局注册 + renderCount + 出入度 + passesProps）、vclass 类实体（props 字段/computed+methods 方法/renders 组合边）、路由导航边、viewer propFlow + 类视图 renders 渲染（mock DOM 执行内嵌脚本）、Vue 无绑定项目 propFlow 为 null 且 vclass 仍产出
- 总计 140 个测试全部通过（既有 132 无回归）；真实项目冒烟：aise-ui（Vue 2.6 + element-ui + vuex 3 + vue-router 3，268 文件）：unresolvedImports = 0（@ 别名全量解析）、198 条 PropEdge / 519 props（来源分布 state×269 / handler×141 / computed×78 / literal×27 / forward×3）、81 个组件 renders 关系、7 个 Vuex store（providerType=vuex）、162 个 vclass 实体（207 条 renders 组合边）、127 条 vue 路由 + 2 条 router-link 静态导航边；端到端蓝图（snapshot.json 1.6MB + blueprint.html 620KB）核验组件数据流 / 实体类图 / 业务数据图 / 路由地图四 Tab 数据齐备；nice-today-2.0（React）回归：414 PropEdge / 831 组件与 0.15.0 基线完全一致

## [0.15.0] - 2026-08-22

### 新增

- **Props 传递链分析（PropEdge 对象体系，React/Next 组件数据流）**：`.tsx/.jsx` 中 PascalCase JSX 标签的属性传递按**组件对聚合**为 PropEdge 对象（`prop:A→B`，19 种对象类型之一），回答"数据怎么在组件间流动"
  - **来源七类分类**（词法近似：组件声明范围 + 文件级变量表判定）：`forward`（父组件 props 透传）/ `state`（useState 解构）/ `store`（useXxxStore 等非内置 hook，附 storeHook 溯源）/ `handler`（内联或本地函数）/ `literal`（字符串/数字/布尔/裸属性）/ `computed`（其余表达式）/ `spread`（`{...obj}` 整体透传不展开）
  - **聚合规则**：同一组件对多处渲染聚合一条边（`renderCount`）；同名 prop 多来源取优先级最高者（forward > state > store > handler > computed > literal > spread）；路由库组件（Link/Navigate/Outlet 等）与 React 内部属性（key/ref/className/style 等）跳过，自渲染不成边
  - **Component 扩展**：`propsNames`（解构 props 名清单）与 `propOutCount`/`propInCount`（传递出入度）
  - **passesProps 链接**（第 22 种链接类型）：`link passesProps --src comp:X` 正向查传递目标；传 `prop:` 边 ID 返回两端组件
- **overlay 路由 props 工厂注入提取**：路由条目的 `props: (app) => ({ item: app.item })` 工厂函数提取注入键为 `factoryProps`（`hasPropsFactory` 标记），App → 工厂 → 页面组件的主干注入链在路由地图以「工厂 N props」徽章展示
- **蓝图查看器「组件数据流」标签页**（新 Tab，位于路由地图之后）：无 PropEdge 项目自动隐藏
  - **组件数据流总览**：传递边 / 参与组件 / props 总数 / spread 透传统计卡 + props 来源分布条形图（七类固定配色与判定说明）
  - **Props 传递图 SVG**：节点按 BFS 传递层数分层（无入边顶层容器 → 1 层 → 2 层…），节点边框色 = 所属域、顶层加粗，边中点标签 = props 数；悬停高亮相邻边、点击节点查看 props 明细（名称 + 来源 + store hook + 值摘要）与传出/传入清单；域下拉筛选 + 组件名/文件路径搜索（默认渲染连接度 Top 80 组件）
  - **高传出 / 高传入组件 Top 榜**：props 分发枢纽 vs 消费方（出入边数 + props 数 + 所属域）
  - **Props 传递边清单**：来源 → 目标 / props 数 / 渲染处 / props 明细（名称:来源）/ 跨域标记（80 条截断保护）
- **propFlow 视图模型**（`buildViewerModel` 新增第 9 节）：传递边（含来源分类与域归属）/ 参与组件出入度 / 来源分布 / 高传出高传入 Top 榜 / 域选项；`export --format viewmodel` 同步携带
- **Markdown 导出扩展**：项目概览新增「Props 传递边数」指标；路由表新增「工厂 props」列；新增「Props 传递链（PropEdge）」章节（汇总行 + 来源分布 + 传递边清单 + 高扇入组件统计）

### 验证

- 新增 `test/propsChain.test.mjs`（7 个用例）：tsAnalyzer 来源分类（七类全覆盖 + propsNames 声明提取）、PropEdge 跨文件聚合与出入度/passesProps 链接、多处渲染 renderCount 聚合与来源优先级、同文件组件对成边、overlay 工厂 factoryProps 提取、propFlow 模型层与渲染输出（mock DOM 执行内嵌脚本）、无 PropEdge 项目 Tab 隐藏
- 总计 132 个测试全部通过；真实项目冒烟：nice-today-2.0（414 条传递边 / 434 组件 / 1120 props，来源分布 computed×323 / handler×273 / state×184 / forward×150 / literal×151 / store×39；高传出 App(23)/HealingTab(18)，高传入 CopyTextButton(27)；SettingsOverlay→SettingsSection 16 个 forward props 批量透传等典型形态命中）与 next-web-app（3 条传递边，服务端组件为主的项目如实稀疏）

## [0.14.0] - 2026-08-22

### 新增

- **蓝图查看器「路由地图」标签页**（新 Tab，位于业务逻辑流向与实体类图之间）：把 0.13.0 扩展后的全类型路由（overlay / react-router / vue-router / Flutter GoRoute+原生 / Next.js App Router）组织为可视化地图；无路由项目自动隐藏 Tab
  - **路由总览**：路由总数 / 导航边 / 入口路由 / 孤岛路由 / 动态路由 / API 路由统计卡 + 路由类型分布条形图（每种类型固定配色：overlay 紫、react 蓝、vue 绿、flutter 青、next 页面琥珀、next API 红）
  - **路由导航链 SVG 图**：节点按导航跳数 BFS 分层（入度 0 的入口路由为第 0 列 → 1 跳 → 2 跳…，环内/未覆盖节点沉底），节点框两行（路径 + 类型·组件），边框色 = 路由类型、入口加粗；悬停高亮相邻路由与导航边、点击查看路由详情（域 / 动态段 / use client·server / API 方法 / layout 层数 / 组件与文件 / 导航去向）；超过 60 条路由按导航活跃度截断
  - **路径层级树**：路由 path 逐段嵌套的缩进树（每节点独立子段索引，不同分支同名段不合并），静态段在前动态段在后、动态段琥珀色高亮，节点标注类型徽标与主组件名
  - **域分组**：按路由 domain 聚合（每组路由清单 + 组件 + 导航去向）
  - **全量路由清单表**：路径 / 类型 / 域 / 组件 / 导航去向 / 被导航 双向列（150 条截断保护）
- **routeMap 视图模型**（`buildViewerModel` 新增第 8 节）：路由条目（path/routeType/domain/componentRef/componentFile/isDynamic/isClient/apiMethods/layoutCount/navToPaths）、导航边（去重 + 自环剔除）、入口/孤岛路由计数（入度 0 且有出边 = 入口；无出入边 = 孤岛）、类型分布、域分组、路径层级树（maxDepth）；`export --format viewmodel` 同步携带
- **Markdown 导出路由地图扩展**：路由表新增「类型 / 动态 / client / API 方法」四列（Next.js App Router 语义），表头前增加类型分布汇总行；页面导航图章节增加入口路由（无入边）清单

### 验证

- 新增 `test/routeMapView.test.mjs`（3 个用例）：routeMap 模型层契约（清单/导航边去重/入口孤岛计数/类型分布/路径层级树嵌套与动态段排序/域分组）、渲染输出（mock DOM 执行内嵌脚本，断言路由总览/导航链 SVG/层级树/域分组/全量清单与动态段高亮）、无路由项目 routeMap 为 null 且 Tab 隐藏
- 总计 125 个测试全部通过；真实项目冒烟（含内嵌脚本运行时渲染验证）：next-web-app（6 路由/5 导航边/1 入口，六域分组）与 keylol_discourse_app（14 路由/6 导航边含自环剔除/1 入口/7 孤岛，`/space/*` 二级树深）

## [0.13.0] - 2026-08-22

### 新增

- **Next.js App Router 路由提取（`nextAppAnalyzer`，文件约定式路由）**：`framework=next` 且存在含约定文件（`page/route/layout`）的 `app/` 或 `src/app/` 目录（后者优先）时自动提取，Route 对象体系新增 `next` / `next-api` 两个 routeType：
  - **页面路由**：`app/**/page.tsx` → Route（`routeType=next`），URL 按目录约定计算——路由组 `(group)` 与平行路由 `@slot` 段剔除出 URL、`_private` 段整目录不产出路由、`[id]` → `:id`、`[...slug]` → `:slug*`、`[[...slug]]` → `:slug?`（`isDynamic` 标记）；`rawPath` 保留原始目录段
  - **API 路由**：`app/**/route.ts` → Route（`routeType=next-api`），导出的 `GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS` 方法名收集为 `apiMethods`
  - **layout 链**：`layout.tsx` 不单独成路由，而是沿真实目录链（外→内，含路由组层）收集进后代路由的 `layoutFileIds`；`loading/error/not-found/template/global-error` 约定文件记入 `specialFiles`
  - **客户端标记**：page/route 文件头 `'use client'` 指令探测为 `isClient`；组件关联沿用 `componentId`/`componentFileId`，page 组件 kind 自动升级
  - **跳转边**：tsAnalyzer 识别 `next/link` 的 `<Link href>`（字符串或 `{ pathname: '/x' }` 对象形式）计入 overlayOpens → 目标路由的 `navigatesToIds`；导航边仅归属 page 文件（layout/共享组件文件内的 Link 不归属，避免边爆炸）
- **Flutter 原生路由表提取（`dartAnalyzer` 扩展）**：`Map<String, WidgetBuilder> routes = { '/x': (ctx) => const XxxPage() }`（MaterialApp `routes:` 命名路由）条目提取，`routeType=flutter`——与 GoRoute 路由并存合并；深度感知条目扫描（仅 map 体顶层的 `'path':` 视为条目键，builder 体内字符串如 `arguments: {'tid': x}` 不误判），值取最后一个大写构造调用（与 GoRoute builder 一致）
- **Flutter 命名路由跳转边**：`Navigator.pushNamed / pushReplacementNamed / popAndPushNamed / restorablePushNamed`（`Navigator.of(context).pushNamed('/x')` 与 `Navigator.pushNamed(context, '/x')` 两种形式）→ 该文件组件所属路由 → 目标路由（`navigatesToIds`）

### 验证

- 新增 `test/nextRoutes.test.mjs`（4 个用例）：App Router 路由提取（页面/API 路由、动态段/路由组/私有目录归一、layout 链、`'use client'` 探测、apiMethods）、`src/app` 优先定位、Link href 跳转边（字符串 + pathname 对象形式 + 自环剔除）、路由条目字段契约（rawPath/specialFiles/componentFileId）
- 新增 `test/dartNativeRoutes.test.mjs`（3 个用例）：原生 routes Map 条目提取（builder 体内字符串不误判 + 块体 builder）、pushNamed 两种调用形式跳转边、GoRoute 与原生 routes 并存合并
- 总计 122 个测试全部通过；真实项目冒烟：next-web-app（6 条 next 路由 + 5 条导航边，`/` 页面 Link 五向导航全解析）与 keylol_discourse_app（14 条 flutter 路由 + 7 条导航边，含 `/space/friends|threads|posts` 同组件多路由与 `/settings → /log,/about` 二级导航）

## [0.12.0] - 2026-08-22

### 新增

- **`serve` 命令（本地数据源 HTTP 服务）**：把本体快照与蓝图暴露为 CORS 就绪的 HTTP 端点，供 AI agent / 油猴脚本 / 网页跨源拉取——解决油猴脚本在 `file://` 协议下无法读取 `snapshot.json`、以及 AI agent 消费快照需要自起服务的问题
  - 端点：`GET /snapshot.json`（完整快照）、`GET /blueprint.html`（蓝图页面）、`GET /api/status`（目录解析结果 + 快照/蓝图就绪状态 + 端点清单）、`GET /api/stats`（项目名/框架/对象计数/循环依赖/死代码候选）、`GET /`（状态首页 HTML）；全端点 CORS `*`，`OPTIONS` 预检放行
  - 目录解析链：`--dir` → 全局 `--snapshot-dir` → `NICE_AOS_SNAPSHOT_DIR` → `<root>/.nice-aos/data`；`--root` 定位 blueprint.html（默认当前目录）；默认监听 `127.0.0.1:8420`（仅本机，`--host 0.0.0.0` 开放局域网），`--port 0` 自动分配可用端口
  - 就绪状态**每次请求实时探测**：支持"先起服务、后 `refreshRepo` / `export`"工作流（文件随后生成即可读，无需重启服务）；快照缺失返回 404（附生成指引）、JSON 损坏返回 500
  - `snapshot.js` 新增 `getSnapshotDirOverride()`：serve 经其读取全局 `--snapshot-dir`（preAction 钩子写入的覆盖值）对齐约定——不在 serve 上重复定义同名选项（Commander 中子命令选项与全局选项重名时值会被父命令吞掉）
- **`contrib/blueprint-ai-agent`（蓝图页 AI 代码分析助手，油猴脚本，按需集成）**：在 `blueprint.html` 右下角注入浮窗按钮，点击展开对话侧边栏，对项目本体（模块/组件/Store/Service/路由/接口/类/方法/功能域/死代码）自然语言问答；不进入 npm 包分发
  - 架构借鉴 steam-ai-agent：**ToolRegistry + ReAct 文本协议**（`<tool_calls>` 纯文本约定，规避各厂商 function-calling 差异），低成本支持多模型接入：内置 DeepSeek / 智谱GLM / 通义千问 / Kimi / 豆包 / OpenAI 预设 + 自定义（任意 OpenAI 兼容地址）
  - **9 个代码分析工具**：getStats（统计总览）/ queryObjects（按类型查询）/ getNodeDetails（对象详情）/ listLinks（关系与反向引用）/ getDomainDetail（功能域构成）/ analyzeFile（单文件分析）/ getArchLayers（架构分层分布）/ findDeadCode（四级死代码候选）/ getProjectContext（当前页面视图上下文）
  - **双数据源**：优先页面内嵌 `viewer-data`（`export --format html` 自带，零依赖离线可用）；「设置」可配本地快照 HTTP 地址（`GM_xmlhttpRequest` 拉取，推荐 `nice-aos serve` 一行启动）
  - 新建会话 / 会话历史（重命名/删除/清空）、会话导出 JSON（可恢复）/ Markdown、流式打字输出、工具调用可视化、可中断生成；仅在内嵌 viewer-data 或存在 `#viewer` 容器的蓝图页自激活，其它页面无副作用

### 修复

- **蓝图查看器实体类图文字不可读（黑字融深色背景）**：UML 类框样式选择器误写为后代形式 `svg .uml ...`，但 `.uml` 类名挂在 `<svg>` 根元素自身上，选择器永远失配 → 类名/构造型/字段/方法文字回退 SVG 默认**黑色填充**，与深色背景融合不可读（类框边框颜色走内联 `stroke` 属性"看似正常"，列标签走 `svg .col-label` 也正常，问题更隐蔽）。改为复合选择器 `svg.uml ...` 后，文字填充（`--fg`/`--fg-dim`/`--fg-faint`）、类框体填充与分隔线描边全部正确生效
- **蓝图页头「生成于」显示 UTC 标准时间**：快照 `_meta.generatedAt` 存 UTC ISO 串，页头原样 `.replace('T',' ').slice(0,19)` 展示，东八区下与实际生成时间差 8 小时。查看器内嵌脚本新增 `fmtLocalTime`，在浏览器查看时转换为本机时区；Markdown 导出报告的「生成时间」行同步修复（快照数据仍存 UTC ISO 不变，展示层转换，无歧义）

### 验证

- 新增 `test/serve.test.mjs`（3 个用例）：端点契约（默认目录解析 + CORS + 各端点响应 + OPTIONS 预检 + 404 JSON 错误体）、快照后生成实时可见回归（启动无快照 404 → 运行中生成即 200 → 损坏 500 → 蓝图后生成即读）、`--dir` / `--snapshot-dir` 别名显式指定目录
- 新增 `test/viewer.test.mjs` 2 个回归用例：实体类图 CSS 选择器（禁止失配的 `svg .uml ` 后代形式 + 文字填充断言）、页头生成时间本机时区显示（mock DOM 执行内嵌脚本，断言 v-sub 文本含本地时间且不含 UTC 原文，时区无关写法）
- 总计 115 个测试全部通过；默认端口 8420 人工冒烟：全端点响应、CORS 头、启动横幅正常（Windows 系统保留端口段会导致 EACCES 启动失败，撞上时用 `--port` 换端口即可，报错信息已给出指引）；steam-game-library 真实项目重新生成蓝图验证两处修复（旧页头显示 UTC `03:42:11` → 新显示本机时区 `11:42:11` 与系统时间一致，CSS 选择器修复后文字填充生效）

## [0.11.0] - 2026-08-21

### 新增

- **Dart/Flutter 实体分析器（`dartAnalyzer`，lib/ 组件）**：与 tsAnalyzer/vueAnalyzer/userScriptAnalyzer/rustAnalyzer 平级共存的轻量语法级解析器（深度状态机 + 等长噪声剥离 + 注解行剥离，不依赖 dart analyzer），实体映射对齐 TS 语义：
  - `abstract class` → Interface、`class/enum/mixin` → Class（extends/implements/with 关系、字段、方法、构造器不实体化为 Method）；类方法/顶层 fn → Method（含 `@override` 标记、signature 归一）
  - **Widget 基类（StatelessWidget/StatefulWidget/ConsumerWidget 等）→ Component**（kind: page/widget、dartdoc 描述、文件名匹配主组件）；**ChangeNotifier/Notifier 子类与 Riverpod Provider 变量（`final xxxProvider = NotifierProvider<...>.new(X.new)`，含 notifierClass 提取）→ Store**（stateKeys/actionKeys）
  - `GoRoute(path/builder)` → dartRoutes：路径常量引用回填（`path: AppRoutes.dashboard`）、builderWidget 跨文件组件解析；`context.go/push('/path' 或 AppRoutes.xxx 常量引用)` → overlayOpens → 路由导航边（`navigatesToIds`）
  - **方法逻辑调用链**：方法体内调用 → callEdges → `Method.callIds/calledByIds/compCallIds`——本类方法/顶层函数/跨文件静态方法（importMap 解析 + 全仓库唯一名兜底）双向链接，Widget 构造调用 → compCallIds 渲染链
- **Flutter 项目扫描与框架识别**：`pubspec.yaml` + `lib/` 自动发现（monorepo 多包递归发现）并把 `lib` 纳入扫描（`.dart`）；pubspec 依赖解析（`flutter: sdk: flutter` 无值键拼接）；`framework=flutter`（依赖含 flutter sdk）/ `framework=dart`（纯 Dart 包）；`frameworkVariants` 新增 riverpod/provider/bloc/getx/go_router；`Project` 画像新增 `dartFileCount`/`flutterDetected`；SKIP_DIRS 新增 android/ios/.dart_tool/linux/macos/windows
- **导入解析扩展**：`package:` 导入（自身包名 → 项目内 lib/ 路径，其余 → pub 依赖）、`dart:` 内置库跳过、Dart 无 `./` 前缀裸相对导入解析
- **蓝图查看器 Dart 展示**：语言标签/分布新增 Dart（teal 色系）；实体类图节点方法与详情面板新增「方法调用链」（方法 → 调用目标/被调方，Widget 渲染链并入）；Route 一览支持 Flutter GoRoute 展示
- **Markdown 导出 Dart 统计**：项目概览新增 Dart 文件数行、Store/路由/依赖标题含 Riverpod/GoRoute/pub；接口/类表语言列支持 Dart；新增「方法调用链 Top 30」章节（出边数/被调用数/调用目标/调用组件）
- **`action analyzeFile` 支持 .dart**（dartAnalyzer 链，输出 Interface/Class/Method）

### 修复

- dartAnalyzer：`@override` 等注解行破坏块状态机行首分类导致带注解方法（如 build）被吞（stripAnnotations 预处理，方法数显著修复）；类构造器误判为 Method；Riverpod Provider 变量正则转义失效（模板字面量 `\\s` 产生字面反斜杠）；`NotifierProvider<X, Y>.new(X.new)` 形式 notifierClass 提取；`_PrivateWidget(...)` 私有类构造归类为 self 调用；signature 残留 CRLF
- dartAnalyzer：GoRouter 常量引用导航（`context.push(AppRoutes.xxx)` / `context.go(home)`）未解析为导航边（navRe 仅匹配字符串字面量参数；顶层函数的导航调用未进 overlayOpens）——扩展参数形式为字符串/常量引用两种，builder 侧用全仓库路由常量表（常量名 → path）跨文件回填，动态变量（`feature.route`）查不到即忽略

### 验证

- 新增 `test/dartEntities.test.mjs`（12 个用例）：Widget 组件/`@override` 回归/抽象类→Interface/ChangeNotifier→Store/Riverpod Provider 变量/GoRoute 常量回填（含 `context.push(AppRoutes.xxx)` 常量引用导航）/调用链分类/Flutter 框架识别/实体入快照/导航边（字符串 + 跨文件常量回填双向）+调用链/蓝图视图模型/Markdown 导出
- 总计 110 个测试全部通过；真实项目回归：steam-game-flutter（186 文件/89 组件/63 Store/15 路由/1624 方法，175 个方法产生 250 条调用边）与 nice-today-flutter（121 文件/103 组件/12 Store/47 路由/1608 方法，288 个方法产生 364 条调用边、4 条路由导航边含 `AppRouter.xxx` 常量回填）

## [0.10.0] - 2026-08-21

### 新增

- **`update` 命令（版本检测与一键升级）**：`nice-aos update --check` 仅检测（输出 JSON：current / latest / upToDate / installMode / upgradeCommand，供 agent 与 CI 前置校验）；`nice-aos update` 一键升级——npm 全局安装时自动执行 `npm install -g nice-aos@latest`，升级后读回磁盘版本复核
- **安装模式自动判定（`installMode`）**：`global`（npm 全局安装，可一键升级；`npm install -g <本地目录>` 的符号链接形式同样识别，升级时替换为 registry 版本并在消息中说明）/ `npx`（缓存运行 → 指引 `npx nice-aos@latest` 拉新）/ `local`（项目依赖 → 指引宿主项目内升级，不改写 package.json）/ `repo`（仓库源码运行 → 跟随 git）。检测采用双信号互补：调用入口路径（`process.argv[1]`，保留符号链接——覆盖 `npm link` 与本地目录全局安装）+ 模块真实路径布局（`lib/node_modules/nice-aos` 强信号，覆盖 homebrew / nvm 多 prefix 环境，PATH 中 npm prefix 与实际安装 prefix 不一致时仍可正确判定）
- **Skills 版本检测要求**：三个 skill（nice-aos / nice-aos-userscript / nice-aos-deadcode）前置条件统一增加"npm 包方式必须先 `update --check` 确认最新版"约定——`upToDate=false` 且 global 时执行 `nice-aos update`；`update` 命令不存在说明版本 < 0.10.0，先 `npm install -g nice-aos@latest` 一次性升级；网络不可达返回 `ok:false` 时跳过升级不阻塞主流程；仓库内源码方式版本跟随 git 无需检测。核心 skill 的 Agent 行为规范表同步增加版本校验行

### 验证

- 新增 `test/update.test.mjs`（2 个用例）：compareVersions 三段语义版本比较（含 0.9.0 < 0.10.0 非字符串比较）、`update --check` 输出 JSON 契约（离线时容忍 `ok:false` 形状）
- 真实环境回归：仓库源码模式（repo）、npm 全局符号链接安装（global + linkedInstall）、多 prefix 全局布局（`lib/node_modules` 布局模拟 homebrew）、一键升级端到端（0.8.0 → 0.9.0，符号链接替换为 registry 版本且磁盘版本复核一致）
- 总计 98 个测试全部通过

## [0.9.0] - 2026-08-21

### 新增

- **Rust 实体分析器（`rustAnalyzer`，Tauri src-tauri 组件）**：与 tsAnalyzer/vueAnalyzer/userScriptAnalyzer 平级共存的轻量语法级解析器（深度状态机 + 等长噪声剥离，不依赖 rustc），实体映射对齐 TS 语义：
  - `pub struct`/`pub enum` → Class（kind: struct/enum，含 fields/derives/variants）；`pub trait` → Interface（supertrait → extends）；`impl` 块内 `fn` → Method（ownerKind=class，含 trait impl 关联）；模块级 `fn` → Method（ownerKind=module）
  - 跨文件路径解析：`use crate::a::B` 模块路径映射为主（`crate::a::b::Name` → `<crateRoot>/a/b.rs` 或 `a/b/mod.rs`）、`super::` 相对路径、全仓库唯一名匹配兜底；支持 `use a::{B, C}` 花括号组与 `use crate::x::*` 通配（目标文件及同目录整体豁免死代码判定）
  - Rust 类型引用即使用（`Vec<Game>` / `-> Game` / `impl Game` 均计入引用），Interface/Class 同样参与类型级死代码判定
- **客户端组件自动发现（Tauri/Electron）**：显式 roots 之外自动发现桌面客户端组件——`src-tauri/tauri.conf.json` 存在时把 `src-tauri/src` 纳入扫描，`electron/` 目录含 TS/JS 文件时纳入扫描；`Project` 画像新增 `rustFileCount`/`tauriDetected`/`electronDetected`/`language`（如 "TypeScript + Rust"），`frameworkVariants` 携带 tauri/electron；架构层新增 `tauri`（Rust 原生层）与 `electron`（主进程层）路径强信号直判；Java/Go 等后端代码不入扫描范围
- **实体类图（蓝图查看器新 Tab）**：UML 风格 SVG 类图——类框（名称 + 字段/变体 + 方法摘要，Rust struct 含 derives 徽标）、关系边（implements 虚线 / extends 实线 / 接口继承）、按派生层级分列布局；语言（TS/Vue/Rust）/类型/架构层分布条形图与跨语言关系计数；模块/类型/语言/关键词过滤 + 实体清单表格（字段/方法/关系度/实现继承/死代码状态）；图节点选取：关系活跃实体优先 + 各语言代表性实体按成员规模轮转补齐（无继承关系的 Rust struct 也能进入类图）；悬停高亮相邻节点与关系边、点击查看实体详情

### 变更

- 扫描扩展名新增 `.rs`；SKIP_DIRS 新增 `target`（Rust 构建产物）；`.backup` 后缀文件跳过
- Markdown 报告实体表格新增「语言」列（TS/Rust），项目概览新增 Rust 文件计数行
- 本体蓝图对象描述更新：Interface/Class/Method 标注 Rust 同构映射（trait → Interface、struct/enum → Class、impl fn → Method）

### 验证

- 新增 `test/rustEntities.test.mjs`（10 个用例）：struct/enum/trait 提取（derives/字段/变体/方法合并）、use 花括号组解析、crate/super 路径映射、通配 use 豁免、Rust 死代码判定
- 新增实体类图 viewmodel/渲染测试（跨语言 UML 类框 + 关系边 + 内嵌脚本可执行）
- 总计 96 个测试全部通过；真实项目回归：steam-game-hub-2.0（React + Tauri，44 个 .rs 文件 → 151 个 Rust 实体入图）与 steam-stat（Vue + Electron，electron/ 目录 7 文件入 electron 层）

## [0.8.0] - 2026-08-21

### 新增

- **单文件分析（`action analyzeFile`）**：不落盘、不建快照，stdout 直接输出 dataMap 形状 JSON（`_meta.mode === 'single-file'`），可与 jq/findstr 管道组合——独立油猴脚本与单个 TS/JS/Vue 文件的零成本体检入口：
  - 参数：`{"file": "path/to/x.js"}`（相对 cwd 或绝对路径；支持 .ts/.tsx/.js/.jsx/.mjs/.vue 与油猴脚本）
  - 油猴文件自动路由 userScriptAnalyzer 链（输出 UserScript/GmApiUsage/InjectionPoint/NetworkEndpoint/ScriptFunction 五类），其余走 tsAnalyzer/vueAnalyzer 链（输出 Interface/Class/Method）
  - 单文件模式判定边界：仅"本文件内零引用"的非导出实体判死；导出实体一律不判死（单文件视角无法判定跨文件使用）
- **导出级死代码（unusedExports）**：每文件导出符号 × 全仓库具名导入对照 → `SourceFile.unusedExports[]` + `_meta.deadExportCandidates` 汇总；入口文件、re-export 链、动态 `import()` 引用保守豁免。死代码检测升级为**四级**（文件级 orphanCandidates / 导出级 unusedExports / 类型级 Interface/Class deadCandidate / 函数级 Method/ScriptFunction deadCandidate），`Project.health` 汇总四级计数（orphanFileCount/deadExportCount/deadTypeCount/deadFunctionCount）
- **油猴脚本函数级死代码（ScriptFunction.deadCandidate）**：复用保守引用计数规则——函数名全文出现次数（排除声明处与自身函数体）为 0，且 calledByCount=0、非 topLevelCalls 命中、非 constructor、非事件回调角色、未暴露到 unsafeWindow → `deadCandidate/deadReason`；UserScript 画像新增 deadFunctionCount
- **Skill 拆分（3 个）**：场景工作流从 CLI/Skill 分离——`nice-aos`（核心查询：快照/影响分析/蓝图导出，瘦身重写）、`nice-aos-userscript`（油猴审计：GM 越权/@connect 白名单/XSS 面/风险分级五步工作流 + 实战修复模板）、`nice-aos-deadcode`（四级死代码清理：检测 → 分级复核 → 清理 → 验证）。三者共享同一份 CLI 与快照；CLI 保持原子普适，场景编排全部下沉 Skill
- Markdown 报告「死代码候选」升级为四级章节（文件级 + 导出级 + 类型级 + 函数级，函数级含油猴 ScriptFunction）

### 变更

- 快照目录约定统一为 `<REPO_ROOT>/.nice-aos/data`（CLI 默认回退链第一候选），三个 skill 共享一份快照；`--snapshot-dir` 显式传参与 `NICE_AOS_SNAPSHOT_DIR` 环境变量继续支持任意路径
- package.json `files` 追加 `skills/**/SKILL.md`（npm 包携带三个 skill 文件，`npx nice-aos` 用户可直接取用）

### 验证

- 新增 `test/deadcode.test.mjs`（7 个用例）：导出级命中与豁免（入口文件/本文件使用不误报）、ScriptFunction deadCandidate（零引用死函数命中、事件回调/被调用函数不误报）、analyzeFile 动作（TS 文件与油猴文件输出形状、不落盘）
- 总计 85 个测试全部通过；自举验证（refreshRepo + query Class/Method 定位自身代码）与真实项目回归见发布流程

## [0.7.0] - 2026-08-21

### 新增

- **类型体系实体化（Interface / Class / Method）**：解决"实体关系记录在实现类中、从接口正向 query 不到"的断层，对象类型 15 → 18 种（均归 CodeUnit/L1 单元层）：
  - **Interface（`iface:`）**：接口全量提取（含 `.d.ts`），携带方法签名、extends 继承链、导出标记
  - **Class（`class:`）**：类全量提取，携带 implements/extends 关系、单例标记（`static getInstance`）、方法清单
  - **Method（`method:`）**：类方法、接口方法签名、模块函数（顶层声明 + const 箭头函数）统一为方法实体；ownerKind（class/interface/module）、isStatic/isAsync、展示用签名；ID 约定 `method:<file>#<Owner>#<name>`（模块函数 `method:<file>#<fnName>`）
- **实现/继承/覆盖关系（链接 15 → 21 种）**：
  - `implements` / `implementedBy`：Class ↔ Interface 双向——`link implementedBy --src "iface:src/types/storage.ts#IStorage"` 直接列出接口的全部实现类
  - `extends` / `extendedBy`：接口与类的继承链双向
  - `overrides` / `overriddenBy`：方法级覆盖双向——接口方法 → 全部实现类方法；子类方法 → 被覆盖的父类/接口方法
  - 跨文件解析：本文件声明优先，其次具名导入（含 `import type` type-only 导入与 `IStorage as StorageContract` 别名导入，按 imported 名定位目标文件导出）；解析失败留存原名不报错
  - `contains` 扩展：`file:` 可下钻类型实体，`iface:`/`class:` 可下钻其方法
- **函数级/类型级死代码候选（保守判定）**：在文件级 orphanCandidates 之外新增两级——非导出实体本文件零引用、导出实体全仓库零导入且本文件零引用 → `deadCandidate/deadReason`；引用计数排除声明处与自递归；接口方法为契约声明永不判死；命名空间导入 / `export *` / 动态 `import()` 目标文件整体豁免。健康指标（Project.health）新增 deadTypeCount / deadFunctionCount
- **Markdown 报告新章节**：「接口与实现」（接口清单 + implementedBy 实现类 + 方法覆盖矩阵，契约方法未被覆盖时标 ⚠️）、「类与方法」（类清单含 implements/extends/单例 + 契约热点 Top 30）、「死代码候选」升级为三级（文件级 + 类型级 + 函数级）；修复表格内联合类型 `|` 撑破列的转义问题

### 验证

- 新增 `test/typeEntities.test.mjs`（11 个用例）：tsAnalyzer 事实提取（接口继承/方法签名/类 implements/静态异步方法/模块函数与引用计数）、端到端实体构建与 objectCounts、跨文件 implements（type-only + 别名导入）、类继承与方法级 overrides 双向、blueprint 六种新链接、保守死代码判定（DeadClass/orphanHelper 命中，AliveClass/caller/接口方法不误报，导出未导入函数按导出级判死）、`--where` 过滤（`name~get,ownerKind=class`）
- 总计 78 个测试全部通过

### 变更说明

- query/link 命令为泛型实现（由 OBJECT_TYPES/LINK_TYPES 驱动），本版本零 CLI 命令改动即支持全部新类型/新链接
- 快照新增 Interface/Class/Method 三个 key，向后兼容（老快照/消费方不受影响）；大仓库快照体积约增至 2-3 倍（万级 Method 实体），载入仍在数百毫秒级
- Vue SFC `<script>` 内的 interface/class 本期不提取（Vue 侧已有 Hook/Composable/Store 体系）；TS 方法级调用图（calls/calledBy）未扩展到 Method

## [0.6.3] - 2026-08-21

### 新增

- **蓝图查看器宽屏适配**：内容宽度从固定 1400px 改为分档自适应（1600/1920/2240/2560px 断点逐步扩至 2400px）并居中，超宽屏不再右侧大片空白；脚本调用图 SVG 等比缩放（viewBox + max-width:100%），宽屏完整呈现、窄屏不再右缘截断
- **油猴脚本三视图意图适配（函数意图分析）**：无 React/Vue 结构的纯脚本仓库，领域蓝图/业务数据图/业务逻辑流向三个视图按函数意图（roles）重建，而非显示空壳占位：
  - **意图功能域**（领域蓝图适配）：单文件脚本无目录级功能域时，按函数主角色（渲染注入/数据获取/状态存取/事件监听/元素构建/纯逻辑）聚合为虚拟功能域，卡片展示各意图的行为计数（DOM 注入/网络/监听/GM API），点击下钻函数清单（按重要性排序）；仅单一意图的纯功能增强脚本不生成
  - **脚本存储枢纽**（业务数据图适配）：无 Zustand/Pinia Store 时以浏览器存储（localStorage/sessionStorage/indexedDB）与 GM 存储 API 为数据枢纽，展示各脚本存储信号、状态存取函数、宿主数据读取（unsafeWindow 全局变量）；无任何持久化信号的脚本不生成
  - **函数意图流转矩阵**（业务逻辑流向适配）：单文件无模块导入时，函数调用边按「调用方意图 → 被调方意图」聚合为热力矩阵（事件监听 → 纯逻辑 → 数据获取/状态存取 → 渲染注入），附高扇入函数（变更影响面）清单；调用流转稀疏的扁平脚本不生成
  - **Tab 显隐**：三视图各自独立判定，分析不出有效数据（如纯功能增强脚本）时隐藏对应 Tab，不再显示空壳
- **解析器：localStorage/sessionStorage/indexedDB 归属函数**：存储操作按包含函数归属（storageOpCount），函数角色推断的 state 角色从仅 GM 存储扩展到浏览器存储

### 验证

- Steam-License-Classifier-2.5.0（单文件审核场景）：意图功能域 6 组（纯逻辑 111 / 渲染注入 26 / 事件监听 9 / 数据获取 3 / 状态存取 3 / 元素构建 1）；存储枢纽 localStorage × 21 + indexedDB × 1 + 宿主读取 g_sessionID/g_steamID；意图流转 247 调用边（跨意图 156），Top 流转 渲染注入→纯逻辑 94、事件监听→纯逻辑 20
- steam-tampermonkey-scripts 全仓（混合场景，228 脚本 + 28 目录级功能域）：领域蓝图走正常目录域路径（不重复生成意图域），存储枢纽 228 个（45 个 localStorage 信号 + 140 个 GM 存储信号）、意图流转 73269 调用边正确激活
- DOM stub 渲染验证：三视图适配渲染器（renderScriptDomainList/renderScriptDataMap/renderScriptFlow）在真实快照上执行通过，矩阵热度格子、高扇入函数、Tab 显隐均正确
- 新增 3 个测试（多意图脚本三视图重建 / 纯功能增强脚本三视图隐藏 / 宽屏断点与 SVG 自适应），总计 67 个全部通过

## [0.6.2] - 2026-08-21

### 修复

- **HTML 入口引用盲区（Vite 多页应用）**：扫描器探测扫描根及宿主根顶层 `*.html` 的 `<script src="/src/xxx.tsx">` 根绝对路径引用，作为硬证据入口（`htmlEntryFiles`）；嵌套入口文件（如 `src/managed-agent/main.tsx`）不再被误判为死代码候选（asdm-agentlink/web：死代码候选 20 → 19，剩余经抽查为真实死代码）
- **蓝图架构分层排版压缩换行**：此前层名与描述拼接（如 `状态层（前端状态共享比如（Zustand / Pinia Store））`）挤在 90px 标签列内，被压缩成 3-5 行竖排堆叠、括号双重嵌套；现改为层名单行标签 + 描述作为条形图下方独立副行（与柱条对齐、小字号弱化），标签列加 `white-space: nowrap` 防压缩，窄屏下副行左对齐

### 验证

- asdm-agentlink monorepo 双子包端到端：web（React + Vite，202 文件，HTML 入口修复生效）+ server（Express 后端，254 文件，子包依赖清单正确，2 个未声明依赖均为真实治理点：@jest/globals 未声明、vitest 与 jest 混用残留）
- 新增 2 个测试（HTML 入口探测 / 架构分层排版 DOM stub 渲染验证），总计 64 个全部通过

## [0.6.1] - 2026-08-21

### 新增

- **项目框架识别增强（扫描 src 等子目录场景）**
  - 宿主项目定位：扫描目录自身无 package.json 时，向上查找最近宿主根（上限 4 层、不越过用户 home），用宿主依赖识别框架、回退项目名/版本，宿主依赖全量进入清单（扫描 src/ 时 react 等不再误判为未声明依赖）
  - 框架识别扩展：expo / react-native / next / nuxt 元框架优先识别，宿主 app.json 含 expo 键作旁证；无任何清单时按代码信号兜底（.vue 文件 → vue，tsx/jsx → react）
  - 跨端/构建变体（frameworkVariants）：Capacitor / Electron / Vite / Webpack，组合标签如 "React 单页应用 + Capacitor 跨端（Vite 构建）"，Project 携带 frameworkLabel / hostRoot / hostConfigs 证据
  - 宿主 tsconfig 路径别名重定基：扫描 src/ 时宿主的 `"@/*": "./src/*"` 重定基为 `./*`，`@/` 别名导入恢复解析（此前 600 处 `@/services` 等全部误判为未声明依赖）
  - solution 风格 tsconfig：根文件仅含 references 时递归合并子配置（tsconfig.app.json 等）的 paths（根 paths 优先，含自引用防御），`#/` 等多别名场景恢复解析
  - Node 内置模块（node: 前缀、裸名 fs/path/child_process/readline 等、子路径 fs/promises 与 readline/promises）识别为 builtin；Vite 虚拟模块（virtual:generated-pages / virtual:app-loading 等构建时生成）识别为 virtual，均不再计入依赖清单与未声明依赖
- 真实仓库验证（四类项目）：
  - nice-today-2.0（React + Capacitor + Vite，扫描 src/）：框架从 unknown → 正确识别；未声明依赖 62 → 8（剩余为真实治理点：d3 子包、@capacitor 局部插件）；别名恢复后暴露 1 组此前隐藏的循环依赖（8 → 9）
  - qa-live-healthcare（Vue 3 + Vite）：框架正确识别为 "Vue 单页应用 + Vite 构建"；未声明依赖仅剩真实治理点（@ant-design/icons-vue 导入未声明）
  - steam-stat（Vue 3 + Electron + Vite）：识别为 "Vue 单页应用 + Electron 桌面端（Vite 构建）"；solution tsconfig 的 `@/*` + `#/*` 双别名全部解析；virtual: 模块排除后未声明依赖归零
  - asdm-agentlink-cli（Node CLI，commander）：不误判为前端框架（保持"前端项目"中性标签）；readline 等内置模块子路径修复后未声明依赖归零
- 新增 12 个测试（宿主定位/框架识别/变体/别名重定基/solution tsconfig/builtin 子路径/virtual 模块），总计 62 个全部通过

## [0.6.0] - 2026-08-20

### 新增

- **脚本蓝图视图（viewer 第五视图"脚本蓝图"）**：油猴脚本开发者一图直读"入口函数 → 调用链 → DOM 注入锚点 / 网络域"的完整逻辑注入链
  - **SVG 函数调用关系图**：函数节点按调用深度分层布局（入口 → 逐层调用，BFS 定级），实线为函数调用、青色虚线为 DOM 注入（指向页面锚点）、紫色点线为网络请求（指向域名端点）；悬停高亮相邻节点与边，点击查看函数详情（角色/行号/调用关系/注入目标/GM API）或锚点详情（归属函数/插值标记/@connect 比对）
  - **DOM 注入锚点列**：还原后的页面选择器（如 `querySelector('#app')`、`getElementById('sfd-status-txt')`）作为图上独立节点，注入类型/次数/动态插值标记一目了然
  - **网络端点列**：请求域名 + kind（gm-xhr/fetch/xhr/websocket/beacon）+ 方法 + `@connect` 声明比对
  - **函数业务角色分布**：render/data/state/event/ui/logic 六类角色统计条形图；函数清单表含调用/被调/GM/DOM/网络五维计数
  - **数据聚合**：函数重要性加权排序（被调×3 + 调出 + 注入/请求×2）取 Top N 入图，入口函数（topLevelCalls 命中）必进图；脚本按函数数排序展示 Top 24；大脚本截断保护（图节点 50 / 函数表 40 / 注入 20 / 端点 12，计数保留全量）
- **油猴脚本解析增强（userScriptAnalyzer）**
  - **函数业务角色推断（roles）**：按函数内行为推断 render（innerHTML/挂载）/ data（网络请求）/ state（GM 存储）/ event（监听/观察/定时）/ ui（createElement）/ logic（纯逻辑），最多双角色；ScriptFunction 携带 `roles` 字段
  - **注入点与网络端点归属函数（fns/fnIds）**：DOM 注入与网络请求按"最内层包含函数"归属，InjectionPoint/NetworkEndpoint 携带 `fns`（函数名）与 `fnIds`（指向 ScriptFunction），构成逻辑注入链的数据基础
  - **类风格调用链补全**：类 `constructor` 收集为逻辑单元；`this.method()` 类内互调解析为 `Owner.method`；`const app = new Xxx(); app.method()` 实例变量调用经别名表解析为 `Xxx.method`；`new Xxx()` 记为 `Xxx.constructor` 入口调用
  - **innerHTML/insertAdjacentHTML 锚点还原**：receiver 为 `querySelector`/`getElementById` 变量时还原为页面选择器（此前保留变量名），与挂载点还原口径一致
- 真实仓库验证：steam-tampermonkey-scripts（227 个脚本 / 47588 个函数 / 5381 个注入点 / 419 个网络端点），最大脚本（Steam 游戏库展示 v2.11.35，900+ 函数）调用图 58 节点中 42 个有调用关系；steam-friend-manager 的注入锚点归属函数链清晰（如 `renderActiveTab/renderPersonalLibraryTab → getElementById('sfd-pl-tab-content')`）
- 新增 4 个测试（类风格调用链解析 / 注入与网络归属函数 / 角色推断 / viewer 脚本蓝图聚合与 HTML 渲染），总计 52 个全部通过

## [0.5.0] - 2026-08-20

### 新增

- **本体查看器（viewer.js，src/ontology 视图层）**：延续"方案查看器（蓝图）"架构理念，把快照的抽象结果聚合为可交互消费的蓝图，解决"语义总结有了、但没有可视化载体"的问题
  - **数据聚合（buildViewerModel）**：DataMap → 视图模型（JSON），四个板块：本体蓝图（taxonomy + 对象/链接类型 + 实例计数）、领域蓝图（每个功能域的业务层级构成/代码组织/模块清单/单元清单/职责画像）、业务数据图（Store 数据枢纽 + 被哪些域使用 + 跨域数据依赖 + 持久化状态汇总）、业务逻辑流向（架构层间导入流向矩阵 + 跨域依赖边 + 高扇入 Service/Store 枢纽节点）
  - **视图渲染（renderViewerHtml）**：视图模型 → 自包含 HTML（零外部依赖，可离线打开），四个标签页：总览 / 领域蓝图 / 业务数据图 / 业务逻辑流向；层间流向以矩阵呈现（行=来源层，列=目标层），单元清单带大仓库截断保护（计数保留全量）
  - **视图模型独立于渲染**：`export --format viewmodel` 输出聚合 JSON，供 AI agent 与其他前端直接消费；`export --format html` 输出蓝图 HTML
  - 真实仓库验证：React 仓库（steam-portal，26 域 / 3 Store / 76 条层间流向 / 15 个服务枢纽）与纯油猴仓库（steam-tampermonkey-scripts，28 域 / 无 Store 与路由时数据图与流向优雅降级为空态）
- 新增 3 个测试（viewer：视图模型结构完整性/自包含 HTML 嵌入数据无损解析/空数据仓库降级），总计 48 个全部通过

## [0.4.0] - 2026-08-20

### 新增

- **语义本体引擎（semantics.js）**：解决"对象结构只罗列事实、无抽象总结"的问题，解析产出从词法级事实升级为具备语义分层与聚合总结的本体
  - **概念分类体系（taxonomy）**：`ONTOLOGY_META` 显式定义概念范畴（Container/CodeUnit/EntryPoint/Script/Environment/AuditFact，is-a 族）与抽象层级（L3 架构层 / L2 结构层 / L1 单元层 / L0 事实层），15 种对象类型全部挂接双维分类，不再平铺
  - **语义架构分层**：`archLayer`（entry/presentation/state/service/integration/shared/types/config/script/test/mixed）按内容信号推断（单元构成、路由归属、引用结构），目录名仅作弱信号回退；Module 附带 `layerComposition` 层构成画像，构成分散（主导层 < 60%）时如实标记 mixed；取代原先"目录名即层级"（`layer` 字段保留为目录层，两者并存）
  - **功能域聚合（Domain 对象，dom:）**：路由域段 + 业务命名目录（父目录为技术边界、自身命名非技术词，大小写/连字符/下划线归一）聚合为横向功能切片，与纵向架构层正交；成员含路由/组件/Store/Hook/Service/油猴脚本/模块，附 `capability`（路由 meta 能力描述）与 `summary`（含 Hook/Service 计数，`hookCount`/`serviceCount` 可查询）
  - **自然语言总结**：Project 新增 `summary`（框架定位 + 分层构成 + 功能域清单 + 健康度）、`architecture`（style 分层画像/层占比）、`health`（循环依赖/死代码/未声明依赖/高风险脚本/解析错误计数）、`capabilities`（每域一句话）；Module 新增 `summary` 职责画像（层级、单元构成、模块外引用数、承载路由数）、`subtreeFileCount`、`unitCounts`、`dominantShare`、`domainIds`
  - **单元级归属回填**：Component/Hook/Store/Service/UserScript/ScriptFunction 新增 `archLayer`（继承所在文件）与 `domainIds`（所属功能域，数组支持 `--where "domainIds=dom:xxx"` 成员过滤）
  - **belongsTo 链接（第 15 种）**：功能域 ↔ 成员双向（`link belongsTo --src dom:health` 列成员；src 传 comp:/store:/mod:/route: 反查所属域，fn: 归属其脚本的功能域）；Project 的 `contains` 现包含直属 Domain
  - **报告升级**：新增"执行摘要"（总结句列表 + 健康指标表）、"架构总览（语义分层）"（层/定位/文件数/占比）、"功能域地图（Domain）"（域/来源/路由/组件/Store/脚本/文件/职责画像）三个章节；模块 Top 30 升级为语义层 + 层构成 + 直属/子树文件数 + 职责画像
- 新增 13 个测试（semantics：taxonomy 完整性/文件级分层推断/模块级混合层判定/总结生成/buildDomains 归一合并/React fixture 端到端/油猴仓库端到端），总计 45 个全部通过

### 修复

- Module 画像口径不一致：原 `fileCount` 为直属文件数，聚合模块（如 `steam-account-history`）显示"0 个文件"却带大量子树单元 → 总结改用 `subtreeFileCount` 子树口径，导出表同时呈现直属/子树两列

## [0.3.0] - 2026-08-20

### 新增

- **油猴脚本（Tampermonkey UserScript）扫描**：独立 `userScriptAnalyzer` 解析器，与 React/Vue 解析器平级共存、逻辑互不干扰
  - 脚本识别：`.user.js` 扩展名，或 `.js` 文件头部 4KB 内含 `==UserScript==` 元数据块；纯油猴仓库无需 package.json（`framework=userscript`，refreshRepo 不再强制要求 package.json）
  - 5 种新对象：`UserScript`（us:，元数据/@match/@grant/@connect/宿主框架/风险等级）、`GmApiUsage`（gm:，GM 调用与 @grant 声明比对）、`InjectionPoint`（inject:，mount/innerHTML/insertAdjacentHTML/document.write/GM_addStyle/style 元素/Shadow DOM，动态插值 XSS 标记）、`NetworkEndpoint`（net:，gm-xhr/fetch/xhr/websocket/beacon 域名 + @connect 白名单比对）、`ScriptFunction`（fn:，函数/类/对象方法逻辑单元）
  - 5 种新链接：`usesGmApi`/`injectsInto`/`requestsTo`（脚本 ↔ 子对象双向）与 `calls`/`calledBy`（脚本内函数静态调用图）
  - 解析能力：元数据块全字段提取（@grant none 归零）、GM_*/GM.* 双风格归一、querySelector 变量锚点还原挂载目标、unsafeWindow 读/写区分、window 全局暴露与 Object.defineProperty、宿主框架推断（__vue__/__reactContainer$）、原型/全局请求劫持识别（fetch/XHR/EventTarget/WebSocket/history）、eval/new Function、cookie 读写、MutationObserver/定时器/CustomEvent 事件总线
  - 安全风险清单：按 high/medium/low 汇总为脚本级 riskLevel（请求劫持/动态执行/沙箱突破/XSS 面/权限越界）
  - 报告新增 6 个章节：油猴脚本一览（含逐脚本元数据详情）、GM API 使用、DOM 注入点、网络请求与请求劫持、脚本函数 Top 30、安全风险清单
- 新增 11 个测试（userScriptAnalyzer：元数据/候选识别/GM API/DOM 注入/网络与劫持/函数调用图/unsafeWindow/宿主框架/扫描器/本体构建与链接），总计 32 个全部通过

### 修复

- 多入口识别：入口文件原硬编码为 `src/App.tsx` 等单根路径，多 roots monorepo 下 isEntry 全部为空 → 改为按扫描根顶层入口文件名动态匹配（`web/src/main.tsx`、`server/src/index.ts` 等均可命中）
- zustand 泛型 `create<T>()(...)` 漏识别：`create` 位于内层 call 不满足收集条件，8 个 store 仅识别 1 个 → 收集阶段向上穿透到外层 call，factory 改取外层 call 的 `arguments[0]`，persist/devtools 双层嵌套（`devtools(persist(...))`）改为循环穿透

## [0.2.0] - 2026-08-20

### 新增

- **Vue 3 框架适配**：`.vue` SFC 扫描与框架检测（package.json 依赖含 vue/nuxt → `framework=vue`，快照新增 `vueFileCount`）
  - SFC 解析：`<script setup>`/`<template>`/`<style>`/`<route lang="yaml">` 块拆分（template 内嵌 `<template #footer>` 不误拆）；组件名 `defineOptions({ name })` 优先，否则文件名派生；`defineProps` 数组/对象形式计数；template 标签（kebab/PascalCase 统一）供 renders 关系
  - Pinia store：`defineStore` 的 setup 写法（shorthand 函数经函数名集合识别为 action）与 options 写法（`login() {}` MethodDeclaration 计入 actions），`persist` 插件第三参数提取 storageKey；支持 unplugin-auto-import 无 import 语句的隐式 `defineStore`
  - vue-router：`RouteRecordRaw` 显式声明（path/name/meta.title/动态 import/Layout 函数包装/children 拼接，空 path 子路由正确拼接）；`useRouter()` 声明与解构 `push` 归属导航调用（数组 push 不误报）；`src/views|pages/**/*.vue` 文件路由推导（`index.vue` → 父级路径，`[...all].vue` → catch-all），`<route lang="yaml">` meta 作为路由描述
- **React JSX 声明式路由提取**（react-router v6/v7 的 `<Routes>/<Route path element>` 模式，自动探测）
  - 布局 Route（无 path）+ 绝对 children、嵌套相对路径拼接（`/:scopeUid` + `edit` → `/:scopeUid/edit`）、`index` 路由、`path="*"` catch-all
  - element 解析链：`<Guard><Page /></Guard>` 嵌套包装取最内层组件；`element={layoutElement}`（`createElement` 布局变量）穿透到实际布局组件
  - `<Navigate to>` 重定向边：字面量提取，相对 `to` 基于所属路由归一为绝对 overlayId（`to="_overview/summary"` → `/:scopeUid/_overview/summary`）；动态 `to={expr}` 不误报
  - 测试文件中的 mock 路由自动排除
- **page kind 升级**：`pages/` 目录下被路由直接引用的 common 组件自动标记为 page（asdm-admin-web 实测 8 → 48）
- Route 对象新增 `routeType`（overlay/react/vue）；React 路由 id 冲突（index 路由与父路由同 overlayId）经 uniqueId 去重

### 修复

- Vue 显式路由的 domain 取自路由声明文件名（`router/index.ts` 时全部为 `index`）→ 统一按路径顶层段计算（`/` 与动态参数/catch-all 顶层段归入 `root`，如 `/:all(.*)*` → `root`）
- tsconfig.json 含 `//`/`/* */` 注释时（如 shadcn-vue CLI 生成的文件）`JSON.parse` 失败导致 `@/` 别名全部解析失败 → 解析前自动剥离注释（字符串内序列受保护）
- `src/views/index.vue` 文件路由被推导为 `/index` 而非 `/`
- 显式路由与文件路由重复生成（explicitRouteFiles 未命中时）

### 测试

- 新增 14 个测试：Vue（SFC 拆分/route meta/defineProps/defineOptions/Pinia setup/options store/RouteRecordRaw/useRouter 导航/template 嵌套）与 React JSX 路由（布局/嵌套相对路径/index/包装 element/Navigate 归一/普通项目空路由），总计 21 个全部通过

## [0.1.0]

### 新增

- 初始版本：React/TypeScript 前端代码本体分析 CLI
  - 结构化本体快照：Project/Module/SourceFile/Component/Hook/Store/Service/Route/Dependency 9 种对象 + import/render/navigate/register/usesStore/usesHook 等关系图谱
  - 命令：`query`（`--where` 过滤）、`link`（关系遍历）、`action`（refreshRepo/markReviewed/addNote）、`export`（Markdown 全景报告/JSON）
  - 解析能力：tsconfig paths 别名解析、`.tsx` 组件识别（React.FC/memo/forwardRef/分离式 default）、Zustand create/persist store、useXxx Hook、Service 目录识别、依赖治理、死代码候选、Tarjan 循环依赖
  - overlay 路由体系自动探测（overlayGroups + lazyImports）
