@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,87 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ blockSymbolItems,
5
+ getFieldValue,
6
+ pushError,
7
+ symbolValues
8
+ } from "../utils.js";
9
+ import {
10
+ resolveShapeBaseFieldNames,
11
+ statementFieldNames
12
+ } from "../model-helpers.js";
13
+
14
+ /**
15
+ * @param {TopogramRegistry} registry
16
+ * @param {string} capabilityId
17
+ * @param {string} direction
18
+ * @returns {Set<string>}
19
+ */
20
+ export function resolveCapabilityContractFields(registry, capabilityId, direction) {
21
+ const capability = registry.get(capabilityId);
22
+ if (!capability || capability.kind !== "capability") {
23
+ return new Set();
24
+ }
25
+
26
+ const refsField = direction === "input" ? getFieldValue(capability, "input") : getFieldValue(capability, "output");
27
+ const shapeId = symbolValues(refsField)[0];
28
+ if (!shapeId) {
29
+ return new Set();
30
+ }
31
+
32
+ const shape = registry.get(shapeId);
33
+ if (!shape || shape.kind !== "shape") {
34
+ return new Set();
35
+ }
36
+
37
+ const explicitFields = statementFieldNames(shape);
38
+ if (explicitFields.length > 0) {
39
+ return new Set(explicitFields);
40
+ }
41
+
42
+ return new Set(resolveShapeBaseFieldNames(shape, registry));
43
+ }
44
+
45
+ /**
46
+ * @param {TopogramRegistry} registry
47
+ * @param {string} capabilityId
48
+ * @returns {TopogramStatement | null}
49
+ */
50
+ export function resolveCapabilityOutputShape(registry, capabilityId) {
51
+ const capability = registry.get(capabilityId);
52
+ if (!capability || capability.kind !== "capability") {
53
+ return null;
54
+ }
55
+
56
+ const shapeId = symbolValues(getFieldValue(capability, "output"))[0];
57
+ const shape = shapeId ? registry.get(shapeId) : null;
58
+ return shape?.kind === "shape" ? shape : null;
59
+ }
60
+
61
+ /**
62
+ * @param {string[]} tokens
63
+ * @param {number} startIndex
64
+ * @param {ValidationErrors} errors
65
+ * @param {TopogramStatement} statement
66
+ * @param {TopogramBlockEntry} entry
67
+ * @param {string} context
68
+ * @returns {Map<string, string>}
69
+ */
70
+ export function parseUiDirectiveMap(tokens, startIndex, errors, statement, entry, context) {
71
+ const directives = new Map();
72
+
73
+ for (let i = startIndex; i < tokens.length; i += 2) {
74
+ const key = tokens[i];
75
+ const value = tokens[i + 1];
76
+ if (!key) {
77
+ continue;
78
+ }
79
+ if (!value) {
80
+ pushError(errors, `Projection ${statement.id} ${context} is missing a value for '${key}'`, entry.loc);
81
+ continue;
82
+ }
83
+ directives.set(key, value);
84
+ }
85
+
86
+ return directives;
87
+ }
@@ -0,0 +1,214 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ blockEntries,
5
+ blockSymbolItems,
6
+ getFieldValue,
7
+ symbolValues
8
+ } from "../utils.js";
9
+ import {
10
+ resolveShapeBaseFieldNames,
11
+ statementFieldNames
12
+ } from "../model-helpers.js";
13
+ import {
14
+ parseUiDirectiveMap,
15
+ resolveCapabilityContractFields
16
+ } from "./helpers.js";
17
+
18
+ /**
19
+ * @param {TopogramStatement} statement
20
+ * @param {TopogramFieldMap} fieldMap
21
+ * @returns {Map<string, TopogramBlockEntry>}
22
+ */
23
+ export function collectProjectionUiScreens(statement, fieldMap) {
24
+ const screensField = fieldMap.get("screens")?.[0];
25
+ if (!screensField || screensField.value.type !== "block") {
26
+ return new Map();
27
+ }
28
+
29
+ const screens = new Map();
30
+ for (const entry of screensField.value.entries) {
31
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
32
+ if (tokens[0] === "screen" && tokens[1]) {
33
+ screens.set(tokens[1], entry);
34
+ }
35
+ }
36
+ return screens;
37
+ }
38
+
39
+ /**
40
+ * @param {TopogramRegistry} registry
41
+ * @param {TopogramBlockEntry} screenEntry
42
+ * @param {TopogramStatement} statement
43
+ * @returns {Set<string>}
44
+ */
45
+
46
+ export function resolveProjectionUiScreenFieldNames(registry, screenEntry, statement) {
47
+ const tokens = blockSymbolItems(screenEntry).map((item) => item.value);
48
+ const directives = parseUiDirectiveMap(tokens, 2, [], statement, screenEntry, "");
49
+ const kind = directives.get("kind");
50
+
51
+ if (kind === "form") {
52
+ const shapeId = directives.get("input_shape");
53
+ const shape = shapeId ? registry.get(shapeId) : null;
54
+ if (!shape || shape.kind !== "shape") {
55
+ return new Set();
56
+ }
57
+ const explicitFields = statementFieldNames(shape);
58
+ return new Set(explicitFields.length > 0 ? explicitFields : resolveShapeBaseFieldNames(shape, registry));
59
+ }
60
+
61
+ if (kind === "list") {
62
+ const loadCapabilityId = directives.get("load");
63
+ return loadCapabilityId ? resolveCapabilityContractFields(registry, loadCapabilityId, "input") : new Set();
64
+ }
65
+
66
+ if (kind === "detail" || kind === "job_status") {
67
+ const loadCapabilityId = directives.get("load");
68
+ return loadCapabilityId ? resolveCapabilityContractFields(registry, loadCapabilityId, "output") : new Set();
69
+ }
70
+
71
+ return new Set();
72
+ }
73
+
74
+ /**
75
+ * @param {TopogramStatement} statement
76
+ * @returns {Set<string>}
77
+ */
78
+
79
+ export function screenIdsFromProjectionStatement(statement) {
80
+ const screens = new Set();
81
+ for (const entry of blockEntries(getFieldValue(statement, "screens"))) {
82
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
83
+ if (tokens[0] === "screen" && tokens[1]) {
84
+ screens.add(tokens[1]);
85
+ }
86
+ }
87
+ return screens;
88
+ }
89
+
90
+ /**
91
+ * @param {TopogramStatement} statement
92
+ * @param {TopogramFieldMap} fieldMap
93
+ * @param {TopogramRegistry} registry
94
+ * @returns {Set<string>}
95
+ */
96
+
97
+ export function collectAvailableUiScreenIds(statement, fieldMap, registry) {
98
+ const available = new Set(collectProjectionUiScreens(statement, fieldMap).keys());
99
+ for (const targetId of symbolValues(getFieldValue(statement, "realizes"))) {
100
+ const target = registry.get(targetId);
101
+ if (target?.kind === "projection") {
102
+ for (const screenId of screenIdsFromProjectionStatement(target)) {
103
+ available.add(screenId);
104
+ }
105
+ }
106
+ }
107
+ return available;
108
+ }
109
+
110
+ /**
111
+ * @param {TopogramStatement} statement
112
+ * @returns {Set<string>}
113
+ */
114
+
115
+ export function collectProjectionUiRegionKeys(statement) {
116
+ const keys = new Set();
117
+ for (const entry of blockEntries(getFieldValue(statement, "screen_regions"))) {
118
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
119
+ if (tokens[0] === "screen" && tokens[1] && tokens[2] === "region" && tokens[3]) {
120
+ keys.add(`${tokens[1]}:${tokens[3]}`);
121
+ }
122
+ }
123
+ return keys;
124
+ }
125
+
126
+ /**
127
+ * @param {TopogramStatement} statement
128
+ * @param {TopogramRegistry} registry
129
+ * @returns {Set<string>}
130
+ */
131
+
132
+ export function collectAvailableUiRegionKeys(statement, registry) {
133
+ const available = collectProjectionUiRegionKeys(statement);
134
+ for (const targetId of symbolValues(getFieldValue(statement, "realizes"))) {
135
+ const target = registry.get(targetId);
136
+ if (target?.kind === "projection") {
137
+ for (const key of collectProjectionUiRegionKeys(target)) {
138
+ available.add(key);
139
+ }
140
+ }
141
+ }
142
+ return available;
143
+ }
144
+
145
+ /**
146
+ * @param {TopogramStatement} statement
147
+ * @returns {Map<string, string>}
148
+ */
149
+
150
+ export function collectProjectionUiRegionPatterns(statement) {
151
+ const patterns = new Map();
152
+ for (const entry of blockEntries(getFieldValue(statement, "screen_regions"))) {
153
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
154
+ if (tokens[0] !== "screen" || !tokens[1] || tokens[2] !== "region" || !tokens[3]) {
155
+ continue;
156
+ }
157
+ for (let i = 4; i < tokens.length; i += 2) {
158
+ if (tokens[i] === "pattern" && tokens[i + 1]) {
159
+ patterns.set(`${tokens[1]}:${tokens[3]}`, tokens[i + 1]);
160
+ }
161
+ }
162
+ }
163
+ return patterns;
164
+ }
165
+
166
+ /**
167
+ * @param {TopogramStatement} statement
168
+ * @param {TopogramRegistry} registry
169
+ * @returns {Map<string, string>}
170
+ */
171
+
172
+ export function collectAvailableUiRegionPatterns(statement, registry) {
173
+ const patterns = collectProjectionUiRegionPatterns(statement);
174
+ for (const targetId of symbolValues(getFieldValue(statement, "realizes"))) {
175
+ const target = registry.get(targetId);
176
+ if (target?.kind !== "projection") {
177
+ continue;
178
+ }
179
+ for (const [key, pattern] of collectProjectionUiRegionPatterns(target)) {
180
+ if (!patterns.has(key)) {
181
+ patterns.set(key, pattern);
182
+ }
183
+ }
184
+ }
185
+ return patterns;
186
+ }
187
+
188
+ /**
189
+ * @param {Map<string, string>} directives
190
+ * @param {string} key
191
+ * @returns {string}
192
+ */
193
+
194
+ export function directiveValue(directives, key) {
195
+ return directives.get(key) || "";
196
+ }
197
+
198
+ export const SHARED_UI_SEMANTIC_BLOCKS = [
199
+ "screens",
200
+ "collection_views",
201
+ "screen_actions",
202
+ "visibility_rules",
203
+ "field_lookups",
204
+ "app_shell",
205
+ "navigation",
206
+ "screen_regions"
207
+ ];
208
+
209
+ /**
210
+ * @param {ValidationErrors} errors
211
+ * @param {TopogramStatement} statement
212
+ * @param {TopogramFieldMap} fieldMap
213
+ * @returns {void}
214
+ */
@@ -0,0 +1,344 @@
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
+ blockSymbolItems,
23
+ getFieldValue,
24
+ pushError,
25
+ symbolValue,
26
+ symbolValues
27
+ } from "../utils.js";
28
+ import {
29
+ collectAvailableUiScreenIds,
30
+ directiveValue
31
+ } from "./ui-helpers.js";
32
+ import { parseUiDirectiveMap } from "./helpers.js";
33
+
34
+ /**
35
+ * @param {ValidationErrors} errors
36
+ * @param {TopogramStatement} statement
37
+ * @param {TopogramFieldMap} fieldMap
38
+ * @param {TopogramRegistry} registry
39
+ * @returns {void}
40
+ */
41
+ export function validateProjectionUiNavigation(errors, statement, fieldMap, registry) {
42
+ if (statement.kind !== "projection") {
43
+ return;
44
+ }
45
+
46
+ const navigationField = fieldMap.get("navigation")?.[0];
47
+ if (!navigationField || navigationField.value.type !== "block") {
48
+ return;
49
+ }
50
+
51
+ const availableScreens = collectAvailableUiScreenIds(statement, fieldMap, registry);
52
+ const groups = new Set();
53
+
54
+ for (const entry of navigationField.value.entries) {
55
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
56
+ const [targetKind, targetId] = tokens;
57
+
58
+ if (targetKind === "group") {
59
+ if (!targetId || !IDENTIFIER_PATTERN.test(targetId)) {
60
+ pushError(errors, `Projection ${statement.id} navigation group entries must include a valid group id`, entry.loc);
61
+ continue;
62
+ }
63
+ groups.add(targetId);
64
+ const directives = parseUiDirectiveMap(tokens, 2, errors, statement, entry, `navigation group '${targetId}'`);
65
+ for (const key of directives.keys()) {
66
+ if (!["label", "placement", "icon", "order", "pattern"].includes(key)) {
67
+ pushError(errors, `Projection ${statement.id} navigation group '${targetId}' has unknown directive '${key}'`, entry.loc);
68
+ }
69
+ }
70
+ if (directives.has("placement") && !["primary", "secondary", "utility"].includes(directiveValue(directives, "placement"))) {
71
+ pushError(errors, `Projection ${statement.id} navigation group '${targetId}' has invalid placement '${directives.get("placement")}'`, entry.loc);
72
+ }
73
+ if (directives.has("pattern") && !UI_NAVIGATION_PATTERNS.has(directiveValue(directives, "pattern"))) {
74
+ pushError(errors, `Projection ${statement.id} navigation group '${targetId}' has invalid pattern '${directives.get("pattern")}'`, entry.loc);
75
+ }
76
+ continue;
77
+ }
78
+
79
+ if (targetKind === "screen") {
80
+ if (!availableScreens.has(targetId)) {
81
+ pushError(errors, `Projection ${statement.id} navigation references unknown screen '${targetId}'`, entry.loc);
82
+ }
83
+ const directives = parseUiDirectiveMap(tokens, 2, errors, statement, entry, `navigation screen '${targetId}'`);
84
+ for (const key of directives.keys()) {
85
+ if (!["group", "label", "order", "visible", "default", "breadcrumb", "sitemap", "placement", "pattern"].includes(key)) {
86
+ pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has unknown directive '${key}'`, entry.loc);
87
+ }
88
+ }
89
+ if (directives.has("visible") && !["true", "false"].includes(directiveValue(directives, "visible"))) {
90
+ pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has invalid visible '${directives.get("visible")}'`, entry.loc);
91
+ }
92
+ if (directives.has("default") && !["true", "false"].includes(directiveValue(directives, "default"))) {
93
+ pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has invalid default '${directives.get("default")}'`, entry.loc);
94
+ }
95
+ if (directives.has("placement") && !["primary", "secondary", "utility"].includes(directiveValue(directives, "placement"))) {
96
+ pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has invalid placement '${directives.get("placement")}'`, entry.loc);
97
+ }
98
+ if (directives.has("sitemap") && !["include", "exclude"].includes(directiveValue(directives, "sitemap"))) {
99
+ pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has invalid sitemap '${directives.get("sitemap")}'`, entry.loc);
100
+ }
101
+ if (directives.has("pattern") && !UI_NAVIGATION_PATTERNS.has(directiveValue(directives, "pattern"))) {
102
+ pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has invalid pattern '${directives.get("pattern")}'`, entry.loc);
103
+ }
104
+ const breadcrumb = directives.get("breadcrumb");
105
+ if (breadcrumb && breadcrumb !== "none" && !availableScreens.has(breadcrumb)) {
106
+ pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' references unknown breadcrumb screen '${breadcrumb}'`, entry.loc);
107
+ }
108
+ continue;
109
+ }
110
+
111
+ pushError(errors, `Projection ${statement.id} navigation entries must start with 'group' or 'screen'`, entry.loc);
112
+ }
113
+
114
+ for (const entry of navigationField.value.entries) {
115
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
116
+ if (tokens[0] !== "screen") {
117
+ continue;
118
+ }
119
+ const directives = parseUiDirectiveMap(tokens, 2, [], statement, entry, "");
120
+ if (directives.has("group") && !groups.has(directives.get("group"))) {
121
+ pushError(errors, `Projection ${statement.id} navigation screen '${tokens[1]}' references unknown group '${directives.get("group")}'`, entry.loc);
122
+ }
123
+ }
124
+ }
125
+
126
+ /**
127
+ * @param {ValidationErrors} errors
128
+ * @param {TopogramStatement} statement
129
+ * @param {TopogramFieldMap} fieldMap
130
+ * @param {TopogramRegistry} registry
131
+ * @returns {void}
132
+ */
133
+
134
+ export function validateProjectionUiScreenRegions(errors, statement, fieldMap, registry) {
135
+ if (statement.kind !== "projection") {
136
+ return;
137
+ }
138
+
139
+ const regionField = fieldMap.get("screen_regions")?.[0];
140
+ if (!regionField || regionField.value.type !== "block") {
141
+ return;
142
+ }
143
+
144
+ const availableScreens = collectAvailableUiScreenIds(statement, fieldMap, registry);
145
+ for (const entry of regionField.value.entries) {
146
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
147
+ const [keyword, screenId, regionKeyword, regionName] = tokens;
148
+
149
+ if (keyword !== "screen") {
150
+ pushError(errors, `Projection ${statement.id} screen_regions entries must start with 'screen'`, entry.loc);
151
+ continue;
152
+ }
153
+ if (!availableScreens.has(screenId)) {
154
+ pushError(errors, `Projection ${statement.id} screen_regions references unknown screen '${screenId}'`, entry.loc);
155
+ }
156
+ if (regionKeyword !== "region") {
157
+ pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' must use 'region'`, entry.loc);
158
+ }
159
+ if (!UI_REGION_KINDS.has(regionName || "")) {
160
+ pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' has invalid region '${regionName}'`, entry.loc);
161
+ }
162
+
163
+ const directives = parseUiDirectiveMap(tokens, 4, errors, statement, entry, `screen_regions for '${screenId}'`);
164
+ for (const key of directives.keys()) {
165
+ if (!["pattern", "placement", "title", "state", "variant"].includes(key)) {
166
+ pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' has unknown directive '${key}'`, entry.loc);
167
+ }
168
+ }
169
+ if (directives.has("pattern") && !UI_PATTERN_KINDS.has(directiveValue(directives, "pattern"))) {
170
+ pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' has invalid pattern '${directives.get("pattern")}'`, entry.loc);
171
+ }
172
+ if (directives.has("placement") && !["primary", "secondary", "supporting"].includes(directiveValue(directives, "placement"))) {
173
+ pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' has invalid placement '${directives.get("placement")}'`, entry.loc);
174
+ }
175
+ if (directives.has("state") && !UI_STATE_KINDS.has(directiveValue(directives, "state"))) {
176
+ pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' has invalid state '${directives.get("state")}'`, entry.loc);
177
+ }
178
+ }
179
+ }
180
+
181
+ /**
182
+ * @param {ValidationErrors} errors
183
+ * @param {TopogramStatement} statement
184
+ * @param {TopogramFieldMap} fieldMap
185
+ * @param {TopogramRegistry} registry
186
+ * @returns {void}
187
+ */
188
+
189
+ export function validateProjectionUiRoutes(errors, statement, fieldMap, registry) {
190
+ if (statement.kind !== "projection") {
191
+ return;
192
+ }
193
+
194
+ const routesField = fieldMap.get("screen_routes")?.[0];
195
+ if (!routesField || routesField.value.type !== "block") {
196
+ return;
197
+ }
198
+
199
+ const availableScreens = collectAvailableUiScreenIds(statement, fieldMap, registry);
200
+ const seenPaths = new Set();
201
+ const projectionType = symbolValue(getFieldValue(statement, "type"));
202
+
203
+ for (const entry of routesField.value.entries) {
204
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
205
+ const [keyword, screenId, pathKeyword, routePath] = tokens;
206
+
207
+ if (keyword !== "screen") {
208
+ pushError(errors, `Projection ${statement.id} screen_routes entries must start with 'screen'`, entry.loc);
209
+ continue;
210
+ }
211
+ if (!availableScreens.has(screenId)) {
212
+ pushError(errors, `Projection ${statement.id} screen_routes references unknown screen '${screenId}'`, entry.loc);
213
+ }
214
+ if (pathKeyword !== "path") {
215
+ pushError(errors, `Projection ${statement.id} screen_routes for '${screenId}' must use 'path'`, entry.loc);
216
+ }
217
+ if (!routePath) {
218
+ pushError(errors, `Projection ${statement.id} screen_routes for '${screenId}' must include a path`, entry.loc);
219
+ continue;
220
+ }
221
+ if ((projectionType === "web_surface" || projectionType === "ios_surface") && !routePath.startsWith("/")) {
222
+ pushError(errors, `Projection ${statement.id} screen_routes for '${screenId}' must use an absolute path`, entry.loc);
223
+ }
224
+ if (seenPaths.has(routePath)) {
225
+ pushError(errors, `Projection ${statement.id} screen_routes has duplicate path '${routePath}'`, entry.loc);
226
+ }
227
+ seenPaths.add(routePath);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * @param {ValidationErrors} errors
233
+ * @param {TopogramStatement} statement
234
+ * @param {TopogramFieldMap} fieldMap
235
+ * @param {TopogramRegistry} registry
236
+ * @param {string} surfaceBlockKey
237
+ * @param {string} expectedProjectionType
238
+ * @returns {void}
239
+ */
240
+
241
+ export function validateProjectionUiSurfaceHints(errors, statement, fieldMap, registry, surfaceBlockKey, expectedProjectionType) {
242
+ if (statement.kind !== "projection") {
243
+ return;
244
+ }
245
+
246
+ const surfaceField = fieldMap.get(surfaceBlockKey)?.[0];
247
+ if (!surfaceField || surfaceField.value.type !== "block") {
248
+ return;
249
+ }
250
+
251
+ const projectionType = symbolValue(getFieldValue(statement, "type"));
252
+ if (projectionType !== expectedProjectionType) {
253
+ pushError(errors, `Projection ${statement.id} may only use '${surfaceBlockKey}' when projection type is '${expectedProjectionType}'`, surfaceField.loc);
254
+ return;
255
+ }
256
+
257
+ const availableScreens = collectAvailableUiScreenIds(statement, fieldMap, registry);
258
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
259
+ for (const entry of surfaceField.value.entries) {
260
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
261
+ const [targetKind, targetId, directive, value] = tokens;
262
+
263
+ if (targetKind === "screen") {
264
+ if (!availableScreens.has(targetId)) {
265
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} references unknown screen '${targetId}'`, entry.loc);
266
+ }
267
+ if (!["layout", "desktop_variant", "mobile_variant", "present", "shell", "collection", "breadcrumbs", "state_style"].includes(directive || "")) {
268
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has unknown directive '${directive}'`, entry.loc);
269
+ }
270
+ if (directive === "desktop_variant" && !UI_COLLECTION_PRESENTATIONS.has(value || "")) {
271
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid desktop_variant '${value}'`, entry.loc);
272
+ }
273
+ if (directive === "mobile_variant" && !UI_COLLECTION_PRESENTATIONS.has(value || "")) {
274
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid mobile_variant '${value}'`, entry.loc);
275
+ }
276
+ if (directive === "collection" && !UI_COLLECTION_PRESENTATIONS.has(value || "")) {
277
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid collection '${value}'`, entry.loc);
278
+ }
279
+ if (directive === "shell" && !["topbar", "sidebar", "dual_nav", "workspace", "wizard", "bottom_tabs", "split_view", "menu_bar"].includes(value || "")) {
280
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid shell '${value}'`, entry.loc);
281
+ }
282
+ if (directive === "present" && !["page", "modal", "drawer", "sheet", "bottom_sheet", "popover"].includes(value || "")) {
283
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid present '${value}'`, entry.loc);
284
+ }
285
+ if (directive === "breadcrumbs" && !["visible", "hidden"].includes(value || "")) {
286
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid breadcrumbs '${value}'`, entry.loc);
287
+ }
288
+ if (directive === "state_style" && !["inline", "panel", "full_page"].includes(value || "")) {
289
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid state_style '${value}'`, entry.loc);
290
+ }
291
+ continue;
292
+ }
293
+
294
+ if (targetKind === "action") {
295
+ const capability = registry.get(targetId);
296
+ if (!capability) {
297
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} references missing capability '${targetId}'`, entry.loc);
298
+ } else if (capability.kind !== "capability") {
299
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} must reference a capability for action '${targetId}', found ${capability.kind} '${capability.id}'`, entry.loc);
300
+ } else if (!realized.has(targetId)) {
301
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} action '${targetId}' must also appear in 'realizes'`, entry.loc);
302
+ }
303
+ if (!["confirm", "present", "placement"].includes(directive || "")) {
304
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for action '${targetId}' has unknown directive '${directive}'`, entry.loc);
305
+ }
306
+ if (directive === "confirm" && !["modal", "inline", "sheet", "bottom_sheet", "popover"].includes(value || "")) {
307
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for action '${targetId}' has invalid confirm mode '${value}'`, entry.loc);
308
+ }
309
+ if (directive === "present" && !["button", "menu_item", "split_button", "bulk_action", "drawer", "sheet", "bottom_sheet", "fab", "popover"].includes(value || "")) {
310
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for action '${targetId}' has invalid present mode '${value}'`, entry.loc);
311
+ }
312
+ if (directive === "placement" && !["toolbar", "menu", "bulk", "inline", "footer"].includes(value || "")) {
313
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for action '${targetId}' has invalid placement '${value}'`, entry.loc);
314
+ }
315
+ continue;
316
+ }
317
+
318
+ pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} entries must start with 'screen' or 'action'`, entry.loc);
319
+ }
320
+ }
321
+
322
+ /**
323
+ * @param {ValidationErrors} errors
324
+ * @param {TopogramStatement} statement
325
+ * @param {TopogramFieldMap} fieldMap
326
+ * @param {TopogramRegistry} registry
327
+ * @returns {void}
328
+ */
329
+
330
+ export function validateProjectionUiWeb(errors, statement, fieldMap, registry) {
331
+ validateProjectionUiSurfaceHints(errors, statement, fieldMap, registry, "web_hints", "web_surface");
332
+ }
333
+
334
+ /**
335
+ * @param {ValidationErrors} errors
336
+ * @param {TopogramStatement} statement
337
+ * @param {TopogramFieldMap} fieldMap
338
+ * @param {TopogramRegistry} registry
339
+ * @returns {void}
340
+ */
341
+
342
+ export function validateProjectionUiIos(errors, statement, fieldMap, registry) {
343
+ validateProjectionUiSurfaceHints(errors, statement, fieldMap, registry, "ios_hints", "ios_surface");
344
+ }