sillyspec 3.20.3 → 3.20.5

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 (225) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +21 -17
  2. package/.claude/skills/sillyspec-auto/SKILL.md +83 -77
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -17
  4. package/.claude/skills/sillyspec-commit/SKILL.md +106 -105
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -44
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +31 -22
  7. package/.claude/skills/sillyspec-execute/SKILL.md +30 -17
  8. package/.claude/skills/sillyspec-explore/SKILL.md +109 -96
  9. package/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  10. package/.claude/skills/sillyspec-plan/SKILL.md +21 -52
  11. package/.claude/skills/sillyspec-propose/SKILL.md +21 -17
  12. package/.claude/skills/sillyspec-quick/SKILL.md +21 -17
  13. package/.claude/skills/sillyspec-resume/SKILL.md +68 -111
  14. package/.claude/skills/sillyspec-scan/SKILL.md +21 -17
  15. package/.claude/skills/sillyspec-state/SKILL.md +54 -54
  16. package/.claude/skills/sillyspec-status/SKILL.md +21 -17
  17. package/.claude/skills/sillyspec-verify/SKILL.md +21 -17
  18. package/.claude/skills/sillyspec-workspace/SKILL.md +157 -149
  19. package/.husky/pre-push +13 -0
  20. package/CLAUDE.md +18 -0
  21. package/README.md +198 -186
  22. package/SKILL.md +90 -87
  23. package/bin/sillyspec.js +2 -2
  24. package/docs/brainstorm-plan-contract.md +64 -0
  25. package/docs/plan-execute-contract.md +123 -0
  26. package/docs/platform-scan-protocol.md +298 -0
  27. package/docs/revision-mode.md +115 -0
  28. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -0
  29. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -0
  30. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -0
  31. package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -0
  32. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -0
  33. package/docs/sillyspec/file-lifecycle.md +131 -0
  34. package/docs/workflow-contract-regression.md +106 -0
  35. package/docs/worktree-isolation.md +252 -0
  36. package/package.json +40 -34
  37. package/packages/dashboard/dist/assets/{index-D1EVTLmc.js → index-Bq_Z2hne.js} +7446 -7446
  38. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  39. package/packages/dashboard/dist/index.html +16 -16
  40. package/packages/dashboard/dist/prototype-dashboard.html +836 -0
  41. package/packages/dashboard/dist/prototype-overview.html +256 -0
  42. package/packages/dashboard/index.html +15 -15
  43. package/packages/dashboard/package-lock.json +2384 -2384
  44. package/packages/dashboard/package.json +25 -25
  45. package/packages/dashboard/public/prototype-dashboard.html +836 -0
  46. package/packages/dashboard/public/prototype-overview.html +256 -0
  47. package/packages/dashboard/server/executor.js +86 -86
  48. package/packages/dashboard/server/index.js +588 -508
  49. package/packages/dashboard/server/parser.js +526 -458
  50. package/packages/dashboard/server/watcher.js +350 -342
  51. package/packages/dashboard/src/App.vue +558 -325
  52. package/packages/dashboard/src/components/ActionBar.vue +93 -84
  53. package/packages/dashboard/src/components/CommandPalette.vue +96 -92
  54. package/packages/dashboard/src/components/DetailPanel.vue +137 -137
  55. package/packages/dashboard/src/components/DocPreview.vue +105 -8
  56. package/packages/dashboard/src/components/DocTree.vue +75 -19
  57. package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
  58. package/packages/dashboard/src/components/LogStream.vue +65 -65
  59. package/packages/dashboard/src/components/PipelineStage.vue +95 -75
  60. package/packages/dashboard/src/components/PipelineView.vue +156 -129
  61. package/packages/dashboard/src/components/ProjectCard.vue +187 -0
  62. package/packages/dashboard/src/components/ProjectList.vue +210 -210
  63. package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
  64. package/packages/dashboard/src/components/StageBadge.vue +67 -53
  65. package/packages/dashboard/src/components/StepCard.vue +94 -89
  66. package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
  67. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
  68. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
  69. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
  70. package/packages/dashboard/src/composables/useDashboard.js +192 -164
  71. package/packages/dashboard/src/composables/useKeyboard.js +119 -119
  72. package/packages/dashboard/src/composables/useLayout.js +131 -0
  73. package/packages/dashboard/src/composables/useWebSocket.js +129 -129
  74. package/packages/dashboard/src/main.js +8 -8
  75. package/packages/dashboard/src/style.css +132 -132
  76. package/packages/dashboard/vite.config.js +18 -18
  77. package/src/brainstorm-postcheck.js +158 -0
  78. package/src/change-list.js +52 -0
  79. package/src/change-risk-profile.js +352 -0
  80. package/src/classify-change.js +73 -0
  81. package/src/constants.js +70 -0
  82. package/src/contract-matrix.js +278 -0
  83. package/src/db.js +201 -0
  84. package/src/endpoint-extractor.js +315 -0
  85. package/src/hooks/claude-pre-tool-use.cjs +125 -0
  86. package/src/hooks/worktree-guard.js +761 -0
  87. package/src/index.js +922 -252
  88. package/src/init.js +470 -375
  89. package/src/knowledge-match.js +130 -0
  90. package/src/migrate.js +117 -117
  91. package/src/modules.js +482 -0
  92. package/src/progress.js +1734 -508
  93. package/src/run.js +3465 -652
  94. package/src/scan-postcheck.js +387 -0
  95. package/src/setup.js +398 -460
  96. package/src/stage-contract.js +700 -0
  97. package/src/stages/archive.js +160 -54
  98. package/src/stages/brainstorm-auto.js +229 -0
  99. package/src/stages/brainstorm.js +645 -239
  100. package/src/stages/doctor.js +365 -312
  101. package/src/stages/execute.js +634 -264
  102. package/src/stages/explore.js +34 -0
  103. package/src/stages/index.js +29 -35
  104. package/src/stages/knowledge.js +498 -0
  105. package/src/stages/plan-postcheck.js +518 -0
  106. package/src/stages/plan.js +582 -279
  107. package/src/stages/propose.js +174 -115
  108. package/src/stages/quick.js +102 -63
  109. package/src/stages/scan.js +558 -141
  110. package/src/stages/status.js +65 -65
  111. package/src/stages/verify.js +322 -135
  112. package/src/sync.js +497 -0
  113. package/src/task-review.js +346 -0
  114. package/src/workflow.js +785 -0
  115. package/src/worktree-apply.js +549 -0
  116. package/src/worktree-deps.js +185 -0
  117. package/src/worktree.js +982 -0
  118. package/templates/workflows/archive-impact.yaml +79 -0
  119. package/templates/workflows/scan-docs.yaml +132 -0
  120. package/test/brainstorm-plan-contract.test.mjs +273 -0
  121. package/test/check-syntax.mjs +26 -0
  122. package/test/cli-top-level-aliases.test.mjs +174 -0
  123. package/test/contract-artifacts.test.mjs +323 -0
  124. package/test/decision-supersede.test.mjs +277 -0
  125. package/test/knowledge-match.test.mjs +231 -0
  126. package/test/plan-execute-contract.test.mjs +357 -0
  127. package/test/plan-optimization.test.mjs +572 -0
  128. package/test/platform-artifacts.test.mjs +190 -0
  129. package/test/platform-failure-samples.test.mjs +199 -0
  130. package/test/platform-recovery-chain.test.mjs +179 -0
  131. package/test/platform-recovery.test.mjs +167 -0
  132. package/test/platform-scan-p0.test.mjs +175 -0
  133. package/test/revision-v1.test.mjs +1145 -0
  134. package/test/run-sanitize-project-name.test.mjs +51 -0
  135. package/test/run-scan-postcheck-fail.test.mjs +64 -0
  136. package/test/run-scan-project-parse.test.mjs +200 -0
  137. package/test/run-tests.mjs +48 -0
  138. package/test/runtime-cleanup-keeps-worktree.test.mjs +107 -0
  139. package/test/scan-docs-yaml-placeholders.test.mjs +84 -0
  140. package/test/scan-knowledge.test.mjs +175 -0
  141. package/test/scan-paths.test.mjs +68 -0
  142. package/test/scan-postcheck-project-priority.test.mjs +85 -0
  143. package/test/scan-postcheck.test.mjs +197 -0
  144. package/test/scan-workflow-anyfailed-block.test.mjs +52 -0
  145. package/test/spec-dir.test.mjs +206 -0
  146. package/test/stage-contract-failed-post-check.test.mjs +102 -0
  147. package/test/stage-contract.test.mjs +299 -0
  148. package/test/stage-definitions.test.mjs +39 -0
  149. package/test/wait-gates.test.mjs +501 -0
  150. package/test/workflow-spec-base.test.mjs +142 -0
  151. package/test/worktree-deps-provision.test.mjs +148 -0
  152. package/test/worktree-guard.test.mjs +136 -0
  153. package/test/worktree-native-overlay.test.mjs +188 -0
  154. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
  155. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
  156. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
  157. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
  158. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
  159. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
  160. package/.sillyspec/changes/auto-mode/design.md +0 -50
  161. package/.sillyspec/changes/auto-mode/proposal.md +0 -19
  162. package/.sillyspec/changes/auto-mode/requirements.md +0 -21
  163. package/.sillyspec/changes/auto-mode/tasks.md +0 -7
  164. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
  165. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
  166. package/.sillyspec/changes/dashboard/design.md +0 -219
  167. package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
  168. package/.sillyspec/changes/run-command-design/design.md +0 -1230
  169. package/.sillyspec/changes/unified-docs-design/design.md +0 -199
  170. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  171. package/.sillyspec/knowledge/INDEX.md +0 -8
  172. package/.sillyspec/knowledge/uncategorized.md +0 -3
  173. package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
  174. package/.sillyspec/projects/sillyspec.yaml +0 -3
  175. package/dist/steps/brainstorm/01-load-context.md +0 -30
  176. package/dist/steps/brainstorm/02-reuse-check.md +0 -6
  177. package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
  178. package/dist/steps/brainstorm/04-module-split.md +0 -23
  179. package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
  180. package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
  181. package/dist/steps/brainstorm/07-present-design.md +0 -3
  182. package/dist/steps/brainstorm/08-write-design.md +0 -21
  183. package/dist/steps/brainstorm/09-self-review.md +0 -15
  184. package/dist/steps/brainstorm/10-user-confirm.md +0 -3
  185. package/dist/steps/brainstorm/11-output-spec.md +0 -7
  186. package/dist/steps/brainstorm/manifest.yaml +0 -26
  187. package/dist/steps/execute/01-load-context.md +0 -41
  188. package/dist/steps/execute/02-scan-conventions.md +0 -47
  189. package/dist/steps/execute/03-skill-mcp.md +0 -19
  190. package/dist/steps/execute/04-assign-task.md +0 -22
  191. package/dist/steps/execute/04b-prompt-template.md +0 -54
  192. package/dist/steps/execute/05-write-test.md +0 -7
  193. package/dist/steps/execute/06-write-code.md +0 -8
  194. package/dist/steps/execute/07-run-test.md +0 -26
  195. package/dist/steps/execute/08-fix-issues.md +0 -28
  196. package/dist/steps/execute/09-next-task.md +0 -33
  197. package/dist/steps/execute/manifest.yaml +0 -28
  198. package/dist/steps/plan/01-load-context.md +0 -22
  199. package/dist/steps/plan/02-anchor-confirm.md +0 -1
  200. package/dist/steps/plan/03-expand-tasks.md +0 -33
  201. package/dist/steps/plan/04-mark-order.md +0 -15
  202. package/dist/steps/plan/05-e2e-planning.md +0 -17
  203. package/dist/steps/plan/06-self-check.md +0 -16
  204. package/dist/steps/plan/07-save.md +0 -1
  205. package/dist/steps/plan/manifest.yaml +0 -18
  206. package/dist/steps/scan/01-env-detect.md +0 -51
  207. package/dist/steps/scan/02-tech-stack.md +0 -16
  208. package/dist/steps/scan/03-conventions.md +0 -16
  209. package/dist/steps/scan/04-structure.md +0 -19
  210. package/dist/steps/scan/05-quality.md +0 -18
  211. package/dist/steps/scan/06-complete.md +0 -49
  212. package/dist/steps/scan/manifest.yaml +0 -16
  213. package/dist/steps/verify/01-load-specs.md +0 -28
  214. package/dist/steps/verify/02-check-tasks.md +0 -1
  215. package/dist/steps/verify/03-check-design.md +0 -6
  216. package/dist/steps/verify/04-run-tests.md +0 -7
  217. package/dist/steps/verify/05-e2e-tests.md +0 -27
  218. package/dist/steps/verify/05b-e2e-fix.md +0 -33
  219. package/dist/steps/verify/06-code-quality.md +0 -25
  220. package/dist/steps/verify/07-lint-check.md +0 -27
  221. package/dist/steps/verify/08-output-report.md +0 -14
  222. package/dist/steps/verify/manifest.yaml +0 -22
  223. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
  224. package/src/derive.js +0 -147
  225. package/src/step.js +0 -543
package/src/modules.js ADDED
@@ -0,0 +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
+ }