dsh-vibe-math 2.7.3 → 2.8.0
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.
- package/LICENSE +21 -21
- package/README.en.md +682 -682
- package/README.md +683 -683
- package/docs/AUDIT-CHECKLIST.md +755 -744
- package/docs/COMPAT-AUDIT-ROUND2.md +351 -351
- package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
- package/docs/audit-methodology.md +93 -0
- package/docs/final-paper.md +172 -172
- package/docs/formal-verification.md +613 -588
- package/docs/generate_framework_diagram_v2.py +114 -114
- package/docs/generate_framework_diagram_v3.py +127 -127
- package/docs/math-computation.md +195 -194
- package/docs/release-notes/README.en.md +1 -0
- package/docs/release-notes/README.md +1 -0
- package/docs/release-notes/RELEASE-NOTES-2.0.22.md +112 -112
- package/docs/release-notes/RELEASE-NOTES-2.1.0.md +143 -143
- package/docs/release-notes/RELEASE-NOTES-2.2.0.md +266 -266
- package/docs/release-notes/RELEASE-NOTES-2.2.1.md +43 -43
- package/docs/release-notes/RELEASE-NOTES-2.2.2.md +88 -88
- package/docs/release-notes/RELEASE-NOTES-2.3.0.md +207 -207
- package/docs/release-notes/RELEASE-NOTES-2.3.1.md +134 -134
- package/docs/release-notes/RELEASE-NOTES-2.3.10.md +105 -105
- package/docs/release-notes/RELEASE-NOTES-2.3.11.md +57 -57
- package/docs/release-notes/RELEASE-NOTES-2.3.12.md +80 -80
- package/docs/release-notes/RELEASE-NOTES-2.3.2.md +145 -145
- package/docs/release-notes/RELEASE-NOTES-2.3.3.md +115 -115
- package/docs/release-notes/RELEASE-NOTES-2.3.4.md +69 -69
- package/docs/release-notes/RELEASE-NOTES-2.3.5.md +63 -63
- package/docs/release-notes/RELEASE-NOTES-2.3.6.md +66 -66
- package/docs/release-notes/RELEASE-NOTES-2.3.7.md +59 -59
- package/docs/release-notes/RELEASE-NOTES-2.3.8.md +45 -45
- package/docs/release-notes/RELEASE-NOTES-2.3.9.md +70 -70
- package/docs/release-notes/RELEASE-NOTES-2.8.0.en.md +65 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.0.md +65 -0
- package/docs/release-playbook.md +103 -0
- package/docs/slv-playbook.md +117 -0
- package/docs/test-timing.md +158 -157
- package/docs//346/236/266/346/236/204/345/233/276.md +77 -77
- package/installer.js +792 -792
- package/package.json +13 -3
- package/prompt-corpus-v2/formal-verify-v2.json +2 -2
- package/prompt-corpus-v2/formal-verify-v2.md +4 -0
- package/prompt-corpus-v3/formal-verify-v3.json +7 -7
- package/prompt-corpus-v3/formal-verify-v3.md +14 -0
- package/prompt-corpus-v5/prompt-corpus-v5.json +61 -61
- package/prompt-corpus-v5/prompt-corpus-v5.md +122 -0
- package/scripts/clean-temp.mjs +234 -0
- package/tests/audit-engine-faces.mjs +298 -0
- package/tests/audit-formal-sensitivity.mjs +345 -342
- package/tests/audit-installer-compat.test.mjs +256 -256
- package/tests/audit-math-computation-sensitivity.mjs +1 -1
- package/tests/audit-persona-sensitivity.mjs +323 -323
- package/tests/audit-persona-surface.test.mjs +431 -431
- package/tests/audit-prompt-duplication.mutants.mjs +3 -2
- package/tests/audit-prompt-invariants.mjs +589 -546
- package/tests/audit-registration.mjs +78 -78
- package/tests/audit-spec-traceability.mjs +193 -193
- package/tests/audit-status-report-fields.mjs +172 -172
- package/tests/audit-v5-integrity.mjs +795 -795
- package/tests/audit-v5-sensitivity.mjs +433 -432
- package/tests/e2e-v5-round2.test.mjs +2453 -2426
- package/tests/formal-verify-v2.test.mjs +1806 -1800
- package/tests/formal-verify-v3.test.mjs +1757 -1750
- package/tests/formal-verify-v4.test.mjs +2114 -2106
- package/tests/formal-verify-v5.test.mjs +774 -767
- package/tests/math-computation-discovery.mutants.mjs +3 -3
- package/tests/math-computation-shared.test.mjs +122 -5
- package/tests/math-computation-v2.test.mjs +14 -2
- package/tests/math-computation-v3.test.mjs +14 -2
- package/tests/math-computation-v4.test.mjs +739 -739
- package/tests/prompt-v5-integrity.test.mjs +1291 -1291
- package/tests/run-tests.mjs +393 -332
- package/tests/selfdrive-v5.mjs +560 -560
- package/tests/temp-hygiene.mutants.mjs +80 -0
- package/tests/v2-fix-probes.mutants.mjs +4 -0
- package/tests/v2-fix-probes.test.mjs +1034 -1033
- package/tests/v2v3-interrupt.mutants.mjs +111 -0
- package/tests/v3-fix-probes.mutants.mjs +99 -95
- package/tests/v3-fix-probes.test.mjs +891 -890
- package/tests/v4-final-paper.mutants.mjs +108 -0
- package/tests/v5-institute-fixes.mutants.mjs +218 -190
- package/vibe-math-v2/agent.cordis.yml +376 -376
- package/vibe-math-v2/math-computation.js +17 -15
- package/vibe-math-v2/preset.yml +2 -2
- package/vibe-math-v2/v2/347/232/204/351/242/235/345/244/226/350/246/201/346/261/202.txt +21 -21
- package/vibe-math-v2/vibe-math-v2.js +5078 -5011
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +486 -486
- package/vibe-math-v3/agent.cordis.yml +420 -420
- package/vibe-math-v3/math-computation.js +17 -15
- package/vibe-math-v3/preset.yml +2 -2
- package/vibe-math-v3/vibe-math-v3.js +6215 -6149
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +649 -649
- package/vibe-math-v4/agent.cordis.yml +438 -438
- package/vibe-math-v4/math-computation.js +17 -15
- package/vibe-math-v4/preset.yml +2 -2
- package/vibe-math-v4/vibe-math-v4.js +4490 -4422
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1015 -1015
- package/vibe-math-v5/math-computation.js +17 -15
- package/vibe-math-v5/vibe-math-v5.js +93 -11
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1728
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
# Vibe-Mathematics × DSH 0.2.0-rc.2 —— 第三轮兼容性审计(适配记录)
|
|
2
|
-
|
|
3
|
-
> 本轮目标:把四套预设适配 **DSH 0.2.0**(当前版本),同时保住旧版本线;并在过程中把"版本线切换"这件事
|
|
4
|
-
> 从"猜"变成"能力探测 + 四宿主实测"。结论先行:**0.2.0 的破坏性变更不是 API 签名**,而是三类:
|
|
5
|
-
> ① 预设交付**形态换代**(旧的目录发现被彻底删除);② 新增**硬性行为**(会话事件白名单、子代理数量上限、
|
|
6
|
-
> 工具名抛错、压缩的"策略 vs 执行"语义);③ 兼容性判定从 `engines` 扩展为 `peerDependencies`。
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 0. 摘要
|
|
11
|
-
|
|
12
|
-
- 并行做了 4 份契约审计(组合行与声明 / `subagents`+`agents`+事件 / `tools`+`commands`+`timer` / `compaction`+`sessionProjections`+`sessions`+`subprocess`+`sandboxPolicy`),
|
|
13
|
-
每份都以**已安装的 0.2.0-rc.2 源码**(`lib/**/*.d.ts` 与实现)为证据。
|
|
14
|
-
- 另外做了**四宿主实测**:把本包分别装进 `0.1.5-rc.2`、`0.1.6-alpha.2`、`0.1.7-rc.2`、`0.2.0-rc.2` 的独立 `DSH_HOME`,
|
|
15
|
-
启动后用宿主自己的 `agentPresets.list()` 读回预设清单。这是"预设到底有没有出现在选择器里"的唯一可信判据。
|
|
16
|
-
- 结果:修复后四条版本线上预设**都**会出现(旧线走目录、新线走声明行),且新线上**零激活警告**。
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 1. 确证的缺陷
|
|
21
|
-
|
|
22
|
-
### G-1 preset 在新宿主上完全不可见(致命,2.3.x 全线命中)
|
|
23
|
-
|
|
24
|
-
DSH 0.1.7 起 agent preset **只**由组合行声明;`$DSH_HOME/.agent-presets/<id>/agent.cordis.yml` 已无人读取
|
|
25
|
-
(宿主自带技能 `editing-cordis-compositions` 明写 "Nothing reads that directory any more")。2.3.x 的安装器
|
|
26
|
-
在新宿主上仍会写那个目录:**日志显示安装成功、`~/.dsh/.agent-presets/` 里文件齐全,选择器里却一个都没有。**
|
|
27
|
-
修复:`cordis.patch.yml` 声明四个组合行;安装器的形态判定改为读 **loader 入口树**(见 G-10)。
|
|
28
|
-
|
|
29
|
-
### G-2 组合行声明有三条硬约束(每条都实测过)
|
|
30
|
-
|
|
31
|
-
| 约束 | 违反后的现象 | 证据 |
|
|
32
|
-
|---|---|---|
|
|
33
|
-
| `dsh.bundle.patch` 必须是**字符串** | 0.1.5/0.1.6 把该值直接送进 `path.join` → `ERR_INVALID_ARG_TYPE`,**整个 profile 起不来** | 实测:数组形态在 0.1.5-rc.2 上必然崩 |
|
|
34
|
-
| 声明行不能命名**宿主包**(如 `@deepseek-ai/dsh-agent-preset`) | 旧线无此包 → 每次启动 `failed to import` 激活失败 | 实测:0.1.5/0.1.6 上该包不存在 |
|
|
35
|
-
| 声明行不能用 **`!!js` 门**(表达式里调 `ctx.get`) | 入口**永不初始化**、预设静默不注册(`N entries did not activate`) | 实测 0.2.0:`!!js "false"` / `typeof ctx` 正常;`ctx.get('fs'\|'agents'\|'pluginManager'\|'agentPresets')` 全部让入口不加载 |
|
|
36
|
-
|
|
37
|
-
因此本包改为:声明行的模块是**自己**的 `preset-declaration.js`(两条线上都可解析),它在运行时读 `agentPresets`
|
|
38
|
-
服务——有就注册,没有就**什么都不做且不报警**。
|
|
39
|
-
|
|
40
|
-
### G-3 声明行里的相对 `name` 解析不到(B3)
|
|
41
|
-
|
|
42
|
-
`app-boot` 只会把 `group && Array.isArray(config)` 的条目名改写成 `file://` URL;声明行的 `config.plugins`
|
|
43
|
-
不在其中,loader 便拿**profile 目录**解析 `./vibe-math-vN.js` → ENOENT → **整个 preset 拒绝挂载**。
|
|
44
|
-
修复:改用包内子路径 `dsh-vibe-math/vibe-math-vN/vibe-math-vN.js` + 在 `exports` 放行。
|
|
45
|
-
|
|
46
|
-
### G-4 v5 把状态写进宿主会话日志 → 用户会话不可恢复(致命,数据安全)
|
|
47
|
-
|
|
48
|
-
`Session.append(type,data,…)` 构造信封时**无法**设置 `ignorable`(只有构造种子接受该字段);而会话持久化在加载时
|
|
49
|
-
要求"未知事件类型必须 `ignorable === true`",否则抛 `SessionFormatUnsupportedError` **拒绝整个会话**。该规则在
|
|
50
|
-
0.1.5-rc.2 / 0.1.7-rc.2 / 0.2.0-rc.2 上一致存在 → v5 每追加一条 `vibe5/*`,就把**用户自己的会话**变成"写入时无声、
|
|
51
|
-
下次恢复时打不开"。修复:v5 只用加固 JSON(`State/<研究所>.v5state.json`),彻底不碰会话日志,并去掉
|
|
52
|
-
`sessions` / `sessionProjections` 依赖;连它写进项目的镜像 Markdown 与**给模型看的工具描述**都一并改正。
|
|
53
|
-
|
|
54
|
-
### G-5 工具过滤里的未注册名让整次派生失败(v4/v5)
|
|
55
|
-
|
|
56
|
-
`tools.restrict()` 对不在宿主可限制集合里的名字**直接抛错**,而该调用发生在建立子代理时 → v4/v5 一次配置错误
|
|
57
|
-
就让每次派生失败(v2/v3 早就有"按宿主提示剔除名字并重试"的守卫)。修复:v4/v5 采用同样的守卫,
|
|
58
|
-
并把返回值改成"按宿主提示剔除后重试一次",且**fail-closed**(绝不在过滤全被剔除时"不过滤"启动)。
|
|
59
|
-
|
|
60
|
-
### G-6 宿主新增"每根代理 8 个存活 continuable 子代理"上限
|
|
61
|
-
|
|
62
|
-
0.2.0 新增 `ActivationPool`(`maxActiveSubagents` 默认 8,来自 `subagent` 行的 config,未写入 `.d.ts`),
|
|
63
|
-
超出时抛 `SubagentError{code:'ACTIVATION_LIMIT_REACHED'}`;v4 的 `residentCount` 无上限、v5 的临时工上限之和可达 12
|
|
64
|
-
→ 撞限。修复:识别该错误码、从宿主消息里记下上限值、明确提示并把该次派生推迟到下一轮(不再让启动循环崩在半途)。
|
|
65
|
-
|
|
66
|
-
### G-7 压缩:策略调用 vs 执行调用(v4/v5)
|
|
67
|
-
|
|
68
|
-
`compactIfNeeded(agent,'pressure',signal)` 是**策略**调用,可能什么都不做且返回 `null`(无日志);真正的执行调用是
|
|
69
|
-
`compactNow(agent, signal)`。另外 v5 过去从**插件平面**取 compaction,而 preset 自己 `isolate` 了 compaction realm,
|
|
70
|
-
取到的是宿主根实例(配置可能不同)。修复:优先 `compactNow`(特性探测,旧宿主回退到策略调用),v5 改用 agent 自身 ctx 的实例。
|
|
71
|
-
|
|
72
|
-
### G-8 命令"业务失败"被当成成功
|
|
73
|
-
|
|
74
|
-
宿主的 `CommandResult` 联合是 `{kind:'success',text?} | {kind:'error',text}`;四套预设的 `/vibe`、`/v4`、`/v5`
|
|
75
|
-
在**所有失败路径**上都返回 `{kind:'success', text:'{"ok":false,…}'}` → UI 永远显示不出"这条命令被拒绝了"。
|
|
76
|
-
|
|
77
|
-
**更正(据实收窄):这条修复不是一次"全仓"修复,而是分两批落地的,本节初稿把"打算做的"写成了"已经做完的"。**
|
|
78
|
-
|
|
79
|
-
- **2.4.0(`cc0c220`)只改了 v2 与 v3**(`vibe-math-v2.js` / `vibe-math-v3.js` 的命令 handler)。当时 v4 与 v5 的
|
|
80
|
-
handler 仍然无条件 `return {kind:'success', text:JSON.stringify(r,null,2)}`(各自的"无 session"分支也返回
|
|
81
|
-
`kind:'success'`)——所以"四套都修了"在 2.4.0 这个提交上是不成立的。
|
|
82
|
-
- **v4 与 v5 在 round-A(`8ec5a9c`)才各自补上同一形状**:v4 的命令 handler 改成
|
|
83
|
-
`const failed = !r || r.ok === false || (r.ok === undefined && !!r.error)` → `{kind: failed?'error':'success'}`;
|
|
84
|
-
v5 改成 `const failed = r !== null && typeof r === 'object' && r.ok === false` → 同样三元返回,并把"无 session"
|
|
85
|
-
分支一并改成 `kind:'error'`。判据:`git log -G "kind:\s*'error'" -- vibe-math-v4/vibe-math-v4.js`(及 v5 同)
|
|
86
|
-
**只命中 `8ec5a9c`**;round-C(`4eb3175`)与 round-D(`0dc6ee3`)都没有再碰这两个 handler(前者的 v4 hunk 在
|
|
87
|
-
1354/1854 行,后者的 v5 hunk 止于 4564 行,均在命令 handler 之外)。
|
|
88
|
-
- **仓库内的回归证据是 round-C(`4eb3175`)补进来的**:`tests/host-failure-paths.test.mjs` 在该提交首次入库,
|
|
89
|
-
它断言 `/v4 set` 未知键、`/v4` 未知子命令与 `/v5` 未知子命令都返回 `kind:'error'`。round-D(`0dc6ee3`)只是
|
|
90
|
-
把"本节仍把 `/v4` 的修复说成全仓,需据实收窄"记进了待办,没有改代码。
|
|
91
|
-
- 该联合在 0.1.5 上同样存在,故旧线安全。也就是说:**"四套最终一致"成立,"四套一次修完"不成立**。
|
|
92
|
-
|
|
93
|
-
### G-9 文档、镜像与工具描述与实现不符
|
|
94
|
-
|
|
95
|
-
v5 的 README、`架构图.md`、`实现方案.md`、它写进用户项目的三个镜像文件头(`Institutes.md`、`Shared/TaskBoard.md`、
|
|
96
|
-
`State/README.md`)以及 `vibe_v5_set` 的**工具描述**都写着"权威状态在会话日志投影里"。修复:全部改为真实位置
|
|
97
|
-
(`State/<研究所>.v5state.json`),并重生成 v5 中英架构图。**这一条是提示词/交互面的事实性修复**,不是文风问题。
|
|
98
|
-
|
|
99
|
-
### G-10 "旧线不读目录"必须用**能力**判定,不能用版本号
|
|
100
|
-
|
|
101
|
-
`agentPresets` 服务在两条线上**都存在**但语义不同(旧线是目录扫描器、**没有** `register`)——用"服务存在"判形态
|
|
102
|
-
会在 0.1.5/0.1.6 上误判为"组合行",从而**跳过**唯一有效的目录安装。修复:判定读 **loader 入口树**里是否挂了
|
|
103
|
-
`@deepseek-ai/dsh-agent-preset`(或其 registry);这在任何插件激活之前就可见,而"读服务"在 bundle 被单独激活
|
|
104
|
-
(`dsh plugin add`)时可能尚未就绪。
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## 2. 四宿主实测矩阵
|
|
109
|
-
|
|
110
|
-
| 宿主 | preset 形态 | 实测结果 |
|
|
111
|
-
|---|---|---|
|
|
112
|
-
| 0.1.5-rc.2 | 目录 | 安装器写入四个目录;声明行静默无副作用;`agentPresets.list()` 列出四个预设 ✅ |
|
|
113
|
-
| 0.1.6-alpha.2 | 目录 | 同上 ✅ |
|
|
114
|
-
| 0.1.7-rc.2 | 组合行 | 声明行注册四个预设(order 20–23);安装器跳过目录(`state` 文件不再被写)✅ |
|
|
115
|
-
| 0.2.0-rc.2 | 组合行 | 同上,且**零激活警告**(此前数组 patch / 宿主包名 / `!!js` 门各会产生一批失败行)✅ |
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## 3. 已确证**正确**的部分(避免下次重复劳动)
|
|
120
|
-
|
|
121
|
-
- `tools.register` 的形状(`name`/`description`/`parameters` + **必需的** `output:{schema,render}` + `execute`)与
|
|
122
|
-
`parameters` 用原生 JSON Schema 的写法(宿主自己的工具也这么写);handler 返回符合 `output.schema` 的 JSON 字符串 ✓。
|
|
123
|
-
- `commands.register`(`name`/`description`/`input:{hint}`/`handler`)与命令名正则 ✓。
|
|
124
|
-
- `tools.restrict` 的 `{allow,deny}` 形状,以及"未注册名"错误消息仍以 `known global tools: …` 结尾 —— v2/v3 的重试正则
|
|
125
|
-
逐字符匹配,行为保持 fail-closed ✓。
|
|
126
|
-
- `fs`(resolve/stat/readText/writeText/listDir)、`getPolicy()` 作为 `writeText` 第 5 参数的位置 ✓;`FsDirEntry` 字段无改名 ✓。
|
|
127
|
-
- `subagents.startContinuable` / `sendMessage`(4 参数 + `{signal}`)/ `interrupt`(第二参数为 authority,自 0.1.1 起)/
|
|
128
|
-
`drainContinuableChildren` 的调用形状自 0.1.2-rc.1 起未变 ✓;`agents.get` 在 `subagent/start` 里可解析、在 `subagent/end`
|
|
129
|
-
里**必然不可解析**——v4/v5 "在 start 时捕获 Agent 引用"的写法继续正确 ✓。
|
|
130
|
-
- `subprocess`(spawn/done/terminate/collected)与 `sandboxPolicy.resolve` ✓;`timer` 服务签名与 disposer 语义 ✓。
|
|
131
|
-
- `sessionProjections.register/stateOf` 的形状本身 ✓(本包不再使用,原因见 G-4)。
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## 4. 修复实施记录(2.4.0)
|
|
136
|
-
|
|
137
|
-
见 `docs/release-notes/RELEASE-NOTES-2.4.0.md`「变更」。工程证据(逐条审计报告与探针输出)保存在仓库之外的
|
|
138
|
-
工作区台账中;本文件只保留结论与判据。
|
|
139
|
-
|
|
140
|
-
新增守卫:`tests/audit-preset-rows.test.mjs`(生成物与生成器逐字节一致、`dsh.bundle.patch` 必须是字符串、
|
|
141
|
-
声明行不得命名宿主包、不得使用会调用服务的 `!!js` 门、每条组合必须含 `present`/`command-goal`/`isolate`/`workflow-ptc`、
|
|
142
|
-
冻结的 0.1.x 组合保持原样);`docs/AUDIT-CHECKLIST.md` 新增 §8 把上述四条硬约束写成流程。
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## 5. 刻意不改(附理由)
|
|
147
|
-
|
|
148
|
-
| 项 | 决定 | 理由 |
|
|
149
|
-
|---|---|---|
|
|
150
|
-
| v2/v3 使用全局 `setInterval`/`setTimeout` | 保留 | 预设是**文件行**,不经动态包 vm 沙箱(沙箱只包动态包宿主半);实测可用,且 `ctx.effect` 内已正确清理。仅修掉"插件运行时没有全局定时器"这句**错误注释**。 |
|
|
151
|
-
| 工具卡片呈现(`presentCall`/`presentResult`) | 不加 | 纯外观增强,与本次适配无关;未来要加需同步四套。 |
|
|
152
|
-
| `subagents.listChildren` 元素类型变化(`SubagentCatalogEntry`) | 不处理 | 四套预设均未调用。 |
|
|
153
|
-
| `additionalProperties:false` 的 `parameters` | 保留 | 宿主 `tools.register` 不校验 `parameters`;仅当预设改造成**动态包**时 `defineTool` 才会拒绝(届时再改)。 |
|
|
154
|
-
| `followup` 回退分支 | 保留 | 该分支只在 0.1.1 及更早存在;本包声明的下限是 0.1.2-alpha.4,其中 `0.1.2-alpha.*` 是否仍带 `followup` 未实测,保留分支零成本且更稳。 |
|
|
1
|
+
# Vibe-Mathematics × DSH 0.2.0-rc.2 —— 第三轮兼容性审计(适配记录)
|
|
2
|
+
|
|
3
|
+
> 本轮目标:把四套预设适配 **DSH 0.2.0**(当前版本),同时保住旧版本线;并在过程中把"版本线切换"这件事
|
|
4
|
+
> 从"猜"变成"能力探测 + 四宿主实测"。结论先行:**0.2.0 的破坏性变更不是 API 签名**,而是三类:
|
|
5
|
+
> ① 预设交付**形态换代**(旧的目录发现被彻底删除);② 新增**硬性行为**(会话事件白名单、子代理数量上限、
|
|
6
|
+
> 工具名抛错、压缩的"策略 vs 执行"语义);③ 兼容性判定从 `engines` 扩展为 `peerDependencies`。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 0. 摘要
|
|
11
|
+
|
|
12
|
+
- 并行做了 4 份契约审计(组合行与声明 / `subagents`+`agents`+事件 / `tools`+`commands`+`timer` / `compaction`+`sessionProjections`+`sessions`+`subprocess`+`sandboxPolicy`),
|
|
13
|
+
每份都以**已安装的 0.2.0-rc.2 源码**(`lib/**/*.d.ts` 与实现)为证据。
|
|
14
|
+
- 另外做了**四宿主实测**:把本包分别装进 `0.1.5-rc.2`、`0.1.6-alpha.2`、`0.1.7-rc.2`、`0.2.0-rc.2` 的独立 `DSH_HOME`,
|
|
15
|
+
启动后用宿主自己的 `agentPresets.list()` 读回预设清单。这是"预设到底有没有出现在选择器里"的唯一可信判据。
|
|
16
|
+
- 结果:修复后四条版本线上预设**都**会出现(旧线走目录、新线走声明行),且新线上**零激活警告**。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. 确证的缺陷
|
|
21
|
+
|
|
22
|
+
### G-1 preset 在新宿主上完全不可见(致命,2.3.x 全线命中)
|
|
23
|
+
|
|
24
|
+
DSH 0.1.7 起 agent preset **只**由组合行声明;`$DSH_HOME/.agent-presets/<id>/agent.cordis.yml` 已无人读取
|
|
25
|
+
(宿主自带技能 `editing-cordis-compositions` 明写 "Nothing reads that directory any more")。2.3.x 的安装器
|
|
26
|
+
在新宿主上仍会写那个目录:**日志显示安装成功、`~/.dsh/.agent-presets/` 里文件齐全,选择器里却一个都没有。**
|
|
27
|
+
修复:`cordis.patch.yml` 声明四个组合行;安装器的形态判定改为读 **loader 入口树**(见 G-10)。
|
|
28
|
+
|
|
29
|
+
### G-2 组合行声明有三条硬约束(每条都实测过)
|
|
30
|
+
|
|
31
|
+
| 约束 | 违反后的现象 | 证据 |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `dsh.bundle.patch` 必须是**字符串** | 0.1.5/0.1.6 把该值直接送进 `path.join` → `ERR_INVALID_ARG_TYPE`,**整个 profile 起不来** | 实测:数组形态在 0.1.5-rc.2 上必然崩 |
|
|
34
|
+
| 声明行不能命名**宿主包**(如 `@deepseek-ai/dsh-agent-preset`) | 旧线无此包 → 每次启动 `failed to import` 激活失败 | 实测:0.1.5/0.1.6 上该包不存在 |
|
|
35
|
+
| 声明行不能用 **`!!js` 门**(表达式里调 `ctx.get`) | 入口**永不初始化**、预设静默不注册(`N entries did not activate`) | 实测 0.2.0:`!!js "false"` / `typeof ctx` 正常;`ctx.get('fs'\|'agents'\|'pluginManager'\|'agentPresets')` 全部让入口不加载 |
|
|
36
|
+
|
|
37
|
+
因此本包改为:声明行的模块是**自己**的 `preset-declaration.js`(两条线上都可解析),它在运行时读 `agentPresets`
|
|
38
|
+
服务——有就注册,没有就**什么都不做且不报警**。
|
|
39
|
+
|
|
40
|
+
### G-3 声明行里的相对 `name` 解析不到(B3)
|
|
41
|
+
|
|
42
|
+
`app-boot` 只会把 `group && Array.isArray(config)` 的条目名改写成 `file://` URL;声明行的 `config.plugins`
|
|
43
|
+
不在其中,loader 便拿**profile 目录**解析 `./vibe-math-vN.js` → ENOENT → **整个 preset 拒绝挂载**。
|
|
44
|
+
修复:改用包内子路径 `dsh-vibe-math/vibe-math-vN/vibe-math-vN.js` + 在 `exports` 放行。
|
|
45
|
+
|
|
46
|
+
### G-4 v5 把状态写进宿主会话日志 → 用户会话不可恢复(致命,数据安全)
|
|
47
|
+
|
|
48
|
+
`Session.append(type,data,…)` 构造信封时**无法**设置 `ignorable`(只有构造种子接受该字段);而会话持久化在加载时
|
|
49
|
+
要求"未知事件类型必须 `ignorable === true`",否则抛 `SessionFormatUnsupportedError` **拒绝整个会话**。该规则在
|
|
50
|
+
0.1.5-rc.2 / 0.1.7-rc.2 / 0.2.0-rc.2 上一致存在 → v5 每追加一条 `vibe5/*`,就把**用户自己的会话**变成"写入时无声、
|
|
51
|
+
下次恢复时打不开"。修复:v5 只用加固 JSON(`State/<研究所>.v5state.json`),彻底不碰会话日志,并去掉
|
|
52
|
+
`sessions` / `sessionProjections` 依赖;连它写进项目的镜像 Markdown 与**给模型看的工具描述**都一并改正。
|
|
53
|
+
|
|
54
|
+
### G-5 工具过滤里的未注册名让整次派生失败(v4/v5)
|
|
55
|
+
|
|
56
|
+
`tools.restrict()` 对不在宿主可限制集合里的名字**直接抛错**,而该调用发生在建立子代理时 → v4/v5 一次配置错误
|
|
57
|
+
就让每次派生失败(v2/v3 早就有"按宿主提示剔除名字并重试"的守卫)。修复:v4/v5 采用同样的守卫,
|
|
58
|
+
并把返回值改成"按宿主提示剔除后重试一次",且**fail-closed**(绝不在过滤全被剔除时"不过滤"启动)。
|
|
59
|
+
|
|
60
|
+
### G-6 宿主新增"每根代理 8 个存活 continuable 子代理"上限
|
|
61
|
+
|
|
62
|
+
0.2.0 新增 `ActivationPool`(`maxActiveSubagents` 默认 8,来自 `subagent` 行的 config,未写入 `.d.ts`),
|
|
63
|
+
超出时抛 `SubagentError{code:'ACTIVATION_LIMIT_REACHED'}`;v4 的 `residentCount` 无上限、v5 的临时工上限之和可达 12
|
|
64
|
+
→ 撞限。修复:识别该错误码、从宿主消息里记下上限值、明确提示并把该次派生推迟到下一轮(不再让启动循环崩在半途)。
|
|
65
|
+
|
|
66
|
+
### G-7 压缩:策略调用 vs 执行调用(v4/v5)
|
|
67
|
+
|
|
68
|
+
`compactIfNeeded(agent,'pressure',signal)` 是**策略**调用,可能什么都不做且返回 `null`(无日志);真正的执行调用是
|
|
69
|
+
`compactNow(agent, signal)`。另外 v5 过去从**插件平面**取 compaction,而 preset 自己 `isolate` 了 compaction realm,
|
|
70
|
+
取到的是宿主根实例(配置可能不同)。修复:优先 `compactNow`(特性探测,旧宿主回退到策略调用),v5 改用 agent 自身 ctx 的实例。
|
|
71
|
+
|
|
72
|
+
### G-8 命令"业务失败"被当成成功
|
|
73
|
+
|
|
74
|
+
宿主的 `CommandResult` 联合是 `{kind:'success',text?} | {kind:'error',text}`;四套预设的 `/vibe`、`/v4`、`/v5`
|
|
75
|
+
在**所有失败路径**上都返回 `{kind:'success', text:'{"ok":false,…}'}` → UI 永远显示不出"这条命令被拒绝了"。
|
|
76
|
+
|
|
77
|
+
**更正(据实收窄):这条修复不是一次"全仓"修复,而是分两批落地的,本节初稿把"打算做的"写成了"已经做完的"。**
|
|
78
|
+
|
|
79
|
+
- **2.4.0(`cc0c220`)只改了 v2 与 v3**(`vibe-math-v2.js` / `vibe-math-v3.js` 的命令 handler)。当时 v4 与 v5 的
|
|
80
|
+
handler 仍然无条件 `return {kind:'success', text:JSON.stringify(r,null,2)}`(各自的"无 session"分支也返回
|
|
81
|
+
`kind:'success'`)——所以"四套都修了"在 2.4.0 这个提交上是不成立的。
|
|
82
|
+
- **v4 与 v5 在 round-A(`8ec5a9c`)才各自补上同一形状**:v4 的命令 handler 改成
|
|
83
|
+
`const failed = !r || r.ok === false || (r.ok === undefined && !!r.error)` → `{kind: failed?'error':'success'}`;
|
|
84
|
+
v5 改成 `const failed = r !== null && typeof r === 'object' && r.ok === false` → 同样三元返回,并把"无 session"
|
|
85
|
+
分支一并改成 `kind:'error'`。判据:`git log -G "kind:\s*'error'" -- vibe-math-v4/vibe-math-v4.js`(及 v5 同)
|
|
86
|
+
**只命中 `8ec5a9c`**;round-C(`4eb3175`)与 round-D(`0dc6ee3`)都没有再碰这两个 handler(前者的 v4 hunk 在
|
|
87
|
+
1354/1854 行,后者的 v5 hunk 止于 4564 行,均在命令 handler 之外)。
|
|
88
|
+
- **仓库内的回归证据是 round-C(`4eb3175`)补进来的**:`tests/host-failure-paths.test.mjs` 在该提交首次入库,
|
|
89
|
+
它断言 `/v4 set` 未知键、`/v4` 未知子命令与 `/v5` 未知子命令都返回 `kind:'error'`。round-D(`0dc6ee3`)只是
|
|
90
|
+
把"本节仍把 `/v4` 的修复说成全仓,需据实收窄"记进了待办,没有改代码。
|
|
91
|
+
- 该联合在 0.1.5 上同样存在,故旧线安全。也就是说:**"四套最终一致"成立,"四套一次修完"不成立**。
|
|
92
|
+
|
|
93
|
+
### G-9 文档、镜像与工具描述与实现不符
|
|
94
|
+
|
|
95
|
+
v5 的 README、`架构图.md`、`实现方案.md`、它写进用户项目的三个镜像文件头(`Institutes.md`、`Shared/TaskBoard.md`、
|
|
96
|
+
`State/README.md`)以及 `vibe_v5_set` 的**工具描述**都写着"权威状态在会话日志投影里"。修复:全部改为真实位置
|
|
97
|
+
(`State/<研究所>.v5state.json`),并重生成 v5 中英架构图。**这一条是提示词/交互面的事实性修复**,不是文风问题。
|
|
98
|
+
|
|
99
|
+
### G-10 "旧线不读目录"必须用**能力**判定,不能用版本号
|
|
100
|
+
|
|
101
|
+
`agentPresets` 服务在两条线上**都存在**但语义不同(旧线是目录扫描器、**没有** `register`)——用"服务存在"判形态
|
|
102
|
+
会在 0.1.5/0.1.6 上误判为"组合行",从而**跳过**唯一有效的目录安装。修复:判定读 **loader 入口树**里是否挂了
|
|
103
|
+
`@deepseek-ai/dsh-agent-preset`(或其 registry);这在任何插件激活之前就可见,而"读服务"在 bundle 被单独激活
|
|
104
|
+
(`dsh plugin add`)时可能尚未就绪。
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 2. 四宿主实测矩阵
|
|
109
|
+
|
|
110
|
+
| 宿主 | preset 形态 | 实测结果 |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| 0.1.5-rc.2 | 目录 | 安装器写入四个目录;声明行静默无副作用;`agentPresets.list()` 列出四个预设 ✅ |
|
|
113
|
+
| 0.1.6-alpha.2 | 目录 | 同上 ✅ |
|
|
114
|
+
| 0.1.7-rc.2 | 组合行 | 声明行注册四个预设(order 20–23);安装器跳过目录(`state` 文件不再被写)✅ |
|
|
115
|
+
| 0.2.0-rc.2 | 组合行 | 同上,且**零激活警告**(此前数组 patch / 宿主包名 / `!!js` 门各会产生一批失败行)✅ |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 3. 已确证**正确**的部分(避免下次重复劳动)
|
|
120
|
+
|
|
121
|
+
- `tools.register` 的形状(`name`/`description`/`parameters` + **必需的** `output:{schema,render}` + `execute`)与
|
|
122
|
+
`parameters` 用原生 JSON Schema 的写法(宿主自己的工具也这么写);handler 返回符合 `output.schema` 的 JSON 字符串 ✓。
|
|
123
|
+
- `commands.register`(`name`/`description`/`input:{hint}`/`handler`)与命令名正则 ✓。
|
|
124
|
+
- `tools.restrict` 的 `{allow,deny}` 形状,以及"未注册名"错误消息仍以 `known global tools: …` 结尾 —— v2/v3 的重试正则
|
|
125
|
+
逐字符匹配,行为保持 fail-closed ✓。
|
|
126
|
+
- `fs`(resolve/stat/readText/writeText/listDir)、`getPolicy()` 作为 `writeText` 第 5 参数的位置 ✓;`FsDirEntry` 字段无改名 ✓。
|
|
127
|
+
- `subagents.startContinuable` / `sendMessage`(4 参数 + `{signal}`)/ `interrupt`(第二参数为 authority,自 0.1.1 起)/
|
|
128
|
+
`drainContinuableChildren` 的调用形状自 0.1.2-rc.1 起未变 ✓;`agents.get` 在 `subagent/start` 里可解析、在 `subagent/end`
|
|
129
|
+
里**必然不可解析**——v4/v5 "在 start 时捕获 Agent 引用"的写法继续正确 ✓。
|
|
130
|
+
- `subprocess`(spawn/done/terminate/collected)与 `sandboxPolicy.resolve` ✓;`timer` 服务签名与 disposer 语义 ✓。
|
|
131
|
+
- `sessionProjections.register/stateOf` 的形状本身 ✓(本包不再使用,原因见 G-4)。
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 4. 修复实施记录(2.4.0)
|
|
136
|
+
|
|
137
|
+
见 `docs/release-notes/RELEASE-NOTES-2.4.0.md`「变更」。工程证据(逐条审计报告与探针输出)保存在仓库之外的
|
|
138
|
+
工作区台账中;本文件只保留结论与判据。
|
|
139
|
+
|
|
140
|
+
新增守卫:`tests/audit-preset-rows.test.mjs`(生成物与生成器逐字节一致、`dsh.bundle.patch` 必须是字符串、
|
|
141
|
+
声明行不得命名宿主包、不得使用会调用服务的 `!!js` 门、每条组合必须含 `present`/`command-goal`/`isolate`/`workflow-ptc`、
|
|
142
|
+
冻结的 0.1.x 组合保持原样);`docs/AUDIT-CHECKLIST.md` 新增 §8 把上述四条硬约束写成流程。
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 5. 刻意不改(附理由)
|
|
147
|
+
|
|
148
|
+
| 项 | 决定 | 理由 |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| v2/v3 使用全局 `setInterval`/`setTimeout` | 保留 | 预设是**文件行**,不经动态包 vm 沙箱(沙箱只包动态包宿主半);实测可用,且 `ctx.effect` 内已正确清理。仅修掉"插件运行时没有全局定时器"这句**错误注释**。 |
|
|
151
|
+
| 工具卡片呈现(`presentCall`/`presentResult`) | 不加 | 纯外观增强,与本次适配无关;未来要加需同步四套。 |
|
|
152
|
+
| `subagents.listChildren` 元素类型变化(`SubagentCatalogEntry`) | 不处理 | 四套预设均未调用。 |
|
|
153
|
+
| `additionalProperties:false` 的 `parameters` | 保留 | 宿主 `tools.register` 不校验 `parameters`;仅当预设改造成**动态包**时 `defineTool` 才会拒绝(届时再改)。 |
|
|
154
|
+
| `followup` 回退分支 | 保留 | 该分支只在 0.1.1 及更早存在;本包声明的下限是 0.1.2-alpha.4,其中 `0.1.2-alpha.*` 是否仍带 `followup` 未实测,保留分支零成本且更稳。 |
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# 审计方法论:可复现的守卫、具名红与实测边界
|
|
2
|
+
|
|
3
|
+
> 面向读者:想理解或复用本仓库验证方式的人。
|
|
4
|
+
> 权威索引是 [`AUDIT-CHECKLIST.md`](./AUDIT-CHECKLIST.md)——本文件讲**方法**,那本文件逐条列**守卫与变异**;两者不重复同一句话。
|
|
5
|
+
> 文中的命令与路径都可以直接跑;每个数字都应**当场重新派生**,不要照抄本文件里的历史读数。
|
|
6
|
+
|
|
7
|
+
## 1. 这套方法要解决的问题
|
|
8
|
+
|
|
9
|
+
三类"看起来通过、其实没验证"的失败:
|
|
10
|
+
|
|
11
|
+
1. **空过(vacuous)**:断言两边来自同一个来源(同一个变量、同一份读数),恒真。
|
|
12
|
+
2. **只数数量**:只断言"共 N 条",不看内容;删掉语义后计数照旧。
|
|
13
|
+
3. **文档漂移**:代码改了,文档/索引里的数字与说法没跟上(本仓库历史上出现过多次)。
|
|
14
|
+
|
|
15
|
+
对应三条纪律:**断言要有具名失败**、**计数要现取并写明单位**、**没有判别力时如实记录边界**。
|
|
16
|
+
|
|
17
|
+
## 2. 门禁的组成与计数
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
node tests/run-tests.mjs # 全部作业
|
|
21
|
+
node tests/run-tests.mjs --counts # 打印派生计数(JSON)
|
|
22
|
+
node tests/run-tests.mjs --only <子串> --exclude <子串>
|
|
23
|
+
node tests/run-tests.mjs --self-check # 校验门禁自身的诊断/超时机制
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- **作业计数(job count)** = 套件(suite)+ 探针/变体(probe)。`--counts` 给出三元组 `{"total","suites","probes"}`,这是**唯一权威**。
|
|
27
|
+
- **文件计数** = `tests/*.mjs` 的文件个数(含 `*.test.mjs` 与 `*.mutants.mjs`),与作业计数**不是同一个量**。文档里写"多少个测试"时必须说明是哪一个。
|
|
28
|
+
- **变体一律算探针,不算新套件**:同一个脚本多带一个参数跑(例如给某套件加 `--self-probe`)是**额外的仪器化运行**,不是新增套件。门禁的作业分类必须是 `kind: 'probe'`;按**文件名**判类会把这个量算错(本仓库真实发生过:一个套件加上 `--self-probe` 后 `suites` 从 44 变成 45)。
|
|
29
|
+
- 变体在 `tests/run-tests.mjs` 的 `VARIANTS` 里注册(`{ file, args, expectExit }`),因此**随包可复现**。
|
|
30
|
+
|
|
31
|
+
## 3. 三种守卫形态(选一种,不要发明第四种)
|
|
32
|
+
|
|
33
|
+
| 形态 | 位置 | 适合 | 怎么让它红一次 |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| **套件内行为断言** | `tests/*.test.mjs` | 真实调用后的行为/字段 | 直接断言;配套变异见下两行 |
|
|
36
|
+
| **随包变异族** | `tests/*.mutants.mjs` | 单点改坏后**具名**变红 | `node tests/<name>.mutants.mjs` |
|
|
37
|
+
| **套件内 `--self-probe` / 变异段** | 套件自身 + `VARIANTS` 注册 | 断言读源码文本、或需要同一批谓词喂坏输入 | `node tests/<suite> --self-probe` |
|
|
38
|
+
|
|
39
|
+
**具名红要求**:变异必须让**一条具名断言**失败。只看退出码不算——退出码可能是别的断言、也可能是崩溃。检测方式因此是"在 `FAIL` 行里找到那条断言的名字",而不是"输出里出现过这个字符串"。
|
|
40
|
+
|
|
41
|
+
两种常用写法:
|
|
42
|
+
|
|
43
|
+
- **同一谓词,两种输入**:把断言用的谓词抽成函数,正常路径喂真实数据,自探针喂**故意改坏**的数据,要求它翻假。仓库里 `tests/audit-participant-set-parity.mjs --self-probe`、`tests/audit-math-computation-parity.mjs --self-probe`、`tests/math-computation-shared.test.mjs --self-probe` 都是这个形态。
|
|
44
|
+
- **忠实副本基线**:变异跑在**副本**上(环境缝指向副本),并且先证明**未变异的副本也是绿的**,否则"红"可能只是副本本身的问题;更强的一步是证明**缝确实被读**(删掉副本里的一个文件后,守卫应以**指向副本路径**的错误失败)。示例:`tests/audit-persona-surface.mutants.mjs`。
|
|
45
|
+
|
|
46
|
+
## 4. 实测边界:宁可"measured, not proven"
|
|
47
|
+
|
|
48
|
+
当**不存在**有判别力的单点变异时,正确做法是**记录边界**,而不是把附近能红的断言当成证明:
|
|
49
|
+
|
|
50
|
+
1. 写清**想要判别的行为**;
|
|
51
|
+
2. 写清**尝试过的变异与实测结果**(哪个数字、哪条输出);
|
|
52
|
+
3. 给出**为什么没有判别力**的结构原因(例如:提示只入队、要等下一次提交才可见,所以单点变异后状态面看不到它);
|
|
53
|
+
4. 结论写 **"实测边界(measured, not proven)"**,并列出**可能的提升路径**(改断言面 / 需要产品改动但被否决)。
|
|
54
|
+
|
|
55
|
+
真实例子见 [`AUDIT-CHECKLIST.md`](./AUDIT-CHECKLIST.md) 的守卫索引:有的条目先被记为"显式有界",后来找到了判别面(把断言从 `status()` 移到 `report()` 的诊断、并补上"再提交一次"的步骤)就**改成引用**并注明"原裁定已被取代";也有的条目曾引用一个内容已经变化的草稿,被**发现引用过期**后改为直接引用随包套件;还有的变异族条目被**回退**(前提与两条随包守卫互斥),族计数随之变化——这些都只是**实测事实**,不是缺陷。
|
|
56
|
+
|
|
57
|
+
## 5. 计数与声称纪律
|
|
58
|
+
|
|
59
|
+
- **现取,不照抄**:任何数字都要说明**单位**与**工具**。例如"行数(`Measure-Object -Line`)"与"字节数(文件大小)"是两个不同的量;同一次读取里 `bytes=12084`、按 LF 切分是 `76` 行,而某些工具对同一文件会给出完全不同的行数——**先说明工具,再给数字**。
|
|
60
|
+
- **同一批对象要同一次运行**:变异族的 `N/N` 与耗时取自**它自己打印**的那一行(`TOTAL WALL TIME …`),不要跨运行拼接;运行之间本身就有负载差异。
|
|
61
|
+
- **文档不许与派生值矛盾**:live 文档里出现的每个 `TOTAL <n>` 必须等于 `node tests/run-tests.mjs --counts` 的派生值;**冻结的历史发布说明按名豁免**(豁免清单本身也断言非空)。改动计数后跑 `node scripts/update-doc-counts.mjs`(`--check` 退出 0 即收敛)。
|
|
62
|
+
- **范围要写明**:说"全部通过"时必须给出**是哪一批作业**(套件 + 探针/变体的三元组),而不是一个孤立的数字。
|
|
63
|
+
|
|
64
|
+
## 6. 工具链陷阱(都真实踩过)
|
|
65
|
+
|
|
66
|
+
1. **非 ASCII 路径**:`git` 默认会把非 ASCII 路径转义后再输出,`git status` / `git diff` 的路径看起来像乱码。要看原样路径用 `git -c core.quotePath=false …`,或让脚本自己读目录。
|
|
67
|
+
2. **读文件必须显式编码**:`Get-Content` 不指定编码时会按系统代码页(CP936)解码 UTF-8 字节,**行数会塌陷**、内容会乱。要么显式 `-Encoding utf8`,要么用脚本语言(Node/Python)读字节。
|
|
68
|
+
3. **含 CJK 路径的脚本要有 UTF-8 BOM**,或用 `$PSScriptRoot` 取脚本自身目录;否则脚本被以别的代码页解析时路径会损坏。
|
|
69
|
+
4. **嵌套 `pwsh -File` 在本环境不可解析**:需要在同一进程里用 `& script.ps1` 调用,避免 `pwsh` 再起一层。
|
|
70
|
+
5. **单次 `ENOENT` 不等于文件不存在**:可能是路径拼接、工作目录或编码问题。判定"文件不在"要看**同一路径**的多次独立观测(例如同时用目录列举与直接读取)。
|
|
71
|
+
6. **`Select-String` 的模式默认是正则**:要按字面匹配就用 `-SimpleMatch`;否则 `[`、`.`、`(` 之类会被当正则元字符,命中数会骗人。
|
|
72
|
+
7. **行尾(EOL)与行号都不能当锚**:`git status` 在 `core.autocrlf` 下**看不见** EOL 漂移(改了行尾仍是 "clean");**孤立 CR** 会让 `--ignore-cr-at-eol` 计数失真,也会让 `Select-String` 与 `read`/`ReadAllLines` 报出**不同的行号**。⇒ 改文件时**只认"唯一文本锚"**(含足够上下文以保证唯一),**绝不认行号**;提交前用 `git diff --ignore-cr-at-eol` 核对**真实语义差异**(而不是行尾噪声)。
|
|
73
|
+
8. **`-Include` 在非递归路径下不生效**:`Get-ChildItem <dir> -File -Include *.md` 会**静默扫不到东西**(于是"零命中"被误读成"不存在")⇒ 要么加 `-Recurse`,要么把过滤写进 `Where-Object`。
|
|
74
|
+
|
|
75
|
+
## 7. 新增一条守卫时的清单
|
|
76
|
+
|
|
77
|
+
1. 选形态(§3 三种之一),并确定"具名红"是哪条断言;
|
|
78
|
+
2. 让变异**单点**、可读、可复现;跑在**副本**上,并保留忠实副本基线;
|
|
79
|
+
3. 若是新作业:在 `package.json#files` 登记(若是 `tests/*.mjs`),必要时在 `run-tests.mjs` 的 `VARIANTS` 注册;
|
|
80
|
+
4. **重跑** `node scripts/update-doc-counts.mjs` 让文档计数收敛;
|
|
81
|
+
5. 在 [`AUDIT-CHECKLIST.md`](./AUDIT-CHECKLIST.md) 增加/更新该行(不变量 + 怎么让它红一次),并在 `docs/release-notes/` 的对应版本里说明**用户可见的变化与验证方式**;
|
|
82
|
+
6. 若最终发现**没有判别力**,按 §4 写成实测边界。
|
|
83
|
+
|
|
84
|
+
## 8. 环境卫兵:被杀死的套件会泄漏临时目录(实测)
|
|
85
|
+
|
|
86
|
+
- **实测**:本机 `%TEMP%`(`C:\Users\admin\AppData\Local\Temp\1`)顶层曾达 **248,737 项**,其中**我们的套件临时目录 245,288 项**(`vibe-*` 221,939、`v2-fix-*` 23,160、…),**111,223 项早于 24 h**(最早约两周前)。
|
|
87
|
+
- **根因**:套件用 `mkdtempSync(join(tmpdir(), '<prefix>-…'))` 建工作区并在 `finally` 清理 ✓,**但被门禁超时 SIGKILL 的进程无法清理**;另有一些套件每次运行创建大量目录 ⇒ 数百次门禁后线性累积。
|
|
88
|
+
- **卫兵(`scripts/clean-temp.mjs`,由 `tests/run-tests.mjs` 启动时调用)**:
|
|
89
|
+
1. **清扫**:只删 `os.tmpdir()` **顶层**、名称匹配**从 `tests/*.mjs` 的 `mkdtempSync` 自动提取的前缀**(实测 **88** 个)、且 **`mtime` 早于阈值**(默认 **6 h**,避免误删并发运行的另一场门禁)的目录;`--dry-run` 只打印计划;`--age-hours=N`/`--no-temp-hygiene` 可调。
|
|
90
|
+
2. **临时目录优先落 D 盘**:`D:\_tmp` 可用时把 `TEMP/TMP/TMPDIR` 指向它(`os.tmpdir()` **自动跟随** ⇒ **零产品改动**),不可用则回退,**不硬失败**。
|
|
91
|
+
- **自证**:`node scripts/clean-temp.mjs --self-test` 断言"**新鲜目录绝不清理**"、"陈旧且前缀匹配才清理"、"**只匹配精确前缀**"、"前缀表来自套件";变异家族 `tests/temp-hygiene.mutants.mjs` 用两条**单点**变异(去掉年龄阈值/去掉前缀判断)证明这些断言**按名变红**(实测 **2/2**)。
|
|
92
|
+
- **通道陷阱(真实踩过)**:卫生信息**必须写 stderr** —— `tests/run-tests.mjs --counts`/`--json` 的 **stdout 是机器可读 JSON**(`scripts/update-doc-counts.mjs` 会 `JSON.parse` 它),把日志写到 stdout 会让计数刷新直接崩(`SyntaxError: Unexpected token 'r'`)。
|
|
93
|
+
- **先查遗留进程,再谈"时序抖动"(实测,task-15)**:一次"门禁红、单跑绿"的**总根源是环境** —— 一只**遗留 DSH 宿主**(`--profile vmfix7`,**两天前**启动、父进程已消失)累计烧掉 **197,410 秒 CPU**(≈55 h;在 4 核机上等于**长期占满一个核**),另有 3 只遗留 `review/l2-*.mjs`。带它时整场门禁 `sum of suite times` **2822 s → 7875 s(×2.8)**、单套件被放慢 **1.3–4.5×**;清掉后同配置门禁 **wall 6705.6 s → 1371.2 s(×4.9)**。⇒ 常规动作:门禁启动时**只读列出**"父进程已消失且命令行属于本工程/团队"的进程(`node scripts/clean-temp.mjs --ps`,`--kill` 必须显式),**绝不自动杀**;判据自带 3 条自检 + 两条单点变异(去掉"父进程已消失"⇒ 自家门禁被误报**按名**变红;去掉命令行闸门 ⇒ 无关程序被误报**按名**变红)。
|