@bridge-ds/cli 0.6.0 → 0.8.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 +664 -308
  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 = {}) {
@@ -3412,21 +3336,27 @@ var GitHubClient = class _GitHubClient {
3412
3336
  return pr ? { number: pr.number, url: pr.html_url, title: pr.title, draft: !!pr.draft, headRef: pr.head.ref } : void 0;
3413
3337
  }
3414
3338
  /** Best effort: labels need the repo to allow it; failure is not fatal. */
3415
- async addLabels(issueNumber2, labels) {
3339
+ async addLabels(issueNumber3, labels) {
3416
3340
  try {
3417
- await this.request("POST", `${this.repoPath}/issues/${issueNumber2}/labels`, { labels });
3341
+ await this.request("POST", `${this.repoPath}/issues/${issueNumber3}/labels`, { labels });
3418
3342
  return true;
3419
3343
  } catch {
3420
3344
  return false;
3421
3345
  }
3422
3346
  }
3423
- async addComment(issueNumber2, body) {
3424
- await this.request("POST", `${this.repoPath}/issues/${issueNumber2}/comments`, { body });
3347
+ async addComment(issueNumber3, body) {
3348
+ await this.request("POST", `${this.repoPath}/issues/${issueNumber3}/comments`, { body });
3425
3349
  }
3426
- /** Comments on an issue or pull request, oldest first (first 100: the sticky preview comment is posted early). */
3427
- 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 }));
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
+ */
3354
+ async listIssueComments(issueNumber3) {
3355
+ const r = await this.request(
3356
+ "GET",
3357
+ `${this.repoPath}/issues/${issueNumber3}/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) {
@@ -3741,8 +3671,8 @@ var GitHubClient = class _GitHubClient {
3741
3671
  });
3742
3672
  return { id: r.id, name: r.name, status: r.status, conclusion: r.conclusion, url: r.html_url };
3743
3673
  }
3744
- async removeLabel(issueNumber2, label) {
3745
- await this.request("DELETE", `${this.repoPath}/issues/${issueNumber2}/labels/${encodeURIComponent(label)}`);
3674
+ async removeLabel(issueNumber3, label) {
3675
+ await this.request("DELETE", `${this.repoPath}/issues/${issueNumber3}/labels/${encodeURIComponent(label)}`);
3746
3676
  }
3747
3677
  // --- branch actions shared by Studio, the MCP server and the CLI (branch-actions.ts) ---
3748
3678
  /** The account the token belongs to (GET /user). Needs a token. */
@@ -3916,6 +3846,169 @@ 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
+ // ../core/src/suggestions.ts
3905
+ var SUGGESTION_STATUS_LABEL = {
3906
+ "needs-review": "Needs review",
3907
+ "changes-requested": "Changes requested",
3908
+ approved: "Approved, ready to apply",
3909
+ applying: "Being applied",
3910
+ applied: "Applied",
3911
+ declined: "Declined",
3912
+ outdated: "Outdated"
3913
+ };
3914
+
3915
+ // ../core/src/suggestion-apply.ts
3916
+ var SUGGESTION_APPLY_EXIT = {
3917
+ ready: 0,
3918
+ "already-applied": 0,
3919
+ conflict: 3,
3920
+ "not-approved": 1,
3921
+ blocked: 1,
3922
+ refused: 1
3923
+ };
3924
+
3925
+ // src/index.ts
3926
+ import { fileURLToPath } from "node:url";
3927
+
3928
+ // ../visual/src/run.ts
3929
+ import { existsSync as existsSync4 } from "node:fs";
3930
+ import { mkdir as mkdir3, readFile as readFile3, rm as rm2, unlink, writeFile as writeFile3 } from "node:fs/promises";
3931
+ import { tmpdir } from "node:os";
3932
+ import { join as join4, relative as relative3, resolve as resolve2 } from "node:path";
3933
+
3934
+ // ../core/src/protection.ts
3935
+ var PROTECTION_PARTS = ["review", "checks", "history", "deletion"];
3936
+ var protectionRulesetName = (defaultBranch) => `Protect the library (${defaultBranch})`;
3937
+ function refPattern(pattern) {
3938
+ const src = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, ".*");
3939
+ return new RegExp(`^${src}$`);
3940
+ }
3941
+ function rulesetTargetsDefault(ruleset, defaultBranch) {
3942
+ if (ruleset.target && ruleset.target !== "branch") return false;
3943
+ const ref = `refs/heads/${defaultBranch}`;
3944
+ const hit = (p) => p === "~ALL" || p === "~DEFAULT_BRANCH" || p === ref || p === defaultBranch || refPattern(p.startsWith("refs/") ? p : `refs/heads/${p}`).test(ref);
3945
+ const cond = ruleset.conditions?.ref_name;
3946
+ if (!cond) return false;
3947
+ if ((cond.exclude ?? []).some(hit)) return false;
3948
+ return (cond.include ?? []).some(hit);
3949
+ }
3950
+ var count = (v) => typeof v === "number" ? v : 0;
3951
+ function rulesetParts(ruleset) {
3952
+ const parts = /* @__PURE__ */ new Set();
3953
+ if (ruleset.enforcement !== "active") return parts;
3954
+ for (const rule of ruleset.rules ?? []) {
3955
+ if (rule.type === "deletion") parts.add("deletion");
3956
+ if (rule.type === "non_fast_forward") parts.add("history");
3957
+ if (rule.type === "pull_request" && count(rule.parameters?.required_approving_review_count) >= 1) parts.add("review");
3958
+ if (rule.type === "required_status_checks" && Array.isArray(rule.parameters?.required_status_checks) && rule.parameters.required_status_checks.length > 0) parts.add("checks");
3959
+ }
3960
+ return parts;
3961
+ }
3962
+ function classicParts(p) {
3963
+ const parts = /* @__PURE__ */ new Set();
3964
+ if (count(p.required_pull_request_reviews?.required_approving_review_count) >= 1) parts.add("review");
3965
+ if (((p.required_status_checks?.contexts?.length ?? 0) || (p.required_status_checks?.checks?.length ?? 0)) > 0) parts.add("checks");
3966
+ if (!p.allow_force_pushes?.enabled) parts.add("history");
3967
+ if (!p.allow_deletions?.enabled) parts.add("deletion");
3968
+ return parts;
3969
+ }
3970
+ var rulesetUrl = (r) => r.html_url ?? r._links?.html?.href;
3971
+ function protectionState(rulesets, branchProtection, defaultBranch) {
3972
+ const bridge = rulesets?.find((r) => r.name === protectionRulesetName(defaultBranch));
3973
+ const extra = bridge?.id !== void 0 ? { bridgeRulesetId: bridge.id } : {};
3974
+ const covered = /* @__PURE__ */ new Set();
3975
+ let url;
3976
+ for (const r of rulesets ?? []) {
3977
+ if (!rulesetTargetsDefault(r, defaultBranch)) continue;
3978
+ const parts = rulesetParts(r);
3979
+ if (parts.size && !url) url = rulesetUrl(r);
3980
+ parts.forEach((p) => covered.add(p));
3981
+ }
3982
+ if (branchProtection) classicParts(branchProtection).forEach((p) => covered.add(p));
3983
+ const missing = PROTECTION_PARTS.filter((p) => !covered.has(p));
3984
+ const withUrl = url ? { url } : {};
3985
+ if (!missing.length) return { kind: "protected", missing, ...extra, ...withUrl };
3986
+ const unreadable = rulesets == null || branchProtection === void 0;
3987
+ if (covered.size === 0) return { kind: unreadable ? "unknown" : "unprotected", missing, ...extra };
3988
+ return { kind: "partial", missing, ...extra, ...withUrl };
3989
+ }
3990
+ var PROTECTION_PART_PHRASE = {
3991
+ review: "changes can skip review",
3992
+ checks: "checks do not have to pass",
3993
+ history: "past changes can be rewritten",
3994
+ deletion: "the library can be deleted"
3995
+ };
3996
+ var joinAnd = (xs) => xs.length <= 1 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
3997
+ function protectionSentence(state) {
3998
+ switch (state.kind) {
3999
+ case "protected":
4000
+ return "Protected: every change goes through review.";
4001
+ case "unprotected":
4002
+ return "Your library can be changed without review.";
4003
+ case "partial": {
4004
+ const s = joinAnd(state.missing.map((p) => PROTECTION_PART_PHRASE[p]));
4005
+ return `Partly protected: ${s}.`;
4006
+ }
4007
+ default:
4008
+ return "Protection status unknown: GitHub did not let this token read it.";
4009
+ }
4010
+ }
4011
+
3919
4012
  // ../core/src/protection-github.ts
3920
4013
  async function loadProtection(client, defaultBranch) {
3921
4014
  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 +4125,6 @@ function impactedModules(index, input) {
4032
4125
  return { modules, globalHits };
4033
4126
  }
4034
4127
 
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
4128
  // ../core/src/fidelity-references.ts
4091
4129
  var FIGMA_REFERENCES_DIR = "figma-references";
4092
4130
  var FIGMA_REFERENCES_MANIFEST = `${FIGMA_REFERENCES_DIR}/manifest.json`;
@@ -4310,15 +4348,15 @@ async function shoot(page, r) {
4310
4348
  }
4311
4349
  async function switchStory(page, storyId2) {
4312
4350
  const ok = await page.evaluate(
4313
- (id) => new Promise((resolve5) => {
4351
+ (id) => new Promise((resolve6) => {
4314
4352
  const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
4315
- if (!ch) return resolve5(false);
4353
+ if (!ch) return resolve6(false);
4316
4354
  const events = ["storyRendered", "storyErrored", "storyThrewException", "storyMissing", "playFunctionThrewException"];
4317
4355
  const timer = setTimeout(() => done(), 15e3);
4318
4356
  function done() {
4319
4357
  clearTimeout(timer);
4320
4358
  for (const e of events) ch.off(e, done);
4321
- resolve5(true);
4359
+ resolve6(true);
4322
4360
  }
4323
4361
  for (const e of events) ch.on(e, done);
4324
4362
  window.scrollTo(0, 0);
@@ -4731,12 +4769,12 @@ function serveDir(dir) {
4731
4769
  res.writeHead(200, { "content-type": TYPES[extname(file)] ?? "application/octet-stream", "cache-control": "no-store" });
4732
4770
  createReadStream(file).pipe(res);
4733
4771
  });
4734
- return new Promise((resolve5, reject) => {
4772
+ return new Promise((resolve6, reject) => {
4735
4773
  server.once("error", reject);
4736
4774
  server.listen(0, "127.0.0.1", () => {
4737
4775
  const addr = server.address();
4738
4776
  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())) });
4777
+ resolve6({ url: `http://127.0.0.1:${port}`, close: () => new Promise((r) => server.close(() => r())) });
4740
4778
  });
4741
4779
  });
4742
4780
  }
@@ -4808,7 +4846,7 @@ async function removeWorktree(repo, dir) {
4808
4846
  await git(repo, ["worktree", "prune"]).catch(() => void 0);
4809
4847
  }
4810
4848
  function run(cmd, args, cwd, log, root = cwd, env = {}) {
4811
- return new Promise((resolve5, reject) => {
4849
+ return new Promise((resolve6, reject) => {
4812
4850
  const bin = [.../* @__PURE__ */ new Set([join3(cwd, "node_modules", ".bin"), join3(root, "node_modules", ".bin")])].join(":");
4813
4851
  const child = spawn(cmd, args, {
4814
4852
  cwd,
@@ -4821,7 +4859,7 @@ function run(cmd, args, cwd, log, root = cwd, env = {}) {
4821
4859
  child.stderr.on("data", keep);
4822
4860
  child.on("error", reject);
4823
4861
  child.on("close", (code) => {
4824
- if (code === 0) resolve5();
4862
+ if (code === 0) resolve6();
4825
4863
  else {
4826
4864
  log(tail);
4827
4865
  reject(new Error(`${cmd} ${args.join(" ")} failed in ${cwd} (exit ${code})`));
@@ -5232,12 +5270,12 @@ async function captureFigmaReferences(o) {
5232
5270
  }
5233
5271
  const plan = JSON.stringify(j.plan.render);
5234
5272
  await page.evaluate(
5235
- ({ id, plan: plan2 }) => new Promise((resolve5) => {
5273
+ ({ id, plan: plan2 }) => new Promise((resolve6) => {
5236
5274
  const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
5237
5275
  const done = () => {
5238
5276
  clearTimeout(timer);
5239
5277
  ch.off("storyRendered", done);
5240
- resolve5();
5278
+ resolve6();
5241
5279
  };
5242
5280
  const timer = setTimeout(done, 1e4);
5243
5281
  ch.on("storyRendered", done);
@@ -5526,7 +5564,7 @@ Claude Code is not installed (no \`claude\` on PATH). Install it (npm install -g
5526
5564
  if (!REQUEST_KINDS.some((k) => k.value === kind)) throw new RequestUsageError(`--kind takes ${REQUEST_KINDS.map((k) => k.value).join(", ")}, got "${kind}"`);
5527
5565
  const dryRun = has(a, "dry-run");
5528
5566
  const ctx = await context(root, io, !dryRun);
5529
- const figma = flag(a, "figma");
5567
+ const figma2 = flag(a, "figma");
5530
5568
  const exploration = flag(a, "exploration");
5531
5569
  const r = await requests2.createRequest(ctx, {
5532
5570
  component,
@@ -5534,7 +5572,7 @@ Claude Code is not installed (no \`claude\` on PATH). Install it (npm install -g
5534
5572
  title,
5535
5573
  summary,
5536
5574
  changes: (a.flags.change ?? []).map(parseChange),
5537
- ...figma ? { figmaUrl: figma } : {},
5575
+ ...figma2 ? { figmaUrl: figma2 } : {},
5538
5576
  ...exploration ? { exploration } : {},
5539
5577
  ...flag(a, "product") ? { product: flag(a, "product") } : {},
5540
5578
  source: "cli",
@@ -5693,7 +5731,195 @@ ${EXPLORE_HELP}`);
5693
5731
  // src/branch.ts
5694
5732
  import { readFile as readFile6 } from "node:fs/promises";
5695
5733
  import { resolve as resolve3 } from "node:path";
5696
- import { BridgeContext as BridgeContext4, branches, studio } from "@bridge-ds/mcp";
5734
+ import { BridgeContext as BridgeContext5, branches, studio } from "@bridge-ds/mcp";
5735
+
5736
+ // src/suggest.ts
5737
+ import { BridgeContext as BridgeContext4, suggestions } from "@bridge-ds/mcp";
5738
+ var SuggestUsageError = class extends Error {
5739
+ };
5740
+ var SUGGESTIONS_HELP = `bridge suggestions: changes filed as GitHub issues (label bridge-suggestion), applied by a developer
5741
+
5742
+ Usage:
5743
+ bridge suggestions [list] [--status needs-review|changes-requested|approved|applying|applied|declined|outdated|all]
5744
+ [--kind token-change|docs|component-spec|brand|revert|config] [--json]
5745
+ bridge suggestions show <n> [--json]
5746
+ the change, the checks, its version, who approved it and what is missing
5747
+ bridge suggestions link <n> --pr <m> | --branch <name>
5748
+ record the pull request that applies it (it must say "Closes #<n>")
5749
+ bridge apply <n> [--write | --patch] [--dry-run] [--accept-edited] [--force-unapproved] [--branch name] [--json]
5750
+ apply it on today's code: a new local branch by default (working tree untouched, nothing
5751
+ pushed), --write into the working tree, --patch prints a patch for git am. --dry-run shows
5752
+ the plan and the diff and writes nothing
5753
+ bridge review <n> --approve | --request-changes --message "..." | --decline --message "..." [--version <sha>]
5754
+ as the owner your GitHub token belongs to; counts only for owners in bridge.config.json
5755
+
5756
+ GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Reading a public repo works without one.
5757
+ Exit codes (apply): 0 applied, already applied or a clean dry run; 1 not approved, blocked by a check or refused;
5758
+ 2 usage or setup error; 3 conflict with today's code.`;
5759
+ var flag3 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
5760
+ var has3 = (a, k) => Boolean(a.flags[k]);
5761
+ function changesFlag(a) {
5762
+ if (has3(a, "suggest") && has3(a, "direct")) throw new SuggestUsageError("Pass --suggest or --direct, not both.");
5763
+ return has3(a, "direct") ? "direct" : has3(a, "suggest") ? "suggest" : void 0;
5764
+ }
5765
+ function tokenFrom2(io) {
5766
+ return io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
5767
+ }
5768
+ async function context3(root, io) {
5769
+ const token = tokenFrom2(io);
5770
+ return BridgeContext4.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
5771
+ }
5772
+ function issueNumber2(raw, usage) {
5773
+ const n = Number((raw ?? "").replace(/^#/, ""));
5774
+ if (!raw || !Number.isInteger(n) || n <= 0) throw new SuggestUsageError(`Usage: ${usage}`);
5775
+ return n;
5776
+ }
5777
+ var STATUSES = ["needs-review", "changes-requested", "approved", "applying", "applied", "declined", "outdated", "all"];
5778
+ var KINDS = ["token-change", "docs", "component-spec", "brand", "revert", "config"];
5779
+ async function runSuggestions(a, root, io = defaultIO()) {
5780
+ const sub = a._[1] ?? "list";
5781
+ if (sub === "help" || has3(a, "help")) return io.print(SUGGESTIONS_HELP), 0;
5782
+ switch (sub) {
5783
+ case "list": {
5784
+ const status = flag3(a, "status") ?? "all";
5785
+ if (!STATUSES.includes(status)) throw new SuggestUsageError(`--status takes ${STATUSES.join(", ")}, got "${status}"`);
5786
+ const kind = flag3(a, "kind");
5787
+ if (kind && !KINDS.includes(kind)) throw new SuggestUsageError(`--kind takes ${KINDS.join(", ")}, got "${kind}"`);
5788
+ const ctx = await context3(root, io);
5789
+ const r = await suggestions.listSuggestionsOp(ctx, { status, ...kind ? { kind } : {} });
5790
+ if (has3(a, "json")) return io.print(JSON.stringify(r, null, 2)), 0;
5791
+ if (!r.suggestions.length) return io.print(`No ${status === "all" ? "open " : `${status} `}suggestion in ${r.repo}.`), 0;
5792
+ for (const x of r.suggestions) {
5793
+ const prs = x.linkedPrs.length ? ` PR ${x.linkedPrs.map((p) => `#${p.number}${p.merged ? " merged" : ` ${p.state}`}`).join(", ")}` : "";
5794
+ io.print(`#${String(x.number).padEnd(5)} ${x.said.padEnd(25)} ${(x.kind ?? "").padEnd(14)} ${x.tier ? `tier ${x.tier}` : " "} ${x.title}${prs}`);
5795
+ }
5796
+ io.print(`
5797
+ ${r.next}`);
5798
+ return 0;
5799
+ }
5800
+ case "show": {
5801
+ const n = issueNumber2(a._[2], "bridge suggestions show <number>");
5802
+ const ctx = await context3(root, io);
5803
+ const r = await suggestions.getSuggestionOp(ctx, { number: n });
5804
+ if (has3(a, "json")) return io.print(JSON.stringify(r, null, 2)), 0;
5805
+ io.print(`#${r.number} ${r.title}`);
5806
+ io.print(`${r.said}${r.kind ? `, ${r.kind}` : ""}${r.tier ? `, tier ${r.tier} (${r.bump})` : ""}${r.source ? `, from ${r.source}` : ""}${r.suggestedBy ? ` by @${r.suggestedBy}` : ""}`);
5807
+ io.print(r.url);
5808
+ if (r.problem) {
5809
+ io.print(`
5810
+ ${r.problem}`);
5811
+ return 1;
5812
+ }
5813
+ if (r.summary) io.print(`
5814
+ ${r.summary}`);
5815
+ const c = r.change ?? {};
5816
+ if (c.tokens?.length) {
5817
+ io.print("\nTokens:");
5818
+ for (const t of c.tokens) io.print(` ${t.kind ?? t.op} ${t.path}${t.before !== void 0 || t.after !== void 0 ? `: ${fmt(t.before)} -> ${fmt(t.after)}` : ""}`);
5819
+ }
5820
+ for (const d of c.docs ?? []) io.print(`Docs: ${d.component} (${d.path})`);
5821
+ if (c.componentSpec) io.print(`Component spec: ${c.componentSpec.component} (${c.componentSpec.specPath})`);
5822
+ for (const f of c.files ?? []) io.print(`File: ${f.change} ${f.path}`);
5823
+ io.print(`
5824
+ Version ${r.version}${r.edited ? ` (${r.edited})` : ""}`);
5825
+ io.print(`Approved by: ${r.approvals.approvedBy.map((l) => `@${l}`).join(", ") || "nobody yet"}`);
5826
+ if (r.approvals.missing.length) io.print(`Missing: ${r.approvals.missing.join(" and ")}`);
5827
+ if (r.approvals.changesRequestedBy.length) io.print(`Changes requested by: ${r.approvals.changesRequestedBy.map((l) => `@${l}`).join(", ")}`);
5828
+ for (const p of r.linkedPrs ?? []) io.print(`Pull request: #${p.number} ${p.merged ? "merged" : p.state} ${p.url}`);
5829
+ io.print(`
5830
+ Next: ${r.next}`);
5831
+ return 0;
5832
+ }
5833
+ case "link": {
5834
+ const n = issueNumber2(a._[2], "bridge suggestions link <number> --pr <m>");
5835
+ const prRaw = flag3(a, "pr");
5836
+ const pr = prRaw ? issueNumber2(prRaw, "bridge suggestions link <number> --pr <m>") : void 0;
5837
+ const branch = flag3(a, "branch");
5838
+ if (!pr && !branch) throw new SuggestUsageError("Usage: bridge suggestions link <number> --pr <m> (or --branch <name>)");
5839
+ const ctx = await context3(root, io);
5840
+ const r = await suggestions.linkSuggestionOp(ctx, { number: n, ...pr ? { pr } : {}, ...branch ? { branch } : {} });
5841
+ if (has3(a, "json")) return io.print(JSON.stringify(r, null, 2)), 0;
5842
+ io.print(`#${n} is being applied${pr ? ` in #${pr}` : branch ? ` on ${branch}` : ""}.`);
5843
+ for (const w of r.warnings ?? []) io.error(`warning: ${w}`);
5844
+ return 0;
5845
+ }
5846
+ default:
5847
+ throw new SuggestUsageError(`Unknown suggestions command "${sub}".
5848
+
5849
+ ${SUGGESTIONS_HELP}`);
5850
+ }
5851
+ }
5852
+ var fmt = (v) => v === void 0 ? "(none)" : typeof v === "string" ? v : JSON.stringify(v);
5853
+ async function runApply(a, root, io = defaultIO()) {
5854
+ if (has3(a, "help") || a._[1] === "help") return io.print(SUGGESTIONS_HELP), 0;
5855
+ const n = issueNumber2(a._[1], "bridge apply <number> [--write | --patch] [--dry-run]");
5856
+ if (has3(a, "write") && has3(a, "patch")) throw new SuggestUsageError("Pass --write or --patch, not both.");
5857
+ const mode2 = has3(a, "write") ? "write" : has3(a, "patch") ? "patch" : "branch";
5858
+ const ctx = await context3(root, io);
5859
+ if (ctx.mode !== "local" || !ctx.isGit) throw new SuggestUsageError("bridge apply runs in a git checkout of the design system: cd into it, or pass --root.");
5860
+ const branch = flag3(a, "branch");
5861
+ const r = await suggestions.applySuggestionOp(ctx, {
5862
+ number: n,
5863
+ mode: has3(a, "dry-run") ? "dry-run" : mode2,
5864
+ acceptEdited: has3(a, "accept-edited"),
5865
+ forceUnapproved: has3(a, "force-unapproved"),
5866
+ ...branch ? { branch } : {}
5867
+ });
5868
+ const code = typeof r.exitCode === "number" ? r.exitCode : SUGGESTION_APPLY_EXIT[r.outcome] ?? 1;
5869
+ if (has3(a, "json")) return io.print(JSON.stringify(r, null, 2)), code;
5870
+ if (mode2 === "patch" && r.patch && !has3(a, "dry-run")) {
5871
+ io.print(r.patch.trimEnd());
5872
+ io.error(r.message);
5873
+ for (const w of r.warnings ?? []) io.error(`warning: ${w}`);
5874
+ return code;
5875
+ }
5876
+ io.print(`#${n}: ${r.message}`);
5877
+ if (r.table) io.print(`
5878
+ ${r.table}`);
5879
+ for (const p of r.problems ?? []) io.print(` ${p}`);
5880
+ for (const note of r.notes ?? []) io.print(` note: ${note}`);
5881
+ if (r.diff) io.print(`
5882
+ ${r.diff.trimEnd()}`);
5883
+ if (r.brief) io.print(`
5884
+ Brief to implement it:
5885
+ ${r.brief}`);
5886
+ if (r.branch) io.print(`
5887
+ branch: ${r.branch}${r.commit ? ` (${String(r.commit).slice(0, 7)})` : ""}`);
5888
+ for (const w of r.warnings ?? []) io.error(`warning: ${w}`);
5889
+ if (r.next && r.next !== r.message) io.print(`
5890
+ Next: ${r.next}`);
5891
+ return code;
5892
+ }
5893
+ async function runReview(a, root, io = defaultIO()) {
5894
+ if (has3(a, "help") || a._[1] === "help") return io.print(SUGGESTIONS_HELP), 0;
5895
+ const usage = 'bridge review <number> --approve | --request-changes --message "..." | --decline --message "..."';
5896
+ const n = issueNumber2(a._[1], usage);
5897
+ const events = ["approve", "request-changes", "decline", "comment"].filter((e) => has3(a, e));
5898
+ if (events.length !== 1) throw new SuggestUsageError(`Usage: ${usage}`);
5899
+ const event = events[0];
5900
+ const inline = flag3(a, event);
5901
+ const message = flag3(a, "message") ?? (inline && inline !== "true" ? inline : void 0);
5902
+ if ((event === "request-changes" || event === "decline" || event === "comment") && !message?.trim()) throw new SuggestUsageError(`--${event} needs --message "..." (the author reads it).`);
5903
+ const role = flag3(a, "role");
5904
+ if (role && !["design", "tech", "approval"].includes(role)) throw new SuggestUsageError(`--role takes design, tech or approval, got "${role}"`);
5905
+ const ctx = await context3(root, io);
5906
+ const version = flag3(a, "version");
5907
+ const r = await suggestions.reviewSuggestionOp(ctx, {
5908
+ number: n,
5909
+ event,
5910
+ ...message ? { message } : {},
5911
+ ...role ? { role } : {},
5912
+ ...version ? { payloadSha: version } : {}
5913
+ });
5914
+ if (has3(a, "json")) return io.print(JSON.stringify(r, null, 2)), 0;
5915
+ const done = event === "approve" ? "Approved" : event === "request-changes" ? "Changes requested on" : event === "decline" ? "Declined" : "Commented on";
5916
+ io.print(`${done} #${n}${r.version ? ` (version ${r.version})` : ""}. Now: ${SUGGESTION_STATUS_LABEL[r.status] ?? r.status}.`);
5917
+ for (const w of r.warnings ?? []) io.error(`warning: ${w}`);
5918
+ if (r.next) io.print(`Next: ${r.next}`);
5919
+ return r.counts === false ? 1 : 0;
5920
+ }
5921
+
5922
+ // src/branch.ts
5697
5923
  var BRANCH_HELP = `bridge branch: branches in review, the same actions as Bridge Studio
5698
5924
 
5699
5925
  Usage:
@@ -5723,11 +5949,11 @@ Usage:
5723
5949
 
5724
5950
  Start work for a product: bridge propose|explore start|request create ... --product <key> [--jira DS-42].
5725
5951
  GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
5726
- var flag3 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
5727
- var has3 = (a, k) => Boolean(a.flags[k]);
5728
- async function context3(root, io) {
5952
+ var flag4 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
5953
+ var has4 = (a, k) => Boolean(a.flags[k]);
5954
+ async function context4(root, io) {
5729
5955
  const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
5730
- return BridgeContext4.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
5956
+ return BridgeContext5.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
5731
5957
  }
5732
5958
  function number(a, at, usage) {
5733
5959
  const raw = a._[at];
@@ -5746,16 +5972,16 @@ function pairs(list2, what) {
5746
5972
  var said = (r) => String(r.said ?? r.summary ?? r.status ?? "Done.");
5747
5973
  async function runBranch(a, root, io = defaultIO()) {
5748
5974
  const sub = a._[1];
5749
- if (!sub || sub === "help" || has3(a, "help")) {
5975
+ if (!sub || sub === "help" || has4(a, "help")) {
5750
5976
  io.print(BRANCH_HELP);
5751
- return sub || has3(a, "help") ? 0 : 2;
5977
+ return sub || has4(a, "help") ? 0 : 2;
5752
5978
  }
5753
- const json = has3(a, "json");
5754
- const ctx = await context3(root, io);
5979
+ const json = has4(a, "json");
5980
+ const ctx = await context4(root, io);
5755
5981
  const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
5756
5982
  switch (sub) {
5757
5983
  case "list": {
5758
- const limit = flag3(a, "limit");
5984
+ const limit = flag4(a, "limit");
5759
5985
  const r = await branches.whatNeedsMe(ctx, limit ? { limit: Number(limit) } : {});
5760
5986
  return out(r, () => {
5761
5987
  io.print(r.summary);
@@ -5789,28 +6015,28 @@ async function runBranch(a, root, io = defaultIO()) {
5789
6015
  }
5790
6016
  case "review": {
5791
6017
  const n = number(a, 2, 'bridge branch review <n> --approve | --request-changes "..." | --comment "..."');
5792
- const decision = has3(a, "approve") ? "approve" : has3(a, "request-changes") ? "request_changes" : has3(a, "comment") ? "comment" : null;
6018
+ const decision = has4(a, "approve") ? "approve" : has4(a, "request-changes") ? "request_changes" : has4(a, "comment") ? "comment" : null;
5793
6019
  if (!decision) throw new RequestUsageError('Say what you decide: --approve, --request-changes "what to change" or --comment "text".');
5794
- const body = decision === "approve" ? flag3(a, "body") : flag3(a, decision === "request_changes" ? "request-changes" : "comment");
6020
+ const body = decision === "approve" ? flag4(a, "body") : flag4(a, decision === "request_changes" ? "request-changes" : "comment");
5795
6021
  const r = await branches.reviewBranch(ctx, { pr: n, decision, ...body && body !== "true" ? { body } : {} });
5796
6022
  return out(r, () => io.print(r.said));
5797
6023
  }
5798
6024
  case "ask": {
5799
- const r = await branches.askSomeoneElse(ctx, { pr: number(a, 2, "bridge branch ask <n>"), dryRun: has3(a, "dry-run") });
6025
+ const r = await branches.askSomeoneElse(ctx, { pr: number(a, 2, "bridge branch ask <n>"), dryRun: has4(a, "dry-run") });
5800
6026
  return out(r, () => io.print(r.said));
5801
6027
  }
5802
6028
  case "comment": {
5803
6029
  const n = number(a, 2, 'bridge branch comment <n> "text"');
5804
- const body = a._.slice(3).join(" ") || flag3(a, "body");
6030
+ const body = a._.slice(3).join(" ") || flag4(a, "body");
5805
6031
  if (!body) throw new RequestUsageError('Usage: bridge branch comment <n> "text" [--reply-to <id>]');
5806
- const reply = flag3(a, "reply-to");
6032
+ const reply = flag4(a, "reply-to");
5807
6033
  const r = await branches.commentOnBranch(ctx, { pr: n, body, ...reply ? { replyTo: Number(reply) } : {} });
5808
6034
  return out(r, () => io.print(r.said));
5809
6035
  }
5810
6036
  case "merge": {
5811
6037
  const n = number(a, 2, 'bridge branch merge <n> [--override "reason"] [--dry-run]');
5812
- const reason = flag3(a, "override");
5813
- const r = await branches.mergeBranch(ctx, { pr: n, ...reason ? { overrideReason: reason } : {}, dryRun: has3(a, "dry-run") });
6038
+ const reason = flag4(a, "override");
6039
+ const r = await branches.mergeBranch(ctx, { pr: n, ...reason ? { overrideReason: reason } : {}, dryRun: has4(a, "dry-run") });
5814
6040
  return out(r, () => io.print(r.said));
5815
6041
  }
5816
6042
  case "close": {
@@ -5825,7 +6051,7 @@ async function runBranch(a, root, io = defaultIO()) {
5825
6051
  const n = number(a, 2, "bridge branch update <n> [--choice key=mine|theirs] [--dry-run]");
5826
6052
  const choices = pairs(a.flags.choice, "choice");
5827
6053
  for (const [k, v] of Object.entries(choices)) if (v !== "mine" && v !== "theirs") throw new RequestUsageError(`--choice ${k}= takes mine or theirs, got "${v}".`);
5828
- const r = await branches.updateFromMain(ctx, { pr: n, ...Object.keys(choices).length ? { choices } : {}, dryRun: has3(a, "dry-run") });
6054
+ const r = await branches.updateFromMain(ctx, { pr: n, ...Object.keys(choices).length ? { choices } : {}, dryRun: has4(a, "dry-run") });
5829
6055
  return out(r, () => {
5830
6056
  io.print(said(r));
5831
6057
  for (const c of r.conflicts ?? []) io.print(` ${c.key} (${c.token}, default ${c.default ?? "none: pick one"})`);
@@ -5857,8 +6083,8 @@ ${BRANCH_HELP}`);
5857
6083
  }
5858
6084
  }
5859
6085
  async function runStudioRead(cmd, a, root, io = defaultIO()) {
5860
- const ctx = await context3(root, io);
5861
- const json = has3(a, "json");
6086
+ const ctx = await context4(root, io);
6087
+ const json = has4(a, "json");
5862
6088
  if (cmd === "products") {
5863
6089
  const r2 = await branches.listProducts(ctx);
5864
6090
  if (json) return io.print(JSON.stringify(r2, null, 2)), 0;
@@ -5887,7 +6113,7 @@ This folder: ${r2.current.name}. Pick another with --product <key>.`);
5887
6113
  }
5888
6114
  async function runDocsSave(a, root, io = defaultIO()) {
5889
6115
  const component = a._[2];
5890
- const file = flag3(a, "file");
6116
+ const file = flag4(a, "file");
5891
6117
  if (!component || !file) throw new RequestUsageError('Usage: bridge docs save <Component> --file docs.json [--pr <n>] [--title "..."] [--jira DS-42] [--product admin] [--dry-run]');
5892
6118
  let docs;
5893
6119
  try {
@@ -5895,28 +6121,32 @@ async function runDocsSave(a, root, io = defaultIO()) {
5895
6121
  } catch (e) {
5896
6122
  throw new RequestUsageError(`${file} is not a readable JSON file: ${e.message}`);
5897
6123
  }
5898
- const ctx = await context3(root, io);
5899
- const pr = flag3(a, "pr");
6124
+ const ctx = await context4(root, io);
6125
+ const pr = flag4(a, "pr");
6126
+ const changes = changesFlag(a);
5900
6127
  const r = await studio.saveDocs(ctx, {
5901
6128
  component,
5902
6129
  docs,
5903
6130
  ...pr ? { pr } : {},
5904
- ...flag3(a, "title") ? { title: flag3(a, "title") } : {},
5905
- ...flag3(a, "notes") ? { notes: flag3(a, "notes") } : {},
5906
- ...flag3(a, "jira") ? { jira: flag3(a, "jira") } : {},
5907
- ...flag3(a, "product") ? { product: flag3(a, "product") } : {},
5908
- dryRun: has3(a, "dry-run")
6131
+ ...flag4(a, "title") ? { title: flag4(a, "title") } : {},
6132
+ ...flag4(a, "notes") ? { notes: flag4(a, "notes") } : {},
6133
+ ...flag4(a, "jira") ? { jira: flag4(a, "jira") } : {},
6134
+ ...flag4(a, "product") ? { product: flag4(a, "product") } : {},
6135
+ ...changes ? { changes } : {},
6136
+ source: "cli",
6137
+ dryRun: has4(a, "dry-run")
5909
6138
  });
5910
- if (has3(a, "json")) io.print(JSON.stringify(r, null, 2));
6139
+ if (has4(a, "json")) io.print(JSON.stringify(r, null, 2));
5911
6140
  else io.print(r.status === "dry-run" || r.status === "preview" ? `${r.said ?? "Dry run, nothing written."}
5912
6141
 
5913
6142
  ${r.path}:
5914
- ${r.content}` : String(r.said));
6143
+ ${r.content}` : `${String(r.said)}${r.next ? `
6144
+ ${String(r.next)}` : ""}`);
5915
6145
  return 0;
5916
6146
  }
5917
6147
 
5918
6148
  // src/pieces.ts
5919
- import { BridgeContext as BridgeContext5, branches as branches2, pieces } from "@bridge-ds/mcp";
6149
+ import { BridgeContext as BridgeContext6, branches as branches2, pieces } from "@bridge-ds/mcp";
5920
6150
  var PIECES_HELP = `bridge pieces: a product branch and the library
5921
6151
 
5922
6152
  Usage:
@@ -5931,10 +6161,10 @@ Usage:
5931
6161
  bridge pieces switch <n> once the library shipped: main into the product branch
5932
6162
 
5933
6163
  GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
5934
- var has4 = (a, k) => Boolean(a.flags[k]);
5935
- async function context4(root, io) {
6164
+ var has5 = (a, k) => Boolean(a.flags[k]);
6165
+ async function context5(root, io) {
5936
6166
  const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
5937
- return BridgeContext5.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
6167
+ return BridgeContext6.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
5938
6168
  }
5939
6169
  function number2(a, at, usage) {
5940
6170
  const raw = a._[at];
@@ -5948,12 +6178,12 @@ function piece(a, at, usage) {
5948
6178
  }
5949
6179
  async function runPieces(a, root, io = defaultIO()) {
5950
6180
  const sub = a._[1];
5951
- if (!sub || sub === "help" || has4(a, "help")) {
6181
+ if (!sub || sub === "help" || has5(a, "help")) {
5952
6182
  io.print(PIECES_HELP);
5953
- return sub || has4(a, "help") ? 0 : 2;
6183
+ return sub || has5(a, "help") ? 0 : 2;
5954
6184
  }
5955
- const json = has4(a, "json");
5956
- const ctx = await context4(root, io);
6185
+ const json = has5(a, "json");
6186
+ const ctx = await context5(root, io);
5957
6187
  const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
5958
6188
  switch (sub) {
5959
6189
  case "status": {
@@ -5968,9 +6198,10 @@ async function runPieces(a, root, io = defaultIO()) {
5968
6198
  }
5969
6199
  case "open": {
5970
6200
  const usage = "bridge pieces open <n> <piece>";
5971
- const r = await pieces.openLibraryBranchOp(ctx, { pr: number2(a, 2, usage), piece: piece(a, 3, usage), usePreview: !has4(a, "no-preview"), dryRun: has4(a, "dry-run"), source: "cli" });
6201
+ const changes = changesFlag(a);
6202
+ const r = await pieces.openLibraryBranchOp(ctx, { pr: number2(a, 2, usage), piece: piece(a, 3, usage), usePreview: !has5(a, "no-preview"), dryRun: has5(a, "dry-run"), source: "cli", ...changes ? { changes } : {} });
5972
6203
  return out(r, () => {
5973
- io.print(`${r.status === "dry-run" ? "Would open" : r.status === "already-linked" ? "Already linked" : "Opened"} a ${r.opens}: ${r.title}, needed by ${r.neededBy}.`);
6204
+ io.print(`${r.status === "dry-run" ? "Would open" : r.status === "already-linked" ? "Already linked" : r.status === "suggested" ? "Suggested" : "Opened"} a ${r.opens}: ${r.title}, needed by ${r.neededBy}.`);
5974
6205
  if (r.library) io.print(` #${r.library.number} ${r.library.url}`);
5975
6206
  if (r.preview) io.print(` ${r.preview.said}`);
5976
6207
  for (const w of r.warnings ?? []) io.print(` ${w}`);
@@ -5987,7 +6218,7 @@ async function runPieces(a, root, io = defaultIO()) {
5987
6218
  return out(r, () => io.print(r.said));
5988
6219
  }
5989
6220
  default: {
5990
- const r = await pieces.missingPiecesOp(ctx, { pr: number2(a, 1, "bridge pieces <n> [--record]"), record: has4(a, "record") });
6221
+ const r = await pieces.missingPiecesOp(ctx, { pr: number2(a, 1, "bridge pieces <n> [--record]"), record: has5(a, "record") });
5991
6222
  return out(r, () => {
5992
6223
  io.print(`#${r.pr} ${r.branch} (${r.product}): ${r.pieces.length ? `${r.pieces.length} ${r.pieces.length === 1 ? "piece is" : "pieces are"} missing from the library` : "nothing is missing from the library"}, ${r.files} files read.`);
5993
6224
  for (const p of r.pieces) io.print(` ${p.id} ${p.title}: ${p.summary} Opens ${p.opens}.${"linked" in p ? ` Linked: #${p.linked}.` : ""}`);
@@ -6000,6 +6231,94 @@ ${r.fixes.sample.map((s) => ` ${s}`).join("\n")}`);
6000
6231
  }
6001
6232
  }
6002
6233
 
6234
+ // src/figma.ts
6235
+ import { readFileSync as readFileSync2 } from "node:fs";
6236
+ import { resolve as resolve4 } from "node:path";
6237
+ import { BridgeContext as BridgeContext7, branches as branches3, figma } from "@bridge-ds/mcp";
6238
+ var FIGMA_HELP = `bridge figma: component changes sent from Figma
6239
+
6240
+ Usage:
6241
+ bridge figma [--all] [--json] the library branches the DS Bridge plugin opened for components
6242
+ changed in Figma, and where each stands
6243
+ bridge figma show <n> [--draft] [--json] one of them: its spec and the brief to draft the code; --draft
6244
+ runs Claude Code on the brief (\`claude\` on PATH)
6245
+ bridge figma drafted <n> [--json] the code is pushed: label it drafted by Claude AI for review
6246
+ bridge figma push <diff.json> [--dry-run] [--json]
6247
+ send a structured component diff like the plugin does: an exact
6248
+ token change, or a branch with the spec for Claude AI
6249
+
6250
+ GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
6251
+ var has6 = (a, k) => Boolean(a.flags[k]);
6252
+ async function context6(root, io) {
6253
+ const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
6254
+ return BridgeContext7.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
6255
+ }
6256
+ function number3(a, usage) {
6257
+ const raw = a._[2];
6258
+ if (!raw) throw new RequestUsageError(`Usage: ${usage}`);
6259
+ return branches3.branchNumber(raw);
6260
+ }
6261
+ async function runFigma(a, root, io = defaultIO()) {
6262
+ const sub = a._[1];
6263
+ if (sub === "help" || has6(a, "help")) {
6264
+ io.print(FIGMA_HELP);
6265
+ return 0;
6266
+ }
6267
+ const json = has6(a, "json");
6268
+ const ctx = await context6(root, io);
6269
+ const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
6270
+ switch (sub) {
6271
+ case void 0:
6272
+ case "list": {
6273
+ const r = await figma.figmaChangesOp(ctx, { state: has6(a, "all") ? "all" : "open" });
6274
+ return out(r, () => {
6275
+ for (const c of r.changes) io.print(`#${c.pr} ${c.title} (${c.route}): ${c.status}. ${c.said}`);
6276
+ io.print(`Next: ${r.next}`);
6277
+ });
6278
+ }
6279
+ case "show": {
6280
+ const r = await figma.getFigmaChangeOp(ctx, { pr: number3(a, "bridge figma show <n>") });
6281
+ if (has6(a, "draft")) {
6282
+ if (!r.brief) throw new RequestUsageError(`#${r.pr} is an exact token change: there is no code to draft.`);
6283
+ 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.");
6284
+ return io.runClaude(r.brief, root);
6285
+ }
6286
+ return out(r, () => {
6287
+ io.print(`#${r.pr} ${r.title} (${r.route}): ${r.status}. ${r.said}`);
6288
+ io.print(` ${r.url}`);
6289
+ if (r.brief) io.print(`
6290
+ ${r.brief}`);
6291
+ });
6292
+ }
6293
+ case "drafted": {
6294
+ const r = await figma.markFigmaDraftedOp(ctx, { pr: number3(a, "bridge figma drafted <n>") });
6295
+ return out(r, () => io.print(`#${r.pr}: ${r.status}. ${r.said}`));
6296
+ }
6297
+ case "push": {
6298
+ const file = a._[2];
6299
+ if (!file) throw new RequestUsageError("Usage: bridge figma push <diff.json> [--dry-run]");
6300
+ let diff2;
6301
+ try {
6302
+ diff2 = JSON.parse(readFileSync2(resolve4(root, file), "utf8"));
6303
+ } catch (err) {
6304
+ throw new RequestUsageError(`${file} could not be read as JSON: ${err.message}`);
6305
+ }
6306
+ const changes = changesFlag(a);
6307
+ const r = await figma.pushFigmaComponentOp(ctx, { diff: diff2, dryRun: has6(a, "dry-run"), source: "cli", ...changes ? { changes } : {} });
6308
+ return out(r, () => {
6309
+ io.print(`${r.component}: ${r.route}. ${r.said}`);
6310
+ for (const x of r.reasons ?? []) io.print(` ${x}`);
6311
+ for (const x of r.fixInFigma ?? []) io.print(` Fix in Figma: ${x.what} ${x.todo}`);
6312
+ if (r.library) io.print(` #${r.library.number} ${r.library.url}`);
6313
+ if (r.suggestion) io.print(` suggestion #${r.suggestion.number} ${r.suggestion.url}`);
6314
+ io.print(`Next: ${r.next}`);
6315
+ });
6316
+ }
6317
+ default:
6318
+ throw new RequestUsageError(`Unknown subcommand "${sub}". ${FIGMA_HELP}`);
6319
+ }
6320
+ }
6321
+
6003
6322
  // src/protection.ts
6004
6323
  var UNKNOWN = { kind: "unknown", missing: ["review", "checks", "history", "deletion"] };
6005
6324
  async function protectionReport(opts) {
@@ -6032,7 +6351,7 @@ async function rolesReport(opts) {
6032
6351
  }
6033
6352
 
6034
6353
  // 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";
6354
+ import { brand, BridgeContext as BridgeContext8, CONFIG_FILE as CONFIG_FILE2, core, detectConfig, GitRefSource, history as hist, LocalSource, ops, optionsFromArgv, serveStdio, toConfigJson, VERSION } from "@bridge-ds/mcp";
6036
6355
  var MCP_PACKAGE = "@bridge-ds/mcp";
6037
6356
  var HELP = `bridge ${VERSION}: design system as code, for you and your Claude
6038
6357
 
@@ -6048,8 +6367,10 @@ Usage:
6048
6367
  bridge diff <base> [--json|--md] designer-readable token diff: working tree vs <base> (git ref)
6049
6368
  bridge propose --title "..." --set path=value [--set ...] [--file f | --mode theme=dark]
6050
6369
  [--edits edits.json] [--summary "..."] [--branch explore/<name>] [--pr <n>]
6051
- [--product <key>] [--jira DS-42] [--dry-run]
6052
- validate + contrast + new local bridge/* branch (never pushes).
6370
+ [--product <key>] [--jira DS-42] [--suggest | --direct] [--dry-run]
6371
+ validate + contrast, then a suggestion (a GitHub issue a developer
6372
+ applies with bridge apply) or, with "changes": "direct" in
6373
+ bridge.config.json or --direct, a new local bridge/* branch (never pushes).
6053
6374
  --branch: write the edits to the checked-out exploration instead.
6054
6375
  --pr: add them to that branch in review (GitHub token).
6055
6376
  --product: the product it is for (bridge products); --jira: the
@@ -6057,9 +6378,10 @@ Usage:
6057
6378
  bridge brand list [--json] brands: slug, name, default, primary action color, theme overrides
6058
6379
  bridge brand create <slug> --from bridge --primary <hex|hue> --neutral <hex|hue>
6059
6380
  [--name "Acme"] [--accent x] [--link x] [--radius soft|balanced|crisp|i,c,co,o]
6060
- [--heading font] [--text font] [--notes "..."] [--dry-run] [--json]
6061
- new brand: generated ramps, contrast gate for every theme,
6062
- new local bridge/brand-<slug> branch (never pushes, never main).
6381
+ [--heading font] [--text font] [--notes "..."] [--suggest | --direct] [--dry-run] [--json]
6382
+ new brand: generated ramps, contrast gate for every theme, then a
6383
+ suggestion, or in direct mode a new local bridge/brand-<slug> branch
6384
+ (never pushes, never main).
6063
6385
  Exit 1 when blocked
6064
6386
  bridge impact [--base main] [--head HEAD] [--out dir] [--only stories|app] [--threshold 0.03]
6065
6387
  [--plan] [--json] [--image-base url] [--artifact-url url] [--keep-unchanged]
@@ -6073,9 +6395,9 @@ Usage:
6073
6395
  Needs Chrome installed
6074
6396
  bridge history [token] [--json] [--limit n] [--author a] [--tier n] [--version v]
6075
6397
  token history: versions, merged changes, who, when, why
6076
- bridge revert <pr|commit|id> [--dry-run] [--restore-conflicts] [--use-before path@file] [--json]
6077
- undo a merged token change against HEAD: conflicts shown,
6078
- new local bridge/* branch (never pushes, never main)
6398
+ bridge revert <pr|commit|id> [--dry-run] [--restore-conflicts] [--use-before path@file] [--suggest | --direct] [--json]
6399
+ undo a merged token change against HEAD: conflicts shown, then a
6400
+ suggestion, or in direct mode a new local bridge/* branch (never pushes)
6079
6401
  bridge audit <path> [--json [out.json]] [--md [out.md]] [--fail-under 80] [--baseline prev.json]
6080
6402
  [--label "..."] [--ds dir] [--no-regress] [--allowlist known.json]
6081
6403
  design system adoption in a product folder: token and component
@@ -6118,6 +6440,20 @@ Usage:
6118
6440
  a product branch and the library (\`bridge pieces help\`): what it adds
6119
6441
  that the library lacks, the library branch each piece opens (linked
6120
6442
  both ways), its preview in the product branch, the published version
6443
+ bridge suggestions [list | show <n> | link <n> --pr <m>] [--status approved] [--json]
6444
+ suggestions: changes filed as GitHub issues (label bridge-suggestion)
6445
+ that the owners approve and a developer applies (\`bridge suggestions help\`)
6446
+ bridge apply <n> [--write | --patch] [--dry-run] [--accept-edited] [--force-unapproved] [--json]
6447
+ apply suggestion #n on today's code: a new local branch (working tree
6448
+ untouched, nothing pushed), --write into the working tree, --patch for
6449
+ git am. Exit 0 applied or already applied, 1 not approved or blocked,
6450
+ 2 usage, 3 conflict
6451
+ bridge review <n> --approve | --request-changes --message "..." | --decline --message "..."
6452
+ review a suggestion as its owner (counts only for owners in
6453
+ bridge.config.json, on the version you saw)
6454
+ bridge figma [show <n> | drafted <n> | push <diff.json>]
6455
+ component changes sent from Figma (\`bridge figma help\`): exact token
6456
+ changes and branches Claude AI drafts from the spec
6121
6457
  bridge products | bridge settings | bridge coverage [Component]
6122
6458
  products (and this folder's), Studio settings safe to read, which
6123
6459
  components are in Figma
@@ -6128,8 +6464,11 @@ Usage:
6128
6464
  Options:
6129
6465
  --root <dir> repo root (default: current directory)
6130
6466
  --ds <dir> audit: design system repo with bridge.config.json (default: --root, or the nearest one above)
6131
- GITHUB_TOKEN env, optional: lets the MCP server open PRs on a remote repo, and bridge request create
6132
- file issues (else \`gh auth token\`). Never needed for local token work.
6467
+ GITHUB_TOKEN env, optional: lets bridge file suggestions and requests (Issues: read and write) and the
6468
+ MCP server open PRs on a remote repo in direct mode (else \`gh auth token\`). Never needed for
6469
+ local token work in direct mode.
6470
+ --suggest | --direct how a change reaches the code for this command (propose, brand create, revert,
6471
+ docs save, pieces open, figma push). Default: "changes" in bridge.config.json, else suggest.
6133
6472
  `;
6134
6473
  function parse(argv) {
6135
6474
  const out = { _: [], flags: {} };
@@ -6139,23 +6478,32 @@ function parse(argv) {
6139
6478
  const [k, inline] = a.slice(2).split(/=(.*)/s, 2);
6140
6479
  const next = argv[i + 1];
6141
6480
  const fileArg = k === "json" && /\.json$/i.test(next ?? "") || k === "md" && /\.(md|markdown)$/i.test(next ?? "");
6142
- const takesValue = inline === void 0 && next !== void 0 && !next.startsWith("--") && (fileArg || !["force", "json", "md", "strict", "dry-run", "help", "fix", "write", "no-regress", "plan", "keep-unchanged", "restore-conflicts", "breaking", "claude", "all", "offline", "approve"].includes(k));
6481
+ const takesValue = inline === void 0 && next !== void 0 && !next.startsWith("--") && (fileArg || !["force", "json", "md", "strict", "dry-run", "help", "fix", "write", "no-regress", "plan", "keep-unchanged", "restore-conflicts", "breaking", "claude", "all", "offline", "approve", "patch", "accept-edited", "force-unapproved", "suggest", "direct"].includes(k));
6143
6482
  (out.flags[k] ??= []).push(inline ?? (takesValue ? argv[++i] : "true"));
6144
6483
  } else out._.push(a);
6145
6484
  }
6146
6485
  return out;
6147
6486
  }
6148
- var flag4 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
6149
- var has5 = (a, k) => Boolean(a.flags[k]);
6487
+ var flag5 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
6488
+ var has7 = (a, k) => Boolean(a.flags[k]);
6150
6489
  var print = (s) => process.stdout.write(`${s}
6151
6490
  `);
6152
6491
  var modeArg = (s) => s ? Object.fromEntries(s.split(",").map((kv) => kv.split("="))) : void 0;
6492
+ var githubToken = () => process.env.BRIDGE_GITHUB_TOKEN || process.env.GITHUB_TOKEN || defaultIO().ghToken();
6493
+ function printSent(r) {
6494
+ if (r.status === "suggested") print(`suggestion: #${r.number} ${r.url}`);
6495
+ if (r.message) print(String(r.message));
6496
+ if (r.branch && r.status !== "suggested") print(`branch: ${r.branch}`);
6497
+ for (const w of r.warnings ?? []) print(`warning: ${w}`);
6498
+ if (r.next) print(String(r.next));
6499
+ if (r.hint) print(String(r.hint));
6500
+ }
6153
6501
  async function init(a, root) {
6154
6502
  const cfgPath = join6(root, CONFIG_FILE2);
6155
6503
  let config;
6156
- if (existsSync6(cfgPath) && !has5(a, "force")) {
6504
+ if (existsSync6(cfgPath) && !has7(a, "force")) {
6157
6505
  print(`${CONFIG_FILE2} exists, keeping it (--force to regenerate).`);
6158
- config = (await (await BridgeContext6.create({ root })).config()).config;
6506
+ config = (await (await BridgeContext8.create({ root })).config()).config;
6159
6507
  } else {
6160
6508
  const src = new LocalSource(root);
6161
6509
  const files = await src.list("");
@@ -6183,14 +6531,14 @@ async function init(a, root) {
6183
6531
  print(` claude mcp add bridge -- ${desktop.command} ${desktop.args.join(" ")}`);
6184
6532
  }
6185
6533
  async function check(a, root) {
6186
- const ctx = await BridgeContext6.create({ root });
6534
+ const ctx = await BridgeContext8.create({ root });
6187
6535
  const v = await ops.validateTokens(ctx, { includeWarnings: true });
6188
6536
  const c = await ops.checkContrast(ctx, {});
6189
6537
  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;
6538
+ const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 || has7(a, "strict") && warnings.length > 0;
6191
6539
  const protection = await protectionOf(a, root, ctx);
6192
6540
  const roles = await rolesOf(a, root, ctx);
6193
- if (has5(a, "json")) {
6541
+ if (has7(a, "json")) {
6194
6542
  print(JSON.stringify({ ok: !failed, validation: v, contrast: c, ...protection ? { protection: protection.state } : {}, ...roles?.teams.length ? { teams: roles.teams } : {} }, null, 2));
6195
6543
  return failed ? 1 : 0;
6196
6544
  }
@@ -6209,7 +6557,7 @@ async function check(a, root) {
6209
6557
  return failed ? 1 : 0;
6210
6558
  }
6211
6559
  async function protectionOf(a, root, ctx) {
6212
- if (has5(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
6560
+ if (has7(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
6213
6561
  const { config } = await ctx.config();
6214
6562
  const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
6215
6563
  if (!repo) return void 0;
@@ -6217,7 +6565,7 @@ async function protectionOf(a, root, ctx) {
6217
6565
  return protectionReport({ repo, defaultBranch: config.repo?.defaultBranch ?? "main", token });
6218
6566
  }
6219
6567
  async function rolesOf(a, root, ctx) {
6220
- if (has5(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
6568
+ if (has7(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
6221
6569
  const { config } = await ctx.config();
6222
6570
  const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
6223
6571
  if (!repo) return void 0;
@@ -6227,9 +6575,9 @@ async function rolesOf(a, root, ctx) {
6227
6575
  async function tokens(a, root) {
6228
6576
  const q = a._.slice(1).join(" ");
6229
6577
  if (!q) throw new UsageError("bridge tokens <query>");
6230
- const ctx = await BridgeContext6.create({ root });
6231
- 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));
6578
+ const ctx = await BridgeContext8.create({ root });
6579
+ const r = await ops.searchTokens(ctx, { query: q, limit: Number(flag5(a, "limit") ?? 20) });
6580
+ if (has7(a, "json")) return print(JSON.stringify(r, null, 2));
6233
6581
  if (!r.results.length) return print(`No token matches "${q}".`);
6234
6582
  const w = Math.max(...r.results.map((x) => x.path.length));
6235
6583
  for (const t of r.results) print(`${t.path.padEnd(w)} ${String(t.value).padEnd(10)} ${(t.tier ?? "").padEnd(9)} ${t.description ?? ""}`);
@@ -6237,7 +6585,7 @@ async function tokens(a, root) {
6237
6585
  async function diff(a, root) {
6238
6586
  const base = a._[1];
6239
6587
  if (!base) throw new UsageError("bridge diff <base> (a git ref: main, HEAD~1, a sha)");
6240
- const ctx = await BridgeContext6.create({ root });
6588
+ const ctx = await BridgeContext8.create({ root });
6241
6589
  const { config } = await ctx.config();
6242
6590
  const before = await core.loadTokenSet(new GitRefSource(root, base), config);
6243
6591
  const after = await ctx.tokens();
@@ -6246,11 +6594,11 @@ async function diff(a, root) {
6246
6594
  const delta2 = pairs2.length ? core.contrastDelta(core.runContrast(before, pairs2, config.contrast?.modes), core.runContrast(after, pairs2, config.contrast?.modes)) : [];
6247
6595
  const tier = core.classify(changes, [], config);
6248
6596
  const imp = core.impact(before, after, changes, 30);
6249
- if (has5(a, "json")) {
6597
+ if (has7(a, "json")) {
6250
6598
  print(JSON.stringify({ base, changes, contrast: delta2, tier, impact: imp }, null, 2));
6251
6599
  return 0;
6252
6600
  }
6253
- if (has5(a, "md")) {
6601
+ if (has7(a, "md")) {
6254
6602
  print(`## Token changes since \`${base}\`
6255
6603
 
6256
6604
  ${core.tokenTable(changes)}
@@ -6285,26 +6633,27 @@ ${imp.total} other token(s) change value through aliases:`);
6285
6633
  return delta2.some((d) => d.passBefore && !d.passAfter) ? 1 : 0;
6286
6634
  }
6287
6635
  async function propose(a, root) {
6288
- const title = flag4(a, "title");
6636
+ const title = flag5(a, "title");
6289
6637
  if (!title) throw new UsageError('bridge propose --title "Darken muted text" --set color.text.muted={palette.neutral.700} --mode theme=light');
6290
6638
  const edits = [];
6291
- const file = flag4(a, "file");
6292
- const mode2 = modeArg(flag4(a, "mode"));
6639
+ const file = flag5(a, "file");
6640
+ const mode2 = modeArg(flag5(a, "mode"));
6293
6641
  for (const s of a.flags.set ?? []) {
6294
6642
  const i = s.indexOf("=");
6295
6643
  if (i < 0) throw new UsageError(`--set needs path=value, got "${s}"`);
6296
6644
  edits.push({ op: "set", path: s.slice(0, i), value: s.slice(i + 1), ...file ? { file } : {}, ...mode2 ? { mode: mode2 } : {} });
6297
6645
  }
6298
- const editsFile = flag4(a, "edits");
6299
- if (editsFile) edits.push(...JSON.parse(await readFile7(resolve4(editsFile), "utf8")));
6646
+ const editsFile = flag5(a, "edits");
6647
+ if (editsFile) edits.push(...JSON.parse(await readFile7(resolve5(editsFile), "utf8")));
6300
6648
  if (!edits.length) throw new UsageError("Give at least one --set path=value or --edits file.json");
6301
- const pr = flag4(a, "pr");
6302
- 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 } : {} });
6304
- const summary = flag4(a, "summary");
6305
- const branch = flag4(a, "branch");
6306
- const jira = flag4(a, "jira");
6307
- const product = flag4(a, "product");
6649
+ const pr = flag5(a, "pr");
6650
+ const changes = changesFlag(a);
6651
+ const token = pr || changes !== "direct" ? githubToken() : void 0;
6652
+ const ctx = await BridgeContext8.create({ root, ...token ? { token } : {} });
6653
+ const summary = flag5(a, "summary");
6654
+ const branch = flag5(a, "branch");
6655
+ const jira = flag5(a, "jira");
6656
+ const product = flag5(a, "product");
6308
6657
  const r = await ops.proposeTokenChange(ctx, {
6309
6658
  title,
6310
6659
  edits,
@@ -6313,34 +6662,36 @@ async function propose(a, root) {
6313
6662
  ...pr ? { pr } : {},
6314
6663
  ...jira ? { jira } : {},
6315
6664
  ...product ? { product } : {},
6316
- dryRun: has5(a, "dry-run")
6665
+ ...changes ? { changes } : {},
6666
+ source: "cli",
6667
+ dryRun: has7(a, "dry-run")
6317
6668
  });
6318
- if (has5(a, "json")) print(JSON.stringify(r, null, 2));
6669
+ if (has7(a, "json")) print(JSON.stringify(r, null, 2));
6319
6670
  else {
6320
6671
  print(`status: ${r.status}${r.why ? ` (${r.why})` : ""}`);
6321
6672
  if (r.tier) print(`tier ${r.tier}, ${r.bump}`);
6322
6673
  for (const c of r.changes ?? []) print(` ${c.kind} ${c.path}: ${c.before ?? ""} -> ${c.after ?? ""}`);
6323
- if (r.branch) print(`branch: ${r.branch}`);
6324
- if (r.next) print(String(r.next));
6325
- if (r.hint) print(String(r.hint));
6674
+ printSent(r);
6326
6675
  if (r.status === "dry-run") print(`
6327
- ${r.prBody}`);
6676
+ ${r.suggestion?.body ?? r.prBody}`);
6328
6677
  }
6329
6678
  return r.status === "blocked" ? 1 : 0;
6330
6679
  }
6331
6680
  async function brandCmd(a, root) {
6332
6681
  const sub = a._[1];
6333
- const ctx = await BridgeContext6.create({ root });
6682
+ const changes = changesFlag(a);
6683
+ const token = sub === "create" && changes !== "direct" ? githubToken() : void 0;
6684
+ const ctx = await BridgeContext8.create({ root, ...token ? { token } : {} });
6334
6685
  if (sub === "list") {
6335
6686
  const r2 = await brand.listBrands(ctx);
6336
- if (has5(a, "json")) print(JSON.stringify(r2.brands, null, 2));
6687
+ if (has7(a, "json")) print(JSON.stringify(r2.brands, null, 2));
6337
6688
  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
6689
  return 0;
6339
6690
  }
6340
6691
  if (sub !== "create") throw new UsageError('bridge brand list | bridge brand create <slug> --from bridge --primary "#1F5F3A" --neutral "#2A2F2C"');
6341
6692
  const slug = a._[2];
6342
6693
  if (!slug) throw new UsageError('bridge brand create <slug> --from bridge --primary "#1F5F3A" --neutral "#2A2F2C"');
6343
- const opt = (k) => flag4(a, k);
6694
+ const opt = (k) => flag5(a, k);
6344
6695
  const fonts = { ...opt("heading") ? { heading: opt("heading") } : {}, ...opt("text") ? { text: opt("text") } : {} };
6345
6696
  const r = await brand.proposeBrand(ctx, {
6346
6697
  slug,
@@ -6353,9 +6704,11 @@ async function brandCmd(a, root) {
6353
6704
  ...opt("radius") ? { radius: opt("radius") } : {},
6354
6705
  ...Object.keys(fonts).length ? { fonts } : {},
6355
6706
  ...opt("notes") ? { notes: opt("notes") } : {},
6356
- dryRun: has5(a, "dry-run")
6707
+ ...changes ? { changes } : {},
6708
+ source: "cli",
6709
+ dryRun: has7(a, "dry-run")
6357
6710
  });
6358
- if (has5(a, "json")) {
6711
+ if (has7(a, "json")) {
6359
6712
  const { fileContents: _f, ...rest } = r;
6360
6713
  print(JSON.stringify(rest, null, 2));
6361
6714
  return r.status === "blocked" ? 1 : 0;
@@ -6370,13 +6723,11 @@ async function brandCmd(a, root) {
6370
6723
  for (const i of r.issues) print(` ${i.level}: ${i.message}`);
6371
6724
  print("files:");
6372
6725
  for (const f of r.files) print(` ${f.action} ${f.path}`);
6373
- if (r.branch) print(`branch: ${r.branch}`);
6374
- if (r.next) print(String(r.next));
6375
- if (r.hint) print(String(r.hint));
6726
+ printSent(r);
6376
6727
  return r.status === "blocked" ? 1 : 0;
6377
6728
  }
6378
6729
  async function figmaReferences(a, root) {
6379
- let sha = flag4(a, "sha");
6730
+ let sha = flag5(a, "sha");
6380
6731
  if (!sha) {
6381
6732
  try {
6382
6733
  sha = execFileSync2("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
@@ -6384,49 +6735,49 @@ async function figmaReferences(a, root) {
6384
6735
  sha = "unknown";
6385
6736
  }
6386
6737
  }
6387
- const only = (flag4(a, "only") ?? "").split(",").filter(Boolean);
6738
+ const only = (flag5(a, "only") ?? "").split(",").filter(Boolean);
6388
6739
  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"),
6740
+ storybook: resolve5(root, flag5(a, "storybook") ?? "storybook-static"),
6741
+ specs: resolve5(root, flag5(a, "specs") ?? "packages/react/component-specs.json"),
6742
+ out: resolve5(root, flag5(a, "out") ?? "figma-references"),
6392
6743
  sha,
6393
6744
  ...only.length ? { only } : {},
6394
- log: has5(a, "json") ? void 0 : (line) => print(line)
6745
+ log: has7(a, "json") ? void 0 : (line) => print(line)
6395
6746
  });
6396
- if (has5(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
6747
+ if (has7(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
6397
6748
  return 0;
6398
6749
  }
6399
6750
  async function impact(a, root) {
6400
- const only = (flag4(a, "only") ?? "").split(",").filter(Boolean);
6751
+ const only = (flag5(a, "only") ?? "").split(",").filter(Boolean);
6401
6752
  for (const x of only) if (x !== "stories" && x !== "app") throw new UsageError(`--only takes stories or app, got "${x}"`);
6402
- const num2 = (k) => flag4(a, k) !== void 0 ? Number(flag4(a, k)) : void 0;
6753
+ const num2 = (k) => flag5(a, k) !== void 0 ? Number(flag5(a, k)) : void 0;
6403
6754
  const threshold = num2("threshold");
6404
6755
  if (threshold !== void 0 && !(threshold >= 0 && threshold <= 1)) throw new UsageError("--threshold is between 0 and 1 (pixelmatch color distance)");
6405
- const imageBase = flag4(a, "image-base");
6406
- const artifactUrl = flag4(a, "artifact-url");
6407
- const out = flag4(a, "out");
6408
- const cacheDir = flag4(a, "cache");
6756
+ const imageBase = flag5(a, "image-base");
6757
+ const artifactUrl = flag5(a, "artifact-url");
6758
+ const out = flag5(a, "out");
6759
+ const cacheDir = flag5(a, "cache");
6409
6760
  const maxStories = num2("max-stories");
6410
6761
  const r = await runImpact({
6411
6762
  root,
6412
- base: flag4(a, "base") ?? "main",
6413
- head: flag4(a, "head") ?? "HEAD",
6414
- ...out ? { out: resolve4(out) } : {},
6763
+ base: flag5(a, "base") ?? "main",
6764
+ head: flag5(a, "head") ?? "HEAD",
6765
+ ...out ? { out: resolve5(out) } : {},
6415
6766
  ...only.length ? { only } : {},
6416
6767
  ...threshold !== void 0 ? { threshold } : {},
6417
- ...cacheDir ? { cacheDir: resolve4(cacheDir) } : {},
6768
+ ...cacheDir ? { cacheDir: resolve5(cacheDir) } : {},
6418
6769
  ...maxStories ? { maxStoriesPerComponent: maxStories } : {},
6419
6770
  ...imageBase !== void 0 ? { imageBase } : {},
6420
6771
  ...artifactUrl ? { artifactUrl } : {},
6421
- plan: has5(a, "plan"),
6422
- keepUnchanged: has5(a, "keep-unchanged")
6772
+ plan: has7(a, "plan"),
6773
+ keepUnchanged: has7(a, "keep-unchanged")
6423
6774
  });
6424
- if (has5(a, "json")) {
6775
+ if (has7(a, "json")) {
6425
6776
  print(JSON.stringify({ summary: r.summary, manifest: r.manifestPath, comment: r.commentPath ?? null, ...r.manifest.summary, selection: r.manifest.selection }, null, 2));
6426
6777
  return 0;
6427
6778
  }
6428
6779
  const sel = r.manifest.selection;
6429
- if (has5(a, "plan")) {
6780
+ if (has7(a, "plan")) {
6430
6781
  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
6782
  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
6783
  print(` components: ${sel.components.map((c) => c.id).join(", ") || "none"}`);
@@ -6442,12 +6793,12 @@ async function impact(a, root) {
6442
6793
  }
6443
6794
  var day = (iso) => iso.slice(0, 10);
6444
6795
  async function historyCmd(a, root) {
6445
- const ctx = await BridgeContext6.create({ root });
6796
+ const ctx = await BridgeContext8.create({ root });
6446
6797
  const token = a._[1];
6447
- const limit = Number(flag4(a, "limit") ?? 20);
6798
+ const limit = Number(flag5(a, "limit") ?? 20);
6448
6799
  if (token) {
6449
6800
  const r2 = await hist.getTokenHistory(ctx, { path: token, limit });
6450
- if (has5(a, "json")) return print(JSON.stringify(r2, null, 2)), 0;
6801
+ if (has7(a, "json")) return print(JSON.stringify(r2, null, 2)), 0;
6451
6802
  if (!r2.total) return print(`No recorded change of ${r2.path} in ${r2.source}.`), 0;
6452
6803
  print(`${r2.path}: ${r2.total} change(s) (${r2.source})
6453
6804
  `);
@@ -6458,9 +6809,9 @@ async function historyCmd(a, root) {
6458
6809
  }
6459
6810
  return 0;
6460
6811
  }
6461
- 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;
6812
+ const tier = flag5(a, "tier");
6813
+ const r = await hist.getHistory(ctx, { limit, ...flag5(a, "author") ? { author: flag5(a, "author") } : {}, ...tier ? { tier: Number(tier) } : {}, ...flag5(a, "version") ? { version: flag5(a, "version") } : {}, ...has7(a, "breaking") ? { breaking: true } : {} });
6814
+ if (has7(a, "json")) return print(JSON.stringify(r, null, 2)), 0;
6464
6815
  print(`${r.source}${r.currentVersion ? `, package version ${r.currentVersion}` : ""}`);
6465
6816
  if (!r.total) return print(r.note ?? "No token change matches."), 0;
6466
6817
  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 +6829,12 @@ ${r.truncated}`);
6478
6829
  async function revertCmd(a, root) {
6479
6830
  const target = a._[1];
6480
6831
  if (!target) throw new UsageError("bridge revert <pr|commit|id> [--dry-run] (ids from `bridge history`)");
6481
- const ctx = await BridgeContext6.create({ root });
6832
+ const changes = changesFlag(a);
6833
+ const token = changes !== "direct" ? githubToken() : void 0;
6834
+ const ctx = await BridgeContext8.create({ root, ...token ? { token } : {} });
6482
6835
  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")) {
6836
+ const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts: has7(a, "restore-conflicts"), dryRun: has7(a, "dry-run"), ...changes ? { changes } : {}, source: "cli" });
6837
+ if (has7(a, "json")) {
6485
6838
  print(JSON.stringify(r, null, 2));
6486
6839
  return r.status === "blocked" ? 1 : 0;
6487
6840
  }
@@ -6496,35 +6849,34 @@ async function revertCmd(a, root) {
6496
6849
  }
6497
6850
  for (const s of r.skipped ?? []) print(` skipped ${s}`);
6498
6851
  if (r.contrast?.moved) print(`contrast: ${r.contrast.moved} pair(s) move, ${r.contrast.newFailures.length} new failure(s)`);
6499
- if (r.branch) print(`branch: ${r.branch}`);
6500
- if (r.next) print(String(r.next));
6852
+ printSent(r);
6501
6853
  if (r.status === "dry-run") print(`
6502
- ${r.prBody}`);
6854
+ ${r.suggestion?.body ?? r.prBody}`);
6503
6855
  return r.status === "blocked" ? 1 : 0;
6504
6856
  }
6505
6857
  async function audit(a, root) {
6506
6858
  const target = a._[1];
6507
6859
  if (!target) throw new UsageError("bridge audit <path> (a product folder or file that uses the design system)");
6508
6860
  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);
6861
+ const dsRoot = flag5(a, "ds") ? resolve5(flag5(a, "ds")) : existsSync6(join6(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
6510
6862
  if (!dsRoot) throw new UsageError(`No ${CONFIG_FILE2} found: run bridge audit from the design system repo or pass --ds <dir>.`);
6511
6863
  const ds = loadDesignSystem(dsRoot);
6512
- if (has5(a, "fix")) {
6513
- const write = has5(a, "write");
6864
+ if (has7(a, "fix")) {
6865
+ const write = has7(a, "write");
6514
6866
  const changes = fixCss(target, ds.index, write);
6515
6867
  const n = changes.reduce((acc, c) => acc + c.edits.length, 0);
6516
6868
  for (const c of changes) for (const e of c.edits) print(`${c.file}:${e.line}:${e.column} ${e.from} -> ${e.to}`);
6517
6869
  print(n ? `${write ? "Replaced" : "Would replace"} ${n} value(s) in ${changes.length} file(s) with exact token matches.${write ? "" : " Run again with --write to apply."}` : "Nothing to fix: no CSS value equals a token exactly.");
6518
6870
  return 0;
6519
6871
  }
6520
- const baselinePath = flag4(a, "baseline");
6872
+ const baselinePath = flag5(a, "baseline");
6521
6873
  let baseline;
6522
6874
  if (baselinePath) {
6523
6875
  if (!existsSync6(baselinePath)) throw new UsageError(`Baseline not found: ${baselinePath}`);
6524
6876
  baseline = JSON.parse(await readFile7(baselinePath, "utf8"));
6525
6877
  }
6526
- const label = flag4(a, "label");
6527
- const allowlistPath = flag4(a, "allowlist");
6878
+ const label = flag5(a, "label");
6879
+ const allowlistPath = flag5(a, "allowlist");
6528
6880
  if (allowlistPath && !existsSync6(allowlistPath)) throw new UsageError(`Allowlist not found: ${allowlistPath}`);
6529
6881
  let allowlist;
6530
6882
  try {
@@ -6534,8 +6886,8 @@ async function audit(a, root) {
6534
6886
  }
6535
6887
  const report = runAudit(target, { ds: dsRoot, version: VERSION, ...label ? { label } : {}, ...baseline ? { baseline } : {}, ...allowlist ? { allowlist } : {} });
6536
6888
  const md = auditMarkdown(report);
6537
- const jsonOut = flag4(a, "json");
6538
- const mdOut = flag4(a, "md");
6889
+ const jsonOut = flag5(a, "json");
6890
+ const mdOut = flag5(a, "md");
6539
6891
  if (jsonOut && jsonOut !== "true") await writeFile5(jsonOut, `${JSON.stringify(report, null, 2)}
6540
6892
  `);
6541
6893
  if (mdOut && mdOut !== "true") await writeFile5(mdOut, `${md}
@@ -6575,7 +6927,7 @@ async function audit(a, root) {
6575
6927
  for (const f of report.files) for (const e of f.ignoreErrors ?? []) process.stderr.write(`${report.target.path}/${f.path}:${e.line}: ${e.message}
6576
6928
  `);
6577
6929
  let code = 0;
6578
- const failUnder = flag4(a, "fail-under");
6930
+ const failUnder = flag5(a, "fail-under");
6579
6931
  if (failUnder !== void 0) {
6580
6932
  const min = Number(failUnder);
6581
6933
  if (!Number.isFinite(min)) throw new UsageError("--fail-under needs a number, e.g. --fail-under 80");
@@ -6586,7 +6938,7 @@ async function audit(a, root) {
6586
6938
  code = 1;
6587
6939
  }
6588
6940
  }
6589
- if (has5(a, "no-regress") && report.baseline) {
6941
+ if (has7(a, "no-regress") && report.baseline) {
6590
6942
  const b = report.baseline;
6591
6943
  if ((b.tokenAdoption.delta ?? 0) < 0 || (b.componentAdoption.delta ?? 0) < 0 || b.hardcoded.delta > 0) {
6592
6944
  process.stderr.write("bridge audit: adoption regressed since the baseline\n");
@@ -6598,9 +6950,9 @@ async function audit(a, root) {
6598
6950
  async function docsCmd(a, root) {
6599
6951
  const [, sub, name] = a._;
6600
6952
  if (sub !== "draft" || !name) throw new UsageError("Usage: bridge docs draft <Component> [--json]");
6601
- const ctx = await BridgeContext6.create({ root });
6953
+ const ctx = await BridgeContext8.create({ root });
6602
6954
  const d = await ops.draftDocsOp(ctx, { name });
6603
- if (has5(a, "json")) {
6955
+ if (has7(a, "json")) {
6604
6956
  print(JSON.stringify({ component: d.component, path: d.path, exists: d.exists, prefill: d.prefill, prompt: d.prompt }, null, 2));
6605
6957
  return 0;
6606
6958
  }
@@ -6631,16 +6983,20 @@ async function main() {
6631
6983
  const argv = process.argv.slice(2);
6632
6984
  const a = parse(argv);
6633
6985
  const cmd = a._[0];
6634
- const root = resolve4(flag4(a, "root") ?? process.cwd());
6986
+ const root = resolve5(flag5(a, "root") ?? process.cwd());
6635
6987
  if (cmd === "request") return runRequest(a, root);
6636
6988
  if (cmd === "explore") return runExplore(a, root);
6637
6989
  if (cmd === "branch") return runBranch(a, root);
6638
6990
  if (cmd === "pieces") return runPieces(a, root);
6991
+ if (cmd === "figma") return runFigma(a, root);
6992
+ if (cmd === "suggestions" || cmd === "suggest") return runSuggestions(a, root);
6993
+ if (cmd === "apply") return runApply(a, root);
6994
+ if (cmd === "review") return runReview(a, root);
6639
6995
  if (cmd === "products" || cmd === "settings" || cmd === "coverage") return runStudioRead(cmd, a, root);
6640
6996
  if (cmd === "docs" && a._[1] === "save") return runDocsSave(a, root);
6641
- if (!cmd || has5(a, "help") || cmd === "help") {
6997
+ if (!cmd || has7(a, "help") || cmd === "help") {
6642
6998
  print(HELP);
6643
- return cmd || has5(a, "help") ? 0 : 2;
6999
+ return cmd || has7(a, "help") ? 0 : 2;
6644
7000
  }
6645
7001
  if (cmd === "--version" || cmd === "version") return print(VERSION), 0;
6646
7002
  switch (cmd) {