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.
Files changed (212) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +17 -0
  2. package/.claude/skills/sillyspec-auto/SKILL.md +78 -0
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +17 -0
  4. package/{templates/commit.md → .claude/skills/sillyspec-commit/SKILL.md} +32 -47
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -0
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +27 -0
  7. package/.claude/skills/sillyspec-execute/SKILL.md +17 -0
  8. package/.claude/skills/sillyspec-explore/SKILL.md +96 -0
  9. package/.claude/skills/sillyspec-export/SKILL.md +53 -0
  10. package/.claude/skills/sillyspec-init/SKILL.md +170 -0
  11. package/.claude/skills/sillyspec-plan/SKILL.md +52 -0
  12. package/.claude/skills/sillyspec-propose/SKILL.md +17 -0
  13. package/.claude/skills/sillyspec-quick/SKILL.md +17 -0
  14. package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
  15. package/.claude/skills/sillyspec-scan/SKILL.md +17 -0
  16. package/.claude/skills/sillyspec-state/SKILL.md +54 -0
  17. package/.claude/skills/sillyspec-status/SKILL.md +17 -0
  18. package/.claude/skills/sillyspec-verify/SKILL.md +17 -0
  19. package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
  20. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
  21. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
  22. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
  23. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
  24. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
  25. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
  26. package/.sillyspec/changes/auto-mode/design.md +50 -0
  27. package/.sillyspec/changes/auto-mode/proposal.md +19 -0
  28. package/.sillyspec/changes/auto-mode/requirements.md +21 -0
  29. package/.sillyspec/changes/auto-mode/tasks.md +7 -0
  30. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
  31. package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
  32. package/.sillyspec/changes/run-command-design/design.md +1230 -0
  33. package/.sillyspec/changes/unified-docs-design/design.md +199 -0
  34. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  35. package/.sillyspec/knowledge/INDEX.md +8 -0
  36. package/.sillyspec/knowledge/uncategorized.md +3 -0
  37. package/.sillyspec/projects/sillyspec.yaml +3 -0
  38. package/README.md +12 -5
  39. package/package.json +7 -9
  40. package/packages/dashboard/dist/assets/index-CntACGUN.css +1 -0
  41. package/packages/dashboard/dist/assets/index-RsLVPAy7.js +7446 -0
  42. package/packages/dashboard/dist/index.html +3 -2
  43. package/packages/dashboard/package-lock.json +226 -6
  44. package/packages/dashboard/package.json +8 -5
  45. package/packages/dashboard/public/logo.jpg +0 -0
  46. package/packages/dashboard/server/executor.js +1 -1
  47. package/packages/dashboard/server/index.js +336 -113
  48. package/packages/dashboard/server/parser.js +333 -29
  49. package/packages/dashboard/server/watcher.js +203 -131
  50. package/packages/dashboard/src/App.vue +187 -11
  51. package/packages/dashboard/src/components/ActionBar.vue +26 -42
  52. package/packages/dashboard/src/components/CommandPalette.vue +40 -65
  53. package/packages/dashboard/src/components/DetailPanel.vue +68 -53
  54. package/packages/dashboard/src/components/DocPreview.vue +160 -0
  55. package/packages/dashboard/src/components/DocTree.vue +58 -0
  56. package/packages/dashboard/src/components/LogStream.vue +13 -33
  57. package/packages/dashboard/src/components/PipelineStage.vue +8 -8
  58. package/packages/dashboard/src/components/PipelineView.vue +80 -45
  59. package/packages/dashboard/src/components/ProjectList.vue +103 -45
  60. package/packages/dashboard/src/components/ProjectOverview.vue +178 -0
  61. package/packages/dashboard/src/components/StageBadge.vue +13 -13
  62. package/packages/dashboard/src/components/StepCard.vue +15 -15
  63. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
  64. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
  65. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
  66. package/packages/dashboard/src/composables/useDashboard.js +20 -6
  67. package/packages/dashboard/src/composables/useKeyboard.js +6 -4
  68. package/packages/dashboard/src/main.js +4 -1
  69. package/packages/dashboard/src/style.css +17 -17
  70. package/src/index.js +134 -22
  71. package/src/init.js +83 -228
  72. package/src/migrate.js +117 -0
  73. package/src/progress.js +459 -0
  74. package/src/run.js +624 -0
  75. package/src/setup.js +2 -72
  76. package/src/stages/archive.js +54 -0
  77. package/src/stages/brainstorm.js +239 -0
  78. package/src/stages/doctor.js +303 -0
  79. package/src/stages/execute.js +262 -0
  80. package/src/stages/index.js +26 -0
  81. package/src/stages/plan.js +282 -0
  82. package/src/stages/propose.js +115 -0
  83. package/src/stages/quick.js +64 -0
  84. package/src/stages/scan.js +141 -0
  85. package/src/stages/status.js +65 -0
  86. package/src/stages/verify.js +135 -0
  87. package/dist/steps/brainstorm/01-load-context.md +0 -30
  88. package/dist/steps/brainstorm/02-reuse-check.md +0 -6
  89. package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
  90. package/dist/steps/brainstorm/04-module-split.md +0 -23
  91. package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
  92. package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
  93. package/dist/steps/brainstorm/07-present-design.md +0 -3
  94. package/dist/steps/brainstorm/08-write-design.md +0 -21
  95. package/dist/steps/brainstorm/09-self-review.md +0 -15
  96. package/dist/steps/brainstorm/10-user-confirm.md +0 -3
  97. package/dist/steps/brainstorm/11-output-spec.md +0 -7
  98. package/dist/steps/brainstorm/manifest.yaml +0 -26
  99. package/dist/steps/execute/01-load-context.md +0 -41
  100. package/dist/steps/execute/02-scan-conventions.md +0 -47
  101. package/dist/steps/execute/03-skill-mcp.md +0 -19
  102. package/dist/steps/execute/04-assign-task.md +0 -22
  103. package/dist/steps/execute/04b-prompt-template.md +0 -54
  104. package/dist/steps/execute/05-write-test.md +0 -7
  105. package/dist/steps/execute/06-write-code.md +0 -8
  106. package/dist/steps/execute/07-run-test.md +0 -26
  107. package/dist/steps/execute/08-fix-issues.md +0 -28
  108. package/dist/steps/execute/09-next-task.md +0 -33
  109. package/dist/steps/execute/manifest.yaml +0 -28
  110. package/dist/steps/plan/01-load-context.md +0 -22
  111. package/dist/steps/plan/02-anchor-confirm.md +0 -1
  112. package/dist/steps/plan/03-expand-tasks.md +0 -33
  113. package/dist/steps/plan/04-mark-order.md +0 -15
  114. package/dist/steps/plan/05-e2e-planning.md +0 -17
  115. package/dist/steps/plan/06-self-check.md +0 -16
  116. package/dist/steps/plan/07-save.md +0 -1
  117. package/dist/steps/plan/manifest.yaml +0 -18
  118. package/dist/steps/scan/01-env-detect.md +0 -51
  119. package/dist/steps/scan/02-tech-stack.md +0 -16
  120. package/dist/steps/scan/03-conventions.md +0 -16
  121. package/dist/steps/scan/04-structure.md +0 -19
  122. package/dist/steps/scan/05-quality.md +0 -18
  123. package/dist/steps/scan/06-complete.md +0 -49
  124. package/dist/steps/scan/manifest.yaml +0 -16
  125. package/dist/steps/verify/01-load-specs.md +0 -28
  126. package/dist/steps/verify/02-check-tasks.md +0 -1
  127. package/dist/steps/verify/03-check-design.md +0 -6
  128. package/dist/steps/verify/04-run-tests.md +0 -7
  129. package/dist/steps/verify/05-e2e-tests.md +0 -27
  130. package/dist/steps/verify/05b-e2e-fix.md +0 -33
  131. package/dist/steps/verify/06-code-quality.md +0 -25
  132. package/dist/steps/verify/07-lint-check.md +0 -27
  133. package/dist/steps/verify/08-output-report.md +0 -14
  134. package/dist/steps/verify/manifest.yaml +0 -22
  135. package/docs/.vitepress/config.mts +0 -45
  136. package/docs/.vitepress/dist/404.html +0 -25
  137. package/docs/.vitepress/dist/assets/app.YytxICdd.js +0 -1
  138. package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +0 -19
  139. package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +0 -1
  140. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +0 -1
  141. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +0 -1
  142. package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  143. package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  144. package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  145. package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  146. package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  147. package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  148. package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  149. package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  150. package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  151. package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  152. package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  153. package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  154. package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  155. package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  156. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.js +0 -15
  157. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +0 -1
  158. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +0 -4
  159. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +0 -1
  160. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +0 -1
  161. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +0 -1
  162. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +0 -4
  163. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +0 -1
  164. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +0 -5
  165. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +0 -1
  166. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +0 -28
  167. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +0 -1
  168. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +0 -30
  169. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +0 -1
  170. package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +0 -1
  171. package/docs/.vitepress/dist/hashmap.json +0 -1
  172. package/docs/.vitepress/dist/index.html +0 -28
  173. package/docs/.vitepress/dist/sillyspec/commands.html +0 -42
  174. package/docs/.vitepress/dist/sillyspec/dashboard.html +0 -31
  175. package/docs/.vitepress/dist/sillyspec/file-io.html +0 -28
  176. package/docs/.vitepress/dist/sillyspec/getting-started.html +0 -31
  177. package/docs/.vitepress/dist/sillyspec/install.html +0 -32
  178. package/docs/.vitepress/dist/sillyspec/lifecycle.html +0 -55
  179. package/docs/.vitepress/dist/sillyspec/structure.html +0 -57
  180. package/docs/.vitepress/dist/vp-icons.css +0 -1
  181. package/docs/index.md +0 -34
  182. package/docs/sillyspec/commands.md +0 -218
  183. package/docs/sillyspec/dashboard.md +0 -51
  184. package/docs/sillyspec/file-io.md +0 -34
  185. package/docs/sillyspec/getting-started.md +0 -61
  186. package/docs/sillyspec/install.md +0 -51
  187. package/docs/sillyspec/lifecycle.md +0 -146
  188. package/docs/sillyspec/structure.md +0 -62
  189. package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +0 -1
  190. package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +0 -17
  191. package/src/step.js +0 -543
  192. package/templates/archive.md +0 -120
  193. package/templates/brainstorm.md +0 -170
  194. package/templates/continue.md +0 -32
  195. package/templates/execute.md +0 -304
  196. package/templates/explore.md +0 -59
  197. package/templates/export.md +0 -21
  198. package/templates/init.md +0 -61
  199. package/templates/plan.md +0 -146
  200. package/templates/quick.md +0 -135
  201. package/templates/scan-quick.md +0 -49
  202. package/templates/scan.md +0 -156
  203. package/templates/skills/playwright-e2e/SKILL.md +0 -340
  204. package/templates/status.md +0 -75
  205. package/templates/verify.md +0 -236
  206. package/templates/workspace-sync.md +0 -99
  207. package/templates/workspace.md +0 -70
  208. /package/.sillyspec/{specs → changes/brainstorm-archive}/2026-04-05-dashboard-design.md +0 -0
  209. /package/{docs/.vitepress/dist/logo.jpg → logo.jpg} +0 -0
  210. /package/{docs/.vitepress → packages/dashboard}/dist/favicon.jpg +0 -0
  211. /package/{docs/public → packages/dashboard/dist}/logo.jpg +0 -0
  212. /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
+ }