@bridge-ds/mcp 0.7.2 → 0.8.2
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-DPK46GA2.js} +33 -5
- package/dist/{chunk-FAB4HMPJ.js → chunk-QCPYJXK4.js} +214 -22
- 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-UNWS2OVM.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);
|
|
@@ -583,7 +596,7 @@ var DEFAULT_GOVERNANCE = {
|
|
|
583
596
|
owners: { design: [], tech: [] }
|
|
584
597
|
};
|
|
585
598
|
var FLAT_TOKEN_KEYS = ["primitives", "brands", "semantic", "base", "density", "components", "contrastChecks"];
|
|
586
|
-
var CANONICAL_TOKEN_KEYS = ["root", "include", "exclude", "tiers", "layers"];
|
|
599
|
+
var CANONICAL_TOKEN_KEYS = ["root", "include", "exclude", "tiers", "layers", "usage"];
|
|
587
600
|
var FLAT_MODE_KEYS = ["brands", "themes", "densities", "defaults"];
|
|
588
601
|
var AXIS_OF = { brands: "brand", themes: "theme", densities: "density" };
|
|
589
602
|
var ConfigError = class extends Error {
|
|
@@ -671,7 +684,7 @@ function defineConfig(input) {
|
|
|
671
684
|
const contrastFile = contrastPath ?? [root === "." ? "" : root, paths.contrastChecks].filter(Boolean).join("/");
|
|
672
685
|
return {
|
|
673
686
|
repo: parseRepoInput(input.repo, input.branch),
|
|
674
|
-
tokens: { ...paths, include: input.tokens?.include ?? ["**/*.tokens.json"], exclude: input.tokens?.exclude ?? [], tiers, layers },
|
|
687
|
+
tokens: { ...paths, include: input.tokens?.include ?? ["**/*.tokens.json"], exclude: input.tokens?.exclude ?? [], tiers, layers, ...t.usage ? { usage: trimSlashes(t.usage) } : {} },
|
|
675
688
|
axes,
|
|
676
689
|
modes,
|
|
677
690
|
contrast: { path: contrastFile, modes: input.contrast?.modes },
|
|
@@ -700,11 +713,13 @@ 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,
|
|
707
721
|
...input.storybook?.url ? { storybook: { url: input.storybook.url.trim().replace(/\/+$/, "") } } : {},
|
|
722
|
+
...input.library?.public === true ? { library: { public: true } } : {},
|
|
708
723
|
...input.audit ? { audit: { ...input.audit } } : {},
|
|
709
724
|
...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
|
|
710
725
|
...input.integrations ? { integrations: { ...input.integrations } } : {},
|
|
@@ -749,7 +764,7 @@ function loadConfig(source) {
|
|
|
749
764
|
if (!isObj(tokens)) problems.push('"tokens" must be an object');
|
|
750
765
|
else
|
|
751
766
|
for (const [k, v] of Object.entries(tokens)) {
|
|
752
|
-
if (FLAT_TOKEN_KEYS.includes(k) || k === "root") {
|
|
767
|
+
if (FLAT_TOKEN_KEYS.includes(k) || k === "root" || k === "usage") {
|
|
753
768
|
if (!isStr(v)) problems.push(`"tokens.${k}" must be a non-empty string`);
|
|
754
769
|
} else if (k === "include" || k === "exclude") {
|
|
755
770
|
if (!isStrList(v)) problems.push(`"tokens.${k}" must be a list of globs`);
|
|
@@ -824,6 +839,7 @@ function loadConfig(source) {
|
|
|
824
839
|
}
|
|
825
840
|
}
|
|
826
841
|
problems.push(...rightsProblems(gov.rights));
|
|
842
|
+
if (gov.soloOwnerCanApproveBoth !== void 0 && typeof gov.soloOwnerCanApproveBoth !== "boolean") problems.push('"governance.soloOwnerCanApproveBoth" must be true or false');
|
|
827
843
|
}
|
|
828
844
|
}
|
|
829
845
|
problems.push(...visualImpactProblems(d.visualImpact));
|
|
@@ -834,6 +850,14 @@ function loadConfig(source) {
|
|
|
834
850
|
else if (!/^https:\/\/[^\s/]+/.test(sb.url.trim())) problems.push('"storybook.url" must start with https://, for example https://storybook.example.com');
|
|
835
851
|
else for (const k of Object.keys(sb)) if (k !== "url") problems.push(`"storybook.${k}" is not a known key`);
|
|
836
852
|
}
|
|
853
|
+
const lib = d.library;
|
|
854
|
+
if (lib !== void 0) {
|
|
855
|
+
if (!isObj(lib)) problems.push('"library" must be an object, for example { "public": true }');
|
|
856
|
+
else {
|
|
857
|
+
if (lib.public !== void 0 && typeof lib.public !== "boolean") problems.push('"library.public" must be true or false');
|
|
858
|
+
for (const k of Object.keys(lib)) if (k !== "public") problems.push(`"library.${k}" is not a known key`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
837
861
|
problems.push(...productProblems(d.products));
|
|
838
862
|
const audit = d.audit;
|
|
839
863
|
if (audit !== void 0) {
|
|
@@ -872,7 +896,7 @@ function toConfigJson(config, schema = "./packages/core/bridge.config.schema.jso
|
|
|
872
896
|
$schema: schema,
|
|
873
897
|
version: 1,
|
|
874
898
|
...config.repo ? { repo: config.repo } : {},
|
|
875
|
-
tokens: { root: config.tokens.root, include: config.tokens.include, ...config.tokens.exclude.length ? { exclude: config.tokens.exclude } : {}, tiers: config.tokens.tiers, layers: config.tokens.layers.map((l) => l.optional ? l : { files: l.files }) },
|
|
899
|
+
tokens: { root: config.tokens.root, include: config.tokens.include, ...config.tokens.exclude.length ? { exclude: config.tokens.exclude } : {}, tiers: config.tokens.tiers, layers: config.tokens.layers.map((l) => l.optional ? l : { files: l.files }), ...config.tokens.usage ? { usage: config.tokens.usage } : {} },
|
|
876
900
|
modes: Object.fromEntries(Object.entries(config.axes).map(([k, a]) => [k, a.parity ? a : { values: a.values, default: a.default }])),
|
|
877
901
|
...config.contrast ? { contrast: config.contrast.modes ? config.contrast : { path: config.contrast.path } } : {},
|
|
878
902
|
...config.components ? { components: Object.fromEntries(Object.entries(config.components).filter(([, v]) => v !== void 0)) } : {},
|
|
@@ -885,6 +909,7 @@ function toConfigJson(config, schema = "./packages/core/bridge.config.schema.jso
|
|
|
885
909
|
...config.visualImpact ? { visualImpact: config.visualImpact } : {},
|
|
886
910
|
changes: changesMode(config),
|
|
887
911
|
...config.storybook ? { storybook: config.storybook } : {},
|
|
912
|
+
...config.library ? { library: config.library } : {},
|
|
888
913
|
...config.products && !sameProducts(config.products, DEFAULT_PRODUCTS) ? { products: config.products } : {}
|
|
889
914
|
};
|
|
890
915
|
}
|
|
@@ -2010,6 +2035,9 @@ export {
|
|
|
2010
2035
|
DEFAULT_RIGHTS,
|
|
2011
2036
|
approverLists,
|
|
2012
2037
|
approverEntries,
|
|
2038
|
+
soloApprover,
|
|
2039
|
+
soloOwnerOf,
|
|
2040
|
+
soloOwnerSentence,
|
|
2013
2041
|
shipPolicy,
|
|
2014
2042
|
unreadableShipTeams,
|
|
2015
2043
|
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-DPK46GA2.js";
|
|
80
83
|
|
|
81
84
|
// src/lib.ts
|
|
82
85
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -1260,6 +1263,44 @@ function classifyAuth(status, detail, opts = {}) {
|
|
|
1260
1263
|
}
|
|
1261
1264
|
return {};
|
|
1262
1265
|
}
|
|
1266
|
+
function memoryEtagStore(max = 500) {
|
|
1267
|
+
const map = /* @__PURE__ */ new Map();
|
|
1268
|
+
return {
|
|
1269
|
+
get(key) {
|
|
1270
|
+
const e = map.get(key);
|
|
1271
|
+
if (e) {
|
|
1272
|
+
map.delete(key);
|
|
1273
|
+
map.set(key, e);
|
|
1274
|
+
}
|
|
1275
|
+
return e;
|
|
1276
|
+
},
|
|
1277
|
+
set(key, entry) {
|
|
1278
|
+
map.delete(key);
|
|
1279
|
+
map.set(key, entry);
|
|
1280
|
+
while (map.size > max) map.delete(map.keys().next().value);
|
|
1281
|
+
},
|
|
1282
|
+
clear: () => map.clear()
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
var sharedEtags = memoryEtagStore();
|
|
1286
|
+
function tokenFingerprint(token) {
|
|
1287
|
+
if (!token) return "anon";
|
|
1288
|
+
let a = 2166136261;
|
|
1289
|
+
let b = 16777619 ^ token.length;
|
|
1290
|
+
for (let i = 0; i < token.length; i++) {
|
|
1291
|
+
const c = token.charCodeAt(i);
|
|
1292
|
+
a = Math.imul(a ^ c, 16777619) >>> 0;
|
|
1293
|
+
b = Math.imul(b ^ c ^ i & 255, 1540483477) >>> 0;
|
|
1294
|
+
}
|
|
1295
|
+
return a.toString(16).padStart(8, "0") + b.toString(16).padStart(8, "0");
|
|
1296
|
+
}
|
|
1297
|
+
function installationFor(list, owner) {
|
|
1298
|
+
const inst = list.find((i) => i.account?.login?.toLowerCase() === owner.toLowerCase());
|
|
1299
|
+
if (!inst) return { reach: "no" };
|
|
1300
|
+
const selection = inst.repositorySelection ?? inst.repository_selection;
|
|
1301
|
+
return selection === "all" ? { reach: "yes", id: inst.id } : { reach: "check", id: inst.id };
|
|
1302
|
+
}
|
|
1303
|
+
var MAX_PAGES = 10;
|
|
1263
1304
|
function rateLimitMessage(x) {
|
|
1264
1305
|
const when = x.resetsAt ? ` It resets at ${x.resetsAt.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}.` : "";
|
|
1265
1306
|
if (x.withToken) return `GitHub's hourly request limit for this token is used up.${when} Try again after that.`;
|
|
@@ -1267,8 +1308,11 @@ function rateLimitMessage(x) {
|
|
|
1267
1308
|
return `GitHub's hourly limit for requests without a token (60 an hour) is used up.${when} To keep going now, ${hint}: a token gets 5,000 requests an hour.`;
|
|
1268
1309
|
}
|
|
1269
1310
|
var DEFAULT_RETRY = { attempts: 3, baseDelayMs: 600, sleep: (ms) => new Promise((r) => setTimeout(r, ms)) };
|
|
1311
|
+
var hourlyLimitErrors = /* @__PURE__ */ new WeakSet();
|
|
1270
1312
|
var isTransient = (status, detail) => status === 0 || status >= 500 || status === 429 || status === 403 && /secondary rate limit/i.test(detail);
|
|
1271
1313
|
var API = "https://api.github.com";
|
|
1314
|
+
var ACCEPT_JSON = "application/vnd.github+json";
|
|
1315
|
+
var ACCEPT_RAW = "application/vnd.github.raw";
|
|
1272
1316
|
var RAW = "https://raw.githubusercontent.com";
|
|
1273
1317
|
function parseRepo(repo) {
|
|
1274
1318
|
const m = /^\s*(?:https?:\/\/github\.com\/|git@github\.com:)?([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?\s*$/.exec(repo);
|
|
@@ -1287,6 +1331,7 @@ function base64ToUtf8(b64) {
|
|
|
1287
1331
|
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
1288
1332
|
return new TextDecoder("utf-8").decode(bytes);
|
|
1289
1333
|
}
|
|
1334
|
+
var copy = (value) => value === null || typeof value !== "object" ? value : typeof structuredClone === "function" ? structuredClone(value) : JSON.parse(JSON.stringify(value));
|
|
1290
1335
|
var encodeRef = (ref) => ref.split("/").map(encodeURIComponent).join("/");
|
|
1291
1336
|
var GitHubClient = class _GitHubClient {
|
|
1292
1337
|
constructor(repo, token, fetchImpl = (url, init) => fetch(url, init), retry = DEFAULT_RETRY, options = {}) {
|
|
@@ -1299,6 +1344,8 @@ var GitHubClient = class _GitHubClient {
|
|
|
1299
1344
|
this.name = r.name;
|
|
1300
1345
|
this.api = (options.apiUrl ?? API).replace(/\/$/, "");
|
|
1301
1346
|
this.protectedBranches = options.protectedBranches ?? DEFAULT_PROTECTED_BRANCHES;
|
|
1347
|
+
this.etags = options.etags === false ? void 0 : options.etags ?? sharedEtags;
|
|
1348
|
+
this.etagScope = options.etagScope ?? tokenFingerprint(token);
|
|
1302
1349
|
}
|
|
1303
1350
|
token;
|
|
1304
1351
|
fetchImpl;
|
|
@@ -1309,6 +1356,15 @@ var GitHubClient = class _GitHubClient {
|
|
|
1309
1356
|
blobCache = /* @__PURE__ */ new Map();
|
|
1310
1357
|
api;
|
|
1311
1358
|
protectedBranches;
|
|
1359
|
+
etags;
|
|
1360
|
+
etagScope;
|
|
1361
|
+
rate;
|
|
1362
|
+
/** What the conditional request cache did for this client. */
|
|
1363
|
+
etagStats = { conditional: 0, notModified: 0, stale: 0 };
|
|
1364
|
+
/** GitHub's rate-limit headers from the last API response, or undefined before any response carried them. */
|
|
1365
|
+
get rateLimit() {
|
|
1366
|
+
return this.rate;
|
|
1367
|
+
}
|
|
1312
1368
|
/** "owner/name". */
|
|
1313
1369
|
get fullName() {
|
|
1314
1370
|
return `${this.owner}/${this.name}`;
|
|
@@ -1332,45 +1388,77 @@ var GitHubClient = class _GitHubClient {
|
|
|
1332
1388
|
get repoPath() {
|
|
1333
1389
|
return `/repos/${this.owner}/${this.name}`;
|
|
1334
1390
|
}
|
|
1335
|
-
async request(method, path, body, retryable = method === "GET") {
|
|
1391
|
+
async request(method, path, body, retryable = method === "GET", opts = {}) {
|
|
1336
1392
|
const attempts = retryable ? Math.max(1, this.retry.attempts) : 1;
|
|
1337
1393
|
for (let attempt = 1; ; attempt++) {
|
|
1338
1394
|
try {
|
|
1339
|
-
return await this.once(method, path, body);
|
|
1395
|
+
return await this.once(method, path, body, opts);
|
|
1340
1396
|
} catch (err) {
|
|
1341
1397
|
const e = err;
|
|
1342
|
-
if (attempt >= attempts || !(e instanceof GitHubError) || !isTransient(e.status, e.detail ?? "")) throw err;
|
|
1398
|
+
if (attempt >= attempts || !(e instanceof GitHubError) || hourlyLimitErrors.has(e) || !isTransient(e.status, e.detail ?? "")) throw err;
|
|
1343
1399
|
await this.retry.sleep(this.retry.baseDelayMs * 2 ** (attempt - 1));
|
|
1344
1400
|
}
|
|
1345
1401
|
}
|
|
1346
1402
|
}
|
|
1347
|
-
|
|
1403
|
+
noteRateLimit(headers) {
|
|
1404
|
+
const remaining = headers?.get("x-ratelimit-remaining");
|
|
1405
|
+
if (remaining == null) return;
|
|
1406
|
+
const limit = headers?.get("x-ratelimit-limit");
|
|
1407
|
+
const reset = headers?.get("x-ratelimit-reset");
|
|
1408
|
+
this.rate = {
|
|
1409
|
+
limit: limit != null ? Number(limit) : void 0,
|
|
1410
|
+
remaining: Number(remaining),
|
|
1411
|
+
resetsAt: reset ? new Date(Number(reset) * 1e3) : void 0,
|
|
1412
|
+
resource: headers?.get("x-ratelimit-resource") ?? void 0
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
async once(method, path, body, opts = {}) {
|
|
1416
|
+
const accept = opts.accept ?? ACCEPT_JSON;
|
|
1348
1417
|
const headers = {
|
|
1349
|
-
Accept:
|
|
1418
|
+
Accept: accept,
|
|
1350
1419
|
"X-GitHub-Api-Version": "2022-11-28"
|
|
1351
1420
|
};
|
|
1352
1421
|
if (this.token) headers.Authorization = `Bearer ${this.token}`;
|
|
1353
1422
|
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
1423
|
+
const key = method === "GET" && this.etags && opts.etag !== false ? `${this.etagScope} ${accept} ${this.api}${path}` : void 0;
|
|
1424
|
+
const cached = key ? this.etags.get(key) : void 0;
|
|
1425
|
+
if (cached && this.token) {
|
|
1426
|
+
headers["If-None-Match"] = cached.etag;
|
|
1427
|
+
this.etagStats.conditional++;
|
|
1428
|
+
}
|
|
1354
1429
|
let res;
|
|
1355
1430
|
try {
|
|
1356
1431
|
res = await this.fetchImpl(`${this.api}${path}`, { method, headers, ...body !== void 0 ? { body: JSON.stringify(body) } : {} });
|
|
1357
1432
|
} catch (err) {
|
|
1433
|
+
if (err instanceof GitHubError) throw err;
|
|
1358
1434
|
throw new GitHubError(0, "Could not reach GitHub. Check your connection, then try again.", err.message);
|
|
1359
1435
|
}
|
|
1436
|
+
this.noteRateLimit(res.headers);
|
|
1437
|
+
if (res.status === 304 && cached && key && this.token) {
|
|
1438
|
+
this.etagStats.notModified++;
|
|
1439
|
+
this.etags.set(key, { ...cached, at: Date.now() });
|
|
1440
|
+
return copy(cached.body);
|
|
1441
|
+
}
|
|
1360
1442
|
if (res.status === 204) return void 0;
|
|
1361
1443
|
if (!res.ok) {
|
|
1362
1444
|
let detail = "";
|
|
1363
1445
|
try {
|
|
1364
|
-
const
|
|
1365
|
-
detail = [
|
|
1446
|
+
const data2 = await res.json();
|
|
1447
|
+
detail = [data2.message, ...(data2.errors ?? []).map((e) => typeof e === "string" ? e : e.message)].filter(Boolean).join(" ");
|
|
1366
1448
|
} catch {
|
|
1367
1449
|
detail = "";
|
|
1368
1450
|
}
|
|
1369
1451
|
const remaining = res.headers?.get("x-ratelimit-remaining");
|
|
1370
1452
|
const reset = res.headers?.get("x-ratelimit-reset");
|
|
1371
1453
|
if ((res.status === 403 || res.status === 429) && remaining === "0") {
|
|
1454
|
+
if (cached && this.options.serveStaleOnRateLimit) {
|
|
1455
|
+
this.etagStats.stale++;
|
|
1456
|
+
return copy(cached.body);
|
|
1457
|
+
}
|
|
1372
1458
|
const resetsAt = reset ? new Date(Number(reset) * 1e3) : void 0;
|
|
1373
|
-
|
|
1459
|
+
const limited = new GitHubError(429, rateLimitMessage({ withToken: Boolean(this.token), resetsAt, tokenHint: this.options.tokenHint }), detail);
|
|
1460
|
+
hourlyLimitErrors.add(limited);
|
|
1461
|
+
throw limited;
|
|
1374
1462
|
}
|
|
1375
1463
|
const info = classifyAuth(res.status, detail, {
|
|
1376
1464
|
path,
|
|
@@ -1379,7 +1467,24 @@ var GitHubClient = class _GitHubClient {
|
|
|
1379
1467
|
});
|
|
1380
1468
|
throw new GitHubError(res.status, friendlyMessage(res.status, detail, info), detail, info);
|
|
1381
1469
|
}
|
|
1382
|
-
|
|
1470
|
+
const data = opts.text ? await res.text() : await res.json();
|
|
1471
|
+
const etag = key ? res.headers?.get("etag") : null;
|
|
1472
|
+
if (key && etag) this.etags.set(key, { etag, body: copy(data), at: Date.now() });
|
|
1473
|
+
return data;
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Every item of a paged listing (`per_page=100`), at most 10 pages. `pick` takes the items out of one page (the
|
|
1477
|
+
* installation endpoints wrap them in an object with `total_count`).
|
|
1478
|
+
*/
|
|
1479
|
+
async paged(path, pick) {
|
|
1480
|
+
const out = [];
|
|
1481
|
+
const sep2 = path.includes("?") ? "&" : "?";
|
|
1482
|
+
for (let page = 1; page <= MAX_PAGES; page++) {
|
|
1483
|
+
const { items, total } = pick(await this.request("GET", `${path}${sep2}per_page=100&page=${page}`));
|
|
1484
|
+
out.push(...items);
|
|
1485
|
+
if (items.length < 100 || total !== void 0 && out.length >= total) break;
|
|
1486
|
+
}
|
|
1487
|
+
return out;
|
|
1383
1488
|
}
|
|
1384
1489
|
async getRepo() {
|
|
1385
1490
|
const r = await this.request(
|
|
@@ -1762,6 +1867,14 @@ var GitHubClient = class _GitHubClient {
|
|
|
1762
1867
|
*/
|
|
1763
1868
|
async readFile(path, ref) {
|
|
1764
1869
|
if (this.token) return this.getFileText(path, ref);
|
|
1870
|
+
return this.readRawFile(path, ref);
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Text of a file from the raw host (raw.githubusercontent.com), sent without any header so the browser makes a
|
|
1874
|
+
* simple CORS request (no preflight) and nothing counts against the API limit. Works for public repositories only:
|
|
1875
|
+
* a private file answers 404 there, like a missing one. Undefined on 404.
|
|
1876
|
+
*/
|
|
1877
|
+
async readRawFile(path, ref) {
|
|
1765
1878
|
const url = `${(this.options.rawUrl ?? RAW).replace(/\/$/, "")}/${this.owner}/${this.name}/${encodeRef(ref)}/${encodeRef(path)}`;
|
|
1766
1879
|
let res;
|
|
1767
1880
|
try {
|
|
@@ -1773,6 +1886,76 @@ var GitHubClient = class _GitHubClient {
|
|
|
1773
1886
|
if (!res.ok) throw new GitHubError(res.status, friendlyMessage(res.status, ""), `raw ${path}@${ref}`);
|
|
1774
1887
|
return res.text();
|
|
1775
1888
|
}
|
|
1889
|
+
/**
|
|
1890
|
+
* Text of a file at a ref through the contents API with the raw media type: the way to read a private file (the raw
|
|
1891
|
+
* host cannot take the token without a preflight). Counts against the API limit; conditional when cached.
|
|
1892
|
+
* Undefined when the file does not exist.
|
|
1893
|
+
*/
|
|
1894
|
+
async getRawText(path, ref) {
|
|
1895
|
+
try {
|
|
1896
|
+
return await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}?ref=${encodeURIComponent(ref)}`, void 0, true, { accept: ACCEPT_RAW, text: true });
|
|
1897
|
+
} catch (err) {
|
|
1898
|
+
if (err instanceof GitHubError && err.status === 404) return void 0;
|
|
1899
|
+
throw err;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Text of a blob by its sha through the git blobs API with the raw media type: how a signed-in reader loads a file
|
|
1904
|
+
* listed by a tree (the raw host refuses any request that carries a token). A blob sha names its content, so the
|
|
1905
|
+
* answer is cached for the client's life and never revalidated. Undefined on 404.
|
|
1906
|
+
*/
|
|
1907
|
+
async getBlobRaw(sha) {
|
|
1908
|
+
const cached = this.blobCache.get(sha);
|
|
1909
|
+
if (cached !== void 0) return cached;
|
|
1910
|
+
try {
|
|
1911
|
+
const text = await this.request("GET", `${this.repoPath}/git/blobs/${encodeURIComponent(sha)}`, void 0, true, { accept: ACCEPT_RAW, text: true, etag: false });
|
|
1912
|
+
this.blobCache.set(sha, text);
|
|
1913
|
+
return text;
|
|
1914
|
+
} catch (err) {
|
|
1915
|
+
if (err instanceof GitHubError && err.status === 404) return void 0;
|
|
1916
|
+
throw err;
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
// --- GitHub App installations (user-to-server tokens) ---------------------------------------
|
|
1920
|
+
/**
|
|
1921
|
+
* Installations of the signing-in GitHub App that the signed-in user can reach (GET /user/installations, every
|
|
1922
|
+
* page). Needs a user-to-server token of that same App: a personal or OAuth token is refused (403), and each App
|
|
1923
|
+
* sees only its own installations. GitHub lists only installations on accounts and repositories the user has
|
|
1924
|
+
* explicit permission on: a public repository where the user is not a collaborator is never listed, installed or
|
|
1925
|
+
* not. (GET /repos/{o}/{r}/installation would say, but it needs the App's private key, which Bridge never holds.)
|
|
1926
|
+
*/
|
|
1927
|
+
async listUserInstallations() {
|
|
1928
|
+
this.assertToken("Reading where Bridge is installed");
|
|
1929
|
+
const list = await this.paged("/user/installations", (p) => ({ items: p.installations ?? [], total: p.total_count }));
|
|
1930
|
+
return list.map((i) => ({
|
|
1931
|
+
id: i.id,
|
|
1932
|
+
account: i.account?.login ? { login: i.account.login, type: i.account.type ?? "User" } : null,
|
|
1933
|
+
repositorySelection: i.repository_selection ?? "selected",
|
|
1934
|
+
appSlug: i.app_slug
|
|
1935
|
+
}));
|
|
1936
|
+
}
|
|
1937
|
+
/** Repositories of one installation the signed-in user can reach (GET /user/installations/{id}/repositories, every page). */
|
|
1938
|
+
async listInstallationRepos(installationId) {
|
|
1939
|
+
this.assertToken("Reading where Bridge is installed");
|
|
1940
|
+
const list = await this.paged(`/user/installations/${encodeURIComponent(String(installationId))}/repositories`, (p) => ({
|
|
1941
|
+
items: p.repositories ?? [],
|
|
1942
|
+
total: p.total_count
|
|
1943
|
+
}));
|
|
1944
|
+
return list.map((r) => ({ owner: r.owner?.login ?? r.full_name.split("/")[0], name: r.name, fullName: r.full_name, private: Boolean(r.private) }));
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* The App installation that gives the signed-in user this repository (this client's by default): its id, or null
|
|
1948
|
+
* when the App is not installed there or the user cannot reach it. One call when the installation covers all of
|
|
1949
|
+
* the owner's repositories, two (paged) when it covers selected ones.
|
|
1950
|
+
*/
|
|
1951
|
+
async findInstallation(owner = this.owner, repo = this.name) {
|
|
1952
|
+
const found = installationFor(await this.listUserInstallations(), owner);
|
|
1953
|
+
if (found.reach === "no") return null;
|
|
1954
|
+
if (found.reach === "yes") return { id: found.id, selection: "all" };
|
|
1955
|
+
const repos = await this.listInstallationRepos(found.id);
|
|
1956
|
+
const hit = repos.some((r) => r.owner.toLowerCase() === owner.toLowerCase() && r.name.toLowerCase() === repo.toLowerCase());
|
|
1957
|
+
return hit ? { id: found.id, selection: "selected" } : null;
|
|
1958
|
+
}
|
|
1776
1959
|
/** The repository's default branch. */
|
|
1777
1960
|
async getDefaultBranch() {
|
|
1778
1961
|
return (await this.getRepo()).defaultBranch;
|
|
@@ -4246,7 +4429,7 @@ function summarizeCi(runs, statuses = []) {
|
|
|
4246
4429
|
const state = total === 0 ? "none" : failed.length ? "fail" : pending.length ? "pending" : "pass";
|
|
4247
4430
|
return { state, total, passed, failed, pending };
|
|
4248
4431
|
}
|
|
4249
|
-
function summarizeApprovals(reviews, owners, author) {
|
|
4432
|
+
function summarizeApprovals(reviews, owners, author, rules = {}) {
|
|
4250
4433
|
const latest = /* @__PURE__ */ new Map();
|
|
4251
4434
|
const sorted = [...reviews].sort((a2, b) => String(a2.submitted_at ?? "").localeCompare(String(b.submitted_at ?? "")));
|
|
4252
4435
|
for (const r of sorted) {
|
|
@@ -4264,7 +4447,8 @@ function summarizeApprovals(reviews, owners, author) {
|
|
|
4264
4447
|
let tech;
|
|
4265
4448
|
if (d.length || t.length) {
|
|
4266
4449
|
design = d.length ? a.some((x) => d.includes(x)) : a.length > 0;
|
|
4267
|
-
const
|
|
4450
|
+
const solo = rules.soloOwnerCanApproveBoth ? soloApprover(owners) : null;
|
|
4451
|
+
const soloBoth = (x) => a.length === 1 && d.includes(x) && x !== solo;
|
|
4268
4452
|
tech = t.length ? a.some((x) => t.includes(x) && !soloBoth(x)) : a.length > 1;
|
|
4269
4453
|
} else {
|
|
4270
4454
|
design = a.length >= 1;
|
|
@@ -4735,7 +4919,7 @@ function summarizeSuggestionApprovals(comments, gov, opts) {
|
|
|
4735
4919
|
latestOld.delete(c.author);
|
|
4736
4920
|
reviews.push({ user: { login: c.author }, state: r.event === "approve" ? "APPROVED" : "CHANGES_REQUESTED", submitted_at: at });
|
|
4737
4921
|
});
|
|
4738
|
-
const summary2 = summarizeApprovals(reviews, lists, opts.author ?? void 0);
|
|
4922
|
+
const summary2 = summarizeApprovals(reviews, lists, opts.author ?? void 0, { soloOwnerCanApproveBoth: gov.soloOwnerCanApproveBoth });
|
|
4739
4923
|
const stale = [...latestOld.keys()].filter((l) => !summary2.approvers.includes(l) && !summary2.changesRequested.includes(l));
|
|
4740
4924
|
return { ...summary2, stale, ignored: [...ignored].filter((l) => !summary2.approvers.includes(l)) };
|
|
4741
4925
|
}
|
|
@@ -6251,6 +6435,7 @@ function shareMessage(x) {
|
|
|
6251
6435
|
// ../core/src/try-in-product.ts
|
|
6252
6436
|
function tryInProductUrl(preview, pr, screen) {
|
|
6253
6437
|
const url = new URL(preview);
|
|
6438
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return "";
|
|
6254
6439
|
url.pathname = "/";
|
|
6255
6440
|
url.search = "";
|
|
6256
6441
|
url.searchParams.set("bridge", String(pr));
|
|
@@ -6829,7 +7014,7 @@ async function planSuggestionApply(input) {
|
|
|
6829
7014
|
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
7015
|
if (links.length) return out("refused", "Not applied: the suggestion writes through a symbolic link.", { problems: links });
|
|
6831
7016
|
}
|
|
6832
|
-
const governance = input.governance ?? { owners: config.governance.owners, rights: config.governance.rights ?? null };
|
|
7017
|
+
const governance = input.governance ?? { owners: config.governance.owners, rights: config.governance.rights ?? null, soloOwnerCanApproveBoth: config.governance.soloOwnerCanApproveBoth };
|
|
6833
7018
|
const approvals = summarizeSuggestionApprovals(input.comments, governance, { payloadSha: parsed.sha, author: input.author });
|
|
6834
7019
|
const review = suggestionReviewState(payload.tier.tier, approvals);
|
|
6835
7020
|
const missing = pendingApprovals(payload.tier.tier, approvals);
|
|
@@ -10200,7 +10385,7 @@ async function people(gh, config) {
|
|
|
10200
10385
|
const { owners, rights } = config.governance;
|
|
10201
10386
|
const teams = await new TeamDirectory(gh).resolve(owners).catch(() => void 0);
|
|
10202
10387
|
const lists = expandOwners(approverEntries(owners, rights), teams);
|
|
10203
|
-
return { lists, ship: shipPolicy(owners, rights, teams) };
|
|
10388
|
+
return { lists, ship: shipPolicy(owners, rights, teams), solo: config.governance.soloOwnerCanApproveBoth ? soloApprover(lists) : null };
|
|
10204
10389
|
}
|
|
10205
10390
|
async function viewerOf(ctx, gh) {
|
|
10206
10391
|
if (!ctx.canWriteRemote) return null;
|
|
@@ -10226,7 +10411,8 @@ async function branchFacts(ctx, gh, n, viewer) {
|
|
|
10226
10411
|
const approvals = summarizeApprovals(
|
|
10227
10412
|
reviews.map((r) => ({ user: r.user ? { login: r.user } : null, state: r.state, submitted_at: r.submittedAt ?? null })),
|
|
10228
10413
|
who.lists,
|
|
10229
|
-
pr.author
|
|
10414
|
+
pr.author,
|
|
10415
|
+
{ soloOwnerCanApproveBoth: Boolean(who.solo) }
|
|
10230
10416
|
);
|
|
10231
10417
|
const tier = effectiveTier(labels, pr.body, files);
|
|
10232
10418
|
const merged = pr.state === "merged";
|
|
@@ -10236,7 +10422,7 @@ async function branchFacts(ctx, gh, n, viewer) {
|
|
|
10236
10422
|
const status = reviewStatusKind({ ...pullLike, ci, approvals, tier, mergeable: pr.mergeable });
|
|
10237
10423
|
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
10424
|
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 };
|
|
10425
|
+
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
10426
|
}
|
|
10241
10427
|
var STATUS_WORDS = {
|
|
10242
10428
|
merged: "Merged",
|
|
@@ -10267,7 +10453,7 @@ async function branchStatus(ctx, a) {
|
|
|
10267
10453
|
lifecycle: f.lifecycle,
|
|
10268
10454
|
status: { kind: f.status, label: STATUS_WORDS[f.status] },
|
|
10269
10455
|
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 },
|
|
10456
|
+
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
10457
|
tests: { state: f.ci.state, failing: f.ci.failed.map((x) => x.name), running: f.ci.pending.map((x) => x.name) },
|
|
10272
10458
|
main: { state: f.fresh.state, behindBy: f.fresh.behindBy },
|
|
10273
10459
|
merge: {
|
|
@@ -10878,7 +11064,7 @@ async function suggestionGovernance(ctx, gh) {
|
|
|
10878
11064
|
const { owners, rights } = config.governance;
|
|
10879
11065
|
const hasTeams = [...owners.design, ...owners.tech].some((o) => o.includes("/"));
|
|
10880
11066
|
const teams = hasTeams ? await new TeamDirectory(gh).resolve(owners).catch(() => void 0) : void 0;
|
|
10881
|
-
return { config, gov: { owners, rights: rights ?? null, ...teams ? { teams } : {} } };
|
|
11067
|
+
return { config, gov: { owners, rights: rights ?? null, ...teams ? { teams } : {}, ...config.governance.soloOwnerCanApproveBoth ? { soloOwnerCanApproveBoth: true } : {} } };
|
|
10882
11068
|
}
|
|
10883
11069
|
var OPEN_STATUSES = ["needs-review", "changes-requested", "approved", "applying", "outdated"];
|
|
10884
11070
|
async function listSuggestionsOp(ctx, a = {}) {
|
|
@@ -10925,6 +11111,7 @@ async function getSuggestionOp(ctx, a) {
|
|
|
10925
11111
|
const p = d.parsed.payload;
|
|
10926
11112
|
const approvals = d.approvals;
|
|
10927
11113
|
const missing = pendingApprovals(p.tier.tier, approvals);
|
|
11114
|
+
const solo = soloOwnerOf(gov, gov.teams);
|
|
10928
11115
|
return {
|
|
10929
11116
|
...base,
|
|
10930
11117
|
kind: p.kind,
|
|
@@ -10952,7 +11139,8 @@ async function getSuggestionOp(ctx, a) {
|
|
|
10952
11139
|
missing: missing.map(roleText),
|
|
10953
11140
|
changesRequestedBy: approvals.changesRequested,
|
|
10954
11141
|
...approvals.stale.length ? { staleReviews: approvals.stale } : {},
|
|
10955
|
-
...approvals.ignored.length ? { notCounted: approvals.ignored } : {}
|
|
11142
|
+
...approvals.ignored.length ? { notCounted: approvals.ignored } : {},
|
|
11143
|
+
...solo && p.tier.tier > 1 ? { soloOwner: soloOwnerSentence(solo) } : {}
|
|
10956
11144
|
},
|
|
10957
11145
|
applied: d.applied,
|
|
10958
11146
|
...p.componentSpec ? { brief: p.componentSpec.brief } : {},
|
|
@@ -11701,9 +11889,11 @@ async function reviewRoles(gh, config, approvers) {
|
|
|
11701
11889
|
});
|
|
11702
11890
|
const ship = shipPolicy(owners, rights, teams);
|
|
11703
11891
|
const unreadable = unreadableTeams(teams);
|
|
11892
|
+
const solo = soloOwnerOf(config.governance, teams);
|
|
11704
11893
|
return {
|
|
11705
11894
|
design: role("design"),
|
|
11706
11895
|
tech: role("tech"),
|
|
11896
|
+
...solo ? { soloOwner: soloOwnerSentence(solo) } : {},
|
|
11707
11897
|
ship: ship ? whoCanShipSentence(ship) : "Anyone with write access can ship.",
|
|
11708
11898
|
...unreadable.length ? { unreadableTeams: unreadable.map((t) => t.message) } : {}
|
|
11709
11899
|
};
|
|
@@ -11745,7 +11935,7 @@ async function visualImpact(ctx, a, run2) {
|
|
|
11745
11935
|
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
11936
|
if (!ctx.isGit) throw new Error(`${ctx.root} is not a git repository.`);
|
|
11747
11937
|
const out = a.out ? resolve3(ctx.root, a.out) : join5(ctx.root, "visual-impact");
|
|
11748
|
-
const runImpact = run2 ?? (await import("./src-
|
|
11938
|
+
const runImpact = run2 ?? (await import("./src-UNWS2OVM.js")).runImpact;
|
|
11749
11939
|
const r = await runImpact({
|
|
11750
11940
|
root: ctx.root,
|
|
11751
11941
|
base: a.base ?? "main",
|
|
@@ -12525,6 +12715,7 @@ async function getSettings(ctx) {
|
|
|
12525
12715
|
const protection = gh ? await loadProtection(gh, baseBranchOf(config)).catch(() => null) : null;
|
|
12526
12716
|
const products = productsOf(config);
|
|
12527
12717
|
const unreadable = teams ? unreadableTeams(teams) : [];
|
|
12718
|
+
const solo = soloOwnerOf(config.governance, teams);
|
|
12528
12719
|
return {
|
|
12529
12720
|
repo: config.repo ? `${config.repo.owner}/${config.repo.name}` : gh?.fullName ?? null,
|
|
12530
12721
|
main: baseBranchOf(config),
|
|
@@ -12532,6 +12723,7 @@ async function getSettings(ctx) {
|
|
|
12532
12723
|
designApproval: entries.design.map((e) => entryLabel(e, teams)),
|
|
12533
12724
|
techApproval: entries.tech.map((e) => entryLabel(e, teams)),
|
|
12534
12725
|
ship: ship ? whoCanShipSentence(ship) : "Anyone with write access can merge.",
|
|
12726
|
+
...solo ? { soloOwner: soloOwnerSentence(solo) } : {},
|
|
12535
12727
|
...unreadable.length ? { teamsNotRead: unreadable.map((t) => t.message) } : {}
|
|
12536
12728
|
},
|
|
12537
12729
|
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 +13003,7 @@ async function pushFigmaComponentOp(ctx, a) {
|
|
|
12811
13003
|
}
|
|
12812
13004
|
|
|
12813
13005
|
// src/server.ts
|
|
12814
|
-
var VERSION = true ? "0.
|
|
13006
|
+
var VERSION = true ? "0.8.2" : "dev";
|
|
12815
13007
|
var ok = (data) => ({ content: [{ type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) }] });
|
|
12816
13008
|
var fail2 = (e) => ({ content: [{ type: "text", text: `Error: ${e.message ?? String(e)}` }], isError: true });
|
|
12817
13009
|
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-QCPYJXK4.js";
|
|
32
32
|
import {
|
|
33
33
|
CONFIG_FILE,
|
|
34
34
|
loadConfig,
|
|
35
35
|
toConfigJson
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-DPK46GA2.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.2",
|
|
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.15.0",
|
|
58
|
+
"@bridge-ds/visual": "^0.3.9",
|
|
59
59
|
"@modelcontextprotocol/sdk": "1.31.0",
|
|
60
60
|
"typescript": "5.9.3",
|
|
61
61
|
"zod": "3.25.76"
|