@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
@@ -0,0 +1,342 @@
1
+ import * as path from "path";
2
+ import { CLIError, ErrorCategory } from "../utils/errors.js";
3
+ function relativeToRoot(codebaseRoot, filePath) {
4
+ return !filePath || !codebaseRoot || !path.isAbsolute(filePath) ? filePath : path.relative(codebaseRoot, filePath);
5
+ }
6
+ class CbmToolError extends CLIError {
7
+ tool;
8
+ detail;
9
+ hint;
10
+ constructor(tool, detail, hint) {
11
+ super(`codebase-memory-mcp ${tool} failed: ${detail}`, ErrorCategory.SYSTEM, hint ? [hint] : void 0), this.tool = tool, this.detail = detail, this.hint = hint, this.name = "CbmToolError";
12
+ }
13
+ }
14
+ const isObj = (v) => typeof v == "object" && v !== null && !Array.isArray(v), SEARCH_PAGE = 500, QUERY_PAGE = 5e3, TRACE_PAGE = 5e3, PROJECTS_PAGE = 500, MAX_OUTPUT_TOKENS = 1e6, MAX_PAGES = 1e3;
15
+ function assertToolResult(tool, raw) {
16
+ if (typeof raw == "string")
17
+ throw new CbmToolError(tool, raw.trim() || "empty response");
18
+ if (!isObj(raw))
19
+ return;
20
+ const hint = typeof raw.hint == "string" ? raw.hint : void 0;
21
+ if (typeof raw.error == "string")
22
+ throw new CbmToolError(tool, raw.error, hint);
23
+ if (raw.status === "error")
24
+ throw new CbmToolError(tool, typeof raw.message == "string" ? raw.message : "status: error", hint);
25
+ }
26
+ function groupedQualifiedName(prefix, name) {
27
+ return typeof prefix == "string" && prefix !== "" ? `${prefix}.${name}` : name;
28
+ }
29
+ const ROUTE_QN_RE = /^__route__([A-Z]+)__(.*)$/;
30
+ function parseLines(lines) {
31
+ if (typeof lines != "string")
32
+ return {};
33
+ const m = lines.match(/^(\d+)-(\d+)$/);
34
+ return m ? { start_line: Number(m[1]), end_line: Number(m[2]) } : {};
35
+ }
36
+ function pageInfo(raw) {
37
+ return {
38
+ hasMore: raw.has_more === !0,
39
+ nextOffset: typeof raw.next_offset == "number" ? raw.next_offset : void 0,
40
+ nextCursor: typeof raw.next_cursor == "string" ? raw.next_cursor : typeof raw.next == "string" ? raw.next : void 0
41
+ };
42
+ }
43
+ function normalizeSearchGraph(raw) {
44
+ if (!isObj(raw))
45
+ throw new CLIError("Invalid response from search_graph", ErrorCategory.SYSTEM, [
46
+ "Ensure codebase-memory-mcp is properly installed and functional"
47
+ ]);
48
+ const flat = raw.results ?? raw.nodes;
49
+ if (Array.isArray(flat))
50
+ return { nodes: flat, hasMore: !1 };
51
+ if (!Array.isArray(raw.groups) || !Array.isArray(raw.cols))
52
+ throw new CLIError("Invalid response from search_graph: no results", ErrorCategory.SYSTEM, [
53
+ `Expected 'groups' (CBM 0.11+) or 'results'/'nodes' (CBM 0.10), got keys: ${Object.keys(raw).join(", ") || "(none)"}`,
54
+ "Ensure codebase-memory-mcp is properly installed and returning valid results"
55
+ ]);
56
+ const cols = raw.cols, nodes = [];
57
+ for (const group of raw.groups) {
58
+ const file = typeof group.file == "string" && group.file !== "" ? group.file : void 0;
59
+ for (const row of group.rows ?? []) {
60
+ const cell = {};
61
+ cols.forEach((c, i) => cell[c] = row[i]);
62
+ const rowName = String(cell.name ?? ""), qualifiedName = groupedQualifiedName(group.qn_prefix, rowName), label = String(cell.label ?? ""), properties = {};
63
+ for (const [k, v] of Object.entries(cell))
64
+ k === "name" || k === "label" || k === "lines" || k === "in" || k === "out" || v != null && (properties[k] = v);
65
+ Object.assign(properties, parseLines(cell.lines)), typeof cell.in == "number" && (properties.fan_in = cell.in), typeof cell.out == "number" && (properties.fan_out = cell.out);
66
+ let name = rowName;
67
+ const route = label === "Route" ? qualifiedName.match(ROUTE_QN_RE) : null;
68
+ route && (name = route[2]), properties.name = name, nodes.push({ name, qualified_name: qualifiedName, label, file_path: file, properties });
69
+ }
70
+ }
71
+ return { nodes, ...pageInfo(raw) };
72
+ }
73
+ function normalizeQueryGraph(raw) {
74
+ if (!isObj(raw))
75
+ throw new CLIError("Invalid response from query_graph", ErrorCategory.SYSTEM, [
76
+ "Ensure codebase-memory-mcp is properly installed and functional"
77
+ ]);
78
+ if (Array.isArray(raw.columns) && Array.isArray(raw.rows))
79
+ return { columns: raw.columns, rows: raw.rows, ...pageInfo(raw) };
80
+ if (Array.isArray(raw.records)) {
81
+ const records = raw.records, columns = Array.from(new Set(records.flatMap((r) => Object.keys(r))));
82
+ return { columns, rows: records.map((r) => columns.map((c) => r[c])), hasMore: !1 };
83
+ }
84
+ throw new CLIError("Invalid response from query_graph: no columns/rows", ErrorCategory.SYSTEM, [
85
+ `Got keys: ${Object.keys(raw).join(", ") || "(none)"}`
86
+ ]);
87
+ }
88
+ function queryRecords(result) {
89
+ return result.rows.map((row) => {
90
+ const rec = {};
91
+ return result.columns.forEach((c, i) => rec[c] = row[i]), rec;
92
+ });
93
+ }
94
+ function cellNumber(v) {
95
+ const n = typeof v == "number" ? v : typeof v == "string" && v.trim() !== "" ? Number(v) : NaN;
96
+ return Number.isFinite(n) ? n : void 0;
97
+ }
98
+ function cellString(v) {
99
+ return typeof v == "string" && v !== "" ? v : void 0;
100
+ }
101
+ function cellLabel(v) {
102
+ if (Array.isArray(v))
103
+ return typeof v[0] == "string" ? v[0] : void 0;
104
+ if (!(typeof v != "string" || v === "")) {
105
+ if (v.startsWith("["))
106
+ try {
107
+ const parsed = JSON.parse(v);
108
+ return Array.isArray(parsed) && typeof parsed[0] == "string" ? parsed[0] : void 0;
109
+ } catch {
110
+ return;
111
+ }
112
+ return v;
113
+ }
114
+ }
115
+ function nodeColumns(v) {
116
+ return `${v}.name AS name, ${v}.qualified_name AS qualified_name, ${v}.file_path AS file_path, ${v}.start_line AS start_line, ${v}.end_line AS end_line, labels(${v}) AS label`;
117
+ }
118
+ function nodesFromQuery(result) {
119
+ return queryRecords(result).flatMap((r) => {
120
+ if (result.columns.length === 1 && isObj(r[result.columns[0]]))
121
+ return [r[result.columns[0]]];
122
+ const name = cellString(r.name);
123
+ if (!name)
124
+ return [];
125
+ const properties = { name }, start = cellNumber(r.start_line), end = cellNumber(r.end_line);
126
+ return start !== void 0 && (properties.start_line = start), end !== void 0 && (properties.end_line = end), [
127
+ {
128
+ name,
129
+ qualified_name: cellString(r.qualified_name),
130
+ label: cellLabel(r.label) ?? "",
131
+ file_path: cellString(r.file_path),
132
+ properties
133
+ }
134
+ ];
135
+ });
136
+ }
137
+ function normalizeTracePath(raw, direction) {
138
+ if (!isObj(raw))
139
+ throw new CLIError("Invalid response from trace_path", ErrorCategory.SYSTEM, [
140
+ "Ensure codebase-memory-mcp is properly installed and functional"
141
+ ]);
142
+ if (Array.isArray(raw.nodes))
143
+ return {
144
+ nodes: raw.nodes,
145
+ edges: Array.isArray(raw.edges) ? raw.edges : [],
146
+ shape: "flat",
147
+ hasMore: !1
148
+ };
149
+ const section = raw[direction === "inbound" ? "callers" : "callees"];
150
+ if (!isObj(section) || !Array.isArray(section.groups))
151
+ return null;
152
+ const cols = Array.isArray(section.cols) ? section.cols : ["name", "hop"], nameIdx = cols.indexOf("name"), hopIdx = cols.indexOf("hop"), nodes = [];
153
+ for (const group of section.groups)
154
+ for (const row of group.rows ?? []) {
155
+ const name = String(row[nameIdx >= 0 ? nameIdx : 0] ?? ""), hop = hopIdx >= 0 ? cellNumber(row[hopIdx]) : void 0, qualifiedName = groupedQualifiedName(group.qn_prefix, name);
156
+ nodes.push({
157
+ id: qualifiedName,
158
+ qualified_name: qualifiedName,
159
+ name,
160
+ source_symbol: name,
161
+ depth: hop ?? 1
162
+ });
163
+ }
164
+ const sectionPage = pageInfo(section), topPage = pageInfo(raw);
165
+ return {
166
+ nodes,
167
+ edges: [],
168
+ shape: "grouped",
169
+ hasMore: topPage.hasMore || sectionPage.hasMore,
170
+ nextCursor: topPage.nextCursor ?? sectionPage.nextCursor
171
+ };
172
+ }
173
+ function normalizeListProjects(raw) {
174
+ if (!isObj(raw))
175
+ throw new CLIError("Invalid response from list_projects", ErrorCategory.SYSTEM, [
176
+ "Ensure codebase-memory-mcp is properly installed and functional"
177
+ ]);
178
+ return { projects: Array.isArray(raw.projects) ? raw.projects : [], ...pageInfo(raw) };
179
+ }
180
+ function attachRouteHandlers(nodes, handlers) {
181
+ const byRoute = /* @__PURE__ */ new Map();
182
+ for (const h of handlers) {
183
+ const list = byRoute.get(h.route_qualified_name) ?? [];
184
+ list.some((x) => x.qualified_name === h.qualified_name) || list.push(h), byRoute.set(h.route_qualified_name, list);
185
+ }
186
+ return nodes.flatMap((node) => {
187
+ const list = byRoute.get(String(node.qualified_name ?? "")) ?? [];
188
+ if (list.length === 0)
189
+ return [node];
190
+ const shared = list.length > 1;
191
+ return list.map((h) => {
192
+ const properties = { ...node.properties ?? {} };
193
+ properties.handler_name ??= h.qualified_name, properties.symbol ??= h.name, h.start_line !== void 0 && (properties.start_line ??= h.start_line), h.end_line !== void 0 && (properties.end_line ??= h.end_line);
194
+ const file = shared ? h.file_path || node.file_path : node.file_path || h.file_path;
195
+ return { ...node, file_path: file, properties };
196
+ });
197
+ });
198
+ }
199
+ const ROUTE_HANDLERS_QUERY = "MATCH (f)-[:HANDLES]->(r:Route) RETURN r.qualified_name AS route_qualified_name, f.qualified_name AS qualified_name, f.name AS name, f.file_path AS file_path, f.start_line AS start_line, f.end_line AS end_line";
200
+ function routeHandlersFromQuery(result) {
201
+ return queryRecords(result).flatMap((r) => {
202
+ const route = cellString(r.route_qualified_name), qn = cellString(r.qualified_name);
203
+ return !route || !qn ? [] : [
204
+ {
205
+ route_qualified_name: route,
206
+ qualified_name: qn,
207
+ name: cellString(r.name) ?? qn.slice(qn.lastIndexOf(".") + 1),
208
+ file_path: cellString(r.file_path),
209
+ start_line: cellNumber(r.start_line),
210
+ end_line: cellNumber(r.end_line)
211
+ }
212
+ ];
213
+ });
214
+ }
215
+ class CbmTools {
216
+ client;
217
+ defaults;
218
+ /**
219
+ * @param defaults.timeout Milliseconds each call waits when the call names none (the client's
220
+ * default is 30s): long reads over a large graph pass more.
221
+ */
222
+ constructor(client, defaults = {}) {
223
+ this.client = client, this.defaults = defaults;
224
+ }
225
+ /** One JSON tool call, with error payloads raised as {@link CbmToolError}. */
226
+ async call(tool, args = {}, options) {
227
+ const timeout = options?.timeout ?? this.defaults.timeout, raw = await this.client.invokeTool(tool, { ...args, format: "json" }, timeout !== void 0 ? { ...options, timeout } : options);
228
+ return assertToolResult(tool, raw), raw;
229
+ }
230
+ async listProjects() {
231
+ const projects = [];
232
+ let offset = 0;
233
+ for (let page = 0; page < MAX_PAGES; page++) {
234
+ const res = normalizeListProjects(await this.call("list_projects", { limit: PROJECTS_PAGE, offset }));
235
+ projects.push(...res.projects);
236
+ const next = res.nextOffset ?? offset + res.projects.length;
237
+ if (!res.hasMore || next <= offset)
238
+ break;
239
+ offset = next;
240
+ }
241
+ return projects;
242
+ }
243
+ /** search_graph across every page. */
244
+ async searchGraph(args) {
245
+ const nodes = [];
246
+ let offset = 0;
247
+ for (let page = 0; page < MAX_PAGES; page++) {
248
+ const res = normalizeSearchGraph(await this.call("search_graph", {
249
+ ...args,
250
+ limit: SEARCH_PAGE,
251
+ offset,
252
+ max_output_tokens: MAX_OUTPUT_TOKENS
253
+ }));
254
+ if (nodes.push(...res.nodes), !res.hasMore || res.nextOffset === void 0 || res.nextOffset <= offset)
255
+ break;
256
+ offset = res.nextOffset;
257
+ }
258
+ return nodes;
259
+ }
260
+ /** query_graph across every page (cursor continuation, offset as fallback). */
261
+ async queryGraph(query, project) {
262
+ const base = { query, project, max_rows: QUERY_PAGE, max_output_tokens: MAX_OUTPUT_TOKENS };
263
+ let res = normalizeQueryGraph(await this.call("query_graph", base));
264
+ const columns = res.columns, rows = [...res.rows];
265
+ let offset = 0;
266
+ for (let page = 1; page < MAX_PAGES && res.hasMore; page++) {
267
+ if (res.nextCursor)
268
+ res = normalizeQueryGraph(await this.call("query_graph", { ...base, cursor: res.nextCursor }));
269
+ else if (res.nextOffset !== void 0 && res.nextOffset > offset)
270
+ offset = res.nextOffset, res = normalizeQueryGraph(await this.call("query_graph", { ...base, offset }));
271
+ else
272
+ break;
273
+ rows.push(...res.rows);
274
+ }
275
+ return { columns, rows };
276
+ }
277
+ /**
278
+ * trace_path across every page. Depth is raised to CBM's minimum of 1. Returns null when
279
+ * the backend's response holds no call graph (see {@link normalizeTracePath}).
280
+ */
281
+ async tracePath(args) {
282
+ const base = {
283
+ ...args,
284
+ depth: Math.max(1, args.depth),
285
+ limit: TRACE_PAGE,
286
+ max_output_tokens: MAX_OUTPUT_TOKENS
287
+ };
288
+ let res = normalizeTracePath(await this.call("trace_path", base), args.direction);
289
+ if (!res)
290
+ return null;
291
+ const { shape } = res, nodes = [...res.nodes], edges = [...res.edges];
292
+ for (let page = 1; page < MAX_PAGES && res.hasMore && res.nextCursor && (res = normalizeTracePath(await this.call("trace_path", { ...base, cursor: res.nextCursor }), args.direction), !!res); page++)
293
+ nodes.push(...res.nodes), edges.push(...res.edges);
294
+ return { nodes, edges, shape };
295
+ }
296
+ /**
297
+ * File path of each qualified name, for trace_path results (which carry none since 0.11).
298
+ * Names containing quotes or backslashes are skipped rather than escaped.
299
+ */
300
+ async filePathsOf(qualifiedNames, project) {
301
+ const files = /* @__PURE__ */ new Map(), safe = Array.from(new Set(qualifiedNames)).filter((q) => q && !/["'\\]/.test(q));
302
+ for (let i = 0; i < safe.length; i += 200) {
303
+ const list = safe.slice(i, i + 200).map((q) => `"${q}"`).join(", "), result = await this.queryGraph(`MATCH (n) WHERE n.qualified_name IN [${list}] RETURN n.qualified_name AS qualified_name, n.file_path AS file_path`, project);
304
+ for (const r of queryRecords(result)) {
305
+ const qn = cellString(r.qualified_name), file = cellString(r.file_path);
306
+ qn && file && !files.has(qn) && files.set(qn, file);
307
+ }
308
+ }
309
+ return files;
310
+ }
311
+ /** index_repository; returns the node/edge counts when CBM reports them. */
312
+ /** @param options.timeout Milliseconds to wait for the index (the client's default is 30s). */
313
+ async indexRepository(repoPath, options) {
314
+ const raw = await this.call("index_repository", { repo_path: repoPath }, options);
315
+ if (!isObj(raw))
316
+ throw new CLIError("Invalid response from index_repository", ErrorCategory.SYSTEM, [
317
+ "Ensure codebase-memory-mcp is properly installed and functional"
318
+ ]);
319
+ return raw;
320
+ }
321
+ }
322
+ export {
323
+ CbmToolError,
324
+ CbmTools,
325
+ ROUTE_HANDLERS_QUERY,
326
+ assertToolResult,
327
+ attachRouteHandlers,
328
+ cellLabel,
329
+ cellNumber,
330
+ cellString,
331
+ groupedQualifiedName,
332
+ nodeColumns,
333
+ nodesFromQuery,
334
+ normalizeListProjects,
335
+ normalizeQueryGraph,
336
+ normalizeSearchGraph,
337
+ normalizeTracePath,
338
+ queryRecords,
339
+ relativeToRoot,
340
+ routeHandlersFromQuery
341
+ };
342
+ //# sourceMappingURL=cbm-responses.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/analyzers/cbm-responses.ts"],
4
+ "sourcesContent": ["/**\n * codebase-memory-mcp (CBM) response contract\n *\n * Every CBM tool call made by the analyzer goes through {@link CbmTools}. It asks for JSON\n * (`format: \"json\"`; CBM 0.11+ answers in text otherwise), follows the result pages, turns\n * error payloads into errors, and normalises each tool's JSON into the shapes the analyzer\n * works with. Two response generations are accepted:\n *\n * - CBM 0.11 / 0.12 (grouped, paged):\n * - search_graph: `{ cols, groups: [{ qn_prefix, file, rows }], has_more, next_offset }`\n * - query_graph: `{ columns, rows, has_more, next_cursor, next_offset }`\n * - trace_path: `{ callers|callees: { cols: [name, hop], groups: [{ qn_prefix, rows }] }, next_cursor }`\n * - list_projects: `{ projects: [{ name, root_path }], has_more }`\n * - CBM 0.10 and earlier (flat): search_graph `{ results | nodes: [...] }`, trace_path\n * `{ nodes, edges }`, query_graph `{ columns, rows }` (or `{ records }`), list_projects\n * `{ projects }`. These pass through unchanged and are never paged.\n *\n * A grouped row's qualified name is `qn_prefix ? qn_prefix + \".\" + name : name` (the\n * response's own `qn_rule`); the row's `name` is that qualified-name suffix, which is the\n * symbol name for code nodes but only a fragment for Route nodes, whose path lives in the\n * qualified name (`__route__GET__/vets.html`).\n */\n\nimport * as path from \"path\";\nimport { CLIError, ErrorCategory } from \"../utils/errors.js\";\n\n/**\n * A CBM file path relative to the codebase root. CBM stores repository-relative paths\n * (0.10 and 0.12 alike); absolute ones are relativised, relative ones kept as they are.\n * `path.relative` alone would resolve a relative path against the process cwd.\n */\nexport function relativeToRoot(codebaseRoot: string | undefined, filePath: string): string {\n if (!filePath || !codebaseRoot || !path.isAbsolute(filePath)) return filePath;\n return path.relative(codebaseRoot, filePath);\n}\n\n/** Graph node in the shape the analyzer consumes (same as CBM 0.10's flat search results). */\nexport interface CbmGraphNode {\n id?: string;\n name?: string;\n qualified_name?: string;\n label: string;\n file_path?: string;\n properties?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface CbmProject {\n name: string;\n root_path: string;\n [key: string]: unknown;\n}\n\nexport interface QueryGraphResult {\n columns: string[];\n rows: unknown[][];\n}\n\n/** Call-graph node from trace_path; `depth` is the hop count from the traced symbol. */\nexport interface TraceNode {\n id?: string;\n qualified_name?: string;\n name?: string;\n file_path?: string;\n source_symbol?: string;\n depth?: number;\n [key: string]: unknown;\n}\n\nexport interface TraceEdge {\n from_node: string;\n to_node: string;\n type?: string;\n [key: string]: unknown;\n}\n\nexport interface TraceResult {\n nodes: TraceNode[];\n edges: TraceEdge[];\n /** \"grouped\" for CBM 0.11+ (no per-edge types, no file paths), \"flat\" for 0.10. */\n shape: \"grouped\" | \"flat\";\n}\n\n/** Handler of a Route, from a `(f)-[:HANDLES]->(r:Route)` query. */\nexport interface RouteHandler {\n route_qualified_name: string;\n qualified_name: string;\n name: string;\n file_path?: string;\n start_line?: number;\n end_line?: number;\n}\n\n/** Minimal client surface; StdioClient satisfies it. */\nexport interface CbmToolInvoker {\n invokeTool(name: string, args?: unknown, options?: { timeout?: number }): Promise<unknown>;\n}\n\n/** CBM answered a tool call with an error (bad Cypher, unknown symbol, failed index, ...). */\nexport class CbmToolError extends CLIError {\n constructor(\n public readonly tool: string,\n public readonly detail: string,\n public readonly hint?: string\n ) {\n super(\n `codebase-memory-mcp ${tool} failed: ${detail}`,\n ErrorCategory.SYSTEM,\n hint ? [hint] : undefined\n );\n this.name = \"CbmToolError\";\n }\n}\n\ntype Obj = Record<string, unknown>;\n\nconst isObj = (v: unknown): v is Obj => typeof v === \"object\" && v !== null && !Array.isArray(v);\n\n/** Page sizes: the largest each tool accepts, so most results arrive in one page. */\nconst SEARCH_PAGE = 500;\nconst QUERY_PAGE = 5000;\nconst TRACE_PAGE = 5000;\nconst PROJECTS_PAGE = 500;\n/** Lifts CBM's default 3,200-token output budget, which otherwise truncates a page early. */\nconst MAX_OUTPUT_TOKENS = 1_000_000;\n/** Guard against a server that keeps reporting has_more without advancing. */\nconst MAX_PAGES = 1000;\n\n/**\n * Throw if a tool result is an error payload. Since we always ask for JSON, a bare string is\n * an error message (CBM 0.12 sends Cypher errors as text); objects carry `error` or\n * `status: \"error\"` with an optional `hint`.\n */\nexport function assertToolResult(tool: string, raw: unknown): void {\n if (typeof raw === \"string\") {\n throw new CbmToolError(tool, raw.trim() || \"empty response\");\n }\n if (!isObj(raw)) return;\n const hint = typeof raw.hint === \"string\" ? raw.hint : undefined;\n if (typeof raw.error === \"string\") {\n throw new CbmToolError(tool, raw.error, hint);\n }\n if (raw.status === \"error\") {\n throw new CbmToolError(tool, typeof raw.message === \"string\" ? raw.message : \"status: error\", hint);\n }\n}\n\n/** Qualified name from a grouped row (the response's qn_rule). */\nexport function groupedQualifiedName(prefix: unknown, name: string): string {\n return typeof prefix === \"string\" && prefix !== \"\" ? `${prefix}.${name}` : name;\n}\n\nconst ROUTE_QN_RE = /^__route__([A-Z]+)__(.*)$/;\n\n/** Parse \"12-30\" into start/end lines. */\nfunction parseLines(lines: unknown): { start_line?: number; end_line?: number } {\n if (typeof lines !== \"string\") return {};\n const m = lines.match(/^(\\d+)-(\\d+)$/);\n return m ? { start_line: Number(m[1]), end_line: Number(m[2]) } : {};\n}\n\n/** Paging cursor fields shared by the grouped tools. */\ninterface PageInfo {\n hasMore: boolean;\n nextOffset?: number;\n nextCursor?: string;\n}\n\nfunction pageInfo(raw: Obj): PageInfo {\n return {\n hasMore: raw.has_more === true,\n nextOffset: typeof raw.next_offset === \"number\" ? raw.next_offset : undefined,\n nextCursor:\n typeof raw.next_cursor === \"string\"\n ? raw.next_cursor\n : typeof raw.next === \"string\"\n ? raw.next\n : undefined,\n };\n}\n\n/**\n * Normalise one search_graph page into analyzer nodes.\n *\n * Grouped rows become `{ name, qualified_name, label, file_path, properties }` where\n * properties carry `name`, `start_line`/`end_line` (from `lines`), `fan_in`/`fan_out`\n * (from `in`/`out`) and any extra `fields` columns. Route nodes get the path from their\n * qualified name as `name` and the method as `properties.method` when absent, matching\n * how CBM stores them (name `/vets`, qualified name `__route__GET__/vets`).\n */\nexport function normalizeSearchGraph(raw: unknown): { nodes: CbmGraphNode[] } & PageInfo {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from search_graph\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n\n // CBM 0.10 and earlier: flat node arrays.\n const flat = raw.results ?? raw.nodes;\n if (Array.isArray(flat)) {\n return { nodes: flat as CbmGraphNode[], hasMore: false };\n }\n\n if (!Array.isArray(raw.groups) || !Array.isArray(raw.cols)) {\n throw new CLIError(\"Invalid response from search_graph: no results\", ErrorCategory.SYSTEM, [\n `Expected 'groups' (CBM 0.11+) or 'results'/'nodes' (CBM 0.10), got keys: ${Object.keys(raw).join(\", \") || \"(none)\"}`,\n \"Ensure codebase-memory-mcp is properly installed and returning valid results\",\n ]);\n }\n\n const cols = raw.cols as string[];\n const nodes: CbmGraphNode[] = [];\n for (const group of raw.groups as Obj[]) {\n const file = typeof group.file === \"string\" && group.file !== \"\" ? group.file : undefined;\n for (const row of (group.rows ?? []) as unknown[][]) {\n const cell: Obj = {};\n cols.forEach((c, i) => (cell[c] = row[i]));\n const rowName = String(cell.name ?? \"\");\n const qualifiedName = groupedQualifiedName(group.qn_prefix, rowName);\n const label = String(cell.label ?? \"\");\n\n const properties: Obj = {};\n for (const [k, v] of Object.entries(cell)) {\n if (k === \"name\" || k === \"label\" || k === \"lines\" || k === \"in\" || k === \"out\") continue;\n if (v !== null && v !== undefined) properties[k] = v;\n }\n Object.assign(properties, parseLines(cell.lines));\n if (typeof cell.in === \"number\") properties.fan_in = cell.in;\n if (typeof cell.out === \"number\") properties.fan_out = cell.out;\n\n let name = rowName;\n const route = label === \"Route\" ? qualifiedName.match(ROUTE_QN_RE) : null;\n // The method stays in the qualified name, where the analyzer reads it\n if (route) name = route[2];\n properties.name = name;\n\n nodes.push({ name, qualified_name: qualifiedName, label, file_path: file, properties });\n }\n }\n return { nodes, ...pageInfo(raw) };\n}\n\n/** Normalise one query_graph page. Accepts `{ columns, rows }` and the legacy `{ records }`. */\nexport function normalizeQueryGraph(raw: unknown): QueryGraphResult & PageInfo {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from query_graph\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n if (Array.isArray(raw.columns) && Array.isArray(raw.rows)) {\n return { columns: raw.columns as string[], rows: raw.rows as unknown[][], ...pageInfo(raw) };\n }\n if (Array.isArray(raw.records)) {\n const records = raw.records as Obj[];\n const columns = Array.from(new Set(records.flatMap((r) => Object.keys(r))));\n return { columns, rows: records.map((r) => columns.map((c) => r[c])), hasMore: false };\n }\n throw new CLIError(\"Invalid response from query_graph: no columns/rows\", ErrorCategory.SYSTEM, [\n `Got keys: ${Object.keys(raw).join(\", \") || \"(none)\"}`,\n ]);\n}\n\n/** query_graph rows as objects keyed by column name (use `AS` aliases for clean keys). */\nexport function queryRecords(result: QueryGraphResult): Array<Record<string, unknown>> {\n return result.rows.map((row) => {\n const rec: Record<string, unknown> = {};\n result.columns.forEach((c, i) => (rec[c] = row[i]));\n return rec;\n });\n}\n\n/** Number from a query_graph cell (CBM 0.12 returns integers as strings). */\nexport function cellNumber(v: unknown): number | undefined {\n const n = typeof v === \"number\" ? v : typeof v === \"string\" && v.trim() !== \"\" ? Number(v) : NaN;\n return Number.isFinite(n) ? n : undefined;\n}\n\n/** String from a query_graph cell; empty strings and nulls become undefined. */\nexport function cellString(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\n/** First label from a `labels(n)` cell (CBM 0.12 returns it as a JSON string `[\"Route\"]`). */\nexport function cellLabel(v: unknown): string | undefined {\n if (Array.isArray(v)) return typeof v[0] === \"string\" ? v[0] : undefined;\n if (typeof v !== \"string\" || v === \"\") return undefined;\n if (v.startsWith(\"[\")) {\n try {\n const parsed = JSON.parse(v) as unknown;\n return Array.isArray(parsed) && typeof parsed[0] === \"string\" ? parsed[0] : undefined;\n } catch {\n return undefined;\n }\n }\n return v;\n}\n\n/**\n * RETURN clause yielding the columns {@link nodesFromQuery} reads, for node variable `v`.\n * CBM 0.12 returns only the name for a bare `RETURN n`, so node queries spell the columns out.\n */\nexport function nodeColumns(v: string): string {\n return (\n `${v}.name AS name, ${v}.qualified_name AS qualified_name, ${v}.file_path AS file_path, ` +\n `${v}.start_line AS start_line, ${v}.end_line AS end_line, labels(${v}) AS label`\n );\n}\n\n/**\n * Graph nodes from a query_graph result whose columns come from {@link nodeColumns}.\n * A legacy `{ records: [{ n: {...node} }] }` result (one node object per record) is\n * passed through.\n */\nexport function nodesFromQuery(result: QueryGraphResult): CbmGraphNode[] {\n return queryRecords(result).flatMap((r) => {\n if (result.columns.length === 1 && isObj(r[result.columns[0]])) {\n return [r[result.columns[0]] as CbmGraphNode];\n }\n const name = cellString(r.name);\n if (!name) return [];\n const properties: Obj = { name };\n const start = cellNumber(r.start_line);\n const end = cellNumber(r.end_line);\n if (start !== undefined) properties.start_line = start;\n if (end !== undefined) properties.end_line = end;\n return [\n {\n name,\n qualified_name: cellString(r.qualified_name),\n label: cellLabel(r.label) ?? \"\",\n file_path: cellString(r.file_path),\n properties,\n },\n ];\n });\n}\n\n/**\n * Normalise one trace_path page into call-graph nodes.\n *\n * Returns null when the response holds no call graph in either shape, which is how a\n * backend without trace_path support answers; callers fall back to a Cypher CALLS query.\n */\nexport function normalizeTracePath(\n raw: unknown,\n direction: \"inbound\" | \"outbound\"\n): (TraceResult & PageInfo) | null {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from trace_path\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n if (Array.isArray(raw.nodes)) {\n return {\n nodes: raw.nodes as TraceNode[],\n edges: Array.isArray(raw.edges) ? (raw.edges as TraceEdge[]) : [],\n shape: \"flat\",\n hasMore: false,\n };\n }\n\n const section = raw[direction === \"inbound\" ? \"callers\" : \"callees\"];\n if (!isObj(section) || !Array.isArray(section.groups)) return null;\n const cols = Array.isArray(section.cols) ? (section.cols as string[]) : [\"name\", \"hop\"];\n const nameIdx = cols.indexOf(\"name\");\n const hopIdx = cols.indexOf(\"hop\");\n const nodes: TraceNode[] = [];\n for (const group of section.groups as Obj[]) {\n for (const row of (group.rows ?? []) as unknown[][]) {\n const name = String(row[nameIdx >= 0 ? nameIdx : 0] ?? \"\");\n const hop = hopIdx >= 0 ? cellNumber(row[hopIdx]) : undefined;\n const qualifiedName = groupedQualifiedName(group.qn_prefix, name);\n nodes.push({\n id: qualifiedName,\n qualified_name: qualifiedName,\n name,\n source_symbol: name,\n depth: hop ?? 1,\n });\n }\n }\n const sectionPage = pageInfo(section);\n const topPage = pageInfo(raw);\n return {\n nodes,\n edges: [],\n shape: \"grouped\",\n hasMore: topPage.hasMore || sectionPage.hasMore,\n nextCursor: topPage.nextCursor ?? sectionPage.nextCursor,\n };\n}\n\n/** Normalise one list_projects page. */\nexport function normalizeListProjects(raw: unknown): { projects: CbmProject[] } & PageInfo {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from list_projects\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n // No projects array means nothing is indexed yet.\n const projects = Array.isArray(raw.projects) ? (raw.projects as CbmProject[]) : [];\n return { projects, ...pageInfo(raw) };\n}\n\n/**\n * Attach HANDLES-edge handlers to Route nodes.\n *\n * CBM Route nodes carry only `{ method, source }`; the handler, its symbol, its lines and\n * often the file come from the function that HANDLES the route. CBM keys a Route by method\n * and path, so routers mounted under different prefixes can share one Route node with\n * several handlers (fastapi's `GET /{}` is handled by both `users.read_user_by_id` and\n * `items.read_item`): such a route becomes one node per handler, each with the handler's\n * file. A route with a single handler keeps its own values where it has them.\n */\nexport function attachRouteHandlers(nodes: CbmGraphNode[], handlers: RouteHandler[]): CbmGraphNode[] {\n const byRoute = new Map<string, RouteHandler[]>();\n for (const h of handlers) {\n const list = byRoute.get(h.route_qualified_name) ?? [];\n if (!list.some((x) => x.qualified_name === h.qualified_name)) list.push(h);\n byRoute.set(h.route_qualified_name, list);\n }\n return nodes.flatMap((node) => {\n const list = byRoute.get(String(node.qualified_name ?? \"\")) ?? [];\n if (list.length === 0) return [node];\n const shared = list.length > 1;\n return list.map((h) => {\n const properties: Obj = { ...(node.properties ?? {}) };\n properties.handler_name ??= h.qualified_name;\n properties.symbol ??= h.name;\n if (h.start_line !== undefined) properties.start_line ??= h.start_line;\n if (h.end_line !== undefined) properties.end_line ??= h.end_line;\n const file = shared ? h.file_path || node.file_path : node.file_path || h.file_path;\n return { ...node, file_path: file, properties };\n });\n });\n}\n\n/** Cypher returning every Route handler as {@link RouteHandler} columns. */\nexport const ROUTE_HANDLERS_QUERY =\n \"MATCH (f)-[:HANDLES]->(r:Route) RETURN r.qualified_name AS route_qualified_name, \" +\n \"f.qualified_name AS qualified_name, f.name AS name, f.file_path AS file_path, \" +\n \"f.start_line AS start_line, f.end_line AS end_line\";\n\nexport function routeHandlersFromQuery(result: QueryGraphResult): RouteHandler[] {\n return queryRecords(result).flatMap((r) => {\n const route = cellString(r.route_qualified_name);\n const qn = cellString(r.qualified_name);\n if (!route || !qn) return [];\n return [\n {\n route_qualified_name: route,\n qualified_name: qn,\n name: cellString(r.name) ?? qn.slice(qn.lastIndexOf(\".\") + 1),\n file_path: cellString(r.file_path),\n start_line: cellNumber(r.start_line),\n end_line: cellNumber(r.end_line),\n },\n ];\n });\n}\n\n/**\n * Paged, normalised access to the CBM tools.\n *\n * All calls add `format: \"json\"`; CBM 0.10 ignores the argument.\n */\nexport class CbmTools {\n /**\n * @param defaults.timeout Milliseconds each call waits when the call names none (the client's\n * default is 30s): long reads over a large graph pass more.\n */\n constructor(\n private readonly client: CbmToolInvoker,\n private readonly defaults: { timeout?: number } = {}\n ) {}\n\n /** One JSON tool call, with error payloads raised as {@link CbmToolError}. */\n async call(\n tool: string,\n args: Record<string, unknown> = {},\n options?: { timeout?: number }\n ): Promise<unknown> {\n const timeout = options?.timeout ?? this.defaults.timeout;\n const raw = await this.client.invokeTool(tool, { ...args, format: \"json\" }, timeout !== undefined ? { ...options, timeout } : options);\n assertToolResult(tool, raw);\n return raw;\n }\n\n async listProjects(): Promise<CbmProject[]> {\n const projects: CbmProject[] = [];\n let offset = 0;\n for (let page = 0; page < MAX_PAGES; page++) {\n const res = normalizeListProjects(\n await this.call(\"list_projects\", { limit: PROJECTS_PAGE, offset })\n );\n projects.push(...res.projects);\n const next = res.nextOffset ?? offset + res.projects.length;\n if (!res.hasMore || next <= offset) break;\n offset = next;\n }\n return projects;\n }\n\n /** search_graph across every page. */\n async searchGraph(args: Record<string, unknown>): Promise<CbmGraphNode[]> {\n const nodes: CbmGraphNode[] = [];\n let offset = 0;\n for (let page = 0; page < MAX_PAGES; page++) {\n const res = normalizeSearchGraph(\n await this.call(\"search_graph\", {\n ...args,\n limit: SEARCH_PAGE,\n offset,\n max_output_tokens: MAX_OUTPUT_TOKENS,\n })\n );\n nodes.push(...res.nodes);\n if (!res.hasMore || res.nextOffset === undefined || res.nextOffset <= offset) break;\n offset = res.nextOffset;\n }\n return nodes;\n }\n\n /** query_graph across every page (cursor continuation, offset as fallback). */\n async queryGraph(query: string, project: string): Promise<QueryGraphResult> {\n const base = { query, project, max_rows: QUERY_PAGE, max_output_tokens: MAX_OUTPUT_TOKENS };\n let res = normalizeQueryGraph(await this.call(\"query_graph\", base));\n const columns = res.columns;\n const rows = [...res.rows];\n let offset = 0;\n for (let page = 1; page < MAX_PAGES && res.hasMore; page++) {\n if (res.nextCursor) {\n res = normalizeQueryGraph(await this.call(\"query_graph\", { ...base, cursor: res.nextCursor }));\n } else if (res.nextOffset !== undefined && res.nextOffset > offset) {\n offset = res.nextOffset;\n res = normalizeQueryGraph(await this.call(\"query_graph\", { ...base, offset }));\n } else {\n break;\n }\n rows.push(...res.rows);\n }\n return { columns, rows };\n }\n\n /**\n * trace_path across every page. Depth is raised to CBM's minimum of 1. Returns null when\n * the backend's response holds no call graph (see {@link normalizeTracePath}).\n */\n async tracePath(args: {\n function_name: string;\n direction: \"inbound\" | \"outbound\";\n depth: number;\n project: string;\n }): Promise<TraceResult | null> {\n const base = {\n ...args,\n depth: Math.max(1, args.depth),\n limit: TRACE_PAGE,\n max_output_tokens: MAX_OUTPUT_TOKENS,\n };\n let res = normalizeTracePath(await this.call(\"trace_path\", base), args.direction);\n if (!res) return null;\n const { shape } = res;\n const nodes = [...res.nodes];\n const edges = [...res.edges];\n for (let page = 1; page < MAX_PAGES && res.hasMore && res.nextCursor; page++) {\n res = normalizeTracePath(\n await this.call(\"trace_path\", { ...base, cursor: res.nextCursor }),\n args.direction\n );\n if (!res) break;\n nodes.push(...res.nodes);\n edges.push(...res.edges);\n }\n return { nodes, edges, shape };\n }\n\n /**\n * File path of each qualified name, for trace_path results (which carry none since 0.11).\n * Names containing quotes or backslashes are skipped rather than escaped.\n */\n async filePathsOf(qualifiedNames: string[], project: string): Promise<Map<string, string>> {\n const files = new Map<string, string>();\n const safe = Array.from(new Set(qualifiedNames)).filter((q) => q && !/[\"'\\\\]/.test(q));\n for (let i = 0; i < safe.length; i += 200) {\n const list = safe.slice(i, i + 200).map((q) => `\"${q}\"`).join(\", \");\n const result = await this.queryGraph(\n `MATCH (n) WHERE n.qualified_name IN [${list}] RETURN n.qualified_name AS qualified_name, n.file_path AS file_path`,\n project\n );\n for (const r of queryRecords(result)) {\n const qn = cellString(r.qualified_name);\n const file = cellString(r.file_path);\n if (qn && file && !files.has(qn)) files.set(qn, file);\n }\n }\n return files;\n }\n\n /** index_repository; returns the node/edge counts when CBM reports them. */\n /** @param options.timeout Milliseconds to wait for the index (the client's default is 30s). */\n async indexRepository(\n repoPath: string,\n options?: { timeout?: number }\n ): Promise<{ nodes?: number; edges?: number; [k: string]: unknown }> {\n const raw = await this.call(\"index_repository\", { repo_path: repoPath }, options);\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from index_repository\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n return raw;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,159 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { statSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { CLIError, ErrorCategory } from "../utils/errors.js";
5
+ import { SOURCE_EXTENSIONS } from "./cards/build.js";
6
+ const DR_OWN_DIRS = Object.freeze(["documentation-robotics", ".dr"]);
7
+ function inDirs(file, dirs) {
8
+ return dirs.some((d) => !!d && (file === d || file.startsWith(d.replace(/\/+$/, "") + "/")));
9
+ }
10
+ const MAX_BUFFER = 64 * 1024 * 1024;
11
+ function git(args, cwd) {
12
+ const r = spawnSync("git", ["-c", "core.quotepath=false", ...args], { cwd, encoding: "utf-8", stdio: "pipe", timeout: 3e4, maxBuffer: MAX_BUFFER });
13
+ return r.error ? { ok: !1, error: r.error.message } : r.status !== 0 ? { ok: !1, error: (r.stderr || r.stdout || `git exited ${r.status}`).trim() } : { ok: !0, out: r.stdout };
14
+ }
15
+ function toPosix(p) {
16
+ return p.split(path.sep).join("/");
17
+ }
18
+ function parseNameStatus(out, into) {
19
+ const parts = out.split("\0");
20
+ for (let i = 0; i < parts.length; ) {
21
+ const status = parts[i++];
22
+ if (!status)
23
+ continue;
24
+ const code = status[0];
25
+ if (code === "R" || code === "C") {
26
+ const from = parts[i++], to = parts[i++];
27
+ code === "R" && from && (into.deleted.set(from, to ?? null), into.changed.delete(from)), to && (into.changed.add(to), into.deleted.delete(to));
28
+ } else {
29
+ const file = parts[i++];
30
+ if (!file)
31
+ continue;
32
+ code === "D" ? (into.deleted.set(file, null), into.changed.delete(file)) : (into.changed.add(file), into.deleted.delete(file));
33
+ }
34
+ }
35
+ }
36
+ function parseHunks(out) {
37
+ const hunks = /* @__PURE__ */ new Map();
38
+ let file = null, created = !1;
39
+ for (const line of out.split(`
40
+ `))
41
+ if (line.startsWith("diff --git "))
42
+ file = null, created = !1;
43
+ else if (line.startsWith("--- "))
44
+ created = line === "--- /dev/null";
45
+ else if (line.startsWith("+++ "))
46
+ file = line === "+++ /dev/null" ? null : line.slice(4).replace(/\t$/, "").replace(/^b\//, ""), file && created && hunks.set(file, "all");
47
+ else if (file && line.startsWith("@@")) {
48
+ const m = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line), ranges = hunks.get(file) ?? [];
49
+ if (!m || ranges === "all")
50
+ continue;
51
+ const start = Math.max(Number(m[1]), 1), count = m[2] === void 0 ? 1 : Number(m[2]);
52
+ ranges.push(count === 0 ? [start, start, !0] : [start, start + count - 1]), hunks.set(file, ranges);
53
+ }
54
+ return hunks;
55
+ }
56
+ function changedInRange(ranges, start, end) {
57
+ if (!ranges)
58
+ return !1;
59
+ if (ranges === "all")
60
+ return !0;
61
+ if (start === null)
62
+ return !1;
63
+ const last = end ?? start;
64
+ return ranges.some(([a, b, deletion]) => deletion ? a >= start && a < last : a <= last && b >= start);
65
+ }
66
+ async function codeChangesSince(codebaseRoot, since) {
67
+ const top = git(["rev-parse", "--show-toplevel"], codebaseRoot);
68
+ if (!top.ok)
69
+ throw new CLIError(`The code is not in a git repository: ${codebaseRoot}`, ErrorCategory.USER, ["Call codebase_extract without since"]);
70
+ if (since.startsWith("-"))
71
+ throw new CLIError(`since is not a git ref: ${since}`, ErrorCategory.USER);
72
+ const commit = git(["rev-parse", "--verify", "--quiet", `${since}^{commit}`], codebaseRoot);
73
+ if (!commit.ok || !commit.out.trim())
74
+ throw new CLIError(`since is not a commit in this repository: ${since}`, ErrorCategory.USER, [
75
+ "Pass a branch, tag or commit, e.g. the branch the work started from (main)"
76
+ ]);
77
+ const mergeBase = git(["merge-base", commit.out.trim(), "HEAD"], codebaseRoot), base = mergeBase.ok && mergeBase.out.trim() ? mergeBase.out.trim() : commit.out.trim(), repo = { changed: /* @__PURE__ */ new Set(), deleted: /* @__PURE__ */ new Map() };
78
+ for (const args of [
79
+ ["diff", "--name-status", "-z", "-M", base, "HEAD"],
80
+ ["diff", "--name-status", "-z", "-M", "HEAD"]
81
+ ]) {
82
+ const r = git(args, codebaseRoot);
83
+ if (!r.ok)
84
+ throw new CLIError(`git ${args.join(" ")} failed: ${r.error}`, ErrorCategory.SYSTEM);
85
+ parseNameStatus(r.out, repo);
86
+ }
87
+ const untracked = git(["ls-files", "--others", "--exclude-standard", "-z", "--full-name"], top.out.trim()), untrackedFiles = untracked.ok ? untracked.out.split("\0").filter(Boolean) : [];
88
+ for (const f of untrackedFiles)
89
+ repo.changed.add(f), repo.deleted.delete(f);
90
+ const pathspecs = [
91
+ ...SOURCE_EXTENSIONS.map((ext) => `:(glob,icase)**/*.${ext}`),
92
+ ...DR_OWN_DIRS.map((d) => `:(exclude)${d}`)
93
+ ], diff = git(["diff", "-U0", "-M", "--no-color", "--no-ext-diff", base, "--", ...pathspecs], codebaseRoot), repoHunks = diff.ok ? parseHunks(diff.out) : /* @__PURE__ */ new Map(), hunksError = diff.ok ? void 0 : diff.error.split(`
94
+ `)[0];
95
+ for (const f of untrackedFiles)
96
+ repoHunks.set(f, "all");
97
+ const prefix = toPosix(path.relative(top.out.trim(), codebaseRoot)), local = (f) => {
98
+ const l = prefix ? f.startsWith(prefix + "/") ? f.slice(prefix.length + 1) : null : f;
99
+ return l && !inDirs(l, DR_OWN_DIRS) ? l : null;
100
+ }, changed = /* @__PURE__ */ new Set();
101
+ for (const f of repo.changed) {
102
+ const l = local(f);
103
+ l && changed.add(l);
104
+ }
105
+ const deleted = /* @__PURE__ */ new Map();
106
+ for (const [from, to] of repo.deleted) {
107
+ const l = local(from);
108
+ l && deleted.set(l, to ? local(to) : null);
109
+ }
110
+ const hunks = /* @__PURE__ */ new Map();
111
+ for (const [f, ranges] of repoHunks) {
112
+ const l = local(f);
113
+ l && changed.has(l) && hunks.set(l, ranges);
114
+ }
115
+ const topDir = top.out.trim();
116
+ return { base, changed, deleted, hunks, ...hunksError ? { hunksError } : {}, fileAtBase: (file) => {
117
+ const r = git(["show", `${base}:${prefix ? `${prefix}/${file}` : file}`], topDir);
118
+ return r.ok ? r.out : /does not exist in|exists on disk, but not in/.test(r.error) ? null : void 0;
119
+ } };
120
+ }
121
+ function scopeChanges(changes, keep) {
122
+ const changed = new Set([...changes.changed].filter(keep)), deleted = new Map([...changes.deleted].filter(([from, to]) => keep(from) || to !== null && keep(to))), hunks = changes.hunks ? new Map([...changes.hunks].filter(([f]) => changed.has(f))) : void 0;
123
+ return { ...changes, changed, deleted, ...hunks ? { hunks } : {} };
124
+ }
125
+ async function sourceEditsSince(codebaseRoot, sinceMs, isSource) {
126
+ const top = git(["rev-parse", "--show-toplevel"], codebaseRoot);
127
+ if (!top.ok)
128
+ return [];
129
+ const status = git(["status", "--porcelain", "-z", "--untracked-files=all", "--", "."], codebaseRoot);
130
+ if (!status.ok)
131
+ return [];
132
+ const prefix = toPosix(path.relative(top.out.trim(), codebaseRoot)), out = [], parts = status.out.split("\0");
133
+ for (let i = 0; i < parts.length; i++) {
134
+ const entry = parts[i];
135
+ if (entry.length < 4)
136
+ continue;
137
+ const code = entry.slice(0, 2), file = entry.slice(3);
138
+ if ((code[0] === "R" || code[0] === "C") && i++, code.includes("D"))
139
+ continue;
140
+ const local = prefix ? file.startsWith(prefix + "/") ? file.slice(prefix.length + 1) : null : file;
141
+ if (!(!local || !isSource(local)))
142
+ try {
143
+ statSync(path.join(codebaseRoot, local)).mtimeMs > sinceMs && out.push(local);
144
+ } catch {
145
+ }
146
+ }
147
+ return out.sort();
148
+ }
149
+ export {
150
+ DR_OWN_DIRS,
151
+ changedInRange,
152
+ codeChangesSince,
153
+ inDirs,
154
+ parseHunks,
155
+ parseNameStatus,
156
+ scopeChanges,
157
+ sourceEditsSince
158
+ };
159
+ //# sourceMappingURL=code-changes.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/analyzers/code-changes.ts"],
4
+ "sourcesContent": ["/**\n * The files a git range changed, for `codebase_extract` with `since`: what the branch (or the\n * commits since a ref) added, modified, renamed and deleted, plus uncommitted and untracked files,\n * relative to the codebase root, with the lines each changed file changed. DR's own folders\n * (`documentation-robotics/`, `.dr/`) are never part of it. Read-only git, no shell.\n */\nimport { spawnSync } from \"node:child_process\";\nimport { statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { CLIError, ErrorCategory } from \"../utils/errors.js\";\nimport { SOURCE_EXTENSIONS } from \"./cards/build.js\";\nimport type { CodeChanges, LineRanges } from \"./extraction-plan.js\";\n\nexport interface CodeChangesResult extends CodeChanges {\n /** The commit the range starts from: the merge base of `since` and HEAD. */\n base: string;\n}\n\nexport type { LineRanges };\n\n/** Folders, relative to the codebase root, that hold DR's own files: never changed code. */\nexport const DR_OWN_DIRS: readonly string[] = Object.freeze([\"documentation-robotics\", \".dr\"]);\n\n/** Whether a codebase-relative path is inside one of these folders. */\nexport function inDirs(file: string, dirs: readonly string[]): boolean {\n return dirs.some((d) => !!d && (file === d || file.startsWith(d.replace(/\\/+$/, \"\") + \"/\")));\n}\n\n/** Output git may return: a whole range's diff, a large file at the base. */\nconst MAX_BUFFER = 64 * 1024 * 1024;\n\nfunction git(args: string[], cwd: string): { ok: true; out: string } | { ok: false; error: string } {\n const r = spawnSync(\"git\", [\"-c\", \"core.quotepath=false\", ...args], { cwd, encoding: \"utf-8\", stdio: \"pipe\", timeout: 30_000, maxBuffer: MAX_BUFFER });\n if (r.error) return { ok: false, error: r.error.message };\n if (r.status !== 0) return { ok: false, error: (r.stderr || r.stdout || `git exited ${r.status}`).trim() };\n return { ok: true, out: r.stdout };\n}\n\nfunction toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n}\n\n/** Parse `git diff --name-status -z` output into changed and deleted files (repo-relative). */\nexport function parseNameStatus(out: string, into: { changed: Set<string>; deleted: Map<string, string | null> }): void {\n const parts = out.split(\"\\0\");\n for (let i = 0; i < parts.length; ) {\n const status = parts[i++];\n if (!status) continue;\n const code = status[0];\n if (code === \"R\" || code === \"C\") {\n const from = parts[i++];\n const to = parts[i++];\n if (code === \"R\" && from) {\n into.deleted.set(from, to ?? null);\n into.changed.delete(from);\n }\n if (to) {\n into.changed.add(to);\n into.deleted.delete(to);\n }\n } else {\n const file = parts[i++];\n if (!file) continue;\n if (code === \"D\") {\n into.deleted.set(file, null);\n into.changed.delete(file);\n } else {\n into.changed.add(file);\n into.deleted.delete(file);\n }\n }\n }\n}\n\n/**\n * Parse `git diff -U0` output into the new-side line ranges of each file (repo-relative). A file\n * the diff creates (`--- /dev/null`) is \"all\".\n */\nexport function parseHunks(out: string): Map<string, LineRanges> {\n const hunks = new Map<string, LineRanges>();\n let file: string | null = null;\n let created = false;\n for (const line of out.split(\"\\n\")) {\n if (line.startsWith(\"diff --git \")) {\n file = null;\n created = false;\n } else if (line.startsWith(\"--- \")) {\n created = line === \"--- /dev/null\";\n } else if (line.startsWith(\"+++ \")) {\n file = line === \"+++ /dev/null\" ? null : line.slice(4).replace(/\\t$/, \"\").replace(/^b\\//, \"\");\n if (file && created) hunks.set(file, \"all\");\n } else if (file && line.startsWith(\"@@\")) {\n const m = /^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,(\\d+))? @@/.exec(line);\n const ranges = hunks.get(file) ?? [];\n if (!m || ranges === \"all\") continue;\n const start = Math.max(Number(m[1]), 1);\n const count = m[2] === undefined ? 1 : Number(m[2]);\n // A pure deletion (count 0) changes the code around the line it follows; it is marked, so\n // the symbol that line belongs to isn't credited with code removed after it.\n ranges.push(count === 0 ? [start, start, true] : [start, start + count - 1]);\n hunks.set(file, ranges);\n }\n }\n return hunks;\n}\n\n/** Whether a symbol's lines touch a file's changed lines (a symbol with no lines counts only in a new file). */\nexport function changedInRange(ranges: LineRanges | undefined, start: number | null, end: number | null): boolean {\n if (!ranges) return false;\n if (ranges === \"all\") return true;\n if (start === null) return false;\n const last = end ?? start;\n // A deletion after line N is inside the symbol only when N is before its last line: code\n // removed after a method's closing line is its container's change, not the method's.\n return ranges.some(([a, b, deletion]) => (deletion ? a >= start && a < last : a <= last && b >= start));\n}\n\n/**\n * Files changed between the merge base of `since` and HEAD, and in the working tree, relative to\n * `codebaseRoot`. Files outside the codebase root, and DR's own folders ({@link DR_OWN_DIRS}),\n * are left out.\n *\n * @throws CLIError when the codebase is not in git or `since` is not a commit\n */\nexport async function codeChangesSince(codebaseRoot: string, since: string): Promise<CodeChangesResult> {\n const top = git([\"rev-parse\", \"--show-toplevel\"], codebaseRoot);\n if (!top.ok) throw new CLIError(`The code is not in a git repository: ${codebaseRoot}`, ErrorCategory.USER, [\"Call codebase_extract without since\"]);\n if (since.startsWith(\"-\")) throw new CLIError(`since is not a git ref: ${since}`, ErrorCategory.USER);\n const commit = git([\"rev-parse\", \"--verify\", \"--quiet\", `${since}^{commit}`], codebaseRoot);\n if (!commit.ok || !commit.out.trim()) {\n throw new CLIError(`since is not a commit in this repository: ${since}`, ErrorCategory.USER, [\n \"Pass a branch, tag or commit, e.g. the branch the work started from (main)\",\n ]);\n }\n const mergeBase = git([\"merge-base\", commit.out.trim(), \"HEAD\"], codebaseRoot);\n const base = mergeBase.ok && mergeBase.out.trim() ? mergeBase.out.trim() : commit.out.trim();\n\n const repo = { changed: new Set<string>(), deleted: new Map<string, string | null>() };\n for (const args of [\n [\"diff\", \"--name-status\", \"-z\", \"-M\", base, \"HEAD\"],\n [\"diff\", \"--name-status\", \"-z\", \"-M\", \"HEAD\"],\n ]) {\n const r = git(args, codebaseRoot);\n if (!r.ok) throw new CLIError(`git ${args.join(\" \")} failed: ${r.error}`, ErrorCategory.SYSTEM);\n parseNameStatus(r.out, repo);\n }\n const untracked = git([\"ls-files\", \"--others\", \"--exclude-standard\", \"-z\", \"--full-name\"], top.out.trim());\n const untrackedFiles = untracked.ok ? untracked.out.split(\"\\0\").filter(Boolean) : [];\n for (const f of untrackedFiles) {\n // A file deleted or renamed away in the range and then recreated is there again.\n repo.changed.add(f);\n repo.deleted.delete(f);\n }\n // The changed lines, from the base to the working tree; an untracked file is all new.\n // Only source files count, and never DR's own folders.\n const pathspecs = [\n ...SOURCE_EXTENSIONS.map((ext) => `:(glob,icase)**/*.${ext}`),\n ...DR_OWN_DIRS.map((d) => `:(exclude)${d}`),\n ];\n const diff = git([\"diff\", \"-U0\", \"-M\", \"--no-color\", \"--no-ext-diff\", base, \"--\", ...pathspecs], codebaseRoot);\n const repoHunks = diff.ok ? parseHunks(diff.out) : new Map<string, LineRanges>();\n const hunksError = diff.ok ? undefined : diff.error.split(\"\\n\")[0];\n for (const f of untrackedFiles) repoHunks.set(f, \"all\");\n\n // git names files from the top of the work tree; source references from the codebase root.\n const prefix = toPosix(path.relative(top.out.trim(), codebaseRoot));\n const local = (f: string): string | null => {\n const l = !prefix ? f : f.startsWith(prefix + \"/\") ? f.slice(prefix.length + 1) : null;\n return l && !inDirs(l, DR_OWN_DIRS) ? l : null;\n };\n const changed = new Set<string>();\n for (const f of repo.changed) {\n const l = local(f);\n if (l) changed.add(l);\n }\n const deleted = new Map<string, string | null>();\n for (const [from, to] of repo.deleted) {\n const l = local(from);\n if (l) deleted.set(l, to ? local(to) : null);\n }\n const hunks = new Map<string, LineRanges>();\n for (const [f, ranges] of repoHunks) {\n const l = local(f);\n if (l && changed.has(l)) hunks.set(l, ranges);\n }\n const topDir = top.out.trim();\n const fileAtBase = (file: string): string | null | undefined => {\n const r = git([\"show\", `${base}:${prefix ? `${prefix}/${file}` : file}`], topDir);\n if (r.ok) return r.out;\n // Absent at the base, or a failure git can't explain (too big, git broken): can't tell.\n return /does not exist in|exists on disk, but not in/.test(r.error) ? null : undefined;\n };\n return { base, changed, deleted, hunks, ...(hunksError ? { hunksError } : {}), fileAtBase };\n}\n\n/**\n * The part of a range that is code: changed and deleted files that `keep` accepts (a rename when\n * either path is kept). DR's own folders are left out already.\n */\nexport function scopeChanges(changes: CodeChangesResult, keep: (file: string) => boolean): CodeChangesResult {\n const changed = new Set([...changes.changed].filter(keep));\n const deleted = new Map([...changes.deleted].filter(([from, to]) => keep(from) || (to !== null && keep(to))));\n const hunks = changes.hunks ? new Map([...changes.hunks].filter(([f]) => changed.has(f))) : undefined;\n return { ...changes, changed, deleted, ...(hunks ? { hunks } : {}) };\n}\n\n/**\n * Source files with uncommitted changes (modified, added or untracked) written after `sinceMs`,\n * relative to the codebase root. An index counts as fresh while HEAD hasn't moved, so these are\n * edits the index may not have seen. Empty when git can't say.\n */\nexport async function sourceEditsSince(codebaseRoot: string, sinceMs: number, isSource: (file: string) => boolean): Promise<string[]> {\n const top = git([\"rev-parse\", \"--show-toplevel\"], codebaseRoot);\n if (!top.ok) return [];\n const status = git([\"status\", \"--porcelain\", \"-z\", \"--untracked-files=all\", \"--\", \".\"], codebaseRoot);\n if (!status.ok) return [];\n const prefix = toPosix(path.relative(top.out.trim(), codebaseRoot));\n const out: string[] = [];\n const parts = status.out.split(\"\\0\");\n for (let i = 0; i < parts.length; i++) {\n const entry = parts[i];\n if (entry.length < 4) continue;\n const code = entry.slice(0, 2);\n const file = entry.slice(3);\n if (code[0] === \"R\" || code[0] === \"C\") i++; // the next field is the old path\n if (code.includes(\"D\")) continue;\n const local = prefix ? (file.startsWith(prefix + \"/\") ? file.slice(prefix.length + 1) : null) : file;\n if (!local || !isSource(local)) continue;\n try {\n if (statSync(path.join(codebaseRoot, local)).mtimeMs > sinceMs) out.push(local);\n } catch {\n // gone since git listed it\n }\n }\n return out.sort();\n}\n"],
5
+ "mappings": "AAMA,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;AACzB,OAAO,UAAU;AACjB,SAAS,UAAU,qBAAqB;AACxC,SAAS,yBAAyB;AAW3B,MAAM,cAAiC,OAAO,OAAO,CAAC,0BAA0B,KAAK,CAAC;AAGvF,SAAU,OAAO,MAAc,MAAuB;AAC1D,SAAO,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,SAAS,KAAK,KAAK,WAAW,EAAE,QAAQ,QAAQ,EAAE,IAAI,GAAG,EAAE;AAC7F;AAGA,MAAM,aAAa,KAAK,OAAO;AAE/B,SAAS,IAAI,MAAgB,KAAW;AACtC,QAAM,IAAI,UAAU,OAAO,CAAC,MAAM,wBAAwB,GAAG,IAAI,GAAG,EAAE,KAAK,UAAU,SAAS,OAAO,QAAQ,SAAS,KAAQ,WAAW,WAAU,CAAE;AACrJ,SAAI,EAAE,QAAc,EAAE,IAAI,IAAO,OAAO,EAAE,MAAM,QAAO,IACnD,EAAE,WAAW,IAAU,EAAE,IAAI,IAAO,QAAQ,EAAE,UAAU,EAAE,UAAU,cAAc,EAAE,MAAM,IAAI,KAAI,EAAE,IACjG,EAAE,IAAI,IAAM,KAAK,EAAE,OAAM;AAClC;AAEA,SAAS,QAAQ,GAAS;AACxB,SAAO,EAAE,MAAM,KAAK,GAAG,EAAE,KAAK,GAAG;AACnC;AAGM,SAAU,gBAAgB,KAAa,MAAmE;AAC9G,QAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,WAAS,IAAI,GAAG,IAAI,MAAM,UAAU;AAClC,UAAM,SAAS,MAAM,GAAG;AACxB,QAAI,CAAC;AAAQ;AACb,UAAM,OAAO,OAAO,CAAC;AACrB,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,YAAM,OAAO,MAAM,GAAG,GAChB,KAAK,MAAM,GAAG;AACpB,MAAI,SAAS,OAAO,SAClB,KAAK,QAAQ,IAAI,MAAM,MAAM,IAAI,GACjC,KAAK,QAAQ,OAAO,IAAI,IAEtB,OACF,KAAK,QAAQ,IAAI,EAAE,GACnB,KAAK,QAAQ,OAAO,EAAE;IAE1B,OAAO;AACL,YAAM,OAAO,MAAM,GAAG;AACtB,UAAI,CAAC;AAAM;AACX,MAAI,SAAS,OACX,KAAK,QAAQ,IAAI,MAAM,IAAI,GAC3B,KAAK,QAAQ,OAAO,IAAI,MAExB,KAAK,QAAQ,IAAI,IAAI,GACrB,KAAK,QAAQ,OAAO,IAAI;IAE5B;EACF;AACF;AAMM,SAAU,WAAW,KAAW;AACpC,QAAM,QAAQ,oBAAI,IAAG;AACrB,MAAI,OAAsB,MACtB,UAAU;AACd,aAAW,QAAQ,IAAI,MAAM;CAAI;AAC/B,QAAI,KAAK,WAAW,aAAa;AAC/B,aAAO,MACP,UAAU;aACD,KAAK,WAAW,MAAM;AAC/B,gBAAU,SAAS;aACV,KAAK,WAAW,MAAM;AAC/B,aAAO,SAAS,kBAAkB,OAAO,KAAK,MAAM,CAAC,EAAE,QAAQ,OAAO,EAAE,EAAE,QAAQ,QAAQ,EAAE,GACxF,QAAQ,WAAS,MAAM,IAAI,MAAM,KAAK;aACjC,QAAQ,KAAK,WAAW,IAAI,GAAG;AACxC,YAAM,IAAI,0CAA0C,KAAK,IAAI,GACvD,SAAS,MAAM,IAAI,IAAI,KAAK,CAAA;AAClC,UAAI,CAAC,KAAK,WAAW;AAAO;AAC5B,YAAM,QAAQ,KAAK,IAAI,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,GAChC,QAAQ,EAAE,CAAC,MAAM,SAAY,IAAI,OAAO,EAAE,CAAC,CAAC;AAGlD,aAAO,KAAK,UAAU,IAAI,CAAC,OAAO,OAAO,EAAI,IAAI,CAAC,OAAO,QAAQ,QAAQ,CAAC,CAAC,GAC3E,MAAM,IAAI,MAAM,MAAM;IACxB;AAEF,SAAO;AACT;AAGM,SAAU,eAAe,QAAgC,OAAsB,KAAkB;AACrG,MAAI,CAAC;AAAQ,WAAO;AACpB,MAAI,WAAW;AAAO,WAAO;AAC7B,MAAI,UAAU;AAAM,WAAO;AAC3B,QAAM,OAAO,OAAO;AAGpB,SAAO,OAAO,KAAK,CAAC,CAAC,GAAG,GAAG,QAAQ,MAAO,WAAW,KAAK,SAAS,IAAI,OAAO,KAAK,QAAQ,KAAK,KAAM;AACxG;AASA,eAAsB,iBAAiB,cAAsB,OAAa;AACxE,QAAM,MAAM,IAAI,CAAC,aAAa,iBAAiB,GAAG,YAAY;AAC9D,MAAI,CAAC,IAAI;AAAI,UAAM,IAAI,SAAS,wCAAwC,YAAY,IAAI,cAAc,MAAM,CAAC,qCAAqC,CAAC;AACnJ,MAAI,MAAM,WAAW,GAAG;AAAG,UAAM,IAAI,SAAS,2BAA2B,KAAK,IAAI,cAAc,IAAI;AACpG,QAAM,SAAS,IAAI,CAAC,aAAa,YAAY,WAAW,GAAG,KAAK,WAAW,GAAG,YAAY;AAC1F,MAAI,CAAC,OAAO,MAAM,CAAC,OAAO,IAAI,KAAI;AAChC,UAAM,IAAI,SAAS,6CAA6C,KAAK,IAAI,cAAc,MAAM;MAC3F;KACD;AAEH,QAAM,YAAY,IAAI,CAAC,cAAc,OAAO,IAAI,KAAI,GAAI,MAAM,GAAG,YAAY,GACvE,OAAO,UAAU,MAAM,UAAU,IAAI,KAAI,IAAK,UAAU,IAAI,KAAI,IAAK,OAAO,IAAI,KAAI,GAEpF,OAAO,EAAE,SAAS,oBAAI,IAAG,GAAY,SAAS,oBAAI,IAAG,EAAyB;AACpF,aAAW,QAAQ;IACjB,CAAC,QAAQ,iBAAiB,MAAM,MAAM,MAAM,MAAM;IAClD,CAAC,QAAQ,iBAAiB,MAAM,MAAM,MAAM;KAC3C;AACD,UAAM,IAAI,IAAI,MAAM,YAAY;AAChC,QAAI,CAAC,EAAE;AAAI,YAAM,IAAI,SAAS,OAAO,KAAK,KAAK,GAAG,CAAC,YAAY,EAAE,KAAK,IAAI,cAAc,MAAM;AAC9F,oBAAgB,EAAE,KAAK,IAAI;EAC7B;AACA,QAAM,YAAY,IAAI,CAAC,YAAY,YAAY,sBAAsB,MAAM,aAAa,GAAG,IAAI,IAAI,KAAI,CAAE,GACnG,iBAAiB,UAAU,KAAK,UAAU,IAAI,MAAM,IAAI,EAAE,OAAO,OAAO,IAAI,CAAA;AAClF,aAAW,KAAK;AAEd,SAAK,QAAQ,IAAI,CAAC,GAClB,KAAK,QAAQ,OAAO,CAAC;AAIvB,QAAM,YAAY;IAChB,GAAG,kBAAkB,IAAI,CAAC,QAAQ,qBAAqB,GAAG,EAAE;IAC5D,GAAG,YAAY,IAAI,CAAC,MAAM,aAAa,CAAC,EAAE;KAEtC,OAAO,IAAI,CAAC,QAAQ,OAAO,MAAM,cAAc,iBAAiB,MAAM,MAAM,GAAG,SAAS,GAAG,YAAY,GACvG,YAAY,KAAK,KAAK,WAAW,KAAK,GAAG,IAAI,oBAAI,IAAG,GACpD,aAAa,KAAK,KAAK,SAAY,KAAK,MAAM,MAAM;CAAI,EAAE,CAAC;AACjE,aAAW,KAAK;AAAgB,cAAU,IAAI,GAAG,KAAK;AAGtD,QAAM,SAAS,QAAQ,KAAK,SAAS,IAAI,IAAI,KAAI,GAAI,YAAY,CAAC,GAC5D,QAAQ,CAAC,MAA4B;AACzC,UAAM,IAAK,SAAa,EAAE,WAAW,SAAS,GAAG,IAAI,EAAE,MAAM,OAAO,SAAS,CAAC,IAAI,OAA9D;AACpB,WAAO,KAAK,CAAC,OAAO,GAAG,WAAW,IAAI,IAAI;EAC5C,GACM,UAAU,oBAAI,IAAG;AACvB,aAAW,KAAK,KAAK,SAAS;AAC5B,UAAM,IAAI,MAAM,CAAC;AACjB,IAAI,KAAG,QAAQ,IAAI,CAAC;EACtB;AACA,QAAM,UAAU,oBAAI,IAAG;AACvB,aAAW,CAAC,MAAM,EAAE,KAAK,KAAK,SAAS;AACrC,UAAM,IAAI,MAAM,IAAI;AACpB,IAAI,KAAG,QAAQ,IAAI,GAAG,KAAK,MAAM,EAAE,IAAI,IAAI;EAC7C;AACA,QAAM,QAAQ,oBAAI,IAAG;AACrB,aAAW,CAAC,GAAG,MAAM,KAAK,WAAW;AACnC,UAAM,IAAI,MAAM,CAAC;AACjB,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAG,MAAM,IAAI,GAAG,MAAM;EAC9C;AACA,QAAM,SAAS,IAAI,IAAI,KAAI;AAO3B,SAAO,EAAE,MAAM,SAAS,SAAS,OAAO,GAAI,aAAa,EAAE,WAAU,IAAK,CAAA,GAAK,YAN5D,CAAC,SAA2C;AAC7D,UAAM,IAAI,IAAI,CAAC,QAAQ,GAAG,IAAI,IAAI,SAAS,GAAG,MAAM,IAAI,IAAI,KAAK,IAAI,EAAE,GAAG,MAAM;AAChF,WAAI,EAAE,KAAW,EAAE,MAEZ,+CAA+C,KAAK,EAAE,KAAK,IAAI,OAAO;EAC/E,EACyF;AAC3F;AAMM,SAAU,aAAa,SAA4B,MAA+B;AACtF,QAAM,UAAU,IAAI,IAAI,CAAC,GAAG,QAAQ,OAAO,EAAE,OAAO,IAAI,CAAC,GACnD,UAAU,IAAI,IAAI,CAAC,GAAG,QAAQ,OAAO,EAAE,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,KAAM,OAAO,QAAQ,KAAK,EAAE,CAAE,CAAC,GACtG,QAAQ,QAAQ,QAAQ,IAAI,IAAI,CAAC,GAAG,QAAQ,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,QAAQ,IAAI,CAAC,CAAC,CAAC,IAAI;AAC5F,SAAO,EAAE,GAAG,SAAS,SAAS,SAAS,GAAI,QAAQ,EAAE,MAAK,IAAK,CAAA,EAAG;AACpE;AAOA,eAAsB,iBAAiB,cAAsB,SAAiB,UAAmC;AAC/G,QAAM,MAAM,IAAI,CAAC,aAAa,iBAAiB,GAAG,YAAY;AAC9D,MAAI,CAAC,IAAI;AAAI,WAAO,CAAA;AACpB,QAAM,SAAS,IAAI,CAAC,UAAU,eAAe,MAAM,yBAAyB,MAAM,GAAG,GAAG,YAAY;AACpG,MAAI,CAAC,OAAO;AAAI,WAAO,CAAA;AACvB,QAAM,SAAS,QAAQ,KAAK,SAAS,IAAI,IAAI,KAAI,GAAI,YAAY,CAAC,GAC5D,MAAgB,CAAA,GAChB,QAAQ,OAAO,IAAI,MAAM,IAAI;AACnC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,QAAQ,MAAM,CAAC;AACrB,QAAI,MAAM,SAAS;AAAG;AACtB,UAAM,OAAO,MAAM,MAAM,GAAG,CAAC,GACvB,OAAO,MAAM,MAAM,CAAC;AAE1B,SADI,KAAK,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,QAAK,KACpC,KAAK,SAAS,GAAG;AAAG;AACxB,UAAM,QAAQ,SAAU,KAAK,WAAW,SAAS,GAAG,IAAI,KAAK,MAAM,OAAO,SAAS,CAAC,IAAI,OAAQ;AAChG,QAAI,GAAC,SAAS,CAAC,SAAS,KAAK;AAC7B,UAAI;AACF,QAAI,SAAS,KAAK,KAAK,cAAc,KAAK,CAAC,EAAE,UAAU,WAAS,IAAI,KAAK,KAAK;MAChF,QAAQ;MAER;EACF;AACA,SAAO,IAAI,KAAI;AACjB;",
6
+ "names": []
7
+ }