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
@@ -62,7 +62,7 @@ const LEAN_INITIATIVE_MODES = ['off', 'normal', 'eager']
62
62
  { name: 'leanAsync', type: 'boolean', description: 'Lean 编译模式:true(默认)= 后台队列,vibe_math_lean_run / vibe_math_lean_archive{run:true} 立即返回 async.jobId 入队,成员不阻塞,结果由下一轮提示的【形式化结果】行与 vibe_math_lean_job 公告(**只有作业落地为 ok 才会置 passed 并写归档证明**);false = 完全同步 await(与旧行为逐字一致)', suggestion: true },
63
63
  { name: 'leanJobsMaxParallel', type: 'integer', description: '后台 Lean 编译的并发上限(默认 1 = 串行,保持可预测的资源占用;调大可并行编译多个作业)', suggestion: 1 },
64
64
  { name: 'leanInitiative', type: 'enum', options: ['off', 'normal', 'eager'], description: '日常流程中的形式化主动性:off(不主动,只在验证提示词按 formalVerify 的要求做)| normal(默认:顺手把有价值且可能复用的东西形式化)| eager(更主动:日常就主动把有价值的小引理/命题/定义形式化)。注意它与 formalVerify(验证时的要求强度:off|encourage|require)是**两件事**', suggestion: 'normal' },
65
- { name: 'leanSearchPaths', type: 'string[]', description: '额外 Lean 搜索路径(默认空数组 = 只用框架自动注入的 VibeMath 根)。非空时按顺序先注入这里给的路径、再注入自动根(去重);若 leanArgs 里已显式给了 --search-path/-R/--root,则完全尊重用户配置、不注入任何东西', suggestion: [] },
65
+ { name: 'leanSearchPaths', type: 'string[]', description: '额外 Lean 搜索路径(默认空数组 = 只用框架自动注入的 VibeMath 根)。非空时按顺序先注入这里给的路径、再注入自动根(去重);若 leanArgs 里已显式给了 -R/--root,则完全尊重用户配置、不注入任何东西', suggestion: [] },
66
66
  { name: 'finalPaper', type: 'boolean', description: '收口(checkTermination 的完整收口分支:无未解决问题、无待验证对象、无任务/计划/门)时自动撰写最终论文:派遣一名专职「论文撰写」子代理,把已定论命题/解法/方法整理成 Paper/<项目>/{paper.md,paper.tex,paper.meta.json,paper.log.md}。false = 只关自动触发,/vibe paper 手动命令仍可用', suggestion: true },
67
67
  { name: 'paperFormat', type: 'enum', options: ['both', 'md', 'tex'], description: '论文产出格式:both = markdown + latex;md = 只写 paper.md(跳过编译);tex = 只写 paper.tex(tex 才会尝试编译 pdf)', suggestion: 'both' },
68
68
  { name: 'paperLanguage', type: 'enum', options: ['zh', 'en'], description: '论文语言:zh = 中文(LaTeX 用 ctexart,引擎优先 xelatex);en = 英文(article,引擎优先 pdflatex/latexmk)', suggestion: 'zh' },
@@ -158,6 +158,17 @@ function paramProps() {
158
158
  // guessed; otherwise the host resolver (PATH) is tried first, then the known install locations for this
159
159
  // platform. Provenance is returned so the caller can report \`leanFoundVia: 'explicit'|'path'|'known-install'\`,
160
160
  // and the probed list travels on failure ("I looked here too"), never just "not found on PATH".
161
+ // ── task-28: TeX Live's DOCUMENTED typical install positions + a bounded probe budget ────────────────
162
+ // A real-host SLV v5 run (15.2 min, candidate PASS) archived compileStatus `not-detected` on a machine
163
+ // whose TeX Live lives at `D:\texlive\2025\bin\windows\xelatex.exe`: the known list only covered
164
+ // `<ProgramFiles>/texlive`, so an engine that IS installed but NOT on PATH was never found and the run
165
+ // degraded to tex+md only. These are DOCUMENTED roots, never a search: no whole-drive scan, no install,
166
+ // no write outside the workspace (the three hard boundaries stay in the guidance text).
167
+ const TEX_CANDIDATE_CAP = 40 // hard cap on TeX known-root candidate paths probed per detection
168
+ const TEX_YEAR_LOOKBACK = 4 // the newest N year dirs of a TeX Live root (current year + 3)
169
+ const TEX_DRIVE_YEAR_ROOTS = ['D:/texlive', 'C:/texlive'] // Windows: TeX Live at a DRIVE ROOT
170
+ const TEX_SYSTEM_BIN_ROOTS = ['/Library/TeX/texbin'] // macOS: system MacTeX (already a bin dir)
171
+ const TEX_SYSTEM_YEAR_ROOTS = ['/usr/local/texlive', '/opt/texlive'] // Unix: TeX Live (year-globbed)
161
172
  async function resolveKnownTool(sub, opts) {
162
173
  const name = String((opts && opts.name) || '')
163
174
  const explicit = String((opts && opts.explicit) || '').trim()
@@ -177,12 +188,27 @@ async function resolveKnownTool(sub, opts) {
177
188
  return null
178
189
  }
179
190
  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 [] } }
191
+ // task-29: TEST/DIAGNOSTIC sandbox seam (env-gated; UNSET = identity, i.e. exactly today's behaviour).
192
+ // When `V3_TEX_ROOTS_SANDBOX` is set, every known-install TeX candidate is REBASED under that directory
193
+ // (same relative tail) so a suite can pin its "no LaTeX installed here" premise on ANY host: the
194
+ // candidates are still probed ONE BY ONE (triedPaths stays named, TEX_CANDIDATE_CAP still bounds them,
195
+ // still no whole-drive scan) but a real install is never touched. It is not a product switch: it only
196
+ // relocates where the documented roots are looked for, and it is never set by the plugin itself.
197
+ const texSandbox = kind === 'tex' ? String(env.V3_TEX_ROOTS_SANDBOX || '').trim() : ''
198
+ const texProbeBase = (p) => {
199
+ if (!texSandbox) return p
200
+ const tail = String(p).replace(/\\/g, '/').replace(/^[A-Za-z]:/, '').replace(/^\/+/, '')
201
+ return texSandbox.replace(/\\/g, '/').replace(/\/+$/, '') + '/' + tail
202
+ }
180
203
  const knownLean = win
181
204
  ? [elan ? elan + '/bin' : null, home + '/.elan/bin', local ? local + '/Programs/lean' : null, pf + '/lean/bin']
182
205
  : [elan ? elan + '/bin' : null, home + '/.elan/bin', home + '/.local/bin', '/usr/local/bin', '/opt/lean/bin']
206
+ // task-28: the DRIVE-ROOT TeX Live positions come first on Windows (the observed real-host layout);
207
+ // the legacy entries keep their relative order. Unix keeps the legacy order and adds the system
208
+ // MacTeX bin dir last (a documented root, not a guess).
183
209
  const knownTex = win
184
- ? [pf + '/texlive', pf86 + '/texlive', local ? local + '/Programs/MiKTeX/miktex/bin/x64' : null, pf + '/MiKTeX/miktex/bin/x64']
185
- : [home + '/Library/TeX/texbin', '/usr/local/texlive', home + '/.TinyTeX/bin', '/opt/texlive']
210
+ ? TEX_DRIVE_YEAR_ROOTS.concat([pf + '/texlive', pf86 + '/texlive', local ? local + '/Programs/MiKTeX/miktex/bin/x64' : null, pf + '/MiKTeX/miktex/bin/x64'])
211
+ : [home + '/Library/TeX/texbin'].concat(TEX_SYSTEM_YEAR_ROOTS, [home + '/.TinyTeX/bin'], TEX_SYSTEM_BIN_ROOTS)
186
212
  const list = kind === 'tex' ? knownTex : knownLean
187
213
  // 1) EXPLICIT: only it, never a guess.
188
214
  if (explicit) {
@@ -195,19 +221,35 @@ async function resolveKnownTool(sub, opts) {
195
221
  if (typeof sub.resolveExecutable === 'function') {
196
222
  try { const p = await sub.resolveExecutable(name); if (p) return { exe: String(p), via: 'path', tried, name } } catch (e) { /* PATH miss is expected */ }
197
223
  }
198
- // 3) KNOWN INSTALL LOCATIONS (Windows TeX roots are globbed per release: <root>/<year>/bin/windows).
224
+ // 3) KNOWN INSTALL LOCATIONS. A TeX root WITHOUT a `bin` segment is year-globbed
225
+ // (`<root>/<year>/bin/<platform>`): the years actually on disk (newest first) first, then the
226
+ // recent-year window, at most TEX_YEAR_LOOKBACK of them. task-28: the whole stage is BOUNDED by
227
+ // TEX_CANDIDATE_CAP candidate paths — documented roots, never a whole-drive scan.
228
+ const texYearSubdirs = win ? ['bin/windows'] : ['bin/x86_64-linux', 'bin/aarch64-linux', 'bin/universal-darwin', 'bin']
229
+ 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 }
230
+ let texProbes = 0
199
231
  for (const base of list) {
200
232
  if (!base) continue
201
233
  if (kind === 'tex' && !/bin/i.test(base)) {
202
- for (const year of await listDirs(base)) {
203
- for (const sub2 of ['bin/windows', 'bin/x86_64-linux', 'bin/universal-darwin', 'bin']) {
204
- const p = await resolvePath(base + '/' + year + '/' + sub2 + '/' + name)
234
+ const onDisk = (await listDirs(texProbeBase(base))).filter((d) => /^\d{4}$/.test(d)).sort((a, b) => Number(b) - Number(a))
235
+ const years = onDisk.concat(texRecentYears().filter((y) => onDisk.indexOf(y) < 0)).slice(0, TEX_YEAR_LOOKBACK)
236
+ for (const year of years) {
237
+ for (const sub2 of texYearSubdirs) {
238
+ if (texProbes >= TEX_CANDIDATE_CAP) break
239
+ texProbes++
240
+ const p = await resolvePath(texProbeBase(base) + '/' + year + '/' + sub2 + '/' + name)
205
241
  if (p) return { exe: p, via: 'known-install', tried, name }
206
242
  }
243
+ if (texProbes >= TEX_CANDIDATE_CAP) break
207
244
  }
245
+ if (texProbes >= TEX_CANDIDATE_CAP) break
208
246
  continue
209
247
  }
210
- const p = await resolvePath(base + '/' + name)
248
+ if (kind === 'tex') {
249
+ if (texProbes >= TEX_CANDIDATE_CAP) break
250
+ texProbes++
251
+ }
252
+ const p = await resolvePath(texProbeBase(base) + '/' + name)
211
253
  if (p) return { exe: p, via: 'known-install', tried, name }
212
254
  }
213
255
  return { exe: null, via: null, tried, name, reason: 'not found on PATH or in the known install locations' }
@@ -4514,7 +4556,7 @@ try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd
4514
4556
  PAPER_SKELETON.map(function (s, i) { return ' ' + (i + 1) + '. ' + s.key + ' — ' + s.spec }).join('\n') + '\n' +
4515
4557
  '- A section with no evidence must be exactly 「' + PAPER_NO_EVIDENCE + '」 (do not pad it).\n' +
4516
4558
  '- Markdown subset only: `#`/`##`/`###`, `- ` lists, `**bold**`, `*em*`, `` `code` ``, and inline math as `$...$`. No tables, images, footnotes or raw HTML.\n\n' +
4517
- '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' +
4559
+ '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' +
4518
4560
  'OUTPUT CONTRACT — respond with ONLY one ```json code fence, no prose:\n' +
4519
4561
  '{"title":"<paper title>","abstract":"<original problem + main results>","sections":[{"name":"<one of the 9 headings>","body":"<markdown>"}, ...]}\n\n' +
4520
4562
  'MATERIAL (evidence only — do not add anything beyond it):\n' + digest
@@ -4573,7 +4615,7 @@ try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd
4573
4615
  if (o.format === 'md') return { compile: 'skipped', reason: 'paperFormat=md(不产出 tex,跳过编译)', engine: null, attempts: attempts, pdfPreserved: await paperPathExists(relDir + '/paper.pdf') }
4574
4616
  const det = await paperDetectLatex(o.lang)
4575
4617
  if (det.available.length === 0) {
4576
- await paperAppendLog(id, 'latex', '未检测到任何 LaTeX 引擎(' + det.reason + ')→ 只保留 paper.tex + paper.md,不编译 pdf(spec §5:不阻塞定稿)已探测 PATH 与文档化的常见 TeX 根;可用 paperLatexCommand 指定绝对路径。')
4618
+ 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 再探测一次;仍找不到就如实上报所办(由所办向用户确认安装),随后照旧安全降级。')
4577
4619
  return { compile: 'not-detected', reason: det.reason, engine: null, attempts: attempts, triedPaths: det.triedPaths || [], pdfPreserved: await paperPathExists(relDir + '/paper.pdf') }
4578
4620
  }
4579
4621
  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(' > ') + ')')))
@@ -5816,13 +5858,20 @@ function paperTitleFromMd(md) {
5816
5858
  return m ? m[1].trim() : ''
5817
5859
  }
5818
5860
 
5819
- /** 搜索路径 flag:收敛成一个常量(不同 Lean 版本拼写可能不同,改这里即可,接口不变)。 */
5820
- const LEAN_SEARCH_PATH_FLAG = '--search-path'
5821
- /** 用户已显式给出搜索路径就不再注入(显式覆盖优先)。 */
5861
+ /** 搜索路径 flag:收敛成一个常量(不同 Lean 版本拼写可能不同,改这里即可,接口不变)。
5862
+ * task-27(真机 A 轮 + 本机复现):Lean 4 只认 `-R`/`--root`(或 LEAN_PATH);`--search-path` 是 **Lean 3**
5863
+ * 的拼写,会让 `lean` 在**参数解析阶段**就失败(rc=1),文件根本读不到——默认每次编译都失败。
5864
+ * 本机在 Lean 4.34.0 上复现:`lean --search-path <dir> <f>` ⇒ rc=1;`lean -R <dir> <f>` ⇒ 通过(随后正常读文件)。 */
5865
+ const LEAN_SEARCH_PATH_FLAG = '-R'
5866
+ /** 用户已显式给出搜索路径就不再注入(显式覆盖优先)。
5867
+ * task-27:我们注入的是 `-R`(Lean 4);用户在 leanArgs 里给出旧拼写(Lean 3 的 `--search-path`)或 `--root`
5868
+ * 也算已声明——本守卫的唯一职责是“绝不注入第二个搜索根”。 */
5822
5869
  function hasLeanSearchFlag(args) {
5870
+ const ALSO_ACCEPTED = ['--search-path', '--root']
5823
5871
  return (Array.isArray(args) ? args : []).some(function (a) {
5824
5872
  const s = String(a)
5825
- return s === LEAN_SEARCH_PATH_FLAG || s.indexOf(LEAN_SEARCH_PATH_FLAG + '=') === 0 || s === '-R' || s === '--root'
5873
+ return s === LEAN_SEARCH_PATH_FLAG || s.indexOf(LEAN_SEARCH_PATH_FLAG + '=') === 0 ||
5874
+ ALSO_ACCEPTED.some(function (f) { return s === f || s.indexOf(f + '=') === 0 })
5826
5875
  })
5827
5876
  }
5828
5877
  /**
@@ -5954,7 +6003,7 @@ const TOOL_DESC = {
5954
6003
  vibe_math_set_mode: 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.',
5955
6004
  // set_params 的 schema 现在也收 mode(M10):同一能力既有专用工具 vibe_math_set_mode,也可用这个键;
5956
6005
  // 两条注册路径共用这一份描述,`--self-probe` 会证明漂移能被抓到。
5957
- vibe_math_set_params: 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象未达到 Lean 已通过或已记录显式阻塞原因之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式(框架会在用户 leanArgs 之后、文件名之前自动追加 --search-path <VibeMath 根>,用户已显式给出就不注入;leanSearchPaths 可附加额外搜索路径,先注入它们再注入自动根);leanAsync = true(默认,后台队列:入队即返回,只有作业落地 ok 才置 passed 并写归档证明)| false(同步 await 的旧语义);leanJobsMaxParallel = 后台编译并发上限(默认 1 = 串行);leanInitiative = off|normal|eager 控制**日常的**形式化主动性(与 formalVerify 的验证要求强度是两件事)。最终论文:finalPaper(默认 true;收口时自动派遣一名「论文撰写」子代理)/ paperFormat = both|md|tex / paperLanguage = zh|en / paperCompilePdf(检测到 LaTeX 时编译 paper.pdf)/ paperLatexCommand(指定引擎,空 = 自动探测 xelatex→latexmk→pdflatex→lualatex→tectonic),产物在 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 只对当次显式调用生效、永不记忆)。',
6006
+ vibe_math_set_params: 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象未达到 Lean 已通过或已记录显式阻塞原因之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式(框架会在用户 leanArgs 之后、文件名之前自动追加 -R <VibeMath 根>,用户已显式给出就不注入;leanSearchPaths 可附加额外搜索路径,先注入它们再注入自动根);leanAsync = true(默认,后台队列:入队即返回,只有作业落地 ok 才置 passed 并写归档证明)| false(同步 await 的旧语义);leanJobsMaxParallel = 后台编译并发上限(默认 1 = 串行);leanInitiative = off|normal|eager 控制**日常的**形式化主动性(与 formalVerify 的验证要求强度是两件事)。最终论文:finalPaper(默认 true;收口时自动派遣一名「论文撰写」子代理)/ paperFormat = both|md|tex / paperLanguage = zh|en / paperCompilePdf(检测到 LaTeX 时编译 paper.pdf)/ paperLatexCommand(指定引擎,空 = 自动探测 xelatex→latexmk→pdflatex→lualatex→tectonic),产物在 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 只对当次显式调用生效、永不记忆)。',
5958
6007
  vibe_math_setup: 'Return the interactive parameter schema for guided configuration.',
5959
6008
  vibe_math_save_settings: 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.',
5960
6009
  vibe_math_template: 'Create a fresh vibe_math_setting.json template (with defaults + comments) in the workspace (global) or current project folder.',
@@ -73,8 +73,8 @@
73
73
  too; they work in every mode.
74
74
  - vibe_v4_formal_report — human-readable Lean formal-verification mirror (mode, Lean-passed
75
75
  - math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
76
- - 归档→编辑→重跑: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 时显式告警。
77
- - 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.
76
+ - 归档→编辑→重跑(**回执=一次 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 时显式告警。
77
+ - 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.
78
78
  - 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
79
79
  - Declare substitutions (honesty): when an alternative changes EXACTNESS or conclusion strength (exact symbolic solution -> numerical approximation, closed form -> sampling/quadrature, changed precision/tolerances/assumptions, a different algorithm class), the conclusion MUST say so explicitly and must not read as if the original (exact/requested) result had been obtained; if the exact result is unavailable, say so plainly.
80
80
  objects, recorded blockers, formalization TODO, library paths).
@@ -119,7 +119,7 @@
119
119
  `<project root>/Formal/<id>.lean`.
120
120
  - Reuse FIRST: call vibe_v4_lean_lib (and vibe_v4_lean_read for the verbatim text) BEFORE writing a new
121
121
  definition; reuse an archived file with `import Formal.Lib.<name>` / `import Formal.Proved.<name>`
122
- (the framework passes --search-path <VibeMath root>, so VibeMath is the module root).
122
+ (the framework passes -R <VibeMath root>, so VibeMath is the module root).
123
123
  - Choose WHAT to formalize with three criteria: (1) valuable or likely reusable, (2) rather key or
124
124
  necessary, (3) you are confident in the statement — when you are NOT confident, record blocked
125
125
  with the difficulty instead of hiding uncertainty behind a formalization.
@@ -203,8 +203,8 @@
203
203
  too; they work in every mode.
204
204
  - vibe_v4_formal_report — human-readable Lean formal-verification mirror (mode, Lean-passed
205
205
  - math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
206
- - 归档→编辑→重跑: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 时显式告警。
207
- - 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.
206
+ - 归档→编辑→重跑(**回执=一次 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 时显式告警。
207
+ - 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.
208
208
  - 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
209
209
  - Declare substitutions (honesty): when an alternative changes EXACTNESS or conclusion strength (exact symbolic solution -> numerical approximation, closed form -> sampling/quadrature, changed precision/tolerances/assumptions, a different algorithm class), the conclusion MUST say so explicitly and must not read as if the original (exact/requested) result had been obtained; if the exact result is unavailable, say so plainly.
210
210
  objects, recorded blockers, formalization TODO, library paths).
@@ -249,7 +249,7 @@
249
249
  `<project root>/Formal/<id>.lean`.
250
250
  - Reuse FIRST: call vibe_v4_lean_lib (and vibe_v4_lean_read for the verbatim text) BEFORE writing a new
251
251
  definition; reuse an archived file with `import Formal.Lib.<name>` / `import Formal.Proved.<name>`
252
- (the framework passes --search-path <VibeMath root>, so VibeMath is the module root).
252
+ (the framework passes -R <VibeMath root>, so VibeMath is the module root).
253
253
  - Choose WHAT to formalize with three criteria: (1) valuable or likely reusable, (2) rather key or
254
254
  necessary, (3) you are confident in the statement — when you are NOT confident, record blocked
255
255
  with the difficulty instead of hiding uncertainty behind a formalization.
@@ -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')
@@ -46,6 +46,17 @@ import {
46
46
  // guessed; otherwise the host resolver (PATH) is tried first, then the known install locations for this
47
47
  // platform. Provenance is returned so the caller can report \`leanFoundVia: 'explicit'|'path'|'known-install'\`,
48
48
  // and the probed list travels on failure ("I looked here too"), never just "not found on PATH".
49
+ // ── task-28: TeX Live's DOCUMENTED typical install positions + a bounded probe budget ────────────────
50
+ // A real-host SLV v5 run (15.2 min, candidate PASS) archived compileStatus `not-detected` on a machine
51
+ // whose TeX Live lives at `D:\texlive\2025\bin\windows\xelatex.exe`: the known list only covered
52
+ // `<ProgramFiles>/texlive`, so an engine that IS installed but NOT on PATH was never found and the run
53
+ // degraded to tex+md only. These are DOCUMENTED roots, never a search: no whole-drive scan, no install,
54
+ // no write outside the workspace (the three hard boundaries stay in the guidance text).
55
+ const TEX_CANDIDATE_CAP = 40 // hard cap on TeX known-root candidate paths probed per detection
56
+ const TEX_YEAR_LOOKBACK = 4 // the newest N year dirs of a TeX Live root (current year + 3)
57
+ const TEX_DRIVE_YEAR_ROOTS = ['D:/texlive', 'C:/texlive'] // Windows: TeX Live at a DRIVE ROOT
58
+ const TEX_SYSTEM_BIN_ROOTS = ['/Library/TeX/texbin'] // macOS: system MacTeX (already a bin dir)
59
+ const TEX_SYSTEM_YEAR_ROOTS = ['/usr/local/texlive', '/opt/texlive'] // Unix: TeX Live (year-globbed)
49
60
  async function resolveKnownTool(sub, opts) {
50
61
  const name = String((opts && opts.name) || '')
51
62
  const explicit = String((opts && opts.explicit) || '').trim()
@@ -65,12 +76,27 @@ async function resolveKnownTool(sub, opts) {
65
76
  return null
66
77
  }
67
78
  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 [] } }
79
+ // task-29: TEST/DIAGNOSTIC sandbox seam (env-gated; UNSET = identity, i.e. exactly today's behaviour).
80
+ // When `V4_TEX_ROOTS_SANDBOX` is set, every known-install TeX candidate is REBASED under that directory
81
+ // (same relative tail) so a suite can pin its "no LaTeX installed here" premise on ANY host: the
82
+ // candidates are still probed ONE BY ONE (triedPaths stays named, TEX_CANDIDATE_CAP still bounds them,
83
+ // still no whole-drive scan) but a real install is never touched. It is not a product switch: it only
84
+ // relocates where the documented roots are looked for, and it is never set by the plugin itself.
85
+ const texSandbox = kind === 'tex' ? String(env.V4_TEX_ROOTS_SANDBOX || '').trim() : ''
86
+ const texProbeBase = (p) => {
87
+ if (!texSandbox) return p
88
+ const tail = String(p).replace(/\\/g, '/').replace(/^[A-Za-z]:/, '').replace(/^\/+/, '')
89
+ return texSandbox.replace(/\\/g, '/').replace(/\/+$/, '') + '/' + tail
90
+ }
68
91
  const knownLean = win
69
92
  ? [elan ? elan + '/bin' : null, home + '/.elan/bin', local ? local + '/Programs/lean' : null, pf + '/lean/bin']
70
93
  : [elan ? elan + '/bin' : null, home + '/.elan/bin', home + '/.local/bin', '/usr/local/bin', '/opt/lean/bin']
94
+ // task-28: the DRIVE-ROOT TeX Live positions come first on Windows (the observed real-host layout);
95
+ // the legacy entries keep their relative order. Unix keeps the legacy order and adds the system
96
+ // MacTeX bin dir last (a documented root, not a guess).
71
97
  const knownTex = win
72
- ? [pf + '/texlive', pf86 + '/texlive', local ? local + '/Programs/MiKTeX/miktex/bin/x64' : null, pf + '/MiKTeX/miktex/bin/x64']
73
- : [home + '/Library/TeX/texbin', '/usr/local/texlive', home + '/.TinyTeX/bin', '/opt/texlive']
98
+ ? TEX_DRIVE_YEAR_ROOTS.concat([pf + '/texlive', pf86 + '/texlive', local ? local + '/Programs/MiKTeX/miktex/bin/x64' : null, pf + '/MiKTeX/miktex/bin/x64'])
99
+ : [home + '/Library/TeX/texbin'].concat(TEX_SYSTEM_YEAR_ROOTS, [home + '/.TinyTeX/bin'], TEX_SYSTEM_BIN_ROOTS)
74
100
  const list = kind === 'tex' ? knownTex : knownLean
75
101
  // 1) EXPLICIT: only it, never a guess.
76
102
  if (explicit) {
@@ -83,19 +109,35 @@ async function resolveKnownTool(sub, opts) {
83
109
  if (typeof sub.resolveExecutable === 'function') {
84
110
  try { const p = await sub.resolveExecutable(name); if (p) return { exe: String(p), via: 'path', tried, name } } catch (e) { /* PATH miss is expected */ }
85
111
  }
86
- // 3) KNOWN INSTALL LOCATIONS (Windows TeX roots are globbed per release: <root>/<year>/bin/windows).
112
+ // 3) KNOWN INSTALL LOCATIONS. A TeX root WITHOUT a `bin` segment is year-globbed
113
+ // (`<root>/<year>/bin/<platform>`): the years actually on disk (newest first) first, then the
114
+ // recent-year window, at most TEX_YEAR_LOOKBACK of them. task-28: the whole stage is BOUNDED by
115
+ // TEX_CANDIDATE_CAP candidate paths — documented roots, never a whole-drive scan.
116
+ const texYearSubdirs = win ? ['bin/windows'] : ['bin/x86_64-linux', 'bin/aarch64-linux', 'bin/universal-darwin', 'bin']
117
+ 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 }
118
+ let texProbes = 0
87
119
  for (const base of list) {
88
120
  if (!base) continue
89
121
  if (kind === 'tex' && !/bin/i.test(base)) {
90
- for (const year of await listDirs(base)) {
91
- for (const sub2 of ['bin/windows', 'bin/x86_64-linux', 'bin/universal-darwin', 'bin']) {
92
- const p = await resolvePath(base + '/' + year + '/' + sub2 + '/' + name)
122
+ const onDisk = (await listDirs(texProbeBase(base))).filter((d) => /^\d{4}$/.test(d)).sort((a, b) => Number(b) - Number(a))
123
+ const years = onDisk.concat(texRecentYears().filter((y) => onDisk.indexOf(y) < 0)).slice(0, TEX_YEAR_LOOKBACK)
124
+ for (const year of years) {
125
+ for (const sub2 of texYearSubdirs) {
126
+ if (texProbes >= TEX_CANDIDATE_CAP) break
127
+ texProbes++
128
+ const p = await resolvePath(texProbeBase(base) + '/' + year + '/' + sub2 + '/' + name)
93
129
  if (p) return { exe: p, via: 'known-install', tried, name }
94
130
  }
131
+ if (texProbes >= TEX_CANDIDATE_CAP) break
95
132
  }
133
+ if (texProbes >= TEX_CANDIDATE_CAP) break
96
134
  continue
97
135
  }
98
- const p = await resolvePath(base + '/' + name)
136
+ if (kind === 'tex') {
137
+ if (texProbes >= TEX_CANDIDATE_CAP) break
138
+ texProbes++
139
+ }
140
+ const p = await resolvePath(texProbeBase(base) + '/' + name)
99
141
  if (p) return { exe: p, via: 'known-install', tried, name }
100
142
  }
101
143
  return { exe: null, via: null, tried, name, reason: 'not found on PATH or in the known install locations' }
@@ -724,8 +766,8 @@ async function writeCurrentProject(){ try { const ok=await writeTextAbs(vibeRoot
724
766
  return {ok:false,code:'LEAN_NOT_FOUND',message:'cannot resolve "'+String(params.leanCommand||'lean')+'": '+String((e&&e.message)||e)+' — 仍可把形式化代码写下来归档,但无法在此宿主上执行',file:rel,ms:now()-started}
725
767
  }
726
768
  // §3.1: the search path is computed HERE (never persisted into `leanArgs`): user args first,
727
- // then `--search-path <VibeMath root>` unless the user already stated a search root, then the
728
- // file. `lake env lean` therefore becomes `lake env lean --search-path <root> <file>` for free.
769
+ // then `-R <VibeMath root>` unless the user already stated a search root, then the
770
+ // file. `lake env lean` therefore becomes `lake env lean -R <root> <file>` for free.
729
771
  const argv=leanArgv(exe, abs)
730
772
  let handle
731
773
  try {
@@ -804,12 +846,18 @@ async function writeCurrentProject(){ try { const ok=await writeTextAbs(vibeRoot
804
846
  // · ONLY a settled, green job whose file STILL MATCHES the enqueued content hash may set
805
847
  // `passed` / write `Verified/Lean/<id>.lean`; every other outcome stays `attempted`.
806
848
  const LEAN_ASYNC_ON = () => params.leanAsync !== false
807
- // §3.6: the flag spelling is one constant on purpose — if a toolchain wants `-R <root>`, only
808
- // this line changes and the interface stays put.
809
- const LEAN_SEARCH_PATH_FLAG = '--search-path'
849
+ // task-27 (real-host A round + local reproduction): Lean 4 wants `-R`/`--root` (or LEAN_PATH); the
850
+ // `--search-path` spelling is Lean 3 and makes `lean` fail at ARGUMENT PARSING (rc=1) before it reads the file.
851
+ // Local check on Lean 4.34.0: `lean --search-path <dir> <f>` ⇒ rc=1; `lean -R <dir> <f>` ⇒ accepted.
852
+ const LEAN_SEARCH_PATH_FLAG = '-R'
810
853
  const LEAN_READ_MAX_BYTES = 64 * 1024
811
- /** §3.1: never inject when the user already stated a search root (explicit config wins). */
812
- function leanHasSearchFlag(args){ return (Array.isArray(args)?args:[]).some(a=>{ const s=String(a); return s===LEAN_SEARCH_PATH_FLAG||s.indexOf(LEAN_SEARCH_PATH_FLAG+'=')===0||s==='-R'||s==='--root' }) }
854
+ /** §3.1: never inject when the user already stated a search root (explicit config wins).
855
+ * task-27: the flag we INJECT is `-R` (Lean 4). A user may still state any of these in `leanArgs` —
856
+ * the LEGACY Lean-3 spelling included — and this guard's only job is "never inject a SECOND search root". */
857
+ function leanHasSearchFlag(args){
858
+ const ALSO_ACCEPTED=['--search-path','--root']
859
+ return (Array.isArray(args)?args:[]).some(a=>{ const s=String(a); return s===LEAN_SEARCH_PATH_FLAG||s.indexOf(LEAN_SEARCH_PATH_FLAG+'=')===0||ALSO_ACCEPTED.some(f=>s===f||s.indexOf(f+'=')===0) })
860
+ }
813
861
  /** AMENDMENT §2: the roots to inject — USER paths first, then the automatic VibeMath root,
814
862
  * de-duplicated, order preserved. */
815
863
  function leanSearchPathList(){
@@ -2319,7 +2367,10 @@ async function writeCurrentProject(){ try { const ok=await writeTextAbs(vibeRoot
2319
2367
  // speaker must have produced an explicit `true`.
2320
2368
  const speakers=Object.values(st.inputs)
2321
2369
  const allSolved = allSpoke && speakers.length>0 && speakers.every(iv=>iv.voteSolved===true)
2322
- logActivity('meeting', 'concluded'+(allSolved?' → ALL agree solved':' (no unanimous solved vote)'))
2370
+ // task-25 (real-host R3): the log said only "(no unanimous solved vote)", so nobody could tell WHICH
2371
+ // resident had not voted true (a member even concluded "the vote was never registered"). Name them.
2372
+ const notTrue = allSpoke ? Object.entries(st.inputs).filter(([,iv])=>iv.voteSolved!==true).map(([id])=>id) : []
2373
+ logActivity('meeting', 'concluded'+(allSolved?' → ALL agree solved':' (no unanimous solved vote'+(notTrue.length?';未投 true:'+notTrue.join('、'):'')+')'))
2323
2374
  if(allSolved){
2324
2375
  // ── FINAL PAPER ENTRY (docs/final-paper.md §A1) ─────────────────────────────────────────────────
2325
2376
  // The paper phase MUST start HERE, BEFORE `running=false; autoDone=true` and before the
@@ -2890,7 +2941,7 @@ try { ok = await wakeResident(r, await heartbeatPrompt(r), 'normal') } catch(e){
2890
2941
  // because a manually triggered paper legitimately runs on an already-concluded run.
2891
2942
  if(kind.indexOf('paper:')===0){ await onPaperWakeEnd(r,parsed); return }
2892
2943
  if(kind==='meeting' && meetingState){
2893
- meetingState.inputs[r.rId]={input:parsed.input||parsed.summary||'',voteSolved:typeof parsed.voteSolved==='boolean'?parsed.voteSolved:null,propose_verify:parsed.propose_verify||null,propose_task:parsed.propose_task||null,task_desc:parsed.task_desc||'',claim_task:parsed.claim_task||null}
2944
+ meetingState.inputs[r.rId]={input:(parsed.input||parsed.summary||'').trim()||'(本轮结束块未含 input/summary:框架按空发言记录,见 docs/final-paper.md)',voteSolved:typeof parsed.voteSolved==='boolean'?parsed.voteSolved:null,propose_verify:parsed.propose_verify||null,propose_task:parsed.propose_task||null,task_desc:parsed.task_desc||'',claim_task:parsed.claim_task||null}
2894
2945
  meetingState.lastInputAt=now()
2895
2946
  if(parsed.propose_verify) maybeQueueVerify(parsed.propose_verify, r.rId)
2896
2947
  await saveAll()
@@ -3097,8 +3148,8 @@ try { ok = await wakeResident(r, await heartbeatPrompt(r), 'normal') } catch(e){
3097
3148
  const head=zh
3098
3149
  ? '【最终论文·团队合写】你('+rid+')是本次运行的参与常驻。'
3099
3150
  : '[FINAL PAPER — TEAM CO-AUTHORING] You ('+rid+') are a participating resident of this run.'
3100
- const common=(zh?'\n论文目录:`'+ps.dir+'/`(一切写入必须在这里面)。只整理**已有证据**:你自己的库、Verified/、Shared/meetings|debates/ 与既有卡片;**不得编造**;未决/被否证的条目必须显式标注。\n检测不到 LaTeX 引擎时:① 只在文档化的常见 TeX 根与 PATH 上做有界核查(如 where xelatex、latexmk --version),不要全盘扫描;② 找到绝对路径后写入 paperLatexCommand 并重新检测,再继续;③ 仍找不到就向用户问一次(安装 TeX 需用户明确同意);④ 无应答则照旧降级(只交付 paper.tex 与 paper.md)。硬边界:绝不自动安装;绝不写工作区之外;绝不把"未检测到"当失败。'
3101
- :'\nPaper directory: `'+ps.dir+'/` (everything you write stays inside it). Only organise EXISTING evidence (your own library, Verified/, Shared/meetings|debates/, the recorded cards); never invent; flag unresolved/refuted items explicitly.\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.')
3151
+ const common=(zh?'\n论文目录:`'+ps.dir+'/`(一切写入必须在这里面)。只整理**已有证据**:你自己的库、Verified/、Shared/meetings|debates/ 与既有卡片;**不得编造**;未决/被否证的条目必须显式标注。\n检测不到 LaTeX 引擎时:① 只在文档化的常见 TeX 根与 PATH 上做有界核查(如 where xelatex、latexmk --version),不要全盘扫描——**最常见的情形是引擎装了但不在 PATH**:TeX Live 若装在 Windows 的某个**盘根**下,看该盘根里的 `texlive\\<年份>\\bin\\windows`;类 Unix 看标准系统路径 `/usr/local/texlive/<年份>/bin/*`、`/opt/texlive/<年份>/bin/*`;macOS 看 `/Library/TeX/texbin`;② 找到绝对路径后写入 paperLatexCommand 并重新检测,再继续;③ 仍找不到就**如实上报所办(或群聊)**,由**所办**向用户确认(安装 TeX 需用户明确同意);④ 尚无回应则照旧降级(只交付 paper.tex 与 paper.md)。硬边界:绝不自动安装;绝不写工作区之外;绝不把"未检测到"当失败。'
3152
+ :'\nPaper directory: `'+ps.dir+'/` (everything you write stays inside it). Only organise EXISTING evidence (your own library, Verified/, Shared/meetings|debates/, the recorded cards); never invent; flag unresolved/refuted items explicitly.\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; 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.')
3102
3153
  if(kind==='part') return PAPER_MARK.part+'\n'+head+'\n'+(again?(zh?'(上一次没有收到你的部分,请重发。)\n':'(no part was received from you last time — please resend.)\n'):'')
3103
3154
  +(zh?'请撰写**你负责的那一部分**(你库里的命题/方法/子问题/进展,以及你亲自参与的会议与争论中确立的内容),成文即可,不必追求篇幅。\n'
3104
3155
  :'Write the part YOU own (the propositions/methods/sub-problems/progress in your library and what you personally established in meetings/debates).\n')
@@ -3599,7 +3650,7 @@ try { ok = await wakeResident(r, await heartbeatPrompt(r), 'normal') } catch(e){
3599
3650
  ps.status='failed' // terminal, like a failed compile: no complete deliverable is claimed
3600
3651
  if(!warnedPaperWrite){ warnedPaperWrite=true; console.error('vibe-math-v4: paperFinalize: required paper artifact(s) MISSING after finalize (' + _paperGone.join(', ') + ') - the paper is marked FAILED instead of done; the deliverable is incomplete and `files` never claims a missing artifact') }
3601
3652
  }
3602
- const warn=[ps.warning,ps.compile&&ps.compile.result==='failed'?ps.compile.error:'',ps.compile&&ps.compile.result==='not-detected'?('未检测到 LaTeX 引擎('+(ps.compile.reason||'')+'):只产出 tex+md,不编译。已探测 PATH 与文档化的常见 TeX 根;可用 paperLatexCommand 指定绝对路径。'):''].filter(Boolean).join(';')
3653
+ const warn=[ps.warning,ps.compile&&ps.compile.result==='failed'?ps.compile.error:'',ps.compile&&ps.compile.result==='not-detected'?('未检测到 LaTeX 引擎('+(ps.compile.reason||'')+'):只产出 tex+md,不编译。已探测 PATH 与文档化的常见 TeX 根;可用 paperLatexCommand 指定绝对路径。最常见的情形是引擎已安装但不在 PATH(Windows 盘根下的 texlive\\<年>\\bin\\windows、类 Unix /usr/local|/opt/texlive/<年>/bin/*、macOS /Library/TeX/texbin 已纳入有界探测):把引擎的绝对路径写进 paperLatexCommand 再探测一次;仍找不到就如实上报所办(由所办向用户确认安装),随后照旧安全降级。'):''].filter(Boolean).join(';')
3603
3654
  if(warn) ps.warning=warn // task-11: the compile note must reach the finalize return + status view (the paper body is composed before ps.compile exists)
3604
3655
  if(warn) logActivity('paper','最终论文完成(有警告):'+ps.dir+'|'+warn)
3605
3656
  else logActivity('paper','最终论文完成:'+ps.dir+'('+ps.files.join('、')+(ps.compile?('|编译 '+(ps.compile.result||'')):'')+')')
@@ -4206,7 +4257,7 @@ frozenParticipants: (activeRosterSnapshot()||{}).rosterSnapshot||null, // depr
4206
4257
  // MODE is dynamic — switching it changes the very next prompt), leanCommand/leanArgs select the
4207
4258
  // executable, leanTimeoutMs bounds one run. Invalid values fall back to the defaults and an
4208
4259
  // unknown mode degrades to 'off' (never to a STRONGER mode).
4209
- registerTool('vibe_v4_set','Set V4 parameters: residentCount (how many residents a start spawns), compactThreshold (resident context % that triggers a compaction) and compactAfterRounds (rounds between soft compactions), meetingKeepEvery (every N newly accumulated artifacts an automatic sync meeting is convened), maxParallel (how many residents may be woken concurrently), activityTimeoutMs (idle window before a resident is nudged; also the heartbeat/watchdog period), stallAutoMeetingMs (how long the group may make NO progress before an auto sync meeting is convened), verdictMaxRounds (how many verification rounds one object gets: 1 independent round + re-vote debate rounds), model/provider override resident LLM route (empty=inherit main); toolAllow/toolDeny restrict resident tools (arrays of tool names); residentPersona adds a persona line; formalVerify: "off" (default, a true no-op) | "encourage" (residents decide by implementation difficulty whether to formalize in Lean; a passing Lean run turns the vote into a FIDELITY review of the Lean statements) | "require" (same, plus a gate: a unanimous true/false verdict is withheld as undecided until the object is Lean-passed or has an explicit reasoned blocker record); leanCommand/leanArgs/leanTimeoutMs configure the toolchain; leanAsync (default true) ENQUEUES each compile to a per-session background queue (concurrency 1, budget = leanTimeoutMs) and returns immediately with async:{jobId,state:\'queued\'} — results are announced in the next prompt and visible in vibe_v4_lean_lib.jobs; set leanAsync=false for the historical synchronous await; leanInitiative: "off" | "normal" (default) | "eager" is how PROACTIVE the group should be while working (it never changes what formalVerify requires at voting time); leanSearchPaths (array, default empty) adds extra compiler search roots — they are injected BEFORE the automatic VibeMath root, de-duplicated, and an explicit --search-path/-R/--root in leanArgs still wins; leanJobsMaxParallel (default 1) caps how many background compiles run at once; mathComputation: "auto" (default) | "on" | "off" enables the math_computation tool (off = a true no-op: no probe and no prompt line), mathMode: "typed+shell" (default) | "typed" drops the host-shell fallback and refuses the cli engine, mathEngines limits which engines may run (default python|r|octave|julia|matlab|maple|wolfram|cli), mathTimeoutMs bounds one computation, mathPackages is the pre-checked package list, mathInstallScope: "user" (default) | "system" is the scope an approved install plan may use (system applies to that request only and is never remembered); finalPaper (default true) writes the run\'s final PAPER automatically once the closing meeting votes unanimously to stop, paperFormat: "both" (default) | "md" | "tex" decides which text versions are produced, paperLanguage: "zh" (default) | "en", paperCompilePdf (default true) compiles a PDF when a LaTeX engine is actually detected (xelatex|latexmk|pdflatex|lualatex|tectonic, overridable via paperLatexCommand), paperEditor: "office" (default — the session root/human side) | "resident:<id>" names who finalises the merged draft after the cross-review round. A key outside this list is refused (ok:false, ignored:[...]) instead of being silently dropped.',objParams({residentCount:{type:'integer'},compactAfterRounds:{type:'integer'},compactThreshold:{type:'integer'},meetingKeepEvery:{type:'integer'},maxParallel:{type:'integer'},activityTimeoutMs:{type:'integer'},verdictMaxRounds:{type:'integer'},stallAutoMeetingMs:{type:'integer'},provider:{type:'string'},model:{type:'string'},residentPersona:{type:'string'},toolAllow:{type:'array',items:{type:'string'}},toolDeny:{type:'array',items:{type:'string'}},formalVerify:{type:'string',enum:['off','encourage','require']},leanCommand:{type:'string'},leanArgs:{type:'array',items:{type:'string'}},leanTimeoutMs:{type:'integer'},finalPaper:{type:'boolean'},paperFormat:{type:'string',enum:['both','md','tex']},paperLanguage:{type:'string',enum:['zh','en']},paperCompilePdf:{type:'boolean'},paperEditor:{type:'string'},paperLatexCommand:{type:'string'},leanAsync:{type:'boolean'},leanInitiative:{type:'string',enum:['off','normal','eager']},leanSearchPaths:{type:'array',items:{type:'string'}},leanJobsMaxParallel:{type:'integer'},mathComputation:{type:'string',enum:['auto','on','off']},mathMode:{type:'string',enum:['typed+shell','typed']},mathEngines:{type:'array',items:{type:'string'}},mathTimeoutMs:{type:'integer'},mathPackages:{type:'array',items:{type:'string'}},mathInstallScope:{type:'string',enum:['user','system']}}),(s,a)=>{ const r=s.setParams(a); const st=s.status(); if(r&&r.ok===false) return Object.assign({},st,{ok:false,warning:r.message,ignored:r.ignored}); return st })
4260
+ registerTool('vibe_v4_set','Set V4 parameters: residentCount (how many residents a start spawns), compactThreshold (resident context % that triggers a compaction) and compactAfterRounds (rounds between soft compactions), meetingKeepEvery (every N newly accumulated artifacts an automatic sync meeting is convened), maxParallel (how many residents may be woken concurrently), activityTimeoutMs (idle window before a resident is nudged; also the heartbeat/watchdog period), stallAutoMeetingMs (how long the group may make NO progress before an auto sync meeting is convened), verdictMaxRounds (how many verification rounds one object gets: 1 independent round + re-vote debate rounds), model/provider override resident LLM route (empty=inherit main); toolAllow/toolDeny restrict resident tools (arrays of tool names); residentPersona adds a persona line; formalVerify: "off" (default, a true no-op) | "encourage" (residents decide by implementation difficulty whether to formalize in Lean; a passing Lean run turns the vote into a FIDELITY review of the Lean statements) | "require" (same, plus a gate: a unanimous true/false verdict is withheld as undecided until the object is Lean-passed or has an explicit reasoned blocker record); leanCommand/leanArgs/leanTimeoutMs configure the toolchain; leanAsync (default true) ENQUEUES each compile to a per-session background queue (concurrency 1, budget = leanTimeoutMs) and returns immediately with async:{jobId,state:\'queued\'} — results are announced in the next prompt and visible in vibe_v4_lean_lib.jobs; set leanAsync=false for the historical synchronous await; leanInitiative: "off" | "normal" (default) | "eager" is how PROACTIVE the group should be while working (it never changes what formalVerify requires at voting time); leanSearchPaths (array, default empty) adds extra compiler search roots — they are injected BEFORE the automatic VibeMath root, de-duplicated, and an explicit -R/--root in leanArgs still wins; leanJobsMaxParallel (default 1) caps how many background compiles run at once; mathComputation: "auto" (default) | "on" | "off" enables the math_computation tool (off = a true no-op: no probe and no prompt line), mathMode: "typed+shell" (default) | "typed" drops the host-shell fallback and refuses the cli engine, mathEngines limits which engines may run (default python|r|octave|julia|matlab|maple|wolfram|cli), mathTimeoutMs bounds one computation, mathPackages is the pre-checked package list, mathInstallScope: "user" (default) | "system" is the scope an approved install plan may use (system applies to that request only and is never remembered); finalPaper (default true) writes the run\'s final PAPER automatically once the closing meeting votes unanimously to stop, paperFormat: "both" (default) | "md" | "tex" decides which text versions are produced, paperLanguage: "zh" (default) | "en", paperCompilePdf (default true) compiles a PDF when a LaTeX engine is actually detected (xelatex|latexmk|pdflatex|lualatex|tectonic, overridable via paperLatexCommand), paperEditor: "office" (default — the session root/human side) | "resident:<id>" names who finalises the merged draft after the cross-review round. A key outside this list is refused (ok:false, ignored:[...]) instead of being silently dropped.',objParams({residentCount:{type:'integer'},compactAfterRounds:{type:'integer'},compactThreshold:{type:'integer'},meetingKeepEvery:{type:'integer'},maxParallel:{type:'integer'},activityTimeoutMs:{type:'integer'},verdictMaxRounds:{type:'integer'},stallAutoMeetingMs:{type:'integer'},provider:{type:'string'},model:{type:'string'},residentPersona:{type:'string'},toolAllow:{type:'array',items:{type:'string'}},toolDeny:{type:'array',items:{type:'string'}},formalVerify:{type:'string',enum:['off','encourage','require']},leanCommand:{type:'string'},leanArgs:{type:'array',items:{type:'string'}},leanTimeoutMs:{type:'integer'},finalPaper:{type:'boolean'},paperFormat:{type:'string',enum:['both','md','tex']},paperLanguage:{type:'string',enum:['zh','en']},paperCompilePdf:{type:'boolean'},paperEditor:{type:'string'},paperLatexCommand:{type:'string'},leanAsync:{type:'boolean'},leanInitiative:{type:'string',enum:['off','normal','eager']},leanSearchPaths:{type:'array',items:{type:'string'}},leanJobsMaxParallel:{type:'integer'},mathComputation:{type:'string',enum:['auto','on','off']},mathMode:{type:'string',enum:['typed+shell','typed']},mathEngines:{type:'array',items:{type:'string'}},mathTimeoutMs:{type:'integer'},mathPackages:{type:'array',items:{type:'string'}},mathInstallScope:{type:'string',enum:['user','system']}}),(s,a)=>{ const r=s.setParams(a); const st=s.status(); if(r&&r.ok===false) return Object.assign({},st,{ok:false,warning:r.message,ignored:r.ignored}); return st })
4210
4261
  // resident-facing tools: route to the CALLING resident (exec.agent.id === childId);
4211
4262
  // fall back to the last-woken resident when called by the host/assistant.
4212
4263
  registerTool('vibe_v4_send_message','(resident) Send a message to another resident (to=all broadcasts to the whole team).',objParams({to:{type:'string'},content:{type:'string'}},['to','content']),(s,a,x)=>{ const from=s.residentIdOf(x); if(!from) return {ok:false,message:'no such resident'}; if(String(a.to)==='all') return s.broadcast(a.content, from); return s.postMessage(from,a.to,a.content) })
@@ -112,8 +112,8 @@
112
112
  verification (execute / archive / list the reuse library / read an archived file
113
113
  verbatim / watch the background compile queue). Members use them too; they work in every mode.
114
114
  - math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
115
- - 归档→编辑→重跑: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 时显式告警。
116
- - 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.
115
+ - 归档→编辑→重跑(**回执=一次 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 时显式告警。
116
+ - 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.
117
117
  - 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
118
118
  - Declare substitutions (honesty): when an alternative changes EXACTNESS or conclusion strength (exact symbolic solution -> numerical approximation, closed form -> sampling/quadrature, changed precision/tolerances/assumptions, a different algorithm class), the conclusion MUST say so explicitly and must not read as if the original (exact/requested) result had been obtained; if the exact result is unavailable, say so plainly.
119
119
  A /v5 slash command mirrors these (configure|start|resume|pause|stop|status|report|members|message|meeting|hire|fire|add|remove|set|paper).
@@ -143,7 +143,7 @@
143
143
  small lemmas/propositions/definitions into the library even in ordinary rounds).
144
144
  - Reuse first: check vibe_v5_lean_lib BEFORE writing a definition, reuse archived
145
145
  content via `import Formal.Lib.<name>` / `import Formal.Proved.<name>` (the compile
146
- gets `--search-path <VibeMath root>`, preceded by any leanSearchPaths), or copy the
146
+ gets `-R <VibeMath root>`, preceded by any leanSearchPaths), or copy the
147
147
  exact text with vibe_v5_lean_read. Re-archiving identical content is de-duplicated.
148
148
  - Compiles are ASYNC by default (leanAsync=true, leanJobsMaxParallel default 1):
149
149
  vibe_v5_lean_run / vibe_v5_lean_archive enqueue and return at once; watch them with
@@ -245,8 +245,8 @@
245
245
  verification (execute / archive / list the reuse library / read an archived file
246
246
  verbatim / watch the background compile queue). Members use them too; they work in every mode.
247
247
  - math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
248
- - 归档→编辑→重跑: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 时显式告警。
249
- - 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.
248
+ - 归档→编辑→重跑(**回执=一次 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 时显式告警。
249
+ - 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.
250
250
  - 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
251
251
  - Declare substitutions (honesty): when an alternative changes EXACTNESS or conclusion strength (exact symbolic solution -> numerical approximation, closed form -> sampling/quadrature, changed precision/tolerances/assumptions, a different algorithm class), the conclusion MUST say so explicitly and must not read as if the original (exact/requested) result had been obtained; if the exact result is unavailable, say so plainly.
252
252
  A /v5 slash command mirrors these (configure|start|resume|pause|stop|status|report|members|message|meeting|hire|fire|add|remove|set|paper).
@@ -276,7 +276,7 @@
276
276
  small lemmas/propositions/definitions into the library even in ordinary rounds).
277
277
  - Reuse first: check vibe_v5_lean_lib BEFORE writing a definition, reuse archived
278
278
  content via `import Formal.Lib.<name>` / `import Formal.Proved.<name>` (the compile
279
- gets `--search-path <VibeMath root>`, preceded by any leanSearchPaths), or copy the
279
+ gets `-R <VibeMath root>`, preceded by any leanSearchPaths), or copy the
280
280
  exact text with vibe_v5_lean_read. Re-archiving identical content is de-duplicated.
281
281
  - Compiles are ASYNC by default (leanAsync=true, leanJobsMaxParallel default 1):
282
282
  vibe_v5_lean_run / vibe_v5_lean_archive enqueue and return at once; watch them with