dsh-vibe-math 2.7.2 → 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 (153) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +682 -681
  3. package/README.md +683 -682
  4. package/cordis.patch.yml +112 -16
  5. package/docs/AUDIT-CHECKLIST.md +755 -518
  6. package/docs/COMPAT-AUDIT-ROUND2.md +354 -328
  7. package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
  8. package/docs/audit-methodology.md +93 -0
  9. package/docs/final-paper.md +172 -172
  10. package/docs/formal-verification.md +613 -588
  11. package/docs/generate_framework_diagram_v2.py +114 -114
  12. package/docs/generate_framework_diagram_v3.py +127 -127
  13. package/docs/math-computation.md +195 -187
  14. package/docs/parameter-schema.md +37 -0
  15. package/docs/release-notes/README.en.md +2 -0
  16. package/docs/release-notes/README.md +2 -0
  17. package/docs/release-notes/RELEASE-NOTES-2.0.22.md +112 -112
  18. package/docs/release-notes/RELEASE-NOTES-2.1.0.md +143 -143
  19. package/docs/release-notes/RELEASE-NOTES-2.2.0.md +266 -266
  20. package/docs/release-notes/RELEASE-NOTES-2.2.1.md +43 -43
  21. package/docs/release-notes/RELEASE-NOTES-2.2.2.md +88 -88
  22. package/docs/release-notes/RELEASE-NOTES-2.3.0.md +207 -207
  23. package/docs/release-notes/RELEASE-NOTES-2.3.1.md +134 -134
  24. package/docs/release-notes/RELEASE-NOTES-2.3.10.md +105 -105
  25. package/docs/release-notes/RELEASE-NOTES-2.3.11.md +57 -57
  26. package/docs/release-notes/RELEASE-NOTES-2.3.12.md +80 -80
  27. package/docs/release-notes/RELEASE-NOTES-2.3.2.md +145 -145
  28. package/docs/release-notes/RELEASE-NOTES-2.3.3.md +115 -115
  29. package/docs/release-notes/RELEASE-NOTES-2.3.4.md +69 -69
  30. package/docs/release-notes/RELEASE-NOTES-2.3.5.md +63 -63
  31. package/docs/release-notes/RELEASE-NOTES-2.3.6.md +66 -66
  32. package/docs/release-notes/RELEASE-NOTES-2.3.7.md +59 -59
  33. package/docs/release-notes/RELEASE-NOTES-2.3.8.md +45 -45
  34. package/docs/release-notes/RELEASE-NOTES-2.3.9.md +70 -70
  35. package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +2 -2
  36. package/docs/release-notes/RELEASE-NOTES-2.7.2.md +3 -3
  37. package/docs/release-notes/RELEASE-NOTES-2.7.3.en.md +67 -0
  38. package/docs/release-notes/RELEASE-NOTES-2.7.3.md +67 -0
  39. package/docs/release-notes/RELEASE-NOTES-2.8.0.en.md +65 -0
  40. package/docs/release-notes/RELEASE-NOTES-2.8.0.md +65 -0
  41. package/docs/release-notes/TEMPLATE.md +6 -0
  42. package/docs/release-playbook.md +103 -0
  43. package/docs/slv-playbook.md +117 -0
  44. package/docs/status-report-fields.md +167 -0
  45. package/docs/test-timing.md +158 -154
  46. package/docs//346/236/266/346/236/204/345/233/276.md +77 -77
  47. package/installer.js +793 -783
  48. package/package.json +260 -203
  49. package/prompt-corpus-persona/persona-corpus.json +9 -9
  50. package/prompt-corpus-persona/persona-corpus.md +225 -33
  51. package/prompt-corpus-v2/formal-verify-v2.json +295 -115
  52. package/prompt-corpus-v2/formal-verify-v2.md +2290 -282
  53. package/prompt-corpus-v3/formal-verify-v3.json +112 -112
  54. package/prompt-corpus-v3/formal-verify-v3.md +227 -143
  55. package/prompt-corpus-v4/formal-verify-v4.json +18 -18
  56. package/prompt-corpus-v4/formal-verify-v4.md +49 -44
  57. package/prompt-corpus-v5/prompt-corpus-v5.json +82 -82
  58. package/prompt-corpus-v5/prompt-corpus-v5.md +573 -207
  59. package/scripts/clean-temp.mjs +234 -0
  60. package/scripts/update-doc-counts.mjs +71 -0
  61. package/tests/audit-artifact-docs.mjs +72 -0
  62. package/tests/audit-artifact-docs.mutants.mjs +58 -0
  63. package/tests/audit-engine-faces.mjs +298 -0
  64. package/tests/audit-formal-sensitivity.mjs +345 -342
  65. package/tests/audit-installer-assertions.mutants.mjs +63 -0
  66. package/tests/audit-installer-compat.test.mjs +256 -208
  67. package/tests/audit-installer-policy.test.mjs +41 -8
  68. package/tests/audit-math-computation-contract.mjs +64 -8
  69. package/tests/audit-math-computation-contract.mutants.mjs +106 -0
  70. package/tests/audit-math-computation-parity.mjs +164 -3
  71. package/tests/audit-math-computation-sensitivity.mjs +5 -3
  72. package/tests/audit-package-membership.mjs +57 -0
  73. package/tests/audit-package-membership.mutants.mjs +28 -0
  74. package/tests/audit-participant-set-parity.mjs +160 -0
  75. package/tests/audit-path-discipline.mjs +83 -0
  76. package/tests/audit-path-discipline.mutants.mjs +51 -0
  77. package/tests/audit-persona-sensitivity.mjs +323 -309
  78. package/tests/audit-persona-surface.mutants.mjs +69 -0
  79. package/tests/audit-persona-surface.test.mjs +431 -390
  80. package/tests/audit-prompt-duplication.mjs +143 -0
  81. package/tests/audit-prompt-duplication.mutants.mjs +51 -0
  82. package/tests/audit-prompt-invariants.mjs +589 -508
  83. package/tests/audit-readme-bilingual.test.mjs +152 -0
  84. package/tests/audit-readme-counts.mjs +95 -0
  85. package/tests/audit-readme-counts.mutants.mjs +52 -0
  86. package/tests/audit-registration.mjs +78 -0
  87. package/tests/audit-spec-traceability.mjs +193 -193
  88. package/tests/audit-status-report-fields.mjs +172 -0
  89. package/tests/audit-status-report-fields.mutants.mjs +64 -0
  90. package/tests/audit-v3-registration-parity.mjs +156 -0
  91. package/tests/audit-v5-integrity.mjs +795 -508
  92. package/tests/audit-v5-lean-abstention.mjs +392 -0
  93. package/tests/audit-v5-lean-abstention.mutants.mjs +106 -0
  94. package/tests/audit-v5-prompt-duplication.mjs +88 -0
  95. package/tests/audit-v5-prompt-duplication.mutants.mjs +34 -0
  96. package/tests/audit-v5-sensitivity.mjs +433 -427
  97. package/tests/e2e-identity-a6.mutants.mjs +65 -0
  98. package/tests/e2e-v5-round2.test.mjs +2453 -1981
  99. package/tests/formal-verify-v2.test.mjs +1778 -1724
  100. package/tests/formal-verify-v3.mutants.mjs +117 -0
  101. package/tests/formal-verify-v3.test.mjs +1724 -1671
  102. package/tests/formal-verify-v4.mutants.mjs +152 -0
  103. package/tests/formal-verify-v4.test.mjs +2114 -1574
  104. package/tests/formal-verify-v5.test.mjs +790 -724
  105. package/tests/helpers/doc-counts-format.mjs +41 -0
  106. package/tests/helpers/math-computation-fake-seam.mjs +4 -0
  107. package/tests/math-computation-a1.mutants.mjs +72 -0
  108. package/tests/math-computation-archive-rerun.mutants.mjs +65 -0
  109. package/tests/math-computation-discovery.mutants.mjs +76 -0
  110. package/tests/math-computation-shared.test.mjs +392 -6
  111. package/tests/math-computation-v2.test.mjs +779 -0
  112. package/tests/math-computation-v3.test.mjs +771 -0
  113. package/tests/math-computation-v4.mutants.mjs +133 -0
  114. package/tests/math-computation-v4.test.mjs +739 -0
  115. package/tests/prompt-v5-integrity.test.mjs +1291 -1280
  116. package/tests/run-tests.mjs +393 -202
  117. package/tests/run-tests.mutants.mjs +70 -0
  118. package/tests/selfdrive-v5.mjs +560 -508
  119. package/tests/selfdrive-v5.mutants.mjs +132 -0
  120. package/tests/temp-hygiene.mutants.mjs +80 -0
  121. package/tests/v2-fix-probes.mutants.mjs +104 -0
  122. package/tests/v2-fix-probes.test.mjs +1040 -0
  123. package/tests/v2-list-agents-and-next-step.mutants.mjs +106 -0
  124. package/tests/v2v3-interrupt.mutants.mjs +111 -0
  125. package/tests/v3-fix-probes.mutants.mjs +99 -0
  126. package/tests/v3-fix-probes.test.mjs +892 -0
  127. package/tests/v4-final-paper.mutants.mjs +108 -0
  128. package/tests/v5-institute-fixes.mutants.mjs +218 -0
  129. package/vibe-math-v2/agent.cordis.yml +378 -322
  130. package/vibe-math-v2/math-computation.js +144 -53
  131. package/vibe-math-v2/math-engines.js +12 -12
  132. package/vibe-math-v2/preset.yml +2 -2
  133. package/vibe-math-v2/v2/347/232/204/351/242/235/345/244/226/350/246/201/346/261/202.txt +21 -21
  134. package/vibe-math-v2/vibe-math-v2.js +5097 -4755
  135. package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +491 -487
  136. package/vibe-math-v3/agent.cordis.yml +422 -356
  137. package/vibe-math-v3/math-computation.js +144 -53
  138. package/vibe-math-v3/math-engines.js +12 -12
  139. package/vibe-math-v3/preset.yml +2 -2
  140. package/vibe-math-v3/vibe-math-v3.js +6235 -5873
  141. package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +649 -649
  142. package/vibe-math-v4/agent.cordis.yml +438 -402
  143. package/vibe-math-v4/math-computation.js +144 -53
  144. package/vibe-math-v4/math-engines.js +12 -12
  145. package/vibe-math-v4/preset.yml +2 -2
  146. package/vibe-math-v4/vibe-math-v4.js +4490 -4269
  147. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1015 -1013
  148. package/vibe-math-v5/agent.cordis.yml +46 -12
  149. package/vibe-math-v5/math-computation.js +144 -53
  150. package/vibe-math-v5/math-engines.js +12 -12
  151. package/vibe-math-v5/vibe-math-v5.js +1266 -229
  152. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1660
  153. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Mutant harness for the two v2 harness-gap rows (ships: `tests/*.mutants.mjs`):
4
+ * F3 v2 `vibe_math_list_agents` — the tool must EXIST and be MENTIONED
5
+ * F4 v2 require-gate — a refusal must carry an EXECUTABLE NEXT STEP
6
+ *
7
+ * Each case copies the v2 preset (and, for the persona case, all four preset dirs) into a private root,
8
+ * applies ONE logical mutation, drives the SHIPPED owning suite through its own seam, and requires a NAMED
9
+ * red. Every target suite also runs GREEN on the unmutated copies first (faithful-copy baseline), so the
10
+ * mutation is the only red cause. Seams used: MC_V2_PLUGIN, V2_PLUGIN, PERSONA_ROOT.
11
+ *
12
+ * Run: node tests/v2-list-agents-and-next-step.mutants.mjs
13
+ */
14
+ import { mkdtempSync, cpSync, copyFileSync, readFileSync, writeFileSync, rmSync } from 'node:fs'
15
+ import { tmpdir } from 'node:os'
16
+ import { join } from 'node:path'
17
+ import { spawnSync } from 'node:child_process'
18
+ import { fileURLToPath } from 'node:url'
19
+
20
+ const HERE = fileURLToPath(new URL('./', import.meta.url))
21
+ const REPO = join(HERE, '..')
22
+ const PRESETS = ['vibe-math-v2', 'vibe-math-v3', 'vibe-math-v4', 'vibe-math-v5']
23
+ let passed = 0, failed = 0
24
+ const ok = (c, l, d) => { if (c) { passed++; console.log(' ok ' + l) } else { failed++; console.log(' FAIL ' + l + (d ? ' — ' + d : '')) } }
25
+
26
+ function runSuite(file, env) {
27
+ const r = spawnSync(process.execPath, [join(HERE, file)], { cwd: REPO, encoding: 'utf8', maxBuffer: 1 << 26, env: Object.assign({}, process.env, env || {}) })
28
+ const o = String(r.stdout || '') + String(r.stderr || '')
29
+ return {
30
+ status: r.status, out: o,
31
+ failLines: o.split('\n').filter((l) => /^\s*(FAIL| FAIL)\b/.test(l)),
32
+ sum: (o.split('\n').filter((l) => /passed, \d+ failed/.test(l)).slice(-1)[0] || '').trim(),
33
+ }
34
+ }
35
+ const named = (r, needle) => r.failLines.some((l) => l.indexOf(needle) !== -1)
36
+ const V2_SRC = join(REPO, 'vibe-math-v2', 'vibe-math-v2.js')
37
+ const ORIGIN = readFileSync(V2_SRC, 'utf8')
38
+ const REGISTER = "registerTool('vibe_math_list_agents'"
39
+ const NEXT_STEP = '下一步:把该对象形式化到 Lean 通过'
40
+ const COPY_AS = 'vibe_math_list_agentsX'
41
+
42
+ // ---------- baselines: the shipped suites are green on the COPIED artifacts ----------
43
+ const baseRoot = mkdtempSync(join(tmpdir(), 'v2gaps-base-'))
44
+ cpSync(join(REPO, 'vibe-math-v2'), join(baseRoot, 'vibe-math-v2'), { recursive: true })
45
+ {
46
+ const a = runSuite('math-computation-v2.test.mjs', { MC_V2_PLUGIN: join(baseRoot, 'vibe-math-v2', 'vibe-math-v2.js') })
47
+ ok(a.status === 0, 'baseline: math-computation-v2 is green on the COPIED v2 preset (MC_V2_PLUGIN)', a.sum.slice(0, 60))
48
+ const c = runSuite('v2-fix-probes.test.mjs', { V2_PLUGIN: join(baseRoot, 'vibe-math-v2', 'vibe-math-v2.js') })
49
+ ok(c.status === 0, 'baseline: v2-fix-probes is green on the COPIED v2 preset (V2_PLUGIN)', c.sum.slice(0, 60))
50
+ }
51
+ {
52
+ const pRoot = mkdtempSync(join(tmpdir(), 'v2gaps-persona-'))
53
+ for (const d of PRESETS) cpSync(join(REPO, d), join(pRoot, d), { recursive: true })
54
+ const p = runSuite('audit-persona-surface.test.mjs', { PERSONA_ROOT: pRoot })
55
+ ok(p.status === 0, 'baseline: audit-persona-surface is green on the COPIED preset tree (PERSONA_ROOT)', p.sum.slice(0, 60))
56
+ rmSync(pRoot, { recursive: true, force: true })
57
+ }
58
+ rmSync(baseRoot, { recursive: true, force: true })
59
+
60
+ // ---------- F3a: the tool must EXIST (rename the registration -> the documented call fails) ----------
61
+ {
62
+ const dir = mkdtempSync(join(tmpdir(), 'v2gaps-exist-'))
63
+ cpSync(join(REPO, 'vibe-math-v2'), join(dir, 'vibe-math-v2'), { recursive: true })
64
+ const p = join(dir, 'vibe-math-v2', 'vibe-math-v2.js')
65
+ const src = readFileSync(p, 'utf8')
66
+ writeFileSync(p, src.replace(REGISTER, REGISTER.replace('list_agents', 'list_agentsX')))
67
+ const r = runSuite('math-computation-v2.test.mjs', { MC_V2_PLUGIN: p })
68
+ ok(r.status !== 0 && named(r, '对照:vibe_math_list_agents 可调用'),
69
+ '★ F3 (exists): renaming the registration reddens the named "list_agents is callable" assertion (unit: tool name)',
70
+ r.sum.slice(0, 70))
71
+ if (!named(r, '对照:vibe_math_list_agents 可调用')) for (const l of r.failLines.slice(0, 3)) console.log(' ' + l.trim().slice(0, 130))
72
+ rmSync(dir, { recursive: true, force: true })
73
+ }
74
+ // ---------- F3b: the tool must be MENTIONED (persona still names the old name) ----------
75
+ {
76
+ const root = mkdtempSync(join(tmpdir(), 'v2gaps-mention-'))
77
+ for (const d of PRESETS) cpSync(join(REPO, d), join(root, d), { recursive: true })
78
+ const p = join(root, 'vibe-math-v2', 'vibe-math-v2.js')
79
+ writeFileSync(p, readFileSync(p, 'utf8').split(REGISTER).join(REGISTER.replace('list_agents', 'list_agentsX')))
80
+ const r = runSuite('audit-persona-surface.test.mjs', { PERSONA_ROOT: root })
81
+ ok(r.status !== 0 && named(r, 'every registered tool is mentioned in the persona, or explicitly allow-listed'),
82
+ '★ F3 (mentioned): a registered tool the persona never names reddens the named allow-list assertion (unit: registered tool names)',
83
+ r.sum.slice(0, 70))
84
+ if (!named(r, 'every registered tool is mentioned in the persona, or explicitly allow-listed')) for (const l of r.failLines.slice(0, 3)) console.log(' ' + l.trim().slice(0, 130))
85
+ rmSync(root, { recursive: true, force: true })
86
+ }
87
+ // ---------- F4: a require-gate refusal must carry an executable NEXT STEP ----------
88
+ {
89
+ const dir = mkdtempSync(join(tmpdir(), 'v2gaps-next-'))
90
+ cpSync(join(REPO, 'vibe-math-v2'), join(dir, 'vibe-math-v2'), { recursive: true })
91
+ const p = join(dir, 'vibe-math-v2', 'vibe-math-v2.js')
92
+ const src = readFileSync(p, 'utf8')
93
+ if (src.indexOf(NEXT_STEP) === -1) ok(false, 'F4: the single-site next-step anchor applies', 'ANCHOR MISS')
94
+ else {
95
+ writeFileSync(p, src.replace(NEXT_STEP, '下一步:见文档'))
96
+ const r = runSuite('v2-fix-probes.test.mjs', { V2_PLUGIN: p })
97
+ ok(r.status !== 0 && named(r, 'require-gate 反馈行携带下一步'),
98
+ '★ F4: dropping the executable next step from the require-gate refusal reddens its named assertion (unit: refusal sites)',
99
+ r.sum.slice(0, 70))
100
+ if (!named(r, 'require-gate 反馈行携带下一步')) for (const l of r.failLines.slice(0, 3)) console.log(' ' + l.trim().slice(0, 130))
101
+ }
102
+ rmSync(dir, { recursive: true, force: true })
103
+ }
104
+ console.log('')
105
+ console.log('=== V2 LIST-AGENTS / NEXT-STEP MUTANTS: ' + passed + ' passed, ' + failed + ' failed ===')
106
+ process.exit(failed === 0 ? 0 : 1)
@@ -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')
@@ -0,0 +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
+ 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')