@bridge-ds/core 0.20.0 → 0.22.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,50 @@
1
+ import { type DoneExploringPlan, type ExplorationChecks, type ExplorationStatus } from './explorations';
2
+ import { type GitHubClient } from './github-client';
3
+ import { type ProposedPieces } from './prod-branch-github';
4
+ import type { ProductConfig } from './products';
5
+ import type { RequestSource } from './requests';
6
+ export interface DoneExploringInput {
7
+ /** Piece ids the designer ticked: each becomes a request for the DS lead. Unticked ones are not proposed. */
8
+ pieces?: readonly string[] | undefined;
9
+ /** bridge.config.json products, to name the product the pieces are needed by. */
10
+ products?: readonly ProductConfig[] | undefined;
11
+ /** The exploration's preview, linked from each request so the DS lead sees the piece in context. */
12
+ previewUrl?: string | null | undefined;
13
+ /** Who said it, and the AI agent behind it ("claude"). */
14
+ openedBy?: string | undefined;
15
+ agent?: string | undefined;
16
+ source?: RequestSource | undefined;
17
+ date?: Date | undefined;
18
+ dryRun?: boolean | undefined;
19
+ /**
20
+ * The design system repository, when the exploration lives in a product's own repository (`gh` reads that one): the
21
+ * requests for the ticked pieces are filed there, where the DS lead works, linking back to the product pull request.
22
+ */
23
+ library?: GitHubClient | undefined;
24
+ }
25
+ export interface DoneExploring {
26
+ number: number;
27
+ url: string;
28
+ branch: string;
29
+ /** Where it stands after: ready (waiting for code), or what it already was when said again. */
30
+ status: ExplorationStatus;
31
+ /** False when it was already done exploring: only the newly ticked pieces were sent. */
32
+ handedOff: boolean;
33
+ plan: DoneExploringPlan | null;
34
+ pieces: ProposedPieces;
35
+ /** The two commands a developer runs to take it. */
36
+ take: [string, string];
37
+ /** The branch checks at the head developers start from, when GitHub said; null when it could not be read. */
38
+ checks: ExplorationChecks | null;
39
+ /** What developers read next to the commands, true to those checks. */
40
+ note: string;
41
+ /** Steps that did not go through, with what to do on GitHub instead. */
42
+ warnings: string[];
43
+ }
44
+ /**
45
+ * Says "Done exploring" on an exploration: requests for the ticked pieces first, then the note removed, the labels
46
+ * swapped and the body rewritten for developers (with the head they start from, so their pushes read "Being built").
47
+ * Said again on an exploration already handed off, it only sends the newly ticked pieces. Refused on a closed or
48
+ * shipped one. Never closes, merges or marks the draft ready.
49
+ */
50
+ export declare function doneExploringOnGitHub(gh: GitHubClient, ref: string | number, input?: DoneExploringInput): Promise<DoneExploring>;
@@ -0,0 +1,136 @@
1
+ import { type ExplorationStatus } from './explorations';
2
+ /** The phrase Claude recognizes to resume an exploration ("Continue Bridge exploration #12"). */
3
+ export declare const CONTINUE_PHRASE = "Continue Bridge exploration";
4
+ /** True when every changed file is a Bridge note (.bridge/): nothing the product is built from changed yet. */
5
+ export declare const onlyNotes: (files: readonly string[] | null | undefined) => boolean;
6
+ /**
7
+ * Where a running exploration stands: not started (nothing beyond the Bridge notes), exploring (something changed),
8
+ * or handed on (done exploring, being built, shipped, closed, archived: the pages keep their own layout). Files
9
+ * unknown (null): exploring, so a failed read never hides work behind "Nothing built yet".
10
+ */
11
+ export declare function explorationStage(x: {
12
+ status: ExplorationStatus;
13
+ archived?: boolean | null | undefined;
14
+ files: readonly string[] | null | undefined;
15
+ }): 'not-started' | 'exploring' | 'handed';
16
+ export interface ContinueRef {
17
+ number: number;
18
+ name: string;
19
+ /** The product's display name ("Admin demo"). */
20
+ productName: string;
21
+ /** Set when the exploration lives in a product's own repository: its key ("shop") and that repository. */
22
+ product?: string | null | undefined;
23
+ repo?: string | null | undefined;
24
+ }
25
+ /** The ref Claude resolves: "#12", or "shop#12" in a product's own repository. */
26
+ export declare const continueRef: (x: Pick<ContinueRef, "number" | "product" | "repo">) => string;
27
+ /**
28
+ * The two lines a designer pastes to continue: in the Claude app, a sentence; in a terminal, the claude command.
29
+ * No em dash: the line is copied as is.
30
+ */
31
+ export declare function continueLines(x: ContinueRef): {
32
+ app: string;
33
+ terminal: string;
34
+ };
35
+ /** Reads "Continue Bridge exploration #12" or "... shop#12" back into a ref ("#12", "shop#12"); null otherwise. */
36
+ export declare function parseContinueLine(text: string): string | null;
37
+ /** "BulkActionsBar" -> "Bulk actions bar", "DSBridgePanel" -> "DS bridge panel". */
38
+ export declare function wordsOf(name: string): string;
39
+ /**
40
+ * What one file is, for a designer: a component ("Bulk actions bar"), a screen ("Orders screen"), the tokens
41
+ * ("Tokens"), or null when it says nothing to them (helpers, config, tests, notes).
42
+ */
43
+ export declare function thingOf(path: string): string | null;
44
+ /** The distinct things the files are, in file order. */
45
+ export declare function changedThings(files: readonly string[]): string[];
46
+ /** "A", "A and B", "A, B and 3 more". */
47
+ export declare function listThings(things: readonly string[], max?: number): string;
48
+ /** A commit on the branch, as GitHub lists them (GET /pulls/{n}/commits). */
49
+ export interface BranchCommitLike {
50
+ sha: string;
51
+ commit: {
52
+ message: string;
53
+ author?: {
54
+ name?: string | null;
55
+ date?: string | null;
56
+ } | null;
57
+ committer?: {
58
+ date?: string | null;
59
+ } | null;
60
+ };
61
+ author?: {
62
+ login?: string | null;
63
+ } | null;
64
+ }
65
+ export interface ActivityEvent {
66
+ /** "claude" for Claude's work, else the person's login (or git name). */
67
+ who: string;
68
+ /** Claude alone: a Claude account, or Claude's trailer with no person as the author (a bot or none). */
69
+ claude: boolean;
70
+ /** A person's commit Claude co-wrote (its co-author trailer or footer): shown as "Florian with Claude". */
71
+ withClaude?: boolean;
72
+ /** After the actor: "added row selection to the Orders table". */
73
+ text: string;
74
+ at: string;
75
+ sha?: string;
76
+ }
77
+ /** True when the commit carries Claude's co-author trailer or the Claude Code footer. */
78
+ export declare function isClaudeAssisted(c: BranchCommitLike): boolean;
79
+ /** The person who authored the commit (GitHub login, else git name), or null for a bot, Claude or nobody. */
80
+ export declare function humanAuthorOf(c: BranchCommitLike): string | null;
81
+ /**
82
+ * True when Claude alone wrote the commit: a Claude account, or Claude's trailer on a commit no person authored (a
83
+ * bot, or no author). A person's commit with Claude's trailer is theirs, made with Claude (isClaudeAssisted).
84
+ */
85
+ export declare function isClaudeCommit(c: BranchCommitLike): boolean;
86
+ /** True for the commit Bridge writes when it starts an exploration (only the note file). */
87
+ export declare const isStartCommit: (c: BranchCommitLike) => boolean;
88
+ /**
89
+ * A commit subject in words after the actor: "feat(orders): add row selection" -> "added row selection". Conventional
90
+ * prefixes go, an imperative first verb turns past, em dashes become commas. Other subjects: "changed things: <subject>".
91
+ */
92
+ export declare function subjectWords(subject: string): string;
93
+ /**
94
+ * The branch's activity, newest first: one event per commit (Bridge's start commit left out), then "started the
95
+ * exploration". `limit` keeps the newest ones, the start event included when it fits.
96
+ */
97
+ export declare function explorationActivity(input: {
98
+ commits: readonly BranchCommitLike[];
99
+ author: string;
100
+ startedAt?: string | null | undefined;
101
+ limit?: number;
102
+ }): ActivityEvent[];
103
+ /**
104
+ * The line under the title once something changed: who changed what, from the branch files when they name screens
105
+ * or components ("changed Orders table and Bulk actions bar"), else the latest commit in words. Null before any change.
106
+ */
107
+ export declare function lastActivity(input: {
108
+ commits: readonly BranchCommitLike[];
109
+ files: readonly string[] | null | undefined;
110
+ author: string;
111
+ }): {
112
+ who: string;
113
+ claude: boolean;
114
+ withClaude?: boolean;
115
+ text: string;
116
+ at: string;
117
+ } | null;
118
+ /** "just now", "5 min ago", "2 h ago", "Yesterday", "3 days ago", then the date ("Oct 12"). */
119
+ export declare function shortAgo(iso: string, now?: Date): string;
120
+ /** "today", "yesterday", "3 days ago", "Oct 12": when it was started, after "Started by Ann · Admin demo ·". */
121
+ export declare function startedWhen(iso: string, now?: Date): string;
122
+ /** The actor in words: "Claude", "Florian", or "Florian with Claude" when Claude co-wrote a person's change. */
123
+ export declare function actorName(e: Pick<ActivityEvent, 'who' | 'claude' | 'withClaude'>, names?: Record<string, string>): string;
124
+ /**
125
+ * Where it stands, in one or two plain sentences, for Claude to say first when the designer continues it. No
126
+ * technical words: no branch, commit or pull request.
127
+ */
128
+ export declare function whereItStands(input: {
129
+ name: string;
130
+ stage: 'not-started' | 'exploring' | 'handed';
131
+ statusLabel?: string;
132
+ events: readonly ActivityEvent[];
133
+ files: readonly string[] | null | undefined;
134
+ names?: Record<string, string>;
135
+ now?: Date;
136
+ }): string;
@@ -1,7 +1,8 @@
1
1
  import type { BridgeConfig } from './config';
2
- import { type Exploration, type ExplorationSource, type ExplorationStatus, type ExplorationTryIt, type PromotionPlan } from './explorations';
2
+ import { type Exploration, type ExplorationSource, type ExplorationStatus, type ExplorationPrContext, type ExplorationTryIt, type PromotionPlan } from './explorations';
3
3
  import { type GitHubClient, type PullRequestSummary } from './github-client';
4
4
  import { type BranchPreview, type PreviewApps } from './previews';
5
+ import { type ProductConfig } from './products';
5
6
  export interface ExplorationSettings {
6
7
  /** Branch explorations start from and pull requests target (the default branch). */
7
8
  base: string;
@@ -16,7 +17,11 @@ export interface ExplorationSettings {
16
17
  }[] | undefined;
17
18
  /** More labels for the draft pull request, such as the product ones (productMarks). */
18
19
  labels?: string[] | undefined;
20
+ /** Started in a product's own repository (productRepoOf): what its draft pull request body says instead of the library's. */
21
+ inProduct?: ExplorationPrContext['inProduct'];
19
22
  }
23
+ /** The first commit message of an exploration branch: the purpose only when one was given. */
24
+ export declare const explorationStartMessage: (e: Pick<Exploration, "name" | "purpose">) => string;
20
25
  export interface ExplorationSummary {
21
26
  number: number;
22
27
  url: string;
@@ -26,6 +31,8 @@ export interface ExplorationSummary {
26
31
  purpose: string;
27
32
  useCase?: string | undefined;
28
33
  status: ExplorationStatus;
34
+ /** Archived (the `archived` label): hidden from the default lists, its pull request and preview still open. */
35
+ archived: boolean;
29
36
  draft: boolean;
30
37
  author: string;
31
38
  /** Where it was started and the AI agent that started it, when the exploration block says so. */
@@ -43,13 +50,49 @@ export interface ExplorationSummary {
43
50
  label: string;
44
51
  url: string;
45
52
  }[];
53
+ /**
54
+ * Set when the exploration lives in a product's own repository (productRepoOf): that repository ("owner/name") and
55
+ * the product key. Tools name it "<product>#<number>" (explorationRefLabel); absent in the design system repository.
56
+ */
57
+ repo?: string | undefined;
58
+ product?: string | undefined;
46
59
  }
47
60
  /** One exploration from a pull request summary. */
48
61
  export declare function explorationSummary(p: PullRequestSummary, _owner: string, _repo: string, s: Pick<ExplorationSettings, 'packages' | 'apps'>): ExplorationSummary;
49
- /** Explorations of the repo, newest activity first. `state: 'open'` lists the running ones and the promoted open ones. */
62
+ /**
63
+ * Explorations of the repo, newest activity first. `state: 'open'` lists the running ones and the open ones handed to
64
+ * developers. Archived ones are left out unless `archived` is 'include' (all) or 'only' (the Archived filter).
65
+ */
50
66
  export declare function listExplorationsOnGitHub(gh: GitHubClient, s: Pick<ExplorationSettings, 'packages' | 'apps'>, opts?: {
51
67
  state?: 'open' | 'closed' | 'all';
68
+ archived?: 'exclude' | 'include' | 'only';
52
69
  }): Promise<ExplorationSummary[]>;
70
+ /** A product repository Bridge could not read while listing: left out of the lists, said only where connections live. */
71
+ export interface UnreachableProductRepo {
72
+ product: string;
73
+ name: string;
74
+ repo: string;
75
+ /** not-connected: GitHub hides it (Bridge not installed there, or the token does not reach it); unavailable: any other failure. */
76
+ reason: 'not-connected' | 'unavailable';
77
+ }
78
+ /** GitHub hides the repository from this token, or Bridge's proxy says its app is not there. */
79
+ export declare const isHiddenRepoError: (err: unknown) => boolean;
80
+ /**
81
+ * The explorations of every product that has its own repository (bridge.config.json products[].repo): one read of
82
+ * the open (or `state`) pull requests per repository, all in parallel. Each one carries its `repo` and `product`, and
83
+ * the product's preview template as its preview. A repository Bridge cannot read is skipped and reported in
84
+ * `unreachable`, never thrown: designers never see it in a list, Settings says it. `clientFor` gives a client for
85
+ * "owner/name" with the same token (or proxy) as the design system repository.
86
+ */
87
+ export declare function listProductExplorationsOnGitHub(clientFor: (repo: string) => GitHubClient, products: readonly ProductConfig[], designSystemRepo: string | null | undefined, opts?: {
88
+ state?: 'open' | 'closed' | 'all';
89
+ archived?: 'exclude' | 'include' | 'only';
90
+ }): Promise<{
91
+ explorations: ExplorationSummary[];
92
+ unreachable: UnreachableProductRepo[];
93
+ }>;
94
+ /** An exploration read in a product's own repository: its repository, its product and the product's preview. */
95
+ export declare function inProductRepo(x: ExplorationSummary, product: ProductConfig, repo: string): ExplorationSummary;
53
96
  export interface StartedExploration {
54
97
  branch: string;
55
98
  slug: string;
@@ -70,6 +113,20 @@ export interface StartedExploration {
70
113
  * install command. Refuses an exploration whose branch already exists.
71
114
  */
72
115
  export declare function startExplorationOnGitHub(gh: GitHubClient, e: Exploration, s: ExplorationSettings): Promise<StartedExploration>;
116
+ /** Whether Bridge can open an exploration in a product's own repository with this token, and from which branch. */
117
+ export type ProductRepoAccess = {
118
+ ok: true;
119
+ base: string;
120
+ } | {
121
+ ok: false;
122
+ reason: 'not-connected' | 'read-only';
123
+ };
124
+ /**
125
+ * One read of a product's own repository before an exploration starts there: its default branch when the token may
126
+ * push to it; `not-connected` when GitHub hides it (Bridge is not installed on it, or the token does not reach it);
127
+ * `read-only` when the person may only read it. A rejected token or a GitHub outage still throws.
128
+ */
129
+ export declare function productRepoAccess(gh: GitHubClient): Promise<ProductRepoAccess>;
73
130
  /**
74
131
  * Where the test build of an exploration stands: the install command from its sticky preview comment when it matches
75
132
  * the branch head, "building" while the workflow has not caught up, "failed" when the publish failed. Two reads.
@@ -116,7 +173,22 @@ export declare function promoteExplorationOnGitHub(gh: GitHubClient, ref: string
116
173
  date?: Date | undefined;
117
174
  dryRun?: boolean | undefined;
118
175
  }): Promise<PromotedExploration>;
119
- /** Closes an exploration without merging. The branch stays for reference; the workflow removes the prerelease. */
176
+ /**
177
+ * Archives an exploration (the `archived` label), or brings it back with `archived: false`. The pull request stays
178
+ * open, so its preview link keeps working: archiving only hides it from the default lists.
179
+ */
180
+ export declare function archiveExplorationOnGitHub(gh: GitHubClient, ref: string | number, opts?: {
181
+ archived?: boolean;
182
+ }): Promise<{
183
+ number: number;
184
+ url: string;
185
+ branch: string;
186
+ archived: boolean;
187
+ }>;
188
+ /**
189
+ * Closes an exploration without merging. The branch stays for reference, but the workflows remove its preview
190
+ * prerelease and visual images: archiving (archiveExplorationOnGitHub) keeps the link alive instead.
191
+ */
120
192
  export declare function closeExplorationOnGitHub(gh: GitHubClient, ref: string | number): Promise<{
121
193
  number: number;
122
194
  url: string;
@@ -11,8 +11,17 @@ export declare const EXPLORATION_NOTES_DIR = ".bridge/explorations";
11
11
  export declare const EXPLORATION_TIER_LABEL = "Exploration (not mergeable)";
12
12
  /** Name of the CI job that fails while the label is on. */
13
13
  export declare const EXPLORATION_CHECK = "Exploration not merged as is";
14
+ /** Done exploring: the exploration label gives way to this one. Developers take the branch from there. */
15
+ export declare const EXPLORATION_READY_LABEL = "ready-for-devs";
16
+ /** An archived exploration: hidden from the default lists, its pull request and preview still open. */
17
+ export declare const EXPLORATION_ARCHIVED_LABEL = "archived";
14
18
  export type ExplorationUseCase = 'user-test' | 'stakeholder-demo' | 'ab-idea' | 'terminal' | 'other';
15
- export type ExplorationStatus = 'active' | 'promoted' | 'closed';
19
+ /**
20
+ * active: running (the exploration label is on). ready: done exploring, waiting for developers to take it. building:
21
+ * developers pushed to it (or marked it ready for review) since. shipped: merged after done exploring. promoted: sent
22
+ * to review the older way (label removed without done exploring), open or merged. closed: closed without merging.
23
+ */
24
+ export type ExplorationStatus = 'active' | 'ready' | 'building' | 'shipped' | 'promoted' | 'closed';
16
25
  /** Where an exploration was started: Bridge Studio, the Figma plugin, the CLI or the MCP server (an AI agent). */
17
26
  export type ExplorationSource = 'studio' | 'figma' | 'cli' | 'mcp';
18
27
  export declare const EXPLORATION_USE_CASES: {
@@ -24,7 +33,10 @@ export declare const EXPLORATION_STATUS_LABEL: Record<ExplorationStatus, string>
24
33
  export interface Exploration {
25
34
  /** Display name, sentence case: "Denser data tables". */
26
35
  name: string;
27
- /** What the team wants to learn, in one or two sentences. */
36
+ /**
37
+ * What the team wants to learn, in one or two sentences, or empty: designers explore freely and nobody is asked
38
+ * for a brief (Claude passes the designer's first sentence when it has one).
39
+ */
28
40
  purpose: string;
29
41
  /** Branch slug: `explore/<slug>`. Derived from the name when missing. */
30
42
  slug?: string | undefined;
@@ -41,8 +53,14 @@ export interface Exploration {
41
53
  agent?: string | undefined;
42
54
  /** A Jira ticket key ("DS-42"), first in the pull request title so Jira's GitHub integration links it. */
43
55
  jira?: string | undefined;
56
+ /** ISO date someone said "Done exploring". */
57
+ doneAt?: string | undefined;
58
+ /** The branch head at that moment: a newer head means developers are building on it. */
59
+ doneSha?: string | undefined;
44
60
  }
45
- /** "Denser data tables!" -> "denser-data-tables". Up to 40 characters, cut at a word boundary. Empty when nothing is left. */
61
+ /** Longest slug: a name is under 60 characters (explorationProblems), so a valid name is never cut mid-phrase. */
62
+ export declare const EXPLORATION_SLUG_MAX = 60;
63
+ /** "Denser data tables!" -> "denser-data-tables". Up to 60 characters, cut at a word boundary. Empty when nothing is left. */
46
64
  export declare function explorationSlug(name: string): string;
47
65
  /** `explore/<slug>` for a name, a slug or a branch name. */
48
66
  export declare function explorationBranch(nameOrBranch: string): string;
@@ -50,10 +68,35 @@ export declare function explorationBranch(nameOrBranch: string): string;
50
68
  export declare function isExplorationBranch(ref: string | null | undefined): boolean;
51
69
  /** The slug of an exploration branch, or null. */
52
70
  export declare function explorationSlugFromBranch(ref: string): string | null;
71
+ /**
72
+ * Which exploration a person or a tool means, wherever it lives. In the design system repository: its number ("12",
73
+ * "#12") or its name ("pill-buttons", "explore/pill-buttons"). In a product's own repository, the product key or the
74
+ * repository goes first: "shop#12", "peterbono/bridge-demo-shop#12", "shop:checkout-flow", or the pull request link.
75
+ */
76
+ export interface ExplorationRef {
77
+ /** A product key ("shop"), when the reference names one. */
78
+ product?: string | undefined;
79
+ /** "owner/name", when the reference names the repository (a link or owner/name#12). */
80
+ repo?: string | undefined;
81
+ /** The pull request number, or the exploration's name or branch. */
82
+ ref: number | string;
83
+ }
84
+ /** Reads an exploration reference (ExplorationRef). Anything else is taken as a name in the design system repository. */
85
+ export declare function parseExplorationRef(input: string | number): ExplorationRef;
86
+ /** How tools name an exploration back: "#12" in the design system repository, "shop#12" in a product's own one. */
87
+ export declare const explorationRefLabel: (x: {
88
+ number: number;
89
+ product?: string | null | undefined;
90
+ repo?: string | null | undefined;
91
+ }) => string;
53
92
  /** Path of the note that starts an exploration branch. */
54
93
  export declare const explorationNotePath: (slug: string) => string;
55
- /** Problems with a new exploration, as sentences that say how to fix them. Empty when it can start. */
56
- export declare function explorationProblems(e: Pick<Exploration, 'name' | 'purpose'> & {
94
+ /**
95
+ * Problems with a new exploration, as sentences that say how to fix them. Empty when it can start. Only the name is
96
+ * needed: the purpose is optional, nobody has to write a brief to explore.
97
+ */
98
+ export declare function explorationProblems(e: Pick<Exploration, 'name'> & {
99
+ purpose?: string | undefined;
57
100
  slug?: string | undefined;
58
101
  }): string[];
59
102
  /** The note committed as the first commit of the branch (`.bridge/explorations/<slug>.md`). */
@@ -70,12 +113,26 @@ export interface ExplorationPrContext {
70
113
  number: number;
71
114
  title: string;
72
115
  }[] | undefined;
116
+ /**
117
+ * Set when the exploration lives in a product's own repository (productRepoOf): the body says to check the branch
118
+ * out there and to get the library from npm (`library`: the design system repository), with the product's preview
119
+ * template instead of the library's apps.
120
+ */
121
+ inProduct?: {
122
+ name: string;
123
+ library: string;
124
+ preview?: string | undefined;
125
+ } | undefined;
73
126
  }
74
- /** Draft pull request title: "Exploration: denser data tables". */
127
+ /**
128
+ * Draft pull request title: "Exploration: Denser data tables". The name keeps its casing as typed; a name that already
129
+ * starts with "Exploration:" is not prefixed twice, and a colon inside the name becomes a comma so the title has one
130
+ * ("Exploration page: pick up" -> "Exploration: Exploration page, pick up").
131
+ */
75
132
  export declare function explorationPrTitle(e: Pick<Exploration, 'name'>): string;
76
133
  /**
77
134
  * The draft pull request body: purpose, what is experimental, how a product staging branch installs it, the
78
- * previews, and how to promote or close it. Markdown for people plus one hidden JSON block for tools.
135
+ * previews, and how it ends (Done exploring, Archive). Markdown for people plus one hidden JSON block for tools.
79
136
  */
80
137
  export declare function explorationPrBody(e: Exploration, ctx: ExplorationPrContext): string;
81
138
  /** The exploration inside a pull request body, or undefined when it has no valid Bridge block. */
@@ -89,16 +146,88 @@ export interface ExplorationPullLike {
89
146
  })[];
90
147
  headRef?: string | undefined;
91
148
  body?: string | null | undefined;
149
+ /** Head commit and draft flag: after "Done exploring", a newer head or a ready pull request reads "Being built". */
150
+ headSha?: string | undefined;
151
+ draft?: boolean | undefined;
92
152
  }
93
153
  /** True for a pull request that is or was an exploration: an `explore/*` branch, the label, or the Bridge block. */
94
154
  export declare function isExplorationPull(p: ExplorationPullLike): boolean;
155
+ /** "Done exploring" was said: the `ready-for-devs` label, or the date in the exploration block. */
156
+ export declare function isHandedOff(p: Pick<ExplorationPullLike, 'labels' | 'body'>): boolean;
95
157
  /**
96
158
  * Where an exploration stands: closed without merging = closed; open with the `exploration` label = active
97
- * (running); otherwise promoted (the label was removed, so it is a normal pull request now, open or merged).
159
+ * (running). After "Done exploring": merged = shipped; marked ready for review, or a head newer than the one done
160
+ * exploring left = building (developers work on it); otherwise ready (waiting for code). The label removed without
161
+ * done exploring is the older promotion: promoted, open or merged.
98
162
  */
99
163
  export declare function explorationStatus(p: ExplorationPullLike): ExplorationStatus;
100
164
  /** True while the label is on: the pull request must not merge (the CI check fails, Studio blocks the merge). */
101
165
  export declare const isActiveExploration: (labels: ExplorationPullLike["labels"]) => boolean;
166
+ /** True for an archived exploration: hidden from the default lists, reachable from the Archived filter. */
167
+ export declare const isArchivedExploration: (labels: readonly (string | {
168
+ name?: string;
169
+ })[] | null | undefined) => boolean;
170
+ /**
171
+ * Open explorations that stay out of the review queue: running, archived, or handed to developers while still a
172
+ * draft. Once developers mark it ready for review it is a change like any other.
173
+ */
174
+ export declare function isHeldExploration(p: {
175
+ state?: string | undefined;
176
+ draft?: boolean | undefined;
177
+ labels?: readonly (string | {
178
+ name?: string;
179
+ })[] | null | undefined;
180
+ }): boolean;
181
+ /** How a developer takes an exploration: fetch, then check out its branch. */
182
+ export declare const explorationTakeCommands: (branch: string) => [string, string];
183
+ /** What developers read next to those commands, when nothing is known about the branch checks: a starting point. */
184
+ export declare const EXPLORATION_STARTING_POINT = "A starting point, not ready to ship: nobody has reviewed Claude's prototype code yet. Continue on this branch: its pull request already exists, so Bridge follows your pushes, and the merge reads as shipped.";
185
+ /** The branch checks at its head, folded into one state (GitHubClient.getChecks). */
186
+ export type ExplorationChecks = 'success' | 'failure' | 'pending' | 'none';
187
+ /**
188
+ * The starting point note, true to the branch checks: passing checks are said as such (the code is still not
189
+ * reviewed), failing or missing checks too. Without a state it is EXPLORATION_STARTING_POINT.
190
+ */
191
+ export declare function explorationStartingPoint(checks?: ExplorationChecks | null | undefined): string;
192
+ export interface DoneExploringPlan {
193
+ /** Labels to remove (the exploration label) and to add (ready-for-devs). */
194
+ removeLabels: string[];
195
+ addLabels: string[];
196
+ /** Files to delete on the branch first: the exploration note, so main never gets it. */
197
+ removeFiles: string[];
198
+ /** The body once done: the developer section in place of the sandbox note and the "When it is done" part. */
199
+ body: string;
200
+ /** The two commands a developer runs to take it. */
201
+ take: [string, string];
202
+ /** Done exploring never merges, closes or marks the draft ready. */
203
+ markReady: false;
204
+ }
205
+ /**
206
+ * What "Done exploring" does to the pull request: the exploration label gives way to `ready-for-devs` (the merge
207
+ * guard stops failing, so developers can ship it once the code is theirs), the exploration note leaves the branch, and
208
+ * the body says how developers take it. The draft stays a draft and the pull request stays open, so its preview lives
209
+ * on. The caller records the head once the note is gone (withDoneExploring with `doneSha`).
210
+ */
211
+ export declare function doneExploringPlan(input: {
212
+ body: string | null | undefined;
213
+ branch: string;
214
+ files: {
215
+ path: string;
216
+ status?: string | undefined;
217
+ }[];
218
+ date?: Date | undefined;
219
+ }): DoneExploringPlan;
220
+ /**
221
+ * The body after "Done exploring": the sandbox warning and the "When it is done" part give way to the developer
222
+ * section (the two commands and the starting point note), and the hidden block records when, and on which head once
223
+ * known. Calling it again replaces the section and the block.
224
+ */
225
+ export declare function withDoneExploring(body: string | null | undefined, done: {
226
+ branch: string;
227
+ doneAt: string;
228
+ doneSha?: string | undefined;
229
+ checks?: ExplorationChecks | null | undefined;
230
+ }): string;
102
231
  export interface PromotionPlan {
103
232
  tier: RiskTier;
104
233
  reasons: string[];
@@ -68,7 +68,7 @@ export interface GapCaseFor {
68
68
  }
69
69
  /** The gap case of one component, built the way the plugin pulls it: fixes applied, set rebuilt, the run's verdicts. */
70
70
  export declare function gapCaseFor(input: GapCaseForInput): GapCaseFor;
71
- export declare const FIX_SCHEMA_SUMMARY = "figma-fixes.json fixes (version 1). Each fix is one JSON object:\n- Every fix: \"id\" (lower case, digits, dashes, unique), \"kind\", \"component\" (the Figma set, e.g. \"TextInput\"), \"reason\" (one plain sentence, no em dash), optional \"by\": \"claude\", optional \"where\": { \"args\": { \"<arg>\": value }, \"state\": \"rest\" | \"hover\" | \"active\" | \"focus-visible\" | \"disabled\" } (absent: every variant).\n- kind \"part\": \"part\" is a part id from the gap case (\"root\", \"cds--text-input-label-wrapper\") or \".class\". At least one of: \"set\" { field: token path | px number | null (colors only) | \"full\" (cornerRadius only) }, \"width\" / \"height\": \"fixed\" (the measured size) | px number | \"hug\" | \"fill\", \"direction\": \"HORIZONTAL\" | \"VERTICAL\", \"align\": { \"primary\": MIN|CENTER|MAX|SPACE_BETWEEN, \"counter\": MIN|CENTER|MAX|STRETCH }, \"overlay\": true | false | { \"top\": 3, \"left\": 3 }, \"hide\": true (alone; never the root). Fields: fill, stroke, strokeWeight, strokeTopWeight, strokeRightWeight, strokeBottomWeight, strokeLeftWeight, cornerRadius, paddingTop, paddingRight, paddingBottom, paddingLeft, itemSpacing, counterAxisSpacing, width, height, minWidth, minHeight, opacity, textFill, fontFamily, fontSize, fontWeight, lineHeight, letterSpacing, textDecoration.\n- kind \"axis\": \"action\" \"map\" (\"arg\", \"property\" and/or \"values\" { code value: Figma value }), \"ignore\" (\"arg\"), or \"prune\" (\"where\" names the variants to drop).\n- kind \"story\": \"where\" names the variant, \"use\": { \"story\": \"<story id>\", \"args\"?, \"state\"? } is the measured render to draw it from.\n- kind \"status\": \"status\": \"differs\" | \"cant-check\" with the reason; never \"matches\" (only the shots say a component matches). Without \"status\": a note.\nRules: fix only the component you were given; prefer the smallest fix that explains the differences (a part's size \"fixed\" when Figma hugs what code fixes, a missing padding, a hidden part); never invent a part id or a token: use the ones in the gap case.";
71
+ export declare const FIX_SCHEMA_SUMMARY = "figma-fixes.json fixes (version 1). Each fix is one JSON object:\n- Every fix: \"id\" (lower case, digits, dashes, unique), \"kind\", \"component\" (the Figma set, e.g. \"TextInput\"), \"reason\" (one plain sentence, no em dash), optional \"by\": \"claude\", optional \"where\": { \"args\": { \"<arg>\": value }, \"state\": \"rest\" | \"hover\" | \"active\" | \"focus-visible\" | \"disabled\" } (absent: every variant).\n- kind \"part\": \"part\" is a part id from the gap case (\"root\", \"cds--text-input-label-wrapper\") or \".class\". At least one of: \"set\" { field: token path | px number | null (colors only) | \"full\" (cornerRadius only) }, \"width\" / \"height\": \"fixed\" (each story's own measured size; a width that only hugs text in code is skipped, since it is the story's words) | px number | \"hug\" | \"fill\", \"direction\": \"HORIZONTAL\" | \"VERTICAL\", \"align\": { \"primary\": MIN|CENTER|MAX|SPACE_BETWEEN, \"counter\": MIN|CENTER|MAX|STRETCH }, \"overlay\": true | false | { \"top\": 3, \"left\": 3 }, \"hide\": true (alone; never the root). Fields: fill, stroke, strokeWeight, strokeTopWeight, strokeRightWeight, strokeBottomWeight, strokeLeftWeight, cornerRadius, paddingTop, paddingRight, paddingBottom, paddingLeft, itemSpacing, counterAxisSpacing, width, height, minWidth, minHeight, opacity, textFill, fontFamily, fontSize, fontWeight, lineHeight, letterSpacing, textDecoration.\n- kind \"axis\": \"action\" \"map\" (\"arg\", \"property\" and/or \"values\" { code value: Figma value }), \"ignore\" (\"arg\"), or \"prune\" (\"where\" names the variants to drop).\n- kind \"story\": \"where\" names the variant, \"use\": { \"story\": \"<story id>\", \"args\"?, \"state\"? } is the measured render to draw it from.\n- kind \"status\": \"status\": \"differs\" | \"cant-check\" with the reason; never \"matches\" (only the shots say a component matches). Without \"status\": a note.\nRules: fix only the component you were given; prefer the smallest fix that explains the differences (a part's size \"fixed\" when Figma hugs what code fixes, a missing padding, a hidden part); never invent a part id or a token: use the ones in the gap case.";
72
72
  export type FixDiffResult = 'resolved' | 'closer' | 'same' | 'worse' | 'unknown';
73
73
  export type FixVerdict = 'closer' | 'same' | 'worse';
74
74
  export interface TriedDifference {
@@ -134,8 +134,12 @@ export interface AppliedFix {
134
134
  id: string;
135
135
  kind: FigmaFix['kind'];
136
136
  component: string;
137
- /** applied: it changed what the pull draws. not-found: nothing matched (the code or the measure moved: re-check it). already: the input had it. */
138
- result: 'applied' | 'not-found' | 'already';
137
+ /**
138
+ * applied: it changed what the pull draws. not-found: nothing matched (the code or the measure moved: re-check it).
139
+ * skipped: it matched, but the measure has no size of the part's own to pin there (the detail says why). already:
140
+ * the input had it.
141
+ */
142
+ result: 'applied' | 'not-found' | 'skipped' | 'already';
139
143
  /** Story entries it changed. */
140
144
  stories: number;
141
145
  /** Plain words: what it did, or what it did not find. */
@@ -152,6 +156,14 @@ export type FixedMeasuredSpecsFile = Omit<MeasuredSpecsFile, 'stories' | 'compon
152
156
  };
153
157
  /** A story is one of `where`'s variants: every arg it names, at that value, and the state. */
154
158
  export declare function storyMatchesWhere(s: MeasuredSpecsStory, where: FixWhere | undefined): boolean;
159
+ /** Why a `fixed` size was not laid on one part of one story: the measure has no size of the part's own there. */
160
+ export interface SkippedSize {
161
+ part: string;
162
+ side: 'width' | 'height';
163
+ why: 'words' | 'fills' | 'unmeasured';
164
+ /** The measured px it did not pin (words, fills). */
165
+ measured?: number | undefined;
166
+ }
155
167
  /**
156
168
  * The measured specs the pull reads, with the fixes laid on, and one provenance record per fix. Pure: the input is not
157
169
  * changed. Fixes run in file order; a fix whose component, part or story is not in this measure is reported
@@ -638,6 +638,23 @@ export declare class GitHubClient {
638
638
  autoMerge: boolean;
639
639
  }>;
640
640
  listPullRequestFiles(n: number): Promise<PullRequestFile[]>;
641
+ /** The commits of a pull request, oldest first, as GitHub lists them (one read, up to 100). */
642
+ listPullRequestCommits(n: number): Promise<{
643
+ sha: string;
644
+ commit: {
645
+ message: string;
646
+ author?: {
647
+ name?: string | null;
648
+ date?: string | null;
649
+ } | null;
650
+ committer?: {
651
+ date?: string | null;
652
+ } | null;
653
+ };
654
+ author?: {
655
+ login?: string | null;
656
+ } | null;
657
+ }[]>;
641
658
  updatePullRequest(n: number, patch: {
642
659
  title?: string;
643
660
  body?: string;
package/dist/index.d.ts CHANGED
@@ -41,6 +41,8 @@ export * from './change-drafts';
41
41
  export * from './draft-exchange';
42
42
  export * from './explorations';
43
43
  export * from './explorations-github';
44
+ export * from './exploration-done-github';
45
+ export * from './exploration-resume';
44
46
  export * from './requests-github';
45
47
  export * from './component-spec';
46
48
  export * from './measured-spec';