dsh-vibe-math 2.8.4 → 2.8.5
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 +19 -4
- package/README.md +19 -4
- package/cordis.patch.yml +8 -8
- package/docs/COMPAT-AUDIT-ROUND2.md +27 -0
- package/docs/final-paper.md +8 -0
- package/docs/formal-verification.md +6 -6
- package/docs/release-notes/README.en.md +1 -0
- package/docs/release-notes/README.md +1 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.5.en.md +75 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.5.md +75 -0
- package/package.json +5 -3
- package/prompt-corpus-persona/persona-corpus.json +9 -9
- package/prompt-corpus-persona/persona-corpus.md +17 -17
- package/prompt-corpus-v2/formal-verify-v2.json +109 -109
- package/prompt-corpus-v2/formal-verify-v2.md +216 -216
- package/prompt-corpus-v3/formal-verify-v3.json +77 -77
- package/prompt-corpus-v3/formal-verify-v3.md +147 -147
- package/prompt-corpus-v4/formal-verify-v4.json +10 -10
- package/prompt-corpus-v4/formal-verify-v4.md +40 -40
- package/prompt-corpus-v5/prompt-corpus-v5.json +61 -61
- package/prompt-corpus-v5/prompt-corpus-v5.md +613 -186
- package/tests/audit-artifact-docs.mjs +38 -2
- package/tests/audit-artifact-docs.mutants.mjs +66 -1
- package/tests/audit-engine-faces.mjs +2 -2
- package/tests/audit-persona-surface.test.mjs +4 -1
- package/tests/audit-prompt-invariants.mjs +292 -2
- package/tests/e2e-v5-round2.test.mjs +214 -2
- package/tests/formal-verify-v2.test.mjs +6 -5
- package/tests/formal-verify-v3.test.mjs +6 -5
- package/tests/formal-verify-v4.test.mjs +4 -4
- package/tests/prompt-v5-integrity.test.mjs +6 -6
- package/tests/run-tests.mjs +18 -2
- package/tests/v4-final-paper.mutants.mjs +14 -0
- package/tests/v4-final-paper.test.mjs +18 -0
- package/tests/v5-institute-fixes.mutants.mjs +107 -0
- package/vibe-math-v2/agent.cordis.yml +2 -2
- package/vibe-math-v2/math-computation.js +5 -5
- package/vibe-math-v2/vibe-math-v2.js +66 -17
- package/vibe-math-v3/agent.cordis.yml +2 -2
- package/vibe-math-v3/math-computation.js +5 -5
- package/vibe-math-v3/vibe-math-v3.js +64 -15
- package/vibe-math-v4/agent.cordis.yml +6 -6
- package/vibe-math-v4/math-computation.js +5 -5
- package/vibe-math-v4/vibe-math-v4.js +71 -20
- package/vibe-math-v5/agent.cordis.yml +6 -6
- package/vibe-math-v5/math-computation.js +5 -5
- package/vibe-math-v5/vibe-math-v5.js +378 -33
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +55 -2
|
@@ -32,6 +32,7 @@ process.env.DSH_HOME = EMPTY_ENGINE_ROOT
|
|
|
32
32
|
process.env.ProgramFiles = EMPTY_ENGINE_ROOT
|
|
33
33
|
process.env['ProgramFiles(x86)'] = EMPTY_ENGINE_ROOT
|
|
34
34
|
process.env.LOCALAPPDATA = EMPTY_ENGINE_ROOT
|
|
35
|
+
process.env.V5_TEX_ROOTS_SANDBOX = EMPTY_ENGINE_ROOT // task-29: the documented TeX Live roots are sandboxed too (the product's own list now covers D:/C:/texlive)
|
|
35
36
|
|
|
36
37
|
// V5_PLUGIN lets a sensitivity probe point this suite at a deliberately broken copy.
|
|
37
38
|
// Without it every probe against this suite silently tested the UNMUTATED plugin and was
|
|
@@ -1776,9 +1777,9 @@ console.log('\n[35] async Lean: enqueue returns at once, settle(ok) is the only
|
|
|
1776
1777
|
'the proof is archived under Verified/Lean/<id>.lean')
|
|
1777
1778
|
const argv = fake.calls[0].argv
|
|
1778
1779
|
const root = join(h.WS, 'VibeMath').replace(/\\/g, '/')
|
|
1779
|
-
const si = argv.indexOf('
|
|
1780
|
+
const si = argv.indexOf('-R')
|
|
1780
1781
|
assert(si > 0 && argv[si + 1] === root && si + 2 === argv.length - 1 && /\.lean$/.test(argv[argv.length - 1]),
|
|
1781
|
-
'★ argv carries
|
|
1782
|
+
'★ argv carries `-R <ABSOLUTE VibeMath root>` (Lean 4 root; the Lean-3 --search-path made lean fail at parsing) right before the file (' + JSON.stringify(argv) + ')')
|
|
1782
1783
|
assert(fake.calls.length === 1, 'exactly one compile ran for the settle (got ' + fake.calls.length + ')')
|
|
1783
1784
|
}
|
|
1784
1785
|
|
|
@@ -2537,6 +2538,217 @@ console.log('\n[53] v5 L1 leanPathContractOk: the queued note reaches report() d
|
|
|
2537
2538
|
assert(rep.indexOf('Lean path contract broken') === -1,
|
|
2538
2539
|
'★★ [v5 L1] a HEALTHY composition surfaces no Lean path contract note in report() diagnostics')
|
|
2539
2540
|
}
|
|
2541
|
+
// task-24 (real-host R2, D5'): a statement longer than a display cap used to be cut SILENTLY — at the resolve
|
|
2542
|
+
// step (1200 chars) and again in the member prompt (800 chars) — and the cut text is what voters read and what
|
|
2543
|
+
// the verified record carries. SOURCE-LEVEL invariant: both sites must keep a generous cap AND mark any cut
|
|
2544
|
+
// (a runtime scenario would need a >20k-char card, which is impractical here; the cut-marking itself is pinned
|
|
2545
|
+
// by the single-site mutation in tests/v5-institute-fixes.mutants.mjs).
|
|
2546
|
+
{
|
|
2547
|
+
const src = readFileSync(PLUGIN, 'utf8')
|
|
2548
|
+
assert(src.includes('const cap = 20000') && src.includes('(注意:陈述超过 ' + "' + cap + '" + ' 字符已截断,完整文本见 '),
|
|
2549
|
+
'★ [real1004-visibility] the resolved statement keeps a generous cap (20000) and MARKS any cut in-band')
|
|
2550
|
+
assert(src.includes('(已截断;完整陈述见源卡片 ') && !src.includes("' 陈述:' + String(vs.statement).slice(0, 800)"),
|
|
2551
|
+
'★ [real1004-visibility] the member-prompt statement line marks its 800-char cut instead of hiding the tail')
|
|
2552
|
+
}
|
|
2553
|
+
// task-26 (real-host R2): `vibe_v5_set` used to coerce out-of-range values and drop unknown keys with NOTHING in
|
|
2554
|
+
// the response, and two different capacity limits shared one error code. The response must report both, and the
|
|
2555
|
+
// two limits must be distinguishable by a machine-readable field.
|
|
2556
|
+
{
|
|
2557
|
+
const hz = makeHost({ pluginModule })
|
|
2558
|
+
await hz.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 1 })
|
|
2559
|
+
await hz.settleSpawns()
|
|
2560
|
+
const set = await hz.callTool('vibe_v5_set', { quorumCap: -1, unknownKeyXyz: 5 })
|
|
2561
|
+
assert(set.ok === true && Array.isArray(set.dropped) && set.dropped.indexOf('unknownKeyXyz') !== -1,
|
|
2562
|
+
'★ [real1004-visibility] vibe_v5_set REPORTS the unknown key it dropped (dropped=' + JSON.stringify(set.dropped) + ')')
|
|
2563
|
+
assert(set.adjusted && set.adjusted.quorumCap && Number(set.adjusted.quorumCap.from) === -1 && Number(set.adjusted.quorumCap.to) !== -1,
|
|
2564
|
+
'★ [real1004-visibility] vibe_v5_set reports `adjusted` so a coerced value is visible (adjusted=' + JSON.stringify(set.adjusted) + ')')
|
|
2565
|
+
// The per-member vs institute-wide distinction is pinned as a SOURCE invariant instead of by driving two
|
|
2566
|
+
// refusals: `vibe_v5_hire` demands a full brief (`purpose` + `initial_task` + …) before it reaches the cap
|
|
2567
|
+
// check, and assembling that brief here would only test the brief. The mutation in
|
|
2568
|
+
// tests/v5-institute-fixes.mutants.mjs collapses the two scopes and reddens this assertion by name.
|
|
2569
|
+
const src26 = readFileSync(PLUGIN, 'utf8')
|
|
2570
|
+
assert(src26.includes("scope: 'per-member'") && src26.includes("scope: 'institute'"),
|
|
2571
|
+
'★ [real1004-visibility] the two capacity refusals carry DISTINCT machine-readable scopes (both literals present)')
|
|
2572
|
+
}
|
|
2573
|
+
// task-25 (real-host R2/R3): `status().fieldScopes` must cover the fields the payload actually carries; an
|
|
2574
|
+
// incomplete electorate must NAME the missing voters; and v4 must name the residents that did not vote true.
|
|
2575
|
+
{
|
|
2576
|
+
const hf = makeHost({ pluginModule })
|
|
2577
|
+
await hf.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 1 })
|
|
2578
|
+
await hf.settleSpawns()
|
|
2579
|
+
const stf = await hf.callTool('vibe_v5_status', {})
|
|
2580
|
+
const fs = stf.fieldScopes || {}
|
|
2581
|
+
assert(stf.diagnostics !== undefined && stf.backend !== undefined,
|
|
2582
|
+
'precondition: the payload really carries diagnostics/backend (' + JSON.stringify({ d: typeof stf.diagnostics, b: typeof stf.backend }) + ')')
|
|
2583
|
+
assert(Array.isArray(fs.session) && fs.session.indexOf('diagnostics') !== -1 && fs.session.indexOf('backend') !== -1,
|
|
2584
|
+
'★ [real1004-visibility] fieldScopes.session covers the payload fields it used to omit (diagnostics/backend)')
|
|
2585
|
+
assert(Array.isArray(fs.durable) && fs.durable.indexOf('verdicts') !== -1 && fs.durable.indexOf('solve') !== -1,
|
|
2586
|
+
'★ [real1004-visibility] fieldScopes.durable declares verdicts/solve (a reader can tell durable facts apart)')
|
|
2587
|
+
const src25 = readFileSync(PLUGIN, 'utf8')
|
|
2588
|
+
assert(src25.includes('pendingVoters: need.filter((id) => !votes[id])'),
|
|
2589
|
+
'★ [real1004-visibility] an incomplete electorate NAMES the missing voters (pendingVoters)')
|
|
2590
|
+
const v4src = readFileSync(new URL('../vibe-math-v4/vibe-math-v4.js', import.meta.url), 'utf8')
|
|
2591
|
+
assert(v4src.includes(';未投 true:'),
|
|
2592
|
+
'★ [real1004-visibility] v4 names the residents that did not vote solved=true (no silent "no unanimous vote")')
|
|
2593
|
+
}
|
|
2594
|
+
// task-27 (real-host A round + local reproduction): Lean 4 accepts -R/--root (or LEAN_PATH); the Lean-3 spelling
|
|
2595
|
+
// `--search-path` makes `lean` fail at ARGUMENT PARSING (rc=1) before it ever reads the file — every compile failed.
|
|
2596
|
+
{
|
|
2597
|
+
const src27 = readFileSync(PLUGIN, 'utf8')
|
|
2598
|
+
assert(src27.includes("const LEAN_SEARCH_PATH_FLAG = '-R'"),
|
|
2599
|
+
'★ [real1004-lean-flag] the injected Lean search root uses -R (Lean 4), not the Lean-3 --search-path')
|
|
2600
|
+
assert(!src27.includes('--search-path <VibeMath root>') && !/registerTool\(\s*'[^']+'\s*,\s*'[^']*--search-path/.test(src27),
|
|
2601
|
+
'★ [real1004-lean-flag] no agent-facing tool description still teaches --search-path')
|
|
2602
|
+
}
|
|
2603
|
+
// Iron-rules P4: the LaTeX-missing block was pushed into EVERY wake (founding/verify/meeting included) with no
|
|
2604
|
+
// statement of when it applies, so an agent that has nothing to do with the paper met unexplained noise.
|
|
2605
|
+
{
|
|
2606
|
+
const srcP4 = readFileSync(PLUGIN, 'utf8')
|
|
2607
|
+
assert(srcP4.includes('仅在你参与论文写作或编译时适用'),
|
|
2608
|
+
'★ [real1004-prompt-scope] the LaTeX block says WHEN it applies instead of appearing as unexplained noise')
|
|
2609
|
+
assert(/function paperPushLine\(L\)[\s\S]{0,600}仅在你参与论文写作或编译时适用/.test(srcP4),
|
|
2610
|
+
'★ [real1004-prompt-scope] the scope note is what the shared tail actually pushes (not a dangling sentence)')
|
|
2611
|
+
}
|
|
2612
|
+
// ---------- 54. task-30: the methodology/collaboration feedback library (Shared/Feedback/) ----------
|
|
2613
|
+
console.log('\n[54] vibe_v5_feedback: 用途+三路由提示;add→list→状态流转;off 不注入+具名拒绝+不写盘;计数与库一致')
|
|
2614
|
+
{
|
|
2615
|
+
const INSDIR = join('VibeMath', 'Projects', 'default', 'Institutes', 'institute')
|
|
2616
|
+
const h = makeHost({ pluginModule })
|
|
2617
|
+
await h.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 1 })
|
|
2618
|
+
await h.settleSpawns()
|
|
2619
|
+
const r1 = h.childAgent(h.childOf('r-1'))
|
|
2620
|
+
const acad = h.childAgent(h.childOf('acad'))
|
|
2621
|
+
const fbPath = (...p) => join(h.WS, INSDIR, 'Shared', 'Feedback', ...p)
|
|
2622
|
+
const wake = async (member) => {
|
|
2623
|
+
await h.callTool('vibe_v5_say', { to: member, text: '请继续推进。' }, acad)
|
|
2624
|
+
const w = await h.peekWakeOf(member, 3000)
|
|
2625
|
+
if (w) h.fireEnd(w.childId, { progress: '收到。', solved: false, contextPct: 10 })
|
|
2626
|
+
await sleep(25)
|
|
2627
|
+
return w ? w.text : ''
|
|
2628
|
+
}
|
|
2629
|
+
const countsLine = (t) => (String(t).split('\n').find((l) => l.indexOf('条目 ') !== -1 && l.indexOf('未闭环 ') !== -1) || '')
|
|
2630
|
+
|
|
2631
|
+
// (1) 每一轮都可见:为什么记/记什么/三条路由/记了之后会怎样
|
|
2632
|
+
const p1 = await wake('r-1')
|
|
2633
|
+
assert(p1.indexOf('【工作经验/流程反馈') !== -1, '★ [task-30] the per-round prompt carries the feedback hint')
|
|
2634
|
+
assert(p1.indexOf('为什么记') !== -1 && p1.indexOf('记什么') !== -1 && p1.indexOf('记了之后') !== -1,
|
|
2635
|
+
'★ [task-30] the hint explains WHY / WHAT / what happens after')
|
|
2636
|
+
assert(p1.indexOf('self=') !== -1 && p1.indexOf('team=') !== -1 && p1.indexOf('interpersonal=') !== -1,
|
|
2637
|
+
'★ [task-30] the hint distinguishes the THREE routes')
|
|
2638
|
+
assert(p1.indexOf('自己调整,无需谁采纳') !== -1 && p1.indexOf('同样无需审批') !== -1,
|
|
2639
|
+
'★★ [task-30] self/team routes are self-regulation — the hint never asks for an approval')
|
|
2640
|
+
assert(p1.indexOf('缜密评估') !== -1 && p1.indexOf('事后回填验证') !== -1,
|
|
2641
|
+
'★ [task-30] only the interpersonal route demands a careful assessment + written-back verification')
|
|
2642
|
+
assert(p1.indexOf('Progress/') !== -1 && p1.indexOf('Verified/') !== -1,
|
|
2643
|
+
'★ [task-30] the boundary against Progress//Verified/ is stated in the prompt')
|
|
2644
|
+
assert(p1.indexOf('向用户问一次') === -1 && p1.indexOf('ask the user once') === -1,
|
|
2645
|
+
'the hint never demands an action a member cannot perform (I16)')
|
|
2646
|
+
|
|
2647
|
+
// (2) add → list 可见;self/team 的状态流转无需任何审批
|
|
2648
|
+
const add = await h.callTool('vibe_v5_feedback', { op: 'add', category: 'process', route: 'self', context: '每轮都在等别人回复', phenomenon: '轮次空转', impact: '进展慢', action: '我自己改成先写进度再发问' }, r1)
|
|
2649
|
+
assert(add.ok === true && add.entry && add.entry.id === 'fb-1' && add.entry.status === 'open',
|
|
2650
|
+
'★ [task-30] add records an entry (open, id=fb-1): ' + JSON.stringify(add.entry && add.entry.id))
|
|
2651
|
+
assert(add.mirror === true && existsSync(fbPath('process.md')),
|
|
2652
|
+
'★ [task-30] the human-readable mirror is really written (Shared/Feedback/process.md)')
|
|
2653
|
+
const list1 = await h.callTool('vibe_v5_feedback', { op: 'list' }, r1)
|
|
2654
|
+
assert(list1.count === 1 && list1.entries[0].id === 'fb-1' && list1.openOnly === true,
|
|
2655
|
+
'★ [task-30] list defaults to the NOT-yet-closed entries')
|
|
2656
|
+
const upd = await h.callTool('vibe_v5_feedback', { op: 'update', id: 'fb-1', status: 'adjusted', outcome: '工作流改完,空转少了' }, r1)
|
|
2657
|
+
assert(upd.ok === true && upd.entry.status === 'adjusted' && /空转少了/.test(upd.entry.outcome),
|
|
2658
|
+
'★ [task-30] status flows open→adjusted WITHOUT any approval step (self-regulation)')
|
|
2659
|
+
const addTeam = await h.callTool('vibe_v5_feedback', { op: 'add', category: 'cooperation', route: 'team', phenomenon: '任务分配不均', impact: '有人闲置', action: '分派前先看负载' }, acad)
|
|
2660
|
+
assert(addTeam.ok === true, 'the academician can record a team-route entry (organisation / workflow)')
|
|
2661
|
+
const forbidden = await h.callTool('vibe_v5_feedback', { op: 'update', id: addTeam.entry.id, status: 'closed' }, r1)
|
|
2662
|
+
assert(forbidden.ok === false && forbidden.code === 'V5_FEEDBACK_FORBIDDEN',
|
|
2663
|
+
'★ [task-30] a member cannot update somebody else\'s entry (named refusal)')
|
|
2664
|
+
|
|
2665
|
+
// (3) interpersonal:必须带评估;闭环必须回填 outcome(事后验证)
|
|
2666
|
+
const needA = await h.callTool('vibe_v5_feedback', { op: 'add', category: 'conflict', route: 'interpersonal', phenomenon: 'r-2 不回消息', impact: '我卡住', action: '等他回' }, r1)
|
|
2667
|
+
assert(needA.ok === false && needA.code === 'V5_FEEDBACK_NEEDS_ASSESSMENT',
|
|
2668
|
+
'★★ [task-30] the interpersonal route refuses to be recorded WITHOUT the assessment')
|
|
2669
|
+
const inter = await h.callTool('vibe_v5_feedback', { op: 'add', category: 'conflict', route: 'interpersonal', phenomenon: 'r-2 不回消息', impact: '我卡住', action: '约会议当面问', assessment: '让他改习惯是否真更优?先约会议成本更低' }, r1)
|
|
2670
|
+
assert(inter.ok === true && inter.entry.route === 'interpersonal', 'the interpersonal entry is accepted WITH the assessment')
|
|
2671
|
+
const noOut = await h.callTool('vibe_v5_feedback', { op: 'update', id: inter.entry.id, status: 'closed' }, r1)
|
|
2672
|
+
assert(noOut.ok === false && noOut.code === 'V5_FEEDBACK_NEEDS_OUTCOME',
|
|
2673
|
+
'★★ [task-30] closing an interpersonal entry WITHOUT the written-back verification is refused')
|
|
2674
|
+
const officeFix = await h.callTool('vibe_v5_feedback', { op: 'update', id: inter.entry.id, status: 'closed', outcome: '会上确认了回复习惯,之后未再发生' })
|
|
2675
|
+
assert(officeFix.ok === true && officeFix.entry.status === 'closed',
|
|
2676
|
+
'★ [task-30] the OFFICE may resolve ANY entry and write the result back')
|
|
2677
|
+
|
|
2678
|
+
// (4) off:提示词不注入 + 每个 op 都具名拒绝 + 一个字都不写
|
|
2679
|
+
const rpBefore = (await h.callTool('vibe_v5_report', {})).report
|
|
2680
|
+
const idxBefore = readFileSync(fbPath('index.json'), 'utf8')
|
|
2681
|
+
await h.callTool('vibe_v5_set', { feedback: 'off' })
|
|
2682
|
+
const p2 = await wake('r-1')
|
|
2683
|
+
assert(p2.indexOf('【工作经验/流程反馈') === -1, '★★ [task-30] feedback=off: the per-round hint is NOT injected')
|
|
2684
|
+
const offAdd = await h.callTool('vibe_v5_feedback', { op: 'add', category: 'process', route: 'self', phenomenon: 'x', impact: 'y', action: 'z' }, r1)
|
|
2685
|
+
assert(offAdd.ok === false && offAdd.code === 'V5_FEEDBACK_DISABLED' && /已关闭/.test(offAdd.message),
|
|
2686
|
+
'★★ [task-30] off refuses the write BY NAME (never a silent drop)')
|
|
2687
|
+
const offList = await h.callTool('vibe_v5_feedback', { op: 'list', all: true }, r1)
|
|
2688
|
+
assert(offList.ok === false && offList.code === 'V5_FEEDBACK_DISABLED',
|
|
2689
|
+
'off refuses EVERY op, so nothing can look normal while the feature is disabled')
|
|
2690
|
+
const rpAfter = (await h.callTool('vibe_v5_report', {})).report
|
|
2691
|
+
assert(countsLine(rpAfter) === countsLine(rpBefore) && idxBefore === readFileSync(fbPath('index.json'), 'utf8'),
|
|
2692
|
+
'★★ [task-30] off wrote NOTHING (report counts and the on-disk index are unchanged)')
|
|
2693
|
+
await h.callTool('vibe_v5_set', { feedback: 'on' })
|
|
2694
|
+
|
|
2695
|
+
// (5) 计数与库内容一致(工具 summary / report / overview 三个面)
|
|
2696
|
+
const summary = (await h.callTool('vibe_v5_feedback', { op: 'summary' }, r1)).counts
|
|
2697
|
+
const allEntries = (await h.callTool('vibe_v5_feedback', { op: 'list', all: true }, r1)).entries
|
|
2698
|
+
assert(summary.total === allEntries.length && summary.open + summary.closed === summary.total,
|
|
2699
|
+
'★★ [task-30] summary counts match the library contents exactly (' + JSON.stringify(summary) + ')')
|
|
2700
|
+
const byCat = allEntries.reduce((m, e) => { m[e.category] = (m[e.category] || 0) + 1; return m }, {})
|
|
2701
|
+
assert(Object.keys(summary.byCategory).every((k) => summary.byCategory[k] === (byCat[k] || 0)),
|
|
2702
|
+
'★★ [task-30] the by-category counts match the entries')
|
|
2703
|
+
const rp = await h.callTool('vibe_v5_report', {})
|
|
2704
|
+
assert(rp.report.indexOf('## 反馈库') !== -1 && rp.report.indexOf('条目 ' + summary.total + '|未闭环 ' + summary.open) !== -1,
|
|
2705
|
+
'★★ [task-30] report() shows the SAME counts as the library')
|
|
2706
|
+
const ov = await h.callTool('vibe_v5_overview', {}, acad)
|
|
2707
|
+
assert(ov.overview.indexOf('## 反馈库') !== -1 && ov.overview.indexOf('条目 ' + summary.total) !== -1 && ov.overview.indexOf('未闭环 ' + summary.open) !== -1,
|
|
2708
|
+
'★★ [task-30] overview shows the same counts (the academician\'s periodic summary reads this)')
|
|
2709
|
+
}
|
|
2710
|
+
// ---------- 55. task-31: three prompt-review follow-ups (P1 receipt background / P3 empty engine table / P4 frame) ----
|
|
2711
|
+
console.log('\n[55] task-31: P1 首次提回执处给背景+字段最小注解;P3 空引擎表"可用引擎 无";P4 面向代理不再自称"框架"')
|
|
2712
|
+
{
|
|
2713
|
+
// P3/P1 at the SOURCE of the line: the shared module builds it, so call it for real with an empty probe.
|
|
2714
|
+
// The sibling is resolved THROUGH the same seam as the plugin, so a deliberately broken copy is the
|
|
2715
|
+
// module actually under test (otherwise a mutation here could never redden this guard).
|
|
2716
|
+
const mc = await import(new URL('./math-computation.js', PLUGIN))
|
|
2717
|
+
const emptyZh = mc.mathAvailabilityLine({ engines: [] }, 'zh', 'typed+shell')
|
|
2718
|
+
const emptyEn = mc.mathAvailabilityLine({ engines: [] }, 'en', 'typed+shell')
|
|
2719
|
+
// P1: the archive→edit→re-run section explains WHAT a receipt is at its FIRST mention, and the bare
|
|
2720
|
+
// field name carries its own minimal context (only the necessary ones - the section must stay short).
|
|
2721
|
+
assert(emptyZh.indexOf('回执=一次 math_computation 调用的 JSON 结果') !== -1,
|
|
2722
|
+
'★★ [task-31] P1: 归档→编辑→重跑 段首次提到回执处解释了"回执是什么"')
|
|
2723
|
+
assert(emptyZh.indexOf('(脚本内容相对上一份回执变过)') !== -1,
|
|
2724
|
+
'★ [task-31] P1: 裸字段名 scriptChanged 自带最小上下文')
|
|
2725
|
+
assert(emptyEn.indexOf('a RECEIPT is the JSON result of ONE math_computation call') !== -1
|
|
2726
|
+
&& emptyEn.indexOf('(the script content differs from the previous receipt)') !== -1,
|
|
2727
|
+
'★ [task-31] P1: en 段同步给出回执背景与字段上下文')
|
|
2728
|
+
// P3: an empty engine table must not read as "可用的东西:无".
|
|
2729
|
+
assert(emptyZh.indexOf('math_computation:可用引擎 无') !== -1 && emptyZh.indexOf('本机可用') === -1,
|
|
2730
|
+
'★★ [task-31] P3: 空引擎表渲染为「可用引擎 无」: ' + JSON.stringify((/math_computation:[^\n]{0,30}/.exec(emptyZh) || ['<none>'])[0]))
|
|
2731
|
+
assert(emptyEn.indexOf('math_computation: available engines none') !== -1,
|
|
2732
|
+
'★ [task-31] P3: en 空表同步为「available engines none」')
|
|
2733
|
+
// …and the SAME text really reaches a member (end-to-end, not only at the module boundary).
|
|
2734
|
+
const h = makeHost({ pluginModule })
|
|
2735
|
+
await h.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 1 })
|
|
2736
|
+
await h.settleSpawns()
|
|
2737
|
+
await h.callTool('vibe_v5_say', { to: 'r-1', text: '请继续推进。' }, h.childAgent(h.childOf('acad')))
|
|
2738
|
+
const w = await h.peekWakeOf('r-1', 3000)
|
|
2739
|
+
const p = w ? w.text : ''
|
|
2740
|
+
if (w) h.fireEnd(w.childId, { progress: '收到。', solved: false, contextPct: 10 })
|
|
2741
|
+
assert(p.indexOf('回执=一次 math_computation 调用的 JSON 结果') !== -1,
|
|
2742
|
+
'★★ [task-31] P1: 成员那一轮真正读到的提示词就带这段背景')
|
|
2743
|
+
assert(p.indexOf('math_computation:可用引擎 无') !== -1 && p.indexOf('本机可用') === -1,
|
|
2744
|
+
'★★ [task-31] P3: 成员提示词里是「可用引擎 无」')
|
|
2745
|
+
// P4: agent-facing text must not call the plugin "框架" (ambiguous from an agent's point of view:
|
|
2746
|
+
// plugin? host? DSH?). The one class fixed here is the notice frame.
|
|
2747
|
+
const src31 = readFileSync(PLUGIN, 'utf8')
|
|
2748
|
+
assert(src31.indexOf('【框架提示】') === -1, '★★ [task-31] P4: 面向代理文本里不再出现【框架提示】')
|
|
2749
|
+
assert(src31.indexOf("return '【研究所提示】' + m.text") !== -1,
|
|
2750
|
+
'★ [task-31] P4: 研究所回执帧改用【研究所提示】')
|
|
2751
|
+
}
|
|
2540
2752
|
console.log('')
|
|
2541
2753
|
console.log('passed=' + passed + ' failed=' + failed)
|
|
2542
2754
|
if (failed) { console.error('FAILURES:'); for (const f of failures) console.error(' - ' + f); process.exit(1) }
|
|
@@ -1362,9 +1362,10 @@ section('15d Lean 增量 + 异步(spec §1–§4 + 修订 §1–§5):参
|
|
|
1362
1362
|
assert(JSON.stringify(planDefault.paths) === JSON.stringify(['/root/vm']) && planDefault.inject.length === 2, '★ default plan injects exactly the automatic VibeMath root')
|
|
1363
1363
|
const planExtra = H.leanSearchPathPlan(['env', 'lean'], ['/a', '/b'], '/root/vm')
|
|
1364
1364
|
assert(JSON.stringify(planExtra.paths) === JSON.stringify(['/a', '/b', '/root/vm']), '★★ user leanSearchPaths come FIRST, then the automatic root')
|
|
1365
|
-
assert(JSON.stringify(planExtra.inject) === JSON.stringify(['
|
|
1366
|
-
assert(H.leanSearchPathPlan(['--search-path', '/x'], ['/a'], '/root/vm').inject.length === 0, '★ an explicit --search-path in leanArgs suppresses all injection (user wins)')
|
|
1365
|
+
assert(JSON.stringify(planExtra.inject) === JSON.stringify(['-R', '/a', '-R', '/b', '-R', '/root/vm']), 'and each path is injected with its own -R (Lean 4) flag')
|
|
1366
|
+
assert(H.leanSearchPathPlan(['--search-path', '/x'], ['/a'], '/root/vm').inject.length === 0, '★ an explicit legacy Lean-3 --search-path in leanArgs suppresses all injection (user wins)')
|
|
1367
1367
|
assert(H.leanSearchPathPlan(['-R', '/x'], ['/a'], '/root/vm').inject.length === 0 && H.leanSearchPathPlan(['--root', '/x'], ['/a'], '/root/vm').inject.length === 0, '★ -R / --root are honoured too')
|
|
1368
|
+
assert(H.leanSearchPathPlan(['-R=/x'], ['/a'], '/root/vm').inject.length === 0 && H.leanSearchPathPlan(['--search-path=/x'], ['/a'], '/root/vm').inject.length === 0 && H.leanSearchPathPlan(['--root=/x'], ['/a'], '/root/vm').inject.length === 0, '★ the = forms of -R / legacy --search-path / --root are honoured too')
|
|
1368
1369
|
|
|
1369
1370
|
// ── argv:注入位置在用户参数之后、文件名之前 ──────────────────────────────────────
|
|
1370
1371
|
const b = await makeCase('lean-argv')
|
|
@@ -1377,15 +1378,15 @@ section('15d Lean 增量 + 异步(spec §1–§4 + 修订 §1–§5):参
|
|
|
1377
1378
|
const argv1 = leanRuns[leanRuns.length - 1].argv
|
|
1378
1379
|
assert(leanRuns.length === runsBefore + 1, 'the sync run really reached the compiler')
|
|
1379
1380
|
assert(JSON.stringify(argv1.slice(0, 2)) === JSON.stringify(['lean', 'env']), 'the user leanArgs are preserved in order')
|
|
1380
|
-
assert(argv1[argv1.length - 2] === '
|
|
1381
|
+
assert(argv1[argv1.length - 2] === '-R' && SAME(argv1[argv1.length - 1], vibeRoot(b)), '★★ -R + the VibeMath root are injected AFTER the user args and immediately BEFORE the file (argv=' + JSON.stringify(argv1) + ')')
|
|
1381
1382
|
await b.call('vibe_math_set_params', { leanArgs: ['env', 'lean', '--search-path', '/explicit'] })
|
|
1382
1383
|
await b.call('vibe_math_lean_run', { file: 'Formal/srch.lean' })
|
|
1383
1384
|
const argv2 = leanRuns[leanRuns.length - 1].argv
|
|
1384
|
-
assert(argv2.indexOf('--search-path') === 3 && argv2[4] === '/explicit' && argv2.filter((x) => x === '--search-path').length === 1, '★ an explicit --search-path is respected verbatim (no
|
|
1385
|
+
assert(argv2.indexOf('--search-path') === 3 && argv2[4] === '/explicit' && argv2.filter((x) => x === '--search-path').length === 1 && argv2.indexOf('-R') === -1, '★ an explicit legacy --search-path is respected verbatim: nothing is injected (no -R) — got ' + JSON.stringify(argv2))
|
|
1385
1386
|
await b.call('vibe_math_set_params', { leanArgs: ['env', 'lean'], leanSearchPaths: ['/extra1', '/extra2'] })
|
|
1386
1387
|
await b.call('vibe_math_lean_run', { file: 'Formal/srch.lean' })
|
|
1387
1388
|
const argv3 = leanRuns[leanRuns.length - 1].argv
|
|
1388
|
-
assert(JSON.stringify(argv3.slice(-6)) === JSON.stringify(['
|
|
1389
|
+
assert(JSON.stringify(argv3.slice(-6)) === JSON.stringify(['-R', '/extra1', '-R', '/extra2', '-R', slash(vibeRoot(b))]), '★★ leanSearchPaths are injected first, then the automatic root (got ' + JSON.stringify(argv3.slice(-6)) + ')')
|
|
1389
1390
|
|
|
1390
1391
|
// ── 异步状态机:入队立即返回 → 心跳排空 → 只有 settled(ok) 才是通过 ────────────────
|
|
1391
1392
|
const c = await makeCase('lean-async')
|
|
@@ -564,7 +564,7 @@ writeFileSync(join(toolProj, 'Formal', 'hang.lean'), '-- HANG\ntheorem t : 1 = 1
|
|
|
564
564
|
assert(/ok/.test(runGood.stdout), 'the compiler stdout is returned')
|
|
565
565
|
const last = leanRuns[leanRuns.length - 1]
|
|
566
566
|
assert(last.cwd.replace(/\\/g, '/') === toolProj.replace(/\\/g, '/'), 'the toolchain runs with the PROJECT root as cwd')
|
|
567
|
-
assert(last.argv.join(' ') === 'lean
|
|
567
|
+
assert(last.argv.join(' ') === 'lean -R ' + VIBE.replace(/\\/g, '/'), 'by default the argv is the executable + the injected -R (Lean 4) search root (got ' + last.argv.join(' ') + ')')
|
|
568
568
|
assert(last.graceMs === 120000, 'the default leanTimeoutMs is passed down as graceMs (' + last.graceMs + ')')
|
|
569
569
|
}
|
|
570
570
|
{
|
|
@@ -645,7 +645,7 @@ try {
|
|
|
645
645
|
await callTool('vibe_math_set_params', { leanCommand: 'lake', leanArgs: ['env', 'lean'] }, RE)
|
|
646
646
|
const runLake = await callTool('vibe_math_lean_run', { file: 'Formal/good.lean' }, RE)
|
|
647
647
|
assert(runLake.ok === true, 'lake env lean works through leanCommand/leanArgs')
|
|
648
|
-
assert(leanRuns[leanRuns.length - 1].argv.join(' ') === 'lake env lean
|
|
648
|
+
assert(leanRuns[leanRuns.length - 1].argv.join(' ') === 'lake env lean -R ' + VIBE.replace(/\\/g, '/'), 'the argv is [exe, ...leanArgs, injected -R (Lean 4)] (got ' + leanRuns[leanRuns.length - 1].argv.join(' ') + ')')
|
|
649
649
|
await callTool('vibe_math_set_params', { leanCommand: 'lean', leanArgs: [] }, RE)
|
|
650
650
|
}
|
|
651
651
|
|
|
@@ -1324,6 +1324,7 @@ section('9b Lean 增量 + 异步:参数/argv/队列/去重/只读面/主动性
|
|
|
1324
1324
|
const planExtra = H.leanSearchPathPlan(['env', 'lean'], ['/a', '/b'], '/root/vm')
|
|
1325
1325
|
assert(JSON.stringify(planExtra.paths) === JSON.stringify(['/a', '/b', '/root/vm']), '★★ user leanSearchPaths come FIRST, then the automatic root')
|
|
1326
1326
|
assert(H.leanSearchPathPlan(['--search-path', '/x'], ['/a'], '/root/vm').inject.length === 0 && H.leanSearchPathPlan(['-R', '/x'], ['/a'], '/root/vm').inject.length === 0 && H.leanSearchPathPlan(['--root', '/x'], ['/a'], '/root/vm').inject.length === 0, '★ an explicit --search-path/-R/--root in leanArgs suppresses all injection (user wins)')
|
|
1327
|
+
assert(H.leanSearchPathPlan(['-R=/x'], ['/a'], '/root/vm').inject.length === 0 && H.leanSearchPathPlan(['--search-path=/x'], ['/a'], '/root/vm').inject.length === 0 && H.leanSearchPathPlan(['--root=/x'], ['/a'], '/root/vm').inject.length === 0, '★ the = forms of -R / legacy --search-path / --root are honoured too')
|
|
1327
1328
|
const ctxA = H.leanBuildContext('lean', ['a'], ['/p'])
|
|
1328
1329
|
const ctxB = H.leanBuildContext('lean', ['b'], ['/p'])
|
|
1329
1330
|
assert(H.leanJobFingerprint('x\n', ctxA) !== H.leanJobFingerprint('x\n', ctxB), '★ the job fingerprint covers the build context (engine/args/search paths)')
|
|
@@ -1339,15 +1340,15 @@ section('9b Lean 增量 + 异步:参数/argv/队列/去重/只读面/主动性
|
|
|
1339
1340
|
await callTool('vibe_math_lean_run', { file: 'Formal/srch.lean' }, ra)
|
|
1340
1341
|
const argv1 = leanRuns[leanRuns.length - 1].argv
|
|
1341
1342
|
assert(leanRuns.length === runs0 + 1 && JSON.stringify(argv1.slice(0, 3)) === JSON.stringify(['lake', 'env', 'lean']), 'the user leanArgs are preserved in order (lake env lean)')
|
|
1342
|
-
assert(argv1[argv1.length - 2] === '
|
|
1343
|
+
assert(argv1[argv1.length - 2] === '-R' && SAME(argv1[argv1.length - 1], VIBE), '★★ -R + the VibeMath root are injected AFTER the user args and immediately BEFORE the file (' + JSON.stringify(argv1) + ')')
|
|
1343
1344
|
await callTool('vibe_math_set_params', { leanArgs: ['env', 'lean', '--search-path', '/explicit'] }, ra)
|
|
1344
1345
|
await callTool('vibe_math_lean_run', { file: 'Formal/srch.lean' }, ra)
|
|
1345
1346
|
const argv2 = leanRuns[leanRuns.length - 1].argv
|
|
1346
|
-
assert(argv2.filter((x) => x === '--search-path').length === 1 && argv2[4] === '/explicit', '★ an explicit --search-path is respected verbatim (no
|
|
1347
|
+
assert(argv2.filter((x) => x === '--search-path').length === 1 && argv2[4] === '/explicit' && argv2.indexOf('-R') === -1, '★ an explicit legacy --search-path is respected verbatim: nothing is injected (no -R) — got ' + JSON.stringify(argv2))
|
|
1347
1348
|
await callTool('vibe_math_set_params', { leanArgs: ['env', 'lean'], leanSearchPaths: ['/extra1', '/extra2'] }, ra)
|
|
1348
1349
|
await callTool('vibe_math_lean_run', { file: 'Formal/srch.lean' }, ra)
|
|
1349
1350
|
const argv3 = leanRuns[leanRuns.length - 1].argv
|
|
1350
|
-
assert(JSON.stringify(argv3.slice(-6)) === JSON.stringify(['
|
|
1351
|
+
assert(JSON.stringify(argv3.slice(-6)) === JSON.stringify(['-R', '/extra1', '-R', '/extra2', '-R', slash(VIBE)]), '★★ leanSearchPaths are injected first, then the automatic root (got ' + JSON.stringify(argv3.slice(-6)) + ')')
|
|
1351
1352
|
|
|
1352
1353
|
// ── 异步状态机:入队立即返回 → 心跳排空 → 只有 settled(ok) 才是通过 ───────────────
|
|
1353
1354
|
const rq = makeRoot()
|
|
@@ -1507,14 +1507,14 @@ section('22 leanSearchPaths/leanJobsMaxParallel/leanInitiative + build-context j
|
|
|
1507
1507
|
writeIn(N, 'Projects/default/Formal/a-sp.lean', 'theorem a_sp : True := by trivial\n')
|
|
1508
1508
|
await N.callTool('vibe_v4_lean_run', { file: 'Formal/a-sp.lean' }, N.resAgent(N.childOf('r-1')))
|
|
1509
1509
|
let last = leanRuns[leanRuns.length - 1]
|
|
1510
|
-
const spIdx = last.argv.indexOf('
|
|
1511
|
-
assert(last.argv.filter(a => a === '
|
|
1510
|
+
const spIdx = last.argv.indexOf('-R')
|
|
1511
|
+
assert(last.argv.filter(a => a === '-R').length === 3, '★ one -R (Lean 4 root) per DISTINCT root (user paths first, then the automatic VibeMath root)')
|
|
1512
1512
|
assert(last.argv[spIdx + 1] === '/extra/one' && last.argv[spIdx + 3] === '/extra/two' && last.argv[spIdx + 5] === N.vibeRoot.replace(/\\/g, '/'), '★ order + de-duplication: user paths in order, automatic root last')
|
|
1513
|
-
assert(last.argv.indexOf('
|
|
1513
|
+
assert(last.argv.indexOf('-R') < last.argv.length - 1 && /\.lean$/.test(String(last.argv[last.argv.length - 1])), 'the roots stay BEFORE the file name')
|
|
1514
1514
|
await N.callTool('vibe_v4_set', { leanArgs: ['--search-path', '/user/own'] })
|
|
1515
1515
|
await N.callTool('vibe_v4_lean_run', { file: 'Formal/a-sp.lean' }, N.resAgent(N.childOf('r-1')))
|
|
1516
1516
|
last = leanRuns[leanRuns.length - 1]
|
|
1517
|
-
assert(last.argv.filter(a => a === '--search-path').length === 1 && last.argv[last.argv.indexOf('--search-path') + 1] === '/user/own', '★ an explicit --search-path in leanArgs wins outright: nothing is injected')
|
|
1517
|
+
assert(last.argv.filter(a => a === '--search-path').length === 1 && last.argv[last.argv.indexOf('--search-path') + 1] === '/user/own' && last.argv.indexOf('-R') === -1, '★ an explicit legacy --search-path in leanArgs wins outright: nothing is injected (no -R)')
|
|
1518
1518
|
await N.callTool('vibe_v4_set', { leanArgs: ['-R', '/user/root'] })
|
|
1519
1519
|
await N.callTool('vibe_v4_lean_run', { file: 'Formal/a-sp.lean' }, N.resAgent(N.childOf('r-1')))
|
|
1520
1520
|
assert(leanRuns[leanRuns.length - 1].argv.indexOf('--search-path') === -1, '★ -R/--root count as a user-stated root too')
|
|
@@ -639,19 +639,19 @@ const wakeR1With = async (kind, reply) => {
|
|
|
639
639
|
await callTool('vibe_v5_say', { to: 'r-1', text: '请就当前状态给个结论。' }, childAgent(rd2))
|
|
640
640
|
await settle(); await drainWakes(20, RD)
|
|
641
641
|
const handled = delivered.slice()
|
|
642
|
-
for (const w of handled) if (
|
|
642
|
+
for (const w of handled) if (/【研究所提示】/.test(w.prompt)) recordAndCheck(kind, w.owner, w.prompt).length
|
|
643
643
|
return handled.map(w => w.prompt).join('\n')
|
|
644
644
|
}
|
|
645
645
|
const badVerdictText = await wakeR1With('notice', { verdict: { target: 'p-ghost', verdict: 'not-a-number', reason: 'x' }, contextPct: 20 })
|
|
646
|
-
assert(
|
|
646
|
+
assert(/【研究所提示】/.test(badVerdictText), 'a malformed verdict produces a real 【研究所提示】 delivery')
|
|
647
647
|
assert(/verdict 必须是 0-1 的数值/.test(badVerdictText), 'the notice says what was wrong')
|
|
648
|
-
assert(
|
|
648
|
+
assert(/【研究所提示】[^\n]*verdict/.test(badVerdictText), 'the notice is framed by the institute, not by the member itself')
|
|
649
649
|
const badClaimText = await wakeR1With('notice-claim', { task_claim: 't-999', contextPct: 20 })
|
|
650
|
-
assert(
|
|
650
|
+
assert(/【研究所提示】/.test(badClaimText) && /没有任务 t-999/.test(badClaimText), 'a refused claim is reported back to the claimer')
|
|
651
651
|
// A CAS refusal reported through the JSON reply must also come back.
|
|
652
652
|
const t = await callTool('vibe_v5_task_create', { subject: '一个没人认领的任务' }, childAgent(rd1))
|
|
653
653
|
const staleText = await wakeR1With('notice-task', { task_update: { task_id: t.task.id, expected_revision: 99, action: 'claim' }, contextPct: 20 })
|
|
654
|
-
assert(
|
|
654
|
+
assert(/【研究所提示】/.test(staleText) && /V5_TASK_STALE_REVISION/.test(staleText), 'a stale CAS reported through the reply channel is echoed back')
|
|
655
655
|
const selfFramed = corpus.filter(c => new RegExp('【研究所·私信 from ' + c.owner + '】').test(c.prompt))
|
|
656
656
|
assert(selfFramed.length === 0, 'no member ever receives a message framed as coming from itself')
|
|
657
657
|
await endCase(RD)
|
|
@@ -1278,7 +1278,7 @@ const corpusMd = readFileSync(mdPath, 'utf8')
|
|
|
1278
1278
|
assert(corpusMd.length > 30000, 'the corpus is substantial (' + corpusMd.length + ' chars) — the real prompt text is preserved')
|
|
1279
1279
|
assert(corpusMd.indexOf('[状态] 你是 acad(院士)') !== -1, 'a human can verify the academician brief verbatim')
|
|
1280
1280
|
assert(corpusMd.indexOf('[状态] 你是 r-2') !== -1, 'a human can verify a researcher brief naming itself')
|
|
1281
|
-
assert(corpusMd.indexOf('
|
|
1281
|
+
assert(corpusMd.indexOf('【研究所提示】') !== -1, 'the corpus contains the institute-feedback interaction')
|
|
1282
1282
|
assert(corpusMd.indexOf('【会话重建 —— ') !== -1, 'the corpus contains a resume brief')
|
|
1283
1283
|
assert(corpusMd.indexOf('【所办分派】') !== -1, 'the corpus contains an office assignment')
|
|
1284
1284
|
assert(!/你是 \?/.test(corpusMd), 'the corpus contains NO wrong-identity "?" brief')
|
package/tests/run-tests.mjs
CHANGED
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
* node tests/run-tests.mjs --no-temp-hygiene # skip the sweep and the roomier-drive temp root
|
|
38
38
|
*/
|
|
39
39
|
import { spawn } from 'node:child_process'
|
|
40
|
-
import { existsSync, readdirSync } from 'node:fs'
|
|
41
|
-
import { cpus } from 'node:os'
|
|
40
|
+
import { existsSync, readdirSync, mkdtempSync, rmSync } from 'node:fs'
|
|
41
|
+
import { cpus, tmpdir } from 'node:os'
|
|
42
42
|
import { join } from 'node:path'
|
|
43
43
|
import { fileURLToPath } from 'node:url'
|
|
44
44
|
|
|
@@ -92,6 +92,22 @@ if (!has('no-temp-hygiene') && !has('self-check') && !has('counts')) {
|
|
|
92
92
|
}
|
|
93
93
|
} catch (e) { console.error('run-tests: temp hygiene skipped (' + ((e && e.message) || e) + ')') }
|
|
94
94
|
}
|
|
95
|
+
// task-29: HOST-INDEPENDENT GATE. Whether THIS machine has TeX Live installed must not decide whether the
|
|
96
|
+
// suites that assert the "no LaTeX engine here" degradation are green: on this dev box `D:\texlive\...`
|
|
97
|
+
// really exists and the product now FINDS it (that is the task-28 fix), so those suites would redden for a
|
|
98
|
+
// reason that has nothing to do with the code under test. Point every child suite (they inherit
|
|
99
|
+
// `process.env` — see runSuite's spawn, which passes no `env`) at ONE empty scratch directory through the
|
|
100
|
+
// four documented-root sandbox gates: the product still probes every documented TeX root, one by one,
|
|
101
|
+
// inside that empty directory, finds nothing, and `triedPaths` still NAMES what it looked at. Real-machine
|
|
102
|
+
// discovery is covered by the SLV live runs, not by the gate; `audit-engine-faces` FACE 3 drives an
|
|
103
|
+
// explicit `paperLatexCommand`, which the sandbox never touches (only the known-install stage is rebased).
|
|
104
|
+
// Nothing else about the sweep changes; the scratch dir is removed when the runner exits.
|
|
105
|
+
const TEX_ROOTS_SANDBOX_DIR = mkdtempSync(join(tmpdir(), 'vibe-gate-tex-sandbox-'))
|
|
106
|
+
process.env.V2_TEX_ROOTS_SANDBOX = TEX_ROOTS_SANDBOX_DIR
|
|
107
|
+
process.env.V3_TEX_ROOTS_SANDBOX = TEX_ROOTS_SANDBOX_DIR
|
|
108
|
+
process.env.V4_TEX_ROOTS_SANDBOX = TEX_ROOTS_SANDBOX_DIR
|
|
109
|
+
process.env.V5_TEX_ROOTS_SANDBOX = TEX_ROOTS_SANDBOX_DIR
|
|
110
|
+
process.on('exit', () => { try { rmSync(TEX_ROOTS_SANDBOX_DIR, { recursive: true, force: true }) } catch (e) { /* best effort: a leftover empty scratch dir must never fail the gate */ } })
|
|
95
111
|
const asJson = has('json')
|
|
96
112
|
// task-13 (MEASURED): the default used to be min(4, cpus) = 4 on this 4-core box, and the heavy jobs then
|
|
97
113
|
// ran 1.3x-4.5x SLOWER than standalone - the gate's own "slowest" lines recorded v2-fix-probes.mutants
|
|
@@ -103,6 +103,20 @@ const FAMILIES = [
|
|
|
103
103
|
to: "zh?''",
|
|
104
104
|
expect: /\[deliverable\/v4\]/,
|
|
105
105
|
},
|
|
106
|
+
{
|
|
107
|
+
// task-23: a meeting speech missing from the end block must be recorded with a NAMED placeholder.
|
|
108
|
+
name: 'real1004-minutes: a silent meeting speech is stored as an empty body again',
|
|
109
|
+
from: "'(本轮结束块未含 input/summary:框架按空发言记录,见 docs/final-paper.md)'",
|
|
110
|
+
to: "''",
|
|
111
|
+
expect: /\[real1004-minutes\] a meeting speech missing from the end block is recorded with a NAMED placeholder/,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
// task-27: v4 must inject Lean 4's -R, not the Lean-3 --search-path.
|
|
115
|
+
name: 'real1004-lean-flag: v4 falls back to the Lean-3 --search-path',
|
|
116
|
+
from: "const LEAN_SEARCH_PATH_FLAG = '-R'",
|
|
117
|
+
to: "const LEAN_SEARCH_PATH_FLAG = '--search-path'",
|
|
118
|
+
expect: /\[real1004-lean-flag\] v4 injects -R/,
|
|
119
|
+
},
|
|
106
120
|
]
|
|
107
121
|
let red = 0
|
|
108
122
|
for (const f of FAMILIES) { const t0 = Date.now(); const ok = runFamily(f); const ms = Date.now() - t0; if (ok) red++ }
|
|
@@ -621,6 +621,24 @@ section('G3 a paper whose REQUIRED artifact was not written is FAILED, never don
|
|
|
621
621
|
await finish(m)
|
|
622
622
|
}
|
|
623
623
|
|
|
624
|
+
// task-23 (real-host R3): all four meetings recorded correct speaker headers with EMPTY bodies, because a
|
|
625
|
+
// resident's end block carried no `input`/`summary` and the collector stored '' silently. A NAMED placeholder is
|
|
626
|
+
// required instead. SOURCE-LEVEL invariant: driving a full meeting with a deliberately silent resident would
|
|
627
|
+
// exercise the harness more than this one line.
|
|
628
|
+
{
|
|
629
|
+
const v4src = readFileSync(PLUGIN, 'utf8') // PLUGIN points at the mutated COPY when a family runs this suite
|
|
630
|
+
assert(v4src.includes('(本轮结束块未含 input/summary:框架按空发言记录') && !v4src.includes("{input:parsed.input||parsed.summary||'',voteSolved:"),
|
|
631
|
+
'★ [real1004-minutes] a meeting speech missing from the end block is recorded with a NAMED placeholder, never as a silent empty body')
|
|
632
|
+
}
|
|
633
|
+
// task-27: the same Lean-4 flag correction as v5 — the injected search root must be `-R`, and no agent-facing
|
|
634
|
+
// tool description may still teach the Lean-3 `--search-path` (which makes `lean` fail at argument parsing).
|
|
635
|
+
{
|
|
636
|
+
const v4s = readFileSync(PLUGIN, 'utf8')
|
|
637
|
+
assert(v4s.includes("const LEAN_SEARCH_PATH_FLAG = '-R'"),
|
|
638
|
+
'★ [real1004-lean-flag] v4 injects -R (Lean 4 root), not the Lean-3 --search-path')
|
|
639
|
+
assert(!v4s.includes('--search-path <VibeMath root>') && !/registerTool\(\s*'[^']+'\s*,\s*'[^']*--search-path/.test(v4s),
|
|
640
|
+
'★ [real1004-lean-flag] no v4 tool description still teaches --search-path')
|
|
641
|
+
}
|
|
624
642
|
console.log('passed=' + passed + ' failed=' + failed)
|
|
625
643
|
if (failed) { console.error('FAILURES:'); for (const f of failures) console.error(' - ' + f) }
|
|
626
644
|
if (failed) process.exit(1)
|
|
@@ -243,6 +243,113 @@ const FAMILIES = [
|
|
|
243
243
|
to: 'if (stale >= recoverStallMs()) {',
|
|
244
244
|
expect: /\[real1004-stall\] the meeting watchdog exempts ASKED-but-busy speakers/,
|
|
245
245
|
},
|
|
246
|
+
{
|
|
247
|
+
// task-24: the member-prompt statement line must MARK its cut, not hide the tail (a real host registered
|
|
248
|
+
// statements longer than the display cap and the truncated text is what voters read).
|
|
249
|
+
name: 'real1004-visibility: the member-prompt statement cut becomes silent again',
|
|
250
|
+
from: "'…(已截断;完整陈述见源卡片 '",
|
|
251
|
+
to: "''",
|
|
252
|
+
expect: /\[real1004-visibility\] the member-prompt statement line marks its 800-char cut/,
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
// task-26: the set response must keep reporting what it coerced.
|
|
256
|
+
name: 'real1004-visibility: the set response stops reporting adjusted values',
|
|
257
|
+
from: 'return { ok: true, params: visibleParams(), adjusted, dropped: droppedSet.slice() }',
|
|
258
|
+
to: 'return { ok: true, params: visibleParams(), adjusted: {}, dropped: droppedSet.slice() }',
|
|
259
|
+
expect: /\[real1004-visibility\] vibe_v5_set reports `adjusted`/,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
// task-26: the two capacity refusals must stay machine-distinguishable.
|
|
263
|
+
name: 'real1004-visibility: the institute capacity refusal reuses the per-member scope',
|
|
264
|
+
from: "scope: 'institute'",
|
|
265
|
+
to: "scope: 'per-member'",
|
|
266
|
+
expect: /\[real1004-visibility\] the two capacity refusals carry DISTINCT/,
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
// task-25: the fieldScopes declaration must keep covering the payload fields it used to omit.
|
|
270
|
+
name: 'real1004-visibility: fieldScopes stops declaring diagnostics/backend again',
|
|
271
|
+
from: "'pendingSpawns[].attempts', 'diagnostics', 'backend'],",
|
|
272
|
+
to: "'pendingSpawns[].attempts'],",
|
|
273
|
+
expect: /\[real1004-visibility\] fieldScopes.session covers the payload fields it used to omit/,
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
// task-25: an incomplete electorate must keep naming who is still missing.
|
|
277
|
+
name: 'real1004-visibility: the verdict response stops naming the missing voters',
|
|
278
|
+
from: 'return { ok: true, voted: memberId, verdict: p, allVoted, pendingVoters: need.filter((id) => !votes[id]) }',
|
|
279
|
+
to: 'return { ok: true, voted: memberId, verdict: p, allVoted }',
|
|
280
|
+
expect: /\[real1004-visibility\] an incomplete electorate NAMES the missing voters/,
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
// task-27: the injected Lean search root must stay on Lean 4's -R (the Lean-3 spelling fails argument parsing).
|
|
284
|
+
name: 'real1004-lean-flag: the injected search root falls back to the Lean-3 --search-path',
|
|
285
|
+
from: "const LEAN_SEARCH_PATH_FLAG = '-R'",
|
|
286
|
+
to: "const LEAN_SEARCH_PATH_FLAG = '--search-path'",
|
|
287
|
+
expect: /\[real1004-lean-flag\] the injected Lean search root uses -R/,
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
// Iron-rules P4: the shared LaTeX tail must say when it applies (otherwise it is unexplained noise in
|
|
291
|
+
// founding/verify/meeting wakes).
|
|
292
|
+
name: 'real1004-prompt-scope: the LaTeX tail loses its scope note again',
|
|
293
|
+
from: "if (b) L.push('\\n(**以下这段仅在你参与论文写作或编译时适用**;其他阶段可忽略。)' + b)",
|
|
294
|
+
to: 'if (b) L.push(b)',
|
|
295
|
+
expect: /\[real1004-prompt-scope\] the LaTeX block says WHEN it applies/,
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
// task-30: the per-round feedback hint must actually reach the members (it is the only place that
|
|
299
|
+
// explains WHY/WHAT/the three routes); suppressing it must redden the prompt guard BY NAME.
|
|
300
|
+
name: 'task-30: the per-round feedback hint is never injected',
|
|
301
|
+
from: "if (!feedbackOn()) return\n L.push(''",
|
|
302
|
+
to: "if (true) return\n L.push(''",
|
|
303
|
+
expect: /\[task-30\] the per-round prompt carries the feedback hint/,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
// task-30: `off` must be a real switch — if the gate always answers "on", the hint keeps being
|
|
307
|
+
// injected and the refusal never fires.
|
|
308
|
+
name: 'task-30: feedback=off stops suppressing the prompt hint',
|
|
309
|
+
from: "function feedbackOn() { return String(params.feedback || 'on') !== 'off' }",
|
|
310
|
+
to: 'function feedbackOn() { return true }',
|
|
311
|
+
expect: /\[task-30\] feedback=off: the per-round hint is NOT injected/,
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
// task-30: `off` must refuse BY NAME and write NOTHING (a silent write while disabled is the defect
|
|
315
|
+
// class this project keeps hitting).
|
|
316
|
+
name: 'task-30: feedback=off still accepts and writes an entry',
|
|
317
|
+
from: "if (!feedbackOn()) {\n return { ok: false, code: 'V5_FEEDBACK_DISABLED'",
|
|
318
|
+
to: "if (false) {\n return { ok: false, code: 'V5_FEEDBACK_DISABLED'",
|
|
319
|
+
expect: /\[task-30\] off refuses the write BY NAME/,
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
// task-30: the published counts must agree with the library — a wrong `closed` split makes the tool,
|
|
323
|
+
// the report and the overview lie together.
|
|
324
|
+
name: 'task-30: the feedback counters disagree with the library',
|
|
325
|
+
from: 'return { total: all.length, open, closed: all.length - open, byCategory, byRoute }',
|
|
326
|
+
to: 'return { total: all.length, open, closed: all.length, byCategory, byRoute }',
|
|
327
|
+
expect: /\[task-30\] summary counts match the library contents exactly/,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
// task-31/P1: the archive→edit→re-run section must SAY what a receipt is at its first mention,
|
|
331
|
+
// otherwise a member that never produced one can only guess (A/R2: members probed around for it).
|
|
332
|
+
name: 'task-31: the archive-workflow line drops the receipt background again',
|
|
333
|
+
editFile: 'math-computation.js',
|
|
334
|
+
from: '- 归档→编辑→重跑(**回执=一次 math_computation 调用的 JSON 结果**:先 probe 或 run 一次,它有哪些字段就一目了然):mode:',
|
|
335
|
+
to: '- 归档→编辑→重跑:mode:',
|
|
336
|
+
expect: /\[task-31\] P1: (归档→编辑→重跑|成员那一轮真正读到)/,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
// task-31/P3: with no engine the line must not render "本机可用 (无)" (available + none).
|
|
340
|
+
name: 'task-31: the empty engine table reads "本机可用 (无)" again',
|
|
341
|
+
editFile: 'math-computation.js',
|
|
342
|
+
from: "const head = '- math_computation:可用引擎 ' + (list || '无')",
|
|
343
|
+
to: "const head = '- math_computation:本机可用 ' + (list || '(无)')",
|
|
344
|
+
expect: /\[task-31\] P3: (空引擎表渲染为|成员提示词里是)/,
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
// task-31/P4: agent-facing text must not call the plugin "框架" (ambiguous to an agent: plugin? host?).
|
|
348
|
+
name: 'task-31: the notice frame calls the plugin 框架 again',
|
|
349
|
+
from: "if (m.kind === 'notice') return '【研究所提示】' + m.text",
|
|
350
|
+
to: "if (m.kind === 'notice') return '【框架提示】' + m.text",
|
|
351
|
+
expect: /\[task-31\] P4: 面向代理文本里不再出现/,
|
|
352
|
+
},
|
|
246
353
|
]
|
|
247
354
|
let red = 0
|
|
248
355
|
for (const f of FAMILIES) { const ok = runFamily(f); if (ok) red++ }
|