sillyspec 3.9.1 → 3.10.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 (69) 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-resume/SKILL.md +5 -5
  6. package/.claude/skills/sillyspec-state/SKILL.md +1 -1
  7. package/.claude/skills/sillyspec-workspace/SKILL.md +1 -1
  8. package/README.md +7 -6
  9. package/SKILL.md +15 -10
  10. package/package.json +1 -1
  11. package/packages/dashboard/dist/assets/index-BcM2J-hv.css +1 -0
  12. package/packages/dashboard/dist/assets/{index-RsLVPAy7.js → index-DpLHK4jv.js} +974 -974
  13. package/packages/dashboard/dist/index.html +16 -17
  14. package/packages/dashboard/dist/prototype-dashboard.html +836 -0
  15. package/packages/dashboard/dist/prototype-overview.html +256 -0
  16. package/packages/dashboard/public/prototype-dashboard.html +836 -0
  17. package/packages/dashboard/public/prototype-overview.html +256 -0
  18. package/packages/dashboard/server/index.js +18 -13
  19. package/packages/dashboard/server/parser.js +109 -1
  20. package/packages/dashboard/server/watcher.js +14 -6
  21. package/packages/dashboard/src/App.vue +414 -186
  22. package/packages/dashboard/src/components/ActionBar.vue +10 -1
  23. package/packages/dashboard/src/components/CommandPalette.vue +5 -1
  24. package/packages/dashboard/src/components/DocPreview.vue +105 -8
  25. package/packages/dashboard/src/components/DocTree.vue +75 -19
  26. package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
  27. package/packages/dashboard/src/components/PipelineView.vue +23 -4
  28. package/packages/dashboard/src/components/ProjectCard.vue +187 -0
  29. package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
  30. package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
  31. package/packages/dashboard/src/composables/useDashboard.js +28 -0
  32. package/packages/dashboard/src/composables/useLayout.js +131 -0
  33. package/src/index.js +7 -0
  34. package/src/init.js +17 -10
  35. package/src/migrate.js +5 -5
  36. package/src/progress.js +2 -1
  37. package/src/run.js +141 -64
  38. package/src/stages/archive.js +14 -8
  39. package/src/stages/brainstorm.js +29 -4
  40. package/src/stages/execute.js +115 -35
  41. package/src/stages/explore.js +34 -0
  42. package/src/stages/index.js +11 -6
  43. package/src/stages/plan.js +86 -15
  44. package/src/stages/quick.js +1 -1
  45. package/src/stages/scan.js +51 -12
  46. package/src/stages/status.js +1 -1
  47. package/src/stages/verify.js +35 -6
  48. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
  49. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
  50. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
  51. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
  52. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
  53. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
  54. package/.sillyspec/changes/auto-mode/design.md +0 -50
  55. package/.sillyspec/changes/auto-mode/proposal.md +0 -19
  56. package/.sillyspec/changes/auto-mode/requirements.md +0 -21
  57. package/.sillyspec/changes/auto-mode/tasks.md +0 -7
  58. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
  59. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
  60. package/.sillyspec/changes/dashboard/design.md +0 -219
  61. package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
  62. package/.sillyspec/changes/run-command-design/design.md +0 -1230
  63. package/.sillyspec/changes/unified-docs-design/design.md +0 -199
  64. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  65. package/.sillyspec/knowledge/INDEX.md +0 -8
  66. package/.sillyspec/knowledge/uncategorized.md +0 -3
  67. package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
  68. package/.sillyspec/projects/sillyspec.yaml +0 -3
  69. 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(`---`)
@@ -170,6 +174,7 @@ export async function runCommand(args, cwd) {
170
174
  const isSkip = flags.includes('--skip')
171
175
  const isStatus = flags.includes('--status')
172
176
  const isReset = flags.includes('--reset')
177
+ const isConfirm = flags.includes('--confirm')
173
178
 
174
179
  // 解析 --output
175
180
  let outputText = null
@@ -244,7 +249,7 @@ export async function runCommand(args, cwd) {
244
249
 
245
250
  // --done
246
251
  if (isDone) {
247
- return await completeStep(pm, progress, stageName, cwd, outputText, inputText)
252
+ return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm })
248
253
  }
249
254
 
250
255
  // 默认:输出当前步骤
@@ -252,6 +257,12 @@ export async function runCommand(args, cwd) {
252
257
  }
253
258
 
254
259
  async function runStage(pm, progress, stageName, cwd) {
260
+ // 自动探测 currentChange
261
+ if (autoDetectChange(progress, cwd)) {
262
+ progress.lastActive = new Date().toLocaleString('zh-CN', { hour12: false })
263
+ pm._write(cwd, progress)
264
+ }
265
+
255
266
  const stageData = progress.stages[stageName]
256
267
  if (!stageData || !stageData.steps) {
257
268
  console.error(`❌ 阶段 ${stageName} 未初始化`)
@@ -319,7 +330,7 @@ function validateMetadata(cwd, stageName) {
319
330
  }
320
331
 
321
332
  async function completeStep(pm, progress, stageName, cwd, outputText, inputText = null, options = {}) {
322
- const { printNext = true } = options
333
+ const { printNext = true, confirm = false } = options
323
334
  const stageData = progress.stages[stageName]
324
335
  if (!stageData || !stageData.steps) {
325
336
  console.error(`❌ 阶段 ${stageName} 未初始化`)
@@ -352,25 +363,26 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
352
363
  }
353
364
  }
354
365
 
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} 个任务蓝图步骤`)
366
+ // plan 阶段 "展开任务" 完成后,动态插入任务蓝图协调器步骤
367
+ if (stageName === 'plan' && steps[currentIdx]?.name === '展开任务并分组') {
368
+ const changeDir = resolveChangeDir(cwd, progress)
369
+ if (changeDir) {
370
+ const planFile = join(changeDir, 'plan.md')
371
+ if (existsSync(planFile)) {
372
+ const planContent = readFileSync(planFile, 'utf8')
373
+ const { buildPlanSteps, fixedPrefix, fixedSuffix } = await import('./stages/plan.js')
374
+ const fullSteps = buildPlanSteps(changeDir, planContent)
375
+ const prefixLen = fixedPrefix.length
376
+ const suffixLen = fixedSuffix.length
377
+ // 提取协调器步骤(prefix suffix 之间)
378
+ const coordinatorSteps = fullSteps.slice(prefixLen, suffixLen > 0 ? -suffixLen : undefined)
379
+ if (coordinatorSteps.length > 0) {
380
+ // 在当前步骤之后插入协调器步骤
381
+ for (let i = 0; i < coordinatorSteps.length; i++) {
382
+ steps.splice(currentIdx + 1 + i, 0, { name: coordinatorSteps[i].name, status: 'pending' })
383
+ }
384
+ console.log(` 📝 已动态插入 ${coordinatorSteps.length} 个任务蓝图步骤(${coordinatorSteps.map(s => s.name).join(', ')})`)
385
+ }
374
386
  }
375
387
  }
376
388
  }
@@ -394,9 +406,74 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
394
406
  // 验证:检查生成的文件是否包含 author 和 created_at
395
407
  validateMetadata(cwd, stageName)
396
408
 
409
+ // archive 阶段 Step 2(确认归档)完成时自动执行归档移动
410
+ if (stageName === 'archive' && steps[currentIdx]?.name === '确认归档') {
411
+ if (confirm) {
412
+ const { renameSync } = await import('fs')
413
+ const changeName = progress.currentChange
414
+ if (!changeName) {
415
+ console.error('❌ 归档失败:未找到当前变更名(currentChange)')
416
+ process.exit(1)
417
+ }
418
+ const changesDir = join(cwd, '.sillyspec', 'changes')
419
+ const archiveDir = join(changesDir, 'archive')
420
+ const srcDir = join(changesDir, changeName)
421
+ const date = new Date().toISOString().slice(0, 10)
422
+ const destDir = join(archiveDir, `${date}-${changeName}`)
423
+
424
+ if (!existsSync(srcDir)) {
425
+ console.error(`❌ 归档失败:源目录不存在 ${srcDir}`)
426
+ process.exit(1)
427
+ }
428
+ if (existsSync(destDir)) {
429
+ console.error(`❌ 归档失败:目标目录已存在 ${destDir}`)
430
+ process.exit(1)
431
+ }
432
+ mkdirSync(archiveDir, { recursive: true })
433
+ renameSync(srcDir, destDir)
434
+
435
+ // 校验
436
+ if (!existsSync(destDir) || existsSync(srcDir)) {
437
+ console.error('❌ 归档校验失败:移动操作异常')
438
+ process.exit(1)
439
+ }
440
+
441
+ // 清除 currentChange
442
+ progress.currentChange = null
443
+ console.log(`📦 已归档:${changeName} → archive/${date}-${changeName}/`)
444
+ } else {
445
+ console.log('⚠️ 请添加 --confirm 确认归档,例如:sillyspec run archive --done --confirm --output "确认归档"')
446
+ }
447
+ }
448
+
449
+ // 辅助阶段(archive/scan/quick 等)完成后重置步骤,允许重复执行
450
+ const stageDef = stageRegistry[stageName]
451
+ if (stageDef?.auxiliary) {
452
+ const freshSteps = (stageDef.steps || []).map(s => ({
453
+ name: s.name,
454
+ status: 'pending',
455
+ output: null,
456
+ completedAt: null
457
+ }))
458
+ stageData.steps = freshSteps
459
+ stageData.status = 'pending'
460
+ stageData.completedAt = null
461
+ pm._write(cwd, progress)
462
+ }
463
+
397
464
  const total = steps.length
398
465
  console.log(`✅ ${stageName} 阶段已完成(${total}/${total} 步)`)
399
- console.log(`\n下一步由你决定:sillyspec run <stage>(brainstorm/plan/execute/verify/archive 等)`)
466
+
467
+ // 阶段完成后提示下一步
468
+ if (stageName === 'execute') {
469
+ console.log('\n👉 下一步:sillyspec run verify(验证通过后才能归档)')
470
+ } else if (stageName === 'verify') {
471
+ console.log('\n👉 下一步:sillyspec run archive(验证通过,可以归档了)')
472
+ } else if (stageName === 'archive') {
473
+ console.log('\n👉 归档完成!现在可以提交了:git commit -m "..."')
474
+ } else {
475
+ console.log(`\n下一步由你决定:sillyspec run <stage>(brainstorm/plan/execute/verify/archive 等)`)
476
+ }
400
477
  return { stageCompleted: true, currentIdx, nextPendingIdx: -1 }
401
478
  }
402
479
 
@@ -2,19 +2,19 @@ export const definition = {
2
2
  name: 'archive',
3
3
  title: '归档变更',
4
4
  description: '规范沉淀,可追溯',
5
- auxiliary: true,
6
5
  steps: [
7
6
  {
8
7
  name: '任务完成度检查',
9
- prompt: `检查 tasks.md 中所有 checkbox 是否已勾选。
8
+ prompt: `检查 plan.md 中所有任务 checkbox 是否已勾选(plan.md 是任务完成的唯一真相源)。
10
9
 
11
10
  ### 操作
12
- 1. 读取 \`.sillyspec/changes/<change-name>/tasks.md\`
13
- 2. 检查所有 checkbox 是否已勾选
14
- 3. 如有遗漏 询问用户是否继续归档
11
+ 1. 读取 \`.sillyspec/changes/<change-name>/plan.md\`
12
+ 2. 检查所有 \`- [x]\` checkbox 是否已勾选
13
+ 3. 如果 plan.md 不存在,回退读取 tasks.md 作为备选
14
+ 4. 如有遗漏 → 询问用户是否继续归档
15
15
 
16
16
  ### 输出
17
- 完成度报告`,
17
+ 完成度报告(已勾选/总数 + 未完成任务列表)`,
18
18
  outputHint: '完成度报告',
19
19
  optional: false
20
20
  },
@@ -25,9 +25,15 @@ export const definition = {
25
25
  ### 操作
26
26
  1. 展示:变更目录名、包含的文件列表、生成总结
27
27
  2. 请用户确认是否执行归档
28
- 3. 确认后:将 \`.sillyspec/changes/<change-name>/\` 移动到 \`.sillyspec/changes/archive/YYYY-MM-DD-<change-name>/\`
28
+ 3. 确认后:将 \.sillyspec/changes/<change-name>/ 移动到 \.sillyspec/changes/archive/YYYY-MM-DD-<change-name>/
29
29
  4. 确保所有 checkbox 都已勾选
30
30
 
31
+ ### 归档执行
32
+ 确认归档后,执行以下命令自动完成目录移动:
33
+ \ \ sillyspec run archive --done --confirm --output "确认归档"
34
+ - \`--confirm\` 标志会自动执行目录移动(原子操作)
35
+ - 不带 \`--confirm\` 则只提示需要确认
36
+
31
37
  ### 输出
32
38
  归档确认`,
33
39
  outputHint: '归档确认',
@@ -39,7 +45,7 @@ export const definition = {
39
45
 
40
46
  ### 操作
41
47
  1. 如果 \`.sillyspec/ROADMAP.md\` 存在,标记对应 Phase 为已完成
42
- 2. \`git add .sillyspec/\` — **不要 commit**,由用户通过统一提交工具处理
48
+ 2. \`git add .sillyspec/changes/\` — 暂存归档结果(不要 commit,由用户通过统一提交工具处理)
43
49
  3. 更新 progress.json:
44
50
  - 清除当前变更信息(归档后不再活跃)
45
51
  - 如果是主变更(有 MASTER.md),标记所有阶段为 ✅,然后清除
@@ -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 确认的需求
@@ -220,7 +245,7 @@ design.md 文件路径 + 自审结果
220
245
  - **proposal.md**:动机、变更范围、不在范围内、成功标准
221
246
  - **requirements.md**:功能需求、用户场景(Given/When/Then 格式)、非功能需求
222
247
  - **tasks.md**:任务列表(只列名称和对应文件路径,细节在 plan 阶段展开)
223
- - \`git add .sillyspec/\` — **不要 commit**
248
+ - \`git add .sillyspec/\` — 暂存规范文件(不要 commit
224
249
 
225
250
  ### 输出
226
251
  所有规范文件路径
@@ -69,6 +69,73 @@ Wave 分组 + 模型分配 + 确认模式 + 知识库匹配结果
69
69
  }
70
70
  ]
71
71
 
72
+ // 全局验收步骤定义
73
+ const acceptanceSteps = [
74
+ {
75
+ name: '对照设计检查',
76
+ mode: 'acceptance',
77
+ prompt: `对照 design.md 检查所有实现是否与设计一致。
78
+
79
+ ### 执行方式
80
+ 本步骤由当前 agent 汇总执行,不需要为每个检查项启动独立子代理。
81
+ 如需深入验证某个模块,可启动单个 QA 子代理统一处理。
82
+
83
+ ### 操作
84
+ 1. 读取 design.md(技术方案)
85
+ 2. 逐一对照 design.md 中的设计要点与实际代码实现
86
+ 3. 检查接口签名、数据结构、模块划分是否一致
87
+ 4. 记录偏差项(偏差 ≠ 错误,可能是合理的实现调整)
88
+
89
+ ### 输出
90
+ 检查清单:每项设计要点的实现状态 ✅/⚠️/❌ + 偏差说明`,
91
+ outputHint: '设计对照检查清单',
92
+ optional: false
93
+ },
94
+ {
95
+ name: '运行测试',
96
+ mode: 'acceptance',
97
+ prompt: `运行所有测试,验证代码质量。
98
+
99
+ ### 执行方式
100
+ 本步骤由当前 agent 执行,不需要启动独立子代理。
101
+
102
+ ### 操作
103
+ 1. 读取 local.yaml 获取构建和测试命令
104
+ 2. 运行测试套件(单元测试、集成测试)
105
+ 3. 运行 lint 检查
106
+ 4. 如果有测试失败 → 分析原因,标注是代码问题还是测试本身的问题
107
+ 5. 汇总测试结果
108
+
109
+ ### 输出
110
+ 测试结果摘要:通过/失败/跳过数量 + 失败项分析`,
111
+ outputHint: '测试结果摘要',
112
+ optional: false
113
+ },
114
+ {
115
+ name: '代码审查',
116
+ mode: 'acceptance',
117
+ prompt: `对本次变更进行代码审查。
118
+
119
+ ### 执行方式
120
+ 本步骤由当前 agent 或一个 QA agent 汇总执行,不需要为每个文件启动独立子代理。
121
+
122
+ ### 操作
123
+ 1. 检查 git diff 查看所有变更
124
+ 2. 审查要点:
125
+ - 代码风格是否符合 CONVENTIONS.md
126
+ - 是否有明显的 bug 或安全漏洞
127
+ - 是否有未处理的 TODO/FIXME
128
+ - 错误处理是否完善
129
+ - 是否有冗余代码或可简化的逻辑
130
+ 3. 对照 ARCHITECTURE.md 检查架构合规性
131
+
132
+ ### 输出
133
+ 审查结果:问题列表(严重程度 + 建议修复方式)+ 总体评价`,
134
+ outputHint: '代码审查结果',
135
+ optional: true
136
+ }
137
+ ]
138
+
72
139
  // 固定后缀步骤定义
73
140
  const fixedSuffix = [
74
141
  {
@@ -90,11 +157,10 @@ const fixedSuffix = [
90
157
  prompt: `所有任务完成后的收尾。
91
158
 
92
159
  ### 操作
93
- 1. 询问用户下一步:
94
- - 验证 sillyspec run verify
95
- - 归档 → /sillyspec:archive
96
- - 继续开发
97
- 2. 提示 git add 暂存变更
160
+ 1. 建议下一步:
161
+ - 运行 \`sillyspec run verify\` 进行验证(验证通过后才能归档)
162
+ - 如果发现问题需要修复,先修复再验证
163
+ - 用户也可以选择继续开发(不验证)
98
164
 
99
165
  ### 输出
100
166
  用户选择 + 下一步命令
@@ -168,38 +234,54 @@ function parseWavesFromPlan(planContent) {
168
234
  }
169
235
 
170
236
  /**
171
- * 为 Wave 生成 prompt
237
+ * 为 Wave 生成 prompt(强制子代理执行)
172
238
  */
173
239
  function buildWavePrompt(wave, waveIndex, changeDir) {
240
+ // 构建任务摘要(不再内联完整蓝图,减少上下文污染)
241
+ const taskSummary = wave.tasks.map((t, ti) => {
242
+ const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
243
+ const taskRelPath = changeDir
244
+ ? `.sillyspec/changes/${path.basename(changeDir)}/tasks/task-${taskNum}.md`
245
+ : `task-${taskNum}.md`
246
+ const fileInfo = t.file ? ` (${t.file})` : ''
247
+ return `task-${taskNum}: ${t.name}${fileInfo} → ${taskRelPath}`
248
+ }).join('\n')
249
+
174
250
  const taskList = wave.tasks.map((t, ti) => {
175
251
  const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
176
- const taskFile = changeDir ? `${changeDir}/tasks/task-${taskNum}.md` : ''
177
- const taskFileExists = taskFile && existsSync(taskFile)
178
252
  let s = `- [ ] ${t.name}`
179
253
  if (t.file) s += ` (${t.file})`
180
- if (taskFileExists) {
181
- let taskContent = ''
182
- try { taskContent = readFileSync(taskFile, 'utf8').trim() } catch { taskContent = '(无法读取任务蓝图文件)' }
183
- s += `
184
- \n### 📋 任务蓝图(task-${taskNum}.md)\n${taskContent}`
185
- }
186
- if (t.reference) s += `\n 参考: ${t.reference}`
187
- if (t.steps) s += `\n 步骤: ${t.steps}`
188
254
  return s
189
255
  }).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 有缺陷。'
256
+
195
257
  return `## Wave ${waveIndex}: 执行以下任务
196
258
 
259
+ ## 执行方式(必须严格遵守)
260
+
261
+ **每个任务必须由独立子代理执行,你不要自己写代码。**
262
+
263
+ 你的角色是调度者 + 审查者:
264
+ 1. 为每个任务启动一个子代理(Agent tool),同 Wave 内可并行
265
+ 2. 子代理完成后审查结果
266
+ 3. 勾选 plan.md 中的 checkbox
267
+ 4. 记录改动文件和测试结果
268
+
269
+ ### 任务摘要(按需读取完整蓝图)
270
+ 为每个任务启动子代理时,**只需告知任务目标和蓝图文件路径,让子代理按需读取**:
271
+
272
+ ${taskSummary}
273
+
274
+ 子代理 prompt 要点:
275
+ 1. 任务目标(简短描述)
276
+ 2. 蓝图文件路径(让子代理自行读取详情)
277
+ 3. 编码铁律:先读后写、TDD、不编造方法、只做蓝图里写的事
278
+
197
279
  ### Wave 开始前
198
280
  1. 读取 design.md 的「编码铁律」章节(如果存在),严格遵守
199
281
  2. 读取 plan.md 了解全局任务划分和依赖关系
200
- 2. 确认本 Wave 的输入/输出契约(前置 Wave 产出了什么,本 Wave 需要消费什么)
201
- 3. 检查前置 Wave 的产出是否完整(文件是否存在、测试是否通过)
202
- 4. **上下文分层加载**:
282
+ 3. 确认本 Wave 的输入/输出契约(前置 Wave 产出了什么,本 Wave 需要消费什么)
283
+ 4. 检查前置 Wave 的产出是否完整(文件是否存在、测试是否通过)
284
+ 5. **上下文分层加载**:
203
285
  - 🔥 热上下文:design.md 编码铁律 + 当前 Wave 任务(必须加载)
204
286
  - 🌡️ 温上下文:CONVENTIONS.md + ARCHITECTURE.md(需要时加载)
205
287
  - ❄️ 冷上下文:其他变更的 design.md、历史 plan.md(不要主动加载,除非明确需要)
@@ -207,26 +289,23 @@ const hasTaskBlueprints = changeDir && existsSync(join(changeDir, 'tasks'))
207
289
  ### 本 Wave 任务
208
290
  ${taskList}
209
291
 
210
- ### 执行要求
211
- 1. 按任务顺序执行,同一 Wave 内任务可并行
212
- 2. 铁律:先读后写、grep 确认方法存在、不编造、TDD
213
- 3. **禁止发散思维**:你是代码搬运工,严格按任务描述执行,不增不减不改。${taskBlueprintRule}
214
- 4. **Reverse Sync**:发现 Bug 或实现与 design.md/task-N.md 不一致时,先检查是代码错了还是文档有遗漏,有遗漏则先修文档再修代码。
215
- 4. **不要频繁编译!** 编译很慢,只在以下情况运行:
292
+ ### 调度要求
293
+ 1. 同一 Wave 内任务可并行启动子代理
294
+ 2. **Reverse Sync**:子代理报告实现与 design.md 不一致时,先检查是代码错了还是文档有遗漏
295
+ 3. **不要频繁编译!** 编译很慢,只在以下情况运行:
216
296
  - 写了大量代码后需要验证语法正确性
217
297
  - 最后一个 Wave 完成后做一次全量编译验证
218
298
  - 用户明确要求编译时
219
- 5. 单个任务完成后只跑**对应模块的单元测试**(TDD 绿灯确认),不要跑全量编译
220
- 6. 每个任务完成后:
221
- - 勾选 task-N.md 中的验收标准 checkbox
299
+ 4. 每个任务完成后:
222
300
  - 勾选 plan.md / tasks.md 中对应任务的 checkbox
223
301
  - 记录改动文件和测试结果
224
- 7. 遇到 BLOCKED → 记录原因,选择:重试/跳过/停止
302
+ 5. 遇到 BLOCKED → 记录原因,选择:重试/跳过/停止
225
303
 
226
304
  ### 完成后
227
305
  运行 sillyspec run execute --done --input "用户原始反馈" --output "Wave ${waveIndex} 结果摘要"`
228
306
  }
229
307
 
308
+
230
309
  /**
231
310
  * 动态构建 execute 步骤列表
232
311
  * @param {string|null} planFilePath - plan 文件路径,null 则用默认 3 Wave
@@ -253,10 +332,11 @@ export function buildExecuteSteps(planFilePath = null) {
253
332
 
254
333
  const waveSteps = waves.map((wave, i) => ({
255
334
  name: `Wave ${i + 1} 执行`,
335
+ mode: 'implementation',
256
336
  prompt: buildWavePrompt(wave, i + 1, changeDir),
257
337
  outputHint: `Wave ${i + 1} 执行结果`,
258
338
  optional: false
259
339
  }))
260
340
 
261
- return [...fixedPrefix, ...waveSteps, ...fixedSuffix]
341
+ return [...fixedPrefix, ...waveSteps, ...acceptanceSteps, ...fixedSuffix]
262
342
  }
@@ -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,22 +5,27 @@ 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'
11
12
 
13
+ // 标记辅助阶段
14
+ const auxiliaryFlag = { auxiliary: true }
15
+
12
16
  export const stageRegistry = {
13
17
  brainstorm,
14
18
  propose,
15
19
  plan,
16
20
  execute,
17
21
  verify,
18
- scan,
19
- quick,
20
- archive,
21
- status,
22
- doctor
22
+ scan: { ...scan, ...auxiliaryFlag },
23
+ quick: { ...quick, ...auxiliaryFlag },
24
+ explore: { ...explore, ...auxiliaryFlag },
25
+ archive: { ...archive, ...auxiliaryFlag },
26
+ status: { ...status, ...auxiliaryFlag },
27
+ doctor: { ...doctor, ...auxiliaryFlag }
23
28
  }
24
29
 
25
30
  // 辅助命令(在没有 progress.json 时也可执行)
26
- export const auxiliaryStages = ['scan', 'quick', 'archive', 'status', 'doctor']
31
+ export const auxiliaryStages = ['scan', 'quick', 'explore', 'archive', 'status', 'doctor']