본문으로 건너뛰기
B

dsh-consistency-guard

bycall/dsh-consistency-guard

Consistency guard for long-form writing: scans conversation prose for character, timeline and numeric setting conflicts across chapters, then jumps straight to the conflicting message.

설치

dsh plugin --profile web add github:bycall/dsh-consistency-guard

README

dsh-consistency-guard

DeepSeek Harness(dsh)长文创作一致性守卫插件:扫描会话正文中的人物设定、时间线、数值型设定,自动发现跨章节矛盾,并点击直接跳转到冲突原文位置

背景:百万字长篇创作中,人物年龄/等级/境界、时间线、数值设定在数十次对话迭代后极易失控。dsh 内置没有跨章节内容审计能力,现有写作类插件做的是编辑与导出,本插件补上「一致性守卫」这一环。

功能

能力说明
实体提取引擎纯前端规则引擎,从会话正文中提取属性设定:年龄(25 岁 / 二十七岁)、年份(1998 年 / 星历 412 年)、身高(180cm / 身高 180)、等级、境界。中英文数字统一归一化,25岁二十七岁 可正确比对。
实体归属优先匹配设定字典中的已知人名,其次按常见姓氏识别,最后用「动词/属性词邻接」启发式回退;内置停用词拦截,避免把「境界三层的修士」误判成人名。
设定字典settings.section 插槽提供编辑器:维护 实体 / 属性 / 标准值 条目,保存在浏览器本地存储(localStorage),编辑后 dock 面板即时生效,无需重载。
两级冲突检测数值矛盾(numeric):同一实体同一属性出现两个不同数值——硬性矛盾,如林默第 1 轮 25 岁、第 7 轮 27 岁。
表述差异(wording):非数值属性的表述漂移,低置信度提示。
无归属的 mention 归入 (unattributed) 桶,只给提示、不误报硬冲突。
冲突 dock 面板输入框上方( conversation.input.dock 插槽)显示冲突数胶囊:无冲突为绿色、有冲突为红色;点击展开冲突列表(portal 到页面根部,不受布局容器裁剪),按「数值矛盾优先 + 轮次」排序。
点击定位跳转点击任意冲突项 → 切回「对话」视图 → 平滑滚动到第二个冲突值所在消息并红色高亮。若目标在未加载的更早记录中,自动调用 loadOlder(最多 3 次)后重试。

安装

dsh plugin --profile web add file:/path/to/dsh-consistency-guard

然后在 ~/.dsh/profiles/web/package.jsondsh.profile.bundles 数组中加入 "dsh-consistency-guard",重启 DSH.app(或硬刷新浏览器窗口)生效。

卸载

dsh plugin --profile web remove dsh-consistency-guard

工作原理

  • 插槽注册(客户端):通过 ctx.slots 注册两个服务——
    • conversation.input.dock(id consistency-guard-dock):输入框上方的冲突胶囊 + 展开列表。
    • settings.section(id consistency-guard-settings):设定字典编辑区。
  • 数据来源:插槽组件拿到会话快照,读取 snapshot.chat.order / snapshot.chat.nodes,取 assistantdata.blocks[kind=text]user/steering 的文本作为正文来源;工具调用与代码输出刻意跳过,避免把代码里的数字当成设定。
  • 扫描流水线:正文 → 按句切分 → 句内属性模式匹配(年龄/岁/身高/cm/等级/境界/年份)→ 值归一化 → 实体归属 → 按 (实体, 属性) 分组 → 组内出现两个及以上不同值即判定冲突。
  • 属性轴区分境界/修为等级/级别 是中文类型小说中两个独立维度,刻意不合并,否则「境界三层,等级 9」会被误判为矛盾。
  • 跳转机制:复用 dsh 聊天视图的内置语义锚点 data-chat-anchor-key="<key>",跳转 = 找到该行 → scrollIntoView + 红色高亮;点击胶囊先按 label 点击 [role="tab"] 切回「对话」(中文「对话」/ 英文「Chat」,回退首个 Tab),再轮询等待目标行挂载。
  • 字典联动:字典保存在 localStorage,apply 内维护一个极简订阅器(subscribe/getDict/setDict),设置区保存后即时广播给 dock 面板。

目录结构

dsh-consistency-guard/
├── package.json          # dsh.bundle.patch + dsh.client + exports["./client"]
├── cordis.patch.yml      # 挂载插件到 profile 层叠
├── lib/
│   ├── index.js          # 服务端挂载入口(UI-only,最小实现)
│   └── client.js         # 客户端 bundle(window.__ModuleLoader__.load 格式)
└── test/
    └── smoke.cjs         # 冒烟测试(mock ModuleLoader + ctx + 纯函数断言)

开发说明

  • 纯手写客户端 bundle(仅依赖 react,零构建步骤),格式遵循 dsh web 的 lazy-CJS 模块表契约:window.__ModuleLoader__.load({ id, factory })
  • dsh.client.inject 声明了插件可用的客户端服务:slots / sessions / locale
  • 所有核心逻辑(实体提取、归一化、人名校验、冲突检测、字典读写)都是不依赖 react 的纯函数,通过 exports.__test 暴露给冒烟测试。
  • 校验:node --check lib/client.js;冒烟测试 node test/smoke.cjs(覆盖工厂物化、两个插槽注册、中文数字归一、实体归属、去重、人名校验、端到端冲突检测)。

冒烟测试覆盖

factory materialized; exports: [ 'apply', 'inject', '__test' ]
registered slots: ["conversation.input.dock[consistency-guard-dock]","settings.section[consistency-guard-settings]"]
cnNumToInt: ok
normalizeValue: ok
extractMentions attribution + dedup: ok
plausibleName: ok
mentions: 8 conflicts: 2
detectConflicts end-to-end: ok
loadDict without localStorage: ok
ALL CHECKS PASSED

端到端用例模拟《归墟》修订场景:林默第 1 轮 25 岁 / 第 7 轮 27 岁(报数值矛盾)、沈砚青等级 9 → 12(报数值矛盾)、苏晚晴两处均为 27 岁(不误报)、境界三层与等级 9 不合并(不误报)。

已知限制

  • 规则引擎覆盖常见中文小说设定的属性词,未收录的属性不会参与比对;设定字典用于补强专有名词。
  • 人名识别基于常见姓氏表 + 启发式,生僻姓名建议加入设定字典以确保归属准确。
  • 设定字典保存在浏览器 localStorage,换浏览器或清理站点数据后需要重新录入。

路线图(二期)

  • 伏笔追踪:标记「伏笔-回收」对,扫描未回收伏笔
  • 时间线校验:事件先后顺序矛盾检测
  • 语料库统计:章节字数、出场角色频次、设定引用热图
  • 一致性审计报告(Markdown)一键导出

License

MIT

관련 플러그인