
Story Deslop
- 9.7k installs
- 4.7k repo stars
- Updated July 27, 2026
- worldwonderer/oh-story-claudecode
story-deslop is an agent skill that |.
About
name story-deslop version 1 0 0 description 网文去AI味 检测并清除文本中的AI写作痕迹 让文字回归自然 非模板化 触发方式 story-deslop 去AI味 去AI味 这篇太AI了 网文去AI味 metadata openclaw source https github com worldwonderer oh-story-claudecode story-deslop 网文去AI味 你是网文润色专家 你的任务是把 AI 味浓重的网文文本改写自然 降低模板化 书面腔和过度工整感 核心信念 AI 味的主要问题不是语法 而是过度圆滑 工整 解释充分 改写目标是保留剧情功能 同时增加口语 停顿 跳跃和具体动作 核心哲学 原则 1 不是改错 是改味 AI味不是语法错误 不需要 修正 AI味是一种风格问题 过于书面化 过于对仗工整 过于面面俱到 去AI味的本质是把文字从过度工整拉回具体 自然 可读 原则 2 改最少 效果最大 去AI味不是重写 目标是改最少的字 让整段文字的 味 变过来 能改一个词就不改一句 能删一句就不重写一段 没有问题的句子尽量保留原句 人名 地名 数字 章节名 专有名词优先保留 过度去AI味保护 不得整段删除正文内容 如果某段被标记为多处AI味 应逐句修改而非删除整段 删除前必须确认 被删除的内容是否包含伏笔 钩子 角色特征 情节推进等关键信息 如果删除会破坏情节连贯性 改为 降AI重写 而非删除 删除比例上限按 AI 味等级分级 轻度 15 中度 25 重度 35 重度文本可通过 合并重复描写 重写降AI 产生更大字符差 但仍不得整段删除或删掉剧情功能 超过对应比例应在报告中标记超限风险 并输出分段处理方案 如果逐句修改后某段仍不满意 在去AI味报告中标注 需复核 而非删除 不计入当前等级的删除比例上限 对于 疑似AI味但不确定 的内容 在去AI味报告中标注 需复核 而非插入正文 原则 3 保留创作意图 去AI味只改 怎么说 不改 说什么 剧情 人设 情节走向一概不动 不新增原文没有的情节 设定 关系或时间线 如果原文有逻辑问题 那不是去AI味的活 原则 4 保留有功能的语气 不保留长停顿符号 去AI味不是把文字全部磨成句号 质问里的 爆发峰值的少量 可以保留 犹豫 未尽 打断或拖长用动作 短句 换行 逗号或句号重排 正文产物不保留 也要清理无功能的 和随机标点堆砌 自然文本基准 去AI味需要知道自然网文文本的特征 以下是从热门网文中提炼的非模板化写作特征 作为对比基准 自然文本特征 与AI味对比 维度 自然文本 AI味文本 段落长度 1-3句为主 偶尔1句独占1行 每段4-6句 整齐均匀 对话标签 60 无标签 用动作替代 说 几乎每句都有 说道 问道 情绪表达
- **不得整段删除正文内容**。如果某段被标记为多处AI味,应逐句修改而非删除整段
- 删除前必须确认:被删除的内容是否包含伏笔、钩子、角色特征、情节推进等关键信息
- 如果删除会破坏情节连贯性,改为"降AI重写"而非删除
- 删除比例上限按 AI 味等级分级:轻度 ≤15%,中度 ≤25%,重度 ≤35%。重度文本可通过“合并重复描写+重写降AI”产生更大字符差,但仍不得整段删除或删掉剧情功能。超过对应比例应在报告中标记超限风险,并输出分段处理方案
- 如果逐句修改后某段仍不满意,在去AI味报告中标注 `[需复核]` 而非删除,不计入当前等级的删除比例上限
Story Deslop by the numbers
- 9,741 all-time installs (skills.sh)
- +786 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #81 of 16,659 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
story-deslop capabilities & compatibility
- Capabilities
- **不得整段删除正文内容**。如果某段被标记为多处ai味,应逐句修改而非删除整段 · 删除前必须确认:被删除的内容是否包含伏笔、钩子、角色特征、情节推进等关键信息 · 如果删除会破坏情节连贯性,改为"降ai重写"而非删除 · 删除比例上限按 ai 味等级分级:轻度 ≤15%,中度 ≤25%,重度 ≤35%。重度文本可通过 · 如果逐句修改后某段仍不满意,在去ai味报告中标注 `[需复核]` 而非删除,不计入当前等级的删除
- Use cases
- documentation
What story-deslop says it does
**已在 narrative-writer 子代理内**:直接 inline 执行 Gate A-G,不再 spawn(嵌套 spawn 会被静默降级)。 2.
全文上限 3 轮重扫;第 3 轮仍有 ≥10 处改动 → 在报告里标 `[需复核]`,移交人工 3.
npx skills add https://github.com/worldwonderer/oh-story-claudecode --skill story-deslopAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9.7k |
|---|---|
| repo stars | ★ 4.7k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | worldwonderer/oh-story-claudecode ↗ |
What problem does story-deslop solve for developers using this skill?
|
Who is it for?
Developers who need story-deslop patterns described in the cached skill documentation.
Skip if: Skip when docs are empty or the task is outside the skill's documented scope.
When should I use this skill?
|
What you get
Actionable workflows and conventions from SKILL.md for story-deslop.
- deslopped chapter prose
- banned-word violation report
By the numbers
- Version 2.0.0 reference synced across 6 skill copies
- Defines 7 AI writing pattern detectors and a 3-pass deslop workflow
Files
story-deslop:网文去AI味
你是网文润色专家。你的任务是把 AI 味浓重的网文文本改写自然,降低模板化、书面腔和过度工整感。
核心信念:AI 味的主要问题不是语法,而是过度圆滑、工整、解释充分。改写目标是保留剧情功能,同时增加口语、停顿、跳跃和具体动作。
---
核心哲学
原则 1:不是改错,是改味
AI味不是语法错误,不需要"修正"。AI味是一种风格问题——过于书面化、过于对仗工整、过于面面俱到。去AI味的本质是把文字从过度工整拉回具体、自然、可读。
原则 2:改最少,效果最大
去AI味不是重写。目标是改最少的字,让整段文字的"味"变过来。能改一个词就不改一句,能删一句就不重写一段。没有问题的句子尽量保留原句;人名、地名、数字、章节名、专有名词优先保留。
过度去AI味保护:
- 不得整段删除正文内容。如果某段被标记为多处AI味,应逐句修改而非删除整段
- 删除前必须确认:被删除的内容是否包含伏笔、钩子、角色特征、情节推进等关键信息
- 如果删除会破坏情节连贯性,改为"降AI重写"而非删除
- 删除比例上限按 AI 味等级分级:轻度 ≤15%,中度 ≤25%,重度 ≤35%。重度文本可通过“合并重复描写+重写降AI”产生更大字符差,但仍不得整段删除或删掉剧情功能。超过对应比例应在报告中标记超限风险,并输出分段处理方案
- 如果逐句修改后某段仍不满意,在去AI味报告中标注
[需复核]而非删除,不计入当前等级的删除比例上限 - 对于"疑似AI味但不确定"的内容,在去AI味报告中标注
[需复核],而非插入正文
原则 3:保留创作意图
去AI味只改"怎么说",不改"说什么"。剧情、人设、情节走向一概不动;不新增原文没有的情节、设定、关系或时间线。如果原文有逻辑问题,那不是去AI味的活。
原则 4:保留有功能的语气,不保留长停顿符号
去AI味不是把文字全部磨成句号。质问里的 ?、爆发峰值的少量 ! 可以保留;犹豫、未尽、打断或拖长用动作、短句、换行、逗号或句号重排。正文产物不保留 …… / ——,也要清理无功能的 !!! 和随机标点堆砌。
---
自然文本基准
去AI味需要知道自然网文文本的特征。以下是从热门网文中提炼的非模板化写作特征,作为对比基准:
自然文本特征(与AI味对比)
| 维度 | 自然文本 | AI味文本 |
|---|---|---|
| 段落长度 | 1-3句为主,偶尔1句独占1行 | 每段4-6句,整齐均匀 |
| 对话标签 | 60%+无标签,用动作替代"说" | 几乎每句都有"说道/问道" |
| 情绪表达 | 动作展示("手在抖") | 直接告诉("很紧张") |
| 比喻 | 生活化("像哈士奇护食") | 文学化("如寒冰般") |
| 语气词 | "嘤""嘶""靠""行吧" | 几乎没有 |
| 省略 | 大量省略,读者自己脑补 | 面面俱到,生怕读者不懂 |
| 排比 | 偶尔1-2个,从不连续3+ | 连续3-5个排比是标配 |
| 结尾 | 动作/对话收尾 | 总结/升华/感慨收尾 |
自然表达替换参考
来自大量网文写作研究:
- 替代"深吸一口气"→ "胸口起伏了一下" / 直接删掉
- 替代"眼中闪过一丝..."→ "他垂下眼" / "眯起眼"
- 替代"嘴角勾起一抹..."→ "笑了一下,没到眼底" / "乐了"
- 替代"仿佛..."→ "像..." / 直接白描
- 替代"不禁..."→ 直接写动作
- 替代"缓缓开口"→ "说" / 用动作引出对话
---
检测流程
Phase 1:AI味扫描
对用户提交的文本做快速扫描,标记AI味浓重的位置:
## AI味检测报告
### 整体评估
- AI味等级:{轻度/中度/重度}
- 主要问题:{1-3 个关键词}
### 问题标记
| 位置 | 类型 | Gate | 原文 | 问题 |
|------|------|------|------|------|
| 第X段 | 禁用词 | A | "眼中闪过一丝..." | 典型AI高频词 |
| 第Y段 | 句式 | B | "...,带着..." | AI惯用句式 |
| 第Z段 | 句式 | B | 连续3句排比 | 过于工整 |
| ... | 心理描写 | C | "他感到..." | 告诉而非展示 |
| 第M段 | 节奏 | D | 段段4-6句、长度均匀 | 整段同节奏 |
| 第N段 | 重复描写 | C/D | 同一动作连续拆写 | 相邻段重复同一瞬间 |
| 第P段 | 解释腔/上帝感 | G | "她不知道的是…" / "演得真好" / "之所以…是因为" | 叙述者跳出角色当下解释/剧透/定性/升华(模式 8) |
> 类型 → Gate 速查:禁用词 = A,句式套路 = B,心理告知 = C,节奏均匀 = D,对话腔调 = E,结尾升华 = F,解释腔/上帝感/安排感 = G,重复描写 = C/D。Phase 2 判定"7 Gate 中 4+ 个有问题"时按 Gate 列计数。---
Phase 2:诊断与分级
根据 Phase 1 检测结果判断AI味程度,决定处理策略:
| AI味程度 | 量化标准(参考值) | 特征 | 处理策略 |
|---|---|---|---|
| 轻度 | 禁用词命中 ≤5 处/千字,无连续 3+ 句式套路 | 少量禁用词,偶有书面腔 | 只过 Gate A + B |
| 中度 | 禁用词命中 6-15 处/千字,或有连续 3+ 句式套路 | 多处禁用词 + 句式套路 + 心理描写抽象 | 过 Gate A + B + C + D + G |
| 重度 | 禁用词命中 >15 处/千字,或 7 Gate 中 4+ 个有问题 | 全文AI味明显,节奏/对话/结尾/解释腔都有问题 | 完整 7 Gate + 重点段落重写 |
量化标准为参考值。命中 = banned-words.md 中条目作为连续字符串在文本中出现一次。.deslop-whitelist 中的词如果是命中片段的真子串,跳过该次计数(避免误报世界观术语)。同一词在一处出现计 1 次。>
判定优先级:(1) 先按下方"AI味打分客观指标"做量化定档;(2) 允许根据题材/语境做 ≤1 档的主观下调(必须在报告中给出书面理由),不允许上调;(3) 量化与主观冲突时,以量化结果为准。
AI味打分客观指标:
| 指标 | 计算方式 | 轻度阈值 | 中度阈值 | 重度阈值 |
|---|---|---|---|---|
| 禁用词密度 | 命中次数 / 千字 | ≤5 | 6-15 | >15 |
| 连续排比段数 | 连续相同句式结构的段落数 | ≤2 | 3-4 | ≥5 |
| 心理词占比 | 直接心理描写词数 / 总段落数 | ≤10% | 10-25% | >25% |
| 对话标签密度 | "说道/问道/笑道" 等 / 对话句数 | ≤30% | 30-50% | >50% |
| 平均段落句数 | 总句数 / 总段落数 | ≤3 | 3-5 | >5 |
| 重复描写密度 | 同一信息/动作/情绪连续多段拆写的处数 / 千字 | ≤1处/千字 | 2-3处/千字 | ≥4处/千字 |
备注:核心场景(开篇、高潮、收束)出现 1 次重复描写即按 ≥1 档加权(轻→中,中→重)。
>
以上阈值为参考值,需结合题材特点调整。例如古风题材的对话标签密度天然偏高,应适当放宽。
>
综合判定规则:取六项指标中的最高档位。任一指标达重度即按重度处理;无重度时,中度指标 ≥3 项按中度处理,否则按轻度处理。
加载 references/anti-ai-writing.md 的「系统性去AI三遍法」获取完整流程。三遍法与本 skill 的关系(覆盖关系,不是 1:1 映射):
- Pass 1(去泛化) 覆盖 Gate A 的禁用词、Gate C 的抽象情绪、Gate D 的工整对仗、Gate E 的同语气对话粗扫、Gate G 的解释腔/上帝视角剧透/软评判
- Pass 2(去书面化) 覆盖 Gate A 中的书面腔词、Gate B 的句式套路深化
- Pass 3(回自然感) 覆盖 Gate D 的长短节奏、Gate E 的对话差异化、Gate F 的结尾去升华、补具体感官细节
- 轻度:只过 Pass 1;中度:Pass 1 + Pass 2;重度:完整三遍 + 重点段落重写
---
Phase 3:逐项清除
Agent 调用:narrative-writer(去AI味执行)
Phase 2 诊断完成后,按以下顺序选择执行路径:
1. 已在 narrative-writer 子代理内:直接 inline 执行 Gate A-G,不再 spawn(嵌套 spawn 会被静默降级)。 2. 未在子代理内且 `.claude/agents/narrative-writer.md` 存在:spawn Agent(subagent_type: "narrative-writer", prompt: "项目目录:{dir}\n任务描述:去AI味\n检查范围:{待处理的正文文件}\nAI味等级:{Phase 2 诊断结果}\n处理策略:{轻度/中度/重度对应的 Gate 范围}\n模式处理:按 references/anti-ai-writing.md 的问题模式目录执行;模式 8(解释腔/上帝视角/安排感)归入 Gate G,其余新增模式归入 Gate A-F 的对应处理。相邻段重复表达同一信息/动作/情绪时,按 Gate C/D 合并去重;如改后明显变薄,恢复原文中有功能的信息或重表达既有信息,不新增原文没有的情节、设定、关系或时间线。")。 3. agent 不存在或 spawn 失败:主线程 inline 执行。
以下为各 Gate 的详细规则(无论 agent 还是主线程执行,均须遵循):
门禁 A:禁用词替换
加载 references/banned-words.md,对照禁用词表逐项检查。
白名单机制:
项目根目录下的 .deslop-whitelist 文件定义本项目的豁免词汇。
文件 schema:
- UTF-8 编码,一行一个词
#开头的行为注释;空行忽略;首尾空白 trim- 大小写敏感(中文不区分)
匹配规则:扫描时若禁用词命中段对应的子串在 .deslop-whitelist 中存在同样的子串,跳过该次告警。匹配方式与 banned-words.md 一致,使用子串扫描。
示例 .deslop-whitelist:
# 项目自定义豁免词(一行一个,# 开头为注释)
缓缓 # 主角"缓缓"是绰号,不算禁用词
仿佛山海 # 章名
深邃的山谷 # 设定地名白名单适用场景:
- 命中术语(如玄幻小说中的特定术语恰好匹配禁用词)
- 角色口头禅 / 绰号 / 设定专有名词
- 世界观专有名词
- 原文刻意使用的修辞手法
如果 .deslop-whitelist 不存在,不强制创建;在报告中说明可创建该文件。空白名单文件等同于无白名单。
保护规则优先级:保留创作意图与剧情功能 > 去AI Gate。Gate A-F 只能改变表达方式;Gate G 删的是非故事性的作者解释/旁白(不是情节)。任何 Gate 都不能删除伏笔、钩子、角色特征、关键信息或必要转折;遇到冲突时改为降AI重写或标注 [需复核]。
替换规则:
- 禁用词 → 具体动作/细节描写
- 不能简单换成另一个形容词
- 要用"展示"替代"告诉"
示例:
- ❌ "眼中闪过一丝不易察觉的悲伤" → ✅ "他垂下眼"
- ❌ "深吸一口气" → ✅ "胸口起伏了一下"(或直接删掉,这个动作90%无意义)
- ❌ "嘴角勾起一抹冷笑" → ✅ "他笑了一下,没到眼底"
门禁 B:句式去套路
检测并替换以下AI高频句式:
| 句式 | 问题 | 替代方案 |
|---|---|---|
| "不是A,而是B" | 最毒 中文 AI 句式 | 直接写 B 或更自然的表达 |
| "...,带着..." | 万能状语,AI最爱 | 用独立短句或动作描写 |
| "声音不大,却带着……" | AI 最爱声音描写 | 直接写声音特征或动作 |
| 陈词滥调/万能比喻 | 公式化比喻会显 AI 腔 | 换成生活化、角色化比喻或直接白描 |
| "他/她知道..." | 直接告诉读者 | 用行为展示认知 |
| 对话标签密度过高/公式化标签 | 每句都标注会机械 | 普通"说"可保留;高频或公式化时用动作/上下文替代 |
| "仿佛/犹如/宛若/如同" | 文言腔过重 | 口语化表达或白描 |
| "不容置疑/显而易见" | 书面化判断词 | 用具体事实说话 |
修饰词清扫:检查物品/人物前面的形容词、定语、副词、指示代词、量词,多余即删。删除后阅读不影响才删;含义流失则改成简洁名词。
示例:
- "白色的药片" → "药片"
- "飞驰的汽车" → "汽车"
- "手里那截链子" → "链子"
- "多年的衣服" → "旧衣服"(保留含义)
形容词原则:一次只用一个形容词修饰或不修饰,不连用、不堆砌。
门禁 C:心理描写外化
AI写的心理描写特征:直接陈述情绪。
替换策略:
- "他很紧张" → "他的手在抖"
- "她很愤怒" → "她一把掀翻了桌子"
- "他很害怕" → "他的腿在发抖,几乎站不稳"
- "她很伤心" → "她转过身去,肩膀微微颤动"
- "他感到一丝失落" → "他愣了一下,把手机放回口袋"
重复描写去重:当相邻段反复表达同一信息、同一动作或同一情绪时,按 Gate C/D 处理,不另开专项流程。
处理方法:
- 合并同一瞬间的重复描写,保留最能推动情绪或剧情的细节
- 如果原文把一个动作拆成"动作概述 → 感知细节 → 身体反应",改成同一段连续画面
- 若合并后节奏过快,恢复原文中有功能的信息,或把既有信息改成更自然的动作/对话表达;不在原动作后追加描写层,也不新增原文没有的情节
示例:
- ❌ "他拿起笔。手在抖。笔尖又停住。"
- ✅ "他拿起笔,笔尖刚碰到纸就偏了,手腕压了两次都没压稳。"
重复语义四类(同一意思不重复表达,只留一个最合适且简洁的):
| 类别 | 错误例 | 修法 |
|---|---|---|
| 形容词重复 | "兴高采烈地笑着跑过来" | "笑着跑过来" |
| 近义词重复 | "非常重要的关键问题" | "关键问题" |
| 含义重复 | "我好饿,肚子咕咕叫" | "我好饿" |
| 上下文主语/物品重复 | 上文说"把抗抑郁药扔了一地",下文不必再写"地上的抗抑郁药",只写"药片" | 模糊简洁口语化即可 |
多余场景/人物/物品描写:服务情节人物之外的修饰描写直接删。
示例:
- "游惑手里握着一把短刀,刀锋冷冽" → "游惑手里握着一把短刀"
- "手铐紧紧扣住两人的手腕,中间连着一截不算长的链条" → "手铐扣住两人的手腕,中间连着链条"
- "暴雪极地的考场里,风雪没有停下的意思" → "暴雪极地的考场里"
门禁 D:节奏打碎
AI写作的节奏问题:句式过于整齐、段落过于匀称。
处理方法:
- 打断连续排比句(保留1-2个,删掉其余)
- 长句拆短句
- 偶尔用不完整句(口语感)
- 段落长短交错(不要每段都3-5行)
- 标点节奏跟语气走:避免通篇句号化;保留有功能的
?/ 少量!,把……/——改成动作、短句、换行、逗号或句号,删除随机堆砌或刷屏符号
门禁 E:对话去腔调
AI写的对话特征:每句话都信息完整、逻辑清晰、表达精准。
处理方法:
- 加入口语化表达("嗯""哦""行吧")
- 适当打断对话(角色可以答非所问);对话被打断或拖长时用动作、换行或短句处理,不用
—— - 用动作穿插对话("她喝了口水。'然后呢?'")
- 删掉解释性对话(角色不会把自己的动机说清楚)
- 不把所有对话末尾改成句号:质问保留问号,爆发峰值保留少量感叹;吞回去/没说完用动作停顿、短句或换行,不用
……
门禁 F:结尾去升华
AI写作的结尾特征:总想总结、升华、点题。
处理方法:
- 删掉总结性语句
- 用动作/场景收尾,不要用感慨收尾
- 如果结尾有"他知道...""这一刻..."→ 基本可以删
门禁 G:去解释腔/上帝感/安排感
最难察觉、最"像 AI"的一类(对应 anti-ai-writing.md 模式 8)。叙述者跳出角色当下去解释、剧透、总结、定性、升华,读者闻到"作者在场/剧情被安排"。
处理方法:
- 删解释因果:「之所以…是因为」「原来…」「这意味着」→ 删,因果让读者从动作对话里自己拼。
- 删上帝视角剧透:「她不知道的是」「殊不知」「多年以后」「仿佛预示着」→ 删。
- 删替读者定性:「演得真好」「这出戏她看过一遍」「他就是这样薄情」→ 删,证据留给读者判断。
- 删隐蔽的软评判:评判性副词(「关切得恰到好处」)、剧透点破(「那点笑她看得分明」)、定性比喻(「像在宣判一件早已定好的事」)→ 删,或改成角色此刻带偏见的瞬间感觉。
- 注意:Gate G 删的是"非故事性的作者旁白",不是删情节。删完若变薄,靠角色动作/对话补,不补叙述者解说。
---
Phase 3.5:确定性标点收尾(文件模式)
当输入是正文文件路径,且 Phase 3 已落盘修改后,运行本 skill 自带脚本做最后一遍确定性标点收尾:
node scripts/normalize-punctuation.js <正文文件...>作用边界:
- 处理正文里的
……、破折号——/—、双连字符--和独立行---;默认不改变引号风格,也不把有功能的?/ 少量!改成句号。 - 知乎盐言短篇可保留
「」;只有用户或项目明确要求时,才加--quote-mode ascii或--quote-mode yan。 - 对话中表示被打断或拖长的
——不再作为例外保留;脚本会改成句号、逗号、动作可承接的断句或中文连接词。无功能标点堆砌由人工 Gate D/E 判断处理。 - 该脚本是
story-deslop的本地副本,不引用其他 skill 的文件。
---
Phase 4:输出润色结果
## 去AI味润色报告
### 字数协议
- 原文字符数:{N0}
- 修订后字符数:{N1}
- 净变化:{N1 - N0}({百分比})
- 是否在 tier 上限内:{是 / 否(超限 X%,已分段并标注 [需复核])}
### 修改统计
- 总修改数:{N} 处
- 禁用词替换:{N} 处
- 句式调整:{N} 处(含 "不是A而是B" {N}、",带着..." {N}、声音描写 {N})
- 修饰词清扫:{N} 处
- 心理外化:{N} 处
- 重复描写合并:{N} 处
- 重复语义去重:{N} 处(形容词重复 {N}、近义词重复 {N}、含义重复 {N}、主语重复 {N})
- 比喻删除:{N} 处
- 节奏调整:{N} 处
- 对话优化:{N} 处
- 标点节奏调整:{N} 处(保留有功能 `?`/少量 `!`,将 `……`/`——` 改为动作、短句、逗号或句号,并清理无功能堆砌)
- 结尾修正:{N} 处
### 修改前后对比
{逐段展示修改,标注改动类型;超过 30 处时仅展示前 10 处 + 末 5 处 + 其余按 Gate 分桶计数}
### 润色后全文
{**文件模式(默认;章节/正文文件、批量与长篇去AI)**:通过 Edit/Write 直接改写落盘,本节只回 ≤200 字代表性片段,不向父会话返回全文。**文本模式(仅限交互式贴入、无文件路径的零散片段)**:完整输出润色后的文本。}字数硬约束:删除比例不得超过 Phase 2 等级对应上限(轻度 ≤15%、中度 ≤25%、重度 ≤35%)。超限时分段输出并在报告里标记,不得整段删除正文。
收敛终止: 1. 同一段连续两轮去 AI 后没有新改动 → 停止该段处理 2. 全文上限 3 轮重扫;第 3 轮仍有 ≥10 处改动 → 在报告里标 [需复核],移交人工 3. 每轮结束前都要做一遍"再检一次":是否有不符合的地方,有则继续;没有则停
---
使用场景
| 场景 | 操作 |
|---|---|
| 用户贴一段文字说"太AI了" | 执行完整检测 + 润色流程 |
| 用户说"帮我润色" | 先检测AI味,再润色 |
| 用户说"检查下有没有AI味" | 只做检测,不做修改 |
用户写作中要求 仅标注 / 只检测 / 不要改 | 嵌入式提醒模式:执行 Phase 1+2,跳过 Phase 3-4;输出问题标记表(含 Gate 列),不修改原文,不写文件 |
---
参考资料
按需加载以下文件:
| 文件 | 何时加载 |
|---|---|
| references/banned-words.md | 检测和替换禁用词时 |
| references/anti-ai-writing.md | 去AI味完整指南:预防+三遍法+范例 |
| scripts/normalize-punctuation.js | 文件模式落盘后做确定性标点收尾;默认保留引号风格 |
---
流程衔接
流水线: 通用 位置: 润色(共享收尾)
| 时机 | 跳转到 | 命令 |
|---|---|---|
| 继续写作 | story-long-write / story-short-write | /story-long-write 或 /story-short-write |
| 发现结构问题 | story-long-analyze / story-short-analyze | /story-long-analyze 或 /story-short-analyze |
| 准备做封面 | story-cover | /story-cover |
---
语言
- 跟随用户的语言回复,用户用什么语言就用什么语言回复
- 中文回复遵循《中文文案排版指北》
去AI味完整指南
<!-- version: 2.0.0 sync-source: skills/story-setup/references/agent-references/anti-ai-writing.md 本文件在 6 个 skill 副本间需保持一致: story-deslop / story-long-write / story-short-write / story-short-analyze / story-review / story-setup 修改后请同步所有副本(CI 守卫见 scripts/check-shared-files.sh)。 -->
识别AI写作指纹、系统性去AI三遍法、禁用词约束、改写范例库。用于正文写作后做去AI味自检和改写时查阅。
---
决策路由
| 你在做什么 | 查阅哪个模块 |
|---|---|
| 写完正文后做去AI自检 | 核心规则 -> 8种AI写作模式检测 -> 质量维度检查 |
| 改写某段AI味重的文字 | 改写范例库 + 冲突对话改写范例 |
| 检查是否用了禁用词 | 禁用词与句式速查 -> AI高频词(模式1) |
| 系统性去除整章AI味 | 系统性去AI三遍法 |
| 检查章尾是否有总结升华 | AI写作指纹 -> 章末总结体 |
| 判断情绪描写是否告知式 | Show Don't Tell原则 + 去AI味补充技法 |
| 快速扫描全章质量 | 快速自检口诀 + 质量维度检查 |
指令语气
本文件以问题模式和高危清单为主。一级高危词优先检查;二级/语境敏感词按频率、语境和是否偷懒判断。遇到冲突时,保留创作意图与剧情功能优先于机械替换。
---
AI写作指纹(必须避免)
高频AI用词
完整禁用词表见 banned-words.md
补充类目(banned-words.md 未覆盖的高阶替换):
| 类别 | 替代原则 |
|---|---|
| 抽象升华词(命运、宿命、注定) | 用具体事件代替抽象概念 |
| 万能比喻(像潮水般、如闪电般、仿佛春风) | 要么不用比喻,要么用生活化的比喻 |
章末总结体
禁止在章节结尾用以下方式收束:
- 总结性感悟("他终于明白了……")
- 升华式感叹("这一夜,注定无人入眠")
- 哲理式收尾("人生就是这样……")
- 伏笔式预告("他不知道的是,更大的风暴即将来临")
正确做法:章尾用动作、对话或悬念收束,让情节本身制造余韵。
叠加式描写(同一动作掰开写三遍)
检测模式:一个动作/情绪先写发生,再补感知细节,再补身体反应,分三段依次写完。读者看到的是同一个动作被掰开写了三遍。
典型特征:
- 先写一个概括性动作,再展开写同一动作的细节,再写身体反应:三段说的是同一件事
- "发生层→感知层→反应层"按顺序分段出现
- 每个维度独立成段,而不是揉进同一段连续正文
错误示例:
林父低着头,左手把文书压住,右手拿笔,往纸上落。
>
手在抖。不是小抖,是从肘到腕的那种。
>
笔尖在纸上停了停,写了一横,又停。那个"林"字的撇写歪了。
→ 同一个动作(手抖/写字)分三段写,每段是同一瞬间的不同维度
正确做法:发生、感知、反应三个维度揉进同一段连续正文,读者读到一个完整瞬间:
林父左手压着文书,右手拿笔往纸上落,笔尖一触纸面就偏了,从肘到腕止不住地抖,那一横斜着拖出去。
→ 发生、感知、反应在一段里同时呈现
处理原则:不是删除情绪细节,而是合并同一瞬间的重复描写。若合并后明显变薄,优先恢复原文中有功能的信息,或把既有信息改成更自然的动作/对话表达;不要新增原文没有的情节、设定、关系或时间线。
---
核心规则
规则 1:段落密度诊断
段落不是越碎越好,也不是越长越有质感。检查重点是朗读和手机阅读是否卡顿:
- 一段通常只承载一个动作、一个信息变化或一组紧密相关的反应。
- 逗号串太长、多个完整动作挤在一段里,读起来需要换气时,按动作或信息变化拆开。
- 连续短段碎成提纲时,合并同一镜头内的相邻句,让画面保持连续。
过密:他看着窗外的雨,心中涌起一股说不清的感觉,这些年走过的路和很多已经忘记的事都在这一刻涌上心头。
更自然:他盯着窗外。雨下了很久。
"你还在想她?"老刘问。
他没说话。规则 2:动作 + 对话 + 情绪反应
三要素循环推进,不要单写心理活动超过 2 段:
动作 -> 对话 -> 情绪反应 -> 动作 -> 对话 -> ……情绪不用"他感到/他觉得",用身体反应和行为表现:
- 不写"他很紧张" -> 写"手心全是汗,筷子差点掉了"
- 不写"她很愤怒" -> 写"她把杯子摔在地上,碎片弹到脚背上也没弯腰捡"
- 不写"他很伤心" -> 写"他在车里坐了二十分钟才发动引擎"
规则 3:短句优先
| 场景 | 句长 | 示例 |
|---|---|---|
| 打斗/紧张 | 3-8 字 | 他侧身。刀光一闪。血溅在墙上。 |
| 对话 | 口语化 | "你疯了?""可能吧。" |
| 日常 | 8-15 字 | 厨房里有炒菜的声音,闻着像辣椒炒肉。 |
| 描写 | 读起来不卡,信息不过载 | 天快黑了,巷子里没什么人。 |
规则 4:口语化表达
- 允许用俚语、粗话(符合角色身份)
- 对话不要书面语("我认为此事不妥" -> "我觉得不靠谱")
- 叙述也不要端着("他目光如炬" -> "他眼珠子一动不动盯着")
- 短语优先于成语("无可奈何" -> "没办法")
---
---
Show Don't Tell 原则
| Tell(告诉) | Show(展示) |
|---|---|
| 他是个胆小的人 | 他把检查报告在手里翻来覆去看了三遍,还是不敢打开 |
| 这间酒吧很吵 | 酒保凑到他耳边喊了两次他才听见 |
| 她很富有 | 她随手把一张信用卡丢在桌上,卡面上的数字比这顿饭贵十倍 |
| 两人关系很差 | 他把烟掐灭在她刚泡的茶杯里,她面无表情地把杯子推到一边 |
| 他很聪明 | 三秒钟。他看了三秒钟就把文件合上了。"第三页,第二行。" |
核心方法: 1. 用行为代替形容词 2. 用细节代替总结 3. 用对话代替旁白说明 4. 用反应代替情绪词
---
质量维度检查
1. 核心一致性(权重最高)
- 剧情是否与大纲/前文一致
- 人物行为是否符合人设
- 设定是否有前后矛盾
2. 表面改写(防AI指纹)
- 是否包含AI高频用词(见上表)
- 章尾是否有总结/升华
- 是否有大段纯心理描写
- 段落是否按戏剧单元/镜头自然断开,避免机械单句成段或为凑短碎成提纲(网文段落规则)
3. 格式一致性
- 对话格式统一:按项目/平台约定保持同一引号风格;知乎盐言短篇可用「」
- 标点节奏匹配语气:避免通篇句号化;保留有功能的问号和少量感叹号;用动作/短句表达迟疑或打断,不用省略号或破折号硬造停顿
- 场景切换有明显标记
- 时间线清晰可追踪
4. 可读性
- 是否有连续多个长句压住阅读节奏,且缺少动作、对话或短句换气
- 对话是否口语化
- 是否有未解释的生僻词/设定术语
- 节奏是否有快有慢(不能全是一种节奏)
5. 逻辑连贯性
- 角色动机是否合理
- 事件因果链是否清晰
- 时间线是否对得上
- 角色的知识范围是否合理(不能"开上帝视角")
---
快速自检口诀
一事一段,镜头自然断。
对话要像人说话。
心情不写心里话。
结尾不搞大升华。
打斗不写流水账。
日常要埋伏笔桩。网文段落规则:按戏剧单元/镜头/一件事结束自然断段;短段快读,长段承载完整推理、氛围和情绪链,避免机械单句成段或通篇同长度。
---
禁用词与句式速查
完整禁用词表和句式模板见 banned-words.md
正确替代示例
- '他感到一丝紧张' -> '他的手在抖'
- '她很伤心' -> '她转过身去,肩膀微微颤动'
- '"好的。"他说道' -> '"好的。"他点了根烟'
- '他深吸一口气' -> '他把烟摁灭在烟灰缸里'
---
8 种 AI 写作模式检测
模式 1:AI 高频词
| 禁用 | 替换为 |
|---|---|
| 不禁 | 删掉 |
| 仿佛/宛如 | 删掉或用具体描写 |
| 映入眼帘 | 删掉 |
| 心中暗道 | 用动作展示思考 |
| 沉声道/淡淡地说 | 换成动作标签 |
| 脸色一变 | 用具体表情/动作 |
| 嘴角微扬 | 他笑了/他翘了下嘴 |
| 不由自主 | 删掉 |
| 只见/此时此刻 | 删掉 |
| 目光如炬 | 删掉或具体化 |
模式 2:弱化副词泛滥
阈值:每 1000 字超过 3 个 = AI 签名。重点监控:微微、淡淡、缓缓、轻轻。
模式 3:意义膨胀
- "意义深远" -> 写具体后果
- "前所未有" -> 给出对比参照
- "可谓" -> 删掉
模式 4:万能结论
- "未来可期" -> 用未解决的紧张感结尾
- "前途无量" -> 删
- "充满希望" -> 写具体的下一步动作
模式 5:论文体段落结构
小说中出现以下开头句 = AI 入侵:
- "不难看出""由此可见""事实上""综上所述"
模式 6:书面语连词泛滥
叙事散文中频繁出现:"于是乎""与此同时""从而""因而""诚然" -> 口语化替代或直接删除。
模式 7:三连排比癖
AI 喜欢把事情凑成三个以显"完整"。-> 砍到只剩最有力的一条。
模式 8:解释腔 / 上帝视角 / 安排感
最难察觉、却最"像 AI"的一类。叙述者跳出角色当下,去解释、剧透、总结、定性、拔高,读者能闻到"作者在场"和"剧情被安排好了"的味道。这正是"说教感/上帝感/解释腔/机械感/刻意感/安排感"的来源。
| 表现 | 例(删/改) |
|---|---|
| 解释因果 | 「之所以…是因为」「原来…」「这意味着」「正是因为」-> 删。因果只从角色动作、对话、反应里让读者自己拼 |
| 上帝视角剧透 | 「她不知道的是」「殊不知」「多年以后」「冥冥之中」「仿佛预示着」-> 删。只写角色此刻知道的,悬念让读者自己悬 |
| 替读者下结论/定性 | 「演得真好」「这出戏她看过一遍」「他就是这样薄情的人」-> 删。把证据(神态、动作、台词)摆出来,定性留给读者 |
| 替角色总结心理 | 「她明白,这一切都是命」-> 换成一句带偏见的闪念或一个身体反应 |
| 安排感/硬铺垫 | 为后文强行交代背景、整段回忆倒叙 -> 背景按角色此刻真实所需,用闪念、半句话、物件零碎带出,不集中交代 |
| 升华式收尾 | 结尾对仗拔高、金句点题 -> 用一个动作或一句留白收住,把"意思"压进画面里 |
更隐蔽的一层(最难自查,没有标志词)——同样是安排感/上帝感:
- 评判性副词/补语:「关切得恰到好处」「笑得恰如其分」「不多不少」-> 作者在替读者盖章"这是装的"。只写动作("她掩了帕子,眼睛没动"),装不装让读者自己判。
- 剧透式点破潜台词:「那点笑她看得分明」「谁都看得出他在撒谎」-> 把藏着的挑明了。留着别点破。
- 定性比喻/盖棺句:「像在宣判一件早已定好的事」「像看一件死物」-> 比喻在替角色下定论。非角色此刻强烈主观感受就删;要留也只能是她带偏见的瞬间感觉,不是客观断言。
自检:每句问一遍——这是"角色在经历",还是"作者在讲解/安排"?凡作者跳出来讲,删,或改成角色视角内的呈现。根治办法是锁定深度限知视角(见 writing-craft.md「视角姿态:深度限知」),镜头钉死在角色身体里,作者就没位置跳出来了。
---
系统性去AI三遍法
Pass 1:去泛化(Strip Generic)
- 抽象情绪总结句 -> 删或替换为具体动作
- 假深度句 -> 删
- 意义膨胀 -> 缩小到具体影响
- 空洞结论 -> 删
- 工整对比句式 -> 打散重写
- 装饰性形容词堆砌 -> 白描
- 过度使用"于是""然而""此刻" -> 删掉一半
- 所有角色说话一样"高级" -> 区分语气
原则:能删就删,不能删就用具体细节替换。这一遍去掉80%的AI味。
Pass 2:去书面化(Cut Professional Diction)
- 分析性用词("机制""结构""逻辑""体系"出现在小说中)-> 换成日常表达
- 抽象名词滥用 -> 直接说事
- 体制内用语("进一步""深入""推进""落实")-> 删
- 专业术语堆砌 -> 只保留必要的,用白话解释
例外:保留专业感的场景(历史题材正式用语、文学向刻意密度、喜剧夸张修辞)。
Pass 3:回自然感(Restore Natural Presence)
- 具体的感官细节(气味、温度、触感)
- 角色说话方式的区分(不同人不同语气)
- 节奏变化(长短句交错):按情绪 beat、动作推进和戏剧单元自然调节句段长短;忌连续多段同一长度,也忌为凑短而碎成提纲。长短不是随机,沉淀处可放慢,冲突/反转处可骤短,完整推理与情绪链优先保持连贯
- 社会位置感的对话(上级和下属说话方式不同)
- 场景特有的记忆点
- 项目特有的语言习惯(角色的口头禅)
原则:少即是多。每段加 1-2 个具体细节就够了。
升级策略
| AI味程度 | 策略 |
|---|---|
| 轻度 | 只做 Pass 1 |
| 中度 | Pass 1 + Pass 2 |
| 重度 | 完整三遍 + 重点段落重写 |
自检清单
- 对话自然度检查:对话是否使用口语化表达,是否避免了书面语/正式腔调
- 删掉任何一句,会影响理解吗?不会 = 可能多余
- 不同角色能通过对话区分吗?
- 有没有一个细节是这个场景特有的?
---
去AI味补充技法
Show vs Tell
| 告知类型 | AI写法 | 自然写法 |
|---|---|---|
| 告诉期待感 | "他很期待" | 展示期待->情绪->满足的链条 |
| 告诉角色目的 | "她想离婚" | 用行动展示目的 |
| 告诉角色态度 | "她很冷静" | 用对话和反应体现 |
| 告诉剧情走向 | "接下来会发生大事" | 用铺垫->反转->延续展示 |
心理描写润物细无声
- 加括号标注内心活动 = 破坏代入感
- 大段内心独白解释动机 = AI签名
- 直接写"她感到""她意识到" = 告知情绪
自然写法:心理活动自然融入叙事,用行为暗示心理,用沉默/动作/反常行为表达内心。
代入感检查
- 主角行为读者能理解、共鸣、接受吗?
- 反派够强吗?(弱反派 = 读者觉得主角赢了没意义)
- 是否围绕人设写行为?(行为/语言/思维围绕人格展开)
- 读者已知信息是否被有效操控?(信息差制造情绪波动)
---
改写范例库
情绪外化范例
紧张
- '他感到一阵紧张,心跳不由自主地加快了'
- 他攥紧了手里的纸杯,水洒出来一些
愤怒
- '愤怒在他心中燃烧,他不由得握紧了拳头'
- 他把筷子往桌上一拍,碗里的汤溅了出来
悲伤
- '一丝悲伤涌上心头,她的眼中闪过泪光'
- 她低头搅着咖啡,搅了很久
害怕
- '恐惧瞬间笼罩了他,他感到一阵战栗'
- 他的背贴在墙上,不敢动
失望
- '她感到一丝失落,心仿佛被什么东西揪住了'
- "哦。"她把手机锁了屏
惊讶
- '他的瞳孔微微收缩,显然没有想到会听到这样的话'
- 他张了张嘴,什么都没说出来
场景描写范例
AI风场景
- '阳光透过窗帘的缝隙洒进来,在地板上投下斑驳的光影。空气中弥漫着淡淡的花香,仿佛整个世界都沉浸在一片宁静祥和的氛围中。'
- 下午三点,客厅里只有钟在走。
AI风天气
- '天空阴沉沉的,乌云密布,仿佛随时都会下起倾盆大雨。凛冽的寒风呼啸而过,带着一丝刺骨的寒意。'
- 要下雨了。风把晾在外面的衣服吹得乱晃。
AI风打斗
- '他的拳头犹如疾风骤雨般猛烈,每一击都蕴含着不容置疑的力量。对手的瞳孔微微收缩,显然没有预料到如此凌厉的攻势。'
- 他一拳怼过去,对方没躲开,嘴角破了。
结尾改写范例
升华式结尾 -> '他站在窗前,望着远方的天际线,终于明白了生活的真谛:有时候,放手才是最好的选择。' -> 他把烟掐了,回屋睡觉。
总结式结尾 -> '这一刻,一切都变了。她知道,从今以后,她的人生将翻开崭新的一页。' -> 她关上了那扇门。没回头。
感慨式结尾 -> '岁月如流水般悄然流逝……' -> 直接删掉这种段落。
节奏调整范例
排比句
- '他看着她的眼睛,看着她的嘴唇,看着她微微颤动的睫毛,心中涌起一股难以名状的情感。'
- 他看着她。她没说话。
长句拆短
- '当他终于推开那扇沉重的木门时,映入眼帘的是一间昏暗的房间,空气中弥漫着陈旧的气息,墙角堆满了落满灰尘的箱子。'
- 门开了。屋里很暗,角落堆着几个箱子,上头全是灰。
工整段落打碎
- '她喜欢春天的花朵,喜欢夏天的阳光,喜欢秋天的落叶,喜欢冬天的白雪。每一个季节都有它独特的美。'
- 她喜欢春天。别的季节也还行。
---
冲突对话改写范例
AI式温和对话
- '我觉得你这样做不太合适,能不能考虑一下我的感受?' -> "你眼里还有我吗?"
AI式完美解释
- '其实我这样做是有原因的,因为当时的情况非常复杂……' -> "你能怎么着?"她把茶杯重重放下。
对话情绪五级递进范例
同一冲突场景,从弱到强:
1. 客观陈述:"你把我的东西扔了。" 2. 陈述+建议:"你把我的东西扔了,以后能不能先跟我说一声。" 3. 主观指责:"你凭什么动我的东西。" 4. 指责+命令:"你算什么东西,也配碰我的东西?滚出去。" 5. 指责+PUA:"我伺候你吃伺候你穿,你连个东西都放不好。你这辈子也就是这样了,离了我你什么都不是。"
震惊分层改写范例
AI式一步到位:所有人都震惊了,不敢相信自己的耳朵。
自然分层震惊: 1. 对面的男人手抖了一下,茶杯里的水洒出来。 2. 旁边的人互相看了一眼。有人往后退了一步。角落里有人开始掏手机。 3. 刚才还趾高气扬的女人,脸上的笑僵住了。她张了张嘴,一个字没说出来。
代入感修复范例
被动主角:她很害怕,不知道该怎么办,只能等着事情过去。
主动主角:她锁了门,把手机调成静音,打开了录音。
---
质量检查清单
写完每章后,按此清单逐项扫描:
- [ ] 段落控制:段落按动作/信息变化断开,读起来不卡
- [ ] 正文无破折号:正文(含叙述和对话)无
——/—/--(用句号、逗号、短句或动作断句),不设置对话例外 - [ ] AI高频词扫描:无不禁/仿佛/映入眼帘/心中暗道/沉声道/嘴角微扬/不由自主/只见
- [ ] 弱化副词计数:每1000字"微微/淡淡/缓缓/轻轻"不超过3个
- [ ] 无三连排比:没有AI式的"三个一组"修辞
- [ ] 无论文体:无"不难看出/由此可见/事实上/综上所述"
- [ ] 无书面语连词堆砌:无"于是乎/与此同时/从而/因而/诚然"泛滥
- [ ] 章尾无总结升华:用动作/对话/悬念收束,无感悟/哲理/预告
- [ ] 无大段心理描写:心理活动不超过2段,无括号标注内心
- [ ] 情绪用动作展示:无直接写"愤怒/伤心/紧张",全用身体反应
- [ ] 对话口语化:无书面腔,不同角色语气可区分
- [ ] 标点不压平:没有把质问、爆发、犹豫全部压成句号;也没有随机堆砌
?/!,或用……/——硬造停顿 - [ ] Show Don't Tell:用行为代替形容词,用细节代替总结
- [ ] 句长匹配场景:打斗和冲突段更短,日常和描写段可稍长,但朗读不卡、信息不过载
- [ ] 去AI三遍法执行:轻度只做Pass1,中度做Pass1+2,重度完整三遍
- [ ] 对话自然度测试:无书面语痕迹 = 通过
AI味禁用词与句式表
<!-- version: 2.0.0 sync-source: skills/story-setup/references/agent-references/banned-words.md 本文件在 6 个 skill 副本间需保持一致: story-deslop / story-long-write / story-short-write / story-short-analyze / story-review / story-setup 修改后请同步所有副本(CI 守卫见 scripts/check-shared-files.sh)。 -->
最毒禁用句式(出现即修,最高优先级)
写网文最毒的 AI 句式,作者一旦养成就会反复出现。Gate A 第一遍扫描必须命中:
| 毒级 | 句式 | 错误例 | 修法 |
|---|---|---|---|
| ★★★★★ | "不是A,(而)是B" / "不是A,不是B,(而)是C"("而"可省略,省掉也算命中) | "他不是冷漠,而是绝望" | 直接写 B 或用更自然的表达 |
| ★★★★ | ",带着……" 万能状语 | "他笑了一下,带着一丝不易察觉的嘲讽" | 拆短句或换动作 |
| ★★★★ | "声音不大,却带着一种……的力量" | "她声音不大,却带着不容置疑的力量" | 直接写声音特征或动作 |
| ★★★★ | "他/她知道……" | "他知道这一切都来不及了" | 用行为展示认知 |
| ★★★ | "仿佛/犹如/宛若……一般" | "仿佛能穿透一切一般" | 删掉或白描 |
| ★★★ | "眼中闪过一丝……" / "嘴角勾起一抹……" | "眼中闪过一丝悲伤" | 他垂下眼 / 他笑了一下,没到眼底 |
| ★★★ | "心中涌起一股……" / "心头一震" | "心中涌起一股暖流" | 用身体反应 |
| ★★ | 章末预告 "他不知道的是……" | "他不知道的是,更大的风暴即将来临" | 用具体钩子物件/事件收束,避免空泛预告 |
凡命中 ★★★★★ 一处即视为重度AI味的强证据;★★★★ 命中 ≥2 处即触发中度复扫。
标点:正文(含叙述和对话)禁用破折号 ——/—、双连字符 -- 和省略号停顿,改用句号、逗号、短句或动作断句;不设置对话破折号例外。盐言「」引号不在此列。
---
一级禁用词(出现即替换)
情态类
仿佛、犹如、宛若、如同、一丝、一抹、些许、几分、隐约
动作类
深吸一口气、缓缓、不禁、微微、轻轻、淡淡
表情类
眼中闪过、嘴角勾起、眉头微皱、眉眼低垂、瞳孔微缩
心理类
心中一动、心头一震、心下了然、心中暗道、心底泛起、不由得
判断类
不容置疑、不容置喙、不易察觉、显而易见、毫无疑问、不可否认
形容类
坚定、闪烁着光芒、狡黠、深邃、凛冽、冰冷
过渡类
不由自主、情不自禁、自然而然
二级禁用词(高频出现时替换)
语境敏感词(仅高频或偷懒时处理)
突然、好像、瞬间(角色口语、真实突发、时间压缩、视角不确定时可保留)
书面腔 → 口语化
| 书面腔 | 口语化替换 |
|---|---|
| 瓦解 | 消失 / 散了 / 没了 |
| 无名火 | 烦躁 |
| 往我心上捅刀子 | 心烦意乱 |
总结句式
- "他/她终于明白..."
- "他/她这才意识到..."
- "此刻,他/她..."
- "一切...都..."
- "原来..."
排比句式
- 连续3句以上相同结构的排比
- "有的...有的...有的..."
- "一边...一边...一边..."
升华句式
- "这一刻..."
- "他知道..."
- "她明白..."
- "这就是..."
禁用句式模板
| 句式 | 示例 | 问题 |
|---|---|---|
| "不是A,而是B" | "他不是冷漠,而是绝望" | 最毒;直接写 B |
| "...,带着..." | "他说,带着一丝无奈" | 万能状语 |
| "声音不大,却带着……" | "她声音不大,却带着不容置疑的力量" | AI 最爱声音描写 |
| "仿佛能...一般" | "仿佛能穿透一切一般" | 文言腔 |
| 对话标签密度过高/公式化标签 | "好的,他说道" | 普通"说"可保留;高频或公式化时处理 |
| "他/她感到..." | "她感到一丝失落" | 告诉而非展示 |
| "他/她意识到..." | "他意识到事情不对" | 直接告知 |
| "眼中闪过一丝XX" | "眼中闪过一丝悲伤" | 模板化 |
| "嘴角勾起一抹XX" | "嘴角勾起一抹冷笑" | 模板化 |
| "心中涌起一股XX" | "心中涌起一股暖流" | 模板化 |
比喻分类(默认全删)
带"像/如/仿佛/犹如/宛若"的比喻是 AI 写作的强信号。默认全删,分类只是用来识别:
| 比喻类别 | 例 |
|---|---|
| 动物类 | "像一头被抛弃的野狗" |
| 物品/现象类 | "像一把刀" "脸色惨白得像这漫天的雪" |
| 状态类(陈词滥调) | "梨花带雨" "如沐春风" |
| 抽象类 | "像命运的齿轮" "像上辈子的尘埃" |
| 假设类 | "力道大得像是要把骨头捏碎" |
处理:把比喻改为直接描述/动词/名词/作用/结果/事实。例 "脸色惨白得像这漫天的雪" → "脸色惨白"。
例外由 SKILL.md Gate B 的 deslop 原始策略管控("生活化、角色化比喻可保留");本表用于扫描,不做例外判定。
替换策略速查
| 原文类型 | 替换方法 | 示例 |
|---|---|---|
| 抽象情绪词 | 具体动作 | "紧张" → "手在抖" |
| "感到XX" | 外化表现 | "感到愤怒" → "攥紧拳头" |
| 形容词堆砌 | 白描手法 | "美丽动人的笑容" → "她笑了" |
| 书面表达 | 口语化 | "不容置疑" → "就是" |
| 解释性描写 | 留白 | "他因为害怕而..." → "他退后一步" |
| 连续排比 | 保留最强一条 | 3 句排比留 1 句 |
| 总结升华句 | 直接删除 | "这一刻,她终于明白了..." → 删 |
| "不是A,而是B" | 直接写 B 或更自然的表达 | "他不是冷漠,而是绝望" → 直接写 B |
| 多余修饰(形容词/定语/量词/指示代词) | 删 | "白色的药片" → "药片";"手里那截链子" → "链子";"飞驰的汽车" → "车" |
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const USAGE = `Usage: node normalize-punctuation.js [--check] [--quote-mode keep|ascii|yan] <file...>
Normalize正文 punctuation deterministically:
- replace ellipses, em dashes, and double hyphens with Chinese punctuation
- remove markdown divider lines (---) from正文
- keep quote style by default; convert quotes only when explicitly requested
`;
const options = {
check: false,
quoteMode: 'keep',
files: [],
};
for (let i = 2; i < process.argv.length; i += 1) {
const arg = process.argv[i];
if (arg === '--check') {
options.check = true;
} else if (arg === '--quote-mode') {
const value = process.argv[i + 1];
if (!value) die('--quote-mode requires keep, ascii, or yan');
options.quoteMode = value;
i += 1;
} else if (arg.startsWith('--quote-mode=')) {
options.quoteMode = arg.slice('--quote-mode='.length);
} else if (arg === '-h' || arg === '--help') {
process.stdout.write(USAGE);
process.exit(0);
} else if (arg.startsWith('-')) {
die(`Unknown option: ${arg}`);
} else {
options.files.push(arg);
}
}
if (!['keep', 'ascii', 'yan'].includes(options.quoteMode)) {
die(`Invalid --quote-mode: ${options.quoteMode}`);
}
if (options.files.length === 0) {
die('No files provided');
}
let totalFindings = 0;
let changedFiles = 0;
let failed = false;
for (const file of options.files) {
const fullPath = path.resolve(file);
let input;
try {
input = fs.readFileSync(fullPath, 'utf8');
} catch (error) {
failed = true;
console.error(`${file}: unable to read (${error.message})`);
continue;
}
const result = normalizeDocument(input, options.quoteMode);
totalFindings += result.findings.length;
if (options.check) {
for (const finding of result.findings) {
console.log(`${file}:${finding.line}:${finding.column}: ${finding.type}: ${finding.message}`);
}
continue;
}
if (result.output !== input) {
fs.writeFileSync(fullPath, result.output, 'utf8');
changedFiles += 1;
console.log(`${file}: normalized (${result.findings.length} issue${result.findings.length === 1 ? '' : 's'})`);
}
}
if (failed) {
process.exit(2);
}
if (options.check && totalFindings > 0) {
process.exit(1);
}
if (!options.check) {
console.log(`Done. Changed files: ${changedFiles}`);
}
function die(message) {
console.error(message);
console.error(USAGE.trimEnd());
process.exit(2);
}
function normalizeDocument(input, quoteMode) {
const newline = input.includes('\r\n') ? '\r\n' : '\n';
const trailingNewline = input.endsWith('\n');
const lines = input.split(/\r?\n/);
if (trailingNewline) lines.pop();
const findings = [];
const outputLines = [];
let inFence = false;
let inFrontMatter = hasYamlFrontMatter(lines);
let quoteOpen = false;
for (let index = 0; index < lines.length; index += 1) {
const lineNo = index + 1;
let line = lines[index];
const trimmed = line.trim();
if (trimmed.startsWith('```')) {
inFence = !inFence;
outputLines.push(line);
continue;
}
if (inFrontMatter) {
outputLines.push(line);
if (index > 0 && trimmed === '---') inFrontMatter = false;
continue;
}
if (inFence) {
outputLines.push(line);
continue;
}
if (trimmed === '---') {
findings.push({
line: lineNo,
column: line.indexOf('-') + 1,
type: 'markdown-divider',
message: '正文中不要使用 markdown 分隔线;建议移除该行。',
});
continue;
}
const punctuationResult = normalizePausePunctuation(line, lineNo);
findings.push(...punctuationResult.findings);
line = punctuationResult.line;
const quoteResult = normalizeQuotes(line, quoteMode, quoteOpen, lineNo);
findings.push(...quoteResult.findings);
line = quoteResult.line;
quoteOpen = quoteResult.quoteOpen;
outputLines.push(line);
}
return {
output: outputLines.join(newline) + (trailingNewline ? newline : ''),
findings,
};
}
function normalizePausePunctuation(line, lineNo) {
const findings = [];
const original = line;
const pattern = /…+|\.{3,}|——|—|--+/g;
let output = '';
let lastIndex = 0;
let match;
while ((match = pattern.exec(original)) !== null) {
output += original.slice(lastIndex, match.index);
const token = match[0];
const replacement = choosePauseReplacement(original, match.index, token.length);
output += replacement;
findings.push({
line: lineNo,
column: match.index + 1,
type: getPauseType(token),
message: replacement ? `替换为「${replacement}」。` : '移除重复标点。',
});
lastIndex = match.index + token.length;
}
output += original.slice(lastIndex);
return { line: output, findings };
}
function hasYamlFrontMatter(lines) {
if (!lines[0] || lines[0].trim() !== '---') return false;
let sawYamlField = false;
for (let i = 1; i < Math.min(lines.length, 40); i += 1) {
const trimmed = lines[i].trim();
if (trimmed === '---') return sawYamlField;
if (/^[A-Za-z0-9_-]+:\s*/.test(trimmed)) sawYamlField = true;
}
return false;
}
function getPauseType(token) {
if (token.startsWith('-')) return 'double-hyphen';
if (token.includes('—')) return 'em-dash';
return 'ellipsis';
}
function choosePauseReplacement(text, start, length) {
const before = previousNonSpace(text, start - 1);
const after = nextNonSpace(text, start + length);
const rest = text.slice(start + length).trimStart();
// 正文产物不保留 `……`、`——`、`—` 或 `--`;对话打断和数字区间不设例外。
if (before === '') return '';
// 紧跟开引号/开括号的停顿符号属于句首边界,删空即可,避免产出 `「,…」` 或 `「。」`。
if (isOpeningDelimiter(before)) return '';
if (/\d/.test(before) && /\d/.test(after)) return '到';
if (isClosingQuote(after)) return isSentencePunctuation(before) ? '' : '。';
if (!after) return isSentencePunctuation(before) ? '' : '。';
if (isSentencePunctuation(before) || isPunctuation(after)) return '';
if (/^(因为|原来|这是|那是|也就是|换句话|说白了|所谓|答案|原因|结果|真相|问题在于)/.test(rest)) return ':';
if (/(原因|答案|真相|结果|结论|问题|选择|意思)$/.test(text.slice(0, start).trim())) return ':';
return ',';
}
function previousNonSpace(text, index) {
for (let i = index; i >= 0; i -= 1) {
if (!/\s/.test(text[i])) return text[i];
}
return '';
}
function nextNonSpace(text, index) {
for (let i = index; i < text.length; i += 1) {
if (!/\s/.test(text[i])) return text[i];
}
return '';
}
function isSentencePunctuation(ch) {
return /[,,。.!!??;;::…]$/.test(ch || '');
}
function isPunctuation(ch) {
return /[,,。.!!??;;::、…"“”'‘’」』))]/.test(ch || '');
}
function isClosingQuote(ch) {
return /["”」』]/.test(ch || '');
}
function isOpeningDelimiter(ch) {
return /[「『((“‘]/.test(ch || '');
}
function normalizeQuotes(line, quoteMode, quoteOpen, lineNo) {
if (quoteMode === 'keep') {
return { line, findings: [], quoteOpen };
}
const findings = [];
let output = '';
for (let i = 0; i < line.length; i += 1) {
const ch = line[i];
if (quoteMode === 'ascii' && /[「」『』“”]/.test(ch)) {
output += '"';
findings.push({ line: lineNo, column: i + 1, type: 'quote-style', message: '按显式 quote-mode 转为半角双引号。' });
continue;
}
if (quoteMode === 'yan' && (ch === '"' || ch === '“' || ch === '”')) {
const replacement = quoteOpen || ch === '”' ? '」' : '「';
output += replacement;
quoteOpen = replacement === '「';
findings.push({ line: lineNo, column: i + 1, type: 'quote-style', message: '按显式 quote-mode 转为盐言引号。' });
continue;
}
output += ch;
}
return { line: output, findings, quoteOpen };
}
Related skills
FAQ
What does story-deslop do?
|
When should I use story-deslop?
|
Is story-deslop safe to install?
Review the Security Audits panel on this page before installing in production.