dsh-vibe-math 2.2.2 → 2.3.0
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 +28 -0
- package/README.md +98 -1
- package/RELEASE-NOTES-2.3.0.md +207 -0
- package/audit-formal-sensitivity.mjs +247 -0
- package/audit-persona-sensitivity.mjs +249 -0
- package/audit-persona-surface.test.mjs +349 -0
- package/audit-v5-integrity.mjs +40 -1
- package/audit-v5-sensitivity.mjs +77 -6
- package/docs/formal-verification.md +321 -0
- package/docs/generate_framework_diagram_v5.mjs +22 -16
- package/formal-verify-v2.test.mjs +672 -0
- package/formal-verify-v3.test.mjs +824 -0
- package/formal-verify-v4.test.mjs +603 -0
- package/formal-verify-v5.test.mjs +526 -0
- package/package.json +15 -2
- package/prompt-corpus-persona/persona-corpus.json +32 -0
- package/prompt-corpus-persona/persona-corpus.md +674 -0
- package/prompt-corpus-v3/formal-verify-v3.json +280 -0
- package/prompt-corpus-v3/formal-verify-v3.md +2826 -0
- package/prompt-corpus-v5/prompt-corpus-v5.json +75 -9
- package/prompt-corpus-v5/prompt-corpus-v5.md +384 -65
- package/prompt-v5-integrity.test.mjs +111 -10
- package/vibe-math-v2/agent.cordis.yml +40 -2
- package/vibe-math-v2/vibe-math-v2.js +627 -19
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +145 -1
- package/vibe-math-v3/agent.cordis.yml +46 -2
- package/vibe-math-v3/vibe-math-v3.js +749 -21
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +87 -2
- package/vibe-math-v4/agent.cordis.yml +46 -4
- package/vibe-math-v4/vibe-math-v4.js +652 -15
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +226 -0
- package/vibe-math-v5/agent.cordis.yml +41 -5
- package/vibe-math-v5/vibe-math-v5.js +562 -9
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +108 -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
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// V5 LEAN FORMAL VERIFICATION SUITE (docs/formal-verification.md)
|
|
3
|
+
//
|
|
4
|
+
// Asserts the whole contract of the `formalVerify` knob:
|
|
5
|
+
// · 'off' is a TRUE no-op (no Lean text anywhere, no gate)
|
|
6
|
+
// · 'encourage' injects the Lean section AND — the actual point of the feature — turns the
|
|
7
|
+
// voting prompt into a FIDELITY review once a Lean run has passed
|
|
8
|
+
// · 'require' withholds a true/false verdict as 未定论 until the object is Lean-passed
|
|
9
|
+
// or carries an explicit, reasoned blocker record; then allows it, and the
|
|
10
|
+
// Verified card records how strong the result really is
|
|
11
|
+
// · the three tools (run / archive / lib) write the right things to the right paths
|
|
12
|
+
//
|
|
13
|
+
// The Lean toolchain is mocked through the subprocess SERVICE, so the tests exercise the
|
|
14
|
+
// real code path (resolveExecutable → spawn → collected stdout → exit code) without
|
|
15
|
+
// requiring Lean to be installed.
|
|
16
|
+
//
|
|
17
|
+
// Run: node formal-verify-v5.test.mjs
|
|
18
|
+
// ============================================================
|
|
19
|
+
import { mkdtempSync, existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs'
|
|
20
|
+
import { tmpdir } from 'node:os'
|
|
21
|
+
import { join, dirname, isAbsolute } from 'node:path'
|
|
22
|
+
import { fileURLToPath } from 'node:url'
|
|
23
|
+
|
|
24
|
+
const PLUGIN = process.env.V5_PLUGIN
|
|
25
|
+
? new URL('file:///' + String(process.env.V5_PLUGIN).replace(/\\/g, '/'))
|
|
26
|
+
: new URL('./vibe-math-v5/vibe-math-v5.js', import.meta.url)
|
|
27
|
+
const WS = mkdtempSync(join(tmpdir(), 'vibe-v5-lean-'))
|
|
28
|
+
|
|
29
|
+
let passed = 0, failed = 0
|
|
30
|
+
const failures = []
|
|
31
|
+
const assert = (c, m) => { if (c) { passed++; console.log(' ok - ' + m) } else { failed++; failures.push(m); console.error(' FAIL - ' + m) } }
|
|
32
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
33
|
+
const section = (t) => console.log('\n[' + t + ']')
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------
|
|
36
|
+
// mock host
|
|
37
|
+
// ---------------------------------------------------------------
|
|
38
|
+
function makeProjectionRegistry() {
|
|
39
|
+
const units = new Map(); const cells = new Map()
|
|
40
|
+
const cellMap = (s) => { const id = String(s.id); let m = cells.get(id); if (!m) { m = new Map(); cells.set(id, m) } return m }
|
|
41
|
+
return {
|
|
42
|
+
register(def) { units.set(def.key, def); return () => { units.delete(def.key) } },
|
|
43
|
+
stateOf(session, key) {
|
|
44
|
+
const def = units.get(key); if (!def) return undefined
|
|
45
|
+
const m = cellMap(session)
|
|
46
|
+
if (!m.has(key)) m.set(key, def.init(session.header, session.inheritedEventCount || 0))
|
|
47
|
+
return m.get(key)
|
|
48
|
+
},
|
|
49
|
+
_drive(session, event) {
|
|
50
|
+
const m = cellMap(session)
|
|
51
|
+
for (const [k, def] of units) {
|
|
52
|
+
const cur = m.has(k) ? m.get(k) : def.init(session.header, session.inheritedEventCount || 0)
|
|
53
|
+
let next; try { next = def.apply(cur, event) } catch (e) { next = cur }
|
|
54
|
+
m.set(k, next)
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const projections = makeProjectionRegistry()
|
|
61
|
+
const listeners = {}
|
|
62
|
+
const toolRegs = []
|
|
63
|
+
const liveAgents = new Map()
|
|
64
|
+
const spawns = []
|
|
65
|
+
const wakes = []
|
|
66
|
+
const delivered = []
|
|
67
|
+
const leanRuns = []
|
|
68
|
+
|
|
69
|
+
// A fake Lean: a file PASSES unless it still contains `sorry` or the marker `-- FAIL`.
|
|
70
|
+
// This mirrors the one property that matters for the feature — an exit code that says
|
|
71
|
+
// "the kernel accepted this".
|
|
72
|
+
let toolchainAvailable = true
|
|
73
|
+
const subprocess = {
|
|
74
|
+
async resolveExecutable(cmd) {
|
|
75
|
+
if (!toolchainAvailable) throw new Error('spawn lean ENOENT')
|
|
76
|
+
if (String(cmd) !== 'lean') throw new Error('unknown executable ' + cmd)
|
|
77
|
+
return 'lean'
|
|
78
|
+
},
|
|
79
|
+
spawn(spec) {
|
|
80
|
+
const file = spec.argv[spec.argv.length - 1]
|
|
81
|
+
const text = existsSync(file) ? readFileSync(file, 'utf8') : ''
|
|
82
|
+
const bad = /sorry|-- FAIL/.test(text)
|
|
83
|
+
leanRuns.push({ argv: spec.argv.slice(0, -1), file, cwd: spec.cwd })
|
|
84
|
+
const stdout = bad ? '' : 'ok\n'
|
|
85
|
+
const stderr = bad ? 'error: declaration uses sorry\n' : ''
|
|
86
|
+
return {
|
|
87
|
+
done: Promise.resolve({ exitCode: bad ? 1 : 0, signal: null }),
|
|
88
|
+
collected: {
|
|
89
|
+
stdout: { readFrom: () => ({ text: stdout, nextOffset: stdout.length, lossy: false }) },
|
|
90
|
+
stderr: { readFrom: () => ({ text: stderr, nextOffset: stderr.length, lossy: false }) },
|
|
91
|
+
},
|
|
92
|
+
terminate() {},
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function makeMockSession(id, parentSession) {
|
|
98
|
+
const events = []
|
|
99
|
+
const s = {
|
|
100
|
+
id,
|
|
101
|
+
header: { version: 1, id, createdAt: Date.now(), cwd: WS, parentSession, isSeeded: false },
|
|
102
|
+
inheritedEventCount: 0,
|
|
103
|
+
get seq() { return events.length },
|
|
104
|
+
append(type, data) { const ev = { type, data, seq: events.length, time: Date.now() }; events.push(ev); projections._drive(s, ev); return ev },
|
|
105
|
+
deriveMessages() { return [] },
|
|
106
|
+
snapshotEvents(from) { return events.slice(from || 0) },
|
|
107
|
+
ownEvents() { return events.slice() },
|
|
108
|
+
_events: events,
|
|
109
|
+
}
|
|
110
|
+
return s
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const roots = new Map()
|
|
114
|
+
let rootSeq = 0
|
|
115
|
+
function makeRoot() {
|
|
116
|
+
const id = 'sess-' + String.fromCharCode(65 + rootSeq++)
|
|
117
|
+
const session = makeMockSession(id, undefined)
|
|
118
|
+
const root = { id, options: { provider: 'mock', model: 'm' }, session, ctx: undefined }
|
|
119
|
+
roots.set(id, root)
|
|
120
|
+
return root
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const ctx = {
|
|
124
|
+
get(name) {
|
|
125
|
+
if (name === 'sessionProjections') return projections
|
|
126
|
+
if (name === 'sandboxPolicy') return undefined
|
|
127
|
+
if (name === 'compaction') return undefined
|
|
128
|
+
if (name === 'subprocess') return subprocess
|
|
129
|
+
return undefined
|
|
130
|
+
},
|
|
131
|
+
on(e, fn) { (listeners[e] = listeners[e] || []).push(fn) },
|
|
132
|
+
effect(fn) { const d = fn(); return () => { if (typeof d === 'function') d() } },
|
|
133
|
+
logger: { info() {}, warn() {}, error() {} },
|
|
134
|
+
timeout(cb, ms) { const h = setTimeout(cb, ms); return () => clearTimeout(h) },
|
|
135
|
+
tools: { register(spec) { toolRegs.push(spec); return () => {} } },
|
|
136
|
+
commands: { register() { return () => {} } },
|
|
137
|
+
sessions: { async flush() { return true } },
|
|
138
|
+
subagents: {
|
|
139
|
+
list() { return ['spawn'] },
|
|
140
|
+
async startContinuable({ label, request }) {
|
|
141
|
+
const rootId = (request && request.parent && request.parent.id) || 'sess-A'
|
|
142
|
+
const id = 'c' + (spawns.length + 1)
|
|
143
|
+
liveAgents.set(id, { id, session: makeMockSession(id, rootId), options: request && request.agentOptions })
|
|
144
|
+
spawns.push({ label, childId: id, rootId, persona: request && request.persona, prompt: request && request.prompt && request.prompt[0] && request.prompt[0].text })
|
|
145
|
+
return { childId: id, messageId: 'm' + spawns.length }
|
|
146
|
+
},
|
|
147
|
+
async sendMessage(parent, childId, blocks) {
|
|
148
|
+
wakes.push({ childId, rootId: (parent && parent.id) || 'sess-A', prompt: (blocks && blocks[0] && blocks[0].text) || '' })
|
|
149
|
+
return 'w' + (wakes.length + delivered.length)
|
|
150
|
+
},
|
|
151
|
+
interrupt() {},
|
|
152
|
+
async drainContinuableChildren(parent, ids) { for (const i of ids) liveAgents.delete(i) },
|
|
153
|
+
},
|
|
154
|
+
agents: {
|
|
155
|
+
roots() { return [...roots.values()] },
|
|
156
|
+
get(id) { return roots.get(id) || liveAgents.get(id) },
|
|
157
|
+
list() { return [...roots.values(), ...liveAgents.values()] },
|
|
158
|
+
},
|
|
159
|
+
fs: {
|
|
160
|
+
async resolve(rel, opts) {
|
|
161
|
+
const b = (opts && opts.cwd) || WS
|
|
162
|
+
const p = (typeof rel === 'string' && isAbsolute(rel)) ? rel.replace(/\//g, '\\') : join(b, ...String(rel).split('/'))
|
|
163
|
+
return { targetKey: p, displayPath: p }
|
|
164
|
+
},
|
|
165
|
+
async stat(t) { return existsSync(t.targetKey) ? { version: 'v1', type: 'file', size: 1 } : undefined },
|
|
166
|
+
async readText(t) { return readFileSync(t.targetKey, 'utf8') },
|
|
167
|
+
async writeText(t, c) { mkdirSync(dirname(t.targetKey), { recursive: true }); writeFileSync(t.targetKey, c, 'utf8') },
|
|
168
|
+
async listDir(t) { if (!existsSync(t.targetKey)) return []; return readdirSync(t.targetKey, { withFileTypes: true }).map(e => ({ name: e.name, type: e.isDirectory() ? 'directory' : 'file' })) },
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const mod = await import(PLUGIN.href + '?t=' + Date.now())
|
|
173
|
+
;(mod.default || mod).apply(ctx)
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------
|
|
176
|
+
// driving helpers
|
|
177
|
+
// ---------------------------------------------------------------
|
|
178
|
+
async function callTool(name, args, agent) {
|
|
179
|
+
const spec = toolRegs.find(x => x.name === name)
|
|
180
|
+
if (!spec) throw new Error('no tool ' + name)
|
|
181
|
+
return JSON.parse(await spec.execute(args || {}, { agent }))
|
|
182
|
+
}
|
|
183
|
+
const childAgent = (childId) => liveAgents.get(childId)
|
|
184
|
+
function fireEnd(childId, reply) {
|
|
185
|
+
const blocks = reply === undefined ? [] : [{ type: 'text', text: '```json\n' + JSON.stringify(reply) + '\n```' }]
|
|
186
|
+
for (const h of (listeners['subagent/end'] || [])) {
|
|
187
|
+
h({ id: childId, runId: 'r', provider: 'spawn', local: true, stopReason: 'completed', lastAssistantMessage: blocks })
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const settle = async () => { await sleep(30) }
|
|
191
|
+
const memberOfChild = (childId) => { const s = spawns.find(x => x.childId === childId); const m = s ? /vibe5 (\S+) /.exec(s.label) : null; return m ? m[1] : '' }
|
|
192
|
+
const spawnOf = (root, id) => spawns.find(s => s.rootId === root.id && s.label.indexOf('vibe5 ' + id + ' ') !== -1)
|
|
193
|
+
const childOf = (root, id) => { const s = spawnOf(root, id); return s ? s.childId : '' }
|
|
194
|
+
const spawnsFor = (root) => spawns.filter(s => s.rootId === root.id)
|
|
195
|
+
|
|
196
|
+
let votePlan = new Map()
|
|
197
|
+
async function drainWakes(budget, root) {
|
|
198
|
+
let n = 0
|
|
199
|
+
while (n < budget) {
|
|
200
|
+
const idx = wakes.findIndex(w => !root || w.rootId === root.id)
|
|
201
|
+
if (idx === -1) break
|
|
202
|
+
const w = wakes.splice(idx, 1)[0]
|
|
203
|
+
const owner = memberOfChild(w.childId)
|
|
204
|
+
delivered.push({ prompt: w.prompt, owner, rootId: w.rootId })
|
|
205
|
+
let reply
|
|
206
|
+
if (/【求真表决/.test(w.prompt)) {
|
|
207
|
+
const target = (/"target"\s*:\s*"([^"]+)"/.exec(w.prompt) || [])[1] || ''
|
|
208
|
+
const v = votePlan.has(owner) ? votePlan.get(owner) : 0.5
|
|
209
|
+
reply = { verdict: { target, verdict: v, reason: owner + ' 的判断' }, contextPct: 20 }
|
|
210
|
+
} else if (/【研究所会议/.test(w.prompt)) reply = { input: owner + ':意见。', solved: false, contextPct: 20 }
|
|
211
|
+
else reply = { progress: owner + ':继续推进。', solved: false, contextPct: 20 }
|
|
212
|
+
fireEnd(w.childId, reply)
|
|
213
|
+
n++
|
|
214
|
+
await settle()
|
|
215
|
+
}
|
|
216
|
+
return n
|
|
217
|
+
}
|
|
218
|
+
async function settleInstitute(root, rounds = 12) {
|
|
219
|
+
for (let i = 0; i < rounds; i++) {
|
|
220
|
+
await drainWakes(40, root)
|
|
221
|
+
await sleep(20)
|
|
222
|
+
const st = await callTool('vibe_v5_status', {}, root)
|
|
223
|
+
if (!st.members.some(m => m.busy) && !st.meeting && !st.verify && wakes.filter(w => w.rootId === root.id).length === 0) return st
|
|
224
|
+
}
|
|
225
|
+
return await callTool('vibe_v5_status', {}, root)
|
|
226
|
+
}
|
|
227
|
+
async function foundInstitute(root, problem, params) {
|
|
228
|
+
await callTool('vibe_v5_start', Object.assign({ problem, researcherCount: 2 }, params || {}), root)
|
|
229
|
+
for (const sp of spawnsFor(root)) { fireEnd(sp.childId, { progress: memberOfChild(sp.childId) + ':初见解。', solved: false, contextPct: 10 }); await settle() }
|
|
230
|
+
await callTool('vibe_v5_set', { maxParallel: 8 }, root)
|
|
231
|
+
return await settleInstitute(root)
|
|
232
|
+
}
|
|
233
|
+
// Vote a verdict through to completion. `waitRounds` bounds the debate loop.
|
|
234
|
+
async function voteToConclusion(root, target, plan, rounds = 5) {
|
|
235
|
+
votePlan = plan
|
|
236
|
+
for (let i = 0; i < rounds; i++) {
|
|
237
|
+
delivered.length = 0
|
|
238
|
+
await settle(); await drainWakes(10, root)
|
|
239
|
+
const st = await callTool('vibe_v5_status', {}, root)
|
|
240
|
+
if (!st.verify || st.verify.target !== target) return st
|
|
241
|
+
if (st.undecided.indexOf(target) !== -1 || st.verified.indexOf(target) !== -1) return st
|
|
242
|
+
}
|
|
243
|
+
return await callTool('vibe_v5_status', {}, root)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Deterministically wake ONE member and answer with a chosen reply.
|
|
247
|
+
// Firing an end for a member that has no in-flight turn is a no-op (the in-flight token is
|
|
248
|
+
// what makes onMemberEnd honour the event), so the wake must be created first.
|
|
249
|
+
async function wakeAndReply(root, memberId, reply, fromMember) {
|
|
250
|
+
delivered.length = 0
|
|
251
|
+
await callTool('vibe_v5_say', { to: memberId, text: '请处理这件事。' }, childAgent(childOf(root, fromMember || 'acad')))
|
|
252
|
+
await settle()
|
|
253
|
+
const idx = wakes.findIndex(w => w.rootId === root.id && memberOfChild(w.childId) === memberId)
|
|
254
|
+
if (idx === -1) return null
|
|
255
|
+
const w = wakes.splice(idx, 1)[0]
|
|
256
|
+
fireEnd(w.childId, reply)
|
|
257
|
+
await settle()
|
|
258
|
+
await drainWakes(8, root)
|
|
259
|
+
return w
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const instRootOf = (root) => join(WS, 'VibeMath', 'Projects', 'default', 'Institutes', 'institute')
|
|
263
|
+
const vibeRoot = join(WS, 'VibeMath')
|
|
264
|
+
const readIf = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : '')
|
|
265
|
+
|
|
266
|
+
// ===============================================================
|
|
267
|
+
console.log('-- V5 Lean formal verification --')
|
|
268
|
+
|
|
269
|
+
// ---------- 1. 'off' is a true no-op ----------
|
|
270
|
+
section("1 'off' (default) is a true no-op")
|
|
271
|
+
const RA = makeRoot()
|
|
272
|
+
const st0 = await foundInstitute(RA, '形式化开关默认关闭测试')
|
|
273
|
+
assert(st0.params.formalVerify === 'off', "the default is 'off' (got " + st0.params.formalVerify + ')')
|
|
274
|
+
{
|
|
275
|
+
const allPrompts = spawnsFor(RA).map(s => s.prompt || '').join('\n') + '\n' + delivered.filter(d => d.rootId === RA.id).map(d => d.prompt).join('\n')
|
|
276
|
+
assert(!/Lean/.test(allPrompts), 'no founding/round prompt mentions Lean in off mode')
|
|
277
|
+
assert(!/形式化/.test(allPrompts), 'no prompt mentions 形式化 in off mode')
|
|
278
|
+
assert(!/\[形式化\]/.test(allPrompts), 'the [形式化] state line is absent in off mode')
|
|
279
|
+
}
|
|
280
|
+
// an off-mode verification must behave exactly as before (no gate at all)
|
|
281
|
+
await callTool('vibe_v5_record_proposition', { id: 'p-off', statement: '关模式下的普通命题', value: 0.6, motive: 'm', p: 0.8 }, childAgent(childOf(RA, 'r-1')))
|
|
282
|
+
await callTool('vibe_v5_propose_verify', { target: 'p-off', kind: 'proposition', reason: '直接表决' }, childAgent(childOf(RA, 'r-1')))
|
|
283
|
+
const offDone = await voteToConclusion(RA, 'p-off', new Map([['acad', 1], ['r-1', 1], ['r-2', 1]]))
|
|
284
|
+
assert(offDone.verified.indexOf('p-off') !== -1, "'off' mode still finalizes on a unanimous boolean vote with NO Lean artifact")
|
|
285
|
+
assert(existsSync(join(instRootOf(RA), 'Verified', '命题', 'p-off.md')), "the Verified card was written in 'off' mode")
|
|
286
|
+
assert(!/形式化/.test(readIf(join(instRootOf(RA), 'Verified', '命题', 'p-off.md'))), 'the card carries no formal line in off mode')
|
|
287
|
+
// the tools still EXIST in off mode (static registration), they are just never advertised
|
|
288
|
+
assert(!!toolRegs.find(t => t.name === 'vibe_v5_lean_run') && !!toolRegs.find(t => t.name === 'vibe_v5_lean_archive') && !!toolRegs.find(t => t.name === 'vibe_v5_lean_lib'),
|
|
289
|
+
'the three Lean tools are registered in every mode (registration is static)')
|
|
290
|
+
|
|
291
|
+
// ---------- 2. parameter validation ----------
|
|
292
|
+
section('2 parameter validation and runtime switching')
|
|
293
|
+
const RB = makeRoot()
|
|
294
|
+
await foundInstitute(RB, '形式化参数校验测试')
|
|
295
|
+
const bad = await callTool('vibe_v5_set', { formalVerify: 'banana' }, RB)
|
|
296
|
+
assert(bad.params.formalVerify === 'off', "an unknown mode degrades to 'off', never to a stronger mode (got " + bad.params.formalVerify + ')')
|
|
297
|
+
const enc = await callTool('vibe_v5_set', { formalVerify: 'encourage' }, RB)
|
|
298
|
+
assert(enc.params.formalVerify === 'encourage', "'encourage' is accepted")
|
|
299
|
+
const req = await callTool('vibe_v5_set', { formalVerify: 'require', leanTimeoutMs: -5, leanCommand: ' ' }, RB)
|
|
300
|
+
assert(req.params.formalVerify === 'require', "'require' is accepted")
|
|
301
|
+
assert(req.params.leanTimeoutMs === 120000, 'a non-positive leanTimeoutMs falls back to the default (' + req.params.leanTimeoutMs + ')')
|
|
302
|
+
assert(req.params.leanCommand === 'lean', 'a blank leanCommand falls back to "lean"')
|
|
303
|
+
await callTool('vibe_v5_set', { leanCommand: 'lake', leanArgs: ['env', 'lean'] }, RB)
|
|
304
|
+
const stL = await callTool('vibe_v5_status', {}, RB)
|
|
305
|
+
assert(stL.params.leanCommand === 'lake' && stL.params.leanArgs.join(' ') === 'env lean', 'leanCommand/leanArgs are settable (lake env lean)')
|
|
306
|
+
|
|
307
|
+
// ---------- 3. 'encourage' injection ----------
|
|
308
|
+
section("3 'encourage' injects the Lean section into the right prompts")
|
|
309
|
+
const RC = makeRoot()
|
|
310
|
+
await foundInstitute(RC, '鼓励模式注入测试')
|
|
311
|
+
await callTool('vibe_v5_set', { formalVerify: 'encourage' }, RC)
|
|
312
|
+
{
|
|
313
|
+
// a plain work round must carry the "formalize reusable things as you go" line
|
|
314
|
+
delivered.length = 0
|
|
315
|
+
await callTool('vibe_v5_say', { to: 'r-1', text: '请继续。' }, childAgent(childOf(RC, 'acad')))
|
|
316
|
+
await settle(); await drainWakes(6, RC)
|
|
317
|
+
const work = delivered.filter(d => d.rootId === RC.id).map(d => d.prompt).join('\n')
|
|
318
|
+
assert(/\[形式化\] 鼓励 Lean/.test(work), "the state block gains a [形式化] 鼓励 Lean line")
|
|
319
|
+
assert(/【顺手形式化(鼓励)】/.test(work), 'the work round tells members to formalize reusable objects as they go')
|
|
320
|
+
assert(/vibe_v5_lean_archive kind='def'/.test(work), 'the work round points at the archive tool for reusable definitions')
|
|
321
|
+
}
|
|
322
|
+
await callTool('vibe_v5_record_proposition', { id: 'p-enc', statement: '鼓励模式下的忠实性审查', value: 0.6, motive: 'm', p: 0.8 }, childAgent(childOf(RC, 'r-1')))
|
|
323
|
+
await callTool('vibe_v5_propose_verify', { target: 'p-enc', kind: 'proposition', reason: '先看看提示词' }, childAgent(childOf(RC, 'r-1')))
|
|
324
|
+
await settle(); delivered.length = 0; await drainWakes(3, RC)
|
|
325
|
+
{
|
|
326
|
+
const vp = delivered.filter(d => d.rootId === RC.id).map(d => d.prompt).join('\n')
|
|
327
|
+
assert(/【Lean 形式化验证(鼓励模式)】/.test(vp), 'the voting prompt explains the Lean mode')
|
|
328
|
+
assert(/一旦 Lean 通过,你唯一需要确认的就是忠实性/.test(vp), 'the voting prompt states that a passing Lean run shrinks the question to fidelity')
|
|
329
|
+
assert(/实现难度/.test(vp), 'the voting prompt asks for the implementation-difficulty judgement')
|
|
330
|
+
assert(/可以不做,但请在回执的 formal 字段写明难度判断/.test(vp), "'encourage' explicitly allows skipping (with a recorded judgement)")
|
|
331
|
+
assert(/"formal":/.test(vp), 'the reply contract documents the formal field')
|
|
332
|
+
}
|
|
333
|
+
await drainWakes(10, RC)
|
|
334
|
+
|
|
335
|
+
// ---------- 4. the run tool ----------
|
|
336
|
+
section('4 lean_run executes through the subprocess service and reports honestly')
|
|
337
|
+
const RD = makeRoot()
|
|
338
|
+
await foundInstitute(RD, 'Lean 运行测试')
|
|
339
|
+
await callTool('vibe_v5_set', { formalVerify: 'encourage' }, RD)
|
|
340
|
+
const instD = instRootOf(RD)
|
|
341
|
+
mkdirSync(join(instD, 'Formal'), { recursive: true })
|
|
342
|
+
writeFileSync(join(instD, 'Formal', 'good.lean'), 'theorem t : 1 = 1 := rfl\n', 'utf8')
|
|
343
|
+
writeFileSync(join(instD, 'Formal', 'bad.lean'), 'theorem t : 1 = 2 := by sorry\n', 'utf8')
|
|
344
|
+
const runGood = await callTool('vibe_v5_lean_run', { file: 'Formal/good.lean' }, childAgent(childOf(RD, 'r-1')))
|
|
345
|
+
assert(runGood.ok === true && runGood.exitCode === 0, 'a file with no sorry runs green (' + JSON.stringify({ ok: runGood.ok, exitCode: runGood.exitCode }) + ')')
|
|
346
|
+
assert(leanRuns.length > 0 && leanRuns[leanRuns.length - 1].cwd.replace(/\\/g, '/') === instD.replace(/\\/g, '/'), 'the toolchain runs with the institute root as cwd')
|
|
347
|
+
const runBad = await callTool('vibe_v5_lean_run', { file: 'Formal/bad.lean' }, childAgent(childOf(RD, 'r-1')))
|
|
348
|
+
assert(runBad.ok === false && runBad.exitCode === 1, 'a file that still uses sorry reports a red run')
|
|
349
|
+
assert(/sorry/.test(runBad.stderr), 'the compiler output is returned verbatim (' + JSON.stringify(runBad.stderr).slice(0, 60) + ')')
|
|
350
|
+
const runMissing = await callTool('vibe_v5_lean_run', { file: 'Formal/nope.lean' }, childAgent(childOf(RD, 'r-1')))
|
|
351
|
+
assert(runMissing.ok === false && runMissing.code === 'V5_NOT_FOUND', 'a missing file is refused with a typed code')
|
|
352
|
+
// The guard's boundary is the VibeMath ROOT, not the institute: the global reuse library
|
|
353
|
+
// deliberately lives at <VibeMath>/Formal/{Lib,Proved}, so climbing out of the institute
|
|
354
|
+
// but staying inside VibeMath must remain legal (it just fails as a missing file).
|
|
355
|
+
const runOutsideInst = await callTool('vibe_v5_lean_run', { file: '../../../Formal/Lib/x.lean' }, childAgent(childOf(RD, 'r-1')))
|
|
356
|
+
assert(runOutsideInst.code === 'V5_NOT_FOUND', 'climbing out of the institute but staying inside VibeMath is allowed (the global library lives there)')
|
|
357
|
+
const runEscape = await callTool('vibe_v5_lean_run', { file: '../../../../../etc/evil.lean' }, childAgent(childOf(RD, 'r-1')))
|
|
358
|
+
assert(runEscape.ok === false && runEscape.code === 'V5_INVALID_ARGUMENT', '★ a traversal that climbs ABOVE the VibeMath root is refused')
|
|
359
|
+
const runEscape2 = await callTool('vibe_v5_lean_run', { file: 'Formal/../../../../../../evil.lean' }, childAgent(childOf(RD, 'r-1')))
|
|
360
|
+
assert(runEscape2.ok === false && runEscape2.code === 'V5_INVALID_ARGUMENT', 'a deeper traversal is refused too')
|
|
361
|
+
const runEscape3 = await callTool('vibe_v5_lean_run', { file: '/etc/evil.lean' }, childAgent(childOf(RD, 'r-1')))
|
|
362
|
+
assert(runEscape3.ok === false && runEscape3.code === 'V5_INVALID_ARGUMENT', 'an unrelated absolute path is refused')
|
|
363
|
+
const runNotLean = await callTool('vibe_v5_lean_run', { file: 'Formal/good.txt' }, childAgent(childOf(RD, 'r-1')))
|
|
364
|
+
assert(runNotLean.ok === false && runNotLean.code === 'V5_INVALID_ARGUMENT', 'only .lean files can be executed')
|
|
365
|
+
toolchainAvailable = false
|
|
366
|
+
const runNoTc = await callTool('vibe_v5_lean_run', { file: 'Formal/good.lean' }, childAgent(childOf(RD, 'r-1')))
|
|
367
|
+
assert(runNoTc.ok === false && runNoTc.code === 'LEAN_NOT_FOUND', 'a missing toolchain returns LEAN_NOT_FOUND instead of crashing')
|
|
368
|
+
assert(/仍可把形式化代码写下来归档/.test(runNoTc.message), 'the failure explains the graceful degradation')
|
|
369
|
+
toolchainAvailable = true
|
|
370
|
+
|
|
371
|
+
// ---------- 5. archive a proof → the vote becomes a FIDELITY review ----------
|
|
372
|
+
section('5 a passing proof flips the review subject to fidelity')
|
|
373
|
+
const arc = await callTool('vibe_v5_lean_archive', {
|
|
374
|
+
kind: 'proof', target: 'p-proof', content: 'theorem p_proof : 3 * 1 ^ 2 - 2 = (1:Nat) ^ 2 := by decide\n', note: '',
|
|
375
|
+
}, childAgent(childOf(RD, 'r-1')))
|
|
376
|
+
assert(arc.ok === true && arc.passed === true, 'the proof is archived and passes (' + JSON.stringify({ ok: arc.ok, passed: arc.passed }) + ')')
|
|
377
|
+
assert(arc.file === 'Formal/p-proof.lean', 'the working file is Formal/<target>.lean')
|
|
378
|
+
assert(arc.proof === 'Verified/Lean/p-proof.lean', 'the archived proof path is Verified/Lean/<target>.lean')
|
|
379
|
+
assert(existsSync(join(instD, 'Formal', 'p-proof.lean')), 'the working file exists on disk')
|
|
380
|
+
assert(existsSync(join(instD, 'Verified', 'Lean', 'p-proof.lean')), '★ the proof is archived under Verified/Lean/ as the proof of that object')
|
|
381
|
+
const stD = await callTool('vibe_v5_status', {}, RD)
|
|
382
|
+
assert(stD.formal.passed.indexOf('p-proof') !== -1, 'status reports the object as Lean-passed')
|
|
383
|
+
const idxD = readIf(join(instD, 'Formal', 'Index.md'))
|
|
384
|
+
assert(/p-proof/.test(idxD) && /passed/.test(idxD) && /Verified\/Lean\/p-proof\.lean/.test(idxD), 'Formal/Index.md indexes the object, its status and its archived proof')
|
|
385
|
+
// now the voting prompt must ASK FOR FIDELITY, not for a re-derivation
|
|
386
|
+
await callTool('vibe_v5_record_proposition', { id: 'p-proof', statement: '3N²−2=b² 在 N=1 时成立', value: 0.6, motive: 'm', p: 0.9 }, childAgent(childOf(RD, 'r-1')))
|
|
387
|
+
await callTool('vibe_v5_propose_verify', { target: 'p-proof', kind: 'proposition', reason: '已有 Lean 证明' }, childAgent(childOf(RD, 'r-1')))
|
|
388
|
+
await settle(); delivered.length = 0; await drainWakes(3, RD)
|
|
389
|
+
{
|
|
390
|
+
const vp = delivered.filter(d => d.rootId === RD.id).map(d => d.prompt).join('\n')
|
|
391
|
+
assert(/该对象已有\*\*通过的 Lean 形式化证明\*\*/.test(vp), 'the voting prompt announces the passing proof')
|
|
392
|
+
assert(/你不需要重新检查推导/.test(vp), '★ it tells voters NOT to re-derive')
|
|
393
|
+
assert(/忠实性审查/.test(vp), '★ it tells voters the review subject is now fidelity')
|
|
394
|
+
assert(/定义 \/ 对象 \/ 条件 \/ 假设 \/ 结论是否与命题原文\*\*完全一致\*\*/.test(vp), 'it enumerates exactly what fidelity means')
|
|
395
|
+
}
|
|
396
|
+
await drainWakes(10, RD)
|
|
397
|
+
|
|
398
|
+
// ---------- 6. the reusable cross-project library ----------
|
|
399
|
+
section('6 reusable definitions and lemmas go to the GLOBAL library')
|
|
400
|
+
const libPath = join(vibeRoot, 'Formal', 'Lib')
|
|
401
|
+
const provedPath = join(vibeRoot, 'Formal', 'Proved')
|
|
402
|
+
const defRes = await callTool('vibe_v5_lean_archive', { kind: 'def', name: 'ZMod5', content: 'def ZMod5 := Fin 5\n' }, childAgent(childOf(RD, 'r-1')))
|
|
403
|
+
assert(defRes.ok === true && defRes.file === 'Formal/Lib/ZMod5.lean', 'a reusable definition is archived to the global lib (' + defRes.file + ')')
|
|
404
|
+
assert(existsSync(join(libPath, 'ZMod5.lean')), '★ the definition exists under VibeMath/Formal/Lib/ (cross-project, NOT inside the institute)')
|
|
405
|
+
assert(!existsSync(join(instD, 'Formal', 'Lib', 'ZMod5.lean')), 'it is NOT duplicated inside the institute tree')
|
|
406
|
+
const lemRes = await callTool('vibe_v5_lean_archive', { kind: 'lemma', name: 'sq_odd', content: 'theorem sq_odd (n : Nat) : Odd (n*n) → Odd n := by omega\n' }, childAgent(childOf(RD, 'r-1')))
|
|
407
|
+
assert(lemRes.ok === true && lemRes.file === 'Formal/Proved/sq_odd.lean', 'a lemma is archived to Proved/')
|
|
408
|
+
assert(existsSync(join(provedPath, 'sq_odd.lean')), 'the lemma exists under VibeMath/Formal/Proved/')
|
|
409
|
+
const libIdx = readIf(join(libPath, 'Index.md'))
|
|
410
|
+
assert(/ZMod5/.test(libIdx), 'Lib/Index.md lists the new definition')
|
|
411
|
+
const provedIdx = readIf(join(provedPath, 'Index.md'))
|
|
412
|
+
assert(/sq_odd/.test(provedIdx), 'Proved/Index.md lists the new lemma')
|
|
413
|
+
const libList = await callTool('vibe_v5_lean_lib', {}, childAgent(childOf(RD, 'r-1')))
|
|
414
|
+
assert(libList.ok === true && libList.counts.lib >= 1 && libList.counts.proved >= 1, 'lean_lib reports the reuse library sizes (' + JSON.stringify(libList.counts) + ')')
|
|
415
|
+
assert(libList.objects.some(o => o.target === 'p-proof' && o.status === 'passed'), 'lean_lib lists per-object formal status')
|
|
416
|
+
assert(/复用优先/.test(libList.hint || ''), 'lean_lib tells agents to reuse before redefining')
|
|
417
|
+
const noName = await callTool('vibe_v5_lean_archive', { kind: 'def', content: 'def x := 1\n' }, childAgent(childOf(RD, 'r-1')))
|
|
418
|
+
assert(noName.ok === false && noName.code === 'V5_INVALID_ARGUMENT', 'archiving a definition without a name is refused')
|
|
419
|
+
const badKind = await callTool('vibe_v5_lean_archive', { kind: 'nonsense' }, childAgent(childOf(RD, 'r-1')))
|
|
420
|
+
assert(badKind.ok === false && badKind.code === 'V5_INVALID_ARGUMENT', 'an unknown archive kind is refused')
|
|
421
|
+
|
|
422
|
+
// ---------- 7. blocked needs a reason ----------
|
|
423
|
+
section('7 a "blocker" record must be explicit and reasoned')
|
|
424
|
+
const blkNoNote = await callTool('vibe_v5_lean_archive', { kind: 'blocked', target: 'p-blk' }, childAgent(childOf(RD, 'r-1')))
|
|
425
|
+
assert(blkNoNote.ok === false && blkNoNote.code === 'V5_INVALID_ARGUMENT', 'blocked without a note is refused')
|
|
426
|
+
const blk = await callTool('vibe_v5_lean_archive', { kind: 'blocked', target: 'p-blk', note: '需要外层解析数论框架,本轮工作量不可接受' }, childAgent(childOf(RD, 'r-1')))
|
|
427
|
+
assert(blk.ok === true && blk.status === 'blocked', 'a reasoned blocker is recorded')
|
|
428
|
+
const stBlk = await callTool('vibe_v5_status', {}, RD)
|
|
429
|
+
assert(stBlk.formal.blocked.indexOf('p-blk') !== -1, 'status lists the blocked object')
|
|
430
|
+
assert(/需要外层解析数论框架/.test(readIf(join(instD, 'Formal', 'Index.md'))), 'the blocker reason is written into the index')
|
|
431
|
+
|
|
432
|
+
// ---------- 8. the 'require' gate ----------
|
|
433
|
+
section("8 'require' withholds a verdict until the formal record exists")
|
|
434
|
+
const RE = makeRoot()
|
|
435
|
+
await foundInstitute(RE, 'require 门禁测试')
|
|
436
|
+
await callTool('vibe_v5_set', { formalVerify: 'require' }, RE)
|
|
437
|
+
const instE = instRootOf(RE)
|
|
438
|
+
await callTool('vibe_v5_record_proposition', { id: 'p-gate', statement: '必须形式化的命题', value: 0.6, motive: 'm', p: 0.8 }, childAgent(childOf(RE, 'r-1')))
|
|
439
|
+
await callTool('vibe_v5_propose_verify', { target: 'p-gate', kind: 'proposition', reason: '直接表决试试' }, childAgent(childOf(RE, 'r-1')))
|
|
440
|
+
await settle(); delivered.length = 0; await drainWakes(3, RE)
|
|
441
|
+
{
|
|
442
|
+
const vp = delivered.filter(d => d.rootId === RE.id).map(d => d.prompt).join('\n')
|
|
443
|
+
assert(/【Lean 形式化验证(强制模式)】/.test(vp), 'the voting prompt says 强制模式')
|
|
444
|
+
assert(/必须产出 Lean 形式化/.test(vp), "'require' states the formalization is mandatory")
|
|
445
|
+
assert(/本次裁定不会生效/.test(vp), 'the prompt warns that the verdict will not take effect without it')
|
|
446
|
+
}
|
|
447
|
+
const gated = await voteToConclusion(RE, 'p-gate', new Map([['acad', 1], ['r-1', 1], ['r-2', 1]]))
|
|
448
|
+
assert(gated.verified.indexOf('p-gate') === -1, '★ a unanimous TRUE verdict did NOT promote the object to Verified/')
|
|
449
|
+
assert(gated.undecided.indexOf('p-gate') !== -1, '★ it is recorded as 未定论 instead')
|
|
450
|
+
assert(!existsSync(join(instE, 'Verified', '命题', 'p-gate.md')), 'no Verified card was written')
|
|
451
|
+
const todoE = readIf(join(instE, 'Formal', 'TODO.md'))
|
|
452
|
+
assert(existsSync(join(instE, 'Formal', 'TODO.md')), 'Formal/TODO.md was created')
|
|
453
|
+
assert(/p-gate/.test(todoE) && /formal-required/.test(todoE), '★ the object is on the formalization TODO with the machine-readable reason')
|
|
454
|
+
{
|
|
455
|
+
const chat = readdirSync(join(instE, 'Shared', 'Chat')).map(f => readIf(join(instE, 'Shared', 'Chat', f))).join('\n')
|
|
456
|
+
assert(/require 模式.*先有 Lean 通过或显式阻塞记录/.test(chat), 'the withholding is announced in the group chat')
|
|
457
|
+
}
|
|
458
|
+
// now formalize it and re-verify: the gate must open
|
|
459
|
+
const proofNow = await callTool('vibe_v5_lean_archive', { kind: 'proof', target: 'p-gate', content: 'theorem p_gate : 2 + 2 = 4 := by decide\n' }, childAgent(childOf(RE, 'r-1')))
|
|
460
|
+
assert(proofNow.ok === true && proofNow.passed === true, 'the object is now Lean-passed')
|
|
461
|
+
await callTool('vibe_v5_propose_verify', { target: 'p-gate', kind: 'proposition', reason: '已形式化,重新表决' }, childAgent(childOf(RE, 'r-1')))
|
|
462
|
+
const gated2 = await voteToConclusion(RE, 'p-gate', new Map([['acad', 1], ['r-1', 1], ['r-2', 1]]))
|
|
463
|
+
assert(gated2.verified.indexOf('p-gate') !== -1, '★ with a passing Lean artifact the same vote DOES promote it')
|
|
464
|
+
const card = readIf(join(instE, 'Verified', '命题', 'p-gate.md'))
|
|
465
|
+
assert(/- 形式化: Lean 通过/.test(card), '★ the Verified card records how strong the result is (Lean 通过)')
|
|
466
|
+
assert(/Verified\/Lean\/p-gate\.lean/.test(card), 'the card points at the archived proof')
|
|
467
|
+
// the blocker escape hatch must also open the gate
|
|
468
|
+
await callTool('vibe_v5_record_proposition', { id: 'p-blocked-ok', statement: '记录阻塞后可定论', value: 0.6, motive: 'm', p: 0.8 }, childAgent(childOf(RE, 'r-1')))
|
|
469
|
+
await callTool('vibe_v5_lean_archive', { kind: 'blocked', target: 'p-blocked-ok', note: '命题涉及未形式化的分析学,本轮不做' }, childAgent(childOf(RE, 'r-1')))
|
|
470
|
+
await callTool('vibe_v5_propose_verify', { target: 'p-blocked-ok', kind: 'proposition', reason: '已记录阻塞' }, childAgent(childOf(RE, 'r-1')))
|
|
471
|
+
const gated3 = await voteToConclusion(RE, 'p-blocked-ok', new Map([['acad', 1], ['r-1', 1], ['r-2', 1]]))
|
|
472
|
+
assert(gated3.verified.indexOf('p-blocked-ok') !== -1, '★ an explicit reasoned blocker also lets the verdict through (decide by difficulty, but decide out loud)')
|
|
473
|
+
assert(/- 形式化: 阻塞(/.test(readIf(join(instE, 'Verified', '命题', 'p-blocked-ok.md'))), 'the card records the blocker')
|
|
474
|
+
|
|
475
|
+
// ---------- 9. the reply-channel judgement ----------
|
|
476
|
+
section('9 the per-round `formal` reply channel records the difficulty judgement')
|
|
477
|
+
const RF = makeRoot()
|
|
478
|
+
await foundInstitute(RF, '回执 formal 通道测试')
|
|
479
|
+
await callTool('vibe_v5_set', { formalVerify: 'require' }, RF)
|
|
480
|
+
const instF = instRootOf(RF)
|
|
481
|
+
await callTool('vibe_v5_record_proposition', { id: 'p-reply', statement: '用回执记录阻塞', value: 0.6, motive: 'm', p: 0.8 }, childAgent(childOf(RF, 'r-1')))
|
|
482
|
+
// A member that never calls a Lean tool still has to state its difficulty judgement, so the
|
|
483
|
+
// JSON reply channel matters: it is the path that actually fires in practice.
|
|
484
|
+
const w1 = await wakeAndReply(RF, 'r-1', {
|
|
485
|
+
progress: '难度太高,本轮不做形式化。',
|
|
486
|
+
formal: { target: 'p-reply', decision: 'blocked', note: '需要大量未形式化的实分析前置知识' },
|
|
487
|
+
contextPct: 20,
|
|
488
|
+
})
|
|
489
|
+
assert(!!w1, 'r-1 was woken and answered')
|
|
490
|
+
const stF = await callTool('vibe_v5_status', {}, RF)
|
|
491
|
+
assert(stF.formal.blocked.indexOf('p-reply') !== -1, 'a `formal.decision=blocked` reply is recorded as a blocker')
|
|
492
|
+
assert(/实分析前置知识/.test(readIf(join(instF, 'Formal', 'Index.md'))), 'and its reason reaches the index')
|
|
493
|
+
// a `used` judgement records the file as attempted (not yet passed — nothing ran green)
|
|
494
|
+
const w2 = await wakeAndReply(RF, 'r-2', {
|
|
495
|
+
progress: '我写了形式化草稿。',
|
|
496
|
+
formal: { target: 'p-reply2', decision: 'used', file: 'Formal/p-reply2.lean' },
|
|
497
|
+
contextPct: 20,
|
|
498
|
+
})
|
|
499
|
+
assert(!!w2, 'r-2 was woken and answered')
|
|
500
|
+
const stF2 = await callTool('vibe_v5_status', {}, RF)
|
|
501
|
+
assert(stF2.formal.objects.some(o => o.target === 'p-reply2' && o.status === 'attempted'),
|
|
502
|
+
'a `formal.decision=used` reply records the object as attempted with its file')
|
|
503
|
+
// a blocker with no note must be refused AND reported back to the member
|
|
504
|
+
delivered.length = 0
|
|
505
|
+
const w3 = await wakeAndReply(RF, 'r-1', { formal: { target: 'p-reply3', decision: 'blocked' }, contextPct: 20 })
|
|
506
|
+
assert(!!w3, 'r-1 was woken for the third judgement')
|
|
507
|
+
const said = delivered.map(d => d.prompt).join('\n')
|
|
508
|
+
assert(/必须写明 note/.test(said), 'a blocked judgement without a note is refused with an explicit notice to the member')
|
|
509
|
+
assert((await callTool('vibe_v5_status', {}, RF)).formal.objects.every(o => o.target !== 'p-reply3'),
|
|
510
|
+
'and no blocker record is created for the refused judgement')
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
// ---------- 10. reporting ----------
|
|
514
|
+
section('10 the office can audit formal strength')
|
|
515
|
+
const rep = await callTool('vibe_v5_report', {}, RE)
|
|
516
|
+
assert(/## Lean 形式化/.test(rep.report), 'the report has a Lean formal-verification section')
|
|
517
|
+
assert(/已通过:.*p-gate/.test(rep.report), 'the report lists Lean-passed objects')
|
|
518
|
+
assert(/已记录阻塞:.*p-blocked-ok/.test(rep.report), 'the report lists blocked objects')
|
|
519
|
+
const stOff = await callTool('vibe_v5_report', {}, RA)
|
|
520
|
+
assert(/未启用(`formalVerify` = off/.test(stOff.report), 'in off mode the report says the feature is not enabled')
|
|
521
|
+
|
|
522
|
+
console.log('')
|
|
523
|
+
console.log('passed=' + passed + ' failed=' + failed)
|
|
524
|
+
if (failed) { console.error('FAILURES:'); for (const f of failures) console.error(' - ' + f); process.exit(1) }
|
|
525
|
+
console.log('ALL GREEN')
|
|
526
|
+
process.exit(0)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vibe-math",
|
|
3
3
|
"description": "Multi-agent mathematical problem-solving & verification frameworks for DeepSeek Harness — FOUR agent presets in one install: vibe-math-v2 (probability-driven: qs.json + Propos knowledge base + explorer→solver→review/debate verdict), vibe-math-v3 (THIRD-generation, recommended: paper-style Markdown knowledge base with Problems/Progress/Propos/Methods/Verified + planner-agent scheduling that decides the next N actions + universal theory/method invention library + agents write their own Markdown directly via a per-file write lock), and vibe-math-v4 (FOURTH-generation: persistent self-organizing resident subagents that message & meet to decide all tasks, verify only by unanimous consensus, /compact at a context threshold, and stop only when all agree the problem is solved), and vibe-math-v5 (FIFTH-generation research institute: an academician as the organizational centre who decomposes and ASSIGNS work and chairs meetings; permanent researchers who hold the vote and may hire/fire their own temp workers; temp workers with no vote; a group chat and meetings; a durable per-recipient mailbox; a compare-and-set task DAG; and a boolean m-vote consensus rule where an object enters Verified/ only when at least m voting members agree AND every one of them returns exactly 1 or exactly 0). Installing this bundle auto-installs all four presets (v1 was removed at v2.0.0).",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -14,10 +14,17 @@
|
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"AUDIT-CHECKLIST.md",
|
|
17
|
+
"audit-formal-sensitivity.mjs",
|
|
18
|
+
"audit-persona-sensitivity.mjs",
|
|
19
|
+
"audit-persona-surface.test.mjs",
|
|
17
20
|
"audit-v5-integrity.mjs",
|
|
18
21
|
"audit-v5-sensitivity.mjs",
|
|
19
22
|
"cordis.patch.yml",
|
|
20
23
|
"e2e-v5-round2.test.mjs",
|
|
24
|
+
"formal-verify-v2.test.mjs",
|
|
25
|
+
"formal-verify-v3.test.mjs",
|
|
26
|
+
"formal-verify-v4.test.mjs",
|
|
27
|
+
"formal-verify-v5.test.mjs",
|
|
21
28
|
"installer.js",
|
|
22
29
|
"prompt-v5-integrity.test.mjs",
|
|
23
30
|
"RELEASE-NOTES-2.0.22.md",
|
|
@@ -25,10 +32,16 @@
|
|
|
25
32
|
"RELEASE-NOTES-2.2.0.md",
|
|
26
33
|
"RELEASE-NOTES-2.2.1.md",
|
|
27
34
|
"RELEASE-NOTES-2.2.2.md",
|
|
35
|
+
"RELEASE-NOTES-2.3.0.md",
|
|
28
36
|
"selfdrive-v5.mjs",
|
|
29
37
|
"示例图/框架图-v5.svg",
|
|
30
38
|
"docs/架构图.md",
|
|
39
|
+
"docs/formal-verification.md",
|
|
31
40
|
"docs/generate_framework_diagram_v5.mjs",
|
|
41
|
+
"prompt-corpus-persona/persona-corpus.json",
|
|
42
|
+
"prompt-corpus-persona/persona-corpus.md",
|
|
43
|
+
"prompt-corpus-v3/formal-verify-v3.json",
|
|
44
|
+
"prompt-corpus-v3/formal-verify-v3.md",
|
|
32
45
|
"prompt-corpus-v5/prompt-corpus-v5.json",
|
|
33
46
|
"prompt-corpus-v5/prompt-corpus-v5.md",
|
|
34
47
|
"vibe-math-v2/实现方案.md",
|
|
@@ -78,7 +91,7 @@
|
|
|
78
91
|
},
|
|
79
92
|
"minVersion": "0.1.2-rc.1",
|
|
80
93
|
"testedVersion": "0.1.5-rc.2",
|
|
81
|
-
"compatNote": "依赖宿主提供的 subagents/agents/tools/commands/fs 服务与 @deepseek-ai/dsh-* 插件行;可选 subprocess/sandboxPolicy/compaction。persona 行同时携带 prefix 与 text 两个键,以兼容 0.1.3-alpha.2 的 schema 更名(prefix 必填)与 0.1.2 及更早的 text 键。已在 dsh-v0.1.5-rc.2(@deepseek-ai/dsh-persona 0.1.5-rc.2)上逐行校验全部预设行并通过(v2/v3/v4;v1 已于 v2.0.0 移除)。注意:DSH 0.1.2 起 subagents.startContinuable 的 agentOptions/toolFilter 需要宿主 provider 声明对应 capability(spawn/fork 进程内 provider 均支持),安装器启动时会做能力自检并在旧版宿主上告警。2026 兼容性修复:v2/v3 工具权限名表原先硬编码 web/fetch/bash(未注册名会使 tools.restrict() 抛错、子代理无法建立),现按真实注册名并加带守卫的重试;v4 真实 /compact 原先在 subagent/end 里查 agents.get()(该事件触发时子代理已移出注册表,属死代码),现改为在 subagent/start 捕获 Agent 引用;三套预设的可选服务改为惰性读取,不再在 apply() 快照;v4 的 tools/commands 注册补入 ctx.effect;安装器自检新增 subprocess/sandboxPolicy/compaction。v2.1.0 新增 v5 研究所体系:状态存于宿主 host-only 会话投影单元(键 vibeMathV5),因此自检新增 sessionProjections/sessions(均为可选;缺失时 v5 回退到加固 JSON 状态文件)。v5 不依赖任何 npm 实验包,纯 preset 内单文件实现。v2.2.2 新增 v5 架构图(示例图/框架图-v5.svg + docs/generate_framework_diagram_v5.mjs 零依赖 Node 生成器 + vibe-math-v5/架构图.md 全套 Mermaid 细节图),并修复在绘制架构图时暴露的真实缺陷:会议进行中提出的验证会并发启动(会议与验证的互斥此前只做了单向),现改为排队。v2.2.1 把「全面检查必查清单」(AUDIT-CHECKLIST.md) 作为随包强制流程发布,提示词/交互正确性列为第一优先审计维度。v2.2.0 修复实测发现的提示词身份错乱:状态块改为显式接收它所描述的成员,创建成员时先落盘进编制再构造入职提示词,章程快照冻结在入职时,重建会话不再自称“刚入职”,所办调用不再被误判成某位研究员,框架反馈改为独立发送者投递,一次提示词不再重复投递同一条消息,并新增 prompt-v5-integrity 提示词完整性套件 + 可人工复核的提示词语料(随包发布)。",
|
|
94
|
+
"compatNote": "依赖宿主提供的 subagents/agents/tools/commands/fs 服务与 @deepseek-ai/dsh-* 插件行;可选 subprocess/sandboxPolicy/compaction。persona 行同时携带 prefix 与 text 两个键,以兼容 0.1.3-alpha.2 的 schema 更名(prefix 必填)与 0.1.2 及更早的 text 键。已在 dsh-v0.1.5-rc.2(@deepseek-ai/dsh-persona 0.1.5-rc.2)上逐行校验全部预设行并通过(v2/v3/v4;v1 已于 v2.0.0 移除)。注意:DSH 0.1.2 起 subagents.startContinuable 的 agentOptions/toolFilter 需要宿主 provider 声明对应 capability(spawn/fork 进程内 provider 均支持),安装器启动时会做能力自检并在旧版宿主上告警。2026 兼容性修复:v2/v3 工具权限名表原先硬编码 web/fetch/bash(未注册名会使 tools.restrict() 抛错、子代理无法建立),现按真实注册名并加带守卫的重试;v4 真实 /compact 原先在 subagent/end 里查 agents.get()(该事件触发时子代理已移出注册表,属死代码),现改为在 subagent/start 捕获 Agent 引用;三套预设的可选服务改为惰性读取,不再在 apply() 快照;v4 的 tools/commands 注册补入 ctx.effect;安装器自检新增 subprocess/sandboxPolicy/compaction。v2.1.0 新增 v5 研究所体系:状态存于宿主 host-only 会话投影单元(键 vibeMathV5),因此自检新增 sessionProjections/sessions(均为可选;缺失时 v5 回退到加固 JSON 状态文件)。v5 不依赖任何 npm 实验包,纯 preset 内单文件实现。v2.3.0 为四个架构新增可调控的 Lean 形式化验证(参数 formalVerify = off/encourage/require,默认 off):验证时按实现难度决定是否用 Lean 形式化(写代码+执行),一旦通过则审查对象从「推导是否正确」变成「Lean 的定义/对象/条件/假设/结论是否忠实于命题原文」;形式化代码归档为命题的证明(Verified/Lean/<id>.lean),可复用定义与已证引理归档到跨项目的 VibeMath/Formal/{Lib,Proved}/。require 档带门禁:真/假结论必须先有 Lean 通过或显式阻塞记录,否则记为未定论并进入形式化待办。共用契约 docs/formal-verification.md,四套各带 formal-verify-vN 套件(v2 177 / v3 189 / v4 144 / v5 88 断言)与 audit-formal-sensitivity.mjs 探针。同一次审计还发现并修复了一整类**静态提示词面**缺陷(persona ↔ 工具注册表,既有套件全部盲):v2/v3/v4 的 persona 从未列出无条件注册的三个 *_lean_* 工具,v4 的 vibe_v4_set 参数表漏了 formalVerify/leanCommand/leanArgs/leanTimeoutMs,v3 漏了 setup/save_settings/template,v4 漏了 vibe_v4_prompts,v5 漏了增删常驻研究员的工具、且 prefix 与 text 两个块存在文字漂移;现由 audit-persona-surface.test.mjs(197 断言:双向一致性 + 未文档化工具显式快照 + prefix/text 逐行一致 + 斜杠命令 hint/usage/实际分支三处一致 + Lean 参数/档位/路径,并生成随包发布的 prompt-corpus-persona/ 人读语料)与 audit-persona-sensitivity.mjs(11 条探针,含「未变异副本必须为绿」的对照)守护,AUDIT-CHECKLIST.md 新增 §1.6。v2.2.2 新增 v5 架构图(示例图/框架图-v5.svg + docs/generate_framework_diagram_v5.mjs 零依赖 Node 生成器 + vibe-math-v5/架构图.md 全套 Mermaid 细节图),并修复在绘制架构图时暴露的真实缺陷:会议进行中提出的验证会并发启动(会议与验证的互斥此前只做了单向),现改为排队。v2.2.1 把「全面检查必查清单」(AUDIT-CHECKLIST.md) 作为随包强制流程发布,提示词/交互正确性列为第一优先审计维度。v2.2.0 修复实测发现的提示词身份错乱:状态块改为显式接收它所描述的成员,创建成员时先落盘进编制再构造入职提示词,章程快照冻结在入职时,重建会话不再自称“刚入职”,所办调用不再被误判成某位研究员,框架反馈改为独立发送者投递,一次提示词不再重复投递同一条消息,并新增 prompt-v5-integrity 提示词完整性套件 + 可人工复核的提示词语料(随包发布)。",
|
|
82
95
|
"compatibility": {
|
|
83
96
|
"dshReleases": {
|
|
84
97
|
"0.1.2-alpha.4": "compatible",
|