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