@bridge-ds/core 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +367 -2
  2. package/audit-report.schema.json +732 -0
  3. package/bridge.config.schema.json +600 -0
  4. package/dist/audit/code.d.ts +16 -0
  5. package/dist/audit/color.d.ts +18 -0
  6. package/dist/audit/components.d.ts +12 -0
  7. package/dist/audit/css.d.ts +24 -0
  8. package/dist/audit/ds-index.d.ts +40 -0
  9. package/dist/audit/index.d.ts +9 -0
  10. package/dist/audit/index.js +40 -0
  11. package/dist/audit/index.js.map +1 -0
  12. package/dist/audit/report.d.ts +53 -0
  13. package/dist/audit/scan-value.d.ts +30 -0
  14. package/dist/audit/tailwind.d.ts +14 -0
  15. package/dist/audit/types.d.ts +181 -0
  16. package/dist/brand.d.ts +152 -0
  17. package/dist/change-drafts.d.ts +26 -0
  18. package/dist/changeset.d.ts +24 -0
  19. package/dist/component-spec-B0V_D3gM.js +2642 -0
  20. package/dist/component-spec-B0V_D3gM.js.map +1 -0
  21. package/dist/component-spec.d.ts +164 -0
  22. package/dist/compose.d.ts +15 -0
  23. package/dist/config.d.ts +218 -0
  24. package/dist/diff-engine.d.ts +41 -0
  25. package/dist/docs-draft.d.ts +127 -0
  26. package/dist/draft-exchange.d.ts +138 -0
  27. package/dist/dtcg-codec.d.ts +46 -0
  28. package/dist/explorations-github.d.ts +84 -0
  29. package/dist/explorations.d.ts +156 -0
  30. package/dist/github-client.d.ts +379 -0
  31. package/dist/governance.d.ts +13 -0
  32. package/dist/history-demo.d.ts +8 -0
  33. package/dist/history-github.d.ts +14 -0
  34. package/dist/history.d.ts +339 -0
  35. package/dist/id-map.d.ts +31 -0
  36. package/dist/index.d.ts +35 -0
  37. package/dist/index.js +2935 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/json-ast.d.ts +64 -0
  40. package/dist/layered-DGate4nv.js +500 -0
  41. package/dist/layered-DGate4nv.js.map +1 -0
  42. package/dist/layered-review.d.ts +88 -0
  43. package/dist/layered.d.ts +93 -0
  44. package/dist/node/audit.d.ts +43 -0
  45. package/dist/node/component-specs.d.ts +47 -0
  46. package/dist/node/git-history.d.ts +14 -0
  47. package/dist/node/git-local.d.ts +31 -0
  48. package/dist/node/index.d.ts +5 -0
  49. package/dist/node/index.js +1765 -0
  50. package/dist/node/index.js.map +1 -0
  51. package/dist/node/spec-css.d.ts +87 -0
  52. package/dist/pr-composer.d.ts +55 -0
  53. package/dist/previews.d.ts +79 -0
  54. package/dist/ramps.d.ts +29 -0
  55. package/dist/report-tNzTriGF.js +1454 -0
  56. package/dist/report-tNzTriGF.js.map +1 -0
  57. package/dist/requests.d.ts +109 -0
  58. package/dist/resolver.d.ts +39 -0
  59. package/dist/risk.d.ts +41 -0
  60. package/dist/scopes.d.ts +10 -0
  61. package/dist/token-edits.d.ts +24 -0
  62. package/dist/token-set.d.ts +51 -0
  63. package/dist/types.d.ts +86 -0
  64. package/dist/usage.d.ts +84 -0
  65. package/dist/validators.d.ts +44 -0
  66. package/dist/values.d.ts +30 -0
  67. package/dist/wcag.d.ts +92 -0
  68. package/package.json +64 -4
package/README.md CHANGED
@@ -1,3 +1,368 @@
1
- # Temporary Holding Version
1
+ # @bridge-ds/core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Pure design-token logic for Bridge: load DTCG 2025.10 tokens across tiers, resolve aliases per brand,
4
+ theme and density, check WCAG contrast, diff three versions of a token set, edit `*.tokens.json` files
5
+ without reformatting them, build the pull request (body, risk tier, changeset) and talk to GitHub.
6
+
7
+ No Figma API, no DOM, no Node-only API in the main entry (Node helpers live in `@bridge-ds/core/node`): it runs in a Figma plugin sandbox, a browser, Node or an MCP
8
+ server. The DS Bridge Figma plugin (`apps/figma-plugin`) is built on it; a CLI and an MCP server can be
9
+ too. Nothing here hardcodes a repository: callers pass a `BridgeConfig` (usually `bridge.config.json`).
10
+
11
+ ```ts
12
+ import { loadConfig, buildTokenSet, layoutOptions, validateSet, parseContrastChecks } from '@bridge-ds/core';
13
+ ```
14
+
15
+ Published builds ship ES modules and type declarations from `dist/` (`npm run build:packages`). Inside this
16
+ monorepo the `source` export condition resolves to the TypeScript sources instead, so nothing needs building
17
+ first. Tests: `npx vitest run --project core` (part of the root `npm test`).
18
+ Typecheck: `npx tsc --noEmit -p packages/core`.
19
+
20
+ ## Tiers
21
+
22
+ | Tier | Files (under `tokens.root`) | Modes | May alias |
23
+ | --- | --- | --- | --- |
24
+ | `primitive` | `primitives/*.tokens.json` | single (`"value"`) | primitives |
25
+ | `brand` | `brands/<brand>.tokens.json` | one per brand | primitives |
26
+ | `base` | `semantic/base.tokens.json` | single | primitives (literals allowed) |
27
+ | `semantic` | `semantic/<theme>.tokens.json` | one per theme | colors: `palette.*` (or `color.alpha.*`); others: brand roles or primitives |
28
+ | `density` | `density/<density>.tokens.json` | one per density | primitives |
29
+ | `component` | `components/*.tokens.json` | single | semantic, base, density, brand roles, primitive scale steps; never a palette role or a raw color |
30
+ | overrides | `brands/<brand>.<theme>.tokens.json` | brand x theme | same as semantic; must override an existing semantic token |
31
+
32
+ ## config
33
+
34
+ `bridge.config.json` at the repository root: one file and one schema (`packages/core/bridge.config.schema.json`,
35
+ also exported as `@bridge-ds/core/bridge.config.schema.json`) for the plugin, the docs site, the CLI and the MCP
36
+ server. Canonical form: `repo`, `tokens` (`root`, `include`, `exclude`, `tiers`, ordered `layers` with `{mode}`
37
+ placeholders and `optional`), `modes` (name -> `{ values, default, parity }`), `contrast` (`path`, `modes`),
38
+ `components`, `docs`, `changesets`, `cssVar`, `governance` (`protectedBranches`, `branchPrefix`, `labels`,
39
+ `guardrails`, `owners: { design, tech }`). The older flat form (`tokens.primitives`, `modes.brands`...) still loads.
40
+
41
+ ```ts
42
+ const CONFIG_FILE = 'bridge.config.json';
43
+ interface BridgeConfig {
44
+ repo: { owner; name; defaultBranch } | undefined;
45
+ tokens: TokenPaths & { include; exclude; tiers: Record<string, string[]>; layers: { files; optional }[] };
46
+ axes: Record<string, { values; default; parity }>; // canonical modes
47
+ modes: { brands; themes; densities; defaults }; // brand/theme/density view for the tier model
48
+ contrast: { path; modes? } | undefined;
49
+ components?: { dir; docsSuffix; styleSuffix; reference?; index? };
50
+ docs; changesets: { dir; package } | undefined; changeset?: { package };
51
+ cssVar: { prefix; separator };
52
+ governance: { protectedBranches; branchPrefix; labels; guardrails; owners: { design: string[]; tech: string[] } };
53
+ }
54
+ function defineConfig(input): BridgeConfig; function loadConfig(source): BridgeConfig; // ConfigError { problems }
55
+ function toConfigJson(config): object; // canonical JSON (what `bridge init` writes)
56
+ function protectedBranchesOf(config); function baseBranchOf(config); function tokensPath(config, rel?);
57
+ ```
58
+
59
+ ## governance
60
+
61
+ ```ts
62
+ function assertSafeBranch(branch, { protectedBranches?, base?, branchPrefix? }): void; // ProtectedBranchError
63
+ const DEFAULT_PROTECTED_BRANCHES = ['main', 'master'];
64
+ ```
65
+
66
+ The GitHub client and the local git writer both call it: no Bridge surface writes to a protected branch.
67
+
68
+ ## layered sets (CLI and MCP)
69
+
70
+ ```ts
71
+ new LayeredTokenSet(config, files: Map<relPath, text>); // combos(), view(combo), resolve(path, combo), tierOf(path), cssVarName(path)
72
+ function validateLayered(set): LayerIssue[]; // DTCG shapes, names, aliases per combo, missing layers, mode parity
73
+ function runLayeredContrast(set, pairs, modes?); function contrastDelta(before, after);
74
+ function applyTokenEdits(set, edits: TokenEdit[]): { files; touched; notes }; // set, add, remove, rename; minimal diffs
75
+ function coerceValue(input, type?, existing?); // "#ff5500", "12px", "150ms" -> the repo's DTCG form
76
+ function diffLayered(before, after); function layeredImpact(before, after, changes);
77
+ function classifyLayered(changes, otherFiles?, config?); // classifyRisk underneath
78
+ function composeProposalBody(input); function changeTable(changes); function contrastDeltaTable(deltas);
79
+ ```
80
+
81
+ `@bridge-ds/core/node` (Node only): `git`, `isGitRepo`, `currentBranch`, `branchExists`, `showFile`, `listFilesAt`,
82
+ `originRepo`, `commitToNewLocalBranch` (plumbing commit on a new branch; working tree, index and checkout untouched).
83
+
84
+ ## types
85
+
86
+ ```ts
87
+ type Tier = 'primitive' | 'brand' | 'base' | 'semantic' | 'density' | 'component';
88
+ const TIER_ORDER: Tier[]; // dependency order
89
+ const TIER_AXIS: Record<Tier, 'brand' | 'theme' | 'density' | undefined>;
90
+ const TIER_NAMES: Record<Tier, string>; // "Primitives", "Brand", "Base", "Semantic", "Density", "Component"
91
+ const SINGLE_MODE = 'value'; // mode key of single-mode tiers (alias PRIMITIVE_MODE)
92
+ interface LogicalToken { id; path; type; tier; values: Record<mode, unknown>; description; scopes; files: Record<mode, file> }
93
+ interface ThemeOverride { brand; theme; path; type; value; file }
94
+ interface RepoLayout { primitiveFiles; brands; baseFiles; themes; overrides; densities; componentFiles }
95
+ function isMultiMode(tier: Tier): boolean;
96
+ function modeDisplayName(mode: string): string; // "light" -> "Light"
97
+ ```
98
+
99
+ ## json-ast and dtcg-codec (minimal-diff edits)
100
+
101
+ A format-preserving JSON parser: an untouched file serializes byte for byte; an edited token keeps key
102
+ order, indentation, inline or multi-line layout and number spelling.
103
+
104
+ ```ts
105
+ function parseJson(text: string): JsonDocument;
106
+ function serializeJson(doc: JsonDocument): string;
107
+ function parseTokenFile(file: string, text: string): TokenDocument;
108
+ function serializeTokenFile(doc: TokenDocument): string;
109
+ function flattenTokens(doc: TokenDocument): FlatToken[]; // $type inherited from groups
110
+ function readTokenAt(doc, path): FlatToken | undefined;
111
+ function upsertToken(doc, path, fields: { type; value; description; extensions? }, template?): boolean; // false when nothing changed
112
+ function removeToken(doc, path): boolean; // prunes empty groups
113
+ function moveToken(doc, from, to): boolean; // keeps every field
114
+ function emptyTokenFile(file): TokenDocument;
115
+ ```
116
+
117
+ ## values and scopes
118
+
119
+ ```ts
120
+ function isAlias(v): v is string; function aliasTarget(v: string): string; function toAlias(path): string;
121
+ function parseColor(v): Rgba | undefined; function rgbaToDtcg(c: Rgba): object; function rgbaToHex(c): string;
122
+ function parseDimension(v): { value: number; unit: string } | undefined;
123
+ function valuesEqual(type, a, b): boolean; // colors by 8-bit hex + alpha, numbers to 1e-4
124
+ function formatValue(type, v): string; // "palette.primary.600", "#4b5562", "16px"
125
+ function defaultScopes(path, type, tier): string[]; // Figma scopes inferred from path and type
126
+ function effectiveScopes(t): string[];
127
+ ```
128
+
129
+ ## token-set (load)
130
+
131
+ ```ts
132
+ function buildTokenSet(files: Record<relPath, text>, options?: LayoutOptions): TokenSet;
133
+ function layoutOptions(config: BridgeConfig): LayoutOptions; // tier folders + mode order from the config
134
+ function detectLayout(files: string[], options?: LayoutOptions): RepoLayout;
135
+ function modesOf(layout, tier): string[]; // ["bridge", "ocean"], ["light", "dark"], ["value"]...
136
+ function modeFile(layout, tier, mode): string | undefined;
137
+ function defaultFile(tier, path, dirs?): string; // where a new single-mode token goes
138
+ function defaultContext(set): ResolveContext; // first brand, theme, density, with overrides
139
+ function contextFor(set, tier, mode): ResolveContext;
140
+ interface TokenSet { tokens; byPath; docs; layout; options; overrides: ThemeOverride[]; overrideIndex; problems: string[] }
141
+ ```
142
+
143
+ ## resolver (aliases across tiers)
144
+
145
+ ```ts
146
+ interface ResolveContext { brand?; theme?; density?; overrides?: Map<string, unknown> }
147
+ function resolve(index: Map<path, LogicalToken>, path, ctx): { value?; chain: string[]; via: ('token' | 'override')[]; error?: 'cycle' | 'broken' | 'missing-mode'; at? };
148
+ function valueIn(t: LogicalToken, ctx): { value; via };
149
+ function findReferenceProblems(index, axes: { brand?: string[]; theme?: string[]; density?: string[] }): ReferenceProblem[];
150
+ ```
151
+
152
+ Semantic tokens take a brand override when the context's brand x theme has one.
153
+
154
+ ## wcag (contrast)
155
+
156
+ ```ts
157
+ const CONTRAST_CHECKS_FILE = 'contrast.checks.json';
158
+ function parseContrastChecks(text): ContrastPair[]; // { pairs: [[fg, bg, min], ...] }; kind = min >= 4.5 ? 'text' : 'ui'
159
+ function contrastScope(set): ContrastScope; // every brand x theme, default density, overrides
160
+ function evaluatePairs(index, pairs, scope): ContrastResult[]; // { pair, brand, theme, ratio, level, error?, steps }
161
+ function contrastRatio(fg, bg): number; function contrastLevel(ratio, kind, min?): 'AAA' | 'AA' | 'fail';
162
+ function renderedColor(v): Rgba | undefined; // hex wins over components, like tokens.css
163
+ function contextLabel(r): string; // "Ocean dark"
164
+ function pairKey(r): string;
165
+ ```
166
+
167
+ `steps` lists every token both colors resolve through, which is how a change finds the pairs it
168
+ affects (see `affectedContrast`).
169
+
170
+ ## validators (guardrails)
171
+
172
+ ```ts
173
+ function validateSet(set, pairs): { issues: Issue[]; contrast: ContrastResult[] };
174
+ function validateProposal(proposed, head, pairs, typeChanges?): { blocking; warnings; contrastBefore; contrastAfter };
175
+ function shadowedEdits(set, edits: { path; tier; modes }[]): Issue[]; // semantic edits a brand override hides
176
+ type IssueCode = 'file-problem' | 'raw-value' | 'alias-tier' | 'broken-reference' | 'alias-cycle' | 'all-scopes'
177
+ | 'missing-mode' | 'override-orphan' | 'override-shadowed' | 'contrast' | 'contrast-unresolved' | 'type-change';
178
+ ```
179
+
180
+ Problems already on the base branch come back as warnings, so only what a change introduces blocks.
181
+
182
+ ## id-map and diff-engine (three-way diff)
183
+
184
+ ```ts
185
+ function threeWayDiff(base?: Map<id, LogicalToken>, edited: Map, head: Map): { local; remote; conflicts; unchanged };
186
+ function toMap(tokens): Map<id, LogicalToken>;
187
+ function fieldChanges(a, b): FieldChange[];
188
+ function reconcileIds(edited: LogicalToken[], git: LogicalToken[]): { tokens; adoptedByName; suggestions: RenameSuggestion[] };
189
+ function applyConfirmedRenames(tokens, confirmed): LogicalToken[];
190
+ function idExtensionFor(t): string | undefined; // "com.dsbridge.id" only after a rename
191
+ ```
192
+
193
+ ## compose (write changes back)
194
+
195
+ ```ts
196
+ interface AcceptedChange { target?: LogicalToken; current?: LogicalToken } // target undefined = remove
197
+ function composeFiles(head: TokenSet, originals: Record<relPath, text>, changes): { files; allFiles };
198
+ ```
199
+
200
+ Each value goes to the file of its tier and mode; only changed files are returned.
201
+
202
+ ## risk
203
+
204
+ ```ts
205
+ type RiskTier = 1 | 2 | 3;
206
+ function classifyRisk(input: { tokenChanges?; files?: { path; status? }[]; config? }): { tier; label; reasons: string[]; bump?: 'patch' | 'minor' | 'major' };
207
+ function tokenBump(changes); function isGuardrail(path, config?);
208
+ const RISK_LABELS: Record<RiskTier, string>;
209
+ ```
210
+
211
+ The one classifier for the plugin, the docs site, the CLI and the MCP server. Rules (from
212
+ `docs/GOVERNANCE.md`, highest wins): Tier 3 for any token added, changed (value, type, scopes), renamed or
213
+ removed in any tier or override, `*.tokens.json` files when their token changes are unknown, and guardrail
214
+ files (`governance.guardrails`, the contrast checks, `bridge.config.json`, `.github/**`). Tier 2 for component
215
+ sources under `components.dir` and unknown source files. Tier 1 for a description-only token edit, docs,
216
+ `*.md`, stories, `*.docs.json`, tests and changesets. Bumps: removals and renames `major`, other token
217
+ changes `minor`, description-only `patch`. Token changes can be `AcceptedChange`s or `{ kind, path,
218
+ descriptionOnly? }` summaries.
219
+
220
+ ## pr-composer and changeset
221
+
222
+ ```ts
223
+ function composePrBody(input: { changes; head; proposed; validation; baseBranch; headSha; baseSha; origin: PrOrigin; title; notes; upstreamPaths; resolvedConflicts }): string;
224
+ interface PrOrigin { tool: string; surface: string; location?: string; authoredBy: string }
225
+ function affectedContrast(change, before: ContrastResult[], after: ContrastResult[]): { before?; after? }[];
226
+ function changedModes(change): Set<string> | undefined;
227
+ function defaultTitle(changes): string; function branchName(slug, date): string; function slugify(text, max?): string;
228
+ function commitMessage(title, changes, footer?): string;
229
+ function changesetBump(changes): 'minor' | 'major';
230
+ function composeChangeset(changes, { title, branch, packageName, source? }): { path; content };
231
+ ```
232
+
233
+ The body has a Changes table (token, tier, old -> new per mode, contrast before -> after), a Contrast
234
+ impact table, the guardrail checklist and the risk tier. A pair is listed under a changed token when its
235
+ foreground or background resolves through that token, directly or through aliases, in a brand x theme
236
+ where the changed mode is used (PR #4 regression: changing `color.bg.default` lists every pair on it).
237
+
238
+ ## github-client
239
+
240
+ ```ts
241
+ new GitHubClient(repo: 'owner/name', token: string, fetchImpl?: FetchLike, retry?: RetryPolicy);
242
+ // refs and git data
243
+ getRepo(); getBranchSha(branch); getCommitTree(sha); getTree(treeSha, recursive?); getBlobText(sha);
244
+ createBlob(text); createTree(baseTree, files); createCommit(message, tree, parents); createBranch(name, sha); updateBranch(name, sha);
245
+ compare(base, head);
246
+ // contents API
247
+ getFileText(path, ref?): Promise<string | undefined>; putFile({ path, content, branch, message, sha? }): Promise<commitSha>;
248
+ // pull requests, reviews, checks, labels
249
+ createPullRequest({ title, head, base, body, draft }); getPullRequest(n); findOpenPullRequest(branch); updatePullRequest(n, patch);
250
+ listPullRequestFiles(n); listReviews(n); createReview(n, { event, body });
251
+ listCheckRuns(ref); createCheckRun({ name, headSha, status?, conclusion?, output? }); // check runs need a GitHub App token
252
+ addLabels(n, labels); removeLabel(n, label); addComment(n, body);
253
+ // whole token set at a commit, honoring bridge.config.json in the repo
254
+ loadRepoFiles(client, commitSha, fallbackTokensPath): Promise<{ files; contrastChecks; config?; tokensPath; warnings }>;
255
+ ```
256
+
257
+ Reads and content-addressed writes retry on network errors, 5xx and secondary rate limits; errors
258
+ become `GitHubError { status, message, detail }` with plain-language messages.
259
+
260
+ ## history (history.ts, history-github.ts, history-demo.ts)
261
+
262
+ What changed in the tokens, when, who changed it and why, and how to undo it safely.
263
+
264
+ ```ts
265
+ // model
266
+ interface HistoryEntry { id; sha; baseSha?; date; author; title; why; pr?; changes: LayeredChange[]; counts; tier; bump; breaking; reasons; changesets; version?; bulk?; demo? }
267
+ function buildHistoryEntry({ config, commit, before, after, changesets? }): HistoryEntry | null;
268
+ function collectHistory(config, backend: HistoryBackend, { ref, limit }): Promise<HistoryEntry[]>;
269
+ // backends: GitHub REST (browser or Node) and local git (Node, @bridge-ds/core/node)
270
+ function gitHubHistoryBackend(client, config); function loadGitHubHistory(client, config, { ref?, limit? }): Promise<HistoryReport>;
271
+ function localGitHistoryBackend(root, config); function loadLocalHistory(root, config, { ref?, limit? }): Promise<HistoryReport>;
272
+ // versions: CHANGELOG sections and tags; the timeline of design system versions
273
+ function parseChangelog(text); function releasesFromTags(tags, pkg?); function assignVersions(entries, releases); function versionTimeline(entries, releases?);
274
+ // reading
275
+ function filterHistory(entries, { token?, author?, tier?, version?, breaking?, since?, until?, query? });
276
+ function tokenHistory(entries, path, config?): TokenHistoryItem[]; // every change of one token, renames followed
277
+ function fileModes(config, file); function modeLabel(modes); // semantic/dark.tokens.json -> { theme: "dark" } -> "Dark"
278
+ // planning (never writes)
279
+ function planRevert({ current, entry, later?, pairs?, resolutions? }): ChangePlan;
280
+ function planRestore({ current, path, file?, value, label?, item?, pairs? }): ChangePlan;
281
+ function planToJson(plan); // a patch-like JSON for people without a token
282
+ function demoHistory(config, currentFiles): HistoryReport; // fixture entries, flagged demo, never real
283
+ ```
284
+
285
+ Local history follows the first parent of the ref, so a merged branch is one entry (the merge commit against
286
+ the base it merged into); GitHub history asks each commit for its pull request and diffs the PR's merge commit.
287
+ Bulk entries (more than 60 token changes: an import or a restructure) are flagged.
288
+
289
+ A revert is not a blind `git revert`. `planRevert` computes the inverse token edits against the current base
290
+ branch: a token that changed again later (`changed-later`), was removed (`removed-later`), came back
291
+ (`recreated-later`), moved (`renamed-later`) or that other tokens now alias (`still-referenced`) is a conflict,
292
+ listed with the later changes that explain it and kept as it is unless `resolutions[key]` is `"use-before"`.
293
+ Changes already undone are skipped. The rest goes through `applyTokenEdits` (minimal diffs), validation, the
294
+ contrast gate, `classifyRisk` and a changeset, like any proposal: `status` is `ready`, `blocked` (new
295
+ validation errors or a failing contrast pair) or `nothing-to-do`. The PR body starts with "Reverts #n" and the
296
+ conflicts table.
297
+ ## audit (`@bridge-ds/core/audit`)
298
+
299
+ Design system adoption and drift in a product codebase that consumes the DS. Pure (file texts in, report
300
+ out); it reads TSX/JSX with the TypeScript compiler API (syntax only), which is why it is its own entry and the
301
+ Figma plugin never loads it. `@bridge-ds/core/node` adds the disk side.
302
+
303
+ ```ts
304
+ import { DesignSystemIndex, buildAuditReport, compareWithBaseline, auditMarkdown, planCssFixes, auditFile } from '@bridge-ds/core/audit';
305
+ import { loadDesignSystem, runAudit, collectAuditFiles, fixCss, findDesignSystemRoot } from '@bridge-ds/core/node';
306
+
307
+ const ds = loadDesignSystem('/path/to/ds-repo'); // bridge.config.json: tokens in every mode, components, packages
308
+ const report = runAudit('apps/admin/src', { ds: ds.root, label: 'admin', baseline }); // AuditReport, schemaVersion 1
309
+ ```
310
+
311
+ - **Scanned**: CSS, SCSS, Less, CSS modules; `style`, MUI `sx`, `styled()`, `makeStyles`, `createTheme` and theme
312
+ objects, styled-components / emotion templates; Tailwind class strings (`className`, `cn()`, `clsx()`, `cva()`,
313
+ `@apply`); any color literal in code.
314
+ - **Hardcoded values**: colors (hex, rgb, hsl, named, bare HSL channels), spacing, sizes, radii, border widths,
315
+ shadows, durations, easings, z-index, font family, size, weight and line height. Each gets the closest token:
316
+ colors by deltaE in OKLab across every mode (semantic tokens and tokens that fit the property win near-ties),
317
+ lengths by nearest scale step, with a confidence (`exact`, `high`, `medium`, `low`).
318
+ - **Token references**: `var(--x)` of a DS token (also in Tailwind arbitrary values). Other variables, MUI palette
319
+ paths, `theme.spacing()` and Tailwind scale classes count as references to another theme, not as hardcoded.
320
+ - **Components**: JSX instances imported from the DS packages (`audit.packages`, default: the package that holds
321
+ `components.dir`, plus `@bridge-ds/react`) versus foreign UI libraries (MUI, react-admin, antd, Chakra, Radix,
322
+ Headless UI, Mantine, shadcn/ui local components...), native elements that should be DS components
323
+ (`button`, `input`, `select`, `textarea`, `dialog`, `table`, `a` with `href`, `img`), router links (react-router,
324
+ ra-core `LinkBase`, next/link) unless passed as a DS component's `render` element, chart libraries (recharts,
325
+ Nivo, Chart.js...: foreign with no DS equivalent) and local components named like a DS component. Providers and
326
+ other non-visual parts never count, DS ones included.
327
+ - **Metrics**: token adoption = token references / (token references + hardcoded values); component adoption =
328
+ DS instances / all UI instances. By category, top files, top fixes, per-file findings with line numbers.
329
+ - **Baseline**: `compareWithBaseline(report, previous)` adds deltas per metric, category and file, and carries the
330
+ history. `auditMarkdown(report)` renders the summary. `planCssFixes(text, lang, ds)` is the safe autofix: only
331
+ CSS values equal to a token become `var(--token)`.
332
+
333
+ The report schema is `packages/core/audit-report.schema.json` (also exported as
334
+ `@bridge-ds/core/audit-report.schema.json`). `bridge.config.json` takes an optional `audit` section:
335
+ `packages`, `foreignLibraries`, `nativeElements`, `exclude`.
336
+
337
+ ## component specs (`component-spec.ts`, generator in `@bridge-ds/core/node`)
338
+
339
+ A declarative description of a coded component that a design tool can rebuild: variant axes, TEXT and BOOLEAN
340
+ properties, parts (frames, text, icons with auto layout) and, per variant, the token path behind every fill, stroke,
341
+ radius, padding, gap, size and text property. The DS Bridge Figma plugin pulls it as component sets bound to variables.
342
+
343
+ ```ts
344
+ import { parseComponentSpecs, variantKeyOf, type ComponentSpec } from '@bridge-ds/core';
345
+ import { generateRepoComponentSpecs, serializeComponentSpecs } from '@bridge-ds/core/node';
346
+
347
+ const file = generateRepoComponentSpecs('/path/to/repo', ['Button']); // reads bridge.config.json, tokens, TSX, CSS modules
348
+ ```
349
+
350
+ Derived from code, never hand-maintained: `npm run specs` writes `packages/react/component-specs.json` (path:
351
+ `components.specs` in the config), and `packages/core/test/component-specs.test.ts` fails when it is out of date.
352
+ The generator reads the component with the TypeScript parser (props interface, defaults, the JSX it returns for given
353
+ props, nested DS components such as Icon) and runs a small CSS cascade (`spec-css.ts`: classes, attributes, `:hover`,
354
+ `:active`, `:not()`, `:has()`, combinators, `@media (hover: hover)`, specificity, inheritance, custom properties).
355
+ Union props that change the styles become axes; a `State` axis lists the states (Hover, Pressed, Disabled) that look
356
+ different. Values that are not a token (`calc()`, percentages) and props it does not draw are listed in `unmapped`.
357
+
358
+ Swappable parts carry a `swap` (INSTANCE_SWAP property name, default component, options) and each variant lists the
359
+ component it shows in `slots`:
360
+
361
+ - Icon parts (`LucideIcon` props) default to the icon the docs preview, then the `AllStates` story, passes to the
362
+ prop (else Plus, Info for Badge). The file's `icons` section holds the normalized 24x24 SVG (`stroke="currentColor"`)
363
+ of every icon a slot defaults to, every icon the components' docs and stories reference, and a common set
364
+ (`COMMON_ICONS`), read from the installed `lucide-react`; aliases of one Lucide icon fold into the first name seen.
365
+ `iconStyle` names the tokens of the icon components themselves (size, stroke color, stroke width).
366
+ - A compound part's text that code draws full-bleed in some variant (no inline padding: Card `padding="none"`) is a
367
+ content slot: `Slot/<Component> <part> text` or `Slot/Media`, Media where it is full-bleed. The `slots` section
368
+ describes both components.