Release standalone PPT template workflow 0.5.0

This commit is contained in:
2026-10-10 21:02:47 +08:00
commit 4e3271ff62
303 changed files with 190946 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
# 可移动的 PPT 消费工程
接口版本:`ppt-project/v1`。内容日期:2026-10-10。实现为 [ppt_project.py](../scripts/ppt_project.py),基础模板接口仍以 [template-interface.md](template-interface.md) 为准。本接口不改变 `template_ref.py`,不安装全局工具,不初始化 Git,不提交或发布。
工程把正文、页面源、数据、素材、模板源、必要工具和依赖说明放在同一目录。Git 可追踪这些输入;设备相关绝对路径、缓存和导出物在运行时重建。一次工程固定一个 template 版本和一个 theme,页面顺序由 `pages` 数组表达,重排页面无需更换页面 ID。
## 初始化与作者工作流
解析和工程管理要求 Python 3.10+,只用标准库;实际 builder 的依赖由工程自行声明。消费方明确逐页制作时,保留 `interactive-page`,不要因建立工程而自动批量创作。
```sh
python3 /path/to/skill/scripts/ppt_project.py init \
--project /path/to/report \
--manifest /path/to/released-template/manifest.json \
--theme chosen-theme --title '报告标题' --project-id example-report \
--route native --workflow interactive-page
```
`--project` 对六个命令均必需。`init` 另要求 `--manifest`、`--theme`、`--title`;`--project-id` 默认生成合法 UUID,显式值为小写字母/数字及单连字符组成的 ID。路线默认为 `native`,另支持 `image-rebuild`;工作流默认为 `interactive-page`,另支持 `batch`。统一的 `--theme` 在消费 v1 清单时映射为其 `mode`,不改变基础解析器的 v1 CLI。
目标必须不存在或为空目录。初始化在相邻临时目录完成验证后整体重命名,失败不会把半成品合并入已有目录;非空目标保留。模板包根目录为选中 manifest 的父目录,按相对路径复制到 `vendor/template/`,排除所有 `exports/`、缓存、虚拟环境、Git 和本地配置。遇到复制范围内的 symlink 拒绝且不跟随。源清单一般命名为 `manifest.json`;选择其他文件名时保留原文件并添加相同字节的 `manifest.json`,有冲突则拒绝。
初始工程是诚实骨架:没有页面,`build.entrypoint` 为 `null`。`check` 可以通过并返回 `build_ready: false`;`build` 明确拒绝,不生成占位 PPTX。作者加入页面、数据与真实 builder 后执行:
采用本包原生编译器时,可先运行 `python3 /path/to/skill/scripts/prepare_native_project.py --project /path/to/report`。它只接受没有 builder、依赖/字体/工具声明为空的已锁定骨架,复制项目内 compiler/wrapper,写入 `tools/native_builder.py` 和环境声明,并保留旧作者锁。审阅这些改动后显式 `lock`;不覆盖已有 builder 或已配置环境,不改变工程 schema 1。完整行为见 [运行库与出处](runtime-and-provenance.md)。
该通用 builder 读取各页已完成的 SVG 和 Markdown 讲稿;`content.json`/`data` 仍是锁定作者输入,不会自动绑定到 SVG。需要数据驱动生成的消费方应提供自己的明确绑定或预处理步骤。
```sh
python3 tools/ppt_project.py lock --project .
python3 tools/ppt_project.py check --project .
python3 tools/ppt_project.py resolve --project .
python3 tools/ppt_project.py build --project .
python3 tools/ppt_project.py pack --project . --out /outside/report-source.zip
```
`resolve` 是可选的制作准备步骤,`build` 会自动执行。`check` 只读且不运行 builder;`resolve` 只生成运行时记录且不运行 builder。所有命令输出 JSON,输入错误返回退出码 2 和 stderr JSON。初始化和移动工程时均不复用另一台机器的选择记录。
## 目录与字段
```text
report/
project.json
project.lock.json
template.lock.json
environment.json
README.md
.gitignore
.gitattributes
pages/<stable-id>/source.svg
pages/<stable-id>/content.json
pages/<stable-id>/notes.md
data/
assets/
tools/template_ref.py
tools/ppt_project.py
tools/build_deck.py # 作者提供并声明的可选入口
vendor/template/manifest.json
build/local/ # 可丢弃的本机绝对路径记录
build/tmp/ # 本次唯一临时产物
exports/deck.pptx # 可丢弃的构建结果
```
`project.json` 的固定基础字段如下;示例中页面和 builder 由作者加入,初始值为 `pages: []`、`entrypoint: null`。`data`、`assets`、`inputs` 即便为空也保留数组。所有路径字段为相对于工程根的 POSIX 路径。
```json
{
"schema_version": 1,
"project_id": "example-report",
"title": "报告标题",
"template": {
"manifest": "vendor/template/manifest.json",
"theme": "chosen-theme",
"lock": "template.lock.json"
},
"authoring": {"route": "native", "workflow": "interactive-page"},
"environment": "environment.json",
"pages": [
{
"id": "opening",
"layout": "declared-layout-id",
"source": "pages/opening/source.svg",
"content": "pages/opening/content.json",
"notes": "pages/opening/notes.md",
"data": ["data/observations.json"],
"assets": ["assets/figure.svg"]
}
],
"build": {
"entrypoint": "tools/build_deck.py",
"output": "exports/deck.pptx",
"inputs": ["tools/helper.py"]
}
}
```
页面 ID 必须稳定且唯一,`layout` 必须在模板 manifest 中声明。三项页面文件名固定为 `pages/<id>/source.svg`、`content.json`、`notes.md`。数据文件可用 JSON 数组等合法 JSON 值;页面内容结构由 builder 定义。工具拒绝重复 JSON 键和非标准 NaN/Infinity。
声明文件必须存在。路径字段拒绝绝对路径、`..`、反斜杠、URL/盘符形式以及 symlink,包括仍指向工程内部的 symlink。SVG `href` 和 CSS `url(...)` 的本地资源、Markdown 行内及引用式链接递归纳入作者输入;相对于源文件解析,允许 `../../assets/...` 等仍在工程内的引用,拒绝逃逸、绝对路径和缺文件。SVG 允许 `#id` 与内嵌 `data:`;Markdown 允许 HTTP(S)/mailto 来源链接和 `#anchor`。这不是完整的 HTML/JavaScript/Python 依赖分析器:builder 导入的辅助文件、动态读取的数据和自定义格式资源仍须在 `build.inputs`、页面 `data`/`assets` 中显式声明。
`vendor/` 下的第三方 Markdown 说明作为固定字节锁定,不递归跟随其工作流或参考文档链接;裁剪后的编译器快照可以保留完整归属说明而无需携带无关文档库。实际构建所需文件仍须显式列入 `build.inputs`。工程 README、页面讲稿及其本地引用继续检查;此例外不豁免 SVG 素材引用。
`environment.json` 初始内容为:
```json
{
"schema_version": 1,
"python": {"minimum": "3.10"},
"packages": {},
"fonts": [],
"tools": []
}
```
作者应补充实际 Python/包版本、可重建的安装说明、外部工具与字体名称、版本、来源及许可。`check` 校验此文件的基本结构、存在与锁定,不探测所有平台的字体和工具,不自动下载或安装依赖。字体不会因写入清单而获得分发许可;工程只纳入确有许可的字体文件,并显式列入输入。设备设置写入被忽略的 `environment.local.json`,不要把用户名、绝对路径、凭据、私有配置写入受版本管理的清单。
## 两层锁与协作
`template.lock.json` 使用 `schema_version: 1`,包含 `template_id`、`version`、`theme`、工程相对 `manifest` 和有序 `inputs`。每个 input 为 `{"path": "vendor/template/...", "sha256": "..."}`。复用模板解析器验证 released 状态、路线、工作区和选择后,锁定全部复制的模板源,包括未激活 theme;不把 review `exports/` 当源。模板新增、删除、字节变化,或改变 template/theme 选择均使验证失败。
`project.lock.json` 使用 `schema_version: 1`、`project_id` 和相同形式的有序 `inputs`,包括 `project.json`、环境说明、页面 source/content/notes/data/assets、本地资源闭包、builder 与 `build.inputs`、随包两脚本,以及 README/Git 元数据。运行输出不进入锁。
`lock` 先验证模板锁未变,再校验作者输入和引用,原子更新作者锁。它不会修改模板锁、自动接受升级或掩盖缺文件。模板需要升级时,新建显式选择新版本的工程,审阅接口变化后迁移作者输入;不要通过反复 `lock` 接受发布模板漂移。
协作者一起审阅 source、data、project 与锁差异。多人编辑不同稳定页面时仍由工程负责人整合 `project.json` 页序与锁;不要把生成的绝对路径收据作为可移植状态提交。生成的 `.gitignore` 忽略 `build/`、`exports/`、`.venv/`、`node_modules/`、`__pycache__/`、`*.pyc` 和 `environment.local.json`,不忽略正文、数据、素材或讲稿;`.gitattributes` 为 JSON/Python/Markdown/SVG/CSV/TSV 声明 UTF-8/LF,为 PPTX/PDF/PNG 等声明二进制。
## 构建与移动重建
`resolve` 先 `check`,在 `build/local/` 写入本次唯一 ID 的 selection/style JSON。每页使用对应 layout;空骨架生成工程级记录。记录的绝对路径只对当前工程位置有效,v1 模板仅生成 selection。它们不进入 Git 或 pack。
builder 必须是工程内 `.py` 文件。执行前审阅入口代码及依赖;`build` 意味着运行作者声明的工程 Python 代码,不是沙箱。工具先验证锁并生成新收据,再使用当前 `sys.executable`、`shell=False`、工程根为 cwd,固定调用:
```text
python /absolute/project/tools/build_deck.py \
--project /absolute/project \
--output /absolute/project/build/tmp/<new-unique-id>.pptx
```
入口只能通过固定的 `--project`、`--output` 接收工程位置与本次输出位置,不接受 shell 字符串配置;退出 0 后必须实际产生新文件,最长运行 600 秒。工具检查 ZIP 成员、CRC、必要 XML 根和 slide 数量/presentation 页表,重新验证输入锁,然后原子替换 `build.output`。输出必须位于 `exports/` 且扩展名为 `.pptx`。builder 失败、超时、缺输出、无效 ZIP 或输入被修改时,旧产物保留;不能用已有 exports 推断本次成功。成功 JSON 返回本次输出的 SHA-256。
`pack --out` 必需,必须是工程外 `.zip` 路径。既有 ZIP 或同名 `.zip.sha256.json` 均拒绝覆盖。包仅包含已锁作者输入、模板源、两层锁和必要元数据;未声明文件、`build`、`exports`、Git、虚拟环境、缓存及本地配置不进入包。固定路径排序、ZIP 时间与权限,同一输入在同一压缩环境下得到相同 ZIP 字节;不同 Python/zlib 版本未保证逐字节压缩结果相同。另写 JSON SHA-256 收据,收据 archive 字段只存文件名。
移动整个源工程或解压 ZIP 后,先配置 `environment.json` 记录的依赖与有许可的字体,再运行工程随包的 `tools/ppt_project.py check` 和 `build`。不需要访问原模板目录、原 Skill 安装路径、原工作区或旧 exports。缺少外部工具时,由 destination 环境提供,不用旧输出冒充重建。
## 证据与限制
维护仓库的工程专项检查使用合成 released 模板与两页工程,覆盖锁定、路径/资源/symlink 拒绝、作者及模板漂移、失败保留旧产物、确定性源码包、缓存排除,并在原路径不存在时用解压后的工具实际生成新的基本 PPTX;修改数据后再次构建并检查 slide XML 的新数据。Skill 0.5 的独立编译器检查另覆盖原生导出、字体身份、素材边界、项目内 runtime 和移动重建;安装包不要求维护仓库的检查脚本在运行时存在。
这些证据证明本机新路径下的源码可移动与构建接口行为,不代表已在另一台物理设备执行,也不代替渲染、字体一致性、完整 OOXML 关系图、PowerPoint 打开/实际编辑或投影可读性验收。按实际作者路线另外完成原生结构、逐页渲染和产品验收。