@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.
- package/package.json +1 -1
- package/src/adoption/plan/index.js +716 -0
- package/src/adoption/plan.js +12 -703
- package/src/adoption/reporting.js +1 -1
- package/src/agent-brief.js +7 -21
- package/src/agent-ops/query-builders/auth.js +375 -0
- package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
- package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
- package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
- package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
- package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
- package/src/agent-ops/query-builders/change-risk.js +25 -0
- package/src/agent-ops/query-builders/common.js +149 -0
- package/src/agent-ops/query-builders/maintained-risk.js +539 -0
- package/src/agent-ops/query-builders/maintained-shared.js +120 -0
- package/src/agent-ops/query-builders/multi-agent.js +547 -0
- package/src/agent-ops/query-builders/projection-impacts.js +514 -0
- package/src/agent-ops/query-builders/work-packets.js +417 -0
- package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
- package/src/agent-ops/query-builders/workflow-context.js +398 -0
- package/src/agent-ops/query-builders/workflow-presets-core.js +677 -0
- package/src/agent-ops/query-builders/workflow-presets.js +341 -0
- package/src/agent-ops/query-builders.d.ts +26 -26
- package/src/agent-ops/query-builders.js +42 -5021
- package/src/archive/jsonl.js +2 -2
- package/src/archive/resolver-bridge.js +1 -1
- package/src/archive/unarchive.js +2 -1
- package/src/catalog/constants.js +10 -0
- package/src/catalog/copy.js +65 -0
- package/src/catalog/diagnostics.js +15 -0
- package/src/catalog/entries.js +42 -0
- package/src/catalog/files.js +67 -0
- package/src/catalog/provenance.js +123 -0
- package/src/catalog/source.js +150 -0
- package/src/catalog/validation.js +252 -0
- package/src/catalog.d.ts +2 -0
- package/src/catalog.js +18 -746
- package/src/cli/command-parsers/project.js +3 -0
- package/src/cli/command-parsers/shared.js +1 -1
- package/src/cli/commands/agent.js +2 -2
- package/src/cli/commands/catalog/check.js +31 -0
- package/src/cli/commands/catalog/copy.js +59 -0
- package/src/cli/commands/catalog/doctor.js +248 -0
- package/src/cli/commands/catalog/help.js +21 -0
- package/src/cli/commands/catalog/list.js +52 -0
- package/src/cli/commands/catalog/runner.js +92 -0
- package/src/cli/commands/catalog/shared.js +17 -0
- package/src/cli/commands/catalog/show.js +134 -0
- package/src/cli/commands/catalog.js +30 -615
- package/src/cli/commands/check.js +3 -3
- package/src/cli/commands/doctor.js +2 -9
- package/src/cli/commands/generator-policy/package-info.js +162 -0
- package/src/cli/commands/generator-policy/payloads.js +372 -0
- package/src/cli/commands/generator-policy/printers.js +159 -0
- package/src/cli/commands/generator-policy/runner.js +81 -0
- package/src/cli/commands/generator-policy/shared.js +39 -0
- package/src/cli/commands/generator-policy.js +15 -783
- package/src/cli/commands/import/adopt.js +170 -0
- package/src/cli/commands/import/check.js +91 -0
- package/src/cli/commands/import/diff.js +84 -0
- package/src/cli/commands/import/help.js +47 -0
- package/src/cli/commands/import/paths.js +269 -0
- package/src/cli/commands/import/plan.js +292 -0
- package/src/cli/commands/import/refresh.js +471 -0
- package/src/cli/commands/import/status-history.js +196 -0
- package/src/cli/commands/import/workspace.js +233 -0
- package/src/cli/commands/import.js +33 -1732
- package/src/cli/commands/migrate.js +153 -0
- package/src/cli/commands/package/constants.js +17 -0
- package/src/cli/commands/package/doctor.js +240 -0
- package/src/cli/commands/package/help.js +27 -0
- package/src/cli/commands/package/lockfile.js +135 -0
- package/src/cli/commands/package/npm.js +97 -0
- package/src/cli/commands/package/reporting.js +35 -0
- package/src/cli/commands/package/runner.js +33 -0
- package/src/cli/commands/package/shared.js +9 -0
- package/src/cli/commands/package/update-cli.js +252 -0
- package/src/cli/commands/package/versions.js +35 -0
- package/src/cli/commands/package.js +29 -813
- package/src/cli/commands/query/change-plan.js +68 -0
- package/src/cli/commands/query/definitions.js +202 -0
- package/src/cli/commands/query/import-adopt.js +121 -0
- package/src/cli/commands/query/runner/artifacts.js +102 -0
- package/src/cli/commands/query/runner/boundaries.js +211 -0
- package/src/cli/commands/query/runner/change.js +182 -0
- package/src/cli/commands/query/runner/import-adopt.js +111 -0
- package/src/cli/commands/query/runner/index.js +31 -0
- package/src/cli/commands/query/runner/output.js +12 -0
- package/src/cli/commands/query/runner/workflow.js +241 -0
- package/src/cli/commands/query/runner.js +3 -0
- package/src/cli/commands/query/workflow-context.js +5 -0
- package/src/cli/commands/query/workspace.js +270 -0
- package/src/cli/commands/query.js +9 -1300
- package/src/cli/commands/source.js +3 -12
- package/src/cli/commands/template/baseline.js +100 -0
- package/src/cli/commands/template/check.js +467 -0
- package/src/cli/commands/template/constants.js +8 -0
- package/src/cli/commands/template/diagnostics.js +26 -0
- package/src/cli/commands/template/help.js +28 -0
- package/src/cli/commands/template/lifecycle.js +404 -0
- package/src/cli/commands/template/list-show.js +287 -0
- package/src/cli/commands/template/policy.js +422 -0
- package/src/cli/commands/template/shared.js +127 -0
- package/src/cli/commands/template/updates.js +352 -0
- package/src/cli/commands/template-runner.js +6 -6
- package/src/cli/commands/template.js +41 -2143
- package/src/cli/commands/trust.js +1 -1
- package/src/cli/commands/workflow.js +6 -1
- package/src/cli/dispatcher.js +6 -1
- package/src/cli/help.js +15 -14
- package/src/cli/migration-guidance.js +1 -1
- package/src/cli/output-safety.js +2 -1
- package/src/cli/path-normalization.js +3 -13
- package/src/generator/api/contracts.js +497 -0
- package/src/generator/api/metadata.js +221 -0
- package/src/generator/api/openapi.js +559 -0
- package/src/generator/api/schema.js +124 -0
- package/src/generator/api/types.d.ts +98 -0
- package/src/generator/api.js +3 -1195
- package/src/generator/context/domain-page.js +1 -1
- package/src/generator/context/shared/domain-sdlc.js +282 -0
- package/src/generator/context/shared/maintained-boundary.js +665 -0
- package/src/generator/context/shared/metrics.js +85 -0
- package/src/generator/context/shared/primitives.js +64 -0
- package/src/generator/context/shared/relationships.js +453 -0
- package/src/generator/context/shared/summaries.js +263 -0
- package/src/generator/context/shared/types.d.ts +207 -0
- package/src/generator/context/shared.d.ts +42 -0
- package/src/generator/context/shared.js +80 -1390
- package/src/generator/context/slice/core.js +397 -0
- package/src/generator/context/slice/sdlc.js +417 -0
- package/src/generator/context/slice/ui-packets.js +183 -0
- package/src/generator/context/slice.js +2 -859
- package/src/generator/context/task-mode.js +2 -2
- package/src/generator/registry/index.js +507 -0
- package/src/generator/registry.js +18 -504
- package/src/generator/runtime/environment/index.js +666 -0
- package/src/generator/runtime/environment.js +4 -666
- package/src/generator/runtime/runtime-check/index.js +554 -0
- package/src/generator/runtime/runtime-check.js +4 -554
- package/src/generator/runtime/shared/index.js +572 -0
- package/src/generator/runtime/shared.js +19 -570
- package/src/generator/sdlc/doc-page.js +1 -1
- package/src/generator/shared.d.ts +2 -0
- package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
- package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
- package/src/generator/surfaces/shared.d.ts +3 -0
- package/src/generator/widget-conformance/behavior-report.js +258 -0
- package/src/generator/widget-conformance/checks.js +371 -0
- package/src/generator/widget-conformance/projection-context.js +200 -0
- package/src/generator/widget-conformance/report.js +166 -0
- package/src/generator/widget-conformance/types.d.ts +121 -0
- package/src/generator/widget-conformance.js +3 -824
- package/src/import/core/context.d.ts +3 -0
- package/src/import/core/context.js +5 -7
- package/src/import/core/contracts.d.ts +1 -0
- package/src/import/core/registry.d.ts +4 -0
- package/src/import/core/runner/candidates.js +337 -0
- package/src/import/core/runner/options.js +22 -0
- package/src/import/core/runner/reports.js +51 -0
- package/src/import/core/runner/run.js +79 -0
- package/src/import/core/runner/tracks.js +150 -0
- package/src/import/core/runner/ui-drafts.js +393 -0
- package/src/import/core/runner.js +3 -698
- package/src/import/core/shared/api-routes.js +221 -0
- package/src/import/core/shared/candidates.js +97 -0
- package/src/import/core/shared/files.js +177 -0
- package/src/import/core/shared/next-app.js +389 -0
- package/src/import/core/shared/types.d.ts +51 -0
- package/src/import/core/shared/ui-routes.js +230 -0
- package/src/import/core/shared.js +60 -861
- package/src/new-project/constants.js +128 -0
- package/src/new-project/create.js +90 -0
- package/src/new-project/json.js +28 -0
- package/src/new-project/metadata.js +96 -0
- package/src/new-project/package-spec.js +161 -0
- package/src/new-project/project-files.js +351 -0
- package/src/new-project/template-policy.js +269 -0
- package/src/new-project/template-resolution.js +370 -0
- package/src/new-project/template-snapshots.js +442 -0
- package/src/new-project/template-updates.js +512 -0
- package/src/new-project/types.d.ts +83 -0
- package/src/new-project.js +6 -2277
- package/src/parser.d.ts +87 -1
- package/src/parser.js +118 -0
- package/src/policy/review-boundaries.d.ts +15 -0
- package/src/project-config/index.js +591 -0
- package/src/project-config.js +19 -561
- package/src/resolver/enrich/acceptance-criterion.js +2 -0
- package/src/resolver/enrich/bug.js +2 -0
- package/src/resolver/enrich/pitch.js +2 -0
- package/src/resolver/enrich/requirement.js +2 -0
- package/src/resolver/enrich/task.js +2 -0
- package/src/resolver/index.js +19 -2089
- package/src/resolver/normalize.js +384 -1
- package/src/resolver/plans.js +168 -0
- package/src/resolver/projections-api.js +494 -0
- package/src/resolver/projections-db.js +133 -0
- package/src/resolver/projections-ui.js +317 -0
- package/src/resolver/shapes.js +251 -0
- package/src/resolver/shared.js +278 -0
- package/src/resolver/widgets.js +132 -0
- package/src/sdlc/adopt.js +6 -5
- package/src/sdlc/paths.js +3 -5
- package/src/sdlc/scaffold.js +2 -1
- package/src/template-trust/constants.js +62 -0
- package/src/template-trust/content.js +258 -0
- package/src/template-trust/diff.js +92 -0
- package/src/template-trust/policy.js +61 -0
- package/src/template-trust/record.js +90 -0
- package/src/template-trust/status.js +182 -0
- package/src/template-trust.js +24 -687
- package/src/text-helpers.d.ts +1 -0
- package/src/topogram-types.d.ts +69 -0
- package/src/validator/common.js +488 -0
- package/src/validator/data-model.js +237 -0
- package/src/validator/docs.js +167 -0
- package/src/validator/expressions.js +146 -1
- package/src/validator/index.d.ts +23 -0
- package/src/validator/index.js +32 -3585
- package/src/validator/kinds.d.ts +41 -0
- package/src/validator/kinds.js +2 -0
- package/src/validator/model-helpers.js +46 -0
- package/src/validator/per-kind/acceptance-criterion.js +5 -0
- package/src/validator/per-kind/bug.js +6 -0
- package/src/validator/per-kind/domain.js +15 -2
- package/src/validator/per-kind/pitch.js +7 -0
- package/src/validator/per-kind/requirement.js +5 -0
- package/src/validator/per-kind/task.js +7 -0
- package/src/validator/per-kind/widget.js +14 -0
- package/src/validator/projections/api-http-async.js +410 -0
- package/src/validator/projections/api-http-authz.js +88 -0
- package/src/validator/projections/api-http-core.js +205 -0
- package/src/validator/projections/api-http-policies.js +339 -0
- package/src/validator/projections/api-http-responses.js +233 -0
- package/src/validator/projections/api-http.js +44 -0
- package/src/validator/projections/db.js +353 -0
- package/src/validator/projections/generator-defaults.js +45 -0
- package/src/validator/projections/helpers.js +87 -0
- package/src/validator/projections/ui-helpers.js +214 -0
- package/src/validator/projections/ui-navigation.js +344 -0
- package/src/validator/projections/ui-structure.js +364 -0
- package/src/validator/projections/ui-widgets.js +493 -0
- package/src/validator/projections/ui.js +46 -0
- package/src/validator/registry.js +48 -1
- package/src/validator/utils.d.ts +20 -0
- package/src/validator/utils.js +115 -12
- package/src/widget-behavior.d.ts +1 -0
- package/src/workflows/import-app/api/collect.js +221 -0
- package/src/workflows/import-app/api/openapi.js +257 -0
- package/src/workflows/import-app/api/routes.js +327 -0
- package/src/workflows/import-app/api/sources.js +22 -0
- package/src/workflows/import-app/api.js +2 -797
- package/src/workflows/reconcile/adoption-plan/build.js +212 -0
- package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
- package/src/workflows/reconcile/adoption-plan/outputs.js +153 -0
- package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
- package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
- package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
- package/src/workflows/reconcile/adoption-plan.js +30 -740
- package/src/workflows/reconcile/auth/closures.js +115 -0
- package/src/workflows/reconcile/auth/formatters.js +142 -0
- package/src/workflows/reconcile/auth/inference.js +330 -0
- package/src/workflows/reconcile/auth/roles.js +122 -0
- package/src/workflows/reconcile/auth.js +35 -690
- package/src/workflows/reconcile/bundle-core/index.js +600 -0
- package/src/workflows/reconcile/bundle-core.js +12 -598
- package/src/workflows/reconcile/candidate-model.js +18 -2
- package/src/workflows/reconcile/canonical-surface.js +1 -1
- package/src/workflows/reconcile/impacts/adoption-plan.js +196 -0
- package/src/workflows/reconcile/impacts/indexes.js +105 -0
- package/src/workflows/reconcile/impacts/patches.js +252 -0
- package/src/workflows/reconcile/impacts/reports.js +80 -0
- package/src/workflows/reconcile/impacts.js +14 -623
- package/src/workflows/reconcile/renderers.js +41 -6
- package/src/workflows/shared.js +5 -11
- package/src/workspace-docs.d.ts +29 -0
- package/src/workspace-paths.js +328 -0
|
@@ -1,825 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { buildWidgetBehaviorRealizations } from "../widget-behavior.js";
|
|
1
|
+
// @ts-check
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function stableUnique(values) {
|
|
9
|
-
return [...new Set(values.filter(Boolean))].sort();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function sourcePath(entry) {
|
|
13
|
-
return entry?.loc?.file || null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function widgetContract(widget) {
|
|
17
|
-
return widget?.widgetContract || null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function summarizeProjection(projection) {
|
|
21
|
-
return projection
|
|
22
|
-
? {
|
|
23
|
-
id: projection.id,
|
|
24
|
-
name: projection.name || projection.id,
|
|
25
|
-
type: projection.type || projection.type || null,
|
|
26
|
-
status: projection.status || null,
|
|
27
|
-
source_path: sourcePath(projection)
|
|
28
|
-
}
|
|
29
|
-
: null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function summarizeWidget(widget) {
|
|
33
|
-
return widget
|
|
34
|
-
? {
|
|
35
|
-
id: widget.id,
|
|
36
|
-
name: widget.name || widget.id,
|
|
37
|
-
category: widget.category || null,
|
|
38
|
-
version: widget.version || null,
|
|
39
|
-
status: widget.status || null,
|
|
40
|
-
source_path: sourcePath(widget)
|
|
41
|
-
}
|
|
42
|
-
: null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function summarizeWidgetContract(widget) {
|
|
46
|
-
const contract = widgetContract(widget);
|
|
47
|
-
if (!contract) return null;
|
|
48
|
-
return {
|
|
49
|
-
id: contract.id,
|
|
50
|
-
name: contract.name,
|
|
51
|
-
category: contract.category || null,
|
|
52
|
-
version: contract.version || null,
|
|
53
|
-
status: contract.status || null,
|
|
54
|
-
props: contract.props || [],
|
|
55
|
-
events: contract.events || [],
|
|
56
|
-
behaviors: contract.behaviors || [],
|
|
57
|
-
behavior: contract.behavior || [],
|
|
58
|
-
approvals: contract.approvals || [],
|
|
59
|
-
dependencies: contract.dependencies || [],
|
|
60
|
-
source_path: sourcePath(widget)
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function projectionRealizesIds(projection) {
|
|
65
|
-
return new Set((projection?.realizes || []).map((ref) => ref.id).filter(Boolean));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function ownProjectionScreenMap(projection) {
|
|
69
|
-
return new Map((projection?.uiScreens || []).map((screen) => [screen.id, screen]));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function ownProjectionRegionKeys(projection) {
|
|
73
|
-
return new Set((projection?.uiScreenRegions || []).map((entry) => `${entry.screenId}:${entry.region}`));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function projectionById(graph) {
|
|
77
|
-
return byId(graph.byKind.projection || []);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function projectionContext(graph, projection) {
|
|
81
|
-
const projections = [];
|
|
82
|
-
const seen = new Set();
|
|
83
|
-
const projectionsById = projectionById(graph);
|
|
84
|
-
|
|
85
|
-
function visit(current) {
|
|
86
|
-
if (!current || seen.has(current.id)) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
seen.add(current.id);
|
|
90
|
-
projections.push(current);
|
|
91
|
-
for (const ref of current.realizes || []) {
|
|
92
|
-
const target = projectionsById.get(ref.id);
|
|
93
|
-
if (target) {
|
|
94
|
-
visit(target);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
visit(projection);
|
|
100
|
-
return projections;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function projectionScreenMap(graph, projection) {
|
|
104
|
-
const screens = new Map();
|
|
105
|
-
for (const contextProjection of projectionContext(graph, projection).reverse()) {
|
|
106
|
-
for (const [id, screen] of ownProjectionScreenMap(contextProjection)) {
|
|
107
|
-
screens.set(id, screen);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return screens;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function projectionRegionKeys(graph, projection) {
|
|
114
|
-
const regions = new Set();
|
|
115
|
-
for (const contextProjection of projectionContext(graph, projection)) {
|
|
116
|
-
for (const key of ownProjectionRegionKeys(contextProjection)) {
|
|
117
|
-
regions.add(key);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return regions;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function projectionContextRealizesIds(graph, projection) {
|
|
124
|
-
const ids = new Set();
|
|
125
|
-
for (const contextProjection of projectionContext(graph, projection)) {
|
|
126
|
-
for (const id of projectionRealizesIds(contextProjection)) {
|
|
127
|
-
ids.add(id);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return ids;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function checkRecord({
|
|
134
|
-
code,
|
|
135
|
-
severity,
|
|
136
|
-
message,
|
|
137
|
-
projection,
|
|
138
|
-
sourceProjection,
|
|
139
|
-
widget,
|
|
140
|
-
usage,
|
|
141
|
-
prop = null,
|
|
142
|
-
event = null,
|
|
143
|
-
behavior = null,
|
|
144
|
-
suggestedFix
|
|
145
|
-
}) {
|
|
146
|
-
return {
|
|
147
|
-
code,
|
|
148
|
-
severity,
|
|
149
|
-
message,
|
|
150
|
-
projection: projection?.id || null,
|
|
151
|
-
source_projection: sourceProjection?.id || null,
|
|
152
|
-
widget: widget?.id || usage.widget?.id || null,
|
|
153
|
-
screen: usage.screenId || null,
|
|
154
|
-
region: usage.region || null,
|
|
155
|
-
prop,
|
|
156
|
-
event,
|
|
157
|
-
behavior,
|
|
158
|
-
suggested_fix: suggestedFix
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function widgetUsageKey(projection, sourceProjection, usage, index) {
|
|
163
|
-
return [
|
|
164
|
-
projection.id,
|
|
165
|
-
sourceProjection?.id || projection.id,
|
|
166
|
-
usage.screenId || "screen",
|
|
167
|
-
usage.region || "region",
|
|
168
|
-
usage.widget?.id || "widget",
|
|
169
|
-
String(index)
|
|
170
|
-
].join(":");
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function collectUsageChecks({ graph, projection, sourceProjection, usage, widget }) {
|
|
174
|
-
const checks = [];
|
|
175
|
-
const contract = widgetContract(widget);
|
|
176
|
-
const props = contract?.props || [];
|
|
177
|
-
const events = contract?.events || [];
|
|
178
|
-
const propNames = new Set(props.map((prop) => prop.name));
|
|
179
|
-
const eventNames = new Set(events.map((event) => event.id));
|
|
180
|
-
const boundProps = new Set((usage.dataBindings || []).map((binding) => binding.prop).filter(Boolean));
|
|
181
|
-
const statements = byId(graph.statements || []);
|
|
182
|
-
const screens = projectionScreenMap(graph, sourceProjection);
|
|
183
|
-
const regionKeys = projectionRegionKeys(graph, sourceProjection);
|
|
184
|
-
const realizedIds = projectionContextRealizesIds(graph, sourceProjection);
|
|
185
|
-
|
|
186
|
-
if (!widget) {
|
|
187
|
-
checks.push(checkRecord({
|
|
188
|
-
code: "widget_missing",
|
|
189
|
-
severity: "error",
|
|
190
|
-
message: `Widget '${usage.widget?.id || "(missing)"}' could not be resolved.`,
|
|
191
|
-
projection,
|
|
192
|
-
sourceProjection,
|
|
193
|
-
widget,
|
|
194
|
-
usage,
|
|
195
|
-
suggestedFix: "Create the widget or update the projection widget_bindings binding."
|
|
196
|
-
}));
|
|
197
|
-
return checks;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (projection.status === "active" && widget.status && widget.status !== "active") {
|
|
201
|
-
checks.push(checkRecord({
|
|
202
|
-
code: "widget_status_not_active",
|
|
203
|
-
severity: "warning",
|
|
204
|
-
message: `Active projection '${projection.id}' uses widget '${widget.id}' with status '${widget.status}'.`,
|
|
205
|
-
projection,
|
|
206
|
-
sourceProjection,
|
|
207
|
-
widget,
|
|
208
|
-
usage,
|
|
209
|
-
suggestedFix: "Promote the widget to active or move the usage behind an explicit review boundary."
|
|
210
|
-
}));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (!screens.has(usage.screenId)) {
|
|
214
|
-
checks.push(checkRecord({
|
|
215
|
-
code: "widget_usage_screen_missing",
|
|
216
|
-
severity: "error",
|
|
217
|
-
message: `Widget usage references missing screen '${usage.screenId}'.`,
|
|
218
|
-
projection,
|
|
219
|
-
sourceProjection,
|
|
220
|
-
widget,
|
|
221
|
-
usage,
|
|
222
|
-
suggestedFix: "Add the screen to screens or update the widget usage screen id."
|
|
223
|
-
}));
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (!regionKeys.has(`${usage.screenId}:${usage.region}`)) {
|
|
227
|
-
checks.push(checkRecord({
|
|
228
|
-
code: "widget_usage_region_missing",
|
|
229
|
-
severity: "error",
|
|
230
|
-
message: `Widget usage references undeclared region '${usage.region}' on screen '${usage.screenId}'.`,
|
|
231
|
-
projection,
|
|
232
|
-
sourceProjection,
|
|
233
|
-
widget,
|
|
234
|
-
usage,
|
|
235
|
-
suggestedFix: "Add the region to screen_regions or update the widget usage region."
|
|
236
|
-
}));
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
for (const prop of props.filter((entry) => entry.requiredness === "required")) {
|
|
240
|
-
if (!boundProps.has(prop.name)) {
|
|
241
|
-
checks.push(checkRecord({
|
|
242
|
-
code: "widget_required_prop_missing",
|
|
243
|
-
severity: "error",
|
|
244
|
-
message: `Required prop '${prop.name}' is not bound for widget '${widget.id}'.`,
|
|
245
|
-
projection,
|
|
246
|
-
sourceProjection,
|
|
247
|
-
widget,
|
|
248
|
-
usage,
|
|
249
|
-
prop: prop.name,
|
|
250
|
-
suggestedFix: `Add 'data ${prop.name} from <source>' to the projection widget_bindings entry.`
|
|
251
|
-
}));
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
for (const binding of usage.dataBindings || []) {
|
|
256
|
-
if (!propNames.has(binding.prop)) {
|
|
257
|
-
checks.push(checkRecord({
|
|
258
|
-
code: "widget_prop_unknown",
|
|
259
|
-
severity: "error",
|
|
260
|
-
message: `Prop '${binding.prop}' is not declared by widget '${widget.id}'.`,
|
|
261
|
-
projection,
|
|
262
|
-
sourceProjection,
|
|
263
|
-
widget,
|
|
264
|
-
usage,
|
|
265
|
-
prop: binding.prop || null,
|
|
266
|
-
suggestedFix: "Declare the prop on the widget or update the projection binding."
|
|
267
|
-
}));
|
|
268
|
-
}
|
|
269
|
-
if (!binding.source?.id || !statements.has(binding.source.id)) {
|
|
270
|
-
checks.push(checkRecord({
|
|
271
|
-
code: "widget_data_source_missing",
|
|
272
|
-
severity: "error",
|
|
273
|
-
message: `Data binding for prop '${binding.prop}' references a missing source.`,
|
|
274
|
-
projection,
|
|
275
|
-
sourceProjection,
|
|
276
|
-
widget,
|
|
277
|
-
usage,
|
|
278
|
-
prop: binding.prop || null,
|
|
279
|
-
suggestedFix: "Bind the prop to an existing capability, projection, shape, or entity."
|
|
280
|
-
}));
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
for (const binding of usage.eventBindings || []) {
|
|
285
|
-
if (!eventNames.has(binding.event)) {
|
|
286
|
-
checks.push(checkRecord({
|
|
287
|
-
code: "widget_event_unknown",
|
|
288
|
-
severity: "error",
|
|
289
|
-
message: `Event '${binding.event}' is not declared by widget '${widget.id}'.`,
|
|
290
|
-
projection,
|
|
291
|
-
sourceProjection,
|
|
292
|
-
widget,
|
|
293
|
-
usage,
|
|
294
|
-
event: binding.event || null,
|
|
295
|
-
suggestedFix: "Declare the event on the widget or update the projection binding."
|
|
296
|
-
}));
|
|
297
|
-
}
|
|
298
|
-
if (binding.action === "navigate") {
|
|
299
|
-
if (!screens.has(binding.target?.id)) {
|
|
300
|
-
checks.push(checkRecord({
|
|
301
|
-
code: "widget_event_navigation_target_missing",
|
|
302
|
-
severity: "error",
|
|
303
|
-
message: `Event '${binding.event}' navigates to missing screen '${binding.target?.id || "(missing)"}'.`,
|
|
304
|
-
projection,
|
|
305
|
-
sourceProjection,
|
|
306
|
-
widget,
|
|
307
|
-
usage,
|
|
308
|
-
event: binding.event || null,
|
|
309
|
-
suggestedFix: "Add the target screen or update the event navigation target."
|
|
310
|
-
}));
|
|
311
|
-
}
|
|
312
|
-
} else if (binding.action === "action") {
|
|
313
|
-
const target = binding.target?.id ? statements.get(binding.target.id) : null;
|
|
314
|
-
if (!target || target.kind !== "capability") {
|
|
315
|
-
checks.push(checkRecord({
|
|
316
|
-
code: "widget_event_action_missing",
|
|
317
|
-
severity: "error",
|
|
318
|
-
message: `Event '${binding.event}' targets missing capability '${binding.target?.id || "(missing)"}'.`,
|
|
319
|
-
projection,
|
|
320
|
-
sourceProjection,
|
|
321
|
-
widget,
|
|
322
|
-
usage,
|
|
323
|
-
event: binding.event || null,
|
|
324
|
-
suggestedFix: "Bind the event to an existing capability."
|
|
325
|
-
}));
|
|
326
|
-
} else if (!realizedIds.has(target.id)) {
|
|
327
|
-
checks.push(checkRecord({
|
|
328
|
-
code: "widget_event_action_not_in_projection",
|
|
329
|
-
severity: "error",
|
|
330
|
-
message: `Event '${binding.event}' targets capability '${target.id}', but projection '${sourceProjection.id}' does not realize it through its UI context.`,
|
|
331
|
-
projection,
|
|
332
|
-
sourceProjection,
|
|
333
|
-
widget,
|
|
334
|
-
usage,
|
|
335
|
-
event: binding.event || null,
|
|
336
|
-
suggestedFix: `Add '${target.id}' to projection '${sourceProjection.id}' or an inherited shared projection realizes list, or choose a capability already in this projection context.`
|
|
337
|
-
}));
|
|
338
|
-
}
|
|
339
|
-
} else {
|
|
340
|
-
checks.push(checkRecord({
|
|
341
|
-
code: "widget_event_action_unsupported",
|
|
342
|
-
severity: "error",
|
|
343
|
-
message: `Event '${binding.event}' uses unsupported action '${binding.action}'.`,
|
|
344
|
-
projection,
|
|
345
|
-
sourceProjection,
|
|
346
|
-
widget,
|
|
347
|
-
usage,
|
|
348
|
-
event: binding.event || null,
|
|
349
|
-
suggestedFix: "Use 'navigate' or 'action'."
|
|
350
|
-
}));
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
for (const behavior of contract?.behaviors || []) {
|
|
355
|
-
const stateProp = behavior.directives?.state;
|
|
356
|
-
if (stateProp && !propNames.has(stateProp)) {
|
|
357
|
-
checks.push(checkRecord({
|
|
358
|
-
code: "widget_behavior_prop_missing",
|
|
359
|
-
severity: "error",
|
|
360
|
-
message: `Behavior '${behavior.kind}' references missing prop '${stateProp}'.`,
|
|
361
|
-
projection,
|
|
362
|
-
sourceProjection,
|
|
363
|
-
widget,
|
|
364
|
-
usage,
|
|
365
|
-
prop: stateProp,
|
|
366
|
-
behavior: behavior.kind,
|
|
367
|
-
suggestedFix: "Update the behavior directive or declare the referenced prop."
|
|
368
|
-
}));
|
|
369
|
-
}
|
|
370
|
-
const emits = Array.isArray(behavior.directives?.emits)
|
|
371
|
-
? behavior.directives.emits
|
|
372
|
-
: [behavior.directives?.emits].filter(Boolean);
|
|
373
|
-
for (const eventName of emits) {
|
|
374
|
-
if (!eventNames.has(eventName)) {
|
|
375
|
-
checks.push(checkRecord({
|
|
376
|
-
code: "widget_behavior_event_missing",
|
|
377
|
-
severity: "error",
|
|
378
|
-
message: `Behavior '${behavior.kind}' references missing event '${eventName}'.`,
|
|
379
|
-
projection,
|
|
380
|
-
sourceProjection,
|
|
381
|
-
widget,
|
|
382
|
-
usage,
|
|
383
|
-
event: eventName,
|
|
384
|
-
behavior: behavior.kind,
|
|
385
|
-
suggestedFix: "Update the behavior directive or declare the referenced event."
|
|
386
|
-
}));
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
if (!(usage.eventBindings || []).some((binding) => binding.event === eventName)) {
|
|
390
|
-
checks.push(checkRecord({
|
|
391
|
-
code: "widget_behavior_event_unbound",
|
|
392
|
-
severity: "warning",
|
|
393
|
-
message: `Behavior '${behavior.kind}' emits event '${eventName}', but this projection usage does not bind that event to navigation or an action.`,
|
|
394
|
-
projection,
|
|
395
|
-
sourceProjection,
|
|
396
|
-
widget,
|
|
397
|
-
usage,
|
|
398
|
-
event: eventName,
|
|
399
|
-
behavior: behavior.kind,
|
|
400
|
-
suggestedFix: `Add 'event ${eventName} navigate <screen>' or 'event ${eventName} action <capability>' to the projection widget_bindings entry.`
|
|
401
|
-
}));
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
const declaredActions = [
|
|
405
|
-
...(Array.isArray(behavior.directives?.actions) ? behavior.directives.actions : [behavior.directives?.actions].filter(Boolean)),
|
|
406
|
-
...(Array.isArray(behavior.directives?.submit) ? behavior.directives.submit : [behavior.directives?.submit].filter(Boolean))
|
|
407
|
-
];
|
|
408
|
-
for (const actionTarget of declaredActions) {
|
|
409
|
-
if (eventNames.has(actionTarget)) {
|
|
410
|
-
if (!(usage.eventBindings || []).some((binding) => binding.event === actionTarget)) {
|
|
411
|
-
checks.push(checkRecord({
|
|
412
|
-
code: "widget_behavior_action_unbound",
|
|
413
|
-
severity: "warning",
|
|
414
|
-
message: `Behavior '${behavior.kind}' declares action event '${actionTarget}', but this projection usage does not bind that event to navigation or an action.`,
|
|
415
|
-
projection,
|
|
416
|
-
sourceProjection,
|
|
417
|
-
widget,
|
|
418
|
-
usage,
|
|
419
|
-
event: actionTarget,
|
|
420
|
-
behavior: behavior.kind,
|
|
421
|
-
suggestedFix: `Add 'event ${actionTarget} action <capability>' or 'event ${actionTarget} navigate <screen>' to the projection widget_bindings entry.`
|
|
422
|
-
}));
|
|
423
|
-
}
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
const target = statements.get(actionTarget);
|
|
428
|
-
if (!target || target.kind !== "capability") {
|
|
429
|
-
checks.push(checkRecord({
|
|
430
|
-
code: "widget_behavior_action_missing",
|
|
431
|
-
severity: "error",
|
|
432
|
-
message: `Behavior '${behavior.kind}' references missing capability action '${actionTarget}'.`,
|
|
433
|
-
projection,
|
|
434
|
-
sourceProjection,
|
|
435
|
-
widget,
|
|
436
|
-
usage,
|
|
437
|
-
behavior: behavior.kind,
|
|
438
|
-
suggestedFix: "Update the behavior directive or declare the referenced capability."
|
|
439
|
-
}));
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
if (!realizedIds.has(actionTarget)) {
|
|
443
|
-
checks.push(checkRecord({
|
|
444
|
-
code: "widget_behavior_action_not_in_projection",
|
|
445
|
-
severity: "error",
|
|
446
|
-
message: `Behavior '${behavior.kind}' references capability '${actionTarget}', but projection '${sourceProjection.id}' does not realize it.`,
|
|
447
|
-
projection,
|
|
448
|
-
sourceProjection,
|
|
449
|
-
widget,
|
|
450
|
-
usage,
|
|
451
|
-
behavior: behavior.kind,
|
|
452
|
-
suggestedFix: `Add '${actionTarget}' to projection '${sourceProjection.id}' realizes or choose a capability already in this projection context.`
|
|
453
|
-
}));
|
|
454
|
-
}
|
|
455
|
-
if (!(usage.eventBindings || []).some((binding) =>
|
|
456
|
-
binding.action === "action" &&
|
|
457
|
-
binding.target?.id === actionTarget &&
|
|
458
|
-
binding.target?.kind === "capability"
|
|
459
|
-
)) {
|
|
460
|
-
checks.push(checkRecord({
|
|
461
|
-
code: "widget_behavior_action_unbound",
|
|
462
|
-
severity: "warning",
|
|
463
|
-
message: `Behavior '${behavior.kind}' declares capability action '${actionTarget}', but this projection usage does not bind any widget event to that capability.`,
|
|
464
|
-
projection,
|
|
465
|
-
sourceProjection,
|
|
466
|
-
widget,
|
|
467
|
-
usage,
|
|
468
|
-
behavior: behavior.kind,
|
|
469
|
-
suggestedFix: `Add 'event <widget_event> action ${actionTarget}' to the projection widget_bindings entry.`
|
|
470
|
-
}));
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return checks;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function projectionUsageEntries(graph, projection) {
|
|
479
|
-
const sharedProjection = sharedUiProjectionForWeb(graph, projection);
|
|
480
|
-
const entries = [];
|
|
481
|
-
if (sharedProjection) {
|
|
482
|
-
entries.push(...(sharedProjection.widgetBindings || []).map((usage, index) => ({
|
|
483
|
-
projection,
|
|
484
|
-
sourceProjection: sharedProjection,
|
|
485
|
-
usage,
|
|
486
|
-
index
|
|
487
|
-
})));
|
|
488
|
-
}
|
|
489
|
-
entries.push(...(projection.widgetBindings || []).map((usage, index) => ({
|
|
490
|
-
projection,
|
|
491
|
-
sourceProjection: projection,
|
|
492
|
-
usage,
|
|
493
|
-
index
|
|
494
|
-
})));
|
|
495
|
-
return entries;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
function candidateProjections(graph, projectionId) {
|
|
499
|
-
if (projectionId) {
|
|
500
|
-
return [getProjection(graph, projectionId)];
|
|
501
|
-
}
|
|
502
|
-
const direct = uiProjectionCandidates(graph).filter((projection) => (projection.widgetBindings || []).length > 0);
|
|
503
|
-
const inherited = (graph.byKind.projection || []).filter((projection) => {
|
|
504
|
-
if ((projection.widgetBindings || []).length > 0) return false;
|
|
505
|
-
return Boolean(sharedUiProjectionForWeb(graph, projection)?.widgetBindings?.length);
|
|
506
|
-
});
|
|
507
|
-
return [...direct, ...inherited].sort((a, b) => a.id.localeCompare(b.id));
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function relatedVerificationFiles(graph, widgetIds, projectionIds) {
|
|
511
|
-
const ids = new Set([...widgetIds, ...projectionIds]);
|
|
512
|
-
return stableUnique((graph.byKind.verification || [])
|
|
513
|
-
.filter((verification) => (verification.validates || []).some((ref) => ids.has(ref.id)))
|
|
514
|
-
.map((verification) => sourcePath(verification)));
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
export function generateWidgetConformanceReport(graph, options = {}) {
|
|
518
|
-
const selectedWidgetId = options.widgetId || options.componentId || null;
|
|
519
|
-
const widgets = byId(graph.byKind.widget || []);
|
|
520
|
-
if (selectedWidgetId && !widgets.has(selectedWidgetId)) {
|
|
521
|
-
throw new Error(`No widget found with id '${selectedWidgetId}'`);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
const projectionUsageRecords = [];
|
|
525
|
-
const checks = [];
|
|
526
|
-
const referencedWidgetIds = new Set();
|
|
527
|
-
const affectedProjectionIds = new Set();
|
|
528
|
-
|
|
529
|
-
for (const projection of candidateProjections(graph, options.projectionId)) {
|
|
530
|
-
for (const entry of projectionUsageEntries(graph, projection)) {
|
|
531
|
-
const widgetId = entry.usage.widget?.id || null;
|
|
532
|
-
if (selectedWidgetId && widgetId !== selectedWidgetId) {
|
|
533
|
-
continue;
|
|
534
|
-
}
|
|
535
|
-
const widget = widgetId ? widgets.get(widgetId) : null;
|
|
536
|
-
if (widgetId) referencedWidgetIds.add(widgetId);
|
|
537
|
-
affectedProjectionIds.add(entry.projection.id);
|
|
538
|
-
if (entry.sourceProjection?.id) affectedProjectionIds.add(entry.sourceProjection.id);
|
|
539
|
-
const usageChecks = collectUsageChecks({ graph, projection: entry.projection, sourceProjection: entry.sourceProjection, usage: entry.usage, widget });
|
|
540
|
-
checks.push(...usageChecks);
|
|
541
|
-
const outcome = usageChecks.some((check) => check.severity === "error")
|
|
542
|
-
? "error"
|
|
543
|
-
: usageChecks.some((check) => check.severity === "warning")
|
|
544
|
-
? "warning"
|
|
545
|
-
: "pass";
|
|
546
|
-
projectionUsageRecords.push({
|
|
547
|
-
key: widgetUsageKey(entry.projection, entry.sourceProjection, entry.usage, entry.index),
|
|
548
|
-
projection: summarizeProjection(entry.projection),
|
|
549
|
-
source_projection: entry.sourceProjection.id === entry.projection.id ? null : summarizeProjection(entry.sourceProjection),
|
|
550
|
-
screen: {
|
|
551
|
-
id: entry.usage.screenId || null,
|
|
552
|
-
kind: projectionScreenMap(graph, entry.sourceProjection).get(entry.usage.screenId)?.kind || null,
|
|
553
|
-
title: projectionScreenMap(graph, entry.sourceProjection).get(entry.usage.screenId)?.title || null
|
|
554
|
-
},
|
|
555
|
-
region: entry.usage.region || null,
|
|
556
|
-
widget: summarizeWidget(widget) || { id: widgetId, name: widgetId, category: null, version: null, status: null, source_path: null },
|
|
557
|
-
data_bindings: entry.usage.dataBindings || [],
|
|
558
|
-
event_bindings: entry.usage.eventBindings || [],
|
|
559
|
-
behavior_realizations: buildWidgetBehaviorRealizations(widgetContract(widget), entry.usage),
|
|
560
|
-
outcome,
|
|
561
|
-
check_codes: usageChecks.map((check) => check.code)
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const widgetFiles = stableUnique([...referencedWidgetIds].map((id) => sourcePath(widgets.get(id))));
|
|
567
|
-
const projectionFiles = stableUnique(
|
|
568
|
-
[...affectedProjectionIds].map((id) => sourcePath((graph.byKind.projection || []).find((projection) => projection.id === id)))
|
|
569
|
-
);
|
|
570
|
-
const verificationFiles = relatedVerificationFiles(graph, referencedWidgetIds, affectedProjectionIds);
|
|
571
|
-
const errors = checks.filter((check) => check.severity === "error");
|
|
572
|
-
const warnings = checks.filter((check) => check.severity === "warning");
|
|
573
|
-
|
|
574
|
-
return {
|
|
575
|
-
type: "widget_conformance_report",
|
|
576
|
-
filters: {
|
|
577
|
-
projection: options.projectionId || null,
|
|
578
|
-
widget: selectedWidgetId
|
|
579
|
-
},
|
|
580
|
-
summary: {
|
|
581
|
-
total_usages: projectionUsageRecords.length,
|
|
582
|
-
passed_usages: projectionUsageRecords.filter((usage) => usage.outcome === "pass").length,
|
|
583
|
-
warning_usages: projectionUsageRecords.filter((usage) => usage.outcome === "warning").length,
|
|
584
|
-
error_usages: projectionUsageRecords.filter((usage) => usage.outcome === "error").length,
|
|
585
|
-
warnings: warnings.length,
|
|
586
|
-
errors: errors.length,
|
|
587
|
-
affected_projections: stableUnique([...affectedProjectionIds]),
|
|
588
|
-
affected_widgets: stableUnique([...referencedWidgetIds])
|
|
589
|
-
},
|
|
590
|
-
projection_usages: projectionUsageRecords,
|
|
591
|
-
checks,
|
|
592
|
-
widget_contracts: stableUnique([...referencedWidgetIds])
|
|
593
|
-
.map((id) => summarizeWidgetContract(widgets.get(id)))
|
|
594
|
-
.filter(Boolean),
|
|
595
|
-
write_scope: {
|
|
596
|
-
widget_files: widgetFiles,
|
|
597
|
-
projection_files: projectionFiles,
|
|
598
|
-
verification_files: verificationFiles,
|
|
599
|
-
paths: stableUnique([...widgetFiles, ...projectionFiles, ...verificationFiles])
|
|
600
|
-
},
|
|
601
|
-
impact: {
|
|
602
|
-
projections: stableUnique([...affectedProjectionIds]),
|
|
603
|
-
widgets: stableUnique([...referencedWidgetIds])
|
|
604
|
-
}
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function behaviorReportKey(usage, behavior, index) {
|
|
609
|
-
return [usage.key, behavior.kind || "behavior", String(index)].join(":");
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
function capabilityIdsFromBehavior(behavior) {
|
|
613
|
-
const ids = [];
|
|
614
|
-
for (const dependency of behavior.dataDependencies || []) {
|
|
615
|
-
if (dependency.source?.kind === "capability" && dependency.source.id) {
|
|
616
|
-
ids.push(dependency.source.id);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
for (const action of behavior.actions || []) {
|
|
620
|
-
if (action.capability?.id) {
|
|
621
|
-
ids.push(action.capability.id);
|
|
622
|
-
}
|
|
623
|
-
for (const effect of action.effects || []) {
|
|
624
|
-
if (effect.capability?.id) {
|
|
625
|
-
ids.push(effect.capability.id);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
for (const effect of behavior.effects || []) {
|
|
630
|
-
if (effect.capability?.id) {
|
|
631
|
-
ids.push(effect.capability.id);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
return stableUnique(ids);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
function effectTypesFromBehavior(behavior) {
|
|
638
|
-
const effects = behavior.effects || [];
|
|
639
|
-
if (effects.length === 0) {
|
|
640
|
-
return ["none"];
|
|
641
|
-
}
|
|
642
|
-
return stableUnique(effects.map((effect) => effect.type || "unknown"));
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
function checksForBehavior(conformanceReport, usage, behavior) {
|
|
646
|
-
return (conformanceReport.checks || [])
|
|
647
|
-
.filter((check) =>
|
|
648
|
-
check.code?.startsWith("widget_behavior_") &&
|
|
649
|
-
check.projection === usage.projection?.id &&
|
|
650
|
-
check.widget === usage.widget?.id &&
|
|
651
|
-
check.screen === usage.screen?.id &&
|
|
652
|
-
check.region === usage.region &&
|
|
653
|
-
(!check.behavior || check.behavior === behavior.kind)
|
|
654
|
-
)
|
|
655
|
-
.map((check) => ({
|
|
656
|
-
code: check.code,
|
|
657
|
-
severity: check.severity,
|
|
658
|
-
message: check.message,
|
|
659
|
-
event: check.event || null,
|
|
660
|
-
behavior: check.behavior || behavior.kind || null,
|
|
661
|
-
suggested_fix: check.suggested_fix || null
|
|
662
|
-
}));
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
function behaviorHighlights(behaviorRows) {
|
|
666
|
-
const highlights = [];
|
|
667
|
-
for (const row of behaviorRows) {
|
|
668
|
-
if (row.behavior.status === "partial") {
|
|
669
|
-
highlights.push({
|
|
670
|
-
severity: "warning",
|
|
671
|
-
code: "widget_behavior_partial",
|
|
672
|
-
message: `Behavior '${row.behavior.kind}' is partially realized for widget '${row.widget.id}' on screen '${row.screen.id}'.`,
|
|
673
|
-
projection: row.projection.id,
|
|
674
|
-
widget: row.widget.id,
|
|
675
|
-
screen: row.screen.id,
|
|
676
|
-
region: row.region,
|
|
677
|
-
behavior: row.behavior.kind,
|
|
678
|
-
suggested_fix: "Bind the required behavior data, events, or capability actions in the projection widget_bindings entry."
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
for (const emittedEvent of row.emits || []) {
|
|
682
|
-
if (!emittedEvent.bound) {
|
|
683
|
-
highlights.push({
|
|
684
|
-
severity: "warning",
|
|
685
|
-
code: "widget_behavior_event_unbound",
|
|
686
|
-
message: `Behavior '${row.behavior.kind}' emits event '${emittedEvent.event}', but this widget usage does not bind it.`,
|
|
687
|
-
projection: row.projection.id,
|
|
688
|
-
widget: row.widget.id,
|
|
689
|
-
screen: row.screen.id,
|
|
690
|
-
region: row.region,
|
|
691
|
-
event: emittedEvent.event || null,
|
|
692
|
-
behavior: row.behavior.kind,
|
|
693
|
-
suggested_fix: `Add 'event ${emittedEvent.event} navigate <screen>' or 'event ${emittedEvent.event} action <capability>' to the projection widget_bindings entry.`
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
for (const action of row.actions || []) {
|
|
698
|
-
if (!action.bound) {
|
|
699
|
-
const target = action.capability?.id || action.event || "(unknown)";
|
|
700
|
-
highlights.push({
|
|
701
|
-
severity: "warning",
|
|
702
|
-
code: "widget_behavior_action_unbound",
|
|
703
|
-
message: `Behavior '${row.behavior.kind}' declares action '${target}', but this widget usage does not bind it.`,
|
|
704
|
-
projection: row.projection.id,
|
|
705
|
-
widget: row.widget.id,
|
|
706
|
-
screen: row.screen.id,
|
|
707
|
-
region: row.region,
|
|
708
|
-
event: action.event || null,
|
|
709
|
-
capability: action.capability?.id || null,
|
|
710
|
-
behavior: row.behavior.kind,
|
|
711
|
-
suggested_fix: action.capability?.id
|
|
712
|
-
? `Add 'event <widget_event> action ${action.capability.id}' to the projection widget_bindings entry.`
|
|
713
|
-
: `Add 'event ${action.event} action <capability>' or 'event ${action.event} navigate <screen>' to the projection widget_bindings entry.`
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
return highlights;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
function groupBehaviorRows(rows, keyFn, itemFn = null) {
|
|
722
|
-
const groups = new Map();
|
|
723
|
-
for (const row of rows) {
|
|
724
|
-
for (const key of keyFn(row)) {
|
|
725
|
-
if (!groups.has(key)) {
|
|
726
|
-
groups.set(key, []);
|
|
727
|
-
}
|
|
728
|
-
groups.get(key).push(row);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
return [...groups.entries()]
|
|
732
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
733
|
-
.map(([id, entries]) => ({
|
|
734
|
-
id,
|
|
735
|
-
total_behaviors: entries.length,
|
|
736
|
-
realized: entries.filter((entry) => entry.behavior.status === "realized").length,
|
|
737
|
-
partial: entries.filter((entry) => entry.behavior.status === "partial").length,
|
|
738
|
-
declared: entries.filter((entry) => entry.behavior.status === "declared").length,
|
|
739
|
-
behaviors: entries.map((entry) => itemFn ? itemFn(entry) : entry.key).sort()
|
|
740
|
-
}));
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
export function generateWidgetBehaviorReport(graph, options = {}) {
|
|
744
|
-
const conformanceReport = generateWidgetConformanceReport(graph, options);
|
|
745
|
-
const behaviorRows = [];
|
|
746
|
-
|
|
747
|
-
for (const usage of conformanceReport.projection_usages || []) {
|
|
748
|
-
for (const [index, behavior] of (usage.behavior_realizations || []).entries()) {
|
|
749
|
-
const diagnostics = checksForBehavior(conformanceReport, usage, behavior);
|
|
750
|
-
behaviorRows.push({
|
|
751
|
-
key: behaviorReportKey(usage, behavior, index),
|
|
752
|
-
projection: usage.projection,
|
|
753
|
-
source_projection: usage.source_projection,
|
|
754
|
-
screen: usage.screen,
|
|
755
|
-
region: usage.region,
|
|
756
|
-
widget: usage.widget,
|
|
757
|
-
behavior: {
|
|
758
|
-
kind: behavior.kind || null,
|
|
759
|
-
source: behavior.source || null,
|
|
760
|
-
status: behavior.status || "declared",
|
|
761
|
-
directives: behavior.directives || {}
|
|
762
|
-
},
|
|
763
|
-
data_dependencies: behavior.dataDependencies || [],
|
|
764
|
-
emits: behavior.emits || [],
|
|
765
|
-
actions: behavior.actions || [],
|
|
766
|
-
effects: behavior.effects || [],
|
|
767
|
-
capabilities: capabilityIdsFromBehavior(behavior),
|
|
768
|
-
effect_types: effectTypesFromBehavior(behavior),
|
|
769
|
-
diagnostics,
|
|
770
|
-
check_codes: diagnostics.map((check) => check.code)
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
const highlights = behaviorHighlights(behaviorRows);
|
|
776
|
-
const affectedCapabilities = stableUnique(behaviorRows.flatMap((row) => row.capabilities));
|
|
777
|
-
|
|
778
|
-
return {
|
|
779
|
-
type: "widget_behavior_report",
|
|
780
|
-
filters: conformanceReport.filters,
|
|
781
|
-
summary: {
|
|
782
|
-
total_usages: conformanceReport.summary.total_usages,
|
|
783
|
-
total_behaviors: behaviorRows.length,
|
|
784
|
-
realized: behaviorRows.filter((row) => row.behavior.status === "realized").length,
|
|
785
|
-
partial: behaviorRows.filter((row) => row.behavior.status === "partial").length,
|
|
786
|
-
declared: behaviorRows.filter((row) => row.behavior.status === "declared").length,
|
|
787
|
-
warnings: conformanceReport.summary.warnings,
|
|
788
|
-
errors: conformanceReport.summary.errors,
|
|
789
|
-
affected_widgets: conformanceReport.summary.affected_widgets,
|
|
790
|
-
affected_projections: conformanceReport.summary.affected_projections,
|
|
791
|
-
affected_capabilities: affectedCapabilities
|
|
792
|
-
},
|
|
793
|
-
groups: {
|
|
794
|
-
widgets: groupBehaviorRows(
|
|
795
|
-
behaviorRows,
|
|
796
|
-
(row) => [row.widget.id].filter(Boolean),
|
|
797
|
-
(row) => row.key
|
|
798
|
-
),
|
|
799
|
-
screens: groupBehaviorRows(
|
|
800
|
-
behaviorRows,
|
|
801
|
-
(row) => [row.screen.id].filter(Boolean),
|
|
802
|
-
(row) => row.key
|
|
803
|
-
),
|
|
804
|
-
capabilities: groupBehaviorRows(
|
|
805
|
-
behaviorRows,
|
|
806
|
-
(row) => row.capabilities,
|
|
807
|
-
(row) => row.key
|
|
808
|
-
),
|
|
809
|
-
effects: groupBehaviorRows(
|
|
810
|
-
behaviorRows,
|
|
811
|
-
(row) => row.effect_types,
|
|
812
|
-
(row) => row.key
|
|
813
|
-
)
|
|
814
|
-
},
|
|
815
|
-
behaviors: behaviorRows,
|
|
816
|
-
highlights,
|
|
817
|
-
checks: conformanceReport.checks.filter((check) => check.code?.startsWith("widget_behavior_")),
|
|
818
|
-
write_scope: conformanceReport.write_scope,
|
|
819
|
-
impact: {
|
|
820
|
-
projections: conformanceReport.impact.projections,
|
|
821
|
-
widgets: conformanceReport.impact.widgets,
|
|
822
|
-
capabilities: affectedCapabilities
|
|
823
|
-
}
|
|
824
|
-
};
|
|
825
|
-
}
|
|
3
|
+
export { generateWidgetBehaviorReport } from "./widget-conformance/behavior-report.js";
|
|
4
|
+
export { generateWidgetConformanceReport } from "./widget-conformance/report.js";
|