@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,493 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ IDENTIFIER_PATTERN,
5
+ UI_APP_SHELL_KINDS,
6
+ UI_COLLECTION_PRESENTATIONS,
7
+ UI_DESIGN_ACCESSIBILITY_VALUES,
8
+ UI_DESIGN_ACTION_ROLES,
9
+ UI_DESIGN_COLOR_ROLES,
10
+ UI_DESIGN_DENSITIES,
11
+ UI_DESIGN_RADIUS_SCALES,
12
+ UI_DESIGN_TONES,
13
+ UI_DESIGN_TYPOGRAPHY_ROLES,
14
+ UI_NAVIGATION_PATTERNS,
15
+ UI_PATTERN_KINDS,
16
+ UI_REGION_KINDS,
17
+ UI_SCREEN_KINDS,
18
+ UI_STATE_KINDS,
19
+ UI_WINDOWING_MODES
20
+ } from "../kinds.js";
21
+ import {
22
+ blockEntries,
23
+ blockSymbolItems,
24
+ getFieldValue,
25
+ pushError,
26
+ symbolValue,
27
+ symbolValues
28
+ } from "../utils.js";
29
+ import {
30
+ collectAvailableUiRegionKeys,
31
+ collectAvailableUiRegionPatterns,
32
+ collectAvailableUiScreenIds,
33
+ collectProjectionUiScreens,
34
+ resolveProjectionUiScreenFieldNames
35
+ } from "./ui-helpers.js";
36
+ import {
37
+ resolveShapeBaseFieldNames,
38
+ statementFieldNames
39
+ } from "../model-helpers.js";
40
+ import {
41
+ parseUiDirectiveMap,
42
+ resolveCapabilityContractFields,
43
+ resolveCapabilityOutputShape
44
+ } from "./helpers.js";
45
+
46
+ /**
47
+ * @param {ValidationErrors} errors
48
+ * @param {TopogramStatement} statement
49
+ * @param {TopogramFieldMap} fieldMap
50
+ * @param {TopogramRegistry} registry
51
+ * @returns {void}
52
+ */
53
+ export function validateProjectionUiCollections(errors, statement, fieldMap, registry) {
54
+ if (statement.kind !== "projection") {
55
+ return;
56
+ }
57
+
58
+ const collectionsField = fieldMap.get("collection_views")?.[0];
59
+ if (!collectionsField || collectionsField.value.type !== "block") {
60
+ return;
61
+ }
62
+
63
+ const screens = collectProjectionUiScreens(statement, fieldMap);
64
+ for (const entry of collectionsField.value.entries) {
65
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
66
+ const [keyword, screenId, operation, value, extra] = tokens;
67
+
68
+ if (keyword !== "screen") {
69
+ pushError(errors, `Projection ${statement.id} collection_views entries must start with 'screen'`, entry.loc);
70
+ continue;
71
+ }
72
+ const screenEntry = screens.get(screenId);
73
+ if (!screenEntry) {
74
+ pushError(errors, `Projection ${statement.id} collection_views references unknown screen '${screenId}'`, entry.loc);
75
+ continue;
76
+ }
77
+
78
+ const screenTokens = blockSymbolItems(screenEntry).map((item) => item.value);
79
+ const screenDirectives = parseUiDirectiveMap(screenTokens, 2, [], statement, screenEntry, "");
80
+ if (screenDirectives.get("kind") !== "list") {
81
+ pushError(errors, `Projection ${statement.id} collection_views may only target list screens, found '${screenId}'`, entry.loc);
82
+ }
83
+
84
+ if (!["filter", "search", "pagination", "sort", "group", "view", "refresh"].includes(operation)) {
85
+ pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' has invalid operation '${operation}'`, entry.loc);
86
+ continue;
87
+ }
88
+
89
+ const loadCapabilityId = screenDirectives.get("load");
90
+ const inputFields = loadCapabilityId ? resolveCapabilityContractFields(registry, loadCapabilityId, "input") : new Set();
91
+ const outputShape = loadCapabilityId ? resolveCapabilityOutputShape(registry, loadCapabilityId) : null;
92
+ const outputFields = outputShape
93
+ ? new Set((statementFieldNames(outputShape).length > 0 ? statementFieldNames(outputShape) : resolveShapeBaseFieldNames(outputShape, registry)))
94
+ : new Set();
95
+
96
+ if (operation === "filter" || operation === "search") {
97
+ if (!value) {
98
+ pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' must include a field for '${operation}'`, entry.loc);
99
+ } else if (inputFields.size > 0 && !inputFields.has(value)) {
100
+ pushError(errors, `Projection ${statement.id} collection_views references unknown input field '${value}' for '${operation}' on '${screenId}'`, entry.loc);
101
+ }
102
+ }
103
+
104
+ if (operation === "pagination" && !["cursor", "paged", "none"].includes(value || "")) {
105
+ pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' has invalid pagination '${value}'`, entry.loc);
106
+ }
107
+
108
+ if (operation === "sort") {
109
+ if (!value || !extra) {
110
+ pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' must use 'sort <field> <asc|desc>'`, entry.loc);
111
+ } else {
112
+ if (!["asc", "desc"].includes(extra)) {
113
+ pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' has invalid sort direction '${extra}'`, entry.loc);
114
+ }
115
+ if (outputFields.size > 0 && !outputFields.has(value)) {
116
+ pushError(errors, `Projection ${statement.id} collection_views references unknown output field '${value}' for sort on '${screenId}'`, entry.loc);
117
+ }
118
+ }
119
+ }
120
+
121
+ if (operation === "group") {
122
+ if (!value) {
123
+ pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' must include a field for 'group'`, entry.loc);
124
+ } else if (outputFields.size > 0 && !outputFields.has(value)) {
125
+ pushError(errors, `Projection ${statement.id} collection_views references unknown output field '${value}' for group on '${screenId}'`, entry.loc);
126
+ }
127
+ }
128
+
129
+ if (operation === "view" && !UI_COLLECTION_PRESENTATIONS.has(value || "")) {
130
+ pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' has invalid view '${value}'`, entry.loc);
131
+ }
132
+
133
+ if (operation === "refresh" && !["manual", "pull_to_refresh", "auto"].includes(value || "")) {
134
+ pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' has invalid refresh '${value}'`, entry.loc);
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * @param {ValidationErrors} errors
141
+ * @param {TopogramStatement} statement
142
+ * @param {TopogramFieldMap} fieldMap
143
+ * @param {TopogramRegistry} registry
144
+ * @returns {void}
145
+ */
146
+
147
+ export function validateProjectionUiActions(errors, statement, fieldMap, registry) {
148
+ if (statement.kind !== "projection") {
149
+ return;
150
+ }
151
+
152
+ const actionsField = fieldMap.get("screen_actions")?.[0];
153
+ if (!actionsField || actionsField.value.type !== "block") {
154
+ return;
155
+ }
156
+
157
+ const screens = collectProjectionUiScreens(statement, fieldMap);
158
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
159
+
160
+ for (const entry of actionsField.value.entries) {
161
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
162
+ const [keyword, screenId, actionKeyword, capabilityId, prominenceKeyword, prominence, placementKeyword, placement] = tokens;
163
+
164
+ if (keyword !== "screen") {
165
+ pushError(errors, `Projection ${statement.id} screen_actions entries must start with 'screen'`, entry.loc);
166
+ continue;
167
+ }
168
+ if (!screens.has(screenId)) {
169
+ pushError(errors, `Projection ${statement.id} screen_actions references unknown screen '${screenId}'`, entry.loc);
170
+ }
171
+ if (actionKeyword !== "action") {
172
+ pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' must use 'action'`, entry.loc);
173
+ }
174
+ const capability = registry.get(capabilityId);
175
+ if (!capability) {
176
+ pushError(errors, `Projection ${statement.id} screen_actions references missing capability '${capabilityId}'`, entry.loc);
177
+ } else if (capability.kind !== "capability") {
178
+ pushError(errors, `Projection ${statement.id} screen_actions must reference a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
179
+ } else if (!realized.has(capabilityId)) {
180
+ pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' capability '${capabilityId}' must also appear in 'realizes'`, entry.loc);
181
+ }
182
+ if (prominenceKeyword !== "prominence") {
183
+ pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' must use 'prominence'`, entry.loc);
184
+ }
185
+ if (!["primary", "secondary", "destructive", "contextual"].includes(prominence || "")) {
186
+ pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' has invalid prominence '${prominence}'`, entry.loc);
187
+ }
188
+ if (placementKeyword && placementKeyword !== "placement") {
189
+ pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' has unknown directive '${placementKeyword}'`, entry.loc);
190
+ }
191
+ if (placementKeyword === "placement" && !["toolbar", "menu", "bulk", "inline", "footer"].includes(placement || "")) {
192
+ pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' has invalid placement '${placement}'`, entry.loc);
193
+ }
194
+ }
195
+ }
196
+
197
+ /**
198
+ * @param {ValidationErrors} errors
199
+ * @param {TopogramStatement} statement
200
+ * @param {TopogramFieldMap} fieldMap
201
+ * @param {TopogramRegistry} registry
202
+ * @returns {void}
203
+ */
204
+
205
+ export function validateProjectionUiComponents(errors, statement, fieldMap, registry) {
206
+ if (statement.kind !== "projection") {
207
+ return;
208
+ }
209
+
210
+ const componentsField = fieldMap.get("widget_bindings")?.[0];
211
+ if (!componentsField || componentsField.value.type !== "block") {
212
+ return;
213
+ }
214
+
215
+ if (symbolValue(getFieldValue(statement, "type")) !== "ui_contract") {
216
+ pushError(errors, `Projection ${statement.id} widget_bindings belongs on shared UI projections; concrete UI projections inherit widget placement through 'realizes'`, componentsField.loc);
217
+ }
218
+
219
+ const availableScreens = collectAvailableUiScreenIds(statement, fieldMap, registry);
220
+ const availableRegions = collectAvailableUiRegionKeys(statement, registry);
221
+ const availableRegionPatterns = collectAvailableUiRegionPatterns(statement, registry);
222
+
223
+ for (const entry of componentsField.value.entries) {
224
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
225
+ const [screenKeyword, screenId, regionKeyword, regionName, componentKeyword, componentId] = tokens;
226
+
227
+ if (screenKeyword !== "screen") {
228
+ pushError(errors, `Projection ${statement.id} widget_bindings entries must start with 'screen'`, entry.loc);
229
+ continue;
230
+ }
231
+ if (!availableScreens.has(screenId)) {
232
+ pushError(errors, `Projection ${statement.id} widget_bindings references unknown screen '${screenId}'`, entry.loc);
233
+ }
234
+ if (regionKeyword !== "region") {
235
+ pushError(errors, `Projection ${statement.id} widget_bindings for '${screenId}' must use 'region'`, entry.loc);
236
+ }
237
+ if (!UI_REGION_KINDS.has(regionName || "")) {
238
+ pushError(errors, `Projection ${statement.id} widget_bindings for '${screenId}' has invalid region '${regionName}'`, entry.loc);
239
+ } else if (!availableRegions.has(`${screenId}:${regionName}`)) {
240
+ pushError(errors, `Projection ${statement.id} widget_bindings for '${screenId}' references undeclared region '${regionName}'`, entry.loc);
241
+ }
242
+ if (componentKeyword !== "widget") {
243
+ pushError(errors, `Projection ${statement.id} widget_bindings for '${screenId}' must use 'widget'`, entry.loc);
244
+ }
245
+
246
+ const widget = registry.get(componentId);
247
+ if (!widget) {
248
+ pushError(errors, `Projection ${statement.id} widget_bindings references missing widget '${componentId}'`, entry.loc);
249
+ continue;
250
+ }
251
+ if (widget.kind !== "widget") {
252
+ pushError(errors, `Projection ${statement.id} widget_bindings must reference a widget, found ${widget.kind} '${widget.id}'`, entry.loc);
253
+ continue;
254
+ }
255
+
256
+ const propNames = new Set(blockEntries(getFieldValue(widget, "props"))
257
+ .map((propEntry) => propEntry.items[0])
258
+ .filter((item) => item?.type === "symbol")
259
+ .map((item) => item.value));
260
+ const eventNames = new Set(blockEntries(getFieldValue(widget, "events"))
261
+ .map((eventEntry) => eventEntry.items[0])
262
+ .filter((item) => item?.type === "symbol")
263
+ .map((item) => item.value));
264
+ const componentRegions = symbolValues(getFieldValue(widget, "regions"));
265
+ const componentPatterns = symbolValues(getFieldValue(widget, "patterns"));
266
+ if (componentRegions.length > 0 && !componentRegions.includes(regionName)) {
267
+ pushError(
268
+ errors,
269
+ `Projection ${statement.id} widget_bindings uses widget '${componentId}' in region '${regionName}', but the widget supports regions [${componentRegions.join(", ")}]`,
270
+ entry.loc
271
+ );
272
+ }
273
+ const regionPattern = availableRegionPatterns.get(`${screenId}:${regionName}`) || null;
274
+ if (regionPattern && componentPatterns.length > 0 && !componentPatterns.includes(regionPattern)) {
275
+ pushError(
276
+ errors,
277
+ `Projection ${statement.id} widget_bindings uses widget '${componentId}' in '${screenId}:${regionName}' with pattern '${regionPattern}', but the widget supports patterns [${componentPatterns.join(", ")}]`,
278
+ entry.loc
279
+ );
280
+ }
281
+
282
+ for (let i = 6; i < tokens.length;) {
283
+ const directive = tokens[i];
284
+ if (directive === "data") {
285
+ const propName = tokens[i + 1];
286
+ const fromKeyword = tokens[i + 2];
287
+ const sourceId = tokens[i + 3];
288
+ if (!propName || fromKeyword !== "from" || !sourceId) {
289
+ pushError(errors, `Projection ${statement.id} widget_bindings data bindings must use 'data <prop> from <source>'`, entry.loc);
290
+ break;
291
+ }
292
+ if (!propNames.has(propName)) {
293
+ pushError(errors, `Projection ${statement.id} widget_bindings references unknown prop '${propName}' on widget '${componentId}'`, entry.loc);
294
+ }
295
+ const source = registry.get(sourceId);
296
+ if (!source || !["capability", "projection", "shape", "entity"].includes(source.kind)) {
297
+ pushError(errors, `Projection ${statement.id} widget_bindings data binding for '${propName}' references missing source '${sourceId}'`, entry.loc);
298
+ }
299
+ i += 4;
300
+ continue;
301
+ }
302
+
303
+ if (directive === "event") {
304
+ const eventName = tokens[i + 1];
305
+ const action = tokens[i + 2];
306
+ const targetId = tokens[i + 3];
307
+ if (!eventName || !action || !targetId) {
308
+ pushError(errors, `Projection ${statement.id} widget_bindings event bindings must use 'event <event> <navigate|action> <target>'`, entry.loc);
309
+ break;
310
+ }
311
+ if (!eventNames.has(eventName)) {
312
+ pushError(errors, `Projection ${statement.id} widget_bindings references unknown event '${eventName}' on widget '${componentId}'`, entry.loc);
313
+ }
314
+ if (action === "navigate") {
315
+ if (!availableScreens.has(targetId)) {
316
+ pushError(errors, `Projection ${statement.id} widget_bindings event '${eventName}' references unknown navigation target '${targetId}'`, entry.loc);
317
+ }
318
+ } else if (action === "action") {
319
+ const target = registry.get(targetId);
320
+ if (!target || target.kind !== "capability") {
321
+ pushError(errors, `Projection ${statement.id} widget_bindings event '${eventName}' references missing capability action '${targetId}'`, entry.loc);
322
+ }
323
+ } else {
324
+ pushError(errors, `Projection ${statement.id} widget_bindings event '${eventName}' has unsupported action '${action}'`, entry.loc);
325
+ }
326
+ i += 4;
327
+ continue;
328
+ }
329
+
330
+ pushError(errors, `Projection ${statement.id} widget_bindings has unknown directive '${directive}'`, entry.loc);
331
+ break;
332
+ }
333
+ }
334
+ }
335
+
336
+ /**
337
+ * @param {ValidationErrors} errors
338
+ * @param {TopogramStatement} statement
339
+ * @param {TopogramFieldMap} fieldMap
340
+ * @param {TopogramRegistry} registry
341
+ * @returns {void}
342
+ */
343
+
344
+ export function validateProjectionUiVisibility(errors, statement, fieldMap, registry) {
345
+ if (statement.kind !== "projection") {
346
+ return;
347
+ }
348
+
349
+ const visibilityField = fieldMap.get("visibility_rules")?.[0];
350
+ if (!visibilityField || visibilityField.value.type !== "block") {
351
+ return;
352
+ }
353
+
354
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
355
+ for (const entry of visibilityField.value.entries) {
356
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
357
+ const [keyword, capabilityId, predicateKeyword, predicateType, predicateValue] = tokens;
358
+
359
+ if (keyword !== "action") {
360
+ pushError(errors, `Projection ${statement.id} visibility_rules entries must start with 'action'`, entry.loc);
361
+ continue;
362
+ }
363
+
364
+ const capability = registry.get(capabilityId);
365
+ if (!capability) {
366
+ pushError(errors, `Projection ${statement.id} visibility_rules references missing capability '${capabilityId}'`, entry.loc);
367
+ } else if (capability.kind !== "capability") {
368
+ pushError(errors, `Projection ${statement.id} visibility_rules must reference a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
369
+ } else if (!realized.has(capabilityId)) {
370
+ pushError(errors, `Projection ${statement.id} visibility_rules action '${capabilityId}' must also appear in 'realizes'`, entry.loc);
371
+ }
372
+
373
+ if (predicateKeyword !== "visible_if") {
374
+ pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' must use 'visible_if'`, entry.loc);
375
+ }
376
+ if (!["permission", "ownership", "claim"].includes(predicateType || "")) {
377
+ pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' has invalid predicate '${predicateType}'`, entry.loc);
378
+ }
379
+ if (!predicateValue) {
380
+ pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' must include a predicate value`, entry.loc);
381
+ }
382
+ if (predicateType === "ownership" && !["owner", "owner_or_admin", "project_member", "none"].includes(predicateValue || "")) {
383
+ pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' has invalid ownership '${predicateValue}'`, entry.loc);
384
+ }
385
+ const directiveTokens = blockSymbolItems(entry).map((item) => item.value);
386
+ const directives = new Map();
387
+ for (let i = 5; i < directiveTokens.length; i += 2) {
388
+ const key = directiveTokens[i];
389
+ const value = directiveTokens[i + 1];
390
+ if (!value) {
391
+ pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
392
+ continue;
393
+ }
394
+ directives.set(key, value);
395
+ }
396
+ for (const key of directives.keys()) {
397
+ if (!["claim_value"].includes(key)) {
398
+ pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
399
+ }
400
+ }
401
+ if (directives.get("claim_value") && predicateType !== "claim") {
402
+ pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' cannot declare claim_value without claim`, entry.loc);
403
+ }
404
+ }
405
+ }
406
+
407
+ /**
408
+ * @param {ValidationErrors} errors
409
+ * @param {TopogramStatement} statement
410
+ * @param {TopogramFieldMap} fieldMap
411
+ * @param {TopogramRegistry} registry
412
+ * @returns {void}
413
+ */
414
+
415
+ export function validateProjectionUiLookups(errors, statement, fieldMap, registry) {
416
+ if (statement.kind !== "projection") {
417
+ return;
418
+ }
419
+
420
+ const lookupsField = fieldMap.get("field_lookups")?.[0];
421
+ if (!lookupsField || lookupsField.value.type !== "block") {
422
+ return;
423
+ }
424
+
425
+ const screens = collectProjectionUiScreens(statement, fieldMap);
426
+
427
+ for (const entry of lookupsField.value.entries) {
428
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
429
+ const [keyword, screenId, fieldKeyword, fieldName, entityKeyword, entityId, labelKeyword, labelField, maybeEmptyKeyword, maybeEmptyLabel] = tokens;
430
+
431
+ if (keyword !== "screen") {
432
+ pushError(errors, `Projection ${statement.id} field_lookups entries must start with 'screen'`, entry.loc);
433
+ continue;
434
+ }
435
+
436
+ const screenEntry = screens.get(screenId);
437
+ if (!screenEntry) {
438
+ pushError(errors, `Projection ${statement.id} field_lookups references unknown screen '${screenId}'`, entry.loc);
439
+ continue;
440
+ }
441
+
442
+ if (fieldKeyword !== "field") {
443
+ pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must use 'field'`, entry.loc);
444
+ }
445
+ if (!fieldName) {
446
+ pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must include a field name`, entry.loc);
447
+ }
448
+
449
+ if (entityKeyword !== "entity") {
450
+ pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must use 'entity'`, entry.loc);
451
+ }
452
+ const entity = entityId ? registry.get(entityId) : null;
453
+ if (!entity) {
454
+ pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' references missing entity '${entityId}'`, entry.loc);
455
+ } else if (entity.kind !== "entity") {
456
+ pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must reference an entity, found ${entity.kind} '${entity.id}'`, entry.loc);
457
+ }
458
+
459
+ if (labelKeyword !== "label_field") {
460
+ pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must use 'label_field'`, entry.loc);
461
+ }
462
+ if (!labelField) {
463
+ pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must include a label_field`, entry.loc);
464
+ }
465
+
466
+ if (maybeEmptyKeyword && maybeEmptyKeyword !== "empty_label") {
467
+ pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' has unknown directive '${maybeEmptyKeyword}'`, entry.loc);
468
+ }
469
+ if (maybeEmptyKeyword === "empty_label" && !maybeEmptyLabel) {
470
+ pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must include a value for 'empty_label'`, entry.loc);
471
+ }
472
+
473
+ const availableFields = resolveProjectionUiScreenFieldNames(registry, screenEntry, statement);
474
+ if (fieldName && availableFields.size > 0 && !availableFields.has(fieldName)) {
475
+ pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' references unknown screen field '${fieldName}'`, entry.loc);
476
+ }
477
+
478
+ if (entity?.kind === "entity") {
479
+ const entityFieldNames = new Set(statementFieldNames(entity));
480
+ if (labelField && !entityFieldNames.has(labelField)) {
481
+ pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' references unknown entity field '${labelField}' on '${entity.id}'`, entry.loc);
482
+ }
483
+ }
484
+ }
485
+ }
486
+
487
+ /**
488
+ * @param {ValidationErrors} errors
489
+ * @param {TopogramStatement} statement
490
+ * @param {TopogramFieldMap} fieldMap
491
+ * @param {TopogramRegistry} registry
492
+ * @returns {void}
493
+ */
@@ -0,0 +1,46 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ validateProjectionUiOwnership,
5
+ validateProjectionUiScreens,
6
+ validateProjectionUiAppShell,
7
+ validateProjectionUiDesign
8
+ } from "./ui-structure.js";
9
+ import {
10
+ validateProjectionUiNavigation,
11
+ validateProjectionUiScreenRegions,
12
+ validateProjectionUiRoutes,
13
+ validateProjectionUiWeb,
14
+ validateProjectionUiIos
15
+ } from "./ui-navigation.js";
16
+ import {
17
+ validateProjectionUiCollections,
18
+ validateProjectionUiActions,
19
+ validateProjectionUiComponents,
20
+ validateProjectionUiVisibility,
21
+ validateProjectionUiLookups
22
+ } from "./ui-widgets.js";
23
+
24
+ /**
25
+ * @param {ValidationErrors} errors
26
+ * @param {TopogramStatement} statement
27
+ * @param {TopogramFieldMap} fieldMap
28
+ * @param {TopogramRegistry} registry
29
+ * @returns {void}
30
+ */
31
+ export function validateUiProjection(errors, statement, fieldMap, registry) {
32
+ validateProjectionUiOwnership(errors, statement, fieldMap);
33
+ validateProjectionUiScreens(errors, statement, fieldMap, registry);
34
+ validateProjectionUiCollections(errors, statement, fieldMap, registry);
35
+ validateProjectionUiActions(errors, statement, fieldMap, registry);
36
+ validateProjectionUiVisibility(errors, statement, fieldMap, registry);
37
+ validateProjectionUiLookups(errors, statement, fieldMap, registry);
38
+ validateProjectionUiRoutes(errors, statement, fieldMap, registry);
39
+ validateProjectionUiAppShell(errors, statement, fieldMap);
40
+ validateProjectionUiDesign(errors, statement, fieldMap);
41
+ validateProjectionUiNavigation(errors, statement, fieldMap, registry);
42
+ validateProjectionUiScreenRegions(errors, statement, fieldMap, registry);
43
+ validateProjectionUiComponents(errors, statement, fieldMap, registry);
44
+ validateProjectionUiWeb(errors, statement, fieldMap, registry);
45
+ validateProjectionUiIos(errors, statement, fieldMap, registry);
46
+ }
@@ -1 +1,48 @@
1
- export { buildRegistry } from "./index.js";
1
+ // @ts-check
2
+
3
+ import { IDENTIFIER_PATTERN, STATEMENT_KINDS } from "./kinds.js";
4
+ import { pushError } from "./utils.js";
5
+
6
+ /**
7
+ * @param {string} oldName
8
+ * @param {string} newName
9
+ * @param {string} example
10
+ * @returns {string}
11
+ */
12
+ function renameDiagnostic(oldName, newName, example) {
13
+ return `${oldName} was renamed to ${newName}. Example fix: ${example}`;
14
+ }
15
+
16
+ /**
17
+ * @param {import("../parser.js").WorkspaceAst} workspaceAst
18
+ * @param {ValidationErrors} errors
19
+ * @returns {TopogramRegistry}
20
+ */
21
+ export function buildRegistry(workspaceAst, errors) {
22
+ const registry = new Map();
23
+
24
+ for (const file of workspaceAst.files) {
25
+ for (const statement of file.statements) {
26
+ if (!STATEMENT_KINDS.has(statement.kind)) {
27
+ if (statement.kind === "component") {
28
+ pushError(errors, `Statement kind ${renameDiagnostic("'component'", "'widget'", "widget widget_data_grid { ... }")}`, statement.loc);
29
+ } else {
30
+ pushError(errors, `Unknown statement kind '${statement.kind}'`, statement.loc);
31
+ }
32
+ }
33
+
34
+ if (!IDENTIFIER_PATTERN.test(statement.id)) {
35
+ pushError(errors, `Invalid identifier '${statement.id}'`, statement.loc);
36
+ }
37
+
38
+ if (registry.has(statement.id)) {
39
+ pushError(errors, `Duplicate statement id '${statement.id}'`, statement.loc);
40
+ continue;
41
+ }
42
+
43
+ registry.set(statement.id, statement);
44
+ }
45
+ }
46
+
47
+ return registry;
48
+ }
@@ -0,0 +1,20 @@
1
+ import type {
2
+ TopogramBlockEntry,
3
+ TopogramField,
4
+ TopogramFieldMap,
5
+ TopogramStatement,
6
+ TopogramToken,
7
+ ValidationErrors
8
+ } from "../topogram-types.js";
9
+
10
+ export function blockEntries(value: TopogramToken | null | undefined): TopogramBlockEntry[];
11
+ export function blockSymbolItems(entry: TopogramBlockEntry): TopogramToken[];
12
+ export function collectFieldMap(statement: TopogramStatement): TopogramFieldMap;
13
+ export function formatLoc(loc: any): string;
14
+ export function getField(statement: TopogramStatement, key: string): TopogramField | null;
15
+ export function getFieldValue(statement: TopogramStatement, key: string): TopogramToken | null;
16
+ export function pushError(errors: ValidationErrors, message: string, loc?: any): void;
17
+ export function stringValue(value: TopogramToken | null | undefined): string | null;
18
+ export function symbolValue(value: TopogramToken | null | undefined): string | null;
19
+ export function symbolValues(value: TopogramToken | null | undefined): string[];
20
+ export function valueAsArray(value: TopogramToken | null | undefined): TopogramToken[];