@bridge-ds/mcp 0.7.2 → 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.
- package/dist/branches.d.ts +2 -0
- package/dist/{chunk-BZ3KXXMM.js → chunk-BHODFN52.js} +19 -1
- package/dist/{chunk-FAB4HMPJ.js → chunk-VV5HHLLG.js} +24 -13
- package/dist/index.js +2 -2
- package/dist/lib.js +2 -2
- package/dist/ops.d.ts +2 -1
- package/dist/{src-D6YUOJC7.js → src-P3GHW467.js} +1 -1
- package/dist/studio.d.ts +1 -0
- package/dist/suggestions.d.ts +1 -0
- package/package.json +3 -3
package/dist/branches.d.ts
CHANGED
|
@@ -60,6 +60,7 @@ export declare function branchFacts(ctx: BridgeContext, gh: GitHubClient, n: num
|
|
|
60
60
|
push?: boolean;
|
|
61
61
|
};
|
|
62
62
|
} | null;
|
|
63
|
+
solo: string | null;
|
|
63
64
|
}>;
|
|
64
65
|
/** One branch, the way Studio shows it: status, what blocks the merge and who acts, how it stands against main. */
|
|
65
66
|
export declare function branchStatus(ctx: BridgeContext, a: {
|
|
@@ -80,6 +81,7 @@ export declare function branchStatus(ctx: BridgeContext, a: {
|
|
|
80
81
|
};
|
|
81
82
|
approvalNeeded: string;
|
|
82
83
|
approvals: {
|
|
84
|
+
soloOwner?: string | undefined;
|
|
83
85
|
approvedBy: string[];
|
|
84
86
|
changesRequestedBy: string[];
|
|
85
87
|
design: boolean;
|
|
@@ -424,6 +424,19 @@ function approverEntries(owners, rights) {
|
|
|
424
424
|
if (!rights) return { design: [...owners.design], tech: [...owners.tech] };
|
|
425
425
|
return { design: holdersOf("approveDesign", owners, rights), tech: holdersOf("approveTech", owners, rights) };
|
|
426
426
|
}
|
|
427
|
+
function soloApprover(owners) {
|
|
428
|
+
const d = [...new Set(owners.design.map(norm2))];
|
|
429
|
+
const t = [...new Set(owners.tech.map(norm2))];
|
|
430
|
+
if (d.length !== 1 || t.length !== 1 || d[0] !== t[0] || !d[0] || d[0].includes("/")) return null;
|
|
431
|
+
return d[0];
|
|
432
|
+
}
|
|
433
|
+
function soloOwnerOf(gov, teams) {
|
|
434
|
+
if (!gov.soloOwnerCanApproveBoth) return null;
|
|
435
|
+
return soloApprover(approverLists(gov.owners, gov.rights ?? null, teams));
|
|
436
|
+
}
|
|
437
|
+
function soloOwnerSentence(login, you = false) {
|
|
438
|
+
return you ? "As the only owner, your approval covers design and code." : `As the only owner, @${norm2(login)} approves design and code in one review.`;
|
|
439
|
+
}
|
|
427
440
|
function shipPolicy(owners, rights, teams) {
|
|
428
441
|
if (!rights) return null;
|
|
429
442
|
const entries = holdersOf("ship", owners, rights);
|
|
@@ -700,7 +713,8 @@ function defineConfig(input) {
|
|
|
700
713
|
minReasonLength: g.override.minReasonLength ?? DEFAULT_OVERRIDE.minReasonLength
|
|
701
714
|
}
|
|
702
715
|
} : {},
|
|
703
|
-
...g.rights ? { rights: normalizeRights(g.rights) } : {}
|
|
716
|
+
...g.rights ? { rights: normalizeRights(g.rights) } : {},
|
|
717
|
+
...g.soloOwnerCanApproveBoth === true ? { soloOwnerCanApproveBoth: true } : {}
|
|
704
718
|
},
|
|
705
719
|
products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
|
|
706
720
|
changes: input.changes === "direct" ? "direct" : DEFAULT_CHANGES_MODE,
|
|
@@ -824,6 +838,7 @@ function loadConfig(source) {
|
|
|
824
838
|
}
|
|
825
839
|
}
|
|
826
840
|
problems.push(...rightsProblems(gov.rights));
|
|
841
|
+
if (gov.soloOwnerCanApproveBoth !== void 0 && typeof gov.soloOwnerCanApproveBoth !== "boolean") problems.push('"governance.soloOwnerCanApproveBoth" must be true or false');
|
|
827
842
|
}
|
|
828
843
|
}
|
|
829
844
|
problems.push(...visualImpactProblems(d.visualImpact));
|
|
@@ -2010,6 +2025,9 @@ export {
|
|
|
2010
2025
|
DEFAULT_RIGHTS,
|
|
2011
2026
|
approverLists,
|
|
2012
2027
|
approverEntries,
|
|
2028
|
+
soloApprover,
|
|
2029
|
+
soloOwnerOf,
|
|
2030
|
+
soloOwnerSentence,
|
|
2013
2031
|
shipPolicy,
|
|
2014
2032
|
unreadableShipTeams,
|
|
2015
2033
|
canShip,
|
|
@@ -68,6 +68,9 @@ import {
|
|
|
68
68
|
selectTokenFiles,
|
|
69
69
|
shipPolicy,
|
|
70
70
|
showFile,
|
|
71
|
+
soloApprover,
|
|
72
|
+
soloOwnerOf,
|
|
73
|
+
soloOwnerSentence,
|
|
71
74
|
threeWayDiff,
|
|
72
75
|
toConfigJson,
|
|
73
76
|
tokensPath,
|
|
@@ -76,7 +79,7 @@ import {
|
|
|
76
79
|
validateLayered,
|
|
77
80
|
variantKeyOf,
|
|
78
81
|
whoCanShipSentence
|
|
79
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-BHODFN52.js";
|
|
80
83
|
|
|
81
84
|
// src/lib.ts
|
|
82
85
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -4246,7 +4249,7 @@ function summarizeCi(runs, statuses = []) {
|
|
|
4246
4249
|
const state = total === 0 ? "none" : failed.length ? "fail" : pending.length ? "pending" : "pass";
|
|
4247
4250
|
return { state, total, passed, failed, pending };
|
|
4248
4251
|
}
|
|
4249
|
-
function summarizeApprovals(reviews, owners, author) {
|
|
4252
|
+
function summarizeApprovals(reviews, owners, author, rules = {}) {
|
|
4250
4253
|
const latest = /* @__PURE__ */ new Map();
|
|
4251
4254
|
const sorted = [...reviews].sort((a2, b) => String(a2.submitted_at ?? "").localeCompare(String(b.submitted_at ?? "")));
|
|
4252
4255
|
for (const r of sorted) {
|
|
@@ -4264,7 +4267,8 @@ function summarizeApprovals(reviews, owners, author) {
|
|
|
4264
4267
|
let tech;
|
|
4265
4268
|
if (d.length || t.length) {
|
|
4266
4269
|
design = d.length ? a.some((x) => d.includes(x)) : a.length > 0;
|
|
4267
|
-
const
|
|
4270
|
+
const solo = rules.soloOwnerCanApproveBoth ? soloApprover(owners) : null;
|
|
4271
|
+
const soloBoth = (x) => a.length === 1 && d.includes(x) && x !== solo;
|
|
4268
4272
|
tech = t.length ? a.some((x) => t.includes(x) && !soloBoth(x)) : a.length > 1;
|
|
4269
4273
|
} else {
|
|
4270
4274
|
design = a.length >= 1;
|
|
@@ -4735,7 +4739,7 @@ function summarizeSuggestionApprovals(comments, gov, opts) {
|
|
|
4735
4739
|
latestOld.delete(c.author);
|
|
4736
4740
|
reviews.push({ user: { login: c.author }, state: r.event === "approve" ? "APPROVED" : "CHANGES_REQUESTED", submitted_at: at });
|
|
4737
4741
|
});
|
|
4738
|
-
const summary2 = summarizeApprovals(reviews, lists, opts.author ?? void 0);
|
|
4742
|
+
const summary2 = summarizeApprovals(reviews, lists, opts.author ?? void 0, { soloOwnerCanApproveBoth: gov.soloOwnerCanApproveBoth });
|
|
4739
4743
|
const stale = [...latestOld.keys()].filter((l) => !summary2.approvers.includes(l) && !summary2.changesRequested.includes(l));
|
|
4740
4744
|
return { ...summary2, stale, ignored: [...ignored].filter((l) => !summary2.approvers.includes(l)) };
|
|
4741
4745
|
}
|
|
@@ -6829,7 +6833,7 @@ async function planSuggestionApply(input) {
|
|
|
6829
6833
|
for (const p of new Set(targets)) if (await ws.isSymlink(p)) links.push(`"${p}" is a symbolic link (or inside one): Bridge never writes through links.`);
|
|
6830
6834
|
if (links.length) return out("refused", "Not applied: the suggestion writes through a symbolic link.", { problems: links });
|
|
6831
6835
|
}
|
|
6832
|
-
const governance = input.governance ?? { owners: config.governance.owners, rights: config.governance.rights ?? null };
|
|
6836
|
+
const governance = input.governance ?? { owners: config.governance.owners, rights: config.governance.rights ?? null, soloOwnerCanApproveBoth: config.governance.soloOwnerCanApproveBoth };
|
|
6833
6837
|
const approvals = summarizeSuggestionApprovals(input.comments, governance, { payloadSha: parsed.sha, author: input.author });
|
|
6834
6838
|
const review = suggestionReviewState(payload.tier.tier, approvals);
|
|
6835
6839
|
const missing = pendingApprovals(payload.tier.tier, approvals);
|
|
@@ -10200,7 +10204,7 @@ async function people(gh, config) {
|
|
|
10200
10204
|
const { owners, rights } = config.governance;
|
|
10201
10205
|
const teams = await new TeamDirectory(gh).resolve(owners).catch(() => void 0);
|
|
10202
10206
|
const lists = expandOwners(approverEntries(owners, rights), teams);
|
|
10203
|
-
return { lists, ship: shipPolicy(owners, rights, teams) };
|
|
10207
|
+
return { lists, ship: shipPolicy(owners, rights, teams), solo: config.governance.soloOwnerCanApproveBoth ? soloApprover(lists) : null };
|
|
10204
10208
|
}
|
|
10205
10209
|
async function viewerOf(ctx, gh) {
|
|
10206
10210
|
if (!ctx.canWriteRemote) return null;
|
|
@@ -10226,7 +10230,8 @@ async function branchFacts(ctx, gh, n, viewer) {
|
|
|
10226
10230
|
const approvals = summarizeApprovals(
|
|
10227
10231
|
reviews.map((r) => ({ user: r.user ? { login: r.user } : null, state: r.state, submitted_at: r.submittedAt ?? null })),
|
|
10228
10232
|
who.lists,
|
|
10229
|
-
pr.author
|
|
10233
|
+
pr.author,
|
|
10234
|
+
{ soloOwnerCanApproveBoth: Boolean(who.solo) }
|
|
10230
10235
|
);
|
|
10231
10236
|
const tier = effectiveTier(labels, pr.body, files);
|
|
10232
10237
|
const merged = pr.state === "merged";
|
|
@@ -10236,7 +10241,7 @@ async function branchFacts(ctx, gh, n, viewer) {
|
|
|
10236
10241
|
const status = reviewStatusKind({ ...pullLike, ci, approvals, tier, mergeable: pr.mergeable });
|
|
10237
10242
|
const fresh = freshness({ compare: compare ? { behind_by: compare.behindBy, merge_base_commit: compare.mergeBase ? { sha: compare.mergeBase } : null } : null, mergeable: pr.mergeable, open: pr.state === "open" });
|
|
10238
10243
|
const product = productOfBranch(productsOf(config), { files: files.map((f) => f.filename), labels: pr.labels });
|
|
10239
|
-
return { config, pr, files, ci, approvals, tier, gate, status, fresh, compare, exploration, product, lifecycle: pullState(pullLike), write: repo ? hasWriteAccess(repo.permissions) : null, repo };
|
|
10244
|
+
return { config, pr, files, ci, approvals, tier, gate, status, fresh, compare, exploration, product, lifecycle: pullState(pullLike), write: repo ? hasWriteAccess(repo.permissions) : null, repo, solo: who.solo };
|
|
10240
10245
|
}
|
|
10241
10246
|
var STATUS_WORDS = {
|
|
10242
10247
|
merged: "Merged",
|
|
@@ -10267,7 +10272,7 @@ async function branchStatus(ctx, a) {
|
|
|
10267
10272
|
lifecycle: f.lifecycle,
|
|
10268
10273
|
status: { kind: f.status, label: STATUS_WORDS[f.status] },
|
|
10269
10274
|
approvalNeeded: f.tier === 1 ? "One approval" : "Design and tech approval",
|
|
10270
|
-
approvals: { approvedBy: f.approvals.approvers, changesRequestedBy: f.approvals.changesRequested, design: f.approvals.design, tech: f.approvals.tech },
|
|
10275
|
+
approvals: { approvedBy: f.approvals.approvers, changesRequestedBy: f.approvals.changesRequested, design: f.approvals.design, tech: f.approvals.tech, ...f.solo && (f.tier ?? 3) > 1 ? { soloOwner: soloOwnerSentence(f.solo, Boolean(viewer && viewer.toLowerCase() === f.solo)) } : {} },
|
|
10271
10276
|
tests: { state: f.ci.state, failing: f.ci.failed.map((x) => x.name), running: f.ci.pending.map((x) => x.name) },
|
|
10272
10277
|
main: { state: f.fresh.state, behindBy: f.fresh.behindBy },
|
|
10273
10278
|
merge: {
|
|
@@ -10878,7 +10883,7 @@ async function suggestionGovernance(ctx, gh) {
|
|
|
10878
10883
|
const { owners, rights } = config.governance;
|
|
10879
10884
|
const hasTeams = [...owners.design, ...owners.tech].some((o) => o.includes("/"));
|
|
10880
10885
|
const teams = hasTeams ? await new TeamDirectory(gh).resolve(owners).catch(() => void 0) : void 0;
|
|
10881
|
-
return { config, gov: { owners, rights: rights ?? null, ...teams ? { teams } : {} } };
|
|
10886
|
+
return { config, gov: { owners, rights: rights ?? null, ...teams ? { teams } : {}, ...config.governance.soloOwnerCanApproveBoth ? { soloOwnerCanApproveBoth: true } : {} } };
|
|
10882
10887
|
}
|
|
10883
10888
|
var OPEN_STATUSES = ["needs-review", "changes-requested", "approved", "applying", "outdated"];
|
|
10884
10889
|
async function listSuggestionsOp(ctx, a = {}) {
|
|
@@ -10925,6 +10930,7 @@ async function getSuggestionOp(ctx, a) {
|
|
|
10925
10930
|
const p = d.parsed.payload;
|
|
10926
10931
|
const approvals = d.approvals;
|
|
10927
10932
|
const missing = pendingApprovals(p.tier.tier, approvals);
|
|
10933
|
+
const solo = soloOwnerOf(gov, gov.teams);
|
|
10928
10934
|
return {
|
|
10929
10935
|
...base,
|
|
10930
10936
|
kind: p.kind,
|
|
@@ -10952,7 +10958,8 @@ async function getSuggestionOp(ctx, a) {
|
|
|
10952
10958
|
missing: missing.map(roleText),
|
|
10953
10959
|
changesRequestedBy: approvals.changesRequested,
|
|
10954
10960
|
...approvals.stale.length ? { staleReviews: approvals.stale } : {},
|
|
10955
|
-
...approvals.ignored.length ? { notCounted: approvals.ignored } : {}
|
|
10961
|
+
...approvals.ignored.length ? { notCounted: approvals.ignored } : {},
|
|
10962
|
+
...solo && p.tier.tier > 1 ? { soloOwner: soloOwnerSentence(solo) } : {}
|
|
10956
10963
|
},
|
|
10957
10964
|
applied: d.applied,
|
|
10958
10965
|
...p.componentSpec ? { brief: p.componentSpec.brief } : {},
|
|
@@ -11701,9 +11708,11 @@ async function reviewRoles(gh, config, approvers) {
|
|
|
11701
11708
|
});
|
|
11702
11709
|
const ship = shipPolicy(owners, rights, teams);
|
|
11703
11710
|
const unreadable = unreadableTeams(teams);
|
|
11711
|
+
const solo = soloOwnerOf(config.governance, teams);
|
|
11704
11712
|
return {
|
|
11705
11713
|
design: role("design"),
|
|
11706
11714
|
tech: role("tech"),
|
|
11715
|
+
...solo ? { soloOwner: soloOwnerSentence(solo) } : {},
|
|
11707
11716
|
ship: ship ? whoCanShipSentence(ship) : "Anyone with write access can ship.",
|
|
11708
11717
|
...unreadable.length ? { unreadableTeams: unreadable.map((t) => t.message) } : {}
|
|
11709
11718
|
};
|
|
@@ -11745,7 +11754,7 @@ async function visualImpact(ctx, a, run2) {
|
|
|
11745
11754
|
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.");
|
|
11746
11755
|
if (!ctx.isGit) throw new Error(`${ctx.root} is not a git repository.`);
|
|
11747
11756
|
const out = a.out ? resolve3(ctx.root, a.out) : join5(ctx.root, "visual-impact");
|
|
11748
|
-
const runImpact = run2 ?? (await import("./src-
|
|
11757
|
+
const runImpact = run2 ?? (await import("./src-P3GHW467.js")).runImpact;
|
|
11749
11758
|
const r = await runImpact({
|
|
11750
11759
|
root: ctx.root,
|
|
11751
11760
|
base: a.base ?? "main",
|
|
@@ -12525,6 +12534,7 @@ async function getSettings(ctx) {
|
|
|
12525
12534
|
const protection = gh ? await loadProtection(gh, baseBranchOf(config)).catch(() => null) : null;
|
|
12526
12535
|
const products = productsOf(config);
|
|
12527
12536
|
const unreadable = teams ? unreadableTeams(teams) : [];
|
|
12537
|
+
const solo = soloOwnerOf(config.governance, teams);
|
|
12528
12538
|
return {
|
|
12529
12539
|
repo: config.repo ? `${config.repo.owner}/${config.repo.name}` : gh?.fullName ?? null,
|
|
12530
12540
|
main: baseBranchOf(config),
|
|
@@ -12532,6 +12542,7 @@ async function getSettings(ctx) {
|
|
|
12532
12542
|
designApproval: entries.design.map((e) => entryLabel(e, teams)),
|
|
12533
12543
|
techApproval: entries.tech.map((e) => entryLabel(e, teams)),
|
|
12534
12544
|
ship: ship ? whoCanShipSentence(ship) : "Anyone with write access can merge.",
|
|
12545
|
+
...solo ? { soloOwner: soloOwnerSentence(solo) } : {},
|
|
12535
12546
|
...unreadable.length ? { teamsNotRead: unreadable.map((t) => t.message) } : {}
|
|
12536
12547
|
},
|
|
12537
12548
|
ownerOverride: override.allowed.length ? { on: true, who: override.allowed.map((l) => `@${l}`), reason: override.requireReason ? `A reason of at least ${override.minReasonLength} characters, posted on the branch.` : "No reason required." } : { on: false, who: [], reason: "Nobody can merge without the approvals." },
|
|
@@ -12811,7 +12822,7 @@ async function pushFigmaComponentOp(ctx, a) {
|
|
|
12811
12822
|
}
|
|
12812
12823
|
|
|
12813
12824
|
// src/server.ts
|
|
12814
|
-
var VERSION = true ? "0.
|
|
12825
|
+
var VERSION = true ? "0.8.0" : "dev";
|
|
12815
12826
|
var ok = (data) => ({ content: [{ type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) }] });
|
|
12816
12827
|
var fail2 = (e) => ({ content: [{ type: "text", text: `Error: ${e.message ?? String(e)}` }], isError: true });
|
|
12817
12828
|
var run = (fn) => async (a) => {
|
package/dist/index.js
CHANGED
package/dist/lib.js
CHANGED
|
@@ -28,12 +28,12 @@ import {
|
|
|
28
28
|
serveStdio,
|
|
29
29
|
studio_exports,
|
|
30
30
|
suggestions_exports
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-VV5HHLLG.js";
|
|
32
32
|
import {
|
|
33
33
|
CONFIG_FILE,
|
|
34
34
|
loadConfig,
|
|
35
35
|
toConfigJson
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-BHODFN52.js";
|
|
37
37
|
export {
|
|
38
38
|
BridgeContext,
|
|
39
39
|
CONFIG_FILE,
|
package/dist/ops.d.ts
CHANGED
|
@@ -736,6 +736,8 @@ export declare function getReview(ctx: BridgeContext, a: {
|
|
|
736
736
|
*/
|
|
737
737
|
export declare function reviewRoles(gh: GitHubClient, config: BridgeConfig, approvers: string[]): Promise<{
|
|
738
738
|
unreadableTeams?: string[] | undefined;
|
|
739
|
+
ship: string;
|
|
740
|
+
soloOwner?: string | undefined;
|
|
739
741
|
design: {
|
|
740
742
|
owners: string[];
|
|
741
743
|
approvedBy: string[];
|
|
@@ -744,7 +746,6 @@ export declare function reviewRoles(gh: GitHubClient, config: BridgeConfig, appr
|
|
|
744
746
|
owners: string[];
|
|
745
747
|
approvedBy: string[];
|
|
746
748
|
};
|
|
747
|
-
ship: string;
|
|
748
749
|
}>;
|
|
749
750
|
export declare function reviewPr(ctx: BridgeContext, a: {
|
|
750
751
|
pr: number;
|
package/dist/studio.d.ts
CHANGED
package/dist/suggestions.d.ts
CHANGED
|
@@ -137,6 +137,7 @@ export declare function getSuggestionOp(ctx: BridgeContext, a: {
|
|
|
137
137
|
next: string;
|
|
138
138
|
brief?: string | undefined;
|
|
139
139
|
approvals: {
|
|
140
|
+
soloOwner?: string | undefined;
|
|
140
141
|
notCounted?: string[] | undefined;
|
|
141
142
|
staleReviews?: string[] | undefined;
|
|
142
143
|
approvedBy: string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge-ds/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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.
|
|
58
|
-
"@bridge-ds/visual": "^0.3.
|
|
57
|
+
"@bridge-ds/core": "^0.13.0",
|
|
58
|
+
"@bridge-ds/visual": "^0.3.7",
|
|
59
59
|
"@modelcontextprotocol/sdk": "1.31.0",
|
|
60
60
|
"typescript": "5.9.3",
|
|
61
61
|
"zod": "3.25.76"
|