@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,49 @@
1
+ function edgePreferences(m) {
2
+ if (m.usage !== "verification_and_mapping")
3
+ return [];
4
+ const listed = m.conditions?.predicates;
5
+ return Array.isArray(listed) && listed.length ? listed.map((p, i) => {
6
+ if (typeof p == "string")
7
+ return { predicate: p, invert: !1 };
8
+ if (p && typeof p == "object" && typeof p.predicate == "string") {
9
+ const o = p, types = (v) => Array.isArray(v) ? v.filter((t) => typeof t == "string") : void 0, from = types(o.from_types), to = types(o.to_types);
10
+ return { predicate: o.predicate, invert: o.invert === !0, ...from ? { from_types: from } : {}, ...to ? { to_types: to } : {} };
11
+ }
12
+ throw new Error(`Edge mapping ${m.cbm_edge}: conditions.predicates[${i}] must be a predicate name or {predicate, invert}`);
13
+ }) : m.dr_relationship ? [{ predicate: m.dr_relationship, invert: m.directionality_transform === "invert" }] : [];
14
+ }
15
+ function typeAllowed(allowed, type) {
16
+ return !Array.isArray(allowed) || !allowed.length ? !0 : allowed.some((a) => typeof a == "string" && (a === type || a.endsWith(".*") && type.startsWith(a.slice(0, -1))));
17
+ }
18
+ function edgeAllowed(m, fromType, toType) {
19
+ const c = m.conditions ?? {};
20
+ return typeAllowed(c.from_types, fromType) && typeAllowed(c.to_types, toType);
21
+ }
22
+ function relationshipFor(m, fromType, toType, hasTriple) {
23
+ if (!edgeAllowed(m, fromType, toType))
24
+ return null;
25
+ for (const pref of edgePreferences(m)) {
26
+ if (!typeAllowed(pref.from_types, fromType) || !typeAllowed(pref.to_types, toType))
27
+ continue;
28
+ const [source, destination] = pref.invert ? [toType, fromType] : [fromType, toType], id = `${source}.${pref.predicate}.${destination}`;
29
+ if (hasTriple(id))
30
+ return { id, source, predicate: pref.predicate, destination, inverted: pref.invert };
31
+ }
32
+ return null;
33
+ }
34
+ function dedupeRelationships(items) {
35
+ const seen = /* @__PURE__ */ new Set();
36
+ return items.filter((x) => {
37
+ if (x.sourceId === x.destinationId)
38
+ return !1;
39
+ const key = `${x.sourceId}|${x.relationship.id}|${x.destinationId}`;
40
+ return seen.has(key) ? !1 : (seen.add(key), !0);
41
+ });
42
+ }
43
+ export {
44
+ dedupeRelationships,
45
+ edgeAllowed,
46
+ edgePreferences,
47
+ relationshipFor
48
+ };
49
+ //# sourceMappingURL=edge-relationships.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/analyzers/edge-relationships.ts"],
4
+ "sourcesContent": ["/**\n * Turn a CBM edge between two classified symbols into a DR relationship.\n *\n * An edge mapping (spec/analyzers/<name>/edge-mapping.json) that creates relationships\n * (`usage: \"verification_and_mapping\"`) lists, in `conditions.predicates`, the predicates that can\n * express it in order of preference, each optionally inverted (the DR relationship runs against\n * the CBM edge). The relationship is the first catalog triple\n * `{source type}.{predicate}.{destination type}` that exists for the two element types the ends\n * were classified as; the relationship catalog stays the authority on which triples are valid.\n * Without `conditions.predicates` the mapping's own `dr_relationship` and\n * `directionality_transform` are the only preference. A preference may carry its own `from_types`\n * / `to_types`: it applies only between those types (CALLS from a test case is `tests`).\n *\n * `conditions.from_types` / `conditions.to_types`, when present, restrict the element types the\n * CBM edge's source and target may have (\"layer.type\", or \"layer.*\" for any type of a layer); an\n * edge between other types creates no relationship, whatever the catalog holds.\n *\n * Example: CALLS prefers depends-on, then uses, then accesses. A call from an\n * application.applicationfunction to another becomes\n * application.applicationfunction.depends-on.application.applicationfunction; a call from an\n * application.applicationservice to an application.dataobject has no depends-on or uses triple and\n * becomes application.applicationservice.accesses.application.dataobject.\n *\n * Callers pass only edges between two different accepted elements, and should collapse duplicates\n * with dedupeRelationships: CALLS, CALL_REFERENCE and USAGE can all link the same pair.\n */\nimport type { AnalyzerEdgeMapping } from \"./types.js\";\n\nexport interface EdgePreference {\n predicate: string;\n /** true when the DR relationship runs from the CBM edge's target to its source */\n invert: boolean;\n /** Only when the CBM edge's source is one of these types (\"layer.type\" or \"layer.*\"). */\n from_types?: string[];\n /** Only when the CBM edge's target is one of these types. */\n to_types?: string[];\n}\n\nexport interface EdgeRelationship {\n id: string;\n source: string;\n predicate: string;\n destination: string;\n inverted: boolean;\n}\n\n/** The predicates an edge mapping may use, in order of preference. */\nexport function edgePreferences(m: AnalyzerEdgeMapping): EdgePreference[] {\n if (m.usage !== \"verification_and_mapping\") return [];\n const listed = (m.conditions as { predicates?: unknown } | undefined)?.predicates;\n if (Array.isArray(listed) && listed.length) {\n return listed.map((p, i) => {\n if (typeof p === \"string\") return { predicate: p, invert: false };\n if (p && typeof p === \"object\" && typeof (p as { predicate?: unknown }).predicate === \"string\") {\n const o = p as { predicate: string; invert?: unknown; from_types?: unknown; to_types?: unknown };\n const types = (v: unknown) => (Array.isArray(v) ? v.filter((t): t is string => typeof t === \"string\") : undefined);\n const from = types(o.from_types);\n const to = types(o.to_types);\n return { predicate: o.predicate, invert: o.invert === true, ...(from ? { from_types: from } : {}), ...(to ? { to_types: to } : {}) };\n }\n throw new Error(`Edge mapping ${m.cbm_edge}: conditions.predicates[${i}] must be a predicate name or {predicate, invert}`);\n });\n }\n return m.dr_relationship ? [{ predicate: m.dr_relationship, invert: m.directionality_transform === \"invert\" }] : [];\n}\n\nfunction typeAllowed(allowed: unknown, type: string): boolean {\n if (!Array.isArray(allowed) || !allowed.length) return true;\n return allowed.some((a) => typeof a === \"string\" && (a === type || (a.endsWith(\".*\") && type.startsWith(a.slice(0, -1)))));\n}\n\n/** Whether an edge mapping allows an edge between these two element types (CBM direction). */\nexport function edgeAllowed(m: AnalyzerEdgeMapping, fromType: string, toType: string): boolean {\n const c = (m.conditions ?? {}) as { from_types?: unknown; to_types?: unknown };\n return typeAllowed(c.from_types, fromType) && typeAllowed(c.to_types, toType);\n}\n\n/**\n * The relationship a CBM edge `from \u2192 to` becomes, given the DR element types its ends were\n * classified as, or null when no preferred predicate has a catalog triple for that pair.\n */\nexport function relationshipFor(\n m: AnalyzerEdgeMapping,\n fromType: string,\n toType: string,\n hasTriple: (id: string) => boolean\n): EdgeRelationship | null {\n if (!edgeAllowed(m, fromType, toType)) return null;\n for (const pref of edgePreferences(m)) {\n if (!typeAllowed(pref.from_types, fromType) || !typeAllowed(pref.to_types, toType)) continue;\n const [source, destination] = pref.invert ? [toType, fromType] : [fromType, toType];\n const id = `${source}.${pref.predicate}.${destination}`;\n if (hasTriple(id)) return { id, source, predicate: pref.predicate, destination, inverted: pref.invert };\n }\n return null;\n}\n\n/**\n * One relationship per (source element, relationship id, destination element), keeping the first.\n * `items` carry the element ids of the two ends alongside the relationship.\n */\nexport function dedupeRelationships<T extends { sourceId: string; destinationId: string; relationship: EdgeRelationship }>(items: T[]): T[] {\n const seen = new Set<string>();\n return items.filter((x) => {\n if (x.sourceId === x.destinationId) return false;\n const key = `${x.sourceId}|${x.relationship.id}|${x.destinationId}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n"],
5
+ "mappings": "AA+CM,SAAU,gBAAgB,GAAsB;AACpD,MAAI,EAAE,UAAU;AAA4B,WAAO,CAAA;AACnD,QAAM,SAAU,EAAE,YAAqD;AACvE,SAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,SAC3B,OAAO,IAAI,CAAC,GAAG,MAAK;AACzB,QAAI,OAAO,KAAM;AAAU,aAAO,EAAE,WAAW,GAAG,QAAQ,GAAK;AAC/D,QAAI,KAAK,OAAO,KAAM,YAAY,OAAQ,EAA8B,aAAc,UAAU;AAC9F,YAAM,IAAI,GACJ,QAAQ,CAAC,MAAgB,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,MAAmB,OAAO,KAAM,QAAQ,IAAI,QAClG,OAAO,MAAM,EAAE,UAAU,GACzB,KAAK,MAAM,EAAE,QAAQ;AAC3B,aAAO,EAAE,WAAW,EAAE,WAAW,QAAQ,EAAE,WAAW,IAAM,GAAI,OAAO,EAAE,YAAY,KAAI,IAAK,CAAA,GAAK,GAAI,KAAK,EAAE,UAAU,GAAE,IAAK,CAAA,EAAG;IACpI;AACA,UAAM,IAAI,MAAM,gBAAgB,EAAE,QAAQ,2BAA2B,CAAC,mDAAmD;EAC3H,CAAC,IAEI,EAAE,kBAAkB,CAAC,EAAE,WAAW,EAAE,iBAAiB,QAAQ,EAAE,6BAA6B,SAAQ,CAAE,IAAI,CAAA;AACnH;AAEA,SAAS,YAAY,SAAkB,MAAY;AACjD,SAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,CAAC,QAAQ,SAAe,KAChD,QAAQ,KAAK,CAAC,MAAM,OAAO,KAAM,aAAa,MAAM,QAAS,EAAE,SAAS,IAAI,KAAK,KAAK,WAAW,EAAE,MAAM,GAAG,EAAE,CAAC,EAAG;AAC3H;AAGM,SAAU,YAAY,GAAwB,UAAkB,QAAc;AAClF,QAAM,IAAK,EAAE,cAAc,CAAA;AAC3B,SAAO,YAAY,EAAE,YAAY,QAAQ,KAAK,YAAY,EAAE,UAAU,MAAM;AAC9E;AAMM,SAAU,gBACd,GACA,UACA,QACA,WAAkC;AAElC,MAAI,CAAC,YAAY,GAAG,UAAU,MAAM;AAAG,WAAO;AAC9C,aAAW,QAAQ,gBAAgB,CAAC,GAAG;AACrC,QAAI,CAAC,YAAY,KAAK,YAAY,QAAQ,KAAK,CAAC,YAAY,KAAK,UAAU,MAAM;AAAG;AACpF,UAAM,CAAC,QAAQ,WAAW,IAAI,KAAK,SAAS,CAAC,QAAQ,QAAQ,IAAI,CAAC,UAAU,MAAM,GAC5E,KAAK,GAAG,MAAM,IAAI,KAAK,SAAS,IAAI,WAAW;AACrD,QAAI,UAAU,EAAE;AAAG,aAAO,EAAE,IAAI,QAAQ,WAAW,KAAK,WAAW,aAAa,UAAU,KAAK,OAAM;EACvG;AACA,SAAO;AACT;AAMM,SAAU,oBAA2G,OAAU;AACnI,QAAM,OAAO,oBAAI,IAAG;AACpB,SAAO,MAAM,OAAO,CAAC,MAAK;AACxB,QAAI,EAAE,aAAa,EAAE;AAAe,aAAO;AAC3C,UAAM,MAAM,GAAG,EAAE,QAAQ,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa;AACjE,WAAI,KAAK,IAAI,GAAG,IAAU,MAC1B,KAAK,IAAI,GAAG,GACL;EACT,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,541 @@
1
+ import path from "node:path";
2
+ import { toKebabCase } from "../utils/id-generator.js";
3
+ import { changedInRange } from "./code-changes.js";
4
+ import { dedupeRelationships, relationshipFor } from "./edge-relationships.js";
5
+ import { FILE_JOINED_EDGES, parseRouteName, shortQualifiedName, symbolSpellings } from "./cards/extract.js";
6
+ const REFERENCE_EDGES = /* @__PURE__ */ new Set(["USAGE", "IMPORTS", "CALLS"]);
7
+ function refersToClass(refs, cls) {
8
+ if (!refs)
9
+ return !1;
10
+ const simple = cls.slice(cls.lastIndexOf(".") + 1);
11
+ return refs.has(cls) || refs.has(`${cls}.${simple}`) || refs.has(`${cls}.constructor`) || refs.has(`${cls}.__init__`);
12
+ }
13
+ function decisionKey(file, symbol) {
14
+ return `${file ?? ""}#${routeKey(symbol) ?? symbol}`;
15
+ }
16
+ function routePathOf(s, route) {
17
+ return s.routePath ?? route.path;
18
+ }
19
+ function sourceSymbolOf(s, project) {
20
+ const { card } = s, route = parseRouteName(card.name);
21
+ if (route)
22
+ return `${route.method} ${routePathOf(s, route)}`;
23
+ if (card.label === "Module")
24
+ return shortQualifiedName(s.qualifiedName, project);
25
+ if (card.label === "Method" && card.parent_class) {
26
+ const parent = card.parent_class.split(".").pop();
27
+ if (parent)
28
+ return `${parent}.${card.name}`;
29
+ }
30
+ return card.name;
31
+ }
32
+ function baseNameOf(s, project) {
33
+ const route = parseRouteName(s.card.name);
34
+ return route ? `${route.method} ${routePathOf(s, route).replace(/\{\}/g, "{param}")}` : s.card.label === "Module" ? (s.card.file_path || s.card.name).replace(/\.[^./]+$/, "") : sourceSymbolOf(s, project);
35
+ }
36
+ function modelAddName(name) {
37
+ const root = /^\s*([A-Z]+)\s+\/\s*$/.exec(name), out = root ? `${root[1]} root` : /[/{}]/.test(name) ? name.replace(/[./{}]+/g, " ").replace(/\s+/g, " ").trim() : name.replace(/([\w$)])\.(?=[A-Za-z_$])/g, "$1 ");
38
+ return out !== name ? out : void 0;
39
+ }
40
+ function extractedElementId(layer, type, name) {
41
+ const root = /^\s*([A-Z]+)\s+\/\s*$/.exec(name);
42
+ return `${layer}.${type}.${toKebabCase(root ? `${root[1]} root` : name.replace(/[./{}]+/g, " "))}`;
43
+ }
44
+ function describeDocstring(doc) {
45
+ if (!doc)
46
+ return;
47
+ const text = doc.replace(/^\s*\/\*\*?/, "").replace(/\*\/\s*$/, "").split(`
48
+ `).map((l) => l.replace(/^\s*(\*|\/\/\/?|#)\s?/, "").trim()).join(`
49
+ `).replace(/^["']{3}|["']{3}$/g, "").trim().split(/\n\s*\n/)[0].replace(/\s+/g, " ").trim();
50
+ if (!(!text || text.startsWith("@")))
51
+ return text.length > 280 ? `${text.slice(0, 277)}...` : text;
52
+ }
53
+ function deriveAttributes(layer, type, s) {
54
+ const key = `${layer}.${type}`, route = parseRouteName(s.card.name);
55
+ switch (key) {
56
+ case "api.operation": {
57
+ if (!route)
58
+ return { facts: {}, defaults: {} };
59
+ const p = routePathOf(s, route), segment = p.split("/").find((x) => x && !x.startsWith("{") && !x.startsWith(":")) ?? "default", words = p.replace(/[/{}:]+/g, " ").trim();
60
+ return {
61
+ facts: { http_method: route.method, http_path: p },
62
+ defaults: {
63
+ operationId: toKebabCase(`${route.method} ${words || "root"}`),
64
+ summary: `${route.method} ${p}`,
65
+ tags: segment
66
+ }
67
+ };
68
+ }
69
+ case "testing.testcasesketch":
70
+ return { facts: {}, defaults: { status: "automated" } };
71
+ case "application.applicationinterface":
72
+ return { facts: {}, defaults: { protocol: s.facts.decorator_names.includes("RestController") ? "REST" : "HTTP" } };
73
+ case "application.applicationcomponent":
74
+ return { facts: {}, defaults: { type: "internal" } };
75
+ case "application.applicationservice":
76
+ return { facts: {}, defaults: { serviceType: "synchronous" } };
77
+ case "data-model.objectschema":
78
+ return { facts: {}, defaults: { type: "object" } };
79
+ case "data-store.accesspattern": {
80
+ const entity = /Repository\s*<\s*([\w.]+)/.exec(s.card.source ?? s.card.signature ?? "")?.[1];
81
+ return { facts: {}, defaults: { patternType: "CUSTOM", ...entity ? { targetCollection: entity.split(".").pop() } : {} } };
82
+ }
83
+ default:
84
+ return { facts: {}, defaults: {} };
85
+ }
86
+ }
87
+ function codeLocations(e) {
88
+ return (e.sourceReference?.locations ?? []).filter((l) => typeof l.file == "string" && l.file).map((l) => ({ file: l.file.replace(/\\/g, "/").replace(/^\.\//, ""), ...l.symbol ? { symbol: l.symbol } : {} }));
89
+ }
90
+ function normPath(p) {
91
+ return p.replace(/\{[^}]*\}|:[A-Za-z_]\w*/g, "{}").replace(/\/+$/, "") || "/";
92
+ }
93
+ function indexExisting(graph) {
94
+ const bySymbol = /* @__PURE__ */ new Map(), byRouteSymbol = /* @__PURE__ */ new Map(), byRouteSymbolAnyFile = /* @__PURE__ */ new Map(), byRoute = /* @__PURE__ */ new Map(), byId = /* @__PURE__ */ new Map(), live = [], push = (m, k, e) => (m.get(k) ?? m.set(k, []).get(k)).push(e);
95
+ for (const e of graph.elements.values()) {
96
+ if (byId.set(e.id, e), e.status === "deleted")
97
+ continue;
98
+ live.push(e);
99
+ const locations = codeLocations(e);
100
+ for (const loc of locations) {
101
+ if (!loc.symbol)
102
+ continue;
103
+ push(bySymbol, `${loc.file}#${loc.symbol}`, e);
104
+ const route = routeKey(loc.symbol);
105
+ route && (push(byRouteSymbol, `${loc.file}#${route}`, e), push(byRouteSymbolAnyFile, route, e));
106
+ }
107
+ if (e.layer === "api" && e.type === "operation" && !locations.length) {
108
+ const method = e.attributes.http_method, p = e.attributes.http_path;
109
+ typeof method == "string" && typeof p == "string" && push(byRoute, `${method.toUpperCase()} ${normPath(p)}`, e);
110
+ }
111
+ }
112
+ return { bySymbol, byRouteSymbol, byRouteSymbolAnyFile, byRoute, byId, live };
113
+ }
114
+ function findBySymbol(index, file, spellings) {
115
+ for (const s of spellings)
116
+ if (file) {
117
+ const hit = index.bySymbol.get(`${file}#${s}`);
118
+ if (hit?.length)
119
+ return hit[0];
120
+ }
121
+ }
122
+ function hasSymbol(spellings, symbol) {
123
+ return !!spellings && spellings.has(symbol);
124
+ }
125
+ const LABEL_ORDER = ["Route", "Class", "Interface", "Type", "Enum", "Trait", "Struct", "Function", "Method", "Module"], KIND_ORDER = ["needs-attributes", "ambiguous", "narrowed", "no-rule", "unhandled-route"];
126
+ function inLayers(layer, layers) {
127
+ return !layers || layers.has(layer);
128
+ }
129
+ function sortCards(cards) {
130
+ const rank = (l) => {
131
+ const i = LABEL_ORDER.indexOf(l);
132
+ return i < 0 ? LABEL_ORDER.length : i;
133
+ };
134
+ return cards.sort((a, b) => (a.changedInRange ? 0 : 1) - (b.changedInRange ? 0 : 1) || KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind) || rank(a.label) - rank(b.label) || (a.file ?? "").localeCompare(b.file ?? "") || a.qualifiedName.localeCompare(b.qualifiedName));
135
+ }
136
+ function cardOf(s, project, kind, reason, extra = {}, hunks, renamedTo) {
137
+ const { card } = s, doc = describeDocstring(card.docstring), name = modelAddName(baseNameOf(s, project)), ranges = hunks && card.file_path ? hunks.get(card.file_path) : void 0, changed = card.label === "Module" ? !!hunks && !!card.file_path && (ranges === "all" || !!renamedTo?.has(card.file_path)) : changedInRange(ranges, card.start_line, card.end_line);
138
+ return {
139
+ cardId: card.id,
140
+ kind,
141
+ label: card.label,
142
+ symbol: sourceSymbolOf(s, project),
143
+ ...changed ? { changedInRange: !0 } : {},
144
+ ...name ? { name } : {},
145
+ file: card.file_path,
146
+ lines: card.start_line !== null ? `${card.start_line}-${card.end_line ?? card.start_line}` : null,
147
+ qualifiedName: card.qualified_name,
148
+ ...card.signature ? { signature: card.signature.slice(0, 200) } : {},
149
+ ...doc ? { docstring: doc } : {},
150
+ ...s.facts.decorator_names.length ? { decorators: s.facts.decorator_names } : {},
151
+ candidates: [],
152
+ reason,
153
+ ...extra
154
+ };
155
+ }
156
+ function planElements(extraction, existing, options) {
157
+ const { project, config } = extraction, layers = options.layers?.length ? new Set(options.layers) : null, index = indexExisting(existing), taken = new Set(index.byId.keys()), plan = {
158
+ adds: [],
159
+ updates: [],
160
+ unchanged: 0,
161
+ modelled: 0,
162
+ rejected: 0,
163
+ skipped: 0,
164
+ outsideLayers: 0,
165
+ retyped: [],
166
+ undecided: [],
167
+ candidateDeletes: [],
168
+ elementOf: /* @__PURE__ */ new Map(),
169
+ descriptions: {}
170
+ }, matched = /* @__PURE__ */ new Set(), hunks = options.changes?.hunks, renamedTo = renameTargets(options.changes), cardFor = (s, kind, reason, extra = {}) => cardOf(s, project, kind, reason, extra, hunks, renamedTo), notElements = options.notElements ?? /* @__PURE__ */ new Set(), symbols = [...extraction.symbols].sort((a, b) => (a.card.file_path ?? "").localeCompare(b.card.file_path ?? "") || a.qualifiedName.localeCompare(b.qualifiedName)), claimed = /* @__PURE__ */ new Set(), matchOf = /* @__PURE__ */ new Map(), ambiguousWith = /* @__PURE__ */ new Map(), pick = (candidates, prefer) => {
171
+ if (!candidates?.length)
172
+ return;
173
+ const free = candidates.filter((e) => !claimed.has(e.id));
174
+ return free.length ? (prefer && free.find(prefer)) ?? free[0] : "claimed";
175
+ }, tryLookups = (s, lookups) => {
176
+ for (const lookup of lookups) {
177
+ const hit = lookup();
178
+ if (hit === "claimed") {
179
+ const owner = lookupOwner(s);
180
+ owner && ambiguousWith.set(s, owner);
181
+ continue;
182
+ }
183
+ if (hit) {
184
+ claimed.add(hit.id), matchOf.set(s, hit), ambiguousWith.delete(s);
185
+ return;
186
+ }
187
+ }
188
+ }, lookupOwner = (s) => {
189
+ const file = s.card.file_path, route = parseRouteName(s.card.name);
190
+ return (route ? [file ? index.byRouteSymbol.get(`${file}#${routeKey(s.card.name)}`) : index.byRouteSymbolAnyFile.get(routeKey(s.card.name)), index.byRoute.get(`${route.method} ${normPath(route.path)}`)] : [file ? index.bySymbol.get(`${file}#${s.card.name}`) : void 0]).find((k) => k?.length)?.[0]?.id;
191
+ }, strongSpellings = (s) => {
192
+ const symbol = sourceSymbolOf(s, project);
193
+ return [symbol, ...symbolSpellings(s.qualifiedName, s.card.name, project)].filter((sp, i, all) => all.indexOf(sp) === i && (sp === symbol || sp !== s.card.name));
194
+ };
195
+ for (const s of symbols) {
196
+ const file = s.card.file_path;
197
+ file && tryLookups(s, strongSpellings(s).map((sp) => () => pick(index.bySymbol.get(`${file}#${sp}`))));
198
+ }
199
+ for (const s of symbols) {
200
+ if (matchOf.has(s))
201
+ continue;
202
+ const { card, tier0 } = s, file = card.file_path, route = parseRouteName(card.name), lookups = [];
203
+ if (route) {
204
+ const key = routeKey(card.name), exact = (e) => e.attributes.http_path === route.path;
205
+ lookups.push(() => pick(file ? index.byRouteSymbol.get(`${file}#${key}`) : index.byRouteSymbolAnyFile.get(key))), lookups.push(() => pick(index.byRoute.get(`${route.method} ${normPath(route.path)}`), exact));
206
+ } else file && sourceSymbolOf(s, project) !== card.name && lookups.push(() => pick(index.bySymbol.get(`${file}#${card.name}`)));
207
+ if (tier0?.kind === "decide") {
208
+ const decided = tier0;
209
+ lookups.push(() => {
210
+ const byId = index.byId.get(extractedElementId(decided.layer, decided.type, baseNameOf(s, project)));
211
+ if (!(!byId || byId.status === "deleted" || byId.layer !== decided.layer || byId.type !== decided.type || codeLocations(byId).length))
212
+ return pick([byId]);
213
+ });
214
+ }
215
+ tryLookups(s, lookups);
216
+ }
217
+ for (const s of symbols) {
218
+ const { card, tier0 } = s, file = card.file_path, symbol = sourceSymbolOf(s, project), decided = tier0?.kind === "decide" ? tier0 : null, spellings = [symbol, ...symbolSpellings(s.qualifiedName, card.name, project)], match = matchOf.get(s);
219
+ if (!match && notElements.has(decisionKey(file, symbol))) {
220
+ plan.skipped++;
221
+ continue;
222
+ }
223
+ const ambiguous = !match && tier0?.kind !== "reject" ? ambiguousWith.get(s) : void 0;
224
+ if (ambiguous) {
225
+ if (decided && !inLayers(decided.layer, layers)) {
226
+ plan.outsideLayers++;
227
+ continue;
228
+ }
229
+ plan.undecided.push(cardFor(s, "ambiguous", `Its name also matches ${ambiguous}, which another symbol already matched. If it is a separate element, stage it under its own symbol; if it is that one, add this location to it.`, {
230
+ candidates: decided ? [`${decided.layer}.${decided.type}`] : tier0?.kind === "narrow" ? tier0.candidates.map((c) => `${c.layer}.${c.type ?? "*"}`) : [],
231
+ ...tier0 && tier0.kind !== "reject" ? { rule: tier0.rule } : {}
232
+ }));
233
+ continue;
234
+ }
235
+ if (match) {
236
+ if (matched.add(match.id), plan.elementOf.set(s.qualifiedName, match.id), !decided) {
237
+ plan.modelled++;
238
+ continue;
239
+ }
240
+ if (match.layer !== decided.layer || match.type !== decided.type) {
241
+ plan.retyped.push({ id: match.id, currentType: `${match.layer}.${match.type}`, decidedType: `${decided.layer}.${decided.type}`, rule: decided.rule, file, symbol });
242
+ continue;
243
+ }
244
+ if (!inLayers(decided.layer, layers)) {
245
+ plan.outsideLayers++;
246
+ continue;
247
+ }
248
+ const fields = changedFields(match, s, symbol, spellings, decided.layer, decided.type, options.extractedDescriptions ?? {});
249
+ typeof fields.description == "string" && (plan.descriptions[match.id] = fields.description), Object.keys(fields).length ? plan.updates.push({ id: match.id, layer: match.layer, fields, symbol: s }) : plan.unchanged++;
250
+ continue;
251
+ }
252
+ if (s.unhandled) {
253
+ if (layers && !layers.has("api")) {
254
+ plan.outsideLayers++;
255
+ continue;
256
+ }
257
+ plan.undecided.push(cardFor(s, "unhandled-route", "Nothing in this code handles this route (no handler, no file of its own): it is most likely a URL that tests or client code call. Model it as an api.operation only if this codebase serves it; otherwise reject it.", { candidates: ["api.operation"] }));
258
+ continue;
259
+ }
260
+ if (!tier0 || tier0.kind === "narrow") {
261
+ const candidates = tier0 ? tier0.candidates.map((c) => `${c.layer}.${c.type ?? "*"}`) : [], hint = config.labelDefaults[card.label];
262
+ if (!(layers ? tier0 ? tier0.candidates.some((c) => layers.has(c.layer)) : !!hint && layers.has(hint.split(".")[0]) : !0)) {
263
+ plan.outsideLayers++;
264
+ continue;
265
+ }
266
+ plan.undecided.push(tier0 ? cardFor(s, "narrowed", `Tier 0 rule ${tier0.rule} narrows it to these types; choosing needs judgment (Tier 1 is not wired).`, {
267
+ candidates: candidates.filter((c) => !layers || layers.has(c.split(".")[0])),
268
+ rule: tier0.rule
269
+ }) : cardFor(s, "no-rule", `No Tier-0 rule covers this ${card.label}: decide whether it is a model element, and which.`, {
270
+ ...hint ? { hint } : {}
271
+ }));
272
+ continue;
273
+ }
274
+ if (tier0.kind === "reject") {
275
+ plan.rejected++;
276
+ continue;
277
+ }
278
+ if (!inLayers(tier0.layer, layers)) {
279
+ plan.outsideLayers++;
280
+ continue;
281
+ }
282
+ const { facts, defaults } = deriveAttributes(tier0.layer, tier0.type, s), base = baseNameOf(s, project), name = uniqueName(base, s, tier0.layer, tier0.type, taken), id = extractedElementId(tier0.layer, tier0.type, name);
283
+ taken.add(id);
284
+ const description = describeDocstring(card.docstring);
285
+ description && (plan.descriptions[id] = description), plan.adds.push({
286
+ id,
287
+ layer: tier0.layer,
288
+ type: tier0.type,
289
+ name,
290
+ ...description ? { description } : {},
291
+ attributes: { ...defaults, ...facts },
292
+ ...file ? { sourceReference: { provenance: "extracted", locations: [{ file, symbol }] } } : {},
293
+ symbol: s,
294
+ rule: tier0.rule
295
+ }), plan.elementOf.set(s.qualifiedName, id);
296
+ }
297
+ return plan.candidateDeletes = candidateDeletes(extraction, index, matched, plan.adds, options, layers), plan.undecided = sortCards(plan.undecided), plan;
298
+ }
299
+ function renameTargets(changes) {
300
+ return new Set([...changes?.deleted.values() ?? []].filter((to) => !!to));
301
+ }
302
+ function needsAttributesCard(add, project, errors, hunks) {
303
+ return cardOf(add.symbol, project, "needs-attributes", `Tier 0 decided ${add.layer}.${add.type} (${add.rule}), but its attributes don't validate: ${errors.map((e) => e.replace(/^Element '[^']*':\s*/, "")).join("; ")}. Stage it with valid attributes (model_add with proposed.name gives it proposed.id), or reject it if it is not an element.`, {
304
+ candidates: [`${add.layer}.${add.type}`],
305
+ rule: add.rule,
306
+ proposed: { layer: add.layer, type: add.type, name: modelAddName(add.name) ?? add.name, id: add.id, attributes: add.attributes }
307
+ }, hunks);
308
+ }
309
+ function lineSpan(card) {
310
+ const m = /^(\d+)-(\d+)$/.exec(card.lines ?? "");
311
+ return m ? [Number(m[1]), Number(m[2])] : null;
312
+ }
313
+ function attributeChangedLines(cards, hunks) {
314
+ if (!hunks)
315
+ return cards;
316
+ const flagged = cards.filter((c) => c.changedInRange && c.file);
317
+ return cards.map((card) => {
318
+ if (!card.changedInRange || !card.file || card.label === "Module")
319
+ return card;
320
+ const ranges = hunks.get(card.file), own = lineSpan(card);
321
+ if (!ranges || ranges === "all" || !own)
322
+ return card;
323
+ const members = flagged.filter((m) => m !== card && m.file === card.file && m.qualifiedName.startsWith(card.qualifiedName + ".")).map(lineSpan).filter((span) => span !== null);
324
+ if (!members.length)
325
+ return card;
326
+ const inMember = (line) => members.some(([a, b]) => line >= a && line <= b);
327
+ if (ranges.some(([a, b, deletion]) => {
328
+ if (deletion)
329
+ return a >= own[0] && a < own[1] && !members.some(([m0, m1]) => a >= m0 && a < m1);
330
+ for (let line = Math.max(a, own[0]); line <= Math.min(b, own[1]); line++)
331
+ if (!inMember(line))
332
+ return !0;
333
+ return !1;
334
+ }))
335
+ return card;
336
+ const { changedInRange: _changed, ...rest } = card;
337
+ return rest;
338
+ });
339
+ }
340
+ function sortUndecided(cards) {
341
+ return sortCards(cards);
342
+ }
343
+ function uniqueName(base, s, layer, type, taken) {
344
+ const file = s.card.file_path ?? "", stem = path.posix.basename(file).replace(/\.[^.]+$/, ""), dir = path.posix.dirname(file), options = [base, `${stem} ${base}`, `${dir}/${stem} ${base}`, s.card.qualified_name];
345
+ for (const name of options)
346
+ if (!taken.has(extractedElementId(layer, type, name)))
347
+ return name;
348
+ for (let i = 2; ; i++) {
349
+ const name = `${s.card.qualified_name} ${i}`;
350
+ if (!taken.has(extractedElementId(layer, type, name)))
351
+ return name;
352
+ }
353
+ }
354
+ function changedFields(match, s, symbol, spellings, layer, type, extractedDescriptions) {
355
+ const fields = {}, file = s.card.file_path, locations = codeLocations(match), route = routeKey(s.card.name), names = (l) => !!l.symbol && (spellings.includes(l.symbol) || !!route && routeKey(l.symbol) === route);
356
+ if (file && !locations.some((l) => l.file === file && names(l))) {
357
+ const ref = match.sourceReference;
358
+ fields.source_reference = {
359
+ provenance: ref?.provenance ?? "extracted",
360
+ locations: [...ref?.locations ?? [], { file, symbol }],
361
+ ...ref?.repository ? { repository: ref.repository } : {}
362
+ };
363
+ }
364
+ const { facts } = deriveAttributes(layer, type, s);
365
+ for (const [k, v] of Object.entries(facts))
366
+ sameFact(k, match.attributes[k], v) || (fields[`attributes.${k}`] = v);
367
+ const description = describeDocstring(s.card.docstring), current = match.description ?? "";
368
+ return description && current !== description && (!current || current === extractedDescriptions[match.id]) && (fields.description = description), fields;
369
+ }
370
+ function sameFact(key, current, fromCode) {
371
+ if (typeof current == "string" && typeof fromCode == "string") {
372
+ if (key === "http_path")
373
+ return normPath(current) === normPath(fromCode);
374
+ if (key === "http_method")
375
+ return current.toUpperCase() === fromCode.toUpperCase();
376
+ }
377
+ return current === fromCode;
378
+ }
379
+ function candidateDeletes(extraction, index, matched, adds, options, layers) {
380
+ const routes = new Set(extraction.routes.map(routeKey).filter((k) => !!k)), fileSymbols = new Map(Object.entries(extraction.fileSymbols).map(([f, s]) => [f, new Set(s)])), addAt = /* @__PURE__ */ new Map();
381
+ for (const a of adds)
382
+ for (const l of a.sourceReference?.locations ?? [])
383
+ addAt.set(`${l.file}#${l.symbol ?? ""}`, a.id);
384
+ const out = [];
385
+ for (const e of index.live) {
386
+ if (matched.has(e.id) || !inLayers(e.layer, layers))
387
+ continue;
388
+ const locations = codeLocations(e);
389
+ if (!locations.length)
390
+ continue;
391
+ let gone = null, present = !1;
392
+ for (const loc of locations) {
393
+ const verdict = judgeLocation(loc, options, fileSymbols, routes, addAt);
394
+ verdict ? gone ??= verdict : present = !0;
395
+ }
396
+ gone && !present && out.push({ id: e.id, layer: e.layer, type: e.type, ...gone });
397
+ }
398
+ return out.sort((a, b) => a.id.localeCompare(b.id));
399
+ }
400
+ function judgeLocation(loc, options, fileSymbols, routes, addAt) {
401
+ const base = { file: loc.file, ...loc.symbol ? { symbol: loc.symbol } : {} }, changes = options.changes;
402
+ if (changes) {
403
+ if (changes.deleted.has(loc.file)) {
404
+ const to = changes.deleted.get(loc.file);
405
+ if (to) {
406
+ const replacedBy = loc.symbol ? addAt.get(`${to}#${loc.symbol}`) : void 0;
407
+ return { ...base, evidence: "file-renamed", detail: `${loc.file} was renamed to ${to} in the range`, ...replacedBy ? { replacedBy } : {} };
408
+ }
409
+ return { ...base, evidence: "file-deleted", detail: `${loc.file} was deleted in the range` };
410
+ }
411
+ if (!changes.changed.has(loc.file))
412
+ return null;
413
+ } else if (!options.fileExists(loc.file))
414
+ return { ...base, evidence: "file-missing", detail: `${loc.file} does not exist in the codebase` };
415
+ if (!loc.symbol)
416
+ return null;
417
+ const route = routeKey(loc.symbol);
418
+ if (route)
419
+ return routes.has(route) ? null : { ...base, evidence: "symbol-missing", detail: `the code has no route ${loc.symbol}` };
420
+ if (!/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(loc.symbol))
421
+ return null;
422
+ const symbols = fileSymbols.get(loc.file);
423
+ if (hasSymbol(symbols, loc.symbol))
424
+ return null;
425
+ const last = loc.symbol.split(".").pop().replace(/\$/g, "\\$"), names = (text2) => new RegExp(`(^|[^\\w$])${last}($|[^\\w$])`).test(text2), text = options.fileText?.(loc.file) ?? null;
426
+ if (text === null) {
427
+ if (!symbols)
428
+ return null;
429
+ } else if (names(text))
430
+ return null;
431
+ if (changes?.fileAtBase) {
432
+ const oldPath = [...changes.deleted].find(([, to]) => to === loc.file)?.[0], before = changes.fileAtBase(oldPath ?? loc.file);
433
+ return before === void 0 ? { ...base, evidence: "symbol-missing", detail: `${loc.file} has no ${loc.symbol} (not among its symbols, and the name is not in the file); its text at the start of the range couldn't be read` } : before === null || !names(before) ? {
434
+ ...base,
435
+ evidence: "reference-never-matched",
436
+ detail: `The model's reference to ${loc.symbol} matches nothing in ${loc.file}, and ${before === null ? "the file didn't exist" : "the file didn't have it either"} at the start of the range: the range didn't remove it, the reference was wrong before it`
437
+ } : { ...base, evidence: "symbol-missing", detail: `${loc.file} had ${loc.symbol} at the start of the range and doesn't now (not among its symbols, and the name is not in the file)` };
438
+ }
439
+ return { ...base, evidence: "symbol-missing", detail: `${loc.file} has no ${loc.symbol} (not among its symbols, and the name is not in the file)` };
440
+ }
441
+ function routeKey(symbol) {
442
+ const parsed = parseRouteName(symbol);
443
+ if (parsed)
444
+ return `${parsed.method} ${normPath(parsed.path)}`;
445
+ const m = /^([A-Z]+) (\/\S*)$/.exec(symbol);
446
+ return m ? `${m[1]} ${normPath(m[2])}` : null;
447
+ }
448
+ function planRelationships(extraction, existing, elementOf, elements, hasTriple, layers) {
449
+ const { project } = extraction, index = indexExisting(existing), inScope = new Set(extraction.symbols.map((s) => s.qualifiedName)), moduleOf = /* @__PURE__ */ new Map();
450
+ for (const s of extraction.symbols) {
451
+ const id = elementOf.get(s.qualifiedName);
452
+ s.card.label === "Module" && s.card.file_path && id && moduleOf.set(s.card.file_path, id);
453
+ }
454
+ const moduleIds = new Set(moduleOf.values()), layerSet = layers?.length ? new Set(layers) : null, mappings = new Map(extraction.config.edgeMappings.map((m) => [m.cbm_edge, m])), referencedIn = /* @__PURE__ */ new Map();
455
+ for (const e of extraction.edges) {
456
+ if (!REFERENCE_EDGES.has(e.type) || !e.from.file)
457
+ continue;
458
+ let refs = referencedIn.get(e.from.file);
459
+ refs || referencedIn.set(e.from.file, refs = /* @__PURE__ */ new Set()), refs.add(e.to.qualifiedName);
460
+ }
461
+ const direct = (qualifiedName, name, file) => {
462
+ const own = elementOf.get(qualifiedName);
463
+ if (own)
464
+ return own;
465
+ if (inScope.has(qualifiedName))
466
+ return;
467
+ const route = parseRouteName(name) ?? parseRouteName(qualifiedName);
468
+ if (route) {
469
+ const key = `${route.method} ${normPath(route.path)}`;
470
+ return (index.byRouteSymbolAnyFile.get(key)?.[0] ?? index.byRoute.get(key)?.[0])?.id;
471
+ }
472
+ return findBySymbol(index, file, symbolSpellings(qualifiedName, name, project))?.id;
473
+ }, resolve = (end, fileJoined) => {
474
+ const own = direct(end.qualifiedName, end.name, end.file);
475
+ if (own)
476
+ return { id: own };
477
+ if (fileJoined)
478
+ return end.file && moduleOf.has(end.file) ? { id: moduleOf.get(end.file) } : void 0;
479
+ if (parseRouteName(end.name))
480
+ return;
481
+ const cut = end.qualifiedName.lastIndexOf("."), parent = cut > 0 ? end.qualifiedName.slice(0, cut) : "";
482
+ if (!parent || parent === project)
483
+ return;
484
+ const id = direct(parent, parent.slice(parent.lastIndexOf(".") + 1), end.file);
485
+ return id ? { id, rolledUp: parent } : void 0;
486
+ }, found = [];
487
+ for (const edge of extraction.edges) {
488
+ const m = mappings.get(edge.type);
489
+ if (!m)
490
+ continue;
491
+ const fileJoined = FILE_JOINED_EDGES.has(edge.type), ra = resolve(edge.from, fileJoined), rb = resolve(edge.to, fileJoined);
492
+ if (!ra || !rb || ra.rolledUp && edge.to.qualifiedName.startsWith(ra.rolledUp + ".") || rb.rolledUp && edge.from.qualifiedName.startsWith(rb.rolledUp + "."))
493
+ continue;
494
+ const a = ra.id, b = rb.id;
495
+ if (a === b)
496
+ continue;
497
+ const ea = elements.get(a), eb = elements.get(b);
498
+ if (!ea || !eb || ea.layer === "testing" && rb.rolledUp && !moduleIds.has(b) && !(edge.from.file && refersToClass(referencedIn.get(edge.from.file), rb.rolledUp)))
499
+ continue;
500
+ const rel = relationshipFor(m, `${ea.layer}.${ea.type}`, `${eb.layer}.${eb.type}`, hasTriple);
501
+ if (!rel)
502
+ continue;
503
+ const [sourceId, destinationId] = rel.inverted ? [b, a] : [a, b];
504
+ found.push({ sourceId, destinationId, relationship: rel, edge: edge.type });
505
+ }
506
+ const relationships = [];
507
+ let alreadyPresent = 0;
508
+ for (const r of dedupeRelationships(found)) {
509
+ const source = elements.get(r.sourceId), target = elements.get(r.destinationId);
510
+ if (!(layerSet && !layerSet.has(source.layer) && !layerSet.has(target.layer))) {
511
+ if (existing.getRelationship(r.sourceId, r.relationship.predicate, r.destinationId)) {
512
+ alreadyPresent++;
513
+ continue;
514
+ }
515
+ relationships.push({
516
+ source: r.sourceId,
517
+ predicate: r.relationship.predicate,
518
+ target: r.destinationId,
519
+ sourceLayer: source.layer,
520
+ targetLayer: target.layer,
521
+ edge: r.edge
522
+ });
523
+ }
524
+ }
525
+ return relationships.sort((x, y) => x.source.localeCompare(y.source) || x.predicate.localeCompare(y.predicate) || x.target.localeCompare(y.target)), { relationships, alreadyPresent };
526
+ }
527
+ export {
528
+ attributeChangedLines,
529
+ decisionKey,
530
+ deriveAttributes,
531
+ describeDocstring,
532
+ extractedElementId,
533
+ modelAddName,
534
+ needsAttributesCard,
535
+ planElements,
536
+ planRelationships,
537
+ sameFact,
538
+ sortUndecided,
539
+ sourceSymbolOf
540
+ };
541
+ //# sourceMappingURL=extraction-plan.js.map