sillyspec 3.12.8 → 3.14.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.
- package/package.json +2 -1
- package/src/index.js +173 -0
- package/src/modules.js +427 -0
- package/src/progress.js +51 -0
- package/src/run.js +161 -0
- package/src/stages/archive.js +66 -84
- package/src/stages/brainstorm.js +17 -4
- package/src/stages/doctor.js +30 -1
- package/src/stages/execute.js +5 -1
- package/src/stages/plan.js +6 -1
- package/src/stages/scan.js +277 -125
- package/src/stages/verify.js +2 -1
- package/src/workflow.js +670 -0
package/src/run.js
CHANGED
|
@@ -224,6 +224,7 @@ async function outputStep(stageName, stepIndex, steps, cwd, changeName, dbProjec
|
|
|
224
224
|
console.log('- 不要回头修改已完成的步骤')
|
|
225
225
|
console.log('- 不要编造不存在的 CLI 子命令')
|
|
226
226
|
console.log('- 完成后立即执行 --done 命令,不得跳过')
|
|
227
|
+
console.log('- 不要用 mv/rename 重命名变更目录,必须用 `sillyspec change-rename <旧名> <新名>`')
|
|
227
228
|
console.log('- 文档类型文件(.md/.yaml/.json 等)头部必须包含 author(git 用户名)和 created_at(精确到秒)')
|
|
228
229
|
console.log('- 执行构建/测试前必须先读 local.yaml,优先使用其中配置的命令、路径和环境变量;未配置时才使用默认值')
|
|
229
230
|
// 路径安全规则:防止 AI 拼错变更目录
|
|
@@ -310,6 +311,7 @@ export async function runCommand(args, cwd) {
|
|
|
310
311
|
const autoName = `${date}-new-change`
|
|
311
312
|
console.log(`🔄 自动创建变更:${autoName}`)
|
|
312
313
|
console.log(` 提示:可以用 --change <名称> 指定自定义变更名`)
|
|
314
|
+
console.log(` 或事后重命名:sillyspec change-rename ${autoName} <新名称>`)
|
|
313
315
|
progress = await pm.initChange(cwd, autoName)
|
|
314
316
|
changeName = autoName
|
|
315
317
|
} else {
|
|
@@ -582,6 +584,88 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
582
584
|
}
|
|
583
585
|
}
|
|
584
586
|
|
|
587
|
+
// scan 阶段 step 2 "构建扫描项目列表" 完成后,按项目展开 perProject 步骤
|
|
588
|
+
if (stageName === 'scan' && steps[currentIdx]?.name === '构建扫描项目列表') {
|
|
589
|
+
// 解析项目列表:从 step 2 输出提取,或回退读取 projects/*.yaml
|
|
590
|
+
let projectNames = []
|
|
591
|
+
if (outputText) {
|
|
592
|
+
// 匹配 "1. project-name" 格式
|
|
593
|
+
const matches = outputText.match(/^\s*\d+\.\s+(\S+)/gm)
|
|
594
|
+
if (matches) {
|
|
595
|
+
projectNames = matches.map(m => m.replace(/^\s*\d+\.\s+/, '').replace(/[—\-:].*$/, '').trim())
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (projectNames.length === 0) {
|
|
599
|
+
// 回退:读取所有已注册项目
|
|
600
|
+
console.warn('⚠️ 未能从 step 2 输出解析项目列表,回退扫描所有注册项目')
|
|
601
|
+
const projectsDir = join(cwd, '.sillyspec', 'projects')
|
|
602
|
+
if (existsSync(projectsDir)) {
|
|
603
|
+
projectNames = readdirSync(projectsDir)
|
|
604
|
+
.filter(f => f.endsWith('.yaml'))
|
|
605
|
+
.map(f => f.replace(/\.yaml$/, ''))
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (projectNames.length === 0) {
|
|
609
|
+
projectNames = ['sillyspec'] // 最终兜底
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// 保存到 runtime 供后续使用 + 防重复展开
|
|
613
|
+
const scanStatePath = join(cwd, '.sillyspec', '.runtime', 'scan-projects.json')
|
|
614
|
+
mkdirSync(join(cwd, '.sillyspec', '.runtime'), { recursive: true })
|
|
615
|
+
let scanState = { projects: projectNames, expanded: false }
|
|
616
|
+
if (existsSync(scanStatePath)) {
|
|
617
|
+
try { scanState = JSON.parse(readFileSync(scanStatePath, 'utf8')) } catch {}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// 收集当前步骤之后所有 perProject 步骤
|
|
621
|
+
const stageDef = stageRegistry[stageName]
|
|
622
|
+
const allSteps = stageDef?.steps || []
|
|
623
|
+
const perProjectSteps = allSteps.filter(s => s.perProject)
|
|
624
|
+
|
|
625
|
+
// 防重复展开:runtime 标记 或 steps 已含项目标识
|
|
626
|
+
const alreadyExpanded = scanState.expanded || steps.some(s => s.name?.match(/\[.+\]\s*$/))
|
|
627
|
+
if (!alreadyExpanded && perProjectSteps.length > 0) {
|
|
628
|
+
// 找到当前步骤(step 2)在动态 steps 中的位置
|
|
629
|
+
const insertBase = currentIdx + 1
|
|
630
|
+
let insertPos = insertBase
|
|
631
|
+
for (const pName of projectNames) {
|
|
632
|
+
// 读取项目配置获取 projectRoot
|
|
633
|
+
const projYaml = join(cwd, '.sillyspec', 'projects', `${pName}.yaml`)
|
|
634
|
+
let projectRoot = '.'
|
|
635
|
+
if (existsSync(projYaml)) {
|
|
636
|
+
const yamlContent = readFileSync(projYaml, 'utf8')
|
|
637
|
+
const pathMatch = yamlContent.match(/^path:\s*(.+)/m)
|
|
638
|
+
if (pathMatch) projectRoot = pathMatch[1].trim()
|
|
639
|
+
}
|
|
640
|
+
const docOutputDir = `.sillyspec/docs/${pName}`
|
|
641
|
+
const contextPrefix = `\n---\n## 当前项目\n- **项目名**: ${pName}\n- **项目路径**: ${projectRoot}\n- **文档输出**: ${docOutputDir}\n\n⚠️ 本步骤只处理上面这个项目,不要处理其他项目。\n---\n\n`
|
|
642
|
+
|
|
643
|
+
for (const ppStep of perProjectSteps) {
|
|
644
|
+
steps.splice(insertPos, 0, {
|
|
645
|
+
name: `${ppStep.name} [${pName}]`,
|
|
646
|
+
project: pName,
|
|
647
|
+
status: 'pending',
|
|
648
|
+
prompt: contextPrefix + ppStep.prompt,
|
|
649
|
+
outputHint: ppStep.outputHint,
|
|
650
|
+
optional: ppStep.optional
|
|
651
|
+
})
|
|
652
|
+
insertPos++
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// 移除原始的 perProject 步骤(未展开的版本)
|
|
656
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
657
|
+
if (steps[i].perProject && !steps[i].name?.includes('[')) {
|
|
658
|
+
steps.splice(i, 1)
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
console.log(` 📝 已按项目展开 ${perProjectSteps.length} 个步骤 × ${projectNames.length} 个项目 = ${perProjectSteps.length * projectNames.length} 个项目步骤`)
|
|
662
|
+
console.log(` 📁 扫描项目:${projectNames.join(', ')}`)
|
|
663
|
+
// 标记已展开,防止 resume 重复插入
|
|
664
|
+
scanState.expanded = true
|
|
665
|
+
writeFileSync(scanStatePath, JSON.stringify(scanState))
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
585
669
|
const nextPendingIdx = steps.findIndex(s => s.status === 'pending')
|
|
586
670
|
|
|
587
671
|
if (nextPendingIdx === -1) {
|
|
@@ -685,6 +769,83 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
685
769
|
|
|
686
770
|
const defSteps = await getStageSteps(stageName, cwd, progress)
|
|
687
771
|
console.log(`✅ Step ${currentIdx + 1}/${steps.length} 完成:${steps[currentIdx].name}\n`)
|
|
772
|
+
|
|
773
|
+
// Workflow post_check:scan 深度扫描完成后自动检查产物
|
|
774
|
+
if (stageName === 'scan' && steps[currentIdx]?.name?.includes('深度扫描')) {
|
|
775
|
+
try {
|
|
776
|
+
const { loadWorkflow, runPostCheck, formatCheckReport, saveWorkflowRun } = await import('./workflow.js')
|
|
777
|
+
const wf = loadWorkflow(cwd, 'scan-docs')
|
|
778
|
+
if (wf) {
|
|
779
|
+
// 确定当前项目:优先从 step metadata 读取,回退从 display name 提取
|
|
780
|
+
const currentProjectName = steps[currentIdx].project
|
|
781
|
+
|| (steps[currentIdx].name.match(/\[([^\]]+)\]\s*$/) || [])[1]
|
|
782
|
+
|| null
|
|
783
|
+
|
|
784
|
+
// 确定要检查的项目列表
|
|
785
|
+
let projectsToCheck = []
|
|
786
|
+
if (currentProjectName) {
|
|
787
|
+
// 按项目展开模式:只检查当前项目
|
|
788
|
+
projectsToCheck = [currentProjectName]
|
|
789
|
+
} else {
|
|
790
|
+
// 兼容旧模式(未展开):检查所有项目
|
|
791
|
+
const projectsDir = join(cwd, '.sillyspec', 'projects')
|
|
792
|
+
const projectFiles = existsSync(projectsDir)
|
|
793
|
+
? readdirSync(projectsDir).filter(f => f.endsWith('.yaml'))
|
|
794
|
+
: []
|
|
795
|
+
projectsToCheck = projectFiles.map(f => f.replace(/\.yaml$/, ''))
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
let anyFailed = false
|
|
799
|
+
for (const pName of projectsToCheck) {
|
|
800
|
+
const result = runPostCheck(wf, cwd, pName)
|
|
801
|
+
const report = formatCheckReport(result)
|
|
802
|
+
console.log(report)
|
|
803
|
+
if (result.status === 'fail') {
|
|
804
|
+
anyFailed = true
|
|
805
|
+
// retry_prompts 由 _checkWorkflow 自动生成
|
|
806
|
+
for (const rp of (result.retry_prompts || [])) {
|
|
807
|
+
console.log(`\n🔄 重试提示(项目 ${pName}):\n`)
|
|
808
|
+
console.log(rp.prompt)
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const saved = saveWorkflowRun(result, { cwd, source: 'run.js', stage: 'verify', step: steps[currentIdx]?.name })
|
|
812
|
+
if (saved) console.log(`📁 结果已归档:${saved}`)
|
|
813
|
+
}
|
|
814
|
+
if (anyFailed) {
|
|
815
|
+
console.log(`\n⚠️ 存在检查失败项,请按上面的重试提示修复后再继续。`)
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
} catch (e) {
|
|
819
|
+
console.warn(`⚠️ workflow 检查跳过:${e.message}`)
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Workflow post_check:archive extract-module-impact 完成后检查产物
|
|
824
|
+
if (stageName === 'archive' && steps[currentIdx]?.name?.includes('extract-module-impact')) {
|
|
825
|
+
try {
|
|
826
|
+
const { loadWorkflow, runPostCheck, formatCheckReport, saveWorkflowRun } = await import('./workflow.js')
|
|
827
|
+
const wf = loadWorkflow(cwd, 'archive-impact')
|
|
828
|
+
if (wf && changeName) {
|
|
829
|
+
const raw = JSON.stringify(wf)
|
|
830
|
+
const resolved = JSON.parse(raw.replace(/<change-name>/g, changeName))
|
|
831
|
+
const result = runPostCheck(resolved, cwd, 'sillyspec')
|
|
832
|
+
// 只报告 impact-analyzer 的结果(doc-syncer 是后续步骤)
|
|
833
|
+
const impactResult = (result.roles || []).find(r => r.id === 'impact-analyzer')
|
|
834
|
+
if (impactResult) {
|
|
835
|
+
const icon = impactResult.status === 'pass' ? '✅' : '❌'
|
|
836
|
+
console.log(`${icon} module-impact.md 检查${impactResult.status === 'pass' ? '通过' : '失败'}`)
|
|
837
|
+
for (const f of (result.failures || []).filter(f => f.role_id === 'impact-analyzer')) {
|
|
838
|
+
console.log(` └─ ${f}`)
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
const saved = saveWorkflowRun(result, { cwd, source: 'run.js', stage: 'archive', step: steps[currentIdx]?.name })
|
|
842
|
+
if (saved) console.log(`📁 结果已归档:${saved}`)
|
|
843
|
+
}
|
|
844
|
+
} catch (e) {
|
|
845
|
+
console.warn(`⚠️ workflow 检查跳过:${e.message}`)
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
688
849
|
if (printNext) {
|
|
689
850
|
await outputStep(stageName, nextPendingIdx, defSteps, cwd, changeName, progress.project || null)
|
|
690
851
|
}
|
package/src/stages/archive.js
CHANGED
|
@@ -20,123 +20,105 @@ export const definition = {
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
name: 'extract-module-impact',
|
|
23
|
-
prompt:
|
|
23
|
+
prompt: `按照 \`.sillyspec/workflows/archive-impact.yaml\` 中定义的 \`impact-analyzer\` 角色规则,分析本次变更影响的模块。
|
|
24
24
|
|
|
25
25
|
### 操作
|
|
26
|
-
1.
|
|
27
|
-
2.
|
|
28
|
-
3.
|
|
26
|
+
1. 读取 \`.sillyspec/workflows/archive-impact.yaml\`,了解角色定义和检查规则
|
|
27
|
+
2. 读取变更目录下的 proposal.md、design.md、tasks.md
|
|
28
|
+
3. 运行 \`git diff --name-only HEAD~1\`(或 \`git diff --name-only --cached\`)获取真实修改文件列表
|
|
29
|
+
4. 读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`
|
|
29
30
|
- **如果不存在**:提示"建议运行 scan 生成模块映射",但继续执行。跳到步骤 7 生成只有 unmapped 部分的 module-impact.md
|
|
30
|
-
|
|
31
|
+
5. 三重交叉验证:
|
|
31
32
|
- 声明范围:proposal.md / design.md 中的"变更范围"/"文件变更清单"
|
|
32
33
|
- 任务范围:tasks.md / plan.md 中的任务文件路径
|
|
33
34
|
- 真实变更:git diff 文件列表
|
|
34
35
|
- **以 git diff 为准**(真实 > 声明)
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
6. 将 git diff 文件按 \`_module-map.yaml\` 的 paths glob 匹配到模块
|
|
37
|
+
7. 生成模块影响矩阵:
|
|
37
38
|
|
|
38
|
-
| 模块 | 影响类型 | 相关文件 | 更新内容摘要 |
|
|
39
|
-
|
|
39
|
+
| 模块 | 影响类型 | 相关文件 | 更新内容摘要 | needs_review |
|
|
40
|
+
|------|----------|----------|-------------|-------------|
|
|
40
41
|
|
|
41
42
|
影响类型:逻辑变更 / 数据结构变更 / 接口变更 / 调用关系变更 / 配置变更 / 新增
|
|
43
|
+
needs_review:如果影响无法完全确定,标记为 true
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# 模块影响分析
|
|
48
|
-
|
|
49
|
-
author: <git-user>
|
|
50
|
-
created_at: <now-datetime>
|
|
51
|
-
|
|
52
|
-
## 变更:<change-name>
|
|
53
|
-
|
|
54
|
-
## 模块影响矩阵
|
|
55
|
-
| 模块 | 影响类型 | 相关文件 | 更新内容摘要 |
|
|
56
|
-
|------|----------|----------|-------------|
|
|
57
|
-
|
|
58
|
-
## 未匹配文件
|
|
59
|
-
| 文件路径 | 说明 |
|
|
60
|
-
|----------|------|
|
|
61
|
-
|
|
62
|
-
## 更新结果
|
|
63
|
-
(sync-module-docs 步骤完成后回填)
|
|
64
|
-
| 模块文档 | 操作 | 状态 |
|
|
65
|
-
|----------|------|------|
|
|
66
|
-
\`\`\`
|
|
67
|
-
|
|
68
|
-
### 输出
|
|
69
|
-
module-impact.md 路径 + 影响模块数量 + 未匹配文件数量`,
|
|
45
|
+
8. 未匹配到任何模块的文件归入"未匹配文件"表格
|
|
46
|
+
9. 生成 \`.sillyspec/changes/<change-name>/module-impact.md\`
|
|
47
|
+
10. 完成后运行 workflow 检查:
|
|
48
|
+
\`node -e "import('./src/workflow.js').then(w => { /* 用 loadWorkflow 加载 archive-impact,用 runPostCheck 检查 */ })\``,
|
|
70
49
|
outputHint: 'module-impact.md 路径 + 影响摘要',
|
|
71
50
|
optional: false
|
|
72
51
|
},
|
|
73
52
|
{
|
|
74
53
|
name: 'sync-module-docs',
|
|
75
|
-
prompt: `根据 module-impact.md
|
|
54
|
+
prompt: `根据 module-impact.md 同步更新模块索引和卡片文档。
|
|
76
55
|
|
|
77
|
-
###
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
56
|
+
### ⚠️ 核心原则:结构化事实改 _module-map.yaml,语义解释改模块卡片
|
|
57
|
+
- \`_module-map.yaml\` 是唯一的结构化索引源(paths/tags/entrypoints/depends_on/used_by/status/needs_review)
|
|
58
|
+
- 模块卡片只负责语义说明(定位/契约摘要/关键逻辑/注意事项/人工备注)
|
|
59
|
+
- 一个信息只维护一次,不要两边重复
|
|
81
60
|
|
|
82
61
|
### 操作
|
|
83
62
|
1. 读取 \`.sillyspec/changes/<change-name>/module-impact.md\`
|
|
84
63
|
2. 如果没有受影响模块(只有 unmapped)→ 提示用户,跳过同步
|
|
85
|
-
3.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
64
|
+
3. 对每个受影响模块,按影响类型分别更新:
|
|
65
|
+
|
|
66
|
+
#### 更新 _module-map.yaml 的规则
|
|
67
|
+
- **路径变化** → 更新对应模块的 paths
|
|
68
|
+
- **依赖变化** → 更新 depends_on / used_by(同时更新反向模块的 used_by / depends_on)
|
|
69
|
+
- **导出符号变化** → 更新 entrypoints / main_symbols
|
|
70
|
+
- **新增模块** → 添加完整条目
|
|
71
|
+
- **模块废弃** → status: deprecated
|
|
72
|
+
- **不确定的影响** → needs_review: true, review_reasons 追加原因
|
|
73
|
+
- 如果 _module-map.yaml 的 generated_at 已过时,更新为当前时间
|
|
74
|
+
|
|
75
|
+
#### 更新模块卡片(modules/<module-id>.md)的规则
|
|
76
|
+
- **契约语义变化**(新增/删除对外能力) → 更新"契约摘要"
|
|
77
|
+
- **关键逻辑变化** → 更新"关键逻辑"
|
|
78
|
+
- **边界变化**(模块职责扩大/缩小) → 更新"定位"
|
|
79
|
+
- **注意事项变化** → 更新"注意事项"
|
|
80
|
+
- **内部实现变化**(不影响对外接口) → 通常不更新卡片
|
|
81
|
+
- **人工备注** → 永远保护,不覆盖
|
|
82
|
+
|
|
83
|
+
#### 人工备注保护
|
|
84
|
+
1. 用正则提取 \`<!-- MANUAL_NOTES_START -->\` 到 \`<!-- MANUAL_NOTES_END -->\` 之间的内容
|
|
85
|
+
2. 生成新卡片后,原样回填到人工备注区域
|
|
86
|
+
3. 如果标记缺失或重复 → 在 _module-map.yaml 中标记 needs_review: true
|
|
87
|
+
|
|
88
|
+
#### 新建模块卡片模板
|
|
100
89
|
\`\`\`markdown
|
|
101
|
-
|
|
90
|
+
---
|
|
91
|
+
schema_version: 1
|
|
92
|
+
doc_type: module-card
|
|
93
|
+
module_id: <module-id>
|
|
94
|
+
---
|
|
102
95
|
|
|
103
|
-
|
|
104
|
-
> 最近变更:<change-name>
|
|
105
|
-
> 模块路径:<glob patterns>
|
|
96
|
+
# <module-id>
|
|
106
97
|
|
|
107
|
-
##
|
|
108
|
-
(一句话说清这个模块做什么)
|
|
98
|
+
## 定位
|
|
109
99
|
|
|
110
|
-
##
|
|
111
|
-
(架构、数据流、关键逻辑 — 描述当前状态,不是历史)
|
|
100
|
+
## 契约摘要
|
|
112
101
|
|
|
113
|
-
##
|
|
114
|
-
| 接口 | 说明 | 调用方 |
|
|
115
|
-
|------|------|--------|
|
|
116
|
-
|
|
117
|
-
## 关键数据流
|
|
118
|
-
\`\`\`text
|
|
119
|
-
调用方 → 模块.方法() → 依赖模块.方法() → 返回结果
|
|
120
|
-
\`\`\`
|
|
102
|
+
## 关键逻辑
|
|
121
103
|
|
|
122
|
-
##
|
|
123
|
-
| 决策 | 理由 | 来源 |
|
|
124
|
-
|------|------|------|
|
|
104
|
+
## 注意事项
|
|
125
105
|
|
|
126
|
-
##
|
|
127
|
-
### 依赖本模块
|
|
128
|
-
### 本模块依赖
|
|
106
|
+
## 人工备注
|
|
129
107
|
|
|
130
|
-
|
|
131
|
-
(维护提醒、已知限制、修改时需同步检查的模块)
|
|
108
|
+
<!-- MANUAL_NOTES_START -->
|
|
132
109
|
|
|
133
|
-
|
|
134
|
-
| 日期 | 变更 | 摘要 |
|
|
135
|
-
|------|------|------|
|
|
110
|
+
<!-- MANUAL_NOTES_END -->
|
|
136
111
|
\`\`\`
|
|
137
112
|
|
|
113
|
+
4. 展示所有更新内容(diff 摘要),请用户确认
|
|
114
|
+
5. 用户确认后,写入 _module-map.yaml 和受影响的模块卡片
|
|
115
|
+
6. 用户拒绝时,不写入,但提示"module-impact.md 已保留,可稍后手动同步"
|
|
116
|
+
7. 回填 module-impact.md 的"更新结果"表格,区分目标:
|
|
117
|
+
- 目标列写 "\`_module-map.yaml: <module-id>\`" 或 "\`modules/<module-id>.md\`"
|
|
118
|
+
8. **同步完成后**,运行 \`sillyspec modules rebuild\` 刷新索引(如果需要),或手动更新 dependencies.md
|
|
119
|
+
|
|
138
120
|
### 输出
|
|
139
|
-
|
|
121
|
+
已更新的文件路径列表 + 用户确认状态`,
|
|
140
122
|
outputHint: '模块文档更新结果',
|
|
141
123
|
optional: false
|
|
142
124
|
},
|
package/src/stages/brainstorm.js
CHANGED
|
@@ -12,13 +12,15 @@ export const definition = {
|
|
|
12
12
|
2. 确认 currentStage 为 "brainstorm"
|
|
13
13
|
3. 如果有进行中的 brainstorm,提示选择继续或重新开始
|
|
14
14
|
4. 如果未初始化,提示先运行 sillyspec init
|
|
15
|
+
5. **检查变更名称是否有意义**:如果当前变更名是自动生成的(如 \`2026-06-02-new-change\`),先询问用户确认实际变更名,然后运行 \`sillyspec change-rename <旧名> <新名>\` 重命名
|
|
15
16
|
|
|
16
17
|
### 输出
|
|
17
18
|
当前状态摘要(1-2 句话)
|
|
18
19
|
|
|
19
20
|
### 注意
|
|
20
21
|
- 以 CLI 返回为准,不要自行推断阶段
|
|
21
|
-
-
|
|
22
|
+
- 如果阶段不对,输出正确提示并停止
|
|
23
|
+
- **不要用 mv 命令重命名变更目录**,必须使用 \`sillyspec change-rename\`,否则 DB 和目录会脱节`,
|
|
22
24
|
outputHint: '状态摘要',
|
|
23
25
|
optional: false
|
|
24
26
|
},
|
|
@@ -32,14 +34,25 @@ export const definition = {
|
|
|
32
34
|
3. 加载本地配置:\`cat .sillyspec/local.yaml 2>/dev/null\`
|
|
33
35
|
4. 询问本次需求属于哪个子项目
|
|
34
36
|
5. 棕地项目:读取 .sillyspec/docs/<project>/scan/ 下的 STRUCTURE.md、CONVENTIONS.md、ARCHITECTURE.md
|
|
35
|
-
6.
|
|
37
|
+
6. **加载模块索引**:读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`(如存在)
|
|
38
|
+
- 这一步是高频操作,_module-map.yaml 回答“哪个文件属于哪个模块、模块之间怎么依赖”
|
|
39
|
+
- 用 tags/aliases 字段做需求关键词→模块的粗匹配
|
|
40
|
+
- 用 entrypoints 字段快速了解模块对外能力
|
|
41
|
+
7. 查看进行中的变更:\`ls .sillyspec/changes/ | grep -v archive\`
|
|
42
|
+
|
|
43
|
+
### 模块匹配方法
|
|
44
|
+
读取 _module-map.yaml 后,根据用户描述的需求关键词,匹配相关模块:
|
|
45
|
+
- 需求中提到"登录""认证""token" → 匹配 tags/aliases 中含这些词的模块
|
|
46
|
+
- 需求中提到特定文件路径 → 匹配 paths 字段
|
|
47
|
+
- 匹配结果用于后续 design.md 的文件变更清单
|
|
36
48
|
|
|
37
49
|
### 输出
|
|
38
|
-
项目现状理解摘要(3-5
|
|
50
|
+
项目现状理解摘要(3-5 句话,关键约定和架构决策)+ 可能涉及的模块列表
|
|
39
51
|
|
|
40
52
|
### 注意
|
|
41
53
|
- 始终询问本次需求属于哪个子项目
|
|
42
|
-
-
|
|
54
|
+
- 棕地项目必须读取数据模型章节
|
|
55
|
+
- 模块匹配只是粗筛,后续步骤会细化`,
|
|
43
56
|
outputHint: '上下文摘要',
|
|
44
57
|
optional: false
|
|
45
58
|
},
|
package/src/stages/doctor.js
CHANGED
|
@@ -238,9 +238,38 @@ timeout 5 which docker 2>/dev/null && echo "✅ Docker 可用" || echo "ℹ️ D
|
|
|
238
238
|
outputHint: '外部依赖检查结果',
|
|
239
239
|
optional: false
|
|
240
240
|
},
|
|
241
|
+
{
|
|
242
|
+
name: '模块文档健康检查',
|
|
243
|
+
prompt: `检查模块索引和卡片文档的健康状态。
|
|
244
|
+
|
|
245
|
+
### 操作
|
|
246
|
+
1. 运行 \`sillyspec modules status\` 查看模块索引概览
|
|
247
|
+
2. 读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`
|
|
248
|
+
3. 对每个模块,检查:
|
|
249
|
+
- module_id 是否有对应的卡片文件(modules/<module-id>.md)
|
|
250
|
+
- 卡片文件是否有有效的 frontmatter(schema_version, doc_type, module_id)
|
|
251
|
+
- 人工备注标记是否配对(MANUAL_NOTES_START 和 MANUAL_NOTES_END 必须成对出现)
|
|
252
|
+
- needs_review 为 true 的模块列表
|
|
253
|
+
4. 汇总结果
|
|
254
|
+
|
|
255
|
+
### 输出格式
|
|
256
|
+
\`\`\`
|
|
257
|
+
📋 模块文档健康检查
|
|
258
|
+
✅ _module-map.yaml — 存在,N 个模块
|
|
259
|
+
⚠️ auth-service — needs_review=true,原因:...
|
|
260
|
+
❌ payment-service — 卡片文件缺失
|
|
261
|
+
✅ 所有模块人工备注标记配对正常
|
|
262
|
+
\`\`\`
|
|
263
|
+
|
|
264
|
+
### 注意
|
|
265
|
+
- 如果 _module-map.yaml 不存在,输出"模块索引未生成,建议运行 scan"
|
|
266
|
+
`,
|
|
267
|
+
outputHint: '模块文档健康状态',
|
|
268
|
+
optional: false
|
|
269
|
+
},
|
|
241
270
|
{
|
|
242
271
|
name: '汇总报告',
|
|
243
|
-
prompt:
|
|
272
|
+
prompt: `汇总前四步的所有检查结果,生成最终的自检报告。
|
|
244
273
|
|
|
245
274
|
### 输出格式
|
|
246
275
|
\`\`\`
|
package/src/stages/execute.js
CHANGED
|
@@ -41,9 +41,13 @@ const fixedPrefix = [
|
|
|
41
41
|
7. 根据 plan.md 中的任务文件路径匹配 _module-map.yaml 中的模块
|
|
42
42
|
8. 读取匹配到的 \`.sillyspec/docs/<project>/modules/<module>.md\`
|
|
43
43
|
9. 实现代码时遵循模块文档中描述的接口约定、数据流和依赖关系
|
|
44
|
+
10. **利用模块索引快速定位源码**:
|
|
45
|
+
- 用 entrypoints 字段直接找到模块对外 API 的源码位置
|
|
46
|
+
- 用 main_symbols 字段找到核心类/函数的定义位置
|
|
47
|
+
- 子代理优先读模块卡片理解语义,再读 entrypoints/main_symbols 对应的源码
|
|
44
48
|
|
|
45
49
|
### 输出
|
|
46
|
-
|
|
50
|
+
已加载的上下文摘要(含模块文档 + 源码锚点)`,
|
|
47
51
|
outputHint: '上下文摘要',
|
|
48
52
|
optional: false
|
|
49
53
|
},
|
package/src/stages/plan.js
CHANGED
|
@@ -38,9 +38,14 @@ export const fixedPrefix = [
|
|
|
38
38
|
6. 根据 design.md 的文件变更清单匹配 _module-map.yaml 中的模块
|
|
39
39
|
7. 读取匹配到的 \`.sillyspec/docs/<project>/modules/<module>.md\`
|
|
40
40
|
8. 将模块文档作为制定计划的上下文,确保计划符合模块当前设计
|
|
41
|
+
9. **利用模块依赖关系辅助分析**:
|
|
42
|
+
- 用 depends_on 判断哪些模块会被间接影响
|
|
43
|
+
- 用 used_by 判断变更会不会影响下游模块
|
|
44
|
+
- 将依赖关系纳入 Wave 分组决策(依赖同一模块的任务尽量同 Wave)
|
|
45
|
+
- 如果变更涉及多个有依赖关系的模块,在 plan.md 的任务总表中标注模块依赖
|
|
41
46
|
|
|
42
47
|
### 输出
|
|
43
|
-
|
|
48
|
+
已加载的文件清单(含模块文档 + 模块依赖关系摘要)`,
|
|
44
49
|
outputHint: '文件清单',
|
|
45
50
|
optional: false
|
|
46
51
|
},
|