@bridge-ds/core 0.8.0 → 0.9.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.
@@ -0,0 +1,266 @@
1
+ import { a1 as slugify } from "./pr-composer-D2SZm7cg.js";
2
+ const PIECES_MARK = "bridge-pieces";
3
+ const NEEDED_MARK = "bridge-needed-by";
4
+ const SECTION = "### Missing from the library";
5
+ const NEEDED_BY_LABEL = "needed-by-product";
6
+ const hiddenJson = (mark, data) => `<!-- ${mark} ${JSON.stringify(data).replace(/-->/g, "--\\u003e")} -->`;
7
+ const readHidden = (mark, body) => {
8
+ if (!body) return void 0;
9
+ const m = new RegExp(`<!-- ${mark} (\\{[\\s\\S]*?\\}) -->`).exec(body);
10
+ if (!m) return void 0;
11
+ try {
12
+ return JSON.parse(m[1]);
13
+ } catch {
14
+ return void 0;
15
+ }
16
+ };
17
+ const KIND_WORD = { "token-change": "token change", exploration: "library exploration", request: "component request" };
18
+ function parsePiecesRecord(body) {
19
+ const r = readHidden(PIECES_MARK, body);
20
+ if (!r || !Array.isArray(r.pieces) || !Array.isArray(r.links) || typeof r.checkedAt !== "string") return void 0;
21
+ return r;
22
+ }
23
+ function piecesSection(r) {
24
+ const link = (p) => r.links.find((l) => l.piece === p.id);
25
+ const lines = r.pieces.length ? r.pieces.map((p) => {
26
+ const l = link(p);
27
+ return `- **${p.title}**: ${p.summary}${l ? ` ${cap(KIND_WORD[l.kind])}: #${l.number}.` : " No library branch yet."}`;
28
+ }) : ["- Nothing: every value and component comes from the library."];
29
+ return [SECTION, "", `Checked ${r.checkedAt.slice(0, 10)}${r.headSha ? ` on ${r.headSha.slice(0, 7)}` : ""} by Bridge.`, "", ...lines, "", hiddenJson(PIECES_MARK, r)].join("\n");
30
+ }
31
+ function withPiecesRecord(body, r) {
32
+ const b = (body ?? "").trimEnd();
33
+ const start = b.indexOf(SECTION);
34
+ const markAt = b.indexOf(`<!-- ${PIECES_MARK} `);
35
+ if (start >= 0 && markAt > start) {
36
+ const end = b.indexOf("-->", markAt);
37
+ return `${b.slice(0, start)}${piecesSection(r)}${end >= 0 ? b.slice(end + 3) : ""}`.trimEnd();
38
+ }
39
+ return `${b}${b ? "\n\n" : ""}${piecesSection(r)}`;
40
+ }
41
+ function mergePiecesRecord(prev, next) {
42
+ const links = [...prev?.links ?? []];
43
+ for (const l of next.links ?? []) {
44
+ const at = links.findIndex((x) => x.piece === l.piece);
45
+ if (at >= 0) links[at] = l;
46
+ else links.push(l);
47
+ }
48
+ return { ...next, links };
49
+ }
50
+ function withLink(r, l) {
51
+ return { ...r, links: [...r.links.filter((x) => x.piece !== l.piece), l] };
52
+ }
53
+ function neededBySection(n) {
54
+ const who = n.author ? ` for @${n.author}` : "";
55
+ const near = n.nearest ? ` The closest the library has today is ${n.nearest}${n.nearestToken ? ` (\`${n.nearestToken}\`)` : ""}.` : "";
56
+ return [`**Needed by:** [${n.title}](${n.url}) (#${n.pr}) in ${n.productName}${who}.${near}`, "", hiddenJson(NEEDED_MARK, n)].join("\n");
57
+ }
58
+ function parseNeededBy(body) {
59
+ const n = readHidden(NEEDED_MARK, body);
60
+ return n && typeof n.pr === "number" && typeof n.title === "string" && typeof n.piece === "string" ? n : void 0;
61
+ }
62
+ function withNeededBy(body, n) {
63
+ let b = (body ?? "").trim();
64
+ const at = b.indexOf("**Needed by:**");
65
+ const mark = b.indexOf(`<!-- ${NEEDED_MARK} `);
66
+ if (at >= 0 && mark > at) {
67
+ const end = b.indexOf("-->", mark);
68
+ b = `${b.slice(0, at)}${b.slice(end + 3)}`.trim();
69
+ }
70
+ return `${neededBySection(n)}${b ? `
71
+
72
+ ${b}` : ""}`;
73
+ }
74
+ const cap = (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
75
+ const forLine = (b) => `${b.productName} needs it for "${b.title.replace(/"/g, "'")}" (#${b.pr})`;
76
+ function libraryBranchPlan(piece, branch, opts = {}) {
77
+ const neededBy = {
78
+ pr: branch.pr,
79
+ title: branch.title,
80
+ branch: branch.branch,
81
+ url: branch.url,
82
+ product: branch.productKey,
83
+ productName: branch.productName,
84
+ ...branch.author ? { author: branch.author } : {},
85
+ piece: piece.id,
86
+ pieceTitle: piece.title,
87
+ ...piece.nearest ? { nearest: piece.nearest.label, nearestToken: piece.nearest.token } : {}
88
+ };
89
+ const near = piece.nearest ? ` The closest the library has today is ${piece.nearest.label} (${piece.nearest.token}, ${piece.nearest.value}).` : "";
90
+ if (piece.kind === "token" && piece.token) {
91
+ const t = piece.token;
92
+ const files = t.files.length ? t.files : [void 0];
93
+ const edits = files.map((file) => ({
94
+ op: "add",
95
+ path: t.path,
96
+ type: t.type,
97
+ value: t.value,
98
+ description: `${cap(piece.name)}: ${forLine(branch)}.${files.length > 1 ? " Same value in every theme: review it in each." : ""}`,
99
+ ...file ? { file } : {}
100
+ }));
101
+ return { kind: "token-change", title: `Add ${t.path}`, summary: `${forLine(branch)}: ${piece.summary}${near}`, edits, neededBy };
102
+ }
103
+ if (piece.kind === "variant" && piece.component) {
104
+ const values = piece.values.map((v) => `${v.property ?? v.category} ${v.value}`).join(", ");
105
+ const exploration = {
106
+ name: piece.title,
107
+ purpose: `${forLine(branch)}: ${/^[aeiou]/i.test(piece.name) ? "an" : "a"} ${piece.name} ${piece.aspect ?? "style"} on ${piece.component} (${values}).${near}`,
108
+ useCase: "terminal",
109
+ experimental: [piece.title],
110
+ source: opts.source === "figma" ? "figma" : opts.source ?? "mcp"
111
+ };
112
+ const brief = [
113
+ `Add the ${piece.name} ${piece.aspect ?? "style"} to ${piece.component} in the library, on this exploration branch.`,
114
+ `The product branch writes it by hand: ${values}. Express it with tokens (a component or semantic token per value), add it to the component's props, docs and examples, and keep every existing ${piece.component} unchanged.`,
115
+ piece.nearest ? `Say in the pull request why ${piece.nearest.label} is not enough, or recommend it instead.` : ""
116
+ ].filter(Boolean).join(" ");
117
+ return { kind: "exploration", title: piece.title, exploration, neededBy, brief };
118
+ }
119
+ const name = piece.component ?? cap(piece.name);
120
+ const request = {
121
+ component: name,
122
+ kind: "new-component",
123
+ title: `Add ${name}`,
124
+ summary: `${forLine(branch)}: ${piece.summary}${near}`,
125
+ changes: piece.values.slice(0, 6).map((v) => ({ field: v.property ?? v.category, after: v.value })),
126
+ source: opts.source ?? "mcp",
127
+ ...opts.requestedBy ? { requestedBy: opts.requestedBy } : {}
128
+ };
129
+ return { kind: "request", title: `New component: ${name}`, request, neededBy };
130
+ }
131
+ const STATUS_LABEL = {
132
+ draft: "Draft",
133
+ "in-review": "In review",
134
+ changes: "Changes requested",
135
+ approved: "Approved",
136
+ shipped: "Shipped",
137
+ closed: "Closed",
138
+ exploring: "Being built",
139
+ requested: "Requested",
140
+ "in-progress": "In progress",
141
+ done: "Done",
142
+ declined: "Declined"
143
+ };
144
+ const LINK_STATUS_TONE = {
145
+ draft: "neutral",
146
+ "in-review": "info",
147
+ changes: "warning",
148
+ approved: "success",
149
+ shipped: "success",
150
+ closed: "neutral",
151
+ exploring: "neutral",
152
+ requested: "neutral",
153
+ "in-progress": "info",
154
+ done: "success",
155
+ declined: "neutral"
156
+ };
157
+ const names = (people) => people.length <= 2 ? people.map((p) => `@${p}`).join(" and ") : `@${people[0]} and ${people.length - 1} more`;
158
+ function linkStatus(x) {
159
+ const make = (key, people = [], verb = "reviews") => {
160
+ const label = STATUS_LABEL[key];
161
+ const tail = people.length ? ` · ${names(people)} ${people.length === 1 ? verb : verb.replace(/s$/, "")}` : "";
162
+ return { key, label, people, line: `${label}${tail}` };
163
+ };
164
+ if (x.kind === "request") {
165
+ if (x.state === "closed") return make(x.stateReason === "not_planned" ? "declined" : "done");
166
+ return x.implemented ? make("in-progress", [...x.assignees ?? []], "builds") : make("requested", [...x.assignees ?? []], "builds");
167
+ }
168
+ if (x.state === "merged") return make("shipped");
169
+ if (x.state !== "open") return make("closed");
170
+ if ((x.labels ?? []).includes("exploration")) return make("exploring");
171
+ if (x.draft) return make("draft");
172
+ const latest = /* @__PURE__ */ new Map();
173
+ for (const r of x.reviews ?? []) if (r.user && r.state !== "COMMENTED") latest.set(r.user, r.state);
174
+ const states = [...latest.values()];
175
+ if (states.includes("CHANGES_REQUESTED")) return make("changes", [...latest].filter(([, s]) => s === "CHANGES_REQUESTED").map(([u]) => u), "asks");
176
+ const waiting = [...x.requestedReviewers ?? []];
177
+ if (states.includes("APPROVED") && !waiting.length) return make("approved");
178
+ return make("in-review", waiting);
179
+ }
180
+ function libraryPreviewState(x) {
181
+ if (x.kind === "request") {
182
+ if (x.lifecycle === "closed") return { key: "declined", sentence: `The library declined this request.${x.nearest ? ` Ask Claude to use ${x.nearest} instead.` : ""}`, action: null };
183
+ if (x.lifecycle === "merged") return x.behindMain === 0 ? { key: "switched", sentence: "Your branch uses the published version.", action: null } : { key: "published", sentence: "The library built it. Switch your branch to the published version, then ask Claude to use it.", action: { kind: "switch", label: "Switch to the published version" } };
184
+ return { key: "nothing-yet", sentence: "Nothing to preview yet: the library builds it first. Your branch keeps its own version until then.", action: null };
185
+ }
186
+ if (x.lifecycle === "merged") {
187
+ if (x.behindMain === 0) return { key: "switched", sentence: "Your branch uses the published version.", action: null };
188
+ return { key: "published", sentence: "The library published it. Switch your branch to the published version.", action: { kind: "switch", label: "Switch to the published version" } };
189
+ }
190
+ if (x.lifecycle === "closed") return { key: "declined", sentence: `The library closed it without shipping.${x.nearest ? ` Ask Claude to use ${x.nearest} instead.` : ""}`, action: null };
191
+ if (!x.hasChanges) return { key: "nothing-yet", sentence: "The library branch has no change yet. Claude adds it there, then your branch can use its preview.", action: null };
192
+ if (x.missing === 0) return { key: "preview", sentence: "Your branch uses a preview of the library branch. When the library publishes it, switch to the real version here in one click.", action: null };
193
+ if (x.missing !== null && x.total !== null && x.missing < x.total) return { key: "outdated", sentence: "The library branch changed since your branch took its preview.", action: { kind: "refresh-preview", label: "Get the latest preview" } };
194
+ return { key: "not-used", sentence: "Your branch does not use the library branch yet.", action: { kind: "use-preview", label: "Use its preview in my branch" } };
195
+ }
196
+ const nPieces = (n) => `${n} ${n === 1 ? "piece" : "pieces"}`;
197
+ function productBranchSentence(r, opts = {}) {
198
+ if (!r) return { lead: "Not checked against the library yet.", strong: "", rest: " Ask Claude what is missing from the library." };
199
+ const n = r.pieces.length;
200
+ if (!n) return { lead: "Built with the library.", strong: " Nothing is missing", rest: ": every value and component comes from it." };
201
+ const linked = r.pieces.filter((p) => r.links.some((l) => l.piece === p.id));
202
+ const openers = [...new Set(linked.map((p) => r.links.find((l) => l.piece === p.id)).map((l) => l.agent ? opts.agentName ?? "Claude AI" : l.openedBy && l.openedBy === opts.viewer ? "you" : l.openedBy ? `@${l.openedBy}` : "someone"))];
203
+ const by = openers.length === 1 ? openers[0] : "your team";
204
+ const was = n === 1 ? "was" : "were";
205
+ if (linked.length === n) {
206
+ const what = n === 1 ? "a library branch for it" : "library branches for them";
207
+ return { lead: "Built with the library. ", strong: `${nPieces(n)} ${was} missing`, rest: `, and ${by} opened ${what}.` };
208
+ }
209
+ if (!linked.length) return { lead: "Built with the library. ", strong: `${nPieces(n)} ${n === 1 ? "is" : "are"} missing`, rest: n === 1 ? ": open a library branch for it." : ": open a library branch for each." };
210
+ return { lead: "Built with the library. ", strong: `${nPieces(n)} ${was} missing`, rest: `: ${linked.length} ${linked.length === 1 ? "has" : "have"} a library branch, ${n - linked.length} still ${n - linked.length === 1 ? "needs" : "need"} one.` };
211
+ }
212
+ function whyItExists(n, opts = {}) {
213
+ const who = n.author ? opts.names?.[n.author] ?? `@${n.author}` : "A product team";
214
+ return { who, title: n.title, product: n.productName };
215
+ }
216
+ function nearestQuestion(n, opts = {}) {
217
+ if (!n.nearest) return null;
218
+ const who = n.author ? opts.names?.[n.author] ?? `@${n.author}` : "the product team";
219
+ return `It looks a lot like "${n.nearest}". Is a new one worth it, or should ${who} use ${n.nearest}?`;
220
+ }
221
+ function suggestNearestComment(n) {
222
+ if (!n.nearest) return null;
223
+ return `Let's not add it to the library: ${n.nearest}${n.nearestToken ? ` (\`${n.nearestToken}\`)` : ""} covers it. Use it on ${n.title} (#${n.pr}) instead, then close this branch.`;
224
+ }
225
+ function pieceId(parts) {
226
+ return slugify(parts.filter(Boolean).join(" "), 48);
227
+ }
228
+ const TONE_WORDS = ["neutral", "success", "warning", "danger", "info", "error", "critical", "accent", "brand", "primary", "secondary"];
229
+ function tokenLabel(path) {
230
+ const parts = path.split(".");
231
+ const tone = parts.find((p) => TONE_WORDS.includes(p));
232
+ if (tone) return tone;
233
+ const meaningful = parts.filter((p) => !/^(color|bg|fg|text|border|default|base|value|\d+)$/.test(p));
234
+ return meaningful[meaningful.length - 1] ?? parts[parts.length - 1] ?? path;
235
+ }
236
+ function proposedTokenPath(nearest, name, category) {
237
+ const slug = slugify(name, 32);
238
+ if (!nearest) return `${category === "color" ? "color" : category}.${slug}`;
239
+ const parts = nearest.split(".");
240
+ const toneAt = parts.findIndex((p) => TONE_WORDS.includes(p));
241
+ if (toneAt > 0) return [...parts.slice(0, toneAt), slug, ...parts.slice(toneAt + 1)].join(".");
242
+ return [...parts.slice(0, -1), slug].join(".");
243
+ }
244
+ export {
245
+ LINK_STATUS_TONE as L,
246
+ NEEDED_BY_LABEL as N,
247
+ proposedTokenPath as a,
248
+ libraryPreviewState as b,
249
+ parsePiecesRecord as c,
250
+ libraryBranchPlan as d,
251
+ withLink as e,
252
+ withPiecesRecord as f,
253
+ productBranchSentence as g,
254
+ neededBySection as h,
255
+ parseNeededBy as i,
256
+ piecesSection as j,
257
+ whyItExists as k,
258
+ linkStatus as l,
259
+ mergePiecesRecord as m,
260
+ nearestQuestion as n,
261
+ pieceId as p,
262
+ suggestNearestComment as s,
263
+ tokenLabel as t,
264
+ withNeededBy as w
265
+ };
266
+ //# sourceMappingURL=prod-branch-D5Tl87Wf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prod-branch-D5Tl87Wf.js","sources":["../src/prod-branch.ts"],"sourcesContent":["// Product branches and the library: a designer explores a feature with Claude on a product branch (an exploration\n// labeled `product: <key>`) built on the real components. What the branch invents outside the library (a colour\n// written by hand, a component restyled into a new tone, a component the library lacks) are \"missing pieces\". Each\n// one becomes a library branch linked to the product branch: a token change, a library exploration for a variant, or\n// a component request. Once the library ships it, the product branch switches to the real version.\n//\n// Pure and browser safe: the pieces record kept in the product branch body, the \"needed by\" mark on the library side,\n// the status and preview sentences Studio, the CLI and the MCP server all show, and what each piece opens. Finding the\n// pieces needs the adoption audit (audit/missing-pieces.ts, Node or a bundler with TypeScript); the GitHub writes are\n// in prod-branch-github.ts.\n\nimport type { Exploration } from './explorations';\nimport { slugify } from './pr-composer';\nimport type { ComponentChangeRequest } from './requests';\nimport type { TokenEdit } from './token-edits';\n\n/** What the library lacks: a token value, a variant of an existing component, or a component. */\nexport type PieceKind = 'token' | 'variant' | 'component';\n\n/** What a variant changes on its component, in designer words. */\nexport type PieceAspect = 'tone' | 'shape' | 'size' | 'style';\n\nexport interface PieceValue {\n /** Audit category: color, radius, spacing... */\n category: string;\n /** CSS property, when known: background-color, border-radius. */\n property?: string | undefined;\n /** As written: \"#c2410c\", \"10px\". */\n value: string;\n}\n\nexport interface PieceNearest {\n /** Token path of the closest library value: color.feedback.danger.bg. */\n token: string;\n cssVar: string;\n /** Its value, as the audit resolved it. */\n value: string;\n /** How close: high means it looks a lot like it. */\n level: 'exact' | 'high' | 'medium' | 'low';\n /** The word people use for it: \"danger\" for color.feedback.danger.bg, \"control\" for radius.control. */\n label: string;\n}\n\nexport interface MissingPiece {\n /** Stable id inside one product branch: \"badge-overdue-tone\", \"color-c2410c\". */\n id: string;\n kind: PieceKind;\n /** Designer title: \"Badge: overdue tone\", \"New color #C2410C\", \"New component: Timeline\". */\n title: string;\n /** One plain sentence: what the branch did and what the library has today. */\n summary: string;\n /** The name the branch gives it (\"overdue\"), from a class name, the element's text or the component name. */\n name: string;\n /** The library component it extends (variant) or the proposed component name (component). */\n component?: string | undefined;\n aspect?: PieceAspect | undefined;\n values: PieceValue[];\n nearest?: PieceNearest | undefined;\n /** Where the branch writes it, first places only. */\n where: { file: string; line: number }[];\n /** Token pieces: the token the library branch would add (one value, every file the closest token lives in). */\n token?: { path: string; type: string; value: string; files: string[] } | undefined;\n}\n\n/** Where the library side of a piece lives. */\nexport type LibraryLinkKind = 'token-change' | 'exploration' | 'request';\n\nexport interface LibraryLink {\n /** The piece id. */\n piece: string;\n kind: LibraryLinkKind;\n /** Pull request number (token change, exploration) or issue number (request). */\n number: number;\n url: string;\n title: string;\n /** Head branch of a pull request. */\n branch?: string | undefined;\n /** GitHub login of who opened it, and the AI agent behind it (\"claude\") when one did. */\n openedBy?: string | undefined;\n agent?: string | undefined;\n /** ISO date. */\n at: string;\n}\n\n/** What the product branch body keeps: the last check and the library branches opened for its pieces. */\nexport interface PiecesRecord {\n /** ISO date of the last check. */\n checkedAt: string;\n /** Head commit the check read. */\n headSha?: string | undefined;\n pieces: MissingPiece[];\n links: LibraryLink[];\n /** Values and elements the library already has (a token or a component to use instead): fixes, not pieces. */\n fixes?: number | undefined;\n}\n\nconst PIECES_MARK = 'bridge-pieces';\nconst NEEDED_MARK = 'bridge-needed-by';\nconst SECTION = '### Missing from the library';\n\n/** Every library branch opened for a product branch carries this label, so lists can find them without the body. */\nexport const NEEDED_BY_LABEL = 'needed-by-product';\n\nconst hiddenJson = (mark: string, data: unknown) => `<!-- ${mark} ${JSON.stringify(data).replace(/-->/g, '--\\\\u003e')} -->`;\nconst readHidden = <T>(mark: string, body: string | null | undefined): T | undefined => {\n if (!body) return undefined;\n const m = new RegExp(`<!-- ${mark} (\\\\{[\\\\s\\\\S]*?\\\\}) -->`).exec(body);\n if (!m) return undefined;\n try {\n return JSON.parse(m[1]!) as T;\n } catch {\n return undefined;\n }\n};\n\nconst KIND_WORD: Record<LibraryLinkKind, string> = { 'token-change': 'token change', exploration: 'library exploration', request: 'component request' };\n\n/** The record in a product branch body, or undefined when the branch was never checked. */\nexport function parsePiecesRecord(body: string | null | undefined): PiecesRecord | undefined {\n const r = readHidden<PiecesRecord>(PIECES_MARK, body);\n if (!r || !Array.isArray(r.pieces) || !Array.isArray(r.links) || typeof r.checkedAt !== 'string') return undefined;\n return r;\n}\n\n/** The section people read on GitHub, then the hidden record tools read. */\nexport function piecesSection(r: PiecesRecord): string {\n const link = (p: MissingPiece) => r.links.find((l) => l.piece === p.id);\n const lines = r.pieces.length\n ? r.pieces.map((p) => {\n const l = link(p);\n return `- **${p.title}**: ${p.summary}${l ? ` ${cap(KIND_WORD[l.kind])}: #${l.number}.` : ' No library branch yet.'}`;\n })\n : ['- Nothing: every value and component comes from the library.'];\n return [SECTION, '', `Checked ${r.checkedAt.slice(0, 10)}${r.headSha ? ` on ${r.headSha.slice(0, 7)}` : ''} by Bridge.`, '', ...lines, '', hiddenJson(PIECES_MARK, r)].join('\\n');\n}\n\n/** The body with the pieces section replaced, or added at the end. */\nexport function withPiecesRecord(body: string | null | undefined, r: PiecesRecord): string {\n const b = (body ?? '').trimEnd();\n const start = b.indexOf(SECTION);\n const markAt = b.indexOf(`<!-- ${PIECES_MARK} `);\n if (start >= 0 && markAt > start) {\n const end = b.indexOf('-->', markAt);\n return `${b.slice(0, start)}${piecesSection(r)}${end >= 0 ? b.slice(end + 3) : ''}`.trimEnd();\n }\n return `${b}${b ? '\\n\\n' : ''}${piecesSection(r)}`;\n}\n\n/**\n * A new check merged with the previous record: links stay with their piece (same id), and links of pieces the branch\n * no longer has stay too, so a library branch is never forgotten.\n */\nexport function mergePiecesRecord(prev: PiecesRecord | undefined, next: Omit<PiecesRecord, 'links'> & { links?: LibraryLink[] | undefined }): PiecesRecord {\n const links = [...(prev?.links ?? [])];\n for (const l of next.links ?? []) {\n const at = links.findIndex((x) => x.piece === l.piece);\n if (at >= 0) links[at] = l;\n else links.push(l);\n }\n return { ...next, links };\n}\n\n/** The record with one more link (replacing the piece's earlier one). */\nexport function withLink(r: PiecesRecord, l: LibraryLink): PiecesRecord {\n return { ...r, links: [...r.links.filter((x) => x.piece !== l.piece), l] };\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// The library side: \"needed by\"\n\nexport interface NeededBy {\n /** The product branch (pull request number), its title and branch. */\n pr: number;\n title: string;\n branch: string;\n url: string;\n /** Product key and name: \"admin\", \"Admin demo\". */\n product: string;\n productName: string;\n /** Who started the product branch (GitHub login). */\n author?: string | undefined;\n /** The piece it answers. */\n piece: string;\n pieceTitle: string;\n /** The closest thing the library has today, so a reviewer can suggest it instead: \"danger\". */\n nearest?: string | undefined;\n nearestToken?: string | undefined;\n}\n\n/** \"Needed by\" for a library branch body: one line people read, one hidden block tools read. */\nexport function neededBySection(n: NeededBy): string {\n const who = n.author ? ` for @${n.author}` : '';\n const near = n.nearest ? ` The closest the library has today is ${n.nearest}${n.nearestToken ? ` (\\`${n.nearestToken}\\`)` : ''}.` : '';\n return [`**Needed by:** [${n.title}](${n.url}) (#${n.pr}) in ${n.productName}${who}.${near}`, '', hiddenJson(NEEDED_MARK, n)].join('\\n');\n}\n\nexport function parseNeededBy(body: string | null | undefined): NeededBy | undefined {\n const n = readHidden<NeededBy>(NEEDED_MARK, body);\n return n && typeof n.pr === 'number' && typeof n.title === 'string' && typeof n.piece === 'string' ? n : undefined;\n}\n\n/** The body with the \"Needed by\" section first (replacing an earlier one). */\nexport function withNeededBy(body: string | null | undefined, n: NeededBy): string {\n let b = (body ?? '').trim();\n const at = b.indexOf('**Needed by:**');\n const mark = b.indexOf(`<!-- ${NEEDED_MARK} `);\n if (at >= 0 && mark > at) {\n const end = b.indexOf('-->', mark);\n b = `${b.slice(0, at)}${b.slice(end + 3)}`.trim();\n }\n return `${neededBySection(n)}${b ? `\\n\\n${b}` : ''}`;\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// What each piece opens in the library\n\nexport interface ProductBranchRef {\n pr: number;\n title: string;\n branch: string;\n url: string;\n productKey: string;\n productName: string;\n author?: string | undefined;\n}\n\nexport type LibraryBranchPlan =\n | { kind: 'token-change'; title: string; summary: string; edits: TokenEdit[]; neededBy: NeededBy }\n | { kind: 'exploration'; title: string; exploration: Exploration; neededBy: NeededBy; brief: string }\n | { kind: 'request'; title: string; request: ComponentChangeRequest; neededBy: NeededBy };\n\nconst cap = (s: string) => (s ? s.charAt(0).toUpperCase() + s.slice(1) : s);\n\n/** \"Admin demo needs it for \"Billing screen with overdue invoices\"\". */\nconst forLine = (b: ProductBranchRef) => `${b.productName} needs it for \"${b.title.replace(/\"/g, \"'\")}\" (#${b.pr})`;\n\n/**\n * What a piece opens in the library, honestly labelled by what Bridge can write:\n * - a token piece: a token change (the new token, a draft for review, the usual gate);\n * - a variant: a library exploration (a branch and a draft for the component work, with a brief for Claude);\n * - a new component: a component request (an issue): it needs design work before code.\n */\nexport function libraryBranchPlan(piece: MissingPiece, branch: ProductBranchRef, opts: { source?: ComponentChangeRequest['source']; requestedBy?: string | undefined } = {}): LibraryBranchPlan {\n const neededBy: NeededBy = {\n pr: branch.pr,\n title: branch.title,\n branch: branch.branch,\n url: branch.url,\n product: branch.productKey,\n productName: branch.productName,\n ...(branch.author ? { author: branch.author } : {}),\n piece: piece.id,\n pieceTitle: piece.title,\n ...(piece.nearest ? { nearest: piece.nearest.label, nearestToken: piece.nearest.token } : {}),\n };\n const near = piece.nearest ? ` The closest the library has today is ${piece.nearest.label} (${piece.nearest.token}, ${piece.nearest.value}).` : '';\n if (piece.kind === 'token' && piece.token) {\n const t = piece.token;\n const files = t.files.length ? t.files : [undefined];\n const edits: TokenEdit[] = files.map((file) => ({\n op: 'add',\n path: t.path,\n type: t.type,\n value: t.value,\n description: `${cap(piece.name)}: ${forLine(branch)}.${files.length > 1 ? ' Same value in every theme: review it in each.' : ''}`,\n ...(file ? { file } : {}),\n }));\n return { kind: 'token-change', title: `Add ${t.path}`, summary: `${forLine(branch)}: ${piece.summary}${near}`, edits, neededBy };\n }\n if (piece.kind === 'variant' && piece.component) {\n const values = piece.values.map((v) => `${v.property ?? v.category} ${v.value}`).join(', ');\n const exploration: Exploration = {\n name: piece.title,\n purpose: `${forLine(branch)}: ${/^[aeiou]/i.test(piece.name) ? 'an' : 'a'} ${piece.name} ${piece.aspect ?? 'style'} on ${piece.component} (${values}).${near}`,\n useCase: 'terminal',\n experimental: [piece.title],\n source: opts.source === 'figma' ? 'figma' : (opts.source ?? 'mcp'),\n };\n const brief = [\n `Add the ${piece.name} ${piece.aspect ?? 'style'} to ${piece.component} in the library, on this exploration branch.`,\n `The product branch writes it by hand: ${values}. Express it with tokens (a component or semantic token per value), add it to the component's props, docs and examples, and keep every existing ${piece.component} unchanged.`,\n piece.nearest ? `Say in the pull request why ${piece.nearest.label} is not enough, or recommend it instead.` : '',\n ]\n .filter(Boolean)\n .join(' ');\n return { kind: 'exploration', title: piece.title, exploration, neededBy, brief };\n }\n const name = piece.component ?? cap(piece.name);\n const request: ComponentChangeRequest = {\n component: name,\n kind: 'new-component',\n title: `Add ${name}`,\n summary: `${forLine(branch)}: ${piece.summary}${near}`,\n changes: piece.values.slice(0, 6).map((v) => ({ field: v.property ?? v.category, after: v.value })),\n source: opts.source ?? 'mcp',\n ...(opts.requestedBy ? { requestedBy: opts.requestedBy } : {}),\n };\n return { kind: 'request', title: `New component: ${name}`, request, neededBy };\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// Where a linked library branch stands\n\nexport type LinkStatusKey = 'draft' | 'in-review' | 'changes' | 'approved' | 'shipped' | 'closed' | 'exploring' | 'requested' | 'in-progress' | 'done' | 'declined';\n\nexport interface LinkStatus {\n key: LinkStatusKey;\n /** \"In review\", \"Shipped\". */\n label: string;\n /** Logins asked to review (pull requests), or assignees (requests). */\n people: string[];\n /** \"In review · @dana reviews\". */\n line: string;\n}\n\nconst STATUS_LABEL: Record<LinkStatusKey, string> = {\n draft: 'Draft',\n 'in-review': 'In review',\n changes: 'Changes requested',\n approved: 'Approved',\n shipped: 'Shipped',\n closed: 'Closed',\n exploring: 'Being built',\n requested: 'Requested',\n 'in-progress': 'In progress',\n done: 'Done',\n declined: 'Declined',\n};\n\n/** Badge tone of a status: status colours only for status. */\nexport const LINK_STATUS_TONE: Record<LinkStatusKey, 'neutral' | 'info' | 'warning' | 'success' | 'danger'> = {\n draft: 'neutral',\n 'in-review': 'info',\n changes: 'warning',\n approved: 'success',\n shipped: 'success',\n closed: 'neutral',\n exploring: 'neutral',\n requested: 'neutral',\n 'in-progress': 'info',\n done: 'success',\n declined: 'neutral',\n};\n\nconst names = (people: string[]) => (people.length <= 2 ? people.map((p) => `@${p}`).join(' and ') : `@${people[0]} and ${people.length - 1} more`);\n\n/**\n * Where a library branch stands, from what GitHub says: a pull request (state, draft, labels, requested reviewers,\n * latest review per person) or a request issue (state, reason, assignees, whether a pull request implements it).\n */\nexport function linkStatus(\n x:\n | { kind: 'token-change' | 'exploration'; state: string; draft: boolean; labels?: readonly string[] | undefined; requestedReviewers?: readonly string[] | undefined; reviews?: readonly { user?: string | undefined; state: string }[] | undefined }\n | { kind: 'request'; state: 'open' | 'closed'; stateReason?: string | null | undefined; assignees?: readonly string[] | undefined; implemented?: boolean | undefined },\n): LinkStatus {\n const make = (key: LinkStatusKey, people: string[] = [], verb = 'reviews'): LinkStatus => {\n const label = STATUS_LABEL[key];\n const tail = people.length ? ` · ${names(people)} ${people.length === 1 ? verb : verb.replace(/s$/, '')}` : '';\n return { key, label, people, line: `${label}${tail}` };\n };\n if (x.kind === 'request') {\n if (x.state === 'closed') return make(x.stateReason === 'not_planned' ? 'declined' : 'done');\n return x.implemented ? make('in-progress', [...(x.assignees ?? [])], 'builds') : make('requested', [...(x.assignees ?? [])], 'builds');\n }\n if (x.state === 'merged') return make('shipped');\n if (x.state !== 'open') return make('closed');\n if ((x.labels ?? []).includes('exploration')) return make('exploring');\n if (x.draft) return make('draft');\n const latest = new Map<string, string>();\n for (const r of x.reviews ?? []) if (r.user && r.state !== 'COMMENTED') latest.set(r.user, r.state);\n const states = [...latest.values()];\n if (states.includes('CHANGES_REQUESTED')) return make('changes', [...latest].filter(([, s]) => s === 'CHANGES_REQUESTED').map(([u]) => u), 'asks');\n const waiting = [...(x.requestedReviewers ?? [])];\n if (states.includes('APPROVED') && !waiting.length) return make('approved');\n return make('in-review', waiting);\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// The preview: the product branch uses the library branch before it ships\n\nexport type LibraryPreviewKey = 'nothing-yet' | 'not-used' | 'preview' | 'outdated' | 'published' | 'switched' | 'declined';\n\nexport interface LibraryPreviewState {\n key: LibraryPreviewKey;\n /** One sentence in designer words. */\n sentence: string;\n /** The one action that moves it on, when there is one. */\n action: { kind: 'use-preview' | 'refresh-preview' | 'switch'; label: string } | null;\n}\n\n/**\n * What the product branch shows of a library branch, and what to do next:\n * - while the library branch is open, the product branch carries its commits (Bridge merges the library branch in),\n * so the product preview shows the real library change; new library commits make it outdated;\n * - once the library branch ships, \"Switch to the published version\" brings main into the product branch: its own\n * diff no longer carries the library change, the shipped one is used;\n * - a request has nothing to preview until someone builds it; a closed library branch says so.\n *\n * `missing` is how many library commits the product branch lacks, `total` how many the library branch has on top of\n * main (null when unknown). `behindMain` is how far the product branch is behind main.\n */\nexport function libraryPreviewState(x: {\n kind: LibraryLinkKind;\n lifecycle: 'open' | 'merged' | 'closed';\n /** The library branch changes something beyond its exploration note. */\n hasChanges: boolean;\n missing: number | null;\n total: number | null;\n behindMain: number | null;\n nearest?: string | undefined;\n}): LibraryPreviewState {\n if (x.kind === 'request') {\n if (x.lifecycle === 'closed') return { key: 'declined', sentence: `The library declined this request.${x.nearest ? ` Ask Claude to use ${x.nearest} instead.` : ''}`, action: null };\n if (x.lifecycle === 'merged') return x.behindMain === 0 ? { key: 'switched', sentence: 'Your branch uses the published version.', action: null } : { key: 'published', sentence: 'The library built it. Switch your branch to the published version, then ask Claude to use it.', action: { kind: 'switch', label: 'Switch to the published version' } };\n return { key: 'nothing-yet', sentence: 'Nothing to preview yet: the library builds it first. Your branch keeps its own version until then.', action: null };\n }\n if (x.lifecycle === 'merged') {\n if (x.behindMain === 0) return { key: 'switched', sentence: 'Your branch uses the published version.', action: null };\n return { key: 'published', sentence: 'The library published it. Switch your branch to the published version.', action: { kind: 'switch', label: 'Switch to the published version' } };\n }\n if (x.lifecycle === 'closed') return { key: 'declined', sentence: `The library closed it without shipping.${x.nearest ? ` Ask Claude to use ${x.nearest} instead.` : ''}`, action: null };\n if (!x.hasChanges) return { key: 'nothing-yet', sentence: 'The library branch has no change yet. Claude adds it there, then your branch can use its preview.', action: null };\n if (x.missing === 0) return { key: 'preview', sentence: 'Your branch uses a preview of the library branch. When the library publishes it, switch to the real version here in one click.', action: null };\n if (x.missing !== null && x.total !== null && x.missing < x.total) return { key: 'outdated', sentence: 'The library branch changed since your branch took its preview.', action: { kind: 'refresh-preview', label: 'Get the latest preview' } };\n return { key: 'not-used', sentence: 'Your branch does not use the library branch yet.', action: { kind: 'use-preview', label: 'Use its preview in my branch' } };\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// The summary sentence of a product branch\n\nconst nPieces = (n: number) => `${n} ${n === 1 ? 'piece' : 'pieces'}`;\n\n/**\n * The one sentence first on a product branch: \"Built with the library. 1 piece was missing, and Claude AI opened a\n * library branch for it.\" `who` names who opened the library branches when they are all from the same one.\n */\nexport function productBranchSentence(r: PiecesRecord | undefined, opts: { agentName?: string | undefined; viewer?: string | null | undefined } = {}): { lead: string; strong: string; rest: string } {\n if (!r) return { lead: 'Not checked against the library yet.', strong: '', rest: ' Ask Claude what is missing from the library.' };\n const n = r.pieces.length;\n if (!n) return { lead: 'Built with the library.', strong: ' Nothing is missing', rest: ': every value and component comes from it.' };\n const linked = r.pieces.filter((p) => r.links.some((l) => l.piece === p.id));\n const openers = [...new Set(linked.map((p) => r.links.find((l) => l.piece === p.id)!).map((l) => (l.agent ? (opts.agentName ?? 'Claude AI') : l.openedBy && l.openedBy === opts.viewer ? 'you' : l.openedBy ? `@${l.openedBy}` : 'someone')))];\n const by = openers.length === 1 ? openers[0]! : 'your team';\n const was = n === 1 ? 'was' : 'were';\n if (linked.length === n) {\n const what = n === 1 ? 'a library branch for it' : 'library branches for them';\n return { lead: 'Built with the library. ', strong: `${nPieces(n)} ${was} missing`, rest: `, and ${by} opened ${what}.` };\n }\n if (!linked.length) return { lead: 'Built with the library. ', strong: `${nPieces(n)} ${n === 1 ? 'is' : 'are'} missing`, rest: n === 1 ? ': open a library branch for it.' : ': open a library branch for each.' };\n return { lead: 'Built with the library. ', strong: `${nPieces(n)} ${was} missing`, rest: `: ${linked.length} ${linked.length === 1 ? 'has' : 'have'} a library branch, ${n - linked.length} still ${n - linked.length === 1 ? 'needs' : 'need'} one.` };\n}\n\n/** \"Why it exists\" on the library review: \"Ann needs it for <title> in Admin demo.\" */\nexport function whyItExists(n: NeededBy, opts: { names?: Record<string, string> } = {}): { who: string; title: string; product: string } {\n const who = n.author ? (opts.names?.[n.author] ?? `@${n.author}`) : 'A product team';\n return { who, title: n.title, product: n.productName };\n}\n\n/** The reviewer's question when the closest library value looks a lot like the piece. */\nexport function nearestQuestion(n: NeededBy, opts: { names?: Record<string, string> } = {}): string | null {\n if (!n.nearest) return null;\n const who = n.author ? (opts.names?.[n.author] ?? `@${n.author}`) : 'the product team';\n return `It looks a lot like \"${n.nearest}\". Is a new one worth it, or should ${who} use ${n.nearest}?`;\n}\n\n/** The review comment of \"Suggest <nearest>\": request changes with the library value to use instead. */\nexport function suggestNearestComment(n: NeededBy): string | null {\n if (!n.nearest) return null;\n return `Let's not add it to the library: ${n.nearest}${n.nearestToken ? ` (\\`${n.nearestToken}\\`)` : ''} covers it. Use it on ${n.title} (#${n.pr}) instead, then close this branch.`;\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// Naming helpers shared with the audit side\n\n/** A piece id from its kind and names: \"badge-overdue-tone\". */\nexport function pieceId(parts: (string | undefined)[]): string {\n return slugify(parts.filter(Boolean).join(' '), 48);\n}\n\nconst TONE_WORDS = ['neutral', 'success', 'warning', 'danger', 'info', 'error', 'critical', 'accent', 'brand', 'primary', 'secondary'];\n\n/** The word people use for a token: the tone in its path, else its last meaningful segment. */\nexport function tokenLabel(path: string): string {\n const parts = path.split('.');\n const tone = parts.find((p) => TONE_WORDS.includes(p));\n if (tone) return tone;\n const meaningful = parts.filter((p) => !/^(color|bg|fg|text|border|default|base|value|\\d+)$/.test(p));\n return meaningful[meaningful.length - 1] ?? parts[parts.length - 1] ?? path;\n}\n\n/** A new token beside the closest one, named after the piece: color.feedback.danger.bg + \"overdue\" -> color.feedback.overdue.bg when the closest has a tone, else a sibling. */\nexport function proposedTokenPath(nearest: string | undefined, name: string, category: string): string {\n const slug = slugify(name, 32);\n if (!nearest) return `${category === 'color' ? 'color' : category}.${slug}`;\n const parts = nearest.split('.');\n const toneAt = parts.findIndex((p) => TONE_WORDS.includes(p));\n if (toneAt > 0) return [...parts.slice(0, toneAt), slug, ...parts.slice(toneAt + 1)].join('.');\n return [...parts.slice(0, -1), slug].join('.');\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ import { type ExplorationSettings } from './explorations-github';
2
+ import { type GitHubClient, type PullRequestSummary } from './github-client';
3
+ import { type ProductConfig } from './products';
4
+ import { productBranchSentence, type LibraryBranchPlan, type LibraryLink, type LinkStatus, type MissingPiece, type PiecesRecord, type LibraryPreviewState, type ProductBranchRef } from './prod-branch';
5
+ import { type RequestSource } from './requests';
6
+ /** At most this many product files are read per check (two reads each). */
7
+ export declare const PIECES_FILE_LIMIT = 40;
8
+ type Pull = PullRequestSummary & {
9
+ body: string;
10
+ mergeable: boolean | null;
11
+ requestedReviewers: string[];
12
+ };
13
+ export interface ProductBranchRead {
14
+ pull: Pull;
15
+ product: ProductConfig;
16
+ /** The product's changed files, their texts at the branch start (merge base with main) and at its head. */
17
+ files: {
18
+ path: string;
19
+ before: string | null;
20
+ after: string | null;
21
+ }[];
22
+ /** Product files left unread past the limit. */
23
+ skipped: number;
24
+ record: PiecesRecord | undefined;
25
+ }
26
+ /**
27
+ * Reads a product branch for the check: the pull request, its product, and the product files it changes with their
28
+ * text before and after (two reads per file, up to PIECES_FILE_LIMIT files).
29
+ */
30
+ export declare function readProductBranchOnGitHub(gh: GitHubClient, pr: number, opts: {
31
+ products: readonly ProductConfig[];
32
+ }): Promise<ProductBranchRead>;
33
+ /** Saves a check in the product branch body, keeping the library branches already opened for its pieces. */
34
+ export declare function recordPiecesOnGitHub(gh: GitHubClient, pr: number, check: {
35
+ pieces: MissingPiece[];
36
+ fixes: number;
37
+ headSha?: string | undefined;
38
+ checkedAt?: string | undefined;
39
+ }): Promise<PiecesRecord>;
40
+ export interface PreviewMerge {
41
+ status: 'merged' | 'up-to-date' | 'conflict';
42
+ sha: string | null;
43
+ said: string;
44
+ }
45
+ /**
46
+ * The product branch uses the library branch's preview: Bridge merges the library branch into it, so the product
47
+ * preview deploy and anyone working on the branch get the real library change. A conflict is left to a developer.
48
+ */
49
+ export declare function useLibraryPreviewOnGitHub(gh: GitHubClient, input: {
50
+ productBranch: string;
51
+ libraryBranch: string;
52
+ libraryNumber: number;
53
+ }): Promise<PreviewMerge>;
54
+ export interface OpenLibraryBranchInput {
55
+ /** The product branch (pull request number). */
56
+ pr: number;
57
+ /** The piece id, from the recorded check. */
58
+ piece: string;
59
+ /** Branch library work starts from (the default branch). */
60
+ base: string;
61
+ products: readonly ProductConfig[];
62
+ /** Previews and snapshot packages for a library exploration. */
63
+ exploration: Pick<ExplorationSettings, 'packages' | 'apps'>;
64
+ /**
65
+ * Opens the token change of a token piece (planTokenProposal + writeTokenProposalOnGitHub where the caller has the
66
+ * token files and contrast pairs). Without it a token piece is refused.
67
+ */
68
+ writeTokens?: ((plan: Extract<LibraryBranchPlan, {
69
+ kind: 'token-change';
70
+ }>) => Promise<{
71
+ number: number;
72
+ url: string;
73
+ branch: string;
74
+ }>) | undefined;
75
+ /** Who opens it, and the AI agent behind it ("claude"). */
76
+ openedBy?: string | undefined;
77
+ agent?: string | undefined;
78
+ source?: RequestSource | undefined;
79
+ /** Merge the new library branch into the product branch when it already changes something (default true). */
80
+ usePreview?: boolean | undefined;
81
+ dryRun?: boolean | undefined;
82
+ }
83
+ export interface OpenedLibraryBranch {
84
+ status: 'opened' | 'already-linked' | 'dry-run';
85
+ plan: LibraryBranchPlan;
86
+ link: LibraryLink | null;
87
+ preview: PreviewMerge | null;
88
+ warnings: string[];
89
+ }
90
+ /** The product branch as the library side names it. */
91
+ export declare function productBranchRef(pull: Pick<Pull, 'number' | 'title' | 'headRef' | 'url' | 'author' | 'body'>, product: ProductConfig): ProductBranchRef;
92
+ /**
93
+ * Opens the library side of a missing piece and links both ways: the library branch (or request) says "Needed by" the
94
+ * product branch and carries the `needed-by-product` label; the product branch records the link. A token piece opens a
95
+ * token change (through `writeTokens`), a variant a library exploration, a new component a request. When the new
96
+ * library branch already changes something (a token change), the product branch takes its preview.
97
+ */
98
+ export declare function openLibraryBranchOnGitHub(gh: GitHubClient, input: OpenLibraryBranchInput): Promise<OpenedLibraryBranch>;
99
+ export interface LinkedLibraryBranch {
100
+ link: LibraryLink;
101
+ piece: MissingPiece | undefined;
102
+ status: LinkStatus;
103
+ preview: LibraryPreviewState;
104
+ /** Requested reviewers of a library pull request, or assignees of a request. */
105
+ people: string[];
106
+ }
107
+ export interface ProductBranchView {
108
+ pull: Pull;
109
+ product: ProductConfig;
110
+ record: PiecesRecord | undefined;
111
+ sentence: ReturnType<typeof productBranchSentence>;
112
+ linked: LinkedLibraryBranch[];
113
+ /** Pieces with no library branch yet. */
114
+ open: MissingPiece[];
115
+ }
116
+ /** Where one linked library branch stands, and what its preview means for the product branch. */
117
+ export declare function linkedLibraryBranchOnGitHub(gh: GitHubClient, link: LibraryLink, product: {
118
+ headSha: string;
119
+ baseRef: string;
120
+ }, piece?: MissingPiece): Promise<LinkedLibraryBranch>;
121
+ /**
122
+ * Everything a product branch page shows: the record, the sentence, and each linked library branch with its status,
123
+ * its reviewers and what its preview means for the product branch. A few reads per linked branch.
124
+ */
125
+ export declare function productBranchViewOnGitHub(gh: GitHubClient, pr: number, opts: {
126
+ products: readonly ProductConfig[];
127
+ viewer?: string | null | undefined;
128
+ agentName?: string | undefined;
129
+ }): Promise<ProductBranchView>;
130
+ /**
131
+ * "Switch to the published version": once the library branch shipped, main comes into the product branch (GitHub's
132
+ * update branch), so the product branch stops carrying the library change and uses the shipped one. Conflicts are
133
+ * left to bridge_update_from_main or a developer.
134
+ */
135
+ export declare function switchToPublishedOnGitHub(gh: GitHubClient, pr: number): Promise<{
136
+ status: 'switched' | 'up-to-date';
137
+ said: string;
138
+ }>;
139
+ export {};