sillyspec 3.11.11 → 3.12.1

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,22 +260,25 @@ 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)
243
- } else if (!isAuxiliary) {
269
+ progress = await pm.initChange(cwd, autoChange)
270
+ } else if (isAuxiliary) {
271
+ // 辅助阶段(scan/explore/quick/doctor/status)不需要 currentChange
272
+ // 但仍然需要初始化一个空的 progress 对象以避免后续引用报错
273
+ progress = { currentStage: stageName, stages: {}, lastActive: new Date().toLocaleString('zh-CN', { hour12: false }) }
274
+ } else {
244
275
  // brainstorm / propose 作为流程入口,自动生成变更名并初始化
245
276
  if (stageName === 'brainstorm' || stageName === 'propose') {
246
277
  const date = new Date().toISOString().slice(0, 10)
247
278
  const autoName = `${date}-new-change`
248
279
  console.log(`🔄 自动创建变更:${autoName}`)
249
280
  console.log(` 提示:可以用 --change <名称> 指定自定义变更名`)
250
- progress = pm.initChange(cwd, autoName)
281
+ progress = await pm.initChange(cwd, autoName)
251
282
  changeName = autoName
252
283
  } else {
253
284
  console.error('❌ 未找到 progress.json,请先运行 sillyspec init 或指定 --change <变更名>')
@@ -267,7 +298,7 @@ export async function runCommand(args, cwd) {
267
298
  // --change 只作为变更名标识,不再拦截流程
268
299
  // 注册变更到全局活跃列表(如果尚未注册)
269
300
  if (effectiveChange) {
270
- pm.registerChange(cwd, effectiveChange)
301
+ await pm.registerChange(cwd, effectiveChange)
271
302
  }
272
303
 
273
304
  // --reset
@@ -277,9 +308,10 @@ export async function runCommand(args, cwd) {
277
308
 
278
309
  // 确保步骤已初始化
279
310
  const changed = await ensureStageSteps(progress, stageName, cwd)
280
- if (changed) {
281
- pm._write(cwd, progress, effectiveChange)
282
- progress = pm.read(cwd, effectiveChange)
311
+ if (changed && effectiveChange) {
312
+ await pm._write(cwd, progress, effectiveChange)
313
+ triggerSync(cwd, effectiveChange)
314
+ progress = await pm.read(cwd, effectiveChange) || progress
283
315
  }
284
316
 
285
317
  // --status
@@ -298,7 +330,7 @@ export async function runCommand(args, cwd) {
298
330
  }
299
331
 
300
332
  // 默认:输出当前步骤
301
- return await runStage(pm, progress, stageName, cwd, effectiveChange)
333
+ return await runStage(pm, progress, stageName, cwd, effectiveChange, isSkipApproval)
302
334
  }
303
335
 
304
336
  /**
@@ -313,11 +345,27 @@ function resolveChangeNameAuto(cwd) {
313
345
  return null
314
346
  }
315
347
 
316
- async function runStage(pm, progress, stageName, cwd, changeName) {
348
+ async function runStage(pm, progress, stageName, cwd, changeName, skipApproval = false) {
349
+ // execute 阶段启动前检查审批
350
+ if (stageName === 'execute' && !skipApproval) {
351
+ const approval = await checkApproval(cwd, changeName)
352
+ if (approval) {
353
+ if (approval.status === 'rejected') {
354
+ console.error(`❌ 变更 ${changeName} 的执行已被拒绝:${approval.reason || '无原因'}`)
355
+ process.exit(1)
356
+ }
357
+ if (approval.status === 'pending') {
358
+ console.log(`⏳ 变更 ${changeName} 的执行审批待处理中...`)
359
+ console.log(' 提示:使用 --skip-approval 跳过审批检查')
360
+ }
361
+ }
362
+ }
363
+
317
364
  // 自动探测 currentChange
318
365
  if (autoDetectChange(progress, cwd)) {
319
366
  progress.lastActive = new Date().toLocaleString('zh-CN', { hour12: false })
320
- pm._write(cwd, progress, changeName)
367
+ await pm._write(cwd, progress, changeName)
368
+ triggerSync(cwd, changeName)
321
369
  }
322
370
 
323
371
  const stageData = progress.stages[stageName]
@@ -330,7 +378,8 @@ async function runStage(pm, progress, stageName, cwd, changeName) {
330
378
  if (progress.currentStage !== stageName) {
331
379
  progress.currentStage = stageName
332
380
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
333
- pm._write(cwd, progress, changeName)
381
+ await pm._write(cwd, progress, changeName)
382
+ triggerSync(cwd, changeName)
334
383
  }
335
384
 
336
385
  const steps = stageData.steps
@@ -345,7 +394,8 @@ async function runStage(pm, progress, stageName, cwd, changeName) {
345
394
  stageData.status = 'in-progress'
346
395
  stageData.startedAt = new Date().toLocaleString('zh-CN', { hour12: false })
347
396
  stageData.completedAt = null
348
- pm._write(cwd, progress, changeName)
397
+ await pm._write(cwd, progress, changeName)
398
+ triggerSync(cwd, changeName)
349
399
  currentIdx = 0
350
400
  console.log(`🔄 ${stageName} 阶段已自动重置,重新开始。\n`)
351
401
  }
@@ -506,7 +556,8 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
506
556
  stageData.status = 'completed'
507
557
  stageData.completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
508
558
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
509
- pm._write(cwd, progress, changeName)
559
+ await pm._write(cwd, progress, changeName)
560
+ triggerSync(cwd, changeName)
510
561
 
511
562
  // Append to user-inputs.md
512
563
  if (outputText) {
@@ -552,7 +603,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
552
603
  }
553
604
 
554
605
  // 从全局活跃列表移除
555
- pm.unregisterChange(cwd, archiveChangeName)
606
+ await pm.unregisterChange(cwd, archiveChangeName)
556
607
  console.log(`📦 已归档:${archiveChangeName} → archive/${date}-${archiveChangeName}/`)
557
608
  } else {
558
609
  console.log('⚠️ 请添加 --confirm 确认归档,例如:sillyspec run archive --done --confirm --output "确认归档"')
@@ -571,7 +622,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
571
622
  stageData.steps = freshSteps
572
623
  stageData.status = 'pending'
573
624
  stageData.completedAt = null
574
- pm._write(cwd, progress, changeName)
625
+ await pm._write(cwd, progress, changeName)
575
626
  }
576
627
 
577
628
  const total = steps.length
@@ -590,7 +641,8 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
590
641
  }
591
642
 
592
643
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
593
- pm._write(cwd, progress, changeName)
644
+ await pm._write(cwd, progress, changeName)
645
+ triggerSync(cwd, changeName)
594
646
 
595
647
  // Append to user-inputs.md
596
648
  if (outputText) {
@@ -632,7 +684,8 @@ async function skipStep(pm, progress, stageName, cwd, changeName) {
632
684
  steps[currentIdx].status = 'skipped'
633
685
  steps[currentIdx].skippedAt = new Date().toLocaleString('zh-CN',{hour12:false})
634
686
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
635
- pm._write(cwd, progress, changeName)
687
+ await pm._write(cwd, progress, changeName)
688
+ triggerSync(cwd, changeName)
636
689
 
637
690
  console.log(`⏭️ Step ${currentIdx + 1}/${steps.length} 已跳过:${steps[currentIdx].name}`)
638
691
 
@@ -694,7 +747,8 @@ async function resetStage(pm, progress, stageName, cwd, changeName) {
694
747
  steps: defSteps ? defSteps.map(s => ({ name: s.name, status: 'pending' })) : []
695
748
  }
696
749
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
697
- pm._write(cwd, progress, changeName)
750
+ await pm._write(cwd, progress, changeName)
751
+ triggerSync(cwd, changeName)
698
752
  console.log(`🔄 ${stageName} 阶段已重置`)
699
753
  }
700
754
 
@@ -708,6 +762,7 @@ async function runAutoMode(pm, progress, cwd, flags, changeName) {
708
762
  const outputText = outputIdx !== -1 && flags[outputIdx + 1] ? flags[outputIdx + 1] : null
709
763
  const inputIdx = flags.indexOf('--input')
710
764
  const inputText = inputIdx !== -1 && flags[inputIdx + 1] ? flags[inputIdx + 1] : null
765
+ const skipApproval = flags.includes('--skip-approval')
711
766
  const nextInFlow = (stage) => {
712
767
  const i = flowStages.indexOf(stage)
713
768
  return i >= 0 && i < flowStages.length - 1 ? flowStages[i + 1] : null
@@ -717,8 +772,11 @@ async function runAutoMode(pm, progress, cwd, flags, changeName) {
717
772
  const stageChanged = progress.currentStage !== stage
718
773
  progress.currentStage = stage
719
774
  const changed = await ensureStageSteps(progress, stage, cwd)
720
- if (stageChanged || changed) pm._write(cwd, progress, changeName)
721
- progress = pm.read(cwd, changeName)
775
+ if (stageChanged || changed) {
776
+ await pm._write(cwd, progress, changeName)
777
+ triggerSync(cwd, changeName)
778
+ }
779
+ progress = await pm.read(cwd, changeName)
722
780
  return progress
723
781
  }
724
782
 
@@ -765,6 +823,20 @@ async function runAutoMode(pm, progress, cwd, flags, changeName) {
765
823
  else console.log('All auto flow stages are complete.')
766
824
  return
767
825
  }
826
+ // execute 阶段启动前检查审批
827
+ if (currentStage === 'execute' && !skipApproval) {
828
+ const approval = await checkApproval(cwd, changeName)
829
+ if (approval) {
830
+ if (approval.status === 'rejected') {
831
+ console.error(`❌ 变更 ${changeName} 的执行已被拒绝:${approval.reason || '无原因'}`)
832
+ process.exit(1)
833
+ }
834
+ if (approval.status === 'pending') {
835
+ console.log(`⏳ 变更 ${changeName} 的执行审批待处理中...`)
836
+ console.log(' 提示:使用 --skip-approval 跳过审批检查')
837
+ }
838
+ }
839
+ }
768
840
  outputStep(currentStage, pendingIdx, defSteps, cwd, changeName)
769
841
  return
770
842
  }
@@ -776,11 +848,25 @@ async function runAutoMode(pm, progress, cwd, flags, changeName) {
776
848
 
777
849
  const result = await completeStep(pm, progress, currentStage, cwd, outputText, inputText, { printNext: false, changeName })
778
850
  if (!result) return
779
- progress = pm.read(cwd, changeName)
851
+ progress = await pm.read(cwd, changeName)
780
852
 
781
853
  const nextPendingIdx = progress.stages[currentStage]?.steps?.findIndex(step => step.status === 'pending') ?? -1
782
854
  if (nextPendingIdx !== -1) {
783
855
  const defSteps = await getStageSteps(currentStage, cwd, progress)
856
+ // execute 阶段启动前检查审批
857
+ if (currentStage === 'execute' && !skipApproval) {
858
+ const approval = await checkApproval(cwd, changeName)
859
+ if (approval) {
860
+ if (approval.status === 'rejected') {
861
+ console.error(`❌ 变更 ${changeName} 的执行已被拒绝:${approval.reason || '无原因'}`)
862
+ process.exit(1)
863
+ }
864
+ if (approval.status === 'pending') {
865
+ console.log(`⏳ 变更 ${changeName} 的执行审批待处理中...`)
866
+ console.log(' 提示:使用 --skip-approval 跳过审批检查')
867
+ }
868
+ }
869
+ }
784
870
  outputStep(currentStage, nextPendingIdx, defSteps, cwd, changeName)
785
871
  return
786
872
  }
@@ -801,11 +887,28 @@ async function runAutoMode(pm, progress, cwd, flags, changeName) {
801
887
  }
802
888
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
803
889
  await ensureStageSteps(progress, next, cwd)
804
- pm._write(cwd, progress, changeName)
805
- progress = pm.read(cwd, changeName)
890
+ await pm._write(cwd, progress, changeName)
891
+ triggerSync(cwd, changeName)
892
+ progress = await pm.read(cwd, changeName)
806
893
 
807
894
  console.log(`\n${currentStage} complete. Auto advanced to ${next}.`)
808
895
  const nextSteps = await getStageSteps(next, cwd, progress)
809
896
  const firstPending = progress.stages[next]?.steps?.findIndex(step => step.status === 'pending') ?? -1
810
- if (firstPending !== -1) outputStep(next, firstPending, nextSteps, cwd, changeName)
897
+ if (firstPending !== -1) {
898
+ // execute 阶段启动前检查审批
899
+ if (next === 'execute' && !skipApproval) {
900
+ const approval = await checkApproval(cwd, changeName)
901
+ if (approval) {
902
+ if (approval.status === 'rejected') {
903
+ console.error(`❌ 变更 ${changeName} 的执行已被拒绝:${approval.reason || '无原因'}`)
904
+ process.exit(1)
905
+ }
906
+ if (approval.status === 'pending') {
907
+ console.log(`⏳ 变更 ${changeName} 的执行审批待处理中...`)
908
+ console.log(' 提示:使用 --skip-approval 跳过审批检查')
909
+ }
910
+ }
911
+ }
912
+ outputStep(next, firstPending, nextSteps, cwd, changeName)
913
+ }
811
914
  }
@@ -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
  }