sillyspec 3.9.0 → 3.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/sillyspec-archive/SKILL.md +17 -0
- package/.claude/skills/sillyspec-auto/SKILL.md +78 -0
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +17 -0
- package/{templates/commit.md → .claude/skills/sillyspec-commit/SKILL.md} +32 -47
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -0
- package/.claude/skills/sillyspec-doctor/SKILL.md +27 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +17 -0
- package/.claude/skills/sillyspec-explore/SKILL.md +96 -0
- package/.claude/skills/sillyspec-export/SKILL.md +53 -0
- package/.claude/skills/sillyspec-init/SKILL.md +170 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +52 -0
- package/.claude/skills/sillyspec-propose/SKILL.md +17 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +17 -0
- package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
- package/.claude/skills/sillyspec-scan/SKILL.md +17 -0
- package/.claude/skills/sillyspec-state/SKILL.md +54 -0
- package/.claude/skills/sillyspec-status/SKILL.md +17 -0
- package/.claude/skills/sillyspec-verify/SKILL.md +17 -0
- package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
- package/.sillyspec/changes/auto-mode/design.md +50 -0
- package/.sillyspec/changes/auto-mode/proposal.md +19 -0
- package/.sillyspec/changes/auto-mode/requirements.md +21 -0
- package/.sillyspec/changes/auto-mode/tasks.md +7 -0
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
- package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
- package/.sillyspec/changes/run-command-design/design.md +1230 -0
- package/.sillyspec/changes/unified-docs-design/design.md +199 -0
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +8 -0
- package/.sillyspec/knowledge/uncategorized.md +3 -0
- package/.sillyspec/projects/sillyspec.yaml +3 -0
- package/README.md +12 -5
- package/package.json +7 -9
- package/packages/dashboard/dist/assets/index-CntACGUN.css +1 -0
- package/packages/dashboard/dist/assets/index-RsLVPAy7.js +7446 -0
- package/packages/dashboard/dist/index.html +3 -2
- package/packages/dashboard/package-lock.json +226 -6
- package/packages/dashboard/package.json +8 -5
- package/packages/dashboard/public/logo.jpg +0 -0
- package/packages/dashboard/server/executor.js +1 -1
- package/packages/dashboard/server/index.js +336 -113
- package/packages/dashboard/server/parser.js +333 -29
- package/packages/dashboard/server/watcher.js +203 -131
- package/packages/dashboard/src/App.vue +187 -11
- package/packages/dashboard/src/components/ActionBar.vue +26 -42
- package/packages/dashboard/src/components/CommandPalette.vue +40 -65
- package/packages/dashboard/src/components/DetailPanel.vue +68 -53
- package/packages/dashboard/src/components/DocPreview.vue +160 -0
- package/packages/dashboard/src/components/DocTree.vue +58 -0
- package/packages/dashboard/src/components/LogStream.vue +13 -33
- package/packages/dashboard/src/components/PipelineStage.vue +8 -8
- package/packages/dashboard/src/components/PipelineView.vue +80 -45
- package/packages/dashboard/src/components/ProjectList.vue +103 -45
- package/packages/dashboard/src/components/ProjectOverview.vue +178 -0
- package/packages/dashboard/src/components/StageBadge.vue +13 -13
- package/packages/dashboard/src/components/StepCard.vue +15 -15
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
- package/packages/dashboard/src/composables/useDashboard.js +20 -6
- package/packages/dashboard/src/composables/useKeyboard.js +6 -4
- package/packages/dashboard/src/main.js +4 -1
- package/packages/dashboard/src/style.css +17 -17
- package/src/index.js +134 -22
- package/src/init.js +83 -228
- package/src/migrate.js +117 -0
- package/src/progress.js +459 -0
- package/src/run.js +624 -0
- package/src/setup.js +2 -72
- package/src/stages/archive.js +54 -0
- package/src/stages/brainstorm.js +239 -0
- package/src/stages/doctor.js +303 -0
- package/src/stages/execute.js +262 -0
- package/src/stages/index.js +26 -0
- package/src/stages/plan.js +282 -0
- package/src/stages/propose.js +115 -0
- package/src/stages/quick.js +64 -0
- package/src/stages/scan.js +141 -0
- package/src/stages/status.js +65 -0
- package/src/stages/verify.js +135 -0
- package/dist/steps/brainstorm/01-load-context.md +0 -30
- package/dist/steps/brainstorm/02-reuse-check.md +0 -6
- package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
- package/dist/steps/brainstorm/04-module-split.md +0 -23
- package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
- package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
- package/dist/steps/brainstorm/07-present-design.md +0 -3
- package/dist/steps/brainstorm/08-write-design.md +0 -21
- package/dist/steps/brainstorm/09-self-review.md +0 -15
- package/dist/steps/brainstorm/10-user-confirm.md +0 -3
- package/dist/steps/brainstorm/11-output-spec.md +0 -7
- package/dist/steps/brainstorm/manifest.yaml +0 -26
- package/dist/steps/execute/01-load-context.md +0 -41
- package/dist/steps/execute/02-scan-conventions.md +0 -47
- package/dist/steps/execute/03-skill-mcp.md +0 -19
- package/dist/steps/execute/04-assign-task.md +0 -22
- package/dist/steps/execute/04b-prompt-template.md +0 -54
- package/dist/steps/execute/05-write-test.md +0 -7
- package/dist/steps/execute/06-write-code.md +0 -8
- package/dist/steps/execute/07-run-test.md +0 -26
- package/dist/steps/execute/08-fix-issues.md +0 -28
- package/dist/steps/execute/09-next-task.md +0 -33
- package/dist/steps/execute/manifest.yaml +0 -28
- package/dist/steps/plan/01-load-context.md +0 -22
- package/dist/steps/plan/02-anchor-confirm.md +0 -1
- package/dist/steps/plan/03-expand-tasks.md +0 -33
- package/dist/steps/plan/04-mark-order.md +0 -15
- package/dist/steps/plan/05-e2e-planning.md +0 -17
- package/dist/steps/plan/06-self-check.md +0 -16
- package/dist/steps/plan/07-save.md +0 -1
- package/dist/steps/plan/manifest.yaml +0 -18
- package/dist/steps/scan/01-env-detect.md +0 -51
- package/dist/steps/scan/02-tech-stack.md +0 -16
- package/dist/steps/scan/03-conventions.md +0 -16
- package/dist/steps/scan/04-structure.md +0 -19
- package/dist/steps/scan/05-quality.md +0 -18
- package/dist/steps/scan/06-complete.md +0 -49
- package/dist/steps/scan/manifest.yaml +0 -16
- package/dist/steps/verify/01-load-specs.md +0 -28
- package/dist/steps/verify/02-check-tasks.md +0 -1
- package/dist/steps/verify/03-check-design.md +0 -6
- package/dist/steps/verify/04-run-tests.md +0 -7
- package/dist/steps/verify/05-e2e-tests.md +0 -27
- package/dist/steps/verify/05b-e2e-fix.md +0 -33
- package/dist/steps/verify/06-code-quality.md +0 -25
- package/dist/steps/verify/07-lint-check.md +0 -27
- package/dist/steps/verify/08-output-report.md +0 -14
- package/dist/steps/verify/manifest.yaml +0 -22
- package/docs/.vitepress/config.mts +0 -45
- package/docs/.vitepress/dist/404.html +0 -25
- package/docs/.vitepress/dist/assets/app.YytxICdd.js +0 -1
- package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +0 -19
- package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +0 -1
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +0 -1
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +0 -1
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.js +0 -15
- package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +0 -4
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +0 -4
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +0 -5
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +0 -28
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +0 -30
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +0 -1
- package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +0 -1
- package/docs/.vitepress/dist/hashmap.json +0 -1
- package/docs/.vitepress/dist/index.html +0 -28
- package/docs/.vitepress/dist/sillyspec/commands.html +0 -42
- package/docs/.vitepress/dist/sillyspec/dashboard.html +0 -31
- package/docs/.vitepress/dist/sillyspec/file-io.html +0 -28
- package/docs/.vitepress/dist/sillyspec/getting-started.html +0 -31
- package/docs/.vitepress/dist/sillyspec/install.html +0 -32
- package/docs/.vitepress/dist/sillyspec/lifecycle.html +0 -55
- package/docs/.vitepress/dist/sillyspec/structure.html +0 -57
- package/docs/.vitepress/dist/vp-icons.css +0 -1
- package/docs/index.md +0 -34
- package/docs/sillyspec/commands.md +0 -218
- package/docs/sillyspec/dashboard.md +0 -51
- package/docs/sillyspec/file-io.md +0 -34
- package/docs/sillyspec/getting-started.md +0 -61
- package/docs/sillyspec/install.md +0 -51
- package/docs/sillyspec/lifecycle.md +0 -146
- package/docs/sillyspec/structure.md +0 -62
- package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +0 -1
- package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +0 -17
- package/src/step.js +0 -543
- package/templates/archive.md +0 -120
- package/templates/brainstorm.md +0 -170
- package/templates/continue.md +0 -32
- package/templates/execute.md +0 -304
- package/templates/explore.md +0 -59
- package/templates/export.md +0 -21
- package/templates/init.md +0 -61
- package/templates/plan.md +0 -146
- package/templates/quick.md +0 -135
- package/templates/scan-quick.md +0 -49
- package/templates/scan.md +0 -156
- package/templates/skills/playwright-e2e/SKILL.md +0 -340
- package/templates/status.md +0 -75
- package/templates/verify.md +0 -236
- package/templates/workspace-sync.md +0 -99
- package/templates/workspace.md +0 -70
- /package/.sillyspec/{specs → changes/brainstorm-archive}/2026-04-05-dashboard-design.md +0 -0
- /package/{docs/.vitepress/dist/logo.jpg → logo.jpg} +0 -0
- /package/{docs/.vitepress → packages/dashboard}/dist/favicon.jpg +0 -0
- /package/{docs/public → packages/dashboard/dist}/logo.jpg +0 -0
- /package/{docs → packages/dashboard}/public/favicon.jpg +0 -0
package/src/run.js
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sillyspec run 命令实现
|
|
3
|
+
*
|
|
4
|
+
* CLI 成为流程引擎,AI 变成步骤执行器。
|
|
5
|
+
*/
|
|
6
|
+
import { basename, join } from 'path'
|
|
7
|
+
import { existsSync, readdirSync, mkdirSync, writeFileSync, appendFileSync, readFileSync, statSync } from 'fs'
|
|
8
|
+
import { ProgressManager } from './progress.js'
|
|
9
|
+
import { stageRegistry, auxiliaryStages } from './stages/index.js'
|
|
10
|
+
import { buildExecuteSteps } from './stages/execute.js'
|
|
11
|
+
import { buildPlanSteps } from './stages/plan.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 获取阶段的步骤定义(execute 需要动态构建)
|
|
15
|
+
*/
|
|
16
|
+
async function getStageSteps(stageName, cwd, progress) {
|
|
17
|
+
if (stageName === 'execute') {
|
|
18
|
+
const changesDir = join(cwd, '.sillyspec', 'changes')
|
|
19
|
+
let planFile = null
|
|
20
|
+
// 优先用 currentChange 指定的变更名
|
|
21
|
+
if (progress.currentChange) {
|
|
22
|
+
const target = join(changesDir, progress.currentChange, 'plan.md')
|
|
23
|
+
if (existsSync(target)) planFile = target
|
|
24
|
+
}
|
|
25
|
+
// fallback:扫描 changes/ 非 archive 目录下的 plan.md
|
|
26
|
+
if (!planFile && existsSync(changesDir)) {
|
|
27
|
+
const candidates = []
|
|
28
|
+
for (const entry of readdirSync(changesDir, { withFileTypes: true })) {
|
|
29
|
+
if (!entry.isDirectory() || entry.name === 'archive') continue
|
|
30
|
+
const p = join(changesDir, entry.name, 'plan.md')
|
|
31
|
+
if (existsSync(p)) candidates.push({ name: entry.name, path: p })
|
|
32
|
+
}
|
|
33
|
+
if (candidates.length === 1) {
|
|
34
|
+
planFile = candidates[0].path
|
|
35
|
+
} else if (candidates.length > 1) {
|
|
36
|
+
console.log('⚠️ 检测到多个变更,请选择:')
|
|
37
|
+
candidates.forEach((c, i) => console.log(` ${i + 1}. ${c.name}`))
|
|
38
|
+
const readline = await import('readline')
|
|
39
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
40
|
+
const answer = await new Promise(resolve => {
|
|
41
|
+
rl.question(`\n请输入编号(默认 1):`, input => {
|
|
42
|
+
rl.close()
|
|
43
|
+
const num = parseInt(input) || 1
|
|
44
|
+
resolve(num >= 1 && num <= candidates.length ? num - 1 : 0)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
planFile = candidates[answer].path
|
|
48
|
+
console.log(`✅ 已选择:${candidates[answer].name}\n`)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return buildExecuteSteps(planFile)
|
|
52
|
+
}
|
|
53
|
+
if (stageName === 'plan') {
|
|
54
|
+
const changesDir = join(cwd, '.sillyspec', 'changes')
|
|
55
|
+
let changeDir = null
|
|
56
|
+
if (progress.currentChange) {
|
|
57
|
+
const target = join(changesDir, progress.currentChange)
|
|
58
|
+
if (existsSync(target)) changeDir = target
|
|
59
|
+
}
|
|
60
|
+
if (!changeDir && existsSync(changesDir)) {
|
|
61
|
+
const entries = readdirSync(changesDir, { withFileTypes: true }).filter(e => e.isDirectory() && e.name !== 'archive')
|
|
62
|
+
if (entries.length === 1) changeDir = join(changesDir, entries[0].name)
|
|
63
|
+
}
|
|
64
|
+
return buildPlanSteps(changeDir)
|
|
65
|
+
}
|
|
66
|
+
const def = stageRegistry[stageName]
|
|
67
|
+
return def ? def.steps : null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 确保阶段的 steps 已初始化到 progress.json
|
|
72
|
+
*/
|
|
73
|
+
async function ensureStageSteps(progress, stageName, cwd) {
|
|
74
|
+
if (!progress.stages) progress.stages = {}
|
|
75
|
+
|
|
76
|
+
const steps = await getStageSteps(stageName, cwd, progress)
|
|
77
|
+
if (!steps) return false
|
|
78
|
+
|
|
79
|
+
if (!progress.stages[stageName] || !progress.stages[stageName].steps || progress.stages[stageName].steps.length === 0) {
|
|
80
|
+
progress.stages[stageName] = {
|
|
81
|
+
status: 'in-progress',
|
|
82
|
+
startedAt: new Date().toLocaleString('zh-CN',{hour12:false}),
|
|
83
|
+
completedAt: null,
|
|
84
|
+
steps: steps.map(s => ({ name: s.name, status: 'pending' }))
|
|
85
|
+
}
|
|
86
|
+
return true // 需要写入
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 检查步骤数量是否匹配(execute 动态步骤可能变化)
|
|
90
|
+
if (progress.stages[stageName].steps.length !== steps.length) {
|
|
91
|
+
// 保留已完成的状态,重新构建步骤列表
|
|
92
|
+
const oldSteps = progress.stages[stageName].steps
|
|
93
|
+
progress.stages[stageName].steps = steps.map(s => {
|
|
94
|
+
const old = oldSteps.find(step => step.name === s.name)
|
|
95
|
+
if (old) return old
|
|
96
|
+
return { name: s.name, status: 'pending' }
|
|
97
|
+
})
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 输出当前步骤的 prompt
|
|
106
|
+
*/
|
|
107
|
+
function outputStep(stageName, stepIndex, steps, cwd) {
|
|
108
|
+
const step = steps[stepIndex]
|
|
109
|
+
const total = steps.length
|
|
110
|
+
const projectName = basename(cwd)
|
|
111
|
+
|
|
112
|
+
const personas = {
|
|
113
|
+
brainstorm: `### 🎯 你的角色:资深架构师
|
|
114
|
+
你是一位有 15 年经验的系统架构师。先理解业务本质,再设计技术方案。决策附理由,方案列 trade-off。不确定就说不确定,不猜。`,
|
|
115
|
+
plan: `### 📋 你的角色:技术项目经理
|
|
116
|
+
你是一位经验丰富的技术项目经理。任务拆解粒度均匀,依赖关系明确。每个任务有完成标准,Wave 间有依赖说明。条理清晰,不做模糊描述。`,
|
|
117
|
+
execute: `### 💻 你的角色:高级工程师
|
|
118
|
+
你是一位严谨的高级工程师。先读规范再写代码,严格遵循 CONVENTIONS.md 和 plan.md。**你不是设计师,是执行者——按 plan 搬砖,禁止发散思维。** 发现 plan 不合理就停下来反馈,不要自己改方案。代码有清晰职责划分,边界处理完善。少说多做,遇到规范冲突优先问。`,
|
|
119
|
+
verify: `### 🔍 你的角色:QA 专家
|
|
120
|
+
你是一位吹毛求疵的 QA 专家。假设所有代码都有 bug,用最坏情况测试。关注边界、异常、并发。有问题直说,用证据说话,不写"看起来没问题"。`,
|
|
121
|
+
quick: `### 💻 你的角色:全栈老兵
|
|
122
|
+
你是一位实战经验丰富的全栈工程师。不纠结架构和流程,理解需求就直接干。不确定的地方先问清楚再动手,先读后写,改完就收。问题排查思路开阔,前端报错不一定是前端问题——可能是后端数据、浏览器兼容、甚至设备硬件。解决方案实用接地气,用户描述有误敢于直接指出。`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(`---`)
|
|
126
|
+
console.log(`stage: ${stageName}`)
|
|
127
|
+
console.log(`step: ${stepIndex + 1}/${total}`)
|
|
128
|
+
console.log(`stepName: ${step.name}`)
|
|
129
|
+
console.log(`project: ${projectName}`)
|
|
130
|
+
console.log(`---\n`)
|
|
131
|
+
if (personas[stageName]) {
|
|
132
|
+
console.log(personas[stageName])
|
|
133
|
+
console.log('')
|
|
134
|
+
}
|
|
135
|
+
console.log(`## Step ${stepIndex + 1}/${total}: ${step.name}\n`)
|
|
136
|
+
console.log(step.prompt)
|
|
137
|
+
console.log(`\n### ⚠️ 铁律`)
|
|
138
|
+
console.log('- **文档是核心资产,代码是文档的产物。** 没有文档就没有代码——文档是 AI 的记忆,是团队协作的基础,是后续维护的唯一依据。任何代码产出必须先有对应的设计/规范文档支撑。')
|
|
139
|
+
console.log('- 只做本步骤描述的操作,不得自行扩展或跳过')
|
|
140
|
+
console.log('- 不要回头修改已完成的步骤')
|
|
141
|
+
console.log('- 不要编造不存在的 CLI 子命令')
|
|
142
|
+
console.log('- 完成后立即执行 --done 命令,不得跳过')
|
|
143
|
+
console.log('- 文档类型文件(.md/.yaml/.json 等)头部必须包含 author(git 用户名)和 created_at(精确到秒)')
|
|
144
|
+
console.log('- 执行构建/测试前必须先读 local.yaml,优先使用其中配置的命令、路径和环境变量;未配置时才使用默认值')
|
|
145
|
+
console.log(`\n### 完成后执行`)
|
|
146
|
+
console.log(`sillyspec run ${stageName} --done --input "用户原始需求/反馈" --output "你的摘要"`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* sillyspec run <stage> 主命令
|
|
151
|
+
*/
|
|
152
|
+
export async function runCommand(args, cwd) {
|
|
153
|
+
// 解析参数
|
|
154
|
+
const stageName = args[0]
|
|
155
|
+
const flags = args.slice(1)
|
|
156
|
+
|
|
157
|
+
if (!stageName) {
|
|
158
|
+
console.error('❌ 请指定阶段,例如: sillyspec run brainstorm')
|
|
159
|
+
console.error(`可选: ${Object.keys(stageRegistry).join(', ')}, auto`)
|
|
160
|
+
process.exit(1)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!stageRegistry[stageName] && stageName !== 'auto') {
|
|
164
|
+
console.error(`❌ 未知阶段: ${stageName}`)
|
|
165
|
+
console.error(`可选: ${Object.keys(stageRegistry).join(', ')}, auto`)
|
|
166
|
+
process.exit(1)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const isDone = flags.includes('--done')
|
|
170
|
+
const isSkip = flags.includes('--skip')
|
|
171
|
+
const isStatus = flags.includes('--status')
|
|
172
|
+
const isReset = flags.includes('--reset')
|
|
173
|
+
|
|
174
|
+
// 解析 --output
|
|
175
|
+
let outputText = null
|
|
176
|
+
const outputIdx = flags.indexOf('--output')
|
|
177
|
+
if (outputIdx !== -1 && flags[outputIdx + 1]) {
|
|
178
|
+
outputText = flags[outputIdx + 1]
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 解析 --input
|
|
182
|
+
let inputText = null
|
|
183
|
+
const inputIdx = flags.indexOf('--input')
|
|
184
|
+
if (inputIdx !== -1 && flags[inputIdx + 1]) {
|
|
185
|
+
inputText = flags[inputIdx + 1]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 解析 --change <name>
|
|
189
|
+
let changeName = null
|
|
190
|
+
const changeIdx = flags.indexOf('--change')
|
|
191
|
+
if (changeIdx !== -1 && flags[changeIdx + 1]) {
|
|
192
|
+
changeName = flags[changeIdx + 1]
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const isAuxiliary = auxiliaryStages.includes(stageName)
|
|
196
|
+
|
|
197
|
+
const pm = new ProgressManager()
|
|
198
|
+
let progress = pm.read(cwd)
|
|
199
|
+
|
|
200
|
+
if (!progress) {
|
|
201
|
+
// 辅助命令可以在没有 progress.json 时工作(比如 scan)
|
|
202
|
+
if (!isAuxiliary) {
|
|
203
|
+
console.error('❌ 未找到 progress.json,请先运行 sillyspec init')
|
|
204
|
+
process.exit(1)
|
|
205
|
+
}
|
|
206
|
+
progress = pm.init(cwd)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// -- auto 模式:自动推进所有流程阶段
|
|
210
|
+
if (stageName === 'auto') {
|
|
211
|
+
return await runAutoMode(pm, progress, cwd, flags)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// --change 设置当前变更名
|
|
215
|
+
if (changeName) {
|
|
216
|
+
progress.currentChange = changeName
|
|
217
|
+
progress.lastActive = new Date().toLocaleString('zh-CN', { hour12: false })
|
|
218
|
+
pm._write(cwd, progress)
|
|
219
|
+
console.log(`✅ 当前变更设置为:${changeName}`)
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// --reset
|
|
224
|
+
if (isReset) {
|
|
225
|
+
return await resetStage(pm, progress, stageName, cwd)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 确保步骤已初始化
|
|
229
|
+
const changed = await ensureStageSteps(progress, stageName, cwd)
|
|
230
|
+
if (changed) {
|
|
231
|
+
pm._write(cwd, progress)
|
|
232
|
+
progress = pm.read(cwd)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// --status
|
|
236
|
+
if (isStatus) {
|
|
237
|
+
return showStatus(progress, stageName)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// --skip
|
|
241
|
+
if (isSkip) {
|
|
242
|
+
return await skipStep(pm, progress, stageName, cwd)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// --done
|
|
246
|
+
if (isDone) {
|
|
247
|
+
return await completeStep(pm, progress, stageName, cwd, outputText, inputText)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 默认:输出当前步骤
|
|
251
|
+
return await runStage(pm, progress, stageName, cwd)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function runStage(pm, progress, stageName, cwd) {
|
|
255
|
+
const stageData = progress.stages[stageName]
|
|
256
|
+
if (!stageData || !stageData.steps) {
|
|
257
|
+
console.error(`❌ 阶段 ${stageName} 未初始化`)
|
|
258
|
+
process.exit(1)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 用户显式调用 sillyspec run <stage>:把它标记为当前阶段
|
|
262
|
+
if (progress.currentStage !== stageName) {
|
|
263
|
+
progress.currentStage = stageName
|
|
264
|
+
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
265
|
+
pm._write(cwd, progress)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const steps = stageData.steps
|
|
269
|
+
let currentIdx = steps.findIndex(s => s.status !== 'completed' && s.status !== 'skipped')
|
|
270
|
+
|
|
271
|
+
if (currentIdx === -1) {
|
|
272
|
+
console.log(`✅ ${stageName} 阶段已完成。`)
|
|
273
|
+
console.log(` 如需重新开始,请显式执行 --reset。\n`)
|
|
274
|
+
return
|
|
275
|
+
} else if (currentIdx > 0) {
|
|
276
|
+
// 有进行中的步骤,提示用户
|
|
277
|
+
const completed = currentIdx
|
|
278
|
+
const total = steps.length
|
|
279
|
+
console.log(`⚠️ ${stageName} 已进行到第 ${currentIdx + 1}/${total} 步(前 ${completed} 步已完成)。`)
|
|
280
|
+
console.log(` 继续执行将从中断处恢复,用 --reset 可重新开始。\n`)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const defSteps = await getStageSteps(stageName, cwd, progress)
|
|
284
|
+
if (defSteps && defSteps[currentIdx]) {
|
|
285
|
+
outputStep(stageName, currentIdx, defSteps, cwd)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function validateMetadata(cwd, stageName) {
|
|
290
|
+
const changesDir = join(cwd, '.sillyspec', 'changes')
|
|
291
|
+
if (!existsSync(changesDir)) return
|
|
292
|
+
|
|
293
|
+
// 找最近 10 分钟内修改的 md/yaml 文件
|
|
294
|
+
const cutoff = Date.now() - 10 * 60 * 1000
|
|
295
|
+
const missing = []
|
|
296
|
+
|
|
297
|
+
function walk(dir) {
|
|
298
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
299
|
+
const full = join(dir, entry.name)
|
|
300
|
+
try {
|
|
301
|
+
if (entry.isDirectory()) { walk(full); continue }
|
|
302
|
+
if (!/\.(md|yaml|yml)$/.test(entry.name)) continue
|
|
303
|
+
const mtime = statSync(full).mtimeMs
|
|
304
|
+
if (mtime < cutoff) continue
|
|
305
|
+
const content = readFileSync(full, 'utf-8')
|
|
306
|
+
if (!content.includes('author:') && !content.includes('author:')) missing.push(full)
|
|
307
|
+
if (!content.includes('created_at:') && !content.includes('created_at:')) missing.push(full)
|
|
308
|
+
} catch (e) { /* skip unreadable files */ }
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
walk(changesDir)
|
|
313
|
+
const unique = [...new Set(missing)]
|
|
314
|
+
if (unique.length > 0) {
|
|
315
|
+
console.log(`\n⚠️ 以下文件缺少 author 或 created_at 元数据:`)
|
|
316
|
+
unique.forEach(f => console.log(` - ${f.replace(cwd + '/', '')}`))
|
|
317
|
+
console.log('请在文件头部添加 author(git 用户名)和 created_at(精确到秒)')
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function completeStep(pm, progress, stageName, cwd, outputText, inputText = null, options = {}) {
|
|
322
|
+
const { printNext = true } = options
|
|
323
|
+
const stageData = progress.stages[stageName]
|
|
324
|
+
if (!stageData || !stageData.steps) {
|
|
325
|
+
console.error(`❌ 阶段 ${stageName} 未初始化`)
|
|
326
|
+
process.exit(1)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const steps = stageData.steps
|
|
330
|
+
const currentIdx = steps.findIndex(s => s.status === 'pending')
|
|
331
|
+
|
|
332
|
+
if (currentIdx === -1) {
|
|
333
|
+
console.error('没有待完成的步骤')
|
|
334
|
+
process.exit(1)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 标记完成(先标记,再处理动态步骤插入)
|
|
338
|
+
|
|
339
|
+
steps[currentIdx].status = 'completed'
|
|
340
|
+
steps[currentIdx].completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
341
|
+
if (outputText) {
|
|
342
|
+
const MAX_OUTPUT = 200
|
|
343
|
+
if (outputText.length > MAX_OUTPUT) {
|
|
344
|
+
steps[currentIdx].output = outputText.slice(0, MAX_OUTPUT) + '…'
|
|
345
|
+
// Save full output to artifacts/
|
|
346
|
+
const artifactsDir = join(cwd, '.sillyspec', '.runtime', 'artifacts')
|
|
347
|
+
mkdirSync(artifactsDir, { recursive: true })
|
|
348
|
+
const ts = new Date().toISOString().slice(0,19).replace(/[-T:]/g, '')
|
|
349
|
+
writeFileSync(join(artifactsDir, `${stageName}-step${currentIdx + 1}-${ts}.txt`), outputText)
|
|
350
|
+
} else {
|
|
351
|
+
steps[currentIdx].output = outputText
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// plan 阶段 "展开任务" 完成后,动态插入 task 蓝图步骤
|
|
356
|
+
if (stageName === 'plan' && steps[currentIdx]?.name === '展开任务并分组' && progress.currentChange) {
|
|
357
|
+
const planFile = join(cwd, '.sillyspec', 'changes', progress.currentChange, 'plan.md')
|
|
358
|
+
if (existsSync(planFile)) {
|
|
359
|
+
const planContent = readFileSync(planFile, 'utf8')
|
|
360
|
+
const { buildPlanSteps, fixedPrefix, fixedSuffix } = await import('./stages/plan.js')
|
|
361
|
+
const fullSteps = buildPlanSteps(join(cwd, '.sillyspec', 'changes', progress.currentChange), planContent)
|
|
362
|
+
const prefixLen = fixedPrefix.length
|
|
363
|
+
const suffixLen = fixedSuffix.length
|
|
364
|
+
const taskSteps = fullSteps.slice(prefixLen, suffixLen > 0 ? -suffixLen : undefined)
|
|
365
|
+
if (taskSteps.length > 0) {
|
|
366
|
+
const oldSteps = [...steps]
|
|
367
|
+
const rebuilt = fullSteps.map(stepDef => {
|
|
368
|
+
const existing = oldSteps.find(s => s.name === stepDef.name && s.status !== 'pending')
|
|
369
|
+
if (existing) return existing
|
|
370
|
+
return { name: stepDef.name, status: 'pending' }
|
|
371
|
+
})
|
|
372
|
+
steps.splice(0, steps.length, ...rebuilt)
|
|
373
|
+
console.log(` 📝 已动态插入 ${taskSteps.length} 个任务蓝图步骤`)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const nextPendingIdx = steps.findIndex(s => s.status === 'pending')
|
|
379
|
+
|
|
380
|
+
if (nextPendingIdx === -1) {
|
|
381
|
+
// 全部完成 — 仅标记当前阶段,不自动推进 currentStage
|
|
382
|
+
stageData.status = 'completed'
|
|
383
|
+
stageData.completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
384
|
+
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
385
|
+
pm._write(cwd, progress)
|
|
386
|
+
|
|
387
|
+
// Append to user-inputs.md
|
|
388
|
+
if (outputText) {
|
|
389
|
+
const inputsPath = join(cwd, '.sillyspec', '.runtime', 'user-inputs.md')
|
|
390
|
+
const entry = `\n## ${new Date().toLocaleString('zh-CN',{hour12:false})} | ${stageName}: ${steps[currentIdx].name}\n${inputText ? "- 输入:" + inputText + "\n" : ""}- 输出:${outputText}\n`
|
|
391
|
+
appendFileSync(inputsPath, entry)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// 验证:检查生成的文件是否包含 author 和 created_at
|
|
395
|
+
validateMetadata(cwd, stageName)
|
|
396
|
+
|
|
397
|
+
const total = steps.length
|
|
398
|
+
console.log(`✅ ${stageName} 阶段已完成(${total}/${total} 步)`)
|
|
399
|
+
console.log(`\n下一步由你决定:sillyspec run <stage>(brainstorm/plan/execute/verify/archive 等)`)
|
|
400
|
+
return { stageCompleted: true, currentIdx, nextPendingIdx: -1 }
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
404
|
+
pm._write(cwd, progress)
|
|
405
|
+
|
|
406
|
+
// Append to user-inputs.md
|
|
407
|
+
if (outputText) {
|
|
408
|
+
const inputsPath = join(cwd, '.sillyspec', '.runtime', 'user-inputs.md')
|
|
409
|
+
const entry = `\n## ${new Date().toLocaleString('zh-CN',{hour12:false})} | ${stageName}: ${steps[currentIdx].name}\n${inputText ? "- 输入:" + inputText + "\n" : ""}- 输出:${outputText}\n`
|
|
410
|
+
appendFileSync(inputsPath, entry)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const defSteps = await getStageSteps(stageName, cwd, progress)
|
|
414
|
+
console.log(`✅ Step ${currentIdx + 1}/${steps.length} 完成:${steps[currentIdx].name}\n`)
|
|
415
|
+
if (printNext) {
|
|
416
|
+
outputStep(stageName, nextPendingIdx, defSteps, cwd)
|
|
417
|
+
}
|
|
418
|
+
return { stageCompleted: false, currentIdx, nextPendingIdx }
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async function skipStep(pm, progress, stageName, cwd) {
|
|
422
|
+
const stageData = progress.stages[stageName]
|
|
423
|
+
if (!stageData || !stageData.steps) {
|
|
424
|
+
console.error(`❌ 阶段 ${stageName} 未初始化`)
|
|
425
|
+
process.exit(1)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const steps = stageData.steps
|
|
429
|
+
const currentIdx = steps.findIndex(s => s.status === 'pending')
|
|
430
|
+
|
|
431
|
+
if (currentIdx === -1) {
|
|
432
|
+
console.error('没有待跳过的步骤')
|
|
433
|
+
process.exit(1)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const defSteps = await getStageSteps(stageName, cwd, progress)
|
|
437
|
+
const stepDef = defSteps ? defSteps[currentIdx] : null
|
|
438
|
+
if (stepDef && !stepDef.optional) {
|
|
439
|
+
console.error(`❌ 步骤 "${steps[currentIdx].name}" 不可跳过`)
|
|
440
|
+
process.exit(1)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
steps[currentIdx].status = 'skipped'
|
|
444
|
+
steps[currentIdx].skippedAt = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
445
|
+
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
446
|
+
pm._write(cwd, progress)
|
|
447
|
+
|
|
448
|
+
console.log(`⏭️ Step ${currentIdx + 1}/${steps.length} 已跳过:${steps[currentIdx].name}`)
|
|
449
|
+
|
|
450
|
+
// 输出下一步
|
|
451
|
+
const nextPendingIdx = steps.findIndex(s => s.status === 'pending')
|
|
452
|
+
if (nextPendingIdx !== -1 && defSteps) {
|
|
453
|
+
console.log('')
|
|
454
|
+
outputStep(stageName, nextPendingIdx, defSteps, cwd)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function showStatus(progress, stageName) {
|
|
459
|
+
const stageData = progress.stages[stageName]
|
|
460
|
+
const stageDef = stageRegistry[stageName]
|
|
461
|
+
|
|
462
|
+
if (!stageData || !stageData.steps || stageData.steps.length === 0) {
|
|
463
|
+
console.log(`阶段:${stageName}(${stageDef.title})`)
|
|
464
|
+
console.log(`进度:未初始化`)
|
|
465
|
+
return
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const steps = stageData.steps
|
|
469
|
+
const completed = steps.filter(s => s.status === 'completed' || s.status === 'skipped').length
|
|
470
|
+
const bar = '█'.repeat(completed) + '░'.repeat(steps.length - completed)
|
|
471
|
+
|
|
472
|
+
console.log(`阶段:${stageName}(${stageDef.title})`)
|
|
473
|
+
console.log(`进度:[${bar}] ${completed}/${steps.length}\n`)
|
|
474
|
+
|
|
475
|
+
const firstPending = steps.findIndex(s => s.status === 'pending')
|
|
476
|
+
|
|
477
|
+
// 批量进度
|
|
478
|
+
if (progress.batchProgress) {
|
|
479
|
+
const bp = progress.batchProgress
|
|
480
|
+
const bpTotal = bp.total || 0
|
|
481
|
+
const bpCompleted = bp.completed || 0
|
|
482
|
+
const bpFailed = bp.failed || 0
|
|
483
|
+
const bpSkipped = bp.skipped || 0
|
|
484
|
+
const bpBarLen = 20
|
|
485
|
+
const bpFilled = Math.round((bpCompleted / Math.max(bpTotal, 1)) * bpBarLen)
|
|
486
|
+
const bpBar = '█'.repeat(bpFilled) + '░'.repeat(bpBarLen - bpFilled)
|
|
487
|
+
const bpParts = []
|
|
488
|
+
if (bpFailed > 0) bpParts.push(`${bpFailed} 失败`)
|
|
489
|
+
if (bpSkipped > 0) bpParts.push(`${bpSkipped} 跳过`)
|
|
490
|
+
const bpSuffix = bpParts.length ? ` (${bpParts.join(', ')})` : ''
|
|
491
|
+
console.log(`\n📊 批量进度: ${bpBar} ${bpCompleted}/${bpTotal}${bpSuffix}\n`)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
steps.forEach((step, i) => {
|
|
495
|
+
const icon = step.status === 'completed' ? '✅' : step.status === 'skipped' ? '⏭️' : '⬜'
|
|
496
|
+
const isCurrent = step.status === 'pending' && i === firstPending
|
|
497
|
+
console.log(`${icon} Step ${i + 1}: ${step.name}${isCurrent ? ' ← 当前' : ''}`)
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function resetStage(pm, progress, stageName, cwd) {
|
|
502
|
+
const defSteps = await getStageSteps(stageName, cwd, progress)
|
|
503
|
+
progress.stages[stageName] = {
|
|
504
|
+
status: 'in-progress',
|
|
505
|
+
startedAt: new Date().toLocaleString('zh-CN',{hour12:false}),
|
|
506
|
+
completedAt: null,
|
|
507
|
+
steps: defSteps ? defSteps.map(s => ({ name: s.name, status: 'pending' })) : []
|
|
508
|
+
}
|
|
509
|
+
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
510
|
+
pm._write(cwd, progress)
|
|
511
|
+
console.log(`🔄 ${stageName} 阶段已重置`)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* auto 模式:自动推进 brainstorm → plan → execute → verify
|
|
516
|
+
*/
|
|
517
|
+
async function runAutoMode(pm, progress, cwd, flags) {
|
|
518
|
+
const flowStages = ['brainstorm', 'plan', 'execute', 'verify']
|
|
519
|
+
const isDone = flags.includes('--done')
|
|
520
|
+
const outputIdx = flags.indexOf('--output')
|
|
521
|
+
const outputText = outputIdx !== -1 && flags[outputIdx + 1] ? flags[outputIdx + 1] : null
|
|
522
|
+
const inputIdx = flags.indexOf('--input')
|
|
523
|
+
const inputText = inputIdx !== -1 && flags[inputIdx + 1] ? flags[inputIdx + 1] : null
|
|
524
|
+
const nextInFlow = (stage) => {
|
|
525
|
+
const i = flowStages.indexOf(stage)
|
|
526
|
+
return i >= 0 && i < flowStages.length - 1 ? flowStages[i + 1] : null
|
|
527
|
+
}
|
|
528
|
+
const firstOpenStage = () => flowStages.find(s => progress.stages?.[s]?.status !== 'completed')
|
|
529
|
+
const ensureAutoStage = async (stage) => {
|
|
530
|
+
const stageChanged = progress.currentStage !== stage
|
|
531
|
+
progress.currentStage = stage
|
|
532
|
+
const changed = await ensureStageSteps(progress, stage, cwd)
|
|
533
|
+
if (stageChanged || changed) pm._write(cwd, progress)
|
|
534
|
+
progress = pm.read(cwd)
|
|
535
|
+
return progress
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
let currentStage = progress.currentStage
|
|
539
|
+
if (!currentStage || progress.stages?.[currentStage]?.status === 'completed') {
|
|
540
|
+
currentStage = firstOpenStage()
|
|
541
|
+
}
|
|
542
|
+
if (!currentStage) {
|
|
543
|
+
console.log('All auto flow stages are complete.')
|
|
544
|
+
return
|
|
545
|
+
}
|
|
546
|
+
if (!flowStages.includes(currentStage)) {
|
|
547
|
+
// 当前在辅助阶段(scan/quick/archive 等),自动跳到第一个未完成的流程阶段
|
|
548
|
+
const openStage = firstOpenStage()
|
|
549
|
+
if (!openStage) {
|
|
550
|
+
console.log('All auto flow stages are complete.')
|
|
551
|
+
return
|
|
552
|
+
}
|
|
553
|
+
console.log(`⚠️ 当前阶段 ${currentStage} 不在 auto 流程中,自动跳转到 ${openStage}`)
|
|
554
|
+
currentStage = openStage
|
|
555
|
+
}
|
|
556
|
+
await ensureAutoStage(currentStage)
|
|
557
|
+
|
|
558
|
+
if (!isDone) {
|
|
559
|
+
console.log('════════════════════════════════════════')
|
|
560
|
+
console.log(' SillySpec Auto Mode')
|
|
561
|
+
console.log('════════════════════════════════════════')
|
|
562
|
+
console.log(` Flow: ${flowStages.join(' -> ')}`)
|
|
563
|
+
console.log(` Current: ${currentStage}`)
|
|
564
|
+
for (const stage of flowStages) {
|
|
565
|
+
const stageData = progress.stages?.[stage]
|
|
566
|
+
const total = stageData?.steps?.length || '?'
|
|
567
|
+
const completed = stageData?.steps?.filter(step => step.status === 'completed' || step.status === 'skipped').length || 0
|
|
568
|
+
const marker = stageData?.status === 'completed' ? 'done' : stage === currentStage ? 'active' : 'pending'
|
|
569
|
+
console.log(` ${marker} ${stage} (${completed}/${total})`)
|
|
570
|
+
}
|
|
571
|
+
console.log('')
|
|
572
|
+
|
|
573
|
+
const defSteps = await getStageSteps(currentStage, cwd, progress)
|
|
574
|
+
const pendingIdx = progress.stages[currentStage]?.steps?.findIndex(step => step.status === 'pending') ?? -1
|
|
575
|
+
if (pendingIdx === -1) {
|
|
576
|
+
const next = nextInFlow(currentStage)
|
|
577
|
+
if (next) console.log(`${currentStage} is complete. Run: sillyspec run auto --done --output "${currentStage} complete"`)
|
|
578
|
+
else console.log('All auto flow stages are complete.')
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
outputStep(currentStage, pendingIdx, defSteps, cwd)
|
|
582
|
+
return
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (!outputText) {
|
|
586
|
+
console.error('auto --done requires --output')
|
|
587
|
+
process.exit(1)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const result = await completeStep(pm, progress, currentStage, cwd, outputText, inputText, { printNext: false })
|
|
591
|
+
if (!result) return
|
|
592
|
+
progress = pm.read(cwd)
|
|
593
|
+
|
|
594
|
+
const nextPendingIdx = progress.stages[currentStage]?.steps?.findIndex(step => step.status === 'pending') ?? -1
|
|
595
|
+
if (nextPendingIdx !== -1) {
|
|
596
|
+
const defSteps = await getStageSteps(currentStage, cwd, progress)
|
|
597
|
+
outputStep(currentStage, nextPendingIdx, defSteps, cwd)
|
|
598
|
+
return
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const next = nextInFlow(currentStage)
|
|
602
|
+
if (!next) {
|
|
603
|
+
console.log('\nAll auto flow stages are complete.')
|
|
604
|
+
return
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
progress.currentStage = next
|
|
608
|
+
if (!progress.stages[next]) {
|
|
609
|
+
progress.stages[next] = { status: 'pending', steps: [], startedAt: null, completedAt: null }
|
|
610
|
+
}
|
|
611
|
+
if (progress.stages[next].status === 'pending' || !progress.stages[next].status) {
|
|
612
|
+
progress.stages[next].status = 'in-progress'
|
|
613
|
+
progress.stages[next].startedAt = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
614
|
+
}
|
|
615
|
+
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
616
|
+
await ensureStageSteps(progress, next, cwd)
|
|
617
|
+
pm._write(cwd, progress)
|
|
618
|
+
progress = pm.read(cwd)
|
|
619
|
+
|
|
620
|
+
console.log(`\n${currentStage} complete. Auto advanced to ${next}.`)
|
|
621
|
+
const nextSteps = await getStageSteps(next, cwd, progress)
|
|
622
|
+
const firstPending = progress.stages[next]?.steps?.findIndex(step => step.status === 'pending') ?? -1
|
|
623
|
+
if (firstPending !== -1) outputStep(next, firstPending, nextSteps, cwd)
|
|
624
|
+
}
|