dsh-vibe-math 2.7.2 → 2.7.3
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/README.en.md +11 -10
- package/README.md +13 -12
- package/cordis.patch.yml +112 -16
- package/docs/AUDIT-CHECKLIST.md +229 -3
- package/docs/COMPAT-AUDIT-ROUND2.md +35 -9
- package/docs/final-paper.md +2 -2
- package/docs/math-computation.md +13 -6
- package/docs/parameter-schema.md +37 -0
- package/docs/release-notes/README.en.md +1 -0
- package/docs/release-notes/README.md +1 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +2 -2
- package/docs/release-notes/RELEASE-NOTES-2.7.2.md +3 -3
- package/docs/release-notes/RELEASE-NOTES-2.7.3.en.md +67 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.3.md +67 -0
- package/docs/release-notes/TEMPLATE.md +6 -0
- package/docs/status-report-fields.md +167 -0
- package/docs/test-timing.md +8 -5
- package/installer.js +27 -17
- package/package.json +250 -203
- package/prompt-corpus-persona/persona-corpus.json +9 -9
- package/prompt-corpus-persona/persona-corpus.md +225 -33
- package/prompt-corpus-v2/formal-verify-v2.json +295 -115
- package/prompt-corpus-v2/formal-verify-v2.md +2287 -283
- package/prompt-corpus-v3/formal-verify-v3.json +105 -105
- package/prompt-corpus-v3/formal-verify-v3.md +213 -143
- package/prompt-corpus-v4/formal-verify-v4.json +18 -18
- package/prompt-corpus-v4/formal-verify-v4.md +49 -44
- package/prompt-corpus-v5/prompt-corpus-v5.json +82 -82
- package/prompt-corpus-v5/prompt-corpus-v5.md +451 -207
- package/scripts/update-doc-counts.mjs +71 -0
- package/tests/audit-artifact-docs.mjs +72 -0
- package/tests/audit-artifact-docs.mutants.mjs +58 -0
- package/tests/audit-installer-assertions.mutants.mjs +63 -0
- package/tests/audit-installer-compat.test.mjs +52 -4
- package/tests/audit-installer-policy.test.mjs +41 -8
- package/tests/audit-math-computation-contract.mjs +64 -8
- package/tests/audit-math-computation-contract.mutants.mjs +106 -0
- package/tests/audit-math-computation-parity.mjs +164 -3
- package/tests/audit-math-computation-sensitivity.mjs +4 -2
- package/tests/audit-package-membership.mjs +57 -0
- package/tests/audit-package-membership.mutants.mjs +28 -0
- package/tests/audit-participant-set-parity.mjs +160 -0
- package/tests/audit-path-discipline.mjs +83 -0
- package/tests/audit-path-discipline.mutants.mjs +51 -0
- package/tests/audit-persona-sensitivity.mjs +14 -0
- package/tests/audit-persona-surface.mutants.mjs +69 -0
- package/tests/audit-persona-surface.test.mjs +63 -22
- package/tests/audit-prompt-duplication.mjs +143 -0
- package/tests/audit-prompt-duplication.mutants.mjs +50 -0
- package/tests/audit-prompt-invariants.mjs +43 -5
- package/tests/audit-readme-bilingual.test.mjs +152 -0
- package/tests/audit-readme-counts.mjs +95 -0
- package/tests/audit-readme-counts.mutants.mjs +52 -0
- package/tests/audit-registration.mjs +78 -0
- package/tests/audit-status-report-fields.mjs +172 -0
- package/tests/audit-status-report-fields.mutants.mjs +64 -0
- package/tests/audit-v3-registration-parity.mjs +156 -0
- package/tests/audit-v5-integrity.mjs +300 -13
- package/tests/audit-v5-lean-abstention.mjs +392 -0
- package/tests/audit-v5-lean-abstention.mutants.mjs +106 -0
- package/tests/audit-v5-prompt-duplication.mjs +88 -0
- package/tests/audit-v5-prompt-duplication.mutants.mjs +34 -0
- package/tests/audit-v5-sensitivity.mjs +12 -7
- package/tests/e2e-identity-a6.mutants.mjs +65 -0
- package/tests/e2e-v5-round2.test.mjs +458 -13
- package/tests/formal-verify-v2.test.mjs +91 -43
- package/tests/formal-verify-v3.mutants.mjs +117 -0
- package/tests/formal-verify-v3.test.mjs +85 -39
- package/tests/formal-verify-v4.mutants.mjs +152 -0
- package/tests/formal-verify-v4.test.mjs +541 -9
- package/tests/formal-verify-v5.test.mjs +68 -9
- package/tests/helpers/doc-counts-format.mjs +41 -0
- package/tests/helpers/math-computation-fake-seam.mjs +4 -0
- package/tests/math-computation-a1.mutants.mjs +72 -0
- package/tests/math-computation-archive-rerun.mutants.mjs +65 -0
- package/tests/math-computation-discovery.mutants.mjs +76 -0
- package/tests/math-computation-shared.test.mjs +274 -5
- package/tests/math-computation-v2.test.mjs +767 -0
- package/tests/math-computation-v3.test.mjs +759 -0
- package/tests/math-computation-v4.mutants.mjs +133 -0
- package/tests/math-computation-v4.test.mjs +739 -0
- package/tests/prompt-v5-integrity.test.mjs +13 -2
- package/tests/run-tests.mjs +139 -9
- package/tests/run-tests.mutants.mjs +70 -0
- package/tests/selfdrive-v5.mjs +53 -1
- package/tests/selfdrive-v5.mutants.mjs +132 -0
- package/tests/v2-fix-probes.mutants.mjs +100 -0
- package/tests/v2-fix-probes.test.mjs +1039 -0
- package/tests/v2-list-agents-and-next-step.mutants.mjs +106 -0
- package/tests/v3-fix-probes.mutants.mjs +95 -0
- package/tests/v3-fix-probes.test.mjs +891 -0
- package/tests/v5-institute-fixes.mutants.mjs +190 -0
- package/vibe-math-v2/agent.cordis.yml +62 -6
- package/vibe-math-v2/math-computation.js +130 -41
- package/vibe-math-v2/math-engines.js +12 -12
- package/vibe-math-v2/vibe-math-v2.js +388 -113
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +5 -1
- package/vibe-math-v3/agent.cordis.yml +72 -6
- package/vibe-math-v3/math-computation.js +130 -41
- package/vibe-math-v3/math-engines.js +12 -12
- package/vibe-math-v3/vibe-math-v3.js +422 -126
- package/vibe-math-v4/agent.cordis.yml +44 -8
- package/vibe-math-v4/math-computation.js +130 -41
- package/vibe-math-v4/math-engines.js +12 -12
- package/vibe-math-v4/vibe-math-v4.js +196 -43
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +2 -0
- package/vibe-math-v5/agent.cordis.yml +46 -12
- package/vibe-math-v5/math-computation.js +130 -41
- package/vibe-math-v5/math-engines.js +12 -12
- package/vibe-math-v5/vibe-math-v5.js +1173 -218
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +80 -12
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +2 -2
|
@@ -277,7 +277,7 @@ console.log('-- V5 Lean formal verification --')
|
|
|
277
277
|
section("1 'off' (default) is a true no-op")
|
|
278
278
|
const RA = makeRoot()
|
|
279
279
|
const st0 = await foundInstitute(RA, '形式化开关默认关闭测试')
|
|
280
|
-
assert(st0.params.formalVerify === 'off', "the default is 'off' (got " + st0.params.formalVerify + ')')
|
|
280
|
+
assert((st0.params||{}).formalVerify === 'off', "the default is 'off' (got " + (st0.params||{}).formalVerify + ')')
|
|
281
281
|
|
|
282
282
|
// ★ The persistence contract (the DSH 0.2.0 audit fix). v5 used to keep the institute in a
|
|
283
283
|
// host-only session projection: it appended `vibe5/*` events to the user's OWN session log, and
|
|
@@ -294,8 +294,9 @@ assert(st0.params.formalVerify === 'off', "the default is 'off' (got " + st0.par
|
|
|
294
294
|
const before = await callTool('vibe_v5_status', {}, RA)
|
|
295
295
|
const RA2 = makeRoot(wsOf(RA)) // fresh root, fresh backend, same workspace = a reload
|
|
296
296
|
const after = await callTool('vibe_v5_status', {}, RA2)
|
|
297
|
-
assert(after.
|
|
298
|
-
|
|
297
|
+
assert(before.params && after.params && typeof before.params === 'object' && typeof after.params === 'object', 'non-vacuous: BOTH reload views must EXPOSE params before they are compared (undefined === undefined is not a pass)')
|
|
298
|
+
assert(after.members.length === before.members.length && (after.params||{}).formalVerify === (before.params||{}).formalVerify,
|
|
299
|
+
'★ a reload reads the roster and the parameters back out of the JSON file (' + after.members.length + ' members, formalVerify=' + (after.params||{}).formalVerify + ')')
|
|
299
300
|
assert(RA2.session._events.length === 0, '★ the reloading root appends nothing to its session log either')
|
|
300
301
|
}
|
|
301
302
|
{
|
|
@@ -360,16 +361,16 @@ section('2 parameter validation and runtime switching')
|
|
|
360
361
|
const RB = makeRoot()
|
|
361
362
|
await foundInstitute(RB, '形式化参数校验测试')
|
|
362
363
|
const bad = await callTool('vibe_v5_set', { formalVerify: 'banana' }, RB)
|
|
363
|
-
assert(bad.params.formalVerify === 'off', "an unknown mode degrades to 'off', never to a stronger mode (got " + bad.params.formalVerify + ')')
|
|
364
|
+
assert((bad.params||{}).formalVerify === 'off', "an unknown mode degrades to 'off', never to a stronger mode (got " + (bad.params||{}).formalVerify + ')')
|
|
364
365
|
const enc = await callTool('vibe_v5_set', { formalVerify: 'encourage' }, RB)
|
|
365
|
-
assert(enc.params.formalVerify === 'encourage', "'encourage' is accepted")
|
|
366
|
+
assert((enc.params||{}).formalVerify === 'encourage', "'encourage' is accepted")
|
|
366
367
|
const req = await callTool('vibe_v5_set', { formalVerify: 'require', leanTimeoutMs: -5, leanCommand: ' ' }, RB)
|
|
367
|
-
assert(req.params.formalVerify === 'require', "'require' is accepted")
|
|
368
|
-
assert(req.params.leanTimeoutMs === 120000, 'a non-positive leanTimeoutMs falls back to the default (' + req.params.leanTimeoutMs + ')')
|
|
369
|
-
assert(req.params.leanCommand === 'lean', 'a blank leanCommand falls back to "lean"')
|
|
368
|
+
assert((req.params||{}).formalVerify === 'require', "'require' is accepted")
|
|
369
|
+
assert((req.params||{}).leanTimeoutMs === 120000, 'a non-positive leanTimeoutMs falls back to the default (' + (req.params||{}).leanTimeoutMs + ')')
|
|
370
|
+
assert((req.params||{}).leanCommand === 'lean', 'a blank leanCommand falls back to "lean"')
|
|
370
371
|
await callTool('vibe_v5_set', { leanCommand: 'lake', leanArgs: ['env', 'lean'] }, RB)
|
|
371
372
|
const stL = await callTool('vibe_v5_status', {}, RB)
|
|
372
|
-
assert(stL.params.leanCommand === 'lake' && stL.params.leanArgs.join(' ') === 'env lean', 'leanCommand/leanArgs are settable (lake env lean)')
|
|
373
|
+
assert((stL.params||{}).leanCommand === 'lake' && ((stL.params||{}).leanArgs||[]).join(' ') === 'env lean', 'leanCommand/leanArgs are settable (lake env lean)')
|
|
373
374
|
|
|
374
375
|
// ---------- 3. 'encourage' injection ----------
|
|
375
376
|
section("3 'encourage' injects the Lean section into the right prompts")
|
|
@@ -717,6 +718,64 @@ assert(stDef.undecided.indexOf('p-def') !== -1, '★ it is recorded as 未定论
|
|
|
717
718
|
assert(!existsSync(join(instG, 'Verified', '命题', 'p-def.md')), 'no Verified card is written for it')
|
|
718
719
|
assert(/p-def/.test(readIf(join(instG, 'Formal', 'TODO.md'))), 'it is on the formalisation TODO list')
|
|
719
720
|
|
|
721
|
+
|
|
722
|
+
// ===============================================================
|
|
723
|
+
// N. v5 LIB_SPEC path basis (D3 class): every member-visible library declaration must carry the
|
|
724
|
+
// SAME root the code's readers/writers use (Members/<id>/Progress/progress.md etc.), otherwise a
|
|
725
|
+
// compliant member writes a file nobody reads (the vm-selftest3 symptom).
|
|
726
|
+
// ===============================================================
|
|
727
|
+
section('N v5 LIB_SPEC declares the same path basis the code reads/writes (D3 class)')
|
|
728
|
+
{
|
|
729
|
+
const { readFileSync } = await import('node:fs')
|
|
730
|
+
const src = readFileSync(PLUGIN, 'utf8')
|
|
731
|
+
const start = src.indexOf('const LIB_SPEC = [')
|
|
732
|
+
assert(start > 0, 'the charter LIB_SPEC block exists')
|
|
733
|
+
const spec = src.slice(start, src.indexOf("].join('\\n')", start))
|
|
734
|
+
for (const d of ['Progress/progress.md', 'Propos/<id>.md', 'Methods/<id>.md', 'Subproblems/<id>.md']) {
|
|
735
|
+
assert(spec.includes('Members/<你>/' + d), '★ LIB_SPEC declares ' + d + ' under the member root (Members/<你>/' + d + ') - the root the readers/writers use')
|
|
736
|
+
}
|
|
737
|
+
assert(!/·\s+(?:Progress|Propos|Methods|Subproblems)\/<你>\//.test(spec), '★ no LIB_SPEC declaration keeps the legacy bare Progress/<你>/ shape (a member copying it writes outside Members/<id>/)')
|
|
738
|
+
assert(src.includes("const rel = 'Members/' + memberId + '/Progress/progress.md'"), '★ the code writer path shares that root (Members/<id>/Progress/progress.md)')
|
|
739
|
+
}
|
|
740
|
+
// ===============================================================
|
|
741
|
+
// N2. v5 assign/prioritize/nudge: the tool DESCRIPTION's allowed-caller set must match the GATE.
|
|
742
|
+
// All three share one gate (office always; academician only when academicianLeads), while the
|
|
743
|
+
// descriptions used to claim academician-only - so a reader (or a model) would never call them
|
|
744
|
+
// from the office, which the prompt explicitly allows.
|
|
745
|
+
// ===============================================================
|
|
746
|
+
section('N2 v5 assign/prioritize/nudge descriptions match their shared gate')
|
|
747
|
+
{
|
|
748
|
+
const { readFileSync } = await import('node:fs')
|
|
749
|
+
const src = readFileSync(PLUGIN, 'utf8')
|
|
750
|
+
const gate = 'if (!isOffice(memberId) && !(isAcademician(memberId) && params.academicianLeads)) {'
|
|
751
|
+
const gates = src.split(gate).length - 1
|
|
752
|
+
assert(gates >= 2, '* assign + prioritize share the office-or-academician gate condition (found ' + gates + ')')
|
|
753
|
+
assert(/async function nudge[\s\S]{0,900}isOffice\((?:memberId|callerId)\)/.test(src), '* the nudge gate also admits the office (isOffice, whatever the caller parameter is named)')
|
|
754
|
+
for (const tool of ['vibe_v5_assign', 'vibe_v5_prioritize', 'vibe_v5_nudge']) {
|
|
755
|
+
const at = src.indexOf("registerTool('" + tool + "', '")
|
|
756
|
+
assert(at > 0, '* ' + tool + ' is registered')
|
|
757
|
+
const dstart = at + ("registerTool('" + tool + "', '").length
|
|
758
|
+
const desc = src.slice(dstart, src.indexOf("',", dstart))
|
|
759
|
+
assert(/\(office/.test(desc) && /academician/.test(desc), '* ' + tool + ' description names BOTH allowed callers (office + academician), matching its gate')
|
|
760
|
+
assert(!desc.includes("'(academician)"), '* ' + tool + ' description does not claim academician-only while the gate allows the office')
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// ===============================================================
|
|
764
|
+
// N3. DEFECT-1 contract for EVERY refusal: a caller that cannot be identified must still get a
|
|
765
|
+
// next{tool,hint}, not just a code+message (the office/session root and stale children hit
|
|
766
|
+
// these 'no calling member' returns).
|
|
767
|
+
// ===============================================================
|
|
768
|
+
section('N3 every "no calling member" refusal carries a next{tool,hint}')
|
|
769
|
+
{
|
|
770
|
+
const { readFileSync } = await import('node:fs')
|
|
771
|
+
const src = readFileSync(PLUGIN, 'utf8')
|
|
772
|
+
const msgs = [...src.matchAll(/message: 'no calling member:[\s\S]*?'/g)]
|
|
773
|
+
assert(msgs.length >= 6, '* the member-scoped writing tools refuse an unidentified caller (' + msgs.length + ' sites)')
|
|
774
|
+
for (const m of msgs) {
|
|
775
|
+
const after = src.slice(m.index + m[0].length, m.index + m[0].length + 260)
|
|
776
|
+
assert(/next:\s*\{\s*kind:/.test(after), '* refusal "' + m[0].slice(0, 60) + '..." carries next{tool,hint} (not a bare code+message)')
|
|
777
|
+
}
|
|
778
|
+
}
|
|
720
779
|
console.log('')
|
|
721
780
|
console.log('passed=' + passed + ' failed=' + failed)
|
|
722
781
|
if (failed) { console.error('FAILURES:'); for (const f of failures) console.error(' - ' + f); process.exit(1) }
|
|
@@ -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 p\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 = 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')
|
|
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)
|