Browse Source

feat(knowledge): integrate story object knowledge

lingfengQAQ 3 months ago
parent
commit
16acb2eea8

+ 24 - 1
.trellis/spec/backend/knowledge-runtime.md

@@ -10,7 +10,7 @@
 
 
 ```text
 ```text
 knowledge-pack --类型=<主题材> [--副题材=a,b] [--流派=a,b]
 knowledge-pack --类型=<主题材> [--副题材=a,b] [--流派=a,b]
-knowledge-query --维度=<十维之一> --问题=<真实未决问题>
+knowledge-query --维度=<十维之一> --问题=<真实未决问题> [--类型=<对象子类>]
 persist-book --file=<json> [--dir=<目录>]
 persist-book --file=<json> [--dir=<目录>]
 persist-contract --file=<json>
 persist-contract --file=<json>
 persist-design --file=<json>
 persist-design --file=<json>
@@ -28,6 +28,9 @@ finalize <章号> --payload=<json>
 - `节奏与兑现参数` 没有数值配额时不增加占位;出现比例、固定章位或单位周期内次数时,必须分别提供非空的“统计对象 / 使用目的 / 失效条件”。仅出现三个空标签仍应拒绝。
 - `节奏与兑现参数` 没有数值配额时不增加占位;出现比例、固定章位或单位周期内次数时,必须分别提供非空的“统计对象 / 使用目的 / 失效条件”。仅出现三个空标签仍应拒绝。
 - `persist-contract` 只保存作者确认的新版本;版本严格递增,生效起章不得早于下一未定稿章。
 - `persist-contract` 只保存作者确认的新版本;版本严格递增,生效起章不得早于下一未定稿章。
 - `persist-design` 写 `大纲/创作设计/设定|人物/`;对象至少含 `ID`、`名称`、`## 本书设计`、`## 一致性边界`。目录即计划状态。
 - `persist-design` 写 `大纲/创作设计/设定|人物/`;对象至少含 `ID`、`名称`、`## 本书设计`、`## 一致性边界`。目录即计划状态。
+- 故事对象通用条目只在对象设计阶段调用:设定 front matter 为 `名称 / 对象类型 / 一句话`,人物为 `名称 / 人物类别 / 一句话`,命名为 `名称 / 命名对象 / 一句话`;正文只保留非空 `## 规划时`。写作和审稿只读作者确认后的计划对象与事实,不回读通用条目。
+- `knowledge-query --类型` 在故事对象三维必须使用唯一筛选字段和受控值:设定 `对象类型=世界|规则|机制|能力|物品|组织|制度|地点`;人物 `人物类别=角色|关系`;命名 `命名对象=书名|卷名|章节名|角色|地点|组织|能力|物品|制度|术语|任务|副本|赛事|代号`。人物不得恢复没有独立命令消费者的 `关系类别`。
+- 命名知识只生成和校验名称;最终正名、别名、语系和近名检查附着于书级产物或设定/人物计划对象,不建立平行命名实体目录。普通未选候选不持久化。
 - 事实审查可在报告顶层输出 `factChanges`;`validateReviewReport`、合并和 `工作区/评审报告/事实审查.json` 必须保留该数组。finalize 在任何写盘前校验冲突、路径和 hash,并把事实写入、计划删除、章节提交纳入同一原子边界。
 - 事实审查可在报告顶层输出 `factChanges`;`validateReviewReport`、合并和 `工作区/评审报告/事实审查.json` 必须保留该数组。finalize 在任何写盘前校验冲突、路径和 hash,并把事实写入、计划删除、章节提交纳入同一原子边界。
 - 章档案的 `知识选择` 只保存 `维度|名称` 与真实 `变体|说明`,是近期软降权的唯一输入;不保存评分、满意度或空反馈。
 - 章档案的 `知识选择` 只保存 `维度|名称` 与真实 `变体|说明`,是近期软降权的唯一输入;不保存评分、满意度或空反馈。
 - npm 包和 vendored 运行时必须同时包含 `references/` 与 `docs/knowledge/`;治理 README 链接不得失效。
 - npm 包和 vendored 运行时必须同时包含 `references/` 与 `docs/knowledge/`;治理 README 链接不得失效。
@@ -44,6 +47,8 @@ finalize <章号> --payload=<json>
 | `类型=衍生` 但没有非“衍生”的正式副题材 | 建书或契约更新失败,要求补充实际世界题材 |
 | `类型=衍生` 但没有非“衍生”的正式副题材 | 建书或契约更新失败,要求补充实际世界题材 |
 | 数值节奏配额缺任一非空说明 | 建书或契约更新失败,指出须补统计对象、使用目的和失效条件 |
 | 数值节奏配额缺任一非空说明 | 建书或契约更新失败,指出须补统计对象、使用目的和失效条件 |
 | 计划对象 ID、正名或别名冲突 | 写盘前拒绝,零文件变更 |
 | 计划对象 ID、正名或别名冲突 | 写盘前拒绝,零文件变更 |
+| 故事对象 `--类型` 不在受控值中 | 命令前置拒绝并列出该维允许值;禁止模糊归类 |
+| 故事对象目录缺失或问题无命中 | 返回空候选并允许完全自定义;禁止回退全量旧 CSV |
 | `factChanges` 为冲突/歧义、hash 漂移或路径逃逸 | 整章零写入,要求作者裁决或重新审查 |
 | `factChanges` 为冲突/歧义、hash 漂移或路径逃逸 | 整章零写入,要求作者裁决或重新审查 |
 | installer 缺治理文档或 README 链接目标 | 单测和 pack-install e2e 必须失败 |
 | installer 缺治理文档或 README 链接目标 | 单测和 pack-install e2e 必须失败 |
 
 
@@ -51,11 +56,16 @@ finalize <章号> --payload=<json>
 
 
 - Good:同一组主题材、副题材和流派按不同未决问题采用不同创意约束;融合协议写清共同冲突、副题材介入条件和失焦风险,作者确认后保存。
 - Good:同一组主题材、副题材和流派按不同未决问题采用不同创意约束;融合协议写清共同冲突、副题材介入条件和失焦风险,作者确认后保存。
 - Base:流派兼容范围或某知识维度为空,不制造提醒或默认答案;允许对谈自定义继续。
 - Base:流派兼容范围或某知识维度为空,不制造提醒或默认答案;允许对谈自定义继续。
+- Good:以“能力太万能且后遗症不落地”查询设定,选择或改写少量候选,将来源与本书适配写入计划对象;后续章节只精准读取该对象。
+- Base:对象类型明确但正式知识无合适方法,直接自定义本书对象;不保存未选候选,也不阻断计划落盘。
+- Bad:按题材固定选择一个人物模板,或让 prep/review 每章重新读取当前通用人物条目。
 - Bad:把快穿流写进衍生作品的副题材;只写“统计对象:/使用目的:/失效条件:”三个空标签;按题材固定选择唯一创意或关系结算答案。
 - Bad:把快穿流写进衍生作品的副题材;只写“统计对象:/使用目的:/失效条件:”三个空标签;按题材固定选择唯一创意或关系结算答案。
 
 
 ## 6. Tests Required
 ## 6. Tests Required
 
 
 - 知识查询:每维最多三条、无命中空集、近期历史只降权不排除。
 - 知识查询:每维最多三条、无命中空集、近期历史只降权不排除。
+- 故事对象:三维精确类型筛选、非法类型拒绝、最小 front matter、单一 `规划时` 切片、命名对象受控列表和禁用“全部”。
+- 本书化链路:候选来源可写入 `知识依据`;prep/review 只加载细纲声明的计划对象;未声明不扫描,事实转正后不依赖通用条目。
 - 建书/契约:必需字段、分类/来源同源、更新版本与批次失效、旧路径不回退。
 - 建书/契约:必需字段、分类/来源同源、更新版本与批次失效、旧路径不回退。
 - 书级路由:题材名单与契约校验名单完全一致;流派兼容范围不含“全部”,空值不提醒;歧义词与跨维词保持未命中。
 - 书级路由:题材名单与契约校验名单完全一致;流派兼容范围不含“全部”,空值不提醒;歧义词与跨维词保持未命中。
 - 衍生/配额:非题材副题材被拒;至少一个正式世界副题材才能通过;“每三章”“每章 2 个”“每卷 1 次”和空说明标签均有反例,完整三项说明有正例。
 - 衍生/配额:非题材副题材被拒;至少一个正式世界副题材才能通过;“每三章”“每章 2 个”“每卷 1 次”和空说明标签均有反例,完整三项说明有正例。
@@ -84,3 +94,16 @@ const candidates = await queryKnowledge(packageRoot, dimension, { 问题: questi
 ```
 ```
 
 
 前者制造固定答案和双真源,还会把任意副题材或空说明误当有效;后者验证正式分类与非空说明,并以作者确认的本书契约驱动运行时。
 前者制造固定答案和双真源,还会把任意副题材或空说明误当有效;后者验证正式分类与非空说明,并以作者确认的本书契约驱动运行时。
+
+故事对象查询同样不得用题材路由替代真实问题:
+
+```js
+// Wrong:无消费者字段和固定人物答案
+const candidate = defaults[book.类型].反派
+
+// Correct:唯一受控筛选字段,空结果可自定义
+const candidates = await queryKnowledge(packageRoot, '人物', {
+  问题: unresolvedProblem,
+  筛选: { 人物类别: '角色' },
+})
+```

+ 16 - 16
.trellis/tasks/07-10-knowledge-taxonomy-governance/implement.md

@@ -21,32 +21,32 @@
 
 
 依赖:阶段 1 的维度宪章、字段矩阵和目标书仓路径冻结。
 依赖:阶段 1 的维度宪章、字段矩阵和目标书仓路径冻结。
 
 
-- [ ] 2.1 将正式维度扩展为十维,修改 `v7/src/knowledge/` 的分维读取、canonical 归一和精准切片接口。
-- [ ] 2.2 改造建书 DTO、`knowledge-pack`、`persist-book` 和持久化服务:生成并强制校验 `作品契约/作品契约.md`,保存 `知识选择记录.md`,删除题材流派指导与恩怨清算。
-- [ ] 2.3 改造 prep/review:下游只读取冻结的本书契约和本书化对象,不回读当前通用题材/流派毒点;各阶段只提取需要的小节。
-- [ ] 2.4 实现故事对象计划区、事实转正差异和 finalize 原子提交;冲突/歧义进入作者裁决,不静默覆盖。
-- [ ] 2.5 改造 chapter DTO 与细纲声明:分维少量候选、整体确认、允许组合/变体/自定义,最小持久化所选知识与变体,写作与审稿精准读取所选切片;问题、预期和效果不预设独立字段。
-- [ ] 2.6 实现近期历史软降权与机械重复提醒;不实现硬禁、评分、章后反馈、作者提交或自动扩库。
-- [ ] 2.7 更新 installer、host shells、技能文档、fixture 和所有路径/DTO 行为测试,一次性删除旧路径和旧字段。
+- [x] 2.1 将正式维度扩展为十维,修改 `v7/src/knowledge/` 的分维读取、canonical 归一和精准切片接口。
+- [x] 2.2 改造建书 DTO、`knowledge-pack`、`persist-book` 和持久化服务:生成并强制校验 `作品契约/作品契约.md`,保存 `知识选择记录.md`,删除题材流派指导与恩怨清算。
+- [x] 2.3 改造 prep/review:下游只读取冻结的本书契约和本书化对象,不回读当前通用题材/流派毒点;各阶段只提取需要的小节。
+- [x] 2.4 实现故事对象计划区、事实转正差异和 finalize 原子提交;冲突/歧义进入作者裁决,不静默覆盖。
+- [x] 2.5 改造 chapter DTO 与细纲声明:分维少量候选、整体确认、允许组合/变体/自定义,最小持久化所选知识与变体,写作与审稿精准读取所选切片;问题、预期和效果不预设独立字段。
+- [x] 2.6 实现近期历史软降权与机械重复提醒;不实现硬禁、评分、章后反馈、作者提交或自动扩库。
+- [x] 2.7 更新 installer、host shells、技能文档、fixture 和所有路径/DTO 行为测试,一次性删除旧路径和旧字段。
 
 
 ## 阶段 3:书级知识策展子任务
 ## 阶段 3:书级知识策展子任务
 
 
 依赖:运行时书级 schema、作品契约结构和候选接口冻结。
 依赖:运行时书级 schema、作品契约结构和候选接口冻结。
 
 
-- [ ] 3.1 校准题材与流派边界、canonical 名称、别名和主副题材语义。
-- [ ] 3.2 建立 `创意约束/`,逐条重写 v6 反套路候选,删除具体剧情模板。
-- [ ] 3.3 用多个同题材与复合题材样例验证整体方向包可产生差异化契约,不固定路由到唯一答案。
-- [ ] 3.4 完成逐条策展裁决、近重复检查、独立内容审查和开发阶段签收。
+- [x] 3.1 校准题材与流派边界、canonical 名称、别名和主副题材语义。
+- [x] 3.2 建立 `创意约束/`,逐条重写 v6 反套路候选,删除具体剧情模板。
+- [x] 3.3 用多个同题材与复合题材样例验证整体方向包可产生差异化契约,不固定路由到唯一答案。
+- [x] 3.4 完成逐条策展裁决、近重复检查、独立内容审查和开发阶段签收。
 
 
 ## 阶段 4:故事对象知识策展子任务
 ## 阶段 4:故事对象知识策展子任务
 
 
 依赖:设定/人物/命名 schema、计划对象持久化和事实转正接口冻结。
 依赖:设定/人物/命名 schema、计划对象持久化和事实转正接口冻结。
 
 
-- [ ] 4.1 策展 `设定/`:世界规则、体系、能力、物品、资源、组织、制度、地理等受控类型。
-- [ ] 4.2 策展 `人物/`:角色功能、欲望缺陷、行为边界、人物弧、关系和反派体系。
-- [ ] 4.3 策展 `命名/`:标题及对象命名方法、语系、近名/重名、正名/别名和可读性。
-- [ ] 4.4 对 v6 284 条候选逐条做保留、重写、拆分、合并或删除裁决;验证唯一归属且命名结果附着于对象。
-- [ ] 4.5 用计划创建、修改、放弃、首次定稿转正和事实冲突样例完成内容与行为联合审查。
+- [x] 4.1 策展 `设定/`:世界规则、体系、能力、物品、资源、组织、制度、地理等受控类型。
+- [x] 4.2 策展 `人物/`:角色功能、欲望缺陷、行为边界、人物弧、关系和反派体系。
+- [x] 4.3 策展 `命名/`:标题及对象命名方法、语系、近名/重名、正名/别名和可读性。
+- [x] 4.4 对 v6 284 条候选逐条做保留、重写、拆分、合并或删除裁决;验证唯一归属且命名结果附着于对象。
+- [x] 4.5 用计划创建、修改、放弃、首次定稿转正和事实冲突样例完成内容与行为联合审查。
 
 
 ## 阶段 5:篇章知识策展子任务
 ## 阶段 5:篇章知识策展子任务
 
 

+ 145 - 0
.trellis/tasks/07-10-story-object-knowledge-migration/design.md

@@ -0,0 +1,145 @@
+# Design:故事对象知识策展
+
+## 1. 目标与非目标
+
+本任务把已经生效的故事对象运行时接口填充为可调用、可本书化、可审查的正式知识内容。通用条目只帮助设计对象;作者确认后,真正约束后续规划、写作和审查的是书仓中的计划对象与定稿事实。
+
+目标:
+
+- 为 `设定/人物/命名` 建立最小且互不混用的正式 schema。
+- 对 284 条 v6 候选及书级任务转交项逐条裁决,删除模板、固定答案和无消费者字段。
+- 每个正式条目能被真实对象问题命中,且最多返回三条材料,不替作者决定具体对象。
+- 选定知识能够转化为 `本书设计 / 一致性边界 / 知识依据`,命名结果附着原对象。
+- 用格式、查询、计划对象和事实转正测试证明内容确实进入调用闭环。
+
+非目标:
+
+- 不修改十维边界、不新增物品、能力、组织、关系等顶层目录。
+- 不恢复 v6 的全量 CSV 检索、题材固定路由或具体人物/设定模板。
+- 不建设作者投稿、评分、自动扩库或跨作品知识晋升。
+- 不重做运行时计划/事实架构;只有内容暴露出已实现 schema 的最小不一致时才做窄修正。
+- 不处理篇章技法、场景、节拍、追读或桥段资产。
+
+## 2. 已冻结的数据流
+
+```text
+真实对象问题
+  -> knowledge-query 查询 设定 / 人物 / 命名,最多三条
+  -> 作者选择、组合、修改、拒绝或自定义
+  -> persist-design 写入 大纲/创作设计/设定|人物/
+  -> 细纲通过 本章对象 精确引用
+  -> prep/review 只读计划对象的本书设计与一致性边界
+  -> 正文首次出现并定稿
+  -> factChanges 无冲突原子转正;冲突/歧义先由作者裁决
+```
+
+通用条目在计划对象落盘后不再被写作或审查回读。来源版本、本书采用方式和真实修改写进计划对象的 `知识依据`,普通未选候选不持久化。
+
+## 3. 三维 schema
+
+### 3.1 设定
+
+front matter 只允许:
+
+| 字段 | 必需 | 消费者 |
+|---|---|---|
+| `名称` | 是 | 候选展示、精确匹配、来源记录 |
+| `对象类型` | 是 | `knowledge-query --类型` 的精确筛选 |
+| `一句话` | 是 | 问题文本匹配与紧凑候选展示 |
+
+`对象类型` 与计划对象校验器保持一致,只使用:`世界 / 规则 / 机制 / 能力 / 物品 / 组织 / 制度 / 地点`。资源、技术、体系等来源概念按其实际产出归入上述类型,不为来源列名扩充运行时枚举。
+
+条目回答“这个对象如何被设计成自洽机制”。具体能力名、数值表、物品属性、组织成员和世界事实必须本书化后才进入计划对象。
+
+### 3.2 人物
+
+front matter 只允许:
+
+| 字段 | 必需 | 消费者 |
+|---|---|---|
+| `名称` | 是 | 候选展示、精确匹配、来源记录 |
+| `人物类别` | 是 | `knowledge-query --类型` 的精确筛选 |
+| `一句话` | 是 | 问题文本匹配与紧凑候选展示 |
+
+`人物类别` 只取 `角色` 或 `关系`,与计划对象的两类产出一致。角色条目解决欲望、缺陷、底线、行为逻辑、人物弧或反派职责;关系条目解决长期互动、利益、权力、信任与变化机制。旧运行时定义中的 `关系类别` 没有独立命令消费者,本任务删除该索引字段,不为分类精细度保留僵尸 schema。
+
+对白、心理描写、视角呈现和单场关系推进属于篇章技法或场景,不进入人物条目。
+
+### 3.3 命名
+
+front matter 只允许:
+
+| 字段 | 必需 | 消费者 |
+|---|---|---|
+| `名称` | 是 | 候选展示、精确匹配、来源记录 |
+| `命名对象` | 是,列表 | `knowledge-query --类型` 的精确筛选 |
+| `一句话` | 是 | 问题文本匹配与紧凑候选展示 |
+
+`命名对象` 归一到 `书名 / 卷名 / 章节名 / 角色 / 地点 / 组织 / 能力 / 物品 / 制度 / 术语 / 任务 / 副本 / 赛事 / 代号`。一条真正通用的方法可以覆盖多个对象类型,但不得用“全部”占位。
+
+命名条目只提供生成和校验方法。具体候选名、正名、别名、语系与近名检查结果附着到对应计划对象或书级产物,不建立平行命名实体目录。
+
+### 3.4 正文切片
+
+三维条目都只要求一个 `## 规划时` 运行时切片。切片内部必须同时给出:
+
+1. 可执行的设计或命名动作;
+2. 需要向本书锁定的问题;
+3. 应写入计划对象的可观察一致性边界;
+4. 能区分近邻方法的失败模式。
+
+不增加 `落笔时 / 审稿时` 通用条目小节。后续写作与审稿消费的是本书化计划对象,继续回读通用条目会造成版本漂移,也会让未被选中的知识静默影响作品。
+
+正式条目不得保留 `编号 / 适用技能 / 分类 / 层级 / 关键词 / 意图与同义词 / 适用题材 / 大模型指令 / 毒点 / 正例 / 反例 / 来源 / 状态`。来源编号与处置保存在任务研究记录中。
+
+## 4. 候选裁决
+
+### 4.1 输入
+
+- `webnovel-writer/references/csv/金手指与设定.csv`:104 条。
+- `webnovel-writer/references/csv/人设与关系.csv`:101 条。
+- `webnovel-writer/references/csv/命名规则.csv`:79 条。
+- 书级任务转交:洪荒/克苏鲁/诡秘规则、反派镜像、关系阻力、规则身份、内鬼、循环/扩张等候选。
+- v6 worldbuilding 指南只作为补充证据;不能绕过 284 条来源裁决,也不能直接复制长模板。
+
+### 4.2 单条处置
+
+每条来源必须记录:来源 ID、主要问题、唯一维度、处置、canonical 去向、近重复、删除内容与理由。处置仅为:`保留重写 / 合并吸收 / 拆分 / 转交 / 删除`。
+
+- **保留重写**:有独有问题、动作、失败模式和触发信号,形成正式条目。
+- **合并吸收**:题材化改写或动作实质相同,归入一个 canonical 条目。
+- **拆分**:一行同时含设定机制、人物配置或呈现技法;每个拆分结果重新过准入门。
+- **转交**:主要产出属于创意约束或篇章技法;只在裁决记录登记,不在本任务复制正式文件。
+- **删除**:依赖具体人名、书名、固定数值、事件顺序或答案模板,无法抽象为方法。
+
+不设正式条目数量目标。缺少独有动作的候选宁可合并或删除,也不为覆盖来源行而增加近重复条目。
+
+## 5. 批次与回滚
+
+1. **schema 与行为样例**:格式测试、人物字段收口、三维查询与本书化链路样例。
+2. **设定**:先裁决 104 条及转交规则,再按世界/规则/机制与能力/物品/组织/制度/地点分两批写正式内容。
+3. **人物**:先裁决 101 条及转交项,再按角色与关系分两批写正式内容。
+4. **命名**:先裁决 79 条,再按书卷章与故事对象命名分两批写正式内容。
+5. **集成收口**:跨维近重复、真实问题命中、计划对象实例化、事实转正、README、技能与 installer。
+
+每个维度形成独立提交和内容审查记录。回滚某维只删除该维正式条目与对应测试/文档,不回退已经验证的计划/事实运行时框架,也不引入旧格式双读。
+
+## 6. 验证
+
+- 格式:三维 front matter 只有允许字段,`规划时` 非空,禁止“全部”占位和来源字段回流。
+- 查询:真实问题与类型筛选每维最多三条;无命中返回空集;允许组合、拒绝和自定义。
+- 本书化:候选来源与本书适配写入计划对象,命名结果附着对象,未选候选不落盘。
+- 事实:计划对象创建、修改、放弃、无冲突转正、冲突/歧义阻断和 hash 漂移行为保持通过。
+- 内容:每条正式知识通过唯一归属、近重复、非模板化和失败模式独立审查。
+- 分发:源码与 installer 内的 references、技能和测试保持一致。
+
+## 7. 风险
+
+| 风险 | 防线 |
+|---|---|
+| 284 条一对一平移造成知识膨胀 | 先做全量裁决和聚类,正式条目不设数量目标 |
+| 具体金手指/人物/名称成为默认模板 | 删除实例、数值、正反例和固定组合;只保留方法与边界 |
+| 人物与技法混维 | 只保留长期行为/关系机制;呈现方法转篇章任务 |
+| 命名形成平行实体库 | 正名/别名只写计划对象或书级产物 |
+| 通用知识升级改变在写作品 | 下游只读本书化计划对象和定稿事实 |
+| schema 再次膨胀 | 每个字段与小节必须有当前消费者和行为测试 |

+ 94 - 0
.trellis/tasks/07-10-story-object-knowledge-migration/implement.md

@@ -0,0 +1,94 @@
+# Implement:故事对象知识策展
+
+> 当前状态:ready_for_commit(2026-07-19)。三维内容、运行时窄修、独立审查与完整质量门均已完成。
+
+## 门 0:启动前检查
+
+- [x] 0.1 核对父任务 PRD/design/implement、维度宪章、字段矩阵和策展规则。
+- [x] 0.2 核对运行时 `knowledge-query`、`persist-design`、`read-design`、prep/review 和 factChanges 的真实消费者。
+- [x] 0.3 盘点三份 CSV:设定 104、人物 101、命名 79;确认旧字段、模板化和跨维风险。
+- [x] 0.4 补齐本子任务 PRD、design 与本实施计划,明确不设条目数量目标。
+- [x] 0.5 对 PRD/design/implement 做一致性与 convergence 检查,确认没有未决架构问题。
+- [x] 0.6 作者确认规划后运行 `task.py start 07-10-story-object-knowledge-migration`;进入实现前加载 `trellis-before-dev`。
+
+## 批次 1:最小 schema 与调用样例
+
+- [x] 1.1 扩展 `v7/test/references/format.test.js`:三维正式条目只允许各自字段并要求非空 `规划时`。
+- [x] 1.2 将设定对象类型、人物类别和命名对象受控值写入测试;禁止 `全部`、来源编号、适用题材、关键词、毒点和正反例回流。
+- [x] 1.3 删除 `v7/src/knowledge/index.js` 中没有独立命令消费者的 `关系类别` 索引字段;人物类型只使用 `角色 / 关系`。
+- [x] 1.4 增加真实问题和 `--类型` 查询测试,证明三维各自最多三条、空结果可自定义、固定字段不决定具体对象答案。
+- [x] 1.5 增加“候选 → 计划对象 → prep/review → factChanges”行为样例,证明通用条目不被下游持续回读。
+- [x] 1.6 更新 `v7/skills/webnovel-writer/SKILL.md` 的故事对象说明,只保存最终来源、本书适配和真实变更。
+
+## 批次 2:设定策展
+
+- [x] 2.1 建立 `research/设定候选裁决.csv`,覆盖 SY-001 至 SY-104 及书级任务转交的世界/规则候选;检查漏号与重复。
+- [x] 2.2 对候选做问题/动作/失败模式聚类,删除具体金手指、数值表、事件模板和题材化复述。
+- [x] 2.3 对拆出的创意约束或篇章技法只记录转交,不在 `设定/` 复制。
+- [x] 2.4 先落世界/规则/机制,再落能力/物品/组织/制度/地点正式条目;每批同步查询样例和近重复检查。
+- [x] 2.5 建立 `research/content-review/设定.md`,逐条核对对象类型、可实例化问题、一致性边界和非模板化。
+
+## 批次 3:人物策展
+
+- [x] 3.1 建立 `research/人物候选裁决.csv`,覆盖 CH-001 至 CH-101 及反派镜像、关系阻力、内鬼等转交项;检查漏号与重复。
+- [x] 3.2 把来源聚类为角色方法与关系方法;删除具体主角/配角配置、题材换皮和单场呈现技巧。
+- [x] 3.3 先落角色设计,再落关系设计;同一方法只保留一个 canonical 路径,不因主角/反派/题材标签复制。
+- [x] 3.4 用主角驱动、反派职责、人物弧、利益关系、权力关系等真实问题验证候选差异。
+- [x] 3.5 建立 `research/content-review/人物.md`,核对长期行为机制、关系变化、OOC 边界和技法排除。
+
+## 批次 4:命名策展
+
+- [x] 4.1 建立 `research/命名候选裁决.csv`,覆盖 NR-001 至 NR-079;检查漏号与重复。
+- [x] 4.2 删除具体书名、人名、正反例和题材词表,把题材化命名规则合并为语系、功能、可读性与区分度方法。
+- [x] 4.3 先落书名/卷名/章节名,再落角色/地点/组织/能力/物品/制度/术语/任务/副本/赛事/代号方法。
+- [x] 4.4 用现有名册、近名、正名/别名、跨语系和可读性问题验证查询与计划对象落盘。
+- [x] 4.5 建立 `research/content-review/命名.md`,核对候选生成方法、冲突检查、对象附着和非模板化。
+
+## 批次 5:跨维审计与分发
+
+- [x] 5.1 对三维及题材/流派/创意约束/技法边界做近重复审计;洪荒、克苏鲁、诡秘和反派镜像等转交项必须有唯一去向。
+- [x] 5.2 证明明确作者可直接自定义,空白作者只得到少量相关候选;不展示 284 条全量菜单。
+- [x] 5.3 更新 `v7/references/README.md` 的正式条目数量、schema 和故事对象调用说明。
+- [x] 5.4 核对 installer vendoring、host shells、技能和 fixture;正式库只含签收条目,研究记录留在任务目录。
+- [x] 5.5 运行定向测试、全量测试、installer e2e、任务 validate 和 `git diff --check`。
+
+## 验证命令
+
+```powershell
+node --test v7/test/references/format.test.js
+node --test v7/test/knowledge/index.test.js
+node --test v7/test/knowledge/query-command.test.js
+node --test v7/test/commands/persist-design.test.js
+node --test v7/test/knowledge/design.test.js
+node --test v7/test/knowledge/fact-changes.test.js
+node --test v7/test/knowledge/pipeline.test.js
+npm --prefix v7 test
+npm --prefix v7 run e2e:install
+python ./.trellis/scripts/task.py validate 07-10-story-object-knowledge-migration
+git diff --check
+```
+
+## 验证结果
+
+- 定向故事对象与知识链路:53 项通过。
+- `npm --prefix v7 test`:574 项通过。
+- `npm --prefix v7 run e2e:install`:pack、中文路径安装、建书、next、update 全通过,安装产物写入 420 个文件。
+- `build-host-shells.mjs --check`、任务 validate 与 `git diff --check` 全通过。
+
+## 风险与回滚点
+
+| 风险 | 受影响范围 | 回滚边界 |
+|---|---|---|
+| 最小 schema 与运行时定义不一致 | `src/knowledge/index.js`、format/query 测试 | 只回退批次 1,不回退计划/事实框架 |
+| 设定条目变成金手指模板库 | `references/设定/`、设定裁决与审查 | 整批撤销设定正式条目,保留裁决重新策展 |
+| 人物条目按题材/角色身份重复 | `references/人物/`、人物裁决与审查 | 按角色或关系批次回退,重新聚类 canonical |
+| 命名条目携带具体名称模板 | `references/命名/`、命名裁决与审查 | 整批撤销对应命名对象组,不影响对象持久化 |
+| installer 与源码内容漂移 | installer vendor、技能、README | 与对应内容批次同回滚,不保留双份知识 |
+
+## 完成条件
+
+- [x] 284 条来源和全部转交项均有唯一裁决,无漏号、重复或悬空去向。
+- [x] 三维正式条目只有最小字段与单一 `规划时` 切片,且全部有真实查询消费者。
+- [x] 每个正式条目有唯一 canonical 路径,内容不含可直接复刻的对象或名称模板。
+- [x] 查询、本书化、计划/事实两阶段和分发行为测试全部通过。
+- [x] 三份独立内容审查关闭所有发现,README、spec、技能、代码和测试一致。

+ 19 - 5
.trellis/tasks/07-10-story-object-knowledge-migration/prd.md

@@ -4,26 +4,40 @@
 
 
 建立 `设定/`、`人物/`、`命名/`三个故事对象维度,对旧资产进行重新策展,使知识能够在对象创建时产生本书化计划,并在正文定稿后形成可供精准读取与审查的作品事实。
 建立 `设定/`、`人物/`、`命名/`三个故事对象维度,对旧资产进行重新策展,使知识能够在对象创建时产生本书化计划,并在正文定稿后形成可供精准读取与审查的作品事实。
 
 
+## Background
+
+- 知识库运行时框架和书级知识策展已经完成并归档;`设定/人物/命名` 的查询、计划对象持久化、精准读取和事实转正接口已具备,当前三个正式内容目录仍为空。
+- v6 候选素材为 `金手指与设定.csv` 104 条、`人设与关系.csv` 101 条、`命名规则.csv` 79 条,共 284 条。旧表包含具体能力/人物/名称模板、题材标签、固定数值、正反例和无消费者字段,只能作为候选证据,不能直接平移。
+- 书级知识任务另转交了洪荒/克苏鲁/诡秘世界规则、反派镜像、规则身份、内鬼和关系阻力等故事对象候选;这些候选与三份 CSV 一并裁决,不能形成跨任务重复条目。
+- 通用故事对象知识只在创建或修改计划对象时查询。选定方法必须本书化进 `大纲/创作设计/` 的 `本书设计 / 一致性边界 / 知识依据`;写作和审查读取计划对象或定稿事实,不持续回读通用条目。
+
 ## Requirements
 ## Requirements
 
 
 - 前置依赖:父任务最终规划与维度宪章已冻结;运行时框架的设定/人物/命名 schema、计划对象持久化、事实转正和精准读取接口已实现并通过行为测试。
 - 前置依赖:父任务最终规划与维度宪章已冻结;运行时框架的设定/人物/命名 schema、计划对象持久化、事实转正和精准读取接口已实现并通过行为测试。
 - 以 v6 `金手指与设定` 104 条、`人设与关系` 101 条、`命名规则` 79 条及相关 worldbuilding 指南为候选素材;逐条重写策展,不原样平移,也不承担旧格式兼容。
 - 以 v6 `金手指与设定` 104 条、`人设与关系` 101 条、`命名规则` 79 条及相关 worldbuilding 指南为候选素材;逐条重写策展,不原样平移,也不承担旧格式兼容。
 - 设定使用受控对象类型;人物包含角色与关系子类;命名覆盖标题与各类 canonical 实体名。
 - 设定使用受控对象类型;人物包含角色与关系子类;命名覆盖标题与各类 canonical 实体名。
+- 正式条目只保留实际查询所需的最小机器字段:设定为 `名称 / 对象类型 / 一句话`,人物为 `名称 / 人物类别 / 一句话`,命名为 `名称 / 命名对象 / 一句话`。不保留来源编号、适用题材、关键词、层级、毒点、正例、反例或治理状态。
+- 故事对象条目只有 `规划时` 一个运行时切片;设计动作、本书化问题、一致性边界和失败模式均放在该切片内。后续写作与审查只读本书化计划对象,不给通用条目增加无人读取的 `落笔时 / 审稿时` 小节。
+- `对象类型` 与计划对象允许值对齐为 `世界 / 规则 / 机制 / 能力 / 物品 / 组织 / 制度 / 地点`;人物以 `人物类别: 角色|关系` 区分主要产出,不保留无独立消费者的关系细分类字段;命名对象使用受控列表且允许一条方法覆盖多个真实对象类型。
 - 通用知识只保存设计方法、边界和审查点;具体能力、物品、组织、角色、关系和名称必须实例化后写入本书。
 - 通用知识只保存设计方法、边界和审查点;具体能力、物品、组织、角色、关系和名称必须实例化后写入本书。
 - 本书实例先进入 `大纲/创作设计/`;只有正文首次出现并定稿后才转入或更新 `定稿/设定/`。计划对象可修改或放弃,事实对象的根本修改需影响分析。
 - 本书实例先进入 `大纲/创作设计/`;只有正文首次出现并定稿后才转入或更新 `定稿/设定/`。计划对象可修改或放弃,事实对象的根本修改需影响分析。
 - 命名结果附着于对应计划对象或事实对象,不建立平行的本书命名实体目录;一次性路人、普通物品和临时地点不强制提前建档。
 - 命名结果附着于对应计划对象或事实对象,不建立平行的本书命名实体目录;一次性路人、普通物品和临时地点不强制提前建档。
 - 每条知识只有一个正式维度归属;跨维材料通过拆分或组合调用处理,不在多个目录复制。
 - 每条知识只有一个正式维度归属;跨维材料通过拆分或组合调用处理,不在多个目录复制。
+- 三份来源的每一数据行必须有唯一裁决记录;正式条目数量不是目标,无法抽象成独有问题、动作和失败模式的候选应合并、转交或删除。
 - 若单任务内容量超出独立审查能力,继续按设定/人物/命名拆子任务。
 - 若单任务内容量超出独立审查能力,继续按设定/人物/命名拆子任务。
 
 
 ## Acceptance Criteria
 ## Acceptance Criteria
 
 
-- [ ] 三个维度均有独立 schema、调用入口、本书化产物和审查闭环。
-- [ ] 物品、能力、组织、世界规则等稳定归入设定子类,不再形成平行顶层目录。
-- [ ] 命名支持语系一致、近名/重名、正名/别名和可读性检查。
-- [ ] 计划与事实两阶段行为通过测试;无冲突事实自动转正,冲突或歧义由作者裁决,一次性对象不会造成书仓膨胀。
-- [ ] v6 284 条候选素材均有策展裁决记录并通过独立内容审查。
+- [x] 三个维度均有独立最小 schema、调用入口、本书化产物和审查闭环,正式条目没有无消费者字段或小节。
+- [x] 物品、能力、组织、世界规则等稳定归入设定子类,不再形成平行顶层目录。
+- [x] 人物方法能稳定区分角色设计与关系设计,人物呈现技法不会混入人物维度。
+- [x] 命名支持书名、卷章名和故事对象的语系一致、近名/重名、正名/别名和可读性检查,结果附着原对象。
+- [x] 计划与事实两阶段行为通过测试;无冲突事实自动转正,冲突或歧义由作者裁决,一次性对象不会造成书仓膨胀。
+- [x] `knowledge-query` 对三维均按真实问题或对象类型返回最多三条材料,允许空结果、自定义和组合,不形成固定人物/设定/名称答案。
+- [x] v6 284 条候选素材及书级任务转交项均有策展裁决记录;每个正式条目通过近重复、非模板化和独立内容审查。
 
 
 ## Notes
 ## Notes
 
 
 - 本任务可进一步拆成三个并行内容子任务,但集成边界由本任务负责。
 - 本任务可进一步拆成三个并行内容子任务,但集成边界由本任务负责。
 - 启动前补齐自身 `design.md` 与 `implement.md`;按设定、人物、命名或更小审查批次独立提交,回滚不得删除已转正事实接口或把计划对象重新当作事实。
 - 启动前补齐自身 `design.md` 与 `implement.md`;按设定、人物、命名或更小审查批次独立提交,回滚不得删除已转正事实接口或把计划对象重新当作事实。
+- 本任务不新增顶层维度、不恢复 v6 运行时检索、不建设作者扩库、评分或自动知识晋升,也不承担篇章技法/场景/桥段策展。

+ 26 - 0
.trellis/tasks/07-10-story-object-knowledge-migration/research/候选覆盖审计.md

@@ -0,0 +1,26 @@
+# 候选覆盖审计
+
+> 2026-07-19;审计对象为三份 v6 来源表及本任务登记的书级转交项。研究表是裁决真源,正式目录只保留签收后的 canonical 条目。
+
+## 数量与动作
+
+| 维度 | v6 来源 | 书级转交 | 裁决记录 | 正式条目 | 来源动作分布 |
+|---|---:|---:|---:|---:|---|
+| 设定 | 104 | 6 | 110 | 27 | 保留重写 22;拆分 10;合并吸收 49;删除 6;转交 17 |
+| 人物 | 101 | 4 | 105 | 28 | 保留重写 24;拆分 20;合并吸收 50;删除 3;转交 4 |
+| 命名 | 79 | 2 | 81 | 17 | 保留重写 19;拆分 12;合并吸收 38;删除 6;转交 4 |
+| 合计 | 284 | 12 | 296 | 72 | 来源行 284 条均有且仅有一个处置 |
+
+## 覆盖检查
+
+- `SY-001` 至 `SY-104`、`CH-001` 至 `CH-101`、`NR-001` 至 `NR-079` 均逐号出现一次;没有漏号或重复 ID。
+- 296 条记录中,272 条指向现有正式路径,13 条明确删除,11 条转交 `篇章执行/技法`、`篇章执行/节拍` 或 `篇章执行/追读`,由篇章知识任务继续策展;不存在未说明的悬空去向。
+- 设定、人物、命名的正式条目分别为 27、28、17 条。每条正式条目只有一个 canonical 文件;合并与拆分关系只保留在本目录 CSV,不复制到多个维度。
+- 书级转交项已在本任务记录唯一目标:规则类进入设定,人际与镜像类进入人物,名称冲突类进入命名;篇章呈现类只登记下游任务,不在故事对象目录复刻。
+
+## 复核方式
+
+1. PowerShell `Import-Csv` 校验三份表的 ID 连续性、重复性和动作集合。
+2. 对每个非删除目标检查 `v7/references/<canonical>.md`;篇章目标按下游任务维度登记。
+3. 格式测试逐文件检查三维最小 schema、受控值、单一 `规划时` 切片和禁用字段。
+4. 知识查询测试逐条用 front matter 的未决问题摘要与受控类型调用,确保每条知识都能进入最多三条候选结果。

+ 2 - 2
.trellis/tasks/07-10-story-object-knowledge-migration/task.json

@@ -3,7 +3,7 @@
   "name": "story-object-knowledge-migration",
   "name": "story-object-knowledge-migration",
   "title": "故事对象知识策展",
   "title": "故事对象知识策展",
   "description": "",
   "description": "",
-  "status": "planning",
+  "status": "in_progress",
   "dev_type": null,
   "dev_type": null,
   "scope": null,
   "scope": null,
   "package": null,
   "package": null,
@@ -23,4 +23,4 @@
   "relatedFiles": [],
   "relatedFiles": [],
   "notes": "",
   "notes": "",
   "meta": {}
   "meta": {}
-}
+}

+ 3 - 3
v7/docs/knowledge/调用者与字段矩阵.md

@@ -51,7 +51,7 @@
 | 创意约束 | 名称、紧凑摘要 | 条目索引 | 组成不同整体方向包 | 必要 |
 | 创意约束 | 名称、紧凑摘要 | 条目索引 | 组成不同整体方向包 | 必要 |
 | 创意约束 | 适用、迁移和禁用条件 | 正文;只有机械筛选时才升为字段 | 防止固定路由到唯一约束 | 条件式 |
 | 创意约束 | 适用、迁移和禁用条件 | 正文;只有机械筛选时才升为字段 | 防止固定路由到唯一约束 | 条件式 |
 | 设定 | 名称、对象类型、紧凑摘要 | 条目索引 | 按待设计对象筛选方法 | 必要 |
 | 设定 | 名称、对象类型、紧凑摘要 | 条目索引 | 按待设计对象筛选方法 | 必要 |
-| 人物 | 名称、人物/关系类别、紧凑摘要 | 条目索引 | 按角色职责和关系问题筛选 | 必要 |
+| 人物 | 名称、人物类别(角色/关系)、紧凑摘要 | 条目索引 | 按角色职责和关系问题筛选 | 必要 |
 | 命名 | 名称、命名对象、紧凑摘要 | 条目索引 | 按书名、人物、地点等对象筛选 | 必要 |
 | 命名 | 名称、命名对象、紧凑摘要 | 条目索引 | 按书名、人物、地点等对象筛选 | 必要 |
 | 节拍 | 稳定编号、名称、紧凑摘要 | 条目索引 | 细纲声明和候选展示 | 必要 |
 | 节拍 | 稳定编号、名称、紧凑摘要 | 条目索引 | 细纲声明和候选展示 | 必要 |
 | 节拍 | 关键词、适用题材 | 仅在候选算法真实读取时保留 | 影响候选排序 | 条件式;无消费者即删 |
 | 节拍 | 关键词、适用题材 | 仅在候选算法真实读取时保留 | 影响候选排序 | 条件式;无消费者即删 |
@@ -71,8 +71,8 @@
 |---|---|---|
 |---|---|---|
 | 题材、流派 | 核心承诺、骨架/引擎、与本书对撞的问题 | 失败模式与不可违背边界 |
 | 题材、流派 | 核心承诺、骨架/引擎、与本书对撞的问题 | 失败模式与不可违背边界 |
 | 创意约束 | 被挑战的常规、决策压力、长期驱动、防绕过条件 | 约束是否被轻易绕过、代价是否真实发生 |
 | 创意约束 | 被挑战的常规、决策压力、长期驱动、防绕过条件 | 约束是否被轻易绕过、代价是否真实发生 |
-| 设定、人物 | 设计方法、实例化问题、计划对象边界 | 自洽、OOC、规则和关系失败模式 |
-| 命名 | 候选生成方法、语系与辨识度规则 | 重名、近名、歧义和可读性检查 |
+| 设定、人物 | 通用方法候选只用于规划;计划对象的本书设计与一致性边界 | `prep/review` 只读计划对象与事实,不回读通用条目 |
+| 命名 | 候选生成方法、语系与辨识度规则;正名/别名结果附着对象 | 计划对象或书级名册中的重名、近名、歧义和可读性检查 |
 | 节拍、场景、技法、追读 | `规划时` 与 `落笔时` 所需的可执行切片 | `审稿时` 的可核对判据 |
 | 节拍、场景、技法、追读 | `规划时` 与 `落笔时` 所需的可执行切片 | `审稿时` 的可核对判据 |
 
 
 若审稿已经读取正文失败模式,不得在 front matter 再保存同义 `毒点`。内容任务必须在迁移时选择一个真源。
 若审稿已经读取正文失败模式,不得在 front matter 再保存同义 `毒点`。内容任务必须在迁移时选择一个真源。

+ 10 - 2
v7/references/README.md

@@ -38,7 +38,7 @@ references/
 
 
 ## 当前基线
 ## 当前基线
 
 
-截至 2026-07-18,十维目录和运行时调用契约已生效。当前正式内容基线为 182 条:题材 19、流派 24、创意约束 11、节拍 102、场景 16、追读 10;设定、人物、命名和技法的正式条目由对应内容策展任务补充,空目录或无命中不会阻断创作。
+截至 2026-07-19,十维目录和运行时调用契约已生效。当前正式内容基线为 254 条:题材 19、流派 24、创意约束 11、设定 27、人物 28、命名 17、节拍 102、场景 16、追读 10;技法正式条目由篇章内容策展任务补充,空目录或无命中不会阻断创作。
 
 
 这里不建立旧路径双读、旧 schema 兼容或历史书仓迁移器。运行时、installer、fixture 和测试只认本页列出的十维结构。
 这里不建立旧路径双读、旧 schema 兼容或历史书仓迁移器。运行时、installer、fixture 和测试只认本页列出的十维结构。
 
 
@@ -51,6 +51,13 @@ references/
 
 
 题材与流派 front matter 只含 `名称`;创意约束只含 `名称 / 一句话`。别名与流派兼容范围只在 `路由.csv` 保存,空兼容范围表示无需窄范围提醒。
 题材与流派 front matter 只含 `名称`;创意约束只含 `名称 / 一句话`。别名与流派兼容范围只在 `路由.csv` 保存,空兼容范围表示无需窄范围提醒。
 
 
+## 故事对象调用
+
+- 只有对象需要跨章复用或影响一致性时,才用 `knowledge-query --维度=设定|人物|命名 --问题=<未决问题> [--类型=<受控子类>]` 取最多三条方法材料;一次性路人、普通物品和临时地点不强制建档。
+- 设定、人物、命名 front matter 分别只含 `名称 / 对象类型 / 一句话`、`名称 / 人物类别 / 一句话`、`名称 / 命名对象 / 一句话`;正文只含 `规划时`,没有无人读取的落笔或审稿切片。
+- 选定方法须转成作者确认的 `本书设计 / 一致性边界`,可把来源和本书适配写入 `知识依据`;普通未选候选不保存。
+- 命名结果附着在书名或计划对象上。写作、审稿和事实转正只精准读取本书计划对象与定稿事实,不回读通用故事对象条目。
+
 ## 内容纪律
 ## 内容纪律
 
 
 - 每条正式知识只有一个 canonical 路径;跨维使用通过组合或拆分解决,不复制文件。
 - 每条正式知识只有一个 canonical 路径;跨维使用通过组合或拆分解决,不复制文件。
@@ -64,4 +71,5 @@ references/
 - 2026-07-07 任务 `07-07-knowledge-layer-rework` 将 v6 题材模板、题材与调性推理、爽点节奏和追读力资产重写为 146 条 v7 基线,审查记录见任务归档。
 - 2026-07-07 任务 `07-07-knowledge-layer-rework` 将 v6 题材模板、题材与调性推理、爽点节奏和追读力资产重写为 146 条 v7 基线,审查记录见任务归档。
 - 2026-07-08 任务 `07-08-kb-ima-distill-integration` 新增 20 条、修正 9 件,形成 166 条签收基线,审查记录见任务归档。
 - 2026-07-08 任务 `07-08-kb-ima-distill-integration` 新增 20 条、修正 9 件,形成 166 条签收基线,审查记录见任务归档。
 - 2026-07-18 任务 `07-10-book-level-knowledge-migration` 将书级基线校准为 19 个题材、24 个流派,并从 v6 创意资产蒸馏出 11 条创意约束,形成 182 条当前基线。
 - 2026-07-18 任务 `07-10-book-level-knowledge-migration` 将书级基线校准为 19 个题材、24 个流派,并从 v6 创意资产蒸馏出 11 条创意约束,形成 182 条当前基线。
-- v6 命名、设定、人物、技法、场景和桥段资产仍只作为后续策展候选,不承担格式兼容义务。
+- 2026-07-19 任务 `07-10-story-object-knowledge-migration` 逐条裁决 v6 的 284 条设定、人物、命名候选及书级转交项,策展出 72 条故事对象方法,形成 254 条当前基线。
+- v6 技法、场景和桥段资产仍只作为篇章知识策展候选,不承担格式兼容义务。

+ 1 - 1
v7/skills/webnovel-writer/SKILL.md

@@ -26,7 +26,7 @@ SessionStart 已注入「当前在写哪本 / 共几本 / 全书近况入口」
 - 序6 起草细纲:吃 DTO 拟整份细纲提案(本章定位 + 本章要写到的事 + 备选)。`dto.章级知识候选` 每维最多三条,只是材料,不是固定答案;可组合、变体、拒绝或自定义。提案段按需写 `本章节拍:`、`本章场景:`、`本章技法:`、`本章追读:`、`知识变体:`、`本章对象:`,六者皆可空,前四者可多选,变体可多行。作者确认整份细纲后产出 `{"细纲"}`,运行 `{{cmd}} persist-outline --file=<json路径>`;若 DTO 带作品契约复盘提示,只呈报修订候选,不自动改契约。
 - 序6 起草细纲:吃 DTO 拟整份细纲提案(本章定位 + 本章要写到的事 + 备选)。`dto.章级知识候选` 每维最多三条,只是材料,不是固定答案;可组合、变体、拒绝或自定义。提案段按需写 `本章节拍:`、`本章场景:`、`本章技法:`、`本章追读:`、`知识变体:`、`本章对象:`,六者皆可空,前四者可多选,变体可多行。作者确认整份细纲后产出 `{"细纲"}`,运行 `{{cmd}} persist-outline --file=<json路径>`;若 DTO 带作品契约复盘提示,只呈报修订候选,不自动改契约。
 
 
 ## 故事对象
 ## 故事对象
-跨章复用或影响一致性的设定、人物、关系、地点、组织、能力和特殊物品才建计划对象;一次性路人、普通物品和临时地点不建档。按真实问题用 `knowledge-query --维度=设定|人物|命名` 取少量材料,作者确认后生成完整 Markdown(front matter 至少有 `ID`、`名称`,可有 `对象类型`、`正名`、`别名`;正文至少有 `## 本书设计`、`## 一致性边界`),通过 `{"作者已确认":true,"对象":[{"分类":"设定|人物","内容":"完整 Markdown"}],"放弃":[]}` 运行 `{{cmd}} persist-design --file=<json路径>`。规划与细纲用 `{{cmd}} read-design <ID或名称>` 精读;计划不是既成事实。
+跨章复用或影响一致性的设定、人物、关系、地点、组织、能力和特殊物品才建计划对象;一次性路人、普通物品和临时地点不建档。对象设计阶段按真实问题用 `knowledge-query --维度=设定|人物|命名 [--类型=受控对象类型]` 取最多三条材料;候选可以选择、组合、修改、拒绝或完全自定义,不是固定答案。作者确认后生成完整 Markdown(front matter 至少有 `ID`、`名称`,可有 `对象类型`、`正名`、`别名`;正文至少有 `## 本书设计`、`## 一致性边界`),把采用的来源与本书适配写进 `## 知识依据`(如需)。通过 `{"作者已确认":true,"对象":[{"分类":"设定|人物","内容":"完整 Markdown"}],"放弃":[]}` 运行 `{{cmd}} persist-design --file=<json路径>`。规划与细纲用 `{{cmd}} read-design <ID或名称>` 精读;写作、审查和事实转正只读本书化计划对象与已确认事实,不回读未选的通用条目。
 
 
 ## 写章流程
 ## 写章流程
 1. 备料:`{{cmd}} prepare-chapter <章号>`,读 `工作区/本章写作材料.md` 写草稿到 `工作区/草稿-A.md`。
 1. 备料:`{{cmd}} prepare-chapter <章号>`,读 `工作区/本章写作材料.md` 写草稿到 `工作区/草稿-A.md`。

+ 12 - 2
v7/src/commands/knowledge-query.js

@@ -1,4 +1,10 @@
-import { KNOWLEDGE_DIMENSIONS, MAX_KNOWLEDGE_CANDIDATES, queryKnowledge } from '../knowledge/index.js'
+import {
+  KNOWLEDGE_DIMENSIONS,
+  KNOWLEDGE_FILTER_FIELDS,
+  KNOWLEDGE_FILTER_VALUES,
+  MAX_KNOWLEDGE_CANDIDATES,
+  queryKnowledge,
+} from '../knowledge/index.js'
 
 
 /**
 /**
  * knowledge-query --维度=<十维之一> --问题=<当前未决问题> [--类型=<对象子类>]:
  * knowledge-query --维度=<十维之一> --问题=<当前未决问题> [--类型=<对象子类>]:
@@ -17,8 +23,12 @@ export async function run(args, options, ctx) {
     return { ok: false, error: '请提供 --问题=<当前未决问题>;对象知识也可同时给 --类型=<对象子类>' }
     return { ok: false, error: '请提供 --问题=<当前未决问题>;对象知识也可同时给 --类型=<对象子类>' }
   }
   }
 
 
-  const filterField = { 设定: '对象类型', 人物: '人物类别', 命名: '命名对象', 技法: '类别' }[维度]
+  const filterField = KNOWLEDGE_FILTER_FIELDS[维度]
   const 筛选 = filterField && 类型 ? { [filterField]: 类型 } : {}
   const 筛选 = filterField && 类型 ? { [filterField]: 类型 } : {}
+  const allowedTypes = KNOWLEDGE_FILTER_VALUES[维度]
+  if (类型 && allowedTypes && !allowedTypes.includes(类型)) {
+    return { ok: false, error: `--类型 在${维度}中只能是:${allowedTypes.join('、')}` }
+  }
   const candidates = await queryKnowledge(ctx.packageRoot, 维度, { 问题, 筛选 })
   const candidates = await queryKnowledge(ctx.packageRoot, 维度, { 问题, 筛选 })
   const lines = [`# ${维度}知识候选`, '']
   const lines = [`# ${维度}知识候选`, '']
   if (!candidates.length) {
   if (!candidates.length) {

+ 7 - 2
v7/src/knowledge/design.js

@@ -5,9 +5,14 @@ import { sanitizeFileName } from '../util/filename.js'
 export const DESIGN_CLASSES = Object.freeze(['设定', '人物'])
 export const DESIGN_CLASSES = Object.freeze(['设定', '人物'])
 export const DESIGN_ROOT = '大纲/创作设计'
 export const DESIGN_ROOT = '大纲/创作设计'
 
 
+export const DESIGN_OBJECT_TYPES = Object.freeze({
+  设定: Object.freeze(['世界', '规则', '机制', '能力', '物品', '组织', '制度', '地点']),
+  人物: Object.freeze(['角色', '关系']),
+})
+
 const OBJECT_TYPES = Object.freeze({
 const OBJECT_TYPES = Object.freeze({
-  设定: new Set(['世界', '规则', '机制', '能力', '物品', '组织', '制度', '地点']),
-  人物: new Set(['角色', '关系']),
+  设定: new Set(DESIGN_OBJECT_TYPES.设定),
+  人物: new Set(DESIGN_OBJECT_TYPES.人物),
 })
 })
 const REQUIRED_SECTIONS = ['本书设计', '一致性边界']
 const REQUIRED_SECTIONS = ['本书设计', '一致性边界']
 const ID_RE = /^[\p{L}\p{N}]+(?:-[\p{L}\p{N}]+)+$/u
 const ID_RE = /^[\p{L}\p{N}]+(?:-[\p{L}\p{N}]+)+$/u

+ 32 - 2
v7/src/knowledge/index.js

@@ -3,6 +3,7 @@ import path from 'node:path'
 import { parseFrontMatter } from '../storage/parsers/front-matter.js'
 import { parseFrontMatter } from '../storage/parsers/front-matter.js'
 import { extractSection } from '../util/markdown.js'
 import { extractSection } from '../util/markdown.js'
 import { sha256 } from '../util/hash.js'
 import { sha256 } from '../util/hash.js'
+import { DESIGN_OBJECT_TYPES } from './design.js'
 
 
 const REF = 'references'
 const REF = 'references'
 export const MAX_KNOWLEDGE_CANDIDATES = 3
 export const MAX_KNOWLEDGE_CANDIDATES = 3
@@ -17,6 +18,36 @@ export const KNOWLEDGE_DIMENSIONS = Object.freeze(
   Object.values(KNOWLEDGE_GROUPS).flat()
   Object.values(KNOWLEDGE_GROUPS).flat()
 )
 )
 
 
+export const NAMING_OBJECT_TYPES = Object.freeze([
+  '书名',
+  '卷名',
+  '章节名',
+  '角色',
+  '地点',
+  '组织',
+  '能力',
+  '物品',
+  '制度',
+  '术语',
+  '任务',
+  '副本',
+  '赛事',
+  '代号',
+])
+
+export const KNOWLEDGE_FILTER_FIELDS = Object.freeze({
+  设定: '对象类型',
+  人物: '人物类别',
+  命名: '命名对象',
+  技法: '类别',
+})
+
+export const KNOWLEDGE_FILTER_VALUES = Object.freeze({
+  设定: DESIGN_OBJECT_TYPES.设定,
+  人物: DESIGN_OBJECT_TYPES.人物,
+  命名: NAMING_OBJECT_TYPES,
+})
+
 const DEFINITIONS = Object.freeze({
 const DEFINITIONS = Object.freeze({
   题材: Object.freeze({ 阶段: '作品契约', 索引字段: Object.freeze(['名称']) }),
   题材: Object.freeze({ 阶段: '作品契约', 索引字段: Object.freeze(['名称']) }),
   流派: Object.freeze({ 阶段: '作品契约', 索引字段: Object.freeze(['名称']) }),
   流派: Object.freeze({ 阶段: '作品契约', 索引字段: Object.freeze(['名称']) }),
@@ -30,7 +61,7 @@ const DEFINITIONS = Object.freeze({
   }),
   }),
   人物: Object.freeze({
   人物: Object.freeze({
     阶段: '故事对象',
     阶段: '故事对象',
-    索引字段: Object.freeze(['名称', '人物类别', '关系类别', '一句话']),
+    索引字段: Object.freeze(['名称', '人物类别', '一句话']),
   }),
   }),
   命名: Object.freeze({
   命名: Object.freeze({
     阶段: '故事对象',
     阶段: '故事对象',
@@ -304,7 +335,6 @@ function adaptIndexFields(dimension, fm) {
     return {
     return {
       ...base,
       ...base,
       人物类别: fm.人物类别 ? String(fm.人物类别) : '',
       人物类别: fm.人物类别 ? String(fm.人物类别) : '',
-      关系类别: fm.关系类别 ? String(fm.关系类别) : '',
     }
     }
   }
   }
   if (dimension === '命名') {
   if (dimension === '命名') {

+ 96 - 1
v7/test/knowledge/design-pipeline.test.js

@@ -1,10 +1,14 @@
 import { test } from 'node:test'
 import { test } from 'node:test'
 import assert from 'node:assert/strict'
 import assert from 'node:assert/strict'
 import path from 'node:path'
 import path from 'node:path'
+import os from 'node:os'
 import { promises as fs } from 'node:fs'
 import { promises as fs } from 'node:fs'
 import { prepareChapterMaterials } from '../../src/prep/index.js'
 import { prepareChapterMaterials } from '../../src/prep/index.js'
 import { assembleReviewInput } from '../../src/review/index.js'
 import { assembleReviewInput } from '../../src/review/index.js'
-import { tempBookCtx } from '../commands/_helper.js'
+import { queryKnowledge } from '../../src/knowledge/index.js'
+import { validateFactChanges } from '../../src/knowledge/fact-changes.js'
+import { run as persistDesign } from '../../src/commands/persist-design.js'
+import { gitBookCtx, tempBookCtx } from '../commands/_helper.js'
 import { designFixture } from './_design-fixture.js'
 import { designFixture } from './_design-fixture.js'
 import { PLAN_PATH } from './_fact-fixture.js'
 import { PLAN_PATH } from './_fact-fixture.js'
 
 
@@ -65,3 +69,94 @@ test('细纲未声明本章对象时不扫描或全量注入计划目录', async
     await cleanup()
     await cleanup()
   }
   }
 })
 })
+
+test('故事对象候选只在规划阶段本书化;prep/review 不回读通用条目,事实转正仍走计划边界', async () => {
+  const packageRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'wnw-object-reference-'))
+  const { ctx, cleanup } = await gitBookCtx()
+  try {
+    ctx.packageRoot = packageRoot
+    const referenceDir = path.join(packageRoot, 'references', '人物')
+    await fs.mkdir(referenceDir, { recursive: true })
+    const referencePath = path.join(referenceDir, '价值自洽反派与现实代价.md')
+    await fs.writeFile(
+      referencePath,
+      [
+        '---',
+        '名称: 价值自洽反派与现实代价',
+        '人物类别: 角色',
+        '一句话: 反派只因邪恶行动,理念没有内部逻辑',
+        '---',
+        '## 规划时',
+        '',
+        '锁定价值排序与现实代价。',
+      ].join('\n'),
+      'utf8'
+    )
+    const candidates = await queryKnowledge(packageRoot, '人物', {
+      问题: '反派只因邪恶行动,理念没有内部逻辑',
+    })
+    assert.equal(candidates[0]?.名称, '价值自洽反派与现实代价')
+
+    const planContent = [
+      '---',
+      'ID: CHAR-900',
+      '名称: 对手计划',
+      '对象类型: 角色',
+      '正名: 顾衡',
+      '---',
+      '## 本书设计',
+      '顾衡要保护证人,但不能替证人做选择。',
+      '## 一致性边界',
+      '顾衡不会用牺牲无辜换取胜利。',
+      '## 知识依据',
+      `${candidates[0].来源版本}`,
+      '只采用价值排序检查,不采用固定反派身份。',
+    ].join('\n')
+    const payloadPath = path.join(ctx.repoPath, '工作区', '对象设计.json')
+    await fs.writeFile(payloadPath, JSON.stringify({ 作者已确认: true, 对象: [{ 分类: '人物', 内容: planContent }] }), 'utf8')
+    const saved = await persistDesign([], { file: payloadPath }, ctx)
+    assert.equal(saved.ok, true, saved.error)
+    await fs.writeFile(
+      referencePath,
+      [
+        '---',
+        '名称: 价值自洽反派与现实代价',
+        '人物类别: 角色',
+        '一句话: 通用条目后来发生漂移',
+        '---',
+        '## 规划时',
+        '',
+        '通用条目后来发生漂移。',
+      ].join('\\n'),
+      'utf8'
+    )
+    await fs.writeFile(
+      path.join(ctx.repoPath, '工作区', '细纲.md'),
+      '## 本章提案\n本章对象:CHAR-900\n## 本章要写到的事\n保护证人。\n',
+      'utf8'
+    )
+
+    const materials = await prepareChapterMaterials(ctx, { chapterNum: 3 })
+    assert.equal(materials.ok, true, materials.error)
+    assert.match(materials.content, /顾衡要保护证人/)
+    assert.doesNotMatch(materials.content, /通用条目后来发生漂移|反派只因邪恶行动/)
+    const draft = path.join(ctx.repoPath, '工作区', '草稿-A.md')
+    await fs.writeFile(draft, '顾衡保护证人。', 'utf8')
+    const review = await assembleReviewInput(ctx, { chapterNum: 3, draftPath: draft })
+    assert.equal(review.ok, true, review.error)
+    assert.equal(review.input.本章计划对象.length, 1)
+    assert.doesNotMatch(JSON.stringify(review.input), /通用条目后来发生漂移/)
+
+    const fact = await validateFactChanges(ctx.repoPath, [{
+      planPath: '大纲/创作设计/人物/CHAR-900-顾衡.md',
+      factPath: '定稿/设定/角色/顾衡.md',
+      content: '---\n姓名: 顾衡\n---\n## 设定\n已在正文成立。\n',
+      decision: '无冲突',
+      removePlan: true,
+    }])
+    assert.equal(fact.ok, true, fact.errors.join(';'))
+  } finally {
+    await cleanup()
+    await fs.rm(packageRoot, { recursive: true, force: true })
+  }
+})

+ 65 - 1
v7/test/knowledge/index.test.js

@@ -14,6 +14,8 @@ import {
   readEntry,
   readEntry,
   listKnowledgeEntries,
   listKnowledgeEntries,
   queryKnowledge,
   queryKnowledge,
+  KNOWLEDGE_FILTER_FIELDS,
+  KNOWLEDGE_FILTER_VALUES,
 } from '../../src/knowledge/index.js'
 } from '../../src/knowledge/index.js'
 
 
 // 真源知识库(v7 包根)——样例条目即测试夹具
 // 真源知识库(v7 包根)——样例条目即测试夹具
@@ -25,6 +27,12 @@ test('十维注册表:三阶段边界完整且没有兜底维度', () => {
   assert.deepEqual(KNOWLEDGE_GROUPS.篇章执行, ['节拍', '场景', '技法', '追读'])
   assert.deepEqual(KNOWLEDGE_GROUPS.篇章执行, ['节拍', '场景', '技法', '追读'])
   assert.equal(new Set(KNOWLEDGE_DIMENSIONS).size, 10)
   assert.equal(new Set(KNOWLEDGE_DIMENSIONS).size, 10)
   assert.equal(getDimensionDefinition('桥段'), null)
   assert.equal(getDimensionDefinition('桥段'), null)
+  assert.deepEqual(getDimensionDefinition('人物').索引字段, ['名称', '人物类别', '一句话'])
+  assert.equal(getDimensionDefinition('人物').索引字段.includes('关系类别'), false)
+  assert.deepEqual(KNOWLEDGE_FILTER_FIELDS, { 设定: '对象类型', 人物: '人物类别', 命名: '命名对象', 技法: '类别' })
+  assert.deepEqual(KNOWLEDGE_FILTER_VALUES.设定, ['世界', '规则', '机制', '能力', '物品', '组织', '制度', '地点'])
+  assert.deepEqual(KNOWLEDGE_FILTER_VALUES.人物, ['角色', '关系'])
+  assert.deepEqual(KNOWLEDGE_FILTER_VALUES.命名, ['书名', '卷名', '章节名', '角色', '地点', '组织', '能力', '物品', '制度', '术语', '任务', '副本', '赛事', '代号'])
 })
 })
 
 
 test('loadRoutes 读路由表:题材/流派两维齐全', async () => {
 test('loadRoutes 读路由表:题材/流派两维齐全', async () => {
@@ -169,11 +177,67 @@ test('queryKnowledge:对象子类筛选有消费者,缺目录与非法维度
       ['---', '名称: 组织层级', '对象类型: 组织', '一句话: 设计组织权力层级', '---', '## 设计时', '', '先定权力。'].join('\n'),
       ['---', '名称: 组织层级', '对象类型: 组织', '一句话: 设计组织权力层级', '---', '## 设计时', '', '先定权力。'].join('\n'),
       'utf8'
       'utf8'
     )
     )
+    const personDir = path.join(tmp, 'references', '人物')
+    const namingDir = path.join(tmp, 'references', '命名')
+    await mkdir(personDir, { recursive: true })
+    await mkdir(namingDir, { recursive: true })
+    await writeFile(
+      path.join(personDir, '角色欲望.md'),
+      ['---', '名称: 角色欲望', '人物类别: 角色', '一句话: 用不可替代欲望驱动长期选择', '---', '## 规划时', '', '锁定欲望、底线和可观察行动。'].join('\n'),
+      'utf8'
+    )
+    await writeFile(
+      path.join(personDir, '关系阻力.md'),
+      ['---', '名称: 关系阻力', '人物类别: 关系', '一句话: 用利益错位维持关系张力', '---', '## 规划时', '', '锁定双方利益与变化条件。'].join('\n'),
+      'utf8'
+    )
+    await writeFile(
+      path.join(namingDir, '角色命名.md'),
+      ['---', '名称: 角色命名', '命名对象:', '  - 角色', '一句话: 让名字承担身份和辨识功能', '---', '## 规划时', '', '先列语系、功能和近名冲突。'].join('\n'),
+      'utf8'
+    )
     const hits = await queryKnowledge(tmp, '设定', { 筛选: { 对象类型: '能力' } })
     const hits = await queryKnowledge(tmp, '设定', { 筛选: { 对象类型: '能力' } })
     assert.deepEqual(hits.map((entry) => entry.名称), ['能力代价'])
     assert.deepEqual(hits.map((entry) => entry.名称), ['能力代价'])
-    assert.deepEqual(await queryKnowledge(tmp, '人物', { 问题: '反派关系' }), [])
+    assert.deepEqual((await queryKnowledge(tmp, '人物', { 筛选: { 人物类别: '关系' }, 问题: '关系' })).map((entry) => entry.名称), ['关系阻力'])
+    assert.deepEqual((await queryKnowledge(tmp, '命名', { 筛选: { 命名对象: '角色' }, 问题: '角色' })).map((entry) => entry.名称), ['角色命名'])
+    assert.deepEqual((await queryKnowledge(tmp, '人物', { 筛选: { 人物类别: '角色' } })).map((entry) => entry.名称), ['角色欲望'])
     assert.deepEqual(await queryKnowledge(tmp, '桥段', { 问题: '冲突' }), [])
     assert.deepEqual(await queryKnowledge(tmp, '桥段', { 问题: '冲突' }), [])
   } finally {
   } finally {
     await rm(tmp, { recursive: true, force: true })
     await rm(tmp, { recursive: true, force: true })
   }
   }
 })
 })
+
+test('queryKnowledge:正式故事对象条目按真实未决问题命中且不超过三条', async () => {
+  const cases = [
+    ['设定', '能力太万能,能力没有代价,使用后还有后遗症', '能力代价与使用边界'],
+    ['人物', '反派只因邪恶行动,理念没有内部逻辑', '价值自洽反派与现实代价'],
+    ['命名', '名称之间近名重名和正名别名未校验', '名册近名与别名校验'],
+  ]
+  for (const [dimension, problem, expected] of cases) {
+    const hits = await queryKnowledge(packageRoot, dimension, { 问题: problem, limit: 99 })
+    assert.ok(hits.length > 0, `${dimension} 未命中真实问题`)
+    assert.ok(hits.length <= 3, `${dimension} 超过候选上限`)
+    assert.equal(hits[0].名称, expected)
+    assert.ok(hits[0].规划)
+  }
+})
+
+test('queryKnowledge:每条正式故事对象条目均可按未决问题与受控类型调用', async () => {
+  for (const dimension of ['设定', '人物', '命名']) {
+    const field = KNOWLEDGE_FILTER_FIELDS[dimension]
+    const entries = await listKnowledgeEntries(packageRoot, dimension)
+    assert.ok(entries.length > 0, `${dimension} 没有正式条目`)
+    for (const entry of entries) {
+      const values = Array.isArray(entry[field]) ? entry[field] : [entry[field]]
+      assert.ok(values[0], `${dimension}/${entry.名称} 缺少 ${field}`)
+      const hits = await queryKnowledge(packageRoot, dimension, {
+        问题: entry.一句话,
+        筛选: { [field]: values[0] },
+        limit: 99,
+      })
+      assert.equal(hits[0]?.名称, entry.名称, `${dimension}/${entry.名称} 无法精确调用`)
+      assert.ok(hits.length <= 3, `${dimension}/${entry.名称} 超过候选上限`)
+      assert.ok(hits[0]?.规划, `${dimension}/${entry.名称} 缺少规划切片`)
+    }
+  }
+})

+ 24 - 0
v7/test/knowledge/query-command.test.js

@@ -1,6 +1,8 @@
 import { test } from 'node:test'
 import { test } from 'node:test'
 import assert from 'node:assert/strict'
 import assert from 'node:assert/strict'
 import path from 'node:path'
 import path from 'node:path'
+import os from 'node:os'
+import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
 import { fileURLToPath } from 'node:url'
 import { fileURLToPath } from 'node:url'
 import { run } from '../../src/commands/knowledge-query.js'
 import { run } from '../../src/commands/knowledge-query.js'
 
 
@@ -35,3 +37,25 @@ test('knowledge-query:创意约束按未决问题给材料,不按题材固
   assert.match(result.output, /知识转化有门槛/)
   assert.match(result.output, /知识转化有门槛/)
   assert.match(result.output, /选择、组合、修改、拒绝或自定义/)
   assert.match(result.output, /选择、组合、修改、拒绝或自定义/)
 })
 })
+
+test('knowledge-query:故事对象类型筛选使用受控字段并拒绝未知类型', async () => {
+  const root = await mkdtemp(path.join(os.tmpdir(), 'wnw-object-query-'))
+  try {
+    for (const dir of ['设定', '人物', '命名']) await mkdir(path.join(root, 'references', dir), { recursive: true })
+    await writeFile(path.join(root, 'references', '设定', '能力.md'), ['---', '名称: 能力', '对象类型: 能力', '一句话: 能力有代价', '---', '## 规划时', '', '锁定代价。'].join('\n'), 'utf8')
+    await writeFile(path.join(root, 'references', '人物', '关系.md'), ['---', '名称: 关系', '人物类别: 关系', '一句话: 利益变化推动关系', '---', '## 规划时', '', '锁定变化条件。'].join('\n'), 'utf8')
+    await writeFile(path.join(root, 'references', '命名', '角色名.md'), ['---', '名称: 角色名', '命名对象:', '  - 角色', '一句话: 名字承担身份辨识', '---', '## 规划时', '', '检查近名冲突。'].join('\n'), 'utf8')
+    const person = await run([], { 维度: '人物', 类型: '关系' }, { packageRoot: root })
+    assert.equal(person.ok, true, person.error)
+    assert.match(person.output, /关系/)
+    assert.match(person.output, /规划切片/)
+    const naming = await run([], { 维度: '命名', 类型: '角色' }, { packageRoot: root })
+    assert.equal(naming.ok, true, naming.error)
+    assert.match(naming.output, /角色名/)
+    const invalid = await run([], { 维度: '人物', 类型: '反派' }, { packageRoot: root })
+    assert.equal(invalid.ok, false)
+    assert.match(invalid.error, /角色、关系/)
+  } finally {
+    await rm(root, { recursive: true, force: true })
+  }
+})

+ 35 - 3
v7/test/references/format.test.js

@@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url'
 import { promises as fs } from 'node:fs'
 import { promises as fs } from 'node:fs'
 import { parseFrontMatter } from '../../src/storage/parsers/front-matter.js'
 import { parseFrontMatter } from '../../src/storage/parsers/front-matter.js'
 import { extractSection } from '../../src/util/markdown.js'
 import { extractSection } from '../../src/util/markdown.js'
-import { loadRoutes } from '../../src/knowledge/index.js'
+import { KNOWLEDGE_FILTER_VALUES, loadRoutes } from '../../src/knowledge/index.js'
 import { CANONICAL_TOPIC_NAMES } from '../../src/knowledge/contract.js'
 import { CANONICAL_TOPIC_NAMES } from '../../src/knowledge/contract.js'
 
 
 /**
 /**
@@ -21,6 +21,12 @@ const 书级格式 = Object.freeze({
   创意约束: Object.freeze({ 字段: ['名称', '一句话'], 小节: ['约束机制', '适用边界', '本书化问题', '失败模式'] }),
   创意约束: Object.freeze({ 字段: ['名称', '一句话'], 小节: ['约束机制', '适用边界', '本书化问题', '失败模式'] }),
 })
 })
 const 书级目录 = Object.keys(书级格式)
 const 书级目录 = Object.keys(书级格式)
+const 故事对象格式 = Object.freeze({
+  设定: Object.freeze({ 字段: ['名称', '对象类型', '一句话'] }),
+  人物: Object.freeze({ 字段: ['名称', '人物类别', '一句话'] }),
+  命名: Object.freeze({ 字段: ['名称', '命名对象', '一句话'] }),
+})
+const 故事对象目录 = Object.keys(故事对象格式)
 const canonicalTopicSet = new Set(CANONICAL_TOPIC_NAMES)
 const canonicalTopicSet = new Set(CANONICAL_TOPIC_NAMES)
 
 
 async function mdFiles(dir) {
 async function mdFiles(dir) {
@@ -32,7 +38,7 @@ async function mdFiles(dir) {
 }
 }
 
 
 test('知识条目 front matter 全部可解析且有 名称', async () => {
 test('知识条目 front matter 全部可解析且有 名称', async () => {
-  for (const dir of [...章级目录, ...书级目录]) {
+  for (const dir of [...章级目录, ...书级目录, ...故事对象目录]) {
     for (const f of await mdFiles(dir)) {
     for (const f of await mdFiles(dir)) {
       const raw = await fs.readFile(path.join(ROOT, dir, f), 'utf8')
       const raw = await fs.readFile(path.join(ROOT, dir, f), 'utf8')
       const fm = parseFrontMatter(raw)
       const fm = parseFrontMatter(raw)
@@ -42,6 +48,32 @@ test('知识条目 front matter 全部可解析且有 名称', async () => {
   }
   }
 })
 })
 
 
+test('故事对象条目只保留最小索引字段和规划时切片', async () => {
+  for (const dir of 故事对象目录) {
+    const files = await mdFiles(dir)
+    assert.ok(files.length > 0, `${dir} 尚未形成正式条目`)
+    for (const f of files) {
+      const raw = await fs.readFile(path.join(ROOT, dir, f), 'utf8')
+      const fm = parseFrontMatter(raw)
+      assert.deepEqual(
+        Object.keys(fm.data).sort(),
+        [...故事对象格式[dir].字段].sort(),
+        `${dir}/${f} front matter 含无消费者字段`
+      )
+      assert.ok(extractSection(fm.body, '规划时'), `${dir}/${f} 缺「规划时」节`)
+      const allowed = KNOWLEDGE_FILTER_VALUES[dir]
+      const field = dir === '设定' ? '对象类型' : dir === '人物' ? '人物类别' : '命名对象'
+      const values = Array.isArray(fm.data[field]) ? fm.data[field] : [fm.data[field]]
+      assert.ok(values.length > 0 && values.every((value) => allowed.includes(String(value))), `${dir}/${f} 含未登记受控值`)
+      assert.ok(!values.includes('全部'), `${dir}/${f} 不得使用“全部”占位`)
+      assert.doesNotMatch(raw, /编号:|适用技能:|分类:|层级:|关键词:|意图与同义词:|适用题材:|大模型指令:|毒点:|正例:|反例:|来源:|状态:/u)
+      for (const forbidden of ['落笔时', '审稿时']) {
+        assert.equal(extractSection(fm.body, forbidden), '', `${dir}/${f} 不应携带「${forbidden}」死切片`)
+      }
+    }
+  }
+})
+
 test('章级条目三节齐全(规划这一章时/落笔时/审稿时)且毒点非空', async () => {
 test('章级条目三节齐全(规划这一章时/落笔时/审稿时)且毒点非空', async () => {
   for (const dir of 章级目录) {
   for (const dir of 章级目录) {
     for (const f of await mdFiles(dir)) {
     for (const f of await mdFiles(dir)) {
@@ -140,7 +172,7 @@ test('路由表:名称/别名唯一、维度合法,19 个题材和 24 个流
 })
 })
 
 
 test('毒点措辞不以禁止某类角色替代叙事结果核对', async () => {
 test('毒点措辞不以禁止某类角色替代叙事结果核对', async () => {
-  for (const dir of [...章级目录, ...书级目录]) {
+  for (const dir of [...章级目录, ...书级目录, ...故事对象目录]) {
     for (const f of await mdFiles(dir)) {
     for (const f of await mdFiles(dir)) {
       const raw = await fs.readFile(path.join(ROOT, dir, f), 'utf8')
       const raw = await fs.readFile(path.join(ROOT, dir, f), 'utf8')
       const fm = parseFrontMatter(raw)
       const fm = parseFrontMatter(raw)