dsh-vibe-math 2.3.16 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.en.md +64 -61
  2. package/README.md +64 -61
  3. package/SECURITY.md +56 -0
  4. package/cordis.patch.yml +1123 -10
  5. package/docs/AUDIT-CHECKLIST.md +499 -384
  6. package/docs/COMPAT-AUDIT-ROUND2.md +3 -0
  7. package/docs/COMPAT-AUDIT-ROUND3.md +139 -0
  8. package/docs/generate_framework_diagram_v5.mjs +27 -27
  9. package/docs/release-notes/RELEASE-NOTES-2.4.0.md +172 -0
  10. package/docs/release-notes/RELEASE-NOTES-2.4.1.md +129 -0
  11. package/docs/test-timing.md +2 -1
  12. package/installer.js +89 -21
  13. package/package.json +36 -10
  14. package/preset-declaration.js +62 -0
  15. package/prompt-corpus-persona/persona-corpus.json +2 -2
  16. package/prompt-corpus-persona/persona-corpus.md +2 -2
  17. package/prompt-corpus-v5/prompt-corpus-v5.json +7 -7
  18. package/prompt-corpus-v5/prompt-corpus-v5.md +8 -129
  19. package/scripts/build-preset-rows.mjs +146 -0
  20. package/tests/audit-installer-compat.test.mjs +9 -3
  21. package/tests/audit-preset-rows.test.mjs +185 -0
  22. package/tests/audit-v5-integrity.mjs +49 -3
  23. package/tests/e2e-v5-round2.test.mjs +137 -67
  24. package/tests/formal-verify-v5.test.mjs +43 -35
  25. package/tests/prompt-v5-integrity.test.mjs +49 -47
  26. package/tests/selfdrive-v5.mjs +42 -55
  27. package/vibe-math-v2/agent.cordis.yml +5 -5
  28. package/vibe-math-v2/preset.yml +1 -1
  29. package/vibe-math-v2/vibe-math-v2.js +111 -64
  30. package/vibe-math-v3/vibe-math-v3.js +213 -150
  31. package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +2 -2
  32. package/vibe-math-v4/vibe-math-v4.js +257 -34
  33. package/vibe-math-v5/agent.cordis.yml +8 -8
  34. package/vibe-math-v5/preset.yml +1 -1
  35. package/vibe-math-v5/vibe-math-v5.js +292 -129
  36. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +56 -44
  37. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +24 -22
  38. package//347/244/272/344/276/213/345/233/276//346/241/206/346/236/266/345/233/276-v5-en.svg +14 -14
  39. package//347/244/272/344/276/213/345/233/276//346/241/206/346/236/266/345/233/276-v5.svg +14 -14
@@ -197,17 +197,10 @@ export function apply(ctx) {
197
197
 
198
198
  // ================= helpers =================
199
199
  function textBlock(t) { return { type: 'text', text: String(t) } }
200
- function now() { return Date.now() }
201
- function uuid() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 36; i++) { if (i === 8 || i === 13 || i === 18 || i === 23) s += '-'; else s += h[Math.floor(Math.random() * 16)] } return s }
202
- function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
203
- function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
204
- function fmtTime(ts) { try { return new Date(ts || now()).toISOString().replace('T', ' ').slice(0, 19) } catch (e) { return String(ts || '') } }
205
200
  function workspaceRoot() { try { if (rootAgent && rootAgent.session && rootAgent.session.header && rootAgent.session.header.cwd) return rootAgent.session.header.cwd } catch (e) {} const sp = sandboxPolicyOf(); if (sp && sp.workspaceRoot) return sp.workspaceRoot; return '.' }
206
201
  function vibeRoot() { return (workspaceRoot() + '/VibeMath').replace(/\\/g, '/') }
207
202
  function projectRoot(slug) { return vibeRoot() + '/Projects/' + slug }
208
203
  function frameworkRoot() { return projectRoot(currentProject) }
209
- function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
210
- function safeId(s) { return String(s == null ? 'anon' : s).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80) || 'anon' }
211
204
  /**
212
205
  * 把模型/用户提供的对象 id 消毒成**单个安全文件名**。v4 已有等价的 idSafe(见
213
206
  * vibe-math-v4.js),v3 此前缺这一步:saveProblem/saveProposition/saveVerified 直接
@@ -215,14 +208,6 @@ export function apply(ctx) {
215
208
  * 这里替换路径分隔符与控制字符、折叠连续连字符,并剥掉首尾的点/连字符,
216
209
  * 保证结果永不为 `.` / `..`、也不含分隔符。
217
210
  */
218
- function idSafe(s) {
219
- const t = String(s == null ? '' : s).trim()
220
- .replace(/[\\/:*?"<>|\u0000-\u001f]+/g, '-')
221
- .replace(/-{2,}/g, '-')
222
- .replace(/^[.\-]+|[.\-]+$/g, '')
223
- .slice(0, 80)
224
- return t || 'id'
225
- }
226
211
  let warnedNoPolicy = false
227
212
  function warnNoPolicyOnce() { if (!warnedNoPolicy) { warnedNoPolicy = true; console.error('vibe-math-v3: sandboxPolicy unavailable; writes go out with no explicit policy') } }
228
213
  // Sandbox fence for our own writes. The `resolve({})` fallback is a last resort and is
@@ -231,33 +216,6 @@ export function apply(ctx) {
231
216
  // which is not necessarily this session's workspace — a silently different fence.
232
217
  function getPolicy() { const sp = sandboxPolicyOf(); if (!sp) { warnNoPolicyOnce(); return undefined } try { if (rootAgent && rootAgent.session) return sp.resolve({ session: rootAgent.session }) } catch (e) { warnNoPolicyOnce() } try { const p = sp.resolve({}); if (!warnedNoPolicy) { warnedNoPolicy = true; console.error('vibe-math-v3: falling back to sandboxPolicy.resolve({}) — the fence root is the host-configured workspace, not necessarily this session cwd') } return p } catch (e) { warnNoPolicyOnce() } return undefined }
233
218
  function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
234
- function blocksToText(blocks) { if (!blocks) return ''; let out = ''; for (let i = 0; i < blocks.length; i++) { const b = blocks[i]; if (b && b.type === 'text' && typeof b.text === 'string') out += b.text + '\n' } return out.trim() }
235
- function parseJson(text) {
236
- if (typeof text !== 'string') return undefined
237
- const tryObj = function (s) { try { const v = JSON.parse(s); return (v && typeof v === 'object' && !Array.isArray(v)) ? v : undefined } catch (e) { return undefined } }
238
- const fenceRe = /```(?:json)?[ \t]*([\s\S]*?)```/gi
239
- let m
240
- while ((m = fenceRe.exec(text)) !== null) { const obj = tryObj(m[1].trim()); if (obj !== undefined) return obj }
241
- const whole = tryObj(text.trim()); if (whole !== undefined) return whole
242
- let best = undefined; let bestLen = -1
243
- for (let start = 0; start < text.length; start++) {
244
- if (text[start] !== '{') continue
245
- let depth = 0, inStr = false, esc = false, end = -1
246
- for (let i = start; i < text.length; i++) {
247
- const c = text[i]
248
- if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue }
249
- if (c === '"') { inStr = true; continue }
250
- if (c === '{') depth++
251
- else if (c === '}') { depth--; if (depth === 0) { end = i; break } }
252
- }
253
- if (end === -1) continue
254
- const obj = tryObj(text.slice(start, end + 1))
255
- if (obj !== undefined && (end - start + 1) > bestLen) { best = obj; bestLen = end - start + 1 }
256
- }
257
- return best
258
- }
259
- function safeJson(v, fb) { if (v == null || v === '') return fb; try { return JSON.parse(v) } catch (e) { return fb } }
260
- function stripJsonComments(text) { let out = ''; let inStr = false; let inLine = false; let inBlock = false; let esc = false; for (let i = 0; i < text.length; i++) { const c = text[i]; const n = text[i + 1]; if (inLine) { if (c === '\n') { inLine = false; out += c } continue } if (inBlock) { if (c === '*' && n === '/') { inBlock = false; i++ } continue } if (inStr) { out += c; if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue } if (c === '"') { inStr = true; out += c; continue } if (c === '/' && n === '/') { inLine = true; i++; continue } if (c === '/' && n === '*') { inBlock = true; i++; continue } out += c } return out }
261
219
 
262
220
  // ================= parameter schema =================
263
221
  const PARAM_SCHEMA = [
@@ -466,75 +424,14 @@ export function apply(ctx) {
466
424
  // ================= md soft-spec helpers =================
467
425
  // 软规范:对象 md 头部锚点行(唯一强制部分)+ 正文自由叙述。调度器只解析
468
426
  // 头部锚点与条目标题行(### 解法/证明/证伪 N|标题|概率X|状态Y),从不解析正文散文。
469
- function splitHeader(text) {
470
- const idx = String(text).search(/\n## /)
471
- const head = idx === -1 ? String(text) : String(text).slice(0, idx)
472
- const body = idx === -1 ? '' : String(text).slice(idx + 1)
473
- return { head: head, body: body }
474
- }
475
- function parseAnchors(head) {
476
- const anchors = {}
477
- const re = /^-\s*([A-Za-z\u4e00-\u9fa5/]+)\s*:\s*(.*)$/gm
478
- let m
479
- while ((m = re.exec(head)) !== null) anchors[m[1].trim()] = m[2].trim()
480
- return anchors
481
- }
482
427
  function anchorLine(k, v) { return '- ' + k + ': ' + String(v == null ? '' : v) }
483
428
  // 解析条目标题行 + 其后正文,直到下一个 ### / ## 标题。返回 [{heading, text}]
484
- function parseEntries(body, kindRe) {
485
- const out = []
486
- const lines = String(body).split('\n')
487
- let cur = null
488
- for (let i = 0; i < lines.length; i++) {
489
- const line = lines[i]
490
- const m = kindRe.exec(line)
491
- if (m) {
492
- if (cur) out.push(cur)
493
- // 约定:m[1]=标题段,m[2]=概率段,m[3]=状态段(应用记录正则只有 m[1])
494
- cur = { heading: line.trim(), title: (m[1] || '').trim(), prob: m[2] !== undefined ? Number(m[2]) : undefined, status: (m[3] || '').trim(), text: [] }
495
- continue
496
- }
497
- if (/^#{2,3}\s/.test(line)) {
498
- if (cur) { out.push(cur); cur = null }
499
- continue
500
- }
501
- if (cur) cur.text.push(line)
502
- }
503
- if (cur) out.push(cur)
504
- for (const e of out) e.text = e.text.join('\n').trim()
505
- return out
506
- }
507
- function section(body, name) {
508
- const re = new RegExp('^##\\s+' + name + '\\s*$', 'm')
509
- const m = re.exec(String(body))
510
- if (!m) return ''
511
- const rest = String(body).slice(m.index + m[0].length)
512
- const end = rest.search(/\n##\s/)
513
- return (end === -1 ? rest : rest.slice(0, end)).trim()
514
- }
515
429
  /**
516
430
  * 按顺序切出正文里的所有 `## ` 顶层段。用于"无损往返":compose 只重新生成自己管理的
517
431
  * 那几段,其余段(例如 经验与教训、或代理自加的任何段)必须原样保留,否则每次调度器
518
432
  * 回写都会把代理写进卡片的内容抹掉(违反 实现方案.md「正文只追加,不覆盖」)。
519
433
  */
520
- function parseBodySections(body) {
521
- const text = String(body || '')
522
- const re = /^##\s+(.+?)\s*$/gm
523
- const found = []
524
- let m
525
- while ((m = re.exec(text)) !== null) found.push({ name: m[1].trim(), from: m.index, bodyFrom: m.index + m[0].length })
526
- for (let i = 0; i < found.length; i++) {
527
- const to = i + 1 < found.length ? found[i + 1].from : text.length
528
- found[i].text = text.slice(found[i].bodyFrom, to).trim()
529
- }
530
- return found
531
- }
532
434
  /** 未被 compose 接管的段:原样保留,避免回写时丢失。 */
533
- function extraBodySections(body, managedNames) {
534
- return parseBodySections(body)
535
- .filter(function (s) { return !managedNames.includes(s.name) })
536
- .map(function (s) { return { name: s.name, text: s.text } })
537
- }
538
435
  /** 把保留段追加到 compose 输出末尾(无则原样返回),保证正文只增不减。 */
539
436
  function withExtraSections(lines, extras) {
540
437
  if (!Array.isArray(extras) || extras.length === 0) return lines
@@ -691,34 +588,6 @@ export function apply(ctx) {
691
588
  * 必须把 问题/方向 取回来:compose 会按本对象的字段重写该标题行,若解析时丢成空串,
692
589
  * 每次回写都会把"用在哪"永久抹掉(实现方案.md 要求记录 问题/方向)。
693
590
  */
694
- function parseAppTitle(title) {
695
- const t = String(title || '')
696
- const mQ = /问题\s*(\S+)/.exec(t)
697
- const mD = /方向\s*(\S+)/.exec(t)
698
- // `at` = 第一段(时间戳)。取第一个 '|' 之前的部分。
699
- const at = t.split('|')[0].trim()
700
- return { at: at, 问题: mQ ? mQ[1] : '', 方向: mD ? mD[1] : '' }
701
- }
702
- function parseMethodMd(id, text) {
703
- const { head, body } = splitHeader(text)
704
- const a = parseAnchors(head)
705
- const apps = parseEntries(body, /^###\s*应用\s*\d+|(.*?)$/).map(function (e) {
706
- const t = parseAppTitle(e.title)
707
- return { at: t.at, 问题: t.问题, 方向: t.方向, text: e.text }
708
- })
709
- // 改进历史必须真正解析回来:此前硬编码 [] 导致每次 compose 都写成占位符,
710
- // 于是"下一次用到该方法"就把代理沉淀的改进历史静默销毁(违反「正文只追加,不覆盖」)。
711
- const improvements = parseEntries(body, /^###\s*v(\d+)\s*((.*?))\s*$/).map(function (e) {
712
- return { v: Number(e.title) || 0, 原因: e.status || '', text: e.text }
713
- })
714
- return {
715
- id: id, 标题: a['标题'] || id, 类型: a['类型'] || '方法', 状态: a['状态'] || '经验',
716
- 可信断言: safeJson(a['可信断言'], []), 上级体系: safeJson(a['上级体系'], []), 子方法: safeJson(a['子方法'], []), 相关: safeJson(a['相关'], []),
717
- 适用场景: a['适用场景'] || '', 核心内容: section(body, '核心内容'), 定义与记号: section(body, '定义与记号'),
718
- applications: apps, improvements: improvements, 来源: a['来源'] || '',
719
- extraSections: extraBodySections(body, ['核心内容', '定义与记号', '应用记录', '改进历史']),
720
- }
721
- }
722
591
 
723
592
  // ---- compose: Verified card ----
724
593
  function composeVerifiedMd(card) {
@@ -1096,24 +965,7 @@ export function apply(ctx) {
1096
965
  * own rejection message, which lists every registered global tool, so this
1097
966
  * never guesses. Returns undefined when nothing usable remains.
1098
967
  */
1099
- function sanitizeToolFilter(filter, known) {
1100
- if (!filter || !(known instanceof Set) || known.size === 0) return filter
1101
- const out = {}
1102
- for (const key of ['allow', 'deny']) {
1103
- const list = filter[key]
1104
- if (!Array.isArray(list)) continue
1105
- const kept = list.filter(function (n) { return known.has(String(n).trim()) })
1106
- if (kept.length > 0) out[key] = kept
1107
- }
1108
- return (out.allow || out.deny) ? out : undefined
1109
- }
1110
968
  /** The host names the offending tools and then lists the registered ones. */
1111
- function registeredToolsFromError(message) {
1112
- const m = /known global tools:\s*([^]*)$/.exec(String(message || ''))
1113
- if (!m) return undefined
1114
- const names = m[1].split(',').map(function (s) { return s.trim() }).filter(Boolean)
1115
- return names.length > 0 ? new Set(names) : undefined
1116
- }
1117
969
  function buildToolFilter(role) {
1118
970
  const allow = role === 'solver' ? params.solverToolAllow : role === 'verifier' ? params.verifierToolAllow : undefined
1119
971
  const deny = role === 'solver' ? params.solverToolDeny : role === 'verifier' ? params.verifierToolDeny : undefined
@@ -3772,13 +3624,17 @@ export function apply(ctx) {
3772
3624
  input: { hint: '[start|resume|pause|abort|status|report|mode <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]' },
3773
3625
  handler: async function (invocation) {
3774
3626
  const s = getSession(invocation && invocation.agent)
3775
- if (!s) return { kind: 'success', text: JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' }) }
3627
+ if (!s) return { kind: 'error', text: JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' }) }
3776
3628
  const line = String(invocation && invocation.rawInput ? invocation.rawInput : '').trim()
3777
3629
  const parts = line.length > 0 ? line.split(/\s+/) : []
3778
3630
  const cmd = parts[0] || ''
3779
3631
  const rest = parts.slice(1)
3780
3632
  const result = await s.dispatchVibeCommand(cmd, rest)
3781
- return { kind: 'success', text: JSON.stringify(result, null, 2) }
3633
+ // A business failure (the dispatch result's own ok:false) is a FAILED command: the host's
3634
+ // CommandResult union distinguishes success from error, and returning 'success' made a rejected
3635
+ // invocation look identical to a successful one in the UI.
3636
+ const failed = result !== null && typeof result === 'object' && result.ok === false
3637
+ return { kind: failed ? 'error' : 'success', text: JSON.stringify(result, null, 2) }
3782
3638
  },
3783
3639
  }))
3784
3640
 
@@ -3795,3 +3651,210 @@ export function apply(ctx) {
3795
3651
  // tick timer (registered once; ticks every running session at its own pace)
3796
3652
  ctx.effect(() => { const t = setInterval(function () { for (const s of sessions.values()) { if (s.getRunning() && !s.tickInFlight && s.tickDue() && s.scheduler.gate === null) s.scheduleTick() } }, 1000); return () => clearInterval(t) })
3797
3653
  }
3654
+
3655
+ // ---- test seam: pure, stateless helpers --------------------------------
3656
+ // These helpers were declared inside `apply()` and are now declared at module scope, so
3657
+ // `apply()` closes over exactly the same function objects this export hands out. The audit
3658
+ // suites therefore exercise the REAL implementations by importing this module, instead of
3659
+ // extracting source text and compiling function bodies through the Function constructor
3660
+ // (dynamic code execution, rejected by the plugin-catalog security scan as
3661
+ // DANGEROUS_DYNAMIC_EXECUTION).
3662
+ //
3663
+ // Contract: no member may touch `ctx`, session state or mutable module state. Most are pure;
3664
+ // three are deliberately non-deterministic (`uuid`/`shortId` use Math.random, `fmtTime` falls back
3665
+ // to the clock) and `parseProgress` normalises the object it is handed in place (pre-existing).
3666
+ // Nothing here is used by the plugin at runtime except through `apply()`, and behaviour is
3667
+ // byte-identical to the previous in-`apply` declarations.
3668
+ export const __testHelpers = {
3669
+ uuid,
3670
+ shortId,
3671
+ clamp01,
3672
+ fmtTime,
3673
+ slugify,
3674
+ safeId,
3675
+ idSafe,
3676
+ blocksToText,
3677
+ parseJson,
3678
+ safeJson,
3679
+ stripJsonComments,
3680
+ splitHeader,
3681
+ parseAnchors,
3682
+ parseEntries,
3683
+ section,
3684
+ parseBodySections,
3685
+ extraBodySections,
3686
+ parseAppTitle,
3687
+ parseMethodMd,
3688
+ sanitizeToolFilter,
3689
+ registeredToolsFromError,
3690
+ }
3691
+
3692
+ function now() { return Date.now() }
3693
+
3694
+ function uuid() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 36; i++) { if (i === 8 || i === 13 || i === 18 || i === 23) s += '-'; else s += h[Math.floor(Math.random() * 16)] } return s }
3695
+
3696
+ function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
3697
+
3698
+ function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
3699
+
3700
+ function fmtTime(ts) { try { return new Date(ts || now()).toISOString().replace('T', ' ').slice(0, 19) } catch (e) { return String(ts || '') } }
3701
+
3702
+ function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
3703
+
3704
+ function safeId(s) { return String(s == null ? 'anon' : s).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80) || 'anon' }
3705
+
3706
+ function idSafe(s) {
3707
+ const t = String(s == null ? '' : s).trim()
3708
+ .replace(/[\\/:*?"<>|\u0000-\u001f]+/g, '-')
3709
+ .replace(/-{2,}/g, '-')
3710
+ .replace(/^[.\-]+|[.\-]+$/g, '')
3711
+ .slice(0, 80)
3712
+ return t || 'id'
3713
+ }
3714
+
3715
+ function blocksToText(blocks) { if (!blocks) return ''; let out = ''; for (let i = 0; i < blocks.length; i++) { const b = blocks[i]; if (b && b.type === 'text' && typeof b.text === 'string') out += b.text + '\n' } return out.trim() }
3716
+
3717
+ function parseJson(text) {
3718
+ if (typeof text !== 'string') return undefined
3719
+ const tryObj = function (s) { try { const v = JSON.parse(s); return (v && typeof v === 'object' && !Array.isArray(v)) ? v : undefined } catch (e) { return undefined } }
3720
+ const fenceRe = /```(?:json)?[ \t]*([\s\S]*?)```/gi
3721
+ let m
3722
+ while ((m = fenceRe.exec(text)) !== null) { const obj = tryObj(m[1].trim()); if (obj !== undefined) return obj }
3723
+ const whole = tryObj(text.trim()); if (whole !== undefined) return whole
3724
+ let best = undefined; let bestLen = -1
3725
+ for (let start = 0; start < text.length; start++) {
3726
+ if (text[start] !== '{') continue
3727
+ let depth = 0, inStr = false, esc = false, end = -1
3728
+ for (let i = start; i < text.length; i++) {
3729
+ const c = text[i]
3730
+ if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue }
3731
+ if (c === '"') { inStr = true; continue }
3732
+ if (c === '{') depth++
3733
+ else if (c === '}') { depth--; if (depth === 0) { end = i; break } }
3734
+ }
3735
+ if (end === -1) continue
3736
+ const obj = tryObj(text.slice(start, end + 1))
3737
+ if (obj !== undefined && (end - start + 1) > bestLen) { best = obj; bestLen = end - start + 1 }
3738
+ }
3739
+ return best
3740
+ }
3741
+
3742
+ function safeJson(v, fb) { if (v == null || v === '') return fb; try { return JSON.parse(v) } catch (e) { return fb } }
3743
+
3744
+ function stripJsonComments(text) { let out = ''; let inStr = false; let inLine = false; let inBlock = false; let esc = false; for (let i = 0; i < text.length; i++) { const c = text[i]; const n = text[i + 1]; if (inLine) { if (c === '\n') { inLine = false; out += c } continue } if (inBlock) { if (c === '*' && n === '/') { inBlock = false; i++ } continue } if (inStr) { out += c; if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue } if (c === '"') { inStr = true; out += c; continue } if (c === '/' && n === '/') { inLine = true; i++; continue } if (c === '/' && n === '*') { inBlock = true; i++; continue } out += c } return out }
3745
+
3746
+ function splitHeader(text) {
3747
+ const idx = String(text).search(/\n## /)
3748
+ const head = idx === -1 ? String(text) : String(text).slice(0, idx)
3749
+ const body = idx === -1 ? '' : String(text).slice(idx + 1)
3750
+ return { head: head, body: body }
3751
+ }
3752
+
3753
+ function parseAnchors(head) {
3754
+ const anchors = {}
3755
+ const re = /^-\s*([A-Za-z\u4e00-\u9fa5/]+)\s*:\s*(.*)$/gm
3756
+ let m
3757
+ while ((m = re.exec(head)) !== null) anchors[m[1].trim()] = m[2].trim()
3758
+ return anchors
3759
+ }
3760
+
3761
+ function parseEntries(body, kindRe) {
3762
+ const out = []
3763
+ const lines = String(body).split('\n')
3764
+ let cur = null
3765
+ for (let i = 0; i < lines.length; i++) {
3766
+ const line = lines[i]
3767
+ const m = kindRe.exec(line)
3768
+ if (m) {
3769
+ if (cur) out.push(cur)
3770
+ // 约定:m[1]=标题段,m[2]=概率段,m[3]=状态段(应用记录正则只有 m[1])
3771
+ cur = { heading: line.trim(), title: (m[1] || '').trim(), prob: m[2] !== undefined ? Number(m[2]) : undefined, status: (m[3] || '').trim(), text: [] }
3772
+ continue
3773
+ }
3774
+ if (/^#{2,3}\s/.test(line)) {
3775
+ if (cur) { out.push(cur); cur = null }
3776
+ continue
3777
+ }
3778
+ if (cur) cur.text.push(line)
3779
+ }
3780
+ if (cur) out.push(cur)
3781
+ for (const e of out) e.text = e.text.join('\n').trim()
3782
+ return out
3783
+ }
3784
+
3785
+ function section(body, name) {
3786
+ const re = new RegExp('^##\\s+' + name + '\\s*$', 'm')
3787
+ const m = re.exec(String(body))
3788
+ if (!m) return ''
3789
+ const rest = String(body).slice(m.index + m[0].length)
3790
+ const end = rest.search(/\n##\s/)
3791
+ return (end === -1 ? rest : rest.slice(0, end)).trim()
3792
+ }
3793
+
3794
+ function parseBodySections(body) {
3795
+ const text = String(body || '')
3796
+ const re = /^##\s+(.+?)\s*$/gm
3797
+ const found = []
3798
+ let m
3799
+ while ((m = re.exec(text)) !== null) found.push({ name: m[1].trim(), from: m.index, bodyFrom: m.index + m[0].length })
3800
+ for (let i = 0; i < found.length; i++) {
3801
+ const to = i + 1 < found.length ? found[i + 1].from : text.length
3802
+ found[i].text = text.slice(found[i].bodyFrom, to).trim()
3803
+ }
3804
+ return found
3805
+ }
3806
+
3807
+ function extraBodySections(body, managedNames) {
3808
+ return parseBodySections(body)
3809
+ .filter(function (s) { return !managedNames.includes(s.name) })
3810
+ .map(function (s) { return { name: s.name, text: s.text } })
3811
+ }
3812
+
3813
+ function parseAppTitle(title) {
3814
+ const t = String(title || '')
3815
+ const mQ = /问题\s*(\S+)/.exec(t)
3816
+ const mD = /方向\s*(\S+)/.exec(t)
3817
+ // `at` = 第一段(时间戳)。取第一个 '|' 之前的部分。
3818
+ const at = t.split('|')[0].trim()
3819
+ return { at: at, 问题: mQ ? mQ[1] : '', 方向: mD ? mD[1] : '' }
3820
+ }
3821
+
3822
+ function parseMethodMd(id, text) {
3823
+ const { head, body } = splitHeader(text)
3824
+ const a = parseAnchors(head)
3825
+ const apps = parseEntries(body, /^###\s*应用\s*\d+|(.*?)$/).map(function (e) {
3826
+ const t = parseAppTitle(e.title)
3827
+ return { at: t.at, 问题: t.问题, 方向: t.方向, text: e.text }
3828
+ })
3829
+ // 改进历史必须真正解析回来:此前硬编码 [] 导致每次 compose 都写成占位符,
3830
+ // 于是"下一次用到该方法"就把代理沉淀的改进历史静默销毁(违反「正文只追加,不覆盖」)。
3831
+ const improvements = parseEntries(body, /^###\s*v(\d+)\s*((.*?))\s*$/).map(function (e) {
3832
+ return { v: Number(e.title) || 0, 原因: e.status || '', text: e.text }
3833
+ })
3834
+ return {
3835
+ id: id, 标题: a['标题'] || id, 类型: a['类型'] || '方法', 状态: a['状态'] || '经验',
3836
+ 可信断言: safeJson(a['可信断言'], []), 上级体系: safeJson(a['上级体系'], []), 子方法: safeJson(a['子方法'], []), 相关: safeJson(a['相关'], []),
3837
+ 适用场景: a['适用场景'] || '', 核心内容: section(body, '核心内容'), 定义与记号: section(body, '定义与记号'),
3838
+ applications: apps, improvements: improvements, 来源: a['来源'] || '',
3839
+ extraSections: extraBodySections(body, ['核心内容', '定义与记号', '应用记录', '改进历史']),
3840
+ }
3841
+ }
3842
+
3843
+ function sanitizeToolFilter(filter, known) {
3844
+ if (!filter || !(known instanceof Set) || known.size === 0) return filter
3845
+ const out = {}
3846
+ for (const key of ['allow', 'deny']) {
3847
+ const list = filter[key]
3848
+ if (!Array.isArray(list)) continue
3849
+ const kept = list.filter(function (n) { return known.has(String(n).trim()) })
3850
+ if (kept.length > 0) out[key] = kept
3851
+ }
3852
+ return (out.allow || out.deny) ? out : undefined
3853
+ }
3854
+
3855
+ function registeredToolsFromError(message) {
3856
+ const m = /known global tools:\s*([^]*)$/.exec(String(message || ''))
3857
+ if (!m) return undefined
3858
+ const names = m[1].split(',').map(function (s) { return s.trim() }).filter(Boolean)
3859
+ return names.length > 0 ? new Set(names) : undefined
3860
+ }
@@ -610,7 +610,7 @@ VibeMath/ # 工作区级
610
610
  | 4. 方法库 | 方法卡+体系层级、应用记录自动追加、`new_inventions` 上报、Method Keeper 提炼、经验条目可信约束、项目→全局晋升 | 上报→沉淀→体系化链路通;经验条目不被当定理;晋升走门 |
611
611
  | 5. 控制面 | 参数/工具/命令、人工干预门(计划审批/裁决/方法晋升)、汇报、提示词全量 | manual 门挂起与 decide 放行;push 汇报触发 |
612
612
  | 6. 多会话 | per-session 隔离 + 项目锁 + 进程纪元 | 双会话并行各跑各的项目;resume 0 误中断 |
613
- | 7. 文档与迁移 | README(v3 为主推)、可选 v2→v3 迁移工具(JSON 项目 → md 骨架) | 迁移后项目可被 v3 调度器索引与续跑 |
613
+ | 7. 文档与迁移 | README(v3 为经典)、可选 v2→v3 迁移工具(JSON 项目 → md 骨架) | 迁移后项目可被 v3 调度器索引与续跑 |
614
614
 
615
615
  ---
616
616
 
@@ -634,7 +634,7 @@ VibeMath/ # 工作区级
634
634
  3. **Verified 采用"调度器生成副本"**(Propos 保留历史,Verified 为定论只读版本),而非原位删除/移动。
635
635
  4. **规划代理输出 JSON**(机器接口),知识才用 md——调度器需要可校验的动作流,JSON 是最低成本的机器契约;md 的自由叙述留给知识对象。
636
636
  5. **软规范取折中**(锚点行强制 + 正文自由),而非完全零规范——否则调度器无法可靠索引;锚点仅 4~7 行,符合"不设较高严格规范"。
637
- 6. **v3 作为新 preset id** `vibe-math-v3` 并入现有 npm 包 `dsh-vibe-math`(与 v2/v4 同包多预设),版本发布策略沿用(README 标注 v2 与 v3 同级主推、按需选择;老的 `vibe-math-v1` 已于 v2.0.0 移除,本包仅含 v2/v3/v4)。
637
+ 6. **v3 作为新 preset id** `vibe-math-v3` 并入现有 npm 包 `dsh-vibe-math`(与 v2/v4 同包多预设),版本发布策略沿用(README 标注四者同级、v2 与 v3 为经典;老的 `vibe-math-v1` 已于 v2.0.0 移除,本包仅含 v2/v3/v4/v5)。
638
638
  7. **v2 审计发现的两个缺陷**(flat 高置信分歧误判 0.5、引用被证伪卡片)在 v3 默认修复(§8.2)。
639
639
  8. **方法库收录范围**:不限解题技巧——任何过程中发明的/经验性总结出的有价值理论体系、框架、工具、方法、思想、范式、技巧均可沉淀(§7.1);允许经验性条目(§7.4);条目可组成体系层级并跨项目持续扩充(§7.3)。
640
640