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/cli/dcv.js CHANGED
@@ -2,30 +2,12 @@
2
2
  // Clean minimal DCV CLI entrypoint
3
3
  import yargs from 'yargs/yargs';
4
4
  import { hideBin } from 'yargs/helpers';
5
- import { spawnSync } from 'node:child_process';
6
- import { createRequire } from 'module';
7
- import path from 'node:path';
8
5
  import { setCommand, buildCommand, validateCommand, graphCommand, whyCommand, patchCommand, patchApplyCommand } from './commands/index.js';
9
- // Early intercept: experimental graph diff helper
10
- (() => {
11
- const args = process.argv.slice(2);
12
- if (args[0] === 'graph' && args[1] === 'diff') {
13
- const pass = args.slice(2);
14
- const require = createRequire(import.meta.url);
15
- const tsx = (() => { try {
16
- return require.resolve('tsx/dist/cli.mjs');
17
- }
18
- catch {
19
- return path.resolve('node_modules/tsx/dist/cli.mjs');
20
- } })();
21
- const r = spawnSync(process.execPath, [tsx, path.resolve('scripts/graph-diff.ts'), ...pass], { stdio: 'inherit', env: process.env });
22
- process.exit(r.status ?? 0);
23
- }
24
- })();
25
6
  const cli = yargs(hideBin(process.argv))
26
7
  .scriptName('dcv')
27
8
  .parserConfiguration({ 'camel-case-expansion': false })
28
- .option('quiet', { type: 'boolean' });
9
+ .option('quiet', { type: 'boolean' })
10
+ .option('config', { type: 'string', describe: 'Path to JSON config file' });
29
11
  cli.command('set <expressions..>', 'Set token values', y => y
30
12
  .positional('expressions', { type: 'string', array: true })
31
13
  .option('dry-run', { type: 'boolean', default: false })
@@ -43,14 +25,16 @@ cli.command('build', 'Build token outputs', y => y
43
25
  .option('mapper', { type: 'string' })
44
26
  .option('theme', { type: 'string' })
45
27
  .option('dry-run', { type: 'boolean', default: false })
46
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }), a => buildCommand(a));
47
- cli.command('validate', 'Validate constraints', y => y
28
+ .option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' }), a => buildCommand(a));
29
+ cli.command('validate [tokens-path]', 'Validate constraints', y => y
30
+ .positional('tokens-path', { type: 'string', describe: 'Path to a tokens file (positional alias for --tokens)' })
31
+ .option('constraints-dir', { type: 'string', describe: 'Directory holding order / cross-axis constraint files (default: themes)' })
48
32
  .option('fail-on', { type: 'string', choices: ['off', 'warn', 'error'], default: 'error' })
49
33
  .option('summary', { type: 'string', choices: ['none', 'table', 'json'], default: 'none' })
50
34
  .option('format', { type: 'string', choices: ['text', 'json'], default: 'text', describe: 'Output format' })
51
35
  .option('output', { type: 'string', describe: 'Write JSON output to file' })
52
36
  .option('receipt', { type: 'string', describe: 'Generate validation receipt with audit trail' })
53
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
37
+ .option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' })
54
38
  .option('theme', { type: 'string', describe: 'Apply named theme tokens before validation' })
55
39
  .option('breakpoint', { type: 'string' })
56
40
  .option('all-breakpoints', { type: 'boolean' })
@@ -70,7 +54,7 @@ cli.command('graph', 'Generate dependency / constraint graph', y => y
70
54
  .option('min-severity', { type: 'string', choices: ['warn', 'error'], default: 'warn' })
71
55
  .option('focus', { type: 'string' })
72
56
  .option('radius', { type: 'number', default: 1 })
73
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }), a => graphCommand(a));
57
+ .option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' }), a => graphCommand(a));
74
58
  cli.command('why <tokenId>', 'Explain token provenance', y => y
75
59
  .positional('tokenId', { type: 'string', demandOption: true })
76
60
  .option('format', { type: 'string', choices: ['json', 'table'], default: 'json' })
package/cli/dcv.ts CHANGED
@@ -2,28 +2,14 @@
2
2
  // Clean minimal DCV CLI entrypoint
3
3
  import yargs from 'yargs/yargs';
4
4
  import { hideBin } from 'yargs/helpers';
5
- import { spawnSync } from 'node:child_process';
6
- import { createRequire } from 'module';
7
- import path from 'node:path';
8
5
  import { type SetOptions, type BuildOptions, type ValidateOptions, type GraphOptions, type WhyOptions, type PatchOptions, type PatchApplyOptions } from './types.js';
9
6
  import { setCommand, buildCommand, validateCommand, graphCommand, whyCommand, patchCommand, patchApplyCommand } from './commands/index.js';
10
7
 
11
- // Early intercept: experimental graph diff helper
12
- (() => {
13
- const args = process.argv.slice(2);
14
- if (args[0] === 'graph' && args[1] === 'diff') {
15
- const pass = args.slice(2);
16
- const require = createRequire(import.meta.url);
17
- const tsx = (() => { try { return require.resolve('tsx/dist/cli.mjs'); } catch { return path.resolve('node_modules/tsx/dist/cli.mjs'); }})();
18
- const r = spawnSync(process.execPath, [tsx, path.resolve('scripts/graph-diff.ts'), ...pass], { stdio: 'inherit', env: process.env });
19
- process.exit(r.status ?? 0);
20
- }
21
- })();
22
-
23
8
  const cli = yargs(hideBin(process.argv))
24
9
  .scriptName('dcv')
25
10
  .parserConfiguration({ 'camel-case-expansion': false })
26
- .option('quiet', { type: 'boolean' });
11
+ .option('quiet', { type: 'boolean' })
12
+ .option('config', { type: 'string', describe: 'Path to JSON config file' });
27
13
 
28
14
  cli.command<SetOptions>('set <expressions..>', 'Set token values', y => y
29
15
  .positional('expressions', { type: 'string', array: true })
@@ -45,17 +31,19 @@ cli.command<BuildOptions>('build', 'Build token outputs', y => y
45
31
  .option('mapper', { type: 'string' })
46
32
  .option('theme', { type: 'string' })
47
33
  .option('dry-run', { type: 'boolean', default: false })
48
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
34
+ .option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' }),
49
35
  a => buildCommand(a)
50
36
  );
51
37
 
52
- cli.command<ValidateOptions>('validate', 'Validate constraints', y => y
38
+ cli.command<ValidateOptions>('validate [tokens-path]', 'Validate constraints', y => y
39
+ .positional('tokens-path', { type: 'string', describe: 'Path to a tokens file (positional alias for --tokens)' })
40
+ .option('constraints-dir', { type: 'string', describe: 'Directory holding order / cross-axis constraint files (default: themes)' })
53
41
  .option('fail-on', { type: 'string', choices: ['off','warn','error'], default: 'error' })
54
42
  .option('summary', { type: 'string', choices: ['none','table','json'], default: 'none' })
55
43
  .option('format', { type: 'string', choices: ['text','json'], default: 'text', describe: 'Output format' })
56
44
  .option('output', { type: 'string', describe: 'Write JSON output to file' })
57
45
  .option('receipt', { type: 'string', describe: 'Generate validation receipt with audit trail' })
58
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
46
+ .option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' })
59
47
  .option('theme', { type: 'string', describe: 'Apply named theme tokens before validation' })
60
48
  .option('breakpoint', { type: 'string' })
61
49
  .option('all-breakpoints', { type: 'boolean' })
@@ -78,7 +66,7 @@ cli.command<GraphOptions>('graph', 'Generate dependency / constraint graph', y =
78
66
  .option('min-severity', { type: 'string', choices: ['warn','error'], default: 'warn' })
79
67
  .option('focus', { type: 'string' })
80
68
  .option('radius', { type: 'number', default: 1 })
81
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
69
+ .option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' }),
82
70
  a => graphCommand(a)
83
71
  );
84
72
 
@@ -21,11 +21,18 @@ export interface ValidationResult {
21
21
  };
22
22
  violations: ConstraintViolation[];
23
23
  warnings?: ConstraintViolation[];
24
+ /** Set when tokens were validated but no active constraint referenced any of them. */
25
+ note?: string;
24
26
  stats: {
25
27
  durationMs: number;
26
28
  engineVersion: string;
27
29
  timestamp: string;
28
30
  };
31
+ dcv: {
32
+ name: string;
33
+ version: string;
34
+ repository: string;
35
+ };
29
36
  }
30
37
  export interface ValidationReceipt extends ValidationResult {
31
38
  environment: {
@@ -52,7 +59,7 @@ export declare function formatViolation(issue: ConstraintIssue): ConstraintViola
52
59
  /**
53
60
  * Generate a ValidationResult from collected issues
54
61
  */
55
- export declare function createValidationResult(errors: ConstraintIssue[], warnings: ConstraintIssue[], durationMs: number, engineVersion: string): ValidationResult;
62
+ export declare function createValidationResult(errors: ConstraintIssue[], warnings: ConstraintIssue[], durationMs: number, engineVersion: string, note?: string): ValidationResult;
56
63
  /**
57
64
  * Generate a ValidationReceipt with full audit trail
58
65
  */
@@ -1 +1 @@
1
- {"version":3,"file":"json-output.d.ts","sourceRoot":"","sources":["json-output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKzD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,WAAW,EAAE;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,MAAM,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACzC,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,MAAM,EAAE;QACN,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;QACjC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,mBAAmB,CAiB3E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,eAAe,EAAE,EACzB,QAAQ,EAAE,eAAe,EAAE,EAC3B,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,GACpB,gBAAgB,CAmBlB;AAcD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAC/B,iBAAiB,CAoCnB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CASxE"}
1
+ {"version":3,"file":"json-output.d.ts","sourceRoot":"","sources":["json-output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAMzD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,GAAG,EAAE;QACH,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,WAAW,EAAE;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,MAAM,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACzC,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,MAAM,EAAE;QACN,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;QACjC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,mBAAmB,CAsB3E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,eAAe,EAAE,EACzB,QAAQ,EAAE,eAAe,EAAE,EAC3B,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,MAAM,GACZ,gBAAgB,CAqBlB;AAcD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAC/B,iBAAiB,CAoCnB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CASxE"}
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { createHash } from 'node:crypto';
4
+ import { getVersionInfo } from './version-banner.js';
4
5
  /**
5
6
  * Convert internal ConstraintIssue to standardized ConstraintViolation format
6
7
  */
@@ -10,19 +11,25 @@ export function formatViolation(issue) {
10
11
  level: issue.level === 'error' ? 'error' : 'warn',
11
12
  message: issue.message,
12
13
  };
13
- if (issue.id) {
14
+ if (issue.involvedTokens?.length) {
15
+ violation.nodes = issue.involvedTokens;
16
+ }
17
+ else if (issue.id) {
14
18
  violation.nodes = [issue.id];
15
19
  }
16
20
  // Add context from where field or other metadata
17
- if (issue.where) {
18
- violation.context = { where: issue.where };
21
+ if (issue.where || issue.metadata) {
22
+ violation.context = {
23
+ ...(issue.where ? { where: issue.where } : {}),
24
+ ...(issue.metadata ?? {}),
25
+ };
19
26
  }
20
27
  return violation;
21
28
  }
22
29
  /**
23
30
  * Generate a ValidationResult from collected issues
24
31
  */
25
- export function createValidationResult(errors, warnings, durationMs, engineVersion) {
32
+ export function createValidationResult(errors, warnings, durationMs, engineVersion, note) {
26
33
  const violations = errors.map(formatViolation);
27
34
  const warningViolations = warnings.map(formatViolation);
28
35
  return {
@@ -34,11 +41,13 @@ export function createValidationResult(errors, warnings, durationMs, engineVersi
34
41
  },
35
42
  violations,
36
43
  warnings: warningViolations.length > 0 ? warningViolations : undefined,
44
+ note,
37
45
  stats: {
38
46
  durationMs: Math.round(durationMs),
39
47
  engineVersion,
40
48
  timestamp: new Date().toISOString(),
41
49
  },
50
+ dcv: getVersionInfo(),
42
51
  };
43
52
  }
44
53
  /**
@@ -2,6 +2,7 @@ import type { ConstraintIssue } from '../core/engine.js';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
  import { createHash } from 'node:crypto';
5
+ import { getVersionInfo } from './version-banner.js';
5
6
 
6
7
  export interface ConstraintViolation {
7
8
  ruleId: string;
@@ -26,11 +27,18 @@ export interface ValidationResult {
26
27
  };
27
28
  violations: ConstraintViolation[];
28
29
  warnings?: ConstraintViolation[];
30
+ /** Set when tokens were validated but no active constraint referenced any of them. */
31
+ note?: string;
29
32
  stats: {
30
33
  durationMs: number;
31
34
  engineVersion: string;
32
35
  timestamp: string;
33
36
  };
37
+ dcv: {
38
+ name: string;
39
+ version: string;
40
+ repository: string;
41
+ };
34
42
  }
35
43
 
36
44
  export interface ValidationReceipt extends ValidationResult {
@@ -62,13 +70,18 @@ export function formatViolation(issue: ConstraintIssue): ConstraintViolation {
62
70
  message: issue.message,
63
71
  };
64
72
 
65
- if (issue.id) {
73
+ if (issue.involvedTokens?.length) {
74
+ violation.nodes = issue.involvedTokens;
75
+ } else if (issue.id) {
66
76
  violation.nodes = [issue.id];
67
77
  }
68
78
 
69
79
  // Add context from where field or other metadata
70
- if (issue.where) {
71
- violation.context = { where: issue.where };
80
+ if (issue.where || issue.metadata) {
81
+ violation.context = {
82
+ ...(issue.where ? { where: issue.where } : {}),
83
+ ...(issue.metadata ?? {}),
84
+ };
72
85
  }
73
86
 
74
87
  return violation;
@@ -81,7 +94,8 @@ export function createValidationResult(
81
94
  errors: ConstraintIssue[],
82
95
  warnings: ConstraintIssue[],
83
96
  durationMs: number,
84
- engineVersion: string
97
+ engineVersion: string,
98
+ note?: string
85
99
  ): ValidationResult {
86
100
  const violations = errors.map(formatViolation);
87
101
  const warningViolations = warnings.map(formatViolation);
@@ -95,11 +109,13 @@ export function createValidationResult(
95
109
  },
96
110
  violations,
97
111
  warnings: warningViolations.length > 0 ? warningViolations : undefined,
112
+ note,
98
113
  stats: {
99
114
  durationMs: Math.round(durationMs),
100
115
  engineVersion,
101
116
  timestamp: new Date().toISOString(),
102
117
  },
118
+ dcv: getVersionInfo(),
103
119
  };
104
120
  }
105
121
 
package/cli/types.d.ts CHANGED
@@ -45,6 +45,8 @@ export interface BuildOptions extends GlobalOptions {
45
45
  export interface ValidateOptions extends GlobalOptions {
46
46
  strict?: boolean;
47
47
  constraints?: string[];
48
+ 'tokens-path'?: string;
49
+ 'constraints-dir'?: string;
48
50
  perf?: boolean;
49
51
  budgetTotalMs?: number;
50
52
  budgetPerBpMs?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,WAAW,cAAc;IAAG,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE;AAChH,MAAM,MAAM,SAAS,GAAG,eAAe,CAAC;AACxC,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AACrD,MAAM,WAAW,aAAa;IAAG,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE;AAClE,MAAM,MAAM,aAAa,GAAG;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,aAAa,CAAA;CAAE,GAAG,aAAa,CAAC;AAE3F,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAChC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AACD,MAAM,WAAW,UAAW,SAAQ,aAAa;IAC/C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AACD,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AACD,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;CACrC;AACD,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;IACpD,SAAS,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AACD,MAAM,WAAW,UAAW,SAAQ,aAAa;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AACD,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AACD,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AACD,YAAY,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"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"}
package/cli/types.ts CHANGED
@@ -38,6 +38,8 @@ export interface BuildOptions extends GlobalOptions {
38
38
  export interface ValidateOptions extends GlobalOptions {
39
39
  strict?: boolean;
40
40
  constraints?: string[];
41
+ 'tokens-path'?: string;
42
+ 'constraints-dir'?: string;
41
43
  perf?: boolean;
42
44
  budgetTotalMs?: number;
43
45
  budgetPerBpMs?: number;
@@ -0,0 +1,40 @@
1
+ import { type TokenNode } from '../core/flatten.js';
2
+ import type { Breakpoint } from '../core/breakpoints.js';
3
+ import { type ConstraintViolation } from './json-output.js';
4
+ import type { DcvConfig } from './types.js';
5
+ export interface ValidateInput {
6
+ /** Inline tokens object (DTCG-style `$value`/`$type`). Takes precedence over `tokensPath`. */
7
+ tokens?: TokenNode;
8
+ /** Path to a tokens file. Used when `tokens` is not provided. */
9
+ tokensPath?: string;
10
+ /** Inline constraints config (the `constraints` block of dcv.config.json). Takes precedence over `configPath`. */
11
+ constraints?: DcvConfig['constraints'];
12
+ /** Path to a config file. When neither `constraints` nor `configPath` is given, a dcv.config.* in cwd is discovered. */
13
+ configPath?: string;
14
+ /** Directory holding order / cross-axis constraint files. Defaults to `themes`. */
15
+ constraintsDir?: string;
16
+ /** Optional breakpoint, selecting `<axis>.<bp>.order.json` / cross-axis variants. */
17
+ breakpoint?: Breakpoint;
18
+ }
19
+ export interface ValidateResult {
20
+ /** True when there are no error-level violations. */
21
+ ok: boolean;
22
+ counts: {
23
+ checked: number;
24
+ violations: number;
25
+ warnings: number;
26
+ };
27
+ violations: ConstraintViolation[];
28
+ warnings: ConstraintViolation[];
29
+ /** Set when tokens were validated but no active constraint referenced any of them. */
30
+ note?: string;
31
+ }
32
+ /**
33
+ * Validate a token set against constraints and return structured results.
34
+ *
35
+ * Unlike the CLI, this treats `tokens` / `tokensPath` as the complete token set
36
+ * (no implicit cwd token overrides), so it is safe to call from a server that
37
+ * cannot share a filesystem with the caller.
38
+ */
39
+ export declare function validate(input?: ValidateInput): ValidateResult;
40
+ //# sourceMappingURL=validate-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-api.d.ts","sourceRoot":"","sources":["validate-api.ts"],"names":[],"mappings":"AAYA,OAAO,EAAiB,KAAK,SAAS,EAAkB,MAAM,oBAAoB,CAAC;AAEnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGzD,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,8FAA8F;IAC9F,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kHAAkH;IAClH,WAAW,CAAC,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;IACvC,wHAAwH;IACxH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qFAAqF;IACrF,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAyBD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,GAAE,aAAkB,GAAG,cAAc,CAkDlE"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Programmatic validation API.
3
+ *
4
+ * A thin convenience wrapper over the same flatten + engine + constraint-registry
5
+ * machinery the CLI uses, so library consumers (and the MCP server) get one call
6
+ * that takes tokens + constraints and returns structured violations — no argv, no
7
+ * process.exit, no prose.
8
+ *
9
+ * Re-exported from the package root (`design-constraint-validator`) via
10
+ * `core/index.ts`.
11
+ */
12
+ import fs from 'node:fs';
13
+ import { flattenTokens } from '../core/flatten.js';
14
+ import { Engine } from '../core/engine.js';
15
+ import { loadConfig } from './config.js';
16
+ import { setupConstraints, collectReferencedIds } from './constraint-registry.js';
17
+ import { formatViolation } from './json-output.js';
18
+ function readTokensFile(p) {
19
+ if (!fs.existsSync(p)) {
20
+ throw new Error(`Tokens file not found: ${p}`);
21
+ }
22
+ try {
23
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
24
+ }
25
+ catch (e) {
26
+ const detail = e instanceof Error ? e.message : String(e);
27
+ throw new Error(`Tokens file is not valid JSON: ${p} (${detail})`);
28
+ }
29
+ }
30
+ function resolveConfig(input) {
31
+ if (input.constraints !== undefined) {
32
+ return { constraints: input.constraints };
33
+ }
34
+ const res = loadConfig(input.configPath);
35
+ if (!res.ok) {
36
+ throw new Error(res.error);
37
+ }
38
+ return res.value;
39
+ }
40
+ /**
41
+ * Validate a token set against constraints and return structured results.
42
+ *
43
+ * Unlike the CLI, this treats `tokens` / `tokensPath` as the complete token set
44
+ * (no implicit cwd token overrides), so it is safe to call from a server that
45
+ * cannot share a filesystem with the caller.
46
+ */
47
+ export function validate(input = {}) {
48
+ const tokens = input.tokens !== undefined
49
+ ? input.tokens
50
+ : input.tokensPath !== undefined
51
+ ? readTokensFile(input.tokensPath)
52
+ : {};
53
+ const config = resolveConfig(input);
54
+ const { flat, edges } = flattenTokens(tokens);
55
+ const init = {};
56
+ for (const t of Object.values(flat)) {
57
+ init[t.id] = t.value;
58
+ }
59
+ const engine = new Engine(init, edges);
60
+ const knownIds = new Set(Object.keys(init));
61
+ const sources = setupConstraints(engine, { config, bp: input.breakpoint, constraintsDir: input.constraintsDir ?? 'themes' }, { knownIds });
62
+ const issues = engine.evaluate(knownIds);
63
+ const errors = issues.filter((i) => i.level === 'error');
64
+ const warnings = issues.filter((i) => i.level !== 'error');
65
+ // No-match note: validated tokens that no active constraint references.
66
+ const coverage = collectReferencedIds(sources);
67
+ let matched = false;
68
+ for (const id of coverage.ids) {
69
+ if (knownIds.has(id)) {
70
+ matched = true;
71
+ break;
72
+ }
73
+ }
74
+ const note = knownIds.size > 0 && coverage.coverageKnown && !matched
75
+ ? `No active constraint references any of the ${knownIds.size} validated token(s) — nothing was checked. ` +
76
+ `Define constraints (constraints.wcag / constraints.thresholds) or point constraintsDir at your order/cross-axis files.`
77
+ : undefined;
78
+ return {
79
+ ok: errors.length === 0,
80
+ counts: { checked: issues.length, violations: errors.length, warnings: warnings.length },
81
+ violations: errors.map(formatViolation),
82
+ warnings: warnings.map(formatViolation),
83
+ note,
84
+ };
85
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Programmatic validation API.
3
+ *
4
+ * A thin convenience wrapper over the same flatten + engine + constraint-registry
5
+ * machinery the CLI uses, so library consumers (and the MCP server) get one call
6
+ * that takes tokens + constraints and returns structured violations — no argv, no
7
+ * process.exit, no prose.
8
+ *
9
+ * Re-exported from the package root (`design-constraint-validator`) via
10
+ * `core/index.ts`.
11
+ */
12
+ import fs from 'node:fs';
13
+ import { flattenTokens, type TokenNode, type FlatToken } from '../core/flatten.js';
14
+ import { Engine, type ConstraintIssue } from '../core/engine.js';
15
+ import type { Breakpoint } from '../core/breakpoints.js';
16
+ import { loadConfig } from './config.js';
17
+ import { setupConstraints, collectReferencedIds } from './constraint-registry.js';
18
+ import { formatViolation, type ConstraintViolation } from './json-output.js';
19
+ import type { DcvConfig } from './types.js';
20
+
21
+ export interface ValidateInput {
22
+ /** Inline tokens object (DTCG-style `$value`/`$type`). Takes precedence over `tokensPath`. */
23
+ tokens?: TokenNode;
24
+ /** Path to a tokens file. Used when `tokens` is not provided. */
25
+ tokensPath?: string;
26
+ /** Inline constraints config (the `constraints` block of dcv.config.json). Takes precedence over `configPath`. */
27
+ constraints?: DcvConfig['constraints'];
28
+ /** Path to a config file. When neither `constraints` nor `configPath` is given, a dcv.config.* in cwd is discovered. */
29
+ configPath?: string;
30
+ /** Directory holding order / cross-axis constraint files. Defaults to `themes`. */
31
+ constraintsDir?: string;
32
+ /** Optional breakpoint, selecting `<axis>.<bp>.order.json` / cross-axis variants. */
33
+ breakpoint?: Breakpoint;
34
+ }
35
+
36
+ export interface ValidateResult {
37
+ /** True when there are no error-level violations. */
38
+ ok: boolean;
39
+ counts: { checked: number; violations: number; warnings: number };
40
+ violations: ConstraintViolation[];
41
+ warnings: ConstraintViolation[];
42
+ /** Set when tokens were validated but no active constraint referenced any of them. */
43
+ note?: string;
44
+ }
45
+
46
+ function readTokensFile(p: string): TokenNode {
47
+ if (!fs.existsSync(p)) {
48
+ throw new Error(`Tokens file not found: ${p}`);
49
+ }
50
+ try {
51
+ return JSON.parse(fs.readFileSync(p, 'utf8')) as TokenNode;
52
+ } catch (e) {
53
+ const detail = e instanceof Error ? e.message : String(e);
54
+ throw new Error(`Tokens file is not valid JSON: ${p} (${detail})`);
55
+ }
56
+ }
57
+
58
+ function resolveConfig(input: ValidateInput): DcvConfig {
59
+ if (input.constraints !== undefined) {
60
+ return { constraints: input.constraints };
61
+ }
62
+ const res = loadConfig(input.configPath);
63
+ if (!res.ok) {
64
+ throw new Error(res.error);
65
+ }
66
+ return res.value;
67
+ }
68
+
69
+ /**
70
+ * Validate a token set against constraints and return structured results.
71
+ *
72
+ * Unlike the CLI, this treats `tokens` / `tokensPath` as the complete token set
73
+ * (no implicit cwd token overrides), so it is safe to call from a server that
74
+ * cannot share a filesystem with the caller.
75
+ */
76
+ export function validate(input: ValidateInput = {}): ValidateResult {
77
+ const tokens: TokenNode =
78
+ input.tokens !== undefined
79
+ ? input.tokens
80
+ : input.tokensPath !== undefined
81
+ ? readTokensFile(input.tokensPath)
82
+ : {};
83
+
84
+ const config = resolveConfig(input);
85
+
86
+ const { flat, edges } = flattenTokens(tokens);
87
+ const init: Record<string, string | number> = {};
88
+ for (const t of Object.values(flat)) {
89
+ init[(t as FlatToken).id] = (t as FlatToken).value;
90
+ }
91
+ const engine = new Engine(init, edges);
92
+ const knownIds = new Set(Object.keys(init));
93
+
94
+ const sources = setupConstraints(
95
+ engine,
96
+ { config, bp: input.breakpoint, constraintsDir: input.constraintsDir ?? 'themes' },
97
+ { knownIds },
98
+ );
99
+
100
+ const issues = engine.evaluate(knownIds);
101
+ const errors = issues.filter((i: ConstraintIssue) => i.level === 'error');
102
+ const warnings = issues.filter((i: ConstraintIssue) => i.level !== 'error');
103
+
104
+ // No-match note: validated tokens that no active constraint references.
105
+ const coverage = collectReferencedIds(sources);
106
+ let matched = false;
107
+ for (const id of coverage.ids) {
108
+ if (knownIds.has(id)) {
109
+ matched = true;
110
+ break;
111
+ }
112
+ }
113
+ const note =
114
+ knownIds.size > 0 && coverage.coverageKnown && !matched
115
+ ? `No active constraint references any of the ${knownIds.size} validated token(s) — nothing was checked. ` +
116
+ `Define constraints (constraints.wcag / constraints.thresholds) or point constraintsDir at your order/cross-axis files.`
117
+ : undefined;
118
+
119
+ return {
120
+ ok: errors.length === 0,
121
+ counts: { checked: issues.length, violations: errors.length, warnings: warnings.length },
122
+ violations: errors.map(formatViolation),
123
+ warnings: warnings.map(formatViolation),
124
+ note,
125
+ };
126
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Version banner utility
3
+ * Displays DCV version info when validation runs
4
+ */
5
+ /**
6
+ * Print DCV version banner
7
+ * Shows: DCV v{version} | {repo_url}
8
+ */
9
+ export declare function printVersionBanner(options?: {
10
+ quiet?: boolean;
11
+ }): void;
12
+ /**
13
+ * Get version string for JSON output
14
+ */
15
+ export declare function getVersionInfo(): {
16
+ name: string;
17
+ version: string;
18
+ repository: string;
19
+ };
20
+ //# sourceMappingURL=version-banner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-banner.d.ts","sourceRoot":"","sources":["version-banner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkCH;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAOtE;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAOtF"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Version banner utility
3
+ * Displays DCV version info when validation runs
4
+ */
5
+ import { readFileSync } from 'node:fs';
6
+ import { join, dirname } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ let cachedPkg = null;
10
+ function getPackageInfo() {
11
+ if (cachedPkg)
12
+ return cachedPkg;
13
+ try {
14
+ const pkgPath = join(__dirname, '../package.json');
15
+ const pkgContent = readFileSync(pkgPath, 'utf-8');
16
+ cachedPkg = JSON.parse(pkgContent);
17
+ return cachedPkg;
18
+ }
19
+ catch {
20
+ // Fallback if package.json not found
21
+ return {
22
+ name: 'design-constraint-validator',
23
+ version: '2.0.0',
24
+ homepage: 'https://github.com/CseperkePapp/design-constraint-validator'
25
+ };
26
+ }
27
+ }
28
+ /**
29
+ * Print DCV version banner
30
+ * Shows: DCV v{version} | {repo_url}
31
+ */
32
+ export function printVersionBanner(options) {
33
+ if (options?.quiet)
34
+ return;
35
+ const pkg = getPackageInfo();
36
+ const repoUrl = pkg.homepage || 'https://github.com/CseperkePapp/design-constraint-validator';
37
+ console.log(`\x1b[2m${pkg.name} v${pkg.version} | ${repoUrl}\x1b[0m`);
38
+ }
39
+ /**
40
+ * Get version string for JSON output
41
+ */
42
+ export function getVersionInfo() {
43
+ const pkg = getPackageInfo();
44
+ return {
45
+ name: pkg.name,
46
+ version: pkg.version,
47
+ repository: pkg.homepage || 'https://github.com/CseperkePapp/design-constraint-validator'
48
+ };
49
+ }