@topogram/cli 0.3.63 → 0.3.65

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 (344) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.d.ts +6 -0
  4. package/src/adoption/plan.js +12 -703
  5. package/src/adoption/reporting.d.ts +10 -0
  6. package/src/adoption/review-groups.d.ts +6 -0
  7. package/src/agent-brief.d.ts +3 -0
  8. package/src/agent-brief.js +495 -0
  9. package/src/agent-ops/query-builders/auth.js +375 -0
  10. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  12. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  13. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  14. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  15. package/src/agent-ops/query-builders/change-risk.js +25 -0
  16. package/src/agent-ops/query-builders/common.js +149 -0
  17. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  18. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  19. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  20. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  21. package/src/agent-ops/query-builders/work-packets.js +417 -0
  22. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  23. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  24. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  25. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  26. package/src/agent-ops/query-builders.d.ts +26 -0
  27. package/src/agent-ops/query-builders.js +42 -5021
  28. package/src/archive/archive.d.ts +2 -0
  29. package/src/archive/compact.d.ts +1 -0
  30. package/src/archive/unarchive.d.ts +1 -0
  31. package/src/catalog/constants.js +10 -0
  32. package/src/catalog/copy.js +60 -0
  33. package/src/catalog/diagnostics.js +15 -0
  34. package/src/catalog/entries.js +42 -0
  35. package/src/catalog/files.js +67 -0
  36. package/src/catalog/provenance.js +122 -0
  37. package/src/catalog/source.js +150 -0
  38. package/src/catalog/validation.js +252 -0
  39. package/src/catalog.d.ts +12 -0
  40. package/src/catalog.js +18 -750
  41. package/src/cli/catalog-alias.d.ts +1 -0
  42. package/src/cli/command-parser.js +38 -0
  43. package/src/cli/command-parsers/core.js +102 -0
  44. package/src/cli/command-parsers/generator.js +39 -0
  45. package/src/cli/command-parsers/import.js +44 -0
  46. package/src/cli/command-parsers/legacy-workflow.js +21 -0
  47. package/src/cli/command-parsers/project.js +47 -0
  48. package/src/cli/command-parsers/sdlc.js +47 -0
  49. package/src/cli/command-parsers/shared.js +51 -0
  50. package/src/cli/command-parsers/template.js +48 -0
  51. package/src/cli/commands/agent.js +47 -0
  52. package/src/cli/commands/catalog/check.js +31 -0
  53. package/src/cli/commands/catalog/copy.js +59 -0
  54. package/src/cli/commands/catalog/doctor.js +248 -0
  55. package/src/cli/commands/catalog/help.js +21 -0
  56. package/src/cli/commands/catalog/list.js +52 -0
  57. package/src/cli/commands/catalog/runner.js +92 -0
  58. package/src/cli/commands/catalog/shared.js +17 -0
  59. package/src/cli/commands/catalog/show.js +134 -0
  60. package/src/cli/commands/catalog.js +32 -0
  61. package/src/cli/commands/check.js +268 -0
  62. package/src/cli/commands/doctor.js +268 -0
  63. package/src/cli/commands/emit.js +149 -0
  64. package/src/cli/commands/generate.js +96 -0
  65. package/src/cli/commands/generator-policy/package-info.js +162 -0
  66. package/src/cli/commands/generator-policy/payloads.js +372 -0
  67. package/src/cli/commands/generator-policy/printers.js +159 -0
  68. package/src/cli/commands/generator-policy/runner.js +81 -0
  69. package/src/cli/commands/generator-policy/shared.js +39 -0
  70. package/src/cli/commands/generator-policy.js +17 -0
  71. package/src/cli/commands/generator.js +443 -0
  72. package/src/cli/commands/import/adopt.js +170 -0
  73. package/src/cli/commands/import/check.js +91 -0
  74. package/src/cli/commands/import/diff.js +84 -0
  75. package/src/cli/commands/import/help.js +47 -0
  76. package/src/cli/commands/import/paths.js +277 -0
  77. package/src/cli/commands/import/plan.js +284 -0
  78. package/src/cli/commands/import/refresh.js +470 -0
  79. package/src/cli/commands/import/status-history.js +196 -0
  80. package/src/cli/commands/import/workspace.js +230 -0
  81. package/src/cli/commands/import-runner.js +157 -0
  82. package/src/cli/commands/import.js +35 -0
  83. package/src/cli/commands/inspect.js +55 -0
  84. package/src/cli/commands/new.js +94 -0
  85. package/src/cli/commands/package/constants.js +17 -0
  86. package/src/cli/commands/package/doctor.js +240 -0
  87. package/src/cli/commands/package/help.js +27 -0
  88. package/src/cli/commands/package/lockfile.js +135 -0
  89. package/src/cli/commands/package/npm.js +97 -0
  90. package/src/cli/commands/package/reporting.js +35 -0
  91. package/src/cli/commands/package/runner.js +33 -0
  92. package/src/cli/commands/package/shared.js +9 -0
  93. package/src/cli/commands/package/update-cli.js +252 -0
  94. package/src/cli/commands/package/versions.js +35 -0
  95. package/src/cli/commands/package.js +31 -0
  96. package/src/cli/commands/query/change-plan.js +68 -0
  97. package/src/cli/commands/query/definitions.js +202 -0
  98. package/src/cli/commands/query/import-adopt.js +121 -0
  99. package/src/cli/commands/query/runner/artifacts.js +102 -0
  100. package/src/cli/commands/query/runner/boundaries.js +211 -0
  101. package/src/cli/commands/query/runner/change.js +182 -0
  102. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  103. package/src/cli/commands/query/runner/index.js +31 -0
  104. package/src/cli/commands/query/runner/output.js +12 -0
  105. package/src/cli/commands/query/runner/workflow.js +241 -0
  106. package/src/cli/commands/query/runner.js +3 -0
  107. package/src/cli/commands/query/workflow-context.js +5 -0
  108. package/src/cli/commands/query/workspace.js +274 -0
  109. package/src/cli/commands/query.js +11 -0
  110. package/src/cli/commands/release-rollout.js +257 -0
  111. package/src/cli/commands/release-shared.js +528 -0
  112. package/src/cli/commands/release-status.js +429 -0
  113. package/src/cli/commands/release.js +107 -0
  114. package/src/cli/commands/sdlc.js +168 -0
  115. package/src/cli/commands/setup.js +76 -0
  116. package/src/cli/commands/source.js +291 -0
  117. package/src/cli/commands/template/baseline.js +100 -0
  118. package/src/cli/commands/template/check.js +466 -0
  119. package/src/cli/commands/template/constants.js +8 -0
  120. package/src/cli/commands/template/diagnostics.js +26 -0
  121. package/src/cli/commands/template/help.js +28 -0
  122. package/src/cli/commands/template/lifecycle.js +404 -0
  123. package/src/cli/commands/template/list-show.js +287 -0
  124. package/src/cli/commands/template/policy.js +422 -0
  125. package/src/cli/commands/template/shared.js +127 -0
  126. package/src/cli/commands/template/updates.js +352 -0
  127. package/src/cli/commands/template-runner.js +198 -0
  128. package/src/cli/commands/template.js +43 -0
  129. package/src/cli/commands/trust.js +219 -0
  130. package/src/cli/commands/version.js +40 -0
  131. package/src/cli/commands/widget.js +168 -0
  132. package/src/cli/commands/workflow.js +63 -0
  133. package/src/cli/dispatcher.js +392 -0
  134. package/src/cli/help-dispatch.js +188 -0
  135. package/src/cli/help.js +296 -0
  136. package/src/cli/migration-guidance.js +59 -0
  137. package/src/cli/options.js +96 -0
  138. package/src/cli/output-safety.js +107 -0
  139. package/src/cli/path-normalization.js +29 -0
  140. package/src/cli.js +47 -11711
  141. package/src/example-implementation.d.ts +2 -0
  142. package/src/format.d.ts +1 -0
  143. package/src/generator/api/contracts.js +497 -0
  144. package/src/generator/api/metadata.js +221 -0
  145. package/src/generator/api/openapi.js +559 -0
  146. package/src/generator/api/schema.js +124 -0
  147. package/src/generator/api/types.d.ts +98 -0
  148. package/src/generator/api.js +3 -1195
  149. package/src/generator/check.d.ts +1 -0
  150. package/src/generator/context/bundle.d.ts +1 -0
  151. package/src/generator/context/shared/domain-sdlc.js +282 -0
  152. package/src/generator/context/shared/maintained-boundary.js +665 -0
  153. package/src/generator/context/shared/metrics.js +85 -0
  154. package/src/generator/context/shared/primitives.js +64 -0
  155. package/src/generator/context/shared/relationships.js +453 -0
  156. package/src/generator/context/shared/summaries.js +263 -0
  157. package/src/generator/context/shared/types.d.ts +207 -0
  158. package/src/generator/context/shared.d.ts +44 -0
  159. package/src/generator/context/shared.js +80 -1390
  160. package/src/generator/context/slice/core.js +397 -0
  161. package/src/generator/context/slice/sdlc.js +417 -0
  162. package/src/generator/context/slice/ui-packets.js +183 -0
  163. package/src/generator/context/slice.js +2 -859
  164. package/src/generator/native/parity-bundle.js +2 -1
  165. package/src/generator/registry/index.js +507 -0
  166. package/src/generator/registry.js +18 -504
  167. package/src/generator/runtime/environment/index.js +666 -0
  168. package/src/generator/runtime/environment.js +4 -666
  169. package/src/generator/runtime/runtime-check/index.js +554 -0
  170. package/src/generator/runtime/runtime-check.js +4 -554
  171. package/src/generator/runtime/shared/index.js +572 -0
  172. package/src/generator/runtime/shared.js +19 -570
  173. package/src/generator/shared.d.ts +2 -0
  174. package/src/generator/surfaces/shared.d.ts +3 -0
  175. package/src/generator/surfaces/web/html-escape.js +22 -0
  176. package/src/generator/surfaces/web/react.js +10 -8
  177. package/src/generator/surfaces/web/sveltekit.js +7 -5
  178. package/src/generator/surfaces/web/vanilla.js +8 -4
  179. package/src/generator/widget-conformance/behavior-report.js +258 -0
  180. package/src/generator/widget-conformance/checks.js +371 -0
  181. package/src/generator/widget-conformance/projection-context.js +200 -0
  182. package/src/generator/widget-conformance/report.js +166 -0
  183. package/src/generator/widget-conformance/types.d.ts +121 -0
  184. package/src/generator/widget-conformance.js +3 -824
  185. package/src/generator.d.ts +2 -0
  186. package/src/github-client.js +520 -0
  187. package/src/import/core/context.d.ts +3 -0
  188. package/src/import/core/contracts.d.ts +1 -0
  189. package/src/import/core/registry.d.ts +4 -0
  190. package/src/import/core/runner/candidates.js +217 -0
  191. package/src/import/core/runner/options.js +22 -0
  192. package/src/import/core/runner/reports.js +50 -0
  193. package/src/import/core/runner/run.js +79 -0
  194. package/src/import/core/runner/tracks.js +150 -0
  195. package/src/import/core/runner/ui-drafts.js +337 -0
  196. package/src/import/core/runner.js +3 -698
  197. package/src/import/core/shared/api-routes.js +221 -0
  198. package/src/import/core/shared/candidates.js +97 -0
  199. package/src/import/core/shared/files.js +177 -0
  200. package/src/import/core/shared/next-app.js +389 -0
  201. package/src/import/core/shared/types.d.ts +51 -0
  202. package/src/import/core/shared/ui-routes.js +230 -0
  203. package/src/import/core/shared.js +67 -910
  204. package/src/import/extractors/api/flutter-dio.js +4 -8
  205. package/src/import/extractors/api/react-native-repository.js +4 -8
  206. package/src/import/index.d.ts +4 -0
  207. package/src/import/provenance.d.ts +4 -0
  208. package/src/new-project/constants.js +128 -0
  209. package/src/new-project/create.js +83 -0
  210. package/src/new-project/json.js +28 -0
  211. package/src/new-project/metadata.js +96 -0
  212. package/src/new-project/package-spec.js +161 -0
  213. package/src/new-project/project-files.js +348 -0
  214. package/src/new-project/template-policy.js +269 -0
  215. package/src/new-project/template-resolution.js +368 -0
  216. package/src/new-project/template-snapshots.js +430 -0
  217. package/src/new-project/template-updates.js +512 -0
  218. package/src/new-project/types.d.ts +83 -0
  219. package/src/new-project.js +6 -2188
  220. package/src/npm-safety.js +79 -0
  221. package/src/parser.d.ts +87 -0
  222. package/src/parser.js +118 -0
  223. package/src/path-helpers.d.ts +1 -0
  224. package/src/path-helpers.js +20 -0
  225. package/src/policy/review-boundaries.d.ts +15 -0
  226. package/src/project-config/index.js +564 -0
  227. package/src/project-config.js +19 -560
  228. package/src/reconcile/docs.d.ts +8 -0
  229. package/src/reconcile/journeys.d.ts +1 -0
  230. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  231. package/src/resolver/enrich/bug.js +2 -0
  232. package/src/resolver/enrich/pitch.js +2 -0
  233. package/src/resolver/enrich/requirement.js +2 -0
  234. package/src/resolver/enrich/task.js +2 -0
  235. package/src/resolver/index.js +19 -2089
  236. package/src/resolver/normalize.js +384 -1
  237. package/src/resolver/plans.js +168 -0
  238. package/src/resolver/projections-api.js +494 -0
  239. package/src/resolver/projections-db.js +133 -0
  240. package/src/resolver/projections-ui.js +317 -0
  241. package/src/resolver/shapes.js +251 -0
  242. package/src/resolver/shared.js +278 -0
  243. package/src/resolver/widgets.js +132 -0
  244. package/src/resolver.d.ts +1 -0
  245. package/src/runtime-support.js +29 -0
  246. package/src/sdlc/adopt.d.ts +1 -0
  247. package/src/sdlc/check.d.ts +1 -0
  248. package/src/sdlc/explain.d.ts +1 -0
  249. package/src/sdlc/release.d.ts +1 -0
  250. package/src/sdlc/scaffold.d.ts +1 -0
  251. package/src/sdlc/transition.d.ts +1 -0
  252. package/src/template-trust/constants.js +62 -0
  253. package/src/template-trust/content.js +258 -0
  254. package/src/template-trust/diff.js +92 -0
  255. package/src/template-trust/policy.js +61 -0
  256. package/src/template-trust/record.js +90 -0
  257. package/src/template-trust/status.js +182 -0
  258. package/src/template-trust.js +24 -687
  259. package/src/text-helpers.d.ts +7 -0
  260. package/src/text-helpers.js +245 -0
  261. package/src/topogram-config.js +306 -0
  262. package/src/topogram-types.d.ts +69 -0
  263. package/src/validator/common.js +488 -0
  264. package/src/validator/data-model.js +237 -0
  265. package/src/validator/docs.js +167 -0
  266. package/src/validator/expressions.js +146 -1
  267. package/src/validator/index.d.ts +23 -0
  268. package/src/validator/index.js +32 -3585
  269. package/src/validator/kinds.d.ts +41 -0
  270. package/src/validator/kinds.js +2 -0
  271. package/src/validator/model-helpers.js +46 -0
  272. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  273. package/src/validator/per-kind/bug.js +6 -0
  274. package/src/validator/per-kind/domain.js +15 -2
  275. package/src/validator/per-kind/pitch.js +7 -0
  276. package/src/validator/per-kind/requirement.js +5 -0
  277. package/src/validator/per-kind/task.js +7 -0
  278. package/src/validator/per-kind/widget.js +14 -0
  279. package/src/validator/projections/api-http-async.js +410 -0
  280. package/src/validator/projections/api-http-authz.js +88 -0
  281. package/src/validator/projections/api-http-core.js +205 -0
  282. package/src/validator/projections/api-http-policies.js +339 -0
  283. package/src/validator/projections/api-http-responses.js +233 -0
  284. package/src/validator/projections/api-http.js +44 -0
  285. package/src/validator/projections/db.js +353 -0
  286. package/src/validator/projections/generator-defaults.js +45 -0
  287. package/src/validator/projections/helpers.js +87 -0
  288. package/src/validator/projections/ui-helpers.js +214 -0
  289. package/src/validator/projections/ui-navigation.js +344 -0
  290. package/src/validator/projections/ui-structure.js +364 -0
  291. package/src/validator/projections/ui-widgets.js +493 -0
  292. package/src/validator/projections/ui.js +46 -0
  293. package/src/validator/registry.js +48 -1
  294. package/src/validator/utils.d.ts +20 -0
  295. package/src/validator/utils.js +115 -12
  296. package/src/validator.d.ts +2 -0
  297. package/src/widget-behavior.d.ts +1 -0
  298. package/src/workflows/adoption/index.js +26 -0
  299. package/src/workflows/docs-generate.js +262 -0
  300. package/src/workflows/docs-scan.js +703 -0
  301. package/src/workflows/docs.js +15 -0
  302. package/src/workflows/import-app/api/collect.js +221 -0
  303. package/src/workflows/import-app/api/openapi.js +257 -0
  304. package/src/workflows/import-app/api/routes.js +327 -0
  305. package/src/workflows/import-app/api/sources.js +22 -0
  306. package/src/workflows/import-app/api.js +4 -0
  307. package/src/workflows/import-app/db.js +538 -0
  308. package/src/workflows/import-app/index.js +30 -0
  309. package/src/workflows/import-app/shared.js +218 -0
  310. package/src/workflows/import-app/ui.js +443 -0
  311. package/src/workflows/import-app/workflow.js +159 -0
  312. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  313. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  314. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  315. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  316. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  317. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  318. package/src/workflows/reconcile/adoption-plan.js +32 -0
  319. package/src/workflows/reconcile/auth/closures.js +115 -0
  320. package/src/workflows/reconcile/auth/formatters.js +142 -0
  321. package/src/workflows/reconcile/auth/inference.js +330 -0
  322. package/src/workflows/reconcile/auth/roles.js +122 -0
  323. package/src/workflows/reconcile/auth.js +37 -0
  324. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  325. package/src/workflows/reconcile/bundle-core.js +14 -0
  326. package/src/workflows/reconcile/bundle-shared.js +75 -0
  327. package/src/workflows/reconcile/candidate-model.js +477 -0
  328. package/src/workflows/reconcile/canonical-surface.js +264 -0
  329. package/src/workflows/reconcile/gap-report.js +333 -0
  330. package/src/workflows/reconcile/ids.js +6 -0
  331. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  332. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  333. package/src/workflows/reconcile/impacts/patches.js +252 -0
  334. package/src/workflows/reconcile/impacts/reports.js +80 -0
  335. package/src/workflows/reconcile/impacts.js +16 -0
  336. package/src/workflows/reconcile/index.js +7 -0
  337. package/src/workflows/reconcile/renderers.js +461 -0
  338. package/src/workflows/reconcile/summary.js +90 -0
  339. package/src/workflows/reconcile/workflow.js +309 -0
  340. package/src/workflows/shared.js +189 -0
  341. package/src/workflows/types.d.ts +93 -0
  342. package/src/workflows.d.ts +1 -0
  343. package/src/workflows.js +10 -7652
  344. package/src/workspace-docs.d.ts +29 -0
@@ -0,0 +1,538 @@
1
+ // @ts-check
2
+ import path from "node:path";
3
+
4
+ import { relativeTo } from "../../path-helpers.js";
5
+ import { idHintify, slugify, titleCase } from "../../text-helpers.js";
6
+ import { readJsonIfExists, readTextIfExists } from "../shared.js";
7
+ import { dedupeCandidateRecords, findImportFiles, makeCandidateRecord, selectPreferredImportFiles } from "./shared.js";
8
+
9
+ /** @param {any} typeName @returns {any} */
10
+ function normalizePrismaType(typeName) {
11
+ const normalized = String(typeName || "").toLowerCase();
12
+ switch (normalized) {
13
+ case "string":
14
+ return "string";
15
+ case "int":
16
+ return "int";
17
+ case "bigint":
18
+ return "bigint";
19
+ case "float":
20
+ return "float";
21
+ case "decimal":
22
+ return "decimal";
23
+ case "boolean":
24
+ case "bool":
25
+ return "boolean";
26
+ case "datetime":
27
+ return "datetime";
28
+ case "bytes":
29
+ return "bytes";
30
+ case "json":
31
+ return "json";
32
+ default:
33
+ return typeName;
34
+ }
35
+ }
36
+
37
+ /** @param {any} schemaText @returns {any} */
38
+ function parsePrismaSchema(schemaText) {
39
+ /** @type {any[]} */
40
+ const enums = [];
41
+ /** @type {any[]} */
42
+ const entities = [];
43
+ /** @type {any[]} */
44
+ const relations = [];
45
+ /** @type {any[]} */
46
+ const indexes = [];
47
+ const enumNames = new Set();
48
+ /** @type {any[]} */
49
+ const modelNames = [];
50
+
51
+ for (const match of schemaText.matchAll(/^enum\s+([A-Za-z0-9_]+)\s*\{([\s\S]*?)^\}/gm)) {
52
+ const [, enumName, body] = match;
53
+ const values = body
54
+ .split(/\r?\n/)
55
+ .map((/** @type {any} */ line) => line.replace(/\/\/.*$/, "").trim())
56
+ .filter((/** @type {any} */ line) => line && !line.startsWith("@@"))
57
+ .map((/** @type {any} */ line) => line.split(/\s+/)[0]);
58
+ enumNames.add(enumName);
59
+ enums.push({ name: enumName, values });
60
+ }
61
+
62
+ for (const match of schemaText.matchAll(/^model\s+([A-Za-z0-9_]+)\s*\{/gm)) {
63
+ modelNames.push(match[1]);
64
+ }
65
+ const modelNameSet = new Set(modelNames);
66
+
67
+ for (const match of schemaText.matchAll(/^model\s+([A-Za-z0-9_]+)\s*\{([\s\S]*?)^\}/gm)) {
68
+ const [, modelName, body] = match;
69
+ /** @type {any[]} */
70
+ const fields = [];
71
+ /** @type {any[]} */
72
+ const localIndexes = [];
73
+ const lines = body
74
+ .split(/\r?\n/)
75
+ .map((/** @type {any} */ line) => line.replace(/\/\/.*$/, "").trim())
76
+ .filter(Boolean);
77
+
78
+ for (const line of lines) {
79
+ if (line.startsWith("@@")) {
80
+ const indexMatch = line.match(/^@@(unique|index)\(\[([^\]]+)\]/);
81
+ if (indexMatch) {
82
+ const [, type, rawFields] = indexMatch;
83
+ localIndexes.push({
84
+ id_hint: `index_${slugify(`${modelName}_${rawFields}`)}`,
85
+ fields: rawFields.split(",").map((/** @type {any} */ field) => field.trim()),
86
+ unique: type === "unique"
87
+ });
88
+ }
89
+ continue;
90
+ }
91
+
92
+ const fieldMatch = line.match(/^([A-Za-z0-9_]+)\s+([^\s]+)(.*)$/);
93
+ if (!fieldMatch) {
94
+ continue;
95
+ }
96
+ const [, fieldName, rawTypeToken, remainder] = fieldMatch;
97
+ const list = rawTypeToken.endsWith("[]");
98
+ const optional = rawTypeToken.endsWith("?");
99
+ const baseType = rawTypeToken.replace(/\?|\[\]/g, "");
100
+ const referencesModel = modelNameSet.has(baseType) && !enumNames.has(baseType);
101
+ const hasRelationDirective = remainder.includes("@relation(");
102
+
103
+ if (referencesModel && hasRelationDirective) {
104
+ const relationMatch = remainder.match(/@relation\(([^)]*)\)/);
105
+ const relationArgs = relationMatch?.[1] || "";
106
+ const fieldsMatch = relationArgs.match(/fields:\s*\[([^\]]+)\]/);
107
+ const refsMatch = relationArgs.match(/references:\s*\[([^\]]+)\]/);
108
+ relations.push({
109
+ from_entity: `entity_${slugify(modelName)}`,
110
+ to_entity: `entity_${slugify(baseType)}`,
111
+ relation_field: fieldName,
112
+ fields: fieldsMatch ? fieldsMatch[1].split(",").map((/** @type {any} */ field) => field.trim()) : [],
113
+ references: refsMatch ? refsMatch[1].split(",").map((/** @type {any} */ field) => field.trim()) : []
114
+ });
115
+ continue;
116
+ }
117
+
118
+ if (referencesModel) {
119
+ continue;
120
+ }
121
+
122
+ const fieldType = enumNames.has(baseType) ? baseType : normalizePrismaType(baseType);
123
+ fields.push({
124
+ name: fieldName,
125
+ field_type: fieldType,
126
+ required: !optional && !list,
127
+ list,
128
+ unique: /@unique\b/.test(remainder),
129
+ primary_key: /@id\b/.test(remainder)
130
+ });
131
+
132
+ if (/@unique\b/.test(remainder)) {
133
+ localIndexes.push({
134
+ id_hint: `index_${slugify(`${modelName}_${fieldName}_unique`)}`,
135
+ fields: [fieldName],
136
+ unique: true
137
+ });
138
+ }
139
+ }
140
+
141
+ entities.push({ name: modelName, fields });
142
+ indexes.push(...localIndexes.map((/** @type {any} */ index) => ({ ...index, entity: `entity_${slugify(modelName)}` })));
143
+ }
144
+
145
+ return { entities, enums, relations, indexes };
146
+ }
147
+
148
+ /** @param {string} body @returns {any} */
149
+ function splitSqlSegments(body) {
150
+ return body
151
+ .split(/,\s*\n/)
152
+ .map((/** @type {any} */ segment) => segment.trim())
153
+ .filter(Boolean);
154
+ }
155
+
156
+ /** @param {any} sqlText @returns {any} */
157
+ function parseSqlSchema(sqlText) {
158
+ /** @type {any[]} */
159
+ const entities = [];
160
+ /** @type {any[]} */
161
+ const enums = [];
162
+ /** @type {any[]} */
163
+ const relations = [];
164
+ /** @type {any[]} */
165
+ const indexes = [];
166
+
167
+ for (const match of sqlText.matchAll(/CREATE\s+TYPE\s+([A-Za-z0-9_"]+)\s+AS\s+ENUM\s*\(([\s\S]*?)\);/gi)) {
168
+ const enumName = match[1].replace(/"/g, "");
169
+ const values = [...match[2].matchAll(/'([^']+)'/g)].map((/** @type {any} */ valueMatch) => valueMatch[1]);
170
+ enums.push({ name: enumName, values });
171
+ }
172
+
173
+ for (const match of sqlText.matchAll(/CREATE\s+TABLE\s+([A-Za-z0-9_"]+)\s*\(([\s\S]*?)\);/gi)) {
174
+ const tableName = match[1].replace(/"/g, "");
175
+ const entityId = `entity_${slugify(tableName.replace(/s$/, ""))}`;
176
+ /** @type {any[]} */
177
+ const fields = [];
178
+ for (const segment of splitSqlSegments(match[2])) {
179
+ if (/^(PRIMARY\s+KEY|UNIQUE|CONSTRAINT|FOREIGN\s+KEY)/i.test(segment)) {
180
+ const foreignKeyMatch = segment.match(/FOREIGN\s+KEY\s*\(([^)]+)\)\s+REFERENCES\s+([A-Za-z0-9_"]+)\s*\(([^)]+)\)/i);
181
+ if (foreignKeyMatch) {
182
+ relations.push({
183
+ from_entity: entityId,
184
+ to_entity: `entity_${slugify(foreignKeyMatch[2].replace(/"/g, "").replace(/s$/, ""))}`,
185
+ relation_field: foreignKeyMatch[1].replace(/"/g, "").trim(),
186
+ fields: foreignKeyMatch[1].split(",").map((/** @type {any} */ field) => field.replace(/"/g, "").trim()),
187
+ references: foreignKeyMatch[3].split(",").map((/** @type {any} */ field) => field.replace(/"/g, "").trim())
188
+ });
189
+ }
190
+ const uniqueMatch = segment.match(/UNIQUE\s*\(([^)]+)\)/i);
191
+ if (uniqueMatch) {
192
+ indexes.push({
193
+ entity: entityId,
194
+ id_hint: `index_${slugify(`${tableName}_${uniqueMatch[1]}`)}`,
195
+ fields: uniqueMatch[1].split(",").map((/** @type {any} */ field) => field.replace(/"/g, "").trim()),
196
+ unique: true
197
+ });
198
+ }
199
+ continue;
200
+ }
201
+ const fieldMatch = segment.match(/^"?([A-Za-z0-9_]+)"?\s+([A-Za-z0-9_()[\]]+)(.*)$/i);
202
+ if (!fieldMatch) {
203
+ continue;
204
+ }
205
+ const [, fieldName, rawType, remainder] = fieldMatch;
206
+ fields.push({
207
+ name: fieldName,
208
+ field_type: normalizePrismaType(rawType.replace(/\(.+$/, "")),
209
+ required: /NOT\s+NULL/i.test(remainder),
210
+ list: false,
211
+ unique: /\bUNIQUE\b/i.test(remainder),
212
+ primary_key: /\bPRIMARY\s+KEY\b/i.test(remainder)
213
+ });
214
+ const inlineReferenceMatch = remainder.match(/REFERENCES\s+([A-Za-z0-9_"]+)\s*\(([^)]+)\)/i);
215
+ if (inlineReferenceMatch) {
216
+ relations.push({
217
+ from_entity: entityId,
218
+ to_entity: `entity_${slugify(inlineReferenceMatch[1].replace(/"/g, "").replace(/s$/, ""))}`,
219
+ relation_field: fieldName,
220
+ fields: [fieldName],
221
+ references: inlineReferenceMatch[2].split(",").map((/** @type {any} */ field) => field.replace(/"/g, "").trim())
222
+ });
223
+ }
224
+ if (/\bUNIQUE\b/i.test(remainder)) {
225
+ indexes.push({
226
+ entity: entityId,
227
+ id_hint: `index_${slugify(`${tableName}_${fieldName}_unique`)}`,
228
+ fields: [fieldName],
229
+ unique: true
230
+ });
231
+ }
232
+ }
233
+ entities.push({ name: tableName.replace(/s$/, ""), table_name: tableName, fields });
234
+ }
235
+
236
+ for (const match of sqlText.matchAll(/CREATE\s+(UNIQUE\s+)?INDEX\s+([A-Za-z0-9_"]+)\s+ON\s+([A-Za-z0-9_"]+)\s*\(([^)]+)\)/gi)) {
237
+ indexes.push({
238
+ entity: `entity_${slugify(match[3].replace(/"/g, "").replace(/s$/, ""))}`,
239
+ id_hint: `index_${slugify(match[2].replace(/"/g, ""))}`,
240
+ fields: match[4].split(",").map((/** @type {any} */ field) => field.replace(/"/g, "").trim()),
241
+ unique: Boolean(match[1])
242
+ });
243
+ }
244
+
245
+ return { entities, enums, relations, indexes };
246
+ }
247
+
248
+ /** @param {any} snapshot @returns {any} */
249
+ function parseDbSchemaSnapshot(snapshot) {
250
+ return {
251
+ entities: (snapshot.tables || []).map((/** @type {any} */ table) => ({
252
+ name: table.entity?.name || table.table.replace(/s$/, ""),
253
+ table_name: table.table,
254
+ fields: (table.columns || []).map((/** @type {any} */ column) => ({
255
+ name: column.name,
256
+ field_type: column.type,
257
+ required: !column.nullable,
258
+ list: false,
259
+ unique: false,
260
+ primary_key: false
261
+ }))
262
+ })),
263
+ enums: (snapshot.enums || []).map((/** @type {any} */ entry) => ({
264
+ name: entry.name || entry.id,
265
+ values: entry.values || []
266
+ })),
267
+ relations: (snapshot.tables || []).flatMap((/** @type {any} */ table) =>
268
+ (table.foreignKeys || []).map((/** @type {any} */ foreignKey) => ({
269
+ from_entity: table.entity?.id || `entity_${slugify(table.table.replace(/s$/, ""))}`,
270
+ to_entity: foreignKey.references?.id || foreignKey.reference?.id || `entity_${slugify((foreignKey.references?.table || "").replace(/s$/, ""))}`,
271
+ relation_field: foreignKey.columns?.[0] || "",
272
+ fields: foreignKey.columns || [],
273
+ references: foreignKey.references?.columns || []
274
+ }))
275
+ ),
276
+ indexes: (snapshot.tables || []).flatMap((/** @type {any} */ table) =>
277
+ (table.indexes || []).map((/** @type {any} */ index) => ({
278
+ entity: table.entity?.id || `entity_${slugify(table.table.replace(/s$/, ""))}`,
279
+ id_hint: `index_${slugify(index.name || `${table.table}_${(index.columns || []).join("_")}`)}`,
280
+ fields: index.columns || [],
281
+ unique: Boolean(index.unique)
282
+ }))
283
+ )
284
+ };
285
+ }
286
+
287
+ /** @param {WorkspacePaths} paths @returns {any} */
288
+ export function discoverDbSources(paths) {
289
+ const allPrismaFiles = findImportFiles(paths, (/** @type {any} */ filePath) => filePath.endsWith(path.join("prisma", "schema.prisma")) || filePath.endsWith("/prisma/schema.prisma"));
290
+ const allSqlFiles = findImportFiles(paths, (/** @type {any} */ filePath) => filePath.endsWith(".sql") && /(schema|migration|migrations|db)/i.test(filePath));
291
+ const snapshotFiles = findImportFiles(paths, (/** @type {any} */ filePath) => filePath.endsWith(".db-schema-snapshot.json"));
292
+ const prismaFiles = selectPreferredImportFiles(paths, allPrismaFiles, "prisma");
293
+ const schemaSqlFiles = allSqlFiles.filter((/** @type {any} */ filePath) => !/migration/i.test(path.basename(filePath)));
294
+ const migrationSqlFiles = allSqlFiles.filter((/** @type {any} */ filePath) => /migration/i.test(path.basename(filePath)));
295
+ const sqlFiles =
296
+ prismaFiles.length > 0
297
+ ? []
298
+ : schemaSqlFiles.length > 0
299
+ ? selectPreferredImportFiles(paths, schemaSqlFiles, "sql")
300
+ : selectPreferredImportFiles(paths, migrationSqlFiles, "sql");
301
+ return { prismaFiles, sqlFiles, snapshotFiles };
302
+ }
303
+
304
+ /** @param {WorkspacePaths} paths @returns {any} */
305
+ export function collectDbImport(paths) {
306
+ /** @type {any[]} */
307
+ const findings = [];
308
+ /** @type {WorkflowRecord} */
309
+ const candidates = {
310
+ entities: [],
311
+ enums: [],
312
+ relations: [],
313
+ indexes: []
314
+ };
315
+ const { prismaFiles, sqlFiles, snapshotFiles } = discoverDbSources(paths);
316
+ let hasPrimarySchemaSource = false;
317
+
318
+ for (const filePath of prismaFiles) {
319
+ const parsed = parsePrismaSchema(readTextIfExists(filePath) || "");
320
+ const provenance = relativeTo(paths.repoRoot, filePath);
321
+ hasPrimarySchemaSource = true;
322
+ findings.push({
323
+ kind: "prisma_schema",
324
+ file: provenance,
325
+ entity_count: parsed.entities.length,
326
+ enum_count: parsed.enums.length
327
+ });
328
+ candidates.entities.push(
329
+ ...parsed.entities.map((/** @type {any} */ entity) =>
330
+ makeCandidateRecord({
331
+ kind: "entity",
332
+ idHint: `entity_${slugify(entity.name)}`,
333
+ label: titleCase(entity.name),
334
+ confidence: "high",
335
+ sourceKind: "schema",
336
+ provenance,
337
+ table_name: slugify(entity.table_name || entity.name),
338
+ fields: entity.fields
339
+ })
340
+ )
341
+ );
342
+ candidates.enums.push(
343
+ ...parsed.enums.map((/** @type {any} */ entry) =>
344
+ makeCandidateRecord({
345
+ kind: "enum",
346
+ idHint: idHintify(entry.name),
347
+ label: titleCase(entry.name),
348
+ confidence: "high",
349
+ sourceKind: "schema",
350
+ provenance,
351
+ values: entry.values
352
+ })
353
+ )
354
+ );
355
+ candidates.relations.push(
356
+ ...parsed.relations.map((/** @type {any} */ relation) =>
357
+ makeCandidateRecord({
358
+ kind: "relation",
359
+ idHint: slugify(`${relation.from_entity}_${relation.relation_field}_${relation.to_entity}`),
360
+ label: `${relation.from_entity} -> ${relation.to_entity}`,
361
+ confidence: "high",
362
+ sourceKind: "schema",
363
+ provenance,
364
+ ...relation
365
+ })
366
+ )
367
+ );
368
+ candidates.indexes.push(
369
+ ...parsed.indexes.map((/** @type {any} */ index) =>
370
+ makeCandidateRecord({
371
+ kind: "index",
372
+ idHint: index.id_hint,
373
+ label: titleCase(index.id_hint.replace(/^index_/, "")),
374
+ confidence: "medium",
375
+ sourceKind: "schema",
376
+ provenance,
377
+ entity: index.entity,
378
+ fields: index.fields,
379
+ unique: index.unique
380
+ })
381
+ )
382
+ );
383
+ }
384
+
385
+ for (const filePath of sqlFiles) {
386
+ const parsed = parseSqlSchema(readTextIfExists(filePath) || "");
387
+ const provenance = relativeTo(paths.repoRoot, filePath);
388
+ hasPrimarySchemaSource = true;
389
+ findings.push({
390
+ kind: "sql_schema",
391
+ file: provenance,
392
+ entity_count: parsed.entities.length,
393
+ enum_count: parsed.enums.length
394
+ });
395
+ candidates.entities.push(
396
+ ...parsed.entities.map((/** @type {any} */ entity) =>
397
+ makeCandidateRecord({
398
+ kind: "entity",
399
+ idHint: `entity_${slugify(entity.name)}`,
400
+ label: titleCase(entity.name),
401
+ confidence: /migration/i.test(filePath) ? "medium" : "high",
402
+ sourceKind: /migration/i.test(filePath) ? "migration" : "schema",
403
+ provenance,
404
+ table_name: entity.table_name || slugify(entity.name),
405
+ fields: entity.fields
406
+ })
407
+ )
408
+ );
409
+ candidates.enums.push(
410
+ ...parsed.enums.map((/** @type {any} */ entry) =>
411
+ makeCandidateRecord({
412
+ kind: "enum",
413
+ idHint: idHintify(entry.name),
414
+ label: titleCase(entry.name),
415
+ confidence: /migration/i.test(filePath) ? "medium" : "high",
416
+ sourceKind: /migration/i.test(filePath) ? "migration" : "schema",
417
+ provenance,
418
+ values: entry.values
419
+ })
420
+ )
421
+ );
422
+ candidates.relations.push(
423
+ ...parsed.relations.map((/** @type {any} */ relation) =>
424
+ makeCandidateRecord({
425
+ kind: "relation",
426
+ idHint: slugify(`${relation.from_entity}_${relation.relation_field}_${relation.to_entity}`),
427
+ label: `${relation.from_entity} -> ${relation.to_entity}`,
428
+ confidence: "medium",
429
+ sourceKind: /migration/i.test(filePath) ? "migration" : "schema",
430
+ provenance,
431
+ ...relation
432
+ })
433
+ )
434
+ );
435
+ candidates.indexes.push(
436
+ ...parsed.indexes.map((/** @type {any} */ index) =>
437
+ makeCandidateRecord({
438
+ kind: "index",
439
+ idHint: index.id_hint,
440
+ label: titleCase(index.id_hint.replace(/^index_/, "")),
441
+ confidence: "medium",
442
+ sourceKind: /migration/i.test(filePath) ? "migration" : "schema",
443
+ provenance,
444
+ entity: index.entity,
445
+ fields: index.fields,
446
+ unique: index.unique
447
+ })
448
+ )
449
+ );
450
+ }
451
+
452
+ if (!hasPrimarySchemaSource) {
453
+ for (const filePath of snapshotFiles) {
454
+ const snapshot = readJsonIfExists(filePath);
455
+ if (!snapshot) {
456
+ continue;
457
+ }
458
+ const parsed = parseDbSchemaSnapshot(snapshot);
459
+ const provenance = relativeTo(paths.repoRoot, filePath);
460
+ findings.push({
461
+ kind: "db_schema_snapshot",
462
+ file: provenance,
463
+ entity_count: parsed.entities.length,
464
+ enum_count: parsed.enums.length
465
+ });
466
+ candidates.entities.push(
467
+ ...parsed.entities.map((/** @type {any} */ entity) =>
468
+ makeCandidateRecord({
469
+ kind: "entity",
470
+ idHint: `entity_${slugify(entity.name)}`,
471
+ label: titleCase(entity.name),
472
+ confidence: "medium",
473
+ sourceKind: "generated_artifact",
474
+ provenance,
475
+ table_name: entity.table_name || slugify(entity.name),
476
+ fields: entity.fields
477
+ })
478
+ )
479
+ );
480
+ candidates.enums.push(
481
+ ...parsed.enums.map((/** @type {any} */ entry) =>
482
+ makeCandidateRecord({
483
+ kind: "enum",
484
+ idHint: idHintify(entry.name),
485
+ label: titleCase(entry.name),
486
+ confidence: "medium",
487
+ sourceKind: "generated_artifact",
488
+ provenance,
489
+ values: entry.values
490
+ })
491
+ )
492
+ );
493
+ candidates.relations.push(
494
+ ...parsed.relations.map((/** @type {any} */ relation) =>
495
+ makeCandidateRecord({
496
+ kind: "relation",
497
+ idHint: slugify(`${relation.from_entity}_${relation.relation_field}_${relation.to_entity}`),
498
+ label: `${relation.from_entity} -> ${relation.to_entity}`,
499
+ confidence: "medium",
500
+ sourceKind: "generated_artifact",
501
+ provenance,
502
+ ...relation
503
+ })
504
+ )
505
+ );
506
+ candidates.indexes.push(
507
+ ...parsed.indexes.map((/** @type {any} */ index) =>
508
+ makeCandidateRecord({
509
+ kind: "index",
510
+ idHint: index.id_hint,
511
+ label: titleCase(index.id_hint.replace(/^index_/, "")),
512
+ confidence: "medium",
513
+ sourceKind: "generated_artifact",
514
+ provenance,
515
+ entity: index.entity,
516
+ fields: index.fields,
517
+ unique: index.unique
518
+ })
519
+ )
520
+ );
521
+ }
522
+ } else {
523
+ for (const filePath of snapshotFiles) {
524
+ findings.push({
525
+ kind: "db_schema_snapshot",
526
+ file: relativeTo(paths.repoRoot, filePath),
527
+ used_as_primary: false
528
+ });
529
+ }
530
+ }
531
+
532
+ candidates.entities = dedupeCandidateRecords(candidates.entities, (/** @type {any} */ record) => record.id_hint);
533
+ candidates.enums = dedupeCandidateRecords(candidates.enums, (/** @type {any} */ record) => record.id_hint);
534
+ candidates.relations = dedupeCandidateRecords(candidates.relations, (/** @type {any} */ record) => record.id_hint);
535
+ candidates.indexes = dedupeCandidateRecords(candidates.indexes, (/** @type {any} */ record) => record.id_hint);
536
+
537
+ return { findings, candidates };
538
+ }
@@ -0,0 +1,30 @@
1
+ // @ts-check
2
+ import { runImportAppWorkflow } from "../../import/index.js";
3
+ import { scanDocsWorkflow } from "../docs.js";
4
+
5
+ export { collectApiImport, discoverApiSources } from "./api.js";
6
+ export { collectDbImport, discoverDbSources } from "./db.js";
7
+ export {
8
+ IMPORT_TRACKS,
9
+ SCALAR_FIELD_TYPES,
10
+ dedupeCandidateRecords,
11
+ findImportFiles,
12
+ importSearchRoots,
13
+ inferCapabilityEntityId,
14
+ makeCandidateRecord,
15
+ normalizeEndpointPathForMatch,
16
+ normalizeImportRelativePath,
17
+ normalizeOpenApiPath,
18
+ parseImportTracks,
19
+ selectPreferredImportFiles
20
+ } from "./shared.js";
21
+ export { collectUiImport } from "./ui.js";
22
+ export { collectWorkflowImport } from "./workflow.js";
23
+
24
+ /** @param {string} inputPath @param {WorkflowOptions} options @returns {any} */
25
+ export function importAppWorkflow(inputPath, options = {}) {
26
+ return runImportAppWorkflow(inputPath, {
27
+ ...options,
28
+ scanDocsSummary: () => scanDocsWorkflow(inputPath).summary
29
+ });
30
+ }