design-constraint-validator 2.0.1 → 2.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 (118) hide show
  1. package/README.md +89 -23
  2. package/cli/commands/build.d.ts.map +1 -1
  3. package/cli/commands/build.js +32 -24
  4. package/cli/commands/build.ts +26 -17
  5. package/cli/commands/graph.d.ts.map +1 -1
  6. package/cli/commands/graph.js +35 -18
  7. package/cli/commands/graph.ts +30 -17
  8. package/cli/commands/patch-apply.d.ts.map +1 -1
  9. package/cli/commands/patch-apply.js +4 -1
  10. package/cli/commands/patch-apply.ts +4 -1
  11. package/cli/commands/set.d.ts.map +1 -1
  12. package/cli/commands/set.js +18 -19
  13. package/cli/commands/set.ts +19 -19
  14. package/cli/commands/utils.d.ts +1 -0
  15. package/cli/commands/utils.d.ts.map +1 -1
  16. package/cli/commands/utils.js +20 -1
  17. package/cli/commands/utils.ts +23 -1
  18. package/cli/commands/validate.d.ts.map +1 -1
  19. package/cli/commands/validate.js +45 -23
  20. package/cli/commands/validate.ts +47 -26
  21. package/cli/commands/why.d.ts.map +1 -1
  22. package/cli/commands/why.js +22 -10
  23. package/cli/commands/why.ts +20 -9
  24. package/cli/config-schema.d.ts +171 -166
  25. package/cli/config-schema.d.ts.map +1 -1
  26. package/cli/config-schema.js +29 -7
  27. package/cli/config-schema.ts +31 -7
  28. package/cli/config.d.ts.map +1 -1
  29. package/cli/config.js +8 -2
  30. package/cli/config.ts +8 -2
  31. package/cli/constraint-registry.d.ts +16 -0
  32. package/cli/constraint-registry.d.ts.map +1 -1
  33. package/cli/constraint-registry.js +115 -44
  34. package/cli/constraint-registry.ts +118 -47
  35. package/cli/cross-axis-loader.d.ts +62 -0
  36. package/cli/cross-axis-loader.d.ts.map +1 -1
  37. package/cli/cross-axis-loader.js +186 -31
  38. package/cli/cross-axis-loader.ts +199 -24
  39. package/cli/dcv.js +31 -25
  40. package/cli/dcv.ts +31 -21
  41. package/cli/json-output.d.ts +3 -1
  42. package/cli/json-output.d.ts.map +1 -1
  43. package/cli/json-output.js +11 -4
  44. package/cli/json-output.ts +13 -4
  45. package/cli/types.d.ts +21 -9
  46. package/cli/types.d.ts.map +1 -1
  47. package/cli/types.ts +25 -10
  48. package/cli/validate-api.d.ts +40 -0
  49. package/cli/validate-api.d.ts.map +1 -0
  50. package/cli/validate-api.js +90 -0
  51. package/cli/validate-api.ts +131 -0
  52. package/core/breakpoints.d.ts +8 -2
  53. package/core/breakpoints.d.ts.map +1 -1
  54. package/core/breakpoints.js +24 -3
  55. package/core/breakpoints.ts +22 -3
  56. package/core/color.js +4 -4
  57. package/core/color.ts +4 -4
  58. package/core/constraints/cross-axis.d.ts.map +1 -1
  59. package/core/constraints/cross-axis.js +37 -9
  60. package/core/constraints/cross-axis.ts +37 -9
  61. package/core/constraints/monotonic-lightness.d.ts.map +1 -1
  62. package/core/constraints/monotonic-lightness.js +9 -5
  63. package/core/constraints/monotonic-lightness.ts +9 -4
  64. package/core/constraints/monotonic.d.ts.map +1 -1
  65. package/core/constraints/monotonic.js +32 -8
  66. package/core/constraints/monotonic.ts +29 -8
  67. package/core/constraints/threshold.d.ts.map +1 -1
  68. package/core/constraints/threshold.js +24 -4
  69. package/core/constraints/threshold.ts +23 -4
  70. package/core/constraints/wcag.d.ts.map +1 -1
  71. package/core/constraints/wcag.js +7 -1
  72. package/core/constraints/wcag.ts +7 -1
  73. package/core/dtcg.d.ts +38 -0
  74. package/core/dtcg.d.ts.map +1 -0
  75. package/core/dtcg.js +88 -0
  76. package/core/dtcg.ts +102 -0
  77. package/core/engine.d.ts +6 -0
  78. package/core/engine.d.ts.map +1 -1
  79. package/core/engine.ts +7 -0
  80. package/core/flatten.d.ts +5 -3
  81. package/core/flatten.d.ts.map +1 -1
  82. package/core/flatten.js +32 -10
  83. package/core/flatten.ts +48 -16
  84. package/core/image-export.d.ts.map +1 -1
  85. package/core/image-export.js +10 -7
  86. package/core/image-export.ts +9 -6
  87. package/core/index.d.ts +2 -0
  88. package/core/index.d.ts.map +1 -1
  89. package/core/index.js +4 -0
  90. package/core/index.ts +6 -0
  91. package/core/poset.d.ts +6 -1
  92. package/core/poset.d.ts.map +1 -1
  93. package/core/poset.js +7 -2
  94. package/core/poset.ts +7 -2
  95. package/core/why.d.ts +1 -1
  96. package/core/why.d.ts.map +1 -1
  97. package/core/why.ts +1 -1
  98. package/mcp/contracts.d.ts +1561 -0
  99. package/mcp/contracts.d.ts.map +1 -0
  100. package/mcp/contracts.js +74 -0
  101. package/mcp/contracts.ts +105 -0
  102. package/mcp/index.d.ts +11 -0
  103. package/mcp/index.d.ts.map +1 -0
  104. package/mcp/index.js +35 -0
  105. package/mcp/index.ts +97 -0
  106. package/mcp/insights.d.ts +94 -0
  107. package/mcp/insights.d.ts.map +1 -0
  108. package/mcp/insights.js +445 -0
  109. package/mcp/insights.ts +541 -0
  110. package/mcp/tools.d.ts +63 -0
  111. package/mcp/tools.d.ts.map +1 -0
  112. package/mcp/tools.js +299 -0
  113. package/mcp/tools.ts +431 -0
  114. package/package.json +36 -26
  115. package/server.json +21 -0
  116. package/cli/constraints-loader.d.ts.map +0 -1
  117. package/cli/engine-helpers.d.ts.map +0 -1
  118. package/core/cross-axis-config.d.ts.map +0 -1
package/core/flatten.ts CHANGED
@@ -1,16 +1,22 @@
1
+ import { normalizeDtcgValue } from "./dtcg.js";
2
+
1
3
  export type TokenId = string; // e.g. "color.palette.brand.600"
2
4
  export type TokenValue = string | number;
3
- export type TokenNode = {
4
- $type?: string;
5
- $value?: TokenValue;
6
- [k: string]: TokenNode | string | number | undefined;
5
+ // $value may also be a DTCG 2025.10 structured object (color / dimension); it is
6
+ // normalized to a TokenValue at ingestion. See normalizeDtcgValue.
7
+ export type DtcgStructuredValue = Record<string, unknown>;
8
+ export type TokenNode = {
9
+ $type?: string;
10
+ $value?: TokenValue | DtcgStructuredValue;
11
+ $extensions?: DtcgStructuredValue; // spec passthrough — preserved, never interpreted
12
+ [k: string]: TokenNode | string | number | DtcgStructuredValue | undefined;
7
13
  };
8
14
 
9
15
  export type FlatToken = {
10
16
  id: TokenId;
11
17
  type: string;
12
18
  value: TokenValue; // resolved (if ref)
13
- raw: TokenValue; // original $value
19
+ raw: TokenValue | DtcgStructuredValue; // original $value
14
20
  refs: TokenId[]; // referenced token IDs found in raw
15
21
  };
16
22
 
@@ -19,9 +25,19 @@ export type FlattenResult = {
19
25
  edges: Array<[from: TokenId, to: TokenId]>; // from ref -> to dependent
20
26
  };
21
27
 
22
- const REF_RE = /\{([a-z0-9.-]+)\}/gi;
28
+ const REF_RE = /\{([a-z0-9_.-]+)\}/gi;
29
+ const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
23
30
 
24
31
  export function flattenTokens(root: TokenNode): FlattenResult {
32
+ // Fail closed on a malformed token root (TASK-017). A non-object root — null,
33
+ // an array, or a scalar — previously walked to a silently-empty set that
34
+ // "validated" with ok:true; that hid garbage input. The token root must be a
35
+ // JSON object. (An empty object `{}` is still valid — it just has no tokens.)
36
+ if (root === null || typeof root !== 'object' || Array.isArray(root)) {
37
+ const got = root === null ? 'null' : Array.isArray(root) ? 'array' : typeof root;
38
+ throw new Error(`Token root must be a JSON object (got ${got}).`);
39
+ }
40
+
25
41
  const flat: Record<TokenId, FlatToken> = {};
26
42
  const edges: Array<[TokenId, TokenId]> = [];
27
43
 
@@ -31,14 +47,18 @@ export function flattenTokens(root: TokenNode): FlattenResult {
31
47
 
32
48
  if (Object.prototype.hasOwnProperty.call(node, '$value')) {
33
49
  const id = path.join('.');
50
+ if (node.$value === undefined) return; // Skip tokens without values
51
+ // Normalize DTCG 2025.10 structured color/dimension objects to the
52
+ // string/number form the engine + plugins expect (strings, incl. aliases,
53
+ // pass through unchanged). Keeps the color math in core/color.ts untouched.
34
54
  const raw = node.$value;
35
- if (raw === undefined) return; // Skip tokens without values
36
-
55
+ const normalized = normalizeDtcgValue(raw, node.$type);
56
+
37
57
  const refs: TokenId[] = [];
38
58
 
39
59
  // Find all references in the value
40
- if (typeof raw === 'string') {
41
- const matches = raw.matchAll(REF_RE);
60
+ if (typeof normalized === 'string') {
61
+ const matches = normalized.matchAll(REF_RE);
42
62
  for (const match of matches) {
43
63
  refs.push(match[1]);
44
64
  }
@@ -47,7 +67,7 @@ export function flattenTokens(root: TokenNode): FlattenResult {
47
67
  flat[id] = {
48
68
  id,
49
69
  type: String(node.$type ?? 'unknown'),
50
- value: raw,
70
+ value: normalized,
51
71
  raw,
52
72
  refs
53
73
  };
@@ -72,8 +92,8 @@ export function flattenTokens(root: TokenNode): FlattenResult {
72
92
  // Second pass: resolve references iteratively
73
93
  let changed = true;
74
94
  let iterations = 0;
75
- const maxIterations = Object.keys(flat).length * 2; // Safety limit
76
-
95
+ const maxIterations = Object.keys(flat).length * 2 + 1; // Safety limit (never 0)
96
+
77
97
  while (changed && iterations < maxIterations) {
78
98
  changed = false;
79
99
  iterations++;
@@ -96,7 +116,7 @@ export function flattenTokens(root: TokenNode): FlattenResult {
96
116
  }
97
117
 
98
118
  // Replace the reference with the resolved value
99
- const refPattern = new RegExp(`\\{${refId}\\}`, 'g');
119
+ const refPattern = new RegExp(`\\{${escapeRegExp(refId)}\\}`, 'g');
100
120
  newValue = newValue.replace(refPattern, String(refToken.value));
101
121
  }
102
122
 
@@ -108,8 +128,20 @@ export function flattenTokens(root: TokenNode): FlattenResult {
108
128
  }
109
129
  }
110
130
 
111
- if (iterations >= maxIterations) {
112
- throw new Error('Token resolution exceeded maximum iterations - possible circular reference');
131
+ // A token still carrying an unresolved "{ref}" placeholder after the fixpoint
132
+ // means a genuine cycle (a -> b -> a) or a self-reference. An empty or
133
+ // fully-literal token set resolves cleanly and must never trip this guard —
134
+ // the old `iterations >= maxIterations` check threw a bogus "circular
135
+ // reference" error whenever no tokens were found (maxIterations === 0).
136
+ const unresolved = Object.values(flat).filter(
137
+ (t) => typeof t.value === 'string' && t.value.includes('{'),
138
+ );
139
+ if (unresolved.length > 0) {
140
+ throw new Error(
141
+ `Token resolution exceeded maximum iterations - possible circular reference (unresolved: ${unresolved
142
+ .map((t) => t.id)
143
+ .join(', ')})`,
144
+ );
113
145
  }
114
146
 
115
147
  return { flat, edges };
@@ -1 +1 @@
1
- {"version":3,"file":"image-export.d.ts","sourceRoot":"","sources":["image-export.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC;AACrC,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;AAEzC,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EAAe,6BAA6B;AAC7D,OAAO,EAAE,MAAM,EAAiB,wBAAwB;AACxD,GAAG,EAAE,QAAQ,EAAmB,gBAAgB;AAChD,QAAQ,EAAE,QAAQ,GACjB;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAoBhC"}
1
+ {"version":3,"file":"image-export.d.ts","sourceRoot":"","sources":["image-export.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC;AACrC,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;AAEzC,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EAAe,6BAA6B;AAC7D,OAAO,EAAE,MAAM,EAAiB,wBAAwB;AACxD,GAAG,EAAE,QAAQ,EAAmB,gBAAgB;AAChD,QAAQ,EAAE,QAAQ,GACjB;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAoBhC"}
@@ -5,13 +5,16 @@ import { spawnSync } from "node:child_process";
5
5
  function which(cmd) {
6
6
  const paths = (process.env.PATH || "").split(path.delimiter);
7
7
  for (const p of paths) {
8
- const full = path.join(p, cmd + (process.platform === "win32" ? ".cmd" : ""));
9
- try {
10
- fs.accessSync(full, fs.constants.X_OK);
11
- return full;
12
- }
13
- catch {
14
- // File not accessible, continue to next path
8
+ const names = process.platform === "win32" ? [cmd + ".cmd", cmd + ".exe", cmd] : [cmd];
9
+ for (const name of names) {
10
+ const full = path.join(p, name);
11
+ try {
12
+ fs.accessSync(full, fs.constants.X_OK);
13
+ return full;
14
+ }
15
+ catch {
16
+ // File not accessible, continue to next path
17
+ }
15
18
  }
16
19
  }
17
20
  return null;
@@ -6,12 +6,15 @@ import { spawnSync } from "node:child_process";
6
6
  function which(cmd: string): string | null {
7
7
  const paths = (process.env.PATH || "").split(path.delimiter);
8
8
  for (const p of paths) {
9
- const full = path.join(p, cmd + (process.platform === "win32" ? ".cmd" : ""));
10
- try {
11
- fs.accessSync(full, fs.constants.X_OK);
12
- return full;
13
- } catch {
14
- // File not accessible, continue to next path
9
+ const names = process.platform === "win32" ? [cmd + ".cmd", cmd + ".exe", cmd] : [cmd];
10
+ for (const name of names) {
11
+ const full = path.join(p, name);
12
+ try {
13
+ fs.accessSync(full, fs.constants.X_OK);
14
+ return full;
15
+ } catch {
16
+ // File not accessible, continue to next path
17
+ }
15
18
  }
16
19
  }
17
20
  return null;
package/core/index.d.ts CHANGED
@@ -7,4 +7,6 @@
7
7
  export type { TokenId, TokenValue } from "./flatten.js";
8
8
  export type { ConstraintIssue, ConstraintPlugin, Graph } from "./engine.js";
9
9
  export { Engine } from "./engine.js";
10
+ export { validate } from "../cli/validate-api.js";
11
+ export type { ValidateInput, ValidateResult } from "../cli/validate-api.js";
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAKrC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC"}
package/core/index.js CHANGED
@@ -5,3 +5,7 @@
5
5
  * See engine.ts for full implementation with Phase 3C enhancements.
6
6
  */
7
7
  export { Engine } from "./engine.js";
8
+ // Programmatic convenience API (validate tokens against constraints in one call).
9
+ // Implemented in the CLI layer atop the shared registry; surfaced here as the
10
+ // package's public entry point.
11
+ export { validate } from "../cli/validate-api.js";
package/core/index.ts CHANGED
@@ -8,3 +8,9 @@
8
8
  export type { TokenId, TokenValue } from "./flatten.js";
9
9
  export type { ConstraintIssue, ConstraintPlugin, Graph } from "./engine.js";
10
10
  export { Engine } from "./engine.js";
11
+
12
+ // Programmatic convenience API (validate tokens against constraints in one call).
13
+ // Implemented in the CLI layer atop the shared registry; surfaced here as the
14
+ // package's public entry point.
15
+ export { validate } from "../cli/validate-api.js";
16
+ export type { ValidateInput, ValidateResult } from "../cli/validate-api.js";
package/core/poset.d.ts CHANGED
@@ -3,7 +3,12 @@ export type Comp = "<=" | ">=";
3
3
  export type Order = [Id, Comp, Id];
4
4
  export type Digraph = Map<Id, Set<Id>>;
5
5
  export type EdgeLabels = Map<string, string>;
6
- /** Safe ID for Mermaid/DOT node identifiers */
6
+ /**
7
+ * Safe, INJECTIVE node identifier for Mermaid/DOT. Each non-alphanumeric byte
8
+ * (including `_`) is encoded as `_<charCode>_`, so distinct token ids can never
9
+ * collapse to the same node id (e.g. `a.b` -> `a_46_b`, `a_b` -> `a_95_b`).
10
+ * A naive `[^a-zA-Z0-9_] -> _` replacement merged those, corrupting graphs.
11
+ */
7
12
  export declare function sanitizeId(id: string): string;
8
13
  export type Highlight = {
9
14
  nodes?: Set<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"poset.d.ts","sourceRoot":"","sources":["poset.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,EAAE,GAAG,MAAM,CAAC;AACxB,MAAM,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAC/B,MAAM,MAAM,KAAK,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;AAEnC,MAAM,MAAM,OAAO,GAAG,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAGvC,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAM7C,+CAA+C;AAC/C,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAYnD;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAwBvD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,EAAC,KAAe,EAAC;;CAAK,GAAG,MAAM,CAQ9E;AAGD,wBAAgB,oBAAoB,CAClC,CAAC,EAAE,OAAO,EACV,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACxE,MAAM,CAuCR;AAED,wBAAgB,UAAU,CACxB,MAAM,EAAE,OAAO,EACf,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACjD,MAAM,CAoBR;AAGD,wBAAgB,gBAAgB,CAC9B,CAAC,EAAE,OAAO,EACV,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACxE,MAAM,CAyCR;AAGD,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAA;CAAE,CA2C7E;AAED,wBAAgB,aAAa,CAC3B,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GACjC,OAAO,CAYT;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAKtE;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAK3E;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,SAAI,GAAG,OAAO,CA8B3E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAOxF"}
1
+ {"version":3,"file":"poset.d.ts","sourceRoot":"","sources":["poset.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,EAAE,GAAG,MAAM,CAAC;AACxB,MAAM,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAC/B,MAAM,MAAM,KAAK,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;AAEnC,MAAM,MAAM,OAAO,GAAG,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAGvC,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAM7C;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAYnD;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAwBvD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,EAAC,KAAe,EAAC;;CAAK,GAAG,MAAM,CAQ9E;AAGD,wBAAgB,oBAAoB,CAClC,CAAC,EAAE,OAAO,EACV,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACxE,MAAM,CAuCR;AAED,wBAAgB,UAAU,CACxB,MAAM,EAAE,OAAO,EACf,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACjD,MAAM,CAoBR;AAGD,wBAAgB,gBAAgB,CAC9B,CAAC,EAAE,OAAO,EACV,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACxE,MAAM,CAyCR;AAGD,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAA;CAAE,CA2C7E;AAED,wBAAgB,aAAa,CAC3B,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GACjC,OAAO,CAYT;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAKtE;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAK3E;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,SAAI,GAAG,OAAO,CA8B3E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAOxF"}
package/core/poset.js CHANGED
@@ -3,9 +3,14 @@
3
3
  // Small escapes so Mermaid/DOT don't choke on special characters
4
4
  const escMermaid = (s) => s.replace(/"/g, '\\"').replace(/\n/g, "\\n");
5
5
  const escDot = (s) => s.replace(/"/g, '\\"').replace(/\n/g, "\\n");
6
- /** Safe ID for Mermaid/DOT node identifiers */
6
+ /**
7
+ * Safe, INJECTIVE node identifier for Mermaid/DOT. Each non-alphanumeric byte
8
+ * (including `_`) is encoded as `_<charCode>_`, so distinct token ids can never
9
+ * collapse to the same node id (e.g. `a.b` -> `a_46_b`, `a_b` -> `a_95_b`).
10
+ * A naive `[^a-zA-Z0-9_] -> _` replacement merged those, corrupting graphs.
11
+ */
7
12
  export function sanitizeId(id) {
8
- return id.replace(/[^a-zA-Z0-9_]/g, "_");
13
+ return id.replace(/[^a-zA-Z0-9]/g, (c) => `_${c.charCodeAt(0)}_`);
9
14
  }
10
15
  export function buildPoset(orders) {
11
16
  const g = new Map();
package/core/poset.ts CHANGED
@@ -14,9 +14,14 @@ export type EdgeLabels = Map<string, string>; // key = "a|b" (raw ids)
14
14
  const escMermaid = (s: string) => s.replace(/"/g, '\\"').replace(/\n/g, "\\n");
15
15
  const escDot = (s: string) => s.replace(/"/g, '\\"').replace(/\n/g, "\\n");
16
16
 
17
- /** Safe ID for Mermaid/DOT node identifiers */
17
+ /**
18
+ * Safe, INJECTIVE node identifier for Mermaid/DOT. Each non-alphanumeric byte
19
+ * (including `_`) is encoded as `_<charCode>_`, so distinct token ids can never
20
+ * collapse to the same node id (e.g. `a.b` -> `a_46_b`, `a_b` -> `a_95_b`).
21
+ * A naive `[^a-zA-Z0-9_] -> _` replacement merged those, corrupting graphs.
22
+ */
18
23
  export function sanitizeId(id: string): string {
19
- return id.replace(/[^a-zA-Z0-9_]/g, "_");
24
+ return id.replace(/[^a-zA-Z0-9]/g, (c) => `_${c.charCodeAt(0)}_`);
20
25
  }
21
26
 
22
27
  export type Highlight = {
package/core/why.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { FlatToken } from "./flatten.js";
2
2
  export type WhyReport = {
3
3
  id: string;
4
4
  value: string | number | undefined;
5
- raw?: string | number;
5
+ raw?: unknown;
6
6
  refs?: string[];
7
7
  provenance: "base" | "theme" | "override" | "unknown";
8
8
  dependsOn: string[];
package/core/why.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"why.d.ts","sourceRoot":"","sources":["why.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACnC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;IACtD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAC,MAAM,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAQxF;AAED,wBAAgB,OAAO,CACrB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAC/B,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAC,MAAM,CAAC,CAAC,EAC7B,MAAM,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAO,GACpF,SAAS,CAkCX"}
1
+ {"version":3,"file":"why.d.ts","sourceRoot":"","sources":["why.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACnC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;IACtD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAC,MAAM,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAQxF;AAED,wBAAgB,OAAO,CACrB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAC/B,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAC,MAAM,CAAC,CAAC,EAC7B,MAAM,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAO,GACpF,SAAS,CAkCX"}
package/core/why.ts CHANGED
@@ -3,7 +3,7 @@ import type { FlatToken } from "./flatten.js";
3
3
  export type WhyReport = {
4
4
  id: string;
5
5
  value: string | number | undefined;
6
- raw?: string | number;
6
+ raw?: unknown;
7
7
  refs?: string[];
8
8
  provenance: "base" | "theme" | "override" | "unknown";
9
9
  dependsOn: string[]; // immediate refs (parents)