dsh-vibe-math 2.7.0 → 2.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +681 -685
- package/README.md +9 -13
- package/cordis.patch.yml +12 -6
- package/docs/AUDIT-CHECKLIST.md +518 -518
- package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
- package/docs/final-paper.md +172 -172
- package/docs/formal-verification.md +588 -588
- package/docs/math-computation.md +187 -121
- package/docs/release-notes/README.en.md +48 -0
- package/docs/release-notes/README.md +47 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.1.en.md +86 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.1.md +86 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +72 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.2.md +72 -0
- package/docs/release-notes/TEMPLATE.md +120 -0
- package/docs/test-timing.md +154 -154
- package/package.json +208 -201
- package/prompt-corpus-persona/persona-corpus.json +8 -8
- package/prompt-corpus-persona/persona-corpus.md +24 -12
- package/prompt-corpus-v2/formal-verify-v2.json +176 -98
- package/prompt-corpus-v2/formal-verify-v2.md +1161 -96
- package/prompt-corpus-v3/formal-verify-v3.json +201 -63
- package/prompt-corpus-v3/formal-verify-v3.md +1401 -57
- package/prompt-corpus-v4/formal-verify-v4.json +10 -10
- package/prompt-corpus-v4/formal-verify-v4.md +40 -20
- package/prompt-corpus-v5/prompt-corpus-v5.json +81 -81
- package/prompt-corpus-v5/prompt-corpus-v5.md +199 -128
- package/tests/audit-math-computation-contract.mjs +56 -1
- package/tests/audit-math-computation-parity.mjs +30 -1
- package/tests/audit-math-computation-sensitivity.mjs +2 -2
- package/tests/audit-v5-sensitivity.mjs +3 -1
- package/tests/e2e-v5-round2.test.mjs +116 -0
- package/tests/formal-verify-v2.test.mjs +108 -2
- package/tests/formal-verify-v3.test.mjs +117 -2
- package/tests/formal-verify-v4.test.mjs +87 -0
- package/tests/helpers/math-computation-fake-seam.mjs +30 -2
- package/tests/math-computation-shared.test.mjs +430 -3
- package/vibe-math-v2/agent.cordis.yml +4 -2
- package/vibe-math-v2/math-computation.js +509 -55
- package/vibe-math-v2/math-engines.js +47 -6
- package/vibe-math-v2/vibe-math-v2.js +92 -12
- package/vibe-math-v3/agent.cordis.yml +4 -2
- package/vibe-math-v3/math-computation.js +509 -55
- package/vibe-math-v3/math-engines.js +47 -6
- package/vibe-math-v3/vibe-math-v3.js +77 -14
- package/vibe-math-v4/agent.cordis.yml +8 -4
- package/vibe-math-v4/math-computation.js +509 -55
- package/vibe-math-v4/math-engines.js +47 -6
- package/vibe-math-v4/vibe-math-v4.js +70 -15
- package/vibe-math-v5/agent.cordis.yml +8 -4
- package/vibe-math-v5/math-computation.js +509 -55
- package/vibe-math-v5/math-engines.js +47 -6
- package/vibe-math-v5/vibe-math-v5.js +152 -31
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1660 -1655
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
|
@@ -22,14 +22,47 @@ export const MATH_ENGINES = {
|
|
|
22
22
|
candidates: ['python3', 'python', 'py'], winPrefix: ['-3'],
|
|
23
23
|
versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+\\.\\d+)',
|
|
24
24
|
scriptArgv: ['<script>'], evalArgv: ['-c', '<expr>'], stdinArgv: ['-'], ext: '.py',
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// round-7 (live): NO shell metacharacters (`|`, `%`) in any probe argv - a real host rejected
|
|
26
|
+
// them with a null spawn. One line per package: "name ok|missing".
|
|
27
|
+
probeCode: "import importlib.util,sys\nfor p in sys.argv[1:]: print(p, 'ok' if importlib.util.find_spec(p) is not None else 'missing')",
|
|
28
|
+
// round-7 (finding 6, found on the real bundled python): the probe code iterates `sys.argv[1:]`,
|
|
29
|
+
// so the names must be SEPARATE argv items - the old comma-joined `<pkgs>` made it look up one
|
|
30
|
+
// package literally named "numpy,pandas" and reported everything missing.
|
|
31
|
+
packageProbe: { argv: ['-c', '<probeCode>', '<pkgs...>'], parse: 'pairs' },
|
|
27
32
|
license: 'free', argErrorHints: ARG_ERROR_HINTS,
|
|
28
33
|
install: {
|
|
29
34
|
manager: 'pip',
|
|
30
35
|
userArgv: ['<exe>', '-m', 'pip', 'install', '--user', '<pkg>'],
|
|
31
36
|
systemArgv: ['<exe>', '-m', 'pip', 'install', '<pkg>'],
|
|
32
37
|
uninstallArgv: ['<exe>', '-m', 'pip', 'uninstall', '-y', '<pkg>'],
|
|
38
|
+
// Round-6 (B): DISPATCH by the detected interpreter environment. `pip` is the documented
|
|
39
|
+
// fallback; the module picks conda/mamba/uv only when the interpreter's OWN environment says so
|
|
40
|
+
// (detect, don't guess) and the plan states which manager it assumed.
|
|
41
|
+
managers: {
|
|
42
|
+
conda: {
|
|
43
|
+
detect: 'conda-markers',
|
|
44
|
+
userArgv: ['conda', 'install', '-y', '-c', 'conda-forge', '<pkg>'],
|
|
45
|
+
uninstallArgv: ['conda', 'remove', '-y', '<pkg>'],
|
|
46
|
+
systemUnsupportedReason: 'conda 环境是用户级的;系统级安装不在 conda 的概念里(本工具只出用户级计划)',
|
|
47
|
+
},
|
|
48
|
+
mamba: {
|
|
49
|
+
detect: 'conda-markers',
|
|
50
|
+
userArgv: ['mamba', 'install', '-y', '-c', 'conda-forge', '<pkg>'],
|
|
51
|
+
uninstallArgv: ['mamba', 'remove', '-y', '<pkg>'],
|
|
52
|
+
systemUnsupportedReason: 'mamba 环境是用户级的;系统级安装不在 mamba 的概念里(本工具只出用户级计划)',
|
|
53
|
+
},
|
|
54
|
+
uv: {
|
|
55
|
+
detect: 'uv-next-to-interpreter',
|
|
56
|
+
userArgv: ['uv', 'pip', 'install', '<pkg>'],
|
|
57
|
+
uninstallArgv: ['uv', 'pip', 'uninstall', '<pkg>'],
|
|
58
|
+
systemUnsupportedReason: 'uv 装进当前虚拟环境(用户级);系统级需要发行版包管理器,本工具只出用户级计划',
|
|
59
|
+
},
|
|
60
|
+
pip: {
|
|
61
|
+
detect: 'fallback',
|
|
62
|
+
systemArgv: ['<exe>', '-m', 'pip', 'install', '<pkg>'],
|
|
63
|
+
uninstallArgv: ['<exe>', '-m', 'pip', 'uninstall', '-y', '<pkg>'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
33
66
|
},
|
|
34
67
|
userInstall: { windows: 'winget install --id Python.Python.3.12', macos: 'brew install python', linux: 'apt install python3' },
|
|
35
68
|
},
|
|
@@ -38,13 +71,16 @@ export const MATH_ENGINES = {
|
|
|
38
71
|
candidates: ['Rscript', 'R'],
|
|
39
72
|
versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+\\.\\d+)',
|
|
40
73
|
scriptArgv: ['<script>'], evalArgv: ['-e', '<expr>'], stdinArgv: ['--no-save', '--slave'], ext: '.R',
|
|
41
|
-
|
|
74
|
+
// round-7 (live): line-per-package, no `|` in the argv.
|
|
75
|
+
probeCode: "for (p in strsplit('__PKGS__',',')[[1]]) cat(p, if (requireNamespace(p, quietly=TRUE)) 'ok' else 'missing', '\\n')",
|
|
42
76
|
packageProbe: { argv: ['-e', '<probeCode>'], parse: 'pairs' },
|
|
43
77
|
license: 'free', argErrorHints: ARG_ERROR_HINTS,
|
|
44
78
|
install: {
|
|
45
79
|
manager: 'r',
|
|
46
80
|
userArgv: ['<exe>', '-e', "install.packages('__PKG__',lib=Sys.getenv('R_LIBS_USER'),repos='https://cloud.r-project.org')"],
|
|
47
81
|
uninstallArgv: ['<exe>', '-e', "remove.packages('__PKG__',lib=Sys.getenv('R_LIBS_USER'))"],
|
|
82
|
+
// Round-6 (C): user-only BY DESIGN, and the refusal now says why per engine.
|
|
83
|
+
systemUnsupportedReason: 'R 的用户库是 R_LIBS_USER;系统级安装要写发行版的包目录(需 root 或发行版包管理器),本工具只出用户级计划',
|
|
48
84
|
},
|
|
49
85
|
userInstall: { windows: 'winget install --id RProject.R', macos: 'brew install r', linux: 'apt install r-base' },
|
|
50
86
|
},
|
|
@@ -54,13 +90,15 @@ export const MATH_ENGINES = {
|
|
|
54
90
|
versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+\\.\\d+)',
|
|
55
91
|
scriptArgv: ['--no-gui', '--quiet', '<script>'], evalArgv: ['--no-gui', '--quiet', '--eval', '<expr>'],
|
|
56
92
|
stdinArgv: ['--no-gui', '--quiet'], ext: '.m',
|
|
57
|
-
|
|
93
|
+
// round-7 (live): line-per-package via disp, no `|` and no `%` in the argv.
|
|
94
|
+
probeCode: "s=pkg('list'); n={'__QPKGS__'}; for i=1:numel(n); if any(cellfun(@(x) strcmp(x.name,n{i}),s)); disp([n{i} ' ok']); else; disp([n{i} ' missing']); end; end",
|
|
58
95
|
packageProbe: { argv: ['--no-gui', '--quiet', '--eval', '<probeCode>'], parse: 'pairs' },
|
|
59
96
|
license: 'free', argErrorHints: ARG_ERROR_HINTS,
|
|
60
97
|
install: {
|
|
61
98
|
manager: 'octave-pkg',
|
|
62
99
|
userArgv: ['<exe>', '--no-gui', '--quiet', '--eval', "pkg install -forge __PKG__"],
|
|
63
100
|
uninstallArgv: ['<exe>', '--no-gui', '--quiet', '--eval', 'pkg uninstall __PKG__'],
|
|
101
|
+
systemUnsupportedReason: 'Octave 的 pkg install 装进用户包目录;系统级要写 Octave 的 share/packages(需 root),本工具只出用户级计划',
|
|
64
102
|
},
|
|
65
103
|
userInstall: { windows: 'winget install --id GNU.Octave', macos: 'brew install octave', linux: 'apt install octave' },
|
|
66
104
|
},
|
|
@@ -76,6 +114,7 @@ export const MATH_ENGINES = {
|
|
|
76
114
|
manager: 'julia-pkg',
|
|
77
115
|
userArgv: ['<exe>', '-e', 'using Pkg; Pkg.add("__PKG__")'],
|
|
78
116
|
uninstallArgv: ['<exe>', '-e', 'using Pkg; Pkg.rm("__PKG__")'],
|
|
117
|
+
systemUnsupportedReason: 'Julia 的 Pkg.add 装进当前活动环境 / 用户 depot(JULIA_DEPOT_PATH);系统级不是 Julia 的概念,本工具只出用户级计划',
|
|
79
118
|
},
|
|
80
119
|
userInstall: { windows: 'winget install --id Julialang.Julia', macos: 'brew install julia', linux: 'apt install julia' },
|
|
81
120
|
},
|
|
@@ -84,7 +123,8 @@ export const MATH_ENGINES = {
|
|
|
84
123
|
candidates: ['matlab'],
|
|
85
124
|
versionArgv: ['-batch', 'disp(version)'], versionRe: '(\\d+\\.\\d+)',
|
|
86
125
|
scriptArgv: ['-batch', "run('<script>')"], evalArgv: ['-batch', '<expr>'], stdinArgv: null, ext: '.m',
|
|
87
|
-
|
|
126
|
+
// round-7 (live): no `|`/`%` in any probe argv (a real host rejected such argv with a null spawn).
|
|
127
|
+
probeCode: "t={'__QPKGS__'}; for i=1:numel(t); if license('test',t{i}); disp([t{i} ' ok']); else; disp([t{i} ' missing']); end; end",
|
|
88
128
|
packageProbe: { argv: ['-batch', '<probeCode>'], parse: 'pairs' },
|
|
89
129
|
license: 'commercial', argErrorHints: ARG_ERROR_HINTS,
|
|
90
130
|
licenseProbe: { argv: ['-batch', "disp(license('test','MATLAB'))"], okWhen: 'trim-1' },
|
|
@@ -96,7 +136,8 @@ export const MATH_ENGINES = {
|
|
|
96
136
|
candidates: ['maple'],
|
|
97
137
|
versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+)',
|
|
98
138
|
scriptArgv: ['-q', '<script>'], evalArgv: ['-q', '-c', '<expr>'], stdinArgv: null, ext: '.mpl',
|
|
99
|
-
|
|
139
|
+
// round-7 (live): `print` instead of printf so the argv carries no `|`/`%`.
|
|
140
|
+
probeCode: 'for p in ["__PKGS__"] do try with(p) catch: print(p, " missing"); next end try; print(p, " ok") end do',
|
|
100
141
|
packageProbe: { argv: ['-q', '-c', '<probeCode>'], parse: 'pairs' },
|
|
101
142
|
license: 'commercial', argErrorHints: ARG_ERROR_HINTS,
|
|
102
143
|
licenseProbe: { argv: ['-q', '-c', 'printf("1")'], okWhen: 'exit-0' },
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
MATH_TOOL_DESCRIPTION,
|
|
31
31
|
MATH_TOOL_SCHEMA,
|
|
32
32
|
MATH_ARCHIVE_WORKFLOW_LINE,
|
|
33
|
+
MATH_SUBSTITUTION_RULE_LINE,
|
|
33
34
|
MATH_PERSONA_TOOL_LINE,
|
|
34
35
|
MATH_RULE_LINES,
|
|
35
36
|
normalizeMathParams,
|
|
@@ -536,6 +537,11 @@ export function apply(ctx) {
|
|
|
536
537
|
// **可选能力,按宿主实际情况声明**:宿主 fs 没有 listDir 就不给这个回调(模块据此跳过
|
|
537
538
|
// "每项目 run 目录数 >200" 的保留检查,而不是每次运行都白问一次)。
|
|
538
539
|
listDir: (typeof fs.listDir === 'function') ? mathListDir : undefined,
|
|
540
|
+
// round-7 (fix 2): DSH's own bundled runtimes (`<home>/.dsh/dsh-runtimes/*/dependencies/<engine>/`)
|
|
541
|
+
// as a LAST resort after PATH. Only generic roots are supplied here; the module globs the tree
|
|
542
|
+
// name (never `dsh-primary-runtime`) and only accepts the descriptor's own candidate names.
|
|
543
|
+
runtimeRoots: () => { const env = process.env.DSH_HOME; if (env) return [String(env)]; const home = String(process.env.HOME || process.env.USERPROFILE || ''); return home ? [home.replace(/[\\/]+$/, '') + '/.dsh'] : [] },
|
|
544
|
+
listDirAbs: async function (abs) { try { const t = await fsTarget(abs); const st = await fs.stat(t); if (!st) return []; return (await fs.listDir(t)) || [] } catch (e) { return [] } },
|
|
539
545
|
writeText: async function (rel, text) { return await writeText(rel, text) },
|
|
540
546
|
readText: async function (rel) { return await readText(rel) },
|
|
541
547
|
exists: mathExists,
|
|
@@ -557,7 +563,15 @@ export function apply(ctx) {
|
|
|
557
563
|
async function refreshMathProbe(force) {
|
|
558
564
|
if (params.mathComputation === 'off') { mathProbe = null; mathProbeAt = 0; return null }
|
|
559
565
|
if (!mathTool) return null
|
|
560
|
-
try {
|
|
566
|
+
try {
|
|
567
|
+
mathProbe = await mathTool.probe(force ? { refresh: true } : undefined)
|
|
568
|
+
mathProbeAt = now()
|
|
569
|
+
// 规则漂移自检(A 项):可用性行必须带"替代必须声明(诚实性)"这条规则。这里引用**常量**
|
|
570
|
+
// 而不是比对文本——模块以后改措辞不会误报,只有整条规则从 MATH_RULE_LINES 里消失才会响。
|
|
571
|
+
if (mathProbe && mathAvailabilityLine(mathProbe, 'zh', params.mathMode).indexOf(MATH_SUBSTITUTION_RULE_LINE) === -1) {
|
|
572
|
+
console.error('vibe-math-v2: 可用性行缺少替代声明规则(MATH_SUBSTITUTION_RULE_LINE)')
|
|
573
|
+
}
|
|
574
|
+
} catch (e) { mathProbe = null }
|
|
561
575
|
return mathProbe
|
|
562
576
|
}
|
|
563
577
|
/** 心跳里的 TTL 刷新(每拍一次布尔判断,成本可忽略)。 */
|
|
@@ -2035,6 +2049,8 @@ export function apply(ctx) {
|
|
|
2035
2049
|
propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
|
|
2036
2050
|
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
|
|
2037
2051
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
2052
|
+
// D4 审计面:每个在飞验证任务的参与集/票数都由 voteCount 投影出来(两个视图可对照)。
|
|
2053
|
+
verifyTasks: Object.keys(tasks).filter(function (k) { return tasks[k] && tasks[k].type === 'verify' }).map(function (k) { const t = tasks[k]; const v = voteCount(t); return { rId: t.rId, status: t.status, round: v.round, expected: v.expected, dispatched: v.dispatched, reported: v.reportedThisRound, quorum: v.quorum } }),
|
|
2038
2054
|
recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)),
|
|
2039
2055
|
// Lean 形式化:可调档位与开关同处可读参数表(契约 §1),并附当前形式化记录概况。
|
|
2040
2056
|
formal: {
|
|
@@ -2151,7 +2167,22 @@ export function apply(ctx) {
|
|
|
2151
2167
|
// 不再手工累加并发计数:唤醒的是 registry 里已登记的 child,计数已由 registry 长度体现。
|
|
2152
2168
|
await saveAll()
|
|
2153
2169
|
}
|
|
2154
|
-
|
|
2170
|
+
/**
|
|
2171
|
+
* 中断一个**在册**子代理。审计 D1:旧实现 `try{…}catch(e){}` 把"没有这个 child / 宿主拒绝中断"
|
|
2172
|
+
* 都吞成静默成功(调用方拿到 undefined,还以为已经中断)。现在:空/未知 id ⇒ `code` +
|
|
2173
|
+
* `next{tool,hint}`,宿主抛错 ⇒ 保留错误文本并给替代出口;只有真的发出中断才 `ok:true`。
|
|
2174
|
+
*/
|
|
2175
|
+
async function interruptChild(childId) {
|
|
2176
|
+
const id = String(childId == null ? '' : childId).trim()
|
|
2177
|
+
if (!id) return { ok: false, code: 'VIBE_MATH_INVALID_ARGUMENT', message: 'interruptChild 失败:childId 为空——无法确定要中断哪个子代理。', next: { kind: 'reason', tool: 'vibe_math_list_agents', hint: '用 vibe_math_list_agents 列出本会话在册子代理的 id,再带 childId 调用。' } }
|
|
2178
|
+
if (!Object.prototype.hasOwnProperty.call(agentRegistry, id)) return { ok: false, code: 'VIBE_MATH_CHILD_NOT_FOUND', message: 'interruptChild 失败:' + id + ' 不在本会话的在册子代理里(在册 ' + Object.keys(agentRegistry).length + ' 个)——可能已经结束,或不是本会话的子代理。', next: { kind: 'reason', tool: 'vibe_math_list_agents', hint: '用 vibe_math_list_agents 取当前在册 id;要停整个会话用 vibe_math_abort。' } }
|
|
2179
|
+
try {
|
|
2180
|
+
await subagents.interrupt(id, { kind: 'ancestor', agent: rootAgent })
|
|
2181
|
+
return { ok: true, childId: id }
|
|
2182
|
+
} catch (e) {
|
|
2183
|
+
return { ok: false, code: 'VIBE_MATH_INTERRUPT_FAILED', message: 'interruptChild 失败:宿主拒绝中断 ' + id + '(' + String((e && e.message) || e) + ')。', next: { kind: 'reason', tool: 'vibe_math_abort', hint: '宿主不接受单点中断时,用 vibe_math_abort 终止整个会话的子代理。' } }
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2155
2186
|
|
|
2156
2187
|
// ================= prompts =================
|
|
2157
2188
|
function solverPersonaText() { return params.solverPersona ? (String(params.solverPersona) + '\n\n') : '' }
|
|
@@ -2388,7 +2419,10 @@ export function apply(ctx) {
|
|
|
2388
2419
|
if (node === 'verdict') { const overridden = resolution.action === 'override' && (resolution.verdict === 1 || resolution.verdict === 0); const v = overridden ? Number(resolution.verdict) : data.verdict; const applied = await settleVerdict(data.task, v); delete tasks[data.task.id]; return { verdict: v, overridden: overridden, applied: applied } }
|
|
2389
2420
|
return {}
|
|
2390
2421
|
}
|
|
2391
|
-
async function resolveDecision(id, resolution) {
|
|
2422
|
+
async function resolveDecision(id, resolution) {
|
|
2423
|
+
const d = decisionQueue.find(function (x) { return x.id === id })
|
|
2424
|
+
if (!d) return { ok: false, code: 'VIBE_MATH_DECISION_NOT_FOUND', message: 'decision not found:' + String(id == null ? '' : id) + ' 不在人工决策队列里(队列现有 ' + decisionQueue.length + ' 个' + (decisionQueue.length ? (':' + decisionQueue.map(function (x) { return x.id }).join(', ')) : '') + ')——可能已被 vibe_math_decide 结清,或被 start/resume/abort/换项目作废(作废是终态,不会回到 pending)。', next: { kind: 'reason', tool: 'vibe_math_list_decisions', hint: '先用 vibe_math_list_decisions 取当前 pending 的 id;若期望它还在,检查是否发生过 start/resume/abort/换项目。' } }
|
|
2425
|
+
if (d.status !== 'pending') return { ok: false, code: 'VIBE_MATH_DECISION_ALREADY_RESOLVED', message: 'decision already resolved:' + String(id) + ' 当前状态是 ' + String(d.status) + '——每次决策只生效一次。', next: { kind: 'reason', tool: 'vibe_math_list_decisions', hint: '用 vibe_math_list_decisions 看还有哪些 pending;要重新决策请等框架发起新的决策项。' } }; d.status = 'resolved'; d.resolution = resolution; if (scheduler.gate && scheduler.gate.decisionId === id) scheduler.gate = null; logActivity('decide', id + ' resolved: ' + resolution.action + (resolution.verdict !== undefined ? ' ' + resolution.verdict : '')); await saveAll(); scheduleTick(); return { ok: true, message: 'decision resolved' } }
|
|
2392
2426
|
/**
|
|
2393
2427
|
* 清掉 gate 时**必须结清它指向的那个人工决策**(审计 M10)。start/resume/abort/切项目都会离开
|
|
2394
2428
|
* 需要那次决策的运行,而决策若仍是 `pending`,它会永远留在 `vibe_math_list_decisions` 里反复出现——
|
|
@@ -2680,13 +2714,14 @@ export function apply(ctx) {
|
|
|
2680
2714
|
}
|
|
2681
2715
|
async function backfillVerifiers(t) {
|
|
2682
2716
|
const cap = reviewerCap()
|
|
2683
|
-
|
|
2717
|
+
const want = voteCount(t).expected // 快照目标(不再直接读 t.expectedCount,保持单一来源)
|
|
2718
|
+
while (t.children.length < want && t.children.length < cap) {
|
|
2684
2719
|
if (activeCount() >= params.maxParallelThreshold) break
|
|
2685
2720
|
const index = t.children.length
|
|
2686
2721
|
const childId = await spawnChild('verifier:' + t.rId + ':' + index, verifierReviewPrompt(t.r), { role: 'verifier', rId: t.rId, round: 1, index: index })
|
|
2687
2722
|
t.children.push(childId)
|
|
2688
2723
|
}
|
|
2689
|
-
if (t.children.length >=
|
|
2724
|
+
if (t.children.length >= want) t.status = 'debating'
|
|
2690
2725
|
}
|
|
2691
2726
|
async function reconcileVerify() {
|
|
2692
2727
|
const ids = Object.keys(tasks)
|
|
@@ -2702,7 +2737,7 @@ export function apply(ctx) {
|
|
|
2702
2737
|
} else continue
|
|
2703
2738
|
}
|
|
2704
2739
|
if (t.status === 'paused') {
|
|
2705
|
-
const allReported =
|
|
2740
|
+
const allReported = voteCount(t, t.round).allParticipantsReported
|
|
2706
2741
|
if (allReported) { t.status = 'debating'; await advanceVerification(t, t.round); continue }
|
|
2707
2742
|
}
|
|
2708
2743
|
if (t.status !== 'spawning') continue
|
|
@@ -2926,8 +2961,40 @@ export function apply(ctx) {
|
|
|
2926
2961
|
}
|
|
2927
2962
|
|
|
2928
2963
|
// ================= verification (验证器) =================
|
|
2964
|
+
/**
|
|
2965
|
+
* 验证任务的**票数/参与集单一来源**(跨预设审计 D4,与 v3 的 voteCount 同型)。
|
|
2966
|
+
*
|
|
2967
|
+
* 定义(本文件里"参与集"只有一个含义):一个 verify 任务的参与集 = **本轮被派出的评审**,
|
|
2968
|
+
* 也就是 `t.children`(当前 round 的 `childResults` 条目是它们的票);"需要几票"= **任务创建时的
|
|
2969
|
+
* 快照** `t.expectedCount`(processVerify 建任务时写入),**绝不**读实时 `params.verifierCount`
|
|
2970
|
+
* 或 `params.maxParallelThreshold`。旧实现把这个答案分散在三处——`consensus()` 只看
|
|
2971
|
+
* `Object.keys(t.childResults)`(已报票)且用常量 MIN_REVIEWERS 当下限、`finalizeVerification()`
|
|
2972
|
+
* 又自己数一遍、两处内联 `allReported` 用 `t.children` + round——同一个计数在不同视图里可以不同
|
|
2973
|
+
* (中途增/减评审、某个评审掉线后,辩论视图与裁决视图可能对"这一轮算不算完"给出不同答案)。
|
|
2974
|
+
* 现在所有视图都读这一个函数。
|
|
2975
|
+
*/
|
|
2976
|
+
// 契约(P2-v2 修复轮,必须保持):一致/票数下限仍按 **MIN_REVIEWERS** 判定,一致性的对象是
|
|
2977
|
+
// **所有已报结果**(reportedIds)。`voteCount` 只是把这些读法收拢到一个来源 + 提供审计投影;
|
|
2978
|
+
// 它**不**改变"一票不算共识"的既有语义(v2-fix-probes 的 M11 探针钉着这条)。
|
|
2979
|
+
function voteCount(t, round) {
|
|
2980
|
+
const tt = t || {}
|
|
2981
|
+
const expected = Math.max(MIN_REVIEWERS, Number(tt.expectedCount) || 0)
|
|
2982
|
+
const r = Number((round === undefined || round === null) ? (tt.round || 1) : round) || 1
|
|
2983
|
+
const participants = Array.isArray(tt.children) ? tt.children.slice() : []
|
|
2984
|
+
const results = tt.childResults || {}
|
|
2985
|
+
const reportedIds = Object.keys(results)
|
|
2986
|
+
const thisRoundIds = participants.filter(function (cid) { const x = results[cid]; return x && Number(x.round) === r })
|
|
2987
|
+
return {
|
|
2988
|
+
round: r, expected: expected, participants: participants,
|
|
2989
|
+
dispatched: participants.length, reported: reportedIds.length, reportedIds: reportedIds,
|
|
2990
|
+
reportedThisRound: thisRoundIds.length,
|
|
2991
|
+
// 旧语义的「本轮都报了吗」:只看**已派出的评审**,不额外要求达到快照票数(那一条属于裁决下限)。
|
|
2992
|
+
allParticipantsReported: participants.length > 0 && thisRoundIds.length === participants.length,
|
|
2993
|
+
quorum: participants.length > 0 && participants.length >= expected && thisRoundIds.length === participants.length,
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2929
2996
|
// 一票不算共识:≥MIN_REVIEWERS 份独立评审才可能达成/否决共识(审计 M11;与 v3 的最小票数同型)。
|
|
2930
|
-
function consensus(t) { const
|
|
2997
|
+
function consensus(t) { const v = voteCount(t); if (v.reportedIds.length < MIN_REVIEWERS) return false; const vs = v.reportedIds.map(function (cid) { return t.childResults[cid].Result }); return vs.every(function (x) { return x === 1 }) || vs.every(function (x) { return x === 0 }) }
|
|
2931
2998
|
function buildTranscript(t) { const parts = []; const cids = Object.keys(t.childResults); for (let i = 0; i < cids.length; i++) { const r = t.childResults[cids[i]]; parts.push('Reviewer ' + i + ': Result=' + r.Result + ' Reason=' + r.Reason) } return parts.join('\n') }
|
|
2932
2999
|
// 说明(审计 L17):这里曾有一个 `verifierWeight(cid, rigor)`,但全仓只有它的定义、没有任何调用点,
|
|
2933
3000
|
// 且其公式(clamped ±0.2 rigor 加成、按 childId 取准确率)与 finalVerdict 里真正在用的
|
|
@@ -2986,7 +3053,7 @@ export function apply(ctx) {
|
|
|
2986
3053
|
if (t.children.indexOf(childId) === -1) t.children.push(childId)
|
|
2987
3054
|
t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round, key: meta.verificationKey || verifierIdentityKey() }
|
|
2988
3055
|
delete agentRegistry[childId]
|
|
2989
|
-
const allReported =
|
|
3056
|
+
const allReported = voteCount(t, meta.round).allParticipantsReported
|
|
2990
3057
|
if (!allReported) { await saveAll(); return }
|
|
2991
3058
|
await advanceVerification(t, meta.round)
|
|
2992
3059
|
await saveAll()
|
|
@@ -3026,7 +3093,7 @@ export function apply(ctx) {
|
|
|
3026
3093
|
* `stalled`,冷却期后重试(任务保留 ⇒ processVerify 不会为同一个 rId 另造任务,也不会每 tick 重试)。
|
|
3027
3094
|
*/
|
|
3028
3095
|
async function finalizeVerification(t) {
|
|
3029
|
-
const reviews =
|
|
3096
|
+
const reviews = voteCount(t).reportedIds.length
|
|
3030
3097
|
if (reviews < MIN_REVIEWERS) {
|
|
3031
3098
|
const tried = Number(t.reviewerRespawn || 0)
|
|
3032
3099
|
if (scheduler.running && tried < 2 && reviewerCap() >= MIN_REVIEWERS) {
|
|
@@ -3057,7 +3124,8 @@ export function apply(ctx) {
|
|
|
3057
3124
|
}
|
|
3058
3125
|
}
|
|
3059
3126
|
function finalVerdict(t) {
|
|
3060
|
-
const
|
|
3127
|
+
const v = voteCount(t)
|
|
3128
|
+
const rs = v.reportedIds.map(function (cid) { return t.childResults[cid] })
|
|
3061
3129
|
if (rs.length === 0) return 0.5
|
|
3062
3130
|
if (rs.every(function (r) { return r.Result === 1 })) return 1
|
|
3063
3131
|
if (rs.every(function (r) { return r.Result === 0 })) return 0
|
|
@@ -3437,6 +3505,12 @@ export function apply(ctx) {
|
|
|
3437
3505
|
/** 证据索引(spec §3.9):只列**确实存在**的文件;绝不含 Paper/ 自己(否则输入哈希会自我污染)。
|
|
3438
3506
|
* off 档必须真的是无操作(formal-verify-v2 的语料守卫会断言"off 的提示词里零 Lean 文本"):
|
|
3439
3507
|
* formal 未启用时,形式化相关的路径与字段一概不写进材料。 */
|
|
3508
|
+
/**
|
|
3509
|
+
* 成员可见路径说明(跨预设审计 P0)。成员/子代理的**文件工具按会话 cwd 解析**相对路径,而下面列出的
|
|
3510
|
+
* 路径都是**项目根相对**的 ⇒ 必须显式说明"先拼绝对前缀";计算产物用回执里的绝对字段。一处常量、
|
|
3511
|
+
* 五处材料文本各自 append(绝不替换既有行)。
|
|
3512
|
+
*/
|
|
3513
|
+
const PAPER_PATH_NOTE = '(路径说明:成员/子代理的文件工具按**会话 cwd** 解析相对路径,因此上面列出的相对路径都必须先拼上**项目根的绝对前缀**再使用;计算产物请用回执里的绝对字段 `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来。)'
|
|
3440
3514
|
async function paperEvidenceIndex() {
|
|
3441
3515
|
const formalNow = formalOn()
|
|
3442
3516
|
const out = ['qs/qs.json']
|
|
@@ -3446,6 +3520,7 @@ export function apply(ctx) {
|
|
|
3446
3520
|
if (formalNow) for (const f of await listFiles('Verified/Lean')) out.push('Verified/Lean/' + f)
|
|
3447
3521
|
const dirs = ['Verification_logs', 'Progress_Logs'].concat(formalNow ? ['Formal'] : [])
|
|
3448
3522
|
for (let i = 0; i < dirs.length; i++) { const files = await listFiles(dirs[i]); for (let j = 0; j < files.length; j++) out.push(dirs[i] + '/' + files[j]) }
|
|
3523
|
+
out.push(PAPER_PATH_NOTE)
|
|
3449
3524
|
return out.filter(function (x) { return x.indexOf('Paper/') !== 0 }).sort()
|
|
3450
3525
|
}
|
|
3451
3526
|
/** 汇总材料(spec §4):只含既有证据 + 未决/被否证的显式标注。不含时间戳 ⇒ 哈希稳定。 */
|
|
@@ -3470,6 +3545,7 @@ export function apply(ctx) {
|
|
|
3470
3545
|
const props = await getPropos()
|
|
3471
3546
|
L.push('')
|
|
3472
3547
|
L.push('[PROPOSITIONS] (Propos/*.json)')
|
|
3548
|
+
L.push(PAPER_PATH_NOTE)
|
|
3473
3549
|
if (props.length === 0) L.push('- (none)')
|
|
3474
3550
|
for (let i = 0; i < props.length; i++) {
|
|
3475
3551
|
const p = props[i]
|
|
@@ -3923,6 +3999,8 @@ export function apply(ctx) {
|
|
|
3923
3999
|
propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
|
|
3924
4000
|
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
|
|
3925
4001
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
4002
|
+
// D4 审计面:每个在飞验证任务的参与集/票数都由 voteCount 投影出来(两个视图可对照)。
|
|
4003
|
+
verifyTasks: Object.keys(tasks).filter(function (k) { return tasks[k] && tasks[k].type === 'verify' }).map(function (k) { const t = tasks[k]; const v = voteCount(t); return { rId: t.rId, status: t.status, round: v.round, expected: v.expected, dispatched: v.dispatched, reported: v.reportedThisRound, quorum: v.quorum } }),
|
|
3926
4004
|
recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)), params: params,
|
|
3927
4005
|
formal: {
|
|
3928
4006
|
mode: formalMode(), required: formalRequired(),
|
|
@@ -3997,10 +4075,10 @@ export function apply(ctx) {
|
|
|
3997
4075
|
registerTool('vibe_math_set_project', 'Switch the current math project.', objParams({ name: { type: 'string' } }, ['name']), async function (args) { const slug = slugify(args.name); return await setProject(slug, false) })
|
|
3998
4076
|
registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), async function () { return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') } })
|
|
3999
4077
|
registerTool('vibe_math_list_decisions', 'List pending manual decisions.', objParams({}), async function () { return { ok: true, decisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }) } })
|
|
4000
|
-
registerTool('vibe_math_decide', 'Resolve a pending manual decision (verdict override uses verdict: 1|0).', objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'number' } }, ['id', 'action']), async function (args) { const d = decisionQueue.find(function (x) { return x.id === args.id });
|
|
4078
|
+
registerTool('vibe_math_decide', 'Resolve a pending manual decision (verdict override uses verdict: 1|0).', objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'number' } }, ['id', 'action']), async function (args) { const d = decisionQueue.find(function (x) { return x.id === args.id }); const resolution = { action: args.action, verdict: args.verdict }; if (!d || d.status !== 'pending') return await resolveDecision(args.id, resolution); const applied = await applyDecision(d.node, d.data, resolution); const r = await resolveDecision(args.id, resolution); return Object.assign({ ok: true, applied: applied }, r) })
|
|
4001
4079
|
registerTool('vibe_math_list_agents', 'List tracked sub-agents (child sessions).', objParams({}), async function () { const out = []; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) { const m = agentRegistry[ids[i]]; out.push({ childId: ids[i], role: m.role, qid: m.qid, direction: m.direction, round: m.round, rId: m.rId }) } return { ok: true, agents: out, count: out.length } })
|
|
4002
4080
|
registerTool('vibe_math_message_agent', 'Send a message to a tracked child agent (next turn).', objParams({ childId: { type: 'string' }, message: { type: 'string' } }, ['childId', 'message']), async function (args) { if (!agentRegistry[args.childId]) return { ok: false, message: 'unknown childId' }; await followupChild(args.childId, args.message); return { ok: true, message: 'message delivered' } })
|
|
4003
|
-
registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), async function (args) { await interruptChild(args.childId); return { ok:
|
|
4081
|
+
registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), async function (args) { const r = await interruptChild(args.childId); return Object.assign({}, r, { ok: !!(r && r.ok) }) })
|
|
4004
4082
|
// ---- Lean 形式化验证(契约 §5)----
|
|
4005
4083
|
registerTool('vibe_math_lean_run', '(member) Execute the Lean toolchain on one .lean file inside the VibeMath root and report the result. Never throws: a missing toolchain returns LEAN_NOT_FOUND, a non-zero exit returns the compiler output. Pass target=<object id> to also record the run against that object.', objParams({ file: { type: 'string' }, target: { type: 'string' }, timeout_ms: { type: 'integer' } }, ['file']), async function (args, agent) { return await leanRunTool(memberIdOf(agent), args) })
|
|
4006
4084
|
registerTool('vibe_math_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: { type: 'string' }, target: { type: 'string' }, content: { type: 'string' }, from: { type: 'string' }, note: { type: 'string' }, run: { type: 'boolean' } }, ['kind']), async function (args, agent) { return await leanArchive(memberIdOf(agent), args) })
|
|
@@ -4074,6 +4152,8 @@ export function apply(ctx) {
|
|
|
4074
4152
|
mathProbe: function () { return mathProbe },
|
|
4075
4153
|
// P2a:persona 两个文本块必须含的「归档→编辑→重跑」规则(文本取自共享模块常量,persona 不手抄)。
|
|
4076
4154
|
mathArchiveWorkflowLine: MATH_ARCHIVE_WORKFLOW_LINE,
|
|
4155
|
+
// A 项:替代声明规则(文本由共享模块给出;persona 两块与每轮可用性行都必须带它)。
|
|
4156
|
+
mathSubstitutionRuleLine: MATH_SUBSTITUTION_RULE_LINE,
|
|
4077
4157
|
leanQueueSize: function () { return leanQueue.length },
|
|
4078
4158
|
leanJobsPublic: function () { const out = []; for (const j of leanJobs.values()) out.push(leanJobPublic(j)); return out },
|
|
4079
4159
|
// childOwner 裁剪用:这个会话当前仍"可能再发 subagent/end"的 child(在册的 + 任务正在等的)。
|
|
@@ -68,7 +68,8 @@
|
|
|
68
68
|
mode.
|
|
69
69
|
|
|
70
70
|
- math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
|
|
71
|
-
-
|
|
71
|
+
- 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
|
|
72
|
+
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以打开它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有);编辑后用 mode:'file' 指向它重跑,会写出一份**新回执/新 attempt**(含新的 scriptHash)。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
|
|
72
73
|
|
|
73
74
|
A /vibe slash command mirrors the main controls (/vibe start [override]|resume [override]|pause|abort|status|report|mode
|
|
74
75
|
<auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|methods|index|plan|lock|project [list|new <name>|<name>]|decisions|agents|paper [lang=zh|en] [format=both|md|tex] [force]).
|
|
@@ -159,7 +160,8 @@
|
|
|
159
160
|
mode.
|
|
160
161
|
|
|
161
162
|
- math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
|
|
162
|
-
-
|
|
163
|
+
- 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
|
|
164
|
+
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以打开它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有);编辑后用 mode:'file' 指向它重跑,会写出一份**新回执/新 attempt**(含新的 scriptHash)。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
|
|
163
165
|
|
|
164
166
|
A /vibe slash command mirrors the main controls (/vibe start [override]|resume [override]|pause|abort|status|report|mode
|
|
165
167
|
<auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|methods|index|plan|lock|project [list|new <name>|<name>]|decisions|agents|paper [lang=zh|en] [format=both|md|tex] [force]).
|