sillyspec 3.10.5 → 3.10.7

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
@@ -2,6 +2,7 @@
2
2
  * sillyspec run 命令实现
3
3
  *
4
4
  * CLI 成为流程引擎,AI 变成步骤执行器。
5
+ * 支持多变更并行:每个变更独立 progress.json。
5
6
  */
6
7
  import { basename, join } from 'path'
7
8
  import { existsSync, readdirSync, mkdirSync, writeFileSync, appendFileSync, readFileSync, statSync } from 'fs'
@@ -11,7 +12,7 @@ import { buildExecuteSteps } from './stages/execute.js'
11
12
  import { buildPlanSteps } from './stages/plan.js'
12
13
 
13
14
  /**
14
- * 统一查找变更目录
15
+ * 统一查找变更目录(与 progress.js 的变更检测逻辑一致)
15
16
  */
16
17
  function resolveChangeDir(cwd, progress) {
17
18
  const changesDir = join(cwd, '.sillyspec', 'changes')
@@ -48,6 +49,19 @@ function autoDetectChange(progress, cwd) {
48
49
  return false
49
50
  }
50
51
 
52
+ /**
53
+ * 从 progress 或变更目录推导变更名
54
+ */
55
+ function resolveChangeName(cwd, progress) {
56
+ if (progress.currentChange) return progress.currentChange
57
+ const changesDir = join(cwd, '.sillyspec', 'changes')
58
+ if (!existsSync(changesDir)) return null
59
+ const entries = readdirSync(changesDir, { withFileTypes: true })
60
+ .filter(e => e.isDirectory() && e.name !== 'archive')
61
+ if (entries.length === 1) return entries[0].name
62
+ return null
63
+ }
64
+
51
65
  /**
52
66
  * 获取阶段的步骤定义(execute 需要动态构建)
53
67
  */
@@ -70,7 +84,7 @@ async function getStageSteps(stageName, cwd, progress) {
70
84
  }
71
85
 
72
86
  /**
73
- * 确保阶段的 steps 已初始化到 progress.json
87
+ * 确保阶段的 steps 已初始化到 progress
74
88
  */
75
89
  async function ensureStageSteps(progress, stageName, cwd) {
76
90
  if (!progress.stages) progress.stages = {}
@@ -90,7 +104,6 @@ async function ensureStageSteps(progress, stageName, cwd) {
90
104
 
91
105
  // 检查步骤数量是否匹配(execute 动态步骤可能变化)
92
106
  if (progress.stages[stageName].steps.length !== steps.length) {
93
- // 保留已完成的状态,重新构建步骤列表
94
107
  const oldSteps = progress.stages[stageName].steps
95
108
  progress.stages[stageName].steps = steps.map(s => {
96
109
  const old = oldSteps.find(step => step.name === s.name)
@@ -106,7 +119,7 @@ async function ensureStageSteps(progress, stageName, cwd) {
106
119
  /**
107
120
  * 输出当前步骤的 prompt
108
121
  */
109
- function outputStep(stageName, stepIndex, steps, cwd) {
122
+ function outputStep(stageName, stepIndex, steps, cwd, changeName) {
110
123
  const step = steps[stepIndex]
111
124
  const total = steps.length
112
125
  const projectName = basename(cwd)
@@ -131,6 +144,7 @@ function outputStep(stageName, stepIndex, steps, cwd) {
131
144
  console.log(`step: ${stepIndex + 1}/${total}`)
132
145
  console.log(`stepName: ${step.name}`)
133
146
  console.log(`project: ${projectName}`)
147
+ if (changeName) console.log(`change: ${changeName}`)
134
148
  console.log(`---\n`)
135
149
  if (personas[stageName]) {
136
150
  console.log(personas[stageName])
@@ -146,8 +160,9 @@ function outputStep(stageName, stepIndex, steps, cwd) {
146
160
  console.log('- 完成后立即执行 --done 命令,不得跳过')
147
161
  console.log('- 文档类型文件(.md/.yaml/.json 等)头部必须包含 author(git 用户名)和 created_at(精确到秒)')
148
162
  console.log('- 执行构建/测试前必须先读 local.yaml,优先使用其中配置的命令、路径和环境变量;未配置时才使用默认值')
163
+ const changeFlag = changeName ? ` --change ${changeName}` : ''
149
164
  console.log(`\n### 完成后执行`)
150
- console.log(`sillyspec run ${stageName} --done --input "用户原始需求/反馈" --output "你的摘要"`)
165
+ console.log(`sillyspec run ${stageName} --done${changeFlag} --input "用户原始需求/反馈" --output "你的摘要"`)
151
166
  }
152
167
 
153
168
  /**
@@ -200,41 +215,45 @@ export async function runCommand(args, cwd) {
200
215
  const isAuxiliary = auxiliaryStages.includes(stageName)
201
216
 
202
217
  const pm = new ProgressManager()
203
- let progress = pm.read(cwd)
218
+ pm._migrateIfNeeded(cwd)
219
+ let progress = pm.read(cwd, changeName)
204
220
 
205
221
  if (!progress) {
206
- // 辅助命令可以在没有 progress.json 时工作(比如 scan)
207
- if (!isAuxiliary) {
222
+ // 如果指定了变更名或有变更目录,自动初始化变更的 progress
223
+ const autoChange = changeName || resolveChangeNameAuto(cwd)
224
+ if (autoChange) {
225
+ progress = pm.initChange(cwd, autoChange)
226
+ } else if (!isAuxiliary) {
208
227
  console.error('❌ 未找到 progress.json,请先运行 sillyspec init')
228
+ console.error(' 提示:使用 --change <name> 指定变更名')
209
229
  process.exit(1)
210
230
  }
211
- progress = pm.init(cwd)
212
231
  }
213
232
 
233
+ // 确保 progress 有 currentChange
234
+ const effectiveChange = changeName || progress.currentChange || resolveChangeName(cwd, progress)
235
+
214
236
  // -- auto 模式:自动推进所有流程阶段
215
237
  if (stageName === 'auto') {
216
- return await runAutoMode(pm, progress, cwd, flags)
238
+ return await runAutoMode(pm, progress, cwd, flags, effectiveChange)
217
239
  }
218
240
 
219
- // --change 设置当前变更名
220
- if (changeName) {
221
- progress.currentChange = changeName
222
- progress.lastActive = new Date().toLocaleString('zh-CN', { hour12: false })
223
- pm._write(cwd, progress)
224
- console.log(`✅ 当前变更设置为:${changeName}`)
225
- return
241
+ // --change 只作为变更名标识,不再拦截流程
242
+ // 注册变更到全局活跃列表(如果尚未注册)
243
+ if (effectiveChange) {
244
+ pm.registerChange(cwd, effectiveChange)
226
245
  }
227
246
 
228
247
  // --reset
229
248
  if (isReset) {
230
- return await resetStage(pm, progress, stageName, cwd)
249
+ return await resetStage(pm, progress, stageName, cwd, effectiveChange)
231
250
  }
232
251
 
233
252
  // 确保步骤已初始化
234
253
  const changed = await ensureStageSteps(progress, stageName, cwd)
235
254
  if (changed) {
236
- pm._write(cwd, progress)
237
- progress = pm.read(cwd)
255
+ pm._write(cwd, progress, effectiveChange)
256
+ progress = pm.read(cwd, effectiveChange)
238
257
  }
239
258
 
240
259
  // --status
@@ -244,23 +263,35 @@ export async function runCommand(args, cwd) {
244
263
 
245
264
  // --skip
246
265
  if (isSkip) {
247
- return await skipStep(pm, progress, stageName, cwd)
266
+ return await skipStep(pm, progress, stageName, cwd, effectiveChange)
248
267
  }
249
268
 
250
269
  // --done
251
270
  if (isDone) {
252
- return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm })
271
+ return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange })
253
272
  }
254
273
 
255
274
  // 默认:输出当前步骤
256
- return await runStage(pm, progress, stageName, cwd)
275
+ return await runStage(pm, progress, stageName, cwd, effectiveChange)
257
276
  }
258
277
 
259
- async function runStage(pm, progress, stageName, cwd) {
278
+ /**
279
+ * 自动推导变更名(不依赖 progress)
280
+ */
281
+ function resolveChangeNameAuto(cwd) {
282
+ const changesDir = join(cwd, '.sillyspec', 'changes')
283
+ if (!existsSync(changesDir)) return null
284
+ const entries = readdirSync(changesDir, { withFileTypes: true })
285
+ .filter(e => e.isDirectory() && e.name !== 'archive')
286
+ if (entries.length === 1) return entries[0].name
287
+ return null
288
+ }
289
+
290
+ async function runStage(pm, progress, stageName, cwd, changeName) {
260
291
  // 自动探测 currentChange
261
292
  if (autoDetectChange(progress, cwd)) {
262
293
  progress.lastActive = new Date().toLocaleString('zh-CN', { hour12: false })
263
- pm._write(cwd, progress)
294
+ pm._write(cwd, progress, changeName)
264
295
  }
265
296
 
266
297
  const stageData = progress.stages[stageName]
@@ -273,7 +304,7 @@ async function runStage(pm, progress, stageName, cwd) {
273
304
  if (progress.currentStage !== stageName) {
274
305
  progress.currentStage = stageName
275
306
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
276
- pm._write(cwd, progress)
307
+ pm._write(cwd, progress, changeName)
277
308
  }
278
309
 
279
310
  const steps = stageData.steps
@@ -288,13 +319,12 @@ async function runStage(pm, progress, stageName, cwd) {
288
319
  stageData.status = 'in-progress'
289
320
  stageData.startedAt = new Date().toLocaleString('zh-CN', { hour12: false })
290
321
  stageData.completedAt = null
291
- pm._write(cwd, progress)
322
+ pm._write(cwd, progress, changeName)
292
323
  currentIdx = 0
293
324
  console.log(`🔄 ${stageName} 阶段已自动重置,重新开始。\n`)
294
325
  }
295
326
 
296
327
  if (currentIdx > 0) {
297
- // 有进行中的步骤,提示用户
298
328
  const completed = currentIdx
299
329
  const total = steps.length
300
330
  console.log(`⚠️ ${stageName} 已进行到第 ${currentIdx + 1}/${total} 步(前 ${completed} 步已完成)。`)
@@ -303,7 +333,7 @@ async function runStage(pm, progress, stageName, cwd) {
303
333
 
304
334
  const defSteps = await getStageSteps(stageName, cwd, progress)
305
335
  if (defSteps && defSteps[currentIdx]) {
306
- outputStep(stageName, currentIdx, defSteps, cwd)
336
+ outputStep(stageName, currentIdx, defSteps, cwd, changeName)
307
337
  }
308
338
  }
309
339
 
@@ -311,7 +341,6 @@ function validateMetadata(cwd, stageName) {
311
341
  const changesDir = join(cwd, '.sillyspec', 'changes')
312
342
  if (!existsSync(changesDir)) return
313
343
 
314
- // 找最近 10 分钟内修改的 md/yaml 文件
315
344
  const cutoff = Date.now() - 10 * 60 * 1000
316
345
  const missing = []
317
346
 
@@ -340,7 +369,7 @@ function validateMetadata(cwd, stageName) {
340
369
  }
341
370
 
342
371
  async function completeStep(pm, progress, stageName, cwd, outputText, inputText = null, options = {}) {
343
- const { printNext = true, confirm = false } = options
372
+ const { printNext = true, confirm = false, changeName } = options
344
373
  const stageData = progress.stages[stageName]
345
374
  if (!stageData || !stageData.steps) {
346
375
  console.error(`❌ 阶段 ${stageName} 未初始化`)
@@ -355,19 +384,16 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
355
384
  process.exit(1)
356
385
  }
357
386
 
358
- // 标记完成(先标记,再处理动态步骤插入)
359
-
360
387
  steps[currentIdx].status = 'completed'
361
388
  steps[currentIdx].completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
362
389
  if (outputText) {
363
390
  const MAX_OUTPUT = 200
364
391
  if (outputText.length > MAX_OUTPUT) {
365
392
  steps[currentIdx].output = outputText.slice(0, MAX_OUTPUT) + '…'
366
- // Save full output to artifacts/
367
393
  const artifactsDir = join(cwd, '.sillyspec', '.runtime', 'artifacts')
368
394
  mkdirSync(artifactsDir, { recursive: true })
369
395
  const ts = new Date().toISOString().slice(0,19).replace(/[-T:]/g, '')
370
- writeFileSync(join(artifactsDir, `${stageName}-step${currentIdx + 1}-${ts}.txt`), outputText)
396
+ writeFileSync(join(artifactsDir, `${changeName || 'unknown'}-${stageName}-step${currentIdx + 1}-${ts}.txt`), outputText)
371
397
  } else {
372
398
  steps[currentIdx].output = outputText
373
399
  }
@@ -384,10 +410,8 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
384
410
  const fullSteps = buildPlanSteps(changeDir, planContent)
385
411
  const prefixLen = fixedPrefix.length
386
412
  const suffixLen = fixedSuffix.length
387
- // 提取协调器步骤(prefix 和 suffix 之间)
388
413
  const coordinatorSteps = fullSteps.slice(prefixLen, suffixLen > 0 ? -suffixLen : undefined)
389
414
  if (coordinatorSteps.length > 0) {
390
- // 在当前步骤之后插入协调器步骤(含 prompt,否则 outputStep 无法打印)
391
415
  for (let i = 0; i < coordinatorSteps.length; i++) {
392
416
  steps.splice(currentIdx + 1 + i, 0, {
393
417
  name: coordinatorSteps[i].name,
@@ -406,36 +430,34 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
406
430
  const nextPendingIdx = steps.findIndex(s => s.status === 'pending')
407
431
 
408
432
  if (nextPendingIdx === -1) {
409
- // 全部完成 — 仅标记当前阶段,不自动推进 currentStage
410
433
  stageData.status = 'completed'
411
434
  stageData.completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
412
435
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
413
- pm._write(cwd, progress)
436
+ pm._write(cwd, progress, changeName)
414
437
 
415
438
  // Append to user-inputs.md
416
439
  if (outputText) {
417
440
  const inputsPath = join(cwd, '.sillyspec', '.runtime', 'user-inputs.md')
418
- const entry = `\n## ${new Date().toLocaleString('zh-CN',{hour12:false})} | ${stageName}: ${steps[currentIdx].name}\n${inputText ? "- 输入:" + inputText + "\n" : ""}- 输出:${outputText}\n`
441
+ const entry = `\n## ${new Date().toLocaleString('zh-CN',{hour12:false})} | ${changeName || '?'} | ${stageName}: ${steps[currentIdx].name}\n${inputText ? "- 输入:" + inputText + "\n" : ""}- 输出:${outputText}\n`
419
442
  appendFileSync(inputsPath, entry)
420
443
  }
421
444
 
422
- // 验证:检查生成的文件是否包含 author 和 created_at
423
445
  validateMetadata(cwd, stageName)
424
446
 
425
- // archive 阶段 Step 2(确认归档)完成时自动执行归档移动
447
+ // archive 阶段确认归档
426
448
  if (stageName === 'archive' && steps[currentIdx]?.name === '确认归档') {
427
449
  if (confirm) {
428
450
  const { renameSync } = await import('fs')
429
- const changeName = progress.currentChange
430
- if (!changeName) {
451
+ const archiveChangeName = progress.currentChange
452
+ if (!archiveChangeName) {
431
453
  console.error('❌ 归档失败:未找到当前变更名(currentChange)')
432
454
  process.exit(1)
433
455
  }
434
456
  const changesDir = join(cwd, '.sillyspec', 'changes')
435
457
  const archiveDir = join(changesDir, 'archive')
436
- const srcDir = join(changesDir, changeName)
458
+ const srcDir = join(changesDir, archiveChangeName)
437
459
  const date = new Date().toISOString().slice(0, 10)
438
- const destDir = join(archiveDir, `${date}-${changeName}`)
460
+ const destDir = join(archiveDir, `${date}-${archiveChangeName}`)
439
461
 
440
462
  if (!existsSync(srcDir)) {
441
463
  console.error(`❌ 归档失败:源目录不存在 ${srcDir}`)
@@ -448,21 +470,20 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
448
470
  mkdirSync(archiveDir, { recursive: true })
449
471
  renameSync(srcDir, destDir)
450
472
 
451
- // 校验
452
473
  if (!existsSync(destDir) || existsSync(srcDir)) {
453
474
  console.error('❌ 归档校验失败:移动操作异常')
454
475
  process.exit(1)
455
476
  }
456
477
 
457
- // 清除 currentChange
458
- progress.currentChange = null
459
- console.log(`📦 已归档:${changeName} → archive/${date}-${changeName}/`)
478
+ // 从全局活跃列表移除
479
+ pm.unregisterChange(cwd, archiveChangeName)
480
+ console.log(`📦 已归档:${archiveChangeName} → archive/${date}-${archiveChangeName}/`)
460
481
  } else {
461
482
  console.log('⚠️ 请添加 --confirm 确认归档,例如:sillyspec run archive --done --confirm --output "确认归档"')
462
483
  }
463
484
  }
464
485
 
465
- // 辅助阶段(archive/scan/quick 等)完成后重置步骤,允许重复执行
486
+ // 辅助阶段完成后重置步骤
466
487
  const stageDef = stageRegistry[stageName]
467
488
  if (stageDef?.auxiliary) {
468
489
  const freshSteps = (stageDef.steps || []).map(s => ({
@@ -474,13 +495,12 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
474
495
  stageData.steps = freshSteps
475
496
  stageData.status = 'pending'
476
497
  stageData.completedAt = null
477
- pm._write(cwd, progress)
498
+ pm._write(cwd, progress, changeName)
478
499
  }
479
500
 
480
501
  const total = steps.length
481
502
  console.log(`✅ ${stageName} 阶段已完成(${total}/${total} 步)`)
482
503
 
483
- // 阶段完成后提示下一步
484
504
  if (stageName === 'execute') {
485
505
  console.log('\n👉 下一步:sillyspec run verify(验证通过后才能归档)')
486
506
  } else if (stageName === 'verify') {
@@ -494,24 +514,24 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
494
514
  }
495
515
 
496
516
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
497
- pm._write(cwd, progress)
517
+ pm._write(cwd, progress, changeName)
498
518
 
499
519
  // Append to user-inputs.md
500
520
  if (outputText) {
501
521
  const inputsPath = join(cwd, '.sillyspec', '.runtime', 'user-inputs.md')
502
- const entry = `\n## ${new Date().toLocaleString('zh-CN',{hour12:false})} | ${stageName}: ${steps[currentIdx].name}\n${inputText ? "- 输入:" + inputText + "\n" : ""}- 输出:${outputText}\n`
522
+ const entry = `\n## ${new Date().toLocaleString('zh-CN',{hour12:false})} | ${changeName || '?'} | ${stageName}: ${steps[currentIdx].name}\n${inputText ? "- 输入:" + inputText + "\n" : ""}- 输出:${outputText}\n`
503
523
  appendFileSync(inputsPath, entry)
504
524
  }
505
525
 
506
526
  const defSteps = await getStageSteps(stageName, cwd, progress)
507
527
  console.log(`✅ Step ${currentIdx + 1}/${steps.length} 完成:${steps[currentIdx].name}\n`)
508
528
  if (printNext) {
509
- outputStep(stageName, nextPendingIdx, defSteps, cwd)
529
+ outputStep(stageName, nextPendingIdx, defSteps, cwd, changeName)
510
530
  }
511
531
  return { stageCompleted: false, currentIdx, nextPendingIdx }
512
532
  }
513
533
 
514
- async function skipStep(pm, progress, stageName, cwd) {
534
+ async function skipStep(pm, progress, stageName, cwd, changeName) {
515
535
  const stageData = progress.stages[stageName]
516
536
  if (!stageData || !stageData.steps) {
517
537
  console.error(`❌ 阶段 ${stageName} 未初始化`)
@@ -536,15 +556,14 @@ async function skipStep(pm, progress, stageName, cwd) {
536
556
  steps[currentIdx].status = 'skipped'
537
557
  steps[currentIdx].skippedAt = new Date().toLocaleString('zh-CN',{hour12:false})
538
558
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
539
- pm._write(cwd, progress)
559
+ pm._write(cwd, progress, changeName)
540
560
 
541
561
  console.log(`⏭️ Step ${currentIdx + 1}/${steps.length} 已跳过:${steps[currentIdx].name}`)
542
562
 
543
- // 输出下一步
544
563
  const nextPendingIdx = steps.findIndex(s => s.status === 'pending')
545
564
  if (nextPendingIdx !== -1 && defSteps) {
546
565
  console.log('')
547
- outputStep(stageName, nextPendingIdx, defSteps, cwd)
566
+ outputStep(stageName, nextPendingIdx, defSteps, cwd, changeName)
548
567
  }
549
568
  }
550
569
 
@@ -567,7 +586,6 @@ function showStatus(progress, stageName) {
567
586
 
568
587
  const firstPending = steps.findIndex(s => s.status === 'pending')
569
588
 
570
- // 批量进度
571
589
  if (progress.batchProgress) {
572
590
  const bp = progress.batchProgress
573
591
  const bpTotal = bp.total || 0
@@ -591,7 +609,7 @@ function showStatus(progress, stageName) {
591
609
  })
592
610
  }
593
611
 
594
- async function resetStage(pm, progress, stageName, cwd) {
612
+ async function resetStage(pm, progress, stageName, cwd, changeName) {
595
613
  const defSteps = await getStageSteps(stageName, cwd, progress)
596
614
  progress.stages[stageName] = {
597
615
  status: 'in-progress',
@@ -600,14 +618,14 @@ async function resetStage(pm, progress, stageName, cwd) {
600
618
  steps: defSteps ? defSteps.map(s => ({ name: s.name, status: 'pending' })) : []
601
619
  }
602
620
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
603
- pm._write(cwd, progress)
621
+ pm._write(cwd, progress, changeName)
604
622
  console.log(`🔄 ${stageName} 阶段已重置`)
605
623
  }
606
624
 
607
625
  /**
608
626
  * auto 模式:自动推进 brainstorm → plan → execute → verify
609
627
  */
610
- async function runAutoMode(pm, progress, cwd, flags) {
628
+ async function runAutoMode(pm, progress, cwd, flags, changeName) {
611
629
  const flowStages = ['brainstorm', 'plan', 'execute', 'verify']
612
630
  const isDone = flags.includes('--done')
613
631
  const outputIdx = flags.indexOf('--output')
@@ -623,8 +641,8 @@ async function runAutoMode(pm, progress, cwd, flags) {
623
641
  const stageChanged = progress.currentStage !== stage
624
642
  progress.currentStage = stage
625
643
  const changed = await ensureStageSteps(progress, stage, cwd)
626
- if (stageChanged || changed) pm._write(cwd, progress)
627
- progress = pm.read(cwd)
644
+ if (stageChanged || changed) pm._write(cwd, progress, changeName)
645
+ progress = pm.read(cwd, changeName)
628
646
  return progress
629
647
  }
630
648
 
@@ -637,7 +655,6 @@ async function runAutoMode(pm, progress, cwd, flags) {
637
655
  return
638
656
  }
639
657
  if (!flowStages.includes(currentStage)) {
640
- // 当前在辅助阶段(scan/quick/archive 等),自动跳到第一个未完成的流程阶段
641
658
  const openStage = firstOpenStage()
642
659
  if (!openStage) {
643
660
  console.log('All auto flow stages are complete.')
@@ -651,6 +668,7 @@ async function runAutoMode(pm, progress, cwd, flags) {
651
668
  if (!isDone) {
652
669
  console.log('════════════════════════════════════════')
653
670
  console.log(' SillySpec Auto Mode')
671
+ if (changeName) console.log(` Change: ${changeName}`)
654
672
  console.log('════════════════════════════════════════')
655
673
  console.log(` Flow: ${flowStages.join(' -> ')}`)
656
674
  console.log(` Current: ${currentStage}`)
@@ -671,7 +689,7 @@ async function runAutoMode(pm, progress, cwd, flags) {
671
689
  else console.log('All auto flow stages are complete.')
672
690
  return
673
691
  }
674
- outputStep(currentStage, pendingIdx, defSteps, cwd)
692
+ outputStep(currentStage, pendingIdx, defSteps, cwd, changeName)
675
693
  return
676
694
  }
677
695
 
@@ -680,14 +698,14 @@ async function runAutoMode(pm, progress, cwd, flags) {
680
698
  process.exit(1)
681
699
  }
682
700
 
683
- const result = await completeStep(pm, progress, currentStage, cwd, outputText, inputText, { printNext: false })
701
+ const result = await completeStep(pm, progress, currentStage, cwd, outputText, inputText, { printNext: false, changeName })
684
702
  if (!result) return
685
- progress = pm.read(cwd)
703
+ progress = pm.read(cwd, changeName)
686
704
 
687
705
  const nextPendingIdx = progress.stages[currentStage]?.steps?.findIndex(step => step.status === 'pending') ?? -1
688
706
  if (nextPendingIdx !== -1) {
689
707
  const defSteps = await getStageSteps(currentStage, cwd, progress)
690
- outputStep(currentStage, nextPendingIdx, defSteps, cwd)
708
+ outputStep(currentStage, nextPendingIdx, defSteps, cwd, changeName)
691
709
  return
692
710
  }
693
711
 
@@ -707,11 +725,11 @@ async function runAutoMode(pm, progress, cwd, flags) {
707
725
  }
708
726
  progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
709
727
  await ensureStageSteps(progress, next, cwd)
710
- pm._write(cwd, progress)
711
- progress = pm.read(cwd)
728
+ pm._write(cwd, progress, changeName)
729
+ progress = pm.read(cwd, changeName)
712
730
 
713
731
  console.log(`\n${currentStage} complete. Auto advanced to ${next}.`)
714
732
  const nextSteps = await getStageSteps(next, cwd, progress)
715
733
  const firstPending = progress.stages[next]?.steps?.findIndex(step => step.status === 'pending') ?? -1
716
- if (firstPending !== -1) outputStep(next, firstPending, nextSteps, cwd)
734
+ if (firstPending !== -1) outputStep(next, firstPending, nextSteps, cwd, changeName)
717
735
  }
@@ -5,7 +5,7 @@ export const definition = {
5
5
  steps: [
6
6
  {
7
7
  name: '状态检查',
8
- prompt: `检查 .sillyspec/.runtime/progress.json 确认当前状态。
8
+ prompt: `检查当前变更的 progress.json 确认当前状态。
9
9
 
10
10
  ### 操作
11
11
  1. 运行 \`sillyspec progress show\`
@@ -18,9 +18,15 @@ export const definition = {
18
18
  for d in .sillyspec .sillyspec/projects .sillyspec/docs .sillyspec/changes .sillyspec/.runtime; do
19
19
  [ -d "$d" ] && echo "✅ $d" || echo "❌ $d"
20
20
  done
21
- # 检查 progress.json
22
- [ -f .sillyspec/.runtime/progress.json ] && echo "✅ progress.json 存在" || echo "❌ progress.json 不存在"
23
- node -e "JSON.parse(require('fs').readFileSync('.sillyspec/.runtime/progress.json','utf8')); console.log('✅ progress.json 可解析')" 2>/dev/null || echo "⚠️ progress.json 不可解析"
21
+ # 检查 progress.json(支持新版按变更隔离和旧版全局)
22
+ if [ -d .sillyspec/changes ]; then
23
+ PROGRESS_FILE=$(find .sillyspec/changes -maxdepth 2 -name progress.json | head -1)
24
+ fi
25
+ if [ -z "$PROGRESS_FILE" ]; then
26
+ PROGRESS_FILE='.sillyspec/.runtime/progress.json'
27
+ fi
28
+ [ -f "$PROGRESS_FILE" ] && echo "✅ progress.json 存在 ($PROGRESS_FILE)" || echo "❌ progress.json 不存在"
29
+ node -e "JSON.parse(require('fs').readFileSync('$PROGRESS_FILE','utf8')); console.log('✅ progress.json 可解析')" 2>/dev/null || echo "⚠️ progress.json 不可解析"
24
30
  \`\`\`
25
31
 
26
32
  ### 2. 项目配置检查
@@ -42,7 +48,7 @@ done
42
48
  \`\`\`bash
43
49
  # 读取 currentChange 并检查目录存在性
44
50
  node -e "
45
- const p = JSON.parse(require('fs').readFileSync('.sillyspec/.runtime/progress.json','utf8'));
51
+ const p = JSON.parse(require('fs').readFileSync(process.env.PROGRESS_FILE || '.sillyspec/.runtime/progress.json','utf8'));
46
52
  const cc = p.currentChange;
47
53
  if (!cc) { console.log('ℹ️ 无当前变更'); process.exit(0); }
48
54
  const dir = '.sillyspec/changes/' + cc;
@@ -68,7 +74,7 @@ if (!fs.existsSync(dir)) { console.log('ℹ️ changes/ 目录不存在'); proce
68
74
  const subs = fs.readdirSync(dir).filter(f => fs.statSync(dir+'/'+f).isDirectory());
69
75
  if (subs.length === 0) { console.log('ℹ️ 无变更目录'); process.exit(0); }
70
76
  let progress;
71
- try { progress = JSON.parse(fs.readFileSync('.sillyspec/.runtime/progress.json','utf8')); } catch { console.log('⚠️ 无法读取 progress.json'); subs.forEach(s => console.log('❓ ' + s)); process.exit(0); }
77
+ try { progress = JSON.parse(fs.readFileSync(process.env.PROGRESS_FILE || '.sillyspec/.runtime/progress.json','utf8')); } catch { console.log('⚠️ 无法读取 progress.json'); subs.forEach(s => console.log('❓ ' + s)); process.exit(0); }
72
78
  const known = new Set();
73
79
  if (progress.currentChange) known.add(progress.currentChange);
74
80
  for (const sd of Object.values(progress.stages || {})) {
@@ -121,7 +127,7 @@ done
121
127
  # 确定项目路径(使用 progress.json 中的项目或当前目录)
122
128
  PROJECT_DIR=$(node -e "
123
129
  const fs=require('fs');
124
- try{const p=JSON.parse(fs.readFileSync('.sillyspec/.runtime/progress.json','utf8'));if(p.project){console.log(p.project);process.exit(0)}}catch{}
130
+ try{const fs=require('fs'),path=require('path');const changesDir='.sillyspec/changes';let pp=null;if(fs.existsSync(changesDir)){const entries=fs.readdirSync(changesDir,{withFileTypes:true}).filter(e=>e.isDirectory()&&e.name!=='archive');if(entries.length===1)pp=path.join(changesDir,entries[0].name,'progress.json');}if(!pp)pp='.sillyspec/.runtime/progress.json';const p=JSON.parse(fs.readFileSync(pp,'utf8'));if(p.project){console.log(p.project);process.exit(0)}}catch{}
125
131
  const files=fs.readdirSync('.sillyspec/projects').filter(f=>f.endsWith('.yaml'));
126
132
  if(files.length>0){const c=fs.readFileSync('.sillyspec/projects/'+files[0],'utf8');const m=c.match(/^path:\\s*(.+)/m);console.log(m?m[1].trim():'.')}else console.log('.')
127
133
  " 2>/dev/null)
@@ -39,7 +39,7 @@ export const definition = {
39
39
  prompt: `检测已有扫描文档,只生成缺失的。
40
40
 
41
41
  ### 操作
42
- 1. \`PROJECT=$(python3 -c "import sys,json; print(json.load(open('.sillyspec/.runtime/progress.json')).get('project',''))" 2>/dev/null || basename "$(pwd)")\`
42
+ 1. \`PROJECT=$(python3 -c "import sys,json,glob; files=glob.glob('.sillyspec/changes/*/progress.json'); print(json.load(open(files[0])).get('project','')) if files else print('')" 2>/dev/null || basename "$(pwd)")\`
43
43
  2. 检查 7 份文档是否存在:ARCHITECTURE、STRUCTURE、CONVENTIONS、INTEGRATIONS、TESTING、CONCERNS、PROJECT
44
44
  3. 列出已有 ✅ 和缺失 ⬜
45
45
  4. 只生成缺失的文档