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.
@@ -3,6 +3,12 @@
3
3
  //
4
4
  // node docs/generate_framework_diagram_v5.mjs
5
5
  // → 写出 示例图/框架图-v5.svg
6
+ // node docs/generate_framework_diagram_v5.mjs --lang=en (也接受 --lang en)
7
+ // → 写出 示例图/框架图-v5-en.svg(同一张图,全部文字换成英文)
8
+ //
9
+ // 图上的**每一句人话**都放在本文件顶部的 zh / en 两张表里,绘图代码只从当前语言的表里取字:
10
+ // 因此两种语言共用同一套几何、配色、坐标、元素顺序与图元,不存在"两套版式各自漂移"的问题。
11
+ // 无参数时取到的就是中文原文,输出与加语言开关之前**逐字节相同**。
6
12
  //
7
13
  // v2/v3 用 matplotlib 脚本生成 PNG;v4/v5 用 Node 直接生成 **SVG**:
8
14
  // · 本仓库的运行时就包含 Node,不需要额外装 Python / matplotlib;
@@ -19,14 +25,316 @@
19
25
  // · 每个 band 的标题在 y+27、副标题在 y+47,**内容从 y+56 开始**;
20
26
  // · 左侧 x=40..214 是一条「控制面通道」,只有所办↔框架的连线走这里,
21
27
  // 研究所/框架两个 band 从 x=226 开始 —— 这样控制面的箭头不会穿过所内成员;
22
- // · 生成时会估算文字宽度,超出容器的行会打印 WARN,便于立刻发现溢出。
28
+ // · 生成时会估算文字宽度,超出容器的行会打印 WARN,便于立刻发现溢出(两种语言同样生效)。
23
29
  // ============================================================
24
30
  import { mkdirSync, writeFileSync } from 'node:fs'
25
31
  import { dirname, join } from 'node:path'
26
32
  import { fileURLToPath } from 'node:url'
27
33
 
28
34
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
29
- const OUT = join(ROOT, '示例图', '框架图-v5.svg')
35
+
36
+ // ---------- 语言 ----------
37
+ // `--lang=en` / `--lang en` → 英文;缺省或 `--lang=zh` / `--lang zh` → 中文(保持原样)。
38
+ function readLang(argv) {
39
+ for (let i = 0; i < argv.length; i++) {
40
+ const a = argv[i]
41
+ if (a === '--lang') { if (argv[i + 1] !== undefined) return argv[i + 1] }
42
+ else if (a.startsWith('--lang=')) return a.slice('--lang='.length)
43
+ }
44
+ return 'zh'
45
+ }
46
+ const LANG = readLang(process.argv.slice(2)).toLowerCase()
47
+ if (LANG !== 'zh' && LANG !== 'en') {
48
+ console.error(`未知语言 "${LANG}"(只支持 zh / en)—— 用法: node docs/generate_framework_diagram_v5.mjs [--lang=zh|en]`)
49
+ process.exit(2)
50
+ }
51
+
52
+ // ============================================================
53
+ // 文案表:zh 是图上原有的全部中文(一字不改),en 是同一句话的英文。
54
+ // 排版相关的容器假设(每张卡几行、每行多长)在这里必须与 zh 对齐或更短。
55
+ // 工具名(vibe_v5_*)、参数名、路径(Verified/、Shared/…)、JSON 键、标识符、Lean / DSH / Cordis 一律原样保留。
56
+ // ============================================================
57
+ const STRINGS = {
58
+ zh: {
59
+ doc: {
60
+ ariaLabel: 'Vibe Math V5 研究所架构图',
61
+ warnHeading: '\n布局告警(文字可能溢出容器):',
62
+ warnPrefix: ' WARN ',
63
+ saved: 'saved ',
64
+ noWarn: 'layout: no overflow warnings',
65
+ overflowHint: '文字可能溢出容器',
66
+ cardTooShort: (n, need, h) => `${n} 行需要高 ${need} > 容器 ${h}`,
67
+ },
68
+ title: 'Vibe Math V5 —— 研究所体系',
69
+ subtitle: '框架只是媒介:中继消息 · 沉淀产物 · 计数表决 · 管理上下文。它绝不指派任务 —— 组织与分派是所内院士的职责',
70
+
71
+ gate: {
72
+ bandTitle: '求真门槛 —— 唯一让对象进入 Verified/ 的规则',
73
+ bandSub: 'm = min(quorumCap, 在册有表决权人数) · 票是 [0,1] 概率:恰好 1 = 断言为真,恰好 0 = 断言为假,严格介于两者 = 弃权/存疑(不计入 m,计入全组平均)',
74
+ cards: [
75
+ ['布尔票 ≥ m 且全部为 1 或全部为 0 → 定论,写入 Verified/'],
76
+ ['同时出现 1 与 0 → 阻塞:少数派无法靠别人弃权把结论推过去'],
77
+ ['未达门槛 → 留在原库 + 全组平均概率 + 完整辩论录(不强行裁决)'],
78
+ ],
79
+ },
80
+
81
+ office: {
82
+ bandTitle: '所办(会话根代理 / 人)—— 研究所的对外接口',
83
+ bandSub: '不参与研究 · 不投票 · 只汇报与转达人的指令;代持平台要求的一次性创建权',
84
+ cards: [
85
+ ['自然语言接口 + 汇报者', '「求解 XX」「进度如何」「暂停」'],
86
+ ['vibe_v5_configure → start', 'message / meeting / hire / fire'],
87
+ ['pause / resume / stop', 'set 调参(立即生效)'],
88
+ ['/v5 斜杠命令(与工具等价)', 'configure|start|resume|pause|status|report|members|message|meeting|hire|fire|set'],
89
+ ],
90
+ },
91
+
92
+ institute: {
93
+ bandTitle: '研究所(所内自治)—— 编制、组织与分派都在成员之间完成,框架永不指派',
94
+ bandSub: '院士 acad:领头人 / 组织与协调中心 · 常驻研究员 r-n:有表决权 · 临时工 t-n:无表决权(由雇主雇入与解雇)',
95
+ acadCard: [
96
+ '院士 acad',
97
+ '领头人 · 组织与协调中心',
98
+ 'L1 建立全所视图(overview)',
99
+ 'L2 拆解并分派任务(assign)',
100
+ 'L3 设定优先级(prioritize)',
101
+ 'L4 召集并主持会议(convene)',
102
+ 'L5 督导进度(nudge)',
103
+ 'L6 调配临时工 · L7 对外代表',
104
+ ],
105
+ acadNote: ['四条边界:一票与他人等重 / 分派的是工作不是结论 /', '成员可据理反对 / 不能自我扩张编制'],
106
+ researcherLabel: '常驻研究员(有表决权 · 可自主雇佣/解雇自己的临时工)',
107
+ researcherCard: ['常驻研究员 r-', 'continuable 持久会话 · 自主方向 · 主动汇报'],
108
+ tempLabel: '临时工(无表决权 · 为特定任务临时雇入 · 雇主或院士可解雇)',
109
+ tempCard: ['临时工 t-', '由 r-', ' 雇入 · 可读/可想/可发言/可写自己的库'],
110
+ houseNote: '所内组织动作(分派 · 优先级 · 督办 · 会议 · 用人)由院士发起,不是框架行为 —— 框架只负责送达',
111
+ },
112
+
113
+ framework: {
114
+ bandTitle: '框架 vibe-v5 —— 只是媒介(middleware):中继 · 沉淀 · 计数 · 调度',
115
+ bandSub: '每轮发「状态块 + 本轮问句」(规章在 persona 里,不进对话);成员回一个 JSON:say / progress / record / verdict / task_* / hire / fire / reject_assign / input / vote_solved',
116
+ chips: [
117
+ ['消息中继(持久邮箱)', '群聊 / 私信 / 致全体表决者', '所办通知 / 分派 / 督办 / 框架提示', '逐收件人落盘 · 先落盘再投递 · 群聊合批'],
118
+ ['会议 / 辩论', '议程 · 实时名册对账 · 随机发言序', '与验证互斥:任一方进行中,另一方排队', '看门狗:卡死即放弃并回到自组织'],
119
+ ['任务板(CAS + DAG)', '开 / 认领 / 释放 / 完成 / 重开 / 分派', '版本号比较交换 · 依赖环检测', '写范围重叠告警 · 解雇自动收回'],
120
+ ['m 票共识验证', '发起 → 独立初评 → 公开辩论重投', '布尔一致 + ≥ m 才定论', 'Verified/ 卡 + 辩论录 + 全组平均概率'],
121
+ ['上下文与活性', '阈值压缩(人设不丢)· 免轮询活动等待', '优先级调度 · 心跳永远重武装', '并发闸 maxParallel · 停滞自动开会'],
122
+ ['编制与雇佣', '院士/研究员可雇自己的临时工', '雇主或院士可真实解雇(释放子会话)', '代号永不复用 · 按人/全所双配额'],
123
+ ],
124
+ leanCard: [
125
+ 'Lean 形式化验证(可调参数 formalVerify = off / encourage / require)',
126
+ 'encourage:验证时按实现难度决定是否形式化;require:真/假结论必须先有「Lean 通过」或显式阻塞记录',
127
+ '★ 一旦 Lean 通过,审查对象就变了:不再是「推导对不对」,而是「Lean 的定义/对象/条件/假设/结论是否忠实于命题原文」',
128
+ '★ 忠实性缺陷(decision=defect)≠ 命题为假:撤回「已通过」+ 撤回归档证明 + 进「形式化待办」,绝不记成 0/假',
129
+ '归档:证明 → Verified/Lean/<对象>.lean|可复用定义 → VibeMath/Formal/Lib/|已证引理 → VibeMath/Formal/Proved/',
130
+ ],
131
+ schedulerCard: [
132
+ '调度器优先级:进行中的会议或验证(二者互斥,永不同时)→ 队列中的验证 → 暂存会议 → 已认领/被分派的在办任务(按 activityTimeoutMs 节流)→ 加急邮件 → 群聊摘要 → 停滞自动开会 → 兜底心跳',
133
+ ],
134
+ },
135
+
136
+ projection: {
137
+ bandTitle: '状态权威源:会话日志的 host-only 投影单元(键 vibeMathV5)',
138
+ bandSub: '副作用只是往会话日志追加事件 —— 不进模型上下文(零 token 成本),checkpoint / restore 交给 DSH',
139
+ eventsCard: [
140
+ 'applyV5Event(纯折叠,只此一份)',
141
+ '11 类事件:institute / member / task /',
142
+ 'message / delivered / meeting / debate /',
143
+ 'verdict / queue / counters / progress',
144
+ '未知或损坏事件 → 跳过并记入 diagnostics:',
145
+ '可用性优先,绝不因一条坏事件卡死全场',
146
+ ],
147
+ checkpointCard: [
148
+ 'checkpoint / restore / resume',
149
+ '投影随会话日志一起 checkpoint;restore',
150
+ '时从快照 + 日志尾部重新折叠 → 跨进程与',
151
+ '同进程恢复走同一条代码路径',
152
+ '回退:宿主无 sessionProjections 时改用',
153
+ '加固 JSON:State/<institute>.v5state.json',
154
+ ],
155
+ },
156
+
157
+ files: {
158
+ bandTitle: '文件面:人可读产物(投影之外的一切都只是镜像)',
159
+ bandSub: '共识的权威在投影;文件是工作区与可读产物,手工改坏不会破坏研究所',
160
+ card: [
161
+ 'Members/<id>/Progress|Propos|Methods|Subproblems/',
162
+ '只有本人可写,人人可读(跨读被鼓励)',
163
+ 'Shared/Chat/*.md · Shared/Meetings/<mt-id>.md · Shared/Debates/<obj>.md',
164
+ 'Shared/TaskBoard.md · Institutes.md(编制镜像)· State/README.md(说明此处非权威)',
165
+ 'Problems/<id>.md(原问题)· Problems/conclusion.md(结题)· Verified/<类型>/<id>.md(定论,只读)',
166
+ ],
167
+ },
168
+
169
+ control: {
170
+ note1: '所办不投票', note2: '不能让结论变真', note3: '也不替成员', note4: '思考或署名',
171
+ },
172
+
173
+ links: {
174
+ promptLabel: '↑ 每轮提示词(状态块 + 本轮问句)  ↓ 单个 JSON 回执',
175
+ filesLabel: '读写产物',
176
+ chLabel: '工具面',
177
+ chCommand: 'vibe_v5_*',
178
+ chSlash: '/v5 命令',
179
+ chDown: '↓',
180
+ reportLabel: '汇报',
181
+ chUp: '↑',
182
+ },
183
+
184
+ legend: {
185
+ heading: '图例',
186
+ items: [
187
+ '所办 / 人(外部接口,不研究不投票)',
188
+ '所内成员与所内组织(虚线 = 不由框架执行)',
189
+ '框架(中继 / 沉淀 / 计数 / 调度)',
190
+ '状态与产物',
191
+ '求真门槛 / Lean 形式化(可调)',
192
+ ],
193
+ },
194
+ },
195
+
196
+ en: {
197
+ doc: {
198
+ ariaLabel: 'Vibe Math V5 institute architecture diagram',
199
+ warnHeading: '\nLayout warnings (text may overflow its container):',
200
+ warnPrefix: ' WARN ',
201
+ saved: 'saved ',
202
+ noWarn: 'layout: no overflow warnings',
203
+ overflowHint: 'text may overflow its container',
204
+ cardTooShort: (n, need, h) => `${n} lines need height ${need} > container ${h}`,
205
+ },
206
+ title: 'Vibe Math V5 —— Institute System',
207
+ subtitle: 'The framework is only a medium: relaying messages · persisting artifacts · counting votes · managing context. It never assigns tasks —— organizing and assigning is the academician\'s job',
208
+
209
+ gate: {
210
+ bandTitle: 'Truth gate —— the only rule that lets an object enter Verified/',
211
+ bandSub: 'm = min(quorumCap, number of registered voting members) · a vote is a [0,1] probability: exactly 1 = assert true, exactly 0 = assert false, strictly between = abstention/doubt (not counted toward m, counted in the group average)',
212
+ cards: [
213
+ ['Boolean votes ≥ m and all 1 or all 0 → conclusion, written to Verified/'],
214
+ ['Both 1 and 0 → blocked: the minority cannot push a conclusion through'],
215
+ ['Below quorum → stays in the library + average probability + debate record'],
216
+ ],
217
+ },
218
+
219
+ office: {
220
+ bandTitle: 'Institute office (session root agent / human) —— the institute\'s external interface',
221
+ bandSub: 'Does not research · does not vote · only reports and relays the human\'s instructions; holds the one-off creation rights the platform requires',
222
+ cards: [
223
+ ['Natural-language interface + reporter', '"solve XX" "how is it going" "pause"'],
224
+ ['vibe_v5_configure → start', 'message / meeting / hire / fire'],
225
+ ['pause / resume / stop', 'set parameters (effective immediately)'],
226
+ ['/v5 slash commands (equivalent to the tools)', 'configure|start|resume|pause|status|report|members|message|meeting|hire|fire|set'],
227
+ ],
228
+ },
229
+
230
+ institute: {
231
+ bandTitle: 'Institute (internal autonomy) —— roster, organization and assignment all happen among members; the framework never assigns',
232
+ bandSub: 'Academician acad: leader / center of organization and coordination · resident researcher r-n: voting rights · temp worker t-n: no voting rights (hired and fired by its employer)',
233
+ acadCard: [
234
+ 'Academician acad',
235
+ 'Leader · organization and coordination center',
236
+ 'L1 institute-wide overview (overview)',
237
+ 'L2 decompose and assign tasks (assign)',
238
+ 'L3 set priorities (prioritize)',
239
+ 'L4 convene and chair meetings (convene)',
240
+ 'L5 supervise progress (nudge)',
241
+ 'L6 move temp workers · L7 external',
242
+ ],
243
+ acadNote: ['Four boundaries: one vote, equally weighted /', 'what is assigned is work, not conclusions /'],
244
+ researcherLabel: 'Resident researcher (voting rights · may autonomously hire/fire its own temp workers)',
245
+ researcherCard: ['Resident researcher r-', 'continuable persistent session · own direction'],
246
+ tempLabel: 'Temp worker (no voting rights · hired temporarily for a specific task · fired by its employer or the academician)',
247
+ tempCard: ['Temp worker t-', 'Hired by r-', ' · read/think/speak, write own library'],
248
+ houseNote: 'In-institute organization (assignment · priority · nudge · meetings · staffing) is initiated by the academician, not the framework —— it only delivers',
249
+ },
250
+
251
+ framework: {
252
+ bandTitle: 'Framework vibe-v5 —— only a medium (middleware): relay · persistence · counting · scheduling',
253
+ bandSub: 'Each round: "state block + this round\'s question" (the charter is in the persona); a member returns one JSON: say / progress / record / verdict / task_* / hire / fire / reject_assign / input / vote_solved',
254
+ chips: [
255
+ ['Message relay (persistent mailbox)', 'group chat / direct message / to all voters', 'office notice / assignment / nudge / framework hint', 'persisted per recipient · written before delivery · group chat batched'],
256
+ ['Meeting / debate', 'agenda · live roster check · random speaking order', 'mutually exclusive with verification: while one runs, the other queues', 'watchdog: if it hangs, abandon it and return to self-organization'],
257
+ ['Task board (CAS + DAG)', 'open / claim / release / complete / reopen / assign', 'compare-and-set revision · dependency cycle detection', 'write-scope overlap warning · firing reclaims tasks'],
258
+ ['m-vote consensus verification', 'propose → independent initial assessment → open debate and re-vote', 'Boolean unanimity + ≥ m decides it', 'Verified/ card + debate record + group average probability'],
259
+ ['Context and liveness', 'threshold compaction (persona kept) · poll-free activity wait', 'priority scheduling · heartbeat always re-armed', 'concurrency gate maxParallel · automatic meeting on stall'],
260
+ ['Roster and hiring', 'academician/researchers can hire their own temp workers', 'employer or academician can really fire (releases the child session)', 'codenames never reused · dual quota per member / institute-wide'],
261
+ ],
262
+ leanCard: [
263
+ 'Lean formal verification (adjustable parameter formalVerify = off / encourage / require)',
264
+ 'encourage: at verification time, implementation difficulty decides whether to formalize; require: a true/false conclusion needs "Lean passed" or an explicit blocking record first',
265
+ '★ Once Lean passes, the object of review changes: no longer "is the derivation correct", but "are Lean\'s definitions/objects/conditions/assumptions/conclusions faithful to the proposition"',
266
+ '★ A fidelity defect (decision=defect) ≠ the proposition is false: revoke "passed" + revoke the archived proof + enter the "formalization TODO", never recorded as 0/false',
267
+ 'Archive: proof → Verified/Lean/<object>.lean | reusable definition → VibeMath/Formal/Lib/ | proved lemma → VibeMath/Formal/Proved/',
268
+ ],
269
+ schedulerCard: [
270
+ 'Scheduler priority: meeting/verification (mutually exclusive) → queued verification → meetings → active tasks (throttled by activityTimeoutMs) → mail → chat digest → meeting on stall → heartbeat',
271
+ ],
272
+ },
273
+
274
+ projection: {
275
+ bandTitle: 'Source of truth: host-only projection cell of the session log (key vibeMathV5)',
276
+ bandSub: 'The only side effect is appending events to the session log, outside the model context (zero token cost)',
277
+ eventsCard: [
278
+ 'applyV5Event (pure fold, the only copy)',
279
+ '11 kinds of events: institute / member / task /',
280
+ 'message / delivered / meeting / debate /',
281
+ 'verdict / queue / counters / progress',
282
+ 'unknown/bad event → skipped and recorded in diagnostics:',
283
+ 'availability first, one bad event never stalls the field',
284
+ ],
285
+ checkpointCard: [
286
+ 'checkpoint / restore / resume',
287
+ 'the projection is checkpointed with the session log; restore',
288
+ 're-folds snapshot + log tail → across and within processes',
289
+ 'one process recovery takes the same code path',
290
+ 'fallback: host without sessionProjections → hardened',
291
+ 'JSON: State/<institute>.v5state.json',
292
+ ],
293
+ },
294
+
295
+ files: {
296
+ bandTitle: 'File plane: human-readable artifacts (everything outside the projection is only a mirror)',
297
+ bandSub: 'The authority for consensus is the projection; files are readable artifacts, breaking them by hand does not damage it',
298
+ card: [
299
+ 'Members/<id>/Progress|Propos|Methods|Subproblems/',
300
+ 'only the owner can write, everyone can read (cross-reading is encouraged)',
301
+ 'Shared/Chat/*.md · Shared/Meetings/<mt-id>.md · Shared/Debates/<obj>.md',
302
+ 'Shared/TaskBoard.md · Institutes.md (roster mirror) · State/README.md (says the authority is not here)',
303
+ 'Problems/<id>.md (original problem) · Problems/conclusion.md (conclusion) · Verified/<type>/<id>.md (conclusion, read-only)',
304
+ ],
305
+ },
306
+
307
+ control: {
308
+ note1: 'the office does not vote', note2: 'cannot make a conclusion true', note3: 'nor think or sign', note4: 'on a member\'s behalf',
309
+ },
310
+
311
+ links: {
312
+ promptLabel: '↑ per-round prompt (state block + this round\'s question)  ↓ single JSON receipt',
313
+ filesLabel: 'read/write artifacts',
314
+ chLabel: 'Tool surface',
315
+ chCommand: 'vibe_v5_*',
316
+ chSlash: '/v5 commands',
317
+ chDown: '↓',
318
+ reportLabel: 'Report',
319
+ chUp: '↑',
320
+ },
321
+
322
+ legend: {
323
+ heading: 'Legend',
324
+ items: [
325
+ 'Institute office / human (no research, no vote)',
326
+ 'Members and org (dashed = not framework)',
327
+ 'Framework (relay / persist / count / sched.)',
328
+ 'State and artifacts',
329
+ 'Truth gate / Lean formalization (adjustable)',
330
+ ],
331
+ },
332
+ },
333
+ }
334
+
335
+ const T = STRINGS[LANG] // 绘图代码只会读这张表
336
+
337
+ const OUT = join(ROOT, '示例图', LANG === 'en' ? '框架图-v5-en.svg' : '框架图-v5.svg')
30
338
 
31
339
  const W = 1720, H = 1204
32
340
  const FONT = "'Microsoft YaHei','PingFang SC','Hiragino Sans GB','Noto Sans CJK SC','Source Han Sans SC',sans-serif"
@@ -53,7 +361,7 @@ const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replac
53
361
  const textWidth = (s, fs) => [...String(s)].reduce((n, ch) => n + (/[\u2e80-\u9fff\uff00-\uffef]/.test(ch) ? 1 : 0.56), 0) * fs
54
362
  const fits = (s, fs, limit, where) => {
55
363
  const w = textWidth(s, fs)
56
- if (w > limit) warnings.push(`${where}: 文字宽 ${w.toFixed(0)} > 可用 ${limit.toFixed(0)} — ${String(s).slice(0, 42)}…`)
364
+ if (w > limit) warnings.push(`${where}: ${T.doc.overflowHint} ${w.toFixed(0)} > ${limit.toFixed(0)} — ${String(s).slice(0, 42)}…`)
57
365
  return w
58
366
  }
59
367
 
@@ -76,7 +384,7 @@ function card(x, y, w, h, lines, { fill = '#ffffff', stroke = C.frame, fs = 13,
76
384
  const cx = x + w / 2
77
385
  const lead = fs + 4
78
386
  const total = (lines.length - 1) * lead
79
- if (total + fs > h) warnings.push(`card@${x},${y}: ${lines.length} 行需要高 ${(total + fs).toFixed(0)} > 容器 ${h}`)
387
+ if (total + fs > h) warnings.push(`card@${x},${y}: ${T.doc.cardTooShort(lines.length, (total + fs).toFixed(0), h)}`)
80
388
  let ty = y + h / 2 - total / 2 + fs * 0.36
81
389
  lines.forEach((ln, i) => {
82
390
  const size = i === 0 ? fs : sfs
@@ -109,7 +417,7 @@ function polyline(points, { color = C.human, width = 2, dashed = false, marker =
109
417
 
110
418
  // ---------- document ----------
111
419
  push('<?xml version="1.0" encoding="UTF-8"?>')
112
- push(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" role="img" aria-label="Vibe Math V5 研究所架构图">`)
420
+ push(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" role="img" aria-label="${esc(T.doc.ariaLabel)}">`)
113
421
  push(`<defs>
114
422
  <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>
115
423
  <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>
@@ -121,139 +429,89 @@ push(`<rect width="${W}" height="${H}" fill="#ffffff"/>`)
121
429
  push(`<g font-family="${FONT}">`)
122
430
 
123
431
  // ---- 标题 ----
124
- plain(W / 2, 40, 'Vibe Math V5 —— 研究所体系', { size: 27, fill: C.ink, anchor: 'middle', weight: 700 })
125
- plain(W / 2, 67, '框架只是媒介:中继消息 · 沉淀产物 · 计数表决 · 管理上下文。它绝不指派任务 —— 组织与分派是所内院士的职责',
432
+ plain(W / 2, 40, T.title, { size: 27, fill: C.ink, anchor: 'middle', weight: 700 })
433
+ plain(W / 2, 67, T.subtitle,
126
434
  { size: 13, fill: C.mute, anchor: 'middle', limit: W - 80, where: 'subtitle' })
127
435
 
128
436
  // ---- 求真门槛 ----
129
- band(FULL_L, 84, FULL_R - FULL_L, 94, '求真门槛 —— 唯一让对象进入 Verified/ 的规则',
130
- 'm = min(quorumCap, 在册有表决权人数) · 票是 [0,1] 概率:恰好 1 = 断言为真,恰好 0 = 断言为假,严格介于两者 = 弃权/存疑(不计入 m,计入全组平均)',
437
+ band(FULL_L, 84, FULL_R - FULL_L, 94, T.gate.bandTitle, T.gate.bandSub,
131
438
  { fill: C.gateBg, stroke: C.gate, titleFill: C.gate })
132
- card(62, 140, 506, 28, ['布尔票 ≥ m 且全部为 1 或全部为 0 → 定论,写入 Verified/'], { stroke: C.gate, fs: 12 })
133
- card(588, 140, 506, 28, ['同时出现 1 与 0 → 阻塞:少数派无法靠别人弃权把结论推过去'], { stroke: C.gate, fs: 12 })
134
- card(1114, 140, 524, 28, ['未达门槛 → 留在原库 + 全组平均概率 + 完整辩论录(不强行裁决)'], { stroke: C.gate, fs: 12 })
439
+ card(62, 140, 506, 28, T.gate.cards[0], { stroke: C.gate, fs: 12 })
440
+ card(588, 140, 506, 28, T.gate.cards[1], { stroke: C.gate, fs: 12 })
441
+ card(1114, 140, 524, 28, T.gate.cards[2], { stroke: C.gate, fs: 12 })
135
442
 
136
443
  // ---- 所办 ----
137
- band(FULL_L, 190, FULL_R - FULL_L, 100, '所办(会话根代理 / 人)—— 研究所的对外接口',
138
- '不参与研究 · 不投票 · 只汇报与转达人的指令;代持平台要求的一次性创建权',
444
+ band(FULL_L, 190, FULL_R - FULL_L, 100, T.office.bandTitle, T.office.bandSub,
139
445
  { fill: C.humanBg, stroke: C.human, titleFill: C.human })
140
- card(62, 246, 372, 38, ['自然语言接口 + 汇报者', '「求解 XX」「进度如何」「暂停」'], { stroke: C.human, fs: 12, sfs: 10.5 })
141
- card(452, 246, 372, 38, ['vibe_v5_configure → start', 'message / meeting / hire / fire'], { stroke: C.human, fs: 12, sfs: 10.5 })
142
- card(842, 246, 330, 38, ['pause / resume / stop', 'set 调参(立即生效)'], { stroke: C.human, fs: 12, sfs: 10.5 })
143
- card(1190, 246, 448, 38, ['/v5 斜杠命令(与工具等价)', 'configure|start|resume|pause|status|report|members|message|meeting|hire|fire|set'],
446
+ card(62, 246, 372, 38, T.office.cards[0], { stroke: C.human, fs: 12, sfs: 10.5 })
447
+ card(452, 246, 372, 38, T.office.cards[1], { stroke: C.human, fs: 12, sfs: 10.5 })
448
+ card(842, 246, 330, 38, T.office.cards[2], { stroke: C.human, fs: 12, sfs: 10.5 })
449
+ card(1190, 246, 448, 38, T.office.cards[3],
144
450
  { stroke: C.human, fs: 12, sfs: 9 })
145
451
 
146
452
  // ---- 研究所 ----
147
- band(MAIN_L, 302, MAIN_R - MAIN_L, 240, '研究所(所内自治)—— 编制、组织与分派都在成员之间完成,框架永不指派',
148
- '院士 acad:领头人 / 组织与协调中心 · 常驻研究员 r-n:有表决权 · 临时工 t-n:无表决权(由雇主雇入与解雇)',
453
+ band(MAIN_L, 302, MAIN_R - MAIN_L, 240, T.institute.bandTitle, T.institute.bandSub,
149
454
  { fill: C.houseBg, stroke: C.house, titleFill: '#8a5200' })
150
- card(IN_L, 362, 300, 142, [
151
- '院士 acad',
152
- '领头人 · 组织与协调中心',
153
- 'L1 建立全所视图(overview)',
154
- 'L2 拆解并分派任务(assign)',
155
- 'L3 设定优先级(prioritize)',
156
- 'L4 召集并主持会议(convene)',
157
- 'L5 督导进度(nudge)',
158
- 'L6 调配临时工 · L7 对外代表',
159
- ], { stroke: C.house, fs: 13.5, sfs: 10.5 })
160
- plain(IN_L + 150, 520, '四条边界:一票与他人等重 / 分派的是工作不是结论 /', { size: 10, fill: C.gate, anchor: 'middle', limit: 300, where: 'acad note' })
161
- plain(IN_L + 150, 536, '成员可据理反对 / 不能自我扩张编制', { size: 10, fill: C.gate, anchor: 'middle', limit: 300, where: 'acad note' })
455
+ card(IN_L, 362, 300, 142, T.institute.acadCard, { stroke: C.house, fs: 13.5, sfs: 10.5 })
456
+ plain(IN_L + 150, 520, T.institute.acadNote[0], { size: 10, fill: C.gate, anchor: 'middle', limit: 300, where: 'acad note' })
457
+ plain(IN_L + 150, 536, T.institute.acadNote[1], { size: 10, fill: C.gate, anchor: 'middle', limit: 300, where: 'acad note' })
162
458
 
163
459
  const RX = IN_L + 328, RW = 1390 - 328 - 44
164
460
  const rcw = (RW - 2 * 20) / 3
165
- plain(RX, 372, '常驻研究员(有表决权 · 可自主雇佣/解雇自己的临时工)', { size: 11.5, fill: '#8a5200', weight: 700, limit: RW, where: 'researcher label' })
461
+ plain(RX, 372, T.institute.researcherLabel, { size: 11.5, fill: '#8a5200', weight: 700, limit: RW, where: 'researcher label' })
166
462
  for (let i = 0; i < 3; i++) {
167
- card(RX + i * (rcw + 20), 380, rcw, 52, [`常驻研究员 r-${i + 1}`, 'continuable 持久会话 · 自主方向 · 主动汇报'],
463
+ card(RX + i * (rcw + 20), 380, rcw, 52, [T.institute.researcherCard[0] + (i + 1), T.institute.researcherCard[1]],
168
464
  { stroke: C.house, fs: 13, sfs: 10.5 })
169
465
  }
170
- plain(RX, 452, '临时工(无表决权 · 为特定任务临时雇入 · 雇主或院士可解雇)', { size: 11.5, fill: '#8a5200', weight: 700, limit: RW, where: 'temp label' })
466
+ plain(RX, 452, T.institute.tempLabel, { size: 11.5, fill: '#8a5200', weight: 700, limit: RW, where: 'temp label' })
171
467
  for (let i = 0; i < 3; i++) {
172
- card(RX + i * (rcw + 20), 460, rcw, 52, [`临时工 t-${i + 1}`, `由 r-${i + 1} 雇入 · 可读/可想/可发言/可写自己的库`],
468
+ card(RX + i * (rcw + 20), 460, rcw, 52, [T.institute.tempCard[0] + (i + 1), T.institute.tempCard[1] + (i + 1) + T.institute.tempCard[2]],
173
469
  { stroke: '#c99a4a', fs: 13, sfs: 10.5 })
174
470
  }
175
- plain(RX, 536, '所内组织动作(分派 · 优先级 · 督办 · 会议 · 用人)由院士发起,不是框架行为 —— 框架只负责送达',
471
+ plain(RX, 536, T.institute.houseNote,
176
472
  { size: 10.5, fill: C.mute, limit: RW, where: 'house note' })
177
473
 
178
474
  // ---- 框架 ----
179
- band(MAIN_L, 566, MAIN_R - MAIN_L, 382, '框架 vibe-v5 —— 只是媒介(middleware):中继 · 沉淀 · 计数 · 调度',
180
- '每轮发「状态块 + 本轮问句」(规章在 persona 里,不进对话);成员回一个 JSON:say / progress / record / verdict / task_* / hire / fire / reject_assign / input / vote_solved',
475
+ band(MAIN_L, 566, MAIN_R - MAIN_L, 382, T.framework.bandTitle, T.framework.bandSub,
181
476
  { fill: C.frameBg, stroke: C.frame, titleFill: '#084d80' })
182
- const chips = [
183
- ['消息中继(持久邮箱)', '群聊 / 私信 / 致全体表决者', '所办通知 / 分派 / 督办 / 框架提示', '逐收件人落盘 · 先落盘再投递 · 群聊合批'],
184
- ['会议 / 辩论', '议程 · 实时名册对账 · 随机发言序', '与验证互斥:任一方进行中,另一方排队', '看门狗:卡死即放弃并回到自组织'],
185
- ['任务板(CAS + DAG)', '开 / 认领 / 释放 / 完成 / 重开 / 分派', '版本号比较交换 · 依赖环检测', '写范围重叠告警 · 解雇自动收回'],
186
- ['m 票共识验证', '发起 → 独立初评 → 公开辩论重投', '布尔一致 + ≥ m 才定论', 'Verified/ 卡 + 辩论录 + 全组平均概率'],
187
- ['上下文与活性', '阈值压缩(人设不丢)· 免轮询活动等待', '优先级调度 · 心跳永远重武装', '并发闸 maxParallel · 停滞自动开会'],
188
- ['编制与雇佣', '院士/研究员可雇自己的临时工', '雇主或院士可真实解雇(释放子会话)', '代号永不复用 · 按人/全所双配额'],
189
- ]
477
+ const chips = T.framework.chips
190
478
  const ccw = (IN_R - IN_L - 2 * 22) / 3
191
479
  chips.forEach((c, i) => {
192
480
  card(IN_L + (i % 3) * (ccw + 22), i < 3 ? 620 : 708, ccw, 78, c, { stroke: C.frame, fs: 12.5, sfs: 10.5 })
193
481
  })
194
- card(IN_L, 796, IN_R - IN_L, 90, [
195
- 'Lean 形式化验证(可调参数 formalVerify = off / encourage / require)',
196
- 'encourage:验证时按实现难度决定是否形式化;require:真/假结论必须先有「Lean 通过」或显式阻塞记录',
197
- '★ 一旦 Lean 通过,审查对象就变了:不再是「推导对不对」,而是「Lean 的定义/对象/条件/假设/结论是否忠实于命题原文」',
198
- '★ 忠实性缺陷(decision=defect)≠ 命题为假:撤回「已通过」+ 撤回归档证明 + 进「形式化待办」,绝不记成 0/假',
199
- '归档:证明 → Verified/Lean/<对象>.lean|可复用定义 → VibeMath/Formal/Lib/|已证引理 → VibeMath/Formal/Proved/',
200
- ], { stroke: C.gate, fs: 12.5, sfs: 10.5 })
201
- card(IN_L, 880, IN_R - IN_L, 38, [
202
- '调度器优先级:进行中的会议或验证(二者互斥,永不同时)→ 队列中的验证 → 暂存会议 → 已认领/被分派的在办任务(按 activityTimeoutMs 节流)→ 加急邮件 → 群聊摘要 → 停滞自动开会 → 兜底心跳',
203
- ], { stroke: C.frame, fs: 11.5 })
482
+ card(IN_L, 796, IN_R - IN_L, 90, T.framework.leanCard, { stroke: C.gate, fs: 12.5, sfs: 10.5 })
483
+ card(IN_L, 880, IN_R - IN_L, 38, T.framework.schedulerCard, { stroke: C.frame, fs: 11.5 })
204
484
 
205
485
  // ---- 数据面 ----
206
- band(FULL_L, 964, 790, 196, '状态权威源:会话日志的 host-only 投影单元(键 vibeMathV5)',
207
- '副作用只是往会话日志追加事件 —— 不进模型上下文(零 token 成本),checkpoint / restore 交给 DSH',
486
+ band(FULL_L, 964, 790, 196, T.projection.bandTitle, T.projection.bandSub,
208
487
  { fill: C.dataBg, stroke: C.data, titleFill: '#125a3c' })
209
- card(62, 1020, 360, 124, [
210
- 'applyV5Event(纯折叠,只此一份)',
211
- '11 类事件:institute / member / task /',
212
- 'message / delivered / meeting / debate /',
213
- 'verdict / queue / counters / progress',
214
- '未知或损坏事件 → 跳过并记入 diagnostics:',
215
- '可用性优先,绝不因一条坏事件卡死全场',
216
- ], { stroke: C.data, fs: 12, sfs: 10.5 })
217
- card(434, 1020, 374, 124, [
218
- 'checkpoint / restore / resume',
219
- '投影随会话日志一起 checkpoint;restore',
220
- '时从快照 + 日志尾部重新折叠 → 跨进程与',
221
- '同进程恢复走同一条代码路径',
222
- '回退:宿主无 sessionProjections 时改用',
223
- '加固 JSON:State/<institute>.v5state.json',
224
- ], { stroke: C.data, fs: 12, sfs: 10.5 })
225
-
226
- band(852, 964, FULL_R - 852, 196, '文件面:人可读产物(投影之外的一切都只是镜像)',
227
- '共识的权威在投影;文件是工作区与可读产物,手工改坏不会破坏研究所',
488
+ card(62, 1020, 360, 124, T.projection.eventsCard, { stroke: C.data, fs: 12, sfs: 10.5 })
489
+ card(434, 1020, 374, 124, T.projection.checkpointCard, { stroke: C.data, fs: 12, sfs: 10.5 })
490
+
491
+ band(852, 964, FULL_R - 852, 196, T.files.bandTitle, T.files.bandSub,
228
492
  { fill: C.dataBg, stroke: C.data, titleFill: '#125a3c' })
229
- card(874, 1020, FULL_R - 874 - 22, 124, [
230
- 'Members/<id>/Progress|Propos|Methods|Subproblems/',
231
- '只有本人可写,人人可读(跨读被鼓励)',
232
- 'Shared/Chat/*.md · Shared/Meetings/<mt-id>.md · Shared/Debates/<obj>.md',
233
- 'Shared/TaskBoard.md · Institutes.md(编制镜像)· State/README.md(说明此处非权威)',
234
- 'Problems/<id>.md(原问题)· Problems/conclusion.md(结题)· Verified/<类型>/<id>.md(定论,只读)',
235
- ], { stroke: C.data, fs: 11.5, sfs: 10.5 })
493
+ card(874, 1020, FULL_R - 874 - 22, 124, T.files.card, { stroke: C.data, fs: 11.5, sfs: 10.5 })
236
494
 
237
495
  // ---- 连线:控制面(左侧通道,不穿过所内成员) ----
238
496
  const CHX = 127
239
497
  polyline([[CHX, 290], [CHX, 700], [MAIN_L, 700]], { color: C.human, width: 2 })
240
- plain(CHX, 336, '工具面', { fill: C.human, anchor: 'middle', size: 12, weight: 700, halo: true })
241
- plain(CHX, 356, 'vibe_v5_*', { fill: C.human, anchor: 'middle', size: 11, halo: true })
242
- plain(CHX, 374, '/v5 命令', { fill: C.human, anchor: 'middle', size: 11, halo: true })
243
- plain(CHX, 398, '↓', { fill: C.human, anchor: 'middle', size: 13, weight: 700, halo: true })
244
- plain(CHX, 470, '汇报', { fill: C.human, anchor: 'middle', size: 12, weight: 700, halo: true })
498
+ plain(CHX, 336, T.links.chLabel, { fill: C.human, anchor: 'middle', size: 12, weight: 700, halo: true })
499
+ plain(CHX, 356, T.links.chCommand, { fill: C.human, anchor: 'middle', size: 11, halo: true })
500
+ plain(CHX, 374, T.links.chSlash, { fill: C.human, anchor: 'middle', size: 11, halo: true })
501
+ plain(CHX, 398, T.links.chDown, { fill: C.human, anchor: 'middle', size: 13, weight: 700, halo: true })
502
+ plain(CHX, 470, T.links.reportLabel, { fill: C.human, anchor: 'middle', size: 12, weight: 700, halo: true })
245
503
  plain(CHX, 490, 'status', { fill: C.human, anchor: 'middle', size: 11, halo: true })
246
504
  plain(CHX, 508, 'report', { fill: C.human, anchor: 'middle', size: 11, halo: true })
247
- plain(CHX, 532, '↑', { fill: C.human, anchor: 'middle', size: 13, weight: 700, halo: true })
248
- plain(CHX, 600, '所办不投票', { fill: C.mute, anchor: 'middle', size: 10, limit: 170, where: 'ch note' })
249
- plain(CHX, 616, '不能让结论变真', { fill: C.mute, anchor: 'middle', size: 10, limit: 170, where: 'ch note' })
250
- plain(CHX, 644, '也不替成员', { fill: C.mute, anchor: 'middle', size: 10, limit: 170, where: 'ch note' })
251
- plain(CHX, 660, '思考或署名', { fill: C.mute, anchor: 'middle', size: 10, limit: 170, where: 'ch note' })
505
+ plain(CHX, 532, T.links.chUp, { fill: C.human, anchor: 'middle', size: 13, weight: 700, halo: true })
506
+ plain(CHX, 600, T.control.note1, { fill: C.mute, anchor: 'middle', size: 10, limit: 170, where: 'ch note' })
507
+ plain(CHX, 616, T.control.note2, { fill: C.mute, anchor: 'middle', size: 10, limit: 170, where: 'ch note' })
508
+ plain(CHX, 644, T.control.note3, { fill: C.mute, anchor: 'middle', size: 10, limit: 170, where: 'ch note' })
509
+ plain(CHX, 660, T.control.note4, { fill: C.mute, anchor: 'middle', size: 10, limit: 170, where: 'ch note' })
252
510
 
253
511
  // ---- 连线:研究所 ↔ 框架(提示词 / 回执) ----
254
512
  arrow(560, 542, 560, 566, { color: C.frame, marker: 'bothFrame', width: 2 })
255
513
  arrow(1400, 542, 1400, 566, { color: C.frame, marker: 'bothFrame', width: 2 })
256
- plain(980, 559, '↑ 每轮提示词(状态块 + 本轮问句)  ↓ 单个 JSON 回执',
514
+ plain(980, 559, T.links.promptLabel,
257
515
  { fill: C.frame, anchor: 'middle', size: 10.5, limit: 780, where: 'prompt label', halo: true })
258
516
 
259
517
  // ---- 连线:所内组织(虚线 = 不由框架执行) ----
@@ -264,16 +522,16 @@ arrow(556, 430, 578, 430, { color: C.house, dashed: true, width: 1.8, marker: 'b
264
522
  arrow(400, 940, 400, 964, { color: C.data, marker: 'bothData', width: 2 })
265
523
  plain(392, 956, 'append / fold / stateOf', { fill: C.data, anchor: 'end', size: 10.5, halo: true })
266
524
  arrow(1280, 940, 1280, 964, { color: C.data, marker: 'bothData', width: 2 })
267
- plain(1288, 956, '读写产物', { fill: C.data, anchor: 'start', size: 10.5, halo: true })
525
+ plain(1288, 956, T.links.filesLabel, { fill: C.data, anchor: 'start', size: 10.5, halo: true })
268
526
 
269
527
  // ---- 图例 ----
270
- plain(52, 1186, '图例', { size: 12, fill: C.ink, weight: 700 })
528
+ plain(52, 1186, T.legend.heading, { size: 12, fill: C.ink, weight: 700 })
271
529
  const legend = [
272
- [C.human, '所办 / 人(外部接口,不研究不投票)'],
273
- [C.house, '所内成员与所内组织(虚线 = 不由框架执行)'],
274
- [C.frame, '框架(中继 / 沉淀 / 计数 / 调度)'],
275
- [C.data, '状态与产物'],
276
- [C.gate, '求真门槛 / Lean 形式化(可调)'],
530
+ [C.human, T.legend.items[0]],
531
+ [C.house, T.legend.items[1]],
532
+ [C.frame, T.legend.items[2]],
533
+ [C.data, T.legend.items[3]],
534
+ [C.gate, T.legend.items[4]],
277
535
  ]
278
536
  legend.forEach(([col, txt], i) => {
279
537
  const x = 108 + i * 330
@@ -287,10 +545,10 @@ push('</svg>')
287
545
  mkdirSync(dirname(OUT), { recursive: true })
288
546
  const svg = out.join('\n')
289
547
  writeFileSync(OUT, svg, 'utf8')
290
- console.log('saved ' + OUT + ' (' + svg.length + ' bytes)')
548
+ console.log(T.doc.saved + OUT + ' (' + svg.length + ' bytes)')
291
549
  if (warnings.length) {
292
- console.error('\n布局告警(文字可能溢出容器):')
293
- for (const w of warnings) console.error(' WARN ' + w)
550
+ console.error(T.doc.warnHeading)
551
+ for (const w of warnings) console.error(T.doc.warnPrefix + w)
294
552
  process.exit(1)
295
553
  }
296
- console.log('layout: no overflow warnings')
554
+ console.log(T.doc.noWarn)