@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,258 @@
1
+ // @ts-check
2
+
3
+ import { stableUnique } from "./projection-context.js";
4
+ import { generateWidgetConformanceReport } from "./report.js";
5
+
6
+ /**
7
+ * @param {import("./types.d.ts").WidgetUsageReport} usage
8
+ * @param {import("./types.d.ts").WidgetBehavior} behavior
9
+ * @param {any} index
10
+ * @returns {any}
11
+ */
12
+ export function behaviorReportKey(usage, behavior, index) {
13
+ return [usage.key, behavior.kind || "behavior", String(index)].join(":");
14
+ }
15
+
16
+ /**
17
+ * @param {import("./types.d.ts").WidgetBehavior} behavior
18
+ * @returns {any}
19
+ */
20
+ export function capabilityIdsFromBehavior(behavior) {
21
+ const ids = /** @type {string[]} */ ([]);
22
+ for (const dependency of behavior.dataDependencies || []) {
23
+ if (dependency.source?.kind === "capability" && dependency.source.id) {
24
+ ids.push(dependency.source.id);
25
+ }
26
+ }
27
+ for (const action of behavior.actions || []) {
28
+ if (action.capability?.id) {
29
+ ids.push(action.capability.id);
30
+ }
31
+ for (const effect of action.effects || []) {
32
+ if (effect.capability?.id) {
33
+ ids.push(effect.capability.id);
34
+ }
35
+ }
36
+ }
37
+ for (const effect of behavior.effects || []) {
38
+ if (effect.capability?.id) {
39
+ ids.push(effect.capability.id);
40
+ }
41
+ }
42
+ return stableUnique(ids);
43
+ }
44
+
45
+ /**
46
+ * @param {import("./types.d.ts").WidgetBehavior} behavior
47
+ * @returns {any}
48
+ */
49
+ export function effectTypesFromBehavior(behavior) {
50
+ const effects = behavior.effects || [];
51
+ if (effects.length === 0) {
52
+ return ["none"];
53
+ }
54
+ return stableUnique(effects.map(/** @param {any} effect */ (effect) => effect.type || "unknown"));
55
+ }
56
+
57
+ /**
58
+ * @param {import("./types.d.ts").WidgetConformanceReport} conformanceReport
59
+ * @param {import("./types.d.ts").WidgetUsageReport} usage
60
+ * @param {import("./types.d.ts").WidgetBehavior} behavior
61
+ * @returns {any}
62
+ */
63
+ export function checksForBehavior(conformanceReport, usage, behavior) {
64
+ return (conformanceReport.checks || [])
65
+ .filter(/** @param {any} check */ (check) =>
66
+ check.code?.startsWith("widget_behavior_") &&
67
+ check.projection === usage.projection?.id &&
68
+ check.widget === usage.widget?.id &&
69
+ check.screen === usage.screen?.id &&
70
+ check.region === usage.region &&
71
+ (!check.behavior || check.behavior === behavior.kind)
72
+ )
73
+ .map(/** @param {any} check */ (check) => ({
74
+ code: check.code,
75
+ severity: check.severity,
76
+ message: check.message,
77
+ event: check.event || null,
78
+ behavior: check.behavior || behavior.kind || null,
79
+ suggested_fix: check.suggested_fix || null
80
+ }));
81
+ }
82
+
83
+ /**
84
+ * @param {import("./types.d.ts").WidgetUsageReport[]} behaviorRows
85
+ * @returns {any}
86
+ */
87
+ export function behaviorHighlights(behaviorRows) {
88
+ const highlights = /** @type {any[]} */ ([]);
89
+ for (const row of behaviorRows) {
90
+ if (row.behavior.status === "partial") {
91
+ highlights.push({
92
+ severity: "warning",
93
+ code: "widget_behavior_partial",
94
+ message: `Behavior '${row.behavior.kind}' is partially realized for widget '${row.widget.id}' on screen '${row.screen.id}'.`,
95
+ projection: row.projection.id,
96
+ widget: row.widget.id,
97
+ screen: row.screen.id,
98
+ region: row.region,
99
+ behavior: row.behavior.kind,
100
+ suggested_fix: "Bind the required behavior data, events, or capability actions in the projection widget_bindings entry."
101
+ });
102
+ }
103
+ for (const emittedEvent of row.emits || []) {
104
+ if (!emittedEvent.bound) {
105
+ highlights.push({
106
+ severity: "warning",
107
+ code: "widget_behavior_event_unbound",
108
+ message: `Behavior '${row.behavior.kind}' emits event '${emittedEvent.event}', but this widget usage does not bind it.`,
109
+ projection: row.projection.id,
110
+ widget: row.widget.id,
111
+ screen: row.screen.id,
112
+ region: row.region,
113
+ event: emittedEvent.event || null,
114
+ behavior: row.behavior.kind,
115
+ suggested_fix: `Add 'event ${emittedEvent.event} navigate <screen>' or 'event ${emittedEvent.event} action <capability>' to the projection widget_bindings entry.`
116
+ });
117
+ }
118
+ }
119
+ for (const action of row.actions || []) {
120
+ if (!action.bound) {
121
+ const target = action.capability?.id || action.event || "(unknown)";
122
+ highlights.push({
123
+ severity: "warning",
124
+ code: "widget_behavior_action_unbound",
125
+ message: `Behavior '${row.behavior.kind}' declares action '${target}', but this widget usage does not bind it.`,
126
+ projection: row.projection.id,
127
+ widget: row.widget.id,
128
+ screen: row.screen.id,
129
+ region: row.region,
130
+ event: action.event || null,
131
+ capability: action.capability?.id || null,
132
+ behavior: row.behavior.kind,
133
+ suggested_fix: action.capability?.id
134
+ ? `Add 'event <widget_event> action ${action.capability.id}' to the projection widget_bindings entry.`
135
+ : `Add 'event ${action.event} action <capability>' or 'event ${action.event} navigate <screen>' to the projection widget_bindings entry.`
136
+ });
137
+ }
138
+ }
139
+ }
140
+ return highlights;
141
+ }
142
+
143
+ /**
144
+ * @param {any} rows
145
+ * @param {any} keyFn
146
+ * @param {any} itemFn
147
+ * @returns {any}
148
+ */
149
+ export function groupBehaviorRows(rows, keyFn, itemFn = null) {
150
+ const groups = new Map();
151
+ for (const row of rows) {
152
+ for (const key of keyFn(row)) {
153
+ if (!groups.has(key)) {
154
+ groups.set(key, []);
155
+ }
156
+ groups.get(key).push(row);
157
+ }
158
+ }
159
+ return [...groups.entries()]
160
+ .sort(([a], [b]) => a.localeCompare(b))
161
+ .map(([id, entries]) => ({
162
+ id,
163
+ total_behaviors: entries.length,
164
+ realized: entries.filter(/** @param {any} entry */ (entry) => entry.behavior.status === "realized").length,
165
+ partial: entries.filter(/** @param {any} entry */ (entry) => entry.behavior.status === "partial").length,
166
+ declared: entries.filter(/** @param {any} entry */ (entry) => entry.behavior.status === "declared").length,
167
+ behaviors: entries.map(/** @param {any} entry */ (entry) => itemFn ? itemFn(entry) : entry.key).sort()
168
+ }));
169
+ }
170
+
171
+ /**
172
+ * @param {import("./types.d.ts").WidgetGraph} graph
173
+ * @param {import("./types.d.ts").WidgetOptions} options
174
+ * @returns {any}
175
+ */
176
+ export function generateWidgetBehaviorReport(graph, options = {}) {
177
+ const conformanceReport = generateWidgetConformanceReport(graph, options);
178
+ const behaviorRows = /** @type {any[]} */ ([]);
179
+
180
+ for (const usage of conformanceReport.projection_usages || []) {
181
+ for (const [index, behavior] of (usage.behavior_realizations || []).entries()) {
182
+ const diagnostics = checksForBehavior(conformanceReport, usage, behavior);
183
+ behaviorRows.push({
184
+ key: behaviorReportKey(usage, behavior, index),
185
+ projection: usage.projection,
186
+ source_projection: usage.source_projection,
187
+ screen: usage.screen,
188
+ region: usage.region,
189
+ widget: usage.widget,
190
+ behavior: {
191
+ kind: behavior.kind || null,
192
+ source: behavior.source || null,
193
+ status: behavior.status || "declared",
194
+ directives: behavior.directives || {}
195
+ },
196
+ data_dependencies: behavior.dataDependencies || [],
197
+ emits: behavior.emits || [],
198
+ actions: behavior.actions || [],
199
+ effects: behavior.effects || [],
200
+ capabilities: capabilityIdsFromBehavior(behavior),
201
+ effect_types: effectTypesFromBehavior(behavior),
202
+ diagnostics,
203
+ check_codes: diagnostics.map(/** @param {any} check */ (check) => check.code)
204
+ });
205
+ }
206
+ }
207
+
208
+ const highlights = behaviorHighlights(behaviorRows);
209
+ const affectedCapabilities = stableUnique(behaviorRows.flatMap(/** @param {any} row */ (row) => row.capabilities));
210
+
211
+ return {
212
+ type: "widget_behavior_report",
213
+ filters: conformanceReport.filters,
214
+ summary: {
215
+ total_usages: conformanceReport.summary.total_usages,
216
+ total_behaviors: behaviorRows.length,
217
+ realized: behaviorRows.filter(/** @param {any} row */ (row) => row.behavior.status === "realized").length,
218
+ partial: behaviorRows.filter(/** @param {any} row */ (row) => row.behavior.status === "partial").length,
219
+ declared: behaviorRows.filter(/** @param {any} row */ (row) => row.behavior.status === "declared").length,
220
+ warnings: conformanceReport.summary.warnings,
221
+ errors: conformanceReport.summary.errors,
222
+ affected_widgets: conformanceReport.summary.affected_widgets,
223
+ affected_projections: conformanceReport.summary.affected_projections,
224
+ affected_capabilities: affectedCapabilities
225
+ },
226
+ groups: {
227
+ widgets: groupBehaviorRows(
228
+ behaviorRows,
229
+ /** @param {any} row */ (row) => [row.widget.id].filter(Boolean),
230
+ /** @param {any} row */ (row) => row.key
231
+ ),
232
+ screens: groupBehaviorRows(
233
+ behaviorRows,
234
+ /** @param {any} row */ (row) => [row.screen.id].filter(Boolean),
235
+ /** @param {any} row */ (row) => row.key
236
+ ),
237
+ capabilities: groupBehaviorRows(
238
+ behaviorRows,
239
+ /** @param {any} row */ (row) => row.capabilities,
240
+ /** @param {any} row */ (row) => row.key
241
+ ),
242
+ effects: groupBehaviorRows(
243
+ behaviorRows,
244
+ /** @param {any} row */ (row) => row.effect_types,
245
+ /** @param {any} row */ (row) => row.key
246
+ )
247
+ },
248
+ behaviors: behaviorRows,
249
+ highlights,
250
+ checks: conformanceReport.checks.filter(/** @param {any} check */ (check) => check.code?.startsWith("widget_behavior_")),
251
+ write_scope: conformanceReport.write_scope,
252
+ impact: {
253
+ projections: conformanceReport.impact.projections,
254
+ widgets: conformanceReport.impact.widgets,
255
+ capabilities: affectedCapabilities
256
+ }
257
+ };
258
+ }
@@ -0,0 +1,371 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ byId,
5
+ projectionContextRealizesIds,
6
+ projectionRegionKeys,
7
+ projectionScreenMap,
8
+ widgetContract
9
+ } from "./projection-context.js";
10
+
11
+ /**
12
+ * @param {any} arg1
13
+ * @returns {any}
14
+ */
15
+ export function checkRecord(arg1) {
16
+ const {
17
+ code,
18
+ severity,
19
+ message,
20
+ projection,
21
+ sourceProjection,
22
+ widget,
23
+ usage,
24
+ prop = null,
25
+ event = null,
26
+ behavior = null,
27
+ suggestedFix
28
+ } = arg1;
29
+ return {
30
+ code,
31
+ severity,
32
+ message,
33
+ projection: projection?.id || null,
34
+ source_projection: sourceProjection?.id || null,
35
+ widget: widget?.id || usage.widget?.id || null,
36
+ screen: usage.screenId || null,
37
+ region: usage.region || null,
38
+ prop,
39
+ event,
40
+ behavior,
41
+ suggested_fix: suggestedFix
42
+ };
43
+ }
44
+
45
+ /**
46
+ * @param {import("./types.d.ts").WidgetProjection} projection
47
+ * @param {import("./types.d.ts").WidgetProjection} sourceProjection
48
+ * @param {import("./types.d.ts").WidgetUsage} usage
49
+ * @param {any} index
50
+ * @returns {any}
51
+ */
52
+ export function widgetUsageKey(projection, sourceProjection, usage, index) {
53
+ return [
54
+ projection.id,
55
+ sourceProjection?.id || projection.id,
56
+ usage.screenId || "screen",
57
+ usage.region || "region",
58
+ usage.widget?.id || "widget",
59
+ String(index)
60
+ ].join(":");
61
+ }
62
+
63
+ /**
64
+ * @param {any} arg1
65
+ * @returns {any}
66
+ */
67
+ export function collectUsageChecks(arg1) {
68
+ const { graph, projection, sourceProjection, usage, widget } = arg1;
69
+ const checks = /** @type {any[]} */ ([]);
70
+ const contract = widgetContract(widget);
71
+ const props = contract?.props || [];
72
+ const events = contract?.events || [];
73
+ const propNames = new Set(props.map(/** @param {any} prop */ (prop) => prop.name));
74
+ const eventNames = new Set(events.map(/** @param {any} event */ (event) => event.id));
75
+ const boundProps = new Set((usage.dataBindings || []).map(/** @param {any} binding */ (binding) => binding.prop).filter(Boolean));
76
+ const statements = byId(graph.statements || []);
77
+ const screens = projectionScreenMap(graph, sourceProjection);
78
+ const regionKeys = projectionRegionKeys(graph, sourceProjection);
79
+ const realizedIds = projectionContextRealizesIds(graph, sourceProjection);
80
+
81
+ if (!widget) {
82
+ checks.push(checkRecord({
83
+ code: "widget_missing",
84
+ severity: "error",
85
+ message: `Widget '${usage.widget?.id || "(missing)"}' could not be resolved.`,
86
+ projection,
87
+ sourceProjection,
88
+ widget,
89
+ usage,
90
+ suggestedFix: "Create the widget or update the projection widget_bindings binding."
91
+ }));
92
+ return checks;
93
+ }
94
+
95
+ if (projection.status === "active" && widget.status && widget.status !== "active") {
96
+ checks.push(checkRecord({
97
+ code: "widget_status_not_active",
98
+ severity: "warning",
99
+ message: `Active projection '${projection.id}' uses widget '${widget.id}' with status '${widget.status}'.`,
100
+ projection,
101
+ sourceProjection,
102
+ widget,
103
+ usage,
104
+ suggestedFix: "Promote the widget to active or move the usage behind an explicit review boundary."
105
+ }));
106
+ }
107
+
108
+ if (!screens.has(usage.screenId)) {
109
+ checks.push(checkRecord({
110
+ code: "widget_usage_screen_missing",
111
+ severity: "error",
112
+ message: `Widget usage references missing screen '${usage.screenId}'.`,
113
+ projection,
114
+ sourceProjection,
115
+ widget,
116
+ usage,
117
+ suggestedFix: "Add the screen to screens or update the widget usage screen id."
118
+ }));
119
+ }
120
+
121
+ if (!regionKeys.has(`${usage.screenId}:${usage.region}`)) {
122
+ checks.push(checkRecord({
123
+ code: "widget_usage_region_missing",
124
+ severity: "error",
125
+ message: `Widget usage references undeclared region '${usage.region}' on screen '${usage.screenId}'.`,
126
+ projection,
127
+ sourceProjection,
128
+ widget,
129
+ usage,
130
+ suggestedFix: "Add the region to screen_regions or update the widget usage region."
131
+ }));
132
+ }
133
+
134
+ for (const prop of props.filter(/** @param {any} entry */ (entry) => entry.requiredness === "required")) {
135
+ if (!boundProps.has(prop.name)) {
136
+ checks.push(checkRecord({
137
+ code: "widget_required_prop_missing",
138
+ severity: "error",
139
+ message: `Required prop '${prop.name}' is not bound for widget '${widget.id}'.`,
140
+ projection,
141
+ sourceProjection,
142
+ widget,
143
+ usage,
144
+ prop: prop.name,
145
+ suggestedFix: `Add 'data ${prop.name} from <source>' to the projection widget_bindings entry.`
146
+ }));
147
+ }
148
+ }
149
+
150
+ for (const binding of usage.dataBindings || []) {
151
+ if (!propNames.has(binding.prop)) {
152
+ checks.push(checkRecord({
153
+ code: "widget_prop_unknown",
154
+ severity: "error",
155
+ message: `Prop '${binding.prop}' is not declared by widget '${widget.id}'.`,
156
+ projection,
157
+ sourceProjection,
158
+ widget,
159
+ usage,
160
+ prop: binding.prop || null,
161
+ suggestedFix: "Declare the prop on the widget or update the projection binding."
162
+ }));
163
+ }
164
+ if (!binding.source?.id || !statements.has(binding.source.id)) {
165
+ checks.push(checkRecord({
166
+ code: "widget_data_source_missing",
167
+ severity: "error",
168
+ message: `Data binding for prop '${binding.prop}' references a missing source.`,
169
+ projection,
170
+ sourceProjection,
171
+ widget,
172
+ usage,
173
+ prop: binding.prop || null,
174
+ suggestedFix: "Bind the prop to an existing capability, projection, shape, or entity."
175
+ }));
176
+ }
177
+ }
178
+
179
+ for (const binding of usage.eventBindings || []) {
180
+ if (!eventNames.has(binding.event)) {
181
+ checks.push(checkRecord({
182
+ code: "widget_event_unknown",
183
+ severity: "error",
184
+ message: `Event '${binding.event}' is not declared by widget '${widget.id}'.`,
185
+ projection,
186
+ sourceProjection,
187
+ widget,
188
+ usage,
189
+ event: binding.event || null,
190
+ suggestedFix: "Declare the event on the widget or update the projection binding."
191
+ }));
192
+ }
193
+ if (binding.action === "navigate") {
194
+ if (!screens.has(binding.target?.id)) {
195
+ checks.push(checkRecord({
196
+ code: "widget_event_navigation_target_missing",
197
+ severity: "error",
198
+ message: `Event '${binding.event}' navigates to missing screen '${binding.target?.id || "(missing)"}'.`,
199
+ projection,
200
+ sourceProjection,
201
+ widget,
202
+ usage,
203
+ event: binding.event || null,
204
+ suggestedFix: "Add the target screen or update the event navigation target."
205
+ }));
206
+ }
207
+ } else if (binding.action === "action") {
208
+ const target = binding.target?.id ? statements.get(binding.target.id) : null;
209
+ if (!target || target.kind !== "capability") {
210
+ checks.push(checkRecord({
211
+ code: "widget_event_action_missing",
212
+ severity: "error",
213
+ message: `Event '${binding.event}' targets missing capability '${binding.target?.id || "(missing)"}'.`,
214
+ projection,
215
+ sourceProjection,
216
+ widget,
217
+ usage,
218
+ event: binding.event || null,
219
+ suggestedFix: "Bind the event to an existing capability."
220
+ }));
221
+ } else if (!realizedIds.has(target.id)) {
222
+ checks.push(checkRecord({
223
+ code: "widget_event_action_not_in_projection",
224
+ severity: "error",
225
+ message: `Event '${binding.event}' targets capability '${target.id}', but projection '${sourceProjection.id}' does not realize it through its UI context.`,
226
+ projection,
227
+ sourceProjection,
228
+ widget,
229
+ usage,
230
+ event: binding.event || null,
231
+ suggestedFix: `Add '${target.id}' to projection '${sourceProjection.id}' or an inherited shared projection realizes list, or choose a capability already in this projection context.`
232
+ }));
233
+ }
234
+ } else {
235
+ checks.push(checkRecord({
236
+ code: "widget_event_action_unsupported",
237
+ severity: "error",
238
+ message: `Event '${binding.event}' uses unsupported action '${binding.action}'.`,
239
+ projection,
240
+ sourceProjection,
241
+ widget,
242
+ usage,
243
+ event: binding.event || null,
244
+ suggestedFix: "Use 'navigate' or 'action'."
245
+ }));
246
+ }
247
+ }
248
+
249
+ for (const behavior of contract?.behaviors || []) {
250
+ const stateProp = behavior.directives?.state;
251
+ if (stateProp && !propNames.has(stateProp)) {
252
+ checks.push(checkRecord({
253
+ code: "widget_behavior_prop_missing",
254
+ severity: "error",
255
+ message: `Behavior '${behavior.kind}' references missing prop '${stateProp}'.`,
256
+ projection,
257
+ sourceProjection,
258
+ widget,
259
+ usage,
260
+ prop: stateProp,
261
+ behavior: behavior.kind,
262
+ suggestedFix: "Update the behavior directive or declare the referenced prop."
263
+ }));
264
+ }
265
+ const emits = Array.isArray(behavior.directives?.emits)
266
+ ? behavior.directives.emits
267
+ : [behavior.directives?.emits].filter(Boolean);
268
+ for (const eventName of emits) {
269
+ if (!eventNames.has(eventName)) {
270
+ checks.push(checkRecord({
271
+ code: "widget_behavior_event_missing",
272
+ severity: "error",
273
+ message: `Behavior '${behavior.kind}' references missing event '${eventName}'.`,
274
+ projection,
275
+ sourceProjection,
276
+ widget,
277
+ usage,
278
+ event: eventName,
279
+ behavior: behavior.kind,
280
+ suggestedFix: "Update the behavior directive or declare the referenced event."
281
+ }));
282
+ continue;
283
+ }
284
+ if (!(usage.eventBindings || []).some(/** @param {any} binding */ (binding) => binding.event === eventName)) {
285
+ checks.push(checkRecord({
286
+ code: "widget_behavior_event_unbound",
287
+ severity: "warning",
288
+ message: `Behavior '${behavior.kind}' emits event '${eventName}', but this projection usage does not bind that event to navigation or an action.`,
289
+ projection,
290
+ sourceProjection,
291
+ widget,
292
+ usage,
293
+ event: eventName,
294
+ behavior: behavior.kind,
295
+ suggestedFix: `Add 'event ${eventName} navigate <screen>' or 'event ${eventName} action <capability>' to the projection widget_bindings entry.`
296
+ }));
297
+ }
298
+ }
299
+ const declaredActions = [
300
+ ...(Array.isArray(behavior.directives?.actions) ? behavior.directives.actions : [behavior.directives?.actions].filter(Boolean)),
301
+ ...(Array.isArray(behavior.directives?.submit) ? behavior.directives.submit : [behavior.directives?.submit].filter(Boolean))
302
+ ];
303
+ for (const actionTarget of declaredActions) {
304
+ if (eventNames.has(actionTarget)) {
305
+ if (!(usage.eventBindings || []).some(/** @param {any} binding */ (binding) => binding.event === actionTarget)) {
306
+ checks.push(checkRecord({
307
+ code: "widget_behavior_action_unbound",
308
+ severity: "warning",
309
+ message: `Behavior '${behavior.kind}' declares action event '${actionTarget}', but this projection usage does not bind that event to navigation or an action.`,
310
+ projection,
311
+ sourceProjection,
312
+ widget,
313
+ usage,
314
+ event: actionTarget,
315
+ behavior: behavior.kind,
316
+ suggestedFix: `Add 'event ${actionTarget} action <capability>' or 'event ${actionTarget} navigate <screen>' to the projection widget_bindings entry.`
317
+ }));
318
+ }
319
+ continue;
320
+ }
321
+
322
+ const target = statements.get(actionTarget);
323
+ if (!target || target.kind !== "capability") {
324
+ checks.push(checkRecord({
325
+ code: "widget_behavior_action_missing",
326
+ severity: "error",
327
+ message: `Behavior '${behavior.kind}' references missing capability action '${actionTarget}'.`,
328
+ projection,
329
+ sourceProjection,
330
+ widget,
331
+ usage,
332
+ behavior: behavior.kind,
333
+ suggestedFix: "Update the behavior directive or declare the referenced capability."
334
+ }));
335
+ continue;
336
+ }
337
+ if (!realizedIds.has(actionTarget)) {
338
+ checks.push(checkRecord({
339
+ code: "widget_behavior_action_not_in_projection",
340
+ severity: "error",
341
+ message: `Behavior '${behavior.kind}' references capability '${actionTarget}', but projection '${sourceProjection.id}' does not realize it.`,
342
+ projection,
343
+ sourceProjection,
344
+ widget,
345
+ usage,
346
+ behavior: behavior.kind,
347
+ suggestedFix: `Add '${actionTarget}' to projection '${sourceProjection.id}' realizes or choose a capability already in this projection context.`
348
+ }));
349
+ }
350
+ if (!(usage.eventBindings || []).some(/** @param {any} binding */ (binding) =>
351
+ binding.action === "action" &&
352
+ binding.target?.id === actionTarget &&
353
+ binding.target?.kind === "capability"
354
+ )) {
355
+ checks.push(checkRecord({
356
+ code: "widget_behavior_action_unbound",
357
+ severity: "warning",
358
+ message: `Behavior '${behavior.kind}' declares capability action '${actionTarget}', but this projection usage does not bind any widget event to that capability.`,
359
+ projection,
360
+ sourceProjection,
361
+ widget,
362
+ usage,
363
+ behavior: behavior.kind,
364
+ suggestedFix: `Add 'event <widget_event> action ${actionTarget}' to the projection widget_bindings entry.`
365
+ }));
366
+ }
367
+ }
368
+ }
369
+
370
+ return checks;
371
+ }