← dsh-novel-writer 技术文档 / novel_settings

novel_settings

设定管理。用 category 选定五张设定表之一(人物 / 地点 / 道具 / 时间线 / 世界观用语规范),以 action 执行增删改查;scan 从全书正文里按类别正则提取候选条目名,detect 用词表统计自动判定作品的文化基准(西方欧式 / 东方中式 / 现代都市 / 混合)。全部为本地规则统计与字符串写入,单部作品一份 JSON,五张表同文件存放。v6.5.0 起 worldview 条目还可用 allowWords 做本书范围内的逐词静音(「这个词在本书里是对的」)。

参数

参数类型必填说明
bookstring是书名,即 novels/ 下的子目录名。经 sanitizeSegment 过滤。
categorystring否枚举 character / location / item / timeline / worldview。非枚举取值(含省略)一律按 character 处理;action:"detect" 时被强制为 worldview。
actionstring否枚举 list / add / update / delete / scan / detect。非枚举取值按 list 处理。
namestring否add / update / delete 的条目名;timeline 可用 day 代替。timeline 条目登记时 name 与 day 同值。
descriptionstring否通用描述字段。
traitsstring否character 专用:性格 / 外貌特征。
relationshipsstring否character 专用:人际关系。
aliasstring[]否character 专用:别名。
firstSeenstring否首次出现的章节。
owner / status / lastSeenstring否item 专用:当前持有者 / 状态 / 最近出现章节。
daystring否timeline 专用:时间点标识。add 时作为条目名登记;update / delete 用 name 定位旧标识,改名时 name 传旧值并同时传 day 新值。
event / chapterstring否timeline 专用:事件与对应章节。
notesstring否备注。
basisstring否worldview 专用:判断依据 / 文化基准说明,同时参与语用规范的自动推导。
ritualstring否worldview 专用:仪式规范(如「点烛不烧香」)。
speechStyleobject否worldview 专用:说话方式规范。可含 title / tone / honorBad / honorGood / ritualBadPatterns / ritualGoodNote。
bannedWordsstring[]否worldview 专用:禁用词表。
allowWordsstring[]否worldview 专用(v6.5.0):本书用词豁免(逐词静音)——登记后这些词在本书不再报用语冲突,词仍留在词表里,别的书照常检查。值会净化:去首尾空白、丢弃空串、按词去重、单条截 40 字、最多 200 条。示例与语义见下文「逐词静音:worldview.allowWords」。
recommendedobject否worldview 专用:替代词映射,如 { "上香": "点烛" }。
rootstring否章节库根目录。

action 六个取值的语义:list 查看(默认);add 登记;update 修改;delete 删除;scan 扫描章节提取候选条目名;detect 自动判断世界观文化基准(worldview 专用,会忽略传入的 category)。

输出结构

契约字段(required: ["book","category","action"])恒返;其余字段按动作出现,形态并不统一。

动作返回的字段说明
listtotal + characters locations items timeline worldview五张表全量返回,total 为五表条目数之和;无 message
add该类别对应的表 + message只含新增的那一条;表键由 category 决定
update该类别对应的表 + message只含更新后的那一条
deletetotal + 五张表 + message删除分支不提前返回,落到与 list 相同的收尾块,返回全表
scancandidates + 五张表 + message候选为字符串数组;不含 total
detectculture / confidence / scores / evidence / total / worldview / message,条件性带 genre / theme / vibe文化基准判定结果 + 世界观表
字段类型内容
characters / locations / items / timeline / worldviewobject[]五张表的条目数组,条目结构见下
totalinteger五张表条目数之和
candidatesstring[]形如「琉璃(7次)」,已过滤已登记名,最多 15 条
culturestringwestern / eastern / modern / mixed / unknown
confidencenumber0–0.95,保留两位小数
scoresobject{ western, eastern, modern } 命中计数
evidenceobject按文化分组的证据串数组,如 ["教堂×3"],每组最多 8 条
messagestring结果文案;detect 下形如「自动判断:东方/中式古代(置信度 82%)」
genre / themeobjectdetect 且 genreTheme 功能开关开启时出现
vibeobjectdetect 下的氛围光谱(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表键渲染标签专属字段
charactercharacters人物卡traits relationships alias
locationlocations地点卡description firstSeen
itemitems道具清单owner status lastSeen
timelinetimeline时间线day event chapter(day 与 name 同值)
worldviewworldview世界观/用语规范basis ritual speechStyle bannedWords allowWords(v6.5.0)recommended

字段白名单不按类别强制:normalizeSettingEntry 对任意类别都保留全部已知字段,跨类别传字段不会被拒绝,只会原样落盘。字段值统一经 String() 强转(name 缺省写成空串)。

add 的写入与 worldview 语用规范自动推导

name 取值优先级为 args.name → args.day,二者都空则报错。随后按固定键序把已在参数里出现的标量字段 String() 写入;alias / bannedWords 仅在是数组时写入;recommended / speechStyle 仅在是非 null 对象时写入。类别为 timeline 时额外写入 day = name。

category:"worldview" 且未显式传入 speechStyle 时,插件按 basis 文本自动推导语用规范,判定顺序为:

① eastHint = /中式|东方|中原|中土|古装|古风|客栈|老爷|少侠|江湖|茶楼|媒婆|太监|衙门|香客/.test(basis) ② westHint = /west|西方|欧式|西式|教廷|教堂|神甫|公爵|骑士|城堡|女巫|庄园|圣器|牧师|弥撒/.test(basis) ③ detected = eastHint ? {culture:"eastern"} : westHint ? {culture:"western"} : detectCulture(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 的合并语义

  • 可改字段:description traits relationships firstSeen owner status lastSeen event chapter notes basis ritual day(以及下方的数组 / 对象字段)。allowWords 不在其中:豁免表只在 add 时收下,要改请 delete 后重新 add,或直接改设定文件。
  • 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 的候选提取

先拼读全书章节正文,再用类别专属正则提取候选,最后统一过滤与截断:

保留条件:count >= 2 且 该名不在本类别已登记条目名集合中 输出格式:"名字(N次)",slice(0, 15)
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 按词长降序、用长度等于文本长度的占用位图消费命中区间:同表重复词不双计,同一位置的长词命中后其子串不再计数(「高潮迭起」命中则「高潮」不重复计)。

total = western + eastern + modern total = 0 → culture = "unknown",confidence = 0 modern >= 5 且 modern >= max(western, eastern, 1) → culture = "modern" 否则 western + eastern > 0: ratio = western / max(eastern, 1) ratio >= 2 → culture = "western" ratio <= 0.5 → culture = "eastern" 否则 → culture = "mixed",confidence = 0.5 命中分支的 confidence = min(0.95, 0.5 + 该文化得分 / (total × 2)) 返回前四舍五入到两位小数

modern 优先是为避免剧情里的西式道具词把现代都市题材误判为西式。detect 分支还会读取功能开关:genreTheme 开启时附带 genre(流派)与 theme(题材)检测结果;webnovelVibe 开启时把正文交给氛围光谱聚合,失败则降级为 { axes: [], top: [], conclusion: "气质聚合失败: …", confidence: 0, evidence: [] },不影响文化基准结论。

默认用语规范(欧式基准)与中式标记词接线

未登记 worldview、或登记文本判不出文化基准时,欧式基准使用 DEFAULT_BANNED_WORDS(lib/lexicons/markers.js)。v6.4.0 起该表改为「七类分组 + 派生的扁平并集」两级结构。起因:用户在欧式中世纪设定下仍被写出「掌柜」「多谢提点」这类中式市井/客套词,而旧表 42 词偏「仙侠/官制」(老夫、衙门、科举、俸禄、绣楼…),生活化桥段里的市井词(客官、店小二、跑堂、铜板、干粮…)一个都没有,且 42 词里只有 16 条配了替代词——命中后只能报「不符」却给不出改法。

字段类型含义
groupsRecord<string, string[]>v6.4.0 新增:按「称谓 / 客套 / 仪式 / 器物 / 市井 / 计量 / 制度」分组,供后续按类成套启用/裁剪与渲染时的分组紧凑展示。分组口径是「成套裁剪」而非语言学分类——客官 / 小二 / 跑堂 / 打尖 / 店钱 / 赏钱 / 天字号同属客栈市井场景,一刀裁掉就把这一整片渗透词清空(那正是本 bug 的现场)。
groupNotesRecord<string, string>v6.4.0 新增:每组一句话写清「为什么这一组在欧式背景下成套突兀」,供文档 / 渲染解释分类依据,不参与扫描。
bannedWordsstring[]全部词条的扁平并集(v6.4.0 起由 groups 派生:Object.values(groups).flat(),单一事实源,从结构上排除「改了分组忘了改并集」的漂移)。字段名与类型未变——resolveStyleRule 仍直接读该字段。实测 69 词(旧表 42 + 新增 27,无重复)。
recommendedRecord<string, string>替代词。v6.4.0 补齐:实测 67 / 69 条有替代词(旧表仅 16 条);「江湖」「武林」在欧式里没有对等词,刻意不给,避免编出误导性的替换建议。全部键都落在 bannedWords 内。
culturestring默认表的文化名(渲染用),未改。
groups(v6.4.0):7 类 / 69 词 称谓 11:老夫 丫鬟 婶子 伙计 掌柜 郎中 家丁 门房 官差 账房 大侠 客套 5:奉茶 有劳 劳烦 失敬 见笑 仪式 10:上香 三炷香 香炉 烧香 磕头 祠堂 和尚 道士 菩萨 佛祖 器物 11:布鞋 糖人 菜篮 青石板 八仙桌 轿子 花轿 府邸 火折子 包袱 干粮 市井 14:客栈 厢房 幌子 油条 绣楼 江湖 武林 客官 小二 跑堂 打尖 店钱 赏钱 天字号 计量 6:时辰 银两 银子 碎银 铜板 铜钱 制度 12:衙门 科举 俸禄 里正 保长 捕快 皂隶 私塾 秀才 员外 更夫 钱庄 bannedWords = Object.values(groups).flat() → 69 词(= 各组词数之和,无遗漏无重复)

加词纪律:只收「读者一眼能看出文化 / 时代错位」的中式词。不收通用词(先生 / 大人);不收西式译法里同样常用的词(夫人 / 老爷);不收歧义词(老板娘:兼指现代「老板」之妻;上房:兼指正房 / 上屋顶);不收与既有词构成包含关系的词(店小二 含 小二、掌柜的 含 掌柜、跑江湖 含 江湖、两银子 含 银子——扫描是逐词 text.includes() 判定,收了只会让同一次命中报出两条候选)。

同时新增导出 EASTERN_MARKER_BANLIST(纯数据,不改 lib/index.js):把 CULTURE_MARKERS.eastern.words 里可作禁词用的子集标出来,供审计侧在欧式 / 现代基准下提示「这是中式标记词,应替换」。起因:eastern.words 本来就有「掌柜」(还有伙计 / 客栈 / 银两 / 衙门 / 郎中 / 幌子 / 青石板),但那条路只服务 detectCulture,与禁词扫描完全没有打通——两表其实同源(旧默认表恰是 eastern 词去掉 庙 / 老爷 / 夫人),却长期各自维护。

字段类型含义
wordsstring[]可直接当禁词用的子集,由原表派生(恒为 eastern.words 的子集,将来往 eastern 表加词会自动进入,除非同时登记进 excluded)。实测 42 词。
excluded{ word, reason }[]不宜直接当禁词用的词与理由:老爷 / 夫人(西式译法 my lord / my lady 的常见中译,作禁词会把合法译文也判成冲突)、庙(单字词,替换建议落不到实处)。实测 3 条:45 − 3 = 42。
label / source / notestring渲染与溯源用的说明文本(source 恒为 CULTURE_MARKERS.eastern.words)。
为什么不把 eastern 词直接并进默认禁词表?两个方向的禁词表语义相反:欧式基准禁的是「中式词」,中式基准禁的是「西式词」(SPEECH_STYLE_RULES.eastern.honorBad)。把中式标记词并进默认表,会让中式基准的书反过来被自己的标记词误伤;只在欧式 / 现代基准下按需读取这份子集才是安全接线。接线由协调方在审计侧完成——本版只加数据与分组。

逐词静音:worldview.allowWords(v6.5.0,仅本书生效)

词表是「猜」这本书的。有些词在这一本书里就是对的:修仙小说里的「修士」=修行者、中文历史小说里真有一座「教堂」。此前作者没有任何办法告诉插件「这个我知道」——只能每次手动忽略(下次还报)、或关掉整个用语检查(别的词也不查了)、或把词从表里删掉(所有书都不查了,影响面过大)。allowWords 就是补上这条路的:把词登记成本书豁免,插件的用语/语用扫描不再报它。

allowWords: string[] // worldview 条目字段;多个条目的值取并集 解析端:resolveStyleRule(settings, chapterTexts, opts) allow = 并集(opts.allowWords, 全部 worldview 条目的 allowWords) bannedWords ← 过滤掉 allow 里的词 speechStyle.honorBad ← 同样过滤 回传: allowedWords: string[] // 实际生效的静音词 sources.muted: number // 被静音掉的词数(= 合并总数 − 生效总数)

为什么只做「按书」、不做全局静音。全局静音会静默削弱所有书的覆盖:一个词的豁免被记到全局后,另一本书里它本该报出来也不再报,而人很难记得自己什么时候加过什么、加过几本书。按书静音的最坏结果只是「这本书里漏」,看得见、摸得着,换本书立刻恢复默认覆盖。同理,静音按词做等值匹配,因此它不覆盖 speechStyle.ritualBadPatterns——那是正则而不是词,要改模式或删条目。

登记示例(name 只是条目名,可自取;该条目不含文化标记词,因此不会影响 hintCulture 的基准判定。只想静音、不想改文化基准的书,加这一条即可——没有登记过 worldview 的书同样可用):

novel_settings add category=worldview name="用词豁免" allowWords=["修士"] 等价地,也可直接写设定文件 <root>/.novel-writer/settings/<book>.json: "worldview": [ { "name": "用词豁免", "allowWords": ["修士"] } ]
登记与核对(两处边界)。登记走 add,字段值会被净化:去掉首尾空白、丢弃空串与非字符串项、按词去重、单条截到 40 字、最多 200 条(豁免表是给人看的,不该无限长)。① update 通道不支持该字段——要改豁免表,请 delete 后重新 add,或直接改设定文件里的对应条目。② 工具返回体里看不到 allowWords:list / scan / detect / add 的条目都经 normalizeSettingEntry 归一,而它的白名单还没有这个字段(写盘是完整的,resolveStyleRule 读的是文件里的原始字段,静音照常生效)。核对豁免表请直接看设定文件,或看改稿台 summary.notes 里的「已按本书用词豁免…静音 N 个词;豁免表:…」。
静音为什么放在 resolveStyleRule 这一处。开写包(lib/brief.js)/ 连贯性审计(lib/index.js)/ 改稿台(lib/fixplan.js)三个消费方都从这里取词表,过滤放在单一裁决点,结构上不会出现「审计静音了、开写包还在警告」的分裂。

约束与边界

  • 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 也被视为未提供。allowWords:"修士"(字符串)同样不写入——它先过数组判定再净化,净化后为空就不产生该字段。
  • 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。
  • 逐词静音的覆盖范围有限,且只作用于本书:allowWords 按词过滤 bannedWords 与 speechStyle.honorBad 两处,不覆盖 ritualBadPatterns(正则)与 title 称谓规范;也不提供全局静音(全局静音会静默削弱所有书的覆盖,而人很难记得自己加过什么)。生效结果在解析端的 allowedWords 与 sources.muted 里,供调用方说明「为什么少报了几条」。
  • 参数表禁止额外字段:parameters.additionalProperties = false。

相关工具

  • novel_lexicon —— 必用词表(「必须写什么」)。与本工具 worldview 的分工:本工具的 bannedWords / speechStyle 管「不要写什么」(禁词与语用规范,为文化基准一致服务),词表管「必须写什么」(优先词,为模仿作者的用词习惯服务);两者互补、不重复——同一个概念的两面可以分别登记(禁「铜灯」+ 必用「青铜灯」)。
  • 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(v6.4.0 起为分组 + 派生并集)/ EASTERN_MARKER_BANLIST(v6.4.0 新增,中式标记词的可作禁词子集,纯数据)(全文件 240 行,末尾统一导出)。
  • lib/core.js —— withFileTx(约 969 行)与 atomicWriteJson(约 942 行)。
  • mcp/server.mjs —— buildArgs / isInsideRoot(约 259–289 行)。