dsh-vibe-math 2.4.0 → 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.
- package/README.en.md +15 -15
- package/README.md +16 -16
- package/SECURITY.md +56 -0
- package/cordis.patch.yml +5 -5
- package/docs/AUDIT-CHECKLIST.md +499 -429
- package/docs/release-notes/RELEASE-NOTES-2.4.1.md +129 -0
- package/package.json +5 -3
- package/prompt-corpus-persona/persona-corpus.json +2 -2
- package/prompt-corpus-persona/persona-corpus.md +2 -2
- package/vibe-math-v2/agent.cordis.yml +5 -5
- package/vibe-math-v2/preset.yml +1 -1
- package/vibe-math-v2/vibe-math-v2.js +105 -62
- package/vibe-math-v3/vibe-math-v3.js +207 -148
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +2 -2
- package/vibe-math-v4/vibe-math-v4.js +83 -38
- package/vibe-math-v5/vibe-math-v5.js +94 -55
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Vibe Math V2 — host plugin implementing the
|
|
2
|
-
// ("
|
|
1
|
+
// Vibe Math V2 — host plugin implementing the classic-architecture spec
|
|
2
|
+
// ("vibe-math-v2/实现方案.md"): probability-driven scheduling over a
|
|
3
3
|
// problem list (qs/qs.json) and a proposition knowledge base (Propos/),
|
|
4
4
|
// multi-agent independent review → debate → consensus verification, with
|
|
5
5
|
// checkpoint resume, manual intervention, and file/push progress reporting.
|
|
@@ -137,16 +137,10 @@ export function apply(ctx) {
|
|
|
137
137
|
|
|
138
138
|
// ================= helpers =================
|
|
139
139
|
function textBlock(t) { return { type: 'text', text: String(t) } }
|
|
140
|
-
function now() { return Date.now() }
|
|
141
|
-
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 }
|
|
142
|
-
function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
|
|
143
|
-
function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
|
|
144
140
|
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 '.' }
|
|
145
141
|
function vibeRoot() { return (workspaceRoot() + '/VibeMath').replace(/\\/g, '/') }
|
|
146
142
|
function projectRoot(slug) { return vibeRoot() + '/Projects/' + slug }
|
|
147
143
|
function frameworkRoot() { return projectRoot(currentProject) }
|
|
148
|
-
function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
|
|
149
|
-
function safeId(s) { return String(s == null ? 'anon' : s).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80) || 'anon' }
|
|
150
144
|
let warnedNoPolicy = false
|
|
151
145
|
function warnNoPolicyOnce() { if (!warnedNoPolicy) { warnedNoPolicy = true; console.error('vibe-math-v2: sandboxPolicy unavailable; writes go out with no explicit policy') } }
|
|
152
146
|
// Sandbox fence for our own writes. The `resolve({})` fallback is a last resort and is
|
|
@@ -155,33 +149,6 @@ export function apply(ctx) {
|
|
|
155
149
|
// which is not necessarily this session's workspace — a silently different fence.
|
|
156
150
|
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-v2: 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 }
|
|
157
151
|
function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
|
|
158
|
-
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() }
|
|
159
|
-
function parseJson(text) {
|
|
160
|
-
if (typeof text !== 'string') return undefined
|
|
161
|
-
const tryObj = function (s) { try { const v = JSON.parse(s); return (v && typeof v === 'object' && !Array.isArray(v)) ? v : undefined } catch (e) { return undefined } }
|
|
162
|
-
const fenceRe = /```(?:json)?[ \t]*([\s\S]*?)```/gi
|
|
163
|
-
let m
|
|
164
|
-
while ((m = fenceRe.exec(text)) !== null) { const obj = tryObj(m[1].trim()); if (obj !== undefined) return obj }
|
|
165
|
-
const whole = tryObj(text.trim()); if (whole !== undefined) return whole
|
|
166
|
-
let best = undefined; let bestLen = -1
|
|
167
|
-
for (let start = 0; start < text.length; start++) {
|
|
168
|
-
if (text[start] !== '{') continue
|
|
169
|
-
let depth = 0, inStr = false, esc = false, end = -1
|
|
170
|
-
for (let i = start; i < text.length; i++) {
|
|
171
|
-
const c = text[i]
|
|
172
|
-
if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue }
|
|
173
|
-
if (c === '"') { inStr = true; continue }
|
|
174
|
-
if (c === '{') depth++
|
|
175
|
-
else if (c === '}') { depth--; if (depth === 0) { end = i; break } }
|
|
176
|
-
}
|
|
177
|
-
if (end === -1) continue
|
|
178
|
-
const obj = tryObj(text.slice(start, end + 1))
|
|
179
|
-
if (obj !== undefined && (end - start + 1) > bestLen) { best = obj; bestLen = end - start + 1 }
|
|
180
|
-
}
|
|
181
|
-
return best
|
|
182
|
-
}
|
|
183
|
-
function safeJson(v, fb) { if (v == null || v === '') return fb; try { return JSON.parse(v) } catch (e) { return fb } }
|
|
184
|
-
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 }
|
|
185
152
|
|
|
186
153
|
// ================= parameter schema =================
|
|
187
154
|
const PARAM_SCHEMA = [
|
|
@@ -996,7 +963,6 @@ export function apply(ctx) {
|
|
|
996
963
|
L.push('')
|
|
997
964
|
await writeText('Formal/TODO.md', L.join('\n'))
|
|
998
965
|
}
|
|
999
|
-
function fmtTime(ms) { try { return new Date(Number(ms) || now()).toISOString().replace('T', ' ').slice(0, 19) } catch (e) { return '' } }
|
|
1000
966
|
async function rebuildLeanLibIndexes() {
|
|
1001
967
|
// 扫描**不执行**工具链:每次问"有什么可复用"就跑一遍 lean 既慢又出人意料。
|
|
1002
968
|
// 每个对象的运行结果存在对象记录里,显示在 Formal/Index.md。
|
|
@@ -1187,15 +1153,6 @@ export function apply(ctx) {
|
|
|
1187
1153
|
|
|
1188
1154
|
// progress:结构化 JSON 对象(旧数据可能是 JSON 字符串,两者兼容解析)。
|
|
1189
1155
|
// 注意:必须保证返回对象含 directions 数组(晋升/判断/子问题等 progress 可能只有来源/说明等字段)。
|
|
1190
|
-
function parseProgress(q) {
|
|
1191
|
-
const raw = (q && q.progress) || null
|
|
1192
|
-
let p = null
|
|
1193
|
-
if (raw && typeof raw === 'object' && !Array.isArray(raw)) p = raw
|
|
1194
|
-
else p = safeJson(raw, null)
|
|
1195
|
-
if (!p || typeof p !== 'object' || Array.isArray(p)) return { directions: [], experience: '' }
|
|
1196
|
-
if (!Array.isArray(p.directions)) p.directions = []
|
|
1197
|
-
return p
|
|
1198
|
-
}
|
|
1199
1156
|
async function saveProgress(qid, progObj) { const qs = await getQs(); const q = qs.find(function (x) { return x.id === qid }); if (!q) return; q.progress = progObj; await writeQs(qs) }
|
|
1200
1157
|
|
|
1201
1158
|
// ================= data layer: Propos =================
|
|
@@ -1322,24 +1279,7 @@ export function apply(ctx) {
|
|
|
1322
1279
|
* own rejection message, which lists every registered global tool, so this
|
|
1323
1280
|
* never guesses. Returns undefined when nothing usable remains.
|
|
1324
1281
|
*/
|
|
1325
|
-
function sanitizeToolFilter(filter, known) {
|
|
1326
|
-
if (!filter || !(known instanceof Set) || known.size === 0) return filter
|
|
1327
|
-
const out = {}
|
|
1328
|
-
for (const key of ['allow', 'deny']) {
|
|
1329
|
-
const list = filter[key]
|
|
1330
|
-
if (!Array.isArray(list)) continue
|
|
1331
|
-
const kept = list.filter(function (n) { return known.has(String(n).trim()) })
|
|
1332
|
-
if (kept.length > 0) out[key] = kept
|
|
1333
|
-
}
|
|
1334
|
-
return (out.allow || out.deny) ? out : undefined
|
|
1335
|
-
}
|
|
1336
1282
|
/** The host names the offending tools and then lists the registered ones. */
|
|
1337
|
-
function registeredToolsFromError(message) {
|
|
1338
|
-
const m = /known global tools:\s*([^]*)$/.exec(String(message || ''))
|
|
1339
|
-
if (!m) return undefined
|
|
1340
|
-
const names = m[1].split(',').map(function (s) { return s.trim() }).filter(Boolean)
|
|
1341
|
-
return names.length > 0 ? new Set(names) : undefined
|
|
1342
|
-
}
|
|
1343
1283
|
function buildToolFilter(role) { const allow = role === 'solver' ? params.solverToolAllow : role === 'verifier' ? params.verifierToolAllow : undefined; const deny = role === 'solver' ? params.solverToolDeny : role === 'verifier' ? params.verifierToolDeny : undefined; const net = role === 'solver' ? params.solverAllowNetwork : role === 'verifier' ? params.verifierAllowNetwork : undefined; const scr = role === 'solver' ? params.solverAllowScripts : role === 'verifier' ? params.verifierAllowScripts : undefined; let a = Array.isArray(allow) ? allow.slice() : []; let d = Array.isArray(deny) ? deny.slice() : []; if (net === false) d = d.concat(NETWORK_TOOLS); else if (net === true && a.length > 0) a = a.concat(NETWORK_TOOLS); if (scr === false) d = d.concat(SCRIPT_TOOLS); else if (scr === true && a.length > 0) a = a.concat(SCRIPT_TOOLS); const f = {}; if (a.length > 0) f.allow = a; if (d.length > 0) f.deny = d; return (f.allow || f.deny) ? f : undefined }
|
|
1344
1284
|
async function spawnChild(label, promptText, meta) {
|
|
1345
1285
|
const request = { prompt: [textBlock(promptText)], parent: rootAgent, agentOptions: childAgentOptions() }
|
|
@@ -2622,3 +2562,106 @@ export function apply(ctx) {
|
|
|
2622
2562
|
// tick timer (registered once; ticks every running session at its own pace)
|
|
2623
2563
|
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) })
|
|
2624
2564
|
}
|
|
2565
|
+
|
|
2566
|
+
// ---- test seam: pure, stateless helpers --------------------------------
|
|
2567
|
+
// These helpers were declared inside `apply()` and are now declared at module scope, so
|
|
2568
|
+
// `apply()` closes over exactly the same function objects this export hands out. The audit
|
|
2569
|
+
// suites therefore exercise the REAL implementations by importing this module, instead of
|
|
2570
|
+
// extracting source text and compiling function bodies through the Function constructor
|
|
2571
|
+
// (dynamic code execution, rejected by the plugin-catalog security scan as
|
|
2572
|
+
// DANGEROUS_DYNAMIC_EXECUTION).
|
|
2573
|
+
//
|
|
2574
|
+
// Contract: no member may touch `ctx`, session state or mutable module state. Most are pure;
|
|
2575
|
+
// three are deliberately non-deterministic (`uuid`/`shortId` use Math.random, `fmtTime` falls back
|
|
2576
|
+
// to the clock) and `parseProgress` normalises the object it is handed in place (pre-existing).
|
|
2577
|
+
// Nothing here is used by the plugin at runtime except through `apply()`, and behaviour is
|
|
2578
|
+
// byte-identical to the previous in-`apply` declarations.
|
|
2579
|
+
export const __testHelpers = {
|
|
2580
|
+
uuid,
|
|
2581
|
+
shortId,
|
|
2582
|
+
clamp01,
|
|
2583
|
+
slugify,
|
|
2584
|
+
safeId,
|
|
2585
|
+
blocksToText,
|
|
2586
|
+
parseJson,
|
|
2587
|
+
safeJson,
|
|
2588
|
+
stripJsonComments,
|
|
2589
|
+
fmtTime,
|
|
2590
|
+
parseProgress,
|
|
2591
|
+
sanitizeToolFilter,
|
|
2592
|
+
registeredToolsFromError,
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
function now() { return Date.now() }
|
|
2596
|
+
|
|
2597
|
+
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 }
|
|
2598
|
+
|
|
2599
|
+
function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
|
|
2600
|
+
|
|
2601
|
+
function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
|
|
2602
|
+
|
|
2603
|
+
function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
|
|
2604
|
+
|
|
2605
|
+
function safeId(s) { return String(s == null ? 'anon' : s).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80) || 'anon' }
|
|
2606
|
+
|
|
2607
|
+
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() }
|
|
2608
|
+
|
|
2609
|
+
function parseJson(text) {
|
|
2610
|
+
if (typeof text !== 'string') return undefined
|
|
2611
|
+
const tryObj = function (s) { try { const v = JSON.parse(s); return (v && typeof v === 'object' && !Array.isArray(v)) ? v : undefined } catch (e) { return undefined } }
|
|
2612
|
+
const fenceRe = /```(?:json)?[ \t]*([\s\S]*?)```/gi
|
|
2613
|
+
let m
|
|
2614
|
+
while ((m = fenceRe.exec(text)) !== null) { const obj = tryObj(m[1].trim()); if (obj !== undefined) return obj }
|
|
2615
|
+
const whole = tryObj(text.trim()); if (whole !== undefined) return whole
|
|
2616
|
+
let best = undefined; let bestLen = -1
|
|
2617
|
+
for (let start = 0; start < text.length; start++) {
|
|
2618
|
+
if (text[start] !== '{') continue
|
|
2619
|
+
let depth = 0, inStr = false, esc = false, end = -1
|
|
2620
|
+
for (let i = start; i < text.length; i++) {
|
|
2621
|
+
const c = text[i]
|
|
2622
|
+
if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue }
|
|
2623
|
+
if (c === '"') { inStr = true; continue }
|
|
2624
|
+
if (c === '{') depth++
|
|
2625
|
+
else if (c === '}') { depth--; if (depth === 0) { end = i; break } }
|
|
2626
|
+
}
|
|
2627
|
+
if (end === -1) continue
|
|
2628
|
+
const obj = tryObj(text.slice(start, end + 1))
|
|
2629
|
+
if (obj !== undefined && (end - start + 1) > bestLen) { best = obj; bestLen = end - start + 1 }
|
|
2630
|
+
}
|
|
2631
|
+
return best
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
function safeJson(v, fb) { if (v == null || v === '') return fb; try { return JSON.parse(v) } catch (e) { return fb } }
|
|
2635
|
+
|
|
2636
|
+
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 }
|
|
2637
|
+
|
|
2638
|
+
function fmtTime(ms) { try { return new Date(Number(ms) || now()).toISOString().replace('T', ' ').slice(0, 19) } catch (e) { return '' } }
|
|
2639
|
+
|
|
2640
|
+
function parseProgress(q) {
|
|
2641
|
+
const raw = (q && q.progress) || null
|
|
2642
|
+
let p = null
|
|
2643
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) p = raw
|
|
2644
|
+
else p = safeJson(raw, null)
|
|
2645
|
+
if (!p || typeof p !== 'object' || Array.isArray(p)) return { directions: [], experience: '' }
|
|
2646
|
+
if (!Array.isArray(p.directions)) p.directions = []
|
|
2647
|
+
return p
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
function sanitizeToolFilter(filter, known) {
|
|
2651
|
+
if (!filter || !(known instanceof Set) || known.size === 0) return filter
|
|
2652
|
+
const out = {}
|
|
2653
|
+
for (const key of ['allow', 'deny']) {
|
|
2654
|
+
const list = filter[key]
|
|
2655
|
+
if (!Array.isArray(list)) continue
|
|
2656
|
+
const kept = list.filter(function (n) { return known.has(String(n).trim()) })
|
|
2657
|
+
if (kept.length > 0) out[key] = kept
|
|
2658
|
+
}
|
|
2659
|
+
return (out.allow || out.deny) ? out : undefined
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
function registeredToolsFromError(message) {
|
|
2663
|
+
const m = /known global tools:\s*([^]*)$/.exec(String(message || ''))
|
|
2664
|
+
if (!m) return undefined
|
|
2665
|
+
const names = m[1].split(',').map(function (s) { return s.trim() }).filter(Boolean)
|
|
2666
|
+
return names.length > 0 ? new Set(names) : undefined
|
|
2667
|
+
}
|
|
@@ -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
|
|
@@ -3799,3 +3651,210 @@ export function apply(ctx) {
|
|
|
3799
3651
|
// tick timer (registered once; ticks every running session at its own pace)
|
|
3800
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) })
|
|
3801
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
|
|
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
|
|
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
|
|