@minonl/dia 0.0.15 → 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,78 @@
1
+ # Kit 层:把「经验」从 persona 里抽出来(设计 v0.1)
2
+
3
+ > 2026-11-06 用户定调:dws-hub 作为 **channel 层**重构,同时把 git / node 这类**通用工程经验**抽成 **kit 层**,
4
+ > 让 yan-agent 更薄、更专业,dabo 等其它 persona 直接复用。本文定 kit 的边界、清单、检索、治理与迁移顺序。
5
+
6
+ ## 0. 三层模型
7
+
8
+ | 层 | 回答的问题 | 例子 | 归属 |
9
+ |---|---|---|---|
10
+ | **persona** | 你是谁、怎么干活(身份/语气/工作流编排) | yan、dabo | 各自 persona 仓 |
11
+ | **kit** | 你会什么(可复用的经验、工具、知识页) | `kit/git`、`kit/node`、`kit/compose-delivery` | kit 源仓 / registry |
12
+ | **channel** | 消息怎么进出 | dws-hub(钉钉 channel+) | dws-hub 仓 |
13
+
14
+ 约束:
15
+
16
+ - kit **不带人格**、不写人设与语气;只放「换个 agent/换个人也用得上」的内容。
17
+ - persona 只保留:身份、规则(must/must-not)、流程编排、**对 kit 的引用**。
18
+ - channel 只管传输与协议,不承载业务经验。
19
+
20
+ ## 1. 双标签:一份内容、多领域可检索
21
+
22
+ 同一条经验经常跨领域(本次实例:hook manager 共存 = git × node)。做法:
23
+
24
+ - kit 清单与知识页 front-matter 都支持 `tags: string[]`;
25
+ - 检索按 tag 命中**同一份内容**,不复制两套;
26
+ - tag 命名:`git` / `node` / `pnpm` / `hooks` / `worktree` / `channel` / `delivery` …(扁平,不做层级)。
27
+
28
+ 示例:
29
+
30
+ ```json
31
+ { "id": "git", "version": "0.1.0", "tags": ["git", "worktree", "hooks"], "knowledge": ["knowledge/**"] }
32
+ ```
33
+
34
+ ## 2. 清单(`dia.json`,kind=kit)增量
35
+
36
+ 在现有字段(files / skills / bin / dirs / commands / knowledge / data_root / requires)上新增:
37
+
38
+ | 字段 | 说明 |
39
+ |---|---|
40
+ | `tags: string[]` | 检索标签(双/多标签) |
41
+ | `provides: string[]` | 提供的知识主题(供 persona 引用与 doctor 校验,如 `git.hooks`) |
42
+ | `requires: { "<kit>": "<semver>" }` | 依赖其它 kit(如 `dingtalk` requires `node`) |
43
+
44
+ ## 3. 检索:kb 必须同时搜 persona KB 与已装 kit
45
+
46
+ 现状:`dia kb` 只索引 persona 自带 knowledge。抽 kit 后若不同时索引 kit,会出现「装了解析不到」的假空。
47
+
48
+ 要求:
49
+
50
+ - `dia kb query --tag git "hook"` → 命中 persona KB + 所有已装 kit 的 knowledge;
51
+ - 结果标注来源(`persona:yan` / `kit:git@0.1.0`),便于追踪;
52
+ - 0 命中时显式告警(沿用现规则,别当「没有知识」)。
53
+
54
+ ## 4. 治理(doctor)
55
+
56
+ | 检查 | 级别 |
57
+ |---|---|
58
+ | kit 已安装且版本满足 persona 的 `requires` | FAIL |
59
+ | kit 内容哈希与 lock 一致(drift) | FAIL(可 `dia kit verify` 修复) |
60
+ | kit 声明的 bin/skills/files 落点仍在 | FAIL |
61
+ | tag 索引与实际知识页 front-matter 一致 | WARN |
62
+ | 同一 tag 下存在重复内容(hash 相同) | WARN(提示合并) |
63
+
64
+ ## 5. 迁移顺序(小步,不搬家式重写)
65
+
66
+ 1. **P0**:本设计 + `dia kit` 支持 `tags`(本 PR)
67
+ 2. **P1(pilot)**:抽 `kit/git` 与 `kit/node`
68
+ - `kit/git`:worktree 纪律、adopt/doctor/gate、守卫模型、对外无逃生口原则
69
+ - `kit/node`:pnpm 11 `allowBuilds`、lockfile 最小变更、frozen-lockfile、hook manager(husky→simple-git-hooks)
70
+ - 交叉页(hook manager 共存)挂在 `kit/git`,`tags` 双写 `[git, node, hooks]`
71
+ 3. **P2**:yan-agent 变薄 —— git/node 页面移入 kit,persona 只留身份 + 工作流 + 索引;`install.sh` 改 `dia kit add <src>`
72
+ 4. **P3**:dabo 装同一套 kit,验证「换 persona 不换经验」;doctor 校验 kit 版本约束
73
+
74
+ ## 6. 非目标
75
+
76
+ - 不做 kit 市场/评分;kit 来源先支持 git ref 与本地路径。
77
+ - 不把 persona 规则(must/must-not)塞进 kit —— 规则仍归 persona,kit 只提供「怎么做」与「踩过什么坑」。
78
+ - channel 层不因 kit 化而承担知识分发。
@@ -9,7 +9,7 @@
9
9
  # 1) 备份(可选但建议)
10
10
  mv ~/.local/share/dia ~/.local/share/dia.bak-$(date +%Y%m%d)
11
11
 
12
- # 2) 用新安装器迁(自动 mv 旧根 → ~/.dia 并装新版)
12
+ # 2) 用新安装器迁数据(只 mv 旧数据根 → ~/.dia;不装、不改 bin 链接)
13
13
  cd ~/repos/github/dia
14
14
  bun run build
15
15
  bash install.sh --migrate
@@ -52,9 +52,10 @@ tar czf ~/backups/dia-job-$TS.tgz -C ~ .local/share/dia-job
52
52
  crontab -l > ~/backups/crontab-$TS.txt
53
53
  # 把所有 `dia-job:` 行注释掉(记下原样,后面恢复)
54
54
 
55
- # 1) 装新版 + 迁数据(只搬数据子目录,不动安装物)
55
+ # 1) 只迁数据(2026-11-06 起 --migrate 不再顺带安装/改 bin 链接,避免抢走 npm 安装)
56
56
  # 源码来自 mac 构建产物:tar 传 bin/dist/packages/docs/install.sh/package.json
57
57
  bash install.sh --migrate
58
+ # 装新版走 npm(推荐):npm i -g @minonl/dia@latest
58
59
 
59
60
  # 2) cache(install.sh 不搬)
60
61
  mkdir -p ~/.dia/cache && [ -d ~/.cache/dia/index ] && mv ~/.cache/dia/index ~/.dia/cache/
package/docs/status.md CHANGED
@@ -28,6 +28,7 @@
28
28
  | `history/framework-bootstrap.md` | 早期启动与分发选型(已归档) | **归档**:方案未采用,实际分发是 `install.sh`,不要按它部署 |
29
29
  | `entrypoints.md` | 用户入口矩阵 | **部分过时**:终端 dabo preset 已渲染;IM bridge 未写;`dia status` / `dia model` 不存在 |
30
30
  | `kit-install.md` | kit 清单、渲染、生命周期 | **设计**:实现只有 `add/list/verify/remove`;`show/update/gc/create` 未做 |
31
+ | `install-guard-design.md` | **安装防呆**:脚本安装 vs npm 全局安装的抢链接判据、exit 3 逃生舱、`doctor install_shadow` | **实现**(D0.65 + `tests/install-guard.test.sh`) |
31
32
  | `patching.md` | 本地 patch | **未实现**(`unknown command`) |
32
33
  | `team-automation.md` | 计划→实现→验证全自动循环 | **未实现**(D0.6–D0.9 只跑通 mock / ACP 驱动) |
33
34
  | `proposals.md` | `team next` 目标候选 | **v0 已实现**(`dia team next`,纯规则打分) |
@@ -90,6 +91,8 @@
90
91
  | D0.21 | worktree 抽取 `packages/wt` | **完成** | 7 处 python→node;`dia wt`;4 套测试原样迁移全绿 |
91
92
  | D0.23 | doctor 平台版本自检 | **完成** | `platform:opencode` / `platform:dsh` 报版本(缺宿主 = warn,不判红);`tests/active-doctor.test.ts` |
92
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) |
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` |
93
96
  | D0.64 | 复制过滤按相对路径判断 | **完成** | 绝对路径匹配 `node_modules` 会误伤 npm 全局安装(kit 整树被过滤 → `E_KIT_EMPTY`);改 `copyFilter(base)` 用相对路径,persona/kit 共用 |
94
97
  | D0.63 | 运行时数据 `.data/` 不进快照、不参与哈希 | **完成** | `TREE_SKIP`(kit/persona 共用):安装复制与 `hashTree`/verify 都跳过 `.data`,防运行时写入造成 verify 假漂移 |
95
98
  | D0.62 | persona 旧版技能软链视为受管 | **完成** | `linkManaged` 同时认 `~/.dia/kits/**` 与 `~/.dia/personas/**`;`persona update` 升级不再需要 `--force` |
@@ -0,0 +1,10 @@
1
+ {
2
+ "schema": 1,
3
+ "kind": "kit",
4
+ "id": "git",
5
+ "version": "0.1.0",
6
+ "root": ".",
7
+ "tags": ["git", "worktree", "hooks"],
8
+ "knowledge": ["knowledge"],
9
+ "requires": { "dia": ">=0.0.2" }
10
+ }
@@ -0,0 +1,30 @@
1
+ ---
2
+ title: hook 模型与管理器共存(git × node)
3
+ tags: [git, node, hooks]
4
+ ---
5
+
6
+ # 守卫钩子与 hook 管理器共存
7
+
8
+ ## 三种模式(dia wt adopt 自动判定)
9
+
10
+ | 仓类型 | 做法 | 效果 |
11
+ |---|---|---|
12
+ | 普通仓 | 钩子写 `<common>/dia/hooks`,仓库本地 `core.hooksPath` 指过去,并链式调用原钩子目录 | 守卫 + 原钩子都执行 |
13
+ | simple-git-hooks 仓 | 不改 `.git/hooks` / `package.json`;守卫写进 `SIMPLE_GIT_HOOKS_RC`,由管理器钩子 source | install 不会覆盖守卫 |
14
+ | 其它管理器(husky / `.githooks`) | 不抢占,doctor WARN | 守卫不生效,建议迁移 |
15
+
16
+ ## 为什么不能接管 simple-git-hooks
17
+
18
+ 它只读**仓库本地** `core.hooksPath`:有值就写到那里 → 一旦把 hooksPath 指向 dia 目录,`pnpm install` 就会把守卫文件覆盖掉。
19
+ **正确做法是 RC 注入**:钩子模板会 `source "$SIMPLE_GIT_HOOKS_RC"`,守卫在那一步执行。
20
+
21
+ ## RC 自递归(已修复,务必保留标记)
22
+
23
+ 管理器钩子 → source RC → `dia wt guard` → 守卫末尾又链回 `<common>/hooks/<name>`(同一文件)→ 死循环,表现为 `git commit` 卡死。
24
+ RC 必须 `export DIA_WT_IN_MANAGER_HOOK=1`,守卫看到标记只判定、不再链式。
25
+
26
+ ## husky → simple-git-hooks 迁移
27
+
28
+ 1. `.husky/*` 内容搬进 `package.json` 的 `simple-git-hooks` 配置;
29
+ 2. 删 `.husky/` 与 husky 依赖,加 `simple-git-hooks` devDependency + `"postinstall": "simple-git-hooks"`;
30
+ 3. 清理失效的本地 `core.hooksPath=.husky`,再 `pnpm install` 重建钩子。
@@ -0,0 +1,34 @@
1
+ ---
2
+ title: worktree 纪律(adopt / doctor / gate)
3
+ tags: [git, worktree, discipline]
4
+ ---
5
+
6
+ # worktree 纪律
7
+
8
+ ## 一次性接管
9
+
10
+ ```bash
11
+ dia wt adopt <repo> # dry-run
12
+ dia wt adopt <repo> --apply
13
+ dia wt adopt --all --apply # 仓根下所有主 checkout(跳过 *-worktree--*)
14
+ ```
15
+
16
+ - 策略写在 `<git-common-dir>/dia/config.json`(defaultBranch / template / hooksManager)
17
+ - 普通仓:钩子写 `<common>/dia/hooks`,仓库本地 `core.hooksPath` 指过去
18
+ - simple-git-hooks 仓:走 `SIMPLE_GIT_HOOKS_RC`(见 hooks 页),不改仓内文件
19
+ - **不写全局 git 配置**(外溢;D20 会检查残留)
20
+
21
+ ## 巡检与门禁
22
+
23
+ ```bash
24
+ dia wt doctor <repo>... [--all] [--fix]
25
+ dia wt gate <repo> <feature> # 提 MR 前:check → doctor → 工作区干净 → ahead>0
26
+ ```
27
+
28
+ 判据要点:D01 配置 / D02 钩子或 RC 就绪 / D03 守卫探针 / D04 主 checkout 干净 / D05–D07 worktree 卫生 / D11 默认分支 / D20 全局残留 / D21 本地 hooksPath 与模式一致。
29
+
30
+ ## 纪律
31
+
32
+ - 任何 git 改动先 `dia wt create`;主 checkout 只 `pull --ff-only` / 看代码。
33
+ - 守卫没有「跳过」开关:钩子慢就先量化再优化(增量 lint、`nx affected`、缓存、下沉 CI)。
34
+ - worktree 内依赖必须**真装**(R20):跨 checkout 软链 `node_modules` 会让 pnpm 改写主仓链接,删 worktree 后整片悬空。
@@ -0,0 +1,10 @@
1
+ {
2
+ "schema": 1,
3
+ "kind": "kit",
4
+ "id": "node",
5
+ "version": "0.1.0",
6
+ "root": ".",
7
+ "tags": ["node", "pnpm", "hooks"],
8
+ "knowledge": ["knowledge"],
9
+ "requires": { "dia": ">=0.0.2" }
10
+ }
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: node 侧 hook 管理器(husky / simple-git-hooks)
3
+ tags: [node, git, hooks]
4
+ ---
5
+
6
+ # node 项目管理 git 钩子的两种主流方式
7
+
8
+ | 方案 | 落点 | 特点 |
9
+ |---|---|---|
10
+ | husky v6+ | `.husky/`(提交进仓),`core.hooksPath=.husky` | 钩子内容进版本库,团队一键生效;但会覆盖任何外部 hooksPath |
11
+ | simple-git-hooks | 生成到 `core.hooksPath`(默认 `.git/hooks`) | 配置在 `package.json`,`postinstall` 自动生成;有 `SIMPLE_GIT_HOOKS_RC` 注入点 |
12
+
13
+ ## 统一建议
14
+
15
+ - 新仓/可改的仓:用 **simple-git-hooks**(配置集中、可被 RC 注入、不占 `.husky`)。
16
+ - 已有 husky 的仓:按 git kit 的「husky → simple-git-hooks 迁移」执行;迁移属于提交改动,团队仓走 MR。
17
+ - 任何情况下**不要**在主 checkout 用跳过开关绕钩子;钩子慢就优化(增量 lint / 缓存 / 下沉 CI)。
@@ -0,0 +1,187 @@
1
+ ---
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
8
+ ---
9
+
10
+ # pnpm 升级与 runtime 架构(可复用工具知识)
11
+
12
+ > 来源:yan-agent `core/knowledge/tools/pnpm.md`(2026-11-09 迁入 kit/node)。
13
+
14
+ > 跨项目可复用的 pnpm 版本升级坑与 pnpm 12「runtime」发行架构,方案式记录(条件 → 解法)。
15
+ > hummus 专属升级上下文见 hummus 仓库 `docs/knowledge/pnpm-12-upgrade.md`;dockerfiles-v2 镜像精确发布姿势见 dockerfiles-v2 仓库 `CLAUDE.md`「精确发布指定镜像」。
16
+
17
+ ## pnpm 12 发行版结构(native binary + wrapper)
18
+
19
+ pnpm 11 的 npm 包是**直接内置 native binary**(顶层 `pnpm` 文件即 ELF)。pnpm 12 起改为
20
+ **wrapper + native binary 拆分**:
21
+
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/...)
170
+ ```
171
+
172
+ **修法**:仓库根 `pnpm install --frozen-lockfile --prefer-offline` —— 实测 hummus 主仓 21.9s
173
+ (94 个悬空链 → 0),只重建链接:**不改** `pnpm-lock.yaml`、工作区仍干净、`oxfmt/oxlint/nx` 全部恢复可执行。
174
+
175
+ **要点**:
176
+
177
+ - 本地「lint / format / typecheck 跑不起来」时**先查软链,再怀疑代码**——否则会得出「本地验证通过/不通过」
178
+ 的假结论(本机实证:连"删远端分支"这种操作都被 pre-push 拦下——这是钩子在工作,先修环境而不是绕过)。
179
+ - 成因未完全定位(软链指向**另一个 worktree** 的 `.pnpm`,疑与该 worktree 安装时共用 virtual store 有关);
180
+ 但检测 + 修复手段通用 → **删掉 worktree 后顺手跑一次悬空检测**,成本 <1 分钟。
181
+
182
+ ## 版本迁移坑与受限环境 EPERM → 已迁出 `tools/pnpm-migration.md`
183
+
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、设置读取、升级操作清单、悬空软链。
187
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minonl/dia",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "type": "module",
5
5
  "description": "dia (嗲) — lightweight agent framework: knowledge index, audit, session ingest",
6
6
  "bin": {
@@ -1251,6 +1251,8 @@ if [ "$adopted" = 1 ]; then
1251
1251
  fi
1252
1252
 
1253
1253
  # 链式:仓库自己的钩子(simple-git-hooks 等管理器生成在 <common>/hooks)
1254
+ # 若当前就是从管理器钩子里的 RC 进来的,再链回去会自递归 → 直接结束
1255
+ if [ "${DIA_WT_IN_MANAGER_HOOK:-0}" = "1" ]; then exit 0; fi
1254
1256
  orig="$common/hooks/$name"
1255
1257
  if [ -x "$orig" ] && [ "$orig" != "$0" ]; then
1256
1258
  if [ "$name" = "pre-push" ] && [ ${#push_lines[@]} -gt 0 ]; then
@@ -1435,10 +1437,22 @@ PYCHK
1435
1437
  # 由 simple-git-hooks 生成的钩子 source 本文件(SIMPLE_GIT_HOOKS_RC)
1436
1438
  # 只对已 adopt 的仓生效;未 adopt → 直接返回
1437
1439
  if command -v dia >/dev/null 2>&1; then
1440
+ export DIA_WT_IN_MANAGER_HOOK=1 # 防止守卫再链回管理器钩子(自递归)
1438
1441
  dia wt guard "$(basename "$0")" || exit $?
1439
1442
  fi
1440
1443
  RC
1441
1444
  wt_ensure_env_export SIMPLE_GIT_HOOKS_RC "$rcf"
1445
+ # 迁移残留:本地 hooksPath 指向已删除的目录(如刚从 husky 切过来)→ 撤掉,让管理器写 .git/hooks
1446
+ if [ -n "$prev" ]; then
1447
+ local resolved="$prev"
1448
+ case "$resolved" in /*) ;; *) resolved="$repo/$resolved" ;; esac
1449
+ if [ ! -d "$resolved" ]; then
1450
+ git -C "$repo" config --local --unset core.hooksPath 2>/dev/null || true
1451
+ echo "[dia-wt] 清理失效的本地 core.hooksPath($prev 已不存在)" >&2
1452
+ else
1453
+ echo "[dia-wt] ⚠️ 本仓仍设有 core.hooksPath=$prev(会覆盖管理器钩子),请确认后移除" >&2
1454
+ fi
1455
+ fi
1442
1456
  python3 - "$cfg" "$repo" "$def" "$template" "$prev" <<'PYG'
1443
1457
  import json, sys, datetime
1444
1458
  cfg, repo, br, tmpl, prev = sys.argv[1:6]
@@ -1551,6 +1565,7 @@ cmd_doctor() {
1551
1565
  cat > "$rcf" <<'RC'
1552
1566
  # 由 simple-git-hooks 生成的钩子 source 本文件(SIMPLE_GIT_HOOKS_RC)
1553
1567
  if command -v dia >/dev/null 2>&1; then
1568
+ export DIA_WT_IN_MANAGER_HOOK=1 # 防止守卫再链回管理器钩子(自递归)
1554
1569
  dia wt guard "$(basename "$0")" || exit $?
1555
1570
  fi
1556
1571
  RC
@@ -61,6 +61,14 @@ grep -q "SIMPLE_GIT_HOOKS_RC" "$HOME/.zshenv" 2>/dev/null && ok "shell rc 注入
61
61
  bash "$WT" doctor "$RN" >/dev/null 2>&1 && ok "npm 仓 doctor 通过" || bad "npm 仓 doctor 失败"
62
62
  (cd "$RN" && DIA_WT_GUARD_PROBE=1 bash "$WT" guard pre-commit >/dev/null 2>&1) && bad "guard 未拦截 main 提交" || ok "guard 拦截 main 提交"
63
63
 
64
+ # RC 注入不得自递归:模拟管理器钩子(source RC 后应直接结束,不再链回自己)
65
+ RC="$( wt_hooks_rc_dir 2>/dev/null || true )"; true
66
+ printf '#!/usr/bin/env bash\nset -e\nname=pre-commit\nsource "$SIMPLE_GIT_HOOKS_RC"\n' > "$TD/pre-commit"
67
+ chmod +x "$TD/pre-commit"
68
+ (cd "$RN" && timeout 10 env SIMPLE_GIT_HOOKS_RC="$DIA_ROOT/hooks/simple-git-hooks-rc.sh" "$TD/pre-commit" >/tmp/rc.log 2>&1)
69
+ rc=$?; [ "$rc" != 124 ] && ok "RC 注入不自递归(main 上被守卫拦截,退出码 $rc)" || bad "RC 注入自递归/超时"
70
+ cd - >/dev/null
71
+
64
72
  # 全局配置残留检查:旧版全局钩子目录 → doctor FAIL
65
73
  LEGACY="$TD/legacy-hooks"; mkdir -p "$LEGACY"
66
74
  printf '#!/usr/bin/env bash\n# dia-wt guard(旧)\nexit 0\n' > "$LEGACY/pre-commit"; chmod +x "$LEGACY/pre-commit"