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/.claude/skills/sillyspec-archive/SKILL.md +4 -0
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +4 -0
- package/.claude/skills/sillyspec-doctor/SKILL.md +4 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +4 -0
- package/.claude/skills/sillyspec-explore/SKILL.md +4 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +4 -0
- package/.claude/skills/sillyspec-propose/SKILL.md +4 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +4 -0
- package/.claude/skills/sillyspec-scan/SKILL.md +4 -0
- package/.claude/skills/sillyspec-status/SKILL.md +4 -0
- package/.claude/skills/sillyspec-verify/SKILL.md +4 -0
- 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 +92 -74
- package/src/stages/brainstorm.js +1 -1
- package/src/stages/doctor.js +12 -6
- 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,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
|
|
447
|
+
// archive 阶段确认归档
|
|
426
448
|
if (stageName === 'archive' && steps[currentIdx]?.name === '确认归档') {
|
|
427
449
|
if (confirm) {
|
|
428
450
|
const { renameSync } = await import('fs')
|
|
429
|
-
const
|
|
430
|
-
if (!
|
|
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,
|
|
458
|
+
const srcDir = join(changesDir, archiveChangeName)
|
|
437
459
|
const date = new Date().toISOString().slice(0, 10)
|
|
438
|
-
const destDir = join(archiveDir, `${date}-${
|
|
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
|
-
//
|
|
458
|
-
|
|
459
|
-
console.log(`📦 已归档:${
|
|
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
|
-
//
|
|
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
|
}
|
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/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. 只生成缺失的文档
|