sillyspec 3.27.2 → 3.27.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 (221) hide show
  1. package/.claude/skills/sillyspec-execute/SKILL.md +8 -0
  2. package/.claude/skills/sillyspec-quick/SKILL.md +2 -1
  3. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  4. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  5. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  6. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  7. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  8. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  9. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  10. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  11. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-export/SKILL.md +57 -0
  12. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-init/SKILL.md +177 -0
  13. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  14. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  15. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  16. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  17. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  18. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-state/SKILL.md +64 -0
  19. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-status/SKILL.md +54 -0
  20. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  21. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  22. package/.claude/worktrees/sillyspec-data-model-research/.gitattributes +2 -0
  23. package/.claude/worktrees/sillyspec-data-model-research/.husky/pre-push +20 -0
  24. package/.claude/worktrees/sillyspec-data-model-research/README.md +203 -0
  25. package/.claude/worktrees/sillyspec-data-model-research/SKILL.md +91 -0
  26. package/.claude/worktrees/sillyspec-data-model-research/bin/sillyspec.js +2 -0
  27. package/.claude/worktrees/sillyspec-data-model-research/logo.jpg +0 -0
  28. package/.claude/worktrees/sillyspec-data-model-research/package-lock.json +977 -0
  29. package/.claude/worktrees/sillyspec-data-model-research/package.json +39 -0
  30. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  31. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  32. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/favicon.jpg +0 -0
  33. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/index.html +16 -0
  34. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/logo.jpg +0 -0
  35. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-dashboard.html +836 -0
  36. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-overview.html +256 -0
  37. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/package.json +25 -0
  38. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/executor.js +95 -0
  39. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/index.js +679 -0
  40. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/parser.js +526 -0
  41. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/watcher.js +357 -0
  42. package/.claude/worktrees/sillyspec-data-model-research/src/agent-session-log.js +803 -0
  43. package/.claude/worktrees/sillyspec-data-model-research/src/change-list.js +290 -0
  44. package/.claude/worktrees/sillyspec-data-model-research/src/change-risk-profile.js +270 -0
  45. package/.claude/worktrees/sillyspec-data-model-research/src/check-primitives.js +65 -0
  46. package/.claude/worktrees/sillyspec-data-model-research/src/classify-change.js +109 -0
  47. package/.claude/worktrees/sillyspec-data-model-research/src/commit-suggest.js +202 -0
  48. package/.claude/worktrees/sillyspec-data-model-research/src/config-cat.js +88 -0
  49. package/.claude/worktrees/sillyspec-data-model-research/src/config-schema.js +328 -0
  50. package/.claude/worktrees/sillyspec-data-model-research/src/constants.js +97 -0
  51. package/.claude/worktrees/sillyspec-data-model-research/src/contract-matrix.js +480 -0
  52. package/.claude/worktrees/sillyspec-data-model-research/src/datetime.js +20 -0
  53. package/.claude/worktrees/sillyspec-data-model-research/src/db-engine.js +55 -0
  54. package/.claude/worktrees/sillyspec-data-model-research/src/db.js +381 -0
  55. package/.claude/worktrees/sillyspec-data-model-research/src/decision-distill.js +517 -0
  56. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/local-agent.js +155 -0
  57. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/sillyhub-mcp.js +245 -0
  58. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/probe.js +246 -0
  59. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/strategy.js +121 -0
  60. package/.claude/worktrees/sillyspec-data-model-research/src/docs-check.js +825 -0
  61. package/.claude/worktrees/sillyspec-data-model-research/src/docs-debt.js +393 -0
  62. package/.claude/worktrees/sillyspec-data-model-research/src/docs-gate.js +107 -0
  63. package/.claude/worktrees/sillyspec-data-model-research/src/doctor-diagnostics.js +1099 -0
  64. package/.claude/worktrees/sillyspec-data-model-research/src/endpoint-extractor.js +420 -0
  65. package/.claude/worktrees/sillyspec-data-model-research/src/foreign-declared.js +78 -0
  66. package/.claude/worktrees/sillyspec-data-model-research/src/fs-atomic.js +73 -0
  67. package/.claude/worktrees/sillyspec-data-model-research/src/git-helper.js +150 -0
  68. package/.claude/worktrees/sillyspec-data-model-research/src/hooks/claude-pre-tool-use.cjs +130 -0
  69. package/.claude/worktrees/sillyspec-data-model-research/src/hooks/worktree-guard.js +892 -0
  70. package/.claude/worktrees/sillyspec-data-model-research/src/index.js +2798 -0
  71. package/.claude/worktrees/sillyspec-data-model-research/src/init.js +626 -0
  72. package/.claude/worktrees/sillyspec-data-model-research/src/knowledge-match.js +231 -0
  73. package/.claude/worktrees/sillyspec-data-model-research/src/local-detect.js +128 -0
  74. package/.claude/worktrees/sillyspec-data-model-research/src/local-register.js +104 -0
  75. package/.claude/worktrees/sillyspec-data-model-research/src/machine-interface.js +543 -0
  76. package/.claude/worktrees/sillyspec-data-model-research/src/migrate.js +127 -0
  77. package/.claude/worktrees/sillyspec-data-model-research/src/module-impact.js +166 -0
  78. package/.claude/worktrees/sillyspec-data-model-research/src/module-resolve.js +222 -0
  79. package/.claude/worktrees/sillyspec-data-model-research/src/modules.js +580 -0
  80. package/.claude/worktrees/sillyspec-data-model-research/src/progress/change-registry.js +336 -0
  81. package/.claude/worktrees/sillyspec-data-model-research/src/progress/consistency-doctor.js +355 -0
  82. package/.claude/worktrees/sillyspec-data-model-research/src/progress/shared.js +36 -0
  83. package/.claude/worktrees/sillyspec-data-model-research/src/progress/stage-machine.js +594 -0
  84. package/.claude/worktrees/sillyspec-data-model-research/src/progress/step-store.js +265 -0
  85. package/.claude/worktrees/sillyspec-data-model-research/src/progress.js +1304 -0
  86. package/.claude/worktrees/sillyspec-data-model-research/src/quick-recommend.js +115 -0
  87. package/.claude/worktrees/sillyspec-data-model-research/src/quicklog.js +852 -0
  88. package/.claude/worktrees/sillyspec-data-model-research/src/review-tier.js +77 -0
  89. package/.claude/worktrees/sillyspec-data-model-research/src/run/command.js +1595 -0
  90. package/.claude/worktrees/sillyspec-data-model-research/src/run/complete-handlers.js +1552 -0
  91. package/.claude/worktrees/sillyspec-data-model-research/src/run/complete.js +1259 -0
  92. package/.claude/worktrees/sillyspec-data-model-research/src/run/concurrent-detect.js +156 -0
  93. package/.claude/worktrees/sillyspec-data-model-research/src/run/gates.js +1084 -0
  94. package/.claude/worktrees/sillyspec-data-model-research/src/run/multi-repo-context.js +208 -0
  95. package/.claude/worktrees/sillyspec-data-model-research/src/run/next.js +128 -0
  96. package/.claude/worktrees/sillyspec-data-model-research/src/run/prompt.js +843 -0
  97. package/.claude/worktrees/sillyspec-data-model-research/src/run/quick-audit.js +186 -0
  98. package/.claude/worktrees/sillyspec-data-model-research/src/run/scan-profile.js +269 -0
  99. package/.claude/worktrees/sillyspec-data-model-research/src/run/shared.js +1562 -0
  100. package/.claude/worktrees/sillyspec-data-model-research/src/run/stage.js +584 -0
  101. package/.claude/worktrees/sillyspec-data-model-research/src/run.js +23 -0
  102. package/.claude/worktrees/sillyspec-data-model-research/src/scan-diff.js +388 -0
  103. package/.claude/worktrees/sillyspec-data-model-research/src/scan-postcheck.js +512 -0
  104. package/.claude/worktrees/sillyspec-data-model-research/src/scan-staleness.js +95 -0
  105. package/.claude/worktrees/sillyspec-data-model-research/src/setup.js +414 -0
  106. package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/client.js +597 -0
  107. package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/config.js +62 -0
  108. package/.claude/worktrees/sillyspec-data-model-research/src/spec-dir-typo.js +50 -0
  109. package/.claude/worktrees/sillyspec-data-model-research/src/spec-sync.js +358 -0
  110. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-engine.js +171 -0
  111. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-spec.js +550 -0
  112. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract.js +889 -0
  113. package/.claude/worktrees/sillyspec-data-model-research/src/stage-review.js +597 -0
  114. package/.claude/worktrees/sillyspec-data-model-research/src/stages/archive.js +199 -0
  115. package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm-auto.js +236 -0
  116. package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm.js +617 -0
  117. package/.claude/worktrees/sillyspec-data-model-research/src/stages/cmd-existence.js +145 -0
  118. package/.claude/worktrees/sillyspec-data-model-research/src/stages/doctor.js +540 -0
  119. package/.claude/worktrees/sillyspec-data-model-research/src/stages/execute.js +1275 -0
  120. package/.claude/worktrees/sillyspec-data-model-research/src/stages/explore.js +43 -0
  121. package/.claude/worktrees/sillyspec-data-model-research/src/stages/index.js +29 -0
  122. package/.claude/worktrees/sillyspec-data-model-research/src/stages/knowledge.js +514 -0
  123. package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan-postcheck.js +1344 -0
  124. package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan.js +650 -0
  125. package/.claude/worktrees/sillyspec-data-model-research/src/stages/quick.js +167 -0
  126. package/.claude/worktrees/sillyspec-data-model-research/src/stages/scan.js +608 -0
  127. package/.claude/worktrees/sillyspec-data-model-research/src/stages/status.js +64 -0
  128. package/.claude/worktrees/sillyspec-data-model-research/src/stages/verify.js +246 -0
  129. package/.claude/worktrees/sillyspec-data-model-research/src/sync.js +1470 -0
  130. package/.claude/worktrees/sillyspec-data-model-research/src/task-review.js +1498 -0
  131. package/.claude/worktrees/sillyspec-data-model-research/src/taskcard.js +210 -0
  132. package/.claude/worktrees/sillyspec-data-model-research/src/verify-postcheck.js +1718 -0
  133. package/.claude/worktrees/sillyspec-data-model-research/src/verify-probes.js +277 -0
  134. package/.claude/worktrees/sillyspec-data-model-research/src/version.js +19 -0
  135. package/.claude/worktrees/sillyspec-data-model-research/src/workflow.js +600 -0
  136. package/.claude/worktrees/sillyspec-data-model-research/src/workspace.js +125 -0
  137. package/.claude/worktrees/sillyspec-data-model-research/src/worktree-apply.js +1446 -0
  138. package/.claude/worktrees/sillyspec-data-model-research/src/worktree-deps.js +576 -0
  139. package/.claude/worktrees/sillyspec-data-model-research/src/worktree.js +1715 -0
  140. package/.claude/worktrees/sillyspec-data-model-research/templates/claude-instruction.md +23 -0
  141. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/taskcard-rules.md +28 -0
  142. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/testcase-design.md +12 -0
  143. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/verify-probes.md +32 -0
  144. package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/archive-impact.yaml +79 -0
  145. package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/scan-docs.yaml +132 -0
  146. package/eval/README.md +86 -0
  147. package/eval/eval.config.json +28 -0
  148. package/eval/lib/adapters/cli.mjs +63 -0
  149. package/eval/lib/adapters/dryrun.mjs +27 -0
  150. package/eval/lib/config.mjs +82 -0
  151. package/eval/lib/db.mjs +76 -0
  152. package/eval/lib/report.mjs +120 -0
  153. package/eval/lib/runner.mjs +157 -0
  154. package/eval/lib/tasks.mjs +73 -0
  155. package/eval/lib/util.mjs +107 -0
  156. package/eval/lib/workdir.mjs +30 -0
  157. package/eval/run.mjs +83 -0
  158. package/eval/tasks/001-slugify/files/package.json +6 -0
  159. package/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  160. package/eval/tasks/001-slugify/task.json +6 -0
  161. package/eval/tasks/001-slugify/verify.mjs +28 -0
  162. package/eval/tasks/002-each-day/files/package.json +6 -0
  163. package/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  164. package/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  165. package/eval/tasks/002-each-day/task.json +6 -0
  166. package/eval/tasks/002-each-day/verify.mjs +31 -0
  167. package/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  168. package/eval/tasks/003-word-report/files/package.json +6 -0
  169. package/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  170. package/eval/tasks/003-word-report/task.json +6 -0
  171. package/eval/tasks/003-word-report/verify.mjs +38 -0
  172. package/package.json +4 -2
  173. package/src/agent-session-log.js +45 -5
  174. package/src/change-risk-profile.js +83 -14
  175. package/src/config-schema.js +18 -3
  176. package/src/contract-matrix.js +90 -29
  177. package/src/datetime.js +20 -0
  178. package/src/decision-distill.js +517 -0
  179. package/src/dispatch/backends/local-agent.js +4 -4
  180. package/src/docs-check.js +227 -0
  181. package/src/docs-debt.js +187 -24
  182. package/src/doctor-diagnostics.js +102 -2
  183. package/src/foreign-declared.js +78 -0
  184. package/src/index.js +89 -9
  185. package/src/init.js +9 -7
  186. package/src/knowledge-match.js +107 -6
  187. package/src/local-register.js +7 -1
  188. package/src/module-resolve.js +12 -1
  189. package/src/modules.js +2 -1
  190. package/src/plan-adopt-waves.js +126 -0
  191. package/src/quicklog.js +50 -4
  192. package/src/run/command.js +122 -43
  193. package/src/run/complete-handlers.js +48 -19
  194. package/src/run/complete.js +5 -2
  195. package/src/run/gates.js +87 -22
  196. package/src/run/prompt.js +84 -42
  197. package/src/run/quick-audit.js +11 -0
  198. package/src/run/shared.js +152 -10
  199. package/src/run.js +2 -2
  200. package/src/scan-postcheck.js +30 -17
  201. package/src/stage-contract-spec.js +2 -1
  202. package/src/stage-contract.js +68 -4
  203. package/src/stages/archive.js +53 -12
  204. package/src/stages/brainstorm-auto.js +7 -0
  205. package/src/stages/brainstorm.js +11 -0
  206. package/src/stages/doctor.js +157 -1
  207. package/src/stages/execute.js +32 -6
  208. package/src/stages/plan-postcheck.js +76 -17
  209. package/src/stages/plan.js +1 -0
  210. package/src/stages/quick.js +6 -1
  211. package/src/stages/verify.js +31 -14
  212. package/src/sync.js +41 -3
  213. package/src/task-review.js +2 -2
  214. package/src/taskcard-placeholders.js +21 -0
  215. package/src/taskcard.js +10 -1
  216. package/src/verify-postcheck.js +315 -13
  217. package/src/verify-probes.js +46 -4
  218. package/src/worktree-apply.js +184 -7
  219. package/src/worktree-deps.js +57 -0
  220. package/src/worktree.js +201 -58
  221. package/templates/prompts/taskcard-rules.md +1 -0
@@ -0,0 +1,580 @@
1
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { gitQuiet } from './git-helper.js';
4
+ import { nowWallClock } from './datetime.js';
5
+
6
+
7
+ /**
8
+ * 查找项目下的 _module-map.yaml 路径
9
+ */
10
+ function findModuleMapPath(cwd) {
11
+ // 查找 .sillyspec/docs/<project>/modules/_module-map.yaml
12
+ const docsDir = join(cwd, '.sillyspec', 'docs');
13
+ if (!existsSync(docsDir)) return null;
14
+
15
+ const projects = readdirSync(docsDir, { withFileTypes: true })
16
+ .filter(e => e.isDirectory());
17
+
18
+ for (const proj of projects) {
19
+ const mapPath = join(docsDir, proj.name, 'modules', '_module-map.yaml');
20
+ if (existsSync(mapPath)) return mapPath;
21
+ }
22
+
23
+ // 如果没有找到已有的,返回第一个项目的路径(用于创建)
24
+ if (projects.length > 0) {
25
+ return join(docsDir, projects[0].name, 'modules', '_module-map.yaml');
26
+ }
27
+
28
+ return null;
29
+ }
30
+
31
+ /**
32
+ * 从模块卡片文件名提取 module_id
33
+ */
34
+ function parseModuleIdFromFilename(filename) {
35
+ return filename.replace(/\.md$/, '');
36
+ }
37
+
38
+ /**
39
+ * 从模块卡片读取 frontmatter 中的 module_id
40
+ */
41
+ function parseModuleCardFrontmatter(content) {
42
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
43
+ if (!fmMatch) return {};
44
+ const fm = fmMatch[1];
45
+ const moduleIdMatch = fm.match(/^module_id:\s*(.+)$/m);
46
+ return {
47
+ module_id: moduleIdMatch ? moduleIdMatch[1].trim() : null
48
+ };
49
+ }
50
+
51
+ /**
52
+ * 从模块卡片 + 源码重建 _module-map.yaml
53
+ *
54
+ * schema_version 2 扩展字段:
55
+ * - role: 模块职责描述
56
+ * - core_files: 核心源文件列表
57
+ * - test_files: 测试文件列表
58
+ * - entrypoints: 入口点
59
+ * - depends_on: 依赖的其他模块
60
+ * - used_by: 被哪些模块使用
61
+ * - risk_level: 风险等级(low/medium/high)
62
+ * - verify_commands: 验证命令(build/test/lint)
63
+ * - related_docs: 关联文档路径
64
+ */
65
+ export async function rebuildModuleMap(cwd, { force = false } = {}) {
66
+ const mapPath = findModuleMapPath(cwd);
67
+ if (!mapPath) {
68
+ console.error('❌ 未找到 .sillyspec/docs/<project>/modules/ 目录');
69
+ console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
70
+ return;
71
+ }
72
+
73
+ const modulesDir = join(mapPath, '..');
74
+ const existingMap = existsSync(mapPath) ? readFileSync(mapPath, 'utf8') : null;
75
+
76
+ // 收集现有模块卡片
77
+ const cards = [];
78
+ if (existsSync(modulesDir)) {
79
+ const files = readdirSync(modulesDir).filter(f => f.endsWith('.md') && f !== '_module-map.yaml');
80
+ for (const f of files) {
81
+ const content = readFileSync(join(modulesDir, f), 'utf8');
82
+ const fm = parseModuleCardFrontmatter(content);
83
+ const moduleId = fm.module_id || parseModuleIdFromFilename(f);
84
+ cards.push({ filename: f, moduleId, content });
85
+ }
86
+ }
87
+
88
+ // 解析现有 _module-map.yaml 保留已有字段
89
+ let existingModules = {};
90
+ if (existingMap) {
91
+ // 简单解析:提取 modules: 下的条目
92
+ const lines = existingMap.split('\n');
93
+ let currentModule = null;
94
+ let inModules = false;
95
+ for (const line of lines) {
96
+ if (line.startsWith('modules:')) { inModules = true; continue; }
97
+ if (!inModules) continue;
98
+ if (line.startsWith(' ') === false && line.trim() !== '') break;
99
+ const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):/);
100
+ if (moduleMatch) {
101
+ currentModule = moduleMatch[1];
102
+ existingModules[currentModule] = existingModules[currentModule] || { paths: [] };
103
+ }
104
+ }
105
+ }
106
+
107
+ // 如果没有模块卡片也没有已有映射
108
+ if (cards.length === 0 && Object.keys(existingModules).length === 0) {
109
+ console.log('📭 没有模块卡片,也没有已有的模块映射');
110
+ console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
111
+ return;
112
+ }
113
+
114
+ // 生成新的 _module-map.yaml
115
+ // 保留已有的完整字段,补齐新卡片
116
+ const now = nowWallClock();
117
+ let headCommit = '';
118
+ try {
119
+ // QUAL-01 收口:走 git-helper 统一入口(safe.directory + 数组形式)
120
+ headCommit = gitQuiet(cwd, ['rev-parse', '--short', 'HEAD']) || '';
121
+ } catch { /* ignore */ }
122
+
123
+ let yaml = `schema_version: 2\n`;
124
+ yaml += `generated_at: ${now}\n`;
125
+ yaml += `generator: sillyspec-modules-rebuild\n`;
126
+ if (headCommit) yaml += `source_commit: ${headCommit}\n`;
127
+ yaml += `\n# Module Context Index — 机器可读的模块上下文索引\n`;
128
+ yaml += `# scan 阶段自动生成,brainstorm/plan/execute 阶段按任务命中模块精准注入上下文\n`;
129
+ yaml += `\nmodules:\n`;
130
+
131
+ // 合并:已有映射 + 新卡片
132
+ const allModuleIds = new Set([
133
+ ...Object.keys(existingModules),
134
+ ...cards.map(c => c.moduleId)
135
+ ]);
136
+
137
+ for (const moduleId of allModuleIds) {
138
+ const card = cards.find(c => c.moduleId === moduleId);
139
+ const cardContent = card?.content || '';
140
+
141
+ // 从模块卡片提取 context index 字段
142
+ const role = extractSection(cardContent, '定位') || '';
143
+ const contract = extractSection(cardContent, '契约摘要') || '';
144
+ const logic = extractSection(cardContent, '关键逻辑') || '';
145
+ const notes = extractSection(cardContent, '注意事项') || '';
146
+
147
+ yaml += ` ${moduleId}:\n`;
148
+ yaml += ` status: active\n`;
149
+ if (card) yaml += ` doc: modules/${card.filename}\n`;
150
+ else yaml += ` doc: modules/${moduleId}.md\n`;
151
+ yaml += ` needs_review: false\n`;
152
+ yaml += ` review_reasons: []\n`;
153
+ // context index 字段(v2)
154
+ if (role) yaml += ` role: "${escapeYamlString(role)}"\n`;
155
+ // core_files / test_files / entrypoints 从已有 _module-map 保留,或从卡片文件名推导
156
+ const existingPaths = existingModules[moduleId]?.paths || [];
157
+ if (existingPaths.length > 0) {
158
+ yaml += ` core_files:\n`;
159
+ for (const p of existingPaths) yaml += ` - ${p}\n`;
160
+ }
161
+ if (card) yaml += ` verify_commands: []\n`;
162
+ yaml += ` risk_level: low\n`;
163
+ if (contract || logic) yaml += ` related_docs: []\n`;
164
+ yaml += `\n`;
165
+ }
166
+
167
+ // 破坏性保护(multi-agent-platform 坑 modules-rebuild-destructive):默认 dry-run 只预览不写,--force 才覆盖。
168
+ if (!force && existingMap) {
169
+ console.log('⚠️ rebuild 默认不写入(破坏性保护):该命令会覆盖 _module-map.yaml,清空 tags/entrypoints/main_symbols/depends_on/used_by 等手动维护字段');
170
+ console.log(' 本次为预览,未写入磁盘。确认覆盖请运行:sillyspec modules rebuild --force');
171
+ return { dryRun: true, path: mapPath }
172
+ }
173
+
174
+ writeFileSync(mapPath, yaml, 'utf8');
175
+ console.log(`✅ _module-map.yaml 已重建:${mapPath}`);
176
+ console.log(` 模块数量:${allModuleIds.size}`);
177
+ for (const id of allModuleIds) {
178
+ console.log(` - ${id}`);
179
+ }
180
+ console.log(`\n⚠️ 注意:rebuild 只重建骨架。tags/entrypoints/main_symbols/depends_on/used_by 需要重新运行 scan 或手动补充。`);
181
+ console.log(`ℹ️ schema 已升级到 v2,支持 role/core_files/test_files/entrypoints/depends_on/risk_level/verify_commands 等字段。`);
182
+ return { dryRun: false, path: mapPath, modules: allModuleIds.size }
183
+ }
184
+
185
+ /**
186
+ * split-changelog:把模块卡的「变更索引」历史段迁出到同目录 <module>.changelog.md sidecar
187
+ * (token 成本优化 P0b,2026-08-22-token-cost-optimization)。
188
+ *
189
+ * 背景:变更索引段每变更追加、单调膨胀(multi-agent-platform backend.md 实测 19.9KB/占整卡
190
+ * 36%,18 天 3.6 倍),而子代理实现新 task 只需要卡的「定位/契约摘要/注意事项」当前态——
191
+ * 历史段是纯读取税。迁出后卡只留指针行,新条目由 prompt 约定直接追加 sidecar。
192
+ *
193
+ * 安全(对齐 rebuild 的破坏性保护):默认 dry-run 只预览不写,--force 才落盘。
194
+ * 只动「## 变更索引」一节,卡内其他节原样保留;sidecar 已存在则追加(幂等可重跑)。
195
+ *
196
+ * @param {string} cwd 项目根
197
+ * @param {{ force?: boolean }} opts
198
+ * @returns {{ dryRun: boolean, files: Array<{ card: string, sidecar: string, bytesMoved: number }>, skipped: number }}
199
+ */
200
+ export function splitChangelog(cwd, { force = false } = {}) {
201
+ const docsDir = join(cwd, '.sillyspec', 'docs');
202
+ const results = [];
203
+ let skipped = 0;
204
+ const projects = existsSync(docsDir)
205
+ ? readdirSync(docsDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name)
206
+ : [];
207
+
208
+ for (const project of projects) {
209
+ const modulesDir = join(docsDir, project, 'modules');
210
+ if (!existsSync(modulesDir)) continue;
211
+ const cards = readdirSync(modulesDir).filter(f => f.endsWith('.md') && f !== '_module-map.yaml');
212
+ for (const f of cards) {
213
+ const cardPath = join(modulesDir, f);
214
+ const content = readFileSync(cardPath, 'utf8');
215
+ // 提取「## 变更索引」节(CRLF 归一后行级匹配,与 modules.js 其余解析同防线)
216
+ const lines = content.replace(/\r\n/g, '\n').split('\n');
217
+ const startIdx = lines.findIndex(l => /^##\s*变更索引\s*$/.test(l));
218
+ if (startIdx < 0) { skipped++; continue; }
219
+ let endIdx = lines.length;
220
+ for (let i = startIdx + 1; i < lines.length; i++) {
221
+ if (/^##\s/.test(lines[i])) { endIdx = i; break; }
222
+ }
223
+ const body = lines.slice(startIdx + 1, endIdx).join('\n').trim();
224
+ // 已迁出(只剩指针行)或本就为空 → 跳过
225
+ if (!body || /^见\s/.test(body) || body.includes('.changelog.md')) { skipped++; continue; }
226
+ const bytesMoved = Buffer.byteLength(body, 'utf8');
227
+ const stem = f.replace(/\.md$/, '');
228
+ const sidecarPath = join(modulesDir, `${stem}.changelog.md`);
229
+ results.push({ card: cardPath, sidecar: sidecarPath, bytesMoved, _lines: lines, _startIdx: startIdx, _endIdx: endIdx, _body: body });
230
+ }
231
+ }
232
+
233
+ if (!force) {
234
+ if (results.length === 0) {
235
+ console.log('📭 没有需要迁出的「变更索引」段(无模块卡 / 已迁出 / 段为空)');
236
+ } else {
237
+ console.log('⚠️ split-changelog 默认不写入(破坏性保护):会把模块卡「变更索引」节迁出到同目录 <module>.changelog.md');
238
+ for (const r of results) {
239
+ console.log(` ${r.card} → ${r.sidecar}(${(r.bytesMoved / 1024).toFixed(1)}KB)`);
240
+ }
241
+ console.log(' 本次为预览,未写入磁盘。确认迁出请运行:sillyspec modules split-changelog --force');
242
+ }
243
+ return { dryRun: true, files: results.map(({ card, sidecar, bytesMoved }) => ({ card, sidecar, bytesMoved })), skipped };
244
+ }
245
+
246
+ for (const r of results) {
247
+ // sidecar:已存在追加(分隔线 + 迁出来源),否则新建带头部
248
+ const header = `# ${r.card.split(/[\\/]/).pop().replace(/\.md$/, '')} 变更索引\n\n> 由 sillyspec modules split-changelog 自模块卡迁出;新条目继续追加到本文件,勿写回模块卡。\n`;
249
+ let sidecarContent = header;
250
+ if (existsSync(r.sidecar)) {
251
+ sidecarContent = readFileSync(r.sidecar, 'utf8').replace(/\s*$/, '') + '\n\n---\n\n' + r._body + '\n';
252
+ } else {
253
+ sidecarContent = header + '\n' + r._body + '\n';
254
+ }
255
+ writeFileSync(r.sidecar, sidecarContent, 'utf8');
256
+ // 卡内节体替换为指针行(只动该节,其余逐字保留;_lines 已是 LF 归一)
257
+ const stem = r.card.split(/[\\/]/).pop().replace(/\.md$/, '');
258
+ const newLines = [...r._lines.slice(0, r._startIdx + 1), '', `见 \`${stem}.changelog.md\`——历史条目已迁出;新条目直接追加 sidecar,勿写回本卡。`, '', ...r._lines.slice(r._endIdx)];
259
+ writeFileSync(r.card, newLines.join('\n'), 'utf8');
260
+ console.log(`✅ ${r.card} → ${r.sidecar}(迁出 ${(r.bytesMoved / 1024).toFixed(1)}KB,卡内留指针)`);
261
+ }
262
+ if (results.length === 0) console.log('📭 没有需要迁出的「变更索引」段');
263
+ return { dryRun: false, files: results.map(({ card, sidecar, bytesMoved }) => ({ card, sidecar, bytesMoved })), skipped };
264
+ }
265
+
266
+ /**
267
+ * status: 显示模块索引状态
268
+ */
269
+ export async function showModuleStatus(cwd) { const mapPath = findModuleMapPath(cwd);
270
+ if (!mapPath || !existsSync(mapPath)) {
271
+ console.log('📭 未找到 _module-map.yaml');
272
+ console.log(' 提示:先运行 sillyspec run scan 生成模块文档');
273
+ return;
274
+ }
275
+
276
+ const content = readFileSync(mapPath, 'utf8');
277
+
278
+ // 简单解析(CRLF 兼容:坑 verify-modules-crlf-blanket-fallback 同类——`$` 锚定正则在
279
+ // Windows 编辑器写出的 CRLF 文件上模块条目全失配,模块数恒显示 0)
280
+ const lines = content.split(/\r?\n/);
281
+ const modules = [];
282
+ let currentModule = null;
283
+ let needsReview = false;
284
+
285
+ for (const line of lines) {
286
+ const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):$/);
287
+ if (moduleMatch) {
288
+ if (currentModule) modules.push(currentModule);
289
+ currentModule = { id: moduleMatch[1], hasTags: false, hasEntryPoints: false, hasDepends: false, needsReview: false, hasRole: false, hasCoreFiles: false, hasRisk: false, hasVerify: false };
290
+ }
291
+ if (currentModule) {
292
+ if (line.includes('tags:')) currentModule.hasTags = true;
293
+ if (line.includes('entrypoints:')) currentModule.hasEntryPoints = true;
294
+ if (line.includes('depends_on:')) currentModule.hasDepends = true;
295
+ if (line.includes('needs_review: true')) { currentModule.needsReview = true; needsReview = true; }
296
+ if (line.includes('role:')) currentModule.hasRole = true;
297
+ if (line.includes('core_files:')) currentModule.hasCoreFiles = true;
298
+ if (line.includes('risk_level:')) currentModule.hasRisk = true;
299
+ if (line.includes('verify_commands:')) currentModule.hasVerify = true;
300
+ }
301
+ }
302
+ if (currentModule) modules.push(currentModule);
303
+
304
+ console.log(`\n_module-map.yaml: ${mapPath}`);
305
+ console.log(`模块数量:${modules.length}\n`);
306
+
307
+ const maxId = Math.max(5, ...modules.map(m => m.id.length));
308
+ console.log(` ${'模块'.padEnd(maxId)} tags entry deps role core risk review`);
309
+ console.log(` ${''.padEnd(maxId, '─')} ──── ───── ──── ──── ──── ──── ──────`);
310
+ for (const m of modules) {
311
+ const tags = m.hasTags ? '✅' : '⬜';
312
+ const entry = m.hasEntryPoints ? '✅' : '⬜';
313
+ const deps = m.hasDepends ? '✅' : '⬜';
314
+ const role = m.hasRole ? '✅' : '⬜';
315
+ const core = m.hasCoreFiles ? '✅' : '⬜';
316
+ const risk = m.hasRisk ? '✅' : '⬜';
317
+ const verify = m.hasVerify ? '✅' : '⬜';
318
+ const review = m.needsReview ? '⚠️' : '✅';
319
+ console.log(` ${m.id.padEnd(maxId)} ${tags} ${entry} ${deps} ${role} ${core} ${risk} ${verify} ${review}`);
320
+ }
321
+
322
+ if (needsReview) {
323
+ console.log(`\n⚠️ 有模块标记为 needs_review,建议检查或重新 scan`);
324
+ }
325
+ }
326
+
327
+ /**
328
+ * 从模块卡片提取指定 section 的内容
329
+ */
330
+ function extractSection(content, sectionName) {
331
+ const match = content.match(new RegExp(`## ${escapeRegExp(sectionName)}\\n([\\s\\S]*?)(?=\\n##|$)`));
332
+ return match ? match[1].trim().split('\n')[0] : ''; // 只取第一行作为摘要
333
+ }
334
+
335
+ function escapeRegExp(s) {
336
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
337
+ }
338
+
339
+ function escapeYamlString(s) {
340
+ return s.replace(/"/g, '\\"').replace(/\n/g, ' ').slice(0, 200); // 截断避免过长
341
+ }
342
+
343
+ /**
344
+ */
345
+ export async function generateDependenciesMd(cwd) {
346
+ const mapPath = findModuleMapPath(cwd);
347
+ if (!mapPath || !existsSync(mapPath)) return;
348
+
349
+ const content = readFileSync(mapPath, 'utf8');
350
+ const modules = parseModuleMapSimple(content);
351
+
352
+ if (Object.keys(modules).length === 0) return;
353
+
354
+ const depsPath = join(mapPath, '..', 'dependencies.md');
355
+ const maxName = Math.max(6, ...Object.keys(modules).map(k => k.length));
356
+
357
+ let md = `# Module Dependencies\n\n`;
358
+ md += `> 自动生成,由 sillyspec 维护\n\n`;
359
+ md += `| ${'Module'.padEnd(maxName)} | Depends On | Used By |\n`;
360
+ md += `| ${''.padEnd(maxName, '-')}:--- | :--- | :--- |\n`;
361
+
362
+ for (const [id, data] of Object.entries(modules)) {
363
+ const depends = (data.depends_on || []).join(', ') || '—';
364
+ const usedBy = (data.used_by || []).join(', ') || '—';
365
+ md += `| ${id.padEnd(maxName)} | ${depends} | ${usedBy} |\n`;
366
+ }
367
+
368
+ writeFileSync(depsPath, md, 'utf8');
369
+ console.log(`✅ dependencies.md 已生成`);
370
+ }
371
+
372
+ // _module-map.yaml 简单 YAML 解析(模块上下文索引)。canonical 单一真相源——run/prompt.js
373
+ // 的 loadModuleContextIndex 与本文件 generateDependenciesMd 共用此函数(合并历史两份分叉副本,
374
+ // 2026-08-07;字段集取并集:core_files/test_files/related_docs/verify_commands + role/risk_level)。
375
+ export function parseModuleMapSimple(content) {
376
+ // CRLF 归一(2026-08-15 docs-debt-inject P1):Windows 下生成的 map 是 CRLF 行尾,下方全部
377
+ // 行级正则(/^ (w+):$/ 等)不容 会整体失配 → 解析返回空对象 → 模块上下文注入全部哑掉。
378
+ // 入口一行归一,纯正则无副作用;行为扩散(本仓模块注入被激活)记录在 file-lifecycle。
379
+ if (typeof content === 'string') content = content.replace(/\r\n/g, '\n');
380
+ const modules = {};
381
+ let currentModule = null;
382
+ let currentKey = null;
383
+ let currentArray = null;
384
+
385
+ for (const line of content.split('\n')) {
386
+ const moduleMatch = line.match(/^ ([a-zA-Z0-9_-]+):$/);
387
+ if (moduleMatch) {
388
+ if (currentArray && currentModule && currentKey) {
389
+ modules[currentModule][currentKey] = currentArray;
390
+ }
391
+ currentModule = moduleMatch[1];
392
+ modules[currentModule] = {};
393
+ currentKey = null;
394
+ currentArray = null;
395
+ continue;
396
+ }
397
+ if (!currentModule) continue;
398
+
399
+ // Array field like depends_on:\n - xxx
400
+ const arrayFieldMatch = line.match(/^ (depends_on|used_by|paths|tags|aliases|entrypoints|main_symbols|review_reasons|core_files|test_files|related_docs|verify_commands):$/);
401
+ if (arrayFieldMatch) {
402
+ if (currentArray && currentKey) modules[currentModule][currentKey] = currentArray;
403
+ currentKey = arrayFieldMatch[1];
404
+ currentArray = [];
405
+ continue;
406
+ }
407
+
408
+ // Inline array like tags: [a, b]
409
+ const inlineArrayMatch = line.match(/^ (depends_on|used_by|paths|tags|aliases|entrypoints|main_symbols|review_reasons|core_files|test_files|related_docs|verify_commands): \[(.*)\]$/);
410
+ if (inlineArrayMatch) {
411
+ if (currentArray && currentKey) modules[currentModule][currentKey] = currentArray;
412
+ const vals = inlineArrayMatch[2].split(',').map(v => v.trim()).filter(Boolean);
413
+ modules[currentModule][inlineArrayMatch[1]] = vals;
414
+ currentKey = null;
415
+ currentArray = null;
416
+ continue;
417
+ }
418
+
419
+ // Scalar field
420
+ const scalarMatch = line.match(/^ (status|doc|needs_review|role|risk_level): (.+)$/);
421
+ if (scalarMatch) {
422
+ if (currentArray && currentKey) { modules[currentModule][currentKey] = currentArray; currentArray = null; currentKey = null; }
423
+ modules[currentModule][scalarMatch[1]] = scalarMatch[2];
424
+ continue;
425
+ }
426
+
427
+ // Array item
428
+ const itemMatch = line.match(/^ - (.+)$/);
429
+ if (itemMatch && currentArray !== null) {
430
+ currentArray.push(itemMatch[1].trim());
431
+ continue;
432
+ }
433
+ }
434
+
435
+ // Flush last
436
+ if (currentArray && currentModule && currentKey) {
437
+ modules[currentModule][currentKey] = currentArray;
438
+ }
439
+
440
+ return modules;
441
+ }
442
+
443
+ /**
444
+ * migrate: 旧格式模块文档迁移到新格式
445
+ */
446
+ export async function migrateModuleDocs(cwd) {
447
+ const mapPath = findModuleMapPath(cwd);
448
+ if (!mapPath || !existsSync(mapPath)) {
449
+ console.error('❌ 未找到 _module-map.yaml');
450
+ return;
451
+ }
452
+
453
+ const modulesDir = join(mapPath, '..');
454
+ const files = readdirSync(modulesDir).filter(f => f.endsWith('.md') && f !== '_module-map.yaml');
455
+
456
+ if (files.length === 0) {
457
+ console.log('📭 没有模块文档需要迁移');
458
+ return;
459
+ }
460
+
461
+ let migrated = 0;
462
+ let skipped = 0;
463
+
464
+ for (const f of files) {
465
+ const filePath = join(modulesDir, f);
466
+ const content = readFileSync(filePath, 'utf8');
467
+
468
+ // 检查是否已经是新格式(有 schema_version + doc_type: module-card)
469
+ if (content.includes('schema_version:') && content.includes('doc_type: module-card')) {
470
+ skipped++;
471
+ continue;
472
+ }
473
+
474
+ // 提取模块名(从 H1 或文件名)
475
+ const h1Match = content.match(/^# (.+)$/m);
476
+ const moduleId = h1Match ? h1Match[1].trim().replace(/\s+/g, '-').toLowerCase() : f.replace('.md', '');
477
+
478
+ // 提取旧的“职责”和“注意事项”作为语义内容保留
479
+ const dutyMatch = content.match(/## 职责\n([\s\S]*?)(?=\n##|$)/);
480
+ const notesMatch = content.match(/## 注意事项\n([\s\S]*?)(?=\n##|$)/);
481
+ const designMatch = content.match(/## 当前设计\n([\s\S]*?)(?=\n##|$)/);
482
+ const interfaceMatch = content.match(/## 对外接口[\s\S]*?(?=\n##|$)/);
483
+
484
+ const duty = dutyMatch ? dutyMatch[1].trim() : '';
485
+ const notes = notesMatch ? notesMatch[1].trim() : '';
486
+ const design = designMatch ? designMatch[1].trim() : '';
487
+
488
+ // 检查是否有人工备注
489
+ const manualMatch = content.match(/<!-- MANUAL_NOTES_START -->([\s\S]*?)<!-- MANUAL_NOTES_END -->/);
490
+ const manualContent = manualMatch ? manualMatch[1].trim() : '';
491
+
492
+ // 生成新格式
493
+ let newContent = `---
494
+ schema_version: 1
495
+ doc_type: module-card
496
+ module_id: ${moduleId}
497
+ ---
498
+
499
+ # ${moduleId}
500
+
501
+ `;
502
+
503
+ if (duty) {
504
+ newContent += `## 定位
505
+
506
+ ${duty}
507
+
508
+ `;
509
+ } else {
510
+ newContent += `## 定位
511
+
512
+ (待补充)
513
+
514
+ `;
515
+ }
516
+
517
+ if (design || interfaceMatch) {
518
+ newContent += `## 契约摘要
519
+
520
+ `;
521
+ if (interfaceMatch) newContent += `${interfaceMatch[0].replace('## 对外接口', '').trim()}
522
+
523
+ `;
524
+ else newContent += `(待从源码提取)
525
+
526
+ `;
527
+ } else {
528
+ newContent += `## 契约摘要
529
+
530
+ (待从源码提取)
531
+
532
+ `;
533
+ }
534
+
535
+ if (design) {
536
+ newContent += `## 关键逻辑
537
+
538
+ ${design}
539
+
540
+ `;
541
+ } else {
542
+ newContent += `## 关键逻辑
543
+
544
+ (待补充)
545
+
546
+ `;
547
+ }
548
+
549
+ if (notes) {
550
+ newContent += `## 注意事项
551
+
552
+ ${notes}
553
+
554
+ `;
555
+ } else {
556
+ newContent += `## 注意事项
557
+
558
+ (待补充)
559
+
560
+ `;
561
+ }
562
+
563
+ newContent += `## 人工备注
564
+
565
+ <!-- MANUAL_NOTES_START -->
566
+ ${manualContent}
567
+ <!-- MANUAL_NOTES_END -->
568
+ `;
569
+
570
+ writeFileSync(filePath, newContent, 'utf8');
571
+ migrated++;
572
+ }
573
+
574
+ console.log(`✅ 迁移完成`);
575
+ console.log(` 已迁移:${migrated} 个`);
576
+ console.log(` 已跳过(新格式):${skipped} 个`);
577
+ if (migrated > 0) {
578
+ console.log(`\n⚠️ 迁移后的卡片可能需要补充内容,建议检查并运行 sillyspec modules status`);
579
+ }
580
+ }