@documentation-robotics/cli 0.1.0 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (510) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +466 -693
  3. package/dist/analysis/data-model-analyzer.js +189 -0
  4. package/dist/analysis/data-model-analyzer.js.map +7 -0
  5. package/dist/analysis/index.js +3 -0
  6. package/dist/analysis/index.js.map +7 -0
  7. package/dist/analysis/relationship-classifier.js +181 -0
  8. package/dist/analysis/relationship-classifier.js.map +7 -0
  9. package/dist/analyzers/base-analyzer.js +1 -0
  10. package/dist/analyzers/base-analyzer.js.map +7 -0
  11. package/dist/analyzers/call-graph-utils.js +39 -0
  12. package/dist/analyzers/call-graph-utils.js.map +7 -0
  13. package/dist/analyzers/cbm-analyzer.js +1205 -0
  14. package/dist/analyzers/cbm-analyzer.js.map +7 -0
  15. package/dist/analyzers/heuristic-utils.js +55 -0
  16. package/dist/analyzers/heuristic-utils.js.map +7 -0
  17. package/dist/analyzers/mapping-loader.js +146 -0
  18. package/dist/analyzers/mapping-loader.js.map +7 -0
  19. package/dist/analyzers/registry.js +124 -0
  20. package/dist/analyzers/registry.js.map +7 -0
  21. package/dist/analyzers/session-state.js +176 -0
  22. package/dist/analyzers/session-state.js.map +7 -0
  23. package/dist/analyzers/stdio-client.js +206 -0
  24. package/dist/analyzers/stdio-client.js.map +7 -0
  25. package/dist/analyzers/types.js +15 -0
  26. package/dist/analyzers/types.js.map +7 -0
  27. package/dist/analyzers/verify-engine.js +209 -0
  28. package/dist/analyzers/verify-engine.js.map +7 -0
  29. package/dist/analyzers/verify-ignore.js +130 -0
  30. package/dist/analyzers/verify-ignore.js.map +7 -0
  31. package/dist/analyzers/version.js +8 -0
  32. package/dist/analyzers/version.js.map +7 -0
  33. package/dist/audit/ai/runner.js +41 -0
  34. package/dist/audit/ai/runner.js.map +7 -0
  35. package/dist/audit/differential-analyzer.js +183 -0
  36. package/dist/audit/differential-analyzer.js.map +7 -0
  37. package/dist/audit/index.js +32 -0
  38. package/dist/audit/index.js.map +7 -0
  39. package/dist/audit/nodes/ai/evaluator.js +49 -0
  40. package/dist/audit/nodes/ai/evaluator.js.map +7 -0
  41. package/dist/audit/nodes/ai/parser.js +55 -0
  42. package/dist/audit/nodes/ai/parser.js.map +7 -0
  43. package/dist/audit/nodes/ai/prompts.js +103 -0
  44. package/dist/audit/nodes/ai/prompts.js.map +7 -0
  45. package/dist/audit/nodes/analysis/node-completeness-checker.js +32 -0
  46. package/dist/audit/nodes/analysis/node-completeness-checker.js.map +7 -0
  47. package/dist/audit/nodes/analysis/node-definition-analyzer.js +47 -0
  48. package/dist/audit/nodes/analysis/node-definition-analyzer.js.map +7 -0
  49. package/dist/audit/nodes/analysis/node-overlap-detector.js +79 -0
  50. package/dist/audit/nodes/analysis/node-overlap-detector.js.map +7 -0
  51. package/dist/audit/nodes/model/orchestrator.js +82 -0
  52. package/dist/audit/nodes/model/orchestrator.js.map +7 -0
  53. package/dist/audit/nodes/spec/loader.js +116 -0
  54. package/dist/audit/nodes/spec/loader.js.map +7 -0
  55. package/dist/audit/nodes/spec/orchestrator.js +68 -0
  56. package/dist/audit/nodes/spec/orchestrator.js.map +7 -0
  57. package/dist/audit/nodes/types.js +1 -0
  58. package/dist/audit/nodes/types.js.map +7 -0
  59. package/dist/audit/pipeline/index.js +5 -0
  60. package/dist/audit/pipeline/index.js.map +7 -0
  61. package/dist/audit/pipeline/pipeline-orchestrator.js +218 -0
  62. package/dist/audit/pipeline/pipeline-orchestrator.js.map +7 -0
  63. package/dist/audit/relationships/ai/evaluator.js +235 -0
  64. package/dist/audit/relationships/ai/evaluator.js.map +7 -0
  65. package/dist/audit/relationships/ai/parser.js +197 -0
  66. package/dist/audit/relationships/ai/parser.js.map +7 -0
  67. package/dist/audit/relationships/ai/prompts.js +274 -0
  68. package/dist/audit/relationships/ai/prompts.js.map +7 -0
  69. package/dist/audit/relationships/analysis/balance-assessor.js +115 -0
  70. package/dist/audit/relationships/analysis/balance-assessor.js.map +7 -0
  71. package/dist/audit/relationships/analysis/coverage-analyzer.js +99 -0
  72. package/dist/audit/relationships/analysis/coverage-analyzer.js.map +7 -0
  73. package/dist/audit/relationships/analysis/duplicate-detector.js +95 -0
  74. package/dist/audit/relationships/analysis/duplicate-detector.js.map +7 -0
  75. package/dist/audit/relationships/analysis/gap-analyzer.js +99 -0
  76. package/dist/audit/relationships/analysis/gap-analyzer.js.map +7 -0
  77. package/dist/audit/relationships/analysis/layer-templates.js +294 -0
  78. package/dist/audit/relationships/analysis/layer-templates.js.map +7 -0
  79. package/dist/audit/relationships/graph/connectivity.js +129 -0
  80. package/dist/audit/relationships/graph/connectivity.js.map +7 -0
  81. package/dist/audit/relationships/graph/relationship-graph.js +179 -0
  82. package/dist/audit/relationships/graph/relationship-graph.js.map +7 -0
  83. package/dist/audit/relationships/model/balance-assessor.js +49 -0
  84. package/dist/audit/relationships/model/balance-assessor.js.map +7 -0
  85. package/dist/audit/relationships/model/coverage-analyzer.js +34 -0
  86. package/dist/audit/relationships/model/coverage-analyzer.js.map +7 -0
  87. package/dist/audit/relationships/model/duplicate-detector.js +68 -0
  88. package/dist/audit/relationships/model/duplicate-detector.js.map +7 -0
  89. package/dist/audit/relationships/model/gap-analyzer.js +41 -0
  90. package/dist/audit/relationships/model/gap-analyzer.js.map +7 -0
  91. package/dist/audit/relationships/model/orchestrator.js +126 -0
  92. package/dist/audit/relationships/model/orchestrator.js.map +7 -0
  93. package/dist/audit/relationships/spec/orchestrator.js +157 -0
  94. package/dist/audit/relationships/spec/orchestrator.js.map +7 -0
  95. package/dist/audit/reports/index.js +5 -0
  96. package/dist/audit/reports/index.js.map +7 -0
  97. package/dist/audit/reports/report-generator.js +118 -0
  98. package/dist/audit/reports/report-generator.js.map +7 -0
  99. package/dist/audit/snapshot-storage.js +163 -0
  100. package/dist/audit/snapshot-storage.js.map +7 -0
  101. package/dist/audit/types.js +21 -0
  102. package/dist/audit/types.js.map +7 -0
  103. package/dist/cli.js +223 -92
  104. package/dist/cli.js.map +3 -3
  105. package/dist/coding-agents/agents/claude-code.js +147 -0
  106. package/dist/coding-agents/agents/claude-code.js.map +7 -0
  107. package/dist/coding-agents/agents/index.js +5 -0
  108. package/dist/coding-agents/agents/index.js.map +7 -0
  109. package/dist/coding-agents/agents/types.js +1 -0
  110. package/dist/coding-agents/agents/types.js.map +7 -0
  111. package/dist/coding-agents/base-chat-client.js +45 -0
  112. package/dist/coding-agents/base-chat-client.js.map +7 -0
  113. package/dist/coding-agents/chat-utils.js +23 -0
  114. package/dist/coding-agents/chat-utils.js.map +7 -0
  115. package/dist/coding-agents/claude-code-client.js +246 -0
  116. package/dist/coding-agents/claude-code-client.js.map +7 -0
  117. package/dist/coding-agents/copilot-client.js +209 -0
  118. package/dist/coding-agents/copilot-client.js.map +7 -0
  119. package/dist/commands/add.js +95 -33
  120. package/dist/commands/add.js.map +2 -2
  121. package/dist/commands/analyzer.js +511 -0
  122. package/dist/commands/analyzer.js.map +7 -0
  123. package/dist/commands/audit-diff.js +49 -0
  124. package/dist/commands/audit-diff.js.map +7 -0
  125. package/dist/commands/audit-snapshots.js +62 -0
  126. package/dist/commands/audit-snapshots.js.map +7 -0
  127. package/dist/commands/audit.js +81 -0
  128. package/dist/commands/audit.js.map +7 -0
  129. package/dist/commands/catalog.js +248 -0
  130. package/dist/commands/catalog.js.map +7 -0
  131. package/dist/commands/changeset.js +577 -66
  132. package/dist/commands/changeset.js.map +2 -2
  133. package/dist/commands/chat.js +106 -83
  134. package/dist/commands/chat.js.map +2 -2
  135. package/dist/commands/claude.js +74 -0
  136. package/dist/commands/claude.js.map +7 -0
  137. package/dist/commands/conformance.js +71 -68
  138. package/dist/commands/conformance.js.map +2 -2
  139. package/dist/commands/copilot.js +75 -0
  140. package/dist/commands/copilot.js.map +7 -0
  141. package/dist/commands/delete.js +125 -11
  142. package/dist/commands/delete.js.map +3 -3
  143. package/dist/commands/discover-logic.js +44 -0
  144. package/dist/commands/discover-logic.js.map +7 -0
  145. package/dist/commands/docs.js +87 -0
  146. package/dist/commands/docs.js.map +7 -0
  147. package/dist/commands/element.js +14 -20
  148. package/dist/commands/element.js.map +2 -2
  149. package/dist/commands/export.js +13 -3
  150. package/dist/commands/export.js.map +2 -2
  151. package/dist/commands/import.js +51 -0
  152. package/dist/commands/import.js.map +7 -0
  153. package/dist/commands/info.js +30 -24
  154. package/dist/commands/info.js.map +2 -2
  155. package/dist/commands/init.js +27 -11
  156. package/dist/commands/init.js.map +2 -2
  157. package/dist/commands/list.js +63 -13
  158. package/dist/commands/list.js.map +3 -3
  159. package/dist/commands/relationship.js +225 -37
  160. package/dist/commands/relationship.js.map +2 -2
  161. package/dist/commands/report.js +54 -0
  162. package/dist/commands/report.js.map +7 -0
  163. package/dist/commands/reports.js +45 -0
  164. package/dist/commands/reports.js.map +7 -0
  165. package/dist/commands/schema.js +135 -0
  166. package/dist/commands/schema.js.map +7 -0
  167. package/dist/commands/search.js +40 -12
  168. package/dist/commands/search.js.map +2 -2
  169. package/dist/commands/show.js +58 -27
  170. package/dist/commands/show.js.map +2 -2
  171. package/dist/commands/stats.js +35 -0
  172. package/dist/commands/stats.js.map +7 -0
  173. package/dist/commands/trace.js +39 -16
  174. package/dist/commands/trace.js.map +2 -2
  175. package/dist/commands/update.js +141 -14
  176. package/dist/commands/update.js.map +2 -2
  177. package/dist/commands/upgrade.js +210 -46
  178. package/dist/commands/upgrade.js.map +3 -3
  179. package/dist/commands/validate.js +142 -23
  180. package/dist/commands/validate.js.map +3 -3
  181. package/dist/commands/version.js +8 -12
  182. package/dist/commands/version.js.map +2 -2
  183. package/dist/commands/visualize.js +155 -25
  184. package/dist/commands/visualize.js.map +2 -2
  185. package/dist/core/base-snapshot-manager.js +84 -0
  186. package/dist/core/base-snapshot-manager.js.map +7 -0
  187. package/dist/core/changeset-exporter.js +305 -0
  188. package/dist/core/changeset-exporter.js.map +7 -0
  189. package/dist/core/changeset-validator.js +41 -0
  190. package/dist/core/changeset-validator.js.map +7 -0
  191. package/dist/core/changeset.js +88 -173
  192. package/dist/core/changeset.js.map +2 -2
  193. package/dist/core/dependency-tracker.js +4 -2
  194. package/dist/core/dependency-tracker.js.map +2 -2
  195. package/dist/core/element.js +98 -27
  196. package/dist/core/element.js.map +2 -2
  197. package/dist/core/golden-copy-cache.js +290 -0
  198. package/dist/core/golden-copy-cache.js.map +7 -0
  199. package/dist/core/graph-model.js +266 -0
  200. package/dist/core/graph-model.js.map +7 -0
  201. package/dist/core/layer.js +159 -16
  202. package/dist/core/layer.js.map +2 -2
  203. package/dist/core/layers.js +32 -0
  204. package/dist/core/layers.js.map +7 -0
  205. package/dist/core/manifest.js +32 -11
  206. package/dist/core/manifest.js.map +2 -2
  207. package/dist/core/migration-registry.js +56 -1
  208. package/dist/core/migration-registry.js.map +2 -2
  209. package/dist/core/model.js +424 -217
  210. package/dist/core/model.js.map +3 -3
  211. package/dist/core/mutation-handler.js +179 -0
  212. package/dist/core/mutation-handler.js.map +7 -0
  213. package/dist/core/reference-registry.js +29 -21
  214. package/dist/core/reference-registry.js.map +2 -2
  215. package/dist/core/relationship-catalog.js +189 -0
  216. package/dist/core/relationship-catalog.js.map +7 -0
  217. package/dist/core/relationship-registry.js.map +2 -2
  218. package/dist/core/relationships.js.map +1 -1
  219. package/dist/core/report-data-model.js +285 -0
  220. package/dist/core/report-data-model.js.map +7 -0
  221. package/dist/core/spec-data-service.js +185 -0
  222. package/dist/core/spec-data-service.js.map +7 -0
  223. package/dist/core/spec-loader-types.js +1 -0
  224. package/dist/core/spec-loader-types.js.map +7 -0
  225. package/dist/core/spec-loader.js +234 -0
  226. package/dist/core/spec-loader.js.map +7 -0
  227. package/dist/core/staged-changeset-storage.js +282 -0
  228. package/dist/core/staged-changeset-storage.js.map +7 -0
  229. package/dist/core/staging-area.js +1002 -0
  230. package/dist/core/staging-area.js.map +7 -0
  231. package/dist/core/stats-collector.js +169 -0
  232. package/dist/core/stats-collector.js.map +7 -0
  233. package/dist/core/virtual-projection.js +443 -0
  234. package/dist/core/virtual-projection.js.map +7 -0
  235. package/dist/export/archimate-exporter.js +99 -60
  236. package/dist/export/archimate-exporter.js.map +2 -2
  237. package/dist/export/audit-formatters.js +479 -0
  238. package/dist/export/audit-formatters.js.map +7 -0
  239. package/dist/export/diff-formatters.js +99 -0
  240. package/dist/export/diff-formatters.js.map +7 -0
  241. package/dist/export/export-manager.js.map +2 -2
  242. package/dist/export/graphml-exporter.js +44 -27
  243. package/dist/export/graphml-exporter.js.map +2 -2
  244. package/dist/export/index.js +4 -0
  245. package/dist/export/index.js.map +2 -2
  246. package/dist/export/json-schema-exporter.js +55 -42
  247. package/dist/export/json-schema-exporter.js.map +2 -2
  248. package/dist/export/markdown-exporter.js +92 -70
  249. package/dist/export/markdown-exporter.js.map +2 -2
  250. package/dist/export/markdown-generator.js +248 -0
  251. package/dist/export/markdown-generator.js.map +7 -0
  252. package/dist/export/markdown-utils.js +30 -0
  253. package/dist/export/markdown-utils.js.map +7 -0
  254. package/dist/export/mermaid-markdown-exporter.js +37 -0
  255. package/dist/export/mermaid-markdown-exporter.js.map +7 -0
  256. package/dist/export/openapi-exporter.js +119 -71
  257. package/dist/export/openapi-exporter.js.map +2 -2
  258. package/dist/export/plantuml-exporter.js +104 -39
  259. package/dist/export/plantuml-exporter.js.map +2 -2
  260. package/dist/export/report-formatters.js +111 -0
  261. package/dist/export/report-formatters.js.map +7 -0
  262. package/dist/export/stats-formatters.js +98 -0
  263. package/dist/export/stats-formatters.js.map +7 -0
  264. package/dist/export/types.js +2 -14
  265. package/dist/export/types.js.map +2 -2
  266. package/dist/export/verify-formatters.js +153 -0
  267. package/dist/export/verify-formatters.js.map +7 -0
  268. package/dist/generated/compiled-validators.js +17 -0
  269. package/dist/generated/compiled-validators.js.map +7 -0
  270. package/dist/generated/index.js +31 -0
  271. package/dist/generated/index.js.map +7 -0
  272. package/dist/generated/layer-registry.js +190 -0
  273. package/dist/generated/layer-registry.js.map +7 -0
  274. package/dist/generated/layer-types.js +7 -0
  275. package/dist/generated/layer-types.js.map +7 -0
  276. package/dist/generated/node-types.js +1719 -0
  277. package/dist/generated/node-types.js.map +7 -0
  278. package/dist/generated/relationship-index.js +14552 -0
  279. package/dist/generated/relationship-index.js.map +7 -0
  280. package/dist/import/archimate-importer.js +166 -0
  281. package/dist/import/archimate-importer.js.map +7 -0
  282. package/dist/import/index.js +47 -0
  283. package/dist/import/index.js.map +7 -0
  284. package/dist/import/openapi-importer.js +143 -0
  285. package/dist/import/openapi-importer.js.map +7 -0
  286. package/dist/import/types.js +1 -0
  287. package/dist/import/types.js.map +7 -0
  288. package/dist/integrations/base-manager.js +311 -0
  289. package/dist/integrations/base-manager.js.map +7 -0
  290. package/dist/integrations/claude-manager.js +360 -0
  291. package/dist/integrations/claude-manager.js.map +7 -0
  292. package/dist/integrations/claude_code/agents/dr-advisor.md +253 -0
  293. package/dist/integrations/claude_code/agents/dr-architect.md +278 -0
  294. package/dist/integrations/claude_code/agents/dr-extractor.md +286 -0
  295. package/dist/integrations/claude_code/commands/dr-changeset.md +657 -0
  296. package/dist/integrations/claude_code/commands/dr-design.md +373 -0
  297. package/dist/integrations/claude_code/commands/dr-info.md +132 -0
  298. package/dist/integrations/claude_code/commands/dr-init.md +251 -0
  299. package/dist/integrations/claude_code/commands/dr-map.md +936 -0
  300. package/dist/integrations/claude_code/commands/dr-model.md +493 -0
  301. package/dist/integrations/claude_code/commands/dr-relate.md +814 -0
  302. package/dist/integrations/claude_code/commands/dr-sync.md +279 -0
  303. package/dist/integrations/claude_code/commands/dr-validate.md +654 -0
  304. package/dist/integrations/claude_code/commands/dr-verify.md +559 -0
  305. package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +903 -0
  306. package/dist/integrations/claude_code/docs/QUICK_START.md +271 -0
  307. package/dist/integrations/claude_code/docs/README.md +347 -0
  308. package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +550 -0
  309. package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +620 -0
  310. package/dist/integrations/claude_code/skills/dr_03_security_layer/SKILL.md +642 -0
  311. package/dist/integrations/claude_code/skills/dr_04_application_layer/SKILL.md +911 -0
  312. package/dist/integrations/claude_code/skills/dr_05_technology_layer/SKILL.md +836 -0
  313. package/dist/integrations/claude_code/skills/dr_06_api_layer/SKILL.md +933 -0
  314. package/dist/integrations/claude_code/skills/dr_07_data_model_layer/SKILL.md +210 -0
  315. package/dist/integrations/claude_code/skills/dr_08_dataset_layer/SKILL.md +319 -0
  316. package/dist/integrations/claude_code/skills/dr_09_ux_layer/SKILL.md +319 -0
  317. package/dist/integrations/claude_code/skills/dr_10_navigation_layer/SKILL.md +318 -0
  318. package/dist/integrations/claude_code/skills/dr_11_apm_layer/SKILL.md +273 -0
  319. package/dist/integrations/claude_code/skills/dr_12_testing_layer/SKILL.md +273 -0
  320. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +187 -0
  321. package/dist/integrations/claude_code/skills/dr_codebase_memory/SKILL.md +590 -0
  322. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +295 -0
  323. package/dist/integrations/copilot-manager.js +328 -0
  324. package/dist/integrations/copilot-manager.js.map +7 -0
  325. package/dist/integrations/github_copilot/agents/dr-advisor.md +243 -0
  326. package/dist/integrations/github_copilot/agents/dr-architect.md +1567 -0
  327. package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +491 -0
  328. package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +539 -0
  329. package/dist/integrations/github_copilot/skills/dr_03_security_layer/SKILL.md +667 -0
  330. package/dist/integrations/github_copilot/skills/dr_04_application_layer/SKILL.md +670 -0
  331. package/dist/integrations/github_copilot/skills/dr_05_technology_layer/SKILL.md +660 -0
  332. package/dist/integrations/github_copilot/skills/dr_06_api_layer/SKILL.md +855 -0
  333. package/dist/integrations/github_copilot/skills/dr_07_data_model_layer/SKILL.md +171 -0
  334. package/dist/integrations/github_copilot/skills/dr_08_dataset_layer/SKILL.md +216 -0
  335. package/dist/integrations/github_copilot/skills/dr_09_ux_layer/SKILL.md +230 -0
  336. package/dist/integrations/github_copilot/skills/dr_10_navigation_layer/SKILL.md +228 -0
  337. package/dist/integrations/github_copilot/skills/dr_11_apm_layer/SKILL.md +234 -0
  338. package/dist/integrations/github_copilot/skills/dr_12_testing_layer/SKILL.md +230 -0
  339. package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +187 -0
  340. package/dist/integrations/hash-utils.js +51 -0
  341. package/dist/integrations/hash-utils.js.map +7 -0
  342. package/dist/integrations/types.js +16 -0
  343. package/dist/integrations/types.js.map +7 -0
  344. package/dist/reports/model-layer-report-generator.js +278 -0
  345. package/dist/reports/model-layer-report-generator.js.map +7 -0
  346. package/dist/reports/model-report-data.js +57 -0
  347. package/dist/reports/model-report-data.js.map +7 -0
  348. package/dist/reports/model-report-orchestrator.js +103 -0
  349. package/dist/reports/model-report-orchestrator.js.map +7 -0
  350. package/dist/schemas/bundled/analyzers/cbm.json +595 -0
  351. package/dist/schemas/bundled/analyzers/manifest.json +9 -0
  352. package/dist/schemas/bundled/api.json +3411 -0
  353. package/dist/schemas/bundled/apm.json +2371 -0
  354. package/dist/schemas/bundled/application.json +1253 -0
  355. package/dist/schemas/bundled/base.json +1622 -0
  356. package/dist/schemas/bundled/business.json +1279 -0
  357. package/dist/schemas/bundled/data-model.json +1298 -0
  358. package/dist/schemas/bundled/data-store.json +2025 -0
  359. package/dist/schemas/bundled/manifest.json +93 -0
  360. package/dist/schemas/bundled/motivation.json +1360 -0
  361. package/dist/schemas/bundled/navigation.json +1874 -0
  362. package/dist/schemas/bundled/security.json +3488 -0
  363. package/dist/schemas/bundled/technology.json +2067 -0
  364. package/dist/schemas/bundled/testing.json +3120 -0
  365. package/dist/schemas/bundled/ux.json +3592 -0
  366. package/dist/server/schemas.js +202 -0
  367. package/dist/server/schemas.js.map +7 -0
  368. package/dist/server/server.js +1336 -252
  369. package/dist/server/server.js.map +3 -3
  370. package/dist/server/telemetry-middleware.js +22 -15
  371. package/dist/server/telemetry-middleware.js.map +2 -2
  372. package/dist/server-entry.js +65 -0
  373. package/dist/server-entry.js.map +7 -0
  374. package/dist/telemetry/config.js +89 -0
  375. package/dist/telemetry/config.js.map +7 -0
  376. package/dist/telemetry/console-interceptor.js.map +1 -1
  377. package/dist/telemetry/detect.js +24 -0
  378. package/dist/telemetry/detect.js.map +7 -0
  379. package/dist/telemetry/fetch-otlp-exporter.js +112 -0
  380. package/dist/telemetry/fetch-otlp-exporter.js.map +7 -0
  381. package/dist/telemetry/index.js +153 -3
  382. package/dist/telemetry/index.js.map +2 -2
  383. package/dist/telemetry/resilient-exporter.js +22 -8
  384. package/dist/telemetry/resilient-exporter.js.map +2 -2
  385. package/dist/telemetry/resilient-log-exporter.js +17 -8
  386. package/dist/telemetry/resilient-log-exporter.js.map +2 -2
  387. package/dist/telemetry/test-instrumentation.js.map +2 -2
  388. package/dist/types/index.js +84 -0
  389. package/dist/types/index.js.map +3 -3
  390. package/dist/types/source-reference.js +1 -0
  391. package/dist/types/source-reference.js.map +7 -0
  392. package/dist/utils/chat-logger.js +292 -0
  393. package/dist/utils/chat-logger.js.map +7 -0
  394. package/dist/utils/claude-stream.js +8 -0
  395. package/dist/utils/claude-stream.js.map +7 -0
  396. package/dist/utils/date-utils.js +7 -0
  397. package/dist/utils/date-utils.js.map +7 -0
  398. package/dist/utils/element-utils.js.map +1 -1
  399. package/dist/utils/errors.js +88 -15
  400. package/dist/utils/errors.js.map +3 -3
  401. package/dist/utils/export-python-annotations.js +82 -0
  402. package/dist/utils/export-python-annotations.js.map +7 -0
  403. package/dist/utils/file-io.js +7 -2
  404. package/dist/utils/file-io.js.map +2 -2
  405. package/dist/utils/file-lock.js +156 -0
  406. package/dist/utils/file-lock.js.map +7 -0
  407. package/dist/utils/id-generator.js +18 -0
  408. package/dist/utils/id-generator.js.map +7 -0
  409. package/dist/utils/layer-name-formatter.js +16 -0
  410. package/dist/utils/layer-name-formatter.js.map +7 -0
  411. package/dist/utils/markdown-anchor.js +7 -0
  412. package/dist/utils/markdown-anchor.js.map +7 -0
  413. package/dist/utils/markdown-link.js +12 -0
  414. package/dist/utils/markdown-link.js.map +7 -0
  415. package/dist/utils/markdown-table.js +17 -0
  416. package/dist/utils/markdown-table.js.map +7 -0
  417. package/dist/utils/mermaid-utils.js +11 -0
  418. package/dist/utils/mermaid-utils.js.map +7 -0
  419. package/dist/utils/project-paths.js.map +2 -2
  420. package/dist/utils/source-reference.js +52 -0
  421. package/dist/utils/source-reference.js.map +7 -0
  422. package/dist/utils/spec-installer.js +40 -53
  423. package/dist/utils/spec-installer.js.map +2 -2
  424. package/dist/utils/spec-version.js +5 -1
  425. package/dist/utils/spec-version.js.map +2 -2
  426. package/dist/utils/string-utils.js +12 -0
  427. package/dist/utils/string-utils.js.map +7 -0
  428. package/dist/utils/table-formatting.js +19 -0
  429. package/dist/utils/table-formatting.js.map +7 -0
  430. package/dist/validators/index.js +4 -0
  431. package/dist/validators/index.js.map +2 -2
  432. package/dist/validators/naming-validator.js +20 -23
  433. package/dist/validators/naming-validator.js.map +2 -2
  434. package/dist/validators/reference-validator.js +23 -38
  435. package/dist/validators/reference-validator.js.map +2 -2
  436. package/dist/validators/relationship-schema-validator.js +243 -0
  437. package/dist/validators/relationship-schema-validator.js.map +7 -0
  438. package/dist/validators/schema-validator.js +168 -68
  439. package/dist/validators/schema-validator.js.map +2 -2
  440. package/dist/validators/semantic-validator.js +36 -60
  441. package/dist/validators/semantic-validator.js.map +2 -2
  442. package/dist/validators/types.js.map +2 -2
  443. package/dist/validators/validation-formatter.js +159 -0
  444. package/dist/validators/validation-formatter.js.map +7 -0
  445. package/dist/validators/validator.js +24 -38
  446. package/dist/validators/validator.js.map +2 -2
  447. package/dist/viewer/README.md +139 -0
  448. package/dist/viewer/assets/main-CY0Xufuj.css +1 -0
  449. package/dist/viewer/assets/main-DtgErnwF.js +81 -0
  450. package/dist/viewer/assets/vendor-CxKvDp2x.js +17 -0
  451. package/dist/viewer/favicon.svg +11 -0
  452. package/dist/viewer/fonts/inter/Inter-Black.woff2 +0 -0
  453. package/dist/viewer/fonts/inter/Inter-Bold.woff2 +0 -0
  454. package/dist/viewer/fonts/inter/Inter-ExtraBold.woff2 +0 -0
  455. package/dist/viewer/fonts/inter/Inter-Light.woff2 +0 -0
  456. package/dist/viewer/fonts/inter/Inter-Medium.woff2 +0 -0
  457. package/dist/viewer/fonts/inter/Inter-Regular.woff2 +0 -0
  458. package/dist/viewer/fonts/inter/Inter-SemiBold.woff2 +0 -0
  459. package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2 +0 -0
  460. package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2 +0 -0
  461. package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-SemiBold.woff2 +0 -0
  462. package/dist/viewer/index.html +15 -0
  463. package/dist/viewer/manifest.json +99 -0
  464. package/dist/viewer/version.json +1 -0
  465. package/package.json +37 -13
  466. package/PYTHON_CLI_PARITY.md +0 -265
  467. package/PYTHON_CLI_STRUCTURE_UPDATE.md +0 -246
  468. package/biome.json +0 -42
  469. package/bunfig.toml +0 -3
  470. package/dist/ai/claude-client.js +0 -71
  471. package/dist/ai/claude-client.js.map +0 -7
  472. package/dist/ai/context-provider.js +0 -129
  473. package/dist/ai/context-provider.js.map +0 -7
  474. package/dist/ai/tools.js +0 -231
  475. package/dist/ai/tools.js.map +0 -7
  476. package/dist/commands/migrate.js +0 -36
  477. package/dist/commands/migrate.js.map +0 -7
  478. package/dist/commands/project.js +0 -74
  479. package/dist/commands/project.js.map +0 -7
  480. package/dist/core/active-changeset.js +0 -52
  481. package/dist/core/active-changeset.js.map +0 -7
  482. package/dist/core/projection-engine.js +0 -288
  483. package/dist/core/projection-engine.js.map +0 -7
  484. package/dist/schemas/bundled/01-motivation-layer.schema.json +0 -942
  485. package/dist/schemas/bundled/02-business-layer.schema.json +0 -1293
  486. package/dist/schemas/bundled/03-security-layer.schema.json +0 -2177
  487. package/dist/schemas/bundled/04-application-layer.schema.json +0 -1328
  488. package/dist/schemas/bundled/05-technology-layer.schema.json +0 -1465
  489. package/dist/schemas/bundled/06-api-layer.schema.json +0 -1743
  490. package/dist/schemas/bundled/07-data-model-layer.schema.json +0 -1253
  491. package/dist/schemas/bundled/08-datastore-layer.schema.json +0 -892
  492. package/dist/schemas/bundled/09-ux-layer.schema.json +0 -1506
  493. package/dist/schemas/bundled/10-navigation-layer.schema.json +0 -843
  494. package/dist/schemas/bundled/11-apm-observability-layer.schema.json +0 -1430
  495. package/dist/schemas/bundled/12-testing-layer.schema.json +0 -1091
  496. package/dist/schemas/bundled/common/layer-extensions.schema.json +0 -266
  497. package/dist/schemas/bundled/common/predicates.schema.json +0 -493
  498. package/dist/schemas/bundled/common/relationships.schema.json +0 -791
  499. package/dist/schemas/bundled/common/source-references.schema.json +0 -71
  500. package/dist/schemas/bundled/link-registry.json +0 -1839
  501. package/dist/schemas/bundled/link-registry.schema.json +0 -339
  502. package/dist/schemas/bundled/relationship-catalog.json +0 -1302
  503. package/dist/schemas/bundled/relationship-type.schema.json +0 -221
  504. package/dist/utils/model-path.js +0 -22
  505. package/dist/utils/model-path.js.map +0 -7
  506. package/docs/TEST-INSTRUMENTATION.md +0 -405
  507. package/docs/telemetry.md +0 -427
  508. package/test-debug.js +0 -33
  509. package/test-special.mjs +0 -46
  510. package/test_runner.ts +0 -7
@@ -0,0 +1,1205 @@
1
+ import { spawnSync } from "child_process";
2
+ import * as path from "path";
3
+ import { readFile } from "fs/promises";
4
+ import { CLIError, ErrorCategory, handleWarning } from "../utils/errors.js";
5
+ import { VALID_HTTP_METHODS } from "./types.js";
6
+ import { StdioClient } from "./stdio-client.js";
7
+ import { readIndexMeta, writeIndexMeta } from "./session-state.js";
8
+ import { getCliVersion } from "./version.js";
9
+ import { StagingAreaManager } from "../core/staging-area.js";
10
+ import { VerifyEngine } from "./verify-engine.js";
11
+ import { clampDepth, shapeCallGraphNode, determineEdgeType } from "./call-graph-utils.js";
12
+ import { evaluateHeuristic, capConfidence, matchPattern, getKnownHeuristicNames } from "./heuristic-utils.js";
13
+ import { toKebabCase } from "../utils/id-generator.js";
14
+ const SOURCE_CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs|vue|svelte|py|go|rb|java|rs|php|cs|cpp|c|h|kt|swift|sh|bash)$/i;
15
+ function isLikelyHttpRoute(qualifiedName) {
16
+ const httpPath = qualifiedName.replace(/^__route__[A-Z]+__/, "");
17
+ return !SOURCE_CODE_EXT_RE.test(httpPath) && !httpPath.startsWith("/.dr/") && !httpPath.startsWith("/schemas/");
18
+ }
19
+ function inferHttpMethodFromFunctionName(name) {
20
+ const lower = name.toLowerCase();
21
+ return /^(create|add|post|submit|send)/.test(lower) ? "POST" : /^(update|put|save|replace|set)/.test(lower) ? "PUT" : /^(patch|merge)/.test(lower) ? "PATCH" : /^(delete|remove|destroy)/.test(lower) ? "DELETE" : "GET";
22
+ }
23
+ function inferHttpPathFromFunctionName(name) {
24
+ const stripped = name.replace(/^(fetch|get|list|create|add|update|put|delete|remove|patch|merge|load|save|replace|post|submit|send|destroy)/i, ""), segment = toKebabCase(stripped || name);
25
+ return segment ? `/${segment}` : "/";
26
+ }
27
+ class CbmAnalyzer {
28
+ mapper;
29
+ testCodePatternWarned = !1;
30
+ warnedHeuristics = /* @__PURE__ */ new Set();
31
+ /**
32
+ * Default regex patterns for test code detection
33
+ */
34
+ static DEFAULT_TEST_PATTERNS = [
35
+ /\.test\./,
36
+ /\.spec\./,
37
+ /\/tests?\//,
38
+ /\/__tests__\//,
39
+ /\/test-/,
40
+ /\/fixtures?\//,
41
+ /\/__fixtures__\//
42
+ ];
43
+ constructor(mapper) {
44
+ this.mapper = mapper;
45
+ }
46
+ /**
47
+ * Get the analyzer name
48
+ */
49
+ get name() {
50
+ return this.mapper.getAnalyzerName();
51
+ }
52
+ /**
53
+ * Get the analyzer display name
54
+ */
55
+ get displayName() {
56
+ return this.mapper.getAnalyzerMetadata()?.display_name || this.mapper.getAnalyzerName();
57
+ }
58
+ /**
59
+ * Check if the MCP server is registered in .mcp.json
60
+ *
61
+ * Reads .mcp.json from the project root and checks if the analyzer's MCP server
62
+ * is registered there. This is extracted into a separate method for easier testing.
63
+ *
64
+ * @param projectRoot Absolute path to the project root
65
+ * @returns True if the MCP server is registered, false otherwise
66
+ * @throws CLIError if .mcp.json exists but is unreadable or malformed
67
+ */
68
+ async checkMcpRegistration(projectRoot) {
69
+ try {
70
+ const mcpJsonPath = path.join(projectRoot, ".mcp.json"), mcpContent = await readFile(mcpJsonPath, "utf-8"), mcpConfig = JSON.parse(mcpContent), mcpServerName = this.mapper.getAnalyzerMetadata()?.mcp_server_name ?? "codebase-memory-mcp";
71
+ return mcpConfig.mcpServers && mcpServerName in mcpConfig.mcpServers || !1;
72
+ } catch (error) {
73
+ const err = error;
74
+ if (err.code === "ENOENT")
75
+ return !1;
76
+ throw err.code === "EACCES" ? new CLIError("Permission denied reading .mcp.json", ErrorCategory.SYSTEM, [
77
+ `Cannot read ${path.join(projectRoot, ".mcp.json")}`,
78
+ "Check file permissions"
79
+ ]) : new CLIError("Invalid .mcp.json format", ErrorCategory.VALIDATION, [
80
+ `Check that ${path.join(projectRoot, ".mcp.json")} contains valid JSON`,
81
+ `Error: ${error instanceof Error ? error.message : String(error)}`
82
+ ]);
83
+ }
84
+ }
85
+ /**
86
+ * Detect if the analyzer is installed and functional
87
+ *
88
+ * Checks for the presence of the analyzer binary, reads .mcp.json for registration info,
89
+ * and validates the required tool contract by calling initialize and verifying required_tools.
90
+ *
91
+ * @param projectRoot Optional absolute path to project root (defaults to cwd)
92
+ * @returns Detection result with binary path, version, and MCP registration status
93
+ */
94
+ async detect(projectRoot) {
95
+ const metadata = this.mapper.getAnalyzerMetadata(), binaryNames = metadata?.binary_names ?? [
96
+ "codebase-memory-mcp"
97
+ ];
98
+ let mcpRegistered = !1;
99
+ try {
100
+ mcpRegistered = await this.checkMcpRegistration(projectRoot || process.cwd());
101
+ } catch (error) {
102
+ if (error instanceof CLIError)
103
+ handleWarning(error.message, error.suggestions);
104
+ else {
105
+ const errorMsg = error instanceof Error ? error.message : String(error);
106
+ handleWarning("Unexpected error checking MCP registration", [errorMsg]);
107
+ }
108
+ mcpRegistered = !1;
109
+ }
110
+ for (const binaryName of binaryNames) {
111
+ const whichResult = spawnSync("which", [binaryName], {
112
+ stdio: "pipe",
113
+ encoding: "utf-8"
114
+ });
115
+ if (whichResult.status !== 0)
116
+ continue;
117
+ const binaryPath = whichResult.stdout.trim();
118
+ if (!binaryPath)
119
+ continue;
120
+ const client = new StdioClient();
121
+ try {
122
+ client.spawn(binaryPath);
123
+ const version = await getCliVersion();
124
+ await client.initialize({
125
+ name: "dr-cli",
126
+ version
127
+ });
128
+ let contractOk = !0;
129
+ const requiredTools = metadata?.supported_tool_contract?.required_tools ?? [];
130
+ if (requiredTools.length > 0)
131
+ try {
132
+ const response = await client.callTool("tools/list", {}), availableTools = new Set((response.tools ?? []).map((tool) => tool.name));
133
+ for (const tool of requiredTools)
134
+ availableTools.has(tool) || (contractOk = !1, handleWarning(`Required tool '${tool}' not available in ${binaryName}`, ["Ensure the analyzer server exports all required tools"]));
135
+ } catch (error) {
136
+ contractOk = !1;
137
+ const errorMsg = error instanceof Error ? error.message : String(error);
138
+ handleWarning(`Failed to verify required tools for ${binaryName}: ${errorMsg}`, ["Ensure all required tools are available in the analyzer server"]);
139
+ }
140
+ return {
141
+ installed: !0,
142
+ binary_path: binaryPath,
143
+ version: void 0,
144
+ // Version could be extracted from --version if needed
145
+ mcp_registered: mcpRegistered,
146
+ contract_ok: contractOk
147
+ };
148
+ } catch (error) {
149
+ const errorMessage = error instanceof Error ? error.message : String(error);
150
+ handleWarning(`Binary ${binaryName} failed initialization: ${errorMessage}`, ["Check that the binary is properly installed and functional"]);
151
+ continue;
152
+ } finally {
153
+ try {
154
+ client.close();
155
+ } catch (error) {
156
+ const errorMessage = error instanceof Error ? error.message : String(error);
157
+ handleWarning(`Failed to close MCP client for ${binaryName}: ${errorMessage}`, [
158
+ "The MCP server process may still be running",
159
+ "Try restarting your terminal or manually killing the process"
160
+ ]);
161
+ }
162
+ }
163
+ }
164
+ return {
165
+ installed: !1
166
+ };
167
+ }
168
+ /**
169
+ * Get the current status of the analyzer for a project
170
+ *
171
+ * Checks if the project is indexed and whether the index is fresh relative to git HEAD.
172
+ *
173
+ * @param projectRoot Absolute path to the project root
174
+ * @returns Current status including detection, index state, and freshness
175
+ */
176
+ async status(projectRoot) {
177
+ const detected = await this.detect(projectRoot), analyzerName = this.mapper.getAnalyzerName(), indexMeta = await readIndexMeta(projectRoot, analyzerName);
178
+ if (!(indexMeta !== null))
179
+ return {
180
+ detected,
181
+ indexed: !1,
182
+ fresh: !1
183
+ };
184
+ let fresh = !1;
185
+ try {
186
+ const currentHeadResult = spawnSync("git", ["rev-parse", "HEAD"], {
187
+ cwd: projectRoot,
188
+ stdio: "pipe",
189
+ encoding: "utf-8"
190
+ });
191
+ if (currentHeadResult.status === 0)
192
+ fresh = currentHeadResult.stdout.trim() === indexMeta.git_head;
193
+ else {
194
+ const gitError = currentHeadResult.stderr?.trim() || "git returned non-zero status";
195
+ handleWarning("Failed to check git freshness", [
196
+ `Git error: ${gitError}`,
197
+ "Index freshness cannot be determined - treating as stale"
198
+ ]), fresh = !1;
199
+ }
200
+ } catch (error) {
201
+ const errorMsg = error instanceof Error ? error.message : String(error);
202
+ handleWarning("Failed to spawn git process", [
203
+ `Error: ${errorMsg}`,
204
+ "Index freshness cannot be determined - treating as stale"
205
+ ]), fresh = !1;
206
+ }
207
+ return {
208
+ detected,
209
+ indexed: !0,
210
+ index_meta: indexMeta,
211
+ fresh,
212
+ last_indexed: indexMeta.timestamp
213
+ };
214
+ }
215
+ /**
216
+ * Index a project or update the index
217
+ *
218
+ * Creates a project entry in the CBM backend if it doesn't already exist,
219
+ * indexes the repository, and writes freshness metadata.
220
+ *
221
+ * Skips indexing if the index is fresh (git HEAD matches) unless --force is set.
222
+ *
223
+ * @param projectRoot Absolute path to the project root
224
+ * @param options Optional configuration
225
+ * @returns Index result with counts and metadata
226
+ */
227
+ async index(projectRoot, options) {
228
+ const status = await this.status(projectRoot);
229
+ if (status.indexed && status.fresh && !options?.force) {
230
+ handleWarning("Index is fresh. Use --force to reindex.", [
231
+ `Last indexed: ${status.last_indexed}`
232
+ ]);
233
+ const meta = status.index_meta;
234
+ return {
235
+ node_count: meta.node_count,
236
+ edge_count: meta.edge_count,
237
+ git_head: meta.git_head,
238
+ timestamp: meta.timestamp
239
+ };
240
+ }
241
+ const detection = status.detected;
242
+ if (!detection.installed)
243
+ throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, [
244
+ "Install codebase-memory-mcp: npm install -g codebase-memory-mcp",
245
+ "Ensure the binary is in your PATH"
246
+ ]);
247
+ const client = new StdioClient();
248
+ try {
249
+ client.spawn(detection.binary_path);
250
+ const version = await getCliVersion();
251
+ await client.initialize({
252
+ name: "dr-cli",
253
+ version
254
+ });
255
+ const listProjectsResponse = await client.invokeTool("list_projects");
256
+ if (!listProjectsResponse || typeof listProjectsResponse != "object")
257
+ throw new CLIError("Invalid response from list_projects", ErrorCategory.SYSTEM, ["Ensure codebase-memory-mcp is properly installed and functional"]);
258
+ if ((Array.isArray(listProjectsResponse.projects) ? listProjectsResponse.projects : []).some((p) => p.root_path === projectRoot) && status.fresh && !options?.force) {
259
+ if (handleWarning("Project already indexed in CBM backend with fresh index.", ["Use --force to re-index the project"]), !status.index_meta)
260
+ throw new CLIError("Project exists in CBM backend but local metadata is missing", ErrorCategory.VALIDATION, [
261
+ "Use --force to re-index and regenerate metadata",
262
+ "Or manually delete the project from CBM backend and re-index"
263
+ ]);
264
+ const meta2 = status.index_meta;
265
+ return {
266
+ node_count: meta2.node_count,
267
+ edge_count: meta2.edge_count,
268
+ git_head: meta2.git_head,
269
+ timestamp: meta2.timestamp
270
+ };
271
+ }
272
+ const headResult = spawnSync("git", ["rev-parse", "HEAD"], {
273
+ cwd: projectRoot,
274
+ stdio: "pipe",
275
+ encoding: "utf-8"
276
+ });
277
+ if (headResult.status !== 0)
278
+ throw new CLIError("Failed to get git HEAD", ErrorCategory.SYSTEM, [
279
+ "Ensure the project is a git repository",
280
+ "Verify git is installed and functional",
281
+ `Error: ${headResult.stderr?.trim() || "unknown error"}`
282
+ ]);
283
+ const gitHead = headResult.stdout.trim();
284
+ try {
285
+ const activeChangesetId = await new StagingAreaManager(projectRoot).getActiveId();
286
+ activeChangesetId && console.log(`Indexing with active changeset: ${activeChangesetId}`);
287
+ } catch (error) {
288
+ const errorMsg = error instanceof Error ? error.message : String(error);
289
+ handleWarning("Failed to read active changeset", [`Error: ${errorMsg}`, "Continuing with indexing"]);
290
+ }
291
+ const indexResponse = await client.invokeTool("index_repository", {
292
+ repo_path: projectRoot
293
+ });
294
+ if (!indexResponse || typeof indexResponse != "object")
295
+ throw new CLIError("Invalid response from index_repository", ErrorCategory.SYSTEM, ["Ensure codebase-memory-mcp is properly installed and functional"]);
296
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString(), nodeCount = typeof indexResponse.nodes == "number" ? indexResponse.nodes : 0, edgeCount = typeof indexResponse.edges == "number" ? indexResponse.edges : 0;
297
+ typeof indexResponse.nodes != "number" && handleWarning("Invalid node count from index_repository", [
298
+ `Expected number, got ${typeof indexResponse.nodes}: ${String(indexResponse.nodes)}`,
299
+ "Defaulting to 0 - index may be incomplete or corrupted"
300
+ ]), typeof indexResponse.edges != "number" && handleWarning("Invalid edge count from index_repository", [
301
+ `Expected number, got ${typeof indexResponse.edges}: ${String(indexResponse.edges)}`,
302
+ "Defaulting to 0 - index may be incomplete or corrupted"
303
+ ]);
304
+ const meta = {
305
+ git_head: gitHead,
306
+ timestamp,
307
+ node_count: nodeCount,
308
+ edge_count: edgeCount
309
+ }, analyzerName = this.mapper.getAnalyzerName();
310
+ return await writeIndexMeta(meta, projectRoot, analyzerName), {
311
+ node_count: nodeCount,
312
+ edge_count: edgeCount,
313
+ git_head: gitHead,
314
+ timestamp
315
+ };
316
+ } finally {
317
+ client.close();
318
+ }
319
+ }
320
+ /**
321
+ * Validate search_graph response structure
322
+ *
323
+ * Ensures response is an object and nodes is an array.
324
+ * Throws CLIError if validation fails.
325
+ *
326
+ * @private
327
+ * @param response Raw response from search_graph MCP tool
328
+ * @returns Typed nodes array (may be empty if no results found)
329
+ * @throws CLIError if response structure is invalid
330
+ */
331
+ validateSearchResponse(response) {
332
+ if (!response || typeof response != "object")
333
+ throw new CLIError("Invalid response from search_graph", ErrorCategory.SYSTEM, ["Ensure codebase-memory-mcp is properly installed and functional"]);
334
+ const responseObj = response, nodeArray = responseObj.results ?? responseObj.nodes;
335
+ if (!Array.isArray(nodeArray))
336
+ throw new CLIError("Invalid response from search_graph: results must be an array", ErrorCategory.SYSTEM, [
337
+ `Expected array in 'results' or 'nodes', got ${typeof nodeArray}`,
338
+ "Ensure codebase-memory-mcp is properly installed and returning valid results"
339
+ ]);
340
+ return nodeArray;
341
+ }
342
+ /**
343
+ * Resolve the internal project name used by codebase-memory-mcp for a given root path.
344
+ *
345
+ * list_projects returns { name, root_path } per entry. search_graph and other
346
+ * query tools require the internal `name`, not the absolute `root_path`.
347
+ * Falls back to the absolute path with a warning if the project is not found.
348
+ */
349
+ async resolveProjectName(client, projectRoot) {
350
+ const listResponse = await client.invokeTool("list_projects"), match = (Array.isArray(listResponse?.projects) ? listResponse.projects : []).find((p) => p.root_path === projectRoot);
351
+ return match ? match.name : (handleWarning("Project not found in CBM backend; using absolute path as project identifier", [
352
+ `Project root: ${projectRoot}`,
353
+ "Run `dr analyzer index` to ensure the project is indexed"
354
+ ]), projectRoot);
355
+ }
356
+ /**
357
+ * Query for API endpoints/routes in the indexed project
358
+ *
359
+ * Searches the indexed graph for Route nodes, applies field mappings from
360
+ * the analyzer's mapping, and filters based on test code exclusion rules.
361
+ *
362
+ * @param projectRoot Absolute path to the project root
363
+ * @returns Array of endpoint candidates with confidence and source info
364
+ * @throws CLIError if the project is not indexed
365
+ */
366
+ async endpoints(projectRoot) {
367
+ const status = await this.status(projectRoot);
368
+ if (!status.indexed)
369
+ throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
370
+ "Run `dr analyzer index` to index the project",
371
+ "Ensure codebase-memory-mcp is installed and working"
372
+ ]);
373
+ const detection = status.detected;
374
+ if (!detection.installed)
375
+ throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, ["Install codebase-memory-mcp: npm install -g codebase-memory-mcp"]);
376
+ const routeMapping = this.mapper.getNodeMapping("Route");
377
+ if (!routeMapping)
378
+ throw new CLIError("Route mapping not found in analyzer", ErrorCategory.VALIDATION, ["Run `npm run build:spec` to recompile the analyzer artifacts"]);
379
+ const client = new StdioClient();
380
+ try {
381
+ client.spawn(detection.binary_path);
382
+ const version = await getCliVersion();
383
+ await client.initialize({
384
+ name: "dr-cli",
385
+ version
386
+ });
387
+ const projectName = await this.resolveProjectName(client, projectRoot), searchResponse = await client.invokeTool("search_graph", {
388
+ label: routeMapping.cbm_label,
389
+ project: projectName
390
+ }), nodes = this.validateSearchResponse(searchResponse).filter((n) => /^__route__[A-Z]+__\//.test(String(n.qualified_name ?? ""))).filter((n) => isLikelyHttpRoute(String(n.qualified_name ?? ""))), candidates = [];
391
+ for (const node of nodes) {
392
+ const candidate = await this.transformNodeToEndpoint(node, routeMapping, projectRoot);
393
+ candidate.source_file && (this.isTestCode(candidate) || candidates.push(candidate));
394
+ }
395
+ if (candidates.length === 0)
396
+ try {
397
+ const qResp = await client.invokeTool("query_graph", {
398
+ query: `
399
+ MATCH (n:Function)
400
+ WHERE (n.file_path CONTAINS 'client' OR n.file_path CONTAINS 'Client'
401
+ OR n.file_path CONTAINS 'Api' OR n.file_path CONTAINS 'Service'
402
+ OR n.file_path CONTAINS 'service' OR n.file_path CONTAINS 'http'
403
+ OR n.file_path CONTAINS 'Http')
404
+ AND (n.name STARTS WITH 'fetch' OR n.name STARTS WITH 'get'
405
+ OR n.name STARTS WITH 'post' OR n.name STARTS WITH 'put'
406
+ OR n.name STARTS WITH 'delete' OR n.name STARTS WITH 'patch'
407
+ OR n.name STARTS WITH 'create' OR n.name STARTS WITH 'update'
408
+ OR n.name STARTS WITH 'list')
409
+ RETURN n
410
+ `,
411
+ project: projectName
412
+ });
413
+ if (qResp && typeof qResp == "object") {
414
+ const records = qResp.records ?? [];
415
+ for (const record of records) {
416
+ const node = record.n;
417
+ if (!node || !node.file_path)
418
+ continue;
419
+ const candidate = this.shapeClientSideEndpoint(node, projectRoot);
420
+ candidate && !this.isTestCode(candidate) && candidates.push(candidate);
421
+ }
422
+ }
423
+ } catch {
424
+ }
425
+ return this.warnOnDuplicateIdFragments(candidates, (c) => c.suggested_id_fragment, (c) => c.source_symbol || c.source_file || "(unknown)"), candidates;
426
+ } finally {
427
+ client.close();
428
+ }
429
+ }
430
+ /**
431
+ * Execute a raw Cypher query against the analyzer's graph
432
+ *
433
+ * Passes the raw Cypher string directly to the query_graph MCP tool.
434
+ * The query_graph tool is optional (not in required_tools contract).
435
+ * If the tool call fails, surfaces a clear error message.
436
+ *
437
+ * @param projectRoot Absolute path to the project root
438
+ * @param rawQuery Raw Cypher query string
439
+ * @returns Raw result from query_graph tool
440
+ * @throws CLIError if project not indexed or query_graph tool unavailable
441
+ */
442
+ async query(projectRoot, rawQuery) {
443
+ const status = await this.status(projectRoot);
444
+ if (!status.indexed)
445
+ throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
446
+ "Run `dr analyzer index` to index the project",
447
+ "Ensure codebase-memory-mcp is installed and working"
448
+ ]);
449
+ const detection = status.detected;
450
+ if (!detection.installed)
451
+ throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, ["Install codebase-memory-mcp: npm install -g codebase-memory-mcp"]);
452
+ const client = new StdioClient();
453
+ try {
454
+ client.spawn(detection.binary_path);
455
+ const version = await getCliVersion();
456
+ await client.initialize({
457
+ name: "dr-cli",
458
+ version
459
+ });
460
+ const projectName = await this.resolveProjectName(client, projectRoot);
461
+ try {
462
+ return await client.invokeTool("query_graph", {
463
+ query: rawQuery,
464
+ project: projectName
465
+ });
466
+ } catch (error) {
467
+ const errorMessage = error instanceof Error ? error.message : String(error);
468
+ throw errorMessage.includes("query_graph") || errorMessage.includes("Unknown method") ? new CLIError("query_graph not supported by this analyzer", ErrorCategory.USER, [
469
+ "The query_graph tool is optional in the CBM analyzer contract",
470
+ "Ensure your codebase-memory-mcp version supports raw graph queries",
471
+ "Contact the analyzer maintainers if you need this feature"
472
+ ]) : error;
473
+ }
474
+ } finally {
475
+ client.close();
476
+ }
477
+ }
478
+ warnOnDuplicateIdFragments(items, getKey, getLabel) {
479
+ const seen = /* @__PURE__ */ new Map();
480
+ for (const item of items) {
481
+ const key = getKey(item);
482
+ if (!key)
483
+ continue;
484
+ const label = getLabel(item);
485
+ seen.has(key) ? handleWarning(`Duplicate suggested ID "${key}" \u2014 multiple symbols map to the same element ID`, [
486
+ `First: ${seen.get(key)}`,
487
+ `Conflict: ${label}`,
488
+ 'Rename one or use "dr add" with a custom name'
489
+ ]) : seen.set(key, label);
490
+ }
491
+ }
492
+ /**
493
+ * Transform a CBM graph node to an endpoint candidate
494
+ *
495
+ * Maps CBM Route node properties to DR EndpointCandidate contract:
496
+ * - suggested_layer: always "api" for endpoints
497
+ * - suggested_element_type: always "operation" for routes
498
+ * - suggested_name: from node properties or id (kebab-case)
499
+ * - http_method: from properties.method or defaults to "GET"
500
+ * - http_path: from properties.path or defaults to "/"
501
+ * - handler_qualified_name: from properties.handler_name or empty string
502
+ * - source_symbol: from properties.symbol or empty string
503
+ * - source_start_line: from properties.start_line or 0
504
+ * - source_end_line: from properties.end_line or 0
505
+ *
506
+ * Downgrades confidence if required fields are missing.
507
+ *
508
+ * @private
509
+ */
510
+ async transformNodeToEndpoint(node, mapping, projectRoot) {
511
+ const properties = node.properties ?? {}, nodeId = String(node.name ?? node.id ?? "(unknown)");
512
+ let confidence = mapping.confidence;
513
+ const suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? "")), suggestedIdFragment = suggestedName;
514
+ let rawMethod;
515
+ typeof properties.method == "string" ? rawMethod = properties.method.toUpperCase() : (rawMethod = String(node.qualified_name ?? "").match(/^__route__([A-Z]+)__/)?.[1] ?? "GET", rawMethod === "ANY" && (rawMethod = "GET"));
516
+ const validMethods = new Set(VALID_HTTP_METHODS), isValidMethod = validMethods.has(rawMethod), httpMethod = isValidMethod ? rawMethod : "GET";
517
+ isValidMethod || handleWarning(`Node ${nodeId}: Invalid HTTP method`, [
518
+ `Method '${rawMethod}' is not a valid HTTP method`,
519
+ `Valid methods: ${Array.from(validMethods).join(", ")}`,
520
+ "Defaulting to GET - endpoint documentation may be inaccurate"
521
+ ]);
522
+ let httpPath;
523
+ typeof properties.path == "string" ? httpPath = properties.path : typeof node.name == "string" && node.name.startsWith("/") ? httpPath = node.name : (handleWarning(`Node ${nodeId}: Missing or invalid HTTP path`, [
524
+ `Expected string, got ${typeof properties.path}`,
525
+ "Defaulting to / - endpoint may be incomplete"
526
+ ]), httpPath = "/");
527
+ const handlerQualifiedName = String(properties.handler_name ?? properties.handler_qualified_name ?? ""), sourceSymbol = String(properties.symbol ?? properties.source_symbol ?? ""), sourceStartLine = Number(properties.start_line ?? 0), sourceEndLine = Number(properties.end_line ?? 0);
528
+ (!handlerQualifiedName || !sourceSymbol) && (confidence = capConfidence(confidence));
529
+ let sourceFile = String(node.file_path ?? properties.file_path ?? properties.source_file ?? "");
530
+ if (sourceFile && projectRoot)
531
+ try {
532
+ sourceFile = path.relative(projectRoot, sourceFile);
533
+ } catch (error) {
534
+ const errorMsg = error instanceof Error ? error.message : String(error);
535
+ handleWarning(`Failed to compute relative path for ${nodeId}`, [
536
+ `Error: ${errorMsg}`,
537
+ "Using absolute file path instead of relative"
538
+ ]);
539
+ }
540
+ return {
541
+ source_file: sourceFile,
542
+ confidence,
543
+ suggested_layer: "api",
544
+ suggested_element_type: "operation",
545
+ suggested_name: suggestedName,
546
+ suggested_id_fragment: suggestedIdFragment,
547
+ http_method: httpMethod,
548
+ http_path: httpPath,
549
+ handler_qualified_name: handlerQualifiedName,
550
+ source_symbol: sourceSymbol,
551
+ source_start_line: sourceStartLine,
552
+ source_end_line: sourceEndLine,
553
+ source_reference: {
554
+ provenance: "extracted",
555
+ locations: sourceFile ? [
556
+ {
557
+ file: sourceFile,
558
+ symbol: sourceSymbol || void 0
559
+ }
560
+ ] : void 0
561
+ }
562
+ };
563
+ }
564
+ /**
565
+ * Check if a candidate (endpoint, service, or datastore) is in test code
566
+ *
567
+ * Applies the test_code_exclusion filtering rule from the analyzer mapping.
568
+ * If the configured regex pattern is invalid, warns the user once and falls back to defaults.
569
+ * Uses a one-shot flag to ensure the warning is only emitted once even if called in a loop.
570
+ *
571
+ * Accepts any object with a source_file property (structural typing), allowing it to work
572
+ * with EndpointCandidate, ServiceCandidate, DatastoreCandidate, and temporary objects.
573
+ *
574
+ * @private
575
+ */
576
+ /**
577
+ * Build a low-confidence EndpointCandidate from a client-side API function node.
578
+ * Used as a fallback when no server-side Route nodes are found (ISSUE-003).
579
+ */
580
+ shapeClientSideEndpoint(node, projectRoot) {
581
+ const name = String(node.name ?? node.id ?? "");
582
+ if (!name)
583
+ return null;
584
+ let sourceFile = String(node.file_path ?? "");
585
+ if (sourceFile && projectRoot)
586
+ try {
587
+ sourceFile = path.relative(projectRoot, sourceFile);
588
+ } catch {
589
+ }
590
+ if (!sourceFile)
591
+ return null;
592
+ const httpMethod = inferHttpMethodFromFunctionName(name), httpPath = inferHttpPathFromFunctionName(name), suggestedName = toKebabCase(name);
593
+ return {
594
+ source_file: sourceFile,
595
+ confidence: "low",
596
+ suggested_layer: "api",
597
+ suggested_element_type: "operation",
598
+ suggested_name: suggestedName,
599
+ suggested_id_fragment: suggestedName,
600
+ http_method: httpMethod,
601
+ http_path: httpPath,
602
+ handler_qualified_name: name,
603
+ source_symbol: name,
604
+ source_start_line: Number(node.properties?.start_line ?? 0),
605
+ source_end_line: Number(node.properties?.end_line ?? 0),
606
+ source_reference: {
607
+ provenance: "inferred",
608
+ locations: [{ file: sourceFile, symbol: name }]
609
+ }
610
+ };
611
+ }
612
+ isTestCode(candidate) {
613
+ const testCodeRule = this.mapper.getFilteringRules().find((rule) => rule.name === "test_code_exclusion" && rule.enabled);
614
+ if (!testCodeRule || !testCodeRule.pattern)
615
+ return CbmAnalyzer.DEFAULT_TEST_PATTERNS.some((pattern) => pattern.test(candidate.source_file));
616
+ try {
617
+ return new RegExp(testCodeRule.pattern).test(candidate.source_file);
618
+ } catch (error) {
619
+ if (!this.testCodePatternWarned) {
620
+ const errorMsg = error instanceof Error ? error.message : String(error);
621
+ handleWarning("Invalid test_code_exclusion regex pattern in analyzer mapping", [
622
+ `Pattern: ${testCodeRule.pattern}`,
623
+ `Error: ${errorMsg}`,
624
+ "Falling back to default test code patterns"
625
+ ]), this.testCodePatternWarned = !0;
626
+ }
627
+ return CbmAnalyzer.DEFAULT_TEST_PATTERNS.some((pattern) => pattern.test(candidate.source_file));
628
+ }
629
+ }
630
+ /**
631
+ * Query for services/components in the indexed project
632
+ *
633
+ * Searches for application-layer nodes using the mapping-loader-driven pattern.
634
+ * For each node, evaluates all promotion_heuristics and records which fire in
635
+ * qualifying_heuristics. Drops candidates with zero qualifying heuristics (unless
636
+ * is_entry_point heuristic fires, adding it to qualifying_heuristics). Caps confidence
637
+ * at "medium" and excludes test files.
638
+ *
639
+ * @param projectRoot Absolute path to the project root
640
+ * @returns Array of service candidates with qualifying heuristics populated
641
+ * @throws CLIError if the project is not indexed
642
+ */
643
+ async services(projectRoot) {
644
+ const status = await this.status(projectRoot);
645
+ if (!status.indexed)
646
+ throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
647
+ "Run `dr analyzer index` to index the project",
648
+ "Ensure codebase-memory-mcp is installed and working"
649
+ ]);
650
+ const detection = status.detected;
651
+ if (!detection.installed)
652
+ throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, ["Install codebase-memory-mcp: npm install -g codebase-memory-mcp"]);
653
+ const client = new StdioClient();
654
+ try {
655
+ client.spawn(detection.binary_path);
656
+ const version = await getCliVersion();
657
+ await client.initialize({
658
+ name: "dr-cli",
659
+ version
660
+ });
661
+ const nodeLabels = this.mapper.getNodeLabels(), applicationLabels = [];
662
+ for (const label of nodeLabels) {
663
+ const mapping = this.mapper.getNodeMapping(label);
664
+ mapping && mapping.dr_layer === "application" && applicationLabels.push(label);
665
+ }
666
+ if (applicationLabels.length === 0)
667
+ return handleWarning("No application-layer labels found in analyzer mapping", [
668
+ "Check that the analyzer is properly configured with application-layer node mappings",
669
+ "Services detection requires at least one mapped application-layer node type",
670
+ "Returning empty services list"
671
+ ]), [];
672
+ const candidates = [], projectName = await this.resolveProjectName(client, projectRoot), labelPriority = { Class: 0, Module: 1 };
673
+ applicationLabels.sort((a, b) => (labelPriority[a] ?? 99) - (labelPriority[b] ?? 99));
674
+ for (const label of applicationLabels) {
675
+ const searchResponse = await client.invokeTool("search_graph", {
676
+ label,
677
+ project: projectName
678
+ }), nodes = this.validateSearchResponse(searchResponse), nodeMapping = this.mapper.getNodeMapping(label);
679
+ if (!nodeMapping)
680
+ continue;
681
+ const promotionHeuristicNames = nodeMapping.promotion_heuristics || [];
682
+ for (const node of nodes) {
683
+ const candidate = await this.transformNodeToService(node, nodeMapping, projectRoot, promotionHeuristicNames);
684
+ candidate.qualifying_heuristics.length > 0 && !this.isTestCode(candidate) && candidates.push(candidate);
685
+ }
686
+ }
687
+ const seenFiles = /* @__PURE__ */ new Set(), deduped = candidates.filter((c) => c.source_file ? seenFiles.has(c.source_file) ? !1 : (seenFiles.add(c.source_file), !0) : !0);
688
+ return this.warnOnDuplicateIdFragments(deduped, (c) => c.suggested_id_fragment, (c) => c.source_symbol || c.source_file || "(unknown)"), deduped;
689
+ } finally {
690
+ client.close();
691
+ }
692
+ }
693
+ /**
694
+ * Transform a CBM graph node to a service candidate
695
+ *
696
+ * Evaluates all promotion heuristics against the node's properties and records
697
+ * which heuristics fire. Drops candidates where no heuristics fire (unless the
698
+ * is_entry_point heuristic fires, which adds it to qualifying_heuristics).
699
+ * Caps confidence at "medium".
700
+ *
701
+ * @private
702
+ */
703
+ async transformNodeToService(node, mapping, projectRoot, promotionHeuristicNames) {
704
+ const properties = node.properties ?? {}, suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? "")), suggestedIdFragment = suggestedName;
705
+ let sourceFile = node.file_path ?? "";
706
+ if (sourceFile && projectRoot)
707
+ try {
708
+ sourceFile = path.relative(projectRoot, sourceFile);
709
+ } catch (error) {
710
+ const errorMsg = error instanceof Error ? error.message : String(error);
711
+ handleWarning(`Failed to compute relative path for ${String(node.name ?? node.id ?? "(unknown)")}`, [
712
+ `Error: ${errorMsg}`,
713
+ "Using absolute file path instead of relative"
714
+ ]);
715
+ }
716
+ const qualifiedName = String(properties.qualified_name ?? node.qualified_name ?? node.name ?? node.id ?? ""), fanIn = typeof properties.fan_in == "number" ? properties.fan_in : 0, fanOut = typeof properties.fan_out == "number" ? properties.fan_out : 0, qualifyingHeuristics = [];
717
+ let isEntryPoint = !1;
718
+ for (const heuristicName of promotionHeuristicNames) {
719
+ const heuristic = this.mapper.getHeuristic(heuristicName);
720
+ if (!heuristic) {
721
+ this.warnedHeuristics.has(heuristicName) || (this.warnedHeuristics.add(heuristicName), handleWarning(`Promotion heuristic "${heuristicName}" not found in analyzer mapping`, [
722
+ "Check the mapping configuration for typos",
723
+ `Available heuristics: ${getKnownHeuristicNames().join(", ")}`,
724
+ "This heuristic will be skipped for service promotion"
725
+ ]));
726
+ continue;
727
+ }
728
+ this.evaluateHeuristic(heuristic, node, sourceFile) && (qualifyingHeuristics.push(heuristicName), heuristicName === "is_entry_point" && (isEntryPoint = !0));
729
+ }
730
+ if (!(qualifyingHeuristics.length > 0 || isEntryPoint))
731
+ return {
732
+ suggested_layer: "application",
733
+ suggested_element_type: mapping.dr_element_type_promoted === "applicationcomponent" ? "applicationcomponent" : "applicationservice",
734
+ suggested_id_fragment: suggestedIdFragment,
735
+ suggested_name: suggestedName,
736
+ source_file: sourceFile,
737
+ source_symbol: String(properties.name ?? node.name ?? node.id ?? ""),
738
+ qualified_name: qualifiedName,
739
+ qualifying_heuristics: [],
740
+ confidence: "low",
741
+ fan_in: fanIn,
742
+ fan_out: fanOut
743
+ };
744
+ const confidence = capConfidence(mapping.confidence);
745
+ return {
746
+ suggested_layer: "application",
747
+ suggested_element_type: mapping.dr_element_type_promoted === "applicationcomponent" ? "applicationcomponent" : "applicationservice",
748
+ suggested_id_fragment: suggestedIdFragment,
749
+ suggested_name: suggestedName,
750
+ source_file: sourceFile,
751
+ source_symbol: String(properties.name ?? node.name ?? node.id ?? ""),
752
+ qualified_name: qualifiedName,
753
+ qualifying_heuristics: qualifyingHeuristics,
754
+ confidence,
755
+ fan_in: fanIn,
756
+ fan_out: fanOut
757
+ };
758
+ }
759
+ /**
760
+ * Evaluate a single heuristic against a node
761
+ *
762
+ * Checks if the heuristic's conditions are met based on the node's properties
763
+ * and file path. Unknown heuristics are logged with a warning rather than
764
+ * silently ignored, to support dynamic heuristics from cbm.json.
765
+ *
766
+ * Warnings for unknown heuristics are deduplicated - each heuristic name is warned
767
+ * about only once, even if evaluated against many nodes.
768
+ *
769
+ * @private
770
+ */
771
+ evaluateHeuristic(heuristic, node, sourceFile) {
772
+ const properties = node.properties ?? {}, result = evaluateHeuristic(heuristic, properties, sourceFile);
773
+ return heuristic.name && !getKnownHeuristicNames().includes(heuristic.name) && (this.warnedHeuristics.has(heuristic.name) || (handleWarning(`Unknown heuristic type: ${heuristic.name}`, [
774
+ "The heuristic is defined in cbm.json but not implemented in heuristic-utils.ts",
775
+ "This heuristic will be treated as not met (returning false)",
776
+ "Add support for this heuristic to evaluateHeuristic() if it should fire"
777
+ ]), this.warnedHeuristics.add(heuristic.name))), result;
778
+ }
779
+ /**
780
+ * Match a file path against a glob-like pattern
781
+ *
782
+ * @param filePath The file path to match
783
+ * @param pattern The glob pattern
784
+ * @returns True if the pattern matches, false otherwise
785
+ * @private
786
+ */
787
+ matchPattern(filePath, pattern) {
788
+ return matchPattern(filePath, pattern);
789
+ }
790
+ /**
791
+ * Query for datastores/databases inferred from code analysis
792
+ *
793
+ * Applies datastore_detection heuristic rules to aggregate signals by file/module.
794
+ * Matches import target names against import_patterns and function/symbol names against
795
+ * naming_indicators from the heuristic parameters. All candidates have confidence "low".
796
+ *
797
+ * @param projectRoot Absolute path to the project root
798
+ * @returns Array of datastore candidates aggregated by inferred datastore
799
+ * @throws CLIError if the project is not indexed
800
+ */
801
+ async datastores(projectRoot) {
802
+ const status = await this.status(projectRoot);
803
+ if (!status.indexed)
804
+ throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
805
+ "Run `dr analyzer index` to index the project",
806
+ "Ensure codebase-memory-mcp is installed and working"
807
+ ]);
808
+ const detection = status.detected;
809
+ if (!detection.installed)
810
+ throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, ["Install codebase-memory-mcp: npm install -g codebase-memory-mcp"]);
811
+ const client = new StdioClient();
812
+ try {
813
+ client.spawn(detection.binary_path);
814
+ const version = await getCliVersion();
815
+ await client.initialize({
816
+ name: "dr-cli",
817
+ version
818
+ });
819
+ const datastoreHeuristic = this.mapper.getHeuristic("datastore_detection");
820
+ if (!datastoreHeuristic)
821
+ throw new CLIError("datastore_detection heuristic not found in analyzer", ErrorCategory.VALIDATION, [
822
+ "The analyzer configuration is missing the datastore_detection heuristic",
823
+ "Run `npm run build:spec` to recompile the analyzer artifacts"
824
+ ]);
825
+ const importPatterns = datastoreHeuristic.parameters?.patterns ?? [], namingIndicators = datastoreHeuristic.parameters?.naming_indicators ?? [], datastoreSignals = /* @__PURE__ */ new Map(), datastoreFirstSource = /* @__PURE__ */ new Map(), projectName = await this.resolveProjectName(client, projectRoot);
826
+ let allNodes = [];
827
+ try {
828
+ const queryResponse = await client.invokeTool("query_graph", {
829
+ query: `
830
+ MATCH (n1)-[r:IMPORTS]->(n2)
831
+ RETURN DISTINCT n1 as node
832
+ UNION
833
+ MATCH (n1)-[r:IMPORTS]->(n2)
834
+ RETURN DISTINCT n2 as node
835
+ `,
836
+ project: projectName
837
+ });
838
+ queryResponse && typeof queryResponse == "object" && Array.isArray(queryResponse.records) && (allNodes = (queryResponse.records || []).map((r) => r.node).filter((n) => n && typeof n == "object"));
839
+ } catch (error) {
840
+ const errorMsg = error instanceof Error ? error.message : String(error);
841
+ handleWarning("query_graph tool failed; falling back to search_graph for datastore detection", [
842
+ `Error: ${errorMsg}`,
843
+ "Using search_graph will return ALL nodes instead of only IMPORTS-connected nodes",
844
+ "Datastore detection results may include false positives or be less accurate",
845
+ "Ensure query_graph is supported by your analyzer version for best results"
846
+ ]);
847
+ const nodeLabels = this.mapper.getNodeLabels();
848
+ for (const label of nodeLabels) {
849
+ const searchResponse = await client.invokeTool("search_graph", {
850
+ label,
851
+ project: projectName
852
+ }), nodes = this.validateSearchResponse(searchResponse);
853
+ allNodes.push(...nodes);
854
+ }
855
+ }
856
+ for (const node of allNodes) {
857
+ const filePath = node.file_path ?? "", importTargetName = String(node.label ?? node.properties?.name ?? node.id ?? "").toLowerCase();
858
+ let matchedImportPattern;
859
+ for (const pattern of importPatterns)
860
+ if (this.matchPattern(importTargetName, pattern)) {
861
+ matchedImportPattern = pattern;
862
+ break;
863
+ }
864
+ const nodeName = String(node.properties?.name ?? "").toLowerCase(), matchedIndicators = namingIndicators.filter((indicator) => nodeName.includes(indicator.toLowerCase()));
865
+ if (!matchedImportPattern && matchedIndicators.length === 0)
866
+ continue;
867
+ let datastoreName = this.inferDatastoreName(filePath, node, matchedImportPattern, importTargetName);
868
+ if (!datastoreName)
869
+ continue;
870
+ datastoreName = toKebabCase(datastoreName), datastoreSignals.has(datastoreName) ? datastoreFirstSource.get(datastoreName) !== importTargetName && handleWarning(`Multiple imports normalize to datastore name "${datastoreName}"`, [
871
+ `First: ${datastoreFirstSource.get(datastoreName)}`,
872
+ `Conflict: ${importTargetName}`,
873
+ 'Use "dr add" with a custom name to distinguish them'
874
+ ]) : (datastoreSignals.set(datastoreName, []), datastoreFirstSource.set(datastoreName, importTargetName));
875
+ const signals = datastoreSignals.get(datastoreName);
876
+ let relativeFile = filePath;
877
+ if (filePath && projectRoot)
878
+ try {
879
+ relativeFile = path.relative(projectRoot, filePath);
880
+ } catch (error) {
881
+ const errorMsg = error instanceof Error ? error.message : String(error);
882
+ handleWarning("Failed to compute relative path in datastore detection", [
883
+ `File: ${filePath}`,
884
+ `Error: ${errorMsg}`,
885
+ "Using absolute file path instead of relative"
886
+ ]);
887
+ }
888
+ signals.push({
889
+ sourceFile: relativeFile,
890
+ importPattern: matchedImportPattern,
891
+ functionPatterns: matchedIndicators
892
+ });
893
+ }
894
+ const candidates = [], entriesArray = Array.from(datastoreSignals.entries());
895
+ for (const [datastoreName, signals] of entriesArray) {
896
+ const inferredFromMap = /* @__PURE__ */ new Map();
897
+ for (const signal of signals) {
898
+ inferredFromMap.has(signal.sourceFile) || inferredFromMap.set(signal.sourceFile, {
899
+ importPatterns: /* @__PURE__ */ new Set(),
900
+ functionPatterns: /* @__PURE__ */ new Set()
901
+ });
902
+ const entry = inferredFromMap.get(signal.sourceFile);
903
+ signal.importPattern && entry.importPatterns.add(signal.importPattern), signal.functionPatterns.forEach((fp) => entry.functionPatterns.add(fp));
904
+ }
905
+ let inferredFromMapEntries = Array.from(inferredFromMap.entries());
906
+ if (inferredFromMapEntries = inferredFromMapEntries.filter(([sourceFile]) => {
907
+ const tempCandidate = {
908
+ source_file: sourceFile
909
+ };
910
+ return !this.isTestCode(tempCandidate);
911
+ }), inferredFromMapEntries.length === 0)
912
+ continue;
913
+ const inferredFrom = inferredFromMapEntries.map(([sourceFile, evidence]) => ({
914
+ source_file: sourceFile,
915
+ import_pattern: Array.from(evidence.importPatterns).join(", ") || datastoreName,
916
+ function_patterns: Array.from(evidence.functionPatterns)
917
+ }));
918
+ candidates.push({
919
+ suggested_layer: "data-store",
920
+ suggested_name: datastoreName,
921
+ inferred_from: inferredFrom,
922
+ confidence: "low",
923
+ notes: `Inferred from ${inferredFrom.length} source file(s) based on datastore detection heuristics`
924
+ });
925
+ }
926
+ const browserStorageTypes = ["local-storage", "session-storage"], browserStorageSignals = /* @__PURE__ */ new Map();
927
+ try {
928
+ const storageResp = await client.invokeTool("query_graph", {
929
+ query: `
930
+ MATCH (n:Function)
931
+ WHERE n.file_path CONTAINS 'Store' OR n.file_path CONTAINS 'store'
932
+ OR n.file_path CONTAINS 'storage' OR n.file_path CONTAINS 'Storage'
933
+ OR n.file_path CONTAINS 'auth' OR n.file_path CONTAINS 'Auth'
934
+ OR n.file_path CONTAINS 'preference' OR n.file_path CONTAINS 'Preference'
935
+ OR n.file_path CONTAINS 'layout' OR n.file_path CONTAINS 'Layout'
936
+ OR n.file_path CONTAINS 'settings' OR n.file_path CONTAINS 'Settings'
937
+ RETURN n
938
+ `,
939
+ project: projectName
940
+ });
941
+ if (storageResp && typeof storageResp == "object") {
942
+ const records = storageResp.records ?? [];
943
+ for (const record of records) {
944
+ const node = record.n;
945
+ if (!node?.file_path)
946
+ continue;
947
+ let relFile = node.file_path;
948
+ try {
949
+ relFile = path.relative(projectRoot, node.file_path);
950
+ } catch {
951
+ }
952
+ const storageKey = /session/i.test(relFile) ? "session-storage" : "local-storage";
953
+ browserStorageSignals.has(storageKey) || browserStorageSignals.set(storageKey, /* @__PURE__ */ new Set()), browserStorageSignals.get(storageKey).add(relFile);
954
+ }
955
+ }
956
+ } catch {
957
+ }
958
+ for (const storageType of browserStorageTypes) {
959
+ const sourceFiles = browserStorageSignals.get(storageType);
960
+ if (!sourceFiles || sourceFiles.size === 0)
961
+ continue;
962
+ const accessPattern = storageType === "local-storage" ? "localStorage" : "sessionStorage", inferredFrom = Array.from(sourceFiles).filter((f) => !this.isTestCode({ source_file: f })).map((f) => ({
963
+ source_file: f,
964
+ import_pattern: accessPattern,
965
+ function_patterns: []
966
+ }));
967
+ inferredFrom.length > 0 && candidates.push({
968
+ suggested_layer: "data-store",
969
+ suggested_name: storageType,
970
+ inferred_from: inferredFrom,
971
+ confidence: "low",
972
+ notes: `Inferred from ${inferredFrom.length} source file(s) using browser ${accessPattern} (global API, no import required)`
973
+ });
974
+ }
975
+ return candidates;
976
+ } finally {
977
+ client.close();
978
+ }
979
+ }
980
+ /**
981
+ * Infer a datastore name from import target name, file path, node properties, or import pattern
982
+ *
983
+ * @private
984
+ */
985
+ inferDatastoreName(filePath, node, matchedPattern, importTargetName) {
986
+ if (importTargetName && matchedPattern && this.matchPattern(importTargetName, matchedPattern))
987
+ return importTargetName;
988
+ if (matchedPattern) {
989
+ const match = matchedPattern.match(/[a-z]+/i);
990
+ if (match)
991
+ return match[0];
992
+ }
993
+ const fileName = path.basename(filePath);
994
+ if (fileName.includes("migration") || fileName.includes("schema")) {
995
+ const match = fileName.match(/^([a-z0-9_-]+)/i);
996
+ if (match && match[1])
997
+ return match[1];
998
+ }
999
+ const nodeName = String(node.label ?? node.id ?? "");
1000
+ if (nodeName) {
1001
+ const nodeMapping = this.mapper.getNodeMapping(nodeName);
1002
+ if (nodeMapping && nodeMapping.dr_layer !== "api" && nodeMapping.dr_layer !== "application" || nodeName.toLowerCase().includes("database") || nodeName.toLowerCase().includes("repository") || nodeName.toLowerCase().includes("datastore"))
1003
+ return nodeName;
1004
+ }
1005
+ }
1006
+ /**
1007
+ * Query for callers of a specific function or symbol
1008
+ *
1009
+ * Delegates to trace_path MCP tool with direction="inbound" to find
1010
+ * all functions that call the specified qualified name. Applies depth clamping
1011
+ * (default 3, max 10) and transforms CBM response nodes into CallGraphNode objects
1012
+ * using edge-type mappings from the analyzer.
1013
+ *
1014
+ * @param projectRoot Absolute path to the project root
1015
+ * @param qualifiedName Fully qualified symbol name (e.g., "com.example.UserService.getUser")
1016
+ * @param depth Maximum traversal depth (default 3, clamped to max 10)
1017
+ * @returns Array of callers in the call graph
1018
+ * @throws CLIError if project not indexed or analyzer not installed
1019
+ */
1020
+ async callers(projectRoot, qualifiedName, depth) {
1021
+ return this.traceCallPath(projectRoot, qualifiedName, depth, "inbound");
1022
+ }
1023
+ /**
1024
+ * Query for callees of a specific function or symbol
1025
+ *
1026
+ * Delegates to trace_path MCP tool with direction="outbound" to find
1027
+ * all functions called by the specified qualified name. Applies depth clamping
1028
+ * (default 3, max 10) and transforms CBM response nodes into CallGraphNode objects
1029
+ * using edge-type mappings from the analyzer.
1030
+ *
1031
+ * @param projectRoot Absolute path to the project root
1032
+ * @param qualifiedName Fully qualified symbol name (e.g., "com.example.UserService.getUser")
1033
+ * @param depth Maximum traversal depth (default 3, clamped to max 10)
1034
+ * @returns Array of callees in the call graph
1035
+ * @throws CLIError if project not indexed or analyzer not installed
1036
+ */
1037
+ async callees(projectRoot, qualifiedName, depth) {
1038
+ return this.traceCallPath(projectRoot, qualifiedName, depth, "outbound");
1039
+ }
1040
+ /**
1041
+ * Cypher fallback for call graph traversal when trace_path is unavailable.
1042
+ *
1043
+ * Uses query_graph directly with CALLS-edge Cypher when trace_path returns a
1044
+ * non-array nodes field (i.e. is not implemented in the binary).
1045
+ *
1046
+ * @private
1047
+ */
1048
+ async callGraphViaCypher(binaryPath, projectName, qualifiedName, direction, depth, projectRoot) {
1049
+ const safeQN = qualifiedName.replace(/'/g, "''"), limit = Math.min(depth * 20, 100), cypher = direction === "inbound" ? `MATCH (caller)-[:CALLS]->(target) WHERE target.qualified_name = '${safeQN}' OR target.name = '${safeQN}' RETURN caller.id AS id, caller.qualified_name AS qualified_name, caller.file_path AS file_path LIMIT ${limit}` : `MATCH (source)-[:CALLS]->(callee) WHERE source.qualified_name = '${safeQN}' OR source.name = '${safeQN}' RETURN callee.id AS id, callee.qualified_name AS qualified_name, callee.file_path AS file_path LIMIT ${limit}`;
1050
+ depth > 1 && handleWarning(`Cypher CALLS fallback: depth ${depth} requested but fallback only traverses 1 hop`, ["Multi-hop traversal requires trace_path support in the analyzer binary"]);
1051
+ const client = new StdioClient();
1052
+ try {
1053
+ client.spawn(binaryPath);
1054
+ const version = await getCliVersion();
1055
+ await client.initialize({ name: "dr-cli", version });
1056
+ const queryResponse = await client.invokeTool("query_graph", {
1057
+ query: cypher,
1058
+ project: projectName
1059
+ }), cols = queryResponse?.columns ?? [];
1060
+ return (queryResponse?.rows ?? []).map((row) => {
1061
+ const obj = {};
1062
+ return cols.forEach((col, i) => {
1063
+ const key = col.includes(".") ? col.slice(col.lastIndexOf(".") + 1) : col;
1064
+ obj[key] = row[i];
1065
+ }), shapeCallGraphNode(obj, projectRoot);
1066
+ });
1067
+ } catch (error) {
1068
+ const errorMsg = error instanceof Error ? error.message : String(error);
1069
+ return handleWarning("Cypher CALLS fallback failed \u2014 returning empty call graph", [
1070
+ `Error: ${errorMsg}`,
1071
+ "Neither trace_path nor query_graph is available for call graph traversal"
1072
+ ]), [];
1073
+ } finally {
1074
+ client.close();
1075
+ }
1076
+ }
1077
+ /**
1078
+ * Internal helper to trace call paths in either direction
1079
+ *
1080
+ * Handles common logic for callers() and callees(): pre-flight checks,
1081
+ * depth clamping, tool invocation, and response transformation.
1082
+ *
1083
+ * @private
1084
+ */
1085
+ async traceCallPath(projectRoot, qualifiedName, depth, direction) {
1086
+ const status = await this.status(projectRoot);
1087
+ if (!status.indexed)
1088
+ throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
1089
+ "Run `dr analyzer index` to index the project",
1090
+ "Ensure codebase-memory-mcp is installed and working"
1091
+ ]);
1092
+ const detection = status.detected;
1093
+ if (!detection.installed)
1094
+ throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, ["Install codebase-memory-mcp: npm install -g codebase-memory-mcp"]);
1095
+ const clampedDepth = clampDepth(depth), client = new StdioClient();
1096
+ try {
1097
+ client.spawn(detection.binary_path);
1098
+ const version = await getCliVersion();
1099
+ await client.initialize({
1100
+ name: "dr-cli",
1101
+ version
1102
+ });
1103
+ const projectName = await this.resolveProjectName(client, projectRoot), traceResponse = await client.invokeTool("trace_path", {
1104
+ function_name: qualifiedName,
1105
+ direction,
1106
+ depth: clampedDepth,
1107
+ project: projectName
1108
+ });
1109
+ if (!traceResponse || typeof traceResponse != "object")
1110
+ throw new CLIError("Invalid response from trace_path", ErrorCategory.SYSTEM, ["Ensure codebase-memory-mcp is properly installed and functional"]);
1111
+ if (!Array.isArray(traceResponse.nodes))
1112
+ return this.callGraphViaCypher(detection.binary_path, projectName, qualifiedName, direction, clampedDepth, projectRoot);
1113
+ if (!Array.isArray(traceResponse.edges))
1114
+ return handleWarning("trace_path returned no edges array \u2014 returning empty call graph", [
1115
+ `Expected array, got ${typeof traceResponse.edges}`,
1116
+ "Ensure codebase-memory-mcp supports trace_path and the project is indexed"
1117
+ ]), [];
1118
+ const nodes = traceResponse.nodes, edges = traceResponse.edges, validEdgeTypes = this.mapper.getEdgeTypes(), callGraphNodes = [];
1119
+ for (const node of nodes) {
1120
+ const nodeQualifiedName = node.qualified_name || node.id;
1121
+ let callGraphNode = shapeCallGraphNode(node, projectRoot);
1122
+ const nodeDepth = typeof node.depth == "number" ? node.depth : 0, edgeType = determineEdgeType(nodeDepth, edges, nodeQualifiedName, validEdgeTypes);
1123
+ callGraphNode.edge_type = edgeType, callGraphNodes.push(callGraphNode);
1124
+ }
1125
+ return callGraphNodes;
1126
+ } finally {
1127
+ client.close();
1128
+ }
1129
+ }
1130
+ /**
1131
+ * Shape a single CBM graph node into a route object with defaults applied
1132
+ *
1133
+ * This is extracted to be testable - applies the defaulting logic used in verify()
1134
+ * to ensure empty/missing properties are handled consistently.
1135
+ *
1136
+ * @param node Raw node from CBM graph search
1137
+ * @returns Shaped route object with defaults applied
1138
+ * @internal
1139
+ */
1140
+ shapeRoute(node) {
1141
+ const properties = node.properties ?? {}, nodeName = String(node.name ?? node.id ?? "");
1142
+ let httpMethod = "GET";
1143
+ if (typeof properties.method == "string")
1144
+ httpMethod = properties.method.toUpperCase();
1145
+ else {
1146
+ const extracted = String(node.qualified_name ?? "").match(/^__route__([A-Z]+)__/)?.[1] ?? "";
1147
+ extracted && VALID_HTTP_METHODS.includes(extracted) && (httpMethod = extracted);
1148
+ }
1149
+ const httpPath = typeof properties.path == "string" ? properties.path : typeof node.name == "string" && node.name.startsWith("/") ? node.name : "/", handler = String(properties.handler_name ?? ""), sourceFile = node.file_path ?? "", sourceSymbol = String(properties.symbol ?? properties.source_symbol ?? properties.handler_name ?? "");
1150
+ return {
1151
+ id: sourceFile && sourceSymbol ? `${sourceFile}:${sourceSymbol}` : nodeName || node.id || "(unknown)",
1152
+ http_method: httpMethod,
1153
+ http_path: httpPath,
1154
+ handler,
1155
+ source_file: sourceFile,
1156
+ source_symbol: sourceSymbol
1157
+ };
1158
+ }
1159
+ /**
1160
+ * Verify that graph-discovered routes align with model endpoints
1161
+ *
1162
+ * Queries the CBM graph for Route nodes, shapes them via the Route mapping,
1163
+ * and delegates comparison to VerifyEngine. Entirely read-only.
1164
+ *
1165
+ * @param projectRoot Absolute path to the project root
1166
+ * @param options Verification options
1167
+ * @returns Comprehensive verification report
1168
+ * @throws CLIError if project not indexed or analyzer not installed
1169
+ */
1170
+ async verify(projectRoot, options) {
1171
+ const status = await this.status(projectRoot);
1172
+ if (!status.indexed)
1173
+ throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
1174
+ "Run `dr analyzer index` to index the project",
1175
+ "Ensure codebase-memory-mcp is installed and working"
1176
+ ]);
1177
+ const detection = status.detected;
1178
+ if (!detection.installed)
1179
+ throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, ["Install codebase-memory-mcp: npm install -g codebase-memory-mcp"]);
1180
+ const routeMapping = this.mapper.getNodeMapping("Route");
1181
+ if (!routeMapping)
1182
+ throw new CLIError("Route mapping not found in analyzer", ErrorCategory.VALIDATION, ["Run `npm run build:spec` to recompile the analyzer artifacts"]);
1183
+ const client = new StdioClient();
1184
+ try {
1185
+ client.spawn(detection.binary_path);
1186
+ const version = await getCliVersion();
1187
+ await client.initialize({
1188
+ name: "dr-cli",
1189
+ version
1190
+ });
1191
+ const projectName = await this.resolveProjectName(client, projectRoot), searchResponse = await client.invokeTool("search_graph", {
1192
+ label: routeMapping.cbm_label,
1193
+ project: projectName
1194
+ }), routes = this.validateSearchResponse(searchResponse).filter((n) => /^__route__[A-Z]+__\//.test(String(n.qualified_name ?? ""))).filter((n) => isLikelyHttpRoute(String(n.qualified_name ?? ""))).map((node) => this.shapeRoute(node)).filter((r) => r.source_file), engine = new VerifyEngine(), indexMeta = status.index_meta, analyzerName = this.name;
1195
+ return await engine.computeReport(projectRoot, routes, options, analyzerName, indexMeta);
1196
+ } finally {
1197
+ client.close();
1198
+ }
1199
+ }
1200
+ }
1201
+ export {
1202
+ CbmAnalyzer,
1203
+ SOURCE_CODE_EXT_RE
1204
+ };
1205
+ //# sourceMappingURL=cbm-analyzer.js.map