Back to skills
SKILL.md
Doc Redline
ASecurity核对文书两个版本的差异,并可一键产出带修订痕迹的 Word。比对「纸质/扫描/照片稿」与「电子稿(.docx)」究竟改了什么,只报真实增删改、不把排版残留误判成删除;双方都是 .docx 时,可生成原生 Word 修订模式文件(像有人在 Word 里开着修订模式把旧稿改成新稿),逐条接受或拒绝。当用户说「比较打印稿和 Word 稿」「照片版和电子版有什么不同」「对方把哪几条删了」「两个版本对照一下」「给我一份修订稿/红线稿」时触发。产出:删改位置 + 被删原文(可原文引用)+ 对谁有利不利,可选交付修订版文件。核心是四类假阳性识别:Word 自动编号、页眉页脚、页外浮动文本框、域代码/修订痕迹。
- 9 stars
- 0 votes
- 0 copies
- 0 views
- Added September 25, 2026
Security analysis
100/100Pro scans all 13 files and shows the line behind each finding
npx -y skills add CSlawyer1985/legal-skillhub --skill doc-redline --agent claude-codeAre you the author of Doc Redline?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/cslawyer1985-doc-redline)---
name: doc-redline
description: 核对文书两个版本的差异,并可一键产出带修订痕迹的 Word。比对「纸质/扫描/照片稿」与「电子稿(.docx)」究竟改了什么,只报真实增删改、不把排版残留误判成删除;双方都是 .docx 时,可生成原生 Word 修订模式文件(像有人在 Word 里开着修订模式把旧稿改成新稿),逐条接受或拒绝。当用户说「比较打印稿和 Word 稿」「照片版和电子版有什么不同」「对方把哪几条删了」「两个版本对照一下」「给我一份修订稿/红线稿」时触发。产出:删改位置 + 被删原文(可原文引用)+ 对谁有利不利,可选交付修订版文件。核心是四类假阳性识别:Word 自动编号、页眉页脚、页外浮动文本框、域代码/修订痕迹。
license: MIT
metadata:
slug: doc-redline
displayName: 文书比对与修订稿生成
author: 陆凌燕(北京德恒(无锡)律师事务所)
agent_created: true
version: 1.0.2
---
# 目的
回答一个问题:**纸质稿相对电子稿,究竟增删改了什么。** 达成标准三条,缺一不可:
1. **不漏**——每一处真实增删改都列出,附被删/被改原文;
2. **不假**——排版与载体造成的差异(自动编号、页脚、页外浮动对象)不得报成“删除”;
3. **不乱**——每条差异能追溯到“第几条、哪个位置”,并说明改动对哪一方有利。
> 纸质稿是“看起来的样子”,电子稿是“存下来的样子”。两者不同 ≠ 内容被删。
> 本 skill 的全部价值在于:**把渲染差异从内容差异里摘干净。**
## 触发场景
- 合同/协议:对方发来电子稿、签章版是扫描件或照片,要核对改了什么
- 诉讼文书:双方各持一版,核对庭上提交稿与送达稿是否一致
- 内部流转:纸质审批稿与最终 Word 稿是否同源
## 铁律(禁止项)
1. **禁止肉眼通读两边下结论。** 必须走完:照片切条回读 → 转写成文 → 脚本比对 → 逐条回读确认 → 才写结论。肉眼对 200 字以上的段落必漏。
2. **禁止把以下四类报成“内容删除”**(均为渲染/载体差异):
- **Word 自动编号**(`w:numPr`):正文 XML 里根本没有“三、”“1、”这些字,是渲染时按 `numbering.xml` 生成的。直接取 `<w:t>` 会得到光秃秃的标题,一比就“发现”编号被删了。
- **页眉/页脚**(`word/footer*.xml` / `header*.xml`):独立部件,`w:sectPr` 里的 `footerReference` 决定作用于哪些页。
- **页外浮动对象**:`wp:positionV` 以页为基准且偏移 ≥ 页高 → 本来就在纸外,**永远印不出来**,不是“被删”。
- **域代码 / 自动目录 / 修订痕迹与批注**:`w:ins`/`w:del` 未接受时,删除内容仍在文件里但打印已不显示。
3. **禁止只比文字不比骨架。** 条号、金额、比例、日期、期限一旦对不上,先查是不是编号体系/大写小写/全角半角变了,再下“改内容”的结论。
4. **禁止把照片当基线做全文 OCR,也禁止凭印象转写。** 照片有折痕、卷边、裁边,必须用 `photo_bands.py` 切条放大后逐条读(流程 ②);**转写必须与电子稿同段同颗粒**——编号抄全、同段并排的内容不拆行、跨页续行拼回同一段(流程 ③)。转写误差是本 skill 的**最大误差源**。
5. **禁止只查“纸质稿有的”。** 必须双向查:电子稿有而纸质稿没有的(删除),纸质稿有而电子稿没有的(新增)。
6. **禁止在结论里省掉“对谁有利”**。删除担保/陈述保证条款、删掉违约救济、删掉返还义务——方向性必须点明。
## 标准流程
```
① 电子稿体检
docx_dump.py 合同.docx --footer --floating --revisions --json /tmp/el.json
→ 渲染后全文(编号已展开成"三、""1、")、页脚文本、浮动对象(含"是否在纸面内")、修订痕迹
→ 同时看 docProps/app.xml 的 <Pages>,与纸质稿页数对一下
② 照片切条(先看版式,再读细节)
photo_bands.py 照片目录/ -o /tmp/bands
→ 每页出一张 30% 缩略图(**先看这张**:整段消失这类问题在缩略图上最显眼)
+若干条横条(b1、b2…按阅读顺序,相邻条有意重叠,读时接起来)
→ "为什么 step 必须小于 band""为什么统一缩放到 1500px",见脚本 docstring
③ 纸质稿转写(人工环节,最易出错——**本 skill 的主要误差来源**)
按阅读顺序一行一段,颗粒度与电子稿严格一致:
- 编号照抄("三、"要写出来)
- **电子稿里同段并排的内容,转写也必须并排**(如甲方/乙方签字栏用制表符排在同一段)
- 跨页续行要拼回同一段,不能拆成两段
底稿**留档**(`纸质稿转写.txt`);比对中间产物(如 `el.json`)一并留——被追问“是不是看漏了”时,凭据比解释有用
④ 比对
version_diff.py /tmp/el.json 纸质稿转写.txt --exclude-text <页脚/页外对象文字> --min-len 8
--exclude-text 用于把页脚"2"、页外对象"3"这类非正文文字降级成"提示"而不是"删除"
**跑出来先怀疑转写,再怀疑原稿**:条目数比预期多时,先回去逐条对照片,
而不是直接写进结论(实测最易犯:漏一个"的"、把同段并排内容拆成两行)
⑤ 逐条回读确认 → 写结论(三件套:删了什么·在哪儿·对谁不利)
⑥ 【可选·仅当双方都是 .docx】把结论落成 Word 修订版
compare_docx_tracked.py OLD.docx NEW.docx OUT.docx --author "陆凌燕" --date "<ISO8601>"
verify_tracked.py OUT.docx OLD.docx NEW.docx ← 七项验证,必须全 PASS 才交付
→ 产出"像有人在 Word 里开着修订模式把旧稿改成新稿"的文件,可逐条接受/拒绝
→ 纸质稿一侧是照片/扫描件时**走不到这一步**(上游只处理 docx↔docx),仍用 ⑤ 出结论
```
### 第 ⑥ 步的用法与口径(借用上游能力,署名见文末)
| 参数 | 默认 | 说明 |
| --- | --- | --- |
| `--author` | `Editor` | Word 修订面板里显示的**修订人**。填写“是谁改的”——通常是对方或己方经办人,**不是助手/AI** |
| `--date` | 当天 | ISO8601,如 `2026-09-16T00:00:00Z` |
| `--threshold` | `0.45` | 段落相似度门槛。**调低**:轻度改动也按行内修订(不显示为整段替换);**调高**:大改段落不再产生满屏碎词修订 |
**输出命名**:`<旧标签>-vs-<新标签>-tracked-changes.docx`(例:`20260914-vs-20260916-tracked-changes.docx`)。
**必须回报的两个数字**:脚本打印的 `del_para` / `ins_para` / `mod_para` / `mod_fallback`,与 ④ 得出的差异条目数**交叉核对**——对不上就说明有一侧判错了。
**人工兜底**:验证器验的是 XML 结构(接受/拒绝双向等价),验不了“Word 里的观感”。金额、当事人、期限这几处,仍要**在 Word 的「所有标记」视图里肉眼过一遍**再交出去。
> 含图片、公式、超链接的段落可能合理地出现“整段删+整段插”而不是行内修订(脚本以 `mod_fallback` 计数),这是设计内回退,**不是 bug**;如出现要主动向用户说明。
## 自检(交付前,逐项打勾)
- [ ] **数量校验**:电子稿段数 − 纸质稿段数 = 整段删除数;不相等要解释(如新增抵消)
- [ ] **补集校验**:difflib 双向 opcodes 都看,insert(纸质稿新增)必须也是 0 或已解释
- [ ] **转写颗粒度自查**:转写稿与电子稿**同段同颗粒**——编号已抄全、同段并排内容仍并排、跨页续行已拼回。转写稿与照片逐条可对位
- [ ] **假阳性清单**:所有“命中 --exclude-text”的项单独列出并说明为何不算删除
- [ ] **金额/比例一致性**:删改涉及的数字与其它条款交叉核对(如 30%×200万=60万)。**被删段落里的数字也要核**,不能只看保留下来的部分
- [ ] **页数校验**:`docProps/app.xml` 的 `<Pages>` 与纸质稿实际页数一致
- [ ] **签署状态**:逐页看签字/盖章/日期栏是否已填,这往往比正文差异更要紧(谁签了、签的是哪一版)
- [ ] **〔走第 ⑥ 步时〕修订版双向等价**:`verify_tracked.py` 七项必须全 PASS——尤其「接受全部修订 == NEW」「拒绝全部修订 == OLD」
- [ ] **〔走第 ⑥ 步时〕修订标记逐条目视**:打开「审阅 → 所有标记」,**确认每一处标记都对应一处真实改动**。出现“没动过的字被标成插入/删除”就是缺陷——**不得以“accept-all 等于新版、语义无误”放行**,那只是结构没错,交给对方会被拿来说事。全部标记应能用一句话说清“这里删了什么”
- [ ] **〔走第 ⑥ 步时〕改过 vendor 就跑回归**:动过 `vendor/` 里任何代码,必须跑 `scripts/regress_trackdiff.py`,三条判据全过才算数
- [ ] **〔走第 ⑥ 步时〕条数与结论互印**:脚本的 `del_para + ins_para + mod_para` 与 ④ 的差异条目数对得上
- [ ] **〔走第 ⑥ 步时〕输出包逐部件比对**:与旧稿逐 md5,**只允许 3 个部件不同**——`word/document.xml`(修订写在这)、`word/settings.xml`(加修订开关)、`word/_rels/document.xml.rels`(序列化差异)。其余(页脚、样式、编号、媒体)必须逐字节一致
- [ ] **〔走第 ⑥ 步时〕修订人别写错**:`--author` 填**改动的来源方**(如「甲方(签章纸质稿)」),不要留默认的 `Editor`,更不要填 AI
## 环境
- python3:3.9 及以上即可(脚本只依赖标准库 + `PIL` / `numpy`;走第 ⑥ 步另需 `lxml`)
- 自有脚本四个,每个 docstring 里写了每一条“为什么”,本文件不重复:
- `scripts/docx_dump.py` —— 电子稿按“眼睛看到的样子”导出(展开编号、剥离纸外浮动对象)
- `scripts/version_diff.py` —— 逐段比对+假阳性降级
- `scripts/photo_bands.py` —— 照片定位纸张、切可读横条
- `scripts/regress_trackdiff.py` —— 上游原版 vs 现行版本的标记质量回归(动过 vendor 就跑)
- 第 ⑥ 步的脚本在 `vendor/`,来源与许可见该目录 `UPSTREAM.md`。
**含 1 处本地补丁**(分词器不粘连续标点),上游原版另存为 `compare_docx_tracked_upstream.py` 备对拍与回退
## 已知坑
| 坑 | 现象 | 处置 |
| --- | --- | --- |
| 自动编号未展开 | 逐段比对后“发现”标题编号被删 | 用 `docx_dump.py`,不用裸 XML 取文本 |
| 浮动文本框混入正文 | 末尾多出“3”,看着像多了/少了字符 | `--floating` 看 `on_paper`,false 即纸外对象 |
| VML Fallback 重复计数 | 同一个对象出现两次 | `AlternateContent` 的 Fallback 是副本,按名称+坐标去重 |
| exclude-text 子串误伤 | 关键字“2”命中正文“200万元” | 已限定:片段级差异不做 exclude,只有整段缺失才做 |
| 转写时漏掉跨页续行 | 上页末字 + 下页首字被当成两段 | 跨页处单独裁一条读,续行要拼回同一段 |
| **转写颗粒度与电子稿不一致** | 电子稿里同段并排的「甲方(签字)(制表符)乙方(签字)」被转写成两行 → 报出一条假“差异” | 转写与电子稿**同段同颗粒**;复核出的每条先回照片对,别直接写进结论 |
| **转写漏字/多字** | 漏一个「的」被报成“纸质稿删除了「的」” | 同上。`.txt` 底稿留档,便于逐条回溯 |
| **相邻切条之间有空隙** | 行被切在两条之间,读出来是残句,伪装成“原稿少了几个字” | `photo_bands.py` 强制 `--step < --band`(默认 470 < 560),脚本会拒绝 |
| **尾部多出一条重复残条** | 纸张下缘恰在条末附近时多切一条(实测 46px),肉眼像“多了一段” | 已在脚本内 `yb >= y1` 处 break |
| **用 shell `grep` 查脚本逻辑得到空结果** | BSD grep 的基本正则里 `\|` 是字面量 → **静默假阴性**,误以为“代码里没有这行” | 一律用 Grep 工具(ripgrep),不要用 shell `grep "A\|B"` |
| **手工点名判断 zip 部件丢没丢** | 写 `"footer1.xml" in namelist()` 得 False,误判“页脚丢了”(真名是 `word/footer1.xml`)→ 自己造出假警报 | 不做手工点名,改**逐部件 md5 比对**(见自检清单) |
| **Word 打不开、报“无法读取的内容”** | `<w:del>` 内残留 `<w:t>`/`w:id` 重复/`r:id` 悬空 | 跑 `verify_tracked.py` 直接定位;不要手工改 XML |
| **删除段落在 Word 里显示成空行** | 只包了 run,没标段落标记本身 | 需 `w:pPr/w:rPr/w:del`;上游脚本已实现,出问题说明包被改过 |
| **修订不显示,文档看着是干净的** | `word/settings.xml` 缺 `<w:trackChanges/>` | 同上,验证器第 2 项专查这个 |
| **“拒绝全部修订”后文本不对** | 段落边界标记被误判 | 验证器会把 `pPr/rPr` 内的 ins/del 当段落级标记处理,用它做双向模拟 |
| **删除段落里的旧图片丢失** | `rId` 未重映射 / media 未复制进新包 | 验证器第 7 项查旧图字节;上游按 `tracked_` 前缀复制媒体 |
| **「)」被标成插入(蓝色)** | 旧稿「**),**」→ 新稿「**)。**」;上游分词 `TOK_RE` 末尾的 `+` 把连续标点粘成一个不可分 token,两块互不相等 → 被判成“删一块 + 插一块”,**两版里都在同一位置的括号与句号被裹进修订标记** | 已由**本地补丁 1** 修复(`[^\w\s]+` → `[^\w\s]`),`<w:ins>` 由 2 归零。改完必跑 `scripts/regress_trackdiff.py`;补丁说明与回退见 `UPSTREAM.md` |
---
## 第三方归属与致谢
第 ⑥ 步「生成 Word 修订模式文件」的能力,**借用自开源项目 [`docx-trackdiff`](https://github.com/stephenlzc/docx-trackdiff)**(MIT 许可),代码原样置于 `vendor/`:
- **作者**:**Big Stephen**(GitHub [@stephenlzc](https://github.com/stephenlzc))—— 需求、真实场景测试
- **共同作者**:**Kimi K3 Agent Swarm**([Moonshot AI](https://www.moonshot.ai/) 出品,[@MoonshotAI](https://github.com/MoonshotAI))—— 实现、验证、打包
- **上游版本**:1.0.0(获取日期 2026-09-16)
- **许可证**:MIT(上游 README 原话:「随意使用,保留署名即可」)
- **借的具体内容**:以 NEW 为底包 + 段落标记与行内两套修订语法 + 四条 OOXML 硬规则 + 深拷贝旧段落的清理项 + 复杂块整段回退策略。详见 `vendor/UPSTREAM.md`
上游只处理 `.docx ↔ .docx`;本 skill 的**扫描/照片稿 ↔ 电子稿主链路、四类渲染假阳性降级**均为自有能力,与上游无关。
> 修订版生成这件事,我们选择「原样引用 + 明确署名」而不是「重写一遍」:上游的 OOXML 规则是踩过坑验出来的(其 OOXML 规则速查已随包置于 `vendor/ooxml-revision-rules.md`,列了 5 类失败模式),重写一遍等于把那些坑重踩一次。MIT 允许这样用,我们按许可要求保留署名。
**本地补丁 1(已记录在 `UPSTREAM.md`)**:上游分词器 `TOK_RE` 末尾的 `+` 会把连续标点粘成一个不可分 token,导致中文文书里「),」→「)。」这类相邻标点变化被误报成“删一块 + 插一块”,把**两版里都在同一位置的括号、句号**标成修订。本 skill 去掉该 `+`(`[^\w\s]+` → `[^\w\s]`),实测 `<w:ins>` 由 2 归零、删除内容变准确。上游原版保留为 `vendor/compare_docx_tracked_upstream.py`,改动范围、验证方式与回退步骤均见 `UPSTREAM.md`;上游若自行修好此问题,删掉本补丁换用上游版本即可。
---
> 法律科技实务工具 · 维护者陆凌燕律师(北京德恒·无锡)
Files in this skill
- SKILL.md
- _meta.json
- scripts/docx_dump.py
- scripts/photo_bands.py
- scripts/regress_trackdiff.py
- scripts/version_diff.py
- vendor/EVALUATION.upstream.md
- vendor/README.upstream.zh-CN.md
- vendor/UPSTREAM.md
- vendor/compare_docx_tracked.py
- vendor/compare_docx_tracked_upstream.py
- vendor/ooxml-revision-rules.md
- vendor/verify_tracked.py
Attribution
Comments
Loading comments…