@documentation-robotics/cli 0.1.13 → 0.3.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 (351) hide show
  1. package/README.md +74 -2
  2. package/dist/analysis/relationship-classifier.js +2 -9
  3. package/dist/analysis/relationship-classifier.js.map +2 -2
  4. package/dist/analyzers/call-graph-utils.js +2 -2
  5. package/dist/analyzers/call-graph-utils.js.map +2 -2
  6. package/dist/analyzers/cards/build.js +171 -0
  7. package/dist/analyzers/cards/build.js.map +7 -0
  8. package/dist/analyzers/cards/classify.js +42 -0
  9. package/dist/analyzers/cards/classify.js.map +7 -0
  10. package/dist/analyzers/cards/edge-index.js +44 -0
  11. package/dist/analyzers/cards/edge-index.js.map +7 -0
  12. package/dist/analyzers/cards/facts.js +46 -0
  13. package/dist/analyzers/cards/facts.js.map +7 -0
  14. package/dist/analyzers/cards/graph.js +42 -0
  15. package/dist/analyzers/cards/graph.js.map +7 -0
  16. package/dist/analyzers/cards/index.js +8 -0
  17. package/dist/analyzers/cards/index.js.map +7 -0
  18. package/dist/analyzers/cards/tier0.js +18 -0
  19. package/dist/analyzers/cards/tier0.js.map +7 -0
  20. package/dist/analyzers/cards/types.js +1 -0
  21. package/dist/analyzers/cards/types.js.map +7 -0
  22. package/dist/analyzers/cbm-analyzer.js +195 -171
  23. package/dist/analyzers/cbm-analyzer.js.map +2 -2
  24. package/dist/analyzers/cbm-responses.js +336 -0
  25. package/dist/analyzers/cbm-responses.js.map +7 -0
  26. package/dist/analyzers/edge-relationships.js +45 -0
  27. package/dist/analyzers/edge-relationships.js.map +7 -0
  28. package/dist/analyzers/mapping-loader.js +9 -0
  29. package/dist/analyzers/mapping-loader.js.map +2 -2
  30. package/dist/analyzers/tier0-rules.js +153 -0
  31. package/dist/analyzers/tier0-rules.js.map +7 -0
  32. package/dist/analyzers/types.js.map +1 -1
  33. package/dist/analyzers/verify-engine.js +22 -19
  34. package/dist/analyzers/verify-engine.js.map +2 -2
  35. package/dist/audit/relationships/ai/prompts.js +10 -10
  36. package/dist/audit/relationships/ai/prompts.js.map +2 -2
  37. package/dist/audit/relationships/analysis/layer-templates.js +52 -52
  38. package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
  39. package/dist/cli.js +112 -28
  40. package/dist/cli.js.map +3 -3
  41. package/dist/coding-agents/agents/claude-code.js +3 -1
  42. package/dist/coding-agents/agents/claude-code.js.map +2 -2
  43. package/dist/coding-agents/claude-code-client.js +2 -1
  44. package/dist/coding-agents/claude-code-client.js.map +2 -2
  45. package/dist/coding-agents/copilot-client.js +17 -12
  46. package/dist/coding-agents/copilot-client.js.map +2 -2
  47. package/dist/coding-agents/default-permissions.js +152 -0
  48. package/dist/coding-agents/default-permissions.js.map +7 -0
  49. package/dist/coding-agents/drbot-mcp.js +167 -0
  50. package/dist/coding-agents/drbot-mcp.js.map +7 -0
  51. package/dist/commands/add.js +12 -7
  52. package/dist/commands/add.js.map +2 -2
  53. package/dist/commands/analyzer.js +134 -23
  54. package/dist/commands/analyzer.js.map +2 -2
  55. package/dist/commands/audit.js +6 -6
  56. package/dist/commands/audit.js.map +2 -2
  57. package/dist/commands/changeset-verify.js +55 -0
  58. package/dist/commands/changeset-verify.js.map +7 -0
  59. package/dist/commands/changeset.js +261 -147
  60. package/dist/commands/changeset.js.map +2 -2
  61. package/dist/commands/delete.js +52 -19
  62. package/dist/commands/delete.js.map +3 -3
  63. package/dist/commands/element.js +3 -2
  64. package/dist/commands/element.js.map +2 -2
  65. package/dist/commands/farm.js +773 -0
  66. package/dist/commands/farm.js.map +7 -0
  67. package/dist/commands/init.js +11 -1
  68. package/dist/commands/init.js.map +2 -2
  69. package/dist/commands/mcp.js +122 -0
  70. package/dist/commands/mcp.js.map +7 -0
  71. package/dist/commands/relationship.js +251 -47
  72. package/dist/commands/relationship.js.map +2 -2
  73. package/dist/commands/reports.js +1 -1
  74. package/dist/commands/reports.js.map +2 -2
  75. package/dist/commands/update.js +56 -24
  76. package/dist/commands/update.js.map +2 -2
  77. package/dist/commands/upgrade.js +99 -11
  78. package/dist/commands/upgrade.js.map +3 -3
  79. package/dist/commands/validate.js +41 -3
  80. package/dist/commands/validate.js.map +2 -2
  81. package/dist/commands/version.js +2 -2
  82. package/dist/commands/version.js.map +2 -2
  83. package/dist/commands/visualize.js +4 -3
  84. package/dist/commands/visualize.js.map +2 -2
  85. package/dist/config.js +83 -0
  86. package/dist/config.js.map +7 -0
  87. package/dist/core/annotation-store.js +138 -0
  88. package/dist/core/annotation-store.js.map +7 -0
  89. package/dist/core/catalog-migration.js +33 -0
  90. package/dist/core/catalog-migration.js.map +7 -0
  91. package/dist/core/changeset-analysis.js +421 -0
  92. package/dist/core/changeset-analysis.js.map +7 -0
  93. package/dist/core/changeset-base-index.js +153 -0
  94. package/dist/core/changeset-base-index.js.map +7 -0
  95. package/dist/core/changeset-brief-delta.js +102 -0
  96. package/dist/core/changeset-brief-delta.js.map +7 -0
  97. package/dist/core/changeset-graph.js +281 -0
  98. package/dist/core/changeset-graph.js.map +7 -0
  99. package/dist/core/changeset-only-guard.js +32 -0
  100. package/dist/core/changeset-only-guard.js.map +7 -0
  101. package/dist/core/changeset-summary.js +68 -0
  102. package/dist/core/changeset-summary.js.map +7 -0
  103. package/dist/core/changeset-validator.js +66 -20
  104. package/dist/core/changeset-validator.js.map +2 -2
  105. package/dist/core/changeset-verify.js +518 -0
  106. package/dist/core/changeset-verify.js.map +7 -0
  107. package/dist/core/changeset.js +56 -9
  108. package/dist/core/changeset.js.map +2 -2
  109. package/dist/core/element-type-migration.js +145 -0
  110. package/dist/core/element-type-migration.js.map +7 -0
  111. package/dist/core/farm-manifest.js +140 -0
  112. package/dist/core/farm-manifest.js.map +7 -0
  113. package/dist/core/farm-sync-engine.js +346 -0
  114. package/dist/core/farm-sync-engine.js.map +7 -0
  115. package/dist/core/farm-sync-state.js +167 -0
  116. package/dist/core/farm-sync-state.js.map +7 -0
  117. package/dist/core/manifest.js +6 -3
  118. package/dist/core/manifest.js.map +2 -2
  119. package/dist/core/migration-registry.js +84 -1
  120. package/dist/core/migration-registry.js.map +2 -2
  121. package/dist/core/model-upgrade.js +102 -0
  122. package/dist/core/model-upgrade.js.map +7 -0
  123. package/dist/core/model.js +257 -44
  124. package/dist/core/model.js.map +3 -3
  125. package/dist/core/mutation-handler.js +71 -6
  126. package/dist/core/mutation-handler.js.map +2 -2
  127. package/dist/core/relationship-migration-table.js +6639 -0
  128. package/dist/core/relationship-migration-table.js.map +7 -0
  129. package/dist/core/relationship-migration.js +272 -0
  130. package/dist/core/relationship-migration.js.map +7 -0
  131. package/dist/core/relationship-prune.js +49 -0
  132. package/dist/core/relationship-prune.js.map +7 -0
  133. package/dist/core/relationship-staging.js +68 -0
  134. package/dist/core/relationship-staging.js.map +7 -0
  135. package/dist/core/relationships.js +26 -4
  136. package/dist/core/relationships.js.map +2 -2
  137. package/dist/core/report-data-model.js +7 -13
  138. package/dist/core/report-data-model.js.map +2 -2
  139. package/dist/core/staged-changeset-storage.js +64 -17
  140. package/dist/core/staged-changeset-storage.js.map +2 -2
  141. package/dist/core/staged-source-reference.js +28 -0
  142. package/dist/core/staged-source-reference.js.map +7 -0
  143. package/dist/core/staging-area.js +83 -18
  144. package/dist/core/staging-area.js.map +2 -2
  145. package/dist/core/virtual-projection.js +14 -5
  146. package/dist/core/virtual-projection.js.map +2 -2
  147. package/dist/export/archimate-exporter.js +1 -0
  148. package/dist/export/archimate-exporter.js.map +2 -2
  149. package/dist/generated/compiled-validators.js +2 -2
  150. package/dist/generated/compiled-validators.js.map +2 -2
  151. package/dist/generated/layer-registry.js +16 -16
  152. package/dist/generated/layer-registry.js.map +2 -2
  153. package/dist/generated/node-types.js +186 -168
  154. package/dist/generated/node-types.js.map +2 -2
  155. package/dist/generated/relationship-index.js +2209 -7727
  156. package/dist/generated/relationship-index.js.map +2 -2
  157. package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
  158. package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
  159. package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
  160. package/dist/integrations/claude_code/commands/dr-map.md +13 -0
  161. package/dist/integrations/claude_code/commands/dr-plan.md +40 -0
  162. package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
  163. package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
  164. package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
  165. package/dist/integrations/claude_code/docs/MCP_SERVER.md +79 -0
  166. package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
  167. package/dist/integrations/claude_code/docs/README.md +52 -1
  168. package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
  169. package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
  170. package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
  171. package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
  172. package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
  173. package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
  174. package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
  175. package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
  176. package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
  177. package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
  178. package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
  179. package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
  180. package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +92 -15
  181. package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
  182. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
  183. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
  184. package/dist/integrations/github_copilot/agents/dr-architect.md +1 -1
  185. package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
  186. package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
  187. package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
  188. package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
  189. package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
  190. package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
  191. package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
  192. package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
  193. package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
  194. package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
  195. package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
  196. package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
  197. package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +222 -102
  198. package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -0
  199. package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
  200. package/dist/integrations/hash-utils.js.map +2 -2
  201. package/dist/mcp/api-key-manager.js +111 -0
  202. package/dist/mcp/api-key-manager.js.map +7 -0
  203. package/dist/mcp/changeset-only.js +34 -0
  204. package/dist/mcp/changeset-only.js.map +7 -0
  205. package/dist/mcp/http-transport.js +176 -0
  206. package/dist/mcp/http-transport.js.map +7 -0
  207. package/dist/mcp/resource-registry.js +97 -0
  208. package/dist/mcp/resource-registry.js.map +7 -0
  209. package/dist/mcp/tool-registry.js +60 -0
  210. package/dist/mcp/tool-registry.js.map +7 -0
  211. package/dist/mcp/tools/annotation-create.js +39 -0
  212. package/dist/mcp/tools/annotation-create.js.map +7 -0
  213. package/dist/mcp/tools/annotation-delete.js +29 -0
  214. package/dist/mcp/tools/annotation-delete.js.map +7 -0
  215. package/dist/mcp/tools/annotation-get.js +29 -0
  216. package/dist/mcp/tools/annotation-get.js.map +7 -0
  217. package/dist/mcp/tools/annotation-list.js +24 -0
  218. package/dist/mcp/tools/annotation-list.js.map +7 -0
  219. package/dist/mcp/tools/annotation-reply.js +34 -0
  220. package/dist/mcp/tools/annotation-reply.js.map +7 -0
  221. package/dist/mcp/tools/annotation-shared.js +10 -0
  222. package/dist/mcp/tools/annotation-shared.js.map +7 -0
  223. package/dist/mcp/tools/annotation-update.js +39 -0
  224. package/dist/mcp/tools/annotation-update.js.map +7 -0
  225. package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
  226. package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
  227. package/dist/mcp/tools/changeset-editing.js +184 -0
  228. package/dist/mcp/tools/changeset-editing.js.map +7 -0
  229. package/dist/mcp/tools/changeset-lifecycle.js +83 -0
  230. package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
  231. package/dist/mcp/tools/changeset-list.js +33 -0
  232. package/dist/mcp/tools/changeset-list.js.map +7 -0
  233. package/dist/mcp/tools/changeset-shared.js +31 -0
  234. package/dist/mcp/tools/changeset-shared.js.map +7 -0
  235. package/dist/mcp/tools/changeset-show.js +55 -0
  236. package/dist/mcp/tools/changeset-show.js.map +7 -0
  237. package/dist/mcp/tools/changeset-verify.js +43 -0
  238. package/dist/mcp/tools/changeset-verify.js.map +7 -0
  239. package/dist/mcp/tools/chat-launch.js +50 -0
  240. package/dist/mcp/tools/chat-launch.js.map +7 -0
  241. package/dist/mcp/tools/chat-status.js +26 -0
  242. package/dist/mcp/tools/chat-status.js.map +7 -0
  243. package/dist/mcp/tools/index.js +65 -0
  244. package/dist/mcp/tools/index.js.map +7 -0
  245. package/dist/mcp/tools/model-add.js +107 -0
  246. package/dist/mcp/tools/model-add.js.map +7 -0
  247. package/dist/mcp/tools/model-delete.js +99 -0
  248. package/dist/mcp/tools/model-delete.js.map +7 -0
  249. package/dist/mcp/tools/model-export.js +72 -0
  250. package/dist/mcp/tools/model-export.js.map +7 -0
  251. package/dist/mcp/tools/model-info.js +51 -0
  252. package/dist/mcp/tools/model-info.js.map +7 -0
  253. package/dist/mcp/tools/model-list.js +53 -0
  254. package/dist/mcp/tools/model-list.js.map +7 -0
  255. package/dist/mcp/tools/model-reload.js +34 -0
  256. package/dist/mcp/tools/model-reload.js.map +7 -0
  257. package/dist/mcp/tools/model-search.js +102 -0
  258. package/dist/mcp/tools/model-search.js.map +7 -0
  259. package/dist/mcp/tools/model-show.js +68 -0
  260. package/dist/mcp/tools/model-show.js.map +7 -0
  261. package/dist/mcp/tools/model-stats.js +22 -0
  262. package/dist/mcp/tools/model-stats.js.map +7 -0
  263. package/dist/mcp/tools/model-trace.js +118 -0
  264. package/dist/mcp/tools/model-trace.js.map +7 -0
  265. package/dist/mcp/tools/model-update.js +121 -0
  266. package/dist/mcp/tools/model-update.js.map +7 -0
  267. package/dist/mcp/tools/model-validate.js +78 -0
  268. package/dist/mcp/tools/model-validate.js.map +7 -0
  269. package/dist/mcp/tools/relationship-tools.js +98 -0
  270. package/dist/mcp/tools/relationship-tools.js.map +7 -0
  271. package/dist/mcp/tools/shared.js +126 -0
  272. package/dist/mcp/tools/shared.js.map +7 -0
  273. package/dist/mcp/tools/spec-type.js +90 -0
  274. package/dist/mcp/tools/spec-type.js.map +7 -0
  275. package/dist/reports/model-layer-report-generator.js +5 -3
  276. package/dist/reports/model-layer-report-generator.js.map +2 -2
  277. package/dist/reports/model-readme-generator.js +116 -0
  278. package/dist/reports/model-readme-generator.js.map +7 -0
  279. package/dist/reports/model-report-data.js +37 -1
  280. package/dist/reports/model-report-data.js.map +2 -2
  281. package/dist/reports/model-report-orchestrator.js +29 -6
  282. package/dist/reports/model-report-orchestrator.js.map +2 -2
  283. package/dist/reports/model-report-utils.js +9 -0
  284. package/dist/reports/model-report-utils.js.map +7 -0
  285. package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
  286. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  287. package/dist/schemas/bundled/api.json +1029 -1658
  288. package/dist/schemas/bundled/apm.json +499 -734
  289. package/dist/schemas/bundled/application.json +745 -370
  290. package/dist/schemas/bundled/base.json +3 -220
  291. package/dist/schemas/bundled/business.json +452 -366
  292. package/dist/schemas/bundled/data-model.json +403 -383
  293. package/dist/schemas/bundled/data-store.json +334 -558
  294. package/dist/schemas/bundled/manifest.json +18 -18
  295. package/dist/schemas/bundled/motivation.json +349 -368
  296. package/dist/schemas/bundled/navigation.json +415 -731
  297. package/dist/schemas/bundled/product.json +325 -159
  298. package/dist/schemas/bundled/security.json +864 -1232
  299. package/dist/schemas/bundled/technology.json +574 -703
  300. package/dist/schemas/bundled/testing.json +718 -1643
  301. package/dist/schemas/bundled/ux.json +739 -1352
  302. package/dist/server/schemas.js +8 -0
  303. package/dist/server/schemas.js.map +2 -2
  304. package/dist/server/server.js +234 -31
  305. package/dist/server/server.js.map +2 -2
  306. package/dist/server-entry.js +2 -1
  307. package/dist/server-entry.js.map +2 -2
  308. package/dist/telemetry/config.js +2 -40
  309. package/dist/telemetry/config.js.map +2 -2
  310. package/dist/telemetry/index.js +1 -1
  311. package/dist/types/index.js.map +2 -2
  312. package/dist/utils/ansi-suppressor.js +23 -0
  313. package/dist/utils/ansi-suppressor.js.map +7 -0
  314. package/dist/utils/attribute-patch.js +41 -0
  315. package/dist/utils/attribute-patch.js.map +7 -0
  316. package/dist/utils/color-utils.js +34 -0
  317. package/dist/utils/color-utils.js.map +7 -0
  318. package/dist/utils/element-reference-shape.js +15 -1
  319. package/dist/utils/element-reference-shape.js.map +3 -3
  320. package/dist/utils/error-hints.js +29 -0
  321. package/dist/utils/error-hints.js.map +7 -0
  322. package/dist/utils/errors.js +75 -10
  323. package/dist/utils/errors.js.map +2 -2
  324. package/dist/utils/globals.js +17 -1
  325. package/dist/utils/globals.js.map +2 -2
  326. package/dist/utils/project-paths.js +50 -3
  327. package/dist/utils/project-paths.js.map +2 -2
  328. package/dist/utils/reference-path-parser.js +49 -0
  329. package/dist/utils/reference-path-parser.js.map +7 -0
  330. package/dist/utils/source-reference.js +17 -10
  331. package/dist/utils/source-reference.js.map +2 -2
  332. package/dist/utils/spec-version.js +10 -2
  333. package/dist/utils/spec-version.js.map +2 -2
  334. package/dist/validators/composed-reference-validator.js +240 -0
  335. package/dist/validators/composed-reference-validator.js.map +7 -0
  336. package/dist/validators/reference-validator.js +57 -13
  337. package/dist/validators/reference-validator.js.map +3 -3
  338. package/dist/validators/relationship-schema-validator.js +33 -5
  339. package/dist/validators/relationship-schema-validator.js.map +3 -3
  340. package/dist/validators/schema-validator.js +27 -9
  341. package/dist/validators/schema-validator.js.map +2 -2
  342. package/dist/validators/types.js.map +1 -1
  343. package/dist/viewer/README.md +6 -6
  344. package/dist/viewer/assets/main-CxzcuU_o.js +84 -0
  345. package/dist/viewer/assets/main-j-7ULYpm.css +1 -0
  346. package/dist/viewer/index.html +2 -2
  347. package/dist/viewer/manifest.json +10 -10
  348. package/dist/viewer/version.json +1 -1
  349. package/package.json +7 -3
  350. package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
  351. package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
@@ -11,6 +11,11 @@ import { VerifyEngine } from "./verify-engine.js";
11
11
  import { clampDepth, shapeCallGraphNode, determineEdgeType } from "./call-graph-utils.js";
12
12
  import { evaluateHeuristic, capConfidence, matchPattern, getKnownHeuristicNames } from "./heuristic-utils.js";
13
13
  import { toKebabCase } from "../utils/id-generator.js";
14
+ import { CbmTools, CbmToolError, ROUTE_HANDLERS_QUERY, attachRouteHandlers, nodeColumns, nodesFromQuery, relativeToRoot, routeHandlersFromQuery } from "./cbm-responses.js";
15
+ import { cbmCardGraph } from "./cards/graph.js";
16
+ import { classifyTier0, defaultLabels } from "./cards/classify.js";
17
+ import { CARD_FIELDS } from "./cards/build.js";
18
+ import { tier0RulesFrom } from "./tier0-rules.js";
14
19
  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
20
  function isLikelyHttpRoute(qualifiedName) {
16
21
  const httpPath = qualifiedName.replace(/^__route__[A-Z]+__/, "");
@@ -24,21 +29,17 @@ function inferHttpPathFromFunctionName(name) {
24
29
  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
30
  return segment ? `/${segment}` : "/";
26
31
  }
32
+ const SERVICE_SOURCE_TYPES = /* @__PURE__ */ new Set(["applicationfunction", "applicationcomponent", "applicationservice"]);
27
33
  class CbmAnalyzer {
28
34
  mapper;
29
35
  testCodePatternWarned = !1;
30
36
  warnedHeuristics = /* @__PURE__ */ new Set();
31
37
  /**
32
- * Default regex patterns for test code detection
38
+ * Test code detection when the mapping has no test_code_exclusion rule: the same test-file
39
+ * pattern as the mapping's rule and its tier0_test_file rule.
33
40
  */
34
41
  static DEFAULT_TEST_PATTERNS = [
35
- /\.test\./,
36
- /\.spec\./,
37
- /\/tests?\//,
38
- /\/__tests__\//,
39
- /\/test-/,
40
- /\/fixtures?\//,
41
- /\/__fixtures__\//
42
+ /((^|\/)(tests?|__tests__|__fixtures__|fixtures?)\/|(^|\/)test_[^/]*\.py$|(^|\/)conftest\.py$|_test\.(go|py)$|\.(test|spec)\.[cm]?[jt]sx?$|Tests?\.(java|kt)$)/
42
43
  ];
43
44
  constructor(mapper) {
44
45
  this.mapper = mapper;
@@ -171,10 +172,11 @@ class CbmAnalyzer {
171
172
  * Checks if the project is indexed and whether the index is fresh relative to git HEAD.
172
173
  *
173
174
  * @param projectRoot Absolute path to the project root
175
+ * @param options Optional codebase configuration
174
176
  * @returns Current status including detection, index state, and freshness
175
177
  */
176
- async status(projectRoot) {
177
- const detected = await this.detect(projectRoot), analyzerName = this.mapper.getAnalyzerName(), indexMeta = await readIndexMeta(projectRoot, analyzerName);
178
+ async status(projectRoot, options) {
179
+ const detected = await this.detect(projectRoot), effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, analyzerName = this.mapper.getAnalyzerName(), indexMeta = await readIndexMeta(projectRoot, analyzerName);
178
180
  if (!(indexMeta !== null))
179
181
  return {
180
182
  detected,
@@ -184,7 +186,7 @@ class CbmAnalyzer {
184
186
  let fresh = !1;
185
187
  try {
186
188
  const currentHeadResult = spawnSync("git", ["rev-parse", "HEAD"], {
187
- cwd: projectRoot,
189
+ cwd: effectiveCodebaseRoot,
188
190
  stdio: "pipe",
189
191
  encoding: "utf-8"
190
192
  });
@@ -192,7 +194,11 @@ class CbmAnalyzer {
192
194
  fresh = currentHeadResult.stdout.trim() === indexMeta.git_head;
193
195
  else {
194
196
  const gitError = currentHeadResult.stderr?.trim() || "git returned non-zero status";
195
- handleWarning("Failed to check git freshness", [
197
+ options?.codebaseRoot ? handleWarning("Failed to check git freshness in codebase root", [
198
+ `Codebase root: ${effectiveCodebaseRoot}`,
199
+ `Git error: ${gitError}`,
200
+ "Index freshness cannot be determined - treating as stale"
201
+ ]) : handleWarning("Failed to check git freshness", [
196
202
  `Git error: ${gitError}`,
197
203
  "Index freshness cannot be determined - treating as stale"
198
204
  ]), fresh = !1;
@@ -221,11 +227,11 @@ class CbmAnalyzer {
221
227
  * Skips indexing if the index is fresh (git HEAD matches) unless --force is set.
222
228
  *
223
229
  * @param projectRoot Absolute path to the project root
224
- * @param options Optional configuration
230
+ * @param options Optional configuration including force flag and codebase root
225
231
  * @returns Index result with counts and metadata
226
232
  */
227
233
  async index(projectRoot, options) {
228
- const status = await this.status(projectRoot);
234
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
229
235
  if (status.indexed && status.fresh && !options?.force) {
230
236
  handleWarning("Index is fresh. Use --force to reindex.", [
231
237
  `Last indexed: ${status.last_indexed}`
@@ -252,10 +258,8 @@ class CbmAnalyzer {
252
258
  name: "dr-cli",
253
259
  version
254
260
  });
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) {
261
+ const tools = new CbmTools(client);
262
+ if ((await tools.listProjects()).some((p) => p.root_path === effectiveCodebaseRoot) && status.fresh && !options?.force) {
259
263
  if (handleWarning("Project already indexed in CBM backend with fresh index.", ["Use --force to re-index the project"]), !status.index_meta)
260
264
  throw new CLIError("Project exists in CBM backend but local metadata is missing", ErrorCategory.VALIDATION, [
261
265
  "Use --force to re-index and regenerate metadata",
@@ -270,13 +274,14 @@ class CbmAnalyzer {
270
274
  };
271
275
  }
272
276
  const headResult = spawnSync("git", ["rev-parse", "HEAD"], {
273
- cwd: projectRoot,
277
+ cwd: effectiveCodebaseRoot,
274
278
  stdio: "pipe",
275
279
  encoding: "utf-8"
276
280
  });
277
281
  if (headResult.status !== 0)
278
282
  throw new CLIError("Failed to get git HEAD", ErrorCategory.SYSTEM, [
279
- "Ensure the project is a git repository",
283
+ "Ensure the codebase root is a git repository",
284
+ `Codebase root: ${effectiveCodebaseRoot}`,
280
285
  "Verify git is installed and functional",
281
286
  `Error: ${headResult.stderr?.trim() || "unknown error"}`
282
287
  ]);
@@ -288,12 +293,7 @@ class CbmAnalyzer {
288
293
  const errorMsg = error instanceof Error ? error.message : String(error);
289
294
  handleWarning("Failed to read active changeset", [`Error: ${errorMsg}`, "Continuing with indexing"]);
290
295
  }
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;
296
+ const indexResponse = await tools.indexRepository(effectiveCodebaseRoot), timestamp = (/* @__PURE__ */ new Date()).toISOString(), nodeCount = typeof indexResponse.nodes == "number" ? indexResponse.nodes : 0, edgeCount = typeof indexResponse.edges == "number" ? indexResponse.edges : 0;
297
297
  typeof indexResponse.nodes != "number" && handleWarning("Invalid node count from index_repository", [
298
298
  `Expected number, got ${typeof indexResponse.nodes}: ${String(indexResponse.nodes)}`,
299
299
  "Defaulting to 0 - index may be incomplete or corrupted"
@@ -318,26 +318,25 @@ class CbmAnalyzer {
318
318
  }
319
319
  }
320
320
  /**
321
- * Validate search_graph response structure
321
+ * Fetch Route nodes with their HANDLES-edge handlers attached.
322
322
  *
323
- * Ensures response is an object and nodes is an array.
324
- * Throws CLIError if validation fails.
323
+ * CBM Route nodes carry only `{ method, source }` and often no file (every fastapi route,
324
+ * for example); the handler function, its symbol, lines and file come from HANDLES edges.
325
+ * If the handler query fails the routes are returned as they are.
325
326
  *
326
327
  * @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
328
  */
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;
329
+ async fetchRoutes(tools, projectName, label) {
330
+ const routes = await tools.searchGraph({ label, project: projectName });
331
+ try {
332
+ const handlers = routeHandlersFromQuery(await tools.queryGraph(ROUTE_HANDLERS_QUERY, projectName));
333
+ return attachRouteHandlers(routes, handlers);
334
+ } catch (error) {
335
+ const errorMsg = error instanceof Error ? error.message : String(error);
336
+ return handleWarning("Could not read route handlers (HANDLES edges); routes lack handler details", [
337
+ `Error: ${errorMsg}`
338
+ ]), routes;
339
+ }
341
340
  }
342
341
  /**
343
342
  * Resolve the internal project name used by codebase-memory-mcp for a given root path.
@@ -346,12 +345,12 @@ class CbmAnalyzer {
346
345
  * query tools require the internal `name`, not the absolute `root_path`.
347
346
  * Falls back to the absolute path with a warning if the project is not found.
348
347
  */
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);
348
+ async resolveProjectName(client, codebaseRoot) {
349
+ const match = (await (client instanceof CbmTools ? client : new CbmTools(client)).listProjects()).find((p) => p.root_path === codebaseRoot);
351
350
  return match ? match.name : (handleWarning("Project not found in CBM backend; using absolute path as project identifier", [
352
- `Project root: ${projectRoot}`,
351
+ `Codebase root: ${codebaseRoot}`,
353
352
  "Run `dr analyzer index` to ensure the project is indexed"
354
- ]), projectRoot);
353
+ ]), codebaseRoot);
355
354
  }
356
355
  /**
357
356
  * Query for API endpoints/routes in the indexed project
@@ -360,11 +359,12 @@ class CbmAnalyzer {
360
359
  * the analyzer's mapping, and filters based on test code exclusion rules.
361
360
  *
362
361
  * @param projectRoot Absolute path to the project root
362
+ * @param options Optional codebase configuration
363
363
  * @returns Array of endpoint candidates with confidence and source info
364
364
  * @throws CLIError if the project is not indexed
365
365
  */
366
- async endpoints(projectRoot) {
367
- const status = await this.status(projectRoot);
366
+ async endpoints(projectRoot, options) {
367
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
368
368
  if (!status.indexed)
369
369
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
370
370
  "Run `dr analyzer index` to index the project",
@@ -384,18 +384,14 @@ class CbmAnalyzer {
384
384
  name: "dr-cli",
385
385
  version
386
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 = [];
387
+ const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot), nodes = (await this.fetchRoutes(tools, projectName, routeMapping.cbm_label)).filter((n) => /^__route__[A-Z]+__\//.test(String(n.qualified_name ?? ""))).filter((n) => isLikelyHttpRoute(String(n.qualified_name ?? ""))), candidates = [];
391
388
  for (const node of nodes) {
392
- const candidate = await this.transformNodeToEndpoint(node, routeMapping, projectRoot);
389
+ const candidate = await this.transformNodeToEndpoint(node, routeMapping, effectiveCodebaseRoot);
393
390
  candidate.source_file && (this.isTestCode(candidate) || candidates.push(candidate));
394
391
  }
395
392
  if (candidates.length === 0)
396
393
  try {
397
- const qResp = await client.invokeTool("query_graph", {
398
- query: `
394
+ const clientQuery = `
399
395
  MATCH (n:Function)
400
396
  WHERE (n.file_path CONTAINS 'client' OR n.file_path CONTAINS 'Client'
401
397
  OR n.file_path CONTAINS 'Api' OR n.file_path CONTAINS 'Service'
@@ -406,21 +402,17 @@ class CbmAnalyzer {
406
402
  OR n.name STARTS WITH 'delete' OR n.name STARTS WITH 'patch'
407
403
  OR n.name STARTS WITH 'create' OR n.name STARTS WITH 'update'
408
404
  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
- }
405
+ RETURN ${nodeColumns("n")}
406
+ `, clientNodes = nodesFromQuery(await tools.queryGraph(clientQuery, projectName));
407
+ for (const node of clientNodes) {
408
+ if (!node.file_path)
409
+ continue;
410
+ const candidate = this.shapeClientSideEndpoint(node, effectiveCodebaseRoot);
411
+ candidate && !this.isTestCode(candidate) && candidates.push(candidate);
422
412
  }
423
- } catch {
413
+ } catch (error) {
414
+ const errorMsg = error instanceof Error ? error.message : String(error);
415
+ handleWarning("Client-side endpoint fallback skipped: query_graph failed", [`Error: ${errorMsg}`]);
424
416
  }
425
417
  return this.warnOnDuplicateIdFragments(candidates, (c) => c.suggested_id_fragment, (c) => c.source_symbol || c.source_file || "(unknown)"), candidates;
426
418
  } finally {
@@ -436,11 +428,12 @@ class CbmAnalyzer {
436
428
  *
437
429
  * @param projectRoot Absolute path to the project root
438
430
  * @param rawQuery Raw Cypher query string
431
+ * @param options Optional codebase configuration
439
432
  * @returns Raw result from query_graph tool
440
433
  * @throws CLIError if project not indexed or query_graph tool unavailable
441
434
  */
442
- async query(projectRoot, rawQuery) {
443
- const status = await this.status(projectRoot);
435
+ async query(projectRoot, rawQuery, options) {
436
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
444
437
  if (!status.indexed)
445
438
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
446
439
  "Run `dr analyzer index` to index the project",
@@ -457,13 +450,13 @@ class CbmAnalyzer {
457
450
  name: "dr-cli",
458
451
  version
459
452
  });
460
- const projectName = await this.resolveProjectName(client, projectRoot);
453
+ const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
461
454
  try {
462
- return await client.invokeTool("query_graph", {
463
- query: rawQuery,
464
- project: projectName
465
- });
455
+ const result = await tools.queryGraph(rawQuery, projectName);
456
+ return { ...result, total: result.rows.length };
466
457
  } catch (error) {
458
+ if (error instanceof CbmToolError)
459
+ throw new CLIError(`Query failed: ${error.detail}`, ErrorCategory.USER, error.suggestions);
467
460
  const errorMessage = error instanceof Error ? error.message : String(error);
468
461
  throw errorMessage.includes("query_graph") || errorMessage.includes("Unknown method") ? new CLIError("query_graph not supported by this analyzer", ErrorCategory.USER, [
469
462
  "The query_graph tool is optional in the CBM analyzer contract",
@@ -507,7 +500,7 @@ class CbmAnalyzer {
507
500
  *
508
501
  * @private
509
502
  */
510
- async transformNodeToEndpoint(node, mapping, projectRoot) {
503
+ async transformNodeToEndpoint(node, mapping, codebaseRoot) {
511
504
  const properties = node.properties ?? {}, nodeId = String(node.name ?? node.id ?? "(unknown)");
512
505
  let confidence = mapping.confidence;
513
506
  const suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? "")), suggestedIdFragment = suggestedName;
@@ -527,9 +520,9 @@ class CbmAnalyzer {
527
520
  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
521
  (!handlerQualifiedName || !sourceSymbol) && (confidence = capConfidence(confidence));
529
522
  let sourceFile = String(node.file_path ?? properties.file_path ?? properties.source_file ?? "");
530
- if (sourceFile && projectRoot)
523
+ if (sourceFile && codebaseRoot)
531
524
  try {
532
- sourceFile = path.relative(projectRoot, sourceFile);
525
+ sourceFile = relativeToRoot(codebaseRoot, sourceFile);
533
526
  } catch (error) {
534
527
  const errorMsg = error instanceof Error ? error.message : String(error);
535
528
  handleWarning(`Failed to compute relative path for ${nodeId}`, [
@@ -577,14 +570,14 @@ class CbmAnalyzer {
577
570
  * Build a low-confidence EndpointCandidate from a client-side API function node.
578
571
  * Used as a fallback when no server-side Route nodes are found (ISSUE-003).
579
572
  */
580
- shapeClientSideEndpoint(node, projectRoot) {
573
+ shapeClientSideEndpoint(node, codebaseRoot) {
581
574
  const name = String(node.name ?? node.id ?? "");
582
575
  if (!name)
583
576
  return null;
584
577
  let sourceFile = String(node.file_path ?? "");
585
- if (sourceFile && projectRoot)
578
+ if (sourceFile && codebaseRoot)
586
579
  try {
587
- sourceFile = path.relative(projectRoot, sourceFile);
580
+ sourceFile = relativeToRoot(codebaseRoot, sourceFile);
588
581
  } catch {
589
582
  }
590
583
  if (!sourceFile)
@@ -637,11 +630,12 @@ class CbmAnalyzer {
637
630
  * at "medium" and excludes test files.
638
631
  *
639
632
  * @param projectRoot Absolute path to the project root
633
+ * @param options Optional codebase configuration
640
634
  * @returns Array of service candidates with qualifying heuristics populated
641
635
  * @throws CLIError if the project is not indexed
642
636
  */
643
- async services(projectRoot) {
644
- const status = await this.status(projectRoot);
637
+ async services(projectRoot, options) {
638
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
645
639
  if (!status.indexed)
646
640
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
647
641
  "Run `dr analyzer index` to index the project",
@@ -661,7 +655,7 @@ class CbmAnalyzer {
661
655
  const nodeLabels = this.mapper.getNodeLabels(), applicationLabels = [];
662
656
  for (const label of nodeLabels) {
663
657
  const mapping = this.mapper.getNodeMapping(label);
664
- mapping && mapping.dr_layer === "application" && applicationLabels.push(label);
658
+ mapping && mapping.dr_layer === "application" && SERVICE_SOURCE_TYPES.has(mapping.dr_element_type) && applicationLabels.push(label);
665
659
  }
666
660
  if (applicationLabels.length === 0)
667
661
  return handleWarning("No application-layer labels found in analyzer mapping", [
@@ -669,18 +663,15 @@ class CbmAnalyzer {
669
663
  "Services detection requires at least one mapped application-layer node type",
670
664
  "Returning empty services list"
671
665
  ]), [];
672
- const candidates = [], projectName = await this.resolveProjectName(client, projectRoot), labelPriority = { Class: 0, Module: 1 };
666
+ const candidates = [], tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot), labelPriority = { Class: 0, Module: 1 };
673
667
  applicationLabels.sort((a, b) => (labelPriority[a] ?? 99) - (labelPriority[b] ?? 99));
674
668
  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);
669
+ const nodes = await tools.searchGraph({ label, project: projectName }), nodeMapping = this.mapper.getNodeMapping(label);
679
670
  if (!nodeMapping)
680
671
  continue;
681
672
  const promotionHeuristicNames = nodeMapping.promotion_heuristics || [];
682
673
  for (const node of nodes) {
683
- const candidate = await this.transformNodeToService(node, nodeMapping, projectRoot, promotionHeuristicNames);
674
+ const candidate = await this.transformNodeToService(node, nodeMapping, effectiveCodebaseRoot, promotionHeuristicNames);
684
675
  candidate.qualifying_heuristics.length > 0 && !this.isTestCode(candidate) && candidates.push(candidate);
685
676
  }
686
677
  }
@@ -700,12 +691,12 @@ class CbmAnalyzer {
700
691
  *
701
692
  * @private
702
693
  */
703
- async transformNodeToService(node, mapping, projectRoot, promotionHeuristicNames) {
694
+ async transformNodeToService(node, mapping, codebaseRoot, promotionHeuristicNames) {
704
695
  const properties = node.properties ?? {}, suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? "")), suggestedIdFragment = suggestedName;
705
696
  let sourceFile = node.file_path ?? "";
706
- if (sourceFile && projectRoot)
697
+ if (sourceFile && codebaseRoot)
707
698
  try {
708
- sourceFile = path.relative(projectRoot, sourceFile);
699
+ sourceFile = relativeToRoot(codebaseRoot, sourceFile);
709
700
  } catch (error) {
710
701
  const errorMsg = error instanceof Error ? error.message : String(error);
711
702
  handleWarning(`Failed to compute relative path for ${String(node.name ?? node.id ?? "(unknown)")}`, [
@@ -795,11 +786,12 @@ class CbmAnalyzer {
795
786
  * naming_indicators from the heuristic parameters. All candidates have confidence "low".
796
787
  *
797
788
  * @param projectRoot Absolute path to the project root
789
+ * @param options Optional codebase configuration
798
790
  * @returns Array of datastore candidates aggregated by inferred datastore
799
791
  * @throws CLIError if the project is not indexed
800
792
  */
801
- async datastores(projectRoot) {
802
- const status = await this.status(projectRoot);
793
+ async datastores(projectRoot, options) {
794
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
803
795
  if (!status.indexed)
804
796
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
805
797
  "Run `dr analyzer index` to index the project",
@@ -822,20 +814,17 @@ class CbmAnalyzer {
822
814
  "The analyzer configuration is missing the datastore_detection heuristic",
823
815
  "Run `npm run build:spec` to recompile the analyzer artifacts"
824
816
  ]);
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);
817
+ const importPatterns = datastoreHeuristic.parameters?.patterns ?? [], namingIndicators = datastoreHeuristic.parameters?.naming_indicators ?? [], datastoreSignals = /* @__PURE__ */ new Map(), datastoreFirstSource = /* @__PURE__ */ new Map(), tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
826
818
  let allNodes = [];
827
819
  try {
828
- const queryResponse = await client.invokeTool("query_graph", {
829
- query: `
820
+ const importsQuery = `
830
821
  MATCH (n1)-[r:IMPORTS]->(n2)
831
- RETURN DISTINCT n1 as node
822
+ RETURN DISTINCT ${nodeColumns("n1")}
832
823
  UNION
833
824
  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"));
825
+ RETURN DISTINCT ${nodeColumns("n2")}
826
+ `;
827
+ allNodes = nodesFromQuery(await tools.queryGraph(importsQuery, projectName));
839
828
  } catch (error) {
840
829
  const errorMsg = error instanceof Error ? error.message : String(error);
841
830
  handleWarning("query_graph tool failed; falling back to search_graph for datastore detection", [
@@ -845,13 +834,8 @@ class CbmAnalyzer {
845
834
  "Ensure query_graph is supported by your analyzer version for best results"
846
835
  ]);
847
836
  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
- }
837
+ for (const label of nodeLabels)
838
+ allNodes.push(...await tools.searchGraph({ label, project: projectName }));
855
839
  }
856
840
  for (const node of allNodes) {
857
841
  const filePath = node.file_path ?? "", importTargetName = String(node.label ?? node.properties?.name ?? node.id ?? "").toLowerCase();
@@ -874,9 +858,9 @@ class CbmAnalyzer {
874
858
  ]) : (datastoreSignals.set(datastoreName, []), datastoreFirstSource.set(datastoreName, importTargetName));
875
859
  const signals = datastoreSignals.get(datastoreName);
876
860
  let relativeFile = filePath;
877
- if (filePath && projectRoot)
861
+ if (filePath && effectiveCodebaseRoot)
878
862
  try {
879
- relativeFile = path.relative(projectRoot, filePath);
863
+ relativeFile = relativeToRoot(effectiveCodebaseRoot, filePath);
880
864
  } catch (error) {
881
865
  const errorMsg = error instanceof Error ? error.message : String(error);
882
866
  handleWarning("Failed to compute relative path in datastore detection", [
@@ -925,8 +909,7 @@ class CbmAnalyzer {
925
909
  }
926
910
  const browserStorageTypes = ["local-storage", "session-storage"], browserStorageSignals = /* @__PURE__ */ new Map();
927
911
  try {
928
- const storageResp = await client.invokeTool("query_graph", {
929
- query: `
912
+ const storageQuery = `
930
913
  MATCH (n:Function)
931
914
  WHERE n.file_path CONTAINS 'Store' OR n.file_path CONTAINS 'store'
932
915
  OR n.file_path CONTAINS 'storage' OR n.file_path CONTAINS 'Storage'
@@ -934,24 +917,13 @@ class CbmAnalyzer {
934
917
  OR n.file_path CONTAINS 'preference' OR n.file_path CONTAINS 'Preference'
935
918
  OR n.file_path CONTAINS 'layout' OR n.file_path CONTAINS 'Layout'
936
919
  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
- }
920
+ RETURN ${nodeColumns("n")}
921
+ `, storageNodes = nodesFromQuery(await tools.queryGraph(storageQuery, projectName));
922
+ for (const node of storageNodes) {
923
+ if (!node.file_path)
924
+ continue;
925
+ const relFile = relativeToRoot(effectiveCodebaseRoot, node.file_path), storageKey = /session/i.test(relFile) ? "session-storage" : "local-storage";
926
+ browserStorageSignals.has(storageKey) || browserStorageSignals.set(storageKey, /* @__PURE__ */ new Set()), browserStorageSignals.get(storageKey).add(relFile);
955
927
  }
956
928
  } catch {
957
929
  }
@@ -1014,11 +986,12 @@ class CbmAnalyzer {
1014
986
  * @param projectRoot Absolute path to the project root
1015
987
  * @param qualifiedName Fully qualified symbol name (e.g., "com.example.UserService.getUser")
1016
988
  * @param depth Maximum traversal depth (default 3, clamped to max 10)
989
+ * @param options Optional codebase configuration
1017
990
  * @returns Array of callers in the call graph
1018
991
  * @throws CLIError if project not indexed or analyzer not installed
1019
992
  */
1020
- async callers(projectRoot, qualifiedName, depth) {
1021
- return this.traceCallPath(projectRoot, qualifiedName, depth, "inbound");
993
+ async callers(projectRoot, qualifiedName, depth, options) {
994
+ return this.traceCallPath(projectRoot, qualifiedName, depth, "inbound", options);
1022
995
  }
1023
996
  /**
1024
997
  * Query for callees of a specific function or symbol
@@ -1031,11 +1004,12 @@ class CbmAnalyzer {
1031
1004
  * @param projectRoot Absolute path to the project root
1032
1005
  * @param qualifiedName Fully qualified symbol name (e.g., "com.example.UserService.getUser")
1033
1006
  * @param depth Maximum traversal depth (default 3, clamped to max 10)
1007
+ * @param options Optional codebase configuration
1034
1008
  * @returns Array of callees in the call graph
1035
1009
  * @throws CLIError if project not indexed or analyzer not installed
1036
1010
  */
1037
- async callees(projectRoot, qualifiedName, depth) {
1038
- return this.traceCallPath(projectRoot, qualifiedName, depth, "outbound");
1011
+ async callees(projectRoot, qualifiedName, depth, options) {
1012
+ return this.traceCallPath(projectRoot, qualifiedName, depth, "outbound", options);
1039
1013
  }
1040
1014
  /**
1041
1015
  * Cypher fallback for call graph traversal when trace_path is unavailable.
@@ -1045,7 +1019,7 @@ class CbmAnalyzer {
1045
1019
  *
1046
1020
  * @private
1047
1021
  */
1048
- async callGraphViaCypher(binaryPath, projectName, qualifiedName, direction, depth, projectRoot) {
1022
+ async callGraphViaCypher(binaryPath, projectName, qualifiedName, direction, depth, codebaseRoot) {
1049
1023
  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
1024
  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
1025
  const client = new StdioClient();
@@ -1053,16 +1027,13 @@ class CbmAnalyzer {
1053
1027
  client.spawn(binaryPath);
1054
1028
  const version = await getCliVersion();
1055
1029
  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) => {
1030
+ const queryResponse = await new CbmTools(client).queryGraph(cypher, projectName), cols = queryResponse.columns;
1031
+ return queryResponse.rows.map((row) => {
1061
1032
  const obj = {};
1062
1033
  return cols.forEach((col, i) => {
1063
1034
  const key = col.includes(".") ? col.slice(col.lastIndexOf(".") + 1) : col;
1064
1035
  obj[key] = row[i];
1065
- }), shapeCallGraphNode(obj, projectRoot);
1036
+ }), shapeCallGraphNode(obj, codebaseRoot);
1066
1037
  });
1067
1038
  } catch (error) {
1068
1039
  const errorMsg = error instanceof Error ? error.message : String(error);
@@ -1082,8 +1053,8 @@ class CbmAnalyzer {
1082
1053
  *
1083
1054
  * @private
1084
1055
  */
1085
- async traceCallPath(projectRoot, qualifiedName, depth, direction) {
1086
- const status = await this.status(projectRoot);
1056
+ async traceCallPath(projectRoot, qualifiedName, depth, direction, options) {
1057
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
1087
1058
  if (!status.indexed)
1088
1059
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
1089
1060
  "Run `dr analyzer index` to index the project",
@@ -1100,25 +1071,37 @@ class CbmAnalyzer {
1100
1071
  name: "dr-cli",
1101
1072
  version
1102
1073
  });
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 = [];
1074
+ const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
1075
+ let trace;
1076
+ try {
1077
+ trace = await tools.tracePath({
1078
+ function_name: qualifiedName,
1079
+ direction,
1080
+ depth: clampedDepth,
1081
+ project: projectName
1082
+ });
1083
+ } catch (error) {
1084
+ throw error instanceof CbmToolError && /not found/i.test(error.detail) ? new CLIError(`Symbol not found in the code graph: ${qualifiedName}`, ErrorCategory.NOT_FOUND, [
1085
+ ...error.suggestions ?? [],
1086
+ "Pass the fully qualified name as CBM records it (see `dr analyzer services --json`)"
1087
+ ]) : error;
1088
+ }
1089
+ if (!trace)
1090
+ return this.callGraphViaCypher(detection.binary_path, projectName, qualifiedName, direction, clampedDepth, effectiveCodebaseRoot);
1091
+ const nodes = trace.nodes, edges = trace.edges;
1092
+ if (trace.shape === "grouped" && nodes.length > 0)
1093
+ try {
1094
+ const files = await tools.filePathsOf(nodes.map((n) => String(n.qualified_name ?? "")), projectName);
1095
+ for (const node of nodes)
1096
+ node.file_path ??= files.get(String(node.qualified_name ?? ""));
1097
+ } catch (error) {
1098
+ const errorMsg = error instanceof Error ? error.message : String(error);
1099
+ handleWarning("Could not look up source files for the call graph", [`Error: ${errorMsg}`]);
1100
+ }
1101
+ const validEdgeTypes = ["CALLS", ...this.mapper.getEdgeTypes().filter((t) => t !== "CALLS")], callGraphNodes = [];
1119
1102
  for (const node of nodes) {
1120
- const nodeQualifiedName = node.qualified_name || node.id;
1121
- let callGraphNode = shapeCallGraphNode(node, projectRoot);
1103
+ const nodeQualifiedName = String(node.qualified_name || node.id || "");
1104
+ let callGraphNode = shapeCallGraphNode(node, effectiveCodebaseRoot);
1122
1105
  const nodeDepth = typeof node.depth == "number" ? node.depth : 0, edgeType = determineEdgeType(nodeDepth, edges, nodeQualifiedName, validEdgeTypes);
1123
1106
  callGraphNode.edge_type = edgeType, callGraphNodes.push(callGraphNode);
1124
1107
  }
@@ -1156,6 +1139,50 @@ class CbmAnalyzer {
1156
1139
  source_symbol: sourceSymbol
1157
1140
  };
1158
1141
  }
1142
+ /**
1143
+ * Tier-0 classification of the indexed project (`dr analyzer classify --tier0-only`).
1144
+ *
1145
+ * Builds a card for every candidate symbol (src/analyzers/cards) and applies the Tier-0 rules
1146
+ * from this analyzer's extraction heuristics. Reads the graph only: no model calls, no writes.
1147
+ *
1148
+ * @throws CLIError if the project is not indexed or the mapping has no Tier-0 rules
1149
+ */
1150
+ async classify(projectRoot, options = {}) {
1151
+ const effectiveCodebaseRoot = options.codebaseRoot ?? projectRoot, rules = tier0RulesFrom(this.mapper.getHeuristics());
1152
+ if (rules.length === 0)
1153
+ throw new CLIError(`The ${this.name} analyzer mapping has no Tier-0 rules`, ErrorCategory.USER, [
1154
+ "Tier-0 rules are heuristics with a parameters.tier0 block in the analyzer's extraction-heuristics.json"
1155
+ ]);
1156
+ if (options.labels?.length) {
1157
+ const known = /* @__PURE__ */ new Set([...this.mapper.getNodeLabels(), ...defaultLabels(rules), ...Object.keys(CARD_FIELDS)]), unknown = options.labels.filter((l) => !known.has(l));
1158
+ if (unknown.length)
1159
+ throw new CLIError(`Unknown label${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}`, ErrorCategory.USER, [
1160
+ `Labels are case-sensitive; known labels: ${[...known].sort().join(", ")}`
1161
+ ]);
1162
+ }
1163
+ const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
1164
+ if (!status.indexed)
1165
+ throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
1166
+ "Run `dr analyzer index` to index the project",
1167
+ "Ensure codebase-memory-mcp is installed and working"
1168
+ ]);
1169
+ const detection = status.detected;
1170
+ if (!detection.installed)
1171
+ throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, [
1172
+ "Install codebase-memory-mcp: npm install -g codebase-memory-mcp"
1173
+ ]);
1174
+ const client = new StdioClient();
1175
+ try {
1176
+ client.spawn(detection.binary_path), await client.initialize({ name: "dr-cli", version: await getCliVersion() });
1177
+ const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
1178
+ return await classifyTier0(cbmCardGraph(tools, projectName), rules, this.name, {
1179
+ sourceRoot: effectiveCodebaseRoot,
1180
+ ...options
1181
+ });
1182
+ } finally {
1183
+ client.close();
1184
+ }
1185
+ }
1159
1186
  /**
1160
1187
  * Verify that graph-discovered routes align with model endpoints
1161
1188
  *
@@ -1163,12 +1190,12 @@ class CbmAnalyzer {
1163
1190
  * and delegates comparison to VerifyEngine. Entirely read-only.
1164
1191
  *
1165
1192
  * @param projectRoot Absolute path to the project root
1166
- * @param options Verification options
1193
+ * @param options Verification options including codebase root
1167
1194
  * @returns Comprehensive verification report
1168
1195
  * @throws CLIError if project not indexed or analyzer not installed
1169
1196
  */
1170
1197
  async verify(projectRoot, options) {
1171
- const status = await this.status(projectRoot);
1198
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
1172
1199
  if (!status.indexed)
1173
1200
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
1174
1201
  "Run `dr analyzer index` to index the project",
@@ -1188,10 +1215,7 @@ class CbmAnalyzer {
1188
1215
  name: "dr-cli",
1189
1216
  version
1190
1217
  });
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;
1218
+ const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot), routes = (await this.fetchRoutes(tools, projectName, routeMapping.cbm_label)).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
1219
  return await engine.computeReport(projectRoot, routes, options, analyzerName, indexMeta);
1196
1220
  } finally {
1197
1221
  client.close();