diff --git a/resources/skills/deai-writing/SKILL.md b/resources/skills/deai-writing/SKILL.md index a02fc794..d3d0bb0c 100644 --- a/resources/skills/deai-writing/SKILL.md +++ b/resources/skills/deai-writing/SKILL.md @@ -1,10 +1,10 @@ --- name: deai-writing -version: 1.1.0 +version: 1.3.0 description: | - 中文正式文档「去 AI 味」审校。用于任何需要产出不露 AI 痕迹的正式中文文本: + 中文正式文档「去 AI 味」审校。用于降低模板化、空泛和过度工整的机器生成痕迹: 投标方案、技术方案、公司官网文案、研究文章、汇报材料、说明文档、商务邮件。 - 在生成或润色中文正式文档之后调用,也可在评审阶段单独调用做质量门禁。 + 在生成或润色中文正式文档之后调用,也可在评审阶段单独调用做质量检查。 提供可执行的病症词典扫描脚本,把「凭感觉找 AI 味」变成「按清单定位并改写」。 触发词:去 AI 味、AI 腔、AI 味、文案审校、润色中文文档、官网文案评审。 allowed-tools: @@ -17,13 +17,14 @@ compatibility: Python 3.9+,不依赖第三方 Python 包 # 中文正式文档「去 AI 味」审校 -AI 味不是玄学,而是一批可枚举、可正则命中、可批量修改的固定套路。所以这件事 -能做成脚本 + 清单反复调用,不用每次靠人肉感觉。 +所谓 AI 味,通常是模板化连接词、空心形容词、强迫式排比、元话语和低信息密度 +等可观察特征。扫描器负责找候选,Agent 结合语境改写,人工负责事实与语气验收。 +检测结果不是作者身份鉴定,也不能证明一段文字由 AI 生成。 ## 什么时候用 - 刚用大模型生成或润色完一份中文正式文档,交付前。 -- 长文方案编制流程里,接在关键词核验之后、人工通读之前,作为固定质量门禁 +- 长文方案编制流程里,接在关键词核验之后、人工通读之前,作为固定检查步骤 (见 `longdoc-docx` 技能)。 - 官网/产品文案评审,被人挑出「像 AI 写的」但说不清哪里像。 @@ -46,7 +47,8 @@ AI 味不是玄学,而是一批可枚举、可正则命中、可批量修改 输出分两级: -- **阻断项**:命中即应改写,目标压到接近 0。 +- **阻断项**:高置信度套路表达,应逐条核对并改写,目标压到接近 0;法律原文、 + 客户原话、引用内容和固定术语可以保留,但要记录理由。 - **复核项**:只是候选,结合页面类型、事实边界和专业语境逐条判断,**不追求 机械清零**。研究文章里的「闭环」如果确有定义就该留着。 @@ -56,7 +58,9 @@ AI 味不是玄学,而是一批可枚举、可正则命中、可批量修改 2. 扫描源文件,将 JSON 命中清单与原文一起交给 Agent 定向改写。 3. 逐条核对改写没有编造数字、删除限制条件或改变责任主体。 4. 复扫,直到阻断项收敛;逐条处理复核项,不机械清零。 -5. 通读全文,检查关键词扫描无法发现的前后矛盾和主体错位。 +5. 做一次结构复核:检查段落是否等长、标题是否同构、列表是否总凑三点、句子 + 是否反复使用同一种节奏。 +6. 通读全文,检查关键词扫描无法发现的前后矛盾、主体错位和语气漂移。 扫描器只负责定位,不提供自动替换。语义改写必须由 Agent 结合上下文完成, 避免把专业术语、法定提示和事实边界误删。 @@ -140,8 +144,9 @@ AI 味不是玄学,而是一批可枚举、可正则命中、可批量修改 按事实保留。** ### 11. 对照句式、问答式标题与人为凑数 -句式:`不是 A,而是 B`、`并非 A,而是 B`、`A,而不是 B`、`不先谈 A,先看 B`。 -英文的 `not A but B`、`rather than`、`instead of` 同属一类。 +句式:`不是 A,而是 B`、`并非 A,而是 B`、`不只是 A,还要 B`、 +`A,而不是 B`、`不先谈 A,先看 B`。英文的 `not A but B`、`rather than`、 +`instead of` 同属一类。 标题:`结果回答了三个具体问题`、`以下四点值得关注`、`三个发现`、 `我们需要回答什么`。这类标题只描述文章结构,没有说明本节内容。 @@ -190,9 +195,89 @@ CTA 应指向项目咨询、合作沟通或联系团队,避免 `按清单准 等内部审查语言,改写为客户可理解的适用范围、接入条件和分阶段交付边界;规划 能力仍须用将来时或设计阶段表述。 +### 14. 万能分析框架与标签化收束 +`从本质上看`、`从某种意义上说`、`从多个维度`、`核心在于`、`关键在于`、 +`归根结底`、`可以看出`、`由此可见`、`具体而言`、`从……角度来看`。 + +这些表达不一定错误,但连续出现时常在替代真实推理。删掉框架词后检查句子: +如果仍没有对象、动作、条件或证据,就补充具体信息;如果信息已经完整,直接 +陈述即可。 + +### 15. 假引用、假共识与来源模糊 +`业内普遍认为`、`研究表明`、`数据显示`、`专家指出`、`实践证明`、 +`有观点认为`,但没有来源、样本、日期或适用范围。 + +改法:能追溯就补来源和口径;不能追溯就改成本文可承担的判断,或删除。不要用 +模糊权威给观点加重量,也不要为了显得具体而编造论文、机构、链接和数字。 + +### 16. 句式和段落节奏过度整齐 +命中信号:连续段落以同一短语开头;每段都是「结论 + 三个分点 + 小结」; +多个标题使用同一种问句或冒号结构;句长和段长近乎一致。 + +改法:按信息关系组织,不按模板配平。合并重复段,拆开超载句;事实、解释、 +限制和动作可以使用不同长度,不必每节都做总结。扫描器只能发现少量局部信号, +这项必须人工复核。 + +### 17. 角色槽位式三段法 +`系统做什么、学生做什么、教师看到什么`、`用户需要什么、平台提供什么、 +最终获得什么`、`输入是什么、处理什么、输出什么`。这类句子先盘点角色或槽位, +再把内容填进去,形式完整但没有描述真实流程。 + +角色主语本身没有问题,问题在于反复使用 `谁做什么`、`谁看到什么` 等元问题, +或把每个角色机械配成长度相近的一段。改写时按事件顺序写清触发条件、系统动作 +和可见结果,不必给每个角色单独设栏。 + +- 「系统做什么、学生做什么、教师看到什么」 + → 「学生提交答案时,系统保存答题步骤和引用依据;教师可在评分页展开查看」 +- 「教师看到的不只是最终分数」 + → 「评分页同时展示最终得分、答题步骤和引用依据」 + +需求矩阵、职责表和泳道图本来就按角色组织,可以保留;叙事正文和宣传标题应 +优先描述具体流程或结果。 + +### 18. 闸门、门禁、硬约束等工程黑话 +`闸门`、`门禁`、`硬约束`、`卡点`、`护栏` 在工程语境中可能是准确术语,也可能 +只是给普通规则套上的比喻。单独出现只作复核,不机械删除。 + +检查它是否说明了四件事:对象、条件、执行动作和结果。例如: + +- 「设置质量门禁」→「测试失败时禁止合并」 +- 「形成发布闸门」→「延迟超过 200 ms 时停止发布」 +- 「建立安全门禁」→「未通过权限校验的请求直接拒绝」 +- 「施加硬约束」→「显存占用不得超过 80 GB」 + +`CI 门禁`、优化问题中的 `硬约束` 等团队已有明确定义的术语可以保留。若删掉 +黑话后仍看不出谁在什么条件下阻止什么,就应改成可执行规则。 + +### 19. 拟人化数量词与连续工程隐喻 +`每一次思考都有证据`、`一颗权重`、`数十亿颗权重`、`数据喂不够快`、 +`存储到计算的路越来越繁忙`。拟人化或比喻偶尔能辅助解释,但多个比喻连续出现, +会让技术说明像自动生成的科普文案,并掩盖可验证的技术关系。 + +改写时回到实际对象、单位和瓶颈: + +- 「每一次思考都有证据」→「系统保存答题步骤及其引用依据」 +- 「数十亿颗权重都要被反复读取」→「每生成一个 Token,都需要访问模型各层的 + 大量权重」 +- 「从算得不够快变成数据喂不够快」→「Decode 阶段通常受显存带宽而非计算吞吐 + 限制」 +- 「这条路越繁忙」→「权重读取总量上升,显存带宽压力随之增加」 + +不要为了去掉比喻而引入未经测量的结论。`通常受显存带宽限制` 仍需结合模型、 +批量大小、硬件和推理实现确认。 + +### 20. 绝对化承诺与全称判断 +`每一次……都……`、`所有……均……`、`任何情况下`、`始终确保`、`完全避免`。 +这类表达容易把局部能力写成无条件保证,也常与口号、拟人化表达组合出现。 + +先核对产品机制和证据范围。能够保证时写清条件,例如「开启过程记录后,评分页 +保存每次提交的步骤与引用」;不能保证时改成实际覆盖范围。法规、数学命题和测试 +验收标准中的全称表达按原意保留。 + ## 改写纪律 -去 AI 味不是把文本改得干瘪,而是去套路、留信息。四条底线: +去 AI 味不是把文本改得干瘪,更不是伪装作者身份,而是去套路、留信息。六条 +底线: 1. **只删套路,不删事实。** 形容词换成数字/动作是「换」不是「删信息」; 连接词、铺垫、升华才是直接删。 @@ -203,21 +288,32 @@ CTA 应指向项目咨询、合作沟通或联系团队,避免 `按清单准 规划能力写成已经实现。 4. **改完复读一遍出声。** AI 味的本质是结构过于工整、信息密度偏低,出声读 最容易发现。 +5. **不编造“人味”。** 不故意加入错别字、口头禅、虚构经历、情绪或未经证实的 + 个人判断。自然表达来自具体信息和真实语境,不来自人为瑕疵。 +6. **尊重引用与固定文本。** 法规、合同条款、客户原话、品牌标语和准确引文不 + 因命中词典而改写;必要时从扫描范围排除,或在评审记录中说明保留原因。 ## 一分钟自查清单(不跑脚本时) - 开头有没有「随着/在……的今天」?删。 - 有没有「不仅……而且/综上所述/值得注意的是」?删。 - 有没有「不是 A,而是 B」或「回答三个问题」式标题?直接写 B 或具体结果。 +- 有没有「不只是 A,还要 B」?删掉对照架子,直接并列实际信息。 - 公司首页是否写成了实施步骤?改成服务领域、产品能力和公司定位。 - CTA 是否像需求填报表?改成项目咨询或合作沟通。 - 官网是否出现「当前基线」「已知缺口」等内部审校语言?改成适用范围和交付条件。 - 标题是否以 `把/让/先/再` 或 `Bring/Start/Let/First` 发出操作口令?改名词性。 - 产品首屏是否塞入接口字段、回退链、调试过程?下沉到技术说明。 +- 有没有「研究表明/数据显示/业内普遍认为」却不给来源?补证据或删除归因。 +- 是否反复用「从本质上看/核心在于/具体而言」代替实际分析?直接写对象和结论。 +- 是否在盘点「系统/用户/教师做什么」?按真实事件顺序描述触发、动作和结果。 +- `闸门/门禁/硬约束/护栏` 是否说明了执行条件和后果?否则改成具体规则。 +- 是否使用「一颗权重/数据喂不够快/道路繁忙」等连续比喻?改回技术对象和指标。 +- `每一次/所有/始终/完全` 是否超出了证据范围?补条件或缩小范围。 - 限制是否在同一页面重复出现?合并为一处正向范围说明,同时保留必要的研究、 安全和人工复核边界。 - 形容词能不能换成数字或具体动作?能就换。 -- 是不是每段都凑三点、每句都加粗?打破它。 +- 是不是每段都凑三点、每句都加粗、相邻段落等长同构?按信息关系重组。 - 有没有破折号、Emoji、客服式结尾?清掉。 - 出声读一遍:像人说话,还是像念 PPT? @@ -267,6 +363,8 @@ deai-writing/ ## 已知边界 - 结构性问题(排比、三段式、每段凑三点)正则只能给候选,最终要人读。 +- 角色槽位、工程黑话、拟人化比喻和绝对化表达都有合理语境,只能作为复核线索。 - 「不仅……而且」等词单独出现误报率高,词典里已收敛成句式匹配;仍会有误报, 阻断项要逐条看过再改,不能盲目全局替换。 - 扫描器不判断事实正确性。改写时新引入的数字必须有证据支撑。 +- 扫描结果只描述文本特征,不判断作者身份,也不应用于学术诚信或人员考核结论。 diff --git a/resources/skills/deai-writing/ai_smell_dict.py b/resources/skills/deai-writing/ai_smell_dict.py index f2c2f3fb..06598a0a 100644 --- a/resources/skills/deai-writing/ai_smell_dict.py +++ b/resources/skills/deai-writing/ai_smell_dict.py @@ -25,6 +25,8 @@ AI_SMELL = { ], "对照模板": [ r"不是.{0,40}而是", r"并非.{0,40}而是", "而不是", + r"不只是.{0,40}(而是|还|也|更)", r"不止是.{0,40}(而是|还|也|更)", + r"不只是[^,。;!?\n]{1,30}(?:[。;!?\n]|$)", r"不先.{0,30}先", r"下一步是.{0,40}而不是", ], "问答式标题": [ @@ -67,6 +69,13 @@ AI_SMELL = { "过度对冲": [ "在某种程度上", "总体而言", "一般来说", ], + "万能收束": [ + "从本质上看", "从某种意义上说", "归根结底", "可以看出", + "由此可见", + ], + "来源模糊": [ + "业内普遍认为", "专家指出", "有观点认为", + ], "格式痕迹": [ "——", "✅", "❌", "🚀", "💡", "🎯", "✨", "🔥", "📌", "⚡", ], @@ -93,6 +102,38 @@ REVIEW_ONLY = { "否定句堆叠": [ "不代表", "不包含", "尚未", "不能", ], + "分析框架": [ + "从多个维度", "核心在于", "关键在于", "具体而言", + r"从.{0,20}角度来看", + ], + "无来源归因": [ + "研究表明", "数据显示", "实践证明", + ], + "角色槽位": [ + r"(系统|平台).{0,20}(做什么|负责什么).{0,40}" + r"(学生|教师|用户|客户).{0,20}(做什么|看到什么|获得什么)", + r"(学生|教师|用户|客户).{0,20}(做什么|负责什么).{0,40}" + r"(系统|平台|教师|用户|客户).{0,20}(做什么|看到什么|获得什么)", + r"输入是什么.{0,30}(处理什么|怎么处理).{0,30}输出什么", + r"用户需要什么.{0,30}平台提供什么.{0,30}(最终)?获得什么", + ], + "工程黑话": [ + "闸门", "门禁", "硬约束", "卡点", "护栏", + ], + "拟人化技术表达": [ + r"[一两二三四五六七八九十百千万亿0-9]+颗(权重|参数)", + "数据喂不够快", r"(存储|内存|显存).{0,20}(道路|路).{0,20}(繁忙|拥堵)", + r"(模型|系统).{0,10}(思考|理解|读懂|看懂)", + ], + "绝对化承诺": [ + r"每一(次|个|项|条).{0,30}(都|均)", r"所有.{0,30}均", + "任何情况下", "始终确保", "完全避免", + ], + "标签化三联": [ + r"(看得见|可见).{0,20}(查得到|可查).{0,20}(可追溯|能追溯)", + r"事前.{0,20}事中.{0,20}事后", + r"业务侧.{0,20}技术侧.{0,20}管理侧", + ], } # --------------------------------------------------------------------------- diff --git a/resources/skills/deai-writing/deai_scan.py b/resources/skills/deai-writing/deai_scan.py index 4fa78bce..217159fc 100644 --- a/resources/skills/deai-writing/deai_scan.py +++ b/resources/skills/deai-writing/deai_scan.py @@ -219,6 +219,19 @@ def collect_files(targets, exts): return list(dict.fromkeys(out)) +def summarize_findings(findings): + """Count findings by level and category for CI and agent consumers.""" + counts = {LEVEL_BLOCK: {}, LEVEL_REVIEW: {}} + for finding in findings: + level_counts = counts[finding["level"]] + category = finding["category"] + level_counts[category] = level_counts.get(category, 0) + 1 + return { + level: dict(sorted(category_counts.items())) + for level, category_counts in counts.items() + } + + def main(): ap = argparse.ArgumentParser(description="中文正式文档 AI 味扫描") ap.add_argument("targets", nargs="+", help="待扫描的文件或目录") @@ -271,6 +284,7 @@ def main(): n_block = sum(1 for f in all_findings if f["level"] == LEVEL_BLOCK) n_review = len(all_findings) - n_block + by_category = summarize_findings(all_findings) if args.json: print(json.dumps({ @@ -278,6 +292,7 @@ def main(): "requested_files": len(files), "block": n_block, "review": n_review, + "by_category": by_category, "errors": errors, "findings": all_findings, }, ensure_ascii=False, indent=2)) diff --git a/resources/skills/deai-writing/tests/test_deai_scan.py b/resources/skills/deai-writing/tests/test_deai_scan.py index 34faacea..d01a8d54 100644 --- a/resources/skills/deai-writing/tests/test_deai_scan.py +++ b/resources/skills/deai-writing/tests/test_deai_scan.py @@ -31,6 +31,55 @@ class DeaiScanTests(unittest.TestCase): levels = {finding["level"] for finding in findings} self.assertEqual(levels, {deai_scan.LEVEL_BLOCK, deai_scan.LEVEL_REVIEW}) + def test_reports_new_reasoning_and_attribution_patterns(self): + findings = self.scan( + "从本质上看,业内普遍认为该方案有效。\n" + "研究表明,核心在于形成闭环。" + ) + categories = {finding["category"] for finding in findings} + self.assertTrue({"万能收束", "来源模糊"}.issubset(categories)) + self.assertTrue({"无来源归因", "分析框架"}.issubset(categories)) + + def test_summarizes_findings_by_level_and_category(self): + findings = self.scan("综上所述,归根结底,需要复核当前基线。") + summary = deai_scan.summarize_findings(findings) + self.assertEqual(summary[deai_scan.LEVEL_BLOCK]["套路连接词"], 1) + self.assertEqual(summary[deai_scan.LEVEL_BLOCK]["万能收束"], 1) + self.assertEqual(summary[deai_scan.LEVEL_REVIEW]["内部审校话语"], 1) + + def test_reports_user_identified_templates_and_metaphors(self): + findings = self.scan( + "每一次思考都有证据,教师看到的不只是最终分数。\n" + "系统做什么、学生做什么、教师看到什么。\n" + "设置质量门禁和硬约束,让数据不再喂不够快。\n" + "Decode 时需要读取数十亿颗权重。" + ) + categories = {finding["category"] for finding in findings} + self.assertIn("对照模板", categories) + self.assertTrue( + { + "角色槽位", + "工程黑话", + "拟人化技术表达", + "绝对化承诺", + }.issubset(categories) + ) + + def test_keeps_concrete_engineering_rules_clean(self): + findings = self.scan( + "测试失败时禁止合并。延迟超过 200 ms 时停止发布。" + "显存占用不得超过 80 GB。" + ) + watched = { + "角色槽位", + "工程黑话", + "拟人化技术表达", + "绝对化承诺", + } + self.assertFalse( + any(finding["category"] in watched for finding in findings) + ) + def test_masks_markdown_code(self): findings = self.scan("正文没有问题。\n```text\n综上所述,打造闭环。\n```\n") self.assertEqual(findings, []) @@ -80,6 +129,8 @@ class DeaiScanTests(unittest.TestCase): result = subprocess.run(command, check=True, capture_output=True, text=True) report = json.loads(result.stdout) self.assertGreater(report["block"], 0) + self.assertIn("阻断", report["by_category"]) + self.assertIn("复核", report["by_category"]) failed = subprocess.run( command + ["--fail-on-block"],