@bridge-ds/core 0.2.0 → 0.4.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/config.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { IntegrationsConfig } from './requests';
2
2
  import { type PreviewApps, type PreviewsConfig } from './previews';
3
+ import { type Rights, type VisualImpactConfig } from './rights';
3
4
  /** File name Bridge looks for at the repository root. */
4
5
  export declare const CONFIG_FILE = "bridge.config.json";
5
6
  export interface RepoRef {
@@ -63,6 +64,10 @@ export interface ComponentsConfig {
63
64
  /** Generated component specs the Figma plugin pulls (default packages/react/component-specs.json). */
64
65
  specs?: string | undefined;
65
66
  }
67
+ /**
68
+ * Design and tech owners. Each entry is a person ("peterbono" or "@peterbono") or a GitHub team ("@acme/design"),
69
+ * whose members GitHub manages (teams.ts expands them).
70
+ */
66
71
  export interface Owners {
67
72
  design: string[];
68
73
  tech: string[];
@@ -86,10 +91,15 @@ export interface GovernanceConfig {
86
91
  labels: string[];
87
92
  /** Files whose change is Tier 3 on its own, repo-relative; a trailing "/" matches a directory. */
88
93
  guardrails: string[];
89
- /** GitHub logins that count as design and tech owners. Empty: two distinct approvers stand in. */
94
+ /** People and teams that count as design and tech owners. Empty: two distinct approvers stand in. */
90
95
  owners: Owners;
91
96
  /** Owner override for missing approvals only (never CI, changes requested or drafts). Absent: no override. */
92
97
  override?: OverridePolicy | undefined;
98
+ /**
99
+ * Which owner group may approve design, approve tech and ship (see rights.ts). Absent: approvals follow `owners`
100
+ * and anyone with write access may ship, as before the field existed.
101
+ */
102
+ rights?: Rights | undefined;
93
103
  }
94
104
  /** `bridge audit` settings: how product code that consumes the design system is measured. */
95
105
  export interface AuditConfig {
@@ -139,6 +149,8 @@ export interface BridgeConfig {
139
149
  previews?: PreviewsConfig | undefined;
140
150
  /** Where component change requests go besides the GitHub issue (see requests.ts). */
141
151
  integrations?: IntegrationsConfig | undefined;
152
+ /** Visual before and after on pull requests: automatic (default) or on demand (label `visual-check`). */
153
+ visualImpact?: VisualImpactConfig | undefined;
142
154
  }
143
155
  type Partialish<T> = {
144
156
  [K in keyof T]?: T[K] | undefined;
@@ -185,9 +197,10 @@ export interface BridgeConfigInput {
185
197
  package: string;
186
198
  };
187
199
  cssVar?: Partial<BridgeConfig['cssVar']>;
188
- governance?: Partial<Omit<GovernanceConfig, 'owners' | 'override'>> & {
200
+ governance?: Partial<Omit<GovernanceConfig, 'owners' | 'override' | 'rights'>> & {
189
201
  owners?: Partial<Owners>;
190
202
  override?: Partial<OverridePolicy>;
203
+ rights?: Partial<Rights>;
191
204
  };
192
205
  audit?: AuditConfig;
193
206
  previews?: {
@@ -196,6 +209,7 @@ export interface BridgeConfigInput {
196
209
  production?: PreviewApps;
197
210
  };
198
211
  integrations?: IntegrationsConfig;
212
+ visualImpact?: Partial<VisualImpactConfig>;
199
213
  }
200
214
  export declare const DEFAULT_TOKEN_PATHS: TokenPaths;
201
215
  export declare const DEFAULT_GOVERNANCE: GovernanceConfig;
@@ -0,0 +1,11 @@
1
+ import { type ResolveContext } from './resolver';
2
+ import type { TokenSet } from './token-set';
3
+ /** CSS value of a resolved DTCG value, the way tokens.css writes it. */
4
+ export declare function cssOf(type: string, value: unknown): string | undefined;
5
+ export declare const cssVarOf: (path: string) => string;
6
+ /**
7
+ * CSS custom properties to set on a scope so it renders `proposed` instead of `head`: every token whose resolved value
8
+ * differs in that context. Setting resolved values (not var() references) keeps chains correct even when the original
9
+ * variable is declared on an ancestor.
10
+ */
11
+ export declare function previewOverrides(head: TokenSet, proposed: TokenSet, ctx: ResolveContext): Record<string, string>;
@@ -0,0 +1,116 @@
1
+ import { type ComponentDocsJson, type DocsConventions, type DocsPrefill, type ProseKey } from './docs-draft';
2
+ export declare const DOCS_DRAFT_MODEL = "claude-sonnet-5-5";
3
+ export declare const ANTHROPIC_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
4
+ /** How a selected passage is rewritten. */
5
+ export type DocsSelectionMode = 'clearer' | 'shorter' | 'voice';
6
+ export declare const DOCS_SELECTION_MODES: Record<DocsSelectionMode, string>;
7
+ export declare const isDocsSelectionMode: (m: unknown) => m is DocsSelectionMode;
8
+ export declare const isProseKey: (k: unknown) => k is ProseKey;
9
+ /**
10
+ * One drafting job on a component's docs. "sections": write these prose sections (rewrite: even when they hold text;
11
+ * check: written sections older than the code, returned only when no longer true for it, so an absent one is still
12
+ * accurate). "selection": rewrite one passage of a section, nothing else.
13
+ */
14
+ export type DocsDraftTask = {
15
+ kind: 'sections';
16
+ keys: ProseKey[];
17
+ rewrite?: boolean | undefined;
18
+ check?: ProseKey[] | undefined;
19
+ } | {
20
+ kind: 'selection';
21
+ key: ProseKey;
22
+ text: string;
23
+ mode: DocsSelectionMode;
24
+ };
25
+ /** Problems in a task, as sentences. Empty when valid. */
26
+ export declare function validateDocsTask(x: unknown): string[];
27
+ /** The keys a task writes. */
28
+ export declare const docsTaskKeys: (t: DocsDraftTask) => ProseKey[];
29
+ /** The prompt to rewrite a selected passage of a section: the facts and the conventions, the passage, one rule. */
30
+ export declare function docsSelectionPrompt(prefill: DocsPrefill, conventions: DocsConventions, sel: {
31
+ key: ProseKey;
32
+ text: string;
33
+ mode: DocsSelectionMode;
34
+ }): string;
35
+ /** The prompt of a task: docsPrompt for sections (only those keys), docsSelectionPrompt for a selection. */
36
+ export declare function docsTaskPrompt(prefill: DocsPrefill, conventions: DocsConventions, task: DocsDraftTask): string;
37
+ /** The first JSON object in a model reply (plain, or inside a ```json fence). Throws when there is none. */
38
+ export declare function parseDocsReply(text: string): Record<string, unknown>;
39
+ /** One line of docs prose: em dashes out, whitespace folded, trimmed. */
40
+ export declare const cleanDocsText: (s: string) => string;
41
+ export type DocsSections = Partial<Pick<ComponentDocsJson, ProseKey>>;
42
+ /**
43
+ * The prose sections of an answer, kept to the docs.json shapes: only `keys`, summary one cleaned paragraph, lists of
44
+ * non-empty cleaned strings, dos and donts as {title, text} (an example only when `examples` and well formed). Em
45
+ * dashes removed everywhere. Sections that come back empty or in the wrong shape are left out.
46
+ */
47
+ export declare function sanitizeDocsSections(raw: unknown, keys?: readonly ProseKey[], opts?: {
48
+ examples?: boolean;
49
+ }): DocsSections;
50
+ /** A rewritten passage: one cleaned paragraph without wrapping quotes, or '' when there is none. */
51
+ export declare function sanitizeDocsReplacement(raw: unknown): string;
52
+ /**
53
+ * A task's answer. Sections tasks: `sections` drafted or fixed, and `checked`, the checked sections Claude verified and
54
+ * left unchanged (still accurate). Selection: `replacement` only.
55
+ */
56
+ export type DocsTaskAnswer = {
57
+ sections: DocsSections;
58
+ checked: ProseKey[];
59
+ replacement?: undefined;
60
+ } | {
61
+ sections: DocsSections;
62
+ checked?: undefined;
63
+ replacement: string;
64
+ };
65
+ /**
66
+ * A task's answer from Claude's reply text. Sections: the requested keys, sanitized; throws when none came back.
67
+ * Selection: `replacement` only (sections stay empty); a reply that is plain text counts as the passage.
68
+ */
69
+ export declare function parseDocsTaskAnswer(task: DocsDraftTask, text: string): DocsTaskAnswer;
70
+ /**
71
+ * Sections and checked of a sections task from an answer object: absent checked keys are still accurate. Throws only
72
+ * when the task checks nothing and none of its keys came back.
73
+ */
74
+ export declare function docsSectionsAnswer(task: Extract<DocsDraftTask, {
75
+ kind: 'sections';
76
+ }>, raw: unknown): {
77
+ sections: DocsSections;
78
+ checked: ProseKey[];
79
+ };
80
+ /** The sections of a task from an answer object (API reply or the exchange). Throws when none of the keys came back. */
81
+ export declare function docsSectionsResult(task: Extract<DocsDraftTask, {
82
+ kind: 'sections';
83
+ }>, raw: unknown): DocsSections;
84
+ /**
85
+ * Drafted sections applied to a docs.json, marked for review: the sections replace the ones there, the rest stays as
86
+ * is (anatomy and preview are code-decided and never come from a draft), and draftedSections lists what waits.
87
+ */
88
+ export declare function applyDocsSections<T extends Partial<ComponentDocsJson>>(docs: T, sections: DocsSections): T;
89
+ /**
90
+ * The docs-draft Action's merge: Claude's prose on top of the pre-fill draft, only where the pre-fill is empty (a
91
+ * written section is never overwritten), anatomy kept from the code, the drafted sections marked for review.
92
+ */
93
+ export declare function mergeDocsDraft(prefillDraft: Partial<ComponentDocsJson>, reply: Record<string, unknown>): Partial<ComponentDocsJson>;
94
+ export interface AnthropicHttpRequest {
95
+ url: string;
96
+ init: {
97
+ method: 'POST';
98
+ headers: Record<string, string>;
99
+ body: string;
100
+ };
101
+ }
102
+ export interface AnthropicRequestInput {
103
+ apiKey: string;
104
+ prompt: string;
105
+ model?: string | undefined;
106
+ system?: string | undefined;
107
+ maxTokens?: number | undefined;
108
+ /** A direct call from a browser page (the user's own key): adds the header the API asks for. */
109
+ browser?: boolean | undefined;
110
+ /** Re-run a declined request on a fallback model inside the same call (server-side fallback beta). */
111
+ fallback?: boolean | undefined;
112
+ }
113
+ /** The Messages API request for one prompt. The key goes in x-api-key, to api.anthropic.com only. */
114
+ export declare function anthropicRequest(input: AnthropicRequestInput): AnthropicHttpRequest;
115
+ /** The text of a Messages API reply. Throws when Claude declined or the reply was cut off. */
116
+ export declare function anthropicReplyText(msg: unknown): string;
@@ -23,6 +23,11 @@ export interface ComponentDocsJson {
23
23
  anatomy?: Record<string, string>;
24
24
  /** Who wrote the prose when it was not a person in the editor. Only "claude" today. */
25
25
  draftedBy?: string;
26
+ /**
27
+ * The prose sections Claude drafted that wait for review, when a draft covered only some of them (in-place drafting
28
+ * on the Library page, the docs-draft Action). Missing means the whole file. Accepting removes it.
29
+ */
30
+ draftedSections?: ProseKey[];
26
31
  /** True while drafted prose waits for a reviewer. Accepting sets it to false. */
27
32
  needsReview?: boolean;
28
33
  }
@@ -100,15 +105,36 @@ export interface DocsConventions {
100
105
  example?: string;
101
106
  /** The component source, so the prose stays true to the code. */
102
107
  source?: string;
108
+ /** The component's CSS module: its states and parts, so the prose stays true to the look. */
109
+ styles?: string;
103
110
  }
111
+ /** What docsPrompt asks for. Without `keys`, the missing prose as a full docs.json (the docs-draft Action, bridge_draft_docs). */
112
+ export interface DocsPromptOptions {
113
+ /** Only these sections, answered as an object holding only them. */
114
+ keys?: readonly ProseKey[];
115
+ /** Rewrite the sections even when they hold text. */
116
+ rewrite?: boolean;
117
+ /**
118
+ * Written sections older than the component code, to check (among `keys`): Claude returns one only when something
119
+ * in it is no longer true for the code, keeping the writer's wording elsewhere; an accurate one is left out.
120
+ */
121
+ check?: readonly ProseKey[];
122
+ }
123
+ /** The JSON shape of these sections, for a prompt: {"summary": "...", "dos": [{ "title", "text" }]}. */
124
+ export declare const docsSectionShapes: (keys: readonly ProseKey[]) => string;
104
125
  /**
105
126
  * The prompt a model gets to write the prose of a docs.json: the pre-fill as facts, the conventions, and strict
106
127
  * rules (no invention, JSON only). Used by the GitHub Action and returned by bridge_draft_docs for the user's own
107
128
  * Claude.
108
129
  */
109
- export declare function docsPrompt(prefill: DocsPrefill, conventions?: DocsConventions): string;
110
- /** Marks prose as drafted by Claude and waiting for review. */
111
- export declare function markClaudeDraft(d: ComponentDocsJson): ComponentDocsJson;
130
+ export declare function docsPrompt(prefill: DocsPrefill, conventions?: DocsConventions, options?: DocsPromptOptions): string;
131
+ /**
132
+ * Marks prose as drafted by Claude and waiting for review. With `sections`, only those sections were drafted: they
133
+ * join the ones already waiting (draftedSections, in the PROSE_KEYS order). Without, the whole file was.
134
+ */
135
+ export declare function markClaudeDraft<T extends Partial<ComponentDocsJson>>(d: T, sections?: readonly ProseKey[]): T;
136
+ /** Prose sections that wait for review: draftedSections, every prose key for a whole-file draft, none once accepted. */
137
+ export declare function sectionsNeedingReview(d: Partial<ComponentDocsJson> | null | undefined): ProseKey[];
112
138
  /** True when the docs hold AI-drafted prose nobody has reviewed yet. */
113
139
  export declare const needsDocsReview: (d: Partial<ComponentDocsJson> | null | undefined) => boolean;
114
140
  /** A reviewer accepts drafted prose: needsReview goes false, draftedBy stays as the record of who wrote it. */
@@ -121,7 +147,7 @@ export declare function formatDocsJson(d: Partial<ComponentDocsJson>): string;
121
147
  * original (or with one that is not valid JSON) it falls back to formatDocsJson.
122
148
  */
123
149
  export declare function patchDocsJson(original: string | null | undefined, next: Partial<ComponentDocsJson>): string;
124
- /** Accepts the drafted prose inside a docs.json text: only the needsReview line changes. Throws on invalid JSON. */
150
+ /** Accepts the drafted prose inside a docs.json text: only the review lines change. Throws on invalid JSON. */
125
151
  export declare function acceptDocsText(text: string): string;
126
152
  /** Tier and bump of a docs-only change: Tier 1 under the shared classifier, and a patch. */
127
153
  export declare const DOCS_CHANGE_BUMP: "patch";
@@ -1,4 +1,6 @@
1
- import { type DraftText } from './change-drafts';
1
+ import { DRAFT_LINE_MAX, type DraftText } from './change-drafts';
2
+ import { type DocsDraftTask, type DocsSections } from './docs-ai';
3
+ import type { ProseKey } from './docs-draft';
2
4
  import { type GitHubClient } from './github-client';
3
5
  import { type RequestChange, type RequestKind } from './requests';
4
6
  export declare const DRAFT_EXCHANGE_VERSION = 1;
@@ -8,7 +10,10 @@ export declare const DRAFTS_DIR = ".bridge/drafts";
8
10
  export declare const DRAFT_PHRASE = "Draft my Bridge changes";
9
11
  export declare const DRAFT_TITLE_MAX = 70;
10
12
  export declare const DRAFT_SUMMARY_SENTENCES = 3;
11
- export type DraftScope = 'request' | 'push';
13
+ export { DRAFT_LINE_MAX };
14
+ export type DraftScope = 'request' | 'push' | 'docs';
15
+ /** Who wrote the session: the DS Bridge Figma plugin, or Bridge Studio (component docs on the Library page). */
16
+ export type DraftSource = 'figma' | 'studio';
12
17
  export type DraftSessionStatus = 'pending' | 'drafted';
13
18
  export interface DraftImage {
14
19
  label: 'before' | 'after';
@@ -16,6 +21,23 @@ export interface DraftImage {
16
21
  url: string;
17
22
  /** Repo path on the requests branch, so tools can read it with the token. */
18
23
  path?: string | undefined;
24
+ /** The change line (DraftLine id) this image shows, when an item has several components (a push). */
25
+ line?: string | undefined;
26
+ }
27
+ /**
28
+ * One detected component change to describe in one sentence (its "change line"): the main line of its row in the
29
+ * plugin and the first line of its request. A request item has one, a push item one per component it requests.
30
+ */
31
+ export interface DraftLine {
32
+ /** Stable id to answer with (the plugin uses the component's node id). */
33
+ id: string;
34
+ component?: string | undefined;
35
+ kind?: RequestKind | undefined;
36
+ changes: RequestChange[];
37
+ /** The deterministic line, from the changes only, as a starting point. */
38
+ line: string;
39
+ /** The deterministic request title of this component change, as a starting point. Missing from older plugins. */
40
+ title?: string | undefined;
19
41
  }
20
42
  export interface DraftVariableRow {
21
43
  path: string;
@@ -38,20 +60,52 @@ export interface DraftItem {
38
60
  images?: DraftImage[] | undefined;
39
61
  /** The deterministic draft (from the detected changes only), as a starting point. */
40
62
  draft: DraftText;
63
+ /** Component changes to write one line each for. Missing in sessions from older plugins. */
64
+ lines?: DraftLine[] | undefined;
65
+ /** Docs only: the component docs to write. */
66
+ docs?: DraftDocsItem | undefined;
67
+ }
68
+ /** A docs item: prose sections of one component's docs.json (or one selected passage), from Bridge Studio. */
69
+ export interface DraftDocsItem {
70
+ /** Component slug on the Library page (button, icon-button). */
71
+ slug: string;
72
+ /** docs.json path in the repo. */
73
+ path?: string | undefined;
74
+ task: DocsDraftTask;
75
+ /** The full prompt: the task, the rules, the facts from the code (props, CSS module classes, JSDoc, tokens), the voice. */
76
+ prompt: string;
41
77
  }
42
78
  export interface DraftResult {
43
79
  id: string;
44
80
  title: string;
45
81
  summary: string;
82
+ /**
83
+ * One sentence per change line of the item (by DraftLine id), and the title of the request it becomes. Optional:
84
+ * older writers send none, or lines without a title.
85
+ */
86
+ lines?: DraftResultLine[] | undefined;
87
+ /** Docs items (sections task): the sections Claude wrote, sanitized to the docs.json shapes. */
88
+ docs?: DocsSections | undefined;
89
+ /** Docs items (selection task): the rewritten passage. */
90
+ replacement?: string | undefined;
91
+ /** Docs items (sections task): checked sections Claude verified and left unchanged (still accurate). */
92
+ checked?: ProseKey[] | undefined;
46
93
  by: 'claude';
47
94
  at: string;
48
95
  }
96
+ export interface DraftResultLine {
97
+ id: string;
98
+ /** May be empty when only the title was written. */
99
+ line: string;
100
+ /** The request title of this component change: short, sentence case, at most 70 characters ("Add an icon after the label"). */
101
+ title?: string | undefined;
102
+ }
49
103
  export interface PendingDrafts {
50
104
  version: 1;
51
105
  sessionId: string;
52
106
  createdAt: string;
53
107
  status: DraftSessionStatus;
54
- source: 'figma';
108
+ source: DraftSource;
55
109
  items: DraftItem[];
56
110
  results?: DraftResult[] | undefined;
57
111
  }
@@ -66,6 +120,7 @@ export declare function draftSessionPath(sessionId: string): string;
66
120
  export declare function draftImagePath(sessionId: string, itemId: string, label: DraftImage['label']): string;
67
121
  export interface ComposeDraftsInput {
68
122
  items: DraftItem[];
123
+ source?: DraftSource | undefined;
69
124
  sessionId?: string | undefined;
70
125
  now?: Date | undefined;
71
126
  random?: (() => number) | undefined;
@@ -85,15 +140,31 @@ export declare function sanitizeDraftText(raw: {
85
140
  title?: unknown;
86
141
  summary?: unknown;
87
142
  }): DraftText;
143
+ /** What a writer (bridge_write_drafts) sends for one item, before sanitizing. */
144
+ export interface DraftResultInput {
145
+ id: string;
146
+ title?: unknown;
147
+ summary?: unknown;
148
+ lines?: unknown;
149
+ /** Docs items: the sections written (sections task). */
150
+ docs?: unknown;
151
+ /** Docs items: the rewritten passage (selection task). */
152
+ replacement?: unknown;
153
+ }
154
+ /** The docs part of a result for a docs item, sanitized to its task. Throws when it holds nothing the task asked for. */
155
+ export declare function sanitizeDocsResult(item: DraftItem, r: DraftResultInput): Pick<DraftResult, 'docs' | 'replacement' | 'checked'>;
156
+ /**
157
+ * The change lines of a result kept to the contract: known line ids only (throws on an unknown one), one sentence of
158
+ * at most 120 characters each, empty ones dropped, the last one per id kept.
159
+ */
160
+ export declare function sanitizeResultLines(item: Pick<DraftItem, 'id' | 'lines'>, raw: unknown): DraftResultLine[];
161
+ /** A request title kept to the contract: one line, no em dash, sentence case, at most 70 characters, no trailing period. */
162
+ export declare function sanitizeDraftTitle(raw: unknown): string;
88
163
  /**
89
164
  * The session with these results added (a result for the same item replaces the earlier one). Results are sanitized.
90
165
  * Throws on an unknown item id or an empty result. Status is "drafted" once every item has a result.
91
166
  */
92
- export declare function mergeDraftResults(p: PendingDrafts, results: {
93
- id: string;
94
- title?: unknown;
95
- summary?: unknown;
96
- }[], at?: Date): PendingDrafts;
167
+ export declare function mergeDraftResults(p: PendingDrafts, results: DraftResultInput[], at?: Date): PendingDrafts;
97
168
  /** The Markdown brief of one item, for Claude: what to write, the facts, the starting draft. */
98
169
  export declare function draftItemBrief(i: DraftItem): string;
99
170
  type DraftClient = Pick<GitHubClient, 'getFile' | 'putFile' | 'deleteFile' | 'listDirectory'>;
@@ -119,11 +190,7 @@ export declare function updatePendingDrafts(client: DraftClient, sessionId: stri
119
190
  message?: string;
120
191
  }): Promise<PendingDrafts>;
121
192
  /** Writes Claude's results into a session (sanitized, merged, status "drafted" when complete), retrying on 409. */
122
- export declare function writeDraftResults(client: DraftClient, sessionId: string, results: {
123
- id: string;
124
- title?: unknown;
125
- summary?: unknown;
126
- }[], opts?: {
193
+ export declare function writeDraftResults(client: DraftClient, sessionId: string, results: DraftResultInput[], opts?: {
127
194
  branch?: string;
128
195
  now?: Date;
129
196
  attempts?: number;
@@ -135,4 +202,58 @@ export declare function writeDraftResults(client: DraftClient, sessionId: string
135
202
  export declare function deletePendingDrafts(client: DraftClient, sessionId: string, opts?: {
136
203
  branch?: string;
137
204
  }): Promise<boolean>;
138
- export {};
205
+ /** Creates the requests branch from the head of `baseBranch` when it is missing. Never moves an existing branch. */
206
+ export declare function ensureRequestsBranch(client: Pick<GitHubClient, 'getBranchSha' | 'createBranch'>, baseBranch: string): Promise<void>;
207
+ export interface DocsDraftSessionInput {
208
+ component: string;
209
+ slug: string;
210
+ path?: string | undefined;
211
+ task: DocsDraftTask;
212
+ prompt: string;
213
+ /** Branch the requests branch starts from when it does not exist yet. */
214
+ baseBranch: string;
215
+ now?: Date | undefined;
216
+ random?: (() => number) | undefined;
217
+ }
218
+ /**
219
+ * Bridge Studio asks the user's own Claude for component docs: one docs item in a new session (source "studio") on
220
+ * the requests branch, created from `baseBranch` when missing. Returns the session to poll.
221
+ */
222
+ export declare function createDocsDraftSession(client: Pick<GitHubClient, 'getBranchSha' | 'createBranch' | 'getFile' | 'putFile' | 'deleteFile' | 'listDirectory'>, input: DocsDraftSessionInput): Promise<PendingDrafts>;
223
+ export declare const DRAFT_POLL_INTERVAL_MS = 5000;
224
+ export declare const DRAFT_POLL_TIMEOUT_MS: number;
225
+ export interface DraftPollerOptions {
226
+ client: Pick<GitHubClient, 'getFile'>;
227
+ sessionId: string;
228
+ intervalMs?: number | undefined;
229
+ timeoutMs?: number | undefined;
230
+ now?: (() => number) | undefined;
231
+ setTimer?: ((fn: () => void, ms: number) => unknown) | undefined;
232
+ clearTimer?: ((handle: unknown) => void) | undefined;
233
+ /** Claude answered every item. Polling has stopped. */
234
+ onResults: (results: DraftResult[], drafts: PendingDrafts) => void;
235
+ /** No answer within the timeout. Polling has stopped; start() polls again. */
236
+ onTimeout: () => void;
237
+ /** The file is gone (sent or cancelled elsewhere). Polling has stopped. */
238
+ onGone?: (() => void) | undefined;
239
+ /** A read failed; polling goes on. */
240
+ onError?: ((message: string) => void) | undefined;
241
+ }
242
+ /** Reads the session every 5 seconds until Claude answers, the file goes away, or 10 minutes pass. */
243
+ export declare class DraftPoller {
244
+ private readonly o;
245
+ private handle;
246
+ private startedAt;
247
+ private running;
248
+ private busy;
249
+ constructor(o: DraftPollerOptions);
250
+ get active(): boolean;
251
+ private now;
252
+ private schedule;
253
+ /** Starts (or restarts, after a timeout) polling; the timeout counts from now. */
254
+ start(): void;
255
+ stop(): void;
256
+ private tick;
257
+ /** Reads the file once. True when polling is over (answered or gone). */
258
+ check(): Promise<boolean>;
259
+ }
@@ -1,7 +1,7 @@
1
1
  import type { BridgeConfig } from './config';
2
- import { type Exploration, type ExplorationInstall, type ExplorationStatus, type PromotionPlan } from './explorations';
2
+ import { type Exploration, type ExplorationSource, type ExplorationStatus, type ExplorationTryIt, type PromotionPlan } from './explorations';
3
3
  import { type GitHubClient, type PullRequestSummary } from './github-client';
4
- import { type PreviewApps } from './previews';
4
+ import { type BranchPreview, type PreviewApps } from './previews';
5
5
  export interface ExplorationSettings {
6
6
  /** Branch explorations start from and pull requests target (the default branch). */
7
7
  base: string;
@@ -9,6 +9,11 @@ export interface ExplorationSettings {
9
9
  packages: string[];
10
10
  /** Per-branch preview URL templates (bridge.config.json previews.apps). */
11
11
  apps: PreviewApps;
12
+ /** Requests the exploration answers, listed in its draft pull request body. */
13
+ requests?: {
14
+ number: number;
15
+ title: string;
16
+ }[] | undefined;
12
17
  }
13
18
  export interface ExplorationSummary {
14
19
  number: number;
@@ -21,16 +26,24 @@ export interface ExplorationSummary {
21
26
  status: ExplorationStatus;
22
27
  draft: boolean;
23
28
  author: string;
29
+ /** Where it was started and the AI agent that started it, when the exploration block says so. */
30
+ source?: ExplorationSource | undefined;
31
+ agent?: string | undefined;
24
32
  updatedAt: string;
33
+ /** Head commit of the branch. */
34
+ headSha: string;
35
+ /**
36
+ * Template previews Vercel cannot have shortened (a host label of 63 characters or less), without a round trip. The
37
+ * URLs Vercel reports, with their build state, come from `explorationTestBuildOnGitHub`.
38
+ */
25
39
  previews: {
26
40
  app: string;
27
41
  label: string;
28
42
  url: string;
29
43
  }[];
30
- install: ExplorationInstall;
31
44
  }
32
45
  /** One exploration from a pull request summary. */
33
- export declare function explorationSummary(p: PullRequestSummary, owner: string, repo: string, s: Pick<ExplorationSettings, 'packages' | 'apps'>): ExplorationSummary;
46
+ export declare function explorationSummary(p: PullRequestSummary, _owner: string, _repo: string, s: Pick<ExplorationSettings, 'packages' | 'apps'>): ExplorationSummary;
34
47
  /** Explorations of the repo, newest activity first. `state: 'open'` lists the running ones and the promoted open ones. */
35
48
  export declare function listExplorationsOnGitHub(gh: GitHubClient, s: Pick<ExplorationSettings, 'packages' | 'apps'>, opts?: {
36
49
  state?: 'open' | 'closed' | 'all';
@@ -41,7 +54,8 @@ export interface StartedExploration {
41
54
  pr: number;
42
55
  url: string;
43
56
  commit: string;
44
- install: ExplorationInstall;
57
+ /** Just started: the snapshot is not built yet. `explorationTestBuildOnGitHub` reads the command once it is. */
58
+ tryIt: ExplorationTryIt;
45
59
  previews: {
46
60
  app: string;
47
61
  label: string;
@@ -54,6 +68,30 @@ export interface StartedExploration {
54
68
  * install command. Refuses an exploration whose branch already exists.
55
69
  */
56
70
  export declare function startExplorationOnGitHub(gh: GitHubClient, e: Exploration, s: ExplorationSettings): Promise<StartedExploration>;
71
+ /**
72
+ * Where the test build of an exploration stands: the install command from its sticky preview comment when it matches
73
+ * the branch head, "building" while the workflow has not caught up, "failed" when the publish failed. Two reads.
74
+ */
75
+ export declare function explorationTryItOnGitHub(gh: GitHubClient, e: {
76
+ number: number;
77
+ branch: string;
78
+ }): Promise<ExplorationTryIt>;
79
+ export interface ExplorationTestBuild {
80
+ /** The install command from the sticky preview comment, or building/failed. Never a guessed command. */
81
+ tryIt: ExplorationTryIt;
82
+ /** Every configured app with the URL Vercel reports (null while unknown) and its build state. */
83
+ previews: BranchPreview[];
84
+ /** True when the command is ready (or the publish failed) and every preview is built: polling can stop. */
85
+ settled: boolean;
86
+ }
87
+ /**
88
+ * Everything "Try it" shows for an exploration, read in one go: the command from the preview comment and the real
89
+ * preview URLs (deployments, Vercel statuses, the Vercel comment). Comments and the branch head are read once.
90
+ */
91
+ export declare function explorationTestBuildOnGitHub(gh: GitHubClient, e: {
92
+ number: number;
93
+ branch: string;
94
+ }, config: Pick<BridgeConfig, 'previews'> | undefined): Promise<ExplorationTestBuild>;
57
95
  /** The open exploration pull request of a branch, slug or number. */
58
96
  export declare function findExplorationOnGitHub(gh: GitHubClient, ref: string | number): Promise<PullRequestSummary & {
59
97
  body: string;
@@ -13,6 +13,8 @@ export declare const EXPLORATION_TIER_LABEL = "Exploration (not mergeable)";
13
13
  export declare const EXPLORATION_CHECK = "Exploration not merged as is";
14
14
  export type ExplorationUseCase = 'user-test' | 'stakeholder-demo' | 'ab-idea' | 'terminal' | 'other';
15
15
  export type ExplorationStatus = 'active' | 'promoted' | 'closed';
16
+ /** Where an exploration was started: Bridge Studio, the Figma plugin, the CLI or the MCP server (an AI agent). */
17
+ export type ExplorationSource = 'studio' | 'figma' | 'cli' | 'mcp';
16
18
  export declare const EXPLORATION_USE_CASES: {
17
19
  value: ExplorationUseCase;
18
20
  label: string;
@@ -33,6 +35,10 @@ export interface Exploration {
33
35
  author?: string | undefined;
34
36
  /** ISO date it started. */
35
37
  startedAt?: string | undefined;
38
+ /** Where it was started, so Studio says "You" only for what the viewer started there. */
39
+ source?: ExplorationSource | undefined;
40
+ /** The AI agent that started it ("claude"), when one did: the GitHub account alone cannot tell. */
41
+ agent?: string | undefined;
36
42
  }
37
43
  /** "Denser data tables!" -> "denser-data-tables". Up to 40 characters, cut at a word boundary. Empty when nothing is left. */
38
44
  export declare function explorationSlug(name: string): string;
@@ -50,34 +56,6 @@ export declare function explorationProblems(e: Pick<Exploration, 'name' | 'purpo
50
56
  }): string[];
51
57
  /** The note committed as the first commit of the branch (`.bridge/explorations/<slug>.md`). */
52
58
  export declare function explorationNote(e: Exploration): string;
53
- export interface ExplorationInstall {
54
- /** One command for every snapshot package, `npm i @bridge-ds/react@<snapshot> ...`, so internal dependencies match. */
55
- command: string;
56
- packages: {
57
- name: string;
58
- url: string;
59
- }[];
60
- /** Tarballs of the `preview-pr-<n>` prerelease, when pkg.pr.new is not set up. Needs the head commit. */
61
- fallback?: {
62
- command: string;
63
- packages: {
64
- name: string;
65
- url: string;
66
- }[];
67
- release: string;
68
- } | undefined;
69
- }
70
- /**
71
- * How a product staging branch installs an exploration: pkg.pr.new snapshots by pull request number (always the
72
- * latest commit), as `name@url` so package.json keeps the package name, and the prerelease tarballs as a fallback.
73
- */
74
- export declare function explorationInstall(input: {
75
- owner: string;
76
- repo: string;
77
- pr: number;
78
- packages: string[];
79
- sha?: string | undefined;
80
- }): ExplorationInstall;
81
59
  export interface ExplorationPrContext {
82
60
  owner: string;
83
61
  repo: string;
@@ -149,6 +127,40 @@ export declare function promotionPlan(input: {
149
127
  requests?: number[] | undefined;
150
128
  date?: Date | undefined;
151
129
  }): PromotionPlan;
130
+ /** Where the test build of an exploration stands, from its sticky preview comment and the branch head. */
131
+ export type ExplorationTryIt = {
132
+ state: 'ready';
133
+ command: string;
134
+ source: 'pkg.pr.new' | 'release' | 'other';
135
+ sha7: string | null;
136
+ } | {
137
+ state: 'building';
138
+ } | {
139
+ state: 'failed';
140
+ };
141
+ /**
142
+ * The install command a product staging branch can trust, read from the `<!-- bridge-branch-preview -->` comment,
143
+ * never guessed: no comment yet, or a comment about an older commit than the head, means the snapshot is still
144
+ * building; a comment without a command means the publish failed.
145
+ */
146
+ export declare function explorationTryIt(input: {
147
+ comment?: string | null | undefined;
148
+ headSha?: string | null | undefined;
149
+ }): ExplorationTryIt;
150
+ /**
151
+ * The message a designer sends a developer: what the exploration is, the install command for a product staging
152
+ * branch, the previews and the pull request. Plain text, one paragraph, ready to paste in a chat.
153
+ */
154
+ export declare function explorationShareMessage(input: {
155
+ name: string;
156
+ branch: string;
157
+ url: string;
158
+ command?: string | null | undefined;
159
+ previews?: {
160
+ label: string;
161
+ url: string;
162
+ }[] | undefined;
163
+ }): string;
152
164
  /**
153
165
  * The rules Claude follows on an exploration branch (request briefs, docs drafts, token edits with a target branch):
154
166
  * work there, never on main, and never open a pull request to main: the exploration's draft pull request is the place.