spec-gen-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1078 -0
  3. package/dist/api/analyze.d.ts +17 -0
  4. package/dist/api/analyze.d.ts.map +1 -0
  5. package/dist/api/analyze.js +109 -0
  6. package/dist/api/analyze.js.map +1 -0
  7. package/dist/api/drift.d.ts +21 -0
  8. package/dist/api/drift.d.ts.map +1 -0
  9. package/dist/api/drift.js +145 -0
  10. package/dist/api/drift.js.map +1 -0
  11. package/dist/api/generate.d.ts +18 -0
  12. package/dist/api/generate.d.ts.map +1 -0
  13. package/dist/api/generate.js +251 -0
  14. package/dist/api/generate.js.map +1 -0
  15. package/dist/api/index.d.ts +39 -0
  16. package/dist/api/index.d.ts.map +1 -0
  17. package/dist/api/index.js +32 -0
  18. package/dist/api/index.js.map +1 -0
  19. package/dist/api/init.d.ts +18 -0
  20. package/dist/api/init.d.ts.map +1 -0
  21. package/dist/api/init.js +82 -0
  22. package/dist/api/init.js.map +1 -0
  23. package/dist/api/run.d.ts +19 -0
  24. package/dist/api/run.d.ts.map +1 -0
  25. package/dist/api/run.js +291 -0
  26. package/dist/api/run.js.map +1 -0
  27. package/dist/api/specs.d.ts +49 -0
  28. package/dist/api/specs.d.ts.map +1 -0
  29. package/dist/api/specs.js +136 -0
  30. package/dist/api/specs.js.map +1 -0
  31. package/dist/api/types.d.ts +176 -0
  32. package/dist/api/types.d.ts.map +1 -0
  33. package/dist/api/types.js +9 -0
  34. package/dist/api/types.js.map +1 -0
  35. package/dist/api/verify.d.ts +20 -0
  36. package/dist/api/verify.d.ts.map +1 -0
  37. package/dist/api/verify.js +117 -0
  38. package/dist/api/verify.js.map +1 -0
  39. package/dist/cli/commands/analyze.d.ts +27 -0
  40. package/dist/cli/commands/analyze.d.ts.map +1 -0
  41. package/dist/cli/commands/analyze.js +485 -0
  42. package/dist/cli/commands/analyze.js.map +1 -0
  43. package/dist/cli/commands/drift.d.ts +9 -0
  44. package/dist/cli/commands/drift.d.ts.map +1 -0
  45. package/dist/cli/commands/drift.js +540 -0
  46. package/dist/cli/commands/drift.js.map +1 -0
  47. package/dist/cli/commands/generate.d.ts +9 -0
  48. package/dist/cli/commands/generate.d.ts.map +1 -0
  49. package/dist/cli/commands/generate.js +633 -0
  50. package/dist/cli/commands/generate.js.map +1 -0
  51. package/dist/cli/commands/init.d.ts +9 -0
  52. package/dist/cli/commands/init.d.ts.map +1 -0
  53. package/dist/cli/commands/init.js +171 -0
  54. package/dist/cli/commands/init.js.map +1 -0
  55. package/dist/cli/commands/mcp.d.ts +638 -0
  56. package/dist/cli/commands/mcp.d.ts.map +1 -0
  57. package/dist/cli/commands/mcp.js +574 -0
  58. package/dist/cli/commands/mcp.js.map +1 -0
  59. package/dist/cli/commands/run.d.ts +24 -0
  60. package/dist/cli/commands/run.d.ts.map +1 -0
  61. package/dist/cli/commands/run.js +546 -0
  62. package/dist/cli/commands/run.js.map +1 -0
  63. package/dist/cli/commands/verify.d.ts +9 -0
  64. package/dist/cli/commands/verify.d.ts.map +1 -0
  65. package/dist/cli/commands/verify.js +417 -0
  66. package/dist/cli/commands/verify.js.map +1 -0
  67. package/dist/cli/commands/view.d.ts +9 -0
  68. package/dist/cli/commands/view.d.ts.map +1 -0
  69. package/dist/cli/commands/view.js +511 -0
  70. package/dist/cli/commands/view.js.map +1 -0
  71. package/dist/cli/index.d.ts +9 -0
  72. package/dist/cli/index.d.ts.map +1 -0
  73. package/dist/cli/index.js +83 -0
  74. package/dist/cli/index.js.map +1 -0
  75. package/dist/core/analyzer/architecture-writer.d.ts +67 -0
  76. package/dist/core/analyzer/architecture-writer.d.ts.map +1 -0
  77. package/dist/core/analyzer/architecture-writer.js +209 -0
  78. package/dist/core/analyzer/architecture-writer.js.map +1 -0
  79. package/dist/core/analyzer/artifact-generator.d.ts +222 -0
  80. package/dist/core/analyzer/artifact-generator.d.ts.map +1 -0
  81. package/dist/core/analyzer/artifact-generator.js +726 -0
  82. package/dist/core/analyzer/artifact-generator.js.map +1 -0
  83. package/dist/core/analyzer/call-graph.d.ts +83 -0
  84. package/dist/core/analyzer/call-graph.d.ts.map +1 -0
  85. package/dist/core/analyzer/call-graph.js +827 -0
  86. package/dist/core/analyzer/call-graph.js.map +1 -0
  87. package/dist/core/analyzer/code-shaper.d.ts +33 -0
  88. package/dist/core/analyzer/code-shaper.d.ts.map +1 -0
  89. package/dist/core/analyzer/code-shaper.js +149 -0
  90. package/dist/core/analyzer/code-shaper.js.map +1 -0
  91. package/dist/core/analyzer/dependency-graph.d.ts +179 -0
  92. package/dist/core/analyzer/dependency-graph.d.ts.map +1 -0
  93. package/dist/core/analyzer/dependency-graph.js +574 -0
  94. package/dist/core/analyzer/dependency-graph.js.map +1 -0
  95. package/dist/core/analyzer/duplicate-detector.d.ts +52 -0
  96. package/dist/core/analyzer/duplicate-detector.d.ts.map +1 -0
  97. package/dist/core/analyzer/duplicate-detector.js +279 -0
  98. package/dist/core/analyzer/duplicate-detector.js.map +1 -0
  99. package/dist/core/analyzer/embedding-service.d.ts +50 -0
  100. package/dist/core/analyzer/embedding-service.d.ts.map +1 -0
  101. package/dist/core/analyzer/embedding-service.js +104 -0
  102. package/dist/core/analyzer/embedding-service.js.map +1 -0
  103. package/dist/core/analyzer/file-walker.d.ts +78 -0
  104. package/dist/core/analyzer/file-walker.d.ts.map +1 -0
  105. package/dist/core/analyzer/file-walker.js +531 -0
  106. package/dist/core/analyzer/file-walker.js.map +1 -0
  107. package/dist/core/analyzer/import-parser.d.ts +91 -0
  108. package/dist/core/analyzer/import-parser.d.ts.map +1 -0
  109. package/dist/core/analyzer/import-parser.js +720 -0
  110. package/dist/core/analyzer/import-parser.js.map +1 -0
  111. package/dist/core/analyzer/index.d.ts +10 -0
  112. package/dist/core/analyzer/index.d.ts.map +1 -0
  113. package/dist/core/analyzer/index.js +10 -0
  114. package/dist/core/analyzer/index.js.map +1 -0
  115. package/dist/core/analyzer/refactor-analyzer.d.ts +80 -0
  116. package/dist/core/analyzer/refactor-analyzer.d.ts.map +1 -0
  117. package/dist/core/analyzer/refactor-analyzer.js +339 -0
  118. package/dist/core/analyzer/refactor-analyzer.js.map +1 -0
  119. package/dist/core/analyzer/repository-mapper.d.ts +150 -0
  120. package/dist/core/analyzer/repository-mapper.d.ts.map +1 -0
  121. package/dist/core/analyzer/repository-mapper.js +731 -0
  122. package/dist/core/analyzer/repository-mapper.js.map +1 -0
  123. package/dist/core/analyzer/signature-extractor.d.ts +31 -0
  124. package/dist/core/analyzer/signature-extractor.d.ts.map +1 -0
  125. package/dist/core/analyzer/signature-extractor.js +387 -0
  126. package/dist/core/analyzer/signature-extractor.js.map +1 -0
  127. package/dist/core/analyzer/significance-scorer.d.ts +79 -0
  128. package/dist/core/analyzer/significance-scorer.d.ts.map +1 -0
  129. package/dist/core/analyzer/significance-scorer.js +407 -0
  130. package/dist/core/analyzer/significance-scorer.js.map +1 -0
  131. package/dist/core/analyzer/subgraph-extractor.d.ts +43 -0
  132. package/dist/core/analyzer/subgraph-extractor.d.ts.map +1 -0
  133. package/dist/core/analyzer/subgraph-extractor.js +129 -0
  134. package/dist/core/analyzer/subgraph-extractor.js.map +1 -0
  135. package/dist/core/analyzer/vector-index.d.ts +63 -0
  136. package/dist/core/analyzer/vector-index.d.ts.map +1 -0
  137. package/dist/core/analyzer/vector-index.js +169 -0
  138. package/dist/core/analyzer/vector-index.js.map +1 -0
  139. package/dist/core/drift/drift-detector.d.ts +102 -0
  140. package/dist/core/drift/drift-detector.d.ts.map +1 -0
  141. package/dist/core/drift/drift-detector.js +597 -0
  142. package/dist/core/drift/drift-detector.js.map +1 -0
  143. package/dist/core/drift/git-diff.d.ts +55 -0
  144. package/dist/core/drift/git-diff.d.ts.map +1 -0
  145. package/dist/core/drift/git-diff.js +356 -0
  146. package/dist/core/drift/git-diff.js.map +1 -0
  147. package/dist/core/drift/index.d.ts +12 -0
  148. package/dist/core/drift/index.d.ts.map +1 -0
  149. package/dist/core/drift/index.js +9 -0
  150. package/dist/core/drift/index.js.map +1 -0
  151. package/dist/core/drift/spec-mapper.d.ts +73 -0
  152. package/dist/core/drift/spec-mapper.d.ts.map +1 -0
  153. package/dist/core/drift/spec-mapper.js +353 -0
  154. package/dist/core/drift/spec-mapper.js.map +1 -0
  155. package/dist/core/generator/adr-generator.d.ts +32 -0
  156. package/dist/core/generator/adr-generator.d.ts.map +1 -0
  157. package/dist/core/generator/adr-generator.js +192 -0
  158. package/dist/core/generator/adr-generator.js.map +1 -0
  159. package/dist/core/generator/index.d.ts +9 -0
  160. package/dist/core/generator/index.d.ts.map +1 -0
  161. package/dist/core/generator/index.js +12 -0
  162. package/dist/core/generator/index.js.map +1 -0
  163. package/dist/core/generator/mapping-generator.d.ts +54 -0
  164. package/dist/core/generator/mapping-generator.d.ts.map +1 -0
  165. package/dist/core/generator/mapping-generator.js +239 -0
  166. package/dist/core/generator/mapping-generator.js.map +1 -0
  167. package/dist/core/generator/openspec-compat.d.ts +160 -0
  168. package/dist/core/generator/openspec-compat.d.ts.map +1 -0
  169. package/dist/core/generator/openspec-compat.js +523 -0
  170. package/dist/core/generator/openspec-compat.js.map +1 -0
  171. package/dist/core/generator/openspec-format-generator.d.ts +111 -0
  172. package/dist/core/generator/openspec-format-generator.d.ts.map +1 -0
  173. package/dist/core/generator/openspec-format-generator.js +817 -0
  174. package/dist/core/generator/openspec-format-generator.js.map +1 -0
  175. package/dist/core/generator/openspec-writer.d.ts +131 -0
  176. package/dist/core/generator/openspec-writer.d.ts.map +1 -0
  177. package/dist/core/generator/openspec-writer.js +379 -0
  178. package/dist/core/generator/openspec-writer.js.map +1 -0
  179. package/dist/core/generator/prompts.d.ts +35 -0
  180. package/dist/core/generator/prompts.d.ts.map +1 -0
  181. package/dist/core/generator/prompts.js +212 -0
  182. package/dist/core/generator/prompts.js.map +1 -0
  183. package/dist/core/generator/spec-pipeline.d.ts +94 -0
  184. package/dist/core/generator/spec-pipeline.d.ts.map +1 -0
  185. package/dist/core/generator/spec-pipeline.js +474 -0
  186. package/dist/core/generator/spec-pipeline.js.map +1 -0
  187. package/dist/core/generator/stages/stage1-survey.d.ts +19 -0
  188. package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -0
  189. package/dist/core/generator/stages/stage1-survey.js +105 -0
  190. package/dist/core/generator/stages/stage1-survey.js.map +1 -0
  191. package/dist/core/generator/stages/stage2-entities.d.ts +11 -0
  192. package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -0
  193. package/dist/core/generator/stages/stage2-entities.js +67 -0
  194. package/dist/core/generator/stages/stage2-entities.js.map +1 -0
  195. package/dist/core/generator/stages/stage3-services.d.ts +11 -0
  196. package/dist/core/generator/stages/stage3-services.d.ts.map +1 -0
  197. package/dist/core/generator/stages/stage3-services.js +75 -0
  198. package/dist/core/generator/stages/stage3-services.js.map +1 -0
  199. package/dist/core/generator/stages/stage4-api.d.ts +11 -0
  200. package/dist/core/generator/stages/stage4-api.d.ts.map +1 -0
  201. package/dist/core/generator/stages/stage4-api.js +65 -0
  202. package/dist/core/generator/stages/stage4-api.js.map +1 -0
  203. package/dist/core/generator/stages/stage5-architecture.d.ts +10 -0
  204. package/dist/core/generator/stages/stage5-architecture.d.ts.map +1 -0
  205. package/dist/core/generator/stages/stage5-architecture.js +62 -0
  206. package/dist/core/generator/stages/stage5-architecture.js.map +1 -0
  207. package/dist/core/generator/stages/stage6-adr.d.ts +8 -0
  208. package/dist/core/generator/stages/stage6-adr.d.ts.map +1 -0
  209. package/dist/core/generator/stages/stage6-adr.js +41 -0
  210. package/dist/core/generator/stages/stage6-adr.js.map +1 -0
  211. package/dist/core/services/chat-agent.d.ts +45 -0
  212. package/dist/core/services/chat-agent.d.ts.map +1 -0
  213. package/dist/core/services/chat-agent.js +310 -0
  214. package/dist/core/services/chat-agent.js.map +1 -0
  215. package/dist/core/services/chat-tools.d.ts +32 -0
  216. package/dist/core/services/chat-tools.d.ts.map +1 -0
  217. package/dist/core/services/chat-tools.js +270 -0
  218. package/dist/core/services/chat-tools.js.map +1 -0
  219. package/dist/core/services/config-manager.d.ts +61 -0
  220. package/dist/core/services/config-manager.d.ts.map +1 -0
  221. package/dist/core/services/config-manager.js +143 -0
  222. package/dist/core/services/config-manager.js.map +1 -0
  223. package/dist/core/services/gitignore-manager.d.ts +29 -0
  224. package/dist/core/services/gitignore-manager.d.ts.map +1 -0
  225. package/dist/core/services/gitignore-manager.js +106 -0
  226. package/dist/core/services/gitignore-manager.js.map +1 -0
  227. package/dist/core/services/index.d.ts +8 -0
  228. package/dist/core/services/index.d.ts.map +1 -0
  229. package/dist/core/services/index.js +8 -0
  230. package/dist/core/services/index.js.map +1 -0
  231. package/dist/core/services/llm-service.d.ts +336 -0
  232. package/dist/core/services/llm-service.d.ts.map +1 -0
  233. package/dist/core/services/llm-service.js +1155 -0
  234. package/dist/core/services/llm-service.js.map +1 -0
  235. package/dist/core/services/mcp-handlers/analysis.d.ts +42 -0
  236. package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -0
  237. package/dist/core/services/mcp-handlers/analysis.js +300 -0
  238. package/dist/core/services/mcp-handlers/analysis.js.map +1 -0
  239. package/dist/core/services/mcp-handlers/graph.d.ts +65 -0
  240. package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -0
  241. package/dist/core/services/mcp-handlers/graph.js +509 -0
  242. package/dist/core/services/mcp-handlers/graph.js.map +1 -0
  243. package/dist/core/services/mcp-handlers/semantic.d.ts +38 -0
  244. package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -0
  245. package/dist/core/services/mcp-handlers/semantic.js +172 -0
  246. package/dist/core/services/mcp-handlers/semantic.js.map +1 -0
  247. package/dist/core/services/mcp-handlers/utils.d.ts +21 -0
  248. package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -0
  249. package/dist/core/services/mcp-handlers/utils.js +62 -0
  250. package/dist/core/services/mcp-handlers/utils.js.map +1 -0
  251. package/dist/core/services/project-detector.d.ts +32 -0
  252. package/dist/core/services/project-detector.d.ts.map +1 -0
  253. package/dist/core/services/project-detector.js +111 -0
  254. package/dist/core/services/project-detector.js.map +1 -0
  255. package/dist/core/verifier/index.d.ts +5 -0
  256. package/dist/core/verifier/index.d.ts.map +1 -0
  257. package/dist/core/verifier/index.js +5 -0
  258. package/dist/core/verifier/index.js.map +1 -0
  259. package/dist/core/verifier/verification-engine.d.ts +226 -0
  260. package/dist/core/verifier/verification-engine.d.ts.map +1 -0
  261. package/dist/core/verifier/verification-engine.js +681 -0
  262. package/dist/core/verifier/verification-engine.js.map +1 -0
  263. package/dist/types/index.d.ts +252 -0
  264. package/dist/types/index.d.ts.map +1 -0
  265. package/dist/types/index.js +5 -0
  266. package/dist/types/index.js.map +1 -0
  267. package/dist/types/pipeline.d.ts +148 -0
  268. package/dist/types/pipeline.d.ts.map +1 -0
  269. package/dist/types/pipeline.js +5 -0
  270. package/dist/types/pipeline.js.map +1 -0
  271. package/dist/utils/errors.d.ts +51 -0
  272. package/dist/utils/errors.d.ts.map +1 -0
  273. package/dist/utils/errors.js +128 -0
  274. package/dist/utils/errors.js.map +1 -0
  275. package/dist/utils/logger.d.ts +149 -0
  276. package/dist/utils/logger.d.ts.map +1 -0
  277. package/dist/utils/logger.js +331 -0
  278. package/dist/utils/logger.js.map +1 -0
  279. package/dist/utils/progress.d.ts +142 -0
  280. package/dist/utils/progress.d.ts.map +1 -0
  281. package/dist/utils/progress.js +280 -0
  282. package/dist/utils/progress.js.map +1 -0
  283. package/dist/utils/prompts.d.ts +53 -0
  284. package/dist/utils/prompts.d.ts.map +1 -0
  285. package/dist/utils/prompts.js +199 -0
  286. package/dist/utils/prompts.js.map +1 -0
  287. package/dist/utils/shutdown.d.ts +89 -0
  288. package/dist/utils/shutdown.d.ts.map +1 -0
  289. package/dist/utils/shutdown.js +237 -0
  290. package/dist/utils/shutdown.js.map +1 -0
  291. package/package.json +114 -0
  292. package/src/viewer/InteractiveGraphViewer.jsx +1486 -0
  293. package/src/viewer/app/index.html +17 -0
  294. package/src/viewer/app/main.jsx +13 -0
  295. package/src/viewer/components/ArchitectureView.jsx +177 -0
  296. package/src/viewer/components/ChatPanel.jsx +448 -0
  297. package/src/viewer/components/ClusterGraph.jsx +441 -0
  298. package/src/viewer/components/FilterBar.jsx +179 -0
  299. package/src/viewer/components/FlatGraph.jsx +275 -0
  300. package/src/viewer/components/MicroComponents.jsx +83 -0
  301. package/src/viewer/hooks/usePanZoom.js +79 -0
  302. package/src/viewer/utils/constants.js +47 -0
  303. package/src/viewer/utils/graph-helpers.js +291 -0
@@ -0,0 +1,726 @@
1
+ /**
2
+ * Analysis Artifact Generator
3
+ *
4
+ * Takes all analysis results and generates structured output files
5
+ * that will be consumed by the LLM generation phase and optionally by humans.
6
+ */
7
+ import { writeFile, mkdir, readFile } from 'node:fs/promises';
8
+ import { join, basename } from 'node:path';
9
+ import { toMermaidFormat } from './dependency-graph.js';
10
+ /**
11
+ * Heuristic to detect test/spec files across languages.
12
+ * Excludes them from call graph analysis — test helpers inflate fanIn,
13
+ * and test functions are never "unreachable" by definition.
14
+ *
15
+ * Patterns covered:
16
+ * TypeScript/JS: *.test.ts, *.spec.ts, *.test.tsx, __tests__/*, test_*.ts
17
+ * Python: test_*.py, *_test.py, tests/*.py
18
+ * Go: *_test.go
19
+ * Rust: files with #[cfg(test)] (not detectable here — excluded by directory pattern)
20
+ * Java/Kotlin: *Test.java, *Spec.kt
21
+ */
22
+ export function isTestFile(filePath) {
23
+ const name = filePath.replace(/\\/g, '/');
24
+ return (/\.(test|spec)\.(ts|tsx|js|jsx|mjs|cjs)$/.test(name) || // JS/TS: foo.test.ts
25
+ /(^|\/)__tests__\//.test(name) || // JS/TS: __tests__/
26
+ /(^|\/)test_[^/]+\.(ts|js|py)$/.test(name) || // Python/TS: test_foo.py
27
+ /[^/]+_test\.(py|go)$/.test(name) || // Python/Go: foo_test.py, foo_test.go
28
+ /(^|\/)tests?\/[^/]+\.(py|ts|js|rb|php)$/.test(name) || // tests/ directory
29
+ /[A-Z][a-zA-Z0-9]*Test\.(java|kt|scala)$/.test(name) || // Java: FooTest.java
30
+ /[A-Z][a-zA-Z0-9]*Spec\.(kt|scala|rb)$/.test(name) // Kotlin/Ruby: FooSpec.kt
31
+ );
32
+ }
33
+ // ============================================================================
34
+ // ARTIFACT GENERATOR
35
+ // ============================================================================
36
+ /**
37
+ * Generates analysis artifacts from repository map and dependency graph
38
+ */
39
+ export class AnalysisArtifactGenerator {
40
+ options;
41
+ constructor(options) {
42
+ this.options = {
43
+ rootDir: options.rootDir,
44
+ outputDir: options.outputDir,
45
+ maxDeepAnalysisFiles: options.maxDeepAnalysisFiles ?? 20,
46
+ maxValidationFiles: options.maxValidationFiles ?? 5,
47
+ tokensPerChar: options.tokensPerChar ?? 0.25, // ~4 chars per token
48
+ };
49
+ }
50
+ /**
51
+ * Generate all artifacts
52
+ */
53
+ async generate(repoMap, depGraph) {
54
+ // Generate each artifact
55
+ const repoStructure = this.generateRepoStructure(repoMap, depGraph);
56
+ const summaryMarkdown = this.generateSummaryMarkdown(repoMap, depGraph, repoStructure);
57
+ const dependencyDiagram = this.generateDependencyDiagram(depGraph);
58
+ const llmContext = await this.generateLLMContext(repoMap, depGraph);
59
+ return {
60
+ repoStructure,
61
+ summaryMarkdown,
62
+ dependencyDiagram,
63
+ llmContext,
64
+ };
65
+ }
66
+ /**
67
+ * Generate and save all artifacts to disk
68
+ */
69
+ async generateAndSave(repoMap, depGraph) {
70
+ const artifacts = await this.generate(repoMap, depGraph);
71
+ // Ensure output directory exists
72
+ await mkdir(this.options.outputDir, { recursive: true });
73
+ // Save each artifact
74
+ await Promise.all([
75
+ writeFile(join(this.options.outputDir, 'repo-structure.json'), JSON.stringify(artifacts.repoStructure, null, 2)),
76
+ writeFile(join(this.options.outputDir, 'SUMMARY.md'), artifacts.summaryMarkdown),
77
+ writeFile(join(this.options.outputDir, 'dependencies.mermaid'), artifacts.dependencyDiagram),
78
+ writeFile(join(this.options.outputDir, 'llm-context.json'), JSON.stringify(artifacts.llmContext, null, 2)),
79
+ ]);
80
+ return artifacts;
81
+ }
82
+ /**
83
+ * Generate repo-structure.json
84
+ */
85
+ generateRepoStructure(repoMap, depGraph) {
86
+ // Detect architecture pattern
87
+ const architecturePattern = this.detectArchitecturePattern(repoMap, depGraph);
88
+ // Generate layers
89
+ const layers = this.generateArchitectureLayers(repoMap);
90
+ // Generate domains from clusters
91
+ const domains = this.generateDomains(repoMap, depGraph);
92
+ // Generate entry points
93
+ const entryPoints = this.generateEntryPoints(repoMap);
94
+ // Generate data flow
95
+ const dataFlow = this.generateDataFlow(repoMap);
96
+ // Generate key files
97
+ const keyFiles = this.generateKeyFiles(repoMap);
98
+ // Calculate statistics
99
+ const avgScore = repoMap.allFiles.length > 0
100
+ ? repoMap.allFiles.reduce((sum, f) => sum + f.score, 0) / repoMap.allFiles.length
101
+ : 0;
102
+ return {
103
+ projectName: repoMap.metadata.projectName,
104
+ projectType: this.formatProjectType(repoMap.metadata.projectType),
105
+ frameworks: repoMap.summary.frameworks.map(f => f.name),
106
+ architecture: {
107
+ pattern: architecturePattern,
108
+ layers,
109
+ },
110
+ domains,
111
+ entryPoints,
112
+ dataFlow,
113
+ keyFiles,
114
+ statistics: {
115
+ totalFiles: repoMap.summary.totalFiles,
116
+ analyzedFiles: repoMap.summary.analyzedFiles,
117
+ skippedFiles: repoMap.summary.skippedFiles,
118
+ avgFileScore: Math.round(avgScore * 10) / 10,
119
+ nodeCount: depGraph.statistics.nodeCount,
120
+ edgeCount: depGraph.statistics.edgeCount,
121
+ cycleCount: depGraph.statistics.cycleCount,
122
+ clusterCount: depGraph.statistics.clusterCount,
123
+ },
124
+ };
125
+ }
126
+ /**
127
+ * Format project type for display
128
+ */
129
+ formatProjectType(type) {
130
+ const mapping = {
131
+ nodejs: 'node-typescript',
132
+ python: 'python',
133
+ rust: 'rust',
134
+ go: 'go',
135
+ java: 'java',
136
+ ruby: 'ruby',
137
+ php: 'php',
138
+ unknown: 'unknown',
139
+ };
140
+ return mapping[type] ?? type;
141
+ }
142
+ /**
143
+ * Detect architecture pattern from code structure
144
+ */
145
+ detectArchitecturePattern(repoMap, _depGraph) {
146
+ const dirs = repoMap.summary.directories;
147
+ const dirNames = dirs.map(d => basename(d.path).toLowerCase());
148
+ // Check for layered architecture indicators
149
+ const layeredIndicators = ['controllers', 'services', 'repositories', 'routes', 'models', 'views'];
150
+ const hasLayeredStructure = layeredIndicators.filter(i => dirNames.some(d => d.includes(i))).length >= 3;
151
+ // Check for modular/domain-driven indicators
152
+ const moduleIndicators = ['modules', 'features', 'domains'];
153
+ const hasModularStructure = moduleIndicators.some(i => dirNames.includes(i));
154
+ // Check for microservices indicators
155
+ const hasMultiplePackageJson = repoMap.configFiles.filter(f => f.name === 'package.json').length > 1;
156
+ const hasDockerCompose = repoMap.configFiles.some(f => f.name.includes('docker-compose'));
157
+ // Determine pattern
158
+ if (hasMultiplePackageJson && hasDockerCompose) {
159
+ return 'microservices';
160
+ }
161
+ if (hasModularStructure) {
162
+ return 'modular';
163
+ }
164
+ if (hasLayeredStructure) {
165
+ return 'layered';
166
+ }
167
+ if (repoMap.summary.totalFiles < 50) {
168
+ return 'monolith';
169
+ }
170
+ return 'unknown';
171
+ }
172
+ /**
173
+ * Generate architecture layers
174
+ */
175
+ generateArchitectureLayers(repoMap) {
176
+ const layers = [];
177
+ // API/Routes layer
178
+ const apiFiles = repoMap.allFiles.filter(f => f.directory.includes('routes') ||
179
+ f.directory.includes('controllers') ||
180
+ f.directory.includes('api') ||
181
+ f.name.includes('route') ||
182
+ f.name.includes('controller'));
183
+ if (apiFiles.length > 0) {
184
+ layers.push({
185
+ name: 'API Layer',
186
+ purpose: 'HTTP request handling and routing',
187
+ files: apiFiles.map(f => f.path),
188
+ representativeFile: apiFiles[0]?.path ?? null,
189
+ });
190
+ }
191
+ // Service/Business layer
192
+ const serviceFiles = repoMap.allFiles.filter(f => f.directory.includes('services') ||
193
+ f.directory.includes('business') ||
194
+ f.directory.includes('domain') ||
195
+ f.name.includes('service') ||
196
+ f.name.includes('manager'));
197
+ if (serviceFiles.length > 0) {
198
+ layers.push({
199
+ name: 'Service Layer',
200
+ purpose: 'Business logic and domain operations',
201
+ files: serviceFiles.map(f => f.path),
202
+ representativeFile: serviceFiles[0]?.path ?? null,
203
+ });
204
+ }
205
+ // Data/Repository layer
206
+ const dataFiles = repoMap.allFiles.filter(f => f.directory.includes('repositories') ||
207
+ f.directory.includes('data') ||
208
+ f.directory.includes('database') ||
209
+ f.directory.includes('models') ||
210
+ f.name.includes('repository') ||
211
+ f.name.includes('model'));
212
+ if (dataFiles.length > 0) {
213
+ layers.push({
214
+ name: 'Data Layer',
215
+ purpose: 'Data access and persistence',
216
+ files: dataFiles.map(f => f.path),
217
+ representativeFile: dataFiles[0]?.path ?? null,
218
+ });
219
+ }
220
+ // Infrastructure layer
221
+ const infraFiles = repoMap.allFiles.filter(f => f.directory.includes('infrastructure') ||
222
+ f.directory.includes('config') ||
223
+ f.directory.includes('middleware') ||
224
+ f.directory.includes('utils') ||
225
+ f.isConfig);
226
+ if (infraFiles.length > 0) {
227
+ layers.push({
228
+ name: 'Infrastructure Layer',
229
+ purpose: 'Configuration, middleware, and utilities',
230
+ files: infraFiles.map(f => f.path),
231
+ representativeFile: infraFiles[0]?.path ?? null,
232
+ });
233
+ }
234
+ return layers;
235
+ }
236
+ /**
237
+ * Generate domains from clusters
238
+ */
239
+ generateDomains(repoMap, depGraph) {
240
+ const domains = [];
241
+ // Use directory-based clusters from repo map
242
+ for (const [dirName, files] of Object.entries(repoMap.clusters.byDomain)) {
243
+ if (files.length === 0)
244
+ continue;
245
+ // Skip infrastructure directories
246
+ const skipDirs = ['utils', 'helpers', 'common', 'shared', 'config', 'middleware'];
247
+ if (skipDirs.includes(dirName.toLowerCase()))
248
+ continue;
249
+ // Extract potential entities from file names
250
+ const entities = this.extractEntities(files);
251
+ // Find the key file (highest score in domain)
252
+ const keyFile = files.sort((a, b) => b.score - a.score)[0];
253
+ // Generate suggested spec path
254
+ const domainName = this.normalizeDomainName(dirName);
255
+ domains.push({
256
+ name: domainName,
257
+ suggestedSpecPath: `openspec/specs/${domainName}/spec.md`,
258
+ files: files.map(f => f.path),
259
+ entities,
260
+ keyFile: keyFile?.path ?? null,
261
+ });
262
+ }
263
+ // Also consider clusters from dependency graph
264
+ for (const cluster of depGraph.clusters) {
265
+ const clusterName = this.normalizeDomainName(cluster.suggestedDomain);
266
+ // Skip if already covered
267
+ if (domains.some(d => d.name === clusterName))
268
+ continue;
269
+ // Skip small clusters
270
+ if (cluster.files.length < 2)
271
+ continue;
272
+ // Get file details
273
+ const files = cluster.files
274
+ .map(id => depGraph.nodes.find(n => n.id === id)?.file)
275
+ .filter((f) => f !== undefined);
276
+ if (files.length === 0)
277
+ continue;
278
+ const entities = this.extractEntities(files);
279
+ const keyFile = files.sort((a, b) => b.score - a.score)[0];
280
+ domains.push({
281
+ name: clusterName,
282
+ suggestedSpecPath: `openspec/specs/${clusterName}/spec.md`,
283
+ files: files.map(f => f.path),
284
+ entities,
285
+ keyFile: keyFile?.path ?? null,
286
+ });
287
+ }
288
+ return domains;
289
+ }
290
+ /**
291
+ * Normalize domain name for OpenSpec path
292
+ */
293
+ normalizeDomainName(name) {
294
+ return name
295
+ .toLowerCase()
296
+ .replace(/[^a-z0-9]/g, '-')
297
+ .replace(/-+/g, '-')
298
+ .replace(/^-|-$/g, '') || 'misc';
299
+ }
300
+ /**
301
+ * Extract potential entity names from files
302
+ */
303
+ extractEntities(files) {
304
+ const entities = new Set();
305
+ for (const file of files) {
306
+ // Extract from file name
307
+ const name = file.name.replace(/\.(ts|js|tsx|jsx|py)$/, '');
308
+ // Convert to PascalCase as potential entity name
309
+ const entityName = name
310
+ .split(/[-_.]/)
311
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
312
+ .join('');
313
+ // Skip generic names
314
+ const skipNames = ['Index', 'Types', 'Utils', 'Helpers', 'Constants', 'Test', 'Spec'];
315
+ if (!skipNames.includes(entityName) && entityName.length > 2) {
316
+ entities.add(entityName);
317
+ }
318
+ }
319
+ return Array.from(entities).slice(0, 5); // Limit to top 5
320
+ }
321
+ /**
322
+ * Generate entry points information
323
+ */
324
+ generateEntryPoints(repoMap) {
325
+ return repoMap.entryPoints.map(file => {
326
+ // Determine entry point type
327
+ let type = 'application-entry';
328
+ if (file.name.includes('test') || file.name.includes('spec')) {
329
+ type = 'test-entry';
330
+ }
331
+ else if (file.name.includes('route') || file.name.includes('api')) {
332
+ type = 'api-entry';
333
+ }
334
+ else if (file.name.includes('build') || file.name.includes('webpack')) {
335
+ type = 'build-entry';
336
+ }
337
+ // Infer what gets initialized (simplified)
338
+ const initializes = [];
339
+ if (file.name.includes('app') || file.name === 'index.ts') {
340
+ initializes.push('application');
341
+ }
342
+ if (file.directory.includes('database')) {
343
+ initializes.push('database');
344
+ }
345
+ return {
346
+ file: file.path,
347
+ type,
348
+ initializes,
349
+ };
350
+ });
351
+ }
352
+ /**
353
+ * Generate data flow information
354
+ */
355
+ generateDataFlow(repoMap) {
356
+ const sources = [];
357
+ const sinks = [];
358
+ const transformers = [];
359
+ for (const file of repoMap.allFiles) {
360
+ const dir = file.directory.toLowerCase();
361
+ const name = file.name.toLowerCase();
362
+ // Sources: routes, controllers, APIs
363
+ if (dir.includes('routes') || dir.includes('controllers') || dir.includes('api')) {
364
+ sources.push(file.path);
365
+ }
366
+ // Sinks: repositories, database, storage
367
+ else if (dir.includes('repositories') || dir.includes('database') || dir.includes('storage')) {
368
+ sinks.push(file.path);
369
+ }
370
+ // Transformers: services, middleware
371
+ else if (dir.includes('services') || dir.includes('middleware') || name.includes('service')) {
372
+ transformers.push(file.path);
373
+ }
374
+ }
375
+ return { sources, sinks, transformers };
376
+ }
377
+ /**
378
+ * Generate key files by category
379
+ */
380
+ generateKeyFiles(repoMap) {
381
+ const keyFiles = {
382
+ schemas: [],
383
+ config: [],
384
+ auth: [],
385
+ database: [],
386
+ routes: [],
387
+ services: [],
388
+ };
389
+ for (const file of repoMap.allFiles) {
390
+ const dir = file.directory.toLowerCase();
391
+ const name = file.name.toLowerCase();
392
+ if (dir.includes('models') || dir.includes('schemas') || name.includes('schema')) {
393
+ keyFiles.schemas.push(file.path);
394
+ }
395
+ if (file.isConfig || dir.includes('config')) {
396
+ keyFiles.config.push(file.path);
397
+ }
398
+ if (dir.includes('auth') || name.includes('auth')) {
399
+ keyFiles.auth.push(file.path);
400
+ }
401
+ if (dir.includes('database') || dir.includes('db') || name.includes('database')) {
402
+ keyFiles.database.push(file.path);
403
+ }
404
+ if (dir.includes('routes') || name.includes('route')) {
405
+ keyFiles.routes.push(file.path);
406
+ }
407
+ if (dir.includes('services') || name.includes('service')) {
408
+ keyFiles.services.push(file.path);
409
+ }
410
+ }
411
+ return keyFiles;
412
+ }
413
+ /**
414
+ * Generate SUMMARY.md
415
+ */
416
+ generateSummaryMarkdown(repoMap, depGraph, repoStructure) {
417
+ const lines = [];
418
+ // Header
419
+ lines.push(`# Repository Analysis: ${repoMap.metadata.projectName}`);
420
+ lines.push('');
421
+ // Overview
422
+ lines.push('## Overview');
423
+ lines.push(`- **Type**: ${this.formatProjectTypeReadable(repoMap.metadata.projectType)}`);
424
+ if (repoMap.summary.frameworks.length > 0) {
425
+ lines.push(`- **Frameworks**: ${repoMap.summary.frameworks.map(f => f.name).join(', ')}`);
426
+ }
427
+ lines.push(`- **Files Analyzed**: ${repoMap.summary.analyzedFiles} of ${repoMap.summary.totalFiles} (${repoMap.summary.skippedFiles} skipped)`);
428
+ lines.push(`- **Analysis Date**: ${repoMap.metadata.analyzedAt}`);
429
+ lines.push('');
430
+ // Architecture
431
+ lines.push('## Architecture Pattern');
432
+ lines.push(`This appears to be a **${repoStructure.architecture.pattern}** architecture.`);
433
+ if (repoStructure.architecture.layers.length > 0) {
434
+ lines.push('');
435
+ lines.push('**Detected Layers:**');
436
+ for (const layer of repoStructure.architecture.layers) {
437
+ lines.push(`- ${layer.name}: ${layer.purpose} (${layer.files.length} files)`);
438
+ }
439
+ }
440
+ lines.push('');
441
+ // Languages
442
+ if (repoMap.summary.languages.length > 0) {
443
+ lines.push('## Language Breakdown');
444
+ lines.push('| Language | Files | Percentage |');
445
+ lines.push('|----------|-------|------------|');
446
+ for (const lang of repoMap.summary.languages.slice(0, 5)) {
447
+ lines.push(`| ${lang.language} | ${lang.fileCount} | ${lang.percentage.toFixed(1)}% |`);
448
+ }
449
+ lines.push('');
450
+ }
451
+ // Domains
452
+ if (repoStructure.domains.length > 0) {
453
+ lines.push('## Detected Domains');
454
+ lines.push('These domains will become OpenSpec specifications:');
455
+ lines.push('');
456
+ lines.push('| Domain | Files | Key Entities | Spec Path |');
457
+ lines.push('|--------|-------|--------------|-----------|');
458
+ for (const domain of repoStructure.domains.slice(0, 10)) {
459
+ const entities = domain.entities.slice(0, 3).join(', ') || '-';
460
+ lines.push(`| ${domain.name} | ${domain.files.length} | ${entities} | \`${domain.suggestedSpecPath}\` |`);
461
+ }
462
+ lines.push('');
463
+ }
464
+ // Dependency insights
465
+ lines.push('## Dependency Insights');
466
+ // Most connected
467
+ const topConnected = depGraph.rankings.byConnectivity.slice(0, 3);
468
+ if (topConnected.length > 0) {
469
+ lines.push('');
470
+ lines.push('**Most Connected Files:**');
471
+ for (const nodeId of topConnected) {
472
+ const node = depGraph.nodes.find(n => n.id === nodeId);
473
+ if (node) {
474
+ const totalDegree = node.metrics.inDegree + node.metrics.outDegree;
475
+ lines.push(`- \`${node.file.path}\` (${totalDegree} connections)`);
476
+ }
477
+ }
478
+ }
479
+ // Cycles
480
+ if (depGraph.cycles.length > 0) {
481
+ lines.push('');
482
+ lines.push(`**Circular Dependencies**: ${depGraph.cycles.length} cycle(s) detected`);
483
+ for (const cycle of depGraph.cycles.slice(0, 3)) {
484
+ const cycleFiles = cycle.map(id => {
485
+ const node = depGraph.nodes.find(n => n.id === id);
486
+ return node ? basename(node.file.path) : basename(id);
487
+ });
488
+ lines.push(`- ${cycleFiles.join(' → ')}`);
489
+ }
490
+ }
491
+ // Orphans
492
+ if (depGraph.rankings.orphanNodes.length > 0) {
493
+ lines.push('');
494
+ lines.push(`**Orphan Files**: ${depGraph.rankings.orphanNodes.length} file(s) with no imports or exports`);
495
+ }
496
+ lines.push('');
497
+ // Top files
498
+ lines.push('## Files Selected for Deep Analysis');
499
+ lines.push('The following files were selected as most significant:');
500
+ lines.push('');
501
+ const topFiles = repoMap.highValueFiles.slice(0, 15);
502
+ for (let i = 0; i < topFiles.length; i++) {
503
+ const file = topFiles[i];
504
+ const tags = file.tags.length > 0 ? ` - ${file.tags.join(', ')}` : '';
505
+ lines.push(`${i + 1}. \`${file.path}\` (score: ${file.score})${tags}`);
506
+ }
507
+ lines.push('');
508
+ // Recommendations
509
+ lines.push('## Recommendations');
510
+ const recommendations = [];
511
+ if (depGraph.cycles.length > 0) {
512
+ recommendations.push(`- Consider breaking the ${depGraph.cycles.length} circular dependency cycle(s)`);
513
+ }
514
+ if (depGraph.rankings.orphanNodes.length > 0) {
515
+ recommendations.push(`- Review ${depGraph.rankings.orphanNodes.length} orphan file(s) that may be unused`);
516
+ }
517
+ if (depGraph.rankings.bridgeNodes.length > 0) {
518
+ recommendations.push(`- The following files are critical bridges: ${depGraph.rankings.bridgeNodes.slice(0, 3).map(id => {
519
+ const node = depGraph.nodes.find(n => n.id === id);
520
+ return node ? `\`${basename(node.file.path)}\`` : '';
521
+ }).filter(Boolean).join(', ')}`);
522
+ }
523
+ if (recommendations.length === 0) {
524
+ recommendations.push('- No immediate architectural concerns detected');
525
+ }
526
+ for (const rec of recommendations) {
527
+ lines.push(rec);
528
+ }
529
+ lines.push('');
530
+ // Footer
531
+ lines.push('---');
532
+ lines.push(`*Generated by spec-gen v${repoMap.metadata.version}*`);
533
+ return lines.join('\n');
534
+ }
535
+ /**
536
+ * Format project type for human reading
537
+ */
538
+ formatProjectTypeReadable(type) {
539
+ const mapping = {
540
+ nodejs: 'Node.js/TypeScript',
541
+ python: 'Python',
542
+ rust: 'Rust',
543
+ go: 'Go',
544
+ java: 'Java',
545
+ ruby: 'Ruby',
546
+ php: 'PHP',
547
+ unknown: 'Unknown',
548
+ };
549
+ return mapping[type] ?? type;
550
+ }
551
+ /**
552
+ * Generate dependency diagram in Mermaid format
553
+ */
554
+ generateDependencyDiagram(depGraph) {
555
+ // Use the built-in Mermaid converter with clustering
556
+ const lines = ['```mermaid'];
557
+ // Generate diagram with top 30 files
558
+ const mermaid = toMermaidFormat(depGraph, 30);
559
+ lines.push(mermaid);
560
+ lines.push('```');
561
+ return lines.join('\n');
562
+ }
563
+ /**
564
+ * Generate LLM context preparation
565
+ */
566
+ async generateLLMContext(repoMap, depGraph) {
567
+ // Phase 1: Survey (repo structure summary)
568
+ const phase1 = {
569
+ purpose: 'Initial project categorization',
570
+ files: [
571
+ {
572
+ path: 'repo-structure.json',
573
+ tokens: 2000, // Estimate
574
+ },
575
+ ],
576
+ // FIX 1: estimatedTokens → totalTokens pour cohérence avec phase2/phase3
577
+ totalTokens: 2000,
578
+ };
579
+ // Phase 2: Deep analysis (top files by importance, excluding test files)
580
+ const phase2Files = [];
581
+ const topFiles = repoMap.highValueFiles
582
+ .filter(f => !isTestFile(f.path))
583
+ .slice(0, this.options.maxDeepAnalysisFiles);
584
+ for (const file of topFiles) {
585
+ try {
586
+ const content = await readFile(file.absolutePath, 'utf-8');
587
+ const tokens = Math.ceil(content.length * this.options.tokensPerChar);
588
+ phase2Files.push({
589
+ path: file.path,
590
+ content: content.slice(0, 10000), // Limit content size
591
+ tokens,
592
+ });
593
+ }
594
+ catch {
595
+ // File couldn't be read, skip
596
+ }
597
+ }
598
+ const phase2 = {
599
+ purpose: 'Core entity and logic extraction',
600
+ files: phase2Files,
601
+ // FIX 2: tokens peut être undefined → utiliser ?? 0
602
+ totalTokens: phase2Files.reduce((sum, f) => sum + (f.tokens ?? 0), 0),
603
+ };
604
+ // Phase 3: Validation (random leaf nodes not in phase 2, excluding test files)
605
+ const phase2Paths = new Set(phase2Files.map(f => f.path));
606
+ const leafFiles = depGraph.rankings.leafNodes
607
+ .map(id => depGraph.nodes.find(n => n.id === id)?.file)
608
+ .filter((f) => f !== undefined)
609
+ .filter(f => !phase2Paths.has(f.path))
610
+ .filter(f => !isTestFile(f.path));
611
+ // FIX 3: Fisher-Yates shuffle (sort(() => Math.random()) est biaisé + mute le tableau original)
612
+ const shuffled = [...leafFiles];
613
+ for (let i = shuffled.length - 1; i > 0; i--) {
614
+ const j = Math.floor(Math.random() * (i + 1));
615
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
616
+ }
617
+ const validationFiles = shuffled.slice(0, this.options.maxValidationFiles);
618
+ const phase3Files = [];
619
+ for (const file of validationFiles) {
620
+ try {
621
+ const content = await readFile(file.absolutePath, 'utf-8');
622
+ const tokens = Math.ceil(content.length * this.options.tokensPerChar);
623
+ phase3Files.push({
624
+ path: file.path,
625
+ content: content.slice(0, 5000),
626
+ tokens,
627
+ });
628
+ }
629
+ catch {
630
+ // File couldn't be read, skip
631
+ }
632
+ }
633
+ const phase3 = {
634
+ purpose: 'Verification samples',
635
+ files: phase3Files,
636
+ totalTokens: phase3Files.reduce((sum, f) => sum + (f.tokens ?? 0), 0),
637
+ };
638
+ // Signature extraction + call graph for ALL analyzed files
639
+ // Read each file once and reuse the content for both operations.
640
+ // All dynamic imports grouped here; CALL_GRAPH_LANGS hoisted out of the loop.
641
+ const { extractSignatures, detectLanguage } = await import('./signature-extractor.js');
642
+ const { CallGraphBuilder, serializeCallGraph } = await import('./call-graph.js');
643
+ const { detectDuplicates } = await import('./duplicate-detector.js');
644
+ const { analyzeForRefactoring } = await import('./refactor-analyzer.js');
645
+ const CALL_GRAPH_LANGS = new Set(['Python', 'TypeScript', 'JavaScript', 'Go', 'Rust', 'Ruby', 'Java']);
646
+ const signatures = [];
647
+ const callGraphFiles = [];
648
+ for (const file of repoMap.allFiles) {
649
+ try {
650
+ const content = await readFile(file.absolutePath, 'utf-8');
651
+ const isTest = isTestFile(file.path);
652
+ // Signatures: exclude test files
653
+ if (!isTest) {
654
+ const map = extractSignatures(file.path, content);
655
+ if (map.entries.length > 0) {
656
+ signatures.push(map);
657
+ }
658
+ }
659
+ // Call graph — all supported languages, exclude test files
660
+ const lang = detectLanguage(file.path);
661
+ if (!isTest && CALL_GRAPH_LANGS.has(lang)) {
662
+ callGraphFiles.push({ path: file.path, content, language: lang });
663
+ }
664
+ }
665
+ catch {
666
+ // skip unreadable files
667
+ }
668
+ }
669
+ // Build call graph
670
+ const builder = new CallGraphBuilder();
671
+ const callGraphResult = await builder.build(callGraphFiles);
672
+ const callGraph = serializeCallGraph(callGraphResult);
673
+ // Duplicate detection — static analysis, no LLM (Types 1-2-3)
674
+ const duplicates = detectDuplicates(callGraphFiles, callGraphResult);
675
+ // Save duplicates
676
+ try {
677
+ await writeFile(join(this.options.outputDir, 'duplicates.json'), JSON.stringify(duplicates, null, 2));
678
+ }
679
+ catch {
680
+ // non-fatal if output dir doesn't exist yet
681
+ }
682
+ // Refactoring priorities (structural — enriched after generate)
683
+ let mappings;
684
+ try {
685
+ const mappingRaw = await readFile(join(this.options.outputDir, 'mapping.json'), 'utf-8');
686
+ const mappingJson = JSON.parse(mappingRaw);
687
+ mappings = mappingJson.mappings;
688
+ }
689
+ catch {
690
+ // mapping.json not yet available — that's fine
691
+ }
692
+ const refactorReport = analyzeForRefactoring(callGraph, mappings, duplicates);
693
+ // Save refactor priorities
694
+ try {
695
+ await writeFile(join(this.options.outputDir, 'refactor-priorities.json'), JSON.stringify(refactorReport, null, 2));
696
+ }
697
+ catch {
698
+ // non-fatal
699
+ }
700
+ return {
701
+ phase1_survey: phase1,
702
+ phase2_deep: phase2,
703
+ phase3_validation: phase3,
704
+ signatures,
705
+ callGraph,
706
+ };
707
+ }
708
+ }
709
+ // ============================================================================
710
+ // CONVENIENCE FUNCTIONS
711
+ // ============================================================================
712
+ /**
713
+ * Generate all artifacts
714
+ */
715
+ export async function generateArtifacts(repoMap, depGraph, options) {
716
+ const generator = new AnalysisArtifactGenerator(options);
717
+ return generator.generate(repoMap, depGraph);
718
+ }
719
+ /**
720
+ * Generate and save all artifacts
721
+ */
722
+ export async function generateAndSaveArtifacts(repoMap, depGraph, options) {
723
+ const generator = new AnalysisArtifactGenerator(options);
724
+ return generator.generateAndSave(repoMap, depGraph);
725
+ }
726
+ //# sourceMappingURL=artifact-generator.js.map