@bridge-ds/cli 0.4.0 → 0.6.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 +643 -149
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { existsSync as existsSync6 } from "node:fs";
5
- import { readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
6
- import { isAbsolute, join as join6, relative as relative4, resolve as resolve3 } from "node:path";
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";
7
7
 
8
8
  // ../core/src/audit/types.ts
9
9
  var AUDIT_SCHEMA_VERSION = 1;
@@ -2158,57 +2158,6 @@ function planCssFixes(text, lang, ds) {
2158
2158
  return { edits: edits.map(({ start: _s, end: _e, ...e }) => e), output };
2159
2159
  }
2160
2160
 
2161
- // ../core/src/node/git-local.ts
2162
- import { execFile } from "node:child_process";
2163
-
2164
- // ../core/src/governance.ts
2165
- var DEFAULT_PROTECTED_BRANCHES = ["main", "master"];
2166
- var ProtectedBranchError = class extends Error {
2167
- constructor(message) {
2168
- super(message);
2169
- this.name = "ProtectedBranchError";
2170
- }
2171
- };
2172
- function assertSafeBranch(branch, rules = {}) {
2173
- const name = branch.replace(/^refs\/heads\//, "").trim();
2174
- const blocked = /* @__PURE__ */ new Set([...rules.protectedBranches ?? DEFAULT_PROTECTED_BRANCHES, ...rules.base ? [rules.base] : []]);
2175
- if (!name || blocked.has(name)) {
2176
- throw new ProtectedBranchError(`Refusing to write to protected branch "${name}". Bridge only creates new branches and pull requests.`);
2177
- }
2178
- if (rules.branchPrefix && !name.startsWith(rules.branchPrefix)) {
2179
- throw new ProtectedBranchError(`Branch "${name}" must start with "${rules.branchPrefix}".`);
2180
- }
2181
- }
2182
-
2183
- // ../core/src/node/git-local.ts
2184
- function git(root, args, opts = {}) {
2185
- return new Promise((resolve4, reject) => {
2186
- const child = execFile("git", args, { cwd: root, env: { ...process.env, ...opts.env }, maxBuffer: 32 * 1024 * 1024 }, (err, stdout, stderr) => {
2187
- if (err) reject(new Error(`git ${args.join(" ")}: ${stderr || err.message}`.trim()));
2188
- else resolve4(stdout);
2189
- });
2190
- if (opts.input !== void 0) child.stdin.end(opts.input);
2191
- });
2192
- }
2193
- async function showFile(root, ref, path) {
2194
- try {
2195
- return await git(root, ["show", `${ref}:${path}`]);
2196
- } catch {
2197
- return null;
2198
- }
2199
- }
2200
- async function listFilesAt(root, ref) {
2201
- return (await git(root, ["ls-tree", "-r", "--name-only", ref])).split("\n").filter(Boolean);
2202
- }
2203
- async function originRepo(root) {
2204
- try {
2205
- const url = (await git(root, ["remote", "get-url", "origin"])).trim();
2206
- return /github\.com[:/]([\w.-]+\/[\w.-]+?)(\.git)?$/.exec(url)?.[1];
2207
- } catch {
2208
- return void 0;
2209
- }
2210
- }
2211
-
2212
2161
  // ../core/src/previews.ts
2213
2162
  var PREVIEW_APP_LABELS = { docs: "Docs", storybook: "Storybook", admin: "Admin demo" };
2214
2163
 
@@ -2432,6 +2381,47 @@ function visualImpactProblems(raw) {
2432
2381
  return out;
2433
2382
  }
2434
2383
 
2384
+ // ../core/src/products.ts
2385
+ var DEFAULT_PRODUCTS = [
2386
+ { key: "admin", name: "Admin demo", path: "apps/admin", framework: "react" },
2387
+ { key: "library", name: "Library", default: true }
2388
+ ];
2389
+ var KEY = /^[a-z0-9][a-z0-9-]*$/;
2390
+ var REPO = /^[\w.-]+\/[\w.-]+$/;
2391
+ var FIELDS = ["key", "name", "repo", "path", "preview", "framework", "default"];
2392
+ function productProblems(raw) {
2393
+ if (raw === void 0) return [];
2394
+ if (!Array.isArray(raw)) return ['"products" must be a list of { "key", "name" } objects'];
2395
+ if (!raw.length) return ['"products" must list at least one product, or be left out to use the defaults'];
2396
+ const problems = [];
2397
+ const seen = /* @__PURE__ */ new Set();
2398
+ let defaults = 0;
2399
+ raw.forEach((p, i) => {
2400
+ const at = `"products[${i}]`;
2401
+ if (!p || typeof p !== "object" || Array.isArray(p)) {
2402
+ problems.push(`${at}" must be an object with a "key" and a "name"`);
2403
+ return;
2404
+ }
2405
+ const o = p;
2406
+ for (const k of Object.keys(o)) if (!FIELDS.includes(k)) problems.push(`${at}.${k}" is not a known field (${FIELDS.join(", ")})`);
2407
+ if (typeof o.key !== "string" || !KEY.test(o.key)) problems.push(`${at}.key" must be lowercase letters, digits and dashes, such as "admin"`);
2408
+ else if (seen.has(o.key)) problems.push(`${at}.key" ("${o.key}") is used twice: give each product its own key`);
2409
+ else seen.add(o.key);
2410
+ if (typeof o.name !== "string" || !o.name.trim()) problems.push(`${at}.name" must be the name people read, such as "Admin demo"`);
2411
+ if (o.repo !== void 0 && (typeof o.repo !== "string" || !REPO.test(o.repo))) problems.push(`${at}.repo" must be "owner/name"`);
2412
+ if (o.path !== void 0) {
2413
+ const bad = typeof o.path !== "string" || !o.path.trim() || o.path.startsWith("/") || o.path.split("/").includes("..");
2414
+ if (bad) problems.push(`${at}.path" must be a folder relative to the repository root, without a leading "/" or "..", such as "apps/admin"`);
2415
+ }
2416
+ if (o.preview !== void 0 && (typeof o.preview !== "string" || !/^https?:\/\//.test(o.preview))) problems.push(`${at}.preview" must be an http(s) URL template, such as "https://app-git-{branch}.vercel.app"`);
2417
+ if (o.framework !== void 0 && (typeof o.framework !== "string" || !o.framework.trim())) problems.push(`${at}.framework" must be a name such as "react", "vue" or "other"`);
2418
+ if (o.default !== void 0 && typeof o.default !== "boolean") problems.push(`${at}.default" must be true or false`);
2419
+ if (o.default === true) defaults++;
2420
+ });
2421
+ if (defaults > 1) problems.push('"products" marks more than one product as default: keep "default": true on the library only');
2422
+ return problems;
2423
+ }
2424
+
2435
2425
  // ../core/src/config.ts
2436
2426
  var CONFIG_FILE = "bridge.config.json";
2437
2427
  var DEFAULT_OVERRIDE = { allowed: [], requireReason: true, minReasonLength: 20 };
@@ -2572,6 +2562,7 @@ function defineConfig(input) {
2572
2562
  } : {},
2573
2563
  ...g.rights ? { rights: normalizeRights(g.rights) } : {}
2574
2564
  },
2565
+ products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
2575
2566
  ...input.audit ? { audit: { ...input.audit } } : {},
2576
2567
  ...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
2577
2568
  ...input.integrations ? { integrations: { ...input.integrations } } : {},
@@ -2694,6 +2685,7 @@ function loadConfig(source) {
2694
2685
  }
2695
2686
  }
2696
2687
  problems.push(...visualImpactProblems(d.visualImpact));
2688
+ problems.push(...productProblems(d.products));
2697
2689
  const audit2 = d.audit;
2698
2690
  if (audit2 !== void 0) {
2699
2691
  if (!isObj(audit2)) problems.push('"audit" must be an object');
@@ -2723,6 +2715,57 @@ function loadConfig(source) {
2723
2715
  return defineConfig(d);
2724
2716
  }
2725
2717
 
2718
+ // ../core/src/node/git-local.ts
2719
+ import { execFile } from "node:child_process";
2720
+
2721
+ // ../core/src/governance.ts
2722
+ var DEFAULT_PROTECTED_BRANCHES = ["main", "master"];
2723
+ var ProtectedBranchError = class extends Error {
2724
+ constructor(message) {
2725
+ super(message);
2726
+ this.name = "ProtectedBranchError";
2727
+ }
2728
+ };
2729
+ function assertSafeBranch(branch, rules = {}) {
2730
+ const name = branch.replace(/^refs\/heads\//, "").trim();
2731
+ const blocked = /* @__PURE__ */ new Set([...rules.protectedBranches ?? DEFAULT_PROTECTED_BRANCHES, ...rules.base ? [rules.base] : []]);
2732
+ if (!name || blocked.has(name)) {
2733
+ throw new ProtectedBranchError(`Refusing to write to protected branch "${name}". Bridge only creates new branches and pull requests.`);
2734
+ }
2735
+ if (rules.branchPrefix && !name.startsWith(rules.branchPrefix)) {
2736
+ throw new ProtectedBranchError(`Branch "${name}" must start with "${rules.branchPrefix}".`);
2737
+ }
2738
+ }
2739
+
2740
+ // ../core/src/node/git-local.ts
2741
+ function git(root, args, opts = {}) {
2742
+ return new Promise((resolve5, reject) => {
2743
+ const child = execFile("git", args, { cwd: root, env: { ...process.env, ...opts.env }, maxBuffer: 32 * 1024 * 1024 }, (err, stdout, stderr) => {
2744
+ if (err) reject(new Error(`git ${args.join(" ")}: ${stderr || err.message}`.trim()));
2745
+ else resolve5(stdout);
2746
+ });
2747
+ if (opts.input !== void 0) child.stdin.end(opts.input);
2748
+ });
2749
+ }
2750
+ async function showFile(root, ref, path) {
2751
+ try {
2752
+ return await git(root, ["show", `${ref}:${path}`]);
2753
+ } catch {
2754
+ return null;
2755
+ }
2756
+ }
2757
+ async function listFilesAt(root, ref) {
2758
+ return (await git(root, ["ls-tree", "-r", "--name-only", ref])).split("\n").filter(Boolean);
2759
+ }
2760
+ async function originRepo(root) {
2761
+ try {
2762
+ const url = (await git(root, ["remote", "get-url", "origin"])).trim();
2763
+ return /github\.com[:/]([\w.-]+\/[\w.-]+?)(\.git)?$/.exec(url)?.[1];
2764
+ } catch {
2765
+ return void 0;
2766
+ }
2767
+ }
2768
+
2726
2769
  // ../core/src/layered-review.ts
2727
2770
  var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
2728
2771
  function diffLayered(before, after) {
@@ -3624,7 +3667,7 @@ var GitHubClient = class _GitHubClient {
3624
3667
  }
3625
3668
  async getPullRequest(n) {
3626
3669
  const r = await this.request("GET", `${this.repoPath}/pulls/${n}`);
3627
- return { ...toSummary(r), body: r.body ?? "", mergeable: r.mergeable ?? null };
3670
+ return { ...toSummary(r), body: r.body ?? "", mergeable: r.mergeable ?? null, requestedReviewers: (r.requested_reviewers ?? []).map((x) => x.login) };
3628
3671
  }
3629
3672
  async listPullRequestFiles(n) {
3630
3673
  const r = await this.request("GET", `${this.repoPath}/pulls/${n}/files?per_page=100`);
@@ -3701,6 +3744,111 @@ var GitHubClient = class _GitHubClient {
3701
3744
  async removeLabel(issueNumber2, label) {
3702
3745
  await this.request("DELETE", `${this.repoPath}/issues/${issueNumber2}/labels/${encodeURIComponent(label)}`);
3703
3746
  }
3747
+ // --- branch actions shared by Studio, the MCP server and the CLI (branch-actions.ts) ---
3748
+ /** The account the token belongs to (GET /user). Needs a token. */
3749
+ async getViewer() {
3750
+ this.assertToken("Reading who you are on GitHub");
3751
+ const r = await this.request("GET", "/user");
3752
+ return { login: r.login };
3753
+ }
3754
+ /** The repository's merge settings and the viewer's permissions (unknown settings count as allowed). */
3755
+ async getMergeSettings() {
3756
+ return this.request("GET", this.repoPath);
3757
+ }
3758
+ /**
3759
+ * Merges a pull request, only at `sha` (GitHub refuses when the branch moved meanwhile). The repository's branch
3760
+ * rules still apply: GitHub refuses a merge they block, whatever Bridge says.
3761
+ */
3762
+ async mergePullRequest(n, input) {
3763
+ this.assertToken("Merging a branch");
3764
+ const r = await this.request("PUT", `${this.repoPath}/pulls/${n}/merge`, {
3765
+ merge_method: input.method,
3766
+ sha: input.sha,
3767
+ ...input.commitMessage !== void 0 && input.method !== "rebase" ? { commit_message: input.commitMessage } : {}
3768
+ });
3769
+ return { sha: r?.sha ?? "", merged: r?.merged ?? true };
3770
+ }
3771
+ /** GitHub's "Update branch": merges the base into the pull request, only when its head is still `expectedHeadSha`. */
3772
+ async updatePullRequestBranch(n, expectedHeadSha) {
3773
+ this.assertToken("Updating a branch from main");
3774
+ await this.request("PUT", `${this.repoPath}/pulls/${n}/update-branch`, { expected_head_sha: expectedHeadSha });
3775
+ }
3776
+ /**
3777
+ * Merges the branch `head` into the branch `base` (GitHub's merges API): a merge commit on `base`, or nothing when
3778
+ * `base` already has every commit of `head`. A conflict throws a GitHubError with status 409. Refuses protected
3779
+ * branches and, with a branch prefix, a `base` without it.
3780
+ */
3781
+ async mergeIntoBranch(base, head, message) {
3782
+ this.assertToken("Merging a branch into another");
3783
+ this.assertWritableBranch(base);
3784
+ const r = await this.request("POST", `${this.repoPath}/merges`, { base, head, commit_message: message });
3785
+ return r?.sha ? { status: "merged", sha: r.sha } : { status: "up-to-date", sha: null };
3786
+ }
3787
+ /** Posts a comment on an issue or pull request and returns its id and link. */
3788
+ async createIssueComment(n, body) {
3789
+ this.assertToken("Commenting");
3790
+ const r = await this.request("POST", `${this.repoPath}/issues/${n}/comments`, { body });
3791
+ return { id: r.id, url: r.html_url };
3792
+ }
3793
+ /** Replaces the text of a comment. */
3794
+ async updateIssueComment(id, body) {
3795
+ this.assertToken("Editing a comment");
3796
+ await this.request("PATCH", `${this.repoPath}/issues/comments/${id}`, { body });
3797
+ }
3798
+ /** Submits a review on a given commit (the head the reviewer saw); the body may be empty for an approval. */
3799
+ async submitReview(n, input) {
3800
+ this.assertToken("Reviewing a branch");
3801
+ const r = await this.request("POST", `${this.repoPath}/pulls/${n}/reviews`, {
3802
+ event: input.event,
3803
+ ...input.body ? { body: input.body } : {},
3804
+ ...input.commitId ? { commit_id: input.commitId } : {}
3805
+ });
3806
+ return { id: r.id, user: r.user?.login, state: r.state, body: r.body ?? "" };
3807
+ }
3808
+ /** Line comments of a pull request (review threads), oldest first. */
3809
+ async listReviewComments(n) {
3810
+ const r = await this.request(
3811
+ "GET",
3812
+ `${this.repoPath}/pulls/${n}/comments?per_page=100`
3813
+ );
3814
+ return r.map((c) => ({ id: c.id, body: c.body ?? "", author: c.user?.login ?? "unknown", path: c.path, line: c.line ?? null, inReplyTo: c.in_reply_to_id ?? null, url: c.html_url, createdAt: c.created_at }));
3815
+ }
3816
+ /** Asks these people to review a pull request. */
3817
+ async requestReviewers(n, logins) {
3818
+ this.assertToken("Asking for a review");
3819
+ await this.request("POST", `${this.repoPath}/pulls/${n}/requested_reviewers`, { reviewers: logins });
3820
+ }
3821
+ /** Takes these people off the requested reviewers. */
3822
+ async removeRequestedReviewers(n, logins) {
3823
+ this.assertToken("Changing the requested reviewers");
3824
+ await this.request("DELETE", `${this.repoPath}/pulls/${n}/requested_reviewers`, { reviewers: logins });
3825
+ }
3826
+ /** Replies in the thread of a line comment. */
3827
+ async replyToReviewComment(n, commentId, body) {
3828
+ this.assertToken("Replying to a comment");
3829
+ const r = await this.request("POST", `${this.repoPath}/pulls/${n}/comments/${commentId}/replies`, { body });
3830
+ return { id: r.id, url: r.html_url };
3831
+ }
3832
+ /**
3833
+ * What `head` adds on top of `base`, with what library sync needs: the shared start (merge base), the base
3834
+ * commit and its tree, how far behind, and the files with their previous names.
3835
+ */
3836
+ async compareDetailed(base, head) {
3837
+ const r = await this.request("GET", `${this.repoPath}/compare/${encodeRef(base)}...${encodeRef(head)}`);
3838
+ return {
3839
+ behindBy: r.behind_by,
3840
+ aheadBy: r.ahead_by,
3841
+ mergeBase: r.merge_base_commit?.sha ?? null,
3842
+ baseSha: r.base_commit?.sha ?? null,
3843
+ baseTree: r.base_commit?.commit?.tree?.sha ?? null,
3844
+ files: (r.files ?? []).map((f) => ({ filename: f.filename, status: f.status, ...f.previous_filename ? { previous_filename: f.previous_filename } : {} }))
3845
+ };
3846
+ }
3847
+ /** A tree on top of `baseTree` from entries with their own file mode (blob sha, inline content, or sha null to delete). */
3848
+ async createTreeEntries(baseTree, entries) {
3849
+ const r = await this.request("POST", `${this.repoPath}/git/trees`, { base_tree: baseTree, tree: entries.map((e) => ({ ...e, type: "blob" })) }, true);
3850
+ return r.sha;
3851
+ }
3704
3852
  };
3705
3853
  function toCommitSummary(c) {
3706
3854
  return {
@@ -4162,15 +4310,15 @@ async function shoot(page, r) {
4162
4310
  }
4163
4311
  async function switchStory(page, storyId2) {
4164
4312
  const ok = await page.evaluate(
4165
- (id) => new Promise((resolve4) => {
4313
+ (id) => new Promise((resolve5) => {
4166
4314
  const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
4167
- if (!ch) return resolve4(false);
4315
+ if (!ch) return resolve5(false);
4168
4316
  const events = ["storyRendered", "storyErrored", "storyThrewException", "storyMissing", "playFunctionThrewException"];
4169
4317
  const timer = setTimeout(() => done(), 15e3);
4170
4318
  function done() {
4171
4319
  clearTimeout(timer);
4172
4320
  for (const e of events) ch.off(e, done);
4173
- resolve4(true);
4321
+ resolve5(true);
4174
4322
  }
4175
4323
  for (const e of events) ch.on(e, done);
4176
4324
  window.scrollTo(0, 0);
@@ -4583,12 +4731,12 @@ function serveDir(dir) {
4583
4731
  res.writeHead(200, { "content-type": TYPES[extname(file)] ?? "application/octet-stream", "cache-control": "no-store" });
4584
4732
  createReadStream(file).pipe(res);
4585
4733
  });
4586
- return new Promise((resolve4, reject) => {
4734
+ return new Promise((resolve5, reject) => {
4587
4735
  server.once("error", reject);
4588
4736
  server.listen(0, "127.0.0.1", () => {
4589
4737
  const addr = server.address();
4590
4738
  const port = typeof addr === "object" && addr ? addr.port : 0;
4591
- resolve4({ url: `http://127.0.0.1:${port}`, close: () => new Promise((r) => server.close(() => r())) });
4739
+ resolve5({ url: `http://127.0.0.1:${port}`, close: () => new Promise((r) => server.close(() => r())) });
4592
4740
  });
4593
4741
  });
4594
4742
  }
@@ -4660,7 +4808,7 @@ async function removeWorktree(repo, dir) {
4660
4808
  await git(repo, ["worktree", "prune"]).catch(() => void 0);
4661
4809
  }
4662
4810
  function run(cmd, args, cwd, log, root = cwd, env = {}) {
4663
- return new Promise((resolve4, reject) => {
4811
+ return new Promise((resolve5, reject) => {
4664
4812
  const bin = [.../* @__PURE__ */ new Set([join3(cwd, "node_modules", ".bin"), join3(root, "node_modules", ".bin")])].join(":");
4665
4813
  const child = spawn(cmd, args, {
4666
4814
  cwd,
@@ -4673,7 +4821,7 @@ function run(cmd, args, cwd, log, root = cwd, env = {}) {
4673
4821
  child.stderr.on("data", keep);
4674
4822
  child.on("error", reject);
4675
4823
  child.on("close", (code) => {
4676
- if (code === 0) resolve4();
4824
+ if (code === 0) resolve5();
4677
4825
  else {
4678
4826
  log(tail);
4679
4827
  reject(new Error(`${cmd} ${args.join(" ")} failed in ${cwd} (exit ${code})`));
@@ -5084,12 +5232,12 @@ async function captureFigmaReferences(o) {
5084
5232
  }
5085
5233
  const plan = JSON.stringify(j.plan.render);
5086
5234
  await page.evaluate(
5087
- ({ id, plan: plan2 }) => new Promise((resolve4) => {
5235
+ ({ id, plan: plan2 }) => new Promise((resolve5) => {
5088
5236
  const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
5089
5237
  const done = () => {
5090
5238
  clearTimeout(timer);
5091
5239
  ch.off("storyRendered", done);
5092
- resolve4();
5240
+ resolve5();
5093
5241
  };
5094
5242
  const timer = setTimeout(done, 1e4);
5095
5243
  ch.on("storyRendered", done);
@@ -5233,7 +5381,7 @@ Usage:
5233
5381
  --branch: implement it on that exploration, never in a pull request to main
5234
5382
  bridge request create --component Button --kind new-variant --title "Add a danger ghost variant"
5235
5383
  --summary "..." [--change field=before:after ...] [--figma url]
5236
- [--exploration explore/<name>] [--dry-run] [--json]
5384
+ [--exploration explore/<name>] [--product <key>] [--dry-run] [--json]
5237
5385
  bridge request jira-sync
5238
5386
  GitHub Action step: mirror a request issue to Jira (see docs/REQUESTS.md)
5239
5387
 
@@ -5388,6 +5536,7 @@ Claude Code is not installed (no \`claude\` on PATH). Install it (npm install -g
5388
5536
  changes: (a.flags.change ?? []).map(parseChange),
5389
5537
  ...figma ? { figmaUrl: figma } : {},
5390
5538
  ...exploration ? { exploration } : {},
5539
+ ...flag(a, "product") ? { product: flag(a, "product") } : {},
5391
5540
  source: "cli",
5392
5541
  dryRun
5393
5542
  });
@@ -5433,7 +5582,7 @@ staging branch without touching main (an explore/<name> branch with a draft pull
5433
5582
 
5434
5583
  Usage:
5435
5584
  bridge explore start <name> --purpose "..." [--use-case ${EXPLORATION_USE_CASES.map((u) => u.value).join("|")}]
5436
- [--experimental "..." ...] [--dry-run] [--json]
5585
+ [--experimental "..." ...] [--product <key>] [--dry-run] [--json]
5437
5586
  GitHub token: the branch and the draft pull request on GitHub. Without one, in a git repo:
5438
5587
  a local branch only, nothing pushed
5439
5588
  bridge explore list [--all] [--json]
@@ -5471,7 +5620,7 @@ async function runExplore(a, root, io = defaultIO()) {
5471
5620
  if (!name || !purpose) throw new RequestUsageError('Usage: bridge explore start <name> --purpose "What you want to learn"');
5472
5621
  const useCase = flag2(a, "use-case");
5473
5622
  const ctx = await context2(root, io);
5474
- const r = await explorations.startExploration(ctx, { name, purpose, ...exploreSource(io.env), ...useCase ? { useCase } : {}, ...a.flags.experimental ? { experimental: a.flags.experimental } : {}, dryRun: has2(a, "dry-run") });
5623
+ const r = await explorations.startExploration(ctx, { name, purpose, ...exploreSource(io.env), ...useCase ? { useCase } : {}, ...a.flags.experimental ? { experimental: a.flags.experimental } : {}, ...flag2(a, "product") ? { product: flag2(a, "product") } : {}, dryRun: has2(a, "dry-run") });
5475
5624
  if (json) return io.print(JSON.stringify(r, null, 2)), 0;
5476
5625
  if (r.status === "dry-run") {
5477
5626
  io.print(`Dry run, nothing written.
@@ -5541,6 +5690,316 @@ ${EXPLORE_HELP}`);
5541
5690
  }
5542
5691
  }
5543
5692
 
5693
+ // src/branch.ts
5694
+ import { readFile as readFile6 } from "node:fs/promises";
5695
+ import { resolve as resolve3 } from "node:path";
5696
+ import { BridgeContext as BridgeContext4, branches, studio } from "@bridge-ds/mcp";
5697
+ var BRANCH_HELP = `bridge branch: branches in review, the same actions as Bridge Studio
5698
+
5699
+ Usage:
5700
+ bridge branch list [--limit 20] [--json] what needs me: the review queue, mine first
5701
+ bridge branch status <n> [--json] status, approvals, tests, main, may I merge, next step
5702
+ bridge branch comments <n> [--json] the conversation and line threads (ids to reply to)
5703
+ bridge branch ready <n> send a draft for review (its author)
5704
+ bridge branch draft <n> back to draft (its author)
5705
+ bridge branch review <n> --approve [--body "..."] | --request-changes "..." | --comment "..."
5706
+ bridge branch ask <n> [--dry-run] ask someone else to review (who can give the missing approvals)
5707
+ bridge branch comment <n> "text" [--reply-to <id>]
5708
+ bridge branch merge <n> [--override "reason"] [--dry-run]
5709
+ same gate as the Merge button; --override: owner override,
5710
+ the reason is posted on the branch first
5711
+ bridge branch close <n> | bridge branch reopen <n>
5712
+ bridge branch update <n> [--choice <key>=mine|theirs ...] [--dry-run]
5713
+ update from main; token conflicts: pick per key
5714
+ bridge branch try <n> [--json] try it in a product, share it with a product team
5715
+ bridge branch combine <n> <n> [<n> <n>] [--pick <key>=<n> ...] [--json]
5716
+ try 2 to 4 token branches together (writes nothing)
5717
+ bridge products [--json] the products and the one this folder belongs to
5718
+ bridge settings [--json] roles, who ships, owner override, protection, products
5719
+ bridge coverage [Component] [--json] which components are in Figma
5720
+ bridge docs save <Component> --file docs.json [--pr <n>] [--title "..."] [--jira DS-42]
5721
+ [--product admin] [--dry-run] [--json]
5722
+ save usage guidance as in the Library edit mode
5723
+
5724
+ Start work for a product: bridge propose|explore start|request create ... --product <key> [--jira DS-42].
5725
+ 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) {
5729
+ 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 } : {} });
5731
+ }
5732
+ function number(a, at, usage) {
5733
+ const raw = a._[at];
5734
+ if (!raw) throw new RequestUsageError(`Usage: ${usage}`);
5735
+ return branches.branchNumber(raw);
5736
+ }
5737
+ function pairs(list2, what) {
5738
+ const out = {};
5739
+ for (const s of list2 ?? []) {
5740
+ const i = s.lastIndexOf("=");
5741
+ if (i <= 0) throw new RequestUsageError(`--${what} needs key=value, got "${s}".`);
5742
+ out[s.slice(0, i)] = s.slice(i + 1);
5743
+ }
5744
+ return out;
5745
+ }
5746
+ var said = (r) => String(r.said ?? r.summary ?? r.status ?? "Done.");
5747
+ async function runBranch(a, root, io = defaultIO()) {
5748
+ const sub = a._[1];
5749
+ if (!sub || sub === "help" || has3(a, "help")) {
5750
+ io.print(BRANCH_HELP);
5751
+ return sub || has3(a, "help") ? 0 : 2;
5752
+ }
5753
+ const json = has3(a, "json");
5754
+ const ctx = await context3(root, io);
5755
+ const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
5756
+ switch (sub) {
5757
+ case "list": {
5758
+ const limit = flag3(a, "limit");
5759
+ const r = await branches.whatNeedsMe(ctx, limit ? { limit: Number(limit) } : {});
5760
+ return out(r, () => {
5761
+ io.print(r.summary);
5762
+ for (const b of r.branches) io.print(`#${b.pr} ${b.statusLabel.padEnd(22)} ${b.title}${b.needsYou ? ` <- ${b.needsYou}` : ""}`);
5763
+ });
5764
+ }
5765
+ case "status": {
5766
+ const r = await branches.branchStatus(ctx, { pr: number(a, 2, "bridge branch status <n>") });
5767
+ return out(r, () => {
5768
+ io.print(`#${r.pr} ${r.title} (${r.product.name})`);
5769
+ io.print(`${r.status.label}. ${r.approvalNeeded} needed; approved by ${r.approvals.approvedBy.map((x) => `@${x}`).join(", ") || "nobody yet"}.`);
5770
+ if (r.merge.blockedBy?.length) for (const x of r.merge.blockedBy) io.print(` ${x}`);
5771
+ io.print(`Next: ${r.next}`);
5772
+ io.print(r.url);
5773
+ });
5774
+ }
5775
+ case "comments": {
5776
+ const r = await branches.listComments(ctx, { pr: number(a, 2, "bridge branch comments <n>") });
5777
+ return out(r, () => {
5778
+ for (const c of r.conversation) io.print(`@${c.author}: ${c.body}`);
5779
+ for (const t of r.threads) io.print(`[thread ${t.id}] ${t.file}:${t.line ?? ""} @${t.author}: ${t.body}${t.replies.length ? ` (${t.replies.length} replies)` : ""}`);
5780
+ });
5781
+ }
5782
+ case "ready": {
5783
+ const r = await branches.sendForReview(ctx, { pr: number(a, 2, "bridge branch ready <n>") });
5784
+ return out(r, () => io.print(r.said));
5785
+ }
5786
+ case "draft": {
5787
+ const r = await branches.backToDraft(ctx, { pr: number(a, 2, "bridge branch draft <n>") });
5788
+ return out(r, () => io.print(r.said));
5789
+ }
5790
+ case "review": {
5791
+ 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;
5793
+ 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");
5795
+ const r = await branches.reviewBranch(ctx, { pr: n, decision, ...body && body !== "true" ? { body } : {} });
5796
+ return out(r, () => io.print(r.said));
5797
+ }
5798
+ case "ask": {
5799
+ const r = await branches.askSomeoneElse(ctx, { pr: number(a, 2, "bridge branch ask <n>"), dryRun: has3(a, "dry-run") });
5800
+ return out(r, () => io.print(r.said));
5801
+ }
5802
+ case "comment": {
5803
+ const n = number(a, 2, 'bridge branch comment <n> "text"');
5804
+ const body = a._.slice(3).join(" ") || flag3(a, "body");
5805
+ if (!body) throw new RequestUsageError('Usage: bridge branch comment <n> "text" [--reply-to <id>]');
5806
+ const reply = flag3(a, "reply-to");
5807
+ const r = await branches.commentOnBranch(ctx, { pr: n, body, ...reply ? { replyTo: Number(reply) } : {} });
5808
+ return out(r, () => io.print(r.said));
5809
+ }
5810
+ case "merge": {
5811
+ 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") });
5814
+ return out(r, () => io.print(r.said));
5815
+ }
5816
+ case "close": {
5817
+ const r = await branches.closeBranch(ctx, { pr: number(a, 2, "bridge branch close <n>") });
5818
+ return out(r, () => io.print(r.said));
5819
+ }
5820
+ case "reopen": {
5821
+ const r = await branches.reopenBranch(ctx, { pr: number(a, 2, "bridge branch reopen <n>") });
5822
+ return out(r, () => io.print(r.said));
5823
+ }
5824
+ case "update": {
5825
+ const n = number(a, 2, "bridge branch update <n> [--choice key=mine|theirs] [--dry-run]");
5826
+ const choices = pairs(a.flags.choice, "choice");
5827
+ 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") });
5829
+ return out(r, () => {
5830
+ io.print(said(r));
5831
+ for (const c of r.conflicts ?? []) io.print(` ${c.key} (${c.token}, default ${c.default ?? "none: pick one"})`);
5832
+ });
5833
+ }
5834
+ case "try": {
5835
+ const r = await branches.tryBranch(ctx, { pr: number(a, 2, "bridge branch try <n>") });
5836
+ return out(r, () => {
5837
+ for (const p of r.tryInProduct) io.print(`${p.name}: ${p.url ?? p.how}`);
5838
+ io.print(`
5839
+ Share with a product team: ${r.share.install ? r.share.install : r.share.sentence}`);
5840
+ });
5841
+ }
5842
+ case "combine": {
5843
+ const prs = a._.slice(2);
5844
+ const picks = Object.fromEntries(Object.entries(pairs(a.flags.pick, "pick")).map(([k, v]) => [k, branches.branchNumber(v)]));
5845
+ const r = await branches.combineBranches(ctx, { prs, ...Object.keys(picks).length ? { picks } : {} });
5846
+ return out(r, () => {
5847
+ io.print(`${r.tokens} tokens change together.`);
5848
+ for (const c of r.conflicts) io.print(` ${c.key}: pick one of ${c.options.map((o) => `#${o.use}`).join(", ")}${c.picked ? ` (picked #${c.picked})` : ""}`);
5849
+ for (const [k, v] of Object.entries(r.open)) io.print(`${k}: ${v}`);
5850
+ io.print(r.next);
5851
+ });
5852
+ }
5853
+ default:
5854
+ throw new RequestUsageError(`Unknown branch command "${sub}".
5855
+
5856
+ ${BRANCH_HELP}`);
5857
+ }
5858
+ }
5859
+ async function runStudioRead(cmd, a, root, io = defaultIO()) {
5860
+ const ctx = await context3(root, io);
5861
+ const json = has3(a, "json");
5862
+ if (cmd === "products") {
5863
+ const r2 = await branches.listProducts(ctx);
5864
+ if (json) return io.print(JSON.stringify(r2, null, 2)), 0;
5865
+ for (const p of r2.products) io.print(`${p.key === r2.current.key ? "*" : " "} ${p.key.padEnd(12)} ${p.name}${p.default ? " (default)" : ""}${p.path ? ` ${p.path}` : ""}${p.repo ? ` ${p.repo}` : ""}`);
5866
+ io.print(`
5867
+ This folder: ${r2.current.name}. Pick another with --product <key>.`);
5868
+ return 0;
5869
+ }
5870
+ if (cmd === "settings") {
5871
+ const r2 = await studio.getSettings(ctx);
5872
+ if (json) return io.print(JSON.stringify(r2, null, 2)), 0;
5873
+ io.print(`Design approval: ${r2.roles.designApproval.join(", ") || "any reviewer"}`);
5874
+ io.print(`Tech approval: ${r2.roles.techApproval.join(", ") || "any reviewer"}`);
5875
+ io.print(`Merge: ${r2.roles.ship}`);
5876
+ io.print(`Owner override: ${r2.ownerOverride.on ? `${r2.ownerOverride.who.join(", ")}. ${r2.ownerOverride.reason}` : r2.ownerOverride.reason}`);
5877
+ io.print(`Protection: ${r2.protection}`);
5878
+ io.print(`Products: ${r2.products.map((p) => p.name).join(", ")}`);
5879
+ return 0;
5880
+ }
5881
+ const r = await studio.figmaCoverage(ctx, a._[1] ? { component: a._[1] } : {});
5882
+ if (json) return io.print(JSON.stringify(r, null, 2)), 0;
5883
+ io.print(r.summary);
5884
+ for (const c of r.components) io.print(` ${c.name.padEnd(20)} ${c.inFigma}${c.why ? ` (${c.why})` : ""}`);
5885
+ if (r.fidelity) io.print(r.fidelity);
5886
+ return 0;
5887
+ }
5888
+ async function runDocsSave(a, root, io = defaultIO()) {
5889
+ const component = a._[2];
5890
+ const file = flag3(a, "file");
5891
+ 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
+ let docs;
5893
+ try {
5894
+ docs = JSON.parse(await readFile6(resolve3(root, file), "utf8"));
5895
+ } catch (e) {
5896
+ throw new RequestUsageError(`${file} is not a readable JSON file: ${e.message}`);
5897
+ }
5898
+ const ctx = await context3(root, io);
5899
+ const pr = flag3(a, "pr");
5900
+ const r = await studio.saveDocs(ctx, {
5901
+ component,
5902
+ docs,
5903
+ ...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")
5909
+ });
5910
+ if (has3(a, "json")) io.print(JSON.stringify(r, null, 2));
5911
+ else io.print(r.status === "dry-run" || r.status === "preview" ? `${r.said ?? "Dry run, nothing written."}
5912
+
5913
+ ${r.path}:
5914
+ ${r.content}` : String(r.said));
5915
+ return 0;
5916
+ }
5917
+
5918
+ // src/pieces.ts
5919
+ import { BridgeContext as BridgeContext5, branches as branches2, pieces } from "@bridge-ds/mcp";
5920
+ var PIECES_HELP = `bridge pieces: a product branch and the library
5921
+
5922
+ Usage:
5923
+ bridge pieces <n> [--record] [--json] what the product branch adds that the library lacks (the audit
5924
+ on its diff against main); --record saves it in the branch,
5925
+ where Bridge Studio shows it
5926
+ bridge pieces status <n> [--json] each linked library branch: status, reviewers, preview, next step
5927
+ bridge pieces open <n> <piece> [--no-preview] [--dry-run] [--json]
5928
+ open the library side of a piece, linked both ways: a token
5929
+ change, a library exploration (variant) or a request (component)
5930
+ bridge pieces preview <n> <piece> the product branch takes the latest library branch (merged in)
5931
+ bridge pieces switch <n> once the library shipped: main into the product branch
5932
+
5933
+ 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) {
5936
+ 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 } : {} });
5938
+ }
5939
+ function number2(a, at, usage) {
5940
+ const raw = a._[at];
5941
+ if (!raw) throw new RequestUsageError(`Usage: ${usage}`);
5942
+ return branches2.branchNumber(raw);
5943
+ }
5944
+ function piece(a, at, usage) {
5945
+ const raw = a._[at];
5946
+ if (!raw) throw new RequestUsageError(`Usage: ${usage}. \`bridge pieces <n>\` lists the piece ids.`);
5947
+ return raw;
5948
+ }
5949
+ async function runPieces(a, root, io = defaultIO()) {
5950
+ const sub = a._[1];
5951
+ if (!sub || sub === "help" || has4(a, "help")) {
5952
+ io.print(PIECES_HELP);
5953
+ return sub || has4(a, "help") ? 0 : 2;
5954
+ }
5955
+ const json = has4(a, "json");
5956
+ const ctx = await context4(root, io);
5957
+ const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
5958
+ switch (sub) {
5959
+ case "status": {
5960
+ const r = await pieces.productBranchOp(ctx, { pr: number2(a, 2, "bridge pieces status <n>") });
5961
+ return out(r, () => {
5962
+ io.print(`#${r.pr} ${r.title} (${r.product})`);
5963
+ io.print(r.said);
5964
+ for (const l of r.linked) io.print(` ${l.piece}: #${l.library.number} ${l.status}. ${l.preview}${l.action ? ` Next: ${l.action}.` : ""}`);
5965
+ for (const p of r.notLinked) io.print(` ${p.title} (${p.id}): no library branch yet. bridge pieces open ${r.pr} ${p.id}`);
5966
+ if (r.next) io.print(r.next);
5967
+ });
5968
+ }
5969
+ case "open": {
5970
+ 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" });
5972
+ 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}.`);
5974
+ if (r.library) io.print(` #${r.library.number} ${r.library.url}`);
5975
+ if (r.preview) io.print(` ${r.preview.said}`);
5976
+ for (const w of r.warnings ?? []) io.print(` ${w}`);
5977
+ io.print(`Next: ${r.next}`);
5978
+ });
5979
+ }
5980
+ case "preview": {
5981
+ const usage = "bridge pieces preview <n> <piece>";
5982
+ const r = await pieces.useLibraryPreviewOp(ctx, { pr: number2(a, 2, usage), piece: piece(a, 3, usage) });
5983
+ return out(r, () => io.print(r.said));
5984
+ }
5985
+ case "switch": {
5986
+ const r = await pieces.switchToPublishedOp(ctx, { pr: number2(a, 2, "bridge pieces switch <n>") });
5987
+ return out(r, () => io.print(r.said));
5988
+ }
5989
+ default: {
5990
+ const r = await pieces.missingPiecesOp(ctx, { pr: number2(a, 1, "bridge pieces <n> [--record]"), record: has4(a, "record") });
5991
+ return out(r, () => {
5992
+ 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
+ for (const p of r.pieces) io.print(` ${p.id} ${p.title}: ${p.summary} Opens ${p.opens}.${"linked" in p ? ` Linked: #${p.linked}.` : ""}`);
5994
+ if (r.fixes.count) io.print(`${r.fixes.count} value${r.fixes.count === 1 ? "" : "s"} the library already has (use them instead):
5995
+ ${r.fixes.sample.map((s) => ` ${s}`).join("\n")}`);
5996
+ if (r.skipped) io.print(r.skipped);
5997
+ io.print(`Next: ${r.next}`);
5998
+ });
5999
+ }
6000
+ }
6001
+ }
6002
+
5544
6003
  // src/protection.ts
5545
6004
  var UNKNOWN = { kind: "unknown", missing: ["review", "checks", "history", "deletion"] };
5546
6005
  async function protectionReport(opts) {
@@ -5573,7 +6032,7 @@ async function rolesReport(opts) {
5573
6032
  }
5574
6033
 
5575
6034
  // src/index.ts
5576
- import { brand, BridgeContext as BridgeContext4, CONFIG_FILE as CONFIG_FILE2, core, detectConfig, GitRefSource, history as hist, LocalSource, ops, optionsFromArgv, serveStdio, toConfigJson, VERSION } from "@bridge-ds/mcp";
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";
5577
6036
  var MCP_PACKAGE = "@bridge-ds/mcp";
5578
6037
  var HELP = `bridge ${VERSION}: design system as code, for you and your Claude
5579
6038
 
@@ -5588,9 +6047,13 @@ Usage:
5588
6047
  bridge tokens <query> [--json] search tokens by name, description or value
5589
6048
  bridge diff <base> [--json|--md] designer-readable token diff: working tree vs <base> (git ref)
5590
6049
  bridge propose --title "..." --set path=value [--set ...] [--file f | --mode theme=dark]
5591
- [--edits edits.json] [--summary "..."] [--branch explore/<name>] [--dry-run]
6050
+ [--edits edits.json] [--summary "..."] [--branch explore/<name>] [--pr <n>]
6051
+ [--product <key>] [--jira DS-42] [--dry-run]
5592
6052
  validate + contrast + new local bridge/* branch (never pushes).
5593
- --branch: write the edits to the checked-out exploration instead
6053
+ --branch: write the edits to the checked-out exploration instead.
6054
+ --pr: add them to that branch in review (GitHub token).
6055
+ --product: the product it is for (bridge products); --jira: the
6056
+ ticket key, first in the branch name and title
5594
6057
  bridge brand list [--json] brands: slug, name, default, primary action color, theme overrides
5595
6058
  bridge brand create <slug> --from bridge --primary <hex|hue> --neutral <hex|hue>
5596
6059
  [--name "Acme"] [--accent x] [--link x] [--radius soft|balanced|crisp|i,c,co,o]
@@ -5646,6 +6109,20 @@ Usage:
5646
6109
  explorations: an explore/<name> branch with a draft pull request
5647
6110
  labeled exploration, previews and a snapshot package for a product
5648
6111
  staging branch. Never merged as is: promote it to review or close it
6112
+ bridge branch list|status|comments|ready|draft|review|ask|comment|merge|close|reopen|update|try|combine
6113
+ branches in review, the same actions as Bridge Studio
6114
+ (\`bridge branch help\`): what needs me, send for review, review,
6115
+ merge through the same gate (owner override with a reason), update
6116
+ from main with token conflicts, try it in a product, try together
6117
+ bridge pieces <n> [--record] | status <n> | open <n> <piece> | preview <n> <piece> | switch <n>
6118
+ a product branch and the library (\`bridge pieces help\`): what it adds
6119
+ that the library lacks, the library branch each piece opens (linked
6120
+ both ways), its preview in the product branch, the published version
6121
+ bridge products | bridge settings | bridge coverage [Component]
6122
+ products (and this folder's), Studio settings safe to read, which
6123
+ components are in Figma
6124
+ bridge docs save <Component> --file docs.json [--pr n] [--jira DS-42] [--product key] [--dry-run]
6125
+ save usage guidance as in the Library edit mode (GitHub token)
5649
6126
  bridge mcp [--root dir | --repo owner/name] start the MCP server on stdio
5650
6127
 
5651
6128
  Options:
@@ -5662,23 +6139,23 @@ function parse(argv) {
5662
6139
  const [k, inline] = a.slice(2).split(/=(.*)/s, 2);
5663
6140
  const next = argv[i + 1];
5664
6141
  const fileArg = k === "json" && /\.json$/i.test(next ?? "") || k === "md" && /\.(md|markdown)$/i.test(next ?? "");
5665
- 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"].includes(k));
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));
5666
6143
  (out.flags[k] ??= []).push(inline ?? (takesValue ? argv[++i] : "true"));
5667
6144
  } else out._.push(a);
5668
6145
  }
5669
6146
  return out;
5670
6147
  }
5671
- var flag3 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
5672
- var has3 = (a, k) => Boolean(a.flags[k]);
6148
+ var flag4 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
6149
+ var has5 = (a, k) => Boolean(a.flags[k]);
5673
6150
  var print = (s) => process.stdout.write(`${s}
5674
6151
  `);
5675
6152
  var modeArg = (s) => s ? Object.fromEntries(s.split(",").map((kv) => kv.split("="))) : void 0;
5676
6153
  async function init(a, root) {
5677
6154
  const cfgPath = join6(root, CONFIG_FILE2);
5678
6155
  let config;
5679
- if (existsSync6(cfgPath) && !has3(a, "force")) {
6156
+ if (existsSync6(cfgPath) && !has5(a, "force")) {
5680
6157
  print(`${CONFIG_FILE2} exists, keeping it (--force to regenerate).`);
5681
- config = (await (await BridgeContext4.create({ root })).config()).config;
6158
+ config = (await (await BridgeContext6.create({ root })).config()).config;
5682
6159
  } else {
5683
6160
  const src = new LocalSource(root);
5684
6161
  const files = await src.list("");
@@ -5694,7 +6171,7 @@ async function init(a, root) {
5694
6171
  const inRepo = !relServer.startsWith("..") && !isAbsolute(relServer);
5695
6172
  const server = (dir) => inRepo ? { command: "node", args: [relServer, "--root", dir] } : { command: "npx", args: ["-y", MCP_PACKAGE, "--root", dir] };
5696
6173
  const mcpPath = join6(root, ".mcp.json");
5697
- const existing = existsSync6(mcpPath) ? JSON.parse(await readFile6(mcpPath, "utf8")) : {};
6174
+ const existing = existsSync6(mcpPath) ? JSON.parse(await readFile7(mcpPath, "utf8")) : {};
5698
6175
  existing.mcpServers = { ...existing.mcpServers ?? {}, bridge: server(".") };
5699
6176
  await writeFile5(mcpPath, `${JSON.stringify(existing, null, 2)}
5700
6177
  `);
@@ -5706,14 +6183,14 @@ async function init(a, root) {
5706
6183
  print(` claude mcp add bridge -- ${desktop.command} ${desktop.args.join(" ")}`);
5707
6184
  }
5708
6185
  async function check(a, root) {
5709
- const ctx = await BridgeContext4.create({ root });
6186
+ const ctx = await BridgeContext6.create({ root });
5710
6187
  const v = await ops.validateTokens(ctx, { includeWarnings: true });
5711
6188
  const c = await ops.checkContrast(ctx, {});
5712
6189
  const warnings = "warnings" in v ? v.warnings ?? [] : [];
5713
- const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 || has3(a, "strict") && warnings.length > 0;
6190
+ const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 || has5(a, "strict") && warnings.length > 0;
5714
6191
  const protection = await protectionOf(a, root, ctx);
5715
6192
  const roles = await rolesOf(a, root, ctx);
5716
- if (has3(a, "json")) {
6193
+ if (has5(a, "json")) {
5717
6194
  print(JSON.stringify({ ok: !failed, validation: v, contrast: c, ...protection ? { protection: protection.state } : {}, ...roles?.teams.length ? { teams: roles.teams } : {} }, null, 2));
5718
6195
  return failed ? 1 : 0;
5719
6196
  }
@@ -5732,7 +6209,7 @@ async function check(a, root) {
5732
6209
  return failed ? 1 : 0;
5733
6210
  }
5734
6211
  async function protectionOf(a, root, ctx) {
5735
- if (has3(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
6212
+ if (has5(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
5736
6213
  const { config } = await ctx.config();
5737
6214
  const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
5738
6215
  if (!repo) return void 0;
@@ -5740,7 +6217,7 @@ async function protectionOf(a, root, ctx) {
5740
6217
  return protectionReport({ repo, defaultBranch: config.repo?.defaultBranch ?? "main", token });
5741
6218
  }
5742
6219
  async function rolesOf(a, root, ctx) {
5743
- if (has3(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
6220
+ if (has5(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
5744
6221
  const { config } = await ctx.config();
5745
6222
  const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
5746
6223
  if (!repo) return void 0;
@@ -5750,9 +6227,9 @@ async function rolesOf(a, root, ctx) {
5750
6227
  async function tokens(a, root) {
5751
6228
  const q = a._.slice(1).join(" ");
5752
6229
  if (!q) throw new UsageError("bridge tokens <query>");
5753
- const ctx = await BridgeContext4.create({ root });
5754
- const r = await ops.searchTokens(ctx, { query: q, limit: Number(flag3(a, "limit") ?? 20) });
5755
- if (has3(a, "json")) return print(JSON.stringify(r, null, 2));
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));
5756
6233
  if (!r.results.length) return print(`No token matches "${q}".`);
5757
6234
  const w = Math.max(...r.results.map((x) => x.path.length));
5758
6235
  for (const t of r.results) print(`${t.path.padEnd(w)} ${String(t.value).padEnd(10)} ${(t.tier ?? "").padEnd(9)} ${t.description ?? ""}`);
@@ -5760,20 +6237,20 @@ async function tokens(a, root) {
5760
6237
  async function diff(a, root) {
5761
6238
  const base = a._[1];
5762
6239
  if (!base) throw new UsageError("bridge diff <base> (a git ref: main, HEAD~1, a sha)");
5763
- const ctx = await BridgeContext4.create({ root });
6240
+ const ctx = await BridgeContext6.create({ root });
5764
6241
  const { config } = await ctx.config();
5765
6242
  const before = await core.loadTokenSet(new GitRefSource(root, base), config);
5766
6243
  const after = await ctx.tokens();
5767
6244
  const changes = core.diff(before, after);
5768
- const pairs = await core.readContrastPairs(ctx.source, config).catch(() => []);
5769
- const delta2 = pairs.length ? core.contrastDelta(core.runContrast(before, pairs, config.contrast?.modes), core.runContrast(after, pairs, config.contrast?.modes)) : [];
6245
+ const pairs2 = await core.readContrastPairs(ctx.source, config).catch(() => []);
6246
+ const delta2 = pairs2.length ? core.contrastDelta(core.runContrast(before, pairs2, config.contrast?.modes), core.runContrast(after, pairs2, config.contrast?.modes)) : [];
5770
6247
  const tier = core.classify(changes, [], config);
5771
6248
  const imp = core.impact(before, after, changes, 30);
5772
- if (has3(a, "json")) {
6249
+ if (has5(a, "json")) {
5773
6250
  print(JSON.stringify({ base, changes, contrast: delta2, tier, impact: imp }, null, 2));
5774
6251
  return 0;
5775
6252
  }
5776
- if (has3(a, "md")) {
6253
+ if (has5(a, "md")) {
5777
6254
  print(`## Token changes since \`${base}\`
5778
6255
 
5779
6256
  ${core.tokenTable(changes)}
@@ -5808,24 +6285,37 @@ ${imp.total} other token(s) change value through aliases:`);
5808
6285
  return delta2.some((d) => d.passBefore && !d.passAfter) ? 1 : 0;
5809
6286
  }
5810
6287
  async function propose(a, root) {
5811
- const title = flag3(a, "title");
6288
+ const title = flag4(a, "title");
5812
6289
  if (!title) throw new UsageError('bridge propose --title "Darken muted text" --set color.text.muted={palette.neutral.700} --mode theme=light');
5813
6290
  const edits = [];
5814
- const file = flag3(a, "file");
5815
- const mode2 = modeArg(flag3(a, "mode"));
6291
+ const file = flag4(a, "file");
6292
+ const mode2 = modeArg(flag4(a, "mode"));
5816
6293
  for (const s of a.flags.set ?? []) {
5817
6294
  const i = s.indexOf("=");
5818
6295
  if (i < 0) throw new UsageError(`--set needs path=value, got "${s}"`);
5819
6296
  edits.push({ op: "set", path: s.slice(0, i), value: s.slice(i + 1), ...file ? { file } : {}, ...mode2 ? { mode: mode2 } : {} });
5820
6297
  }
5821
- const editsFile = flag3(a, "edits");
5822
- if (editsFile) edits.push(...JSON.parse(await readFile6(resolve3(editsFile), "utf8")));
6298
+ const editsFile = flag4(a, "edits");
6299
+ if (editsFile) edits.push(...JSON.parse(await readFile7(resolve4(editsFile), "utf8")));
5823
6300
  if (!edits.length) throw new UsageError("Give at least one --set path=value or --edits file.json");
5824
- const ctx = await BridgeContext4.create({ root });
5825
- const summary = flag3(a, "summary");
5826
- const branch = flag3(a, "branch");
5827
- const r = await ops.proposeTokenChange(ctx, { title, edits, ...summary ? { summary } : {}, ...branch ? { branch } : {}, dryRun: has3(a, "dry-run") });
5828
- if (has3(a, "json")) print(JSON.stringify(r, null, 2));
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");
6308
+ const r = await ops.proposeTokenChange(ctx, {
6309
+ title,
6310
+ edits,
6311
+ ...summary ? { summary } : {},
6312
+ ...branch ? { branch } : {},
6313
+ ...pr ? { pr } : {},
6314
+ ...jira ? { jira } : {},
6315
+ ...product ? { product } : {},
6316
+ dryRun: has5(a, "dry-run")
6317
+ });
6318
+ if (has5(a, "json")) print(JSON.stringify(r, null, 2));
5829
6319
  else {
5830
6320
  print(`status: ${r.status}${r.why ? ` (${r.why})` : ""}`);
5831
6321
  if (r.tier) print(`tier ${r.tier}, ${r.bump}`);
@@ -5840,17 +6330,17 @@ ${r.prBody}`);
5840
6330
  }
5841
6331
  async function brandCmd(a, root) {
5842
6332
  const sub = a._[1];
5843
- const ctx = await BridgeContext4.create({ root });
6333
+ const ctx = await BridgeContext6.create({ root });
5844
6334
  if (sub === "list") {
5845
6335
  const r2 = await brand.listBrands(ctx);
5846
- if (has3(a, "json")) print(JSON.stringify(r2.brands, null, 2));
6336
+ if (has5(a, "json")) print(JSON.stringify(r2.brands, null, 2));
5847
6337
  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(", ")}` : ""}`);
5848
6338
  return 0;
5849
6339
  }
5850
6340
  if (sub !== "create") throw new UsageError('bridge brand list | bridge brand create <slug> --from bridge --primary "#1F5F3A" --neutral "#2A2F2C"');
5851
6341
  const slug = a._[2];
5852
6342
  if (!slug) throw new UsageError('bridge brand create <slug> --from bridge --primary "#1F5F3A" --neutral "#2A2F2C"');
5853
- const opt = (k) => flag3(a, k);
6343
+ const opt = (k) => flag4(a, k);
5854
6344
  const fonts = { ...opt("heading") ? { heading: opt("heading") } : {}, ...opt("text") ? { text: opt("text") } : {} };
5855
6345
  const r = await brand.proposeBrand(ctx, {
5856
6346
  slug,
@@ -5863,9 +6353,9 @@ async function brandCmd(a, root) {
5863
6353
  ...opt("radius") ? { radius: opt("radius") } : {},
5864
6354
  ...Object.keys(fonts).length ? { fonts } : {},
5865
6355
  ...opt("notes") ? { notes: opt("notes") } : {},
5866
- dryRun: has3(a, "dry-run")
6356
+ dryRun: has5(a, "dry-run")
5867
6357
  });
5868
- if (has3(a, "json")) {
6358
+ if (has5(a, "json")) {
5869
6359
  const { fileContents: _f, ...rest } = r;
5870
6360
  print(JSON.stringify(rest, null, 2));
5871
6361
  return r.status === "blocked" ? 1 : 0;
@@ -5886,7 +6376,7 @@ async function brandCmd(a, root) {
5886
6376
  return r.status === "blocked" ? 1 : 0;
5887
6377
  }
5888
6378
  async function figmaReferences(a, root) {
5889
- let sha = flag3(a, "sha");
6379
+ let sha = flag4(a, "sha");
5890
6380
  if (!sha) {
5891
6381
  try {
5892
6382
  sha = execFileSync2("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
@@ -5894,49 +6384,49 @@ async function figmaReferences(a, root) {
5894
6384
  sha = "unknown";
5895
6385
  }
5896
6386
  }
5897
- const only = (flag3(a, "only") ?? "").split(",").filter(Boolean);
6387
+ const only = (flag4(a, "only") ?? "").split(",").filter(Boolean);
5898
6388
  const r = await captureFigmaReferences({
5899
- storybook: resolve3(root, flag3(a, "storybook") ?? "storybook-static"),
5900
- specs: resolve3(root, flag3(a, "specs") ?? "packages/react/component-specs.json"),
5901
- out: resolve3(root, flag3(a, "out") ?? "figma-references"),
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"),
5902
6392
  sha,
5903
6393
  ...only.length ? { only } : {},
5904
- log: has3(a, "json") ? void 0 : (line) => print(line)
6394
+ log: has5(a, "json") ? void 0 : (line) => print(line)
5905
6395
  });
5906
- if (has3(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
6396
+ if (has5(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
5907
6397
  return 0;
5908
6398
  }
5909
6399
  async function impact(a, root) {
5910
- const only = (flag3(a, "only") ?? "").split(",").filter(Boolean);
6400
+ const only = (flag4(a, "only") ?? "").split(",").filter(Boolean);
5911
6401
  for (const x of only) if (x !== "stories" && x !== "app") throw new UsageError(`--only takes stories or app, got "${x}"`);
5912
- const num2 = (k) => flag3(a, k) !== void 0 ? Number(flag3(a, k)) : void 0;
6402
+ const num2 = (k) => flag4(a, k) !== void 0 ? Number(flag4(a, k)) : void 0;
5913
6403
  const threshold = num2("threshold");
5914
6404
  if (threshold !== void 0 && !(threshold >= 0 && threshold <= 1)) throw new UsageError("--threshold is between 0 and 1 (pixelmatch color distance)");
5915
- const imageBase = flag3(a, "image-base");
5916
- const artifactUrl = flag3(a, "artifact-url");
5917
- const out = flag3(a, "out");
5918
- const cacheDir = flag3(a, "cache");
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");
5919
6409
  const maxStories = num2("max-stories");
5920
6410
  const r = await runImpact({
5921
6411
  root,
5922
- base: flag3(a, "base") ?? "main",
5923
- head: flag3(a, "head") ?? "HEAD",
5924
- ...out ? { out: resolve3(out) } : {},
6412
+ base: flag4(a, "base") ?? "main",
6413
+ head: flag4(a, "head") ?? "HEAD",
6414
+ ...out ? { out: resolve4(out) } : {},
5925
6415
  ...only.length ? { only } : {},
5926
6416
  ...threshold !== void 0 ? { threshold } : {},
5927
- ...cacheDir ? { cacheDir: resolve3(cacheDir) } : {},
6417
+ ...cacheDir ? { cacheDir: resolve4(cacheDir) } : {},
5928
6418
  ...maxStories ? { maxStoriesPerComponent: maxStories } : {},
5929
6419
  ...imageBase !== void 0 ? { imageBase } : {},
5930
6420
  ...artifactUrl ? { artifactUrl } : {},
5931
- plan: has3(a, "plan"),
5932
- keepUnchanged: has3(a, "keep-unchanged")
6421
+ plan: has5(a, "plan"),
6422
+ keepUnchanged: has5(a, "keep-unchanged")
5933
6423
  });
5934
- if (has3(a, "json")) {
6424
+ if (has5(a, "json")) {
5935
6425
  print(JSON.stringify({ summary: r.summary, manifest: r.manifestPath, comment: r.commentPath ?? null, ...r.manifest.summary, selection: r.manifest.selection }, null, 2));
5936
6426
  return 0;
5937
6427
  }
5938
6428
  const sel = r.manifest.selection;
5939
- if (has3(a, "plan")) {
6429
+ if (has5(a, "plan")) {
5940
6430
  print(`Plan ${r.manifest.base.ref} (${r.manifest.base.sha.slice(0, 7)}) -> ${r.manifest.head.ref} (${r.manifest.head.sha.slice(0, 7)})`);
5941
6431
  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(", ")})` : ""}`);
5942
6432
  print(` components: ${sel.components.map((c) => c.id).join(", ") || "none"}`);
@@ -5952,12 +6442,12 @@ async function impact(a, root) {
5952
6442
  }
5953
6443
  var day = (iso) => iso.slice(0, 10);
5954
6444
  async function historyCmd(a, root) {
5955
- const ctx = await BridgeContext4.create({ root });
6445
+ const ctx = await BridgeContext6.create({ root });
5956
6446
  const token = a._[1];
5957
- const limit = Number(flag3(a, "limit") ?? 20);
6447
+ const limit = Number(flag4(a, "limit") ?? 20);
5958
6448
  if (token) {
5959
6449
  const r2 = await hist.getTokenHistory(ctx, { path: token, limit });
5960
- if (has3(a, "json")) return print(JSON.stringify(r2, null, 2)), 0;
6450
+ if (has5(a, "json")) return print(JSON.stringify(r2, null, 2)), 0;
5961
6451
  if (!r2.total) return print(`No recorded change of ${r2.path} in ${r2.source}.`), 0;
5962
6452
  print(`${r2.path}: ${r2.total} change(s) (${r2.source})
5963
6453
  `);
@@ -5968,9 +6458,9 @@ async function historyCmd(a, root) {
5968
6458
  }
5969
6459
  return 0;
5970
6460
  }
5971
- const tier = flag3(a, "tier");
5972
- const r = await hist.getHistory(ctx, { limit, ...flag3(a, "author") ? { author: flag3(a, "author") } : {}, ...tier ? { tier: Number(tier) } : {}, ...flag3(a, "version") ? { version: flag3(a, "version") } : {}, ...has3(a, "breaking") ? { breaking: true } : {} });
5973
- if (has3(a, "json")) return print(JSON.stringify(r, null, 2)), 0;
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;
5974
6464
  print(`${r.source}${r.currentVersion ? `, package version ${r.currentVersion}` : ""}`);
5975
6465
  if (!r.total) return print(r.note ?? "No token change matches."), 0;
5976
6466
  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" : ""}`);
@@ -5988,10 +6478,10 @@ ${r.truncated}`);
5988
6478
  async function revertCmd(a, root) {
5989
6479
  const target = a._[1];
5990
6480
  if (!target) throw new UsageError("bridge revert <pr|commit|id> [--dry-run] (ids from `bridge history`)");
5991
- const ctx = await BridgeContext4.create({ root });
6481
+ const ctx = await BridgeContext6.create({ root });
5992
6482
  const resolutions = Object.fromEntries((a.flags["use-before"] ?? []).map((k) => [k, "use-before"]));
5993
- const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts: has3(a, "restore-conflicts"), dryRun: has3(a, "dry-run") });
5994
- if (has3(a, "json")) {
6483
+ const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts: has5(a, "restore-conflicts"), dryRun: has5(a, "dry-run") });
6484
+ if (has5(a, "json")) {
5995
6485
  print(JSON.stringify(r, null, 2));
5996
6486
  return r.status === "blocked" ? 1 : 0;
5997
6487
  }
@@ -6016,25 +6506,25 @@ async function audit(a, root) {
6016
6506
  const target = a._[1];
6017
6507
  if (!target) throw new UsageError("bridge audit <path> (a product folder or file that uses the design system)");
6018
6508
  if (!existsSync6(target)) throw new UsageError(`No such file or folder: ${target}`);
6019
- const dsRoot = flag3(a, "ds") ? resolve3(flag3(a, "ds")) : existsSync6(join6(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
6509
+ const dsRoot = flag4(a, "ds") ? resolve4(flag4(a, "ds")) : existsSync6(join6(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
6020
6510
  if (!dsRoot) throw new UsageError(`No ${CONFIG_FILE2} found: run bridge audit from the design system repo or pass --ds <dir>.`);
6021
6511
  const ds = loadDesignSystem(dsRoot);
6022
- if (has3(a, "fix")) {
6023
- const write = has3(a, "write");
6512
+ if (has5(a, "fix")) {
6513
+ const write = has5(a, "write");
6024
6514
  const changes = fixCss(target, ds.index, write);
6025
6515
  const n = changes.reduce((acc, c) => acc + c.edits.length, 0);
6026
6516
  for (const c of changes) for (const e of c.edits) print(`${c.file}:${e.line}:${e.column} ${e.from} -> ${e.to}`);
6027
6517
  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.");
6028
6518
  return 0;
6029
6519
  }
6030
- const baselinePath = flag3(a, "baseline");
6520
+ const baselinePath = flag4(a, "baseline");
6031
6521
  let baseline;
6032
6522
  if (baselinePath) {
6033
6523
  if (!existsSync6(baselinePath)) throw new UsageError(`Baseline not found: ${baselinePath}`);
6034
- baseline = JSON.parse(await readFile6(baselinePath, "utf8"));
6524
+ baseline = JSON.parse(await readFile7(baselinePath, "utf8"));
6035
6525
  }
6036
- const label = flag3(a, "label");
6037
- const allowlistPath = flag3(a, "allowlist");
6526
+ const label = flag4(a, "label");
6527
+ const allowlistPath = flag4(a, "allowlist");
6038
6528
  if (allowlistPath && !existsSync6(allowlistPath)) throw new UsageError(`Allowlist not found: ${allowlistPath}`);
6039
6529
  let allowlist;
6040
6530
  try {
@@ -6044,8 +6534,8 @@ async function audit(a, root) {
6044
6534
  }
6045
6535
  const report = runAudit(target, { ds: dsRoot, version: VERSION, ...label ? { label } : {}, ...baseline ? { baseline } : {}, ...allowlist ? { allowlist } : {} });
6046
6536
  const md = auditMarkdown(report);
6047
- const jsonOut = flag3(a, "json");
6048
- const mdOut = flag3(a, "md");
6537
+ const jsonOut = flag4(a, "json");
6538
+ const mdOut = flag4(a, "md");
6049
6539
  if (jsonOut && jsonOut !== "true") await writeFile5(jsonOut, `${JSON.stringify(report, null, 2)}
6050
6540
  `);
6051
6541
  if (mdOut && mdOut !== "true") await writeFile5(mdOut, `${md}
@@ -6085,7 +6575,7 @@ async function audit(a, root) {
6085
6575
  for (const f of report.files) for (const e of f.ignoreErrors ?? []) process.stderr.write(`${report.target.path}/${f.path}:${e.line}: ${e.message}
6086
6576
  `);
6087
6577
  let code = 0;
6088
- const failUnder = flag3(a, "fail-under");
6578
+ const failUnder = flag4(a, "fail-under");
6089
6579
  if (failUnder !== void 0) {
6090
6580
  const min = Number(failUnder);
6091
6581
  if (!Number.isFinite(min)) throw new UsageError("--fail-under needs a number, e.g. --fail-under 80");
@@ -6096,7 +6586,7 @@ async function audit(a, root) {
6096
6586
  code = 1;
6097
6587
  }
6098
6588
  }
6099
- if (has3(a, "no-regress") && report.baseline) {
6589
+ if (has5(a, "no-regress") && report.baseline) {
6100
6590
  const b = report.baseline;
6101
6591
  if ((b.tokenAdoption.delta ?? 0) < 0 || (b.componentAdoption.delta ?? 0) < 0 || b.hardcoded.delta > 0) {
6102
6592
  process.stderr.write("bridge audit: adoption regressed since the baseline\n");
@@ -6108,9 +6598,9 @@ async function audit(a, root) {
6108
6598
  async function docsCmd(a, root) {
6109
6599
  const [, sub, name] = a._;
6110
6600
  if (sub !== "draft" || !name) throw new UsageError("Usage: bridge docs draft <Component> [--json]");
6111
- const ctx = await BridgeContext4.create({ root });
6601
+ const ctx = await BridgeContext6.create({ root });
6112
6602
  const d = await ops.draftDocsOp(ctx, { name });
6113
- if (has3(a, "json")) {
6603
+ if (has5(a, "json")) {
6114
6604
  print(JSON.stringify({ component: d.component, path: d.path, exists: d.exists, prefill: d.prefill, prompt: d.prompt }, null, 2));
6115
6605
  return 0;
6116
6606
  }
@@ -6141,12 +6631,16 @@ async function main() {
6141
6631
  const argv = process.argv.slice(2);
6142
6632
  const a = parse(argv);
6143
6633
  const cmd = a._[0];
6144
- const root = resolve3(flag3(a, "root") ?? process.cwd());
6634
+ const root = resolve4(flag4(a, "root") ?? process.cwd());
6145
6635
  if (cmd === "request") return runRequest(a, root);
6146
6636
  if (cmd === "explore") return runExplore(a, root);
6147
- if (!cmd || has3(a, "help") || cmd === "help") {
6637
+ if (cmd === "branch") return runBranch(a, root);
6638
+ if (cmd === "pieces") return runPieces(a, root);
6639
+ if (cmd === "products" || cmd === "settings" || cmd === "coverage") return runStudioRead(cmd, a, root);
6640
+ if (cmd === "docs" && a._[1] === "save") return runDocsSave(a, root);
6641
+ if (!cmd || has5(a, "help") || cmd === "help") {
6148
6642
  print(HELP);
6149
- return cmd || has3(a, "help") ? 0 : 2;
6643
+ return cmd || has5(a, "help") ? 0 : 2;
6150
6644
  }
6151
6645
  if (cmd === "--version" || cmd === "version") return print(VERSION), 0;
6152
6646
  switch (cmd) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge-ds/cli",
3
- "version": "0.4.0",
3
+ "version": "0.6.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.3.3",
47
- "@bridge-ds/visual": "^0.3.0",
46
+ "@bridge-ds/mcp": "^0.5.0",
47
+ "@bridge-ds/visual": "^0.3.3",
48
48
  "typescript": "5.9.3"
49
49
  },
50
50
  "devDependencies": {