sillyspec 3.20.2 → 3.20.3

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 (208) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +1 -5
  2. package/.claude/skills/sillyspec-auto/SKILL.md +2 -8
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -28
  4. package/.claude/skills/sillyspec-commit/SKILL.md +3 -4
  5. package/.claude/skills/sillyspec-continue/SKILL.md +4 -5
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +3 -12
  7. package/.claude/skills/sillyspec-execute/SKILL.md +2 -15
  8. package/.claude/skills/sillyspec-explore/SKILL.md +1 -14
  9. package/.claude/skills/sillyspec-plan/SKILL.md +36 -5
  10. package/.claude/skills/sillyspec-propose/SKILL.md +0 -4
  11. package/.claude/skills/sillyspec-quick/SKILL.md +1 -5
  12. package/.claude/skills/sillyspec-resume/SKILL.md +66 -23
  13. package/.claude/skills/sillyspec-scan/SKILL.md +1 -5
  14. package/.claude/skills/sillyspec-state/SKILL.md +8 -8
  15. package/.claude/skills/sillyspec-status/SKILL.md +1 -5
  16. package/.claude/skills/sillyspec-verify/SKILL.md +1 -5
  17. package/.claude/skills/sillyspec-workspace/SKILL.md +3 -11
  18. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
  19. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
  20. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
  21. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
  22. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
  23. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
  24. package/.sillyspec/changes/auto-mode/design.md +50 -0
  25. package/.sillyspec/changes/auto-mode/proposal.md +19 -0
  26. package/.sillyspec/changes/auto-mode/requirements.md +21 -0
  27. package/.sillyspec/changes/auto-mode/tasks.md +7 -0
  28. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +206 -0
  29. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
  30. package/.sillyspec/changes/dashboard/design.md +219 -0
  31. package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
  32. package/.sillyspec/changes/run-command-design/design.md +1230 -0
  33. package/.sillyspec/changes/unified-docs-design/design.md +199 -0
  34. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  35. package/.sillyspec/knowledge/INDEX.md +8 -0
  36. package/.sillyspec/knowledge/uncategorized.md +3 -0
  37. package/.sillyspec/plans/2026-04-05-dashboard.md +737 -0
  38. package/.sillyspec/projects/sillyspec.yaml +3 -0
  39. package/README.md +11 -13
  40. package/SKILL.md +40 -44
  41. package/dist/steps/brainstorm/01-load-context.md +30 -0
  42. package/dist/steps/brainstorm/02-reuse-check.md +6 -0
  43. package/dist/steps/brainstorm/03-prototype-analysis.md +11 -0
  44. package/dist/steps/brainstorm/04-module-split.md +23 -0
  45. package/dist/steps/brainstorm/05-dialog-explore.md +8 -0
  46. package/dist/steps/brainstorm/06-propose-approaches.md +3 -0
  47. package/dist/steps/brainstorm/07-present-design.md +3 -0
  48. package/dist/steps/brainstorm/08-write-design.md +21 -0
  49. package/dist/steps/brainstorm/09-self-review.md +15 -0
  50. package/dist/steps/brainstorm/10-user-confirm.md +3 -0
  51. package/dist/steps/brainstorm/11-output-spec.md +7 -0
  52. package/dist/steps/brainstorm/manifest.yaml +26 -0
  53. package/dist/steps/execute/01-load-context.md +41 -0
  54. package/dist/steps/execute/02-scan-conventions.md +47 -0
  55. package/dist/steps/execute/03-skill-mcp.md +19 -0
  56. package/dist/steps/execute/04-assign-task.md +22 -0
  57. package/dist/steps/execute/04b-prompt-template.md +54 -0
  58. package/dist/steps/execute/05-write-test.md +7 -0
  59. package/dist/steps/execute/06-write-code.md +8 -0
  60. package/dist/steps/execute/07-run-test.md +26 -0
  61. package/dist/steps/execute/08-fix-issues.md +28 -0
  62. package/dist/steps/execute/09-next-task.md +33 -0
  63. package/dist/steps/execute/manifest.yaml +28 -0
  64. package/dist/steps/plan/01-load-context.md +22 -0
  65. package/dist/steps/plan/02-anchor-confirm.md +1 -0
  66. package/dist/steps/plan/03-expand-tasks.md +33 -0
  67. package/dist/steps/plan/04-mark-order.md +15 -0
  68. package/dist/steps/plan/05-e2e-planning.md +17 -0
  69. package/dist/steps/plan/06-self-check.md +16 -0
  70. package/dist/steps/plan/07-save.md +1 -0
  71. package/dist/steps/plan/manifest.yaml +18 -0
  72. package/dist/steps/scan/01-env-detect.md +51 -0
  73. package/dist/steps/scan/02-tech-stack.md +16 -0
  74. package/dist/steps/scan/03-conventions.md +16 -0
  75. package/dist/steps/scan/04-structure.md +19 -0
  76. package/dist/steps/scan/05-quality.md +18 -0
  77. package/dist/steps/scan/06-complete.md +49 -0
  78. package/dist/steps/scan/manifest.yaml +16 -0
  79. package/dist/steps/verify/01-load-specs.md +28 -0
  80. package/dist/steps/verify/02-check-tasks.md +1 -0
  81. package/dist/steps/verify/03-check-design.md +6 -0
  82. package/dist/steps/verify/04-run-tests.md +7 -0
  83. package/dist/steps/verify/05-e2e-tests.md +27 -0
  84. package/dist/steps/verify/05b-e2e-fix.md +33 -0
  85. package/dist/steps/verify/06-code-quality.md +25 -0
  86. package/dist/steps/verify/07-lint-check.md +27 -0
  87. package/dist/steps/verify/08-output-report.md +14 -0
  88. package/dist/steps/verify/manifest.yaml +22 -0
  89. package/package.json +1 -7
  90. package/packages/dashboard/dist/assets/{index-Bq_Z2hne.js → index-D1EVTLmc.js} +1264 -1264
  91. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +1 -0
  92. package/packages/dashboard/dist/index.html +2 -2
  93. package/packages/dashboard/package-lock.json +6 -6
  94. package/packages/dashboard/server/executor.js +1 -1
  95. package/packages/dashboard/server/index.js +18 -98
  96. package/packages/dashboard/server/parser.js +71 -139
  97. package/packages/dashboard/server/watcher.js +6 -14
  98. package/packages/dashboard/src/App.vue +185 -418
  99. package/packages/dashboard/src/components/ActionBar.vue +1 -10
  100. package/packages/dashboard/src/components/CommandPalette.vue +1 -5
  101. package/packages/dashboard/src/components/DocPreview.vue +8 -105
  102. package/packages/dashboard/src/components/DocTree.vue +19 -75
  103. package/packages/dashboard/src/components/PipelineStage.vue +2 -22
  104. package/packages/dashboard/src/components/PipelineView.vue +5 -32
  105. package/packages/dashboard/src/components/ProjectOverview.vue +139 -113
  106. package/packages/dashboard/src/components/StageBadge.vue +3 -17
  107. package/packages/dashboard/src/components/StepCard.vue +2 -7
  108. package/packages/dashboard/src/composables/useDashboard.js +0 -28
  109. package/src/derive.js +147 -0
  110. package/src/index.js +40 -688
  111. package/src/init.js +63 -119
  112. package/src/migrate.js +7 -7
  113. package/src/progress.js +248 -1474
  114. package/src/run.js +302 -3008
  115. package/src/setup.js +64 -2
  116. package/src/stages/archive.js +17 -123
  117. package/src/stages/brainstorm.js +48 -454
  118. package/src/stages/doctor.js +46 -99
  119. package/src/stages/execute.js +59 -420
  120. package/src/stages/index.js +18 -12
  121. package/src/stages/plan.js +189 -492
  122. package/src/stages/propose.js +11 -70
  123. package/src/stages/quick.js +13 -52
  124. package/src/stages/scan.js +68 -485
  125. package/src/stages/status.js +1 -1
  126. package/src/stages/verify.js +16 -203
  127. package/src/step.js +543 -0
  128. package/.claude/skills/sillyspec-knowledge/SKILL.md +0 -270
  129. package/.husky/pre-push +0 -13
  130. package/CLAUDE.md +0 -18
  131. package/docs/brainstorm-plan-contract.md +0 -64
  132. package/docs/plan-execute-contract.md +0 -123
  133. package/docs/platform-scan-protocol.md +0 -298
  134. package/docs/revision-mode.md +0 -115
  135. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +0 -99
  136. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +0 -218
  137. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +0 -167
  138. package/docs/sillyspec/file-lifecycle/storage-and-state.md +0 -148
  139. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +0 -193
  140. package/docs/sillyspec/file-lifecycle.md +0 -125
  141. package/docs/workflow-contract-regression.md +0 -106
  142. package/docs/worktree-isolation.md +0 -252
  143. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +0 -1
  144. package/packages/dashboard/dist/prototype-dashboard.html +0 -836
  145. package/packages/dashboard/dist/prototype-overview.html +0 -256
  146. package/packages/dashboard/public/prototype-dashboard.html +0 -836
  147. package/packages/dashboard/public/prototype-overview.html +0 -256
  148. package/packages/dashboard/src/components/HResizeHandle.vue +0 -48
  149. package/packages/dashboard/src/components/ProjectCard.vue +0 -187
  150. package/packages/dashboard/src/components/VResizeHandle.vue +0 -61
  151. package/packages/dashboard/src/composables/useLayout.js +0 -131
  152. package/src/brainstorm-postcheck.js +0 -158
  153. package/src/change-list.js +0 -52
  154. package/src/change-risk-profile.js +0 -352
  155. package/src/classify-change.js +0 -73
  156. package/src/constants.js +0 -70
  157. package/src/contract-matrix.js +0 -278
  158. package/src/db.js +0 -201
  159. package/src/endpoint-extractor.js +0 -315
  160. package/src/hooks/claude-pre-tool-use.cjs +0 -125
  161. package/src/hooks/worktree-guard.js +0 -761
  162. package/src/knowledge-match.js +0 -130
  163. package/src/modules.js +0 -482
  164. package/src/scan-postcheck.js +0 -383
  165. package/src/stage-contract.js +0 -700
  166. package/src/stages/brainstorm-auto.js +0 -229
  167. package/src/stages/explore.js +0 -34
  168. package/src/stages/knowledge.js +0 -498
  169. package/src/stages/plan-postcheck.js +0 -513
  170. package/src/sync.js +0 -497
  171. package/src/task-review.js +0 -346
  172. package/src/workflow.js +0 -785
  173. package/src/worktree-apply.js +0 -549
  174. package/src/worktree.js +0 -932
  175. package/templates/workflows/archive-impact.yaml +0 -79
  176. package/templates/workflows/scan-docs.yaml +0 -132
  177. package/test/brainstorm-plan-contract.test.mjs +0 -273
  178. package/test/check-syntax.mjs +0 -26
  179. package/test/cli-top-level-aliases.test.mjs +0 -174
  180. package/test/contract-artifacts.test.mjs +0 -323
  181. package/test/decision-supersede.test.mjs +0 -277
  182. package/test/knowledge-match.test.mjs +0 -231
  183. package/test/plan-execute-contract.test.mjs +0 -330
  184. package/test/plan-optimization.test.mjs +0 -572
  185. package/test/platform-artifacts.test.mjs +0 -190
  186. package/test/platform-failure-samples.test.mjs +0 -199
  187. package/test/platform-recovery-chain.test.mjs +0 -179
  188. package/test/platform-recovery.test.mjs +0 -167
  189. package/test/platform-scan-p0.test.mjs +0 -175
  190. package/test/revision-v1.test.mjs +0 -1145
  191. package/test/run-sanitize-project-name.test.mjs +0 -51
  192. package/test/run-scan-postcheck-fail.test.mjs +0 -64
  193. package/test/run-scan-project-parse.test.mjs +0 -200
  194. package/test/run-tests.mjs +0 -48
  195. package/test/scan-docs-yaml-placeholders.test.mjs +0 -84
  196. package/test/scan-knowledge.test.mjs +0 -175
  197. package/test/scan-paths.test.mjs +0 -68
  198. package/test/scan-postcheck-project-priority.test.mjs +0 -85
  199. package/test/scan-postcheck.test.mjs +0 -197
  200. package/test/scan-workflow-anyfailed-block.test.mjs +0 -52
  201. package/test/spec-dir.test.mjs +0 -206
  202. package/test/stage-contract-failed-post-check.test.mjs +0 -102
  203. package/test/stage-contract.test.mjs +0 -299
  204. package/test/stage-definitions.test.mjs +0 -39
  205. package/test/wait-gates.test.mjs +0 -501
  206. package/test/workflow-spec-base.test.mjs +0 -142
  207. package/test/worktree-guard.test.mjs +0 -136
  208. package/test/worktree-native-overlay.test.mjs +0 -188
@@ -1,130 +0,0 @@
1
- /**
2
- * knowledge-match.js — knowledge 关键词匹配引擎
3
- * 从 INDEX.md 解析知识条目,按任务上下文匹配并生成 hit report
4
- */
5
-
6
- import { existsSync, readFileSync } from 'fs'
7
- import { join } from 'path'
8
-
9
- /**
10
- * 从 INDEX.md 解析所有知识条目
11
- * @param {string} indexDir - knowledge 目录路径
12
- * @returns {{ category: string, keywords: string[], file: string, anchor: string, display: string, line: string }[]}
13
- */
14
- export function parseKnowledgeIndex(indexDir) {
15
- const indexPath = join(indexDir, 'INDEX.md')
16
- if (!existsSync(indexPath)) return []
17
-
18
- const content = readFileSync(indexPath, 'utf8')
19
- const entries = []
20
- let currentCategory = ''
21
-
22
- for (const line of content.split('\n')) {
23
- // 匹配 ## Category 行
24
- const catMatch = line.match(/^##\s+(.+)/)
25
- if (catMatch) {
26
- currentCategory = catMatch[1].trim()
27
- continue
28
- }
29
-
30
- // 匹配条目行:- 关键词1|关键词2 → [显示名](文件名#锚点)
31
- const entryMatch = line.match(/^-\s+(.+?)\s*→\s*\[(.+?)\]\(([^#)]+)(?:#([^)]+))?\)/)
32
- if (entryMatch) {
33
- const keywords = entryMatch[1].split('|').map(k => k.trim()).filter(Boolean)
34
- const display = entryMatch[2].trim()
35
- const file = entryMatch[3].trim()
36
- const anchor = entryMatch[4] ? entryMatch[4].trim() : ''
37
- entries.push({ category: currentCategory, keywords, file, anchor, display, line })
38
- }
39
- }
40
-
41
- return entries
42
- }
43
-
44
- function escapeRegex(s) {
45
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
46
- }
47
-
48
- /**
49
- * 单个关键词是否命中上下文。
50
- * - 过短关键词(<2 字符)视为噪音,不参与匹配
51
- * - 非 ASCII(中文等)用子串匹配
52
- * - ASCII 关键词用词边界匹配,避免子串误命中(如 "DB" 不命中 "dashboard")
53
- */
54
- function keywordMatchesContext(keyword, contextLower) {
55
- const kw = keyword.toLowerCase().trim()
56
- if (kw.length < 2) return false
57
- if (/[^\x00-\x7f]/.test(kw)) return contextLower.includes(kw)
58
- return new RegExp(`(^|[^a-z0-9])${escapeRegex(kw)}([^a-z0-9]|$)`).test(contextLower)
59
- }
60
-
61
- /**
62
- * 用任务上下文匹配知识条目
63
- * @param {string} indexDir - knowledge 目录路径
64
- * @param {string} taskContext - 任务上下文(task 名称 + 描述,用于关键词匹配)
65
- * @returns {{ matched: boolean, entries: Array, report: string, json: object }}
66
- */
67
- export function matchKnowledge(indexDir, taskContext) {
68
- const indexPath = join(indexDir, 'INDEX.md')
69
-
70
- // INDEX.md 不存在
71
- if (!existsSync(indexPath)) {
72
- return {
73
- matched: false,
74
- entries: [],
75
- report: 'Status: no matches (INDEX.md not found)',
76
- json: { matched: false, entry_count: 0, entries: [] }
77
- }
78
- }
79
-
80
- const allEntries = parseKnowledgeIndex(indexDir)
81
- if (allEntries.length === 0 || !taskContext) {
82
- return {
83
- matched: false,
84
- entries: [],
85
- report: 'Status: no matches',
86
- json: { matched: false, entry_count: 0, entries: [] }
87
- }
88
- }
89
-
90
- const contextLower = taskContext.toLowerCase()
91
- const matched = allEntries.filter(entry => {
92
- return entry.keywords.some(kw => keywordMatchesContext(kw, contextLower))
93
- })
94
-
95
- if (matched.length === 0) {
96
- return {
97
- matched: false,
98
- entries: [],
99
- report: 'Status: no matches',
100
- json: { matched: false, entry_count: 0, entries: [] }
101
- }
102
- }
103
-
104
- const sources = matched.map(e => {
105
- const base = e.anchor ? `${e.file}#${e.anchor}` : e.file
106
- return ` - ${base}`
107
- }).join('\n')
108
-
109
- const report = [
110
- 'Knowledge Context',
111
- '─────────────────',
112
- `Status: matched`,
113
- `Entries: ${matched.length}`,
114
- 'Sources:',
115
- sources
116
- ].join('\n')
117
-
118
- const json = {
119
- matched: true,
120
- entry_count: matched.length,
121
- entries: matched.map(e => ({
122
- file: e.file,
123
- anchor: e.anchor,
124
- keywords: e.keywords,
125
- category: e.category
126
- }))
127
- }
128
-
129
- return { matched: true, entries: matched, report, json }
130
- }
package/src/modules.js DELETED
@@ -1,482 +0,0 @@
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
- }