@bridge-ds/core 0.9.0 → 0.10.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 (35) hide show
  1. package/README.md +27 -1
  2. package/bridge.config.schema.json +23 -0
  3. package/dist/audit/index.js +3 -3
  4. package/dist/config.d.ts +28 -0
  5. package/dist/docs-proposal.d.ts +58 -10
  6. package/dist/figma-push-github.d.ts +143 -0
  7. package/dist/figma-push.d.ts +204 -0
  8. package/dist/github-client.d.ts +6 -1
  9. package/dist/index.d.ts +6 -0
  10. package/dist/index.js +998 -2861
  11. package/dist/index.js.map +1 -1
  12. package/dist/node/git-local.d.ts +15 -0
  13. package/dist/node/index.d.ts +1 -0
  14. package/dist/node/index.js +201 -10
  15. package/dist/node/index.js.map +1 -1
  16. package/dist/node/suggestion-apply-node.d.ts +69 -0
  17. package/dist/{pr-composer-D2SZm7cg.js → prod-branch-BdMPKffE.js} +491 -173
  18. package/dist/prod-branch-BdMPKffE.js.map +1 -0
  19. package/dist/prod-branch-github.d.ts +28 -7
  20. package/dist/prod-branch.d.ts +17 -7
  21. package/dist/{report-DY_peRy4.js → report-DOnKUw8G.js} +2 -2
  22. package/dist/{report-DY_peRy4.js.map → report-DOnKUw8G.js.map} +1 -1
  23. package/dist/suggestion-apply-ByBEYtct.js +4802 -0
  24. package/dist/suggestion-apply-ByBEYtct.js.map +1 -0
  25. package/dist/suggestion-apply.d.ts +150 -0
  26. package/dist/suggestion-review.d.ts +70 -0
  27. package/dist/suggestions-github.d.ts +131 -0
  28. package/dist/suggestions.d.ts +236 -0
  29. package/dist/token-proposal.d.ts +51 -0
  30. package/package.json +1 -1
  31. package/dist/component-spec-BzfBkYRD.js +0 -1122
  32. package/dist/component-spec-BzfBkYRD.js.map +0 -1
  33. package/dist/pr-composer-D2SZm7cg.js.map +0 -1
  34. package/dist/prod-branch-D5Tl87Wf.js +0 -266
  35. package/dist/prod-branch-D5Tl87Wf.js.map +0 -1
package/README.md CHANGED
@@ -36,7 +36,8 @@ also exported as `@bridge-ds/core/bridge.config.schema.json`) for the plugin, th
36
36
  server. Canonical form: `repo`, `tokens` (`root`, `include`, `exclude`, `tiers`, ordered `layers` with `{mode}`
37
37
  placeholders and `optional`), `modes` (name -> `{ values, default, parity }`), `contrast` (`path`, `modes`),
38
38
  `components`, `docs`, `changesets`, `cssVar`, `governance` (`protectedBranches`, `branchPrefix`, `labels`,
39
- `guardrails`, `owners: { design, tech }`). The older flat form (`tokens.primitives`, `modes.brands`...) still loads.
39
+ `guardrails`, `owners: { design, tech }`), `changes` (`"suggest"`, the default, or `"direct"`) and `storybook`
40
+ (`{ url }`, https). The older flat form (`tokens.primitives`, `modes.brands`...) still loads.
40
41
 
41
42
  ```ts
42
43
  const CONFIG_FILE = 'bridge.config.json';
@@ -50,10 +51,13 @@ interface BridgeConfig {
50
51
  docs; changesets: { dir; package } | undefined; changeset?: { package };
51
52
  cssVar: { prefix; separator };
52
53
  governance: { protectedBranches; branchPrefix; labels; guardrails; owners: { design: string[]; tech: string[] } };
54
+ changes: 'suggest' | 'direct'; // how changes reach the code (default 'suggest')
55
+ storybook?: { url: string };
53
56
  }
54
57
  function defineConfig(input): BridgeConfig; function loadConfig(source): BridgeConfig; // ConfigError { problems }
55
58
  function toConfigJson(config): object; // canonical JSON (what `bridge init` writes)
56
59
  function protectedBranchesOf(config); function baseBranchOf(config); function tokensPath(config, rel?);
60
+ function changesMode(config): 'suggest' | 'direct'; function storybookUrl(config): string | undefined; // storybook.url, else previews.production.storybook
57
61
  ```
58
62
 
59
63
  ## governance
@@ -257,6 +261,28 @@ loadRepoFiles(client, commitSha, fallbackTokensPath): Promise<{ files; contrastC
257
261
  Reads and content-addressed writes retry on network errors, 5xx and secondary rate limits; errors
258
262
  become `GitHubError { status, message, detail }` with plain-language messages.
259
263
 
264
+ ## suggestions (suggestions.ts, suggestion-review.ts, suggestions-github.ts)
265
+
266
+ A change filed as a GitHub issue instead of a branch, so an owner needs only Issues write. Readable Markdown, then a
267
+ collapsed JSON payload (`bridge.suggestion/1`) with the sha256 of its canonical form in a marker; token changes are
268
+ edits (paths and values), other files whole contents with git blob shas before and after. Contents that do not fit
269
+ the body (65,536 characters) go to continuation comments, 8 at most. Reviews are comments bound to the payload sha
270
+ and counted only from the configured owners.
271
+
272
+ ```ts
273
+ composeSuggestionIssue(payload, { labels?, assignees?, state?, details? }): { title; body; labels; assignees; parts: string[]; sha };
274
+ parseSuggestionIssue(body, { comments?, author? }): { ok: true; payload; sha; markedSha; edited; parts } | { ok: false; reason; message };
275
+ suggestionPayloadSha(payload); canonicalJson(value); sha256Hex(text); gitBlobSha(text);
276
+ suggestionLabels(payload, { state?, labels? }); suggestionIssueTitle(payload); suggestionStatus({ state, stateReason?, linkedPrs?, review?, applied?, outdated? });
277
+ suggestionPathProblems(payload, config): string[]; unsafePathProblem(path): string | null;
278
+ summarizeSuggestionApprovals(comments, { owners, rights?, teams? }, { payloadSha, author? }); suggestionReviewState(tier, approvals);
279
+ composeSuggestionReviewComment(record); parseSuggestionReviewComment(body); composeSuggestionAppliedComment(record); parseSuggestionAppliedComment(body);
280
+ // GitHub (Issues read and write only)
281
+ createSuggestionOnGitHub(gh, payload, opts); listSuggestionsOnGitHub(gh, { state? }); getSuggestionOnGitHub(gh, n, gov, { outdated? });
282
+ reviewSuggestionOnGitHub(gh, n, { event, role?, message?, payloadSha? }, gov); linkSuggestionPrOnGitHub(gh, n, { pr?, branch?, commit?, payloadSha });
283
+ declineSuggestionOnGitHub(gh, n, reason, by?); syncSuggestionStateLabel(gh, n, labels, status);
284
+ ```
285
+
260
286
  ## history (history.ts, history-github.ts, history-demo.ts)
261
287
 
262
288
  What changed in the tokens, when, who changed it and why, and how to undo it safely.
@@ -650,6 +650,29 @@
650
650
  }
651
651
  }
652
652
  },
653
+ "changes": {
654
+ "enum": [
655
+ "suggest",
656
+ "direct"
657
+ ],
658
+ "default": "suggest",
659
+ "description": "How Bridge sends a change to the code. suggest (default): Bridge files a GitHub issue that holds the exact change (a suggestion); owners approve it and a developer applies it with their own access. direct: Bridge opens branches and pull requests itself, which needs Contents and Pull requests write. This file is a guardrail, so switching modes is a Tier 3 change."
660
+ },
661
+ "storybook": {
662
+ "type": "object",
663
+ "additionalProperties": false,
664
+ "required": [
665
+ "url"
666
+ ],
667
+ "description": "The deployed Storybook Bridge reads the component list and previews from. Absent: previews.production.storybook when it is set.",
668
+ "properties": {
669
+ "url": {
670
+ "type": "string",
671
+ "pattern": "^https://",
672
+ "description": "Root URL of the deployed Storybook (where index.json and iframe.html are served), https only."
673
+ }
674
+ }
675
+ },
653
676
  "visualImpact": {
654
677
  "type": "object",
655
678
  "additionalProperties": false,
@@ -1,6 +1,6 @@
1
- import { i as isAuditable, a as auditFile } from "../report-DY_peRy4.js";
2
- import { A, b, c, C, D, d, F, I, L, e, M, N, f, g, h, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, B, E, G, H, J, K, O, P, Q, R, S, T, U, V, W, X, Y, Z } from "../report-DY_peRy4.js";
3
- import { p as pieceId, a as proposedTokenPath, t as tokenLabel } from "../prod-branch-D5Tl87Wf.js";
1
+ import { i as isAuditable, a as auditFile } from "../report-DOnKUw8G.js";
2
+ import { A, b, c, C, D, d, F, I, L, e, M, N, f, g, h, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, B, E, G, H, J, K, O, P, Q, R, S, T, U, V, W, X, Y, Z } from "../report-DOnKUw8G.js";
3
+ import { p as pieceId, a as proposedTokenPath, t as tokenLabel } from "../prod-branch-BdMPKffE.js";
4
4
  const lines = (text) => text.split(/\r?\n/);
5
5
  function lineBag(text) {
6
6
  const bag = /* @__PURE__ */ new Map();
package/dist/config.d.ts CHANGED
@@ -4,6 +4,19 @@ import { type Rights, type VisualImpactConfig } from './rights';
4
4
  import { type ProductConfig } from './products';
5
5
  /** File name Bridge looks for at the repository root. */
6
6
  export declare const CONFIG_FILE = "bridge.config.json";
7
+ /**
8
+ * How Bridge sends a change to the code. "suggest": a GitHub issue that holds the exact change (suggestions.ts);
9
+ * owners approve it and a developer applies it with their own access. "direct": Bridge opens branches and pull
10
+ * requests itself. Absent from the file: "suggest".
11
+ */
12
+ export type ChangesMode = 'suggest' | 'direct';
13
+ export declare const CHANGES_MODES: readonly ChangesMode[];
14
+ export declare const DEFAULT_CHANGES_MODE: ChangesMode;
15
+ /** The deployed Storybook Bridge reads the component list and previews from. */
16
+ export interface StorybookConfig {
17
+ /** Root URL (where index.json and iframe.html are served), https only, no trailing slash. */
18
+ url: string;
19
+ }
7
20
  export interface RepoRef {
8
21
  owner: string;
9
22
  name: string;
@@ -154,6 +167,10 @@ export interface BridgeConfig {
154
167
  visualImpact?: VisualImpactConfig | undefined;
155
168
  /** Apps that consume the design system, the default one being the library (see products.ts). Default DEFAULT_PRODUCTS. */
156
169
  products: ProductConfig[];
170
+ /** How changes reach the code: suggestions (default) or branches and pull requests. See changesMode(). */
171
+ changes: ChangesMode;
172
+ /** The Storybook set in the file. Read it through storybookUrl(), which falls back to previews.production.storybook. */
173
+ storybook?: StorybookConfig | undefined;
157
174
  }
158
175
  type Partialish<T> = {
159
176
  [K in keyof T]?: T[K] | undefined;
@@ -214,6 +231,10 @@ export interface BridgeConfigInput {
214
231
  integrations?: IntegrationsConfig;
215
232
  visualImpact?: Partial<VisualImpactConfig>;
216
233
  products?: ProductConfig[];
234
+ changes?: ChangesMode;
235
+ storybook?: {
236
+ url: string;
237
+ };
217
238
  }
218
239
  export declare const DEFAULT_TOKEN_PATHS: TokenPaths;
219
240
  export declare const DEFAULT_GOVERNANCE: GovernanceConfig;
@@ -231,6 +252,13 @@ export declare const tokensPath: (config: BridgeConfig, rel?: string) => string;
231
252
  export declare const protectedBranchesOf: (config: BridgeConfig) => string[];
232
253
  /** The base branch proposals target. */
233
254
  export declare const baseBranchOf: (config: BridgeConfig) => string;
255
+ /** How changes reach the code for this repo: the config's mode ("suggest" when the file does not say). */
256
+ export declare const changesMode: (config: Pick<BridgeConfig, "changes"> | null | undefined) => ChangesMode;
257
+ /**
258
+ * The Storybook to read: `storybook.url`, else the production Storybook of `previews` when it is https. Undefined
259
+ * when neither is set. No trailing slash.
260
+ */
261
+ export declare function storybookUrl(config: Pick<BridgeConfig, 'storybook' | 'previews'> | null | undefined): string | undefined;
234
262
  /** Plain-JSON form of a config, in the canonical shape (what `bridge init` writes). */
235
263
  export declare function toConfigJson(config: BridgeConfig, schema?: string): Record<string, unknown>;
236
264
  export {};
@@ -1,6 +1,9 @@
1
+ import type { ChangesMode } from './config';
1
2
  import { type ComponentDocsJson } from './docs-draft';
2
3
  import type { GitHubClient, PullRequestSummary } from './github-client';
4
+ import type { RequestSource } from './requests';
3
5
  import { type RiskResult } from './risk';
6
+ import { type SuggestionPayloadV1 } from './suggestions';
4
7
  /** One component's docs.json, before (null: a new file) and after, at its repo path. */
5
8
  export interface DocsFileEdit {
6
9
  path: string;
@@ -114,13 +117,7 @@ export declare function docsProposal(edits: DocsFileEdit[], opts: {
114
117
  origin?: DocsOrigin | undefined;
115
118
  authorLine?: string | undefined;
116
119
  }): DocsProposal;
117
- /**
118
- * Opens one pull request with the docs edits from the default branch, the Jira key first in the title and the branch
119
- * name (Jira's GitHub integration links them). Returns its number, link and branch. Labels are best effort.
120
- * One open proposal per component: when the component already has one, the edit goes there as a new commit
121
- * (`continued`; `unchanged` when it already says this) instead of a second pull request.
122
- */
123
- export declare function proposeDocsOnGitHub(gh: GitHubClient, edits: DocsFileEdit[], opts: {
120
+ export interface ProposeDocsOptions {
124
121
  base: string;
125
122
  title?: string | undefined;
126
123
  notes?: string | undefined;
@@ -131,16 +128,67 @@ export declare function proposeDocsOnGitHub(gh: GitHubClient, edits: DocsFileEdi
131
128
  commitNote?: string | undefined;
132
129
  /** Who wrote the text (docsAuthorLine). Without it, the origin's footer, never "no AI" over a Claude draft. */
133
130
  authorLine?: string | undefined;
134
- /** Default true: continue the component's open proposal. False always opens a new one. */
131
+ /** Default true: continue the component's open proposal. False always opens a new one. Direct mode only. */
135
132
  continueOpen?: boolean | undefined;
136
- }): Promise<{
133
+ /**
134
+ * "direct" (default here, so existing callers keep their behaviour): a branch and a pull request. "suggest": a
135
+ * suggestion issue with the cleaned docs, no branch. Callers pass changesMode(config).
136
+ */
137
+ mode?: ChangesMode | undefined;
138
+ /** Suggest mode: where it was sent from (default from the origin: Studio, or MCP) and who sent it. */
139
+ source?: RequestSource | undefined;
140
+ suggestedBy?: string | undefined;
141
+ /** Suggest mode: commit sha of `base`, when the caller already read it. */
142
+ baseSha?: string | undefined;
143
+ }
144
+ export interface ProposedDocs {
145
+ status: 'opened' | 'continued';
137
146
  number: number;
138
147
  url: string;
139
148
  branch: string;
140
149
  title: string;
141
150
  continued: boolean;
142
151
  unchanged: boolean;
143
- }>;
152
+ }
153
+ export interface SuggestedDocs {
154
+ status: 'suggested';
155
+ /** The suggestion issue. */
156
+ number: number;
157
+ url: string;
158
+ title: string;
159
+ /** sha256 of the payload: what approvals bind to. */
160
+ sha: string;
161
+ warnings: string[];
162
+ }
163
+ /**
164
+ * The suggestion payload (bridge.suggestion/1, kind docs) of a set of docs edits: the cleaned docs object of each
165
+ * component (re-written at apply time as a minimal diff of the current file) and the patch changeset. Pure.
166
+ */
167
+ export declare function docsSuggestionPayload(edits: DocsFileEdit[], opts: {
168
+ base: {
169
+ ref: string;
170
+ sha: string;
171
+ };
172
+ title?: string | undefined;
173
+ notes?: string | undefined;
174
+ date?: Date | undefined;
175
+ origin?: DocsOrigin | undefined;
176
+ source?: RequestSource | undefined;
177
+ suggestedBy?: string | undefined;
178
+ packageName?: string | undefined;
179
+ changesetDir?: string | undefined;
180
+ }): SuggestionPayloadV1;
181
+ /**
182
+ * Opens one pull request with the docs edits from the default branch, the Jira key first in the title and the branch
183
+ * name (Jira's GitHub integration links them). Returns its number, link and branch. Labels are best effort.
184
+ * One open proposal per component: when the component already has one, the edit goes there as a new commit
185
+ * (`continued`; `unchanged` when it already says this) instead of a second pull request.
186
+ * In suggest mode (`mode: 'suggest'`) it files a suggestion instead: no branch, no pull request (`status: 'suggested'`).
187
+ */
188
+ export declare function proposeDocsOnGitHub(gh: GitHubClient, edits: DocsFileEdit[], opts: ProposeDocsOptions & {
189
+ mode?: 'direct' | undefined;
190
+ }): Promise<ProposedDocs>;
191
+ export declare function proposeDocsOnGitHub(gh: GitHubClient, edits: DocsFileEdit[], opts: ProposeDocsOptions): Promise<ProposedDocs | SuggestedDocs>;
144
192
  /**
145
193
  * Rewrites one file on an open branch in one commit (`next` gets the current text). Returns the commit sha, or ''
146
194
  * when nothing changed. The Library editor saves an edited docs.json and accepts a Claude draft this way.
@@ -0,0 +1,143 @@
1
+ import type { ChangesMode } from './config';
2
+ import { type GitHubClient } from './github-client';
3
+ import { type SuggestionPayloadV1 } from './suggestions';
4
+ import type { TokenEdit } from './token-edits';
5
+ import { type ComponentPushDiff, type ComponentPushPlan, type FigmaOrigin, type FigmaPushStatus, type PushTokenContext } from './figma-push';
6
+ /**
7
+ * Opens the token change of an exact push: the caller plans it on the library's token files (planTokenProposal). In
8
+ * suggest mode (`mode`), it files a suggestion instead (suggestTokenProposalOnGitHub with `links: { figma }`) and
9
+ * returns its result: a result with `status: 'suggested'` is reported as a suggestion.
10
+ */
11
+ export type FigmaTokenWriter = (input: {
12
+ edits: TokenEdit[];
13
+ title: string;
14
+ summary: string;
15
+ bodyExtra: string[];
16
+ mode: ChangesMode;
17
+ /** The Figma component, for the suggestion's links. */
18
+ figma: {
19
+ fileKey?: string | undefined;
20
+ nodeId?: string | undefined;
21
+ url?: string | undefined;
22
+ };
23
+ }) => Promise<{
24
+ number: number;
25
+ url: string;
26
+ branch: string;
27
+ status?: undefined;
28
+ } | {
29
+ status: 'suggested';
30
+ number: number;
31
+ url: string;
32
+ }>;
33
+ export interface OpenComponentPushInput {
34
+ diff: ComponentPushDiff;
35
+ /** Token facts to route it (planComponentPush), or the route already planned with every component of the file. */
36
+ tokens?: PushTokenContext | undefined;
37
+ plan?: ComponentPushPlan | undefined;
38
+ /** Branch library work starts from (the default branch). */
39
+ base: string;
40
+ /** governance.branchPrefix ("bridge/"). */
41
+ branchPrefix: string;
42
+ /** Labels the repo adds to every Bridge branch (governance.labels), before the Figma ones. */
43
+ labels?: string[] | undefined;
44
+ /** The token change of an exact push (planTokenProposal + writeTokenProposalOnGitHub where the token files are read). */
45
+ writeTokens?: FigmaTokenWriter | undefined;
46
+ /** "the DS Bridge Figma plugin", "Bridge MCP". */
47
+ tool?: string | undefined;
48
+ dryRun?: boolean | undefined;
49
+ /**
50
+ * "direct" (default here, so existing callers keep their behaviour): a branch and a pull request. "suggest": a
51
+ * suggestion issue (a token change, or a component spec for Claude), no branch. Callers pass changesMode(config).
52
+ */
53
+ mode?: ChangesMode | undefined;
54
+ /** Who sends it (a GitHub login), recorded on a suggestion. Defaults to the diff's requestedBy. */
55
+ suggestedBy?: string | undefined;
56
+ /** Commit sha of `base`, when the caller already read it (a component spec suggestion records it). */
57
+ baseSha?: string | undefined;
58
+ }
59
+ export interface OpenedComponentPush {
60
+ status: 'opened' | 'suggested' | 'dry-run' | 'not-sent';
61
+ plan: ComponentPushPlan;
62
+ /** The library branch: a token change or a Claude draft branch (a draft pull request). Null when suggested. */
63
+ link: {
64
+ route: 'token-change' | 'claude-draft';
65
+ number: number;
66
+ url: string;
67
+ branch: string;
68
+ } | null;
69
+ /** The suggestion issue (status 'suggested'): a token change, or a component spec Claude drafts the code from. */
70
+ suggestion?: {
71
+ route: 'token-change' | 'claude-draft';
72
+ number: number;
73
+ url: string;
74
+ } | undefined;
75
+ }
76
+ /** The suggestion payload (bridge.suggestion/1, kind component-spec) of a push that needs code. Pure. */
77
+ export declare function componentSpecSuggestionPayload(d: ComponentPushDiff, plan: Pick<ComponentPushPlan, 'edits' | 'reasons' | 'issues'>, ctx: {
78
+ base: {
79
+ ref: string;
80
+ sha: string;
81
+ };
82
+ suggestedBy?: string | undefined;
83
+ createdAt?: string | undefined;
84
+ componentsDir?: string | undefined;
85
+ }): SuggestionPayloadV1;
86
+ /**
87
+ * Opens the library branch of a component change from Figma, linked to its origin (the Figma component): an exact token
88
+ * swap opens a token change (the shared proposal write: contrast gate, tier, changeset); anything that needs code opens
89
+ * a draft pull request carrying the spec (`.bridge/figma/<slug>.json`), labelled waiting for Claude. A manual change
90
+ * opens nothing: its issues say what to do in Figma.
91
+ * In suggest mode: the token swap is a token suggestion (through `writeTokens`), the code change a component spec
92
+ * suggestion (the spec inside the payload, labelled from Figma and waiting for Claude). Issues write only.
93
+ */
94
+ export declare function openComponentPushOnGitHub(gh: GitHubClient, input: OpenComponentPushInput): Promise<OpenedComponentPush>;
95
+ export interface FigmaChangeRow {
96
+ number: number;
97
+ title: string;
98
+ url: string;
99
+ branch: string;
100
+ origin: FigmaOrigin;
101
+ status: FigmaPushStatus;
102
+ }
103
+ /** Library branches opened from Figma components (label from-figma), newest first, with where each stands. */
104
+ export declare function listFigmaChangesOnGitHub(gh: GitHubClient, opts?: {
105
+ state?: 'open' | 'closed' | 'all';
106
+ }): Promise<FigmaChangeRow[]>;
107
+ /** One library branch from Figma, its spec and Claude's brief to draft the code. */
108
+ export declare function figmaChangeOnGitHub(gh: GitHubClient, n: number, opts?: {
109
+ componentsDir?: string | undefined;
110
+ }): Promise<{
111
+ pull: import("./github-client").PullRequest & import("./github-client").PullRequestSummary & {
112
+ body: string;
113
+ mergeable: boolean | null;
114
+ requestedReviewers: string[];
115
+ };
116
+ origin: FigmaOrigin;
117
+ status: FigmaPushStatus;
118
+ spec: undefined;
119
+ brief: undefined;
120
+ } | {
121
+ pull: import("./github-client").PullRequest & import("./github-client").PullRequestSummary & {
122
+ body: string;
123
+ mergeable: boolean | null;
124
+ requestedReviewers: string[];
125
+ };
126
+ origin: FigmaOrigin;
127
+ status: FigmaPushStatus;
128
+ spec: ComponentPushDiff & {
129
+ tokenEdits?: TokenEdit[];
130
+ reasons?: string[];
131
+ };
132
+ brief: string;
133
+ }>;
134
+ /**
135
+ * Claude wrote the code: the branch is labelled drafted by Claude AI (no longer waiting) and its description says so.
136
+ * Refused while the branch only carries the spec, so the label never claims code that is not there.
137
+ */
138
+ export declare function markFigmaChangeDraftedOnGitHub(gh: GitHubClient, n: number): Promise<{
139
+ status: FigmaPushStatus;
140
+ files: string[];
141
+ }>;
142
+ /** Where each pushed Figma component's library branch stands (by pull request number), for the plugin's Pull. */
143
+ export declare function figmaPushStatusesOnGitHub(gh: GitHubClient, numbers: number[]): Promise<Map<number, FigmaPushStatus>>;
@@ -0,0 +1,204 @@
1
+ import { type ComponentSpecsFile } from './component-spec';
2
+ import { type RequestChange, type RequestKind, type RequestSource } from './requests';
3
+ import type { TokenEdit } from './token-edits';
4
+ import type { ContrastPair } from './wcag';
5
+ /** Where a component change goes: a token change (exact), a library branch Claude drafts, or nowhere yet (manual). */
6
+ export type PushRoute = 'token-change' | 'claude-draft' | 'manual';
7
+ /** A token swap seen in Figma: in these variants, one property moved from one variable to another (token paths). */
8
+ export interface Rebind {
9
+ /** Property family, as the scan names it: fill, stroke, radius, padding, gap, size, typography... */
10
+ kind: string;
11
+ /** Variant names ("Size=md, Tone=primary") where it moved. */
12
+ variants: string[];
13
+ /** Every variant of the component moved the same way. */
14
+ allVariants: boolean;
15
+ before: string[];
16
+ after: string[];
17
+ }
18
+ /** An edit the scan cannot read with confidence, and what the designer does about it. */
19
+ export interface PushIssue {
20
+ what: string;
21
+ todo: string;
22
+ }
23
+ /**
24
+ * A component change from Figma, structured: what the designer sees in the Push tab, and what the library branch
25
+ * carries as its spec (the JSON Claude reads to write the code).
26
+ */
27
+ export interface ComponentPushDiff {
28
+ v: 1;
29
+ /** Coded name ("Button"), or the proposed name of a new component ("DatePicker"). */
30
+ component: string;
31
+ /** Name of the set in Figma ("Forms / Date picker"). */
32
+ figmaName: string;
33
+ /** The component has a folder in code. */
34
+ coded: boolean;
35
+ kind: RequestKind;
36
+ /** One line, sentence case: "Add a lg size". */
37
+ title: string;
38
+ summary: string;
39
+ /** What changes, in one sentence. */
40
+ line?: string | undefined;
41
+ /** The rows of the change, as the Push tab lists them. */
42
+ changes: RequestChange[];
43
+ /** Token swaps, for the exact route. */
44
+ rebinds: Rebind[];
45
+ /** Something only code can carry: a variant, a property, layers, a swapped instance, a new component. */
46
+ needsCode: boolean;
47
+ /** Edits the scan cannot read with confidence: listed with what to do, never sent. */
48
+ unknown: PushIssue[];
49
+ figma?: {
50
+ fileKey?: string | undefined;
51
+ nodeId?: string | undefined;
52
+ nodeName?: string | undefined;
53
+ url?: string | undefined;
54
+ } | undefined;
55
+ /** The component as Figma has it now: variant axes, properties, bound variables (token paths) per variant. */
56
+ variantAxes: Record<string, string[]>;
57
+ properties: {
58
+ name: string;
59
+ type: string;
60
+ default: string;
61
+ }[];
62
+ bindings: Record<string, Record<string, string[]>>;
63
+ /** Layers of each variant, in order, as words ("instance Icon/Pencil, \"Icon\""). */
64
+ anatomy: Record<string, string[]>;
65
+ /** What code gave Figma at the last pull, when known. */
66
+ before?: {
67
+ variantAxes: Record<string, string[]>;
68
+ properties: {
69
+ name: string;
70
+ type: string;
71
+ default: string;
72
+ }[];
73
+ } | undefined;
74
+ /** Screenshot URLs (Figma exports on the requests branch). */
75
+ images?: string[] | undefined;
76
+ source: RequestSource;
77
+ requestedBy?: string | undefined;
78
+ }
79
+ /** What the plan needs to know about tokens, from wherever the caller reads them (the plugin, the MCP server). */
80
+ export interface PushTokenContext {
81
+ /** Tier of a token path ("component", "semantic"...), undefined when the library has no such token. */
82
+ tierOf(path: string): string | undefined;
83
+ /**
84
+ * Where else this token paints: variants of the same component outside the swap ("Size=sm, Variant=primary"), and
85
+ * other components ("IconButton"). Empty lists: nowhere else.
86
+ */
87
+ usedElsewhere(path: string, rebind: Rebind): {
88
+ variants: string[];
89
+ components: string[];
90
+ };
91
+ }
92
+ export interface ComponentPushPlan {
93
+ route: PushRoute;
94
+ /** One sentence in designer words: what happens when it is sent. */
95
+ said: string;
96
+ /** Exact token edits: the whole change (token-change), or the part Claude makes too (claude-draft). */
97
+ edits: TokenEdit[];
98
+ /** Why a token swap needs Claude rather than a token change, one sentence each. */
99
+ reasons: string[];
100
+ /** Listed with what to do, never sent. */
101
+ issues: PushIssue[];
102
+ /** Other components an exact token change also repaints (they share the component token): said, for review. */
103
+ alsoChanges: string[];
104
+ }
105
+ /**
106
+ * How a component change from Figma goes back to the library. A token swap is exact when the property was painted by a
107
+ * component token (one variable before, one after), that token paints nothing else, and the new variable is a token
108
+ * the library has: the token then points to the new variable. Anything else that needs code is drafted by Claude AI;
109
+ * what cannot be read with confidence is listed with what to do.
110
+ */
111
+ export declare function planComponentPush(d: ComponentPushDiff, ctx: PushTokenContext): ComponentPushPlan;
112
+ /**
113
+ * Where else a token paints, from component-specs.json (what code gives each component): the variants of `component`
114
+ * outside the swap, and the other components. For tools that read the code, not a Figma file (the MCP server, the CLI).
115
+ */
116
+ export declare function specsUsage(specs: Pick<ComponentSpecsFile, 'components'>, component: string): PushTokenContext['usedElsewhere'];
117
+ /**
118
+ * The contrast gate for component token swaps. contrast.checks.json lists semantic pairs (action text on action bg);
119
+ * a component token that aliases one side of a pair is checked in its place, against the component token that aliases
120
+ * the other side (button.primary.text on button.primary.bg), else against that other side itself. So pointing
121
+ * button.primary.bg to a pale variable fails the same way the semantic pair would.
122
+ */
123
+ export declare function componentContrastPairs(edits: readonly Pick<TokenEdit, 'path'>[], pairs: readonly ContrastPair[], tokens: {
124
+ aliasOf(path: string): string | undefined;
125
+ paths: readonly string[];
126
+ }): ContrastPair[];
127
+ /** Every library branch opened from a Figma component carries this label. */
128
+ export declare const FROM_FIGMA_LABEL = "from-figma";
129
+ /** A Claude draft branch that carries the spec only: no code yet. */
130
+ export declare const WAITING_FOR_CLAUDE_LABEL = "waiting-for-claude";
131
+ /** A Claude draft branch once Claude wrote the code: a person reviews it. */
132
+ export declare const DRAFTED_BY_CLAUDE_LABEL = "drafted-by-claude";
133
+ /** Where a Claude draft branch keeps the spec it was opened with. */
134
+ export declare const FIGMA_SPEC_DIR = ".bridge/figma";
135
+ export type FigmaOriginStatus = 'spec' | 'drafted';
136
+ /** What the library branch remembers about where it came from: the Figma component and how it was routed. */
137
+ export interface FigmaOrigin {
138
+ component: string;
139
+ figmaName: string;
140
+ route: Exclude<PushRoute, 'manual'>;
141
+ kind: RequestKind;
142
+ nodeId?: string | undefined;
143
+ fileKey?: string | undefined;
144
+ url?: string | undefined;
145
+ /** Claude draft branches: the spec file, and whether Claude wrote the code yet. */
146
+ spec?: string | undefined;
147
+ status?: FigmaOriginStatus | undefined;
148
+ requestedBy?: string | undefined;
149
+ }
150
+ export declare function parseFigmaOrigin(body: string | null | undefined): FigmaOrigin | undefined;
151
+ /** The "From Figma" lines people read on GitHub, then the hidden block tools read. */
152
+ export declare function figmaOriginSection(o: FigmaOrigin): string;
153
+ /** The body with the "From Figma" section first (replacing an earlier one). */
154
+ export declare function withFigmaOrigin(body: string | null | undefined, o: FigmaOrigin): string;
155
+ export declare const figmaSpecSlug: (d: Pick<ComponentPushDiff, "component" | "title">) => string;
156
+ /** The spec file of a Claude draft branch. */
157
+ export declare const figmaSpecPath: (d: Pick<ComponentPushDiff, "component" | "title">) => string;
158
+ /** The structured diff as the spec file stores it (stable key order, two-space JSON). */
159
+ export declare function formatFigmaSpec(d: ComponentPushDiff, plan: Pick<ComponentPushPlan, 'edits' | 'reasons' | 'issues'>): string;
160
+ /** Reads a spec file back; undefined when it is not one. */
161
+ export declare function parseFigmaSpec(text: string | null | undefined): (ComponentPushDiff & {
162
+ tokenEdits?: TokenEdit[];
163
+ reasons?: string[];
164
+ }) | undefined;
165
+ /**
166
+ * What Claude gets to draft the code on the branch (MCP `bridge_get_figma_change`, CLI `bridge figma show`): the change,
167
+ * the spec, the exact token swaps to make too, the rules, and how to say it is drafted.
168
+ */
169
+ export declare function figmaDraftBrief(d: ComponentPushDiff & {
170
+ tokenEdits?: TokenEdit[] | undefined;
171
+ reasons?: string[] | undefined;
172
+ }, ctx: {
173
+ pr: number;
174
+ branch: string;
175
+ specPath: string;
176
+ componentsDir?: string | undefined;
177
+ agentsFile?: string | undefined;
178
+ } | {
179
+ suggestion: true;
180
+ specPath: string;
181
+ componentsDir?: string | undefined;
182
+ agentsFile?: string | undefined;
183
+ }): string;
184
+ /** The pull request body of a Claude draft branch: the origin, the change, the rows and the spec. */
185
+ export declare function figmaDraftBody(d: ComponentPushDiff, plan: Pick<ComponentPushPlan, 'edits' | 'reasons' | 'issues'>, o: FigmaOrigin): string;
186
+ /** The title of a library branch opened from Figma: "Button: add a lg size". */
187
+ export declare const figmaBranchTitle: (d: Pick<ComponentPushDiff, "component" | "title">) => string;
188
+ export type FigmaPushStatusKey = 'waiting-for-claude' | 'drafted' | 'in-review' | 'shipped' | 'closed';
189
+ export interface FigmaPushStatus {
190
+ key: FigmaPushStatusKey;
191
+ /** "Waiting for Claude AI", "Shipped". */
192
+ label: string;
193
+ /** One sentence for the designer. */
194
+ sentence: string;
195
+ /** The Figma component should be rebuilt from code (the branch shipped or was closed). */
196
+ rebuild: boolean;
197
+ }
198
+ /** Where a library branch opened from Figma stands, from its state and labels. */
199
+ export declare function figmaPushStatus(x: {
200
+ state: string;
201
+ draft?: boolean | undefined;
202
+ labels?: readonly string[] | undefined;
203
+ route?: PushRoute | undefined;
204
+ }): FigmaPushStatus;
@@ -302,12 +302,17 @@ export declare class GitHubClient {
302
302
  /** Best effort: labels need the repo to allow it; failure is not fatal. */
303
303
  addLabels(issueNumber: number, labels: string[]): Promise<boolean>;
304
304
  addComment(issueNumber: number, body: string): Promise<void>;
305
- /** Comments on an issue or pull request, oldest first (first 100: the sticky preview comment is posted early). */
305
+ /**
306
+ * Comments on an issue or pull request, oldest first (first 100: the sticky preview comment is posted early).
307
+ * `authorAssociation` is GitHub's OWNER, MEMBER, COLLABORATOR, CONTRIBUTOR, NONE... (suggestion approvals read it).
308
+ */
306
309
  listIssueComments(issueNumber: number): Promise<{
307
310
  id: number;
308
311
  body: string;
309
312
  author: string;
310
313
  updatedAt: string;
314
+ createdAt: string;
315
+ authorAssociation: string;
311
316
  }[]>;
312
317
  createIssue(input: {
313
318
  title: string;
package/dist/index.d.ts CHANGED
@@ -57,3 +57,9 @@ export * from './products';
57
57
  export * from './prod-branch';
58
58
  export * from './prod-branch-github';
59
59
  export * from './token-proposal';
60
+ export * from './figma-push';
61
+ export * from './figma-push-github';
62
+ export * from './suggestions';
63
+ export * from './suggestion-review';
64
+ export * from './suggestions-github';
65
+ export * from './suggestion-apply';