design-constraint-validator 1.1.0 → 2.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 (99) hide show
  1. package/README.md +90 -21
  2. package/adapters/decisionthemes.d.ts +44 -0
  3. package/adapters/decisionthemes.d.ts.map +1 -0
  4. package/adapters/decisionthemes.js +35 -0
  5. package/adapters/decisionthemes.ts +59 -0
  6. package/cli/commands/build.js +1 -1
  7. package/cli/commands/build.ts +1 -1
  8. package/cli/commands/graph.js +4 -4
  9. package/cli/commands/graph.ts +4 -4
  10. package/cli/commands/validate.d.ts.map +1 -1
  11. package/cli/commands/validate.js +43 -6
  12. package/cli/commands/validate.ts +41 -8
  13. package/cli/config-schema.d.ts +39 -0
  14. package/cli/config-schema.d.ts.map +1 -1
  15. package/cli/config-schema.js +4 -2
  16. package/cli/config-schema.ts +4 -2
  17. package/cli/config.d.ts.map +1 -1
  18. package/cli/config.js +8 -2
  19. package/cli/config.ts +8 -2
  20. package/cli/constraint-registry.d.ts +16 -0
  21. package/cli/constraint-registry.d.ts.map +1 -1
  22. package/cli/constraint-registry.js +64 -31
  23. package/cli/constraint-registry.ts +67 -31
  24. package/cli/dcv.js +8 -24
  25. package/cli/dcv.ts +8 -20
  26. package/cli/json-output.d.ts +8 -1
  27. package/cli/json-output.d.ts.map +1 -1
  28. package/cli/json-output.js +13 -4
  29. package/cli/json-output.ts +20 -4
  30. package/cli/types.d.ts +2 -0
  31. package/cli/types.d.ts.map +1 -1
  32. package/cli/types.ts +2 -0
  33. package/cli/validate-api.d.ts +40 -0
  34. package/cli/validate-api.d.ts.map +1 -0
  35. package/cli/validate-api.js +85 -0
  36. package/cli/validate-api.ts +126 -0
  37. package/cli/version-banner.d.ts +20 -0
  38. package/cli/version-banner.d.ts.map +1 -0
  39. package/cli/version-banner.js +49 -0
  40. package/cli/version-banner.ts +61 -0
  41. package/core/breakpoints.d.ts +8 -2
  42. package/core/breakpoints.d.ts.map +1 -1
  43. package/core/breakpoints.js +24 -3
  44. package/core/breakpoints.ts +22 -3
  45. package/core/color.js +4 -4
  46. package/core/color.ts +4 -4
  47. package/core/constraints/monotonic-lightness.d.ts.map +1 -1
  48. package/core/constraints/monotonic-lightness.js +9 -5
  49. package/core/constraints/monotonic-lightness.ts +9 -4
  50. package/core/constraints/wcag.d.ts.map +1 -1
  51. package/core/constraints/wcag.js +6 -0
  52. package/core/constraints/wcag.ts +6 -0
  53. package/core/dtcg.d.ts +38 -0
  54. package/core/dtcg.d.ts.map +1 -0
  55. package/core/dtcg.js +88 -0
  56. package/core/dtcg.ts +102 -0
  57. package/core/engine.d.ts +6 -0
  58. package/core/engine.d.ts.map +1 -1
  59. package/core/engine.ts +7 -0
  60. package/core/flatten.d.ts +5 -3
  61. package/core/flatten.d.ts.map +1 -1
  62. package/core/flatten.js +24 -10
  63. package/core/flatten.ts +39 -16
  64. package/core/image-export.d.ts.map +1 -1
  65. package/core/image-export.js +10 -7
  66. package/core/image-export.ts +9 -6
  67. package/core/index.d.ts +2 -0
  68. package/core/index.d.ts.map +1 -1
  69. package/core/index.js +4 -0
  70. package/core/index.ts +6 -0
  71. package/core/why.d.ts +1 -1
  72. package/core/why.d.ts.map +1 -1
  73. package/core/why.ts +1 -1
  74. package/mcp/contracts.d.ts +118 -0
  75. package/mcp/contracts.d.ts.map +1 -0
  76. package/mcp/contracts.js +30 -0
  77. package/mcp/contracts.ts +51 -0
  78. package/mcp/index.d.ts +9 -0
  79. package/mcp/index.d.ts.map +1 -0
  80. package/mcp/index.js +32 -0
  81. package/mcp/index.ts +70 -0
  82. package/mcp/tools.d.ts +52 -0
  83. package/mcp/tools.d.ts.map +1 -0
  84. package/mcp/tools.js +172 -0
  85. package/mcp/tools.ts +254 -0
  86. package/package.json +41 -26
  87. package/server.json +21 -0
  88. package/cli/constraints-loader.d.ts +0 -30
  89. package/cli/constraints-loader.d.ts.map +0 -1
  90. package/cli/constraints-loader.js +0 -58
  91. package/cli/constraints-loader.ts +0 -83
  92. package/cli/engine-helpers.d.ts +0 -41
  93. package/cli/engine-helpers.d.ts.map +0 -1
  94. package/cli/engine-helpers.js +0 -135
  95. package/cli/engine-helpers.ts +0 -133
  96. package/core/cross-axis-config.d.ts +0 -34
  97. package/core/cross-axis-config.d.ts.map +0 -1
  98. package/core/cross-axis-config.js +0 -173
  99. package/core/cross-axis-config.ts +0 -182
@@ -1,182 +0,0 @@
1
- /**
2
- * @deprecated This module is deprecated. Use cli/cross-axis-loader.ts instead.
3
- *
4
- * Phase 3B (Filesystem Separation): This file contains filesystem access logic
5
- * that has been moved to the CLI layer (cli/cross-axis-loader.ts).
6
- *
7
- * Core modules should not import from node:fs. Instead:
8
- * - CLI code uses cli/cross-axis-loader.ts to read and parse rules
9
- * - Core plugin (core/constraints/cross-axis.ts) accepts pre-parsed rules
10
- *
11
- * Migration:
12
- * ```ts
13
- * // OLD (core reads filesystem):
14
- * import { loadCrossAxisPlugin } from './core/cross-axis-config.js';
15
- * engine.use(loadCrossAxisPlugin(path, bp, { knownIds }));
16
- *
17
- * // NEW (CLI reads, core receives data):
18
- * import { loadCrossAxisRules } from './cli/cross-axis-loader.js';
19
- * import { CrossAxisPlugin } from './core/constraints/cross-axis.js';
20
- * const rules = loadCrossAxisRules(path, { bp, knownIds });
21
- * engine.use(CrossAxisPlugin(rules, bp));
22
- * ```
23
- *
24
- * This file will be removed in a future major version.
25
- */
26
-
27
- import fs from "node:fs";
28
- import { CrossAxisPlugin, type CrossAxisRule, type Ctx } from "./constraints/cross-axis.js";
29
-
30
- type When = { id: string; op: "<="|">="|"<"|">"|"=="|"!="; value: number };
31
- type Require = { id: string; op: "<="|">="|"<"|">"|"=="|"!="; ref?: string; fallback?: string|number };
32
- type Compare = { a: string; op: "<="|">="|"<"|">"|"=="|"!="; b: string; delta?: string|number };
33
- type RawRule = { id: string; level?: "error"|"warn"; where?: string; bp?: string; when?: When; require?: Require; compare?: Compare; };
34
-
35
- /**
36
- * @deprecated Use cli/cross-axis-loader.ts loadCrossAxisRules() + CrossAxisPlugin() instead.
37
- * This function will be removed in a future major version.
38
- */
39
- export function loadCrossAxisPlugin(
40
- path: string,
41
- bp?: string,
42
- opts?: { debug?: boolean; knownIds?: Set<string> }
43
- ) {
44
- const debug = !!opts?.debug;
45
- const known = opts?.knownIds ?? new Set<string>();
46
- const log = (...args: any[]) => { if (debug) console.log("[cross-axis]", ...args); };
47
-
48
- if (!fs.existsSync(path)) {
49
- log(`no rules file at ${path} (bp=${bp ?? "global"})`);
50
- return CrossAxisPlugin([], bp);
51
- }
52
-
53
- const raw = JSON.parse(fs.readFileSync(path, "utf8")) as { rules: RawRule[] };
54
- const rules: CrossAxisRule[] = [];
55
- const unknownIds = new Set<string>();
56
- const skipped: Array<{ id?: string; reason: string }> = [];
57
-
58
- // Fuzzy suggestion helpers (lightweight Levenshtein)
59
- function levenshtein(a: string, b: string) {
60
- const dp = Array(b.length + 1).fill(0).map((_, j) => j);
61
- for (let i = 1; i <= a.length; i++) {
62
- let prev = i - 1, cur = i;
63
- for (let j = 1; j <= b.length; j++) {
64
- const tmp = cur;
65
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
66
- cur = Math.min(dp[j] + 1, cur + 1, prev + cost);
67
- dp[j] = tmp;
68
- prev = tmp;
69
- }
70
- dp[b.length] = cur;
71
- }
72
- return dp[b.length];
73
- }
74
- function suggest(id: string, k = 3) {
75
- return [...known].map(c => ({ id: c, d: levenshtein(id, c) }))
76
- .sort((a, b) => a.d - b.d)
77
- .slice(0, k);
78
- }
79
-
80
- const needId = (id?: string) => {
81
- if (!id) return false;
82
- if (!known.has(id)) { unknownIds.add(id); }
83
- return true;
84
- };
85
-
86
- for (const r of raw.rules || []) {
87
- if (r.bp && bp && r.bp !== bp) { continue; }
88
- if (r.bp && !bp) { // rule targets specific breakpoint; skip in global run
89
- continue;
90
- }
91
- try {
92
- if (r.when && r.require) {
93
- // Validate IDs
94
- needId(r.when.id);
95
- needId(r.require.id);
96
- if (r.require.ref) needId(r.require.ref);
97
-
98
- rules.push({
99
- id: r.id, level: r.level, where: r.where,
100
- when: { id: r.when.id, test: makeOp(r.when.op, r.when.value) },
101
- require: {
102
- id: r.require.id,
103
- test: (v: number, ctx: Ctx) => {
104
- const rhs = valueOrRef(ctx, r.require!.ref, r.require!.fallback);
105
- return cmp(v, rhs, r.require!.op);
106
- },
107
- msg: (v: number, ctx: Ctx) => {
108
- const rhs = valueOrRef(ctx, r.require!.ref, r.require!.fallback);
109
- return `${r.require!.id} ${prettyFail(r.require!.op)} ${fmt(rhs)} (was ${fmt(v)})`;
110
- }
111
- }
112
- });
113
- } else if (r.compare) {
114
- needId(r.compare.a);
115
- needId(r.compare.b);
116
-
117
- rules.push({
118
- id: r.id, level: r.level, where: r.where,
119
- when: { id: r.compare.a, test: () => true },
120
- require: {
121
- id: r.compare.a,
122
- test: (_: number, ctx: Ctx) => {
123
- const a = ctx.getPx(r.compare!.a) ?? NaN;
124
- const b = ctx.getPx(r.compare!.b) ?? NaN;
125
- const delta = px(r.compare!.delta ?? 0);
126
- if (Number.isNaN(a) || Number.isNaN(b)) return true; // skip check if missing
127
- return cmp(a, b + delta, r.compare!.op);
128
- },
129
- msg: (_: number, ctx: Ctx) => {
130
- const a = ctx.getPx(r.compare!.a);
131
- const b = ctx.getPx(r.compare!.b);
132
- const delta = px(r.compare!.delta ?? 0);
133
- return `${r.compare!.a} ${prettyFail(r.compare!.op)} ${fmt((b ?? 0) + delta)} (was ${fmt(a ?? NaN)})`;
134
- }
135
- }
136
- });
137
- } else {
138
- skipped.push({ id: r.id, reason: "neither when+require nor compare present" });
139
- }
140
- } catch (e: any) {
141
- skipped.push({ id: r.id, reason: `exception: ${e?.message ?? e}` });
142
- }
143
- }
144
-
145
- log(`loaded ${rules.length} rule(s) from ${path}${bp ? ` [bp=${bp}]` : ""}`);
146
- if (unknownIds.size) {
147
- log(`unknown ids referenced:`, [...unknownIds].join(", "));
148
- for (const u of unknownIds) {
149
- const s = suggest(u, 3);
150
- if (s.length) log(` did you mean: ${s.map(x => `${x.id} (d=${x.d})`).join(', ')}`);
151
- }
152
- }
153
- if (skipped.length) {
154
- for (const s of skipped) log(`skipped rule ${s.id ?? "(no id)"} — ${s.reason}`);
155
- }
156
-
157
- // Extra hint for common anchor pitfall
158
- for (const r of raw.rules || []) {
159
- if (r.require?.ref && !known.has(r.require.ref)) {
160
- log(`anchor missing: ${r.require.ref} → will use fallback=${JSON.stringify(r.require.fallback)} when evaluating`);
161
- }
162
- }
163
-
164
- return CrossAxisPlugin(rules, bp);
165
- }
166
-
167
- // helpers
168
- const px = (v: string|number) => typeof v === "number" ? v : parseFloat(String(v)) * (String(v).trim().endsWith("rem") ? 16 : 1);
169
- const cmp = (a:number,b:number,op:When["op"]) =>
170
- op === ">="? a>=b : op === ">"? a>b : op === "<="? a<=b : op === "<"? a<b : op === "=="? a===b : a!==b;
171
- const prettyFail = (op: string) => ({">=":"<",">":"≤","<=":">","<": "≥","==":"≠","!=":"="} as any)[op] || "≠";
172
- const fmt = (v:number|string) => Number.isFinite(Number(v)) ? `${Number(v)}px` : String(v);
173
- function valueOrRef(ctx: Ctx, ref?: string, fallback?: string|number) {
174
- if (ref) {
175
- const v = ctx.getPx(ref);
176
- if (v != null) return v;
177
- }
178
- return typeof fallback === "number" ? fallback : px(fallback ?? 0);
179
- }
180
- function makeOp(op: When["op"], rhs: number) {
181
- return (v: number) => cmp(v, rhs, op);
182
- }