dsh-vibe-math 2.3.12 → 2.3.13

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 (47) hide show
  1. package/README.md +17 -13
  2. package/{AUDIT-CHECKLIST.md → docs/AUDIT-CHECKLIST.md} +323 -304
  3. package/docs/COMPAT-AUDIT-ROUND2.md +325 -0
  4. package/docs/generate_framework_diagram_v2.py +114 -0
  5. package/docs/generate_framework_diagram_v3.py +127 -0
  6. package/{RELEASE-NOTES-2.1.0.md → docs/release-notes/RELEASE-NOTES-2.1.0.md} +143 -143
  7. package/{RELEASE-NOTES-2.2.0.md → docs/release-notes/RELEASE-NOTES-2.2.0.md} +266 -266
  8. package/{RELEASE-NOTES-2.2.1.md → docs/release-notes/RELEASE-NOTES-2.2.1.md} +43 -43
  9. package/{RELEASE-NOTES-2.2.2.md → docs/release-notes/RELEASE-NOTES-2.2.2.md} +88 -88
  10. package/{RELEASE-NOTES-2.3.0.md → docs/release-notes/RELEASE-NOTES-2.3.0.md} +207 -207
  11. package/{RELEASE-NOTES-2.3.1.md → docs/release-notes/RELEASE-NOTES-2.3.1.md} +134 -134
  12. package/{RELEASE-NOTES-2.3.10.md → docs/release-notes/RELEASE-NOTES-2.3.10.md} +105 -105
  13. package/{RELEASE-NOTES-2.3.11.md → docs/release-notes/RELEASE-NOTES-2.3.11.md} +57 -57
  14. package/{RELEASE-NOTES-2.3.12.md → docs/release-notes/RELEASE-NOTES-2.3.12.md} +80 -80
  15. package/docs/release-notes/RELEASE-NOTES-2.3.13.md +137 -0
  16. package/{RELEASE-NOTES-2.3.2.md → docs/release-notes/RELEASE-NOTES-2.3.2.md} +145 -145
  17. package/{RELEASE-NOTES-2.3.3.md → docs/release-notes/RELEASE-NOTES-2.3.3.md} +115 -115
  18. package/{RELEASE-NOTES-2.3.4.md → docs/release-notes/RELEASE-NOTES-2.3.4.md} +69 -69
  19. package/{RELEASE-NOTES-2.3.5.md → docs/release-notes/RELEASE-NOTES-2.3.5.md} +63 -63
  20. package/{RELEASE-NOTES-2.3.6.md → docs/release-notes/RELEASE-NOTES-2.3.6.md} +66 -66
  21. package/{RELEASE-NOTES-2.3.7.md → docs/release-notes/RELEASE-NOTES-2.3.7.md} +59 -59
  22. package/{RELEASE-NOTES-2.3.8.md → docs/release-notes/RELEASE-NOTES-2.3.8.md} +45 -45
  23. package/{RELEASE-NOTES-2.3.9.md → docs/release-notes/RELEASE-NOTES-2.3.9.md} +70 -70
  24. package/docs/test-timing.md +18 -17
  25. package/installer.js +105 -41
  26. package/package.json +42 -37
  27. package/{audit-formal-sensitivity.mjs → tests/audit-formal-sensitivity.mjs} +342 -342
  28. package/{audit-installer-compat.test.mjs → tests/audit-installer-compat.test.mjs} +136 -136
  29. package/tests/audit-installer-policy.test.mjs +245 -0
  30. package/{audit-persona-sensitivity.mjs → tests/audit-persona-sensitivity.mjs} +249 -249
  31. package/{audit-persona-surface.test.mjs → tests/audit-persona-surface.test.mjs} +349 -349
  32. package/{audit-prompt-invariants.mjs → tests/audit-prompt-invariants.mjs} +508 -508
  33. package/{audit-spec-traceability.mjs → tests/audit-spec-traceability.mjs} +193 -193
  34. package/{audit-v5-integrity.mjs → tests/audit-v5-integrity.mjs} +448 -448
  35. package/{audit-v5-sensitivity.mjs → tests/audit-v5-sensitivity.mjs} +384 -384
  36. package/{e2e-v5-round2.test.mjs → tests/e2e-v5-round2.test.mjs} +521 -521
  37. package/{formal-verify-v2.test.mjs → tests/formal-verify-v2.test.mjs} +1315 -1315
  38. package/{formal-verify-v3.test.mjs → tests/formal-verify-v3.test.mjs} +1257 -1257
  39. package/{formal-verify-v4.test.mjs → tests/formal-verify-v4.test.mjs} +1082 -1082
  40. package/{formal-verify-v5.test.mjs → tests/formal-verify-v5.test.mjs} +708 -708
  41. package/{prompt-v5-integrity.test.mjs → tests/prompt-v5-integrity.test.mjs} +3 -3
  42. package/{run-tests.mjs → tests/run-tests.mjs} +121 -118
  43. package/{selfdrive-v5.mjs → tests/selfdrive-v5.mjs} +470 -470
  44. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1 -1
  45. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1 -1
  46. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +1 -1
  47. /package/{RELEASE-NOTES-2.0.22.md → docs/release-notes/RELEASE-NOTES-2.0.22.md} +0 -0
@@ -22,7 +22,7 @@
22
22
  // Each case runs in its OWN session root, so one case can never leave a meeting or a
23
23
  // verification in flight to pollute the next one.
24
24
  //
25
- // Run: node prompt-v5-integrity.test.mjs
25
+ // Run: node tests/prompt-v5-integrity.test.mjs
26
26
  // Env: V5_PLUGIN=<abs path> point the suite at a mutated copy (sensitivity probes)
27
27
  // V5_CORPUS_DIR=<dir> where to write the corpus (default: ./prompt-corpus-v5)
28
28
  // ============================================================
@@ -37,8 +37,8 @@ import { fileURLToPath } from 'node:url'
37
37
  const HERE = dirname(fileURLToPath(import.meta.url))
38
38
  const PLUGIN = process.env.V5_PLUGIN
39
39
  ? new URL('file:///' + String(process.env.V5_PLUGIN).replace(/\\/g, '/'))
40
- : new URL('./vibe-math-v5/vibe-math-v5.js', import.meta.url)
41
- const CORPUS_DIR = process.env.V5_CORPUS_DIR ? pathResolve(process.env.V5_CORPUS_DIR) : join(HERE, 'prompt-corpus-v5')
40
+ : new URL('../vibe-math-v5/vibe-math-v5.js', import.meta.url)
41
+ const CORPUS_DIR = process.env.V5_CORPUS_DIR ? pathResolve(process.env.V5_CORPUS_DIR) : join(HERE, '..', 'prompt-corpus-v5')
42
42
  const WS = mkdtempSync(join(tmpdir(), 'vibe-v5-prompt-'))
43
43
 
44
44
  let passed = 0, failed = 0
@@ -1,118 +1,121 @@
1
- #!/usr/bin/env node
2
- /**
3
- * PARALLEL TEST RUNNER — run every shipped suite (or a filtered subset) concurrently and report
4
- * per-suite timings, so the strategy for the next run is chosen from DATA instead of guesswork.
5
- *
6
- * Why this exists: the suites are wildly uneven (one suite is ~160 s, most are under 2 s), so a
7
- * sequential sweep spends almost all of its wall time waiting for the slowest one. Running them
8
- * with a worker pool makes the sweep bounded by the slowest SUITE rather than by their SUM.
9
- * On this machine (4 cores) the sweep went from ~5.5 min to ~2 min; see docs/test-timing.md.
10
- *
11
- * Every suite already isolates itself (each creates its own mkdtemp workspace), so parallelism is
12
- * safe. Suites that WRITE a corpus take a per-run corpus dir from an env var; this runner points
13
- * them at a scratch dir when it runs a suite more than once, which it never does — but the probe
14
- * runner (audit-formal-sensitivity.mjs) does, and it passes its own dirs.
15
- *
16
- * Usage:
17
- * node run-tests.mjs # every *.test.mjs, concurrency = min(4, cpus)
18
- * node run-tests.mjs --concurrency=6
19
- * node run-tests.mjs --only formal # substring match on the file name (repeatable, OR)
20
- * node run-tests.mjs --exclude e2e-v4 # substring to skip (repeatable)
21
- * node run-tests.mjs --json # machine-readable summary on stdout
22
- */
23
- import { spawn } from 'node:child_process'
24
- import { readdirSync } from 'node:fs'
25
- import { cpus } from 'node:os'
26
- import { join } from 'node:path'
27
- import { fileURLToPath } from 'node:url'
28
-
29
- const HERE = fileURLToPath(new URL('./', import.meta.url))
30
- const argv = process.argv.slice(2)
31
- // Accept BOTH `--only=x` and `--only x` (the help text used the space form, which a value-taking
32
- // flag() did not understand — the filter silently did nothing and every suite still ran).
33
- const flag = (name) => {
34
- const out = []
35
- for (let i = 0; i < argv.length; i++) {
36
- const a = argv[i]
37
- if (a === '--' + name && i + 1 < argv.length && !argv[i + 1].startsWith('--')) out.push(argv[++i])
38
- else if (a.startsWith('--' + name + '=')) out.push(a.split('=').slice(1).join('='))
39
- }
40
- return out
41
- }
42
- const has = (name) => argv.includes('--' + name)
43
- const only = flag('only')
44
- const exclude = flag('exclude')
45
- const asJson = has('json')
46
- const concurrency = Math.max(1, Number(flag('concurrency')[0] || Math.min(4, cpus().length)))
47
-
48
- let suites = readdirSync(HERE).filter((f) => f.endsWith('.test.mjs')).sort()
49
- if (only.length) suites = suites.filter((f) => only.some((o) => f.includes(o)))
50
- if (exclude.length) suites = suites.filter((f) => !exclude.some((o) => f.includes(o)))
51
- if (!suites.length) { console.error('no suites matched'); process.exit(2) }
52
-
53
- function runSuite(file) {
54
- return new Promise((resolve) => {
55
- const t0 = Date.now()
56
- const child = spawn(process.execPath, [file], { cwd: HERE, stdio: ['ignore', 'pipe', 'pipe'] })
57
- let out = '', err = ''
58
- child.stdout.on('data', (d) => { out += d.toString() })
59
- child.stderr.on('data', (d) => { err += d.toString() })
60
- child.on('error', (e) => resolve({ file, code: -1, ms: Date.now() - t0, out, err: err + '\n' + String(e) }))
61
- child.on('close', (code) => resolve({ file, code, ms: Date.now() - t0, out, err }))
62
- })
63
- }
64
-
65
- const results = []
66
- let cursor = 0
67
- const started = Date.now()
68
- async function worker(id) {
69
- for (;;) {
70
- const i = cursor++
71
- if (i >= suites.length) return
72
- const r = await runSuite(suites[i])
73
- const tail = String(r.out).trim().split('\n').filter(Boolean).slice(-1)[0] || ''
74
- results[i] = r
75
- if (!asJson) {
76
- const mark = r.code === 0 ? 'PASS' : 'FAIL'
77
- console.log(
78
- mark + ' ' + r.file.padEnd(38) +
79
- ' exit=' + String(r.code).padStart(3) +
80
- ' ' + (r.ms / 1000).toFixed(1).padStart(6) + 's' +
81
- (tail ? ' ' + tail.slice(0, 78) : '')
82
- )
83
- if (r.code !== 0) {
84
- const lines = (r.out + '\n' + r.err).split('\n').filter(Boolean)
85
- for (const l of lines.slice(-15)) console.log(' ' + l)
86
- }
87
- } else if (r.code !== 0) {
88
- // In --json mode keep stdout machine-readable: the failure detail travels in the JSON.
89
- const lines = (r.out + '\n' + r.err).split('\n').filter(Boolean)
90
- r.tailDetail = lines.slice(-15).join('\n')
91
- }
92
- }
93
- }
94
- await Promise.all(Array.from({ length: Math.min(concurrency, suites.length) }, (_, i) => worker(i)))
95
-
96
- const wall = (Date.now() - started) / 1000
97
- const sum = results.reduce((a, r) => a + r.ms, 0) / 1000
98
- const bad = results.filter((r) => r.code !== 0)
99
- const slowest = results.slice().sort((a, b) => b.ms - a.ms).slice(0, 5)
100
-
101
- if (asJson) {
102
- console.log(JSON.stringify({
103
- concurrency, wallSeconds: Number(wall.toFixed(1)), sumSeconds: Number(sum.toFixed(1)),
104
- pass: results.length - bad.length, fail: bad.length,
105
- suites: results.map((r) => ({
106
- file: r.file, exit: r.code, seconds: Number((r.ms / 1000).toFixed(1)),
107
- ...(r.tailDetail ? { detail: r.tailDetail } : {}),
108
- })),
109
- }, null, 2))
110
- } else {
111
- console.log('')
112
- console.log('concurrency ' + concurrency + ' · wall ' + wall.toFixed(1) + 's · sum of suite times ' + sum.toFixed(1) + 's'
113
- + ' · speed-up x' + (sum / Math.max(wall, 0.001)).toFixed(2))
114
- console.log('slowest: ' + slowest.map((r) => r.file.replace('.test.mjs', '') + ' ' + (r.ms / 1000).toFixed(1) + 's').join(' · '))
115
- console.log('TOTAL ' + results.length + ' PASS ' + (results.length - bad.length) + ' FAIL ' + bad.length)
116
- for (const b of bad) console.log(' FAILED: ' + b.file + ' (exit ' + b.code + ')')
117
- }
118
- process.exit(bad.length === 0 ? 0 : 1)
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PARALLEL TEST RUNNER — run every shipped suite (or a filtered subset) concurrently and report
4
+ * per-suite timings, so the strategy for the next run is chosen from DATA instead of guesswork.
5
+ *
6
+ * Why this exists: the suites are wildly uneven (one suite is ~160 s, most are under 2 s), so a
7
+ * sequential sweep spends almost all of its wall time waiting for the slowest one. Running them
8
+ * with a worker pool makes the sweep bounded by the slowest SUITE rather than by their SUM.
9
+ * On this machine (4 cores) the sweep went from ~5.5 min to ~2 min; see ../docs/test-timing.md.
10
+ *
11
+ * Every suite already isolates itself (each creates its own mkdtemp workspace), so parallelism is
12
+ * safe. Suites that WRITE a corpus take a per-run corpus dir from an env var; this runner points
13
+ * them at a scratch dir when it runs a suite more than once, which it never does — but the probe
14
+ * runner (audit-formal-sensitivity.mjs) does, and it passes its own dirs.
15
+ *
16
+ * Usage:
17
+ * node tests/run-tests.mjs # every *.test.mjs, concurrency = min(4, cpus)
18
+ * node tests/run-tests.mjs --concurrency=6
19
+ * node tests/run-tests.mjs --only formal # substring match on the file name (repeatable, OR)
20
+ * node tests/run-tests.mjs --exclude e2e-v4 # substring to skip (repeatable)
21
+ * node tests/run-tests.mjs --json # machine-readable summary on stdout
22
+ */
23
+ import { spawn } from 'node:child_process'
24
+ import { readdirSync } from 'node:fs'
25
+ import { cpus } from 'node:os'
26
+ import { join } from 'node:path'
27
+ import { fileURLToPath } from 'node:url'
28
+
29
+ const HERE = fileURLToPath(new URL('./', import.meta.url))
30
+ // Suites live beside this runner, but they resolve the presets and the corpora from the
31
+ // repository root, so they must keep running with that as their working directory.
32
+ const REPO = fileURLToPath(new URL('../', import.meta.url))
33
+ const argv = process.argv.slice(2)
34
+ // Accept BOTH `--only=x` and `--only x` (the help text used the space form, which a value-taking
35
+ // flag() did not understand — the filter silently did nothing and every suite still ran).
36
+ const flag = (name) => {
37
+ const out = []
38
+ for (let i = 0; i < argv.length; i++) {
39
+ const a = argv[i]
40
+ if (a === '--' + name && i + 1 < argv.length && !argv[i + 1].startsWith('--')) out.push(argv[++i])
41
+ else if (a.startsWith('--' + name + '=')) out.push(a.split('=').slice(1).join('='))
42
+ }
43
+ return out
44
+ }
45
+ const has = (name) => argv.includes('--' + name)
46
+ const only = flag('only')
47
+ const exclude = flag('exclude')
48
+ const asJson = has('json')
49
+ const concurrency = Math.max(1, Number(flag('concurrency')[0] || Math.min(4, cpus().length)))
50
+
51
+ let suites = readdirSync(HERE).filter((f) => f.endsWith('.test.mjs')).sort()
52
+ if (only.length) suites = suites.filter((f) => only.some((o) => f.includes(o)))
53
+ if (exclude.length) suites = suites.filter((f) => !exclude.some((o) => f.includes(o)))
54
+ if (!suites.length) { console.error('no suites matched'); process.exit(2) }
55
+
56
+ function runSuite(file) {
57
+ return new Promise((resolve) => {
58
+ const t0 = Date.now()
59
+ const child = spawn(process.execPath, [join(HERE, file)], { cwd: REPO, stdio: ['ignore', 'pipe', 'pipe'] })
60
+ let out = '', err = ''
61
+ child.stdout.on('data', (d) => { out += d.toString() })
62
+ child.stderr.on('data', (d) => { err += d.toString() })
63
+ child.on('error', (e) => resolve({ file, code: -1, ms: Date.now() - t0, out, err: err + '\n' + String(e) }))
64
+ child.on('close', (code) => resolve({ file, code, ms: Date.now() - t0, out, err }))
65
+ })
66
+ }
67
+
68
+ const results = []
69
+ let cursor = 0
70
+ const started = Date.now()
71
+ async function worker(id) {
72
+ for (;;) {
73
+ const i = cursor++
74
+ if (i >= suites.length) return
75
+ const r = await runSuite(suites[i])
76
+ const tail = String(r.out).trim().split('\n').filter(Boolean).slice(-1)[0] || ''
77
+ results[i] = r
78
+ if (!asJson) {
79
+ const mark = r.code === 0 ? 'PASS' : 'FAIL'
80
+ console.log(
81
+ mark + ' ' + r.file.padEnd(38) +
82
+ ' exit=' + String(r.code).padStart(3) +
83
+ ' ' + (r.ms / 1000).toFixed(1).padStart(6) + 's' +
84
+ (tail ? ' ' + tail.slice(0, 78) : '')
85
+ )
86
+ if (r.code !== 0) {
87
+ const lines = (r.out + '\n' + r.err).split('\n').filter(Boolean)
88
+ for (const l of lines.slice(-15)) console.log(' ' + l)
89
+ }
90
+ } else if (r.code !== 0) {
91
+ // In --json mode keep stdout machine-readable: the failure detail travels in the JSON.
92
+ const lines = (r.out + '\n' + r.err).split('\n').filter(Boolean)
93
+ r.tailDetail = lines.slice(-15).join('\n')
94
+ }
95
+ }
96
+ }
97
+ await Promise.all(Array.from({ length: Math.min(concurrency, suites.length) }, (_, i) => worker(i)))
98
+
99
+ const wall = (Date.now() - started) / 1000
100
+ const sum = results.reduce((a, r) => a + r.ms, 0) / 1000
101
+ const bad = results.filter((r) => r.code !== 0)
102
+ const slowest = results.slice().sort((a, b) => b.ms - a.ms).slice(0, 5)
103
+
104
+ if (asJson) {
105
+ console.log(JSON.stringify({
106
+ concurrency, wallSeconds: Number(wall.toFixed(1)), sumSeconds: Number(sum.toFixed(1)),
107
+ pass: results.length - bad.length, fail: bad.length,
108
+ suites: results.map((r) => ({
109
+ file: r.file, exit: r.code, seconds: Number((r.ms / 1000).toFixed(1)),
110
+ ...(r.tailDetail ? { detail: r.tailDetail } : {}),
111
+ })),
112
+ }, null, 2))
113
+ } else {
114
+ console.log('')
115
+ console.log('concurrency ' + concurrency + ' · wall ' + wall.toFixed(1) + 's · sum of suite times ' + sum.toFixed(1) + 's'
116
+ + ' · speed-up x' + (sum / Math.max(wall, 0.001)).toFixed(2))
117
+ console.log('slowest: ' + slowest.map((r) => r.file.replace('.test.mjs', '') + ' ' + (r.ms / 1000).toFixed(1) + 's').join(' · '))
118
+ console.log('TOTAL ' + results.length + ' PASS ' + (results.length - bad.length) + ' FAIL ' + bad.length)
119
+ for (const b of bad) console.log(' FAILED: ' + b.file + ' (exit ' + b.code + ')')
120
+ }
121
+ process.exit(bad.length === 0 ? 0 : 1)