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.
- package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
- package/.claude/skills/sillyspec-continue/SKILL.md +1 -1
- package/.claude/skills/sillyspec-explore/SKILL.md +9 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +0 -35
- package/.claude/skills/sillyspec-resume/SKILL.md +5 -5
- package/.claude/skills/sillyspec-state/SKILL.md +1 -1
- package/.claude/skills/sillyspec-workspace/SKILL.md +1 -1
- package/README.md +7 -6
- package/SKILL.md +15 -10
- package/package.json +1 -1
- package/packages/dashboard/dist/assets/index-BcM2J-hv.css +1 -0
- package/packages/dashboard/dist/assets/{index-RsLVPAy7.js → index-DpLHK4jv.js} +974 -974
- package/packages/dashboard/dist/index.html +16 -17
- package/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/packages/dashboard/dist/prototype-overview.html +256 -0
- package/packages/dashboard/public/prototype-dashboard.html +836 -0
- package/packages/dashboard/public/prototype-overview.html +256 -0
- package/packages/dashboard/server/index.js +18 -13
- package/packages/dashboard/server/parser.js +109 -1
- package/packages/dashboard/server/watcher.js +14 -6
- package/packages/dashboard/src/App.vue +414 -186
- package/packages/dashboard/src/components/ActionBar.vue +10 -1
- package/packages/dashboard/src/components/CommandPalette.vue +5 -1
- package/packages/dashboard/src/components/DocPreview.vue +105 -8
- package/packages/dashboard/src/components/DocTree.vue +75 -19
- package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
- package/packages/dashboard/src/components/PipelineView.vue +23 -4
- package/packages/dashboard/src/components/ProjectCard.vue +187 -0
- package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
- package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
- package/packages/dashboard/src/composables/useDashboard.js +28 -0
- package/packages/dashboard/src/composables/useLayout.js +131 -0
- package/src/index.js +7 -0
- package/src/init.js +17 -10
- package/src/migrate.js +5 -5
- package/src/progress.js +2 -1
- package/src/run.js +141 -64
- package/src/stages/archive.js +14 -8
- package/src/stages/brainstorm.js +29 -4
- package/src/stages/execute.js +115 -35
- package/src/stages/explore.js +34 -0
- package/src/stages/index.js +11 -6
- package/src/stages/plan.js +86 -15
- package/src/stages/quick.js +1 -1
- package/src/stages/scan.js +51 -12
- package/src/stages/status.js +1 -1
- package/src/stages/verify.js +35 -6
- package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
- package/.sillyspec/changes/auto-mode/design.md +0 -50
- package/.sillyspec/changes/auto-mode/proposal.md +0 -19
- package/.sillyspec/changes/auto-mode/requirements.md +0 -21
- package/.sillyspec/changes/auto-mode/tasks.md +0 -7
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
- package/.sillyspec/changes/dashboard/design.md +0 -219
- package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
- package/.sillyspec/changes/run-command-design/design.md +0 -1230
- package/.sillyspec/changes/unified-docs-design/design.md +0 -199
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +0 -8
- package/.sillyspec/knowledge/uncategorized.md +0 -3
- package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
- package/.sillyspec/projects/sillyspec.yaml +0 -3
- 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
|
|
56
|
+
const changeDir = resolveChangeDir(cwd, progress)
|
|
19
57
|
let planFile = null
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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 阶段 "展开任务"
|
|
356
|
-
if (stageName === 'plan' && steps[currentIdx]?.name === '展开任务并分组'
|
|
357
|
-
const
|
|
358
|
-
if (
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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
|
|
package/src/stages/archive.js
CHANGED
|
@@ -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: `检查
|
|
8
|
+
prompt: `检查 plan.md 中所有任务 checkbox 是否已勾选(plan.md 是任务完成的唯一真相源)。
|
|
10
9
|
|
|
11
10
|
### 操作
|
|
12
|
-
1. 读取 \`.sillyspec/changes/<change-name>/
|
|
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. 确认后:将
|
|
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/\` —
|
|
48
|
+
2. \`git add .sillyspec/changes/\` — 暂存归档结果(不要 commit,由用户通过统一提交工具处理)
|
|
43
49
|
3. 更新 progress.json:
|
|
44
50
|
- 清除当前变更信息(归档后不再活跃)
|
|
45
51
|
- 如果是主变更(有 MASTER.md),标记所有阶段为 ✅,然后清除
|
package/src/stages/brainstorm.js
CHANGED
|
@@ -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/\` —
|
|
248
|
+
- \`git add .sillyspec/\` — 暂存规范文件(不要 commit)
|
|
224
249
|
|
|
225
250
|
### 输出
|
|
226
251
|
所有规范文件路径
|
package/src/stages/execute.js
CHANGED
|
@@ -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
|
-
-
|
|
95
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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.
|
|
212
|
-
2.
|
|
213
|
-
3.
|
|
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
|
-
|
|
220
|
-
6. 每个任务完成后:
|
|
221
|
-
- 勾选 task-N.md 中的验收标准 checkbox
|
|
299
|
+
4. 每个任务完成后:
|
|
222
300
|
- 勾选 plan.md / tasks.md 中对应任务的 checkbox
|
|
223
301
|
- 记录改动文件和测试结果
|
|
224
|
-
|
|
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
|
+
}
|
package/src/stages/index.js
CHANGED
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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']
|