@topogram/cli 0.3.64 → 0.3.66

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 (278) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +716 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/adoption/reporting.js +1 -1
  5. package/src/agent-brief.js +7 -21
  6. package/src/agent-ops/query-builders/auth.js +375 -0
  7. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  8. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  9. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  10. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  12. package/src/agent-ops/query-builders/change-risk.js +25 -0
  13. package/src/agent-ops/query-builders/common.js +149 -0
  14. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  15. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  16. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  17. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  18. package/src/agent-ops/query-builders/work-packets.js +417 -0
  19. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  20. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  21. package/src/agent-ops/query-builders/workflow-presets-core.js +677 -0
  22. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  23. package/src/agent-ops/query-builders.d.ts +26 -26
  24. package/src/agent-ops/query-builders.js +42 -5021
  25. package/src/archive/jsonl.js +2 -2
  26. package/src/archive/resolver-bridge.js +1 -1
  27. package/src/archive/unarchive.js +2 -1
  28. package/src/catalog/constants.js +10 -0
  29. package/src/catalog/copy.js +65 -0
  30. package/src/catalog/diagnostics.js +15 -0
  31. package/src/catalog/entries.js +42 -0
  32. package/src/catalog/files.js +67 -0
  33. package/src/catalog/provenance.js +123 -0
  34. package/src/catalog/source.js +150 -0
  35. package/src/catalog/validation.js +252 -0
  36. package/src/catalog.d.ts +2 -0
  37. package/src/catalog.js +18 -746
  38. package/src/cli/command-parsers/project.js +3 -0
  39. package/src/cli/command-parsers/shared.js +1 -1
  40. package/src/cli/commands/agent.js +2 -2
  41. package/src/cli/commands/catalog/check.js +31 -0
  42. package/src/cli/commands/catalog/copy.js +59 -0
  43. package/src/cli/commands/catalog/doctor.js +248 -0
  44. package/src/cli/commands/catalog/help.js +21 -0
  45. package/src/cli/commands/catalog/list.js +52 -0
  46. package/src/cli/commands/catalog/runner.js +92 -0
  47. package/src/cli/commands/catalog/shared.js +17 -0
  48. package/src/cli/commands/catalog/show.js +134 -0
  49. package/src/cli/commands/catalog.js +30 -615
  50. package/src/cli/commands/check.js +3 -3
  51. package/src/cli/commands/doctor.js +2 -9
  52. package/src/cli/commands/generator-policy/package-info.js +162 -0
  53. package/src/cli/commands/generator-policy/payloads.js +372 -0
  54. package/src/cli/commands/generator-policy/printers.js +159 -0
  55. package/src/cli/commands/generator-policy/runner.js +81 -0
  56. package/src/cli/commands/generator-policy/shared.js +39 -0
  57. package/src/cli/commands/generator-policy.js +15 -783
  58. package/src/cli/commands/import/adopt.js +170 -0
  59. package/src/cli/commands/import/check.js +91 -0
  60. package/src/cli/commands/import/diff.js +84 -0
  61. package/src/cli/commands/import/help.js +47 -0
  62. package/src/cli/commands/import/paths.js +269 -0
  63. package/src/cli/commands/import/plan.js +292 -0
  64. package/src/cli/commands/import/refresh.js +471 -0
  65. package/src/cli/commands/import/status-history.js +196 -0
  66. package/src/cli/commands/import/workspace.js +233 -0
  67. package/src/cli/commands/import.js +33 -1732
  68. package/src/cli/commands/migrate.js +153 -0
  69. package/src/cli/commands/package/constants.js +17 -0
  70. package/src/cli/commands/package/doctor.js +240 -0
  71. package/src/cli/commands/package/help.js +27 -0
  72. package/src/cli/commands/package/lockfile.js +135 -0
  73. package/src/cli/commands/package/npm.js +97 -0
  74. package/src/cli/commands/package/reporting.js +35 -0
  75. package/src/cli/commands/package/runner.js +33 -0
  76. package/src/cli/commands/package/shared.js +9 -0
  77. package/src/cli/commands/package/update-cli.js +252 -0
  78. package/src/cli/commands/package/versions.js +35 -0
  79. package/src/cli/commands/package.js +29 -813
  80. package/src/cli/commands/query/change-plan.js +68 -0
  81. package/src/cli/commands/query/definitions.js +202 -0
  82. package/src/cli/commands/query/import-adopt.js +121 -0
  83. package/src/cli/commands/query/runner/artifacts.js +102 -0
  84. package/src/cli/commands/query/runner/boundaries.js +211 -0
  85. package/src/cli/commands/query/runner/change.js +182 -0
  86. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  87. package/src/cli/commands/query/runner/index.js +31 -0
  88. package/src/cli/commands/query/runner/output.js +12 -0
  89. package/src/cli/commands/query/runner/workflow.js +241 -0
  90. package/src/cli/commands/query/runner.js +3 -0
  91. package/src/cli/commands/query/workflow-context.js +5 -0
  92. package/src/cli/commands/query/workspace.js +270 -0
  93. package/src/cli/commands/query.js +9 -1300
  94. package/src/cli/commands/source.js +3 -12
  95. package/src/cli/commands/template/baseline.js +100 -0
  96. package/src/cli/commands/template/check.js +467 -0
  97. package/src/cli/commands/template/constants.js +8 -0
  98. package/src/cli/commands/template/diagnostics.js +26 -0
  99. package/src/cli/commands/template/help.js +28 -0
  100. package/src/cli/commands/template/lifecycle.js +404 -0
  101. package/src/cli/commands/template/list-show.js +287 -0
  102. package/src/cli/commands/template/policy.js +422 -0
  103. package/src/cli/commands/template/shared.js +127 -0
  104. package/src/cli/commands/template/updates.js +352 -0
  105. package/src/cli/commands/template-runner.js +6 -6
  106. package/src/cli/commands/template.js +41 -2143
  107. package/src/cli/commands/trust.js +1 -1
  108. package/src/cli/commands/workflow.js +6 -1
  109. package/src/cli/dispatcher.js +6 -1
  110. package/src/cli/help.js +15 -14
  111. package/src/cli/migration-guidance.js +1 -1
  112. package/src/cli/output-safety.js +2 -1
  113. package/src/cli/path-normalization.js +3 -13
  114. package/src/generator/api/contracts.js +497 -0
  115. package/src/generator/api/metadata.js +221 -0
  116. package/src/generator/api/openapi.js +559 -0
  117. package/src/generator/api/schema.js +124 -0
  118. package/src/generator/api/types.d.ts +98 -0
  119. package/src/generator/api.js +3 -1195
  120. package/src/generator/context/domain-page.js +1 -1
  121. package/src/generator/context/shared/domain-sdlc.js +282 -0
  122. package/src/generator/context/shared/maintained-boundary.js +665 -0
  123. package/src/generator/context/shared/metrics.js +85 -0
  124. package/src/generator/context/shared/primitives.js +64 -0
  125. package/src/generator/context/shared/relationships.js +453 -0
  126. package/src/generator/context/shared/summaries.js +263 -0
  127. package/src/generator/context/shared/types.d.ts +207 -0
  128. package/src/generator/context/shared.d.ts +42 -0
  129. package/src/generator/context/shared.js +80 -1390
  130. package/src/generator/context/slice/core.js +397 -0
  131. package/src/generator/context/slice/sdlc.js +417 -0
  132. package/src/generator/context/slice/ui-packets.js +183 -0
  133. package/src/generator/context/slice.js +2 -859
  134. package/src/generator/context/task-mode.js +2 -2
  135. package/src/generator/registry/index.js +507 -0
  136. package/src/generator/registry.js +18 -504
  137. package/src/generator/runtime/environment/index.js +666 -0
  138. package/src/generator/runtime/environment.js +4 -666
  139. package/src/generator/runtime/runtime-check/index.js +554 -0
  140. package/src/generator/runtime/runtime-check.js +4 -554
  141. package/src/generator/runtime/shared/index.js +572 -0
  142. package/src/generator/runtime/shared.js +19 -570
  143. package/src/generator/sdlc/doc-page.js +1 -1
  144. package/src/generator/shared.d.ts +2 -0
  145. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  146. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
  147. package/src/generator/surfaces/shared.d.ts +3 -0
  148. package/src/generator/widget-conformance/behavior-report.js +258 -0
  149. package/src/generator/widget-conformance/checks.js +371 -0
  150. package/src/generator/widget-conformance/projection-context.js +200 -0
  151. package/src/generator/widget-conformance/report.js +166 -0
  152. package/src/generator/widget-conformance/types.d.ts +121 -0
  153. package/src/generator/widget-conformance.js +3 -824
  154. package/src/import/core/context.d.ts +3 -0
  155. package/src/import/core/context.js +5 -7
  156. package/src/import/core/contracts.d.ts +1 -0
  157. package/src/import/core/registry.d.ts +4 -0
  158. package/src/import/core/runner/candidates.js +337 -0
  159. package/src/import/core/runner/options.js +22 -0
  160. package/src/import/core/runner/reports.js +51 -0
  161. package/src/import/core/runner/run.js +79 -0
  162. package/src/import/core/runner/tracks.js +150 -0
  163. package/src/import/core/runner/ui-drafts.js +393 -0
  164. package/src/import/core/runner.js +3 -698
  165. package/src/import/core/shared/api-routes.js +221 -0
  166. package/src/import/core/shared/candidates.js +97 -0
  167. package/src/import/core/shared/files.js +177 -0
  168. package/src/import/core/shared/next-app.js +389 -0
  169. package/src/import/core/shared/types.d.ts +51 -0
  170. package/src/import/core/shared/ui-routes.js +230 -0
  171. package/src/import/core/shared.js +60 -861
  172. package/src/new-project/constants.js +128 -0
  173. package/src/new-project/create.js +90 -0
  174. package/src/new-project/json.js +28 -0
  175. package/src/new-project/metadata.js +96 -0
  176. package/src/new-project/package-spec.js +161 -0
  177. package/src/new-project/project-files.js +351 -0
  178. package/src/new-project/template-policy.js +269 -0
  179. package/src/new-project/template-resolution.js +370 -0
  180. package/src/new-project/template-snapshots.js +442 -0
  181. package/src/new-project/template-updates.js +512 -0
  182. package/src/new-project/types.d.ts +83 -0
  183. package/src/new-project.js +6 -2277
  184. package/src/parser.d.ts +87 -1
  185. package/src/parser.js +118 -0
  186. package/src/policy/review-boundaries.d.ts +15 -0
  187. package/src/project-config/index.js +591 -0
  188. package/src/project-config.js +19 -561
  189. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  190. package/src/resolver/enrich/bug.js +2 -0
  191. package/src/resolver/enrich/pitch.js +2 -0
  192. package/src/resolver/enrich/requirement.js +2 -0
  193. package/src/resolver/enrich/task.js +2 -0
  194. package/src/resolver/index.js +19 -2089
  195. package/src/resolver/normalize.js +384 -1
  196. package/src/resolver/plans.js +168 -0
  197. package/src/resolver/projections-api.js +494 -0
  198. package/src/resolver/projections-db.js +133 -0
  199. package/src/resolver/projections-ui.js +317 -0
  200. package/src/resolver/shapes.js +251 -0
  201. package/src/resolver/shared.js +278 -0
  202. package/src/resolver/widgets.js +132 -0
  203. package/src/sdlc/adopt.js +6 -5
  204. package/src/sdlc/paths.js +3 -5
  205. package/src/sdlc/scaffold.js +2 -1
  206. package/src/template-trust/constants.js +62 -0
  207. package/src/template-trust/content.js +258 -0
  208. package/src/template-trust/diff.js +92 -0
  209. package/src/template-trust/policy.js +61 -0
  210. package/src/template-trust/record.js +90 -0
  211. package/src/template-trust/status.js +182 -0
  212. package/src/template-trust.js +24 -687
  213. package/src/text-helpers.d.ts +1 -0
  214. package/src/topogram-types.d.ts +69 -0
  215. package/src/validator/common.js +488 -0
  216. package/src/validator/data-model.js +237 -0
  217. package/src/validator/docs.js +167 -0
  218. package/src/validator/expressions.js +146 -1
  219. package/src/validator/index.d.ts +23 -0
  220. package/src/validator/index.js +32 -3585
  221. package/src/validator/kinds.d.ts +41 -0
  222. package/src/validator/kinds.js +2 -0
  223. package/src/validator/model-helpers.js +46 -0
  224. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  225. package/src/validator/per-kind/bug.js +6 -0
  226. package/src/validator/per-kind/domain.js +15 -2
  227. package/src/validator/per-kind/pitch.js +7 -0
  228. package/src/validator/per-kind/requirement.js +5 -0
  229. package/src/validator/per-kind/task.js +7 -0
  230. package/src/validator/per-kind/widget.js +14 -0
  231. package/src/validator/projections/api-http-async.js +410 -0
  232. package/src/validator/projections/api-http-authz.js +88 -0
  233. package/src/validator/projections/api-http-core.js +205 -0
  234. package/src/validator/projections/api-http-policies.js +339 -0
  235. package/src/validator/projections/api-http-responses.js +233 -0
  236. package/src/validator/projections/api-http.js +44 -0
  237. package/src/validator/projections/db.js +353 -0
  238. package/src/validator/projections/generator-defaults.js +45 -0
  239. package/src/validator/projections/helpers.js +87 -0
  240. package/src/validator/projections/ui-helpers.js +214 -0
  241. package/src/validator/projections/ui-navigation.js +344 -0
  242. package/src/validator/projections/ui-structure.js +364 -0
  243. package/src/validator/projections/ui-widgets.js +493 -0
  244. package/src/validator/projections/ui.js +46 -0
  245. package/src/validator/registry.js +48 -1
  246. package/src/validator/utils.d.ts +20 -0
  247. package/src/validator/utils.js +115 -12
  248. package/src/widget-behavior.d.ts +1 -0
  249. package/src/workflows/import-app/api/collect.js +221 -0
  250. package/src/workflows/import-app/api/openapi.js +257 -0
  251. package/src/workflows/import-app/api/routes.js +327 -0
  252. package/src/workflows/import-app/api/sources.js +22 -0
  253. package/src/workflows/import-app/api.js +2 -797
  254. package/src/workflows/reconcile/adoption-plan/build.js +212 -0
  255. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  256. package/src/workflows/reconcile/adoption-plan/outputs.js +153 -0
  257. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  258. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  259. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  260. package/src/workflows/reconcile/adoption-plan.js +30 -740
  261. package/src/workflows/reconcile/auth/closures.js +115 -0
  262. package/src/workflows/reconcile/auth/formatters.js +142 -0
  263. package/src/workflows/reconcile/auth/inference.js +330 -0
  264. package/src/workflows/reconcile/auth/roles.js +122 -0
  265. package/src/workflows/reconcile/auth.js +35 -690
  266. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  267. package/src/workflows/reconcile/bundle-core.js +12 -598
  268. package/src/workflows/reconcile/candidate-model.js +18 -2
  269. package/src/workflows/reconcile/canonical-surface.js +1 -1
  270. package/src/workflows/reconcile/impacts/adoption-plan.js +196 -0
  271. package/src/workflows/reconcile/impacts/indexes.js +105 -0
  272. package/src/workflows/reconcile/impacts/patches.js +252 -0
  273. package/src/workflows/reconcile/impacts/reports.js +80 -0
  274. package/src/workflows/reconcile/impacts.js +14 -623
  275. package/src/workflows/reconcile/renderers.js +41 -6
  276. package/src/workflows/shared.js +5 -11
  277. package/src/workspace-docs.d.ts +29 -0
  278. package/src/workspace-paths.js +328 -0
@@ -0,0 +1,150 @@
1
+ // @ts-check
2
+
3
+ import { getEnrichersForTrack, getExtractorsForTrack } from "../registry.js";
4
+ import { normalizeCandidatesForTrack } from "./candidates.js";
5
+
6
+ /**
7
+ * @param {any} context
8
+ * @param {any[]} extractors
9
+ * @returns {Array<{ extractor: any, detection: any }>}
10
+ */
11
+ function sortExtractors(context, extractors) {
12
+ return extractors
13
+ .map((extractor) => ({ extractor, detection: extractor.detect(context) || { score: 0, reasons: [] } }))
14
+ .filter((entry) => entry.detection.score > 0)
15
+ .sort((a, b) => b.detection.score - a.detection.score || a.extractor.id.localeCompare(b.extractor.id));
16
+ }
17
+
18
+ /**
19
+ * @param {string} track
20
+ * @param {Array<{ extractor: any, detection: any }>} detections
21
+ * @returns {Array<{ extractor: any, detection: any }>}
22
+ */
23
+ function selectDetectionsForTrack(track, detections) {
24
+ if (track === "db") {
25
+ const prisma = detections.find((entry) => entry.extractor.id === "db.prisma");
26
+ if (prisma) return [prisma];
27
+ const djangoModels = detections.find((entry) => entry.extractor.id === "db.django-models");
28
+ if (djangoModels) return [djangoModels];
29
+ const efCore = detections.find((entry) => entry.extractor.id === "db.ef-core");
30
+ if (efCore) return [efCore];
31
+ const room = detections.find((entry) => entry.extractor.id === "db.room");
32
+ if (room) return [room];
33
+ const swiftData = detections.find((entry) => entry.extractor.id === "db.swiftdata");
34
+ if (swiftData) return [swiftData];
35
+ const dotnetModels = detections.find((entry) => entry.extractor.id === "db.dotnet-models");
36
+ if (dotnetModels) return [dotnetModels];
37
+ const flutterEntities = detections.find((entry) => entry.extractor.id === "db.flutter-entities");
38
+ if (flutterEntities) return [flutterEntities];
39
+ const reactNativeEntities = detections.find((entry) => entry.extractor.id === "db.react-native-entities");
40
+ if (reactNativeEntities) return [reactNativeEntities];
41
+ const railsSchema = detections.find((entry) => entry.extractor.id === "db.rails-schema");
42
+ if (railsSchema) return [railsSchema];
43
+ const liquibase = detections.find((entry) => entry.extractor.id === "db.liquibase");
44
+ if (liquibase) return [liquibase];
45
+ const myBatisXml = detections.find((entry) => entry.extractor.id === "db.mybatis-xml");
46
+ if (myBatisXml) return [myBatisXml];
47
+ const jpa = detections.find((entry) => entry.extractor.id === "db.jpa");
48
+ if (jpa) return [jpa];
49
+ const drizzle = detections.find((entry) => entry.extractor.id === "db.drizzle");
50
+ if (drizzle) return [drizzle];
51
+ const sql = detections.find((entry) => entry.extractor.id === "db.sql");
52
+ if (sql) return [sql];
53
+ const snapshot = detections.find((entry) => entry.extractor.id === "db.snapshot");
54
+ return snapshot ? [snapshot] : [];
55
+ }
56
+ if (track === "api") {
57
+ const openApi = detections.find((entry) => entry.extractor.id === "api.openapi");
58
+ if (openApi) return [openApi];
59
+ const openApiCode = detections.find((entry) => entry.extractor.id === "api.openapi-code");
60
+ if (openApiCode) return [openApiCode];
61
+ const graphQlSdl = detections.find((entry) => entry.extractor.id === "api.graphql-sdl");
62
+ if (graphQlSdl) return [graphQlSdl];
63
+ const trpc = detections.find((entry) => entry.extractor.id === "api.trpc");
64
+ if (trpc) return [trpc];
65
+ const aspNetCore = detections.find((entry) => entry.extractor.id === "api.aspnet-core");
66
+ if (aspNetCore) return [aspNetCore];
67
+ const retrofit = detections.find((entry) => entry.extractor.id === "api.retrofit");
68
+ if (retrofit) return [retrofit];
69
+ const swiftWebApi = detections.find((entry) => entry.extractor.id === "api.swift-webapi");
70
+ if (swiftWebApi) return [swiftWebApi];
71
+ const flutterDio = detections.find((entry) => entry.extractor.id === "api.flutter-dio");
72
+ if (flutterDio) return [flutterDio];
73
+ const reactNativeRepository = detections.find((entry) => entry.extractor.id === "api.react-native-repository");
74
+ if (reactNativeRepository) return [reactNativeRepository];
75
+ const fastify = detections.find((entry) => entry.extractor.id === "api.fastify");
76
+ if (fastify) return [fastify];
77
+ const express = detections.find((entry) => entry.extractor.id === "api.express");
78
+ if (express) return [express];
79
+ const djangoRoutes = detections.find((entry) => entry.extractor.id === "api.django-routes");
80
+ if (djangoRoutes) return [djangoRoutes];
81
+ const railsRoutes = detections.find((entry) => entry.extractor.id === "api.rails-routes");
82
+ if (railsRoutes) return [railsRoutes];
83
+ const micronaut = detections.find((entry) => entry.extractor.id === "api.micronaut");
84
+ if (micronaut) return [micronaut];
85
+ const jaxrs = detections.find((entry) => entry.extractor.id === "api.jaxrs");
86
+ if (jaxrs) return [jaxrs];
87
+ const springWeb = detections.find((entry) => entry.extractor.id === "api.spring-web");
88
+ if (springWeb) return [springWeb];
89
+ }
90
+ return detections;
91
+ }
92
+
93
+ /**
94
+ * @param {string} track
95
+ * @returns {any}
96
+ */
97
+ function initialCandidatesForTrack(track) {
98
+ if (track === "db") {
99
+ return { entities: [], enums: [], relations: [], indexes: [] };
100
+ }
101
+ if (track === "api") {
102
+ return { capabilities: [], routes: [], stacks: [] };
103
+ }
104
+ if (track === "ui") {
105
+ return { screens: [], routes: [], actions: [], stacks: [] };
106
+ }
107
+ if (track === "verification") {
108
+ return { verifications: [], scenarios: [], frameworks: [], scripts: [] };
109
+ }
110
+ return { workflows: [], workflow_states: [], workflow_transitions: [] };
111
+ }
112
+
113
+ /**
114
+ * @param {any} context
115
+ * @param {string} track
116
+ * @returns {{ findings: any[], candidates: any, extractor_detections: any[] }}
117
+ */
118
+ export function runTrack(context, track) {
119
+ const findings = [];
120
+ const rawCandidates = initialCandidatesForTrack(track);
121
+
122
+ for (const { extractor, detection } of selectDetectionsForTrack(track, sortExtractors(context, getExtractorsForTrack(track)))) {
123
+ const result = extractor.extract(context) || { findings: [], candidates: {} };
124
+ findings.push({
125
+ extractor: extractor.id,
126
+ detection,
127
+ findings: result.findings || []
128
+ });
129
+ for (const [key, value] of Object.entries(result.candidates || {})) {
130
+ if (Array.isArray(rawCandidates[key])) {
131
+ rawCandidates[key].push(...value);
132
+ } else if (Array.isArray(value)) {
133
+ rawCandidates[key] = [...value];
134
+ }
135
+ }
136
+ }
137
+
138
+ let candidates = normalizeCandidatesForTrack(track, rawCandidates);
139
+ for (const enricher of getEnrichersForTrack(track)) {
140
+ const applies = enricher.applies(context, candidates);
141
+ if (!applies) continue;
142
+ candidates = normalizeCandidatesForTrack(track, enricher.enrich(context, candidates) || candidates);
143
+ }
144
+
145
+ return {
146
+ findings: findings.flatMap((entry) => entry.findings || []),
147
+ candidates,
148
+ extractor_detections: findings.map(({ extractor, detection }) => ({ extractor, ...detection }))
149
+ };
150
+ }
@@ -0,0 +1,393 @@
1
+ // @ts-check
2
+
3
+ import { ensureTrailingNewline } from "../shared.js";
4
+ import {
5
+ capabilityHintsForScreen,
6
+ importedApiCapabilityIds,
7
+ inferredDataSourceForWidget,
8
+ uiWidgetCandidates
9
+ } from "./candidates.js";
10
+
11
+ /**
12
+ * @param {any[]} values
13
+ * @returns {string[]}
14
+ */
15
+ function uniqueSorted(values) {
16
+ return [...new Set((values || []).filter(Boolean).map(String))].sort();
17
+ }
18
+
19
+ /**
20
+ * @param {string} workspaceRoot
21
+ * @returns {string}
22
+ */
23
+ function projectionIdStem(workspaceRoot) {
24
+ const base = String(workspaceRoot || "").split(/[\\/]/).filter(Boolean).pop() || "imported_app";
25
+ return base
26
+ .toLowerCase()
27
+ .replace(/[^a-z0-9]+/g, "_")
28
+ .replace(/^_+|_+$/g, "") || "imported_app";
29
+ }
30
+
31
+ /**
32
+ * @param {any} widget
33
+ * @returns {string}
34
+ */
35
+ function widgetCandidateFileName(widget) {
36
+ return `${String(widget.id_hint || "widget")
37
+ .replace(/^component_/, "")
38
+ .replace(/_/g, "-")}.tg`;
39
+ }
40
+
41
+ /**
42
+ * @param {any} widget
43
+ * @returns {string}
44
+ */
45
+ function renderWidgetCandidate(widget) {
46
+ const evidenceCount = (widget.evidence || widget.provenance || []).length;
47
+ const missingDecisions = widget.missing_decisions || [
48
+ "confirm widget reuse boundary",
49
+ "confirm prop names and data source",
50
+ "confirm events and behavior"
51
+ ];
52
+ const inferredEvents = Array.isArray(widget.inferred_events) ? widget.inferred_events : [];
53
+ const eventLines = inferredEvents
54
+ .filter((/** @type {any} */ event) => event.name && event.payload_shape)
55
+ .map((/** @type {any} */ event) => ` ${event.name} ${event.payload_shape}`);
56
+ const selectionEvent = inferredEvents.find((/** @type {any} */ event) => event.name);
57
+ const inferredEventComments = inferredEvents.length > 0
58
+ ? `${inferredEvents.map((/** @type {any} */ event) =>
59
+ ` # Inferred event: ${event.name || "event"} ${event.action || "action"} ${event.target_screen || event.target || "target"}; review payload shape ${event.payload_shape || "before adding an events block"}.`
60
+ ).join("\n")}\n`
61
+ : "";
62
+ const behaviorBlock = eventLines.length > 0
63
+ ? ` events {\n${eventLines.join("\n")}\n }\n behavior [selection]\n behaviors {\n selection mode single emits ${selectionEvent?.name || "row_select"}\n }\n`
64
+ : "";
65
+ return `widget ${widget.id_hint} {
66
+ # 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"}.
67
+ # Missing decisions: ${missingDecisions.join("; ")}.
68
+ ${inferredEventComments} # Event declarations are draft bindings and require payload shape review before adoption.
69
+ name "${widget.label || widget.id_hint}"
70
+ description "Candidate reusable widget inferred from imported UI evidence. Review props, behavior, events, and reuse before adoption."
71
+ category collection
72
+ props {
73
+ ${widget.data_prop || "rows"} array required
74
+ }
75
+ ${behaviorBlock} patterns [${widget.pattern || "search_results"}]
76
+ regions [${widget.region || "results"}]
77
+ status proposed
78
+ }
79
+ `;
80
+ }
81
+
82
+ /**
83
+ * @param {any} shape
84
+ * @returns {string}
85
+ */
86
+ function renderShapeCandidate(shape) {
87
+ const fields = Array.isArray(shape.fields) && shape.fields.length > 0
88
+ ? shape.fields
89
+ : [{ name: "id", field_type: "string", required: true }];
90
+ return `shape ${shape.id_hint} {
91
+ # Import metadata: confidence ${shape.confidence || "unknown"}; source ${shape.source_kind || "unknown"}.
92
+ # Missing decisions: ${(shape.missing_decisions || ["confirm event payload fields"]).join("; ")}.
93
+ name "${shape.label || shape.id_hint}"
94
+ description "Candidate event payload shape inferred from imported UI interaction evidence."
95
+ fields {
96
+ ${fields.map((/** @type {any} */ field) => {
97
+ const fieldName = typeof field === "string" ? field : field.name;
98
+ const fieldType = typeof field === "string" ? "string" : (field.field_type || field.type || "string");
99
+ const requiredness = typeof field === "string" || field.required ? "required" : "optional";
100
+ return ` ${fieldName} ${fieldType} ${requiredness}`;
101
+ }).join("\n")}
102
+ }
103
+ status proposed
104
+ }
105
+ `;
106
+ }
107
+
108
+ /**
109
+ * @param {any} widget
110
+ * @returns {string}
111
+ */
112
+ function widgetEventDirectives(widget) {
113
+ return (widget.inferred_events || [])
114
+ .filter((/** @type {any} */ event) => event.name && event.action && (event.target_screen || event.target) && event.payload_shape)
115
+ .map((/** @type {any} */ event) => ` event ${event.name} ${event.action} ${event.target_screen || event.target}`)
116
+ .join("");
117
+ }
118
+
119
+ /**
120
+ * @param {any[]} widgetCandidates
121
+ * @param {Record<string, any>} allCandidates
122
+ * @returns {string[]}
123
+ */
124
+ function uiWidgetLinesForCandidates(widgetCandidates, allCandidates) {
125
+ return widgetCandidates
126
+ .filter((widget) => widget.screen_id && widget.region && widget.id_hint)
127
+ .map((widget) => {
128
+ const dataSource = inferredDataSourceForWidget(widget, allCandidates);
129
+ const dataBinding = dataSource
130
+ ? ` data ${widget.data_prop || "rows"} from ${dataSource}`
131
+ : "";
132
+ return ` screen ${widget.screen_id} region ${widget.region} widget ${widget.id_hint}${dataBinding}${widgetEventDirectives(widget)}`;
133
+ });
134
+ }
135
+
136
+ /**
137
+ * @param {any} context
138
+ * @param {any} candidates
139
+ * @param {Record<string, any>} allCandidates
140
+ * @returns {Record<string, string>}
141
+ */
142
+ export function draftUiProjectionFiles(context, candidates, allCandidates = {}) {
143
+ const ui = candidates || { screens: [], routes: [], actions: [], stacks: [] };
144
+ /** @type {any[]} */
145
+ const screens = [...(ui.screens || [])].sort((a, b) => String(a.route_path || "").localeCompare(String(b.route_path || "")) || a.id_hint.localeCompare(b.id_hint));
146
+ /** @type {any[]} */
147
+ const uiRoutes = ui.routes || [];
148
+ /** @type {Map<string, string>} */
149
+ const routes = new Map(uiRoutes.map((route) => [route.screen_id, route.path]));
150
+ /** @type {any[]} */
151
+ const actions = ui.actions || [];
152
+ const widgetCandidates = [...uiWidgetCandidates(ui)].sort((a, b) => a.id_hint.localeCompare(b.id_hint));
153
+ const shapeCandidates = [...(ui.shapes || [])].sort((a, b) => String(a.id_hint || "").localeCompare(String(b.id_hint || "")));
154
+ const shell = actions.find((entry) => entry.kind === "ui_shell")?.shell_kind || "topbar";
155
+ const navigationPatterns = uniqueSorted(actions.filter((entry) => entry.kind === "navigation").map((entry) => entry.navigation_pattern));
156
+ const presentations = uniqueSorted(actions.filter((entry) => entry.kind === "ui_presentation").map((entry) => entry.presentation));
157
+ const capabilityHints = uniqueSorted([
158
+ ...screens.flatMap((screen) => capabilityHintsForScreen(screen)),
159
+ ...actions.map((entry) => entry.capability_hint).filter(Boolean),
160
+ ...importedApiCapabilityIds(allCandidates)
161
+ ]);
162
+ const stem = projectionIdStem(context.paths.workspaceRoot);
163
+ const defaultScreenId = screens.find((screen) => screen.screen_kind === "list")?.id_hint || screens[0]?.id_hint || null;
164
+
165
+ const uiScreensBlock = screens.length > 0
166
+ ? screens.map((screen) => {
167
+ const directives = [`kind ${screen.screen_kind || "flow"}`, `title "${screen.label || screen.id_hint}"`];
168
+ const screenCapabilityHints = capabilityHintsForScreen(screen);
169
+ if (screenCapabilityHints.length > 0) {
170
+ const loadHint = screenCapabilityHints.find((hint) => /^cap_(list|get)_/.test(hint));
171
+ const submitHint = screenCapabilityHints.find((hint) => /^cap_(create|update|sign_in|follow|delete)_/.test(hint));
172
+ if (loadHint && ["list", "detail", "job_status", "feed", "inbox", "dashboard", "analytics", "report"].includes(screen.screen_kind)) {
173
+ directives.push(`load ${loadHint}`);
174
+ }
175
+ if (submitHint && ["form", "wizard", "settings", "flow"].includes(screen.screen_kind)) {
176
+ directives.push(`submit ${submitHint}`);
177
+ }
178
+ }
179
+ return ` screen ${screen.id_hint} ${directives.join(" ")}`;
180
+ }).join("\n")
181
+ : " // No imported screens detected";
182
+
183
+ const collectionScreens = screens.filter((screen) => screen.screen_kind === "list");
184
+ const uiCollectionsLines = [];
185
+ for (const screen of collectionScreens) {
186
+ const screenPresentations = presentations.filter((presentation) =>
187
+ ["table", "data_grid", "cards", "board", "calendar", "gallery", "list"].includes(presentation)
188
+ );
189
+ const preferredView =
190
+ screenPresentations.find((presentation) => ["data_grid", "table", "cards", "list"].includes(presentation))
191
+ || "list";
192
+ uiCollectionsLines.push(` screen ${screen.id_hint} view ${preferredView}`);
193
+ if (presentations.includes("pull_to_refresh")) {
194
+ uiCollectionsLines.push(` screen ${screen.id_hint} refresh pull_to_refresh`);
195
+ }
196
+ if (presentations.includes("search")) {
197
+ uiCollectionsLines.push(` screen ${screen.id_hint} search query`);
198
+ }
199
+ }
200
+
201
+ const uiActionsLines = actions
202
+ .filter((entry) => entry.kind === "ui_action" && entry.screen_id && entry.capability_hint)
203
+ .map((entry) => ` screen ${entry.screen_id} action ${entry.capability_hint} prominence ${entry.prominence || "secondary"}`);
204
+
205
+ const uiNavigationLines = [];
206
+ if (defaultScreenId) {
207
+ if (navigationPatterns.includes("command_palette")) {
208
+ uiNavigationLines.push(` group workspace label "Workspace" placement primary pattern command_palette`);
209
+ } else {
210
+ uiNavigationLines.push(` group workspace label "Workspace" placement primary`);
211
+ }
212
+ }
213
+ for (const screen of screens) {
214
+ const directives = [
215
+ "group workspace",
216
+ `label "${screen.label || screen.id_hint}"`,
217
+ screen.id_hint === defaultScreenId ? "default true" : null,
218
+ screen.id_hint === defaultScreenId || screen.screen_kind === "list" ? "visible true" : "visible false"
219
+ ].filter(Boolean);
220
+ const matchedPattern =
221
+ navigationPatterns.find((pattern) =>
222
+ (pattern === "stack_navigation" && screen.screen_kind === "detail")
223
+ || (pattern === "segmented_control" && screen.screen_kind === "list")
224
+ || (pattern === "bottom_tabs" && screen.screen_kind === "list")
225
+ ) || null;
226
+ if (matchedPattern) {
227
+ directives.push(`pattern ${matchedPattern}`);
228
+ }
229
+ if (screen.screen_kind === "detail" && defaultScreenId && screen.id_hint !== defaultScreenId) {
230
+ directives.push(`breadcrumb ${defaultScreenId}`);
231
+ directives.push("sitemap exclude");
232
+ }
233
+ uiNavigationLines.push(` screen ${screen.id_hint} ${directives.join(" ")}`);
234
+ }
235
+
236
+ const uiScreenRegionLines = [];
237
+ for (const screen of screens) {
238
+ if (screen.screen_kind === "list") {
239
+ uiScreenRegionLines.push(` screen ${screen.id_hint} region toolbar pattern action_bar placement primary`);
240
+ const preferredPattern =
241
+ presentations.includes("data_grid") ? "data_grid_view"
242
+ : presentations.includes("table") ? "resource_table"
243
+ : presentations.includes("cards") ? "resource_cards"
244
+ : "search_results";
245
+ uiScreenRegionLines.push(` screen ${screen.id_hint} region results pattern ${preferredPattern} placement primary`);
246
+ }
247
+ if (screen.screen_kind === "detail") {
248
+ uiScreenRegionLines.push(` screen ${screen.id_hint} region summary pattern detail_panel placement primary`);
249
+ if (presentations.includes("inspector_pane")) {
250
+ uiScreenRegionLines.push(` screen ${screen.id_hint} region aside pattern inspector_pane placement supporting`);
251
+ }
252
+ }
253
+ }
254
+ const uiWidgetLines = uiWidgetLinesForCandidates(widgetCandidates, allCandidates);
255
+
256
+ const uiSharedDraft = `projection proj_ui_contract_imported_${stem} {
257
+ name "Imported UI Contract Draft"
258
+ description "Drafted from imported UI candidates. Review and adapt before adoption."
259
+
260
+ type ui_contract
261
+ realizes [
262
+ ${capabilityHints.length > 0 ? capabilityHints.map((hint) => ` ${hint}`).join(",\n") : " // add capability ids"}
263
+ ]
264
+ outputs [ui_contract]
265
+
266
+ app_shell {
267
+ brand "Imported ${stem.replace(/_/g, " ")}"
268
+ shell ${shell}
269
+ ${presentations.includes("search") ? " global_search true\n" : ""}${presentations.includes("multi_window") ? " windowing multi_window\n" : ""} }
270
+
271
+ design_tokens {
272
+ density comfortable
273
+ tone operational
274
+ radius_scale medium
275
+ color_role primary accent
276
+ color_role danger critical
277
+ typography_role body readable
278
+ typography_role heading prominent
279
+ action_role primary prominent
280
+ action_role destructive danger
281
+ accessibility contrast aa
282
+ accessibility focus visible
283
+ }
284
+
285
+ screens {
286
+ ${uiScreensBlock}
287
+ }
288
+
289
+ ${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 {
290
+ ${uiNavigationLines.join("\n")}
291
+ }
292
+
293
+ ${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
294
+ }
295
+ `;
296
+
297
+ const webCapHints = capabilityHints.length > 0 ? capabilityHints.join(",\n ") : "// add capability ids";
298
+ const uiRouteLines = screens
299
+ .filter((screen) => routes.has(screen.id_hint))
300
+ .map((screen) => ` screen ${screen.id_hint} path ${routes.get(screen.id_hint)}`);
301
+ const uiWebLines = [];
302
+ for (const screen of screens) {
303
+ if (!routes.has(screen.id_hint)) continue;
304
+ if (screen.id_hint === defaultScreenId) {
305
+ uiWebLines.push(` screen ${screen.id_hint} shell ${shell}`);
306
+ }
307
+ if (screen.screen_kind === "list") {
308
+ const preferredCollection =
309
+ presentations.includes("data_grid") ? "data_grid"
310
+ : presentations.includes("table") ? "table"
311
+ : presentations.includes("cards") ? "cards"
312
+ : "list";
313
+ uiWebLines.push(` screen ${screen.id_hint} collection ${preferredCollection}`);
314
+ if (presentations.includes("cards")) {
315
+ uiWebLines.push(` screen ${screen.id_hint} mobile_variant cards`);
316
+ }
317
+ }
318
+ if (screen.screen_kind === "detail" && presentations.includes("sheet")) {
319
+ uiWebLines.push(` screen ${screen.id_hint} present sheet`);
320
+ }
321
+ if (screen.screen_kind === "detail" && presentations.includes("popover")) {
322
+ uiWebLines.push(` screen ${screen.id_hint} present popover`);
323
+ }
324
+ }
325
+ for (const entry of actions.filter((action) => action.kind === "ui_action" && action.capability_hint)) {
326
+ const actionPresent =
327
+ presentations.includes("fab") ? "fab"
328
+ : presentations.includes("popover") ? "popover"
329
+ : "button";
330
+ uiWebLines.push(` action ${entry.capability_hint} present ${actionPresent}`);
331
+ }
332
+
333
+ const uiWebDraft = `projection proj_web_surface_imported_${stem} {
334
+ name "Imported Web Surface Draft"
335
+ description "Drafted from imported UI candidates. Review and adapt before adoption."
336
+
337
+ type web_surface
338
+ realizes [
339
+ proj_ui_contract_imported_${stem},
340
+ ${webCapHints}
341
+ ]
342
+ outputs [ui_contract, web_app]
343
+
344
+ screen_routes {
345
+ ${uiRouteLines.length > 0 ? uiRouteLines.join("\n") : " // add routes"}
346
+ }
347
+
348
+ ${uiWebLines.length > 0 ? ` web_hints {\n${uiWebLines.join("\n")}\n }\n\n` : ""} generator_defaults {
349
+ profile react
350
+ language typescript
351
+ styling css
352
+ }
353
+
354
+ status proposed
355
+ }
356
+ `;
357
+
358
+ const coverage = `# Imported UI Projection Drafts
359
+
360
+ - Draft UI contract projection: \`candidates/app/ui/drafts/proj-ui-contract.tg\`
361
+ - Draft web surface projection: \`candidates/app/ui/drafts/proj-web-surface.tg\`
362
+ - Draft widget candidates: ${widgetCandidates.length}
363
+ - Draft event payload shape candidates: ${shapeCandidates.length}
364
+ - Imported screens: ${screens.length}
365
+ - Imported routes: ${(ui.routes || []).length}
366
+ - Imported UI actions/presentations: ${actions.length}
367
+ - Imported navigation patterns: ${navigationPatterns.length ? navigationPatterns.join(", ") : "none"}
368
+ - Imported presentations: ${presentations.length ? presentations.join(", ") : "none"}
369
+
370
+ ## Review Notes
371
+
372
+ - These files are drafts, not adopted canonical projections.
373
+ - Capability ids come from imported hints and may need renaming or pruning.
374
+ - Widget candidates are suggested reusable contracts, not canonical ownership.
375
+ - Review widget props, events, behavior, regions, and patterns before adopting.
376
+ - Search and refresh directives are inferred heuristically.
377
+ - Navigation groups currently default to a single \`workspace\` group unless stronger grouping evidence exists.
378
+ `;
379
+
380
+ /** @type {Record<string, string>} */
381
+ const files = {
382
+ "candidates/app/ui/drafts/proj-ui-contract.tg": ensureTrailingNewline(uiSharedDraft),
383
+ "candidates/app/ui/drafts/proj-web-surface.tg": ensureTrailingNewline(uiWebDraft),
384
+ "candidates/app/ui/drafts/README.md": ensureTrailingNewline(coverage)
385
+ };
386
+ for (const shape of shapeCandidates) {
387
+ files[`candidates/app/ui/drafts/shapes/${widgetCandidateFileName(shape)}`] = ensureTrailingNewline(renderShapeCandidate(shape));
388
+ }
389
+ for (const widget of widgetCandidates) {
390
+ files[`candidates/app/ui/drafts/widgets/${widgetCandidateFileName(widget)}`] = ensureTrailingNewline(renderWidgetCandidate(widget));
391
+ }
392
+ return files;
393
+ }