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
package/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
  [![npm downloads](https://img.shields.io/npm/dm/design-constraint-validator.svg)](https://www.npmjs.com/package/design-constraint-validator)
7
7
  [![CI](https://github.com/CseperkePapp/design-constraint-validator/actions/workflows/ci.yml/badge.svg)](https://github.com/CseperkePapp/design-constraint-validator/actions/workflows/ci.yml)
8
8
  [![SBOM](https://img.shields.io/badge/SBOM-CycloneDX-brightgreen)](https://github.com/CseperkePapp/design-constraint-validator/actions/workflows/sbom.yml)
9
+ [![Supply Chain Security](https://img.shields.io/badge/security-hardened-blue)](SECURITY.md)
9
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
10
11
  [![Node](https://img.shields.io/badge/node-%3E%3D18.x-339933.svg)](#)
11
12
 
@@ -35,28 +36,54 @@ npx dcv --help
35
36
 
36
37
  ## Quick Start
37
38
 
39
+ DCV validates **your** tokens against **your** constraints. From an empty directory:
40
+
38
41
  ```bash
39
- # Validate tokens with default constraints
40
- npx dcv validate ./tokens/tokens.json
42
+ # 1. Your design tokens (DTCG-style "$value")
43
+ cat > tokens.json <<'JSON'
44
+ {
45
+ "color": {
46
+ "text": { "$value": "#888888" },
47
+ "bg": { "$value": "#999999" }
48
+ }
49
+ }
50
+ JSON
51
+
52
+ # 2. Your constraints — auto-discovered as dcv.config.json in the cwd
53
+ cat > dcv.config.json <<'JSON'
54
+ {
55
+ "constraints": {
56
+ "enableBuiltInWcagDefaults": false,
57
+ "enableBuiltInThreshold": false,
58
+ "wcag": [
59
+ { "foreground": "color.text", "background": "color.bg", "ratio": 4.5, "description": "Body text on background" }
60
+ ]
61
+ }
62
+ }
63
+ JSON
64
+
65
+ # 3. Validate (positional path or --tokens; exits non-zero on violations)
66
+ npx dcv validate tokens.json --summary table
41
67
 
42
- # Explain failures
43
- npx dcv why --format table
68
+ # Explain one token (the tokenId is required)
69
+ npx dcv why color.text --tokens tokens.json --format table
44
70
 
45
- # Export dependency graph
46
- npx dcv graph --format mermaid > graph.mmd
71
+ # Export the dependency graph
72
+ npx dcv graph --tokens tokens.json --format mermaid > graph.mmd
47
73
  ```
48
74
 
49
- **Example Output:**
75
+ **Example output** (`validate`):
50
76
 
51
- ```
52
- Constraint Status Details
53
- ──────────────────────────── ────── ─────────────────────────────────────────────
54
- WCAG Contrast ≥ 4.5:1 FAIL text.primary(#5A5A5A) on bg.body(#F5F5F5) ⇒ 3.8
55
- Typography monotonic scale FAIL h3(22) < body(18) < h2(21) < h1(34) ✖ out-of-order: h2<h3
56
- Cross-axis (weight vs size) PASS all headings satisfy min weight for size bucket
57
- Exit code: 1 (violations found)
77
+ ```text
78
+ validate: 1 error(s), 0 warning(s)
79
+ ERROR wcag-contrast color.text|color.bg @ Body text on background — Contrast 1.24:1 < 4.5:1
80
+ scope rules warnings errors
81
+ ------ ----- -------- ------
82
+ global 1 0 1
58
83
  ```
59
84
 
85
+ Exit code is `1` when violations are found, `0` when clean (use `--fail-on off` to always exit `0`). The built-in WCAG/threshold defaults target the bundled example token ids, so disable them (as above) when validating your own token names.
86
+
60
87
  ---
61
88
 
62
89
  ## Programmatic API
@@ -64,14 +91,15 @@ Exit code: 1 (violations found)
64
91
  ```ts
65
92
  import { validate } from 'design-constraint-validator';
66
93
 
67
- const result = await validate({
68
- tokensPath: './tokens/tokens.json',
69
- policyPath: './themes/policies/aa.json'
94
+ // Synchronous. Point at files, or pass `tokens` / `constraints` inline.
95
+ const result = validate({
96
+ tokensPath: './tokens.json',
97
+ configPath: './dcv.config.json', // omit to auto-discover dcv.config.json in the cwd
70
98
  });
71
99
 
72
100
  if (!result.ok) {
73
101
  for (const v of result.violations) {
74
- console.log(`[${v.kind}] ${v.message}`, v.context);
102
+ console.log(`[${v.ruleId}] ${v.message}`);
75
103
  }
76
104
  process.exitCode = 1;
77
105
  }
@@ -81,6 +109,31 @@ See **[API Reference](docs/API.md)** for complete programmatic usage.
81
109
 
82
110
  ---
83
111
 
112
+ ## Use from AI agents (MCP)
113
+
114
+ DCV ships a second binary, `dcv-mcp`, that exposes the validator over MCP stdio for agent clients. Add it to a Claude Desktop or generic MCP client config like this:
115
+
116
+ ```json
117
+ {
118
+ "mcpServers": {
119
+ "dcv": {
120
+ "command": "npx",
121
+ "args": ["-y", "--package", "design-constraint-validator", "dcv-mcp"]
122
+ }
123
+ }
124
+ }
125
+ ```
126
+
127
+ The server exposes exactly three JSON-returning tools:
128
+
129
+ - `validate` - validate inline `tokens` or a `tokensPath` against inline `constraints` or a config file.
130
+ - `why` - explain provenance, aliases, dependencies, dependents, and alias chain for one token id.
131
+ - `graph` - return token dependency `nodes` and `edges`.
132
+
133
+ Tool failures are returned as structured JSON: `{ "ok": false, "error": { "code": "...", "message": "..." } }`.
134
+
135
+ ---
136
+
84
137
  ## Documentation
85
138
 
86
139
  ### For Everyone
@@ -103,6 +156,7 @@ See **[API Reference](docs/API.md)** for complete programmatic usage.
103
156
  - **[Prior Art / Method](docs/prior-art/)** - Design rationale (Decision Themes, receipts)
104
157
  - **[AI Guide](docs/AI-GUIDE.md)** - Using DCV with ChatGPT/Claude/Copilot
105
158
  - **[Contributing](CONTRIBUTING.md)** - Contribution guidelines
159
+ - **[Security](SECURITY.md)** - Supply chain security measures
106
160
 
107
161
  ---
108
162
 
@@ -140,12 +194,26 @@ Adapters normalize common ecosystems:
140
194
 
141
195
  - **Style Dictionary** - See [examples/style-dictionary/](examples/style-dictionary/)
142
196
  - **Tokens Studio JSON** - See [examples/tokens-studio/](examples/tokens-studio/)
143
- - **DTCG** (Design Tokens Community Group) - See [examples/dtcg/](examples/dtcg/)
197
+ - **DTCG** (Design Tokens Community Group) — reads the **2025.10 stable spec** (structured sRGB colors, structured dimensions, `{alias}` references, `$extensions` passthrough; non-sRGB spaces warn rather than mis-calculate; composite types out of scope). See [examples/dtcg/](examples/dtcg/)
144
198
 
145
199
  Full adapter documentation: **[Adapters](docs/Adapters.md)**
146
200
 
147
201
  ---
148
202
 
203
+ ## DCV & DecisionThemes
204
+
205
+ DCV is the **standalone validation engine** — use it for any token system.
206
+
207
+ **DecisionThemes** (coming 2026) is a complete design system framework built on DCV:
208
+ - **5-axis decision model** (Tone, Emphasis, Size, Density, Shape)
209
+ - **VT/DT pipeline** (Value Themes + Decision Themes → deterministic CSS configs)
210
+ - **Studio UI** + **Hub marketplace** for sharing Decision Systems
211
+
212
+ DCV powers DecisionThemes' validation layer — but works perfectly standalone.
213
+ Preview: [www.decisionthemes.com](https://www.decisionthemes.com)
214
+
215
+ ---
216
+
149
217
  ## Method & Prior Art
150
218
 
151
219
  The Design Constraint Validator engine is based on a theming and validation method published as **defensive prior art**.
@@ -166,7 +234,8 @@ These documents keep the method openly implementable and prevent patent lock-up.
166
234
  DCV generates CycloneDX-compliant SBOMs for supply chain transparency:
167
235
 
168
236
  - **CI Builds:** SBOM artifacts on every CI run (90-day retention)
169
- - **Releases:** SBOM files (JSON + XML) attached to GitHub releases
237
+ - **Version tags:** SBOM artifacts for release tags
238
+ - **GitHub Releases:** SBOM files (JSON + XML) attached when a GitHub Release is created
170
239
  - **Manual:** Run `npx @cyclonedx/cyclonedx-npm` in project root
171
240
 
172
241
  **Download:**
@@ -180,7 +249,7 @@ DCV generates CycloneDX-compliant SBOMs for supply chain transparency:
180
249
  - Plugin API for **custom constraints**
181
250
  - **VS Code** diagnostics (inline explain)
182
251
  - **Cross-axis packs** (typography × weight × contrast)
183
- - **Receipts & provenance** (hashes, signable reports)
252
+ - **Signed / attestable receipts** — `dcv validate --receipt` already emits environment + input content hashes today; cryptographic **signing** is the roadmap part
184
253
  - UI graph explorer (node inspector, violations focus)
185
254
 
186
255
  ---
@@ -0,0 +1,44 @@
1
+ /**
2
+ * DecisionThemes Adapter (Placeholder)
3
+ *
4
+ * This adapter will integrate with the DecisionThemes framework (coming 2026).
5
+ * It transforms VT (Value Themes) + DT (Decision Themes) into flat tokens for DCV validation.
6
+ *
7
+ * @see https://www.decisionthemes.com
8
+ * @see docs/Adapters.md for implementation details
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { decisionthemesAdapter } from './adapters/decisionthemes.js';
13
+ *
14
+ * const { tokens, policy } = decisionthemesAdapter({
15
+ * vt: { ... }, // Value Themes (raw values)
16
+ * dt: { ... } // Decision Themes (formulas)
17
+ * });
18
+ *
19
+ * // tokens = flat token object for DCV validation
20
+ * // policy = auto-generated constraints from 5-axis model
21
+ * ```
22
+ */
23
+ export interface VT {
24
+ [key: string]: any;
25
+ }
26
+ export interface DT {
27
+ [key: string]: any;
28
+ }
29
+ export interface DecisionThemesInput {
30
+ vt: VT;
31
+ dt: DT;
32
+ }
33
+ export interface DecisionThemesOutput {
34
+ tokens: Record<string, any>;
35
+ policy?: string;
36
+ }
37
+ /**
38
+ * Transform DecisionThemes (VT+DT) into DCV-compatible tokens
39
+ *
40
+ * @param input - VT (values) + DT (decisions)
41
+ * @returns Flat tokens + optional policy JSON
42
+ */
43
+ export declare function decisionthemesAdapter(_input: DecisionThemesInput): DecisionThemesOutput;
44
+ //# sourceMappingURL=decisionthemes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decisionthemes.d.ts","sourceRoot":"","sources":["decisionthemes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,EAAE;IAEjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,EAAE;IAEjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,EAAE,CAAC;IACP,EAAE,EAAE,EAAE,CAAC;CACR;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,mBAAmB,GAAG,oBAAoB,CASvF"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * DecisionThemes Adapter (Placeholder)
3
+ *
4
+ * This adapter will integrate with the DecisionThemes framework (coming 2026).
5
+ * It transforms VT (Value Themes) + DT (Decision Themes) into flat tokens for DCV validation.
6
+ *
7
+ * @see https://www.decisionthemes.com
8
+ * @see docs/Adapters.md for implementation details
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { decisionthemesAdapter } from './adapters/decisionthemes.js';
13
+ *
14
+ * const { tokens, policy } = decisionthemesAdapter({
15
+ * vt: { ... }, // Value Themes (raw values)
16
+ * dt: { ... } // Decision Themes (formulas)
17
+ * });
18
+ *
19
+ * // tokens = flat token object for DCV validation
20
+ * // policy = auto-generated constraints from 5-axis model
21
+ * ```
22
+ */
23
+ /**
24
+ * Transform DecisionThemes (VT+DT) into DCV-compatible tokens
25
+ *
26
+ * @param input - VT (values) + DT (decisions)
27
+ * @returns Flat tokens + optional policy JSON
28
+ */
29
+ export function decisionthemesAdapter(_input) {
30
+ // TODO: Implement when DecisionThemes integration is ready
31
+ // This will call the DecisionThemes resolver/compute engine to process _input
32
+ throw new Error('DecisionThemes adapter not yet implemented. ' +
33
+ 'This is a placeholder for future integration with the DecisionThemes framework. ' +
34
+ 'See https://www.decisionthemes.com for updates.');
35
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * DecisionThemes Adapter (Placeholder)
3
+ *
4
+ * This adapter will integrate with the DecisionThemes framework (coming 2026).
5
+ * It transforms VT (Value Themes) + DT (Decision Themes) into flat tokens for DCV validation.
6
+ *
7
+ * @see https://www.decisionthemes.com
8
+ * @see docs/Adapters.md for implementation details
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { decisionthemesAdapter } from './adapters/decisionthemes.js';
13
+ *
14
+ * const { tokens, policy } = decisionthemesAdapter({
15
+ * vt: { ... }, // Value Themes (raw values)
16
+ * dt: { ... } // Decision Themes (formulas)
17
+ * });
18
+ *
19
+ * // tokens = flat token object for DCV validation
20
+ * // policy = auto-generated constraints from 5-axis model
21
+ * ```
22
+ */
23
+
24
+ export interface VT {
25
+ // Value Themes - raw design values
26
+ [key: string]: any;
27
+ }
28
+
29
+ export interface DT {
30
+ // Decision Themes - formulas and decision mappings
31
+ [key: string]: any;
32
+ }
33
+
34
+ export interface DecisionThemesInput {
35
+ vt: VT;
36
+ dt: DT;
37
+ }
38
+
39
+ export interface DecisionThemesOutput {
40
+ tokens: Record<string, any>;
41
+ policy?: string;
42
+ }
43
+
44
+ /**
45
+ * Transform DecisionThemes (VT+DT) into DCV-compatible tokens
46
+ *
47
+ * @param input - VT (values) + DT (decisions)
48
+ * @returns Flat tokens + optional policy JSON
49
+ */
50
+ export function decisionthemesAdapter(_input: DecisionThemesInput): DecisionThemesOutput {
51
+ // TODO: Implement when DecisionThemes integration is ready
52
+ // This will call the DecisionThemes resolver/compute engine to process _input
53
+
54
+ throw new Error(
55
+ 'DecisionThemes adapter not yet implemented. ' +
56
+ 'This is a placeholder for future integration with the DecisionThemes framework. ' +
57
+ 'See https://www.decisionthemes.com for updates.'
58
+ );
59
+ }
@@ -6,7 +6,7 @@ import { emitJSON } from '../../adapters/json.js';
6
6
  import { emitJS } from '../../adapters/js.js';
7
7
  export async function buildCommand(options) {
8
8
  const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
9
- const tokens = loadTokensWithBreakpoint();
9
+ const tokens = loadTokensWithBreakpoint(undefined, options.tokens);
10
10
  const { flat } = flattenTokens(tokens);
11
11
  let allValues = Object.fromEntries(Object.values(flat).map(t => [t.id, t.value]));
12
12
  if (options.theme) {
@@ -8,7 +8,7 @@ import type { BuildOptions } from '../types.js';
8
8
 
9
9
  export async function buildCommand(options: BuildOptions & { [k: string]: any }): Promise<void> {
10
10
  const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
11
- const tokens = loadTokensWithBreakpoint();
11
+ const tokens = loadTokensWithBreakpoint(undefined, options.tokens);
12
12
  const { flat } = flattenTokens(tokens);
13
13
  let allValues = Object.fromEntries(Object.values(flat).map(t => [t.id, (t as FlatToken).value]));
14
14
  if (options.theme) {
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
2
  import { flattenTokens } from '../../core/flatten.js';
3
3
  import { exportGraphImage } from '../../core/image-export.js';
4
- import { attachRuntimeConstraints } from '../constraints-loader.js';
4
+ import { setupConstraints } from '../constraint-registry.js';
5
5
  import { loadConfig } from '../config.js';
6
6
  // Local helper for non-poset dependency graphs
7
7
  function generateDependencyGraph(edges, format) {
@@ -100,7 +100,7 @@ export async function graphCommand(options) {
100
100
  let edgeLabels;
101
101
  if (onlyViolations || highlightViolations || labelViolations) {
102
102
  const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
103
- const tokens = loadTokensWithBreakpoint(breakpoint);
103
+ const tokens = loadTokensWithBreakpoint(breakpoint, options.tokens);
104
104
  const { flattenTokens } = await import('../../core/flatten.js');
105
105
  const { Engine } = await import('../../core/engine.js');
106
106
  const { MonotonicPlugin, parseSize } = await import('../../core/constraints/monotonic.js');
@@ -127,7 +127,7 @@ export async function graphCommand(options) {
127
127
  if (cfgRes.ok) {
128
128
  const config = cfgRes.value;
129
129
  const knownIds = new Set(Object.keys(flat));
130
- attachRuntimeConstraints(engine, { config, knownIds, bp: breakpoint });
130
+ setupConstraints(engine, { config, bp: breakpoint }, { knownIds });
131
131
  const runtimeIssues = engine.evaluate(allIdsInHasse);
132
132
  issues.push(...runtimeIssues);
133
133
  }
@@ -211,7 +211,7 @@ export async function graphCommand(options) {
211
211
  }
212
212
  for (const breakpoint of plan) {
213
213
  const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
214
- const tokens = loadTokensWithBreakpoint(breakpoint);
214
+ const tokens = loadTokensWithBreakpoint(breakpoint, options.tokens);
215
215
  const { edges } = flattenTokens(tokens);
216
216
  let filteredEdges = edges;
217
217
  if (options.filter) {
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
2
  import type { GraphOptions } from '../types.js';
3
3
  import { flattenTokens, type FlatToken } from '../../core/flatten.js';
4
4
  import { exportGraphImage } from '../../core/image-export.js';
5
- import { attachRuntimeConstraints } from '../constraints-loader.js';
5
+ import { setupConstraints } from '../constraint-registry.js';
6
6
  import { loadConfig } from '../config.js';
7
7
 
8
8
  // Local helper for non-poset dependency graphs
@@ -69,7 +69,7 @@ export async function graphCommand(options: GraphOptions): Promise<void> {
69
69
  let highlight: { nodes: Set<string>; edges: Set<string>; color?: string } | undefined; let edgeLabels: Map<string,string> | undefined;
70
70
  if (onlyViolations || highlightViolations || labelViolations) {
71
71
  const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
72
- const tokens = loadTokensWithBreakpoint(breakpoint);
72
+ const tokens = loadTokensWithBreakpoint(breakpoint, options.tokens);
73
73
  const { flattenTokens } = await import('../../core/flatten.js');
74
74
  const { Engine } = await import('../../core/engine.js');
75
75
  const { MonotonicPlugin, parseSize } = await import('../../core/constraints/monotonic.js');
@@ -98,7 +98,7 @@ export async function graphCommand(options: GraphOptions): Promise<void> {
98
98
  if (cfgRes.ok) {
99
99
  const config = cfgRes.value;
100
100
  const knownIds = new Set(Object.keys(flat as Record<string, FlatToken>));
101
- attachRuntimeConstraints(engine, { config, knownIds, bp: breakpoint });
101
+ setupConstraints(engine, { config, bp: breakpoint }, { knownIds });
102
102
  const runtimeIssues = engine.evaluate(allIdsInHasse);
103
103
  issues.push(...runtimeIssues);
104
104
  }
@@ -170,7 +170,7 @@ export async function graphCommand(options: GraphOptions): Promise<void> {
170
170
  return; }
171
171
  for (const breakpoint of plan) {
172
172
  const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
173
- const tokens = loadTokensWithBreakpoint(breakpoint); const { edges } = flattenTokens(tokens);
173
+ const tokens = loadTokensWithBreakpoint(breakpoint, options.tokens); const { edges } = flattenTokens(tokens);
174
174
  let filteredEdges = edges;
175
175
  if (options.filter) { const filterRegex = new RegExp(options.filter); filteredEdges = edges.filter(([from,to]) => filterRegex.test(from) || filterRegex.test(to)); }
176
176
  const format = options.format || 'json'; const graph = generateDependencyGraph(filteredEdges, format);
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["validate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAMnD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAoK9E"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["validate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAOnD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAoM9E"}
@@ -5,8 +5,11 @@ import { parseBreakpoints, loadTokensWithBreakpoint, mergeTokens } from '../../c
5
5
  import { createValidationResult, createValidationReceipt, writeJsonOutput } from '../json-output.js';
6
6
  import { readFileSync, existsSync } from 'node:fs';
7
7
  import { join } from 'node:path';
8
- import { setupConstraints } from '../constraint-registry.js';
8
+ import { setupConstraints, collectReferencedIds } from '../constraint-registry.js';
9
+ import { printVersionBanner } from '../version-banner.js';
9
10
  export async function validateCommand(_options) {
11
+ // Show version banner (subtle, dimmed)
12
+ printVersionBanner({ quiet: _options.format === 'json' });
10
13
  try {
11
14
  const bps = parseBreakpoints(process.argv);
12
15
  const crossAxisDebug = process.argv.includes('--cross-axis-debug');
@@ -14,6 +17,20 @@ export async function validateCommand(_options) {
14
17
  let anyErrors = false;
15
18
  let totalErrors = 0;
16
19
  let totalWarnings = 0;
20
+ // Coverage tracking for the no-match note (never silently "pass" a file whose
21
+ // tokens are referenced by no active constraint).
22
+ let anyConstraintMatched = false;
23
+ let coverageKnownAll = true;
24
+ let tokenCount = 0;
25
+ // Reconcile the tokens path: the positional `[tokens-path]` is an alias for
26
+ // --tokens. The flag wins; warn on a genuine mismatch. undefined => loader default.
27
+ const flagTokens = _options.tokens;
28
+ const posTokens = _options['tokens-path'];
29
+ if (flagTokens && posTokens && flagTokens !== posTokens) {
30
+ console.error(`warning: both --tokens (${flagTokens}) and positional tokens path (${posTokens}) provided; using --tokens`);
31
+ }
32
+ const tokensPath = flagTokens ?? posTokens;
33
+ const constraintsDir = _options['constraints-dir'] ?? 'themes';
17
34
  const argv = process.argv.slice(2);
18
35
  const failOnIdx = argv.indexOf('--fail-on');
19
36
  const failOn = _options.failOn ?? (failOnIdx >= 0 ? argv[failOnIdx + 1] : 'error');
@@ -52,7 +69,7 @@ export async function validateCommand(_options) {
52
69
  const tStartTotal = globalThis.performance.now();
53
70
  for (const bp of plan) {
54
71
  const tStart = globalThis.performance.now();
55
- let tokens = loadTokensWithBreakpoint(bp);
72
+ let tokens = loadTokensWithBreakpoint(bp, tokensPath);
56
73
  // Optional theme overlay (tokens/themes/<name>.json), mirroring build behavior
57
74
  if (_options.theme) {
58
75
  const themePath = join('tokens/themes', `${_options.theme}.json`);
@@ -75,7 +92,20 @@ export async function validateCommand(_options) {
75
92
  const engine = new Engine(init, edges);
76
93
  const knownIds = new Set(Object.keys(init));
77
94
  // Discover and attach all constraints via centralized registry
78
- setupConstraints(engine, { config, bp, constraintsDir: 'themes' }, { knownIds, crossAxisDebug });
95
+ const sources = setupConstraints(engine, { config, bp, constraintsDir }, { knownIds, crossAxisDebug });
96
+ // Track whether any active constraint actually references a token in this file.
97
+ const coverage = collectReferencedIds(sources);
98
+ if (!coverage.coverageKnown)
99
+ coverageKnownAll = false;
100
+ tokenCount = Math.max(tokenCount, knownIds.size);
101
+ if (!anyConstraintMatched) {
102
+ for (const id of coverage.ids) {
103
+ if (knownIds.has(id)) {
104
+ anyConstraintMatched = true;
105
+ break;
106
+ }
107
+ }
108
+ }
79
109
  const allIds = new Set(Object.keys(init));
80
110
  const issues = engine.evaluate(allIds);
81
111
  const errs = issues.filter((i) => i.level === 'error');
@@ -101,6 +131,14 @@ export async function validateCommand(_options) {
101
131
  }
102
132
  }
103
133
  const totalMs = globalThis.performance.now() - tStartTotal;
134
+ // No-match note: tokens were validated but no active constraint referenced any
135
+ // of them. Surfaced loudly (stderr + JSON `note`) so a foreign file can never
136
+ // silently report "0 errors" when nothing was actually checked.
137
+ const noMatchNote = (tokenCount > 0 && coverageKnownAll && !anyConstraintMatched)
138
+ ? `No active constraint references any of the ${tokenCount} validated token(s) — nothing was checked. Define constraints in dcv.config.json (constraints.wcag / constraints.thresholds) or point --constraints-dir at your order/cross-axis files.`
139
+ : undefined;
140
+ if (noMatchNote)
141
+ console.error(`note: ${noMatchNote}`);
104
142
  // Append aggregate total row if multiple scopes and not already added
105
143
  if (rows.length > 1) {
106
144
  const agg = rows.reduce((a, b) => ({ rules: a.rules + b.rules, warnings: a.warnings + b.warnings, errors: a.errors + b.errors }), { rules: 0, warnings: 0, errors: 0 });
@@ -119,11 +157,10 @@ export async function validateCommand(_options) {
119
157
  }
120
158
  // Handle JSON output mode
121
159
  if (outputFormat === 'json') {
122
- const result = createValidationResult(allErrors, allWarnings, totalMs, engineVersion);
160
+ const result = createValidationResult(allErrors, allWarnings, totalMs, engineVersion, noMatchNote);
123
161
  // If receipt requested, generate full receipt
124
162
  if (_options.receipt) {
125
- const tokensFile = _options.tokens ?? 'tokens/tokens.example.json';
126
- const constraintsDir = 'themes';
163
+ const tokensFile = tokensPath ?? 'tokens/tokens.example.json';
127
164
  const receipt = createValidationReceipt(result, tokensFile, constraintsDir, bps[0], failOn);
128
165
  writeJsonOutput(receipt, _options.receipt);
129
166
  }
@@ -7,14 +7,31 @@ import type { ValidateOptions } from '../types.js';
7
7
  import { createValidationResult, createValidationReceipt, writeJsonOutput } from '../json-output.js';
8
8
  import { readFileSync, existsSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
- import { setupConstraints } from '../constraint-registry.js';
10
+ import { setupConstraints, collectReferencedIds } from '../constraint-registry.js';
11
+ import { printVersionBanner } from '../version-banner.js';
11
12
 
12
13
  export async function validateCommand(_options: ValidateOptions): Promise<void> {
14
+ // Show version banner (subtle, dimmed)
15
+ printVersionBanner({ quiet: _options.format === 'json' });
16
+
13
17
  try {
14
18
  const bps = parseBreakpoints(process.argv);
15
19
  const crossAxisDebug = process.argv.includes('--cross-axis-debug');
16
20
  const plan: (Breakpoint | undefined)[] = bps.length ? bps : [undefined];
17
21
  let anyErrors = false; let totalErrors = 0; let totalWarnings = 0;
22
+ // Coverage tracking for the no-match note (never silently "pass" a file whose
23
+ // tokens are referenced by no active constraint).
24
+ let anyConstraintMatched = false; let coverageKnownAll = true; let tokenCount = 0;
25
+
26
+ // Reconcile the tokens path: the positional `[tokens-path]` is an alias for
27
+ // --tokens. The flag wins; warn on a genuine mismatch. undefined => loader default.
28
+ const flagTokens = _options.tokens;
29
+ const posTokens = _options['tokens-path'];
30
+ if (flagTokens && posTokens && flagTokens !== posTokens) {
31
+ console.error(`warning: both --tokens (${flagTokens}) and positional tokens path (${posTokens}) provided; using --tokens`);
32
+ }
33
+ const tokensPath = flagTokens ?? posTokens;
34
+ const constraintsDir = _options['constraints-dir'] ?? 'themes';
18
35
  const argv = process.argv.slice(2);
19
36
  const failOnIdx = argv.indexOf('--fail-on');
20
37
  type FailOn = 'off' | 'warn' | 'error';
@@ -52,7 +69,7 @@ export async function validateCommand(_options: ValidateOptions): Promise<void>
52
69
  const tStartTotal = globalThis.performance.now();
53
70
  for (const bp of plan) {
54
71
  const tStart = globalThis.performance.now();
55
- let tokens: TokenNode = loadTokensWithBreakpoint(bp);
72
+ let tokens: TokenNode = loadTokensWithBreakpoint(bp, tokensPath);
56
73
  // Optional theme overlay (tokens/themes/<name>.json), mirroring build behavior
57
74
  if (_options.theme) {
58
75
  const themePath = join('tokens/themes', `${_options.theme}.json`);
@@ -75,12 +92,22 @@ export async function validateCommand(_options: ValidateOptions): Promise<void>
75
92
  const knownIds = new Set(Object.keys(init));
76
93
 
77
94
  // Discover and attach all constraints via centralized registry
78
- setupConstraints(
95
+ const sources = setupConstraints(
79
96
  engine,
80
- { config, bp, constraintsDir: 'themes' },
97
+ { config, bp, constraintsDir },
81
98
  { knownIds, crossAxisDebug },
82
99
  );
83
100
 
101
+ // Track whether any active constraint actually references a token in this file.
102
+ const coverage = collectReferencedIds(sources);
103
+ if (!coverage.coverageKnown) coverageKnownAll = false;
104
+ tokenCount = Math.max(tokenCount, knownIds.size);
105
+ if (!anyConstraintMatched) {
106
+ for (const id of coverage.ids) {
107
+ if (knownIds.has(id)) { anyConstraintMatched = true; break; }
108
+ }
109
+ }
110
+
84
111
  const allIds = new Set(Object.keys(init));
85
112
  const issues = engine.evaluate(allIds);
86
113
  const errs = issues.filter((i: ConstraintIssue) => i.level === 'error');
@@ -103,6 +130,13 @@ export async function validateCommand(_options: ValidateOptions): Promise<void>
103
130
  }
104
131
  }
105
132
  const totalMs = globalThis.performance.now() - tStartTotal;
133
+ // No-match note: tokens were validated but no active constraint referenced any
134
+ // of them. Surfaced loudly (stderr + JSON `note`) so a foreign file can never
135
+ // silently report "0 errors" when nothing was actually checked.
136
+ const noMatchNote = (tokenCount > 0 && coverageKnownAll && !anyConstraintMatched)
137
+ ? `No active constraint references any of the ${tokenCount} validated token(s) — nothing was checked. Define constraints in dcv.config.json (constraints.wcag / constraints.thresholds) or point --constraints-dir at your order/cross-axis files.`
138
+ : undefined;
139
+ if (noMatchNote) console.error(`note: ${noMatchNote}`);
106
140
  // Append aggregate total row if multiple scopes and not already added
107
141
  if (rows.length > 1) {
108
142
  const agg = rows.reduce((a,b)=>({ rules:a.rules+b.rules, warnings:a.warnings+b.warnings, errors:a.errors+b.errors }), { rules:0,warnings:0,errors:0 });
@@ -121,12 +155,11 @@ export async function validateCommand(_options: ValidateOptions): Promise<void>
121
155
 
122
156
  // Handle JSON output mode
123
157
  if (outputFormat === 'json') {
124
- const result = createValidationResult(allErrors, allWarnings, totalMs, engineVersion);
125
-
158
+ const result = createValidationResult(allErrors, allWarnings, totalMs, engineVersion, noMatchNote);
159
+
126
160
  // If receipt requested, generate full receipt
127
161
  if (_options.receipt) {
128
- const tokensFile = _options.tokens ?? 'tokens/tokens.example.json';
129
- const constraintsDir = 'themes';
162
+ const tokensFile = tokensPath ?? 'tokens/tokens.example.json';
130
163
  const receipt = createValidationReceipt(result, tokensFile, constraintsDir, bps[0], failOn);
131
164
  writeJsonOutput(receipt, _options.receipt);
132
165
  } else {