--- name: contract-pass-workflow description: 合同审查pass后的标准操作——更新tracker、更新xlsx清单、上传Nextcloud、清缓存。Doro说pass后照此执行。 version: 1.0.0 tags: [合同审查, pass, tracker, xlsx] --- # 合同审查 Pass 后标准操作 ## 交付文件位置(铁律,2026-06-29 Doro纠正) 所有交付文件统一放在 **`Doro合同审查任务/任务交付/`(根目录)**,不放顾问单位子文件夹。 - ✅ `Doro合同审查任务/任务交付/【修】合同_朱家角.docx` - ❌ `Doro合同审查任务/朱家角镇社区卫生服务中心/任务交付/【修】合同_朱家角.docx` workflow YAML deliverer 步骤(第210行)明确写的上传路径是根目录 `任务交付/`。如果 deliverer 或 final_review 把文件放到了子文件夹,pass 步骤0核对时必须发现并移到根目录。 ## 步骤0核对要点 pass 步骤0的核对流程: 1. 取原始文件名(去掉【修】/【审】前缀) 2. 去 `Doro合同审查任务/待审查/` 确认源文件存在 3. **确认交付文件在根目录 `任务交付/`**(不是子文件夹) 4. 顾问单位核对(从合同正文读甲方名称) 5. tracker 查重 ## 红线:验证指令 ≠ 回忆指令(2026-07-02 信任危机后确立) ### 根因排查铁律:抛弃旧判断,先查“发生了什么变化”,再谈原因(2026-07-14 Doro纠正) 当 Doro 明确说“去查原因”“不是让你猜测、推断”“完全抛弃此前的判断,重新核查情况”时,后续动作必须切换为**变化核查模式**,而不是继续打磨措辞、修补上一版归因。 **强制步骤:** 1. **旧判断全部作废**:停止沿用“状态漂移”“异常”“习惯变差”“元控制失效”等任何解释性语言,除非已经有直接证据支持。 2. **先限定时间窗**:如果用户已给出时间边界(如“发生在 7 月 10 日前”),所有核查必须围绕该边界展开;边界外的改动先排除,不得混入结论。 3. **优先查“可观察变化”而不是“解释”**: - 配置文件修改时间与具体改动; - workflow/queue/watchdog/notify 脚本的修改时间与新增逻辑; - skill 规则文本的修改时间; - gateway / auto-notify / watchdog 的退出、重启、恢复日志; - tracker / queue / done / manifest 等状态文件是否出现结构变化。 4. **汇报格式必须是“已查到的变化 / 没查到的变化”**,不能把“更可能”“像是”“说明了”写成原因结论。 5. **只有在“变化事实”查清后,才允许进入第二步根因分析**;若变化事实尚未闭环,明确说“目前只查到这些变化,原因尚未下结论”。 **禁止事项:** - 不断修改上一版判断的措辞,假装自己在继续调查; - 用“异常”“偶发”“状态不好”“坏习惯”去解释持续两天、批量失效的问题; - 把证据和推断混写成同一层结论; - 用户要求查原因时,实际只做语言收缩而不做新核查。 **本会话教训:** Doro连续纠正“不是让你猜测、推断”“不是让你不断修改措辞”“要你完全抛弃此前判断,重新核查 7 月 10 日前发生了什么变化”。以后凡是原因排查任务,第一步不是解释,而是建立时间窗并枚举已发生变化。 ### 先查清时间窗,再查数据(2026-07-13 本会话再犯后补丁) 当 Doro/Maggie 说“上周五”“今天”“昨天”“本周一”这类**相对日期**时,**第一步不是直接查数据,而是先把自然语言时间词锚定成明确的北京时间起止窗口**。没先锚时间窗,就会出现: - 把“上周五”理解成错误日期; - UTC/BJT 边界算错; - 用错窗口后得出“0 份”之类错误结论; - 之后再补查 gateway.log 才发现用户是对的,严重伤信任。 **强制步骤:** 1. 先用北京时间确认当前日期和星期; 2. 把“上周五/今天”等词转换成**北京时间明确起止时间**; 3. 再换算成 UTC 时间戳/窗口; 4. 把这个时间窗写出来后,才开始查 `.meta` / gateway.log / tracker / xlsx。 **执行纪律:** - 如果 `meta` 查不到,但 `gateway.log` 里已经出现该日文件消息证据,**不得继续说“0 份”或“没发”**;必须当场降级结论为“已证明确实发了,但文件清单仍在继续反查”。 - 对周五这类历史批次,**至少要交叉两源**:`gateway.log` 文件消息 + tracker / xlsx / queue 痕迹,不能只信一侧。 - 没闭环前,结论只能说“已查实部分”“尚未查实部分”,不能提前给“全部无遗漏”的总判断。 **本会话教训**:周五邱律师明明发了文件,但因先把时间窗算错、又只依赖 `.meta`,错误说成“0 份”;之后从 `gateway.log` 查到 `msg=''` 文件消息才纠正。以后凡是相对日期统计任务,**先定北京时间窗口,再查数据**。 ## 红线:验证指令 ≠ 回忆指令(2026-07-02 信任危机后确立) ### 当前交付目录全量 pass 指令(2026-07-13 Doro明确授权) 当 Doro 明确下达类似指令: - "任务交付文件夹里所有的合同及companion,都做pass" - "现在这一刻,Nextcloud-任务交付里,所有的合同和companion,都做pass流程" 这属于**对当前任务交付目录的全量授权**,不再局限于单份合同或单个 companion。此时必须: 1. **先列出任务交付目录当前全部文件**,不要凭上一条对话里提到的那一份合同推断。 2. **合同与 companion 都要纳入核查范围**——先查清目录里到底有哪些主合同、有哪些 companion,不能只扫主合同关键词就下结论。 3. **主合同与 companion 的 pass 处理规则不同**: - **主合同**:做完整 pass——tracker 标记 `completed`,并登记到 excel。 - **companion**:属于主合同的附属交付物,**不单独登记到 excel,不单独占 seq**。只在核查结果中确认其存在与关联关系,必要时体现在主合同的 pass 备注/关联记录中。 4. **先查 tracker / xlsx 再写入**,但用户已经授权时,不要卡在"没有 tracker 记录所以我先不做"的保守口径上;对主合同应直接补建记录并完成 completed + excel 登记。 5. **汇报时先给出全量清单,再区分主合同与 companion 的处理结果**,不能把“全部做pass”误写成“所有文件都单独登记 excel”。 ### 本次会话教训 - 错误做法:只围绕白鹤这一份合同回答"已pass",没有先把任务交付目录全量列出,遗漏了香花桥和朱家角积分项目的一整组合同及 companion。 - 第二层错误:把 companion 当成主合同一样单独写入 tracker/xlsx,导致 seq 冲突和错误登记。 - 正确做法:当 Doro 说"任务交付文件夹里所有的合同及companion,都做pass"时,必须把**当前任务交付目录的全部文件**作为审计范围,但**excel 只登记主合同,companion 不单独登记**。 **凡Doro说"查""核实""核对""是不是都""有没有遗漏"→ 回复中必须先有工具调用再有结论。context记忆/session summary ≠ 查证,不可直接输出。** 违反后果:Doro已明确说"到了无法信任你的程度"。这不是规则问题,是行为问题——规则早就写了(见已知坑),照样违反。 四个具体失败模式(2026-07-02 同一轮对话全犯): 1. **拿记忆当查证**:session context有信息→直接组织成答案→包装成"查实结果"→其实没跑工具 2. **渠道遗漏**:只查了QiuTing私信,漏了Doro私信、Doro直接Nextcloud上传、飞书等渠道 3. **不认识自己的输出**:之前汇报过的数据(seq 216-222含重固),被追问时说"找不到"——数据一直在,没看自己历史输出 4. **推责给用户**:找不到时直接问Doro"你记得叫什么名字吗"——把验证责任转嫁给用户。正确做法:穷尽搜索策略(换关键词、按时间批次找、按相邻seq推断、直接读xlsx逐行扫、session_search换多个query)。数据就在tracker里,是自己没认真遍历。 **第4条补充(2026-07-02追加)**:被追问"你真的查了吗"→ 如果上一条回复里没有tool call,直接承认"没查,现在查"。不辩解、不包装。 **唯一可接受的行为**: - 跑工具 → 看输出 → 写结论 - 不确定的说不确定 - 被追问时不防御不绕,直接承认没查到 - 对比自己之前输出过的表格/数据,确认是否已经有答案 ## 恢复到workflow交付状态(2026-07-13 教训) Doro可能要求"恢复到workflow完成的状态"——意思是撤销你的手动修改,让NC上的交付文件回到workflow原始交付版本。 **恢复方法(按优先级)**: 1. **NC版本历史**:`docker exec nextcloud-nextcloud-1 ls /var/www/html/data/doro/files_versions/Doro合同审查任务/任务交付/` 找 `.v` 文件,最早的版本 = workflow原始交付版 2. **`/tmp/pass_check_*` 副本**:pass流程核对时从NC拉取的副本,如果时间早于你的手动修改,就是workflow原版 3. **练塘镇子目录副本**:部分合同在 `练塘镇社区卫生服务中心/任务交付/` 也有一份 **操作**:docker cp 覆盖 → chown www-data → occ files:scan。 **场景**:你手动修复了workflow交付的合同(补编号、修字体等),但Doro认为应该重新走workflow而非手动修补。恢复后等Doro进一步指示。 ## 前置审查(Doro要求"审查workflow修改"时触发) Doro可能在pass之前要求逐份审查workflow交付物的质量。这不是pass流程,而是**前置质量审计**,只有审计通过+手动修复后Doro才会说pass。 ### 审计方法论 1. **区分原文修订 vs workflow修订**:用zipfile读XML,按`w:ins/@author`和`w:del/@author`区分。`author=WB`是workflow的,其余(WB-1/86187/杨丽等)是原文自带的→保持不动。 2. **对照原文确认**:必须docker cp原文件(从NC待审查目录),转换后逐段对比,确认哪些修订是原文自带。 3. **逐项检查清单**: - INS rFonts:WB INS的rFonts属性必须与同段原文run一致(不多不少)。常见问题:多了hAnsi/cs/hint - pStyle:新增条款标题的段落样式必须与原文条款标题一致(如Heading4),不能用Style15等其他样式 - 标题空格:原文"第六条违约责任"无空格→新增也不能有空格"第七条转包与分包" - 子编号顺延:章节编号改了(七→八),内部子编号(7.1→8.1)也必须改 - 赔偿上限:双向条款的赔偿上限(限制甲方获赔)按规则"能删就删" - 内容去重:新增保密存续等条款前,检查原文是否已有同义表述 - 脚注"法律顾问修订版":**必须用修订格式**(w:ins, author=WB),不能是普通文本 - 文件命名:【修】+原文件名一字不动(含扩展名变化.doc→.docx) 4. **通读全文**:接受修订后的全文必须通读,检查WB插入的内容是否与上下文语句通顺、有无重复编号(原文问题不改,但要识别) 5. **Doro的期望**: - "打开文件查清楚再回答我"→ 必须用工具完整检查后才能下结论,不能凭印象 - "看清楚前后文再回答我"→ 报告问题前必须理解修改点的上下文语境 - "有没有该加粗没加粗的"→ 格式检查要全面(bold/style/indent/spacing都要对比) - "按照workflow的规则,手动修改"→ 发现问题后直接修复,不只是报告 ### 修复操作要点 - 用zipfile+lxml直接操作XML(不用python-docx修改tracked changes) - 修复后的INS rFonts只保留原文有的属性(通常eastAsia+ascii) - 子编号顺延:原文数字可能分散在多个run中(如"7"+".1 "两个run),只需DEL+INS第一个数字run - P49类大段文本需精确split run(保留前后文,只DEL中间要删的部分) - 修复后必须python-docx打开验证 ## 触发条件 Doro对已交付的合同**明确说"pass"**(可以是单份或批量)。 ⚠️ **绝对前提:Doro必须亲口说"pass"才能启动此流程。** 合同交付后、workflow完成后、端午节合同end后——这些都**不是**pass。不要因为合同已交付就主动查xlsx/tracker或准备pass操作。Doro没说pass之前,对交付物的一切后续操作(更新tracker、更新xlsx、查重等)都不做。 ⚠️ **"我满意了"≠Doro说pass(2026-07-13教训)**:即使你作为审查负责人完成了质检、修复了所有问题、对文件满意,也**绝不能自行执行pass流程**。必须等Doro明确说"pass"。2026-07-13实证:白鹤劳务派遣协议修复完毕后自行做了pass,被Doro纠正"我没说pass你做什么pass",紧急撤销(tracker回退delivered+xlsx删行)。自主质检和pass是两个完全独立的步骤——前者是你的职责,后者是Doro的权力。 2026-06-15教训:两次被Doro纠正("我都还没说pass呢"、"今天的合同我都没说pass呢")——agent在合同交付后主动查xlsx是否已更新、准备补写tracker,被视为越权操作。 2026-07-13教训:白鹤劳务派遣协议自行质检完后直接执行pass,被纠正"我没说pass你做什么pass",紧急撤销。 **手动审查交付物的完整检查清单**见 `references/manual-review-checklist-0713.md`——当Doro要求"审查workflow修改的情况"时按此执行。核心:自主质检→发现问题直接修→报告→等Doro说pass。不问"需要修复吗",不自行pass。 ## 特殊判定:"终身不通过" Doro可能对某些合同说"终身不通过"——这意味着合同审查质量太差,**永远不会pass**。 - **不是返工**:不是让你修了再交,而是直接否决 - **处理方式**:在tracker中标记`status: "permanently_rejected"`,不进入pass流程 - **反思**:必须分析为什么质量差到这个程度,是workflow哪个角色出了问题,把教训记入historical-failures - **不要追问Doro**:已经定性了就不要再烦,自己复盘 ## 操作步骤(严格按顺序) ### 0. 交付物核对(写入前必做) ⚠️ **PDF 批注交付件的独立核验** → 用 `scripts/verify_pdf_annotation_deliverable.py <原件> [本地核验件]`:一键查 SHA256 字节一致、页数、原文未改动、批注数/author=WB、批注格式("建议"开头无【】)、高亮几何锚定。扫描件/PDF 合同走批注模式(非 docx 修订),docx 专项检查不适用,用此脚本替代。 Doro说pass后,在写tracker/xlsx之前,**必须先核对交付物与源文件的匹配关系**: 1. **取原始文件名**:从交付文件名去掉`【修】`或`【无修改意见】`前缀 → 得到原始文件名 2. **待审查目录核实**:去Nextcloud `Doro合同审查任务/待审查/` 确认该原始文件名存在。不存在则停下来排查,不继续写入 3. **顾问单位核对**:确认要写入xlsx的顾问单位名称正确。⚠️ 不能盲信classifier的`our_party_name`——已有多次classifier误判案例(如智慧医院云项目合同classifier设为"朱家角"实际甲方是"卫健事业发展中心")。**必须用python-docx打开合同原文,直接读取甲方名称** - **⚠️ python-docx 的 `paragraph.text` 会吞掉 `w:ins`(修订插入)内容 → 读出的甲方可能是"补全前"的残缺名(2026-06-25 实证)**:当本次审查的改动之一就是"给甲方补全行政区前缀"(如 WB 修订插入「上海市青浦区」),交付件里甲方全称是「原稿可见文字 + w:ins 插入文字」拼起来的。`python-docx` 遍历 `doc.paragraphs[i].text` 时**未必包含 ins 的文字**,会让你误以为甲方还是缺前缀的简称。**核甲方全称必须把 `w:ins` 算进去**:用 zipfile 读 `word/document.xml`、对甲方那一段 `''.join(t.text for t in p.iter('{...}t'))`(`w:t` 不分 ins/非 ins,全收),或干脆遍历所有 `w:ins` 看 author=WB 插了什么。2026-06-25 项目终止协议书:`paragraph.text` 显示甲方「华新镇社区卫生服务中心」,实际交付件 w:ins 补了「上海市青浦区」,全称应是「上海市青浦区华新镇社区卫生服务中心」——只看 `.text` 就会把残缺简称写进 tracker/xlsx。**写 party 前先确认你读的是"接受修订后"的完整甲方名。** - **顾问单位名称空白的特殊情况(按合同来源分两路,2026-06-25 补全)**:合同正文里顾问单位名称是空白下划线(待签时填)时,无法从正文确定。**先分清这份合同是谁的任务线**,再决定问谁——别默认是邱律师: - **邱律师批量线**(health-centers):私信邱律师询问归属。私信用 `python3 ~/.hermes/scripts/wecom_dm.py --to qiuting --text "…"`(或 `_send_wecom(extra,'QiuTing',msg)`),**不用** `send_message(target='wecom:X')`(静默回退 home channel)。暂停 pass(不写 tracker/xlsx),邱律师回复后从步骤1继续;Doro 说过"邱律师回复了就直接补上 我不管了"——回复后自主补登记,不再找 Doro。 - **Doro 直接指派 / 非批量线**(如幼儿园保密协议、劳动合同等非卫生中心合同):**问发起 pass 的人本人**(通常就是正在对话的 Doro),不要问邱律师,也不要自己瞎填占位符。 - ⚠️ **绝不用泛指占位符当 party 写进 tracker/xlsx**(如"幼儿园(园方,名称空白待填)")——2026-06-25 教训:园名空白我写了这种占位符,Doro 直接纠正"顾问单位是平和学校"。空白就停下来问准确**法律主体全称**,确认后再写。 - **同名/近名主体消歧(2026-06-25 教训,写 party 前必做)**:拿到顾问单位名后,先 `openpyxl` 扫 xlsx 第 C 列看清单里**是否已有多个相似名**的主体——它们往往是**不同法律实体**,不能混用。本会话清单里同时有「上海青浦平和**幼儿园有限公司**」(seq13/15) 和「上海青浦平和**双语学校**」(seq44/210/211),一个园、一个校,是两个主体。判别靠**合同内容性质**:这份通篇"幼儿园/幼儿就读/保教费"→幼儿园主体;但既然清单里并存多个近名实体,**最终仍用 `clarify` 让发起人拍板一个准确全称**,并复用清单里既有的写法(保持前后一致,别造新写法)。 - **更正已写错的 party(两处同步)**:若 tracker/xlsx 已写入后才被纠正,两处都要改:tracker 用原子写回(tempfile+rename)改对应 seq 的 `party`;xlsx 改第 C 列后重新 `docker cp` 上传 + `occ files:scan` + 清 OnlyOffice 缓存重启;最后从线上重新拉 xlsx + 读 tracker **核对两处一致**才算完成。 4. **查重**:在tracker中检查是否已有相同 `original_filename` + `party` 的completed记录。有则为重复交付,不再写入 以上4步全部通过,才进入步骤1写tracker。任何一步不通过,停下来排查原因。 **注意**:Doro批量pass时可能列出审查意见文件(如"朱家角审查意见-xxx"、"采购协议-审查意见")。这些是主合同的附属交付物,**默认不单独写tracker/xlsx条目**。只需为主合同文件(【修】前缀的)写tracker和xlsx。审查意见文件如果不在交付目录中(可能已被清理或因classifier误判未生成),不影响主合同的pass流程——跳过即可,不需要报错或追问Doro。 ### 例外:用户明确说“任务交付文件夹里所有的合同及companion,都做pass流程”时(2026-07-13 Doro明确) 当 Doro 用**当前任务交付目录全量授权**的口径下指示: - “任务交付文件夹里所有的合同及companion,都做pass” - “现在这一刻,Nextcloud-任务交付里,所有的合同和companion,都做pass流程” 此时必须把 companion **纳入 pass 核查范围**,但仍要遵守: - **companion 不是独立合同,不单独登记 excel,不单独占 seq**; - tracker/xlsx 的登记主体仍然是**主合同**; - companion 的处理应当体现在“该主合同已连同 companion 一并核查/补做pass”的结果里,而不是把每个 companion 当主合同单独建台账。 **执行顺序**: 1. 先把当前 `任务交付/` 目录全部文件列出来,不能只围绕当前对话那一份合同; 2. 再识别哪些是主合同、哪些是 companion; 3. 主合同逐份做 pass / tracker / xlsx; 4. companion 只做挂靠核查,不单独占 excel 行; 5. 回复时明确区分“主合同已登记几份、companion 已核查几份”,不要混成一类。 **2026-06-11教训**:安全测试合同因跳过核对,第一次把原始文件名写进xlsx文件名列(没有【修】前缀),发现后补写但未清理错误记录,导致tracker和xlsx各多一条重复数据。 **2026-06-12教训**:手动写xlsx时列顺序写反(日期和序号对调、文件名列写成原始文件名而非delivered_filename、顾问单位列写成"已完成"状态文字)。**写xlsx前必须先读上一行确认列顺序**,不要凭记忆。正确列顺序:A=序号, B=日期, C=顾问单位, D=合同名称, E=文件名(delivered_filename)。 ### 1. 更新 Tracker JSON 路径:`~/.hermes/data/contract-tracker.json` #### 状态流转(新增 `delivered` 中间态,2026-07-02 确立) ``` 文件到达 → workflow审查 → deliverer交付成功 → queue-runner写入 status=delivered ↓ Doro说pass → status=completed ↓ 24h后 → cleanup清理 ``` **三道防线防重复:** | 检查点 | 逻辑 | |--------|------| | queue-runner 启动前 | 查 tracker,`delivered` 或 `completed` → 直接 SKIP 并移入 done/ | | watchdog resume 前 | 查 tracker,已交付的 thread 不恢复 | | deliverer 写入时 | exists 检查,同文件不重复写入 | #### Pass 时的写入逻辑 **如果 tracker 中已有该 `original_filename` 且 `status=delivered`** → 更新该记录为 `completed`,补全 `seq`/`party`/`contract_name`/`delivered_filename`/`converted_filename`/`xlsx_updated_at`/`cleaned=false`。 **如果 tracker 中没有记录**(旧合同、手动审查等情况)→ 新建完整记录,直接 `status=completed`。 完整记录示例: ```json { "original_filename": "消防设施检测服务合同(练塘卫生院).doc", "delivered_filename": "【修】消防设施检测服务合同(练塘卫生院).docx", "converted_filename": "消防设施检测服务合同(练塘卫生院).docx", "party": "上海市青浦区练塘镇社区卫生服务中心", "contract_name": "消防设施2026年度检测服务合同", "seq": 175, "status": "completed", "delivered_at": "2026-06-09T18:30:00+08:00", "xlsx_updated_at": "2026-06-09T20:09:00+08:00", "cleaned": false } ``` 字段说明: - `original_filename`:邱律师发来的原始文件名(可能是.doc) - `delivered_filename`:交付到任务交付目录的文件名(【修】前缀) - `converted_filename`:workflow转换后的.docx文件名(原始是.doc时有值,否则空字符串)。**来源**:workflow的`converted_filename`字段会从classifier一路传递到deliverer/final_review输出。pass流程必须从workflow输出中提取并写入tracker。如果workflow输出中没有此字段(旧workflow跑的合同),则根据original_filename判断:以`.doc`结尾的,converted_filename = 同名`.docx`;以`.docx`结尾的,converted_filename = 空字符串 - `seq`:合同审查清单中的序号 - `delivered_at`:workflow 交付完成时间(queue-runner 写入) - `xlsx_updated_at`:pass 时写入,北京时间ISO格式,清理脚本据此计算24h - `cleaned`:清理脚本执行后改为true **写入方式**:先写临时文件再rename(原子操作),防止进程中断导致JSON损坏。 ```python import json, os, tempfile from datetime import datetime, timezone, timedelta BJT = timezone(timedelta(hours=8)) tracker_path = os.path.expanduser('~/.hermes/data/contract-tracker.json') # 读取现有tracker if os.path.exists(tracker_path): with open(tracker_path, 'r') as f: tracker = json.load(f) else: tracker = {"contracts": []} now = datetime.now(BJT).isoformat() # 查找是否已有 delivered 记录 existing = None for c in tracker["contracts"]: if c.get("original_filename") == original_filename: existing = c break if existing and existing.get("status") == "delivered": # 已有 delivered → 更新为 completed(补全字段) existing["status"] = "completed" existing["delivered_filename"] = delivered_filename existing["converted_filename"] = converted_filename existing["party"] = party existing["contract_name"] = contract_name existing["seq"] = seq existing["xlsx_updated_at"] = now existing["cleaned"] = False elif existing and existing.get("status") == "completed": # 已经 completed → 查重命中,不重复写入 pass else: # 无记录 → 新建(旧合同/手动审查走这条路) tracker["contracts"].append({ "original_filename": original_filename, "delivered_filename": delivered_filename, "converted_filename": converted_filename, "party": party, "contract_name": contract_name, "seq": seq, "status": "completed", "xlsx_updated_at": now, "cleaned": False }) # 原子写入 fd, tmp = tempfile.mkstemp(dir=os.path.dirname(tracker_path), suffix='.json') with os.fdopen(fd, 'w') as f: json.dump(tracker, f, ensure_ascii=False, indent=2) os.replace(tmp, tracker_path) ``` ### 2. 更新合同审查清单 xlsx 路径(Nextcloud):`Doro合同审查任务/合同审查清单.xlsx` ⚠️ **铁律(2026-07-03覆盖事故后确立):写xlsx前必须从Nextcloud实时拉取最新版本,禁止使用/tmp或本地任何已有的xlsx文件。** 违反此规则会用旧版覆盖新版,导致其他session已登记的记录丢失(2026-07-03实证:用240行旧文件覆盖了252行最新版,丢失12条记录)。 **强制检查流程**: 1. `docker cp` 从NC拉取 → 保存到 `/tmp/合同审查清单_LIVE.xlsx`(带LIVE后缀避免与残留文件混淆) 2. 打开后先读 `ws.max_row` 和最后一行的seq → 打印确认 3. 如果发现本地已有同名文件,**必须删除后重新拉取**,不得复用 4. 追加新行后上传 步骤: 1. **从Nextcloud下载最新xlsx(必须实时拉取,禁止复用本地文件)** 2. 用openpyxl追加行(序号、日期、顾问单位全称、合同名称、文件名) 3. 从上一行复制字体和对齐样式(用copy()) 4. border用Side对象重建(不用ref_cell.border直接赋值,会报unhashable错误) 5. 保存后上传回Nextcloud 6. 执行files:scan + 清OnlyOffice缓存 ```bash # 下载(铁律:必须每次实时拉取,rm掉旧文件防止复用) rm -f /tmp/合同审查清单_LIVE.xlsx docker cp nextcloud-nextcloud-1:/var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx /tmp/合同审查清单_LIVE.xlsx sudo chown maggie:maggie /tmp/合同审查清单_LIVE.xlsx # 上传 docker cp /tmp/合同审查清单_LIVE.xlsx nextcloud-nextcloud-1:/var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx # 上传 docker cp /tmp/合同审查清单_LIVE.xlsx nextcloud-nextcloud-1:/var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx python3 -c "import openpyxl; ws=openpyxl.load_workbook('/tmp/合同审查清单_LIVE.xlsx').active; print(f'NC当前版本: {ws.max_row}行, 最后seq={ws.cell(ws.max_row,1).value}')" # 上传 docker cp /tmp/合同审查清单_LIVE.xlsx nextcloud-nextcloud-1:/var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx docker exec nextcloud-nextcloud-1 chown www-data:www-data /var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx docker exec -u www-data nextcloud-nextcloud-1 php occ files:scan --path="doro/files/Doro合同审查任务/合同审查清单.xlsx" # 上传后验证(write-read-verify,防止覆盖事故) rm -f /tmp/合同审查清单_VERIFY.xlsx docker cp nextcloud-nextcloud-1:/var/www/html/data/doro/files/Doro合同审查任务/合同审查清单.xlsx /tmp/合同审查清单_VERIFY.xlsx python3 -c "import openpyxl; ws=openpyxl.load_workbook('/tmp/合同审查清单_VERIFY.xlsx').active; print(f'上传后验证: {ws.max_row}行, 最后seq={ws.cell(ws.max_row,1).value}')" # 清OnlyOffice缓存 docker exec nextcloud-onlyoffice-1 bash -c 'rm -rf /var/lib/onlyoffice/documentserver/App_Data/cache/files/data/*' docker restart nextcloud-onlyoffice-1 ``` ### 3. 确认并汇报 向Doro确认已标记completed,告知清单已更新到第几条。 ### 批量pass后的登记完整性审计(2026-06-15教训) ⚠️ **Doro说\"都pass了\"或\"看下是否都走了pass流程\"时,必须把每一份pass的合同逐一对照 tracker JSON 和 xlsx 两个存储,找出缺漏——不能假设\"workflow跑完了/交付了\"就等于\"登记完整\"。** 2026-06-15教训:6份合同队列全部跑完并交付,Doro说6份都pass。实际核对发现只有2份(端午节、医疗急救招聘)走了完整pass流程,另外4份(金泽蛋糕、赵巷消防、练塘健康积分、徐泾维保)tracker和xlsx**全都没登记**。Doro主动提醒\"excel登记是不够的\"。原因:交付由workflow的final_review完成,但pass流程(写tracker+xlsx)是独立的人工步骤,前面几份漏做了。 **审计脚本逻辑**(用openpyxl+json对照): 1. 列出本批次所有pass合同的 `delivered_filename`(从任务交付目录或Doro的pass消息提取) 2. 读 xlsx 第E列(文件名列)建一个 set 3. 读 tracker.json 的 `contracts[].delivered_filename` 建一个 set 4. 逐份合同检查:`in xlsx?` + `in tracker?`,打印缺失矩阵 5. 对缺失的合同,**完整补走步骤0(待审查核实+正文读甲方+查重)→ 步骤1(tracker)→ 步骤2(xlsx)**,不能只补一个存储 6. 补完后重新跑一遍审计脚本,确认6份全部 ✅ xlsx + ✅ tracker **xlsx与tracker必须成对存在**:xlsx是给人看的登记,tracker是给cleanup cron用的。只有xlsx没tracker→文件永远不被自动清理;只有tracker没xlsx→Doro的清单缺条目。审计时两个都要查。 **openpyxl写xlsx的权限陷阱**:从Nextcloud用`sudo cp`下载的xlsx属主是root,openpyxl保存时报`PermissionError`。先`sudo chown maggie:maggie`改属主到当前用户的可写副本再操作。 ## 触发模式 Doro会**回复引用**某条交付通知消息并说"pass"。可能是单份也可能批量("四份都pass")。 - 引用的消息中包含交付文件名,从中提取合同信息 - 批量pass时逐份处理,每份都写tracker+xlsx ### 审查意见文件的处理 Doro可能把审查意见文件(如"朱家角审查意见-XXX.docx"、"采购协议-审查意见.docx")也列入pass清单。这些是合同的伴随交付物,**不需要单独的tracker条目和xlsx行**——它们跟随主合同的tracker记录: - 主合同"【修】XXX.docx" → 写tracker + 写xlsx - 伴随审查意见文件 → 不写tracker,不写xlsx - 清理时:审查意见文件跟随主合同一起清理 ### Classifier甲方误判的pass处理(2026-06-12教训) 当workflow classifier误判了顾问单位(如把"卫健事业发展中心"误判为"朱家角"),pass流程写tracker/xlsx时必须用**合同正文中的真实甲方名称**,不能用classifier的`our_party_name`。 - 步骤0核对时用python-docx读取合同正文前30段,找甲方全称 - tracker的`party`字段和xlsx的"顾问单位"列写真实甲方 ## 清理cron配套信息 - Cron job name: `contract-cleanup`,job_id: `4636467b715d` - 脚本路径: `~/.hermes/scripts/contract-cleanup.py` - 调度: 每小时,no_agent静默模式 - 逻辑: 读tracker → 找completed + xlsx_updated_at超24h + cleaned=false → docker exec删Nextcloud待审查/和任务交付/中的文件 → 标记cleaned=true → files:scan - 安全: 只删tracker中精确记录的文件名,不通配。xlsx在上一级目录碰不到。JSON损坏时静默不操作。原子写入(tempfile+rename) - **.doc→.docx转换残留(2026-06-11发现+修复)**:原始文件为`.doc`时,workflow会用libreoffice转换为`.docx`副本放在待审查目录。cleanup脚本已有`converted_filename`字段的读取逻辑(第113-116行),但旧workflow跑的合同tracker中缺少此字段导致转换文件残留。**治本修复**(2026-06-11已实施):在review-contract.yaml的classifier procedure中添加`converted_filename`记录步骤,从classifier→reviewer→editor→deliverer→final_review全链路传递该字段。pass流程写tracker时从workflow输出中提取。cleanup脚本自动生效。 - **手动批量清理(Doro授权模式)**:Doro可能要求按日期批量清除旧文件(不走tracker逻辑),此时按修改时间(stat %Y)过滤,在Nextcloud容器内执行rm,完成后files:scan+清OO缓存。2026-06-11执行过一次:保留6月10日及之后的文件,删除之前的全部(待审查11个+任务交付179个)。 ## 已知坑 - **时间窗不能先拍脑袋,必须先用工具确认当天与"上周五"的北京时间边界(2026-07-13 再犯后补强)**:用户要求"查看北京时间上周五及今天,邱律师(查sender id)发了多少合同"时,不能直接按自己理解硬算时间窗,更不能在没交叉验证前就下"周五=0份"结论。必须至少两步核对:①先用 `TZ='Asia/Shanghai' date` 确认当前北京时间与星期;②把北京时间窗口换算成 UTC 后,再同时查 `.meta` 和 `gateway.log`。**只要 gateway.log 已出现 QiuTing 的空消息(msg='')文件记录,就不能再说该时段 0 份。** - **查"邱律师发了多少合同"必须双源交叉,不可只靠 cache meta(2026-07-13 教训)**:仅查 `~/.hermes/cache/documents/*.meta` 可能漏掉周五文件或误判为 0。标准做法: 1. `.meta`:按 `sender_id == QiuTing` + 北京时间窗口换算后的 UTC 区间统计 2. `gateway.log`:按同一时间窗 grep `platform=wecom user=QiuTing chat=QiuTing msg=''`,这是文件消息的一手证据 3. tracker + xlsx:核对这些文件是否都 workflow 完成、是否都 pass、是否都登记 excel 4. **没有把两源对齐前,不得下"都完成了/没有遗漏"结论** - **被 Doro 说"胡说八道,重新查,查明情况再说"时,表示你刚才的结论缺乏证据链**:此时必须回退到原始数据源重查,不得只改措辞继续硬答。正确做法:先承认上一条没查明,再用不同数据源(如从 meta 转到 gateway.log)交叉验证。 - **审查workflow交付物时必须区分原文自带修订 vs WB修订(2026-07-13 Doro指示)**:Doro要求"对照原文件,WB-1如果是原文自带修订,保持不动。你只需要看workflow是不是准确遵守了workflow的规则,包括命名规则"。具体方法:①用zipfile读原文件(待审查目录)的revision authors,确认哪些author是原文已有的 ②读交付件,按author过滤只看WB的修订 ③逐条核对WB修订是否符合审查规则 ④原文自带的修订(其他author)不评价、不报告为问题。**常见误判**:把原文WB-1的修订当成workflow的WB修订来审查——必须先确认author再下结论。 - **核查汇报必须先用工具验证再说(2026-07-01+07-02 升级为顶部红线)**:详见本skill顶部「红线:验证指令 ≠ 回忆指令」章节 + `references/audit-methodology.md`。2026-07-02 同一轮对话中此规则被违反4次,导致Doro信任破裂("到了无法信任你的程度")。不再重复列举——执行时加载顶部红线即可。 - **"确定吗?再仔细查实"(2026-07-02 追加)**:Doro说"确定吗""再仔细认真查实"时,意思是**上一条回复的结论不够可信/不够深入**。正确做法:不要只是重复上一轮的输出,而是用不同角度/更深层的工具去交叉验证。具体:①查session_search看是否有其他session做过相同操作 ②查uwf step list确认每个thread走到了哪一步 ③确认通知确实被发出(session中有_send_wecom的tool call记录)而非假设。核心原则:**每一条"确认X发生了"的结论,都必须有对应的工具调用输出作为证据。** - **文件"消失"的排查思路(2026-07-01 教训)**:Doro发现待审查/交付目录文件不在时,不要急着说"被误删"。先排查:①tracker 的 `cleaned` 字段是否已为true(cron清理了)→ 不可能不到24h;②auto_notify 是否正常工作(文件可能从来没上传到待审查目录,workflow直接从cache路径读文件完成审查,但Doro在Nextcloud看不到);③是否是手动操作中 `docker exec rm` 删了。根因很可能是"从来没上传到待审查"而不是"上传后被删了"。 - **交付件被 Doro 手动编辑后大小/内容会变 —— pass 时不覆盖、只登记(2026-06-23 实证)**:Doro 收到交付件后可能自己在 OnlyOffice 里编辑(删掉部分批注内容、改条款等),导致 Nextcloud 上的交付文件大小/字节/md5 与 workflow 原始交付件不同;且 OnlyOffice 后台处理期间文件**大小会持续变化**,docker cp 出来用 pymupdf/python-docx 读可能是 0 页/0 批注的中间态。**看到交付件与你核验时不一致,先确认是不是 Doro 自己改的,绝不要当成文件损坏去"修复"或用本地完整版覆盖。** Doro 说 pass 后:以 Doro 改后的版本为准,只写 tracker+xlsx 登记,**不触碰交付目录里的文件**。2026-06-23 教训:交付 PDF 从 20MB 变 3.5MB 且持续变化,我误判损坏、准备用本地版覆盖,Doro 说"是我删除了一些内容,你直接做 pass 就可以"。 - **PDF 扫描件合同走批注模式,不 OCR 转 docx(2026-06-23 Doro 明确)**:收到扫描件 PDF 合同(无文字层)时,**不要纠结"OCR 转 docx 才能修订"**——workflow 对 PDF 用**批注模式**审查(高亮 Highlight + 批注气泡 Text 配对,author=WB),原文零污染,这是正常流程(seq=201 朱家角.pdf、seq=207 阳澄湖团建.pdf 都是 PDF 批注交付)。Doro 原话"PDF 的修改使用批注,这是 workflow 的正常流程"。PDF 交付件**核验/pass 时**:核批注数/author/高亮锚定位置/原文页数不变,**不套用** INS/DEL/字号/numPr 那套 docx 专项检查(final_review 对 PDF 会自动判定这些不适用)。 - **交付文件位置:根目录 `任务交付/` 是正确的(2026-06-29 确认)**:workflow deliverer 明确写着上传到 `Doro合同审查任务/任务交付/`(根目录)。不要把文件移到顾问单位子文件夹——那里不是交付目的地。pass 流程步骤0核对时,交付文件应该在根目录 `任务交付/` 中。如果 deliverer 同时上传到了子文件夹(如 final_review 越权重复上传),子文件夹里的是多余的,应清理。 - **final_review 越权重复上传(2026-06-29 教训)**:workflow 中上传是 deliverer 的职责,final_review 只负责质量检查和通知 Doro。但 LLM 执行 final_review 时可能越权上传文件(到子文件夹而非根目录 `任务交付/`),且使用完全不同的命名格式。**判别**:`stat` 比较文件大小,同内容两份不同名=重复。**处理**:保留 deliverer 上传的(【修】/【审】+ 原始文件名),删除 final_review 额外上传的。**根因**:review-contract.yaml 的 final_review procedure 需修改,明确禁止上传动作(待 WeiWei 实施)。 - **审查意见标题空壳(2026-06-29 教训)**:workflow editor 生成的审查意见文档标题是"关于《合同》的审查意见"——"《合同》"是占位符,没有替换为实际合同名称。classifier 已正确提取了 `contract_title`,但 editor 生成审查意见时没有填入。**pass 流程必须检查审查意见标题**:如果标题是"关于《合同》的审查意见",需要手动修正为"关于《{合同实际名称}》的审查意见"。修正方法:用 zipfile + lxml 读 document.xml,找到标题段落的所有 `w:t` 节点,第一个设为完整标题,其余清空。 - **【审】审查意见文件缺失(2026-06-30 发现)**:deliverer 有时只上传【修】修订版而没有上传【审】审查意见文件。**排查**:`sudo find .../任务交付/ -name "【审】*"` 检查是否有对应的审查意见。**处理**:如果缺失,需要手动生成或报告Doro确认是否需要补做。审查意见是companion文件,不影响主合同的pass流程(不需要tracker/xlsx条目),但Doro可能期望看到。 - **Workflow常见格式缺陷清单(2026-07-13 审计总结,详见 `references/pre-pass-audit-checklist-20260713.md`)**: 1. WB INS rFonts多余属性(hAnsi/cs/hint)——每份都有,每次审计必查必清 2. 新增条款标题pStyle错误(用了Style15而非Heading4等) 3. 新增标题带空格("第七条 转包"应为"第七条转包") 4. 子编号未顺延(只改了章编号,没改内部X.Y编号) 5. 赔偿上限遗漏(双向条款的20%上限未删) 6. 保密存续重复插入(原文已有同义表述) 7. 脚注未用修订格式 - **同模板合同修订一致性(2026-07-03 Doro确认:手动调整)**:不同顾问单位提交审查的合同若为相同模板,修订需保持一致(规则9已有)。但不改workflow自动化逻辑——Doro指出不一致时手动调整即可,避免矫枉过正。不需要template_type标签或修订库。 - **非合同文件进入workflow审查(2026-07-01 香花桥招标需求实证)**:classifier识别出"招标需求文件"但仍走了完整review+edit流程并交付了修订版。Doro判定此类文件无需审查,直接通知邱律师。**已修auto_notify加L1文件名预筛**(关键词:招标需求/技术方案/报价单等)。但classifier兜底层未修——仍然会把非合同当合同审。根因:workflow的routing graph没有从classifier直接到$END的non-contract路径。**pass前排查**:如果交付物对应的原始文件明显不是合同(招标需求/投标文件/会议纪要等),直接删除交付物并通知邱律师,不做pass。 - **审查意见文件是错误交付物(2026-07-01 发现)**:workflow 有时会生成不该有的审查意见文件(如合同已被其他thread审查过、或classifier误判导致多生成)。**pass前排查**:检查任务交付目录中是否有不属于本次审查的【审】文件。如果是错误交付物(如旧版残留、重复审查产物),直接删除不pass。判断标准:审查意见的标题和内容是否对应本次审查的合同,标题是否是"关于《合同》的审查意见"(占位符)。 - **cleanup脚本不处理tracker顶层"ready"条目 + 不清理本地目录(2026-07-03 查明根因)**:`contract-cleanup.py` 只遍历 `tracker["contracts"]` 数组中 `status=completed` + `cleaned=False` 的条目。但以下两类文件不会被清理: 1. **Tracker顶层字典键**(status=ready的条目):这些是workflow跑完但尚未被Doro pass的合同,存储在tracker的顶层键而非contracts数组里。cleanup脚本看不到它们。Doro pass后,pass流程会把它们从顶层搬到contracts数组并标记completed——此时cleanup才能处理。**如果pass流程有bug没搬进contracts数组,文件永远不会被清理。** 2. **本地 `~/.hermes/shared/Doro合同审查任务/待审查/` 目录**:cleanup脚本只删Nextcloud容器内的文件(`docker exec rm`),本地shared目录的副本无人管理。这些是auto_notify上传NC后留下的本地副本——NC侧被cleanup清了,本地的永远残留。 - **诊断方法**:`ls ~/.hermes/shared/Doro合同审查任务/待审查/` 如果有文件,且对应的tracker条目已经completed/ready,就是此bug。 - **临时清理**:确认文件在tracker中已completed后,手动 `rm` 本地副本即可。 - **根治**:cleanup脚本需要增加两段逻辑:①遍历tracker顶层ready条目(Doro已pass但pass流程漏搬的)②清理本地shared/待审查/中已completed的文件。 - **沟通时间统一使用北京时间(2026-07-03 Doro多次纠正)**:所有对外沟通(包括向Doro汇报workflow状态、文件接收时间等)一律使用北京时间。服务器UTC时间仅在内部日志/脚本中使用,不对用户展示。 - **Doro说"查清楚"/"你去查原因"——必须用工具验证后再给结论**:Doro要求查证时,不能凭记忆或context summary回答。必须实际调用工具(session_search、terminal读文件/目录、tracker等)取得证据后再汇报。2026-07-13教训:被要求"查清楚我哪些说了pass"时,需要在本session对话记录中找到Doro的原话,不能凭印象列清单。 - **脚注"法律顾问修订版"必须用修订格式(2026-07-13 Doro纠正)**:添加页脚文字时,必须包裹在`w:ins`元素中(author=WB),不能作为普通文本直接写入。OnlyOffice/Word中显示为带修订标记的新增内容。实现方式:用python-docx添加footer文本后,再用zipfile+lxml找到footer XML中的对应run,包裹进`w:ins`元素。 - **交付件字体大小不一致(2026-07-01 Doro纠正,根因细化)**:有两种常见成因: 1. **段内混用**:同一段落内不同run使用不同字号(如sz=21和sz=24混用)。修复:找dominant size统一。 2. **INS-only新段落缺sz**(更隐蔽):`add_clause`插入的全新段落,原文docDefaults无sz定义或sz≠邻居段落的显式sz。新段落INS run不写sz→继承docDefaults→与显式sz=24的邻居段落不一致。**诊断**:找所有INS-only段落(整段只有w:ins),检查其run的rPr是否有sz,再对比前后段落的sz。缺sz且邻居有显式sz=补上。 3. **numPr叠加**:加了手动编号但没去掉原自动编号→显示"1. 第一条"。修法见contract-editor skill的"strip numPr"规则。 - **deliverer 重复上传(同内容两份不同名)(2026-06-29 教训)**:deliverer 可能对同一合同上传两次,文件大小完全一致说明内容相同。**判别**:`stat` 比较文件大小。**处理**:保留命名规范的那份(【修】/【审】+ 原始文件名),删除另一份。 - **原文件自带【修】前缀时命名错误(2026-07-13 璞石合同实证)**:邱律师发来的文件本身已有【修】前缀(如`【修】练塘-硬件购销合同-璞石医疗2026.7.13.wps`),workflow按规则应生成`【修】【修】练塘-...docx`(原文件名一字不动,前面再加【修】),但实际只生成了`【修】练塘-...docx`(吞掉了原文的【修】前缀)。**审查时判别**:原文件名含【修】时,交付文件名应出现两个【修】。只有一个=workflow命名错误。**根因**:workflow的命名逻辑strip了原文件名中已有的【修】前缀后再加自己的。 - **Cache hash前缀混入交付文件名(2026-06-30 实证)**:deliverer 有时把 cache 文件名直接当交付文件名上传,产生类似 `【修】doc_0ad4ee63bff5_印刷品制作合同2026.6(1).docx` 的文件名。**判别**:文件名含 `doc_[a-f0-9]{12}_` 模式。**处理**:删除带 hash 前缀的那份,保留正确命名的(`【修】印刷品制作合同2026.6(1).docx`)。**根因**:deliverer 从 cache 复制文件时没有去掉 cache hash 前缀重命名。 - **companion 文件(审查意见/流程单)不会被 cleanup 自动清理 → 永久残留孤儿(2026-06-21/29 实证)**:`contract-cleanup.py` 只删 tracker 里 `original_filename`/`converted_filename`/`delivered_filename` 三个字段精确匹配的文件。companion 从不进 tracker → 主合同被清理后 companion 永远残留。**排查**:运行 `references/cleanup-audit.py` 审计脚本,列出所有 not-in-tracker 的文件。**应急清理(Doro 授权后)**: ```bash # 必须搜索所有任务交付路径(根目录+顾问单位子目录都可能有) sudo docker exec nextcloud-nextcloud-1 find /var/www/html/data/doro/files/Doro合同审查任务/ -path "*任务交付*" -name "*审*意见*" # 确认后逐个删除 sudo docker exec nextcloud-nextcloud-1 rm -f "" "" ... # 扫描+清缓存 sudo docker exec -u www-data nextcloud-nextcloud-1 php occ files:scan doro --path="/doro/files/Doro合同审查任务/" docker exec nextcloud-onlyoffice-1 bash -c 'rm -rf /var/lib/onlyoffice/documentserver/App_Data/cache/files/data/*' docker restart nextcloud-onlyoffice-1 ``` ⚠️ Companion 可能同时存在于根目录 `任务交付/` 和顾问单位子目录(如 `朱家角镇社区卫生服务中心/任务交付/`)——find 命令用 `-path "*任务交付*"` 通配搜索确保不遗漏(2026-07-06 实证:肃言/恭兴审查意见各在两个位置共4份)。 **治本方案见 `references/companion-cleanup-proposal.md`**,需 WeiWei 决策后实施。 - **Companion 判定不能靠想当然命名(2026-07-13 白鹤劳务派遣协议教训)**:Doro说“交付文件夹里的合同及companion全部做pass”时,**先查清交付文件夹里到底有哪些相关文件,再决定有没有 companion**。不要因为很多合同通常会带审查意见,就默认本案也有;也不要只看主文件名一次就下结论。正确顺序:①先在 `任务交付/` 中做**宽匹配扫描**(合同名关键词、当事人名关键词、业务关键词都要扫)②再把整个 `Doro合同审查任务/` 目录补扫一遍,确认没有藏在别的子目录里的 companion ③最后再对 tracker+xlsx 核对是否已有 completed 记录。**如果实扫结果只有主合同一份,就要明确汇报“合同1份、companion 0份”,而不是笼统说“都pass了”。** - **Companion 命名规则(2026-07-01 Doro确认)**:交付物=`【修】{原文件名}`,companion=`【审】{原文件名去扩展名} 审查意见.docx`。pass skill 扫描时按此规则拼确定性文件名+`nc_file_exists()`查询,查到就写入 tracker 的 `companion_files` 字段。cleanup 脚本读 `companion_files` 一并删除。两头(pass skill + cleanup script)必须同步改才有效果。workflow editor/deliverer 生成审查意见时也必须强制用此命名规则。 - **手动启动workflow前必须查实auto_notify是否已处理(2026-07-03 教训)**:发现cache中有新文件时,不要直接`uwf thread start`。必须先:①查`/tmp/auto_notify_new_file.log`确认auto_notify是否已检测到并启动了workflow ②`uwf thread list | grep running\|idle`看是否已有对应thread在跑。auto_notify正常工作时会自动上传NC+启动queue runner+排队执行,手动启动会导致重复审查。2026-07-03实证:邱律师发了文件,auto_notify 30秒内就启动了workflow(thread 06FJDADW),我没查就又手动启动了第三个重复thread,被Doro纠正。 - **不判断文件是否相同/重复(2026-07-03 Doro纠正)**:邱律师发了几次、发什么文件,不是我该判断的。不要说"同上""同名文件""是重复的"——即使md5一致也不做这个判断。auto_notify和workflow自己处理,我不干涉。待审查目录里有几份就是几份,workflow跑几个就是几个。 - **沟通必须使用北京时间(多次纠正)**:所有与Doro/Maggie的时间沟通一律用北京时间,包括表格、汇报、日志引用。服务器是UTC,必须+8转换后再输出。不写UTC时间。 - **"删掉批注"指令——先验证再操作(2026-07-08)**:Doro可能要求"删掉批注,提交后做pass"。操作前必须先用zipfile检查comments.xml是否存在+commentRangeStart数量。如果文件无批注(comments.xml不存在且无comment引用元素),直接汇报"文件无批注"然后继续后续操作(提交/pass),不需要强行"删除"不存在的东西。 - **邱律师同名文件不自行判断是否重复(2026-07-03+07-09 加强)**:邱律师发的多次同名文件,不要自己判断"重复发送"。同名文件可能:①不同顾问单位②修改版(字节不同=内容变了)③同一份发了两次。**只要字节大小不同,就必须视为不同合同独立审查**。2026-07-09教训:字节差407的两份合同有3处实质性条款差异(项目名称、支付方式、合同期限),不是"重复发送"。正确做法:检查字节是否相同→不同→独立审查或私信邱律师确认。 - **交付件大小/内容突变,先确认是不是用户手动编辑了,别当损坏(2026-06-22 实证)**:pass 前看到 Nextcloud 上的交付 docx/pdf 大小骤变(如 20MB→3.5MB 还在持续变、md5 对不上本地核验版),第一反应**别**判为"文件损坏/被进程写坏"。先确认是不是 Doro 自己在 OnlyOffice 里改了(删批注、调内容)。确认是用户编辑后:**不覆盖、不动交付目录的文件,以用户改后版本为准**,直接走 pass 登记(tracker+xlsx)。**铁律:pass 登记只动台账(tracker/xlsx),交付目录里的成品文件除非用户明确要求重做,否则只读不写。** - **`.doc` 原始文件残留(2026-06-29 实证)**:tracker 的 `original_filename` 有时记录了 `.docx`(转换后文件名)而非 `.doc`(真正的原始文件),cleanup 按 `.docx` 去删找不到 `.doc` → 残留。**排查**:cleanup-audit.py 会标记为 NOT_IN_TRACKER。**预防**:pass 写 tracker 时确保 `original_filename` 是邱律师发来的真实文件名(含扩展名),不要写转换后的文件名。 - **编号"错误"误判——markup视图双编号是正常现象(2026-06-16教训)**:OnlyOffice/Word修订视图(markup)下,自动编号列表项被删除(含他人如屠佳青的删除)或新增条款插入后,后续项会显示"(3)(2)""(4)(3)"这类双编号——前一个是删除/插入前的旧编号,后一个是接受修订后的新编号。这是track changes的正常渲染,**不是编号错误**。Doro/Maggie说某份合同"编号修改错误"时,先别急着改: 1. 用execute_code生成"接受所有修订后"的版本:删除所有`w:del`元素 + 删除带段落标记删除(`pPr/rPr/del`)的整段 + 解包所有`w:ins`(把ins的子元素提到父级再删ins壳) 2. 用OnlyOffice x2t渲染accept版PDF,pdftotext看**最终编号**是否连续正确 3. 若accept后编号正确 → workflow没改错,markup双编号是正常的,**恢复workflow原版即可,不要擅改** 4. 若要改,必须先问清Doro/Maggie指的是accept后哪一处,他人(屠佳青等)的修订按铁律不能动 - 2026-06-16教训:把端午节合同(转包6误改成7)、医疗急救招聘合同的正常编号重排误判为错误并擅自改动,被Maggie两次纠正"workflow改得都没错,恢复成workflow修订版本"。x2t accept渲染命令见本skill的"用OnlyOffice渲染自查"或contract-editor skill。 - **但确有真编号重复时要修(2026-06-15端午节实测,与上条不冲突)**:自动编号列表项(numbering.xml中`numId`对应的`abstractNum`有`start=N`、`lvlText=%1、`)渲染出的编号,与后续**手动键入数字**的tracked插入条款(`w:ins`里`w:t`文本直接以"6、""7、"开头)会真冲突。本案"售后服务"是auto-number(numId=3, start=6 → 渲染为6),紧跟的WB插入条款手动写了"6、转包限制" → 出现两个6。这是**真错误**,不是markup双编号假象。判别要点: 1. 假象(不改):同一条款同时显示两个编号"(3)(2)",是track changes接受前后的新旧编号叠加 → accept后渲染连续就别动 2. 真错(要改):两个**不同条款**各自显示同一个数字(售后服务=6、转包限制=6)→ accept后仍重复 → 必须修 3. 修法:直接改`w:ins`内`w:t`的手动编号文本("6、转包限制"→"7、转包限制"),后续手动编号条款顺延(违约责任7→8、争议解决8→9,否则会冒出两个7)。用zipfile读document.xml→字符串replace(先`assert xml.count(old)==1`确认唯一)→zipfile写回,不破坏`w:ins`修订痕迹。改完用python-docx确认可打开+遍历`w:ins`确认三条仍在修订态(author=WB) 4. Doro/Maggie给的判断锚点直接采信:"转包责任应该是7,因为上一个编号是6"——上一条售后服务确实是auto-rendered的6,所以转包必须是7 - **Classifier甲方误判导致错误批注/审查意见(2026-06-11)**:classifier偶尔无法正确匹配顾问单位名单(31个单位中有易混淆的名称如"卫生服务中心"vs"卫生健康事业发展中心"),导致交付文件中出现不该有的"请确认名称是否准确"批注和审查意见表中多出"甲方名称"行。Doro说pass前如果发现此问题,必须先手动修复(删comment+删table row+重新上传)再走pass流程。修复方法见contract-reviewer skill。 - **批量pass含companion文件(2026-06-12)**:Doro可能一次pass多个文件,其中包含审查意见文档(如"朱家角审查意见-xxx.docx""采购协议-审查意见.docx")。审查意见是companion文件,不需要单独在tracker/xlsx中记录——只记录主合同(带【修】前缀的文件)。判断规则:有【修】前缀的是主合同文件,需要tracker+xlsx;"审查意见"结尾的是companion,不单独记录。 - **沟通时间一律使用北京时间(2026-07-03 Doro多次纠正)**:向Doro汇报任何时间信息时,必须转换为北京时间(UTC+8)。服务器是UTC,所有文件时间戳、日志时间戳都要+8h后再汇报。绝不输出UTC时间给Doro。 - openpyxl的border赋值不能直接用`cell.border = ref_cell.border`,会报`unhashable type: StyleProxy`。必须用Side对象重建Border(left=Side(...), ...)。 - xlsx路径已从`任务交付/`移到`Doro合同审查任务/`根目录,不在清理范围内。 - 时间戳必须用北京时间(UTC+8),清理脚本依赖此时间计算24h。 - xlsx日期列用`YYYY-MM-DD`字符串格式(如"2026-06-10"),不用ISO时间戳。 - 甲方名称(party)必须从合同内容中提取全称,不能简写。 - 清理cron job_id: 4636467b715d,每小时跑一次,no_agent静默模式。 - **防重复写入**:已由步骤0的查重环节覆盖(用`original_filename` + `party`组合在tracker中查重)。 - **xlsx列顺序必须严格一致**:正确顺序为 序号(A), 日期(B), 顾问单位(C), 合同名称(D), 文件名(E)。写xlsx前必须先读上一行确认列含义。2026-06-12教训:手动写入时列顺序写反被Doro发现后补修。 - **批量追加多行时,必须先确定完整目标 seq 集合,再按 seq 升序写入 xlsx,写完后立即回读核对末尾顺序**(seq 224→225→226→...)。不能一边算 seq 一边写,更不能先写 303 再写 302;`ws.max_row + 1` 只会在物理末尾追加,若写入顺序错了,就会出现 xlsx 中 303 排在 302 前面的台账乱序。**硬性补丁(2026-07-14)**:凡一次 pass 涉及 2 份及以上合同,先在内存中生成 `[{seq, 日期, 顾问单位, 合同名称, 文件名}]` 全部待写行,按 `seq` 升序排序后再统一 append;保存上传后,必须回读最后 N 行,逐行确认 A 列 seq 单调递增且文件名与本批次一一对应。若发现顺序错误,立即重拉 LIVE xlsx 重写,不得带错交付。 - **按“北京时间今天/昨天/上周五”统计邱律师发了多少合同时,先定时间窗,再查 meta,再对照审查状态(2026-07-14 再次实证)**:这类问题不能直接凭印象回答“几份、都做完了吗”。标准顺序必须是:①先用 `TZ='Asia/Shanghai' date` 锚定今天的北京时间窗口;②查 `~/.hermes/cache/documents/*.meta` 中 `sender_id=QiuTing` 且落在该窗口内的文件,得到**今天实际收到的文件清单**;③再去 tracker 中逐份核对这些文件对应的 `status/seq/delivered_filename/xlsx_updated_at`;④最后才汇总结论。**重点**:meta 统计的是“今天收到几份”,tracker 统计的是“这些文件审查到了哪一步”,两者不能互相替代。 - **同名不同来源/不同顾问单位的合同,统计和汇报时必须拆开,不得混成一句“劳务派遣协议已完成”(2026-07-14)**:如果今天收到的是 `劳务派遣协议.doc`,但 tracker/任务交付里同时还存在 `【修】白鹤--劳务派遣协议.docx` 等近名文件,汇报时必须明确区分“今天收到的这份”与“其他同类/同模板/同主题文件”。不能把别的合同的 completed 记录拿来替代今天这份的审查状态。标准动作:按 `original_filename` 精确对 tracker 命中,再补充说明是否另有近名已完成文件。 - **被用户要求“再核查一次”时,必须升级为四路交叉核查,不得只复述上一轮结论(2026-07-14)**:对“是不是同名合同但顾问单位不同”“今天收到的到底是哪几份”这类追问,重查时至少同时核:①Nextcloud 全目录文件扫描(不只根目录任务交付)②tracker 命中记录 ③xlsx 命中记录 ④cache meta/原件名称与正文中的甲方或项目编号。回复必须把四路结果并列写出,再下结论。不能只说“我刚才已经看过了,结论不变”。 - **自动 cleanup 依赖 tracker 文件名与 Nextcloud 实际文件名精确一致(2026-07-14)**:若发现“历史台账文件名”和“当前 NC 实际文件名”不一致,即使该条记录已经 completed,仍要同步修正 `tracker.original_filename` / `tracker.delivered_filename`,必要时同步修正 xlsx 第 E 列文件名,确保三者一致:①tracker ②xlsx ③NC 实际文件。否则 cleanup 按精确文件名匹配时会漏删或误判。修正后必须再次核对目标待审查文件与任务交付文件在 NC 中确实存在。 - **用户只要结果,不要过程解释时,汇报必须收敛成一句结论(2026-07-14 Doro纠正)**:当用户明确说“我就要一个结果”时,禁止继续附加过程说明、背景铺垫、风险解释或“补一句严谨的”。此类场景只回复最终结论,例如“能。”、“已完成。”、“不能,需要X条件。”;如确需补充,等用户追问后再展开。 - **统计“今天收到的合同”时,先答收到清单,再答审查状态,别把两件事揉成一句笼统结论(2026-07-14)**:推荐输出结构固定为:①今天收到几份;②逐份列出收到时间;③逐份列出审查状态(未启动 / 审查中 / delivered / completed);④如有同主题近名文件,单列“另有近名已完成文件,不等于今天这份”。这样可以避免把“收到数量”和“已完成数量”说串。 - **乙方空白时的处理**:合同乙方名称空白→不问Doro"该问谁",直接查文件来源(cache/documents时间戳+session_search找发送人),确定是邱律师发的就直接私信邱律师询问。Doro可能说"邱律师回复了就直接补上 我不管了"——意思是邱律师回复后自主完成tracker+xlsx更新,不再找Doro确认。 - **私信邱律师的send_message路由问题**(2026-06-12):`send_message(target='wecom:QiuTing')`会路由到home channel而非QiuTing私信。必须用`_send_wecom(extra, 'QiuTing', msg)`直接发送才能到私信。 - **私信任意企微用户的首选方法 = `wecom_dm.py` 脚本(2026-06-21 再犯后确立)**:`send_message(target='wecom:')` 对企微用户名会**静默回退到 home channel**(JiaQian),既发不到目标、又可能违反信息隔离(把 Doro 团队内容发进 Maggie 渠道)。`_send_wecom(extra, ...)` 需要 gateway 的 `extra` 上下文,在 terminal/execute_code 里拿不到。**最稳的通用方法**是独立脚本:`python3 ~/.hermes/scripts/wecom_dm.py --to <别名> --text "内容"`——自己开 WebSocket + `aibot_send_msg` + `chat_type=1` 发主动私信,永不串号、目标唯一确定。白名单别名:doro / jiaqian(贾茜Maggie) / qiuting(邱律师) / weiwei(技术支持) / shasha(苌莎莎) / yangayi(颜伽艺) / xiaonan。先 `wecom_dm.py --list` 核对别名→userid 再发。**铁律:私信非 home 的企微用户,一律走 `wecom_dm.py`,绝不用 `send_message(target='wecom:X')`。** 2026-06-21 教训:给魏玮发技术讨论用了 `send_message(target='wecom:WeiWei')`,静默落到 JiaQian home channel,既没到魏玮又把 Doro 团队的脚本细节泄露进 Maggie 渠道,改用 `wecom_dm.py --to WeiWei` 才真正送达。 - **不干涉workflow铁律(2026-07-03 Doro纠正)**:发现邱律师发了新文件时,**禁止直接手动启动workflow**。必须先查 `/tmp/auto_notify_new_file.log` 和 `uwf thread list` 确认auto_notify是否已经处理。不判断文件是否重复("你也不要去判断是不是同一份文件")——让系统自己处理。2026-07-03教训:auto_notify已正常工作并启动了workflow,我没查就手动又启动了一个重复的。 - **手动启动workflow前必须查实auto_notify是否已处理(2026-07-03 Doro纠正)**:发现邱律师发了新合同时,**第一步不是手动启动workflow**,而是:①查`/tmp/auto_notify_new_file.log`确认auto_notify是否已检测并处理 ②查`uwf thread list | grep running\|idle`确认是否已有对应thread在跑。只有确认auto_notify没处理(日志无记录+无相关thread)才手动启动。2026-07-03教训:auto_notify已正常触发并启动了workflow,我没查就又手动启动了一个重复的。**"收到即启动"的前提是"确认没有被自动处理"。** - **不判断邱律师发的文件是否重复/相同(2026-07-03 Doro纠正,2026-07-08 再次验证)**:邱律师多次发送同名文件时,不要自行判断"是同一份发重了"。存在同文件名但不同版本、不同条款内容的可能性。2026-07-08实证:同日发两份"2026年华新镇公立中小学生健康体检服务合同.docx"(11:05和16:04),文件大小不同(27889 vs 27482 bytes),实际条款差异显著(项目内容、付款方式、合同起始日均不同)——完全是两份实质不同的合同版本。queue-runner因同名文件已在done/直接SKIP导致第二份没审查。**核查方法**:用python比对两文件文本差异(zipfile提取全文+difflib对比),有实质差异则为不同合同/不同版本,须独立审查。**报告时绝不说"重复发送"**——Doro问"如何判断是重复发送"即提醒不该自行判断。 - **恢复文件到workflow原始交付状态(2026-07-13 实证)**:Doro要求撤销手动修改、恢复到workflow交付版本时,三个来源按优先级尝试:①NC版本历史(`files_versions/`目录下的`.vXXXXXXXXXX`文件,时间戳最早的=workflow首次上传版本)②`/tmp/pass_check_*`文件(之前做pass检查时从NC拉取的快照,如果当时还没手动修改则=workflow版本)③重新跑workflow(最后手段)。恢复后必须`docker cp`回NC + `chown www-data` + `occ files:scan`。 - **Tracker重复条目(同一合同不同文件名)**:workflow跑多轮或auto_notify重复触发时,tracker可能出现同一合同的多条delivered记录(文件名带版本后缀如`_v0113.doc`vs不带的)。批量pass时需注意:只pass实际在任务交付目录中的那份(【修】前缀的),旧的重复delivered条目如果文件名与NC上的交付物不匹配,不影响pass流程但会残留在tracker中(cleanup会因找不到文件而跳过)。 - **手动启动前必须先验证auto_notify是否已处理(2026-07-03铁律)**:发现邱律师新文件后,**禁止**直接手动启动workflow。必须先:①查`/tmp/auto_notify_new_file.log`确认auto_notify是否已检测并处理该文件 ②`uwf thread list | grep running`确认是否已有workflow在跑。只有确认auto_notify确实没工作(进程死亡或模式B静默失效)才手动介入。2026-07-03教训:auto_notify已正常触发workflow,但我没查就手动又启动了一个重复的,被Doro纠正"不要去干涉workflow"。**邱律师发多次同名文件不自行判断是否相同**——让系统处理,同时私信邱律师确认。 - **交付通知丢失的诊断与补发(2026-07-09 实证)**:Doro说"有几份合同我没有收到交付通知"时,按以下流程排查:①检查tracker中status=delivered的记录 ②查gateway.log在对应delivered_at时间前后是否有846609/Timeout/WS closed错误 ③确认是WS断连导致fire-and-forget通知丢失 ④用`wecom_dm.py --to doro`补发。**根因是企微WS凌晨不稳定+通知无重试机制,详见 `references/notification-ws-failure-pattern-20260709.md`。** 临时止血=手动补发;根治=watchdog增加notification_sent检查和补发逻辑(方案待实施)。 - **通知静默丢失——workflow完成但Doro没收到通知(2026-07-09 实证)**:workflow全流程跑完(thread status=end, tracker=delivered),但Doro说没收到交付通知。根因:`final_review`步骤通过`_send_wecom`发通知时,企微WebSocket已断连(errcode 846609: aibot websocket not subscribed),发送静默失败,无重试机制。**症状**:Doro说"没收到通知" + tracker有delivered记录 + `grep '846609' ~/.hermes/logs/gateway.log`在final_review执行时间段有报错。**诊断**:①查tracker找delivered_at时间 ②查gateway.log该时间±5分钟有无846609/WebSocket error ③确认final_review步骤确实跑完了(`uwf step list `有final_review且thread status=end)。**补救**:用`python3 ~/.hermes/scripts/wecom_dm.py --to doro --text "合同审查完成通知(补发):..."`手动补发。**预防**:目前无自动重试机制。如果发现当天有WebSocket中断记录,主动检查该时段内完成的所有workflow是否通知成功——以gateway.log中有对应的"Sending response...to doro"记录(不含后续846609 error)为准。 - **"delivered但Doro没收到通知"诊断(2026-07-09 实证)**:Doro说"有几份没收到交付通知"时,根因通常是final_review发通知时WebSocket已断(846609错误)。Workflow全程跑完(classifier→reviewer→editor→reviewer→deliverer→final_review),queue-runner标记delivered,但final_review内的`_send_wecom`因WS断连静默失败。**诊断步骤**:①tracker找status=delivered的合同 ②gateway.log搜846609和"WebSocket error"确认断连时段 ③`uwf step list `确认final_review确实执行过(有时长=执行了) ④确认交付文件在任务交付目录存在。**补救**:用`wecom_dm.py --to doro --text "合同审查完成通知(补发):..."`补发。补发内容含:合同名、顾问单位、修订数(ins/del计数)、主要修订摘要。注明"因XX时段企微WebSocket中断未实时送达,现补发"。 - **Watchdog "suspended + already delivered" 死循环(2026-07-08 实证)**:当 workflow 在 `final_review` 阶段因 HTTP 500 suspended,但 deliverer 已经成功交付(tracker=delivered)时,watchdog 会无限循环 BLOCK resume 且不归档文件,同时阻止 runner 重启。表现:Doro 没收到通知但文件已在任务交付目录。**诊断**:`tail /tmp/contract-queue/watchdog.log | grep "BLOCK resume"`。**止血三步**:①`uwf thread cancel ` 取消所有suspended thread ②`mv /tmp/contract-queue/ /tmp/contract-queue/done/` 清空queue ③对已delivered的合同直接做pass(tracker delivered→completed + xlsx追加)。**注意**:可能影响一批合同(2026-07-08是5份同时卡住),需全部处理。详见 `references/watchdog-suspended-delivered-deadlock-20260708.md`。 - **同名文件判重铁律(2026-07-08 华新镇体检合同教训,Doro明确要求)**:判断两份合同是否为"同一份",**绝不能只看文件名**。邱律师经常对同一份合同发送修改版(文件名不变但内容已改),也可能不同顾问单位使用相同文件名。**判断标准——必须比对合同实质内容**: 1. 甲方(顾问单位)名称 2. 金额/费用条款(总价、单价、费用上限等关键数字) 3. 合同期限(起止日期) 4. 项目内容描述 5. 字节大小 **以上任何一项不同 → 不同合同/新版本,必须独立审查。全部相同 → 重复发送,可跳过。** 此规则适用于所有环节:auto_notify入队、queue-runner启动前、手动操作、以及任何需要判断"是否已审查过"的场景。auto_notify已通过 `~/.hermes/scripts/contract_content_compare.py` 自动执行内容比对。 **2026-07-08实证**:华新镇体检合同同日发送两版(11:05和16:04),文件名完全相同,但第二版增加了费用上限17万元、项目名称加了"华新镇"、起始日期从9月10日改为9月1日——是实质不同的合同版本。因系统只按文件名判重,第二版被跳过未审查。详见 `references/queue-runner-same-name-different-content-20260708.md`。 - **auto_notify脚本依赖与失败处理**:如果邱律师的合同没有自动启动workflow,先检查`auto_notify_new_file.sh`是否还活着(`ps aux | grep inotifywait`)。该脚本没有守护机制,会静默死亡。**两种失败模式**: - **模式A:进程死亡** — inotifywait进程不存在。watchdog cron (`63bb31d4f050`) 会自动重启。 - **模式B:进程活着但事件静默丢失(更隐蔽)** — 进程在跑但inotifywait没有捕获到任何事件(日志完全为空)。原因可能是:inotifywait的文件描述符失效、文件系统事件被内核丢弃、脚本启动时文件已经到达(race condition)。watchdog无法修复此模式,必须人工介入。 - **诊断方法**:`cat ~/.hermes/logs/auto_notify.log | grep 日期` — 如果日志为空但meta文件有当天文件,就是模式B。 **fallback流程**: 1. 检查 `~/.hermes/cache/documents/` 中是否有未处理的文件(按meta时间戳筛选今天邱律师发的) 2. 手动复制到 `Doro合同审查任务/待审查/`(用 `sudo cp` + `sudo chown www-data:www-data`) 3. 逐个启动 workflow(`uwf thread start review-contract -p "..."`) **⚠️ 主动发现铁律(2026-07-03教训)**:任何操作过程中(查私信状态、查文件、核对数据等),如果发现cache/documents/中有未处理的邱律师文件(meta显示sender_id=QiuTing但对应文件不在待审查目录),必须**立即中断当前任务**,先上传+启动workflow,再继续原任务。"收到即启动"不只是auto_notify的职责——手动发现的文件也必须立即处理,不能"等会再说"。2026-07-03实证:邱律师14:27发了新合同,我在14:30+检查私信时看到了meta记录但没有立即启动workflow,直到Doro追问才处理。 4. **多份合同时串行执行**:写 relay 脚本(等当前 thread `status=end` 后再启动下一个),因为所有合同共用 `/tmp/contract-review/` 工作目录,并行会互相覆盖 5. **后台执行+通知**:`uwf thread exec --count 20 --background`,配合 `notify_on_complete=true` 6. 可选:设 cron job 每15分钟检查 relay 进程是否存活,挂了自动重启 **2026-06-29 实证**:邱律师发了12个文件,auto_notify 没运行,只有4个自动进了 workflow。手动把剩余4个从 cache 移到待审查,写 relay 脚本串行执行,成功完成。 **2026-06-30 实证(模式B)**:邱律师发了4份合同,auto_notify进程在跑但日志完全为空(inotifywait静默失效)。手动处理全部4份。 - **Subagent/delegate_task 的清理禁令**:给 subagent 的 context 中必须明确写入"禁止删除 Nextcloud 待审查/和任务交付/目录中的任何文件。只做被要求的操作(写tracker/写xlsx/上传),不做任何清理"。2026-07-01教训:subagent 在执行 pass 操作时可能做了多余的清理动作导致文件丢失。 - **xlsx文件名列必须统一用delivered_filename**(带【修】或【无修改意见】前缀),不能写原始文件名。 ## 自动清理架构(2026-06-11确认,2026-07-01补充) 活跃 cron 列表(截至 2026-07-02): - **`contract-cleanup`**(job_id: `4636467b715d`):每小时,no_agent,deliver=local。清理 pass 超 24h 的文件。 - **`contract-queue-watchdog`**(job_id: `3174518affda`):每20分钟,no_agent,deliver=local。巡检 queue-runner 是否存活,必要时重启。⚠️ 此 cron 有已知 bug:当 queue/ 中有文件未移入 done/ 时会反复重启 runner 导致重复审查(详见 `references/queue-runner-duplicate-bug-20260702.md`)。**修复前需确保 runner 加了 tracker 查重逻辑。** - **`auto-notify-watchdog`**(job_id: `63bb31d4f050`):每5分钟,no_agent。守护 auto_notify_new_file.sh 的 inotifywait 进程。 - 旧版`清理已交付合同`(agent模式/每6h/deliver到wecom:doro)和`合同审查调度`(每15分钟轮询)已于2026-06-11删除,与现有机制功能重复 - 新文件监控由 `auto_notify_new_file.sh`(inotifywait实时事件驱动)独立承担,不再有cron轮询 ### ⚠️ 文件删除权限铁律(2026-07-01 Doro纠正) **只有 cleanup cron 有权删除待审查/和任务交付/目录中的文件。** 手动操作(包括小Maggie和subagent)不得直接 `docker exec ... rm` 删除这两个目录的文件。 唯一例外:Doro 明确指令删除特定文件(如"删掉这个招标需求")。 2026-07-01教训:小Maggie在执行替换/清理操作时,`docker exec rm` 误删了4份已pass但未满24小时的合同原始文件(徐泾北大居、印刷品、健康积分华新、银发健康包)。文件不在trashbin(docker exec rm绕过trashbin),cleanup cron全天silent确认没动,是手动操作误删。 ### Companion 文件清理方案(2026-07-01 确认) **Companion 类型因顾问单位而异**(不统一为"审查意见"): - 朱家角:审查意见文档(从模板生成) - 爱卫中心:合同流程单(xlsx) - 练塘:脚注(加在合同里,非独立文件,不需清理) - 其他:无 **方案**:deliverer 上传后写 manifest → pass skill 读取 manifest 写入 tracker `companion_files` → cleanup 读取并一并删除。 **manifest 位置**:Nextcloud 任务交付目录下 `.delivery-manifest-{原文件名去扩展名}.json`(隐藏文件)。 **待落地**:cleanup 脚本需增加读取 `companion_files` 字段的逻辑;deliverer YAML 需增加写 manifest 步骤。 ### 清理时机明确定义 - **触发条件**:Doro 说 pass → 写入 tracker(`xlsx_updated_at` 记录当前北京时间) - **清理时机**:cleanup cron 每小时检查 tracker,找 `status=completed` + `xlsx_updated_at` 超过24小时 + `cleaned=false` 的记录 - **即:pass 后 24 小时清理**,不是交付后24小时、不是workflow结束后24小时 - **清理范围**:精确删除 tracker 中记录的 `original_filename`(待审查/)+ `delivered_filename`(任务交付/)+ `converted_filename`(待审查/,.doc转.docx时有值) - **不清理的**:companion 文件(审查意见)不在 tracker 中,不会被自动清理;xlsx 在上一级目录不受影响 ## 防重复审查(2026-07-01 朱家角标识牌 + 2026-07-02 夏阳/家庭医生签约) **已pass合同被重复审查交付的根因**: 1. **relay-runner/auto_notify 不查 tracker**(2026-07-01):启动 workflow 前不检查 tracker 是否已有 completed 记录。manifest 文件残留已 pass 合同的文件名,被重新捡起来跑了一遍。 2. **queue-runner + watchdog 交互 bug**(2026-07-02,详见 `references/queue-runner-duplicate-bug-20260702.md`):watchdog 每20分钟重启 runner(因 done/ 不满 manifest 行数),runner 的 SKIP 逻辑只查文件是否在 queue/ 目录,**不查 tracker**。一天内 watchdog 重启 runner 41次,导致夏阳和家庭医生签约被重复审查并重复通知 Doro。 **铁律**:任何触发 workflow 的流程(auto_notify / relay-runner / queue-runner / 手动启动),**启动前必须检查 contract-tracker.json**: **查错后否认(2026-07-01)**:Doro问朱家角标识牌怎么重复了,回答说"你没pass"——实际查 tracker 发现 seq=229 早在6/27就pass了。**被问任何合同状态时,先查 tracker/xlsx 用工具验证再回答,不凭"印象"。** **Queue-runner + Watchdog 重复审查(2026-07-02 实证,详见 `references/queue-runner-duplicate-review-20260702.md`)**:runner等worker时异常退出→worker独立完成(含通知)→文件没移入done/→watchdog重启runner→重新审查→重复通知。**止血**:杀重复进程→清queue→移文件到done/。**Doro说收到重复通知时,按reference文件中的止血SOP执行。** ```bash # 在 uwf thread start 之前(精确版,匹配 original_filename + status) if python3 -c " import json, sys t = json.load(open('$HOME/.hermes/data/contract-tracker.json')) completed = [c['original_filename'] for c in t['contracts'] if c['status']=='completed'] sys.exit(0 if '${FILENAME}' in completed else 1) " 2>/dev/null; then echo "SKIP: already completed in tracker" # 移入 done/ 防止 watchdog 下次重启时再次尝试 mv "$QUEUE_DIR/$FILENAME" "$QUEUE_DIR/done/" 2>/dev/null exit 0 fi ``` **手动启动时的检查**:用 `python3 -c "import json; ..."` 精确检查 original_filename + status=completed 组合。 **queue-runner 止血方法**(重复通知正在发生时): 1. `kill` runner 进程和 background-worker 进程 2. 将已完成的文件全部移入 `done/`:确保 `done/` 数量 ≥ manifest 行数 3. 验证后 watchdog 自然停止重启(进度检查通过) ## 铁律 - **待审查目录原文件:Doro说pass之前严禁删除(2026-07-01 Doro纠正)**:无论审查了多少轮、出了多少个修订版本,原文件必须留在待审查目录,直到Doro明确说pass。违反此规则等于丢失原始文件。2026-07-01教训:重新审查反委托代发工资和生育友好合同时,把原文件从待审查删了(以为已经审查完),Doro发现后要求恢复。恢复方法:从cache/documents/复制原文件回到待审查目录。 - **Nextcloud 待审查/和任务交付/目录文件:只有 cleanup cron 有权删除(2026-07-01确立)**:手动操作(包括小Maggie本人、subagent/delegate_task)不得使用 docker exec rm 删除这两个目录的文件。唯一例外:Doro 明确指令删除特定文件(如"删掉这个招标需求")。2026-07-01教训:今天pass的4份合同(徐泾北大居、印刷品、健康积分、银发健康包)原始文件和交付文件在pass后不到24小时即被删除,tracker显示cleaned=False,cleanup cron全天silent——是手动操作误删。根因无法追溯。 - **手动操作时 workflow 规则同样适用(2026-07-01确立)**:手动执行合同审查相关操作(修订、交付、清理)时,必须遵守 workflow YAML 中各角色的职责边界和规则约束。不能因为"我知道怎么做"就跳过规则。 - **不主动生成审查意见文档(2026-07-01 Doro纠正)**:除非Doro明确要求,否则pass流程只处理【修】修订版,不主动生成【审】审查意见文档。审查意见是额外交付物。 - **方案不等于授权执行** - 新方案提出后,Doro会问"会有什么影响吗"——必须主动分析潜在影响(token消耗、误判风险、时区问题、单点故障、竞态条件等),不能只说好处。被要求"再想一想不要有漏洞"时,逐一列出漏洞清单+解法,不能遗漏。复杂度过高时Doro会直接砍掉——接受并简化。