@documentation-robotics/cli 0.2.0 → 0.4.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 (335) hide show
  1. package/README.md +46 -16
  2. package/dist/analysis/relationship-classifier.js +2 -9
  3. package/dist/analysis/relationship-classifier.js.map +2 -2
  4. package/dist/analyzers/active-analyzer.js +62 -0
  5. package/dist/analyzers/active-analyzer.js.map +7 -0
  6. package/dist/analyzers/call-graph-utils.js +2 -2
  7. package/dist/analyzers/call-graph-utils.js.map +2 -2
  8. package/dist/analyzers/cards/build.js +191 -0
  9. package/dist/analyzers/cards/build.js.map +7 -0
  10. package/dist/analyzers/cards/classify.js +42 -0
  11. package/dist/analyzers/cards/classify.js.map +7 -0
  12. package/dist/analyzers/cards/edge-index.js +44 -0
  13. package/dist/analyzers/cards/edge-index.js.map +7 -0
  14. package/dist/analyzers/cards/extract.js +138 -0
  15. package/dist/analyzers/cards/extract.js.map +7 -0
  16. package/dist/analyzers/cards/facts.js +46 -0
  17. package/dist/analyzers/cards/facts.js.map +7 -0
  18. package/dist/analyzers/cards/graph.js +42 -0
  19. package/dist/analyzers/cards/graph.js.map +7 -0
  20. package/dist/analyzers/cards/index.js +8 -0
  21. package/dist/analyzers/cards/index.js.map +7 -0
  22. package/dist/analyzers/cards/routes.js +96 -0
  23. package/dist/analyzers/cards/routes.js.map +7 -0
  24. package/dist/analyzers/cards/tier0.js +18 -0
  25. package/dist/analyzers/cards/tier0.js.map +7 -0
  26. package/dist/analyzers/cards/types.js +1 -0
  27. package/dist/analyzers/cards/types.js.map +7 -0
  28. package/dist/analyzers/cbm-analyzer.js +270 -174
  29. package/dist/analyzers/cbm-analyzer.js.map +2 -2
  30. package/dist/analyzers/cbm-responses.js +342 -0
  31. package/dist/analyzers/cbm-responses.js.map +7 -0
  32. package/dist/analyzers/code-changes.js +159 -0
  33. package/dist/analyzers/code-changes.js.map +7 -0
  34. package/dist/analyzers/edge-relationships.js +49 -0
  35. package/dist/analyzers/edge-relationships.js.map +7 -0
  36. package/dist/analyzers/extraction-plan.js +541 -0
  37. package/dist/analyzers/extraction-plan.js.map +7 -0
  38. package/dist/analyzers/index-job.js +229 -0
  39. package/dist/analyzers/index-job.js.map +7 -0
  40. package/dist/analyzers/mapping-loader.js +9 -0
  41. package/dist/analyzers/mapping-loader.js.map +2 -2
  42. package/dist/analyzers/tier0-rules.js +153 -0
  43. package/dist/analyzers/tier0-rules.js.map +7 -0
  44. package/dist/analyzers/types.js.map +1 -1
  45. package/dist/audit/relationships/ai/prompts.js +10 -10
  46. package/dist/audit/relationships/ai/prompts.js.map +2 -2
  47. package/dist/audit/relationships/analysis/layer-templates.js +52 -52
  48. package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
  49. package/dist/cli.js +23 -11
  50. package/dist/cli.js.map +2 -2
  51. package/dist/coding-agents/default-permissions.js +31 -1
  52. package/dist/coding-agents/default-permissions.js.map +2 -2
  53. package/dist/coding-agents/drbot-mcp.js +269 -0
  54. package/dist/coding-agents/drbot-mcp.js.map +7 -0
  55. package/dist/commands/add.js +7 -4
  56. package/dist/commands/add.js.map +2 -2
  57. package/dist/commands/analyzer.js +143 -40
  58. package/dist/commands/analyzer.js.map +2 -2
  59. package/dist/commands/changeset-verify.js +63 -0
  60. package/dist/commands/changeset-verify.js.map +7 -0
  61. package/dist/commands/changeset.js +80 -29
  62. package/dist/commands/changeset.js.map +2 -2
  63. package/dist/commands/delete.js +10 -4
  64. package/dist/commands/delete.js.map +2 -2
  65. package/dist/commands/farm.js +176 -60
  66. package/dist/commands/farm.js.map +2 -2
  67. package/dist/commands/init.js +9 -26
  68. package/dist/commands/init.js.map +2 -2
  69. package/dist/commands/mcp.js +40 -21
  70. package/dist/commands/mcp.js.map +3 -3
  71. package/dist/commands/relationship.js +208 -37
  72. package/dist/commands/relationship.js.map +2 -2
  73. package/dist/commands/reports.js +3 -3
  74. package/dist/commands/reports.js.map +2 -2
  75. package/dist/commands/update.js +52 -32
  76. package/dist/commands/update.js.map +2 -2
  77. package/dist/commands/upgrade.js +99 -103
  78. package/dist/commands/upgrade.js.map +3 -3
  79. package/dist/commands/validate.js +19 -3
  80. package/dist/commands/validate.js.map +2 -2
  81. package/dist/commands/version.js +1 -1
  82. package/dist/commands/visualize.js +34 -15
  83. package/dist/commands/visualize.js.map +2 -2
  84. package/dist/core/annotation-store.js +15 -5
  85. package/dist/core/annotation-store.js.map +2 -2
  86. package/dist/core/catalog-migration.js +34 -0
  87. package/dist/core/catalog-migration.js.map +7 -0
  88. package/dist/core/changeset-analysis.js +421 -0
  89. package/dist/core/changeset-analysis.js.map +7 -0
  90. package/dist/core/changeset-base-index.js +160 -0
  91. package/dist/core/changeset-base-index.js.map +7 -0
  92. package/dist/core/changeset-brief-compact.js +256 -0
  93. package/dist/core/changeset-brief-compact.js.map +7 -0
  94. package/dist/core/changeset-brief-delta.js +102 -0
  95. package/dist/core/changeset-brief-delta.js.map +7 -0
  96. package/dist/core/changeset-graph.js +281 -0
  97. package/dist/core/changeset-graph.js.map +7 -0
  98. package/dist/core/changeset-only-guard.js +32 -0
  99. package/dist/core/changeset-only-guard.js.map +7 -0
  100. package/dist/core/changeset-summary.js +68 -0
  101. package/dist/core/changeset-summary.js.map +7 -0
  102. package/dist/core/changeset-validator.js +66 -20
  103. package/dist/core/changeset-validator.js.map +2 -2
  104. package/dist/core/changeset-verify.js +848 -0
  105. package/dist/core/changeset-verify.js.map +7 -0
  106. package/dist/core/changeset.js +56 -9
  107. package/dist/core/changeset.js.map +2 -2
  108. package/dist/core/element-type-migration.js +145 -0
  109. package/dist/core/element-type-migration.js.map +7 -0
  110. package/dist/core/extraction-observability.js +161 -0
  111. package/dist/core/extraction-observability.js.map +7 -0
  112. package/dist/core/farm-manifest.js +12 -6
  113. package/dist/core/farm-manifest.js.map +2 -2
  114. package/dist/core/farm-sync-engine.js +1 -1
  115. package/dist/core/farm-sync-engine.js.map +2 -2
  116. package/dist/core/farm-sync-state.js +10 -2
  117. package/dist/core/farm-sync-state.js.map +2 -2
  118. package/dist/core/migration-registry.js +88 -47
  119. package/dist/core/migration-registry.js.map +2 -2
  120. package/dist/core/model-upgrade.js +102 -0
  121. package/dist/core/model-upgrade.js.map +7 -0
  122. package/dist/core/model.js +82 -30
  123. package/dist/core/model.js.map +2 -2
  124. package/dist/core/mutation-handler.js +71 -6
  125. package/dist/core/mutation-handler.js.map +2 -2
  126. package/dist/core/project-init.js +53 -0
  127. package/dist/core/project-init.js.map +7 -0
  128. package/dist/core/relationship-migration-table.js +6639 -0
  129. package/dist/core/relationship-migration-table.js.map +7 -0
  130. package/dist/core/relationship-migration.js +272 -0
  131. package/dist/core/relationship-migration.js.map +7 -0
  132. package/dist/core/relationship-prune.js +49 -0
  133. package/dist/core/relationship-prune.js.map +7 -0
  134. package/dist/core/relationship-staging.js +161 -0
  135. package/dist/core/relationship-staging.js.map +7 -0
  136. package/dist/core/relationships.js +26 -4
  137. package/dist/core/relationships.js.map +2 -2
  138. package/dist/core/report-data-model.js +7 -13
  139. package/dist/core/report-data-model.js.map +2 -2
  140. package/dist/core/staged-changeset-storage.js +145 -18
  141. package/dist/core/staged-changeset-storage.js.map +2 -2
  142. package/dist/core/staged-source-reference.js +28 -0
  143. package/dist/core/staged-source-reference.js.map +7 -0
  144. package/dist/core/staging-area.js +166 -24
  145. package/dist/core/staging-area.js.map +2 -2
  146. package/dist/core/upgrade-plan.js +117 -0
  147. package/dist/core/upgrade-plan.js.map +7 -0
  148. package/dist/core/virtual-projection.js +13 -5
  149. package/dist/core/virtual-projection.js.map +2 -2
  150. package/dist/export/archimate-exporter.js +1 -0
  151. package/dist/export/archimate-exporter.js.map +2 -2
  152. package/dist/generated/compiled-validators.js +2 -2
  153. package/dist/generated/compiled-validators.js.map +2 -2
  154. package/dist/generated/layer-registry.js +16 -16
  155. package/dist/generated/layer-registry.js.map +2 -2
  156. package/dist/generated/node-types.js +186 -168
  157. package/dist/generated/node-types.js.map +2 -2
  158. package/dist/generated/relationship-index.js +2468 -7913
  159. package/dist/generated/relationship-index.js.map +2 -2
  160. package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
  161. package/dist/integrations/claude_code/agents/dr-architect.md +30 -3
  162. package/dist/integrations/claude_code/agents/dr-extractor.md +8 -2
  163. package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
  164. package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
  165. package/dist/integrations/claude_code/commands/dr-map.md +20 -1
  166. package/dist/integrations/claude_code/commands/dr-plan.md +66 -0
  167. package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
  168. package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
  169. package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
  170. package/dist/integrations/claude_code/docs/MCP_SERVER.md +47 -26
  171. package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
  172. package/dist/integrations/claude_code/docs/README.md +68 -2
  173. package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
  174. package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
  175. package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
  176. package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
  177. package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
  178. package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
  179. package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
  180. package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
  181. package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
  182. package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
  183. package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
  184. package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
  185. package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
  186. package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
  187. package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +95 -15
  188. package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +178 -0
  189. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +8 -0
  190. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +6 -1
  191. package/dist/integrations/github_copilot/agents/dr-architect.md +3 -1
  192. package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
  193. package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
  194. package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
  195. package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
  196. package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
  197. package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
  198. package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
  199. package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
  200. package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
  201. package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
  202. package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
  203. package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
  204. package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +225 -102
  205. package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +177 -0
  206. package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
  207. package/dist/integrations/hash-utils.js.map +2 -2
  208. package/dist/mcp/api-key-manager.js +14 -1
  209. package/dist/mcp/api-key-manager.js.map +2 -2
  210. package/dist/mcp/changeset-only.js +45 -0
  211. package/dist/mcp/changeset-only.js.map +7 -0
  212. package/dist/mcp/project-root.js +85 -0
  213. package/dist/mcp/project-root.js.map +7 -0
  214. package/dist/mcp/server-instructions.js +27 -0
  215. package/dist/mcp/server-instructions.js.map +7 -0
  216. package/dist/mcp/strict-schema.js +52 -0
  217. package/dist/mcp/strict-schema.js.map +7 -0
  218. package/dist/mcp/tool-registry.js +55 -5
  219. package/dist/mcp/tool-registry.js.map +2 -2
  220. package/dist/mcp/tools/changeset-analysis-tools.js +98 -0
  221. package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
  222. package/dist/mcp/tools/changeset-commit.js +224 -0
  223. package/dist/mcp/tools/changeset-commit.js.map +7 -0
  224. package/dist/mcp/tools/changeset-editing.js +260 -0
  225. package/dist/mcp/tools/changeset-editing.js.map +7 -0
  226. package/dist/mcp/tools/changeset-lifecycle.js +83 -0
  227. package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
  228. package/dist/mcp/tools/changeset-shared.js +31 -0
  229. package/dist/mcp/tools/changeset-shared.js.map +7 -0
  230. package/dist/mcp/tools/changeset-show.js +17 -1
  231. package/dist/mcp/tools/changeset-show.js.map +2 -2
  232. package/dist/mcp/tools/changeset-verify.js +49 -0
  233. package/dist/mcp/tools/changeset-verify.js.map +7 -0
  234. package/dist/mcp/tools/codebase-extract.js +836 -0
  235. package/dist/mcp/tools/codebase-extract.js.map +7 -0
  236. package/dist/mcp/tools/codebase-index.js +125 -0
  237. package/dist/mcp/tools/codebase-index.js.map +7 -0
  238. package/dist/mcp/tools/codebase-shared.js +265 -0
  239. package/dist/mcp/tools/codebase-shared.js.map +7 -0
  240. package/dist/mcp/tools/codebase-verify.js +48 -0
  241. package/dist/mcp/tools/codebase-verify.js.map +7 -0
  242. package/dist/mcp/tools/index.js +34 -1
  243. package/dist/mcp/tools/index.js.map +2 -2
  244. package/dist/mcp/tools/model-add.js +191 -74
  245. package/dist/mcp/tools/model-add.js.map +2 -2
  246. package/dist/mcp/tools/model-delete.js +2 -2
  247. package/dist/mcp/tools/model-delete.js.map +2 -2
  248. package/dist/mcp/tools/model-search.js +14 -0
  249. package/dist/mcp/tools/model-search.js.map +2 -2
  250. package/dist/mcp/tools/model-show.js +29 -2
  251. package/dist/mcp/tools/model-show.js.map +2 -2
  252. package/dist/mcp/tools/model-trace.js +93 -30
  253. package/dist/mcp/tools/model-trace.js.map +2 -2
  254. package/dist/mcp/tools/model-update.js +46 -26
  255. package/dist/mcp/tools/model-update.js.map +2 -2
  256. package/dist/mcp/tools/model-validate.js +46 -4
  257. package/dist/mcp/tools/model-validate.js.map +2 -2
  258. package/dist/mcp/tools/project-status-collectors.js +381 -0
  259. package/dist/mcp/tools/project-status-collectors.js.map +7 -0
  260. package/dist/mcp/tools/project-status.js +237 -0
  261. package/dist/mcp/tools/project-status.js.map +7 -0
  262. package/dist/mcp/tools/project-tools.js +505 -0
  263. package/dist/mcp/tools/project-tools.js.map +7 -0
  264. package/dist/mcp/tools/relationship-tools.js +167 -0
  265. package/dist/mcp/tools/relationship-tools.js.map +7 -0
  266. package/dist/mcp/tools/review-open.js +210 -0
  267. package/dist/mcp/tools/review-open.js.map +7 -0
  268. package/dist/mcp/tools/shared.js +35 -4
  269. package/dist/mcp/tools/shared.js.map +2 -2
  270. package/dist/mcp/tools/spec-type.js +90 -0
  271. package/dist/mcp/tools/spec-type.js.map +7 -0
  272. package/dist/reports/model-report-orchestrator.js +31 -2
  273. package/dist/reports/model-report-orchestrator.js.map +2 -2
  274. package/dist/schemas/bundled/analyzers/cbm.json +1200 -70
  275. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  276. package/dist/schemas/bundled/api.json +1029 -1658
  277. package/dist/schemas/bundled/apm.json +499 -734
  278. package/dist/schemas/bundled/application.json +735 -371
  279. package/dist/schemas/bundled/base.json +3 -220
  280. package/dist/schemas/bundled/business.json +452 -366
  281. package/dist/schemas/bundled/data-model.json +395 -411
  282. package/dist/schemas/bundled/data-store.json +334 -558
  283. package/dist/schemas/bundled/manifest.json +18 -18
  284. package/dist/schemas/bundled/motivation.json +349 -368
  285. package/dist/schemas/bundled/navigation.json +415 -731
  286. package/dist/schemas/bundled/product.json +325 -159
  287. package/dist/schemas/bundled/security.json +864 -1232
  288. package/dist/schemas/bundled/technology.json +574 -703
  289. package/dist/schemas/bundled/testing.json +865 -1646
  290. package/dist/schemas/bundled/ux.json +739 -1352
  291. package/dist/server/schemas.js +8 -0
  292. package/dist/server/schemas.js.map +2 -2
  293. package/dist/server/server.js +686 -231
  294. package/dist/server/server.js.map +3 -3
  295. package/dist/server/viewer-registry.js +264 -0
  296. package/dist/server/viewer-registry.js.map +7 -0
  297. package/dist/server-entry.js +2 -1
  298. package/dist/server-entry.js.map +2 -2
  299. package/dist/telemetry/index.js +1 -1
  300. package/dist/utils/ansi-suppressor.js +6 -8
  301. package/dist/utils/ansi-suppressor.js.map +2 -2
  302. package/dist/utils/attribute-patch.js +41 -0
  303. package/dist/utils/attribute-patch.js.map +7 -0
  304. package/dist/utils/error-hints.js +29 -0
  305. package/dist/utils/error-hints.js.map +7 -0
  306. package/dist/utils/errors.js +9 -3
  307. package/dist/utils/errors.js.map +2 -2
  308. package/dist/utils/id-generator.js +5 -2
  309. package/dist/utils/id-generator.js.map +2 -2
  310. package/dist/utils/project-paths.js +26 -1
  311. package/dist/utils/project-paths.js.map +2 -2
  312. package/dist/utils/source-reference.js +64 -31
  313. package/dist/utils/source-reference.js.map +2 -2
  314. package/dist/utils/spec-version.js +2 -2
  315. package/dist/utils/spec-version.js.map +1 -1
  316. package/dist/validators/composed-reference-validator.js +5 -27
  317. package/dist/validators/composed-reference-validator.js.map +2 -2
  318. package/dist/validators/model-warnings.js +28 -0
  319. package/dist/validators/model-warnings.js.map +7 -0
  320. package/dist/validators/reference-validator.js +0 -7
  321. package/dist/validators/reference-validator.js.map +2 -2
  322. package/dist/validators/relationship-schema-validator.js +3 -3
  323. package/dist/validators/relationship-schema-validator.js.map +2 -2
  324. package/dist/validators/schema-validator.js +27 -9
  325. package/dist/validators/schema-validator.js.map +2 -2
  326. package/dist/validators/types.js.map +1 -1
  327. package/dist/viewer/README.md +6 -6
  328. package/dist/viewer/assets/main-CUOQGGRA.css +1 -0
  329. package/dist/viewer/assets/main-DvH3gYrI.js +84 -0
  330. package/dist/viewer/index.html +2 -2
  331. package/dist/viewer/manifest.json +10 -10
  332. package/dist/viewer/version.json +1 -1
  333. package/package.json +2 -1
  334. package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
  335. package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
@@ -1,17 +1,24 @@
1
1
  import { spawnSync } from "child_process";
2
2
  import * as path from "path";
3
- import { readFile } from "fs/promises";
3
+ import { readFile, writeFile } from "fs/promises";
4
4
  import { CLIError, ErrorCategory, handleWarning } from "../utils/errors.js";
5
5
  import { VALID_HTTP_METHODS } from "./types.js";
6
6
  import { StdioClient } from "./stdio-client.js";
7
7
  import { readIndexMeta, writeIndexMeta } from "./session-state.js";
8
8
  import { getCliVersion } from "./version.js";
9
9
  import { StagingAreaManager } from "../core/staging-area.js";
10
+ import { DR_OWN_DIRS } from "./code-changes.js";
10
11
  import { VerifyEngine } from "./verify-engine.js";
11
12
  import { clampDepth, shapeCallGraphNode, determineEdgeType } from "./call-graph-utils.js";
12
13
  import { evaluateHeuristic, capConfidence, matchPattern, getKnownHeuristicNames } from "./heuristic-utils.js";
13
14
  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
+ import { CbmTools, CbmToolError, ROUTE_HANDLERS_QUERY, attachRouteHandlers, nodeColumns, nodesFromQuery, relativeToRoot, routeHandlersFromQuery } from "./cbm-responses.js";
16
+ import { cbmCardGraph } from "./cards/graph.js";
17
+ import { classifyTier0, defaultLabels } from "./cards/classify.js";
18
+ import { CARD_FIELDS } from "./cards/build.js";
19
+ import { tier0RulesFrom } from "./tier0-rules.js";
20
+ import { collectExtraction, extractionConfigFrom } from "./cards/extract.js";
21
+ const EXTRACT_CALL_TIMEOUT_MS = 12e4, 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
22
  function isLikelyHttpRoute(qualifiedName) {
16
23
  const httpPath = qualifiedName.replace(/^__route__[A-Z]+__/, "");
17
24
  return !SOURCE_CODE_EXT_RE.test(httpPath) && !httpPath.startsWith("/.dr/") && !httpPath.startsWith("/schemas/");
@@ -24,21 +31,17 @@ function inferHttpPathFromFunctionName(name) {
24
31
  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
32
  return segment ? `/${segment}` : "/";
26
33
  }
34
+ const SERVICE_SOURCE_TYPES = /* @__PURE__ */ new Set(["applicationfunction", "applicationcomponent", "applicationservice"]);
27
35
  class CbmAnalyzer {
28
36
  mapper;
29
37
  testCodePatternWarned = !1;
30
38
  warnedHeuristics = /* @__PURE__ */ new Set();
31
39
  /**
32
- * Default regex patterns for test code detection
40
+ * Test code detection when the mapping has no test_code_exclusion rule: the same test-file
41
+ * pattern as the mapping's rule and its tier0_test_file rule.
33
42
  */
34
43
  static DEFAULT_TEST_PATTERNS = [
35
- /\.test\./,
36
- /\.spec\./,
37
- /\/tests?\//,
38
- /\/__tests__\//,
39
- /\/test-/,
40
- /\/fixtures?\//,
41
- /\/__fixtures__\//
44
+ /((^|\/)(tests?|__tests__|__fixtures__|fixtures?)\/|(^|\/)test_[^/]*\.py$|(^|\/)conftest\.py$|_test\.(go|py)$|\.(test|spec)\.[cm]?[jt]sx?$|Tests?\.(java|kt)$)/
42
45
  ];
43
46
  constructor(mapper) {
44
47
  this.mapper = mapper;
@@ -171,10 +174,11 @@ class CbmAnalyzer {
171
174
  * Checks if the project is indexed and whether the index is fresh relative to git HEAD.
172
175
  *
173
176
  * @param projectRoot Absolute path to the project root
177
+ * @param options Optional codebase configuration
174
178
  * @returns Current status including detection, index state, and freshness
175
179
  */
176
- async status(projectRoot) {
177
- const detected = await this.detect(projectRoot), analyzerName = this.mapper.getAnalyzerName(), indexMeta = await readIndexMeta(projectRoot, analyzerName);
180
+ async status(projectRoot, options) {
181
+ const detected = await this.detect(projectRoot), effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, analyzerName = this.mapper.getAnalyzerName(), indexMeta = await readIndexMeta(projectRoot, analyzerName);
178
182
  if (!(indexMeta !== null))
179
183
  return {
180
184
  detected,
@@ -184,7 +188,7 @@ class CbmAnalyzer {
184
188
  let fresh = !1;
185
189
  try {
186
190
  const currentHeadResult = spawnSync("git", ["rev-parse", "HEAD"], {
187
- cwd: projectRoot,
191
+ cwd: effectiveCodebaseRoot,
188
192
  stdio: "pipe",
189
193
  encoding: "utf-8"
190
194
  });
@@ -192,7 +196,11 @@ class CbmAnalyzer {
192
196
  fresh = currentHeadResult.stdout.trim() === indexMeta.git_head;
193
197
  else {
194
198
  const gitError = currentHeadResult.stderr?.trim() || "git returned non-zero status";
195
- handleWarning("Failed to check git freshness", [
199
+ options?.codebaseRoot ? handleWarning("Failed to check git freshness in codebase root", [
200
+ `Codebase root: ${effectiveCodebaseRoot}`,
201
+ `Git error: ${gitError}`,
202
+ "Index freshness cannot be determined - treating as stale"
203
+ ]) : handleWarning("Failed to check git freshness", [
196
204
  `Git error: ${gitError}`,
197
205
  "Index freshness cannot be determined - treating as stale"
198
206
  ]), fresh = !1;
@@ -221,11 +229,11 @@ class CbmAnalyzer {
221
229
  * Skips indexing if the index is fresh (git HEAD matches) unless --force is set.
222
230
  *
223
231
  * @param projectRoot Absolute path to the project root
224
- * @param options Optional configuration
232
+ * @param options Optional configuration including force flag and codebase root
225
233
  * @returns Index result with counts and metadata
226
234
  */
227
235
  async index(projectRoot, options) {
228
- const status = await this.status(projectRoot);
236
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
229
237
  if (status.indexed && status.fresh && !options?.force) {
230
238
  handleWarning("Index is fresh. Use --force to reindex.", [
231
239
  `Last indexed: ${status.last_indexed}`
@@ -252,10 +260,8 @@ class CbmAnalyzer {
252
260
  name: "dr-cli",
253
261
  version
254
262
  });
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) {
263
+ const tools = new CbmTools(client);
264
+ if ((await tools.listProjects()).some((p) => p.root_path === effectiveCodebaseRoot) && status.fresh && !options?.force) {
259
265
  if (handleWarning("Project already indexed in CBM backend with fresh index.", ["Use --force to re-index the project"]), !status.index_meta)
260
266
  throw new CLIError("Project exists in CBM backend but local metadata is missing", ErrorCategory.VALIDATION, [
261
267
  "Use --force to re-index and regenerate metadata",
@@ -270,13 +276,14 @@ class CbmAnalyzer {
270
276
  };
271
277
  }
272
278
  const headResult = spawnSync("git", ["rev-parse", "HEAD"], {
273
- cwd: projectRoot,
279
+ cwd: effectiveCodebaseRoot,
274
280
  stdio: "pipe",
275
281
  encoding: "utf-8"
276
282
  });
277
283
  if (headResult.status !== 0)
278
284
  throw new CLIError("Failed to get git HEAD", ErrorCategory.SYSTEM, [
279
- "Ensure the project is a git repository",
285
+ "Ensure the codebase root is a git repository",
286
+ `Codebase root: ${effectiveCodebaseRoot}`,
280
287
  "Verify git is installed and functional",
281
288
  `Error: ${headResult.stderr?.trim() || "unknown error"}`
282
289
  ]);
@@ -288,12 +295,14 @@ class CbmAnalyzer {
288
295
  const errorMsg = error instanceof Error ? error.message : String(error);
289
296
  handleWarning("Failed to read active changeset", [`Error: ${errorMsg}`, "Continuing with indexing"]);
290
297
  }
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;
298
+ try {
299
+ const ignore = await ensureCbmIgnore(effectiveCodebaseRoot, projectRoot);
300
+ ignore.added.length > 0 && console.log(`Added ${ignore.added.join(", ")} to ${ignore.path} so the index leaves DR's own files out`);
301
+ } catch (error) {
302
+ const errorMsg = error instanceof Error ? error.message : String(error);
303
+ handleWarning("Could not update .cbmignore; DR's own files may be indexed", [`Error: ${errorMsg}`]);
304
+ }
305
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString(), indexResponse = await tools.indexRepository(effectiveCodebaseRoot, options?.timeoutMs ? { timeout: options.timeoutMs } : void 0), nodeCount = typeof indexResponse.nodes == "number" ? indexResponse.nodes : 0, edgeCount = typeof indexResponse.edges == "number" ? indexResponse.edges : 0;
297
306
  typeof indexResponse.nodes != "number" && handleWarning("Invalid node count from index_repository", [
298
307
  `Expected number, got ${typeof indexResponse.nodes}: ${String(indexResponse.nodes)}`,
299
308
  "Defaulting to 0 - index may be incomplete or corrupted"
@@ -318,26 +327,25 @@ class CbmAnalyzer {
318
327
  }
319
328
  }
320
329
  /**
321
- * Validate search_graph response structure
330
+ * Fetch Route nodes with their HANDLES-edge handlers attached.
322
331
  *
323
- * Ensures response is an object and nodes is an array.
324
- * Throws CLIError if validation fails.
332
+ * CBM Route nodes carry only `{ method, source }` and often no file (every fastapi route,
333
+ * for example); the handler function, its symbol, lines and file come from HANDLES edges.
334
+ * If the handler query fails the routes are returned as they are.
325
335
  *
326
336
  * @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
337
  */
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;
338
+ async fetchRoutes(tools, projectName, label) {
339
+ const routes = await tools.searchGraph({ label, project: projectName });
340
+ try {
341
+ const handlers = routeHandlersFromQuery(await tools.queryGraph(ROUTE_HANDLERS_QUERY, projectName));
342
+ return attachRouteHandlers(routes, handlers);
343
+ } catch (error) {
344
+ const errorMsg = error instanceof Error ? error.message : String(error);
345
+ return handleWarning("Could not read route handlers (HANDLES edges); routes lack handler details", [
346
+ `Error: ${errorMsg}`
347
+ ]), routes;
348
+ }
341
349
  }
342
350
  /**
343
351
  * Resolve the internal project name used by codebase-memory-mcp for a given root path.
@@ -346,12 +354,12 @@ class CbmAnalyzer {
346
354
  * query tools require the internal `name`, not the absolute `root_path`.
347
355
  * Falls back to the absolute path with a warning if the project is not found.
348
356
  */
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);
357
+ async resolveProjectName(client, codebaseRoot) {
358
+ const match = (await (client instanceof CbmTools ? client : new CbmTools(client)).listProjects()).find((p) => p.root_path === codebaseRoot);
351
359
  return match ? match.name : (handleWarning("Project not found in CBM backend; using absolute path as project identifier", [
352
- `Project root: ${projectRoot}`,
360
+ `Codebase root: ${codebaseRoot}`,
353
361
  "Run `dr analyzer index` to ensure the project is indexed"
354
- ]), projectRoot);
362
+ ]), codebaseRoot);
355
363
  }
356
364
  /**
357
365
  * Query for API endpoints/routes in the indexed project
@@ -360,11 +368,12 @@ class CbmAnalyzer {
360
368
  * the analyzer's mapping, and filters based on test code exclusion rules.
361
369
  *
362
370
  * @param projectRoot Absolute path to the project root
371
+ * @param options Optional codebase configuration
363
372
  * @returns Array of endpoint candidates with confidence and source info
364
373
  * @throws CLIError if the project is not indexed
365
374
  */
366
- async endpoints(projectRoot) {
367
- const status = await this.status(projectRoot);
375
+ async endpoints(projectRoot, options) {
376
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
368
377
  if (!status.indexed)
369
378
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
370
379
  "Run `dr analyzer index` to index the project",
@@ -384,18 +393,14 @@ class CbmAnalyzer {
384
393
  name: "dr-cli",
385
394
  version
386
395
  });
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 = [];
396
+ 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
397
  for (const node of nodes) {
392
- const candidate = await this.transformNodeToEndpoint(node, routeMapping, projectRoot);
398
+ const candidate = await this.transformNodeToEndpoint(node, routeMapping, effectiveCodebaseRoot);
393
399
  candidate.source_file && (this.isTestCode(candidate) || candidates.push(candidate));
394
400
  }
395
401
  if (candidates.length === 0)
396
402
  try {
397
- const qResp = await client.invokeTool("query_graph", {
398
- query: `
403
+ const clientQuery = `
399
404
  MATCH (n:Function)
400
405
  WHERE (n.file_path CONTAINS 'client' OR n.file_path CONTAINS 'Client'
401
406
  OR n.file_path CONTAINS 'Api' OR n.file_path CONTAINS 'Service'
@@ -406,21 +411,17 @@ class CbmAnalyzer {
406
411
  OR n.name STARTS WITH 'delete' OR n.name STARTS WITH 'patch'
407
412
  OR n.name STARTS WITH 'create' OR n.name STARTS WITH 'update'
408
413
  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
- }
414
+ RETURN ${nodeColumns("n")}
415
+ `, clientNodes = nodesFromQuery(await tools.queryGraph(clientQuery, projectName));
416
+ for (const node of clientNodes) {
417
+ if (!node.file_path)
418
+ continue;
419
+ const candidate = this.shapeClientSideEndpoint(node, effectiveCodebaseRoot);
420
+ candidate && !this.isTestCode(candidate) && candidates.push(candidate);
422
421
  }
423
- } catch {
422
+ } catch (error) {
423
+ const errorMsg = error instanceof Error ? error.message : String(error);
424
+ handleWarning("Client-side endpoint fallback skipped: query_graph failed", [`Error: ${errorMsg}`]);
424
425
  }
425
426
  return this.warnOnDuplicateIdFragments(candidates, (c) => c.suggested_id_fragment, (c) => c.source_symbol || c.source_file || "(unknown)"), candidates;
426
427
  } finally {
@@ -436,11 +437,12 @@ class CbmAnalyzer {
436
437
  *
437
438
  * @param projectRoot Absolute path to the project root
438
439
  * @param rawQuery Raw Cypher query string
440
+ * @param options Optional codebase configuration
439
441
  * @returns Raw result from query_graph tool
440
442
  * @throws CLIError if project not indexed or query_graph tool unavailable
441
443
  */
442
- async query(projectRoot, rawQuery) {
443
- const status = await this.status(projectRoot);
444
+ async query(projectRoot, rawQuery, options) {
445
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
444
446
  if (!status.indexed)
445
447
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
446
448
  "Run `dr analyzer index` to index the project",
@@ -457,13 +459,13 @@ class CbmAnalyzer {
457
459
  name: "dr-cli",
458
460
  version
459
461
  });
460
- const projectName = await this.resolveProjectName(client, projectRoot);
462
+ const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
461
463
  try {
462
- return await client.invokeTool("query_graph", {
463
- query: rawQuery,
464
- project: projectName
465
- });
464
+ const result = await tools.queryGraph(rawQuery, projectName);
465
+ return { ...result, total: result.rows.length };
466
466
  } catch (error) {
467
+ if (error instanceof CbmToolError)
468
+ throw new CLIError(`Query failed: ${error.detail}`, ErrorCategory.USER, error.suggestions);
467
469
  const errorMessage = error instanceof Error ? error.message : String(error);
468
470
  throw errorMessage.includes("query_graph") || errorMessage.includes("Unknown method") ? new CLIError("query_graph not supported by this analyzer", ErrorCategory.USER, [
469
471
  "The query_graph tool is optional in the CBM analyzer contract",
@@ -507,7 +509,7 @@ class CbmAnalyzer {
507
509
  *
508
510
  * @private
509
511
  */
510
- async transformNodeToEndpoint(node, mapping, projectRoot) {
512
+ async transformNodeToEndpoint(node, mapping, codebaseRoot) {
511
513
  const properties = node.properties ?? {}, nodeId = String(node.name ?? node.id ?? "(unknown)");
512
514
  let confidence = mapping.confidence;
513
515
  const suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? "")), suggestedIdFragment = suggestedName;
@@ -527,9 +529,9 @@ class CbmAnalyzer {
527
529
  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
530
  (!handlerQualifiedName || !sourceSymbol) && (confidence = capConfidence(confidence));
529
531
  let sourceFile = String(node.file_path ?? properties.file_path ?? properties.source_file ?? "");
530
- if (sourceFile && projectRoot)
532
+ if (sourceFile && codebaseRoot)
531
533
  try {
532
- sourceFile = path.relative(projectRoot, sourceFile);
534
+ sourceFile = relativeToRoot(codebaseRoot, sourceFile);
533
535
  } catch (error) {
534
536
  const errorMsg = error instanceof Error ? error.message : String(error);
535
537
  handleWarning(`Failed to compute relative path for ${nodeId}`, [
@@ -577,14 +579,14 @@ class CbmAnalyzer {
577
579
  * Build a low-confidence EndpointCandidate from a client-side API function node.
578
580
  * Used as a fallback when no server-side Route nodes are found (ISSUE-003).
579
581
  */
580
- shapeClientSideEndpoint(node, projectRoot) {
582
+ shapeClientSideEndpoint(node, codebaseRoot) {
581
583
  const name = String(node.name ?? node.id ?? "");
582
584
  if (!name)
583
585
  return null;
584
586
  let sourceFile = String(node.file_path ?? "");
585
- if (sourceFile && projectRoot)
587
+ if (sourceFile && codebaseRoot)
586
588
  try {
587
- sourceFile = path.relative(projectRoot, sourceFile);
589
+ sourceFile = relativeToRoot(codebaseRoot, sourceFile);
588
590
  } catch {
589
591
  }
590
592
  if (!sourceFile)
@@ -637,11 +639,12 @@ class CbmAnalyzer {
637
639
  * at "medium" and excludes test files.
638
640
  *
639
641
  * @param projectRoot Absolute path to the project root
642
+ * @param options Optional codebase configuration
640
643
  * @returns Array of service candidates with qualifying heuristics populated
641
644
  * @throws CLIError if the project is not indexed
642
645
  */
643
- async services(projectRoot) {
644
- const status = await this.status(projectRoot);
646
+ async services(projectRoot, options) {
647
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
645
648
  if (!status.indexed)
646
649
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
647
650
  "Run `dr analyzer index` to index the project",
@@ -661,7 +664,7 @@ class CbmAnalyzer {
661
664
  const nodeLabels = this.mapper.getNodeLabels(), applicationLabels = [];
662
665
  for (const label of nodeLabels) {
663
666
  const mapping = this.mapper.getNodeMapping(label);
664
- mapping && mapping.dr_layer === "application" && applicationLabels.push(label);
667
+ mapping && mapping.dr_layer === "application" && SERVICE_SOURCE_TYPES.has(mapping.dr_element_type) && applicationLabels.push(label);
665
668
  }
666
669
  if (applicationLabels.length === 0)
667
670
  return handleWarning("No application-layer labels found in analyzer mapping", [
@@ -669,18 +672,15 @@ class CbmAnalyzer {
669
672
  "Services detection requires at least one mapped application-layer node type",
670
673
  "Returning empty services list"
671
674
  ]), [];
672
- const candidates = [], projectName = await this.resolveProjectName(client, projectRoot), labelPriority = { Class: 0, Module: 1 };
675
+ const candidates = [], tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot), labelPriority = { Class: 0, Module: 1 };
673
676
  applicationLabels.sort((a, b) => (labelPriority[a] ?? 99) - (labelPriority[b] ?? 99));
674
677
  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);
678
+ const nodes = await tools.searchGraph({ label, project: projectName }), nodeMapping = this.mapper.getNodeMapping(label);
679
679
  if (!nodeMapping)
680
680
  continue;
681
681
  const promotionHeuristicNames = nodeMapping.promotion_heuristics || [];
682
682
  for (const node of nodes) {
683
- const candidate = await this.transformNodeToService(node, nodeMapping, projectRoot, promotionHeuristicNames);
683
+ const candidate = await this.transformNodeToService(node, nodeMapping, effectiveCodebaseRoot, promotionHeuristicNames);
684
684
  candidate.qualifying_heuristics.length > 0 && !this.isTestCode(candidate) && candidates.push(candidate);
685
685
  }
686
686
  }
@@ -700,12 +700,12 @@ class CbmAnalyzer {
700
700
  *
701
701
  * @private
702
702
  */
703
- async transformNodeToService(node, mapping, projectRoot, promotionHeuristicNames) {
703
+ async transformNodeToService(node, mapping, codebaseRoot, promotionHeuristicNames) {
704
704
  const properties = node.properties ?? {}, suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? "")), suggestedIdFragment = suggestedName;
705
705
  let sourceFile = node.file_path ?? "";
706
- if (sourceFile && projectRoot)
706
+ if (sourceFile && codebaseRoot)
707
707
  try {
708
- sourceFile = path.relative(projectRoot, sourceFile);
708
+ sourceFile = relativeToRoot(codebaseRoot, sourceFile);
709
709
  } catch (error) {
710
710
  const errorMsg = error instanceof Error ? error.message : String(error);
711
711
  handleWarning(`Failed to compute relative path for ${String(node.name ?? node.id ?? "(unknown)")}`, [
@@ -795,11 +795,12 @@ class CbmAnalyzer {
795
795
  * naming_indicators from the heuristic parameters. All candidates have confidence "low".
796
796
  *
797
797
  * @param projectRoot Absolute path to the project root
798
+ * @param options Optional codebase configuration
798
799
  * @returns Array of datastore candidates aggregated by inferred datastore
799
800
  * @throws CLIError if the project is not indexed
800
801
  */
801
- async datastores(projectRoot) {
802
- const status = await this.status(projectRoot);
802
+ async datastores(projectRoot, options) {
803
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
803
804
  if (!status.indexed)
804
805
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
805
806
  "Run `dr analyzer index` to index the project",
@@ -822,20 +823,17 @@ class CbmAnalyzer {
822
823
  "The analyzer configuration is missing the datastore_detection heuristic",
823
824
  "Run `npm run build:spec` to recompile the analyzer artifacts"
824
825
  ]);
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
+ 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
827
  let allNodes = [];
827
828
  try {
828
- const queryResponse = await client.invokeTool("query_graph", {
829
- query: `
829
+ const importsQuery = `
830
830
  MATCH (n1)-[r:IMPORTS]->(n2)
831
- RETURN DISTINCT n1 as node
831
+ RETURN DISTINCT ${nodeColumns("n1")}
832
832
  UNION
833
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"));
834
+ RETURN DISTINCT ${nodeColumns("n2")}
835
+ `;
836
+ allNodes = nodesFromQuery(await tools.queryGraph(importsQuery, projectName));
839
837
  } catch (error) {
840
838
  const errorMsg = error instanceof Error ? error.message : String(error);
841
839
  handleWarning("query_graph tool failed; falling back to search_graph for datastore detection", [
@@ -845,13 +843,8 @@ class CbmAnalyzer {
845
843
  "Ensure query_graph is supported by your analyzer version for best results"
846
844
  ]);
847
845
  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
- }
846
+ for (const label of nodeLabels)
847
+ allNodes.push(...await tools.searchGraph({ label, project: projectName }));
855
848
  }
856
849
  for (const node of allNodes) {
857
850
  const filePath = node.file_path ?? "", importTargetName = String(node.label ?? node.properties?.name ?? node.id ?? "").toLowerCase();
@@ -874,9 +867,9 @@ class CbmAnalyzer {
874
867
  ]) : (datastoreSignals.set(datastoreName, []), datastoreFirstSource.set(datastoreName, importTargetName));
875
868
  const signals = datastoreSignals.get(datastoreName);
876
869
  let relativeFile = filePath;
877
- if (filePath && projectRoot)
870
+ if (filePath && effectiveCodebaseRoot)
878
871
  try {
879
- relativeFile = path.relative(projectRoot, filePath);
872
+ relativeFile = relativeToRoot(effectiveCodebaseRoot, filePath);
880
873
  } catch (error) {
881
874
  const errorMsg = error instanceof Error ? error.message : String(error);
882
875
  handleWarning("Failed to compute relative path in datastore detection", [
@@ -925,8 +918,7 @@ class CbmAnalyzer {
925
918
  }
926
919
  const browserStorageTypes = ["local-storage", "session-storage"], browserStorageSignals = /* @__PURE__ */ new Map();
927
920
  try {
928
- const storageResp = await client.invokeTool("query_graph", {
929
- query: `
921
+ const storageQuery = `
930
922
  MATCH (n:Function)
931
923
  WHERE n.file_path CONTAINS 'Store' OR n.file_path CONTAINS 'store'
932
924
  OR n.file_path CONTAINS 'storage' OR n.file_path CONTAINS 'Storage'
@@ -934,24 +926,13 @@ class CbmAnalyzer {
934
926
  OR n.file_path CONTAINS 'preference' OR n.file_path CONTAINS 'Preference'
935
927
  OR n.file_path CONTAINS 'layout' OR n.file_path CONTAINS 'Layout'
936
928
  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
- }
929
+ RETURN ${nodeColumns("n")}
930
+ `, storageNodes = nodesFromQuery(await tools.queryGraph(storageQuery, projectName));
931
+ for (const node of storageNodes) {
932
+ if (!node.file_path)
933
+ continue;
934
+ const relFile = relativeToRoot(effectiveCodebaseRoot, node.file_path), storageKey = /session/i.test(relFile) ? "session-storage" : "local-storage";
935
+ browserStorageSignals.has(storageKey) || browserStorageSignals.set(storageKey, /* @__PURE__ */ new Set()), browserStorageSignals.get(storageKey).add(relFile);
955
936
  }
956
937
  } catch {
957
938
  }
@@ -1014,11 +995,12 @@ class CbmAnalyzer {
1014
995
  * @param projectRoot Absolute path to the project root
1015
996
  * @param qualifiedName Fully qualified symbol name (e.g., "com.example.UserService.getUser")
1016
997
  * @param depth Maximum traversal depth (default 3, clamped to max 10)
998
+ * @param options Optional codebase configuration
1017
999
  * @returns Array of callers in the call graph
1018
1000
  * @throws CLIError if project not indexed or analyzer not installed
1019
1001
  */
1020
- async callers(projectRoot, qualifiedName, depth) {
1021
- return this.traceCallPath(projectRoot, qualifiedName, depth, "inbound");
1002
+ async callers(projectRoot, qualifiedName, depth, options) {
1003
+ return this.traceCallPath(projectRoot, qualifiedName, depth, "inbound", options);
1022
1004
  }
1023
1005
  /**
1024
1006
  * Query for callees of a specific function or symbol
@@ -1031,11 +1013,12 @@ class CbmAnalyzer {
1031
1013
  * @param projectRoot Absolute path to the project root
1032
1014
  * @param qualifiedName Fully qualified symbol name (e.g., "com.example.UserService.getUser")
1033
1015
  * @param depth Maximum traversal depth (default 3, clamped to max 10)
1016
+ * @param options Optional codebase configuration
1034
1017
  * @returns Array of callees in the call graph
1035
1018
  * @throws CLIError if project not indexed or analyzer not installed
1036
1019
  */
1037
- async callees(projectRoot, qualifiedName, depth) {
1038
- return this.traceCallPath(projectRoot, qualifiedName, depth, "outbound");
1020
+ async callees(projectRoot, qualifiedName, depth, options) {
1021
+ return this.traceCallPath(projectRoot, qualifiedName, depth, "outbound", options);
1039
1022
  }
1040
1023
  /**
1041
1024
  * Cypher fallback for call graph traversal when trace_path is unavailable.
@@ -1045,7 +1028,7 @@ class CbmAnalyzer {
1045
1028
  *
1046
1029
  * @private
1047
1030
  */
1048
- async callGraphViaCypher(binaryPath, projectName, qualifiedName, direction, depth, projectRoot) {
1031
+ async callGraphViaCypher(binaryPath, projectName, qualifiedName, direction, depth, codebaseRoot) {
1049
1032
  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
1033
  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
1034
  const client = new StdioClient();
@@ -1053,16 +1036,13 @@ class CbmAnalyzer {
1053
1036
  client.spawn(binaryPath);
1054
1037
  const version = await getCliVersion();
1055
1038
  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) => {
1039
+ const queryResponse = await new CbmTools(client).queryGraph(cypher, projectName), cols = queryResponse.columns;
1040
+ return queryResponse.rows.map((row) => {
1061
1041
  const obj = {};
1062
1042
  return cols.forEach((col, i) => {
1063
1043
  const key = col.includes(".") ? col.slice(col.lastIndexOf(".") + 1) : col;
1064
1044
  obj[key] = row[i];
1065
- }), shapeCallGraphNode(obj, projectRoot);
1045
+ }), shapeCallGraphNode(obj, codebaseRoot);
1066
1046
  });
1067
1047
  } catch (error) {
1068
1048
  const errorMsg = error instanceof Error ? error.message : String(error);
@@ -1082,8 +1062,8 @@ class CbmAnalyzer {
1082
1062
  *
1083
1063
  * @private
1084
1064
  */
1085
- async traceCallPath(projectRoot, qualifiedName, depth, direction) {
1086
- const status = await this.status(projectRoot);
1065
+ async traceCallPath(projectRoot, qualifiedName, depth, direction, options) {
1066
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
1087
1067
  if (!status.indexed)
1088
1068
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
1089
1069
  "Run `dr analyzer index` to index the project",
@@ -1100,25 +1080,37 @@ class CbmAnalyzer {
1100
1080
  name: "dr-cli",
1101
1081
  version
1102
1082
  });
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 = [];
1083
+ const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
1084
+ let trace;
1085
+ try {
1086
+ trace = await tools.tracePath({
1087
+ function_name: qualifiedName,
1088
+ direction,
1089
+ depth: clampedDepth,
1090
+ project: projectName
1091
+ });
1092
+ } catch (error) {
1093
+ throw error instanceof CbmToolError && /not found/i.test(error.detail) ? new CLIError(`Symbol not found in the code graph: ${qualifiedName}`, ErrorCategory.NOT_FOUND, [
1094
+ ...error.suggestions ?? [],
1095
+ "Pass the fully qualified name as CBM records it (see `dr analyzer services --json`)"
1096
+ ]) : error;
1097
+ }
1098
+ if (!trace)
1099
+ return this.callGraphViaCypher(detection.binary_path, projectName, qualifiedName, direction, clampedDepth, effectiveCodebaseRoot);
1100
+ const nodes = trace.nodes, edges = trace.edges;
1101
+ if (trace.shape === "grouped" && nodes.length > 0)
1102
+ try {
1103
+ const files = await tools.filePathsOf(nodes.map((n) => String(n.qualified_name ?? "")), projectName);
1104
+ for (const node of nodes)
1105
+ node.file_path ??= files.get(String(node.qualified_name ?? ""));
1106
+ } catch (error) {
1107
+ const errorMsg = error instanceof Error ? error.message : String(error);
1108
+ handleWarning("Could not look up source files for the call graph", [`Error: ${errorMsg}`]);
1109
+ }
1110
+ const validEdgeTypes = ["CALLS", ...this.mapper.getEdgeTypes().filter((t) => t !== "CALLS")], callGraphNodes = [];
1119
1111
  for (const node of nodes) {
1120
- const nodeQualifiedName = node.qualified_name || node.id;
1121
- let callGraphNode = shapeCallGraphNode(node, projectRoot);
1112
+ const nodeQualifiedName = String(node.qualified_name || node.id || "");
1113
+ let callGraphNode = shapeCallGraphNode(node, effectiveCodebaseRoot);
1122
1114
  const nodeDepth = typeof node.depth == "number" ? node.depth : 0, edgeType = determineEdgeType(nodeDepth, edges, nodeQualifiedName, validEdgeTypes);
1123
1115
  callGraphNode.edge_type = edgeType, callGraphNodes.push(callGraphNode);
1124
1116
  }
@@ -1156,6 +1148,85 @@ class CbmAnalyzer {
1156
1148
  source_symbol: sourceSymbol
1157
1149
  };
1158
1150
  }
1151
+ /**
1152
+ * Tier-0 classification of the indexed project (`dr analyzer classify --tier0-only`).
1153
+ *
1154
+ * Builds a card for every candidate symbol (src/analyzers/cards) and applies the Tier-0 rules
1155
+ * from this analyzer's extraction heuristics. Reads the graph only: no model calls, no writes.
1156
+ *
1157
+ * @throws CLIError if the project is not indexed or the mapping has no Tier-0 rules
1158
+ */
1159
+ async classify(projectRoot, options = {}) {
1160
+ const effectiveCodebaseRoot = options.codebaseRoot ?? projectRoot, rules = tier0RulesFrom(this.mapper.getHeuristics());
1161
+ if (rules.length === 0)
1162
+ throw new CLIError(`The ${this.name} analyzer mapping has no Tier-0 rules`, ErrorCategory.USER, [
1163
+ "Tier-0 rules are heuristics with a parameters.tier0 block in the analyzer's extraction-heuristics.json"
1164
+ ]);
1165
+ if (options.labels?.length) {
1166
+ const known = /* @__PURE__ */ new Set([...this.mapper.getNodeLabels(), ...defaultLabels(rules), ...Object.keys(CARD_FIELDS)]), unknown = options.labels.filter((l) => !known.has(l));
1167
+ if (unknown.length)
1168
+ throw new CLIError(`Unknown label${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}`, ErrorCategory.USER, [
1169
+ `Labels are case-sensitive; known labels: ${[...known].sort().join(", ")}`
1170
+ ]);
1171
+ }
1172
+ const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
1173
+ if (!status.indexed)
1174
+ throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
1175
+ "Run `dr analyzer index` to index the project",
1176
+ "Ensure codebase-memory-mcp is installed and working"
1177
+ ]);
1178
+ const detection = status.detected;
1179
+ if (!detection.installed)
1180
+ throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, [
1181
+ "Install codebase-memory-mcp: npm install -g codebase-memory-mcp"
1182
+ ]);
1183
+ const client = new StdioClient();
1184
+ try {
1185
+ client.spawn(detection.binary_path), await client.initialize({ name: "dr-cli", version: await getCliVersion() });
1186
+ const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
1187
+ return await classifyTier0(cbmCardGraph(tools, projectName), rules, this.name, {
1188
+ sourceRoot: effectiveCodebaseRoot,
1189
+ ...options
1190
+ });
1191
+ } finally {
1192
+ client.close();
1193
+ }
1194
+ }
1195
+ /** The Tier-0 rules, edge mappings and node-mapping decisions extraction runs with. */
1196
+ extractionConfig() {
1197
+ const nodeMappings = this.mapper.getNodeLabels().map((label) => this.mapper.getNodeMapping(label)).filter((m) => !!m), edgeMappings = this.mapper.getEdgeTypes().map((type) => this.mapper.getEdgeMapping(type)).filter((m) => !!m);
1198
+ return extractionConfigFrom(tier0RulesFrom(this.mapper.getHeuristics()), edgeMappings, nodeMappings);
1199
+ }
1200
+ /**
1201
+ * What `codebase_extract` reads from the index (src/analyzers/cards/extract.ts): cards with
1202
+ * their Tier-0 outcomes, the mapped edges and the symbols of the files asked about. Read-only.
1203
+ * Graph reads get two minutes each, past the client's 30s default, for large graphs.
1204
+ *
1205
+ * @throws CLIError if the project is not indexed or the mapping has no Tier-0 rules
1206
+ */
1207
+ async extract(projectRoot, options = {}) {
1208
+ const effectiveCodebaseRoot = options.codebaseRoot ?? projectRoot, config = this.extractionConfig();
1209
+ if (config.rules.length === 0)
1210
+ throw new CLIError(`The ${this.name} analyzer mapping has no Tier-0 rules`, ErrorCategory.USER);
1211
+ const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
1212
+ if (!status.indexed)
1213
+ throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, ["Run `dr analyzer index` to index the project"]);
1214
+ if (!status.detected.installed)
1215
+ throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, [
1216
+ "Install codebase-memory-mcp: npm install -g codebase-memory-mcp"
1217
+ ]);
1218
+ const client = new StdioClient();
1219
+ try {
1220
+ client.spawn(status.detected.binary_path), await client.initialize({ name: "dr-cli", version: await getCliVersion() });
1221
+ const tools = new CbmTools(client, { timeout: EXTRACT_CALL_TIMEOUT_MS }), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
1222
+ return await collectExtraction(cbmCardGraph(tools, projectName), config, this.name, {
1223
+ sourceRoot: effectiveCodebaseRoot,
1224
+ ...options
1225
+ });
1226
+ } finally {
1227
+ client.close();
1228
+ }
1229
+ }
1159
1230
  /**
1160
1231
  * Verify that graph-discovered routes align with model endpoints
1161
1232
  *
@@ -1163,12 +1234,12 @@ class CbmAnalyzer {
1163
1234
  * and delegates comparison to VerifyEngine. Entirely read-only.
1164
1235
  *
1165
1236
  * @param projectRoot Absolute path to the project root
1166
- * @param options Verification options
1237
+ * @param options Verification options including codebase root
1167
1238
  * @returns Comprehensive verification report
1168
1239
  * @throws CLIError if project not indexed or analyzer not installed
1169
1240
  */
1170
1241
  async verify(projectRoot, options) {
1171
- const status = await this.status(projectRoot);
1242
+ const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
1172
1243
  if (!status.indexed)
1173
1244
  throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
1174
1245
  "Run `dr analyzer index` to index the project",
@@ -1188,18 +1259,43 @@ class CbmAnalyzer {
1188
1259
  name: "dr-cli",
1189
1260
  version
1190
1261
  });
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;
1262
+ 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
1263
  return await engine.computeReport(projectRoot, routes, options, analyzerName, indexMeta);
1196
1264
  } finally {
1197
1265
  client.close();
1198
1266
  }
1199
1267
  }
1200
1268
  }
1269
+ const CBM_IGNORE_HEADING = "# Added by Documentation Robotics: its model, changesets and analyzer state are not code.";
1270
+ async function ensureCbmIgnore(codebaseRoot, projectRoot) {
1271
+ const file = path.join(codebaseRoot, ".cbmignore");
1272
+ let text = "", created = !0;
1273
+ try {
1274
+ text = await readFile(file, "utf-8"), created = !1;
1275
+ } catch (error) {
1276
+ if (error.code !== "ENOENT")
1277
+ throw error;
1278
+ }
1279
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")), folder = (line) => line.replace(/^\/+/, "").replace(/\/+$/, ""), listed = new Set(lines.filter((line) => !line.startsWith("!")).map(folder)), negated = new Set(lines.filter((line) => line.startsWith("!")).map((line) => folder(line.slice(1)))), added = [];
1280
+ for (const dir of DR_OWN_DIRS) {
1281
+ const relative = path.relative(codebaseRoot, path.join(projectRoot, dir)).split(path.sep).join("/");
1282
+ !relative || relative.startsWith("..") || path.isAbsolute(relative) || !listed.has(relative) && !negated.has(relative) && added.push(`/${relative}/`);
1283
+ }
1284
+ if (added.length === 0)
1285
+ return { path: file, added, created: !1 };
1286
+ const separator = text.length === 0 ? "" : text.endsWith(`
1287
+ `) ? `
1288
+ ` : `
1289
+
1290
+ `, heading = text.includes(CBM_IGNORE_HEADING) ? "" : `${CBM_IGNORE_HEADING}
1291
+ `;
1292
+ return await writeFile(file, `${text}${separator}${heading}${added.join(`
1293
+ `)}
1294
+ `), { path: file, added, created };
1295
+ }
1201
1296
  export {
1202
1297
  CbmAnalyzer,
1203
- SOURCE_CODE_EXT_RE
1298
+ SOURCE_CODE_EXT_RE,
1299
+ ensureCbmIgnore
1204
1300
  };
1205
1301
  //# sourceMappingURL=cbm-analyzer.js.map