review-probe.mjs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. // M1-M5 review 行为探针:真 CLI 子进程跑边角流程,验证代码层疑点。跑完自删临时目录。
  2. import { execFile } from 'node:child_process'
  3. import { promisify } from 'node:util'
  4. import { promises as fs } from 'node:fs'
  5. import os from 'node:os'
  6. import path from 'node:path'
  7. import { fileURLToPath } from 'node:url'
  8. const exec = promisify(execFile)
  9. const BIN = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'webnovel-writer.js')
  10. const workdir = await fs.mkdtemp(path.join(os.tmpdir(), 'wnw-probe-'))
  11. await fs.mkdir(path.join(workdir, '.webnovel'), { recursive: true })
  12. const run = async (args, expectFail = false) => {
  13. try {
  14. const r = await exec(process.execPath, [BIN, ...args], { cwd: workdir, encoding: 'utf8' })
  15. return { code: 0, out: r.stdout, err: r.stderr }
  16. } catch (e) {
  17. if (!expectFail) console.error(`命令失败 ${args.join(' ')}:\n${e.stderr || e.message}`)
  18. return { code: e.code ?? 1, out: e.stdout || '', err: e.stderr || '' }
  19. }
  20. }
  21. const j = async (name, data) => {
  22. const p = path.join(workdir, name)
  23. await fs.writeFile(p, JSON.stringify(data), 'utf8')
  24. return p
  25. }
  26. const repo = path.join(workdir, '测')
  27. try {
  28. // 建书 + 定稿两章
  29. const f建 = await j('建书.json', { book: { spec_version: '7.0', 书名: '测', 卷规模: 40, 体检周期: 50 }, 总纲: '# 总纲\n## 结局\nx', 卷纲: '# 第1卷\ny' })
  30. await run(['persist-book', `--file=${f建}`])
  31. for (const n of [1, 2]) {
  32. const fp = await j(`定稿${n}.json`, {
  33. frontMatter: { 章号: n, 标题: `第${n}章`, 卷: 1, 字数: 10, 章定位: '推进' },
  34. body: `第${n}章正文。`, summary: `摘要${n}`, commitLines: {}, workspaceFiles: [],
  35. })
  36. const r = await run(['finalize', String(n), `--payload=${fp}`])
  37. if (!r.out.includes('已定稿')) console.error(`定稿${n}失败: ${r.err}`)
  38. }
  39. // 探针1:卷复盘产物落盘后 next → 是否误触序2
  40. const f卷 = await j('卷复盘.json', { 卷号: 1, 卷摘要: '# 第01卷复盘\n清账。', 下卷卷纲: '# 第2卷\nz', 伏笔条目: [{ id: '伏笔-001-试', frontMatter: { id: '伏笔-001', 短题: '试', 状态: '进行', 开启章: 2 }, body: '收尾计划:第10章' }] })
  41. const pv = await run(['persist-volume-review', `--file=${f卷}`])
  42. console.log(`persist-volume-review: ${pv.code === 0 ? 'ok' : pv.err}`)
  43. const n1 = await run(['next', '--json'])
  44. const s1 = JSON.parse(n1.out)
  45. console.log(`探针1 卷复盘后 next → 序${s1.序} ${s1.state} ${s1.序 === 2 ? '【误触序2 确认】' : ''}`)
  46. // 探针1b:新伏笔条目在缓存里可见吗(陈旧性)
  47. const lt = await run(['list-threads'], true)
  48. console.log(`探针1b 卷复盘新伏笔 list-threads 可见: ${lt.out.includes('伏笔-001') ? '可见' : '【不可见=缓存陈旧 确认】'}`)
  49. // 收拾:把卷复盘产物 commit 掉,让探针2 干净
  50. const git = (a) => exec('git', a, { cwd: repo })
  51. await git(['add', '-A'])
  52. await git(['commit', '-q', '-m', 'vol(1): probe'])
  53. // 探针2:goto-chapter 1 --confirm 后 next → 起草第几章
  54. const g = await run(['goto-chapter', '1', '--confirm'])
  55. console.log(`goto: ${(g.out || g.err).split('\n')[0]}`)
  56. const n2 = await run(['next', '--json'])
  57. const s2 = JSON.parse(n2.out)
  58. const next2 = s2.dto ? s2.dto.nextChapter : undefined
  59. console.log(`探针2 回到第1章后 next → 序${s2.序} nextChapter=${next2} ${next2 === 3 ? '【缓存领先,起草错章 确认】' : next2 === 2 ? '正常' : `state=${s2.state}`}`)
  60. // 探针3:手改一个已跟踪文件 → next 序2 → 有无补登通道
  61. await fs.appendFile(path.join(repo, '大纲', '总纲.md'), '\n作者手改。', 'utf8')
  62. const n3 = await run(['next', '--json'])
  63. const s3 = JSON.parse(n3.out)
  64. console.log(`探针3 手改后 next → 序${s3.序} ${s3.state} message=${s3.message} dto=${JSON.stringify(s3.dto)}`)
  65. } finally {
  66. await fs.rm(workdir, { recursive: true, force: true, maxRetries: 3 }).catch(() => {})
  67. }