src/export/index.js 导出纯函数层(读定稿 → 组文本)
src/commands/export.js 薄壳(scope: book)
src/migrate/
├─ read-v6.js v6 归一读取层:双形态 state + index.db + 文件树 → V6Facts
├─ transform.js 纯映射层(零 IO):V6Facts → { files: [{path, content}], report }
└─ index.js 编排:读取 → 映射 → 临时目录物化 → git 初始 commit → 缓存重建 →
rename 落位 → books.jsonl 登记 → 迁移报告落工作区
src/commands/migrate.js 薄壳(scope: workdir,同 init/list-books 先例)
docs/migration-guide.md 迁移指引(v6 用户操作步骤,B6)
research/v6-data-inventory.md)的 file:line 证据用 JS 重写,兼容口径以该文件 Q13 十条为准。export <章号> / export --range=a-b / export --all:只读定稿区(ChapterReader.readBody 已剥 front matter;标题取 readFrontMatter)。工作区/导出/(书仓 .gitignore 已含 工作区/,天然不入 git);重复导出覆盖。第0152章-标题.txt:正文体,无标题行。第0006-0012章.txt / 全书 全书-<书名>.txt(书名取 BookConfigReader):每章「第N章 标题」行 + 空行 + 正文,章间空两行——批量导入工具可按标题行分章。V6Facts = {
form: 'inline' | 'sqlite' | 'mixed', // 形态探测结果(报告用)
project: {title, genre, author}, // project|project_info 双键名兼容
progress, protagonistState, strandTracker,
chapters: [{num, title, body, sourcePath}],// 三种命名形态归一(Q13-1)
entities: [{id, type, name, aliases[], tier, isProtagonist, current{}, desc,
firstAppearance, lastAppearance}],
stateChanges: [...], relationships: [...],
foreshadowing: [{content, status, tier, plantedChapter, targetChapter,
resolvedChapter, urgency}], // 规范化别名(Q13-5)
activeThreads: [...], // plot_threads.active_threads
chapterMeta: Map<num, {hook, ...}>, // "0003"|"3" 键归一(Q13-6)
readingPower: Map<num, {hookType, hookStrength, coolpointPatterns}>,
summaries: Map<num, {frontMatter, body}>, // chNNNN.md
outlines: {master, volumes: [{n, 详细大纲, 时间线, 拆分章纲[]}]},
settingFiles: [{name, content}], // 设定集/*.md 原文
scratchpad: {7桶原文}, patterns: [...], // 两个独立文件(Q6)
warnings: [...] // 每条容错决定的如实记录
}
entities_v3 → inline;有 _migrated_to_sqlite 或 index.db 存在 → 读 db(node:sqlite 只读打开);两边都有 → db 优先、state 内联残留仅补缺(mixed,报告注明)。SELECT 包 try——表/列缺失(Q13-10 增量 schema)按空处理并记 warning,不炸。| v6 | v7 产物 | 细则 |
|---|---|---|
| 正文 | 定稿/正文/NNNN-标题.md |
补 front matter:章号/标题(无标题章名"第N章")/卷(卷布局取卷号,平坦按详细大纲卷界推断,兜底 1)/字数(现算)/章定位 迁移/钩子(chapter_meta.hook 或 reading_power:type+strength→危机钩-强 式;无数据省略字段)/情绪定位(无源省略)/本章要写到的事: ["迁移"](映射表明文);书内时间无源→省略,报告提示体检会列缺锚点属预期 |
| summaries | 定稿/摘要/章摘要/NNNN.md |
三源优先级:chNNNN.md 剧情摘要节 > chapters 表 summary 列 > 无(报告计数) |
| entities(角色) | 定稿/设定/角色/<正名>.md + 名册行 |
current{}/desc→设定节;protagonist_state 并入主角卡设定节 |
| entities(非角色)+aliases | 定稿/设定/名册.md |
\| 正名 \| 别名 \| 类型 \| 首现章 \|;alias_index/aliases 表双源归一 |
| state_changes/relationships | 角色卡「关系」节 + 定稿/设定/迁移待校对-实体变更史.md |
关系当前值进卡;历史流水不丢字入待校对 |
| foreshadowing | 大纲/伏笔/伏笔-NNN-<短题>.md |
status 未回收→进行、已回收→已收尾;tier 核心/支线/装饰→强度 高/中/低;planted→开启章+履历首行;target→预计收尾;content→描述;收尾计划缺→迁移待补(报告列为建议先看) |
| plot_threads.active_threads | 卷纲尾「迁移的剧情线」节 | 映射表明文"并入卷纲正文(不设条目)" |
| scratchpad.open_loops | 定稿/设定/迁移待校对-记忆清单.md open_loops 节 |
不并入伏笔条目(与 foreshadowing 双存,防重复污染账本;作者裁决后手动登记,见权衡 4.1) |
| scratchpad 其余 6 桶 | 同上文件分节 | 每条 MemoryItem 原文格式化,不丢字 |
| patterns(project_memory) | 文风/迁移待校对-文风候选.md |
"人工过一遍再入":作者过完并入文风铁律后删除该文件 |
| 大纲/总纲.md | 大纲/总纲.md |
原样 |
| 第N卷-详细大纲.md | 大纲/卷纲/第NN卷.md |
更名归位;拆分章纲/总纲内联章纲附录并入对应卷纲尾 |
| 第N卷-时间线.md | 定稿/设定/时间线/第NN卷.md |
表列映射 章节\|时间\|事件→章\|书内时间\|一句话事件\|在场(空) |
| 设定集/*.md | 定稿/设定/<原名>.md |
自由 md 原样搬(不强行结构化,不丢字优先);世界观.md 恰与 v7 同名同位 |
| project/genre | book.yaml |
title→书名;genre 小码表(xianxia→仙侠 等常见项)映射,未知原值+报告提示;每章目标字数等取 v7 默认 |
| chase_debt/override_contracts/debt_events | 丢弃 | 债务台账 v7 无对应体系;报告如实列计数(Q5 结论) |
| .story-system/、vectors.db、rag.db、projection_log、context_cache、observability、backups、archive、locks | 跳过 | 全部派生可重建(Q8/Q12);报告列跳过清单 |
迁移待校对- 前缀(可 grep);报告「建议先看哪」按固定优先级:书名/类型 → 伏笔收尾计划 → 记忆清单 → 文风候选。--dir 可指定)。.migrate-tmp-<pid>:写全部文件(防呆序列化器出 front matter)→ git init + .gitignore(persistCreateBook 先例)→ 单个初始 commit(init: 迁移自 v6(原 <路径>),即"提交链压成初始 commit")→ CacheManager 全量重建(同时验证格式自洽——重建器即参考实现)。rename 到最终目录 → books.jsonl 登记(M5 写侧)→ 迁移报告写入 <书>/工作区/迁移报告.md。三节:迁了什么(逐映射行计数表:N 章正文、N 条伏笔、N 个角色卡…)|待校对清单(每个 迁移待校对- 文件一行 + 建议先看优先级)|如实丢弃(债务台账 N 条、派生库清单、每条 warning)。