소스 검색

docs(v7): M7 spec 回填——spec 0.13(决策 38)/计划出口达成(进入 beta)+ 任务工件

- story-repo-spec 0.13:§4.7 导出实现口径(.txt/标题行分章/只导定稿区);§12 迁移实现注记
  (v6 双形态读取、「index.db 删除」正音为读完不带入、迁移待校对前缀、整体回退、如实丢弃)
- v7-implementation-plan §M7 出口达成标注:代码面达成即进入 beta;真写 50 章与 ≥3 真实
  v6 项目验证是 beta 期活动;npm 发版沿 M5 决策推迟
- backend error-handling §3.6:一次性建仓的目录级原子边界(临时目录建仓+rename 落位+幂等清扫)
- M7 任务工件(prd/design/implement + research/v6-data-inventory.md),AC1-AC5/AC7 复核打勾
lingfengQAQ 7 시간 전
부모
커밋
557bd424d3

+ 2 - 0
.trellis/spec/backend/error-handling.md

@@ -39,6 +39,8 @@
 
 3.5 同章重写导致旧文件名变化时,旧章文件的删除/挪走也属于本次定稿变更,必须纳入同一回滚边界:commit 成功前保留可恢复备份,commit 失败时恢复旧文件并清除新文件,禁止留下"旧章消失、新章未提交"的中间态。
 
+3.6 **一次性建仓流程(迁移等)的目录级原子边界**(M7 起):在最终落位的同一磁盘卷内用临时目录完整构建(全部文件 + git 初始 commit + 缓存重建校验),全部成功后一次 `rename` 落位;任一步失败必须删除临时目录整树,工作目录零残留。流程启动时必须幂等清扫本流程前缀的历史残留临时目录;迁移的源目录全程只读。
+
 ## 4. 面向作者的错误文案
 
 4.1 必须全中文、自然流畅;禁止出现堆栈、错误码、英文术语,也不强求口语化。

+ 1 - 0
.trellis/tasks/07-05-m7-export-migrate/check.jsonl

@@ -0,0 +1 @@
+{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

+ 110 - 0
.trellis/tasks/07-05-m7-export-migrate/design.md

@@ -0,0 +1,110 @@
+# M7 design:干净导出 + /migrate
+
+## 1. 总体结构:两个独立模块,migrate 是一次性脚本不进状态机
+
+```
+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)
+```
+
+- 导出与迁移零耦合;两者都不动状态机/写章流程(例外流程命令,SKILL「例外流程」段各加一行)。
+- migrate 不复用 v6 Python 代码(v7 零 python 铁律);v6 语义按 research(`research/v6-data-inventory.md`)的 file:line 证据用 JS 重写,兼容口径以该文件 Q13 十条为准。
+
+## 2. 导出契约(spec §4.7)
+
+- `export <章号>` / `export --range=a-b` / `export --all`:只读**定稿区**(ChapterReader.readBody 已剥 front matter;标题取 readFrontMatter)。
+- 落点 `工作区/导出/`(书仓 .gitignore 已含 工作区/,天然不入 git);重复导出覆盖。
+- 文件形态(PRD A2 默认):
+  - 单章 `第0152章-标题.txt`:正文体,无标题行。
+  - 范围 `第0006-0012章.txt` / 全书 `全书-<书名>.txt`(书名取 BookConfigReader):每章「第N章 标题」行 + 空行 + 正文,章间空两行——批量导入工具可按标题行分章。
+- 边界:章号不存在/范围含空洞→人话报错并列缺章;定稿区空→人话报错。范围端点自动夹取(a>b 报错)。
+- 零 token、同步纯读;不碰缓存(直读文件,章数即使 2000 也只是顺序读)。
+
+## 3. migrate 数据流
+
+### 3.1 read-v6.js → V6Facts(归一层,只读源)
+
+```js
+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: [...]                            // 每条容错决定的如实记录
+}
+```
+
+- **形态探测**:state.json 有 `entities_v3` → inline;有 `_migrated_to_sqlite` 或 index.db 存在 → 读 db(node:sqlite 只读打开);两边都有 → db 优先、state 内联残留仅补缺(mixed,报告注明)。
+- **db 容错**:逐表 `SELECT` 包 try——表/列缺失(Q13-10 增量 schema)按空处理并记 warning,不炸。
+- **state.json 空/损坏**(Q13-9):整体按缺失处理,正文/大纲/设定集纯文件面照迁,报告醒目说明"运行态数据不可读,仅迁移了文件"。
+- 源 v6 项目**全程零写入**(AC4 只读断言)。
+
+### 3.2 transform.js → 文件计划(纯函数,逐 §10.3 映射行)
+
+| 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);报告「建议先看哪」按固定优先级:书名/类型 → 伏笔收尾计划 → 记忆清单 → 文风候选。
+
+### 3.3 index.js 物化与原子性
+
+1. 目标目录名 = 净化书名(ChapterWriter 同源 sanitize),已存在→人话拒绝(`--dir` 可指定)。
+2. 工作目录内建临时目录 `.migrate-tmp-<pid>`:写全部文件(防呆序列化器出 front matter)→ `git init` + `.gitignore`(persistCreateBook 先例)→ **单个初始 commit**(`init: 迁移自 v6(原 <路径>)`,即"提交链压成初始 commit")→ CacheManager 全量重建(同时验证格式自洽——重建器即参考实现)。
+3. 全部成功 → 同盘 `rename` 到最终目录 → books.jsonl 登记(M5 写侧)→ 迁移报告写入 `<书>/工作区/迁移报告.md`。
+4. 任一步失败 → 删临时目录整树,工作目录零残留(AC4);源 v6 项目本就只读。
+
+### 3.4 迁移报告(人话)
+
+三节:**迁了什么**(逐映射行计数表:N 章正文、N 条伏笔、N 个角色卡…)|**待校对清单**(每个 `迁移待校对-` 文件一行 + 建议先看优先级)|**如实丢弃**(债务台账 N 条、派生库清单、每条 warning)。
+
+## 4. 权衡记录
+
+- 4.1 **open_loops 不自动并入伏笔条目**:foreshadowing 与 scratchpad.open_loops 双存语义相近(research Q4 附注),自动合并会产重复条目污染"悬了太久"账本;宁可待校对区多一步人工。
+- 4.2 **摘要三源取最富**(summaries 文件 > db summary 列 > 无):人写的 summaries 信息最全;research caveat"三处每章元数据重叠"以此定序。
+- 4.3 **缺书内时间/情绪定位等字段省略而非编造**:迁移章 front matter 只写有据字段;体检"缺时间锚点"全列属预期,报告先说破——诚实优于好看。
+- 4.4 **设定集自由 md 不强行结构化**:主角卡/反派设计等是自由格式,硬拆 front matter 必然臆断;原样搬 + 实体数据另生成角色卡,两者并存交作者收敛。
+- 4.5 **临时目录在工作目录内**:保证与最终位置同盘,Windows rename 原子且不跨卷;不用系统 tmp。
+- 4.6 **fixture 内建 db**:测试用 node:sqlite 现场建最小 index.db(DDL 摘 research Q3 关键表),不提交二进制文件。
+
+## 5. 回滚与安全
+
+- 导出:纯派生输出,无回滚问题。
+- 迁移:源只读 + 临时目录构建 + 失败整删——"整体回退"即"没发生";成功后不满意=删新书目录(未污染任何既有书)。
+- migrate/export 均为新增命令,revert 单 commit 可退,零既有行为变更。

+ 1 - 0
.trellis/tasks/07-05-m7-export-migrate/implement.jsonl

@@ -0,0 +1 @@
+{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

+ 49 - 0
.trellis/tasks/07-05-m7-export-migrate/implement.md

@@ -0,0 +1,49 @@
+# M7 实施清单
+
+执行方式:inline(M1-M6 先例),三期每期一 commit,期末验证绿才进下期。上下文顺序:prd → design → research/v6-data-inventory.md → 本清单;Phase 2 起步走 trellis-before-dev。
+
+## P1 干净导出
+
+- [x] P1.1 `v7/src/export/index.js`:exportChapters(ctx, {mode, chapterNum?, range?}) 纯函数层(design §2:单章无标题行、合并文件「第N章 标题」分隔、边界人话报错)
+- [x] P1.2 `v7/src/commands/export.js` 薄壳(scope: book)+ bin --help 补行
+- [x] P1.3 测试 `v7/test/export/index.test.js` + `v7/test/commands/export.test.js`:
+  - 单章:去 front matter 逐字节断言、文件名 `第NNNN章-标题.txt`、正文体无标题行
+  - 范围/全书:标题行与章间距格式、书名文件名、覆盖旧导出
+  - 边界:缺章列清单报错、a>b 报错、空定稿区人话
+  - 落点 `工作区/导出/`(gitignore 生效 = git status 干净断言)
+
+验证:`node --test v7/test/export/ v7/test/commands/export.test.js`(列文件跑)
+提交:`feat(v7): M7 P1——干净导出(单章/范围/全书)`
+
+## P2 migrate 读取与映射(纯函数面,零落盘)
+
+- [x] P2.1 `v7/src/migrate/read-v6.js`:双形态探测 + state.json 键名/别名归一(Q13-3/4/5/6)+ index.db 只读容错读(逐表 try,Q13-10)+ 正文三命名归一(Q13-1)+ summaries/大纲/设定集/两记忆文件读取;空损坏 state.json 降级(Q13-9)
+- [x] P2.2 `v7/src/migrate/transform.js`:design §3.2 映射表逐行实现(front matter 组装用防呆序列化器;伏笔状态/强度映射;名册/时间线表生成;待校对三文件;genre 小码表)+ 报告数据结构
+- [x] P2.3 fixture:`v7/test/fixtures/v6-inline/`(全量内联形态:三种正文命名混用、chapter_meta 双键格式、伏笔别名 status、project 键名)+ `v7/test/fixtures/v6-sqlite/`(精简 state + 测试内现场建 index.db,DDL 摘 research Q3:entities/aliases/state_changes/relationships/chapter_reading_power)——坑:根 .gitignore `.webnovel/` 会吞 fixture,已加否定规则(目录先重包含)
+- [x] P2.4 测试 `v7/test/migrate/read-v6.test.js` + `transform.test.js`:两形态读出的 V6Facts 等价;映射表每行至少一断言(AC2/AC3 的纯函数半);缺表/缺文件/损坏 state 容错各一例
+
+验证:`node --test v7/test/migrate/`(列文件)
+提交:`feat(v7): M7 P2——v6 双形态归一读取与映射纯函数`
+
+## P3 migrate 物化 + 命令 + 指引收口
+
+- [x] P3.1 `v7/src/migrate/index.js`:临时目录物化 → git init + 初始 commit → 缓存重建 → 同盘 rename → books.jsonl 登记 → 迁移报告落工作区(design §3.3/§3.4);启动清扫 `.migrate-tmp-` 残留
+- [x] P3.2 `v7/src/commands/migrate.js`(scope: workdir,`--dir` 可选)+ bin --help;SKILL.md 例外流程加 export/migrate 两行 + 壳重渲染 + drift 绿
+- [x] P3.3 `v7/docs/migration-guide.md` 迁移指引(卸载市场版 → npx init → migrate → 校对清单;命令名/参数与实现一致,AC7)
+- [x] P3.4 测试 `v7/test/migrate/e2e.test.js`:
+  - AC2 端到端两形态:migrate → 仓库结构合规 → `next` 判定进正常流程 → 删缓存重建一致
+  - AC3 不丢字:fixture 每段 v6 文本在 v7 产物/待校对区 grep 到(逐映射行抽样)
+  - AC4 回退:注入落盘中途失败 → 工作目录零残留 + 源 v6 目录 mtime/内容未动
+  - AC5 报告三节断言;目标目录已存在拒绝;books.jsonl 登记断言
+- [x] P3.5 全量 `node --test`(Windows 本机)429 绿 + AC1-AC7 复核、prd.md 打勾(AC6 CI 部分待 push)
+- [x] P3.6 spec 回填(Phase 3.3):story-repo-spec 0.13(§4.7 导出实现口径;§12 迁移实现注记——双形态/待校对前缀/整体回退/如实丢弃;决策 38);实施计划 §M7 出口达成标注(代码面达成即进入 beta);backend error-handling §3.6 目录级原子边界
+- [ ] P3.7 commit + push,CI 双平台绿;prd.md AC6 回填 run 号
+
+提交:`feat(v7): M7 P3——migrate 物化/命令/指引` + `docs(v7): M7 spec 回填`
+
+## 风险与回滚点
+
+- migrate 是新增独立模块,不碰主循环热路径;export 只读定稿——两者对既有 407 测试零影响(回归面=全量绿)
+- Windows 中文路径 + v6 fixture 文件名(第NNNN章-标题.md)在 CI windows job 全覆盖;fixture 内建 db 避免二进制入库
+- 临时目录若因进程中断遗留:目录名带 `.migrate-tmp-` 前缀,migrate 启动时清扫同前缀残留(幂等)
+- 每期独立 commit 可 revert;migrate 失败路径不触碰既有书目录与 books.jsonl

+ 62 - 0
.trellis/tasks/07-05-m7-export-migrate/prd.md

@@ -0,0 +1,62 @@
+# M7 导出与 v6 迁移:干净导出、/migrate 一次性脚本、beta 入口
+
+## Goal
+
+v7 最后一个功能里程碑(实施计划 §M7):作者发布用的**干净导出**(单章/范围/全书,去 front matter)+ v6 老用户的 **/migrate 一次性迁移**(映射表、迁移报告、整体回退)。出口 = 进入 beta(用 v7 真实写一本书到 50 章是 beta 期活动,不在本任务内;`/migrate` ≥3 真实 v6 项目跑通是 7.0.0 判据,beta 期内完成)。
+
+## Background(已确认事实,均有出处)
+
+- **导出定义**(spec 0.12 §4.7):一键导出去 front matter 纯正文,单章/章范围/全书,落 `工作区/导出/`(不进 git);发布到起点/番茄用。零 token 脚本。
+- **迁移映射表**(PRD 1.2 §10.3 = spec §12,两处一致):正文/→定稿/正文/(补 front matter,"要写到的事"标"迁移");设定集/→定稿/设定/;大纲/→总纲+卷纲;foreshadowing→大纲/伏笔/ 逐条成文件;plot_threads→并入卷纲正文(不设条目);chase_debt/reading_power→知识层与章属性(不设条目);.webnovel/state.json→一次性展开进 定稿/设定/;summaries/→定稿/摘要/;project_memory patterns/scratchpad→文风/文风铁律.md(**人工过一遍再入**);.story-system/ 提交链→压成初始 commit、原目录只读归档;index.db/vectors.db/projection_log→删除。
+- **迁移体验底线**(PRD §2.2 旅程二):迁移不丢任何一个字;v6 的伏笔/剧情线/追读账本全部有去处;失败可整体回退。迁移报告人话列出:迁了什么、哪些条目标了"迁移待校对"、建议先看哪。迁移引导首先处理市场版插件卸载 + npx 重装。
+- **v6 数据面关键事实**:v6 5.4 起 `migrate_state_to_sqlite.py` 把 entities_v3/alias_index/state_changes/structured_relationships 迁入 `.webnovel/index.db`,state.json 只留精简键(progress/protagonist_state/strand_tracker/plot_threads/world_settings 骨架等)——**真实 v6 项目的实体真源可能在 db 里**。映射表"index.db 删除"只能理解为"不带入 v7",/migrate 必须兼容两种形态:老 state.json 全量、新 state.json+db 分置(详细清单见 research/v6-data-inventory.md)。
+- **v6 样本**:`webnovel-writer/agents/evals/files/test-project/`(v6 源码在工作树可直接研究)。
+- **发布判据关联**(PRD §7 / 实施计划 §4):7.0.0 = `/migrate` ≥3 真实 v6 项目 + beta 期无数据丢失级 bug + **迁移指引文档齐**。beta = 真写一本书到 50 章(M7 出口后启动,用户手测)。
+- **既有可复用件**:ChapterReader(front matter 剥离)、`resolveRunContext`(定位)、writeAtomicBatch(原子写)、git.js(commit 单点)、CacheManager.rebuildFromSource(迁移后建缓存)、persistCreateBook(建书 git init + 初始 commit 先例)、防呆序列化器(迁移产物 front matter 写出)。
+
+## Requirements
+
+### A. 干净导出
+- A1 `export` 命令三形态:`export <章号>`(单章)/ `export --range=a-b`(范围)/ `export --all`(全书);只导**定稿区**正文(工作区草稿/待定稿批次不可发布,宪法级),去 front matter,落 `工作区/导出/`
+- A2 文件形态(默认方案,beta 期可按作者反馈调):`.txt` 纯文本;单章 `第0152章-标题.txt`,正文体不带标题行(发布界面标题另填,文件名已含);范围/全书合并单文件(`第0006-0012章.txt` / `全书-<书名>.txt`),每章前加「第N章 标题」行 + 空行分隔(批量导入工具可解析)
+- A3 边界:范围越界/空定稿区人话报错;重复导出覆盖旧文件(导出是派生物)
+
+### B. /migrate
+- B1 `migrate <v6项目路径>` 一次性脚本:按 §10.3 映射表全量迁移,产出 v7 书仓库并登记 books.jsonl;迁移后 `next` 直接可用(衔接主旅程 ④)
+- B2 双形态输入:老 state.json 全量 / 5.4+ state.json+index.db 分置,同一映射结果
+- B3 迁移报告(人话,落工作区):迁了什么(逐映射行计数)、"迁移待校对"清单、建议先看哪
+- B4 整体回退:任何失败不留半成品(目标目录整体删除或不落地;源 v6 项目**永远只读**)
+- B5 不丢字:v6 全部文本内容在 v7 有去处;"人工过一遍再入"类(project_memory patterns/scratchpad、plot_threads 等)落**迁移待校对区**,不静默丢弃
+- B6 迁移指引文档(v6 用户操作步骤:卸载市场版 → npx 装 → migrate → 校对清单)
+
+### C. beta 入口
+- C1 M7 出口自检:AC 全绿 + CI 双平台绿 → 实施计划 §M7 标注出口达成、进入 beta
+
+## Acceptance Criteria
+
+- [x] AC1 导出三形态:单章/范围/全书各有测试——去 front matter 逐字节断言、文件名与标题行格式、范围边界与空区人话报错、落点 `工作区/导出/`(`test/export/index.test.js` + `test/commands/export.test.js`)
+- [x] AC2 migrate 端到端(合成 fixture 覆盖全映射表):老/新两种 v6 形态各一 fixture → 迁移后 v7 仓库结构合规(章 front matter 补齐且"要写到的事"标"迁移"、伏笔逐条成文件、名册/角色卡、摘要、卷纲)→ `next` 判定进正常写章流程(序 6 起草第 4 章)→ 删缓存重建一致(`test/migrate/` 三文件)
+- [x] AC3 不丢字演练:fixture 内每一段 v6 文本在 v7 产物或待校对区可 grep 到(14 类文本逐映射行抽样,`e2e.test.js`)
+- [x] AC4 回退演练:注入中途失败 → 目标目录无半成品残留、源 v6 项目未被改动(逐文件指纹断言);残留临时目录幂等清扫
+- [x] AC5 迁移报告内容断言:计数、待校对清单、如实丢弃三节齐;books.jsonl 登记 + 设当前书;目标目录已存在拒绝
+- [ ] AC6 全量测试绿 + CI 双平台绿(Windows 中文路径含 v6 fixture)——本机 429 绿 + drift 绿;CI run 号待 push 回填
+- [x] AC7 迁移指引文档存在且步骤与实现一致(`v7/docs/migration-guide.md`:卸载市场版→npx init→migrate→校对,命令名/参数与实现逐一核对)
+
+## Out of Scope
+
+- ❌ beta 真写 50 章(用户手测活动,M7 出口后启动)
+- ❌ /migrate ≥3 真实 v6 项目验证(7.0.0 判据,beta 期内完成;M7 用合成 fixture,用户后续提供真实项目可加手测)
+- ❌ npm 真发布与版本号提升(M5 已决策:推迟 beta)
+- ❌ vectors.db / projection_log 内容转换(确认为派生物即直接不迁移)
+- ❌ v6 市场版插件的卸载自动化(指引文档写清步骤即可,卸载动作归宿主/用户)
+
+## 决策记录(规划期)
+
+- **D1 测试输入=合成 fixture 为主**:从 v6 源码 schema + evals test-project 构造覆盖全映射表的两形态 fixture(老 state.json 全量 / 新 db 分置);M7 验收不依赖真实数据,真实项目验证按发布判据留 beta 期。(用户暂离未答,采推荐项;若用户提供真实 v6 项目则追加开发期手测,不入库。)
+- **D2 导出文件形态采 A2 默认方案**:产品细节低风险可逆,beta 期按作者反馈调整;不为此阻塞规划。
+- **D3 M7 纯代码不发版**:M5 已决策 npm 发布/升 7.0.0/README 同步推迟 beta;beta 手测继续用 pack 产物。
+- **D4 migrate 读取面兼容 v6 双形态**:5.4 迁移脚本证实实体真源可能在 index.db(`webnovel-writer/scripts/data_modules/migrate_state_to_sqlite.py` 文档串),"index.db 删除"语义修正为"读完不带入 v7"。若与 research 结论冲突,回改本节。
+
+## Open Questions
+
+(无阻塞项——D1/D2 为默认决策,用户可回来推翻;推翻 D1 影响 AC2 输入面,推翻 D2 只改 A2 文案与测试断言。)

+ 245 - 0
.trellis/tasks/07-05-m7-export-migrate/research/v6-data-inventory.md

@@ -0,0 +1,245 @@
+# Research: v6 数据存储面盘点(供 v7 `/migrate` 设计)
+
+- **Query**: 盘点 v6 书项目的数据存储面(目录布局、state.json、index.db、伏笔/追读力/记忆/摘要/大纲/设定/story-system/向量),为 v7 一次性迁移脚本提供事实依据
+- **Scope**: internal(v6 源码在工作树 `webnovel-writer/`,Python)
+- **Date**: 2026-07-05
+- **代码真源**: `webnovel-writer/scripts/`(多为软链/可执行脚本),核心在 `webnovel-writer/scripts/data_modules/`
+- **样本项目(on-disk 真形态)**: `webnovel-writer/agents/evals/files/test-project/`
+
+> 路径锚点均以仓库根为基准。`.py:行号` 为证据。凡"未确认"处已显式标注。
+
+---
+
+## Findings
+
+### Q1. 书项目根目录完整布局
+
+单一真源是 `DataModulesConfig`(`webnovel-writer/scripts/data_modules/config.py:90-141, 333-343`)。一个 v6 项目 = 项目根目录下:
+
+| 路径 | 类型 | 内容 | 证据 |
+|---|---|---|---|
+| `.webnovel/` | 目录 | 运行态数据根 | `config.py:98-99` |
+| `.webnovel/state.json` | JSON | 精简运行态(进度/主角/伏笔/节奏…) | `config.py:102-103` |
+| `.webnovel/index.db` | SQLite | 实体/别名/状态变化/关系/追读力/审查等 | `config.py:110-111` |
+| `.webnovel/vectors.db` | SQLite | RAG 向量 + BM25(可重建) | `config.py:337-339` |
+| `.webnovel/rag.db` | SQLite | 见 Q12(疑似历史遗留,运行时用 vectors.db) | `config.py:333-335` |
+| `.webnovel/memory_scratchpad.json` | JSON | 长期记忆 scratchpad(7 桶) | `config.py:106-107` |
+| `.webnovel/project_memory.json` | JSON | `/webnovel-learn` 学到的 patterns(**独立文件**) | `project_memory.py:59` |
+| `.webnovel/summaries/` | 目录 | 章摘要 `chNNNN.md`(YAML 头 + 正文) | `config.py`(无常量,见 Q7)/ `init_project.py:284` |
+| `.webnovel/backups/` | 目录 | 备份 | `init_project.py:282`, `backup_manager.py:190` |
+| `.webnovel/archive/` | 目录 | 归档(旧章节/实体) | `init_project.py:283`, `archive_manager.py:75` |
+| `.webnovel/observability/` | 目录 | 可观测性日志 | `observability.py:65` |
+| `.webnovel/projection_log.jsonl` | JSONL | 读模型投影运行日志(可重建) | `projection_log.py:14` |
+| `.webnovel/context_cache.json` | JSON | 上下文缓存(gitignore,可重建) | `init_project.py:665`, `backup_manager.py:125` |
+| `.webnovel/*.lock` / `*.bak` | 锁/备份 | 原子写临时物(gitignore) | `init_project.py:666-667` |
+| `正文/` | 目录 | 章节正文,纯正文无头部(见 Q10) | `config.py:116-117` |
+| `设定集/` | 目录 | 世界观/力量体系/角色卡等 md(见 Q11) | `config.py:120-121` |
+| `大纲/` | 目录 | 总纲/卷详细大纲/时间线(见 Q9) | `config.py:124-125` |
+| `.story-system/` | 目录 | 题材契约产物(见 Q8),**init 不生成,按需生成** | `config.py:128-141` |
+
+**正文章节文件命名**(`chapter_paths.py`,明确支持两种历史布局,见文件头 `chapter_paths.py:4-9`):
+- 平坦(init 默认):`正文/第0004章-标题.md`,章号 **4 位零填充**,标题来自详细大纲(有则拼,无则 `第0004章.md`)—— `chapter_paths.py:101-106, 138-155`
+- 卷布局:`正文/第1卷/第007章-标题.md`,章号 **3 位零填充**,每卷默认 50 章 —— `chapter_paths.py:24-27, 122-124, 150-152`
+- 遗留纯章号:`正文/第0007章.md`(无标题) —— `chapter_paths.py:118`
+- 查找兜底会 `rglob 第{n:03d}章*.md` / `第{n:04d}章*.md` 全 `正文/` 递归 —— `chapter_paths.py:130`
+
+---
+
+### Q2. `.webnovel/state.json` 完整键清单
+
+state.json 有**多种历史形态**,migrate 必须都能读。三条证据链:
+
+**(A) 迁移前「全量」形态**(v5.1 之前,或未迁移的老项目)
+`migrate_state_to_sqlite.py:5-22` 头注 + 读取逻辑列明会被迁走的大字段:
+- `entities_v3`:`{实体类型: {entity_id: {name, canonical_name, tier, aliases[], is_protagonist, first_appearance, last_appearance, current{...}, desc}}}` —— `migrate_state_to_sqlite.py:91-116`;样本 `test-project/.webnovel/state.json:24-64`
+- `alias_index`:`{别名: [{id, type}, ...]}`(一对多) —— `migrate_state_to_sqlite.py:134-155`
+- `state_changes`:`[{entity_id, field, old/old_value, new/new_value, reason, chapter}]` —— `migrate_state_to_sqlite.py:167-190`
+- `structured_relationships`:`[{from/from_entity, to/to_entity, type, description, chapter}]` —— `migrate_state_to_sqlite.py:202-225`
+
+**(B) 迁移后「精简」形态(< 5KB)**(v5.4)—— `migrate_state_to_sqlite.py:245-259` 保留顶层键:
+`project_info`、`progress`、`protagonist_state`、`strand_tracker`、`world_settings`(骨架)、`plot_threads`、`relationships`(简化 dict)、`review_checkpoints`(末 10)、`disambiguation_warnings`(末 20)、`disambiguation_pending`(末 10)、`_migrated_to_sqlite: true`、`_migration_timestamp`。
+
+**(C) 运行时 `_ensure_state_schema` 补齐的键**(权威默认结构)
+两处一致:`state_manager.py:165-219` 与 `init_project.py:133-181`。顶层键 + 结构概要:
+
+| 顶层键 | 结构 | 证据 |
+|---|---|---|
+| `project_info` | `{}`(题材/标题/作者等);**注意样本用的是 `project` 而非 `project_info`** | `state_manager.py:170`;样本 `state.json:7-11` |
+| `progress` | `{current_chapter, total_words, last_updated, volumes_completed[], current_volume, volumes_planned[], chapter_status{}}` | `init_project.py:166-171`, `state_manager.py:215-217, 678` |
+| `protagonist_state` | `{name, power{realm,layer,bottleneck}, location{current,last_chapter}, golden_finger{name,level,cooldown,skills[]}, attributes{}}` | `init_project.py:174-179`;样本 `state.json:12-23` |
+| `relationships` | v5.0+ 为 **dict**(人物/重要关系);旧版可能是 **list**(会被搬进 `structured_relationships` 并清空为 `{}`) | `state_manager.py:174-182` |
+| `structured_relationships` | list(实体关系,迁往 db) | `state_manager.py:177-179` |
+| `world_settings` | `{power_system[], factions[], locations[]}`(精简后各元素退化为 name 或 `{name,type}`) | `state_manager.py:184`, `migrate_state_to_sqlite.py:283-315` |
+| `plot_threads` | `{active_threads[], foreshadowing[]}` —— 伏笔在此,见 Q4 | `state_manager.py:185` |
+| `review_checkpoints` | list | `state_manager.py:186` |
+| `chapter_meta` | `{"NNNN": {hook{type,content,strength}, pattern{...}, ending{...}, coolpoint_patterns[], plot_structure{cbn,cen,cpns[],mandatory_nodes[],prohibitions[]}}}`,键为 4 位零填充或纯数字 | `state_manager.py:187`;样本 `state.json:72-91`;`state_validator.py:214-273` |
+| `strand_tracker` | `{last_quest_chapter, last_fire_chapter, last_constellation_chapter, current_dominant, chapters_since_switch, history[{chapter,strand}]}` | `state_manager.py:188-198`;样本 `state.json:65-71` |
+| `disambiguation_warnings` | list(截断上限 `max_disambiguation_warnings=500`) | `state_manager.py:204-205`, `config.py:203` |
+| `disambiguation_pending` | list(上限 1000) | `state_manager.py:207-208`, `config.py:204` |
+| `entities_v3` / `alias_index` | v5.1 后**不再初始化/维护**,但老项目仍可能残留(migrate 读它) | `state_manager.py:200-202` |
+
+> **重要 gotcha**:样本 `test-project/.webnovel/state.json` 是"全量/手写 fixture"形态——顶层用 `project`(非 `project_info`)、`entities_v3` 内联、伏笔 `status` 为 `"active"`(非规范 `"未回收"`)、`chapter_meta` 用 `hook/pattern/ending` 子结构。真实 init/迁移后的项目则是 `project_info` + 精简。migrate 需同时容忍两套键名。
+
+---
+
+### Q3. `.webnovel/index.db`(SQLite)表清单与列
+
+**建表 DDL 单一真源**:`webnovel-writer/scripts/data_modules/index_manager.py:246-622`(`SQLStateManager` 复用同一 db,`sql_state_manager.py` 只做读写不建表)。事件表另在 `event_log_store.py`。
+
+| 表 | 列(关键) | 引入 | 证据 |
+|---|---|---|---|
+| `chapters` | chapter(PK), title, location, word_count, characters, summary, created_at | 基础 | `index_manager.py:247-255` |
+| `scenes` | id, chapter, scene_index, start_line, end_line, location, summary, characters, UNIQUE(chapter,scene_index) | 基础 | `index_manager.py:260-270` |
+| `appearances` | id, entity_id, chapter, mentions, confidence, UNIQUE(entity_id,chapter) | 基础 | `index_manager.py:275-282` |
+| `entities` | id(PK), type, canonical_name, tier(默认'装饰'), desc, current_json, first_appearance, last_appearance, is_protagonist, is_archived, created_at, updated_at | v5.1(替代 entities_v3) | `index_manager.py:300-313` |
+| `aliases` | alias, entity_id, entity_type, created_at, PK(alias,entity_id,entity_type) | v5.1(替代 alias_index) | `index_manager.py:318-324` |
+| `state_changes` | id, entity_id, field, old_value, new_value, reason, chapter, created_at | v5.1 | `index_manager.py:329-338` |
+| `relationships` | id, from_entity, to_entity, type, description, chapter, created_at, UNIQUE(from,to,type) | v5.1 | `index_manager.py:343-352` |
+| `relationship_events` | id, from_entity, to_entity, type, action, polarity, strength, description, chapter, scene_index, evidence, confidence, created_at | v5.5 | `index_manager.py:389-403` |
+| `override_contracts` | id, chapter, constraint_type, constraint_id, rationale_type, rationale_text, payback_plan, due_chapter, status, created_at, fulfilled_at, `record_type`(补列) | v5.3 | `index_manager.py:422-435`, `override_ledger_service.py:45` |
+| `chase_debt` | id, debt_type, original_amount, current_amount, interest_rate, source_chapter, due_chapter, override_contract_id(FK), status, created_at, updated_at | v5.3 | `index_manager.py:440-453` |
+| `debt_events` | id, debt_id(FK), event_type, amount, chapter, note, created_at | v5.3 | `index_manager.py:458-467` |
+| `chapter_reading_power` | chapter(PK), hook_type, hook_strength, coolpoint_patterns, micropayoffs, hard_violations, soft_suggestions, is_transition, override_count, debt_balance, created_at, updated_at | v5.3 | `index_manager.py:472-485` |
+| `invalid_facts` | id, source_type, source_id, reason, status, marked_by, marked_at, confirmed_at, chapter_discovered | v5.4 | `index_manager.py:519-529` |
+| `review_metrics` | start_chapter, end_chapter, overall_score, dimension_scores, severity_counts, critical_issues, report_file, notes, created_at, updated_at, PK(start,end) | v5.4 | `index_manager.py:541-553` |
+| `rag_query_log` | id, query, query_type, results_count, hit_sources, latency_ms, chapter, created_at | v5.4 | `index_manager.py:561-570` |
+| `tool_call_stats` | id, tool_name, success, retry_count, error_code, error_message, chapter, created_at | v5.4 | `index_manager.py:581-590` |
+| `writing_checklist_scores` | chapter(PK), template, total_items, required_items, completed_items, completed_required, total_weight, completed_weight, completion_rate, score, score_breakdown, pending_items, source, notes, created_at, updated_at | Phase F | `index_manager.py:601-618` |
+| `story_events` | id, event_id, chapter, event_type, subject, payload_json, created_at | 事件溯源 | `event_log_store.py:113-124` |
+
+> `style_sampler.py:63` 另建 `samples` 表,但落在**独立 db**(非 index.db,见 `style_sampler` 自己的路径,未确认具体文件名)。
+
+---
+
+### Q4. 伏笔(foreshadowing)存哪
+
+**存 `state.json` 的 `plot_threads.foreshadowing`(list),无独立 db 表、无独立文件。** 证据:`state_manager.py:185`、`state_validator.py:276-284`(运行时规范化)。
+
+单条伏笔字段(规范化后,`state_validator.py:178-200`):
+- `content`(内容)
+- `status`:规范值 `未回收` / `已回收`(`FORESHADOWING_STATUS_*`,`state_validator.py:13-14`;容忍 `active/pending/已解决/...` 等别名,`:36-37`)
+- `tier`:`核心` / `支线` / `装饰`(`state_validator.py:16-18`;默认 `支线`)
+- `planted_chapter`(埋设章,从 `planted_chapter/added_chapter/source_chapter/start_chapter/chapter` 任一解析,`:20-26`)
+- `target_chapter`(目标回收章,从 `target_chapter/due_chapter/deadline_chapter/resolve_by_chapter/target`,`:28-34`)
+- `resolved_chapter`(实际回收章,可为 null,从 `resolved_chapter/resolved_at_chapter/resolved`)
+- `urgency`(紧急度数值,样本 `state.json:100`,如 80/30)
+
+> 另有"开放线索"的**第二处**存法:记忆 scratchpad 的 `open_loops` 桶(见 Q6),语义相近但独立(`test-project/.webnovel/memory_scratchpad.json:20-34`)。二者并存,migrate 需决定合并策略。
+
+---
+
+### Q5. chase_debt / reading_power 语义与结构
+
+均为 **index.db 表**(DDL 见 Q3)。原始字段(决定 v7 丢弃/转换用):
+
+**追读力债务体系(v5.3)** —— 把"欠读者的爽点/悬念"建模成带利息的债:
+- `chase_debt`:`debt_type`(债务类型)、`original_amount`/`current_amount`(本金/当前额,默认 1.0)、`interest_rate`(利率 0.1)、`source_chapter`→`due_chapter`(欠下→到期章)、`override_contract_id`(关联合约)、`status`(默认 `active`) —— `index_manager.py:440-453`
+- `override_contracts`:为"违反约束"记账的合约——`constraint_type/constraint_id`(违反了什么)、`rationale_type/rationale_text`(理由)、`payback_plan`(还债计划)、`due_chapter`、`status`(默认 `pending`,终态 `fulfilled/cancelled` 冻结) —— `index_manager.py:422-435`, `index_debt_mixin.py:15-55`
+- `debt_events`:债务流水账——`event_type, amount, chapter, note` —— `index_manager.py:458-467`
+
+**章追读力元数据(reading_power)** —— 每章一行:
+- `chapter_reading_power`:`hook_type`(钩子类型)、`hook_strength`(默认 `medium`)、`coolpoint_patterns`(爽点套路,JSON list)、`micropayoffs`(微爽点,JSON list)、`hard_violations`(硬伤,JSON list)、`soft_suggestions`(软建议,JSON list)、`is_transition`(过渡章 0/1)、`override_count`、`debt_balance`(债务余额) —— `index_manager.py:472-485`;写入 `index_reading_mixin.py:16-40`(4 个 list 字段 `json.dumps` 存储)
+
+> 对照 v7 映射"知识层与章属性(不设条目)":`coolpoint_patterns/hook_type` 属**章属性**可保留;`chase_debt/override_contracts/debt_events` 是**债务台账**,v7 若不设条目体系则整体丢弃。
+
+---
+
+### Q6. project_memory:patterns / scratchpad 存哪
+
+**两个独立文件,别混淆:**
+
+1. `.webnovel/memory_scratchpad.json` —— 长期记忆 scratchpad。结构 = `ScratchpadData`(`data_modules/memory/schema.py:103-159`),**7 个桶** + `meta`:
+   `character_state[]`, `story_facts[]`, `world_rules[]`, `timeline[]`, `open_loops[]`, `reader_promises[]`, `relationships[]`, `meta{version, last_updated, total_items}`。
+   每条 = `MemoryItem`(`schema.py:50-100`):`{id, layer(semantic|episodic), category, subject, field, value, payload{}, status(active|outdated|contradicted|tentative), source_chapter, evidence[], updated_at}`。
+   样本:`test-project/.webnovel/memory_scratchpad.json:1-42`。
+
+2. `.webnovel/project_memory.json` —— `/webnovel-learn` 学到的 patterns。结构(`project_memory.py:49-92`):`{patterns: [{pattern_type, description, source_chapter, learned_at, updated_at, category?, importance?}]}`。被 `context_manager.py:229` 读入上下文。
+
+> 命名易混:模块名叫 `project_memory.py`,但它写的是 `project_memory.json`(patterns);scratchpad(7 桶)是另一套(`memory/` 子包 + `memory_cli.py`)。
+
+---
+
+### Q7. `.webnovel/summaries/` 命名与格式
+
+- 命名:`chNNNN.md`,**ch + 4 位零填充章号**。样本:`summaries/ch0003.md`。
+- 格式:**YAML front matter + markdown 正文**。头部字段(样本 `summaries/ch0003.md:1-9`):`chapter`("0003")、`time`、`location`、`characters[]`、`state_changes[]`、`hook_type`、`hook_strength`。
+- 正文分节(`:11-19`):`## 剧情摘要`、`## 伏笔`(`- [推进]/[埋设] ...`)、`## 承接点`。
+- 目录由 `init_project.py:284` 创建;`chapters` 表也存 summary 列(Q3),二者可能并存。
+
+---
+
+### Q8. `.story-system/` 是什么、内含什么
+
+题材"契约"(contract)产物目录,由 story-system 阶段按需生成(`init_project.py` **不创建**它)。内容(`config.py:128-141`, `projection_log.py:51`, `runtime_contract_builder.py:51-55`):
+- `.story-system/MASTER_SETTING.json` —— 题材主契约。结构(`story_system_engine.py:113-132`):`{meta{schema_version:"story-system/v1", contract_type:"MASTER_SETTING"}, route, master_constraints{core_tone, pacing_strategy}, base_context[], source_trace[], override_policy{locked[], append_only[], override_allowed[]}}`。
+- `.story-system/anti_patterns.json` —— 毒点/反模式清单(list,元素含 `text`),源自 CSV `毒点` 字段。`runtime_contract_builder.py:54-55`, `story_system_engine.py:21-30`
+- `.story-system/chapters/` —— 章级 brief 契约(CHAPTER_BRIEF,`config.py:132-133`, `story_system_engine.py:133-146`)。
+- `.story-system/commits/chapter_NNN.commit.json` —— 章提交事件载荷(3 位零填充),投影读模型的来源。`projection_log.py:51`
+
+**非 git 历史/提交链**:名字里的 "commits" 是章级 JSON 快照,不是 git。整个 `.story-system/` 由 CSV 知识 + 大纲**可再生**,属派生产物。
+
+---
+
+### Q9. `大纲/` 文件命名惯例
+
+样本 `test-project/大纲/` 三件套 + init 生成:
+- `大纲/总纲.md` —— 全书骨架(基本信息/卷目录/章纲)。`init_project.py:605`;样本 `大纲/总纲.md`
+- `大纲/第N卷-详细大纲.md` —— 卷级详细大纲,内含 `### 第N章:标题` + 目标/阻力/代价/核心冲突/反派层级/本章变化/未闭合问题/钩子。样本 `大纲/第1卷-详细大纲.md`
+- `大纲/第N卷-时间线.md` —— markdown 表格(章节|时间|事件)。样本 `大纲/第1卷-时间线.md`
+- **可选:拆分式章纲** `大纲/第NNN章-标题.md`(每章一文件)—— `chapter_paths.py:21, 62-79`(`_SPLIT_OUTLINE_FILENAME_RE` 匹配 `第0*N章[-—_ ]标题.md`)
+- 章纲加载器还认总纲内 `## 第N卷章纲` / `### 第N章:标题` 内联段落 —— `chapter_paths.py:20`, `chapter_outline_loader.py`
+
+---
+
+### Q10. 正文章节文件有无结构化头部
+
+**纯正文,无 front matter / 无注释块。** 样本 `正文/第0004章-迦南学院的考验.md` 全文即小说正文(`:1-36`),首行直接是"晨光从窗缝…"。结构化元数据(钩子/摘要/角色)在别处(summaries 头部、chapter_meta、index.db chapters 表),不在正文文件里。
+
+---
+
+### Q11. `设定集/` 典型文件与格式
+
+init 生成的 canonical 集合(`init_project.py:448-598`),均为**自由格式 markdown**:
+- `设定集/世界观.md`(样本存在,`:1-13`:力量体系/地点/关键设定)
+- `设定集/力量体系.md`
+- `设定集/主角卡.md`
+- `设定集/女主卡.md`(可选,`init_project.py:537`)
+- `设定集/主角组.md`
+- `设定集/反派设计.md`
+
+模板源在 `webnovel-writer/scripts/`(或 skill)的 `output_templates_dir` 下 `设定集-*.md`(`init_project.py:391-396`)。
+
+---
+
+### Q12. vectors.db / projection_log 是什么、可否安全丢弃
+
+- `.webnovel/vectors.db` —— **RAG 活跃向量库**。`RAGAdapter` 全程用 `config.vector_db`(`rag_adapter.py:140,151,249`),建表 `vectors / bm25_index / doc_stats / rag_schema_meta`(`rag_adapter.py:189-244`)。从正文/设定 embedding 而来,**可重建 → 可丢弃**(`doctor.py:257` 视其缺失为"退化为关键词召回",非致命)。
+- `.webnovel/rag.db` —— config 里另有 `rag_db` 属性(`config.py:333-335`),但运行时 RAG 走 vectors.db;rag.db **疑似历史遗留/未使用**(未确认有代码写它)。
+- `.webnovel/projection_log.jsonl` —— 读模型投影运行日志(`projection_log.py:14, 41-60`:schema_version/run_id/chapter/commit_hash/writers/status)。派生自 commit + 投影,`doctor.py:340` 明言"旧项目可暂时忽略",**可丢弃**。
+
+结论:vectors.db、rag.db、projection_log.jsonl、context_cache.json、observability/、backups/、*.lock/*.bak 均为派生/缓存,migrate 可安全跳过。
+
+---
+
+### Q13. 历史形态兼容清单(migrate 必须覆盖)
+
+1. **正文命名 3 种**:平坦 `第NNNN章-标题.md`(4 位) / 卷 `第N卷/第NNN章-标题.md`(3 位) / 遗留 `第NNNN章.md`(无标题)。`chapter_paths.py:4-9, 118-135`
+2. **state.json 大字段内联 vs 迁移**:v5.1 前 `entities_v3/alias_index/state_changes/structured_relationships` 内联 state.json;v5.1+ 迁往 index.db;v5.4 精简加 `_migrated_to_sqlite/_migration_timestamp` 标记。`state_manager.py:200-202`, `migrate_state_to_sqlite.py:5-22, 256-259`
+3. **relationships 类型漂移**:老版 list(实体关系)↔ v5.0+ dict(人物关系);list 会被搬进 `structured_relationships`。`state_manager.py:174-182`
+4. **顶层键名漂移**:`project`(fixture)vs `project_info`(init/运行时)。样本 `state.json:7` vs `state_manager.py:170`
+5. **伏笔 status 值漂移**:`active/pending/已解决/...` → 规范 `未回收/已回收`;tier 别名 → `核心/支线/装饰`。`state_validator.py:36-40`
+6. **chapter_meta 键格式**:`"0003"`(4 位)或纯数字字符串;子结构 `hook/pattern/ending`(fixture)vs `coolpoint_patterns/plot_structure`(规范化)。`state_validator.py:259-273`
+7. **别名旧存储**:`alias_index_file` 已于 v5.1 废弃(改 index.db aliases 表);老项目别名内联在 state.json `alias_index`。`config.py:113`
+8. **可选目录/文件按需生成**:`.story-system/`、`summaries/`、`memory_scratchpad.json`、`project_memory.json`、`index.db`、`vectors.db` 在最简项目里可能**全部缺失**(init 只保证 `.webnovel/{backups,archive,summaries}` + `设定集/大纲/正文` + state.json)。`init_project.py:281-291`
+9. **空/损坏 state.json**:`.tmp/manual/*/book/.webnovel/state.json` 实测为 2 字节 `{}`;init 有"原 state.json 损坏 → 另存 corrupt 备份"分支。`init_project.py:302`
+10. **index.db schema 逐版本增量**:表随 v5.1/v5.3/v5.4/v5.5/PhaseF 累加(Q3 列的"引入"列);`CREATE TABLE IF NOT EXISTS` 使老 db 缺新表/新列——迁移读取需容忍表/列缺失。`index_manager.py:296, 418, 515`
+
+---
+
+## Caveats / 未确认
+
+- `style_sampler.py` 的 `samples` 表落在哪个 db 文件名——未逐行确认(不在 index.db 建表流里,疑似独立 `.webnovel/style_samples.db` 或复用 vectors.db,需读 `style_sampler.py` __init__)。
+- `.webnovel/rag.db`(`config.rag_db`)是否有任何写入者——未找到,疑似遗留常量;运行时 RAG 一致走 `vectors.db`。
+- 真实"满血"项目(含 index.db + 多章 + .story-system)在工作树内**没有 on-disk 样本**;`.tmp/manual/*` 均为空 stub,`test-project` 是手写 fixture(全量内联形态)。state.json 的"真实 5.4 精简形态"仅由 `migrate_state_to_sqlite.py:245-259` 代码推断,非 on-disk 实物。
+- `chapter_meta` / summaries / chapters 表三处都存"每章元数据",字段有重叠但不完全一致;migrate 选哪个为真源需 v7 决策(本研究只列存在,不给建议)。
+- 伏笔(plot_threads.foreshadowing)与 scratchpad.open_loops 双存,合并策略待 v7 定。

+ 26 - 0
.trellis/tasks/07-05-m7-export-migrate/task.json

@@ -0,0 +1,26 @@
+{
+  "id": "m7-export-migrate",
+  "name": "m7-export-migrate",
+  "title": "M7 导出与 v6 迁移",
+  "description": "",
+  "status": "in_progress",
+  "dev_type": null,
+  "scope": null,
+  "package": null,
+  "priority": "P2",
+  "creator": "codex",
+  "assignee": "codex",
+  "createdAt": "2026-07-05",
+  "completedAt": null,
+  "branch": null,
+  "base_branch": "v7",
+  "worktree_path": null,
+  "commit": null,
+  "pr_url": null,
+  "subtasks": [],
+  "children": [],
+  "parent": null,
+  "relatedFiles": [],
+  "notes": "",
+  "meta": {}
+}

+ 20 - 2
docs/architecture/story-repo-spec-2026-06-10.md

@@ -1,6 +1,10 @@
-# Story Repo 格式规格(v7 草案 0.12
+# Story Repo 格式规格(v7 草案 0.13
 
-> 状态:0.12。相对 0.11 的变更(2026-07-04 M6 自动模式落地,决策 36-37 见 §14):
+> 状态:0.13。相对 0.12 的变更(2026-07-05 M7 导出与迁移落地,决策 38 见 §14):
+> - §4.7 导出实现口径:`.txt`、单章无标题行、范围/全书合并文件以「第N章 标题」行分章(默认形态,beta 期可按作者反馈调整)
+> - §12 迁移实现注记:v6 双形态读取(state.json 全量内联 / 5.4 精简+index.db)、"人工过一遍再入"落「迁移待校对-」前缀文件、整体回退=临时目录建仓成功才落位、丢弃项进报告如实列出
+>
+> 0.12 相对 0.11 的变更(2026-07-04 M6 自动模式落地,决策 36-37 见 §14):
 > - §3 book.yaml 增「连写无条目变动上限」键(0.6 决策 20 的"连续 3 章(可配)"至此有键,默认 3)
 > - §8.1 实现口径回填:批次目录三件套与批次.json 三态、污染传播机制(0.7 版留白"由 M6 实施时设计"至此收口)、暂存前置"审稿单必须已生成"、批次质检判据("体检不过线"的批次落点)
 > - §8 第 8 步:定稿 commit 明确含条目文件**创建**(新开条目入档通道,M2 起潜伏缺口)
@@ -275,6 +279,8 @@ front matter 是机检消费的结构化字段(平铺、块列表);正文
 
 一键导出去掉 front matter 的纯正文,支持单章 / 章范围 / 全书,落点 `工作区/导出/`(不进 git)。作者发布到平台(起点/番茄)本来就需要纯文本——发布与"正文文件无机器味"两个需求一个脚本解决。
 
+实现口径(M7,默认形态,beta 期可按作者反馈调整):`.txt` 纯文本;单章 `第0152章-标题.txt` 正文体不带标题行(发布界面标题另填);范围 `第0006-0012章.txt` 与全书 `全书-<书名>.txt` 合并单文件,每章前置「第N章 标题」行、章间空两行(平台批量导入工具按标题行分章)。只导定稿区(工作区草稿与待定稿批次不可发布,不变量 5);重复导出覆盖。
+
 ## 5. 三类线索条目(`大纲/伏笔|悬念|感情线/`,每条一文件)
 
 系统的心脏。**有"开启→推进→收尾"生命周期的才设条目**;三类共用同一套引擎(文件格式、履历、"悬了太久"提醒、定稿写入;机器内部状态同构 open/advance/resolve/drop),作者界面按类型用各自的名字和动词:
@@ -541,6 +547,12 @@ v6 的 8 个命令全部内化为以上状态。作者只需要一个入口和"
 
 迁移不丢任何一个字;失败可整体回退。
 
+实现注记(M7,`v7/src/migrate/`,v6 语义证据链见任务 research):
+- **v6 双形态读取**:v5.4 起 v6 把实体等大数据迁入 `.webnovel/index.db`、state.json 精简——表中"index.db 删除"的准确语义是**读完不带入 v7**(实体在迁移时转正成名册/角色卡文件)。读取兼容 state.json 全量内联与精简+db 分置两形态,db 缺表缺列按空容忍(v6 schema 逐版本增量);state.json 空/损坏时降级为纯文件面迁移并在报告说明。
+- **"人工过一遍再入"的落点**:patterns/记忆清单/实体变更流水落「迁移待校对-」前缀文件(入 git,作者校对后归位删除);伏笔与 scratchpad 开放线索双存时**不自动合并**,开放线索进待校对区由作者裁决,防重复条目污染账本。
+- **整体回退**:工作目录内临时目录完整建仓(写产物 → git 单个 `init: 迁移自 v6` commit → 缓存重建即格式自洽校验)→ 成功才同盘改名落位;任一步失败删临时目录整树,源 v6 项目全程只读。
+- **如实丢弃**:债务台账(chase_debt 等)与派生库不迁移,迁移报告(`工作区/迁移报告.md`:迁了什么/待校对/如实丢弃三节)逐项列出;缺书内时间等无源字段省略不编造,体检"缺时间锚点"全列属预期、报告先说破。
+
 ## 13. 不做清单
 
 - ❌ 大一统 YAML 数据文件(多条记录一律每条一个 Markdown 文件)
@@ -554,6 +566,12 @@ v6 的 8 个命令全部内化为以上状态。作者只需要一个入口和"
 
 ## 14. 决策记录
 
+### 0.12 → 0.13(依据:2026-07-05 M7 导出与迁移落地)
+
+| # | 变更 | 落点 | 来源 |
+|---|------|------|------|
+| 38 | 导出与迁移实现口径回填:导出 `.txt` 默认形态(单章无标题行/合并文件标题行分章,只导定稿区);迁移读取兼容 v6 双形态("index.db 删除"正音为**读完不带入 v7**)、"人工过一遍再入"落「迁移待校对-」前缀文件、伏笔与开放线索双存不自动合并、整体回退=临时目录建仓成功才落位、无源字段省略不编造 + 丢弃项报告如实列出 | §4.7、§12 | M7 任务(`.trellis/tasks/07-05-m7-export-migrate/`)决策 D1-D4/权衡 4.1-4.5;PRD §2.2 旅程二 |
+
 ### 0.11 → 0.12(依据:2026-07-04 M6 自动模式落地)
 
 | # | 变更 | 落点 | 来源 |

+ 1 - 0
docs/architecture/v7-implementation-plan.md

@@ -181,6 +181,7 @@ M1 defer 的指纹提取与 M2 D2 决策推迟的统计项在此收口——此
 - 干净导出(单章/范围/全书,去 front matter,落 `工作区/导出/`)
 - `/migrate` 一次性脚本(PRD §10.3 映射表)+ 迁移报告 + 整体回退
 - **出口**:进入 beta——用 v7 真实写一本书到 50 章(建书→日更→吃书→卷复盘全覆盖);`/migrate` 在 ≥3 个真实 v6 项目跑通是 7.0.0 判据,beta 期内完成
+  (出口达成 2026-07-05:export 命令三形态(.txt,单章无标题行/合并文件标题行分章,只导定稿区);migrate 命令 v6 双形态读取(全量内联 / 5.4 精简+index.db,缺表容忍)→ §10.3 映射逐行转正 + 「迁移待校对-」前缀文件 + 人话迁移报告三节;整体回退=工作目录内临时目录建仓成功才 rename 落位,源 v6 全程只读;迁移指引 `v7/docs/migration-guide.md`。测试 429 绿含两形态端到端/十四类文本不丢字/注入失败回退。**代码面达成即进入 beta**:真写 50 章与 ≥3 真实 v6 项目验证是 beta 期活动。npm 发版与 7.0.0 版号沿 M5 决策推迟 beta 收口。任务:`.trellis/tasks/07-05-m7-export-migrate/`,spec 0.13 决策 38)
 
 ## 3. RFC 收口(~06-19,硬节点)