sillyspec 3.20.2 → 3.20.4

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 (133) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +21 -21
  2. package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
  4. package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
  7. package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
  8. package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
  9. package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
  10. package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
  11. package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
  12. package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
  13. package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
  14. package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
  15. package/.claude/skills/sillyspec-state/SKILL.md +54 -54
  16. package/.claude/skills/sillyspec-status/SKILL.md +21 -21
  17. package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
  18. package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
  19. package/.husky/pre-push +13 -13
  20. package/CLAUDE.md +18 -18
  21. package/README.md +198 -188
  22. package/SKILL.md +90 -91
  23. package/bin/sillyspec.js +2 -2
  24. package/docs/brainstorm-plan-contract.md +64 -64
  25. package/docs/plan-execute-contract.md +123 -123
  26. package/docs/platform-scan-protocol.md +298 -298
  27. package/docs/revision-mode.md +115 -115
  28. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
  29. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
  30. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
  31. package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
  32. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
  33. package/docs/sillyspec/file-lifecycle.md +125 -125
  34. package/docs/workflow-contract-regression.md +106 -106
  35. package/docs/worktree-isolation.md +252 -252
  36. package/package.json +40 -40
  37. package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
  38. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
  39. package/packages/dashboard/dist/index.html +16 -16
  40. package/packages/dashboard/index.html +15 -15
  41. package/packages/dashboard/package-lock.json +2384 -2384
  42. package/packages/dashboard/package.json +25 -25
  43. package/packages/dashboard/server/executor.js +86 -86
  44. package/packages/dashboard/server/index.js +588 -588
  45. package/packages/dashboard/server/parser.js +526 -526
  46. package/packages/dashboard/server/watcher.js +344 -344
  47. package/packages/dashboard/src/App.vue +558 -558
  48. package/packages/dashboard/src/components/ActionBar.vue +93 -93
  49. package/packages/dashboard/src/components/CommandPalette.vue +96 -96
  50. package/packages/dashboard/src/components/DetailPanel.vue +137 -137
  51. package/packages/dashboard/src/components/LogStream.vue +65 -65
  52. package/packages/dashboard/src/components/PipelineStage.vue +95 -95
  53. package/packages/dashboard/src/components/PipelineView.vue +156 -156
  54. package/packages/dashboard/src/components/ProjectList.vue +210 -210
  55. package/packages/dashboard/src/components/StageBadge.vue +67 -67
  56. package/packages/dashboard/src/components/StepCard.vue +94 -94
  57. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
  58. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
  59. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
  60. package/packages/dashboard/src/composables/useDashboard.js +170 -170
  61. package/packages/dashboard/src/composables/useKeyboard.js +119 -119
  62. package/packages/dashboard/src/composables/useWebSocket.js +129 -129
  63. package/packages/dashboard/src/main.js +8 -8
  64. package/packages/dashboard/src/style.css +132 -132
  65. package/packages/dashboard/vite.config.js +18 -18
  66. package/src/brainstorm-postcheck.js +158 -158
  67. package/src/change-list.js +52 -52
  68. package/src/change-risk-profile.js +352 -352
  69. package/src/classify-change.js +73 -73
  70. package/src/constants.js +70 -70
  71. package/src/contract-matrix.js +278 -278
  72. package/src/db.js +201 -201
  73. package/src/endpoint-extractor.js +315 -315
  74. package/src/hooks/claude-pre-tool-use.cjs +125 -125
  75. package/src/hooks/worktree-guard.js +653 -653
  76. package/src/index.js +922 -900
  77. package/src/init.js +431 -431
  78. package/src/knowledge-match.js +130 -130
  79. package/src/migrate.js +117 -117
  80. package/src/modules.js +482 -482
  81. package/src/progress.js +1734 -1734
  82. package/src/run.js +3465 -3358
  83. package/src/scan-postcheck.js +387 -383
  84. package/src/setup.js +398 -398
  85. package/src/stage-contract.js +700 -700
  86. package/src/stages/archive.js +160 -160
  87. package/src/stages/brainstorm-auto.js +229 -229
  88. package/src/stages/brainstorm.js +645 -645
  89. package/src/stages/doctor.js +365 -365
  90. package/src/stages/execute.js +625 -625
  91. package/src/stages/explore.js +34 -34
  92. package/src/stages/index.js +29 -29
  93. package/src/stages/knowledge.js +498 -498
  94. package/src/stages/plan-postcheck.js +511 -513
  95. package/src/stages/plan.js +582 -582
  96. package/src/stages/propose.js +174 -174
  97. package/src/stages/quick.js +82 -82
  98. package/src/stages/scan.js +558 -558
  99. package/src/stages/status.js +65 -65
  100. package/src/stages/verify.js +322 -322
  101. package/src/sync.js +497 -497
  102. package/src/task-review.js +346 -346
  103. package/src/workflow.js +785 -785
  104. package/src/worktree-apply.js +549 -549
  105. package/src/worktree-deps.js +185 -0
  106. package/src/worktree.js +982 -932
  107. package/templates/workflows/archive-impact.yaml +79 -79
  108. package/templates/workflows/scan-docs.yaml +132 -132
  109. package/test/brainstorm-plan-contract.test.mjs +273 -273
  110. package/test/check-syntax.mjs +26 -26
  111. package/test/contract-artifacts.test.mjs +323 -323
  112. package/test/decision-supersede.test.mjs +277 -277
  113. package/test/knowledge-match.test.mjs +231 -231
  114. package/test/plan-execute-contract.test.mjs +330 -330
  115. package/test/plan-optimization.test.mjs +572 -572
  116. package/test/platform-artifacts.test.mjs +166 -166
  117. package/test/platform-failure-samples.test.mjs +199 -199
  118. package/test/platform-recovery-chain.test.mjs +167 -167
  119. package/test/platform-recovery.test.mjs +136 -136
  120. package/test/platform-scan-p0.test.mjs +168 -168
  121. package/test/revision-v1.test.mjs +1145 -1145
  122. package/test/run-scan-project-parse.test.mjs +200 -200
  123. package/test/run-tests.mjs +48 -48
  124. package/test/scan-knowledge.test.mjs +175 -175
  125. package/test/scan-paths.test.mjs +68 -68
  126. package/test/scan-postcheck.test.mjs +197 -197
  127. package/test/spec-dir.test.mjs +206 -206
  128. package/test/stage-contract.test.mjs +299 -299
  129. package/test/stage-definitions.test.mjs +39 -39
  130. package/test/wait-gates.test.mjs +496 -496
  131. package/test/worktree-deps-provision.test.mjs +148 -0
  132. package/test/worktree-guard.test.mjs +71 -71
  133. package/test/worktree-native-overlay.test.mjs +188 -188
package/src/modules.js CHANGED
@@ -1,482 +1,482 @@
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
- * 从模块卡片 + 源码重建 _module-map.yaml
51
- *
52
- * schema_version 2 扩展字段:
53
- * - role: 模块职责描述
54
- * - core_files: 核心源文件列表
55
- * - test_files: 测试文件列表
56
- * - entrypoints: 入口点
57
- * - depends_on: 依赖的其他模块
58
- * - used_by: 被哪些模块使用
59
- * - risk_level: 风险等级(low/medium/high)
60
- * - verify_commands: 验证命令(build/test/lint)
61
- * - related_docs: 关联文档路径
62
- */
63
- export async function rebuildModuleMap(cwd) {
64
- const mapPath = findModuleMapPath(cwd);
65
- if (!mapPath) {
66
- console.error('❌ 未找到 .sillyspec/docs/<project>/modules/ 目录');
67
- console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
68
- return;
69
- }
70
-
71
- const modulesDir = join(mapPath, '..');
72
- const existingMap = existsSync(mapPath) ? readFileSync(mapPath, 'utf8') : null;
73
-
74
- // 收集现有模块卡片
75
- const cards = [];
76
- if (existsSync(modulesDir)) {
77
- const files = readdirSync(modulesDir).filter(f => f.endsWith('.md') && f !== '_module-map.yaml');
78
- for (const f of files) {
79
- const content = readFileSync(join(modulesDir, f), 'utf8');
80
- const fm = parseModuleCardFrontmatter(content);
81
- const moduleId = fm.module_id || parseModuleIdFromFilename(f);
82
- cards.push({ filename: f, moduleId });
83
- }
84
- }
85
-
86
- // 解析现有 _module-map.yaml 保留已有字段
87
- let existingModules = {};
88
- if (existingMap) {
89
- // 简单解析:提取 modules: 下的条目
90
- const lines = existingMap.split('\n');
91
- let currentModule = null;
92
- let inModules = false;
93
- for (const line of lines) {
94
- if (line.startsWith('modules:')) { inModules = true; continue; }
95
- if (!inModules) continue;
96
- if (line.startsWith(' ') === false && line.trim() !== '') break;
97
- const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):/);
98
- if (moduleMatch) {
99
- currentModule = moduleMatch[1];
100
- existingModules[currentModule] = existingModules[currentModule] || { paths: [] };
101
- }
102
- }
103
- }
104
-
105
- // 如果没有模块卡片也没有已有映射
106
- if (cards.length === 0 && Object.keys(existingModules).length === 0) {
107
- console.log('📭 没有模块卡片,也没有已有的模块映射');
108
- console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
109
- return;
110
- }
111
-
112
- // 生成新的 _module-map.yaml
113
- // 保留已有的完整字段,补齐新卡片
114
- const now = new Date().toISOString().replace('T', ' ').slice(0, 19);
115
- let headCommit = '';
116
- try {
117
- const { execSync } = await import('child_process');
118
- headCommit = execSync('git rev-parse --short HEAD', { cwd, encoding: 'utf8', timeout: 5000 }).trim();
119
- } catch { /* ignore */ }
120
-
121
- let yaml = `schema_version: 2\n`;
122
- yaml += `generated_at: ${now}\n`;
123
- yaml += `generator: sillyspec-modules-rebuild\n`;
124
- if (headCommit) yaml += `source_commit: ${headCommit}\n`;
125
- yaml += `\n# Module Context Index — 机器可读的模块上下文索引\n`;
126
- yaml += `# scan 阶段自动生成,brainstorm/plan/execute 阶段按任务命中模块精准注入上下文\n`;
127
- yaml += `\nmodules:\n`;
128
-
129
- // 合并:已有映射 + 新卡片
130
- const allModuleIds = new Set([
131
- ...Object.keys(existingModules),
132
- ...cards.map(c => c.moduleId)
133
- ]);
134
-
135
- for (const moduleId of allModuleIds) {
136
- const card = cards.find(c => c.moduleId === moduleId);
137
- const cardContent = card ? readFileSync(join(modulesDir, card.filename), 'utf8') : '';
138
-
139
- // 从模块卡片提取 context index 字段
140
- const role = extractSection(cardContent, '定位') || '';
141
- const contract = extractSection(cardContent, '契约摘要') || '';
142
- const logic = extractSection(cardContent, '关键逻辑') || '';
143
- const notes = extractSection(cardContent, '注意事项') || '';
144
-
145
- yaml += ` ${moduleId}:\n`;
146
- yaml += ` status: active\n`;
147
- if (card) yaml += ` doc: modules/${card.filename}\n`;
148
- else yaml += ` doc: modules/${moduleId}.md\n`;
149
- yaml += ` needs_review: false\n`;
150
- yaml += ` review_reasons: []\n`;
151
- // context index 字段(v2)
152
- if (role) yaml += ` role: "${escapeYamlString(role)}"\n`;
153
- // core_files / test_files / entrypoints 从已有 _module-map 保留,或从卡片文件名推导
154
- const existingPaths = existingModules[moduleId]?.paths || [];
155
- if (existingPaths.length > 0) {
156
- yaml += ` core_files:\n`;
157
- for (const p of existingPaths) yaml += ` - ${p}\n`;
158
- }
159
- if (card) yaml += ` verify_commands: []\n`;
160
- yaml += ` risk_level: low\n`;
161
- if (contract || logic) yaml += ` related_docs: []\n`;
162
- yaml += `\n`;
163
- }
164
-
165
- writeFileSync(mapPath, yaml, 'utf8');
166
- console.log(`✅ _module-map.yaml 已重建:${mapPath}`);
167
- console.log(` 模块数量:${allModuleIds.size}`);
168
- for (const id of allModuleIds) {
169
- console.log(` - ${id}`);
170
- }
171
- console.log(`\n⚠️ 注意:rebuild 只重建骨架。tags/entrypoints/main_symbols/depends_on/used_by 需要重新运行 scan 或手动补充。`);
172
- console.log(`ℹ️ schema 已升级到 v2,支持 role/core_files/test_files/entrypoints/depends_on/risk_level/verify_commands 等字段。`);
173
- }
174
-
175
- /**
176
- * status: 显示模块索引状态
177
- */
178
- export async function showModuleStatus(cwd) {
179
- const mapPath = findModuleMapPath(cwd);
180
- if (!mapPath || !existsSync(mapPath)) {
181
- console.log('📭 未找到 _module-map.yaml');
182
- console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
183
- return;
184
- }
185
-
186
- const content = readFileSync(mapPath, 'utf8');
187
-
188
- // 简单解析
189
- const lines = content.split('\n');
190
- const modules = [];
191
- let currentModule = null;
192
- let needsReview = false;
193
-
194
- for (const line of lines) {
195
- const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):$/);
196
- if (moduleMatch) {
197
- if (currentModule) modules.push(currentModule);
198
- currentModule = { id: moduleMatch[1], hasTags: false, hasEntryPoints: false, hasDepends: false, needsReview: false, hasRole: false, hasCoreFiles: false, hasRisk: false, hasVerify: false };
199
- }
200
- if (currentModule) {
201
- if (line.includes('tags:')) currentModule.hasTags = true;
202
- if (line.includes('entrypoints:')) currentModule.hasEntryPoints = true;
203
- if (line.includes('depends_on:')) currentModule.hasDepends = true;
204
- if (line.includes('needs_review: true')) { currentModule.needsReview = true; needsReview = true; }
205
- if (line.includes('role:')) currentModule.hasRole = true;
206
- if (line.includes('core_files:')) currentModule.hasCoreFiles = true;
207
- if (line.includes('risk_level:')) currentModule.hasRisk = true;
208
- if (line.includes('verify_commands:')) currentModule.hasVerify = true;
209
- }
210
- }
211
- if (currentModule) modules.push(currentModule);
212
-
213
- console.log(`\n_module-map.yaml: ${mapPath}`);
214
- console.log(`模块数量:${modules.length}\n`);
215
-
216
- const maxId = Math.max(5, ...modules.map(m => m.id.length));
217
- console.log(` ${'模块'.padEnd(maxId)} tags entry deps role core risk review`);
218
- console.log(` ${''.padEnd(maxId, '─')} ──── ───── ──── ──── ──── ──── ──────`);
219
- for (const m of modules) {
220
- const tags = m.hasTags ? '✅' : '⬜';
221
- const entry = m.hasEntryPoints ? '✅' : '⬜';
222
- const deps = m.hasDepends ? '✅' : '⬜';
223
- const role = m.hasRole ? '✅' : '⬜';
224
- const core = m.hasCoreFiles ? '✅' : '⬜';
225
- const risk = m.hasRisk ? '✅' : '⬜';
226
- const verify = m.hasVerify ? '✅' : '⬜';
227
- const review = m.needsReview ? '⚠️' : '✅';
228
- console.log(` ${m.id.padEnd(maxId)} ${tags} ${entry} ${deps} ${role} ${core} ${risk} ${verify} ${review}`);
229
- }
230
-
231
- if (needsReview) {
232
- console.log(`\n⚠️ 有模块标记为 needs_review,建议检查或重新 scan`);
233
- }
234
- }
235
-
236
- /**
237
- * 从模块卡片提取指定 section 的内容
238
- */
239
- function extractSection(content, sectionName) {
240
- const match = content.match(new RegExp(`## ${escapeRegExp(sectionName)}\\n([\\s\\S]*?)(?=\\n##|$)`));
241
- return match ? match[1].trim().split('\n')[0] : ''; // 只取第一行作为摘要
242
- }
243
-
244
- function escapeRegExp(s) {
245
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
246
- }
247
-
248
- function escapeYamlString(s) {
249
- return s.replace(/"/g, '\\"').replace(/\n/g, ' ').slice(0, 200); // 截断避免过长
250
- }
251
-
252
- /**
253
- */
254
- export async function generateDependenciesMd(cwd) {
255
- const mapPath = findModuleMapPath(cwd);
256
- if (!mapPath || !existsSync(mapPath)) return;
257
-
258
- const content = readFileSync(mapPath, 'utf8');
259
- const modules = parseModuleMapSimple(content);
260
-
261
- if (Object.keys(modules).length === 0) return;
262
-
263
- const depsPath = join(mapPath, '..', 'dependencies.md');
264
- const maxName = Math.max(6, ...Object.keys(modules).map(k => k.length));
265
-
266
- let md = `# Module Dependencies\n\n`;
267
- md += `> 自动生成,由 sillyspec 维护\n\n`;
268
- md += `| ${'Module'.padEnd(maxName)} | Depends On | Used By |\n`;
269
- md += `| ${''.padEnd(maxName, '-')}:--- | :--- | :--- |\n`;
270
-
271
- for (const [id, data] of Object.entries(modules)) {
272
- const depends = (data.depends_on || []).join(', ') || '—';
273
- const usedBy = (data.used_by || []).join(', ') || '—';
274
- md += `| ${id.padEnd(maxName)} | ${depends} | ${usedBy} |\n`;
275
- }
276
-
277
- writeFileSync(depsPath, md, 'utf8');
278
- console.log(`✅ dependencies.md 已生成`);
279
- }
280
-
281
- function parseModuleMapSimple(content) {
282
- const modules = {};
283
- let currentModule = null;
284
- let currentKey = null;
285
- let currentArray = null;
286
-
287
- for (const line of content.split('\n')) {
288
- const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):$/);
289
- if (moduleMatch) {
290
- if (currentArray && currentModule && currentKey) {
291
- modules[currentModule][currentKey] = currentArray;
292
- }
293
- currentModule = moduleMatch[1];
294
- modules[currentModule] = {};
295
- currentKey = null;
296
- currentArray = null;
297
- continue;
298
- }
299
- if (!currentModule) continue;
300
-
301
- // Array field like depends_on:\n - xxx
302
- const arrayFieldMatch = line.match(/^ (depends_on|used_by|paths|tags|aliases|entrypoints|main_symbols|review_reasons):$/);
303
- if (arrayFieldMatch) {
304
- if (currentArray && currentKey) modules[currentModule][currentKey] = currentArray;
305
- currentKey = arrayFieldMatch[1];
306
- currentArray = [];
307
- continue;
308
- }
309
-
310
- // Inline array like tags: [a, b]
311
- const inlineArrayMatch = line.match(/^ (depends_on|used_by|paths|tags|aliases|entrypoints|main_symbols|review_reasons): \[(.*)\]$/);
312
- if (inlineArrayMatch) {
313
- if (currentArray && currentKey) modules[currentModule][currentKey] = currentArray;
314
- const vals = inlineArrayMatch[2].split(',').map(v => v.trim()).filter(Boolean);
315
- modules[currentModule][inlineArrayMatch[1]] = vals;
316
- currentKey = null;
317
- currentArray = null;
318
- continue;
319
- }
320
-
321
- // Scalar field
322
- const scalarMatch = line.match(/^ (status|doc|needs_review): (.+)$/);
323
- if (scalarMatch) {
324
- if (currentArray && currentKey) { modules[currentModule][currentKey] = currentArray; currentArray = null; currentKey = null; }
325
- modules[currentModule][scalarMatch[1]] = scalarMatch[2];
326
- continue;
327
- }
328
-
329
- // Array item
330
- const itemMatch = line.match(/^ - (.+)$/);
331
- if (itemMatch && currentArray !== null) {
332
- currentArray.push(itemMatch[1].trim());
333
- continue;
334
- }
335
- }
336
-
337
- // Flush last
338
- if (currentArray && currentModule && currentKey) {
339
- modules[currentModule][currentKey] = currentArray;
340
- }
341
-
342
- return modules;
343
- }
344
-
345
- /**
346
- * migrate: 旧格式模块文档迁移到新格式
347
- */
348
- export async function migrateModuleDocs(cwd) {
349
- const mapPath = findModuleMapPath(cwd);
350
- if (!mapPath || !existsSync(mapPath)) {
351
- console.error('❌ 未找到 _module-map.yaml');
352
- return;
353
- }
354
-
355
- const modulesDir = join(mapPath, '..');
356
- const files = readdirSync(modulesDir).filter(f => f.endsWith('.md') && f !== '_module-map.yaml');
357
-
358
- if (files.length === 0) {
359
- console.log('📭 没有模块文档需要迁移');
360
- return;
361
- }
362
-
363
- let migrated = 0;
364
- let skipped = 0;
365
-
366
- for (const f of files) {
367
- const filePath = join(modulesDir, f);
368
- const content = readFileSync(filePath, 'utf8');
369
-
370
- // 检查是否已经是新格式(有 schema_version + doc_type: module-card)
371
- if (content.includes('schema_version:') && content.includes('doc_type: module-card')) {
372
- skipped++;
373
- continue;
374
- }
375
-
376
- // 提取模块名(从 H1 或文件名)
377
- const h1Match = content.match(/^# (.+)$/m);
378
- const moduleId = h1Match ? h1Match[1].trim().replace(/\s+/g, '-').toLowerCase() : f.replace('.md', '');
379
-
380
- // 提取旧的“职责”和“注意事项”作为语义内容保留
381
- const dutyMatch = content.match(/## 职责\n([\s\S]*?)(?=\n##|$)/);
382
- const notesMatch = content.match(/## 注意事项\n([\s\S]*?)(?=\n##|$)/);
383
- const designMatch = content.match(/## 当前设计\n([\s\S]*?)(?=\n##|$)/);
384
- const interfaceMatch = content.match(/## 对外接口[\s\S]*?(?=\n##|$)/);
385
-
386
- const duty = dutyMatch ? dutyMatch[1].trim() : '';
387
- const notes = notesMatch ? notesMatch[1].trim() : '';
388
- const design = designMatch ? designMatch[1].trim() : '';
389
-
390
- // 检查是否有人工备注
391
- const manualMatch = content.match(/<!-- MANUAL_NOTES_START -->([\s\S]*?)<!-- MANUAL_NOTES_END -->/);
392
- const manualContent = manualMatch ? manualMatch[1].trim() : '';
393
-
394
- // 生成新格式
395
- let newContent = `---
396
- schema_version: 1
397
- doc_type: module-card
398
- module_id: ${moduleId}
399
- ---
400
-
401
- # ${moduleId}
402
-
403
- `;
404
-
405
- if (duty) {
406
- newContent += `## 定位
407
-
408
- ${duty}
409
-
410
- `;
411
- } else {
412
- newContent += `## 定位
413
-
414
- (待补充)
415
-
416
- `;
417
- }
418
-
419
- if (design || interfaceMatch) {
420
- newContent += `## 契约摘要
421
-
422
- `;
423
- if (interfaceMatch) newContent += `${interfaceMatch[0].replace('## 对外接口', '').trim()}
424
-
425
- `;
426
- else newContent += `(待从源码提取)
427
-
428
- `;
429
- } else {
430
- newContent += `## 契约摘要
431
-
432
- (待从源码提取)
433
-
434
- `;
435
- }
436
-
437
- if (design) {
438
- newContent += `## 关键逻辑
439
-
440
- ${design}
441
-
442
- `;
443
- } else {
444
- newContent += `## 关键逻辑
445
-
446
- (待补充)
447
-
448
- `;
449
- }
450
-
451
- if (notes) {
452
- newContent += `## 注意事项
453
-
454
- ${notes}
455
-
456
- `;
457
- } else {
458
- newContent += `## 注意事项
459
-
460
- (待补充)
461
-
462
- `;
463
- }
464
-
465
- newContent += `## 人工备注
466
-
467
- <!-- MANUAL_NOTES_START -->
468
- ${manualContent}
469
- <!-- MANUAL_NOTES_END -->
470
- `;
471
-
472
- writeFileSync(filePath, newContent, 'utf8');
473
- migrated++;
474
- }
475
-
476
- console.log(`✅ 迁移完成`);
477
- console.log(` 已迁移:${migrated} 个`);
478
- console.log(` 已跳过(新格式):${skipped} 个`);
479
- if (migrated > 0) {
480
- console.log(`\n⚠️ 迁移后的卡片可能需要补充内容,建议检查并运行 sillyspec modules status`);
481
- }
482
- }
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
+ * 从模块卡片 + 源码重建 _module-map.yaml
51
+ *
52
+ * schema_version 2 扩展字段:
53
+ * - role: 模块职责描述
54
+ * - core_files: 核心源文件列表
55
+ * - test_files: 测试文件列表
56
+ * - entrypoints: 入口点
57
+ * - depends_on: 依赖的其他模块
58
+ * - used_by: 被哪些模块使用
59
+ * - risk_level: 风险等级(low/medium/high)
60
+ * - verify_commands: 验证命令(build/test/lint)
61
+ * - related_docs: 关联文档路径
62
+ */
63
+ export async function rebuildModuleMap(cwd) {
64
+ const mapPath = findModuleMapPath(cwd);
65
+ if (!mapPath) {
66
+ console.error('❌ 未找到 .sillyspec/docs/<project>/modules/ 目录');
67
+ console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
68
+ return;
69
+ }
70
+
71
+ const modulesDir = join(mapPath, '..');
72
+ const existingMap = existsSync(mapPath) ? readFileSync(mapPath, 'utf8') : null;
73
+
74
+ // 收集现有模块卡片
75
+ const cards = [];
76
+ if (existsSync(modulesDir)) {
77
+ const files = readdirSync(modulesDir).filter(f => f.endsWith('.md') && f !== '_module-map.yaml');
78
+ for (const f of files) {
79
+ const content = readFileSync(join(modulesDir, f), 'utf8');
80
+ const fm = parseModuleCardFrontmatter(content);
81
+ const moduleId = fm.module_id || parseModuleIdFromFilename(f);
82
+ cards.push({ filename: f, moduleId });
83
+ }
84
+ }
85
+
86
+ // 解析现有 _module-map.yaml 保留已有字段
87
+ let existingModules = {};
88
+ if (existingMap) {
89
+ // 简单解析:提取 modules: 下的条目
90
+ const lines = existingMap.split('\n');
91
+ let currentModule = null;
92
+ let inModules = false;
93
+ for (const line of lines) {
94
+ if (line.startsWith('modules:')) { inModules = true; continue; }
95
+ if (!inModules) continue;
96
+ if (line.startsWith(' ') === false && line.trim() !== '') break;
97
+ const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):/);
98
+ if (moduleMatch) {
99
+ currentModule = moduleMatch[1];
100
+ existingModules[currentModule] = existingModules[currentModule] || { paths: [] };
101
+ }
102
+ }
103
+ }
104
+
105
+ // 如果没有模块卡片也没有已有映射
106
+ if (cards.length === 0 && Object.keys(existingModules).length === 0) {
107
+ console.log('📭 没有模块卡片,也没有已有的模块映射');
108
+ console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
109
+ return;
110
+ }
111
+
112
+ // 生成新的 _module-map.yaml
113
+ // 保留已有的完整字段,补齐新卡片
114
+ const now = new Date().toISOString().replace('T', ' ').slice(0, 19);
115
+ let headCommit = '';
116
+ try {
117
+ const { execSync } = await import('child_process');
118
+ headCommit = execSync('git rev-parse --short HEAD', { cwd, encoding: 'utf8', timeout: 5000 }).trim();
119
+ } catch { /* ignore */ }
120
+
121
+ let yaml = `schema_version: 2\n`;
122
+ yaml += `generated_at: ${now}\n`;
123
+ yaml += `generator: sillyspec-modules-rebuild\n`;
124
+ if (headCommit) yaml += `source_commit: ${headCommit}\n`;
125
+ yaml += `\n# Module Context Index — 机器可读的模块上下文索引\n`;
126
+ yaml += `# scan 阶段自动生成,brainstorm/plan/execute 阶段按任务命中模块精准注入上下文\n`;
127
+ yaml += `\nmodules:\n`;
128
+
129
+ // 合并:已有映射 + 新卡片
130
+ const allModuleIds = new Set([
131
+ ...Object.keys(existingModules),
132
+ ...cards.map(c => c.moduleId)
133
+ ]);
134
+
135
+ for (const moduleId of allModuleIds) {
136
+ const card = cards.find(c => c.moduleId === moduleId);
137
+ const cardContent = card ? readFileSync(join(modulesDir, card.filename), 'utf8') : '';
138
+
139
+ // 从模块卡片提取 context index 字段
140
+ const role = extractSection(cardContent, '定位') || '';
141
+ const contract = extractSection(cardContent, '契约摘要') || '';
142
+ const logic = extractSection(cardContent, '关键逻辑') || '';
143
+ const notes = extractSection(cardContent, '注意事项') || '';
144
+
145
+ yaml += ` ${moduleId}:\n`;
146
+ yaml += ` status: active\n`;
147
+ if (card) yaml += ` doc: modules/${card.filename}\n`;
148
+ else yaml += ` doc: modules/${moduleId}.md\n`;
149
+ yaml += ` needs_review: false\n`;
150
+ yaml += ` review_reasons: []\n`;
151
+ // context index 字段(v2)
152
+ if (role) yaml += ` role: "${escapeYamlString(role)}"\n`;
153
+ // core_files / test_files / entrypoints 从已有 _module-map 保留,或从卡片文件名推导
154
+ const existingPaths = existingModules[moduleId]?.paths || [];
155
+ if (existingPaths.length > 0) {
156
+ yaml += ` core_files:\n`;
157
+ for (const p of existingPaths) yaml += ` - ${p}\n`;
158
+ }
159
+ if (card) yaml += ` verify_commands: []\n`;
160
+ yaml += ` risk_level: low\n`;
161
+ if (contract || logic) yaml += ` related_docs: []\n`;
162
+ yaml += `\n`;
163
+ }
164
+
165
+ writeFileSync(mapPath, yaml, 'utf8');
166
+ console.log(`✅ _module-map.yaml 已重建:${mapPath}`);
167
+ console.log(` 模块数量:${allModuleIds.size}`);
168
+ for (const id of allModuleIds) {
169
+ console.log(` - ${id}`);
170
+ }
171
+ console.log(`\n⚠️ 注意:rebuild 只重建骨架。tags/entrypoints/main_symbols/depends_on/used_by 需要重新运行 scan 或手动补充。`);
172
+ console.log(`ℹ️ schema 已升级到 v2,支持 role/core_files/test_files/entrypoints/depends_on/risk_level/verify_commands 等字段。`);
173
+ }
174
+
175
+ /**
176
+ * status: 显示模块索引状态
177
+ */
178
+ export async function showModuleStatus(cwd) {
179
+ const mapPath = findModuleMapPath(cwd);
180
+ if (!mapPath || !existsSync(mapPath)) {
181
+ console.log('📭 未找到 _module-map.yaml');
182
+ console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
183
+ return;
184
+ }
185
+
186
+ const content = readFileSync(mapPath, 'utf8');
187
+
188
+ // 简单解析
189
+ const lines = content.split('\n');
190
+ const modules = [];
191
+ let currentModule = null;
192
+ let needsReview = false;
193
+
194
+ for (const line of lines) {
195
+ const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):$/);
196
+ if (moduleMatch) {
197
+ if (currentModule) modules.push(currentModule);
198
+ currentModule = { id: moduleMatch[1], hasTags: false, hasEntryPoints: false, hasDepends: false, needsReview: false, hasRole: false, hasCoreFiles: false, hasRisk: false, hasVerify: false };
199
+ }
200
+ if (currentModule) {
201
+ if (line.includes('tags:')) currentModule.hasTags = true;
202
+ if (line.includes('entrypoints:')) currentModule.hasEntryPoints = true;
203
+ if (line.includes('depends_on:')) currentModule.hasDepends = true;
204
+ if (line.includes('needs_review: true')) { currentModule.needsReview = true; needsReview = true; }
205
+ if (line.includes('role:')) currentModule.hasRole = true;
206
+ if (line.includes('core_files:')) currentModule.hasCoreFiles = true;
207
+ if (line.includes('risk_level:')) currentModule.hasRisk = true;
208
+ if (line.includes('verify_commands:')) currentModule.hasVerify = true;
209
+ }
210
+ }
211
+ if (currentModule) modules.push(currentModule);
212
+
213
+ console.log(`\n_module-map.yaml: ${mapPath}`);
214
+ console.log(`模块数量:${modules.length}\n`);
215
+
216
+ const maxId = Math.max(5, ...modules.map(m => m.id.length));
217
+ console.log(` ${'模块'.padEnd(maxId)} tags entry deps role core risk review`);
218
+ console.log(` ${''.padEnd(maxId, '─')} ──── ───── ──── ──── ──── ──── ──────`);
219
+ for (const m of modules) {
220
+ const tags = m.hasTags ? '✅' : '⬜';
221
+ const entry = m.hasEntryPoints ? '✅' : '⬜';
222
+ const deps = m.hasDepends ? '✅' : '⬜';
223
+ const role = m.hasRole ? '✅' : '⬜';
224
+ const core = m.hasCoreFiles ? '✅' : '⬜';
225
+ const risk = m.hasRisk ? '✅' : '⬜';
226
+ const verify = m.hasVerify ? '✅' : '⬜';
227
+ const review = m.needsReview ? '⚠️' : '✅';
228
+ console.log(` ${m.id.padEnd(maxId)} ${tags} ${entry} ${deps} ${role} ${core} ${risk} ${verify} ${review}`);
229
+ }
230
+
231
+ if (needsReview) {
232
+ console.log(`\n⚠️ 有模块标记为 needs_review,建议检查或重新 scan`);
233
+ }
234
+ }
235
+
236
+ /**
237
+ * 从模块卡片提取指定 section 的内容
238
+ */
239
+ function extractSection(content, sectionName) {
240
+ const match = content.match(new RegExp(`## ${escapeRegExp(sectionName)}\\n([\\s\\S]*?)(?=\\n##|$)`));
241
+ return match ? match[1].trim().split('\n')[0] : ''; // 只取第一行作为摘要
242
+ }
243
+
244
+ function escapeRegExp(s) {
245
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
246
+ }
247
+
248
+ function escapeYamlString(s) {
249
+ return s.replace(/"/g, '\\"').replace(/\n/g, ' ').slice(0, 200); // 截断避免过长
250
+ }
251
+
252
+ /**
253
+ */
254
+ export async function generateDependenciesMd(cwd) {
255
+ const mapPath = findModuleMapPath(cwd);
256
+ if (!mapPath || !existsSync(mapPath)) return;
257
+
258
+ const content = readFileSync(mapPath, 'utf8');
259
+ const modules = parseModuleMapSimple(content);
260
+
261
+ if (Object.keys(modules).length === 0) return;
262
+
263
+ const depsPath = join(mapPath, '..', 'dependencies.md');
264
+ const maxName = Math.max(6, ...Object.keys(modules).map(k => k.length));
265
+
266
+ let md = `# Module Dependencies\n\n`;
267
+ md += `> 自动生成,由 sillyspec 维护\n\n`;
268
+ md += `| ${'Module'.padEnd(maxName)} | Depends On | Used By |\n`;
269
+ md += `| ${''.padEnd(maxName, '-')}:--- | :--- | :--- |\n`;
270
+
271
+ for (const [id, data] of Object.entries(modules)) {
272
+ const depends = (data.depends_on || []).join(', ') || '—';
273
+ const usedBy = (data.used_by || []).join(', ') || '—';
274
+ md += `| ${id.padEnd(maxName)} | ${depends} | ${usedBy} |\n`;
275
+ }
276
+
277
+ writeFileSync(depsPath, md, 'utf8');
278
+ console.log(`✅ dependencies.md 已生成`);
279
+ }
280
+
281
+ function parseModuleMapSimple(content) {
282
+ const modules = {};
283
+ let currentModule = null;
284
+ let currentKey = null;
285
+ let currentArray = null;
286
+
287
+ for (const line of content.split('\n')) {
288
+ const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):$/);
289
+ if (moduleMatch) {
290
+ if (currentArray && currentModule && currentKey) {
291
+ modules[currentModule][currentKey] = currentArray;
292
+ }
293
+ currentModule = moduleMatch[1];
294
+ modules[currentModule] = {};
295
+ currentKey = null;
296
+ currentArray = null;
297
+ continue;
298
+ }
299
+ if (!currentModule) continue;
300
+
301
+ // Array field like depends_on:\n - xxx
302
+ const arrayFieldMatch = line.match(/^ (depends_on|used_by|paths|tags|aliases|entrypoints|main_symbols|review_reasons):$/);
303
+ if (arrayFieldMatch) {
304
+ if (currentArray && currentKey) modules[currentModule][currentKey] = currentArray;
305
+ currentKey = arrayFieldMatch[1];
306
+ currentArray = [];
307
+ continue;
308
+ }
309
+
310
+ // Inline array like tags: [a, b]
311
+ const inlineArrayMatch = line.match(/^ (depends_on|used_by|paths|tags|aliases|entrypoints|main_symbols|review_reasons): \[(.*)\]$/);
312
+ if (inlineArrayMatch) {
313
+ if (currentArray && currentKey) modules[currentModule][currentKey] = currentArray;
314
+ const vals = inlineArrayMatch[2].split(',').map(v => v.trim()).filter(Boolean);
315
+ modules[currentModule][inlineArrayMatch[1]] = vals;
316
+ currentKey = null;
317
+ currentArray = null;
318
+ continue;
319
+ }
320
+
321
+ // Scalar field
322
+ const scalarMatch = line.match(/^ (status|doc|needs_review): (.+)$/);
323
+ if (scalarMatch) {
324
+ if (currentArray && currentKey) { modules[currentModule][currentKey] = currentArray; currentArray = null; currentKey = null; }
325
+ modules[currentModule][scalarMatch[1]] = scalarMatch[2];
326
+ continue;
327
+ }
328
+
329
+ // Array item
330
+ const itemMatch = line.match(/^ - (.+)$/);
331
+ if (itemMatch && currentArray !== null) {
332
+ currentArray.push(itemMatch[1].trim());
333
+ continue;
334
+ }
335
+ }
336
+
337
+ // Flush last
338
+ if (currentArray && currentModule && currentKey) {
339
+ modules[currentModule][currentKey] = currentArray;
340
+ }
341
+
342
+ return modules;
343
+ }
344
+
345
+ /**
346
+ * migrate: 旧格式模块文档迁移到新格式
347
+ */
348
+ export async function migrateModuleDocs(cwd) {
349
+ const mapPath = findModuleMapPath(cwd);
350
+ if (!mapPath || !existsSync(mapPath)) {
351
+ console.error('❌ 未找到 _module-map.yaml');
352
+ return;
353
+ }
354
+
355
+ const modulesDir = join(mapPath, '..');
356
+ const files = readdirSync(modulesDir).filter(f => f.endsWith('.md') && f !== '_module-map.yaml');
357
+
358
+ if (files.length === 0) {
359
+ console.log('📭 没有模块文档需要迁移');
360
+ return;
361
+ }
362
+
363
+ let migrated = 0;
364
+ let skipped = 0;
365
+
366
+ for (const f of files) {
367
+ const filePath = join(modulesDir, f);
368
+ const content = readFileSync(filePath, 'utf8');
369
+
370
+ // 检查是否已经是新格式(有 schema_version + doc_type: module-card)
371
+ if (content.includes('schema_version:') && content.includes('doc_type: module-card')) {
372
+ skipped++;
373
+ continue;
374
+ }
375
+
376
+ // 提取模块名(从 H1 或文件名)
377
+ const h1Match = content.match(/^# (.+)$/m);
378
+ const moduleId = h1Match ? h1Match[1].trim().replace(/\s+/g, '-').toLowerCase() : f.replace('.md', '');
379
+
380
+ // 提取旧的“职责”和“注意事项”作为语义内容保留
381
+ const dutyMatch = content.match(/## 职责\n([\s\S]*?)(?=\n##|$)/);
382
+ const notesMatch = content.match(/## 注意事项\n([\s\S]*?)(?=\n##|$)/);
383
+ const designMatch = content.match(/## 当前设计\n([\s\S]*?)(?=\n##|$)/);
384
+ const interfaceMatch = content.match(/## 对外接口[\s\S]*?(?=\n##|$)/);
385
+
386
+ const duty = dutyMatch ? dutyMatch[1].trim() : '';
387
+ const notes = notesMatch ? notesMatch[1].trim() : '';
388
+ const design = designMatch ? designMatch[1].trim() : '';
389
+
390
+ // 检查是否有人工备注
391
+ const manualMatch = content.match(/<!-- MANUAL_NOTES_START -->([\s\S]*?)<!-- MANUAL_NOTES_END -->/);
392
+ const manualContent = manualMatch ? manualMatch[1].trim() : '';
393
+
394
+ // 生成新格式
395
+ let newContent = `---
396
+ schema_version: 1
397
+ doc_type: module-card
398
+ module_id: ${moduleId}
399
+ ---
400
+
401
+ # ${moduleId}
402
+
403
+ `;
404
+
405
+ if (duty) {
406
+ newContent += `## 定位
407
+
408
+ ${duty}
409
+
410
+ `;
411
+ } else {
412
+ newContent += `## 定位
413
+
414
+ (待补充)
415
+
416
+ `;
417
+ }
418
+
419
+ if (design || interfaceMatch) {
420
+ newContent += `## 契约摘要
421
+
422
+ `;
423
+ if (interfaceMatch) newContent += `${interfaceMatch[0].replace('## 对外接口', '').trim()}
424
+
425
+ `;
426
+ else newContent += `(待从源码提取)
427
+
428
+ `;
429
+ } else {
430
+ newContent += `## 契约摘要
431
+
432
+ (待从源码提取)
433
+
434
+ `;
435
+ }
436
+
437
+ if (design) {
438
+ newContent += `## 关键逻辑
439
+
440
+ ${design}
441
+
442
+ `;
443
+ } else {
444
+ newContent += `## 关键逻辑
445
+
446
+ (待补充)
447
+
448
+ `;
449
+ }
450
+
451
+ if (notes) {
452
+ newContent += `## 注意事项
453
+
454
+ ${notes}
455
+
456
+ `;
457
+ } else {
458
+ newContent += `## 注意事项
459
+
460
+ (待补充)
461
+
462
+ `;
463
+ }
464
+
465
+ newContent += `## 人工备注
466
+
467
+ <!-- MANUAL_NOTES_START -->
468
+ ${manualContent}
469
+ <!-- MANUAL_NOTES_END -->
470
+ `;
471
+
472
+ writeFileSync(filePath, newContent, 'utf8');
473
+ migrated++;
474
+ }
475
+
476
+ console.log(`✅ 迁移完成`);
477
+ console.log(` 已迁移:${migrated} 个`);
478
+ console.log(` 已跳过(新格式):${skipped} 个`);
479
+ if (migrated > 0) {
480
+ console.log(`\n⚠️ 迁移后的卡片可能需要补充内容,建议检查并运行 sillyspec modules status`);
481
+ }
482
+ }