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,624 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Vibe Math V3 architecture diagram generator (English, zero dependency, pure Node)
|
|
3
|
+
//
|
|
4
|
+
// node docs/generate_framework_diagram_v3_en.mjs
|
|
5
|
+
// -> writes 示例图/框架图-v3-en.svg
|
|
6
|
+
//
|
|
7
|
+
// v2/v3 used to ship as matplotlib-generated PNGs; from v4 on this repository
|
|
8
|
+
// generates SVG directly from Node:
|
|
9
|
+
// · the runtime already contains Node, so no Python / matplotlib is needed;
|
|
10
|
+
// · SVG is plain text — diff friendly, reviewable, crisp at any zoom.
|
|
11
|
+
// This file is the English counterpart of the matplotlib v3 diagram. It copies
|
|
12
|
+
// the primitives (band/card/plain/arrow/polyline), the text-width estimator and
|
|
13
|
+
// the "warn + exit 1" layout self-check of docs/generate_framework_diagram_v5.mjs
|
|
14
|
+
// so that both diagrams stay in one visual language. It imports nothing from it.
|
|
15
|
+
//
|
|
16
|
+
// If a PNG is needed (market carousel / preview), do NOT screenshot with
|
|
17
|
+
// "window size = canvas size": --screenshot captures the WINDOW while the
|
|
18
|
+
// VIEWPORT is about 96px shorter, so the bottom of the canvas is never painted
|
|
19
|
+
// (that real defect cost v4/v5 their legend row for two releases).
|
|
20
|
+
// Render from the SVG's own size instead:
|
|
21
|
+
// node docs/render_framework_diagram_png.mjs 示例图/框架图-v3-en.svg 示例图/框架图-v3-en.png 2
|
|
22
|
+
//
|
|
23
|
+
// Layout conventions (keep them when you change the layout, or things overlap):
|
|
24
|
+
// · a band puts its title at y+27, its subtitle at y+47 and starts content at y+56;
|
|
25
|
+
// · x 62..820 is the pipeline column, x 1080..1638 is the md knowledge-base
|
|
26
|
+
// column, x 820..1080 is the arrow corridor where the numbered write-back
|
|
27
|
+
// arrows (5..9) live — their labels sit above the arrow line;
|
|
28
|
+
// · the vertical flow spine (arrows 1..4) runs at x=400 between the bands;
|
|
29
|
+
// · row heights are DERIVED from the card contents, never hand-written;
|
|
30
|
+
// · every text line is measured while generating; a line wider than its
|
|
31
|
+
// container prints WARN and the process exits with code 1.
|
|
32
|
+
//
|
|
33
|
+
// Content sources: vibe-math-v3/实现方案.md (the specification, authoritative)
|
|
34
|
+
// and README.en.md ("Vibe Math V3" section + the four-preset comparison table).
|
|
35
|
+
// Chinese characters appear only where the framework really uses them: the md
|
|
36
|
+
// anchor key names the scheduler parses, the entry-title literal, a few anchor
|
|
37
|
+
// values, and real directory / file names (Verified/命题, Verified/问题,
|
|
38
|
+
// Logs/报告.md, Logs/形式化.md).
|
|
39
|
+
// ============================================================
|
|
40
|
+
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
41
|
+
import { dirname, join } from 'node:path'
|
|
42
|
+
import { fileURLToPath } from 'node:url'
|
|
43
|
+
|
|
44
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
45
|
+
const OUT = join(ROOT, '示例图', '框架图-v3-en.svg')
|
|
46
|
+
|
|
47
|
+
const W = 1720
|
|
48
|
+
const FONT = "'Segoe UI','Microsoft YaHei','PingFang SC','Hiragino Sans GB','Noto Sans CJK SC',sans-serif"
|
|
49
|
+
const MONO = "'Cascadia Mono','Consolas','SFMono-Regular',monospace"
|
|
50
|
+
|
|
51
|
+
const C = {
|
|
52
|
+
ink: '#10202e', ink2: '#2c4557', mute: '#5d7788',
|
|
53
|
+
frame: '#0b6fb8', frameBg: '#eaf3fd',
|
|
54
|
+
house: '#b26a00', houseBg: '#fdf5e2',
|
|
55
|
+
data: '#1f7a52', dataBg: '#eef7f1',
|
|
56
|
+
gate: '#b3202c', gateBg: '#fdeef0',
|
|
57
|
+
human: '#5b4bb8', humanBg: '#f1eefc',
|
|
58
|
+
line: '#7d93a3',
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---- geometry ----
|
|
62
|
+
const FULL_L = 40, FULL_R = W - 40 // 40 .. 1680
|
|
63
|
+
const PL = 62, PR = 820 // pipeline cards
|
|
64
|
+
const AX = 400 // vertical flow spine
|
|
65
|
+
const KL = 1080, KR = 1638 // knowledge-base cards
|
|
66
|
+
const GX = (PR + KL) / 2 // 950 — corridor label centre
|
|
67
|
+
const CORRIDOR = KL - PR - 16 // label limit inside the corridor
|
|
68
|
+
const INNER = KR - PL // 1576 — full band content width
|
|
69
|
+
const C3 = (INNER - 2 * 20) / 3 // 512 — three cards in a row
|
|
70
|
+
const C2 = (INNER - 20) / 2 // 778 — two cards in a row
|
|
71
|
+
|
|
72
|
+
const warnings = []
|
|
73
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
74
|
+
// Rough text-width estimate: CJK / fullwidth 1.0em, everything else 0.56em.
|
|
75
|
+
const textWidth = (s, fs) => [...String(s)].reduce((n, ch) => n + (/[\u2e80-\u9fff\uff00-\uffef]/.test(ch) ? 1 : 0.56), 0) * fs
|
|
76
|
+
const fits = (s, fs, limit, where) => {
|
|
77
|
+
const w = textWidth(s, fs)
|
|
78
|
+
if (w > limit) warnings.push(`${where}: text width ${w.toFixed(0)} > available ${limit.toFixed(0)} — ${String(s).slice(0, 42)}…`)
|
|
79
|
+
return w
|
|
80
|
+
}
|
|
81
|
+
// height a card needs for n lines whose first line is rendered at font size fs
|
|
82
|
+
// (rounded up: every derived coordinate below must stay an integer)
|
|
83
|
+
const CH = (n, fs = 13) => Math.ceil((n - 1) * (fs + 4) + fs + 16)
|
|
84
|
+
|
|
85
|
+
const out = []
|
|
86
|
+
const push = (s) => out.push(s)
|
|
87
|
+
|
|
88
|
+
// ---------- primitives (same language as generate_framework_diagram_v5.mjs) ----------
|
|
89
|
+
function band(x, y, w, h, title, sub, { fill, stroke, titleFill = C.ink }) {
|
|
90
|
+
push(`<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="14" fill="${fill}" stroke="${stroke}" stroke-width="2"/>`)
|
|
91
|
+
fits(title, 15, w - 36, 'band title')
|
|
92
|
+
push(`<text x="${x + 18}" y="${y + 27}" font-size="15" font-weight="700" fill="${titleFill}">${esc(title)}</text>`)
|
|
93
|
+
if (sub) {
|
|
94
|
+
fits(sub, 12, w - 36, 'band sub')
|
|
95
|
+
push(`<text x="${x + 18}" y="${y + 47}" font-size="12" fill="${C.mute}">${esc(sub)}</text>`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function card(x, y, w, h, lines, { fill = '#ffffff', stroke = C.frame, fs = 13, sfs = 11, titleFill = C.ink } = {}) {
|
|
100
|
+
push(`<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="10" fill="${fill}" stroke="${stroke}" stroke-width="1.6"/>`)
|
|
101
|
+
const cx = x + w / 2
|
|
102
|
+
const lead = fs + 4
|
|
103
|
+
const total = (lines.length - 1) * lead
|
|
104
|
+
if (total + fs > h) warnings.push(`card@${x},${y}: ${lines.length} lines need height ${(total + fs).toFixed(0)} > container ${h}`)
|
|
105
|
+
let ty = y + h / 2 - total / 2 + fs * 0.36
|
|
106
|
+
lines.forEach((ln, i) => {
|
|
107
|
+
const size = i === 0 ? fs : sfs
|
|
108
|
+
fits(ln, size, w - 16, `card line@${x},${y}`)
|
|
109
|
+
push(`<text x="${cx}" y="${ty.toFixed(1)}" font-size="${size}" font-weight="${i === 0 ? 700 : 400}" `
|
|
110
|
+
+ `fill="${i === 0 ? titleFill : C.ink2}" text-anchor="middle">${esc(ln)}</text>`)
|
|
111
|
+
ty += lead
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function plain(x, y, text, { size = 12, fill = C.ink2, anchor = 'start', weight = 400, mono = false, limit = null, where = 'plain', halo = false } = {}) {
|
|
116
|
+
if (limit) fits(text, size, limit, where)
|
|
117
|
+
// halo: a white outline under the text, for labels that sit on top of a line
|
|
118
|
+
push(`<text x="${x}" y="${y}" font-size="${size}" font-weight="${weight}" fill="${fill}" text-anchor="${anchor}"`
|
|
119
|
+
+ (halo ? ' paint-order="stroke" stroke="#ffffff" stroke-width="4" stroke-linejoin="round"' : '')
|
|
120
|
+
+ (mono ? ` font-family="${MONO}"` : '') + `>${esc(text)}</text>`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function arrow(x1, y1, x2, y2, { color = C.line, dashed = false, both = true, width = 1.6, marker = 'both' } = {}) {
|
|
124
|
+
push(`<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${color}" stroke-width="${width}" `
|
|
125
|
+
+ (dashed ? 'stroke-dasharray="7 5" ' : '')
|
|
126
|
+
+ (both ? `marker-start="url(#${marker})" ` : '') + `marker-end="url(#${marker})"/>`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function polyline(points, { color = C.human, width = 2, dashed = false, marker = 'bothHuman' } = {}) {
|
|
130
|
+
const d = points.map((p, i) => (i ? 'L' : 'M') + ' ' + p[0] + ' ' + p[1]).join(' ')
|
|
131
|
+
push(`<path d="${d}" fill="none" stroke="${color}" stroke-width="${width}" `
|
|
132
|
+
+ (dashed ? 'stroke-dasharray="7 5" ' : '') + `marker-start="url(#${marker})" marker-end="url(#${marker})"/>`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ============================================================
|
|
136
|
+
// 1. CONTENT — every card, in reading order
|
|
137
|
+
// ============================================================
|
|
138
|
+
const CARD = {
|
|
139
|
+
// --- row 1: interaction layer ---
|
|
140
|
+
interact: { fs: 13, sfs: 10, stroke: C.frame, lines: [
|
|
141
|
+
'User · main agent (assistant + reporter)',
|
|
142
|
+
'natural language: translate requests / report progress / configure in Q&A / control commands',
|
|
143
|
+
'does not solve · does not schedule — it only calls vibe_math_* tools and reports back',
|
|
144
|
+
] },
|
|
145
|
+
trust: { fs: 12.5, sfs: 10, stroke: C.gate, titleFill: C.gate, lines: [
|
|
146
|
+
'Trust tiers — the iron rule of trust',
|
|
147
|
+
'1. Verified/ — absolutely trusted, citable as fact',
|
|
148
|
+
'2. Propos/ cards marked 状态: 已验证·真/假 — trusted,',
|
|
149
|
+
' but the Verified/ copy prevails',
|
|
150
|
+
'3. everything else (unconcluded propositions, Progress/,',
|
|
151
|
+
' Methods/ assertions, Notes/) — experiential reference only',
|
|
152
|
+
] },
|
|
153
|
+
|
|
154
|
+
// --- row 2: scheduler layer ---
|
|
155
|
+
sched: { fs: 13, sfs: 10, stroke: C.house, titleFill: '#8a5200', lines: [
|
|
156
|
+
'Scheduler (plugin code)',
|
|
157
|
+
'sole master control: keeps the md index,',
|
|
158
|
+
'builds the state brief, validates the',
|
|
159
|
+
'plan action by action, writes the files',
|
|
160
|
+
'hard constraints enforced by code:',
|
|
161
|
+
'concurrency · idempotency · already-verified',
|
|
162
|
+
'objects are never rescheduled again · one',
|
|
163
|
+
'writer per file (write ownership)',
|
|
164
|
+
] },
|
|
165
|
+
planner: { fs: 13, sfs: 10, stroke: C.house, titleFill: '#8a5200', lines: [
|
|
166
|
+
'Planner agent (planner)',
|
|
167
|
+
'reads the state brief: problems +',
|
|
168
|
+
'dependencies + survival rates, verifiable',
|
|
169
|
+
'objects, active agents, budget, available',
|
|
170
|
+
'methods, last plan result',
|
|
171
|
+
'arranges the next N steps in one go:',
|
|
172
|
+
'spawn / continue / interrupt / promote /',
|
|
173
|
+
'verify / method-keep / wait',
|
|
174
|
+
] },
|
|
175
|
+
problems: { fs: 12, sfs: 10, stroke: C.human, titleFill: '#3f2f8f', lines: [
|
|
176
|
+
'Problems/ — problem list · one md per problem',
|
|
177
|
+
'statement / status / priority / dependencies /',
|
|
178
|
+
'dependents / source·motivation / plan /',
|
|
179
|
+
'solution candidates',
|
|
180
|
+
'follow-up problems (q_sub) register the temp problem,',
|
|
181
|
+
'the judgement problem and the temporary assumption',
|
|
182
|
+
'p_{q-tmp} (a Propos/ card) — source and motivation',
|
|
183
|
+
'are first-class anchors here',
|
|
184
|
+
] },
|
|
185
|
+
|
|
186
|
+
// --- row 3: direction solving ---
|
|
187
|
+
solver: { fs: 13, sfs: 10, stroke: C.data, titleFill: '#125a3c', lines: [
|
|
188
|
+
'Explorer → Solver × N',
|
|
189
|
+
'splits the problem into widely divergent directions (constraint decomposition, boundary tests, analogous problems);',
|
|
190
|
+
'all dead ends → re-derive, and the old direction log is archived',
|
|
191
|
+
'one solver per direction, multiple rounds inside the same session; it checks Methods/ first, then reports',
|
|
192
|
+
'methods_used / new_inventions and writes Progress/<id>/<direction>.md',
|
|
193
|
+
] },
|
|
194
|
+
progress: { fs: 12, sfs: 10, stroke: C.human, titleFill: '#3f2f8f', lines: [
|
|
195
|
+
'Progress/ — research log · one md per problem',
|
|
196
|
+
'direction × round narrative: one independent file',
|
|
197
|
+
'per direction Progress/<id>/<direction>.md, so',
|
|
198
|
+
'self-organizing agents never conflict with each other',
|
|
199
|
+
'the aggregated index Progress/<id>.md carries the per-',
|
|
200
|
+
'direction summaries + the lemma index + the rounds',
|
|
201
|
+
'old directions are archived [archived directions]',
|
|
202
|
+
] },
|
|
203
|
+
|
|
204
|
+
// --- row 4: cross-verification ---
|
|
205
|
+
verifier: { fs: 13, sfs: 10, stroke: C.data, titleFill: '#125a3c', lines: [
|
|
206
|
+
'Verifier × ≥3 (harsh reviewers)',
|
|
207
|
+
'each object gets ≥3 independent reviewers: independent review → debate (chat group) → ruling;',
|
|
208
|
+
'during the first pass they cannot see each other',
|
|
209
|
+
'each one returns Result ∈ [0,1] plus Reason — 1 needs a complete proof, 0 needs a rigorous disproof,',
|
|
210
|
+
'anything strictly in between is a genuine abstention / doubt',
|
|
211
|
+
'near-consensus ruling: all results on the same side and mean ≥0.85 / ≤0.15 → take the mean',
|
|
212
|
+
'(0.9 vs 1 → 0.95); otherwise verdictMode = forced (historical accuracy + rigor) / flat (0.5)',
|
|
213
|
+
'this rule fixes the v2 defect of misjudging the high-confidence "0.9 vs 1" as 0.5',
|
|
214
|
+
] },
|
|
215
|
+
propos: { fs: 12, sfs: 10, stroke: C.human, titleFill: '#3f2f8f', lines: [
|
|
216
|
+
'Propos/ — proposition library',
|
|
217
|
+
'one md per proposition (a category subdirectory',
|
|
218
|
+
'is optional): statement / probability /',
|
|
219
|
+
'proof·disproof attempts / dependencies',
|
|
220
|
+
'a card marked 状态: 已验证·真/假 is trusted, but',
|
|
221
|
+
'the Verified/ copy prevails; everything else is',
|
|
222
|
+
'an estimate, not an established fact',
|
|
223
|
+
] },
|
|
224
|
+
|
|
225
|
+
// --- row 5: method distillation ---
|
|
226
|
+
keeper: { fs: 13, sfs: 10, stroke: C.data, titleFill: '#125a3c', lines: [
|
|
227
|
+
'Method Keeper (method-organizing agent)',
|
|
228
|
+
'digests recent work, Progress/Propos/Logs and the new_inventions reports (scheduled by',
|
|
229
|
+
'methodKeepIntervalMs / methodKeepEvery) and distils them',
|
|
230
|
+
'distils new method cards (状态: 经验) · merges duplicate and fragmentary entries',
|
|
231
|
+
'improves the system hierarchy (上级体系 / 子方法) · maintains the 可信断言 of each card',
|
|
232
|
+
'the library is universal: theories, frameworks, tools, methods, ideas, paradigms, techniques —',
|
|
233
|
+
'what was invented to solve one problem becomes reusable for the next ones',
|
|
234
|
+
] },
|
|
235
|
+
methods: { fs: 12, sfs: 10, stroke: C.human, titleFill: '#3f2f8f', lines: [
|
|
236
|
+
'Methods/ — general theory invention library',
|
|
237
|
+
'+ the global VibeMath/Methods/, reused across projects',
|
|
238
|
+
'theories / frameworks / tools / methods / ideas',
|
|
239
|
+
'invented while solving (group theory for an equation,',
|
|
240
|
+
'functional analysis for variational problems)',
|
|
241
|
+
'application records · improvement history · the',
|
|
242
|
+
'system hierarchy (上级体系 / 子方法)',
|
|
243
|
+
"a card's 可信断言 may only link IDs that already",
|
|
244
|
+
'entered Verified/ — everything else in the card stays',
|
|
245
|
+
'experiential reference and is never cited as a theorem',
|
|
246
|
+
'project → global promotion uses methodAutoPromote,',
|
|
247
|
+
'otherwise it waits behind a manual gate; the library',
|
|
248
|
+
'keeps growing across projects and across sessions',
|
|
249
|
+
] },
|
|
250
|
+
|
|
251
|
+
// --- row 6: closing / write-back ---
|
|
252
|
+
closing: { fs: 13, sfs: 10, stroke: C.gate, titleFill: C.gate, lines: [
|
|
253
|
+
'Closing — the iron rule of trust',
|
|
254
|
+
'probability = 1 → Verified/ card (read-only, absolutely trusted): the complete trusted statement +',
|
|
255
|
+
'every proof / disproof / solution whose probability is 1 + the verdict (true / false) + time + source',
|
|
256
|
+
'only verified content is injected into later prompts as fact; unverified objects never are',
|
|
257
|
+
'the scheduler rewrites the 状态 / 概率 anchors, generates the Verified/ copy and updates State/index.json',
|
|
258
|
+
] },
|
|
259
|
+
verified: { fs: 12, sfs: 10, stroke: C.human, titleFill: '#3f2f8f', lines: [
|
|
260
|
+
'Verified/ — absolutely trusted (read-only)',
|
|
261
|
+
'Verified/命题/<id>.md · Verified/问题/<id>.md',
|
|
262
|
+
'Logs/Verification/ debate records · Logs/Plans/',
|
|
263
|
+
'plan audits (the planner learning loop) · Logs/报告.md',
|
|
264
|
+
'State/*.json — scheduler-private state, not knowledge',
|
|
265
|
+
] },
|
|
266
|
+
|
|
267
|
+
// --- band 7: the soft-spec contract ---
|
|
268
|
+
softA: { fs: 12.5, sfs: 10, stroke: C.human, titleFill: '#3f2f8f', lines: [
|
|
269
|
+
'Anchors — the only mandatory part',
|
|
270
|
+
'4–7 lines of `- key: value` at the head of an object md',
|
|
271
|
+
'- ID/类型/状态/概率/优先级/依赖/被依赖/来源/计划/',
|
|
272
|
+
' 上级体系/子方法/相关/可信断言:',
|
|
273
|
+
'the scheduler rebuilds State/index.json from these',
|
|
274
|
+
] },
|
|
275
|
+
softB: { fs: 12.5, sfs: 10, stroke: C.human, titleFill: '#3f2f8f', lines: [
|
|
276
|
+
'Entry titles — also machine-parsed',
|
|
277
|
+
'### 解法/证明/证伪 N|标题|概率X|状态Y',
|
|
278
|
+
'one such title per solution / proof / disproof attempt,',
|
|
279
|
+
'each followed by its full narrative text',
|
|
280
|
+
'the scheduler never parses the body prose',
|
|
281
|
+
] },
|
|
282
|
+
softC: { fs: 12.5, sfs: 10, stroke: C.human, titleFill: '#3f2f8f', lines: [
|
|
283
|
+
'Everything else is free prose',
|
|
284
|
+
'paper / research-report style: statements, derivations,',
|
|
285
|
+
'counterexamples, lessons — self-organized by the agent',
|
|
286
|
+
'the 状态 / 概率 anchor lines are rewritten by the',
|
|
287
|
+
'scheduler on verdict write-back; the body is append-only',
|
|
288
|
+
'and is never overwritten (anchors are edited in place,',
|
|
289
|
+
'text-level, so agent-written prose survives)',
|
|
290
|
+
] },
|
|
291
|
+
|
|
292
|
+
// --- band 8: write ownership ---
|
|
293
|
+
lockA: { fs: 12, sfs: 10, stroke: C.frame, lines: [
|
|
294
|
+
'1 — claim the write lock',
|
|
295
|
+
'vibe_math_claim_write({target}) before writing',
|
|
296
|
+
'only one agent may write a given file at a time;',
|
|
297
|
+
'a busy lock tells you to wait and retry',
|
|
298
|
+
'the lock table is process-level, and its purpose is',
|
|
299
|
+
'to stop two agents from interleaving appends into',
|
|
300
|
+
'one md file',
|
|
301
|
+
] },
|
|
302
|
+
lockB: { fs: 12, sfs: 10, stroke: C.frame, lines: [
|
|
303
|
+
'2 — write · 3 — release',
|
|
304
|
+
"write only your own assigned file; append and",
|
|
305
|
+
"continue, never edit another agent's anchors",
|
|
306
|
+
'vibe_math_release_write({target}) when you are done',
|
|
307
|
+
'the content stays in the md file — only lightweight',
|
|
308
|
+
'metadata goes back to the scheduler',
|
|
309
|
+
] },
|
|
310
|
+
lockC: { fs: 12, sfs: 10, stroke: C.frame, lines: [
|
|
311
|
+
'4 — sync metadata (+ fallback)',
|
|
312
|
+
'vibe_math_sync_meta({meta}) reports direction status',
|
|
313
|
+
'/ survival, lemma ids + proof, method card ids, new',
|
|
314
|
+
'inventions, solutions — enough to keep the index in',
|
|
315
|
+
'sync, without moving knowledge into JSON',
|
|
316
|
+
'fallback when file tools are unavailable: reply with',
|
|
317
|
+
'JSON __writes[] + meta and the scheduler writes to disk',
|
|
318
|
+
] },
|
|
319
|
+
|
|
320
|
+
// --- band 9: planner degradation + isolation ---
|
|
321
|
+
fallback: { fs: 12.5, sfs: 10.5, stroke: C.house, titleFill: '#8a5200', lines: [
|
|
322
|
+
'Planner fallback — an enhancement, not a requirement',
|
|
323
|
+
'plannerEnabled = false → the built-in v2-style heuristic scheduling is used instead',
|
|
324
|
+
'no output / timeout / invalid JSON → fall back to the heuristic scheduler within the same tick',
|
|
325
|
+
'plannerMaxFails = 3 consecutive failures → degrade to pure heuristic mode (re-enable in settings)',
|
|
326
|
+
'planMinIntervalMs = 30000 is a cooldown between planning calls; it is ignored when the system is',
|
|
327
|
+
'idle and there is work, so a plan is produced immediately',
|
|
328
|
+
'the plan (JSON, at most planningHorizon = 3 actions) is validated action by action, then executed',
|
|
329
|
+
'actions beyond the current concurrency enter the pending queue (plans.json) and are consumed across',
|
|
330
|
+
'ticks; every plan + its per-action result goes to Logs/Plans/ (the planner learning loop)',
|
|
331
|
+
] },
|
|
332
|
+
projectlock: { fs: 12.5, sfs: 10.5, stroke: C.house, titleFill: '#8a5200', lines: [
|
|
333
|
+
'Project lock and multi-session isolation',
|
|
334
|
+
'per-session Session: root agent / current project / scheduler / registry / decision queue /',
|
|
335
|
+
'parameters / task stack are all isolated per root session; childOwner routes subagent/end',
|
|
336
|
+
'and each session has its own current.<sessionId>.json',
|
|
337
|
+
'State/project.lock (holding the session id + a timestamp): one project is scheduled by one',
|
|
338
|
+
'session at a time — md appends conflict more easily than JSON, which is why this guard exists',
|
|
339
|
+
'a second session is told "project occupied by session X", or waits up to projectLockTimeoutMs',
|
|
340
|
+
"processEpoch stays process-level, so two sessions in one process never mark each other's",
|
|
341
|
+
'subagents stale; the lock is released on pause / termination / when everything is resolved',
|
|
342
|
+
] },
|
|
343
|
+
|
|
344
|
+
// --- band 10: adjustable parameters ---
|
|
345
|
+
parA: { fs: 12.5, sfs: 10, stroke: C.gate, titleFill: C.gate, lines: [
|
|
346
|
+
'Added by v3 (default)',
|
|
347
|
+
'planningHorizon = 3 — actions per plan',
|
|
348
|
+
'plannerEnabled = true',
|
|
349
|
+
'plannerProvider / plannerModel (empty = inherit)',
|
|
350
|
+
'plannerPersona (empty)',
|
|
351
|
+
'planMinIntervalMs = 30000',
|
|
352
|
+
'plannerMaxFails = 3',
|
|
353
|
+
'methodKeepIntervalMs = 0 (event-driven)',
|
|
354
|
+
'methodKeepEvery = 5',
|
|
355
|
+
'methodAutoPromote = false (manual gate)',
|
|
356
|
+
'indexAutoRebuild = true',
|
|
357
|
+
'projectLockTimeoutMs = 60000',
|
|
358
|
+
'methodKeeperPersona (empty)',
|
|
359
|
+
'verdictMode = forced (near-consensus first)',
|
|
360
|
+
] },
|
|
361
|
+
parB: { fs: 12.5, sfs: 10, stroke: C.gate, titleFill: C.gate, lines: [
|
|
362
|
+
'Lean formal verification (4 parameters)',
|
|
363
|
+
'formalVerify = off | encourage | require',
|
|
364
|
+
' default off; an illegal value always falls back to',
|
|
365
|
+
' off, never to a stronger tier',
|
|
366
|
+
' off: no Lean wording appears anywhere (a true no-op),',
|
|
367
|
+
' yet the three Lean tools stay registered',
|
|
368
|
+
' encourage: formalize when the implementation is easy',
|
|
369
|
+
' enough; once Lean passes, the only question left is',
|
|
370
|
+
' fidelity to the proposition text',
|
|
371
|
+
' require: a true/false ruling needs formal.status ∈',
|
|
372
|
+
' {passed, blocked}; otherwise it is recorded as',
|
|
373
|
+
' undecided (formal-required) + Formal/TODO.md',
|
|
374
|
+
'leanCommand = "lean" (e.g. "lake"; blank → "lean")',
|
|
375
|
+
'leanArgs = [] (inserted before the .lean file name)',
|
|
376
|
+
'leanTimeoutMs = 120000',
|
|
377
|
+
'tools: vibe_math_lean_run · vibe_math_lean_archive',
|
|
378
|
+
'· vibe_math_lean_lib (always written in full)',
|
|
379
|
+
] },
|
|
380
|
+
parC: { fs: 12.5, sfs: 10, stroke: C.gate, titleFill: C.gate, lines: [
|
|
381
|
+
'Inherited from v2 (unchanged)',
|
|
382
|
+
'mode · maxParallelThreshold · solverMaxRounds',
|
|
383
|
+
'directionsPerSolver · verifierCount · debateMaxRounds',
|
|
384
|
+
'reportMode = file | push | both · reportIntervalMs',
|
|
385
|
+
'promoteValueThreshold · priorityAdjust',
|
|
386
|
+
'proposPriorityAdjust · knowledgeContext',
|
|
387
|
+
'provider / model · the three personas',
|
|
388
|
+
'tool allow / deny lists · network / script switches',
|
|
389
|
+
'solverMaxToolCalls / verifierMaxToolCalls',
|
|
390
|
+
'tickIntervalMs = 2000 · activityLogCap = 100',
|
|
391
|
+
'maxExplorerRetries = 3',
|
|
392
|
+
'source of truth: vibe_math_setting.json (JSONC,',
|
|
393
|
+
'project level, global fallback) + vibe_math_setup',
|
|
394
|
+
] },
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const H_CARD = (k) => CH(CARD[k].lines.length, CARD[k].fs)
|
|
398
|
+
const put = (k, x, y, w) => card(x, y, w, H_CARD(k), CARD[k].lines, CARD[k])
|
|
399
|
+
|
|
400
|
+
// ---- row layout: [x, width, card key] ----
|
|
401
|
+
const ROW_LAYOUT = [
|
|
402
|
+
[[PL, 758, 'interact'], [KL, KR - KL, 'trust']],
|
|
403
|
+
[[PL, 370, 'sched'], [502, 318, 'planner'], [KL, KR - KL, 'problems']],
|
|
404
|
+
[[PL, 758, 'solver'], [KL, KR - KL, 'progress']],
|
|
405
|
+
[[PL, 758, 'verifier'], [KL, KR - KL, 'propos']],
|
|
406
|
+
[[PL, 758, 'keeper'], [KL, KR - KL, 'methods']],
|
|
407
|
+
[[PL, 758, 'closing'], [KL, KR - KL, 'verified']],
|
|
408
|
+
]
|
|
409
|
+
const rowContent = ROW_LAYOUT.map((row) => Math.max(...row.map(([, , k]) => H_CARD(k))))
|
|
410
|
+
const RH = rowContent.map((c) => 56 + c + 16)
|
|
411
|
+
|
|
412
|
+
const BAND_KEYS = [['softA', 'softB', 'softC'], ['lockA', 'lockB', 'lockC'], ['fallback', 'projectlock'], ['parA', 'parB', 'parC']]
|
|
413
|
+
const BH = BAND_KEYS.map((keys) => 56 + Math.max(...keys.map(H_CARD)) + 16)
|
|
414
|
+
|
|
415
|
+
// ---- vertical stack ----
|
|
416
|
+
const Y0 = 88, G1 = 46, G2 = 34
|
|
417
|
+
const yRow = []
|
|
418
|
+
let cursor = Y0
|
|
419
|
+
for (let i = 0; i < 6; i++) {
|
|
420
|
+
yRow.push(cursor)
|
|
421
|
+
cursor += RH[i] + (i < 5 ? G1 : G2)
|
|
422
|
+
}
|
|
423
|
+
const ySoft = cursor; cursor += BH[0] + G2
|
|
424
|
+
const yWrite = cursor; cursor += BH[1] + G2
|
|
425
|
+
const yIso = cursor; cursor += BH[2] + G2
|
|
426
|
+
const yParam = cursor; cursor += BH[3] + G2
|
|
427
|
+
const yBar = cursor
|
|
428
|
+
const yLegend = yBar + 48 + 24
|
|
429
|
+
const H = yLegend + 24
|
|
430
|
+
|
|
431
|
+
const CT = (i) => yRow[i] + 56 // content top of pipeline row i
|
|
432
|
+
const CY = (i) => CT(i) + rowContent[i] / 2 // arrow y inside pipeline row i
|
|
433
|
+
const RB = (i) => yRow[i] + RH[i] // bottom of pipeline row i
|
|
434
|
+
|
|
435
|
+
// ============================================================
|
|
436
|
+
// 2. DOCUMENT
|
|
437
|
+
// ============================================================
|
|
438
|
+
push('<?xml version="1.0" encoding="UTF-8"?>')
|
|
439
|
+
push(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" role="img" aria-label="Vibe Math V3 architecture diagram (English)">`)
|
|
440
|
+
push(`<defs>
|
|
441
|
+
<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>
|
|
442
|
+
<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>
|
|
443
|
+
<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>
|
|
444
|
+
<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>
|
|
445
|
+
<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>
|
|
446
|
+
<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>
|
|
447
|
+
<marker id="spine" 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.ink2}"/></marker>
|
|
448
|
+
</defs>`)
|
|
449
|
+
push(`<rect width="${W}" height="${H}" fill="#ffffff"/>`)
|
|
450
|
+
push(`<g font-family="${FONT}">`)
|
|
451
|
+
|
|
452
|
+
// ---- title ----
|
|
453
|
+
plain(W / 2, 40, 'Vibe Math V3 — multi-agent mathematical research and verification framework',
|
|
454
|
+
{ size: 26, fill: C.ink, anchor: 'middle', weight: 700, limit: FULL_R - FULL_L, where: 'title' })
|
|
455
|
+
plain(W / 2, 68, 'paper-style md knowledge base (soft-spec anchors + free narration) · the planner agent lays out the next N steps by itself · invented theories / frameworks / tools / methods / ideas are distilled into a universal method library',
|
|
456
|
+
{ size: 12.5, fill: C.mute, anchor: 'middle', limit: FULL_R - FULL_L, where: 'subtitle' })
|
|
457
|
+
|
|
458
|
+
// ---- the six pipeline rows ----
|
|
459
|
+
const BANDS = [
|
|
460
|
+
{
|
|
461
|
+
y: yRow[0], h: RH[0],
|
|
462
|
+
title: 'Interaction layer — user and main agent (assistant + reporter)',
|
|
463
|
+
sub: 'natural language in · reports progress out · configures parameters in Q&A · holds the control commands for the human',
|
|
464
|
+
fill: C.frameBg, stroke: C.frame, titleFill: '#084d80',
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
y: yRow[1], h: RH[1],
|
|
468
|
+
title: 'Scheduler layer — plugin code + planner agent',
|
|
469
|
+
sub: 'the scheduler is the sole writer of the md knowledge base: anchor index, structural moves, and every hard constraint enforced by code',
|
|
470
|
+
fill: C.houseBg, stroke: C.house, titleFill: '#8a5200',
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
y: yRow[2], h: RH[2],
|
|
474
|
+
title: 'Subagent layer — direction solving (continuable persistent sessions)',
|
|
475
|
+
sub: 'Explorer splits directions and one Solver per direction iterates; every subagent writes its own md file',
|
|
476
|
+
fill: C.dataBg, stroke: C.data, titleFill: '#125a3c',
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
y: yRow[3], h: RH[3],
|
|
480
|
+
title: 'Subagent layer — cross-verification',
|
|
481
|
+
sub: 'at least three harsh reviewers per object: independent review → debate (chat group) → ruling; then the scheduler writes the anchors and the Verified/ copy back',
|
|
482
|
+
fill: C.dataBg, stroke: C.data, titleFill: '#125a3c',
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
y: yRow[4], h: RH[4],
|
|
486
|
+
title: 'Subagent layer — method distillation',
|
|
487
|
+
sub: 'the Method Keeper digests recent work and invention reports into Methods/ (project level, promotable to the global cross-project layer)',
|
|
488
|
+
fill: C.dataBg, stroke: C.data, titleFill: '#125a3c',
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
y: yRow[5], h: RH[5],
|
|
492
|
+
title: 'Closing · write-back · data layer (md knowledge base)',
|
|
493
|
+
sub: 'VibeMath/Projects/<project>/ — different types live in different paths; only Verified/ and objects judged true/false may be cited as fact',
|
|
494
|
+
fill: C.humanBg, stroke: C.human, titleFill: '#3f2f8f',
|
|
495
|
+
},
|
|
496
|
+
]
|
|
497
|
+
BANDS.forEach((b, i) => band(FULL_L, b.y, FULL_R - FULL_L, b.h, b.title, b.sub, b))
|
|
498
|
+
ROW_LAYOUT.forEach((row, i) => row.forEach(([x, w, k]) => put(k, x, CT(i), w)))
|
|
499
|
+
|
|
500
|
+
// ---- band 7: the soft-spec contract ----
|
|
501
|
+
band(FULL_L, ySoft, FULL_R - FULL_L, BH[0], 'The soft-spec contract — minimum enforcement + maximum freedom',
|
|
502
|
+
'only the head anchors and the entry title lines are machine-parsed; the rest is free paper-style prose (v3 deliberately imposes no stricter format)',
|
|
503
|
+
{ fill: C.humanBg, stroke: C.human, titleFill: '#3f2f8f' })
|
|
504
|
+
put('softA', PL, ySoft + 56, C3)
|
|
505
|
+
put('softB', PL + C3 + 20, ySoft + 56, C3)
|
|
506
|
+
put('softC', PL + 2 * (C3 + 20), ySoft + 56, C3)
|
|
507
|
+
|
|
508
|
+
// ---- band 8: write ownership ----
|
|
509
|
+
band(FULL_L, yWrite, FULL_R - FULL_L, BH[1], 'Write ownership — agents write their own Markdown, the scheduler keeps the index',
|
|
510
|
+
'one writer per file at a time; only the scheduler performs structural moves (generating Verified/ copies, promotion)',
|
|
511
|
+
{ fill: C.frameBg, stroke: C.frame, titleFill: '#084d80' })
|
|
512
|
+
put('lockA', PL, yWrite + 56, C3)
|
|
513
|
+
put('lockB', PL + C3 + 20, yWrite + 56, C3)
|
|
514
|
+
put('lockC', PL + 2 * (C3 + 20), yWrite + 56, C3)
|
|
515
|
+
|
|
516
|
+
// ---- band 9: planner degradation + isolation ----
|
|
517
|
+
band(FULL_L, yIso, FULL_R - FULL_L, BH[2], 'Planner degradation and multi-session isolation',
|
|
518
|
+
'the planner agent is an enhancement, not a requirement — md appends conflict more easily than JSON, which is why the project lock exists',
|
|
519
|
+
{ fill: C.houseBg, stroke: C.house, titleFill: '#8a5200' })
|
|
520
|
+
put('fallback', PL, yIso + 56, C2)
|
|
521
|
+
put('projectlock', PL + C2 + 20, yIso + 56, C2)
|
|
522
|
+
|
|
523
|
+
// ---- band 10: adjustable parameters ----
|
|
524
|
+
band(FULL_L, yParam, FULL_R - FULL_L, BH[3], 'Adjustable parameters — planner, method library and Lean formal verification',
|
|
525
|
+
'vibe_math_set_params applies them at runtime; the single persistent source is vibe_math_setting.json (JSONC with comments, project level falling back to global)',
|
|
526
|
+
{ fill: C.gateBg, stroke: C.gate, titleFill: C.gate })
|
|
527
|
+
put('parA', PL, yParam + 56, C3)
|
|
528
|
+
put('parB', PL + C3 + 20, yParam + 56, C3)
|
|
529
|
+
put('parC', PL + 2 * (C3 + 20), yParam + 56, C3)
|
|
530
|
+
|
|
531
|
+
// ============================================================
|
|
532
|
+
// 3. ARROWS — drawn after every band and card so the lines stay on top
|
|
533
|
+
// ============================================================
|
|
534
|
+
|
|
535
|
+
// ---- feedback loop: the method library is consulted, and it grows ----
|
|
536
|
+
const loopX = 1072, loopTop = CT(2) + 16, loopBottom = CY(4) + 30
|
|
537
|
+
polyline([[KL, loopBottom], [loopX, loopBottom], [loopX, loopTop], [PR, loopTop]],
|
|
538
|
+
{ color: C.human, width: 1.8, dashed: true })
|
|
539
|
+
plain(GX - 7, loopTop - 17, 'consult Methods/ before starting,', { size: 9.5, fill: C.human, anchor: 'middle', halo: true, limit: 230, where: 'method loop' })
|
|
540
|
+
plain(GX - 7, loopTop - 4, 'record the application after use', { size: 9.5, fill: C.human, anchor: 'middle', halo: true, limit: 230, where: 'method loop' })
|
|
541
|
+
|
|
542
|
+
// ---- spine: the numbered pipeline steps 1..4 ----
|
|
543
|
+
const spine = (i, lines) => {
|
|
544
|
+
arrow(AX, RB(i) + 4, AX, yRow[i + 1] - 4, { color: C.ink2, both: false, width: 2, marker: 'spine' })
|
|
545
|
+
const mid = RB(i) + 23
|
|
546
|
+
lines.forEach((ln, k) => {
|
|
547
|
+
const y = lines.length === 1 ? mid + 4 : mid - 6 + k * 14
|
|
548
|
+
plain(AX + 16, y, ln, { size: 10.5, fill: C.ink2, limit: PR - AX - 16, where: 'spine label', halo: true })
|
|
549
|
+
})
|
|
550
|
+
}
|
|
551
|
+
spine(0, ['vibe_math_* tools'])
|
|
552
|
+
spine(1, ['① plan validated → spawn Explorer and one Solver per direction'])
|
|
553
|
+
spine(2, ['② pick r (proposition / proposition+proof·disproof /', 'problem+solution) → dispatch ≥3 verifiers'])
|
|
554
|
+
spine(3, ['③ ruling → near-consensus close-out'])
|
|
555
|
+
spine(4, ['④ probability = 1 → Verified/ card'])
|
|
556
|
+
|
|
557
|
+
// ---- corridor: the numbered write-back arrows 5..9 ----
|
|
558
|
+
const corridorLabel = (arrowY, lines, color) => {
|
|
559
|
+
lines.forEach((ln, i) => {
|
|
560
|
+
const y = arrowY - 8 - (lines.length - 1 - i) * 13
|
|
561
|
+
plain(GX, y, ln, { size: 9.5, fill: color, anchor: 'middle', limit: CORRIDOR, where: 'corridor label', halo: true })
|
|
562
|
+
})
|
|
563
|
+
}
|
|
564
|
+
arrow(PR, CY(1), KL, CY(1), { color: C.human, both: true, width: 1.8, marker: 'bothHuman' })
|
|
565
|
+
corridorLabel(CY(1), ['⑤ register problems, follow-up problems', 'and the plan write-back'], C.human)
|
|
566
|
+
arrow(PR, CY(2), KL, CY(2), { color: C.human, both: true, width: 1.8, marker: 'bothHuman' })
|
|
567
|
+
corridorLabel(CY(2), ['⑥ write lemmas / sub-routes / solutions', '(probability < 1)'], C.human)
|
|
568
|
+
arrow(KL, CY(3), PR, CY(3), { color: C.human, both: false, width: 1.8, marker: 'bothHuman' })
|
|
569
|
+
corridorLabel(CY(3), ['⑦ propositions / proofs → verification'], C.human)
|
|
570
|
+
arrow(PR, CY(4), KL, CY(4), { color: C.human, both: true, width: 1.8, marker: 'bothHuman' })
|
|
571
|
+
corridorLabel(CY(4), ['⑧ methods_used / new_inventions', '→ methods library'], C.human)
|
|
572
|
+
arrow(PR, CY(5), KL, CY(5), { color: C.gate, both: true, width: 1.8, marker: 'bothGate' })
|
|
573
|
+
corridorLabel(CY(5), ['⑨ conclusion → Verified/ card (read-only)'], C.gate)
|
|
574
|
+
|
|
575
|
+
// ---- scheduler <-> planner: the state brief and the plan ----
|
|
576
|
+
arrow(432, CY(1), 502, CY(1), { color: C.house, both: true, width: 1.8, marker: 'bothHouse' })
|
|
577
|
+
plain(467, CY(1) - 7, 'state brief →', { size: 9, fill: C.house, anchor: 'middle', limit: 70, where: 'brief label', halo: true })
|
|
578
|
+
plain(467, CY(1) + 15, '← plan JSON', { size: 9, fill: C.house, anchor: 'middle', limit: 70, where: 'plan label', halo: true })
|
|
579
|
+
|
|
580
|
+
// ============================================================
|
|
581
|
+
// 4. BOTTOM BAR + LEGEND
|
|
582
|
+
// ============================================================
|
|
583
|
+
const barW = Math.floor((INNER - 4 * 16) / 5)
|
|
584
|
+
const bar = [
|
|
585
|
+
['Only Verified/ and objects judged', 'true/false are absolutely trusted'],
|
|
586
|
+
['Soft-spec anchors + free narration', '(the scheduler parses anchors only)'],
|
|
587
|
+
['Planning failure → automatic', 'fallback to heuristic scheduling'],
|
|
588
|
+
['Checkpoint resume (md is the', 'narrative breakpoint + process epoch)'],
|
|
589
|
+
['Per-session isolation + project lock', 'State/project.lock, projectLockTimeoutMs'],
|
|
590
|
+
]
|
|
591
|
+
bar.forEach((lines, i) => {
|
|
592
|
+
card(PL + i * (barW + 16), yBar, barW, 48, lines, { fill: '#f7f8f9', stroke: C.line, fs: 12, sfs: 9.5 })
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
plain(FULL_L + 12, yLegend, 'Legend', { size: 12, fill: C.ink, weight: 700 })
|
|
596
|
+
const legend = [
|
|
597
|
+
[C.data, 'Subagent layers (solver, verifier, keeper)'],
|
|
598
|
+
[C.house, 'Planner agent and its fallback'],
|
|
599
|
+
[C.frame, 'Framework, scheduler, write lock'],
|
|
600
|
+
[C.human, 'md knowledge base / write-back'],
|
|
601
|
+
[C.gate, 'Truth rules, parameters, Lean verification'],
|
|
602
|
+
]
|
|
603
|
+
legend.forEach(([col, txt], i) => {
|
|
604
|
+
const x = 108 + i * 300
|
|
605
|
+
push(`<rect x="${x}" y="${yLegend - 11}" width="14" height="14" rx="3" fill="#ffffff" stroke="${col}" stroke-width="2"/>`)
|
|
606
|
+
plain(x + 21, yLegend + 1, txt, { size: 11, fill: C.ink2, limit: 272, where: 'legend' })
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
push('</g>')
|
|
610
|
+
push('</svg>')
|
|
611
|
+
|
|
612
|
+
// ============================================================
|
|
613
|
+
// 5. WRITE + LAYOUT SELF-CHECK
|
|
614
|
+
// ============================================================
|
|
615
|
+
mkdirSync(dirname(OUT), { recursive: true })
|
|
616
|
+
const svg = out.join('\n')
|
|
617
|
+
writeFileSync(OUT, svg, 'utf8')
|
|
618
|
+
console.log('saved ' + OUT + ' (' + svg.length + ' bytes, canvas ' + W + 'x' + H + ')')
|
|
619
|
+
if (warnings.length) {
|
|
620
|
+
console.error('\nlayout warnings (text may overflow its container):')
|
|
621
|
+
for (const w of warnings) console.error(' WARN ' + w)
|
|
622
|
+
process.exit(1)
|
|
623
|
+
}
|
|
624
|
+
console.log('layout: no overflow warnings')
|