@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,3 @@
1
+ export function createImportContext(...args: any[]): any;
2
+ export function findNearestGitRoot(...args: any[]): any;
3
+ export function normalizeWorkspacePaths(...args: any[]): any;
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  import { readJsonIfExists, readTextIfExists } from "./shared.js";
5
+ import { resolveWorkspaceContext } from "../../workspace-paths.js";
5
6
 
6
7
  export function findNearestGitRoot(startDir) {
7
8
  let currentDir = path.resolve(startDir);
@@ -19,13 +20,10 @@ export function findNearestGitRoot(startDir) {
19
20
  }
20
21
 
21
22
  export function normalizeWorkspacePaths(inputPath) {
23
+ const context = resolveWorkspaceContext(inputPath);
22
24
  const absolute = path.resolve(inputPath);
23
- const inputExists = fs.existsSync(absolute);
24
- const topogramChild = path.join(absolute, "topogram");
25
- const hasTopogramChild = fs.existsSync(topogramChild) && fs.statSync(topogramChild).isDirectory();
26
- const isTopogramDir = path.basename(absolute) === "topogram" && inputExists;
27
- const topogramRoot = isTopogramDir ? absolute : hasTopogramChild ? topogramChild : path.join(absolute, "topogram");
28
- const workspaceRoot = isTopogramDir ? path.dirname(topogramRoot) : absolute;
25
+ const topogramRoot = context.topoRoot;
26
+ const workspaceRoot = context.projectRoot;
29
27
  const repoRoot = findNearestGitRoot(workspaceRoot);
30
28
  return {
31
29
  inputRoot: absolute,
@@ -33,7 +31,7 @@ export function normalizeWorkspacePaths(inputPath) {
33
31
  workspaceRoot,
34
32
  exampleRoot: workspaceRoot,
35
33
  repoRoot,
36
- bootstrappedTopogramRoot: !fs.existsSync(topogramRoot)
34
+ bootstrappedTopogramRoot: context.bootstrappedTopoRoot
37
35
  };
38
36
  }
39
37
 
@@ -0,0 +1 @@
1
+ export const IMPORT_TRACKS: Set<string>;
@@ -0,0 +1,4 @@
1
+ export const extractorRegistry: Record<string, any[]>;
2
+ export const enricherRegistry: Record<string, any[]>;
3
+ export function getExtractorsForTrack(...args: any[]): any[];
4
+ export function getEnrichersForTrack(...args: any[]): any[];
@@ -0,0 +1,337 @@
1
+ // @ts-check
2
+
3
+ import { collectionPatternFromPresentations } from "../../../ui/taxonomy.js";
4
+ import { dedupeCandidateRecords, idHintify, makeCandidateRecord } from "../shared.js";
5
+
6
+ /**
7
+ * @param {Record<string, any>} allCandidates
8
+ * @returns {string[]}
9
+ */
10
+ export function importedApiCapabilityIds(allCandidates) {
11
+ return [...(allCandidates?.api?.capabilities || [])]
12
+ .map((capability) => capability.id_hint)
13
+ .filter(Boolean)
14
+ .sort();
15
+ }
16
+
17
+ /**
18
+ * @param {any} screen
19
+ * @returns {string|null}
20
+ */
21
+ export function loadCapabilityForScreen(screen) {
22
+ return capabilityHintsForScreen(screen).find((hint) => /^cap_(list|get)_/.test(hint)) || null;
23
+ }
24
+
25
+ /**
26
+ * @param {any} value
27
+ * @returns {string|null}
28
+ */
29
+ function normalizeCapabilityHint(value) {
30
+ if (typeof value === "string") {
31
+ return value;
32
+ }
33
+ if (value && typeof value === "object") {
34
+ return value.id_hint || value.id || value.capability_hint || value.capability?.id || null;
35
+ }
36
+ return null;
37
+ }
38
+
39
+ /**
40
+ * @param {any} screen
41
+ * @returns {string[]}
42
+ */
43
+ export function capabilityHintsForScreen(screen) {
44
+ const rawHints = Array.isArray(screen.capability_hints)
45
+ ? screen.capability_hints
46
+ : [screen.capability_hints].filter(Boolean);
47
+ return rawHints.map(normalizeCapabilityHint).filter(Boolean);
48
+ }
49
+
50
+ /**
51
+ * @param {string|null|undefined} screenId
52
+ * @returns {string}
53
+ */
54
+ function screenConceptStem(screenId) {
55
+ return String(screenId || "")
56
+ .replace(/_(list|index|table|grid|results|detail|show|create|new|edit|form)$/, "")
57
+ .replace(/^(list|show|create|edit)_/, "");
58
+ }
59
+
60
+ /**
61
+ * @param {string|null|undefined} screenId
62
+ * @param {string} eventName
63
+ * @returns {string}
64
+ */
65
+ function eventPayloadShapeId(screenId, eventName) {
66
+ const stem = screenConceptStem(screenId) || idHintify(String(screenId || "widget"));
67
+ return `shape_event_${idHintify(`${stem}_${eventName}`)}`;
68
+ }
69
+
70
+ /**
71
+ * @param {string|null|undefined} routePath
72
+ * @returns {{ name: string, field_type: string, required: boolean }[]}
73
+ */
74
+ function routeParamFields(routePath) {
75
+ const fields = [];
76
+ const pathText = String(routePath || "");
77
+ const routeParamPattern = /[:{]([A-Za-z_][A-Za-z0-9_]*)}?/g;
78
+ for (const match of pathText.matchAll(routeParamPattern)) {
79
+ fields.push({
80
+ name: idHintify(match[1]),
81
+ field_type: "string",
82
+ required: true
83
+ });
84
+ }
85
+ return fields.length > 0 ? fields : [{ name: "id", field_type: "string", required: true }];
86
+ }
87
+
88
+ /**
89
+ * @param {any} sourceScreen
90
+ * @param {any[]} screens
91
+ * @returns {any|null}
92
+ */
93
+ function matchingDetailScreen(sourceScreen, screens) {
94
+ const sourceStem = screenConceptStem(sourceScreen?.id_hint);
95
+ if (!sourceStem) {
96
+ return null;
97
+ }
98
+ return screens.find((screen) =>
99
+ screen?.id_hint !== sourceScreen?.id_hint &&
100
+ screen?.screen_kind === "detail" &&
101
+ screenConceptStem(screen.id_hint) === sourceStem
102
+ ) || null;
103
+ }
104
+
105
+ /**
106
+ * @param {any} screen
107
+ * @param {any[]} screens
108
+ * @returns {any[]}
109
+ */
110
+ function inferredEventsForWidgetScreen(screen, screens) {
111
+ const detailScreen = matchingDetailScreen(screen, screens);
112
+ if (!detailScreen) {
113
+ return [];
114
+ }
115
+ return [{
116
+ name: "row_select",
117
+ kind: "selection",
118
+ action: "navigate",
119
+ target_screen: detailScreen.id_hint,
120
+ payload_shape: eventPayloadShapeId(screen?.id_hint, "row_select"),
121
+ confidence: "medium",
122
+ evidence: detailScreen.provenance || [],
123
+ payload_fields: routeParamFields(detailScreen.route_path),
124
+ requires_payload_shape_review: true
125
+ }];
126
+ }
127
+
128
+ /**
129
+ * @param {any[]} widgets
130
+ * @returns {any[]}
131
+ */
132
+ function deriveUiWidgetEventShapeCandidates(widgets) {
133
+ return widgets.flatMap((widget) =>
134
+ (widget.inferred_events || [])
135
+ .filter((/** @type {any} */ event) => event.payload_shape)
136
+ .map((/** @type {any} */ event) => makeCandidateRecord({
137
+ kind: "shape",
138
+ idHint: event.payload_shape,
139
+ label: `${widget.label || widget.id_hint} ${event.name || "event"} payload`,
140
+ confidence: event.confidence || widget.confidence || "low",
141
+ sourceKind: "ui_widget_event",
142
+ sourceOfTruth: "candidate",
143
+ provenance: [
144
+ ...(widget.provenance || []),
145
+ ...(event.evidence || [])
146
+ ],
147
+ track: "ui",
148
+ widget_id: widget.id_hint,
149
+ event_name: event.name,
150
+ fields: event.payload_fields || [{ name: "id", field_type: "string", required: true }],
151
+ missing_decisions: [
152
+ "confirm selected row identity fields",
153
+ "confirm event payload shape name"
154
+ ],
155
+ notes: [
156
+ "Imported widget event payload shapes are review-only.",
157
+ "Adopt with the widget when the event binding is accepted."
158
+ ]
159
+ }))
160
+ );
161
+ }
162
+
163
+ /**
164
+ * @param {any} candidates
165
+ * @returns {any[]}
166
+ */
167
+ export function uiWidgetCandidates(candidates) {
168
+ return [
169
+ ...(Array.isArray(candidates?.widgets) ? candidates.widgets : []),
170
+ ...(Array.isArray(candidates?.components) ? candidates.components : [])
171
+ ];
172
+ }
173
+
174
+ /**
175
+ * @param {any} widget
176
+ * @param {Record<string, any>} allCandidates
177
+ * @returns {string|null}
178
+ */
179
+ export function inferredDataSourceForWidget(widget, allCandidates) {
180
+ if (widget.data_source) {
181
+ return widget.data_source;
182
+ }
183
+ const capabilityIds = importedApiCapabilityIds(allCandidates);
184
+ const screenStem = String(widget.screen_id || "")
185
+ .replace(/_(list|index|table|grid|results)$/, "")
186
+ .replace(/^list_/, "");
187
+ return capabilityIds.find((id) => /^cap_(list|get)_/.test(id) && id.includes(screenStem)) ||
188
+ capabilityIds.find((id) => /^cap_(list|get)_/.test(id)) ||
189
+ null;
190
+ }
191
+
192
+ /**
193
+ * @param {any} candidates
194
+ * @returns {any[]}
195
+ */
196
+ function deriveUiWidgetCandidates(candidates) {
197
+ /** @type {any[]} */
198
+ const screens = candidates.screens || [];
199
+ /** @type {any[]} */
200
+ const actions = candidates.actions || [];
201
+ const presentations = [...new Set(actions
202
+ .filter((entry) => entry.kind === "ui_presentation")
203
+ .map((entry) => entry.presentation)
204
+ .filter(Boolean))].sort();
205
+ const widgetScreens = screens.filter((screen) => ["list", "dashboard", "analytics", "report", "feed", "inbox"].includes(screen.screen_kind));
206
+
207
+ return widgetScreens.map((screen) => {
208
+ const pattern = collectionPatternFromPresentations(presentations);
209
+ const widgetStem = idHintify(`${screen.id_hint}_results`);
210
+ const loadCapability = loadCapabilityForScreen(screen);
211
+ const inferredEvents = inferredEventsForWidgetScreen(screen, screens);
212
+ return makeCandidateRecord({
213
+ kind: "widget",
214
+ idHint: `widget_${widgetStem}`,
215
+ label: `${screen.label || screen.id_hint} results`,
216
+ confidence: presentations.length > 0 ? "medium" : "low",
217
+ sourceKind: "ui_projection_inference",
218
+ sourceOfTruth: "candidate",
219
+ provenance: screen.provenance || [],
220
+ screen_id: screen.id_hint,
221
+ region: "results",
222
+ pattern,
223
+ data_prop: "rows",
224
+ data_source: loadCapability,
225
+ inferred_props: [{ name: "rows", type: "array", required: true, source: loadCapability }],
226
+ inferred_events: inferredEvents,
227
+ inferred_region: "results",
228
+ inferred_pattern: pattern,
229
+ evidence: [
230
+ ...(screen.provenance || []),
231
+ ...inferredEvents.flatMap((event) => event.evidence || [])
232
+ ],
233
+ missing_decisions: [
234
+ "confirm widget reuse boundary",
235
+ "confirm prop names and data source",
236
+ "confirm events and behavior",
237
+ "confirm supported regions and patterns"
238
+ ],
239
+ notes: [
240
+ "Imported widget candidates are review-only.",
241
+ "Confirm props, behavior, events, and reuse before adoption."
242
+ ]
243
+ });
244
+ });
245
+ }
246
+
247
+ /**
248
+ * @param {string} track
249
+ * @param {any} candidates
250
+ * @returns {any}
251
+ */
252
+ export function normalizeCandidatesForTrack(track, candidates) {
253
+ /** @param {any} record */
254
+ const idHint = (record) => record.id_hint;
255
+ /** @param {any} record */
256
+ const apiRouteKey = (record) => `${record.method}:${record.path}:${record.source_kind}`;
257
+ /** @param {any} entry */
258
+ const withoutIdHint = ({ id_hint, ...entry }) => entry;
259
+ if (track === "db") {
260
+ return {
261
+ entities: dedupeCandidateRecords(candidates.entities || [], idHint),
262
+ enums: dedupeCandidateRecords(candidates.enums || [], idHint),
263
+ relations: dedupeCandidateRecords(candidates.relations || [], idHint),
264
+ indexes: dedupeCandidateRecords(candidates.indexes || [], idHint)
265
+ };
266
+ }
267
+ if (track === "api") {
268
+ return {
269
+ capabilities: dedupeCandidateRecords(candidates.capabilities || [], idHint),
270
+ routes: dedupeCandidateRecords(
271
+ /** @type {any[]} */ (candidates.routes || []).map((route) => ({ ...route, id_hint: route.id_hint || `${route.method}_${route.path}` })),
272
+ apiRouteKey
273
+ ).map(withoutIdHint),
274
+ stacks: [...new Set(candidates.stacks || [])].sort()
275
+ };
276
+ }
277
+ if (track === "ui") {
278
+ const explicitWidgets = uiWidgetCandidates(candidates);
279
+ const derivedWidgets = deriveUiWidgetCandidates(candidates);
280
+ const widgets = dedupeCandidateRecords([...explicitWidgets, ...derivedWidgets], idHint);
281
+ const eventShapes = deriveUiWidgetEventShapeCandidates(widgets);
282
+ return {
283
+ screens: dedupeCandidateRecords(candidates.screens || [], idHint),
284
+ routes: dedupeCandidateRecords(candidates.routes || [], idHint),
285
+ actions: dedupeCandidateRecords(candidates.actions || [], idHint),
286
+ widgets,
287
+ shapes: dedupeCandidateRecords([...(candidates.shapes || []), ...eventShapes], idHint),
288
+ stacks: [...new Set(candidates.stacks || [])].sort()
289
+ };
290
+ }
291
+ if (track === "verification") {
292
+ return {
293
+ verifications: dedupeCandidateRecords(candidates.verifications || [], idHint),
294
+ scenarios: dedupeCandidateRecords(candidates.scenarios || [], idHint),
295
+ frameworks: [...new Set(candidates.frameworks || [])].sort(),
296
+ scripts: dedupeCandidateRecords(
297
+ /** @type {any[]} */ (candidates.scripts || []).map((script) => ({
298
+ ...script,
299
+ id_hint: script.id_hint || `${script.file || "package.json"}:${script.name || "test"}`
300
+ })),
301
+ idHint
302
+ ).map(withoutIdHint)
303
+ };
304
+ }
305
+ return {
306
+ workflows: dedupeCandidateRecords(candidates.workflows || [], idHint),
307
+ workflow_states: dedupeCandidateRecords(candidates.workflow_states || [], idHint),
308
+ workflow_transitions: dedupeCandidateRecords(candidates.workflow_transitions || [], idHint)
309
+ };
310
+ }
311
+
312
+ /**
313
+ * @param {any} uiCandidates
314
+ * @param {Record<string, any>} allCandidates
315
+ * @returns {any}
316
+ */
317
+ export function enrichUiWidgetDataSources(uiCandidates, allCandidates) {
318
+ if (!uiCandidates) {
319
+ return uiCandidates;
320
+ }
321
+ const widgets = uiWidgetCandidates(uiCandidates);
322
+ const { components, ...canonicalCandidates } = uiCandidates;
323
+ return {
324
+ ...canonicalCandidates,
325
+ widgets: widgets.map((widget) => {
326
+ const dataSource = inferredDataSourceForWidget(widget, allCandidates);
327
+ const dataProp = widget.data_prop || "rows";
328
+ return {
329
+ ...widget,
330
+ data_source: widget.data_source || dataSource,
331
+ inferred_props: /** @type {any[]} */ (widget.inferred_props || []).map((prop) =>
332
+ prop.name === dataProp ? { ...prop, source: prop.source || dataSource } : prop
333
+ )
334
+ };
335
+ })
336
+ };
337
+ }
@@ -0,0 +1,22 @@
1
+ // @ts-check
2
+
3
+ import { IMPORT_TRACKS } from "../contracts.js";
4
+
5
+ /**
6
+ * @param {unknown} fromValue
7
+ * @returns {string[]}
8
+ */
9
+ export 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
+ }
@@ -0,0 +1,51 @@
1
+ // @ts-check
2
+
3
+ import { ensureTrailingNewline } from "../shared.js";
4
+ import { uiWidgetCandidates } from "./candidates.js";
5
+
6
+ /**
7
+ * @param {string} track
8
+ * @param {any} candidates
9
+ * @returns {string}
10
+ */
11
+ export function reportMarkdown(track, candidates) {
12
+ if (track === "db") {
13
+ return ensureTrailingNewline(
14
+ `# 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`
15
+ );
16
+ }
17
+ if (track === "api") {
18
+ return ensureTrailingNewline(
19
+ `# API Import Report\n\n- Capabilities: ${candidates.capabilities.length}\n- Routes: ${candidates.routes.length}\n- Stacks: ${candidates.stacks.length ? candidates.stacks.join(", ") : "none"}\n`
20
+ );
21
+ }
22
+ if (track === "ui") {
23
+ const widgets = uiWidgetCandidates(candidates);
24
+ const shapes = candidates.shapes || [];
25
+ const widgetLines = widgets.map((widget) =>
26
+ `- \`${widget.id_hint}\` confidence ${widget.confidence || "unknown"} pattern \`${widget.pattern || widget.inferred_pattern || "unknown"}\` region \`${widget.region || widget.inferred_region || "unknown"}\` events ${(widget.inferred_events || []).length} evidence ${(widget.evidence || widget.provenance || []).length} missing decisions ${(widget.missing_decisions || []).length}`
27
+ );
28
+ return ensureTrailingNewline(
29
+ `# UI Import Report\n\n- Screens: ${candidates.screens.length}\n- Routes: ${candidates.routes.length}\n- Actions: ${candidates.actions.length}\n- Widgets: ${widgets.length}\n- Event payload shapes: ${shapes.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 \`topo/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`
30
+ );
31
+ }
32
+ if (track === "verification") {
33
+ return ensureTrailingNewline(
34
+ `# 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`
35
+ );
36
+ }
37
+ return ensureTrailingNewline(
38
+ `# Workflow Import Report\n\n- Workflows: ${candidates.workflows.length}\n- States: ${candidates.workflow_states.length}\n- Transitions: ${candidates.workflow_transitions.length}\n`
39
+ );
40
+ }
41
+
42
+ /**
43
+ * @param {Record<string, any>} candidates
44
+ * @param {string[]} tracks
45
+ * @returns {string}
46
+ */
47
+ export function appReportMarkdown(candidates, tracks) {
48
+ return ensureTrailingNewline(
49
+ `# 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- Event payload shapes: ${candidates.ui?.shapes?.length || 0}\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`
50
+ );
51
+ }
@@ -0,0 +1,79 @@
1
+ // @ts-check
2
+
3
+ import { createImportContext } from "../context.js";
4
+ import { enrichUiWidgetDataSources } from "./candidates.js";
5
+ import { parseImportTracks } from "./options.js";
6
+ import { appReportMarkdown, reportMarkdown } from "./reports.js";
7
+ import { runTrack } from "./tracks.js";
8
+ import { draftUiProjectionFiles } from "./ui-drafts.js";
9
+
10
+ /**
11
+ * @param {string} inputPath
12
+ * @param {Record<string, any>} [options]
13
+ * @returns {{ summary: any, files: Record<string, string>, defaultOutDir: string }}
14
+ */
15
+ export function runImportApp(inputPath, options = {}) {
16
+ const tracks = parseImportTracks(options.from);
17
+ const context = createImportContext(inputPath, options);
18
+ /** @type {Record<string, ReturnType<typeof runTrack>>} */
19
+ const resultsByTrack = {};
20
+ context.priorResults = resultsByTrack;
21
+ context.scanDocsSummary = options.scanDocsSummary || null;
22
+ /** @type {Record<string, any[]>} */
23
+ const findings = {};
24
+ /** @type {Record<string, any>} */
25
+ const candidates = {};
26
+ /** @type {Record<string, string>} */
27
+ const files = {};
28
+
29
+ for (const track of tracks) {
30
+ if (track === "workflows") {
31
+ if (!resultsByTrack.db) {
32
+ resultsByTrack.db = runTrack(context, "db");
33
+ }
34
+ if (!resultsByTrack.api) {
35
+ resultsByTrack.api = runTrack(context, "api");
36
+ }
37
+ }
38
+ if (track === "verification") {
39
+ if (!resultsByTrack.api) {
40
+ resultsByTrack.api = runTrack(context, "api");
41
+ }
42
+ }
43
+ const result = runTrack(context, track);
44
+ resultsByTrack[track] = result;
45
+ findings[track] = result.findings;
46
+ candidates[track] = result.candidates;
47
+ files[`candidates/app/${track}/findings.json`] = `${JSON.stringify(result.findings, null, 2)}\n`;
48
+ files[`candidates/app/${track}/candidates.json`] = `${JSON.stringify(result.candidates, null, 2)}\n`;
49
+ files[`candidates/app/${track}/report.md`] = reportMarkdown(track, result.candidates);
50
+ }
51
+
52
+ if (candidates.ui) {
53
+ candidates.ui = enrichUiWidgetDataSources(candidates.ui, candidates);
54
+ files["candidates/app/ui/candidates.json"] = `${JSON.stringify(candidates.ui, null, 2)}\n`;
55
+ files["candidates/app/ui/report.md"] = reportMarkdown("ui", candidates.ui);
56
+ Object.assign(files, draftUiProjectionFiles(context, candidates.ui, candidates));
57
+ }
58
+
59
+ const summary = {
60
+ type: "import_app_report",
61
+ workspace: context.paths.workspaceRoot,
62
+ topogram_root: context.paths.topogramRoot,
63
+ bootstrapped_topogram_root: context.paths.bootstrappedTopogramRoot,
64
+ tracks,
65
+ findings_count: Object.values(findings).reduce((total, entries) => total + entries.length, 0),
66
+ extractor_detections: Object.fromEntries(Object.entries(resultsByTrack).map(([track, result]) => [track, result.extractor_detections])),
67
+ candidates
68
+ };
69
+
70
+ files["candidates/app/findings.json"] = `${JSON.stringify(findings, null, 2)}\n`;
71
+ files["candidates/app/candidates.json"] = `${JSON.stringify(candidates, null, 2)}\n`;
72
+ files["candidates/app/report.md"] = appReportMarkdown(candidates, tracks);
73
+
74
+ return {
75
+ summary,
76
+ files,
77
+ defaultOutDir: context.paths.topogramRoot
78
+ };
79
+ }