sillyspec 3.9.1 → 3.10.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.
Files changed (64) hide show
  1. package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
  2. package/.claude/skills/sillyspec-continue/SKILL.md +1 -1
  3. package/.claude/skills/sillyspec-explore/SKILL.md +9 -0
  4. package/.claude/skills/sillyspec-plan/SKILL.md +0 -35
  5. package/.claude/skills/sillyspec-workspace/SKILL.md +1 -1
  6. package/README.md +7 -6
  7. package/SKILL.md +15 -10
  8. package/package.json +1 -1
  9. package/packages/dashboard/dist/assets/index-BcM2J-hv.css +1 -0
  10. package/packages/dashboard/dist/assets/{index-RsLVPAy7.js → index-DpLHK4jv.js} +974 -974
  11. package/packages/dashboard/dist/index.html +16 -17
  12. package/packages/dashboard/dist/prototype-dashboard.html +836 -0
  13. package/packages/dashboard/dist/prototype-overview.html +256 -0
  14. package/packages/dashboard/public/prototype-dashboard.html +836 -0
  15. package/packages/dashboard/public/prototype-overview.html +256 -0
  16. package/packages/dashboard/server/index.js +18 -13
  17. package/packages/dashboard/server/parser.js +109 -1
  18. package/packages/dashboard/server/watcher.js +14 -6
  19. package/packages/dashboard/src/App.vue +414 -186
  20. package/packages/dashboard/src/components/ActionBar.vue +10 -1
  21. package/packages/dashboard/src/components/CommandPalette.vue +5 -1
  22. package/packages/dashboard/src/components/DocPreview.vue +105 -8
  23. package/packages/dashboard/src/components/DocTree.vue +75 -19
  24. package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
  25. package/packages/dashboard/src/components/PipelineView.vue +23 -4
  26. package/packages/dashboard/src/components/ProjectCard.vue +187 -0
  27. package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
  28. package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
  29. package/packages/dashboard/src/composables/useDashboard.js +28 -0
  30. package/packages/dashboard/src/composables/useLayout.js +131 -0
  31. package/src/index.js +7 -0
  32. package/src/init.js +17 -10
  33. package/src/migrate.js +5 -5
  34. package/src/progress.js +2 -1
  35. package/src/run.js +72 -61
  36. package/src/stages/brainstorm.js +28 -3
  37. package/src/stages/execute.js +52 -27
  38. package/src/stages/explore.js +34 -0
  39. package/src/stages/index.js +3 -1
  40. package/src/stages/plan.js +86 -14
  41. package/src/stages/scan.js +11 -11
  42. package/src/stages/status.js +1 -1
  43. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
  44. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
  45. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
  46. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
  47. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
  48. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
  49. package/.sillyspec/changes/auto-mode/design.md +0 -50
  50. package/.sillyspec/changes/auto-mode/proposal.md +0 -19
  51. package/.sillyspec/changes/auto-mode/requirements.md +0 -21
  52. package/.sillyspec/changes/auto-mode/tasks.md +0 -7
  53. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
  54. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
  55. package/.sillyspec/changes/dashboard/design.md +0 -219
  56. package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
  57. package/.sillyspec/changes/run-command-design/design.md +0 -1230
  58. package/.sillyspec/changes/unified-docs-design/design.md +0 -199
  59. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  60. package/.sillyspec/knowledge/INDEX.md +0 -8
  61. package/.sillyspec/knowledge/uncategorized.md +0 -3
  62. package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
  63. package/.sillyspec/projects/sillyspec.yaml +0 -3
  64. package/packages/dashboard/dist/assets/index-CntACGUN.css +0 -1
package/src/run.js CHANGED
@@ -10,57 +10,59 @@ import { stageRegistry, auxiliaryStages } from './stages/index.js'
10
10
  import { buildExecuteSteps } from './stages/execute.js'
11
11
  import { buildPlanSteps } from './stages/plan.js'
12
12
 
13
+ /**
14
+ * 统一查找变更目录
15
+ */
16
+ function resolveChangeDir(cwd, progress) {
17
+ const changesDir = join(cwd, '.sillyspec', 'changes')
18
+ if (!existsSync(changesDir)) return null
19
+
20
+ // 1. 优先用 currentChange
21
+ if (progress.currentChange) {
22
+ const target = join(changesDir, progress.currentChange)
23
+ if (existsSync(target)) return target
24
+ }
25
+
26
+ // 2. fallback:唯一非 archive 目录
27
+ const entries = readdirSync(changesDir, { withFileTypes: true })
28
+ .filter(e => e.isDirectory() && e.name !== 'archive')
29
+ if (entries.length === 1) return join(changesDir, entries[0].name)
30
+
31
+ return null
32
+ }
33
+
34
+ /**
35
+ * 自动探测并设置 currentChange(唯一变更目录时)
36
+ * @returns {boolean} 是否设置了 currentChange
37
+ */
38
+ function autoDetectChange(progress, cwd) {
39
+ if (progress.currentChange) return false
40
+ const changesDir = join(cwd, '.sillyspec', 'changes')
41
+ if (!existsSync(changesDir)) return false
42
+ const entries = readdirSync(changesDir, { withFileTypes: true })
43
+ .filter(e => e.isDirectory() && e.name !== 'archive')
44
+ if (entries.length === 1) {
45
+ progress.currentChange = entries[0].name
46
+ return true
47
+ }
48
+ return false
49
+ }
50
+
13
51
  /**
14
52
  * 获取阶段的步骤定义(execute 需要动态构建)
15
53
  */
16
54
  async function getStageSteps(stageName, cwd, progress) {
17
55
  if (stageName === 'execute') {
18
- const changesDir = join(cwd, '.sillyspec', 'changes')
56
+ const changeDir = resolveChangeDir(cwd, progress)
19
57
  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
- }
58
+ if (changeDir) {
59
+ const p = join(changeDir, 'plan.md')
60
+ if (existsSync(p)) planFile = p
50
61
  }
51
62
  return buildExecuteSteps(planFile)
52
63
  }
53
64
  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
- }
65
+ const changeDir = resolveChangeDir(cwd, progress)
64
66
  return buildPlanSteps(changeDir)
65
67
  }
66
68
  const def = stageRegistry[stageName]
@@ -119,7 +121,9 @@ function outputStep(stageName, stepIndex, steps, cwd) {
119
121
  verify: `### 🔍 你的角色:QA 专家
120
122
  你是一位吹毛求疵的 QA 专家。假设所有代码都有 bug,用最坏情况测试。关注边界、异常、并发。有问题直说,用证据说话,不写"看起来没问题"。`,
121
123
  quick: `### 💻 你的角色:全栈老兵
122
- 你是一位实战经验丰富的全栈工程师。不纠结架构和流程,理解需求就直接干。不确定的地方先问清楚再动手,先读后写,改完就收。问题排查思路开阔,前端报错不一定是前端问题——可能是后端数据、浏览器兼容、甚至设备硬件。解决方案实用接地气,用户描述有误敢于直接指出。`
124
+ 你是一位实战经验丰富的全栈工程师。不纠结架构和流程,理解需求就直接干。不确定的地方先问清楚再动手,先读后写,改完就收。问题排查思路开阔,前端报错不一定是前端问题——可能是后端数据、浏览器兼容、甚至设备硬件。解决方案实用接地气,用户描述有误敢于直接指出。`,
125
+ explore: `### 🧭 你的角色:技术探索伙伴
126
+ 你帮助用户澄清问题、调查代码库、比较方案和暴露风险。探索阶段不写实现代码,不安装依赖,不把讨论强行推进成开发。`
123
127
  }
124
128
 
125
129
  console.log(`---`)
@@ -252,6 +256,12 @@ export async function runCommand(args, cwd) {
252
256
  }
253
257
 
254
258
  async function runStage(pm, progress, stageName, cwd) {
259
+ // 自动探测 currentChange
260
+ if (autoDetectChange(progress, cwd)) {
261
+ progress.lastActive = new Date().toLocaleString('zh-CN', { hour12: false })
262
+ pm._write(cwd, progress)
263
+ }
264
+
255
265
  const stageData = progress.stages[stageName]
256
266
  if (!stageData || !stageData.steps) {
257
267
  console.error(`❌ 阶段 ${stageName} 未初始化`)
@@ -352,25 +362,26 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
352
362
  }
353
363
  }
354
364
 
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} 个任务蓝图步骤`)
365
+ // plan 阶段 "展开任务" 完成后,动态插入任务蓝图协调器步骤
366
+ if (stageName === 'plan' && steps[currentIdx]?.name === '展开任务并分组') {
367
+ const changeDir = resolveChangeDir(cwd, progress)
368
+ if (changeDir) {
369
+ const planFile = join(changeDir, 'plan.md')
370
+ if (existsSync(planFile)) {
371
+ const planContent = readFileSync(planFile, 'utf8')
372
+ const { buildPlanSteps, fixedPrefix, fixedSuffix } = await import('./stages/plan.js')
373
+ const fullSteps = buildPlanSteps(changeDir, planContent)
374
+ const prefixLen = fixedPrefix.length
375
+ const suffixLen = fixedSuffix.length
376
+ // 提取协调器步骤(prefix suffix 之间)
377
+ const coordinatorSteps = fullSteps.slice(prefixLen, suffixLen > 0 ? -suffixLen : undefined)
378
+ if (coordinatorSteps.length > 0) {
379
+ // 在当前步骤之后插入协调器步骤
380
+ for (let i = 0; i < coordinatorSteps.length; i++) {
381
+ steps.splice(currentIdx + 1 + i, 0, { name: coordinatorSteps[i].name, status: 'pending' })
382
+ }
383
+ console.log(` 📝 已动态插入 ${coordinatorSteps.length} 个任务蓝图步骤(${coordinatorSteps.map(s => s.name).join(', ')})`)
384
+ }
374
385
  }
375
386
  }
376
387
  }
@@ -31,7 +31,7 @@ export const definition = {
31
31
  2. 加载项目信息:\`cat .sillyspec/projects/*.yaml 2>/dev/null\`
32
32
  3. 加载本地配置:\`cat .sillyspec/local.yaml 2>/dev/null\`
33
33
  4. 询问本次需求属于哪个子项目
34
- 5. 棕地项目:读取 docs/<project>/scan/ 下的 STRUCTURE.md、CONVENTIONS.md、ARCHITECTURE.md
34
+ 5. 棕地项目:读取 .sillyspec/docs/<project>/scan/ 下的 STRUCTURE.md、CONVENTIONS.md、ARCHITECTURE.md
35
35
  6. 查看进行中的变更:\`ls .sillyspec/changes/ | grep -v archive\`
36
36
 
37
37
  ### 输出
@@ -173,22 +173,47 @@ export const definition = {
173
173
  2. 复杂项目:每段 200-300 字,逐段展示
174
174
  3. 每段展示后等待用户确认
175
175
  4. 收集修改意见,调整设计
176
+ 5. 确认设计后,确定变更名(格式:\`YYYY-MM-DD-<简短描述>\`,例如 \`2026-05-13-user-auth\`)
176
177
 
177
178
  ### 输出
178
- 用户确认的完整设计方案
179
+ 用户确认的完整设计方案 + 变更名
179
180
 
180
181
  ### 注意
181
182
  - 不要一次输出大段文字
182
- - 逐段确认,确保用户跟上`,
183
+ - 逐段确认,确保用户跟上
184
+ - 变更名必须以当天日期开头(YYYY-MM-DD-),后跟英文短横线分隔的简短描述`,
183
185
  outputHint: '用户确认的设计方案',
184
186
  optional: false
185
187
  },
188
+ {
189
+ name: 'HTML 原型生成',
190
+ prompt: `为设计方案生成可交互的 HTML 原型,帮助用户可视化确认。
191
+
192
+ ### 操作
193
+ 1. 判断本次设计是否适合生成 HTML 原型:
194
+ - 适合:有 UI 组件/布局/交互流程/状态转换/架构图
195
+ - 不适合:纯后端逻辑/配置修改/无可视化意义
196
+ 2. 如果适合,生成一个独立的 HTML 文件(内联 CSS + JS),保存到:
197
+ \`.sillyspec/changes/<变更名>/prototype-<名称>.html\`(变更名格式:YYYY-MM-DD-<简短描述>)
198
+ 3. 原型要求:
199
+ - 单文件,浏览器直接打开
200
+ - 展示关键布局结构和交互流程
201
+ - 不需要完整功能,重点是让用户确认设计方向
202
+ - 使用 ASCII/流程图/线框图风格,不需要精美 UI
203
+ 4. 展示给用户确认设计方向
204
+
205
+ ### 输出
206
+ HTML 原型文件路径(或"跳过"如果不适合)`,
207
+ outputHint: '原型文件路径或跳过',
208
+ optional: true
209
+ },
186
210
  {
187
211
  name: '写设计文档并自审',
188
212
  prompt: `撰写 design 文档并进行 AI 自审。
189
213
 
190
214
  ### 操作
191
215
  1. 确认变更目录存在:\`mkdir -p .sillyspec/changes/<变更名>\`(Windows 用 \`mkdir .sillyspec\\changes\\<变更名>\` 或 PowerShell \`New-Item -ItemType Directory -Force -Path .sillyspec/changes/<变更名>\`)
216
+ - 变更名格式必须为 \`YYYY-MM-DD-<简短描述>\`(如 \`2026-05-13-user-auth\`)
192
217
  2. 将确认的设计写入 \`.sillyspec/changes/<变更名>/design.md\`
193
218
  3. 自审检查:
194
219
  - 需求覆盖:是否完整覆盖 Step 6 确认的需求
@@ -168,38 +168,67 @@ function parseWavesFromPlan(planContent) {
168
168
  }
169
169
 
170
170
  /**
171
- * 为 Wave 生成 prompt
171
+ * 为 Wave 生成 prompt(强制子代理执行)
172
172
  */
173
173
  function buildWavePrompt(wave, waveIndex, changeDir) {
174
- const taskList = wave.tasks.map((t, ti) => {
174
+ // 构建子代理 prompt 模板(每个任务的蓝图内容)
175
+ const subagentTemplates = wave.tasks.map((t, ti) => {
175
176
  const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
176
177
  const taskFile = changeDir ? `${changeDir}/tasks/task-${taskNum}.md` : ''
177
178
  const taskFileExists = taskFile && existsSync(taskFile)
178
- let s = `- [ ] ${t.name}`
179
- if (t.file) s += ` (${t.file})`
179
+ let taskContent = ''
180
180
  if (taskFileExists) {
181
- let taskContent = ''
182
181
  try { taskContent = readFileSync(taskFile, 'utf8').trim() } catch { taskContent = '(无法读取任务蓝图文件)' }
183
- s += `
184
- \n### 📋 任务蓝图(task-${taskNum}.md)\n${taskContent}`
185
182
  }
186
- if (t.reference) s += `\n 参考: ${t.reference}`
187
- if (t.steps) s += `\n 步骤: ${t.steps}`
183
+ const fileInfo = t.file ? ` (${t.file})` : ''
184
+ return `\`\`\`
185
+ 任务:${t.name}${fileInfo}
186
+ ${taskContent ? `蓝图内容:\n${taskContent}` : '(无蓝图文件,按任务描述执行)'}
187
+
188
+ 操作:
189
+ 1. 先读后写 — 读取要修改的文件,理解现有结构
190
+ 2. 按 TDD 步骤实现
191
+ 3. 运行对应测试确认通过
192
+ 4. 报告改动文件和测试结果
193
+
194
+ 铁律:
195
+ - 只做蓝图/任务描述里写的事,不增不减
196
+ - 蓝图有问题 → 报告问题,不要自己改
197
+ - 先写测试,再写代码
198
+ - grep 确认方法存在,不要编造
199
+ \`\`\``
200
+ }).join('\n\n')
201
+
202
+ const taskList = wave.tasks.map((t, ti) => {
203
+ const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
204
+ let s = `- [ ] ${t.name}`
205
+ if (t.file) s += ` (${t.file})`
188
206
  return s
189
207
  }).join('\n')
190
- const { join } = path
191
- const hasTaskBlueprints = changeDir && existsSync(join(changeDir, 'tasks'))
192
- const taskBlueprintRule = hasTaskBlueprints
193
- ? '每个任务有独立的 task-N.md 蓝图——只做蓝图里写的事,不要实现蓝图之外的功能。如果蓝图有问题,**停下来反馈**,不要自己改。问题归因:实现困难 → task 蓝图没写好 → plan 没做好 → design 有缺陷。'
194
- : '如果发现 plan 不合理,**停下来反馈**,不要自己改方案。问题归因:实现困难 → plan 没做好 → design 有缺陷。'
208
+
195
209
  return `## Wave ${waveIndex}: 执行以下任务
196
210
 
211
+ ## 执行方式(必须严格遵守)
212
+
213
+ **每个任务必须由独立子代理执行,你不要自己写代码。**
214
+
215
+ 你的角色是调度者 + 审查者:
216
+ 1. 为每个任务启动一个子代理(Agent tool),同 Wave 内可并行
217
+ 2. 子代理完成后审查结果
218
+ 3. 勾选 plan.md 中的 checkbox
219
+ 4. 记录改动文件和测试结果
220
+
221
+ ### 子代理 prompt 模板
222
+ 为每个任务使用以下 prompt 启动子代理:
223
+
224
+ ${subagentTemplates}
225
+
197
226
  ### Wave 开始前
198
227
  1. 读取 design.md 的「编码铁律」章节(如果存在),严格遵守
199
228
  2. 读取 plan.md 了解全局任务划分和依赖关系
200
- 2. 确认本 Wave 的输入/输出契约(前置 Wave 产出了什么,本 Wave 需要消费什么)
201
- 3. 检查前置 Wave 的产出是否完整(文件是否存在、测试是否通过)
202
- 4. **上下文分层加载**:
229
+ 3. 确认本 Wave 的输入/输出契约(前置 Wave 产出了什么,本 Wave 需要消费什么)
230
+ 4. 检查前置 Wave 的产出是否完整(文件是否存在、测试是否通过)
231
+ 5. **上下文分层加载**:
203
232
  - 🔥 热上下文:design.md 编码铁律 + 当前 Wave 任务(必须加载)
204
233
  - 🌡️ 温上下文:CONVENTIONS.md + ARCHITECTURE.md(需要时加载)
205
234
  - ❄️ 冷上下文:其他变更的 design.md、历史 plan.md(不要主动加载,除非明确需要)
@@ -207,21 +236,17 @@ const hasTaskBlueprints = changeDir && existsSync(join(changeDir, 'tasks'))
207
236
  ### 本 Wave 任务
208
237
  ${taskList}
209
238
 
210
- ### 执行要求
211
- 1. 按任务顺序执行,同一 Wave 内任务可并行
212
- 2. 铁律:先读后写、grep 确认方法存在、不编造、TDD
213
- 3. **禁止发散思维**:你是代码搬运工,严格按任务描述执行,不增不减不改。${taskBlueprintRule}
214
- 4. **Reverse Sync**:发现 Bug 或实现与 design.md/task-N.md 不一致时,先检查是代码错了还是文档有遗漏,有遗漏则先修文档再修代码。
215
- 4. **不要频繁编译!** 编译很慢,只在以下情况运行:
239
+ ### 调度要求
240
+ 1. 同一 Wave 内任务可并行启动子代理
241
+ 2. **Reverse Sync**:子代理报告实现与 design.md 不一致时,先检查是代码错了还是文档有遗漏
242
+ 3. **不要频繁编译!** 编译很慢,只在以下情况运行:
216
243
  - 写了大量代码后需要验证语法正确性
217
244
  - 最后一个 Wave 完成后做一次全量编译验证
218
245
  - 用户明确要求编译时
219
- 5. 单个任务完成后只跑**对应模块的单元测试**(TDD 绿灯确认),不要跑全量编译
220
- 6. 每个任务完成后:
221
- - 勾选 task-N.md 中的验收标准 checkbox
246
+ 4. 每个任务完成后:
222
247
  - 勾选 plan.md / tasks.md 中对应任务的 checkbox
223
248
  - 记录改动文件和测试结果
224
- 7. 遇到 BLOCKED → 记录原因,选择:重试/跳过/停止
249
+ 5. 遇到 BLOCKED → 记录原因,选择:重试/跳过/停止
225
250
 
226
251
  ### 完成后
227
252
  运行 sillyspec run execute --done --input "用户原始反馈" --output "Wave ${waveIndex} 结果摘要"`
@@ -0,0 +1,34 @@
1
+ export const definition = {
2
+ name: 'explore',
3
+ title: '自由探索',
4
+ description: '讨论、调研、画图,不写实现代码',
5
+ auxiliary: true,
6
+ steps: [
7
+ {
8
+ name: '自由探索',
9
+ prompt: `围绕用户给出的话题做技术探索,不进入实现。
10
+
11
+ ### 操作
12
+ 1. 明确探索边界:这次只讨论、调研、画图和识别风险
13
+ 2. 如果需要代码库上下文,可以读取:
14
+ - \`.sillyspec/projects/*.yaml\`
15
+ - \`.sillyspec/docs/<project>/scan/ARCHITECTURE.md\`
16
+ - \`.sillyspec/docs/<project>/scan/CONVENTIONS.md\`
17
+ - \`.sillyspec/changes/<change-name>/design.md\`
18
+ 3. 可以用 \`rg\` / \`ls\` / \`cat\` 调查已有结构和集成点
19
+ 4. 输出 2-3 个有价值方向、关键风险和下一步建议
20
+ 5. 如果用户要求保存结论,先明确保存位置,再写入对应文档
21
+
22
+ ### 输出
23
+ 探索结论、选项对比、风险清单或 ASCII 图
24
+
25
+ ### 铁律
26
+ - 不写实现代码
27
+ - 不安装依赖
28
+ - 不修改文件,除非用户明确要求保存探索结论
29
+ - 不强行推进到 brainstorm/plan/execute`,
30
+ outputHint: '探索结论',
31
+ optional: false
32
+ }
33
+ ]
34
+ }
@@ -5,6 +5,7 @@ import { definition as execute } from './execute.js'
5
5
  import { definition as verify } from './verify.js'
6
6
  import { definition as scan } from './scan.js'
7
7
  import { definition as quick } from './quick.js'
8
+ import { definition as explore } from './explore.js'
8
9
  import { definition as archive } from './archive.js'
9
10
  import { definition as status } from './status.js'
10
11
  import { definition as doctor } from './doctor.js'
@@ -17,10 +18,11 @@ export const stageRegistry = {
17
18
  verify,
18
19
  scan,
19
20
  quick,
21
+ explore,
20
22
  archive,
21
23
  status,
22
24
  doctor
23
25
  }
24
26
 
25
27
  // 辅助命令(在没有 progress.json 时也可执行)
26
- export const auxiliaryStages = ['scan', 'quick', 'archive', 'status', 'doctor']
28
+ export const auxiliaryStages = ['scan', 'quick', 'explore', 'archive', 'status', 'doctor']
@@ -218,6 +218,88 @@ ${taskName}
218
218
  任务蓝图内容摘要`
219
219
  }
220
220
 
221
+ /**
222
+ * 构建任务蓝图协调器步骤(单步,子代理并行写蓝图)
223
+ */
224
+ export function buildCoordinatorStep(changeDir, taskNames) {
225
+ const taskList = taskNames.map((name, i) => {
226
+ const num = String(i + 1).padStart(2, '0')
227
+ return `- task-${num}: ${name}`
228
+ }).join('\n')
229
+
230
+ const subagentPrompts = taskNames.map((name, i) => {
231
+ const num = String(i + 1).padStart(2, '0')
232
+ return `\`\`\`
233
+ 任务编号:task-${num}
234
+ 任务名称:${name}
235
+ 文件路径:${changeDir}/tasks/task-${num}.md
236
+
237
+ 操作:
238
+ 1. 读取 ${changeDir}/design.md 和 ${changeDir}/plan.md 了解上下文
239
+ 2. 读取相关源文件了解现有代码
240
+ 3. 按以下格式编写任务蓝图并保存到 ${changeDir}/tasks/task-${num}.md:
241
+
242
+ # task-${num}: ${name}
243
+
244
+ ## 修改文件
245
+ - 文件路径列表
246
+
247
+ ## 实现要求
248
+ 1. 具体做什么
249
+
250
+ ## 接口定义
251
+ (代码类任务必填)
252
+
253
+ ## 边界处理
254
+ - 异常场景
255
+
256
+ ## 参考
257
+ - 可参考的模式
258
+
259
+ ## TDD 步骤
260
+ 1. 写测试 → 2. 确认失败 → 3. 写代码 → 4. 确认通过
261
+
262
+ ## 验收标准
263
+ - [ ] 具体可测试的条件
264
+
265
+ 关键规则:
266
+ - 必须独立完整,execute 子代理只读这一个文件就能干活
267
+ - 不要依赖其他 task-N.md 的内容
268
+ - 接口定义写到"搬砖工照着做"的程度
269
+ - 写完后用 Write tool 保存到文件
270
+ \`\`\``
271
+ }).join('\n\n')
272
+
273
+ return {
274
+ name: '生成任务蓝图(子代理并行)',
275
+ prompt: `为 plan.md 中的每个任务生成独立蓝图文件。
276
+
277
+ ## 任务清单
278
+ ${taskList}
279
+
280
+ ## 执行方式(必须严格遵守)
281
+
282
+ **你必须使用 Agent tool 启动子代理来写每个蓝图,不要自己写。**
283
+
284
+ 1. 确认 \`${changeDir}/tasks/\` 目录存在(不存在则创建)
285
+ 2. 为每个任务启动一个独立子代理(Agent tool),可并行启动多个
286
+ 3. 每个子代理使用对应的 prompt(见下方模板)
287
+ 4. 等待所有子代理完成
288
+ 5. 验证每个 task-N.md 文件已生成且非空
289
+
290
+ ### 子代理 prompt 模板
291
+ 为每个任务使用以下 prompt 启动子代理:
292
+
293
+ ${subagentPrompts}
294
+
295
+ ## 验收
296
+ - 每个 task-N.md 文件存在且非空
297
+ - 包含所有必要章节:修改文件、实现要求、接口定义、边界处理、TDD 步骤、验收标准`,
298
+ outputHint: '蓝图生成结果',
299
+ optional: false
300
+ }
301
+ }
302
+
221
303
  /**
222
304
  * 从 plan.md 解析任务名列表
223
305
  */
@@ -255,8 +337,7 @@ export function buildPlanSteps(changeDir = null, planContent = null) {
255
337
  return [...fixedPrefix, ...fixedSuffix]
256
338
  }
257
339
 
258
- // 动态生成每个任务的蓝图写作步骤
259
- // 尝试从 plan.md 解析任务名
340
+ // 解析任务名
260
341
  let taskNames = []
261
342
  if (planContent) {
262
343
  taskNames = parseTaskNames(planContent)
@@ -267,16 +348,7 @@ export function buildPlanSteps(changeDir = null, planContent = null) {
267
348
  }
268
349
  }
269
350
 
270
- const taskSteps = []
271
- for (let i = 1; i <= taskCount; i++) {
272
- const taskName = taskNames[i - 1] || '(从 plan.md 读取任务名)'
273
- taskSteps.push({
274
- name: `写任务蓝图 task-${String(i).padStart(2, '0')}`,
275
- prompt: `### 注意\n这是第 ${i}/${taskCount} 个任务蓝图。focus 在这一个任务上,不要写其他任务的内容。\n\n${buildTaskPrompt(i, taskName, changeDir)}`,
276
- outputHint: `task-${String(i).padStart(2, '0')} 蓝图`,
277
- optional: false
278
- })
279
- }
280
-
281
- return [...fixedPrefix, ...taskSteps, ...fixedSuffix]
351
+ // 生成单个协调器步骤(子代理并行写蓝图)
352
+ const coordinatorStep = buildCoordinatorStep(changeDir, taskNames)
353
+ return [...fixedPrefix, coordinatorStep, ...fixedSuffix]
282
354
  }
@@ -10,8 +10,8 @@ export const definition = {
10
10
 
11
11
  ### 操作
12
12
  1. \`ls .sillyspec/projects/*.yaml 2>/dev/null | grep -q .\` — 检查已有文档
13
- 1. \`ls docs/*/scan/ 2>/dev/null\` — 检查已有文档
14
- 2. \`wc -l docs/*/scan/*.md 2>/dev/null\` — 文档行数
13
+ 1. \`ls .sillyspec/docs/*/scan/ 2>/dev/null\` — 检查已有文档
14
+ 2. \`wc -l .sillyspec/docs/*/scan/*.md 2>/dev/null\` — 文档行数
15
15
  3. 已有 3 份 → 建议升级深度扫描;已有 7 份 → 建议刷新或跳过
16
16
  5. 显示子项目列表供选择扫描范围
17
17
 
@@ -27,7 +27,7 @@ export const definition = {
27
27
  ### 操作
28
28
  1. \`cat package.json pom.xml build.gradle go.mod Cargo.toml requirements.txt pyproject.toml Gemfile composer.json 2>/dev/null\`
29
29
  2. \`find . -maxdepth 2 -name "*.config.*" -not -path "*/node_modules/*" -not -path "*/.git/*" | head -20 | xargs cat 2>/dev/null\`
30
- 3. 结果保存到 \`docs/<project>/scan/_env-detect.md\`(临时文件,扫描完删除)
30
+ 3. 结果保存到 \`.sillyspec/docs/<project>/scan/_env-detect.md\`(临时文件,扫描完删除)
31
31
 
32
32
  ### 输出
33
33
  环境探测结果摘要`,
@@ -56,7 +56,7 @@ export const definition = {
56
56
  ### 操作
57
57
  1. 用 grep/rg 搜索(\`@Entity\`、\`schema.prisma\`、\`models.py\` 等),**禁止读源码全文**
58
58
  2. Schema 只记表名+说明+字段数
59
- 3. 写入 \`docs/<project>/scan/ARCHITECTURE.md\`
59
+ 3. 写入 \`.sillyspec/docs/<project>/scan/ARCHITECTURE.md\`
60
60
  4. 包含 \`## 技术栈\` \`## 架构概览\` \`## 数据模型(摘要)\`
61
61
 
62
62
  ### 输出
@@ -75,7 +75,7 @@ ARCHITECTURE.md 路径
75
75
  1. 用 grep 搜索拦截器/插件/逻辑删除/基类/审计字段,**禁止读源码全文**
76
76
  2. 根据检测到的语言/框架自行决定搜索什么模式
77
77
  3. 提取 3-5 个典型示例
78
- 4. 写入 \`docs/<project>/scan/CONVENTIONS.md\`
78
+ 4. 写入 \`.sillyspec/docs/<project>/scan/CONVENTIONS.md\`
79
79
  5. 包含 \`## 框架隐形规则\` \`## 实体继承规范\` \`## 代码风格\`
80
80
 
81
81
  ### 输出
@@ -93,8 +93,8 @@ CONVENTIONS.md 路径
93
93
  ### 操作
94
94
  1. 用 find/ls/tree 和 grep,**禁止读源码全文**
95
95
  2. 搜索 API 调用、MQ 配置、缓存、第三方 SDK
96
- 3. 写入 \`docs/<project>/scan/STRUCTURE.md\`(目录树+模块说明)
97
- 4. 写入 \`docs/<project>/scan/INTEGRATIONS.md\`(按类型分组)
96
+ 3. 写入 \`.sillyspec/docs/<project>/scan/STRUCTURE.md\`(目录树+模块说明)
97
+ 4. 写入 \`.sillyspec/docs/<project>/scan/INTEGRATIONS.md\`(按类型分组)
98
98
 
99
99
  ### 输出
100
100
  STRUCTURE.md 和 INTEGRATIONS.md 路径
@@ -110,9 +110,9 @@ STRUCTURE.md 和 INTEGRATIONS.md 路径
110
110
 
111
111
  ### 操作
112
112
  1. 用 grep 搜索测试文件、TODO/FIXME、过时依赖,**禁止读源码全文**
113
- 2. 写入 \`docs/<project>/scan/TESTING.md\`(测试结构)
114
- 3. 写入 \`docs/<project>/scan/CONCERNS.md\`(按严重程度分组)
115
- 4. 写入 \`docs/<project>/scan/PROJECT.md\`(项目信息)
113
+ 2. 写入 \`.sillyspec/docs/<project>/scan/TESTING.md\`(测试结构)
114
+ 3. 写入 \`.sillyspec/docs/<project>/scan/CONCERNS.md\`(按严重程度分组)
115
+ 4. 写入 \`.sillyspec/docs/<project>/scan/PROJECT.md\`(项目信息)
116
116
 
117
117
  ### 输出
118
118
  TESTING.md、CONCERNS.md、PROJECT.md 路径`,
@@ -126,7 +126,7 @@ TESTING.md、CONCERNS.md、PROJECT.md 路径`,
126
126
  ### 操作
127
127
  1. 检查 7 份文档是否全部生成
128
128
  2. 自检门控:ARCHITECTURE(技术栈+Schema摘要)、CONVENTIONS(隐形规则+代码风格)、STRUCTURE(目录结构)、INTEGRATIONS(外部依赖)、TESTING(测试现状)、CONCERNS(技术债务)、PROJECT(项目概览)
129
- 3. 清理:\`rm -f docs/<project>/scan/_env-detect.md\`
129
+ 3. 清理:\`rm -f .sillyspec/docs/<project>/scan/_env-detect.md\`
130
130
  4. \`git add .\` — **不要 commit**,由用户通过统一提交工具处理
131
131
 
132
132
  ### 输出
@@ -11,7 +11,7 @@ export const definition = {
11
11
  ### 操作
12
12
  1. \`cat .sillyspec/PROJECT.md 2>/dev/null || echo "未初始化"\`
13
13
  2. 获取 project 名
14
- 3. \`ls docs/<project>/scan/ 2>/dev/null | head -10\`
14
+ 3. \`ls .sillyspec/docs/<project>/scan/ 2>/dev/null | head -10\`
15
15
  4. \`cat .sillyspec/REQUIREMENTS.md 2>/dev/null | head -20\`
16
16
  5. \`cat .sillyspec/ROADMAP.md 2>/dev/null\`
17
17