dsh-vibe-math 2.3.14 → 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 +876 -0
- package/README.md +11 -7
- package/docs/AUDIT-CHECKLIST.md +61 -0
- 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 +387 -127
- package/docs/generate_framework_diagram_v5.mjs +372 -109
- package/docs/release-notes/RELEASE-NOTES-2.3.15.md +137 -0
- package/docs/release-notes/RELEASE-NOTES-2.3.16.md +165 -0
- package/docs/render_framework_diagram_png.mjs +273 -0
- package/docs/test-timing.md +4 -2
- package/docs//346/236/266/346/236/204/345/233/276.md +3 -1
- package/package.json +13 -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,137 @@
|
|
|
1
|
+
# dsh-vibe-math 2.3.15 — 发布说明(中文)
|
|
2
|
+
|
|
3
|
+
> 上一版:2.3.14。本版新增**英文 README 与中英切换**,把两张架构图重新导出为**完整的高清 PNG**,
|
|
4
|
+
> 并把发布说明固定为**中英双语**。四套预设的代码、提示词与语料**字节未变**,运行时行为不变。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 概览
|
|
9
|
+
|
|
10
|
+
- **英文 README**:仓库现在同时提供中英文文档,页面顶部一行即可切换;中文版仍是默认的 `README.md`,
|
|
11
|
+
所以 npm 页面与 GitHub 首页显示的内容**没有变化**。
|
|
12
|
+
- **架构图重新导出**:`示例图/框架图-v4.png` 与 `-v5.png` 此前**底部被裁掉一条**(图例整行不见了),
|
|
13
|
+
现在重新导出为 **2× 高清、完整无裁切**的图(3440×2520 / 3440×2408)。
|
|
14
|
+
- **发布说明双语**:从现在起每一版都提供中文 + 英文两份,先中文后英文,节名固定。
|
|
15
|
+
|
|
16
|
+
## 变更
|
|
17
|
+
|
|
18
|
+
### 1. 英文 README 与中英切换
|
|
19
|
+
|
|
20
|
+
- 新增 `README.en.md`,与中文版**逐节对应**;两份 README 顶部各有切换链接
|
|
21
|
+
(`English | 中文`)。
|
|
22
|
+
- 中文版仍是 `README.md`,因此 npm 页面与 GitHub 首页的默认内容不变。
|
|
23
|
+
- 新增守卫:两份 README 的**章节结构、图片、本地链接、表格行数、内部锚点**必须一一对应,
|
|
24
|
+
且英文版不得残留未翻译正文——以后只改一边会被测试直接检出。
|
|
25
|
+
|
|
26
|
+
### 2. 架构图重新导出(底部不再被裁)
|
|
27
|
+
|
|
28
|
+
- **现象**:`框架图-v4.png` / `-v5.png` 最下面一条(图例行)不见了;而 PNG 的尺寸又恰好等于画布尺寸,
|
|
29
|
+
所以从尺寸上完全看不出来。
|
|
30
|
+
- **原因**:导出用的是 `--window-size=<画布宽>,<画布高>` + `--headless=new`,而它截的是**窗口**,
|
|
31
|
+
页面**视口**比窗口矮约 96px(浏览器 UI 占位),画布底部那一截根本没被绘制。
|
|
32
|
+
- **现在**:新增 [`docs/render_framework_diagram_png.mjs`](../../docs/render_framework_diagram_png.mjs),
|
|
33
|
+
按 SVG 自身尺寸渲染、裁掉多余高度,并**校验墨迹到达内容底边**——被裁就报错退出,不会再悄悄发布。
|
|
34
|
+
- **新增守卫**:四张架构图 PNG 必须真的装得下它自己的画面(解 PNG 与 SVG 比对墨迹范围),
|
|
35
|
+
v2/v3 的 matplotlib 图同样纳入检查。
|
|
36
|
+
|
|
37
|
+
### 3. 发布说明改为中英双语
|
|
38
|
+
|
|
39
|
+
- GitHub Release 正文固定为「中文 → English」两段,节名统一:
|
|
40
|
+
概览 / 变更 / 兼容性 / 升级 ↔ Overview / Changes / Compatibility / Upgrade。
|
|
41
|
+
- 只写使用者需要知道的信息;工程过程与证据留在仓库的审计台账与提交信息里。
|
|
42
|
+
- 发布门禁新增检查:当版发布说明必须同时包含中文与英文,缺一半直接判红。
|
|
43
|
+
|
|
44
|
+
## 兼容性
|
|
45
|
+
|
|
46
|
+
- 四套预设的代码、提示词与语料**字节未变**;参数、工具面与 `off` / `encourage` / `require` 行为不变。
|
|
47
|
+
- 安装器行为与 2.3.14 相同:版本一变即整体替换受管文件(手改原文先备份),同一版本不重写任何文件。
|
|
48
|
+
- 本版只改文档、图片与测试守卫,不涉及任何运行时行为。
|
|
49
|
+
|
|
50
|
+
## 升级
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npm i dsh-vibe-math@latest
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
若你在 profile 里把版本钉死了,先升级那个依赖再重启 DSH:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
dsh plugin --profile <你的 profile> add dsh-vibe-math@latest
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
无需迁移。
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
# dsh-vibe-math 2.3.15 — Release Notes (English)
|
|
67
|
+
|
|
68
|
+
> Previous: 2.3.14. This release adds an **English README with a language switcher**, re-exports two
|
|
69
|
+
> architecture diagrams as **complete high-resolution PNGs**, and fixes the release notes to a
|
|
70
|
+
> **bilingual** format. The four presets' code, prompts and corpora are byte-identical; runtime
|
|
71
|
+
> behaviour is unchanged.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Overview
|
|
76
|
+
|
|
77
|
+
- **English README**: the repository now documents the project in both languages, switchable from one
|
|
78
|
+
line at the top of either file. The Chinese README is still the default `README.md`, so what the npm
|
|
79
|
+
page and the GitHub landing page show is unchanged.
|
|
80
|
+
- **Diagrams re-exported**: `示例图/框架图-v4.png` and `-v5.png` were **missing their bottom strip**
|
|
81
|
+
(an entire legend row). They are now re-exported **complete at 2× resolution** (3440×2520 / 3440×2408).
|
|
82
|
+
- **Bilingual release notes**: from now on every release ships Chinese and English, Chinese first,
|
|
83
|
+
with fixed section names.
|
|
84
|
+
|
|
85
|
+
## Changes
|
|
86
|
+
|
|
87
|
+
### 1. English README and language switching
|
|
88
|
+
|
|
89
|
+
- Added `README.en.md`, section-for-section parallel to the Chinese one; each file carries a switcher
|
|
90
|
+
link at the top (`English | 中文`).
|
|
91
|
+
- The Chinese README remains `README.md`, so npm and the GitHub landing page keep their current content.
|
|
92
|
+
- New guard: the two READMEs must agree on **heading structure, images, local links, table rows and
|
|
93
|
+
internal anchors**, and the English file must not contain untranslated prose — editing only one side
|
|
94
|
+
now fails the suite.
|
|
95
|
+
|
|
96
|
+
### 2. Diagrams re-exported (nothing is cut off any more)
|
|
97
|
+
|
|
98
|
+
- **Symptom**: the bottom strip of `框架图-v4.png` / `-v5.png` (the legend row) was missing, while the
|
|
99
|
+
PNG still measured exactly the canvas size — so no size-based check could ever see it.
|
|
100
|
+
- **Cause**: the export used `--window-size=<canvas width>,<canvas height>` together with
|
|
101
|
+
`--headless=new`, which screenshots the **window** while the page **viewport** is ~96px shorter
|
|
102
|
+
(browser UI), so the bottom of the canvas was never painted.
|
|
103
|
+
- **Now**: [`docs/render_framework_diagram_png.mjs`](../../docs/render_framework_diagram_png.mjs) renders
|
|
104
|
+
at the SVG's own size, crops the extra height, and **verifies that the ink reaches the artwork's
|
|
105
|
+
bottom edge** — a short raster exits non-zero instead of shipping.
|
|
106
|
+
- **New guard**: each committed diagram PNG must actually contain its own artwork (pixel-extent
|
|
107
|
+
comparison against the SVG); the v2/v3 matplotlib diagrams are covered as well.
|
|
108
|
+
|
|
109
|
+
### 3. Bilingual release notes
|
|
110
|
+
|
|
111
|
+
- The GitHub Release body is now fixed to a Chinese section followed by an English one, with stable
|
|
112
|
+
headings: 概览 / 变更 / 兼容性 / 升级 ↔ Overview / Changes / Compatibility / Upgrade.
|
|
113
|
+
- Only what a user needs to know; engineering evidence stays in the repository's audit ledger and
|
|
114
|
+
commit messages.
|
|
115
|
+
- The release gate now fails when the current version's notes lack either half.
|
|
116
|
+
|
|
117
|
+
## Compatibility
|
|
118
|
+
|
|
119
|
+
- The four presets' code, prompts and corpora are **byte-identical**; parameters, tool surface and the
|
|
120
|
+
`off` / `encourage` / `require` behaviour are unchanged.
|
|
121
|
+
- Installer behaviour is the same as 2.3.14: a version change replaces the managed files (backing up
|
|
122
|
+
user edits first), and the same version rewrites nothing.
|
|
123
|
+
- This release changes documentation, images and test guards only — no runtime behaviour.
|
|
124
|
+
|
|
125
|
+
## Upgrade
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
npm i dsh-vibe-math@latest
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If you pinned the version in a profile, update that dependency first and restart DSH:
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
dsh plugin --profile <your profile> add dsh-vibe-math@latest
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
No migration needed.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# dsh-vibe-math 2.3.16 — 发布说明(中文)
|
|
2
|
+
|
|
3
|
+
> 上一版:2.3.15。本版把**英文文档与英文配图补齐**:英文 README 全篇英文化,并为它单独提供
|
|
4
|
+
> **四张英文版架构图**;同时修正两份 README 里一处与实现不符的 v2 数据层描述,并把 v1 时期的旧架构文档标注清楚。
|
|
5
|
+
> 四套预设的代码、提示词与语料**字节未变**,运行时行为不变。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 概览
|
|
10
|
+
|
|
11
|
+
- **英文 README 全篇英文**:示例对话、参数表、目录树占位符与链接文案都改成英文;只有**必须与实现一致**的真实字面量
|
|
12
|
+
保留中文(真实目录 / 文件名、真实 JSON 键、真实 Markdown 格式锚点),并在首次出现处加了英文括注。
|
|
13
|
+
- **四张英文版架构图**:`示例图/框架图-v2-en.svg` … `-v5-en.svg`,只被英文 README 引用;四张中文图完全未改动。
|
|
14
|
+
- **修正一处事实错误**:两份 README 此前称 v2 会把 `qs.csv` 回写为 `solved`、并生成 `q1-的解法_<标识>.csv`——
|
|
15
|
+
v2 **从不写任何 CSV**,已按实现改正。
|
|
16
|
+
- **旧架构文档标注范围**:`docs/架构图.md` 描述的是 v1 时期的布局,现在文件开头与 README 的引用处都明确标注为历史文档。
|
|
17
|
+
|
|
18
|
+
## 变更
|
|
19
|
+
|
|
20
|
+
### 1. 英文 README 全篇英文化
|
|
21
|
+
|
|
22
|
+
- 示例提示词、参数表、目录树里的占位符(`<项目>` → `<project>` 等)、链接文案全部改为英文。
|
|
23
|
+
- 保留中文的只有**真实字面量**:真实目录与文件名(`示例图/…`、`Verified/命题/`、`Logs/报告.md` 等)、
|
|
24
|
+
真实 JSON 键(`已解决`、`正确概率`)、真实 Markdown 格式锚点(`- ID/类型/状态/概率/…`、
|
|
25
|
+
`### 解法/证明/证伪 N|标题|概率X|状态Y`),以及会话重建标记 `【会话重建 —— <角色> <id>】`。
|
|
26
|
+
这些在英文版里首次出现处都加了英文括注,读者不会误以为是漏译。
|
|
27
|
+
- 守卫加强:英文版除上述字面量外不得残留中文正文;中英两版的章节层级、表格行数、代码块数、图片与本地链接
|
|
28
|
+
必须一一对应,只改一边会被直接检出。
|
|
29
|
+
|
|
30
|
+
### 2. 四张英文版架构图
|
|
31
|
+
|
|
32
|
+
- 新增 `示例图/框架图-v2-en.svg`、`-v3-en.svg`、`-v4-en.svg`、`-v5-en.svg`,英文 README 单独引用;
|
|
33
|
+
**中文版四张图未做任何改动**(中文 README 只同步了下面第 3、4 两节的修正)。
|
|
34
|
+
- 生成方式:v4/v5 用仓库里现有的零依赖生成脚本加 `--lang=en`;v2/v3 新增零依赖 Node 生成脚本
|
|
35
|
+
`docs/generate_framework_diagram_v{2,3}_en.mjs`(中文 v2/v3 海报仍由原来的 matplotlib 脚本产出,未动)。
|
|
36
|
+
- 英文图内同样只保留真实字面量的中文(例如 v3 的 md 锚点、v4/v5 的 `Verified/命题/<id>.md` 路径),其余全英文;
|
|
37
|
+
生成脚本自带「文字溢出即告警并以非零码退出」的自检,守卫会校验每张图完整、可解析且正文为英文。
|
|
38
|
+
|
|
39
|
+
### 3. 修正 v2 数据层描述(两份 README)
|
|
40
|
+
|
|
41
|
+
- 旧文写的是 v1 时代的行为:调度器读 `qs.csv`、把问题回写为 `solved`、解法另存 `q1-的解法_<标识>.csv`。
|
|
42
|
+
- v2 的真实行为:问题与解法都在 `qs/qs.json`(`已解决` 布尔、解法列表带 `正确概率`),
|
|
43
|
+
定论事实进入 `Verified/<分类>_Verified.json`;**v2 不产生任何 CSV**。
|
|
44
|
+
- 英文版同时补上了这些真实键名的英文含义,读者不必对着陌生键名猜。
|
|
45
|
+
|
|
46
|
+
### 4. 旧架构文档标注适用范围
|
|
47
|
+
|
|
48
|
+
- `docs/架构图.md` 中的 `qs.csv`、`Pending_Verification/`、`Under_Verification/`、`Temp_Validated/` 等名称
|
|
49
|
+
在 v2 起已不存在,这些目录在任何一套预设的代码里都找不到。
|
|
50
|
+
- 现在该文件开头写明它描述的是 **v1 历史布局**,并指向当前各版本的实现方案与架构图;
|
|
51
|
+
两份 README 对它的引用也从「(v2 详解)」改为「(v1 历史架构图)」,不再把旧布局当成当前实现。
|
|
52
|
+
|
|
53
|
+
## 兼容性
|
|
54
|
+
|
|
55
|
+
- 四套预设(v2 / v3 / v4 / v5)的代码、提示词、语料、参数与工具面**均未变化**,运行时行为完全不变。
|
|
56
|
+
- 中文 README 仍是默认的 `README.md`,npm 页面与 GitHub 首页的默认内容不变;`screenshots.json` 的市场配图也未改动。
|
|
57
|
+
- 包内新增 4 张英文 SVG、2 个英文版生成脚本与本版发布说明;除此之外随包内容的变化只有 README 与文档说明。
|
|
58
|
+
- 本版只涉及文档、配图与测试守卫,不涉及任何运行时行为。
|
|
59
|
+
|
|
60
|
+
## 升级
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
npm i dsh-vibe-math@latest
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
若你在 profile 里把版本钉死了,先升级那个依赖再重启 DSH:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
dsh plugin --profile <你的 profile> add dsh-vibe-math@latest
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
本次未改动预设文件,已安装的预设无需重新同步。无需迁移。
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
# dsh-vibe-math 2.3.16 — Release Notes (English)
|
|
77
|
+
|
|
78
|
+
> Previous: 2.3.15. This release finishes the **English documentation and its artwork**: the English
|
|
79
|
+
> README is now fully English and ships with its **own four architecture diagrams**, one statement
|
|
80
|
+
> about v2's data layer that did not match the implementation is corrected in both READMEs, and the
|
|
81
|
+
> v1-era architecture document is labelled as such. The four presets' code, prompts and corpora are
|
|
82
|
+
> **byte-identical**; runtime behaviour is unchanged.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Overview
|
|
87
|
+
|
|
88
|
+
- **The English README is fully English**: example conversations, the parameter table, directory-tree
|
|
89
|
+
placeholders and link labels are all English. Only literals that must stay verbatim for the reader
|
|
90
|
+
to match the implementation stay Chinese (real file and directory names, real JSON keys, real
|
|
91
|
+
Markdown anchors), each glossed in English where it first appears.
|
|
92
|
+
- **Four English architecture diagrams**: `示例图/框架图-v2-en.svg` … `-v5-en.svg`, referenced by the
|
|
93
|
+
English README only; all four Chinese diagram files are untouched.
|
|
94
|
+
- **One factual error fixed**: both READMEs claimed v2 writes `qs.csv` back as `solved` and stores
|
|
95
|
+
`q1-的解法_<标识>.csv`. v2 writes **no CSV at all**; the text now matches the implementation.
|
|
96
|
+
- **The old architecture document states its scope**: `docs/架构图.md` describes the v1-era layout, and
|
|
97
|
+
both the file and the README links to it now say so.
|
|
98
|
+
|
|
99
|
+
## Changes
|
|
100
|
+
|
|
101
|
+
### 1. The English README is fully English
|
|
102
|
+
|
|
103
|
+
- Example prompts, the parameter table, directory-tree placeholders (`<项目>` → `<project>`, …) and
|
|
104
|
+
link labels are all English.
|
|
105
|
+
- What stays Chinese is only the **real literals**: real file and directory names (`示例图/…`,
|
|
106
|
+
`Verified/命题/`, `Logs/报告.md`, …), real JSON keys (`已解决`, `正确概率`), real Markdown anchors
|
|
107
|
+
(`- ID/类型/状态/概率/…`, `### 解法/证明/证伪 N|标题|概率X|状态Y`) and the session-rebuild marker
|
|
108
|
+
`【会话重建 —— <角色> <id>】`. Each is glossed in English where it first appears, so nothing reads as
|
|
109
|
+
an untranslated leftover.
|
|
110
|
+
- Stronger guard: outside those literals the English file may not contain Chinese prose, and the two
|
|
111
|
+
READMEs must agree on heading levels, table rows, code fences, images and local links — editing only
|
|
112
|
+
one side now fails the suite.
|
|
113
|
+
|
|
114
|
+
### 2. Four English architecture diagrams
|
|
115
|
+
|
|
116
|
+
- Added `示例图/框架图-v2-en.svg`, `-v3-en.svg`, `-v4-en.svg` and `-v5-en.svg`, referenced by the English
|
|
117
|
+
README. **The four Chinese diagram files are unchanged** (the Chinese README only receives the two
|
|
118
|
+
corrections described in sections 3 and 4 below).
|
|
119
|
+
- How they are produced: v4/v5 use the existing zero-dependency generator with `--lang=en`; v2/v3 have
|
|
120
|
+
new zero-dependency Node generators, `docs/generate_framework_diagram_v{2,3}_en.mjs` (the Chinese
|
|
121
|
+
v2/v3 posters still come from the original matplotlib scripts, which were not touched).
|
|
122
|
+
- The English diagrams also keep Chinese only for real literals (v3's md anchors, v4/v5's
|
|
123
|
+
`Verified/命题/<id>.md` paths); everything else is English. Each generator warns and exits non-zero
|
|
124
|
+
when a line overflows its container, and a guard verifies that every diagram is complete, parseable
|
|
125
|
+
and English.
|
|
126
|
+
|
|
127
|
+
### 3. v2's data layer corrected in both READMEs
|
|
128
|
+
|
|
129
|
+
- The old text described v1 behaviour: a scheduler reading `qs.csv`, writing the problem back as
|
|
130
|
+
`solved`, and storing the solution as `q1-的解法_<标识>.csv`.
|
|
131
|
+
- v2's real behaviour: problems and solutions both live in `qs/qs.json` (`已解决` boolean, solution list
|
|
132
|
+
with `正确概率`), and settled facts go to `Verified/<category>_Verified.json`. **v2 produces no CSV.**
|
|
133
|
+
- The English version spells out what those key names mean, so no reader has to guess.
|
|
134
|
+
|
|
135
|
+
### 4. The old architecture document states its scope
|
|
136
|
+
|
|
137
|
+
- The names in `docs/架构图.md` — `qs.csv`, `Pending_Verification/`, `Under_Verification/`,
|
|
138
|
+
`Temp_Validated/` — have not existed since v2 and appear in none of the shipped presets.
|
|
139
|
+
- The file now opens by saying it documents the **v1 historical layout** and points at the current
|
|
140
|
+
specifications and diagrams; both READMEs now link to it as "(v1-era architecture notes)" instead of
|
|
141
|
+
presenting the old layout as the current implementation.
|
|
142
|
+
|
|
143
|
+
## Compatibility
|
|
144
|
+
|
|
145
|
+
- The four presets (v2 / v3 / v4 / v5) are **unchanged** in code, prompts, corpora, parameters and tool
|
|
146
|
+
surface; runtime behaviour is identical.
|
|
147
|
+
- The Chinese README is still the default `README.md`, so the npm page and the GitHub landing page keep
|
|
148
|
+
their current content; the marketplace images in `screenshots.json` are unchanged.
|
|
149
|
+
- The package adds four English SVGs, two English generator scripts and this release's notes; apart
|
|
150
|
+
from that, only the READMEs and documentation text changed.
|
|
151
|
+
- Documentation, artwork and test guards only — no runtime behaviour.
|
|
152
|
+
|
|
153
|
+
## Upgrade
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
npm i dsh-vibe-math@latest
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
If you pinned the version in a profile, update that dependency first and restart DSH:
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
dsh plugin --profile <your profile> add dsh-vibe-math@latest
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
No preset files changed, so installed presets need no re-sync. No migration needed.
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ============================================================
|
|
3
|
+
// 把生成好的框架图 SVG 光栅化为**完整**的高清 PNG(无头 Chrome)。
|
|
4
|
+
//
|
|
5
|
+
// node docs/render_framework_diagram_png.mjs 示例图/框架图-v5.svg 示例图/框架图-v5.png 2
|
|
6
|
+
//
|
|
7
|
+
// 为什么需要这个脚本(2.3.15 修掉的真实缺陷):过去那条一行命令
|
|
8
|
+
// chrome --headless=new --window-size=1720,1204 --screenshot=框架图-v5.png …
|
|
9
|
+
// 截的是**窗口**,而页面**视口**比窗口矮约 96px(浏览器 UI)——
|
|
10
|
+
// 于是 SVG 底部那一截(图例行)根本没被绘制,而 PNG 的尺寸又恰好等于 SVG 的画布尺寸,
|
|
11
|
+
// 所以"少了 96px"这件事从尺寸上看不出来,一直没人发现。
|
|
12
|
+
//
|
|
13
|
+
// 因此这里:
|
|
14
|
+
// 1. 用**多留一段窗口高度**的方式渲染(保证整个视口都被绘制);
|
|
15
|
+
// 2. 把顶部 `svg宽 × svg高 × scale` 区域裁出来;
|
|
16
|
+
// 3. **校验**结果:尺寸必须精确相等,且"墨迹"必须到达 SVG 自身内容的底边——
|
|
17
|
+
// 凡是比原图矮/被裁的产物都会**直接报错退出**,不会再悄悄发布出去。
|
|
18
|
+
// ============================================================
|
|
19
|
+
import { readFileSync, writeFileSync, copyFileSync, mkdtempSync, rmSync, existsSync } from 'node:fs'
|
|
20
|
+
import { spawnSync } from 'node:child_process'
|
|
21
|
+
import { deflateSync, inflateSync } from 'node:zlib'
|
|
22
|
+
import { tmpdir } from 'node:os'
|
|
23
|
+
import { basename, join } from 'node:path'
|
|
24
|
+
|
|
25
|
+
const CHROME_CANDIDATES = [
|
|
26
|
+
'C:/Program Files/Google/Chrome/Application/chrome.exe',
|
|
27
|
+
'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
|
|
28
|
+
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
|
|
29
|
+
'C:/Program Files/Microsoft/Edge/Application/msedge.exe',
|
|
30
|
+
'/usr/bin/google-chrome', '/usr/bin/chromium', '/usr/bin/chromium-browser',
|
|
31
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
32
|
+
]
|
|
33
|
+
const CHROME = process.env.CHROME_PATH || CHROME_CANDIDATES.find((p) => existsSync(p))
|
|
34
|
+
const PAD = 240 // 额外窗口高度:必须大于 new-headless 的浏览器 UI 高度
|
|
35
|
+
|
|
36
|
+
// ---------- SVG ----------
|
|
37
|
+
export function svgSize(file) {
|
|
38
|
+
const head = readFileSync(file, 'utf8').slice(0, 800)
|
|
39
|
+
const w = Number((head.match(/\bwidth="(\d+(?:\.\d+)?)"/) || [])[1])
|
|
40
|
+
const h = Number((head.match(/\bheight="(\d+(?:\.\d+)?)"/) || [])[1])
|
|
41
|
+
if (!w || !h) throw new Error('无法从 ' + file + ' 读出 width/height')
|
|
42
|
+
return { w, h }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** SVG 内容(含嵌套 translate)的真实外接框,用来判断光栅是否被裁。 */
|
|
46
|
+
export function contentBox(file) {
|
|
47
|
+
const svg = readFileSync(file, 'utf8')
|
|
48
|
+
const stack = [{ x: 0, y: 0 }]
|
|
49
|
+
let maxBottom = -Infinity, maxRight = -Infinity
|
|
50
|
+
const re = /<(\/?)(g|rect|text|line|circle|polyline|polygon|path|image)\b([^>]*)>/g
|
|
51
|
+
let m
|
|
52
|
+
while ((m = re.exec(svg)) !== null) {
|
|
53
|
+
const [, close, tag, attrs] = m
|
|
54
|
+
if (tag === 'g') {
|
|
55
|
+
if (close) { if (stack.length > 1) stack.pop(); continue }
|
|
56
|
+
const t = /transform="translate\(\s*(-?[\d.]+)[ ,]+(-?[\d.]+)/.exec(attrs)
|
|
57
|
+
const cur = stack[stack.length - 1]
|
|
58
|
+
stack.push({ x: cur.x + (t ? Number(t[1]) : 0), y: cur.y + (t ? Number(t[2]) : 0) })
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
if (close) continue
|
|
62
|
+
const cur = stack[stack.length - 1]
|
|
63
|
+
const num = (n) => { const r = new RegExp('\\b' + n + '="(-?[\\d.]+)"').exec(attrs); return r ? Number(r[1]) : null }
|
|
64
|
+
let bottom = null, right = null
|
|
65
|
+
if (tag === 'rect' || tag === 'image') {
|
|
66
|
+
const y = num('y'), h = num('height'), x = num('x'), w = num('width')
|
|
67
|
+
if (y !== null && h !== null) bottom = cur.y + y + h
|
|
68
|
+
if (x !== null && w !== null) right = cur.x + x + w
|
|
69
|
+
} else if (tag === 'text') {
|
|
70
|
+
const y = num('y')
|
|
71
|
+
if (y !== null) bottom = cur.y + y + (num('font-size') || 13) * 0.32
|
|
72
|
+
} else if (tag === 'line') {
|
|
73
|
+
const y1 = num('y1'), y2 = num('y2'), x1 = num('x1'), x2 = num('x2')
|
|
74
|
+
if (y1 !== null && y2 !== null) bottom = cur.y + Math.max(y1, y2)
|
|
75
|
+
if (x1 !== null && x2 !== null) right = cur.x + Math.max(x1, x2)
|
|
76
|
+
} else if (tag === 'circle') {
|
|
77
|
+
const cy = num('cy'), r = num('r')
|
|
78
|
+
if (cy !== null && r !== null) bottom = cur.y + cy + r
|
|
79
|
+
} else if (tag === 'polyline' || tag === 'polygon') {
|
|
80
|
+
const pts = /points="([^"]+)"/.exec(attrs)
|
|
81
|
+
if (pts) {
|
|
82
|
+
const v = pts[1].trim().split(/[\s,]+/).map(Number)
|
|
83
|
+
const ys = v.filter((_, i) => i % 2 === 1)
|
|
84
|
+
if (ys.length) bottom = cur.y + Math.max(...ys)
|
|
85
|
+
}
|
|
86
|
+
} else if (tag === 'path') {
|
|
87
|
+
const d = /d="([^"]+)"/.exec(attrs)
|
|
88
|
+
if (d) {
|
|
89
|
+
const nums = d[1].match(/-?\d+(?:\.\d+)?/g) || []
|
|
90
|
+
const ys = nums.map(Number).filter((_, i) => i % 2 === 1)
|
|
91
|
+
if (ys.length) bottom = cur.y + Math.max(...ys)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (bottom !== null && bottom > maxBottom) maxBottom = bottom
|
|
95
|
+
if (right !== null && right > maxRight) maxRight = right
|
|
96
|
+
}
|
|
97
|
+
return { bottom: maxBottom, right: maxRight }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------- PNG(只处理浏览器截图会产生的 8bit RGB/RGBA 非隔行) ----------
|
|
101
|
+
export function decodePng(buf) {
|
|
102
|
+
if (buf.readUInt32BE(0) !== 0x89504e47) throw new Error('不是 PNG')
|
|
103
|
+
let off = 8, w = 0, h = 0, depth = 0, color = 0, interlace = 0
|
|
104
|
+
const idat = []
|
|
105
|
+
while (off < buf.length) {
|
|
106
|
+
const len = buf.readUInt32BE(off)
|
|
107
|
+
const type = buf.toString('ascii', off + 4, off + 8)
|
|
108
|
+
const data = buf.subarray(off + 8, off + 8 + len)
|
|
109
|
+
if (type === 'IHDR') { w = data.readUInt32BE(0); h = data.readUInt32BE(4); depth = data[8]; color = data[9]; interlace = data[12] } else if (type === 'IDAT') idat.push(data)
|
|
110
|
+
else if (type === 'IEND') break
|
|
111
|
+
off += 12 + len
|
|
112
|
+
}
|
|
113
|
+
if (depth !== 8 || interlace !== 0 || (color !== 2 && color !== 6)) throw new Error(`不支持的 PNG(depth=${depth} color=${color} interlace=${interlace})`)
|
|
114
|
+
const bpp = color === 6 ? 4 : 3
|
|
115
|
+
const stride = w * bpp
|
|
116
|
+
const raw = inflateSync(Buffer.concat(idat))
|
|
117
|
+
const px = Buffer.alloc(h * stride)
|
|
118
|
+
let p = 0
|
|
119
|
+
for (let y = 0; y < h; y++) {
|
|
120
|
+
const ft = raw[p++]
|
|
121
|
+
const row = raw.subarray(p, p + stride); p += stride
|
|
122
|
+
const out = px.subarray(y * stride, (y + 1) * stride)
|
|
123
|
+
const prev = y > 0 ? px.subarray((y - 1) * stride, y * stride) : null
|
|
124
|
+
for (let i = 0; i < stride; i++) {
|
|
125
|
+
const a = i >= bpp ? out[i - bpp] : 0
|
|
126
|
+
const b = prev ? prev[i] : 0
|
|
127
|
+
const c = prev && i >= bpp ? prev[i - bpp] : 0
|
|
128
|
+
let v = row[i]
|
|
129
|
+
if (ft === 1) v = (v + a) & 0xff
|
|
130
|
+
else if (ft === 2) v = (v + b) & 0xff
|
|
131
|
+
else if (ft === 3) v = (v + ((a + b) >> 1)) & 0xff
|
|
132
|
+
else if (ft === 4) {
|
|
133
|
+
const pa = Math.abs(b - c), pb = Math.abs(a - c), pc = Math.abs(a + b - 2 * c)
|
|
134
|
+
v = (v + (pa <= pb && pa <= pc ? a : pb <= pc ? b : c)) & 0xff
|
|
135
|
+
}
|
|
136
|
+
out[i] = v
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { w, h, bpp, px }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const CRC_TABLE = (() => {
|
|
143
|
+
const t = new Int32Array(256)
|
|
144
|
+
for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; t[n] = c }
|
|
145
|
+
return t
|
|
146
|
+
})()
|
|
147
|
+
const crc32 = (buf) => { let c = -1; for (const b of buf) c = CRC_TABLE[(c ^ b) & 0xff] ^ (c >>> 8); return (c ^ -1) >>> 0 }
|
|
148
|
+
function chunk(type, data) {
|
|
149
|
+
const out = Buffer.alloc(8 + data.length + 4)
|
|
150
|
+
out.writeUInt32BE(data.length, 0)
|
|
151
|
+
out.write(type, 4, 'ascii')
|
|
152
|
+
data.copy(out, 8)
|
|
153
|
+
out.writeUInt32BE(crc32(Buffer.concat([Buffer.from(type, 'ascii'), data])), 8 + data.length)
|
|
154
|
+
return out
|
|
155
|
+
}
|
|
156
|
+
function encodePng(w, h, bpp, px) {
|
|
157
|
+
const stride = w * bpp
|
|
158
|
+
const raw = Buffer.alloc(h * (stride + 1))
|
|
159
|
+
for (let y = 0; y < h; y++) {
|
|
160
|
+
raw[y * (stride + 1)] = 0
|
|
161
|
+
px.copy(raw, y * (stride + 1) + 1, y * stride, (y + 1) * stride)
|
|
162
|
+
}
|
|
163
|
+
const ihdr = Buffer.alloc(13)
|
|
164
|
+
ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4)
|
|
165
|
+
ihdr[8] = 8; ihdr[9] = bpp === 4 ? 6 : 2
|
|
166
|
+
return Buffer.concat([
|
|
167
|
+
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
|
168
|
+
chunk('IHDR', ihdr), chunk('IDAT', deflateSync(raw, { level: 9 })), chunk('IEND', Buffer.alloc(0)),
|
|
169
|
+
])
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** 墨迹范围(相对背景色):用来断言"图没被裁"。 */
|
|
173
|
+
function inkExtent(img) {
|
|
174
|
+
const { w, h, bpp, px } = img
|
|
175
|
+
const bg = [px[0], px[1], px[2]]
|
|
176
|
+
let lastInk = -1, lastX = -1
|
|
177
|
+
for (let y = 0; y < h; y++) {
|
|
178
|
+
for (let x = 0; x < w; x++) {
|
|
179
|
+
const i = y * w * bpp + x * bpp
|
|
180
|
+
if (Math.abs(px[i] - bg[0]) + Math.abs(px[i + 1] - bg[1]) + Math.abs(px[i + 2] - bg[2]) > 12) {
|
|
181
|
+
if (y > lastInk) lastInk = y
|
|
182
|
+
if (x > lastX) lastX = x
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { lastInk, lastX }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 同一个问题的**快速**版本:想知道"墨迹最低到哪一行、最右到哪一列",
|
|
191
|
+
* 从底边向上、从右边向左扫,命中即停——守卫里对 3400×2400 的图也只花毫秒级。
|
|
192
|
+
*/
|
|
193
|
+
export function inkExtentFast(img) {
|
|
194
|
+
const { w, h, bpp, px } = img
|
|
195
|
+
const bg = [px[0], px[1], px[2]]
|
|
196
|
+
const isInk = (x, y) => {
|
|
197
|
+
const i = y * w * bpp + x * bpp
|
|
198
|
+
return Math.abs(px[i] - bg[0]) + Math.abs(px[i + 1] - bg[1]) + Math.abs(px[i + 2] - bg[2]) > 12
|
|
199
|
+
}
|
|
200
|
+
let lastInk = -1
|
|
201
|
+
for (let y = h - 1; y >= 0 && lastInk < 0; y--) {
|
|
202
|
+
for (let x = 0; x < w; x++) if (isInk(x, y)) { lastInk = y; break }
|
|
203
|
+
}
|
|
204
|
+
let lastX = -1
|
|
205
|
+
for (let x = w - 1; x >= 0 && lastX < 0; x--) {
|
|
206
|
+
for (let y = 0; y < h; y++) if (isInk(x, y)) { lastX = x; break }
|
|
207
|
+
}
|
|
208
|
+
return { lastInk, lastX }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---------- render ----------
|
|
212
|
+
function asciiTempDir() {
|
|
213
|
+
for (const c of [process.env.TEMP, process.env.TMP, tmpdir(), '/tmp']) {
|
|
214
|
+
if (c && /^[\x20-\x7e]+$/.test(c) && existsSync(c)) return c
|
|
215
|
+
}
|
|
216
|
+
throw new Error('找不到纯 ASCII 的临时目录;无头 Chrome 打不开非 ASCII 路径')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function renderSvgToPng(svgPath, outPng, scale = 2) {
|
|
220
|
+
if (!CHROME) throw new Error('找不到 Chrome/Edge;可用 CHROME_PATH 指定。试过:\n ' + CHROME_CANDIDATES.join('\n '))
|
|
221
|
+
const { w, h } = svgSize(svgPath)
|
|
222
|
+
const { bottom, right } = contentBox(svgPath)
|
|
223
|
+
const dir = mkdtempSync(join(asciiTempDir(), 'svg-png-'))
|
|
224
|
+
try {
|
|
225
|
+
const inSvg = join(dir, 'in.svg')
|
|
226
|
+
const shot = join(dir, 'shot.png')
|
|
227
|
+
copyFileSync(svgPath, inSvg)
|
|
228
|
+
const args = ['--headless=new', '--disable-gpu', '--no-sandbox', '--no-first-run', '--hide-scrollbars',
|
|
229
|
+
`--force-device-scale-factor=${scale}`, `--window-size=${w},${h + PAD}`,
|
|
230
|
+
`--user-data-dir=${join(dir, 'profile')}`, `--screenshot=${shot}`, 'file:///' + inSvg.replace(/\\/g, '/')]
|
|
231
|
+
const r = spawnSync(CHROME, args, { encoding: 'utf8' })
|
|
232
|
+
if (!existsSync(shot)) throw new Error('Chrome 没有产出截图:' + String(r.stderr || '').slice(-300))
|
|
233
|
+
|
|
234
|
+
const img = decodePng(readFileSync(shot))
|
|
235
|
+
const needW = Math.round(w * scale), needH = Math.round(h * scale)
|
|
236
|
+
if (img.w < needW || img.h < needH) throw new Error(`截图 ${img.w}x${img.h} 小于所需 ${needW}x${needH}`)
|
|
237
|
+
// 裁掉多留的高度(以及 new-headless 的浏览器 UI 区域)
|
|
238
|
+
const stride = img.w * img.bpp
|
|
239
|
+
const cropped = Buffer.alloc(needW * img.bpp * needH)
|
|
240
|
+
for (let y = 0; y < needH; y++) {
|
|
241
|
+
img.px.copy(cropped, y * needW * img.bpp, y * stride, y * stride + needW * img.bpp)
|
|
242
|
+
}
|
|
243
|
+
const out = { w: needW, h: needH, bpp: img.bpp, px: cropped }
|
|
244
|
+
const ink = inkExtent(out)
|
|
245
|
+
const tol = 14 * scale
|
|
246
|
+
const problems = []
|
|
247
|
+
if (ink.lastInk < Math.round(bottom * scale) - tol) {
|
|
248
|
+
problems.push(`墨迹只到第 ${ink.lastInk} 行,而 SVG 内容底边在 ${Math.round(bottom * scale)} 行 —— 底部被裁掉了`)
|
|
249
|
+
}
|
|
250
|
+
if (right > 0 && ink.lastX < Math.round(right * scale) - tol) {
|
|
251
|
+
problems.push(`墨迹最右只到第 ${ink.lastX} 列,而 SVG 内容右边界在 ${Math.round(right * scale)} 列 —— 右侧被裁掉了`)
|
|
252
|
+
}
|
|
253
|
+
if (problems.length) throw new Error('光栅化结果不完整:\n - ' + problems.join('\n - '))
|
|
254
|
+
writeFileSync(outPng, encodePng(out.w, out.h, out.bpp, out.px))
|
|
255
|
+
return { svg: { w, h }, png: { w: out.w, h: out.h }, scale, ink, content: { bottom, right } }
|
|
256
|
+
} finally {
|
|
257
|
+
rmSync(dir, { recursive: true, force: true })
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const invokedDirectly = process.argv[1] && basename(process.argv[1]) === 'render_framework_diagram_png.mjs'
|
|
262
|
+
if (invokedDirectly) {
|
|
263
|
+
const [svg, png, scale] = process.argv.slice(2)
|
|
264
|
+
if (!svg || !png) { console.error('用法: node docs/render_framework_diagram_png.mjs <svg> <png> [scale=2]'); process.exit(2) }
|
|
265
|
+
try {
|
|
266
|
+
const r = renderSvgToPng(svg, png, Number(scale || 2))
|
|
267
|
+
console.log(`wrote ${png}: svg ${r.svg.w}x${r.svg.h} × ${r.scale} → ${r.png.w}x${r.png.h}`
|
|
268
|
+
+ ` (ink to row ${r.ink.lastInk}/${r.png.h - 1}, content bottom ${r.content.bottom.toFixed(0)})`)
|
|
269
|
+
} catch (e) {
|
|
270
|
+
console.error(String(e.message || e))
|
|
271
|
+
process.exit(1)
|
|
272
|
+
}
|
|
273
|
+
}
|
package/docs/test-timing.md
CHANGED
|
@@ -25,8 +25,10 @@ node tests/audit-v5-integrity.mjs # 静态:v5 完整性/理念门
|
|
|
25
25
|
|
|
26
26
|
| 脚本 | 串行(sum) | 并行(wall) | 实测输出 |
|
|
27
27
|
|---|---|---|---|
|
|
28
|
-
| `tests/run-tests.mjs`(
|
|
28
|
+
| `tests/run-tests.mjs`(28 个套件) | ≈ 230 s | **≈ 110 s**(并发 4) | 关键路径 = `e2e-v4-fixes` ≈ 96 s |
|
|
29
29
|
| `tests/audit-installer-policy.test.mjs` | ≈ 1 s | — | 在临时 DSH_HOME 里驱动真实安装器(复制 4 套预设 × 2 个版本) |
|
|
30
|
+
| `tests/audit-readme-bilingual.test.mjs` | ≈ 0.2 s | — | 中英 README 的结构/链接/图片/锚点一致性(英文版架构图按 `-en` 变体配对;纯静态) |
|
|
31
|
+
| `tests/audit-diagram-assets.test.mjs` | ≈ 1 s | — | 四张架构图 PNG 必须装得下它自己的画面、四张英文版 SVG 必须存在且正文为英文(解 PNG 与 SVG,纯静态) |
|
|
30
32
|
| `audit-formal-sensitivity.mjs`(49 探针) | 612.0 s | **154.6 s**(并发 4,speed-up x3.96) | 关键路径 = 12 个 v2 探针(每个 ≈32 s) |
|
|
31
33
|
| `audit-persona-sensitivity.mjs`(11 探针) | ≈ 5 s | — | 本身很快,不需要并行 |
|
|
32
34
|
| `audit-prompt-invariants.mjs`(157 条,含 X5–X8b 扫描器自检) | 0.4 s | — | 静态 |
|
|
@@ -50,7 +52,7 @@ node tests/audit-v5-integrity.mjs # 静态:v5 完整性/理念门
|
|
|
50
52
|
| `e2e-d9-d13.test.mjs` | ≈ 13 s | |
|
|
51
53
|
| `e2e-v3.test.mjs` | ≈ 12 s | |
|
|
52
54
|
| `formal-verify-v3.test.mjs` | ≈ 12 s | |
|
|
53
|
-
| 其余
|
|
55
|
+
| 其余 21 个 | ≤ 6 s | 其中绝大多数 < 1 s |
|
|
54
56
|
|
|
55
57
|
## 3. 已经做过的优化(别再重复踩)
|
|
56
58
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
# Vibe Math
|
|
1
|
+
# Vibe Math 架构图(v1 历史版)
|
|
2
|
+
|
|
3
|
+
> ⚠️ **历史文档**:本文件记录最初 **v1** 版本的架构。图中 `qs.csv`、`Pending_Verification/`、`Under_Verification/`、`Temp_Validated/` 等名称在 **v2 起已不存在**(v2 起问题与解法存于 `qs/qs.json`,定论事实存于 `Verified/<分类>_Verified.json`)。**v2–v5 的实际架构**请以 `README.md` 对应章节、`vibe-math-v2/实现方案.md` 等各版本实现方案与 `示例图/框架图-v2.png`…`-v5.svg` 为准。
|
|
2
4
|
|
|
3
5
|
> 框架 = **一个主代理(助手)+ 一个代码调度器 + 五类子代理** + 一套按项目隔离的持久化数据布局。
|
|
4
6
|
> 下方的 Mermaid 代码块在 GitHub 上会**原生渲染**成流程图;本文件即图源,改完重新提交即可自动更新。
|