@invariant-ui/cli 0.0.0-stage → 0.1.0

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 (196) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +248 -2
  3. package/dist/agent-kit/apply.js +232 -0
  4. package/dist/agent-kit/guidance.js +236 -0
  5. package/dist/agent-kit/index.js +5 -0
  6. package/dist/agent-kit/integrations.js +121 -0
  7. package/dist/agent-kit/plugin.js +115 -0
  8. package/dist/artifact-generator/index.js +75 -0
  9. package/dist/browser-verify/driver.js +2 -0
  10. package/dist/browser-verify/index.js +4 -0
  11. package/dist/browser-verify/playwright.js +82 -0
  12. package/dist/browser-verify/runner.js +175 -0
  13. package/dist/browser-verify/spec.js +103 -0
  14. package/dist/cli/args.js +77 -0
  15. package/dist/cli/baseline.js +64 -0
  16. package/dist/cli/cli.js +121 -0
  17. package/dist/cli/commands-browser.js +104 -0
  18. package/dist/cli/commands-discover.js +312 -0
  19. package/dist/cli/commands-enforcement.js +35 -0
  20. package/dist/cli/commands-governance.js +179 -0
  21. package/dist/cli/commands-hook.js +269 -0
  22. package/dist/cli/commands-patterns.js +324 -0
  23. package/dist/cli/commands-setup.js +485 -0
  24. package/dist/cli/commands-validate.js +157 -0
  25. package/dist/cli/commands.js +517 -0
  26. package/dist/cli/config.js +197 -0
  27. package/dist/cli/diff.js +66 -0
  28. package/dist/cli/enforcement-log.js +83 -0
  29. package/dist/cli/enforcement-report.js +96 -0
  30. package/dist/cli/errors.js +10 -0
  31. package/dist/cli/format.js +22 -0
  32. package/dist/cli/index.js +5 -0
  33. package/dist/cli/io.js +2 -0
  34. package/dist/cli/main.js +31 -0
  35. package/dist/cli/pipeline.js +262 -0
  36. package/dist/cli/publish.js +77 -0
  37. package/dist/cli/report.js +127 -0
  38. package/dist/cli/starter-rules.js +57 -0
  39. package/dist/cli/store.js +60 -0
  40. package/dist/cli/version.js +5 -0
  41. package/dist/cli/watch.js +68 -0
  42. package/dist/cli/workflow.js +52 -0
  43. package/dist/compiler-angular/adapter.js +115 -0
  44. package/dist/compiler-angular/analyze.js +222 -0
  45. package/dist/compiler-angular/classes.js +446 -0
  46. package/dist/compiler-angular/declared.js +285 -0
  47. package/dist/compiler-angular/emit.js +183 -0
  48. package/dist/compiler-angular/index.js +4 -0
  49. package/dist/compiler-angular/template.js +142 -0
  50. package/dist/compiler-core/adapter.js +2 -0
  51. package/dist/compiler-core/adapters/inventory.js +312 -0
  52. package/dist/compiler-core/compile.js +148 -0
  53. package/dist/compiler-core/component-facts.js +123 -0
  54. package/dist/compiler-core/discovery/docs.js +66 -0
  55. package/dist/compiler-core/discovery/entries.js +270 -0
  56. package/dist/compiler-core/discovery/exports.js +207 -0
  57. package/dist/compiler-core/discovery/graph.js +59 -0
  58. package/dist/compiler-core/discovery/index.js +103 -0
  59. package/dist/compiler-core/discovery/packages.js +148 -0
  60. package/dist/compiler-core/discovery/schema.js +144 -0
  61. package/dist/compiler-core/discovery/storybook.js +232 -0
  62. package/dist/compiler-core/discovery/support.js +82 -0
  63. package/dist/compiler-core/discovery/tokens.js +186 -0
  64. package/dist/compiler-core/discovery/tooling.js +146 -0
  65. package/dist/compiler-core/fs.js +89 -0
  66. package/dist/compiler-core/glob.js +3 -0
  67. package/dist/compiler-core/index.js +16 -0
  68. package/dist/compiler-core/ir-builder.js +68 -0
  69. package/dist/compiler-core/json-schema.js +11 -0
  70. package/dist/compiler-core/testing/index.js +81 -0
  71. package/dist/compiler-core/ts/ast.js +166 -0
  72. package/dist/compiler-core/ts/members.js +163 -0
  73. package/dist/compiler-core/ts/program.js +81 -0
  74. package/dist/compiler-core/ts/static-eval.js +145 -0
  75. package/dist/compiler-core/ts/tsconfig-paths.js +60 -0
  76. package/dist/compiler-core/walk.js +46 -0
  77. package/dist/compiler-ir/index.js +4 -0
  78. package/dist/compiler-ir/ir.js +67 -0
  79. package/dist/compiler-ir/json-schema.js +6 -0
  80. package/dist/compiler-ir/validate.js +116 -0
  81. package/dist/compiler-react/adapter.js +147 -0
  82. package/dist/compiler-react/analyze.js +199 -0
  83. package/dist/compiler-react/ast.js +105 -0
  84. package/dist/compiler-react/behavior.js +75 -0
  85. package/dist/compiler-react/detect.js +558 -0
  86. package/dist/compiler-react/emit.js +188 -0
  87. package/dist/compiler-react/index.js +2 -0
  88. package/dist/compiler-react/program.js +58 -0
  89. package/dist/compiler-react/props.js +157 -0
  90. package/dist/compiler-react/styling.js +138 -0
  91. package/dist/compiler-react/usage.js +116 -0
  92. package/dist/compiler-react/variants.js +111 -0
  93. package/dist/compiler-storybook/adapter.js +253 -0
  94. package/dist/compiler-storybook/csf.js +521 -0
  95. package/dist/compiler-storybook/emit.js +202 -0
  96. package/dist/compiler-storybook/index-json.js +41 -0
  97. package/dist/compiler-storybook/index.js +5 -0
  98. package/dist/compiler-storybook/mdx.js +120 -0
  99. package/dist/compiler-storybook/static-eval.js +145 -0
  100. package/dist/compiler-tokens/adapter.js +158 -0
  101. package/dist/compiler-tokens/css.js +59 -0
  102. package/dist/compiler-tokens/index.js +5 -0
  103. package/dist/compiler-tokens/json-lines.js +106 -0
  104. package/dist/compiler-tokens/json-tokens.js +52 -0
  105. package/dist/compiler-vue/adapter.js +107 -0
  106. package/dist/compiler-vue/analyze.js +344 -0
  107. package/dist/compiler-vue/declared.js +343 -0
  108. package/dist/compiler-vue/emit.js +129 -0
  109. package/dist/compiler-vue/index.js +5 -0
  110. package/dist/compiler-vue/script.js +472 -0
  111. package/dist/compiler-vue/sfc.js +44 -0
  112. package/dist/compiler-vue/template.js +73 -0
  113. package/dist/contract/archetype-checks.js +153 -0
  114. package/dist/contract/components.js +102 -0
  115. package/dist/contract/conflicts.js +39 -0
  116. package/dist/contract/contract.js +46 -0
  117. package/dist/contract/evidence.js +84 -0
  118. package/dist/contract/fact-path.js +39 -0
  119. package/dist/contract/facts.js +62 -0
  120. package/dist/contract/index.js +16 -0
  121. package/dist/contract/integrity.js +21 -0
  122. package/dist/contract/json-schema.js +11 -0
  123. package/dist/contract/knowledge.js +95 -0
  124. package/dist/contract/pattern-checks.js +254 -0
  125. package/dist/contract/patterns.js +225 -0
  126. package/dist/contract/primitives.js +81 -0
  127. package/dist/contract/sources.js +30 -0
  128. package/dist/contract/tokens.js +41 -0
  129. package/dist/contract/validate.js +253 -0
  130. package/dist/contract-query/archetype.js +188 -0
  131. package/dist/contract-query/index.js +6 -0
  132. package/dist/contract-query/patterns.js +298 -0
  133. package/dist/contract-query/scan.js +164 -0
  134. package/dist/contract-query/search.js +132 -0
  135. package/dist/contract-query/system.js +148 -0
  136. package/dist/contract-query/validate-code.js +239 -0
  137. package/dist/contract-query/validate-usage.js +150 -0
  138. package/dist/contract-query/views.js +288 -0
  139. package/dist/governance/decisions.js +229 -0
  140. package/dist/governance/index.js +5 -0
  141. package/dist/governance/patterns-file.js +156 -0
  142. package/dist/governance/patterns.js +510 -0
  143. package/dist/governance/policies.js +148 -0
  144. package/dist/mcp/index.js +7 -0
  145. package/dist/mcp/loader.js +70 -0
  146. package/dist/mcp/protocol.js +27 -0
  147. package/dist/mcp/resources.js +138 -0
  148. package/dist/mcp/server.js +177 -0
  149. package/dist/mcp/stdio.js +28 -0
  150. package/dist/mcp/tools.js +267 -0
  151. package/dist/reconciliation/code.js +62 -0
  152. package/dist/reconciliation/components.js +401 -0
  153. package/dist/reconciliation/conflicts.js +126 -0
  154. package/dist/reconciliation/facts.js +41 -0
  155. package/dist/reconciliation/index.js +7 -0
  156. package/dist/reconciliation/reconcile.js +134 -0
  157. package/dist/reconciliation/resolve.js +158 -0
  158. package/dist/reconciliation/review.js +81 -0
  159. package/dist/reconciliation/storybook.js +370 -0
  160. package/dist/reconciliation/tokens.js +179 -0
  161. package/dist/reconciliation/util.js +55 -0
  162. package/dist/semantic-engine/additive.js +51 -0
  163. package/dist/semantic-engine/compounds.js +91 -0
  164. package/dist/semantic-engine/documents.js +157 -0
  165. package/dist/semantic-engine/enrich.js +42 -0
  166. package/dist/semantic-engine/index.js +7 -0
  167. package/dist/semantic-engine/infer.js +38 -0
  168. package/dist/semantic-engine/tokens.js +120 -0
  169. package/dist/shared/canonical.js +65 -0
  170. package/dist/shared/diagnostic.js +78 -0
  171. package/dist/shared/glob.js +98 -0
  172. package/dist/shared/index.js +4 -0
  173. package/dist/shared/json.js +2 -0
  174. package/dist/validation/card.js +50 -0
  175. package/dist/validation/discovery/arrangements.js +162 -0
  176. package/dist/validation/discovery/discover.js +70 -0
  177. package/dist/validation/discovery/index.js +8 -0
  178. package/dist/validation/discovery/model.js +127 -0
  179. package/dist/validation/discovery/promote.js +24 -0
  180. package/dist/validation/discovery/reviews.js +63 -0
  181. package/dist/validation/discovery/shapes.js +47 -0
  182. package/dist/validation/discovery/similar-files.js +146 -0
  183. package/dist/validation/discovery/util.js +47 -0
  184. package/dist/validation/index.js +11 -0
  185. package/dist/validation/interaction-facts.js +341 -0
  186. package/dist/validation/interaction-workflows.js +633 -0
  187. package/dist/validation/literals.js +251 -0
  188. package/dist/validation/pattern-rules.js +608 -0
  189. package/dist/validation/policy.js +241 -0
  190. package/dist/validation/report.js +163 -0
  191. package/dist/validation/rules.js +269 -0
  192. package/dist/validation/scan.js +295 -0
  193. package/dist/validation/text.js +34 -0
  194. package/dist/validation/validate-code.js +513 -0
  195. package/dist/validation/validate-usage.js +196 -0
  196. package/package.json +38 -4
@@ -0,0 +1,401 @@
1
+ import { cmp, sameDomain, sameValue, setNested, slug, unique } from "./util.js";
2
+ const ATTRS = {
3
+ props: ["type.text", "type.kind", "required", "default", "allowedValues", "description", "deprecated"],
4
+ events: ["payload.text", "description", "deprecated", "declared"],
5
+ slots: ["required", "description", "declared"],
6
+ variants: ["values", "default", "description"]
7
+ };
8
+ const COLLECTIONS = Object.keys(ATTRS);
9
+ const IMPL_ATTRS = ["packageName", "exportName", "importPath"];
10
+ const NAMESPACE = /^[a-z0-9][a-z0-9._-]*$/;
11
+ const FRAMEWORKS = new Set(["react", "angular", "vue"]);
12
+ function translate(path, impl) {
13
+ const seg = path.split(".");
14
+ const [head, second, ...tail] = seg;
15
+ const invariant = { t: "invariant", path: seg };
16
+ if (seg.length === 1 && (head === "name" || head === "description" || head === "lifecycle"))
17
+ return { t: head };
18
+ if (head && COLLECTIONS.includes(head) && second) {
19
+ const collection = head;
20
+ if (tail[0] === "extensions" &&
21
+ tail.length >= 3 &&
22
+ collection !== "variants" &&
23
+ NAMESPACE.test(tail[1]))
24
+ return { t: "item-ext", collection, name: second, ns: tail[1], rest: tail.slice(2) };
25
+ return ATTRS[collection].includes(tail.join("."))
26
+ ? { t: "item", collection, name: second, attr: tail.join(".") }
27
+ : invariant;
28
+ }
29
+ if (head === "accessibility" && seg.length === 2 && (second === "role" || second === "attributes"))
30
+ return { t: "a11y", attr: second };
31
+ if (head === "implementations" && second === impl.code.framework) {
32
+ if (tail.length === 1 && IMPL_ATTRS.includes(tail[0]))
33
+ return { t: "impl", attr: tail[0] };
34
+ if (tail.length === 1 && tail[0] === "framework")
35
+ return undefined;
36
+ if (tail[0] === "extensions" && tail.length >= 3 && NAMESPACE.test(tail[1]))
37
+ return { t: "impl-ext", ns: tail[1], rest: tail.slice(2) };
38
+ return invariant;
39
+ }
40
+ if (head === "examples" && second && tail.length === 1 && (tail[0] === "language" || tail[0] === "code"))
41
+ return { t: "example", id: second, attr: tail[0] };
42
+ if (head === "composition" && seg.length === 2 && (second === "subcomponents" || second === "renders"))
43
+ return { t: "composition", attr: second };
44
+ if (head === "extensions" && second && tail.length >= 1 && second !== "invariant" && NAMESPACE.test(second))
45
+ return { t: "ext", ns: second, rest: tail };
46
+ return invariant;
47
+ }
48
+ function cpathOf(target, impl, exampleId) {
49
+ switch (target.t) {
50
+ case "name":
51
+ case "description":
52
+ case "lifecycle":
53
+ return target.t;
54
+ case "item":
55
+ return `${target.collection}.${target.name}.${target.attr}`;
56
+ case "item-ext":
57
+ return `${target.collection}.${target.name}.extensions.${target.ns}.${target.rest.join(".")}`;
58
+ case "a11y":
59
+ return `accessibility.${target.attr}`;
60
+ case "impl":
61
+ return `implementations.${impl.id}.${target.attr}`;
62
+ case "impl-ext":
63
+ return `implementations.${impl.id}.extensions.${target.ns}.${target.rest.join(".")}`;
64
+ case "example":
65
+ return `examples.${exampleId(target.id)}.${target.attr}`;
66
+ case "ext":
67
+ return `extensions.${target.ns}.${target.rest.join(".")}`;
68
+ case "composition":
69
+ return `extensions.invariant.composition.${target.attr}`;
70
+ case "invariant":
71
+ return `extensions.invariant.${target.path.join(".")}`;
72
+ }
73
+ }
74
+ /** Comparison rule for a path when implementations disagree. Unlisted paths are never compared. */
75
+ function comparison(target) {
76
+ switch (target.t) {
77
+ case "description":
78
+ return { type: "value-mismatch", severity: "info" };
79
+ case "lifecycle":
80
+ return { type: "deprecation-mismatch", severity: "warning" };
81
+ case "a11y":
82
+ return target.attr === "role"
83
+ ? { type: "value-mismatch", severity: "warning" }
84
+ : { type: "value-mismatch", severity: "info", domain: true };
85
+ case "item": {
86
+ const a = target.attr;
87
+ if (a === "required" || a === "default" || a === "values")
88
+ return { type: "value-mismatch", severity: "warning", domain: a === "values" };
89
+ if (a === "allowedValues")
90
+ return { type: "value-mismatch", severity: "warning", domain: true };
91
+ if (target.collection === "variants" && a === "default")
92
+ return { type: "value-mismatch", severity: "warning" };
93
+ if (a === "deprecated")
94
+ return undefined;
95
+ return { type: "value-mismatch", severity: "info" };
96
+ }
97
+ default:
98
+ return undefined;
99
+ }
100
+ }
101
+ const statusRank = (s) => (s === "discovered" ? 0 : 1);
102
+ function choose(claims) {
103
+ return [...claims].sort((a, b) => statusRank(a.status) - statusRank(b.status) ||
104
+ cmp(a.impl.code.sourceId, b.impl.code.sourceId) ||
105
+ cmp(a.impl.id, b.impl.id))[0];
106
+ }
107
+ const evidenceOfImpl = (impl, path) => impl.facts.find((f) => f.path === path)?.evidenceIds ?? impl.facts[0]?.evidenceIds ?? [];
108
+ /**
109
+ * Builds one contract component from the implementations judged to be the same. Facts, evidence and status
110
+ * come from the IR; nothing is added. Disagreeing implementations keep one value (see `choose`) and every
111
+ * claim is recorded as a conflict.
112
+ */
113
+ export function buildComponent(resolved, ctx) {
114
+ const impls = resolved.implementations;
115
+ const multi = impls.length > 1;
116
+ const placed = [];
117
+ for (const impl of impls) {
118
+ const exampleId = (raw) => (multi ? slug(`${impl.id}-${raw}`) : raw);
119
+ for (const fact of impl.facts) {
120
+ const target = translate(fact.path, impl);
121
+ if (!target)
122
+ continue;
123
+ placed.push({
124
+ cpath: cpathOf(target, impl, exampleId),
125
+ target,
126
+ claim: {
127
+ impl,
128
+ value: fact.value,
129
+ status: fact.origin === "extracted" ? "discovered" : "documented",
130
+ evidenceIds: fact.evidenceIds
131
+ }
132
+ });
133
+ }
134
+ }
135
+ const byPath = new Map();
136
+ for (const p of placed)
137
+ byPath.set(p.cpath, [...(byPath.get(p.cpath) ?? []), p]);
138
+ const facts = {};
139
+ const chosen = new Map();
140
+ for (const [cpath, group] of [...byPath.entries()].sort((a, b) => cmp(a[0], b[0]))) {
141
+ const winner = choose(group.map((g) => g.claim));
142
+ const first = group.find((g) => g.claim === winner);
143
+ chosen.set(cpath, first);
144
+ const rule = comparison(first.target);
145
+ const same = rule?.domain ? sameDomain : sameValue;
146
+ const agreeing = group.filter((g) => same(g.claim.value, winner.value)).map((g) => g.claim);
147
+ const status = agreeing.some((c) => c.status === "discovered") ? "discovered" : "documented";
148
+ facts[cpath] = { status, evidenceIds: unique(agreeing.flatMap((c) => c.evidenceIds)).sort() };
149
+ const distinct = group.filter((g) => !same(g.claim.value, winner.value));
150
+ if (rule && distinct.length > 0) {
151
+ ctx.conflicts.push({
152
+ type: rule.type,
153
+ severity: rule.severity,
154
+ subject: resolved.id,
155
+ path: cpath,
156
+ claims: group
157
+ .map((g) => ({
158
+ sourceId: g.claim.impl.code.sourceId,
159
+ value: g.claim.value,
160
+ status: g.claim.status,
161
+ evidenceIds: g.claim.evidenceIds
162
+ }))
163
+ .sort((a, b) => cmp(a.sourceId, b.sourceId) || cmp(JSON.stringify(a.value), JSON.stringify(b.value))),
164
+ suggestedResolution: `Decide which ${cpath} is intended and align the other implementation, or accept the difference.`
165
+ });
166
+ }
167
+ }
168
+ const component = assemble(resolved, chosen, facts, ctx);
169
+ findMissingProps(resolved, ctx);
170
+ return component;
171
+ }
172
+ function assemble(resolved, chosen, facts, ctx) {
173
+ const items = {
174
+ props: new Map(),
175
+ events: new Map(),
176
+ slots: new Map(),
177
+ variants: new Map()
178
+ };
179
+ const item = (c, name) => {
180
+ const found = items[c].get(name);
181
+ if (found)
182
+ return found;
183
+ const created = { value: {}, ext: {} };
184
+ items[c].set(name, created);
185
+ return created;
186
+ };
187
+ const implementations = new Map(resolved.implementations.map((i) => [i.id, { value: {}, ext: {} }]));
188
+ const a11y = {};
189
+ const examples = new Map();
190
+ const extensions = {};
191
+ let description;
192
+ let lifecycle;
193
+ const place = (store, ns, rest, value, cpath) => {
194
+ store[ns] ??= {};
195
+ if (!setNested(store[ns], rest, value)) {
196
+ delete facts[cpath];
197
+ ctx.diagnostics.warning("reconcile.extension-collision", `${resolved.id}: ${cpath} collides with another extension path and was not placed`, { entityId: resolved.id });
198
+ }
199
+ };
200
+ const implOf = (cpath) => cpath.split(".")[1];
201
+ for (const [cpath, p] of chosen) {
202
+ const value = p.claim.value;
203
+ const t = p.target;
204
+ switch (t.t) {
205
+ case "name":
206
+ break;
207
+ case "description":
208
+ if (typeof value === "string")
209
+ description = value;
210
+ break;
211
+ case "lifecycle":
212
+ if (value === "stable" || value === "experimental" || value === "deprecated")
213
+ lifecycle = value;
214
+ break;
215
+ case "item": {
216
+ const draft = item(t.collection, t.name);
217
+ if (t.attr === "type.text" || t.attr === "type.kind") {
218
+ const type = (draft.value.type ?? {});
219
+ type[t.attr === "type.text" ? "text" : "kind"] = value;
220
+ draft.value.type = type;
221
+ }
222
+ else if (t.attr === "payload.text") {
223
+ draft.value.payload = { text: value };
224
+ }
225
+ else if (t.attr === "declared") {
226
+ facts[`${t.collection}.${t.name}`] = facts[cpath];
227
+ delete facts[cpath];
228
+ }
229
+ else if (t.attr === "deprecated") {
230
+ draft.value.deprecated =
231
+ typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
232
+ }
233
+ else
234
+ draft.value[t.attr] = value;
235
+ break;
236
+ }
237
+ case "item-ext": {
238
+ const draft = item(t.collection, t.name);
239
+ place(draft.ext, t.ns, t.rest, value, cpath);
240
+ break;
241
+ }
242
+ case "a11y":
243
+ a11y[t.attr] = value;
244
+ break;
245
+ case "impl":
246
+ ;
247
+ implementations.get(implOf(cpath)).value[t.attr] = value;
248
+ break;
249
+ case "impl-ext":
250
+ place(implementations.get(implOf(cpath)).ext, t.ns, t.rest, value, cpath);
251
+ break;
252
+ case "example": {
253
+ const id = cpath.split(".")[1];
254
+ const ex = examples.get(id) ?? {};
255
+ ex[t.attr] = value;
256
+ examples.set(id, ex);
257
+ break;
258
+ }
259
+ case "ext":
260
+ place(extensions, t.ns, t.rest, value, cpath);
261
+ break;
262
+ case "composition":
263
+ place(extensions, "invariant", ["composition", t.attr], value, cpath);
264
+ break;
265
+ case "invariant":
266
+ place(extensions, "invariant", t.path, value, cpath);
267
+ break;
268
+ }
269
+ }
270
+ const sources = new Map(resolved.implementations.map((i) => [i.id, i.code.sourceId]));
271
+ const composition = relationships(resolved, ctx, facts);
272
+ const sorted = (list) => list.sort((a, b) => cmp(a.name, b.name));
273
+ const withExt = (d) => Object.keys(d.ext).length > 0 ? { ...d.value, extensions: d.ext } : d.value;
274
+ for (const name of items.events.keys())
275
+ facts[`events.${name}`] ??= facts[`events.${name}.payload.text`];
276
+ const component = {
277
+ id: resolved.id,
278
+ name: resolved.name,
279
+ ...(description ? { description } : {}),
280
+ ...(lifecycle ? { lifecycle } : {}),
281
+ implementations: resolved.implementations
282
+ .map((i) => {
283
+ const d = implementations.get(i.id);
284
+ return {
285
+ id: i.id,
286
+ framework: FRAMEWORKS.has(i.code.framework) ? i.code.framework : "other",
287
+ sourceId: sources.get(i.id),
288
+ ...d.value,
289
+ ...(Object.keys(d.ext).length > 0 ? { extensions: d.ext } : {})
290
+ };
291
+ })
292
+ .sort((a, b) => cmp(a.id, b.id)),
293
+ props: sorted([...items.props].map(([name, d]) => ({
294
+ name,
295
+ type: { text: "unknown" },
296
+ required: false,
297
+ ...withExt(d)
298
+ }))),
299
+ events: sorted([...items.events].map(([name, d]) => ({ name, ...withExt(d) }))),
300
+ slots: sorted([...items.slots].map(([name, d]) => ({ name, ...withExt(d) }))),
301
+ variants: sorted([...items.variants]
302
+ .map(([name, d]) => ({ name, ...d.value, ...withoutEmptyValues(d.value) }))
303
+ .filter((v) => Array.isArray(v.values) && v.values.length > 0)),
304
+ states: [],
305
+ ...(Object.keys(a11y).length > 0 ? { accessibility: a11y } : {}),
306
+ examples: [...examples]
307
+ .filter(([, e]) => typeof e.code === "string" && e.code !== "" && typeof e.language === "string")
308
+ .map(([id, e]) => ({ id, language: e.language, code: e.code, sourceId: [...sources.values()][0] }))
309
+ .sort((a, b) => cmp(a.id, b.id)),
310
+ composition,
311
+ facts: sortedRecord(facts),
312
+ ...(Object.keys(extensions).length > 0 ? { extensions } : {})
313
+ };
314
+ return component;
315
+ }
316
+ /**
317
+ * The contract has no way to say "empty string" (a variant value and a default are at least one character), but a
318
+ * union such as `size?: "" | "small" | "large"` is common (Element Plus, Angular Material). Dropping the value keeps the
319
+ * contract valid; the rest of the domain is still enforced.
320
+ */
321
+ function withoutEmptyValues(value) {
322
+ const values = Array.isArray(value.values) ? value.values.filter((v) => v !== "") : value.values;
323
+ return {
324
+ ...(values !== undefined ? { values: values } : {}),
325
+ ...(value.default === "" ? { default: undefined } : {})
326
+ };
327
+ }
328
+ function sortedRecord(record) {
329
+ return Object.fromEntries(Object.entries(record).sort((a, b) => cmp(a[0], b[0])));
330
+ }
331
+ /** `contains` relationships from static composition facts, resolved to contract component ids. */
332
+ function relationships(resolved, ctx, facts) {
333
+ const targets = new Map();
334
+ for (const impl of resolved.implementations) {
335
+ for (const fact of impl.facts) {
336
+ if (fact.path !== "composition.subcomponents" && fact.path !== "composition.renders")
337
+ continue;
338
+ if (!Array.isArray(fact.value))
339
+ continue;
340
+ for (const entry of fact.value) {
341
+ const ref = typeof entry === "object" && entry !== null && !Array.isArray(entry)
342
+ ? (entry.ref ?? entry.target)
343
+ : undefined;
344
+ if (typeof ref !== "string")
345
+ continue;
346
+ const id = ctx.refToId.get(ref);
347
+ if (!id) {
348
+ ctx.diagnostics.info("reconcile.composition-target-unresolved", `${resolved.id} refers to ${ref}, which is not a public component`, {
349
+ entityId: resolved.id,
350
+ sourceId: impl.code.sourceId
351
+ });
352
+ continue;
353
+ }
354
+ if (id === resolved.id)
355
+ continue;
356
+ targets.set(id, unique([...(targets.get(id) ?? []), ...fact.evidenceIds]).sort());
357
+ }
358
+ }
359
+ }
360
+ const list = [...targets.entries()].sort((a, b) => cmp(a[0], b[0]));
361
+ return list.map(([target, evidenceIds], index) => {
362
+ facts[`composition.${index}`] = { status: "discovered", evidenceIds };
363
+ return { type: "contains", target: target };
364
+ });
365
+ }
366
+ /** A prop one implementation declares and another, whose props are fully known, lacks. */
367
+ function findMissingProps(resolved, ctx) {
368
+ const impls = resolved.implementations;
369
+ if (impls.length < 2)
370
+ return;
371
+ const names = unique(impls.flatMap((i) => [...i.code.props.values()].filter((p) => !p.role || p.role === "prop").map((p) => p.name))).sort();
372
+ for (const name of names) {
373
+ const has = impls.filter((i) => i.code.props.has(name));
374
+ const lacks = impls.filter((i) => !i.code.props.has(name) && i.code.propsComplete);
375
+ if (lacks.length === 0)
376
+ continue;
377
+ ctx.conflicts.push({
378
+ type: "existence-mismatch",
379
+ severity: "info",
380
+ subject: resolved.id,
381
+ path: `props.${name}`,
382
+ claims: [
383
+ ...has.map((i) => ({
384
+ sourceId: i.code.sourceId,
385
+ value: true,
386
+ status: "discovered",
387
+ evidenceIds: i.code.props.get(name)?.evidence.declared.length
388
+ ? i.code.props.get(name)?.evidence.declared
389
+ : evidenceOfImpl(i, `props.${name}.type.text`)
390
+ })),
391
+ ...lacks.map((i) => ({
392
+ sourceId: i.code.sourceId,
393
+ status: "discovered",
394
+ evidenceIds: evidenceOfImpl(i, `implementations.${i.code.framework}.extensions.${i.code.framework}.propsComplete`)
395
+ }))
396
+ ].sort((a, b) => cmp(a.sourceId, b.sourceId)),
397
+ suggestedResolution: "Frameworks may differ legitimately; confirm the prop is meant to exist in every implementation."
398
+ });
399
+ }
400
+ }
401
+ //# sourceMappingURL=components.js.map
@@ -0,0 +1,126 @@
1
+ import { cmp, sameValue, slug, unique } from "./util.js";
2
+ /**
3
+ * Storybook findings that cite evidence on both sides become conflicts: a story or control says one thing
4
+ * and the implementation another. Coverage findings (not demonstrated, unlinked, unverifiable) stay
5
+ * diagnostics, because a conflict needs two evidenced claims.
6
+ */
7
+ export function storybookConflicts(discrepancies, refToId, implementations) {
8
+ const drafts = [];
9
+ for (const d of discrepancies) {
10
+ const subject = d.component ? refToId.get(d.component) : undefined;
11
+ const impl = d.component ? implementations.get(d.component) : undefined;
12
+ if (!subject || !d.prop || !d.claim || d.storybookEvidence.length === 0)
13
+ continue;
14
+ const code = d.code.replace("reconcile.storybook.", "");
15
+ const base = { subject: subject, severity: d.severity };
16
+ const storybook = (value) => ({
17
+ sourceId: d.sourceId,
18
+ ...(value === undefined ? {} : { value }),
19
+ status: "discovered",
20
+ evidenceIds: d.storybookEvidence
21
+ });
22
+ const codeEvidence = d.codeEvidence.length > 0
23
+ ? d.codeEvidence
24
+ : (impl?.facts.find((f) => f.path.endsWith(".propsComplete"))?.evidenceIds ?? []);
25
+ if (codeEvidence.length === 0 || !impl)
26
+ continue;
27
+ const codeClaim = (value) => ({
28
+ sourceId: impl.code.sourceId,
29
+ ...(value === undefined ? {} : { value }),
30
+ status: "discovered",
31
+ evidenceIds: codeEvidence
32
+ });
33
+ if (code === "arg-value-not-allowed" || code === "control-option-not-allowed")
34
+ drafts.push({
35
+ ...base,
36
+ type: "value-mismatch",
37
+ path: `props.${d.prop}.allowedValues`,
38
+ claims: [codeClaim(d.claim.code), storybook(d.claim.storybook)],
39
+ suggestedResolution: "Change the story or control to a declared value, or add the value to the component."
40
+ });
41
+ else if (code === "default-mismatch")
42
+ drafts.push({
43
+ ...base,
44
+ type: "value-mismatch",
45
+ path: `props.${d.prop}.default`,
46
+ claims: [codeClaim(d.claim.code), storybook(d.claim.storybook)],
47
+ suggestedResolution: "Update the documented default or the code default."
48
+ });
49
+ else if (code === "arg-undeclared" || code === "control-undeclared")
50
+ drafts.push({
51
+ ...base,
52
+ type: "existence-mismatch",
53
+ path: `props.${d.prop}`,
54
+ claims: [codeClaim(), storybook(true)],
55
+ suggestedResolution: "Remove the arg or control, or add the prop to the component."
56
+ });
57
+ else if (code === "deprecated-demonstrated")
58
+ drafts.push({
59
+ ...base,
60
+ type: "deprecation-mismatch",
61
+ path: `props.${d.prop}.deprecated`,
62
+ claims: [codeClaim(true), storybook()],
63
+ suggestedResolution: "Stop demonstrating the deprecated prop, or mark it as supported."
64
+ });
65
+ }
66
+ return drafts;
67
+ }
68
+ export function ambiguityConflicts(ambiguities, refToId) {
69
+ const out = [];
70
+ for (const a of ambiguities) {
71
+ const first = a.candidates[0];
72
+ const subject = first ? refToId.get(first.code.ref) : undefined;
73
+ if (!first || !subject)
74
+ continue;
75
+ out.push({
76
+ type: "identity-ambiguity",
77
+ severity: "warning",
78
+ subject: subject,
79
+ claims: a.candidates.map((c) => ({
80
+ sourceId: c.code.sourceId,
81
+ value: c.code.ref,
82
+ status: "discovered",
83
+ evidenceIds: c.facts.find((f) => f.path === "name")?.evidenceIds ?? c.facts[0]?.evidenceIds ?? []
84
+ })),
85
+ suggestedResolution: `Several implementations match the name "${a.key}" and no single pairing is certain. They were not merged; link the ones that are the same component.`
86
+ });
87
+ }
88
+ return out;
89
+ }
90
+ /** Merges drafts about the same subject and path, then mints stable ids. Claims are never dropped. */
91
+ export function mintConflicts(drafts) {
92
+ const grouped = new Map();
93
+ for (const d of drafts) {
94
+ const key = `${d.type}\u0000${d.subject}\u0000${d.path ?? ""}`;
95
+ let existing = grouped.get(key);
96
+ if (!existing) {
97
+ existing = { ...d, claims: [] };
98
+ grouped.set(key, existing);
99
+ }
100
+ else if (d.severity === "warning" || (d.severity === "error" && existing.severity !== "error"))
101
+ existing.severity = d.severity;
102
+ for (const claim of d.claims) {
103
+ const same = existing.claims.find((c) => c.sourceId === claim.sourceId && c.status === claim.status && sameValue(c.value, claim.value));
104
+ if (same)
105
+ same.evidenceIds = unique([...same.evidenceIds, ...claim.evidenceIds]).sort();
106
+ else
107
+ existing.claims.push({ ...claim });
108
+ }
109
+ }
110
+ const used = new Set();
111
+ const out = [];
112
+ for (const [, d] of [...grouped.entries()].sort((a, b) => cmp(a[0], b[0]))) {
113
+ const subject = d.subject.slice(d.subject.indexOf(":") + 1);
114
+ const base = `conflict:${d.type}/${slug(subject)}${d.path ? `/${slug(d.path)}` : ""}`;
115
+ let id = base;
116
+ for (let n = 2; used.has(id); n++)
117
+ id = `${base}-${n}`;
118
+ used.add(id);
119
+ const claims = d.claims
120
+ .map((c) => ({ ...c, evidenceIds: unique(c.evidenceIds).sort() }))
121
+ .sort((a, b) => cmp(a.sourceId, b.sourceId) || cmp(JSON.stringify(a.value ?? null), JSON.stringify(b.value ?? null)));
122
+ out.push({ ...d, id: id, claims, resolution: { state: "open" } });
123
+ }
124
+ return out;
125
+ }
126
+ //# sourceMappingURL=conflicts.js.map
@@ -0,0 +1,41 @@
1
+ /** Facts of one document grouped by subject and path, for cheap lookup. */
2
+ export class FactIndex {
3
+ doc;
4
+ bySubject = new Map();
5
+ evidence = new Map();
6
+ constructor(doc) {
7
+ this.doc = doc;
8
+ for (const e of doc.evidence)
9
+ this.evidence.set(e.id, e);
10
+ for (const fact of doc.facts) {
11
+ const paths = this.bySubject.get(fact.subject) ?? new Map();
12
+ paths.set(fact.path, [...(paths.get(fact.path) ?? []), fact]);
13
+ this.bySubject.set(fact.subject, paths);
14
+ }
15
+ }
16
+ get(subject, path) {
17
+ return this.bySubject.get(subject)?.get(path)?.[0];
18
+ }
19
+ value(subject, path) {
20
+ return this.get(subject, path)?.value;
21
+ }
22
+ /** Paths under `prefix.` for a subject, as the first segment after the prefix. */
23
+ children(subject, prefix) {
24
+ const names = new Set();
25
+ for (const path of this.bySubject.get(subject)?.keys() ?? []) {
26
+ if (path.startsWith(`${prefix}.`))
27
+ names.add(path.slice(prefix.length + 1).split(".")[0]);
28
+ }
29
+ return [...names].sort();
30
+ }
31
+ evidenceIds(subject, path) {
32
+ return this.get(subject, path)?.evidenceIds ?? [];
33
+ }
34
+ location(evidenceId) {
35
+ const location = (evidenceId ? this.evidence.get(evidenceId) : undefined)?.location;
36
+ if (location?.type !== "file")
37
+ return undefined;
38
+ return { path: location.path, ...(location.line ? { line: location.line } : {}) };
39
+ }
40
+ }
41
+ //# sourceMappingURL=facts.js.map
@@ -0,0 +1,7 @@
1
+ export { codeComponents } from "./code.js";
2
+ export { FactIndex } from "./facts.js";
3
+ export { finalizeAndVerify, reconcile } from "./reconcile.js";
4
+ export { resolveComponents, sharedPrefix } from "./resolve.js";
5
+ export { buildReviewQueue } from "./review.js";
6
+ export { reconcileStorybook } from "./storybook.js";
7
+ //# sourceMappingURL=index.js.map