dsh-vibe-math 2.2.2 → 2.3.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/AUDIT-CHECKLIST.md +61 -3
- package/README.md +119 -1
- package/RELEASE-NOTES-2.3.0.md +207 -0
- package/RELEASE-NOTES-2.3.1.md +134 -0
- package/audit-formal-sensitivity.mjs +333 -0
- package/audit-persona-sensitivity.mjs +249 -0
- package/audit-persona-surface.test.mjs +349 -0
- package/audit-v5-integrity.mjs +43 -2
- package/audit-v5-sensitivity.mjs +77 -6
- package/docs/formal-verification.md +401 -0
- package/docs/generate_framework_diagram_v5.mjs +22 -16
- package/docs/test-timing.md +79 -0
- package/formal-verify-v2.test.mjs +951 -0
- package/formal-verify-v3.test.mjs +1031 -0
- package/formal-verify-v4.test.mjs +882 -0
- package/formal-verify-v5.test.mjs +598 -0
- package/package.json +22 -2
- package/prompt-corpus-persona/persona-corpus.json +32 -0
- package/prompt-corpus-persona/persona-corpus.md +674 -0
- package/prompt-corpus-v2/formal-verify-v2.json +394 -0
- package/prompt-corpus-v2/formal-verify-v2.md +4250 -0
- package/prompt-corpus-v3/formal-verify-v3.json +382 -0
- package/prompt-corpus-v3/formal-verify-v3.md +3843 -0
- package/prompt-corpus-v4/formal-verify-v4.json +84 -0
- package/prompt-corpus-v4/formal-verify-v4.md +255 -0
- package/prompt-corpus-v5/prompt-corpus-v5.json +109 -5
- package/prompt-corpus-v5/prompt-corpus-v5.md +653 -109
- package/prompt-v5-integrity.test.mjs +1158 -984
- package/run-tests.mjs +99 -0
- package/vibe-math-v2/agent.cordis.yml +40 -2
- package/vibe-math-v2/vibe-math-v2.js +811 -21
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +218 -1
- package/vibe-math-v3/agent.cordis.yml +46 -2
- package/vibe-math-v3/vibe-math-v3.js +810 -21
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +104 -2
- package/vibe-math-v4/agent.cordis.yml +46 -4
- package/vibe-math-v4/vibe-math-v4.js +744 -15
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +255 -0
- package/vibe-math-v5/agent.cordis.yml +41 -5
- package/vibe-math-v5/vibe-math-v5.js +621 -9
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +131 -4
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +57 -0
- package//347/244/272/344/276/213/345/233/276//346/241/206/346/236/266/345/233/276-v5.svg +51 -46
|
@@ -150,6 +150,11 @@ export function apply(ctx) {
|
|
|
150
150
|
methodAutoPromote: false, // 项目级方法自动晋升全局库(false = 人工门)
|
|
151
151
|
indexAutoRebuild: true, // 每次写盘后自动重建索引(false = 手动 vibe_math_index)
|
|
152
152
|
projectLockTimeoutMs: 60000, // 项目锁等待超时
|
|
153
|
+
// ---- Lean 形式化验证(契约:docs/formal-verification.md,四架构同名同语义)----
|
|
154
|
+
formalVerify: 'off', // off | encourage | require(三档开关,见 实现方案.md §10.1)
|
|
155
|
+
leanCommand: 'lean', // 要执行的 Lean 可执行文件(例:'lake')
|
|
156
|
+
leanArgs: [], // 插在文件名之前的附加参数(例:['env','lean'] 配 leanCommand='lake')
|
|
157
|
+
leanTimeoutMs: 120000, // 单次 Lean 运行超时上限(毫秒,正整数)
|
|
153
158
|
}
|
|
154
159
|
let params = Object.assign({}, DEFAULT_PARAMS)
|
|
155
160
|
let scheduler = { running: false, startedAt: 0, lastCheckpoint: 0, gate: null } // activeCount 由 activeCount() 从 agentRegistry 推导(防漂移,同 v2)
|
|
@@ -179,6 +184,16 @@ export function apply(ctx) {
|
|
|
179
184
|
let projectLock = { sessionId: '', at: 0 }
|
|
180
185
|
let lastIndexWrite = 0 // State/index.json 写入节流(每 5s 至多一次;工具/init 强制时立即)
|
|
181
186
|
let archivedJ = {} // qid -> [md 段]:重派生时被替换方向的 journal 归档(论文式历史保留)
|
|
187
|
+
/**
|
|
188
|
+
* Lean 形式化记录(契约 docs/formal-verification.md §4/§9)。
|
|
189
|
+
*
|
|
190
|
+
* v3 没有会话投影,所以每个对象的形式化记录落在**自己的持久状态** `State/formal.json`
|
|
191
|
+
* (与其它 State/*.json 同样的 readJson/writeJson 通道),按对象 id 索引,跨 resume 存活:
|
|
192
|
+
* records: { <对象id>: {status, file, proof, decision, note, run, updatedAt} }
|
|
193
|
+
* todo: [{id, at, why, verdict, project}] —— require 模式下被搁置的定论
|
|
194
|
+
* libRuns: { 'Lib/<名>.lean' | 'Proved/<名>.lean': {ok, exitCode, ms, at} } —— 索引的「最近运行」列
|
|
195
|
+
*/
|
|
196
|
+
let formalState = { records: {}, todo: [], libRuns: {} }
|
|
182
197
|
|
|
183
198
|
// ================= helpers =================
|
|
184
199
|
function textBlock(t) { return { type: 'text', text: String(t) } }
|
|
@@ -291,6 +306,10 @@ export function apply(ctx) {
|
|
|
291
306
|
{ name: 'methodAutoPromote', type: 'boolean', description: '项目级方法自动晋升全局库(false = 人工门)', suggestion: false },
|
|
292
307
|
{ name: 'indexAutoRebuild', type: 'boolean', description: '每次写盘后自动重建索引(false = 手动 vibe_math_index)', suggestion: true },
|
|
293
308
|
{ name: 'projectLockTimeoutMs', type: 'integer', description: '项目锁等待超时(毫秒)', suggestion: 60000 },
|
|
309
|
+
{ name: 'formalVerify', type: 'enum', options: ['off', 'encourage', 'require'], description: 'Lean 形式化验证:off = 不额外要求(默认,提示词里不出现任何 Lean 内容);encourage = 鼓励按实现难度形式化,Lean 通过后验证转为忠实性审查;require = 同上并加门禁:真/假定论必须先达到 Lean 已通过 或 已记录显式阻塞原因,否则记为未定论(formal-required)并进「形式化待办」', suggestion: 'off' },
|
|
310
|
+
{ name: 'leanCommand', type: 'string', description: 'Lean 可执行文件(例:lean / lake;配合 leanArgs=[env,lean] 用 lake)', suggestion: 'lean' },
|
|
311
|
+
{ name: 'leanArgs', type: 'string[]', description: '插在 .lean 文件名之前的附加参数', suggestion: [] },
|
|
312
|
+
{ name: 'leanTimeoutMs', type: 'integer', description: '单次 Lean 运行的超时上限(毫秒,非正数回退默认)', suggestion: 120000 },
|
|
294
313
|
]
|
|
295
314
|
|
|
296
315
|
// ================= fs (adapted to DSH 0.1.1: resolve returns {targetKey, displayPath}) =================
|
|
@@ -364,7 +383,12 @@ export function apply(ctx) {
|
|
|
364
383
|
return { ok: outcome.exitCode === 0, exitCode: outcome.exitCode }
|
|
365
384
|
} catch (e) { return { ok: false, error: String((e && e.message) || e) } }
|
|
366
385
|
}
|
|
367
|
-
|
|
386
|
+
/**
|
|
387
|
+
* 目录骨架。Lean 形式化(契约 §3)要求项目内 `Formal/`(形式化工作文件 + Index.md/TODO.md)
|
|
388
|
+
* 与 `Verified/Lean/`(归档证明,与定论卡片同处 Verified/,一眼可见"这条结论的证明在哪"),
|
|
389
|
+
* 以及**项目树之外**的全局可复用库 `<VibeMath 根>/Formal/{Lib,Proved}`(跨项目复用是核心收益)。
|
|
390
|
+
*/
|
|
391
|
+
async function ensureDirs() { const base = frameworkRoot(); const dirs = ['Problems', 'Progress', 'Propos', 'Methods', 'Verified/命题', 'Verified/问题', 'Verified/Lean', 'Formal', 'Reliable', 'Notes', 'Logs/Verification', 'Logs/Plans', 'State']; const paths = [vibeRoot() + '/Projects', vibeRoot() + '/Methods', vibeRoot() + '/Formal/Lib', vibeRoot() + '/Formal/Proved'].concat(dirs.map(function (d) { return base + '/' + d })); return await runShell(mkdirCmd(paths)) }
|
|
368
392
|
async function removeFile(rel) { const base = frameworkRoot(); return await runShell(rmCmd(base + '/' + rel)) }
|
|
369
393
|
|
|
370
394
|
// ================= settings =================
|
|
@@ -372,7 +396,7 @@ export function apply(ctx) {
|
|
|
372
396
|
const out = {}
|
|
373
397
|
const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'directionsPerSolver', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries', 'planningHorizon', 'planMinIntervalMs', 'plannerMaxFails', 'methodKeepIntervalMs', 'methodKeepEvery', 'projectLockTimeoutMs']
|
|
374
398
|
const numFields = ['promoteValueThreshold']
|
|
375
|
-
const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny']
|
|
399
|
+
const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny', 'leanArgs']
|
|
376
400
|
const boolFields = ['plannerEnabled', 'methodAutoPromote', 'indexAutoRebuild']
|
|
377
401
|
for (const k of Object.keys(DEFAULT_PARAMS)) {
|
|
378
402
|
if (!(k in obj)) continue
|
|
@@ -386,6 +410,11 @@ export function apply(ctx) {
|
|
|
386
410
|
else if (k === 'reportMode') { out[k] = (v === 'file' || v === 'push' || v === 'both') ? v : DEFAULT_PARAMS[k] }
|
|
387
411
|
else if (k === 'priorityAdjust') { out[k] = (v === 'none' || v === 'deadend-deprioritize' || v === 'survival-map') ? v : DEFAULT_PARAMS[k] }
|
|
388
412
|
else if (k === 'proposPriorityAdjust') { out[k] = (v === 'none' || v === 'progress-graded') ? v : DEFAULT_PARAMS[k] }
|
|
413
|
+
// Lean 形式化(契约 §1):非法值一律回退默认,且**绝不**回退到更强的档位——
|
|
414
|
+
// 一个拼错的值若被当成 encourage/require,会让每个对象都被形式化要求卡住。
|
|
415
|
+
else if (k === 'formalVerify') { out[k] = (v === 'off' || v === 'encourage' || v === 'require') ? v : 'off' }
|
|
416
|
+
else if (k === 'leanCommand') { const s = String(v == null ? '' : v).trim(); out[k] = s || 'lean' }
|
|
417
|
+
else if (k === 'leanTimeoutMs') { const n = Number(v); out[k] = (Number.isFinite(n) && n > 0) ? Math.floor(n) : DEFAULT_PARAMS[k] }
|
|
389
418
|
else if (k === 'solverAllowNetwork' || k === 'verifierAllowNetwork' || k === 'solverAllowScripts' || k === 'verifierAllowScripts') { out[k] = (v === true || v === false || v === '') ? v : DEFAULT_PARAMS[k] }
|
|
390
419
|
else { out[k] = v }
|
|
391
420
|
}
|
|
@@ -528,6 +557,9 @@ export function apply(ctx) {
|
|
|
528
557
|
lines.push(anchorLine('ID', p.id))
|
|
529
558
|
lines.push(anchorLine('类型', '问题'))
|
|
530
559
|
lines.push(anchorLine('状态', p.状态 || '求解中'))
|
|
560
|
+
// 形式化锚点(契约 §6/§9.1):与「状态」同行区,让读者一眼看到这条结论的形式化强度。
|
|
561
|
+
// off 模式或尚无形式化记录时**不写这一行**(off 必须是真正的无操作)。
|
|
562
|
+
if (formalAnchorLine(p.id)) lines.push(anchorLine('形式化', formalAnchorLine(p.id)))
|
|
531
563
|
lines.push(anchorLine('优先级', p.优先级 == null ? 1 : p.优先级))
|
|
532
564
|
lines.push(anchorLine('依赖', JSON.stringify(p.依赖 || [])))
|
|
533
565
|
lines.push(anchorLine('被依赖', JSON.stringify(p.被依赖 || [])))
|
|
@@ -573,6 +605,8 @@ export function apply(ctx) {
|
|
|
573
605
|
lines.push(anchorLine('类型', '命题'))
|
|
574
606
|
lines.push(anchorLine('状态', p.状态 || '未定论'))
|
|
575
607
|
lines.push(anchorLine('概率', p.概率 == null ? 0.5 : p.概率))
|
|
608
|
+
// 形式化锚点(契约 §6/§9.1):紧邻 状态/概率,off 模式或状态为 none 时不写(无操作)。
|
|
609
|
+
if (formalAnchorLine(p.id)) lines.push(anchorLine('形式化', formalAnchorLine(p.id)))
|
|
576
610
|
lines.push(anchorLine('优先级', p.优先级 == null ? 1 : p.优先级))
|
|
577
611
|
lines.push(anchorLine('依赖', JSON.stringify(p.依赖 || [])))
|
|
578
612
|
if (p.价值关键性 != null) lines.push(anchorLine('价值/关键性', p.价值关键性))
|
|
@@ -695,6 +729,8 @@ export function apply(ctx) {
|
|
|
695
729
|
lines.push(anchorLine('类型', card.类型 || '命题'))
|
|
696
730
|
lines.push(anchorLine('结论', card.结论 === true ? '真' : (card.结论 === false ? '假' : '')))
|
|
697
731
|
lines.push(anchorLine('概率', card.概率))
|
|
732
|
+
// 结论卡片必须随信带上形式化强度:读者要能分辨"内核已核对"与"仅共识"(契约 §8)。
|
|
733
|
+
if (formalAnchorLine(card.id)) lines.push(anchorLine('形式化', formalAnchorLine(card.id)))
|
|
698
734
|
lines.push(anchorLine('分类', card.分类 || '未分类'))
|
|
699
735
|
lines.push(anchorLine('来源', card.来源 || ''))
|
|
700
736
|
lines.push(anchorLine('时间', fmtTime(card.时间)))
|
|
@@ -905,6 +941,14 @@ export function apply(ctx) {
|
|
|
905
941
|
const pl = await readJson('State/project_lock.json'); if (pl) projectLock = Object.assign({ sessionId: '', at: 0 }, pl)
|
|
906
942
|
const lp = await readJson('State/last_plan.json'); if (lp) lastPlanSummary = lp
|
|
907
943
|
const aj = await readJson('State/archived_journals.json'); if (aj && typeof aj === 'object') archivedJ = aj
|
|
944
|
+
const fm = await readJson('State/formal.json')
|
|
945
|
+
if (fm && typeof fm === 'object') {
|
|
946
|
+
const rec = (fm.records && typeof fm.records === 'object') ? fm.records : {}
|
|
947
|
+
// 只接受形状正确的记录:单个坏条目不该让整份形式化状态失效(与 loadKnowledgeBase 的容错一致)。
|
|
948
|
+
const clean = {}
|
|
949
|
+
for (const k of Object.keys(rec)) { const r = rec[k]; if (r && typeof r === 'object' && typeof r.status === 'string') clean[k] = r }
|
|
950
|
+
formalState = { records: clean, todo: Array.isArray(fm.todo) ? fm.todo.filter(function (t) { return t && t.id }) : [], libRuns: (fm.libRuns && typeof fm.libRuns === 'object') ? fm.libRuns : {} }
|
|
951
|
+
}
|
|
908
952
|
}
|
|
909
953
|
async function saveAll() {
|
|
910
954
|
await writeJson('State/scheduler_state.json', scheduler)
|
|
@@ -917,6 +961,8 @@ export function apply(ctx) {
|
|
|
917
961
|
await writeJson('State/method_log.json', methodLog)
|
|
918
962
|
await writeJson('State/project_lock.json', projectLock)
|
|
919
963
|
await writeJson('State/archived_journals.json', archivedJ)
|
|
964
|
+
// 未启用且从未产生任何形式化记录时不落这份状态文件(off 保持真正的无操作)。
|
|
965
|
+
if (formalOn() || Object.keys(formalState.records).length || formalState.todo.length || Object.keys(formalState.libRuns).length) await writeJson('State/formal.json', formalState)
|
|
920
966
|
if (lastPlanSummary) await writeJson('State/last_plan.json', lastPlanSummary)
|
|
921
967
|
scheduler.lastCheckpoint = now()
|
|
922
968
|
}
|
|
@@ -952,6 +998,8 @@ export function apply(ctx) {
|
|
|
952
998
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
953
999
|
queuedPlanActions: planQueue.length,
|
|
954
1000
|
recentActivity: activityLog.slice(-Math.min(30, Number(params.activityLogCap) || 100)),
|
|
1001
|
+
// Lean 形式化:模式 + 每个对象的状态 + 形式化待办(可读参数表在 params 里)。
|
|
1002
|
+
formal: formalSummary(),
|
|
955
1003
|
params: params,
|
|
956
1004
|
}
|
|
957
1005
|
}
|
|
@@ -981,6 +1029,18 @@ export function apply(ctx) {
|
|
|
981
1029
|
lines.push('- **' + q.id + '**:' + q.状态 + ',优先级 ' + q.优先级 + ',解法 ' + (q.solutions || []).length + ' 条' + dirInfo)
|
|
982
1030
|
}
|
|
983
1031
|
lines.push('')
|
|
1032
|
+
lines.push('## Lean 形式化')
|
|
1033
|
+
if (!formalOn()) {
|
|
1034
|
+
lines.push('- 未启用(`formalVerify` = off;可用 vibe_math_set_params 切到 encourage / require)')
|
|
1035
|
+
} else {
|
|
1036
|
+
const fs2 = formalSummary()
|
|
1037
|
+
lines.push('- 模式:' + fs2.mode + '(' + (fs2.mode === 'require' ? '强制:定论前必须有 Lean 通过或显式阻塞记录' : '鼓励:按实现难度自行决定') + ')')
|
|
1038
|
+
lines.push('- 已通过:' + (fs2.passed.join('、') || '(无)'))
|
|
1039
|
+
lines.push('- 已记录阻塞:' + (fs2.blocked.join('、') || '(无)'))
|
|
1040
|
+
lines.push('- 形式化待办:' + (fs2.todo.map(function (t) { return t.id }).join('、') || '(无)'))
|
|
1041
|
+
lines.push('- 可复用库:VibeMath/Formal/{Lib,Proved}/(跨项目)|本项目形式化:Formal/|归档证明:Verified/Lean/')
|
|
1042
|
+
}
|
|
1043
|
+
lines.push('')
|
|
984
1044
|
lines.push('## 最近活动')
|
|
985
1045
|
for (const e of activityLog.slice(-10)) lines.push('- ' + fmtTime(e.at) + ' [' + e.event + '] ' + e.detail)
|
|
986
1046
|
lines.push('')
|
|
@@ -1237,7 +1297,9 @@ export function apply(ctx) {
|
|
|
1237
1297
|
'Then propose 3-6 DIVERSE, mutually distinct solution directions (e.g. analytic method, constructive proof, contradiction, numeric approximation + limit passage, categorical abstraction, ...). ' +
|
|
1238
1298
|
'Record each direction with its core assumption and an initial feasibility estimate. Every direction must be self-contained: title / method / core_assumption written completely, defining every object they mention — no 断章取义.\n\n' +
|
|
1239
1299
|
'feasibility ∈ [0,1] = your estimate of the probability this direction leads to a full solution. Respond with ONLY a single JSON object in a ```json code fence (no prose outside it). Register the directions as metadata; the scheduler writes them into the research log:\n' +
|
|
1240
|
-
'{"meta":{"kind":"directions","qid":"<qid>","directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5}],"methods_used":[{"id":"m-...","效果":"<为何该方向借鉴它>","建议":"..."}],"new_inventions":[{"类型":"方法|工具|...","标题":"...","内容描述":"...","是否已入库":false}]}}'
|
|
1300
|
+
'{"meta":{"kind":"directions","qid":"<qid>","directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5}],"methods_used":[{"id":"m-...","效果":"<为何该方向借鉴它>","建议":"..."}],"new_inventions":[{"类型":"方法|工具|...","标题":"...","内容描述":"...","是否已入库":false}]}}' +
|
|
1301
|
+
// 顺手形式化(契约 §6.2)+ 回执字段(契约 §6.3)
|
|
1302
|
+
(formalOn() ? '\n' + formalWorkLine() + formalReplyNote() : '')
|
|
1241
1303
|
}
|
|
1242
1304
|
function rederivePrompt(q, prog) {
|
|
1243
1305
|
const prior = prog.map(function (d) {
|
|
@@ -1251,7 +1313,8 @@ export function apply(ctx) {
|
|
|
1251
1313
|
'\nQuantitatively analyze the historical progress, blocker causes, and feasibility decay of each prior direction. Discard directions already proven dead ends (unless a new tool/idea changes that). ' +
|
|
1252
1314
|
'Then deeply DERIVE 1-3 BRAND-NEW directions never tried before, each with a one-line motivation. Return the UNION of high-potential leftover directions and the brand-new directions (drop dead ends).\n\n' +
|
|
1253
1315
|
'feasibility ∈ [0,1]. Respond with ONLY a single JSON object in a ```json code fence (no prose outside it). Register the directions as metadata; the scheduler writes them into the research log:\n' +
|
|
1254
|
-
'{"meta":{"kind":"directions","qid":"<qid>","directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5}],"methods_used":[{"id":"m-...","效果":"...","建议":"..."}],"new_inventions":[{"类型":"方法|工具|...","标题":"...","内容描述":"...","是否已入库":false}]}}'
|
|
1316
|
+
'{"meta":{"kind":"directions","qid":"<qid>","directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5}],"methods_used":[{"id":"m-...","效果":"...","建议":"..."}],"new_inventions":[{"类型":"方法|工具|...","标题":"...","内容描述":"...","是否已入库":false}]}}' +
|
|
1317
|
+
(formalOn() ? '\n' + formalWorkLine() + formalReplyNote() : '')
|
|
1255
1318
|
}
|
|
1256
1319
|
function directionSummary(d) {
|
|
1257
1320
|
return 'id ' + d.id + '「' + d.title + '」method=' + d.method + ' | round=' + d.round + ' status=' + d.status +
|
|
@@ -1297,6 +1360,9 @@ export function apply(ctx) {
|
|
|
1297
1360
|
'CHANNEL B (your file tools are unavailable): put the content you would have written into __writes and carry the same meta:\n' +
|
|
1298
1361
|
'{"__writes":[{"path":"Progress/' + q.id + '/' + dir.id + '.md","content":"<完整本轮叙述>"}],"meta":{"kind":"solver","qid":"' + q.id + '","dirId":"' + dir.id + '",...同上 meta 字段...}}\n' +
|
|
1299
1362
|
'区分规则:methods_used 只能填**已存在的方法卡 ID**(m-…,来自 AVAILABLE METHODS 列表)——引用你自己刚想出的新方法/新技巧不属于 methods_used,请如实填入 new_inventions(它会由 Method Keeper 蒸馏建卡);不要把方法名/标题当 id 填进 methods_used。'
|
|
1363
|
+
// 顺手形式化(契约 §6.2):把常用/可复用的对象、假设、新定义沉淀到全局 Lean 库;
|
|
1364
|
+
// 回执里同样要带上 formal 难度判断字段(契约 §6.3)。
|
|
1365
|
+
if (formalOn()) head += '\n' + formalWorkLine() + formalReplyNote()
|
|
1300
1366
|
return head
|
|
1301
1367
|
}
|
|
1302
1368
|
function verifierTargetText(r) {
|
|
@@ -1305,6 +1371,7 @@ export function apply(ctx) {
|
|
|
1305
1371
|
return 'PROBLEM (id: ' + r.qid + '): ' + r.概述 + '\nSOLUTION TO CHECK:\n' + r.process
|
|
1306
1372
|
}
|
|
1307
1373
|
function verifierReviewPrompt(r) {
|
|
1374
|
+
const target = verifyTargetId(r)
|
|
1308
1375
|
return personaText('verifierPersona') + 'You are a STRICT peer reviewer verifying one mathematical object. Check it multiple times.\n\nTARGET (r: ' + r.kind + '):\n' + verifierTargetText(r) + '\n' +
|
|
1309
1376
|
knowledgeContextText('verifier') +
|
|
1310
1377
|
capabilitiesText('verifier') +
|
|
@@ -1312,10 +1379,14 @@ export function apply(ctx) {
|
|
|
1312
1379
|
'\nCalibration: 0.5 means "genuinely undecided — there is a real unresolved gap"; it is NOT a safe hedge, so do not default to 0.5. Give the number your honest confidence from the evidence actually supports.\n' +
|
|
1313
1380
|
'\n**Reason is MANDATORY and MUST be non-empty**: name the exact step you verified, or the potential counterexample / fatal flaw, or (for 0.5) the precise gap that blocks a decision. A Result with an empty Reason is non-contributory and will be ignored; never return {"Result":0.5} with no justification.\n' +
|
|
1314
1381
|
'\nCitations: facts may only be cited from Verified/ (or Propos/ 状态: 已验证·真/假). Never cite an unverified or refuted object as a fact — if you need a sub-claim of a refuted card, re-derive it yourself.\n' +
|
|
1382
|
+
// 形式化注入:模式与对象状态都在**构造提示词的这一刻**现算(运行中切档立刻生效)。
|
|
1383
|
+
// 若该对象已有通过的 Lean 证明,这一段把审查对象换成"忠实性",而不是让评审重做推导(契约 §6.1)。
|
|
1384
|
+
(formalOn() ? '\n' + formalPromptBlock(target) + '\n' : '') +
|
|
1315
1385
|
'\nIndependently output your initial review — ONLY a single JSON object in a ```json code fence, no prose outside it:\n' +
|
|
1316
|
-
'{"Result":0.5,"Reason":"<MANDATORY, non-empty: your detailed logic chain / potential counterexample / supporting evidence>"}'
|
|
1386
|
+
'{"Result":0.5,"Reason":"<MANDATORY, non-empty: your detailed logic chain / potential counterexample / supporting evidence>"' + formalJsonField(target) + '}'
|
|
1317
1387
|
}
|
|
1318
1388
|
function verifierDebatePrompt(r, transcript) {
|
|
1389
|
+
const target = verifyTargetId(r)
|
|
1319
1390
|
return personaText('verifierPersona') + 'You are one reviewer in a DEBATE ("交流群") about this object.\n\nTARGET:\n' + verifierTargetText(r) + '\n' +
|
|
1320
1391
|
knowledgeContextText('verifier') +
|
|
1321
1392
|
capabilitiesText('verifier') +
|
|
@@ -1323,8 +1394,10 @@ export function apply(ctx) {
|
|
|
1323
1394
|
'\nRespond to the others (agree / rebut / add new evidence, referencing earlier rounds if needed). If you changed your Result because of them, state the reason explicitly. ' +
|
|
1324
1395
|
'Remember: formal/notation-level flaws in an otherwise correct proof should lower confidence only slightly — a mathematically correct argument is not "uncertain" because of typos; near-consensus is not a deadlock. Undue swing to 0.5 is discouraged: a bare review merits 0.5 ONLY if there is a genuine undecidable gap, never as a hedge.\n' +
|
|
1325
1396
|
'\nReason is MANDATORY and MUST be non-empty; an empty-Reason result (esp. a bare 0.5) is ignored as non-contributory, so always justify your number.\n' +
|
|
1397
|
+
// 辩论轮同样现算:对象已 Lean 通过时,辩论的题目是**忠实性**,不是重新推导。
|
|
1398
|
+
(formalOn() ? '\n' + formalPromptBlock(target) + '\n' : '') +
|
|
1326
1399
|
'\nReply with ONLY a single JSON object in a ```json code fence, no prose outside it:\n' +
|
|
1327
|
-
'{"Result":0.5,"Reason":"<MANDATORY, non-empty: updated logic chain / counterexample / proof / refutation>","changed":"brief reason if you changed your Result, else null"}'
|
|
1400
|
+
'{"Result":0.5,"Reason":"<MANDATORY, non-empty: updated logic chain / counterexample / proof / refutation>","changed":"brief reason if you changed your Result, else null"' + formalJsonField(target) + '}'
|
|
1328
1401
|
}
|
|
1329
1402
|
function plannerPrompt(brief) {
|
|
1330
1403
|
return personaText('plannerPersona') + 'You are the SCHEDULING PLANNER of a multi-agent mathematical research system. Your job: autonomously choose the OPTIMAL schedule — you may lay out the NEXT ' + params.planningHorizon + ' agent-task calls in one plan (they will be executed in order, beyond-capacity ones queued for later ticks).\n\n' +
|
|
@@ -1346,6 +1419,9 @@ export function apply(ctx) {
|
|
|
1346
1419
|
knowledgeContextText('method-keeper') +
|
|
1347
1420
|
'\nRECENT WORK DIGEST:\n' + digest + '\n\n' +
|
|
1348
1421
|
'For each pending invention decide: create a NEW method card, or fold it into an EXISTING method (as an improvement). Only list 可信断言 for claims already verified (ids from Verified/) — everything else stays 经验 (experiential). You may propose 上级体系/子方法 links to organize methods into systems.\n' +
|
|
1422
|
+
// Method Keeper 的职责正是「沉淀可复用方法」,所以形式化的沉淀也归它:可复用的定义/假设
|
|
1423
|
+
// 进全局 Lib/,已成立的引理进 Proved/,让后续项目的证明直接 import 复用(契约 §6.2)。
|
|
1424
|
+
(formalOn() ? formalWorkLine() + '\n【方法沉淀 × Lean 形式化】除了方法卡,你沉淀的每个可复用对象 / 定义 / 假设都应当归档到全局 Lean 库(vibe_math_lean_archive kind=\'def\'),已成立的引理归档到 Proved/(kind=\'lemma\');归档时**连同定义与陈述一起写清**,方便后续直接 import。\n' : '') +
|
|
1349
1425
|
'OUTPUT CONTRACT — pick ONE channel. Write method cards into Markdown; only the created IDs, which cards were used, and improvements cross the machine reply.\n' +
|
|
1350
1426
|
'CHANNEL A (recommended, you can write files): write each method card into `Methods/<m-id>.md` (`# 方法|标题` + `- 标题/ID/类型/状态/可信断言/适用场景` + `## 核心内容`/`## 应用记录`/`## 改进历史`), then reply ONLY this metadata:\n' +
|
|
1351
1427
|
'{"meta":{"kind":"methods","used":[{"id":"m-...","效果":"...","建议":"..."}],"created":["m-xxx"],"improvements":[{"id":"m-...","改进内容":"...","原因":"..."}]}}\n' +
|
|
@@ -1459,32 +1535,51 @@ export function apply(ctx) {
|
|
|
1459
1535
|
for (const q of allProblems()) {
|
|
1460
1536
|
if ((q.solutions || []).some(function (s) { return s.prob === 1 })) {
|
|
1461
1537
|
const wasSolved = q.状态 === '已解决'
|
|
1462
|
-
|
|
1463
|
-
//
|
|
1464
|
-
if (!wasSolved
|
|
1538
|
+
// require 门禁(契约 §8):自报概率 1 是另一条"宣告定论"的入口,同样必须过门。
|
|
1539
|
+
// 不改变对象的既有权重/概率字段,只把结果记为未定论 + 形式化待办。
|
|
1540
|
+
if (!wasSolved && formalBlocksConclusion(q.id)) await deferForFormal(q.id, formalRequiredWhy(q.id), true)
|
|
1541
|
+
else {
|
|
1542
|
+
q.状态 = '已解决'; q.优先级 = 'never'
|
|
1543
|
+
// 先置状态再写卡(writeVerifiedProblemCardIfNeeded 依赖 状态=已解决 才能生成卡)
|
|
1544
|
+
if (!wasSolved) { changed = true; await writeVerifiedProblemCardIfNeeded(q) }
|
|
1545
|
+
}
|
|
1465
1546
|
}
|
|
1466
1547
|
}
|
|
1467
1548
|
for (const p of allPropos()) {
|
|
1468
1549
|
let pChanged = false
|
|
1469
1550
|
const proofOne = (p.proofs || []).some(function (x) { return x.prob === 1 })
|
|
1470
1551
|
const refuteOne = (p.refutes || []).some(function (x) { return x.prob === 1 })
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1552
|
+
const wouldConclude = (proofOne && p.概率 !== 1) || (refuteOne && p.概率 !== 0)
|
|
1553
|
+
if (wouldConclude && formalBlocksConclusion(p.id)) {
|
|
1554
|
+
// 门禁不通过:不写 概率/状态,不写 Verified/ 卡片,只记未定论 + 待办(幂等,不刷屏)。
|
|
1555
|
+
await deferForFormal(p.id, formalRequiredWhy(p.id), proofOne)
|
|
1556
|
+
} else {
|
|
1557
|
+
if (proofOne && p.概率 !== 1) { p.概率 = 1; p.状态 = '已验证·真'; pChanged = true }
|
|
1558
|
+
else if (refuteOne && p.概率 !== 0) { p.概率 = 0; p.状态 = '已验证·假'; pChanged = true }
|
|
1559
|
+
if ((p.概率 === 1 || p.概率 === 0) && p.优先级 !== 'never') { p.优先级 = 'never'; pChanged = true }
|
|
1560
|
+
if (p.概率 === 1 || p.概率 === 0) {
|
|
1561
|
+
if (await writeVerifiedPropositionCardIfNeeded(p)) pChanged = true
|
|
1562
|
+
// 关闭晋升/判断出的"僵尸"问题
|
|
1563
|
+
for (const q of allProblems()) {
|
|
1564
|
+
if (q.状态 === '已解决') continue
|
|
1565
|
+
if (q.判断命题 === p.id || (q.来源命题 === p.id) || ((q.来源 === 'promote' || q.来源 === 'judge') && q.来源与动机 && q.来源与动机.indexOf(p.id) !== -1)) { q.状态 = '已解决'; q.优先级 = 'never'; changed = true }
|
|
1566
|
+
}
|
|
1480
1567
|
}
|
|
1481
1568
|
}
|
|
1482
1569
|
if (pChanged) { await saveProposition(p); changed = true }
|
|
1483
1570
|
}
|
|
1484
1571
|
if (changed) { for (const q of allProblems()) await saveProblem(q); logActivity('update', 'status updates applied (probability-1 closures / verified cards)') }
|
|
1485
1572
|
}
|
|
1573
|
+
/** require 门禁的机器可读原因(写进 Formal/TODO.md 与 Formal/Index.md)。 */
|
|
1574
|
+
function formalRequiredWhy(target) {
|
|
1575
|
+
const rec = formalOf(target)
|
|
1576
|
+
return 'formal-required:尚未取得 Lean 形式化通过,也没有显式阻塞记录(当前状态 ' + (rec.status || 'none') + ')'
|
|
1577
|
+
}
|
|
1486
1578
|
async function writeVerifiedPropositionCardIfNeeded(p) {
|
|
1487
1579
|
if (p.概率 !== 1 && p.概率 !== 0) return false
|
|
1580
|
+
// 写 Verified 卡片是"宣告定论"的收口点:require 模式下没过门就绝不写(防御性双保险,
|
|
1581
|
+
// 正常路径已在 settleVerdict / processStatusUpdates 拦住)。
|
|
1582
|
+
if (formalBlocksConclusion(p.id)) return false
|
|
1488
1583
|
const proofs1 = (p.proofs || []).filter(function (x) { return x.prob === 1 })
|
|
1489
1584
|
const refutes1 = (p.refutes || []).filter(function (x) { return x.prob === 1 })
|
|
1490
1585
|
const parts = []
|
|
@@ -1495,6 +1590,7 @@ export function apply(ctx) {
|
|
|
1495
1590
|
}
|
|
1496
1591
|
async function writeVerifiedProblemCardIfNeeded(q) {
|
|
1497
1592
|
if (q.状态 !== '已解决') return false
|
|
1593
|
+
if (formalBlocksConclusion(q.id)) return false
|
|
1498
1594
|
const sols1 = (q.solutions || []).filter(function (s) { return s.prob === 1 })
|
|
1499
1595
|
const parts = []
|
|
1500
1596
|
for (let i = 0; i < sols1.length; i++) parts.push('【解法 #' + (i + 1) + '】' + (sols1[i].text || ''))
|
|
@@ -1505,6 +1601,12 @@ export function apply(ctx) {
|
|
|
1505
1601
|
async function writeVerifiedCardIfChanged(card) {
|
|
1506
1602
|
const rel = verifiedRel(card) // 必须与 saveVerified 用同一路径,否则读侧永远读不到已写出的卡
|
|
1507
1603
|
const existing = await readText(rel)
|
|
1604
|
+
// 最后一道闸门:require 模式下没有 passed/blocked 记录就不允许**新写** Verified 卡片。
|
|
1605
|
+
// 卡片已经存在(切到 require 之前就已定论)只做刷新,不算"新的定论",因此不记待办。
|
|
1606
|
+
if (formalBlocksConclusion(card.id)) {
|
|
1607
|
+
if (existing === undefined) await deferForFormal(card.id, formalRequiredWhy(card.id), card.结论 === true)
|
|
1608
|
+
return false
|
|
1609
|
+
}
|
|
1508
1610
|
const md = composeVerifiedMd(card)
|
|
1509
1611
|
const strip = function (s) { return String(s).split('\n').filter(function (l) { return l.indexOf('- 时间:') !== 0 }).join('\n').trim() }
|
|
1510
1612
|
if (existing !== undefined && strip(existing) === strip(md)) return false
|
|
@@ -1614,6 +1716,12 @@ export function apply(ctx) {
|
|
|
1614
1716
|
async function createVerifyTask(c) {
|
|
1615
1717
|
const rId = c.rId
|
|
1616
1718
|
if (verifyTaskBusy(rId)) return false
|
|
1719
|
+
// require 门禁的**防空转**(契约 §2「对象留在原库,可形式化后再次提议」):已经被记为
|
|
1720
|
+
// 「形式化待办」、而形式化又还没补齐的对象不再重复表决——重复表决只会一次又一次被同一道
|
|
1721
|
+
// 门拦下(同一 rId 每 tick 重建任务、每轮再派验证器),白白耗尽验证预算。一旦 passed /
|
|
1722
|
+
// blocked 落库,门禁条件满足,候选自然重新出现并继续验证(无需人工干预)。
|
|
1723
|
+
const gateTarget = String(c.pId || c.qid || '')
|
|
1724
|
+
if (formalBlocksConclusion(gateTarget) && formalTodo().some(function (t) { return t && t.id === gateTarget })) return false
|
|
1617
1725
|
tasks['verify:' + rId] = { id: 'verify:' + rId, type: 'verify', r: c, rId: rId, status: 'spawning', children: [], childResults: {}, history: [], round: 1, expectedCount: Math.max(2, params.verifierCount), createdAt: now() }
|
|
1618
1726
|
logActivity('verify', 'verification task created for ' + rId)
|
|
1619
1727
|
await saveAll()
|
|
@@ -1913,8 +2021,10 @@ export function apply(ctx) {
|
|
|
1913
2021
|
if (activeCount() >= params.maxParallelThreshold) break
|
|
1914
2022
|
const c = cands[i]
|
|
1915
2023
|
if (verifyTaskBusy(c.rId)) continue
|
|
1916
|
-
|
|
1917
|
-
|
|
2024
|
+
// 只有**真的建了任务**才吃掉本轮的名额:require 门禁的防空转会跳过待办对象,
|
|
2025
|
+
// 若照旧 `return`,排在这些对象后面的候选会被永久饿死(每次都轮到同一个被跳过的对象)。
|
|
2026
|
+
const created = await createVerifyTask(c)
|
|
2027
|
+
if (created) return // one per tick keeps scheduling simple
|
|
1918
2028
|
}
|
|
1919
2029
|
}
|
|
1920
2030
|
// 3) solve: explorer / solver spawns (manual → gate)
|
|
@@ -1961,6 +2071,9 @@ export function apply(ctx) {
|
|
|
1961
2071
|
async function handleExplorer(childId, meta, output) {
|
|
1962
2072
|
delete agentRegistry[childId]
|
|
1963
2073
|
const parsed = parseJson(output)
|
|
2074
|
+
// 回执里的 formal 字段(契约 §6.3):即使一次 Lean 工具都没调用,代理也必须能留下显式的
|
|
2075
|
+
// 形式化难度判断;meta.formal 与顶层 formal 两种写法都接受。
|
|
2076
|
+
if (parsed) await absorbFormalFromReply(parsed, childId)
|
|
1964
2077
|
// 新协议(代理直接写 md + sync_meta):__writes 落盘,meta.kind==='directions' 走元数据同步
|
|
1965
2078
|
if (parsed && ((Array.isArray(parsed.__writes) && parsed.__writes.length) || (parsed.meta && parsed.meta.kind === 'directions'))) {
|
|
1966
2079
|
await applyAgentWrites(parsed.__writes)
|
|
@@ -1985,6 +2098,7 @@ export function apply(ctx) {
|
|
|
1985
2098
|
async function handleSolver(childId, meta, output, stopReason) {
|
|
1986
2099
|
const qid = meta.qid; const dirId = meta.direction
|
|
1987
2100
|
const parsed = parseJson(output)
|
|
2101
|
+
if (parsed) await absorbFormalFromReply(parsed, childId)
|
|
1988
2102
|
// 新协议(代理直接写 md + sync_meta):__writes 落盘,meta.kind==='solver' 走元数据同步
|
|
1989
2103
|
if (parsed && ((Array.isArray(parsed.__writes) && parsed.__writes.length) || (parsed.meta && parsed.meta.kind === 'solver'))) {
|
|
1990
2104
|
delete agentRegistry[childId]
|
|
@@ -2288,6 +2402,637 @@ export function apply(ctx) {
|
|
|
2288
2402
|
}
|
|
2289
2403
|
}
|
|
2290
2404
|
|
|
2405
|
+
// ================= Lean 形式化验证 =================
|
|
2406
|
+
// 契约:docs/formal-verification.md(v2/v3/v4/v5 共用;实现方案.md §10.1/§11.1)。
|
|
2407
|
+
//
|
|
2408
|
+
// 这套机制要换掉的是**审查对象**,不是给代理加一道苦役:多代理交叉验证的本质是**共识**——
|
|
2409
|
+
// m 个人一致认为"这是对的"既排除不了共同误解,也排除不了共同漏掉的情形;Lean 把"我认为"
|
|
2410
|
+
// 换成"机器已核对",于是剩下的唯一不确定项收缩成一个人和代理都能有效审查的问题:
|
|
2411
|
+
//
|
|
2412
|
+
// Lean 代码里的定义 / 对象 / 条件 / 假设 / 结论,是否与命题原文完全一致?
|
|
2413
|
+
//
|
|
2414
|
+
// 因此一旦 Lean 运行通过,验证提示词就不再要求重做推导,而是要求**忠实性审查**;
|
|
2415
|
+
// require 模式把这句话变成门禁:一个对象要被判定为真(严格证明)或假(严格反驳),必须
|
|
2416
|
+
// 先达到 `passed`(有 Lean 产物且最近一次运行 exit 0)或 `blocked`(代理给出显式、可审计的
|
|
2417
|
+
// 阻塞原因)。门禁只是兜底:它把裁定记为未定论 + 形式化待办,绝不把系统卡死。
|
|
2418
|
+
const FORMAL_MODES = ['off', 'encourage', 'require']
|
|
2419
|
+
function formalMode() { const m = String(params.formalVerify); return FORMAL_MODES.indexOf(m) !== -1 ? m : 'off' }
|
|
2420
|
+
function formalOn() { return formalMode() !== 'off' }
|
|
2421
|
+
function formalRecords() { return formalState.records }
|
|
2422
|
+
function formalTodo() { return formalState.todo }
|
|
2423
|
+
/**
|
|
2424
|
+
* 对象 id → 形式化记录的键。**空 id 必须是空串**:`idSafe('')` 会回退成 'id',那样一个没写
|
|
2425
|
+
* target 的回执(或空参数)就能在对象表里凭空造出一条名为 `id` 的记录。
|
|
2426
|
+
*/
|
|
2427
|
+
function formalId(raw) { const s = String(raw == null ? '' : raw).trim(); return s ? idSafe(s) : '' }
|
|
2428
|
+
function formalOf(target) {
|
|
2429
|
+
const id = formalId(target)
|
|
2430
|
+
if (!id) return { status: 'none' }
|
|
2431
|
+
const r = formalState.records[id]
|
|
2432
|
+
return r || { status: 'none' }
|
|
2433
|
+
}
|
|
2434
|
+
/**
|
|
2435
|
+
* 落一条形式化记录。达到 passed / blocked 时**同时清掉待办**:待办的含义就是"还不满足
|
|
2436
|
+
* require 门禁",留着已形式化完成的对象会让 Formal/TODO.md 永久说谎。
|
|
2437
|
+
*/
|
|
2438
|
+
async function putFormal(target, record) {
|
|
2439
|
+
const id = formalId(target)
|
|
2440
|
+
if (!id) return false
|
|
2441
|
+
let todoChanged = false
|
|
2442
|
+
if (record === null) delete formalState.records[id]
|
|
2443
|
+
else {
|
|
2444
|
+
formalState.records[id] = record
|
|
2445
|
+
if (record.status === 'passed' || record.status === 'blocked') {
|
|
2446
|
+
const i = formalState.todo.findIndex(function (x) { return x && x.id === id })
|
|
2447
|
+
if (i !== -1) { formalState.todo.splice(i, 1); todoChanged = true }
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
await saveAll()
|
|
2451
|
+
return todoChanged
|
|
2452
|
+
}
|
|
2453
|
+
// `passed` 需要的是**绿过的运行**,不是"归档了一个文件":从未执行过的证明文件什么也没证明。
|
|
2454
|
+
function formalGateOk(rec) { return !!rec && (rec.status === 'passed' || rec.status === 'blocked') }
|
|
2455
|
+
function formalBlocksConclusion(target) { return formalMode() === 'require' && !formalGateOk(formalOf(target)) }
|
|
2456
|
+
function formalStatusLine(target) {
|
|
2457
|
+
const r = formalOf(target)
|
|
2458
|
+
if (r.status === 'passed') return 'Lean 通过(' + (r.proof || r.file || '') + ')'
|
|
2459
|
+
if (r.status === 'blocked') return '阻塞(' + (r.note || '未说明') + ')'
|
|
2460
|
+
if (r.status === 'attempted') return '已尝试未通过'
|
|
2461
|
+
return '未尝试'
|
|
2462
|
+
}
|
|
2463
|
+
/** md 卡片锚点行内容(off 模式或状态 none 时为空 = 不写这一行)。 */
|
|
2464
|
+
function formalAnchorLine(target) {
|
|
2465
|
+
if (!formalOn()) return ''
|
|
2466
|
+
const r = formalOf(target)
|
|
2467
|
+
if (!r || r.status === 'none') return ''
|
|
2468
|
+
return formalStatusLine(target)
|
|
2469
|
+
}
|
|
2470
|
+
const formalTail = function (s, n) { const t = String(s == null ? '' : s); return t.length > n ? t.slice(-n) : t }
|
|
2471
|
+
|
|
2472
|
+
// ---- 路径守卫 ----------------------------------------------------------
|
|
2473
|
+
/**
|
|
2474
|
+
* 纯**词法**归一化绝对路径(折叠 '.', '..' 与重复斜杠),完全不碰文件系统。
|
|
2475
|
+
* 只做 `startsWith(root)` 是不够的:`…/VibeMath/Projects/../../../../etc/evil.lean`
|
|
2476
|
+
* 作为字符串仍然以根开头,解析后却在根外。
|
|
2477
|
+
*/
|
|
2478
|
+
function normalizeAbsPath(p) {
|
|
2479
|
+
const parts = String(p == null ? '' : p).replace(/\\/g, '/').split('/')
|
|
2480
|
+
const out = []
|
|
2481
|
+
for (const seg of parts) {
|
|
2482
|
+
if (seg === '') { if (out.length === 0) out.push(''); continue }
|
|
2483
|
+
if (seg === '.') continue
|
|
2484
|
+
if (seg === '..') { if (out.length > 1) out.pop(); continue }
|
|
2485
|
+
out.push(seg)
|
|
2486
|
+
}
|
|
2487
|
+
return out.join('/')
|
|
2488
|
+
}
|
|
2489
|
+
/**
|
|
2490
|
+
* 把 Lean 路径解析成"可证明位于 <VibeMath 根> 之内"的归一化绝对路径,否则 null。
|
|
2491
|
+
* 边界是 **VibeMath 根**而不是项目根:全局可复用库 <VibeMath 根>/Formal/{Lib,Proved}
|
|
2492
|
+
* 按契约 §3 就故意放在项目树之外。越界(爬到 VibeMath 根之上、或无关绝对路径)一律拒绝。
|
|
2493
|
+
*/
|
|
2494
|
+
function leanAbsPathFrom(baseAbs, rel) {
|
|
2495
|
+
const raw = String(rel == null ? '' : rel).trim()
|
|
2496
|
+
if (!raw) return null
|
|
2497
|
+
const abs = (raw.charAt(0) === '/' || /^[a-z]:/i.test(raw)) ? raw : baseAbs + '/' + raw.replace(/^\.\//, '')
|
|
2498
|
+
const norm = normalizeAbsPath(abs)
|
|
2499
|
+
const root = normalizeAbsPath(vibeRoot())
|
|
2500
|
+
if (norm !== root && norm.indexOf(root + '/') !== 0) return null
|
|
2501
|
+
return norm
|
|
2502
|
+
}
|
|
2503
|
+
function leanAbsPath(rel) { return leanAbsPathFrom(frameworkRoot(), rel) }
|
|
2504
|
+
function leanAbsPathVibe(rel) { return leanAbsPathFrom(vibeRoot(), rel) }
|
|
2505
|
+
/** 契约 §5.1:`file` 可相对**项目根**或 **<VibeMath 根>**——两处都过同一守卫,先项目后全局。 */
|
|
2506
|
+
async function leanResolveRun(rel) {
|
|
2507
|
+
const cand = leanAbsPath(rel)
|
|
2508
|
+
if (cand !== null && await readTextAbs(cand) !== undefined) return cand
|
|
2509
|
+
const alt = leanAbsPathVibe(rel)
|
|
2510
|
+
if (alt !== null && await readTextAbs(alt) !== undefined) return alt
|
|
2511
|
+
if (cand !== null) return cand
|
|
2512
|
+
return alt
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// ---- 执行(绝不抛进调度循环)------------------------------------------
|
|
2516
|
+
/**
|
|
2517
|
+
* 在一个 .lean 文件上跑工具链。**任何**失败模式(无 subprocess 服务、工具链不存在、
|
|
2518
|
+
* spawn 失败、超时、非零退出)都变成可读结果:调度循环永远不会因为 Lean 而崩。
|
|
2519
|
+
*/
|
|
2520
|
+
async function leanRunFile(relPath, timeoutMs) {
|
|
2521
|
+
const started = now()
|
|
2522
|
+
const rel = String(relPath == null ? '' : relPath).trim()
|
|
2523
|
+
if (!rel) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'file is required' }
|
|
2524
|
+
const abs = await leanResolveRun(rel)
|
|
2525
|
+
if (abs === null) {
|
|
2526
|
+
return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'Lean 文件必须位于 ' + vibeRoot().replace(/\\/g, '/') + '/ 之内(收到 ' + rel + ')' }
|
|
2527
|
+
}
|
|
2528
|
+
if (!/\.lean$/i.test(abs)) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'only .lean files can be executed' }
|
|
2529
|
+
if (await readTextAbs(abs) === undefined) return { ok: false, code: 'V3_NOT_FOUND', message: 'no such file: ' + rel }
|
|
2530
|
+
const sub = subprocessOf()
|
|
2531
|
+
if (sub === undefined || typeof sub.spawn !== 'function') {
|
|
2532
|
+
return { ok: false, code: 'NO_SUBPROCESS', message: 'the host exposes no subprocess service; Lean cannot be executed here', file: rel, ms: 0 }
|
|
2533
|
+
}
|
|
2534
|
+
const cmd = String(params.leanCommand || 'lean')
|
|
2535
|
+
const cap = Math.max(1000, Number(timeoutMs) || Number(params.leanTimeoutMs) || 120000)
|
|
2536
|
+
if (typeof sub.resolveExecutable !== 'function') {
|
|
2537
|
+
return { ok: false, code: 'LEAN_NOT_FOUND', message: 'the host subprocess service exposes no resolveExecutable(); cannot resolve "' + cmd + '" —— 仍可把形式化代码写下来归档,但无法在此宿主上执行', file: rel, ms: now() - started }
|
|
2538
|
+
}
|
|
2539
|
+
let exe
|
|
2540
|
+
try { exe = await sub.resolveExecutable(cmd) } catch (e) {
|
|
2541
|
+
return { ok: false, code: 'LEAN_NOT_FOUND', message: 'cannot resolve "' + cmd + '": ' + String((e && e.message) || e) + ' —— 仍可把形式化代码写下来归档,但无法在此宿主上执行', file: rel, ms: now() - started }
|
|
2542
|
+
}
|
|
2543
|
+
const argv = [exe].concat((Array.isArray(params.leanArgs) ? params.leanArgs : []).map(String)).concat([abs])
|
|
2544
|
+
let handle
|
|
2545
|
+
try {
|
|
2546
|
+
handle = sub.spawn({
|
|
2547
|
+
argv: argv,
|
|
2548
|
+
cwd: frameworkRoot(),
|
|
2549
|
+
stdio: { stdin: 'ignore', stdout: { maxBytes: 64 * 1024 }, stderr: { maxBytes: 64 * 1024 } },
|
|
2550
|
+
graceMs: cap,
|
|
2551
|
+
})
|
|
2552
|
+
} catch (e) {
|
|
2553
|
+
return { ok: false, code: 'LEAN_SPAWN_FAILED', message: String((e && e.message) || e), file: rel, ms: now() - started }
|
|
2554
|
+
}
|
|
2555
|
+
// 超时必须有**主动**兜底:graceMs 只是宿主侧的宽限,契约 §7 要求超时后调用 handle.terminate()。
|
|
2556
|
+
let timedOut = false
|
|
2557
|
+
let timer = null
|
|
2558
|
+
let outcome
|
|
2559
|
+
try {
|
|
2560
|
+
outcome = await Promise.race([
|
|
2561
|
+
handle.done,
|
|
2562
|
+
new Promise(function (resolve) {
|
|
2563
|
+
timer = setTimeout(function () {
|
|
2564
|
+
timedOut = true
|
|
2565
|
+
try { if (typeof handle.terminate === 'function') handle.terminate() } catch (e) { /* best effort */ }
|
|
2566
|
+
resolve({ exitCode: null, signal: 'SIGTERM' })
|
|
2567
|
+
}, cap)
|
|
2568
|
+
}),
|
|
2569
|
+
])
|
|
2570
|
+
} catch (e) {
|
|
2571
|
+
if (timer) clearTimeout(timer)
|
|
2572
|
+
return { ok: false, code: 'LEAN_RUN_FAILED', message: String((e && e.message) || e), file: rel, ms: now() - started }
|
|
2573
|
+
}
|
|
2574
|
+
if (timer) clearTimeout(timer)
|
|
2575
|
+
let out = '', err = ''
|
|
2576
|
+
try { if (handle.collected && handle.collected.stdout) out = handle.collected.stdout.readFrom(0).text } catch (e) { /* best effort */ }
|
|
2577
|
+
try { if (handle.collected && handle.collected.stderr) err = handle.collected.stderr.readFrom(0).text } catch (e) { /* best effort */ }
|
|
2578
|
+
const exitCode = outcome ? outcome.exitCode : null
|
|
2579
|
+
const ms = now() - started
|
|
2580
|
+
const ok = exitCode === 0
|
|
2581
|
+
return {
|
|
2582
|
+
ok: ok, exitCode: exitCode, signal: (outcome && outcome.signal) || null, ms: ms,
|
|
2583
|
+
command: argv.join(' '), file: rel,
|
|
2584
|
+
stdout: formalTail(out, 4000), stderr: formalTail(err, 4000),
|
|
2585
|
+
timedOut: timedOut,
|
|
2586
|
+
code: ok ? undefined : (timedOut ? 'LEAN_TIMEOUT' : 'LEAN_FAILED'),
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* 把一次运行记到对象上(契约 §4 状态迁移)。普通运行最多把对象推进到 `attempted`;
|
|
2591
|
+
* `passed` 只能由 vibe_math_lean_archive{kind:'proof'} 且该文件最近一次运行 ok 产生——否则一次
|
|
2592
|
+
* 顺手跑个无关文件就能把对象"洗白"成已形式化。passed/blocked 不会被普通运行降级。
|
|
2593
|
+
*/
|
|
2594
|
+
async function formalSetRun(target, run) {
|
|
2595
|
+
const t = formalId(target)
|
|
2596
|
+
if (!t) return
|
|
2597
|
+
const prev = formalOf(t)
|
|
2598
|
+
const status = (prev.status === 'passed' || prev.status === 'blocked') ? prev.status : 'attempted'
|
|
2599
|
+
await putFormal(t, Object.assign({}, prev, {
|
|
2600
|
+
status: status,
|
|
2601
|
+
file: (run && run.file) || prev.file || '',
|
|
2602
|
+
run: { at: now(), ok: !!(run && run.ok), exitCode: (run && run.exitCode !== undefined) ? run.exitCode : null, ms: (run && run.ms) || 0, stdoutTail: formalTail(run && run.stdout, 800), stderrTail: formalTail(run && run.stderr, 800) },
|
|
2603
|
+
updatedAt: now(),
|
|
2604
|
+
}))
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
// ---- 提示词注入(都在**构造提示词的那一刻**现算,故运行中切模式立刻生效)--------
|
|
2608
|
+
function formalPromptBlock(target) {
|
|
2609
|
+
if (!formalOn()) return ''
|
|
2610
|
+
const mode = formalMode()
|
|
2611
|
+
const rec = target ? formalOf(target) : { status: 'none' }
|
|
2612
|
+
const L = []
|
|
2613
|
+
L.push('【Lean 形式化验证(' + (mode === 'require' ? '强制' : '鼓励') + '模式)】')
|
|
2614
|
+
if (rec.status === 'passed') {
|
|
2615
|
+
// 整套机制的要害:审查对象**变了**——不是"推导对不对",而是"这段代码说的是不是这个命题"。
|
|
2616
|
+
// §4.1:忠实性缺陷**不是**"命题为假",所以这一支必须同时给出 defect 出口(不得把偏差记成 0)。
|
|
2617
|
+
L.push(' · 该对象已有**通过的 Lean 形式化证明**(' + (rec.proof || rec.file || '') + ',最近一次运行 exit 0)。')
|
|
2618
|
+
L.push(' **你不需要重新检查推导**。你的任务是**忠实性审查**:逐条核对 Lean 代码里的')
|
|
2619
|
+
L.push(' 定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**。')
|
|
2620
|
+
L.push(' ▸ 一致 → Result = 1。')
|
|
2621
|
+
L.push(' ▸ **发现任何偏差,不要投 0**:偏差只说明**形式化不合格**,不代表命题为假。此时请:')
|
|
2622
|
+
L.push(' ① Result 给一个严格介于 0 与 1 之间的值(记为弃权),并在 Reason 里写清偏差;')
|
|
2623
|
+
L.push(" ② 用回执 formal:{decision:'defect', note:'<具体偏差>'} 记录它。框架会撤回这条证明的")
|
|
2624
|
+
L.push(' 「已通过」状态(降级为 attempted、删除归档证明、写入形式化待办),本次裁定**不定论**;')
|
|
2625
|
+
L.push(' 修正形式化并重新跑通后再投票。')
|
|
2626
|
+
L.push(' ▸ 只有当你**独立于这份 Lean 代码**也能确定命题为假时,才投 0,并在 Reason 里写清独立理由。')
|
|
2627
|
+
} else if (rec.status === 'blocked') {
|
|
2628
|
+
L.push(' · 该对象已被记录为**形式化阻塞**:' + (rec.note || '未说明') + '。')
|
|
2629
|
+
L.push(' 请复核这个判断是否成立;若你认为其实可以形式化,请指出来并动手做。')
|
|
2630
|
+
} else {
|
|
2631
|
+
L.push(' · 请先判断该对象的**实现难度**:若能在可接受的工作量内形式化,优先写 Lean 代码并执行。')
|
|
2632
|
+
L.push(' · 工具:vibe_math_lean_run(执行)· vibe_math_lean_archive(归档)· vibe_math_lean_lib(查已有可复用库)')
|
|
2633
|
+
L.push(' · 工作目录:Formal/(相对项目根);可复用定义放 ' + (vibeRoot() + '/Formal/Lib/').replace(/\\/g, '/')
|
|
2634
|
+
+ ',已证引理放 ' + (vibeRoot() + '/Formal/Proved/').replace(/\\/g, '/') + ';写之前先 vibe_math_lean_lib 查重。')
|
|
2635
|
+
L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义/对象/条件/假设/结论是否与命题原文逐条一致。请把注意力放在这种核对上,而不是重新做一遍推导。')
|
|
2636
|
+
if (mode === 'require') {
|
|
2637
|
+
L.push(' · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(vibe_math_lean_archive kind=\'blocked\' note=… 或回执 formal.note)。若两者都没有,本次裁定不会生效,会被记为未定论(原因 formal-required)并进入「形式化待办」。')
|
|
2638
|
+
} else {
|
|
2639
|
+
L.push(' · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断(decision=\'blocked\' 时必须写明 note)。')
|
|
2640
|
+
}
|
|
2641
|
+
L.push(' · 归档可复用定义/引理前先跑通(vibe_math_lean_archive run=true 或先 vibe_math_lean_run);跑不通不要入库。')
|
|
2642
|
+
L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"——这算显式阻塞原因,定论门禁可以据此放行。')
|
|
2643
|
+
}
|
|
2644
|
+
return L.join('\n')
|
|
2645
|
+
}
|
|
2646
|
+
/** 日常提示词里的"顺手形式化"一行(off 模式返回空串 = 一个字都不多)。 */
|
|
2647
|
+
function formalWorkLine() {
|
|
2648
|
+
if (!formalOn()) return ''
|
|
2649
|
+
return '【顺手形式化(' + (formalMode() === 'require' ? '强制' : '鼓励') + ')】把你工作中常用或可能复用的对象、假设、'
|
|
2650
|
+
+ '新定义用 Lean 形式化定义并归档到全局可复用库(vibe_math_lean_archive kind=\'def\'),已成立的引理归到 '
|
|
2651
|
+
+ (vibeRoot() + '/Formal/Proved/').replace(/\\/g, '/') + '(kind=\'lemma\');写之前先 vibe_math_lean_lib 查重,避免重复定义。'
|
|
2652
|
+
+ (formalMode() === 'require'
|
|
2653
|
+
? '本模式下,任何要定论为真/假的对象都必须先有 Lean 通过或显式阻塞记录。'
|
|
2654
|
+
: '这会让后续的验证与证明省掉大量重复工作。')
|
|
2655
|
+
// 硬要求 3(契约 §6 顶部):可复用库只收**跑通过**的代码,否则它会被不编译的定义污染。
|
|
2656
|
+
+ '归档前先跑通(vibe_math_lean_run 或 run=true);跑不通的定义不要进可复用库。'
|
|
2657
|
+
}
|
|
2658
|
+
/** 回执契约里的 formal 字段(契约 §6.3):非 off 模式必须出现在回执契约里,否则这条通道不可发现。 */
|
|
2659
|
+
function formalJsonField(target) {
|
|
2660
|
+
if (!formalOn()) return ''
|
|
2661
|
+
const id = String(target == null ? '' : target) || '<对象id>'
|
|
2662
|
+
return ',"formal":{"target":"' + id + '","decision":"used|blocked|defect","file":"Formal/' + id + '.lean","note":"难度判断/阻塞原因/具体偏差"}'
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* 工作轮(solver / explorer)回执契约里的 formal 字段。这些角色的回执本身就是一段 JSON 模板,
|
|
2666
|
+
* 直接改模板尾部容易把示例改成非法 JSON,所以在**契约说明**里给出同样的字段(契约 §6.3),
|
|
2667
|
+
* 框架侧 `absorbFormalFromReply` 同时接受顶层 `formal` 与 `meta.formal`。
|
|
2668
|
+
*/
|
|
2669
|
+
function formalReplyNote() {
|
|
2670
|
+
if (!formalOn()) return ''
|
|
2671
|
+
return '\n形式化回执(本模式):若你本轮对某个对象做了形式化难度判断,请在回执里加上 '
|
|
2672
|
+
+ '"formal":{"target":"<对象id>","decision":"used|blocked|defect","file":"Formal/<对象id>.lean","note":"难度判断/阻塞原因/具体偏差"}'
|
|
2673
|
+
+ '(decision=\'blocked\' 与 decision=\'defect\' 时必须写明 note,否则拒绝记录;'
|
|
2674
|
+
+ 'decision=\'defect\' 表示你认定这条已通过的 Lean 形式化**不忠实于命题原文**——'
|
|
2675
|
+
+ '那不是"命题为假",框架会撤回其已通过状态并把对象放回形式化待办)。'
|
|
2676
|
+
}
|
|
2677
|
+
/** 从一条代理回执里取出 formal 判断并落库(顶层 formal 或 meta.formal 都接受)。 */
|
|
2678
|
+
async function absorbFormalFromReply(parsed, memberId) {
|
|
2679
|
+
if (!formalOn() || !parsed || typeof parsed !== 'object') return
|
|
2680
|
+
const f = (parsed.formal && typeof parsed.formal === 'object') ? parsed.formal
|
|
2681
|
+
: ((parsed.meta && typeof parsed.meta.formal === 'object') ? parsed.meta.formal : null)
|
|
2682
|
+
if (f) return await absorbFormalReply(f, memberId)
|
|
2683
|
+
}
|
|
2684
|
+
/** 对象 id → 关联可验证对象(命题/问题)。用于把表决对象映射到形式化记录。 */
|
|
2685
|
+
function verifyTargetId(r) { return String((r && (r.pId || r.qid)) || '') }
|
|
2686
|
+
/**
|
|
2687
|
+
* 这次裁决会不会**宣告某个对象定论**?返回被宣告对象的 id,否则空串。
|
|
2688
|
+
*
|
|
2689
|
+
* 门禁只管 boolean 裁定(真=严格证明 / 假=严格反驳):近共识的小数(例如 0.95)本来就仍留
|
|
2690
|
+
* 原库为未定论,不受门禁约束(契约 §2)。`prop-proof` 判 0 时并不会立刻把命题定论,它往反
|
|
2691
|
+
* 方向推入一条 prob=1 的证伪;下一次 processStatusUpdates 会在那里被同一道门拦住。
|
|
2692
|
+
*/
|
|
2693
|
+
function formalConclusionTarget(r, v) {
|
|
2694
|
+
if (!r) return ''
|
|
2695
|
+
if (r.kind === 'proposition') return (v === 1 || v === 0) ? String(r.pId || '') : ''
|
|
2696
|
+
if (r.kind === 'prop-proof') return v === 1 ? String(r.pId || '') : ''
|
|
2697
|
+
if (r.kind === 'problem-solution') return v === 1 ? String(r.qid || '') : ''
|
|
2698
|
+
return ''
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
// ---- 公告 / 索引 --------------------------------------------------------
|
|
2702
|
+
/**
|
|
2703
|
+
* v3 没有 v5 的群聊通道,对应的"群聊公告"落点是:①活动日志(vibe_math_status.recentActivity /
|
|
2704
|
+
* 报告里可见)②Logs/形式化.md 的追加式公告(人可复核、可 diff)。公告失败绝不影响调度。
|
|
2705
|
+
*/
|
|
2706
|
+
async function formalAnnounce(text) {
|
|
2707
|
+
logActivity('formal', text)
|
|
2708
|
+
try {
|
|
2709
|
+
const rel = 'Logs/形式化.md'
|
|
2710
|
+
const prev = await readText(rel)
|
|
2711
|
+
const head = '# 形式化公告|' + currentProject + '\n\n> 由框架维护的追加式公告(形式化定论、归档、require 门禁搁置)。\n\n'
|
|
2712
|
+
await writeText(rel, ((prev === undefined || !String(prev).trim()) ? head : prev) + '- ' + fmtTime() + ' ' + text + '\n')
|
|
2713
|
+
} catch (e) { /* 公告失败不应影响调度 */ }
|
|
2714
|
+
}
|
|
2715
|
+
/** require 模式:本轮裁定**不生效**——记未定论 + 形式化待办 + 公告,而不写 Verified/ 卡片。 */
|
|
2716
|
+
async function deferForFormal(target, why, isTrue) {
|
|
2717
|
+
const t = formalId(target)
|
|
2718
|
+
if (!t) return { deferred: false }
|
|
2719
|
+
const list = formalTodo()
|
|
2720
|
+
const already = list.some(function (x) { return x && x.id === t })
|
|
2721
|
+
if (!already) {
|
|
2722
|
+
// 幂等:processStatusUpdates 每个 tick 都会再试一次同一对象,只有**首次**才写盘 + 公告,
|
|
2723
|
+
// 否则每秒都会往 Formal/TODO.md 和人读公告里追加一遍(日志刷屏)。
|
|
2724
|
+
list.push({ id: t, at: now(), why: why, verdict: isTrue ? 1 : 0, project: currentProject })
|
|
2725
|
+
await writeFormalTodo()
|
|
2726
|
+
await writeFormalIndex()
|
|
2727
|
+
await formalAnnounce('【形式化】' + t + ' 的表决结果为 ' + (isTrue ? '真' : '假') + ',但 **require 模式**要求'
|
|
2728
|
+
+ '先有 Lean 通过或显式阻塞记录,因此本轮**不定论**(已记入 Formal/TODO.md)。请完成形式化'
|
|
2729
|
+
+ '(vibe_math_lean_archive kind=\'proof\')或记录阻塞原因(kind=\'blocked\')后重新提议验证。')
|
|
2730
|
+
await saveAll()
|
|
2731
|
+
}
|
|
2732
|
+
return { deferred: true, already: already, why: why }
|
|
2733
|
+
}
|
|
2734
|
+
/** status/report 里的形式化摘要:让所办/人能审计"到底拿到了严格证明,还是只有共识"。 */
|
|
2735
|
+
function formalSummary() {
|
|
2736
|
+
const recs = formalRecords()
|
|
2737
|
+
const keys = Object.keys(recs)
|
|
2738
|
+
return {
|
|
2739
|
+
mode: formalMode(),
|
|
2740
|
+
objects: keys.map(function (k) {
|
|
2741
|
+
const r = recs[k] || {}
|
|
2742
|
+
return { target: k, status: r.status || 'none', file: r.file || '', proof: r.proof || '', note: r.note || '', run: r.run ? { ok: r.run.ok, exitCode: r.run.exitCode, ms: r.run.ms } : null }
|
|
2743
|
+
}),
|
|
2744
|
+
passed: keys.filter(function (k) { return (recs[k] || {}).status === 'passed' }),
|
|
2745
|
+
blocked: keys.filter(function (k) { return (recs[k] || {}).status === 'blocked' }),
|
|
2746
|
+
todo: formalTodo().map(function (t) { return { id: t.id, why: t.why || 'formal-required', at: t.at } }),
|
|
2747
|
+
paths: { project: 'Formal/(相对项目根)', lib: 'VibeMath/Formal/Lib/', proved: 'VibeMath/Formal/Proved/', proofs: 'Verified/Lean/' },
|
|
2748
|
+
note: formalOn() ? '' : '未启用(formalVerify = off;可用 vibe_math_set_params 切到 encourage / require)',
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
async function writeFormalIndex() {
|
|
2752
|
+
const recs = formalRecords()
|
|
2753
|
+
const keys = Object.keys(recs).sort()
|
|
2754
|
+
const L = ['# Lean 形式化索引|' + currentProject + '|' + fmtTime(), '',
|
|
2755
|
+
'> 本文件由框架维护(工具调用时增量更新;`vibe_math_lean_lib` 会重建)。权威状态在对象记录里。', '',
|
|
2756
|
+
'| 对象 | 状态 | 形式化文件 | 归档证明 | 最近运行 | 难度判断 / 阻塞原因 |', '|---|---|---|---|---|---|']
|
|
2757
|
+
if (!keys.length) L.push('| (暂无) | | | | | |')
|
|
2758
|
+
for (const k of keys) {
|
|
2759
|
+
const r = recs[k] || {}
|
|
2760
|
+
const run = r.run ? (r.run.ok ? 'ok(exit 0,' + ((r.run.ms || 0) / 1000).toFixed(1) + 's)' : 'fail(exit ' + r.run.exitCode + ',' + ((r.run.ms || 0) / 1000).toFixed(1) + 's)') : '—'
|
|
2761
|
+
L.push('| ' + k + ' | ' + (r.status || 'none') + ' | ' + (r.file || '—') + ' | ' + (r.proof || '—') + ' | ' + run + ' | ' + String(r.note || '—').replace(/\|/g, '/').slice(0, 120) + ' |')
|
|
2762
|
+
}
|
|
2763
|
+
L.push('')
|
|
2764
|
+
if (formalTodo().length) {
|
|
2765
|
+
L.push('## 形式化待办(require 模式:定论被搁置)')
|
|
2766
|
+
for (const t of formalTodo()) L.push('- ' + t.id + ' —— ' + (t.why || 'formal-required') + '(' + fmtTime(t.at) + ')')
|
|
2767
|
+
L.push('')
|
|
2768
|
+
}
|
|
2769
|
+
await writeText('Formal/Index.md', L.join('\n'))
|
|
2770
|
+
}
|
|
2771
|
+
async function writeFormalTodo() {
|
|
2772
|
+
const list = formalTodo()
|
|
2773
|
+
const L = ['# 形式化待办|' + currentProject + '|' + fmtTime(), '',
|
|
2774
|
+
'> 这些对象在 `require` 模式下尚不具备「Lean 已通过」或「显式阻塞记录」,因此**定论被搁置**。',
|
|
2775
|
+
'> 完成形式化(vibe_math_lean_archive kind=\'proof\')或记录阻塞原因(kind=\'blocked\')后,重新提议验证即可。', '']
|
|
2776
|
+
if (!list.length) L.push('(暂无)')
|
|
2777
|
+
for (const t of list) L.push('- ' + t.id + '|' + (t.why || 'formal-required') + '|' + fmtTime(t.at))
|
|
2778
|
+
L.push('')
|
|
2779
|
+
await writeText('Formal/TODO.md', L.join('\n'))
|
|
2780
|
+
}
|
|
2781
|
+
/**
|
|
2782
|
+
* 扫描并重建三处索引(项目 Formal/Index.md + 全局 Lib/Index.md + Proved/Index.md)。
|
|
2783
|
+
* 列举**便宜且无副作用**:不跑工具链("我能复用哪些定义"不该触发编译);每次运行的结果记在
|
|
2784
|
+
* formalState.libRuns 里(归档 kind='def'/'lemma' 时 run:true 记录的),作为"最近运行"列。
|
|
2785
|
+
*/
|
|
2786
|
+
async function rebuildLeanLibIndexes() {
|
|
2787
|
+
const scan = async function (dirAbs, dirRel) {
|
|
2788
|
+
const rows = []
|
|
2789
|
+
const names = await listFilesAbs(dirAbs)
|
|
2790
|
+
for (const name of names) {
|
|
2791
|
+
if (!/\.lean$/i.test(String(name))) continue
|
|
2792
|
+
const rel = dirRel + '/' + name
|
|
2793
|
+
const txt = (await readTextAbs(dirAbs + '/' + name)) || ''
|
|
2794
|
+
const base = String(name).replace(/\.lean$/i, '')
|
|
2795
|
+
const first = (String(txt).split('\n').filter(function (l) { return l.trim() && !/^\s*(\/\/|--|import)/.test(l) })[0] || '').trim().slice(0, 110)
|
|
2796
|
+
const depend = (String(txt).split('\n').filter(function (l) { return /^\s*import\s+/.test(l) }).map(function (l) { return l.replace(/^\s*import\s+/, '').trim() }).join('、')) || '—'
|
|
2797
|
+
const run = formalState.libRuns[rel]
|
|
2798
|
+
rows.push({ base: base, rel: rel, first: first, depend: depend, run: run ? (run.ok ? 'ok' : 'fail') : '—' })
|
|
2799
|
+
}
|
|
2800
|
+
return rows
|
|
2801
|
+
}
|
|
2802
|
+
const libRows = await scan(vibeRoot() + '/Formal/Lib', 'Lib')
|
|
2803
|
+
await writeTextAbs(vibeRoot() + '/Formal/Lib/Index.md', ['# 可复用 Lean 定义库(跨项目)|' + currentProject, '',
|
|
2804
|
+
'> 写新定义之前先查这里:能复用就不要重新定义。', '',
|
|
2805
|
+
'| 名称 | 文件 | 类别 | 摘要 | 最近运行 |', '|---|---|---|---|---|']
|
|
2806
|
+
.concat(libRows.length ? libRows.map(function (r) { return '| ' + r.base + ' | ' + r.rel + ' | def | ' + r.first.replace(/\|/g, '/') + ' | ' + r.run + ' |' }) : ['| (暂无) | | | | |']).join('\n') + '\n')
|
|
2807
|
+
const provedRows = await scan(vibeRoot() + '/Formal/Proved', 'Proved')
|
|
2808
|
+
await writeTextAbs(vibeRoot() + '/Formal/Proved/Index.md', ['# 已成立的 Lean 命题 / 引理(机器已核对,可跨项目复用)|' + currentProject, '',
|
|
2809
|
+
'> 这些文件是通过内核检查的引理,可直接 import 复用。', '',
|
|
2810
|
+
'| 名称 | 文件 | 陈述 | 依赖 | 最近运行 |', '|---|---|---|---|---|']
|
|
2811
|
+
.concat(provedRows.length ? provedRows.map(function (r) { return '| ' + r.base + ' | ' + r.rel + ' | ' + r.first.replace(/\|/g, '/') + ' | ' + r.depend.replace(/\|/g, '/') + ' | ' + r.run + ' |' }) : ['| (暂无) | | | | |']).join('\n') + '\n')
|
|
2812
|
+
await writeFormalIndex()
|
|
2813
|
+
await writeFormalTodo()
|
|
2814
|
+
return { lib: libRows.length, proved: provedRows.length, objects: Object.keys(formalRecords()).length }
|
|
2815
|
+
}
|
|
2816
|
+
/**
|
|
2817
|
+
* 形式化记录变化后,把对象 md 卡片上的 `- 形式化:` 锚点改成一致(任务要求)。
|
|
2818
|
+
*
|
|
2819
|
+
* 用**文本级插入/替换**而不是 saveProposition/saveProblem 整卡重写:v3 支持代理直接写 md,
|
|
2820
|
+
* 整卡重写会把代理在调度器最后一次解析之后写进卡里的内容抹掉(v3 自己已有"代理已直接写了
|
|
2821
|
+
* 该命题卡,保留其内容(不覆盖)"的纪律)。位置与 compose*Md 保持一致:紧随 `- 概率:`,
|
|
2822
|
+
* 无概率锚点时紧随 `- 状态:`。
|
|
2823
|
+
*/
|
|
2824
|
+
function withFormalAnchor(text, line) {
|
|
2825
|
+
const s = String(text)
|
|
2826
|
+
const idx = s.search(/\n## /)
|
|
2827
|
+
const head = idx === -1 ? s.replace(/\s+$/, '') : s.slice(0, idx)
|
|
2828
|
+
const body = idx === -1 ? '' : s.slice(idx)
|
|
2829
|
+
const kept = head.split('\n').filter(function (l) { return !/^-\s*形式化\s*:/.test(l) })
|
|
2830
|
+
if (line) {
|
|
2831
|
+
let at = -1
|
|
2832
|
+
for (let i = 0; i < kept.length; i++) { if (/^-\s*概率\s*:/.test(kept[i])) { at = i; break } }
|
|
2833
|
+
if (at === -1) for (let i = 0; i < kept.length; i++) { if (/^-\s*状态\s*:/.test(kept[i])) { at = i; break } }
|
|
2834
|
+
if (at === -1) kept.push('- 形式化: ' + line)
|
|
2835
|
+
else kept.splice(at + 1, 0, '- 形式化: ' + line)
|
|
2836
|
+
}
|
|
2837
|
+
const out = kept.join('\n') + body
|
|
2838
|
+
if (out === s) return s
|
|
2839
|
+
return /\n$/.test(out) ? out : out + '\n'
|
|
2840
|
+
}
|
|
2841
|
+
async function upsertFormalAnchor(target) {
|
|
2842
|
+
if (!formalOn()) return false
|
|
2843
|
+
const id = formalId(target)
|
|
2844
|
+
if (!id) return false
|
|
2845
|
+
const p = propos.get(id)
|
|
2846
|
+
const q = problems.get(id)
|
|
2847
|
+
const rel = p ? propositionRel(p) : (q ? problemRel(q) : '')
|
|
2848
|
+
if (!rel) return false
|
|
2849
|
+
const txt = await readText(rel)
|
|
2850
|
+
if (txt === undefined) return false
|
|
2851
|
+
const next = withFormalAnchor(txt, formalAnchorLine(id))
|
|
2852
|
+
if (next === txt) return false
|
|
2853
|
+
await writeText(rel, next)
|
|
2854
|
+
return true
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
// ---- 三个工具的实现 -----------------------------------------------------
|
|
2858
|
+
/**
|
|
2859
|
+
* 在一个 .lean 文件上跑工具链、把结果记到对象上(可选)、刷新索引,并如实回报。
|
|
2860
|
+
* off 模式下也照常工作:人/代理主动调用时它不该失效,只是框架不主动宣传它存在。
|
|
2861
|
+
*/
|
|
2862
|
+
async function leanRunTool(memberId, o) {
|
|
2863
|
+
const args = o || {}
|
|
2864
|
+
const run = await leanRunFile(String(args.file || ''), args.timeout_ms)
|
|
2865
|
+
const target = String(args.target || '').trim()
|
|
2866
|
+
if (run.file || run.ok) {
|
|
2867
|
+
if (target) { await formalSetRun(target, run); await upsertFormalAnchor(target) }
|
|
2868
|
+
await writeFormalIndex()
|
|
2869
|
+
}
|
|
2870
|
+
if (run.ok) await formalAnnounce('【形式化】' + (memberId || 'scheduler') + ' 运行 Lean 通过:' + run.file + '(' + ((run.ms || 0) / 1000).toFixed(1) + 's)' + (target ? '|对象 ' + target : ''))
|
|
2871
|
+
return Object.assign({ ok: !!run.ok }, run, {
|
|
2872
|
+
hint: run.ok
|
|
2873
|
+
? '通过。若是某个对象的证明,请用 vibe_math_lean_archive kind=\'proof\' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind=\'def\'/\'lemma\' 归档到全局库。'
|
|
2874
|
+
: '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用 vibe_math_lean_archive kind=\'blocked\' 记录原因。',
|
|
2875
|
+
})
|
|
2876
|
+
}
|
|
2877
|
+
async function leanArchive(memberId, o) {
|
|
2878
|
+
const args = o || {}
|
|
2879
|
+
const kind = String(args.kind || '')
|
|
2880
|
+
const content = typeof args.content === 'string' ? args.content : undefined
|
|
2881
|
+
const from = args.from ? String(args.from) : ''
|
|
2882
|
+
const who = memberId || 'scheduler'
|
|
2883
|
+
// 读来源文件:只在 <VibeMath 根> 之内,且必须存在。
|
|
2884
|
+
const readFrom = async function () {
|
|
2885
|
+
const srcAbs = await leanResolveRun(from)
|
|
2886
|
+
if (srcAbs === null) return { err: 'from must be a .lean file inside the VibeMath root (got ' + from + ')' }
|
|
2887
|
+
const body = await readTextAbs(srcAbs)
|
|
2888
|
+
if (body === undefined) return { err: 'no such file: ' + from }
|
|
2889
|
+
return { body: body }
|
|
2890
|
+
}
|
|
2891
|
+
if (kind === 'def' || kind === 'lemma') {
|
|
2892
|
+
const rawName = String(args.name || '').trim()
|
|
2893
|
+
if (!rawName) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'name is required for a reusable definition/lemma' }
|
|
2894
|
+
const name = idSafe(rawName)
|
|
2895
|
+
let body = content
|
|
2896
|
+
if (body === undefined && from) { const r = await readFrom(); if (r.err) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: r.err }; body = r.body }
|
|
2897
|
+
if (body === undefined) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'provide content, or from=<existing .lean file>' }
|
|
2898
|
+
const dirRel = kind === 'def' ? 'Lib' : 'Proved'
|
|
2899
|
+
const rel = 'Formal/' + dirRel + '/' + name + '.lean'
|
|
2900
|
+
const abs = vibeRoot() + '/' + rel
|
|
2901
|
+
if (!await writeTextAbs(abs, body)) return { ok: false, code: 'V3_WRITE_FAILED', message: 'could not write ' + rel }
|
|
2902
|
+
// 全局库在项目树之外,必须用**绝对路径**执行(相对形式会在项目根里找)。
|
|
2903
|
+
const run = args.run === false ? null : await leanRunFile(abs)
|
|
2904
|
+
if (run && run.file) formalState.libRuns[dirRel + '/' + name + '.lean'] = { ok: !!run.ok, exitCode: run.exitCode === undefined ? null : run.exitCode, ms: run.ms || 0, at: now() }
|
|
2905
|
+
await rebuildLeanLibIndexes()
|
|
2906
|
+
await formalAnnounce('【形式化】' + who + ' 归档了' + (kind === 'def' ? '可复用定义' : '已证引理') + ' `' + name + '` → ' + rel + (run ? '(运行 ' + (run.ok ? '通过' : '未通过') + ')' : '(未运行)'))
|
|
2907
|
+
return { ok: true, kind: kind, name: name, file: rel, run: run || undefined, note: '已并入全局可复用库,后续项目可直接 import 复用' }
|
|
2908
|
+
}
|
|
2909
|
+
if (kind === 'proof') {
|
|
2910
|
+
const rawTarget = String(args.target || '').trim()
|
|
2911
|
+
if (!rawTarget) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'target is required for kind=proof' }
|
|
2912
|
+
const target = idSafe(rawTarget)
|
|
2913
|
+
let body = content
|
|
2914
|
+
if (body === undefined && from) { const r = await readFrom(); if (r.err) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: r.err }; body = r.body }
|
|
2915
|
+
if (body === undefined) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'provide content, or from=<existing .lean file>' }
|
|
2916
|
+
const workRel = 'Formal/' + target + '.lean'
|
|
2917
|
+
if (!await writeText(workRel, body)) return { ok: false, code: 'V3_WRITE_FAILED', message: 'could not write ' + workRel }
|
|
2918
|
+
const run = await leanRunFile(workRel)
|
|
2919
|
+
const prev = formalOf(target)
|
|
2920
|
+
const passed = !!run.ok
|
|
2921
|
+
const rec = Object.assign({}, prev, {
|
|
2922
|
+
status: passed ? 'passed' : 'attempted',
|
|
2923
|
+
file: workRel,
|
|
2924
|
+
proof: passed ? 'Verified/Lean/' + target + '.lean' : (prev.proof || ''),
|
|
2925
|
+
decision: 'used',
|
|
2926
|
+
note: String(args.note || prev.note || ''),
|
|
2927
|
+
run: { at: now(), ok: !!run.ok, exitCode: run.exitCode === undefined ? null : run.exitCode, ms: run.ms || 0, stdoutTail: formalTail(run.stdout, 800), stderrTail: formalTail(run.stderr, 800) },
|
|
2928
|
+
updatedAt: now(),
|
|
2929
|
+
})
|
|
2930
|
+
if (passed) await writeText('Verified/Lean/' + target + '.lean', body)
|
|
2931
|
+
await putFormal(target, rec)
|
|
2932
|
+
await upsertFormalAnchor(target)
|
|
2933
|
+
await rebuildLeanLibIndexes()
|
|
2934
|
+
await formalAnnounce('【形式化】' + who + ' 为 ' + target + ' 归档形式化证明 ' + workRel + '(运行 '
|
|
2935
|
+
+ (passed ? '**通过**,已归档到 ' + rec.proof + ',验证转为忠实性审查' : '**未通过**:' + formalTail(run.stderr || run.message, 160)) + ')')
|
|
2936
|
+
return { ok: true, kind: kind, target: target, file: workRel, proof: rec.proof, passed: passed, run: run, status: rec.status }
|
|
2937
|
+
}
|
|
2938
|
+
if (kind === 'blocked') {
|
|
2939
|
+
const rawTarget = String(args.target || '').trim()
|
|
2940
|
+
if (!rawTarget) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'target is required for kind=blocked' }
|
|
2941
|
+
const target = idSafe(rawTarget)
|
|
2942
|
+
const note = String(args.note || '').trim()
|
|
2943
|
+
// "因难度决定不做形式化"必须**显式、可审计**:note 空就拒绝,不允许静默跳过。
|
|
2944
|
+
if (!note) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: '阻塞记录必须写明原因(note)——"因难度决定不做形式化"必须显式、可审计' }
|
|
2945
|
+
const prev = formalOf(target)
|
|
2946
|
+
await putFormal(target, Object.assign({}, prev, { status: 'blocked', decision: 'blocked', note: note, updatedAt: now() }))
|
|
2947
|
+
await upsertFormalAnchor(target)
|
|
2948
|
+
await rebuildLeanLibIndexes()
|
|
2949
|
+
await formalAnnounce('【形式化】' + who + ' 记录 ' + target + ' 形式化阻塞:' + note)
|
|
2950
|
+
return { ok: true, kind: kind, target: target, status: 'blocked', note: note }
|
|
2951
|
+
}
|
|
2952
|
+
return { ok: false, code: 'V3_INVALID_ARGUMENT', message: "kind must be 'def' | 'lemma' | 'proof' | 'blocked'" }
|
|
2953
|
+
}
|
|
2954
|
+
/**
|
|
2955
|
+
* §4.1 的落地点:表决者认定这条**已通过的** Lean 形式化不忠实于命题原文。
|
|
2956
|
+
*
|
|
2957
|
+
* 偏差不是"命题为假",而恰好是"这次形式化不合格",因此这里的动作与 blocked 不同:
|
|
2958
|
+
* ① 无论此前是 `passed` 还是 `blocked`,一律**降级**为 `attempted`(都让位于"需重做");
|
|
2959
|
+
* ② 清空 `proof` 并**删除** `Verified/Lean/<id>.lean`(工作文件 `Formal/<id>.lean` 保留,代码不丢);
|
|
2960
|
+
* ③ 把具体偏差写进记录与 `Formal/TODO.md`,并公告。
|
|
2961
|
+
* 之后 `formalGateOk` 为假:require 档本次裁定**不定论**,对象进入「形式化待办」——修正形式化
|
|
2962
|
+
* 并重新跑通后再投票。绝不把这条路径写成 `0`(那会让框架记下"命题为假")。
|
|
2963
|
+
*/
|
|
2964
|
+
async function formalRecordDefect(target, note, who) {
|
|
2965
|
+
const id = formalId(target)
|
|
2966
|
+
if (!id) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'defect 记录必须写明 target' }
|
|
2967
|
+
const prev = formalOf(id)
|
|
2968
|
+
const archived = String(prev.proof || '').trim() || ('Verified/Lean/' + id + '.lean')
|
|
2969
|
+
await putFormal(id, Object.assign({}, prev, {
|
|
2970
|
+
status: 'attempted',
|
|
2971
|
+
decision: 'defect',
|
|
2972
|
+
note: note,
|
|
2973
|
+
proof: '',
|
|
2974
|
+
updatedAt: now(),
|
|
2975
|
+
}))
|
|
2976
|
+
// 归档证明必须消失,否则 Verified/Lean/ 里会留下一份"看起来已通过"的不忠实代码。
|
|
2977
|
+
await removeFile(archived)
|
|
2978
|
+
// 待办条目按 id 去重、就地刷新:note 进入 TODO.md 的 why 列(require 档据此搁置定论)。
|
|
2979
|
+
const list = formalTodo()
|
|
2980
|
+
const i = list.findIndex(function (x) { return x && x.id === id })
|
|
2981
|
+
const why = 'formal-defect:' + note
|
|
2982
|
+
if (i === -1) list.push({ id: id, at: now(), why: why, verdict: null, project: currentProject })
|
|
2983
|
+
else list[i] = Object.assign({}, list[i], { why: why, at: now() })
|
|
2984
|
+
await writeFormalTodo()
|
|
2985
|
+
await upsertFormalAnchor(id)
|
|
2986
|
+
await rebuildLeanLibIndexes()
|
|
2987
|
+
await saveAll()
|
|
2988
|
+
await formalAnnounce('【形式化】' + who + ' 认定 ' + id + ' 的 Lean 形式化存在**忠实性缺陷**:' + note
|
|
2989
|
+
+ '。这不是"命题为假",而是**形式化不合格**:已撤回其「已通过」状态(降级为 attempted)、'
|
|
2990
|
+
+ '删除归档证明 ' + archived + '、写入 Formal/TODO.md;本次裁定**不定论**,修正形式化并重新'
|
|
2991
|
+
+ '跑通(vibe_math_lean_archive kind=\'proof\')后再投票。')
|
|
2992
|
+
return { ok: true, kind: 'defect', target: id, status: 'attempted', proof: '', note: note, archived: archived }
|
|
2993
|
+
}
|
|
2994
|
+
/**
|
|
2995
|
+
* 回执通道(契约 §3/§6.3):代理即使一次 Lean 工具都没调用,也必须能留下"实现难度判断"。
|
|
2996
|
+
* decision='blocked' 时 note 必填(否则拒绝记录并公告);'used' 记录草稿文件 → attempted;
|
|
2997
|
+
* decision='defect' 时 note 必填(具体偏差),落库语义见 §4.1 与 `formalRecordDefect`。
|
|
2998
|
+
*/
|
|
2999
|
+
async function absorbFormalReply(f, memberId) {
|
|
3000
|
+
if (!formalOn() || !f || typeof f !== 'object') return
|
|
3001
|
+
const who = memberId || 'member'
|
|
3002
|
+
const target = formalId(f.target)
|
|
3003
|
+
if (!target) return
|
|
3004
|
+
const decision = String(f.decision || '')
|
|
3005
|
+
if (decision === 'blocked') {
|
|
3006
|
+
const note = String(f.note || '').trim()
|
|
3007
|
+
if (!note) { await formalAnnounce('【形式化】' + who + ' 的 formal.decision=blocked 未写明 note,已**拒绝**记录(难度判断必须显式、可审计)。'); return }
|
|
3008
|
+
await putFormal(target, Object.assign({}, formalOf(target), { status: 'blocked', decision: 'blocked', note: note, updatedAt: now() }))
|
|
3009
|
+
await upsertFormalAnchor(target)
|
|
3010
|
+
await rebuildLeanLibIndexes()
|
|
3011
|
+
await formalAnnounce('【形式化】' + who + ' 通过回执记录 ' + target + ' 形式化阻塞:' + note)
|
|
3012
|
+
return
|
|
3013
|
+
}
|
|
3014
|
+
if (decision === 'defect') {
|
|
3015
|
+
const note = String(f.note || '').trim()
|
|
3016
|
+
if (!note) {
|
|
3017
|
+
// 与 blocked 同样"显式、可审计":没写出具体偏差就无法复核,整条记录拒绝(返回该架构的错误码)。
|
|
3018
|
+
await formalAnnounce('【形式化】' + who + ' 的 formal.decision=defect 未写明 note,已**拒绝**记录'
|
|
3019
|
+
+ '(忠实性缺陷必须写出具体偏差,否则无从复核)。该对象的形式化记录与归档证明**保持不变**。')
|
|
3020
|
+
return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'defect 记录必须写明具体偏差(note)——"形式化与命题不一致"必须显式、可复核' }
|
|
3021
|
+
}
|
|
3022
|
+
return await formalRecordDefect(target, note, who)
|
|
3023
|
+
}
|
|
3024
|
+
if (decision === 'used') {
|
|
3025
|
+
const prev = formalOf(target)
|
|
3026
|
+
const file = String(f.file || ('Formal/' + target + '.lean'))
|
|
3027
|
+
await putFormal(target, Object.assign({}, prev, { status: prev.status === 'passed' ? 'passed' : 'attempted', decision: 'used', file: file, updatedAt: now() }))
|
|
3028
|
+
await upsertFormalAnchor(target)
|
|
3029
|
+
await rebuildLeanLibIndexes()
|
|
3030
|
+
await formalAnnounce('【形式化】' + who + ' 通过回执记录 ' + target + ' 形式化草稿:' + file)
|
|
3031
|
+
return
|
|
3032
|
+
}
|
|
3033
|
+
await formalAnnounce('【形式化】' + who + ' 的 formal.decision 只能是 \'used\'、\'blocked\' 或 \'defect\'(收到 ' + String(f.decision) + '),已忽略。')
|
|
3034
|
+
}
|
|
3035
|
+
|
|
2291
3036
|
// ================= verification (验证器) =================
|
|
2292
3037
|
function consensus(t) { const vs = Object.keys(t.childResults).map(function (cid) { return t.childResults[cid].Result }); if (vs.length === 0) return false; return vs.every(function (v) { return v === 1 }) || vs.every(function (v) { return v === 0 }) }
|
|
2293
3038
|
function buildTranscript(t) { const parts = []; const cids = Object.keys(t.childResults); for (let i = 0; i < cids.length; i++) { const r = t.childResults[cids[i]]; parts.push('Reviewer ' + i + ': Result=' + r.Result + ' Reason=' + r.Reason) } return parts.join('\n') }
|
|
@@ -2296,6 +3041,9 @@ export function apply(ctx) {
|
|
|
2296
3041
|
const parsed = parseJson(output)
|
|
2297
3042
|
const Result = clamp01((parsed && parsed.Result != null) ? parsed.Result : 0.5)
|
|
2298
3043
|
const Reason = (parsed && parsed.Reason) || ''
|
|
3044
|
+
// 回执通道(契约 §4/§6.3):验证者在回执里给出的难度判断/阻塞原因也要落成正式记录,
|
|
3045
|
+
// 否则提示词里承诺的 formal 字段就是一条"框架收不到"的死通道。
|
|
3046
|
+
if (parsed && parsed.formal && typeof parsed.formal === 'object') await absorbFormalReply(parsed.formal, childId)
|
|
2299
3047
|
let t = tasks['verify:' + rId]
|
|
2300
3048
|
if (!t) { t = { id: 'verify:' + rId, type: 'verify', r: { kind: 'proposition', pId: rId, 概述: rId }, rId: rId, status: 'debating', children: [], childResults: {}, history: [], round: 1, expectedCount: Math.max(2, params.verifierCount), createdAt: now() }; tasks[t.id] = t }
|
|
2301
3049
|
if (!parsed && !scheduler.running) {
|
|
@@ -2391,6 +3139,18 @@ export function apply(ctx) {
|
|
|
2391
3139
|
verifierAccuracy[cids[i]] = acc
|
|
2392
3140
|
}
|
|
2393
3141
|
await writeJson('Logs/Verification/' + t.rId + '_' + Date.now() + '.json', { r: r, verdict: v, results: t.childResults, transcript: buildTranscript(t), history: t.history || [], at: now() })
|
|
3142
|
+
// ── require 门禁(契约 §8)────────────────────────────────────────────────
|
|
3143
|
+
// 判定为真(严格证明)或假(严格反驳)之前必须先有 `passed` 或 `blocked`。门禁放在**改
|
|
3144
|
+
// 对象之前**:不这样就改写不出"不改变对象的既有权重/概率字段"(一旦先把 概率 写成 1,
|
|
3145
|
+
// 下一 tick 的 processStatusUpdates 还会照着 prob=1 的条目再把它写回去)。
|
|
3146
|
+
// 记录票数(上面那份 Logs/Verification 已落盘)→ 记未定论 + 形式化待办 + 群聊公告 → 返回,
|
|
3147
|
+
// 对象留在原库、可形式化后再次提议,系统继续推进,绝不卡死。
|
|
3148
|
+
const gTarget = formalConclusionTarget(r, v)
|
|
3149
|
+
if (gTarget && formalBlocksConclusion(gTarget)) {
|
|
3150
|
+
const d = await deferForFormal(gTarget, formalRequiredWhy(gTarget), v === 1)
|
|
3151
|
+
logActivity('verdict', t.rId + ' = ' + v + ' 被 require 门禁搁置(formal-required;对象 ' + gTarget + ' 尚无 Lean 通过或阻塞记录)' + (d.already ? '(已在待办中)' : ''))
|
|
3152
|
+
return
|
|
3153
|
+
}
|
|
2394
3154
|
if (r.kind === 'proposition') {
|
|
2395
3155
|
const p = propos.get(r.pId)
|
|
2396
3156
|
if (p) {
|
|
@@ -2563,6 +3323,7 @@ export function apply(ctx) {
|
|
|
2563
3323
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
2564
3324
|
queuedPlanActions: planQueue.length,
|
|
2565
3325
|
plannerEnabled: params.plannerEnabled, plannerFails: plannerFails,
|
|
3326
|
+
formal: formalSummary(),
|
|
2566
3327
|
recentActivity: activityLog.slice(-Math.min(10, Number(params.activityLogCap) || 100)), params: params,
|
|
2567
3328
|
}
|
|
2568
3329
|
}
|
|
@@ -2611,7 +3372,7 @@ export function apply(ctx) {
|
|
|
2611
3372
|
if (!create && !exists) return { ok: false, message: 'project not found: ' + slug }
|
|
2612
3373
|
if (scheduler.running) await abortScheduler()
|
|
2613
3374
|
currentProject = slug; await writeCurrentProject(); await ensureDirs()
|
|
2614
|
-
params = Object.assign({}, DEFAULT_PARAMS); scheduler = { running: false, startedAt: 0, lastCheckpoint: 0, gate: null }; agentRegistry = {}; decisionQueue = []; verifierAccuracy = {}; tasks = {}; explorerRetries = {}; activityLog = []; planQueue = []; plannerFails = 0; methodLog = { pendingInventions: [], keepCount: 0, lastKeepAt: 0 }; projectLock = { sessionId: '', at: 0 }; lastReportWrite = 0; lastPushReport = 0; reportDirty = false; lastPlanSummary = null; archivedJ = {}; lastIndexWrite = 0
|
|
3375
|
+
params = Object.assign({}, DEFAULT_PARAMS); scheduler = { running: false, startedAt: 0, lastCheckpoint: 0, gate: null }; agentRegistry = {}; decisionQueue = []; verifierAccuracy = {}; tasks = {}; explorerRetries = {}; activityLog = []; planQueue = []; plannerFails = 0; methodLog = { pendingInventions: [], keepCount: 0, lastKeepAt: 0 }; projectLock = { sessionId: '', at: 0 }; lastReportWrite = 0; lastPushReport = 0; reportDirty = false; lastPlanSummary = null; archivedJ = {}; lastIndexWrite = 0; formalState = { records: {}, todo: [], libRuns: {} }
|
|
2615
3376
|
await loadSettings(); await migrateLegacyParams(); await loadState(); await loadKnowledgeBase(); await saveAll()
|
|
2616
3377
|
if (params.indexAutoRebuild) await rebuildIndex()
|
|
2617
3378
|
return { ok: true, project: slug, frameworkRoot: frameworkRoot() }
|
|
@@ -2658,6 +3419,29 @@ export function apply(ctx) {
|
|
|
2658
3419
|
registerTool('vibe_math_release_write', 'Release the write lock for one target file (relative to the project root). Call after you finished writing it.', objParams({ target: { type: 'string' } }, ['target']), async function (args, agent) { return await releaseWrite(String(args.target || ''), agent) })
|
|
2659
3420
|
registerTool('vibe_math_sync_meta', 'Report lightweight scheduling metadata after you wrote content to Markdown files (direction status/survival, registered lemma ids, methods_used/new_inventions, new method cards). Content itself stays in the md files; this only keeps the scheduler index/state in sync.', objParams({ meta: { type: 'object' } }, ['meta']), async function (args, agent) { return await syncMeta(args.meta || {}, agent) })
|
|
2660
3421
|
|
|
3422
|
+
// ---- Lean 形式化验证工具(契约 docs/formal-verification.md §5)----
|
|
3423
|
+
// **无条件注册**:注册是静态的(与既有 ctx.effect 纪律一致),模式只决定框架是否主动告诉
|
|
3424
|
+
// 代理它们存在;off 模式下人/代理主动调用时照常工作。
|
|
3425
|
+
registerTool('vibe_math_lean_run', '(member) Execute the Lean toolchain on one .lean file inside the workspace and report the result. Never throws: a missing toolchain returns LEAN_NOT_FOUND, a non-zero exit returns the compiler output. Pass target=<object id> to also record the run against that object.', objParams({ file: { type: 'string' }, target: { type: 'string' }, timeout_ms: { type: 'integer' } }, ['file']), async function (args, agent) { return await leanRunTool(agent && agent.id, args) })
|
|
3426
|
+
registerTool('vibe_math_lean_archive', '(member) Archive Lean code. kind="def": a REUSABLE definition/object/assumption → the global cross-project library (VibeMath/Formal/Lib). kind="lemma": a machine-checked lemma → VibeMath/Formal/Proved. kind="proof": the formal proof of a project object → Formal/<target>.lean, and (when the run passes) also Verified/Lean/<target>.lean, marking the object Lean-passed. kind="blocked": record an explicit, reasoned "cannot/not worth formalizing" decision (note required).', objParams({ kind: { type: 'string', enum: ['def', 'lemma', 'proof', 'blocked'] }, name: { type: 'string' }, target: { type: 'string' }, content: { type: 'string' }, from: { type: 'string' }, note: { type: 'string' }, run: { type: 'boolean' } }, ['kind']), async function (args, agent) { return await leanArchive(agent && agent.id, args) })
|
|
3427
|
+
registerTool('vibe_math_lean_lib', "(member) List (and by default rebuild) the Lean reuse library: this project's Formal/Index.md, plus the global cross-project Formal/Lib and Formal/Proved indexes. Look here BEFORE writing a new definition so you reuse instead of redefining.", objParams({ refresh: { type: 'boolean' } }), async function (args) {
|
|
3428
|
+
const a = args || {}
|
|
3429
|
+
const r = a.refresh === false
|
|
3430
|
+
? { lib: null, proved: null, objects: Object.keys(formalRecords()).length }
|
|
3431
|
+
: await rebuildLeanLibIndexes()
|
|
3432
|
+
return {
|
|
3433
|
+
ok: true, mode: formalMode(), rebuilt: a.refresh !== false,
|
|
3434
|
+
counts: r, todo: formalTodo().map(function (t) { return { id: t.id, why: t.why, at: t.at } }),
|
|
3435
|
+
objects: Object.keys(formalRecords()).map(function (k) {
|
|
3436
|
+
const rec = formalRecords()[k] || {}
|
|
3437
|
+
return { target: k, status: rec.status, file: rec.file, proof: rec.proof, note: rec.note }
|
|
3438
|
+
}),
|
|
3439
|
+
paths: { project: 'Formal/(相对项目根)', lib: 'VibeMath/Formal/Lib/', proved: 'VibeMath/Formal/Proved/', proofs: 'Verified/Lean/' },
|
|
3440
|
+
hint: "复用优先:先在 Lib/ 里找现成定义;新定义用 vibe_math_lean_archive kind='def' 归档,已证引理用 kind='lemma';归档前先跑通(run=true 或先 vibe_math_lean_run),跑不通不要入库。",
|
|
3441
|
+
verify: (Object.keys(tasks).length ? String(tasks[Object.keys(tasks)[0]].rId || '') : null),
|
|
3442
|
+
}
|
|
3443
|
+
})
|
|
3444
|
+
|
|
2661
3445
|
// ---- 代理直接写 md 的写锁 + 轻元数据同步(任务2:代理自组织写各自对应路径的 md,避免并发写同一文件) ----
|
|
2662
3446
|
/**
|
|
2663
3447
|
* 写锁键 = 归一化后的项目内相对路径。
|
|
@@ -2905,6 +3689,11 @@ export function apply(ctx) {
|
|
|
2905
3689
|
registerTool('vibe_math_claim_write', 'Acquire the write lock for one target file (relative to the project root). Call before writing a Markdown file directly.', objParams({ target: { type: 'string' } }, ['target']), 'vibe_math_claim_write')
|
|
2906
3690
|
registerTool('vibe_math_release_write', 'Release the write lock for one target file (relative to the project root).', objParams({ target: { type: 'string' } }, ['target']), 'vibe_math_release_write')
|
|
2907
3691
|
registerTool('vibe_math_sync_meta', 'After you write content into Markdown files, report ONLY lightweight scheduling metadata to keep the scheduler state in sync (content stays in the md files). meta.kind must be one of:\n- "directions": {qid, directions:[{id,title,method,core_assumption,feasibility}], methods_used:[{id,效果,建议}], new_inventions:[{类型,标题,内容描述,是否已入库}]}\n- "solver": {qid, dirId, round, survival, status:"continue|success|dead-end", dead_end_reason, lemmas:[{id,title,statement,proof,prob,分类,优先级}], methods_used, new_inventions, solution_prob, solution_text, sub_questions:[{q_sub_title,q_sub_statement,assumption_title,assumption_statement}]}\n- "methods": {used:[{id,效果,建议}], created:[ids], improvements:[{id,改进内容,原因}]}', objParams({ meta: { type: 'object' } }, ['meta']), 'vibe_math_sync_meta')
|
|
3692
|
+
// Lean 形式化验证(docs/formal-verification.md):三个工具**无条件注册**——注册是静态的,
|
|
3693
|
+
// 模式只决定框架是否主动告诉代理它们存在。off 模式下人/代理主动调用时照常工作。
|
|
3694
|
+
registerTool('vibe_math_lean_run', '(member) Execute the Lean toolchain on one .lean file inside the workspace and report the result. Never throws: a missing toolchain returns LEAN_NOT_FOUND, a non-zero exit returns the compiler output. Pass target=<object id> to also record the run against that object.', objParams({ file: { type: 'string' }, target: { type: 'string' }, timeout_ms: { type: 'integer' } }, ['file']), 'vibe_math_lean_run')
|
|
3695
|
+
registerTool('vibe_math_lean_archive', '(member) Archive Lean code. kind="def": a REUSABLE definition/object/assumption → the global cross-project library (VibeMath/Formal/Lib). kind="lemma": a machine-checked lemma → VibeMath/Formal/Proved. kind="proof": the formal proof of a project object → Formal/<target>.lean, and (when the run passes) also Verified/Lean/<target>.lean, marking the object Lean-passed. kind="blocked": record an explicit, reasoned "cannot/not worth formalizing" decision (note required).', objParams({ kind: { type: 'string', enum: ['def', 'lemma', 'proof', 'blocked'] }, name: { type: 'string' }, target: { type: 'string' }, content: { type: 'string' }, from: { type: 'string' }, note: { type: 'string' }, run: { type: 'boolean' } }, ['kind']), 'vibe_math_lean_archive')
|
|
3696
|
+
registerTool('vibe_math_lean_lib', "(member) List (and by default rebuild) the Lean reuse library: this project's Formal/Index.md, plus the global cross-project Formal/Lib and Formal/Proved indexes. Look here BEFORE writing a new definition so you reuse instead of redefining.", objParams({ refresh: { type: 'boolean' } }), 'vibe_math_lean_lib')
|
|
2908
3697
|
|
|
2909
3698
|
// /vibe slash command (registered once; routed per session)
|
|
2910
3699
|
ctx.effect(() => commands.register({
|