dsh-vibe-math 2.6.0 → 2.7.1
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 +161 -389
- package/README.md +161 -390
- package/cordis.patch.yml +62 -14
- package/docs/AUDIT-CHECKLIST.md +2 -2
- package/docs/final-paper.md +1 -0
- package/docs/formal-verification.md +184 -29
- package/docs/math-computation.md +163 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.0.en.md +151 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.0.md +115 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.1.en.md +86 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.1.md +86 -0
- package/docs/test-timing.md +24 -12
- package/installer.js +7 -4
- package/package.json +21 -3
- package/prompt-corpus-persona/persona-corpus.json +12 -12
- package/prompt-corpus-persona/persona-corpus.md +128 -32
- package/prompt-corpus-v2/formal-verify-v2.json +182 -92
- package/prompt-corpus-v2/formal-verify-v2.md +2379 -55
- package/prompt-corpus-v3/formal-verify-v3.json +215 -53
- package/prompt-corpus-v3/formal-verify-v3.md +2147 -22
- package/prompt-corpus-v4/formal-verify-v4.json +14 -14
- package/prompt-corpus-v4/formal-verify-v4.md +267 -14
- package/prompt-corpus-v5/prompt-corpus-v5.json +62 -62
- package/prompt-corpus-v5/prompt-corpus-v5.md +631 -3
- package/tests/audit-math-computation-contract.mjs +204 -0
- package/tests/audit-math-computation-parity.mjs +198 -0
- package/tests/audit-math-computation-sensitivity.mjs +231 -0
- package/tests/audit-persona-sensitivity.mjs +4 -4
- package/tests/audit-persona-surface.test.mjs +38 -10
- package/tests/audit-v5-integrity.mjs +14 -0
- package/tests/e2e-v5-round2.test.mjs +459 -2
- package/tests/formal-verify-v2.test.mjs +362 -8
- package/tests/formal-verify-v3.test.mjs +355 -6
- package/tests/formal-verify-v4.test.mjs +295 -6
- package/tests/formal-verify-v5.test.mjs +9 -1
- package/tests/helpers/math-computation-fake-seam.mjs +163 -0
- package/tests/math-computation-shared.test.mjs +806 -0
- package/tests/prompt-v5-integrity.test.mjs +5 -5
- package/vibe-math-v2/agent.cordis.yml +22 -8
- package/vibe-math-v2/math-computation.js +1279 -0
- package/vibe-math-v2/math-engines.js +197 -0
- package/vibe-math-v2/vibe-math-v2.js +853 -40
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +6 -0
- package/vibe-math-v3/agent.cordis.yml +22 -8
- package/vibe-math-v3/math-computation.js +1279 -0
- package/vibe-math-v3/math-engines.js +197 -0
- package/vibe-math-v3/vibe-math-v3.js +844 -47
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +6 -0
- package/vibe-math-v4/agent.cordis.yml +38 -6
- package/vibe-math-v4/math-computation.js +1279 -0
- package/vibe-math-v4/math-engines.js +197 -0
- package/vibe-math-v4/vibe-math-v4.js +816 -31
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +6 -0
- package/vibe-math-v5/agent.cordis.yml +42 -6
- package/vibe-math-v5/math-computation.js +1279 -0
- package/vibe-math-v5/math-engines.js +197 -0
- package/vibe-math-v5/vibe-math-v5.js +1132 -37
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +83 -3
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +251 -0
|
@@ -22,10 +22,14 @@
|
|
|
22
22
|
import { mkdtempSync, rmSync, existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
23
23
|
import { tmpdir } from 'node:os'
|
|
24
24
|
import { join, dirname, isAbsolute, resolve as pathResolve } from 'node:path'
|
|
25
|
-
import { fileURLToPath } from 'node:url'
|
|
25
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
26
26
|
|
|
27
|
+
// V3_PLUGIN may be a REPO-RELATIVE path or an ABSOLUTE one (a mutant copy outside the repo, e.g. a
|
|
28
|
+
// temp dir). The previous `new URL('file:///' + path)` form only worked for drive-qualified Windows
|
|
29
|
+
// paths and made out-of-repo variants impossible; this is the same isAbsolute-aware resolution the
|
|
30
|
+
// `math-computation-v*` suites use. Default behaviour (env var unset) is unchanged.
|
|
27
31
|
const PLUGIN = process.env.V3_PLUGIN
|
|
28
|
-
?
|
|
32
|
+
? pathToFileURL(isAbsolute(process.env.V3_PLUGIN) ? process.env.V3_PLUGIN : join(dirname(fileURLToPath(import.meta.url)), '..', process.env.V3_PLUGIN))
|
|
29
33
|
: new URL('../vibe-math-v3/vibe-math-v3.js', import.meta.url)
|
|
30
34
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
31
35
|
// AUDIT-CHECKLIST §2.4: the suite must also keep the INTERACTION TEXT it drove, so a human can
|
|
@@ -55,6 +59,7 @@ const wakes = [] // { childId, rootId, prompt }
|
|
|
55
59
|
const interrupts = []
|
|
56
60
|
const leanRuns = [] // { argv, file, cwd, graceMs }
|
|
57
61
|
const terminated = []
|
|
62
|
+
const disposers = []
|
|
58
63
|
const roots = []
|
|
59
64
|
let rootSeq = 0
|
|
60
65
|
let subprocessAvailable = true
|
|
@@ -187,7 +192,7 @@ const ctx = {
|
|
|
187
192
|
return undefined
|
|
188
193
|
},
|
|
189
194
|
on(e, fn) { (listeners[e] = listeners[e] || []).push(fn) },
|
|
190
|
-
effect(fn) { const d = fn();
|
|
195
|
+
effect(fn) { const d = fn(); const off = () => { if (typeof d === 'function') d() }; disposers.push(off); return off },
|
|
191
196
|
logger: { info() {}, warn() {}, error() {} },
|
|
192
197
|
tools: { register(spec) { toolRegs.push(spec); return () => {} } },
|
|
193
198
|
commands: { register(spec) { cmdRegs.push(spec); return () => {} } },
|
|
@@ -229,6 +234,12 @@ const ctx = {
|
|
|
229
234
|
|
|
230
235
|
const mod = await import(PLUGIN.href + '?t=' + Date.now())
|
|
231
236
|
;(mod.default || mod).apply(ctx)
|
|
237
|
+
// leanAsync defaults to TRUE (background queue). The sections above guard the **synchronous** Lean
|
|
238
|
+
// path verbatim, so pin leanAsync=false through the workspace-level settings file (every
|
|
239
|
+
// setProject → loadSettings() picks it up). Section 9b flips it back on explicitly for the async
|
|
240
|
+
// state machine. Nothing else in the suite depends on the async default.
|
|
241
|
+
mkdirSync(VIBE, { recursive: true })
|
|
242
|
+
writeFileSync(join(VIBE, 'vibe_math_setting.json'), '{ "leanAsync": false }\n', 'utf8')
|
|
232
243
|
|
|
233
244
|
// ---------------------------------------------------------------
|
|
234
245
|
// driving helpers
|
|
@@ -518,7 +529,7 @@ writeFileSync(join(toolProj, 'Formal', 'hang.lean'), '-- HANG\ntheorem t : 1 = 1
|
|
|
518
529
|
assert(/ok/.test(runGood.stdout), 'the compiler stdout is returned')
|
|
519
530
|
const last = leanRuns[leanRuns.length - 1]
|
|
520
531
|
assert(last.cwd.replace(/\\/g, '/') === toolProj.replace(/\\/g, '/'), 'the toolchain runs with the PROJECT root as cwd')
|
|
521
|
-
assert(last.argv.join(' ') === 'lean', 'by default the argv is
|
|
532
|
+
assert(last.argv.join(' ') === 'lean --search-path ' + VIBE.replace(/\\/g, '/'), 'by default the argv is the executable + the injected search path (got ' + last.argv.join(' ') + ')')
|
|
522
533
|
assert(last.graceMs === 120000, 'the default leanTimeoutMs is passed down as graceMs (' + last.graceMs + ')')
|
|
523
534
|
}
|
|
524
535
|
{
|
|
@@ -592,7 +603,7 @@ writeFileSync(join(toolProj, 'Formal', 'hang.lean'), '-- HANG\ntheorem t : 1 = 1
|
|
|
592
603
|
await callTool('vibe_math_set_params', { leanCommand: 'lake', leanArgs: ['env', 'lean'] }, RE)
|
|
593
604
|
const runLake = await callTool('vibe_math_lean_run', { file: 'Formal/good.lean' }, RE)
|
|
594
605
|
assert(runLake.ok === true, 'lake env lean works through leanCommand/leanArgs')
|
|
595
|
-
assert(leanRuns[leanRuns.length - 1].argv.join(' ') === 'lake env lean', 'the argv is [exe, ...leanArgs,
|
|
606
|
+
assert(leanRuns[leanRuns.length - 1].argv.join(' ') === 'lake env lean --search-path ' + VIBE.replace(/\\/g, '/'), 'the argv is [exe, ...leanArgs, injected --search-path] (got ' + leanRuns[leanRuns.length - 1].argv.join(' ') + ')')
|
|
596
607
|
await callTool('vibe_math_set_params', { leanCommand: 'lean', leanArgs: [] }, RE)
|
|
597
608
|
}
|
|
598
609
|
|
|
@@ -609,7 +620,7 @@ const provedPath = join(VIBE, 'Formal', 'Proved')
|
|
|
609
620
|
assert(!existsSync(join(toolProj, 'Formal', 'Lib', 'ZMod5.lean')), 'it is NOT duplicated inside the project tree')
|
|
610
621
|
assert(defRes.run && defRes.run.ok === true, 'the reusable definition is executed on archive (run defaults to true)')
|
|
611
622
|
const libIdx = readIf(join(libPath, 'Index.md'))
|
|
612
|
-
assert(/\| 名称 \| 文件 \| 类别 \| 摘要 \| 最近运行 \|/.test(libIdx), 'Lib/Index.md uses the contract columns')
|
|
623
|
+
assert(/\| 名称 \| 文件 \| 类别 \| 依赖(import) \| 摘要 \| 最近运行 \|/.test(libIdx), 'Lib/Index.md uses the contract columns (incl. the import-dependency column, spec §4.4)')
|
|
613
624
|
assert(/ZMod5/.test(libIdx) && /Lib\/ZMod5\.lean/.test(libIdx) && /\| ok \|/.test(libIdx), 'Lib/Index.md lists the new definition with its run result')
|
|
614
625
|
assert(!/ZMod5/.test(readIf(join(toolProj, 'Formal', 'Index.md'))), 'the global definition does not pollute the project formal index')
|
|
615
626
|
}
|
|
@@ -1224,10 +1235,348 @@ section('9 the office can audit formal strength')
|
|
|
1224
1235
|
assert(existsSync(join(VIBE, 'Formal', 'Proved', 'Index.md')) && existsSync(join(libPath, 'Index.md')), 'both global indexes are framework-maintained')
|
|
1225
1236
|
}
|
|
1226
1237
|
|
|
1238
|
+
// ===============================================================
|
|
1239
|
+
// 9b. Lean 增量 + 异步(spec §1–§4 + 修订 §1–§5):与 v2 同一套断言强度
|
|
1240
|
+
// ===============================================================
|
|
1241
|
+
section('9b Lean 增量 + 异步:参数/argv/队列/去重/只读面/主动性/恢复/卸载')
|
|
1242
|
+
{
|
|
1243
|
+
const H = mod.__testHelpers
|
|
1244
|
+
const slash = (s) => String(s == null ? '' : s).replace(/\\/g, '/')
|
|
1245
|
+
const SAME = (a, b) => slash(a) === slash(b)
|
|
1246
|
+
const formalOf2 = (proj) => JSON.parse(readIf(join(projRoot(proj), 'State', 'formal.json')) || '{}')
|
|
1247
|
+
const jobFile = (proj, id) => join(projRoot(proj), 'Formal', 'Jobs', id + '.json')
|
|
1248
|
+
const readJob = (proj, id) => JSON.parse(readIf(jobFile(proj, id)) || '{}')
|
|
1249
|
+
const vibeCmd = async (rawInput, root) => JSON.parse(await cmdRegs[0].handler({ rawInput, agent: root }))
|
|
1250
|
+
const findJob = async (proj, id, states, tries = 100, ms = 60) => {
|
|
1251
|
+
for (let i = 0; i < tries; i++) { const r = readJob(proj, id); if (r.state && states.indexOf(r.state) !== -1) return r; await sleep(ms) }
|
|
1252
|
+
return undefined
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// ── 参数:默认值/归一化(字符串 'false' 绝不能滑过去)────────────────────────────
|
|
1256
|
+
const rp = makeRoot()
|
|
1257
|
+
await callTool('vibe_math_new_project', { name: 'lean-async-params' }, rp)
|
|
1258
|
+
await callTool('vibe_math_set_params', { leanAsync: true }, rp)
|
|
1259
|
+
const bad = await callTool('vibe_math_set_params', { leanAsync: 'false' }, rp)
|
|
1260
|
+
assert(bad.params.leanAsync === true, '★★★ leanAsync="false" (a STRING) does not slip through the `else out[k]=v` tail: it falls back to the boolean default')
|
|
1261
|
+
const wp = await callTool('vibe_math_set_params', { leanInitiative: 'banana', leanJobsMaxParallel: 0, leanSearchPaths: 'not-an-array' }, rp)
|
|
1262
|
+
assert(wp.params.leanInitiative === 'normal', 'leanInitiative rejects an unknown mode back to normal')
|
|
1263
|
+
assert(wp.params.leanJobsMaxParallel === 1, 'leanJobsMaxParallel floors at 1 (never 0)')
|
|
1264
|
+
assert(Array.isArray(wp.params.leanSearchPaths) && wp.params.leanSearchPaths.length === 0, 'leanSearchPaths rejects a non-array back to []')
|
|
1265
|
+
const dd = await callTool('vibe_math_set_params', { leanSearchPaths: [' /opt/lean-lib ', '/opt/lean-lib', ''] }, rp)
|
|
1266
|
+
assert(JSON.stringify(dd.params.leanSearchPaths) === JSON.stringify(['/opt/lean-lib']), 'leanSearchPaths trims, drops empties and de-duplicates')
|
|
1267
|
+
|
|
1268
|
+
// ── 纯函数:搜索路径计划(修订 §2)与指纹(修订 §4)──────────────────────────────
|
|
1269
|
+
const planDefault = H.leanSearchPathPlan([], [], '/root/vm')
|
|
1270
|
+
assert(JSON.stringify(planDefault.paths) === JSON.stringify(['/root/vm']) && planDefault.inject.length === 2, '★ the default plan injects exactly the automatic root')
|
|
1271
|
+
const planExtra = H.leanSearchPathPlan(['env', 'lean'], ['/a', '/b'], '/root/vm')
|
|
1272
|
+
assert(JSON.stringify(planExtra.paths) === JSON.stringify(['/a', '/b', '/root/vm']), '★★ user leanSearchPaths come FIRST, then the automatic root')
|
|
1273
|
+
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)')
|
|
1274
|
+
const ctxA = H.leanBuildContext('lean', ['a'], ['/p'])
|
|
1275
|
+
const ctxB = H.leanBuildContext('lean', ['b'], ['/p'])
|
|
1276
|
+
assert(H.leanJobFingerprint('x\n', ctxA) !== H.leanJobFingerprint('x\n', ctxB), '★ the job fingerprint covers the build context (engine/args/search paths)')
|
|
1277
|
+
|
|
1278
|
+
// ── argv:注入在用户参数之后、文件名之前 ─────────────────────────────────────────
|
|
1279
|
+
const ra = makeRoot()
|
|
1280
|
+
await callTool('vibe_math_new_project', { name: 'lean-async-argv' }, ra)
|
|
1281
|
+
await callTool('vibe_math_set_params', { leanAsync: false, leanCommand: 'lake', leanArgs: ['env', 'lean'] }, ra)
|
|
1282
|
+
const projA = projRoot('lean-async-argv')
|
|
1283
|
+
mkdirSync(join(projA, 'Formal'), { recursive: true })
|
|
1284
|
+
writeFileSync(join(projA, 'Formal', 'srch.lean'), 'theorem t : 1 = 1 := rfl\n', 'utf8')
|
|
1285
|
+
const runs0 = leanRuns.length
|
|
1286
|
+
await callTool('vibe_math_lean_run', { file: 'Formal/srch.lean' }, ra)
|
|
1287
|
+
const argv1 = leanRuns[leanRuns.length - 1].argv
|
|
1288
|
+
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)')
|
|
1289
|
+
assert(argv1[argv1.length - 2] === '--search-path' && SAME(argv1[argv1.length - 1], VIBE), '★★ --search-path + the VibeMath root are injected AFTER the user args and immediately BEFORE the file (' + JSON.stringify(argv1) + ')')
|
|
1290
|
+
await callTool('vibe_math_set_params', { leanArgs: ['env', 'lean', '--search-path', '/explicit'] }, ra)
|
|
1291
|
+
await callTool('vibe_math_lean_run', { file: 'Formal/srch.lean' }, ra)
|
|
1292
|
+
const argv2 = leanRuns[leanRuns.length - 1].argv
|
|
1293
|
+
assert(argv2.filter((x) => x === '--search-path').length === 1 && argv2[4] === '/explicit', '★ an explicit --search-path is respected verbatim (no second injection) — got ' + JSON.stringify(argv2))
|
|
1294
|
+
await callTool('vibe_math_set_params', { leanArgs: ['env', 'lean'], leanSearchPaths: ['/extra1', '/extra2'] }, ra)
|
|
1295
|
+
await callTool('vibe_math_lean_run', { file: 'Formal/srch.lean' }, ra)
|
|
1296
|
+
const argv3 = leanRuns[leanRuns.length - 1].argv
|
|
1297
|
+
assert(JSON.stringify(argv3.slice(-6)) === JSON.stringify(['--search-path', '/extra1', '--search-path', '/extra2', '--search-path', slash(VIBE)]), '★★ leanSearchPaths are injected first, then the automatic root (got ' + JSON.stringify(argv3.slice(-6)) + ')')
|
|
1298
|
+
|
|
1299
|
+
// ── 异步状态机:入队立即返回 → 心跳排空 → 只有 settled(ok) 才是通过 ───────────────
|
|
1300
|
+
const rq = makeRoot()
|
|
1301
|
+
await callTool('vibe_math_new_project', { name: 'lean-async-q' }, rq)
|
|
1302
|
+
await callTool('vibe_math_set_params', { leanAsync: true }, rq)
|
|
1303
|
+
const projQ = projRoot('lean-async-q')
|
|
1304
|
+
mkdirSync(join(projQ, 'Formal'), { recursive: true })
|
|
1305
|
+
writeFileSync(join(projQ, 'Formal', 'async-good.lean'), 'theorem t : 1 = 1 := rfl\n', 'utf8')
|
|
1306
|
+
writeFileSync(join(projQ, 'Formal', 'async-bad.lean'), 'theorem t : 1 = 2 := by sorry\n', 'utf8')
|
|
1307
|
+
const nBefore = leanRuns.length
|
|
1308
|
+
const q1 = await callTool('vibe_math_lean_run', { file: 'Formal/async-good.lean', target: 'pAsync' }, rq)
|
|
1309
|
+
assert(q1.ok === true && !!q1.async && q1.async.state === 'queued' && typeof q1.async.jobId === 'string', '★★ leanAsync=true returns IMMEDIATELY with {async:{jobId,state:queued}} (got ' + JSON.stringify(q1.async) + ')')
|
|
1310
|
+
assert(leanRuns.length === nBefore, '★★ the tool call itself did NOT block on the compiler (enqueued, not run)')
|
|
1311
|
+
assert(q1.async.jobId.indexOf('pAsync-') === 0, 'the jobId is <target>-<fingerprint[0:12]> (got ' + q1.async.jobId + ')')
|
|
1312
|
+
assert(existsSync(jobFile('lean-async-q', q1.async.jobId)), 'the job record is mirrored to <project>/Formal/Jobs/<jobId>.json')
|
|
1313
|
+
const rec0 = readJob('lean-async-q', q1.async.jobId)
|
|
1314
|
+
assert(rec0.state === 'queued' && typeof rec0.sha256 === 'string' && typeof rec0.buildSha256 === 'string' && String(rec0.buildContext).length > 0, '★ the job record carries state + content hash + build-context hash')
|
|
1315
|
+
assert((formalOf2('lean-async-q').records || {}).pAsync === undefined || formalOf2('lean-async-q').records.pAsync.status !== 'passed', '★★ a queued job never marks the object passed')
|
|
1316
|
+
const done1 = await findJob('lean-async-q', q1.async.jobId, ['settled'])
|
|
1317
|
+
assert(!!done1, 'the apply-level heartbeat drained the queue and settled the job (got ' + readJob('lean-async-q', q1.async.jobId).state + ')')
|
|
1318
|
+
assert(done1 && done1.exitCode === 0, 'the settled record keeps exitCode 0')
|
|
1319
|
+
const recAfter = (formalOf2('lean-async-q').records || {}).pAsync
|
|
1320
|
+
assert(!!recAfter && recAfter.status === 'attempted' && recAfter.run && recAfter.run.ok === true, 'a green RUN lands as attempted with run.ok=true')
|
|
1321
|
+
assert(!!recAfter.async && recAfter.async.state === 'settled' && recAfter.async.jobId === q1.async.jobId, 'the object record carries the async job pointer/state')
|
|
1322
|
+
const q2 = await callTool('vibe_math_lean_run', { file: 'Formal/async-bad.lean', target: 'pAsyncBad' }, rq)
|
|
1323
|
+
const done2 = await findJob('lean-async-q', q2.async.jobId, ['failed'])
|
|
1324
|
+
assert(!!done2 && done2.exitCode === 1, '★ a red async run settles as failed with exitCode 1')
|
|
1325
|
+
const recBad = (formalOf2('lean-async-q').records || {}).pAsyncBad
|
|
1326
|
+
assert(!!recBad && recBad.status === 'attempted' && recBad.run.ok === false && /sorry/.test(String(recBad.run.stderrTail || '')), '★ a red async run is recorded as attempted with the compiler output kept (never passed)')
|
|
1327
|
+
// 归档证明:落地前不是 passed,落地 ok 才写 Verified/Lean/
|
|
1328
|
+
const q3 = await callTool('vibe_math_lean_archive', { kind: 'proof', target: 'pAsymProof', content: 'theorem pAsymProof : 1 = 1 := rfl\n' }, rq)
|
|
1329
|
+
assert(q3.ok === true && q3.async && q3.async.state === 'queued' && q3.status === 'attempted', '★★ kind=proof in async档 returns queued + attempted (NOT passed)')
|
|
1330
|
+
assert(!existsSync(join(projQ, 'Verified', 'Lean', 'pAsymProof.lean')), '★★ the archived proof file does NOT exist while the job is still queued')
|
|
1331
|
+
const done3 = await findJob('lean-async-q', q3.async.jobId, ['settled', 'failed'])
|
|
1332
|
+
assert(!!done3 && done3.state === 'settled', 'the proof job settled green (got ' + (done3 && done3.state) + ')')
|
|
1333
|
+
assert(existsSync(join(projQ, 'Verified', 'Lean', 'pAsymProof.lean')), '★★ only after settled(ok) is Verified/Lean/<id>.lean written')
|
|
1334
|
+
assert(((formalOf2('lean-async-q').records || {}).pAsymProof || {}).status === 'passed', '★★ and only then does the object become passed')
|
|
1335
|
+
assert(/pAsymProof/.test(readIf(join(projQ, 'Formal', 'Index.md'))), 'the index reflects the passed object')
|
|
1336
|
+
// ── lean_job(修订 §3):清单/单查/waitMs ────────────────────────────────────────
|
|
1337
|
+
const listJobs = await callTool('vibe_math_lean_job', {}, rq)
|
|
1338
|
+
assert(listJobs.ok === true && Array.isArray(listJobs.jobs) && listJobs.jobs.length >= 3, '★ lean_job without jobId returns the session job list')
|
|
1339
|
+
assert(listJobs.jobs.every((j) => !!j.paths && typeof j.paths.receipt === 'string'), 'every listed job carries its receipt path')
|
|
1340
|
+
const one = await callTool('vibe_math_lean_job', { jobId: q3.async.jobId }, rq)
|
|
1341
|
+
assert(one.ok === true && one.state === 'settled' && one.passed === true && one.paths.archive === 'Verified/Lean/pAsymProof.lean', '★ lean_job {jobId} returns state/exitCode/paths and the passed verdict')
|
|
1342
|
+
assert((await callTool('vibe_math_lean_job', { jobId: 'nope-000000000000' }, rq)).code === 'V3_NOT_FOUND', 'an unknown jobId is refused with a typed code')
|
|
1343
|
+
const q4 = await callTool('vibe_math_lean_run', { file: 'Formal/async-bad.lean', target: 'pAsyncBad' }, rq)
|
|
1344
|
+
const waited = await callTool('vibe_math_lean_job', { jobId: q4.async.jobId, waitMs: 6000 }, rq)
|
|
1345
|
+
assert(waited.ok === true && waited.state !== 'queued' && waited.stillRunning === false && waited.waitedMs > 0, '★★ lean_job waitMs>0 waits for the job to leave queued/running (got ' + waited.state + ')')
|
|
1346
|
+
assert((await callTool('vibe_math_lean_lib', {}, rq)).paths.searchPath === VIBE.replace(/\\/g, '/'), '★ lean_lib exposes paths.searchPath = the VibeMath root')
|
|
1347
|
+
assert(Array.isArray((await callTool('vibe_math_lean_lib', {}, rq)).jobs), '★ lean_lib exposes the job list')
|
|
1348
|
+
|
|
1349
|
+
// ── 超时:leanTimeoutMs 是每个作业的预算,主动 terminate,绝不 passed ─────────────
|
|
1350
|
+
const rt = makeRoot()
|
|
1351
|
+
await callTool('vibe_math_new_project', { name: 'lean-async-timeout' }, rt)
|
|
1352
|
+
await callTool('vibe_math_set_params', { leanAsync: true, leanTimeoutMs: 1000 }, rt)
|
|
1353
|
+
const projT = projRoot('lean-async-timeout')
|
|
1354
|
+
mkdirSync(join(projT, 'Formal'), { recursive: true })
|
|
1355
|
+
writeFileSync(join(projT, 'Formal', 'hang.lean'), '-- HANG\ntheorem t : 1 = 1 := rfl\n', 'utf8')
|
|
1356
|
+
const termBefore = terminated.length
|
|
1357
|
+
const q5 = await callTool('vibe_math_lean_run', { file: 'Formal/hang.lean', target: 'pHang' }, rt)
|
|
1358
|
+
const done5 = await findJob('lean-async-timeout', q5.async.jobId, ['timeout'], 150)
|
|
1359
|
+
assert(!!done5 && done5.timedOut === true, '★★ a hanging job times out (state=timeout) with leanTimeoutMs as its budget')
|
|
1360
|
+
assert(terminated.length > termBefore, '★★ the timeout ACTIVELY called handle.terminate()')
|
|
1361
|
+
assert(((formalOf2('lean-async-timeout').records || {}).pHang || {}).status !== 'passed', '★ a timed-out job never sets passed')
|
|
1362
|
+
|
|
1363
|
+
// ── 并发参数(修订 §5):leanJobsMaxParallel>1 时两个作业同时进入 running ──────────
|
|
1364
|
+
const rc = makeRoot()
|
|
1365
|
+
await callTool('vibe_math_new_project', { name: 'lean-async-par' }, rc)
|
|
1366
|
+
await callTool('vibe_math_set_params', { leanAsync: true, leanJobsMaxParallel: 2, leanTimeoutMs: 2500 }, rc)
|
|
1367
|
+
const projC = projRoot('lean-async-par')
|
|
1368
|
+
mkdirSync(join(projC, 'Formal'), { recursive: true })
|
|
1369
|
+
writeFileSync(join(projC, 'Formal', 'h1.lean'), '-- HANG\ntheorem h1 : 1 = 1 := rfl\n', 'utf8')
|
|
1370
|
+
writeFileSync(join(projC, 'Formal', 'h2.lean'), '-- HANG\ntheorem h2 : 1 = 1 := rfl\n', 'utf8')
|
|
1371
|
+
const j1 = await callTool('vibe_math_lean_run', { file: 'Formal/h1.lean', target: 'pH1' }, rc)
|
|
1372
|
+
const j2 = await callTool('vibe_math_lean_run', { file: 'Formal/h2.lean', target: 'pH2' }, rc)
|
|
1373
|
+
let both = false
|
|
1374
|
+
for (let i = 0; i < 120 && !both; i++) {
|
|
1375
|
+
const a = readJob('lean-async-par', j1.async.jobId)
|
|
1376
|
+
const b = readJob('lean-async-par', j2.async.jobId)
|
|
1377
|
+
both = (a.state === 'running' && b.state === 'running')
|
|
1378
|
+
if (!both) await sleep(50)
|
|
1379
|
+
}
|
|
1380
|
+
assert(both, '★★ with leanJobsMaxParallel=2 BOTH queued jobs are in running at the same time (concurrency is a parameter, not a constant)')
|
|
1381
|
+
for (const k of [j1, j2]) { try { await callTool('vibe_math_lean_job', { jobId: k.async.jobId, waitMs: 3000 }, rc) } catch (e) { /* ignore */ } }
|
|
1382
|
+
|
|
1383
|
+
// ── 去重(§4.3)+ 构建上下文进指纹(修订 §4)─────────────────────────────────────
|
|
1384
|
+
const rd = makeRoot()
|
|
1385
|
+
await callTool('vibe_math_new_project', { name: 'lean-async-dedupe' }, rd)
|
|
1386
|
+
await callTool('vibe_math_set_params', { leanAsync: true, leanCommand: 'lake', leanArgs: ['env', 'lean'] }, rd)
|
|
1387
|
+
const defBody = 'def reusableOne : Nat := 1\n'
|
|
1388
|
+
const r1 = await callTool('vibe_math_lean_archive', { kind: 'def', name: 'reusableOne', content: defBody }, rd)
|
|
1389
|
+
assert(r1.ok === true && r1.async && r1.deduped === false, 'the first archive of a definition is enqueued (not deduped)')
|
|
1390
|
+
await findJob('lean-async-dedupe', r1.async.jobId, ['settled'])
|
|
1391
|
+
const runsAfter1 = leanRuns.length
|
|
1392
|
+
const r2 = await callTool('vibe_math_lean_archive', { kind: 'def', name: 'reusableOne', content: defBody }, rd)
|
|
1393
|
+
assert(r2.ok === true && r2.deduped === true && r2.async === undefined, '★★★ re-archiving identical content is DEDUPED (no job, no recompile)')
|
|
1394
|
+
assert(leanRuns.length === runsAfter1, '★★ the deduped archive did NOT recompile')
|
|
1395
|
+
await callTool('vibe_math_set_params', { leanArgs: ['env', 'lean', '--threads=2'] }, rd)
|
|
1396
|
+
const r5 = await callTool('vibe_math_lean_archive', { kind: 'def', name: 'reusableOne', content: defBody }, rd)
|
|
1397
|
+
assert(r5.ok === true && r5.deduped === false && !!r5.async && !!r1.async && r5.async.jobId !== r1.async.jobId, '★★★ the SAME content under a DIFFERENT build context gets a different jobId (not treated as already-verified)')
|
|
1398
|
+
if (r5.async) await findJob('lean-async-dedupe', r5.async.jobId, ['settled', 'failed'])
|
|
1399
|
+
await callTool('vibe_math_set_params', { leanArgs: ['env', 'lean'] }, rd)
|
|
1400
|
+
const changed = defBody + '\n-- 内容变了\n'
|
|
1401
|
+
const r3 = await callTool('vibe_math_lean_archive', { kind: 'def', name: 'reusableOne', content: changed }, rd)
|
|
1402
|
+
assert(r3.ok === true && r3.deduped === false && !!r3.async, 'a changed body is NOT deduped (content hash differs)')
|
|
1403
|
+
if (r3.async) await findJob('lean-async-dedupe', r3.async.jobId, ['settled', 'failed'])
|
|
1404
|
+
const r6 = await callTool('vibe_math_lean_archive', { kind: 'def', name: 'reusableOne', content: changed }, rd)
|
|
1405
|
+
assert(r6.ok === true && r6.deduped === true, 'and the changed body dedupes on its own (content-based, not "first wins")')
|
|
1406
|
+
|
|
1407
|
+
// ── 崩溃恢复(§2.6 + 修订 §4):绝不置 passed ────────────────────────────────────
|
|
1408
|
+
const rr = makeRoot()
|
|
1409
|
+
await callTool('vibe_math_new_project', { name: 'lean-async-recover' }, rr)
|
|
1410
|
+
await callTool('vibe_math_set_params', { leanAsync: true }, rr)
|
|
1411
|
+
const projR = projRoot('lean-async-recover')
|
|
1412
|
+
mkdirSync(join(projR, 'Formal', 'Jobs'), { recursive: true })
|
|
1413
|
+
const goodBody = 'theorem recGood : 1 = 1 := rfl\n'
|
|
1414
|
+
writeFileSync(join(projR, 'Formal', 'recGood.lean'), goodBody, 'utf8')
|
|
1415
|
+
const ctxG = H.leanBuildContext('lean', [], [slash(VIBE)])
|
|
1416
|
+
const fpG = H.leanJobFingerprint(goodBody, ctxG)
|
|
1417
|
+
const jobIdG = H.leanJobId('pRecGood', fpG)
|
|
1418
|
+
writeFileSync(jobFile('lean-async-recover', jobIdG), JSON.stringify({
|
|
1419
|
+
jobId: jobIdG, kind: 'archive', state: 'running', attempts: 1, target: 'pRecGood', name: 'pRecGood',
|
|
1420
|
+
rel: 'Formal/recGood.lean', scope: 'project', sha256: H.leanContentSha(goodBody), buildSha256: fpG, buildContext: ctxG,
|
|
1421
|
+
engine: 'lean', argv: [], searchPaths: [slash(VIBE)], archiveKind: 'proof', enqueuedAt: 1, startedAt: 2, settledAt: 0, exitCode: null,
|
|
1422
|
+
}), 'utf8')
|
|
1423
|
+
const otherJob = H.leanJobId('pRecOther', H.leanJobFingerprint('theorem other : 1 = 1 := rfl\n', ctxG))
|
|
1424
|
+
writeFileSync(jobFile('lean-async-recover', otherJob), JSON.stringify({
|
|
1425
|
+
jobId: otherJob, kind: 'archive', state: 'running', attempts: 3, target: 'pRecOther', name: 'pRecOther',
|
|
1426
|
+
rel: 'Formal/recMissing.lean', scope: 'project', sha256: H.leanContentSha('theorem other : 1 = 1 := rfl\n'), buildSha256: 'ffffffffffff', buildContext: ctxG,
|
|
1427
|
+
engine: 'lean', argv: [], searchPaths: [slash(VIBE)], archiveKind: 'proof', enqueuedAt: 1, startedAt: 2, settledAt: 0, exitCode: null,
|
|
1428
|
+
}), 'utf8')
|
|
1429
|
+
await callTool('vibe_math_new_project', { name: 'lean-async-recover' }, rr) // setProject ⇒ recoverLeanJobs()
|
|
1430
|
+
const recsR = formalOf2('lean-async-recover').records || {}
|
|
1431
|
+
assert((recsR.pRecGood || {}).status === 'attempted', '★★ recovery marks a crashed running job as attempted (never passed)')
|
|
1432
|
+
assert((recsR.pRecOther || {}).status === 'attempted', 'recovery marks the hash-mismatched job object as attempted too')
|
|
1433
|
+
assert(!existsSync(join(projR, 'Verified', 'Lean', 'pRecGood.lean')), '★ recovery never writes an archived proof for a crashed job')
|
|
1434
|
+
const afterG = readJob('lean-async-recover', jobIdG)
|
|
1435
|
+
assert(Number(afterG.attempts) === 2 && ['queued', 'running', 'settled'].indexOf(afterG.state) !== -1, '★★ the matching-hash job was re-enqueued with attempts+1 (got ' + JSON.stringify({ state: afterG.state, attempts: afterG.attempts }) + ')')
|
|
1436
|
+
const afterO = readJob('lean-async-recover', otherJob)
|
|
1437
|
+
assert(afterO.state === 'interrupted' && Number(afterO.attempts) === 3, '★★ the mismatched job is only marked interrupted (NOT re-driven, attempts unchanged)')
|
|
1438
|
+
assert(!!(await findJob('lean-async-recover', jobIdG, ['settled'])), 'the re-enqueued job then settles normally on the heartbeat')
|
|
1439
|
+
|
|
1440
|
+
// ── lean_read(§1.2):只读、路径守卫、64KB 截断 ─────────────────────────────────
|
|
1441
|
+
mkdirSync(join(VIBE, 'Formal', 'Lib'), { recursive: true })
|
|
1442
|
+
writeFileSync(join(VIBE, 'Formal', 'Lib', 'recGood.lean'), goodBody, 'utf8')
|
|
1443
|
+
const rd1 = await callTool('vibe_math_lean_read', { name: 'recGood' }, rr)
|
|
1444
|
+
assert(rd1.ok === true && rd1.kind === 'lib' && rd1.sha256 === H.leanContentSha(goodBody) && rd1.truncated === false && rd1.text === goodBody, '★ lean_read returns the archived text + sha256 + bytes for a Lib file')
|
|
1445
|
+
assert(rd1.bytes === Buffer.byteLength(goodBody, 'utf8'), 'lean_read reports the byte length (keys=' + Object.keys(rd1).join(',') + ' got ' + JSON.stringify(rd1.bytes) + ' want ' + Buffer.byteLength(goodBody, 'utf8') + ')')
|
|
1446
|
+
assert((await callTool('vibe_math_lean_read', { name: 'recGood', kind: 'proved' }, rr)).code === 'V3_NOT_FOUND', 'kind=proved looks only in Formal/Proved')
|
|
1447
|
+
for (const badName of ['../etc/passwd', 'a/b', 'C:\\x\\y', '..']) {
|
|
1448
|
+
const rbad = await callTool('vibe_math_lean_read', { name: badName }, rr)
|
|
1449
|
+
assert(rbad.ok === false && rbad.code === 'V3_INVALID_ARGUMENT', '★ lean_read rejects the path-escape attempt ' + JSON.stringify(badName))
|
|
1450
|
+
}
|
|
1451
|
+
writeFileSync(join(VIBE, 'Formal', 'Lib', 'bigOne.lean'), '-- ' + 'x'.repeat(70 * 1024) + '\n', 'utf8')
|
|
1452
|
+
const rbig = await callTool('vibe_math_lean_read', { name: 'bigOne' }, rr)
|
|
1453
|
+
assert(rbig.ok === true && rbig.truncated === true && Buffer.byteLength(rbig.text, 'utf8') <= 64 * 1024, '★ lean_read truncates at 64KB and says so (truncated=true)')
|
|
1454
|
+
writeFileSync(join(VIBE, 'Formal', 'Lib', 'depUser.lean'), 'import Formal.Lib.bigOne\n\ndef depUser := 1\n', 'utf8')
|
|
1455
|
+
await callTool('vibe_math_lean_lib', {}, rr)
|
|
1456
|
+
const libIdx = readIf(join(VIBE, 'Formal', 'Lib', 'Index.md'))
|
|
1457
|
+
assert(/Formal\.Lib\.bigOne/.test(libIdx) || /bigOne/.test(libIdx), '★★ the Lib index carries the dependency scanned from import lines (idx=' + String(libIdx).slice(0, 500).replace(/\n/g, ' | ') + ')')
|
|
1458
|
+
|
|
1459
|
+
// ── 主动性档位(修订 §1 + v5 统一):提示词读 leanInitiative,不是 formalVerify ─────────
|
|
1460
|
+
// 第 4 项为 null = "日线完全不出现";第 6 项是该行的 formalVerify 档(默认 encourage)。
|
|
1461
|
+
for (const [mode, rootName, projName, want, notWant, fv] of [
|
|
1462
|
+
['normal', 'lean-init-normal', 'lean-init-normal-proj', /主动性 normal/, null, 'encourage'],
|
|
1463
|
+
['eager', 'lean-init-eager', 'lean-init-eager-proj', /主动档(leanInitiative=eager)/, null, 'encourage'],
|
|
1464
|
+
['off', 'lean-init-off', 'lean-init-off-proj', null, /顺手形式化/, 'encourage'],
|
|
1465
|
+
// formalVerify:'off' + eager:日线仍要注入(主动性是独立轴),验证阶段文本仍关闭。
|
|
1466
|
+
['eager', 'lean-init-off-eager', 'lean-init-off-eager-proj', /主动档(leanInitiative=eager)/, /【Lean 形式化验证(/, 'off'],
|
|
1467
|
+
]) {
|
|
1468
|
+
const rw = makeRoot()
|
|
1469
|
+
await callTool('vibe_math_new_project', { name: projName }, rw)
|
|
1470
|
+
await callTool('vibe_math_set_params', Object.assign({}, VPARAMS, { formalVerify: fv || 'encourage', leanInitiative: mode }), rw)
|
|
1471
|
+
await callTool('vibe_math_add_problem', { id: 'qInit' + mode + '-' + (fv || 'e'), 陈述: '主动性提示词 ' + mode }, rw)
|
|
1472
|
+
await callTool('vibe_math_start', {}, rw)
|
|
1473
|
+
const got = await drive(rw, () => spawnOf(rw, 'explorer:').length >= 1 || spawnOf(rw, 'solver:').length >= 1, 'work prompt for ' + mode, 12000)
|
|
1474
|
+
const text = promptText(rw)
|
|
1475
|
+
const ok4 = want ? want.test(text) : !/顺手形式化/.test(text)
|
|
1476
|
+
assert(got && ok4, '★★ leanInitiative=' + mode + (fv === 'off' ? ' (formalVerify:off)' : '') + ' is reflected in the work prompt' + (want ? '' : ':off ⇒ 连"顺手形式化"日线都不出现'))
|
|
1477
|
+
if (notWant) assert(!notWant.test(text), 'and the ' + mode + (fv === 'off' ? '/off' : '') + ' work prompt does not carry the verification-phase Lean block (验证要求仍由 formalVerify 表达)')
|
|
1478
|
+
if (fv === 'off') assert(text.indexOf('三条筛选判据') !== -1, '★★★ formalVerify:off + eager 仍带完整日线(三条筛选判据在),不是被 off 静默吞掉')
|
|
1479
|
+
await callTool('vibe_math_pause', {}, rw)
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// ── 卸载(spec §2.5):terminate + interrupted,绝不 passed ──────────────────────
|
|
1483
|
+
const rz = makeRoot()
|
|
1484
|
+
await callTool('vibe_math_new_project', { name: 'lean-async-dispose' }, rz)
|
|
1485
|
+
await callTool('vibe_math_set_params', { leanAsync: true, leanTimeoutMs: 60000 }, rz)
|
|
1486
|
+
const projZ = projRoot('lean-async-dispose')
|
|
1487
|
+
mkdirSync(join(projZ, 'Formal'), { recursive: true })
|
|
1488
|
+
writeFileSync(join(projZ, 'Formal', 'orphan.lean'), '-- HANG\ntheorem orphan : 1 = 1 := rfl\n', 'utf8')
|
|
1489
|
+
const termZ = terminated.length
|
|
1490
|
+
const qz = await callTool('vibe_math_lean_run', { file: 'Formal/orphan.lean', target: 'pOrphan' }, rz)
|
|
1491
|
+
const startedZ = await findJob('lean-async-dispose', qz.async.jobId, ['running'])
|
|
1492
|
+
assert(!!startedZ, 'the dispose case got a job into running')
|
|
1493
|
+
for (const off of disposers) { try { off() } catch (e) { /* ignore */ } }
|
|
1494
|
+
assert(terminated.length > termZ, '★★ unmounting the plugin terminated the in-flight compile (no orphan process)')
|
|
1495
|
+
assert(((formalOf2('lean-async-dispose').records || {}).pOrphan || {}).status !== 'passed', '★ dispose never marks the object passed')
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1227
1498
|
// ===============================================================
|
|
1228
1499
|
// 10. interaction corpus (AUDIT-CHECKLIST §2.4) — a HUMAN must be able to re-read
|
|
1229
1500
|
// every prompt the framework emitted, not just the assertions about them.
|
|
1230
1501
|
// ===============================================================
|
|
1502
|
+
// ===============================================================
|
|
1503
|
+
// 5b. round-2 lens-1 RUNTIME: the initiative axis at EVERY v3 site
|
|
1504
|
+
// ===============================================================
|
|
1505
|
+
// Four sites: the three formalDailySection() call sites (explorer / rederive / solver) plus the
|
|
1506
|
+
// INLINE formalWorkLine() site inside methodKeeperPrompt. (1) formalVerify:'off' + leanInitiative:
|
|
1507
|
+
// 'eager' must carry the daily line at that site; (2) leanInitiative:'off' must drop it there while
|
|
1508
|
+
// the verification phase still follows formalVerify. Names carry the site so a single-site
|
|
1509
|
+
// regression names itself.
|
|
1510
|
+
const SITE_DAILY = /三条筛选判据/
|
|
1511
|
+
const SITE_VERIFY = /【Lean 形式化验证(/
|
|
1512
|
+
async function driveSiteV3(rw, opts, qid) {
|
|
1513
|
+
const q = qid
|
|
1514
|
+
await callTool('vibe_math_set_params', Object.assign({}, VPARAMS, { methodKeepEvery: 1, maxExplorerRetries: 5 }, opts), rw)
|
|
1515
|
+
await callTool('vibe_math_add_problem', { id: q, 陈述: '站点级运行时用例' }, rw)
|
|
1516
|
+
await callTool('vibe_math_start', {}, rw)
|
|
1517
|
+
const exs = () => spawnOf(rw, 'explorer:' + q)
|
|
1518
|
+
await drive(rw, () => exs().length >= 1, 'explorer site ' + q)
|
|
1519
|
+
const out = { explorer: (exs()[0] || {}).prompt || '' }
|
|
1520
|
+
// answer explorers with directions until the solver appears (v3's fallback scheduler re-derives)
|
|
1521
|
+
fireEnd(exs()[0].childId, { meta: { kind: 'directions', qid: q, directions: [{ id: 'd1', title: 'D', method: 'm', core_assumption: '', feasibility: 0.8 }] } })
|
|
1522
|
+
for (let guard = 0; guard < 30 && !lastSpawn(rw, 'solver:' + q + ':d1'); guard++) {
|
|
1523
|
+
const pend = exs().filter((s) => !firedChildren.has(s.childId)).slice(-1)[0]
|
|
1524
|
+
if (pend) { firedChildren.add(pend.childId); fireEnd(pend.childId, { meta: { kind: 'directions', qid: q, directions: [{ id: 'd1', title: 'D', method: 'm', core_assumption: '', feasibility: 0.8 }] } }) }
|
|
1525
|
+
await pump(rw); await sleep(80)
|
|
1526
|
+
}
|
|
1527
|
+
const so = lastSpawn(rw, 'solver:' + q + ':d1')
|
|
1528
|
+
out.solver = (so && so.prompt) || ''
|
|
1529
|
+
// dead-end + a pending invention: the direction is exhausted (⇒ re-derive) AND the method keeper fires
|
|
1530
|
+
if (so) fireEnd(so.childId, { status: 'dead-end', survival_probability: 0.2, dead_end_reason: '站点用例:不可行', new_inventions: [{ 类型: '工具', 标题: '站点工具', 内容描述: 'x', 是否已入库: false }] })
|
|
1531
|
+
await drive(rw, () => exs().length >= 2, 'rederive site ' + q)
|
|
1532
|
+
out.rederive = (exs()[1] || {}).prompt || ''
|
|
1533
|
+
out.distinct = !!out.explorer && !!out.rederive && out.explorer !== out.rederive
|
|
1534
|
+
await drive(rw, () => !!lastSpawn(rw, 'method-keeper'), 'method-keeper site ' + q)
|
|
1535
|
+
out.methodKeeper = (lastSpawn(rw, 'method-keeper') || {}).prompt || ''
|
|
1536
|
+
await callTool('vibe_math_abort', {}, rw)
|
|
1537
|
+
return out
|
|
1538
|
+
}
|
|
1539
|
+
{
|
|
1540
|
+
const re = makeRoot()
|
|
1541
|
+
await callTool('vibe_math_new_project', { name: 'site-eager-off' }, re)
|
|
1542
|
+
const A = await driveSiteV3(re, { formalVerify: 'off', leanInitiative: 'eager' }, 'qS')
|
|
1543
|
+
assert(SITE_DAILY.test(A.explorer), '★ [explorer site] formalVerify:off + leanInitiative:eager injects the daily line')
|
|
1544
|
+
assert(SITE_DAILY.test(A.solver), '★ [solver site] formalVerify:off + leanInitiative:eager injects the daily line')
|
|
1545
|
+
assert(SITE_DAILY.test(A.rederive), '★ [rederive site] formalVerify:off + leanInitiative:eager injects the daily line')
|
|
1546
|
+
assert(SITE_DAILY.test(A.methodKeeper), '★ [methodKeeper site] formalVerify:off + leanInitiative:eager injects the daily line (inline site)')
|
|
1547
|
+
assert(A.explorer.indexOf('【Lean 形式化验证(') === -1 && A.solver.indexOf('【Lean 形式化验证(') === -1 && A.rederive.indexOf('【Lean 形式化验证(') === -1 && A.methodKeeper.indexOf('【Lean 形式化验证(') === -1, '★ [all sites] formalVerify:off keeps the verification-phase block closed while eager still injects the daily line')
|
|
1548
|
+
|
|
1549
|
+
const ro = makeRoot()
|
|
1550
|
+
await callTool('vibe_math_new_project', { name: 'site-init-off' }, ro)
|
|
1551
|
+
const B = await driveSiteV3(ro, { formalVerify: 'encourage', leanInitiative: 'off' }, 'qO')
|
|
1552
|
+
assert(!SITE_DAILY.test(B.explorer) && !/【顺手形式化/.test(B.explorer), '★ [explorer site] leanInitiative:off drops the daily line')
|
|
1553
|
+
assert(!SITE_DAILY.test(B.solver) && !/【顺手形式化/.test(B.solver), '★ [solver site] leanInitiative:off drops the daily line')
|
|
1554
|
+
assert(!SITE_DAILY.test(B.rederive) && !/【顺手形式化/.test(B.rederive), '★ [rederive site] leanInitiative:off drops the daily line')
|
|
1555
|
+
assert(!SITE_DAILY.test(B.methodKeeper) && !/【顺手形式化/.test(B.methodKeeper), '★ [methodKeeper site] leanInitiative:off drops the daily line (inline site)')
|
|
1556
|
+
|
|
1557
|
+
// verification phase still follows formalVerify under leanInitiative:'off'
|
|
1558
|
+
const rvv = makeRoot()
|
|
1559
|
+
await callTool('vibe_math_new_project', { name: 'site-init-off-verify' }, rvv)
|
|
1560
|
+
await callTool('vibe_math_set_params', Object.assign({}, VPARAMS, { formalVerify: 'encourage', leanInitiative: 'off' }), rvv)
|
|
1561
|
+
await callTool('vibe_math_add_problem', { id: 'qV', 陈述: '验证段仍在' }, rvv)
|
|
1562
|
+
await callTool('vibe_math_start', {}, rvv)
|
|
1563
|
+
await drive(rvv, () => !!lastSpawn(rvv, 'explorer:qV'), 'explorer qV')
|
|
1564
|
+
const exv = lastSpawn(rvv, 'explorer:qV')
|
|
1565
|
+
assert(!!exv && !SITE_DAILY.test(exv.prompt || ''), '★ [explorer site] 第二个 root:initiative off 时 explorer 仍无日线')
|
|
1566
|
+
if (exv) fireEnd(exv.childId, { meta: { kind: 'directions', qid: 'qV', directions: [{ id: 'd1', title: 'D', method: 'm', core_assumption: '', feasibility: 0.8 }] } })
|
|
1567
|
+
for (let guard = 0; guard < 30 && !lastSpawn(rvv, 'solver:qV:d1'); guard++) {
|
|
1568
|
+
const pend = spawnOf(rvv, 'explorer:qV').filter((s) => !firedChildren.has(s.childId)).slice(-1)[0]
|
|
1569
|
+
if (pend) { firedChildren.add(pend.childId); fireEnd(pend.childId, { meta: { kind: 'directions', qid: 'qV', directions: [{ id: 'd1', title: 'D', method: 'm', core_assumption: '', feasibility: 0.8 }] } }) }
|
|
1570
|
+
await pump(rvv); await sleep(80)
|
|
1571
|
+
}
|
|
1572
|
+
const sovv = lastSpawn(rvv, 'solver:qV:d1')
|
|
1573
|
+
if (sovv) fireEnd(sovv.childId, { status: 'success', solution: 'complete solution', survival_probability: 0.9 })
|
|
1574
|
+
await drive(rvv, () => spawnOf(rvv, 'verifier:').length >= 1, 'verifier qV')
|
|
1575
|
+
const vsp = spawnOf(rvv, 'verifier:')[0]
|
|
1576
|
+
assert(!!vsp && SITE_VERIFY.test(vsp.prompt || ''), '★ leanInitiative:off 时验证段仍按 formalVerify 注入(verifier 提示词含【Lean 形式化验证()')
|
|
1577
|
+
await callTool('vibe_math_abort', {}, rvv)
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1231
1580
|
section('10 the captured prompt corpus is written for human review')
|
|
1232
1581
|
{
|
|
1233
1582
|
mkdirSync(CORPUS_DIR, { recursive: true })
|