From c66b5ad40da8e9257f65791327f57faa3f2ab75c Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Thu, 29 Oct 2026 16:28:21 +0800 Subject: [PATCH] =?UTF-8?q?gate:=20draw-diagram=20=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E6=AD=A3=E6=96=87=E6=89=A9=E5=86=99=E2=80=94=E2=80=94=E4=BB=A3?= =?UTF-8?q?=E4=B8=BA=E6=8F=90=E4=BA=A4=E4=BB=96=E4=BA=BA=E5=9C=A8=E9=A3=9E?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本文件在本会话开始前就已是工作区未提交状态(疑似技能自进化或另一会话所写, 非本次编辑)。按用户指示代为提交;提交前后逐字节核验内容未被改动、也没被 pre-commit 的改写型钩子动过: - 提交前工作区 git hash-object = a6bdc570fed705fb59380ae25a1c29e7032f482f - 本笔提交里的 blob = a6bdc570fed705fb59380ae25a1c29e7032f482f 内容(4,227 → 14,364 B,+129/−32 行): - 工具选型补「为什么不用 Mermaid」的实测教训(台阶图被压成同一高度) - 新增「二、从零新写生成器」:抄骨架三条约定、预检三件套 OVERFLOW / check_fit / wrap、emit() 按目标编码打印(generated-fresh 把脚本拷进临时目录重跑时不因 GBK 终端崩)、路径锚在脚本自身而非 cwd - 新增「三、目检」:Windows 无 SVG 渲染器时用本机 headless Chrome/Edge 截图回读 - 新增「五、generated-fresh 逐字节幂等」与「六、提交」,陷阱与验证清单同步 核验(手工跑,理由见下一段):skill-size Passed(14,364 B 远低于 65,536 写侧 上限,正文无 reference/ 指针故无悬空);trailing-whitespace Passed;无 BOM、 CR 0、U+FFFD 0、单个 LF 结尾、无三连空行。 ⚠ 顺带发现(本笔未修):本仓 .pre-commit-config.yaml 的全局 exclude 含 .dsh/, 而钩子的 files: ^\.dsh/skills/ 打不过全局 exclude → **只碰 .dsh/skills/ 的提交 一条钩子都不会跑**(这两笔提交期全部钩子显示 no files to check Skipped 即此因; 手工 run --files 会绕过全局 exclude,所以能 Passed)。skill-size 因此目前拦不到 任何真实提交,需要单独裁定怎么接(去全局 exclude 还是给它单独道)。 --- .dsh/skills/draw-diagram/SKILL.md | 161 ++++++++++++++++++++++++------ 1 file changed, 129 insertions(+), 32 deletions(-) diff --git a/.dsh/skills/draw-diagram/SKILL.md b/.dsh/skills/draw-diagram/SKILL.md index b193ab02..a6bdc570 100644 --- a/.dsh/skills/draw-diagram/SKILL.md +++ b/.dsh/skills/draw-diagram/SKILL.md @@ -1,12 +1,14 @@ --- name: draw-diagram -description: docs/ 文档技术配图绘制流程(精确布局图用 SVG 生成器 / 拓扑图用 Mermaid):挑脚本 → 改顶部数据 → 重跑生成 → 目检 → md 相对路径引用 → 脚本+产物一并提交,产物统一放 docs/images/。 -whenToUse: 需要新增/修改 docs/ 文档的技术配图(协议帧、分层架构、依赖/就绪屏障、流程图、时序、状态机),或用户要求"画个图 / 生成图 / 更新架构图 / 帧格式图"时。 +description: docs/ 文档技术配图绘制流程(精确布局图用 tools/arch_diagram/ 的纯标准库 SVG 生成器 / 拓扑图用 Mermaid):挑脚本或照抄骨架新建生成器 → 改顶部数据 → 跑生成并把 check_fit/OVERFLOW 溢出预检清零 → Windows 无渲染器时用 headless Chrome/Edge 截图 + read_image 回读目检 → 回改生成器重跑 → 过 generated-fresh 逐字节幂等 → md 相对路径引用 → 脚本+产物一并提交,产物统一放 docs/images/。 +whenToUse: 需要新增/修改 docs/ 文档的技术配图(协议帧、分层架构、依赖/就绪屏障、台阶/泳道对照、刻度尺、流程图、时序、状态机),从零新写一个 gen_*_svg.py 生成器,在 Windows 上想"目检"一张 SVG,或用户要求"画个图 / 生成图 / 更新架构图 / 帧格式图"时。 --- # 文档配图绘制(SVG / Mermaid) -`docs/` 文档技术配图的统一流程;完整选型依据 / 约定 / 检查清单见 `docs/架构设计/图形绘制流程.md`。**产物统一放 `docs/images/`**(专门配图目录,`.svg` 入库;PNG 预览属本地派生物、不入库)。 +`docs/` 文档技术配图的统一流程;完整选型依据 / 约定 / 检查清单见 `docs/架构设计/图形绘制流程.md`。**产物统一放 `docs/images/`**(专门配图目录,`.svg` 入库;PNG 预览属本地派生物、不入库,`.gitignore` 已含 `docs/images/*.png` 与 `ci-artifacts/`)。 + +> 本窗实测(2026-10-29,新建 2 张图):**"浏览器打开 SVG 目检"这一步在 Windows 上不可直接执行**——没有现成 SVG 栅格化工具,只能走 headless Chrome/Edge 截图 + 图片回读;且 `generated-fresh` 是**逐字节**门禁,生成器自己的控制台打印也会成为失败源(见 §五)。 ## 工具选型 @@ -15,15 +17,15 @@ whenToUse: 需要新增/修改 docs/ 文档的技术配图(协议帧、分层 | 协议帧 / 字节布局(字段宽 ∝ 字节数、偏移标尺) | SVG:`tools/arch_diagram/gen_frame_svg.py` | | 分层架构 / 模块依赖 | SVG:`tools/arch_diagram/gen_arch_svg.py` | | 就绪屏障 / 生产者-消费者 | SVG:`tools/arch_diagram/gen_barrier_svg.py` | +| 台阶 / 泳道对照 / 刻度尺等"高度差、并列对照"类 | SVG:**新写一个** `tools/arch_diagram/gen_<主题>_svg.py`(照抄最近生成器的骨架,见 §二) | | 其他精确布局图 | **复制**最接近的生成器改顶部数据(`build()` 与数据解耦,通用) | -| 流程 / 时序 / 状态机 / ER(只表达拓扑关系) | Markdown 内嵌 ` ```mermaid ` 代码块(`flowchart` / `sequenceDiagram` / `stateDiagram-v2` / `erDiagram`),无需生成图片 | +| 流程 / 时序 / 状态机 / ER(只表达拓扑关系) | Markdown 内嵌 ```mermaid 代码块(`flowchart` / `sequenceDiagram` / `stateDiagram-v2` / `erDiagram`),无需生成图片 | -> Mermaid 盒子按文本自适应宽、自动加有向边,**只适合"关系"类图**;凡需精确像素 / 比例的布局图一律走 SVG 生成器。 +> Mermaid 盒子按文本自适应宽、自动加有向边,**只适合"关系"类图**;凡需精确像素 / 比例 / 高度差的布局图一律走 SVG 生成器(实测教训:五级"台阶"用 Mermaid 会被压成同一高度,"还差一级"就看不出来了)。 -## SVG 生成流程(仓库根目录执行) +## 一、改既有图(最短路线) -1. **挑 / 复制脚本**:新类型图复制最接近的生成器,只改顶部数据区。 -2. **改顶部数据**:字段 / 节点 / 标题 / 配色。沿用既有调色板(主色 / 底色): +1. 改**顶部数据 / 常量区**(字段、节点、标题、配色),不改 `build()` 结构。配色沿用既有调色板(主色 / 底色): | 语义 | 主色 | 底色 | |---|---|---| @@ -35,38 +37,133 @@ whenToUse: 需要新增/修改 docs/ 文档的技术配图(协议帧、分层 | 灰 | `#475569` | `#f1f5f9` | | 玫红 | `#e11d48` | `#fff1f2` | -3. **重跑**(脚本内置 `ElementTree` 自检,XML 不合法直接抛错): +2. 重跑(仓库根;脚本内置 `ET.fromstring` 自检,XML 不合法直接抛错): ```powershell & '.venv\Scripts\python.exe' tools/arch_diagram/gen_xxx.py - # 成功输出:OK <字节> bytes, canvas x -> <路径> + # 期望:OK <字节> bytes, canvas x -> <路径>,且没有 OVERFLOW WARN / overflow warnings 行 ``` -4. **目检**:浏览器打开 `docs/images/*.svg`——文字不溢出盒外、框 / 线 / 标签对齐、配色符合调色板。 -5. **md 引用**(相对路径):`docs/` 根的 md 写 `![标题](images/xxx.svg)`;`docs/UI设计/` 等子目录 md 写 `![…](../images/xxx.svg)`。 -6. **提交**:生成器脚本(源)+ 生成的 `.svg`(产物)**一并**提交。 +3. 目检(§三)→ 引用(§四)→ 幂等与门禁(§五)→ 提交(§六)。 -## SVG 约定(手写 / 新增生成器必须遵循) +## 二、从零新写生成器(先抄骨架,别裸写) -- **纯标准库**拼 SVG(只用 `html` / `os`(+ `math`)),不引第三方库(SVG 由浏览器用系统字体渲染) -- 所有文本经 `html.escape(s, quote=True)`,防止破坏 XML -- 字体栈:`Microsoft YaHei, Segoe UI, PingFang SC, sans-serif` -- 文本宽估算(自适应盒宽):CJK / 全角 / 圈码 / 箭头 ≈ `1.0em`,其余 ≈ `0.58em`(**宁大勿小**防溢出) -- 落盘:`open(OUT, "w", encoding="utf-8", newline="\n")`(UTF-8 无 BOM + LF) -- 写盘后 `xml.etree.ElementTree.fromstring(svg)` 自检 -- 产物放 `docs/images/`,命名 `<主题>.svg`(可与同名 `.md` 对应) +1. **先读同目录 1~2 个最近的脚本**(如 `tools/arch_diagram/gen_adc_frontend_svg.py`、`gen_adc_code_scale_svg.py`),抄它们的三条约定: + - **文件头 docstring** 写清四件事:产物路径 + 这张图回答什么问题 + **「为什么不用 Mermaid」** + 用法("仓库根目录,任意 python3")与"改内容 vs 调坐标"的边界; + - **顶部集中常量块**:画布与几何(`W` / `H` / `PAD` / `RIGHT`)→ `FONT` → 各级字号 → 调色板 → 文案(`TITLE` / `SUB*` / 逐条目 / `FOOTNOTE`);坐标一律由常量推导,别在 `build()` 里塞魔法数; + - **纯标准库**:`import html / os / sys / xml.etree.ElementTree as ET`,helpers 只有 `esc` / `text_w` / `txt` / `line` / `rect` / `wrap` / `check_fit` / `emit` / `build`。 +2. **照抄预检三件套**(`gen_adc_frontend_svg.py` 里还没有;从 `tools/arch_diagram/gen_data_trust_ladder_svg.py` 抄):`OVERFLOW = []` + `check_fit(s, fs, avail, where)` + `wrap(s, fs, avail, max_lines, where)`,标题 / 副标题 / 盒内标题走 `check_fit`,盒内正文与脚注走 `wrap`(`wrap` 按估宽折行、超行数用 `…` 收尾并记一条 OVERFLOW)。`build()` 末尾:`emit("OK ...")` 之后 `if OVERFLOW: emit("!! %d overflow warnings" % len(OVERFLOW))`。估宽口径不变:CJK / 全角 / 圈码 / 箭头 ≈ `1.0em`,其余 ≈ `0.58em`(宁大勿小)。 +3. **照抄 `emit()`**(幂等门禁的地基,见 §五): -## SVG → PNG(可选,本地派生物不入库) + ```python + def emit(s): + """控制台打印按目标编码替换,绝不因 GBK 终端遇上 ²/− 之类字符而崩 + (generated-fresh 钩子会把本脚本拷进临时目录重跑,崩了就是一片假红)。""" + enc = getattr(sys.stdout, "encoding", None) or "ascii" + sys.stdout.write(s.encode(enc, "replace").decode(enc, "replace") + "\n") + ``` -- 最简单:浏览器打开 `.svg` → 另存为图片 -- 脚本化:`& '.venv\Scripts\python.exe' -m pip install cairosvg` 后 - `& '.venv\Scripts\python.exe' -c "import cairosvg; cairosvg.svg2png(url='docs/images/xxx.svg', write_to='x.png', scale=2)"` -- `.gitignore` 已含 `docs/images/*.png` +4. 路径锚在脚本自身位置,别用 cwd(临时目录重跑才不会漂): -## 提交前检查清单 + ```python + ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + OUT = os.path.join(ROOT, "docs", "images", "<主题>.svg") + ``` -- [ ] 生成器输出 `OK … bytes`(无异常 = XML 合法) -- [ ] 浏览器目检:文字不溢出、框 / 线 / 标签对齐、配色符合调色板 -- [ ] 引用它的 `.md` 里图片**相对路径**正确(能显示) -- [ ] 脚本 + 产物 `.svg` **一并**暂存 -- [ ] `pre-commit run --files <脚本> ` 全过(无乱码 / 行尾空白 / 编码 OK) +5. 跑 `& '.venv\Scripts\python.exe' tools/arch_diagram/gen_<主题>_svg.py`,**把 OVERFLOW 告警打到 0 再进目检**——预检能在渲染前就报出 `foot: 974 > 968` 这类溢出(本窗实测两张图首跑各报一条脚注超宽),比靠眼睛反复量便宜一个数量级。 +6. 新脚本落库时**同批**把这一行加进 `tools/arch_diagram/README.md` 的「脚本一览」表。 + +## 三、目检(Windows:本机没有任何现成 SVG 渲染器) + +**先探测,别照抄 cairosvg 命令**:本仓 `.venv` 无 `cairosvg`(`ModuleNotFoundError: No module named 'cairosvg'`),`rsvg-convert` 不存在,只有 Edge / Chrome: + +```bash +# ops_bash(git-bash)里探测;哪个存在用哪个 +ls -1 "/c/Program Files/Google/Chrome/Application/chrome.exe" \ + "/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe" 2>/dev/null +command -v rsvg-convert inkscape magick convert 2>/dev/null || echo "no CLI rasterizer" +``` + +```powershell +Test-Path "C:\Program Files\Google\Chrome\Application\chrome.exe" +Test-Path "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" +``` + +截图(**`--screenshot=` 必须给绝对 Windows 路径 + 绝对 `file:///` 输入**): + +```bash +mkdir -p ci-artifacts # Chrome 没有建目录的参数,先自己建 +OUT="D:\\Code\\DTU Code\\<工作树名>\\ci-artifacts\\<主题>.png" # 绝对路径、反斜杠 +"/c/Program Files/Google/Chrome/Application/chrome.exe" \ + --headless=new --disable-gpu --hide-scrollbars --force-device-scale-factor=1 \ + --window-size=1060,712 \ + --screenshot="$OUT" \ + "file:///D:/Code/DTU Code/<工作树名>/docs/images/<主题>.svg" 2>&1 | tail -2 +``` + +成功标志是 Chrome 自己打印的 ` bytes written to file D:\...`。**回读 PNG 才算目检完成**: + +```ts +const r = await tools.read_image({ file_path: "D:\\Code\\DTU Code\\<工作树名>\\ci-artifacts\\<主题>.png" }); +console.log(r.image.width + "x" + r.image.height, "bytes=" + r.image.bytes); // 应等于 --window-size / 画布 +``` + +看什么:文字压框 / 溢出盒外、标签互撞(本窗实测两轮才干净:台阶图首轮"文字压到盒下方 + 标签互撞",第二、三轮才修完)、`wrap` 收尾的省略号 `…`(有 = 文案超长)、虚线与盒边线重叠、配色是否还在调色板里。发现问题**回改生成器常量并重跑预检**,然后**重新截图**(PNG 是派生物,不会自己更新),`--window-size` 跟着 `canvas WxH` 改。 + +## 四、md 引用(相对路径) + +`docs/` 根的 md:`![标题](images/xxx.svg)`;`docs/任务规划/`、`docs/UI设计/` 等子目录 md:`![…](../images/xxx.svg)`(本窗实例:`docs/任务规划/数据可信.md` 写 `![数据可信是五级台阶,现在踩在第 2 级](../images/数据可信台阶.svg)`)。alt 文本写成"这张图的结论是什么",别只重复文件名。 + +## 五、generated-fresh:逐字节幂等门禁(新图长期不假红的前提) + +`generated-fresh` 把 `tools/arch_diagram/gen_*.py`(`docs-svg` 项:`per_script: true`、`pattern: gen_*.py`、`mode: exact`)**逐个拷进系统临时目录重跑**,与工作区产物**逐字节**比对:内容不同 = `DIFF`(生成物比生成器旧)、临时目录多出一个工作区没有的文件 = `NEW`、`rc≠0` = 「生成器报错」。由此推出四条硬约束: + +1. **生成器只能写那一个 `.svg`**:绝不在脚本里顺手导出 PNG 预览(预览写 `ci-artifacts/`,不进 `docs/images/`)。 +2. **stdout 不许崩**:GBK 终端遇上 `²` / `−` / CJK 路径会 `UnicodeEncodeError` → 钩子 `rc≠0`,一片假红。所以打印一律走 `emit()`,脚本里不留裸 `print()`(本窗实测:两份新生成器只改了一份,另一份照旧崩)。 +3. **产物内容零不确定**:SVG 里不写时间戳 / 绝对路径 / 主机名;浮点坐标一律 `%g` / `%.1f` 定形;字典与 `os.listdir` 结果参与布局时先 `sorted()`。 +4. **`gen_*.py` 命名即自动在册**(pattern 就是 `gen_*.py`)——故意不叫这个名字等于躲开门禁,门禁最大盲区正是"未登记的生成器",别利用它。 + +自查(仓库根): + +```bash +.venv/Scripts/python.exe tools/arch_diagram/gen_<主题>_svg.py +sha1sum docs/images/<主题>.svg +.venv/Scripts/python.exe tools/arch_diagram/gen_<主题>_svg.py # 重跑后 sha1 必须不变 +.venv/Scripts/python.exe scripts/check_generated_fresh.py # 期望 rc=0,无 DIFF/NEW,[BASELINE] 不增 +``` + +```powershell +& '.venv\Scripts\python.exe' scripts/check_generated_fresh.py # 单独跑(不 stash 别人在飞的改动) +& '.venv\Scripts\python.exe' scripts/check_generated_fresh.py --list # 只看在册声明 +& '.venv\Scripts\python.exe' -m pre_commit run generated-fresh --files tools/arch_diagram/gen_<主题>_svg.py docs/images/<主题>.svg +``` + +⚠ **别跑不带 `--files` 也不带 `--all-files` 的裸 `pre_commit run`**(会 stash 掉别人在飞的改动);判读口径 → `docs/门禁说明/generated-fresh.md` §4。 + +## 六、提交 + +生成器脚本(源)+ `docs/images/<主题>.svg`(产物)+ 引用它的 `.md` + `tools/arch_diagram/README.md` 那一行**同一笔**提交;PNG 预览不入库。 + +```powershell +& '.venv\Scripts\python.exe' -m pre_commit run --files <脚本> +``` + +## 陷阱(本窗实测) + +- **照着现文 / `docs/架构设计/图形绘制流程.md` §四 跑 `pip install cairosvg` 或 `rsvg-convert`**:这台机器上没有(前者 `ModuleNotFoundError`、后者根本没有这个命令)→ 先按 §三探测,改用 headless Chrome/Edge。**别在没渲染出 PNG 的情况下声称"已目检"。** +- **`--screenshot=ci-artifacts/ladder.png`(相对路径)** → `EXIT 2` + `Failed to write file ci-artifacts/ladder.png: 系统找不到指定的路径。 (0x3)`,PNG 压根没落盘;换绝对 Windows 路径后写出 95549 bytes。这条报错**不区分**"相对路径"与"目录不存在"两种成因(实测 `mkdir -p ci-artifacts` 已先跑过、仍报同一句),排查时两条都排。 +- **只做预检就收工**:`check_fit` / `OVERFLOW` 归零也不代表干净——本窗实测"门标签压第 5 级盒、蓝底标签挤、多余虚线"三类碰撞只有 PNG 回读看得见;反过来只目检不预检,脚注溢出就得靠眼睛反复量。**两者都要 0**。 +- **手改 SVG 成品**:等于制造「不是我改的」diff,且 `generated-fresh` 立刻 DIFF;也**不要退化成 ASCII 框图**(AGENTS.md 明令勿手绘)。一切修改都落在生成器常量上再重跑。 +- **批量改生成器时 `old_string` 不匹配**:一批 `edit` 会在中途抛错,但**前面的编辑已经落盘** → 半成品生成器(本窗实测:`emit` helper 缺失却仍打印 OK,几轮之后才查出)。正解:改前先 `read` 出精确原文,逐条改,末尾统一复验(`grep -n "def emit"`、`grep -c print`、再重跑看 rc 与 OK 行)。 +- **PNG 尺寸 ≠ 画布**:`--window-size` 忘了跟着 `W`/`H` 改 → 截出来带大片空白或裁掉底部脚注;回读 `r.image.width/height` 与 `canvas WxH` 对一下再判定。 +- **`docs/images/` 下手写 `.svg` 或放 PNG 预览**:前者没有生成器(NEW / 躲过门禁),后者污染"临时目录多了文件"判定。 + +## 验证清单 + +- [ ] 生成器输出 `OK … bytes, canvas WxH`,**无** `OVERFLOW WARN` / `!! N overflow warnings`;`grep -n "def emit"` 命中、`grep -c print` = 0。 +- [ ] 绝对路径 PNG 存在、尺寸 = 画布,`read_image` 回读确认:无文字压框 / 溢出、无标签互撞、无 `…` 截断收尾、配色在调色板内。 +- [ ] 连跑两次产物 sha1 不变;`scripts/check_generated_fresh.py` rc=0(无 DIFF / NEW,`[BASELINE]` 未增加)。 +- [ ] 引用它的 `.md` 相对路径正确(根目录 `images/`,子目录 `../images/`);`tools/arch_diagram/README.md` 已加脚本行。 +- [ ] 脚本 + 产物 `.svg` + `.md` + README 行一并暂存;`pre-commit run --files …` 全过(无乱码 / 行尾空白 / 编码 OK)。 + +> **TODO(口径未与权威文档同步)**:`docs/架构设计/图形绘制流程.md` §3.1 第 4 步「目检:浏览器打开 SVG」与 §四「给 `.venv` 装 cairosvg 转 PNG」在 Windows 上按本 skill 的实测是走不通的。该文档是规范归属(AGENTS.md:同一事实只在一处详述),采纳 §三 / §五 时应把这两段同步过去;目前两处不一致,**做图以本 skill 的实测命令为准,规范条文以该文档为归属**。