@bridge-ds/mcp 0.4.0 → 0.4.1

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.
@@ -154,11 +154,8 @@ export declare function listProducts(ctx: BridgeContext): Promise<{
154
154
  * Throws with the list when the name is unknown.
155
155
  */
156
156
  export declare function resolveProduct(ctx: BridgeContext, name?: string | undefined): Promise<ProductConfig>;
157
- /** Labels and a body line for a branch that starts work for a product (the default product adds nothing). */
158
- export declare function productMarks(products: ProductConfig[], p: ProductConfig): {
159
- labels: string[];
160
- line: string;
161
- };
157
+ /** Labels and a body line for a branch that starts work for a product: the core rule Studio writes too. */
158
+ export { productMarks } from '@bridge-ds/core';
162
159
  /**
163
160
  * Try it in a product and share it with a product team: the preview of each product Vercel reported for this branch
164
161
  * (opened with the Bridge toolbar on it), and the install command product teams use in a staging branch, with a
@@ -52,7 +52,7 @@ import {
52
52
  previewUrl,
53
53
  previewsSettled,
54
54
  productForPlace,
55
- productLabel,
55
+ productMarks,
56
56
  productOfBranch,
57
57
  productsOf,
58
58
  protectedBranchesOf,
@@ -72,7 +72,7 @@ import {
72
72
  unreadableTeams,
73
73
  validateLayered,
74
74
  whoCanShipSentence
75
- } from "./chunk-7DBLDJIL.js";
75
+ } from "./chunk-QTO4EP6V.js";
76
76
 
77
77
  // src/lib.ts
78
78
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -4084,7 +4084,7 @@ Exploration: never merged as is. Promote it to review or close it.`,
4084
4084
  const ctx = { owner: gh.owner, repo: gh.name, packages: s.packages, apps: s.apps, ...s.requests?.length ? { requests: s.requests } : {} };
4085
4085
  const title = full.jira ? `${full.jira.trim().toUpperCase()} ${explorationPrTitle(full)}` : explorationPrTitle(full);
4086
4086
  const pr = await x.createPullRequest({ title, head: branch, base: s.base, body: explorationPrBody(full, ctx), draft: true });
4087
- await x.addLabels(pr.number, [EXPLORATION_LABEL]);
4087
+ await x.addLabels(pr.number, [EXPLORATION_LABEL, ...s.labels ?? []]);
4088
4088
  await x.updatePullRequest(pr.number, { body: explorationPrBody(full, { ...ctx, pr: pr.number }) }).catch(() => void 0);
4089
4089
  return {
4090
4090
  branch,
@@ -4606,7 +4606,7 @@ function branchWithJiraKey(branch, key) {
4606
4606
  // ../core/src/docs-proposal.ts
4607
4607
  var DOCS_EDITOR_ORIGIN = {
4608
4608
  where: "in the Bridge docs editor",
4609
- footer: 'Proposed with the Bridge docs editor by a person (no AI involved in this text unless the docs say "draftedBy").',
4609
+ footer: "Written by a person in the Bridge docs editor. No AI involved in this text.",
4610
4610
  label: "source: bridge-web"
4611
4611
  };
4612
4612
  var DOCS_MCP_ORIGIN = {
@@ -4642,13 +4642,55 @@ function cleanDocs(d) {
4642
4642
  }
4643
4643
  var docsFileText = (d, original) => patchDocsJson(original, cleanDocs(d));
4644
4644
  var slugDate = (d) => d.toISOString().slice(0, 10).replace(/-/g, "");
4645
+ var docsSlug = (nameOrSlug) => nameOrSlug.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9-]+/g, "-").slice(0, 40);
4646
+ function defaultDocsFooter(edits, origin) {
4647
+ if (origin.label !== DOCS_EDITOR_ORIGIN.label) return origin.footer;
4648
+ const claude = edits.some((e) => {
4649
+ try {
4650
+ return JSON.parse(e.after).draftedBy === "claude";
4651
+ } catch {
4652
+ return false;
4653
+ }
4654
+ });
4655
+ return claude ? `Drafted by Claude AI (the docs say "draftedBy": "claude"), sent by a person ${origin.where}.` : origin.footer;
4656
+ }
4657
+ function withAuthorLine(body, line) {
4658
+ const i = body.lastIndexOf("\n---\n");
4659
+ return i < 0 ? `${body.trimEnd()}
4660
+
4661
+ ---
4662
+ ${line}` : `${body.slice(0, i + 5)}${line}`;
4663
+ }
4664
+ var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4665
+ function isDocsProposalBranch(branch, component) {
4666
+ const slugs = /* @__PURE__ */ new Set([docsSlug(component.slug ?? component.name), docsSlug(component.name), docsSlug(component.name.toLowerCase())]);
4667
+ return [...slugs].some((s) => new RegExp(`^docs/(?:[A-Z][A-Z0-9]*-\\d+-)?${escapeRe(s)}-\\d{8}-[a-z0-9]+$`).test(branch));
4668
+ }
4669
+ function findOpenDocsProposal(pulls, component, base) {
4670
+ const hit = pulls.filter((p) => p.state === "open" && isDocsProposalBranch(p.headRef, component) && (!base || !p.baseRef || p.baseRef === base)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))[0];
4671
+ return hit ? { number: hit.number, url: hit.url, branch: hit.headRef, author: hit.author, title: hit.title, updatedAt: hit.updatedAt } : null;
4672
+ }
4673
+ async function findOpenDocsProposalOnGitHub(gh, component, base) {
4674
+ return findOpenDocsProposal(await gh.listPullRequests("open"), component, base);
4675
+ }
4676
+ async function continueDocsProposalOnGitHub(gh, open, edit, opts = {}) {
4677
+ const message = opts.message ?? `docs(${docsSlug(edit.component)}): edit usage guidance in the Bridge docs editor`;
4678
+ const cur = await gh.getFile(edit.path, open.branch);
4679
+ if (cur?.text === edit.after) return "";
4680
+ const sha = await gh.putFile({ path: edit.path, content: edit.after, branch: open.branch, message, sha: cur?.sha });
4681
+ if (opts.authorLine) {
4682
+ const line = opts.authorLine;
4683
+ await gh.getPullRequest(open.number).then((pr) => gh.updatePullRequest(open.number, { body: withAuthorLine(pr.body, line) })).catch(() => void 0);
4684
+ }
4685
+ return sha;
4686
+ }
4645
4687
  function docsProposal(edits, opts) {
4646
4688
  const origin = opts.origin ?? DOCS_EDITOR_ORIGIN;
4647
4689
  if (!edits.length) throw new Error("No docs edits in the draft.");
4648
4690
  const names2 = edits.map((e) => e.component);
4649
4691
  const list = names2.length === 1 ? names2[0] : `${names2.slice(0, -1).join(", ")} and ${names2[names2.length - 1]}`;
4650
4692
  const title = opts.title?.trim() || `docs(${names2.length === 1 ? edits[0].slug : "components"}): usage guidance for ${list}`;
4651
- const slug2 = `${names2.length === 1 ? edits[0].slug : `${edits[0].slug}-and-${names2.length - 1}-more`}`.toLowerCase().replace(/[^a-z0-9-]+/g, "-").slice(0, 40);
4693
+ const slug2 = docsSlug(names2.length === 1 ? edits[0].slug : `${edits[0].slug}-and-${names2.length - 1}-more`);
4652
4694
  const branch = `docs/${slug2}-${slugDate(opts.date)}-${opts.date.getTime().toString(36).slice(-4)}`;
4653
4695
  const csPath = `${opts.changesetDir ?? ".changeset"}/docs-${slug2}-${opts.date.getTime().toString(36)}.md`;
4654
4696
  const pkg = opts.packageName ?? "@bridge-ds/react";
@@ -4685,12 +4727,20 @@ Usage guidance for ${list}.
4685
4727
  ${opts.notes.trim()}
4686
4728
  ` : "",
4687
4729
  "---",
4688
- origin.footer
4730
+ opts.authorLine?.trim() || defaultDocsFooter(edits, origin)
4689
4731
  ].filter((x, i, a) => !(x === "" && a[i - 1] === "")).join("\n");
4690
4732
  return { branch, title, body, files, risk, bump: "patch", labels: ["docs", origin.label, `tier-${risk.tier}`] };
4691
4733
  }
4692
4734
  async function proposeDocsOnGitHub(gh, edits, opts) {
4693
- const p = docsProposal(edits, { title: opts.title, notes: opts.notes, date: opts.date ?? /* @__PURE__ */ new Date(), origin: opts.origin });
4735
+ if (opts.continueOpen !== false && edits.length === 1) {
4736
+ const e = edits[0];
4737
+ const open = await findOpenDocsProposalOnGitHub(gh, { name: e.component, slug: e.slug }, opts.base).catch(() => null);
4738
+ if (open) {
4739
+ const sha = await continueDocsProposalOnGitHub(gh, open, e, { authorLine: opts.authorLine?.trim() || defaultDocsFooter(edits, opts.origin ?? DOCS_EDITOR_ORIGIN) });
4740
+ return { number: open.number, url: open.url, branch: open.branch, title: open.title, continued: true, unchanged: !sha };
4741
+ }
4742
+ }
4743
+ const p = docsProposal(edits, { title: opts.title, notes: opts.notes, date: opts.date ?? /* @__PURE__ */ new Date(), origin: opts.origin, authorLine: opts.authorLine });
4694
4744
  const prTitle = titleWithJiraKey(p.title, opts.jira ?? null);
4695
4745
  const branch = branchWithJiraKey(p.branch, opts.jira ?? null);
4696
4746
  const mainSha = await gh.getBranchSha(opts.base);
@@ -4702,7 +4752,7 @@ ${opts.commitNote ?? "Proposed with the Bridge docs editor."}`, tree, [mainSha])
4702
4752
  await gh.createBranch(branch, commit);
4703
4753
  const pr = await gh.createPullRequest({ title: prTitle, head: branch, base: opts.base, body: p.body, draft: false });
4704
4754
  await gh.addLabels(pr.number, [...p.labels, ...opts.extraLabels ?? []]).catch(() => false);
4705
- return { number: pr.number, url: pr.url, branch, title: prTitle };
4755
+ return { number: pr.number, url: pr.url, branch, title: prTitle, continued: false, unchanged: false };
4706
4756
  }
4707
4757
  async function commitFileOnGitHub(gh, input) {
4708
4758
  const cur = await gh.getFile(input.path, input.branch);
@@ -7803,10 +7853,6 @@ async function resolveProduct(ctx, name) {
7803
7853
  }
7804
7854
  return (await productHere(ctx, config)).product;
7805
7855
  }
7806
- function productMarks(products, p) {
7807
- if (p.key === defaultProduct(products).key) return { labels: [], line: "" };
7808
- return { labels: [productLabel(p.key)], line: `**Product:** ${p.name}` };
7809
- }
7810
7856
  async function tryBranch(ctx, a) {
7811
7857
  const n = branchNumber(a.pr);
7812
7858
  const gh = await reader(ctx);
@@ -8087,8 +8133,7 @@ async function startExploration(ctx, a) {
8087
8133
  if (a.dryRun) return { status: "dry-run", ...preview };
8088
8134
  const work = `Work on it: \`git fetch origin && git switch ${branch}\`. Token edits: bridge_propose_token_change with "branch": "${branch}". Requests: bridge_create_request with "exploration": "${branch}", then bridge_get_request with "branch": "${branch}". It is never merged as is: promote it (\`bridge explore promote ${slug2}\`) or close it.`;
8089
8135
  if (gh && ctx.canWriteRemote) {
8090
- const r = await startExplorationOnGitHub(gh, e, s);
8091
- if (marks.labels.length) await gh.addLabels(r.pr, marks.labels);
8136
+ const r = await startExplorationOnGitHub(gh, e, { ...s, labels: marks.labels });
8092
8137
  return {
8093
8138
  status: "started",
8094
8139
  ...preview,
@@ -8819,7 +8864,7 @@ async function visualImpact(ctx, a, run2) {
8819
8864
  if (ctx.mode !== "local" || !ctx.root) throw new Error("Visual impact needs a local checkout (start Bridge with --root): it builds Storybook and the app at both refs.");
8820
8865
  if (!ctx.isGit) throw new Error(`${ctx.root} is not a git repository.`);
8821
8866
  const out = a.out ? resolve3(ctx.root, a.out) : join4(ctx.root, "visual-impact");
8822
- const runImpact = run2 ?? (await import("./src-2YRRAUUM.js")).runImpact;
8867
+ const runImpact = run2 ?? (await import("./src-DHJOM5PQ.js")).runImpact;
8823
8868
  const r = await runImpact({
8824
8869
  root: ctx.root,
8825
8870
  base: a.base ?? "main",
@@ -9486,8 +9531,10 @@ async function saveDocs(ctx, a) {
9486
9531
  return docsFileText(cleanDocs(merged), current);
9487
9532
  };
9488
9533
  const gh = await ctx.github();
9489
- if (a.pr !== void 0) {
9490
- const n = branchNumber(a.pr);
9534
+ const open = a.pr === void 0 && gh ? await findOpenDocsProposalOnGitHub(gh, { name, slug: docsSlug(name) }, ctx.ref ?? baseBranchOf(config)).catch(() => null) : null;
9535
+ const target = a.pr ?? open?.number;
9536
+ if (target !== void 0) {
9537
+ const n = branchNumber(target);
9491
9538
  if (!gh) throw new Error('No GitHub repo known: set "repo" in bridge.config.json or add an origin remote.');
9492
9539
  const pr = await gh.getPullRequest(n);
9493
9540
  if (pr.state !== "open") throw new Error(`#${n} is ${pr.state}: save the docs on a new branch instead (leave pr out).`);
@@ -9495,14 +9542,23 @@ async function saveDocs(ctx, a) {
9495
9542
  if (current === null) throw new Error(`${path} is not on #${n}. Save the docs on a new branch instead (leave pr out), or check the component name.`);
9496
9543
  const text2 = next(current);
9497
9544
  if (a.dryRun) return { status: "dry-run", pr: n, path, content: text2 };
9545
+ if (!ctx.canWriteRemote && open) return { status: "preview", pr: n, path, content: text2, said: `Nothing sent: the ${name} docs have an open proposal (#${n}) and adding to it needs a GitHub token with ${RIGHTS.docs}. Set GITHUB_TOKEN.` };
9498
9546
  if (!ctx.canWriteRemote) throw new Error(`Saving docs on #${n} needs a GitHub token with ${RIGHTS.docs}. Set GITHUB_TOKEN where the Bridge MCP server runs, or save it in the Library edit mode.`);
9499
9547
  const sha = await commitFileOnGitHub(gh.withBranchPrefix(void 0), { path, branch: pr.headRef, message: `docs(${name.toLowerCase()}): edit usage guidance with Claude (Bridge MCP)`, next });
9500
- return { status: sha ? "committed" : "unchanged", pr: n, path, commit: sha || null, said: sha ? `Saved the ${name} docs on #${n}. Reviewers see them now.` : "The docs on that branch already say this." };
9548
+ const joined = open ? ` It continues the open ${name} docs proposal by @${open.author} (one open proposal per component).` : "";
9549
+ return {
9550
+ status: sha ? "committed" : "unchanged",
9551
+ pr: n,
9552
+ path,
9553
+ commit: sha || null,
9554
+ ...open ? { continued: true } : {},
9555
+ said: (sha ? `Saved the ${name} docs on #${n}. Reviewers see them now.` : "The docs on that branch already say this.") + joined
9556
+ };
9501
9557
  }
9502
9558
  const original = await ctx.source.read(path);
9503
9559
  const text = next(original);
9504
9560
  if (original === text) return { status: "unchanged", path, said: `The ${name} docs on main already say this.` };
9505
- const edit = { path, component: name, slug: name.toLowerCase(), before: original, after: text };
9561
+ const edit = { path, component: name, slug: docsSlug(name), before: original, after: text };
9506
9562
  const marks = productMarks(productsOf(config), product);
9507
9563
  const notes = [a.notes?.trim(), marks.line].filter(Boolean).join("\n\n");
9508
9564
  if (a.dryRun || !gh || !ctx.canWriteRemote) {
@@ -9577,7 +9633,7 @@ async function figmaCoverage(ctx, a = {}) {
9577
9633
  }
9578
9634
 
9579
9635
  // src/server.ts
9580
- var VERSION = true ? "0.4.0" : "dev";
9636
+ var VERSION = true ? "0.4.1" : "dev";
9581
9637
  var ok = (data) => ({ content: [{ type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) }] });
9582
9638
  var fail = (e) => ({ content: [{ type: "text", text: `Error: ${e.message ?? String(e)}` }], isError: true });
9583
9639
  var run = (fn) => async (a) => {
@@ -464,6 +464,10 @@ var DEFAULT_PRODUCTS = [
464
464
  ];
465
465
  var PRODUCT_LABEL_PREFIX = "product: ";
466
466
  var productLabel = (key) => `${PRODUCT_LABEL_PREFIX}${key}`;
467
+ function productMarks(products, p) {
468
+ if (p.key === defaultProduct(products).key) return { labels: [], line: "" };
469
+ return { labels: [productLabel(p.key)], line: `**Product:** ${p.name}` };
470
+ }
467
471
  function productsOf(config) {
468
472
  const list2 = config?.products;
469
473
  return Array.isArray(list2) && list2.length ? list2 : DEFAULT_PRODUCTS;
@@ -1987,7 +1991,7 @@ export {
1987
1991
  unreadableShipTeams,
1988
1992
  canShip,
1989
1993
  whoCanShipSentence,
1990
- productLabel,
1994
+ productMarks,
1991
1995
  productsOf,
1992
1996
  defaultProduct,
1993
1997
  findProduct,
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  VERSION,
4
4
  optionsFromArgv,
5
5
  serveStdio
6
- } from "./chunk-NYFH322K.js";
7
- import "./chunk-7DBLDJIL.js";
6
+ } from "./chunk-MXMMPO7F.js";
7
+ import "./chunk-QTO4EP6V.js";
8
8
 
9
9
  // src/index.ts
10
10
  var argv = process.argv.slice(2);
package/dist/lib.js CHANGED
@@ -25,12 +25,12 @@ import {
25
25
  requests_exports,
26
26
  serveStdio,
27
27
  studio_exports
28
- } from "./chunk-NYFH322K.js";
28
+ } from "./chunk-MXMMPO7F.js";
29
29
  import {
30
30
  CONFIG_FILE,
31
31
  loadConfig,
32
32
  toConfigJson
33
- } from "./chunk-7DBLDJIL.js";
33
+ } from "./chunk-QTO4EP6V.js";
34
34
  export {
35
35
  BridgeContext,
36
36
  CONFIG_FILE,
@@ -11,7 +11,7 @@ import {
11
11
  referenceImagePath,
12
12
  selectTokenFiles,
13
13
  showFile
14
- } from "./chunk-7DBLDJIL.js";
14
+ } from "./chunk-QTO4EP6V.js";
15
15
 
16
16
  // ../visual/src/run.ts
17
17
  import { existsSync as existsSync3 } from "node:fs";
package/dist/studio.d.ts CHANGED
@@ -18,17 +18,26 @@ export declare function saveDocs(ctx: BridgeContext, a: {
18
18
  pr: number;
19
19
  path: string;
20
20
  content: string;
21
- commit?: undefined;
22
21
  said?: undefined;
23
22
  url?: undefined;
24
23
  title?: undefined;
25
24
  product?: undefined;
26
25
  } | {
27
- status: "committed" | "unchanged";
26
+ status: "preview";
28
27
  pr: number;
29
28
  path: string;
30
- commit: string | null;
29
+ content: string;
30
+ said: string;
31
+ url?: undefined;
32
+ title?: undefined;
33
+ product?: undefined;
34
+ } | {
31
35
  said: string;
36
+ continued?: boolean | undefined;
37
+ status: "unchanged" | "committed";
38
+ pr: number;
39
+ path: string;
40
+ commit: string | null;
32
41
  content?: undefined;
33
42
  url?: undefined;
34
43
  title?: undefined;
@@ -39,7 +48,6 @@ export declare function saveDocs(ctx: BridgeContext, a: {
39
48
  said: string;
40
49
  pr?: undefined;
41
50
  content?: undefined;
42
- commit?: undefined;
43
51
  url?: undefined;
44
52
  title?: undefined;
45
53
  product?: undefined;
@@ -50,7 +58,6 @@ export declare function saveDocs(ctx: BridgeContext, a: {
50
58
  content: string;
51
59
  product: string;
52
60
  pr?: undefined;
53
- commit?: undefined;
54
61
  url?: undefined;
55
62
  title?: undefined;
56
63
  } | {
@@ -62,7 +69,6 @@ export declare function saveDocs(ctx: BridgeContext, a: {
62
69
  product: string;
63
70
  said: string;
64
71
  content?: undefined;
65
- commit?: undefined;
66
72
  }>;
67
73
  /** The settings a designer reads in Studio Settings, safe to show: roles, who ships, the owner override, protection, products, previews. */
68
74
  export declare function getSettings(ctx: BridgeContext): Promise<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge-ds/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "description": "Bridge MCP server: lets Claude (Claude Code, Claude Desktop or any MCP client) read, check and propose changes to a DTCG design system repo through pull requests.",
6
6
  "license": "MIT",
@@ -54,8 +54,8 @@
54
54
  "build:package": "node ../../scripts/build-packages.mjs mcp"
55
55
  },
56
56
  "dependencies": {
57
- "@bridge-ds/core": "^0.7.0",
58
- "@bridge-ds/visual": "^0.3.1",
57
+ "@bridge-ds/core": "^0.8.0",
58
+ "@bridge-ds/visual": "^0.3.2",
59
59
  "@modelcontextprotocol/sdk": "1.31.0",
60
60
  "typescript": "5.9.3",
61
61
  "zod": "3.25.76"