diff --git a/PROGRESS.md b/PROGRESS.md
index 85ed77c..47d3522 100644
--- a/PROGRESS.md
+++ b/PROGRESS.md
@@ -2,7 +2,7 @@
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。
-最后更新:2026-07-01(加快捷指令:触发词→完整指令,入口层确定性展开 + bump 0.35.0)
+最后更新:2026-07-02(ppt 渲图验收闭环 + 导出验收硬门 + 几何质检,bump 0.36.0)
---
@@ -21,6 +21,9 @@
## 已完成关键能力
+### 2026-07-02 / ppt 渲图验收闭环 + 导出验收硬门 + 几何质检(139a59c5 复盘,bump 0.36.0)
+复盘 task 139a59c5(deepseek-v4-flash,25 页陶瓷节点方案):用户实报"很多地方错位"。本机 PowerPoint COM 渲全部 25 页定位三类错位:①图标压字/游离(P4/P5/P8/P10/P16/P24——质检报"缺图标"后模型写 `add_icons.py` **regex 批量盲插坐标**,插完没看);②大字号数字压说明文字(P5 万亿/26%);③目录溢出页底(P2)。**根因:SKILL 阶段六"全量渲图验收"被整个跳过**——进度步骤标 completed 但唯一动作是 `echo 交付清单`,`svg_preview` 全程 0 调用;文档要求了但无机制强制(与 0.35.1 教训同构:纯文档约束拦不住弱模型)。改动三层:**A 验收闭环+导出硬门(机制)**——`svg_preview.py` 渲 project 时登记 `.build/acceptance.json`(每页 svg_output 源 sha1 + rendered_from + verdict;svg_output 比 svg_final 新的页拒登记);新增 `accept_pages.py`(`--pass/--pass-all/--fail --reason/--status`,标 pass 前校验"渲过 + PNG 在 + 渲后源没改");`svg_to_pptx` 导出边界加验收门(spec_lock 存在时每页须 verdict=pass 且源 sha1 未变,finalize 前渲的也拒;`--allow-unreviewed` 逃生口)——"从没渲过就交付"和"改页不复看"在导出边界被确定性挡下,单页返工回路(`--pages N` 重渲 merge 记录)已本机全链路验证。**B 几何质检(提前拦截)**——`svg_quality_checker` 新增 check 13:按字符估宽(CJK≈1em/Latin≈0.5-0.7em)+ translate 累加构包围盒;**图标压字、基线出画布=ERROR**(几何精确),**文字-文字重叠一律 WARN 带精确坐标**(估宽分不清擦边与压字,词云/象限图等密排设计会误伤,判断权交渲图验收;SKILL 阶段四明确 Geometry warn 渲图时必须对着坐标看);tspan 按"视觉行"归组续排(`$4.2B (35%)` 是一行不是两段),71 个 charts 模板 0 error 误报、复刻事故的 fixture 全命中。**C 管线顺序+反模式(文档)**——SKILL.md 管线改"后处理→渲图验收→导出"(验收在导出前),阶段五=finalize+全量渲图+逐页过目+标记,阶段六=拆备注+导出(验收门+图标门双硬门);反模式加"没看 PNG 就 --pass-all"和"为消警告脚本批量盲插元素不复看"。SKILL_LIST 同步。已知边界:gate 只能强制"渲过、源没改",看没看 PNG 无法机器验证(--pass-all 仍可被糊弄,但本次事故"从不渲图"的直接通路已封死)。
+
### 2026-07-02 / ppt skill 补「禁自搓导出器」硬约束(966041e5 复盘,bump 0.35.1)
复盘同一 task 后续产物 `陶瓷资源节点建设方案 (3).pptx`(deepseek-v4-flash 跑):python-pptx 拆开验证 **25 页每页只有 1 张 1280×720 整页 PNG 贴图、零原生文本/形状**——skill「原生可编辑 DrawingML」的核心卖点全废。根因:模型**整条绕开官方管线**——DB 轨迹里 `svg_quality_checker / finalize_svg / svg_to_pptx / svg_preview / total_md_split` 官方脚本**调用次数全是 0**,取而代之自己 `pip install cairosvg` + 手搓 `export_pptx.py` 调 16 次,把每页 SVG 渲成 PNG 整页贴进幻灯片。连锁三个用户实报缺陷:①「很多方格子」= 跳过 finalize_svg,图标占位空心 rect 没内嵌;②「生成的图没放进去」= cairosvg 加载不了 `href="../images/*"` 外链(实测 file://+xlink 都渲空白),AI 配图全丢、事后靠 base64 补;③文字溢出出血被裁(P04/P05/P09)+ 标题 font-weight 因属性写坏(`serif" font-weight="bold"` 引号错位)丢加粗。**关键教训**:上一条(0.34.7)硬化的是官方工具**内部**的门(退出码/图标门/验收全量),但只在模型**用了**官方工具时才生效;本次证明模型可完全另起平行管线,内部门无从触发。改动(经用户拍板**只走文档层**、平台层自动检测暂缓):SKILL.md 阶段五加「🛑 导出唯一入口=官方 `svg_to_pptx.py`,默认原生可编辑、纯 Python 无需任何外部渲染器,'渲染器没装'永不是自搓借口」;反模式加「绕开官方管线自搓 SVG→PPTX 导出器 → 一叠不可编辑贴图、价值作废」。**注:仅改 skill 文档,不改线上跑法/官方脚本行为。** 已知残留风险:纯文档约束对'完全无视 skill'的弱模型拦截力有限,真正治本需平台层在 pptx 交付/预览路径自动检测整页贴图(本次未做)。
diff --git a/SKILL_LIST.md b/SKILL_LIST.md
index 593cea0..fca0832 100644
--- a/SKILL_LIST.md
+++ b/SKILL_LIST.md
@@ -1,7 +1,7 @@
# zcbot Skill 清单
服务对象:中国建筑材料科学研究总院 —— 无机非金属材料 R&D(水泥 / 混凝土 / 玻璃 / 陶瓷 / 耐火 / 新型建材)
-最后更新:2026-06-29(ppt skill 重构为 SVG-first,移植自 ppt-master)
+最后更新:2026-07-02(ppt skill 加渲图验收闭环 + 导出验收硬门 + 几何质检)
Skill 总数:17
zcbot 的"skill"是一份可加载的工作流脚本(`skills//SKILL.md` + 配套 templates / scripts / Python helper),模型在识别用户意图后挂载对应 skill,按其内置的阶段化流程产出可交付物。本文档面向**使用方 / 协作方**,按"做什么、什么时候用、什么时候别用、典型产物"组织。
@@ -170,7 +170,7 @@ zcbot 的"skill"是一份可加载的工作流脚本(`skills//SKILL.md` +
### ppt
**生成可编辑 PowerPoint 演示文稿 (.pptx)。SVG-first 路线。**
-把材料(汇报草稿 / 项目方案 / 调研报告)变成可演示、**可编辑**的 .pptx。流程:**素材摄取 → 八条对齐 + 逐页大纲(spec)→ [配图] → 逐页手写 SVG → SVG 质检 → 后处理 → 导出 PPTX → 渲图验收**。核心是 AI 把每页当**矢量设计稿手写成 SVG**(设计自由度=浏览器级),再由纯 Python 转换器逐元素译成**原生 DrawingML**(形状/文本/渐变都能在 PowerPoint 里选中改)——告别 python-pptx 固定版式件的单调与 AI 味。
+把材料(汇报草稿 / 项目方案 / 调研报告)变成可演示、**可编辑**的 .pptx。流程:**素材摄取 → 八条对齐 + 逐页大纲(spec)→ [配图] → 逐页手写 SVG → SVG 质检 → 后处理 → 全量渲图验收 → 导出 PPTX**(导出边界硬门:每页都要渲图过目、标记 pass 且此后源未改动,否则拒绝产出 pptx)。核心是 AI 把每页当**矢量设计稿手写成 SVG**(设计自由度=浏览器级),再由纯 Python 转换器逐元素译成**原生 DrawingML**(形状/文本/渐变都能在 PowerPoint 里选中改)——告别 python-pptx 固定版式件的单调与 AI 味。
**触发**:
- ✅ 用户明确点名 PPT / 幻灯片 / 演示文稿 / .pptx / slide / deck
@@ -186,8 +186,8 @@ zcbot 的"skill"是一份可加载的工作流脚本(`skills//SKILL.md` +
- **19 种视觉风格 + 5 种叙事骨架**:editorial / swiss-minimal / glassmorphism / dark-tech / data-journalism… × pyramid / narrative / instructional / showcase / briefing —— 去 AI 味的关键
- **模板库**:layouts(版式)/ decks(整套:中汽研/招商银行/重庆大学等)/ brands(品牌)/ charts(71 个图表信息图)/ icons(5 套共 1.1w+ 图标,finalize 自动内嵌)
- **逐页节奏纪律**:论断式标题、page_rhythm(anchor/dense/breathing,breathing 页禁卡片墙)、内容→版式映射、图文版式 72 式
-- **SVG 质检** `svg_quality_checker.py`:禁用特性 / viewBox / spec_lock 漂移 / 配色越界(error 必改,回写 SVG)
-- **渲图验收** `svg_preview.py`:无头 Chrome 把 SVG 渲成 PNG 肉眼/vision 验版面;`update_spec.py` 一键改色/字体传播到所有 SVG
+- **SVG 质检** `svg_quality_checker.py`:禁用特性 / viewBox / spec_lock 漂移 / 配色越界 / **几何检测**(文本·图标包围盒估算,拦大字压说明、图标压字、行溢出画布)(error 必改,回写 SVG)
+- **渲图验收闭环** `svg_preview.py` + `accept_pages.py`:无头 Chrome 全量渲 PNG 肉眼/vision 验版面,逐页标 pass/fail 落 `.build/acceptance.json`;**导出 gate 只认"渲过 + 看过标 pass + 渲后源未改(sha1)"**,跳验收/盲改混不过去;`update_spec.py` 一键改色/字体传播到所有 SVG
- AI 配图走 imagegen skill;markitdown 素材摄取
**典型产物**:`exports/_.pptx`(原生可编辑)+ `svg_output/*.svg`(逐页设计源,改稿对象)+ `design_spec.md`/`spec_lock.md`。
diff --git a/core/__init__.py b/core/__init__.py
index 13da7e7..7b00afb 100644
--- a/core/__init__.py
+++ b/core/__init__.py
@@ -1,3 +1,3 @@
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
# 改版本只动这一行。
-__version__ = "0.35.1"
+__version__ = "0.36.0"
diff --git a/skills/ppt/SKILL.md b/skills/ppt/SKILL.md
index b5f4837..c6c78b7 100644
--- a/skills/ppt/SKILL.md
+++ b/skills/ppt/SKILL.md
@@ -7,11 +7,11 @@ description: 生成 PowerPoint 演示文稿 (.pptx) 文件。✅ 触发:用户
把材料变成**可演示、可编辑**的 .pptx。
-**核心管线**:`素材 → 策略(spec)→ [配图] → 执行(逐页手写 SVG)→ SVG 质检 → 后处理 → 导出 PPTX → 渲图验收`
+**核心管线**:`素材 → 策略(spec)→ [配图] → 执行(逐页手写 SVG)→ SVG 质检 → 后处理 → 渲图验收 → 导出 PPTX`(验收在导出**之前**;导出边界有硬门,没验收过的 deck 拒绝产出 pptx)
> **为什么是 SVG**:不再用 python-pptx 拼固定版式件(那是版面单调/AI 味的天花板)。AI 把每页当**矢量设计稿手写成 SVG**(设计自由度 = 浏览器级),再由纯 Python 转换器逐元素译成**原生可编辑的 DrawingML**(形状/文本/渐变都能在 PowerPoint 里选中改)。SVG 与 DrawingML 是同一套"绝对坐标 2D 矢量"世界观的两种方言,转换是翻译而非格式硬凑。详见 `references/shared-standards.md`。
-> 进度展示:多页 deck 用 `task_progress` 标记「摄取素材 / 八条对齐 + 逐页大纲 / [配图] / 逐页 SVG / 质检 / 导出 + 验收」等关键阶段;不要把每页内部写入都当进度步骤。
+> 进度展示:多页 deck 用 `task_progress` 标记「摄取素材 / 八条对齐 + 逐页大纲 / [配图] / 逐页 SVG / 质检 / 渲图验收 / 导出」等关键阶段;不要把每页内部写入都当进度步骤。
## 资源
@@ -21,7 +21,8 @@ description: 生成 PowerPoint 演示文稿 (.pptx) 文件。✅ 触发:用户
- `svg_to_pptx.py` —— **SVG → 原生 PPTX**(逐元素译 DrawingML;默认嵌演讲者备注 + Office 兼容 PNG 兜底)
- `total_md_split.py` —— 把 `notes/total.md` 拆成逐页备注(导出前跑)
- `update_spec.py` —— 改 `spec_lock.md` 的颜色/字体后,**一键传播到所有已生成 SVG**(改稿用)
-- `svg_preview.py` —— **无头 Chrome 把 SVG 渲成 PNG** 供肉眼/vision 验收(SVG 是视觉真相;**替代**了浏览器 live preview)
+- `svg_preview.py` —— **无头 Chrome 把 SVG 渲成 PNG** 供肉眼/vision 验收(SVG 是视觉真相;**替代**了浏览器 live preview);渲 project 目录时同步登记 `.build/acceptance.json` 验收记录(每页源 sha1 + verdict)
+- `accept_pages.py` —— 看完 PNG 后**标记每页验收结论**(`--pass`/`--pass-all`/`--fail --reason`);标 pass 要求"渲过图 + 渲后源没改",导出 gate 只认 pass 页
- `project_utils.py` / `error_helper.py` —— 引擎辅助(canvas 校验 / 友好报错),被上面脚本 import,不直接调
**设计知识(references/,先读相关的,不默写)**:
@@ -59,6 +60,7 @@ description: 生成 PowerPoint 演示文稿 (.pptx) 文件。✅ 触发:用户
└── .build/ # 可再生构建产物(dotfile 隐藏、随时可删;用户文件列表看不到)
├── svg_final/ # finalize 产出(图标/配图已内嵌,自包含;供 legacy 导出 + 忠实预览)
├── preview/ # svg_preview 渲的验收 PNG
+ ├── acceptance.json # 渲图验收记录(每页源 sha1 + verdict;导出 gate 依据)
└── backup/latest/svg_output/ # SVG 源快照(只留最新一份,可不跑模型重新导出)
```
@@ -140,40 +142,45 @@ references/visual-styles/.md # 锁定的视觉风格
```
.venv/Scripts/python.exe /scripts/svg_quality_checker.py
```
-- **任何 `error`(禁用特性 / viewBox 不符 / spec_lock 漂移 / **锁了图标 inventory 却全 deck 0 图标** / **内容 deck 全是文字方块(≥6 页且零 ``/``/``/``)** 等)必须改:回阶段三重写该页再跑**,不放过。
-- `warning`(低分辨率图 / 非 PPT 安全字体等):能顺手改就改,否则知会后放行。
+- **任何 `error`(禁用特性 / viewBox 不符 / spec_lock 漂移 / **图标压在文字上、文字基线超出画布**(Geometry 检测,几何精确)/ **锁了图标 inventory 却全 deck 0 图标** / **内容 deck 全是文字方块(≥6 页且零 ``/``/``/``)** 等)必须改:回阶段三重写该页再跑**,不放过。
+- `warning`(低分辨率图 / 非 PPT 安全字体等):能顺手改就改,否则知会后放行。**例外:`Geometry:` 开头的文字重叠 warning 不许无视** —— 它给了精确坐标,是"大字压说明 / 同行文字互侵"的高嫌疑点(估宽无法区分擦边与压字,所以只报 warn),阶段五渲图时**必须对着该页该坐标专门看**,压了就返工。
- 跑 `svg_output/`(不要在 finalize 后跑 —— finalize 改写 SVG 会掩盖源级违规)。
- ⚠️ **别用 `| head` / `| tail` 截断质检输出**:管道会把脚本的非零退出码换成 `head` 的 0(门形同虚设),`head` 还会截掉打在**最后**的 deck 级门结论(如零图标 `[ERROR]`)。原样跑,读完整输出、认它的退出码。
-## 阶段五:后处理 + 导出
+## 阶段五:后处理 + 渲图验收(强制门)—— 全量,不抽查
⚠️ 三步**一步步来**,别合并成一条命令:
```
-# 5.1 拆备注
-.venv/Scripts/python.exe /scripts/total_md_split.py
-# 5.2 SVG 后处理(图标/配图内嵌 / 文本展平 / 圆角转 path)
+# 5.1 SVG 后处理(图标/配图内嵌 / 文本展平 / 圆角转 path)
.venv/Scripts/python.exe /scripts/finalize_svg.py
-# 5.3 导出原生 PPTX(默认嵌备注 + Office 兼容 PNG 兜底)
+# 5.2 全量渲图(渲 .build/svg_final,同步登记 .build/acceptance.json 验收记录)
+.venv/Scripts/python.exe /scripts/svg_preview.py
+# 5.3 read/look_at_image 逐页过目后,标记验收结论
+.venv/Scripts/python.exe /scripts/accept_pages.py --pass-all
+# (有问题的页:--fail <页名> --reason "…";只标部分页:--pass <页名…>;看状态:--status)
+```
+- **默认渲整本,不带 `--pages`**。抽查 3 页只能覆盖 3 页,错位/文字溢出/元素重叠恰恰藏在没看的那些页里 —— 逐页手写绝对坐标,每页都可能翻车,所以**每页都要过目**。(页数多时可分批渲,但目标是 100% 覆盖,不是采样。)
+- `read` / `look_at_image` **逐页**亲眼过:标题层级、卡片过挤/过空、**文字是否溢出卡片/被裁**、**元素是否重叠错位**、图标在不在(位置对不对)、节奏是否单调、配图位置。**看完才许标 pass** —— `--pass-all` 是"每页都看过且都合格"的宣告,不是跳过看的快捷键。
+- 🚧 **差评即阻断 + 返工回路**:任一页有排版/溢出/重叠/半成品问题(哪怕只是封面)→ **改那一页 svg_output 的 SVG → 重跑 finalize → `svg_preview.py --pages ` 重渲该页 → 复看 → 再标 pass**。机制会强制这个回路:标 pass 和导出 gate 都校验"渲图之后源文件没再改过"(sha1),改了不重渲重看,gate 过不去。不许"看了一页差评、跳去看下一页好评就收尾"——那正是错位交付的来路。
+- ❌ **禁止盲改**:修错位/补图标不许写脚本批量 regex 插元素、改完不看渲染结果(真实事故来源:质检提示缺图标后 regex 批量盲插,图标全压在文字上交付)。每处修改都要走上面的返工回路落到"复看"。
+
+> svg_preview 渲的是 SVG(视觉真相,与导出的 pptx 1:1),比渲最终 pptx 更早更准暴露观感问题。需要校验"SVG→DrawingML 转换是否保真",再开导出的 pptx 在 PowerPoint 里看。
+
+## 阶段六:导出
+
+```
+# 6.1 拆备注
+.venv/Scripts/python.exe /scripts/total_md_split.py
+# 6.2 导出原生 PPTX(默认嵌备注 + Office 兼容 PNG 兜底)
.venv/Scripts/python.exe /scripts/svg_to_pptx.py
# 产物:exports/_.pptx(原生,读 svg_output/)+ .build/backup/latest/svg_output/(源快照,只留最新)
```
+- 🚧 **导出边界验收门(硬)**:spec_lock 存在时,**每页都必须 verdict=pass 且渲图后源未改动**,否则导出 `[ERROR]` 退非零、不产出 pptx(`| head` 绕不过)。被拒就回阶段五补验收/走返工回路;`--allow-unreviewed` 只留给"确实不需要视觉验收"的极端场景,**不是跳过验收的捷径**。
+- 🚧 **导出边界图标门(硬)**:spec_lock 锁了 `icons.library` + 非空 `inventory` 但全 deck 零 `