sillyspec 3.25.8 → 3.25.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.claude/skills/sillyspec-auto/SKILL.md +8 -13
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +8 -6
  3. package/.claude/skills/sillyspec-continue/SKILL.md +3 -3
  4. package/.claude/skills/sillyspec-doctor/SKILL.md +1 -1
  5. package/.claude/skills/sillyspec-execute/SKILL.md +10 -4
  6. package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
  7. package/.claude/skills/sillyspec-verify/SKILL.md +1 -1
  8. package/README.md +9 -0
  9. package/package.json +2 -2
  10. package/src/change-list.js +11 -8
  11. package/src/change-risk-profile.js +22 -0
  12. package/src/db.js +160 -159
  13. package/src/dispatch/backends/local-agent.js +152 -0
  14. package/src/dispatch/backends/sillyhub-mcp.js +242 -0
  15. package/src/dispatch/probe.js +208 -0
  16. package/src/dispatch/strategy.js +121 -0
  17. package/src/doctor-diagnostics.js +19 -25
  18. package/src/fs-atomic.js +69 -69
  19. package/src/git-helper.js +66 -0
  20. package/src/hooks/worktree-guard.js +64 -55
  21. package/src/index.js +215 -34
  22. package/src/init.js +7 -14
  23. package/src/machine-interface.js +54 -19
  24. package/src/migrate.js +14 -4
  25. package/src/modules.js +16 -5
  26. package/src/progress/change-registry.js +72 -70
  27. package/src/progress/consistency-doctor.js +71 -14
  28. package/src/progress/shared.js +6 -3
  29. package/src/progress/stage-machine.js +61 -65
  30. package/src/progress/step-store.js +62 -67
  31. package/src/progress.js +155 -233
  32. package/src/quicklog.js +13 -5
  33. package/src/run/command.js +65 -37
  34. package/src/run/complete-handlers.js +1005 -889
  35. package/src/run/complete.js +115 -208
  36. package/src/run/concurrent-detect.js +132 -0
  37. package/src/run/gates.js +289 -17
  38. package/src/run/prompt.js +22 -68
  39. package/src/run/quick-audit.js +10 -9
  40. package/src/run/shared.js +648 -600
  41. package/src/run/stage.js +43 -24
  42. package/src/scan-postcheck.js +9 -31
  43. package/src/sillyhub-mcp/client.js +389 -0
  44. package/src/spec-dir-typo.js +71 -0
  45. package/src/stage-contract-engine.js +22 -1
  46. package/src/stage-contract-spec.js +11 -4
  47. package/src/stage-contract.js +26 -3
  48. package/src/stage-review.js +13 -9
  49. package/src/stages/archive.js +4 -6
  50. package/src/stages/brainstorm-auto.js +26 -20
  51. package/src/stages/brainstorm.js +29 -5
  52. package/src/stages/cmd-existence.js +145 -0
  53. package/src/stages/doctor.js +9 -6
  54. package/src/stages/execute.js +61 -20
  55. package/src/stages/plan-postcheck.js +152 -0
  56. package/src/stages/plan.js +16 -31
  57. package/src/stages/quick.js +1 -1
  58. package/src/stages/scan.js +9 -3
  59. package/src/stages/status.js +0 -1
  60. package/src/stages/verify.js +12 -7
  61. package/src/sync.js +4 -4
  62. package/src/task-review.js +30 -1
  63. package/src/verify-postcheck.js +933 -862
  64. package/src/version.js +19 -0
  65. package/src/worktree-apply.js +85 -68
  66. package/src/worktree-deps.js +89 -5
  67. package/src/worktree.js +213 -111
  68. package/templates/prompts/taskcard-rules.md +27 -0
  69. package/templates/prompts/verify-probes.md +3 -3
  70. package/.claude/skills/sillyspec-propose/SKILL.md +0 -87
  71. package/src/stages/propose.js +0 -181
package/src/quicklog.js CHANGED
@@ -330,11 +330,19 @@ function flipEntryInContent(content, qlId, result, changedFiles = []) {
330
330
  }
331
331
  // 多行结果(结构化 需求/根因/方案/结果 字段块):逐行插入为独立字段行。
332
332
  // 单行结果:保持「结果:<一句话>」一行,向后兼容简单用例。
333
- if (/\r?\n/.test(body)) {
334
- const resultLines = body.split(/\r?\n/).filter(l => l.trim() !== '')
335
- lines.splice(endIdx, 0, ...resultLines)
336
- } else {
337
- lines.splice(endIdx, 0, `结果:${body}`)
333
+ const resultLines = /\r?\n/.test(body)
334
+ ? body.split(/\r?\n/).filter(l => l.trim() !== '')
335
+ : [`结果:${body}`]
336
+ // 结果块属本条目:从 endIdx 往前跳过本条目尾部空行,在最后一个非空行之后插入。
337
+ // 否则结果块会落在尾空行「之后」,与下一条目标题紧贴(缺空行分隔),且被空行从本条目
338
+ // 「文件:」行隔开——视觉上像属于下一条目(用户实证多条目 QUICKLOG 间距 bug)。
339
+ let insertAt = endIdx
340
+ while (insertAt - 1 > startIdx && lines[insertAt - 1].trim() === '') insertAt--
341
+ lines.splice(insertAt, 0, ...resultLines)
342
+ // 兜底:结果块之后、下一个 ## 标题之间若无空行(条目原本无尾空行 / 轮转归档手改等),补一个。
343
+ const after = insertAt + resultLines.length
344
+ if (after < lines.length && lines[after].startsWith('## ')) {
345
+ lines.splice(after, 0, '')
338
346
  }
339
347
  }
340
348
  return lines.join('\n')
@@ -23,7 +23,7 @@ import { join, resolve, dirname } from 'node:path'
23
23
  import { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs'
24
24
  import { randomBytes, randomUUID } from 'node:crypto'
25
25
  import { writeAtomicSync } from '../fs-atomic.js'
26
- import { resolveSpecDir, countAncestorSpecDirs, resolveChangeDir, triggerSync, getStageSteps, formatWaitOptions, checkApproval, didYouMean, assertSafeChangeName, detectQuickSessionDrift, detectWorktreeSpecDrift } from './shared.js'
26
+ import { resolveSpecDir, countAncestorSpecDirs, resolveChangeDir, triggerSync, getStageSteps, formatWaitOptions, checkApproval, didYouMean, assertSafeChangeName, detectQuickSessionDrift, detectWorktreeSpecDrift, resolveRuntimeRoot } from './shared.js'
27
27
  import { resolveQuickLinkedChanges } from './quick-audit.js'
28
28
  import { outputStep } from './prompt.js'
29
29
  import { completeStep, skipStep, waitStep, continueStep } from './complete.js'
@@ -214,7 +214,6 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
214
214
  const waitReason = getFlagValue('--reason')
215
215
  const waitOptions = getFlagValue('--options')
216
216
  const continueAnswer = getFlagValue('--answer')
217
- const confirmMode = getFlagValue('--confirm-mode')
218
217
  const resolvedSpecDir = specDir || getFlagValue('--spec-dir') || getFlagValue('--spec-root')
219
218
  const platformOpts = {
220
219
  specRoot: resolvedSpecDir ? resolve(resolvedSpecDir) : null,
@@ -291,7 +290,8 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
291
290
 
292
291
  // 统一规范基路径:平台模式用 specRoot,本地模式用 cwd/.sillyspec
293
292
  // runCommand 后续所有 .sillyspec/ 操作必须用 specBase
294
- const specBase = platformOpts.specRoot || join(cwd, '.sillyspec')
293
+ // let:下方 worktree 副本漂移守卫命中时锚回 wt.mainSpecBase(task-05/D-03)
294
+ let specBase = platformOpts.specRoot || join(cwd, '.sillyspec')
295
295
 
296
296
  // 漂移提醒:cwd 祖先链 ≥2 个 .sillyspec = monorepo 多实例,当前命中的「最近」实例
297
297
  // 可能不是用户意图的项目(如 cd 进被独立 scan 的子项目跑测试后忘回根)。
@@ -407,6 +407,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
407
407
  // 3. 仍读不到 → 生成新 UUID(兼容旧行为;进度可能命中空行,由后续 pm.read 兜底)
408
408
  const QUICK_SID_RE = /^quick-[0-9a-f]{8}$/
409
409
  let quickSessionId = null
410
+ let quickFallbackUsed = false // Q7: --done 未带 --change 时 fallback 读 current-quick-run-id 命中(区别于显式 --change quick-<hex>)
410
411
  if (stageName === 'quick') {
411
412
  // 1373-1376 已把 quick 的 --change 值清进 linkedChanges、changeName 置 null。
412
413
  // 此处回看 --change 原始值:若恰好是单个 quick-<8hex> → 识别为本会话 sessionId(精确恢复),
@@ -423,11 +424,11 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
423
424
  const isDoneLike = isDone || isStatus || isSkip || isReset || isReopen
424
425
  if (isDoneLike) {
425
426
  try {
426
- const runtimeRoot = platformOpts.runtimeRoot || join(specRoot, '.runtime')
427
+ const runtimeRoot = resolveRuntimeRoot(platformOpts, specRoot)
427
428
  const idFile = join(runtimeRoot, 'current-quick-run-id')
428
429
  if (existsSync(idFile)) {
429
430
  const v = readFileSync(idFile, 'utf8').trim()
430
- if (QUICK_SID_RE.test(v)) quickSessionId = v
431
+ if (QUICK_SID_RE.test(v)) { quickSessionId = v; quickFallbackUsed = true }
431
432
  }
432
433
  } catch {}
433
434
  }
@@ -484,7 +485,8 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
484
485
  const isAuxiliary = auxiliaryStages.includes(stageName)
485
486
  // scan 元数据追踪(存储在 stageData.scanMeta 中,completeStep 通过 progress 访问)
486
487
 
487
- const pm = new ProgressManager({ specDir: specRoot })
488
+ // let:下方 worktree 副本漂移守卫命中时用主仓 specRoot 重建 pm(task-05/D-03)
489
+ let pm = new ProgressManager({ specDir: specRoot })
488
490
 
489
491
  // quick 阶段:确定关联变更
490
492
  // 优先级:--linked-changes / --change 显式 > 已持久化 quick-guard.json(--done 复用)> 交互式 > 非交互 fallback
@@ -522,18 +524,29 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
522
524
  process.exit(2) // 用法错(execute 必须显式 --change)→ exit 2
523
525
  }
524
526
 
525
- // worktree 副本漂移硬守卫(坑 worktree-execute-spec-drift):specBase 命中 worktree 内 .sillyspec
526
- // checkout 副本 → 进度/产出会写分裂库。必须在 validateChangeExists 之前——副本里 change 目录真实
527
- // 存在,存在性校验会被骗放行。覆盖 plan/execute/verify/archive(validateChangeExists 同 Set):
528
- // 都要求 change 已存在、都被副本骗、都写 .sillyspec 资产(archive 尤甚:在副本里移动 change+commit)。
529
- // 平台模式/显式 --spec-dir 跳过(已明确指定,与 line~285 warn / ~529 quick 守卫条件对齐)。
527
+ // worktree 副本漂移自动锚定守卫(坑 worktree-execute-spec-drift,D-03@v1):specBase 命中 worktree 内
528
+ // .sillyspec checkout 副本 = agent 误 cd 进 worktree 跑 plan/execute/verify/archive(100% 误操作场景)。
529
+ // 不 exit:自动把 specBase 锚回主仓 wt.mainSpecBase 后继续——进度/产出落主仓,不再写分裂副本(副本随
530
+ // 工作树清理丢失)。必须在 validateChangeExists 之前:副本里 change 目录真实存在,存在性校验会被骗
531
+ // 放行;锚定后用主仓 specBase 复查。覆盖 plan/execute/verify/archive(validateChangeExists 同 Set)。
532
+ // 关键坑:连带重写 specBase/specRoot/specDir + 重建 pm——四者同源派生(pm._customSpecDir 持旧 specRoot),
533
+ // 只改 specBase 会让下游 validateChangeExists(specBase) 与 pm.read(走 pm._customSpecDir) 仍指副本。
534
+ // 平台模式/显式 --spec-dir 跳过(已明确指定,与 line~285 warn / quick 守卫条件对齐)。
535
+ // 其他漂移(下方 changeMissing 守卫 / quick session drift 守卫)非副本场景,仍 exit(2) 不自动纠正。
530
536
  if (!platformOpts.specRoot && !specDir
531
537
  && ['plan', 'execute', 'verify', 'archive'].includes(stageName)) {
532
538
  const wt = detectWorktreeSpecDrift(specBase)
533
539
  if (wt) {
534
- console.error(`❌ ${wt.message}`)
535
- console.error(` 当前 cwd=${cwd},命中的 spec=${specBase}。`)
536
- process.exit(2) // 环境错(cwd/spec 漂移)→ exit 2
540
+ specBase = wt.mainSpecBase
541
+ specRoot = wt.mainSpecBase
542
+ specDir = wt.mainSpecBase
543
+ pm = new ProgressManager({ specDir: specRoot })
544
+ // 【坑 execute-runs-isolation】下游 runtimeRoot 解析(execute-runs/stage-reviews 落点)经
545
+ // platformOpts 透传读此锚点,落主仓 .runtime,不随 worktree cleanup 整目录删消失。
546
+ // D-02:只参与 runtimeRoot 解析(resolveRuntimeRoot),绝不设 specRoot/runtimeRoot——
547
+ // 否则触发平台 sentinel 副作用(triggerSync/checkApproval 跳过 + prompt 误进平台渲染分支)。
548
+ platformOpts.specDriftAnchor = wt.mainSpecBase
549
+ console.warn(`⚠️ 已自动锚定主仓 spec:${wt.mainSpecBase}(原 cwd 命中 worktree 副本 ${wt.changeName},已纠正,流程继续)`)
537
550
  }
538
551
  }
539
552
 
@@ -564,13 +577,13 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
564
577
  }
565
578
  }
566
579
 
567
- let progress = await pm.read(cwd, changeName)
580
+ let progress = pm.read(cwd, changeName)
568
581
 
569
582
  if (!progress) {
570
583
  // 如果指定了变更名或有变更目录,自动初始化变更的 progress
571
584
  const autoChange = changeName || resolveChangeNameAuto(cwd, specRoot)
572
585
  if (autoChange) {
573
- progress = await pm.initChange(cwd, autoChange)
586
+ progress = pm.initChange(cwd, autoChange)
574
587
  } else if (isAuxiliary) {
575
588
  let autoName = changeName || resolveChangeNameAuto(cwd, specRoot) || 'default'
576
589
  // archive 特例:归档后变更从活跃列表排除(listChanges WHERE status='active'),
@@ -585,14 +598,14 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
585
598
  .pop()
586
599
  if (latest) {
587
600
  autoName = latest.replace(/^\d{4}-\d{2}-\d{2}-/, '')
588
- progress = await pm.read(cwd, autoName)
601
+ progress = pm.read(cwd, autoName)
589
602
  }
590
603
  }
591
604
  } catch {}
592
605
  }
593
606
  changeName = autoName
594
607
  if (!progress) {
595
- progress = await pm.initChange(cwd, autoName)
608
+ progress = pm.initChange(cwd, autoName)
596
609
  // initChange 可能因 project 表为空返回 null
597
610
  if (!progress) {
598
611
  progress = { currentStage: stageName, stages: {}, lastActive: new Date().toLocaleString('zh-CN', { hour12: false }), project: '' }
@@ -612,7 +625,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
612
625
  console.log(`🔄 自动创建变更:${autoName}`)
613
626
  console.log(` 提示:可以用 --change <名称> 指定自定义变更名`)
614
627
  console.log(` 或事后重命名:sillyspec change-rename ${autoName} <新名称>`)
615
- progress = await pm.initChange(cwd, autoName)
628
+ progress = pm.initChange(cwd, autoName)
616
629
  changeName = autoName
617
630
  } else {
618
631
  console.error('❌ 未找到进度数据,请先运行 sillyspec init 或指定 --change <变更名>')
@@ -624,6 +637,21 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
624
637
  // 确保 progress 有 currentChange
625
638
  const effectiveChange = changeName || progress.currentChange || resolveChangeName(cwd, progress, specRoot)
626
639
 
640
+ // ── Q7:quick --done 不带 --change 时 fallback 读 current-quick-run-id 可能命中他者会话 ──
641
+ // 并发两 quick 会话:B 后启动覆盖 A 的 id(last-writer-wins),A 的 --done 不带 --change → 读到 B 的 sessionId
642
+ // → 误操作 B 的 progress/QUICKLOG。fallback 命中的会话若已完成或无可推进步骤(id stale / 他者已收尾),
643
+ // 拒绝推进并要求显式 --change。(live A/B race 需 per-session 锚 commit,见 review Q2/D2,本守卫只兜 stale/completed。)
644
+ if (stageName === 'quick' && isDone && quickFallbackUsed) {
645
+ const qs = progress.stages.quick
646
+ const hasActionable = qs?.steps && qs.steps.some(s => s.status === 'pending' || s.status === 'waiting' || s.status === 'in-progress')
647
+ if (!qs || qs.status === 'completed' || !hasActionable) {
648
+ console.error(`\n❌ --done 未带 --change,回退读到的 quick 会话 ${changeName} 已不可推进(已完成或无待办步骤)。`)
649
+ console.error(` 并发多 quick 会话时 .runtime/current-quick-run-id 可能指向他者会话(last-writer-wins)。`)
650
+ console.error(` 请显式带 --change <quick-session-id> 指定要完成的会话(sillyspec run quick --status 查各会话进度)。`)
651
+ process.exit(2) // 用法错(fallback 命中 stale/他者会话)→ exit 2
652
+ }
653
+ }
654
+
627
655
  // -- auto 模式:自动推进所有流程阶段
628
656
  if (stageName === 'auto') {
629
657
  return await runAutoMode(pm, progress, cwd, flags, effectiveChange, platformOpts)
@@ -632,7 +660,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
632
660
  // --change 只作为变更名标识,不再拦截流程
633
661
  // 注册变更到全局活跃列表(如果尚未注册)
634
662
  if (effectiveChange) {
635
- await pm.registerChange(cwd, effectiveChange)
663
+ pm.registerChange(cwd, effectiveChange)
636
664
  }
637
665
 
638
666
  // --reset
@@ -669,12 +697,12 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
669
697
  if (freshSteps && freshSteps.length > 0) {
670
698
  if (!progress.stages[stageName]) progress.stages[stageName] = { status: 'stale', steps: [] }
671
699
  progress.stages[stageName].steps = freshSteps.map(s => ({ name: s.name, status: 'pending' }))
672
- await pm._write(cwd, progress, effectiveChange)
673
- progress = await pm.read(cwd, effectiveChange) || progress
700
+ pm._write(cwd, progress, effectiveChange)
701
+ progress = pm.read(cwd, effectiveChange) || progress
674
702
  }
675
703
  }
676
704
 
677
- const result = await pm.reopenStage(cwd, stageName, {
705
+ const result = pm.reopenStage(cwd, stageName, {
678
706
  fromStep: fromStepValue,
679
707
  changeName: effectiveChange,
680
708
  })
@@ -692,7 +720,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
692
720
  console.log('')
693
721
 
694
722
  // 重新读取 progress
695
- progress = await pm.read(cwd, effectiveChange) || progress
723
+ progress = pm.read(cwd, effectiveChange) || progress
696
724
 
697
725
  // 注入 revision context 到 platformOpts,供 outputStep 使用
698
726
  const stageData = progress.stages[stageName]
@@ -724,7 +752,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
724
752
  // currentIdx === -1 自动重置(~1944 行);in-progress 输出当前 step;全新由
725
753
  // ensureStageSteps 初始化;显式从头用 --reset(已由 !isReset 排除出本分支)。
726
754
  try {
727
- const runtimeRoot = platformOpts.runtimeRoot || join(specRoot, '.runtime')
755
+ const runtimeRoot = resolveRuntimeRoot(platformOpts, specRoot)
728
756
  mkdirSync(runtimeRoot, { recursive: true })
729
757
  writeAtomicSync(join(runtimeRoot, 'current-quick-run-id'), quickSessionId + '\n')
730
758
  } catch (e) {
@@ -740,9 +768,9 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
740
768
  // 确保步骤已初始化
741
769
  const changed = await ensureStageSteps(progress, stageName, cwd, specRoot)
742
770
  if (changed && effectiveChange) {
743
- await pm._write(cwd, progress, effectiveChange)
771
+ pm._write(cwd, progress, effectiveChange)
744
772
  triggerSync(cwd, effectiveChange, platformOpts)
745
- progress = await pm.read(cwd, effectiveChange) || progress
773
+ progress = pm.read(cwd, effectiveChange) || progress
746
774
  }
747
775
 
748
776
  // --status
@@ -772,7 +800,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
772
800
  // --done
773
801
  if (isDone) {
774
802
  const doneAnswer = getFlagValue('--answer')
775
- return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts, confirmMode, doneAnswer, isForceBaseline, isAllowNew })
803
+ return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts, doneAnswer, isForceBaseline, isAllowNew })
776
804
  }
777
805
 
778
806
  // 默认:输出当前步骤
@@ -898,7 +926,7 @@ async function resetStage(pm, progress, stageName, cwd, changeName, platformOpts
898
926
  steps: defSteps ? defSteps.map(s => ({ name: s.name, status: 'pending' })) : []
899
927
  }
900
928
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
901
- await pm._write(cwd, progress, changeName)
929
+ pm._write(cwd, progress, changeName)
902
930
  triggerSync(cwd, changeName, platformOpts)
903
931
  console.log(`🔄 ${stageName} 阶段已重置`)
904
932
  }
@@ -948,18 +976,18 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
948
976
  completedAt: null,
949
977
  steps: brainstormAutoDef.steps.map(s => ({ name: s.name, status: 'pending' }))
950
978
  }
951
- await pm._write(cwd, progress, changeName)
979
+ pm._write(cwd, progress, changeName)
952
980
  triggerSync(cwd, changeName, platformOpts)
953
- progress = await pm.read(cwd, changeName)
981
+ progress = pm.read(cwd, changeName)
954
982
  return progress
955
983
  }
956
984
  }
957
985
  const changed = await ensureStageSteps(progress, stage, cwd)
958
986
  if (stageChanged || changed) {
959
- await pm._write(cwd, progress, changeName)
987
+ pm._write(cwd, progress, changeName)
960
988
  triggerSync(cwd, changeName, platformOpts)
961
989
  }
962
- progress = await pm.read(cwd, changeName)
990
+ progress = pm.read(cwd, changeName)
963
991
  return progress
964
992
  }
965
993
 
@@ -1051,7 +1079,7 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
1051
1079
 
1052
1080
  const result = await completeStep(pm, progress, currentStage, cwd, outputText, inputText, { printNext: false, changeName, platformOpts })
1053
1081
  if (!result) return
1054
- progress = await pm.read(cwd, changeName)
1082
+ progress = pm.read(cwd, changeName)
1055
1083
 
1056
1084
  const nextPendingIdx = progress.stages[currentStage]?.steps?.findIndex(step => step.status === 'pending' || step.status === 'in-progress') ?? -1
1057
1085
  if (nextPendingIdx !== -1) {
@@ -1084,7 +1112,7 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
1084
1112
  if (currentStage === 'brainstorm' && next === 'plan') {
1085
1113
  const changeDir = resolveChangeDir(cwd, progress, platformOpts?.specRoot || null)
1086
1114
  if (changeDir) {
1087
- const nextActionFile = join(changeDir, 'brainstorm', 'next-action.json')
1115
+ const nextActionFile = join(changeDir, 'next-action.json')
1088
1116
  try {
1089
1117
  const nextAction = JSON.parse(readFileSync(nextActionFile, 'utf8'))
1090
1118
  if (nextAction.has_blocking_questions === true) {
@@ -1115,9 +1143,9 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
1115
1143
  }
1116
1144
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
1117
1145
  await ensureStageSteps(progress, next, cwd)
1118
- await pm._write(cwd, progress, changeName)
1146
+ pm._write(cwd, progress, changeName)
1119
1147
  triggerSync(cwd, changeName, platformOpts)
1120
- progress = await pm.read(cwd, changeName)
1148
+ progress = pm.read(cwd, changeName)
1121
1149
 
1122
1150
  console.log(`\n${currentStage} complete. Auto advanced to ${next}.`)
1123
1151
  const nextSteps = await getAutoSteps(next)