shiyan688/dsh-novel-craft ↗★ 0
dsh-novel-craft
提供小说创作工作台及作者偏好档案提炼功能 适合小说创作者,通过👍/👎反馈让AI学习并模仿个人写作风格
同名包的其他仓库
安装
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:shiyan688/dsh-novel-craft说明文档
阅读完整 README ↗dsh-novel-craft · 小说创作工作台
English: a novel-writing workbench for DeepSeek Harness (dsh) that learns your taste instead of writing generic prose. See README.en.md.
核心那一件事:把一轮里「结构不同」的候选稿摊开,作者只点 👍好 / 👎坏;标注被压成
可复用的写作规律写进 作者偏好档案.md,驱动下一轮收敛。原文另存,不进写稿上下文。
整个 dsh 插件生态里,做"让 AI 写出合格稿"的很多,做"让 AI 写出像你的稿"的只有这一条路。
口径说明(不说大话):这句"原文不进写稿上下文"是准确的,它指的是—— 写稿/写候选/要方向时,模型只拿到写作包,而包里除了上一章结尾(≤900 字,接续用,包内注明) 之外没有任何正文原文;标注原文、证据摘录、批注都另存为文件,写稿一律不读。 但有三处作者亲手点下的按钮会触发一次辅助调用并带上原文,各自有硬上限: ①「提炼规律」把标过的段落(≤40 段 × 240 字、总计 ≤20KB)交给模型压成规律; ②「补齐来源」把证据摘录里的引文(每条裁 160 字)与规律一起交给模型判断对应关系; ③「按批注微调」把被批注的段落(每段裁 800 字)连同前后段各 80 字交给模型改写。 除这三处,没有任何路径把正文原文送进模型。
围绕这一件事,0.2 起它是一个能陪你把一本书写完的工作台:
| 能力 | 一句话 |
|---|---|
| ✍️ 开新章 | 写新的一章:让模型给 8-12 个场景决策方向(不是换词)→ 一次一篇写候选 → 去抽卡选段 → 合并成定稿并写入正文 |
| 🎴 抽卡 + 偏好档案 | 你标好坏 → 蒸馏成规律 → 规律是唯一进上下文的"口味输入"(含规律→证据反查:点开一条规律看它从哪几段来) |
| 🗂 章节看板 | 自动认出作品根(正文/设定/人物/剧情在哪一层),一章一行:字数、轮次、标注、批注、写作包、读者评分、张力、体检问题 |
| 📦 每章写作包 | 把这一章要用的东西组装成一个文件(本章任务 + 规律 + 上一章结尾 + 前情 + 人物 + 台账 + 情节节点 + 伏笔欠账 + 禁 AI 腔清单 + 写作要求),并给出上下文预算表;写稿会话只读它 |
| 💬 批注式微调 | 正文里读到哪儿不对,点那段按 A 留批注;「按批注微调」只改被批注的段落,其余逐字不动(两道防线核对),采纳前逐段对照,写回时自动备份原稿 |
| 📈 情节体检 | 张力曲线(手工标 / 自动估)、大纲偏移、伏笔欠账、字数失衡、事件密度、评分下滑;全部本地统计,正文不进模型上下文 |
| 🧭 全书阶段 | 立项 → 设定 → 人物 → 大纲 → 逐章正文 → 修订 → 完本:产物清单 + 门禁 + 一键起草(模型写初稿,你改) |
工作台是 dsh 的真插件:侧栏入口 + 全屏浮层,浏览器半区(界面)与宿主半区
(读写文件、回环 HTTP API)分离。它读你已有的目录习惯:逐弈登仙-第8章.txt、
剧情/第 8 章 剧情总结.md、人物/*.json、设定/道具与增益台账.md、
factory/runs//第N章/候选稿/、评论/第N章_评论数据.json 都认,不需要为了用它重新整理文件。
安装
dsh plugin --profile web add dsh-novel-craft # 从 npm
dsh plugin --profile web add github:shiyan688/dsh-novel-craft # 或直接从 GitHub
已发布:dsh-novel-craft@0.1.1(npm 页面)。
装完重启一次该 profile(bundle 与 client 元数据在进程内缓存)。重启后侧栏底部 出现「🎴 抽卡工作台」。
要求:dsh 的 web profile(dsh-web-app),并且挂载了 LLM 服务(ctx.llm)——
「提炼规律」那一步要调一次辅助模型。没挂也能用:标注照点,档案照编辑,只是不能自动提炼。
配套 skill(可选,但强烈建议装)
插件负责"让你低负担地点",skill 负责"告诉 agent 怎么用这些标注"。skills/ 下有两个:
cp -r skills/taste-calibration skills/novel-writing /.dsh/skills/ # 或 ~/.dsh/skills/
taste-calibration— 抽卡式作者品味校准(本插件自研核心):怎么造"结构不同"的候选、怎么把选段转成证据、怎么在下一轮收敛;novel-writing— 中文网文写作手艺:禁AI腔(六维度判定)、多视角信息差、术语与账目口径、克制留白。
界面预览
静态快照(由真实组件渲染生成,不是手画的图):docs/preview.html
—— clone 下来用浏览器打开即可,页签切换「抽卡 / 偏好档案 / 目录选择器」三个界面。
改完组件跑 node scripts/build-preview.mjs 就能刷新。
🎴 抽卡工作台 [📁 第9章/候选稿 ▾] [↻] [✕]
[🎴 抽卡] [📋 偏好档案 · 2]
📂 /你的作品/factory/runs/逐弈登仙/第9章/候选稿
[A · 保守精修 2.1k字 · 标 3] [B · 苏清鸢识药 1.8k字] [C · 玩家算计 1.9k字] …
──────────────────────────────────────────────────────────
天蒙蒙亮,一行人踏着露水朝望舒城城门走去。 (光标段浮出 👍 👎)
│ 为首的是金海,浅青色宗门弟子服纤尘不染。…… ← 左边绿条=标了好
│ 老道捋了捋山羊胡,叹了口气:“你性子急躁……” ← 左边红条=标了坏
宁陈站在队尾,指节在袖子里轻轻蹭了一下。
第 4 / 28 段 · j / k 换段 · G 标好 · B 标坏 · 空格 取消 · n / p 换篇
[⚗️ 提炼规律(12)] [📋 偏好档案]
30 秒上手
只想让下一次抽卡更像你(抽卡这条线):
- 侧栏点「🎴 抽卡工作台」→ 顶栏「📁」挑候选稿目录(推荐/最近/浏览/手动四种入口);
- 读:连续正文,
j/k换段、G标好、B标坏、空格取消、n/p换篇; - 点「📋 偏好档案」→「⚗️ 提炼规律」→ 勾选采纳 → 档案里只留下规律。
- 想追一条规律"凭什么":点规律后面的「🔍 证据」——模型标了编号的就直接列出那几段; 老档案(提炼时还没记编号)点「补齐来源」走一次很便宜的模型调用补上。
想在工作台里把一本书写完(作品这条线):
- 要写新的一章:点「🗂 章节」→「✍️ 开新章」→ 要方向 → 写候选 → 去抽卡选段 → 合并写入正文(详见下面那一节);
- 顶部「📁」选任意一个候选稿目录,工作台会自己向上认出作品根(也可以在「🗂 章节」页看到它认出的路径);
- 「🗂 章节」页就是这本书的看板:点章节标题进正文,右侧四个页签 = 批注 / 写作包 / 微调 / 本章设定;
- 写新章之前:先在「本章设定」里写"这一章要干什么"→ 点「生成写作包」→ 把那个
.md交给写稿会话(只给它这一个文件); - 写完读一遍,不对的地方点那段按
A留批注 →「按批注微调」→ 逐段对照没问题再「采纳并写回」; - 觉得整体节奏不对:去「📈 情节」看张力曲线与问题清单;要补立项/大纲这类产物:去「🧭 全书」。
用了什么
| 位置 | 做什么 |
|---|---|
| 侧栏底部「🎴 抽卡工作台」 | 开/关面板 |
| 顶栏「📁 …」 | 目录选择器:推荐 / 最近使用 / 浏览 / 手动输入 |
| 「🎴 抽卡」页 | 连续正文阅读视图:段落之间不打断,标记按钮只在光标/悬停的那段浮出;键盘流 j/k 换段、G 标好、B 标坏、空格 取消、n/p 换篇 |
| 「📋 偏好档案」页 | 只要规律:规律清单 + 提炼审阅 + 标注统计 + 来源回跳 + 就地编辑;每条规律后面有「🔍 证据」可反查它从哪几段来;原文证据收在折叠区 |
| 「🗂 章节」页 | 章节看板:一章一行(字数/状态/轮次/标注/批注/写作包/评分/张力/体检);点标题进单章视图 |
| 单章视图右侧 | 四个页签:💬 批注(就地留改稿清单)、📦 写作包(上下文预算 + 全文)、✍️ 微调(前后对照 + 采纳写回)、📝 本章设定(这一章要干什么) |
| 「📈 情节」页 | 张力曲线(实线=你手工标的,虚线=按正文估的)+ 问题清单 + 未兑现伏笔;「重跑体检」按需算 |
| 「🧭 全书」页 | 七个阶段(立项 → 完本):产物勾选、门禁提示、一键起草本阶段产物 |
| 底部「⚗️ 提炼规律」 | 标注原文 → 规律条目,勾选后写进 /.dsh-novel-craft/作者偏好档案.md |
标注落在 /.dsh-novel-craft/marks.json,随作品走,不写全局配置。
候选稿目录存在 dsh settings 的 dsh-novel-craft 命名空间里(candidateDir)。
用抽卡写新的一章(完整流程)
这是本插件的主线用法。抽卡解决"哪一段好",开新章解决"候选稿从哪来、选完怎么合起来"。
在「🗂 章节」页点右上角「✍️ 开新章」,四步:
-
这一章要写什么:章号(默认给"下一章")、本章设定(会进写作包第 ① 节;如果
大纲.md里有这一章的节点,会带出来给你参考)、要几个方向(默认 10)、本轮额外要求。 -
方向:让模型给一批「场景决策」方向,每个方向一句话说清"这一篇试什么"。示例:
A 保守精修:以最小改动保留现有骨架/B 配角识货:让同行配角先认出货色,主角的算计藏在他的沉默里/C 双层信息差:让掌柜也在算计,读者比主角先看出一层。 方向就是这一批的卡池:可以改名、改说明、去掉不想要的,勾选哪些就写哪些。 口味是怎么进去的:写方向与写候选用的输入都只有写作包,而写作包的第 ② 节就是作者偏好档案(只有规律、已档掉工作台的账目自动块)。写候选时还会把它念两遍: 系统提示点名"第 ② 节里『避免的写法』是作者明确否决过的,一条都不要出现", 用户提示的末尾再把这份否决清单重述一次——写作包有三四千字,规律放中间容易被后文冲淡, 末尾重申一遍只花两三百字。 -
写候选:一次一篇地写,写完一篇立刻落盘成
factory/runs//第N章/候选稿/第N章-A-保守精修.txt(沿用你自己的命名习惯)。 看得见进度(第 3/10 篇),可以中途停,已经写好的不会丢;单篇失败只影响那一篇,重来一次就行。 点「🎴 去抽卡选段」——工作台会顺手把当前卡池设成刚写出来的这个目录,你不用记路径。 -
合并定稿:回「🗂 章节」→「✍️ 开新章」→ 第 ④ 步。这里列出你在抽卡页标了 👍 的段落(按稿件顺序), 并带上你写的取用原因,成为一个「取用段落表」。跨稿衔接处会写成
〔此处需过渡〕标记 —— 工作台不会让模型替你补写,那些位置留给你用批注式微调一处一处处理。 点「写入正文」,正文写成第N章 标题+ 段落的规范格式,原稿自动备份到.dsh-novel-craft/备份/。
在抽卡页给某个好段写一句「取用原因」(标 👍 之后会出现 ✎ 按钮),合并时它会自动进表格——
这份记录就是 筛选与合并记录.md,和你在第 8 章手工写的那份格式一致。
新增产物都放在哪儿
| 文件 | 内容 | 进不进上下文 |
|---|---|---|
/写作包.md(没有轮次目录时退到 .dsh-novel-craft/写作包/第N章 写作包.md) | 写稿会话唯一该读的文件 | ✅ 就该它进 |
/.dsh-novel-craft/批注/第N章.json | 你的改稿批注(机器可读) | ❌ 只在点「微调」时取被批注的那几段 |
/.dsh-novel-craft/章节设定/第N章.md | 本章目标 / 禁止发生 / 出场人物 / 要求 | ✅ 作为写作包的第 ① 节 |
/.dsh-novel-craft/规则来源.json | 规律 → 证据的对应表 | ❌ 只给界面反查 |
/.dsh-novel-craft/微调/第N章 微调稿.md + 原稿备份/ | 微调前后对照、写回前的原稿 | ❌ 给人看的 |
/.dsh-novel-craft/workspace.json | 阶段进度、手工张力、章节状态覆盖 | ❌ |
/.dsh-novel-craft/取用理由.json | 好段的"为什么用这一段"(合并进记录表) | ❌ |
/第N章/候选稿/、定稿候选/、筛选与合并记录.md | 开新章写出来的候选、合并稿、取用记录 | ❌(只有写候选时喂写作包) |
写作包里有哪十节:① 本章任务(你写的)② 作者偏好档案(只有规律)③ 上一章结尾(只取结尾,接续用) ④ 前情提要(来自各章剧情总结,不重读正文)⑤ 本章人物 ⑥ 道具与增益台账(本章相关 + 当前持有) ⑦ 情节节点(上一章/本章/下一章)⑧ 未兑现伏笔 ⑨ 禁 AI 腔清单 ⑩ 写作要求。 每节都有上限,整包默认 9000 字的预算;超了就按"先削补得回来的、后削没它写不了的"顺序削, 削了谁、削了多少都写在返回的预算表里。包尾固定有一段「不要读进上下文的东西」,把证据摘录、marks.json、批注、全书合并稿点名列为禁区。
批注式微调:模型只能改你标过的那几段
作者读到哪儿不对,点那段按 A(或双击)留一条批注——标签(AI 腔 / 啰嗦 / 情绪直给 / 平 / 人物失真 / 逻辑账目 / 信息差 / 其他)+ 一句自由文字。
点「按批注微调」时才把被批注的那几段(每段裁到 800 字)连同批注、以及前后段各 80 字(供模型贴着邻段语气改)交给模型;
其余段落它碰不到。分两道防线:
- 拼装防线:
applyRevision用「原文段落 + 改写表」重新拼,模型没有机会碰到没批注的段落; - 核对防线:
verifyRevision再逐段比对一次——没批注的段落只要有一个字不同就拒绝写回;段落数变了也拒绝。
核对结果分两档,界线是"你有没有授权":
- 拦住的(error):没批注的段落被改了、段落数变了 → 一律不许写回,界面把问题列出来;
- 只提示的(warn):批注过的段落字数变化过大(你的批注本来就可能写着"这段砍一半")→ 摆出前后对照,你自己决定。
采纳写回时先把原稿备份到 .dsh-novel-craft/微调/原稿备份/第N章 .md,批注自动标成已处理;
模型的原始输出也留档(微调/第N章 原始输出.md)——格式没对上时,靠它排查而不是靠猜。
情节体检:不把正文喂给模型
「📈 情节」页的结论全部来自本地字符串统计,一页正文都不会进模型上下文:
| 检查 | 判据 | 说话方式 |
|---|---|---|
| 中段塌陷 / 全程平缓 | 连续 ≥3 章张力 ≤2(且后面有更高的章) | 给章号区间 + 可执行建议 |
| 大纲偏移 / 大纲缺失 | 大纲节点关键词与该章剧情总结的覆盖率低于阈值 | 压到 0.2 阈值,宁少报;拿不准降级为 info 并写"需人工确认" |
| 伏笔超期 | 大纲里「埋」了但到最后一章还没「收」,且已欠 ≥5 章 | 给出埋的章号与欠的章数 |
| 字数失衡 / 事件密度异常 | 与该书平均字数、章节剧情点条数对比 | info 级,列出章号 |
| 评分下滑 | 连续 ≥2 章下降(累计 ≥0.15),或低于均值 ≥0.8 | 样本 ` 自动产出: |
在临时目录里真装一份该版本的 dsh、按 profile 的方式挂上插件、用独立 DSH_HOME 起服务, | ||
| 断言宿主路由可用 / 客户端半区进启动清单 / 半区能下发且内容正确。 |
注:
0.1.0-rc.x、0.1.1-rc.x这几个更老的版本现在已无法从 npm 全新安装 (包元数据都能取到,但解析/拉包阶段会挂死,属上游制品问题),所以自动化矩阵的老基线 取0.1.2-rc.1;作者本机长期跑在0.1.0-rc.7(CLI)/0.1.0-rc.8(组件)上, 这一档由日常使用覆盖。
已知的版本差异(不影响本插件,但值得知道):
- 新版 web 服务默认带 token 鉴权:直接打开
http://127.0.0.1:/会 401, 要用启动日志里那条带?token=的地址;插件自己的回环路由不在此列。 - 客户端 bundle 的下发地址变了:新版是组合脚本
/plugins/??a/client.js,b/client.js&rev=…,旧版是/plugins//client.js。 插件无需关心(名册会给出地址),但自己写裸 URL 测试的人会踩到。
怎么持续跟进(自动化)
node scripts/check-dsh-compat.mjs # 默认测 npm 的 next
node scripts/check-dsh-compat.mjs alpha # 或 latest / alpha / 具体版本号
脚本会在临时目录里真装一份指定版本的 dsh、按 profile 的方式挂上本插件、用独立
DSH_HOME 起服务,然后断言三件事:宿主路由可用、客户端半区进入启动清单、半区能下发且内容正确。
跑完自动清理,不影响你本机正在用的实例(CI 里由 .github/workflows/compat.yml 每周一跑
latest / next / alpha / 0.1.0-rc.7 四档)。
版本策略:一条代码线,不拆分支
我们不为不同 dsh 版本维护不同的插件版本。理由:实测下来需要的 API 面在 0.1.0-rc.7 与 0.1.6-alpha.1 之间没变,拆分支只会让用户不知道该装哪个。做法是:
- 服务一律按需获取 + 特性探测(
ctx.get()拿可选项、ctx.inject()等就绪、缺了就降级); - 出问题优先补兼容代码而不是发兼容版本;
- 只有当某个版本真的移除了关键能力、无法共存时,才会另发一条 legacy 线并在这里写明
(npm dist-tag 可以同时挂
latest与legacy,用户装哪个都明确)。
踩过的两个版本坑(写给插件作者)
- 行的
apply可能早于服务挂载:在较新的 dsh 里,同步apply中ctx.get('webServer')会拿到undefined(实测 0.1.6-alpha.1)。本插件因此改成ctx.inject(['settings', 'webServer'], …)等两个服务都就绪,并带超时兜底—— 写成"直接 get、拿不到就 return"的插件会在新版本里静默什么都不做。 - 客户端 bundle 的下发地址变了:新版是组合脚本
/plugins/??a/client.js,b/client.js&rev=…,旧版是/plugins//client.js。 插件本身不用关心(名册给出地址),但拿裸 URL 做测试的人会以为"插件没被收录"。
宿主半区对 @deepseek-ai/dsh-llm 采用惰性加载:万一将来某个版本改了它的包名或导出,
插件其余功能(标注、证据摘录、档案)照常可用,只有「提炼规律」这一步会给出明确提示。
三层产物:标注 / 证据摘录 / 规律档案
这是本插件的核心约定——进写稿上下文的只有规律(三处按钮级例外见上方"口径说明"):
文件(都在 /.dsh-novel-craft/) | 给谁看 | 会不会进上下文 |
|---|---|---|
marks.json | 工作台(机器可读的好/坏标注) | ❌ 写稿永不读;只有点「提炼」时以编号形式进一次辅助调用 |
证据摘录.md | 人回查 + 提炼的输入(原文) | ❌ 写稿永不读;只有点「提炼」「补来源」时进那一次辅助调用 |
作者偏好档案.md | 写稿会话只读这一份(只有规律) | ✅ 就它 |
# 作者偏好档案
> 这里只放能复用的写作规律,不放原文摘录——写稿时读这一份就够。
> 原文证据在 `证据摘录.md`(给人回查、给提炼当输入),不要读进写稿上下文。
## 已验证偏好(作者喜欢什么)
- 危险场面只写结果落到谁身上,不写招式过程
- 用一句干巴巴的话接住重击,不铺排情绪
## 避免的写法(作者不喜欢什么)
- 别堆叠"宛如/像是"式的比喻
- 别让角色用解释性台词交代设定
← 工作台维护的账目,每次提炼重写
- 标注:30 段(👍 15 / 👎 15),覆盖 3 篇
- 待提炼:0 段
- 最近提炼:2026-09-16 17:02 · deepseek-official/deepseek-v4-flash
规律是怎么来的:一次便宜的辅助调用
- 作者在「抽卡」页点好/坏 → 落
marks.json; - 「📄 更新证据摘录」把原文收进
证据摘录.md(也可由提炼自动触发); - 「⚗️ 提炼规律」把待提炼的原文(默认最多 40 段、每段 240 字、总 20KB)
交给一次辅助模型调用,系统提示强制它只输出
+ 规律/- 禁忌,禁止抄原文; - 结果不直接进档案,而是列出来让作者逐条勾选,「采纳选中」才写进规律区, 并把提炼水位推到这批标注(下次不会再送一遍)。
- 用哪条模型路由:默认取 dsh 当前的默认模型;想指定便宜档位就在设置里填
dsh-novel-craft的distillProvider/distillModel。 - 不想让任何模型碰原文?跳过第 3 步,把
证据摘录.md交给你的 agent 也可以—— 反正进档案的只有规律。 - 没有规律区内容的档案会显示空态引导;作者手写的任何内容都会被完整渲染、不会被隐藏或覆盖。
- 自动块(begin / end 之间)每次提炼重写;规律区是作者的,永远不动。
宿主半区 HTTP API(回环限定)
全部在 /novel-craft/api/ 下,只服务 127.0.0.1,enabled: false 时整体 503:
| 方法 | 路径 | 作用 |
|---|---|---|
| GET | state | 候选稿段落 + 标注 + 规律档案 + 证据摘录情况 + 待提炼数 + 模型路由 |
| POST | marks | 存某篇某段的 好/坏/取消 |
| POST | evidence | 原文收录进「证据摘录.md」,档案只刷新自动块 |
| POST | distill | 待提炼原文 → 一次辅助模型调用 → 规律条目(待采纳) |
| POST | rules | 采纳规律写进档案(幂等),水位推到这批标注 |
| POST | profile | 保存界面上改过的档案正文 |
| GET | evidence-text | 读证据摘录正文(展开看原文时才拉) |
| GET | discover | 推荐目录(含篇数、相对路径;5 秒短缓存) |
| POST | annotate | 给一批目录回候选稿篇数,供浏览列表打徽标 |
| GET/POST | project | 认出/确认作品根(set / clear / detect),检测结果缓存 30 秒 |
| GET | workspace | 章节看板:每章字数/轮次/标注/批注/写作包/评分/张力/体检问题 + 阶段进度(不带正文) |
| POST | chapter | 单章详情:正文段落 + 批注 + 本章设定 + 写作包情况 + 待采纳微调 |
| POST | setup | 读/写「本章设定」(写作包的第 ① 节) |
| POST | annotation | 批注加/改/删(同段同类型重复提交=更新,不堆两条) |
| POST | pack | 生成写作包(save:false 只预览不落盘),回逐节字数与预算表 |
| POST | revise | 按批注生成微调稿(只改被批注的段落 + 逐字核对 + 落盘前后对照) |
| POST | revise-apply | 采纳写回正文(先备份原稿、批注标为已处理、核对不过就拒绝) |
| POST | tension | 手工标本章张力(1–5,0 表示取消) |
| POST | check | 账目体检 + 情节体检(refresh:true 强制重算,否则复用 10 分钟缓存) |
| POST | stage | 阶段状态 / 设为当前 / 起草(模型)/ 写入产物 |
| GET/POST | rule-evidence | 规律来源表;action:'backfill' 补齐老档案的来源(有模型路由时走模型判断) |
| POST | newchapter | 开新章:status 现状与建议章号 / directions 要方向 / draft 写第 i 篇候选 / merge 合并选中段落 / finalize 写入正文(原稿先备份) |
改代码后怎么生效
- 浏览器半区
lib/client.js:dsh 按请求现读文件下发(cache-control: no-cache), 刷新页面即生效,不必重启。 - 宿主半区
lib/index.js:在 dsh 启动时装载,必须重启 dsh 才生效。 客户端对"宿主半区还没有新接口"做了降级:discover拿到非 JSON(SPA 的 HTML) 一律当空数组,推荐区显示"自己挑",不会卡在转圈。
测试
都不需要浏览器、也不需要起 dsh:假 ctx 挂上真插件,真跑一遍。
npm install # 开发依赖 + peer 依赖(部署里这些 peer 由 profile 提供)
npm test # 7 个文件、580+ 断言,全绿才放行
跑单个文件:node test/workbench.test.mjs(0.2 新增能力)、node test/plot.test.mjs、
node test/ledger.test.mjs、node test/pipeline.test.mjs。
刚 clone 下来还没装依赖时不会甩一堆报错:静态守卫照跑,其他会打印「跳过:先 npm install」并以 0 退出。
host-api.test.mjs:把插件注册的路由放到真实回环端口上打,覆盖标注闭环、 推荐目录、篇数徽标、档案三层产物、旧档案迁移、提炼链路(用假 LLM 验输入边界与 输出解析)、未配置/停用等分支。client-render.test.mjs:借成对的 react / react-dom 把client.js的组件 真渲染成 HTML,断言选择器四个入口、隐藏目录过滤、面包屑省略、档案面板各分支; 纯逻辑(路径缩写、最近使用去重、discover 降级)走exports.__internals断言。 档案面板用例是"真宿主生成档案 → 真渲染",不是替身数据。workbench.test.mjs(0.2):三层都打——纯函数(作品识别、批注核对、写作包组装)、 宿主接口(看板/单章/批注/写作包/张力/体检/阶段/批注式微调端到端)、 客户端面板(看板、情节、阶段、批注、微调、写作包按 props 真渲染)。 微调用例会断言"只有被批注的那一段变了,其余段落逐字相同",并验原稿备份与拒绝重复采纳。ledger.test.mjs/plot.test.mjs/pipeline.test.mjs:账目体检、情节诊断、阶段门禁的 纯函数单测(各 100+ 断言),都带"本机有真实作品就读一遍、没有就跳过"的只读用例。
两个测试都不依赖任何本机路径:作品目录由 test/fixtures.mjs 临时造(含候选稿、
归档目录等),写操作全在临时目录里,跑完就删。找不到成对的 react 时渲染用例会打印
「跳过」并以 0 退出,不给别人一堆看不懂的红;npm install 装上 devDependencies
(或用 DSH_REACT_ROOT=)即可跑全。
目录结构
dsh-novel-craft-plugin/
├── package.json # dsh.bundle.patch + dsh.client.platform=web(插件与 skill 的分水岭)
├── cordis.patch.yml # 安装时把插件行插进 profile 组合
├── lib/index.js # 宿主半区:settings + 回环 HTTP API + 提炼/微调/起草调用
├── lib/client.js # 浏览器半区:★ 工作台本体(抽卡 / 章节 / 情节 /