dto.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import { promises as fs } from 'node:fs'
  2. import path from 'node:path'
  3. import { assembleBookStatus } from '../prep/book-status.js'
  4. /**
  5. * 为 AI 态组装上下文 DTO(M3 只备料,不调 AI)。M4 吃 DTO 出结构化产物,
  6. * 产物回流由 M3 落盘(M4 不碰文件)。每个 DTO 标注 `期望产物` 告诉 M4 该产出什么。
  7. * @param {{repoPath, cache}} ctx
  8. * @param {number} 序
  9. * @param {object} base 路由已知信息(failures / manualEdits / 现存与从哪继续 / 卷 / nextChapter)
  10. */
  11. export async function buildDto(ctx, 序, base = {}) {
  12. switch (序) {
  13. case 0:
  14. return {
  15. state: 'repair-confirm',
  16. failures: base.failures || [],
  17. 期望产物: '逐个给出「保留作者意图」的修复方案,作者确认后由 M3 写回',
  18. }
  19. case 1:
  20. return {
  21. state: 'create-book',
  22. 缺: await whatsMissing(ctx),
  23. 期望产物: '问答生成 book.yaml + 总纲 + 第一卷卷纲(由 M3 落盘 + 登记 books.jsonl)',
  24. }
  25. case 2:
  26. return {
  27. state: 'relink-manual-edits',
  28. 变更文件: base.manualEdits || [],
  29. 补登命令: 'relink --message=<一句话说明>',
  30. 期望产物: '向作者出示变更清单问「补登吗」,确认后运行补登命令(fix(手改) 入档并刷新缓存);不补登则按作者指示处理',
  31. }
  32. case 3:
  33. return {
  34. state: 'resume',
  35. 工作区现存: base.现存 || [],
  36. 从哪继续: base.从哪继续 || '',
  37. 期望产物: '按「从哪继续」回到写章流程对应步骤(spec §10 续跑映射)',
  38. }
  39. case 4: {
  40. const status = await assembleBookStatus(ctx)
  41. return {
  42. state: 'volume-review',
  43. 卷: base.卷,
  44. 全书近况: status.ok ? status.markdown : '',
  45. 悬了太久: status.ok ? status.data.悬了太久 : [],
  46. 期望产物: '卷摘要 + 下卷卷纲 + 伏笔机会候选(作者勾选后 M3 生成条目)',
  47. }
  48. }
  49. case 6: {
  50. const status = await assembleBookStatus(ctx)
  51. return {
  52. state: 'draft-outline',
  53. nextChapter: base.nextChapter,
  54. 全书近况: status.ok ? status.markdown : '',
  55. 期望产物: '工作区/细纲.md(含本章定位声明 + 本章要写到的事 + 备选,由 M3 落盘);卷近尾声时提案可含收卷提议(依据卷纲进度与卷规模参考值,作者确认后定稿写入 收卷: 是)',
  56. }
  57. }
  58. default:
  59. return { state: base.state || 'unknown' }
  60. }
  61. }
  62. async function whatsMissing(ctx) {
  63. if (!ctx.repoPath) return ['book.yaml', '总纲'] // 空工作目录:书仓库还不存在
  64. const missing = []
  65. for (const [label, rel] of [
  66. ['book.yaml', 'book.yaml'],
  67. ['总纲', '大纲/总纲.md'],
  68. ]) {
  69. try {
  70. await fs.access(path.join(ctx.repoPath, rel))
  71. } catch {
  72. missing.push(label)
  73. }
  74. }
  75. return missing
  76. }