@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,241 @@
1
+ import { isPatternConstraintKind } from "../contract/index.js";
2
+ import { normalize } from "../contract-query/index.js";
3
+ import { z } from "zod";
4
+ import { isDynamic } from "./validate-usage.js";
5
+ const names = z.array(z.string().min(1).max(200)).min(1).max(200);
6
+ const scalar = z.union([z.string(), z.number(), z.boolean()]);
7
+ /**
8
+ * What an approved policy enforces, in a form the validator can evaluate. Written by people in
9
+ * `invariant.policies.json`, normalized by governance into a constraint's `extensions.invariant.rule`.
10
+ */
11
+ export const policyRuleSchema = z.discriminatedUnion("kind", [
12
+ z.strictObject({
13
+ kind: z.literal("require-attribute"),
14
+ components: names,
15
+ anyOf: names,
16
+ unlessChildren: z.boolean().optional(),
17
+ category: z.enum(["accessibility", "general"]).optional()
18
+ }),
19
+ z.strictObject({
20
+ kind: z.literal("forbid-prop"),
21
+ component: z.string().min(1).max(200),
22
+ prop: z.string().min(1).max(200),
23
+ replacedBy: z.string().min(1).max(200).optional()
24
+ }),
25
+ z.strictObject({
26
+ kind: z.literal("forbid-prop-value"),
27
+ component: z.string().min(1).max(200),
28
+ prop: z.string().min(1).max(200),
29
+ values: z.array(scalar).min(1).max(100)
30
+ }),
31
+ z.strictObject({
32
+ kind: z.literal("forbid-component"),
33
+ component: z.string().min(1).max(200),
34
+ use: z.string().min(1).max(200).optional()
35
+ }),
36
+ z.strictObject({
37
+ kind: z.literal("forbid-token"),
38
+ tokens: names,
39
+ replacedBy: z.string().min(1).max(200).optional()
40
+ }),
41
+ z.strictObject({
42
+ kind: z.literal("allowed-children"),
43
+ container: z.string().min(1).max(200),
44
+ children: names,
45
+ category: z.enum(["layout", "composition"]).optional()
46
+ }),
47
+ z.strictObject({
48
+ kind: z.literal("required-parent"),
49
+ component: z.string().min(1).max(200),
50
+ parents: names
51
+ })
52
+ ]);
53
+ const capped = (severity, enforcement) => enforcement === "report-only"
54
+ ? "info"
55
+ : enforcement === "warn" && severity === "error"
56
+ ? "warning"
57
+ : severity;
58
+ /** Approved policies and the rules they carry. A policy whose rule cannot be read is reported, not enforced. */
59
+ export function activePolicies(contract) {
60
+ const active = [];
61
+ const problems = [];
62
+ for (const policy of contract.policies) {
63
+ for (const id of policy.basedOn) {
64
+ const constraint = contract.constraints.find((c) => c.id === id);
65
+ // Structural pattern constraints are evaluated by `pattern-rules.ts`, not as component-level rules.
66
+ if (constraint && isPatternConstraintKind(constraint.kind))
67
+ continue;
68
+ const raw = constraint?.extensions?.invariant ?? {};
69
+ const parsed = policyRuleSchema.safeParse(raw.rule);
70
+ if (!constraint || !parsed.success) {
71
+ problems.push(`policy ${policy.id}: ${id} has no readable rule`);
72
+ continue;
73
+ }
74
+ active.push({
75
+ policyId: policy.id,
76
+ name: policy.name,
77
+ severity: capped(policy.severity, policy.enforcement),
78
+ rule: parsed.data,
79
+ ...(typeof raw.source === "string" ? { source: raw.source } : {})
80
+ });
81
+ }
82
+ }
83
+ return { active, problems };
84
+ }
85
+ /** `requires-parent` relationships the contract itself records, as rules (inferred ones are not enforced). */
86
+ export function relationshipRules(ds) {
87
+ const out = [];
88
+ for (const c of ds.contract.components)
89
+ c.composition.forEach((r, i) => {
90
+ if (r.type !== "requires-parent")
91
+ return;
92
+ const status = c.facts[`composition.${i}`]?.status;
93
+ if (status === "inferred")
94
+ return;
95
+ out.push({
96
+ policyId: "",
97
+ name: `${c.name} requires a parent`,
98
+ severity: "error",
99
+ rule: { kind: "required-parent", component: c.id, parents: [r.target] }
100
+ });
101
+ });
102
+ return out;
103
+ }
104
+ const same = (a, b) => normalize(a.replace(/^component:/, "")) === normalize(b.replace(/^component:/, ""));
105
+ const matches = (c, ref) => c.id === ref || same(c.name, ref) || same(c.id, ref);
106
+ const ref = (p) => ({
107
+ ...(p.policyId ? { policy: p.policyId } : {}),
108
+ ...(p.source ? { source: p.source } : {}),
109
+ basis: p.policyId ? "approved" : "discovered"
110
+ });
111
+ const why = (p) => (p.policyId ? ` (policy ${p.policyId})` : "");
112
+ function ancestors(ctx, index) {
113
+ const out = [];
114
+ for (let p = ctx.uses[index]?.parent; p !== undefined; p = ctx.uses[p]?.parent)
115
+ out.push(p);
116
+ return out;
117
+ }
118
+ /** Evaluates component-level rules against one scanned element. */
119
+ export function evaluateUse(policies, ctx, index) {
120
+ const use = ctx.uses[index];
121
+ const comp = ctx.resolved[index];
122
+ if (!comp)
123
+ return [];
124
+ const findings = [];
125
+ const attrNames = new Set(Object.keys(use.attrs).map((n) => normalize(n)));
126
+ for (const p of policies) {
127
+ const r = p.rule;
128
+ switch (r.kind) {
129
+ case "require-attribute": {
130
+ if (!r.components.some((x) => matches(comp, x)) || use.hasSpread)
131
+ break;
132
+ if (r.anyOf.some((a) => attrNames.has(normalize(a))))
133
+ break;
134
+ if (r.unlessChildren) {
135
+ if (use.hasChildren === true)
136
+ break;
137
+ if (use.hasChildren === undefined)
138
+ break;
139
+ }
140
+ const accessibility = r.category === "accessibility";
141
+ findings.push({
142
+ code: accessibility ? "accessibility-requirement-missing" : "policy-attribute-required",
143
+ severity: p.severity,
144
+ subject: comp.id,
145
+ message: `${comp.name} needs one of ${r.anyOf.join(", ")}${r.unlessChildren ? " when it has no children" : ""}${why(p)}`,
146
+ ...ref(p),
147
+ suggestion: `Add ${r.anyOf[0]}="...".`
148
+ });
149
+ break;
150
+ }
151
+ case "forbid-prop": {
152
+ if (!matches(comp, r.component) ||
153
+ !(normalize(r.prop) in Object.fromEntries([...attrNames].map((n) => [n, 1]))))
154
+ break;
155
+ findings.push({
156
+ code: "prop-forbidden",
157
+ severity: p.severity,
158
+ subject: comp.id,
159
+ message: `${comp.name} must not use "${r.prop}"${why(p)}`,
160
+ path: `props.${r.prop}`,
161
+ ...ref(p),
162
+ suggestion: r.replacedBy ? `Use ${r.replacedBy} instead.` : `Remove "${r.prop}".`
163
+ });
164
+ break;
165
+ }
166
+ case "forbid-prop-value": {
167
+ if (!matches(comp, r.component))
168
+ break;
169
+ const key = Object.keys(use.attrs).find((n) => normalize(n) === normalize(r.prop));
170
+ const value = key === undefined ? undefined : use.attrs[key];
171
+ if (value === undefined || isDynamic(value))
172
+ break;
173
+ if (!r.values.some((v) => v === value || String(v) === String(value)))
174
+ break;
175
+ findings.push({
176
+ code: "prop-value-forbidden",
177
+ severity: p.severity,
178
+ subject: comp.id,
179
+ message: `${comp.name}.${r.prop} must not be ${JSON.stringify(value)}${why(p)}`,
180
+ path: `props.${r.prop}`,
181
+ ...ref(p),
182
+ suggestion: "Choose a different value."
183
+ });
184
+ break;
185
+ }
186
+ case "forbid-component": {
187
+ if (!matches(comp, r.component))
188
+ break;
189
+ findings.push({
190
+ code: "component-forbidden",
191
+ severity: p.severity,
192
+ subject: comp.id,
193
+ message: `${comp.name} must not be used${why(p)}`,
194
+ ...ref(p),
195
+ suggestion: r.use ? `Use ${r.use} instead.` : "Use an approved component."
196
+ });
197
+ break;
198
+ }
199
+ case "allowed-children": {
200
+ const parent = ctx.uses[index]?.parent;
201
+ const container = parent === undefined ? undefined : ctx.resolved[parent];
202
+ if (!container || !matches(container, r.container))
203
+ break;
204
+ if (r.children.some((x) => matches(comp, x)))
205
+ break;
206
+ findings.push({
207
+ code: r.category === "layout" ? "layout-pattern-invalid" : "composition-not-allowed",
208
+ severity: p.severity,
209
+ subject: comp.id,
210
+ message: `${comp.name} is not allowed directly inside ${container.name}; allowed: ${r.children.join(", ")}${why(p)}`,
211
+ ...ref(p),
212
+ suggestion: `Put ${comp.name} inside an allowed component, or use one of ${r.children.join(", ")} here.`
213
+ });
214
+ break;
215
+ }
216
+ case "required-parent": {
217
+ if (!matches(comp, r.component))
218
+ break;
219
+ const chain = ancestors(ctx, index);
220
+ const dsAncestors = chain.map((a) => ctx.resolved[a]).filter((c) => c !== undefined);
221
+ // Only a violation when it sits inside other design-system components but none is an allowed parent: at the
222
+ // top of a file the real parent may be in another file.
223
+ if (dsAncestors.length === 0 || dsAncestors.some((a) => r.parents.some((x) => matches(a, x))))
224
+ break;
225
+ findings.push({
226
+ code: p.policyId ? "composition-not-allowed" : "composition-parent-required",
227
+ severity: p.severity,
228
+ subject: comp.id,
229
+ message: `${comp.name} must be inside ${r.parents.join(" or ")}${why(p)}`,
230
+ ...ref(p),
231
+ suggestion: `Wrap it in ${r.parents[0]}.`
232
+ });
233
+ break;
234
+ }
235
+ case "forbid-token":
236
+ break;
237
+ }
238
+ }
239
+ return findings;
240
+ }
241
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1,163 @@
1
+ import { RULES, ruleFor } from "./rules.js";
2
+ export function summarize(violations, files, componentsChecked, evaluations = []) {
3
+ const n = (s) => violations.filter((v) => v.severity === s).length;
4
+ const patternRules = { pass: 0, fail: 0, warning: 0, notVerifiable: 0, skipped: 0 };
5
+ for (const e of evaluations)
6
+ patternRules[e.status === "not-verifiable" ? "notVerifiable" : e.status]++;
7
+ const leveled = evaluations.filter((e) => e.level !== undefined);
8
+ const interactionEvidence = leveled.length === 0
9
+ ? undefined
10
+ : {
11
+ structural: leveled.filter((e) => e.level === "structural").length,
12
+ staticInferred: leveled.filter((e) => e.level === "static-inferred").length,
13
+ runtimeVerified: leveled.filter((e) => e.level === "runtime-verified").length
14
+ };
15
+ return {
16
+ files,
17
+ errors: n("error"),
18
+ warnings: n("warning"),
19
+ info: n("info"),
20
+ componentsChecked,
21
+ patternRules,
22
+ ...(interactionEvidence ? { interactionEvidence } : {})
23
+ };
24
+ }
25
+ const evidenceLine = (s) => {
26
+ const e = s.interactionEvidence;
27
+ if (!e)
28
+ return undefined;
29
+ return `interaction evidence: ${e.structural + e.staticInferred + e.runtimeVerified} structural or better (${e.staticInferred} statically inferred, ${e.structural} structural only, ${e.runtimeVerified} runtime verified; static analysis does not run the code)`;
30
+ };
31
+ const patternLine = (s) => `${s.pass} passed, ${s.fail} failed, ${s.warning} warnings, ${s.notVerifiable} not verifiable, ${s.skipped} skipped`;
32
+ const hasPatternRules = (s) => Object.values(s).some((n) => n > 0);
33
+ export function sortEvaluations(evaluations) {
34
+ return [...evaluations].sort((a, b) => ((a.file ?? "") < (b.file ?? "") ? -1 : (a.file ?? "") > (b.file ?? "") ? 1 : 0) ||
35
+ (a.line ?? 0) - (b.line ?? 0) ||
36
+ (a.column ?? 0) - (b.column ?? 0) ||
37
+ (a.constraint < b.constraint ? -1 : a.constraint > b.constraint ? 1 : 0));
38
+ }
39
+ const RANK = { error: 0, warning: 1, info: 2 };
40
+ /** Sorts worst-first: file, then position in it, then severity, then rule. `file` is optional for snippet checks. */
41
+ export function sortViolations(violations) {
42
+ const fileOf = (f) => f.file ?? "";
43
+ return [...violations].sort((a, b) => (fileOf(a) < fileOf(b) ? -1 : fileOf(a) > fileOf(b) ? 1 : 0) ||
44
+ (a.line ?? 0) - (b.line ?? 0) ||
45
+ (a.column ?? 0) - (b.column ?? 0) ||
46
+ RANK[a.severity] - RANK[b.severity] ||
47
+ (a.code < b.code ? -1 : a.code > b.code ? 1 : 0));
48
+ }
49
+ /** True when the run should fail the build. */
50
+ export function shouldFail(summary, failOn) {
51
+ return failOn === "never"
52
+ ? false
53
+ : failOn === "warning"
54
+ ? summary.errors + summary.warnings > 0
55
+ : summary.errors > 0;
56
+ }
57
+ export function formatText(violations, summary, evaluations = []) {
58
+ const lines = [];
59
+ for (const v of sortViolations(violations)) {
60
+ lines.push(`${v.file}:${v.line ?? 1}:${v.column ?? 1} ${v.severity} ${v.code}`);
61
+ lines.push(` ${v.message}`);
62
+ if (v.suggestion)
63
+ lines.push(` fix: ${v.suggestion}`);
64
+ const refs = [
65
+ v.source ? `evidence ${v.source} (${v.basis})` : `basis ${v.basis}`,
66
+ v.policy ? `policy ${v.policy}` : undefined,
67
+ v.subject ? `subject ${v.subject}` : undefined
68
+ ].filter(Boolean);
69
+ lines.push(` ${refs.join("; ")}`);
70
+ lines.push("");
71
+ }
72
+ const unestablished = sortEvaluations(evaluations).filter((e) => e.status === "not-verifiable" || e.status === "skipped");
73
+ if (hasPatternRules(summary.patternRules)) {
74
+ lines.push(`pattern rules: ${patternLine(summary.patternRules)}`);
75
+ const evidence = evidenceLine(summary);
76
+ if (evidence)
77
+ lines.push(evidence);
78
+ for (const e of unestablished.slice(0, 50))
79
+ lines.push(` ${e.status === "skipped" ? "skipped" : "not verifiable"} ${e.file ?? "(snippet)"}:${e.line ?? 1} ${e.rule}: ${e.reason}`);
80
+ if (unestablished.length > 50)
81
+ lines.push(` ... ${unestablished.length - 50} more (see --format json)`);
82
+ lines.push("");
83
+ }
84
+ lines.push(`${summary.errors} errors, ${summary.warnings} warnings, ${summary.info} info in ${summary.files} files (${summary.componentsChecked} components checked)`);
85
+ return lines.join("\n");
86
+ }
87
+ export function toJson(violations, summary, contractHash, evaluations = []) {
88
+ return {
89
+ contractHash,
90
+ summary,
91
+ violations: sortViolations(violations),
92
+ evaluations: sortEvaluations(evaluations)
93
+ };
94
+ }
95
+ /** The pattern-rule tally for a one-line summary, or an empty string when no pattern rule applied. */
96
+ export const patternSummaryText = (s) => {
97
+ if (!hasPatternRules(s.patternRules))
98
+ return "";
99
+ const evidence = evidenceLine(s);
100
+ return `pattern rules: ${patternLine(s.patternRules)}${evidence ? `\n${evidence}` : ""}`;
101
+ };
102
+ const escData = (s) => s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
103
+ const escProp = (s) => escData(s).replace(/:/g, "%3A").replace(/,/g, "%2C");
104
+ /** GitHub Actions workflow commands: one annotation per violation. */
105
+ export function formatGithub(violations) {
106
+ return sortViolations(violations)
107
+ .map((v) => {
108
+ const level = v.severity === "error" ? "error" : v.severity === "warning" ? "warning" : "notice";
109
+ const props = `file=${escProp(v.file)},line=${v.line ?? 1},col=${v.column ?? 1},title=${escProp(v.code)}`;
110
+ return `::${level} ${props}::${escData(v.suggestion ? `${v.message}. Fix: ${v.suggestion}` : v.message)}`;
111
+ })
112
+ .join("\n");
113
+ }
114
+ /** SARIF 2.1.0, accepted by GitHub code scanning and most CI viewers. */
115
+ export function toSarif(violations, tool, summary) {
116
+ const level = (s) => (s === "info" ? "note" : s);
117
+ const used = new Set(violations.map((v) => v.code));
118
+ return {
119
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
120
+ version: "2.1.0",
121
+ runs: [
122
+ {
123
+ tool: {
124
+ driver: {
125
+ name: tool.name,
126
+ version: tool.version,
127
+ rules: RULES.filter((r) => used.has(r.code)).map((r) => ({
128
+ id: r.code,
129
+ name: r.title,
130
+ shortDescription: { text: r.title },
131
+ properties: { planRule: r.plan, origin: r.origin },
132
+ defaultConfiguration: { level: level(r.severity) }
133
+ }))
134
+ }
135
+ },
136
+ ...(summary && hasPatternRules(summary.patternRules)
137
+ ? { properties: { patternRules: summary.patternRules } }
138
+ : {}),
139
+ results: sortViolations(violations).map((v) => ({
140
+ ruleId: v.code,
141
+ level: level(v.severity),
142
+ message: { text: v.suggestion ? `${v.message}. Fix: ${v.suggestion}` : v.message },
143
+ locations: [
144
+ {
145
+ physicalLocation: {
146
+ artifactLocation: { uri: v.file },
147
+ region: { startLine: v.line ?? 1, startColumn: v.column ?? 1 }
148
+ }
149
+ }
150
+ ],
151
+ properties: {
152
+ basis: v.basis,
153
+ ...(v.source ? { evidence: v.source } : {}),
154
+ ...(v.policy ? { policy: v.policy } : {}),
155
+ ...(v.subject ? { subject: v.subject } : {}),
156
+ ...(ruleFor(v.code) ? {} : {})
157
+ }
158
+ }))
159
+ }
160
+ ]
161
+ };
162
+ }
163
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1,269 @@
1
+ export const RULES = [
2
+ {
3
+ code: "component-unknown",
4
+ title: "Component is not in the design system",
5
+ plan: "Unknown component",
6
+ severity: "error",
7
+ origin: "contract"
8
+ },
9
+ {
10
+ code: "component-ambiguous",
11
+ title: "Name matches several components",
12
+ plan: "support",
13
+ severity: "info",
14
+ origin: "contract"
15
+ },
16
+ {
17
+ code: "framework-not-implemented",
18
+ title: "Component has no implementation in this framework",
19
+ plan: "Unknown component",
20
+ severity: "error",
21
+ origin: "contract"
22
+ },
23
+ {
24
+ code: "unknown-prop",
25
+ title: "Property is not declared by the component",
26
+ plan: "Unknown property",
27
+ severity: "warning",
28
+ origin: "contract"
29
+ },
30
+ {
31
+ code: "unverifiable-prop",
32
+ title: "Property could not be verified (incomplete API listing)",
33
+ plan: "support",
34
+ severity: "info",
35
+ origin: "contract"
36
+ },
37
+ {
38
+ code: "prop-value-not-allowed",
39
+ title: "Property value is not one of the declared values",
40
+ plan: "Invalid property value",
41
+ severity: "error",
42
+ origin: "contract"
43
+ },
44
+ {
45
+ code: "prop-type-mismatch",
46
+ title: "Property value has the wrong type",
47
+ plan: "Invalid property value",
48
+ severity: "warning",
49
+ origin: "contract"
50
+ },
51
+ {
52
+ code: "component-deprecated",
53
+ title: "Component is deprecated",
54
+ plan: "Deprecated component",
55
+ severity: "warning",
56
+ origin: "contract"
57
+ },
58
+ {
59
+ code: "prop-deprecated",
60
+ title: "Property is deprecated",
61
+ plan: "Deprecated property",
62
+ severity: "warning",
63
+ origin: "contract"
64
+ },
65
+ {
66
+ code: "required-prop-missing",
67
+ title: "Required property is missing",
68
+ plan: "Missing required property",
69
+ severity: "error",
70
+ origin: "contract"
71
+ },
72
+ {
73
+ code: "color-not-in-tokens",
74
+ title: "Literal color is not a design token",
75
+ plan: "Hardcoded color",
76
+ severity: "warning",
77
+ origin: "contract"
78
+ },
79
+ {
80
+ code: "color-matches-token",
81
+ title: "Literal color equals a design token",
82
+ plan: "Hardcoded color",
83
+ severity: "info",
84
+ origin: "contract"
85
+ },
86
+ {
87
+ code: "spacing-not-in-tokens",
88
+ title: "Literal spacing value is not a design token",
89
+ plan: "Hardcoded value",
90
+ severity: "warning",
91
+ origin: "contract"
92
+ },
93
+ {
94
+ code: "spacing-matches-token",
95
+ title: "Literal spacing value equals a design token",
96
+ plan: "Hardcoded value",
97
+ severity: "info",
98
+ origin: "contract"
99
+ },
100
+ {
101
+ code: "typography-not-in-tokens",
102
+ title: "Literal typography value is not a design token",
103
+ plan: "Hardcoded value",
104
+ severity: "warning",
105
+ origin: "contract"
106
+ },
107
+ {
108
+ code: "typography-matches-token",
109
+ title: "Literal typography value equals a design token",
110
+ plan: "Hardcoded value",
111
+ severity: "info",
112
+ origin: "contract"
113
+ },
114
+ {
115
+ code: "pattern-required-child-missing",
116
+ title: "Pattern requires a child that is missing",
117
+ plan: "Pattern composition",
118
+ severity: "error",
119
+ origin: "policy"
120
+ },
121
+ {
122
+ code: "pattern-child-forbidden",
123
+ title: "Pattern forbids a child that is present",
124
+ plan: "Pattern composition",
125
+ severity: "error",
126
+ origin: "policy"
127
+ },
128
+ {
129
+ code: "pattern-parent-required",
130
+ title: "Component is used outside the parent its pattern requires",
131
+ plan: "Pattern composition",
132
+ severity: "error",
133
+ origin: "policy"
134
+ },
135
+ {
136
+ code: "pattern-region-missing",
137
+ title: "Pattern region is missing",
138
+ plan: "Pattern composition",
139
+ severity: "error",
140
+ origin: "policy"
141
+ },
142
+ {
143
+ code: "pattern-order-violated",
144
+ title: "Elements are in the wrong order for the pattern",
145
+ plan: "Pattern composition",
146
+ severity: "error",
147
+ origin: "policy"
148
+ },
149
+ {
150
+ code: "pattern-cardinality-violated",
151
+ title: "Number of elements breaks the pattern",
152
+ plan: "Pattern composition",
153
+ severity: "error",
154
+ origin: "policy"
155
+ },
156
+ {
157
+ code: "pattern-state-missing",
158
+ title: "Interaction state the pattern requires is not represented",
159
+ plan: "Pattern composition",
160
+ severity: "error",
161
+ origin: "policy"
162
+ },
163
+ {
164
+ code: "unknown-css-variable",
165
+ title: "CSS variable is not a design token",
166
+ plan: "Unapproved token",
167
+ severity: "warning",
168
+ origin: "contract"
169
+ },
170
+ {
171
+ code: "token-deprecated",
172
+ title: "Token is deprecated",
173
+ plan: "Unapproved token",
174
+ severity: "warning",
175
+ origin: "contract"
176
+ },
177
+ {
178
+ code: "token-forbidden",
179
+ title: "Token is forbidden by policy",
180
+ plan: "Unapproved token",
181
+ severity: "error",
182
+ origin: "policy"
183
+ },
184
+ {
185
+ code: "composition-parent-required",
186
+ title: "Component is used outside the parent it requires",
187
+ plan: "Unsupported composition",
188
+ severity: "error",
189
+ origin: "contract"
190
+ },
191
+ {
192
+ code: "composition-not-allowed",
193
+ title: "Component is not allowed inside this container",
194
+ plan: "Unsupported composition",
195
+ severity: "error",
196
+ origin: "policy"
197
+ },
198
+ {
199
+ code: "layout-pattern-invalid",
200
+ title: "Layout structure is not allowed",
201
+ plan: "Invalid layout pattern",
202
+ severity: "error",
203
+ origin: "policy"
204
+ },
205
+ {
206
+ code: "accessibility-requirement-missing",
207
+ title: "Accessibility requirement is not met",
208
+ plan: "Missing accessibility requirement",
209
+ severity: "error",
210
+ origin: "policy"
211
+ },
212
+ {
213
+ code: "policy-attribute-required",
214
+ title: "Policy requires an attribute",
215
+ plan: "support",
216
+ severity: "error",
217
+ origin: "policy"
218
+ },
219
+ {
220
+ code: "prop-forbidden",
221
+ title: "Property is forbidden by policy",
222
+ plan: "support",
223
+ severity: "error",
224
+ origin: "policy"
225
+ },
226
+ {
227
+ code: "prop-value-forbidden",
228
+ title: "Property value is forbidden by policy",
229
+ plan: "support",
230
+ severity: "error",
231
+ origin: "policy"
232
+ },
233
+ {
234
+ code: "component-forbidden",
235
+ title: "Component is forbidden by policy",
236
+ plan: "support",
237
+ severity: "error",
238
+ origin: "policy"
239
+ },
240
+ {
241
+ code: "native-element-has-equivalent",
242
+ title: "Native element has a similarly named component",
243
+ plan: "support",
244
+ severity: "info",
245
+ origin: "heuristic"
246
+ },
247
+ {
248
+ code: "input-too-large",
249
+ title: "Input exceeds the size limit",
250
+ plan: "support",
251
+ severity: "error",
252
+ origin: "contract"
253
+ }
254
+ ];
255
+ export const ruleFor = (code) => RULES.find((r) => r.code === code);
256
+ export const PLAN_RULES = [
257
+ "Unknown component",
258
+ "Unknown property",
259
+ "Invalid property value",
260
+ "Deprecated component",
261
+ "Deprecated property",
262
+ "Unsupported composition",
263
+ "Missing required property",
264
+ "Hardcoded color",
265
+ "Unapproved token",
266
+ "Missing accessibility requirement",
267
+ "Invalid layout pattern"
268
+ ];
269
+ //# sourceMappingURL=rules.js.map