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,108 @@
|
|
|
1
|
+
// Registered mutant harness for the v4 final-paper path — the suite carries its own proof.
|
|
2
|
+
// Each family is a SINGLE content-anchored mutation of the v4 preset (no line numbers); the suite runs
|
|
3
|
+
// through the V4_PLUGIN seam. Child runs are bounded (a timeout is a HANG); skipped families are printed
|
|
4
|
+
// and counted. Exit criterion: every family reddens BY NAME; ALL MUTANTS RED AS REQUIRED + exit 0 only then.
|
|
5
|
+
// Run: node tests/v4-final-paper.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
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
12
|
+
const REPO = join(HERE, '..')
|
|
13
|
+
const PRESET = 'vibe-math-v4'
|
|
14
|
+
const MAIN = PRESET + '.js'
|
|
15
|
+
const SUITE = 'tests/v4-final-paper.test.mjs'
|
|
16
|
+
const ENV = 'V4_PLUGIN'
|
|
17
|
+
const START_MS = Date.now()
|
|
18
|
+
// This suite is long, so the default child budget is generous; MUTANT_CHILD_TIMEOUT_MS overrides it.
|
|
19
|
+
const CHILD_TIMEOUT_MS = Number(process.env.MUTANT_CHILD_TIMEOUT_MS || 300000)
|
|
20
|
+
const TIMES = []
|
|
21
|
+
const hangs = []
|
|
22
|
+
const skipped = []
|
|
23
|
+
function copyGraph(file, dest) {
|
|
24
|
+
const src = join(REPO, PRESET, file)
|
|
25
|
+
copyFileSync(src, join(dest, file))
|
|
26
|
+
for (const m of readFileSync(src, 'utf8').matchAll(/from\s+'(\.\/[A-Za-z0-9_.-]+\.js)'/g)) {
|
|
27
|
+
const dep = m[1].slice(2)
|
|
28
|
+
if (!readdirSync(dest).includes(dep)) copyGraph(dep, dest)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function runFamily(f) {
|
|
32
|
+
const dest = join(tmpdir(), 'v4paper-mut-' + Math.random().toString(36).slice(2, 10))
|
|
33
|
+
mkdirSync(dest, { recursive: true })
|
|
34
|
+
copyGraph(MAIN, dest)
|
|
35
|
+
const target = join(dest, f.editFile || MAIN)
|
|
36
|
+
const before = readFileSync(target, 'utf8').replace(/\r\n?/g, '\n') // task-7: anchors are LF; a CRLF checkout made them match 0 times
|
|
37
|
+
if (/\r/.test(before)) { console.error('SETUP-FAIL - the anchor source was not EOL-normalised (CRLF leaked into anchor matching)'); process.exit(1) }
|
|
38
|
+
const n = before.split(f.from).length - 1
|
|
39
|
+
const okCount = f.lastOnly || f.all ? n >= 1 : n === 1
|
|
40
|
+
if (!okCount) {
|
|
41
|
+
skipped.push(f.name + ' (anchor x' + n + ')')
|
|
42
|
+
console.error(' SKIP - ' + f.name + ' (anchor x' + n + ')')
|
|
43
|
+
rmSync(dest, { recursive: true, force: true })
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
const mutated = f.lastOnly
|
|
47
|
+
? (() => { const i = before.lastIndexOf(f.from); return before.slice(0, i) + f.to + before.slice(i + f.from.length) })()
|
|
48
|
+
: before.split(f.from).join(f.to)
|
|
49
|
+
writeFileSync(target, mutated, 'utf8')
|
|
50
|
+
try { execFileSync(process.execPath, ['--check', target], { stdio: 'pipe' }) }
|
|
51
|
+
catch (e) {
|
|
52
|
+
writeFileSync(target, before, 'utf8')
|
|
53
|
+
skipped.push(f.name + ' (mutation did not compile)')
|
|
54
|
+
console.error(' SKIP - ' + f.name + ' (mutation did not compile; restored)')
|
|
55
|
+
rmSync(dest, { recursive: true, force: true })
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
const t0 = Date.now()
|
|
59
|
+
let out = ''
|
|
60
|
+
let code = 0
|
|
61
|
+
let hang = false
|
|
62
|
+
try {
|
|
63
|
+
out = execFileSync(process.execPath, [SUITE], {
|
|
64
|
+
cwd: REPO, encoding: 'utf8', timeout: CHILD_TIMEOUT_MS, killSignal: 'SIGKILL',
|
|
65
|
+
env: Object.assign({}, process.env, { [ENV]: join(dest, MAIN) }),
|
|
66
|
+
})
|
|
67
|
+
} catch (e) {
|
|
68
|
+
if (e && (e.killed || e.signal === 'SIGKILL')) hang = true
|
|
69
|
+
code = (e && e.status) || 1
|
|
70
|
+
out = String((e && e.stdout) || '') + String((e && e.stderr) || '')
|
|
71
|
+
}
|
|
72
|
+
const ms = Date.now() - t0
|
|
73
|
+
TIMES.push([f.name, ms])
|
|
74
|
+
if (hang) hangs.push(f.name + '(' + Math.round(ms / 1000) + 's)')
|
|
75
|
+
const named = out.split(/\r?\n/).filter((l) => /^\s*(FAIL - | {2}- )/.test(l) && f.expect.test(l)).map((l) => l.trim())
|
|
76
|
+
const ok = !hang && code !== 0 && named.length > 0
|
|
77
|
+
console.log((hang ? ' HANG - ' : (ok ? ' ok - ' : ' FAIL - ')) + f.name + ' [' + ms + 'ms]' + (named.length ? ' :: ' + named[0].slice(0, 150) : ' :: no named red (exit=' + code + ')'))
|
|
78
|
+
rmSync(dest, { recursive: true, force: true })
|
|
79
|
+
return ok
|
|
80
|
+
}
|
|
81
|
+
const FAMILIES = [
|
|
82
|
+
{
|
|
83
|
+
// task-11: the not-detected case must WARN on the reachable surfaces (finalize return + status view).
|
|
84
|
+
// Dropping the appended note must redden the guarding assertion BY NAME.
|
|
85
|
+
name: 'task-11/v4: the not-detected warning drops the actionable sentence',
|
|
86
|
+
from: '已探测 PATH 与文档化的常见 TeX 根;可用 paperLatexCommand 指定绝对路径。',
|
|
87
|
+
to: '',
|
|
88
|
+
expect: /\[task-9\/v4\]/,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
// task-10: restoring the merged order lets an unresolvable explicit command fall through to another
|
|
92
|
+
// engine again (the documented rule is "explicit ⇒ only it").
|
|
93
|
+
name: 'task-10/v4: the explicit LaTeX command falls through to another engine again',
|
|
94
|
+
from: 'const order=prefer?[prefer]:base',
|
|
95
|
+
to: 'const order=(prefer?[prefer]:[]).concat(base.filter(x=>x!==prefer))',
|
|
96
|
+
expect: /\[task-10\/v4\]/,
|
|
97
|
+
},
|
|
98
|
+
]
|
|
99
|
+
let red = 0
|
|
100
|
+
for (const f of FAMILIES) { const t0 = Date.now(); const ok = runFamily(f); const ms = Date.now() - t0; if (ok) red++ }
|
|
101
|
+
console.log('')
|
|
102
|
+
console.log('mutant families reddening the v4 final-paper path by name: ' + red + '/' + FAMILIES.length)
|
|
103
|
+
console.log('skipped=[' + skipped.join(' | ') + ']')
|
|
104
|
+
console.log('timings: ' + TIMES.map((t) => String(t[0]).split(':')[0] + '=' + t[1] + 'ms').join(' '))
|
|
105
|
+
console.log('TOTAL WALL TIME (all families + setup): ' + (Date.now() - START_MS) + 'ms (' + Math.round((Date.now() - START_MS) / 1000) + 's)')
|
|
106
|
+
console.log('hangs=[' + hangs.join(' | ') + ']')
|
|
107
|
+
if (red !== FAMILIES.length || skipped.length || hangs.length) process.exit(1)
|
|
108
|
+
console.log('ALL MUTANTS RED AS REQUIRED')
|
|
@@ -1,190 +1,218 @@
|
|
|
1
|
-
// Registered mutant harness for the v5 institute fixes — the suite carries its own proof.
|
|
2
|
-
// Each family is a SINGLE content-anchored mutation of the v5 preset (no line numbers); the suite runs
|
|
3
|
-
// through the V5_PLUGIN seam. Child runs are bounded (a timeout is a HANG); skipped families are printed
|
|
4
|
-
// and counted. Exit criterion: every family reddens BY NAME; ALL MUTANTS RED AS REQUIRED + exit 0 only then.
|
|
5
|
-
// Run: node tests/v5-institute-fixes.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
|
-
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
12
|
-
const REPO = join(HERE, '..')
|
|
13
|
-
const PRESET = 'vibe-math-v5'
|
|
14
|
-
const MAIN = PRESET + '.js'
|
|
15
|
-
const SUITE = 'tests/e2e-v5-round2.test.mjs'
|
|
16
|
-
const ENV = 'V5_PLUGIN'
|
|
17
|
-
// This suite is long, so the default child budget is generous; MUTANT_CHILD_TIMEOUT_MS overrides it.
|
|
18
|
-
const CHILD_TIMEOUT_MS = Number(process.env.MUTANT_CHILD_TIMEOUT_MS || 300000)
|
|
19
|
-
const TIMES = []
|
|
20
|
-
const hangs = []
|
|
21
|
-
const skipped = []
|
|
22
|
-
function copyGraph(file, dest) {
|
|
23
|
-
const src = join(REPO, PRESET, file)
|
|
24
|
-
copyFileSync(src, join(dest, file))
|
|
25
|
-
for (const m of readFileSync(src, 'utf8').matchAll(/from\s+'(\.\/[A-Za-z0-9_.-]+\.js)'/g)) {
|
|
26
|
-
const dep = m[1].slice(2)
|
|
27
|
-
if (!readdirSync(dest).includes(dep)) copyGraph(dep, dest)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function runFamily(f) {
|
|
31
|
-
const dest = join(tmpdir(), 'v5fix-mut-' + Math.random().toString(36).slice(2, 10))
|
|
32
|
-
mkdirSync(dest, { recursive: true })
|
|
33
|
-
copyGraph(MAIN, dest)
|
|
34
|
-
const target = join(dest, f.editFile || MAIN)
|
|
35
|
-
const before = readFileSync(target, 'utf8')
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
let
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
1
|
+
// Registered mutant harness for the v5 institute fixes — the suite carries its own proof.
|
|
2
|
+
// Each family is a SINGLE content-anchored mutation of the v5 preset (no line numbers); the suite runs
|
|
3
|
+
// through the V5_PLUGIN seam. Child runs are bounded (a timeout is a HANG); skipped families are printed
|
|
4
|
+
// and counted. Exit criterion: every family reddens BY NAME; ALL MUTANTS RED AS REQUIRED + exit 0 only then.
|
|
5
|
+
// Run: node tests/v5-institute-fixes.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
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
12
|
+
const REPO = join(HERE, '..')
|
|
13
|
+
const PRESET = 'vibe-math-v5'
|
|
14
|
+
const MAIN = PRESET + '.js'
|
|
15
|
+
const SUITE = 'tests/e2e-v5-round2.test.mjs'
|
|
16
|
+
const ENV = 'V5_PLUGIN'
|
|
17
|
+
// This suite is long, so the default child budget is generous; MUTANT_CHILD_TIMEOUT_MS overrides it.
|
|
18
|
+
const CHILD_TIMEOUT_MS = Number(process.env.MUTANT_CHILD_TIMEOUT_MS || 300000)
|
|
19
|
+
const TIMES = []
|
|
20
|
+
const hangs = []
|
|
21
|
+
const skipped = []
|
|
22
|
+
function copyGraph(file, dest) {
|
|
23
|
+
const src = join(REPO, PRESET, file)
|
|
24
|
+
copyFileSync(src, join(dest, file))
|
|
25
|
+
for (const m of readFileSync(src, 'utf8').matchAll(/from\s+'(\.\/[A-Za-z0-9_.-]+\.js)'/g)) {
|
|
26
|
+
const dep = m[1].slice(2)
|
|
27
|
+
if (!readdirSync(dest).includes(dep)) copyGraph(dep, dest)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function runFamily(f) {
|
|
31
|
+
const dest = join(tmpdir(), 'v5fix-mut-' + Math.random().toString(36).slice(2, 10))
|
|
32
|
+
mkdirSync(dest, { recursive: true })
|
|
33
|
+
copyGraph(MAIN, dest)
|
|
34
|
+
const target = join(dest, f.editFile || MAIN)
|
|
35
|
+
const before = readFileSync(target, 'utf8').replace(/\r\n?/g, '\n') // task-7: anchors are LF; a CRLF checkout made them match 0 times
|
|
36
|
+
if (/\r/.test(before)) { console.error('SETUP-FAIL - the anchor source was not EOL-normalised (CRLF leaked into anchor matching)'); process.exit(1) }
|
|
37
|
+
const n = before.split(f.from).length - 1
|
|
38
|
+
const okCount = f.lastOnly || f.all ? n >= 1 : n === 1
|
|
39
|
+
if (!okCount) {
|
|
40
|
+
skipped.push(f.name + ' (anchor x' + n + ')')
|
|
41
|
+
console.error(' SKIP - ' + f.name + ' (anchor x' + n + ')')
|
|
42
|
+
rmSync(dest, { recursive: true, force: true })
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
const mutated = f.lastOnly
|
|
46
|
+
? (() => { const i = before.lastIndexOf(f.from); return before.slice(0, i) + f.to + before.slice(i + f.from.length) })()
|
|
47
|
+
: before.split(f.from).join(f.to)
|
|
48
|
+
writeFileSync(target, mutated, 'utf8')
|
|
49
|
+
try { execFileSync(process.execPath, ['--check', target], { stdio: 'pipe' }) }
|
|
50
|
+
catch (e) {
|
|
51
|
+
writeFileSync(target, before, 'utf8')
|
|
52
|
+
skipped.push(f.name + ' (mutation did not compile)')
|
|
53
|
+
console.error(' SKIP - ' + f.name + ' (mutation did not compile; restored)')
|
|
54
|
+
rmSync(dest, { recursive: true, force: true })
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
const t0 = Date.now()
|
|
58
|
+
let out = ''
|
|
59
|
+
let code = 0
|
|
60
|
+
let hang = false
|
|
61
|
+
try {
|
|
62
|
+
out = execFileSync(process.execPath, [SUITE], {
|
|
63
|
+
cwd: REPO, encoding: 'utf8', timeout: CHILD_TIMEOUT_MS, killSignal: 'SIGKILL',
|
|
64
|
+
// task-13: a mutated CHILD only has to redden BY NAME, so it runs with a SMALL wait floor - the patient
|
|
65
|
+
// default (E2E_V5_WAIT_FLOOR_MS = 30 s inside the suite) belongs to the real gate run. Without this the
|
|
66
|
+
// 15 families x (patient suite) exceeded even the family's 900 s override and timed the family out.
|
|
67
|
+
env: Object.assign({}, process.env, { [ENV]: join(dest, MAIN), E2E_V5_WAIT_FLOOR_MS: '2000' }),
|
|
68
|
+
})
|
|
69
|
+
} catch (e) {
|
|
70
|
+
if (e && (e.killed || e.signal === 'SIGKILL')) hang = true
|
|
71
|
+
code = (e && e.status) || 1
|
|
72
|
+
out = String((e && e.stdout) || '') + String((e && e.stderr) || '')
|
|
73
|
+
}
|
|
74
|
+
const ms = Date.now() - t0
|
|
75
|
+
TIMES.push([f.name, ms])
|
|
76
|
+
if (hang) hangs.push(f.name + '(' + Math.round(ms / 1000) + 's)')
|
|
77
|
+
const named = out.split(/\r?\n/).filter((l) => /^\s*(FAIL - | {2}- )/.test(l) && f.expect.test(l)).map((l) => l.trim())
|
|
78
|
+
const ok = !hang && code !== 0 && named.length > 0
|
|
79
|
+
console.log((hang ? ' HANG - ' : (ok ? ' ok - ' : ' FAIL - ')) + f.name + ' [' + ms + 'ms]' + (named.length ? ' :: ' + named[0].slice(0, 150) : ' :: no named red (exit=' + code + ')'))
|
|
80
|
+
rmSync(dest, { recursive: true, force: true })
|
|
81
|
+
return ok
|
|
82
|
+
}
|
|
83
|
+
const FAMILIES = [
|
|
84
|
+
{
|
|
85
|
+
// G1: acking at prompt-build time again — a failed wake then consumes the message.
|
|
86
|
+
name: 'G1: the mailbox is acked BEFORE the wake again',
|
|
87
|
+
from: 'if (ok && prompt.pending.length) await ackPending(prompt.pending)',
|
|
88
|
+
to: 'if (prompt.pending.length) await ackPending(prompt.pending)',
|
|
89
|
+
expect: /\[G1\] a message whose wake FAILED is still pending/,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
// G2: counting the round BEFORE a send that can fail (the historical shape v4 fixed as F10).
|
|
93
|
+
name: 'G2: the round counter is applied BEFORE the send again',
|
|
94
|
+
from: 'const nextRound = (rounds.get(member.id) || 0) + 1',
|
|
95
|
+
to: 'rounds.set(member.id, (rounds.get(member.id) || 0) + 1); const nextRound = (rounds.get(member.id) || 0) + 1',
|
|
96
|
+
expect: /\[G2\] a failed send must not consume a round/,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
// G4: restoring the second, unprotected whole-object write after the CAS (the lost-update window).
|
|
100
|
+
name: 'G4: a second task write after the CAS is restored',
|
|
101
|
+
from: "const r = await taskUpdate(memberId, { task_id: taskId, expected_revision: (cur ? cur.revision : 1), action: 'reassign', owner: to },\n { assignedBy: isOffice(memberId) ? 'office' : memberId, why, acceptance })",
|
|
102
|
+
to: "const r = await taskUpdate(memberId, { task_id: taskId, expected_revision: (cur ? cur.revision : 1), action: 'reassign', owner: to })\n if (r.ok) await putTask(Object.assign({}, inst().tasks.find((t) => t.id === taskId), { assignedBy: isOffice(memberId) ? 'office' : memberId, why, acceptance }))",
|
|
103
|
+
expect: /\[G4\] the assign path issues NO second, unprotected task write/,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
// A5: a permanent researcher founded with the academician KIND - the count stays 3 but the id
|
|
107
|
+
// set and the kind multiset both break (the allocator also repeats the academician id).
|
|
108
|
+
name: 'A5: a permanent researcher is founded with the academician kind',
|
|
109
|
+
from: "const m = await newMember('researcher', { direction: dir })",
|
|
110
|
+
to: "const m = await newMember('academician', { direction: dir })",
|
|
111
|
+
expect: /A5 the founded (ID SET|KIND MULTISET)/,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
// A5-ids: the COUNT stays 3 and the kinds stay right, but the researcher id PREFIX changes, so the
|
|
115
|
+
// MEASURED (R14): the count assertions stay GREEN and the ID-SET assertion reddens; the run's other
|
|
116
|
+
// reds (82) are the cascade of the same mutation - not a claim that only the new assertion reddens.
|
|
117
|
+
// founded ID SET is no longer {acad, r-1, r-2}. This is the discriminator the count cannot catch.
|
|
118
|
+
name: 'A5-ids: the researcher id prefix is renamed (count still 3)',
|
|
119
|
+
from: "researcher: 'r-',",
|
|
120
|
+
to: "researcher: 'rX-',",
|
|
121
|
+
expect: /A5 the founded ID SET is exactly/,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
// A5-kinds: the COUNT stays 3 and the ids stay right, but a founded researcher's KIND is rewritten,
|
|
125
|
+
// MEASURED (R14): the count and ID-SET assertions stay GREEN and the KIND assertion reddens; the run's
|
|
126
|
+
// other reds (21) are the cascade of the same mutation - not a claim that only the new assertion reddens.
|
|
127
|
+
// so the kind MULTISET is no longer {academician:1, researcher:2}.
|
|
128
|
+
name: 'A5-kinds: a founded researcher kind is rewritten (count still 3)',
|
|
129
|
+
from: "id: '', kind,",
|
|
130
|
+
to: "id: '', kind: kind === 'researcher' ? 'temp' : kind,",
|
|
131
|
+
expect: /A5 the founded KIND MULTISET is exactly/,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
// spawnMember (G2-class): counting the founding round BEFORE `startContinuable`, so a host-cap
|
|
135
|
+
// failure consumes a round and the retried founder is told 轮次 2.
|
|
136
|
+
name: 'spawnMember: the founding round is counted BEFORE the start again',
|
|
137
|
+
from: 'const startRound = (rounds.get(member.id) || 0) + 1',
|
|
138
|
+
to: 'rounds.set(member.id, (rounds.get(member.id) || 0) + 1); const startRound = (rounds.get(member.id) || 0) + 1',
|
|
139
|
+
expect: /\[spawnMember\] a FAILED start must not consume a round/,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
// spawnMember: restoring the destructive rollback — `rounds.delete`/`roundsSinceCompact.delete` in the
|
|
143
|
+
// catch, which ERASES the count of a member being RESUMED (its numbering then restarts at 1).
|
|
144
|
+
name: 'spawnMember: the failed-start rollback DELETES the existing round count again',
|
|
145
|
+
from: " busy.delete(member.id)\n wakeKind.delete(member.id)",
|
|
146
|
+
to: " busy.delete(member.id)\n wakeKind.delete(member.id)\n rounds.delete(member.id)\n roundsSinceCompact.delete(member.id)",
|
|
147
|
+
expect: /\[spawnMember\] a FAILED resume must not move the round number backwards/,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
// spawnMember: dropping the explicit next-round argument, so the founding prompt's status block
|
|
151
|
+
// falls back to the stored counter (still 0 at that point) and announces 轮次 0.
|
|
152
|
+
name: 'spawnMember: the founding prompt is built without the explicit round',
|
|
153
|
+
from: 'const prompt = initialPrompt(member, initialTask, mode, startRound)',
|
|
154
|
+
to: 'const prompt = initialPrompt(member, initialTask, mode)',
|
|
155
|
+
expect: /\[spawnMember\]|the founding prompt never displays 轮次 0|the FOUNDING prompt announces the round/,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
// F6: quietly dropping a failed REQUIRED artifact write again (the v4-G3 shape): the meta write is
|
|
159
|
+
// still checked for `files`, but the failure is no longer NAMED in `warnings`.
|
|
160
|
+
name: 'F6: a failed paper.meta.json write is silently dropped from warnings again',
|
|
161
|
+
from: " else warnings.push(paperWriteFailureWarning(['paper.meta.json']))",
|
|
162
|
+
to: " else { /* F6 mutant: the failure is silent again */ }",
|
|
163
|
+
expect: /\[F6\] a failed REQUIRED write is named EXACTLY once/,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
// F6 (log half): discarding the FINALIZE-time log write result again — the log artifact can then go
|
|
167
|
+
// missing with no warning at all, and the durable meta is not re-written to name it.
|
|
168
|
+
name: 'F6: the finalize-time paper.log.md write result is discarded again',
|
|
169
|
+
from: ' if (!logRes.ok) {',
|
|
170
|
+
to: ' if (false) {',
|
|
171
|
+
expect: /\[F6\] a failed FINALIZE-time log write is named exactly once/,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
// v5 L1 (`leanPathContractOk`): the tools' short form resolves to a DIFFERENT file, so the load-time
|
|
175
|
+
// contract check fails and queues 'Lean path contract broken …'; report() must then surface it.
|
|
176
|
+
name: 'v5 L1: the Lean tools short form resolves to the WRONG file (path contract broken)',
|
|
177
|
+
from: 'const viaShort = leanAbsPath(probe)',
|
|
178
|
+
to: "const viaShort = leanAbsPath(probe + 'x')",
|
|
179
|
+
expect: /\[v5 L1\] a HEALTHY composition surfaces no Lean path contract note/,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
// task-9: the durable meta must record the probed TeX paths — dropping them makes the recorded
|
|
183
|
+
// evidence disappear while the compile result still degrades identically.
|
|
184
|
+
name: 'task-9: the durable meta stops recording the probed TeX paths (triedPaths emptied)',
|
|
185
|
+
from: 'triedPaths: compile.triedPaths || []',
|
|
186
|
+
to: 'triedPaths: []',
|
|
187
|
+
expect: /\[task-9\] the durable meta records the probed TeX paths/,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
// task-9: the not-detected warning keeps its tail but must also say where it looked and how to pin
|
|
191
|
+
// an engine — reverting that sentence to the old text must redden the guarding assertion by name.
|
|
192
|
+
name: 'task-9: the not-detected warning drops the actionable sentence again',
|
|
193
|
+
from: '只交付 paper.tex 与 paper.md。已探测 PATH 与文档化的常见 TeX 根;可用 paperLatexCommand 指定绝对路径。',
|
|
194
|
+
to: '只交付 paper.tex 与 paper.md。',
|
|
195
|
+
expect: /\[task-9\] the not-detected warning keeps its tail AND says where it looked/,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
// task-10: restoring the merged candidate list lets an unresolvable explicit command fall through
|
|
199
|
+
// to another engine (the documented rule is "explicit ⇒ only it").
|
|
200
|
+
name: 'task-10: the explicit LaTeX command falls through to another engine again',
|
|
201
|
+
from: 'return [forced]',
|
|
202
|
+
to: 'return [forced].concat(order.filter((n) => n !== forced))',
|
|
203
|
+
expect: /\[task-10\/v5\] an unresolvable explicit paperLatexCommand degrades/,
|
|
204
|
+
},
|
|
205
|
+
]
|
|
206
|
+
let red = 0
|
|
207
|
+
for (const f of FAMILIES) { const ok = runFamily(f); if (ok) red++ }
|
|
208
|
+
const totalMs = TIMES.reduce((a, t) => a + t[1], 0)
|
|
209
|
+
console.log('')
|
|
210
|
+
console.log('mutant families reddening the v5 institute fixes by name: ' + red + '/' + FAMILIES.length)
|
|
211
|
+
console.log('timings: ' + TIMES.map((t) => String(t[0]).split(':')[0] + '=' + t[1] + 'ms').join(' '))
|
|
212
|
+
// R15: the audit-checklist row says EVERY family prints this line, and override decisions must quote it
|
|
213
|
+
// rather than an external estimate. Same shape as tests/formal-verify-v3.mutants.mjs.
|
|
214
|
+
console.log('TOTAL WALL TIME (all families + setup): ' + totalMs + 'ms (' + Math.round(totalMs / 1000) + 's)')
|
|
215
|
+
console.log('hangs=[' + hangs.join(' | ') + ']')
|
|
216
|
+
console.log('skipped=[' + skipped.join(' | ') + ']')
|
|
217
|
+
if (red !== FAMILIES.length || skipped.length || hangs.length) process.exit(1)
|
|
218
|
+
console.log('ALL MUTANTS RED AS REQUIRED')
|