dsh-vibe-math 2.8.3 → 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.
Files changed (50) hide show
  1. package/README.en.md +19 -4
  2. package/README.md +19 -4
  3. package/cordis.patch.yml +8 -8
  4. package/docs/COMPAT-AUDIT-ROUND2.md +27 -0
  5. package/docs/final-paper.md +13 -0
  6. package/docs/formal-verification.md +6 -6
  7. package/docs/release-notes/README.en.md +2 -0
  8. package/docs/release-notes/README.md +2 -0
  9. package/docs/release-notes/RELEASE-NOTES-2.8.4.en.md +48 -0
  10. package/docs/release-notes/RELEASE-NOTES-2.8.4.md +48 -0
  11. package/docs/release-notes/RELEASE-NOTES-2.8.5.en.md +75 -0
  12. package/docs/release-notes/RELEASE-NOTES-2.8.5.md +75 -0
  13. package/package.json +7 -3
  14. package/prompt-corpus-persona/persona-corpus.json +9 -9
  15. package/prompt-corpus-persona/persona-corpus.md +17 -17
  16. package/prompt-corpus-v2/formal-verify-v2.json +109 -109
  17. package/prompt-corpus-v2/formal-verify-v2.md +216 -216
  18. package/prompt-corpus-v3/formal-verify-v3.json +77 -77
  19. package/prompt-corpus-v3/formal-verify-v3.md +147 -147
  20. package/prompt-corpus-v4/formal-verify-v4.json +10 -10
  21. package/prompt-corpus-v4/formal-verify-v4.md +40 -40
  22. package/prompt-corpus-v5/prompt-corpus-v5.json +61 -61
  23. package/prompt-corpus-v5/prompt-corpus-v5.md +613 -186
  24. package/tests/audit-artifact-docs.mjs +38 -2
  25. package/tests/audit-artifact-docs.mutants.mjs +66 -1
  26. package/tests/audit-engine-faces.mjs +2 -2
  27. package/tests/audit-persona-surface.test.mjs +4 -1
  28. package/tests/audit-prompt-invariants.mjs +292 -2
  29. package/tests/e2e-v5-round2.test.mjs +248 -2
  30. package/tests/formal-verify-v2.test.mjs +6 -5
  31. package/tests/formal-verify-v3.test.mjs +6 -5
  32. package/tests/formal-verify-v4.test.mjs +4 -4
  33. package/tests/prompt-v5-integrity.test.mjs +6 -6
  34. package/tests/run-tests.mjs +18 -2
  35. package/tests/v4-final-paper.mutants.mjs +14 -0
  36. package/tests/v4-final-paper.test.mjs +18 -0
  37. package/tests/v5-institute-fixes.mutants.mjs +115 -0
  38. package/vibe-math-v2/agent.cordis.yml +2 -2
  39. package/vibe-math-v2/math-computation.js +5 -5
  40. package/vibe-math-v2/vibe-math-v2.js +66 -17
  41. package/vibe-math-v3/agent.cordis.yml +2 -2
  42. package/vibe-math-v3/math-computation.js +5 -5
  43. package/vibe-math-v3/vibe-math-v3.js +64 -15
  44. package/vibe-math-v4/agent.cordis.yml +6 -6
  45. package/vibe-math-v4/math-computation.js +5 -5
  46. package/vibe-math-v4/vibe-math-v4.js +71 -20
  47. package/vibe-math-v5/agent.cordis.yml +6 -6
  48. package/vibe-math-v5/math-computation.js +5 -5
  49. package/vibe-math-v5/vibe-math-v5.js +385 -34
  50. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +55 -2
@@ -235,6 +235,121 @@ const FAMILIES = [
235
235
  to: 'const restarted = false',
236
236
  expect: /\[real1004-consult\] an office finalisation after a force restart NAMES the reset/,
237
237
  },
238
+ {
239
+ // real1004-stall: the meeting must NOT be abandoned while an asked speaker is still in flight — doing so is
240
+ // what turned ordinary speeches into "late notes" on a real host (1067 of them).
241
+ name: 'real1004-stall: the meeting is abandoned while an asked member is still busy',
242
+ from: 'if (stale >= recoverStallMs() && (!inFlight || stale >= recoverStallMs() * 3)) {',
243
+ to: 'if (stale >= recoverStallMs()) {',
244
+ expect: /\[real1004-stall\] the meeting watchdog exempts ASKED-but-busy speakers/,
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
+ },
238
353
  ]
239
354
  let red = 0
240
355
  for (const f of FAMILIES) { const ok = runFamily(f); if (ok) red++ }
@@ -42,7 +42,7 @@
42
42
 
43
43
  - math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
44
44
  - 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
45
- - 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:'file' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true` 与 `previousReceipt`(指向上一次)。**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
45
+ - 归档→编辑→重跑(**回执=一次 math_computation 调用的 JSON 结果**:先 probe 或 run 一次,它有哪些字段就一目了然):mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:'file' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true`(脚本内容相对上一份回执变过)与 `previousReceipt`(指向上一次)。**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
46
46
 
47
47
  Final paper: strict termination writes Paper/<project>/{paper.md,paper.tex,paper.pdf,paper.meta.json,paper.log.md}
48
48
  automatically — a dedicated paper-writing child assembles the solved material (finalPaper=false disables only that
@@ -148,7 +148,7 @@
148
148
 
149
149
  - math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
150
150
  - 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
151
- - 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:'file' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true` 与 `previousReceipt`(指向上一次)。**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
151
+ - 归档→编辑→重跑(**回执=一次 math_computation 调用的 JSON 结果**:先 probe 或 run 一次,它有哪些字段就一目了然):mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:'file' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true`(脚本内容相对上一份回执变过)与 `previousReceipt`(指向上一次)。**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
152
152
 
153
153
  Final paper: strict termination writes Paper/<project>/{paper.md,paper.tex,paper.pdf,paper.meta.json,paper.log.md}
154
154
  automatically — a dedicated paper-writing child assembles the solved material (finalPaper=false disables only that
@@ -87,13 +87,13 @@ export const MATH_PERSONA_TOOL_LINE = '- math_computation {op: probe|run|receipt
87
87
  // P2a: the archive -> edit -> re-run workflow. Injected into every preset's rule block (and appended
88
88
  // to both persona blocks) so an agent cannot miss that (a) the script is archived, (b) it may edit
89
89
  // it, and (c) re-running is what produces evidence for the edited code.
90
- export const MATH_ARCHIVE_WORKFLOW_LINE = '- 归档→编辑→重跑:mode:\'code\' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);'
90
+ export const MATH_ARCHIVE_WORKFLOW_LINE = '- 归档→编辑→重跑(**回执=一次 math_computation 调用的 JSON 结果**:先 probe 或 run 一次,它有哪些字段就一目了然):mode:\'code\' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);'
91
91
  + '**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。'
92
- + '要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:\'file\' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true` 与 `previousReceipt`(指向上一次)。'
92
+ + '要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:\'file\' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true`(脚本内容相对上一份回执变过)与 `previousReceipt`(指向上一次)。'
93
93
  + '**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。'
94
94
  + '**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。'
95
95
 
96
- export const MATH_ARCHIVE_WORKFLOW_LINE_EN = '- Archive -> edit -> re-run: for mode:\'code\' the script original is at the receipt\'s scriptPath (Computation/<id>/script.<ext>, **relative to the project root**); member file tools resolve paths against the SESSION CWD, so READ it via the ABSOLUTE `receipt.scriptAbs`, or join `receipt.cwd` with `receipt.scriptPath` (both are in the receipt). To produce evidence for EDITED code, edit the SOURCE FILE you originally ran and re-run mode:\'file\' pointing at THAT SAME PATH: the archive id is keyed by the SOURCE PATH, so this lands on the SAME archive as attempt >= 2, with `scriptChanged:true` and `previousReceipt` pointing at the previous attempt. Pointing mode:\'file\' at the ARCHIVED COPY itself (the `receipt.scriptAbs` path) is a DIFFERENT archive BY DESIGN - a new id, attempt 1, no `previousReceipt`, `scriptChanged:false`: the earlier attempt is never overwritten, but it is NOT "a new attempt of the same archive", and the tool says so via `fileIsArchivedScript` and the `ARCHIVED_SCRIPT_RERUN` warning. **An old receipt is NOT evidence for edited code** - cite the receipt whose scriptHash matches the current code; the tool warns explicitly via scriptChanged / scriptChangedDuringRun.'
96
+ export const MATH_ARCHIVE_WORKFLOW_LINE_EN = '- Archive -> edit -> re-run (a RECEIPT is the JSON result of ONE math_computation call - run `probe` or `run` once and the fields below are right there in front of you): for mode:\'code\' the script original is at the receipt\'s scriptPath (Computation/<id>/script.<ext>, **relative to the project root**); member file tools resolve paths against the SESSION CWD, so READ it via the ABSOLUTE `receipt.scriptAbs`, or join `receipt.cwd` with `receipt.scriptPath` (both are in the receipt). To produce evidence for EDITED code, edit the SOURCE FILE you originally ran and re-run mode:\'file\' pointing at THAT SAME PATH: the archive id is keyed by the SOURCE PATH, so this lands on the SAME archive as attempt >= 2, with `scriptChanged:true` (the script content differs from the previous receipt) and `previousReceipt` pointing at the previous attempt. Pointing mode:\'file\' at the ARCHIVED COPY itself (the `receipt.scriptAbs` path) is a DIFFERENT archive BY DESIGN - a new id, attempt 1, no `previousReceipt`, `scriptChanged:false`: the earlier attempt is never overwritten, but it is NOT "a new attempt of the same archive", and the tool says so via `fileIsArchivedScript` and the `ARCHIVED_SCRIPT_RERUN` warning. **An old receipt is NOT evidence for edited code** - cite the receipt whose scriptHash matches the current code; the tool warns explicitly via scriptChanged / scriptChangedDuringRun.'
97
97
 
98
98
  // Round-6 (A): honesty about SUBSTITUTIONS. An alternative that weakens exactness or conclusion
99
99
  // strength must be declared, and the conclusion must never read as if the requested (exact) result
@@ -1468,13 +1468,13 @@ export function mathAvailabilityLine(probe, lang, mathMode) {
1468
1468
  const mode = mathMode || MATH_PARAM_DEFAULTS.mathMode
1469
1469
  const list = (probe && probe.engines ? probe.engines : []).map((e) => e.name + ' ' + (e.version || '?')).join('、')
1470
1470
  if (lang === 'en') {
1471
- const head = '- math_computation: available ' + (list || '(none)') + '. Probe first, then run'
1471
+ const head = '- math_computation: available engines ' + (list || 'none') + '. Probe first, then run'
1472
1472
  + ' (the tool path leaves a re-runnable receipt - cite it). If a package is missing, say so and offer a fallback or an install plan;'
1473
1473
  + ' installing requires a plan plus confirmation.'
1474
1474
  const shell = mode === 'typed+shell' ? '\n' + MATH_SHELL_RULE_LINE_EN : ''
1475
1475
  return head + shell + '\n' + MATH_ARCHIVE_WORKFLOW_LINE_EN + '\n' + MATH_RULE_LINES_EN.join('\n')
1476
1476
  }
1477
- const head = '- math_computation:本机可用 ' + (list || '(无)') + '。需要数值/符号/统计计算时先 probe 再 run'
1477
+ const head = '- math_computation:可用引擎 ' + (list || '无') + '。需要数值/符号/统计计算时先 probe 再 run'
1478
1478
  + '(工具路径会留下可复核回执,结论请引用回执路径);缺包时如实说明并给替代方案或安装计划(不要假装),安装需先出计划并征得确认。'
1479
1479
  const shell = mode === 'typed+shell' ? '\n' + MATH_SHELL_RULE_LINE : ''
1480
1480
  return head + shell + '\n' + MATH_ARCHIVE_WORKFLOW_LINE + '\n' + MATH_RULE_LINES.join('\n')
@@ -48,7 +48,7 @@ const LEAN_INITIATIVE_MODES = ['off', 'normal', 'eager']
48
48
  { name: 'leanAsync', type: 'boolean', description: 'Lean 编译模式:true(默认)= 后台队列,vibe_math_lean_run / vibe_math_lean_archive{run:true} 立即返回 async.jobId 入队,成员不阻塞,结果由下一轮提示的【形式化结果】行与 vibe_math_lean_lib / vibe_math_lean_job 公告(**只有作业落地为 ok 才会置 passed 并写归档证明**);false = 完全同步 await(与旧行为逐字一致)', suggestion: true },
49
49
  { name: 'leanJobsMaxParallel', type: 'integer', description: '后台 Lean 编译的并发上限(默认 1 = 串行,保持可预测的资源占用;调大可并行编译多个作业)', suggestion: 1 },
50
50
  { name: 'leanInitiative', type: 'string', enum: LEAN_INITIATIVE_MODES.slice(), description: '日常流程中的形式化主动性:off(不主动,只在验证提示词按 formalVerify 的要求做)| normal(默认:顺手把有价值且可能复用的东西形式化)| eager(更主动:日常就主动把有价值的小引理/命题/定义形式化)。注意它与 formalVerify(验证时的要求强度:off|encourage|require)是**两件事**', suggestion: 'normal' },
51
- { name: 'leanSearchPaths', type: 'array', items: { type: 'string' }, description: '额外 Lean 搜索路径(默认空数组 = 只用框架自动注入的 VibeMath 根)。非空时按顺序先注入这里给的路径、再注入自动根(去重);若 leanArgs 里已显式给了 --search-path/-R/--root,则完全尊重用户配置、不注入任何东西', suggestion: [] },
51
+ { name: 'leanSearchPaths', type: 'array', items: { type: 'string' }, description: '额外 Lean 搜索路径(默认空数组 = 只用框架自动注入的 VibeMath 根)。非空时按顺序先注入这里给的路径、再注入自动根(去重);若 leanArgs 里已显式给了 -R/--root,则完全尊重用户配置、不注入任何东西', suggestion: [] },
52
52
  { name: 'finalPaper', type: 'boolean', description: '收口(严格终止)时自动撰写最终论文:派遣一名专职「论文撰写」子代理,把已检验通过的命题/解法/成果整理成 Paper/<项目>/{paper.md,paper.tex,paper.meta.json,paper.log.md}。false = 只关自动触发,/vibe paper 手动命令仍可用', suggestion: true },
53
53
  { name: 'paperFormat', type: 'enum', options: ['both', 'md', 'tex'], description: '论文产出格式:both = markdown + latex;md = 只写 paper.md;tex = 只写 paper.tex(tex 才会尝试编译 pdf)', suggestion: 'both' },
54
54
  { name: 'paperLanguage', type: 'enum', options: ['zh', 'en'], description: '论文语言:zh = 中文(LaTeX 用 ctexart,引擎优先 xelatex);en = 英文(article,引擎优先 pdflatex/latexmk)', suggestion: 'zh' },
@@ -125,6 +125,17 @@ function paramProps() {
125
125
  // guessed; otherwise the host resolver (PATH) is tried first, then the known install locations for this
126
126
  // platform. Provenance is returned so the caller can report \`leanFoundVia: 'explicit'|'path'|'known-install'\`,
127
127
  // and the probed list travels on failure ("I looked here too"), never just "not found on PATH".
128
+ // ── task-28: TeX Live's DOCUMENTED typical install positions + a bounded probe budget ────────────────
129
+ // A real-host SLV v5 run (15.2 min, candidate PASS) archived compileStatus `not-detected` on a machine
130
+ // whose TeX Live lives at `D:\texlive\2025\bin\windows\xelatex.exe`: the known list only covered
131
+ // `<ProgramFiles>/texlive`, so an engine that IS installed but NOT on PATH was never found and the run
132
+ // degraded to tex+md only. These are DOCUMENTED roots, never a search: no whole-drive scan, no install,
133
+ // no write outside the workspace (the three hard boundaries stay in the guidance text).
134
+ const TEX_CANDIDATE_CAP = 40 // hard cap on TeX known-root candidate paths probed per detection
135
+ const TEX_YEAR_LOOKBACK = 4 // the newest N year dirs of a TeX Live root (current year + 3)
136
+ const TEX_DRIVE_YEAR_ROOTS = ['D:/texlive', 'C:/texlive'] // Windows: TeX Live at a DRIVE ROOT
137
+ const TEX_SYSTEM_BIN_ROOTS = ['/Library/TeX/texbin'] // macOS: system MacTeX (already a bin dir)
138
+ const TEX_SYSTEM_YEAR_ROOTS = ['/usr/local/texlive', '/opt/texlive'] // Unix: TeX Live (year-globbed)
128
139
  async function resolveKnownTool(sub, opts) {
129
140
  const name = String((opts && opts.name) || '')
130
141
  const explicit = String((opts && opts.explicit) || '').trim()
@@ -144,12 +155,27 @@ async function resolveKnownTool(sub, opts) {
144
155
  return null
145
156
  }
146
157
  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 [] } }
158
+ // task-29: TEST/DIAGNOSTIC sandbox seam (env-gated; UNSET = identity, i.e. exactly today's behaviour).
159
+ // When `V2_TEX_ROOTS_SANDBOX` is set, every known-install TeX candidate is REBASED under that directory
160
+ // (same relative tail) so a suite can pin its "no LaTeX installed here" premise on ANY host: the
161
+ // candidates are still probed ONE BY ONE (triedPaths stays named, TEX_CANDIDATE_CAP still bounds them,
162
+ // still no whole-drive scan) but a real install is never touched. It is not a product switch: it only
163
+ // relocates where the documented roots are looked for, and it is never set by the plugin itself.
164
+ const texSandbox = kind === 'tex' ? String(env.V2_TEX_ROOTS_SANDBOX || '').trim() : ''
165
+ const texProbeBase = (p) => {
166
+ if (!texSandbox) return p
167
+ const tail = String(p).replace(/\\/g, '/').replace(/^[A-Za-z]:/, '').replace(/^\/+/, '')
168
+ return texSandbox.replace(/\\/g, '/').replace(/\/+$/, '') + '/' + tail
169
+ }
147
170
  const knownLean = win
148
171
  ? [elan ? elan + '/bin' : null, home + '/.elan/bin', local ? local + '/Programs/lean' : null, pf + '/lean/bin']
149
172
  : [elan ? elan + '/bin' : null, home + '/.elan/bin', home + '/.local/bin', '/usr/local/bin', '/opt/lean/bin']
173
+ // task-28: the DRIVE-ROOT TeX Live positions come first on Windows (the observed real-host layout);
174
+ // the legacy entries keep their relative order. Unix keeps the legacy order and adds the system
175
+ // MacTeX bin dir last (a documented root, not a guess).
150
176
  const knownTex = win
151
- ? [pf + '/texlive', pf86 + '/texlive', local ? local + '/Programs/MiKTeX/miktex/bin/x64' : null, pf + '/MiKTeX/miktex/bin/x64']
152
- : [home + '/Library/TeX/texbin', '/usr/local/texlive', home + '/.TinyTeX/bin', '/opt/texlive']
177
+ ? TEX_DRIVE_YEAR_ROOTS.concat([pf + '/texlive', pf86 + '/texlive', local ? local + '/Programs/MiKTeX/miktex/bin/x64' : null, pf + '/MiKTeX/miktex/bin/x64'])
178
+ : [home + '/Library/TeX/texbin'].concat(TEX_SYSTEM_YEAR_ROOTS, [home + '/.TinyTeX/bin'], TEX_SYSTEM_BIN_ROOTS)
153
179
  const list = kind === 'tex' ? knownTex : knownLean
154
180
  // 1) EXPLICIT: only it, never a guess.
155
181
  if (explicit) {
@@ -162,19 +188,35 @@ async function resolveKnownTool(sub, opts) {
162
188
  if (typeof sub.resolveExecutable === 'function') {
163
189
  try { const p = await sub.resolveExecutable(name); if (p) return { exe: String(p), via: 'path', tried, name } } catch (e) { /* PATH miss is expected */ }
164
190
  }
165
- // 3) KNOWN INSTALL LOCATIONS (Windows TeX roots are globbed per release: <root>/<year>/bin/windows).
191
+ // 3) KNOWN INSTALL LOCATIONS. A TeX root WITHOUT a `bin` segment is year-globbed
192
+ // (`<root>/<year>/bin/<platform>`): the years actually on disk (newest first) first, then the
193
+ // recent-year window, at most TEX_YEAR_LOOKBACK of them. task-28: the whole stage is BOUNDED by
194
+ // TEX_CANDIDATE_CAP candidate paths — documented roots, never a whole-drive scan.
195
+ const texYearSubdirs = win ? ['bin/windows'] : ['bin/x86_64-linux', 'bin/aarch64-linux', 'bin/universal-darwin', 'bin']
196
+ const texRecentYears = () => { const now = new Date().getFullYear(); const out = []; for (let i = 0; i < TEX_YEAR_LOOKBACK; i++) out.push(String(now - i)); return out }
197
+ let texProbes = 0
166
198
  for (const base of list) {
167
199
  if (!base) continue
168
200
  if (kind === 'tex' && !/bin/i.test(base)) {
169
- for (const year of await listDirs(base)) {
170
- for (const sub2 of ['bin/windows', 'bin/x86_64-linux', 'bin/universal-darwin', 'bin']) {
171
- const p = await resolvePath(base + '/' + year + '/' + sub2 + '/' + name)
201
+ const onDisk = (await listDirs(texProbeBase(base))).filter((d) => /^\d{4}$/.test(d)).sort((a, b) => Number(b) - Number(a))
202
+ const years = onDisk.concat(texRecentYears().filter((y) => onDisk.indexOf(y) < 0)).slice(0, TEX_YEAR_LOOKBACK)
203
+ for (const year of years) {
204
+ for (const sub2 of texYearSubdirs) {
205
+ if (texProbes >= TEX_CANDIDATE_CAP) break
206
+ texProbes++
207
+ const p = await resolvePath(texProbeBase(base) + '/' + year + '/' + sub2 + '/' + name)
172
208
  if (p) return { exe: p, via: 'known-install', tried, name }
173
209
  }
210
+ if (texProbes >= TEX_CANDIDATE_CAP) break
174
211
  }
212
+ if (texProbes >= TEX_CANDIDATE_CAP) break
175
213
  continue
176
214
  }
177
- const p = await resolvePath(base + '/' + name)
215
+ if (kind === 'tex') {
216
+ if (texProbes >= TEX_CANDIDATE_CAP) break
217
+ texProbes++
218
+ }
219
+ const p = await resolvePath(texProbeBase(base) + '/' + name)
178
220
  if (p) return { exe: p, via: 'known-install', tried, name }
179
221
  }
180
222
  return { exe: null, via: null, tried, name, reason: 'not found on PATH or in the known install locations' }
@@ -1105,7 +1147,7 @@ try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd
1105
1147
  }
1106
1148
  /**
1107
1149
  * 已解析路径的**实际执行**(同步档与后台作业共用同一段:argv 注入、超时 terminate、输出截断)。
1108
- * 搜索路径注入(spec §3):`[exe, ...用户 leanArgs, --search-path <VibeMath 根>, <file>]`;
1150
+ * 搜索路径注入(spec §3):`[exe, ...用户 leanArgs, -R <VibeMath 根>, <file>]`;
1109
1151
  * 用户已显式给过 `--search-path`/`-R`/`--root` 就不再注入(显式覆盖优先)。
1110
1152
  */
1111
1153
  async function leanExecFile(rel, abs, timeoutMs, plan, onHandle) {
@@ -3910,7 +3952,7 @@ function verifyTasksView(tasks) {
3910
3952
  PAPER_SKELETON.map(function (s, i) { return ' ' + (i + 1) + '. ' + s.key + ' — ' + s.spec }).join('\n') + '\n' +
3911
3953
  '- A section with no evidence must be exactly 「' + PAPER_NO_EVIDENCE + '」 (do not pad it).\n' +
3912
3954
  '- Markdown subset only: `#`/`##`/`###`, `- ` lists, `**bold**`, `*em*`, `` `code` ``, and inline math as `$...$`. No tables, images, footnotes or raw HTML.\n\n' +
3913
- 'LATEX-MISSING GUIDANCE: When 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.\n\n' +
3955
+ 'LATEX-MISSING GUIDANCE: When 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; the MOST COMMON case is an engine that IS installed but NOT on PATH — a DRIVE-ROOT Windows TeX Live lives under `texlive\\<year>\\bin\\windows` on that drive, Unix under the standard system paths `/usr/local/texlive/<year>/bin/*` or `/opt/texlive/<year>/bin/*`, macOS at `/Library/TeX/texbin`; (2) once the absolute path is found, write it into paperLatexCommand, re-detect, then continue; (3) if it is still missing, REPORT IT TO THE OFFICE (or the group chat) and let the OFFICE confirm with the user (installing TeX requires the user\'s explicit approval); (4) with no answer yet, 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.\n\n' +
3914
3956
  'OUTPUT CONTRACT — respond with ONLY one ```json code fence, no prose:\n' + REPLY_KEY_STYLE_LINE +
3915
3957
  '{"title":"<paper title>","abstract":"<original problem + main results>","sections":[{"name":"<one of the 9 headings>","body":"<markdown>"}, ...]}\n\n' +
3916
3958
  'MATERIAL (evidence only — do not add anything beyond it):\n' + digest
@@ -3975,7 +4017,7 @@ function verifyTasksView(tasks) {
3975
4017
  if (o.format === 'md') return { compile: 'skipped', reason: 'paperFormat=md(不产出 tex,跳过编译)', engine: null, attempts: attempts, pdfPreserved: await paperPathExists(relDir + '/paper.pdf') }
3976
4018
  const det = await paperDetectLatex(o.lang)
3977
4019
  if (det.available.length === 0) {
3978
- await paperAppendLog(id, 'latex', '未检测到任何 LaTeX 引擎(' + det.reason + ')→ 只保留 paper.tex + paper.md,不编译 pdf(spec §5:不阻塞定稿)已探测 PATH 与文档化的常见 TeX 根;可用 paperLatexCommand 指定绝对路径。')
4020
+ await paperAppendLog(id, 'latex', '未检测到任何 LaTeX 引擎(' + det.reason + ')→ 只保留 paper.tex + paper.md,不编译 pdf(spec §5:不阻塞定稿)已探测 PATH 与文档化的常见 TeX 根;可用 paperLatexCommand 指定绝对路径。最常见的情形是引擎已安装但不在 PATH(Windows 盘根下的 texlive\\<年>\\bin\\windows、类 Unix /usr/local|/opt/texlive/<年>/bin/*、macOS /Library/TeX/texbin 已纳入有界探测):把引擎的绝对路径写进 paperLatexCommand 再探测一次;仍找不到就如实上报所办(由所办向用户确认安装),随后照旧安全降级。')
3979
4021
  return { compile: 'not-detected', reason: det.reason, engine: null, attempts: attempts, triedPaths: det.triedPaths || [], pdfPreserved: await paperPathExists(relDir + '/paper.pdf') }
3980
4022
  }
3981
4023
  await paperAppendLog(id, 'latex', '检测到引擎 ' + det.available.map(function (x) { return x.name }).join(', ') + (det.forced ? '(paperLatexCommand 指定)' : ('(顺序 ' + (o.lang === 'en' ? PAPER_ENGINE_ORDER_EN : PAPER_ENGINE_ORDER_ZH).join(' > ') + ')')))
@@ -4401,7 +4443,7 @@ function verifyTasksView(tasks) {
4401
4443
  registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { await refreshParams(); return await getStatus() })
4402
4444
  registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), async function () { await refreshParams(); await maybeWriteReport(true); return await buildReport() })
4403
4445
  registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); await saveSettings(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } })
4404
- registerTool('vibe_math_set_params', 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象的 formal.status 未达到 passed/blocked 之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式(框架会在用户 leanArgs 之后、文件名之前自动追加 `--search-path <VibeMath 根>`,用户已显式给出就不注入);leanAsync = true(默认,后台队列:入队即返回,只有作业落地 ok 才置 passed 并写归档证明)| false(同步 await 的旧语义)。leanJobsMaxParallel = 后台编译并发上限(默认 1 = 串行);leanInitiative = off|normal|eager 控制**日常的**形式化主动性(与 formalVerify 的验证要求强度是两件事:formalVerify=off 只关验证阶段,eager 仍注入日线);leanSearchPaths = 额外 Lean 搜索路径(默认空 = 只用自动注入的 VibeMath 根;非空时先注入它们、再注入自动根)。最终论文:finalPaper(默认 true;收口时自动派遣一名「论文撰写」子代理)/ paperFormat = both|md|tex / paperLanguage = zh|en / paperCompilePdf(检测到 LaTeX 时编译 paper.pdf)/ paperLatexCommand(指定引擎,空 = 自动探测),产物在 Paper/<项目>/。数学计算(工具 math_computation,回执落在 Computation/<id>/):mathComputation = off|auto|on(默认 auto;off 时提示词零提及)· mathMode = typed|typed+shell(默认 typed+shell;typed 时提示词不含 shell 兜底段且 cli 返回 REFUSED{reason:policy})· mathEngines = 允许的引擎列表(默认含 cli,cli 默认开启)· mathTimeoutMs(默认 60000,最小 1000;到时主动 terminate)· mathPackages(需预检的包,缺包只报告+给安装计划)· mathInstallScope = user|system(默认 user;system 只对当次显式调用生效、永不记忆)。', objParams(paramProps()), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); await saveSettings(); if (args && MATH_PARAM_NAMES.some(function (k) { return k in args })) { try { await refreshMathProbe(true) } catch (e) { /* 探测失败不影响参数保存 */ } } return { ok: true, params: params } })
4446
+ registerTool('vibe_math_set_params', 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象的 formal.status 未达到 passed/blocked 之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式(框架会在用户 leanArgs 之后、文件名之前自动追加 `-R <VibeMath 根>`,用户已显式给出就不注入);leanAsync = true(默认,后台队列:入队即返回,只有作业落地 ok 才置 passed 并写归档证明)| false(同步 await 的旧语义)。leanJobsMaxParallel = 后台编译并发上限(默认 1 = 串行);leanInitiative = off|normal|eager 控制**日常的**形式化主动性(与 formalVerify 的验证要求强度是两件事:formalVerify=off 只关验证阶段,eager 仍注入日线);leanSearchPaths = 额外 Lean 搜索路径(默认空 = 只用自动注入的 VibeMath 根;非空时先注入它们、再注入自动根)。最终论文:finalPaper(默认 true;收口时自动派遣一名「论文撰写」子代理)/ paperFormat = both|md|tex / paperLanguage = zh|en / paperCompilePdf(检测到 LaTeX 时编译 paper.pdf)/ paperLatexCommand(指定引擎,空 = 自动探测),产物在 Paper/<项目>/。数学计算(工具 math_computation,回执落在 Computation/<id>/):mathComputation = off|auto|on(默认 auto;off 时提示词零提及)· mathMode = typed|typed+shell(默认 typed+shell;typed 时提示词不含 shell 兜底段且 cli 返回 REFUSED{reason:policy})· mathEngines = 允许的引擎列表(默认含 cli,cli 默认开启)· mathTimeoutMs(默认 60000,最小 1000;到时主动 terminate)· mathPackages(需预检的包,缺包只报告+给安装计划)· mathInstallScope = user|system(默认 user;system 只对当次显式调用生效、永不记忆)。', objParams(paramProps()), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); await saveSettings(); if (args && MATH_PARAM_NAMES.some(function (k) { return k in args })) { try { await refreshMathProbe(true) } catch (e) { /* 探测失败不影响参数保存 */ } } return { ok: true, params: params } })
4405
4447
  registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), async function () { await refreshParams(); const list = PARAM_SCHEMA.map(function (p) { const out = Object.assign({}, p); out.current = params[p.name]; out.default = DEFAULT_PARAMS[p.name]; return out }); return { ok: true, parameters: list, saveTo: frameworkRoot() + '/vibe_math_setting.json' } })
4406
4448
  registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), async function () { return await saveSettings() })
4407
4449
  registerTool('vibe_math_template', 'Create a fresh vibe_math_setting.json template (with defaults + comments) in the workspace (global) or current project folder.', objParams({ where: { type: 'string', enum: ['global', 'project'] } }), async function (args) { return await createTemplate((args && args.where) || 'global') })
@@ -4539,7 +4581,7 @@ function verifyTasksView(tasks) {
4539
4581
  registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), 'vibe_math_status')
4540
4582
  registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), 'vibe_math_report')
4541
4583
  registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), 'vibe_math_set_mode')
4542
- registerTool('vibe_math_set_params', 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象的 formal.status 未达到 passed/blocked 之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式(框架会在用户 leanArgs 之后、文件名之前自动追加 `--search-path <VibeMath 根>`,用户已显式给出就不注入);leanAsync = true(默认,后台队列:入队即返回,只有作业落地 ok 才置 passed 并写归档证明)| false(同步 await 的旧语义)。leanJobsMaxParallel = 后台编译并发上限(默认 1 = 串行);leanInitiative = off|normal|eager 控制**日常的**形式化主动性(与 formalVerify 的验证要求强度是两件事:formalVerify=off 只关验证阶段,eager 仍注入日线);leanSearchPaths = 额外 Lean 搜索路径(默认空 = 只用自动注入的 VibeMath 根;非空时先注入它们、再注入自动根)。最终论文:finalPaper(默认 true;收口时自动派遣一名「论文撰写」子代理)/ paperFormat = both|md|tex / paperLanguage = zh|en / paperCompilePdf(检测到 LaTeX 时编译 paper.pdf)/ paperLatexCommand(指定引擎,空 = 自动探测),产物在 Paper/<项目>/。数学计算(工具 math_computation,回执落在 Computation/<id>/):mathComputation = off|auto|on(默认 auto;off 时提示词零提及)· mathMode = typed|typed+shell(默认 typed+shell;typed 时提示词不含 shell 兜底段且 cli 返回 REFUSED{reason:policy})· mathEngines = 允许的引擎列表(默认含 cli,cli 默认开启)· mathTimeoutMs(默认 60000,最小 1000;到时主动 terminate)· mathPackages(需预检的包,缺包只报告+给安装计划)· mathInstallScope = user|system(默认 user;system 只对当次显式调用生效、永不记忆)。', objParams(paramProps()), 'vibe_math_set_params')
4584
+ registerTool('vibe_math_set_params', 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象的 formal.status 未达到 passed/blocked 之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式(框架会在用户 leanArgs 之后、文件名之前自动追加 `-R <VibeMath 根>`,用户已显式给出就不注入);leanAsync = true(默认,后台队列:入队即返回,只有作业落地 ok 才置 passed 并写归档证明)| false(同步 await 的旧语义)。leanJobsMaxParallel = 后台编译并发上限(默认 1 = 串行);leanInitiative = off|normal|eager 控制**日常的**形式化主动性(与 formalVerify 的验证要求强度是两件事:formalVerify=off 只关验证阶段,eager 仍注入日线);leanSearchPaths = 额外 Lean 搜索路径(默认空 = 只用自动注入的 VibeMath 根;非空时先注入它们、再注入自动根)。最终论文:finalPaper(默认 true;收口时自动派遣一名「论文撰写」子代理)/ paperFormat = both|md|tex / paperLanguage = zh|en / paperCompilePdf(检测到 LaTeX 时编译 paper.pdf)/ paperLatexCommand(指定引擎,空 = 自动探测),产物在 Paper/<项目>/。数学计算(工具 math_computation,回执落在 Computation/<id>/):mathComputation = off|auto|on(默认 auto;off 时提示词零提及)· mathMode = typed|typed+shell(默认 typed+shell;typed 时提示词不含 shell 兜底段且 cli 返回 REFUSED{reason:policy})· mathEngines = 允许的引擎列表(默认含 cli,cli 默认开启)· mathTimeoutMs(默认 60000,最小 1000;到时主动 terminate)· mathPackages(需预检的包,缺包只报告+给安装计划)· mathInstallScope = user|system(默认 user;system 只对当次显式调用生效、永不记忆)。', objParams(paramProps()), 'vibe_math_set_params')
4543
4585
  registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), 'vibe_math_setup')
4544
4586
  registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), 'vibe_math_save_settings')
4545
4587
  registerTool('vibe_math_template', 'Create a fresh vibe_math_setting.json template (with defaults + comments) in the workspace (global) or current project folder.', objParams({ where: { type: 'string', enum: ['global', 'project'] } }), 'vibe_math_template')
@@ -4910,13 +4952,20 @@ function paperTitleFromMd(md) {
4910
4952
  // Lean 增量 + 异步(规格:docs/formal-verification.md;设计:_oneoff/lean-incremental-async-spec.md)
4911
4953
  // **纯函数部分**(module scope,v2/v3 逐字同构)。
4912
4954
  // ============================================================================================
4913
- /** 搜索路径 flag:收敛成一个常量(不同 Lean 版本拼写可能不同,改这里即可,接口不变)。 */
4914
- const LEAN_SEARCH_PATH_FLAG = '--search-path'
4915
- /** 用户已显式给出搜索路径就不再注入(显式覆盖优先)。 */
4955
+ /** 搜索路径 flag:收敛成一个常量(不同 Lean 版本拼写可能不同,改这里即可,接口不变)。
4956
+ * task-27(真机 A 轮 + 本机复现):Lean 4 只认 `-R`/`--root`(或 LEAN_PATH);`--search-path` 是 **Lean 3**
4957
+ * 的拼写,会让 `lean` 在**参数解析阶段**就失败(rc=1),文件根本读不到——默认每次编译都失败。
4958
+ * 本机在 Lean 4.34.0 上复现:`lean --search-path <dir> <f>` ⇒ rc=1;`lean -R <dir> <f>` ⇒ 通过(随后正常读文件)。 */
4959
+ const LEAN_SEARCH_PATH_FLAG = '-R'
4960
+ /** 用户已显式给出搜索路径就不再注入(显式覆盖优先)。
4961
+ * task-27:我们注入的是 `-R`(Lean 4);用户在 leanArgs 里给出旧拼写(Lean 3 的 `--search-path`)或 `--root`
4962
+ * 也算已声明——本守卫的唯一职责是“绝不注入第二个搜索根”。 */
4916
4963
  function hasLeanSearchFlag(args) {
4964
+ const ALSO_ACCEPTED = ['--search-path', '--root']
4917
4965
  return (Array.isArray(args) ? args : []).some(function (a) {
4918
4966
  const s = String(a)
4919
- return s === LEAN_SEARCH_PATH_FLAG || s.indexOf(LEAN_SEARCH_PATH_FLAG + '=') === 0 || s === '-R' || s === '--root'
4967
+ return s === LEAN_SEARCH_PATH_FLAG || s.indexOf(LEAN_SEARCH_PATH_FLAG + '=') === 0 ||
4968
+ ALSO_ACCEPTED.some(function (f) { return s === f || s.indexOf(f + '=') === 0 })
4920
4969
  })
4921
4970
  }
4922
4971
  /**
@@ -70,7 +70,7 @@
70
70
 
71
71
  - math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
72
72
  - 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
73
- - 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:'file' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true` 与 `previousReceipt`(指向上一次)。**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
73
+ - 归档→编辑→重跑(**回执=一次 math_computation 调用的 JSON 结果**:先 probe 或 run 一次,它有哪些字段就一目了然):mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:'file' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true`(脚本内容相对上一份回执变过)与 `previousReceipt`(指向上一次)。**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
74
74
 
75
75
  A /vibe slash command mirrors the main controls (/vibe start [override]|resume [override]|pause|abort|status|report|mode
76
76
  <auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|methods|index|plan|lock|project [list|new <name>|<name>]|decisions|agents|paper [lang=zh|en] [format=both|md|tex] [force]).
@@ -195,7 +195,7 @@
195
195
 
196
196
  - math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
197
197
  - 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
198
- - 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:'file' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true` 与 `previousReceipt`(指向上一次)。**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
198
+ - 归档→编辑→重跑(**回执=一次 math_computation 调用的 JSON 结果**:先 probe 或 run 一次,它有哪些字段就一目了然):mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:'file' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true`(脚本内容相对上一份回执变过)与 `previousReceipt`(指向上一次)。**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
199
199
 
200
200
  A /vibe slash command mirrors the main controls (/vibe start [override]|resume [override]|pause|abort|status|report|mode
201
201
  <auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|methods|index|plan|lock|project [list|new <name>|<name>]|decisions|agents|paper [lang=zh|en] [format=both|md|tex] [force]).
@@ -87,13 +87,13 @@ export const MATH_PERSONA_TOOL_LINE = '- math_computation {op: probe|run|receipt
87
87
  // P2a: the archive -> edit -> re-run workflow. Injected into every preset's rule block (and appended
88
88
  // to both persona blocks) so an agent cannot miss that (a) the script is archived, (b) it may edit
89
89
  // it, and (c) re-running is what produces evidence for the edited code.
90
- export const MATH_ARCHIVE_WORKFLOW_LINE = '- 归档→编辑→重跑:mode:\'code\' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);'
90
+ export const MATH_ARCHIVE_WORKFLOW_LINE = '- 归档→编辑→重跑(**回执=一次 math_computation 调用的 JSON 结果**:先 probe 或 run 一次,它有哪些字段就一目了然):mode:\'code\' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);'
91
91
  + '**成员的文件工具是按会话 cwd 解析的**,所以读它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有)。'
92
- + '要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:\'file\' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true` 与 `previousReceipt`(指向上一次)。'
92
+ + '要拿到"改过代码"的证据,请**编辑你最初运行的那个源文件**,再用 mode:\'file\' 指向**同一个源路径**重跑:归档 id 以**源路径**为键,因此这落在**同一归档**的 **attempt ≥ 2**,并给出 `scriptChanged:true`(脚本内容相对上一份回执变过)与 `previousReceipt`(指向上一次)。'
93
93
  + '**指向归档副本本身**(`receipt.scriptAbs` 那个路径)按设计是**另一份新归档**:新 id、attempt 1、没有 `previousReceipt`、`scriptChanged:false`;旧 attempt 绝不会被覆盖,但它**不是**"同一归档的新 attempt"——工具会用 `fileIsArchivedScript` 与 `ARCHIVED_SCRIPT_RERUN` 警告明确说明。'
94
94
  + '**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。'
95
95
 
96
- export const MATH_ARCHIVE_WORKFLOW_LINE_EN = '- Archive -> edit -> re-run: for mode:\'code\' the script original is at the receipt\'s scriptPath (Computation/<id>/script.<ext>, **relative to the project root**); member file tools resolve paths against the SESSION CWD, so READ it via the ABSOLUTE `receipt.scriptAbs`, or join `receipt.cwd` with `receipt.scriptPath` (both are in the receipt). To produce evidence for EDITED code, edit the SOURCE FILE you originally ran and re-run mode:\'file\' pointing at THAT SAME PATH: the archive id is keyed by the SOURCE PATH, so this lands on the SAME archive as attempt >= 2, with `scriptChanged:true` and `previousReceipt` pointing at the previous attempt. Pointing mode:\'file\' at the ARCHIVED COPY itself (the `receipt.scriptAbs` path) is a DIFFERENT archive BY DESIGN - a new id, attempt 1, no `previousReceipt`, `scriptChanged:false`: the earlier attempt is never overwritten, but it is NOT "a new attempt of the same archive", and the tool says so via `fileIsArchivedScript` and the `ARCHIVED_SCRIPT_RERUN` warning. **An old receipt is NOT evidence for edited code** - cite the receipt whose scriptHash matches the current code; the tool warns explicitly via scriptChanged / scriptChangedDuringRun.'
96
+ export const MATH_ARCHIVE_WORKFLOW_LINE_EN = '- Archive -> edit -> re-run (a RECEIPT is the JSON result of ONE math_computation call - run `probe` or `run` once and the fields below are right there in front of you): for mode:\'code\' the script original is at the receipt\'s scriptPath (Computation/<id>/script.<ext>, **relative to the project root**); member file tools resolve paths against the SESSION CWD, so READ it via the ABSOLUTE `receipt.scriptAbs`, or join `receipt.cwd` with `receipt.scriptPath` (both are in the receipt). To produce evidence for EDITED code, edit the SOURCE FILE you originally ran and re-run mode:\'file\' pointing at THAT SAME PATH: the archive id is keyed by the SOURCE PATH, so this lands on the SAME archive as attempt >= 2, with `scriptChanged:true` (the script content differs from the previous receipt) and `previousReceipt` pointing at the previous attempt. Pointing mode:\'file\' at the ARCHIVED COPY itself (the `receipt.scriptAbs` path) is a DIFFERENT archive BY DESIGN - a new id, attempt 1, no `previousReceipt`, `scriptChanged:false`: the earlier attempt is never overwritten, but it is NOT "a new attempt of the same archive", and the tool says so via `fileIsArchivedScript` and the `ARCHIVED_SCRIPT_RERUN` warning. **An old receipt is NOT evidence for edited code** - cite the receipt whose scriptHash matches the current code; the tool warns explicitly via scriptChanged / scriptChangedDuringRun.'
97
97
 
98
98
  // Round-6 (A): honesty about SUBSTITUTIONS. An alternative that weakens exactness or conclusion
99
99
  // strength must be declared, and the conclusion must never read as if the requested (exact) result
@@ -1468,13 +1468,13 @@ export function mathAvailabilityLine(probe, lang, mathMode) {
1468
1468
  const mode = mathMode || MATH_PARAM_DEFAULTS.mathMode
1469
1469
  const list = (probe && probe.engines ? probe.engines : []).map((e) => e.name + ' ' + (e.version || '?')).join('、')
1470
1470
  if (lang === 'en') {
1471
- const head = '- math_computation: available ' + (list || '(none)') + '. Probe first, then run'
1471
+ const head = '- math_computation: available engines ' + (list || 'none') + '. Probe first, then run'
1472
1472
  + ' (the tool path leaves a re-runnable receipt - cite it). If a package is missing, say so and offer a fallback or an install plan;'
1473
1473
  + ' installing requires a plan plus confirmation.'
1474
1474
  const shell = mode === 'typed+shell' ? '\n' + MATH_SHELL_RULE_LINE_EN : ''
1475
1475
  return head + shell + '\n' + MATH_ARCHIVE_WORKFLOW_LINE_EN + '\n' + MATH_RULE_LINES_EN.join('\n')
1476
1476
  }
1477
- const head = '- math_computation:本机可用 ' + (list || '(无)') + '。需要数值/符号/统计计算时先 probe 再 run'
1477
+ const head = '- math_computation:可用引擎 ' + (list || '无') + '。需要数值/符号/统计计算时先 probe 再 run'
1478
1478
  + '(工具路径会留下可复核回执,结论请引用回执路径);缺包时如实说明并给替代方案或安装计划(不要假装),安装需先出计划并征得确认。'
1479
1479
  const shell = mode === 'typed+shell' ? '\n' + MATH_SHELL_RULE_LINE : ''
1480
1480
  return head + shell + '\n' + MATH_ARCHIVE_WORKFLOW_LINE + '\n' + MATH_RULE_LINES.join('\n')