@bridge-ds/cli 0.6.0 → 0.7.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 (2) hide show
  1. package/dist/index.js +315 -208
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.ts
4
4
  import { existsSync as existsSync6 } from "node:fs";
5
5
  import { readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
6
- import { isAbsolute, join as join6, relative as relative4, resolve as resolve4 } from "node:path";
6
+ import { isAbsolute, join as join6, relative as relative4, resolve as resolve5 } from "node:path";
7
7
 
8
8
  // ../core/src/audit/types.ts
9
9
  var AUDIT_SCHEMA_VERSION = 1;
@@ -2424,6 +2424,8 @@ function productProblems(raw) {
2424
2424
 
2425
2425
  // ../core/src/config.ts
2426
2426
  var CONFIG_FILE = "bridge.config.json";
2427
+ var CHANGES_MODES = ["suggest", "direct"];
2428
+ var DEFAULT_CHANGES_MODE = "suggest";
2427
2429
  var DEFAULT_OVERRIDE = { allowed: [], requireReason: true, minReasonLength: 20 };
2428
2430
  var DEFAULT_TOKEN_PATHS = {
2429
2431
  root: "tokens",
@@ -2563,6 +2565,8 @@ function defineConfig(input) {
2563
2565
  ...g.rights ? { rights: normalizeRights(g.rights) } : {}
2564
2566
  },
2565
2567
  products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
2568
+ changes: input.changes === "direct" ? "direct" : DEFAULT_CHANGES_MODE,
2569
+ ...input.storybook?.url ? { storybook: { url: input.storybook.url.trim().replace(/\/+$/, "") } } : {},
2566
2570
  ...input.audit ? { audit: { ...input.audit } } : {},
2567
2571
  ...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
2568
2572
  ...input.integrations ? { integrations: { ...input.integrations } } : {},
@@ -2685,6 +2689,13 @@ function loadConfig(source) {
2685
2689
  }
2686
2690
  }
2687
2691
  problems.push(...visualImpactProblems(d.visualImpact));
2692
+ if (d.changes !== void 0 && !CHANGES_MODES.includes(d.changes)) problems.push(`"changes" must be "suggest" or "direct" (got ${JSON.stringify(d.changes)})`);
2693
+ const sb = d.storybook;
2694
+ if (sb !== void 0) {
2695
+ if (!isObj(sb) || !isStr(sb.url)) problems.push('"storybook" must be { "url" } with the deployed Storybook address');
2696
+ else if (!/^https:\/\/[^\s/]+/.test(sb.url.trim())) problems.push('"storybook.url" must start with https://, for example https://storybook.example.com');
2697
+ else for (const k of Object.keys(sb)) if (k !== "url") problems.push(`"storybook.${k}" is not a known key`);
2698
+ }
2688
2699
  problems.push(...productProblems(d.products));
2689
2700
  const audit2 = d.audit;
2690
2701
  if (audit2 !== void 0) {
@@ -2739,10 +2750,10 @@ function assertSafeBranch(branch, rules = {}) {
2739
2750
 
2740
2751
  // ../core/src/node/git-local.ts
2741
2752
  function git(root, args, opts = {}) {
2742
- return new Promise((resolve5, reject) => {
2753
+ return new Promise((resolve6, reject) => {
2743
2754
  const child = execFile("git", args, { cwd: root, env: { ...process.env, ...opts.env }, maxBuffer: 32 * 1024 * 1024 }, (err, stdout, stderr) => {
2744
2755
  if (err) reject(new Error(`git ${args.join(" ")}: ${stderr || err.message}`.trim()));
2745
- else resolve5(stdout);
2756
+ else resolve6(stdout);
2746
2757
  });
2747
2758
  if (opts.input !== void 0) child.stdin.end(opts.input);
2748
2759
  });
@@ -2958,93 +2969,6 @@ function parseComponentSpecs(text) {
2958
2969
  return f;
2959
2970
  }
2960
2971
 
2961
- // src/index.ts
2962
- import { fileURLToPath } from "node:url";
2963
-
2964
- // ../visual/src/run.ts
2965
- import { existsSync as existsSync4 } from "node:fs";
2966
- import { mkdir as mkdir3, readFile as readFile3, rm as rm2, unlink, writeFile as writeFile3 } from "node:fs/promises";
2967
- import { tmpdir } from "node:os";
2968
- import { join as join4, relative as relative3, resolve as resolve2 } from "node:path";
2969
-
2970
- // ../core/src/protection.ts
2971
- var PROTECTION_PARTS = ["review", "checks", "history", "deletion"];
2972
- var protectionRulesetName = (defaultBranch) => `Protect the library (${defaultBranch})`;
2973
- function refPattern(pattern) {
2974
- const src = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, ".*");
2975
- return new RegExp(`^${src}$`);
2976
- }
2977
- function rulesetTargetsDefault(ruleset, defaultBranch) {
2978
- if (ruleset.target && ruleset.target !== "branch") return false;
2979
- const ref = `refs/heads/${defaultBranch}`;
2980
- const hit = (p) => p === "~ALL" || p === "~DEFAULT_BRANCH" || p === ref || p === defaultBranch || refPattern(p.startsWith("refs/") ? p : `refs/heads/${p}`).test(ref);
2981
- const cond = ruleset.conditions?.ref_name;
2982
- if (!cond) return false;
2983
- if ((cond.exclude ?? []).some(hit)) return false;
2984
- return (cond.include ?? []).some(hit);
2985
- }
2986
- var count = (v) => typeof v === "number" ? v : 0;
2987
- function rulesetParts(ruleset) {
2988
- const parts = /* @__PURE__ */ new Set();
2989
- if (ruleset.enforcement !== "active") return parts;
2990
- for (const rule of ruleset.rules ?? []) {
2991
- if (rule.type === "deletion") parts.add("deletion");
2992
- if (rule.type === "non_fast_forward") parts.add("history");
2993
- if (rule.type === "pull_request" && count(rule.parameters?.required_approving_review_count) >= 1) parts.add("review");
2994
- if (rule.type === "required_status_checks" && Array.isArray(rule.parameters?.required_status_checks) && rule.parameters.required_status_checks.length > 0) parts.add("checks");
2995
- }
2996
- return parts;
2997
- }
2998
- function classicParts(p) {
2999
- const parts = /* @__PURE__ */ new Set();
3000
- if (count(p.required_pull_request_reviews?.required_approving_review_count) >= 1) parts.add("review");
3001
- if (((p.required_status_checks?.contexts?.length ?? 0) || (p.required_status_checks?.checks?.length ?? 0)) > 0) parts.add("checks");
3002
- if (!p.allow_force_pushes?.enabled) parts.add("history");
3003
- if (!p.allow_deletions?.enabled) parts.add("deletion");
3004
- return parts;
3005
- }
3006
- var rulesetUrl = (r) => r.html_url ?? r._links?.html?.href;
3007
- function protectionState(rulesets, branchProtection, defaultBranch) {
3008
- const bridge = rulesets?.find((r) => r.name === protectionRulesetName(defaultBranch));
3009
- const extra = bridge?.id !== void 0 ? { bridgeRulesetId: bridge.id } : {};
3010
- const covered = /* @__PURE__ */ new Set();
3011
- let url;
3012
- for (const r of rulesets ?? []) {
3013
- if (!rulesetTargetsDefault(r, defaultBranch)) continue;
3014
- const parts = rulesetParts(r);
3015
- if (parts.size && !url) url = rulesetUrl(r);
3016
- parts.forEach((p) => covered.add(p));
3017
- }
3018
- if (branchProtection) classicParts(branchProtection).forEach((p) => covered.add(p));
3019
- const missing = PROTECTION_PARTS.filter((p) => !covered.has(p));
3020
- const withUrl = url ? { url } : {};
3021
- if (!missing.length) return { kind: "protected", missing, ...extra, ...withUrl };
3022
- const unreadable = rulesets == null || branchProtection === void 0;
3023
- if (covered.size === 0) return { kind: unreadable ? "unknown" : "unprotected", missing, ...extra };
3024
- return { kind: "partial", missing, ...extra, ...withUrl };
3025
- }
3026
- var PROTECTION_PART_PHRASE = {
3027
- review: "changes can skip review",
3028
- checks: "checks do not have to pass",
3029
- history: "past changes can be rewritten",
3030
- deletion: "the library can be deleted"
3031
- };
3032
- var joinAnd = (xs) => xs.length <= 1 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
3033
- function protectionSentence(state) {
3034
- switch (state.kind) {
3035
- case "protected":
3036
- return "Protected: every change goes through review.";
3037
- case "unprotected":
3038
- return "Your library can be changed without review.";
3039
- case "partial": {
3040
- const s = joinAnd(state.missing.map((p) => PROTECTION_PART_PHRASE[p]));
3041
- return `Partly protected: ${s}.`;
3042
- }
3043
- default:
3044
- return "Protection status unknown: GitHub did not let this token read it.";
3045
- }
3046
- }
3047
-
3048
2972
  // ../core/src/github-client.ts
3049
2973
  var GitHubError = class extends Error {
3050
2974
  constructor(status, message, detail, info = {}) {
@@ -3423,10 +3347,16 @@ var GitHubClient = class _GitHubClient {
3423
3347
  async addComment(issueNumber2, body) {
3424
3348
  await this.request("POST", `${this.repoPath}/issues/${issueNumber2}/comments`, { body });
3425
3349
  }
3426
- /** Comments on an issue or pull request, oldest first (first 100: the sticky preview comment is posted early). */
3350
+ /**
3351
+ * Comments on an issue or pull request, oldest first (first 100: the sticky preview comment is posted early).
3352
+ * `authorAssociation` is GitHub's OWNER, MEMBER, COLLABORATOR, CONTRIBUTOR, NONE... (suggestion approvals read it).
3353
+ */
3427
3354
  async listIssueComments(issueNumber2) {
3428
- const r = await this.request("GET", `${this.repoPath}/issues/${issueNumber2}/comments?per_page=100`);
3429
- return r.map((c) => ({ id: c.id, body: c.body ?? "", author: c.user?.login ?? "unknown", updatedAt: c.updated_at }));
3355
+ const r = await this.request(
3356
+ "GET",
3357
+ `${this.repoPath}/issues/${issueNumber2}/comments?per_page=100`
3358
+ );
3359
+ return r.map((c) => ({ id: c.id, body: c.body ?? "", author: c.user?.login ?? "unknown", updatedAt: c.updated_at, createdAt: c.created_at ?? c.updated_at, authorAssociation: c.author_association ?? "NONE" }));
3430
3360
  }
3431
3361
  // --- Issues (component change requests) ---------------------------------------------------
3432
3362
  async createIssue(input) {
@@ -3916,6 +3846,148 @@ function toIssue(r) {
3916
3846
  };
3917
3847
  }
3918
3848
 
3849
+ // ../core/src/explorations.ts
3850
+ var EXPLORATION_USE_CASES = [
3851
+ { value: "user-test", label: "Prototype for a user test", hint: "A coded prototype on a product staging branch, with the real components and tokens." },
3852
+ { value: "stakeholder-demo", label: "Stakeholder demo", hint: "Show an idea on real screens before anyone commits to it." },
3853
+ { value: "ab-idea", label: "A/B idea", hint: "Ship a variant to a staging or experiment branch and compare it with what is live." },
3854
+ { value: "terminal", label: "Exploring with Claude Code", hint: "A designer works from the terminal with Claude and the Bridge MCP server on this branch, never on main." },
3855
+ { value: "other", label: "Other", hint: "Anything else worth trying before it becomes a change." }
3856
+ ];
3857
+ var EXPLORATION_STATUS_LABEL = { active: "Running", promoted: "Promoted to review", closed: "Closed" };
3858
+
3859
+ // ../core/src/requests.ts
3860
+ var REQUEST_KINDS = [
3861
+ { value: "new-component", label: "New component", hint: "A component the design system does not have yet." },
3862
+ { value: "new-variant", label: "New variant", hint: "A size, tone or state the component does not have yet." },
3863
+ { value: "prop-change", label: "Option change (prop)", hint: "Add, rename or change an option (a prop in code) or its default." },
3864
+ { value: "structure", label: "Structure change", hint: "A part was added, removed or moved." },
3865
+ { value: "visual", label: "Visual change", hint: "Spacing, radius, layout or icon placement." },
3866
+ { value: "token-binding", label: "Token change", hint: "The component should paint with another token." },
3867
+ { value: "bug", label: "Bug", hint: "It looks or behaves differently from the guidance." },
3868
+ { value: "other", label: "Other", hint: "Anything else." }
3869
+ ];
3870
+ var REQUEST_LABEL = "component-change";
3871
+ function requestIssueTitle(r) {
3872
+ const t = r.title.trim().replace(/\.$/, "");
3873
+ return `${r.component}: ${t.charAt(0).toLowerCase()}${t.slice(1)}`;
3874
+ }
3875
+ var REQUEST_STATUS_LABEL = { requested: "Requested", "in-progress": "In progress", done: "Done", declined: "Declined" };
3876
+ function validateIntegrations(i) {
3877
+ const out = [];
3878
+ if (!i?.jira) return out;
3879
+ if (!/^https:\/\/[^/]+/.test(i.jira.baseUrl ?? "")) out.push("Jira site must start with https://, for example https://acme.atlassian.net.");
3880
+ if (!/^[A-Z][A-Z0-9_]{1,9}$/.test(i.jira.project ?? "")) out.push("Jira project key is 2 to 10 capital letters or digits, like DS.");
3881
+ return out;
3882
+ }
3883
+ function jiraIssueFields(r, jira, issueUrl) {
3884
+ const text = (t) => ({ type: "text", text: t });
3885
+ const para = (...c) => ({ type: "paragraph", content: c });
3886
+ return {
3887
+ project: { key: jira.project },
3888
+ issuetype: { name: jira.issueType ?? "Task" },
3889
+ summary: requestIssueTitle(r),
3890
+ labels: [.../* @__PURE__ */ new Set(["bridge", REQUEST_LABEL, ...jira.labels ?? []])].map((l) => l.replace(/\s+/g, "-")),
3891
+ description: {
3892
+ type: "doc",
3893
+ version: 1,
3894
+ content: [
3895
+ para(text(r.summary.trim() || r.title)),
3896
+ ...r.line?.trim() ? [para(text(r.line.trim()))] : [],
3897
+ ...r.changes.map((c) => para(text(`${c.field}: ${c.before ?? "(none)"} -> ${c.after ?? "(removed)"}`))),
3898
+ para({ type: "text", text: "GitHub request", marks: [{ type: "link", attrs: { href: issueUrl } }] })
3899
+ ]
3900
+ }
3901
+ };
3902
+ }
3903
+
3904
+ // src/index.ts
3905
+ import { fileURLToPath } from "node:url";
3906
+
3907
+ // ../visual/src/run.ts
3908
+ import { existsSync as existsSync4 } from "node:fs";
3909
+ import { mkdir as mkdir3, readFile as readFile3, rm as rm2, unlink, writeFile as writeFile3 } from "node:fs/promises";
3910
+ import { tmpdir } from "node:os";
3911
+ import { join as join4, relative as relative3, resolve as resolve2 } from "node:path";
3912
+
3913
+ // ../core/src/protection.ts
3914
+ var PROTECTION_PARTS = ["review", "checks", "history", "deletion"];
3915
+ var protectionRulesetName = (defaultBranch) => `Protect the library (${defaultBranch})`;
3916
+ function refPattern(pattern) {
3917
+ const src = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, ".*");
3918
+ return new RegExp(`^${src}$`);
3919
+ }
3920
+ function rulesetTargetsDefault(ruleset, defaultBranch) {
3921
+ if (ruleset.target && ruleset.target !== "branch") return false;
3922
+ const ref = `refs/heads/${defaultBranch}`;
3923
+ const hit = (p) => p === "~ALL" || p === "~DEFAULT_BRANCH" || p === ref || p === defaultBranch || refPattern(p.startsWith("refs/") ? p : `refs/heads/${p}`).test(ref);
3924
+ const cond = ruleset.conditions?.ref_name;
3925
+ if (!cond) return false;
3926
+ if ((cond.exclude ?? []).some(hit)) return false;
3927
+ return (cond.include ?? []).some(hit);
3928
+ }
3929
+ var count = (v) => typeof v === "number" ? v : 0;
3930
+ function rulesetParts(ruleset) {
3931
+ const parts = /* @__PURE__ */ new Set();
3932
+ if (ruleset.enforcement !== "active") return parts;
3933
+ for (const rule of ruleset.rules ?? []) {
3934
+ if (rule.type === "deletion") parts.add("deletion");
3935
+ if (rule.type === "non_fast_forward") parts.add("history");
3936
+ if (rule.type === "pull_request" && count(rule.parameters?.required_approving_review_count) >= 1) parts.add("review");
3937
+ if (rule.type === "required_status_checks" && Array.isArray(rule.parameters?.required_status_checks) && rule.parameters.required_status_checks.length > 0) parts.add("checks");
3938
+ }
3939
+ return parts;
3940
+ }
3941
+ function classicParts(p) {
3942
+ const parts = /* @__PURE__ */ new Set();
3943
+ if (count(p.required_pull_request_reviews?.required_approving_review_count) >= 1) parts.add("review");
3944
+ if (((p.required_status_checks?.contexts?.length ?? 0) || (p.required_status_checks?.checks?.length ?? 0)) > 0) parts.add("checks");
3945
+ if (!p.allow_force_pushes?.enabled) parts.add("history");
3946
+ if (!p.allow_deletions?.enabled) parts.add("deletion");
3947
+ return parts;
3948
+ }
3949
+ var rulesetUrl = (r) => r.html_url ?? r._links?.html?.href;
3950
+ function protectionState(rulesets, branchProtection, defaultBranch) {
3951
+ const bridge = rulesets?.find((r) => r.name === protectionRulesetName(defaultBranch));
3952
+ const extra = bridge?.id !== void 0 ? { bridgeRulesetId: bridge.id } : {};
3953
+ const covered = /* @__PURE__ */ new Set();
3954
+ let url;
3955
+ for (const r of rulesets ?? []) {
3956
+ if (!rulesetTargetsDefault(r, defaultBranch)) continue;
3957
+ const parts = rulesetParts(r);
3958
+ if (parts.size && !url) url = rulesetUrl(r);
3959
+ parts.forEach((p) => covered.add(p));
3960
+ }
3961
+ if (branchProtection) classicParts(branchProtection).forEach((p) => covered.add(p));
3962
+ const missing = PROTECTION_PARTS.filter((p) => !covered.has(p));
3963
+ const withUrl = url ? { url } : {};
3964
+ if (!missing.length) return { kind: "protected", missing, ...extra, ...withUrl };
3965
+ const unreadable = rulesets == null || branchProtection === void 0;
3966
+ if (covered.size === 0) return { kind: unreadable ? "unknown" : "unprotected", missing, ...extra };
3967
+ return { kind: "partial", missing, ...extra, ...withUrl };
3968
+ }
3969
+ var PROTECTION_PART_PHRASE = {
3970
+ review: "changes can skip review",
3971
+ checks: "checks do not have to pass",
3972
+ history: "past changes can be rewritten",
3973
+ deletion: "the library can be deleted"
3974
+ };
3975
+ var joinAnd = (xs) => xs.length <= 1 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
3976
+ function protectionSentence(state) {
3977
+ switch (state.kind) {
3978
+ case "protected":
3979
+ return "Protected: every change goes through review.";
3980
+ case "unprotected":
3981
+ return "Your library can be changed without review.";
3982
+ case "partial": {
3983
+ const s = joinAnd(state.missing.map((p) => PROTECTION_PART_PHRASE[p]));
3984
+ return `Partly protected: ${s}.`;
3985
+ }
3986
+ default:
3987
+ return "Protection status unknown: GitHub did not let this token read it.";
3988
+ }
3989
+ }
3990
+
3919
3991
  // ../core/src/protection-github.ts
3920
3992
  async function loadProtection(client, defaultBranch) {
3921
3993
  const rulesets = await client.listRulesets().then((list2) => Promise.all(list2.map((r) => r.enforcement === "disabled" || r.id === void 0 ? r : client.getRuleset(r.id)))).catch(() => null);
@@ -4032,61 +4104,6 @@ function impactedModules(index, input) {
4032
4104
  return { modules, globalHits };
4033
4105
  }
4034
4106
 
4035
- // ../core/src/explorations.ts
4036
- var EXPLORATION_USE_CASES = [
4037
- { value: "user-test", label: "Prototype for a user test", hint: "A coded prototype on a product staging branch, with the real components and tokens." },
4038
- { value: "stakeholder-demo", label: "Stakeholder demo", hint: "Show an idea on real screens before anyone commits to it." },
4039
- { value: "ab-idea", label: "A/B idea", hint: "Ship a variant to a staging or experiment branch and compare it with what is live." },
4040
- { value: "terminal", label: "Exploring with Claude Code", hint: "A designer works from the terminal with Claude and the Bridge MCP server on this branch, never on main." },
4041
- { value: "other", label: "Other", hint: "Anything else worth trying before it becomes a change." }
4042
- ];
4043
- var EXPLORATION_STATUS_LABEL = { active: "Running", promoted: "Promoted to review", closed: "Closed" };
4044
-
4045
- // ../core/src/requests.ts
4046
- var REQUEST_KINDS = [
4047
- { value: "new-component", label: "New component", hint: "A component the design system does not have yet." },
4048
- { value: "new-variant", label: "New variant", hint: "A size, tone or state the component does not have yet." },
4049
- { value: "prop-change", label: "Option change (prop)", hint: "Add, rename or change an option (a prop in code) or its default." },
4050
- { value: "structure", label: "Structure change", hint: "A part was added, removed or moved." },
4051
- { value: "visual", label: "Visual change", hint: "Spacing, radius, layout or icon placement." },
4052
- { value: "token-binding", label: "Token change", hint: "The component should paint with another token." },
4053
- { value: "bug", label: "Bug", hint: "It looks or behaves differently from the guidance." },
4054
- { value: "other", label: "Other", hint: "Anything else." }
4055
- ];
4056
- var REQUEST_LABEL = "component-change";
4057
- function requestIssueTitle(r) {
4058
- const t = r.title.trim().replace(/\.$/, "");
4059
- return `${r.component}: ${t.charAt(0).toLowerCase()}${t.slice(1)}`;
4060
- }
4061
- var REQUEST_STATUS_LABEL = { requested: "Requested", "in-progress": "In progress", done: "Done", declined: "Declined" };
4062
- function validateIntegrations(i) {
4063
- const out = [];
4064
- if (!i?.jira) return out;
4065
- if (!/^https:\/\/[^/]+/.test(i.jira.baseUrl ?? "")) out.push("Jira site must start with https://, for example https://acme.atlassian.net.");
4066
- if (!/^[A-Z][A-Z0-9_]{1,9}$/.test(i.jira.project ?? "")) out.push("Jira project key is 2 to 10 capital letters or digits, like DS.");
4067
- return out;
4068
- }
4069
- function jiraIssueFields(r, jira, issueUrl) {
4070
- const text = (t) => ({ type: "text", text: t });
4071
- const para = (...c) => ({ type: "paragraph", content: c });
4072
- return {
4073
- project: { key: jira.project },
4074
- issuetype: { name: jira.issueType ?? "Task" },
4075
- summary: requestIssueTitle(r),
4076
- labels: [.../* @__PURE__ */ new Set(["bridge", REQUEST_LABEL, ...jira.labels ?? []])].map((l) => l.replace(/\s+/g, "-")),
4077
- description: {
4078
- type: "doc",
4079
- version: 1,
4080
- content: [
4081
- para(text(r.summary.trim() || r.title)),
4082
- ...r.line?.trim() ? [para(text(r.line.trim()))] : [],
4083
- ...r.changes.map((c) => para(text(`${c.field}: ${c.before ?? "(none)"} -> ${c.after ?? "(removed)"}`))),
4084
- para({ type: "text", text: "GitHub request", marks: [{ type: "link", attrs: { href: issueUrl } }] })
4085
- ]
4086
- }
4087
- };
4088
- }
4089
-
4090
4107
  // ../core/src/fidelity-references.ts
4091
4108
  var FIGMA_REFERENCES_DIR = "figma-references";
4092
4109
  var FIGMA_REFERENCES_MANIFEST = `${FIGMA_REFERENCES_DIR}/manifest.json`;
@@ -4310,15 +4327,15 @@ async function shoot(page, r) {
4310
4327
  }
4311
4328
  async function switchStory(page, storyId2) {
4312
4329
  const ok = await page.evaluate(
4313
- (id) => new Promise((resolve5) => {
4330
+ (id) => new Promise((resolve6) => {
4314
4331
  const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
4315
- if (!ch) return resolve5(false);
4332
+ if (!ch) return resolve6(false);
4316
4333
  const events = ["storyRendered", "storyErrored", "storyThrewException", "storyMissing", "playFunctionThrewException"];
4317
4334
  const timer = setTimeout(() => done(), 15e3);
4318
4335
  function done() {
4319
4336
  clearTimeout(timer);
4320
4337
  for (const e of events) ch.off(e, done);
4321
- resolve5(true);
4338
+ resolve6(true);
4322
4339
  }
4323
4340
  for (const e of events) ch.on(e, done);
4324
4341
  window.scrollTo(0, 0);
@@ -4731,12 +4748,12 @@ function serveDir(dir) {
4731
4748
  res.writeHead(200, { "content-type": TYPES[extname(file)] ?? "application/octet-stream", "cache-control": "no-store" });
4732
4749
  createReadStream(file).pipe(res);
4733
4750
  });
4734
- return new Promise((resolve5, reject) => {
4751
+ return new Promise((resolve6, reject) => {
4735
4752
  server.once("error", reject);
4736
4753
  server.listen(0, "127.0.0.1", () => {
4737
4754
  const addr = server.address();
4738
4755
  const port = typeof addr === "object" && addr ? addr.port : 0;
4739
- resolve5({ url: `http://127.0.0.1:${port}`, close: () => new Promise((r) => server.close(() => r())) });
4756
+ resolve6({ url: `http://127.0.0.1:${port}`, close: () => new Promise((r) => server.close(() => r())) });
4740
4757
  });
4741
4758
  });
4742
4759
  }
@@ -4808,7 +4825,7 @@ async function removeWorktree(repo, dir) {
4808
4825
  await git(repo, ["worktree", "prune"]).catch(() => void 0);
4809
4826
  }
4810
4827
  function run(cmd, args, cwd, log, root = cwd, env = {}) {
4811
- return new Promise((resolve5, reject) => {
4828
+ return new Promise((resolve6, reject) => {
4812
4829
  const bin = [.../* @__PURE__ */ new Set([join3(cwd, "node_modules", ".bin"), join3(root, "node_modules", ".bin")])].join(":");
4813
4830
  const child = spawn(cmd, args, {
4814
4831
  cwd,
@@ -4821,7 +4838,7 @@ function run(cmd, args, cwd, log, root = cwd, env = {}) {
4821
4838
  child.stderr.on("data", keep);
4822
4839
  child.on("error", reject);
4823
4840
  child.on("close", (code) => {
4824
- if (code === 0) resolve5();
4841
+ if (code === 0) resolve6();
4825
4842
  else {
4826
4843
  log(tail);
4827
4844
  reject(new Error(`${cmd} ${args.join(" ")} failed in ${cwd} (exit ${code})`));
@@ -5232,12 +5249,12 @@ async function captureFigmaReferences(o) {
5232
5249
  }
5233
5250
  const plan = JSON.stringify(j.plan.render);
5234
5251
  await page.evaluate(
5235
- ({ id, plan: plan2 }) => new Promise((resolve5) => {
5252
+ ({ id, plan: plan2 }) => new Promise((resolve6) => {
5236
5253
  const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
5237
5254
  const done = () => {
5238
5255
  clearTimeout(timer);
5239
5256
  ch.off("storyRendered", done);
5240
- resolve5();
5257
+ resolve6();
5241
5258
  };
5242
5259
  const timer = setTimeout(done, 1e4);
5243
5260
  ch.on("storyRendered", done);
@@ -5526,7 +5543,7 @@ Claude Code is not installed (no \`claude\` on PATH). Install it (npm install -g
5526
5543
  if (!REQUEST_KINDS.some((k) => k.value === kind)) throw new RequestUsageError(`--kind takes ${REQUEST_KINDS.map((k) => k.value).join(", ")}, got "${kind}"`);
5527
5544
  const dryRun = has(a, "dry-run");
5528
5545
  const ctx = await context(root, io, !dryRun);
5529
- const figma = flag(a, "figma");
5546
+ const figma2 = flag(a, "figma");
5530
5547
  const exploration = flag(a, "exploration");
5531
5548
  const r = await requests2.createRequest(ctx, {
5532
5549
  component,
@@ -5534,7 +5551,7 @@ Claude Code is not installed (no \`claude\` on PATH). Install it (npm install -g
5534
5551
  title,
5535
5552
  summary,
5536
5553
  changes: (a.flags.change ?? []).map(parseChange),
5537
- ...figma ? { figmaUrl: figma } : {},
5554
+ ...figma2 ? { figmaUrl: figma2 } : {},
5538
5555
  ...exploration ? { exploration } : {},
5539
5556
  ...flag(a, "product") ? { product: flag(a, "product") } : {},
5540
5557
  source: "cli",
@@ -6000,6 +6017,92 @@ ${r.fixes.sample.map((s) => ` ${s}`).join("\n")}`);
6000
6017
  }
6001
6018
  }
6002
6019
 
6020
+ // src/figma.ts
6021
+ import { readFileSync as readFileSync2 } from "node:fs";
6022
+ import { resolve as resolve4 } from "node:path";
6023
+ import { BridgeContext as BridgeContext6, branches as branches3, figma } from "@bridge-ds/mcp";
6024
+ var FIGMA_HELP = `bridge figma: component changes sent from Figma
6025
+
6026
+ Usage:
6027
+ bridge figma [--all] [--json] the library branches the DS Bridge plugin opened for components
6028
+ changed in Figma, and where each stands
6029
+ bridge figma show <n> [--draft] [--json] one of them: its spec and the brief to draft the code; --draft
6030
+ runs Claude Code on the brief (\`claude\` on PATH)
6031
+ bridge figma drafted <n> [--json] the code is pushed: label it drafted by Claude AI for review
6032
+ bridge figma push <diff.json> [--dry-run] [--json]
6033
+ send a structured component diff like the plugin does: an exact
6034
+ token change, or a branch with the spec for Claude AI
6035
+
6036
+ GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
6037
+ var has5 = (a, k) => Boolean(a.flags[k]);
6038
+ async function context5(root, io) {
6039
+ const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
6040
+ return BridgeContext6.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
6041
+ }
6042
+ function number3(a, usage) {
6043
+ const raw = a._[2];
6044
+ if (!raw) throw new RequestUsageError(`Usage: ${usage}`);
6045
+ return branches3.branchNumber(raw);
6046
+ }
6047
+ async function runFigma(a, root, io = defaultIO()) {
6048
+ const sub = a._[1];
6049
+ if (sub === "help" || has5(a, "help")) {
6050
+ io.print(FIGMA_HELP);
6051
+ return 0;
6052
+ }
6053
+ const json = has5(a, "json");
6054
+ const ctx = await context5(root, io);
6055
+ const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
6056
+ switch (sub) {
6057
+ case void 0:
6058
+ case "list": {
6059
+ const r = await figma.figmaChangesOp(ctx, { state: has5(a, "all") ? "all" : "open" });
6060
+ return out(r, () => {
6061
+ for (const c of r.changes) io.print(`#${c.pr} ${c.title} (${c.route}): ${c.status}. ${c.said}`);
6062
+ io.print(`Next: ${r.next}`);
6063
+ });
6064
+ }
6065
+ case "show": {
6066
+ const r = await figma.getFigmaChangeOp(ctx, { pr: number3(a, "bridge figma show <n>") });
6067
+ if (has5(a, "draft")) {
6068
+ if (!r.brief) throw new RequestUsageError(`#${r.pr} is an exact token change: there is no code to draft.`);
6069
+ if (!io.hasClaude()) throw new RequestUsageError("`claude` is not on PATH: install Claude Code, or paste the brief from `bridge figma show <n>` into Claude.");
6070
+ return io.runClaude(r.brief, root);
6071
+ }
6072
+ return out(r, () => {
6073
+ io.print(`#${r.pr} ${r.title} (${r.route}): ${r.status}. ${r.said}`);
6074
+ io.print(` ${r.url}`);
6075
+ if (r.brief) io.print(`
6076
+ ${r.brief}`);
6077
+ });
6078
+ }
6079
+ case "drafted": {
6080
+ const r = await figma.markFigmaDraftedOp(ctx, { pr: number3(a, "bridge figma drafted <n>") });
6081
+ return out(r, () => io.print(`#${r.pr}: ${r.status}. ${r.said}`));
6082
+ }
6083
+ case "push": {
6084
+ const file = a._[2];
6085
+ if (!file) throw new RequestUsageError("Usage: bridge figma push <diff.json> [--dry-run]");
6086
+ let diff2;
6087
+ try {
6088
+ diff2 = JSON.parse(readFileSync2(resolve4(root, file), "utf8"));
6089
+ } catch (err) {
6090
+ throw new RequestUsageError(`${file} could not be read as JSON: ${err.message}`);
6091
+ }
6092
+ const r = await figma.pushFigmaComponentOp(ctx, { diff: diff2, dryRun: has5(a, "dry-run"), source: "cli" });
6093
+ return out(r, () => {
6094
+ io.print(`${r.component}: ${r.route}. ${r.said}`);
6095
+ for (const x of r.reasons ?? []) io.print(` ${x}`);
6096
+ for (const x of r.fixInFigma ?? []) io.print(` Fix in Figma: ${x.what} ${x.todo}`);
6097
+ if (r.library) io.print(` #${r.library.number} ${r.library.url}`);
6098
+ io.print(`Next: ${r.next}`);
6099
+ });
6100
+ }
6101
+ default:
6102
+ throw new RequestUsageError(`Unknown subcommand "${sub}". ${FIGMA_HELP}`);
6103
+ }
6104
+ }
6105
+
6003
6106
  // src/protection.ts
6004
6107
  var UNKNOWN = { kind: "unknown", missing: ["review", "checks", "history", "deletion"] };
6005
6108
  async function protectionReport(opts) {
@@ -6032,7 +6135,7 @@ async function rolesReport(opts) {
6032
6135
  }
6033
6136
 
6034
6137
  // src/index.ts
6035
- import { brand, BridgeContext as BridgeContext6, CONFIG_FILE as CONFIG_FILE2, core, detectConfig, GitRefSource, history as hist, LocalSource, ops, optionsFromArgv, serveStdio, toConfigJson, VERSION } from "@bridge-ds/mcp";
6138
+ import { brand, BridgeContext as BridgeContext7, CONFIG_FILE as CONFIG_FILE2, core, detectConfig, GitRefSource, history as hist, LocalSource, ops, optionsFromArgv, serveStdio, toConfigJson, VERSION } from "@bridge-ds/mcp";
6036
6139
  var MCP_PACKAGE = "@bridge-ds/mcp";
6037
6140
  var HELP = `bridge ${VERSION}: design system as code, for you and your Claude
6038
6141
 
@@ -6118,6 +6221,9 @@ Usage:
6118
6221
  a product branch and the library (\`bridge pieces help\`): what it adds
6119
6222
  that the library lacks, the library branch each piece opens (linked
6120
6223
  both ways), its preview in the product branch, the published version
6224
+ bridge figma [show <n> | drafted <n> | push <diff.json>]
6225
+ component changes sent from Figma (\`bridge figma help\`): exact token
6226
+ changes and branches Claude AI drafts from the spec
6121
6227
  bridge products | bridge settings | bridge coverage [Component]
6122
6228
  products (and this folder's), Studio settings safe to read, which
6123
6229
  components are in Figma
@@ -6146,16 +6252,16 @@ function parse(argv) {
6146
6252
  return out;
6147
6253
  }
6148
6254
  var flag4 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
6149
- var has5 = (a, k) => Boolean(a.flags[k]);
6255
+ var has6 = (a, k) => Boolean(a.flags[k]);
6150
6256
  var print = (s) => process.stdout.write(`${s}
6151
6257
  `);
6152
6258
  var modeArg = (s) => s ? Object.fromEntries(s.split(",").map((kv) => kv.split("="))) : void 0;
6153
6259
  async function init(a, root) {
6154
6260
  const cfgPath = join6(root, CONFIG_FILE2);
6155
6261
  let config;
6156
- if (existsSync6(cfgPath) && !has5(a, "force")) {
6262
+ if (existsSync6(cfgPath) && !has6(a, "force")) {
6157
6263
  print(`${CONFIG_FILE2} exists, keeping it (--force to regenerate).`);
6158
- config = (await (await BridgeContext6.create({ root })).config()).config;
6264
+ config = (await (await BridgeContext7.create({ root })).config()).config;
6159
6265
  } else {
6160
6266
  const src = new LocalSource(root);
6161
6267
  const files = await src.list("");
@@ -6183,14 +6289,14 @@ async function init(a, root) {
6183
6289
  print(` claude mcp add bridge -- ${desktop.command} ${desktop.args.join(" ")}`);
6184
6290
  }
6185
6291
  async function check(a, root) {
6186
- const ctx = await BridgeContext6.create({ root });
6292
+ const ctx = await BridgeContext7.create({ root });
6187
6293
  const v = await ops.validateTokens(ctx, { includeWarnings: true });
6188
6294
  const c = await ops.checkContrast(ctx, {});
6189
6295
  const warnings = "warnings" in v ? v.warnings ?? [] : [];
6190
- const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 || has5(a, "strict") && warnings.length > 0;
6296
+ const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 || has6(a, "strict") && warnings.length > 0;
6191
6297
  const protection = await protectionOf(a, root, ctx);
6192
6298
  const roles = await rolesOf(a, root, ctx);
6193
- if (has5(a, "json")) {
6299
+ if (has6(a, "json")) {
6194
6300
  print(JSON.stringify({ ok: !failed, validation: v, contrast: c, ...protection ? { protection: protection.state } : {}, ...roles?.teams.length ? { teams: roles.teams } : {} }, null, 2));
6195
6301
  return failed ? 1 : 0;
6196
6302
  }
@@ -6209,7 +6315,7 @@ async function check(a, root) {
6209
6315
  return failed ? 1 : 0;
6210
6316
  }
6211
6317
  async function protectionOf(a, root, ctx) {
6212
- if (has5(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
6318
+ if (has6(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
6213
6319
  const { config } = await ctx.config();
6214
6320
  const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
6215
6321
  if (!repo) return void 0;
@@ -6217,7 +6323,7 @@ async function protectionOf(a, root, ctx) {
6217
6323
  return protectionReport({ repo, defaultBranch: config.repo?.defaultBranch ?? "main", token });
6218
6324
  }
6219
6325
  async function rolesOf(a, root, ctx) {
6220
- if (has5(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
6326
+ if (has6(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
6221
6327
  const { config } = await ctx.config();
6222
6328
  const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
6223
6329
  if (!repo) return void 0;
@@ -6227,9 +6333,9 @@ async function rolesOf(a, root, ctx) {
6227
6333
  async function tokens(a, root) {
6228
6334
  const q = a._.slice(1).join(" ");
6229
6335
  if (!q) throw new UsageError("bridge tokens <query>");
6230
- const ctx = await BridgeContext6.create({ root });
6336
+ const ctx = await BridgeContext7.create({ root });
6231
6337
  const r = await ops.searchTokens(ctx, { query: q, limit: Number(flag4(a, "limit") ?? 20) });
6232
- if (has5(a, "json")) return print(JSON.stringify(r, null, 2));
6338
+ if (has6(a, "json")) return print(JSON.stringify(r, null, 2));
6233
6339
  if (!r.results.length) return print(`No token matches "${q}".`);
6234
6340
  const w = Math.max(...r.results.map((x) => x.path.length));
6235
6341
  for (const t of r.results) print(`${t.path.padEnd(w)} ${String(t.value).padEnd(10)} ${(t.tier ?? "").padEnd(9)} ${t.description ?? ""}`);
@@ -6237,7 +6343,7 @@ async function tokens(a, root) {
6237
6343
  async function diff(a, root) {
6238
6344
  const base = a._[1];
6239
6345
  if (!base) throw new UsageError("bridge diff <base> (a git ref: main, HEAD~1, a sha)");
6240
- const ctx = await BridgeContext6.create({ root });
6346
+ const ctx = await BridgeContext7.create({ root });
6241
6347
  const { config } = await ctx.config();
6242
6348
  const before = await core.loadTokenSet(new GitRefSource(root, base), config);
6243
6349
  const after = await ctx.tokens();
@@ -6246,11 +6352,11 @@ async function diff(a, root) {
6246
6352
  const delta2 = pairs2.length ? core.contrastDelta(core.runContrast(before, pairs2, config.contrast?.modes), core.runContrast(after, pairs2, config.contrast?.modes)) : [];
6247
6353
  const tier = core.classify(changes, [], config);
6248
6354
  const imp = core.impact(before, after, changes, 30);
6249
- if (has5(a, "json")) {
6355
+ if (has6(a, "json")) {
6250
6356
  print(JSON.stringify({ base, changes, contrast: delta2, tier, impact: imp }, null, 2));
6251
6357
  return 0;
6252
6358
  }
6253
- if (has5(a, "md")) {
6359
+ if (has6(a, "md")) {
6254
6360
  print(`## Token changes since \`${base}\`
6255
6361
 
6256
6362
  ${core.tokenTable(changes)}
@@ -6296,11 +6402,11 @@ async function propose(a, root) {
6296
6402
  edits.push({ op: "set", path: s.slice(0, i), value: s.slice(i + 1), ...file ? { file } : {}, ...mode2 ? { mode: mode2 } : {} });
6297
6403
  }
6298
6404
  const editsFile = flag4(a, "edits");
6299
- if (editsFile) edits.push(...JSON.parse(await readFile7(resolve4(editsFile), "utf8")));
6405
+ if (editsFile) edits.push(...JSON.parse(await readFile7(resolve5(editsFile), "utf8")));
6300
6406
  if (!edits.length) throw new UsageError("Give at least one --set path=value or --edits file.json");
6301
6407
  const pr = flag4(a, "pr");
6302
6408
  const token = pr ? process.env.BRIDGE_GITHUB_TOKEN || process.env.GITHUB_TOKEN || defaultIO().ghToken() : void 0;
6303
- const ctx = await BridgeContext6.create({ root, ...token ? { token } : {} });
6409
+ const ctx = await BridgeContext7.create({ root, ...token ? { token } : {} });
6304
6410
  const summary = flag4(a, "summary");
6305
6411
  const branch = flag4(a, "branch");
6306
6412
  const jira = flag4(a, "jira");
@@ -6313,9 +6419,9 @@ async function propose(a, root) {
6313
6419
  ...pr ? { pr } : {},
6314
6420
  ...jira ? { jira } : {},
6315
6421
  ...product ? { product } : {},
6316
- dryRun: has5(a, "dry-run")
6422
+ dryRun: has6(a, "dry-run")
6317
6423
  });
6318
- if (has5(a, "json")) print(JSON.stringify(r, null, 2));
6424
+ if (has6(a, "json")) print(JSON.stringify(r, null, 2));
6319
6425
  else {
6320
6426
  print(`status: ${r.status}${r.why ? ` (${r.why})` : ""}`);
6321
6427
  if (r.tier) print(`tier ${r.tier}, ${r.bump}`);
@@ -6330,10 +6436,10 @@ ${r.prBody}`);
6330
6436
  }
6331
6437
  async function brandCmd(a, root) {
6332
6438
  const sub = a._[1];
6333
- const ctx = await BridgeContext6.create({ root });
6439
+ const ctx = await BridgeContext7.create({ root });
6334
6440
  if (sub === "list") {
6335
6441
  const r2 = await brand.listBrands(ctx);
6336
- if (has5(a, "json")) print(JSON.stringify(r2.brands, null, 2));
6442
+ if (has6(a, "json")) print(JSON.stringify(r2.brands, null, 2));
6337
6443
  else for (const b of r2.brands) print(`${b.default ? "*" : " "} ${b.slug.padEnd(14)} ${b.name.padEnd(18)} ${b.primary ?? ""}${b.overrides.length ? ` overrides: ${b.overrides.join(", ")}` : ""}`);
6338
6444
  return 0;
6339
6445
  }
@@ -6353,9 +6459,9 @@ async function brandCmd(a, root) {
6353
6459
  ...opt("radius") ? { radius: opt("radius") } : {},
6354
6460
  ...Object.keys(fonts).length ? { fonts } : {},
6355
6461
  ...opt("notes") ? { notes: opt("notes") } : {},
6356
- dryRun: has5(a, "dry-run")
6462
+ dryRun: has6(a, "dry-run")
6357
6463
  });
6358
- if (has5(a, "json")) {
6464
+ if (has6(a, "json")) {
6359
6465
  const { fileContents: _f, ...rest } = r;
6360
6466
  print(JSON.stringify(rest, null, 2));
6361
6467
  return r.status === "blocked" ? 1 : 0;
@@ -6386,14 +6492,14 @@ async function figmaReferences(a, root) {
6386
6492
  }
6387
6493
  const only = (flag4(a, "only") ?? "").split(",").filter(Boolean);
6388
6494
  const r = await captureFigmaReferences({
6389
- storybook: resolve4(root, flag4(a, "storybook") ?? "storybook-static"),
6390
- specs: resolve4(root, flag4(a, "specs") ?? "packages/react/component-specs.json"),
6391
- out: resolve4(root, flag4(a, "out") ?? "figma-references"),
6495
+ storybook: resolve5(root, flag4(a, "storybook") ?? "storybook-static"),
6496
+ specs: resolve5(root, flag4(a, "specs") ?? "packages/react/component-specs.json"),
6497
+ out: resolve5(root, flag4(a, "out") ?? "figma-references"),
6392
6498
  sha,
6393
6499
  ...only.length ? { only } : {},
6394
- log: has5(a, "json") ? void 0 : (line) => print(line)
6500
+ log: has6(a, "json") ? void 0 : (line) => print(line)
6395
6501
  });
6396
- if (has5(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
6502
+ if (has6(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
6397
6503
  return 0;
6398
6504
  }
6399
6505
  async function impact(a, root) {
@@ -6411,22 +6517,22 @@ async function impact(a, root) {
6411
6517
  root,
6412
6518
  base: flag4(a, "base") ?? "main",
6413
6519
  head: flag4(a, "head") ?? "HEAD",
6414
- ...out ? { out: resolve4(out) } : {},
6520
+ ...out ? { out: resolve5(out) } : {},
6415
6521
  ...only.length ? { only } : {},
6416
6522
  ...threshold !== void 0 ? { threshold } : {},
6417
- ...cacheDir ? { cacheDir: resolve4(cacheDir) } : {},
6523
+ ...cacheDir ? { cacheDir: resolve5(cacheDir) } : {},
6418
6524
  ...maxStories ? { maxStoriesPerComponent: maxStories } : {},
6419
6525
  ...imageBase !== void 0 ? { imageBase } : {},
6420
6526
  ...artifactUrl ? { artifactUrl } : {},
6421
- plan: has5(a, "plan"),
6422
- keepUnchanged: has5(a, "keep-unchanged")
6527
+ plan: has6(a, "plan"),
6528
+ keepUnchanged: has6(a, "keep-unchanged")
6423
6529
  });
6424
- if (has5(a, "json")) {
6530
+ if (has6(a, "json")) {
6425
6531
  print(JSON.stringify({ summary: r.summary, manifest: r.manifestPath, comment: r.commentPath ?? null, ...r.manifest.summary, selection: r.manifest.selection }, null, 2));
6426
6532
  return 0;
6427
6533
  }
6428
6534
  const sel = r.manifest.selection;
6429
- if (has5(a, "plan")) {
6535
+ if (has6(a, "plan")) {
6430
6536
  print(`Plan ${r.manifest.base.ref} (${r.manifest.base.sha.slice(0, 7)}) -> ${r.manifest.head.ref} (${r.manifest.head.sha.slice(0, 7)})`);
6431
6537
  print(` tokens changing value: ${sel.tokens.length}${sel.tokens.length ? ` (${sel.tokens.filter((t) => t.kind === "direct").map((t) => t.path).slice(0, 6).join(", ")})` : ""}`);
6432
6538
  print(` components: ${sel.components.map((c) => c.id).join(", ") || "none"}`);
@@ -6442,12 +6548,12 @@ async function impact(a, root) {
6442
6548
  }
6443
6549
  var day = (iso) => iso.slice(0, 10);
6444
6550
  async function historyCmd(a, root) {
6445
- const ctx = await BridgeContext6.create({ root });
6551
+ const ctx = await BridgeContext7.create({ root });
6446
6552
  const token = a._[1];
6447
6553
  const limit = Number(flag4(a, "limit") ?? 20);
6448
6554
  if (token) {
6449
6555
  const r2 = await hist.getTokenHistory(ctx, { path: token, limit });
6450
- if (has5(a, "json")) return print(JSON.stringify(r2, null, 2)), 0;
6556
+ if (has6(a, "json")) return print(JSON.stringify(r2, null, 2)), 0;
6451
6557
  if (!r2.total) return print(`No recorded change of ${r2.path} in ${r2.source}.`), 0;
6452
6558
  print(`${r2.path}: ${r2.total} change(s) (${r2.source})
6453
6559
  `);
@@ -6459,8 +6565,8 @@ async function historyCmd(a, root) {
6459
6565
  return 0;
6460
6566
  }
6461
6567
  const tier = flag4(a, "tier");
6462
- const r = await hist.getHistory(ctx, { limit, ...flag4(a, "author") ? { author: flag4(a, "author") } : {}, ...tier ? { tier: Number(tier) } : {}, ...flag4(a, "version") ? { version: flag4(a, "version") } : {}, ...has5(a, "breaking") ? { breaking: true } : {} });
6463
- if (has5(a, "json")) return print(JSON.stringify(r, null, 2)), 0;
6568
+ const r = await hist.getHistory(ctx, { limit, ...flag4(a, "author") ? { author: flag4(a, "author") } : {}, ...tier ? { tier: Number(tier) } : {}, ...flag4(a, "version") ? { version: flag4(a, "version") } : {}, ...has6(a, "breaking") ? { breaking: true } : {} });
6569
+ if (has6(a, "json")) return print(JSON.stringify(r, null, 2)), 0;
6464
6570
  print(`${r.source}${r.currentVersion ? `, package version ${r.currentVersion}` : ""}`);
6465
6571
  if (!r.total) return print(r.note ?? "No token change matches."), 0;
6466
6572
  for (const v of r.versions) print(` ${v.version.padEnd(11)} ${day(v.date)} ${v.entries} change(s), ${v.tokenChanges} token edit(s)${v.breaking ? ", breaking" : ""}`);
@@ -6478,10 +6584,10 @@ ${r.truncated}`);
6478
6584
  async function revertCmd(a, root) {
6479
6585
  const target = a._[1];
6480
6586
  if (!target) throw new UsageError("bridge revert <pr|commit|id> [--dry-run] (ids from `bridge history`)");
6481
- const ctx = await BridgeContext6.create({ root });
6587
+ const ctx = await BridgeContext7.create({ root });
6482
6588
  const resolutions = Object.fromEntries((a.flags["use-before"] ?? []).map((k) => [k, "use-before"]));
6483
- const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts: has5(a, "restore-conflicts"), dryRun: has5(a, "dry-run") });
6484
- if (has5(a, "json")) {
6589
+ const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts: has6(a, "restore-conflicts"), dryRun: has6(a, "dry-run") });
6590
+ if (has6(a, "json")) {
6485
6591
  print(JSON.stringify(r, null, 2));
6486
6592
  return r.status === "blocked" ? 1 : 0;
6487
6593
  }
@@ -6506,11 +6612,11 @@ async function audit(a, root) {
6506
6612
  const target = a._[1];
6507
6613
  if (!target) throw new UsageError("bridge audit <path> (a product folder or file that uses the design system)");
6508
6614
  if (!existsSync6(target)) throw new UsageError(`No such file or folder: ${target}`);
6509
- const dsRoot = flag4(a, "ds") ? resolve4(flag4(a, "ds")) : existsSync6(join6(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
6615
+ const dsRoot = flag4(a, "ds") ? resolve5(flag4(a, "ds")) : existsSync6(join6(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
6510
6616
  if (!dsRoot) throw new UsageError(`No ${CONFIG_FILE2} found: run bridge audit from the design system repo or pass --ds <dir>.`);
6511
6617
  const ds = loadDesignSystem(dsRoot);
6512
- if (has5(a, "fix")) {
6513
- const write = has5(a, "write");
6618
+ if (has6(a, "fix")) {
6619
+ const write = has6(a, "write");
6514
6620
  const changes = fixCss(target, ds.index, write);
6515
6621
  const n = changes.reduce((acc, c) => acc + c.edits.length, 0);
6516
6622
  for (const c of changes) for (const e of c.edits) print(`${c.file}:${e.line}:${e.column} ${e.from} -> ${e.to}`);
@@ -6586,7 +6692,7 @@ async function audit(a, root) {
6586
6692
  code = 1;
6587
6693
  }
6588
6694
  }
6589
- if (has5(a, "no-regress") && report.baseline) {
6695
+ if (has6(a, "no-regress") && report.baseline) {
6590
6696
  const b = report.baseline;
6591
6697
  if ((b.tokenAdoption.delta ?? 0) < 0 || (b.componentAdoption.delta ?? 0) < 0 || b.hardcoded.delta > 0) {
6592
6698
  process.stderr.write("bridge audit: adoption regressed since the baseline\n");
@@ -6598,9 +6704,9 @@ async function audit(a, root) {
6598
6704
  async function docsCmd(a, root) {
6599
6705
  const [, sub, name] = a._;
6600
6706
  if (sub !== "draft" || !name) throw new UsageError("Usage: bridge docs draft <Component> [--json]");
6601
- const ctx = await BridgeContext6.create({ root });
6707
+ const ctx = await BridgeContext7.create({ root });
6602
6708
  const d = await ops.draftDocsOp(ctx, { name });
6603
- if (has5(a, "json")) {
6709
+ if (has6(a, "json")) {
6604
6710
  print(JSON.stringify({ component: d.component, path: d.path, exists: d.exists, prefill: d.prefill, prompt: d.prompt }, null, 2));
6605
6711
  return 0;
6606
6712
  }
@@ -6631,16 +6737,17 @@ async function main() {
6631
6737
  const argv = process.argv.slice(2);
6632
6738
  const a = parse(argv);
6633
6739
  const cmd = a._[0];
6634
- const root = resolve4(flag4(a, "root") ?? process.cwd());
6740
+ const root = resolve5(flag4(a, "root") ?? process.cwd());
6635
6741
  if (cmd === "request") return runRequest(a, root);
6636
6742
  if (cmd === "explore") return runExplore(a, root);
6637
6743
  if (cmd === "branch") return runBranch(a, root);
6638
6744
  if (cmd === "pieces") return runPieces(a, root);
6745
+ if (cmd === "figma") return runFigma(a, root);
6639
6746
  if (cmd === "products" || cmd === "settings" || cmd === "coverage") return runStudioRead(cmd, a, root);
6640
6747
  if (cmd === "docs" && a._[1] === "save") return runDocsSave(a, root);
6641
- if (!cmd || has5(a, "help") || cmd === "help") {
6748
+ if (!cmd || has6(a, "help") || cmd === "help") {
6642
6749
  print(HELP);
6643
- return cmd || has5(a, "help") ? 0 : 2;
6750
+ return cmd || has6(a, "help") ? 0 : 2;
6644
6751
  }
6645
6752
  if (cmd === "--version" || cmd === "version") return print(VERSION), 0;
6646
6753
  switch (cmd) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge-ds/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "bridge CLI: set up Bridge in a design system repo, check tokens in CI, search and diff tokens, propose changes, start the MCP server.",
6
6
  "license": "MIT",
@@ -43,8 +43,8 @@
43
43
  "build:package": "node ../../scripts/build-packages.mjs cli"
44
44
  },
45
45
  "dependencies": {
46
- "@bridge-ds/mcp": "^0.5.0",
47
- "@bridge-ds/visual": "^0.3.3",
46
+ "@bridge-ds/mcp": "^0.6.0",
47
+ "@bridge-ds/visual": "^0.3.4",
48
48
  "typescript": "5.9.3"
49
49
  },
50
50
  "devDependencies": {