sillyspec 3.12.7 → 3.13.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/SKILL.md CHANGED
@@ -1,46 +1,46 @@
1
1
  ---
2
2
  name: sillyspec
3
- description: "规范驱动开发工具包 v3.9。绿地项目用 /sillyspec:init,棕地项目用 /sillyspec:scan。主线:scanbrainstormplanexecuteverifyarchive。辅助:quick、explore、status、doctor、resume、continue、commit、export、workspace。"
4
- version: "3.9.1"
3
+ description: "规范驱动开发工具包。绿地用 /sillyspec:init,棕地用 /sillyspec:scan,全自动用 /sillyspec:auto。完整流程:scanbrainstormplanexecuteverifyarchive。支持 TDD、子代理并行、worktree 隔离、E2E 验证。兼容 Claude Code / Cursor / Codex / OpenCode / OpenClaw。"
4
+ version: "3.12.7"
5
5
  ---
6
6
 
7
- # SillySpec v3.9
7
+ # SillySpec
8
8
 
9
- 融合 Superpowers + OpenSpec + GSD,从"你说要啥"到"代码能跑"的完整流程。
10
- Claude Code / Cursor / Codex / OpenCode / OpenClaw 都能用。
9
+ "你说要啥"到"代码能跑"的规范驱动开发工具包。
10
+ Claude Code / Cursor / Codex / OpenCode / OpenClaw 通用。
11
11
 
12
- ## 入口选择
12
+ ## 快速开始
13
13
 
14
- | 项目类型 | 首选命令 |
14
+ | 场景 | 命令 |
15
15
  |---|---|
16
+ | 全自动流程 | `/sillyspec:auto <需求描述>` |
16
17
  | 全新项目(空目录) | `/sillyspec:init` |
17
18
  | 已有代码的项目 | `/sillyspec:scan` |
18
19
  | 多项目工作区 | `/sillyspec:workspace` |
19
- | 随时自由思考 | `/sillyspec:explore` |
20
+ | 自由思考 | `/sillyspec:explore` |
20
21
 
21
22
  ## 完整工作流
22
23
 
23
24
  ```
24
- 绿地:init → brainstorm → plan → execute → [verify] → archive
25
+ 绿地:init → brainstorm → plan → execute → verify → archive
25
26
  棕地:scan → brainstorm → plan → execute → verify → archive
26
- 工作区:workspace → (init/scan per project) → brainstorm → ...
27
+ 全自动:auto(自动推进全部阶段,支持用户确认门控)
27
28
  ```
28
29
 
29
- ## 命令
30
-
31
- ### 核心流程
30
+ ## 核心命令
32
31
 
33
32
  | 命令 | 用途 |
34
33
  |---|---|
34
+ | `/sillyspec:auto` | 全自动推进全部流程 |
35
35
  | `/sillyspec:init` | 绿地项目初始化 |
36
- | `/sillyspec:scan` | 棕地项目扫描(7 份文档) |
36
+ | `/sillyspec:scan` | 棕地项目扫描(生成 7 份文档) |
37
37
  | `/sillyspec:brainstorm` | 需求探索 + 生成设计文档 |
38
- | `/sillyspec:plan` | 编写实现计划(Wave 分组) |
38
+ | `/sillyspec:plan` | 编写实现计划(Wave 分组 + 拓扑排序) |
39
39
  | `/sillyspec:execute` | TDD 执行 + 子代理并行 |
40
40
  | `/sillyspec:verify` | 验证(测试 + 代码审查 + E2E) |
41
41
  | `/sillyspec:archive` | 归档变更 |
42
42
 
43
- ### 辅助工具
43
+ ## 辅助命令
44
44
 
45
45
  | 命令 | 用途 |
46
46
  |---|---|
@@ -49,44 +49,43 @@ Claude Code / Cursor / Codex / OpenCode / OpenClaw 都能用。
49
49
  | `/sillyspec:explore` | 自由思考模式 |
50
50
  | `/sillyspec:quick` | 快速任务,跳过完整流程 |
51
51
  | `/sillyspec:resume` | 恢复工作 |
52
- | `/sillyspec:state` | 查看当前工作状态 |
52
+ | `/sillyspec:doctor` | 项目自检 |
53
53
  | `/sillyspec:commit` | 智能提交 |
54
54
  | `/sillyspec:export` | 导出成功方案为可复用模板 |
55
- | `/sillyspec:doctor` | 项目自检 |
56
55
  | `/sillyspec:workspace` | 多项目工作区管理 |
57
56
 
58
57
  ## CLI 命令
59
58
 
60
59
  ```bash
61
- sillyspec run scan 执行代码扫描阶段
62
- sillyspec run brainstorm 执行需求探索阶段
63
- sillyspec run plan 执行实现计划阶段
64
- sillyspec run execute 执行开发阶段
65
- sillyspec run verify 执行验证阶段
66
- sillyspec run archive 执行归档阶段
67
- sillyspec run quick 快速任务
68
- sillyspec run explore 自由探索
69
- sillyspec progress show 显示当前项目状态
70
- sillyspec setup 安装推荐 MCP 工具
71
- sillyspec setup --list 查看已安装 MCP 状态
72
- sillyspec init 初始化(零交互,自动检测工具)
73
- sillyspec init --tool <name> 指定工具安装
74
- sillyspec init --workspace 工作区模式
75
- sillyspec init --interactive 交互式引导
60
+ sillyspec run auto 全自动推进全部流程
61
+ sillyspec run scan 执行代码扫描阶段
62
+ sillyspec run brainstorm 执行需求探索阶段
63
+ sillyspec run plan 执行实现计划阶段
64
+ sillyspec run execute 执行开发阶段(子代理并行 + worktree 隔离)
65
+ sillyspec run verify 执行验证阶段
66
+ sillyspec run archive 执行归档阶段
67
+ sillyspec run quick 快速任务
68
+ sillyspec run explore 自由探索
69
+ sillyspec progress show 显示当前项目状态
70
+ sillyspec setup 安装推荐 MCP 工具
71
+ sillyspec init 初始化(零交互,自动检测工具)
76
72
  ```
77
73
 
78
- ## MCP 增强
74
+ ## 核心特性
79
75
 
80
- 通过 `sillyspec setup` 安装 MCP 工具增强 AI 能力:
76
+ - **规范驱动** 所有代码产出先有设计文档支撑,文档是 AI 的记忆
77
+ - **TDD 强制** — execute 阶段强制先写测试再写实现
78
+ - **子代理并行** — 同一 Wave 内任务并行执行,加快交付
79
+ - **Worktree 隔离** — execute 在独立 worktree 中工作,不污染主分支
80
+ - **拓扑排序 Wave** — plan 阶段根据蓝图依赖关系自动重排 Wave 分组
81
+ - **E2E 验证** — 内置 E2E 测试流程,支持 Playwright / 浏览器 MCP
82
+ - **模块文档** — 支持模块级知识库,AI 执行时按需加载相关模块上下文
83
+ - **进度管理** — SQLite 持久化进度,断点恢复
84
+ - **MCP 增强** — 一键安装 Context7、grep.app、Chrome DevTools
81
85
 
82
- - **Context7** — 查询最新库文档和 API 参考
83
- - **grep.app** — 搜索开源代码实现
84
- - **Chrome DevTools** — 浏览器自动化,支持 E2E 验证
86
+ ## MCP 工具
85
87
 
86
- ## E2E 测试流程
87
-
88
- ```
89
- plan: 识别 UI 功能 → 检测测试框架/浏览器 MCP → 添加 E2E 任务
90
- execute: 编码完成后编写 E2E 测试(测试文件或 e2e-steps.md)
91
- verify: 按优先级执行(E2E框架 > 通用测试 > 浏览器MCP)→ 自动修复循环
88
+ ```bash
89
+ sillyspec setup 安装全部推荐 MCP
90
+ sillyspec setup --list 查看已安装 MCP 状态
92
91
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.12.7",
3
+ "version": "3.13.0",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
package/src/index.js CHANGED
@@ -474,6 +474,45 @@ SillySpec platform — SillyHub 平台同步
474
474
  }
475
475
  break;
476
476
  }
477
+ case 'change-rename': {
478
+ const oldName = filteredArgs[1];
479
+ const newName = filteredArgs[2];
480
+ if (!oldName || !newName) {
481
+ console.error('❌ 用法: sillyspec change-rename <旧变更名> <新变更名>');
482
+ process.exit(1);
483
+ }
484
+ const pm = new ProgressManager();
485
+ await pm.renameChange(dir, oldName, newName);
486
+ break;
487
+ }
488
+ case 'modules': {
489
+ const modulesSub = filteredArgs[1];
490
+ if (!modulesSub || modulesSub === 'help' || modulesSub === '--help') {
491
+ console.log(`
492
+ SillySpec modules — 模块文档管理
493
+
494
+ 用法:
495
+ sillyspec modules rebuild 从模块卡片 + 源码重建 _module-map.yaml
496
+ sillyspec modules status 显示模块索引状态
497
+ sillyspec modules migrate 旧格式模块文档迁移到新格式
498
+ `);
499
+ break;
500
+ }
501
+ if (modulesSub === 'rebuild') {
502
+ const { rebuildModuleMap } = await import('./modules.js');
503
+ await rebuildModuleMap(dir);
504
+ } else if (modulesSub === 'status') {
505
+ const { showModuleStatus } = await import('./modules.js');
506
+ await showModuleStatus(dir);
507
+ } else if (modulesSub === 'migrate') {
508
+ const { migrateModuleDocs } = await import('./modules.js');
509
+ await migrateModuleDocs(dir);
510
+ } else {
511
+ console.error(`❌ 未知子命令: modules ${modulesSub}`);
512
+ process.exit(1);
513
+ }
514
+ break;
515
+ }
477
516
  default:
478
517
  console.error(`❌ 未知命令: ${command}`);
479
518
  printUsage();
package/src/modules.js ADDED
@@ -0,0 +1,427 @@
1
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { DB } from './db.js';
4
+
5
+ /**
6
+ * 查找项目下的 _module-map.yaml 路径
7
+ */
8
+ function findModuleMapPath(cwd) {
9
+ // 查找 .sillyspec/docs/<project>/modules/_module-map.yaml
10
+ const docsDir = join(cwd, '.sillyspec', 'docs');
11
+ if (!existsSync(docsDir)) return null;
12
+
13
+ const projects = readdirSync(docsDir, { withFileTypes: true })
14
+ .filter(e => e.isDirectory());
15
+
16
+ for (const proj of projects) {
17
+ const mapPath = join(docsDir, proj.name, 'modules', '_module-map.yaml');
18
+ if (existsSync(mapPath)) return mapPath;
19
+ }
20
+
21
+ // 如果没有找到已有的,返回第一个项目的路径(用于创建)
22
+ if (projects.length > 0) {
23
+ return join(docsDir, projects[0].name, 'modules', '_module-map.yaml');
24
+ }
25
+
26
+ return null;
27
+ }
28
+
29
+ /**
30
+ * 从模块卡片文件名提取 module_id
31
+ */
32
+ function parseModuleIdFromFilename(filename) {
33
+ return filename.replace(/\.md$/, '');
34
+ }
35
+
36
+ /**
37
+ * 从模块卡片读取 frontmatter 中的 module_id
38
+ */
39
+ function parseModuleCardFrontmatter(content) {
40
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
41
+ if (!fmMatch) return {};
42
+ const fm = fmMatch[1];
43
+ const moduleIdMatch = fm.match(/^module_id:\s*(.+)$/m);
44
+ return {
45
+ module_id: moduleIdMatch ? moduleIdMatch[1].trim() : null
46
+ };
47
+ }
48
+
49
+ /**
50
+ * rebuild: 从模块卡片 + 源码重建 _module-map.yaml
51
+ */
52
+ export async function rebuildModuleMap(cwd) {
53
+ const mapPath = findModuleMapPath(cwd);
54
+ if (!mapPath) {
55
+ console.error('❌ 未找到 .sillyspec/docs/<project>/modules/ 目录');
56
+ console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
57
+ return;
58
+ }
59
+
60
+ const modulesDir = join(mapPath, '..');
61
+ const existingMap = existsSync(mapPath) ? readFileSync(mapPath, 'utf8') : null;
62
+
63
+ // 收集现有模块卡片
64
+ const cards = [];
65
+ if (existsSync(modulesDir)) {
66
+ const files = readdirSync(modulesDir).filter(f => f.endsWith('.md') && f !== '_module-map.yaml');
67
+ for (const f of files) {
68
+ const content = readFileSync(join(modulesDir, f), 'utf8');
69
+ const fm = parseModuleCardFrontmatter(content);
70
+ const moduleId = fm.module_id || parseModuleIdFromFilename(f);
71
+ cards.push({ filename: f, moduleId });
72
+ }
73
+ }
74
+
75
+ // 解析现有 _module-map.yaml 保留已有字段
76
+ let existingModules = {};
77
+ if (existingMap) {
78
+ // 简单解析:提取 modules: 下的条目
79
+ const lines = existingMap.split('\n');
80
+ let currentModule = null;
81
+ let inModules = false;
82
+ for (const line of lines) {
83
+ if (line.startsWith('modules:')) { inModules = true; continue; }
84
+ if (!inModules) continue;
85
+ if (line.startsWith(' ') === false && line.trim() !== '') break;
86
+ const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):/);
87
+ if (moduleMatch) {
88
+ currentModule = moduleMatch[1];
89
+ existingModules[currentModule] = existingModules[currentModule] || { paths: [] };
90
+ }
91
+ }
92
+ }
93
+
94
+ // 如果没有模块卡片也没有已有映射
95
+ if (cards.length === 0 && Object.keys(existingModules).length === 0) {
96
+ console.log('📭 没有模块卡片,也没有已有的模块映射');
97
+ console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
98
+ return;
99
+ }
100
+
101
+ // 生成新的 _module-map.yaml
102
+ // 保留已有的完整字段,补齐新卡片
103
+ const now = new Date().toISOString().replace('T', ' ').slice(0, 19);
104
+ let headCommit = '';
105
+ try {
106
+ const { execSync } = await import('child_process');
107
+ headCommit = execSync('git rev-parse --short HEAD', { cwd, encoding: 'utf8', timeout: 5000 }).trim();
108
+ } catch { /* ignore */ }
109
+
110
+ let yaml = `schema_version: 1\n`;
111
+ yaml += `generated_at: ${now}\n`;
112
+ yaml += `generator: sillyspec-modules-rebuild\n`;
113
+ if (headCommit) yaml += `source_commit: ${headCommit}\n`;
114
+ yaml += `\nmodules:\n`;
115
+
116
+ // 合并:已有映射 + 新卡片
117
+ const allModuleIds = new Set([
118
+ ...Object.keys(existingModules),
119
+ ...cards.map(c => c.moduleId)
120
+ ]);
121
+
122
+ for (const moduleId of allModuleIds) {
123
+ const card = cards.find(c => c.moduleId === moduleId);
124
+ yaml += ` ${moduleId}:\n`;
125
+ yaml += ` status: active\n`;
126
+ if (card) yaml += ` doc: modules/${card.filename}\n`;
127
+ else yaml += ` doc: modules/${moduleId}.md\n`;
128
+ // 保留已有 _module-map.yaml 中的 paths 等字段(如果有的话)
129
+ yaml += ` needs_review: false\n`;
130
+ yaml += ` review_reasons: []\n`;
131
+ yaml += `\n`;
132
+ }
133
+
134
+ writeFileSync(mapPath, yaml, 'utf8');
135
+ console.log(`✅ _module-map.yaml 已重建:${mapPath}`);
136
+ console.log(` 模块数量:${allModuleIds.size}`);
137
+ for (const id of allModuleIds) {
138
+ console.log(` - ${id}`);
139
+ }
140
+ console.log(`\n⚠️ 注意:rebuild 只重建骨架。tags/entrypoints/main_symbols/depends_on/used_by 需要重新运行 scan 或手动补充。`);
141
+ }
142
+
143
+ /**
144
+ * status: 显示模块索引状态
145
+ */
146
+ export async function showModuleStatus(cwd) {
147
+ const mapPath = findModuleMapPath(cwd);
148
+ if (!mapPath || !existsSync(mapPath)) {
149
+ console.log('📭 未找到 _module-map.yaml');
150
+ console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
151
+ return;
152
+ }
153
+
154
+ const content = readFileSync(mapPath, 'utf8');
155
+
156
+ // 简单解析
157
+ const lines = content.split('\n');
158
+ const modules = [];
159
+ let currentModule = null;
160
+ let needsReview = false;
161
+
162
+ for (const line of lines) {
163
+ const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):$/);
164
+ if (moduleMatch) {
165
+ if (currentModule) modules.push(currentModule);
166
+ currentModule = { id: moduleMatch[1], hasTags: false, hasEntryPoints: false, hasDepends: false, needsReview: false };
167
+ }
168
+ if (currentModule) {
169
+ if (line.includes('tags:')) currentModule.hasTags = true;
170
+ if (line.includes('entrypoints:')) currentModule.hasEntryPoints = true;
171
+ if (line.includes('depends_on:')) currentModule.hasDepends = true;
172
+ if (line.includes('needs_review: true')) { currentModule.needsReview = true; needsReview = true; }
173
+ }
174
+ }
175
+ if (currentModule) modules.push(currentModule);
176
+
177
+ console.log(`\n_module-map.yaml: ${mapPath}`);
178
+ console.log(`模块数量:${modules.length}\n`);
179
+
180
+ const maxId = Math.max(5, ...modules.map(m => m.id.length));
181
+ console.log(` ${'模块'.padEnd(maxId)} tags entry deps review`);
182
+ console.log(` ${'─'.repeat(maxId)} ──── ───── ──── ──────`);
183
+ for (const m of modules) {
184
+ const tags = m.hasTags ? '✅' : '⬜';
185
+ const entry = m.hasEntryPoints ? '✅' : '⬜';
186
+ const deps = m.hasDepends ? '✅' : '⬜';
187
+ const review = m.needsReview ? '⚠️' : '✅';
188
+ console.log(` ${m.id.padEnd(maxId)} ${tags} ${entry} ${deps} ${review}`);
189
+ }
190
+
191
+ if (needsReview) {
192
+ console.log(`\n⚠️ 有模块标记为 needs_review,建议检查或重新 scan`);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * 从 _module-map.yaml 聚合生成 dependencies.md
198
+ */
199
+ export async function generateDependenciesMd(cwd) {
200
+ const mapPath = findModuleMapPath(cwd);
201
+ if (!mapPath || !existsSync(mapPath)) return;
202
+
203
+ const content = readFileSync(mapPath, 'utf8');
204
+ const modules = parseModuleMapSimple(content);
205
+
206
+ if (Object.keys(modules).length === 0) return;
207
+
208
+ const depsPath = join(mapPath, '..', 'dependencies.md');
209
+ const maxName = Math.max(6, ...Object.keys(modules).map(k => k.length));
210
+
211
+ let md = `# Module Dependencies\n\n`;
212
+ md += `> 自动生成,由 sillyspec 维护\n\n`;
213
+ md += `| ${'Module'.padEnd(maxName)} | Depends On | Used By |\n`;
214
+ md += `| ${''.padEnd(maxName, '-')}:--- | :--- | :--- |\n`;
215
+
216
+ for (const [id, data] of Object.entries(modules)) {
217
+ const depends = (data.depends_on || []).join(', ') || '—';
218
+ const usedBy = (data.used_by || []).join(', ') || '—';
219
+ md += `| ${id.padEnd(maxName)} | ${depends} | ${usedBy} |\n`;
220
+ }
221
+
222
+ writeFileSync(depsPath, md, 'utf8');
223
+ console.log(`✅ dependencies.md 已生成`);
224
+ }
225
+
226
+ function parseModuleMapSimple(content) {
227
+ const modules = {};
228
+ let currentModule = null;
229
+ let currentKey = null;
230
+ let currentArray = null;
231
+
232
+ for (const line of content.split('\n')) {
233
+ const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):$/);
234
+ if (moduleMatch) {
235
+ if (currentArray && currentModule && currentKey) {
236
+ modules[currentModule][currentKey] = currentArray;
237
+ }
238
+ currentModule = moduleMatch[1];
239
+ modules[currentModule] = {};
240
+ currentKey = null;
241
+ currentArray = null;
242
+ continue;
243
+ }
244
+ if (!currentModule) continue;
245
+
246
+ // Array field like depends_on:\n - xxx
247
+ const arrayFieldMatch = line.match(/^ (depends_on|used_by|paths|tags|aliases|entrypoints|main_symbols|review_reasons):$/);
248
+ if (arrayFieldMatch) {
249
+ if (currentArray && currentKey) modules[currentModule][currentKey] = currentArray;
250
+ currentKey = arrayFieldMatch[1];
251
+ currentArray = [];
252
+ continue;
253
+ }
254
+
255
+ // Inline array like tags: [a, b]
256
+ const inlineArrayMatch = line.match(/^ (depends_on|used_by|paths|tags|aliases|entrypoints|main_symbols|review_reasons): \[(.*)\]$/);
257
+ if (inlineArrayMatch) {
258
+ if (currentArray && currentKey) modules[currentModule][currentKey] = currentArray;
259
+ const vals = inlineArrayMatch[2].split(',').map(v => v.trim()).filter(Boolean);
260
+ modules[currentModule][inlineArrayMatch[1]] = vals;
261
+ currentKey = null;
262
+ currentArray = null;
263
+ continue;
264
+ }
265
+
266
+ // Scalar field
267
+ const scalarMatch = line.match(/^ (status|doc|needs_review): (.+)$/);
268
+ if (scalarMatch) {
269
+ if (currentArray && currentKey) { modules[currentModule][currentKey] = currentArray; currentArray = null; currentKey = null; }
270
+ modules[currentModule][scalarMatch[1]] = scalarMatch[2];
271
+ continue;
272
+ }
273
+
274
+ // Array item
275
+ const itemMatch = line.match(/^ - (.+)$/);
276
+ if (itemMatch && currentArray !== null) {
277
+ currentArray.push(itemMatch[1].trim());
278
+ continue;
279
+ }
280
+ }
281
+
282
+ // Flush last
283
+ if (currentArray && currentModule && currentKey) {
284
+ modules[currentModule][currentKey] = currentArray;
285
+ }
286
+
287
+ return modules;
288
+ }
289
+
290
+ /**
291
+ * migrate: 旧格式模块文档迁移到新格式
292
+ */
293
+ export async function migrateModuleDocs(cwd) {
294
+ const mapPath = findModuleMapPath(cwd);
295
+ if (!mapPath || !existsSync(mapPath)) {
296
+ console.error('❌ 未找到 _module-map.yaml');
297
+ return;
298
+ }
299
+
300
+ const modulesDir = join(mapPath, '..');
301
+ const files = readdirSync(modulesDir).filter(f => f.endsWith('.md') && f !== '_module-map.yaml');
302
+
303
+ if (files.length === 0) {
304
+ console.log('📭 没有模块文档需要迁移');
305
+ return;
306
+ }
307
+
308
+ let migrated = 0;
309
+ let skipped = 0;
310
+
311
+ for (const f of files) {
312
+ const filePath = join(modulesDir, f);
313
+ const content = readFileSync(filePath, 'utf8');
314
+
315
+ // 检查是否已经是新格式(有 schema_version + doc_type: module-card)
316
+ if (content.includes('schema_version:') && content.includes('doc_type: module-card')) {
317
+ skipped++;
318
+ continue;
319
+ }
320
+
321
+ // 提取模块名(从 H1 或文件名)
322
+ const h1Match = content.match(/^# (.+)$/m);
323
+ const moduleId = h1Match ? h1Match[1].trim().replace(/\s+/g, '-').toLowerCase() : f.replace('.md', '');
324
+
325
+ // 提取旧的“职责”和“注意事项”作为语义内容保留
326
+ const dutyMatch = content.match(/## 职责\n([\s\S]*?)(?=\n##|$)/);
327
+ const notesMatch = content.match(/## 注意事项\n([\s\S]*?)(?=\n##|$)/);
328
+ const designMatch = content.match(/## 当前设计\n([\s\S]*?)(?=\n##|$)/);
329
+ const interfaceMatch = content.match(/## 对外接口[\s\S]*?(?=\n##|$)/);
330
+
331
+ const duty = dutyMatch ? dutyMatch[1].trim() : '';
332
+ const notes = notesMatch ? notesMatch[1].trim() : '';
333
+ const design = designMatch ? designMatch[1].trim() : '';
334
+
335
+ // 检查是否有人工备注
336
+ const manualMatch = content.match(/<!-- MANUAL_NOTES_START -->([\s\S]*?)<!-- MANUAL_NOTES_END -->/);
337
+ const manualContent = manualMatch ? manualMatch[1].trim() : '';
338
+
339
+ // 生成新格式
340
+ let newContent = `---
341
+ schema_version: 1
342
+ doc_type: module-card
343
+ module_id: ${moduleId}
344
+ ---
345
+
346
+ # ${moduleId}
347
+
348
+ `;
349
+
350
+ if (duty) {
351
+ newContent += `## 定位
352
+
353
+ ${duty}
354
+
355
+ `;
356
+ } else {
357
+ newContent += `## 定位
358
+
359
+ (待补充)
360
+
361
+ `;
362
+ }
363
+
364
+ if (design || interfaceMatch) {
365
+ newContent += `## 契约摘要
366
+
367
+ `;
368
+ if (interfaceMatch) newContent += `${interfaceMatch[0].replace('## 对外接口', '').trim()}
369
+
370
+ `;
371
+ else newContent += `(待从源码提取)
372
+
373
+ `;
374
+ } else {
375
+ newContent += `## 契约摘要
376
+
377
+ (待从源码提取)
378
+
379
+ `;
380
+ }
381
+
382
+ if (design) {
383
+ newContent += `## 关键逻辑
384
+
385
+ ${design}
386
+
387
+ `;
388
+ } else {
389
+ newContent += `## 关键逻辑
390
+
391
+ (待补充)
392
+
393
+ `;
394
+ }
395
+
396
+ if (notes) {
397
+ newContent += `## 注意事项
398
+
399
+ ${notes}
400
+
401
+ `;
402
+ } else {
403
+ newContent += `## 注意事项
404
+
405
+ (待补充)
406
+
407
+ `;
408
+ }
409
+
410
+ newContent += `## 人工备注
411
+
412
+ <!-- MANUAL_NOTES_START -->
413
+ ${manualContent}
414
+ <!-- MANUAL_NOTES_END -->
415
+ `;
416
+
417
+ writeFileSync(filePath, newContent, 'utf8');
418
+ migrated++;
419
+ }
420
+
421
+ console.log(`✅ 迁移完成`);
422
+ console.log(` 已迁移:${migrated} 个`);
423
+ console.log(` 已跳过(新格式):${skipped} 个`);
424
+ if (migrated > 0) {
425
+ console.log(`\n⚠️ 迁移后的卡片可能需要补充内容,建议检查并运行 sillyspec modules status`);
426
+ }
427
+ }
package/src/progress.js CHANGED
@@ -393,6 +393,57 @@ export class ProgressManager {
393
393
  });
394
394
  }
395
395
 
396
+ /**
397
+ * 重命名变更:同步更新 DB + 目录
398
+ * @param {string} cwd - 项目根目录
399
+ * @param {string} oldName - 旧变更名
400
+ * @param {string} newName - 新变更名
401
+ */
402
+ async renameChange(cwd, oldName, newName) {
403
+ if (!oldName || !newName) {
404
+ console.warn('⚠️ renameChange: 旧名或新名为空,跳过');
405
+ return;
406
+ }
407
+ if (oldName === newName) {
408
+ console.warn('⚠️ renameChange: 新旧名称相同,跳过');
409
+ return;
410
+ }
411
+ const db = await this._ensureDB(cwd);
412
+ // 检查旧名是否存在
413
+ const existing = db.transaction((sqlDb) => {
414
+ const row = sqlDb.exec(`SELECT name, status FROM changes WHERE name = ?`, [oldName]);
415
+ if (!row || !row[0] || row[0].values.length === 0) return null;
416
+ return { name: row[0].values[0][0], status: row[0].values[0][1] };
417
+ });
418
+ if (!existing) {
419
+ console.error(`❌ 变更 ${oldName} 不存在`);
420
+ return;
421
+ }
422
+ // 检查新名是否已存在
423
+ const conflict = db.transaction((sqlDb) => {
424
+ const row = sqlDb.exec(`SELECT name FROM changes WHERE name = ?`, [newName]);
425
+ return row && row[0] && row[0].values.length > 0;
426
+ });
427
+ if (conflict) {
428
+ console.error(`❌ 变更 ${newName} 已存在`);
429
+ return;
430
+ }
431
+ // 重命名目录
432
+ const oldDir = this._changePath(cwd, oldName);
433
+ const newDir = this._changePath(cwd, newName);
434
+ if (existsSync(oldDir)) {
435
+ renameSync(oldDir, newDir);
436
+ } else {
437
+ mkdirSync(newDir, { recursive: true });
438
+ }
439
+ // 更新 DB
440
+ const now = new Date().toISOString();
441
+ db.transaction((sqlDb) => {
442
+ sqlDb.run(`UPDATE changes SET name = ?, last_active = ? WHERE name = ?`, [newName, now, oldName]);
443
+ });
444
+ console.log(`✅ 变更已重命名:${oldName} → ${newName}`);
445
+ }
446
+
396
447
  /**
397
448
  * 从活跃列表移除变更(归档时调用,不物理删除)
398
449
  * SQL: UPDATE changes SET status = 'archived'
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 {
@@ -35,10 +35,11 @@ export const definition = {
35
35
  5. 将 git diff 文件按 \`_module-map.yaml\` 的 paths glob 匹配到模块
36
36
  6. 生成模块影响矩阵:
37
37
 
38
- | 模块 | 影响类型 | 相关文件 | 更新内容摘要 |
39
- |------|----------|----------|-------------|
38
+ | 模块 | 影响类型 | 相关文件 | 更新内容摘要 | needs_review |
39
+ |------|----------|----------|-------------|-------------|
40
40
 
41
41
  影响类型:逻辑变更 / 数据结构变更 / 接口变更 / 调用关系变更 / 配置变更 / 新增
42
+ needs_review:如果影响无法完全确定,标记为 true
42
43
 
43
44
  7. 未匹配到任何模块的文件归入"未匹配文件"表格
44
45
  8. 生成 \`.sillyspec/changes/<change-name>/module-impact.md\`,格式:
@@ -52,8 +53,8 @@ created_at: <now-datetime>
52
53
  ## 变更:<change-name>
53
54
 
54
55
  ## 模块影响矩阵
55
- | 模块 | 影响类型 | 相关文件 | 更新内容摘要 |
56
- |------|----------|----------|-------------|
56
+ | 模块 | 影响类型 | 相关文件 | 更新内容摘要 | needs_review |
57
+ |------|----------|----------|-------------|-------------|
57
58
 
58
59
  ## 未匹配文件
59
60
  | 文件路径 | 说明 |
@@ -61,8 +62,8 @@ created_at: <now-datetime>
61
62
 
62
63
  ## 更新结果
63
64
  (sync-module-docs 步骤完成后回填)
64
- | 模块文档 | 操作 | 状态 |
65
- |----------|------|------|
65
+ | 目标 | 操作 | 状态 |
66
+ |------|------|------|
66
67
  \`\`\`
67
68
 
68
69
  ### 输出
@@ -72,71 +73,74 @@ module-impact.md 路径 + 影响模块数量 + 未匹配文件数量`,
72
73
  },
73
74
  {
74
75
  name: 'sync-module-docs',
75
- prompt: `根据 module-impact.md 同步更新模块设计文档。
76
+ prompt: `根据 module-impact.md 同步更新模块索引和卡片文档。
76
77
 
77
- ### 原则
78
- - 模块文档正文**永远描述当前状态**(快照模式),不是变更日志
79
- - 底部只保留轻量变更索引
80
- - 模块文档是下一次 AI 开发前必须读取的上下文
78
+ ### ⚠️ 核心原则:结构化事实改 _module-map.yaml,语义解释改模块卡片
79
+ - \`_module-map.yaml\` 是唯一的结构化索引源(paths/tags/entrypoints/depends_on/used_by/status/needs_review)
80
+ - 模块卡片只负责语义说明(定位/契约摘要/关键逻辑/注意事项/人工备注)
81
+ - 一个信息只维护一次,不要两边重复
81
82
 
82
83
  ### 操作
83
84
  1. 读取 \`.sillyspec/changes/<change-name>/module-impact.md\`
84
85
  2. 如果没有受影响模块(只有 unmapped)→ 提示用户,跳过同步
85
- 3. 对每个受影响模块:
86
- a. 读取 \`.sillyspec/docs/<project>/modules/<module>.md\`(如不存在则新建)
87
- b. 根据 module-impact.md 中的"更新内容摘要",更新模块文档
88
- c. **更新规则**:
89
- - 新建:全量生成,使用下方模板
90
- - 更新:只改相关章节(当前设计/对外接口/依赖关系等),保持其他章节不变
91
- - 正文重写为当前状态,不追加历史
92
- - 底部"变更索引"追加一行:\`| <日期> | <变更名> | <一句话摘要> |\`
93
- d. 更新头部元数据:\`> 最后更新:<now-date>\`、\`> 最近变更:<change-name>\`
94
- 4. 展示所有模块文档的更新内容(diff 摘要),请用户确认
95
- 5. 用户确认后,写入 \`.sillyspec/docs/<project>/modules/*.md\`
96
- 6. 用户拒绝时,不写入模块文档,但提示"module-impact.md 已保留,可稍后手动同步"
97
- 7. 回填 module-impact.md "更新结果"表格
98
-
99
- ### 模块文档模板
86
+ 3. 对每个受影响模块,按影响类型分别更新:
87
+
88
+ #### 更新 _module-map.yaml 的规则
89
+ - **路径变化** → 更新对应模块的 paths
90
+ - **依赖变化** → 更新 depends_on / used_by(同时更新反向模块的 used_by / depends_on)
91
+ - **导出符号变化** → 更新 entrypoints / main_symbols
92
+ - **新增模块** → 添加完整条目
93
+ - **模块废弃** status: deprecated
94
+ - **不确定的影响** needs_review: true, review_reasons 追加原因
95
+ - 如果 _module-map.yaml generated_at 已过时,更新为当前时间
96
+
97
+ #### 更新模块卡片(modules/<module-id>.md)的规则
98
+ - **契约语义变化**(新增/删除对外能力) 更新"契约摘要"
99
+ - **关键逻辑变化** → 更新"关键逻辑"
100
+ - **边界变化**(模块职责扩大/缩小) → 更新"定位"
101
+ - **注意事项变化** → 更新"注意事项"
102
+ - **内部实现变化**(不影响对外接口) → 通常不更新卡片
103
+ - **人工备注** → 永远保护,不覆盖
104
+
105
+ #### 人工备注保护
106
+ 1. 用正则提取 \`<!-- MANUAL_NOTES_START -->\` 到 \`<!-- MANUAL_NOTES_END -->\` 之间的内容
107
+ 2. 生成新卡片后,原样回填到人工备注区域
108
+ 3. 如果标记缺失或重复 → 在 _module-map.yaml 中标记 needs_review: true
109
+
110
+ #### 新建模块卡片模板
100
111
  \`\`\`markdown
101
- # <module-name>
102
-
103
- > 最后更新:<now-date>
104
- > 最近变更:<change-name>
105
- > 模块路径:<glob patterns>
112
+ ---
113
+ schema_version: 1
114
+ doc_type: module-card
115
+ module_id: <module-id>
116
+ ---
106
117
 
107
- ## 职责
108
- (一句话说清这个模块做什么)
118
+ # <module-id>
109
119
 
110
- ## 当前设计
111
- (架构、数据流、关键逻辑 — 描述当前状态,不是历史)
120
+ ## 定位
112
121
 
113
- ## 对外接口
114
- | 接口 | 说明 | 调用方 |
115
- |------|------|--------|
122
+ ## 契约摘要
116
123
 
117
- ## 关键数据流
118
- \`\`\`text
119
- 调用方 → 模块.方法() → 依赖模块.方法() → 返回结果
120
- \`\`\`
124
+ ## 关键逻辑
121
125
 
122
- ## 设计决策
123
- | 决策 | 理由 | 来源 |
124
- |------|------|------|
126
+ ## 注意事项
125
127
 
126
- ## 依赖关系
127
- ### 依赖本模块
128
- ### 本模块依赖
128
+ ## 人工备注
129
129
 
130
- ## 注意事项
131
- (维护提醒、已知限制、修改时需同步检查的模块)
130
+ <!-- MANUAL_NOTES_START -->
132
131
 
133
- ## 变更索引
134
- | 日期 | 变更 | 摘要 |
135
- |------|------|------|
132
+ <!-- MANUAL_NOTES_END -->
136
133
  \`\`\`
137
134
 
135
+ 4. 展示所有更新内容(diff 摘要),请用户确认
136
+ 5. 用户确认后,写入 _module-map.yaml 和受影响的模块卡片
137
+ 6. 用户拒绝时,不写入,但提示"module-impact.md 已保留,可稍后手动同步"
138
+ 7. 回填 module-impact.md 的"更新结果"表格,区分目标:
139
+ - 目标列写 "\`_module-map.yaml: <module-id>\`" 或 "\`modules/<module-id>.md\`"
140
+ 8. **同步完成后**,运行 \`sillyspec modules rebuild\` 刷新索引(如果需要),或手动更新 dependencies.md
141
+
138
142
  ### 输出
139
- 已更新的模块文档路径列表 + 用户确认状态`,
143
+ 已更新的文件路径列表 + 用户确认状态`,
140
144
  outputHint: '模块文档更新结果',
141
145
  optional: false
142
146
  },
@@ -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. 查看进行中的变更:\`ls .sillyspec/changes/ | grep -v archive\`
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
  },
@@ -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
  \`\`\`
@@ -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
  },
@@ -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
  },
@@ -191,52 +191,127 @@ local.yaml 生成结果(已存在/已生成)`,
191
191
  },
192
192
  {
193
193
  name: '生成模块映射',
194
- prompt: `生成模块映射配置文件,建立"文件路径 模块"的稳定映射。
194
+ prompt: `生成模块索引文件 \`_module-map.yaml\`,它是 agent 高频读取的机器索引。
195
+
196
+ ### ⚠️ 重要:这个文件是唯一的结构化索引源
197
+ 所有结构化事实(paths/tags/entrypoints/depends_on/used_by)只维护在这个文件里。
198
+ 模块卡片(modules/*.md)只负责人类语义说明,不重复索引信息。
195
199
 
196
200
  ### 操作
197
- 1. 检查 \.sillyspec/docs/<project>/modules/_module-map.yaml\` 是否已存在,已存在则跳过
198
- 2. 分析项目 src/ 目录结构(或主代码目录),识别模块划分:
199
- - 用 \`find . -maxdepth 2 -type d -not -path "*/node_modules/*" -not -path "*/.git/*"\` 查看目录结构
200
- - 每个独立目录(有明确职责的)识别为一个模块
201
- - 路径用 glob 模式(如 \`src/auth/**\`)
202
- 3. 生成 \.sillyspec/docs/<project>/modules/_module-map.yaml\`
203
- 4. 如果 modules/ 目录不存在,先创建
204
- 5. 原子写入(先写 tmp 文件再 rename)
201
+ 1. 检查 \`.sillyspec/docs/<project>/modules/_module-map.yaml\` 是否已存在,已存在则跳过
202
+ 2. 分析项目源码目录结构,识别模块划分:
203
+ - 用 \`find . -maxdepth 3 -type d -not -path "*/node_modules/*" -not -path "*/.git/*"\` 查看目录结构
204
+ - 每个有明确职责的独立目录识别为一个模块
205
+ - 路径用 glob 模式
206
+ 3. grep/rg 分析每个模块:
207
+ - \`main_symbols\`:模块导出的主要函数/类/常量(grep export / module.exports / def / class)
208
+ - \`entrypoints\`:对外 API 端点或命令入口(grep route / router / @Controller / @GetMapping 等)
209
+ - \`tags\`:模块相关关键词标签
210
+ - \`aliases\`:模块的别名(其他开发者可能怎么称呼这个模块)
211
+ 4. 分析跨模块依赖关系:
212
+ - 用 grep import/require 分析模块间的引用链
213
+ - 填充 depends_on(本模块依赖谁)和 used_by(谁依赖本模块)
214
+ 5. 生成 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`
215
+ 6. 如果 modules/ 目录不存在,先创建
216
+ 7. 原子写入(先写 tmp 文件再 rename)
205
217
 
206
218
  ### YAML 格式
207
219
  \`\`\`yaml
208
- # 模块映射(自动生成,可手动修改)
209
- # 用于 archive 阶段识别变更影响的模块
220
+ schema_version: 1
221
+ project: <project-name>
222
+ source_commit: <git-head-short>
223
+ generated_at: <now-datetime>
224
+ generator: sillyspec-scan
225
+
210
226
  modules:
211
- <module-name>:
227
+ <module-id>:
228
+ status: active
229
+ doc: modules/<module-id>.md
212
230
  paths:
213
231
  - <glob-pattern>
214
- description: <一句话描述>
232
+ tags:
233
+ - <tag1>
234
+ - <tag2>
235
+ aliases:
236
+ - <alias1>
237
+ entrypoints:
238
+ - <exported-symbol-or-api-endpoint>
239
+ main_symbols:
240
+ - <exported-class-or-function>
241
+ depends_on:
242
+ - <other-module-id>
243
+ used_by:
244
+ - <other-module-id>
245
+ needs_review: false
246
+ concerns: []
247
+ review_reasons: []
215
248
  \`\`\`
216
249
 
217
250
  ### 示例
218
251
  \`\`\`yaml
219
- modules:
220
- core:
221
- paths:
222
- - src/core/**
223
- - src/utils/**
224
- description: 核心工具和公共逻辑
252
+ schema_version: 1
253
+ project: multi-agent-platform
254
+ source_commit: abc1234
255
+ generated_at: 2026-06-02 22:00:00
256
+ generator: sillyspec-scan
225
257
 
226
- stages:
258
+ modules:
259
+ auth-service:
260
+ status: active
261
+ doc: modules/auth-service.md
227
262
  paths:
228
- - src/stages/**
229
- description: 阶段定义(brainstorm/plan/execute/verify/archive等)
263
+ - src/modules/auth/**
264
+ - src/middleware/auth.js
265
+ tags:
266
+ - auth
267
+ - jwt
268
+ - rbac
269
+ - middleware
270
+ aliases:
271
+ - login
272
+ - token
273
+ - authentication
274
+ entrypoints:
275
+ - authenticate
276
+ - authorize
277
+ - signToken
278
+ - refreshToken
279
+ main_symbols:
280
+ - AuthService
281
+ - AuthController
282
+ - hashPassword
283
+ - verifyPassword
284
+ depends_on:
285
+ - users
286
+ - redis
287
+ used_by:
288
+ - api-routes
289
+ - admin-routes
290
+ needs_review: false
291
+ concerns: []
292
+ review_reasons: []
230
293
  \`\`\`
231
294
 
295
+ ### 关键规则
296
+ - module-id 用 kebab-case(如 auth-service)
297
+ - depends_on / used_by 引用其他模块的 module-id
298
+ - tags 和 aliases 用于 brainstorm 阶段的需求→模块匹配,尽量覆盖开发者可能用的词
299
+ - entrypoints 和 main_symbols 用于 execute 阶段快速定位源码
300
+ - 不要编造无法从源码 grep 到的符号
301
+ - 如果无法确定依赖关系,depends_on/used_by 留空列表,不要猜
302
+
232
303
  ### 输出
233
304
  _module-map.yaml 生成结果(已存在/已生成/模块列表)`,
234
305
  outputHint: '_module-map.yaml 生成状态',
235
306
  optional: true
236
307
  },
237
308
  {
238
- name: '生成模块核心文档',
239
- prompt: `根据 _module-map.yaml 中的模块划分,为每个模块生成核心文档(用于后续归档和开发上下文)。
309
+ name: '生成模块卡片文档',
310
+ prompt: `根据 _module-map.yaml 中的模块划分,为每个模块生成精简卡片文档。
311
+
312
+ ### ⚠️ 重要:模块卡片只负责人类语义说明
313
+ 结构化索引(paths/tags/entrypoints/depends_on/used_by)已经在 _module-map.yaml 里维护。
314
+ 卡片里不要重复这些信息,只写 _module-map.yaml 无法表达的语义内容。
240
315
 
241
316
  ### 操作
242
317
  1. 读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`,获取模块列表和路径
@@ -255,32 +330,48 @@ _module-map.yaml 生成结果(已存在/已生成/模块列表)`,
255
330
  - 环境探测结果摘要(构建工具、语言框架)
256
331
  - scan 文档关键信息摘要(ARCHITECTURE.md 的技术栈、CONVENTIONS.md 的代码风格要点,如已生成)
257
332
  \`\`\`
258
- 模块名:<module-name>
333
+ 模块名:<module-id>
259
334
  模块路径:<glob patterns>
260
- 目标文件:.sillyspec/docs/<project>/modules/<module>.md
335
+ 目标文件:.sillyspec/docs/<project>/modules/<module-id>.md
261
336
 
262
337
  操作:
263
338
  1. 用 grep/rg 搜索模块路径范围内的源码(禁止读源码全文)
264
- 2. 提取:模块职责、对外接口(导出函数/API)、关键依赖、设计要点
339
+ 2. 提取:模块职责、对外接口、关键逻辑、注意事项
265
340
  3. 按以下模板写入目标文件:
266
341
 
267
- # <module-name>
268
- > 最后更新:<now-date>
269
- > 最近变更:scan(初始生成)
270
- > 模块路径:<glob patterns>
271
-
272
- ## 职责
273
- ## 当前设计
274
- ## 对外接口(表格)
275
- ## 关键数据流
276
- ## 设计决策(表格)
277
- ## 依赖关系
342
+ ---
343
+ schema_version: 1
344
+ doc_type: module-card
345
+ module_id: <module-id>
346
+ ---
347
+
348
+ # <module-id>
349
+
350
+ ## 定位
351
+ (负责什么,不负责什么 — 明确边界)
352
+
353
+ ## 契约摘要
354
+ (核心能力列表,具体导出符号以 _module-map.yaml 的 entrypoints/main_symbols 为准)
355
+
356
+ ## 关键逻辑
357
+ (最核心的流程摘要,用 text 伪代码,不超过 3-5 行)
358
+
278
359
  ## 注意事项
279
- ## 变更索引(表格,初始为空)
360
+ (维护提醒、已知限制、修改时需同步检查的模块)
361
+
362
+ ## 人工备注
363
+
364
+ <!-- MANUAL_NOTES_START -->
365
+
366
+ <!-- MANUAL_NOTES_END -->
280
367
 
281
368
  规则:
282
369
  - 不要编造接口或依赖,只写 grep/rg 能搜到的
283
- - 模板与 archive 阶段格式一致
370
+ - 目标长度:500-1000 / 80-150 行
371
+ - 如果模块特别复杂(状态机、多角色交互、复杂领域规则),可以在 modules/details/ 下生成扩展文档(如 details/<module-id>-flow.md),agent 默认不读
372
+ - 不要重复 _module-map.yaml 中的索引信息
373
+ - 不要写设计决策表、完整依赖表、变更索引长表
374
+ - 人工备注区域保持空标记,留给用户填写
284
375
  \`\`\`
285
376
 
286
377
  等待所有子代理完成,验证文件是否生成且非空。
@@ -290,6 +381,72 @@ _module-map.yaml 生成结果(已存在/已生成/模块列表)`,
290
381
  outputHint: '模块文档生成状态',
291
382
  optional: true
292
383
  },
384
+ {
385
+ name: '生成业务流程和术语表(可选)',
386
+ prompt: `根据模块依赖关系和源码,生成跨模块业务流程文档和术语表。
387
+
388
+ ⚠️ 这一步是可选的。如果项目模块简单、流程不明显,可以跳过。
389
+
390
+ ### flows/ 目录
391
+ 目标目录:\`.sillyspec/docs/<project>/flows/\`
392
+
393
+ 根据 _module-map.yaml 中的模块依赖关系,识别跨模块业务流程:
394
+ 1. 读取 \`_module-map.yaml\`,分析 used_by 链条
395
+ 2. 用 grep/rg 搜索路由定义、API 端点、事件处理
396
+ 3. 识别跨模块的完整业务流程(如登录→下单→支付)
397
+ 4. 每个流程生成一个文件:\`flows/<flow-name>.md\`
398
+
399
+ 文件格式:
400
+ \`\`\`markdown
401
+ # <flow-name>
402
+
403
+ ## 目标
404
+ (一句话描述这个流程的业务目的)
405
+
406
+ ## 参与模块
407
+ - module-a:做什么
408
+ - module-b:做什么
409
+
410
+ ## 流程摘要
411
+ \`\`\`text
412
+ step1 → step2 → step3
413
+ \`\`\`
414
+
415
+ ## 失败回滚
416
+ | 失败点 | 处理 |
417
+ |---|---|
418
+ \`\`\`
419
+
420
+ ### glossary.md
421
+ 目标文件:\`.sillyspec/docs/<project>/glossary.md\`
422
+
423
+ 提取项目专有术语:
424
+ 1. 用 grep 搜索 TODO/FIXME 注释中的术语定义
425
+ 2. 从数据库表注释提取
426
+ 3. 从 README 和文档中提取定义段落
427
+
428
+ 文件格式:
429
+ \`\`\`markdown
430
+ # Glossary
431
+
432
+ ## Session
433
+ 在本项目中,session 指...(项目内特殊含义)
434
+
435
+ ## Order
436
+ 订单主实体,代表...(业务定义)
437
+ \`\`\`
438
+
439
+ ### 操作
440
+ 1. 分析模块依赖关系,识别可能的业务流程
441
+ 2. 如果发现 2+ 个跨模块流程,生成 flows/ 文档
442
+ 3. 提取术语生成 glossary.md
443
+ 4. 如果没有明显的流程或术语,跳过此步
444
+
445
+ ### 输出
446
+ 生成的文件路径列表(或"已跳过")`,
447
+ outputHint: '流程和术语表生成状态',
448
+ optional: true
449
+ },
293
450
  {
294
451
  name: '自检和提交',
295
452
  prompt: `验证扫描完整性,清理并提交。
@@ -298,8 +455,9 @@ _module-map.yaml 生成结果(已存在/已生成/模块列表)`,
298
455
  1. 检查 7 份 scan 文档是否全部生成
299
456
  2. 检查模块文档状态(如有)
300
457
  3. 自检门控:ARCHITECTURE(技术栈+Schema摘要)、CONVENTIONS(隐形规则+代码风格)、STRUCTURE(目录结构)、INTEGRATIONS(外部依赖)、TESTING(测试现状)、CONCERNS(技术债务)、PROJECT(项目概览)
301
- 4. 清理:\`rm -f .sillyspec/docs/<project>/scan/_env-detect.md\`
302
- 5. \`git add .sillyspec/\` — 暂存扫描结果(不要 commit,由用户通过统一提交工具处理)
458
+ 4. 检查 flows/ 和 glossary.md 是否已生成(如有)
459
+ 5. 清理:\`rm -f .sillyspec/docs/<project>/scan/_env-detect.md\`
460
+ 6. \`git add .sillyspec/\` — 暂存扫描结果(不要 commit,由用户通过统一提交工具处理)
303
461
 
304
462
  ### 输出
305
463
  扫描完整性报告
@@ -63,9 +63,10 @@ export const definition = {
63
63
  6. 读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`(不存在则跳过以下步骤)
64
64
  7. 根据 design.md 的文件变更清单匹配 _module-map.yaml 中的模块
65
65
  8. 读取匹配到的 \`.sillyspec/docs/<project>/modules/<module>.md\`
66
+ 9. **检查模块索引可信度**:如果相关模块的 needs_review 为 true,提示"该模块索引可能不可信,需要回看模块卡片或源码"
66
67
 
67
68
  ### 输出
68
- 文件加载确认清单(含模块文档)`,
69
+ 文件加载确认清单(含模块文档 + 索引可信度)`,
69
70
  outputHint: '文件确认清单',
70
71
  optional: false
71
72
  },