上一期给了办公四件套的清单。这一期把 docx / pptx / xlsx 三个 skill 的 scripts/ 目录拆开看:它们真正值钱的不是 SKILL.md 里那几百字提示词,是那十来个你可以单独拿走、脱离 skill 也能跑的 Python 脚本。
一个 .docx 打不开,用户不会说「模型幻觉了」,只会觉得你这套东西不能用。办公产出类之所以是 skill 生态里最容易见效的一类,就在于它有客观 oracle——文件要么符合 schema,要么不符合,不需要任何人的主观判断。本期拆的就是这个 oracle 怎么被接回循环里:unzip → merge_runs → 改 XML → zip -Xr → validate.py。每一步都点名到具体脚本路径,你可以一个一个拿走用。
# Claude Code:四件套在 document-skills 这个插件里
/plugin marketplace add anthropics/skills
/plugin install document-skills@anthropic-agent-skills
# 只想要脚本、不想装 skill:直接 clone,scripts/ 目录可独立使用
git clone https://github.com/anthropics/skills
# 系统依赖(docx/SKILL.md 自己列的四项)
# docx(npm,环境预装)· pandoc · LibreOffice(soffice)· pdftoppm(Poppler)
unzip -q doc.docx -d unpacked/
find unpacked -type l -delete # 剥掉符号链接
python scripts/merge_runs.py unpacked/ # 先把碎掉的 run 合并
# 手工编辑 unpacked/word/document.xml —— 不要 reformat
(cd unpacked && rm -f ../out.docx && zip -Xr ../out.docx .)
python scripts/office/validate.py out.docx --original doc.docx
这六行里没有一行是装饰:find -type l -delete 防的是符号链接被打进包;merge_runs.py 决定你能不能在 XML 里搜到一句完整的话;zip -Xr 且必须 cd 进目录里打包,否则压出来多一层目录、Word 直接不认;--original 让校验器拿原件做基线。
unpacked 外面执行 zip -r unpacked/(多一层目录)· 用编辑器「格式化 XML」后保存(空白进了 w:t,文本就变了)· 跳过 merge_runs.py 直接搜字符串(搜不到,然后以为文档里没有这句话)。validate.py 接受一个解包目录或一个打包文件,另有四个开关:--original 拿原件做基线、--auto-repair 自动修、--author NAME 开修订校验、-v 详细输出。退出码:0 全过、1 校验失败、2 参数或文件错——这就是可以直接写进循环的那个信号。
schemas/ 里的 XSD 逐个 part 校。这是「文件还能不能打开」这件事最接近真相的判据。paraId / durableId 超出 OOXML 允许的取值范围;w:t 上带空白却缺 xml:space="preserve"。两个都是手改 XML 的高频副作用。--author 就逐字比对原件,凡是文本变了却没有被 <w:ins>/<w:del> 记录下来的,都算错。「假装留了痕」这件事从此有人管。Word 会把一句话切成十几个 <w:r>:拼写检查、语言标记、rsid 都会在中间插刀。于是 XML 里根本不存在「the quick brown fox」这个连续字符串,模型按字面查找必然扑空——然后它会开始瞎猜位置,那才是真正坏文件的起点。先合并再编辑,是把一类幻觉从源头掐掉。
validate.py 对 xlsx 明确跳过、不做 schema 校验。你以为「跑过校验了」,其实什么都没校——xlsx 的 oracle 是下一节的 recalc.py。python scripts/accept_changes.py in.docx out.docx # 接受全部修订
python scripts/comment.py unpacked/ "这里口径不一致" # 目录模式(编辑 XML 时用这个)
python scripts/comment.py unpacked/ "同意" --parent 0 # 回复第 0 条
python scripts/comment.py contract.docx "问一句" -o annotated.docx
python scripts/office/validate.py out.docx --original doc.docx --author "BigCat"
目录模式下 comment.py 会把 <w:commentRangeStart> / <w:commentRangeEnd> / <w:commentReference> 三个片段打印出来给你放进 word/document.xml——批注的挂载点由人决定,编号与关系文件的记账由脚本做。这个分工是整套设计的缩影。
python scripts/thumbnail.py template.pptx template-thumbs # 先看版式,第二个参数务必给
python3 -c "import sys,zipfile; zipfile.ZipFile(sys.argv[1]).extractall('unpacked')" deck.pptx
python scripts/add_slide.py unpacked/ slide2.xml --after slide2.xml # 复制页,别手抄
# 调序 / 删页 = 改 ppt/presentation.xml 里的 <p:sldIdLst>
python scripts/clean.py unpacked/ # 删完清孤儿 slide/media/rels
(cd unpacked && rm -f ../out.pptx && zip -Xr ../out.pptx .)
python scripts/office/validate.py out.pptx --original deck.pptx
两条硬规矩:add_slide.py 直接作用在 .pptx 上时必须带 -o,否则原地改写输入;模板派生的 deck 必须带 --original,否则模板自带的报错会把你自己的错淹掉。
python scripts/recalc.py output.xlsx [timeout_seconds] # 默认 30 秒
# LibreOffice 算完每一个公式并原地重写文件,返回 JSON:
# status(success|errors_found) · total_formulas · total_errors · error_summary
errors_found 的退出码是 0;只有 JSON 里出现 error 键(= 压根没重算成)才非零退出。所以干净的退出码不等于干净的工作簿,必须去读 JSON 正文。另外 recalc 全绿只证明公式能算,不证明它算得对——所以先写两三条公式核一下口径,再动手铺整张表。# ① 文件 QA
python scripts/office/validate.py out.pptx --original src.pptx
# ② 内容 QA:读回来,抓占位符
markitdown out.pptx | grep -iE "\bx{3,}\b|lorem|ipsum|\bTODO|\[insert"
# ③ 视觉 QA:转 PDF 再出图,自己看一眼
python scripts/office/soffice.py --headless --convert-to pdf out.pptx
pdftoppm -jpeg -r 150 out.pdf slide && ls -1 slide-*.jpg
三层各挡各的:schema 挡打不开,grep 挡「xxx / TODO / 请在此处填写」漏进终稿,出图挡文字溢出框和白底白字。少一层就漏一类,而这三类没有任何交集。
pip install 'markitdown[all]';据 GitHub 仓库页 2026-09 约 18.4 万 star、MIT)data_only=True 读出来再保存,会把每一条公式永久替换成字面值;.xlsm 不带 keep_vba=True 就丢宏。这是「不坏文件」这件事最容易破功的地方,而且它不报错。npm install docx,文档在 docx.js.org)只能生成与构造,读不进现成的 .docx。docx 编辑走解包路线不是偷懒,是没有别的路。markitdown file.xlsx 或 pandoc -t markdown file.docx。解包是为了改,不是为了看。npm install pptxgenjs,据 GitHub 仓库页 2026-09 约 6.2 千 star、MIT)写 PPT。生成完照样走 validate 与视觉 QA,但不必碰解包。skill_id,零安装(见 Skill 13)。这条流水线是给 Claude Code 与自建环境用的。先拿这三个脚本(哪怕你完全不用这套 skill):scripts/office/validate.py(退出码即结论,docx/pptx 通用)· scripts/recalc.py(xlsx 唯一的 oracle)· scripts/merge_runs.py(编辑 docx 之前必跑)。
按格式走:docx = unzip → merge_runs.py → 改 word/document.xml → zip -Xr → validate.py --original;pptx = thumbnail.py 看版式 → add_slide.py 复制页 → clean.py 清孤儿 → validate.py --original;xlsx = openpyxl 写 → recalc.py 重算 → 读 JSON 正文。
收尾三层:validate.py(文件)· markitdown | grep(内容)· soffice.py + pdftoppm(视觉)。三层都要,顺序别反。
明确别做:手抄 slide XML 去复制页 · 在 unpacked 外面打 zip · 把 recalc 的 0 退出码当成没错 · 对模板派生文件不加 --original · 用 data_only=True 读完顺手 save · 以为 validate.py 也校了 xlsx。
一句话判据:你这一步有没有一个能退出非零的东西在看着?没有,就别让模型做这一步——把它写成脚本,或者把它从流程里删掉。
[Content_Types].xml 声明每类 part 是什么。它和 _rels/ 里的关系文件一起构成账本——手工增删文件而不记账,就是坏文件最常见的方式。<w:r>)merge_runs.py 要合并的就是它。schemas/ 里放的是 ISO-IEC29500-4:2016 与 ECMA 第四版两套,外加 mce、microsoft 两组扩展。mc:AlternateContent)。它也是为什么校验不能只认一套 schema。<w:ins> / <w:del> 把改动本身写进文档。validate.py --author 校的就是「所有文本差异是否都留了痕」。thumbnail.py / add_slide.py / clean.py 的参数,以及文件 QA + 内容 QA + 视觉 QA 三层的原始写法recalc.py 的 JSON 字段与退出码约定、两次 load_workbook 的读法、keep_vba 与合并单元格等坑soffice.py、validators/(base · docx · pptx · redlining)、schemas/pip install 'markitdown[all]';据 GitHub 仓库页 2026-09 约 18.4 万 star、MITnpm install pptxgenjs;据 GitHub 仓库页 2026-09 约 6.2 千 star、MITskill_id)与十条装法,本期的前置zip -Xr 这种命令写进 SKILL.md 当提示词,和把它包成一个脚本,差别在哪?recalc.py 返回 errors_found 却退出码 0,这是 bug 吗?#REF!」和「重算这件事失败了」是两回事,前者需要人看、后者需要流程停。把两者压进同一个退出码才是真 bug。代价是你必须读 JSON 正文——设计者用一句文档换掉了一个假阴性。