novel_settings
设定管理。用 category 选定五张设定表之一(人物 / 地点 / 道具 / 时间线 / 世界观用语规范),以 action 执行增删改查;scan 从全书正文里按类别正则提取候选条目名,detect 用词表统计自动判定作品的文化基准(西方欧式 / 东方中式 / 现代都市 / 混合)。全部为本地规则统计与字符串写入,单部作品一份 JSON,五张表同文件存放。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
book | string | 是 | 书名,即 novels/ 下的子目录名。经 sanitizeSegment 过滤。 |
category | string | 否 | 枚举 character / location / item / timeline / worldview。非枚举取值(含省略)一律按 character 处理;action:"detect" 时被强制为 worldview。 |
action | string | 否 | 枚举 list / add / update / delete / scan / detect。非枚举取值按 list 处理。 |
name | string | 否 | add / update / delete 的条目名;timeline 可用 day 代替。timeline 条目登记时 name 与 day 同值。 |
description | string | 否 | 通用描述字段。 |
traits | string | 否 | character 专用:性格 / 外貌特征。 |
relationships | string | 否 | character 专用:人际关系。 |
alias | string[] | 否 | character 专用:别名。 |
firstSeen | string | 否 | 首次出现的章节。 |
owner / status / lastSeen | string | 否 | item 专用:当前持有者 / 状态 / 最近出现章节。 |
day | string | 否 | timeline 专用:时间点标识。add 时作为条目名登记;update / delete 用 name 定位旧标识,改名时 name 传旧值并同时传 day 新值。 |
event / chapter | string | 否 | timeline 专用:事件与对应章节。 |
notes | string | 否 | 备注。 |
basis | string | 否 | worldview 专用:判断依据 / 文化基准说明,同时参与语用规范的自动推导。 |
ritual | string | 否 | worldview 专用:仪式规范(如「点烛不烧香」)。 |
speechStyle | object | 否 | worldview 专用:说话方式规范。可含 title / tone / honorBad / honorGood / ritualBadPatterns / ritualGoodNote。 |
bannedWords | string[] | 否 | worldview 专用:禁用词表。 |
recommended | object | 否 | worldview 专用:替代词映射,如 { "上香": "点烛" }。 |
root | string | 否 | 章节库根目录。 |
action 六个取值的语义:list 查看(默认);add 登记;update 修改;delete 删除;scan 扫描章节提取候选条目名;detect 自动判断世界观文化基准(worldview 专用,会忽略传入的 category)。
输出结构
契约字段(required: ["book","category","action"])恒返;其余字段按动作出现,形态并不统一。
| 动作 | 返回的字段 | 说明 |
|---|---|---|
list | total + characters locations items timeline worldview | 五张表全量返回,total 为五表条目数之和;无 message |
add | 该类别对应的表 + message | 只含新增的那一条;表键由 category 决定 |
update | 该类别对应的表 + message | 只含更新后的那一条 |
delete | total + 五张表 + message | 删除分支不提前返回,落到与 list 相同的收尾块,返回全表 |
scan | candidates + 五张表 + message | 候选为字符串数组;不含 total |
detect | culture / confidence / scores / evidence / total / worldview / message,条件性带 genre / theme / vibe | 文化基准判定结果 + 世界观表 |
| 字段 | 类型 | 内容 |
|---|---|---|
characters / locations / items / timeline / worldview | object[] | 五张表的条目数组,条目结构见下 |
total | integer | 五张表条目数之和 |
candidates | string[] | 形如「琉璃(7次)」,已过滤已登记名,最多 15 条 |
culture | string | western / eastern / modern / mixed / unknown |
confidence | number | 0–0.95,保留两位小数 |
scores | object | { western, eastern, modern } 命中计数 |
evidence | object | 按文化分组的证据串数组,如 ["教堂×3"],每组最多 8 条 |
message | string | 结果文案;detect 下形如「自动判断:东方/中式古代(置信度 82%)」 |
genre / theme | object | detect 且 genreTheme 功能开关开启时出现 |
vibe | object | detect 下的氛围光谱(axes / top / conclusion / confidence / evidence) |
category 对应的那一张表。返回体的 JSON 里 list / scan / delete 都带五张表,但 output.render 按 value.category 取表:省略 category 时默认值为 character,MCP 客户端看到的文本就只含「人物卡(N 条)」。需要看其余四张表时必须显式传对应 category,或直接消费 JSON 返回体。
数据模型与落盘
{
"book": "雨夜灯",
"characters": [
{ "name": "沈砚", "traits": "左手旧伤,雨天隐痛", "relationships": "沈家次子",
"alias": ["阿砚"], "firstSeen": "第01章", "notes": "随身带半枚铜钱" }
],
"locations": [
{ "name": "西市当铺", "description": "砖墙剥落,柜台高于人肩", "firstSeen": "第07章" }
],
"items": [
{ "name": "半枚铜钱", "owner": "沈砚", "status": "在沈砚处", "lastSeen": "第19章" }
],
"timeline": [
{ "name": "第1天", "day": "第1天", "event": "沈砚返乡", "chapter": "第01章" }
],
"worldview": [
{ "name": "东方/中式古代", "basis": "全篇出现客栈、掌柜、时辰",
"ritual": "中式宗教仪式=焚香/上香/跪拜;禁点烛/弥撒/礼拜",
"bannedWords": ["教堂", "神甫"],
"recommended": { "教堂": "庙/寺" },
"speechStyle": { "title": "中式称谓:老爷/夫人/姑娘/小姐/官人可用;禁 Miss/Mr.+名式的西式称呼",
"tone": "中式对话:可文言客套(老夫/在下/承蒙/赐教/小姐);禁西式口语(Ah/well/我的上帝)",
"honorBad": ["Miss", "先生", "阁下", "Thank you", "if you please"],
"honorGood": { "先生": "老爷/相公" },
"ritualBadPatterns": ["点烛", "蜡烛", "礼拜", "祈祷跪下?", "light candle"],
"ritualGoodNote": "中式宗教仪式=焚香/上香/跪拜;禁点烛/弥撒/礼拜" } }
]
}
字段结构取自 normalizeSettingEntry 的白名单;上例数值与文本为示意。
落盘位置
<root>/.novel-writer/settings/<book>.json,写入时固定为 { book, characters, locations, items, timeline, worldview }——五张表在同一份文件里。读取时逐表做 Array.isArray 校验,缺失或类型不对的表回落为空数组;文件不存在或 JSON 损坏时五表全空,不抛错。
五张表与 listKey 映射
category | 表键 | 渲染标签 | 专属字段 |
|---|---|---|---|
character | characters | 人物卡 | traits relationships alias |
location | locations | 地点卡 | description firstSeen |
item | items | 道具清单 | owner status lastSeen |
timeline | timeline | 时间线 | day event chapter(day 与 name 同值) |
worldview | worldview | 世界观/用语规范 | basis ritual speechStyle bannedWords recommended |
字段白名单不按类别强制:normalizeSettingEntry 对任意类别都保留全部已知字段,跨类别传字段不会被拒绝,只会原样落盘。字段值统一经 String() 强转(name 缺省写成空串)。
add 的写入与 worldview 语用规范自动推导
name 取值优先级为 args.name → args.day,二者都空则报错。随后按固定键序把已在参数里出现的标量字段 String() 写入;alias / bannedWords 仅在是数组时写入;recommended / speechStyle 仅在是非 null 对象时写入。类别为 timeline 时额外写入 day = name。
category:"worldview" 且未显式传入 speechStyle 时,插件按 basis 文本自动推导语用规范,判定顺序为:
判定为 eastern 或 western 时,从 SPEECH_STYLE_RULES 取 titleGuideline / honorBad / honorGood / ritualBadPatterns(正则对象的 source 字符串)/ ritualGoodNote / toneGuideline 六项写入 speechStyle,并在调用方未给 ritual 时用 ritualGoodNote 兜底。判定结果为 modern / mixed / unknown 时不写 speechStyle,也不写 ritual。
eastern 并写入 speechStyle + ritual,第二块又按「欧/教堂」覆盖为 western,而 ritual 已落定 eastern 值,两个字段来源分裂。现版本三项提示(eastHint → westHint → 词表检测)互斥,只赋值一次。
update 的合并语义
- 可改字段:
descriptiontraitsrelationshipsfirstSeenownerstatuslastSeeneventchapternotesbasisritualday(以及下方的数组 / 对象字段)。 name不是可改字段,只用于定位。改名只能走timeline的day通道。alias/bannedWords整体替换;recommended整体替换;speechStyle与已有对象浅合并({ ...old, ...new }),未提及的子键保留。timeline的day变更时同步name:原name等于旧day则跟随新值;name为空则填新值;其余情况保持不动,避免身份字段分裂成{ name:"第1天", day:"第2天" }。- 定位规则:先按
name全等匹配;timeline类别下额外按day全等匹配(双查)。找不到即报错,并提示timeline需用旧day定位。
scan 的候选提取
先拼读全书章节正文,再用类别专属正则提取候选,最后统一过滤与截断:
category | 提取正则(示意形式) | 附加过滤 |
|---|---|---|
character | ([\u4e00-\u9fff]{2,3}?)(?:说|道|问|喊|叫|笑|叹|点头|摇头) | 丢弃以「他她我你它又再还这那谁」开头的命中;丢弃「那个/这个/什么/怎么/自己/她们/他们/你们/我们」 |
location | 「回到/来到/走进/离开/路过/奔向/赶赴/躲进/藏进/出了/进了」+ 2–5 个汉字,且后随 [的里中前后处。,,!?!?] 或行尾 | 丢弃「自己/他们/她们/我们/你们/这个/那个/什么」起头的命中 |
item | 「一把/一柄/一顶/一枚/一件/一块/一条/一盏/一封/一叠/几件/抓起/拿起/握紧/掏出/收好/放下」+ 2–6 个汉字 | 丢弃「自己/他们/她们/我们/你们/这个/那个/什么/一点」起头的命中 |
timeline / worldview | 不适用「正文高频词」式扫描 | candidates 恒为空数组,message 改为指引用 add / detect |
人物正则的量词是懒惰的({2,3}?),「琉璃说道」不会被吃成「琉璃说」。known 集合只取当前 category 那张表的条目名——跨表同名不会阻止候选出现。
detect 的文化基准判定
先拼读全书正文,再对三张文化词表(CULTURE_MARKERS 的 western / eastern / modern)做一次扫描。扫描器 scanWordHits 按词长降序、用长度等于文本长度的占用位图消费命中区间:同表重复词不双计,同一位置的长词命中后其子串不再计数(「高潮迭起」命中则「高潮」不重复计)。
modern 优先是为避免剧情里的西式道具词把现代都市题材误判为西式。detect 分支还会读取功能开关:genreTheme 开启时附带 genre(流派)与 theme(题材)检测结果;webnovelVibe 开启时把正文交给氛围光谱聚合,失败则降级为 { axes: [], top: [], conclusion: "气质聚合失败: …", confidence: 0, evidence: [] },不影响文化基准结论。
约束与边界
- book 是唯一必填项(
required: ["book"])。category与action都有隐式默认值:分别是character与list,且非枚举取值静默回退而不报错。传category:"person"不会失败,而是操作人物表。 detect覆盖category:返回体的category恒为worldview,与传入值无关。- 同名条目不查重:
add只做list.push,没有名字去重;重复登记会产生两条同name的记录,后续update/delete只命中第一条(findIndex取首次匹配)。 add/update/delete的定位串是精确全等,不做去空格以外的归一:手写「沈 砚」与「沈砚」是两个条目。- 非数组的数组字段被静默忽略:
alias:"阿砚"(字符串)不会写入,也不会报错。同理recommended/speechStyle传字符串时按「未提供」处理,worldview的自动推导分支照常执行;显式传null也被视为未提供。 scan有硬阈值与硬上限:命中少于 2 次的名字不出现;最多返回 15 条;candidates只是候选,插件不会自动登记,必须由调用方逐条add。scan读取的是全书正文:章节数多时 I/O 与内存开销随全书字数线性增长;scanChapters在作品目录缺失时抛错,空目录则返回空候选列表。detect无章节时不报错:作品目录存在但没有章节文件时,正文为空串,判定结果为culture:"unknown"/confidence:0,message为「自动判断:无法判断(词表未命中)(置信度 0%)」。目录不存在则会抛错。- score 与 confidence 不可跨版本横比:v4.3.0 清理了三张词表内部的重复词条(如「城堡」「便利店」「朋友圈」的二度出现)、v4.0.0 把扫描改成单遍占用位图,同一本书在不同版本下的
scores会不同。去重本身不改变扫描语义(位图本就不双计),但历史版本的分数不可与新版本直接对照。 - 并发是文件级的:v3.9.5 起
add/update/delete/scan/detect全部包在withFileTx内(键为 settings 文件的绝对路径),同一本书的并发写被串行化,不会基于同一旧快照互相整表覆盖。代价是不同category的并发写也会互相排队——它们共用同一份文件。 - 写盘是整文件覆盖 + 原子替换:
writeSettings走atomicWriteJson(临时文件 +rename,失败回退直写,两者都失败抛ENOVELWRITEFAIL),并在成功后把书库根记入全局状态。 - 列表动作渲染口径:
list/delete/scan的返回体都带五张表,但渲染层只打印category对应的那一张(见上文警示块)。 - 跨版本字段口径:v4.0.0 起
list的渲染按category取表(旧版??链在list/scan恒返回全部五张表时永不前进,永远只显示人物卡);同版起null的speechStyle不再被当作对象写入空壳,且「按basis自动推导语用规范」改为单一判定;脏条目(null/ 标量)在normalizeSettingEntry里被过滤,旧版会在坏数据上抛TypeError打挂整个工具。 - MCP 形态下的 root 限制:参数
root必须落在服务器启动参数--root的书库根之内,越界会被静默回退,同时记 stderr。 - 参数表禁止额外字段:
parameters.additionalProperties = false。
相关工具
- novel_continuity_check —— 消费本工具的五张表:登记人物的缺场 / 别名未用、设定表重复条目、按
worldview的bannedWords与speechStyle做的用语冲突与语用冲突扫描都在彼处实现。 - novel_plot —— 伏笔登记表,可用
relatedCharacters/locations引用本工具登记的条目名。 - novel_outline —— 创作资料(创作设定 / 人物设定 / 剧情大纲 / 钩子记录 / 创作状态卡),与本工具的五张表是两套并行的资料体系。
- novel_summary —— 章节摘要,与本工具同属「按书写入
.novel-writer/」的持久化数据。
源码位置
- lib/index.js —— 工具注册与
execute:registerNovelSettings(约 1449–1759 行),含六个动作分支、speechStyle自动推导与三套scan正则。 - lib/core.js ——
settingsFile/readSettings/writeSettings/normalizeSettingEntry/normalizeSettingList(约 1999–2054 行)。 - lib/core.js ——
detectCulture/detectGenre/detectTheme/scanWordHits/sortedMarkerWords(约 669–774 行)。文化基准判定与词表不在lib/analysis.js:判定实现在core.js,词表在lib/lexicons/markers.js。 - lib/lexicons/markers.js ——
CULTURE_MARKERS/SPEECH_STYLE_RULES/GENRE_MARKERS/THEME_MARKERS/DEFAULT_BANNED_WORDS(全文件 130 行,末尾统一导出)。 - lib/core.js ——
withFileTx(约 969 行)与atomicWriteJson(约 942 行)。 - mcp/server.mjs ——
buildArgs/isInsideRoot(约 259–289 行)。