design.md 7.5 KB

技术设计:M3 状态机单入口 + git 隐身全套

1. 范围与分层

M3 是编排层:单入口状态机先跑 git 健康检查,再按 §10 序 0-6 命中即停判定「当前该做什么」。状态机只路由、不判业务、不调 AI——AI 态备好 DTO 返回「需 AI」交 M4(架构原则 §1.5)。

执行体分裂:

  • 纯脚本(M3 全做):git 健康检查、路由器、7 态检测、外环流程(影响分析/回到第N章/吃书/手改补登/分支 git 部分)。
  • AI 态动作(M4):建书问答、修复提议、卷复盘对谈、细纲拟提案、推演、圆设定 —— M3 出 DTO + needsAI
  • M3+/后续:体检文体指纹提取(M3 体检只做条目活跃率/时间线孤儿脚本项)。

模块落点(填 M0 占位 v7/src/state-machine/):

v7/src/state-machine/
├── index.js            # determineNextState(ctx):git健康 → 序0-6 路由
├── git-health.js       # 5 类异常检测 + 激进自动修复(D2,可恢复安全网)
├── detectors.js        # 序0-6 各态检测(命中即停)
├── flows/
│   ├── impact.js       # 影响分析(grep 正文+履历+时间线)
│   ├── goto-chapter.js # 回到第N章(git 回滚包装 + 影响范围)
│   └── retcon.js       # 吃书(retcon commit + 设定/条目同步)
└── dto.js              # AI 态上下文 DTO 组装

复用 M1(grep-story/reports/cache)、M2(finalize/git.js 扩展、assembleBookStatus)。

2. 单入口契约

// determineNextState(ctx) → 作者的「继续」按钮背后
// @param {{repoPath, cache}} ctx
// @returns {Promise<{
//   ok, gitHealth: {fixed:[], guidance:[], rescued:[]},
//   序: 0..6, state: string, needsAI: boolean, dto?: object,
//   message: string  // 中文,告诉作者/宿主当前该做什么
// }>}

CLI 单入口:v7/src/commands/next.jswebnovel-writer next,run 契约)→ 调 determineNextState,打印 message + 必要数据。SessionStart hook 注入留 M4/M5(无 hook 宿主走这个 CLI 等价路径)。

3. git 健康检查(D2:激进自动修 + 可恢复安全网)

checkGitHealth(ctx) → {ok, fixed:[], guidance:[], rescued:[]}。在序 0 前跑。改 git 状态前必先快照;返回中文小结,作者永不见 git 英文报错(不变量 8)。

异常 检测 处理(B 激进) 安全网
陈旧锁文件 .git/index.lock 文件存在且陈旧(mtime 超阈值,无活跃 git 进程迹象) 自动删 删前记 rescued 日志
网盘冲突副本 xxx (1).md/xxx 的冲突副本 文件名模式匹配 自动归档工作区/.救援/网盘副本/ 移动非删除,可找回
半提交(中断的定稿,工作树脏) git status --porcelain 非空且非正常流程 自动 git stash(push 到备份) stash 可 pop 恢复
合并冲突 git statusUU/MERGING git stash/备份 ref,再 git merge --abort 备份 ref 可回
.git 损坏 git fsck/git status 报错 例外:不自动修,检测 + 中文指引(建议备份后 git fsck 不动用户仓库

实现:git 操作复用并扩展 M2 src/finalize/git.js(加 status/stash/mergeAbort/fsck/config)。每次自动修在 工作区/.救援/修复日志.md 追加一行(修了什么、时间、怎么撤)。

4. 状态机路由(序 0-6,命中即停)

detectors.js 导出按序判定函数,determineNextState 依序调用,首个命中返回。

检测(脚本) needsAI dto / 动作
0 修复确认 扫源文件,任一 parseFrontMatter/parseMarkdownTable 失败;全角冒号/逗号在结构位 = 确定性错误(预修复) 是(AI 提议保意图修复) dto: {文件, 行, 上下文, 确定性预修复结果}
1 建书引导 book.yaml / books.jsonl 当前书不存在 是(问答) 脚本可先建空骨架;dto: {缺什么}
2 手改补登 git status --porcelain -- 定稿 大纲 非空(未登记手改) 动作:提议 fix(...) commit(复用 M2 git)
3 断点续跑 工作区/ 有未完成流程(草稿/审稿/待定稿批次存在但未定稿) 动作:返回中断阶段,引导续跑
4 卷复盘 最大已定稿章 = 卷末(卷规模整除 / 卷纲耗尽) 是(对谈) 脚本清账(本卷开/收/悬了太久);dto 给 AI
5 体检 章号到体检周期(book.yaml,默认每 50 章) 部分 脚本:条目活跃率(M1)/时间线孤儿;指纹推 M3+
6 起草细纲 其余 是(拟提案) 脚本:assembleBookStatus(M2);dto 给 AI 拟提案

序 0 的「确定性错误预修复」:全角 :, 出现在 YAML 键值结构位 → 脚本可确定性替换为半角后只报不问(spec §10);其余解析失败 → needsAI。

5. 外环脚本流程(§9,纯脚本)

  • 影响分析 analyzeImpact(ctx, {关键词|实体}):grep-story 正文 + 扫条目履历 + 时间线,列引用章号;按「已发布章号」(book.yaml 已发布到章,默认 0=全未发布)分已发布/未发布两清单。返回结构化,不改文件。
  • 回到第 N 章 gotoChapter(ctx, {chapterNum, confirm})git log --grep "ch(N):" 定位 commit → 先展示影响范围(该章之后的章/commit)→ confirm=truegit reset先建备份 ref 救援/回退前-<时间>)。不变量 8:作者只说「回到第 N 章」,不碰 git。
  • 吃书 retcon retcon(ctx, {chapterNum, 原因, 设定变更, 条目变更}):改定稿 + retcon(N): 原因 commit + 设定/条目同步(复用 M2 Writer + finalize 编排范式)。圆设定(AI)留 M4。

6. AI 态 DTO 缝

dto.js:为序 0/1/4/6 + 分支试写组装 AI 所需上下文(不调 AI,只备料)。形如 {state, 上下文片段, 作者可选项, 期望产物}。M4 吃 DTO 出结构化产物,回流由 M3 落盘(M3 提供落盘函数,M4 不碰文件)。M3 测试断言 DTO 字段齐全 + needsAI=true

7. 测试策略(镜像 src,TDD)

git 异常样本库(核心出口):用 mkdtemp + git init 构造每种异常的仓库样本,断言 checkGitHealth 自动修复/归档/指引正确 + 安全网可恢复 + 输出零英文堆栈:

  • 造陈旧 index.lock → 断言自动删 + rescued 记录
  • 章节 (1).md → 断言归档到 工作区/.救援/
  • 造脏工作树 → 断言 stash + 可 pop
  • 造 MERGING 状态(制造冲突)→ 断言备份 + abort
  • .git 损坏(截断 HEAD)→ 断言只指引、不乱动、message 中文

状态机路由test/state-machine/detectors 构造命中各序 0-6 的仓库,断言 determineNextState 返回正确 序/state/needsAI/dto。命中即停顺序也要测(如同时有手改 + 卷末 → 先报序 2)。

外环流程:影响分析(grep 命中 + 已发布/未发布分组)、回到第N章(reset 到目标 + 备份 ref 存在)、吃书(retcon commit message + 设定同步)。

不变量回归:删 .cache 重建一致、定稿原子不破坏。

8. 边界与非目标

  • ❌ AI 态真实动作(建书问答/修复提议/卷复盘对谈/细纲提案/推演/圆设定)→ M4
  • ❌ 体检文体指纹提取 → M3+/M4
  • ❌ 自动模式连写/批次/污染传播 → M6
  • ❌ SessionStart hook 真实注入 → M4/M5(M3 给 CLI 等价入口)
  • .git 损坏的自动修复(D2 例外,只指引)