dsh-ppt-studio 1.1.0
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/LICENSE +21 -0
- package/README.md +480 -0
- package/agent-presets/ppt/agent.cordis.yml +308 -0
- package/agent-presets/ppt/preset.yml +3 -0
- package/cordis.patch.yml +29 -0
- package/docs/00-/350/256/276/350/256/241/346/272/220.md +537 -0
- package/docs/01-/351/234/200/346/261/202/344/270/216/347/233/256/346/240/207.md +136 -0
- package/docs/02-/346/212/200/346/234/257/346/212/245/345/221/212.md +483 -0
- package/docs/03-/346/233/264/346/226/260/346/227/245/345/277/227.md +2898 -0
- package/docs/04-/350/267/257/347/272/277/345/233/276/344/270/216/351/207/214/347/250/213/347/242/221.md +113 -0
- package/docs/05-/350/277/255/344/273/243/346/265/201/347/250/213.md +210 -0
- package/docs/06-/350/257/204/345/256/241/344/270/216/346/265/213/350/257/225.md +296 -0
- package/docs/fidelity-report.md +225 -0
- package/examples/fx/fx-mini/deck.yaml +39 -0
- package/examples/fx/fx-mini/pages/01.yaml +6 -0
- package/examples/fx/fx-mini/pages/02.yaml +2 -0
- package/examples/fx/fx-mini/pages/03.yaml +6 -0
- package/examples/fx/fx-pro/deck.yaml +48 -0
- package/examples/fx/fx-pro/pages/01_cover.yaml +40 -0
- package/examples/fx/fx-pro/pages/02_arch.yaml +55 -0
- package/examples/fx/fx-pro/pages/03_table.yaml +20 -0
- package/examples/fx/fx-pro/pages/04_chart.yaml +40 -0
- package/examples/fx/fx-pro/pages/05_timeline.yaml +71 -0
- package/examples/fx/fx-pro/pages/06_longtext.yaml +13 -0
- package/examples/fx/fx-pro/pages/07_safearea.yaml +33 -0
- package/examples/fx/fx-pro/pages/08_contrast.yaml +22 -0
- package/examples/fx/fx-pro/pages/09_custgeom.yaml +32 -0
- package/examples/fx/fx-pro/pages/10_numbers.yaml +25 -0
- package/examples/fx/fx-pro/pages/11_section.yaml +20 -0
- package/examples/fx/fx-pro/pages/12_summary.yaml +57 -0
- package/examples/fx/fx-pro/preview/01_pages_01_cover.html +1 -0
- package/examples/fx/fx-pro/preview/02_pages_02_arch.html +3 -0
- package/examples/fx/fx-pro/preview/03_pages_03_table.html +1 -0
- package/examples/fx/fx-pro/preview/04_pages_04_chart.html +3 -0
- package/examples/fx/fx-pro/preview/05_pages_05_timeline.html +2 -0
- package/examples/fx/fx-pro/preview/06_pages_06_longtext.html +4 -0
- package/examples/fx/fx-pro/preview/07_pages_07_safearea.html +2 -0
- package/examples/fx/fx-pro/preview/08_pages_08_contrast.html +1 -0
- package/examples/fx/fx-pro/preview/09_pages_09_custgeom.html +1 -0
- package/examples/fx/fx-pro/preview/10_pages_10_numbers.html +1 -0
- package/examples/fx/fx-pro/preview/11_pages_11_section.html +1 -0
- package/examples/fx/fx-pro/preview/12_pages_12_summary.html +1 -0
- package/examples/fx/fx-pro/preview/deck.html +10 -0
- package/examples/fx/fx-pro/preview/layout.json +2325 -0
- package/examples/fx/fx-pro/preview/measured.json +814 -0
- package/examples/fx/fx-pro.pptx +0 -0
- package/examples/fx/ren-single/01.png +0 -0
- package/examples/fx/ren-spliced/01.png +0 -0
- package/examples/fx/ren-spliced/03.png +0 -0
- package/examples/fx/seed.pptx +0 -0
- package/examples/fx/single.pptx +0 -0
- package/examples/fx/spliced.pptx +0 -0
- package/examples/smoke/deck.yaml +26 -0
- package/examples/smoke/imported/deck.yaml +30 -0
- package/examples/smoke/imported/import-styles.json +126 -0
- package/examples/smoke/imported/pages/slide_01.yaml +31 -0
- package/examples/smoke/imported/pages/slide_02.yaml +38 -0
- package/examples/smoke/imported/pages/slide_03.yaml +55 -0
- package/examples/smoke/imported/preview-rt/01_pages_slide_01.html +1 -0
- package/examples/smoke/imported/preview-rt/02_pages_slide_02.html +1 -0
- package/examples/smoke/imported/preview-rt/03_pages_slide_03.html +1 -0
- package/examples/smoke/imported/preview-rt/deck.html +1 -0
- package/examples/smoke/imported/preview-rt/layout.json +434 -0
- package/examples/smoke/imported/reference/previews/01.png +0 -0
- package/examples/smoke/imported/reference/previews/02.png +0 -0
- package/examples/smoke/imported/reference/previews/03.png +0 -0
- package/examples/smoke/imported/source.pptx +0 -0
- package/examples/smoke/media-test.png +0 -0
- package/examples/smoke/out-smoke.pptx +0 -0
- package/examples/smoke/pages/01_cover.yaml +27 -0
- package/examples/smoke/pages/02_content.yaml +42 -0
- package/examples/smoke/pages/03_chart.yaml +35 -0
- package/examples/smoke/preview/01_pages_01_cover.html +1 -0
- package/examples/smoke/preview/02_pages_02_content.html +1 -0
- package/examples/smoke/preview/03_pages_03_chart.html +1 -0
- package/examples/smoke/preview/deck.html +1 -0
- package/examples/smoke/preview/layout.json +378 -0
- package/lib/autodeclare.js +40 -0
- package/lib/commands.js +161 -0
- package/lib/crosscheck.js +300 -0
- package/lib/imgmeta.js +55 -0
- package/lib/index.js +239 -0
- package/lib/measurement.js +209 -0
- package/lib/msrender.js +123 -0
- package/lib/pptd/export-pptx.js +686 -0
- package/lib/pptd/import-pptx.js +774 -0
- package/lib/pptd/layout.js +209 -0
- package/lib/pptd/render-html.js +254 -0
- package/lib/pptd/schema.js +518 -0
- package/lib/pptd/svgCharts.js +147 -0
- package/lib/pptxPy.js +205 -0
- package/lib/preset-delivery.js +195 -0
- package/lib/preview-server.js +164 -0
- package/lib/router.js +165 -0
- package/lib/scaffold.js +315 -0
- package/lib/skill.js +174 -0
- package/lib/splice.js +219 -0
- package/lib/state.js +88 -0
- package/lib/surgical.js +369 -0
- package/lib/templates.js +400 -0
- package/lib/tools.js +955 -0
- package/lib/verify.js +828 -0
- package/lib/xmljs.js +112 -0
- package/lib/zips.js +117 -0
- package/package.json +81 -0
- package/scripts/audit-manual-facts.mjs +294 -0
- package/scripts/audit-template-fidelity.mjs +162 -0
- package/scripts/bench-m2.mjs +48 -0
- package/scripts/build-templates.mjs +63 -0
- package/scripts/build.mjs +14 -0
- package/scripts/build.sh +7 -0
- package/scripts/check-lib-freshness.mjs +70 -0
- package/scripts/check-preset.mjs +189 -0
- package/scripts/check-tracked-lf.mjs +84 -0
- package/scripts/dbg-import.mjs +13 -0
- package/scripts/dbg-media.mjs +10 -0
- package/scripts/dbg-tpl-inventory.mjs +23 -0
- package/scripts/dump-workflow-baseline.mjs +38 -0
- package/scripts/e2e-1.0.mjs +129 -0
- package/scripts/eval-skills-blind.mjs +91 -0
- package/scripts/eval-skills-score.mjs +129 -0
- package/scripts/export-orig-templates.ps1 +17 -0
- package/scripts/fixtures/workflow-baseline.json +300 -0
- package/scripts/fixtures.mjs +501 -0
- package/scripts/import-user-templates.mjs +60 -0
- package/scripts/install-desktop.mjs +312 -0
- package/scripts/install.mjs +209 -0
- package/scripts/migrate-templates-dualtrack.mjs +71 -0
- package/scripts/preflight-1.0.mjs +198 -0
- package/scripts/probe-asset-freshness.mjs +57 -0
- package/scripts/probe-effect-semantics.mjs +69 -0
- package/scripts/probe-reparse-safety.mjs +32 -0
- package/scripts/rebuild-templates-c11.mjs +69 -0
- package/scripts/regression-real.mjs +84 -0
- package/scripts/release-sync.mjs +425 -0
- package/scripts/shot-templates.mjs +49 -0
- package/scripts/smoke.mjs +2512 -0
- package/scripts/verify-bundle-install.mjs +281 -0
- package/scripts/verify-fresh-install.mjs +260 -0
- package/skills/ppt-studio-copy/SKILL.md +139 -0
- package/skills/ppt-studio-craft/SKILL.md +171 -0
- package/skills/ppt-studio-data/SKILL.md +173 -0
- package/skills/ppt-studio-manual/SKILL.md +104 -0
- package/templates/academic-white/deck.yaml +24 -0
- package/templates/academic-white/pages/_chart.yaml +43 -0
- package/templates/academic-white/pages/_compare.yaml +37 -0
- package/templates/academic-white/pages/_content.yaml +35 -0
- package/templates/academic-white/pages/_cover.yaml +28 -0
- package/templates/academic-white/pages/_ending.yaml +28 -0
- package/templates/academic-white/pages/_flow.yaml +61 -0
- package/templates/academic-white/preview.png +0 -0
- package/templates/academic-white/template.yaml +14 -0
- package/templates/business-blue/deck.yaml +28 -0
- package/templates/business-blue/pages/_chart.yaml +36 -0
- package/templates/business-blue/pages/_compare.yaml +45 -0
- package/templates/business-blue/pages/_content.yaml +80 -0
- package/templates/business-blue/pages/_cover.yaml +39 -0
- package/templates/business-blue/pages/_ending.yaml +32 -0
- package/templates/business-blue/pages/_flow.yaml +87 -0
- package/templates/business-blue/preview.png +0 -0
- package/templates/business-blue/template.yaml +14 -0
- package/templates/pitch-bold/deck.yaml +23 -0
- package/templates/pitch-bold/pages/_chart.yaml +32 -0
- package/templates/pitch-bold/pages/_compare.yaml +36 -0
- package/templates/pitch-bold/pages/_content.yaml +58 -0
- package/templates/pitch-bold/pages/_cover.yaml +31 -0
- package/templates/pitch-bold/pages/_ending.yaml +27 -0
- package/templates/pitch-bold/pages/_flow.yaml +46 -0
- package/templates/pitch-bold/preview.png +0 -0
- package/templates/pitch-bold/template.yaml +14 -0
- package/templates/tech-dark/deck.yaml +24 -0
- package/templates/tech-dark/pages/_chart.yaml +27 -0
- package/templates/tech-dark/pages/_compare.yaml +38 -0
- package/templates/tech-dark/pages/_content.yaml +51 -0
- package/templates/tech-dark/pages/_cover.yaml +30 -0
- package/templates/tech-dark/pages/_ending.yaml +25 -0
- package/templates/tech-dark/pages/_flow.yaml +80 -0
- package/templates/tech-dark/preview.png +0 -0
- package/templates/tech-dark/template.yaml +14 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# 02 · 技术报告(实现级参考 · 随源码同步更新)
|
|
2
|
+
|
|
3
|
+
> 读者:迭代者/审计者。目标:看完本文件能定位"某个行为在哪段代码、为什么这么设计、改动会波及哪里"。
|
|
4
|
+
> 代码事实基于 `src/`(**持续更新到当前提交**——本文件里带日期的条目即各轮改动;早期标题里的"v0.3.0"只是当时的起点);构建 = `node scripts/build.mjs`(src → lib 纯复制,无编译)。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. 系统总览
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
用户消息 ──► ppt-studio 插件(host)
|
|
12
|
+
├─ router.js 语义路由 + 工作流提示词注入(system-prompt/assemble)
|
|
13
|
+
├─ commands.js /ppt 命令面(档位/引擎/质量/pause-after)
|
|
14
|
+
├─ tools.js 模型工具面(ppt_check/render/shot/verify/export/import/status/media/schema/new/state/template_*/visual)
|
|
15
|
+
├─ state.js 会话级 ~/.dsh/ppt-studio/session-<id>.json + 项目级 state.json
|
|
16
|
+
├─ templates.js 模板库(双轨:YAML 骨架层 + template.pptx 真相层 + styleAudit)
|
|
17
|
+
├─ msrender.js Office 真渲染通道(PowerPoint COM → 整页 PNG;无 Office 自动隐藏)
|
|
18
|
+
├─ preview-server.js 对话内预览(webServer /ppt-preview 静态服务)
|
|
19
|
+
│
|
|
20
|
+
└─ pptd 中间层(唯一事实源:deck.yaml + pages/*.yaml + media/)
|
|
21
|
+
├─ schema.js 结构校验 + normalize(resolveDeck / validateDeck / validatePage)
|
|
22
|
+
├─ layout.js 布局数学:文本度量估算 + 元素归一化(三端共用)
|
|
23
|
+
├─ render-html.js → preview/*.html + deck.html + layout.json(数字审阅数据源)
|
|
24
|
+
├─ verify.js 断言引擎(overlap/出界/溢出/对齐/密集区)+ 美学建议 + collectDeclarable
|
|
25
|
+
├─ autodeclare.js 一键声明(warning 级重叠 → expectedOverlaps,保留注释)
|
|
26
|
+
├─ export-pptx.js → .pptx(主引擎,矢量拼绘:shape/text/line/image/table/chart)
|
|
27
|
+
├─ import-pptx.js ← .pptx → deck 项目(内容+近似版式)
|
|
28
|
+
├─ svgCharts.js 图表 → SVG(渲染用)
|
|
29
|
+
├─ zips.js zip 读写(deflate 8 + crc32)| xmljs.js 迷你 XML 解析器
|
|
30
|
+
├─ imgmeta.js 图片物理规格(纯解析,无依赖)
|
|
31
|
+
└─ pptxPy.js python-pptx 兜底引擎(subprocess 脚本)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
依赖:仅 `yaml`(npm);零运行时其他依赖;浏览器截图用系统 Edge/Chrome(headless,临时 profile)。
|
|
35
|
+
|
|
36
|
+
## 2. 模块明细
|
|
37
|
+
|
|
38
|
+
### 2.1 schema.js — 结构与归一化的权威
|
|
39
|
+
- 校验(所有错误一次性收集,PptError.messages 数组):
|
|
40
|
+
- deck:version/1、title、size([w,h] 或对象)、theme.colors(hex 或 `$ref`)、theme.textStyles、`theme.safeArea`、`theme.minFontSize`。
|
|
41
|
+
- page:elements(elementId 唯一、elementType ∈ text|shape|line|image|table|chart、bounds、role、各类型专属字段)、`expectedOverlaps`、`overlapMode`、`background`(hex/`$ref`/{solid}/{image})、`safeArea`。
|
|
42
|
+
- **line 的 bounds 可省略**(v0.3,D1):校验放行,normalizePage 用 points AABB 推导。
|
|
43
|
+
- `resolveDeck(dir)`:读取 → 校验 → 返回 `{dir, deck, size(默认960×540), theme, colors, textStyles, minFontSize(默认12), safeAreaOf(page), resolveColor, styleOf, pages:[{file,ref,page,name,index}]}`。**读时快照**——页面 yaml 被外部改写后必须重新 resolveDeck(autoDeclare 流程已如此做,见 2.6)。
|
|
44
|
+
- `styleOf`:`style: "$name"` 引用 theme.textStyles + 页面内联字段覆盖(合并顺序:ref → own)。
|
|
45
|
+
|
|
46
|
+
### 2.2 layout.js — 三端共享度量(渲染/校验/导出)
|
|
47
|
+
- `charWidth`:CJK 1em / 空格 0.4em / 数字 0.6em / Latin 0.6em(bold 0.63)/ 全角标点 1em / 其他 0.65em(v0.3 偏宽保守档)。
|
|
48
|
+
- `wrapLines`:CJK 字符级 token + Latin/数字词级 + **标点禁则**(行首不落 `,。、!?;:)》」…`;行尾不落 `(《【「`)。
|
|
49
|
+
- `measureText` / `textMetricsOf`:输出 textW/textH/lines/**lineWidths**/overflowX/overflowY。**overflowY = round(高 − 容器高),>1px 即溢出**(统一阈值 FIT_TOL=1,见 export)。
|
|
50
|
+
- `normalizePage`:页面元素 → 统一快照对象(id/type/bounds/role + 类型专属字段 + text metrics)。**三端(render/verify/export)共用** → 度量一致是"verify 通过 ⇒ 导出不缩字"的根基。
|
|
51
|
+
|
|
52
|
+
### 2.3 render-html.js — 预览 + layout.json
|
|
53
|
+
- 每页 → `preview/<NN>_<slug>.html`(单页,含内联页面背景)+ `preview/deck.html`(整览,共享 style + 各页内联背景;修复过"拼合丢样式"缺陷)。
|
|
54
|
+
- `layout.json` = **数字审阅的唯一数据源**:`{size, theme{colors,textStyles,grid,safeArea,minFontSize}, pages:[{index,file,name,background,safeArea,expectedOverlaps,overlapMode,elements:[snap]}]}`。
|
|
55
|
+
- snap 记录每元素 id/kind/bounds/role + 专属字段(text 含 style+metrics+text;shape 含 fill/rotation;line 含 points…
|
|
56
|
+
**chart 从 2026-09-14 起含 `colors`**——verify 的"图表显式配色 vs theme.colors"建议必须看得到它,否则 layout.json 里只有 chartType、那条建议永不触发)。
|
|
57
|
+
- `debug=true`:文本描边 + **安全区参考框**(`.sa-guide` 蓝色虚线,v0.3)。
|
|
58
|
+
|
|
59
|
+
### 2.4 verify.js — 断言引擎与美学层
|
|
60
|
+
`analyzePage(page, size)` 产出 findings(severity: error/warning/confirmed/suggestion):
|
|
61
|
+
|
|
62
|
+
| 断言 | 级别 | 说明 |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| out-of-page(页面边界) | ERROR | 超页面边界(放映不可见)——**永远不可声明**(出血设计见 docs/01 C11 待议)|
|
|
65
|
+
| out-of-page(安全区) | ERROR / confirmed | 超 safeArea 但页面内 → **声明制**:命中 `expectedOutOfSafeArea` → confirmed(✓ 预期出界),未命中 → ERROR(提示加入声明)|
|
|
66
|
+
| text-overflow | ERROR | wrap 看垂直(overflowY>1)/ wrap=false 看水平 |
|
|
67
|
+
| content-collision | ERROR | 内容面×内容面(text/table/chart),**不可声明豁免** |
|
|
68
|
+
| unexpected-overlap | ERROR | 警告级重叠未命中 expectedOverlaps(声明制) |
|
|
69
|
+
| expected-overlap | confirmed | 命中声明 → ✓ 不出现在错误/警告 |
|
|
70
|
+
| overlap(warning) | warning | line×任意(引线/箭头)与 内容×承载(shape/image 作底板)|
|
|
71
|
+
| near-align / hotspot / density | warning | 对齐启发式/密集区/密度 |
|
|
72
|
+
| aesthetic-* | suggestion | 8+3 条:重心/外边距/列数/间距节奏/网格/字号层级/主题颜色/文本密度 + **对比度**(WCAG,D6)+ **孤字**(末行 <1.5×fontSize,D7)+ **背景图无 safeArea 提示**(D5)|
|
|
73
|
+
|
|
74
|
+
**主题颜色一致性(第 7 条)的边界(2026-09-14 补)**:文本色 / 形状 fill / 线色都必须 ∈ `theme.colors` 或中性灰(strict 档为 ERROR,见 `themeConformance`);
|
|
75
|
+
**图表配色刻意只做建议级**——`chart.colors` **显式**写了主题外颜色 → 一条 `[·] aesthetic-theme`(消息里点明"含图表显式配色");
|
|
76
|
+
**不写 `colors`(用内置调色板)不提示**,因为那是既有行为,提示只会给所有带图的老工程加噪声。
|
|
77
|
+
刻意**不**把图表配色纳入门禁:门禁化(改 `fillColorsOf`/`themeConformance`)会给既有工程与模板凭空新增 ERROR,
|
|
78
|
+
且 `theme-conformance` 不可声明 —— 那是"让老用户变差"。要改必须先设计"图表调色板如何纳入主题"(docs/04 待议)。
|
|
79
|
+
|
|
80
|
+
> 声明维度(v0.3.2 统一为"空间断言=设计意图声明制"):重叠 = `expectedOverlaps`(可一键 autoDeclare);安全区出界 = `expectedOutOfSafeArea`(**必须手工**,id 防呆);页面出界 = 绝对(C11 待议)。
|
|
81
|
+
|
|
82
|
+
角色推断 `roleOf`:显式 role 覆盖;默认 text/table/chart=content、shape/image=background、line=line;`decoration` 完全豁免 overlap 报告。
|
|
83
|
+
`collectDeclarable(page, size)`:收集"警告级未声明重叠对"(供 autoDeclare;不含 content-collision、不含已声明、不含 decoration)。
|
|
84
|
+
|
|
85
|
+
### 2.5 layout.json 语义(三端契约)
|
|
86
|
+
- 渲染器写、verify 消费、**可选**与导出 .pptx XML 做产物快照交叉验证(tools.js `snapshotPptxShapes`:读 out.pptx zip,数 spTree 顶层形状——仅一致性提示,不算门禁)。
|
|
87
|
+
- 警告:**pptx 产物快照不是布局断言**(导出几何 = 中间层几何,因导出器与校验用同一 normalizePage;真正的"产物档"增强待 R2)。
|
|
88
|
+
|
|
89
|
+
### 2.6 autodeclare.js — 一键声明(D2)
|
|
90
|
+
`applyAutoDeclare(ctx, layout)`:逐页 collectDeclarable → `YAML.parseDocument` **只 setIn expectedOverlaps**(保留页面其余注释,规避 C3-safe_dump 全量重排问题)→ 写回 → 返回 `[{page, added, file}]`。
|
|
91
|
+
**调用方必须重新 resolveDeck 再渲染**(resolveDeck 是读时快照)——tools.js 已如此;smoke 也如此断言。
|
|
92
|
+
**yaml@2 读值教训(v0.14.6 反馈 A)**:`doc.get()` 返回 AST 节点(YAMLSeq 无 .map;`node.toJS()` 裸调用抛 "A document argument is required")——读既有声明一律 **`doc.toJS()`**(Document 级,纯 JS 值),写仍走 `doc.setIn`;`templates.js` 同款(expectedOutOfSafeArea)一致处理。
|
|
93
|
+
|
|
94
|
+
### 2.7 export-pptx.js — 主引擎(pptd)
|
|
95
|
+
- 手写最小 OOXML:content-types / rels / presentation / slideMaster / slideLayout / theme / slides;1px=1pt,EMU=pt×12700。
|
|
96
|
+
- 元素映射:text→`p:sp`(txBody,auto-fit)、shape→`p:sp`(rect/roundRect/ellipse/triangle + rotation)、line→`p:cxnSp`(straightConnector1 + tailEnd 三角箭头)、image→`p:pic`(blipFill 嵌入)、table→`graphicFrame a:tbl`、chart→**矢量拼绘**(bar=矩形、line=连接线+圆点、pie=饼形 `prst=pie` 带 adj1/adj2)。
|
|
97
|
+
- **连线编码三件事(2026-09-14 两轮修复,都在这一层)**:
|
|
98
|
+
① **方向**:`straightConnector1` 在 OOXML 里只画包围盒左上→右下,真实走向必须靠 `flipH`/`flipV`(p2 在 p1 左侧→flipH;上方→flipV);漏写会镜像斜率、并让 × 的两笔重合成一条。
|
|
99
|
+
② **箭头**:`<a:tailEnd>` 必须写在 `<a:ln>` **内部**(写在 `spPr` 里 PowerPoint 静默忽略,预览却有——预览是 render-html 自己画 marker)。
|
|
100
|
+
③ **包围盒精确等于线段跨度**:不能 `max(1,…)` 兜底,否则水平线 `cy=1pt` 会被画成"12pt×1pt 的对角线"=假斜线(`cy=0`/`cx=0` 合法,Office 自己也这么写);只有零长线给 1×1 兜底。
|
|
101
|
+
**自证**:`connectorEndsFromXml()` 从写出的 XML 反解端点并与源 `points` 逐条比对(<0.01pt),并断言 arrow=true 时 `tailEnd` 在 `a:ln` 内;
|
|
102
|
+
结果进 `report.parity.lines{Exp,Out,Wrong}`,`parity.ok` 同时要求表/图/线三项归零。这是"预览层对、成品层错"这类跨层不一致的机器防线。
|
|
103
|
+
- 页面背景:`p:bg` 原生(solidFill / blipFill + 媒体嵌入 + rels,v0.3 修复)。
|
|
104
|
+
- `out`:**绝对路径原样使用 / 相对路径相对 deck 目录**(E1)。
|
|
105
|
+
- **auto-fit 与下限**(E2 修复;2026-09-06 下限语义修订):只缩当 `overflowY > 1`(与 verify 同阈值)→ verify 通过 ⇒ 不缩字;**下限 = 用户指令**:`theme.minFontSize` 显式 → `min(原字号, 用户下限)`(绝不升字);未设置 → 无强制(60% 原字号防荒谬保底,最低 6pt);到下限仍溢出 → `floorHit: true` 进 report(工具输出 ✗ 警告,绝不静默)。
|
|
106
|
+
- **自动回退链**(C1):tools.js `resolveEngine` 决定——auto=pptd+allowFallback;pptd 硬失败 → python-pptx 兜底(报告醒目标注降级);显式指定不回退。
|
|
107
|
+
- **引擎档位不静默(2026-09-18 修,两处)**:① `resolveEngine` 对**未知取值**返回 `unknown` 标记而非静默当成 auto
|
|
108
|
+
(历史形状:命令面接受 `/ppt engine pptxgenjs` 并回执"已记录(待接入)",导出侧却把它当 pptd 跑,报告一字不提——违反明文红线"不静默切换引擎");
|
|
109
|
+
② `/ppt engine X` 设的是**会话/项目档位**,而 `ppt_export` 旧实现只看工具参数 ⇒ 该档位**设了没用**(报告里的 `引擎` 行还显示用户选的那个)。
|
|
110
|
+
现在 `qualityOfDiag` 把档位一并带出,`ppt_export` 在工具参数缺省时**采纳**它;未知取值会在导出报告里明说"已按 auto 处理,没有静默按你的选择跑"。
|
|
111
|
+
`pptxgenjs` 未实现(v1.0 前按计划不动)⇒ 命令面**明确拒绝**,不再收下并装作成功。
|
|
112
|
+
- **出界分级分码(2026-09-18 修)**:`out-of-page`(超**页面边界**:放映不可见、**不可声明**、必须改布局)与
|
|
113
|
+
`out-of-safe-area`(超**安全区**但仍在页面内:**可声明**,补 `expectedOutOfSafeArea` 即可)**拆成两个错误码**。
|
|
114
|
+
此前两者共用 `out-of-page`,于是"分级"这个设计概念在 code 维度上丢失——消费方(`templates.js` 的清理统计、
|
|
115
|
+
任何按 code 的诊断)与模型都无法区分该做什么。两者 severity 都是 error ⇒ 属**诊断精度**修复,对既有工程零影响。
|
|
116
|
+
`audit-manual-facts.mjs` 的"decoration 不豁免出界"运行时事实随之改判为 `out-of-safe-area`。
|
|
117
|
+
- **兜底引擎的生成物必须过真消费者(2026-09-18 修,四处失效一次全中)**:`pptxPy.js` 把中间层映射成 **python 源码**再 subprocess 执行。
|
|
118
|
+
该路径此前在 `scripts/` 里**零调用**——从未被任何测试执行过,而它是"pptd 硬失败时"的最后保险。实测四种失效:
|
|
119
|
+
① **SyntaxError**:填充/描边被写成单行条件表达式 `x = a if c else (y = b, …)`——赋值是语句不是表达式,**连编译都过不去**;
|
|
120
|
+
② **NameError `undefined`/`null`**:fill / line 缺省时把 JS 的 `undefined` / `null` 插值进了 Python 源码;
|
|
121
|
+
③ **AttributeError**:`{color, alpha}` 被 `JSON.stringify` 成 dict 直接喂给 `rgb()`;
|
|
122
|
+
④ **NameError `true`**:`JSON.stringify` 产出的是 **JSON**(`true`/`false`/`null`)而非 Python 字面量——样式字典 `{"wrap":true}` 直接进脚本,
|
|
123
|
+
而**文本是最高频路径**,等于兜底引擎对几乎任何 deck 都不可用。
|
|
124
|
+
另有第五处:`add_text` 把样式设在 `paragraph.font` 上再 `p.text = …`(`p.text` 会新建 run,样式全丢)——导出"看着有字、样式全无"。
|
|
125
|
+
修法:填充/描边改为生成**语句**;新增 `pyLit()`(JS 值 → Python 字面量)替换数据类插值;样式改设到 **run** 上;
|
|
126
|
+
`OUT` 改为变量(旧实现导出时只替换 save 行,`print` 仍读 `sys.argv[1]` ⇒ **文件已写出却报执行失败**)。
|
|
127
|
+
诚实边界:alpha / 渐变在兜底引擎里按**纯色近似**,脚本内留注释(原生引擎 pptd 保留原样)。
|
|
128
|
+
防线:smoke §43 两条——`py_compile` 必须过 + 必须真跑出 .pptx 且 **python-pptx 能读回**(1 页 ≥ 6 形状)。
|
|
129
|
+
校验含义:这条正是"跳过与通过印同一个颜色"的典型——python 是可选依赖,缺了打一行"跳过"算绿,于是没人发现它连编译都过不去。
|
|
130
|
+
- **表格字号三层同源(2026-09-18 修,自相矛盾)**:表格 cell 原本在导出侧硬编码 `sz="1100"`(11pt)、预览侧硬编码 `font-size:12px`——
|
|
131
|
+
同一份内容两层不一致,且都绕开 `theme.minFontSize`。后果:audit 档的回读断言扫**全 slide XML** 取全局最小 sz 与下限比,
|
|
132
|
+
只要工程里有表且用户下限 ≥ 12,audit **永远 ✗**,而 DSL 里没有任何手段能改表格字号(把用户逼到无法满足的门禁)。
|
|
133
|
+
修法:字号由 `layout.js` 的 **`tableCellFontPt(ctx) = max(11, theme.minFontSize)`** 算一次,`normalizePage` 挂到元素的 `fontPt` 上,
|
|
134
|
+
预览(cell px)与成品(`sz`)都读它 ⇒ 三层不可能再各写一个数。默认值仍是 11pt,**未声明下限的既有工程观感零变化**。
|
|
135
|
+
防线:smoke §44 两条(三层同源 + 成品最小 sz ≥ 用户下限)。
|
|
136
|
+
- **表格内容溢出进门禁(2026-09-18 修,内容门禁的盲区)**:`layout.js` 的表格分支此前只产出 cols/rows 的**长度**,
|
|
137
|
+
而 `verify.js` 的溢出判定只认 `kind === 'text'` ⇒ **表格内文字溢出既无门禁也无警告**。
|
|
138
|
+
而表格是 `role: content` 的内容元素——"内容溢出/互压"正是硬底线要管的,属"该严的地方没严"。
|
|
139
|
+
修法:`normalizePage` 按**列宽换行**(复用 `wrapLines`,同一保守度量)算出每行需求高度 →
|
|
140
|
+
快照带 `metrics.overflowY`;`verify.js` 的溢出分支并入 `table`,用**独立错误码 `table-overflow`**
|
|
141
|
+
(处置不同就分码:表格的杠杆是行列尺寸与文案,不是文本框)。
|
|
142
|
+
**影响面先测后改**:仓库全部夹具 2 张表、**0 张**会被判溢出(余量 -228 / -52.8)⇒ 对既有工程零影响;
|
|
143
|
+
e2e 的"fx-pro verify 门禁 0 错误"仍绿。
|
|
144
|
+
注意:错误码提取器是**文本启发式**,要求 `code: '字面量'` 写法——写成三元会让码从守卫视野里消失(本轮踩到,已改独立分支)。
|
|
145
|
+
- **audit 回读断言**(D2):audit 档导出后自动读产物 OOXML,校验页数/尺寸(EMU 精确)/最小字号 ≥ minFontSize,逐项 ✓/✗ 追加进报告。
|
|
146
|
+
- **讲稿 → 备注页(2026-09-14 新增)**:页面 `notes:`(非空字符串)→ `ppt/notesSlides/notesSlide<N>.xml` + `ppt/notesSlides/_rels/notesSlide<N>.xml.rels`(rId1→notesMaster、rId2→回指本页幻灯)+ `ppt/notesMasters/notesMaster1.xml`(+rels→theme),
|
|
147
|
+
并补幻灯 rels(notesSlide 关系)、`[Content_Types]` 两条 Override;文本按 `\n` 分段成多个 `a:p`(实体转义走 `xm()`)。
|
|
148
|
+
编号只为"有讲稿的页"分配(1..N),**没有 notes 的页不产生任何备注部件**(这类工程产物与旧实现逐字节一致)。
|
|
149
|
+
**两个必须照 PowerPoint 原生形状写、否则真 PowerPoint 打不开的点**(实测二分定位):
|
|
150
|
+
① notesSlide 的占位符必须能在 notesMaster 里找到对应项(body 用 `idx="3"`、幻灯图 `sldImg idx="2"`)——notesMaster 因此不能是空 spTree;
|
|
151
|
+
② **`presentation.xml` 不写 `<p:notesMasterIdLst>`**(写了就报"文件或目录损坏"0x80070570,而 python-pptx 照读不误);备注母版只经 `presentation.xml.rels` 的 notesMaster 关系挂载。
|
|
152
|
+
自证:`report.parity.notes{Exp,Out}` + `notesRelOk`(每个有讲稿的页其 rels 确实引用到对应 notesSlide)并纳入 `parity.ok`。
|
|
153
|
+
|
|
154
|
+
### 2.8 import-pptx.js — 导入器(内容保真、版式近似)
|
|
155
|
+
- `relsOf`:正确下钻到 `Relationships` 子节点取 Relationship(修复过"层级错")。
|
|
156
|
+
- `decodeXml`:UTF-8/UTF-16LE/BE BOM 自动识别(兼容 WPS 导出的 UTF-16 rels,P2)。
|
|
157
|
+
- **备注读回(2026-09-14)**:`notesOfSlide()` 从 slide rels 找 `notesSlides/` 目标 → 相对路径解析成包内路径 → 各 `a:p` 段落文本按 `\n` 连接 → 写回页面 `notes:`(单行用引号、多行用 YAML 块标量 `|`)。与导出侧构成回环。
|
|
158
|
+
- **XML 实体解码(2026-09-14 修)**:`xmljs.decodeEntities()` 在**文本节点**解码 `&/</>/"/'` 与数字实体(`A`/`A`)。
|
|
159
|
+
此前只做转义(导出侧)、没有解码(导入侧),于是导入含 `&`/`<`/`>` 的文本会拿到字面量(`R&D` → `R&D`)。**只解码文本、不动属性值**——避免 splice/surgical 的结构性属性往返被改写。
|
|
160
|
+
- **P0-1 保样式(v0.4.0)**:theme1.xml clrScheme → schemeClr 映射;shape fill(solidFill + **gradFill 渐变归一末 stop 主色**,stops 保留)/line(a:ln w/color);文本 rPr(sz/b/i/color/font·latin+ea/align·pPr algn/lineHeight·spcPct)全量映射;`import-styles.json` 原始样式清单(渐变 stops/阴影);deck.yaml theme 聚合建议块(colors 高频 + textStyles 字号→title/body/label)。
|
|
161
|
+
- 背景三层识别:slide 级 `p:bg`(blipFill 提取 + solidFill/schemeClr 背景色)→ 满页图 ≥95% 提升为背景 → layout 级背景 fallback;输出 `background:` 字段。
|
|
162
|
+
- elementId 去重(`-2/-3` 后缀,P5);真实媒体提取(非占位)。
|
|
163
|
+
- **D4 safeArea 探测**(v0.3.2):`detectBands` 统计跨页重复元素签名(≥60% 页数同位置)→ 顶部/底部 30% 带 → deck.yaml **注释**写入 `# safeArea: {top, bottom}`(不自动启用)+ 导入报告提示。
|
|
164
|
+
- 近似映射:roundRect/rotation 保留、渐变→主色(stops 保留在 styles.json)、chart→文本占位并明示。
|
|
165
|
+
- ⚠ 依赖 xmljs 属性解析(v0.4.0 修复:**支持带空格属性值**,如 typeface="Microsoft YaHei"——此前被空格拆坏;此修复对导出/回读/导入全链路生效)。
|
|
166
|
+
|
|
167
|
+
### 2.9 tools.js — 模型工具面(21 个 `ppt_*` + index.js 的 `ppt_state` = 22 个已注册)
|
|
168
|
+
- 工具注册走 `ctx.tools.register(defineTool(...))` + `ctx.effect`(卸载即净)。
|
|
169
|
+
- **会话身份取用(0.1.5-rc.2 适配修正)**:工具里要"当前会话"只能走 `execute(args, exec)` 的 `exec.agent`
|
|
170
|
+
(`exec.agent.session.id`)。DSH 只注册 `agents` 服务,**没有** `agent` 服务——`ctx.get('agent')` 恒 undefined,
|
|
171
|
+
会让会话级设置(quality/template/quick)静默失效、`ppt_state` 写到 `'default'` 文件。`qualityOf(agent, dir)` 即此约定。
|
|
172
|
+
- 关键参数语义:
|
|
173
|
+
- `ppt_verify autoDeclare=true`:写声明 → 重新 load + render + verify,报告"已写入声明 + 剩余错误"。
|
|
174
|
+
- `ppt_verify pages="2,5-7"`(v0.14.6):局部校验——只在内存过滤 layout.pages(**不改写 layout.json**,防部分校验静默漏检其余页);`autoDeclare` 路径不支持 pages(声明必须整册)。
|
|
175
|
+
- `ppt_export engine auto = pptd`(写死);`out` 支持绝对路径。
|
|
176
|
+
- `ppt_schema`(无参,输出 SCHEMA_REF 速查)/ `ppt_new(dir, name)`(scaffoldProject,已存在 deck.yaml 拒绝覆盖)。
|
|
177
|
+
- `shotOne`:每张截图独立临时 `--user-data-dir` + `--disk-cache-size=1`(修复过 Edge 缓存导致截图雷同)。
|
|
178
|
+
|
|
179
|
+
### 2.10 router.js / commands.js / index.js / state.js / skill.js
|
|
180
|
+
- 路由(**0.1.5-rc.2 适配期修正**):判据从"动词紧邻名词"改为**名词 + 任务动词在 32 字符窗口内共现**(双向);
|
|
181
|
+
旧实现漏掉「帮我做一个 5 页的产品介绍 PPT」这类带修饰语的真实说法,导致工作流提示词永不注入(探针实测 28/28 无该段)。
|
|
182
|
+
保留"弱信号不切":只提一句 PPT 不激活;附件 `.pptx` 直接视为强意图。退出判据补"否定/停止/名词+收尾语/明确划清界限"。
|
|
183
|
+
- 工作流提示词注入 `system-prompt/assemble`:激活时替换 `ppt-workflow` section(quick 分支 8 条铁律 / 标准分支 10 铁律 + 入门段);
|
|
184
|
+
会话键取自 waterfall 的 `context.agent.session.id`(与官方包同款用法)。
|
|
185
|
+
- 命令面:`/ppt [on|off|quick|normal|free|mid|strict|fidelity|review|engine|quality|pause-after|help]`。
|
|
186
|
+
- state:`routing/mode/fidelity/review/quality/engine/quick/pauseAfter/workflowActive/taskType/deckDir`(DEFAULT_SESSION 见 state.js)。
|
|
187
|
+
- **状态读取的"坏文件必须可见"(2026-09-18 修)**:`loadSessionDiag` / `loadProjectDiag` 区分两种情形——
|
|
188
|
+
**文件不存在** = 正常(新工程/新会话,用默认值,`error: null`);**文件存在却读不了或解析失败** = 异常,返回 `error` 文本。
|
|
189
|
+
此前两者都静默返回默认值 ⇒ `state.json` 一损坏,`quality: 'audit'` 就**无声**降成 `standard`,
|
|
190
|
+
最严档的额外门禁(禁 autoDeclare / 强制视觉审阅 / 导出回读断言)全部消失,而用户看到的是一轮"正常"的 standard。
|
|
191
|
+
`tools.js` 的 `qualityOfDiag` 据此返回 `{ quality, degraded, reason }`;`ppt_verify` / `ppt_export` / `ppt_status`
|
|
192
|
+
在降级时把原因写进输出(诚实边界优于静默降级)。`loadSession` / `loadProject` 保留为薄封装(旧调用点不变)。
|
|
193
|
+
注意旧实现里 `qualityOf` 自己的 `catch { return { quality: 'standard' } }` 是**第二层**静默兜底,已一并移除。
|
|
194
|
+
- **skill.js(内置技能包)**:扫描随包 `skills/*/SKILL.md`,逐本经 `ctx.skills.register(...)` 注册为**内嵌运行时 skill**
|
|
195
|
+
(落调用 ctx 所在层:预设装配 → 该预设层)。frontmatter 解析与 `dsh-skill-filesystem` 同语义(`content = body.trim()`、name kebab-case);
|
|
196
|
+
`skills` 服务缺失 / 目录缺失 / 单本 frontmatter 非法 → **逐本跳过**,不阻断装配;`ppt_state.manualSkill.skills` 给出逐本注册与可见性自检。
|
|
197
|
+
当前四本:`ppt-studio-manual`(使用手册)、`ppt-studio-craft`(叙事与版式)、`ppt-studio-data`(图表与数据)、`ppt-studio-copy`(中文文案与去 AI 味)。
|
|
198
|
+
**触发面分工(2026-09-15 修正)**:`ppt-studio-manual` 是**答疑**技能——描述与 `whenToUse` 只由"**用户提问**"触发,并显式写明"制作任务进行中不要加载";
|
|
199
|
+
三本制作手册由各自的工作面触发(定纲/放图表/写文案)。修正前它写着"如何开始 / 或模型不确定某一 DSL 写法时加载"——对正在做 PPT 的模型是**恒真条件**,
|
|
200
|
+
于是每轮任务都先付一次几千字符的正文成本,而它讲的是"怎么回答用户提问"。工作流提示段也补了一行显式分行(答疑 vs 制作),
|
|
201
|
+
smoke §42 三条断言钉住这条纪律(技能描述与 whenToUse、正文横幅与工作流分行、制作三本正文零指向答疑手册)。
|
|
202
|
+
**技能是纯增量**:不定义门禁数值、不改铁律;工作流提示词只在标准档加**一行**"何时加载"指引,由机器断言保证旧行超集不变(见 §4 L0 的"★老用户不受影响"两条)。
|
|
203
|
+
**手册事实防线(smoke §37/§38,13 条)**:技能是模型的行为依据,所以"事实说错"单列一类风险——
|
|
204
|
+
`npm test` 直接拿手册去核对源码:`ppt_*` 工具名必须在注册表里;`verify.js` 的**门禁错误码集合**必须与手册 §4 的清单互相覆盖(集合不一致或漏写即红);
|
|
205
|
+
手册门禁清单里写的 code 名必须真实存在;警告标记必须是 `[~]`(`[⚠]` 只属 M2 实测段);`density`/`hotspot`/`near-align` 不得被归到 `[·]` 建议;
|
|
206
|
+
chart 的 `data` 形状必须是 `{cols, rows}`(`data: [{label,value}]` 是错形状,schema 直接拒);预览标签截断阈值必须与 `svgCharts.js` 一致;
|
|
207
|
+
`/ppt` 子命令必须存在;讲稿章节必须写明"插件不导出备注"(导出器只写 `<p:notesSz/>`,实现若真加了备注导出这条会红);
|
|
208
|
+
缩字下限常量必须与 `export-pptx.js` 一致;`SCHEMA_REF` 不得声称 `decoration` 豁免出界(C3 已回滚该语义)。
|
|
209
|
+
最后一条是**引用计数自证**:README/docs/02/docs/06/使用手册里所有"smoke N 断言 / N/N"必须等于本次真实断言数——
|
|
210
|
+
治"加断言忘了同步 6 处引用"这个反复出现的人工疏漏(该断言在实现当轮先红、同步后转绿,正是它的第一次工作)。
|
|
211
|
+
|
|
212
|
+
### 2.11 templates.js — 模板库(双轨:骨架层 + 真相层,v0.9.0)
|
|
213
|
+
- **模板 = 目录包** `templates/<id>/`:`template.yaml`(元数据 + **styleAudit** 视觉审计)+ `deck.yaml`(骨架层,theme/页面母版,**机器可验证**)+ `pages/_*.yaml`(参考母版,不注册不进门禁)+ `media/` + `preview.png`(缩略图)+ **真相层** `template.pptx`(原始文件,零失真)+ `previews/NN.png`(Office 真渲染整页,1920×1080)。
|
|
214
|
+
- **registerTemplate(dir, opts)**(外部模板收纳):resolveDeck 校验 → 页面拷为 `_NN_` 母版 → media 复制 → 洗涤(① 出界元素批量写 expectedOutOfSafeArea ② autoDeclare 重叠 ③ 剩余错误按类型统计进 meta.cleanup)→ 缩略图(模板目录自身渲染 + Edge headless)→ **真相层**(检测 `dir/source.pptx` 或显式 `opts.sourcePptx` → template.pptx;有 Office 则 `renderPptxToPng` → previews/;失败不阻塞)。
|
|
215
|
+
- **两层模板库(v1.0.6)**:随包层 `TEMPLATES_DIR`(只读;升级时 pnpm 重新物化包,写进去的东西会没)+ **用户层** `userTemplatesDir()` = `<dshHome>/ppt-studio/templates`(`DSH_HOME` 优先,**每次调用求值**——这样测试能把 DSH_HOME 指到临时目录验证,常量则无法覆盖)。`listTemplates` 合并两层并标 `source: user|bundled`,**用户层优先**(同名覆盖随包项时标 `shadowsBundled`);`templateDirOf(id)` 按"用户层 → 随包层"解析,`templateWorkspace`/`materializeTemplate`/`ppt_template_styleaudit` 都走它,所以用户模板与随包模板**同一条路**。`registerTemplate` 缺省写用户层,并**拒绝写进随包目录**(防"用户攒的模板升级后消失");`removeTemplate` 只删用户层,随包层**抛错并说明"升级会重新出现"**(避免"删了又回来"的困惑)。工具面:`ppt_template_add` 直接收 `.pptx`(内部 `importPptx` → 收纳,自动留真相层),新增 `ppt_template_remove`。
|
|
216
|
+
- **materializeTemplate(dir, id)**(物化工作区):拒绝覆盖已有 deck.yaml → 正式页 `pages/01_opening.yaml`(首母版副本,注册进门禁;pageType→content)→ 母版页 `_` 前缀不注册 → media 复制 → **参考层拷贝** `reference/{template.pptx,previews/audit.yaml}`(工作区自包含)→ deck.yaml 顶部注入 `referenceTemplate`(id/name/source/previews 清单/audit 引用;**非渲染字段**,schema 轻校验)。
|
|
217
|
+
- **styleAudit 设计时声明**(`ppt_template_styleaudit`):模型 read_image 看 previews 真渲染 → 视觉理解(palette/fonts/layout/decorations/pagesReviewed/notes)写回 template.yaml → materialize 复制为 reference/audit.yaml → 每次"按模板做"直接读缓存,零重复视觉成本。
|
|
218
|
+
- **语义**:"按模板做" = "参考用户给的 ppt 制作":创作前先看 reference/previews/*.png(真身)+ reference/audit.yaml(审计缓存),再动手;无 reference(内置模板/无 Office 收纳)退化为骨架层 + audit 缺失说明。
|
|
219
|
+
|
|
220
|
+
### 2.12 msrender.js / preview-server.js — Office 真渲染通道 + 对话内预览
|
|
221
|
+
- **msrender.js(v0.8.0)**:`findPowerPoint()`(POWERPNT.EXE 候选路径探测,无则 null → 通道自动隐藏);`renderPptxToPng(pptx, outDir, {width,height,timeoutMs})`——写 BOM ps1(Windows PowerShell 按 GBK 读无 BOM 文件会乱码中文路径)→ spawn powershell.exe -File → COM `Presentations.Open(只读)` + 逐页 `Slides.Item(i).Export(...,'PNG',1920,1080)` → 解析 `OK:<n>`;安全:只读打开、finally Quit、超时 SIGKILL 并清理残留 POWERPNT。
|
|
222
|
+
- **用途三点**:① 理解用户原稿(参考任务先看后做,工作流 6a)② 成品视觉审核(导出后审核真实观感,audit 档自动联动)③ 模板真相层整页(2.11)。
|
|
223
|
+
- **preview-server.js(v0.6.x)**:`buildPreview(dir)` → 预览根 `~/.dsh/ppt-studio/preview/<token>/`(pages/ + media/ 复制 + .meta.json 源映射);`registerPreviewRoute` 在 webServer 注册**无尾斜杠** prefix `/ppt-preview`(prefix 语义是前缀匹配,带尾斜杠永不命中);`resolveToken` 持久回退。
|
|
224
|
+
- **`ctx.effect` 真语义与随之修正的两处(2026-09-14,真库探针实测)**:`ctx.effect(cb)` **立即执行 cb**,用 **cb 的返回值**作 disposer。
|
|
225
|
+
实测(`new Context()` + 探针):调用后立刻观察到 `cb-body-ran`,手动 dispose 后才 `returned-fn-ran`。
|
|
226
|
+
① 路由 refcount 原先把清理体写在 cb 体内 ⇒ 注册当刻 `count--` 并 `cancelled=true`,**文档承诺的"首个注册者真注册、最后卸载者真卸载"从未成立**(注册后立即被取消);已改为 cleanup 包在返回函数里。
|
|
227
|
+
② 新增的"装配防重"(见 §2.18)同样必须用返回函数形式。
|
|
228
|
+
③ **为什么长期没暴露**:smoke 里的假 ctx 写成了"cb 即清理"(defer 执行),假契约掩盖了真 bug;假 ctx 已按真语义重写,四条路由断言现在验的是真行为。
|
|
229
|
+
|
|
230
|
+
### 2.13 双轨审阅扩展(v0.8.0)
|
|
231
|
+
- **三渲审核**:数字门禁(render→verify,门禁级)+ HTML 预览档(ppt_preview/ppt_shot,处处可用)+ **Office 真渲染档**(COM,准确、有条件才用)。无 Office → 明确标注"未经 Office 真渲染审核",绝不静默降级。
|
|
232
|
+
- 审计对齐:模板系统 + 参考类任务的视觉理解都走同一通道(6a/6b 等价)。
|
|
233
|
+
|
|
234
|
+
### 2.14 surgical.js — 手术模式(v0.10.0,候选 B:结构精确贴模板)
|
|
235
|
+
- **语义**:以模板 .pptx 为底版,把工作区(deck)内容注入模板页文本/表格槽;其余页**内容逐字节不变**(解包 sha256 验证);重打包输出。与双轨互补(双轨=风格参考,手术=结构保真);与 ppt_export 并存(两条成品路径,互不影响)。
|
|
236
|
+
- **管线**:`scanSlideXml`(spTree 顶层文本 sp / table frame 槽,<a:t> 记录**绝对位置**;跳过 grpSp;无 xfrm 槽与非文本框纯序号槽排除)→ `matchByDistance`(模板槽↔deck 元素贪心最近配对,bounds 数组/对象兼容;页码占位元素排除)→ `patchSlideXml`(**字符串切片只改 <a:t> 区间**——首 run 承载、余 run 置空保留 rPr 链;`escapeXml` 转义;`normText` 空白归一比较:导入/多 run 拼接差异不算改动)→ `surgicalPatch`(slide 顺序 = presentation sldIdLst + rels;页映射 surgicalMap/map 缺省 i↔i;zip 重打包保序)→ `verifySurgical`(未手术条目内容 sha256 对照)。
|
|
237
|
+
- **保真根源**:只动 a:t 区间 → rPr/几何/渐变/字体/图片/组合 100% 原样;"未动页内容哈希一致"是可验证的硬承诺(zip 重压缩后字节不同,故粒度 = 条目解包内容)。
|
|
238
|
+
- **v1 边界**:文本+表格;grpSp 内部/图片/新增页不注入(报告);表格行数超出警告;rels 增量/新增页合并留 v1.1。
|
|
239
|
+
|
|
240
|
+
### 2.15 候选 C 几何闭环(v0.11.0:custGeom/alpha/形状内文本)
|
|
241
|
+
- **custGeom**(自定义几何,模板曲线装饰真身——简约商务 97 个实证;**该模板已随 1.0.4 移出随包发行物**,证据见 docs/fidelity-report.md 与 v1.0.3 资产):import 解析 pathLst(commands: moveTo/lnTo/quadBezTo/cubicBezTo/arcTo/close + path w/h 路径坐标空间,**抽象单位原样保留不除 EMU**)→ PPTD `shape.path`;render 转 SVG `<path>`(viewBox 非等比拉满 bounds;arcTo → SVG A 命令弧终点几何转换:中心=起点−半径×角度向量,y 向下顺时针);export `<a:custGeom>` 命令直通(几何零损失)。
|
|
242
|
+
- **模板库构成(1.0.4 起)**:随包只带 4 套**版权自研精磨模板**(business-blue / academic-white / tech-dark / pitch-bold,合计 0.13 MB,各含 theme + 母版页 + 封面预览图)。此前随包的 4 套"从真实 PPT 导入"的重型模板(46.2 MB,占安装包 96%)已移除——它们只是**收纳能力**的示例数据,`ppt_template_add` 这条通道本身不受影响;`npm pack` 从 **35.1 MB 打包 / 50.5 MB 解包 / 492 文件** 降到 **866 kB / 2.1 MB / 178 文件**。
|
|
243
|
+
- **alpha**(透明度):solid/渐变 stop `<a:alpha val>` → fill `{color, alpha}` 或 stop `alpha`(0-100);render `hexToRgba`;export srgbClr/gs 内 `<a:alpha>` 回写。
|
|
244
|
+
- **形状内文本**(非 rect 形状带文本,v0.11 修复 isText 抢先:custGeom 带文字不再整形状变文本):`_shapeText` 提取为与形状同 bounds 的独立 text 元素(居中)——椭圆序号"1/2/3"等文本复活(20 个实证)。
|
|
245
|
+
- **关键修复**(回环测试暴露):normalizePage shape 分支需透传 `path`(否则 export 空 custGeom)。
|
|
246
|
+
- **页映射(2026-09-18 修)**:`surgicalMap`(deck.yaml)/ `map`(工具参数)→ **参数优先,缺省采纳 deck.yaml 的 `surgicalMap`**,仍缺省则 i↔i 序号对齐。
|
|
247
|
+
该字段此前**只有 schema 校验、全项目无消费者**——而本行文档早就写着"页映射 surgicalMap/map",于是文档是对的、实现是缺的:
|
|
248
|
+
用户把映射写进 deck.yaml 以为生效,实际**静默无效**(同一族"契约存在但不被消费")。防线:smoke §48 两条真产物断言(模板第 1 页是否取到 deck 第 2 页的内容)。
|
|
249
|
+
- **边界**:master/layout 层装饰(背景层——不提取,每页复制母版装饰反而混乱);pathFill/空 path → rect 兜底;LOGO 圆环等组合内图片细节(双轨/手术覆盖)。
|
|
250
|
+
|
|
251
|
+
### 2.16 measurement.js — M2 文本实测档(浏览器真测量,D3 落地)
|
|
252
|
+
- **通道**:renderDeck 预览 HTML → 注入测量脚本(`Range.getClientRects` 行盒 → 行数/内容高/`overflowY = scrollHeight − clientHeight`;`getBoundingClientRect` 几何)→ Edge `--dump-dom --virtual-time-budget=2500 --user-data-dir=<独立>` 提取 `#__ppt_measured` JSON → `preview/measured.json`。
|
|
253
|
+
- **性能(D5 基准)**:100 页 render 70-406ms;measure **220-320s**(顺序 3.2s/页;优化链:并发 4 + 每进程独立 user-data-dir(共享 profile Singleton 锁串行化是首轮大坑)→ 2.2s/页;budget 减小无益(虚拟时间快进非线性))。20 页常用 ≈ 45s。
|
|
254
|
+
- **交叉语义**(verify.js `measuredCrossCheck`):实测溢出且估算未报 = **error(估算漏报——M2 核心捕获)**;实测复现 = warning;估算报但实测通过 = relief warning(字体差异人工确认);行数差异 = suggestion;旋转元素排除几何漂移。`ppt_verify measured=true` 默认关闭(老行为不变)。
|
|
255
|
+
- **两档页号契约(2026-09-18 修,假绿灯事故)**:`layout.json` 与 `measured.json` 每页都写 **1 基 `pageNo`**,交叉核查**按 pageNo 配对**;`index` 退回"各自文件的内部数组下标"(两侧都 0 基)。
|
|
256
|
+
事故:此前按 `index` 配对,而 layout 的 index 是 0 基(render-html 取 schema 的 pages.length)、measured 的 index 是 **1 基**(`i+1`)⇒ **永不配对、恒返空结果**,界面上显示"✓ 全部一致或实测通过"。
|
|
257
|
+
更隐蔽的是 offset 恰为 1:若两侧元素 id 跨页复用,会拿第 N 页的实测去比第 N+1 页的估算。
|
|
258
|
+
**契约破坏现在响亮报错** `measured-unpaired`(缺 pageNo / 找不到对应页),不再静默 `continue`——静默返空与"真的没问题"在输出上不可区分,这正是它藏了整轮 1.0.0 的原因。
|
|
259
|
+
防线:smoke §27 四条**真产物**断言(页号契约 / 第 2 页注入必被捕获 / 第 1 页注入不得串页 / 缺 pageNo 必须报错),全部由 `ppt_render` + `ppt_measure` 真跑产物驱动。
|
|
260
|
+
- **挂载范围(谁看得见这些工具)——与"从哪取字节"是两件事**(2026-09-18 明确):
|
|
261
|
+
`--local` 只管**字节来源**;**谁能看见工具/skills/命令/提示段**由**挂载模式**决定,两者正交。
|
|
262
|
+
① **profile bundle 行**(`dsh plugin add`)= **profile 级**:该 profile 的**每个**会话(含官方标准预设)都能看到 `ppt_*` / `/ppt` / 4 本内嵌技能 / 工作流提示段;
|
|
263
|
+
② ~~**agent preset 行 + junction**(`install.mjs`)= **预设级**~~ **【2026-09-18 作废——见 docs/03 最新条目】**:
|
|
264
|
+
这条路在本 DSH 版本上**不可能成立**——预设行的裸包名一律从 `harnessBase`(安装好的 harness 目录)解析,
|
|
265
|
+
**不是** profile ⇒ 解析不到 ⇒ 该预设被标 `broken` ⇒ 前端选择器只渲染健康预设 ⇒ **用户根本选不到该预设**,
|
|
266
|
+
而 profile 里又没有 bundle ⇒ **两边都没有任何工具**(现场:245 个会话里 `agentPreset=ppt` 的 **0** 个,
|
|
267
|
+
而本机所有文件级检查全绿)。现只保留 ①。
|
|
268
|
+
**插件无法在 bundle 挂载下自己按预设过滤**:注册发生在插件 `apply`(进程/profile 级)时,要按预设隔离,注册就必须从预设组合里发生。
|
|
269
|
+
因此"想要会话级隔离"=把插件行写进**预设声明本身**(0.1.7 起预设是注册表里的一条定义,插件行需要一个绝对 `file://` 名字)。
|
|
270
|
+
**【2026-09-24】该实现尚未完成**:`install.mjs --isolate` 现在明确拒绝并说明两处源码依据(详见 §2.18 末 2026-09-24 条目)。
|
|
271
|
+
配套(同日修):`release-sync --local` 现在**两种模式都自证**——bundle 比"部署副本 vs pnpm 快照",非 bundle 比"部署副本 vs **穿过 junction 读到的字节**";
|
|
272
|
+
此前非 bundle 分支不设 `mountMatch`,而 LOCAL 判据要求它为 true ⇒ 该模式下 `--local` 恒报 ❌(写 `--local` 时只考虑了 bundle 模式)。
|
|
273
|
+
- **诚实边界**:测量 = Chromium 排版真值(系统字库),仍非 PowerPoint 引擎;与 ppt_shot 同渲染链(字体/尺寸差异同源)。
|
|
274
|
+
|
|
275
|
+
### 2.17 crosscheck.js — M3 内容审阅材料包(2026-09-18 用户质疑后重写)
|
|
276
|
+
- **旧版(v0.13.0,已废除)**:跨页数字对账(同数字/百分比入组,cap 30)+ 证据核查表(`source` → grounded/unmapped)。
|
|
277
|
+
- **为什么废除(实测,不是口味问题)**:
|
|
278
|
+
① 按"数字字符串相同"分组只能确认**本来就一致**的数,看不见**同指标不同值**——埋"毛利率 32% / 38%"两页 → 0 命中,反而报了无害的 `12%`(工具声称的能力与实现反向);
|
|
279
|
+
② 噪声 > 信号:`40 字以内` 与 `40px` 混义成一组、版本号 `v1.0.0` 被拆成 `1.0` 与 `0` 两组、提示文案里的数字入组(fx-pro 12 页 6 组 / 0 组可行动);
|
|
280
|
+
③ `source` 是作者自写自由文本,机器只判"有没有填"——不是证据(没有任何环节去取/读/核对来源)、不进成品(全库唯一消费者就是那张表),且按页判导致封面页也 unmapped ⇒ 伪状态。
|
|
281
|
+
- **现职责(只做精确、零判断的事)**:`crosscheckDeck(ctx)` 纯函数汇总 → `crosscheckReport(ctx)` 输出 markdown 并落盘 `preview/review-pack.md`(完整包永不截断;内联超 24000 字符时**明确列出未内联的页**):
|
|
282
|
+
① 全页正文(阅读顺序:文本/表格/图表数据/图片/`notes` 讲稿/作者自述出处);
|
|
283
|
+
② 工作区实扫素材清单(`reference/`+`previews/`+用户素材目录+根目录文档类;据此决定外部主张该判"无来源支撑"还是"无法核实");
|
|
284
|
+
③ 审阅协议:判定四分类(一致/冲突/无来源支撑/无法核实)、**子代理优先且用户禁止时必须自审并标注**、"缺素材不得报绿"、"未覆盖不得说成已通过"。
|
|
285
|
+
- **不变量**:本文件不产生任何判定、状态、比例或阈值 ⇒ 既不进门禁,也不可能与门禁冲突(工具输出里没有可"变绿"的东西)。
|
|
286
|
+
- 消费方:`ppt_crosscheck` 工具(tools.js);`audit-manual-facts` 断言"源码无状态机/无数字分组 + data 手册口径已同步";smoke §28 与 e2e ③ 断言"全页覆盖 + 无 groups/status 字段 + 完整包落盘"。
|
|
287
|
+
- themeRef 断言(schema.js):`$name` 引用(fill/color/style/stop/background)必须在 theme——缺失 = resolveDeck error(防字面 `$xxx` 上屏静默回退)。
|
|
288
|
+
|
|
289
|
+
### 2.18 装配与安装(2026-09-14:新增 profile bundle 路径)
|
|
290
|
+
- **两条互斥的挂载路径**(细节见 docs/01 决策 11、README §0.7):
|
|
291
|
+
| 路径 | 载体 | 层级 | 由谁写 |
|
|
292
|
+
|---|---|---|---|
|
|
293
|
+
| profile bundle 行(**本版本唯一受支持**) | 包的 `cordis.patch.yml`(`dsh.bundle.patch` 指向) | **profile 级**(所有会话) | `dsh plugin --profile <p> add <包>`(launcher 自动入 `dsh.profile.bundles`) |
|
|
294
|
+
| ~~agent preset 插件行~~ | ~~`~/.dsh/.agent-presets/ppt/agent.cordis.yml`~~ | —— | **【2026-09-24 在 0.1.7 上作废】** 目录发现已删除,`--isolate` 由安装器明确拒绝;见下方 2026-09-24 条目 |
|
|
295
|
+
- **`dsh plugin` 的真实语义**(harness 源码 + 真机实验双证):它是**薄 pnpm 转发器**——在 profile 目录跑 `pnpm <args...>`,随后
|
|
296
|
+
`reconcilePlugins()` **按已安装状态**核对 `dsh.profile.bundles`:依赖解析出的包**声明了 `dsh.bundle.patch` → 自动入栈**;
|
|
297
|
+
没有声明 → 打一行 "declares no dsh.bundle … not a profile layer" 警告(装了不挂载——本包在 2026-09-14 之前正是这个状态)。
|
|
298
|
+
对 path/git/tarball/alias 规格按**真实包名**入栈,所以"从 GitHub Release 的 tgz URL 安装"不需要 npm 账号。
|
|
299
|
+
- **npm 通道(2026-09-18 起,v1.0.3)**:包名 `@dsh-external/dsh-ppt-studio` → **无 scope** 的 `dsh-ppt-studio`(`private` 去掉、`npm publish` 公开)。
|
|
300
|
+
于是"一条命令"变成 `dsh plugin --profile web add dsh-ppt-studio`(裸包名 → pnpm 从 registry 取;**装机的人同样不需要 npm 账号**——公开包匿名可下载)。
|
|
301
|
+
两处必须与包名同步、否则静默失效:① `cordis.patch.yml` 里插件行的 `name`(写成解析不到的包名 = 装了不挂载);
|
|
302
|
+
② `scripts/install.mjs` 的落点路径(已改为按 `pkg.name.split('/')` 拼,scoped/无 scope 通吃)与历史行清理正则(**新旧两个包名都认**)。
|
|
303
|
+
双挂载风险:老用户机器上旧名包仍是独立依赖 ⇒ 安装器检测到旧名残留时会**主动提示** `dsh plugin remove @dsh-external/dsh-ppt-studio`。
|
|
304
|
+
- **装配防重**(`index.js`):同进程第二次 apply 直接计数并告警、**不重复注册**(tools/commands/技能/监听器都是"按名注册"的服务,重复注册会撞名)。
|
|
305
|
+
用 `globalThis.__pptCoreReg` refcount(跨模块实例共享;模块级变量在 junction/真实路径双加载下不可靠)。
|
|
306
|
+
注意 `ctx.effect` 语义(§2.12):清理必须写成**返回函数** `() => () => { count-- }`。
|
|
307
|
+
- **互斥由安装器机械保证**(2026-09-15;0.1.7 起只剩默认模式):`install.mjs` 检测 profile 的 `package.json` 依赖 / `dsh.profile.bundles` 是否已含本包
|
|
308
|
+
→ 是(bundle 模式)则**跳过 junction**(该路径归 pnpm 管,不能动)、**不动包内 node_modules**,
|
|
309
|
+
并**清理 0.1.6 遗留的 `.agent-presets/ppt/`**(0.1.7 上那是惰性目录,留着会让"改了预设为什么没生效"变成假线索),
|
|
310
|
+
同时校验"声明预设"所需的包内资产(`agent-presets/ppt/*` + `lib/preset-delivery.js`)确实装到了。
|
|
311
|
+
smoke §41 用临时 prefix 真跑安装器验证这些路径(含 `--isolate` 必须拒绝、遗留目录必须被清)。
|
|
312
|
+
- **发布流程与新装法对齐**(`release-sync.mjs` 第 ⑤b 步):bundle 模式下**挂载源是 pnpm 的快照**,不再是"部署副本被 junction 指向",
|
|
313
|
+
所以发布后要按**同一 GitHub 资产 URL** 重新 `add`,并用**逐文件 sha256 比对**(部署副本 vs 挂载副本)自证同源;不一致则 remove+add 重取。
|
|
314
|
+
`.sync-state.json` 记 `mode: local|release`、`mount: bundle|bundle-local|preset-junction`、`mountMatch`、`assetUrl`、`localTgz`、`lastReleaseAt`。
|
|
315
|
+
- **两种同步模式**(2026-09-18):**日常迭代** `release-sync --local` **不发 GitHub**,用**本地 tgz 的 `file:` 规格**装进 profile
|
|
316
|
+
(构件落在 `D:\plugins\_artifacts\`,**稳定目录**——它就是 profile 的依赖规格,放 tmpdir 会在系统清理后让下次 `pnpm install` 莫名失败;旧构件自动清理只留当前那个),
|
|
317
|
+
判据从"== GitHub"改为"**挂载副本 == 刚构建的字节**";**发版**(不带 `--local`)仍做三向 sha256 校验,把本机与 GitHub 拉回同源。
|
|
318
|
+
为什么 `--local` 走 `file:` 而不切回 junction/preset 装法:junction 会**新增一条挂载路径**("我改的是哪份代码"有两种答案,答错不报错),
|
|
319
|
+
`file:` 仍只有一条路径(profile bundle 行)。**⓪ 落后检查**:`--local` 先 `git fetch` 比对 `HEAD` 与 `origin/<branch>`,落后即失败
|
|
320
|
+
(`--allow-behind` 绕过)——"本地永不落后于 GitHub"**不会自动成立**,且破了以后没有任何症状。
|
|
321
|
+
- **资产名带构建时间 + 构建戳**(2026-09-15,同日两次修正):`<版本>-<YYYYMMDD-HHmm>-<tgz sha256 前 8 位>.tgz`。
|
|
322
|
+
① **戳**:实测同一 URL 被 `--clobber` 覆盖后 `dsh plugin add <同一 URL>` **不会重取**(pnpm 侧按 URL 复用;独立 GET 证明 URL 已是新字节),
|
|
323
|
+
固定名会让"重跑同一条命令升级"静默拿到旧版本;换名 ⇒ URL 必变 ⇒ 必然重取(反向也已实测:`test:bundle` 的升级自证证明新规格真换成新字节且 bundles/dump-config 不丢)。
|
|
324
|
+
② **时间**:Release 页面**有意保留历次构建**(老 URL 留给已装用户重装——删掉会让他们的 `pnpm install` 失败),
|
|
325
|
+
于是页面上并排多条"长得像"的资产,而 sha 段看不出新旧 ⇒ 加 `<YYYYMMDD-HHmm>`,"取时间最新那条"即可判定当前版本。
|
|
326
|
+
**取法要按上传时间,不能按文件名排序**(实测踩到):老的无时间戳资产名 `…-1.0.0-ad78a36d.tgz` 在字典序里排在
|
|
327
|
+
`…-1.0.0-20260916-0226-….tgz` **之后**,`sort | tail -1` 会取到旧版本 ⇒ 正确命令是
|
|
328
|
+
`gh release view v1.0.0 --json assets --jq '.assets | sort_by(.createdAt) | last | .name'`(README §0.2 同步)。
|
|
329
|
+
探针:`npm run probe:asset -- <URL> [marker] [期望sha256]`(直接 GET + 解包;**判决以 sha256 为准**,marker 只作线索)。
|
|
330
|
+
- **包名是单一事实源**:`package.json.name` 必须与 `cordis.patch.yml` 的 insert 行、预设插件行三处一致——smoke §40 有断言钉住。
|
|
331
|
+
- **重解析点安全**(配套探针 `npm run probe:reparse`):`release-sync` 会 `rmSync(D:\plugins\package, {recursive:true})`,而该目录里有
|
|
332
|
+
`node_modules/yaml` junction → 探针实测 **Node 的 rmSync 不跟随 junction**(金丝雀目录存活),该删法安全;
|
|
333
|
+
但**PowerShell 的 `Remove-Item -Recurse` 会跟随**(2026-09-14 事故)——删含 junction 的目录一律先 `cmd rmdir` 链接。
|
|
334
|
+
- **自证**:`npm run test:bundle`(`scripts/verify-bundle-install.mjs`)——隔离 `DSH_HOME` 起临时 profile → 真 `dsh plugin add <tgz>` →
|
|
335
|
+
断言"无 no-dsh.bundle 警告 + 已进 bundles 层栈 + 已物化 + dump-config 出现 ppt-studio 行",**不碰用户 profiles**。
|
|
336
|
+
- **【2026-09-24 适配 DSH 0.1.7-rc.1】预设交付:磁盘目录 → 注册表声明**(本轮唯一的真适配):
|
|
337
|
+
| | 0.1.6 及以前 | 0.1.7+ |
|
|
338
|
+
|---|---|---|
|
|
339
|
+
| 预设是什么 | `<dshHome>/.agent-presets/<id>/` 下的组合文件,**宿主启动时扫描** | 向 `agentPresets` 注册表**声明**的一条定义 |
|
|
340
|
+
| 随包 standard 在哪 | `dsh-agent-presets/presets/standard/agent.cordis.yml`(条目数组) | `dsh-web-app/presets/standard.patch.yml` 的 `insert → config.plugins` |
|
|
341
|
+
| 本插件怎么交付 | `install.mjs` / `preset-delivery.js` **写文件** | `lib/preset-delivery.js` 调 `ctx.agentPresets.register({id,name,description,order,plugins})` |
|
|
342
|
+
| 撤销方式 | 删文件 | 注册返回的 disposer(挂在 `ctx.effect` 上,随插件同生共死) |
|
|
343
|
+
上游证据:`@deepseek-ai/dsh-agent-presets`(复数,含 `discoverPresets`)已不存在,取而代之的是
|
|
344
|
+
`dsh-agent-preset`(29 行,只做 `register`)+ `dsh-agent-preset-registry`(Config 只有 `default/selectedDefault/modeSelectionEnabled`,
|
|
345
|
+
**没有任何目录扫描**);社区插件文档亦明说 "the harness discovers no preset on disk"。
|
|
346
|
+
⇒ 旧写法在 0.1.7 上**不会报错,只是预设永远不出现**(静默失效,本轮就是这么被发现的)。
|
|
347
|
+
- **【同日】注册表 mount 的 baseUrl 决定了"预设什么时候变 broken"**(这条是 `--isolate` 被拒的判据,也决定了 composition 里能写什么):
|
|
348
|
+
`AgentPresetRegistry.register()` 里 `const context = this.ctx`(**注册表自己的 ctx**),
|
|
349
|
+
`activate()` 再 `mountPreset(scope.ctx.extend({ baseUrl: record.context.baseUrl }), …)`;
|
|
350
|
+
而注册表由 `dsh-web-app` 的 patch 插入 ⇒ 预设里每一行的 `name` 都在 **`dsh-web-app` 包目录**下解析
|
|
351
|
+
(既不是 profile、也不是预设目录、更不是"harness 包目录")。
|
|
352
|
+
推论两条:① 组合里写 `@deepseek-ai/*` 这种 dsh 自带依赖**能解析**(实测 29 行全部解析成功,见 `test:bundle`);
|
|
353
|
+
② 写本包(`dsh-ppt-studio`,装在 profile 里)**解析不到** ⇒ 预设 broken ⇒ `presetOptions()` 过滤掉它
|
|
354
|
+
⇒ 这就是 0.1.6 那次"用户根本选不到预设"的同一根因,在 0.1.7 上**依然成立**(换了实现,判据没变)。
|
|
355
|
+
- **`!!js` 必须保真**(最容易写错的一处):Loader 把 `disabled: !!js <expr>` 物化成 `{ __jsExpr: '<expr>' }`
|
|
356
|
+
(`dsh-app-boot` 的 `JsExpr` = `tag:yaml.org,2002:js`,`construct: data => ({__jsExpr: data})`;
|
|
357
|
+
`cordis-plugin-loader` 用 `evaluate(ctx, value.__jsExpr)` 求值)。插件自己读 YAML 时若把它降级成普通字符串,
|
|
358
|
+
`Boolean(<非空字符串>) === true` ⇒ **Windows 上 `tool-pwsh` 行会被误停用(没有 shell)**。
|
|
359
|
+
`preset-delivery.js` 用包内 `yaml` 依赖注册同名 tag 复现该形状;smoke 有一条断言直接钉这两个表达式的原文。
|
|
360
|
+
- **`--isolate` 在 0.1.7 上被拒**(不是遗忘):它原本靠"预设目录内 junction + 相对路径行"实现(相对行按组合文件目录解析),
|
|
361
|
+
而 0.1.7 里预设目录不再被扫描、预设行改在注册表 baseUrl 下解析 ⇒ 两条都解析不到。
|
|
362
|
+
安装器现在 `exit 1` 并打印两处源码依据;要真做隔离,得把插件行放进**预设声明本身**并给它安装期算出的绝对 `file://` 名字(未实现)。
|
|
363
|
+
`release-sync` 的 `preset-junction` 分支保留但只服务升级前就已装成孤岛的老机器,提示语不再叫人重跑 `--isolate`。
|
|
364
|
+
- **【2026-09-26 rc.1 → rc.2 复核:无需改码】**(用户报"已更新至 0.1.7-rc.2"后按升级纪律重跑):
|
|
365
|
+
在**活进程**(确认是 rc.2:包安装于 00:29、进程启动于 00:43)里实测 —— 22 个 `ppt_*` 工具在位、
|
|
366
|
+
4 本内嵌技能 `registered: true`、工作流提示段激活、`ppt_state.presetDelivery` = `declared:true / via:agentPresets.register / 19 行`
|
|
367
|
+
⇒ 预设声明、工具/技能注册、提示段、会话事件四条路径在 rc.2 上**原样工作**。
|
|
368
|
+
另把 rc.1 与 rc.2 的包拉下来逐文件比对(`npm pack` + 逐文件 sha256 + `git diff --no-index`),
|
|
369
|
+
与本次相关的差异只有这些,均**不触及我们的接口**:
|
|
370
|
+
| 包 | rc.2 的改动 | 影响 |
|
|
371
|
+
|---|---|---|
|
|
372
|
+
| `dsh-agent-preset` | 无代码变更(只有 package.json / README / 两本 SKILL.md) | 无——`Config{id,name,description,order,plugins}` 逐字相同 |
|
|
373
|
+
| `dsh-agent-preset-registry` | **删掉 `modeSelectionEnabled` 与 `policy()`**;`remoteExportList()` 不再返回 `modeSelectionEnabled`。`register()`、`Duplicate agent preset` 抛错、`mountPreset(... baseUrl: record.context.baseUrl)` 全部不变;`lib/types/definition.d.ts`(声明契约)未改 | 无——我们只调 `register()`,不读选择器策略 |
|
|
374
|
+
| `dsh-web-app` | `cordis.patch.yml` 新增 `time-context`/`schedule`(默认停用)与 `shortcuts`/`ui-shortcuts` 行;**`presets/*.patch.yml` 逐字节未变** | 无——预设内容零漂移(`check-preset` 印证) |
|
|
375
|
+
| `dsh-app-boot` | 装载失败的 bundle 改为收集进 `skippedBundles`、由 launcher 每次启动打印一次(原先在装载时直接 stderr);`OPTIONAL_BUNDLES` 新增 `dsh-experimental-auto-review`;解析器错误消息重构。**`JsExpr`(`tag:yaml.org,2002:js`)与 patch 语义未变** | 无——反而更好:本包 bundle 装载失败现在会明确打印 |
|
|
376
|
+
| `dsh-tools` | 一处工具描述文案(run_code 审批理由)+ 透传 `displayReason` | 无 |
|
|
377
|
+
| `dsh-skill` / `dsh-persona` | 无代码变更 | 无 |
|
|
378
|
+
`dsh.engines.dsh` 保持 `>=0.1.7-rc.1`(两个 RC 都实测可用),不因 rc.2 收紧范围。
|
|
379
|
+
- **【2026-09-26 桌面端(Electron 应用):运行时兼容,安装通道不通】**(用户问"能否兼容桌面端"):
|
|
380
|
+
桌面端把 harness 打进 `resources/app.asar`(+ `app.asar.unpacked/dsh/node_modules`),共用同一个
|
|
381
|
+
`~/.dsh`,但**有独立的 `desktop` profile**(`dsh-profile-desktop`)。
|
|
382
|
+
运行时判据(活进程实测):桌面端注册表的 Config schema 只有 `default`/`selectedDefault`、**没有 `modeSelectionEnabled`**
|
|
383
|
+
⇒ 与 rc.1 之后的注册表契约一致;`dsh-web-app` 在 `desktop` 的 bundles 里 ⇒ `agentPresets` 注册表在位;
|
|
384
|
+
插件用到的 `tools/commands/systemPrompt/skills/webServer` 全是标准服务;预览 URL 走**同源相对路径**、
|
|
385
|
+
origin 从活 webServer 的 host/port 推导(`previewTools.previewOrigin`,无硬编码端口、不依赖 `DSH_WEB_URL`);
|
|
386
|
+
`dsh.engines.dsh` 的判定用 `semver.satisfies('0.1.7-rc.2', '>=0.1.7-rc.1', { includePrerelease: true })` = true。
|
|
387
|
+
⇒ **运行时不需要改码**。
|
|
388
|
+
但**安装通道有两处硬阻断**(这才是要"完善"的地方):
|
|
389
|
+
① `@deepseek-ai/dsh/lib/bin.js` 的 `rejectElectronProfile()` 对 `desktop` profile 的**启动与 `plugin` 两条路**
|
|
390
|
+
都 `program.error(...)`,且**无环境变量旁路** ⇒ README 那条 `dsh plugin --profile desktop add` 必然失败;
|
|
391
|
+
② 桌面端自带 pnpm **11.7.0** + profile 的 `nodeLinker: hoisted` 下,`pnpm add <https…tgz>` 失败:
|
|
392
|
+
`ERR_PNPM_MISSING_TARBALL_INTEGRITY`("its lockfile entry has no integrity field")。
|
|
393
|
+
隔离复现(`%TEMP%` 干净工作区 + 同一份 pnpm/工作区文件):**去掉 `nodeLinker` 即成功**、
|
|
394
|
+
**换系统 pnpm 11.21 亦成功**、**`file:` 规格亦成功** ⇒ 是 pnpm 11.7 在 hoisted+tarball-URL 组合上的缺陷,
|
|
395
|
+
与插件代码无关;但它让"用发布资产 URL 装"这条**唯一公开通道**在桌面端不可用。
|
|
396
|
+
⇒ 新增 `scripts/install-desktop.mjs`:只用**应用自带** pnpm/Node(避免用系统 Node 重编 profile 里的
|
|
397
|
+
cpu-features/ssh2 等原生模块),把本包打成 tgz 放进 `<profile>/.dsh-plugins/` 后以**相对 `file:`** 安装
|
|
398
|
+
(`file:` 由 pnpm 本地算 integrity,实测在 hoisted 下可用;tarball 落在 profile 内部 ⇒ 不会因外部目录被删
|
|
399
|
+
而让桌面端下次 `pnpm install` 失败),再回写 `dsh.profile.bundles`;失败即还原 profile 文件。
|
|
400
|
+
显式传 URL 时先按原样试,撞上该缺陷则回退到系统 pnpm(其实测写出的锁文件带 `resolution.integrity`,
|
|
401
|
+
桌面端自带 pnpm 随后 `install` 报 "Already up to date")或 profile 内 `file:` 兜底。
|
|
402
|
+
smoke §41c 用**桩工具链 + 临时 DSH_HOME** 跑 `--dry-run` 钉住探测/规划/不写盘。
|
|
403
|
+
- `ppt_shot overview=true`:deck.html 整览截图(N 页纵排,>30 截前 30)——跨页节奏视觉审阅。
|
|
404
|
+
|
|
405
|
+
## 3. 核心机制解读(改这些地方 = 改项目哲学,须更新 01 文件)
|
|
406
|
+
|
|
407
|
+
1. **设计意图声明制**:重叠合法性来自"设计时声明、审阅时对照",不是元素类型。底裤 = content-collision 永不可声明。
|
|
408
|
+
2. **双轨审阅**:数字门禁(快、可全自动)+ 视觉审阅(截图、防估算漏报)。估算档被真实任务证明会骗过(第 6 页溢出截图才发现),视觉审阅因此是"必要"而非"可选"。
|
|
409
|
+
3. **声明制成本控制**:四个缓冲——`autoDeclare` 一键批量(重叠)、`declaredClosure` 嵌套闭包(相邻层声明隔层自动)、`overlapMode: lenient`(草稿)、`role: decoration`(装饰豁免重叠)+ `contrastExempt`(对比度误报豁免)。
|
|
410
|
+
4. **度量一致性**:三端共用 normalizePage + wrapLines;verify 的 TOL(1px) 与 export 的 FIT_TOL(1) 相同 ⇒ 门禁通过 = 导出不缩字 = 承诺兑现(14pt/12pt 下限)。
|
|
411
|
+
5. **引擎质量优先**:auto=pptd;python-pptx 降级为表格,仅显式切换 —— 与设计文档"自动回退链"存在 C1 冲突(待确认,见 01 文档)。
|
|
412
|
+
|
|
413
|
+
## 4. 验证矩阵(质量保障体系)
|
|
414
|
+
|
|
415
|
+
| 层 | 手段 | 触发点 | 覆盖 |
|
|
416
|
+
|---|---|---|---|
|
|
417
|
+
| L0 单测 | `scripts/smoke.mjs`(262 断言) | 每次改动后 | schema/渲染/校验/导出/导入/媒体/背景/声明制(含闭包)/美学/Quick/引擎/样式回环/噪声过滤/**连线方向** |
|
|
418
|
+
| L1 真实回归 | `scripts/regression-real.mjs` | 每次改动后(有资产则跑) | 19 页产能 deck(0 错误 + 0 缩字)、WPS UTF-16 文件导入 |
|
|
419
|
+
| L2 数字门禁 | render→verify 逐页 | 每页制作后(工作流内) | overlap/出界/溢出/对齐 |
|
|
420
|
+
| L3 视觉审阅 | shot + read_image / **Office 真渲染 + read_image**(v0.8,有条件) | 每页/整体(工作流内) | 构图/断行/对比度/孤字(估算漏报兜底)/真实观感 |
|
|
421
|
+
| L4 产物回读 | zip+XML 解析 / python-pptx 回读 / **导出 parity 自证**(表·图·**线方向逐条从 OOXML 反推端点**,`report.parity`) | 交付前(每次 export 自动) | 页数/尺寸/字号/几何/**连线方向**/结构不变量(无非法嵌套 xfrm) |
|
|
422
|
+
| L5 实机打开 | 用户在 PowerPoint/WPS 打开 | 交付验证(用户侧) | 真实渲染/字体/兼容 |
|
|
423
|
+
|
|
424
|
+
> L4 的 parity 自证是 2026-09-14 连线方向事故(预览对、成品错)的机器防线:**凡"预览层"与"成品层"各有一套编码的元素,都要有从产物反推的自证**。新增元素类型时同步补断言(见 docs/05「跨层验证」)。
|
|
425
|
+
|
|
426
|
+
## 5. 已知边界(诚实声明,README §已知边界 同步)
|
|
427
|
+
|
|
428
|
+
- 文本度量 = 估算档(保守系数);**实测档(渲染回读精确测量)为下一里程碑 R1**。
|
|
429
|
+
- chart 在 python-pptx 引擎下降级为表格;pptxgenjs 第三引擎未实现。
|
|
430
|
+
- 导入器不做布局逆推(内容保真、版式可从新);渐变/阴影/动画/超链接不支持。**模板双轨(v0.9)缓解**:模板类场景直接看真相层(template.pptx + previews 真渲染),不依赖导入近似;导入近似仅剩"从零改版"场景的参考价值。
|
|
431
|
+
- **模板参考通道依赖 Office**:无 Office 时真相层缺失 → 退化为 YAML 骨架 + styleAudit(如果有);收纳时无 Office 也不生成 previews/。
|
|
432
|
+
- client 端 dsh 网页内嵌预览面板未实现(设计 §8 已拍板形态,属里程碑 M4)。
|
|
433
|
+
- 无 LibreOffice/Office 环境下无法本地把 .pptx 转 PDF/PNG;视觉验证靠 ppt_shot(预览 HTML)+ 用户实机打开。
|
|
434
|
+
- verify 的启发式建议(对比度/孤字/安全区)只处理实底颜色与估算换行;渐变/图片上文字不参与。
|
|
435
|
+
|
|
436
|
+
## 6. 性能与规模(实测记录)
|
|
437
|
+
|
|
438
|
+
- 19 页 / 530+ 元素 deck:render+verify ≈ 秒级;export ≈ 秒级;无阻塞项。
|
|
439
|
+
- **M2 基准(v0.12,D5 实测 100 页)**:render 70-406ms;**measure 220-320s**(顺序 3.2s/页 → 并发 4 + 独立 user-data-dir 2.2s/页;budget 缩小无益);20 页常用 ≈ 45s。
|
|
440
|
+
- 截图:每页一次 Edge 启动(临时 profile),多页串行——100 页 deck 会慢(路线图:并发/复用 profile 池);**整览截图(overview)单次一次性**。
|
|
441
|
+
- zip 用纯 JS deflate(zips.js),导出大媒体(>10MB 图片)时内存与耗时需关注(路线图:流式写/原样复制)。
|
|
442
|
+
- 未设 scale 上限校验:超大尺寸 deck(>4096px)Edge 截图可能超限(路线图:scale 探测);overview 整览 >30 页自动截前 30。
|
|
443
|
+
|
|
444
|
+
## 7. 代码改动的波及面速查(改 X 要同步 Y)
|
|
445
|
+
|
|
446
|
+
| 改了 | 必须同步 |
|
|
447
|
+
|---|---|
|
|
448
|
+
| schema.js 字段 | layout/render/verify/export/import 对应消费;SCHEMA_REF(scaffold.js);README 视觉子集表;docs/01 决策记录 |
|
|
449
|
+
| layout.js 度量 | smoke 溢出断言可能翻转;工作流提示词"下限=用户指令"话术(2026-09-06 修订);README/SCHEMA_REF 同步 |
|
|
450
|
+
| verify.js 规则 | 01 文档冲突清单(C3/C11);README 布局工程规则;提示词铁律 2(声明制/出界分级) |
|
|
451
|
+
| tools.js 工具 | SCHEMA_REF、README 工具面列表、工作流提示词入门段 |
|
|
452
|
+
| export/import | README 视觉子集表;fixtures 真实回归 |
|
|
453
|
+
| 路由/命令 | 01 文档路由决策;测试反馈语义检测断言 |
|
|
454
|
+
| `agent-presets/ppt/agent.cordis.yml`(宿主升级) | 必须与本机随包 `standard` 预设逐行对齐(本文件 = standard 全量 + 本插件人格,**不含插件行**;**不要手工维护**:以随包 standard 为底重新生成);参照物位置/格式随宿主版本变化(0.1.6- `dsh-agent-presets/presets/standard/agent.cordis.yml`;0.1.7+ `dsh-web-app/presets/standard.patch.yml`),`scripts/check-preset.mjs` 两种都认并在输出里写明认到哪种;`npm test` 里的预设自检(`--require`);README §0.1 宿主基线 |
|
|
455
|
+
|
|
456
|
+
## 8. 目录与产物
|
|
457
|
+
|
|
458
|
+
```
|
|
459
|
+
src/ 源码(唯一编辑入口;build.mjs 复制到 lib/)
|
|
460
|
+
lib/ 构建产物(src/ 的逐字节拷贝;**2026-09-16 起已入库**——创意工坊走 git 安装只拿已提交内容;勿手改,改 src 后 build 一并提交)
|
|
461
|
+
scripts/ build.mjs / smoke.mjs / regression-real.mjs
|
|
462
|
+
examples/ smoke 系列自建工程(smoke/media-smoke/overlap-smoke/v3-smoke/scaffold-smoke + 构建产物)
|
|
463
|
+
fixtures/ 真实回归资产(WPS UTF-16 文件等,记录来源)
|
|
464
|
+
docs/ 本档案库(00 设计源 / 01 需求目标 / 02 技术报告 / 03 更新日志 / 04 争议 / 05 迭代流程)
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## 9. v0.15.0 增补(反馈二:保真交付与度量修正)
|
|
468
|
+
|
|
469
|
+
### 9.1 splice.js — 保真交付(A3/A9)
|
|
470
|
+
- `spliceIntoSource`:deck 第 D 页(pptd 导出单页 XML + 媒体包)替换进 source.pptx 第 P 页。**只改源幻灯条目与其 rels**(rels 保留源布局/备注、媒体关系追加 + rId 重编号),其余条目经 zipRead→zipWrite 原样重打包(字节级不变,SHA256 自证)。
|
|
471
|
+
- `sliceSource`:单页化——sldIdLst 只留 1 条(完整捕获 `<p:sldId .../>` 标签)、presentation.xml.rels 删非目标 slide 关系、Content_Types 删其余 Override、其余页/备注/rels 丢弃;**母版/布局/主题/媒体全保留**(横幅/页脚随母版)。
|
|
472
|
+
- 数据源契约:`[Content_Types].xml` 第一条目不被移动(原是第一条保持第一条);媒体同名复用源条目;缺失媒体 1×1 占位。
|
|
473
|
+
- **两类已修复的坑**(E2E 捕获):①幻灯 rels 与 presentation rels 混淆(写错位置 → PowerPoint 判损坏);②sldId 正则丢 `/>`。
|
|
474
|
+
|
|
475
|
+
### 9.2 度量与校验变更
|
|
476
|
+
- `charWidth`:CJK/全角标点 bold ×**1.06**(微软雅黑粗体实测 1.03-1.07);其他字符 bold ×1.05(0.65→0.69 保持既有相对比)。
|
|
477
|
+
- verify `carrierColorOf`:载体颜色支持渐变(取最接近中心 stop;pos 0-100 与 0-1 自适应)与 {color,alpha}(≥0.85 才算承载);载体元素放宽为"非 text/line"(shape/table/image 均可承载)。
|
|
478
|
+
- verify near-align:**包含关系成对(内边距 ≤6px)豁免**(有意内边距不算疑似未对齐)。
|
|
479
|
+
- validatePage:**元素级 TEXT_STYLE_KEYS 报错**(样式必须在 content 内)+ YAML 解析错误附笔误提示。
|
|
480
|
+
- import:theme colors = 原稿**全量色板**(c1-7 高频 + c8 起扩展);媒体引用一致性自检(缺失 → 提示 + 1×1 占位)。
|
|
481
|
+
- export:缺失媒体占位兜底(mediaPlaceholders 进报告);msrender `renderPptxToPng({pages})` 按页 COM 渲染(png 文件名 = 原页号)。
|
|
482
|
+
README.md 使用者入口(轻量,指向 docs/)
|
|
483
|
+
```
|