sillyspec 3.10.4 → 3.10.6
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/package.json +1 -1
- package/src/index.js +16 -13
- package/src/init.js +5 -8
- package/src/progress.js +337 -90
- package/src/run.js +99 -75
- package/src/stages/brainstorm.js +1 -1
- package/src/stages/doctor.js +12 -6
- package/src/stages/plan.js +6 -4
- package/src/stages/scan.js +1 -1
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
|
|
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
|
-
|
|
218
|
+
pm._migrateIfNeeded(cwd)
|
|
219
|
+
let progress = pm.read(cwd, changeName)
|
|
204
220
|
|
|
205
221
|
if (!progress) {
|
|
206
|
-
//
|
|
207
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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,12 +410,16 @@ 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
|
-
// 在当前步骤之后插入协调器步骤
|
|
391
415
|
for (let i = 0; i < coordinatorSteps.length; i++) {
|
|
392
|
-
steps.splice(currentIdx + 1 + i, 0, {
|
|
416
|
+
steps.splice(currentIdx + 1 + i, 0, {
|
|
417
|
+
name: coordinatorSteps[i].name,
|
|
418
|
+
status: 'pending',
|
|
419
|
+
prompt: coordinatorSteps[i].prompt,
|
|
420
|
+
outputHint: coordinatorSteps[i].outputHint,
|
|
421
|
+
optional: coordinatorSteps[i].optional
|
|
422
|
+
})
|
|
393
423
|
}
|
|
394
424
|
console.log(` 📝 已动态插入 ${coordinatorSteps.length} 个任务蓝图步骤(${coordinatorSteps.map(s => s.name).join(', ')})`)
|
|
395
425
|
}
|
|
@@ -400,36 +430,34 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
400
430
|
const nextPendingIdx = steps.findIndex(s => s.status === 'pending')
|
|
401
431
|
|
|
402
432
|
if (nextPendingIdx === -1) {
|
|
403
|
-
// 全部完成 — 仅标记当前阶段,不自动推进 currentStage
|
|
404
433
|
stageData.status = 'completed'
|
|
405
434
|
stageData.completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
406
435
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
407
|
-
pm._write(cwd, progress)
|
|
436
|
+
pm._write(cwd, progress, changeName)
|
|
408
437
|
|
|
409
438
|
// Append to user-inputs.md
|
|
410
439
|
if (outputText) {
|
|
411
440
|
const inputsPath = join(cwd, '.sillyspec', '.runtime', 'user-inputs.md')
|
|
412
|
-
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`
|
|
413
442
|
appendFileSync(inputsPath, entry)
|
|
414
443
|
}
|
|
415
444
|
|
|
416
|
-
// 验证:检查生成的文件是否包含 author 和 created_at
|
|
417
445
|
validateMetadata(cwd, stageName)
|
|
418
446
|
|
|
419
|
-
// archive
|
|
447
|
+
// archive 阶段确认归档
|
|
420
448
|
if (stageName === 'archive' && steps[currentIdx]?.name === '确认归档') {
|
|
421
449
|
if (confirm) {
|
|
422
450
|
const { renameSync } = await import('fs')
|
|
423
|
-
const
|
|
424
|
-
if (!
|
|
451
|
+
const archiveChangeName = progress.currentChange
|
|
452
|
+
if (!archiveChangeName) {
|
|
425
453
|
console.error('❌ 归档失败:未找到当前变更名(currentChange)')
|
|
426
454
|
process.exit(1)
|
|
427
455
|
}
|
|
428
456
|
const changesDir = join(cwd, '.sillyspec', 'changes')
|
|
429
457
|
const archiveDir = join(changesDir, 'archive')
|
|
430
|
-
const srcDir = join(changesDir,
|
|
458
|
+
const srcDir = join(changesDir, archiveChangeName)
|
|
431
459
|
const date = new Date().toISOString().slice(0, 10)
|
|
432
|
-
const destDir = join(archiveDir, `${date}-${
|
|
460
|
+
const destDir = join(archiveDir, `${date}-${archiveChangeName}`)
|
|
433
461
|
|
|
434
462
|
if (!existsSync(srcDir)) {
|
|
435
463
|
console.error(`❌ 归档失败:源目录不存在 ${srcDir}`)
|
|
@@ -442,21 +470,20 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
442
470
|
mkdirSync(archiveDir, { recursive: true })
|
|
443
471
|
renameSync(srcDir, destDir)
|
|
444
472
|
|
|
445
|
-
// 校验
|
|
446
473
|
if (!existsSync(destDir) || existsSync(srcDir)) {
|
|
447
474
|
console.error('❌ 归档校验失败:移动操作异常')
|
|
448
475
|
process.exit(1)
|
|
449
476
|
}
|
|
450
477
|
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
console.log(`📦 已归档:${
|
|
478
|
+
// 从全局活跃列表移除
|
|
479
|
+
pm.unregisterChange(cwd, archiveChangeName)
|
|
480
|
+
console.log(`📦 已归档:${archiveChangeName} → archive/${date}-${archiveChangeName}/`)
|
|
454
481
|
} else {
|
|
455
482
|
console.log('⚠️ 请添加 --confirm 确认归档,例如:sillyspec run archive --done --confirm --output "确认归档"')
|
|
456
483
|
}
|
|
457
484
|
}
|
|
458
485
|
|
|
459
|
-
//
|
|
486
|
+
// 辅助阶段完成后重置步骤
|
|
460
487
|
const stageDef = stageRegistry[stageName]
|
|
461
488
|
if (stageDef?.auxiliary) {
|
|
462
489
|
const freshSteps = (stageDef.steps || []).map(s => ({
|
|
@@ -468,13 +495,12 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
468
495
|
stageData.steps = freshSteps
|
|
469
496
|
stageData.status = 'pending'
|
|
470
497
|
stageData.completedAt = null
|
|
471
|
-
pm._write(cwd, progress)
|
|
498
|
+
pm._write(cwd, progress, changeName)
|
|
472
499
|
}
|
|
473
500
|
|
|
474
501
|
const total = steps.length
|
|
475
502
|
console.log(`✅ ${stageName} 阶段已完成(${total}/${total} 步)`)
|
|
476
503
|
|
|
477
|
-
// 阶段完成后提示下一步
|
|
478
504
|
if (stageName === 'execute') {
|
|
479
505
|
console.log('\n👉 下一步:sillyspec run verify(验证通过后才能归档)')
|
|
480
506
|
} else if (stageName === 'verify') {
|
|
@@ -488,24 +514,24 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
488
514
|
}
|
|
489
515
|
|
|
490
516
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
491
|
-
pm._write(cwd, progress)
|
|
517
|
+
pm._write(cwd, progress, changeName)
|
|
492
518
|
|
|
493
519
|
// Append to user-inputs.md
|
|
494
520
|
if (outputText) {
|
|
495
521
|
const inputsPath = join(cwd, '.sillyspec', '.runtime', 'user-inputs.md')
|
|
496
|
-
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`
|
|
497
523
|
appendFileSync(inputsPath, entry)
|
|
498
524
|
}
|
|
499
525
|
|
|
500
526
|
const defSteps = await getStageSteps(stageName, cwd, progress)
|
|
501
527
|
console.log(`✅ Step ${currentIdx + 1}/${steps.length} 完成:${steps[currentIdx].name}\n`)
|
|
502
528
|
if (printNext) {
|
|
503
|
-
outputStep(stageName, nextPendingIdx, defSteps, cwd)
|
|
529
|
+
outputStep(stageName, nextPendingIdx, defSteps, cwd, changeName)
|
|
504
530
|
}
|
|
505
531
|
return { stageCompleted: false, currentIdx, nextPendingIdx }
|
|
506
532
|
}
|
|
507
533
|
|
|
508
|
-
async function skipStep(pm, progress, stageName, cwd) {
|
|
534
|
+
async function skipStep(pm, progress, stageName, cwd, changeName) {
|
|
509
535
|
const stageData = progress.stages[stageName]
|
|
510
536
|
if (!stageData || !stageData.steps) {
|
|
511
537
|
console.error(`❌ 阶段 ${stageName} 未初始化`)
|
|
@@ -530,15 +556,14 @@ async function skipStep(pm, progress, stageName, cwd) {
|
|
|
530
556
|
steps[currentIdx].status = 'skipped'
|
|
531
557
|
steps[currentIdx].skippedAt = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
532
558
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
533
|
-
pm._write(cwd, progress)
|
|
559
|
+
pm._write(cwd, progress, changeName)
|
|
534
560
|
|
|
535
561
|
console.log(`⏭️ Step ${currentIdx + 1}/${steps.length} 已跳过:${steps[currentIdx].name}`)
|
|
536
562
|
|
|
537
|
-
// 输出下一步
|
|
538
563
|
const nextPendingIdx = steps.findIndex(s => s.status === 'pending')
|
|
539
564
|
if (nextPendingIdx !== -1 && defSteps) {
|
|
540
565
|
console.log('')
|
|
541
|
-
outputStep(stageName, nextPendingIdx, defSteps, cwd)
|
|
566
|
+
outputStep(stageName, nextPendingIdx, defSteps, cwd, changeName)
|
|
542
567
|
}
|
|
543
568
|
}
|
|
544
569
|
|
|
@@ -561,7 +586,6 @@ function showStatus(progress, stageName) {
|
|
|
561
586
|
|
|
562
587
|
const firstPending = steps.findIndex(s => s.status === 'pending')
|
|
563
588
|
|
|
564
|
-
// 批量进度
|
|
565
589
|
if (progress.batchProgress) {
|
|
566
590
|
const bp = progress.batchProgress
|
|
567
591
|
const bpTotal = bp.total || 0
|
|
@@ -585,7 +609,7 @@ function showStatus(progress, stageName) {
|
|
|
585
609
|
})
|
|
586
610
|
}
|
|
587
611
|
|
|
588
|
-
async function resetStage(pm, progress, stageName, cwd) {
|
|
612
|
+
async function resetStage(pm, progress, stageName, cwd, changeName) {
|
|
589
613
|
const defSteps = await getStageSteps(stageName, cwd, progress)
|
|
590
614
|
progress.stages[stageName] = {
|
|
591
615
|
status: 'in-progress',
|
|
@@ -594,14 +618,14 @@ async function resetStage(pm, progress, stageName, cwd) {
|
|
|
594
618
|
steps: defSteps ? defSteps.map(s => ({ name: s.name, status: 'pending' })) : []
|
|
595
619
|
}
|
|
596
620
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
597
|
-
pm._write(cwd, progress)
|
|
621
|
+
pm._write(cwd, progress, changeName)
|
|
598
622
|
console.log(`🔄 ${stageName} 阶段已重置`)
|
|
599
623
|
}
|
|
600
624
|
|
|
601
625
|
/**
|
|
602
626
|
* auto 模式:自动推进 brainstorm → plan → execute → verify
|
|
603
627
|
*/
|
|
604
|
-
async function runAutoMode(pm, progress, cwd, flags) {
|
|
628
|
+
async function runAutoMode(pm, progress, cwd, flags, changeName) {
|
|
605
629
|
const flowStages = ['brainstorm', 'plan', 'execute', 'verify']
|
|
606
630
|
const isDone = flags.includes('--done')
|
|
607
631
|
const outputIdx = flags.indexOf('--output')
|
|
@@ -617,8 +641,8 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
617
641
|
const stageChanged = progress.currentStage !== stage
|
|
618
642
|
progress.currentStage = stage
|
|
619
643
|
const changed = await ensureStageSteps(progress, stage, cwd)
|
|
620
|
-
if (stageChanged || changed) pm._write(cwd, progress)
|
|
621
|
-
progress = pm.read(cwd)
|
|
644
|
+
if (stageChanged || changed) pm._write(cwd, progress, changeName)
|
|
645
|
+
progress = pm.read(cwd, changeName)
|
|
622
646
|
return progress
|
|
623
647
|
}
|
|
624
648
|
|
|
@@ -631,7 +655,6 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
631
655
|
return
|
|
632
656
|
}
|
|
633
657
|
if (!flowStages.includes(currentStage)) {
|
|
634
|
-
// 当前在辅助阶段(scan/quick/archive 等),自动跳到第一个未完成的流程阶段
|
|
635
658
|
const openStage = firstOpenStage()
|
|
636
659
|
if (!openStage) {
|
|
637
660
|
console.log('All auto flow stages are complete.')
|
|
@@ -645,6 +668,7 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
645
668
|
if (!isDone) {
|
|
646
669
|
console.log('════════════════════════════════════════')
|
|
647
670
|
console.log(' SillySpec Auto Mode')
|
|
671
|
+
if (changeName) console.log(` Change: ${changeName}`)
|
|
648
672
|
console.log('════════════════════════════════════════')
|
|
649
673
|
console.log(` Flow: ${flowStages.join(' -> ')}`)
|
|
650
674
|
console.log(` Current: ${currentStage}`)
|
|
@@ -665,7 +689,7 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
665
689
|
else console.log('All auto flow stages are complete.')
|
|
666
690
|
return
|
|
667
691
|
}
|
|
668
|
-
outputStep(currentStage, pendingIdx, defSteps, cwd)
|
|
692
|
+
outputStep(currentStage, pendingIdx, defSteps, cwd, changeName)
|
|
669
693
|
return
|
|
670
694
|
}
|
|
671
695
|
|
|
@@ -674,14 +698,14 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
674
698
|
process.exit(1)
|
|
675
699
|
}
|
|
676
700
|
|
|
677
|
-
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 })
|
|
678
702
|
if (!result) return
|
|
679
|
-
progress = pm.read(cwd)
|
|
703
|
+
progress = pm.read(cwd, changeName)
|
|
680
704
|
|
|
681
705
|
const nextPendingIdx = progress.stages[currentStage]?.steps?.findIndex(step => step.status === 'pending') ?? -1
|
|
682
706
|
if (nextPendingIdx !== -1) {
|
|
683
707
|
const defSteps = await getStageSteps(currentStage, cwd, progress)
|
|
684
|
-
outputStep(currentStage, nextPendingIdx, defSteps, cwd)
|
|
708
|
+
outputStep(currentStage, nextPendingIdx, defSteps, cwd, changeName)
|
|
685
709
|
return
|
|
686
710
|
}
|
|
687
711
|
|
|
@@ -701,11 +725,11 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
701
725
|
}
|
|
702
726
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
703
727
|
await ensureStageSteps(progress, next, cwd)
|
|
704
|
-
pm._write(cwd, progress)
|
|
705
|
-
progress = pm.read(cwd)
|
|
728
|
+
pm._write(cwd, progress, changeName)
|
|
729
|
+
progress = pm.read(cwd, changeName)
|
|
706
730
|
|
|
707
731
|
console.log(`\n${currentStage} complete. Auto advanced to ${next}.`)
|
|
708
732
|
const nextSteps = await getStageSteps(next, cwd, progress)
|
|
709
733
|
const firstPending = progress.stages[next]?.steps?.findIndex(step => step.status === 'pending') ?? -1
|
|
710
|
-
if (firstPending !== -1) outputStep(next, firstPending, nextSteps, cwd)
|
|
734
|
+
if (firstPending !== -1) outputStep(next, firstPending, nextSteps, cwd, changeName)
|
|
711
735
|
}
|
package/src/stages/brainstorm.js
CHANGED
package/src/stages/doctor.js
CHANGED
|
@@ -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
|
-
[ -
|
|
23
|
-
|
|
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
|
|
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)
|
package/src/stages/plan.js
CHANGED
|
@@ -367,9 +367,7 @@ allowed_paths:
|
|
|
367
367
|
}).join('\n\n')
|
|
368
368
|
|
|
369
369
|
|
|
370
|
-
|
|
371
|
-
name: '生成任务蓝图(子代理并行)',
|
|
372
|
-
prompt: `为 plan.md 中的每个任务生成独立蓝图文件。
|
|
370
|
+
const prompt = `为 plan.md 中的每个任务生成独立蓝图文件。
|
|
373
371
|
|
|
374
372
|
## 任务清单
|
|
375
373
|
${taskList}
|
|
@@ -393,7 +391,11 @@ ${subagentPrompts}
|
|
|
393
391
|
- 每个 task-N.md 文件存在且非空
|
|
394
392
|
- 包含 YAML frontmatter(id、title、priority、depends_on、blocks、allowed_paths)
|
|
395
393
|
- 包含所有必要章节:修改文件、实现要求、接口定义、边界处理(≥5条)、非目标、TDD 步骤、验收标准(表格格式)
|
|
396
|
-
- 边界处理覆盖:null
|
|
394
|
+
- 边界处理覆盖:null/空值、兼容性、异常处理、参数不可变、歧义场景`
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
name: '生成任务蓝图(子代理并行)',
|
|
398
|
+
prompt,
|
|
397
399
|
outputHint: '蓝图生成结果',
|
|
398
400
|
optional: false
|
|
399
401
|
}
|
package/src/stages/scan.js
CHANGED
|
@@ -39,7 +39,7 @@ export const definition = {
|
|
|
39
39
|
prompt: `检测已有扫描文档,只生成缺失的。
|
|
40
40
|
|
|
41
41
|
### 操作
|
|
42
|
-
1. \`PROJECT=$(python3 -c "import sys,json;
|
|
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. 只生成缺失的文档
|