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,41 @@
1
+ // ONE definition of every count sentence (D1). The writer (scripts/update-doc-counts.mjs), the guard
2
+ // (tests/audit-readme-counts.mjs) and the mutant harness all import this module, so the three cannot
3
+ // disagree about the shape. Sentences are rebuilt WHOLE (never partially regex-edited), which makes the
4
+ // writer idempotent — `--check` converges.
5
+ //
6
+ // VOCABULARY: `derived.*` are JOB counts (the gate's own job list: base jobs + VARIANTS jobs), NOT file
7
+ // counts (a naive file count gives 82/38). `shipped.*` ARE file counts (package.json#files is a file list).
8
+
9
+ export function claims(derived, shipped) {
10
+ const zhTotal = '`TOTAL ' + derived.total + '`(' + derived.suites + ' 套件 + ' + derived.probes + ' 探针/变体,**作业计数**(job count),不是文件计数)'
11
+ const zhShipped = shipped.total + ' 个 `tests/*.mjs`(' + shipped.suites + ' 个套件 + ' + shipped.probes + ' 个探针/脚本)'
12
+ const enTotal = '`TOTAL ' + derived.total + ' (' + derived.suites + ' suites + ' + derived.probes + ' probes/variants — JOB counts, not file counts)'
13
+ const enShipped = 'which ' + shipped.total + ' of the `tests/*.mjs` files ship in the package (' + shipped.suites + ' suites + ' + shipped.probes + ' probe/script files)'
14
+ const ttRow = '| `tests/run-tests.mjs`(**' + derived.total + ' 项作业**(job count)= ' + derived.suites + ' 套件 + '
15
+ + derived.probes + ' 探针/变体;**由 `--counts` 派生**) | ≈ 424 s(历史基线) | **≈ 121 s**(历史基线;当前实测墙钟 ≈ 200 s,并发 4) | `TOTAL '
16
+ + derived.total + ' PASS ' + derived.total + ' FAIL 0 (suites ' + derived.suites + ' · probes ' + derived.probes + ')`;实测 `wall 120.7s · sum 424.2s`,关键路径 = `e2e-v4-fixes`(≈ 98 s) |'
17
+ const ttOutput = '`TOTAL ' + derived.total + ' PASS ' + derived.total + ' FAIL 0 (suites ' + derived.suites + ' · probes ' + derived.probes + ')`'
18
+ const ckClaim = derived.total + ' 项作业(job count)= ' + derived.suites + ' 套件 + ' + derived.probes + ' 探针/变体'
19
+ const ckShipped = '只发 `tests/` 的 **' + shipped.total + '** 项(**文件计数**;其中 `.test.mjs` **' + shipped.suites + '** 个)'
20
+ const d1Row = '- **计数(实测)**:`TOTAL ' + derived.total + '`(' + derived.suites + ' 套件 + ' + derived.probes
21
+ + ' 探针/变体,**作业计数/job count**,不是文件计数);随包 `tests/*.mjs` = ' + shipped.total + '(**文件计数**:' + shipped.suites
22
+ + ' 个套件 + ' + shipped.probes + ' 个探针/脚本)。'
23
+ const zhBullet = '- **测试耗时基线与并行跑法**:[`docs/test-timing.md`](docs/test-timing.md)(`node tests/run-tests.mjs` 并行跑**全部套件 + 全部探针**:'
24
+ + zhTotal + ';**这些数字必须派生**:`node tests/run-tests.mjs --counts` 是权威来源(**作业计数**,不是文件计数),并由 '
25
+ + '[`tests/audit-readme-counts.mjs`](tests/audit-readme-counts.mjs) 校验本页与另两份文档;随包发布的是 `files[]` 里的 '
26
+ + zhShipped + '(文件计数),其余仅开发检出可见,清单见该文档 §1.1;每个 runner 都会打印耗时/加速比供下次选策略)'
27
+ const enBullet = '- **Test timing baseline and parallel run recipes**: [`docs/test-timing.md`](docs/test-timing.md) '
28
+ + '(`node tests/run-tests.mjs` runs **every suite AND every probe** in parallel: ' + enTotal + '; the authority is '
29
+ + '`node tests/run-tests.mjs --counts` (JOB counts, not file counts), checked by '
30
+ + '[`tests/audit-readme-counts.mjs`](tests/audit-readme-counts.mjs); ' + enShipped
31
+ + ' and which are repository-only is in §1.1 of that document; every runner prints its elapsed time/speedup so the next strategy can be chosen)'
32
+ return { zhTotal, zhShipped, enTotal, enShipped, ttRow, ttOutput, ckClaim, ckShipped, d1Row, zhBullet, enBullet }
33
+ }
34
+
35
+ /** Stable line markers: each canonical line replaces the line that carries its marker. */
36
+ export const MARKERS = {
37
+ zhBullet: '[`docs/test-timing.md`](docs/test-timing.md)(`node tests/run-tests.mjs`',
38
+ enBullet: '[`docs/test-timing.md`](docs/test-timing.md) (`node tests/run-tests.mjs`',
39
+ ttRow: '| `tests/run-tests.mjs`(',
40
+ d1Row: '- **计数(实测',
41
+ }
@@ -88,6 +88,10 @@ export function makeFakeHost(opts = {}) {
88
88
  // `absTree` maps an absolute directory to its entries, so a test can fake DSH's layout
89
89
  // (`<root>/dsh-runtimes/<tree>/dependencies/python/python.exe`).
90
90
  runtimeRoots: ('runtimeRoots' in opts) ? (async () => (opts.runtimeRoots || [])) : undefined,
91
+ // round-B (F4): OPTIONAL per-engine install roots. When supplied, the module uses THESE instead of
92
+ // this process's environment, so a test never depends on the ambient machine's %ProgramFiles% /
93
+ // %LOCALAPPDATA% (or on which engines the developer happens to have installed).
94
+ installRoots: opts.installRoots ? (async (engine) => (opts.installRoots[engine] || [])) : undefined,
91
95
  listDirAbs: opts.absTree ? (async (abs) => (opts.absTree[String(abs).replace(/\\/g, '/')] || [])) : undefined,
92
96
  resolveExecutable: async (cmd) => {
93
97
  if (opts.resolveThrows) throw new Error('resolve failed: ' + cmd)
@@ -0,0 +1,72 @@
1
+ // Registered mutant harness for the A1 skip-ledger contract (tests/math-computation-v2|v3.test.mjs).
2
+ // A1 is load-bearing only if NON-EXERCISE reddens it: each family nulls the copy module's
3
+ // packageManager/packageManagerAvailable and must redden the named line. Two families (one per preset).
4
+ // Exit criterion: every family reddens BY NAME; any skipped family prints, is counted, and forces exit 1.
5
+ // Run: node tests/math-computation-a1.mutants.mjs
6
+ import { readFileSync, writeFileSync, mkdirSync, copyFileSync, rmSync, readdirSync } from 'node:fs'
7
+ import { execFileSync } from 'node:child_process'
8
+ import { dirname, join } from 'node:path'
9
+ import { tmpdir } from 'node:os'
10
+ import { fileURLToPath } from 'node:url'
11
+
12
+ // Bound every child run: run-tests has no per-suite timeout, so an unbounded child hangs the gate.
13
+ // A timeout is reported as HANG, counts as a failure for that family, and is listed in hangs=[].
14
+ const CHILD_TIMEOUT_MS = Number(process.env.MUTANT_CHILD_TIMEOUT_MS || 120000)
15
+ const TIMES = []
16
+ const hangs = []
17
+ let lastHang = false
18
+ const HERE = dirname(fileURLToPath(import.meta.url))
19
+ const REPO = join(HERE, '..')
20
+ function copyGraph(preset, file, dest) {
21
+ const src = join(REPO, preset, file)
22
+ copyFileSync(src, join(dest, file))
23
+ for (const m of readFileSync(src, 'utf8').matchAll(/from\s+'(\.\/[A-Za-z0-9_.-]+\.js)'/g)) {
24
+ const dep = m[1].slice(2)
25
+ if (!readdirSync(dest).includes(dep)) copyGraph(preset, dep, dest)
26
+ }
27
+ }
28
+ const skipped = []
29
+ function runFamily(f) {
30
+ const dest = join(tmpdir(), 'a1-mut-' + Math.random().toString(36).slice(2, 10))
31
+ mkdirSync(dest, { recursive: true })
32
+ copyGraph(f.preset, f.preset + '.js', dest)
33
+ const target = join(dest, 'math-computation.js')
34
+ const before = readFileSync(target, 'utf8')
35
+ let mutated = before
36
+ for (const [from, to] of f.edits) {
37
+ const n = mutated.split(from).length - 1
38
+ if (n !== 1) { skipped.push(f.name + ' (' + from.slice(0, 24) + ' x' + n + ')'); console.error(' SKIP - ' + f.name + ' (anchor x' + n + ')'); rmSync(dest, { recursive: true, force: true }); return false }
39
+ mutated = mutated.split(from).join(to)
40
+ }
41
+ writeFileSync(target, mutated, 'utf8')
42
+ try { execFileSync(process.execPath, ['--check', target], { stdio: 'pipe' }) }
43
+ catch (e) { writeFileSync(target, before, 'utf8'); skipped.push(f.name + ' (mutation did not compile)'); console.error(' SKIP - ' + f.name + ' (mutation did not compile; restored)'); rmSync(dest, { recursive: true, force: true }); return false }
44
+ let out = ''
45
+ let code = 0
46
+ try { out = execFileSync(process.execPath, [f.suite], { cwd: REPO, encoding: 'utf8', timeout: CHILD_TIMEOUT_MS, killSignal: 'SIGKILL', env: Object.assign({}, process.env, { [f.env]: join(dest, f.preset + '.js') }) }) }
47
+ 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) || '') }
48
+ const named = out.split(/\r?\n/).filter((l) => /^\s*(FAIL - | {2}- )/.test(l) && f.expect.test(l)).map((l) => l.trim())
49
+ const ok = code !== 0 && named.length > 0
50
+ console.log((ok ? ' ok - ' : ' FAIL - ') + f.name + (named.length ? ' :: ' + named[0].slice(0, 135) : ' :: no named red (exit=' + code + ')'))
51
+ rmSync(dest, { recursive: true, force: true })
52
+ return ok
53
+ }
54
+ const EDITS = [
55
+ [' packageManager: mgr,', ' packageManager: null,'],
56
+ [' packageManagerAvailable: mgr ? available : null,', ' packageManagerAvailable: null,'],
57
+ ]
58
+ const FAMILIES = [
59
+ { name: 'A1-v2 (packageManager nulled)', preset: 'vibe-math-v2', suite: 'tests/math-computation-v2.test.mjs', env: 'MC_V2_PLUGIN', edits: EDITS,
60
+ expect: /A1:「包管理器存在 ⇒ 可执行命令」这半契约必须真的跑过/ },
61
+ { name: 'A1-v3 (packageManager nulled)', preset: 'vibe-math-v3', suite: 'tests/math-computation-v3.test.mjs', env: 'MC_V3_PLUGIN', edits: EDITS,
62
+ expect: /A1:「包管理器存在 ⇒ 可执行命令」这半契约必须真的跑过/ },
63
+ ]
64
+ let red = 0
65
+ 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++ }
66
+ console.log('')
67
+ console.log('mutant families reddening the A1 contract by name: ' + red + '/' + FAMILIES.length)
68
+ console.log('skipped=[' + skipped.join(' | ') + ']')
69
+ console.log('timings: ' + TIMES.map((t) => String(t[0]).split(':')[0] + '=' + t[1] + 'ms').join(' '))
70
+ console.log('hangs=[' + hangs.join(' | ') + ']')
71
+ if (red !== FAMILIES.length || skipped.length || hangs.length) process.exit(1)
72
+ console.log('ALL MUTANTS RED AS REQUIRED')
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Mutant harness for tests/math-computation-shared.test.mjs §26 — ARCHIVE / RERUN provenance (ships:
4
+ * `tests/*.mutants.mjs`). Replaces the dev-only draft `_oneoff/auditR2/roundA-mutants.mjs` (a1/a2).
5
+ *
6
+ * §26: re-running an EDITED original source keeps the same archive (attempt >= 2, `scriptChanged`), while
7
+ * pointing at an ARCHIVED copy yields a NEW archive and flags `fileIsArchivedScript` /
8
+ * `ARCHIVED_SCRIPT_RERUN`.
9
+ *
10
+ * Same shape as `math-computation-discovery.mutants.mjs`: copy `math-engines.js` + a ONE-SITE-mutated
11
+ * `math-computation.js` into a private dir, run the SHIPPED suite through `MATH_COMPUTATION_MODULE`, and
12
+ * require the named §26 assertion to redden. Baseline: the same copied pair, UNMUTATED, stays green.
13
+ *
14
+ * Run: node tests/math-computation-archive-rerun.mutants.mjs
15
+ */
16
+ import { mkdtempSync, copyFileSync, readFileSync, writeFileSync, rmSync } from 'node:fs'
17
+ import { tmpdir } from 'node:os'
18
+ import { join } from 'node:path'
19
+ import { spawnSync } from 'node:child_process'
20
+ import { fileURLToPath } from 'node:url'
21
+
22
+ const HERE = fileURLToPath(new URL('./', import.meta.url))
23
+ const REPO = join(HERE, '..')
24
+ const SRC = join(REPO, 'vibe-math-v2')
25
+ const SUITE = join(HERE, 'math-computation-shared.test.mjs')
26
+ let passed = 0, failed = 0
27
+ const ok = (c, l, d) => { if (c) { passed++; console.log(' ok ' + l) } else { failed++; console.log(' FAIL ' + l + (d ? ' — ' + d : '')) } }
28
+
29
+ function runSuite(dir) {
30
+ const r = spawnSync(process.execPath, [SUITE], { cwd: REPO, encoding: 'utf8', maxBuffer: 1 << 26, env: Object.assign({}, process.env, { MATH_COMPUTATION_MODULE: join(dir, 'math-computation.js') }) })
31
+ const o = String(r.stdout || '') + String(r.stderr || '')
32
+ return { status: r.status, out: o, failLines: o.split('\n').filter((l) => /^\s*FAIL\b/.test(l)), sum: (o.split('\n').filter((l) => /passed, \d+ failed/.test(l)).slice(-1)[0] || '').trim() }
33
+ }
34
+ const CASES = [
35
+ { name: 'a1: the ARCHIVED_SCRIPT_RERUN warning is dropped',
36
+ expect: 'the response warns ARCHIVED_SCRIPT_RERUN',
37
+ edit: (t) => t.replace(/^[ \t]*if \(archivedScriptSource\) warnings\.push\(warning\('ARCHIVED_SCRIPT_RERUN'.*$/m, '') },
38
+ { name: 'a2: an archived source is never detected (always false)',
39
+ expect: 'the response flags fileIsArchivedScript',
40
+ edit: (t) => t.split('archivedScriptSource = /^Computation\\//.test(String(fileRel))').join('archivedScriptSource = false') },
41
+ ]
42
+ {
43
+ const dir = mkdtempSync(join(tmpdir(), 'mc-archive-base-'))
44
+ copyFileSync(join(SRC, 'math-engines.js'), join(dir, 'math-engines.js'))
45
+ copyFileSync(join(SRC, 'math-computation.js'), join(dir, 'math-computation.js'))
46
+ const b = runSuite(dir)
47
+ ok(b.status === 0, 'baseline: the shipped suite is green on the copied module pair (so a red below is the mutation)', b.sum.slice(0, 70))
48
+ rmSync(dir, { recursive: true, force: true })
49
+ }
50
+ const ORIGIN = readFileSync(join(SRC, 'math-computation.js'), 'utf8')
51
+ for (const c of CASES) {
52
+ const mutated = c.edit(ORIGIN)
53
+ if (mutated === ORIGIN) { ok(false, c.name + ' — the single-site anchor applies', 'ANCHOR MISS'); continue }
54
+ const dir = mkdtempSync(join(tmpdir(), 'mc-archive-mut-'))
55
+ copyFileSync(join(SRC, 'math-engines.js'), join(dir, 'math-engines.js'))
56
+ writeFileSync(join(dir, 'math-computation.js'), mutated)
57
+ const r = runSuite(dir)
58
+ const named = r.failLines.some((l) => l.indexOf(c.expect) !== -1)
59
+ ok(r.status !== 0 && named, '★ ' + c.name + ' -> ' + r.sum, named ? '' : 'named red not found (unit: named §26 assertion labels)')
60
+ if (!named) for (const l of r.failLines.slice(0, 3)) console.log(' ' + l.trim().slice(0, 140))
61
+ rmSync(dir, { recursive: true, force: true })
62
+ }
63
+ console.log('')
64
+ console.log('=== MATH ARCHIVE/RERUN MUTANTS: ' + passed + ' passed, ' + failed + ' failed ===')
65
+ process.exit(failed === 0 ? 0 : 1)
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Mutant harness for tests/math-computation-shared.test.mjs §23 / §23b (ENGINE DISCOVERY) and §24 (install
4
+ * suggestions must be runnable). Ships: `tests/*.mutants.mjs`. Replaces the dev-only drafts
5
+ * `_oneoff/auditR2/round9-mutants.mjs` (m3, m4) and `_oneoff/auditR2/roundB-mutants.mjs` (b1/b2).
6
+ *
7
+ * §23: an engine INSTALLED but not on PATH must still be discovered (R's default install dir); §23b: the
8
+ * same walker covers Octave and Julia, and every engine with a well-defined default dir declares per-OS
9
+ * roots. The roots are INJECTED through the host seam, so the fixtures do not depend on this machine's
10
+ * `ProgramFiles`/`LOCALAPPDATA` (the round-B F4 fix).
11
+ *
12
+ * Each case copies `math-engines.js` + a ONE-SITE-mutated `math-computation.js` into a private dir and runs
13
+ * the SHIPPED suite through `MATH_COMPUTATION_MODULE`, requiring the named §23/§23b assertion to redden.
14
+ * Baseline: the same copied setup, UNMUTATED, must stay green (so the mutation is the only red cause).
15
+ *
16
+ * Run: node tests/math-computation-discovery.mutants.mjs
17
+ */
18
+ import { mkdtempSync, copyFileSync, readFileSync, writeFileSync, rmSync } from 'node:fs'
19
+ import { tmpdir } from 'node:os'
20
+ import { join } from 'node:path'
21
+ import { spawnSync } from 'node:child_process'
22
+ import { fileURLToPath } from 'node:url'
23
+
24
+ const HERE = fileURLToPath(new URL('./', import.meta.url))
25
+ const REPO = join(HERE, '..')
26
+ const SRC = join(REPO, 'vibe-math-v2')
27
+ const SUITE = join(HERE, 'math-computation-shared.test.mjs')
28
+ let passed = 0, failed = 0
29
+ const ok = (c, l, d) => { if (c) { passed++; console.log(' ok ' + l) } else { failed++; console.log(' FAIL ' + l + (d ? ' — ' + d : '')) } }
30
+
31
+ function runSuite(dir) {
32
+ const r = spawnSync(process.execPath, [SUITE], { cwd: REPO, encoding: 'utf8', maxBuffer: 1 << 26, env: Object.assign({}, process.env, { MATH_COMPUTATION_MODULE: join(dir, 'math-computation.js') }) })
33
+ const o = String(r.stdout || '') + String(r.stderr || '')
34
+ return { status: r.status, out: o, failLines: o.split('\n').filter((l) => /^\s*FAIL\b/.test(l)), sum: (o.split('\n').filter((l) => /passed, \d+ failed/.test(l)).slice(-1)[0] || '').trim() }
35
+ }
36
+ const M3_FROM = " // round-9: then the known per-OS install locations (an engine installed without touching PATH).\n for (const p of await knownInstallCandidates(H, d.name)) {\n if (typeof p === 'string' && p) return { exe: p, via: 'known-install' }\n }\n"
37
+ const CASES = [
38
+ { name: 'm3: the known-install-location lookup is dropped (PATH + bundled runtime only)',
39
+ expect: 'a Rscript that exists ONLY in the default install dir is discovered',
40
+ edit: (t) => t.replace(M3_FROM, '') },
41
+ { name: 'b1: the host-injected install roots are ignored (back to ambient env)',
42
+ expect: 'a Rscript that exists ONLY in the default install dir is discovered',
43
+ edit: (t) => t.replace('const roots = (Array.isArray(injected) && injected.length) ? injected : mathInstallRoots(engineName, env, win)', 'const roots = mathInstallRoots(engineName, env, win)') },
44
+ { name: 'b2: the octave/julia per-OS root entries are removed',
45
+ expect: 'every engine with a well-defined default install dir declares per-OS roots: octave',
46
+ edit: (t) => t.split('octave: win').join('octaveDisabled: win').split('julia: win').join('juliaDisabled: win') },
47
+ { name: 'm4: a package manager is assumed available (a command that cannot run is offered)',
48
+ expect: 'no runnable-looking command when its package manager is absent',
49
+ edit: (t) => t.replace(" try { available = !!(await H.resolveExecutable(mgr)) } catch (e) { available = false }", ' available = true') },
50
+ ]
51
+
52
+ // baseline: the copied setup, unmutated, must stay green
53
+ {
54
+ const dir = mkdtempSync(join(tmpdir(), 'mc-discovery-base-'))
55
+ copyFileSync(join(SRC, 'math-engines.js'), join(dir, 'math-engines.js'))
56
+ copyFileSync(join(SRC, 'math-computation.js'), join(dir, 'math-computation.js'))
57
+ const b = runSuite(dir)
58
+ ok(b.status === 0, 'baseline: the shipped suite is green on the copied module pair (so a red below is the mutation)', b.sum.slice(0, 70))
59
+ rmSync(dir, { recursive: true, force: true })
60
+ }
61
+ const ORIGIN = readFileSync(join(SRC, 'math-computation.js'), 'utf8').replace(/\r\n/g, '\n') // anchors are LF
62
+ for (const c of CASES) {
63
+ const mutated = c.edit(ORIGIN)
64
+ if (mutated === ORIGIN) { ok(false, c.name + ' — the single-site anchor applies', 'ANCHOR MISS'); continue }
65
+ const dir = mkdtempSync(join(tmpdir(), 'mc-discovery-mut-'))
66
+ copyFileSync(join(SRC, 'math-engines.js'), join(dir, 'math-engines.js'))
67
+ writeFileSync(join(dir, 'math-computation.js'), mutated)
68
+ const r = runSuite(dir)
69
+ const named = r.failLines.some((l) => l.indexOf(c.expect) !== -1)
70
+ ok(r.status !== 0 && named, '★ ' + c.name + ' -> ' + r.sum, named ? '' : 'named red not found (unit: named §23/§23b assertion labels)')
71
+ if (!named) for (const l of r.failLines.slice(0, 3)) console.log(' ' + l.trim().slice(0, 140))
72
+ rmSync(dir, { recursive: true, force: true })
73
+ }
74
+ console.log('')
75
+ console.log('=== MATH DISCOVERY MUTANTS: ' + passed + ' passed, ' + failed + ' failed ===')
76
+ process.exit(failed === 0 ? 0 : 1)