@topogram/cli 0.3.64 → 0.3.65

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 (245) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/agent-ops/query-builders/auth.js +375 -0
  5. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  6. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  7. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  8. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  9. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  10. package/src/agent-ops/query-builders/change-risk.js +25 -0
  11. package/src/agent-ops/query-builders/common.js +149 -0
  12. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  13. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  14. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  15. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  16. package/src/agent-ops/query-builders/work-packets.js +417 -0
  17. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  18. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  19. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  20. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  21. package/src/agent-ops/query-builders.d.ts +26 -26
  22. package/src/agent-ops/query-builders.js +42 -5021
  23. package/src/catalog/constants.js +10 -0
  24. package/src/catalog/copy.js +60 -0
  25. package/src/catalog/diagnostics.js +15 -0
  26. package/src/catalog/entries.js +42 -0
  27. package/src/catalog/files.js +67 -0
  28. package/src/catalog/provenance.js +122 -0
  29. package/src/catalog/source.js +150 -0
  30. package/src/catalog/validation.js +252 -0
  31. package/src/catalog.d.ts +2 -0
  32. package/src/catalog.js +18 -746
  33. package/src/cli/commands/catalog/check.js +31 -0
  34. package/src/cli/commands/catalog/copy.js +59 -0
  35. package/src/cli/commands/catalog/doctor.js +248 -0
  36. package/src/cli/commands/catalog/help.js +21 -0
  37. package/src/cli/commands/catalog/list.js +52 -0
  38. package/src/cli/commands/catalog/runner.js +92 -0
  39. package/src/cli/commands/catalog/shared.js +17 -0
  40. package/src/cli/commands/catalog/show.js +134 -0
  41. package/src/cli/commands/catalog.js +30 -615
  42. package/src/cli/commands/generator-policy/package-info.js +162 -0
  43. package/src/cli/commands/generator-policy/payloads.js +372 -0
  44. package/src/cli/commands/generator-policy/printers.js +159 -0
  45. package/src/cli/commands/generator-policy/runner.js +81 -0
  46. package/src/cli/commands/generator-policy/shared.js +39 -0
  47. package/src/cli/commands/generator-policy.js +15 -783
  48. package/src/cli/commands/import/adopt.js +170 -0
  49. package/src/cli/commands/import/check.js +91 -0
  50. package/src/cli/commands/import/diff.js +84 -0
  51. package/src/cli/commands/import/help.js +47 -0
  52. package/src/cli/commands/import/paths.js +277 -0
  53. package/src/cli/commands/import/plan.js +284 -0
  54. package/src/cli/commands/import/refresh.js +470 -0
  55. package/src/cli/commands/import/status-history.js +196 -0
  56. package/src/cli/commands/import/workspace.js +230 -0
  57. package/src/cli/commands/import.js +33 -1732
  58. package/src/cli/commands/package/constants.js +17 -0
  59. package/src/cli/commands/package/doctor.js +240 -0
  60. package/src/cli/commands/package/help.js +27 -0
  61. package/src/cli/commands/package/lockfile.js +135 -0
  62. package/src/cli/commands/package/npm.js +97 -0
  63. package/src/cli/commands/package/reporting.js +35 -0
  64. package/src/cli/commands/package/runner.js +33 -0
  65. package/src/cli/commands/package/shared.js +9 -0
  66. package/src/cli/commands/package/update-cli.js +252 -0
  67. package/src/cli/commands/package/versions.js +35 -0
  68. package/src/cli/commands/package.js +29 -813
  69. package/src/cli/commands/query/change-plan.js +68 -0
  70. package/src/cli/commands/query/definitions.js +202 -0
  71. package/src/cli/commands/query/import-adopt.js +121 -0
  72. package/src/cli/commands/query/runner/artifacts.js +102 -0
  73. package/src/cli/commands/query/runner/boundaries.js +211 -0
  74. package/src/cli/commands/query/runner/change.js +182 -0
  75. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  76. package/src/cli/commands/query/runner/index.js +31 -0
  77. package/src/cli/commands/query/runner/output.js +12 -0
  78. package/src/cli/commands/query/runner/workflow.js +241 -0
  79. package/src/cli/commands/query/runner.js +3 -0
  80. package/src/cli/commands/query/workflow-context.js +5 -0
  81. package/src/cli/commands/query/workspace.js +274 -0
  82. package/src/cli/commands/query.js +9 -1300
  83. package/src/cli/commands/template/baseline.js +100 -0
  84. package/src/cli/commands/template/check.js +466 -0
  85. package/src/cli/commands/template/constants.js +8 -0
  86. package/src/cli/commands/template/diagnostics.js +26 -0
  87. package/src/cli/commands/template/help.js +28 -0
  88. package/src/cli/commands/template/lifecycle.js +404 -0
  89. package/src/cli/commands/template/list-show.js +287 -0
  90. package/src/cli/commands/template/policy.js +422 -0
  91. package/src/cli/commands/template/shared.js +127 -0
  92. package/src/cli/commands/template/updates.js +352 -0
  93. package/src/cli/commands/template.js +41 -2143
  94. package/src/generator/api/contracts.js +497 -0
  95. package/src/generator/api/metadata.js +221 -0
  96. package/src/generator/api/openapi.js +559 -0
  97. package/src/generator/api/schema.js +124 -0
  98. package/src/generator/api/types.d.ts +98 -0
  99. package/src/generator/api.js +3 -1195
  100. package/src/generator/context/shared/domain-sdlc.js +282 -0
  101. package/src/generator/context/shared/maintained-boundary.js +665 -0
  102. package/src/generator/context/shared/metrics.js +85 -0
  103. package/src/generator/context/shared/primitives.js +64 -0
  104. package/src/generator/context/shared/relationships.js +453 -0
  105. package/src/generator/context/shared/summaries.js +263 -0
  106. package/src/generator/context/shared/types.d.ts +207 -0
  107. package/src/generator/context/shared.d.ts +42 -0
  108. package/src/generator/context/shared.js +80 -1390
  109. package/src/generator/context/slice/core.js +397 -0
  110. package/src/generator/context/slice/sdlc.js +417 -0
  111. package/src/generator/context/slice/ui-packets.js +183 -0
  112. package/src/generator/context/slice.js +2 -859
  113. package/src/generator/registry/index.js +507 -0
  114. package/src/generator/registry.js +18 -504
  115. package/src/generator/runtime/environment/index.js +666 -0
  116. package/src/generator/runtime/environment.js +4 -666
  117. package/src/generator/runtime/runtime-check/index.js +554 -0
  118. package/src/generator/runtime/runtime-check.js +4 -554
  119. package/src/generator/runtime/shared/index.js +572 -0
  120. package/src/generator/runtime/shared.js +19 -570
  121. package/src/generator/shared.d.ts +2 -0
  122. package/src/generator/surfaces/shared.d.ts +3 -0
  123. package/src/generator/widget-conformance/behavior-report.js +258 -0
  124. package/src/generator/widget-conformance/checks.js +371 -0
  125. package/src/generator/widget-conformance/projection-context.js +200 -0
  126. package/src/generator/widget-conformance/report.js +166 -0
  127. package/src/generator/widget-conformance/types.d.ts +121 -0
  128. package/src/generator/widget-conformance.js +3 -824
  129. package/src/import/core/context.d.ts +3 -0
  130. package/src/import/core/contracts.d.ts +1 -0
  131. package/src/import/core/registry.d.ts +4 -0
  132. package/src/import/core/runner/candidates.js +217 -0
  133. package/src/import/core/runner/options.js +22 -0
  134. package/src/import/core/runner/reports.js +50 -0
  135. package/src/import/core/runner/run.js +79 -0
  136. package/src/import/core/runner/tracks.js +150 -0
  137. package/src/import/core/runner/ui-drafts.js +337 -0
  138. package/src/import/core/runner.js +3 -698
  139. package/src/import/core/shared/api-routes.js +221 -0
  140. package/src/import/core/shared/candidates.js +97 -0
  141. package/src/import/core/shared/files.js +177 -0
  142. package/src/import/core/shared/next-app.js +389 -0
  143. package/src/import/core/shared/types.d.ts +51 -0
  144. package/src/import/core/shared/ui-routes.js +230 -0
  145. package/src/import/core/shared.js +60 -861
  146. package/src/new-project/constants.js +128 -0
  147. package/src/new-project/create.js +83 -0
  148. package/src/new-project/json.js +28 -0
  149. package/src/new-project/metadata.js +96 -0
  150. package/src/new-project/package-spec.js +161 -0
  151. package/src/new-project/project-files.js +348 -0
  152. package/src/new-project/template-policy.js +269 -0
  153. package/src/new-project/template-resolution.js +368 -0
  154. package/src/new-project/template-snapshots.js +430 -0
  155. package/src/new-project/template-updates.js +512 -0
  156. package/src/new-project/types.d.ts +83 -0
  157. package/src/new-project.js +6 -2277
  158. package/src/parser.d.ts +87 -1
  159. package/src/parser.js +118 -0
  160. package/src/policy/review-boundaries.d.ts +15 -0
  161. package/src/project-config/index.js +564 -0
  162. package/src/project-config.js +19 -561
  163. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  164. package/src/resolver/enrich/bug.js +2 -0
  165. package/src/resolver/enrich/pitch.js +2 -0
  166. package/src/resolver/enrich/requirement.js +2 -0
  167. package/src/resolver/enrich/task.js +2 -0
  168. package/src/resolver/index.js +19 -2089
  169. package/src/resolver/normalize.js +384 -1
  170. package/src/resolver/plans.js +168 -0
  171. package/src/resolver/projections-api.js +494 -0
  172. package/src/resolver/projections-db.js +133 -0
  173. package/src/resolver/projections-ui.js +317 -0
  174. package/src/resolver/shapes.js +251 -0
  175. package/src/resolver/shared.js +278 -0
  176. package/src/resolver/widgets.js +132 -0
  177. package/src/template-trust/constants.js +62 -0
  178. package/src/template-trust/content.js +258 -0
  179. package/src/template-trust/diff.js +92 -0
  180. package/src/template-trust/policy.js +61 -0
  181. package/src/template-trust/record.js +90 -0
  182. package/src/template-trust/status.js +182 -0
  183. package/src/template-trust.js +24 -687
  184. package/src/text-helpers.d.ts +1 -0
  185. package/src/topogram-types.d.ts +69 -0
  186. package/src/validator/common.js +488 -0
  187. package/src/validator/data-model.js +237 -0
  188. package/src/validator/docs.js +167 -0
  189. package/src/validator/expressions.js +146 -1
  190. package/src/validator/index.d.ts +23 -0
  191. package/src/validator/index.js +32 -3585
  192. package/src/validator/kinds.d.ts +41 -0
  193. package/src/validator/kinds.js +2 -0
  194. package/src/validator/model-helpers.js +46 -0
  195. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  196. package/src/validator/per-kind/bug.js +6 -0
  197. package/src/validator/per-kind/domain.js +15 -2
  198. package/src/validator/per-kind/pitch.js +7 -0
  199. package/src/validator/per-kind/requirement.js +5 -0
  200. package/src/validator/per-kind/task.js +7 -0
  201. package/src/validator/per-kind/widget.js +14 -0
  202. package/src/validator/projections/api-http-async.js +410 -0
  203. package/src/validator/projections/api-http-authz.js +88 -0
  204. package/src/validator/projections/api-http-core.js +205 -0
  205. package/src/validator/projections/api-http-policies.js +339 -0
  206. package/src/validator/projections/api-http-responses.js +233 -0
  207. package/src/validator/projections/api-http.js +44 -0
  208. package/src/validator/projections/db.js +353 -0
  209. package/src/validator/projections/generator-defaults.js +45 -0
  210. package/src/validator/projections/helpers.js +87 -0
  211. package/src/validator/projections/ui-helpers.js +214 -0
  212. package/src/validator/projections/ui-navigation.js +344 -0
  213. package/src/validator/projections/ui-structure.js +364 -0
  214. package/src/validator/projections/ui-widgets.js +493 -0
  215. package/src/validator/projections/ui.js +46 -0
  216. package/src/validator/registry.js +48 -1
  217. package/src/validator/utils.d.ts +20 -0
  218. package/src/validator/utils.js +115 -12
  219. package/src/widget-behavior.d.ts +1 -0
  220. package/src/workflows/import-app/api/collect.js +221 -0
  221. package/src/workflows/import-app/api/openapi.js +257 -0
  222. package/src/workflows/import-app/api/routes.js +327 -0
  223. package/src/workflows/import-app/api/sources.js +22 -0
  224. package/src/workflows/import-app/api.js +2 -797
  225. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  226. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  227. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  228. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  229. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  230. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  231. package/src/workflows/reconcile/adoption-plan.js +30 -740
  232. package/src/workflows/reconcile/auth/closures.js +115 -0
  233. package/src/workflows/reconcile/auth/formatters.js +142 -0
  234. package/src/workflows/reconcile/auth/inference.js +330 -0
  235. package/src/workflows/reconcile/auth/roles.js +122 -0
  236. package/src/workflows/reconcile/auth.js +35 -690
  237. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  238. package/src/workflows/reconcile/bundle-core.js +12 -598
  239. package/src/workflows/reconcile/canonical-surface.js +1 -1
  240. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  241. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  242. package/src/workflows/reconcile/impacts/patches.js +252 -0
  243. package/src/workflows/reconcile/impacts/reports.js +80 -0
  244. package/src/workflows/reconcile/impacts.js +14 -623
  245. package/src/workspace-docs.d.ts +29 -0
@@ -0,0 +1,514 @@
1
+ import {
2
+ getJourneyDoc,
3
+ getStatement,
4
+ getWorkflowDoc,
5
+ relatedProjectionsForCapability,
6
+ relatedProjectionsForEntity,
7
+ relatedProjectionsForWidget,
8
+ relatedShapesForProjection,
9
+ relatedWidgetsForProjection,
10
+ widgetById
11
+ } from "../../generator/context/shared.js";
12
+ import { generatorDefaultsMap } from "../../generator/surfaces/shared.js";
13
+ import {
14
+ stableSortedStrings,
15
+ targetWidgetId
16
+ } from "./common.js";
17
+ export function projectionKindForImpact(projection) {
18
+ if (!projection) return "unknown";
19
+ if ((projection.http || []).length > 0 || projection.type === "api" || projection.type === "backend") {
20
+ return "api";
21
+ }
22
+ if (
23
+ (projection.uiRoutes || []).length > 0 ||
24
+ (projection.uiWeb || []).length > 0 ||
25
+ (projection.uiIos || []).length > 0 ||
26
+ (projection.uiScreens || []).length > 0 ||
27
+ projection.type === "web_surface" ||
28
+ projection.type === "ios_surface" ||
29
+ projection.type === "ui_contract"
30
+ ) {
31
+ return "ui";
32
+ }
33
+ if (
34
+ (projection.dbTables || []).length > 0 ||
35
+ (projection.dbColumns || []).length > 0 ||
36
+ (projection.dbRelations || []).length > 0 ||
37
+ String(projection.type || "").startsWith("db_")
38
+ ) {
39
+ return "db";
40
+ }
41
+ return "unknown";
42
+ }
43
+
44
+ export function projectionSummaryForImpact(projection) {
45
+ if (!projection) return null;
46
+ return {
47
+ projection_id: projection.id,
48
+ kind: projectionKindForImpact(projection),
49
+ type: projection.type || null,
50
+ outputs: stableSortedStrings(projection.outputs || [])
51
+ };
52
+ }
53
+
54
+ export function projectionById(graph, projectionId) {
55
+ return (graph?.byKind?.projection || []).find((projection) => projection.id === projectionId) || null;
56
+ }
57
+
58
+ export function impactsFromProjectionIds(graph, projectionIds, impactSource, reasonBuilder) {
59
+ return projectionIds
60
+ .map((projectionId) => {
61
+ const projection = projectionById(graph, projectionId);
62
+ if (!projection) return null;
63
+ return {
64
+ ...projectionSummaryForImpact(projection),
65
+ impact_source: impactSource,
66
+ reason: reasonBuilder(projection)
67
+ };
68
+ })
69
+ .filter(Boolean);
70
+ }
71
+
72
+ export function addImpact(map, impact) {
73
+ if (!impact?.projection_id) return;
74
+ const existing = map.get(impact.projection_id);
75
+ if (!existing) {
76
+ map.set(impact.projection_id, {
77
+ ...impact,
78
+ impact_sources: [impact.impact_source],
79
+ reasons: [impact.reason]
80
+ });
81
+ return;
82
+ }
83
+
84
+ const priority = [
85
+ "direct_projection_change",
86
+ "selected_projection",
87
+ "changed_capability",
88
+ "changed_entity",
89
+ "changed_shape",
90
+ "changed_widget",
91
+ "changed_rule",
92
+ "changed_workflow",
93
+ "changed_journey",
94
+ "selected_rule",
95
+ "selected_capability",
96
+ "selected_entity",
97
+ "selected_workflow",
98
+ "selected_journey"
99
+ ];
100
+ existing.impact_sources = stableSortedStrings([...(existing.impact_sources || []), impact.impact_source]);
101
+ existing.reasons = stableSortedStrings([...(existing.reasons || []), impact.reason]);
102
+ const currentPriority = priority.indexOf(existing.impact_source);
103
+ const nextPriority = priority.indexOf(impact.impact_source);
104
+ if (currentPriority === -1 || (nextPriority !== -1 && nextPriority < currentPriority)) {
105
+ existing.impact_source = impact.impact_source;
106
+ existing.reason = impact.reason;
107
+ }
108
+ }
109
+
110
+ export function projectionImpactsFromRule(graph, ruleId, selected = false) {
111
+ let rule;
112
+ try {
113
+ rule = getStatement(graph, "rule", ruleId);
114
+ } catch {
115
+ return [];
116
+ }
117
+ const impacts = [];
118
+ for (const target of rule.appliesTo || []) {
119
+ if (target.id?.startsWith("cap_")) {
120
+ impacts.push(...impactsFromProjectionIds(
121
+ graph,
122
+ relatedProjectionsForCapability(graph, target.id),
123
+ selected ? "selected_rule" : "changed_rule",
124
+ (projection) => `Projection ${projection.id} is affected because rule ${ruleId} applies to capability ${target.id}.`
125
+ ));
126
+ } else if (target.id?.startsWith("entity_")) {
127
+ impacts.push(...impactsFromProjectionIds(
128
+ graph,
129
+ relatedProjectionsForEntity(graph, target.id),
130
+ selected ? "selected_rule" : "changed_rule",
131
+ (projection) => `Projection ${projection.id} is affected because rule ${ruleId} applies to entity ${target.id}.`
132
+ ));
133
+ } else if (target.kind === "projection" || target.id?.startsWith("proj_")) {
134
+ impacts.push(...impactsFromProjectionIds(
135
+ graph,
136
+ [target.id],
137
+ selected ? "selected_rule" : "changed_rule",
138
+ () => `Projection ${target.id} is affected because rule ${ruleId} applies directly to that projection.`
139
+ ));
140
+ }
141
+ }
142
+ return impacts;
143
+ }
144
+
145
+ export function projectionImpactsFromWorkflow(graph, workflowId, selected = false) {
146
+ let workflow;
147
+ try {
148
+ workflow = getWorkflowDoc(graph, workflowId);
149
+ } catch {
150
+ return [];
151
+ }
152
+ const direct = stableSortedStrings(workflow.relatedProjections || []);
153
+ const viaCapabilities = stableSortedStrings((workflow.relatedCapabilities || []).flatMap((capabilityId) =>
154
+ relatedProjectionsForCapability(graph, capabilityId)
155
+ ));
156
+
157
+ return [
158
+ ...impactsFromProjectionIds(
159
+ graph,
160
+ direct,
161
+ selected ? "selected_workflow" : "changed_workflow",
162
+ (projection) => `Projection ${projection.id} is affected because workflow ${workflowId} links to that projection directly.`
163
+ ),
164
+ ...impactsFromProjectionIds(
165
+ graph,
166
+ viaCapabilities.filter((id) => !direct.includes(id)),
167
+ selected ? "selected_workflow" : "changed_workflow",
168
+ (projection) => `Projection ${projection.id} is affected because workflow ${workflowId} includes related capabilities realized by that projection.`
169
+ )
170
+ ];
171
+ }
172
+
173
+ export function projectionImpactsFromJourney(graph, journeyId, selected = false) {
174
+ let journey;
175
+ try {
176
+ journey = getJourneyDoc(graph, journeyId);
177
+ } catch {
178
+ return [];
179
+ }
180
+ const direct = stableSortedStrings(journey.relatedProjections || []);
181
+ const viaCapabilities = stableSortedStrings((journey.relatedCapabilities || []).flatMap((capabilityId) =>
182
+ relatedProjectionsForCapability(graph, capabilityId)
183
+ ));
184
+ const viaWorkflows = stableSortedStrings((journey.relatedWorkflows || []).flatMap((workflowId) => {
185
+ try {
186
+ const workflow = getWorkflowDoc(graph, workflowId);
187
+ return workflow.relatedProjections || [];
188
+ } catch {
189
+ return [];
190
+ }
191
+ }));
192
+
193
+ return [
194
+ ...impactsFromProjectionIds(
195
+ graph,
196
+ direct,
197
+ selected ? "selected_journey" : "changed_journey",
198
+ (projection) => `Projection ${projection.id} is affected because journey ${journeyId} links to that projection directly.`
199
+ ),
200
+ ...impactsFromProjectionIds(
201
+ graph,
202
+ [...viaCapabilities, ...viaWorkflows].filter((id) => !direct.includes(id)),
203
+ selected ? "selected_journey" : "changed_journey",
204
+ (projection) => `Projection ${projection.id} is affected because journey ${journeyId} links to related workflow or capability surfaces realized by that projection.`
205
+ )
206
+ ];
207
+ }
208
+
209
+ export function projectionImpactsFromShape(graph, shapeId) {
210
+ const directProjectionIds = stableSortedStrings((graph?.byKind?.projection || [])
211
+ .filter((projection) => relatedShapesForProjection(projection).includes(shapeId))
212
+ .map((projection) => projection.id));
213
+ const viaCapabilities = stableSortedStrings((graph?.byKind?.capability || [])
214
+ .filter((capability) => [...(capability.input || []), ...(capability.output || [])].some((item) => item.id === shapeId))
215
+ .flatMap((capability) => relatedProjectionsForCapability(graph, capability.id)));
216
+
217
+ return [
218
+ ...impactsFromProjectionIds(
219
+ graph,
220
+ directProjectionIds,
221
+ "changed_shape",
222
+ (projection) => `Projection ${projection.id} is affected because it references changed shape ${shapeId} directly.`
223
+ ),
224
+ ...impactsFromProjectionIds(
225
+ graph,
226
+ viaCapabilities.filter((id) => !directProjectionIds.includes(id)),
227
+ "changed_shape",
228
+ (projection) => `Projection ${projection.id} is affected because changed shape ${shapeId} is used by related capabilities realized by that projection.`
229
+ )
230
+ ];
231
+ }
232
+
233
+ export function projectionImpactsFromWidget(graph, widgetId) {
234
+ const widget = widgetById(graph, widgetId);
235
+ if (!widget) {
236
+ return [];
237
+ }
238
+
239
+ return impactsFromProjectionIds(
240
+ graph,
241
+ relatedProjectionsForWidget(graph, widgetId),
242
+ "changed_widget",
243
+ (projection) => `Projection ${projection.id} is affected because widget ${widgetId} is used by that UI surface.`
244
+ ).map((impact) => ({
245
+ ...impact,
246
+ widget_ids: stableSortedStrings([widgetId])
247
+ }));
248
+ }
249
+
250
+ export function buildProjectionImpacts(graph, { sliceArtifact, diffArtifact }) {
251
+ const impactMap = new Map();
252
+
253
+ if (diffArtifact) {
254
+ for (const entry of diffArtifact.projections || []) {
255
+ addImpact(impactMap, {
256
+ ...projectionSummaryForImpact(projectionById(graph, entry.id)),
257
+ impact_source: "direct_projection_change",
258
+ reason: `Projection ${entry.id} changed directly in the semantic diff.`
259
+ });
260
+ }
261
+
262
+ for (const entry of diffArtifact.capabilities || []) {
263
+ for (const impact of impactsFromProjectionIds(
264
+ graph,
265
+ relatedProjectionsForCapability(graph, entry.id),
266
+ "changed_capability",
267
+ (projection) => `Projection ${projection.id} is affected because changed capability ${entry.id} is realized by that projection.`
268
+ )) addImpact(impactMap, impact);
269
+ }
270
+
271
+ for (const entry of diffArtifact.entities || []) {
272
+ for (const impact of impactsFromProjectionIds(
273
+ graph,
274
+ relatedProjectionsForEntity(graph, entry.id),
275
+ "changed_entity",
276
+ (projection) => `Projection ${projection.id} is affected because changed entity ${entry.id} participates in that projection.`
277
+ )) addImpact(impactMap, impact);
278
+ }
279
+
280
+ for (const entry of diffArtifact.shapes || []) {
281
+ for (const impact of projectionImpactsFromShape(graph, entry.id)) addImpact(impactMap, impact);
282
+ }
283
+
284
+ for (const entry of diffArtifact.widgets || diffArtifact.components || []) {
285
+ for (const impact of projectionImpactsFromWidget(graph, entry.id)) addImpact(impactMap, impact);
286
+ }
287
+
288
+ for (const entry of diffArtifact.rules || []) {
289
+ for (const impact of projectionImpactsFromRule(graph, entry.id)) addImpact(impactMap, impact);
290
+ }
291
+
292
+ for (const entry of diffArtifact.workflows || []) {
293
+ for (const impact of projectionImpactsFromWorkflow(graph, entry.id)) addImpact(impactMap, impact);
294
+ }
295
+
296
+ for (const entry of diffArtifact.journeys || []) {
297
+ for (const impact of projectionImpactsFromJourney(graph, entry.id)) addImpact(impactMap, impact);
298
+ }
299
+ } else if (sliceArtifact?.focus) {
300
+ const { kind, id } = sliceArtifact.focus;
301
+ if (kind === "projection") {
302
+ addImpact(impactMap, {
303
+ ...projectionSummaryForImpact(projectionById(graph, id)),
304
+ impact_source: "selected_projection",
305
+ reason: `Projection ${id} is the selected focus surface.`
306
+ });
307
+ } else if (kind === "capability") {
308
+ for (const impact of impactsFromProjectionIds(
309
+ graph,
310
+ stableSortedStrings(sliceArtifact.depends_on?.projections || relatedProjectionsForCapability(graph, id)),
311
+ "selected_capability",
312
+ (projection) => `Projection ${projection.id} is in scope because selected capability ${id} is realized by that projection.`
313
+ )) addImpact(impactMap, impact);
314
+ } else if (kind === "entity") {
315
+ for (const impact of impactsFromProjectionIds(
316
+ graph,
317
+ stableSortedStrings(sliceArtifact.depends_on?.projections || relatedProjectionsForEntity(graph, id)),
318
+ "selected_entity",
319
+ (projection) => `Projection ${projection.id} is in scope because selected entity ${id} participates in that projection.`
320
+ )) addImpact(impactMap, impact);
321
+ } else if (kind === "widget") {
322
+ for (const impact of projectionImpactsFromWidget(graph, id)) addImpact(impactMap, impact);
323
+ } else if (kind === "workflow") {
324
+ for (const impact of projectionImpactsFromWorkflow(graph, id, true)) addImpact(impactMap, impact);
325
+ } else if (kind === "journey") {
326
+ for (const impact of projectionImpactsFromJourney(graph, id, true)) addImpact(impactMap, impact);
327
+ }
328
+ }
329
+
330
+ return [...impactMap.values()]
331
+ .map((impact) => ({
332
+ ...impact,
333
+ impact_sources: stableSortedStrings(impact.impact_sources || []),
334
+ reasons: stableSortedStrings(impact.reasons || [])
335
+ }))
336
+ .sort((a, b) => a.projection_id.localeCompare(b.projection_id));
337
+ }
338
+
339
+ export function buildGeneratorTargets(graph, projectionImpacts = [], diffArtifact = null) {
340
+ const targets = [];
341
+ const addTarget = (target) => {
342
+ if (!target?.target) return;
343
+ if (target.component_id && !target.widget_id) {
344
+ target.widget_id = target.component_id;
345
+ delete target.component_id;
346
+ }
347
+ if (!target.projection_id && !target.widget_id) return;
348
+ if (!targets.some((entry) =>
349
+ entry.target === target.target &&
350
+ entry.projection_id === target.projection_id &&
351
+ targetWidgetId(entry) === targetWidgetId(target)
352
+ )) {
353
+ targets.push(target);
354
+ }
355
+ };
356
+
357
+ for (const entry of diffArtifact?.widgets || diffArtifact?.components || []) {
358
+ addTarget({
359
+ target: "ui-widget-contract",
360
+ widget_id: entry.id,
361
+ required: true,
362
+ reason: `Widget ${entry.id} changed directly, so its widget contract should be refreshed.`
363
+ });
364
+ addTarget({
365
+ target: "widget-behavior-report",
366
+ widget_id: entry.id,
367
+ required: true,
368
+ reason: `Widget ${entry.id} changed directly, so behavior realizations should be reviewed across affected projections.`
369
+ });
370
+ }
371
+
372
+ for (const impact of projectionImpacts) {
373
+ const projection = projectionById(graph, impact.projection_id);
374
+ if (!projection) continue;
375
+ const profile = generatorDefaultsMap(projection).profile || null;
376
+ const outputs = stableSortedStrings(projection.outputs || []);
377
+
378
+ if (impact.kind === "api") {
379
+ addTarget({
380
+ target: "api-contract-graph",
381
+ projection_id: impact.projection_id,
382
+ required: true,
383
+ reason: `Projection ${impact.projection_id} is API-facing, so API contract regeneration is required.`
384
+ });
385
+ addTarget({
386
+ target: "server-contract",
387
+ projection_id: impact.projection_id,
388
+ required: true,
389
+ reason: `Projection ${impact.projection_id} is API-facing, so the server contract should stay aligned.`
390
+ });
391
+ addTarget({
392
+ target: "api-contract-debug",
393
+ projection_id: impact.projection_id,
394
+ required: false,
395
+ reason: `Projection ${impact.projection_id} may benefit from API contract debug output during review.`
396
+ });
397
+ }
398
+
399
+ if (projection.type === "ui_contract") {
400
+ addTarget({
401
+ target: "ui-contract-graph",
402
+ projection_id: impact.projection_id,
403
+ required: true,
404
+ reason: `Projection ${impact.projection_id} is a shared UI surface, so the UI contract should be refreshed.`
405
+ });
406
+ addTarget({
407
+ target: "ui-contract-debug",
408
+ projection_id: impact.projection_id,
409
+ required: false,
410
+ reason: `Projection ${impact.projection_id} may benefit from UI contract debug output during review.`
411
+ });
412
+ }
413
+
414
+ const widgetIds = stableSortedStrings([
415
+ ...(impact.widget_ids || []),
416
+ ...(impact.kind === "ui" ? relatedWidgetsForProjection(graph, projection) : [])
417
+ ]);
418
+
419
+ for (const widgetId of widgetIds) {
420
+ addTarget({
421
+ target: "ui-widget-contract",
422
+ widget_id: widgetId,
423
+ projection_id: impact.projection_id,
424
+ required: true,
425
+ reason: `Projection ${impact.projection_id} is affected by widget ${widgetId}, so the widget contract should be refreshed.`
426
+ });
427
+ addTarget({
428
+ target: "widget-behavior-report",
429
+ widget_id: widgetId,
430
+ projection_id: impact.projection_id,
431
+ required: true,
432
+ reason: `Projection ${impact.projection_id} is affected by widget ${widgetId}, so behavior data/event/action wiring should be reviewed.`
433
+ });
434
+ }
435
+
436
+ if (projection.type === "web_surface") {
437
+ addTarget({
438
+ target: "ui-surface-contract",
439
+ projection_id: impact.projection_id,
440
+ required: true,
441
+ reason: `Projection ${impact.projection_id} is a web UI surface, so the web UI contract should be refreshed.`
442
+ });
443
+ addTarget({
444
+ target: "ui-surface-debug",
445
+ projection_id: impact.projection_id,
446
+ required: false,
447
+ reason: `Projection ${impact.projection_id} may benefit from UI web debug output during review.`
448
+ });
449
+ if (outputs.includes("web_app") && profile === "sveltekit") {
450
+ addTarget({
451
+ target: "sveltekit-app",
452
+ projection_id: impact.projection_id,
453
+ required: false,
454
+ reason: `Projection ${impact.projection_id} emits a web app with SvelteKit profile, so the app scaffold may need regeneration.`
455
+ });
456
+ }
457
+ }
458
+
459
+ if (projection.type === "ios_surface") {
460
+ addTarget({
461
+ target: "swiftui-app",
462
+ projection_id: impact.projection_id,
463
+ required: false,
464
+ reason: `Projection ${impact.projection_id} is a native iOS UI surface, so the SwiftUI client scaffold may need regeneration.`
465
+ });
466
+ }
467
+
468
+ if (String(projection.type || "").startsWith("db_")) {
469
+ addTarget({
470
+ target: "db-contract-graph",
471
+ projection_id: impact.projection_id,
472
+ required: true,
473
+ reason: `Projection ${impact.projection_id} is database-facing, so the DB contract graph should be refreshed.`
474
+ });
475
+ addTarget({
476
+ target: "db-contract-debug",
477
+ projection_id: impact.projection_id,
478
+ required: false,
479
+ reason: `Projection ${impact.projection_id} may benefit from DB contract debug output during review.`
480
+ });
481
+ addTarget({
482
+ target: "db-schema-snapshot",
483
+ projection_id: impact.projection_id,
484
+ required: true,
485
+ reason: `Projection ${impact.projection_id} is database-facing, so the schema snapshot should stay aligned.`
486
+ });
487
+
488
+ const schemaSensitiveDiff = Boolean(
489
+ diffArtifact &&
490
+ (
491
+ (diffArtifact.entities || []).length > 0 ||
492
+ (diffArtifact.shapes || []).length > 0 ||
493
+ (diffArtifact.projections || []).some((entry) => entry.id === impact.projection_id)
494
+ )
495
+ );
496
+ if (schemaSensitiveDiff) {
497
+ addTarget({
498
+ target: "db-migration-plan",
499
+ projection_id: impact.projection_id,
500
+ required: false,
501
+ reason: `Projection ${impact.projection_id} has schema-sensitive diff inputs, so reviewing a migration plan is recommended.`
502
+ });
503
+ }
504
+ }
505
+ }
506
+
507
+ return targets.sort((a, b) => {
508
+ const projectionCompare = String(a.projection_id || "").localeCompare(String(b.projection_id || ""));
509
+ if (projectionCompare !== 0) return projectionCompare;
510
+ const componentCompare = String(targetWidgetId(a) || "").localeCompare(String(targetWidgetId(b) || ""));
511
+ if (componentCompare !== 0) return componentCompare;
512
+ return projectionCompare !== 0 ? projectionCompare : a.target.localeCompare(b.target);
513
+ });
514
+ }