dsh-vibe-math 2.3.3 → 2.3.5

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.
@@ -266,6 +266,12 @@ provider **直接拒绝**。于是"提示词/规格/状态表都写着这个参
266
266
  ——缺文件必须是**一条干净的断言失败**,绝不能是崩溃。参见该套件里的 `readArtifact`。
267
267
  - [ ] 发布产物自证:不是"publish 退出 0"就算完成——要从 registry 取回 tarball,
268
268
  核对 shasum、逐字节比对插件、确认修复标记存在、并在**已发布包内**跑一遍套件。
269
+ - [ ] **每个"自己动手抹注释/抹字符串"的脚本都要有解析级自检**:这类扫描器一旦读错词法
270
+ (最典型:正则字面量里的引号被当成字符串开头),它脚下**所有**静态检查都会静默失明。
271
+ 真实事故:`audit-prompt-invariants.mjs`(2.3.2 完全不认正则 → 四套源码抹完直接语法错误)与
272
+ `audit-v5-integrity.mjs`(不认正则 → v5 有 30 行读错、抹完不能解析)。自检写法:把抹除后的
273
+ 真实源码写进临时文件跑 `node --check`,再加一个"引号在字符类里"的夹具。**并且在写结论前先量准**
274
+ ——用"下一个引号"估算受影响区域曾让我把 30 行误报成 402 行。
269
275
  - [ ] **发布元数据交给序列化器**:不要用字符串替换手写 `package.json` 的字段(`compatNote` 里一个
270
276
  ASCII 双引号就能产出非法 JSON;`\\u0000` 之类转义在单引号字符串里会变成真控制字符)。
271
277
  正确写法是 `JSON.parse` → 改属性 → `JSON.stringify(pkg, null, 2)`,写完再 `JSON.parse` 复核
package/README.md CHANGED
@@ -817,8 +817,9 @@ v5 的完整架构(含成员生命周期、一轮时序、共识状态机、
817
817
  - **测试耗时基线与并行跑法**:[`docs/test-timing.md`](docs/test-timing.md)(`node run-tests.mjs` 并行跑全部套件 ≈1.9 min;探针脚本 ≈2.6 min;每个 runner 都会打印耗时/加速比供下次选策略)
818
818
  - **静态提示词面一致性(persona ↔ 工具注册表 ↔ 斜杠命令 hint/usage)**:[`audit-persona-surface.test.mjs`](audit-persona-surface.test.mjs)(197 条断言,并生成 [`prompt-corpus-persona/persona-corpus.md`](prompt-corpus-persona/persona-corpus.md) 供人工复核)+ [`audit-persona-sensitivity.mjs`](audit-persona-sensitivity.mjs)(11 条灵敏度探针)——守"注册的工具必须在 persona 里出现 / persona 里的名字必须真的注册 / `prefix` 与 `text` 两块逐行一致 / hint、usage、实际分支三处必须一致"
819
819
  - **全面检查必查清单**:[`AUDIT-CHECKLIST.md`](AUDIT-CHECKLIST.md)(本仓库的强制审计流程;§1.9 专门查"工具参数 schema 收不收得下")
820
- - **提示词/交互不变式(四套一起,可一键复核)**:[`audit-prompt-invariants.mjs`](audit-prompt-invariants.mjs)(151 条断言)——把"历史上真实发生过的提示词/工具面缺陷类别"逐条编码成静态不变式(缩写工具名、把忠实性缺陷投成 0、`defect` 只写在提示词里没实现、回执契约缺 `defect`、无 note 放行、字段名错、`off` 档回执仍能写状态、语料不确定、探针缺失、**工具的封闭 schema 收不下它自己文档里的参数**、**schema 声明了参数层却静默丢弃的键**)。加 `--self-probe` 会在内存里注入这些缺陷形状,要求对应不变式**变红**、未变异的对照跑**仍为绿**(5/5);脚本自身另带 X5–X7 三条自检(注释扫描器必须认正则字面量、字符串里的 `//` 必须保留、抹注释不改变行结构)
820
+ - **提示词/交互不变式(四套一起,可一键复核)**:[`audit-prompt-invariants.mjs`](audit-prompt-invariants.mjs)(157 条断言)——把"历史上真实发生过的提示词/工具面缺陷类别"逐条编码成静态不变式(缩写工具名、把忠实性缺陷投成 0、`defect` 只写在提示词里没实现、回执契约缺 `defect`、无 note 放行、字段名错、`off` 档回执仍能写状态、语料不确定、探针缺失、**工具的封闭 schema 收不下它自己文档里的参数**、**schema 声明了参数层却静默丢弃的键**)。加 `--self-probe` 会在内存里注入这些缺陷形状,要求对应不变式**变红**、未变异的对照跑**仍为绿**(5/5);脚本自身另带 X5–X8b 六条自检(注释扫描器必须认正则字面量——包括 `return /…/ ` 这种**关键字后面**的正则——字符串里的 `//` 必须保留、抹注释不改变行结构,以及"四套源码抹掉注释后仍必须能被 `node --check` 解析"这条解析级判据)
821
821
  - **规格 ↔ 代码可追溯(四套一起)**:[`audit-spec-traceability.mjs`](audit-spec-traceability.mjs)(94 条断言)——`实现方案.md`/README 里承诺的工具必须真的注册;四个 Lean 参数必须同时被文档与代码接受
822
+ - **v5 静态完整性**:[`audit-v5-integrity.mjs`](audit-v5-integrity.mjs)(≈0.5 s)——调用了但未定义的函数、未声明的 `params.*` 读取、会话 API 上不存在的方法、文档化但从未抛出的错误码、遗留开发标记,外加**扫描器解析级自检**(它先把注释/字符串/正则抹掉再扫描,自检保证"抹除后的源码仍能被 `node --check` 解析"——2.3.5 修掉的正是这个扫描器读错 30 行的盲区);配套 [`audit-v5-sensitivity.mjs`](audit-v5-sensitivity.mjs)(39 条探针,全红才算通过)
822
823
 
823
824
  ---
824
825
 
@@ -0,0 +1,69 @@
1
+ # dsh-vibe-math 2.3.4 — 审计守卫的最后一处盲区:正则字面量的**关键字规则** + 解析级扫描器判据
2
+
3
+ > 上一版:2.3.3。本版是 round 4:**只审"上一轮改动过的东西"**——2.3.3 修好的注释扫描器(它是
4
+ > I1/I13/I14 三条不变式的地基)与它新增的 X5–X7 自检。无破坏性变更,默认仍为 `formalVerify: 'off'`。
5
+
6
+ ---
7
+
8
+ ## 1. 扫描器还剩一个盲区:`return /…/ ` 被当成除法
9
+
10
+ 2.3.3 给扫描器加了正则字面量支持,但"这个 `/` 是正则还是除法"的判据**只看前一个字符**:
11
+
12
+ ```js
13
+ if (c === '/' && /[(,=:[!&|?{};+\-*%~^<>]|^$/.test(prev)) → 正则
14
+ ```
15
+
16
+ 于是**关键字后面**的正则会被读成除法——而 v3 源码里真有这种写法:
17
+
18
+ | 位置 | 代码 |
19
+ |---|---|
20
+ | v3 L2813 | `return /^\s*import\s+/.test(l)` |
21
+ | v3 L2856 | `return /\n$/.test(out) ? out : out + '\n'` |
22
+
23
+ 这两处恰好字符类里没有引号,所以**今天**还没坏;但只要有人在 `return /…/` 的正则里写一个引号
24
+ (例如 `return /["']/.test(s)`),扫描器就会把那个引号当成字符串开头,此后整个文件的词法状态都是错的
25
+ ——**又回到 2.3.2 修掉的那个坑**(真代码被当注释抹掉 → 真缺陷可能被漏看;注释被当代码 → 误报)。
26
+ 这是"守卫自己的守卫"里最后一块没盖住的地方。
27
+
28
+ **修复**:判据从"前一个字符"升级为"前一个**记号**":除运算符/开括号之外,`return` / `typeof` /
29
+ `case` / `delete` / `void` / `instanceof` / `in` / `of` / `yield` / `await` / `new` / `do` / `else`
30
+ 之后也按正则解析;标识符、`)`、`]` 之后仍按除法。
31
+
32
+ ## 2. 新增**解析级**判据 X8 / X8b(并实测其灵敏度)
33
+
34
+ 静态自检(X5–X7)只覆盖我想到的构造;这一版加了一条**不依赖我列举**的强判据:
35
+
36
+ - **X8**:把四套源码各自"抹掉注释"之后,输出**必须仍能被 `node --check` 解析**。扫描器一旦把代码
37
+ 读错(正则当除法、引号当字符串开头),产物往往直接语法错误。
38
+ - **X8b**:一个专门针对关键字规则的夹具(`return /["']/.test(l)` + 紧随其后的注释)。
39
+
40
+ 灵敏度是**实测**的,不是假设的:
41
+
42
+ | 扫描器版本 | X8(四套源码可解析) | X8b 夹具 |
43
+ |---|---|---|
44
+ | 2.3.2(完全不认正则) | **四个预设全部 SyntaxError**(红) | 红 |
45
+ | 2.3.3(认正则、无关键字规则) | 绿(那两处 `return /…/ ` 里没有引号) | **红**:紧随其后的注释没被抹掉(`comment-blanked=false`) |
46
+ | 2.3.4(当前) | 绿 | 绿 |
47
+
48
+ ——即:X8 守住大回退,X8b 守住这个窄口子;**2.3.3 的扫描器会被 X8b 判红**,说明这条判据不是摆设。
49
+ 不变式 151 → **157**。
50
+
51
+ ## 3. 验收(实测)
52
+
53
+ | 套件 / 脚本 | 2.3.3 | 2.3.4 |
54
+ |---|---|---|
55
+ | `audit-prompt-invariants.mjs` | 151 | **157**(+X8 ×4、+X8b ×2) |
56
+ | `audit-prompt-invariants.mjs --self-probe` | 5/5 | **5/5** |
57
+ | `audit-spec-traceability.mjs` | 94 | 94 |
58
+ | `formal-verify-v2/v3/v4/v5` | 334 / 294 / 277 / 153 | 不变(本版未动插件) |
59
+ | `prompt-v5-integrity.test.mjs` | 506 | 不变 |
60
+ | 全量并行回归 | 23/23 | **23/23** |
61
+ | 49 条 formal 探针 | 全红 | 全红 |
62
+
63
+ ## 4. 升级
64
+
65
+ ```
66
+ npm i dsh-vibe-math@latest
67
+ ```
68
+
69
+ 无迁移,无行为变更(本版只改随包发布的审计脚本与文档;四套预设的字节与 2.3.3 相同)。
@@ -0,0 +1,63 @@
1
+ # dsh-vibe-math 2.3.5 — 同一类扫描器缺陷也在 `audit-v5-integrity.mjs` 里(潜在盲区)+ 解析级自检
2
+
3
+ > 上一版:2.3.4。本版是 round 5:把"注释/字符串扫描器"这一类**追到所有自带扫描器的脚本**上。
4
+ > 无破坏性变更,默认仍为 `formalVerify: 'off'`,四套预设字节未变。
5
+
6
+ ---
7
+
8
+ ## 1. 同一类的第二处:v5 完整性审计的扫描器
9
+
10
+ `audit-v5-integrity.mjs`(静态自检:调用了但未定义的函数 / 未声明的 `params.X` 读取 / 会话 API 上
11
+ 不存在的方法 / 遗留开发标记)在扫描前会先把注释与字符串抹掉——而它的 `stripNoise()` **完全不认正则
12
+ 字面量**,v5 源码里恰好有一个字符类带双引号的 sanitize 正则:
13
+
14
+ ```js
15
+ .replace(/[\\/:*?"<>|\u0000-\u001f]+/g, '-')
16
+ ```
17
+
18
+ 那个 `"` 被当成字符串开头。**实测**(旧 vs 新逐行比对,v5 共 4365 行):
19
+
20
+ | 指标 | 旧扫描器 | 新扫描器 |
21
+ |---|---|---|
22
+ | 与正确扫描结果不一致的行 | **30 行**(都是含"字符类里有引号"的正则的行) | 0 |
23
+ | 抹除后的文本能否 `node --check` 解析 | **不能**(SyntaxError) | 能 |
24
+ | 漏掉的调用名 / `params.*` 读取 / `s.*()` 方法 | **本次为 0**(该行其余标识符仍在文本里) | — |
25
+
26
+ 也就是说:**这是潜在盲区而非已发生的漏检**——只要某个被检查的调用/参数读取**恰好只出现在这种行上**,
27
+ 它就会完全隐形。审计脚本自己都"读不对文件"是比漏一条规则更根本的问题。
28
+
29
+ **修复**:`stripNoise()` 升级为与 `audit-prompt-invariants.mjs` 同一套词法处理(正则字面量 + 字符类 +
30
+ 转义 + flags + **关键字规则**),正则与字符串统一替换为值占位符。
31
+
32
+ > **我自己也在这条上翻过一次车**:第一版测量用"文件里下一个引号"估算被吞掉的区域,得出"402 行 /
33
+ > 18 KB / 20 个函数"的结论;但那台扫描器**遇到换行就结束字符串**,真实影响只有 30 行。发布前用
34
+ > 逐行比对复核才发现,代码注释与下面的记录都已按实测改正。(教训:**先量准,再下结论**。)
35
+
36
+ ## 2. 新增解析级自检(并实测其灵敏度)
37
+
38
+ `audit-v5-integrity.mjs` 现在每次都自检它脚下的扫描器:
39
+
40
+ 1. 抹除后的 v5 源码**必须仍能被 `node --check` 解析**;
41
+ 2. 一个"引号在字符类里的正则 + 紧随其后的注释"夹具必须完好(引号不得吞掉注释、必须留下值占位符)。
42
+
43
+ **灵敏度实测**:把旧的 `stripNoise()` 放回去,审计立刻报
44
+ `stripNoise() corrupted the source it scans … every identifier check below is unreliable`(exit 1),
45
+ 换回新版则 clean。39 条 v5 灵敏度探针仍然全红(0 盲点)。
46
+
47
+ ## 3. 验收(实测)
48
+
49
+ | 项 | 2.3.4 | 2.3.5 |
50
+ |---|---|---|
51
+ | `audit-v5-integrity.mjs` | clean(但扫描器会读错 30 行) | **clean + `scanner self-check: stripped output parses=true; quoted-class fixture=true`** |
52
+ | `audit-v5-sensitivity.mjs` | 39/39 全红 | 39/39 全红 |
53
+ | `audit-prompt-invariants.mjs` | 157 | 157 |
54
+ | 四套 formal 套件 | 334 / 294 / 277 / 153 | 不变(本版未动插件) |
55
+ | 全量并行回归 | 23/23 | 23/23 |
56
+
57
+ ## 4. 升级
58
+
59
+ ```
60
+ npm i dsh-vibe-math@latest
61
+ ```
62
+
63
+ 无迁移、无行为变更(本版只改随包发布的审计脚本与文档)。
@@ -26,8 +26,9 @@
26
26
  * prove the guard is a guard: re-run itself on mutated sources and require the matching
27
27
  * invariant to go RED (control run must stay green)
28
28
  */
29
- import { readFileSync, existsSync } from 'node:fs'
29
+ import { readFileSync, existsSync, mkdtempSync, writeFileSync, rmSync } from 'node:fs'
30
30
  import { spawnSync } from 'node:child_process'
31
+ import { tmpdir } from 'node:os'
31
32
  import { fileURLToPath } from 'node:url'
32
33
  import { join } from 'node:path'
33
34
 
@@ -133,18 +134,26 @@ function stripComments(src) {
133
134
  const out = []
134
135
  let i = 0
135
136
  let state = 'code' // code | line | block | sq | dq | tpl | regex
136
- let prev = '' // last significant code char, to tell a regex literal from division
137
+ let prev = '' // last significant code token (a single char, or a whole word such as `return`)
138
+ let word = '' // identifier/keyword accumulator, so `return /re/` is not read as division
139
+ // A `/` starts a REGEX LITERAL after an operator/keyword, and DIVISION after a value. Tracking only
140
+ // the previous CHARACTER is not enough: `return /^\s*import/.test(l)` (v3 really contains it) puts
141
+ // an identifier before the slash. Getting this wrong is exactly the bug X5–X8 guard against.
142
+ const REGEX_AFTER_KEYWORD = /^(?:return|typeof|case|delete|void|instanceof|in|of|yield|await|new|do|else)$/
137
143
  while (i < src.length) {
138
144
  const c = src[i]
139
145
  const c2 = src[i + 1]
140
146
  if (state === 'code') {
141
147
  if (c === '/' && c2 === '/') { state = 'line'; out.push(' '); i += 2; continue }
142
148
  if (c === '/' && c2 === '*') { state = 'block'; out.push(' '); i += 2; continue }
143
- if (c === '/' && /[(,=:[!&|?{};+\-*%~^<>]|^$/.test(prev)) { state = 'regex'; out.push(c); i++; continue }
149
+ if (c === '/' && (prev === '' || REGEX_AFTER_KEYWORD.test(prev) || /[(,=:[!&|?{};+\-*%~^<>]/.test(prev))) {
150
+ state = 'regex'; out.push(c); i++; continue
151
+ }
144
152
  if (c === "'") state = 'sq'
145
153
  else if (c === '"') state = 'dq'
146
154
  else if (c === '`') state = 'tpl'
147
- if (!/\s/.test(c)) prev = c
155
+ if (/[A-Za-z0-9_$]/.test(c)) word += c
156
+ else { if (word) { prev = word; word = '' } if (!/\s/.test(c)) prev = c }
148
157
  out.push(c); i++; continue
149
158
  }
150
159
  if (state === 'line') {
@@ -445,6 +454,46 @@ for (const P of PRESETS) {
445
454
  }
446
455
  }
447
456
 
457
+ // X8 — the strongest scanner oracle there is: the comment-stripped source of EVERY preset must still
458
+ // PARSE. A scanner that mis-lexes (regex read as division, or a quote inside a regex read as a string
459
+ // start) corrupts real code, and `node --check` sees it. Sensitivity is MEASURED, not assumed:
460
+ // · the 2.3.2 scanner (no regex support at all) → SyntaxError on all four presets → X8 red;
461
+ // · the 2.3.3 scanner (regex-aware but no keyword rule) → X8 still green, which is exactly why
462
+ // X8b below exists (it covers that narrower gap).
463
+ {
464
+ const dir = mkdtempSync(join(tmpdir(), 'prompt-invariants-strip-'))
465
+ try {
466
+ for (const P of PRESETS) {
467
+ const src = read(P.js)
468
+ if (!src) continue
469
+ const f = join(dir, P.tag + '.mjs')
470
+ writeFileSync(f, stripComments(src))
471
+ const r = spawnSync(process.execPath, ['--check', f], { encoding: 'utf8' })
472
+ check(r.status === 0,
473
+ 'X8: the comment-stripped source of ' + P.tag + ' is still valid JS (the scanner must not corrupt code)',
474
+ String(r.stderr || '').split('\n').filter((l) => l.trim()).slice(-2).join(' ').slice(0, 160))
475
+ }
476
+ // X8b: a fixture that needs the KEYWORD rule — a regex AFTER `return` whose character class holds
477
+ // a quote (v3 really has `return /^\s*…/.test()`, so the rule is not hypothetical). A scanner that
478
+ // reads that `/` as division enters "string" state at the quote and then leaves the NEXT comment
479
+ // unblanked. Sensitivity MEASURED: the 2.3.3 scanner fails the second assertion below
480
+ // (comment-blanked=false) while passing the first; the current scanner passes both.
481
+ const fixtureKeyword = [
482
+ 'function f(l) { return /["\']/.test(l) }',
483
+ '// lean_run must stay blanked',
484
+ 'const h = 4 / 2',
485
+ ].join('\n')
486
+ const f2 = join(dir, 'fixture.mjs')
487
+ const stripped = stripComments(fixtureKeyword)
488
+ writeFileSync(f2, stripped)
489
+ const r2 = spawnSync(process.execPath, ['--check', f2], { encoding: 'utf8' })
490
+ check(r2.status === 0, 'X8b: a regex after a KEYWORD (return /…/) does not corrupt the scan', String(r2.stderr || '').slice(0, 120))
491
+ check(!stripped.includes('lean_run'), 'X8b: and the comment after it is still blanked (the keyword rule is what decides this)')
492
+ } finally {
493
+ rmSync(dir, { recursive: true, force: true })
494
+ }
495
+ }
496
+
448
497
  const out = { passed, failed: failures.length, failures, notes }
449
498
  if (process.argv.includes('--json')) {
450
499
  console.log(JSON.stringify(out, null, 2))
@@ -9,21 +9,53 @@
9
9
  // 5. leftover development markers / TODO scaffolding
10
10
  // Run: node audit-v5-integrity.mjs (exit 1 on any finding)
11
11
  // ============================================================
12
- import { readFileSync, existsSync } from 'node:fs'
12
+ import { readFileSync, existsSync, mkdtempSync, writeFileSync, rmSync } from 'node:fs'
13
+ import { spawnSync } from 'node:child_process'
14
+ import { tmpdir } from 'node:os'
15
+ import { join } from 'node:path'
13
16
 
14
17
  const FILE = new URL('./vibe-math-v5/vibe-math-v5.js', import.meta.url)
15
18
  const raw = readFileSync(FILE, 'utf8')
16
- // Strip comments and string literals before any identifier scan. Without this the
19
+ // Strip comments, string literals AND regex literals before any identifier scan. Without this the
17
20
  // heuristic matches English words inside comments that merely precede a '(' (e.g.
18
21
  // "// per unit (" becomes a phantom call to unit()), drowning the real findings.
22
+ //
23
+ // REGEX LITERALS ARE NOT OPTIONAL HERE: v5 contains `/[\\/:*?"<>|\u0000-\u001f]+/` — a character class
24
+ // holding a DOUBLE QUOTE. A scanner without regex support reads that quote as a string start and
25
+ // mangles the rest of the line. Measured on this very file (old vs new, line by line): 30 of 4365
26
+ // lines differed and the stripped output did NOT parse. No identifier this audit checks
27
+ // (call names, `params.*` reads, `s.*()` methods) happened to be missed in the current source, so this
28
+ // was latent rather than exploited — but a single call appearing only on such a line would have been
29
+ // invisible. The self-check at the bottom of this file keeps the scanner honest.
30
+ // The keyword rule matters for the same reason as in audit-prompt-invariants.mjs (`return /…/`).
19
31
  function stripNoise(s) {
20
32
  let out = ''
21
33
  let i = 0
22
34
  const n = s.length
35
+ let prev = '' // last significant token (single char, or a whole word such as `return`)
36
+ let word = ''
37
+ const REGEX_AFTER_KEYWORD = /^(?:return|typeof|case|delete|void|instanceof|in|of|yield|await|new|do|else)$/
38
+ const regexAllowed = () => prev === '' || REGEX_AFTER_KEYWORD.test(prev) || /[(,=:[!&|?{};+\-*%~^<>]/.test(prev)
23
39
  while (i < n) {
24
40
  const c = s[i], c2 = s[i + 1]
25
41
  if (c === '/' && c2 === '/') { while (i < n && s[i] !== '\n') i++; continue }
26
42
  if (c === '/' && c2 === '*') { i += 2; while (i < n && !(s[i] === '*' && s[i + 1] === '/')) i++; i += 2; continue }
43
+ if (c === '/' && regexAllowed()) {
44
+ i++
45
+ let inClass = false
46
+ while (i < n) {
47
+ if (s[i] === '\\') { i += 2; continue }
48
+ if (s[i] === '[') inClass = true
49
+ else if (s[i] === ']') inClass = false
50
+ else if (s[i] === '/' && !inClass) { i++; break }
51
+ else if (s[i] === '\n') break // a regex literal cannot span lines
52
+ i++
53
+ }
54
+ while (i < n && /[a-z]/i.test(s[i])) i++ // flags
55
+ out += "''" // a value placeholder, like strings: keeps `X: <value>` shapes but no phantom calls
56
+ prev = ')'
57
+ continue
58
+ }
27
59
  if (c === "'" || c === '"' || c === '`') {
28
60
  const q = c
29
61
  i++
@@ -34,9 +66,12 @@ function stripNoise(s) {
34
66
  i++
35
67
  }
36
68
  out += q + q // keep a placeholder so `X: ''` shape survives
69
+ prev = ')'
37
70
  continue
38
71
  }
39
72
  out += c
73
+ if (/[A-Za-z0-9_$]/.test(c)) word += c
74
+ else { if (word) { prev = word; word = '' } if (!/\s/.test(c)) prev = c }
40
75
  i++
41
76
  }
42
77
  return out
@@ -370,6 +405,35 @@ notes.push('composition rows: ' + v5rows.length + '; non-v4 package rows: ' + v5
370
405
  }
371
406
  }
372
407
 
408
+ // ---- scanner self-check -------------------------------------------------
409
+ // The whole audit rests on stripNoise(); a scanner that mis-lexes silently BLINDS it (that is how the
410
+ // regex-with-a-quote bug lived here). Two checks, both cheap and both measured to be sensitive:
411
+ // · the stripped source must still PARSE (the pre-fix scanner produced a SyntaxError);
412
+ // · a fixture with a quoted character class + a following comment must survive intact.
413
+ {
414
+ const tmp = mkdtempSync(join(tmpdir(), 'v5-integrity-strip-'))
415
+ try {
416
+ const f = join(tmp, 'v5.mjs')
417
+ writeFileSync(f, src)
418
+ const r = spawnSync(process.execPath, ['--check', f], { encoding: 'utf8' })
419
+ if (r.status !== 0) {
420
+ findings.push('stripNoise() corrupted the source it scans (the stripped text does not parse) — every identifier check below is unreliable: ' +
421
+ String(r.stderr || '').split('\n').filter((l) => l.trim()).slice(-2).join(' ').slice(0, 160))
422
+ }
423
+ const fixture = [
424
+ 'function f(s) { return s.replace(/["\']/g, "-") }',
425
+ '// phantom_call( must not survive as a call site',
426
+ 'const div = 6 / 2',
427
+ ].join('\n')
428
+ const stripped = stripNoise(fixture)
429
+ if (stripped.includes('phantom_call')) findings.push('stripNoise() left a comment in the code stream (a phantom call site would be reported)')
430
+ if (!/'/.test(stripped)) findings.push('stripNoise() dropped a value placeholder')
431
+ notes.push('scanner self-check: stripped output parses=' + (r.status === 0) + '; quoted-class fixture=' + (!stripped.includes('phantom_call')))
432
+ } finally {
433
+ rmSync(tmp, { recursive: true, force: true })
434
+ }
435
+ }
436
+
373
437
  // ---- report ------------------------------------------------------------
374
438
  console.log('-- V5 integrity audit --')
375
439
  for (const n of notes) console.log(' note: ' + n)
@@ -14,9 +14,9 @@ node run-tests.mjs --concurrency=6 # 手动指定并发
14
14
  node audit-formal-sensitivity.mjs # 49 条不变式探针,并行(--concurrency=N / --only=<preset> / --list)
15
15
  node audit-persona-sensitivity.mjs # 11 条提示词面探针(串行,本身只要几秒)
16
16
  node audit-prompt-invariants.mjs # 静态:四套的提示词/工具面不变式 + 扫描器自检(< 0.1s)
17
- node audit-prompt-invariants.mjs --self-probe # 证明上面那 151 条不变式真的会变红(5 个自探针)
17
+ node audit-prompt-invariants.mjs --self-probe # 证明上面那 157 条不变式真的会变红(5 个自探针)
18
18
  node audit-spec-traceability.mjs # 静态:规格/README ↔ 代码可追溯(< 0.1s)
19
- node audit-v5-integrity.mjs # 静态:v5 完整性/理念门禁(≈3 s)
19
+ node audit-v5-integrity.mjs # 静态:v5 完整性/理念门禁 + 扫描器解析级自检(≈0.5 s)
20
20
  ```
21
21
 
22
22
  两个并行 runner 都会打印**每项耗时 + 汇总(wall / sum / speed-up / 最慢几项)**。跑完请读这几行。
@@ -28,10 +28,10 @@ node audit-v5-integrity.mjs # 静态:v5 完整性/理念门禁(
28
28
  | `run-tests.mjs`(23 个套件) | 221.5 s | **111.5 s**(并发 4,speed-up x1.99) | 关键路径 = `e2e-v4-fixes` 98.1 s |
29
29
  | `audit-formal-sensitivity.mjs`(49 探针) | 612.0 s | **154.6 s**(并发 4,speed-up x3.96) | 关键路径 = 12 个 v2 探针(每个 ≈32 s) |
30
30
  | `audit-persona-sensitivity.mjs`(11 探针) | ≈ 5 s | — | 本身很快,不需要并行 |
31
- | `audit-prompt-invariants.mjs`(151 条,含 X5–X7 扫描器自检) | 0.3 s | — | 静态 |
31
+ | `audit-prompt-invariants.mjs`(157 条,含 X5–X8b 扫描器自检) | 0.4 s | — | 静态 |
32
32
  | `audit-prompt-invariants.mjs --self-probe`(5 探针) | 1.5 s | — | 每个探针 = 一次自我重跑(0.3 s) |
33
33
  | `audit-spec-traceability.mjs`(94 条) | 0.3 s | — | 静态 |
34
- | `audit-v5-integrity.mjs` | ≈ 3 s | — | 静态审计 |
34
+ | `audit-v5-integrity.mjs` | ≈ 0.5 s | — | 静态审计(含扫描器自检) |
35
35
  | `prompt-v5-integrity.test.mjs` | 1.6 s | — | 虚拟时钟下生成 v5 语料(语料字节稳定) |
36
36
 
37
37
  > 优化前:全量回归 ≈ 5.5 min(串行,`formal-verify-v2` 单独 186 s);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-vibe-math",
3
3
  "description": "Multi-agent mathematical problem-solving & verification frameworks for DeepSeek Harness — FOUR agent presets in one install: vibe-math-v2 (probability-driven: qs.json + Propos knowledge base + explorer→solver→review/debate verdict), vibe-math-v3 (THIRD-generation, recommended: paper-style Markdown knowledge base with Problems/Progress/Propos/Methods/Verified + planner-agent scheduling that decides the next N actions + universal theory/method invention library + agents write their own Markdown directly via a per-file write lock), and vibe-math-v4 (FOURTH-generation: persistent self-organizing resident subagents that message & meet to decide all tasks, verify only by unanimous consensus, /compact at a context threshold, and stop only when all agree the problem is solved), and vibe-math-v5 (FIFTH-generation research institute: an academician as the organizational centre who decomposes and ASSIGNS work and chairs meetings; permanent researchers who hold the vote and may hire/fire their own temp workers; temp workers with no vote; a group chat and meetings; a durable per-recipient mailbox; a compare-and-set task DAG; and a boolean m-vote consensus rule where an object enters Verified/ only when at least m voting members agree AND every one of them returns exactly 1 or exactly 0). Installing this bundle auto-installs all four presets (v1 was removed at v2.0.0).",
4
- "version": "2.3.3",
4
+ "version": "2.3.5",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -39,6 +39,8 @@
39
39
  "RELEASE-NOTES-2.3.1.md",
40
40
  "RELEASE-NOTES-2.3.2.md",
41
41
  "RELEASE-NOTES-2.3.3.md",
42
+ "RELEASE-NOTES-2.3.4.md",
43
+ "RELEASE-NOTES-2.3.5.md",
42
44
  "selfdrive-v5.mjs",
43
45
  "示例图/框架图-v5.svg",
44
46
  "docs/架构图.md",
@@ -102,7 +104,7 @@
102
104
  },
103
105
  "minVersion": "0.1.2-rc.1",
104
106
  "testedVersion": "0.1.5-rc.2",
105
- "compatNote": "依赖宿主提供的 subagents/agents/tools/commands/fs 服务与 @deepseek-ai/dsh-* 插件行;可选 subprocess/sandboxPolicy/compaction。persona 行同时携带 prefix 与 text 两个键,以兼容 0.1.3-alpha.2 的 schema 更名(prefix 必填)与 0.1.2 及更早的 text 键。已在 dsh-v0.1.5-rc.2(@deepseek-ai/dsh-persona 0.1.5-rc.2)上逐行校验全部预设行并通过(v2/v3/v4;v1 已于 v2.0.0 移除)。注意:DSH 0.1.2 起 subagents.startContinuable 的 agentOptions/toolFilter 需要宿主 provider 声明对应 capability(spawn/fork 进程内 provider 均支持),安装器启动时会做能力自检并在旧版宿主上告警。2026 兼容性修复:v2/v3 工具权限名表原先硬编码 web/fetch/bash(未注册名会使 tools.restrict() 抛错、子代理无法建立),现按真实注册名并加带守卫的重试;v4 真实 /compact 原先在 subagent/end 里查 agents.get()(该事件触发时子代理已移出注册表,属死代码),现改为在 subagent/start 捕获 Agent 引用;三套预设的可选服务改为惰性读取,不再在 apply() 快照;v4 的 tools/commands 注册补入 ctx.effect;安装器自检新增 subprocess/sandboxPolicy/compaction。v2.1.0 新增 v5 研究所体系:状态存于宿主 host-only 会话投影单元(键 vibeMathV5),因此自检新增 sessionProjections/sessions(均为可选;缺失时 v5 回退到加固 JSON 状态文件)。v5 不依赖任何 npm 实验包,纯 preset 内单文件实现。v2.3.3 是 2.3.2 之后的确认轮(重新审计改动过的每一处:四套插件、四套套件、共享契约,以及审计脚本自身),修掉一处真实缺陷与两处同类/字面问题:① v2 的 formal.decision=used 回执**无条件**把记录写成 attempted —— 一句「这一轮碰了形式化」会把已经 passed 的对象在两个 id 空间上一起降级,而 proof 指针仍留着(记录自相矛盾),后果是后续审查提示词丢掉忠实性分支、require 档对一份已跑通的归档证明重新关门并把对象丢进形式化待办;现改为先取合并后的记录状态(formalGateRecord,两套 id 都认),passed/blocked 一律保留,只有从未尝试起步才写 attempted(与 v4 的 formalSetRun 缺陷同类,只是长在回执通道上且只长在一套里)。② v3 的 used 分支只保留 passed(prev.status === passed ? passed : attempted),会把 blocked 打回 attempted —— blocked 本身就是「门禁已放行」的记录,一句 used 就把门禁重新关上;现两者都保留,四套同构。③ 共享契约 docs/formal-verification.md §4 迁移表有两行与实现相反(used 行与两行 lean_run 都写成「→ attempted」),而规范文档写反最危险之处是下一次修改会照它写(2.3.2 修好 v2 后若只读契约就会把修复改回去);现已改正为「已是 passed/blocked 则保持原状」,并补上「lean_archive kind=proof 但运行失败 → attempted + 清空 proof + 撤回旧归档证明」这一行,四套实现方案与 v5 规格同步,audit-spec-traceability 增加 3 条字面校验(91 → 94)。④ 审计脚本自身的守卫加固:audit-prompt-invariants.mjs 的注释扫描器此前不认正则字面量,而四套源码都含一个「字符类里带双引号」的 sanitize 正则字面量,旧扫描器把那个引号当成字符串开头、此后整个文件词法状态都是错的(实测新旧逐行比对:v2 190 行 / v3 159 行 / v4 14 行 / v5 121 行不同,典型后果是真实代码行被当注释抹掉 → 真缺陷可能被 I1/I13/I14 漏看);现在扫描器支持正则字面量(字符类/转义/flags)并新增 X5–X7 三条自检(正则里的引号不得吞掉注释、字符串里的 // 必须保留、转义斜杠不得提前结束正则,且都保持行结构),修复前 X5 会变红。⑤ 行为断言补齐四套同构(此前只有 v4 断言「普通 run 不降级 passed」):四套各新增 used 回执不得降级已 passed 对象、不得把 blocked 打回 attempted、普通 lean_run 不得降级 passed,v2 还断言「used 之后 require 门禁仍放行(把该轮驱动到一致为真不会产生 formal-required 待办)」;本轮是先用断言复现(v2 修复前实测三条变红:got attempted),再修复。套件断言 v2 334 / v3 294 / v4 277 / v5 153 / prompt-v5-integrity 506,audit-prompt-invariants 151/0(--self-probe 5/5),audit-spec-traceability 94/0。v2.3.2 是对四个架构各做一轮深度审计 + 横向同构对照后的修复版(无破坏性变更,默认仍为 off),修掉三处会让「严格验证」失效或不可用的高危缺陷:① v3 的四个 Lean 参数从未写进 vibe_math_set_params 的参数 schema(该 schema 是 additionalProperties:false,遵守 schema 的 provider 会拒绝这个调用)→ 用户永远无法开启该功能,而套件全绿(它直接调 handler、绕过 schema);② v2 的 require 门禁只读验证侧自己的 id,而代理用对象 id 归档、别名同步只更新已存在的键 → 「归档了 passed、验证侧还没有记录」时门禁永远搁置,搁置本身又写下 rId=none,于是每轮重开一次辩论、对象永远无法定论并饿死其它对象;③ v4 的 formalSetRun 硬编码 status=attempted,把已验证对象的 passed 抹掉(与注释、规格、v2/v5 都矛盾)→ 一次随手 lean_run 就让对象丢掉「已形式化」,忠实性分支消失、require 档对已有绿色证明的对象重新关门。另修 v2 的一处门禁旁路(settleVerdict 的「判断命题」转移在 v=0 时直接写 布尔估计=0/已验证/优先级 never 并压入 正确概率:1 条目,完全不看门禁)。撤回语义按 v5 的正确做法统一四套:撤回归档证明时先删、再用 fs 复核文件真的没了、仍在则就地覆盖为撤回说明,并如实公告是哪一种(含两者都失败的告警);lean_archive kind=proof 跑红时不再保留 prev.proof(proof 只属于 passed)并撤回旧的归档证明。提示词/交互修复:忠实性分支改为按档位承诺(只有 require 有门禁,encourage 明确写「本档没有门禁:请务必给弃权值」);无 Lean 工具链的出路同时点名 LEAN_NOT_FOUND 与 NO_SUBPROCESS;v3 的失败提示不再让人去看并不存在的编译器输出;v4 活动日志里的缩写工具名改为注册名(并纳入注入文本扫描);v2 的工作轮不再把可复用引理指向项目内不存在的 Formal/Proved/;v2 的 kind=def/lemma 跑红不再声称可复用;v3 setup 的重复 plannerPersona 去重;v4 规格里的幽灵工具 vibe_v4_propose_verify 修正为回执字段。语料确定性:v5 语料此前的非确定有三层根因(心跳/会议依赖真实时钟与异步顺序、最闲成员抖动、写入端只按 kind 排序),现由套件虚拟时钟 + 单成员研究所内捕捉心跳 + 写入端全序排序(kind→owner→prompt)修复,连跑 6 次字节一致且套件 7–9 s → 1.7 s;v3 的 20 条 planner:* 随机 plan id 与 epoch 时间戳一并 scrub。新增两个随包发布的常驻守卫:audit-prompt-invariants.mjs(四套 × 26 条静态不变式 + 3 条跨套检查,把历次真实发生过的提示词缺陷类别编码住,当前 121/0)与 audit-spec-traceability.mjs(规格/README 承诺的工具必须真的注册,能识别「文档里说它不存在」的否定语境;四个 Lean 参数必须同时被文档与代码接受;契约 §7 的 terminate();契约 §8 的门禁收口点无旁路,当前 91/0),以及 run-tests.mjs(并行跑全部套件并打印耗时/加速比/最慢项,修掉 --only x 空格形式被静默忽略与 --json 混入人类输出两个 bug)。AUDIT-CHECKLIST.md 新增 §1.8「四套同构:任何语义修正必须四套同步」——本轮三处高危里有两处正是改一套或四套同写法却无人横向对照造成的。本轮最贵的一处缺陷还暴露出一整类既有测试全都盲的漏洞:四个预设的工具 schema 都由 objParams 以 additionalProperties:false 关闭,schema 没列出的键会被遵守 schema 的 provider 直接拒绝,而提示词/规格/状态行可以全都在说这个参数、套件也可以全绿(套件直接调 handler、绕过 schema)——因此新增三重守卫:① 四套各自的 formal-verify-vN 套件现在直接检查**真实注册的** schema 对象(封闭性 + 四个 Lean 参数 + formalVerify 的 enum 恰好三档,已用探针证明:去掉 v3 真实注册那份的 leanArgs 立刻变红);② audit-prompt-invariants.mjs 新增 I13(每一处 set 工具定义都必须声明这四个参数,且每一份 objParams 都必须关闭 schema);③ 新增 I14(schema 声明的每个键都必须被参数层真正接收:v2/v3 的闸门是 DEFAULT_PARAMS 键集、v4 是 k in params、v5 是 normalizeParams 的类型列表——声明而不接收 = 调用返回 ok:true 却什么都不发生)。该脚本同时新增 --self-probe:在内存里注入这些缺陷形状,要求对应不变式变红、未变异的对照跑仍为绿(5/5),现为 145 条不变式。套件断言 v2 319 / v3 283 / v4 269 / v5 145 / prompt-v5-integrity 506,e2e-v4-fixes 修掉并行下的抖动(T21 会议看门狗 80 ms 在 CPU 争用下提前放弃会议);全量并行回归连续 3 次 23/23 全绿(最新实测 wall 111.5 s / sum 221.5 s / x1.99)。v2.3.1 是审计驱动的提示词/交互修复版(无破坏性变更,默认仍为 off):① 忠实性缺陷不再被记成「命题为假」——新增回执取值 decision='defect'(表决者发现 Lean 代码与命题原文不一致时不得投 0,给中间值并记录具体偏差;框架随即把该对象降级为 attempted、清空 proof、撤回归档证明 Verified/Lean/<id>.lean、写入 Formal/TODO.md,require 档下本次裁定不定论),encourage 档不承诺它无法强制的搁置;② 修复 v2 的 formal 回执通道是死代码(提示词要求写进回执、契约里却没有该字段、框架也从不解析)——补齐 formalJsonField/formalReplyNote/absorbFormalFromReply 并接进初评与辩论两条路径,套件改为行为断言而非措辞断言;③ 修复 v2/v3 忠实性分支的字段名错误(写成 verdict,真实字段是 Result,会导致该票被静默丢弃);④ 注入文本里的工具名一律改为注册名全称(v2/v3/v5 原先出现 lean_lib/lean_archive 缩写,含工具自身返回的 hint);⑤ 新增「归档可复用定义/引理前先跑通」与「宿主无 Lean 工具链(LEAN_NOT_FOUND)时把代码归档并在 note 写明,算显式阻塞原因」两条硬要求;⑥ 四套各自新增随包发布的人工复核语料 prompt-corpus-vN/(覆盖 off/encourage/require/忠实性/工作轮/回执契约),并修复 v5 语料路径归一化在 Windows 大小写差异下漏掉 VibeMath 根绝对路径、导致语料不确定且泄露本机路径的问题;⑦ 新增 16 条提示词灵敏度探针(删掉「不要投 0」、工具名换缩写、删掉 require 门禁措辞、回执契约去掉 defect,各四套),全套件断言 v2 261 / v3 247 / v4 226 / v5 120 / prompt-v5-integrity 588。v2.3.0 为四个架构新增可调控的 Lean 形式化验证(参数 formalVerify = off/encourage/require,默认 off):验证时按实现难度决定是否用 Lean 形式化(写代码+执行),一旦通过则审查对象从「推导是否正确」变成「Lean 的定义/对象/条件/假设/结论是否忠实于命题原文」;形式化代码归档为命题的证明(Verified/Lean/<id>.lean),可复用定义与已证引理归档到跨项目的 VibeMath/Formal/{Lib,Proved}/。require 档带门禁:真/假结论必须先有 Lean 通过或显式阻塞记录,否则记为未定论并进入形式化待办。共用契约 docs/formal-verification.md,四套各带 formal-verify-vN 套件(v2 177 / v3 189 / v4 144 / v5 88 断言)与 audit-formal-sensitivity.mjs 探针。同一次审计还发现并修复了一整类**静态提示词面**缺陷(persona ↔ 工具注册表,既有套件全部盲):v2/v3/v4 的 persona 从未列出无条件注册的三个 *_lean_* 工具,v4 的 vibe_v4_set 参数表漏了 formalVerify/leanCommand/leanArgs/leanTimeoutMs,v3 漏了 setup/save_settings/template,v4 漏了 vibe_v4_prompts,v5 漏了增删常驻研究员的工具、且 prefix 与 text 两个块存在文字漂移;现由 audit-persona-surface.test.mjs(197 断言:双向一致性 + 未文档化工具显式快照 + prefix/text 逐行一致 + 斜杠命令 hint/usage/实际分支三处一致 + Lean 参数/档位/路径,并生成随包发布的 prompt-corpus-persona/ 人读语料)与 audit-persona-sensitivity.mjs(11 条探针,含「未变异副本必须为绿」的对照)守护,AUDIT-CHECKLIST.md 新增 §1.6。v2.2.2 新增 v5 架构图(示例图/框架图-v5.svg + docs/generate_framework_diagram_v5.mjs 零依赖 Node 生成器 + vibe-math-v5/架构图.md 全套 Mermaid 细节图),并修复在绘制架构图时暴露的真实缺陷:会议进行中提出的验证会并发启动(会议与验证的互斥此前只做了单向),现改为排队。v2.2.1 把「全面检查必查清单」(AUDIT-CHECKLIST.md) 作为随包强制流程发布,提示词/交互正确性列为第一优先审计维度。v2.2.0 修复实测发现的提示词身份错乱:状态块改为显式接收它所描述的成员,创建成员时先落盘进编制再构造入职提示词,章程快照冻结在入职时,重建会话不再自称“刚入职”,所办调用不再被误判成某位研究员,框架反馈改为独立发送者投递,一次提示词不再重复投递同一条消息,并新增 prompt-v5-integrity 提示词完整性套件 + 可人工复核的提示词语料(随包发布)。",
107
+ "compatNote": "v2.3.5 是 round 5:把\"注释/字符串扫描器\"这一类缺陷追到所有自带扫描器的脚本上。audit-v5-integrity.mjs(静态自检:调用了但未定义的函数 / 未声明的 params.X 读取 / 会话 API 上不存在的方法 / 遗留开发标记)的 stripNoise() 完全不认正则字面量,而 v5 源码里有一个字符类带双引号的 sanitize 正则,那个引号被当成字符串开头。实测(旧 vs 新逐行比对,v5 共 4365 行):30 行不一致(都是含\"字符类里有引号\"的正则的行),抹除后的文本无法通过 node --check;本次没有任何调用名 / params.* 读取 / s.*() 方法被漏掉,所以是潜在盲区而非已发生的漏检 —— 但只要某个被检查的标识符恰好只出现在这种行上就会完全隐形。现把 stripNoise() 升级为与 audit-prompt-invariants.mjs 同一套词法处理(正则字面量 + 字符类 + 转义 + flags + 关键字规则),正则与字符串统一替换为值占位符;并给该审计加上解析级自检:抹除后的 v5 源码必须仍能被 node --check 解析,且\"引号在字符类里的正则 + 紧随其后的注释\"夹具必须完好。灵敏度实测:把旧扫描器放回去,审计立刻报 stripNoise() corrupted the source it scans(exit 1),换回新版则 clean;39 条 v5 灵敏度探针仍全红。(过程教训:第一版测量用\"文件里下一个引号\"估算受影响区域,得出\"402 行 / 20 个函数\"的错误结论;该扫描器遇到换行即结束字符串,真实影响是 30 行。发布前逐行复核才发现并改正 —— 先量准再下结论。)v2.3.4 是 round 4:只审上一轮改动过的东西(2.3.3 修好的注释扫描器 —— 它是 I1/I13/I14 三条不变式的地基 —— 与它新增的 X5–X7 自检)。扫描器还剩一个盲区:判据\"这个斜杠是正则还是除法\"只看前一个字符,于是关键字后面的正则被读成除法 —— 而 v3 源码里真有 return /^\\s*import\\s+/.test(l) 与 return /\\n$/.test(out) 两处;这两处字符类里恰好没有引号所以今天还没坏,但只要有人在 return /…/ 的正则里写一个引号(如 return /[\"']/.test(s)),扫描器就会把那个引号当成字符串开头、此后整个文件词法状态都是错的,又回到 2.3.2 修掉的坑。现把判据从\"前一个字符\"升级为\"前一个记号\":除运算符/开括号外,return/typeof/case/delete/void/instanceof/in/of/yield/await/new/do/else 之后也按正则解析,标识符、右括号、右方括号之后仍按除法。并新增解析级判据 X8/X8b:X8 要求四套源码\"抹掉注释\"之后的产物仍能被 node --check 解析(扫描器读错代码时产物往往直接语法错误);X8b 是专测关键字规则的夹具(return /[\"']/.test(l) + 紧随其后的注释)。灵敏度已实测:2.3.2 的扫描器让四个预设全部 SyntaxError(X8 红),2.3.3 的扫描器 X8 绿但 X8b 红(紧随其后的注释没被抹掉),当前版本两者皆绿 —— 即 X8 守住大回退、X8b 守住这个窄口子,2.3.3 的扫描器会被 X8b 判红。不变式 151 → 157。本版只改随包发布的审计脚本与文档,四套预设字节未变。依赖宿主提供的 subagents/agents/tools/commands/fs 服务与 @deepseek-ai/dsh-* 插件行;可选 subprocess/sandboxPolicy/compaction。persona 行同时携带 prefix 与 text 两个键,以兼容 0.1.3-alpha.2 的 schema 更名(prefix 必填)与 0.1.2 及更早的 text 键。已在 dsh-v0.1.5-rc.2(@deepseek-ai/dsh-persona 0.1.5-rc.2)上逐行校验全部预设行并通过(v2/v3/v4;v1 已于 v2.0.0 移除)。注意:DSH 0.1.2 起 subagents.startContinuable 的 agentOptions/toolFilter 需要宿主 provider 声明对应 capability(spawn/fork 进程内 provider 均支持),安装器启动时会做能力自检并在旧版宿主上告警。2026 兼容性修复:v2/v3 工具权限名表原先硬编码 web/fetch/bash(未注册名会使 tools.restrict() 抛错、子代理无法建立),现按真实注册名并加带守卫的重试;v4 真实 /compact 原先在 subagent/end 里查 agents.get()(该事件触发时子代理已移出注册表,属死代码),现改为在 subagent/start 捕获 Agent 引用;三套预设的可选服务改为惰性读取,不再在 apply() 快照;v4 的 tools/commands 注册补入 ctx.effect;安装器自检新增 subprocess/sandboxPolicy/compaction。v2.1.0 新增 v5 研究所体系:状态存于宿主 host-only 会话投影单元(键 vibeMathV5),因此自检新增 sessionProjections/sessions(均为可选;缺失时 v5 回退到加固 JSON 状态文件)。v5 不依赖任何 npm 实验包,纯 preset 内单文件实现。v2.3.3 是 2.3.2 之后的确认轮(重新审计改动过的每一处:四套插件、四套套件、共享契约,以及审计脚本自身),修掉一处真实缺陷与两处同类/字面问题:① v2 的 formal.decision=used 回执**无条件**把记录写成 attempted —— 一句「这一轮碰了形式化」会把已经 passed 的对象在两个 id 空间上一起降级,而 proof 指针仍留着(记录自相矛盾),后果是后续审查提示词丢掉忠实性分支、require 档对一份已跑通的归档证明重新关门并把对象丢进形式化待办;现改为先取合并后的记录状态(formalGateRecord,两套 id 都认),passed/blocked 一律保留,只有从未尝试起步才写 attempted(与 v4 的 formalSetRun 缺陷同类,只是长在回执通道上且只长在一套里)。② v3 的 used 分支只保留 passed(prev.status === passed ? passed : attempted),会把 blocked 打回 attempted —— blocked 本身就是「门禁已放行」的记录,一句 used 就把门禁重新关上;现两者都保留,四套同构。③ 共享契约 docs/formal-verification.md §4 迁移表有两行与实现相反(used 行与两行 lean_run 都写成「→ attempted」),而规范文档写反最危险之处是下一次修改会照它写(2.3.2 修好 v2 后若只读契约就会把修复改回去);现已改正为「已是 passed/blocked 则保持原状」,并补上「lean_archive kind=proof 但运行失败 → attempted + 清空 proof + 撤回旧归档证明」这一行,四套实现方案与 v5 规格同步,audit-spec-traceability 增加 3 条字面校验(91 → 94)。④ 审计脚本自身的守卫加固:audit-prompt-invariants.mjs 的注释扫描器此前不认正则字面量,而四套源码都含一个「字符类里带双引号」的 sanitize 正则字面量,旧扫描器把那个引号当成字符串开头、此后整个文件词法状态都是错的(实测新旧逐行比对:v2 190 行 / v3 159 行 / v4 14 行 / v5 121 行不同,典型后果是真实代码行被当注释抹掉 → 真缺陷可能被 I1/I13/I14 漏看);现在扫描器支持正则字面量(字符类/转义/flags)并新增 X5–X7 三条自检(正则里的引号不得吞掉注释、字符串里的 // 必须保留、转义斜杠不得提前结束正则,且都保持行结构),修复前 X5 会变红。⑤ 行为断言补齐四套同构(此前只有 v4 断言「普通 run 不降级 passed」):四套各新增 used 回执不得降级已 passed 对象、不得把 blocked 打回 attempted、普通 lean_run 不得降级 passed,v2 还断言「used 之后 require 门禁仍放行(把该轮驱动到一致为真不会产生 formal-required 待办)」;本轮是先用断言复现(v2 修复前实测三条变红:got attempted),再修复。套件断言 v2 334 / v3 294 / v4 277 / v5 153 / prompt-v5-integrity 506,audit-prompt-invariants 151/0(--self-probe 5/5),audit-spec-traceability 94/0。v2.3.2 是对四个架构各做一轮深度审计 + 横向同构对照后的修复版(无破坏性变更,默认仍为 off),修掉三处会让「严格验证」失效或不可用的高危缺陷:① v3 的四个 Lean 参数从未写进 vibe_math_set_params 的参数 schema(该 schema 是 additionalProperties:false,遵守 schema 的 provider 会拒绝这个调用)→ 用户永远无法开启该功能,而套件全绿(它直接调 handler、绕过 schema);② v2 的 require 门禁只读验证侧自己的 id,而代理用对象 id 归档、别名同步只更新已存在的键 → 「归档了 passed、验证侧还没有记录」时门禁永远搁置,搁置本身又写下 rId=none,于是每轮重开一次辩论、对象永远无法定论并饿死其它对象;③ v4 的 formalSetRun 硬编码 status=attempted,把已验证对象的 passed 抹掉(与注释、规格、v2/v5 都矛盾)→ 一次随手 lean_run 就让对象丢掉「已形式化」,忠实性分支消失、require 档对已有绿色证明的对象重新关门。另修 v2 的一处门禁旁路(settleVerdict 的「判断命题」转移在 v=0 时直接写 布尔估计=0/已验证/优先级 never 并压入 正确概率:1 条目,完全不看门禁)。撤回语义按 v5 的正确做法统一四套:撤回归档证明时先删、再用 fs 复核文件真的没了、仍在则就地覆盖为撤回说明,并如实公告是哪一种(含两者都失败的告警);lean_archive kind=proof 跑红时不再保留 prev.proof(proof 只属于 passed)并撤回旧的归档证明。提示词/交互修复:忠实性分支改为按档位承诺(只有 require 有门禁,encourage 明确写「本档没有门禁:请务必给弃权值」);无 Lean 工具链的出路同时点名 LEAN_NOT_FOUND 与 NO_SUBPROCESS;v3 的失败提示不再让人去看并不存在的编译器输出;v4 活动日志里的缩写工具名改为注册名(并纳入注入文本扫描);v2 的工作轮不再把可复用引理指向项目内不存在的 Formal/Proved/;v2 的 kind=def/lemma 跑红不再声称可复用;v3 setup 的重复 plannerPersona 去重;v4 规格里的幽灵工具 vibe_v4_propose_verify 修正为回执字段。语料确定性:v5 语料此前的非确定有三层根因(心跳/会议依赖真实时钟与异步顺序、最闲成员抖动、写入端只按 kind 排序),现由套件虚拟时钟 + 单成员研究所内捕捉心跳 + 写入端全序排序(kind→owner→prompt)修复,连跑 6 次字节一致且套件 7–9 s → 1.7 s;v3 的 20 条 planner:* 随机 plan id 与 epoch 时间戳一并 scrub。新增两个随包发布的常驻守卫:audit-prompt-invariants.mjs(四套 × 26 条静态不变式 + 3 条跨套检查,把历次真实发生过的提示词缺陷类别编码住,当前 121/0)与 audit-spec-traceability.mjs(规格/README 承诺的工具必须真的注册,能识别「文档里说它不存在」的否定语境;四个 Lean 参数必须同时被文档与代码接受;契约 §7 的 terminate();契约 §8 的门禁收口点无旁路,当前 91/0),以及 run-tests.mjs(并行跑全部套件并打印耗时/加速比/最慢项,修掉 --only x 空格形式被静默忽略与 --json 混入人类输出两个 bug)。AUDIT-CHECKLIST.md 新增 §1.8「四套同构:任何语义修正必须四套同步」——本轮三处高危里有两处正是改一套或四套同写法却无人横向对照造成的。本轮最贵的一处缺陷还暴露出一整类既有测试全都盲的漏洞:四个预设的工具 schema 都由 objParams 以 additionalProperties:false 关闭,schema 没列出的键会被遵守 schema 的 provider 直接拒绝,而提示词/规格/状态行可以全都在说这个参数、套件也可以全绿(套件直接调 handler、绕过 schema)——因此新增三重守卫:① 四套各自的 formal-verify-vN 套件现在直接检查**真实注册的** schema 对象(封闭性 + 四个 Lean 参数 + formalVerify 的 enum 恰好三档,已用探针证明:去掉 v3 真实注册那份的 leanArgs 立刻变红);② audit-prompt-invariants.mjs 新增 I13(每一处 set 工具定义都必须声明这四个参数,且每一份 objParams 都必须关闭 schema);③ 新增 I14(schema 声明的每个键都必须被参数层真正接收:v2/v3 的闸门是 DEFAULT_PARAMS 键集、v4 是 k in params、v5 是 normalizeParams 的类型列表——声明而不接收 = 调用返回 ok:true 却什么都不发生)。该脚本同时新增 --self-probe:在内存里注入这些缺陷形状,要求对应不变式变红、未变异的对照跑仍为绿(5/5),现为 145 条不变式。套件断言 v2 319 / v3 283 / v4 269 / v5 145 / prompt-v5-integrity 506,e2e-v4-fixes 修掉并行下的抖动(T21 会议看门狗 80 ms 在 CPU 争用下提前放弃会议);全量并行回归连续 3 次 23/23 全绿(最新实测 wall 111.5 s / sum 221.5 s / x1.99)。v2.3.1 是审计驱动的提示词/交互修复版(无破坏性变更,默认仍为 off):① 忠实性缺陷不再被记成「命题为假」——新增回执取值 decision='defect'(表决者发现 Lean 代码与命题原文不一致时不得投 0,给中间值并记录具体偏差;框架随即把该对象降级为 attempted、清空 proof、撤回归档证明 Verified/Lean/<id>.lean、写入 Formal/TODO.md,require 档下本次裁定不定论),encourage 档不承诺它无法强制的搁置;② 修复 v2 的 formal 回执通道是死代码(提示词要求写进回执、契约里却没有该字段、框架也从不解析)——补齐 formalJsonField/formalReplyNote/absorbFormalFromReply 并接进初评与辩论两条路径,套件改为行为断言而非措辞断言;③ 修复 v2/v3 忠实性分支的字段名错误(写成 verdict,真实字段是 Result,会导致该票被静默丢弃);④ 注入文本里的工具名一律改为注册名全称(v2/v3/v5 原先出现 lean_lib/lean_archive 缩写,含工具自身返回的 hint);⑤ 新增「归档可复用定义/引理前先跑通」与「宿主无 Lean 工具链(LEAN_NOT_FOUND)时把代码归档并在 note 写明,算显式阻塞原因」两条硬要求;⑥ 四套各自新增随包发布的人工复核语料 prompt-corpus-vN/(覆盖 off/encourage/require/忠实性/工作轮/回执契约),并修复 v5 语料路径归一化在 Windows 大小写差异下漏掉 VibeMath 根绝对路径、导致语料不确定且泄露本机路径的问题;⑦ 新增 16 条提示词灵敏度探针(删掉「不要投 0」、工具名换缩写、删掉 require 门禁措辞、回执契约去掉 defect,各四套),全套件断言 v2 261 / v3 247 / v4 226 / v5 120 / prompt-v5-integrity 588。v2.3.0 为四个架构新增可调控的 Lean 形式化验证(参数 formalVerify = off/encourage/require,默认 off):验证时按实现难度决定是否用 Lean 形式化(写代码+执行),一旦通过则审查对象从「推导是否正确」变成「Lean 的定义/对象/条件/假设/结论是否忠实于命题原文」;形式化代码归档为命题的证明(Verified/Lean/<id>.lean),可复用定义与已证引理归档到跨项目的 VibeMath/Formal/{Lib,Proved}/。require 档带门禁:真/假结论必须先有 Lean 通过或显式阻塞记录,否则记为未定论并进入形式化待办。共用契约 docs/formal-verification.md,四套各带 formal-verify-vN 套件(v2 177 / v3 189 / v4 144 / v5 88 断言)与 audit-formal-sensitivity.mjs 探针。同一次审计还发现并修复了一整类**静态提示词面**缺陷(persona ↔ 工具注册表,既有套件全部盲):v2/v3/v4 的 persona 从未列出无条件注册的三个 *_lean_* 工具,v4 的 vibe_v4_set 参数表漏了 formalVerify/leanCommand/leanArgs/leanTimeoutMs,v3 漏了 setup/save_settings/template,v4 漏了 vibe_v4_prompts,v5 漏了增删常驻研究员的工具、且 prefix 与 text 两个块存在文字漂移;现由 audit-persona-surface.test.mjs(197 断言:双向一致性 + 未文档化工具显式快照 + prefix/text 逐行一致 + 斜杠命令 hint/usage/实际分支三处一致 + Lean 参数/档位/路径,并生成随包发布的 prompt-corpus-persona/ 人读语料)与 audit-persona-sensitivity.mjs(11 条探针,含「未变异副本必须为绿」的对照)守护,AUDIT-CHECKLIST.md 新增 §1.6。v2.2.2 新增 v5 架构图(示例图/框架图-v5.svg + docs/generate_framework_diagram_v5.mjs 零依赖 Node 生成器 + vibe-math-v5/架构图.md 全套 Mermaid 细节图),并修复在绘制架构图时暴露的真实缺陷:会议进行中提出的验证会并发启动(会议与验证的互斥此前只做了单向),现改为排队。v2.2.1 把「全面检查必查清单」(AUDIT-CHECKLIST.md) 作为随包强制流程发布,提示词/交互正确性列为第一优先审计维度。v2.2.0 修复实测发现的提示词身份错乱:状态块改为显式接收它所描述的成员,创建成员时先落盘进编制再构造入职提示词,章程快照冻结在入职时,重建会话不再自称“刚入职”,所办调用不再被误判成某位研究员,框架反馈改为独立发送者投递,一次提示词不再重复投递同一条消息,并新增 prompt-v5-integrity 提示词完整性套件 + 可人工复核的提示词语料(随包发布)。",
106
108
  "compatibility": {
107
109
  "dshReleases": {
108
110
  "0.1.2-alpha.4": "compatible",