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.
- package/LICENSE +21 -21
- package/README.en.md +682 -682
- package/README.md +683 -683
- package/docs/AUDIT-CHECKLIST.md +755 -744
- package/docs/COMPAT-AUDIT-ROUND2.md +351 -351
- package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
- package/docs/audit-methodology.md +93 -0
- package/docs/final-paper.md +172 -172
- package/docs/formal-verification.md +613 -588
- package/docs/generate_framework_diagram_v2.py +114 -114
- package/docs/generate_framework_diagram_v3.py +127 -127
- package/docs/math-computation.md +195 -194
- package/docs/release-notes/README.en.md +1 -0
- package/docs/release-notes/README.md +1 -0
- package/docs/release-notes/RELEASE-NOTES-2.0.22.md +112 -112
- package/docs/release-notes/RELEASE-NOTES-2.1.0.md +143 -143
- package/docs/release-notes/RELEASE-NOTES-2.2.0.md +266 -266
- package/docs/release-notes/RELEASE-NOTES-2.2.1.md +43 -43
- package/docs/release-notes/RELEASE-NOTES-2.2.2.md +88 -88
- package/docs/release-notes/RELEASE-NOTES-2.3.0.md +207 -207
- package/docs/release-notes/RELEASE-NOTES-2.3.1.md +134 -134
- package/docs/release-notes/RELEASE-NOTES-2.3.10.md +105 -105
- package/docs/release-notes/RELEASE-NOTES-2.3.11.md +57 -57
- package/docs/release-notes/RELEASE-NOTES-2.3.12.md +80 -80
- package/docs/release-notes/RELEASE-NOTES-2.3.2.md +145 -145
- package/docs/release-notes/RELEASE-NOTES-2.3.3.md +115 -115
- package/docs/release-notes/RELEASE-NOTES-2.3.4.md +69 -69
- package/docs/release-notes/RELEASE-NOTES-2.3.5.md +63 -63
- package/docs/release-notes/RELEASE-NOTES-2.3.6.md +66 -66
- package/docs/release-notes/RELEASE-NOTES-2.3.7.md +59 -59
- package/docs/release-notes/RELEASE-NOTES-2.3.8.md +45 -45
- package/docs/release-notes/RELEASE-NOTES-2.3.9.md +70 -70
- package/docs/release-notes/RELEASE-NOTES-2.8.0.en.md +65 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.0.md +65 -0
- package/docs/release-playbook.md +103 -0
- package/docs/slv-playbook.md +117 -0
- package/docs/test-timing.md +158 -157
- package/docs//346/236/266/346/236/204/345/233/276.md +77 -77
- package/installer.js +792 -792
- package/package.json +13 -3
- package/prompt-corpus-v2/formal-verify-v2.json +2 -2
- package/prompt-corpus-v2/formal-verify-v2.md +4 -0
- package/prompt-corpus-v3/formal-verify-v3.json +7 -7
- package/prompt-corpus-v3/formal-verify-v3.md +14 -0
- package/prompt-corpus-v5/prompt-corpus-v5.json +61 -61
- package/prompt-corpus-v5/prompt-corpus-v5.md +122 -0
- package/scripts/clean-temp.mjs +234 -0
- package/tests/audit-engine-faces.mjs +298 -0
- package/tests/audit-formal-sensitivity.mjs +345 -342
- package/tests/audit-installer-compat.test.mjs +256 -256
- package/tests/audit-math-computation-sensitivity.mjs +1 -1
- package/tests/audit-persona-sensitivity.mjs +323 -323
- package/tests/audit-persona-surface.test.mjs +431 -431
- package/tests/audit-prompt-duplication.mutants.mjs +3 -2
- package/tests/audit-prompt-invariants.mjs +589 -546
- package/tests/audit-registration.mjs +78 -78
- package/tests/audit-spec-traceability.mjs +193 -193
- package/tests/audit-status-report-fields.mjs +172 -172
- package/tests/audit-v5-integrity.mjs +795 -795
- package/tests/audit-v5-sensitivity.mjs +433 -432
- package/tests/e2e-v5-round2.test.mjs +2453 -2426
- package/tests/formal-verify-v2.test.mjs +1806 -1800
- package/tests/formal-verify-v3.test.mjs +1757 -1750
- package/tests/formal-verify-v4.test.mjs +2114 -2106
- package/tests/formal-verify-v5.test.mjs +774 -767
- package/tests/math-computation-discovery.mutants.mjs +3 -3
- package/tests/math-computation-shared.test.mjs +122 -5
- package/tests/math-computation-v2.test.mjs +14 -2
- package/tests/math-computation-v3.test.mjs +14 -2
- package/tests/math-computation-v4.test.mjs +739 -739
- package/tests/prompt-v5-integrity.test.mjs +1291 -1291
- package/tests/run-tests.mjs +393 -332
- package/tests/selfdrive-v5.mjs +560 -560
- package/tests/temp-hygiene.mutants.mjs +80 -0
- package/tests/v2-fix-probes.mutants.mjs +4 -0
- package/tests/v2-fix-probes.test.mjs +1034 -1033
- package/tests/v2v3-interrupt.mutants.mjs +111 -0
- package/tests/v3-fix-probes.mutants.mjs +99 -95
- package/tests/v3-fix-probes.test.mjs +891 -890
- package/tests/v4-final-paper.mutants.mjs +108 -0
- package/tests/v5-institute-fixes.mutants.mjs +218 -190
- package/vibe-math-v2/agent.cordis.yml +376 -376
- package/vibe-math-v2/math-computation.js +17 -15
- package/vibe-math-v2/preset.yml +2 -2
- package/vibe-math-v2/v2/347/232/204/351/242/235/345/244/226/350/246/201/346/261/202.txt +21 -21
- package/vibe-math-v2/vibe-math-v2.js +5078 -5011
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +486 -486
- package/vibe-math-v3/agent.cordis.yml +420 -420
- package/vibe-math-v3/math-computation.js +17 -15
- package/vibe-math-v3/preset.yml +2 -2
- package/vibe-math-v3/vibe-math-v3.js +6215 -6149
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +649 -649
- package/vibe-math-v4/agent.cordis.yml +438 -438
- package/vibe-math-v4/math-computation.js +17 -15
- package/vibe-math-v4/preset.yml +2 -2
- package/vibe-math-v4/vibe-math-v4.js +4490 -4422
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1015 -1015
- package/vibe-math-v5/math-computation.js +17 -15
- package/vibe-math-v5/vibe-math-v5.js +93 -11
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1728
- 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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
console.log('
|
|
94
|
-
|
|
95
|
-
console.log('
|
|
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')
|