dsh-vibe-math 2.8.0 → 2.8.2

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.
@@ -194,13 +194,30 @@ const FAMILIES = [
194
194
  to: '只交付 paper.tex 与 paper.md。',
195
195
  expect: /\[task-9\] the not-detected warning keeps its tail AND says where it looked/,
196
196
  },
197
+ // RETIRED (task-17, 2.8.1 real-host fix): the former entry here mutated `return [forced]` into the merged
198
+ // `[forced].concat(order…)`, which used to let an unresolvable explicit command fall through to a real
199
+ // engine. After task-17 the explicit value is ALSO handed to resolveKnownTool as `explicit` (the explicit
200
+ // branch probes only it and returns early), so the property is now enforced at TWO independent sites and
201
+ // NO single-site mutation can defeat it — the merged list merely probes dead candidates. The assertion
202
+ // itself stays in tests/e2e-v5-round2.test.mjs (`★ [task-10/v5] …degrades`); only the falsifier is retired
203
+ // (and the task-17 entry below covers the hand-off single-site).
197
204
  {
198
- // task-10: restoring the merged candidate list lets an unresolvable explicit command fall through
199
- // to another engine (the documented rule is "explicit ⇒ only it").
200
- name: 'task-10: the explicit LaTeX command falls through to another engine again',
201
- from: 'return [forced]',
202
- to: 'return [forced].concat(order.filter((n) => n !== forced))',
203
- expect: /\[task-10\/v5\] an unresolvable explicit paperLatexCommand degrades/,
205
+ // task-17 (real host, 2.8.1): probing the explicit value as a BARE COMMAND NAME lets the known-root stage
206
+ // join it onto every TeX root, so `triedPaths` reports impossible paths. Reverting the hand-off must
207
+ // redden the guarding assertion by name.
208
+ name: 'task-17: the explicit paperLatexCommand is probed as a bare command name again (triedPaths gets fake joins)',
209
+ from: "explicit: forcedTex, kind: 'tex'",
210
+ to: "explicit: '', kind: 'tex'",
211
+ expect: /\[task-17\/v5\] an ABSOLUTE explicit paperLatexCommand is probed ALONE/,
212
+ },
213
+ {
214
+ // task-18: the DELIVERED artifacts must say why no PDF was produced (the body is composed before the
215
+ // compile runs, so the note is written after it). Blunting the zh note must redden that guard BY NAME.
216
+ // The scenario that guards this runs with lang='en', so the ENGLISH branch is the one to blunt.
217
+ name: 'task-18: the delivered paper stops stating that no engine was detected',
218
+ from: ": 'No LaTeX engine detected: tex+md only'",
219
+ to: ": ''",
220
+ expect: /\[task-18\/v5\] the DELIVERED paper.md and paper.tex/,
204
221
  },
205
222
  ]
206
223
  let red = 0
@@ -2998,6 +2998,34 @@ try { ok = await wakeResident(r, await heartbeatPrompt(r), 'normal') } catch(e){
2998
2998
  return await writeText(norm,content)
2999
2999
  }
3000
3000
  function paperLog(line){ const ps=paperState; if(ps) ps.log.push(fmtTime()+'|'+String(line)) }
3001
+ /**
3002
+ * Compile-outcome note (docs/final-paper.md §D). The paper body is COMPOSED before compilation runs, so
3003
+ * the spec's two conditional bullets in §7 can only fire on a re-entry (where the persisted meta already
3004
+ * carries `compile`). State the outcome in the artifacts the user actually reads instead - ONCE and
3005
+ * idempotently, because finalize can be re-entered. The note text is the SAME string as those bullets, so
3006
+ * the bullet path and this path can never both add it.
3007
+ */
3008
+ async function paperAnnotateCompileOutcome(ps){
3009
+ const res=ps.compile&&ps.compile.result
3010
+ if(res!=='failed'&&res!=='not-detected') return
3011
+ const zh=ps.cfg.language!=='en'
3012
+ const note=res==='failed'
3013
+ ? (zh?'PDF 编译失败:已保留 paper.tex 与 paper.md 并上报':'PDF compilation failed: paper.tex and paper.md were kept and reported')
3014
+ : (zh?'本机未检测到 LaTeX 引擎:只产出 tex+md':'No LaTeX engine detected: tex+md only')
3015
+ if(ps.files.indexOf('paper.md')!==-1){
3016
+ const md=await readText(ps.dir+'/paper.md')
3017
+ if(typeof md==='string'&&md.indexOf(note)===-1) await paperWriteRel(ps.dir+'/paper.md',md.replace(/\s*$/,'')+'\n- '+note+'\n')
3018
+ }
3019
+ if(ps.files.indexOf('paper.tex')!==-1){
3020
+ const tex=await readText(ps.dir+'/paper.tex')
3021
+ if(typeof tex==='string'&&tex.indexOf(note)===-1){
3022
+ const line='% '+note
3023
+ await paperWriteRel(ps.dir+'/paper.tex',tex.indexOf('\\end{document}')!==-1
3024
+ ?tex.replace('\\end{document}',line+'\n\\end{document}')
3025
+ :tex.replace(/\s*$/,'')+'\n'+line+'\n')
3026
+ }
3027
+ }
3028
+ }
3001
3029
  function paperStepLimit(){ return Math.max(PAPER_WAIT_FLOOR_MS, recoverStallMs()*4) }
3002
3030
  function paperCfg(ov){
3003
3031
  const o=ov||{}
@@ -3543,6 +3571,8 @@ try { ok = await wakeResident(r, await heartbeatPrompt(r), 'normal') } catch(e){
3543
3571
  if(ps.cfg.format!=='md'){ if(await paperWriteRel(ps.dir+'/paper.tex',composed.tex)) ps.files.push('paper.tex') }
3544
3572
  ps.compile=await paperCompile(ps,composed,ev)
3545
3573
  if(ps.compile&&ps.compile.result==='ok') ps.files.push('paper.pdf')
3574
+ // The body was composed just above, BEFORE this compile ran, so the outcome is stated HERE (idempotent).
3575
+ await paperAnnotateCompileOutcome(ps)
3546
3576
  ps.finalizedAt=now(); ps.status='done'
3547
3577
  await paperWriteMeta(); await paperWriteLog()
3548
3578
  await paperWriteMeta(); await paperWriteLog()
@@ -5893,8 +5893,13 @@ export function apply(ctx) {
5893
5893
  }
5894
5894
  const engines = []
5895
5895
  const triedPaths = [] // task-9: union of the known-root candidates probed across THIS detection
5896
+ // task-17: when paperLatexCommand is set, paperEngineCandidates() returns THAT VALUE ALONE. Probing it as
5897
+ // a bare command name made resolveKnownTool join it onto every known TeX root, so `triedPaths` reported
5898
+ // impossible paths like `<MiKTeX root>/Z:/no/such/xelatex.exe` (observed on a real host with 2.8.1).
5899
+ // Handing it over as the EXPLICIT value probes only it (docs/final-paper.md §B); '' keeps auto-detection.
5900
+ const forcedTex = String(params.paperLatexCommand || '').trim()
5896
5901
  for (const name of paperEngineCandidates(lang)) {
5897
- try { const _vr = await resolveKnownTool(sub, { name, explicit: '', kind: 'tex' }); triedPaths.push(..._vr.tried); const exe = _vr.exe; if (exe) engines.push({ name: String(name), exe: String(exe), via: _vr.via }) } catch (e) { /* not installed */ }
5902
+ try { const _vr = await resolveKnownTool(sub, { name, explicit: forcedTex, kind: 'tex' }); triedPaths.push(..._vr.tried); const exe = _vr.exe; if (exe) engines.push({ name: String(name), exe: String(exe), via: _vr.via }) } catch (e) { /* not installed */ }
5898
5903
  }
5899
5904
  return { engines, triedPaths, reason: engines.length ? '' : ('未检测到 LaTeX 引擎(' + paperEngineCandidates(lang).join('/') + ')') }
5900
5905
  }
@@ -6065,6 +6070,27 @@ export function apply(ctx) {
6065
6070
  // broken engine. Whatever the compile outcome, the DELIVERED tex must be the real,
6066
6071
  // fully-generated one (the attempts are recorded in the meta either way).
6067
6072
  if (p.format !== 'md') await writeTextRel(PAPER_FILE(id, 'paper.tex'), tex)
6073
+ // task-18: the body is composed BEFORE compilation runs, so the outcome cannot live in the sections.
6074
+ // v4 states it in the delivered artifacts since 2.8.1; v5 must too — ONCE and idempotently (finalisation
6075
+ // can be re-entered). The marker sentence is the same family as the warning below, and the presence
6076
+ // check keeps the two paths from ever duplicating it.
6077
+ if (compile.status === 'failed' || compile.status === 'not-detected') {
6078
+ const zhNote = p.lang !== 'en'
6079
+ const note = compile.status === 'failed'
6080
+ ? (zhNote ? 'PDF 编译失败:已保留 paper.tex 与 paper.md 并上报' : 'PDF compilation failed: paper.tex and paper.md were kept and reported')
6081
+ : (zhNote ? '本机未检测到 LaTeX 引擎:只产出 tex+md' : 'No LaTeX engine detected: tex+md only')
6082
+ const annotate = async (rel, file, isTex) => {
6083
+ const cur = await readTextAbs(paperAbs(id, file))
6084
+ if (typeof cur !== 'string' || cur.indexOf(note) !== -1) return
6085
+ const line = (isTex ? '% ' : '- ') + note
6086
+ const next = isTex && cur.indexOf('\\end{document}') !== -1
6087
+ ? cur.replace('\\end{document}', line + '\n\\end{document}')
6088
+ : cur.replace(/\s*$/, '') + '\n' + line + '\n'
6089
+ await writeTextRel(rel, next)
6090
+ }
6091
+ if (p.format !== 'tex') await annotate(PAPER_FILE(id, 'paper.md'), 'paper.md', false)
6092
+ if (p.format !== 'md') await annotate(PAPER_FILE(id, 'paper.tex'), 'paper.tex', true)
6093
+ }
6068
6094
  if (await fileExistsAbs(paperAbs(id, 'paper.pdf'))) files.push('paper.pdf')
6069
6095
  const warnings = (p.warnings || []).slice()
6070
6096
  if (compile.status === 'failed') warnings.push('LaTeX 编译失败(已保留 paper.tex 与 paper.md,不阻塞定稿):' + (compile.attempts || []).map((a) => a.engine + '/' + a.label + ' exit=' + a.exitCode + (a.message ? '(' + a.message + ')' : '')).join(';'))