真源:.trellis/tasks/07-05-m1-m7-review/review-m1-m7.md(汇总)+ research/review-{A..G}.md(细节与行号)。本文按修复批次给出每条的技术方案与关键决策;实现时以分区报告的 file:line 为准。
| # | 决策 | 内容 |
|---|---|---|
| D1 | 实体类型值规范(G-3/R5) | 名册文件(作者域)写中文(角色/地点/组织/物品),缓存 entities.type(机器域)存英文 machine 值。入库点做归一映射 normalizeEntityType():角色→character、地点→location、组织/势力→organization、物品→item;英文值恒等收纳(character→character);未知值原样保留+warning。fixture 名册的 character 改中文「角色」(消除 G-2 掩盖源)。 |
| D2 | 一对多别名降级(R10) | 迁移时同一别名指向多实体 → 按 facts.entities 数组序首实体保留该别名,其余实体剥离;剥离明细写 定稿/设定/迁移待校对-别名歧义.md 并进迁移报告待校对清单。整体迁移不再硬回滚。 |
| D3 | finalize-batch 幂等重跑(E3 尾链) | 转正循环顺序改为「先更新批次.json(移除该章行)再删章目录」;目录删除失败 → 人话错误 + 指引重跑;finalizeBatch 开头对目标章做已定稿检测(定稿文件已存在且章号 ≤ 缓存 maxChapter → 跳过转正、只清目录),重跑不会二次定稿撞「已存在」。 |
| D4 | 源只读硬承诺(F-6) | openReadOnly 的可写回退分支删除;readOnly 打开失败直接人话报错("源数据库无法只读打开(可能被占用),关闭占用程序后重试")。 |
| D5 | imagery_top 陈旧(B) | rebuild 加 preserveDerived 选项:删缓存裸重建=保留(丢失重测无害的对称面),改源刷新(refreshCacheAfterSourceChange)=作废 imagery_top(宁可无提醒不给陈旧提醒)。 |
| D6 | 序2/relink/goto 脏树范围(D3 区) | 定稿/大纲 前缀集合扩为 定稿/大纲/文风/book.yaml,抽单一常量 TRACKED_SOURCE_PREFIXES 供 detectors.listManualEdits、relink、goto dirtyScoped 三处共用(防双写)。修复后的文风铁律/book.yaml 由此走序2 补登入档。 |
test/commands/_helper.js gitBookCtx 对齐 persistCreateBook(state-machine/persist.js:58-78):写 .gitignore(.cache/、工作区/)、git config core.quotepath false、只 add 跟踪面(不 add -A)。存量测试若依赖"工作区被跟踪"的断言,按真实形态修正断言(测试是探针不是约束)。
state-machine/flows/goto-chapter.js:入口(needsConfirm 判定前)readBatch(repoPath),exists → {ok:false} 人话:"有进行中的待定稿批次(第 X-Y 章)。先 finalize-batch 转正或 batch-discard 丢弃,再回退。" 附 gitHealth 照常返回。commands/finalize.js:同守卫。文案区分两种情况:目标章在批内("第 N 章已在待定稿批次中,用 finalize-batch 转正")/ 批次存在但目标章不在批内("有进行中批次,手动定稿会造成章号错位")。state-machine/persist.js:118-127:新增 validateRepairContent(file, content) 按路径分派——book.yaml → parseBookConfig;定稿/设定/名册.md、定稿/设定/时间线/ 前缀 → parseMarkdownTable;其余 → parseFrontMatter。分派规则与 detectors.js 检测器选型对齐(同一映射,注释互指)。
finalize/index.js:141-144:
fs.rm(..., { recursive: true, force: true });失败只收集 warning,不改写 ok(commit 已发生),返回 {ok:true, commitHash, warnings}。util/workspace-path.js normalizeWorkspaceRel(name)——剥 工作区/ 前缀 + 按路径段拒绝 ..(split(/[\\/]/).includes('..'),替换 stageChapter 的 includes('..') 过宽判定=C4)。finalizeChapter、stageChapter、commands/finalize.js、commands/stage-chapter.js 四处共用(消除双源)。storage/serializers/yaml-dialect.js:
needsQuoting 补:空串;value !== value.trim();带符号/十六进制/科学计数数字形态(/^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?$/、/^0[xXoObB]/ 且后随合法数字);~、.inf/.nan 变体;起首 YAML 指示符 [ ] { } * & ! | > @ % \ " ' ,;? 起首;含 #`(行内注释)。\ → \\(先做)、" → \"、\n → \n、\r、\t、其余 C0 控制字符 → \xXX。""、" 甲 "、0x1F、1e3、+5、~、[快穿]反派、*追读、"引号"书、- 开局、C:\Users\x、*ptr、&a、[TBD)serialize→parse 逐一断言相等。storage/atomic.js:plan 增加 renamedIn 标志(第二循环 rename(tmp→final) 成功后置 true)。restorePlan 改为:rm(tmp) → if (renamedIn) rm(final) → if (existed) rename(backup→final)。existed=false 且 renamedIn=false 时绝不碰 final(本 bug 场景:原文件未动过)。附故障注入测试:mock writeFile 对第 2 个文件抛错,断言已存在的 B 原文完好。
cache/rebuilder.js:257(scanEntities)与 storage/adapters/EntityReader.js 文件降级路径:经 util/entity-type.js normalizeEntityType() 入库/过滤。test/fixtures/sample-book/定稿/设定/名册.md 类型列改中文;migrate/transform 名册照写中文(现状已对)。staging 的 splitAliases(切 [,,、]、容 undefined/数组)抽到 util/aliases.js;EntityReader.js:87、cache/rebuilder.js:262、staging 三处共用。A13(名册缺「别名」列 undefined.split 抛错→整次重建 ROLLBACK)由 splitAliases 的 undefined 容错顺带修掉。
migrate/transform.js:41-48 手写拼接改 serializeYAML({ spec_version:'7.0', 书名, 类型, 每章目标字数:3000, 卷规模:40 })。前置依赖 R7([/* 起首书名先由 needsQuoting 兜住)。
migrate/transform.js 名册生成前全实体扫别名:Map<别名, 首实体>,重复别名从后续实体剥离,剥离清单写 迁移待校对-别名歧义.md + report.待校对。
serializers/markdown-table.js:单元格 | → \|,换行 → 空格(+warning 语义:表格单元格不含换行)。parsers/markdown-table.js:识别 \| 转义(先占位替换再 split 再还原);行首为全角 | 时整行归一为 |(救"整表全角"场景);行 cell 多于表头时不再静默截断——保留进行对象(headers 并集),或至少 push warning。EntityWriter.upsertRosterRow:rows[idx] = { ...旧行, ...新行 } 合并(保作者额外列);serializeMarkdownTable 的 headers 取传入 headers ∪ 行内额外键(额外列追加在尾),全表重写不再丢列。storage/adapters
_findThreadFile(ThreadLedgerReader:213、ThreadLedgerWriter:99)与 SecretReader:115 改有界匹配 f === id+'.md' || f.startsWith(id+'-')(与 createThread 同口径)。sanitizeFileName(name);finalize/index.js:93 的角色卡路径改用 EntityWriter 返回的 filePath(消除路径双源)。includes 改精确标题匹配(^##\s*标题\s*$)。cache
staging / dto
--until 后剩余章全为待审收时,建议改为「finalize-batch 转正剩余 X 章」。isWeakHook()(落 style-stats 或 util),staging×2、book-status、report-weak-hook-streak 四处共用。heal=false 参数,dto 读路径不落盘自愈;D6:detectors.js:129 待定稿现存判定改用 readBatch().exists 口径。state-machine / flows
r.gitHealth?.fixed?.length 防御。migrate / export
Array.from(s).slice(0,9).join('')。.migrate-tmp-*(并行进程的活跃 tmp 不误删)。character 等英文类型也出卡)。_)。bin / docs
/^[a-z0-9-]+$/,不匹配走「未知命令」。docs/(migration-guide 随 npm 可达),迁移报告尾部指路一句。spec / 文档(AC4)
\| 转义读写同批落地,旧表无 \| 不受影响。