@invariant-ui/cli 0.0.0-stage → 0.2.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 (190) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +250 -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/behavior.js +75 -0
  84. package/dist/compiler-react/detect.js +558 -0
  85. package/dist/compiler-react/emit.js +188 -0
  86. package/dist/compiler-react/index.js +2 -0
  87. package/dist/compiler-react/props.js +157 -0
  88. package/dist/compiler-react/styling.js +138 -0
  89. package/dist/compiler-react/usage.js +116 -0
  90. package/dist/compiler-react/variants.js +111 -0
  91. package/dist/compiler-storybook/adapter.js +253 -0
  92. package/dist/compiler-storybook/csf.js +521 -0
  93. package/dist/compiler-storybook/emit.js +202 -0
  94. package/dist/compiler-storybook/index-json.js +41 -0
  95. package/dist/compiler-storybook/index.js +5 -0
  96. package/dist/compiler-storybook/mdx.js +120 -0
  97. package/dist/compiler-tokens/adapter.js +158 -0
  98. package/dist/compiler-tokens/css.js +59 -0
  99. package/dist/compiler-tokens/index.js +5 -0
  100. package/dist/compiler-tokens/json-lines.js +106 -0
  101. package/dist/compiler-tokens/json-tokens.js +52 -0
  102. package/dist/compiler-vue/adapter.js +107 -0
  103. package/dist/compiler-vue/analyze.js +344 -0
  104. package/dist/compiler-vue/declared.js +343 -0
  105. package/dist/compiler-vue/emit.js +129 -0
  106. package/dist/compiler-vue/index.js +5 -0
  107. package/dist/compiler-vue/script.js +472 -0
  108. package/dist/compiler-vue/sfc.js +44 -0
  109. package/dist/compiler-vue/template.js +73 -0
  110. package/dist/contract/archetype-checks.js +153 -0
  111. package/dist/contract/components.js +102 -0
  112. package/dist/contract/conflicts.js +39 -0
  113. package/dist/contract/contract.js +46 -0
  114. package/dist/contract/evidence.js +84 -0
  115. package/dist/contract/fact-path.js +39 -0
  116. package/dist/contract/facts.js +62 -0
  117. package/dist/contract/index.js +16 -0
  118. package/dist/contract/integrity.js +21 -0
  119. package/dist/contract/json-schema.js +11 -0
  120. package/dist/contract/knowledge.js +95 -0
  121. package/dist/contract/pattern-checks.js +254 -0
  122. package/dist/contract/patterns.js +225 -0
  123. package/dist/contract/primitives.js +81 -0
  124. package/dist/contract/sources.js +30 -0
  125. package/dist/contract/tokens.js +41 -0
  126. package/dist/contract/validate.js +253 -0
  127. package/dist/contract-query/archetype.js +188 -0
  128. package/dist/contract-query/index.js +6 -0
  129. package/dist/contract-query/patterns.js +298 -0
  130. package/dist/contract-query/search.js +132 -0
  131. package/dist/contract-query/system.js +148 -0
  132. package/dist/contract-query/views.js +288 -0
  133. package/dist/governance/decisions.js +229 -0
  134. package/dist/governance/index.js +5 -0
  135. package/dist/governance/patterns-file.js +156 -0
  136. package/dist/governance/patterns.js +510 -0
  137. package/dist/governance/policies.js +148 -0
  138. package/dist/mcp/index.js +7 -0
  139. package/dist/mcp/loader.js +70 -0
  140. package/dist/mcp/protocol.js +27 -0
  141. package/dist/mcp/resources.js +138 -0
  142. package/dist/mcp/server.js +177 -0
  143. package/dist/mcp/stdio.js +28 -0
  144. package/dist/mcp/tools.js +267 -0
  145. package/dist/reconciliation/code.js +62 -0
  146. package/dist/reconciliation/components.js +401 -0
  147. package/dist/reconciliation/conflicts.js +126 -0
  148. package/dist/reconciliation/facts.js +41 -0
  149. package/dist/reconciliation/index.js +7 -0
  150. package/dist/reconciliation/reconcile.js +134 -0
  151. package/dist/reconciliation/resolve.js +158 -0
  152. package/dist/reconciliation/review.js +81 -0
  153. package/dist/reconciliation/storybook.js +370 -0
  154. package/dist/reconciliation/tokens.js +179 -0
  155. package/dist/reconciliation/util.js +55 -0
  156. package/dist/semantic-engine/additive.js +51 -0
  157. package/dist/semantic-engine/compounds.js +91 -0
  158. package/dist/semantic-engine/documents.js +157 -0
  159. package/dist/semantic-engine/enrich.js +42 -0
  160. package/dist/semantic-engine/index.js +7 -0
  161. package/dist/semantic-engine/infer.js +38 -0
  162. package/dist/semantic-engine/tokens.js +120 -0
  163. package/dist/shared/canonical.js +65 -0
  164. package/dist/shared/diagnostic.js +78 -0
  165. package/dist/shared/glob.js +98 -0
  166. package/dist/shared/index.js +4 -0
  167. package/dist/shared/json.js +2 -0
  168. package/dist/validation/card.js +50 -0
  169. package/dist/validation/discovery/arrangements.js +162 -0
  170. package/dist/validation/discovery/discover.js +70 -0
  171. package/dist/validation/discovery/index.js +8 -0
  172. package/dist/validation/discovery/model.js +127 -0
  173. package/dist/validation/discovery/promote.js +24 -0
  174. package/dist/validation/discovery/reviews.js +63 -0
  175. package/dist/validation/discovery/shapes.js +47 -0
  176. package/dist/validation/discovery/similar-files.js +146 -0
  177. package/dist/validation/discovery/util.js +47 -0
  178. package/dist/validation/index.js +11 -0
  179. package/dist/validation/interaction-facts.js +341 -0
  180. package/dist/validation/interaction-workflows.js +633 -0
  181. package/dist/validation/literals.js +251 -0
  182. package/dist/validation/pattern-rules.js +608 -0
  183. package/dist/validation/policy.js +241 -0
  184. package/dist/validation/report.js +163 -0
  185. package/dist/validation/rules.js +269 -0
  186. package/dist/validation/scan.js +295 -0
  187. package/dist/validation/text.js +34 -0
  188. package/dist/validation/validate-code.js +513 -0
  189. package/dist/validation/validate-usage.js +196 -0
  190. package/package.json +38 -4
@@ -0,0 +1,134 @@
1
+ import { CONTRACT_VERSION, createEvidenceFile, finalizeContract, verifyContract } from "../contract/index.js";
2
+ import { DiagnosticCollector } from "../shared/index.js";
3
+ import { buildComponent } from "./components.js";
4
+ import { ambiguityConflicts, mintConflicts, storybookConflicts } from "./conflicts.js";
5
+ import { resolveComponents } from "./resolve.js";
6
+ import { reconcileStorybook } from "./storybook.js";
7
+ import { buildTokens } from "./tokens.js";
8
+ import { cmp, slug, unique } from "./util.js";
9
+ function inferredFact(link) {
10
+ return {
11
+ status: "inferred",
12
+ evidenceIds: link.evidenceIds,
13
+ confidence: link.confidence,
14
+ derivation: { rule: link.rule, reasoning: link.reasoning, inputs: link.inputs }
15
+ };
16
+ }
17
+ function sourceOf(doc) {
18
+ return {
19
+ id: doc.source.id,
20
+ type: doc.source.type,
21
+ location: doc.source.location,
22
+ ...(doc.source.version ? { version: doc.source.version } : {}),
23
+ capabilities: [...doc.capabilities].sort(),
24
+ health: {
25
+ status: doc.completeness === "complete" ? "ok" : doc.completeness === "partial" ? "degraded" : "failed",
26
+ diagnosticCount: doc.diagnostics.length
27
+ }
28
+ };
29
+ }
30
+ /**
31
+ * Turns IR documents into one contract draft. Deterministic: same documents and options, same bytes. Every
32
+ * value and fact comes from an IR claim; the only things reconciliation adds are identity links between
33
+ * differently named entities, and those are `inferred` with a rule and confidence.
34
+ */
35
+ export function reconcile(docs, options) {
36
+ const diagnostics = new DiagnosticCollector();
37
+ const conflicts = [];
38
+ const resolution = resolveComponents(docs);
39
+ const refToId = new Map();
40
+ const byRef = new Map();
41
+ for (const rc of resolution.components)
42
+ for (const impl of rc.implementations) {
43
+ refToId.set(impl.code.ref, rc.id);
44
+ byRef.set(impl.code.ref, impl);
45
+ }
46
+ const components = resolution.components.map((rc) => {
47
+ const component = buildComponent(rc, { refToId, diagnostics, conflicts });
48
+ const facts = { ...component.facts };
49
+ for (const impl of rc.implementations) {
50
+ facts[`implementations.${impl.id}`] = rc.identity
51
+ ? inferredFact(rc.identity)
52
+ : {
53
+ status: "discovered",
54
+ evidenceIds: impl.facts.find((f) => f.path === "name")?.evidenceIds ?? impl.facts[0]?.evidenceIds ?? []
55
+ };
56
+ }
57
+ return { ...component, facts: Object.fromEntries(Object.entries(facts).sort((a, b) => cmp(a[0], b[0]))) };
58
+ });
59
+ const tokenResult = buildTokens(docs, { diagnostics, conflicts });
60
+ const tokens = tokenResult.tokens.map((t) => {
61
+ const link = tokenResult.identities.find((i) => i.tokenId === t.id)?.link;
62
+ return link ? { ...t, facts: { ...t.facts, name: inferredFact(link) } } : t;
63
+ });
64
+ const storybook = reconcileStorybook(docs);
65
+ diagnostics.addAll(storybook.diagnostics);
66
+ conflicts.push(...storybookConflicts(storybook.discrepancies, refToId, byRef));
67
+ conflicts.push(...ambiguityConflicts(resolution.ambiguities, refToId));
68
+ for (const doc of docs)
69
+ for (const e of doc.entities)
70
+ if (e.kind === "component" && !refToId.has(e.ref))
71
+ diagnostics.info("reconcile.component-skipped", `${e.name} publishes no framework link key and was not placed in the contract`, {
72
+ sourceId: doc.source.id,
73
+ entityId: e.ref
74
+ });
75
+ const minted = mintConflicts(conflicts);
76
+ const sources = new Map();
77
+ for (const doc of [...docs].sort((a, b) => cmp(a.source.id, b.source.id)))
78
+ if (!sources.has(doc.source.id))
79
+ sources.set(doc.source.id, sourceOf(doc));
80
+ const cited = new Set();
81
+ for (const c of components)
82
+ for (const f of Object.values(c.facts))
83
+ for (const id of f.evidenceIds)
84
+ cited.add(id);
85
+ for (const t of tokens)
86
+ for (const f of Object.values(t.facts))
87
+ for (const id of f.evidenceIds)
88
+ cited.add(id);
89
+ for (const c of minted)
90
+ for (const claim of c.claims)
91
+ for (const id of claim.evidenceIds)
92
+ cited.add(id);
93
+ const pool = new Map();
94
+ for (const doc of docs)
95
+ for (const e of doc.evidence)
96
+ if (!pool.has(e.id))
97
+ pool.set(e.id, e);
98
+ const evidence = [];
99
+ for (const id of [...cited].sort()) {
100
+ const found = pool.get(id);
101
+ if (found)
102
+ evidence.push(found);
103
+ else
104
+ diagnostics.error("reconcile.missing-evidence", `A fact cites evidence ${id}, which no IR document contains`);
105
+ }
106
+ const contract = {
107
+ contractVersion: CONTRACT_VERSION,
108
+ metadata: {
109
+ contractId: options.contractId ?? slug(options.name),
110
+ name: options.name,
111
+ ...(options.description ? { description: options.description } : {}),
112
+ compilerVersion: options.compilerVersion,
113
+ frameworks: unique(components.flatMap((c) => c.implementations.map((i) => i.framework))).sort(),
114
+ compiledAt: (options.clock ?? (() => new Date().toISOString()))()
115
+ },
116
+ sources: [...sources.values()],
117
+ themes: tokenResult.themes,
118
+ components,
119
+ tokens,
120
+ patterns: [],
121
+ guidelines: [],
122
+ constraints: [],
123
+ policies: [],
124
+ conflicts: minted
125
+ };
126
+ return { contract, evidence, diagnostics: diagnostics.toArray() };
127
+ }
128
+ /** Hashes the draft and verifies it, with its evidence, against the contract's own rules. */
129
+ export function finalizeAndVerify(draft, evidence) {
130
+ const contract = finalizeContract(draft);
131
+ const evidenceFile = createEvidenceFile(contract.integrity.contentHash, evidence);
132
+ return { contract, evidenceFile, diagnostics: verifyContract(contract, evidenceFile) };
133
+ }
134
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1,158 @@
1
+ import { codeComponents } from "./code.js";
2
+ import { cmp, normalizeName, slug, unique } from "./util.js";
3
+ const MIN_PREFIX_COMPONENTS = 3;
4
+ const MAX_PREFIX_LENGTH = 5;
5
+ const PREFIX_SHARE = 0.8;
6
+ /**
7
+ * A design-system prefix such as `Lds` in `LdsButton`: a short capitalized word that most components of one
8
+ * source start with. Components that are the prefix itself prove nothing about it, so it is rejected then.
9
+ */
10
+ export function sharedPrefix(names) {
11
+ if (names.length < MIN_PREFIX_COMPONENTS)
12
+ return undefined;
13
+ const counts = new Map();
14
+ for (const name of names) {
15
+ const word = /^[A-Z][a-z0-9]*/.exec(name)?.[0];
16
+ if (word && word.length >= 2 && word.length <= MAX_PREFIX_LENGTH && name.length > word.length)
17
+ counts.set(word, (counts.get(word) ?? 0) + 1);
18
+ }
19
+ const best = [...counts.entries()].sort((a, b) => b[1] - a[1] || cmp(a[0], b[0]))[0];
20
+ if (!best || best[1] / names.length < PREFIX_SHARE)
21
+ return undefined;
22
+ return names.includes(best[0]) ? undefined : best[0];
23
+ }
24
+ /** The leading word of an Angular class name, when it equals the first word of the component's selector. */
25
+ function selectorPrefix(impl) {
26
+ const selector = impl.facts.find((f) => /^implementations\.angular\.extensions\.angular\.selector$/.test(f.path))?.value;
27
+ if (typeof selector !== "string")
28
+ return undefined;
29
+ const first = /^[a-z][a-z0-9]*/.exec(selector)?.[0];
30
+ if (!first || selector === first)
31
+ return undefined;
32
+ const lead = first.charAt(0).toUpperCase() + first.slice(1);
33
+ return impl.code.name.startsWith(lead) && impl.code.name.length > lead.length ? lead : undefined;
34
+ }
35
+ function evidenceOf(impl) {
36
+ const named = impl.facts.find((f) => f.path === "name") ?? impl.facts[0];
37
+ return named ? named.evidenceIds : [];
38
+ }
39
+ /**
40
+ * Groups implementations into conceptual components. Same-framework implementations are never merged. Across
41
+ * frameworks a merge needs an equal normalized name where each framework has exactly one candidate; otherwise
42
+ * the match is reported as an ambiguity and nothing is merged.
43
+ */
44
+ export function resolveComponents(docs) {
45
+ const factsByDoc = new Map();
46
+ for (const doc of docs) {
47
+ const map = new Map();
48
+ for (const f of doc.facts)
49
+ map.set(f.subject, [...(map.get(f.subject) ?? []), f]);
50
+ factsByDoc.set(doc, map);
51
+ }
52
+ const implementations = [];
53
+ for (const code of codeComponents(docs)) {
54
+ const doc = docs.find((d) => d.source.id === code.sourceId && d.entities.some((e) => e.ref === code.ref));
55
+ if (!doc)
56
+ continue;
57
+ const packageName = code.index.value(code.ref, `implementations.${code.framework}.packageName`);
58
+ implementations.push({
59
+ code,
60
+ doc,
61
+ facts: factsByDoc.get(doc)?.get(code.ref) ?? [],
62
+ id: "",
63
+ ...(packageName ? { packageName } : {})
64
+ });
65
+ }
66
+ implementations.sort((a, b) => cmp(a.code.sourceId, b.code.sourceId) || cmp(a.code.ref, b.code.ref));
67
+ const prefixBySource = new Map();
68
+ for (const sourceId of unique(implementations.map((i) => i.code.sourceId)))
69
+ prefixBySource.set(sourceId, sharedPrefix(implementations.filter((i) => i.code.sourceId === sourceId).map((i) => i.code.name)));
70
+ const keyed = implementations.map((impl) => {
71
+ const prefix = prefixBySource.get(impl.code.sourceId);
72
+ if (prefix && impl.code.name.startsWith(prefix) && impl.code.name.length > prefix.length)
73
+ return { impl, key: normalizeName(impl.code.name.slice(prefix.length)), via: "shared-prefix", prefix };
74
+ const fromSelector = selectorPrefix(impl);
75
+ if (fromSelector)
76
+ return {
77
+ impl,
78
+ key: normalizeName(impl.code.name.slice(fromSelector.length)),
79
+ via: "selector-prefix",
80
+ prefix: fromSelector
81
+ };
82
+ return { impl, key: normalizeName(impl.code.name), via: "name" };
83
+ });
84
+ const groups = new Map();
85
+ for (const k of keyed)
86
+ groups.set(k.key, [...(groups.get(k.key) ?? []), k]);
87
+ const resolved = [];
88
+ const ambiguities = [];
89
+ for (const [key, members] of [...groups.entries()].sort((a, b) => cmp(a[0], b[0]))) {
90
+ const frameworks = unique(members.map((m) => m.impl.code.framework));
91
+ const perFramework = frameworks.map((f) => members.filter((m) => m.impl.code.framework === f).length);
92
+ if (frameworks.length === 1) {
93
+ for (const m of members)
94
+ resolved.push({ name: m.impl.code.name, members: [m.impl] });
95
+ continue;
96
+ }
97
+ if (perFramework.some((n) => n > 1)) {
98
+ ambiguities.push({ key, candidates: members.map((m) => m.impl) });
99
+ for (const m of members)
100
+ resolved.push({ name: m.impl.code.name, members: [m.impl] });
101
+ continue;
102
+ }
103
+ const usedPrefix = members.some((m) => m.via !== "name");
104
+ const rule = usedPrefix ? "name-match.stripped-prefix" : "name-match.exact";
105
+ const confidence = usedPrefix ? 0.75 : 0.9;
106
+ const names = members.map((m) => m.impl.code.name);
107
+ const display = [...members].sort((a, b) => a.impl.code.name.length - b.impl.code.name.length || cmp(a.impl.code.ref, b.impl.code.ref))[0];
108
+ resolved.push({
109
+ name: display?.impl.code.name ?? names[0] ?? key,
110
+ members: members.map((m) => m.impl),
111
+ identity: {
112
+ rule,
113
+ confidence,
114
+ reasoning: usedPrefix
115
+ ? `${names.join(", ")} are in different frameworks and share the name "${key}" once the prefix ${unique(members.flatMap((m) => (m.prefix ? [`"${m.prefix}"`] : []))).join(", ")} is removed`
116
+ : `${names.join(", ")} are in different frameworks and share the name "${key}" ignoring case and separators`,
117
+ inputs: members.map((m) => m.impl.code.ref).sort(),
118
+ evidenceIds: unique(members.flatMap((m) => evidenceOf(m.impl))).sort()
119
+ }
120
+ });
121
+ }
122
+ return { components: assignIds(resolved), ambiguities };
123
+ }
124
+ function assignIds(resolved) {
125
+ const byBase = new Map();
126
+ for (const r of resolved)
127
+ byBase.set(slug(r.name), (byBase.get(slug(r.name)) ?? 0) + 1);
128
+ const used = new Set();
129
+ const out = [];
130
+ for (const r of [...resolved].sort((a, b) => cmp(a.members[0]?.code.ref ?? "", b.members[0]?.code.ref ?? ""))) {
131
+ const base = slug(r.name);
132
+ const pkg = r.members.find((m) => m.packageName)?.packageName;
133
+ let id = `component:${(byBase.get(base) ?? 0) > 1 && pkg ? `${base}.${slug(pkg)}` : base}`;
134
+ for (let n = 2; used.has(id); n++)
135
+ id = `component:${base}-${n}`;
136
+ used.add(id);
137
+ out.push({
138
+ id,
139
+ name: r.name,
140
+ implementations: assignImplementationIds(r.members),
141
+ ...(r.identity ? { identity: r.identity } : {})
142
+ });
143
+ }
144
+ return out.sort((a, b) => cmp(a.id, b.id));
145
+ }
146
+ function assignImplementationIds(members) {
147
+ const used = new Set();
148
+ return members.map((m) => {
149
+ let id = slug(m.code.framework);
150
+ if (used.has(id))
151
+ id = slug(`${m.code.framework}-${m.code.sourceId}`);
152
+ for (let n = 2; used.has(id); n++)
153
+ id = slug(`${m.code.framework}-${m.code.sourceId}-${n}`);
154
+ used.add(id);
155
+ return { ...m, id };
156
+ });
157
+ }
158
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1,81 @@
1
+ import { hashJson } from "../shared/index.js";
2
+ import { cmp } from "./util.js";
3
+ const PRIORITY = { high: 0, medium: 1, low: 2 };
4
+ /**
5
+ * Everything a person must look at: open conflicts and every inferred fact. Derived from the contract and
6
+ * not stored in it; rebuilding it from the same contract gives the same list.
7
+ */
8
+ export function buildReviewQueue(contract) {
9
+ const items = [];
10
+ for (const c of contract.conflicts) {
11
+ if (c.resolution.state !== "open")
12
+ continue;
13
+ items.push({
14
+ id: `rv_${hashJson({ kind: "conflict", id: c.id }).slice(0, 16)}`,
15
+ kind: "conflict",
16
+ conflictId: c.id,
17
+ subject: c.subject,
18
+ ...(c.path ? { path: c.path } : {}),
19
+ priority: c.severity === "error" ? "high" : c.severity === "warning" ? "medium" : "low",
20
+ reason: `${c.type}: ${c.claims.length} claims disagree${c.suggestedResolution ? `. ${c.suggestedResolution}` : ""}`,
21
+ evidenceIds: [...new Set(c.claims.flatMap((x) => x.evidenceIds))].sort(),
22
+ approvalRequired: true,
23
+ state: "open"
24
+ });
25
+ }
26
+ const entities = [
27
+ ...contract.components,
28
+ ...contract.tokens,
29
+ ...contract.guidelines,
30
+ ...contract.patterns,
31
+ ...contract.constraints
32
+ ];
33
+ for (const e of entities) {
34
+ for (const [path, fact] of Object.entries(e.facts)) {
35
+ if (fact.status !== "inferred")
36
+ continue;
37
+ const confidence = fact.confidence ?? 0;
38
+ items.push({
39
+ id: `rv_${hashJson({ kind: "inferred-fact", subject: e.id, path }).slice(0, 16)}`,
40
+ kind: "inferred-fact",
41
+ subject: e.id,
42
+ path,
43
+ priority: confidence < 0.6 ? "high" : confidence < 0.85 ? "medium" : "low",
44
+ reason: fact.derivation?.reasoning ?? "inferred",
45
+ confidence,
46
+ ...(fact.derivation ? { rule: fact.derivation.rule } : {}),
47
+ evidenceIds: [...fact.evidenceIds].sort(),
48
+ approvalRequired: true,
49
+ state: "pending"
50
+ });
51
+ }
52
+ }
53
+ // A pattern written by a person is `documented`, not approved: someone must still say it is the standard.
54
+ // Its constraints are listed too, so each fact can be approved on its own.
55
+ const owned = new Set(contract.patterns.flatMap((p) => p.constraints ?? []));
56
+ const authored = [
57
+ ...contract.patterns,
58
+ ...contract.constraints.filter((c) => owned.has(c.id))
59
+ ];
60
+ for (const e of authored)
61
+ for (const [path, fact] of Object.entries(e.facts)) {
62
+ if (fact.status !== "documented")
63
+ continue;
64
+ items.push({
65
+ id: `rv_${hashJson({ kind: "pattern-fact", subject: e.id, path }).slice(0, 16)}`,
66
+ kind: "pattern-fact",
67
+ subject: e.id,
68
+ path,
69
+ priority: "low",
70
+ reason: "Authored pattern definition that has not been approved. Approve it to make it a standard.",
71
+ evidenceIds: [...fact.evidenceIds].sort(),
72
+ approvalRequired: true,
73
+ state: "pending"
74
+ });
75
+ }
76
+ return items.sort((a, b) => PRIORITY[a.priority] - PRIORITY[b.priority] ||
77
+ cmp(a.subject, b.subject) ||
78
+ cmp(a.path ?? "", b.path ?? "") ||
79
+ cmp(a.kind, b.kind));
80
+ }
81
+ //# sourceMappingURL=review.js.map