@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
@@ -1,699 +1,4 @@
1
- import { IMPORT_TRACKS } from "./contracts.js";
2
- import { dedupeCandidateRecords, ensureTrailingNewline, idHintify, makeCandidateRecord } from "./shared.js";
3
- import { createImportContext } from "./context.js";
4
- import { getEnrichersForTrack, getExtractorsForTrack } from "./registry.js";
5
- import {
6
- collectionPatternFromPresentations
7
- } from "../../ui/taxonomy.js";
1
+ // @ts-check
8
2
 
9
- function parseImportTracks(fromValue) {
10
- if (!fromValue) {
11
- return ["db", "api", "ui", "workflows", "verification"];
12
- }
13
- const tracks = String(fromValue).split(",").map((track) => track.trim().toLowerCase()).filter(Boolean);
14
- if (tracks.length === 0) {
15
- throw new Error("Expected --from to include at least one import track");
16
- }
17
- const invalid = tracks.filter((track) => !IMPORT_TRACKS.has(track));
18
- if (invalid.length > 0) {
19
- throw new Error(`Unsupported import track(s): ${invalid.join(", ")}`);
20
- }
21
- return [...new Set(tracks)];
22
- }
23
-
24
- function sortExtractors(context, extractors) {
25
- return extractors
26
- .map((extractor) => ({ extractor, detection: extractor.detect(context) || { score: 0, reasons: [] } }))
27
- .filter((entry) => entry.detection.score > 0)
28
- .sort((a, b) => b.detection.score - a.detection.score || a.extractor.id.localeCompare(b.extractor.id));
29
- }
30
-
31
- function selectDetectionsForTrack(track, detections) {
32
- if (track === "db") {
33
- const prisma = detections.find((entry) => entry.extractor.id === "db.prisma");
34
- if (prisma) return [prisma];
35
- const djangoModels = detections.find((entry) => entry.extractor.id === "db.django-models");
36
- if (djangoModels) return [djangoModels];
37
- const efCore = detections.find((entry) => entry.extractor.id === "db.ef-core");
38
- if (efCore) return [efCore];
39
- const room = detections.find((entry) => entry.extractor.id === "db.room");
40
- if (room) return [room];
41
- const swiftData = detections.find((entry) => entry.extractor.id === "db.swiftdata");
42
- if (swiftData) return [swiftData];
43
- const dotnetModels = detections.find((entry) => entry.extractor.id === "db.dotnet-models");
44
- if (dotnetModels) return [dotnetModels];
45
- const flutterEntities = detections.find((entry) => entry.extractor.id === "db.flutter-entities");
46
- if (flutterEntities) return [flutterEntities];
47
- const reactNativeEntities = detections.find((entry) => entry.extractor.id === "db.react-native-entities");
48
- if (reactNativeEntities) return [reactNativeEntities];
49
- const railsSchema = detections.find((entry) => entry.extractor.id === "db.rails-schema");
50
- if (railsSchema) return [railsSchema];
51
- const liquibase = detections.find((entry) => entry.extractor.id === "db.liquibase");
52
- if (liquibase) return [liquibase];
53
- const myBatisXml = detections.find((entry) => entry.extractor.id === "db.mybatis-xml");
54
- if (myBatisXml) return [myBatisXml];
55
- const jpa = detections.find((entry) => entry.extractor.id === "db.jpa");
56
- if (jpa) return [jpa];
57
- const drizzle = detections.find((entry) => entry.extractor.id === "db.drizzle");
58
- if (drizzle) return [drizzle];
59
- const sql = detections.find((entry) => entry.extractor.id === "db.sql");
60
- if (sql) return [sql];
61
- const snapshot = detections.find((entry) => entry.extractor.id === "db.snapshot");
62
- return snapshot ? [snapshot] : [];
63
- }
64
- if (track === "api") {
65
- const openApi = detections.find((entry) => entry.extractor.id === "api.openapi");
66
- if (openApi) return [openApi];
67
- const openApiCode = detections.find((entry) => entry.extractor.id === "api.openapi-code");
68
- if (openApiCode) return [openApiCode];
69
- const graphQlSdl = detections.find((entry) => entry.extractor.id === "api.graphql-sdl");
70
- if (graphQlSdl) return [graphQlSdl];
71
- const trpc = detections.find((entry) => entry.extractor.id === "api.trpc");
72
- if (trpc) return [trpc];
73
- const aspNetCore = detections.find((entry) => entry.extractor.id === "api.aspnet-core");
74
- if (aspNetCore) return [aspNetCore];
75
- const retrofit = detections.find((entry) => entry.extractor.id === "api.retrofit");
76
- if (retrofit) return [retrofit];
77
- const swiftWebApi = detections.find((entry) => entry.extractor.id === "api.swift-webapi");
78
- if (swiftWebApi) return [swiftWebApi];
79
- const flutterDio = detections.find((entry) => entry.extractor.id === "api.flutter-dio");
80
- if (flutterDio) return [flutterDio];
81
- const reactNativeRepository = detections.find((entry) => entry.extractor.id === "api.react-native-repository");
82
- if (reactNativeRepository) return [reactNativeRepository];
83
- const fastify = detections.find((entry) => entry.extractor.id === "api.fastify");
84
- if (fastify) return [fastify];
85
- const express = detections.find((entry) => entry.extractor.id === "api.express");
86
- if (express) return [express];
87
- const djangoRoutes = detections.find((entry) => entry.extractor.id === "api.django-routes");
88
- if (djangoRoutes) return [djangoRoutes];
89
- const railsRoutes = detections.find((entry) => entry.extractor.id === "api.rails-routes");
90
- if (railsRoutes) return [railsRoutes];
91
- const micronaut = detections.find((entry) => entry.extractor.id === "api.micronaut");
92
- if (micronaut) return [micronaut];
93
- const jaxrs = detections.find((entry) => entry.extractor.id === "api.jaxrs");
94
- if (jaxrs) return [jaxrs];
95
- const springWeb = detections.find((entry) => entry.extractor.id === "api.spring-web");
96
- if (springWeb) return [springWeb];
97
- }
98
- return detections;
99
- }
100
-
101
- function importedApiCapabilityIds(allCandidates) {
102
- return [...(allCandidates?.api?.capabilities || [])]
103
- .map((capability) => capability.id_hint)
104
- .filter(Boolean)
105
- .sort();
106
- }
107
-
108
- function loadCapabilityForScreen(screen) {
109
- return capabilityHintsForScreen(screen).find((hint) => /^cap_(list|get)_/.test(hint)) || null;
110
- }
111
-
112
- function normalizeCapabilityHint(value) {
113
- if (typeof value === "string") {
114
- return value;
115
- }
116
- if (value && typeof value === "object") {
117
- return value.id_hint || value.id || value.capability_hint || value.capability?.id || null;
118
- }
119
- return null;
120
- }
121
-
122
- function capabilityHintsForScreen(screen) {
123
- const rawHints = Array.isArray(screen.capability_hints)
124
- ? screen.capability_hints
125
- : [screen.capability_hints].filter(Boolean);
126
- return rawHints.map(normalizeCapabilityHint).filter(Boolean);
127
- }
128
-
129
- function uiWidgetCandidates(candidates) {
130
- return [
131
- ...(Array.isArray(candidates?.widgets) ? candidates.widgets : []),
132
- ...(Array.isArray(candidates?.components) ? candidates.components : [])
133
- ];
134
- }
135
-
136
- function inferredDataSourceForWidget(widget, allCandidates) {
137
- if (widget.data_source) {
138
- return widget.data_source;
139
- }
140
- const capabilityIds = importedApiCapabilityIds(allCandidates);
141
- const screenStem = String(widget.screen_id || "")
142
- .replace(/_(list|index|table|grid|results)$/, "")
143
- .replace(/^list_/, "");
144
- return capabilityIds.find((id) => /^cap_(list|get)_/.test(id) && id.includes(screenStem)) ||
145
- capabilityIds.find((id) => /^cap_(list|get)_/.test(id)) ||
146
- null;
147
- }
148
-
149
- function deriveUiWidgetCandidates(candidates) {
150
- const screens = candidates.screens || [];
151
- const actions = candidates.actions || [];
152
- const presentations = [...new Set(actions
153
- .filter((entry) => entry.kind === "ui_presentation")
154
- .map((entry) => entry.presentation)
155
- .filter(Boolean))].sort();
156
- const widgetScreens = screens.filter((screen) => ["list", "dashboard", "analytics", "report", "feed", "inbox"].includes(screen.screen_kind));
157
-
158
- return widgetScreens.map((screen) => {
159
- const pattern = collectionPatternFromPresentations(presentations);
160
- const widgetStem = idHintify(`${screen.id_hint}_results`);
161
- const loadCapability = loadCapabilityForScreen(screen);
162
- return makeCandidateRecord({
163
- kind: "widget",
164
- idHint: `widget_${widgetStem}`,
165
- label: `${screen.label || screen.id_hint} results`,
166
- confidence: presentations.length > 0 ? "medium" : "low",
167
- sourceKind: "ui_projection_inference",
168
- sourceOfTruth: "candidate",
169
- provenance: screen.provenance || [],
170
- screen_id: screen.id_hint,
171
- region: "results",
172
- pattern,
173
- data_prop: "rows",
174
- data_source: loadCapability,
175
- inferred_props: [{ name: "rows", type: "array", required: true, source: loadCapability }],
176
- inferred_events: [],
177
- inferred_region: "results",
178
- inferred_pattern: pattern,
179
- evidence: screen.provenance || [],
180
- missing_decisions: [
181
- "confirm widget reuse boundary",
182
- "confirm prop names and data source",
183
- "confirm events and behavior",
184
- "confirm supported regions and patterns"
185
- ],
186
- notes: [
187
- "Imported widget candidates are review-only.",
188
- "Confirm props, behavior, events, and reuse before adoption."
189
- ]
190
- });
191
- });
192
- }
193
-
194
- function normalizeCandidatesForTrack(track, candidates) {
195
- if (track === "db") {
196
- return {
197
- entities: dedupeCandidateRecords(candidates.entities || [], (record) => record.id_hint),
198
- enums: dedupeCandidateRecords(candidates.enums || [], (record) => record.id_hint),
199
- relations: dedupeCandidateRecords(candidates.relations || [], (record) => record.id_hint),
200
- indexes: dedupeCandidateRecords(candidates.indexes || [], (record) => record.id_hint)
201
- };
202
- }
203
- if (track === "api") {
204
- return {
205
- capabilities: dedupeCandidateRecords(candidates.capabilities || [], (record) => record.id_hint),
206
- routes: dedupeCandidateRecords(
207
- (candidates.routes || []).map((route) => ({ ...route, id_hint: route.id_hint || `${route.method}_${route.path}` })),
208
- (record) => `${record.method}:${record.path}:${record.source_kind}`
209
- ).map(({ id_hint, ...route }) => route),
210
- stacks: [...new Set(candidates.stacks || [])].sort()
211
- };
212
- }
213
- if (track === "ui") {
214
- const explicitWidgets = uiWidgetCandidates(candidates);
215
- const derivedWidgets = deriveUiWidgetCandidates(candidates);
216
- return {
217
- screens: dedupeCandidateRecords(candidates.screens || [], (record) => record.id_hint),
218
- routes: dedupeCandidateRecords(candidates.routes || [], (record) => record.id_hint),
219
- actions: dedupeCandidateRecords(candidates.actions || [], (record) => record.id_hint),
220
- widgets: dedupeCandidateRecords([...explicitWidgets, ...derivedWidgets], (record) => record.id_hint),
221
- stacks: [...new Set(candidates.stacks || [])].sort()
222
- };
223
- }
224
- if (track === "verification") {
225
- return {
226
- verifications: dedupeCandidateRecords(candidates.verifications || [], (record) => record.id_hint),
227
- scenarios: dedupeCandidateRecords(candidates.scenarios || [], (record) => record.id_hint),
228
- frameworks: [...new Set(candidates.frameworks || [])].sort(),
229
- scripts: dedupeCandidateRecords(
230
- (candidates.scripts || []).map((script) => ({
231
- ...script,
232
- id_hint: script.id_hint || `${script.file || "package.json"}:${script.name || "test"}`
233
- })),
234
- (record) => record.id_hint
235
- ).map(({ id_hint, ...script }) => script)
236
- };
237
- }
238
- return {
239
- workflows: dedupeCandidateRecords(candidates.workflows || [], (record) => record.id_hint),
240
- workflow_states: dedupeCandidateRecords(candidates.workflow_states || [], (record) => record.id_hint),
241
- workflow_transitions: dedupeCandidateRecords(candidates.workflow_transitions || [], (record) => record.id_hint)
242
- };
243
- }
244
-
245
- function runTrack(context, track) {
246
- const findings = [];
247
- const rawCandidates = track === "db"
248
- ? { entities: [], enums: [], relations: [], indexes: [] }
249
- : track === "api"
250
- ? { capabilities: [], routes: [], stacks: [] }
251
- : track === "ui"
252
- ? { screens: [], routes: [], actions: [], stacks: [] }
253
- : track === "verification"
254
- ? { verifications: [], scenarios: [], frameworks: [], scripts: [] }
255
- : { workflows: [], workflow_states: [], workflow_transitions: [] };
256
-
257
- for (const { extractor, detection } of selectDetectionsForTrack(track, sortExtractors(context, getExtractorsForTrack(track)))) {
258
- const result = extractor.extract(context) || { findings: [], candidates: {} };
259
- findings.push({
260
- extractor: extractor.id,
261
- detection,
262
- findings: result.findings || []
263
- });
264
- for (const [key, value] of Object.entries(result.candidates || {})) {
265
- if (Array.isArray(rawCandidates[key])) {
266
- rawCandidates[key].push(...value);
267
- } else if (Array.isArray(value)) {
268
- rawCandidates[key] = [...value];
269
- }
270
- }
271
- }
272
-
273
- let candidates = normalizeCandidatesForTrack(track, rawCandidates);
274
- for (const enricher of getEnrichersForTrack(track)) {
275
- const applies = enricher.applies(context, candidates);
276
- if (!applies) continue;
277
- candidates = normalizeCandidatesForTrack(track, enricher.enrich(context, candidates) || candidates);
278
- }
279
-
280
- return {
281
- findings: findings.flatMap((entry) => entry.findings || []),
282
- candidates,
283
- extractor_detections: findings.map(({ extractor, detection }) => ({ extractor, ...detection }))
284
- };
285
- }
286
-
287
- function reportMarkdown(track, candidates) {
288
- if (track === "db") {
289
- return ensureTrailingNewline(
290
- `# DB Import Report\n\n- Entities: ${candidates.entities.length}\n- Enums: ${candidates.enums.length}\n- Relations: ${candidates.relations.length}\n- Indexes: ${candidates.indexes.length}\n`
291
- );
292
- }
293
- if (track === "api") {
294
- return ensureTrailingNewline(
295
- `# API Import Report\n\n- Capabilities: ${candidates.capabilities.length}\n- Routes: ${candidates.routes.length}\n- Stacks: ${candidates.stacks.length ? candidates.stacks.join(", ") : "none"}\n`
296
- );
297
- }
298
- if (track === "ui") {
299
- const widgets = uiWidgetCandidates(candidates);
300
- const widgetLines = widgets.map((widget) =>
301
- `- \`${widget.id_hint}\` confidence ${widget.confidence || "unknown"} pattern \`${widget.pattern || widget.inferred_pattern || "unknown"}\` region \`${widget.region || widget.inferred_region || "unknown"}\` evidence ${(widget.evidence || widget.provenance || []).length} missing decisions ${(widget.missing_decisions || []).length}`
302
- );
303
- return ensureTrailingNewline(
304
- `# UI Import Report\n\n- Screens: ${candidates.screens.length}\n- Routes: ${candidates.routes.length}\n- Actions: ${candidates.actions.length}\n- Widgets: ${widgets.length}\n- Stacks: ${candidates.stacks.length ? candidates.stacks.join(", ") : "none"}\n\n## Widget Candidates\n\n${widgetLines.length ? widgetLines.join("\n") : "- none"}\n\n## Next Validation\n\n- Review candidates under \`topogram/candidates/app/ui/drafts/widgets/**\`.\n- Run \`topogram import plan <path>\` before adoption.\n- After adoption, run \`topogram check <path>\`, \`topogram widget check <path>\`, and \`topogram widget behavior <path>\`.\n`
305
- );
306
- }
307
- if (track === "verification") {
308
- return ensureTrailingNewline(
309
- `# Verification Import Report\n\n- Verifications: ${candidates.verifications.length}\n- Scenarios: ${candidates.scenarios.length}\n- Frameworks: ${candidates.frameworks.length ? candidates.frameworks.join(", ") : "none"}\n- Scripts: ${candidates.scripts.length}\n`
310
- );
311
- }
312
- return ensureTrailingNewline(
313
- `# Workflow Import Report\n\n- Workflows: ${candidates.workflows.length}\n- States: ${candidates.workflow_states.length}\n- Transitions: ${candidates.workflow_transitions.length}\n`
314
- );
315
- }
316
-
317
- function uniqueSorted(values) {
318
- return [...new Set((values || []).filter(Boolean))].sort();
319
- }
320
-
321
- function projectionIdStem(workspaceRoot) {
322
- const base = String(workspaceRoot || "").split(/[\\/]/).filter(Boolean).pop() || "imported_app";
323
- return base
324
- .toLowerCase()
325
- .replace(/[^a-z0-9]+/g, "_")
326
- .replace(/^_+|_+$/g, "") || "imported_app";
327
- }
328
-
329
- function widgetCandidateFileName(widget) {
330
- return `${String(widget.id_hint || "widget")
331
- .replace(/^component_/, "")
332
- .replace(/_/g, "-")}.tg`;
333
- }
334
-
335
- function renderWidgetCandidate(widget) {
336
- const evidenceCount = (widget.evidence || widget.provenance || []).length;
337
- const missingDecisions = widget.missing_decisions || [
338
- "confirm widget reuse boundary",
339
- "confirm prop names and data source",
340
- "confirm events and behavior"
341
- ];
342
- return `widget ${widget.id_hint} {
343
- # Import metadata: confidence ${widget.confidence || "unknown"}; evidence ${evidenceCount}; inferred pattern ${widget.pattern || widget.inferred_pattern || "search_results"}; inferred region ${widget.region || widget.inferred_region || "results"}.
344
- # Missing decisions: ${missingDecisions.join("; ")}.
345
- name "${widget.label || widget.id_hint}"
346
- description "Candidate reusable widget inferred from imported UI evidence. Review props, behavior, events, and reuse before adoption."
347
- category collection
348
- props {
349
- ${widget.data_prop || "rows"} array required
350
- }
351
- patterns [${widget.pattern || "search_results"}]
352
- regions [${widget.region || "results"}]
353
- status proposed
354
- }
355
- `;
356
- }
357
-
358
- function uiWidgetLinesForCandidates(widgetCandidates, allCandidates) {
359
- return widgetCandidates
360
- .filter((widget) => widget.screen_id && widget.region && widget.id_hint)
361
- .map((widget) => {
362
- const dataSource = inferredDataSourceForWidget(widget, allCandidates);
363
- const dataBinding = dataSource
364
- ? ` data ${widget.data_prop || "rows"} from ${dataSource}`
365
- : "";
366
- return ` screen ${widget.screen_id} region ${widget.region} widget ${widget.id_hint}${dataBinding}`;
367
- });
368
- }
369
-
370
- function enrichUiWidgetDataSources(uiCandidates, allCandidates) {
371
- if (!uiCandidates) {
372
- return uiCandidates;
373
- }
374
- const widgets = uiWidgetCandidates(uiCandidates);
375
- const { components, ...canonicalCandidates } = uiCandidates;
376
- return {
377
- ...canonicalCandidates,
378
- widgets: widgets.map((widget) => {
379
- const dataSource = inferredDataSourceForWidget(widget, allCandidates);
380
- const dataProp = widget.data_prop || "rows";
381
- return {
382
- ...widget,
383
- data_source: widget.data_source || dataSource,
384
- inferred_props: (widget.inferred_props || []).map((prop) =>
385
- prop.name === dataProp ? { ...prop, source: prop.source || dataSource } : prop
386
- )
387
- };
388
- })
389
- };
390
- }
391
-
392
- function draftUiProjectionFiles(context, candidates, allCandidates = {}) {
393
- const ui = candidates || { screens: [], routes: [], actions: [], stacks: [] };
394
- const screens = [...(ui.screens || [])].sort((a, b) => String(a.route_path || "").localeCompare(String(b.route_path || "")) || a.id_hint.localeCompare(b.id_hint));
395
- const routes = new Map((ui.routes || []).map((route) => [route.screen_id, route.path]));
396
- const actions = ui.actions || [];
397
- const widgetCandidates = [...uiWidgetCandidates(ui)].sort((a, b) => a.id_hint.localeCompare(b.id_hint));
398
- const shell = actions.find((entry) => entry.kind === "ui_shell")?.shell_kind || "topbar";
399
- const navigationPatterns = uniqueSorted(actions.filter((entry) => entry.kind === "navigation").map((entry) => entry.navigation_pattern));
400
- const presentations = uniqueSorted(actions.filter((entry) => entry.kind === "ui_presentation").map((entry) => entry.presentation));
401
- const capabilityHints = uniqueSorted([
402
- ...screens.flatMap((screen) => capabilityHintsForScreen(screen)),
403
- ...actions.map((entry) => entry.capability_hint).filter(Boolean),
404
- ...importedApiCapabilityIds(allCandidates)
405
- ]);
406
- const stem = projectionIdStem(context.paths.workspaceRoot);
407
- const screenKinds = new Map(screens.map((screen) => [screen.id_hint, screen.screen_kind || "flow"]));
408
- const defaultScreenId = screens.find((screen) => screen.screen_kind === "list")?.id_hint || screens[0]?.id_hint || null;
409
-
410
- const uiScreensBlock = screens.length > 0
411
- ? screens.map((screen) => {
412
- const directives = [`kind ${screen.screen_kind || "flow"}`, `title "${screen.label || screen.id_hint}"`];
413
- const screenCapabilityHints = capabilityHintsForScreen(screen);
414
- if (screenCapabilityHints.length > 0) {
415
- const loadHint = screenCapabilityHints.find((hint) => /^cap_(list|get)_/.test(hint));
416
- const submitHint = screenCapabilityHints.find((hint) => /^cap_(create|update|sign_in|follow|delete)_/.test(hint));
417
- if (loadHint && ["list", "detail", "job_status", "feed", "inbox", "dashboard", "analytics", "report"].includes(screen.screen_kind)) {
418
- directives.push(`load ${loadHint}`);
419
- }
420
- if (submitHint && ["form", "wizard", "settings", "flow"].includes(screen.screen_kind)) {
421
- directives.push(`submit ${submitHint}`);
422
- }
423
- }
424
- return ` screen ${screen.id_hint} ${directives.join(" ")}`;
425
- }).join("\n")
426
- : " // No imported screens detected";
427
-
428
- const collectionScreens = screens.filter((screen) => screen.screen_kind === "list");
429
- const uiCollectionsLines = [];
430
- for (const screen of collectionScreens) {
431
- const screenPresentations = presentations.filter((presentation) =>
432
- ["table", "data_grid", "cards", "board", "calendar", "gallery", "list"].includes(presentation)
433
- );
434
- const preferredView =
435
- screenPresentations.find((presentation) => ["data_grid", "table", "cards", "list"].includes(presentation))
436
- || "list";
437
- uiCollectionsLines.push(` screen ${screen.id_hint} view ${preferredView}`);
438
- if (presentations.includes("pull_to_refresh")) {
439
- uiCollectionsLines.push(` screen ${screen.id_hint} refresh pull_to_refresh`);
440
- }
441
- if (presentations.includes("search")) {
442
- uiCollectionsLines.push(` screen ${screen.id_hint} search query`);
443
- }
444
- }
445
-
446
- const uiActionsLines = actions
447
- .filter((entry) => entry.kind === "ui_action" && entry.screen_id && entry.capability_hint)
448
- .map((entry) => ` screen ${entry.screen_id} action ${entry.capability_hint} prominence ${entry.prominence || "secondary"}`);
449
-
450
- const uiNavigationLines = [];
451
- if (defaultScreenId) {
452
- if (navigationPatterns.includes("command_palette")) {
453
- uiNavigationLines.push(` group workspace label "Workspace" placement primary pattern command_palette`);
454
- } else {
455
- uiNavigationLines.push(` group workspace label "Workspace" placement primary`);
456
- }
457
- }
458
- for (const screen of screens) {
459
- const directives = [
460
- "group workspace",
461
- `label "${screen.label || screen.id_hint}"`,
462
- screen.id_hint === defaultScreenId ? "default true" : null,
463
- screen.id_hint === defaultScreenId || screen.screen_kind === "list" ? "visible true" : "visible false"
464
- ].filter(Boolean);
465
- const matchedPattern =
466
- navigationPatterns.find((pattern) =>
467
- (pattern === "stack_navigation" && screen.screen_kind === "detail")
468
- || (pattern === "segmented_control" && screen.screen_kind === "list")
469
- || (pattern === "bottom_tabs" && screen.screen_kind === "list")
470
- ) || null;
471
- if (matchedPattern) {
472
- directives.push(`pattern ${matchedPattern}`);
473
- }
474
- if (screen.screen_kind === "detail" && defaultScreenId && screen.id_hint !== defaultScreenId) {
475
- directives.push(`breadcrumb ${defaultScreenId}`);
476
- directives.push("sitemap exclude");
477
- }
478
- uiNavigationLines.push(` screen ${screen.id_hint} ${directives.join(" ")}`);
479
- }
480
-
481
- const uiScreenRegionLines = [];
482
- for (const screen of screens) {
483
- if (screen.screen_kind === "list") {
484
- uiScreenRegionLines.push(` screen ${screen.id_hint} region toolbar pattern action_bar placement primary`);
485
- const preferredPattern =
486
- presentations.includes("data_grid") ? "data_grid_view"
487
- : presentations.includes("table") ? "resource_table"
488
- : presentations.includes("cards") ? "resource_cards"
489
- : "search_results";
490
- uiScreenRegionLines.push(` screen ${screen.id_hint} region results pattern ${preferredPattern} placement primary`);
491
- }
492
- if (screen.screen_kind === "detail") {
493
- uiScreenRegionLines.push(` screen ${screen.id_hint} region summary pattern detail_panel placement primary`);
494
- if (presentations.includes("inspector_pane")) {
495
- uiScreenRegionLines.push(` screen ${screen.id_hint} region aside pattern inspector_pane placement supporting`);
496
- }
497
- }
498
- }
499
- const uiWidgetLines = uiWidgetLinesForCandidates(widgetCandidates, allCandidates);
500
-
501
- const uiSharedDraft = `projection proj_ui_contract_imported_${stem} {
502
- name "Imported UI Contract Draft"
503
- description "Drafted from imported UI candidates. Review and adapt before adoption."
504
-
505
- type ui_contract
506
- realizes [
507
- ${capabilityHints.length > 0 ? capabilityHints.map((hint) => ` ${hint}`).join(",\n") : " // add capability ids"}
508
- ]
509
- outputs [ui_contract]
510
-
511
- app_shell {
512
- brand "Imported ${stem.replace(/_/g, " ")}"
513
- shell ${shell}
514
- ${presentations.includes("search") ? " global_search true\n" : ""}${presentations.includes("multi_window") ? " windowing multi_window\n" : ""} }
515
-
516
- design_tokens {
517
- density comfortable
518
- tone operational
519
- radius_scale medium
520
- color_role primary accent
521
- color_role danger critical
522
- typography_role body readable
523
- typography_role heading prominent
524
- action_role primary prominent
525
- action_role destructive danger
526
- accessibility contrast aa
527
- accessibility focus visible
528
- }
529
-
530
- screens {
531
- ${uiScreensBlock}
532
- }
533
-
534
- ${uiCollectionsLines.length > 0 ? ` collection_views {\n${uiCollectionsLines.join("\n")}\n }\n\n` : ""}${uiActionsLines.length > 0 ? ` screen_actions {\n${uiActionsLines.join("\n")}\n }\n\n` : ""} navigation {
535
- ${uiNavigationLines.join("\n")}
536
- }
537
-
538
- ${uiScreenRegionLines.length > 0 ? ` screen_regions {\n${uiScreenRegionLines.join("\n")}\n }\n\n` : ""}${uiWidgetLines.length > 0 ? ` widget_bindings {\n${uiWidgetLines.join("\n")}\n }\n\n` : ""} status proposed
539
- }
540
- `;
541
-
542
- const webCapHints = capabilityHints.length > 0 ? capabilityHints.join(",\n ") : "// add capability ids";
543
- const uiRouteLines = screens
544
- .filter((screen) => routes.has(screen.id_hint))
545
- .map((screen) => ` screen ${screen.id_hint} path ${routes.get(screen.id_hint)}`);
546
- const uiWebLines = [];
547
- for (const screen of screens) {
548
- if (!routes.has(screen.id_hint)) continue;
549
- if (screen.id_hint === defaultScreenId) {
550
- uiWebLines.push(` screen ${screen.id_hint} shell ${shell}`);
551
- }
552
- if (screen.screen_kind === "list") {
553
- const preferredCollection =
554
- presentations.includes("data_grid") ? "data_grid"
555
- : presentations.includes("table") ? "table"
556
- : presentations.includes("cards") ? "cards"
557
- : "list";
558
- uiWebLines.push(` screen ${screen.id_hint} collection ${preferredCollection}`);
559
- if (presentations.includes("cards")) {
560
- uiWebLines.push(` screen ${screen.id_hint} mobile_variant cards`);
561
- }
562
- }
563
- if (screen.screen_kind === "detail" && presentations.includes("sheet")) {
564
- uiWebLines.push(` screen ${screen.id_hint} present sheet`);
565
- }
566
- if (screen.screen_kind === "detail" && presentations.includes("popover")) {
567
- uiWebLines.push(` screen ${screen.id_hint} present popover`);
568
- }
569
- }
570
- for (const entry of actions.filter((action) => action.kind === "ui_action" && action.capability_hint)) {
571
- const actionPresent =
572
- presentations.includes("fab") ? "fab"
573
- : presentations.includes("popover") ? "popover"
574
- : "button";
575
- uiWebLines.push(` action ${entry.capability_hint} present ${actionPresent}`);
576
- }
577
-
578
- const uiWebDraft = `projection proj_web_surface_imported_${stem} {
579
- name "Imported Web Surface Draft"
580
- description "Drafted from imported UI candidates. Review and adapt before adoption."
581
-
582
- type web_surface
583
- realizes [
584
- proj_ui_contract_imported_${stem},
585
- ${webCapHints}
586
- ]
587
- outputs [ui_contract, web_app]
588
-
589
- screen_routes {
590
- ${uiRouteLines.length > 0 ? uiRouteLines.join("\n") : " // add routes"}
591
- }
592
-
593
- ${uiWebLines.length > 0 ? ` web_hints {\n${uiWebLines.join("\n")}\n }\n\n` : ""} generator_defaults {
594
- profile react
595
- language typescript
596
- styling css
597
- }
598
-
599
- status proposed
600
- }
601
- `;
602
-
603
- const coverage = `# Imported UI Projection Drafts
604
-
605
- - Draft UI contract projection: \`candidates/app/ui/drafts/proj-ui-contract.tg\`
606
- - Draft web surface projection: \`candidates/app/ui/drafts/proj-web-surface.tg\`
607
- - Draft widget candidates: ${widgetCandidates.length}
608
- - Imported screens: ${screens.length}
609
- - Imported routes: ${(ui.routes || []).length}
610
- - Imported UI actions/presentations: ${actions.length}
611
- - Imported navigation patterns: ${navigationPatterns.length ? navigationPatterns.join(", ") : "none"}
612
- - Imported presentations: ${presentations.length ? presentations.join(", ") : "none"}
613
-
614
- ## Review Notes
615
-
616
- - These files are drafts, not adopted canonical projections.
617
- - Capability ids come from imported hints and may need renaming or pruning.
618
- - Widget candidates are suggested reusable contracts, not canonical ownership.
619
- - Review widget props, events, behavior, regions, and patterns before adopting.
620
- - Search and refresh directives are inferred heuristically.
621
- - Navigation groups currently default to a single \`workspace\` group unless stronger grouping evidence exists.
622
- `;
623
-
624
- const files = {
625
- "candidates/app/ui/drafts/proj-ui-contract.tg": ensureTrailingNewline(uiSharedDraft),
626
- "candidates/app/ui/drafts/proj-web-surface.tg": ensureTrailingNewline(uiWebDraft),
627
- "candidates/app/ui/drafts/README.md": ensureTrailingNewline(coverage)
628
- };
629
- for (const widget of widgetCandidates) {
630
- files[`candidates/app/ui/drafts/widgets/${widgetCandidateFileName(widget)}`] = ensureTrailingNewline(renderWidgetCandidate(widget));
631
- }
632
- return files;
633
- }
634
-
635
- export function runImportApp(inputPath, options = {}) {
636
- const tracks = parseImportTracks(options.from);
637
- const context = createImportContext(inputPath, options);
638
- const resultsByTrack = {};
639
- context.priorResults = resultsByTrack;
640
- context.scanDocsSummary = options.scanDocsSummary || null;
641
- const findings = {};
642
- const candidates = {};
643
- const files = {};
644
-
645
- for (const track of tracks) {
646
- if (track === "workflows") {
647
- if (!resultsByTrack.db) {
648
- resultsByTrack.db = runTrack(context, "db");
649
- }
650
- if (!resultsByTrack.api) {
651
- resultsByTrack.api = runTrack(context, "api");
652
- }
653
- }
654
- if (track === "verification") {
655
- if (!resultsByTrack.api) {
656
- resultsByTrack.api = runTrack(context, "api");
657
- }
658
- }
659
- const result = runTrack(context, track);
660
- resultsByTrack[track] = result;
661
- findings[track] = result.findings;
662
- candidates[track] = result.candidates;
663
- files[`candidates/app/${track}/findings.json`] = `${JSON.stringify(result.findings, null, 2)}\n`;
664
- files[`candidates/app/${track}/candidates.json`] = `${JSON.stringify(result.candidates, null, 2)}\n`;
665
- files[`candidates/app/${track}/report.md`] = reportMarkdown(track, result.candidates);
666
- }
667
-
668
- if (candidates.ui) {
669
- candidates.ui = enrichUiWidgetDataSources(candidates.ui, candidates);
670
- files["candidates/app/ui/candidates.json"] = `${JSON.stringify(candidates.ui, null, 2)}\n`;
671
- files["candidates/app/ui/report.md"] = reportMarkdown("ui", candidates.ui);
672
- Object.assign(files, draftUiProjectionFiles(context, candidates.ui, candidates));
673
- }
674
-
675
- const summary = {
676
- type: "import_app_report",
677
- workspace: context.paths.workspaceRoot,
678
- topogram_root: context.paths.topogramRoot,
679
- bootstrapped_topogram_root: context.paths.bootstrappedTopogramRoot,
680
- tracks,
681
- findings_count: Object.values(findings).reduce((total, entries) => total + entries.length, 0),
682
- extractor_detections: Object.fromEntries(Object.entries(resultsByTrack).map(([track, result]) => [track, result.extractor_detections])),
683
- candidates
684
- };
685
-
686
- files["candidates/app/findings.json"] = `${JSON.stringify(findings, null, 2)}\n`;
687
- files["candidates/app/candidates.json"] = `${JSON.stringify(candidates, null, 2)}\n`;
688
- files["candidates/app/report.md"] = ensureTrailingNewline(
689
- `# App Import Report\n\nTracks: ${tracks.join(", ")}\n\n## DB\n\n- Entities: ${candidates.db?.entities?.length || 0}\n- Enums: ${candidates.db?.enums?.length || 0}\n- Relations: ${candidates.db?.relations?.length || 0}\n\n## API\n\n- Capabilities: ${candidates.api?.capabilities?.length || 0}\n- Routes: ${candidates.api?.routes?.length || 0}\n- Stacks: ${candidates.api?.stacks?.length ? candidates.api.stacks.join(", ") : "none"}\n\n## UI\n\n- Screens: ${candidates.ui?.screens?.length || 0}\n- Routes: ${candidates.ui?.routes?.length || 0}\n- Actions: ${candidates.ui?.actions?.length || 0}\n- Widgets: ${uiWidgetCandidates(candidates.ui).length}\n- Stacks: ${candidates.ui?.stacks?.length ? candidates.ui.stacks.join(", ") : "none"}\n\n## Workflows\n\n- Workflows: ${candidates.workflows?.workflows?.length || 0}\n- States: ${candidates.workflows?.workflow_states?.length || 0}\n- Transitions: ${candidates.workflows?.workflow_transitions?.length || 0}\n\n## Verification\n\n- Verifications: ${candidates.verification?.verifications?.length || 0}\n- Scenarios: ${candidates.verification?.scenarios?.length || 0}\n- Frameworks: ${candidates.verification?.frameworks?.length ? candidates.verification.frameworks.join(", ") : "none"}\n- Scripts: ${candidates.verification?.scripts?.length || 0}\n`
690
- );
691
-
692
- return {
693
- summary,
694
- files,
695
- defaultOutDir: context.paths.topogramRoot
696
- };
697
- }
698
-
699
- export { parseImportTracks };
3
+ export { parseImportTracks } from "./runner/options.js";
4
+ export { runImportApp } from "./runner/run.js";