@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,288 @@
1
+ import { cmp } from "./system.js";
2
+ const clamp = (n, fallback, max) => Math.max(1, Math.min(n ?? fallback, max));
3
+ const statusOf = (f) => (f ? f : null);
4
+ export function page(all, offset = 0, limit) {
5
+ const l = clamp(limit, 50, 200);
6
+ const o = Math.max(0, offset);
7
+ return { total: all.length, offset: o, limit: l, truncated: o + l < all.length, items: all.slice(o, o + l) };
8
+ }
9
+ export function overview(ds) {
10
+ const c = ds.contract;
11
+ const inferred = [...c.components, ...c.tokens, ...c.guidelines, ...c.patterns].reduce((n, e) => n + Object.values(e.facts).filter((f) => f.status === "inferred").length, 0);
12
+ const open = c.conflicts.filter((x) => x.resolution.state === "open");
13
+ const by = (key) => {
14
+ const out = {};
15
+ for (const x of open)
16
+ out[key(x)] = (out[key(x)] ?? 0) + 1;
17
+ return out;
18
+ };
19
+ const perFramework = {};
20
+ for (const comp of c.components)
21
+ for (const fw of new Set(comp.implementations.map((i) => i.framework)))
22
+ perFramework[fw] = (perFramework[fw] ?? 0) + 1;
23
+ const categories = {};
24
+ for (const t of c.tokens)
25
+ categories[t.category] = (categories[t.category] ?? 0) + 1;
26
+ const incomplete = c.components.filter((x) => !ds.propsComplete(x)).length;
27
+ const notes = [];
28
+ if (c.patterns.length === 0)
29
+ notes.push("No patterns are defined: none are inferred from code and no source documents one.");
30
+ if (c.policies.length === 0)
31
+ notes.push("No approved policies exist. Validation reports facts from the contract, not organizational rules.");
32
+ if (incomplete > 0)
33
+ notes.push(`${incomplete} component(s) have an incomplete API listing (inherited or generic props); a missing prop there is not proof it does not exist.`);
34
+ if (inferred > 0)
35
+ notes.push(`${inferred} fact(s) are inferred, not discovered; each carries a confidence and needs human approval.`);
36
+ if (open.length > 0)
37
+ notes.push(`${open.length} open conflict(s) between sources; the contract keeps one value and records every claim.`);
38
+ return {
39
+ name: c.metadata.name,
40
+ contractId: c.metadata.contractId,
41
+ contractVersion: c.contractVersion,
42
+ contentHash: c.integrity.contentHash,
43
+ compilerVersion: c.metadata.compilerVersion,
44
+ frameworks: c.metadata.frameworks,
45
+ sources: c.sources.map((s) => ({ id: s.id, type: s.type, health: s.health.status })),
46
+ counts: {
47
+ components: c.components.length,
48
+ tokens: c.tokens.length,
49
+ themes: c.themes.length,
50
+ guidelines: c.guidelines.length,
51
+ patterns: c.patterns.length,
52
+ policies: c.policies.length,
53
+ inferredFacts: inferred,
54
+ componentsWithIncompleteApi: incomplete
55
+ },
56
+ componentsByFramework: perFramework,
57
+ tokenCategories: categories,
58
+ openConflicts: { total: open.length, bySeverity: by((x) => x.severity), byType: by((x) => x.type) },
59
+ notes
60
+ };
61
+ }
62
+ export function catalog(ds) {
63
+ return ds.contract.components.map((c) => ({
64
+ id: c.id,
65
+ name: c.name,
66
+ frameworks: [...new Set(c.implementations.map((i) => i.framework))].sort(),
67
+ ...(c.lifecycle ? { lifecycle: c.lifecycle } : {}),
68
+ props: c.props.length,
69
+ events: c.events.length,
70
+ slots: c.slots.length,
71
+ variants: c.variants.map((v) => v.name)
72
+ }));
73
+ }
74
+ function roleOf(c, prop) {
75
+ for (const ext of Object.values(c.props.find((p) => p.name === prop)?.extensions ?? {}))
76
+ if (typeof ext.role === "string")
77
+ return ext.role;
78
+ return undefined;
79
+ }
80
+ export function componentView(ds, c) {
81
+ const conflicts = ds.conflictsFor(c.id);
82
+ const notes = [];
83
+ if (!ds.propsComplete(c))
84
+ notes.push("The prop list is incomplete for at least one implementation: do not conclude a prop is absent from this list alone; read the source.");
85
+ if (c.lifecycle === "deprecated")
86
+ notes.push("This component is deprecated.");
87
+ const identity = c.implementations.find((i) => c.facts[`implementations.${i.id}`]?.status === "inferred");
88
+ if (identity)
89
+ notes.push("Implementations in different frameworks were linked by name, which is inferred (see each implementation's status).");
90
+ return {
91
+ id: c.id,
92
+ name: c.name,
93
+ ...(c.description ? { description: c.description } : {}),
94
+ ...(c.lifecycle ? { lifecycle: c.lifecycle } : {}),
95
+ implementations: c.implementations.map((i) => ({
96
+ id: i.id,
97
+ framework: i.framework,
98
+ ...(i.packageName ? { package: i.packageName } : {}),
99
+ ...(i.exportName ? { export: i.exportName } : {}),
100
+ ...(i.importPath ? { importPath: i.importPath } : {}),
101
+ apiComplete: ds.propsComplete(c, i.framework),
102
+ status: statusOf(ds.factView(c.facts, `implementations.${i.id}`))
103
+ })),
104
+ props: c.props.map((p) => ({
105
+ name: p.name,
106
+ type: p.type.text,
107
+ ...(p.type.kind ? { kind: p.type.kind } : {}),
108
+ required: p.required,
109
+ ...(p.default !== undefined ? { default: p.default } : {}),
110
+ ...(p.allowedValues ? { allowedValues: p.allowedValues } : {}),
111
+ ...(p.description ? { description: p.description } : {}),
112
+ ...(p.deprecated ? { deprecated: p.deprecated } : {}),
113
+ ...(roleOf(c, p.name) ? { role: roleOf(c, p.name) } : {}),
114
+ status: statusOf(ds.factView(c.facts, `props.${p.name}.type.text`))
115
+ })),
116
+ events: c.events.map((e) => ({ name: e.name, ...(e.payload ? { payload: e.payload.text } : {}) })),
117
+ slots: c.slots.map((s) => ({
118
+ name: s.name,
119
+ ...(s.required !== undefined ? { required: s.required } : {})
120
+ })),
121
+ variants: c.variants.map((v) => ({
122
+ name: v.name,
123
+ values: v.values,
124
+ ...(v.default ? { default: v.default } : {})
125
+ })),
126
+ ...(c.accessibility ? { accessibility: c.accessibility } : {}),
127
+ conflicts: conflicts.map((x) => ({
128
+ type: x.type,
129
+ severity: x.severity,
130
+ ...(x.path ? { path: x.path } : {}),
131
+ claims: x.claims.map((cl) => ({
132
+ source: cl.sourceId,
133
+ ...(cl.value === undefined ? {} : { value: cl.value }),
134
+ status: cl.status
135
+ }))
136
+ })),
137
+ guidelines: ds.contract.guidelines.filter((g) => g.appliesTo.includes(c.id)).map((g) => g.id),
138
+ notes
139
+ };
140
+ }
141
+ export function usageView(ds, c) {
142
+ const required = c.props.filter((p) => p.required).map((p) => p.name);
143
+ const deprecatedProps = c.props
144
+ .filter((p) => p.deprecated)
145
+ .map((p) => ({ name: p.name, ...(p.deprecated?.reason ? { reason: p.deprecated.reason } : {}) }));
146
+ const usage = c.extensions?.invariant?.usage;
147
+ const notes = [];
148
+ if (!ds.propsComplete(c))
149
+ notes.push("API listing is incomplete; confirm unlisted props in the source.");
150
+ if (usage === undefined)
151
+ notes.push("No observed usage statistics for this component (only React usage is observed).");
152
+ return {
153
+ id: c.id,
154
+ name: c.name,
155
+ lifecycle: c.lifecycle ?? "unspecified",
156
+ howToImport: c.implementations.map((i) => ({
157
+ framework: i.framework,
158
+ ...(i.packageName ? { package: i.packageName } : {}),
159
+ ...(i.exportName ? { export: i.exportName } : {}),
160
+ ...(i.importPath ? { from: i.importPath } : {})
161
+ })),
162
+ requiredProps: required,
163
+ variants: c.variants.map((v) => ({
164
+ name: v.name,
165
+ values: v.values,
166
+ ...(v.default ? { default: v.default } : {})
167
+ })),
168
+ defaults: Object.fromEntries(c.props.filter((p) => p.default !== undefined).map((p) => [p.name, p.default])),
169
+ events: c.events.map((e) => e.name),
170
+ slots: c.slots.map((s) => s.name),
171
+ deprecatedProps,
172
+ examples: c.examples.map((e) => ({
173
+ id: e.id,
174
+ language: e.language,
175
+ code: e.code,
176
+ status: statusOf(ds.factView(c.facts, `examples.${e.id}.code`))
177
+ })),
178
+ ...(usage === undefined ? {} : { observedUsage: usage }),
179
+ guidelines: ds.contract.guidelines
180
+ .filter((g) => g.appliesTo.includes(c.id))
181
+ .map((g) => ({ id: g.id, kind: g.kind, title: g.name })),
182
+ notes
183
+ };
184
+ }
185
+ export function compositionView(ds, c) {
186
+ const staticRefs = c.extensions?.invariant?.composition;
187
+ const name = (id) => ds.component(id)?.name ?? id;
188
+ const notes = [];
189
+ const contains = c.composition.map((r, i) => ({
190
+ relation: r.type,
191
+ id: r.target,
192
+ name: name(r.target),
193
+ status: statusOf(ds.factView(c.facts, `composition.${i}`))
194
+ }));
195
+ if (contains.some((x) => x.status?.status === "inferred"))
196
+ notes.push("Some containment links are inferred from names and are low confidence; verify against the source.");
197
+ if (contains.length === 0)
198
+ notes.push("No composition relationships were found; that does not mean the component cannot be composed.");
199
+ return {
200
+ id: c.id,
201
+ name: c.name,
202
+ contains,
203
+ containedBy: ds.containedBy(c.id).map((x) => ({
204
+ id: x.id,
205
+ name: name(x.id),
206
+ status: x.fact
207
+ ? {
208
+ status: x.fact.status,
209
+ ...(x.fact.confidence === undefined ? {} : { confidence: x.fact.confidence })
210
+ }
211
+ : null
212
+ })),
213
+ slots: c.slots.map((s) => ({ name: s.name, ...(s.accepts ? { accepts: s.accepts } : {}) })),
214
+ ...(staticRefs === undefined ? {} : { staticReferences: staticRefs }),
215
+ notes
216
+ };
217
+ }
218
+ export function tokenView(ds, t, theme) {
219
+ const resolved = ds.resolveToken(t, theme);
220
+ return {
221
+ id: t.id,
222
+ name: t.name,
223
+ category: t.category,
224
+ categoryStatus: statusOf(ds.factView(t.facts, "category")),
225
+ ...(t.description ? { description: t.description } : {}),
226
+ ...(t.value.aliasOf ? { aliasOf: t.value.aliasOf } : { value: t.value.value }),
227
+ ...(resolved.value !== undefined ? { resolvedValue: resolved.value } : {}),
228
+ ...(resolved.chain.length > 1 ? { aliasChain: resolved.chain } : {}),
229
+ ...(resolved.note ? { note: resolved.note } : {}),
230
+ modes: Object.fromEntries(Object.entries(t.modes ?? {}).map(([k, v]) => [
231
+ k,
232
+ (v.aliasOf ? { aliasOf: v.aliasOf } : { value: v.value })
233
+ ])),
234
+ ...(t.deprecated ? { deprecated: t.deprecated } : {})
235
+ };
236
+ }
237
+ export function tokensView(ds, f) {
238
+ const q = f.query?.toLowerCase();
239
+ const theme = f.theme ? (f.theme.startsWith("theme:") ? f.theme : `theme:${f.theme}`) : undefined;
240
+ const all = ds.contract.tokens
241
+ .filter((t) => !f.category || t.category === f.category)
242
+ .filter((t) => !q ||
243
+ t.id.includes(q) ||
244
+ t.name.toLowerCase().includes(q) ||
245
+ JSON.stringify(t.value).toLowerCase().includes(q))
246
+ .sort((a, b) => cmp(a.id, b.id));
247
+ const p = page(all, f.offset, f.limit);
248
+ return {
249
+ total: p.total,
250
+ offset: p.offset,
251
+ truncated: p.truncated,
252
+ themes: ds.contract.themes.map((t) => t.id),
253
+ tokens: p.items.map((t) => tokenView(ds, t, theme)),
254
+ notes: all.some((t) => ds.factView(t.facts, "category")?.status === "inferred")
255
+ ? ["Token categories marked inferred are guesses from names and value kinds, not declarations."]
256
+ : []
257
+ };
258
+ }
259
+ export function guidelinesView(ds, f) {
260
+ const target = f.component ? ds.findComponent(f.component).match?.id : undefined;
261
+ const q = f.query?.toLowerCase();
262
+ const all = ds.contract.guidelines
263
+ .filter((g) => !f.kind || g.kind === f.kind)
264
+ .filter((g) => !f.component || (target !== undefined && g.appliesTo.includes(target)))
265
+ .filter((g) => !q || g.name.toLowerCase().includes(q) || g.id.includes(q));
266
+ const p = page(all, f.offset, f.limit);
267
+ return {
268
+ total: p.total,
269
+ offset: p.offset,
270
+ truncated: p.truncated,
271
+ guidelines: p.items.map((g) => ({
272
+ id: g.id,
273
+ title: g.name,
274
+ kind: g.kind,
275
+ kindStatus: statusOf(ds.factView(g.facts, "kind")),
276
+ appliesTo: g.appliesTo,
277
+ references: g.references.map((r) => ({ source: r.sourceId, document: r.documentId }))
278
+ })),
279
+ notes: [
280
+ "Guideline text is the page title: Invariant does not read page bodies, so open the referenced document for the actual guidance.",
281
+ ...(f.component && !target ? [`No component matches "${f.component}".`] : [])
282
+ ]
283
+ };
284
+ }
285
+ export function factsOf(c) {
286
+ return Object.keys(c.facts).length;
287
+ }
288
+ //# sourceMappingURL=views.js.map
@@ -0,0 +1,229 @@
1
+ import { computeFactFingerprint, resolveFactPath } from "../contract/index.js";
2
+ import { canonicalStringify, DiagnosticCollector, hashJson } from "../shared/index.js";
3
+ import { z } from "zod";
4
+ export const DECISIONS_FILE = "invariant.decisions.json";
5
+ const who = {
6
+ by: z.string().min(1).max(200),
7
+ at: z.iso.datetime(),
8
+ note: z.string().min(1).max(2000).optional()
9
+ };
10
+ export const decisionSchema = z.discriminatedUnion("kind", [
11
+ z.strictObject({
12
+ kind: z.literal("fact"),
13
+ action: z.enum(["approve", "reject"]),
14
+ subject: z.string().min(1).max(300),
15
+ path: z.string().min(1).max(300),
16
+ /** The value at `path` when the decision was made. If it has changed since, the decision is stale. */
17
+ value: z.custom((v) => v !== undefined),
18
+ role: z.string().min(1).max(200).optional(),
19
+ reference: z.string().min(1).max(500).optional(),
20
+ ...who
21
+ }),
22
+ z.strictObject({
23
+ kind: z.literal("conflict"),
24
+ action: z.enum(["resolve", "accept", "dismiss"]),
25
+ id: z.string().min(1).max(300),
26
+ /** Hash of the conflict's claims when the decision was made. */
27
+ claimsHash: z.string().regex(/^[0-9a-f]{64}$/),
28
+ chosenSourceId: z.string().min(1).max(100).optional(),
29
+ ...who
30
+ })
31
+ ]);
32
+ export const decisionsFileSchema = z.strictObject({
33
+ version: z.literal(1),
34
+ decisions: z.array(decisionSchema)
35
+ });
36
+ export function parseDecisions(text) {
37
+ let raw;
38
+ try {
39
+ raw = JSON.parse(text);
40
+ }
41
+ catch (error) {
42
+ return { ok: false, message: `${DECISIONS_FILE} is not valid JSON: ${error.message}` };
43
+ }
44
+ const parsed = decisionsFileSchema.safeParse(raw);
45
+ if (!parsed.success) {
46
+ const i = parsed.error.issues[0];
47
+ return {
48
+ ok: false,
49
+ message: `${DECISIONS_FILE} is invalid at ${i?.path.join(".") || "(root)"}: ${i?.message ?? "schema error"}`
50
+ };
51
+ }
52
+ return { ok: true, value: parsed.data };
53
+ }
54
+ export const serializeDecisions = (file) => `${canonicalStringify(file, 2)}\n`;
55
+ export const claimsHash = (conflict) => hashJson(conflict.claims);
56
+ const targetKey = (d) => d.kind === "conflict"
57
+ ? `conflict\u0000${d.id}`
58
+ : `fact\u0000${d.subject}\u0000${canonicalStringify(d.value)}\u0000${d.path.replace(/\.\d+$/, "")}`;
59
+ /** Adds a decision, replacing an earlier one about the same target so the last word stands. */
60
+ export function addDecision(file, decision) {
61
+ const kept = (file?.decisions ?? []).filter((d) => targetKey(d) !== targetKey(decision));
62
+ return { version: 1, decisions: [...kept, decision] };
63
+ }
64
+ const COLLECTIONS = ["components", "tokens", "guidelines", "patterns", "constraints"];
65
+ const same = (a, b) => canonicalStringify(a ?? null) === canonicalStringify(b ?? null);
66
+ const INDEXED = /^(composition|appliesTo)\.(\d+)$/;
67
+ function find(draft, id) {
68
+ for (const collection of COLLECTIONS) {
69
+ const entity = draft[collection].find((e) => e.id === id);
70
+ if (entity)
71
+ return { collection, entity };
72
+ }
73
+ return undefined;
74
+ }
75
+ /** Removes array item `index` from an entity and renumbers its facts for that collection. */
76
+ function removeIndexed(entity, collection, index) {
77
+ const e = entity;
78
+ const items = [...e[collection]];
79
+ items.splice(index, 1);
80
+ const facts = {};
81
+ for (const [path, fact] of Object.entries(e.facts)) {
82
+ const m = new RegExp(`^${collection}\\.(\\d+)$`).exec(path);
83
+ if (!m)
84
+ facts[path] = fact;
85
+ else {
86
+ const n = Number(m[1]);
87
+ if (n < index)
88
+ facts[path] = fact;
89
+ else if (n > index)
90
+ facts[`${collection}.${n - 1}`] = fact;
91
+ }
92
+ }
93
+ return {
94
+ ...e,
95
+ [collection]: items,
96
+ facts: Object.fromEntries(Object.entries(facts).sort((a, b) => (a[0] < b[0] ? -1 : 1)))
97
+ };
98
+ }
99
+ /**
100
+ * Applies people's decisions to a contract draft, in file order. Approving layers `approved` over the
101
+ * fact's prior status with the contract's own fingerprint; rejecting removes an inferred fact where removal
102
+ * is well defined; resolving a conflict records the decision and leaves the kept value alone (the fix
103
+ * belongs in the source). A decision about a value or claims that have since changed is stale and ignored.
104
+ */
105
+ export function applyDecisions(draft, file, options = {}) {
106
+ const collector = new DiagnosticCollector();
107
+ const applied = [];
108
+ const stale = [];
109
+ let contract = draft;
110
+ const approvers = options.approvers && options.approvers.length > 0 ? new Set(options.approvers) : undefined;
111
+ const replace = (collection, entity) => {
112
+ contract = {
113
+ ...contract,
114
+ [collection]: contract[collection].map((e) => (e.id === entity.id ? entity : e))
115
+ };
116
+ };
117
+ for (const d of file?.decisions ?? []) {
118
+ const label = d.kind === "conflict" ? d.id : `${d.subject} ${d.path}`;
119
+ if (approvers && !approvers.has(d.by)) {
120
+ collector.error("governance.unauthorized", `${label}: "${d.by}" is not listed in governance.approvers, so the decision was not applied`);
121
+ continue;
122
+ }
123
+ if (d.kind === "conflict") {
124
+ const conflict = contract.conflicts.find((c) => c.id === d.id);
125
+ if (!conflict) {
126
+ collector.warning("governance.decision-target-missing", `${d.id}: the conflict no longer exists (it may have been fixed at the source); remove the decision`);
127
+ continue;
128
+ }
129
+ if (claimsHash(conflict) !== d.claimsHash) {
130
+ collector.warning("governance.decision-stale", `${d.id}: the claims changed since ${d.by} decided, so the decision was not applied`);
131
+ stale.push(d);
132
+ continue;
133
+ }
134
+ if (d.chosenSourceId && !conflict.claims.some((c) => c.sourceId === d.chosenSourceId)) {
135
+ collector.error("governance.decision-invalid", `${d.id}: "${d.chosenSourceId}" is not one of the conflict's sources`);
136
+ continue;
137
+ }
138
+ const state = d.action === "resolve" ? "resolved" : d.action === "accept" ? "accepted" : "dismissed";
139
+ const next = {
140
+ ...conflict,
141
+ resolution: {
142
+ state,
143
+ ...(d.chosenSourceId ? { chosenSourceId: d.chosenSourceId } : {}),
144
+ decidedBy: d.by,
145
+ decidedAt: d.at,
146
+ ...(d.note ? { note: d.note } : {})
147
+ }
148
+ };
149
+ contract = { ...contract, conflicts: contract.conflicts.map((c) => (c.id === conflict.id ? next : c)) };
150
+ applied.push({ action: d.action, target: d.id });
151
+ continue;
152
+ }
153
+ const hit = find(contract, d.subject);
154
+ if (!hit) {
155
+ collector.warning("governance.decision-target-missing", `${label}: ${d.subject} is no longer in the contract; remove the decision`);
156
+ continue;
157
+ }
158
+ let path = d.path;
159
+ const indexed = INDEXED.exec(d.path);
160
+ const entity = hit.entity;
161
+ if (indexed) {
162
+ const items = entity[indexed[1]] ?? [];
163
+ const at = items.findIndex((item) => same(item, d.value));
164
+ if (at < 0) {
165
+ collector.warning("governance.decision-stale", `${label}: the value it was about is gone, so the decision was not applied`);
166
+ stale.push(d);
167
+ continue;
168
+ }
169
+ path = `${indexed[1]}.${at}`;
170
+ }
171
+ else {
172
+ const current = resolveFactPath(entity, path);
173
+ if (!current.found || !same(current.value, d.value)) {
174
+ collector.warning("governance.decision-stale", `${label}: the value changed since ${d.by} decided, so the decision was not applied`);
175
+ stale.push(d);
176
+ continue;
177
+ }
178
+ }
179
+ const record = entity.facts[path];
180
+ if (!record) {
181
+ collector.warning("governance.decision-target-missing", `${label}: there is no fact at ${path}`);
182
+ continue;
183
+ }
184
+ if (d.action === "approve") {
185
+ if (record.status === "approved")
186
+ continue;
187
+ const value = resolveFactPath(entity, path);
188
+ if (!value.found)
189
+ continue;
190
+ const next = {
191
+ ...record,
192
+ status: "approved",
193
+ approval: {
194
+ approvedBy: d.by,
195
+ approvedAt: d.at,
196
+ ...(d.role ? { role: d.role } : {}),
197
+ ...(d.reference ? { reference: d.reference } : {}),
198
+ fingerprint: computeFactFingerprint({ entityId: entity.id, path, value: value.value }),
199
+ priorStatus: record.status
200
+ }
201
+ };
202
+ replace(hit.collection, { ...entity, facts: { ...entity.facts, [path]: next } });
203
+ applied.push({ action: "approve", target: label });
204
+ continue;
205
+ }
206
+ if (record.status === "approved") {
207
+ collector.error("governance.reject-approved", `${label}: the fact is approved; remove its approval decision before rejecting it`);
208
+ continue;
209
+ }
210
+ if (record.status !== "inferred") {
211
+ collector.error("governance.reject-unsupported", `${label}: only inferred facts can be rejected; a ${record.status} fact is stated by a source, so fix the source`);
212
+ continue;
213
+ }
214
+ if (indexed) {
215
+ replace(hit.collection, removeIndexed(hit.entity, indexed[1], Number(path.split(".")[1])));
216
+ }
217
+ else if (hit.collection === "tokens" && path === "category") {
218
+ const { category: _gone, ...facts } = entity.facts;
219
+ replace("tokens", { ...entity, category: "other", facts });
220
+ }
221
+ else {
222
+ collector.error("governance.reject-unsupported", `${label}: rejecting this kind of inference is not supported (for an inferred identity between implementations, fix the names or split the component at the source)`);
223
+ continue;
224
+ }
225
+ applied.push({ action: "reject", target: label });
226
+ }
227
+ return { contract, diagnostics: collector.toArray(), applied, stale };
228
+ }
229
+ //# sourceMappingURL=decisions.js.map
@@ -0,0 +1,5 @@
1
+ export { addDecision, applyDecisions, claimsHash, DECISIONS_FILE, decisionSchema, decisionsFileSchema, parseDecisions, serializeDecisions } from "./decisions.js";
2
+ export { compilePatterns, PATTERN_SOURCE_ID } from "./patterns.js";
3
+ export { PATTERNS_FILE, PATTERNS_FILE_VERSION, parsePatterns, patternDefinitionSchema, patternsFileJsonSchema, patternsFileSchema } from "./patterns-file.js";
4
+ export { compilePolicies, POLICIES_FILE, parsePolicies, policiesFileSchema } from "./policies.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,156 @@
1
+ import { behaviorSchema, compositionPartSchema, frameworkSchema, layoutRegionSchema, PATTERN_CATEGORIES, PATTERN_CONSTRAINT_KINDS, PATTERN_RELATIONSHIP_TYPES, partNameSchema, patternAccessibilitySchema } from "../contract/index.js";
2
+ import { z } from "zod";
3
+ export const PATTERNS_FILE = "invariant.patterns.json";
4
+ export const PATTERNS_FILE_VERSION = 1;
5
+ /**
6
+ * What a person writes. References to components, patterns, tokens and guidelines are names or ids here
7
+ * (`DataTable`, `component:data-table`, `page.data-management`); compiling resolves them to ids or says why
8
+ * it cannot. Everything else has the same shape as in the contract, so a definition reads like the entity it
9
+ * becomes.
10
+ */
11
+ const reference = z.string().min(1).max(200);
12
+ const part = compositionPartSchema.extend({ ref: reference });
13
+ const region = layoutRegionSchema
14
+ .omit({ accepts: true, grid: true, spacing: true, responsive: true })
15
+ .extend({
16
+ accepts: z.array(reference).optional(),
17
+ grid: z
18
+ .strictObject({
19
+ columns: layoutRegionSchema.shape.grid.unwrap().shape.columns,
20
+ gap: reference.optional()
21
+ })
22
+ .optional(),
23
+ spacing: reference.optional(),
24
+ responsive: z
25
+ .array(z.strictObject({
26
+ breakpoint: reference,
27
+ behavior: layoutRegionSchema.shape.responsive.unwrap().element.shape.behavior
28
+ }))
29
+ .optional()
30
+ });
31
+ const constraintName = z
32
+ .string()
33
+ .max(64)
34
+ .regex(/^[a-z0-9][a-z0-9_-]*$/);
35
+ const constraint = z.strictObject({
36
+ /** Local to the pattern. The contract id is `constraint:<pattern>.<id>`. */
37
+ id: constraintName,
38
+ name: z.string().min(1).max(200).optional(),
39
+ kind: z.enum(PATTERN_CONSTRAINT_KINDS),
40
+ description: z.string().min(1).max(2000),
41
+ /** Checked against the kind's schema after references are resolved. */
42
+ details: z.record(z.string().max(100), z.unknown()),
43
+ /** Makes the constraint enforceable. It becomes a policy, which needs the pattern's approval. */
44
+ enforce: z
45
+ .strictObject({
46
+ severity: z.enum(["error", "warning", "info"]),
47
+ enforcement: z.enum(["block", "warn", "report-only"]).default("block")
48
+ })
49
+ .optional()
50
+ });
51
+ /** A named way the composition may differ. Its added parts are written with the same references as any part. */
52
+ const variation = z
53
+ .strictObject({
54
+ name: partNameSchema,
55
+ description: z.string().min(1).max(500).optional(),
56
+ when: z.string().min(1).max(500).optional(),
57
+ roles: z
58
+ .array(z.strictObject({ part: partNameSchema, role: z.enum(["required", "optional", "prohibited"]) }))
59
+ .optional(),
60
+ parts: z.array(part).optional()
61
+ })
62
+ .refine((v) => (v.roles?.length ?? 0) + (v.parts?.length ?? 0) > 0, {
63
+ message: "a variation changes at least one part's role or adds a part"
64
+ });
65
+ const relationship = z.strictObject({
66
+ id: partNameSchema,
67
+ type: z.enum(PATTERN_RELATIONSHIP_TYPES),
68
+ target: reference,
69
+ description: z.string().min(1).max(500).optional()
70
+ });
71
+ export const patternDefinitionSchema = z.strictObject({
72
+ id: z
73
+ .string()
74
+ .max(120)
75
+ .regex(/^(?:pattern:)?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9][a-z0-9._-]*)*$/),
76
+ name: z.string().min(1).max(200),
77
+ category: z.enum(PATTERN_CATEGORIES),
78
+ description: z.string().min(1).max(2000),
79
+ intent: z.string().min(1).max(500).optional(),
80
+ composition: z.strictObject({ parts: z.array(part).min(1) }).optional(),
81
+ layout: z.strictObject({ regions: z.array(region).min(1) }).optional(),
82
+ behavior: behaviorSchema.optional(),
83
+ accessibility: patternAccessibilitySchema.optional(),
84
+ variations: z.array(variation).optional(),
85
+ relationships: z.array(relationship).optional(),
86
+ constraints: z.array(constraint).optional(),
87
+ guidelines: z.array(reference).optional(),
88
+ /** Where the pattern applies. Carried into the policies it generates; enforcement scoping arrives with validation. */
89
+ appliesTo: z
90
+ .strictObject({
91
+ paths: z.array(z.string().min(1).max(300)).min(1).max(50).optional(),
92
+ frameworks: z.array(frameworkSchema).min(1).optional()
93
+ })
94
+ .optional(),
95
+ /**
96
+ * Where a definition came from when `invariant candidates promote` wrote it: the discovered candidate, how it was
97
+ * found, and who accepted it. Provenance only. It is kept on the pattern as `extensions.discovery` and grants nothing.
98
+ */
99
+ origin: z
100
+ .strictObject({
101
+ candidate: z.string().min(1).max(100),
102
+ method: z.string().min(1).max(100),
103
+ support: z.number().int().positive(),
104
+ files: z.number().int().positive(),
105
+ acceptedBy: z.string().min(1).max(200).optional(),
106
+ acceptedAt: z.iso.datetime().optional(),
107
+ reference: z.string().min(1).max(500).optional()
108
+ })
109
+ .optional(),
110
+ /**
111
+ * Opt in: files matching `appliesTo.paths` are also held to the constraints of every pattern this one composes.
112
+ * Needs `appliesTo.paths` and an approval; without them nothing is inherited.
113
+ */
114
+ inheritsConstraints: z.boolean().optional(),
115
+ /**
116
+ * Who approved this definition. With it, the pattern's facts are `approved`; without it they stay `documented`
117
+ * until someone approves them with `invariant review`. A file in the repository is not an approval by itself.
118
+ */
119
+ approval: z
120
+ .strictObject({
121
+ approvedBy: z.string().min(1).max(200),
122
+ approvedAt: z.iso.datetime(),
123
+ role: z.string().min(1).max(200).optional(),
124
+ reference: z.string().min(1).max(500).optional()
125
+ })
126
+ .optional()
127
+ });
128
+ export const patternsFileSchema = z.strictObject({
129
+ /** Lets an editor validate the file against the exported JSON Schema. Ignored by the compiler. */
130
+ $schema: z.string().max(500).optional(),
131
+ version: z.literal(PATTERNS_FILE_VERSION),
132
+ patterns: z.array(patternDefinitionSchema)
133
+ });
134
+ export function parsePatterns(text, name = PATTERNS_FILE) {
135
+ let raw;
136
+ try {
137
+ raw = JSON.parse(text);
138
+ }
139
+ catch (error) {
140
+ return { ok: false, message: `${name} is not valid JSON: ${error.message}` };
141
+ }
142
+ const parsed = patternsFileSchema.safeParse(raw);
143
+ if (!parsed.success) {
144
+ const i = parsed.error.issues[0];
145
+ return {
146
+ ok: false,
147
+ message: `${name} is invalid at ${i?.path.join(".") || "(root)"}: ${i?.message ?? "schema error"}`
148
+ };
149
+ }
150
+ return { ok: true, value: parsed.data };
151
+ }
152
+ /** JSON Schema for editors and CI, generated from the same definition. Refinements are not representable. */
153
+ export function patternsFileJsonSchema() {
154
+ return { title: "Invariant pattern definitions v1", ...z.toJSONSchema(patternsFileSchema) };
155
+ }
156
+ //# sourceMappingURL=patterns-file.js.map