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
|
@@ -51,6 +51,7 @@ const EV = {
|
|
|
51
51
|
queue: 'vibe5/queue',
|
|
52
52
|
counters: 'vibe5/counters',
|
|
53
53
|
progress: 'vibe5/progress',
|
|
54
|
+
formal: 'vibe5/formal',
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
// Stable error codes (ported from DSH agent-teams' typed-error discipline).
|
|
@@ -135,6 +136,10 @@ export function apply(ctx) {
|
|
|
135
136
|
meetings: [],
|
|
136
137
|
debates: [],
|
|
137
138
|
verdicts: {},
|
|
139
|
+
// Per-object Lean formalization record (docs/formal-verification.md). Kept in the
|
|
140
|
+
// projection so it survives restore with zero context cost, exactly like verdicts.
|
|
141
|
+
formal: {},
|
|
142
|
+
todo: [],
|
|
138
143
|
queue: [],
|
|
139
144
|
counters: { academician: 0, researcher: 0, temp: 0, task: 0, meeting: 0, message: 0, verify: 0 },
|
|
140
145
|
runId: '',
|
|
@@ -252,6 +257,16 @@ export function apply(ctx) {
|
|
|
252
257
|
return Object.assign({}, inst, { verdicts })
|
|
253
258
|
})
|
|
254
259
|
}
|
|
260
|
+
if (t === EV.formal) {
|
|
261
|
+
return withInstitute(state, key, (inst) => {
|
|
262
|
+
if (!d.target || typeof d.target !== 'string') return inst
|
|
263
|
+
const formal = Object.assign({}, inst.formal)
|
|
264
|
+
if (d.record === null) delete formal[d.target]
|
|
265
|
+
else formal[d.target] = d.record
|
|
266
|
+
const todo = d.todo === undefined ? (inst.todo || []) : (Array.isArray(d.todo) ? d.todo : (inst.todo || []))
|
|
267
|
+
return Object.assign({}, inst, { formal, todo })
|
|
268
|
+
})
|
|
269
|
+
}
|
|
255
270
|
if (t === EV.queue) {
|
|
256
271
|
return withInstitute(state, key, (inst) => Object.assign({}, inst, { queue: Array.isArray(d.queue) ? d.queue : [] }))
|
|
257
272
|
}
|
|
@@ -396,6 +411,14 @@ export function apply(ctx) {
|
|
|
396
411
|
chatDigestMs: 45000,
|
|
397
412
|
chatDigestMax: 12,
|
|
398
413
|
meetingKeepEvery: 5,
|
|
414
|
+
// ── Lean formal verification (§ docs/formal-verification.md) ─────────
|
|
415
|
+
// 'off' — 不额外进行任何要求(默认)
|
|
416
|
+
// 'encourage' — 鼓励:验证时按实现难度决定是否用 Lean 形式化;平时顺手形式化可复用对象
|
|
417
|
+
// 'require' — 强制:真/假结论必须已有「形式化已通过」或「显式阻塞原因」,否则记为未定论
|
|
418
|
+
formalVerify: 'off',
|
|
419
|
+
leanCommand: 'lean',
|
|
420
|
+
leanArgs: [],
|
|
421
|
+
leanTimeoutMs: 120000,
|
|
399
422
|
// ── model / tools ────────────────────────────────────────────────────
|
|
400
423
|
provider: '',
|
|
401
424
|
model: '',
|
|
@@ -898,6 +921,15 @@ export function apply(ctx) {
|
|
|
898
921
|
// failed provision invisible to the whole institute.
|
|
899
922
|
const absent = inst().members.filter((m) => m.phase !== 'active' && m.phase !== 'dismissed')
|
|
900
923
|
if (absent.length) b.push('[未就位] ' + absent.map((m) => m.id + '(' + m.phase + ')').join('、'))
|
|
924
|
+
// Lean mode is a RUNTIME knob, so its line is computed here (per round) rather than
|
|
925
|
+
// frozen into the charter — changing the mode must reach members immediately.
|
|
926
|
+
if (formalOn()) {
|
|
927
|
+
const recs = formalRecords()
|
|
928
|
+
const passed = Object.keys(recs).filter((k) => recs[k] && recs[k].status === 'passed').length
|
|
929
|
+
const blocked = Object.keys(recs).filter((k) => recs[k] && recs[k].status === 'blocked').length
|
|
930
|
+
b.push('[形式化] ' + (formalMode() === 'require' ? '强制' : '鼓励') + ' Lean|已通过 ' + passed
|
|
931
|
+
+ '|已记录阻塞 ' + blocked + (formalTodo().length ? '|形式化待办 ' + formalTodo().length + ' 项(见 Formal/TODO.md)' : ''))
|
|
932
|
+
}
|
|
901
933
|
const tasks = inst().tasks.filter((t) => t.status !== 'deleted')
|
|
902
934
|
const mine = tasks.filter((t) => t.ownerId === member.id && t.status === 'in_progress')
|
|
903
935
|
const ready = tasks.filter((t) => t.status === 'pending' && taskReady(t))
|
|
@@ -983,13 +1015,18 @@ export function apply(ctx) {
|
|
|
983
1015
|
}
|
|
984
1016
|
async function mkdirs() {
|
|
985
1017
|
const base = instRoot()
|
|
986
|
-
const dirs = ['Shared/Chat', 'Shared/Meetings', 'Shared/Debates', 'State', 'Problems']
|
|
1018
|
+
const dirs = ['Shared/Chat', 'Shared/Meetings', 'Shared/Debates', 'State', 'Problems', 'Formal', 'Verified/Lean']
|
|
1019
|
+
// The REUSABLE Lean library is global (cross-project), so it hangs off the VibeMath
|
|
1020
|
+
// root rather than the institute root — creating it under instRoot would scatter a
|
|
1021
|
+
// second, invisible copy per institute.
|
|
1022
|
+
const globalDirs = ['Formal/Lib', 'Formal/Proved']
|
|
987
1023
|
for (const m of activeMembers()) {
|
|
988
1024
|
for (const d of ['Progress', 'Propos', 'Methods', 'Subproblems']) dirs.push('Members/' + m.id + '/' + d)
|
|
989
1025
|
}
|
|
1026
|
+
const paths = dirs.map((d) => base + '/' + d).concat(globalDirs.map((d) => vibeRoot() + '/' + d))
|
|
990
1027
|
const script = isWindows()
|
|
991
|
-
? 'New-Item -Force -ItemType Directory -Path ' +
|
|
992
|
-
: 'mkdir -p ' +
|
|
1028
|
+
? 'New-Item -Force -ItemType Directory -Path ' + paths.map((p) => psQuote(p)).join(',') + ' | Out-Null'
|
|
1029
|
+
: 'mkdir -p ' + paths.map((p) => shQuote(p)).join(' ')
|
|
993
1030
|
return await runShell(script)
|
|
994
1031
|
}
|
|
995
1032
|
|
|
@@ -1344,6 +1381,12 @@ export function apply(ctx) {
|
|
|
1344
1381
|
L.push(' "task_done": "t-3",')
|
|
1345
1382
|
L.push(' "task_update": {"task_id":"t-3","expected_revision":2,"action":"complete|release|reopen|edit|set_dependencies|delete"},')
|
|
1346
1383
|
L.push(' "input": "本轮会议/辩论的发言正文(会议轮用;也可直接用 say)",')
|
|
1384
|
+
if (formalOn()) {
|
|
1385
|
+
L.push(' "formal": {"target":"p-x","decision":"used|blocked|defect","file":"Formal/p-x.lean","note":"难度判断/阻塞原因"}')
|
|
1386
|
+
L.push(' ← Lean 形式化:' + (formalMode() === 'require'
|
|
1387
|
+
? '**强制**:定论前必须有「Lean 已通过」或显式阻塞原因(note 必填),否则本轮裁定记为未定论,'
|
|
1388
|
+
: '**鼓励**:按实现难度自行决定;做了就归档,没做就写明难度判断,') + '详见提示词里的【Lean 形式化验证】段')
|
|
1389
|
+
}
|
|
1347
1390
|
L.push(' "reject_assign": {"task_id":"t-3","why":"你对这项分派的异议理由"} ← 有异议时填;理由会被广播给')
|
|
1348
1391
|
L.push(' 全体表决者(任务仍会执行,但你的理由不会被埋掉),')
|
|
1349
1392
|
if (kind !== 'temp') {
|
|
@@ -1399,6 +1442,7 @@ export function apply(ctx) {
|
|
|
1399
1442
|
L.push('作为院士,除了做研究,你还要**统筹全所**:用 vibe_v5_overview 看清谁在做什么、')
|
|
1400
1443
|
L.push('哪里是瓶颈;把工作拆成任务并用 vibe_v5_assign 分派;必要时用 vibe_v5_nudge 督办。')
|
|
1401
1444
|
}
|
|
1445
|
+
if (formalOn()) { L.push(''); L.push(formalWorkLine()) }
|
|
1402
1446
|
L.push('')
|
|
1403
1447
|
L.push('------------')
|
|
1404
1448
|
L.push(stateBlock(member))
|
|
@@ -1414,6 +1458,7 @@ export function apply(ctx) {
|
|
|
1414
1458
|
L.push('读一读同事的库、推进你的子问题/引理/方法、尝试一条新路线;')
|
|
1415
1459
|
L.push('或者向团队发消息(say)、开一个议题(propose_meeting)、给某个方向开任务(task_create)。')
|
|
1416
1460
|
L.push('如果你确实已无路可走或认为原问题接近解决,请说明你的判断与理由。')
|
|
1461
|
+
if (formalOn()) { L.push(''); L.push(formalWorkLine()) }
|
|
1417
1462
|
L.push('')
|
|
1418
1463
|
L.push('------------')
|
|
1419
1464
|
L.push(stateBlock(member))
|
|
@@ -1461,6 +1506,19 @@ export function apply(ctx) {
|
|
|
1461
1506
|
L.push(' 介于 0 与 1 之间(例如 0.9)表示你不确定——这会被记为**弃权/存疑**,')
|
|
1462
1507
|
L.push(' 不计入法定票数 m,但会计入全组平均概率。')
|
|
1463
1508
|
L.push('')
|
|
1509
|
+
if (formalOn()) {
|
|
1510
|
+
const rec = formalOf(vs.target)
|
|
1511
|
+
L.push(formalPromptBlock(vs.target))
|
|
1512
|
+
// Make the SHIFT explicit: with a machine-checked proof in hand, re-deriving is
|
|
1513
|
+
// wasted effort and the real risk is a statement that does not say what we meant.
|
|
1514
|
+
if (rec.status === 'blocked') {
|
|
1515
|
+
L.push(' ▸ 因此请把 verdict 用在"这个阻塞判断是否成立 / 是否仍有别的形式化路线"上,并给出理由。')
|
|
1516
|
+
} else if (rec.status !== 'passed') {
|
|
1517
|
+
L.push(' ▸ 若你在本轮把它形式化并跑通(vibe_v5_lean_archive kind=\'proof\'),后续轮次的')
|
|
1518
|
+
L.push(' 审查对象就会从"推导是否正确"变成"Lean 代码是否忠实于命题"。')
|
|
1519
|
+
}
|
|
1520
|
+
L.push('')
|
|
1521
|
+
}
|
|
1464
1522
|
if (vs.stage === 'debate' && vs.history) {
|
|
1465
1523
|
L.push('### 上一轮各成员的意见(框架已公开给你,请参考后重新判断)')
|
|
1466
1524
|
for (const [k, v] of Object.entries(vs.history)) {
|
|
@@ -1477,6 +1535,12 @@ export function apply(ctx) {
|
|
|
1477
1535
|
L.push('------------')
|
|
1478
1536
|
L.push('结束时请**只**输出一个 JSON 对象(```json 围栏内):')
|
|
1479
1537
|
L.push('{"verdict":{"target":"' + vs.target + '","verdict":<0-1 数值>,"reason":"<你的理由>"}, "contextPct": 40}')
|
|
1538
|
+
if (formalOn()) {
|
|
1539
|
+
// The formal field belongs in the VOTING contract too: voters are exactly the agents
|
|
1540
|
+
// who must either formalize the object or record why they judged it infeasible.
|
|
1541
|
+
L.push('若你本轮做了形式化或给出难度判断,请一并加上:')
|
|
1542
|
+
L.push('{"formal":{"target":"' + vs.target + '","decision":"used|blocked|defect","file":"Formal/' + vs.target + '.lean","note":"难度判断/阻塞原因"}}')
|
|
1543
|
+
}
|
|
1480
1544
|
return L.join('\n')
|
|
1481
1545
|
}
|
|
1482
1546
|
function kindLabel2(k) { return k === 'method' ? '方法/理论' : k === 'subproblem' ? '子问题' : '命题' }
|
|
@@ -1542,6 +1606,412 @@ export function apply(ctx) {
|
|
|
1542
1606
|
return t
|
|
1543
1607
|
}
|
|
1544
1608
|
|
|
1609
|
+
// ================= Lean formal verification ==============================
|
|
1610
|
+
// Contract: docs/formal-verification.md.
|
|
1611
|
+
//
|
|
1612
|
+
// The point of this feature is a SHIFT IN WHAT MUST BE REVIEWED, not an extra chore.
|
|
1613
|
+
// Consensus verification answers "do we all believe this?"; a machine-checked Lean
|
|
1614
|
+
// development answers "is this true?" and shrinks the open question to one a human can
|
|
1615
|
+
// actually audit:
|
|
1616
|
+
//
|
|
1617
|
+
// do the Lean definitions / objects / conditions / assumptions / conclusion
|
|
1618
|
+
// match the proposition as originally stated?
|
|
1619
|
+
//
|
|
1620
|
+
// So once a Lean run passes, the voting prompt stops asking voters to redo the
|
|
1621
|
+
// derivation and asks them to do a FIDELITY review. `require` mode makes that concrete:
|
|
1622
|
+
// a 真/假 verdict does not take effect until the object is either `passed` or has an
|
|
1623
|
+
// explicit, reasoned `blocked` record — "decide by difficulty, but decide out loud".
|
|
1624
|
+
const FORMAL_MODES = ['off', 'encourage', 'require']
|
|
1625
|
+
const formalMode = () => (FORMAL_MODES.indexOf(String(params.formalVerify)) !== -1 ? String(params.formalVerify) : 'off')
|
|
1626
|
+
const formalOn = () => formalMode() !== 'off'
|
|
1627
|
+
const formalRoot = () => instRoot() + '/Formal'
|
|
1628
|
+
const formalLibRoot = () => vibeRoot() + '/Formal/Lib'
|
|
1629
|
+
const formalProvedRoot = () => vibeRoot() + '/Formal/Proved'
|
|
1630
|
+
const verifiedLeanRoot = () => instRoot() + '/Verified/Lean'
|
|
1631
|
+
const formalRecords = () => (inst().formal || {})
|
|
1632
|
+
const formalTodo = () => (inst().todo || [])
|
|
1633
|
+
function formalOf(target) {
|
|
1634
|
+
const r = formalRecords()[idSafe(String(target || ''))]
|
|
1635
|
+
return r || { status: 'none' }
|
|
1636
|
+
}
|
|
1637
|
+
async function putFormal(target, record, todo) {
|
|
1638
|
+
await commit(EV.formal, { target: idSafe(String(target)), record: record || null, todo })
|
|
1639
|
+
}
|
|
1640
|
+
// `passed` requires a GREEN RUN, not merely an archived file: a proof file that has
|
|
1641
|
+
// never been executed proves nothing.
|
|
1642
|
+
const formalGateOk = (rec) => !!rec && (rec.status === 'passed' || rec.status === 'blocked')
|
|
1643
|
+
function formalStatusLine(target) {
|
|
1644
|
+
const r = formalOf(target)
|
|
1645
|
+
if (r.status === 'passed') return 'Lean 通过(' + (r.proof || r.file || '') + ')'
|
|
1646
|
+
if (r.status === 'blocked') return '阻塞(' + (r.note || '未说明') + ')'
|
|
1647
|
+
if (r.status === 'attempted') return '已尝试未通过'
|
|
1648
|
+
return '未尝试'
|
|
1649
|
+
}
|
|
1650
|
+
const tail = (s, n) => { const t = String(s == null ? '' : s); return t.length > n ? t.slice(-n) : t }
|
|
1651
|
+
|
|
1652
|
+
// Lexically normalise an absolute path (collapse '.', '..' and duplicate slashes)
|
|
1653
|
+
// WITHOUT touching the filesystem. A plain `startsWith(root)` check is not enough:
|
|
1654
|
+
// "…/VibeMath/Projects/../../../../etc/evil.lean" still starts with the root as a
|
|
1655
|
+
// string while resolving outside it.
|
|
1656
|
+
function normalizeAbsPath(p) {
|
|
1657
|
+
const parts = String(p == null ? '' : p).replace(/\\/g, '/').split('/')
|
|
1658
|
+
const out = []
|
|
1659
|
+
for (const seg of parts) {
|
|
1660
|
+
if (seg === '' ) { if (out.length === 0) out.push(''); continue }
|
|
1661
|
+
if (seg === '.') continue
|
|
1662
|
+
if (seg === '..') { if (out.length > 1) out.pop(); continue }
|
|
1663
|
+
out.push(seg)
|
|
1664
|
+
}
|
|
1665
|
+
return out.join('/')
|
|
1666
|
+
}
|
|
1667
|
+
// Resolve a Lean path to an absolute, NORMALISED path that is provably inside the
|
|
1668
|
+
// VibeMath root — or null. Every Lean file access (run, archive, read) goes through it.
|
|
1669
|
+
function leanAbsPath(rel) {
|
|
1670
|
+
const raw = String(rel == null ? '' : rel).trim()
|
|
1671
|
+
if (!raw) return null
|
|
1672
|
+
const abs = (raw.charAt(0) === '/' || /^[a-z]:/i.test(raw)) ? raw : instRoot() + '/' + raw.replace(/^\.\//, '')
|
|
1673
|
+
const norm = normalizeAbsPath(abs)
|
|
1674
|
+
const root = normalizeAbsPath(vibeRoot())
|
|
1675
|
+
if (norm !== root && norm.indexOf(root + '/') !== 0) return null
|
|
1676
|
+
return norm
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// Run the toolchain on one file. NEVER throws into the scheduler: every failure mode
|
|
1680
|
+
// (no service, no executable, timeout, non-zero exit) becomes a readable result.
|
|
1681
|
+
async function leanRunFile(relPath, timeoutMs) {
|
|
1682
|
+
const started = now()
|
|
1683
|
+
const rel = String(relPath || '').trim()
|
|
1684
|
+
if (!rel) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'file is required' }
|
|
1685
|
+
// Path guard: only files inside the VibeMath tree may be executed, so a crafted path
|
|
1686
|
+
// can never make the framework run something outside the workspace.
|
|
1687
|
+
const abs = leanAbsPath(rel)
|
|
1688
|
+
if (abs === null) {
|
|
1689
|
+
return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'Lean file must live under ' + vibeRoot().replace(/\\/g, '/') + '/ (got ' + rel + ')' }
|
|
1690
|
+
}
|
|
1691
|
+
if (!/\.lean$/.test(abs)) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'only .lean files can be executed' }
|
|
1692
|
+
if (await readTextAbs(abs) === undefined) return { ok: false, code: 'V5_NOT_FOUND', message: 'no such file: ' + rel }
|
|
1693
|
+
const sub = subprocessOf()
|
|
1694
|
+
if (sub === undefined || typeof sub.spawn !== 'function') {
|
|
1695
|
+
return { ok: false, code: 'NO_SUBPROCESS', message: 'the host exposes no subprocess service; Lean cannot be executed here', file: rel, ms: 0 }
|
|
1696
|
+
}
|
|
1697
|
+
const cap = Math.max(1000, Number(timeoutMs) || Number(params.leanTimeoutMs) || 120000)
|
|
1698
|
+
let exe
|
|
1699
|
+
try {
|
|
1700
|
+
exe = await sub.resolveExecutable(String(params.leanCommand || 'lean'))
|
|
1701
|
+
} catch (e) {
|
|
1702
|
+
return { ok: false, code: 'LEAN_NOT_FOUND', message: 'cannot resolve "' + String(params.leanCommand || 'lean') + '": ' + String((e && e.message) || e) + ' — 仍可把形式化代码写下来归档,但无法在此宿主上执行', file: rel, ms: now() - started }
|
|
1703
|
+
}
|
|
1704
|
+
const argv = [exe].concat((Array.isArray(params.leanArgs) ? params.leanArgs : []).map(String)).concat([abs])
|
|
1705
|
+
let handle
|
|
1706
|
+
try {
|
|
1707
|
+
handle = sub.spawn({
|
|
1708
|
+
argv,
|
|
1709
|
+
cwd: instRoot(),
|
|
1710
|
+
stdio: { stdin: 'ignore', stdout: { maxBytes: 64 * 1024 }, stderr: { maxBytes: 64 * 1024 } },
|
|
1711
|
+
graceMs: cap,
|
|
1712
|
+
})
|
|
1713
|
+
} catch (e) {
|
|
1714
|
+
return { ok: false, code: 'LEAN_SPAWN_FAILED', message: String((e && e.message) || e), file: rel, ms: now() - started }
|
|
1715
|
+
}
|
|
1716
|
+
let outcome
|
|
1717
|
+
try {
|
|
1718
|
+
outcome = await handle.done
|
|
1719
|
+
} catch (e) {
|
|
1720
|
+
return { ok: false, code: 'LEAN_RUN_FAILED', message: String((e && e.message) || e), file: rel, ms: now() - started }
|
|
1721
|
+
}
|
|
1722
|
+
let out = '', err = ''
|
|
1723
|
+
try { if (handle.collected && handle.collected.stdout) out = handle.collected.stdout.readFrom(0).text } catch (e) { /* best effort */ }
|
|
1724
|
+
try { if (handle.collected && handle.collected.stderr) err = handle.collected.stderr.readFrom(0).text } catch (e) { /* best effort */ }
|
|
1725
|
+
const exitCode = outcome ? outcome.exitCode : null
|
|
1726
|
+
const ms = now() - started
|
|
1727
|
+
const ok = exitCode === 0
|
|
1728
|
+
return {
|
|
1729
|
+
ok, exitCode, signal: (outcome && outcome.signal) || null, ms,
|
|
1730
|
+
command: argv.join(' '), file: rel,
|
|
1731
|
+
stdout: tail(out, 4000), stderr: tail(err, 4000),
|
|
1732
|
+
timedOut: ms >= cap,
|
|
1733
|
+
code: ok ? undefined : (ms >= cap ? 'LEAN_TIMEOUT' : 'LEAN_FAILED'),
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
async function formalSetRun(target, run) {
|
|
1737
|
+
const t = idSafe(String(target || ''))
|
|
1738
|
+
if (!t) return
|
|
1739
|
+
const prev = formalOf(t)
|
|
1740
|
+
// Running a file NEVER changes an already-decided status: a green run does not by
|
|
1741
|
+
// itself make an object `passed` (only archiving a proof does), and a failing scratch
|
|
1742
|
+
// run must not silently erase a recorded `passed`/`blocked` decision. Everything else
|
|
1743
|
+
// becomes `attempted`, which is the honest "we tried, see the compiler output" state.
|
|
1744
|
+
const keep = (prev.status === 'passed' || prev.status === 'blocked') ? prev.status : 'attempted'
|
|
1745
|
+
await putFormal(t, Object.assign({}, prev, {
|
|
1746
|
+
status: keep,
|
|
1747
|
+
file: run.file || prev.file || '',
|
|
1748
|
+
run: { at: now(), ok: !!run.ok, exitCode: run.exitCode === undefined ? null : run.exitCode, ms: run.ms || 0, stdoutTail: tail(run.stdout, 800), stderrTail: tail(run.stderr, 800) },
|
|
1749
|
+
updatedAt: now(),
|
|
1750
|
+
}))
|
|
1751
|
+
}
|
|
1752
|
+
// Withdrawing an archived proof needs a DELETE, but the fs service exposes no unlink and
|
|
1753
|
+
// subprocess is optional. Use the shell when it is available; otherwise overwrite the
|
|
1754
|
+
// file with an explicit withdrawal notice, so it can never be read as the object's proof.
|
|
1755
|
+
async function removeArchivedProof(rel) {
|
|
1756
|
+
const abs = leanAbsPath(rel)
|
|
1757
|
+
if (abs === null) return false
|
|
1758
|
+
const sub = subprocessOf()
|
|
1759
|
+
if (sub !== undefined && typeof sub.spawn === 'function') {
|
|
1760
|
+
const script = isWindows()
|
|
1761
|
+
? 'Remove-Item -LiteralPath ' + psQuote(abs) + ' -Force -ErrorAction SilentlyContinue'
|
|
1762
|
+
: 'rm -f ' + shQuote(abs)
|
|
1763
|
+
try { await runShell(script) } catch (e) { /* fall through to the overwrite */ }
|
|
1764
|
+
// A shell that exits 0 without removing anything (a stub host, a permissions quirk)
|
|
1765
|
+
// must not leave a withdrawn proof where everyone looks for proofs: verify, fall back.
|
|
1766
|
+
if (await readTextAbs(abs) === undefined) return true
|
|
1767
|
+
}
|
|
1768
|
+
return (await writeTextAbs(abs, '-- 已撤回(' + fmtTime() + '):该形式化被认定与命题原文不一致。\n'
|
|
1769
|
+
+ '-- 原代码保留在工作文件 Formal/' + String(rel).split('/').pop() + ';修正并重新跑通后重新归档。\n')) !== false
|
|
1770
|
+
}
|
|
1771
|
+
// A formalization that says something else than the proposition is NOT a refutation:
|
|
1772
|
+
// withdraw the proof instead of letting the group conclude 假 (contract §4.1).
|
|
1773
|
+
async function recordFidelityDefect(memberId, rawTarget, note) {
|
|
1774
|
+
const t = idSafe(String(rawTarget || ''))
|
|
1775
|
+
const why = String(note || '').trim()
|
|
1776
|
+
if (!t) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'target is required' }
|
|
1777
|
+
if (!why) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: "decision='defect' 必须写明 note(具体偏差)" }
|
|
1778
|
+
const prev = formalOf(t)
|
|
1779
|
+
const proofRel = String(prev.proof || ('Verified/Lean/' + t + '.lean'))
|
|
1780
|
+
await putFormal(t, Object.assign({}, prev, {
|
|
1781
|
+
status: 'attempted', proof: '', decision: 'defect', note: why,
|
|
1782
|
+
fidelity: { at: now(), by: String(memberId || ''), note: why }, updatedAt: now(),
|
|
1783
|
+
}))
|
|
1784
|
+
const removed = await removeArchivedProof(proofRel)
|
|
1785
|
+
const list = formalTodo().filter((x) => x.id !== t)
|
|
1786
|
+
list.push({ id: t, at: now(), why: '形式化不合格(忠实性缺陷):' + why })
|
|
1787
|
+
await putFormal(t, formalOf(t), list)
|
|
1788
|
+
await writeFormalTodo()
|
|
1789
|
+
await writeFormalIndex()
|
|
1790
|
+
await saveChatLine('【形式化】' + (memberId || '成员') + ' 认定 ' + t + ' 的形式化**不忠实**:' + why
|
|
1791
|
+
+ ' —— 已撤回「已通过」状态并从 Verified/Lean/ 删除归档证明;请修正形式化、重新跑通后再投票。')
|
|
1792
|
+
return { ok: true, target: t, status: 'attempted', removed }
|
|
1793
|
+
}
|
|
1794
|
+
function formalPromptBlock(target) {
|
|
1795
|
+
if (!formalOn()) return ''
|
|
1796
|
+
const mode = formalMode()
|
|
1797
|
+
const rec = target ? formalOf(target) : { status: 'none' }
|
|
1798
|
+
const L = []
|
|
1799
|
+
L.push('【Lean 形式化验证(' + (mode === 'require' ? '强制' : '鼓励') + '模式)】')
|
|
1800
|
+
if (rec.status === 'passed') {
|
|
1801
|
+
// The whole point of the feature: the review subject CHANGES. And the SECOND half of
|
|
1802
|
+
// that point (docs/formal-verification.md §4.1): a statement that says something else
|
|
1803
|
+
// than the proposition is NOT a refutation — "偏离 → 0" would make the very mechanism
|
|
1804
|
+
// that exists for rigour fabricate a false negative conclusion.
|
|
1805
|
+
L.push(' · 该对象已有**通过的 Lean 形式化证明**(' + (rec.proof || rec.file) + ',最近运行 exit 0)。')
|
|
1806
|
+
L.push(' **你不需要重新检查推导**。你的任务是**忠实性审查**:逐条核对 Lean 代码里的')
|
|
1807
|
+
L.push(' 定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**。')
|
|
1808
|
+
L.push(' ▸ 一致 → verdict = 1。')
|
|
1809
|
+
L.push(' ▸ **发现任何偏差,不要投 0**:偏差只说明**形式化不合格**,不代表命题为假。此时请:')
|
|
1810
|
+
L.push(' ① verdict 给一个严格介于 0 与 1 之间的值(记为弃权),并在 reason 里写清偏差;')
|
|
1811
|
+
L.push(" ② 用回执 formal:{decision:'defect', note:'<具体偏差>'} 记录它。框架会撤回这条证明的")
|
|
1812
|
+
// Only `require` actually GATES the conclusion; in `encourage` the framework still
|
|
1813
|
+
// withdraws the proof (and records the defect) but must not promise a hold it cannot
|
|
1814
|
+
// enforce — the voter's own abstention is what keeps the ballot from concluding.
|
|
1815
|
+
L.push(' 「已通过」状态(降级为 attempted、删除归档证明、写入形式化待办)'
|
|
1816
|
+
+ (mode === 'require' ? ',本次裁定**不定论**;' : ';'))
|
|
1817
|
+
L.push(' 修正形式化并重新跑通后再投票。')
|
|
1818
|
+
L.push(' ▸ 只有当你**独立于这份 Lean 代码**也能确定命题为假时,才投 0,并在 reason 里写清独立理由。')
|
|
1819
|
+
} else if (rec.status === 'blocked') {
|
|
1820
|
+
L.push(' · 该对象已被记录为**形式化阻塞**:' + (rec.note || '未说明') + '。')
|
|
1821
|
+
L.push(' 请复核这个判断是否成立;若你认为其实可以形式化,请指出来并动手做。')
|
|
1822
|
+
} else {
|
|
1823
|
+
L.push(' · 请先判断该对象的**实现难度**:若能在可接受的工作量内形式化,优先写 Lean 代码并执行。')
|
|
1824
|
+
L.push(' · 工具:vibe_v5_lean_run(执行)· vibe_v5_lean_archive(归档)· vibe_v5_lean_lib(查已有可复用库)')
|
|
1825
|
+
L.push(' · 工作目录:Formal/(相对研究所根);可复用定义放 ' + formalLibRoot().replace(/\\/g, '/') + '/,')
|
|
1826
|
+
L.push(' 已证引理放 ' + formalProvedRoot().replace(/\\/g, '/') + '/;写之前先 vibe_v5_lean_lib 查重。')
|
|
1827
|
+
L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义 / 对象 / 条件 / 假设 / 结论是否与')
|
|
1828
|
+
L.push(' 命题原文逐条一致。请把注意力放在这种核对上,而不是重新做一遍推导。')
|
|
1829
|
+
if (mode === 'require') {
|
|
1830
|
+
L.push(' · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(vibe_v5_lean_archive')
|
|
1831
|
+
L.push(' kind=\'blocked\' note=… 或回执 formal.note)。若两者都没有,本次裁定不会生效,')
|
|
1832
|
+
L.push(' 会被记为未定论(原因 formal-required)并进入「形式化待办」。')
|
|
1833
|
+
} else {
|
|
1834
|
+
L.push(" · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断(decision='blocked' 时必须写明 note)。")
|
|
1835
|
+
}
|
|
1836
|
+
L.push(' · 归档可复用定义/引理前先跑通(vibe_v5_lean_archive run=true 或先 vibe_v5_lean_run);跑不通不要入库。')
|
|
1837
|
+
L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"——这算显式阻塞原因,定论门禁可以据此放行。')
|
|
1838
|
+
}
|
|
1839
|
+
return L.join('\n')
|
|
1840
|
+
}
|
|
1841
|
+
function formalWorkLine() {
|
|
1842
|
+
if (!formalOn()) return ''
|
|
1843
|
+
return '【顺手形式化(' + (formalMode() === 'require' ? '强制' : '鼓励') + ')】把你工作中常用或可能复用的对象、假设、'
|
|
1844
|
+
+ '新定义用 Lean 形式化定义并归档到全局可复用库(vibe_v5_lean_archive kind=\'def\'),已成立的引理归到 Proved/'
|
|
1845
|
+
+ '(kind=\'lemma\');写之前先 vibe_v5_lean_lib 查重,避免重复定义。'
|
|
1846
|
+
+ '归档前先跑通(vibe_v5_lean_run 或 run=true);跑不通的定义不要进可复用库。'
|
|
1847
|
+
+ (formalMode() === 'require'
|
|
1848
|
+
? '本模式下,任何要定论为真/假的对象都必须先有 Lean 通过或显式阻塞记录。'
|
|
1849
|
+
: '这会让后续的验证与证明省掉大量重复工作。')
|
|
1850
|
+
}
|
|
1851
|
+
// ---- the three indexes (framework-maintained) ---------------------------
|
|
1852
|
+
async function writeFormalIndex() {
|
|
1853
|
+
const recs = formalRecords()
|
|
1854
|
+
const L = ['# Lean 形式化索引|' + instituteName + '|' + fmtTime(), '',
|
|
1855
|
+
'> 本文件由框架维护(工具调用时更新;`vibe_v5_lean_lib` 会重建)。权威状态在会话日志投影里。', '',
|
|
1856
|
+
'| 对象 | 状态 | 形式化文件 | 归档证明 | 最近运行 | 难度判断 / 阻塞原因 |', '|---|---|---|---|---|---|']
|
|
1857
|
+
const keys = Object.keys(recs)
|
|
1858
|
+
if (!keys.length) L.push('| (暂无) | | | | | |')
|
|
1859
|
+
for (const k of keys) {
|
|
1860
|
+
const r = recs[k] || {}
|
|
1861
|
+
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)') : '—'
|
|
1862
|
+
L.push('| ' + k + ' | ' + (r.status || 'none') + ' | ' + (r.file || '—') + ' | ' + (r.proof || '—') + ' | ' + run + ' | ' + String(r.note || '—').replace(/\|/g, '/').slice(0, 120) + ' |')
|
|
1863
|
+
}
|
|
1864
|
+
L.push('')
|
|
1865
|
+
if (formalTodo().length) {
|
|
1866
|
+
L.push('## 形式化待办(require 模式:定论被搁置)')
|
|
1867
|
+
for (const t of formalTodo()) L.push('- ' + t.id + ' —— ' + (t.why || 'formal-required') + '(' + fmtTime(t.at) + ')')
|
|
1868
|
+
L.push('')
|
|
1869
|
+
}
|
|
1870
|
+
await writeTextRel('Formal/Index.md', L.join('\n'))
|
|
1871
|
+
}
|
|
1872
|
+
async function writeFormalTodo() {
|
|
1873
|
+
const list = formalTodo()
|
|
1874
|
+
const L = ['# 形式化待办|' + instituteName + '|' + fmtTime(), '',
|
|
1875
|
+
'> 这些对象在 `require` 模式下尚不具备「Lean 已通过」或「显式阻塞记录」,因此**定论被搁置**。',
|
|
1876
|
+
'> 完成形式化(vibe_v5_lean_archive kind=\'proof\')或记录阻塞原因(kind=\'blocked\')后,重新提议验证即可。', '']
|
|
1877
|
+
if (!list.length) L.push('(暂无)')
|
|
1878
|
+
for (const t of list) L.push('- ' + t.id + '|' + (t.why || 'formal-required') + '|' + fmtTime(t.at))
|
|
1879
|
+
L.push('')
|
|
1880
|
+
await writeTextRel('Formal/TODO.md', L.join('\n'))
|
|
1881
|
+
}
|
|
1882
|
+
async function rebuildLeanLibIndexes() {
|
|
1883
|
+
// Listing must be CHEAP and side-effect free: it does NOT execute the toolchain
|
|
1884
|
+
// (running `lean` on every library file each time an agent asked "what can I reuse?"
|
|
1885
|
+
// would be slow and surprising). Per-object run results live in the object records
|
|
1886
|
+
// and are shown in Formal/Index.md.
|
|
1887
|
+
const scan = async (dirAbs, dirRel, kindLabel) => {
|
|
1888
|
+
const rows = []
|
|
1889
|
+
try {
|
|
1890
|
+
const t = await fs.resolve(dirAbs)
|
|
1891
|
+
if (await fs.stat(t) === undefined) return rows
|
|
1892
|
+
const entries = await fs.listDir(t)
|
|
1893
|
+
for (const e of entries || []) {
|
|
1894
|
+
if (!e || e.type !== 'file' || !/\.lean$/.test(String(e.name))) continue
|
|
1895
|
+
const rel = dirRel + '/' + e.name
|
|
1896
|
+
const txt = (await readTextAbs(dirAbs + '/' + e.name)) || ''
|
|
1897
|
+
const name = String(e.name).replace(/\.lean$/, '')
|
|
1898
|
+
const first = (txt.split('\n').filter((l) => l.trim() && !/^\s*(\/\/|--|import)/.test(l))[0] || '').trim().slice(0, 110)
|
|
1899
|
+
rows.push('| ' + name + ' | ' + rel + ' | ' + kindLabel + ' | ' + first.replace(/\|/g, '/') + ' |')
|
|
1900
|
+
}
|
|
1901
|
+
} catch (e) { /* listing is best-effort */ }
|
|
1902
|
+
return rows
|
|
1903
|
+
}
|
|
1904
|
+
const libRows = await scan(formalLibRoot(), 'Formal/Lib', 'def')
|
|
1905
|
+
await writeTextAbs(vibeRoot() + '/Formal/Lib/Index.md', ['# 可复用 Lean 定义库(跨项目)|' + instituteName, '',
|
|
1906
|
+
'> 写新定义之前先查这里:能复用就不要重新定义。', '',
|
|
1907
|
+
'| 名称 | 文件 | 类别 | 摘要 |', '|---|---|---|---|']
|
|
1908
|
+
.concat(libRows.length ? libRows : ['| (暂无) | | | |']).join('\n') + '\n')
|
|
1909
|
+
const provedRows = await scan(formalProvedRoot(), 'Formal/Proved', 'lemma')
|
|
1910
|
+
await writeTextAbs(vibeRoot() + '/Formal/Proved/Index.md', ['# 已成立的 Lean 命题 / 引理(机器已核对,可跨项目复用)|' + instituteName, '',
|
|
1911
|
+
'> 这些文件是通过内核检查的引理,可直接 import 复用。', '',
|
|
1912
|
+
'| 名称 | 文件 | 类别 | 陈述 |', '|---|---|---|---|']
|
|
1913
|
+
.concat(provedRows.length ? provedRows : ['| (暂无) | | | |']).join('\n') + '\n')
|
|
1914
|
+
await writeFormalIndex()
|
|
1915
|
+
await writeFormalTodo()
|
|
1916
|
+
return { lib: libRows.length, proved: provedRows.length, objects: Object.keys(formalRecords()).length }
|
|
1917
|
+
}
|
|
1918
|
+
// A vibe-root-relative path → absolute. Used for the GLOBAL library, which sits beside
|
|
1919
|
+
// the project tree rather than inside the current institute.
|
|
1920
|
+
function instRootless(rel) { return vibeRoot() + '/' + String(rel).replace(/^\.\//, '') }
|
|
1921
|
+
|
|
1922
|
+
// Execute one Lean file through the toolchain, record the run (optionally against an
|
|
1923
|
+
// object), refresh the indexes, and report the outcome verbatim. Deliberately called
|
|
1924
|
+
// even from `off` mode: a human debugging their toolchain may want it.
|
|
1925
|
+
async function leanRunTool(memberId, o) {
|
|
1926
|
+
const args = o || {}
|
|
1927
|
+
const run = await leanRunFile(String(args.file || ''), args.timeout_ms)
|
|
1928
|
+
if (run.ok || run.file) {
|
|
1929
|
+
if (String(args.target || '').trim()) await formalSetRun(String(args.target), run)
|
|
1930
|
+
await writeFormalIndex()
|
|
1931
|
+
}
|
|
1932
|
+
if (run.ok) {
|
|
1933
|
+
await saveChatLine('【形式化】' + (memberId || 'office') + ' 运行 Lean 通过:' + run.file
|
|
1934
|
+
+ '(' + (run.ms / 1000).toFixed(1) + 's)' + (args.target ? '|对象 ' + args.target + ' 记为已尝试/已通过' : ''))
|
|
1935
|
+
}
|
|
1936
|
+
return Object.assign({ ok: !!run.ok }, run, {
|
|
1937
|
+
hint: run.ok
|
|
1938
|
+
? '通过。若是某个对象的证明,请用 vibe_v5_lean_archive kind=\'proof\' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind=\'def\'/\'lemma\' 归档到全局库。'
|
|
1939
|
+
: '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用 vibe_v5_lean_archive kind=\'blocked\' 记录原因。',
|
|
1940
|
+
})
|
|
1941
|
+
}
|
|
1942
|
+
async function leanArchive(memberId, o) {
|
|
1943
|
+
const args = o || {}
|
|
1944
|
+
const kind = String(args.kind || '')
|
|
1945
|
+
const content = typeof args.content === 'string' ? args.content : undefined
|
|
1946
|
+
const from = args.from ? String(args.from) : ''
|
|
1947
|
+
if (kind === 'def' || kind === 'lemma') {
|
|
1948
|
+
const name = idSafe(String(args.name || ''))
|
|
1949
|
+
if (!name) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'name is required for a reusable definition/lemma' }
|
|
1950
|
+
let body = content
|
|
1951
|
+
if (body === undefined && from) {
|
|
1952
|
+
const srcAbs = leanAbsPath(from)
|
|
1953
|
+
if (srcAbs === null) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'from must be a .lean file inside the workspace (got ' + from + ')' }
|
|
1954
|
+
body = await readTextAbs(srcAbs)
|
|
1955
|
+
}
|
|
1956
|
+
if (body === undefined) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'provide content, or from=<existing .lean file>' }
|
|
1957
|
+
const rel = 'Formal/' + (kind === 'def' ? 'Lib' : 'Proved') + '/' + name + '.lean'
|
|
1958
|
+
const okWrite = await writeTextAbs(instRootless(rel), body)
|
|
1959
|
+
if (!okWrite) return { ok: false, code: 'V5_WRITE_FAILED', message: 'could not write ' + rel }
|
|
1960
|
+
// The global library sits beside the project tree, so it must be executed through
|
|
1961
|
+
// its ABSOLUTE path (the relative form would resolve inside the institute root).
|
|
1962
|
+
const run = args.run === false ? null : await leanRunFile(instRootless(rel))
|
|
1963
|
+
await rebuildLeanLibIndexes()
|
|
1964
|
+
await saveChatLine('【形式化】' + memberId + ' 归档了' + (kind === 'def' ? '可复用定义' : '已证引理') + ' `' + name + '` → ' + rel
|
|
1965
|
+
+ (run ? '(运行 ' + (run.ok ? '通过' : '未通过') + ')' : ''))
|
|
1966
|
+
return { ok: true, kind, name, file: rel, run: run || undefined, note: '已并入全局可复用库,后续项目可直接 import 复用' }
|
|
1967
|
+
}
|
|
1968
|
+
if (kind === 'proof') {
|
|
1969
|
+
const target = idSafe(String(args.target || ''))
|
|
1970
|
+
if (!target) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'target is required for kind=proof' }
|
|
1971
|
+
let body = content
|
|
1972
|
+
if (body === undefined && from) {
|
|
1973
|
+
const srcAbs = leanAbsPath(from)
|
|
1974
|
+
if (srcAbs === null) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'from must be a .lean file inside the workspace (got ' + from + ')' }
|
|
1975
|
+
body = await readTextAbs(srcAbs)
|
|
1976
|
+
}
|
|
1977
|
+
if (body === undefined) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'provide content, or from=<existing .lean file>' }
|
|
1978
|
+
const workRel = 'Formal/' + target + '.lean'
|
|
1979
|
+
if (!await writeTextRel(workRel, body)) return { ok: false, code: 'V5_WRITE_FAILED', message: 'could not write ' + workRel }
|
|
1980
|
+
const run = await leanRunFile(workRel)
|
|
1981
|
+
const prev = formalOf(target)
|
|
1982
|
+
const passed = !!run.ok
|
|
1983
|
+
const rec = Object.assign({}, prev, {
|
|
1984
|
+
status: passed ? 'passed' : 'attempted',
|
|
1985
|
+
file: workRel,
|
|
1986
|
+
proof: passed ? 'Verified/Lean/' + target + '.lean' : (prev.proof || ''),
|
|
1987
|
+
decision: 'used',
|
|
1988
|
+
note: String(args.note || prev.note || ''),
|
|
1989
|
+
run: { at: now(), ok: !!run.ok, exitCode: run.exitCode === undefined ? null : run.exitCode, ms: run.ms || 0, stdoutTail: tail(run.stdout, 800), stderrTail: tail(run.stderr, 800) },
|
|
1990
|
+
updatedAt: now(),
|
|
1991
|
+
})
|
|
1992
|
+
if (passed) await writeTextRel('Verified/Lean/' + target + '.lean', body)
|
|
1993
|
+
await putFormal(target, rec)
|
|
1994
|
+
await rebuildLeanLibIndexes()
|
|
1995
|
+
await saveChatLine('【形式化】' + memberId + ' 为 ' + target + ' 归档形式化证明 ' + workRel
|
|
1996
|
+
+ '(运行 ' + (passed ? '**通过**,已归档到 ' + rec.proof + ',验证转为忠实性审查' : '**未通过**:' + tail(run.stderr || run.message, 160)) + ')')
|
|
1997
|
+
return { ok: true, kind, target, file: workRel, proof: rec.proof, passed, run, status: rec.status }
|
|
1998
|
+
}
|
|
1999
|
+
if (kind === 'blocked') {
|
|
2000
|
+
const target = idSafe(String(args.target || ''))
|
|
2001
|
+
if (!target) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'target is required for kind=blocked' }
|
|
2002
|
+
const note = String(args.note || '').trim()
|
|
2003
|
+
if (!note) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: '阻塞记录必须写明原因(note)——"因难度决定不做形式化"必须显式、可审计' }
|
|
2004
|
+
const prev = formalOf(target)
|
|
2005
|
+
const rec = Object.assign({}, prev, { status: 'blocked', decision: 'blocked', note, updatedAt: now() })
|
|
2006
|
+
await putFormal(target, rec)
|
|
2007
|
+
await rebuildLeanLibIndexes()
|
|
2008
|
+
await saveChatLine('【形式化】' + memberId + ' 记录 ' + target + ' 形式化阻塞:' + note)
|
|
2009
|
+
return { ok: true, kind, target, status: 'blocked', note }
|
|
2010
|
+
}
|
|
2011
|
+
return { ok: false, code: 'V5_INVALID_ARGUMENT', message: "kind must be 'def' | 'lemma' | 'proof' | 'blocked'" }
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
|
|
1545
2015
|
// ---- context / compaction accounting ---------------------------------
|
|
1546
2016
|
// A SHORT core-rules recap, injected ONLY (a) right after a REAL compaction, or
|
|
1547
2017
|
// (b) in the same wake as a soft-compact directive — never on every round. The
|
|
@@ -2193,7 +2663,19 @@ export function apply(ctx) {
|
|
|
2193
2663
|
try {
|
|
2194
2664
|
const j = judgeVerdict(vs)
|
|
2195
2665
|
if (j.outcome === 'true' || j.outcome === 'false') {
|
|
2196
|
-
|
|
2666
|
+
// ── the `require` gate (§8 of docs/formal-verification.md) ────────────────
|
|
2667
|
+
// A unanimous boolean verdict is a CONSENSUS, not a proof. In `require` mode the
|
|
2668
|
+
// institute has decided that consensus alone may not be promoted to Verified/:
|
|
2669
|
+
// the object must also be either machine-checked (`passed`) or carry an explicit,
|
|
2670
|
+
// reasoned "we judged this infeasible" record (`blocked`). The gate never wedges
|
|
2671
|
+
// the run — it records 未定论 + a formalization TODO so the institute can keep
|
|
2672
|
+
// going and formalize later.
|
|
2673
|
+
const rec = formalOf(vs.target)
|
|
2674
|
+
if (formalMode() === 'require' && !formalGateOk(rec)) {
|
|
2675
|
+
await deferForFormal(vs, j, isTrueVote(j))
|
|
2676
|
+
} else {
|
|
2677
|
+
await closeVerify(vs, j.outcome === 'true', j)
|
|
2678
|
+
}
|
|
2197
2679
|
} else if (vs.round >= Math.max(1, Math.floor(Number(params.verdictMaxRounds) || 3))) {
|
|
2198
2680
|
await finalizeUndecided(vs, j)
|
|
2199
2681
|
} else {
|
|
@@ -2218,6 +2700,30 @@ export function apply(ctx) {
|
|
|
2218
2700
|
}
|
|
2219
2701
|
await scheduleNext()
|
|
2220
2702
|
}
|
|
2703
|
+
const isTrueVote = (j) => j && j.outcome === 'true'
|
|
2704
|
+
// `require` mode withheld the verdict: record it as 未定论 with a machine-readable
|
|
2705
|
+
// reason, put the object on the formalization TODO, and say so in the group chat. The
|
|
2706
|
+
// object keeps its mean probability and its debate record, so nothing is lost.
|
|
2707
|
+
async function deferForFormal(vs, j, isTrue) {
|
|
2708
|
+
const rec = formalOf(vs.target)
|
|
2709
|
+
const why = 'formal-required:尚未取得 Lean 形式化通过,也没有显式阻塞记录(当前状态 ' + (rec.status || 'none') + ')'
|
|
2710
|
+
await writeDebateDoc(vs, false, j)
|
|
2711
|
+
await putVerdict(vs.target, Object.assign({}, vs, {
|
|
2712
|
+
closed: true, outcome: 'undecided', reason: why, mean: j.mean, formalDeferred: true,
|
|
2713
|
+
m: j.m, P: j.P, bTrue: j.bTrue, bFalse: j.bFalse, abstain: j.abstain, closedAt: now(),
|
|
2714
|
+
}))
|
|
2715
|
+
const list = formalTodo().filter((t) => t.id !== vs.target)
|
|
2716
|
+
list.push({ id: vs.target, at: now(), why, verdict: isTrue ? 1 : 0 })
|
|
2717
|
+
await putFormal(vs.target, rec.status === 'none' ? { status: 'none', deferredAt: now() } : rec, list)
|
|
2718
|
+
await writeFormalTodo()
|
|
2719
|
+
await writeFormalIndex()
|
|
2720
|
+
await saveChatLine('【形式化】' + vs.target + ' 的表决结果为 ' + (isTrue ? '真' : '假')
|
|
2721
|
+
+ ',但 **require 模式**要求先有 Lean 通过或显式阻塞记录,因此本轮**不定论**(已记入 Formal/TODO.md)。'
|
|
2722
|
+
+ '请完成形式化(vibe_v5_lean_archive kind=\'proof\')或记录阻塞原因(kind=\'blocked\')后重新提议验证。')
|
|
2723
|
+
await markProgress()
|
|
2724
|
+
await armNextVerify()
|
|
2725
|
+
await scheduleNext()
|
|
2726
|
+
}
|
|
2221
2727
|
async function finalizeUndecided(vs, j) {
|
|
2222
2728
|
await writeDebateDoc(vs, false, j)
|
|
2223
2729
|
// Keep it in the library with the group's MEAN probability — the design's
|
|
@@ -2323,6 +2829,9 @@ export function apply(ctx) {
|
|
|
2323
2829
|
'- 来源: ' + (isTrue ? j.bTrue : j.bFalse) + ' 名有表决权者一致判' + (isTrue ? '真' : '假') + '(m=' + j.m + ')',
|
|
2324
2830
|
'- 表决者: ' + j.voters.join('、'),
|
|
2325
2831
|
'- 弃权: ' + j.abstain + '|全组平均概率: ' + Number(j.mean).toFixed(2),
|
|
2832
|
+
// The formal record travels WITH the conclusion: a reader of the card must be able
|
|
2833
|
+
// to see how strong the result really is (machine-checked vs consensus-only).
|
|
2834
|
+
...(formalOn() ? ['- 形式化: ' + formalStatusLine(vs.target) + (formalOf(vs.target).proof ? '(证明:' + formalOf(vs.target).proof + ')' : '')] : []),
|
|
2326
2835
|
'- 时间: ' + fmtTime(),
|
|
2327
2836
|
'',
|
|
2328
2837
|
'## 陈述',
|
|
@@ -2943,6 +3452,37 @@ export function apply(ctx) {
|
|
|
2943
3452
|
} else await notice(member.id, '标记完成失败:没有任务 ' + String(p.task_done))
|
|
2944
3453
|
}
|
|
2945
3454
|
// (5) verification
|
|
3455
|
+
// (4b) Lean formalization signal — the mandatory difficulty judgement. This is the
|
|
3456
|
+
// path that matters in practice: a member that never calls a Lean tool still has to
|
|
3457
|
+
// say "used / blocked", and `require` mode refuses to conclude without it.
|
|
3458
|
+
if (p.formal && typeof p.formal === 'object') {
|
|
3459
|
+
const f = p.formal
|
|
3460
|
+
const target = idSafe(String(f.target || ''))
|
|
3461
|
+
if (target) {
|
|
3462
|
+
const decision = String(f.decision || '').trim()
|
|
3463
|
+
const note = String(f.note || '').trim()
|
|
3464
|
+
if (decision === 'blocked' || decision === 'defect') {
|
|
3465
|
+
if (!note) await notice(member.id, "formal.decision='" + decision + "' 必须写明 note(难度判断/阻塞原因/具体偏差)——本次未记录。")
|
|
3466
|
+
else if (decision === 'defect') {
|
|
3467
|
+
const r = await recordFidelityDefect(member.id, target, note)
|
|
3468
|
+
if (r.ok === false) await notice(member.id, '记录忠实性缺陷失败(' + (r.code || '') + '):' + (r.message || ''))
|
|
3469
|
+
} else {
|
|
3470
|
+
const r = await leanArchive(member.id, { kind: 'blocked', target, note })
|
|
3471
|
+
if (r.ok === false) await notice(member.id, '记录形式化阻塞失败(' + (r.code || '') + '):' + (r.message || ''))
|
|
3472
|
+
}
|
|
3473
|
+
} else if (decision === 'used') {
|
|
3474
|
+
const file = String(f.file || ('Formal/' + target + '.lean'))
|
|
3475
|
+
const prev = formalOf(target)
|
|
3476
|
+
await putFormal(target, Object.assign({}, prev, {
|
|
3477
|
+
status: prev.status === 'passed' || prev.status === 'blocked' ? prev.status : 'attempted',
|
|
3478
|
+
file, decision: 'used', note: String(f.note || prev.note || ''), updatedAt: now(),
|
|
3479
|
+
}))
|
|
3480
|
+
await writeFormalIndex()
|
|
3481
|
+
} else if (decision) {
|
|
3482
|
+
await notice(member.id, "formal.decision 只能是 'used' / 'blocked' / 'defect'(收到 " + decision + ')')
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
2946
3486
|
if (p.propose_verify) {
|
|
2947
3487
|
const pv = typeof p.propose_verify === 'string' ? { target: p.propose_verify } : p.propose_verify
|
|
2948
3488
|
if (pv && pv.target) {
|
|
@@ -3075,10 +3615,10 @@ export function apply(ctx) {
|
|
|
3075
3615
|
const out = {}
|
|
3076
3616
|
const ints = ['researcherCount', 'quorumCap', 'verdictMaxRounds', 'maxTempPerMember', 'maxTempTotal',
|
|
3077
3617
|
'compactThreshold', 'compactAfterRounds', 'maxParallel', 'activityTimeoutMs', 'stallAutoMeetingMs',
|
|
3078
|
-
'chatDigestMs', 'chatDigestMax', 'meetingKeepEvery']
|
|
3618
|
+
'chatDigestMs', 'chatDigestMax', 'meetingKeepEvery', 'leanTimeoutMs']
|
|
3079
3619
|
const bools = ['academician', 'academicianLeads', 'memberMayRejectAssign']
|
|
3080
|
-
const strs = ['quorumMode', 'provider', 'model', 'staffPersona']
|
|
3081
|
-
const arrs = ['toolAllow', 'toolDeny', 'tempToolAllow', 'tempToolDeny']
|
|
3620
|
+
const strs = ['quorumMode', 'provider', 'model', 'staffPersona', 'formalVerify', 'leanCommand']
|
|
3621
|
+
const arrs = ['toolAllow', 'toolDeny', 'tempToolAllow', 'tempToolDeny', 'leanArgs']
|
|
3082
3622
|
for (const k of ints) if (input[k] !== undefined) { const n = Math.floor(Number(input[k])); if (Number.isFinite(n)) out[k] = n }
|
|
3083
3623
|
for (const k of bools) if (input[k] !== undefined) out[k] = (input[k] === true || input[k] === 'true')
|
|
3084
3624
|
for (const k of strs) if (input[k] !== undefined) out[k] = String(input[k])
|
|
@@ -3088,11 +3628,18 @@ export function apply(ctx) {
|
|
|
3088
3628
|
out[k] = Array.isArray(v) ? v.map(String).filter((x) => x.trim()) : String(v).split(',').map((x) => x.trim()).filter(Boolean)
|
|
3089
3629
|
}
|
|
3090
3630
|
if (out.quorumMode !== undefined && out.quorumMode !== 'm-unanimous' && out.quorumMode !== 'all-unanimous') out.quorumMode = 'm-unanimous'
|
|
3631
|
+
// An unknown Lean mode must degrade to 'off' (the no-op), never to a stronger mode:
|
|
3632
|
+
// a typo silently forcing formal verification would block every conclusion.
|
|
3633
|
+
if (out.formalVerify !== undefined) {
|
|
3634
|
+
out.formalVerify = ['off', 'encourage', 'require'].indexOf(out.formalVerify) !== -1 ? out.formalVerify : 'off'
|
|
3635
|
+
}
|
|
3636
|
+
if (out.leanCommand !== undefined && !String(out.leanCommand).trim()) out.leanCommand = 'lean'
|
|
3091
3637
|
// Guard every duration against a negative/NaN value: such a value would make a
|
|
3092
3638
|
// watchdog fire instantly and abandon all consensus (v4 §30-T41).
|
|
3093
|
-
for (const k of ['activityTimeoutMs', 'stallAutoMeetingMs', 'chatDigestMs']) {
|
|
3639
|
+
for (const k of ['activityTimeoutMs', 'stallAutoMeetingMs', 'chatDigestMs', 'leanTimeoutMs']) {
|
|
3094
3640
|
if (out[k] !== undefined && !(out[k] > 0)) delete out[k]
|
|
3095
3641
|
}
|
|
3642
|
+
if (out.leanTimeoutMs !== undefined) out.leanTimeoutMs = Math.max(1000, out.leanTimeoutMs)
|
|
3096
3643
|
if (out.researcherCount !== undefined && out.researcherCount < 0) out.researcherCount = 0
|
|
3097
3644
|
if (out.quorumCap !== undefined && out.quorumCap < 1) out.quorumCap = 1
|
|
3098
3645
|
return out
|
|
@@ -3121,6 +3668,8 @@ export function apply(ctx) {
|
|
|
3121
3668
|
maxParallel: params.maxParallel, activityTimeoutMs: params.activityTimeoutMs,
|
|
3122
3669
|
stallAutoMeetingMs: params.stallAutoMeetingMs, chatDigestMs: params.chatDigestMs,
|
|
3123
3670
|
chatDigestMax: params.chatDigestMax, meetingKeepEvery: params.meetingKeepEvery,
|
|
3671
|
+
formalVerify: params.formalVerify, leanCommand: params.leanCommand,
|
|
3672
|
+
leanArgs: params.leanArgs, leanTimeoutMs: params.leanTimeoutMs,
|
|
3124
3673
|
provider: params.provider, model: params.model,
|
|
3125
3674
|
toolAllow: params.toolAllow, toolDeny: params.toolDeny,
|
|
3126
3675
|
}
|
|
@@ -3385,6 +3934,18 @@ export function apply(ctx) {
|
|
|
3385
3934
|
verified: Object.keys(s.verdicts).filter((k) => s.verdicts[k] && s.verdicts[k].closed && s.verdicts[k].outcome !== 'undecided'),
|
|
3386
3935
|
undecided: Object.keys(s.verdicts).filter((k) => { const v = s.verdicts[k]; return v && v.closed && v.outcome === 'undecided' }),
|
|
3387
3936
|
solveVotes: Array.from(solveVotes.entries()).map(([k, v]) => k + '=' + v),
|
|
3937
|
+
// Lean formal verification: mode + per-object status + the formalization TODO. This
|
|
3938
|
+
// is how an office/human audits "did we really get strict proofs, or only consensus?"
|
|
3939
|
+
formal: {
|
|
3940
|
+
mode: formalMode(),
|
|
3941
|
+
objects: Object.keys(formalRecords()).map((k) => {
|
|
3942
|
+
const r = formalRecords()[k] || {}
|
|
3943
|
+
return { target: k, status: r.status, 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 }
|
|
3944
|
+
}),
|
|
3945
|
+
passed: Object.keys(formalRecords()).filter((k) => (formalRecords()[k] || {}).status === 'passed'),
|
|
3946
|
+
blocked: Object.keys(formalRecords()).filter((k) => (formalRecords()[k] || {}).status === 'blocked'),
|
|
3947
|
+
todo: formalTodo(),
|
|
3948
|
+
},
|
|
3388
3949
|
lastProgressAt, params: visibleParams(),
|
|
3389
3950
|
}
|
|
3390
3951
|
}
|
|
@@ -3436,6 +3997,16 @@ export function apply(ctx) {
|
|
|
3436
3997
|
L.push('- 历史会议:' + s.meetings.length + ' 次|辩论录:' + s.debates.length + ' 份')
|
|
3437
3998
|
L.push('- 解决票:' + (solveVotes.size ? Array.from(solveVotes.entries()).map(([k, v]) => k + '=' + v).join('、') : '(无)'))
|
|
3438
3999
|
L.push('')
|
|
4000
|
+
L.push('## Lean 形式化')
|
|
4001
|
+
if (!formalOn()) L.push('- 未启用(`formalVerify` = off;可用 vibe_v5_set 切到 encourage / require)')
|
|
4002
|
+
else {
|
|
4003
|
+
L.push('- 模式:' + formalMode() + '(' + (formalMode() === 'require' ? '强制:定论前必须有 Lean 通过或显式阻塞记录' : '鼓励:按实现难度自行决定') + ')')
|
|
4004
|
+
L.push('- 已通过:' + (Object.keys(s.formal || {}).filter((k) => (s.formal || {})[k].status === 'passed').join('、') || '(无)'))
|
|
4005
|
+
L.push('- 已记录阻塞:' + (Object.keys(s.formal || {}).filter((k) => (s.formal || {})[k].status === 'blocked').join('、') || '(无)'))
|
|
4006
|
+
L.push('- 形式化待办:' + ((s.todo || []).map((t) => t.id).join('、') || '(无)'))
|
|
4007
|
+
L.push('- 可复用库:VibeMath/Formal/{Lib,Proved}/(跨项目)|本所形式化:Formal/|归档证明:Verified/Lean/')
|
|
4008
|
+
}
|
|
4009
|
+
L.push('')
|
|
3439
4010
|
L.push('## 文件位置')
|
|
3440
4011
|
L.push('- 根目录:' + instRoot())
|
|
3441
4012
|
L.push('- 已确立:Verified/|成员库:Members/<id>/|群聊:Shared/Chat/|会议:Shared/Meetings/|辩论:Shared/Debates/')
|
|
@@ -3495,6 +4066,10 @@ export function apply(ctx) {
|
|
|
3495
4066
|
taskCreate, taskList: listTasks, getTask, taskUpdate, taskAssign, taskPrioritize,
|
|
3496
4067
|
// libraries
|
|
3497
4068
|
publishProgress, recordCard, readLibrary,
|
|
4069
|
+
// Lean formal verification (docs/formal-verification.md)
|
|
4070
|
+
formalMode, formalOn, formalRecords, formalTodo, formalOf, rebuildLeanLibIndexes,
|
|
4071
|
+
leanArchive, leanRunTool, writeFormalIndex, writeFormalTodo, recordFidelityDefect,
|
|
4072
|
+
leanRunToolApi: async (relPath, timeoutMs) => await leanRunFile(relPath, timeoutMs),
|
|
3498
4073
|
// consensus / meetings
|
|
3499
4074
|
maybeQueueVerify, castVerdict, currentVerify, hasVerifyInFlight, startMeeting, quorumM, voterCount,
|
|
3500
4075
|
// authorization helpers (used by tool handlers)
|
|
@@ -3535,12 +4110,14 @@ export function apply(ctx) {
|
|
|
3535
4110
|
registerTool('vibe_v5_stop', 'Stop the institute: interrupt every member, clear coordination state, and release their child sessions.', objParams({}), (s) => s.initStop())
|
|
3536
4111
|
registerTool('vibe_v5_status', 'Machine-readable institute status (members, tasks, quorum, meetings, verification, mail).', objParams({}), (s) => s.status())
|
|
3537
4112
|
registerTool('vibe_v5_report', 'Human-readable institute report (staffing, tasks, consensus, meetings, file locations).', objParams({}), (s) => s.report())
|
|
3538
|
-
registerTool('vibe_v5_set', 'Tune institute parameters (persisted in the session-log projection). provider/model override staff LLM routes (empty = inherit the office route). toolAllow/toolDeny restrict PERMANENT staff tools; tempToolAllow/tempToolDeny restrict temp workers. quorumCap sets m = min(quorumCap, voters); quorumMode "m-unanimous" (v5) or "all-unanimous" (v4 legacy).', objParams({
|
|
4113
|
+
registerTool('vibe_v5_set', 'Tune institute parameters (persisted in the session-log projection). provider/model override staff LLM routes (empty = inherit the office route). toolAllow/toolDeny restrict PERMANENT staff tools; tempToolAllow/tempToolDeny restrict temp workers. quorumCap sets m = min(quorumCap, voters); quorumMode "m-unanimous" (v5) or "all-unanimous" (v4 legacy). formalVerify: "off" (default, no extra requirement) | "encourage" (agents decide by implementation difficulty whether to formalize in Lean; a passing Lean run turns the vote into a FIDELITY review of the Lean statements) | "require" (same, plus a gate: a true/false verdict is withheld as undecided until the object is Lean-passed or has an explicit reasoned blocker record).', objParams({
|
|
3539
4114
|
academician: B, academicianLeads: B, memberMayRejectAssign: B, researcherCount: I,
|
|
3540
4115
|
quorumCap: I, quorumMode: S, verdictMaxRounds: I,
|
|
3541
4116
|
maxTempPerMember: I, maxTempTotal: I,
|
|
3542
4117
|
compactThreshold: I, compactAfterRounds: I, maxParallel: I,
|
|
3543
4118
|
activityTimeoutMs: I, stallAutoMeetingMs: I, chatDigestMs: I, chatDigestMax: I, meetingKeepEvery: I,
|
|
4119
|
+
formalVerify: { type: 'string', enum: ['off', 'encourage', 'require'] },
|
|
4120
|
+
leanCommand: S, leanArgs: SA, leanTimeoutMs: I,
|
|
3544
4121
|
provider: S, model: S, staffPersona: S, toolAllow: SA, toolDeny: SA, tempToolAllow: SA, tempToolDeny: SA,
|
|
3545
4122
|
}), (s, a) => s.setParams(a))
|
|
3546
4123
|
registerTool('vibe_v5_message', 'Relay a message from the office/human into the institute (to a member id, to "all", or to "voters").', objParams({ to: S, content: S }, ['to', 'content']), (s, a) => {
|
|
@@ -3606,6 +4183,21 @@ export function apply(ctx) {
|
|
|
3606
4183
|
parts.push('(见 Shared/Chat/ 目录;未读消息 ' + st.chat.pending + ' 条)')
|
|
3607
4184
|
if (st.verify) parts.push('## 进行中表决\n' + JSON.stringify(st.verify))
|
|
3608
4185
|
if (st.meeting) parts.push('## 进行中会议\n' + JSON.stringify(st.meeting))
|
|
4186
|
+
// The academician supervises progress; in a formalization mode "which objects still lack
|
|
4187
|
+
// a Lean result" is exactly the kind of bottleneck it must see to organise the work.
|
|
4188
|
+
if (s.formalOn()) {
|
|
4189
|
+
parts.push('## Lean 形式化(' + s.formalMode() + ')')
|
|
4190
|
+
const recs = s.formalRecords()
|
|
4191
|
+
const keys = Object.keys(recs)
|
|
4192
|
+
if (!keys.length) parts.push('- 尚无对象被形式化')
|
|
4193
|
+
for (const k of keys) {
|
|
4194
|
+
const r = recs[k] || {}
|
|
4195
|
+
parts.push('- ' + k + '|' + (r.status || 'none') + (r.proof ? '|' + r.proof : '') + (r.note ? '|' + r.note : ''))
|
|
4196
|
+
}
|
|
4197
|
+
const todo = s.formalTodo()
|
|
4198
|
+
if (todo.length) parts.push('- 形式化待办(定论被搁置):' + todo.map((t) => t.id).join('、') + '(见 Formal/TODO.md)')
|
|
4199
|
+
parts.push('- 可复用库:vibe_v5_lean_lib 可列出 Formal/Lib 与 Formal/Proved')
|
|
4200
|
+
}
|
|
3609
4201
|
parts.push('## 停滞提示')
|
|
3610
4202
|
const idleFor = Date.now() - st.lastProgressAt
|
|
3611
4203
|
parts.push('- 距上次实质进展:' + Math.round(idleFor / 1000) + ' 秒')
|
|
@@ -3615,6 +4207,26 @@ export function apply(ctx) {
|
|
|
3615
4207
|
registerTool('vibe_v5_prioritize', '(academician) Set institute-wide priorities: an ordered list of {task_id, priority} plus WHY. This orders work only — it never changes what is true.', objParams({ order: { type: 'array', items: { type: 'object' } }, why: S }), (s, a, x) => s.taskPrioritize(s.memberIdOfAgent(x), a))
|
|
3616
4208
|
registerTool('vibe_v5_nudge', '(academician) Supervise: wake one member with a stated reason and a concrete suggested next step. Prefer a specific next step over a bare "hurry up".', objParams({ to: S, why: S, next_step: S }, ['to', 'why']), (s, a, x) => s.nudge(s.memberIdOfAgent(x), a))
|
|
3617
4209
|
|
|
4210
|
+
// ── Lean formal verification (docs/formal-verification.md) ────────────────
|
|
4211
|
+
// These three tools are registered UNCONDITIONALLY: tool registration is static (a
|
|
4212
|
+
// dynamic registration would depend on a runtime knob and break the effect discipline),
|
|
4213
|
+
// while the MODE only decides whether the framework TELLS members about them. In 'off'
|
|
4214
|
+
// mode they still work if a human or agent calls them deliberately.
|
|
4215
|
+
registerTool('vibe_v5_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: S, target: S, timeout_ms: I }, ['file']), (s, a, x) => s.leanRunTool(s.memberIdOfAgent(x), a))
|
|
4216
|
+
registerTool('vibe_v5_lean_archive', '(member) Archive Lean code. kind="def": a REUSABLE definition/object/assumption → the global cross-project library (Formal/Lib). kind="lemma": a machine-checked lemma → 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: S, target: S, content: S, from: S, note: S, run: B }, ['kind']), (s, a, x) => s.leanArchive(s.memberIdOfAgent(x), a))
|
|
4217
|
+
registerTool('vibe_v5_lean_lib', '(member) List (and by default rebuild) the Lean reuse library: your institute\'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: B }), async (s, a) => {
|
|
4218
|
+
const r = a && a.refresh === false ? { lib: null, proved: null, objects: Object.keys(s.formalRecords()).length } : await s.rebuildLeanLibIndexes()
|
|
4219
|
+
const st = s.status()
|
|
4220
|
+
return {
|
|
4221
|
+
ok: true, mode: s.formalMode(), rebuilt: !(a && a.refresh === false),
|
|
4222
|
+
counts: r, todo: s.formalTodo(),
|
|
4223
|
+
objects: Object.keys(s.formalRecords()).map((k) => ({ target: k, status: (s.formalRecords()[k] || {}).status, file: (s.formalRecords()[k] || {}).file, proof: (s.formalRecords()[k] || {}).proof, note: (s.formalRecords()[k] || {}).note })),
|
|
4224
|
+
paths: { project: 'Formal/(相对研究所根)', lib: 'VibeMath/Formal/Lib/', proved: 'VibeMath/Formal/Proved/', proofs: 'Verified/Lean/' },
|
|
4225
|
+
hint: '复用优先:先在 Lib/ 里找现成定义;新定义用 vibe_v5_lean_archive kind=\'def\' 归档,已证引理用 kind=\'lemma\'。',
|
|
4226
|
+
verify: st.verify ? st.verify.target : null,
|
|
4227
|
+
}
|
|
4228
|
+
})
|
|
4229
|
+
|
|
3618
4230
|
// ── /v5 slash command ────────────────────────────────────────────────────
|
|
3619
4231
|
ctx.effect(() => commands.register({
|
|
3620
4232
|
name: 'v5', description: 'control the Vibe Math V5 research institute',
|