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.
Files changed (101) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +682 -682
  3. package/README.md +683 -683
  4. package/docs/AUDIT-CHECKLIST.md +755 -744
  5. package/docs/COMPAT-AUDIT-ROUND2.md +351 -351
  6. package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
  7. package/docs/audit-methodology.md +93 -0
  8. package/docs/final-paper.md +172 -172
  9. package/docs/formal-verification.md +613 -588
  10. package/docs/generate_framework_diagram_v2.py +114 -114
  11. package/docs/generate_framework_diagram_v3.py +127 -127
  12. package/docs/math-computation.md +195 -194
  13. package/docs/release-notes/README.en.md +1 -0
  14. package/docs/release-notes/README.md +1 -0
  15. package/docs/release-notes/RELEASE-NOTES-2.0.22.md +112 -112
  16. package/docs/release-notes/RELEASE-NOTES-2.1.0.md +143 -143
  17. package/docs/release-notes/RELEASE-NOTES-2.2.0.md +266 -266
  18. package/docs/release-notes/RELEASE-NOTES-2.2.1.md +43 -43
  19. package/docs/release-notes/RELEASE-NOTES-2.2.2.md +88 -88
  20. package/docs/release-notes/RELEASE-NOTES-2.3.0.md +207 -207
  21. package/docs/release-notes/RELEASE-NOTES-2.3.1.md +134 -134
  22. package/docs/release-notes/RELEASE-NOTES-2.3.10.md +105 -105
  23. package/docs/release-notes/RELEASE-NOTES-2.3.11.md +57 -57
  24. package/docs/release-notes/RELEASE-NOTES-2.3.12.md +80 -80
  25. package/docs/release-notes/RELEASE-NOTES-2.3.2.md +145 -145
  26. package/docs/release-notes/RELEASE-NOTES-2.3.3.md +115 -115
  27. package/docs/release-notes/RELEASE-NOTES-2.3.4.md +69 -69
  28. package/docs/release-notes/RELEASE-NOTES-2.3.5.md +63 -63
  29. package/docs/release-notes/RELEASE-NOTES-2.3.6.md +66 -66
  30. package/docs/release-notes/RELEASE-NOTES-2.3.7.md +59 -59
  31. package/docs/release-notes/RELEASE-NOTES-2.3.8.md +45 -45
  32. package/docs/release-notes/RELEASE-NOTES-2.3.9.md +70 -70
  33. package/docs/release-notes/RELEASE-NOTES-2.8.0.en.md +65 -0
  34. package/docs/release-notes/RELEASE-NOTES-2.8.0.md +65 -0
  35. package/docs/release-playbook.md +103 -0
  36. package/docs/slv-playbook.md +117 -0
  37. package/docs/test-timing.md +158 -157
  38. package/docs//346/236/266/346/236/204/345/233/276.md +77 -77
  39. package/installer.js +792 -792
  40. package/package.json +13 -3
  41. package/prompt-corpus-v2/formal-verify-v2.json +2 -2
  42. package/prompt-corpus-v2/formal-verify-v2.md +4 -0
  43. package/prompt-corpus-v3/formal-verify-v3.json +7 -7
  44. package/prompt-corpus-v3/formal-verify-v3.md +14 -0
  45. package/prompt-corpus-v5/prompt-corpus-v5.json +61 -61
  46. package/prompt-corpus-v5/prompt-corpus-v5.md +122 -0
  47. package/scripts/clean-temp.mjs +234 -0
  48. package/tests/audit-engine-faces.mjs +298 -0
  49. package/tests/audit-formal-sensitivity.mjs +345 -342
  50. package/tests/audit-installer-compat.test.mjs +256 -256
  51. package/tests/audit-math-computation-sensitivity.mjs +1 -1
  52. package/tests/audit-persona-sensitivity.mjs +323 -323
  53. package/tests/audit-persona-surface.test.mjs +431 -431
  54. package/tests/audit-prompt-duplication.mutants.mjs +3 -2
  55. package/tests/audit-prompt-invariants.mjs +589 -546
  56. package/tests/audit-registration.mjs +78 -78
  57. package/tests/audit-spec-traceability.mjs +193 -193
  58. package/tests/audit-status-report-fields.mjs +172 -172
  59. package/tests/audit-v5-integrity.mjs +795 -795
  60. package/tests/audit-v5-sensitivity.mjs +433 -432
  61. package/tests/e2e-v5-round2.test.mjs +2453 -2426
  62. package/tests/formal-verify-v2.test.mjs +1806 -1800
  63. package/tests/formal-verify-v3.test.mjs +1757 -1750
  64. package/tests/formal-verify-v4.test.mjs +2114 -2106
  65. package/tests/formal-verify-v5.test.mjs +774 -767
  66. package/tests/math-computation-discovery.mutants.mjs +3 -3
  67. package/tests/math-computation-shared.test.mjs +122 -5
  68. package/tests/math-computation-v2.test.mjs +14 -2
  69. package/tests/math-computation-v3.test.mjs +14 -2
  70. package/tests/math-computation-v4.test.mjs +739 -739
  71. package/tests/prompt-v5-integrity.test.mjs +1291 -1291
  72. package/tests/run-tests.mjs +393 -332
  73. package/tests/selfdrive-v5.mjs +560 -560
  74. package/tests/temp-hygiene.mutants.mjs +80 -0
  75. package/tests/v2-fix-probes.mutants.mjs +4 -0
  76. package/tests/v2-fix-probes.test.mjs +1034 -1033
  77. package/tests/v2v3-interrupt.mutants.mjs +111 -0
  78. package/tests/v3-fix-probes.mutants.mjs +99 -95
  79. package/tests/v3-fix-probes.test.mjs +891 -890
  80. package/tests/v4-final-paper.mutants.mjs +108 -0
  81. package/tests/v5-institute-fixes.mutants.mjs +218 -190
  82. package/vibe-math-v2/agent.cordis.yml +376 -376
  83. package/vibe-math-v2/math-computation.js +17 -15
  84. package/vibe-math-v2/preset.yml +2 -2
  85. package/vibe-math-v2/v2/347/232/204/351/242/235/345/244/226/350/246/201/346/261/202.txt +21 -21
  86. package/vibe-math-v2/vibe-math-v2.js +5078 -5011
  87. package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +486 -486
  88. package/vibe-math-v3/agent.cordis.yml +420 -420
  89. package/vibe-math-v3/math-computation.js +17 -15
  90. package/vibe-math-v3/preset.yml +2 -2
  91. package/vibe-math-v3/vibe-math-v3.js +6215 -6149
  92. package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +649 -649
  93. package/vibe-math-v4/agent.cordis.yml +438 -438
  94. package/vibe-math-v4/math-computation.js +17 -15
  95. package/vibe-math-v4/preset.yml +2 -2
  96. package/vibe-math-v4/vibe-math-v4.js +4490 -4422
  97. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1015 -1015
  98. package/vibe-math-v5/math-computation.js +17 -15
  99. package/vibe-math-v5/vibe-math-v5.js +93 -11
  100. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1728
  101. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
@@ -0,0 +1,111 @@
1
+ // Registered mutant harness for the v2/v3 math-computation suites (task-14 / SLV probe P3): the suites'
2
+ // §19 D1 section already covers "unknown child" and "empty childId" — this family makes the missing one
3
+ // executable: the "host REFUSES to interrupt" branch must keep its NAMED code and its actionable `next`.
4
+ //
5
+ // Measured traps reused from tests/math-computation-v4.mutants.mjs:
6
+ // · the seam must point at the PLUGIN file (`MC_V2_PLUGIN` / `MC_V3_PLUGIN`), and the copied graph must
7
+ // include the plugin's local dependencies, or the suite dies before reaching any assertion;
8
+ // · an anchor that matches a different number of times than expected is a SETUP failure, not a red.
9
+ //
10
+ // Usage: node tests/v2v3-interrupt.mutants.mjs
11
+ import { readFileSync, writeFileSync, mkdirSync, copyFileSync, rmSync, readdirSync } from 'node:fs'
12
+ import { execFileSync } from 'node:child_process'
13
+ import { dirname, join } from 'node:path'
14
+ import { tmpdir } from 'node:os'
15
+ import { fileURLToPath } from 'node:url'
16
+
17
+ const HERE = dirname(fileURLToPath(import.meta.url))
18
+ const REPO = join(HERE, '..')
19
+ const CHILD_TIMEOUT_MS = Number(process.env.MUTANT_CHILD_TIMEOUT_MS || 300000)
20
+ const TIMES = []
21
+ const hangs = []
22
+ const skipped = []
23
+
24
+ function copyGraph(preset, file, dest) {
25
+ const src = join(REPO, preset, file)
26
+ copyFileSync(src, join(dest, file))
27
+ for (const m of readFileSync(src, 'utf8').matchAll(/from\s+'(\.\/[A-Za-z0-9_.-]+\.js)'/g)) {
28
+ const dep = m[1].slice(2)
29
+ if (!readdirSync(dest).includes(dep)) copyGraph(preset, dep, dest)
30
+ }
31
+ }
32
+
33
+ function runFamily(f) {
34
+ const dest = join(tmpdir(), f.preset.replace(/[^a-z0-9]/gi, '') + '-mut-' + Math.random().toString(36).slice(2, 10))
35
+ mkdirSync(dest, { recursive: true })
36
+ copyGraph(f.preset, f.main, dest)
37
+ const target = join(dest, f.main)
38
+ // task-7 discipline: anchor matching is done on an EOL-normalised copy, and a leaked CR is a SETUP
39
+ // failure (a lone CR would silently change the anchor's match count).
40
+ let before = readFileSync(target, 'utf8')
41
+ if (/\r/.test(before.replace(/\r\n/g, '\n'))) {
42
+ console.error('SETUP-FAIL - the anchor source was not EOL-normalised (a lone CR leaked in)')
43
+ rmSync(dest, { recursive: true, force: true })
44
+ process.exit(1)
45
+ }
46
+ const n = before.split(f.from).length - 1
47
+ if (n !== 1) {
48
+ skipped.push(f.name + ' (anchor x' + n + ')')
49
+ console.error(' SKIP - ' + f.name + ' (anchor x' + n + ', expected exactly 1)')
50
+ rmSync(dest, { recursive: true, force: true })
51
+ return false
52
+ }
53
+ writeFileSync(target, before.replace(f.from, f.to), 'utf8')
54
+ try { execFileSync(process.execPath, ['--check', target], { stdio: 'pipe' }) }
55
+ catch (e) {
56
+ writeFileSync(target, before, 'utf8')
57
+ skipped.push(f.name + ' (mutation did not compile)')
58
+ console.error(' SKIP - ' + f.name + ' (mutation did not compile; restored)')
59
+ rmSync(dest, { recursive: true, force: true })
60
+ return false
61
+ }
62
+ const t0 = Date.now()
63
+ let out = ''
64
+ let code = 0
65
+ let hang = false
66
+ try {
67
+ out = execFileSync(process.execPath, [f.suite], {
68
+ cwd: REPO, encoding: 'utf8', timeout: CHILD_TIMEOUT_MS, killSignal: 'SIGKILL',
69
+ env: Object.assign({}, process.env, { [f.env]: join(dest, f.main) }),
70
+ })
71
+ } catch (e) {
72
+ if (e && (e.killed || e.signal === 'SIGKILL')) hang = true
73
+ code = (e && e.status) || 1
74
+ out = String((e && e.stdout) || '') + String((e && e.stderr) || '')
75
+ }
76
+ const ms = Date.now() - t0
77
+ TIMES.push([f.name, ms])
78
+ if (hang) hangs.push(f.name + '(' + Math.round(ms / 1000) + 's)')
79
+ const named = out.split(/\r?\n/).filter((l) => /^\s*(FAIL - | {2}- )/.test(l) && f.expect.test(l)).map((l) => l.trim())
80
+ const ok = !hang && code !== 0 && named.length > 0
81
+ console.log((hang ? ' HANG - ' : (ok ? ' ok - ' : ' FAIL - ')) + f.name + ' [' + ms + 'ms]' + (named.length ? ' :: ' + named[0].slice(0, 150) : ' :: no named red (exit=' + code + ')'))
82
+ rmSync(dest, { recursive: true, force: true })
83
+ return ok
84
+ }
85
+
86
+ const FAMILIES = [
87
+ {
88
+ name: 'P3/v2: the host-refused-interrupt code is renamed (the named failure disappears)',
89
+ preset: 'vibe-math-v2', main: 'vibe-math-v2.js',
90
+ suite: 'tests/math-computation-v2.test.mjs', env: 'MC_V2_PLUGIN',
91
+ from: 'VIBE_MATH_INTERRUPT_FAILED', to: 'VIBE_MATH_INTERRUPT_RENAMED',
92
+ expect: /\[P3\/v2\] 宿主拒绝中断/,
93
+ },
94
+ {
95
+ name: 'P3/v3: the host-refused-interrupt code is renamed (the named failure disappears)',
96
+ preset: 'vibe-math-v3', main: 'vibe-math-v3.js',
97
+ suite: 'tests/math-computation-v3.test.mjs', env: 'MC_V3_PLUGIN',
98
+ from: 'VIBE_MATH_INTERRUPT_FAILED', to: 'VIBE_MATH_INTERRUPT_RENAMED',
99
+ expect: /\[P3\/v3\] 宿主拒绝中断/,
100
+ },
101
+ ]
102
+
103
+ let red = 0
104
+ for (const f of FAMILIES) { if (runFamily(f)) red++ }
105
+ console.log('')
106
+ console.log('mutant families reddening the v2/v3 host-refused-interrupt guard by name: ' + red + '/' + FAMILIES.length)
107
+ console.log('skipped=[' + skipped.join(' | ') + ']')
108
+ console.log('timings: ' + TIMES.map((t) => String(t[0]).split(':')[0] + '=' + t[1] + 'ms').join(' '))
109
+ console.log('hangs=[' + hangs.join(' | ') + ']')
110
+ if (red !== FAMILIES.length || skipped.length || hangs.length) process.exit(1)
111
+ console.log('ALL MUTANTS RED AS REQUIRED')
@@ -1,95 +1,99 @@
1
- // Registered mutant harness for tests/v3-fix-probes.test.mjs — the probe carries its own proof.
2
- // Defect class covered: silent-failure surfacing (F6a/F6b/F6c), cap sharing (F2cap), declared asymmetry (F-A),
3
- // fence-root drift (F5), shell-fallback silence (F4c). Every family is a SINGLE content-anchored mutation
4
- // of the v3 preset (no line numbers) and the suite runs through its V3_PLUGIN seam.
5
- // Exit criterion (audit-path-discipline.mutants.mjs paradigm): every family must redden BY NAME;
6
- // ALL MUTANTS RED AS REQUIRED + exit 0 only when all do.
7
- // Run: node tests/v3-fix-probes.mutants.mjs
8
- import { readFileSync, writeFileSync, mkdirSync, copyFileSync, rmSync, readdirSync } from 'node:fs'
9
- import { execFileSync } from 'node:child_process'
10
- import { dirname, join } from 'node:path'
11
- import { tmpdir } from 'node:os'
12
- import { fileURLToPath } from 'node:url'
13
- const HERE = dirname(fileURLToPath(import.meta.url))
14
- const REPO = join(HERE, '..')
15
- const PRESET = 'vibe-math-v3'
16
- const SUITE = 'tests/v3-fix-probes.test.mjs'
17
- const ENV = 'V3_PLUGIN'
18
- // Bound every child run: run-tests has no per-suite timeout, so an unbounded child hangs the whole gate.
19
- // A timeout is reported as HANG, counts as a failure for that family, and is listed in hangs=[].
20
- const CHILD_TIMEOUT_MS = Number(process.env.MUTANT_CHILD_TIMEOUT_MS || 120000)
21
- const TIMES = []
22
- // R15: whole-process wall time, printed at the end (the timeout row quotes this line).
23
- const START_MS = Date.now()
24
- const hangs = []
25
- let lastHang = false
26
- function copyGraph(file, dest) {
27
- const src = join(REPO, PRESET, file)
28
- copyFileSync(src, join(dest, file))
29
- for (const m of readFileSync(src, 'utf8').matchAll(/from\s+'(\.\/[A-Za-z0-9_.-]+\.js)'/g)) {
30
- const dep = m[1].slice(2)
31
- if (!readdirSync(dest).includes(dep)) copyGraph(dep, dest)
32
- }
33
- }
34
- function runFamily(f) {
35
- const dest = join(tmpdir(), 'v3probes-mut-' + Math.random().toString(36).slice(2, 10))
36
- mkdirSync(dest, { recursive: true })
37
- copyGraph(PRESET + '.js', dest)
38
- const target = join(dest, f.editFile || (PRESET + '.js'))
39
- const before = readFileSync(target, 'utf8')
40
- const n = before.split(f.from).length - 1
41
- if (f.lastOnly ? n < 1 : n !== 1) { console.error(' NO-OP - ' + f.name + ' (anchor x' + n + (f.lastOnly ? ', lastOnly' : '') + ')'); rmSync(dest, { recursive: true, force: true }); return false }
42
- const mutated = f.lastOnly
43
- ? (() => { const i = before.lastIndexOf(f.from); return before.slice(0, i) + f.to + before.slice(i + f.from.length) })()
44
- : before.split(f.from).join(f.to)
45
- writeFileSync(target, mutated, 'utf8')
46
- try { execFileSync(process.execPath, ['--check', target], { stdio: 'pipe' }) }
47
- catch (e) { writeFileSync(target, before, 'utf8'); console.error(' FAIL - ' + f.name + ' (mutation did not compile; restored)'); rmSync(dest, { recursive: true, force: true }); return false }
48
- let out = ''
49
- let code = 0
50
- try { out = execFileSync(process.execPath, [SUITE], { cwd: REPO, encoding: 'utf8', timeout: CHILD_TIMEOUT_MS, killSignal: 'SIGKILL', env: Object.assign({}, process.env, { [ENV]: join(dest, PRESET + '.js') }) }) }
51
- catch (e) { if (e && (e.killed || e.signal === 'SIGKILL')) lastHang = true; code = (e && e.status) || 1; out = String((e && e.stdout) || '') + String((e && e.stderr) || '') }
52
- const named = out.split(/\r?\n/).filter((l) => /^\s*(FAIL - | {2}- )/.test(l) && f.expect.test(l)).map((l) => l.trim())
53
- const ok = !lastHang && code !== 0 && named.length > 0
54
- console.log((lastHang ? ' HANG - ' : (ok ? ' ok - ' : ' FAIL - ')) + f.name + (named.length ? ' :: ' + named[0].slice(0, 140) : ' :: no named red (exit=' + code + ')'))
55
- rmSync(dest, { recursive: true, force: true })
56
- return ok
57
- }
58
- const FAMILIES = [
59
- { name: 'F2cap: status site reverted to a literal 10', expect: /\[F2cap\] status 与 report 两处都走同一常量/,
60
- from: "recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)), params: params,",
61
- to: "recentActivity: activityLog.slice(-Math.min(10, Number(params.activityLogCap) || 100)), params: params," },
62
- { name: 'F2cap: param description hard-codes the number', expect: /\[F2cap\] 参数描述\*\*引用\*\*该常量/,
63
- from: "' + ACTIVITY_REPORT_MAX + ' 条)', suggestion: 100 },", to: "' + 30 + ' 条)', suggestion: 100 }," },
64
- { name: 'F-A: status gains pendingDecisionItems', expect: /\[F-A\] status \*\*不得\*\*携带明细/,
65
- from: "pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,",
66
- to: "pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,\n pendingDecisionItems: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),",
67
- lastOnly: true },
68
- { name: 'F5: fence-root comparison removed', expect: /\[F5\/v3\] 围栏根漂移/,
69
- from: 'if (actual && expected && actual !== expected) warnFenceDriftOnce(actual, expected)', to: 'void actual' },
70
- { name: 'F4c: ensureDirs failure warning removed', expect: /\[F4c\] ensureDirs 失败不是沉默/,
71
- from: "if (!r || !r.ok) warnShellOnce('ensureDirs (mkdir ' + base + ')', r)", to: 'void r' },
72
- { name: 'F6a: provider list-failure warning removed', expect: /\[F6a\/v3\]/,
73
- from: "warnProviderFallback('subagents.list() failed: ' + ((e && e.message) || e))", to: 'void e' },
74
- { name: 'F6b: interrupt emit statement deleted (anchored by content)', expect: /\[F6b\/v3\]/,
75
- from: "if (r && r.ok === false) logActivity('interrupt', '中断失败(' + why + '):' + String(cid) + ' — ' + String(r.message || r.code || ''))", to: 'void r' },
76
- { name: 'F6c: project-pointer write failure re-silenced', expect: /\[F6c\/v3\]/,
77
- from: "noteStateWriteFailure('current.' + safeId(sessionId) + '.json', (e && e.message) || e)", to: 'void e' },
78
- ]
79
- let red = 0
80
- FAMILIES.push(
81
- { name: 'F-B: derivation drops the source descriptions', expect: /\[F-B\/v3\] 每个机读描述都来自真源且非空/,
82
- from: 'if (e.description) p.description = e.description', to: 'void e /* MUTANT F-B: no source description */' },
83
- { name: 'F-B: one exception entry deleted', expect: /\[F-B\/v3\] 例外集合 == docs\/parameter-schema\.md/,
84
- from: "mode: { type: 'string', enum: ['manual', 'auto'] },", to: '' },
85
- { name: 'F-B: one registration site reverted to a literal', expect: /\[F-B\/v3\] 两处注册点都由 paramProps\(\)/,
86
- from: "objParams(paramProps()), 'vibe_math_set_params')", to: "objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }), 'vibe_math_set_params')" },
87
- )
88
- for (const f of FAMILIES) { const t0 = Date.now(); lastHang = false; const ok = runFamily(f); const ms = Date.now() - t0; TIMES.push([f.name, ms]); if (lastHang) hangs.push(f.name + '(' + Math.round(ms / 1000) + 's)'); if (ok) red++ }
89
- console.log('')
90
- console.log('mutant families reddening the v3 probe by name: ' + red + '/' + FAMILIES.length)
91
- console.log('timings: ' + TIMES.map((t) => String(t[0]).split(':')[0] + '=' + t[1] + 'ms').join(' '))
92
- console.log('TOTAL WALL TIME (all families + setup): ' + (Date.now() - START_MS) + 'ms (' + Math.round((Date.now() - START_MS) / 1000) + 's)')
93
- console.log('hangs=[' + hangs.join(' | ') + ']')
94
- if (red !== FAMILIES.length || hangs.length) process.exit(1)
95
- console.log('ALL MUTANTS RED AS REQUIRED')
1
+ // Registered mutant harness for tests/v3-fix-probes.test.mjs — the probe carries its own proof.
2
+ // Defect class covered: silent-failure surfacing (F6a/F6b/F6c), cap sharing (F2cap), declared asymmetry (F-A),
3
+ // fence-root drift (F5), shell-fallback silence (F4c). Every family is a SINGLE content-anchored mutation
4
+ // of the v3 preset (no line numbers) and the suite runs through its V3_PLUGIN seam.
5
+ // Exit criterion (audit-path-discipline.mutants.mjs paradigm): every family must redden BY NAME;
6
+ // ALL MUTANTS RED AS REQUIRED + exit 0 only when all do.
7
+ // Run: node tests/v3-fix-probes.mutants.mjs
8
+ import { readFileSync, writeFileSync, mkdirSync, copyFileSync, rmSync, readdirSync } from 'node:fs'
9
+ import { execFileSync } from 'node:child_process'
10
+ import { dirname, join } from 'node:path'
11
+ import { tmpdir } from 'node:os'
12
+ import { fileURLToPath } from 'node:url'
13
+ const HERE = dirname(fileURLToPath(import.meta.url))
14
+ const REPO = join(HERE, '..')
15
+ const PRESET = 'vibe-math-v3'
16
+ const SUITE = 'tests/v3-fix-probes.test.mjs'
17
+ const ENV = 'V3_PLUGIN'
18
+ // Bound every child run: run-tests has no per-suite timeout, so an unbounded child hangs the whole gate.
19
+ // A timeout is reported as HANG, counts as a failure for that family, and is listed in hangs=[].
20
+ const CHILD_TIMEOUT_MS = Number(process.env.MUTANT_CHILD_TIMEOUT_MS || 120000)
21
+ const TIMES = []
22
+ // R15: whole-process wall time, printed at the end (the timeout row quotes this line).
23
+ const START_MS = Date.now()
24
+ const hangs = []
25
+ let lastHang = false
26
+ function copyGraph(file, dest) {
27
+ const src = join(REPO, PRESET, file)
28
+ copyFileSync(src, join(dest, file))
29
+ for (const m of readFileSync(src, 'utf8').matchAll(/from\s+'(\.\/[A-Za-z0-9_.-]+\.js)'/g)) {
30
+ const dep = m[1].slice(2)
31
+ if (!readdirSync(dest).includes(dep)) copyGraph(dep, dest)
32
+ }
33
+ }
34
+ function runFamily(f) {
35
+ const dest = join(tmpdir(), 'v3probes-mut-' + Math.random().toString(36).slice(2, 10))
36
+ mkdirSync(dest, { recursive: true })
37
+ copyGraph(PRESET + '.js', dest)
38
+ const target = join(dest, f.editFile || (PRESET + '.js'))
39
+ const before = readFileSync(target, 'utf8')
40
+ const n = before.split(f.from).length - 1
41
+ if (f.lastOnly ? n < 1 : n !== 1) { console.error(' NO-OP - ' + f.name + ' (anchor x' + n + (f.lastOnly ? ', lastOnly' : '') + ')'); rmSync(dest, { recursive: true, force: true }); return false }
42
+ const mutated = f.lastOnly
43
+ ? (() => { const i = before.lastIndexOf(f.from); return before.slice(0, i) + f.to + before.slice(i + f.from.length) })()
44
+ : before.split(f.from).join(f.to)
45
+ writeFileSync(target, mutated, 'utf8')
46
+ try { execFileSync(process.execPath, ['--check', target], { stdio: 'pipe' }) }
47
+ catch (e) { writeFileSync(target, before, 'utf8'); console.error(' FAIL - ' + f.name + ' (mutation did not compile; restored)'); rmSync(dest, { recursive: true, force: true }); return false }
48
+ let out = ''
49
+ let code = 0
50
+ try { out = execFileSync(process.execPath, [SUITE], { cwd: REPO, encoding: 'utf8', timeout: CHILD_TIMEOUT_MS, killSignal: 'SIGKILL', env: Object.assign({}, process.env, { [ENV]: join(dest, PRESET + '.js') }) }) }
51
+ catch (e) { if (e && (e.killed || e.signal === 'SIGKILL')) lastHang = true; code = (e && e.status) || 1; out = String((e && e.stdout) || '') + String((e && e.stderr) || '') }
52
+ const named = out.split(/\r?\n/).filter((l) => /^\s*(FAIL - | {2}- )/.test(l) && f.expect.test(l)).map((l) => l.trim())
53
+ const ok = !lastHang && code !== 0 && named.length > 0
54
+ console.log((lastHang ? ' HANG - ' : (ok ? ' ok - ' : ' FAIL - ')) + f.name + (named.length ? ' :: ' + named[0].slice(0, 140) : ' :: no named red (exit=' + code + ')'))
55
+ rmSync(dest, { recursive: true, force: true })
56
+ return ok
57
+ }
58
+ const FAMILIES = [
59
+ { name: 'F2cap: status site reverted to a literal 10', expect: /\[F2cap\] status 与 report 两处都走同一常量/,
60
+ from: "recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)), params: params,",
61
+ to: "recentActivity: activityLog.slice(-Math.min(10, Number(params.activityLogCap) || 100)), params: params," },
62
+ { name: 'F2cap: param description hard-codes the number', expect: /\[F2cap\] 参数描述\*\*引用\*\*该常量/,
63
+ from: "' + ACTIVITY_REPORT_MAX + ' 条)', suggestion: 100 },", to: "' + 30 + ' 条)', suggestion: 100 }," },
64
+ { name: 'F-A: status gains pendingDecisionItems', expect: /\[F-A\] status \*\*不得\*\*携带明细/,
65
+ from: "pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,",
66
+ to: "pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,\n pendingDecisionItems: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),",
67
+ lastOnly: true },
68
+ { name: 'F5: fence-root comparison removed', expect: /\[F5\/v3\] 围栏根漂移/,
69
+ from: 'if (actual && expected && actual !== expected) warnFenceDriftOnce(actual, expected)', to: 'void actual' },
70
+ { name: 'F4c: ensureDirs failure warning removed', expect: /\[F4c\] ensureDirs 失败不是沉默/,
71
+ from: "if (!r || !r.ok) warnShellOnce('ensureDirs (mkdir ' + base + ')', r)", to: 'void r' },
72
+ { name: 'F6a: provider list-failure warning removed', expect: /\[F6a\/v3\]/,
73
+ from: "warnProviderFallback('subagents.list() failed: ' + ((e && e.message) || e))", to: 'void e' },
74
+ { name: 'F6b: interrupt emit statement deleted (anchored by content)', expect: /\[F6b\/v3\]/,
75
+ from: "if (r && r.ok === false) logActivity('interrupt', '中断失败(' + why + '):' + String(cid) + ' — ' + String(r.message || r.code || ''))", to: 'void r' },
76
+ { name: 'F6c: project-pointer write failure re-silenced', expect: /\[F6c\/v3\]/,
77
+ from: "noteStateWriteFailure('current.' + safeId(sessionId) + '.json', (e && e.message) || e)", to: 'void e' },
78
+ ]
79
+ let red = 0
80
+ FAMILIES.push(
81
+ { name: 'F-B: derivation drops the source descriptions', expect: /\[F-B\/v3\] 每个机读描述都来自真源且非空/,
82
+ from: 'if (e.description) p.description = e.description', to: 'void e /* MUTANT F-B: no source description */' },
83
+ { name: 'F-B: one exception entry deleted', expect: /\[F-B\/v3\] 例外集合 == docs\/parameter-schema\.md/,
84
+ from: "mode: { type: 'string', enum: ['manual', 'auto'] },", to: '' },
85
+ { name: 'F-B: one registration site reverted to a literal', expect: /\[F-B\/v3\] 两处注册点都由 paramProps\(\)/,
86
+ from: "objParams(paramProps()), 'vibe_math_set_params')", to: "objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }), 'vibe_math_set_params')" },
87
+ )
88
+ FAMILIES.push(
89
+ { name: 'task-9/v3: the not-detected log drops the actionable sentence', expect: /\[task-9\/v3\]/,
90
+ from: '已探测 PATH 与文档化的常见 TeX 根;可用 paperLatexCommand 指定绝对路径。', to: '' },
91
+ )
92
+ for (const f of FAMILIES) { const t0 = Date.now(); lastHang = false; const ok = runFamily(f); const ms = Date.now() - t0; TIMES.push([f.name, ms]); if (lastHang) hangs.push(f.name + '(' + Math.round(ms / 1000) + 's)'); if (ok) red++ }
93
+ console.log('')
94
+ console.log('mutant families reddening the v3 probe by name: ' + red + '/' + FAMILIES.length)
95
+ console.log('timings: ' + TIMES.map((t) => String(t[0]).split(':')[0] + '=' + t[1] + 'ms').join(' '))
96
+ console.log('TOTAL WALL TIME (all families + setup): ' + (Date.now() - START_MS) + 'ms (' + Math.round((Date.now() - START_MS) / 1000) + 's)')
97
+ console.log('hangs=[' + hangs.join(' | ') + ']')
98
+ if (red !== FAMILIES.length || hangs.length) process.exit(1)
99
+ console.log('ALL MUTANTS RED AS REQUIRED')