dsh-vibe-math 2.3.12 → 2.3.14

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.
Files changed (49) hide show
  1. package/README.md +17 -13
  2. package/cordis.patch.yml +1 -1
  3. package/{AUDIT-CHECKLIST.md → docs/AUDIT-CHECKLIST.md} +323 -304
  4. package/docs/COMPAT-AUDIT-ROUND2.md +325 -0
  5. package/docs/generate_framework_diagram_v2.py +114 -0
  6. package/docs/generate_framework_diagram_v3.py +127 -0
  7. package/{RELEASE-NOTES-2.1.0.md → docs/release-notes/RELEASE-NOTES-2.1.0.md} +143 -143
  8. package/{RELEASE-NOTES-2.2.0.md → docs/release-notes/RELEASE-NOTES-2.2.0.md} +266 -266
  9. package/{RELEASE-NOTES-2.2.1.md → docs/release-notes/RELEASE-NOTES-2.2.1.md} +43 -43
  10. package/{RELEASE-NOTES-2.2.2.md → docs/release-notes/RELEASE-NOTES-2.2.2.md} +88 -88
  11. package/{RELEASE-NOTES-2.3.0.md → docs/release-notes/RELEASE-NOTES-2.3.0.md} +207 -207
  12. package/{RELEASE-NOTES-2.3.1.md → docs/release-notes/RELEASE-NOTES-2.3.1.md} +134 -134
  13. package/{RELEASE-NOTES-2.3.10.md → docs/release-notes/RELEASE-NOTES-2.3.10.md} +105 -105
  14. package/{RELEASE-NOTES-2.3.11.md → docs/release-notes/RELEASE-NOTES-2.3.11.md} +57 -57
  15. package/{RELEASE-NOTES-2.3.12.md → docs/release-notes/RELEASE-NOTES-2.3.12.md} +80 -80
  16. package/docs/release-notes/RELEASE-NOTES-2.3.13.md +137 -0
  17. package/docs/release-notes/RELEASE-NOTES-2.3.14.md +83 -0
  18. package/{RELEASE-NOTES-2.3.2.md → docs/release-notes/RELEASE-NOTES-2.3.2.md} +145 -145
  19. package/{RELEASE-NOTES-2.3.3.md → docs/release-notes/RELEASE-NOTES-2.3.3.md} +115 -115
  20. package/{RELEASE-NOTES-2.3.4.md → docs/release-notes/RELEASE-NOTES-2.3.4.md} +69 -69
  21. package/{RELEASE-NOTES-2.3.5.md → docs/release-notes/RELEASE-NOTES-2.3.5.md} +63 -63
  22. package/{RELEASE-NOTES-2.3.6.md → docs/release-notes/RELEASE-NOTES-2.3.6.md} +66 -66
  23. package/{RELEASE-NOTES-2.3.7.md → docs/release-notes/RELEASE-NOTES-2.3.7.md} +59 -59
  24. package/{RELEASE-NOTES-2.3.8.md → docs/release-notes/RELEASE-NOTES-2.3.8.md} +45 -45
  25. package/{RELEASE-NOTES-2.3.9.md → docs/release-notes/RELEASE-NOTES-2.3.9.md} +70 -70
  26. package/docs/test-timing.md +19 -18
  27. package/installer.js +115 -41
  28. package/package.json +43 -37
  29. package/{audit-formal-sensitivity.mjs → tests/audit-formal-sensitivity.mjs} +342 -342
  30. package/{audit-installer-compat.test.mjs → tests/audit-installer-compat.test.mjs} +136 -136
  31. package/tests/audit-installer-policy.test.mjs +261 -0
  32. package/{audit-persona-sensitivity.mjs → tests/audit-persona-sensitivity.mjs} +249 -249
  33. package/{audit-persona-surface.test.mjs → tests/audit-persona-surface.test.mjs} +349 -349
  34. package/{audit-prompt-invariants.mjs → tests/audit-prompt-invariants.mjs} +508 -508
  35. package/{audit-spec-traceability.mjs → tests/audit-spec-traceability.mjs} +193 -193
  36. package/{audit-v5-integrity.mjs → tests/audit-v5-integrity.mjs} +448 -448
  37. package/{audit-v5-sensitivity.mjs → tests/audit-v5-sensitivity.mjs} +384 -384
  38. package/{e2e-v5-round2.test.mjs → tests/e2e-v5-round2.test.mjs} +521 -521
  39. package/{formal-verify-v2.test.mjs → tests/formal-verify-v2.test.mjs} +1315 -1315
  40. package/{formal-verify-v3.test.mjs → tests/formal-verify-v3.test.mjs} +1257 -1257
  41. package/{formal-verify-v4.test.mjs → tests/formal-verify-v4.test.mjs} +1082 -1082
  42. package/{formal-verify-v5.test.mjs → tests/formal-verify-v5.test.mjs} +708 -708
  43. package/{prompt-v5-integrity.test.mjs → tests/prompt-v5-integrity.test.mjs} +3 -3
  44. package/{run-tests.mjs → tests/run-tests.mjs} +121 -118
  45. package/{selfdrive-v5.mjs → tests/selfdrive-v5.mjs} +470 -470
  46. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1 -1
  47. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1 -1
  48. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +1 -1
  49. /package/{RELEASE-NOTES-2.0.22.md → docs/release-notes/RELEASE-NOTES-2.0.22.md} +0 -0
@@ -1,70 +1,70 @@
1
- # dsh-vibe-math 2.3.9 — v4 架构图重绘为 SVG(零依赖生成器 + README 展示)
2
-
3
- > 上一版:2.3.8。本版把 v4 的架构图从"matplotlib 生成的位图"换成**零依赖 Node 生成的 SVG**,
4
- > 与 v5 同一套版式语言,并把它纳入随包发布(README 里的图在 GitHub 与 npm 上都能正常显示)。
5
- > 无代码行为变更,四套预设字节未变。
6
-
7
- ---
8
-
9
- ## 1. 为什么换
10
-
11
- | | 旧(`框架图-v4.png` + `generate_framework_diagram_v4.py`) | 新(`框架图-v4.svg` + `generate_framework_diagram_v4.mjs`) |
12
- |---|---|---|
13
- | 生成方式 | Python + matplotlib | **纯 Node,零依赖**(本仓库运行时自带 Node) |
14
- | 产物 | 位图,缩放糊、无法 diff | 纯文本 SVG,任意缩放、可评审、diff 友好 |
15
- | 信息量 | 5 个方框 + 若干小字 | 分带:定论门槛 / 常驻层 / 框架六面 / 数据面 / 哲学红线 + 图例 |
16
- | 版式校验 | 无(只能靠眼睛) | 生成时**估算文字宽度**,任何一行溢出容器即 WARN + 退出码 1 |
17
- | 随包发布 | 否(npm 上 README 图裂) | **是**(`files` 收录,GitHub 与 npm 均可显示) |
18
-
19
- 旧文件已删除(需要时可从 git 历史取);`README.md` 的图源链接与说明同步更新,
20
- `docs/generate_framework_diagram_v5.mjs` 头部注释里的"v2/v3/v4 用 matplotlib"也改成"v2/v3"。
21
-
22
- ## 2. 新图覆盖的内容(全部与实现逐项核对过)
23
-
24
- - **定论门槛**:全体一致为真(全 1)/ 一致为假(全 0)→ 写入 `Verified/`;
25
- **没有** forced / flat / 近共识收口;分歧 → 公开辩论重评;仍未全票 → 留库 + 平均概率 + 辩论录。
26
- - **常驻层**:`continuable` 持久会话、独立上下文、自主方向、只写自己的库 / 跨读他人库(只读)、
27
- 唤醒 = `subagents.sendMessage`、一轮 = 一次完整思考;起始 brainstorm 产出各自方向;全票"已解决"才停。
28
- - **框架六面**(逐条对齐代码/规格):
29
- - 消息总线·邮件箱:`vibe_v4_message(to|all)` → 入目标邮箱 → 空闲则唤醒;常驻之间不直接互调;
30
- - 会议 / 辩论:`vibe_v4_meeting(agenda)` → 全体发言 → `Shared/meetings/<id>.md` → 广播结论;
31
- 看门狗防死锁;与验证**互斥**(排队,不抢占);
32
- - 任务板(只搬运):提议/认领写在回执里 → `Shared/taskboard.md`;框架不决定谁做什么;
33
- - 共识验证(全票):回执字段 `propose_verify` → FIFO 排队 → 独立初评(互不可见)→ 公开辩论重评
34
- (最多 `verdictMaxRounds` 轮,默认 3);
35
- - 上下文 / compact:`contextPct ≥ compactThreshold`(默认 66)触发 DSH `/compact`,
36
- `compactAfterRounds` 默认 8,压缩后重申核心规则;
37
- - 活性 / 并发 / 恢复:`activityTimeoutMs` 心跳、`maxParallel` 并发闸、停滞 `stallAutoMeetingMs`
38
- (默认 6 min)自动开会、`resume` 用 `residents.json` + `progress.md` 重种化。
39
- - **回执字段**(**按代码里真正的解析字段列**,不是照抄记忆):
40
- `summary / solved / input / vote / propose_verify / propose_task / claim_task / task_done / voteSolved / formal / contextPct`。
41
- (核对时发现我第一版图里写了 `reject_assign` 与 `vote_solved` —— 前者是 **v5** 的字段、后者是拼错的
42
- 驼峰名,v4 实际是 `voteSolved`;已改正。)
43
- - **数据面**(真实路径核对过):
44
- `Progress/<r-id>/progress.md`、`Propos/<r-id>/<p-id>.md`、`Methods/<r-id>/<m-id>.md`、
45
- `Subproblems/<r-id>/<s-id>.md`(每条记录必填 价值程度 / 动机用途计划 / 自身概率估计);
46
- `Shared/meetings|debates`、`Shared/taskboard.md`、`Shared/meetings/brainstorm.md`、
47
- `Verified/命题|问题/<id>.md`、`Problems/<id>.md`;
48
- `State/residents.json`、`mailboxes.json`、`taskboard.json`、`decisions.json`、`session.json`、
49
- `settings.json`、`formal.json`;`fileOwner` 写锁 / `projectLock` / `processEpoch` / `abort → resume`。
50
- - **哲学红线**:框架绝不指派 / 定论必须全体一致 / 只有 `Verified/` 绝对可信 / 常驻不直接互调 /
51
- 人工干预不改变自组织。
52
-
53
- ## 3. 验收
54
-
55
- | 项 | 结果 |
56
- |---|---|
57
- | 生成器 | `node docs/generate_framework_diagram_v4.mjs` → `示例图/框架图-v4.svg`(23501 字节),**0 条溢出告警** |
58
- | 渲染校验 | 无头 Chrome 截图 1760×1300 逐带人工核对(无重叠、无截断;左侧控制通道不穿过常驻) |
59
- | 全量并行回归 | 23/23 |
60
- | 静态守卫 | invariants 157/0(self-probe 5/5)、traceability 94/0、v5-integrity clean、persona-surface 197/0 |
61
- | closing verification | 18/18(含"包内所有声明文件存在") |
62
- | 发布产物自证 | registry 取回 tarball 比对 sha1 + 包内跑随包套件(见发布记录) |
63
-
64
- ## 4. 升级
65
-
66
- ```
67
- npm i dsh-vibe-math@latest
68
- ```
69
-
70
- 无迁移。README 里的 v4 图现在读 `示例图/框架图-v4.svg`。
1
+ # dsh-vibe-math 2.3.9 — v4 架构图重绘为 SVG(零依赖生成器 + README 展示)
2
+
3
+ > 上一版:2.3.8。本版把 v4 的架构图从"matplotlib 生成的位图"换成**零依赖 Node 生成的 SVG**,
4
+ > 与 v5 同一套版式语言,并把它纳入随包发布(README 里的图在 GitHub 与 npm 上都能正常显示)。
5
+ > 无代码行为变更,四套预设字节未变。
6
+
7
+ ---
8
+
9
+ ## 1. 为什么换
10
+
11
+ | | 旧(`框架图-v4.png` + `generate_framework_diagram_v4.py`) | 新(`框架图-v4.svg` + `generate_framework_diagram_v4.mjs`) |
12
+ |---|---|---|
13
+ | 生成方式 | Python + matplotlib | **纯 Node,零依赖**(本仓库运行时自带 Node) |
14
+ | 产物 | 位图,缩放糊、无法 diff | 纯文本 SVG,任意缩放、可评审、diff 友好 |
15
+ | 信息量 | 5 个方框 + 若干小字 | 分带:定论门槛 / 常驻层 / 框架六面 / 数据面 / 哲学红线 + 图例 |
16
+ | 版式校验 | 无(只能靠眼睛) | 生成时**估算文字宽度**,任何一行溢出容器即 WARN + 退出码 1 |
17
+ | 随包发布 | 否(npm 上 README 图裂) | **是**(`files` 收录,GitHub 与 npm 均可显示) |
18
+
19
+ 旧文件已删除(需要时可从 git 历史取);`README.md` 的图源链接与说明同步更新,
20
+ `docs/generate_framework_diagram_v5.mjs` 头部注释里的"v2/v3/v4 用 matplotlib"也改成"v2/v3"。
21
+
22
+ ## 2. 新图覆盖的内容(全部与实现逐项核对过)
23
+
24
+ - **定论门槛**:全体一致为真(全 1)/ 一致为假(全 0)→ 写入 `Verified/`;
25
+ **没有** forced / flat / 近共识收口;分歧 → 公开辩论重评;仍未全票 → 留库 + 平均概率 + 辩论录。
26
+ - **常驻层**:`continuable` 持久会话、独立上下文、自主方向、只写自己的库 / 跨读他人库(只读)、
27
+ 唤醒 = `subagents.sendMessage`、一轮 = 一次完整思考;起始 brainstorm 产出各自方向;全票"已解决"才停。
28
+ - **框架六面**(逐条对齐代码/规格):
29
+ - 消息总线·邮件箱:`vibe_v4_message(to|all)` → 入目标邮箱 → 空闲则唤醒;常驻之间不直接互调;
30
+ - 会议 / 辩论:`vibe_v4_meeting(agenda)` → 全体发言 → `Shared/meetings/<id>.md` → 广播结论;
31
+ 看门狗防死锁;与验证**互斥**(排队,不抢占);
32
+ - 任务板(只搬运):提议/认领写在回执里 → `Shared/taskboard.md`;框架不决定谁做什么;
33
+ - 共识验证(全票):回执字段 `propose_verify` → FIFO 排队 → 独立初评(互不可见)→ 公开辩论重评
34
+ (最多 `verdictMaxRounds` 轮,默认 3);
35
+ - 上下文 / compact:`contextPct ≥ compactThreshold`(默认 66)触发 DSH `/compact`,
36
+ `compactAfterRounds` 默认 8,压缩后重申核心规则;
37
+ - 活性 / 并发 / 恢复:`activityTimeoutMs` 心跳、`maxParallel` 并发闸、停滞 `stallAutoMeetingMs`
38
+ (默认 6 min)自动开会、`resume` 用 `residents.json` + `progress.md` 重种化。
39
+ - **回执字段**(**按代码里真正的解析字段列**,不是照抄记忆):
40
+ `summary / solved / input / vote / propose_verify / propose_task / claim_task / task_done / voteSolved / formal / contextPct`。
41
+ (核对时发现我第一版图里写了 `reject_assign` 与 `vote_solved` —— 前者是 **v5** 的字段、后者是拼错的
42
+ 驼峰名,v4 实际是 `voteSolved`;已改正。)
43
+ - **数据面**(真实路径核对过):
44
+ `Progress/<r-id>/progress.md`、`Propos/<r-id>/<p-id>.md`、`Methods/<r-id>/<m-id>.md`、
45
+ `Subproblems/<r-id>/<s-id>.md`(每条记录必填 价值程度 / 动机用途计划 / 自身概率估计);
46
+ `Shared/meetings|debates`、`Shared/taskboard.md`、`Shared/meetings/brainstorm.md`、
47
+ `Verified/命题|问题/<id>.md`、`Problems/<id>.md`;
48
+ `State/residents.json`、`mailboxes.json`、`taskboard.json`、`decisions.json`、`session.json`、
49
+ `settings.json`、`formal.json`;`fileOwner` 写锁 / `projectLock` / `processEpoch` / `abort → resume`。
50
+ - **哲学红线**:框架绝不指派 / 定论必须全体一致 / 只有 `Verified/` 绝对可信 / 常驻不直接互调 /
51
+ 人工干预不改变自组织。
52
+
53
+ ## 3. 验收
54
+
55
+ | 项 | 结果 |
56
+ |---|---|
57
+ | 生成器 | `node docs/generate_framework_diagram_v4.mjs` → `示例图/框架图-v4.svg`(23501 字节),**0 条溢出告警** |
58
+ | 渲染校验 | 无头 Chrome 截图 1760×1300 逐带人工核对(无重叠、无截断;左侧控制通道不穿过常驻) |
59
+ | 全量并行回归 | 23/23 |
60
+ | 静态守卫 | invariants 157/0(self-probe 5/5)、traceability 94/0、v5-integrity clean、persona-surface 197/0 |
61
+ | closing verification | 18/18(含"包内所有声明文件存在") |
62
+ | 发布产物自证 | registry 取回 tarball 比对 sha1 + 包内跑随包套件(见发布记录) |
63
+
64
+ ## 4. 升级
65
+
66
+ ```
67
+ npm i dsh-vibe-math@latest
68
+ ```
69
+
70
+ 无迁移。README 里的 v4 图现在读 `示例图/框架图-v4.svg`。
@@ -8,15 +8,15 @@
8
8
  ## 1. 怎么跑(并行是默认)
9
9
 
10
10
  ```bash
11
- node run-tests.mjs # 全部 *.test.mjs,并行(并发 = min(4, CPU 核数))
12
- node run-tests.mjs --only formal # 只跑名字含 formal 的套件
13
- node run-tests.mjs --concurrency=6 # 手动指定并发
14
- node audit-formal-sensitivity.mjs # 49 条不变式探针,并行(--concurrency=N / --only=<preset> / --list)
15
- node audit-persona-sensitivity.mjs # 11 条提示词面探针(串行,本身只要几秒)
16
- node audit-prompt-invariants.mjs # 静态:四套的提示词/工具面不变式 + 扫描器自检(< 0.1s)
17
- node audit-prompt-invariants.mjs --self-probe # 证明上面那 157 条不变式真的会变红(5 个自探针)
18
- node audit-spec-traceability.mjs # 静态:规格/README ↔ 代码可追溯(< 0.1s)
19
- node audit-v5-integrity.mjs # 静态:v5 完整性/理念门禁 + 扫描器解析级自检(≈0.5 s)
11
+ node tests/run-tests.mjs # 全部 *.test.mjs(在 tests/ 下),并行(并发 = min(4, CPU 核数);命令都从**仓库根**执行)
12
+ node tests/run-tests.mjs --only formal # 只跑名字含 formal 的套件
13
+ node tests/run-tests.mjs --concurrency=6 # 手动指定并发
14
+ node tests/audit-formal-sensitivity.mjs # 49 条不变式探针,并行(--concurrency=N / --only=<preset> / --list)
15
+ node tests/audit-persona-sensitivity.mjs # 11 条提示词面探针(串行,本身只要几秒)
16
+ node tests/audit-prompt-invariants.mjs # 静态:四套的提示词/工具面不变式 + 扫描器自检(< 0.1s)
17
+ node tests/audit-prompt-invariants.mjs --self-probe # 证明上面那 157 条不变式真的会变红(5 个自探针)
18
+ node tests/audit-spec-traceability.mjs # 静态:规格/README ↔ 代码可追溯(< 0.1s)
19
+ node tests/audit-v5-integrity.mjs # 静态:v5 完整性/理念门禁 + 扫描器解析级自检(≈0.5 s)
20
20
  ```
21
21
 
22
22
  两个并行 runner 都会打印**每项耗时 + 汇总(wall / sum / speed-up / 最慢几项)**。跑完请读这几行。
@@ -25,14 +25,15 @@ node audit-v5-integrity.mjs # 静态:v5 完整性/理念门禁 +
25
25
 
26
26
  | 脚本 | 串行(sum) | 并行(wall) | 实测输出 |
27
27
  |---|---|---|---|
28
- | `run-tests.mjs`(23 个套件) | 221.5 s | **111.5 s**(并发 4,speed-up x1.99) | 关键路径 = `e2e-v4-fixes` 98.1 s |
28
+ | `tests/run-tests.mjs`(26 个套件) | 225.8 s | **109.7 s**(并发 4,speed-up x2.06) | 关键路径 = `e2e-v4-fixes` 95.9 s |
29
+ | `tests/audit-installer-policy.test.mjs` | ≈ 1 s | — | 在临时 DSH_HOME 里驱动真实安装器(复制 4 套预设 × 2 个版本) |
29
30
  | `audit-formal-sensitivity.mjs`(49 探针) | 612.0 s | **154.6 s**(并发 4,speed-up x3.96) | 关键路径 = 12 个 v2 探针(每个 ≈32 s) |
30
31
  | `audit-persona-sensitivity.mjs`(11 探针) | ≈ 5 s | — | 本身很快,不需要并行 |
31
32
  | `audit-prompt-invariants.mjs`(157 条,含 X5–X8b 扫描器自检) | 0.4 s | — | 静态 |
32
33
  | `audit-prompt-invariants.mjs --self-probe`(5 探针) | 1.5 s | — | 每个探针 = 一次自我重跑(0.3 s) |
33
34
  | `audit-spec-traceability.mjs`(94 条) | 0.3 s | — | 静态 |
34
35
  | `audit-v5-integrity.mjs` | ≈ 0.5 s | — | 静态审计(含扫描器自检) |
35
- | `prompt-v5-integrity.test.mjs` | 1.6 s | — | 虚拟时钟下生成 v5 语料(语料字节稳定) |
36
+ | `prompt-v5-integrity.test.mjs` | 1.6 s | — | 虚拟时钟下生成 v5 语料(语料字节稳定;**stdout 里的会议成员顺序仍是运行间随机的**,只有落盘语料是逐字节确定的) |
36
37
 
37
38
  > 优化前:全量回归 ≈ 5.5 min(串行,`formal-verify-v2` 单独 186 s);
38
39
  > 探针脚本 ≈ **38 min**(49 条串行,其中 12 条 × `formal-verify-v2` 162 s)。
@@ -49,7 +50,7 @@ node audit-v5-integrity.mjs # 静态:v5 完整性/理念门禁 +
49
50
  | `e2e-d9-d13.test.mjs` | ≈ 13 s | |
50
51
  | `e2e-v3.test.mjs` | ≈ 12 s | |
51
52
  | `formal-verify-v3.test.mjs` | ≈ 12 s | |
52
- | 其余 16 个 | ≤ 6 s | 其中 8 个 < 1 s |
53
+ | 其余 19 个 | ≤ 6 s | 其中 12 个 < 1 s |
53
54
 
54
55
  ## 3. 已经做过的优化(别再重复踩)
55
56
 
@@ -90,12 +91,12 @@ node audit-v5-integrity.mjs # 静态:v5 完整性/理念门禁 +
90
91
 
91
92
  | 目的 | 跑什么 | 预期 |
92
93
  |---|---|---|
93
- | 改了某个架构的插件 | `node run-tests.mjs --only <vN>` + `node audit-formal-sensitivity.mjs --only=vN` | 30 s – 2 min |
94
- | 改了提示词/人设 | `node run-tests.mjs --only persona --only prompt` + `node audit-persona-sensitivity.mjs` | ≈ 15 s |
95
- | **改了任何工具的参数 schema / 参数处理** | `node audit-prompt-invariants.mjs --self-probe` + `node run-tests.mjs --only formal` | ≈ 40 s(v2 套件占大头) |
96
- | 改了共享契约 / 发版前 | `node run-tests.mjs` + `node audit-formal-sensitivity.mjs` + `node audit-persona-sensitivity.mjs` + `node audit-prompt-invariants.mjs --self-probe` + `node audit-spec-traceability.mjs` + `node audit-v5-integrity.mjs` | ≈ 4.5 min |
97
- | 只想快速看提示词/文档有没有漂移 | `node audit-prompt-invariants.mjs && node audit-spec-traceability.mjs` | **< 0.5 s** |
98
- | 只想知道"快不快" | `node run-tests.mjs --json` | 读 `wallSeconds` / `slowest` |
94
+ | 改了某个架构的插件 | `node tests/run-tests.mjs --only <vN>` + `node tests/audit-formal-sensitivity.mjs --only=vN` | 30 s – 2 min |
95
+ | 改了提示词/人设 | `node tests/run-tests.mjs --only persona --only prompt` + `node tests/audit-persona-sensitivity.mjs` | ≈ 15 s |
96
+ | **改了任何工具的参数 schema / 参数处理** | `node tests/audit-prompt-invariants.mjs --self-probe` + `node tests/run-tests.mjs --only formal` | ≈ 40 s(v2 套件占大头) |
97
+ | 改了共享契约 / 发版前 | `node tests/run-tests.mjs` + `node tests/audit-formal-sensitivity.mjs` + `node tests/audit-persona-sensitivity.mjs` + `node tests/audit-prompt-invariants.mjs --self-probe` + `node tests/audit-spec-traceability.mjs` + `node tests/audit-v5-integrity.mjs` | ≈ 4.5 min |
98
+ | 只想快速看提示词/文档有没有漂移 | `node tests/audit-prompt-invariants.mjs && node tests/audit-spec-traceability.mjs` | **< 0.5 s** |
99
+ | 只想知道"快不快" | `node tests/run-tests.mjs --json` | 读 `wallSeconds` / `slowest` |
99
100
 
100
101
  **每次跑完都要看那几行 timing**:如果某个套件突然比基线慢很多,先怀疑新增的固定等待,
101
102
  再怀疑它是否在等一个永远不会发生的条件(这正是 v2 套件 186 s 的成因)。
package/installer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // dsh-vibe-math merged bundle installer — VERSIONED AUTO-UPDATE.
2
- // When this bundle is installed (e.g. `dsh plugin add dsh-vibe-math` or from the
2
+ // When this bundle is installed (e.g. `dsh plugin --profile <name> add dsh-vibe-math`, or from the
3
3
  // dsh-market), this plugin copies ALL FOUR agent presets out of the package into
4
4
  // the DSH preset root, so the user immediately gets four presets in the picker:
5
5
  // vibe-math-v2/ (probability-driven architecture)
@@ -16,16 +16,21 @@
16
16
  // ships v2/v3/v4/v5.)
17
17
  //
18
18
  // UPDATE POLICY (state recorded in <presetRoot>/.vibe-math-installed.json):
19
- // - baseline (no state file — e.g. upgrading from an installer that predates
20
- // this mechanism): every existing owned file is refreshed to the current
21
- // package version and recorded as package-owned (user policy: auto-update
22
- // old installs; any manual edits made before this baseline are overwritten
23
- // once — from then on edits are protected).
24
- // - upgrade (recorded version != current package.json version): every owned
25
- // file that is byte-identical to the previously installed copy (i.e. NOT
26
- // user-edited since) is overwritten with the new version; user-edited files
27
- // are preserved and reported via the logger.
28
- // - same version: no-op (idempotent). Missing files are ALWAYS restored.
19
+ // - FORCE-REPLACE ON VERSION CHANGE. When the recorded version differs from this package's
20
+ // version — or there is no record at all (an install made by an older installer) — every
21
+ // managed file is overwritten with the shipped bytes. This is deliberately NOT conditional on
22
+ // the file being unmodified. Two reasons:
23
+ // · a preset assembled from two different versions (the old policy updated a file's
24
+ // neighbours and kept the file the user had touched) is exactly the state that fails to
25
+ // mount or misbehaves subtly, and the user has no way to see that from the outside;
26
+ // · editing a shipped preset in place is not the supported way to customize one — DSH
27
+ // provides a real one (copy the preset: the picker's copy action, or a new directory
28
+ // under <presetRoot>), which leaves the managed set updatable.
29
+ // - Nothing is destroyed silently: before a file whose bytes are not what the installer last
30
+ // wrote is replaced, the user's copy is kept under
31
+ // <presetRoot>/.vibe-math-backup/<fromVersion>/<preset>/<file> and named in the log.
32
+ // - same version: no-op (idempotent) — restarting DSH never rewrites a file or churns the
33
+ // preset's generation stamp. Missing files are ALWAYS restored, at any version.
29
34
  // - force a full refresh at any time: delete the preset dirs and restart DSH.
30
35
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, unlinkSync, rmdirSync } from 'node:fs'
31
36
  import { createRequire } from 'node:module'
@@ -36,7 +41,10 @@ import { fileURLToPath } from 'node:url'
36
41
 
37
42
  export const name = 'vibe-math-preset-installer'
38
43
 
39
- const PRESETS = [
44
+ // Exported so the shipped policy suite can prove the managed list still covers what each preset
45
+ // needs at runtime (a file present in the preset directory but missing here is copied by nobody,
46
+ // and the installed preset then cannot mount).
47
+ export const PRESETS = [
40
48
  {
41
49
  src: 'vibe-math-v2',
42
50
  dst: 'vibe-math-v2',
@@ -62,9 +70,34 @@ const PRESETS = [
62
70
  ]
63
71
 
64
72
  const STATE_FILE = '.vibe-math-installed.json'
73
+ // where a replaced user edit is preserved; a leading dot keeps DSH's preset discovery from ever
74
+ // treating it as a preset directory (ids must match [a-z0-9][a-z0-9-]*)
75
+ const BACKUP_DIR = '.vibe-math-backup'
65
76
 
66
77
  function sha256(buf) { return createHash('sha256').update(buf).digest('hex') }
67
78
 
79
+ /**
80
+ * Preserve one file that is about to be replaced by the shipped version, under
81
+ * `<presetRoot>/.vibe-math-backup/<fromVersion>/<preset>/<file>`.
82
+ *
83
+ * Returns `'written'` (a copy was made now), `'kept'` (a copy for this version was already there —
84
+ * the earliest edit is the one worth keeping, and a re-run must not overwrite it with an
85
+ * already-replaced file) or `'failed'`. A caller must not report `'kept'` as a failure: the user's
86
+ * bytes are preserved, just from an earlier run.
87
+ */
88
+ function backupReplacedFile(presetRoot, fromVersion, presetDir, fileName, buf) {
89
+ try {
90
+ const dir = join(presetRoot, BACKUP_DIR, String(fromVersion || 'unversioned'), presetDir)
91
+ const dst = join(dir, fileName)
92
+ if (existsSync(dst)) return 'kept' // the earliest copy for this version is the one worth keeping
93
+ mkdirSync(dir, { recursive: true })
94
+ writeFileSync(dst, buf)
95
+ return 'written'
96
+ } catch (e) {
97
+ return 'failed' // a backup failure must never stop the update; it is reported by the caller
98
+ }
99
+ }
100
+
68
101
  function readState(path) {
69
102
  try {
70
103
  const raw = readFileSync(path, 'utf8')
@@ -350,11 +383,35 @@ export async function apply(ctx) {
350
383
  const state = readState(stateFile)
351
384
  const prevFiles = (state && state.files) || {}
352
385
  const isUpgrade = state !== null && pkgVersion !== '' && state.version !== pkgVersion
353
- const isBaseline = state === null // no recorded history → refresh everything (user policy: auto-update old installs)
386
+ const isBaseline = state === null // no recorded history → refresh everything
387
+ // A version change (or a first sighting) REPLACES the managed files; only a same-version boot
388
+ // leaves the working tree alone. See UPDATE POLICY at the top of this file.
389
+ const refresh = isBaseline || isUpgrade
390
+ const fromVersion = (state && state.version) || '(unversioned)'
391
+ if (pkgVersion === '') {
392
+ // Without a version there is nothing to compare against, so this run must NOT replace
393
+ // anything (a wrong guess would overwrite files for no reason); it still restores missing
394
+ // ones and keeps the previously recorded version, so the next readable run reports the
395
+ // right "from" version.
396
+ logger?.warn?.('[dsh-vibe-math] 读不到本包版本(package.json 缺失或损坏):本次不做版本比对,只补回缺失的 preset 文件。')
397
+ }
398
+ // A recorded version NEWER than this package means this run replaces preset bytes BACKWARDS.
399
+ // The policy still does it — "the preset directory equals the installed package" is the whole
400
+ // point — but a silent downgrade (an old bundle still installed in the profile while the presets
401
+ // were synced from a newer one) is exactly the surprise worth naming.
402
+ if (isUpgrade) {
403
+ const recorded = parseSemver(fromVersion)
404
+ const installed = parseSemver(pkgVersion)
405
+ if (recorded !== null && installed !== null && compareSemver(recorded, installed) > 0) {
406
+ logger?.warn?.('[dsh-vibe-math] 记录里的版本(' + fromVersion + ')比本包版本(' + pkgVersion + ')新,本次会把 preset 换回旧字节。' +
407
+ '若这不是你想要的,请先升级 profile 里的依赖:dsh plugin --profile <name> add dsh-vibe-math@latest,再重启 DSH。')
408
+ }
409
+ }
354
410
 
355
411
  const nextFiles = {}
356
412
  let installed = 0, updated = 0, kept = 0
357
- const keptList = []
413
+ const replacedEdits = []
414
+ const backupFailures = []
358
415
 
359
416
  for (const p of PRESETS) {
360
417
  const srcDir = join(here, p.src)
@@ -375,32 +432,33 @@ export async function apply(ctx) {
375
432
  nextFiles[key] = { hash: curHash, provenance: 'package' }
376
433
  continue
377
434
  }
378
- const destHash = sha256(readFileSync(d))
379
- if (isBaseline) {
380
- // no recorded history: refresh to the current package (one-time; edits
381
- // made before this mechanism are overwritten, later edits are protected)
382
- if (destHash === curHash) { nextFiles[key] = { hash: curHash, provenance: 'package' } }
383
- else { writeFileSync(d, cur); updated += 1; nextFiles[key] = { hash: curHash, provenance: 'package' } }
384
- continue
385
- }
435
+ const destBuf = readFileSync(d)
436
+ const destHash = sha256(destBuf)
386
437
  const prev = prevFiles[key]
387
438
  const prevRec = (prev && typeof prev === 'object') ? prev : { hash: prev, provenance: 'package' }
388
- const prevProv = (prevRec.provenance === 'user') ? 'user' : 'package' // 未知来源按包文件处理
389
- if (prevProv === 'package' && destHash === prevRec.hash) {
390
- // 包文件且未被改动 → 可安全升级(内容相同则跳过写入)
391
- if (destHash !== curHash) { writeFileSync(d, cur); updated += 1 }
439
+ if (destHash === curHash) {
440
+ // already the shipped bytes: never rewrite, so the file's mtime (which keys the preset's
441
+ // DSH generation) stays put
392
442
  nextFiles[key] = { hash: curHash, provenance: 'package' }
393
- } else if (prevProv === 'user') {
394
- // 用户持有 → 永不覆盖
395
- kept += 1
396
- if (isUpgrade) keptList.push(key + ' (用户持有)')
397
- nextFiles[key] = { hash: destHash, provenance: 'user' }
398
- } else {
399
- // 包文件但自上次安装后已被用户改动
443
+ continue
444
+ }
445
+ if (!refresh) {
446
+ // same version: nothing is being updated, so a file that differs from the package is left
447
+ // exactly as it is. The recorded hash stays "what this installer last wrote" (or unknown),
448
+ // so the drift is still recognised — and backed up — at the next version change.
400
449
  kept += 1
401
- if (isUpgrade) keptList.push(key + ' (已修改)')
402
- nextFiles[key] = { hash: destHash, provenance: 'user' }
450
+ nextFiles[key] = typeof prevRec.hash === 'string' ? { hash: prevRec.hash, provenance: 'package' } : { provenance: 'package' }
451
+ continue
452
+ }
453
+ // replacing: preserve the user's bytes when they are not what this installer last wrote
454
+ // (a legacy state without a hash cannot tell, so it backs the file up rather than risk it)
455
+ if (typeof prevRec.hash !== 'string' || destHash !== prevRec.hash) {
456
+ if (backupReplacedFile(presetRoot, fromVersion, p.dst, f, destBuf) === 'failed') backupFailures.push(key)
457
+ replacedEdits.push(key)
403
458
  }
459
+ writeFileSync(d, cur)
460
+ updated += 1
461
+ nextFiles[key] = { hash: curHash, provenance: 'package' }
404
462
  }
405
463
  }
406
464
 
@@ -434,22 +492,38 @@ export async function apply(ctx) {
434
492
  if (dirEmpty && existsSync(dir)) { try { rmdirSync(dir); removedDirs.push(dir) } catch (e) {} }
435
493
  }
436
494
 
437
- writeState(stateFile, { version: pkgVersion, files: nextFiles, updatedAt: Date.now() })
495
+ writeState(stateFile, { version: pkgVersion || (state && state.version) || '', files: nextFiles, updatedAt: Date.now() })
438
496
 
439
497
  if (removedFiles > 0 || removedDirs.length > 0) {
440
498
  logger?.info?.('[dsh-vibe-math] preset cleanup: removed ' + removedFiles + ' file(s) from ' + removedDirs.length + ' stale preset dir(s) (' + removedDirs.map(d => d.split(/[\\/]/).pop()).join(', ') + ') that are no longer shipped.')
441
499
  }
442
500
 
443
501
  if (isUpgrade) {
444
- logger?.info?.('[dsh-vibe-math] preset auto-update: version ' + (state.version || '(none)') + ' → ' + pkgVersion +
445
- ' — 新增 ' + installed + ' 个文件,更新 ' + updated + ' 个文件' +
446
- (kept > 0 ? ',保留 ' + kept + ' 个未覆盖文件(' + keptList.join('; ') + ')' : '') +
447
- '。新版本 preset 将在新会话生效。')
502
+ logger?.info?.('[dsh-vibe-math] preset auto-update: version ' + fromVersion + ' → ' + pkgVersion +
503
+ ' — 新增 ' + installed + ' 个文件,更新 ' + updated + ' 个文件。' +
504
+ (replacedEdits.length > 0
505
+ ? '其中 ' + replacedEdits.length + ' 个文件与上一次安装的字节不同(被改过),已按版本一致化覆盖' +
506
+ (backupFailures.length === 0
507
+ ? ',原文备份在 ' + join(presetRoot, BACKUP_DIR, String(fromVersion)) + ':' + replacedEdits.join(', ')
508
+ : ';这 ' + backupFailures.length + ' 个文件**备份失败**(原文未保留):' + backupFailures.join(', ')) +
509
+ '。要自定义 preset,请复制一份而不是改这几个文件——被管理的文件在下一次版本变更时一定会被替换。'
510
+ : '') +
511
+ '新版本 preset 将在新会话生效。')
448
512
  } else if (isBaseline) {
449
513
  logger?.info?.('[dsh-vibe-math] preset baseline: refreshed ' + (installed + updated) + ' file(s) to v' + pkgVersion +
450
- ' — 已启用自动更新(后续版本升级将自动替换未被手动修改的 preset 文件)。')
514
+ (replacedEdits.length > 0
515
+ ? ',其中 ' + replacedEdits.length + ' 个原有文件与随包版本不同' +
516
+ (backupFailures.length === 0 ? ',原文已备份在 ' + join(presetRoot, BACKUP_DIR, String(fromVersion))
517
+ : ',但有 ' + backupFailures.length + ' 个备份失败(原文未保留):' + backupFailures.join(', '))
518
+ : '') +
519
+ ' — 已启用版本化自动更新(后续版本变更会直接替换被管理的 preset 文件)。')
451
520
  } else if (installed > 0) {
452
521
  logger?.info?.('[dsh-vibe-math] restored ' + installed + ' missing preset file(s)')
522
+ } else if (kept > 0) {
523
+ // same version, and some managed file on disk differs from the package: reported, never
524
+ // rewritten mid-version (it is replaced, with a backup, at the next version change)
525
+ logger?.info?.('[dsh-vibe-math] preset files untouched (v' + pkgVersion + ' unchanged): ' + kept +
526
+ ' file(s) differ from the shipped copy; they will be replaced on the next version change (原件会先备份)')
453
527
  }
454
528
  await checkHostCapabilities(ctx, logger)
455
529
  } catch (err) {