dsh-vibe-math 2.3.15 → 2.3.16
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 +45 -42
- package/README.md +11 -9
- package/docs/AUDIT-CHECKLIST.md +62 -43
- package/docs/generate_framework_diagram_v2_en.mjs +595 -0
- package/docs/generate_framework_diagram_v3_en.mjs +624 -0
- package/docs/generate_framework_diagram_v4.mjs +380 -125
- package/docs/generate_framework_diagram_v5.mjs +365 -107
- package/docs/release-notes/RELEASE-NOTES-2.3.16.md +165 -0
- package/docs/test-timing.md +2 -2
- package/docs//346/236/266/346/236/204/345/233/276.md +3 -1
- package/package.json +9 -2
- package//347/244/272/344/276/213/345/233/276//346/241/206/346/236/266/345/233/276-v2-en.svg +291 -0
- package//347/244/272/344/276/213/345/233/276//346/241/206/346/236/266/345/233/276-v3-en.svg +316 -0
- package//347/244/272/344/276/213/345/233/276//346/241/206/346/236/266/345/233/276-v4-en.svg +187 -0
- package//347/244/272/344/276/213/345/233/276//346/241/206/346/236/266/345/233/276-v5-en.svg +174 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Vibe Math V2 architecture diagram generator (English edition, zero-dependency, pure Node)
|
|
3
|
+
//
|
|
4
|
+
// node docs/generate_framework_diagram_v2_en.mjs
|
|
5
|
+
// → writes 示例图/框架图-v2-en.svg
|
|
6
|
+
//
|
|
7
|
+
// Why an SVG generator instead of the matplotlib PNG (docs/generate_framework_diagram_v2.py):
|
|
8
|
+
// · the repository runtime already ships Node — no Python / matplotlib to install;
|
|
9
|
+
// · SVG is plain text: diff-friendly, reviewable, and crisp at any zoom.
|
|
10
|
+
// When a PNG is needed (market carousel / preview), do NOT screenshot a window —
|
|
11
|
+
// render from the SVG's own size instead:
|
|
12
|
+
// node docs/render_framework_diagram_png.mjs 示例图/框架图-v2-en.svg 示例图/框架图-v2-en.png 2
|
|
13
|
+
//
|
|
14
|
+
// Layout conventions (keep them when editing, or boxes will overlap):
|
|
15
|
+
// · a band's title sits at y+27, its subtitle at y+47, content starts at y+56;
|
|
16
|
+
// · pipeline column x=190..850, data column x=940..1656; x=22 stays free so the
|
|
17
|
+
// write-back loop can run outside every band and never crosses title text;
|
|
18
|
+
// · text width is estimated while generating; any line that would overflow its
|
|
19
|
+
// container prints a WARN and the process exits with code 1.
|
|
20
|
+
//
|
|
21
|
+
// Content sources: vibe-math-v2/实现方案.md (specification) and README.en.md.
|
|
22
|
+
// Identifiers follow README.en.md: v2's JSON keys are Chinese in the implementation,
|
|
23
|
+
// so their official English renderings are used here (overview / solved / boolean
|
|
24
|
+
// estimate / proof list / disproof list / value·criticality / priority / progress).
|
|
25
|
+
// ============================================================
|
|
26
|
+
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
27
|
+
import { dirname, join } from 'node:path'
|
|
28
|
+
import { fileURLToPath } from 'node:url'
|
|
29
|
+
|
|
30
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
31
|
+
const OUT = join(ROOT, '示例图', '框架图-v2-en.svg')
|
|
32
|
+
|
|
33
|
+
const W = 1720
|
|
34
|
+
const FONT = "'Segoe UI','Helvetica Neue',Arial,'Noto Sans',sans-serif"
|
|
35
|
+
const MONO = "'Cascadia Mono','Consolas','SFMono-Regular',monospace"
|
|
36
|
+
|
|
37
|
+
const C = {
|
|
38
|
+
ink: '#10202e', ink2: '#2c4557', mute: '#5d7788',
|
|
39
|
+
frame: '#0b6fb8', frameBg: '#eaf3fd',
|
|
40
|
+
house: '#b26a00', houseBg: '#fdf5e2',
|
|
41
|
+
data: '#1f7a52', dataBg: '#eef7f1',
|
|
42
|
+
gate: '#b3202c', gateBg: '#fdeef0',
|
|
43
|
+
human: '#5b4bb8', humanBg: '#f1eefc',
|
|
44
|
+
line: '#7d93a3',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------- geometry ----------
|
|
48
|
+
const FULL_L = 40, FULL_R = W - 40 // 40 .. 1680
|
|
49
|
+
const IN_L = FULL_L + 22, IN_R = FULL_R - 22 // 62 .. 1658
|
|
50
|
+
const COL1_X = 190, COL1_W = 660 // pipeline column 190 .. 850
|
|
51
|
+
const COL2_X = 940, COL2_W = 716 // data column 940 .. 1656
|
|
52
|
+
const GRID_X = [62, 600, 1138], GRID_W = 518 // 3-column grid 62 .. 1656
|
|
53
|
+
const FLOW_X = 540, LOOP_X = 22
|
|
54
|
+
const GAP = 18
|
|
55
|
+
|
|
56
|
+
const warnings = []
|
|
57
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
58
|
+
// Rough text-width estimate: CJK / full-width 1.0em, everything else 0.56em.
|
|
59
|
+
// Used to catch overflow far faster than rendering a screenshot.
|
|
60
|
+
const textWidth = (s, fs) => [...String(s)].reduce((n, ch) => n + (/[\u2e80-\u9fff\uff00-\uffef]/.test(ch) ? 1 : 0.56), 0) * fs
|
|
61
|
+
const fits = (s, fs, limit, where) => {
|
|
62
|
+
const w = textWidth(s, fs)
|
|
63
|
+
if (w > limit) warnings.push(`${where}: text width ${w.toFixed(0)} > available ${limit.toFixed(0)} — ${String(s).slice(0, 48)}…`)
|
|
64
|
+
return w
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const body = []
|
|
68
|
+
const push = (s) => body.push(s)
|
|
69
|
+
|
|
70
|
+
// ---------- primitives ----------
|
|
71
|
+
function band(x, y, w, h, title, sub, { fill, stroke, titleFill = C.ink }) {
|
|
72
|
+
push(`<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="14" fill="${fill}" stroke="${stroke}" stroke-width="2"/>`)
|
|
73
|
+
fits(title, 15, w - 36, 'band title')
|
|
74
|
+
push(`<text x="${x + 18}" y="${y + 27}" font-size="15" font-weight="700" fill="${titleFill}">${esc(title)}</text>`)
|
|
75
|
+
if (sub) {
|
|
76
|
+
fits(sub, 12, w - 36, 'band sub')
|
|
77
|
+
push(`<text x="${x + 18}" y="${y + 47}" font-size="12" fill="${C.mute}">${esc(sub)}</text>`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function card(x, y, w, h, lines, { fill = '#ffffff', stroke = C.frame, fs = 13, sfs = 11, titleFill = C.ink } = {}) {
|
|
82
|
+
push(`<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="10" fill="${fill}" stroke="${stroke}" stroke-width="1.6"/>`)
|
|
83
|
+
const cx = x + w / 2
|
|
84
|
+
const lead = fs + 4
|
|
85
|
+
const total = (lines.length - 1) * lead
|
|
86
|
+
if (total + fs > h) warnings.push(`card@${x},${y}: ${lines.length} lines need height ${(total + fs).toFixed(0)} > container ${h}`)
|
|
87
|
+
let ty = y + h / 2 - total / 2 + fs * 0.36
|
|
88
|
+
lines.forEach((ln, i) => {
|
|
89
|
+
const size = i === 0 ? fs : sfs
|
|
90
|
+
fits(ln, size, w - 16, `card line@${x},${y}`)
|
|
91
|
+
push(`<text x="${cx}" y="${ty.toFixed(1)}" font-size="${size}" font-weight="${i === 0 ? 700 : 400}" `
|
|
92
|
+
+ `fill="${i === 0 ? titleFill : C.ink2}" text-anchor="middle">${esc(ln)}</text>`)
|
|
93
|
+
ty += lead
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function plain(x, y, text, { size = 12, fill = C.ink2, anchor = 'start', weight = 400, mono = false, limit = null, where = 'plain', halo = false } = {}) {
|
|
98
|
+
if (limit) fits(text, size, limit, where)
|
|
99
|
+
push(`<text x="${x}" y="${y}" font-size="${size}" font-weight="${weight}" fill="${fill}" text-anchor="${anchor}"`
|
|
100
|
+
+ (halo ? ' paint-order="stroke" stroke="#ffffff" stroke-width="4" stroke-linejoin="round"' : '')
|
|
101
|
+
+ (mono ? ` font-family="${MONO}"` : '') + `>${esc(text)}</text>`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function arrow(x1, y1, x2, y2, { color = C.line, dashed = false, both = true, width = 1.6, marker = 'both' } = {}) {
|
|
105
|
+
push(`<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${color}" stroke-width="${width}" `
|
|
106
|
+
+ (dashed ? 'stroke-dasharray="7 5" ' : '')
|
|
107
|
+
+ (both ? `marker-start="url(#${marker})" ` : '') + `marker-end="url(#${marker})"/>`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function polyline(points, { color = C.human, width = 2, dashed = false, marker = 'bothHuman' } = {}) {
|
|
111
|
+
const d = points.map((p, i) => (i ? 'L' : 'M') + ' ' + p[0] + ' ' + p[1]).join(' ')
|
|
112
|
+
push(`<path d="${d}" fill="none" stroke="${color}" stroke-width="${width}" `
|
|
113
|
+
+ (dashed ? 'stroke-dasharray="7 5" ' : '') + `marker-start="url(#${marker})" marker-end="url(#${marker})"/>`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Left-aligned monospace block with its own frame (used for the file-layout trees).
|
|
117
|
+
// Runs of spaces are turned into NBSP: SVG collapses whitespace by default, which
|
|
118
|
+
// would destroy the column alignment of a tree.
|
|
119
|
+
function block(x, y, w, lines, { stroke = C.data, fs = 11.5, lead = 15.5, pad = 12 } = {}) {
|
|
120
|
+
const h = (lines.length - 1) * lead + fs + pad * 2
|
|
121
|
+
push(`<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="10" fill="#ffffff" stroke="${stroke}" stroke-width="1.6"/>`)
|
|
122
|
+
lines.forEach((ln, i) => {
|
|
123
|
+
fits(ln, fs, w - 20, 'tree line')
|
|
124
|
+
plain(x + 12, y + pad + fs + i * lead, ln.replace(/ /g, '\u00a0'), { size: fs, mono: true, fill: C.ink2 })
|
|
125
|
+
})
|
|
126
|
+
return h
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function legend(y, items, { x0 = 108, step = 320, box = 14, size = 11, limit = 300 } = {}) {
|
|
130
|
+
plain(x0 - 56, y + 12, 'Legend', { size: 12, fill: C.ink, weight: 700 })
|
|
131
|
+
items.forEach(([col, txt], i) => {
|
|
132
|
+
const x = x0 + i * step
|
|
133
|
+
push(`<rect x="${x}" y="${y}" width="${box}" height="${box}" rx="3" fill="#ffffff" stroke="${col}" stroke-width="2"/>`)
|
|
134
|
+
plain(x + box + 7, y + 12, txt, { size, fill: C.ink2, limit, where: 'legend' })
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---------- document head ----------
|
|
139
|
+
// The canvas height is only known once the body has been laid out, so the whole
|
|
140
|
+
// drawing goes into `body` first and the <svg> header is assembled at the end.
|
|
141
|
+
|
|
142
|
+
// ---------- layout cursor ----------
|
|
143
|
+
let y = 84
|
|
144
|
+
function nextBand(h) { const top = y; y = top + h + GAP; return top }
|
|
145
|
+
|
|
146
|
+
// ============================================================
|
|
147
|
+
// 1. Truth gate
|
|
148
|
+
// ============================================================
|
|
149
|
+
const bGate = nextBand(114)
|
|
150
|
+
band(FULL_L, bGate, FULL_R - FULL_L, 114,
|
|
151
|
+
'Truth gate — the only rule that lets an object enter Verified/',
|
|
152
|
+
'a probability is strict: new results must be strictly between 0 and 1 · only the verifiers settle exactly 1 (true) or exactly 0 (false) · anything in between stays in its library',
|
|
153
|
+
{ fill: C.gateBg, stroke: C.gate, titleFill: C.gate })
|
|
154
|
+
card(GRID_X[0], bGate + 56, GRID_W, 44, [
|
|
155
|
+
'all verifiers agree (all 1, or all 0)',
|
|
156
|
+
'near-consensus → that boolean is the verdict, immediately',
|
|
157
|
+
], { stroke: C.gate, fs: 12.5, sfs: 10.5 })
|
|
158
|
+
card(GRID_X[1], bGate + 56, GRID_W, 44, [
|
|
159
|
+
'otherwise: adjudication by verdictMode',
|
|
160
|
+
'flat = balanced (0.5) · forced = weighted by accuracy + rigor',
|
|
161
|
+
], { stroke: C.gate, fs: 12.5, sfs: 10.5 })
|
|
162
|
+
card(GRID_X[2], bGate + 56, GRID_W, 44, [
|
|
163
|
+
'only exactly 1 or exactly 0 enters Verified/',
|
|
164
|
+
'a solver must never mark its own new lemma or solution as 1 or 0',
|
|
165
|
+
], { stroke: C.gate, fs: 12.5, sfs: 10.5 })
|
|
166
|
+
|
|
167
|
+
// ============================================================
|
|
168
|
+
// 2. Interaction layer — user · main agent
|
|
169
|
+
// ============================================================
|
|
170
|
+
const bInter = nextBand(56 + 104 + 14)
|
|
171
|
+
band(FULL_L, bInter, FULL_R - FULL_L, 56 + 104 + 14,
|
|
172
|
+
'Interaction layer — user · main agent (assistant + reporter)',
|
|
173
|
+
'natural language in · requirements, progress and configuration out · the main agent neither solves nor schedules nor votes',
|
|
174
|
+
{ fill: C.humanBg, stroke: C.human, titleFill: C.human })
|
|
175
|
+
card(IN_L, bInter + 56, 788, 104, [
|
|
176
|
+
'user · main agent (assistant + reporter)',
|
|
177
|
+
'the user states a need in natural language: "solve XX" · "what is the progress?" · "pause"',
|
|
178
|
+
'the main agent translates it into vibe_math_* tool calls and echoes the results back',
|
|
179
|
+
'it configures parameters by Q&A (/vibe setup) and reports progress in plain language',
|
|
180
|
+
'it never solves and never schedules — the scheduler is plugin code, not a model',
|
|
181
|
+
'it does not vote either: only verifiers settle a probability at exactly 1 or exactly 0',
|
|
182
|
+
], { stroke: C.human, fs: 13, sfs: 10.5 })
|
|
183
|
+
card(COL2_X, bInter + 56, COL2_W, 104, [
|
|
184
|
+
'vibe_math_* tools and the /vibe slash command (equivalent)',
|
|
185
|
+
'add_problem · add_proposition · list_propositions · new_project · set_project · list_projects',
|
|
186
|
+
'start · resume · pause · abort · status · report · set_mode · set_params · setup',
|
|
187
|
+
'save_settings · template · list_decisions · decide · list_agents · message_agent',
|
|
188
|
+
'interrupt_agent · lean_run · lean_archive · lean_lib (the three Lean tools)',
|
|
189
|
+
'/vibe start|resume|pause|abort|status|report|mode|setup|save|template|add|add-proposition|…',
|
|
190
|
+
], { stroke: C.human, fs: 13, sfs: 10.5 })
|
|
191
|
+
arrow(IN_L + 788, bInter + 108, COL2_X, bInter + 108, { color: C.human, marker: 'bothHuman', width: 1.8 })
|
|
192
|
+
plain((IN_L + 788 + COL2_X) / 2, bInter + 102, 'tools', { size: 10, fill: C.human, anchor: 'middle', halo: true })
|
|
193
|
+
|
|
194
|
+
// ============================================================
|
|
195
|
+
// 3. Scheduling layer — plugin code
|
|
196
|
+
// ============================================================
|
|
197
|
+
const bSched = nextBand(56 + 124 + 14)
|
|
198
|
+
band(FULL_L, bSched, FULL_R - FULL_L, 56 + 124 + 14,
|
|
199
|
+
'Scheduling layer — plugin code · probability-driven · the sole file writer',
|
|
200
|
+
'a smaller priority integer is scheduled first · "never" is never scheduled (and does not block termination) · a new dispatch waits until active sub-agents < maxParallelThreshold',
|
|
201
|
+
{ fill: C.houseBg, stroke: C.house, titleFill: '#8a5200' })
|
|
202
|
+
card(COL1_X, bSched + 56, COL1_W, 124, [
|
|
203
|
+
'scheduler — plugin code · probability-driven (never a model)',
|
|
204
|
+
'the sole file writer: sub-agents only return structured JSON and never write files',
|
|
205
|
+
'takes the highest-priority unsolved problem from qs/qs.json, dispatches explorer /',
|
|
206
|
+
'solver / verifier, advances the state machine, and writes every result to disk',
|
|
207
|
+
'a new dispatch waits until active sub-agents < maxParallelThreshold (default 4)',
|
|
208
|
+
'checkpoint persistence: VibeMath_State/*.json + process epoch · tickIntervalMs heartbeat',
|
|
209
|
+
], { stroke: C.house, fs: 12.5, sfs: 10.5 })
|
|
210
|
+
card(COL2_X, bSched + 56, COL2_W, 58, [
|
|
211
|
+
'qs/qs.json — problem list (single source for solving & verification)',
|
|
212
|
+
'overview / solved / solution list (complete solution · correctness probability) / priority / progress',
|
|
213
|
+
'v2 writes JSON only — the v1-era qs/qs.csv is no longer produced',
|
|
214
|
+
], { stroke: C.data, fs: 12.5, sfs: 10.5 })
|
|
215
|
+
card(COL2_X, bSched + 122, COL2_W, 58, [
|
|
216
|
+
'VibeMath_State/*.json — scheduler-private state (checkpoint / resume)',
|
|
217
|
+
'scheduler_state · agent_registry · decision_queue · verifier_accuracy',
|
|
218
|
+
'tasks · explorer_retries · process_epoch · formal.json',
|
|
219
|
+
], { stroke: C.data, fs: 12.5, sfs: 10.5 })
|
|
220
|
+
arrow(COL1_X + COL1_W, bSched + 85, COL2_X, bSched + 85, { color: C.data, marker: 'bothData' })
|
|
221
|
+
plain((COL1_X + COL1_W + COL2_X) / 2, bSched + 79, 'read/write', { size: 10, fill: C.data, anchor: 'middle', halo: true })
|
|
222
|
+
arrow(COL1_X + COL1_W, bSched + 151, COL2_X, bSched + 151, { color: C.data, marker: 'bothData' })
|
|
223
|
+
plain((COL1_X + COL1_W + COL2_X) / 2, bSched + 145, 'state', { size: 10, fill: C.data, anchor: 'middle', halo: true })
|
|
224
|
+
|
|
225
|
+
// ============================================================
|
|
226
|
+
// 4. Sub-agent layer · direction solving
|
|
227
|
+
// ============================================================
|
|
228
|
+
const bSolve = nextBand(56 + 124 + 14)
|
|
229
|
+
band(FULL_L, bSolve, FULL_R - FULL_L, 56 + 124 + 14,
|
|
230
|
+
'Sub-agent layer · direction solving — brainstorm, then one solver per direction',
|
|
231
|
+
'all directions are dead ends → re-derive brand-new ones (maxExplorerRetries) · one dedicated continuable session per direction, iterating up to solverMaxRounds rounds',
|
|
232
|
+
{ fill: C.frameBg, stroke: C.frame, titleFill: '#084d80' })
|
|
233
|
+
card(COL1_X, bSolve + 56, COL1_W, 124, [
|
|
234
|
+
'explorer → solver × N (one continuable session each)',
|
|
235
|
+
'brainstorm: constraint decomposition · boundary / extreme tests · similar-problem mapping',
|
|
236
|
+
'split q into widely-divergent directions m_i, each with assumptions + a feasibility estimate',
|
|
237
|
+
'non-empty progress: quantify it, prune proven dead ends, derive 1–3 brand-new directions',
|
|
238
|
+
'the solver iterates in-session: lemmas + sub-routes + survival probability + full solution',
|
|
239
|
+
'self-adversarial check (counterexamples, boundary cases) before END; solverMaxRounds cap',
|
|
240
|
+
], { stroke: C.frame, fs: 12.5, sfs: 10.5 })
|
|
241
|
+
card(COL2_X, bSolve + 56, COL2_W, 76, [
|
|
242
|
+
'Propos/<category>_Propos.json — the proposition library',
|
|
243
|
+
'overview · boolean estimate · fine type · priority · value/criticality · progress',
|
|
244
|
+
'proof list / disproof list: complete derivation · correctness probability · evidence',
|
|
245
|
+
], { stroke: C.data, fs: 12.5, sfs: 10.5 })
|
|
246
|
+
card(COL2_X, bSolve + 140, COL2_W, 40, [
|
|
247
|
+
'new results are written by the scheduler with probability < 1',
|
|
248
|
+
'a hard sub-problem q_sub becomes a problem + a temporary hypothesis (boolean estimate 0.5)',
|
|
249
|
+
], { stroke: C.data, fs: 12.5, sfs: 10.5 })
|
|
250
|
+
arrow(COL1_X + COL1_W, bSolve + 94, COL2_X, bSolve + 94, { color: C.data, marker: 'bothData' })
|
|
251
|
+
plain((COL1_X + COL1_W + COL2_X) / 2, bSolve + 88, '② write', { size: 10, fill: C.data, anchor: 'middle', halo: true })
|
|
252
|
+
arrow(COL1_X + COL1_W, bSolve + 160, COL2_X, bSolve + 160, { color: C.data, marker: 'bothData' })
|
|
253
|
+
plain((COL1_X + COL1_W + COL2_X) / 2, bSolve + 154, 'append', { size: 10, fill: C.data, anchor: 'middle', halo: true })
|
|
254
|
+
|
|
255
|
+
// ============================================================
|
|
256
|
+
// 5. Sub-agent layer · cross-verification
|
|
257
|
+
// ============================================================
|
|
258
|
+
const bVeri = nextBand(56 + 124 + 14)
|
|
259
|
+
band(FULL_L, bVeri, FULL_R - FULL_L, 56 + 124 + 14,
|
|
260
|
+
'Sub-agent layer · cross-verification — independent review → debate → adjudication',
|
|
261
|
+
'every target r gets ≥ verifierCount (default 3) harsh reviewers · no communication before their first reports are in · the debate lasts at most debateMaxRounds rounds',
|
|
262
|
+
{ fill: C.frameBg, stroke: C.frame, titleFill: '#084d80' })
|
|
263
|
+
card(COL1_X, bVeri + 56, COL1_W, 124, [
|
|
264
|
+
'verifier × ≥ 3 (harsh reviewers, one session each)',
|
|
265
|
+
'r = proposition | proposition + proof/disproof | problem + solution',
|
|
266
|
+
'independent review first: Result in [0,1] + Reason (a full proof at 1, a disproof at 0)',
|
|
267
|
+
'they must not communicate before every first report is in — independence is the point',
|
|
268
|
+
'debate in one chat group: every verifier speaks each round (agree · rebut · new evidence)',
|
|
269
|
+
'the debate ends the moment all Results are 1 or all are 0; debateMaxRounds cap',
|
|
270
|
+
], { stroke: C.frame, fs: 12.5, sfs: 10.5 })
|
|
271
|
+
card(COL2_X, bVeri + 56, COL2_W, 58, [
|
|
272
|
+
'Verification_logs/<rId>_<time>.json — the audit trail',
|
|
273
|
+
'r · verdict · every verifier Result · the debate transcript · history',
|
|
274
|
+
'verifier historical accuracy is kept for the forced (weighted) mode',
|
|
275
|
+
], { stroke: C.data, fs: 12.5, sfs: 10.5 })
|
|
276
|
+
card(COL2_X, bVeri + 122, COL2_W, 58, [
|
|
277
|
+
'how the scheduler picks the next r',
|
|
278
|
+
'from qs/qs.json or Propos/: an unsolved object whose probability is not 1 or 0',
|
|
279
|
+
'a bare proposition may be picked only while both proof and disproof lists are empty',
|
|
280
|
+
], { stroke: C.data, fs: 12.5, sfs: 10.5 })
|
|
281
|
+
arrow(COL1_X + COL1_W, bVeri + 85, COL2_X, bVeri + 85, { color: C.data, marker: 'bothData' })
|
|
282
|
+
plain((COL1_X + COL1_W + COL2_X) / 2, bVeri + 79, 'log', { size: 10, fill: C.data, anchor: 'middle', halo: true })
|
|
283
|
+
arrow(COL1_X + COL1_W, bVeri + 151, COL2_X, bVeri + 151, { color: C.data, marker: 'bothData' })
|
|
284
|
+
plain((COL1_X + COL1_W + COL2_X) / 2, bVeri + 145, 'pick r', { size: 10, fill: C.data, anchor: 'middle', halo: true })
|
|
285
|
+
|
|
286
|
+
// ============================================================
|
|
287
|
+
// 6. Lean formal verification — the four adjustable parameters
|
|
288
|
+
// ============================================================
|
|
289
|
+
const LEAN_H = 56 + 84 + 16 + 84 + 14
|
|
290
|
+
const bLean = nextBand(LEAN_H)
|
|
291
|
+
band(FULL_L, bLean, FULL_R - FULL_L, LEAN_H,
|
|
292
|
+
'Lean formal verification — four adjustable parameters (formalVerify · leanCommand · leanArgs · leanTimeoutMs)',
|
|
293
|
+
'the level is dynamic: prompt text is recomputed at the moment the prompt is built, so switching formalVerify takes effect on the very next prompt',
|
|
294
|
+
{ fill: C.gateBg, stroke: C.gate, titleFill: C.gate })
|
|
295
|
+
const leanCards = [
|
|
296
|
+
[
|
|
297
|
+
'formalVerify = off | encourage | require (default off)',
|
|
298
|
+
'off: a true no-op — no Lean text in any prompt, the verification flow is unchanged',
|
|
299
|
+
'the three tools stay registered, so a proactive call still works',
|
|
300
|
+
'encourage: formalize when the implementation difficulty allows — no gate',
|
|
301
|
+
'require: a true/false verdict needs Lean passed or an explicit blocked record',
|
|
302
|
+
],
|
|
303
|
+
[
|
|
304
|
+
'require — the gate sits at the only closing point',
|
|
305
|
+
'status passed (the last run has exitCode 0) or blocked (a non-empty note)',
|
|
306
|
+
'otherwise the verdict does not take effect: undecided, reason formal-required',
|
|
307
|
+
'no Verified/ card · boolean estimate / solved / priority stay unchanged',
|
|
308
|
+
'the object stays in its library and can be re-proposed after formalizing',
|
|
309
|
+
],
|
|
310
|
+
[
|
|
311
|
+
'leanCommand = lean · leanArgs = [] · leanTimeoutMs = 120000',
|
|
312
|
+
'leanArgs are inserted before the .lean file name (lake + ["env","lean"])',
|
|
313
|
+
'an illegal formalVerify falls back to off, never to a stronger level',
|
|
314
|
+
'a non-positive leanTimeoutMs is dropped and falls back to the default',
|
|
315
|
+
'a timeout actively terminates the run (LEAN_TIMEOUT) and never throws',
|
|
316
|
+
],
|
|
317
|
+
[
|
|
318
|
+
'a fidelity defect is not "the proposition is false"',
|
|
319
|
+
'the verifier must abstain (strictly between 0 and 1) and report',
|
|
320
|
+
'formal:{decision:"defect", note:"<specific deviation>"}',
|
|
321
|
+
'the framework withdraws the archived proof and downgrades it to attempted',
|
|
322
|
+
'under require the verdict is undecided; vote 0 only with independent reasons',
|
|
323
|
+
],
|
|
324
|
+
[
|
|
325
|
+
'paths: Formal/<object id>.lean · Verified/Lean/<object id>.lean',
|
|
326
|
+
'Formal/Index.md (object → status → file → run) · Formal/TODO.md',
|
|
327
|
+
'cross-project: Formal/Lib/<name>.lean · Formal/Proved/<name>.lean',
|
|
328
|
+
'tools: vibe_math_lean_run · vibe_math_lean_archive · vibe_math_lean_lib',
|
|
329
|
+
'full tool names only — an abbreviation is not a registered tool',
|
|
330
|
+
],
|
|
331
|
+
[
|
|
332
|
+
'records: status none|attempted|passed|blocked + run {ok, exitCode, ms}',
|
|
333
|
+
'persisted in VibeMath_State/formal.json → resume does not forget the gate',
|
|
334
|
+
'two id spaces stay in sync: r-pGate / r-q1-s0 and the object id pGate',
|
|
335
|
+
'a receipt may carry the defect from a verifier who never ran Lean',
|
|
336
|
+
'the framework does not bundle Lean; a missing toolchain is a blocked note',
|
|
337
|
+
],
|
|
338
|
+
]
|
|
339
|
+
leanCards.forEach((lines, i) => {
|
|
340
|
+
const col = i % 3, row = (i - col) / 3
|
|
341
|
+
card(GRID_X[col], bLean + 56 + row * 100, GRID_W, 84, lines, { stroke: C.gate, fs: 12.5, sfs: 10.5 })
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
// ============================================================
|
|
345
|
+
// 7. Closing · write-back · data layer
|
|
346
|
+
// ============================================================
|
|
347
|
+
const bClose = nextBand(56 + 124 + 14)
|
|
348
|
+
band(FULL_L, bClose, FULL_R - FULL_L, 56 + 124 + 14,
|
|
349
|
+
'Closing · write-back · data layer — probability = 1 closes the object',
|
|
350
|
+
'problem solved = true · proposition boolean estimate 1/0 · priority = never · the scheduler is still the only writer; a sub-agent never touches a file',
|
|
351
|
+
{ fill: C.dataBg, stroke: C.data, titleFill: '#125a3c' })
|
|
352
|
+
card(COL1_X, bClose + 56, COL1_W, 124, [
|
|
353
|
+
'probability = 1 → close out',
|
|
354
|
+
'problem: solved = true · the complete solution is stored · priority = never',
|
|
355
|
+
'proposition: boolean estimate = 1 or 0 · the proof / disproof is recorded · priority = never',
|
|
356
|
+
'exactly 1 appends the proof at probability 1; exactly 0 appends the disproof at 1',
|
|
357
|
+
'nothing strictly between 0 and 1 enters Verified/ — it stays in its library',
|
|
358
|
+
'the loop stops when every problem in qs/qs.json is solved (never does not block it)',
|
|
359
|
+
], { stroke: C.data, fs: 12.5, sfs: 10.5 })
|
|
360
|
+
card(COL2_X, bClose + 56, COL2_W, 58, [
|
|
361
|
+
'Verified/<category>_Verified.json — concluded facts',
|
|
362
|
+
'boolean estimate exactly 1 (true) or exactly 0 (false) · read-only for everyone',
|
|
363
|
+
'citations must name the file path + the object id — unsourced references are rejected',
|
|
364
|
+
], { stroke: C.gate, fs: 12.5, sfs: 10.5 })
|
|
365
|
+
card(COL2_X, bClose + 122, COL2_W, 58, [
|
|
366
|
+
'Progress_Logs/report.json · Reliable/',
|
|
367
|
+
'event-driven by default (reportIntervalMs = 0) · reportMode file | push | both',
|
|
368
|
+
'Reliable/ is placed by the user and read-only; agents reason on Propos/ + Reliable/',
|
|
369
|
+
], { stroke: C.data, fs: 12.5, sfs: 10.5 })
|
|
370
|
+
arrow(COL1_X + COL1_W, bClose + 85, COL2_X, bClose + 85, { color: C.gate, marker: 'bothGate' })
|
|
371
|
+
plain((COL1_X + COL1_W + COL2_X) / 2, bClose + 79, 'gate', { size: 10, fill: C.gate, anchor: 'middle', halo: true })
|
|
372
|
+
arrow(COL1_X + COL1_W, bClose + 151, COL2_X, bClose + 151, { color: C.data, marker: 'bothData' })
|
|
373
|
+
plain((COL1_X + COL1_W + COL2_X) / 2, bClose + 145, 'report', { size: 10, fill: C.data, anchor: 'middle', halo: true })
|
|
374
|
+
|
|
375
|
+
// ============================================================
|
|
376
|
+
// 8. File layout (mono trees)
|
|
377
|
+
// ============================================================
|
|
378
|
+
// Aligns a tree's path column with the description column (padding spaces become
|
|
379
|
+
// NBSP later, see block(): SVG collapses whitespace by default).
|
|
380
|
+
const tree = (rows) => {
|
|
381
|
+
const col = Math.max(...rows.map(([p]) => p.length)) + 2
|
|
382
|
+
return rows.map(([p, d]) => p + ' '.repeat(Math.max(1, col - p.length)) + d)
|
|
383
|
+
}
|
|
384
|
+
const TREE_LINES_L = tree([
|
|
385
|
+
['VibeMath/Projects/<project>/', ''],
|
|
386
|
+
['├─ qs/qs.json', 'problems: overview / solved / solutions / priority / progress'],
|
|
387
|
+
['├─ Propos/<category>_Propos.json', 'propositions: boolean estimate / proof·disproof lists'],
|
|
388
|
+
['├─ Reliable/', 'trusted references placed by the user (read-only)'],
|
|
389
|
+
['├─ Verified/<category>_Verified.json', 'concluded facts: boolean estimate 0 or 1'],
|
|
390
|
+
['├─ Verified/Lean/<object id>.lean', 'archived Lean proofs'],
|
|
391
|
+
['├─ Verification_logs/<rId>_<time>.json', 'one debate transcript per verification run'],
|
|
392
|
+
['├─ Progress_Logs/report.json', 'event-driven progress report'],
|
|
393
|
+
['├─ Formal/<object id>.lean + Index.md + TODO.md', 'per-object formalization'],
|
|
394
|
+
['├─ VibeMath_State/', 'scheduler-private state (checkpoint / resume)'],
|
|
395
|
+
['└─ vibe_math_setting.json', 'project parameters (global fallback supported)'],
|
|
396
|
+
])
|
|
397
|
+
const TREE_LINES_R = tree([
|
|
398
|
+
['VibeMath/ (the framework root)', ''],
|
|
399
|
+
['├─ current.<session id>.json', 'the current project, per session'],
|
|
400
|
+
['├─ vibe_math_setting.json', 'optional global fallback parameters'],
|
|
401
|
+
['└─ Formal/', 'cross-project reusable Lean library'],
|
|
402
|
+
[' ├─ Lib/<name>.lean + Lib/Index.md', 'reusable definitions / objects'],
|
|
403
|
+
[' └─ Proved/<name>.lean + Proved/Index.md', 'machine-checked lemmas'],
|
|
404
|
+
])
|
|
405
|
+
const TREE_H = Math.max(
|
|
406
|
+
(TREE_LINES_L.length - 1) * 15.5 + 11.5 + 24,
|
|
407
|
+
(TREE_LINES_R.length - 1) * 15.5 + 11.5 + 24,
|
|
408
|
+
)
|
|
409
|
+
const bTree = nextBand(56 + TREE_H + 14)
|
|
410
|
+
band(FULL_L, bTree, FULL_R - FULL_L, 56 + TREE_H + 14,
|
|
411
|
+
'File layout — every path below is relative to VibeMath/Projects/<project>/',
|
|
412
|
+
'the project skeleton is created up front (qs, Propos, Reliable, Verified, Verification_logs, Progress_Logs, VibeMath_State, Formal) · hand-editing a file cannot corrupt the scheduler, which rewrites it',
|
|
413
|
+
{ fill: C.dataBg, stroke: C.data, titleFill: '#125a3c' })
|
|
414
|
+
block(IN_L, bTree + 56, 794, TREE_LINES_L, { fs: 11.5 })
|
|
415
|
+
block(876, bTree + 56, 782, TREE_LINES_R, { fs: 11.5 })
|
|
416
|
+
|
|
417
|
+
// ============================================================
|
|
418
|
+
// 9. Adjustable parameters
|
|
419
|
+
// ============================================================
|
|
420
|
+
const bParam = nextBand(56 + 84 + 16 + 84 + 14)
|
|
421
|
+
band(FULL_L, bParam, FULL_R - FULL_L, 56 + 84 + 16 + 84 + 14,
|
|
422
|
+
'Adjustable parameters — vibe_math_set_params · /vibe setup · vibe_math_setting.json',
|
|
423
|
+
'the project-level vibe_math_setting.json wins over the global fallback (<workspace>/VibeMath/vibe_math_setting.json), then built-in defaults · a runtime change takes effect immediately and is persisted',
|
|
424
|
+
{ fill: C.houseBg, stroke: C.house, titleFill: '#8a5200' })
|
|
425
|
+
const paramCards = [
|
|
426
|
+
[
|
|
427
|
+
'scheduling & concurrency',
|
|
428
|
+
'maxParallelThreshold 4 · tickIntervalMs 2000 · activityLogCap 100',
|
|
429
|
+
'solverMaxRounds 3 · directionsPerSolver 1 · maxExplorerRetries 3',
|
|
430
|
+
'a new dispatch waits until active sub-agents < maxParallelThreshold',
|
|
431
|
+
'priority: a smaller integer first · never = never scheduled',
|
|
432
|
+
],
|
|
433
|
+
[
|
|
434
|
+
'verification & adjudication',
|
|
435
|
+
'verifierCount 3 · debateMaxRounds 5 · verdictMode flat | forced (weighted)',
|
|
436
|
+
'promoteValueThreshold 0.7 — a valuable undecided proposition joins qs.json',
|
|
437
|
+
'priorityAdjust none | deadend-deprioritize | survival-map',
|
|
438
|
+
'proposPriorityAdjust none | progress-graded',
|
|
439
|
+
],
|
|
440
|
+
[
|
|
441
|
+
'reporting · model · projects',
|
|
442
|
+
'reportMode file | push | both · reportIntervalMs 0 (event-driven only)',
|
|
443
|
+
'mode auto | manual · provider / model (empty = inherit the main agent)',
|
|
444
|
+
'solverPersona · verifierPersona · explorerPersona · knowledgeContext',
|
|
445
|
+
'vibe_math_new_project / set_project / list_projects · list_decisions',
|
|
446
|
+
],
|
|
447
|
+
[
|
|
448
|
+
'Lean parameters (semantics in the Lean band above)',
|
|
449
|
+
'formalVerify off | encourage | require (default off)',
|
|
450
|
+
'leanCommand lean · leanArgs [] · leanTimeoutMs 120000',
|
|
451
|
+
'all four appear in the schema, in status / report and in the card',
|
|
452
|
+
'an illegal formalVerify falls back to off, never to a stronger level',
|
|
453
|
+
],
|
|
454
|
+
[
|
|
455
|
+
'sub-agent permissions',
|
|
456
|
+
'solverToolAllow / solverToolDeny · verifierToolAllow / verifierToolDeny',
|
|
457
|
+
'solverAllowNetwork · verifierAllowNetwork · solver/verifierAllowScripts',
|
|
458
|
+
'solverMaxToolCalls / verifierMaxToolCalls (0 = unlimited)',
|
|
459
|
+
'all agents may read Verified/ as a trusted dependency, plus Reliable/',
|
|
460
|
+
],
|
|
461
|
+
[
|
|
462
|
+
'configuration surface',
|
|
463
|
+
'vibe_math_setup returns the schema (/vibe setup asks item by item)',
|
|
464
|
+
'vibe_math_set_params / set_mode write back immediately and persist',
|
|
465
|
+
'vibe_math_template writes a commented template · save_settings saves',
|
|
466
|
+
'vibe_math_save_settings makes the current values the new defaults',
|
|
467
|
+
],
|
|
468
|
+
]
|
|
469
|
+
paramCards.forEach((lines, i) => {
|
|
470
|
+
const col = i % 3, row = (i - col) / 3
|
|
471
|
+
card(GRID_X[col], bParam + 56 + row * 100, GRID_W, 84, lines, { stroke: C.house, fs: 12.5, sfs: 10.5 })
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
// ============================================================
|
|
475
|
+
// 10. Two hard requirements + isolation
|
|
476
|
+
// ============================================================
|
|
477
|
+
const bHard = nextBand(56 + 76 + 16 + 76 + 14)
|
|
478
|
+
band(FULL_L, bHard, FULL_R - FULL_L, 56 + 76 + 16 + 76 + 14,
|
|
479
|
+
'Two hard requirements — checkpoint resume · mid-run manual intervention',
|
|
480
|
+
'every architecture shares the same four foundations: checkpoint resume, mid-run intervention, progress reporting and per-session isolation',
|
|
481
|
+
{ fill: C.humanBg, stroke: C.human, titleFill: C.human })
|
|
482
|
+
const hardCards = [
|
|
483
|
+
[
|
|
484
|
+
'checkpoint resume (hard requirement 1)',
|
|
485
|
+
'VibeMath_State/*.json is written throughout and every sub-agent is a continuable',
|
|
486
|
+
'DSH session; vibe_math_resume restores tasks, agents, decisions, verifier accuracy;',
|
|
487
|
+
'a process epoch separates same-process pause → resume from a cross-process restart',
|
|
488
|
+
],
|
|
489
|
+
[
|
|
490
|
+
'manual / auto intervention (hard requirement 2)',
|
|
491
|
+
'vibe_math_set_mode auto | manual — switching back to auto clears pending decisions;',
|
|
492
|
+
'in manual mode dispatch and verdict suspend a decision for list_decisions,',
|
|
493
|
+
'then decide (approve | reject | override); message / interrupt any sub-agent',
|
|
494
|
+
],
|
|
495
|
+
[
|
|
496
|
+
'progress reporting',
|
|
497
|
+
'event-driven: a report is written when an agent status changes (reportIntervalMs = 0)',
|
|
498
|
+
'reportMode file (Progress_Logs/report.json) | push (wake the main agent) | both',
|
|
499
|
+
'vibe_math_status / vibe_math_report answer "what is going on" at any moment',
|
|
500
|
+
],
|
|
501
|
+
[
|
|
502
|
+
'multi-session isolation & projects',
|
|
503
|
+
'one preset is a standing mount: the plugin isolates all state by root session id',
|
|
504
|
+
'each problem is a project folder (VibeMath/Projects/<project>/) — switch at any time',
|
|
505
|
+
'current.<session id>.json keeps the current project per session (legacy current.json)',
|
|
506
|
+
],
|
|
507
|
+
]
|
|
508
|
+
hardCards.forEach((lines, i) => {
|
|
509
|
+
const card_w = 788
|
|
510
|
+
const x = IN_L + (i % 2) * (card_w + 20)
|
|
511
|
+
const row = (i - (i % 2)) / 2
|
|
512
|
+
card(x, bHard + 56 + row * 92, card_w, 76, lines, { stroke: C.human, fs: 12.5, sfs: 10.5 })
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
// ============================================================
|
|
516
|
+
// 11. Feature strip + legend
|
|
517
|
+
// ============================================================
|
|
518
|
+
const feats = [
|
|
519
|
+
'probability = 1 auto-closes (solved / 1|0)',
|
|
520
|
+
'never = never scheduled · never blocks the stop',
|
|
521
|
+
'checkpoint resume (vibe_math_resume · process epoch)',
|
|
522
|
+
'reportMode file|push|both · per-project isolation',
|
|
523
|
+
]
|
|
524
|
+
const fTop = y
|
|
525
|
+
feats.forEach((f, i) => {
|
|
526
|
+
const fw = 385
|
|
527
|
+
card(IN_L + i * (fw + 18), fTop, fw, 44, [f], { stroke: C.line, fs: 11.5, fill: '#f4f7f9' })
|
|
528
|
+
})
|
|
529
|
+
legend(fTop + 44 + 34, [
|
|
530
|
+
[C.human, 'user / main agent (neither solves nor schedules)'],
|
|
531
|
+
[C.house, 'scheduler & parameters (plugin code · writer)'],
|
|
532
|
+
[C.frame, 'sub-agents and the pipeline flow (①–④)'],
|
|
533
|
+
[C.data, 'data and artifacts (probability-driven JSON)'],
|
|
534
|
+
[C.gate, 'truth gate & Lean formalization (adjustable)'],
|
|
535
|
+
])
|
|
536
|
+
y = fTop + 44 + 34 + 14 + 22
|
|
537
|
+
const H = Math.round(y)
|
|
538
|
+
|
|
539
|
+
// ---------- flow arrows between bands ----------
|
|
540
|
+
const gapLabel = (yy, text, color) => plain(FLOW_X + 22, yy, text, { size: 10.5, fill: color, halo: true, limit: 1000, where: 'flow label' })
|
|
541
|
+
arrow(FLOW_X, bInter + 56 + 104, FLOW_X, bSched, { color: C.frame, marker: 'bothFrame', width: 2 })
|
|
542
|
+
gapLabel(bSched - 4, 'vibe_math_* tools ↓ status / report ↑', C.frame)
|
|
543
|
+
arrow(FLOW_X, bSched + 56 + 124, FLOW_X, bSolve, { color: C.frame, marker: 'bothFrame', width: 2 })
|
|
544
|
+
gapLabel(bSolve - 4, '① take the highest-priority unsolved problem → dispatch the explorer', C.frame)
|
|
545
|
+
arrow(FLOW_X, bSolve + 56 + 124, FLOW_X, bVeri, { color: C.frame, marker: 'bothFrame', width: 2 })
|
|
546
|
+
gapLabel(bVeri - 4, '② results are written with probability < 1 · ③ pick r by priority · ④ dispatch ≥ 3 verifiers', C.frame)
|
|
547
|
+
arrow(FLOW_X, bVeri + 56 + 124, FLOW_X, bLean, { color: C.frame, marker: 'bothFrame', width: 2 })
|
|
548
|
+
gapLabel(bLean - 4, '⑤ the debate ends → verdict 0 / 1 / in between', C.frame)
|
|
549
|
+
arrow(FLOW_X, bLean + LEAN_H, FLOW_X, bClose, { color: C.gate, marker: 'bothGate', width: 2 })
|
|
550
|
+
gapLabel(bClose - 4, '⑥ require: the gate guards this closing point (Verified/ card · settleVerdict)', C.gate)
|
|
551
|
+
|
|
552
|
+
// ---------- write-back loop (outer channel, never crosses a band) ----------
|
|
553
|
+
polyline([[COL1_X, bClose + 88], [LOOP_X, bClose + 88], [LOOP_X, bSched + 88], [COL1_X, bSched + 88]],
|
|
554
|
+
{ color: C.house, width: 2, marker: 'bothHouse' })
|
|
555
|
+
plain((LOOP_X + COL1_X) / 2, bClose + 82, 'write-back', { size: 10, fill: C.house, anchor: 'middle', halo: true })
|
|
556
|
+
plain((LOOP_X + COL1_X) / 2, bClose + 108, 'solved / 1 / 0', { size: 10, fill: C.house, anchor: 'middle', halo: true })
|
|
557
|
+
plain((LOOP_X + COL1_X) / 2, bSched + 64, 'read qs/qs.json', { size: 10, fill: C.house, anchor: 'middle', halo: true })
|
|
558
|
+
plain((LOOP_X + COL1_X) / 2, bSched + 90, 'next round', { size: 10, fill: C.house, anchor: 'middle', halo: true })
|
|
559
|
+
|
|
560
|
+
// ---------- assemble ----------
|
|
561
|
+
const head = [
|
|
562
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
563
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" role="img" aria-label="Vibe Math V2 architecture diagram (English)">`,
|
|
564
|
+
`<defs>
|
|
565
|
+
<marker id="both" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="${C.line}"/></marker>
|
|
566
|
+
<marker id="bothHuman" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="${C.human}"/></marker>
|
|
567
|
+
<marker id="bothFrame" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="${C.frame}"/></marker>
|
|
568
|
+
<marker id="bothData" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="${C.data}"/></marker>
|
|
569
|
+
<marker id="bothHouse" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="${C.house}"/></marker>
|
|
570
|
+
<marker id="bothGate" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="${C.gate}"/></marker>
|
|
571
|
+
</defs>`,
|
|
572
|
+
`<rect width="${W}" height="${H}" fill="#ffffff"/>`,
|
|
573
|
+
`<g font-family="${FONT}">`,
|
|
574
|
+
]
|
|
575
|
+
const titleBlock = [
|
|
576
|
+
`<text x="${W / 2}" y="40" font-size="27" font-weight="700" fill="${C.ink}" text-anchor="middle">`
|
|
577
|
+
+ `${esc('Vibe Math V2 — multi-agent math problem solving & verification framework (probability-driven)')}</text>`,
|
|
578
|
+
`<text x="${W / 2}" y="67" font-size="13" fill="${C.mute}" text-anchor="middle">`
|
|
579
|
+
+ `${esc('qs/qs.json problem list + Propos/ proposition library · scheduled by correctness probability / value · explorer → one solver per direction → ≥3 verifiers debate and adjudicate')}</text>`,
|
|
580
|
+
]
|
|
581
|
+
const svg = head.concat(titleBlock, body, ['</g>', '</svg>']).join('\n')
|
|
582
|
+
|
|
583
|
+
// English-only assertion: this edition must not contain any CJK character.
|
|
584
|
+
const cjk = svg.match(/[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]+/g)
|
|
585
|
+
if (cjk) warnings.push(`non-English (CJK) text found: ${[...new Set(cjk)].slice(0, 8).join(' / ')}`)
|
|
586
|
+
|
|
587
|
+
mkdirSync(dirname(OUT), { recursive: true })
|
|
588
|
+
writeFileSync(OUT, svg, 'utf8')
|
|
589
|
+
console.log('saved ' + OUT + ' (' + Buffer.byteLength(svg, 'utf8') + ' bytes, ' + W + 'x' + H + ')')
|
|
590
|
+
if (warnings.length) {
|
|
591
|
+
console.error('\nlayout warnings (text may overflow its container):')
|
|
592
|
+
for (const w of warnings) console.error(' WARN ' + w)
|
|
593
|
+
process.exit(1)
|
|
594
|
+
}
|
|
595
|
+
console.log('layout: no overflow warnings')
|