sillyspec 3.11.11 → 3.12.0

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.
package/src/run.js CHANGED
@@ -11,6 +11,33 @@ import { stageRegistry, auxiliaryStages } from './stages/index.js'
11
11
  import { buildExecuteSteps } from './stages/execute.js'
12
12
  import { buildPlanSteps } from './stages/plan.js'
13
13
 
14
+ /**
15
+ * 同步触发辅助函数:_write 后 best-effort 同步到平台
16
+ */
17
+ async function triggerSync(cwd, changeName) {
18
+ try {
19
+ const syncMod = await import('./sync.js')
20
+ await syncMod.sync(cwd, changeName)
21
+ } catch (e) {
22
+ // sync.js 不存在或同步失败,静默跳过
23
+ console.warn('⚠️ 同步失败:', e.message)
24
+ }
25
+ }
26
+
27
+ /**
28
+ * 审批检查辅助函数:execute 阶段启动前检查
29
+ * @returns {{ status: string, reason?: string } | null}
30
+ */
31
+ async function checkApproval(cwd, changeName) {
32
+ try {
33
+ const syncMod = await import('./sync.js')
34
+ return await syncMod.checkApproval(cwd, changeName)
35
+ } catch (e) {
36
+ // sync.js 不存在或检查失败,静默跳过
37
+ return null
38
+ }
39
+ }
40
+
14
41
  /**
15
42
  * 统一查找变更目录(与 progress.js 的变更检测逻辑一致)
16
43
  */
@@ -207,6 +234,7 @@ export async function runCommand(args, cwd) {
207
234
  const isStatus = flags.includes('--status')
208
235
  const isReset = flags.includes('--reset')
209
236
  const isConfirm = flags.includes('--confirm')
237
+ const isSkipApproval = flags.includes('--skip-approval')
210
238
 
211
239
  // 解析 --output
212
240
  let outputText = null
@@ -232,14 +260,13 @@ export async function runCommand(args, cwd) {
232
260
  const isAuxiliary = auxiliaryStages.includes(stageName)
233
261
 
234
262
  const pm = new ProgressManager()
235
- pm._migrateIfNeeded(cwd)
236
- let progress = pm.read(cwd, changeName)
263
+ let progress = await pm.read(cwd, changeName)
237
264
 
238
265
  if (!progress) {
239
266
  // 如果指定了变更名或有变更目录,自动初始化变更的 progress
240
267
  const autoChange = changeName || resolveChangeNameAuto(cwd)
241
268
  if (autoChange) {
242
- progress = pm.initChange(cwd, autoChange)
269
+ progress = await pm.initChange(cwd, autoChange)
243
270
  } else if (!isAuxiliary) {
244
271
  // brainstorm / propose 作为流程入口,自动生成变更名并初始化
245
272
  if (stageName === 'brainstorm' || stageName === 'propose') {
@@ -247,7 +274,7 @@ export async function runCommand(args, cwd) {
247
274
  const autoName = `${date}-new-change`
248
275
  console.log(`🔄 自动创建变更:${autoName}`)
249
276
  console.log(` 提示:可以用 --change <名称> 指定自定义变更名`)
250
- progress = pm.initChange(cwd, autoName)
277
+ progress = await pm.initChange(cwd, autoName)
251
278
  changeName = autoName
252
279
  } else {
253
280
  console.error('❌ 未找到 progress.json,请先运行 sillyspec init 或指定 --change <变更名>')
@@ -267,7 +294,7 @@ export async function runCommand(args, cwd) {
267
294
  // --change 只作为变更名标识,不再拦截流程
268
295
  // 注册变更到全局活跃列表(如果尚未注册)
269
296
  if (effectiveChange) {
270
- pm.registerChange(cwd, effectiveChange)
297
+ await pm.registerChange(cwd, effectiveChange)
271
298
  }
272
299
 
273
300
  // --reset
@@ -278,8 +305,9 @@ export async function runCommand(args, cwd) {
278
305
  // 确保步骤已初始化
279
306
  const changed = await ensureStageSteps(progress, stageName, cwd)
280
307
  if (changed) {
281
- pm._write(cwd, progress, effectiveChange)
282
- progress = pm.read(cwd, effectiveChange)
308
+ await pm._write(cwd, progress, effectiveChange)
309
+ triggerSync(cwd, effectiveChange)
310
+ progress = await pm.read(cwd, effectiveChange)
283
311
  }
284
312
 
285
313
  // --status
@@ -298,7 +326,7 @@ export async function runCommand(args, cwd) {
298
326
  }
299
327
 
300
328
  // 默认:输出当前步骤
301
- return await runStage(pm, progress, stageName, cwd, effectiveChange)
329
+ return await runStage(pm, progress, stageName, cwd, effectiveChange, isSkipApproval)
302
330
  }
303
331
 
304
332
  /**
@@ -313,11 +341,27 @@ function resolveChangeNameAuto(cwd) {
313
341
  return null
314
342
  }
315
343
 
316
- async function runStage(pm, progress, stageName, cwd, changeName) {
344
+ async function runStage(pm, progress, stageName, cwd, changeName, skipApproval = false) {
345
+ // execute 阶段启动前检查审批
346
+ if (stageName === 'execute' && !skipApproval) {
347
+ const approval = await checkApproval(cwd, changeName)
348
+ if (approval) {
349
+ if (approval.status === 'rejected') {
350
+ console.error(`❌ 变更 ${changeName} 的执行已被拒绝:${approval.reason || '无原因'}`)
351
+ process.exit(1)
352
+ }
353
+ if (approval.status === 'pending') {
354
+ console.log(`⏳ 变更 ${changeName} 的执行审批待处理中...`)
355
+ console.log(' 提示:使用 --skip-approval 跳过审批检查')
356
+ }
357
+ }
358
+ }
359
+
317
360
  // 自动探测 currentChange
318
361
  if (autoDetectChange(progress, cwd)) {
319
362
  progress.lastActive = new Date().toLocaleString('zh-CN', { hour12: false })
320
- pm._write(cwd, progress, changeName)
363
+ await pm._write(cwd, progress, changeName)
364
+ triggerSync(cwd, changeName)
321
365
  }
322
366
 
323
367
  const stageData = progress.stages[stageName]
@@ -330,7 +374,8 @@ async function runStage(pm, progress, stageName, cwd, changeName) {
330
374
  if (progress.currentStage !== stageName) {
331
375
  progress.currentStage = stageName
332
376
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
333
- pm._write(cwd, progress, changeName)
377
+ await pm._write(cwd, progress, changeName)
378
+ triggerSync(cwd, changeName)
334
379
  }
335
380
 
336
381
  const steps = stageData.steps
@@ -345,7 +390,8 @@ async function runStage(pm, progress, stageName, cwd, changeName) {
345
390
  stageData.status = 'in-progress'
346
391
  stageData.startedAt = new Date().toLocaleString('zh-CN', { hour12: false })
347
392
  stageData.completedAt = null
348
- pm._write(cwd, progress, changeName)
393
+ await pm._write(cwd, progress, changeName)
394
+ triggerSync(cwd, changeName)
349
395
  currentIdx = 0
350
396
  console.log(`🔄 ${stageName} 阶段已自动重置,重新开始。\n`)
351
397
  }
@@ -506,7 +552,8 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
506
552
  stageData.status = 'completed'
507
553
  stageData.completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
508
554
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
509
- pm._write(cwd, progress, changeName)
555
+ await pm._write(cwd, progress, changeName)
556
+ triggerSync(cwd, changeName)
510
557
 
511
558
  // Append to user-inputs.md
512
559
  if (outputText) {
@@ -552,7 +599,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
552
599
  }
553
600
 
554
601
  // 从全局活跃列表移除
555
- pm.unregisterChange(cwd, archiveChangeName)
602
+ await pm.unregisterChange(cwd, archiveChangeName)
556
603
  console.log(`📦 已归档:${archiveChangeName} → archive/${date}-${archiveChangeName}/`)
557
604
  } else {
558
605
  console.log('⚠️ 请添加 --confirm 确认归档,例如:sillyspec run archive --done --confirm --output "确认归档"')
@@ -571,7 +618,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
571
618
  stageData.steps = freshSteps
572
619
  stageData.status = 'pending'
573
620
  stageData.completedAt = null
574
- pm._write(cwd, progress, changeName)
621
+ await pm._write(cwd, progress, changeName)
575
622
  }
576
623
 
577
624
  const total = steps.length
@@ -590,7 +637,8 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
590
637
  }
591
638
 
592
639
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
593
- pm._write(cwd, progress, changeName)
640
+ await pm._write(cwd, progress, changeName)
641
+ triggerSync(cwd, changeName)
594
642
 
595
643
  // Append to user-inputs.md
596
644
  if (outputText) {
@@ -632,7 +680,8 @@ async function skipStep(pm, progress, stageName, cwd, changeName) {
632
680
  steps[currentIdx].status = 'skipped'
633
681
  steps[currentIdx].skippedAt = new Date().toLocaleString('zh-CN',{hour12:false})
634
682
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
635
- pm._write(cwd, progress, changeName)
683
+ await pm._write(cwd, progress, changeName)
684
+ triggerSync(cwd, changeName)
636
685
 
637
686
  console.log(`⏭️ Step ${currentIdx + 1}/${steps.length} 已跳过:${steps[currentIdx].name}`)
638
687
 
@@ -694,7 +743,8 @@ async function resetStage(pm, progress, stageName, cwd, changeName) {
694
743
  steps: defSteps ? defSteps.map(s => ({ name: s.name, status: 'pending' })) : []
695
744
  }
696
745
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
697
- pm._write(cwd, progress, changeName)
746
+ await pm._write(cwd, progress, changeName)
747
+ triggerSync(cwd, changeName)
698
748
  console.log(`🔄 ${stageName} 阶段已重置`)
699
749
  }
700
750
 
@@ -708,6 +758,7 @@ async function runAutoMode(pm, progress, cwd, flags, changeName) {
708
758
  const outputText = outputIdx !== -1 && flags[outputIdx + 1] ? flags[outputIdx + 1] : null
709
759
  const inputIdx = flags.indexOf('--input')
710
760
  const inputText = inputIdx !== -1 && flags[inputIdx + 1] ? flags[inputIdx + 1] : null
761
+ const skipApproval = flags.includes('--skip-approval')
711
762
  const nextInFlow = (stage) => {
712
763
  const i = flowStages.indexOf(stage)
713
764
  return i >= 0 && i < flowStages.length - 1 ? flowStages[i + 1] : null
@@ -717,8 +768,11 @@ async function runAutoMode(pm, progress, cwd, flags, changeName) {
717
768
  const stageChanged = progress.currentStage !== stage
718
769
  progress.currentStage = stage
719
770
  const changed = await ensureStageSteps(progress, stage, cwd)
720
- if (stageChanged || changed) pm._write(cwd, progress, changeName)
721
- progress = pm.read(cwd, changeName)
771
+ if (stageChanged || changed) {
772
+ await pm._write(cwd, progress, changeName)
773
+ triggerSync(cwd, changeName)
774
+ }
775
+ progress = await pm.read(cwd, changeName)
722
776
  return progress
723
777
  }
724
778
 
@@ -765,6 +819,20 @@ async function runAutoMode(pm, progress, cwd, flags, changeName) {
765
819
  else console.log('All auto flow stages are complete.')
766
820
  return
767
821
  }
822
+ // execute 阶段启动前检查审批
823
+ if (currentStage === 'execute' && !skipApproval) {
824
+ const approval = await checkApproval(cwd, changeName)
825
+ if (approval) {
826
+ if (approval.status === 'rejected') {
827
+ console.error(`❌ 变更 ${changeName} 的执行已被拒绝:${approval.reason || '无原因'}`)
828
+ process.exit(1)
829
+ }
830
+ if (approval.status === 'pending') {
831
+ console.log(`⏳ 变更 ${changeName} 的执行审批待处理中...`)
832
+ console.log(' 提示:使用 --skip-approval 跳过审批检查')
833
+ }
834
+ }
835
+ }
768
836
  outputStep(currentStage, pendingIdx, defSteps, cwd, changeName)
769
837
  return
770
838
  }
@@ -776,11 +844,25 @@ async function runAutoMode(pm, progress, cwd, flags, changeName) {
776
844
 
777
845
  const result = await completeStep(pm, progress, currentStage, cwd, outputText, inputText, { printNext: false, changeName })
778
846
  if (!result) return
779
- progress = pm.read(cwd, changeName)
847
+ progress = await pm.read(cwd, changeName)
780
848
 
781
849
  const nextPendingIdx = progress.stages[currentStage]?.steps?.findIndex(step => step.status === 'pending') ?? -1
782
850
  if (nextPendingIdx !== -1) {
783
851
  const defSteps = await getStageSteps(currentStage, cwd, progress)
852
+ // execute 阶段启动前检查审批
853
+ if (currentStage === 'execute' && !skipApproval) {
854
+ const approval = await checkApproval(cwd, changeName)
855
+ if (approval) {
856
+ if (approval.status === 'rejected') {
857
+ console.error(`❌ 变更 ${changeName} 的执行已被拒绝:${approval.reason || '无原因'}`)
858
+ process.exit(1)
859
+ }
860
+ if (approval.status === 'pending') {
861
+ console.log(`⏳ 变更 ${changeName} 的执行审批待处理中...`)
862
+ console.log(' 提示:使用 --skip-approval 跳过审批检查')
863
+ }
864
+ }
865
+ }
784
866
  outputStep(currentStage, nextPendingIdx, defSteps, cwd, changeName)
785
867
  return
786
868
  }
@@ -801,11 +883,28 @@ async function runAutoMode(pm, progress, cwd, flags, changeName) {
801
883
  }
802
884
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
803
885
  await ensureStageSteps(progress, next, cwd)
804
- pm._write(cwd, progress, changeName)
805
- progress = pm.read(cwd, changeName)
886
+ await pm._write(cwd, progress, changeName)
887
+ triggerSync(cwd, changeName)
888
+ progress = await pm.read(cwd, changeName)
806
889
 
807
890
  console.log(`\n${currentStage} complete. Auto advanced to ${next}.`)
808
891
  const nextSteps = await getStageSteps(next, cwd, progress)
809
892
  const firstPending = progress.stages[next]?.steps?.findIndex(step => step.status === 'pending') ?? -1
810
- if (firstPending !== -1) outputStep(next, firstPending, nextSteps, cwd, changeName)
893
+ if (firstPending !== -1) {
894
+ // execute 阶段启动前检查审批
895
+ if (next === 'execute' && !skipApproval) {
896
+ const approval = await checkApproval(cwd, changeName)
897
+ if (approval) {
898
+ if (approval.status === 'rejected') {
899
+ console.error(`❌ 变更 ${changeName} 的执行已被拒绝:${approval.reason || '无原因'}`)
900
+ process.exit(1)
901
+ }
902
+ if (approval.status === 'pending') {
903
+ console.log(`⏳ 变更 ${changeName} 的执行审批待处理中...`)
904
+ console.log(' 提示:使用 --skip-approval 跳过审批检查')
905
+ }
906
+ }
907
+ }
908
+ outputStep(next, firstPending, nextSteps, cwd, changeName)
909
+ }
811
910
  }
@@ -142,22 +142,18 @@ module-impact.md 路径 + 影响模块数量 + 未匹配文件数量`,
142
142
  },
143
143
  {
144
144
  name: '确认归档',
145
- prompt: `展示即将归档的内容,请用户确认。
145
+ prompt: `确认归档内容并执行目录移动。
146
146
 
147
147
  ### 操作
148
148
  1. 展示:变更目录名、包含的文件列表(含 module-impact.md)、生成总结
149
- 2. 请用户确认是否执行归档
150
- 3. 确认后:将 \`.sillyspec/changes/<change-name>/\` 移动到 \`.sillyspec/changes/archive/YYYY-MM-DD-<change-name>/\`
151
- 4. 确保所有 checkbox 都已勾选
152
-
153
- ### 归档执行
154
- 确认归档后,执行以下命令自动完成目录移动:
155
- \t\tsillyspec run archive --done --confirm --output "确认归档"
156
- - \`--confirm\` 标志会自动执行目录移动(原子操作)
157
- - 不带 \`--confirm\` 则只提示需要确认
149
+ 2. **直接执行归档**(本步骤完成后自动移动):
150
+ - 创建 archive 目录:\`mkdir -p .sillyspec/changes/archive\`
151
+ - 移动变更目录:\`mv .sillyspec/changes/<change-name> .sillyspec/changes/archive/<change-name>\`
152
+ - 确认移动成功:\`ls .sillyspec/changes/archive/<change-name>/\`
153
+ 3. 确保所有 checkbox 都已勾选
158
154
 
159
155
  ### 输出
160
- 归档确认`,
156
+ 归档完成 + archive 目录路径`,
161
157
  outputHint: '归档确认',
162
158
  optional: false
163
159
  },
@@ -27,7 +27,7 @@ export const definition = {
27
27
  ### 只允许的操作
28
28
  - git status / git diff / git show / git log / git stash list(只读)
29
29
  - cat / head / grep / find / wc(只读检查)
30
- - 写入 .sillyspec/changes/ 下的报告文件(verification.md)
30
+ - 写入 .sillyspec/changes/ 下的报告文件(verify-result.md)
31
31
  - 运行测试命令(不修改源码)
32
32
  - 运行 lint 命令(不自动修复)
33
33
 
@@ -185,14 +185,14 @@ grep -rl "<关键词>" <源码目录>/ --include="*.java" --include="*.js" --inc
185
185
  },
186
186
  {
187
187
  name: '输出验证报告',
188
- prompt: `生成完整验证报告,并写入 verification.md。
188
+ prompt: `生成完整验证报告,并写入 verify-result.md。
189
189
 
190
190
  ### 操作
191
191
  1. 汇总以上所有检查结果
192
- 2. 生成 verification.md 文件,保存到 \`.sillyspec/changes/<change-name>/verification.md\`
192
+ 2. 生成 verify-result.md 文件,保存到 \`.sillyspec/changes/<change-name>/verify-result.md\`
193
193
  3. 给出结论:PASS / PASS WITH NOTES / FAIL
194
194
 
195
- ### verification.md 格式
195
+ ### verify-result.md 格式
196
196
  \`\`\`markdown
197
197
  # 验证报告
198
198
 
@@ -221,12 +221,12 @@ PASS / PASS WITH NOTES / FAIL
221
221
  \`\`\`
222
222
 
223
223
  ### 输出
224
- verification.md 路径 + 验证报告摘要 + 下一步命令
224
+ verify-result.md 路径 + 验证报告摘要 + 下一步命令
225
225
 
226
226
  ### 注意
227
227
  - PASS → 运行 \`sillyspec run archive\` 归档
228
228
  - FAIL → 修复后运行 \`sillyspec run verify\` 重新验证
229
- - verification.md 是变更包的正式验收记录,归档后保留`,
229
+ - verify-result.md 是变更包的正式验收记录,归档后保留`,
230
230
  outputHint: '验证报告',
231
231
  optional: false
232
232
  }