@bridge-ds/core 0.18.0 → 0.20.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 (69) hide show
  1. package/LICENSE +17 -0
  2. package/bridge.config.schema.json +21 -0
  3. package/dist/audit/index.js +3 -3
  4. package/dist/audit/report.d.ts +17 -2
  5. package/dist/branch-actions.d.ts +7 -1
  6. package/dist/build-requests-github.d.ts +25 -0
  7. package/dist/build-requests.d.ts +403 -0
  8. package/dist/changelog-model.d.ts +102 -0
  9. package/dist/component-spec.d.ts +3 -1
  10. package/dist/config.d.ts +26 -0
  11. package/dist/css-color.d.ts +25 -0
  12. package/dist/docs-proposal.d.ts +1 -0
  13. package/dist/explorations.d.ts +1 -0
  14. package/dist/fidelity-references.d.ts +12 -0
  15. package/dist/fidelity.d.ts +12 -1
  16. package/dist/figma-fix-loop.d.ts +153 -0
  17. package/dist/figma-fix-run.d.ts +103 -0
  18. package/dist/figma-fixes.d.ts +169 -0
  19. package/dist/figma-push-github.d.ts +7 -2
  20. package/dist/figma-push.d.ts +15 -2
  21. package/dist/gap-case.d.ts +143 -0
  22. package/dist/github-client.d.ts +31 -0
  23. package/dist/id-map.d.ts +10 -0
  24. package/dist/index.d.ts +17 -0
  25. package/dist/index.js +3996 -818
  26. package/dist/index.js.map +1 -1
  27. package/dist/layered-review.d.ts +2 -0
  28. package/dist/measure-location.d.ts +42 -0
  29. package/dist/measure-status.d.ts +127 -0
  30. package/dist/measure-workflow.d.ts +48 -0
  31. package/dist/measured-only.d.ts +30 -0
  32. package/dist/measured-spec.d.ts +349 -0
  33. package/dist/measured-tokens.d.ts +25 -0
  34. package/dist/motion-docs.d.ts +51 -0
  35. package/dist/node/audit.d.ts +11 -3
  36. package/dist/node/index.d.ts +1 -0
  37. package/dist/node/index.js +1640 -24
  38. package/dist/node/index.js.map +1 -1
  39. package/dist/node/measure-script.d.ts +1 -0
  40. package/dist/node/measure.d.ts +212 -0
  41. package/dist/node/spec-css.d.ts +9 -1
  42. package/dist/node/tokens-script.d.ts +3 -0
  43. package/dist/pr-composer.d.ts +5 -0
  44. package/dist/previews.d.ts +4 -1
  45. package/dist/{prod-branch-DenQkr_e.js → prod-branch-CJZwMfVD.js} +223 -183
  46. package/dist/prod-branch-CJZwMfVD.js.map +1 -0
  47. package/dist/prod-branch.d.ts +1 -1
  48. package/dist/repo-model.d.ts +7 -1
  49. package/dist/{repo-source-CRfhLO87.js → repo-source-CshjjC7x.js} +1485 -629
  50. package/dist/repo-source-CshjjC7x.js.map +1 -0
  51. package/dist/repo-source-github.d.ts +8 -0
  52. package/dist/repo-source.d.ts +6 -0
  53. package/dist/{report-DVEmUr0p.js → report-CE-jHAuw.js} +17 -6
  54. package/dist/{report-DVEmUr0p.js.map → report-CE-jHAuw.js.map} +1 -1
  55. package/dist/requester.d.ts +14 -0
  56. package/dist/requests.d.ts +2 -0
  57. package/dist/review-gate.d.ts +189 -2
  58. package/dist/risk.d.ts +12 -0
  59. package/dist/share-branch.d.ts +1 -1
  60. package/dist/snapshot.d.ts +34 -0
  61. package/dist/suggestion-review.d.ts +45 -2
  62. package/dist/suggestions-github.d.ts +33 -1
  63. package/dist/suggestions.d.ts +6 -0
  64. package/dist/tier1-publish.d.ts +48 -0
  65. package/dist/token-proposal.d.ts +7 -0
  66. package/dist/try-in-product.d.ts +9 -0
  67. package/package.json +1 -1
  68. package/dist/prod-branch-DenQkr_e.js.map +0 -1
  69. package/dist/repo-source-CRfhLO87.js.map +0 -1
@@ -8,7 +8,9 @@ export declare const DEFAULT_COMPONENT_SPECS_FILE = "packages/react/component-sp
8
8
  * (gap), sizes. Text: textFill (color), font family, size, weight, line height, letter spacing, and textDecoration (a
9
9
  * keyword, "UNDERLINE" or "STRIKETHROUGH", not a token).
10
10
  */
11
- export type SpecField = 'fill' | 'stroke' | 'strokeWeight' | 'strokeTopWeight' | 'strokeRightWeight' | 'strokeBottomWeight' | 'strokeLeftWeight' | 'cornerRadius' | 'paddingTop' | 'paddingRight' | 'paddingBottom' | 'paddingLeft' | 'itemSpacing' | 'counterAxisSpacing' | 'width' | 'height' | 'minWidth' | 'minHeight' | 'opacity' | 'textFill' | 'fontFamily' | 'fontSize' | 'fontWeight' | 'lineHeight' | 'letterSpacing' | 'textDecoration';
11
+ export type SpecField = 'fill' | 'stroke' | 'strokeWeight' | 'strokeTopWeight' | 'strokeRightWeight' | 'strokeBottomWeight' | 'strokeLeftWeight' | 'cornerRadius' | 'paddingTop' | 'paddingRight' | 'paddingBottom' | 'paddingLeft' | 'itemSpacing' | 'counterAxisSpacing' | 'width' | 'height' | 'minWidth' | 'minHeight' | 'opacity'
12
+ /** A CSS box-shadow list (drop and inner shadows), as the page computes it. */
13
+ | 'shadow' | 'textFill' | 'fontFamily' | 'fontSize' | 'fontWeight' | 'lineHeight' | 'letterSpacing' | 'textDecoration';
12
14
  export declare const SPEC_FIELDS: readonly SpecField[];
13
15
  /** Fields whose token must be a color. */
14
16
  export declare const COLOR_FIELDS: ReadonlySet<SpecField>;
package/dist/config.d.ts CHANGED
@@ -17,6 +17,16 @@ export interface StorybookConfig {
17
17
  /** Root URL (where index.json and iframe.html are served), https only, no trailing slash. */
18
18
  url: string;
19
19
  }
20
+ /**
21
+ * A public demo's persona (Bridge's own demo repositories only): who a visitor without an account stands in for,
22
+ * so the demo home greets them as that person ("Good morning, Dana.") and names their role. Ignored elsewhere.
23
+ */
24
+ export interface DemoConfig {
25
+ /** The persona's first name: "Dana". */
26
+ viewer: string;
27
+ /** What they do: "DS lead". */
28
+ role?: string;
29
+ }
20
30
  /** The team's hosted Library (/library/<owner>/<repo>/). */
21
31
  export interface LibraryConfig {
22
32
  /** True lets search engines index the hosted Library of a public repository. Default false: noindex. */
@@ -131,6 +141,11 @@ export interface GovernanceConfig {
131
141
  * person's approval counts for design and tech. Default false: one person never covers both approvals alone.
132
142
  */
133
143
  soloOwnerCanApproveBoth?: boolean | undefined;
144
+ /**
145
+ * Team admins and the reviewers of the matching domain publish a Tier 1 change without review (docs/GOVERNANCE.md,
146
+ * "Tier 1 publish by leads"). Absent counts as on (leadsPublishTier1On); false turns it off.
147
+ */
148
+ leadsPublishTier1?: boolean | undefined;
134
149
  }
135
150
  /** `bridge audit` settings: how product code that consumes the design system is measured. */
136
151
  export interface AuditConfig {
@@ -144,6 +159,8 @@ export interface AuditConfig {
144
159
  exclude?: string[] | undefined;
145
160
  }
146
161
  export interface BridgeConfig {
162
+ /** The team's display name ("Bridge", "Acme Design"), 1 to 60 characters. Undefined: Bridge names the team after its repository. */
163
+ name?: string | undefined;
147
164
  /** GitHub repository. Undefined only for a detected config of a repo with no known remote. */
148
165
  repo: RepoRef | undefined;
149
166
  tokens: TokenConfig;
@@ -190,6 +207,8 @@ export interface BridgeConfig {
190
207
  storybook?: StorybookConfig | undefined;
191
208
  /** The hosted Library. Read it through libraryIsPublic(). */
192
209
  library?: LibraryConfig | undefined;
210
+ /** A public demo's persona (see DemoConfig). */
211
+ demo?: DemoConfig | undefined;
193
212
  }
194
213
  type Partialish<T> = {
195
214
  [K in keyof T]?: T[K] | undefined;
@@ -198,6 +217,8 @@ type Partialish<T> = {
198
217
  export interface BridgeConfigInput {
199
218
  $schema?: string;
200
219
  version?: number;
220
+ /** The team's display name, shown on invites instead of owner/repo. */
221
+ name?: string;
201
222
  repo?: (Partial<RepoRef> & {
202
223
  owner: string;
203
224
  name: string;
@@ -256,7 +277,12 @@ export interface BridgeConfigInput {
256
277
  url: string;
257
278
  };
258
279
  library?: LibraryConfig;
280
+ demo?: DemoConfig;
259
281
  }
282
+ /** Longest team display name bridge.config.json accepts. */
283
+ export declare const TEAM_NAME_MAX = 60;
284
+ /** The team's display name from a config value: trimmed, 1 to 60 characters; undefined for anything else. */
285
+ export declare function teamName(value: unknown): string | undefined;
260
286
  export declare const DEFAULT_TOKEN_PATHS: TokenPaths;
261
287
  export declare const DEFAULT_GOVERNANCE: GovernanceConfig;
262
288
  export declare class ConfigError extends Error {
@@ -0,0 +1,25 @@
1
+ /** One CSS color function (`oklch(0.205 0 0)`, `color(display-p3 1 0 0 / 0.5)`) as `rgb(r, g, b)` / `rgba(...)`. */
2
+ export declare function cssColorToRgb(fn: string, inner: string): string | undefined;
3
+ /** Every oklch/oklab/lab/lch/color() in a CSS value (a color, a shadow, SVG markup) as sRGB; the rest unchanged. */
4
+ export declare function cssColorsToRgb(text: string): string;
5
+ /** The same on every string inside a value (a measure result, a token harvest). */
6
+ export declare function withSrgbColors<T>(value: T): T;
7
+ export interface CssShadow {
8
+ inset: boolean;
9
+ x: number;
10
+ y: number;
11
+ blur: number;
12
+ spread: number;
13
+ /** 0..1 sRGB and alpha. */
14
+ color: {
15
+ r: number;
16
+ g: number;
17
+ b: number;
18
+ a: number;
19
+ };
20
+ }
21
+ /**
22
+ * A computed `box-shadow` (`rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, inset ...`) as shadows, transparent ones left out (the
23
+ * zero rings utility CSS stacks under every shadow). Colors in any notation the page kept are read as sRGB.
24
+ */
25
+ export declare function parseBoxShadow(css: string | undefined): CssShadow[];
@@ -21,6 +21,7 @@ export interface DocsOrigin {
21
21
  }
22
22
  export declare const DOCS_EDITOR_ORIGIN: DocsOrigin;
23
23
  export declare const DOCS_MCP_ORIGIN: DocsOrigin;
24
+ export declare const DOCS_CLI_ORIGIN: DocsOrigin;
24
25
  export interface DocsIssue {
25
26
  field: string;
26
27
  message: string;
@@ -162,6 +162,7 @@ export declare function explorationShareMessage(input: {
162
162
  label: string;
163
163
  url: string;
164
164
  }[] | undefined;
165
+ local?: string | undefined;
165
166
  }): string;
166
167
  /**
167
168
  * The rules Claude follows on an exploration branch (request briefs, docs drafts, token edits with a target branch):
@@ -31,6 +31,18 @@ export interface ReferenceRender {
31
31
  * elements with their props, icons by name. Replaces `text` and `parts`.
32
32
  */
33
33
  children?: (string | SpecExample)[] | undefined;
34
+ /**
35
+ * Export name of the compound component this set is a part of (AccordionItem in Accordion, Radio in RadioGroup,
36
+ * MenuItem in Menu): the part needs its parent around it to render, and the image shows the part alone.
37
+ */
38
+ within?: string | undefined;
39
+ /**
40
+ * A popup drawn open (Dialog/Popup): the root renders open (`defaultOpen`), and the image is the popup Base UI
41
+ * portals, found by the class of the spec's root part ("popup", "tooltip"), not the trigger.
42
+ */
43
+ popup?: {
44
+ className: string;
45
+ } | undefined;
34
46
  }
35
47
  export type ReferencePlan = {
36
48
  ok: true;
@@ -132,7 +132,18 @@ export declare function buildFidelityReport(input: {
132
132
  file?: string;
133
133
  now?: Date;
134
134
  }): FidelityReport;
135
- /** One variant's verdict: "Matches the code" or "Differs from the code: 12% of pixels, size 120x40 vs 118x40". */
135
+ /**
136
+ * A size difference past the tolerance, in designer words: "height 89 in Figma vs 48 in code". It says more than the
137
+ * share of pixels when the two images do not have the same size (a 1% pixel score can hide a set twice as tall).
138
+ */
139
+ export declare function sizeGap(s: {
140
+ figma: Size;
141
+ code: Size;
142
+ }, tolerance?: number): string | undefined;
143
+ /**
144
+ * One variant's verdict: "Matches the code", "Differs from the code: 12% of pixels", or the size first when the size
145
+ * differs: "Differs from the code: height 89 in Figma vs 48 in code, 1.4% of pixels".
146
+ */
136
147
  export declare function fidelityVerdict(v: Pick<FidelityVariantResult, 'status' | 'percent' | 'size' | 'reason'>): string;
137
148
  /** One component's line, from its worst variant. */
138
149
  export declare function fidelityComponentLine(c: FidelityComponentResult): string;
@@ -0,0 +1,153 @@
1
+ import { type ComponentSpec, type SpecValue } from './component-spec';
2
+ import type { FidelityComponentResult, FidelityReport } from './fidelity';
3
+ import { type AppliedFix, type FigmaFix, type FigmaFixesFile, type FixedMeasuredSpecsFile } from './figma-fixes';
4
+ import { type GapBuiltVariant, type GapCase, type GapDifference } from './gap-case';
5
+ import type { MeasuredSpecsFile } from './measured-spec';
6
+ /** Where the plugin's Check fidelity run lives in the team's repo (or on the requests branch), next to measured-specs.json. */
7
+ export declare const DEFAULT_FIDELITY_RUN_FILE = "figma-fidelity.json";
8
+ /** Rounds of try per component before the loop gives up and proposes an honest status instead. */
9
+ export declare const FIX_LOOP_MAX_ROUNDS = 3;
10
+ /** The model the CLI loop runner asks when the team's API key is set and no model is named. */
11
+ export declare const FIX_LOOP_MODEL = "claude-sonnet-5-5";
12
+ /**
13
+ * One Check fidelity run, as the plugin saves it: the report (the shot referee), and per set what the layers read back
14
+ * (values, hidden parts, boxes: they let the gap say where space is missing) and a reference to each Figma render.
15
+ */
16
+ export interface FigmaFidelityRun {
17
+ version: 1;
18
+ report: FidelityReport;
19
+ /** Set name -> variant key -> what the plugin read back from the layers. */
20
+ built?: Record<string, Record<string, GapBuiltVariant>> | undefined;
21
+ /** Set name -> variant key -> node id or exported PNG. */
22
+ figmaRenders?: Record<string, Record<string, string>> | undefined;
23
+ /** measuredAt of the measured-specs.json the pull drew from. */
24
+ measuredAt?: string | undefined;
25
+ }
26
+ /**
27
+ * Reads a fidelity run: the plugin's run file, a bare FidelityReport ("Copy the report"), or one component's result.
28
+ * Throws a sentence that says what is wrong.
29
+ */
30
+ export declare function parseFidelityRun(input: string | unknown): FigmaFidelityRun;
31
+ export interface FigmaGapRow {
32
+ component: string;
33
+ /** Differs: the shots (or the measure) say so. Can't check: nothing to compare, or a status fix says so. */
34
+ status: 'differs' | 'cant-check';
35
+ /** One sentence for a designer. */
36
+ summary: string;
37
+ /** What said it: the plugin's Check fidelity (shots), the measure's --check (values), or a status fix. */
38
+ source: 'fidelity' | 'measure' | 'figma-fixes';
39
+ checked?: number | undefined;
40
+ differing?: number | undefined;
41
+ /** Fix ids figma-fixes.json already applies to it. */
42
+ fixes?: string[] | undefined;
43
+ }
44
+ /**
45
+ * The components whose Figma build differs from their story, or that cannot be checked: from the fidelity run when
46
+ * there is one (the shots are the referee), else from the measure's statuses (`bridge specs --measure --check`). A
47
+ * status fix in figma-fixes.json shows with its reason. Matching components are left out.
48
+ */
49
+ export declare function figmaGaps(measured: FixedMeasuredSpecsFile | MeasuredSpecsFile, run?: FigmaFidelityRun): FigmaGapRow[];
50
+ export interface GapCaseForInput {
51
+ component: string;
52
+ /** measured-specs.json as measured (the team's fixes are applied here). */
53
+ measured: MeasuredSpecsFile;
54
+ /** figma-fixes.json, when the repo has one. */
55
+ fixes?: FigmaFixesFile | undefined;
56
+ run?: FigmaFidelityRun | undefined;
57
+ }
58
+ export interface GapCaseFor {
59
+ gap: GapCase;
60
+ /** The measure with the team's fixes on (what the pull reads). */
61
+ measured: FixedMeasuredSpecsFile;
62
+ /** The set the pull draws from it (specsFromMeasure), when the measure gives one. */
63
+ spec?: ComponentSpec | undefined;
64
+ fidelity?: FidelityComponentResult | undefined;
65
+ shots: Record<string, {
66
+ file: string;
67
+ }>;
68
+ }
69
+ /** The gap case of one component, built the way the plugin pulls it: fixes applied, set rebuilt, the run's verdicts. */
70
+ export declare function gapCaseFor(input: GapCaseForInput): GapCaseFor;
71
+ export declare const FIX_SCHEMA_SUMMARY = "figma-fixes.json fixes (version 1). Each fix is one JSON object:\n- Every fix: \"id\" (lower case, digits, dashes, unique), \"kind\", \"component\" (the Figma set, e.g. \"TextInput\"), \"reason\" (one plain sentence, no em dash), optional \"by\": \"claude\", optional \"where\": { \"args\": { \"<arg>\": value }, \"state\": \"rest\" | \"hover\" | \"active\" | \"focus-visible\" | \"disabled\" } (absent: every variant).\n- kind \"part\": \"part\" is a part id from the gap case (\"root\", \"cds--text-input-label-wrapper\") or \".class\". At least one of: \"set\" { field: token path | px number | null (colors only) | \"full\" (cornerRadius only) }, \"width\" / \"height\": \"fixed\" (the measured size) | px number | \"hug\" | \"fill\", \"direction\": \"HORIZONTAL\" | \"VERTICAL\", \"align\": { \"primary\": MIN|CENTER|MAX|SPACE_BETWEEN, \"counter\": MIN|CENTER|MAX|STRETCH }, \"overlay\": true | false | { \"top\": 3, \"left\": 3 }, \"hide\": true (alone; never the root). Fields: fill, stroke, strokeWeight, strokeTopWeight, strokeRightWeight, strokeBottomWeight, strokeLeftWeight, cornerRadius, paddingTop, paddingRight, paddingBottom, paddingLeft, itemSpacing, counterAxisSpacing, width, height, minWidth, minHeight, opacity, textFill, fontFamily, fontSize, fontWeight, lineHeight, letterSpacing, textDecoration.\n- kind \"axis\": \"action\" \"map\" (\"arg\", \"property\" and/or \"values\" { code value: Figma value }), \"ignore\" (\"arg\"), or \"prune\" (\"where\" names the variants to drop).\n- kind \"story\": \"where\" names the variant, \"use\": { \"story\": \"<story id>\", \"args\"?, \"state\"? } is the measured render to draw it from.\n- kind \"status\": \"status\": \"differs\" | \"cant-check\" with the reason; never \"matches\" (only the shots say a component matches). Without \"status\": a note.\nRules: fix only the component you were given; prefer the smallest fix that explains the differences (a part's size \"fixed\" when Figma hugs what code fixes, a missing padding, a hidden part); never invent a part id or a token: use the ones in the gap case.";
72
+ export type FixDiffResult = 'resolved' | 'closer' | 'same' | 'worse' | 'unknown';
73
+ export type FixVerdict = 'closer' | 'same' | 'worse';
74
+ export interface TriedDifference {
75
+ text: string;
76
+ part: string;
77
+ property: GapDifference['property'];
78
+ /** Code (the measure). */
79
+ expected: SpecValue | boolean;
80
+ /** Figma now (the gap case), and what the rebuilt set draws with the fixes. */
81
+ before: SpecValue | boolean;
82
+ after: SpecValue | boolean | undefined;
83
+ deltaBefore?: number | undefined;
84
+ deltaAfter?: number | undefined;
85
+ result: FixDiffResult;
86
+ /** The size after is worked out from the layer boxes and the changed paddings, gaps and fixed sizes. */
87
+ estimated?: boolean | undefined;
88
+ }
89
+ export interface TriedVariant {
90
+ key: string;
91
+ story?: string | undefined;
92
+ /** gone: the fixes drop this variant (prune, ignore). */
93
+ verdict: FixVerdict | 'gone';
94
+ differences: TriedDifference[];
95
+ /** Differences the fixes add: the rebuilt set now disagrees with the code where it agreed. */
96
+ regressions: GapDifference[];
97
+ }
98
+ export interface TryFixesResult {
99
+ status: 'invalid' | 'checked';
100
+ component: string;
101
+ /** Validation problems (status invalid): fix them and try again. */
102
+ problems: string[];
103
+ /** One record per proposed fix: applied, not-found (its part or story is not in this measure), already. */
104
+ applied: AppliedFix[];
105
+ /** The values referee's verdict on the whole: closer, same or worse. */
106
+ verdict: FixVerdict;
107
+ referee: 'values';
108
+ variants: TriedVariant[];
109
+ /** One sentence. */
110
+ summary: string;
111
+ /** Lines for Claude: per variant, each difference before and after. */
112
+ detail: string;
113
+ /** The figma-fixes.json the fixes make (the repo's fixes, with these added or replacing the same ids). */
114
+ file?: FigmaFixesFile | undefined;
115
+ }
116
+ export interface TryFixesInput extends GapCaseForInput {
117
+ /** The fixes to try, added to the repo's (a fix with the id of one already there replaces it). */
118
+ proposed: unknown[];
119
+ }
120
+ /** The repo's fixes with the proposed ones: a proposed fix replaces one with the same id, the others are appended. */
121
+ export declare function mergeFigmaFixes(existing: FigmaFixesFile | undefined, proposed: FigmaFix[]): FigmaFixesFile;
122
+ /**
123
+ * The values referee: validates the proposed fixes, lays them on the measure, rebuilds the set the way the plugin pulls
124
+ * it, and compares each variant the gap case lists with what the code renders, before and after. A difference is
125
+ * resolved, closer, the same or worse; a difference the fixes add elsewhere is a regression. The verdict is closer when
126
+ * more moved closer than got worse. Offline: no Figma, no shots. The pixel check runs in the plugin after the pull.
127
+ */
128
+ export declare function tryFigmaFixes(input: TryFixesInput): TryFixesResult;
129
+ /** Plain words for one fix, for the pull request body. */
130
+ export declare function fixWords(f: FigmaFix): string;
131
+ /** A part fix that sizes, places or lays out a part: the values referee can't judge it, Check fidelity does. */
132
+ export declare function touchesLayout(f: FigmaFix): boolean;
133
+ /** The pull request body of a figma-fixes.json change: the gap, each fix with its reason, before and after. */
134
+ export declare function figmaFixesPrBody(input: {
135
+ component: string;
136
+ summary: string;
137
+ gap: GapCase;
138
+ tried: TryFixesResult;
139
+ fixes: FigmaFix[];
140
+ tool: string;
141
+ }): string;
142
+ export interface FixAttempt {
143
+ fixes: unknown[];
144
+ /** Validation problems, or the try's detail. */
145
+ feedback: string;
146
+ verdict?: FixVerdict | undefined;
147
+ }
148
+ /** The prompt for one round: the gap case, the schema, and what the earlier rounds gave. Asks for fix objects only. */
149
+ export declare function fixPrompt(gap: GapCase, attempts?: FixAttempt[]): string;
150
+ /** The fix objects in a reply: a JSON array (or { "fixes": [...] }), in a code fence or not. Throws a sentence otherwise. */
151
+ export declare function parseFixReply(text: string): unknown[];
152
+ /** A status fix for a component that did not converge: what the loop proposes after its last round. */
153
+ export declare function statusFixFor(gap: GapCase, existingIds?: string[]): FigmaFix;
@@ -0,0 +1,103 @@
1
+ import { type BridgeConfig } from './config';
2
+ import { type FixAttempt, type FixVerdict, type GapCaseForInput, type TriedVariant } from './figma-fix-loop';
3
+ import type { FigmaFix } from './figma-fixes';
4
+ import type { GapCase } from './gap-case';
5
+ /** Where `bridge specs --measure` writes, from the repo root. */
6
+ export declare const DEFAULT_MEASURED_SPECS_FILE = "measured-specs.json";
7
+ /** The label every figma-fixes.json change carries, next to drafted-by-claude. */
8
+ export declare const FIGMA_FIXES_LABEL = "figma-fixes";
9
+ /** Asks Claude one prompt, returns the reply text. Runners pass the team's key; tests pass a fake. */
10
+ export type FixAsk = (prompt: string) => Promise<string>;
11
+ /** Where a loop stands between rounds: plain JSON, so a server can keep it between requests. */
12
+ export interface FixLoopState {
13
+ component: string;
14
+ /** Rounds done. */
15
+ round: number;
16
+ attempts: FixAttempt[];
17
+ /** The closest fixes so far (verdict closer), with their score and the referee's summary. */
18
+ best?: {
19
+ fixes: unknown[];
20
+ score: number;
21
+ summary: string;
22
+ } | undefined;
23
+ /** No round left, or every difference resolved. */
24
+ done: boolean;
25
+ }
26
+ export interface FixLoopRound {
27
+ round: number;
28
+ fixes: unknown[];
29
+ /** invalid: the fixes broke the schema; undefined: no fixes could be read from the reply. */
30
+ verdict?: FixVerdict | 'invalid' | undefined;
31
+ summary: string;
32
+ }
33
+ export declare const startFixLoop: (component: string) => FixLoopState;
34
+ /**
35
+ * One round: Claude proposes fixes from the gap case and what the earlier rounds gave, the values referee tries them,
36
+ * and the state keeps the closest. `ask` may throw (the network, the key): the state is then unchanged.
37
+ */
38
+ export declare function fixLoopRound(input: GapCaseForInput, state: FixLoopState, ask: FixAsk, gap?: GapCase): Promise<{
39
+ state: FixLoopState;
40
+ round: FixLoopRound;
41
+ }>;
42
+ /** What the loop proposes once done: the closest fixes, or a status fix (Differs, with the reason) when none came closer. */
43
+ export declare function fixLoopOutcome(gap: GapCase, state: FixLoopState, existingIds?: string[]): {
44
+ kind: 'fixes' | 'status';
45
+ fixes: unknown[];
46
+ summary: string;
47
+ };
48
+ /** The branch a component's fixes go on (a number is added when it is taken): bridge/figma-fixes-text-input. */
49
+ export declare const figmaFixesBranchStem: (config: BridgeConfig, component: string) => string;
50
+ export interface FigmaFixesProposalInput extends GapCaseForInput {
51
+ /** The fixes to propose (fix objects; the repo's fixes with the same ids are replaced). */
52
+ proposed: unknown[];
53
+ /** One or two sentences a reviewer reads first. */
54
+ summary: string;
55
+ config: BridgeConfig;
56
+ /** figma-fixes.json path in the repo. */
57
+ fixesPath: string;
58
+ /** Who drafted it, for the body and the commit: "Bridge MCP (source: mcp)". */
59
+ tool: string;
60
+ /** The `source:` label: mcp, cli, studio. */
61
+ source: string;
62
+ }
63
+ export interface FigmaFixesReport {
64
+ component: string;
65
+ verdict: FixVerdict;
66
+ summary: string;
67
+ variants: TriedVariant[];
68
+ fixes: string[];
69
+ tier: number;
70
+ prTitle: string;
71
+ }
72
+ export type FigmaFixesProposal = {
73
+ status: 'blocked';
74
+ verdict: FixVerdict;
75
+ why: string;
76
+ detail: string;
77
+ component: string;
78
+ } | {
79
+ status: 'ready';
80
+ report: FigmaFixesReport;
81
+ /** The accepted fixes as validated. */
82
+ fixes: FigmaFix[];
83
+ labels: string[];
84
+ prBody: string;
85
+ fileContents: {
86
+ path: string;
87
+ content: string;
88
+ }[];
89
+ title: string;
90
+ message: string;
91
+ /** Tier 2 and above open as a draft. */
92
+ draft: boolean;
93
+ /** The default branch it targets. */
94
+ base: string;
95
+ branchStem: string;
96
+ };
97
+ /**
98
+ * The change for review of proposed fixes. The values referee runs again first: a fix that moves the component further
99
+ * from the code is refused, and part or story fixes that move nothing are refused (propose a status fix), unless they
100
+ * size, place or lay out a part: the values referee can't judge layout, Check fidelity decides. Throws a sentence when
101
+ * the fixes are invalid or the summary is not one.
102
+ */
103
+ export declare function figmaFixesProposal(input: FigmaFixesProposalInput): FigmaFixesProposal;
@@ -0,0 +1,169 @@
1
+ import { type SpecField, type SpecLayout, type SpecValue } from './component-spec';
2
+ import type { MeasuredSpecsComponent, MeasuredSpecsFile, MeasuredSpecsStory } from './measured-spec';
3
+ import { type GapState } from './gap-case';
4
+ export declare const FIGMA_FIXES_VERSION = 1;
5
+ /** Where the fixes live in the team's repo when nothing else is said. */
6
+ export declare const DEFAULT_FIGMA_FIXES_FILE = "figma-fixes.json";
7
+ export type FixArgValue = string | number | boolean;
8
+ /** Which variants a fix applies to: stories whose props (args over defaults, plus overrides) include `args`, in `state`. */
9
+ export interface FixWhere {
10
+ args?: Record<string, FixArgValue> | undefined;
11
+ state?: GapState | undefined;
12
+ }
13
+ interface FixBase {
14
+ /** Stable and unique in the file: lower case, digits and dashes ("toggle-knob-radius"). Provenance names it. */
15
+ id: string;
16
+ /** The Figma set: the last segment of the stories' title ("Toggle"), case ignored. */
17
+ component: string;
18
+ /** Why, in plain words: read in review and shown where the fix applies. */
19
+ reason: string;
20
+ /** Who wrote it: "claude" or a GitHub login. */
21
+ by?: string | undefined;
22
+ /** Only these variants. Absent: every story of the component. */
23
+ where?: FixWhere | undefined;
24
+ }
25
+ export type FixSize = 'hug' | 'fill' | 'fixed' | number;
26
+ export type FixPrimary = SpecLayout['primary'];
27
+ export type FixCounter = SpecLayout['counter'];
28
+ export type FixPin = {
29
+ top?: number;
30
+ right?: number;
31
+ bottom?: number;
32
+ left?: number;
33
+ };
34
+ /** (a) A spec patch on one part of the measured render. */
35
+ export interface PartFix extends FixBase {
36
+ kind: 'part';
37
+ /** Part id ("cds--toggle-switch", "root"), or a CSS class with a dot (".cds--toggle__switch"): every part with it. */
38
+ part: string;
39
+ /** Field values: a token path, a px number, null (no paint, colors only), or "full" for a corner radius (pill). */
40
+ set?: Partial<Record<SpecField, SpecValue | 'full'>> | undefined;
41
+ /** fixed: the measured size (or this number, px), hug: its content, fill: its container. */
42
+ width?: FixSize | undefined;
43
+ height?: FixSize | undefined;
44
+ /** Auto layout of a frame part. */
45
+ direction?: SpecLayout['direction'] | undefined;
46
+ align?: {
47
+ primary?: FixPrimary | undefined;
48
+ counter?: FixCounter | undefined;
49
+ } | undefined;
50
+ /** true: over its parent, filling it (out of auto layout). An object: pinned at these offsets (px). false: back in the flow. */
51
+ overlay?: boolean | FixPin | undefined;
52
+ /** Not drawn (with everything inside it). */
53
+ hide?: boolean | undefined;
54
+ }
55
+ /** (b) How a story arg becomes a Figma property. */
56
+ export interface AxisFix extends FixBase {
57
+ kind: 'axis';
58
+ /**
59
+ * map: name the property (`property`) and its values (`values`: code value -> Figma value).
60
+ * ignore: the arg is not a property; only stories at its default value are drawn.
61
+ * prune: drop the variants `where` names (a combination code allows that the library never shows).
62
+ */
63
+ action: 'map' | 'ignore' | 'prune';
64
+ arg?: string | undefined;
65
+ property?: string | undefined;
66
+ values?: Record<string, string> | undefined;
67
+ }
68
+ /** (c) The story a variant is drawn from. */
69
+ export interface StoryFix extends FixBase {
70
+ kind: 'story';
71
+ /** The story to use, as measured: its id, and the args and state of the measured entry when there are several. */
72
+ use: {
73
+ story: string;
74
+ args?: Record<string, FixArgValue> | undefined;
75
+ state?: GapState | undefined;
76
+ };
77
+ }
78
+ /** (d) A note, or the status to show with its reason, when a variant will not converge. Never "matches". */
79
+ export interface StatusFix extends FixBase {
80
+ kind: 'status';
81
+ /** Absent: a note only. */
82
+ status?: 'differs' | 'cant-check' | undefined;
83
+ }
84
+ export type FigmaFix = PartFix | AxisFix | StoryFix | StatusFix;
85
+ export interface FigmaFixesFile {
86
+ $schema?: string | undefined;
87
+ version: typeof FIGMA_FIXES_VERSION;
88
+ fixes: FigmaFix[];
89
+ }
90
+ export declare class FigmaFixesError extends Error {
91
+ readonly problems: string[];
92
+ constructor(problems: string[]);
93
+ }
94
+ /**
95
+ * Every problem in a fixes file, in plain words (empty when it is valid): version, unique ids, a component and a reason
96
+ * on every fix, known kinds and keys, and each kind's fields.
97
+ */
98
+ export declare function validateFigmaFixes(data: unknown): string[];
99
+ /** Reads figma-fixes.json (text or parsed JSON); throws FigmaFixesError with every problem. */
100
+ export declare function parseFigmaFixes(input: string | unknown): FigmaFixesFile;
101
+ /** What applyFixes writes on a component entry for the pull: Figma names, dropped args and combinations, status. */
102
+ export interface FigmaHints {
103
+ /** Arg -> Figma property name. */
104
+ properties?: Record<string, string> | undefined;
105
+ /** Arg -> code value -> Figma value. */
106
+ values?: Record<string, Record<string, string>> | undefined;
107
+ /** Args that are not Figma properties. */
108
+ ignored?: string[] | undefined;
109
+ /** Variant combinations dropped. */
110
+ pruned?: FixWhere[] | undefined;
111
+ /** Status to show for the component, or for some variants (`where`), with the reason. */
112
+ status?: {
113
+ status: 'differs' | 'cant-check';
114
+ reason: string;
115
+ fix: string;
116
+ where?: FixWhere | undefined;
117
+ }[] | undefined;
118
+ notes?: {
119
+ text: string;
120
+ fix: string;
121
+ where?: FixWhere | undefined;
122
+ }[] | undefined;
123
+ /** Ids of the fixes applied to this component, in file order. */
124
+ fixes?: string[] | undefined;
125
+ }
126
+ export type FixedComponent = MeasuredSpecsComponent & {
127
+ figma?: FigmaHints | undefined;
128
+ };
129
+ /** A measured story a fix changed: the ids of those fixes. */
130
+ export type FixedStory = MeasuredSpecsStory & {
131
+ fixes?: string[] | undefined;
132
+ };
133
+ export interface AppliedFix {
134
+ id: string;
135
+ kind: FigmaFix['kind'];
136
+ component: string;
137
+ /** applied: it changed what the pull draws. not-found: nothing matched (the code or the measure moved: re-check it). already: the input had it. */
138
+ result: 'applied' | 'not-found' | 'already';
139
+ /** Story entries it changed. */
140
+ stories: number;
141
+ /** Plain words: what it did, or what it did not find. */
142
+ detail: string;
143
+ }
144
+ /** The pull's input with the fixes laid on, and where they came from. */
145
+ export type FixedMeasuredSpecsFile = Omit<MeasuredSpecsFile, 'stories' | 'components'> & {
146
+ stories: FixedStory[];
147
+ components?: FixedComponent[] | undefined;
148
+ /** The fixes run that made this file. */
149
+ figmaFixes?: {
150
+ applied: AppliedFix[];
151
+ } | undefined;
152
+ };
153
+ /** A story is one of `where`'s variants: every arg it names, at that value, and the state. */
154
+ export declare function storyMatchesWhere(s: MeasuredSpecsStory, where: FixWhere | undefined): boolean;
155
+ /**
156
+ * The measured specs the pull reads, with the fixes laid on, and one provenance record per fix. Pure: the input is not
157
+ * changed. Fixes run in file order; a fix whose component, part or story is not in this measure is reported
158
+ * `not-found` (the code or the measure moved: re-check it) and changes nothing. A file that already carries a fix run
159
+ * (`figmaFixes`) keeps those fixes and only runs the new ones.
160
+ */
161
+ export declare function applyFixes(measured: MeasuredSpecsFile | FixedMeasuredSpecsFile, fixes: FigmaFixesFile | FigmaFix[]): {
162
+ file: FixedMeasuredSpecsFile;
163
+ applied: AppliedFix[];
164
+ };
165
+ /** A part fix in a few words, for provenance and review: "toggle switch: cornerRadius full, width fixed". */
166
+ export declare function describePartFix(f: PartFix): string;
167
+ /** One line per fix result, for the CLI and the plugin's pull report. */
168
+ export declare function appliedFixLines(applied: AppliedFix[]): string[];
169
+ export {};
@@ -117,6 +117,7 @@ export declare function figmaChangeOnGitHub(gh: GitHubClient, n: number, opts?:
117
117
  body: string;
118
118
  mergeable: boolean | null;
119
119
  requestedReviewers: string[];
120
+ autoMerge: boolean;
120
121
  };
121
122
  origin: FigmaOrigin;
122
123
  status: FigmaPushStatus;
@@ -127,6 +128,7 @@ export declare function figmaChangeOnGitHub(gh: GitHubClient, n: number, opts?:
127
128
  body: string;
128
129
  mergeable: boolean | null;
129
130
  requestedReviewers: string[];
131
+ autoMerge: boolean;
130
132
  };
131
133
  origin: FigmaOrigin;
132
134
  status: FigmaPushStatus;
@@ -137,12 +139,15 @@ export declare function figmaChangeOnGitHub(gh: GitHubClient, n: number, opts?:
137
139
  brief: string;
138
140
  }>;
139
141
  /**
140
- * Claude wrote the code: the branch is labelled drafted by Claude AI (no longer waiting) and its description says so.
141
- * Refused while the branch only carries the spec, so the label never claims code that is not there.
142
+ * Claude wrote the code: the pull request is marked ready for review, then the branch is labelled drafted-by-claude
143
+ * (no longer waiting) and its description says so. Refused while the branch only carries the spec, so the label never
144
+ * claims code that is not there. Ready goes first: when GitHub refuses it, nothing else changed, so the label never
145
+ * says "drafted" on a branch reviewers cannot see yet. Running it again after a partial failure finishes the job.
142
146
  */
143
147
  export declare function markFigmaChangeDraftedOnGitHub(gh: GitHubClient, n: number): Promise<{
144
148
  status: FigmaPushStatus;
145
149
  files: string[];
150
+ ready: boolean;
146
151
  }>;
147
152
  /** Where each pushed Figma component's library branch stands (by pull request number), for the plugin's Pull. */
148
153
  export declare function figmaPushStatusesOnGitHub(gh: GitHubClient, numbers: number[]): Promise<Map<number, FigmaPushStatus>>;