@minonl/dia 0.0.16 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ # 内置能力的分层:dia-msg 不该让用户「装」
2
+
3
+ > 日期:2026-11-09 | 状态:**实现中(P0 已合,P1 本文档)** | 触发:用户质疑「dia 是命令入口,为什么内置的还要单独装?dia-msg 都算内置」
4
+
5
+ ## 1. 问题
6
+
7
+ `dia-msg` 随包发布(`package.json files` 含 `kits/`),但运行时被当成第三方 kit:
8
+
9
+ - `default-msg.ts` 唯一的动作是 `kit.install(<install>/kits/dia-msg)` → 复制到 `~/.dia/kits/dia-msg/<ver>`、写 `kit.lock`、建 skill 软链
10
+ - 复制只在 `persona add/render` 时触发;漏了就 `⚠ missing-kit`,而且 **exit 0**(静默失去消息能力)
11
+
12
+ 用户视角:装完 dia,消息能力「有时有、有时没有」,还要 `dia collab enable` 手动补。
13
+
14
+ ## 2. 三层边界
15
+
16
+ | 层 | 内容 | 要不要「装」 | 理由 |
17
+ |---|---|---|---|
18
+ | **用户资产** | persona、第三方 kit | 要(lock / 内容哈希 / remove / 多版本) | 内容来自外部仓库,会漂移,必须能验、能回滚 |
19
+ | **内置能力** | dia-msg(未来 git/node kit) | **不要(用户零操作)** | 版本必须跟包走;复制只是「让运行时能读」的实现细节 |
20
+ | **宿主可见性** | skill 软链 / agent 入口 / 配置片段 | 首次运行生成,幂等可重建 | 不能随包发布,必须落到各自 HOME |
21
+
22
+ **结论**:内置能力≠复制一份到数据根,但也不等于直接读包内(pnpm/npx/换 Node 会断链)。折中:
23
+
24
+ ## 3. 拍板方案:懒复制 + 版本戳(B)
25
+
26
+ - 复制仍发生,但**用户永远不用手装**:任何首次需要它的路径(`persona add/render`、`dia collab enable`)自动完成
27
+ - 副本用 **内容哈希**判断是否刷新(版本号常不 bump,只看 semver 会漂移)
28
+ - 写自举戳 `~/.dia/state/bootstrap.json`:`{pkg, kit, at}`;`dia doctor` 有 `bootstrap` 检查
29
+ - 无戳 → warn「首次 persona add/render 或 collab enable 会自动装」
30
+ - `stamp.pkg != 当前包版本` → warn「内置能力可能过期;修复:`dia collab enable`」
31
+ - `dia collab enable` 装失败 → **exit 32**(不再 exit 0 假成功)
32
+
33
+ ### 为什么不选「内置只读(直接读包内)」
34
+
35
+ | 阻断 | 后果 |
36
+ |---|---|
37
+ | `linkManaged` 只认 `~/.dia/kits` / `~/.dia/personas` | 指向包内的软链判 unmanaged → `E_CONFLICT` |
38
+ | persona 的 `kits[]` 只允许 persona 树内路径 | 内置 kit 无法被 persona 声明,只能留特例 |
39
+ | pnpm `.pnpm/<pkg@ver>/`、npx `_npx/<hash>`、换 Node 前缀 | 包路径会变,软链必然断,需每次升级重链 |
40
+
41
+ 只读模式的收益(少一次复制)不值得新增三条契约;B 保留了 verify/remove/回滚。
42
+
43
+ ## 4. P0:先修安全底座(本 PR 之前已完成)
44
+
45
+ | 项 | 之前 | 现在 |
46
+ |---|---|---|
47
+ | `kit.install` 落盘 | `rm -rf target` 后 `cp`,中途失败旧版全没 | staging 目录 + `rename` 原子换入,失败回滚并保留旧版 |
48
+ | 并发安装 | 无锁,可能装成「有 lock 但内容残缺」 | 数据根级 `O_EXCL` 锁(`DIA_KIT_LOCK_MS`,默认 5s,陈旧锁 120s 自动清) |
49
+ | 版本选择 | 字典序(`0.1.9` > `0.1.10`) | semver 比较,隐藏目录(`.staging`/`.install.lock`)不参与 |
50
+ | `installDir()` | 环境变量指向不存在目录也照用 → missing-kit 谜之失败 | 校验存在性,告警并回退探测 |
51
+ | `collab enable` | 安装失败仍 exit 0 | exit 32 |
52
+
53
+ ## 5. P2(下一步,未做)
54
+
55
+ - `install.sh` 文档降级为「开发 / 离线通道」,主推 `npm i -g @minonl/dia`;`install_shadow` 已从 fail 降为 warn
56
+ - `dia update` 文案统一成「npm/bun 全局升级」,不再引导 `bash install.sh`
57
+ - 若未来出现第二个内置 kit:抽 `builtin kits` 注册表,避免每加一个都改 `default-msg`
58
+
59
+ ## 6. 判据
60
+
61
+ ```bash
62
+ bun test tests/kit-atomic.test.ts # 原子安装 / 锁 / semver / installDir
63
+ dia doctor --json | grep -q '"bootstrap"' # 自举戳可见
64
+ dia collab enable; echo $? # 装不上 → 32
65
+ ```
package/docs/cli.md CHANGED
@@ -99,7 +99,7 @@ dia persona remove dabo
99
99
  | `env list / check / run` | **已实现** | 白名单注入、不回显值、`--redact`、密钥不进 argv;清单由 persona 声明(`src/env.ts` + `tests/env.test.ts`) |
100
100
  | `update [--check]` | **已实现** | 查 npm `dist-tags`;`--check` 有新版 exit 1、领先/最新 exit 0;**按安装方式分流**:npm 全局包 → `npm i -g`;脚本安装 → 提示 `git pull && bun run build && bash install.sh`(避免装到不在 PATH 的 npm prefix) |
101
101
  | `deps list / record` | **已实现** | `list` = 登记 + 实测派生视图(required 缺失 exit 2);`record` = 只追加一行(`--id/--kind/--version` 必填);`src/deps.ts` + `tests/deps*.test.ts` |
102
- | `doctor` | **已实现** | 组合自检:persona verify / **persona.render(宿主产物只读漂移,warn + 修复命令)** / default_agent / kit(**只查该 persona 引用的**,孤儿 kit 不拖红)/ knowledge(**索引新鲜度三态**,过期给重建命令)/ dsh preset / **deps 登记校验** / **platform 版本(读 `docs/platforms/registry.json`)** / wt / **install_shadow(脚本安装 vs 包管理器全局安装互相遮挡:双向一致判定,不一致 fail + 一行修复命令;shim 形态按内容识别)**;exit 0/1/2。不指定 persona 且装了多个 → **逐个自检**(条目加 `<id>.` 前缀),不再整体 `E_SCOPE` |
102
+ | `doctor` | **已实现** | 组合自检:persona verify / **persona.render(宿主产物只读漂移,warn + 修复命令)** / default_agent / kit(**只查该 persona 引用的**,孤儿 kit 不拖红)/ knowledge(**索引新鲜度三态**,过期给重建命令)/ dsh preset / **deps 登记校验** / **platform 版本(读 `docs/platforms/registry.json`)** / wt / **install_shadow(遮蔽提示,warn)** / **bootstrap(内置能力是否跟随包版本)**(脚本安装 vs 包管理器全局安装互相遮挡:双向一致判定,不一致 fail + 一行修复命令;shim 形态按内容识别)**;exit 0/1/2。不指定 persona 且装了多个 → **逐个自检**(条目加 `<id>.` 前缀),不再整体 `E_SCOPE` |
103
103
  | `wt …` | **已实现(需安装包)** | 转发 `packages/wt/bin/dia-wt`(create/adopt/doctor/gate/list/status/check/clean/prune-merged;详见 [worktree.md](worktree.md));未装时 `E_WT`(exit 33),`install.sh` 会分发 |
104
104
  | `kb index/search/query/get/lint/path/review` | **已实现** | persona 作用域下索引过期自动重建;显式 `--root` 保持「缺索引即报错」 |
105
105
  | `kb <域命令>` | **已实现(委派)** | 未命中内置 → persona `commands` → 委派;argv 透传、exit code 原样 |
package/docs/index.md CHANGED
@@ -10,6 +10,7 @@ Agent 入口:[`llms.txt`](https://github.com/minonl/dia/blob/main/llms.txt)(
10
10
  |---|---|
11
11
  | [ACP 集成与降级设计(2026-10)](acp-integration.md) | 问题:通过 ACP 是否更好?fallback 怎么做?配置失败如何降级、体验如何? |
12
12
  | [架构:框架 / 场景入口 / kit](architecture.md) | 本文回答一个问题:什么该留在 dabo-agent,什么该抽到独立框架。 |
13
+ | [内置能力的分层:dia-msg 不该让用户「装」](builtin-kit-design.md) | 日期:2026-11-09 | 状态:实现中(P0 已合,P1 本文档) | 触发:用户质疑「dia 是命令入口,为什么内置的还要单独装?dia-msg 都算内置」 |
13
14
  | [dia CLI 词表(动作规范)](cli.md) | 原则:动作成对、名词分层、不混轴。 |
14
15
  | [外部依赖索引与原始记录维护(2026-10)](dependency-registry.md) | 回答两件事:依赖怎么登记/验证/淘汰,原始记录怎么保留/重建/删除。 |
15
16
  | [生态与组合:可复用性调研(2026-10)](ecosystem-integration.md) | 问题:dia 的包和集成面应该长什么样,才能同时服务 opencode / dsh(未来 OpenClaw/Hermes/Pi)? |
package/docs/status.md CHANGED
@@ -91,6 +91,7 @@
91
91
  | D0.21 | worktree 抽取 `packages/wt` | **完成** | 7 处 python→node;`dia wt`;4 套测试原样迁移全绿 |
92
92
  | D0.23 | doctor 平台版本自检 | **完成** | `platform:opencode` / `platform:dsh` 报版本(缺宿主 = warn,不判红);`tests/active-doctor.test.ts` |
93
93
  | D0.44 | 配置迁移走 LLM 任务 | **完成** | `docs/migrations/llm-migration.md`:升级只改声明;迁移=任务+判据+回滚;禁止代码里堆版本分支(安全敏感/原子场景例外) |
94
+ | D0.66 | 内置能力分层(dia-msg 用户零操作) | **完成** | `kit.install` 原子换入 + 数据根锁;版本 semver 排序;`installDir()` 校验;`collab enable` 失败退 32;自举戳 `~/.dia/state/bootstrap.json` + `doctor bootstrap` 检查;`install_shadow` 降 warn;设计见 [builtin-kit-design.md](builtin-kit-design.md) |
94
95
  | D0.65 | 安装防呆:脚本安装不抢包管理器全局安装 | **完成** | `install.sh` 守卫(目标 bin 属于包管理器全局安装且为 `command -v dia` 命中项 → exit 3,零改动):npm/bun 软链看 `node_modules/@minonl/dia`,pnpm/yarn shim 看内容,volta 看 bin 目录;放行 = `DIA_ALLOW_SCRIPT_INSTALL=1` / `--dry-run` / **bin 目录被重定向**(`--home` 等只改安装目录不算沙箱);装完打印 `effective:`;`doctor install_shadow` 双向一致判定(含 shim);`tests/install-guard.test.sh` + `tests/install-guard-strict.test.sh` |
95
96
  | D0.64 | 复制过滤按相对路径判断 | **完成** | 绝对路径匹配 `node_modules` 会误伤 npm 全局安装(kit 整树被过滤 → `E_KIT_EMPTY`);改 `copyFilter(base)` 用相对路径,persona/kit 共用 |
96
97
  | D0.63 | 运行时数据 `.data/` 不进快照、不参与哈希 | **完成** | `TREE_SKIP`(kit/persona 共用):安装复制与 `hashTree`/verify 都跳过 `.data`,防运行时写入造成 verify 假漂移 |
@@ -1,34 +1,187 @@
1
1
  ---
2
- title: pnpm 11 与构建脚本(allowBuilds)
3
- tags: [node, pnpm, ci]
2
+ id: tools/pnpm
3
+ kind: tool
4
+ topic: pnpm 升级与迁移 / node_modules 悬空软链排查
5
+ title: pnpm 升级与 runtime 架构(可复用工具知识)
6
+ intents: [pnpm, 包管理, pnpm 升级, 悬空软链, dangling symlink, 找不到 Nx 模块, node_modules 损坏, lockfile 漂移, lockfile-only, global virtual store, GVS, 多 worktree, virtualStoreType, enableGlobalVirtualStore]
7
+ updated: 2026-11-09
4
8
  ---
5
9
 
6
- # pnpm 11 要点
10
+ # pnpm 升级与 runtime 架构(可复用工具知识)
7
11
 
8
- ## 构建脚本默认被忽略
12
+ > 来源:yan-agent `core/knowledge/tools/pnpm.md`(2026-11-09 迁入 kit/node)。
9
13
 
10
- pnpm 10+ 默认不执行依赖的 `postinstall`。表现:
14
+ > 跨项目可复用的 pnpm 版本升级坑与 pnpm 12「runtime」发行架构,方案式记录(条件 → 解法)。
15
+ > hummus 专属升级上下文见 hummus 仓库 `docs/knowledge/pnpm-12-upgrade.md`;dockerfiles-v2 镜像精确发布姿势见 dockerfiles-v2 仓库 `CLAUDE.md`「精确发布指定镜像」。
11
16
 
12
- - 本机:`ERR_PNPM_IGNORED_BUILDS`(退出码 1)
13
- - CI:`Job failed: command terminated with exit code 1`
17
+ ## pnpm 12 发行版结构(native binary + wrapper)
14
18
 
15
- 放行方式(**写在 `pnpm-workspace.yaml`**,pnpm 11 起 `package.json` 的 `pnpm` 字段不再读取):
19
+ pnpm 11 的 npm 包是**直接内置 native binary**(顶层 `pnpm` 文件即 ELF)。pnpm 12 起改为
20
+ **wrapper + native binary 拆分**:
16
21
 
17
- ```yaml
18
- allowBuilds:
19
- simple-git-hooks: true
22
+ - `package.json#bin` 指向顶层 `pnpm`(shebang-less placeholder)
23
+ - `install.js`(lifecycle script)把 `@pnpm/exe.<platform>`(如 `@pnpm/exe.linux-x64`)里的
24
+ native binary 链接过来替换 placeholder
25
+ - `bin/pnpm.mjs` 是 corepack 入口(corepack 不进 native binary,走 Node 加载)
26
+
27
+ **坑:升级场景 native binary 不替换**——本机从 pnpm 11 → 12 `npm i -g pnpm@12` 后,顶层 `pnpm`
28
+ 还是旧的 11.15.1 ELF(install.js 未覆盖旧文件),需手动 `node lib/node_modules/pnpm/install.js`
29
+ 重链。**全新环境(Docker 镜像 `npm install -g pnpm@latest-12`)无此问题**。
30
+
31
+ ## pnpm 12 是「runtime」,按 packageManager 字段解析版本
32
+
33
+ pnpm 12 默认开启 `manage-package-manager-versions`(corepack 替代)。binary 是 runtime,
34
+ 根据**当前目录 package.json 的 `packageManager` 字段**解析实际运行版本:
35
+
36
+ - `packageManager: pnpm@12.4.2` 的项目 → 用 12.4.2 行为(`pnpm install` 输出 `using pnpm v12.4.2`)
37
+ - `packageManager: pnpm@11.15.1` 的项目 → 自动委托 pnpm 11.15.1
38
+ - 裸环境(无 packageManager)→ 回退到**本机历史激活版本**(`getpwuid` 读真实 home,绕过 `$HOME`)
39
+ 或自身版本
40
+
41
+ **坑:`pnpm --version` 在裸环境可能显示旧版本号**(如本机有 pnpm 11 状态则显示 11.15.1),
42
+ 但这是 runtime 回退显示,**不代表实际 install 行为**——实际行为由 packageManager 字段决定。
43
+ 验证真实版本用「项目目录内 `pnpm install` 的 `using pnpm vX` 输出」,别信裸 `--version`。
44
+
45
+ ## lockfile env 文档头部
46
+
47
+ `packageManager` 声明 pnpm 12 后,`pnpm install`(非 frozen)会在 `pnpm-lock.yaml` **头部新增
48
+ 一个 env 文档**(`---` 分隔的第二个 YAML 文档),包含 `packageManagerDependencies` + 锁定
49
+ `@pnpm/exe.*` 各平台二进制(12.x)。`--frozen-lockfile` 时 lockfile 若缺此头部会报
50
+ `ERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE`。
51
+
52
+ **坑:升级必须提交 lockfile env 头部变更**(约 +158 行,全是 pnpm 自身 exe 包,无业务依赖变化)。
53
+
54
+ ## 存量 node_modules 兼容 + store 复用
55
+
56
+ - lockfileVersion 仍 9.0,pnpm 11 ↔ 12 **锁文件互读兼容**(pnpm 11 读 pnpm 12 的 env 头部文档
57
+ 无碍,`Lockfile is up to date, resolution step is skipped`)。
58
+ - pnpm 12 直接复用 pnpm 11 的 node_modules 结构(`.modules.yaml` 不重写,`Already up to date`)。
59
+ - store 目录沿用平台默认 store(**平台感知**):Linux/WSL `~/.local/share/pnpm/store/v11`;macOS `~/Library/pnpm/store/v11`
60
+ (CAS 内容寻址,跨版本共享,重装快)。取法:`pnpm store path`(不要硬编码)。
61
+
62
+ ## 多 worktree 支持 = global virtual store(GVS):现状与启用姿势(2026-10-15 实测)
63
+
64
+ > 我们大量用 pnpm + 大量 worktree(本机 44 个 checkout)⇒ 「pnpm 官方对多 worktree 的支持是否启用」是个常问项。
65
+ > 结论:**项目级全部未启用**;共享方案本身**实测安全且极快**,但属 **experimental + 逐仓决策**,不要全局一刀切。
66
+
67
+ - **是什么**:默认每个项目的 `node_modules/.pnpm` 各自硬链一份目录结构;开 GVS 后 pnpm 只维护**一份共享虚拟存储**
68
+ `<store-path>/links/`,各项目 `node_modules` 顶层链直接指向它 ⇒ 新增 worktree 安装近乎瞬时、几乎不占磁盘。
69
+ - **怎么开**:`pnpm-workspace.yaml` 加 `virtualStoreType: global`(**≥ 11.23.0** 正式拼写;
70
+ 更早版本为 `enableGlobalVirtualStore: true`,仍兼容)。**CI 无收益**(冷 store)。
71
+ - ⚠️ **死配置陷阱(本机实测)**:写在 `~/.npmrc` 里的 `enableGlobalVirtualStore: true` **不生效**——
72
+ pnpm 12 起 `.npmrc` 只读 auth/registry。判据:中性目录 `pnpm config get enableGlobalVirtualStore` → `undefined`,
73
+ 且真装产物仍是本地 `.pnpm`。**要全局开只能写 `~/.config/pnpm/config.yaml`**。
74
+ - ⚠️ **`<store>/links/@/` 里有条目 ≠ 项目启用过 GVS**:`pnpm dlx` / `pnpm add -g` **默认走 GVS**
75
+ (本机 128 条 9 月残留即来自此)。
76
+ - **本机实测(pnpm 12.5.1,scratch 双 worktree;对照真实仓默认真装 13.8~57.5s / 名义 1.3G)**:
77
+
78
+ | 方案 | worktree 装 | worktree `node_modules` | 顶层链指向 | 删 worktree 后对方 |
79
+ |---|---|---|---|---|
80
+ | 默认 | 小包 8ms;真实仓 13.8~57.5s | 436K(真实 1.3G) | `.pnpm/…`(checkout 内) | 不受影响 |
81
+ | `virtualStoreType: global` | **43ms** | **12K** | `<store>/links/@/…`(**共享 store**) | **仍可解析、不悬空** ✅ |
82
+
83
+ - **与 R20 的关系(判据必须对齐,否则误报)**:GVS 下**顶层链全部"逃出 checkout"**;
84
+ 但它指向的是**共享 store**(删 worktree 不连累他人)——与「软链别人的 checkout」(指向短命 checkout)**本质不同**。
85
+ `dia wt check` 已把 `<storeDir>/links` 列入白名单(另有 `virtualStoreDir` 绝对路径的情形)。
86
+ - **已知局限**:① 共享可写状态,仅同一信任域;② ESM 提升依赖走 `NODE_PATH`——
87
+ **≥11.23.0** 起 pnpm 自己 spawn 的进程(`run/exec/dlx`、生命周期脚本)带 `NODE_OPTIONS --import` resolve hook 兜底,
88
+ 但**在 pnpm 之外直接 `node xxx.mjs` 的脚本拿不到**;③ 官方标注 experimental
89
+ ("some tools may not work correctly with symlinked node_modules")。
90
+ - **启用清单(逐仓)**:升 `packageManager` 到 ≥11.23.0(同步 lockfile env 头部)→ `pnpm-workspace.yaml` 加
91
+ `virtualStoreType: global` → 全量重装 → **lint / typecheck / test / build / serve 全验**(含 `node *.mjs` 类脚本)。
92
+ 迁移背景见 `workflows/worktree-forms.md` §3。
93
+
94
+ ### ⛔ 试点结论(2026-10-16,hummus):**React+TS monorepo 当前不可用**,暂停启用
95
+
96
+ **一句话**:收益很实在(新 worktree 安装 **2.9s**、`node_modules` **1.9M**),但 GVS 会**打破 TS 类型单例**
97
+ ⇒ hummus 的 CI 同款 `nx typecheck web-hub` 直接失败。**我们这边不改动任何仓**,等上游或另找方案。
98
+
99
+ **双 worktree 实测(hummus origin/main,pnpm 11.28.2 + `virtualStoreType: global`)**:
100
+
101
+ | 维度 | 对照(现状 isolated) | GVS |
102
+ |---|---|---|
103
+ | 首装(冷 store) | 17.5–20.8s | 36.1s |
104
+ | **新 worktree 安装(热 store)** | 17.5–20.8s | **2.9s** ⭐ |
105
+ | `node_modules` 体积 | 名义 1.3G | **1.9M** ⭐ |
106
+ | `pnpm-lock.yaml` | — | **零变更**(11.x 不需 env 头部;仅 packageManager 一行) |
107
+ | nx 项目发现 / lint / test / build / dev | 99 / ✓ / ✓ | 99 / ✓ / 185 用例✓ / business build ✓ / HTTP 200 ✓ |
108
+ | `.vite`/`.vitest` 缓存 | 本 worktree | 本 worktree ✓(无跨 worktree 串台) |
109
+ | **`nx typecheck web-hub`(CI 同款)** | ✅ 通过 | ❌ **40 条错误** |
110
+
111
+ **根因(`--traceResolution` 定位)**:GVS 把「每个包的依赖图目录」**实体化**,于是 `@types/react@18.3.1`
112
+ 在 program 里出现 **6 个不同 realpath 的副本**(对照只有 1 个);TS 默认按 realpath 归并声明
113
+ ⇒ 出现"两份 React" ⇒ `not a valid JSX element type` / `TS7006 implicit any`。
114
+
115
+ **单变量对照(关键,别把锅扣给版本升级)**:同 `pnpm@11.28.2`、仅**关闭** GVS ⇒ typecheck **通过**;
116
+ `pnpm@12.11.2` + GVS ⇒ 同样 40 错 ⇒ **上游未修**,是 GVS 本身。
117
+
118
+ **绕法失败记录(省得重试)**:
119
+ - `tsconfig.base.json` `paths` 加 `"@types/react"` 单钉 ⇒ **无效**(TS 的 `@types/*` **自动查找不走 paths**)。
120
+ - 把 `react` 映射到 `./node_modules/@types/react` ⇒ **TS 侧 0 错**(类型单例达成),但 hummus 的 vite 配置
121
+ 有 **`resolve.tsconfigPaths: true`** ⇒ 同一别名**被用到运行时** ⇒ vitest 4 个文件挂
122
+ (`"." is not exported under the conditions [...] from .../@types/react`)⇒ **不可用**。
123
+ ⇒ 真要救,必须把 pin 放进「**只有 tsc 读到、vite 读不到**」的配置层(typecheck / build-dts 专用 tsconfig),
124
+ 需逐项目验证——本轮**不做**(用户 2026-10-16 拍板暂停)。
125
+
126
+ **复现步骤(约 3 分钟,勿在共享主 checkout 做)**:
127
+ ```bash
128
+ bash `dia wt` create <hummus 主仓> gvs-pilot --base origin/main
129
+ cd <worktree> && # package.json: pnpm@11.15.1 → pnpm@11.28.2;pnpm-workspace.yaml 头部加 virtualStoreType: global
130
+ pnpm install # 首装冷 store ~36s;再装(热)~3s
131
+ pnpm exec nx typecheck web-hub --skip-nx-cache # ← GVS 下 40 错;关掉 GVS 同版本 pnpm 下 0 错
132
+ readlink node_modules/react # → <store>/links/@/react/18.3.1/<hash>/...
133
+ ```
134
+
135
+ **与本机其他机制的关系**:GVS 与 `dia wt check` 的白名单已对齐(`<storeDir>/links` 不算违规);
136
+ 与「禁跨 checkout 软链」(R20)**互补不替代** —— 一个仓若将来启用 GVS,仍应保持 `check` 0 违规。
137
+
138
+ **何时可重评**:① pnpm 上游自行修掉「GVS 下 `@types` 跨图目录重复」;② 或对**不依赖 TS 类型单例**的仓试点
139
+ (纯 JS / 非 React / 无 monorepo 类型共享)。**当前结论:不启用** ——「真装 + 禁软链」的 13.8–57.5s 成本可接受。
140
+
141
+ > 📌 **不上报上游(2026-10-16 用户决定:没必要性、不需上报任何人)**——本条只当作**将来重评的条件**,
142
+ > 后续会话**不要**再提议向 pnpm 提 issue。
143
+
144
+ ## 设置读取变化(pnpm 12)
145
+
146
+ pnpm 12 起 **`.npmrc` 只读 auth/registry 设置**,其余设置(`hoistPattern`/`nodeLinker`/
147
+ `shamefullyHoist` 等)**只从 `pnpm-workspace.yaml` 或全局 `~/.config/pnpm/config.yaml` 读**。
148
+ 项目若有非 auth 的 `.npmrc` 设置,升级后须迁移到 `pnpm-workspace.yaml`。
149
+
150
+ ## 升级操作清单
151
+
152
+ 1. `package.json` 改 `packageManager: pnpm@<新版本>`
153
+ 2. `pnpm install`(非 frozen)生成 lockfile env 头部 → 提交
154
+ 3. 存量验证:`pnpm install --frozen-lockfile` 应 `Already up to date`
155
+ 4. 全量重装验证:删 node_modules 后 `pnpm install` + typecheck/lint/build
156
+ 5. CI:镜像(dockerfiles-v2 `pnpm` 镜像)与 packageManager 版本对齐;`pnpm install -g pnpm@latest-N`
157
+ 装的是 runtime binary,配合 packageManager 字段自动用对应版本
158
+
159
+ ## node_modules 悬空软链:`nx` / 二进制集体「找不到模块」(2026-10-08 实测,hummus 主仓)
160
+
161
+ **症状**:仓库内 `nx` / `oxfmt` / pre-push hook 报类如
162
+ `Could not find Nx modules at "<repo>"` 或 `Cannot find module '<repo>/node_modules/<pkg>/bin/...'`,
163
+ 但 `ls node_modules/<pkg>` 明明存在(它是个软链)。
164
+
165
+ **判定(一条命令)**:软链指向**已不存在的 worktree**——
166
+
167
+ ```bash
168
+ find node_modules -maxdepth 1 -type l ! -exec test -e {} \; -print # 悬空软链清单
169
+ readlink node_modules/oxfmt # 看指向(本机实测指向已删的 hummus-worktree--pe-close/node_modules/.pnpm/...)
20
170
  ```
21
171
 
22
- 旧版本写法是 `onlyBuiltDependencies: ["simple-git-hooks"]`。
172
+ **修法**:仓库根 `pnpm install --frozen-lockfile --prefer-offline` —— 实测 hummus 主仓 21.9s
173
+ (94 个悬空链 → 0),只重建链接:**不改** `pnpm-lock.yaml`、工作区仍干净、`oxfmt/oxlint/nx` 全部恢复可执行。
23
174
 
24
- ## lockfile 只做最小增删
175
+ **要点**:
25
176
 
26
- 团队仓加/删依赖时,别用新版 pnpm 全量重解析(会重写上万行、把无关依赖升级):
177
+ - 本地「lint / format / typecheck 跑不起来」时**先查软链,再怀疑代码**——否则会得出「本地验证通过/不通过」
178
+ 的假结论(本机实证:连"删远端分支"这种操作都被 pre-push 拦下——这是钩子在工作,先修环境而不是绕过)。
179
+ - 成因未完全定位(软链指向**另一个 worktree** 的 `.pnpm`,疑与该 worktree 安装时共用 virtual store 有关);
180
+ 但检测 + 修复手段通用 → **删掉 worktree 后顺手跑一次悬空检测**,成本 <1 分钟。
27
181
 
28
- 1. 手工改 `pnpm-lock.yaml`:`importers` 段 + `packages` 段 + `snapshots` 段各一处;
29
- 2. 用 `pnpm install --frozen-lockfile`(不带 `--lockfile-only`)验证:通过即说明 lockfile 满足约束,且不会回写。
182
+ ## 版本迁移坑与受限环境 EPERM → 已迁出 `tools/pnpm-migration.md`
30
183
 
31
- ## 其它
184
+ > 2026-10-26 C13 分页(本页此前 26 KB > 24 KB):**10→11 迁移坑(musl 二进制 / `minimumReleaseAge` /
185
+ > lockfile 同步 / 依赖别名 / `pnpm` 字段失效 / 跨仓升包 / 钉版 EPERM / Alpine libseccomp)** 全部迁至
186
+ > **`tools/pnpm-migration.md`**;本页保留 runtime 架构、store/存量兼容、GVS、设置读取、升级操作清单、悬空软链。
32
187
 
33
- - `pnpm install --frozen-lockfile` 是 CI 的硬门槛,本地也要真装(1~2s 很正常),不要用「借二进制」替代。
34
- - worktree 里安装依赖 = 在**该 worktree** 内安装,不要软链主仓 `node_modules`(见 git kit 的 worktree 页)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minonl/dia",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "type": "module",
5
5
  "description": "dia (嗲) — lightweight agent framework: knowledge index, audit, session ingest",
6
6
  "bin": {