openlore 2.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 (634) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +268 -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 +143 -0
  6. package/dist/api/analyze.js.map +1 -0
  7. package/dist/api/audit.d.ts +10 -0
  8. package/dist/api/audit.d.ts.map +1 -0
  9. package/dist/api/audit.js +117 -0
  10. package/dist/api/audit.js.map +1 -0
  11. package/dist/api/decisions.d.ts +55 -0
  12. package/dist/api/decisions.d.ts.map +1 -0
  13. package/dist/api/decisions.js +157 -0
  14. package/dist/api/decisions.js.map +1 -0
  15. package/dist/api/drift.d.ts +21 -0
  16. package/dist/api/drift.d.ts.map +1 -0
  17. package/dist/api/drift.js +152 -0
  18. package/dist/api/drift.js.map +1 -0
  19. package/dist/api/generate.d.ts +18 -0
  20. package/dist/api/generate.d.ts.map +1 -0
  21. package/dist/api/generate.js +259 -0
  22. package/dist/api/generate.js.map +1 -0
  23. package/dist/api/index.d.ts +41 -0
  24. package/dist/api/index.d.ts.map +1 -0
  25. package/dist/api/index.js +34 -0
  26. package/dist/api/index.js.map +1 -0
  27. package/dist/api/init.d.ts +18 -0
  28. package/dist/api/init.d.ts.map +1 -0
  29. package/dist/api/init.js +83 -0
  30. package/dist/api/init.js.map +1 -0
  31. package/dist/api/run.d.ts +19 -0
  32. package/dist/api/run.d.ts.map +1 -0
  33. package/dist/api/run.js +312 -0
  34. package/dist/api/run.js.map +1 -0
  35. package/dist/api/specs.d.ts +49 -0
  36. package/dist/api/specs.d.ts.map +1 -0
  37. package/dist/api/specs.js +137 -0
  38. package/dist/api/specs.js.map +1 -0
  39. package/dist/api/types.d.ts +201 -0
  40. package/dist/api/types.d.ts.map +1 -0
  41. package/dist/api/types.js +9 -0
  42. package/dist/api/types.js.map +1 -0
  43. package/dist/api/verify.d.ts +20 -0
  44. package/dist/api/verify.d.ts.map +1 -0
  45. package/dist/api/verify.js +117 -0
  46. package/dist/api/verify.js.map +1 -0
  47. package/dist/cli/commands/analyze.d.ts +30 -0
  48. package/dist/cli/commands/analyze.d.ts.map +1 -0
  49. package/dist/cli/commands/analyze.js +683 -0
  50. package/dist/cli/commands/analyze.js.map +1 -0
  51. package/dist/cli/commands/audit.d.ts +9 -0
  52. package/dist/cli/commands/audit.d.ts.map +1 -0
  53. package/dist/cli/commands/audit.js +98 -0
  54. package/dist/cli/commands/audit.js.map +1 -0
  55. package/dist/cli/commands/decisions.d.ts +16 -0
  56. package/dist/cli/commands/decisions.d.ts.map +1 -0
  57. package/dist/cli/commands/decisions.js +864 -0
  58. package/dist/cli/commands/decisions.js.map +1 -0
  59. package/dist/cli/commands/digest.d.ts +9 -0
  60. package/dist/cli/commands/digest.d.ts.map +1 -0
  61. package/dist/cli/commands/digest.js +61 -0
  62. package/dist/cli/commands/digest.js.map +1 -0
  63. package/dist/cli/commands/doctor.d.ts +9 -0
  64. package/dist/cli/commands/doctor.d.ts.map +1 -0
  65. package/dist/cli/commands/doctor.js +398 -0
  66. package/dist/cli/commands/doctor.js.map +1 -0
  67. package/dist/cli/commands/drift.d.ts +9 -0
  68. package/dist/cli/commands/drift.d.ts.map +1 -0
  69. package/dist/cli/commands/drift.js +550 -0
  70. package/dist/cli/commands/drift.js.map +1 -0
  71. package/dist/cli/commands/generate.d.ts +9 -0
  72. package/dist/cli/commands/generate.d.ts.map +1 -0
  73. package/dist/cli/commands/generate.js +565 -0
  74. package/dist/cli/commands/generate.js.map +1 -0
  75. package/dist/cli/commands/init.d.ts +9 -0
  76. package/dist/cli/commands/init.d.ts.map +1 -0
  77. package/dist/cli/commands/init.js +173 -0
  78. package/dist/cli/commands/init.js.map +1 -0
  79. package/dist/cli/commands/mcp.d.ts +2235 -0
  80. package/dist/cli/commands/mcp.d.ts.map +1 -0
  81. package/dist/cli/commands/mcp.js +1384 -0
  82. package/dist/cli/commands/mcp.js.map +1 -0
  83. package/dist/cli/commands/refresh-stories.d.ts +10 -0
  84. package/dist/cli/commands/refresh-stories.d.ts.map +1 -0
  85. package/dist/cli/commands/refresh-stories.js +314 -0
  86. package/dist/cli/commands/refresh-stories.js.map +1 -0
  87. package/dist/cli/commands/run.d.ts +9 -0
  88. package/dist/cli/commands/run.d.ts.map +1 -0
  89. package/dist/cli/commands/run.js +459 -0
  90. package/dist/cli/commands/run.js.map +1 -0
  91. package/dist/cli/commands/setup.d.ts +19 -0
  92. package/dist/cli/commands/setup.d.ts.map +1 -0
  93. package/dist/cli/commands/setup.js +355 -0
  94. package/dist/cli/commands/setup.js.map +1 -0
  95. package/dist/cli/commands/test.d.ts +22 -0
  96. package/dist/cli/commands/test.d.ts.map +1 -0
  97. package/dist/cli/commands/test.js +180 -0
  98. package/dist/cli/commands/test.js.map +1 -0
  99. package/dist/cli/commands/verify.d.ts +9 -0
  100. package/dist/cli/commands/verify.d.ts.map +1 -0
  101. package/dist/cli/commands/verify.js +383 -0
  102. package/dist/cli/commands/verify.js.map +1 -0
  103. package/dist/cli/commands/view.d.ts +13 -0
  104. package/dist/cli/commands/view.d.ts.map +1 -0
  105. package/dist/cli/commands/view.js +547 -0
  106. package/dist/cli/commands/view.js.map +1 -0
  107. package/dist/cli/index.d.ts +9 -0
  108. package/dist/cli/index.d.ts.map +1 -0
  109. package/dist/cli/index.js +118 -0
  110. package/dist/cli/index.js.map +1 -0
  111. package/dist/cli/tui-approval.d.ts +11 -0
  112. package/dist/cli/tui-approval.d.ts.map +1 -0
  113. package/dist/cli/tui-approval.js +129 -0
  114. package/dist/cli/tui-approval.js.map +1 -0
  115. package/dist/constants.d.ts +314 -0
  116. package/dist/constants.d.ts.map +1 -0
  117. package/dist/constants.js +382 -0
  118. package/dist/constants.js.map +1 -0
  119. package/dist/core/analyzer/ai-config-generator.d.ts +54 -0
  120. package/dist/core/analyzer/ai-config-generator.d.ts.map +1 -0
  121. package/dist/core/analyzer/ai-config-generator.js +98 -0
  122. package/dist/core/analyzer/ai-config-generator.js.map +1 -0
  123. package/dist/core/analyzer/architecture-writer.d.ts +67 -0
  124. package/dist/core/analyzer/architecture-writer.d.ts.map +1 -0
  125. package/dist/core/analyzer/architecture-writer.js +209 -0
  126. package/dist/core/analyzer/architecture-writer.js.map +1 -0
  127. package/dist/core/analyzer/artifact-generator.d.ts +261 -0
  128. package/dist/core/analyzer/artifact-generator.d.ts.map +1 -0
  129. package/dist/core/analyzer/artifact-generator.js +909 -0
  130. package/dist/core/analyzer/artifact-generator.js.map +1 -0
  131. package/dist/core/analyzer/ast-chunker.d.ts +24 -0
  132. package/dist/core/analyzer/ast-chunker.d.ts.map +1 -0
  133. package/dist/core/analyzer/ast-chunker.js +198 -0
  134. package/dist/core/analyzer/ast-chunker.js.map +1 -0
  135. package/dist/core/analyzer/call-graph.d.ts +162 -0
  136. package/dist/core/analyzer/call-graph.d.ts.map +1 -0
  137. package/dist/core/analyzer/call-graph.js +2040 -0
  138. package/dist/core/analyzer/call-graph.js.map +1 -0
  139. package/dist/core/analyzer/code-shaper.d.ts +33 -0
  140. package/dist/core/analyzer/code-shaper.d.ts.map +1 -0
  141. package/dist/core/analyzer/code-shaper.js +154 -0
  142. package/dist/core/analyzer/code-shaper.js.map +1 -0
  143. package/dist/core/analyzer/codebase-digest.d.ts +40 -0
  144. package/dist/core/analyzer/codebase-digest.d.ts.map +1 -0
  145. package/dist/core/analyzer/codebase-digest.js +195 -0
  146. package/dist/core/analyzer/codebase-digest.js.map +1 -0
  147. package/dist/core/analyzer/cpp-header-resolver.d.ts +30 -0
  148. package/dist/core/analyzer/cpp-header-resolver.d.ts.map +1 -0
  149. package/dist/core/analyzer/cpp-header-resolver.js +71 -0
  150. package/dist/core/analyzer/cpp-header-resolver.js.map +1 -0
  151. package/dist/core/analyzer/dependency-graph.d.ts +230 -0
  152. package/dist/core/analyzer/dependency-graph.d.ts.map +1 -0
  153. package/dist/core/analyzer/dependency-graph.js +752 -0
  154. package/dist/core/analyzer/dependency-graph.js.map +1 -0
  155. package/dist/core/analyzer/duplicate-detector.d.ts +52 -0
  156. package/dist/core/analyzer/duplicate-detector.d.ts.map +1 -0
  157. package/dist/core/analyzer/duplicate-detector.js +289 -0
  158. package/dist/core/analyzer/duplicate-detector.js.map +1 -0
  159. package/dist/core/analyzer/embedding-service.d.ts +56 -0
  160. package/dist/core/analyzer/embedding-service.d.ts.map +1 -0
  161. package/dist/core/analyzer/embedding-service.js +118 -0
  162. package/dist/core/analyzer/embedding-service.js.map +1 -0
  163. package/dist/core/analyzer/env-extractor.d.ts +33 -0
  164. package/dist/core/analyzer/env-extractor.d.ts.map +1 -0
  165. package/dist/core/analyzer/env-extractor.js +196 -0
  166. package/dist/core/analyzer/env-extractor.js.map +1 -0
  167. package/dist/core/analyzer/external-packages.d.ts +20 -0
  168. package/dist/core/analyzer/external-packages.d.ts.map +1 -0
  169. package/dist/core/analyzer/external-packages.js +175 -0
  170. package/dist/core/analyzer/external-packages.js.map +1 -0
  171. package/dist/core/analyzer/file-walker.d.ts +78 -0
  172. package/dist/core/analyzer/file-walker.d.ts.map +1 -0
  173. package/dist/core/analyzer/file-walker.js +532 -0
  174. package/dist/core/analyzer/file-walker.js.map +1 -0
  175. package/dist/core/analyzer/function-registry-trie.d.ts +21 -0
  176. package/dist/core/analyzer/function-registry-trie.d.ts.map +1 -0
  177. package/dist/core/analyzer/function-registry-trie.js +39 -0
  178. package/dist/core/analyzer/function-registry-trie.js.map +1 -0
  179. package/dist/core/analyzer/http-route-parser.d.ts +152 -0
  180. package/dist/core/analyzer/http-route-parser.d.ts.map +1 -0
  181. package/dist/core/analyzer/http-route-parser.js +971 -0
  182. package/dist/core/analyzer/http-route-parser.js.map +1 -0
  183. package/dist/core/analyzer/import-parser.d.ts +100 -0
  184. package/dist/core/analyzer/import-parser.d.ts.map +1 -0
  185. package/dist/core/analyzer/import-parser.js +952 -0
  186. package/dist/core/analyzer/import-parser.js.map +1 -0
  187. package/dist/core/analyzer/import-resolver-bridge.d.ts +25 -0
  188. package/dist/core/analyzer/import-resolver-bridge.d.ts.map +1 -0
  189. package/dist/core/analyzer/import-resolver-bridge.js +99 -0
  190. package/dist/core/analyzer/import-resolver-bridge.js.map +1 -0
  191. package/dist/core/analyzer/index.d.ts +10 -0
  192. package/dist/core/analyzer/index.d.ts.map +1 -0
  193. package/dist/core/analyzer/index.js +10 -0
  194. package/dist/core/analyzer/index.js.map +1 -0
  195. package/dist/core/analyzer/middleware-extractor.d.ts +29 -0
  196. package/dist/core/analyzer/middleware-extractor.d.ts.map +1 -0
  197. package/dist/core/analyzer/middleware-extractor.js +195 -0
  198. package/dist/core/analyzer/middleware-extractor.js.map +1 -0
  199. package/dist/core/analyzer/refactor-analyzer.d.ts +83 -0
  200. package/dist/core/analyzer/refactor-analyzer.d.ts.map +1 -0
  201. package/dist/core/analyzer/refactor-analyzer.js +351 -0
  202. package/dist/core/analyzer/refactor-analyzer.js.map +1 -0
  203. package/dist/core/analyzer/repository-mapper.d.ts +150 -0
  204. package/dist/core/analyzer/repository-mapper.d.ts.map +1 -0
  205. package/dist/core/analyzer/repository-mapper.js +740 -0
  206. package/dist/core/analyzer/repository-mapper.js.map +1 -0
  207. package/dist/core/analyzer/schema-extractor.d.ts +41 -0
  208. package/dist/core/analyzer/schema-extractor.d.ts.map +1 -0
  209. package/dist/core/analyzer/schema-extractor.js +229 -0
  210. package/dist/core/analyzer/schema-extractor.js.map +1 -0
  211. package/dist/core/analyzer/signature-extractor.d.ts +31 -0
  212. package/dist/core/analyzer/signature-extractor.d.ts.map +1 -0
  213. package/dist/core/analyzer/signature-extractor.js +675 -0
  214. package/dist/core/analyzer/signature-extractor.js.map +1 -0
  215. package/dist/core/analyzer/significance-scorer.d.ts +79 -0
  216. package/dist/core/analyzer/significance-scorer.d.ts.map +1 -0
  217. package/dist/core/analyzer/significance-scorer.js +407 -0
  218. package/dist/core/analyzer/significance-scorer.js.map +1 -0
  219. package/dist/core/analyzer/spec-snapshot-generator.d.ts +17 -0
  220. package/dist/core/analyzer/spec-snapshot-generator.d.ts.map +1 -0
  221. package/dist/core/analyzer/spec-snapshot-generator.js +201 -0
  222. package/dist/core/analyzer/spec-snapshot-generator.js.map +1 -0
  223. package/dist/core/analyzer/spec-vector-index.d.ts +68 -0
  224. package/dist/core/analyzer/spec-vector-index.d.ts.map +1 -0
  225. package/dist/core/analyzer/spec-vector-index.js +340 -0
  226. package/dist/core/analyzer/spec-vector-index.js.map +1 -0
  227. package/dist/core/analyzer/subgraph-extractor.d.ts +51 -0
  228. package/dist/core/analyzer/subgraph-extractor.d.ts.map +1 -0
  229. package/dist/core/analyzer/subgraph-extractor.js +147 -0
  230. package/dist/core/analyzer/subgraph-extractor.js.map +1 -0
  231. package/dist/core/analyzer/type-inference-engine.d.ts +23 -0
  232. package/dist/core/analyzer/type-inference-engine.d.ts.map +1 -0
  233. package/dist/core/analyzer/type-inference-engine.js +130 -0
  234. package/dist/core/analyzer/type-inference-engine.js.map +1 -0
  235. package/dist/core/analyzer/ui-component-extractor.d.ts +43 -0
  236. package/dist/core/analyzer/ui-component-extractor.d.ts.map +1 -0
  237. package/dist/core/analyzer/ui-component-extractor.js +245 -0
  238. package/dist/core/analyzer/ui-component-extractor.js.map +1 -0
  239. package/dist/core/analyzer/unified-search.d.ts +116 -0
  240. package/dist/core/analyzer/unified-search.d.ts.map +1 -0
  241. package/dist/core/analyzer/unified-search.js +231 -0
  242. package/dist/core/analyzer/unified-search.js.map +1 -0
  243. package/dist/core/analyzer/vector-index.d.ts +92 -0
  244. package/dist/core/analyzer/vector-index.d.ts.map +1 -0
  245. package/dist/core/analyzer/vector-index.js +451 -0
  246. package/dist/core/analyzer/vector-index.js.map +1 -0
  247. package/dist/core/decisions/consolidator.d.ts +14 -0
  248. package/dist/core/decisions/consolidator.d.ts.map +1 -0
  249. package/dist/core/decisions/consolidator.js +169 -0
  250. package/dist/core/decisions/consolidator.js.map +1 -0
  251. package/dist/core/decisions/extractor.d.ts +26 -0
  252. package/dist/core/decisions/extractor.d.ts.map +1 -0
  253. package/dist/core/decisions/extractor.js +156 -0
  254. package/dist/core/decisions/extractor.js.map +1 -0
  255. package/dist/core/decisions/index.d.ts +19 -0
  256. package/dist/core/decisions/index.d.ts.map +1 -0
  257. package/dist/core/decisions/index.js +16 -0
  258. package/dist/core/decisions/index.js.map +1 -0
  259. package/dist/core/decisions/store.d.ts +36 -0
  260. package/dist/core/decisions/store.d.ts.map +1 -0
  261. package/dist/core/decisions/store.js +109 -0
  262. package/dist/core/decisions/store.js.map +1 -0
  263. package/dist/core/decisions/syncer.d.ts +27 -0
  264. package/dist/core/decisions/syncer.d.ts.map +1 -0
  265. package/dist/core/decisions/syncer.js +214 -0
  266. package/dist/core/decisions/syncer.js.map +1 -0
  267. package/dist/core/decisions/verifier.d.ts +20 -0
  268. package/dist/core/decisions/verifier.d.ts.map +1 -0
  269. package/dist/core/decisions/verifier.js +115 -0
  270. package/dist/core/decisions/verifier.js.map +1 -0
  271. package/dist/core/digest/digest-generator.d.ts +29 -0
  272. package/dist/core/digest/digest-generator.d.ts.map +1 -0
  273. package/dist/core/digest/digest-generator.js +181 -0
  274. package/dist/core/digest/digest-generator.js.map +1 -0
  275. package/dist/core/drift/drift-detector.d.ts +102 -0
  276. package/dist/core/drift/drift-detector.d.ts.map +1 -0
  277. package/dist/core/drift/drift-detector.js +598 -0
  278. package/dist/core/drift/drift-detector.js.map +1 -0
  279. package/dist/core/drift/git-diff.d.ts +60 -0
  280. package/dist/core/drift/git-diff.d.ts.map +1 -0
  281. package/dist/core/drift/git-diff.js +383 -0
  282. package/dist/core/drift/git-diff.js.map +1 -0
  283. package/dist/core/drift/index.d.ts +12 -0
  284. package/dist/core/drift/index.d.ts.map +1 -0
  285. package/dist/core/drift/index.js +9 -0
  286. package/dist/core/drift/index.js.map +1 -0
  287. package/dist/core/drift/spec-mapper.d.ts +73 -0
  288. package/dist/core/drift/spec-mapper.d.ts.map +1 -0
  289. package/dist/core/drift/spec-mapper.js +353 -0
  290. package/dist/core/drift/spec-mapper.js.map +1 -0
  291. package/dist/core/drift/test-suggester.d.ts +18 -0
  292. package/dist/core/drift/test-suggester.d.ts.map +1 -0
  293. package/dist/core/drift/test-suggester.js +107 -0
  294. package/dist/core/drift/test-suggester.js.map +1 -0
  295. package/dist/core/generator/adr-generator.d.ts +32 -0
  296. package/dist/core/generator/adr-generator.d.ts.map +1 -0
  297. package/dist/core/generator/adr-generator.js +192 -0
  298. package/dist/core/generator/adr-generator.js.map +1 -0
  299. package/dist/core/generator/index.d.ts +9 -0
  300. package/dist/core/generator/index.d.ts.map +1 -0
  301. package/dist/core/generator/index.js +12 -0
  302. package/dist/core/generator/index.js.map +1 -0
  303. package/dist/core/generator/mapping-generator.d.ts +54 -0
  304. package/dist/core/generator/mapping-generator.d.ts.map +1 -0
  305. package/dist/core/generator/mapping-generator.js +240 -0
  306. package/dist/core/generator/mapping-generator.js.map +1 -0
  307. package/dist/core/generator/openspec-compat.d.ts +160 -0
  308. package/dist/core/generator/openspec-compat.d.ts.map +1 -0
  309. package/dist/core/generator/openspec-compat.js +524 -0
  310. package/dist/core/generator/openspec-compat.js.map +1 -0
  311. package/dist/core/generator/openspec-format-generator.d.ts +131 -0
  312. package/dist/core/generator/openspec-format-generator.d.ts.map +1 -0
  313. package/dist/core/generator/openspec-format-generator.js +963 -0
  314. package/dist/core/generator/openspec-format-generator.js.map +1 -0
  315. package/dist/core/generator/openspec-writer.d.ts +130 -0
  316. package/dist/core/generator/openspec-writer.d.ts.map +1 -0
  317. package/dist/core/generator/openspec-writer.js +404 -0
  318. package/dist/core/generator/openspec-writer.js.map +1 -0
  319. package/dist/core/generator/prompts.d.ts +35 -0
  320. package/dist/core/generator/prompts.d.ts.map +1 -0
  321. package/dist/core/generator/prompts.js +212 -0
  322. package/dist/core/generator/prompts.js.map +1 -0
  323. package/dist/core/generator/rag-manifest-generator.d.ts +37 -0
  324. package/dist/core/generator/rag-manifest-generator.d.ts.map +1 -0
  325. package/dist/core/generator/rag-manifest-generator.js +134 -0
  326. package/dist/core/generator/rag-manifest-generator.js.map +1 -0
  327. package/dist/core/generator/schemas.d.ts +365 -0
  328. package/dist/core/generator/schemas.d.ts.map +1 -0
  329. package/dist/core/generator/schemas.js +190 -0
  330. package/dist/core/generator/schemas.js.map +1 -0
  331. package/dist/core/generator/spec-pipeline.d.ts +123 -0
  332. package/dist/core/generator/spec-pipeline.d.ts.map +1 -0
  333. package/dist/core/generator/spec-pipeline.js +699 -0
  334. package/dist/core/generator/spec-pipeline.js.map +1 -0
  335. package/dist/core/generator/stages/stage1-survey.d.ts +19 -0
  336. package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -0
  337. package/dist/core/generator/stages/stage1-survey.js +171 -0
  338. package/dist/core/generator/stages/stage1-survey.js.map +1 -0
  339. package/dist/core/generator/stages/stage2-entities.d.ts +11 -0
  340. package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -0
  341. package/dist/core/generator/stages/stage2-entities.js +74 -0
  342. package/dist/core/generator/stages/stage2-entities.js.map +1 -0
  343. package/dist/core/generator/stages/stage3-services.d.ts +11 -0
  344. package/dist/core/generator/stages/stage3-services.d.ts.map +1 -0
  345. package/dist/core/generator/stages/stage3-services.js +85 -0
  346. package/dist/core/generator/stages/stage3-services.js.map +1 -0
  347. package/dist/core/generator/stages/stage4-api.d.ts +11 -0
  348. package/dist/core/generator/stages/stage4-api.d.ts.map +1 -0
  349. package/dist/core/generator/stages/stage4-api.js +72 -0
  350. package/dist/core/generator/stages/stage4-api.js.map +1 -0
  351. package/dist/core/generator/stages/stage5-architecture.d.ts +11 -0
  352. package/dist/core/generator/stages/stage5-architecture.d.ts.map +1 -0
  353. package/dist/core/generator/stages/stage5-architecture.js +75 -0
  354. package/dist/core/generator/stages/stage5-architecture.js.map +1 -0
  355. package/dist/core/generator/stages/stage6-adr.d.ts +8 -0
  356. package/dist/core/generator/stages/stage6-adr.d.ts.map +1 -0
  357. package/dist/core/generator/stages/stage6-adr.js +47 -0
  358. package/dist/core/generator/stages/stage6-adr.js.map +1 -0
  359. package/dist/core/services/chat-agent.d.ts +50 -0
  360. package/dist/core/services/chat-agent.d.ts.map +1 -0
  361. package/dist/core/services/chat-agent.js +369 -0
  362. package/dist/core/services/chat-agent.js.map +1 -0
  363. package/dist/core/services/chat-tools.d.ts +32 -0
  364. package/dist/core/services/chat-tools.d.ts.map +1 -0
  365. package/dist/core/services/chat-tools.js +494 -0
  366. package/dist/core/services/chat-tools.js.map +1 -0
  367. package/dist/core/services/config-manager.d.ts +61 -0
  368. package/dist/core/services/config-manager.d.ts.map +1 -0
  369. package/dist/core/services/config-manager.js +149 -0
  370. package/dist/core/services/config-manager.js.map +1 -0
  371. package/dist/core/services/edge-store.d.ts +57 -0
  372. package/dist/core/services/edge-store.d.ts.map +1 -0
  373. package/dist/core/services/edge-store.js +419 -0
  374. package/dist/core/services/edge-store.js.map +1 -0
  375. package/dist/core/services/gitignore-manager.d.ts +29 -0
  376. package/dist/core/services/gitignore-manager.d.ts.map +1 -0
  377. package/dist/core/services/gitignore-manager.js +95 -0
  378. package/dist/core/services/gitignore-manager.js.map +1 -0
  379. package/dist/core/services/index.d.ts +8 -0
  380. package/dist/core/services/index.d.ts.map +1 -0
  381. package/dist/core/services/index.js +8 -0
  382. package/dist/core/services/index.js.map +1 -0
  383. package/dist/core/services/llm-service.d.ts +379 -0
  384. package/dist/core/services/llm-service.d.ts.map +1 -0
  385. package/dist/core/services/llm-service.js +1553 -0
  386. package/dist/core/services/llm-service.js.map +1 -0
  387. package/dist/core/services/mcp-handlers/analysis.d.ts +127 -0
  388. package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -0
  389. package/dist/core/services/mcp-handlers/analysis.js +1185 -0
  390. package/dist/core/services/mcp-handlers/analysis.js.map +1 -0
  391. package/dist/core/services/mcp-handlers/change.d.ts +14 -0
  392. package/dist/core/services/mcp-handlers/change.d.ts.map +1 -0
  393. package/dist/core/services/mcp-handlers/change.js +416 -0
  394. package/dist/core/services/mcp-handlers/change.js.map +1 -0
  395. package/dist/core/services/mcp-handlers/decisions.d.ts +16 -0
  396. package/dist/core/services/mcp-handlers/decisions.d.ts.map +1 -0
  397. package/dist/core/services/mcp-handlers/decisions.js +239 -0
  398. package/dist/core/services/mcp-handlers/decisions.js.map +1 -0
  399. package/dist/core/services/mcp-handlers/graph.d.ts +94 -0
  400. package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -0
  401. package/dist/core/services/mcp-handlers/graph.js +693 -0
  402. package/dist/core/services/mcp-handlers/graph.js.map +1 -0
  403. package/dist/core/services/mcp-handlers/orient.d.ts +17 -0
  404. package/dist/core/services/mcp-handlers/orient.d.ts.map +1 -0
  405. package/dist/core/services/mcp-handlers/orient.js +357 -0
  406. package/dist/core/services/mcp-handlers/orient.js.map +1 -0
  407. package/dist/core/services/mcp-handlers/semantic.d.ts +66 -0
  408. package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -0
  409. package/dist/core/services/mcp-handlers/semantic.js +432 -0
  410. package/dist/core/services/mcp-handlers/semantic.js.map +1 -0
  411. package/dist/core/services/mcp-handlers/utils.d.ts +85 -0
  412. package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -0
  413. package/dist/core/services/mcp-handlers/utils.js +262 -0
  414. package/dist/core/services/mcp-handlers/utils.js.map +1 -0
  415. package/dist/core/services/mcp-watcher.d.ts +41 -0
  416. package/dist/core/services/mcp-watcher.d.ts.map +1 -0
  417. package/dist/core/services/mcp-watcher.js +254 -0
  418. package/dist/core/services/mcp-watcher.js.map +1 -0
  419. package/dist/core/services/project-detector.d.ts +32 -0
  420. package/dist/core/services/project-detector.d.ts.map +1 -0
  421. package/dist/core/services/project-detector.js +100 -0
  422. package/dist/core/services/project-detector.js.map +1 -0
  423. package/dist/core/test-generator/coverage-analyzer.d.ts +27 -0
  424. package/dist/core/test-generator/coverage-analyzer.d.ts.map +1 -0
  425. package/dist/core/test-generator/coverage-analyzer.js +285 -0
  426. package/dist/core/test-generator/coverage-analyzer.js.map +1 -0
  427. package/dist/core/test-generator/framework-detector.d.ts +17 -0
  428. package/dist/core/test-generator/framework-detector.d.ts.map +1 -0
  429. package/dist/core/test-generator/framework-detector.js +65 -0
  430. package/dist/core/test-generator/framework-detector.js.map +1 -0
  431. package/dist/core/test-generator/index.d.ts +14 -0
  432. package/dist/core/test-generator/index.d.ts.map +1 -0
  433. package/dist/core/test-generator/index.js +11 -0
  434. package/dist/core/test-generator/index.js.map +1 -0
  435. package/dist/core/test-generator/renderers/catch2.d.ts +8 -0
  436. package/dist/core/test-generator/renderers/catch2.d.ts.map +1 -0
  437. package/dist/core/test-generator/renderers/catch2.js +47 -0
  438. package/dist/core/test-generator/renderers/catch2.js.map +1 -0
  439. package/dist/core/test-generator/renderers/gtest.d.ts +8 -0
  440. package/dist/core/test-generator/renderers/gtest.d.ts.map +1 -0
  441. package/dist/core/test-generator/renderers/gtest.js +45 -0
  442. package/dist/core/test-generator/renderers/gtest.js.map +1 -0
  443. package/dist/core/test-generator/renderers/index.d.ts +20 -0
  444. package/dist/core/test-generator/renderers/index.d.ts.map +1 -0
  445. package/dist/core/test-generator/renderers/index.js +35 -0
  446. package/dist/core/test-generator/renderers/index.js.map +1 -0
  447. package/dist/core/test-generator/renderers/playwright.d.ts +8 -0
  448. package/dist/core/test-generator/renderers/playwright.d.ts.map +1 -0
  449. package/dist/core/test-generator/renderers/playwright.js +44 -0
  450. package/dist/core/test-generator/renderers/playwright.js.map +1 -0
  451. package/dist/core/test-generator/renderers/pytest.d.ts +8 -0
  452. package/dist/core/test-generator/renderers/pytest.d.ts.map +1 -0
  453. package/dist/core/test-generator/renderers/pytest.js +44 -0
  454. package/dist/core/test-generator/renderers/pytest.js.map +1 -0
  455. package/dist/core/test-generator/renderers/shared.d.ts +21 -0
  456. package/dist/core/test-generator/renderers/shared.d.ts.map +1 -0
  457. package/dist/core/test-generator/renderers/shared.js +56 -0
  458. package/dist/core/test-generator/renderers/shared.js.map +1 -0
  459. package/dist/core/test-generator/renderers/vitest.d.ts +8 -0
  460. package/dist/core/test-generator/renderers/vitest.d.ts.map +1 -0
  461. package/dist/core/test-generator/renderers/vitest.js +52 -0
  462. package/dist/core/test-generator/renderers/vitest.js.map +1 -0
  463. package/dist/core/test-generator/scenario-parser.d.ts +33 -0
  464. package/dist/core/test-generator/scenario-parser.d.ts.map +1 -0
  465. package/dist/core/test-generator/scenario-parser.js +244 -0
  466. package/dist/core/test-generator/scenario-parser.js.map +1 -0
  467. package/dist/core/test-generator/test-generator.d.ts +30 -0
  468. package/dist/core/test-generator/test-generator.d.ts.map +1 -0
  469. package/dist/core/test-generator/test-generator.js +174 -0
  470. package/dist/core/test-generator/test-generator.js.map +1 -0
  471. package/dist/core/test-generator/test-writer.d.ts +25 -0
  472. package/dist/core/test-generator/test-writer.d.ts.map +1 -0
  473. package/dist/core/test-generator/test-writer.js +128 -0
  474. package/dist/core/test-generator/test-writer.js.map +1 -0
  475. package/dist/core/test-generator/then-matchers.d.ts +35 -0
  476. package/dist/core/test-generator/then-matchers.d.ts.map +1 -0
  477. package/dist/core/test-generator/then-matchers.js +211 -0
  478. package/dist/core/test-generator/then-matchers.js.map +1 -0
  479. package/dist/core/verifier/index.d.ts +5 -0
  480. package/dist/core/verifier/index.d.ts.map +1 -0
  481. package/dist/core/verifier/index.js +5 -0
  482. package/dist/core/verifier/index.js.map +1 -0
  483. package/dist/core/verifier/verification-engine.d.ts +293 -0
  484. package/dist/core/verifier/verification-engine.d.ts.map +1 -0
  485. package/dist/core/verifier/verification-engine.js +919 -0
  486. package/dist/core/verifier/verification-engine.js.map +1 -0
  487. package/dist/types/index.d.ts +368 -0
  488. package/dist/types/index.d.ts.map +1 -0
  489. package/dist/types/index.js +5 -0
  490. package/dist/types/index.js.map +1 -0
  491. package/dist/types/pipeline.d.ts +167 -0
  492. package/dist/types/pipeline.d.ts.map +1 -0
  493. package/dist/types/pipeline.js +5 -0
  494. package/dist/types/pipeline.js.map +1 -0
  495. package/dist/types/test-generator.d.ts +103 -0
  496. package/dist/types/test-generator.d.ts.map +1 -0
  497. package/dist/types/test-generator.js +17 -0
  498. package/dist/types/test-generator.js.map +1 -0
  499. package/dist/utils/command-helpers.d.ts +68 -0
  500. package/dist/utils/command-helpers.d.ts.map +1 -0
  501. package/dist/utils/command-helpers.js +150 -0
  502. package/dist/utils/command-helpers.js.map +1 -0
  503. package/dist/utils/errors.d.ts +51 -0
  504. package/dist/utils/errors.d.ts.map +1 -0
  505. package/dist/utils/errors.js +129 -0
  506. package/dist/utils/errors.js.map +1 -0
  507. package/dist/utils/logger.d.ts +149 -0
  508. package/dist/utils/logger.d.ts.map +1 -0
  509. package/dist/utils/logger.js +342 -0
  510. package/dist/utils/logger.js.map +1 -0
  511. package/dist/utils/misc.d.ts +10 -0
  512. package/dist/utils/misc.d.ts.map +1 -0
  513. package/dist/utils/misc.js +21 -0
  514. package/dist/utils/misc.js.map +1 -0
  515. package/dist/utils/progress.d.ts +142 -0
  516. package/dist/utils/progress.d.ts.map +1 -0
  517. package/dist/utils/progress.js +283 -0
  518. package/dist/utils/progress.js.map +1 -0
  519. package/dist/utils/prompts.d.ts +53 -0
  520. package/dist/utils/prompts.d.ts.map +1 -0
  521. package/dist/utils/prompts.js +199 -0
  522. package/dist/utils/prompts.js.map +1 -0
  523. package/dist/utils/shutdown.d.ts +89 -0
  524. package/dist/utils/shutdown.d.ts.map +1 -0
  525. package/dist/utils/shutdown.js +238 -0
  526. package/dist/utils/shutdown.js.map +1 -0
  527. package/examples/bmad/README.md +113 -0
  528. package/examples/bmad/agents/architect.md +226 -0
  529. package/examples/bmad/agents/dev-brownfield.md +69 -0
  530. package/examples/bmad/setup/architect.customize.yaml +14 -0
  531. package/examples/bmad/tasks/implement-story.md +254 -0
  532. package/examples/bmad/tasks/onboarding.md +169 -0
  533. package/examples/bmad/tasks/refactor.md +178 -0
  534. package/examples/bmad/tasks/sprint-planning.md +168 -0
  535. package/examples/bmad/templates/story.md +108 -0
  536. package/examples/cline-workflows/openlore-analyze-codebase.md +101 -0
  537. package/examples/cline-workflows/openlore-check-spec-drift.md +102 -0
  538. package/examples/cline-workflows/openlore-execute-refactor.md +212 -0
  539. package/examples/cline-workflows/openlore-implement-feature.md +266 -0
  540. package/examples/cline-workflows/openlore-plan-refactor.md +279 -0
  541. package/examples/cline-workflows/openlore-refactor-codebase.md +16 -0
  542. package/examples/cline-workflows/openlore-write-tests.md +177 -0
  543. package/examples/drift-demo/openspec/config.yaml +14 -0
  544. package/examples/drift-demo/openspec/specs/architecture/spec.md +30 -0
  545. package/examples/drift-demo/openspec/specs/auth/spec.md +71 -0
  546. package/examples/drift-demo/openspec/specs/database/spec.md +33 -0
  547. package/examples/drift-demo/openspec/specs/overview/spec.md +20 -0
  548. package/examples/drift-demo/openspec/specs/projects/spec.md +55 -0
  549. package/examples/drift-demo/openspec/specs/tasks/spec.md +78 -0
  550. package/examples/drift-demo/package.json +21 -0
  551. package/examples/drift-demo/src/auth/auth-middleware.ts +30 -0
  552. package/examples/drift-demo/src/auth/auth-routes.ts +29 -0
  553. package/examples/drift-demo/src/auth/auth-service.ts +45 -0
  554. package/examples/drift-demo/src/database/connection.ts +27 -0
  555. package/examples/drift-demo/src/index.ts +16 -0
  556. package/examples/drift-demo/src/projects/project-model.ts +15 -0
  557. package/examples/drift-demo/src/projects/project-service.ts +34 -0
  558. package/examples/drift-demo/src/tasks/task-model.ts +37 -0
  559. package/examples/drift-demo/src/tasks/task-routes.ts +53 -0
  560. package/examples/drift-demo/src/tasks/task-service.ts +60 -0
  561. package/examples/drift-demo/src/utils/validation.ts +11 -0
  562. package/examples/drift-demo/tests/auth.test.ts +4 -0
  563. package/examples/drift-demo/tests/tasks.test.ts +4 -0
  564. package/examples/drift-demo/tsconfig.json +10 -0
  565. package/examples/drift-test/run-drift-test.sh +1087 -0
  566. package/examples/gsd/README.md +119 -0
  567. package/examples/gsd/commands/gsd/openlore-drift.md +111 -0
  568. package/examples/gsd/commands/gsd/openlore-orient.md +191 -0
  569. package/examples/mistral-vibe/README.md +101 -0
  570. package/examples/mistral-vibe/antipatterns-template.md +18 -0
  571. package/examples/mistral-vibe/skills/openlore-analyze-codebase/SKILL.md +124 -0
  572. package/examples/mistral-vibe/skills/openlore-brainstorm/SKILL.md +379 -0
  573. package/examples/mistral-vibe/skills/openlore-debug/SKILL.md +330 -0
  574. package/examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md +291 -0
  575. package/examples/mistral-vibe/skills/openlore-generate/SKILL.md +245 -0
  576. package/examples/mistral-vibe/skills/openlore-implement-story/SKILL.md +326 -0
  577. package/examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md +365 -0
  578. package/examples/mistral-vibe/skills/openlore-review-changes/SKILL.md +128 -0
  579. package/examples/mistral-vibe/skills/openlore-write-tests/SKILL.md +261 -0
  580. package/examples/opencode/agent-guard.ts +170 -0
  581. package/examples/opencode/plugins/anti-laziness.ts +202 -0
  582. package/examples/opencode/plugins/lib/openlore-context-injector-helpers.ts +116 -0
  583. package/examples/opencode/plugins/lib/openlore-decision-extractor-helpers.ts +65 -0
  584. package/examples/opencode/plugins/openlore-context-injector.test.ts +211 -0
  585. package/examples/opencode/plugins/openlore-context-injector.ts +165 -0
  586. package/examples/opencode/plugins/openlore-decision-extractor.test.ts +131 -0
  587. package/examples/opencode/plugins/openlore-decision-extractor.ts +322 -0
  588. package/examples/opencode/plugins/openlore-enforcer.ts +227 -0
  589. package/examples/opencode/prompts/sisyphus-sdd.md +150 -0
  590. package/examples/opencode-skills/openlore-analyze-codebase/SKILL.md +101 -0
  591. package/examples/opencode-skills/openlore-brainstorm/SKILL.md +354 -0
  592. package/examples/opencode-skills/openlore-debug/SKILL.md +291 -0
  593. package/examples/opencode-skills/openlore-execute-refactor/SKILL.md +241 -0
  594. package/examples/opencode-skills/openlore-generate/SKILL.md +236 -0
  595. package/examples/opencode-skills/openlore-implement-story/SKILL.md +251 -0
  596. package/examples/opencode-skills/openlore-plan-refactor/SKILL.md +298 -0
  597. package/examples/opencode-skills/openlore-review-changes/SKILL.md +134 -0
  598. package/examples/opencode-skills/openlore-write-tests/SKILL.md +230 -0
  599. package/examples/openspec-analysis/README.md +59 -0
  600. package/examples/openspec-analysis/SUMMARY.md +72 -0
  601. package/examples/openspec-analysis/config.json +16 -0
  602. package/examples/openspec-analysis/dependencies.mermaid +35 -0
  603. package/examples/openspec-analysis/dependency-graph.json +12116 -0
  604. package/examples/openspec-analysis/llm-context.json +119 -0
  605. package/examples/openspec-analysis/repo-structure.json +871 -0
  606. package/examples/openspec-cli/README.md +67 -0
  607. package/examples/openspec-cli/openspec/config.yaml +26 -0
  608. package/examples/openspec-cli/openspec/specs/architecture/spec.md +178 -0
  609. package/examples/openspec-cli/openspec/specs/artifact-graph/spec.md +143 -0
  610. package/examples/openspec-cli/openspec/specs/cli/spec.md +138 -0
  611. package/examples/openspec-cli/openspec/specs/overview/spec.md +60 -0
  612. package/examples/openspec-cli/openspec/specs/parsing/spec.md +123 -0
  613. package/examples/openspec-cli/openspec/specs/validation/spec.md +108 -0
  614. package/examples/spec-kit/README.md +104 -0
  615. package/examples/spec-kit/commands/drift.md +87 -0
  616. package/examples/spec-kit/commands/orient.md +138 -0
  617. package/examples/spec-kit/extension.yml +54 -0
  618. package/package.json +125 -0
  619. package/src/viewer/InteractiveGraphViewer.jsx +1600 -0
  620. package/src/viewer/app/index.html +17 -0
  621. package/src/viewer/app/main.jsx +13 -0
  622. package/src/viewer/components/ArchitectureView.jsx +177 -0
  623. package/src/viewer/components/ChatPanel.jsx +450 -0
  624. package/src/viewer/components/ClassGraph.jsx +782 -0
  625. package/src/viewer/components/ClusterGraph.jsx +469 -0
  626. package/src/viewer/components/FilterBar.jsx +179 -0
  627. package/src/viewer/components/FlatGraph.jsx +282 -0
  628. package/src/viewer/components/MicroComponents.jsx +85 -0
  629. package/src/viewer/hooks/usePanZoom.js +79 -0
  630. package/src/viewer/utils/constants.js +64 -0
  631. package/src/viewer/utils/graph-helpers.js +303 -0
  632. package/src/viewer/utils/graph-helpers.test.ts +39 -0
  633. package/src/viewer/utils/themes.js +206 -0
  634. package/stubs/tree-sitter-cli-stub/package.json +6 -0
@@ -0,0 +1,1185 @@
1
+ /**
2
+ * MCP tool handlers for codebase analysis:
3
+ * analyze_codebase, get_architecture_overview, get_refactor_report,
4
+ * get_duplicate_report, get_signatures, get_mapping, check_spec_drift,
5
+ * get_function_skeleton, get_god_functions, get_route_inventory,
6
+ * get_middleware_inventory, get_schema_inventory, get_ui_components.
7
+ */
8
+ import { readFile, stat } from 'node:fs/promises';
9
+ import { join, relative } from 'node:path';
10
+ import { spawnSync } from 'node:child_process';
11
+ import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
12
+ import { tmpdir } from 'node:os';
13
+ import { DEFAULT_MAX_FILES, DEFAULT_DRIFT_MAX_FILES, TOP_REFACTOR_ISSUES_LIMIT, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, OPENLORE_ANALYSIS_REL_PATH, OPENSPEC_DIR, ARTIFACT_DEPENDENCY_GRAPH, ARTIFACT_MAPPING, ARTIFACT_REPO_STRUCTURE, ARTIFACT_ROUTE_INVENTORY, ARTIFACT_MIDDLEWARE_INVENTORY, ARTIFACT_SCHEMA_INVENTORY, ARTIFACT_UI_INVENTORY, ARTIFACT_ENV_INVENTORY, ARTIFACT_EXTERNAL_PACKAGES, TRANSITIVE_SCORE_MAX, } from '../../../constants.js';
14
+ import { runAnalysis } from '../../../cli/commands/analyze.js';
15
+ import { analyzeForRefactoring } from '../../analyzer/refactor-analyzer.js';
16
+ import { formatSignatureMaps } from '../../analyzer/signature-extractor.js';
17
+ import { getSkeletonContent, detectLanguage, isSkeletonWorthIncluding } from '../../analyzer/code-shaper.js';
18
+ import { buildArchitectureOverview } from '../../analyzer/architecture-writer.js';
19
+ import { isGitRepository, getChangedFiles, buildSpecMap, buildADRMap, detectDrift, } from '../../drift/index.js';
20
+ import { readOpenLoreConfig } from '../config-manager.js';
21
+ import { validateDirectory, readCachedContext, isCacheFresh, safeJoin } from './utils.js';
22
+ import { openloreAudit } from '../../../api/audit.js';
23
+ // ============================================================================
24
+ // HANDLERS
25
+ // ============================================================================
26
+ /**
27
+ * Run a full static analysis pass on `directory` and return a compact summary.
28
+ */
29
+ export async function handleAnalyzeCodebase(directory, force) {
30
+ const absDir = await validateDirectory(directory);
31
+ const outputPath = join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR);
32
+ if (!force && await isCacheFresh(absDir)) {
33
+ const ctx = await readCachedContext(absDir);
34
+ if (ctx) {
35
+ const cg = ctx.callGraph;
36
+ const topRefactorIssues = cg
37
+ ? analyzeForRefactoring(cg).priorities.slice(0, TOP_REFACTOR_ISSUES_LIMIT).map(e => ({
38
+ function: e.function, file: e.file, issues: e.issues, priorityScore: e.priorityScore,
39
+ }))
40
+ : [];
41
+ return {
42
+ cached: true,
43
+ callGraph: cg
44
+ ? { totalNodes: cg.stats.totalNodes, totalEdges: cg.stats.totalEdges,
45
+ hubs: cg.hubFunctions.length, entryPoints: cg.entryPoints.length,
46
+ layerViolations: cg.layerViolations.length }
47
+ : null,
48
+ topRefactorIssues,
49
+ analysisPath: OPENLORE_ANALYSIS_REL_PATH,
50
+ };
51
+ }
52
+ }
53
+ const result = await runAnalysis(absDir, outputPath, {
54
+ maxFiles: DEFAULT_MAX_FILES,
55
+ include: [],
56
+ exclude: [],
57
+ });
58
+ const { artifacts, repoMap, depGraph } = result;
59
+ const rs = artifacts.repoStructure;
60
+ const cg = artifacts.llmContext.callGraph;
61
+ let topRefactorIssues = [];
62
+ if (cg) {
63
+ const report = analyzeForRefactoring(cg);
64
+ topRefactorIssues = report.priorities.slice(0, TOP_REFACTOR_ISSUES_LIMIT).map(e => ({
65
+ function: e.function,
66
+ file: e.file,
67
+ issues: e.issues,
68
+ priorityScore: e.priorityScore,
69
+ }));
70
+ }
71
+ return {
72
+ projectName: rs.projectName,
73
+ projectType: rs.projectType,
74
+ frameworks: rs.frameworks,
75
+ architecture: rs.architecture.pattern,
76
+ stats: {
77
+ files: repoMap.summary.totalFiles,
78
+ analyzedFiles: repoMap.summary.analyzedFiles,
79
+ depNodes: depGraph.statistics.nodeCount,
80
+ depEdges: depGraph.statistics.edgeCount,
81
+ importEdges: depGraph.statistics.importEdgeCount,
82
+ httpCrossEdges: depGraph.statistics.httpEdgeCount,
83
+ cycles: depGraph.statistics.cycleCount,
84
+ },
85
+ callGraph: cg
86
+ ? {
87
+ totalNodes: cg.stats.totalNodes,
88
+ totalEdges: cg.stats.totalEdges,
89
+ hubs: cg.hubFunctions.length,
90
+ entryPoints: cg.entryPoints.length,
91
+ layerViolations: cg.layerViolations.length,
92
+ }
93
+ : null,
94
+ domains: rs.domains.map((d) => d.name),
95
+ topRefactorIssues,
96
+ analysisPath: OPENLORE_ANALYSIS_REL_PATH,
97
+ };
98
+ }
99
+ /**
100
+ * High-level architecture map: clusters, cross-cluster deps, entry points, hubs.
101
+ */
102
+ export async function handleGetArchitectureOverview(directory) {
103
+ const absDir = await validateDirectory(directory);
104
+ let depGraph = null;
105
+ try {
106
+ const raw = await readFile(join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, ARTIFACT_DEPENDENCY_GRAPH), 'utf-8');
107
+ depGraph = JSON.parse(raw);
108
+ }
109
+ catch { /* ignore */ }
110
+ const ctx = await readCachedContext(absDir);
111
+ if (!depGraph && !ctx) {
112
+ return { error: 'No analysis found. Run analyze_codebase first.' };
113
+ }
114
+ const overview = buildArchitectureOverview(depGraph, ctx, absDir);
115
+ return {
116
+ summary: overview.summary,
117
+ clusters: overview.clusters,
118
+ globalEntryPoints: overview.globalEntryPoints,
119
+ criticalHubs: overview.criticalHubs,
120
+ };
121
+ }
122
+ /**
123
+ * Return a prioritized refactor report from cached analysis.
124
+ */
125
+ export async function handleGetRefactorReport(directory) {
126
+ const absDir = await validateDirectory(directory);
127
+ const ctx = await readCachedContext(absDir);
128
+ if (!ctx)
129
+ return { error: 'No analysis found. Run analyze_codebase first.' };
130
+ if (!ctx.callGraph)
131
+ return { error: 'Call graph not available in cached analysis. Re-run analyze_codebase.' };
132
+ return analyzeForRefactoring(ctx.callGraph);
133
+ }
134
+ /**
135
+ * Read the cached duplicate detection result.
136
+ */
137
+ export async function handleGetDuplicateReport(directory) {
138
+ const absDir = await validateDirectory(directory);
139
+ const cachePath = join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, 'duplicates.json');
140
+ let raw;
141
+ try {
142
+ raw = await readFile(cachePath, 'utf-8');
143
+ }
144
+ catch {
145
+ return {
146
+ error: 'No duplicate report found. Run analyze_codebase first ' +
147
+ '(duplicates.json is generated during analysis).',
148
+ };
149
+ }
150
+ try {
151
+ return JSON.parse(raw);
152
+ }
153
+ catch {
154
+ return { error: 'Duplicate report cache is corrupted. Re-run analyze_codebase.' };
155
+ }
156
+ }
157
+ /**
158
+ * Return compact function and class signatures for files in the project.
159
+ */
160
+ export async function handleGetSignatures(directory, filePattern) {
161
+ const absDir = await validateDirectory(directory);
162
+ const ctx = await readCachedContext(absDir);
163
+ if (!ctx)
164
+ return 'No analysis found. Run analyze_codebase first.';
165
+ if (!ctx.signatures || ctx.signatures.length === 0) {
166
+ return 'No signatures available in cached analysis. Re-run analyze_codebase.';
167
+ }
168
+ const filtered = filePattern
169
+ ? ctx.signatures.filter((s) => s.path.includes(filePattern))
170
+ : ctx.signatures;
171
+ if (filtered.length === 0) {
172
+ return `No files matching pattern "${filePattern}" found in analysis.`;
173
+ }
174
+ const chunks = formatSignatureMaps(filtered);
175
+ return chunks.join('\n\n---\n\n');
176
+ }
177
+ /**
178
+ * Return the requirement→function mapping from mapping.json.
179
+ */
180
+ export async function handleGetMapping(directory, domain, orphansOnly) {
181
+ const absDir = await validateDirectory(directory);
182
+ let raw;
183
+ try {
184
+ raw = await readFile(join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, ARTIFACT_MAPPING), 'utf-8');
185
+ }
186
+ catch {
187
+ return { error: 'No mapping found. Run openlore generate first.' };
188
+ }
189
+ let mapping;
190
+ try {
191
+ mapping = JSON.parse(raw);
192
+ }
193
+ catch {
194
+ return { error: 'Mapping file is corrupted. Re-run openlore generate.' };
195
+ }
196
+ if (orphansOnly) {
197
+ const filtered = domain
198
+ ? mapping.orphanFunctions.filter((f) => f.file.includes(domain))
199
+ : mapping.orphanFunctions;
200
+ return { generatedAt: mapping.generatedAt, stats: mapping.stats, orphanFunctions: filtered };
201
+ }
202
+ const filteredMappings = domain
203
+ ? mapping.mappings.filter((m) => m.domain === domain)
204
+ : mapping.mappings;
205
+ return {
206
+ generatedAt: mapping.generatedAt,
207
+ stats: mapping.stats,
208
+ mappings: filteredMappings,
209
+ orphanFunctions: domain ? [] : mapping.orphanFunctions,
210
+ };
211
+ }
212
+ /**
213
+ * Run spec-drift detection in static mode (no LLM).
214
+ */
215
+ export async function handleCheckSpecDrift(directory, base = 'auto', files = [], domains = [], failOn = 'warning', maxFiles = DEFAULT_DRIFT_MAX_FILES) {
216
+ const absDir = await validateDirectory(directory);
217
+ if (!(await isGitRepository(absDir))) {
218
+ return { error: 'Not a git repository. Drift detection requires git.' };
219
+ }
220
+ const openloreConfig = await readOpenLoreConfig(absDir);
221
+ if (!openloreConfig) {
222
+ return { error: 'No openlore configuration found. Run "openlore init" first.' };
223
+ }
224
+ const openspecPath = join(absDir, openloreConfig.openspecPath ?? OPENSPEC_DIR);
225
+ const specsPath = join(openspecPath, 'specs');
226
+ try {
227
+ await stat(specsPath);
228
+ }
229
+ catch {
230
+ return { error: 'No specs found. Run "openlore generate" first.' };
231
+ }
232
+ const startTime = Date.now();
233
+ const gitResult = await getChangedFiles({
234
+ rootPath: absDir,
235
+ baseRef: base,
236
+ pathFilter: files.length > 0 ? files : undefined,
237
+ includeUnstaged: true,
238
+ });
239
+ if (gitResult.files.length === 0) {
240
+ return {
241
+ timestamp: new Date().toISOString(),
242
+ baseRef: gitResult.resolvedBase,
243
+ totalChangedFiles: 0,
244
+ specRelevantFiles: 0,
245
+ issues: [],
246
+ summary: { gaps: 0, stale: 0, uncovered: 0, orphanedSpecs: 0, adrGaps: 0, adrOrphaned: 0, total: 0 },
247
+ hasDrift: false,
248
+ duration: Date.now() - startTime,
249
+ mode: 'static',
250
+ };
251
+ }
252
+ const actualChangedFiles = gitResult.files.length;
253
+ if (gitResult.files.length > maxFiles) {
254
+ gitResult.files = gitResult.files.slice(0, maxFiles);
255
+ }
256
+ const repoStructurePath = join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, ARTIFACT_REPO_STRUCTURE);
257
+ let hasRepoStructure = false;
258
+ try {
259
+ await stat(repoStructurePath);
260
+ hasRepoStructure = true;
261
+ }
262
+ catch { /* no prior analysis */ }
263
+ const specMap = await buildSpecMap({
264
+ rootPath: absDir,
265
+ openspecPath,
266
+ repoStructurePath: hasRepoStructure ? repoStructurePath : undefined,
267
+ });
268
+ const adrMap = await buildADRMap({
269
+ rootPath: absDir,
270
+ openspecPath,
271
+ repoStructurePath: hasRepoStructure ? repoStructurePath : undefined,
272
+ });
273
+ const result = await detectDrift({
274
+ rootPath: absDir,
275
+ specMap,
276
+ changedFiles: gitResult.files,
277
+ failOn,
278
+ domainFilter: domains.length > 0 ? domains : undefined,
279
+ openspecRelPath: openloreConfig.openspecPath ?? OPENSPEC_DIR,
280
+ baseRef: gitResult.resolvedBase,
281
+ adrMap: adrMap ?? undefined,
282
+ });
283
+ result.baseRef = gitResult.resolvedBase;
284
+ result.totalChangedFiles = actualChangedFiles;
285
+ return result;
286
+ }
287
+ /**
288
+ * Return a noise-stripped skeleton of a source file.
289
+ */
290
+ export async function handleGetFunctionSkeleton(directory, filePath) {
291
+ const absDir = await validateDirectory(directory);
292
+ const absFile = safeJoin(absDir, filePath);
293
+ let source;
294
+ try {
295
+ source = await readFile(absFile, 'utf-8');
296
+ }
297
+ catch {
298
+ return { error: `File not found: ${filePath}` };
299
+ }
300
+ const language = detectLanguage(filePath);
301
+ const skeleton = getSkeletonContent(source, language);
302
+ const worthIncluding = isSkeletonWorthIncluding(source, skeleton);
303
+ return {
304
+ filePath,
305
+ language,
306
+ originalLines: source.split('\n').length,
307
+ skeletonLines: skeleton.split('\n').length,
308
+ reductionPct: Math.round((1 - skeleton.length / source.length) * 100),
309
+ worthIncluding,
310
+ skeleton,
311
+ };
312
+ }
313
+ // Note: handleGetGodFunctions lives in graph.ts (alongside other call-graph tools)
314
+ /**
315
+ * Extract the exact source text of a named function using the cached call graph.
316
+ *
317
+ * Uses the startIndex/endIndex byte offsets recorded in llm-context.json to
318
+ * slice the source file — no extra tree-sitter parsing needed at query time.
319
+ * Falls back to a line-number scan when the call graph is unavailable.
320
+ */
321
+ export async function handleGetFunctionBody(directory, filePath, functionName) {
322
+ const absDir = await validateDirectory(directory);
323
+ const absFile = safeJoin(absDir, filePath);
324
+ let source;
325
+ try {
326
+ source = await readFile(absFile, 'utf-8');
327
+ }
328
+ catch {
329
+ return { error: `File not found: ${filePath}` };
330
+ }
331
+ // Try call graph first: exact byte-range slice, no ambiguity
332
+ const contextPath = join(absDir, '.openlore', 'analysis', 'llm-context.json');
333
+ try {
334
+ const raw = await readFile(contextPath, 'utf-8');
335
+ const ctx = JSON.parse(raw);
336
+ if (ctx.callGraph?.nodes) {
337
+ const node = ctx.callGraph.nodes.find(n => n.name === functionName && (n.filePath === filePath || n.filePath.endsWith('/' + filePath.replace(/^\//, ''))));
338
+ if (node && node.startIndex < node.endIndex) {
339
+ const body = source.slice(node.startIndex, node.endIndex);
340
+ return {
341
+ functionName,
342
+ filePath,
343
+ language: node.language,
344
+ className: node.className ?? null,
345
+ startIndex: node.startIndex,
346
+ endIndex: node.endIndex,
347
+ body,
348
+ lineCount: body.split('\n').length,
349
+ };
350
+ }
351
+ }
352
+ }
353
+ catch { /* no call graph — fall through to line-scan */ }
354
+ // Fallback: find the function by scanning for its declaration line
355
+ const lines = source.split('\n');
356
+ const declPattern = new RegExp(`\\b${functionName}\\s*[(<]`);
357
+ const startLine = lines.findIndex(l => declPattern.test(l));
358
+ if (startLine === -1) {
359
+ return { error: `Function "${functionName}" not found in ${filePath}. Run analyze_codebase first for exact byte-range extraction.` };
360
+ }
361
+ // Collect lines until matching brace depth returns to 0 (works for C-style languages)
362
+ let depth = 0;
363
+ let endLine = startLine;
364
+ for (let i = startLine; i < lines.length; i++) {
365
+ for (const ch of lines[i]) {
366
+ if (ch === '{')
367
+ depth++;
368
+ else if (ch === '}')
369
+ depth--;
370
+ }
371
+ if (depth > 0 || i === startLine) {
372
+ endLine = i;
373
+ continue;
374
+ }
375
+ endLine = i;
376
+ break;
377
+ }
378
+ const body = lines.slice(startLine, endLine + 1).join('\n');
379
+ return {
380
+ functionName,
381
+ filePath,
382
+ language: detectLanguage(filePath),
383
+ className: null,
384
+ startLine: startLine + 1,
385
+ endLine: endLine + 1,
386
+ body,
387
+ lineCount: endLine - startLine + 1,
388
+ note: 'Extracted via line scan (no call graph available). Run analyze_codebase for exact extraction.',
389
+ };
390
+ }
391
+ /**
392
+ * List and optionally filter Architecture Decision Records from openspec/decisions/.
393
+ */
394
+ export async function handleGetDecisions(directory, query) {
395
+ const { existsSync } = await import('node:fs');
396
+ const { readdir } = await import('node:fs/promises');
397
+ const { join: pjoin } = await import('node:path');
398
+ const absDir = await validateDirectory(directory);
399
+ // Resolve openspec path from config if present
400
+ let openspecRelPath = 'openspec';
401
+ try {
402
+ const cfgRaw = await readFile(join(absDir, '.openlore', 'config.json'), 'utf-8');
403
+ const cfg = JSON.parse(cfgRaw);
404
+ if (cfg.openspecPath)
405
+ openspecRelPath = cfg.openspecPath;
406
+ }
407
+ catch { /* use default */ }
408
+ const decisionsDir = pjoin(absDir, openspecRelPath, 'decisions');
409
+ if (!existsSync(decisionsDir)) {
410
+ return { decisions: [], note: `No decisions directory found at ${openspecRelPath}/decisions/. Run "openlore generate --adrs" first.` };
411
+ }
412
+ let entries;
413
+ try {
414
+ entries = await readdir(decisionsDir);
415
+ }
416
+ catch {
417
+ return { decisions: [] };
418
+ }
419
+ // Each ADR is a .md file directly in decisions/
420
+ const adrFiles = entries.filter(e => e.endsWith('.md'));
421
+ // Read all ADR files in parallel
422
+ const adrs = await Promise.all(adrFiles.map(async (filename) => {
423
+ const filePath = pjoin(decisionsDir, filename);
424
+ const content = await readFile(filePath, 'utf-8');
425
+ // Extract title from first H1 or H2
426
+ const titleMatch = content.match(/^#{1,2}\s+(.+)$/m);
427
+ const title = titleMatch?.[1]?.trim() ?? filename.replace(/\.md$/, '');
428
+ // Extract status from "Status:" line (ADR convention)
429
+ const statusMatch = content.match(/^\*\*?Status\*\*?:?\s*(.+)$/im);
430
+ const status = statusMatch?.[1]?.trim() ?? 'unknown';
431
+ return { filename, title, status, content };
432
+ }));
433
+ // Filter by query text if provided
434
+ const lowerQuery = query?.toLowerCase();
435
+ const filtered = lowerQuery
436
+ ? adrs.filter(a => a.title.toLowerCase().includes(lowerQuery) ||
437
+ a.content.toLowerCase().includes(lowerQuery))
438
+ : adrs;
439
+ return {
440
+ count: filtered.length,
441
+ query: query ?? null,
442
+ decisions: filtered.map(a => ({
443
+ filename: a.filename,
444
+ title: a.title,
445
+ status: a.status,
446
+ content: a.content,
447
+ })),
448
+ };
449
+ }
450
+ // ============================================================================
451
+ // ROUTE INVENTORY HANDLER
452
+ // ============================================================================
453
+ /**
454
+ * Return the pre-computed route inventory from the last analysis run.
455
+ * Falls back to re-computing from source files if the artifact is missing.
456
+ */
457
+ export async function handleGetRouteInventory(directory) {
458
+ const absDir = await validateDirectory(directory);
459
+ const artifactPath = join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, ARTIFACT_ROUTE_INVENTORY);
460
+ // Try reading cached artifact first
461
+ try {
462
+ const raw = await readFile(artifactPath, 'utf-8');
463
+ const inventory = JSON.parse(raw);
464
+ return { cached: true, ...inventory };
465
+ }
466
+ catch {
467
+ // Artifact not present — run live extraction
468
+ }
469
+ const { buildRouteInventory } = await import('../../analyzer/http-route-parser.js');
470
+ const { RepositoryMapper } = await import('../../analyzer/repository-mapper.js');
471
+ const { readOpenLoreConfig } = await import('../config-manager.js');
472
+ const openloreConfig = await readOpenLoreConfig(absDir);
473
+ const configExclude = openloreConfig?.analysis.excludePatterns ?? [];
474
+ const mapper = new RepositoryMapper(absDir, {
475
+ maxFiles: DEFAULT_MAX_FILES,
476
+ excludePatterns: configExclude.length > 0 ? configExclude : undefined,
477
+ });
478
+ const repoMap = await mapper.map();
479
+ const filePaths = repoMap.allFiles.map(f => f.path);
480
+ const inventory = await buildRouteInventory(filePaths, absDir);
481
+ return { cached: false, ...inventory };
482
+ }
483
+ // ============================================================================
484
+ // MIDDLEWARE INVENTORY HANDLER
485
+ // ============================================================================
486
+ /**
487
+ * Return the pre-computed middleware inventory from the last analysis run.
488
+ * Falls back to re-computing from source files if the artifact is missing.
489
+ */
490
+ export async function handleGetMiddlewareInventory(directory) {
491
+ const absDir = await validateDirectory(directory);
492
+ const artifactPath = join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, ARTIFACT_MIDDLEWARE_INVENTORY);
493
+ // Try reading cached artifact first
494
+ try {
495
+ const raw = await readFile(artifactPath, 'utf-8');
496
+ const inventory = JSON.parse(raw);
497
+ return { cached: true, total: inventory.length, entries: inventory };
498
+ }
499
+ catch {
500
+ // Artifact not present — run live extraction
501
+ }
502
+ const { extractMiddleware } = await import('../../analyzer/middleware-extractor.js');
503
+ const { RepositoryMapper } = await import('../../analyzer/repository-mapper.js');
504
+ const { readOpenLoreConfig } = await import('../config-manager.js');
505
+ const openloreConfig = await readOpenLoreConfig(absDir);
506
+ const configExclude = openloreConfig?.analysis.excludePatterns ?? [];
507
+ const mapper = new RepositoryMapper(absDir, {
508
+ maxFiles: DEFAULT_MAX_FILES,
509
+ excludePatterns: configExclude.length > 0 ? configExclude : undefined,
510
+ });
511
+ const repoMap = await mapper.map();
512
+ const filePaths = repoMap.allFiles.map(f => f.path);
513
+ const entries = await extractMiddleware(filePaths, absDir);
514
+ return { cached: false, total: entries.length, entries };
515
+ }
516
+ // ============================================================================
517
+ // SCHEMA INVENTORY HANDLER
518
+ // ============================================================================
519
+ /**
520
+ * Return the pre-computed database schema inventory from the last analysis run.
521
+ * Falls back to re-computing from source files if the artifact is missing.
522
+ */
523
+ export async function handleGetSchemaInventory(directory) {
524
+ const absDir = await validateDirectory(directory);
525
+ const artifactPath = join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, ARTIFACT_SCHEMA_INVENTORY);
526
+ try {
527
+ const raw = await readFile(artifactPath, 'utf-8');
528
+ const schemas = JSON.parse(raw);
529
+ return { cached: true, total: schemas.length, schemas };
530
+ }
531
+ catch {
532
+ // Artifact not present — run live extraction
533
+ }
534
+ const { extractSchemas } = await import('../../analyzer/schema-extractor.js');
535
+ const { RepositoryMapper } = await import('../../analyzer/repository-mapper.js');
536
+ const { readOpenLoreConfig } = await import('../config-manager.js');
537
+ const openloreConfig = await readOpenLoreConfig(absDir);
538
+ const configExclude = openloreConfig?.analysis.excludePatterns ?? [];
539
+ const mapper = new RepositoryMapper(absDir, {
540
+ maxFiles: DEFAULT_MAX_FILES,
541
+ excludePatterns: configExclude.length > 0 ? configExclude : undefined,
542
+ });
543
+ const repoMap = await mapper.map();
544
+ const filePaths = repoMap.allFiles.map(f => f.path);
545
+ const schemas = await extractSchemas(filePaths, absDir);
546
+ return { cached: false, total: schemas.length, schemas };
547
+ }
548
+ // ============================================================================
549
+ // UI COMPONENTS HANDLER
550
+ // ============================================================================
551
+ /**
552
+ * Return the pre-computed UI component inventory from the last analysis run.
553
+ * Falls back to re-computing from source files if the artifact is missing.
554
+ */
555
+ export async function handleGetUIComponents(directory) {
556
+ const absDir = await validateDirectory(directory);
557
+ const artifactPath = join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, ARTIFACT_UI_INVENTORY);
558
+ try {
559
+ const raw = await readFile(artifactPath, 'utf-8');
560
+ const components = JSON.parse(raw);
561
+ return { cached: true, total: components.length, components };
562
+ }
563
+ catch {
564
+ // Artifact not present — run live extraction
565
+ }
566
+ const { extractUIComponents } = await import('../../analyzer/ui-component-extractor.js');
567
+ const { RepositoryMapper } = await import('../../analyzer/repository-mapper.js');
568
+ const { readOpenLoreConfig } = await import('../config-manager.js');
569
+ const openloreConfig = await readOpenLoreConfig(absDir);
570
+ const configExclude = openloreConfig?.analysis.excludePatterns ?? [];
571
+ const mapper = new RepositoryMapper(absDir, {
572
+ maxFiles: DEFAULT_MAX_FILES,
573
+ excludePatterns: configExclude.length > 0 ? configExclude : undefined,
574
+ });
575
+ const repoMap = await mapper.map();
576
+ const filePaths = repoMap.allFiles.map(f => f.path);
577
+ const components = await extractUIComponents(filePaths, absDir);
578
+ return { cached: false, total: components.length, components };
579
+ }
580
+ // ============================================================================
581
+ // ENV VARS HANDLER
582
+ // ============================================================================
583
+ /**
584
+ * Return the pre-computed environment variable inventory from the last analysis run.
585
+ * Falls back to re-computing from source files if the artifact is missing.
586
+ */
587
+ export async function handleGetEnvVars(directory) {
588
+ const absDir = await validateDirectory(directory);
589
+ const artifactPath = join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, ARTIFACT_ENV_INVENTORY);
590
+ try {
591
+ const raw = await readFile(artifactPath, 'utf-8');
592
+ const envVars = JSON.parse(raw);
593
+ return { cached: true, total: envVars.length, envVars };
594
+ }
595
+ catch {
596
+ // Artifact not present — run live extraction
597
+ }
598
+ const { extractEnvVars } = await import('../../analyzer/env-extractor.js');
599
+ const { RepositoryMapper } = await import('../../analyzer/repository-mapper.js');
600
+ const { readOpenLoreConfig } = await import('../config-manager.js');
601
+ const openloreConfig = await readOpenLoreConfig(absDir);
602
+ const configExclude = openloreConfig?.analysis.excludePatterns ?? [];
603
+ const mapper = new RepositoryMapper(absDir, {
604
+ maxFiles: DEFAULT_MAX_FILES,
605
+ excludePatterns: configExclude.length > 0 ? configExclude : undefined,
606
+ });
607
+ const repoMap = await mapper.map();
608
+ const filePaths = repoMap.allFiles.map(f => f.path);
609
+ const envVars = await extractEnvVars(filePaths, absDir);
610
+ return { cached: false, total: envVars.length, envVars };
611
+ }
612
+ // ============================================================================
613
+ // EXTERNAL PACKAGES HANDLER
614
+ // ============================================================================
615
+ /**
616
+ * Return direct external dependencies from package manifests
617
+ * (package.json, pyproject.toml, requirements.txt, Cargo.toml, go.mod).
618
+ * Falls back to live extraction if cached artifact is absent.
619
+ */
620
+ export async function handleGetExternalPackages(directory) {
621
+ const absDir = await validateDirectory(directory);
622
+ const artifactPath = join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, ARTIFACT_EXTERNAL_PACKAGES);
623
+ try {
624
+ const raw = await readFile(artifactPath, 'utf-8');
625
+ const result = JSON.parse(raw);
626
+ return { cached: true, ...result };
627
+ }
628
+ catch { /* not cached */ }
629
+ const { extractExternalPackages } = await import('../../analyzer/external-packages.js');
630
+ const result = await extractExternalPackages(absDir);
631
+ return { cached: false, ...result };
632
+ }
633
+ /**
634
+ * Parity audit: report spec coverage gaps without any LLM call.
635
+ * Returns uncovered functions, hub gaps, orphan requirements, and stale domains.
636
+ */
637
+ export async function handleAuditSpecCoverage(directory, maxUncovered = 50, hubThreshold = 5) {
638
+ const absDir = await validateDirectory(directory);
639
+ try {
640
+ const report = await openloreAudit({
641
+ rootPath: absDir,
642
+ maxUncovered,
643
+ hubThreshold,
644
+ save: true,
645
+ });
646
+ return report;
647
+ }
648
+ catch (err) {
649
+ return { error: `Audit failed: ${err instanceof Error ? err.message : String(err)}` };
650
+ }
651
+ }
652
+ // ============================================================================
653
+ // TEST GENERATION HANDLERS
654
+ // ============================================================================
655
+ /**
656
+ * Generate spec-driven test files from OpenSpec scenarios.
657
+ */
658
+ export async function handleGenerateTests(args) {
659
+ const absDir = await validateDirectory(args.directory);
660
+ const { parseScenarios, generateTests, writeTestFiles, detectFramework } = await import('../../../core/test-generator/index.js');
661
+ const { FRAMEWORK_EXTENSIONS } = await import('../../../types/test-generator.js');
662
+ const scenarios = await parseScenarios({
663
+ rootPath: absDir,
664
+ domains: args.domains,
665
+ });
666
+ if (scenarios.length === 0) {
667
+ return { files: [], message: 'No scenarios found. Run "openlore generate" first.' };
668
+ }
669
+ // Resolve framework
670
+ let framework;
671
+ const valid = Object.keys(FRAMEWORK_EXTENSIONS);
672
+ if (!args.framework || args.framework === 'auto') {
673
+ framework = await detectFramework(absDir);
674
+ }
675
+ else if (valid.includes(args.framework)) {
676
+ framework = args.framework;
677
+ }
678
+ else {
679
+ return { error: `Unknown framework "${args.framework}". Valid: ${valid.join(', ')}` };
680
+ }
681
+ const files = await generateTests({
682
+ scenarios,
683
+ framework,
684
+ outputDir: 'spec-tests',
685
+ rootPath: absDir,
686
+ useLlm: args.useLlm ?? false,
687
+ });
688
+ const dryRun = args.dryRun ?? true; // MCP defaults to dry-run for safety
689
+ const writeResult = await writeTestFiles({
690
+ files,
691
+ rootPath: absDir,
692
+ dryRun,
693
+ merge: false,
694
+ });
695
+ return {
696
+ framework,
697
+ dryRun,
698
+ files: files.map((f) => ({
699
+ path: f.outputPath,
700
+ domain: f.domain,
701
+ scenarioCount: f.scenarios.length,
702
+ content: f.content,
703
+ })),
704
+ summary: writeResult,
705
+ };
706
+ }
707
+ /**
708
+ * Report spec test coverage for a project.
709
+ */
710
+ export async function handleGetTestCoverage(args) {
711
+ const absDir = await validateDirectory(args.directory);
712
+ const { analyzeTestCoverage } = await import('../../../core/test-generator/index.js');
713
+ const report = await analyzeTestCoverage({
714
+ rootPath: absDir,
715
+ testDirs: ['spec-tests', 'src'],
716
+ domains: args.domains,
717
+ minCoverage: args.minCoverage,
718
+ // discover without LLM: tag-based only
719
+ discover: false,
720
+ });
721
+ return report;
722
+ }
723
+ // ============================================================================
724
+ // get_minimal_context
725
+ // ============================================================================
726
+ /**
727
+ * Return the bare minimum an agent needs to safely modify a function:
728
+ * its signature + body, direct callers (signatures), direct callees (signatures),
729
+ * and which test files cover it. Typically 200-600 tokens vs orient's 2000+.
730
+ */
731
+ export async function handleGetMinimalContext(directory, functionName, filePath) {
732
+ const absDir = await validateDirectory(directory);
733
+ const ctx = await readCachedContext(absDir);
734
+ if (!ctx?.callGraph)
735
+ return { error: 'No call graph. Run analyze_codebase first.' };
736
+ const cg = ctx.callGraph;
737
+ const nodeMap = new Map(cg.nodes.map(n => [n.id, n]));
738
+ // Find target node(s)
739
+ const candidates = cg.nodes.filter(n => n.name === functionName &&
740
+ !n.isExternal && !n.isTest &&
741
+ (!filePath || n.filePath.endsWith(filePath)));
742
+ if (candidates.length === 0)
743
+ return { error: `Function "${functionName}" not found. Run analyze_codebase first.` };
744
+ const target = candidates[0];
745
+ // Risk-aware depth: high fan-in/out functions get more caller/callee context
746
+ const riskLevel = target.fanIn >= 30 || target.fanOut >= 15 ? 'high' :
747
+ target.fanIn >= 15 || target.fanOut >= 8 ? 'medium' : 'low';
748
+ const callerLimit = riskLevel === 'high' ? 24 : riskLevel === 'medium' ? 18 : 12;
749
+ const calleeLimit = riskLevel === 'high' ? 24 : riskLevel === 'medium' ? 18 : 12;
750
+ // Direct callers and callees from calls edges
751
+ const callsEdges = cg.edges.filter(e => !e.kind || e.kind === 'calls');
752
+ const sig = (n) => n.signature ?? n.name + (n.isExternal ? ' [external]' : '');
753
+ // Build per-callee edge list to get callType
754
+ const edgesForCallee = new Map();
755
+ const edgesForCaller = new Map();
756
+ for (const e of callsEdges) {
757
+ if (!edgesForCallee.has(e.calleeId))
758
+ edgesForCallee.set(e.calleeId, []);
759
+ edgesForCallee.get(e.calleeId).push({ callerId: e.callerId, callType: e.callType });
760
+ if (!edgesForCaller.has(e.callerId))
761
+ edgesForCaller.set(e.callerId, []);
762
+ edgesForCaller.get(e.callerId).push({ calleeId: e.calleeId, callType: e.callType });
763
+ }
764
+ const callerEdges = edgesForCallee.get(target.id) ?? [];
765
+ const calleeEdgeList = edgesForCaller.get(target.id) ?? [];
766
+ const callerIds = callerEdges.map(e => e.callerId);
767
+ const calleeIds = calleeEdgeList.map(e => e.calleeId);
768
+ const callers = [...new Set(callerIds)]
769
+ .map(id => {
770
+ const n = nodeMap.get(id);
771
+ if (!n || n.isExternal)
772
+ return null;
773
+ const edge = callerEdges.find(e => e.callerId === id);
774
+ return { name: n.name, file: relative(absDir, n.filePath), sig: sig(n), callType: edge?.callType ?? 'direct', isExternal: false };
775
+ })
776
+ .filter((n) => !!n)
777
+ .slice(0, callerLimit);
778
+ const callees = [...new Set(calleeIds)]
779
+ .map(id => {
780
+ const n = nodeMap.get(id);
781
+ if (!n)
782
+ return null;
783
+ const edge = calleeEdgeList.find(e => e.calleeId === id);
784
+ return {
785
+ name: n.isExternal ? `[external] ${n.name}` : n.name,
786
+ file: n.isExternal ? 'external' : relative(absDir, n.filePath),
787
+ sig: sig(n),
788
+ callType: edge?.callType ?? 'direct',
789
+ isExternal: n.isExternal ?? false,
790
+ kind: n.externalKind,
791
+ };
792
+ })
793
+ .filter((n) => !!n)
794
+ .slice(0, calleeLimit);
795
+ // Test coverage — distinguish import-based vs call-based tracing
796
+ const seenTestNames = new Set();
797
+ const testedBy = cg.edges
798
+ .filter(e => e.kind === 'tested_by' && e.callerId === target.id)
799
+ .flatMap(e => {
800
+ if (seenTestNames.has(e.calleeName))
801
+ return [];
802
+ seenTestNames.add(e.calleeName);
803
+ const confidence = e.confidence === 'import' ? 'imported' : 'called';
804
+ return [{ name: e.calleeName, confidence }];
805
+ });
806
+ // Function body (byte-range slice from source)
807
+ let body = null;
808
+ try {
809
+ const src = await readFile(target.filePath, 'utf-8');
810
+ body = src.slice(target.startIndex, target.endIndex);
811
+ }
812
+ catch { /* source unavailable */ }
813
+ return {
814
+ function: {
815
+ name: target.name,
816
+ file: relative(absDir, target.filePath),
817
+ signature: target.signature ?? target.name,
818
+ language: target.language,
819
+ className: target.className ?? null,
820
+ startLine: target.startLine ?? null,
821
+ fanIn: target.fanIn,
822
+ fanOut: target.fanOut,
823
+ community: target.communityLabel ?? null,
824
+ riskLevel,
825
+ body,
826
+ },
827
+ callers,
828
+ callees,
829
+ testedBy,
830
+ };
831
+ }
832
+ // ============================================================================
833
+ // get_cluster
834
+ // ============================================================================
835
+ /**
836
+ * Return all functions in the same community as the given function.
837
+ * Communities are computed via label propagation on the call graph at analyze time.
838
+ * Useful for understanding the "cluster" of related functions without traversing the graph.
839
+ */
840
+ export async function handleGetCluster(directory, functionName) {
841
+ const absDir = await validateDirectory(directory);
842
+ const ctx = await readCachedContext(absDir);
843
+ if (!ctx?.callGraph)
844
+ return { error: 'No call graph. Run analyze_codebase first.' };
845
+ const cg = ctx.callGraph;
846
+ // Find the target node
847
+ const target = cg.nodes.find(n => n.name === functionName && !n.isExternal && !n.isTest);
848
+ if (!target)
849
+ return { error: `Function "${functionName}" not found.` };
850
+ if (!target.communityId)
851
+ return { error: `No community data. Re-run analyze_codebase.` };
852
+ // All nodes in same community
853
+ const members = cg.nodes
854
+ .filter(n => n.communityId === target.communityId && !n.isExternal && !n.isTest)
855
+ .sort((a, b) => b.fanIn - a.fanIn);
856
+ // Internal edges within community
857
+ const memberIds = new Set(members.map(n => n.id));
858
+ const nameById = new Map(members.map(n => [n.id, n.name]));
859
+ const rawInternal = cg.edges
860
+ .filter(e => (!e.kind || e.kind === 'calls') && memberIds.has(e.callerId) && memberIds.has(e.calleeId));
861
+ // Deduplicate, count all unique internal edges for density, show top 15
862
+ const seen = new Set();
863
+ const callEdges = [];
864
+ let uniqueInternalCount = 0;
865
+ for (const e of rawInternal) {
866
+ const key = `${e.callerId}→${e.calleeId}`;
867
+ if (seen.has(key))
868
+ continue;
869
+ seen.add(key);
870
+ uniqueInternalCount++;
871
+ if (callEdges.length < 15) {
872
+ callEdges.push(`${nameById.get(e.callerId)} → ${nameById.get(e.calleeId)}`);
873
+ }
874
+ }
875
+ const m = members.length;
876
+ const clusterDensity = m > 1 ? Math.round((uniqueInternalCount / (m * (m - 1))) * 1000) / 1000 : 0;
877
+ // Files the community spans
878
+ const files = [...new Set(members.map(n => relative(absDir, n.filePath)))].sort();
879
+ return {
880
+ communityLabel: target.communityLabel,
881
+ communityId: target.communityId,
882
+ stats: {
883
+ members: m,
884
+ files: files.length,
885
+ internalEdges: uniqueInternalCount,
886
+ clusterDensity,
887
+ },
888
+ files,
889
+ // Internal call edges show WHY these functions cluster together
890
+ internalCallGraph: callEdges,
891
+ functions: members.map(n => ({
892
+ name: n.name,
893
+ file: relative(absDir, n.filePath),
894
+ signature: n.signature ?? n.name,
895
+ fanIn: n.fanIn,
896
+ fanOut: n.fanOut,
897
+ })),
898
+ };
899
+ }
900
+ // ============================================================================
901
+ // detect_changes
902
+ // ============================================================================
903
+ /** Run git with explicit stdio pipes — safe inside MCP server (which owns stdin/stdout). */
904
+ function runGit(args, cwd) {
905
+ // Use shell file-redirect to temp files instead of pipes — libuv pipe() calls
906
+ // fail with EBADF inside the MCP server because its FD 0/1 are the JSON-RPC
907
+ // protocol sockets; avoiding pipe creation altogether sidesteps the issue.
908
+ return new Promise((resolve, reject) => {
909
+ const PATH = (process.env.PATH ?? '') + ':/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin';
910
+ const tmp = mkdtempSync(join(tmpdir(), 'sg-git-'));
911
+ const outPath = join(tmp, 'o');
912
+ const errPath = join(tmp, 'e');
913
+ const escaped = args.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ');
914
+ const cmd = `/usr/bin/git ${escaped} >'${outPath}' 2>'${errPath}'`;
915
+ const r = spawnSync('/bin/sh', ['-c', cmd], {
916
+ cwd,
917
+ stdio: 'ignore',
918
+ env: { ...process.env, PATH },
919
+ });
920
+ let stdout = '';
921
+ let stderr = '';
922
+ try {
923
+ stdout = readFileSync(outPath, 'utf8');
924
+ }
925
+ catch { /* no output */ }
926
+ try {
927
+ stderr = readFileSync(errPath, 'utf8');
928
+ }
929
+ catch { /* no output */ }
930
+ rmSync(tmp, { recursive: true, force: true });
931
+ if (r.error) {
932
+ reject(r.error);
933
+ return;
934
+ }
935
+ if ((r.status ?? 1) !== 0) {
936
+ reject(new Error(stderr || `git exit ${r.status}`));
937
+ return;
938
+ }
939
+ resolve(stdout);
940
+ });
941
+ }
942
+ // ── change-type classifier ────────────────────────────────────────────────────
943
+ // Signature line patterns across languages (TS/JS, Python, Go, Rust, Java/C++)
944
+ const SIG_PATTERN = /^\s*(export\s+)?(default\s+)?(async\s+)?function\b|\bdef\s+\w+\s*[([:]|\bfunc\s+(\([^)]*\)\s*)?\w+\s*\(|\bfn\s+\w+\b|\bclass\s+\w+/;
945
+ // Control-flow keywords (broad, multi-language)
946
+ const LOGIC_PATTERN = /\b(if|else|for|while|switch|try|catch|throw|return|yield|await|break|continue|elif|except|raise|match|case)\b/;
947
+ function classifyChangeType(node, addedLines) {
948
+ const fnStart = node.startLine ?? 1;
949
+ const fnEnd = node.endLine ?? fnStart;
950
+ const linesInFn = [];
951
+ for (const [ln, content] of addedLines) {
952
+ if (ln >= fnStart && ln <= fnEnd)
953
+ linesInFn.push(content);
954
+ }
955
+ if (linesInFn.length === 0)
956
+ return 'logic';
957
+ // Signature change: function's own declaration line was modified
958
+ const sigLine = addedLines.get(fnStart);
959
+ if (sigLine !== undefined && SIG_PATTERN.test(sigLine))
960
+ return 'signature';
961
+ // Logic change: any added line has a control-flow keyword
962
+ if (linesInFn.some(l => LOGIC_PATTERN.test(l)))
963
+ return 'logic';
964
+ return 'config';
965
+ }
966
+ const CHANGE_TYPE_MULTIPLIER = {
967
+ signature: 1.5, // breaking-change candidate
968
+ logic: 1.0, // default
969
+ config: 0.4, // literal / comment / config tweak
970
+ };
971
+ function buildReason(params) {
972
+ const { changeType, directCallers, tests, bScore, fanIn } = params;
973
+ const parts = [];
974
+ if (changeType === 'signature')
975
+ parts.push('signature change');
976
+ else if (changeType === 'config')
977
+ parts.push('config/literal change');
978
+ if (fanIn > 0) {
979
+ const awaitedCount = directCallers.filter(c => c.callType === 'awaited').length;
980
+ const total = directCallers.length || fanIn;
981
+ if (awaitedCount === total && awaitedCount > 0)
982
+ parts.push('all callers awaited');
983
+ else if (awaitedCount > 0)
984
+ parts.push(`${awaitedCount} awaited callers`);
985
+ else
986
+ parts.push(`${fanIn} callers`);
987
+ }
988
+ const calledTests = tests.filter(t => t.confidence === 'called').length;
989
+ if (tests.length === 0)
990
+ parts.push('no tests');
991
+ else if (calledTests === 0)
992
+ parts.push('import-only tests');
993
+ else
994
+ parts.push(`${calledTests} direct test${calledTests > 1 ? 's' : ''}`);
995
+ if (bScore >= 0.67)
996
+ parts.push('HTTP/DB boundary');
997
+ else if (bScore > 0)
998
+ parts.push('external boundary');
999
+ return parts.join(' · ') || 'low-risk change';
1000
+ }
1001
+ // ─────────────────────────────────────────────────────────────────────────────
1002
+ /**
1003
+ * Detect recently changed functions and rank them by blast radius (fanIn of callers via BFS).
1004
+ * Runs git diff to find changed files/lines, maps to function nodes, scores by impact.
1005
+ */
1006
+ export async function handleDetectChanges(directory, base) {
1007
+ const absDir = await validateDirectory(directory);
1008
+ const ctx = await readCachedContext(absDir);
1009
+ if (!ctx?.callGraph)
1010
+ return { error: 'No call graph. Run analyze_codebase first.' };
1011
+ const ref = base ?? 'HEAD';
1012
+ let diffOutput;
1013
+ try {
1014
+ diffOutput = await runGit(['diff', '--unified=0', ref, '--', '.'], absDir);
1015
+ if (!diffOutput.trim()) {
1016
+ diffOutput = await runGit(['diff', '--unified=0', '--cached', '--', '.'], absDir);
1017
+ }
1018
+ }
1019
+ catch (err) {
1020
+ return { error: `git diff failed: ${err.message}` };
1021
+ }
1022
+ if (!diffOutput.trim())
1023
+ return { changedFunctions: [], message: 'No changes detected.' };
1024
+ // Parse unified diff: collect line ranges AND added-line content per file.
1025
+ // --unified=0 means no context lines; only '+' and '-' lines appear in hunks.
1026
+ const changedFileData = new Map();
1027
+ let curFile = null;
1028
+ let newLineNum = 0;
1029
+ for (const line of diffOutput.split('\n')) {
1030
+ const fileMatch = line.match(/^\+\+\+ b\/(.+)$/);
1031
+ if (fileMatch) {
1032
+ curFile = join(absDir, fileMatch[1]);
1033
+ newLineNum = 0;
1034
+ continue;
1035
+ }
1036
+ const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
1037
+ if (hunkMatch && curFile) {
1038
+ newLineNum = parseInt(hunkMatch[1], 10);
1039
+ const count = hunkMatch[2] !== undefined ? parseInt(hunkMatch[2], 10) : 1;
1040
+ if (count === 0)
1041
+ continue;
1042
+ if (!changedFileData.has(curFile))
1043
+ changedFileData.set(curFile, { ranges: [], addedLines: new Map() });
1044
+ changedFileData.get(curFile).ranges.push([newLineNum, newLineNum + count - 1]);
1045
+ continue;
1046
+ }
1047
+ if (!curFile || !changedFileData.has(curFile))
1048
+ continue;
1049
+ if (line.startsWith('+') && !line.startsWith('+++')) {
1050
+ changedFileData.get(curFile).addedLines.set(newLineNum++, line.slice(1));
1051
+ }
1052
+ // '-' lines don't advance new-file position; no context lines with --unified=0
1053
+ }
1054
+ // Backward-compat: changedLines used by the node-overlap loop below
1055
+ const changedLines = new Map([...changedFileData.entries()].map(([k, v]) => [k, v.ranges]));
1056
+ const cg = ctx.callGraph;
1057
+ const nodeMap = new Map(cg.nodes.map(n => [n.id, n]));
1058
+ // Map changed line ranges to function nodes; track overlapping line count per function
1059
+ const changedFnIds = new Set();
1060
+ const fnChangedLineCount = new Map(); // nodeId → #lines overlapping with diff
1061
+ for (const [filePath, ranges] of changedLines) {
1062
+ const fileNodes = cg.nodes.filter(n => n.filePath === filePath && !n.isExternal && !n.isTest && n.startLine);
1063
+ for (const node of fileNodes) {
1064
+ const fnEnd = node.endLine ?? node.startLine;
1065
+ let overlap = 0;
1066
+ for (const [start, end] of ranges) {
1067
+ if (node.startLine <= end && fnEnd >= start) {
1068
+ changedFnIds.add(node.id);
1069
+ overlap += Math.min(end, fnEnd) - Math.max(start, node.startLine) + 1;
1070
+ }
1071
+ }
1072
+ if (overlap > 0)
1073
+ fnChangedLineCount.set(node.id, (fnChangedLineCount.get(node.id) ?? 0) + overlap);
1074
+ }
1075
+ // Fallback: no line match — include all functions in the file
1076
+ if (fileNodes.length > 0 && !fileNodes.some(n => changedFnIds.has(n.id))) {
1077
+ for (const n of fileNodes)
1078
+ changedFnIds.add(n.id);
1079
+ }
1080
+ }
1081
+ if (changedFnIds.size === 0) {
1082
+ return { changedFunctions: [], message: 'Changed files found but no matching function nodes. Re-run analyze_codebase.' };
1083
+ }
1084
+ const callsEdges = cg.edges.filter(e => !e.kind || e.kind === 'calls');
1085
+ // callerIndex: calleeId → [{id, callType}] — callType weights BFS contribution
1086
+ const callerIndex = new Map();
1087
+ for (const e of callsEdges) {
1088
+ if (!callerIndex.has(e.calleeId))
1089
+ callerIndex.set(e.calleeId, []);
1090
+ callerIndex.get(e.calleeId).push({ id: e.callerId, callType: e.callType });
1091
+ }
1092
+ // calleeIndex: callerId → calleeIds (for boundary score)
1093
+ const calleeIndex = new Map();
1094
+ for (const e of callsEdges) {
1095
+ if (!calleeIndex.has(e.callerId))
1096
+ calleeIndex.set(e.callerId, []);
1097
+ calleeIndex.get(e.callerId).push(e.calleeId);
1098
+ }
1099
+ // awaited callers most sensitive; callback least (detached, survives interface change)
1100
+ const callTypeWeight = (ct) => ct === 'awaited' ? 1.0 : ct === 'direct' ? 0.7 : ct === 'method' ? 0.6 :
1101
+ ct === 'callback' ? 0.4 : 0.5; // 0.5 default covers 'constructor' and unknown
1102
+ // Distance-weighted BFS: Σ weight/d² — clamped to prevent cross-repo drift
1103
+ const transitiveScore = (startId) => {
1104
+ const visited = new Set();
1105
+ const queue = [{ id: startId, depth: 1 }];
1106
+ let score = 0;
1107
+ while (queue.length) {
1108
+ const { id, depth } = queue.shift();
1109
+ for (const caller of callerIndex.get(id) ?? []) {
1110
+ if (!visited.has(caller.id)) {
1111
+ visited.add(caller.id);
1112
+ score += callTypeWeight(caller.callType) / (depth * depth);
1113
+ queue.push({ id: caller.id, depth: depth + 1 });
1114
+ }
1115
+ }
1116
+ }
1117
+ return Math.min(score, TRANSITIVE_SCORE_MAX);
1118
+ };
1119
+ // Boundary score: outgoing edges to external nodes; http/db weighted 3×, others 1×; normalized
1120
+ const boundaryScore = (nodeId) => {
1121
+ let raw = 0;
1122
+ for (const calleeId of calleeIndex.get(nodeId) ?? []) {
1123
+ const callee = nodeMap.get(calleeId);
1124
+ if (!callee?.isExternal)
1125
+ continue;
1126
+ raw += (callee.externalKind === 'http' || callee.externalKind === 'database') ? 3 : 1;
1127
+ }
1128
+ return Math.min(raw / 3, 1);
1129
+ };
1130
+ // testedBy map: nodeId → [{name, confidence}]
1131
+ const testedByMap = new Map();
1132
+ for (const e of cg.edges.filter(e => e.kind === 'tested_by')) {
1133
+ if (!testedByMap.has(e.callerId))
1134
+ testedByMap.set(e.callerId, []);
1135
+ const arr = testedByMap.get(e.callerId);
1136
+ if (!arr.some(x => x.name === e.calleeName)) {
1137
+ arr.push({ name: e.calleeName, confidence: e.confidence === 'import' ? 'imported' : 'called' });
1138
+ }
1139
+ }
1140
+ const scored = [...changedFnIds].map(id => {
1141
+ const n = nodeMap.get(id);
1142
+ const fnLength = Math.max(1, (n.endLine ?? n.startLine ?? 1) - (n.startLine ?? 1) + 1);
1143
+ const changed = fnChangedLineCount.get(id) ?? Math.round(fnLength * 0.5);
1144
+ // Blend relative (sensitivity) + absolute (log scale) — prevents tiny fully-changed fns
1145
+ // from outranking large ones; log(201)≈5.3 so 200 changed lines ≈ absScore 1.0
1146
+ const relScore = changed / fnLength;
1147
+ const absScore = Math.log(1 + changed) / Math.log(201);
1148
+ const rawChangeScore = Math.min(0.6 * relScore + 0.4 * absScore, 1);
1149
+ // Semantic modifier: signature changes are higher risk than config/literal tweaks
1150
+ const changeType = classifyChangeType(n, changedFileData.get(n.filePath)?.addedLines ?? new Map());
1151
+ const changeScore = Math.min(rawChangeScore * CHANGE_TYPE_MULTIPLIER[changeType], 1);
1152
+ const tests = testedByMap.get(id) ?? [];
1153
+ // called-edges are direct proof; imported-only is weaker (survives vi.mock)
1154
+ const effectiveTests = tests.reduce((s, t) => s + (t.confidence === 'called' ? 1.0 : 0.3), 0);
1155
+ const coveragePenalty = 1 / (1 + Math.log(1 + effectiveTests));
1156
+ const tScore = transitiveScore(id);
1157
+ const bScore = boundaryScore(id);
1158
+ // Multiplicative model: risk = likelihood × impact
1159
+ // Decouples change probability from structural blast radius; prevents correlated
1160
+ // signals (fanIn ↔ transitive) from triple-stacking additively
1161
+ const likelihood = changeScore * (1 + coveragePenalty);
1162
+ const impact = Math.log(1 + n.fanIn) * 0.8 + tScore + bScore;
1163
+ const riskScore = Math.round(likelihood * impact * 100) / 100;
1164
+ const directCallers = callerIndex.get(id) ?? [];
1165
+ const reason = buildReason({ changeType, directCallers, tests, bScore, fanIn: n.fanIn });
1166
+ return {
1167
+ name: n.name,
1168
+ file: relative(absDir, n.filePath),
1169
+ startLine: n.startLine ?? null,
1170
+ endLine: n.endLine ?? null,
1171
+ fanIn: n.fanIn,
1172
+ blastRadius: Math.round(tScore * 100) / 100,
1173
+ changeType,
1174
+ riskScore,
1175
+ reason,
1176
+ testedBy: testedByMap.get(id) ?? [],
1177
+ };
1178
+ }).sort((a, b) => b.riskScore - a.riskScore);
1179
+ return {
1180
+ base: ref,
1181
+ totalChanged: scored.length,
1182
+ changedFunctions: scored,
1183
+ };
1184
+ }
1185
+ //# sourceMappingURL=analysis.js.map