dsh-vibe-math 2.7.3 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.en.md +682 -682
- package/README.md +683 -683
- package/docs/AUDIT-CHECKLIST.md +755 -744
- package/docs/COMPAT-AUDIT-ROUND2.md +351 -351
- package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
- package/docs/audit-methodology.md +93 -0
- package/docs/final-paper.md +172 -172
- package/docs/formal-verification.md +613 -588
- package/docs/generate_framework_diagram_v2.py +114 -114
- package/docs/generate_framework_diagram_v3.py +127 -127
- package/docs/math-computation.md +195 -194
- package/docs/release-notes/README.en.md +1 -0
- package/docs/release-notes/README.md +1 -0
- package/docs/release-notes/RELEASE-NOTES-2.0.22.md +112 -112
- package/docs/release-notes/RELEASE-NOTES-2.1.0.md +143 -143
- package/docs/release-notes/RELEASE-NOTES-2.2.0.md +266 -266
- package/docs/release-notes/RELEASE-NOTES-2.2.1.md +43 -43
- package/docs/release-notes/RELEASE-NOTES-2.2.2.md +88 -88
- package/docs/release-notes/RELEASE-NOTES-2.3.0.md +207 -207
- package/docs/release-notes/RELEASE-NOTES-2.3.1.md +134 -134
- package/docs/release-notes/RELEASE-NOTES-2.3.10.md +105 -105
- package/docs/release-notes/RELEASE-NOTES-2.3.11.md +57 -57
- package/docs/release-notes/RELEASE-NOTES-2.3.12.md +80 -80
- package/docs/release-notes/RELEASE-NOTES-2.3.2.md +145 -145
- package/docs/release-notes/RELEASE-NOTES-2.3.3.md +115 -115
- package/docs/release-notes/RELEASE-NOTES-2.3.4.md +69 -69
- package/docs/release-notes/RELEASE-NOTES-2.3.5.md +63 -63
- package/docs/release-notes/RELEASE-NOTES-2.3.6.md +66 -66
- package/docs/release-notes/RELEASE-NOTES-2.3.7.md +59 -59
- package/docs/release-notes/RELEASE-NOTES-2.3.8.md +45 -45
- package/docs/release-notes/RELEASE-NOTES-2.3.9.md +70 -70
- package/docs/release-notes/RELEASE-NOTES-2.8.0.en.md +65 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.0.md +65 -0
- package/docs/release-playbook.md +103 -0
- package/docs/slv-playbook.md +117 -0
- package/docs/test-timing.md +158 -157
- package/docs//346/236/266/346/236/204/345/233/276.md +77 -77
- package/installer.js +792 -792
- package/package.json +13 -3
- package/prompt-corpus-v2/formal-verify-v2.json +2 -2
- package/prompt-corpus-v2/formal-verify-v2.md +4 -0
- package/prompt-corpus-v3/formal-verify-v3.json +7 -7
- package/prompt-corpus-v3/formal-verify-v3.md +14 -0
- package/prompt-corpus-v5/prompt-corpus-v5.json +61 -61
- package/prompt-corpus-v5/prompt-corpus-v5.md +122 -0
- package/scripts/clean-temp.mjs +234 -0
- package/tests/audit-engine-faces.mjs +298 -0
- package/tests/audit-formal-sensitivity.mjs +345 -342
- package/tests/audit-installer-compat.test.mjs +256 -256
- package/tests/audit-math-computation-sensitivity.mjs +1 -1
- package/tests/audit-persona-sensitivity.mjs +323 -323
- package/tests/audit-persona-surface.test.mjs +431 -431
- package/tests/audit-prompt-duplication.mutants.mjs +3 -2
- package/tests/audit-prompt-invariants.mjs +589 -546
- package/tests/audit-registration.mjs +78 -78
- package/tests/audit-spec-traceability.mjs +193 -193
- package/tests/audit-status-report-fields.mjs +172 -172
- package/tests/audit-v5-integrity.mjs +795 -795
- package/tests/audit-v5-sensitivity.mjs +433 -432
- package/tests/e2e-v5-round2.test.mjs +2453 -2426
- package/tests/formal-verify-v2.test.mjs +1806 -1800
- package/tests/formal-verify-v3.test.mjs +1757 -1750
- package/tests/formal-verify-v4.test.mjs +2114 -2106
- package/tests/formal-verify-v5.test.mjs +774 -767
- package/tests/math-computation-discovery.mutants.mjs +3 -3
- package/tests/math-computation-shared.test.mjs +122 -5
- package/tests/math-computation-v2.test.mjs +14 -2
- package/tests/math-computation-v3.test.mjs +14 -2
- package/tests/math-computation-v4.test.mjs +739 -739
- package/tests/prompt-v5-integrity.test.mjs +1291 -1291
- package/tests/run-tests.mjs +393 -332
- package/tests/selfdrive-v5.mjs +560 -560
- package/tests/temp-hygiene.mutants.mjs +80 -0
- package/tests/v2-fix-probes.mutants.mjs +4 -0
- package/tests/v2-fix-probes.test.mjs +1034 -1033
- package/tests/v2v3-interrupt.mutants.mjs +111 -0
- package/tests/v3-fix-probes.mutants.mjs +99 -95
- package/tests/v3-fix-probes.test.mjs +891 -890
- package/tests/v4-final-paper.mutants.mjs +108 -0
- package/tests/v5-institute-fixes.mutants.mjs +218 -190
- package/vibe-math-v2/agent.cordis.yml +376 -376
- package/vibe-math-v2/math-computation.js +17 -15
- package/vibe-math-v2/preset.yml +2 -2
- package/vibe-math-v2/v2/347/232/204/351/242/235/345/244/226/350/246/201/346/261/202.txt +21 -21
- package/vibe-math-v2/vibe-math-v2.js +5078 -5011
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +486 -486
- package/vibe-math-v3/agent.cordis.yml +420 -420
- package/vibe-math-v3/math-computation.js +17 -15
- package/vibe-math-v3/preset.yml +2 -2
- package/vibe-math-v3/vibe-math-v3.js +6215 -6149
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +649 -649
- package/vibe-math-v4/agent.cordis.yml +438 -438
- package/vibe-math-v4/math-computation.js +17 -15
- package/vibe-math-v4/preset.yml +2 -2
- package/vibe-math-v4/vibe-math-v4.js +4490 -4422
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1015 -1015
- package/vibe-math-v5/math-computation.js +17 -15
- package/vibe-math-v5/vibe-math-v5.js +93 -11
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1728
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
|
@@ -405,12 +405,12 @@ export async function probeMathEngines(host, opts) {
|
|
|
405
405
|
if (name === 'cli') continue // cli needs a caller-supplied command, so it is not auto-detected
|
|
406
406
|
const hit = await resolveCandidate(H, d)
|
|
407
407
|
if (!hit) continue
|
|
408
|
-
const v = await probeVersion(H, d, hit)
|
|
408
|
+
const v = await probeVersion(H, d, hit.exe)
|
|
409
409
|
if (!v.ok) {
|
|
410
410
|
if (d.license === 'commercial') continue // installed but not usable: reported by op=run as LICENSE_REQUIRED
|
|
411
411
|
continue
|
|
412
412
|
}
|
|
413
|
-
engines.push(Object.assign({ name: name, version: v.version, path: hit, license: d.license }, verifyFields(name)))
|
|
413
|
+
engines.push(Object.assign({ name: name, version: v.version, path: hit.exe, foundVia: hit.via, license: d.license }, verifyFields(name)))
|
|
414
414
|
}
|
|
415
415
|
const result = { ok: true, engines: engines, available: engines.length > 0 }
|
|
416
416
|
PROBE_CACHE.set(H, result)
|
|
@@ -461,7 +461,9 @@ async function knownInstallCandidates(H, engineName) {
|
|
|
461
461
|
if (Array.isArray(r)) injected = r.filter((x) => typeof x === 'string' && x)
|
|
462
462
|
} catch (e) { injected = null }
|
|
463
463
|
}
|
|
464
|
-
|
|
464
|
+
// A JS empty array is TRUTHY: a host that answers `[]` must fall back to the built-in table,
|
|
465
|
+
// otherwise the whole per-OS stage is silently suppressed (task-2).
|
|
466
|
+
const roots = (Array.isArray(injected) && injected.length) ? injected : mathInstallRoots(engineName, env, win)
|
|
465
467
|
const exts = win ? ['', '.exe', '.cmd'] : ['']
|
|
466
468
|
const out = []
|
|
467
469
|
const listNames = async (dir, kind) => {
|
|
@@ -491,17 +493,17 @@ async function resolveCandidate(H, d) {
|
|
|
491
493
|
for (const c of cands) {
|
|
492
494
|
try {
|
|
493
495
|
const p = await H.resolveExecutable(c)
|
|
494
|
-
if (typeof p === 'string' && p) return p
|
|
496
|
+
if (typeof p === 'string' && p) return { exe: p, via: 'path' }
|
|
495
497
|
} catch (e) { /* not this one */ }
|
|
496
498
|
}
|
|
497
499
|
// round-7 (fix 2): DSH ships its own runtimes; PATH always wins, so this is a LAST resort. The
|
|
498
500
|
// tree name is globbed (never hard-coded) and executable names come from the descriptor candidates.
|
|
499
501
|
for (const p of await dshRuntimeCandidates(H, d.name)) {
|
|
500
|
-
if (typeof p === 'string' && p) return p
|
|
502
|
+
if (typeof p === 'string' && p) return { exe: p, via: 'host-runtime' }
|
|
501
503
|
}
|
|
502
504
|
// round-9: then the known per-OS install locations (an engine installed without touching PATH).
|
|
503
505
|
for (const p of await knownInstallCandidates(H, d.name)) {
|
|
504
|
-
if (typeof p === 'string' && p) return p
|
|
506
|
+
if (typeof p === 'string' && p) return { exe: p, via: 'known-install' }
|
|
505
507
|
}
|
|
506
508
|
return null
|
|
507
509
|
}
|
|
@@ -660,16 +662,16 @@ async function resolveEngine(H, requested, params, args) {
|
|
|
660
662
|
cliProbe = fv.diag || null
|
|
661
663
|
if (fv.ok) cliVersion = fv.version
|
|
662
664
|
}
|
|
663
|
-
return { ok: true, name: name, desc: d, exe: exe, version: cliVersion, family: fam || null, probe: cliProbe }
|
|
665
|
+
return { ok: true, name: name, desc: d, exe: exe, version: cliVersion, family: fam || null, probe: cliProbe, via: 'cli' }
|
|
664
666
|
}
|
|
665
667
|
const hit = await resolveCandidate(H, d)
|
|
666
668
|
if (!hit) continue
|
|
667
|
-
const v = await probeVersionWithRetry(H, d, hit)
|
|
669
|
+
const v = await probeVersionWithRetry(H, d, hit.exe)
|
|
668
670
|
if (!v.ok) {
|
|
669
671
|
if (d.license === 'commercial' || d.versionOptional) {
|
|
670
|
-
const lic = await checkLicence(H, d, hit)
|
|
672
|
+
const lic = await checkLicence(H, d, hit.exe)
|
|
671
673
|
if (!lic.ok) return fail('MATH_ENGINE_LICENSE_REQUIRED', name, name + ' 已安装但许可不可用(仅厂商可激活)', { next: next('vendor', Object.assign({ engine: name, url: d.vendor || '' }, verifyFields(name))) })
|
|
672
|
-
return { ok: true, name: name, desc: d, exe: hit, version: 'unknown' }
|
|
674
|
+
return { ok: true, name: name, desc: d, exe: hit.exe, version: 'unknown', via: hit.via }
|
|
673
675
|
}
|
|
674
676
|
// round-7 (live-session fix): the engine WAS found - so an install guide would be misleading
|
|
675
677
|
// (the user does not need to install anything; the host path is what failed). Report the probe
|
|
@@ -679,9 +681,9 @@ async function resolveEngine(H, requested, params, args) {
|
|
|
679
681
|
next: next('note', { guidance: '引擎已找到但探测失败:请检查宿主 subprocess 通路(冷启动/超时/权限),或用 mathEngineOverride 调整 versionArgv 模板;已装的引擎不需要重装。' }),
|
|
680
682
|
})
|
|
681
683
|
}
|
|
682
|
-
const lic = await checkLicence(H, d, hit)
|
|
684
|
+
const lic = await checkLicence(H, d, hit.exe)
|
|
683
685
|
if (!lic.ok) return fail('MATH_ENGINE_LICENSE_REQUIRED', name, name + ' 已安装但许可不可用(仅厂商可激活)', { next: next('vendor', Object.assign({ engine: name, url: d.vendor || '' }, verifyFields(name))) })
|
|
684
|
-
return { ok: true, name: name, desc: d, exe: hit, version: v.version }
|
|
686
|
+
return { ok: true, name: name, desc: d, exe: hit.exe, version: v.version, via: hit.via }
|
|
685
687
|
}
|
|
686
688
|
const first = requested === 'auto' ? (params.mathEngines[0] || 'python') : requested
|
|
687
689
|
return fail('MATH_ENGINE_NOT_FOUND', first, '本机没有可用的计算引擎(试过:' + names.join(', ') + ')', { next: await userInstallNext(H, first) })
|
|
@@ -967,7 +969,7 @@ async function opProbe(H, args, params) {
|
|
|
967
969
|
if (chosen && chosen.ok && !engines.some((e) => e.name === chosen.name)) {
|
|
968
970
|
engines.push(Object.assign({ name: chosen.name, path: chosen.exe, version: chosen.version, license: (MATH_ENGINES[chosen.name] || {}).license }, verifyFields(chosen.name)))
|
|
969
971
|
}
|
|
970
|
-
const engineInfo = (chosen && chosen.ok) ? Object.assign({ name: chosen.name, path: chosen.exe, version: chosen.version }, verifyFields(chosen.name)) : null
|
|
972
|
+
const engineInfo = (chosen && chosen.ok) ? Object.assign({ name: chosen.name, path: chosen.exe, version: chosen.version, foundVia: chosen.via }, verifyFields(chosen.name)) : null
|
|
971
973
|
// round-9 (F3): the availability line must never be silently partial - name the CONFIGURED engines
|
|
972
974
|
// that were not found (and why), instead of just listing the ones that happened to resolve.
|
|
973
975
|
const foundNames = engines.map((e) => e.name)
|
|
@@ -1000,7 +1002,7 @@ async function opRun(H, args, params) {
|
|
|
1000
1002
|
const det = await resolveEngine(H, args.engine || 'auto', params, args)
|
|
1001
1003
|
if (!det.ok) return det
|
|
1002
1004
|
const desc = applyOverride(det, params, args)
|
|
1003
|
-
const det2 = { ok: true, name: det.name, desc: desc, exe: det.exe, version: det.version }
|
|
1005
|
+
const det2 = { ok: true, name: det.name, desc: desc, exe: det.exe, version: det.version, via: det.via }
|
|
1004
1006
|
// The generic escape hatch is recorded as `cli:<command>` (spec §4.3 / guards §18): the model and
|
|
1005
1007
|
// the receipt must be able to tell WHICH command was run, not just that it was "cli".
|
|
1006
1008
|
const cliCommand = args.cli && typeof args.cli.command === 'string' ? args.cli.command : ''
|
|
@@ -1118,7 +1120,7 @@ async function opRun(H, args, params) {
|
|
|
1118
1120
|
schema: 'vibe-math/math-computation-receipt@1',
|
|
1119
1121
|
runId: runId, preset: H.designator, project: slug(root), op: 'run', mode: mode,
|
|
1120
1122
|
attempt: attempt, attemptDir: dir, baseRunDir: baseDir,
|
|
1121
|
-
engine: { name: engineLabel, path: det.exe, version: det.version, source: det.name === 'cli' ? 'cli' : 'path' },
|
|
1123
|
+
engine: { name: engineLabel, path: det.exe, version: det.version, source: det.name === 'cli' ? 'cli' : 'path', foundVia: det2.via },
|
|
1122
1124
|
script: { path: scriptRel, sha256: scriptHash, bytes: Buffer.byteLength(scriptText, 'utf8') },
|
|
1123
1125
|
scriptPath: scriptRel, scriptHash: scriptHash,
|
|
1124
1126
|
// round-8 (P0/D3): member file tools are session-cwd relative, so the ABSOLUTE locations must be
|
|
@@ -50,6 +50,69 @@
|
|
|
50
50
|
// to a host session log: DSH's session persistence refuses to load a session that carries an unknown
|
|
51
51
|
// event type unless the writer marked it `ignorable: true`, and `Session.append` cannot set that
|
|
52
52
|
// field — so the old projection-based primary made the user's own session unresumable.
|
|
53
|
+
|
|
54
|
+
// ── task-4: known-location fallback (math-engine parity) ────────────────────────────────────────────
|
|
55
|
+
// Resolution order: an EXPLICIT command (leanCommand / paperLatexCommand) is used alone and never
|
|
56
|
+
// guessed; otherwise the host resolver (PATH) is tried first, then the known install locations for this
|
|
57
|
+
// platform. Provenance is returned so the caller can report \`leanFoundVia: 'explicit'|'path'|'known-install'\`,
|
|
58
|
+
// and the probed list travels on failure ("I looked here too"), never just "not found on PATH".
|
|
59
|
+
async function resolveKnownTool(sub, opts) {
|
|
60
|
+
const name = String((opts && opts.name) || '')
|
|
61
|
+
const explicit = String((opts && opts.explicit) || '').trim()
|
|
62
|
+
const kind = String((opts && opts.kind) || 'lean')
|
|
63
|
+
const tried = []
|
|
64
|
+
const env = (typeof process !== 'undefined' && process.env) || {}
|
|
65
|
+
const win = !!(typeof process !== 'undefined' && process.platform === 'win32')
|
|
66
|
+
const home = String(env.USERPROFILE || env.HOME || (win ? 'C:/Users/Default' : '/root'))
|
|
67
|
+
const local = String(env.LOCALAPPDATA || '')
|
|
68
|
+
const pf = String(env.ProgramFiles || 'C:/Program Files')
|
|
69
|
+
const pf86 = String(env['ProgramFiles(x86)'] || 'C:/Program Files (x86)')
|
|
70
|
+
const elan = String(env.ELAN_HOME || '')
|
|
71
|
+
const exts = win ? ['', '.exe', '.cmd', '.bat'] : ['']
|
|
72
|
+
const resolvePath = async (p) => {
|
|
73
|
+
tried.push(p)
|
|
74
|
+
try { const fs = await import('node:fs'); for (const e of exts) { const q = p + e; try { if (fs.existsSync(q) && fs.statSync(q).isFile()) return q } catch (err) { /* keep looking */ } } } catch (err) { /* no fs */ }
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
const listDirs = async (p) => { try { const fs = await import('node:fs'); return fs.readdirSync(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name) } catch (err) { return [] } }
|
|
78
|
+
const knownLean = win
|
|
79
|
+
? [elan ? elan + '/bin' : null, home + '/.elan/bin', local ? local + '/Programs/lean' : null, pf + '/lean/bin']
|
|
80
|
+
: [elan ? elan + '/bin' : null, home + '/.elan/bin', home + '/.local/bin', '/usr/local/bin', '/opt/lean/bin']
|
|
81
|
+
const knownTex = win
|
|
82
|
+
? [pf + '/texlive', pf86 + '/texlive', local ? local + '/Programs/MiKTeX/miktex/bin/x64' : null, pf + '/MiKTeX/miktex/bin/x64']
|
|
83
|
+
: [home + '/Library/TeX/texbin', '/usr/local/texlive', home + '/.TinyTeX/bin', '/opt/texlive']
|
|
84
|
+
const list = kind === 'tex' ? knownTex : knownLean
|
|
85
|
+
// 1) EXPLICIT: only it, never a guess.
|
|
86
|
+
if (explicit) {
|
|
87
|
+
if (typeof sub.resolveExecutable === 'function') { try { const p = await sub.resolveExecutable(explicit); if (p) return { exe: String(p), via: 'explicit', tried, name: explicit } } catch (e) { /* fall through to PATH */ } }
|
|
88
|
+
const p = await resolvePath(explicit)
|
|
89
|
+
if (p) return { exe: p, via: 'explicit', tried, name: explicit }
|
|
90
|
+
return { exe: null, via: null, tried, name: explicit, reason: 'explicit command not resolvable: ' + explicit }
|
|
91
|
+
}
|
|
92
|
+
// 2) PATH via the host resolver.
|
|
93
|
+
if (typeof sub.resolveExecutable === 'function') {
|
|
94
|
+
try { const p = await sub.resolveExecutable(name); if (p) return { exe: String(p), via: 'path', tried, name } } catch (e) { /* PATH miss is expected */ }
|
|
95
|
+
}
|
|
96
|
+
// 3) KNOWN INSTALL LOCATIONS (Windows TeX roots are globbed per release: <root>/<year>/bin/windows).
|
|
97
|
+
for (const base of list) {
|
|
98
|
+
if (!base) continue
|
|
99
|
+
if (kind === 'tex' && !/bin/i.test(base)) {
|
|
100
|
+
for (const year of await listDirs(base)) {
|
|
101
|
+
for (const sub2 of ['bin/windows', 'bin/x86_64-linux', 'bin/universal-darwin', 'bin']) {
|
|
102
|
+
const p = await resolvePath(base + '/' + year + '/' + sub2 + '/' + name)
|
|
103
|
+
if (p) return { exe: p, via: 'known-install', tried, name }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
const p = await resolvePath(base + '/' + name)
|
|
109
|
+
if (p) return { exe: p, via: 'known-install', tried, name }
|
|
110
|
+
}
|
|
111
|
+
return { exe: null, via: null, tried, name, reason: 'not found on PATH or in the known install locations' }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { resolveKnownTool }
|
|
115
|
+
|
|
53
116
|
export const inject = ['subagents', 'agents', 'fs', 'tools', 'commands', 'timer']
|
|
54
117
|
|
|
55
118
|
// ── math_computation (docs/math-computation.md) ──────────────────────────────
|
|
@@ -2367,6 +2430,7 @@ export function apply(ctx) {
|
|
|
2367
2430
|
if (initialTask) { L.push(resume ? '恢复说明:' : '你的初始任务/用途:'); L.push(' ' + initialTask); L.push('') }
|
|
2368
2431
|
if (member.direction && !resume) { L.push('给你的起点方向:' + member.direction); L.push('') }
|
|
2369
2432
|
mathPushLine(L)
|
|
2433
|
+
paperPushLine(L)
|
|
2370
2434
|
L.push('------------')
|
|
2371
2435
|
L.push(stateBlock(member, roundNo))
|
|
2372
2436
|
L.push('------------')
|
|
@@ -2386,6 +2450,7 @@ export function apply(ctx) {
|
|
|
2386
2450
|
}
|
|
2387
2451
|
if (leanDailyOn()) { L.push(''); L.push(formalWorkLine()) }
|
|
2388
2452
|
mathPushLine(L)
|
|
2453
|
+
paperPushLine(L)
|
|
2389
2454
|
L.push('')
|
|
2390
2455
|
L.push('------------')
|
|
2391
2456
|
L.push(stateBlock(member))
|
|
@@ -2403,6 +2468,7 @@ export function apply(ctx) {
|
|
|
2403
2468
|
L.push('如果你确实已无路可走或认为原问题接近解决,请说明你的判断与理由。')
|
|
2404
2469
|
if (leanDailyOn()) { L.push(''); L.push(formalWorkLine()) }
|
|
2405
2470
|
mathPushLine(L)
|
|
2471
|
+
paperPushLine(L)
|
|
2406
2472
|
L.push('')
|
|
2407
2473
|
L.push('------------')
|
|
2408
2474
|
L.push(stateBlock(member))
|
|
@@ -2438,6 +2504,7 @@ export function apply(ctx) {
|
|
|
2438
2504
|
L.push('如果你认为原问题已解决,请填 "vote_solved": true —— 只有当**全体有表决权者**都')
|
|
2439
2505
|
L.push('一致认为是真时,本所才会停下来。')
|
|
2440
2506
|
mathPushLine(L)
|
|
2507
|
+
paperPushLine(L)
|
|
2441
2508
|
L.push('')
|
|
2442
2509
|
L.push('------------')
|
|
2443
2510
|
L.push(stateBlock(member))
|
|
@@ -2483,6 +2550,7 @@ export function apply(ctx) {
|
|
|
2483
2550
|
L.push('**不要为了配合别人而改票,也不要为了让流程往前走而给出你不相信的 1 或 0。**')
|
|
2484
2551
|
L.push('本所宁可留下未定论,也不要一个骗人的结论。')
|
|
2485
2552
|
mathPushLine(L)
|
|
2553
|
+
paperPushLine(L)
|
|
2486
2554
|
L.push('')
|
|
2487
2555
|
L.push('------------')
|
|
2488
2556
|
L.push(stateBlock(member))
|
|
@@ -2673,9 +2741,10 @@ export function apply(ctx) {
|
|
|
2673
2741
|
return { ok: false, code: 'NO_SUBPROCESS', message: 'the host exposes no subprocess service; Lean cannot be executed here', file: rel, ms: 0 }
|
|
2674
2742
|
}
|
|
2675
2743
|
const cap = Math.max(1000, Number(timeoutMs) || Number(params.leanTimeoutMs) || 120000)
|
|
2744
|
+
let leanVia = null
|
|
2676
2745
|
let exe
|
|
2677
2746
|
try {
|
|
2678
|
-
|
|
2747
|
+
{ const _lc = String(params.leanCommand || 'lean').trim(); const _r = await resolveKnownTool(sub, { name: 'lean', explicit: (_lc && _lc !== 'lean') ? _lc : '', kind: 'lean' }); exe = _r.exe; leanVia = _r.via; if (!exe) return { ok: false, code: 'LEAN_NOT_FOUND', message: 'cannot resolve "' + _lc + '": ' + String(_r.reason || 'not found') + ' — 仍可把形式化代码写下来归档,但无法在此宿主上执行', file: rel, ms: now() - started, next: { kind: 'note', tried: _r.tried } } }
|
|
2679
2748
|
} catch (e) {
|
|
2680
2749
|
return { ok: false, code: 'LEAN_NOT_FOUND', message: 'cannot resolve "' + String(params.leanCommand || 'lean') + '": ' + String((e && e.message) || e) + ' — 仍可把形式化代码写下来归档,但无法在此宿主上执行', file: rel, ms: now() - started }
|
|
2681
2750
|
}
|
|
@@ -2740,7 +2809,7 @@ export function apply(ctx) {
|
|
|
2740
2809
|
const ok = exitCode === 0
|
|
2741
2810
|
return {
|
|
2742
2811
|
ok, exitCode, signal: (outcome && outcome.signal) || null, ms,
|
|
2743
|
-
command: argv.join(' '), file: rel,
|
|
2812
|
+
command: argv.join(' '), file: rel, leanFoundVia: leanVia,
|
|
2744
2813
|
stdout: tail(out, 4000), stderr: tail(err, 4000),
|
|
2745
2814
|
timedOut: timedOut || ms >= cap,
|
|
2746
2815
|
code: ok ? undefined : ((timedOut || ms >= cap) ? 'LEAN_TIMEOUT' : 'LEAN_FAILED'),
|
|
@@ -2959,7 +3028,7 @@ export function apply(ctx) {
|
|
|
2959
3028
|
if (sub === undefined || typeof sub.spawn !== 'function') {
|
|
2960
3029
|
return { ok: false, code: 'NO_SUBPROCESS', message: 'the host exposes no subprocess service; Lean cannot be executed here', file: rel }
|
|
2961
3030
|
}
|
|
2962
|
-
try {
|
|
3031
|
+
try { const _lc2 = String(params.leanCommand || 'lean').trim(); const _r2 = await resolveKnownTool(sub, { name: 'lean', explicit: (_lc2 && _lc2 !== 'lean') ? _lc2 : '', kind: 'lean' }); if (!_r2.exe) return { ok: false, code: 'LEAN_NOT_FOUND', message: 'cannot resolve "' + _lc2 + '": ' + String(_r2.reason || 'not found') + ' — 仍可把形式化代码写下来归档,但无法在此宿主上执行', file: rel, next: { kind: 'note', tried: _r2.tried } } } catch (e) {
|
|
2963
3032
|
return { ok: false, code: 'LEAN_NOT_FOUND', message: 'cannot resolve "' + String(params.leanCommand || 'lean') + '": ' + String((e && e.message) || e) + ' — 仍可把形式化代码写下来归档,但无法在此宿主上执行', file: rel }
|
|
2964
3033
|
}
|
|
2965
3034
|
return { ok: true, rel, abs }
|
|
@@ -3462,6 +3531,15 @@ export function apply(ctx) {
|
|
|
3462
3531
|
// is the "off means zero mention" discipline).
|
|
3463
3532
|
function mathPushLine(L) { const b = mathPromptBlock('zh'); if (b) L.push(b) }
|
|
3464
3533
|
|
|
3534
|
+
// task-9 2b: the LaTeX-missing guidance (four bounded steps + three hard boundaries). Kept
|
|
3535
|
+
// BYTE-IDENTICAL with the other presets; per-preset voice may only follow this block.
|
|
3536
|
+
function paperHintBlock(lang) {
|
|
3537
|
+
return lang === 'en'
|
|
3538
|
+
? '\nWhen no LaTeX engine is detected: (1) probe only the documented common TeX roots and PATH (e.g. where xelatex, latexmk --version) - never scan whole drives; (2) once the absolute path is found, write it into paperLatexCommand, re-detect, then continue; (3) if it is still missing, ask the user once (installing TeX requires the user\'s explicit approval); (4) with no answer, degrade exactly as today (deliver paper.tex and paper.md only). Hard boundaries: never auto-install; never write outside the workspace; never treat "not detected" as a failure.'
|
|
3539
|
+
: '\n检测不到 LaTeX 引擎时:① 只在文档化的常见 TeX 根与 PATH 上做有界核查(如 where xelatex、latexmk --version),不要全盘扫描;② 找到绝对路径后写入 paperLatexCommand 并重新检测,再继续;③ 仍找不到就向用户问一次(安装 TeX 需用户明确同意);④ 无应答则照旧降级(只交付 paper.tex 与 paper.md)。硬边界:绝不自动安装;绝不写工作区之外;绝不把"未检测到"当失败。'
|
|
3540
|
+
}
|
|
3541
|
+
function paperPushLine(L) { const b = paperHintBlock('zh'); if (b) L.push(b) }
|
|
3542
|
+
|
|
3465
3543
|
function formalPromptBlock(target) {
|
|
3466
3544
|
if (!formalOn()) return ''
|
|
3467
3545
|
const mode = formalMode()
|
|
@@ -5803,18 +5881,22 @@ export function apply(ctx) {
|
|
|
5803
5881
|
const order = PAPER_ENGINE_ORDER[lang === 'en' ? 'en' : 'zh'].slice()
|
|
5804
5882
|
const forced = String(params.paperLatexCommand || '').trim()
|
|
5805
5883
|
if (!forced) return order
|
|
5806
|
-
|
|
5884
|
+
// task-10: an EXPLICIT paperLatexCommand is used ALONE (docs/final-paper.md §B) - no fall-through.
|
|
5885
|
+
return [forced]
|
|
5807
5886
|
}
|
|
5808
5887
|
async function latexEngines(lang) {
|
|
5809
5888
|
const sub = subprocessOf()
|
|
5810
|
-
|
|
5811
|
-
|
|
5889
|
+
// task-4: only a missing subprocess short-circuits - a host without resolveExecutable is still
|
|
5890
|
+
// served by the known-location stage (resolveKnownTool tolerates that host).
|
|
5891
|
+
if (!sub) {
|
|
5892
|
+
return { engines: [], reason: '宿主没有 subprocess 服务:无法检测或调用 LaTeX', triedPaths: [] }
|
|
5812
5893
|
}
|
|
5813
5894
|
const engines = []
|
|
5895
|
+
const triedPaths = [] // task-9: union of the known-root candidates probed across THIS detection
|
|
5814
5896
|
for (const name of paperEngineCandidates(lang)) {
|
|
5815
|
-
try { const
|
|
5897
|
+
try { const _vr = await resolveKnownTool(sub, { name, explicit: '', kind: 'tex' }); triedPaths.push(..._vr.tried); const exe = _vr.exe; if (exe) engines.push({ name: String(name), exe: String(exe), via: _vr.via }) } catch (e) { /* not installed */ }
|
|
5816
5898
|
}
|
|
5817
|
-
return { engines, reason: engines.length ? '' : ('未检测到 LaTeX 引擎(' + paperEngineCandidates(lang).join('/') + ')') }
|
|
5899
|
+
return { engines, triedPaths, reason: engines.length ? '' : ('未检测到 LaTeX 引擎(' + paperEngineCandidates(lang).join('/') + ')') }
|
|
5818
5900
|
}
|
|
5819
5901
|
// ONE engine invocation. NEVER throws: every failure becomes a readable result (the Lean
|
|
5820
5902
|
// seam's discipline) and a timeout really terminates the process.
|
|
@@ -5852,7 +5934,7 @@ export function apply(ctx) {
|
|
|
5852
5934
|
return { status: 'kept-existing', engine: '', reason: 'Paper/' + id + '/paper.pdf 已存在:不覆盖(docs/final-paper.md §5)', attempts: [] }
|
|
5853
5935
|
}
|
|
5854
5936
|
const det = await latexEngines(lang)
|
|
5855
|
-
if (!det.engines.length) return { status: 'not-detected', engine: '', reason: det.reason, attempts: [] }
|
|
5937
|
+
if (!det.engines.length) return { status: 'not-detected', engine: '', reason: det.reason, triedPaths: det.triedPaths || [], attempts: [] }
|
|
5856
5938
|
const e0 = det.engines[0], e1 = det.engines[1] || e0
|
|
5857
5939
|
const plan = [
|
|
5858
5940
|
{ label: 'full', engine: e0, text: tex, nonstopOnly: false },
|
|
@@ -5986,12 +6068,12 @@ export function apply(ctx) {
|
|
|
5986
6068
|
if (await fileExistsAbs(paperAbs(id, 'paper.pdf'))) files.push('paper.pdf')
|
|
5987
6069
|
const warnings = (p.warnings || []).slice()
|
|
5988
6070
|
if (compile.status === 'failed') warnings.push('LaTeX 编译失败(已保留 paper.tex 与 paper.md,不阻塞定稿):' + (compile.attempts || []).map((a) => a.engine + '/' + a.label + ' exit=' + a.exitCode + (a.message ? '(' + a.message + ')' : '')).join(';'))
|
|
5989
|
-
if (compile.status === 'not-detected') warnings.push('未检测到 LaTeX 引擎(' + (compile.reason || '') + '):只交付 paper.tex 与 paper.md
|
|
6071
|
+
if (compile.status === 'not-detected') warnings.push('未检测到 LaTeX 引擎(' + (compile.reason || '') + '):只交付 paper.tex 与 paper.md。已探测 PATH 与文档化的常见 TeX 根;可用 paperLatexCommand 指定绝对路径。')
|
|
5990
6072
|
// F6: the failed REQUIRED writes are named ONCE (for this finalisation), before the meta is built,
|
|
5991
6073
|
// so the warning lands in `meta.warnings`, in the flow log, in the state and in the return value.
|
|
5992
6074
|
if (writeFailures.length) warnings.push(paperWriteFailureWarning(writeFailures))
|
|
5993
6075
|
const meta = paperMeta(Object.assign({}, p, { finalizedAt, compile: compile.status, engine: compile.engine || '' }), {
|
|
5994
|
-
finalizedAt, warnings, compile: { status: compile.status, engine: compile.engine || '', reason: compile.reason || '', attempts: compile.attempts || [] }, files,
|
|
6076
|
+
finalizedAt, warnings, compile: { status: compile.status, engine: compile.engine || '', reason: compile.reason || '', triedPaths: compile.triedPaths || [], attempts: compile.attempts || [] }, files,
|
|
5995
6077
|
})
|
|
5996
6078
|
if (await writeTextRel(PAPER_FILE(id, 'paper.meta.json'), JSON.stringify(meta, null, 2) + '\n')) files.push('paper.meta.json')
|
|
5997
6079
|
// `meta.warnings` is the SAME array, so a meta write failure still reaches the state, the log and
|