@bridge-ds/core 0.9.0 → 0.10.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/README.md +27 -1
- package/bridge.config.schema.json +23 -0
- package/dist/audit/index.js +3 -3
- package/dist/config.d.ts +28 -0
- package/dist/docs-proposal.d.ts +58 -10
- package/dist/figma-push-github.d.ts +143 -0
- package/dist/figma-push.d.ts +204 -0
- package/dist/github-client.d.ts +6 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +998 -2861
- package/dist/index.js.map +1 -1
- package/dist/node/git-local.d.ts +15 -0
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +201 -10
- package/dist/node/index.js.map +1 -1
- package/dist/node/suggestion-apply-node.d.ts +69 -0
- package/dist/{pr-composer-D2SZm7cg.js → prod-branch-BdMPKffE.js} +491 -173
- package/dist/prod-branch-BdMPKffE.js.map +1 -0
- package/dist/prod-branch-github.d.ts +28 -7
- package/dist/prod-branch.d.ts +17 -7
- package/dist/{report-DY_peRy4.js → report-DOnKUw8G.js} +2 -2
- package/dist/{report-DY_peRy4.js.map → report-DOnKUw8G.js.map} +1 -1
- package/dist/suggestion-apply-ByBEYtct.js +4802 -0
- package/dist/suggestion-apply-ByBEYtct.js.map +1 -0
- package/dist/suggestion-apply.d.ts +150 -0
- package/dist/suggestion-review.d.ts +70 -0
- package/dist/suggestions-github.d.ts +131 -0
- package/dist/suggestions.d.ts +236 -0
- package/dist/token-proposal.d.ts +51 -0
- package/package.json +1 -1
- package/dist/component-spec-BzfBkYRD.js +0 -1122
- package/dist/component-spec-BzfBkYRD.js.map +0 -1
- package/dist/pr-composer-D2SZm7cg.js.map +0 -1
- package/dist/prod-branch-D5Tl87Wf.js +0 -266
- package/dist/prod-branch-D5Tl87Wf.js.map +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { BridgeConfig } from './config';
|
|
2
|
+
import { type LayerIssue } from './layered';
|
|
3
|
+
import { type ContrastDelta, type LayeredChange } from './layered-review';
|
|
4
|
+
import { type ApprovalRole } from './review-gate';
|
|
5
|
+
import { type RiskResult, type RiskTier } from './risk';
|
|
6
|
+
import { type SuggestionApprovals, type SuggestionCommentLike, type SuggestionGovernance } from './suggestion-review';
|
|
7
|
+
import { type ParsedSuggestion, type SuggestionPayloadV1 } from './suggestions';
|
|
8
|
+
import { type TokenEdit } from './token-edits';
|
|
9
|
+
/** What the planner reads. Paths are repo-relative and already checked by suggestionPathProblems. */
|
|
10
|
+
export interface SuggestionWorkspace {
|
|
11
|
+
/** The file as it is now (text and git blob sha), or null when it does not exist. */
|
|
12
|
+
read(path: string): Promise<{
|
|
13
|
+
text: string;
|
|
14
|
+
sha: string;
|
|
15
|
+
} | null>;
|
|
16
|
+
/** The file at a commit (the payload's base sha), or null when it is missing there or the commit is unknown. */
|
|
17
|
+
readAt(ref: string, path: string): Promise<string | null>;
|
|
18
|
+
/** Every file path now, repo-relative (used to find the token files). */
|
|
19
|
+
list(): Promise<string[]>;
|
|
20
|
+
/** True when the path, or a folder on the way to it, is a symbolic link. Absent: no symlinks (a browser). */
|
|
21
|
+
isSymlink?(path: string): Promise<boolean>;
|
|
22
|
+
/** 3-way merge of a text file. Absent: a file changed on both sides is a conflict. */
|
|
23
|
+
merge?(input: {
|
|
24
|
+
path: string;
|
|
25
|
+
ours: string;
|
|
26
|
+
base: string;
|
|
27
|
+
theirs: string;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
clean: boolean;
|
|
30
|
+
text: string;
|
|
31
|
+
conflicts: number;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
export type SuggestionApplyOpState = 'already-applied' | 'apply' | 'apply-merged' | 'conflict';
|
|
35
|
+
/** One change of the suggestion against the current tree. */
|
|
36
|
+
export interface SuggestionApplyOp {
|
|
37
|
+
type: 'token' | 'file' | 'docs' | 'component-spec';
|
|
38
|
+
/** Token path for token edits, repo path otherwise. */
|
|
39
|
+
path: string;
|
|
40
|
+
/** Token edits: the edit as suggested. */
|
|
41
|
+
edit?: TokenEdit | undefined;
|
|
42
|
+
/** Token file the edit targets, relative to tokens.root, when known. */
|
|
43
|
+
file?: string | undefined;
|
|
44
|
+
state: SuggestionApplyOpState;
|
|
45
|
+
/** One sentence: what happens or why it conflicts. */
|
|
46
|
+
detail: string;
|
|
47
|
+
/** Conflicts: the value when suggested, the suggested value, and the value now. */
|
|
48
|
+
before?: unknown;
|
|
49
|
+
after?: unknown;
|
|
50
|
+
now?: unknown;
|
|
51
|
+
/** 3-way merge conflicts: how many conflict regions git found. */
|
|
52
|
+
hunks?: number | undefined;
|
|
53
|
+
}
|
|
54
|
+
export type SuggestionApplyOutcome =
|
|
55
|
+
/** The checks pass and every change can be written. */
|
|
56
|
+
'ready'
|
|
57
|
+
/** Every change is already in the tree: nothing to write. */
|
|
58
|
+
| 'already-applied'
|
|
59
|
+
/** At least one change conflicts with the current tree: nothing is written. */
|
|
60
|
+
| 'conflict'
|
|
61
|
+
/** The approvals on the current payload do not cover its tier. */
|
|
62
|
+
| 'not-approved'
|
|
63
|
+
/** The re-run checks stop it: the tier went up, a contrast pair would fail, or a new validation error. */
|
|
64
|
+
| 'blocked'
|
|
65
|
+
/** Not applied at all: unsafe path, symlink, edited payload, invalid content. */
|
|
66
|
+
| 'refused';
|
|
67
|
+
/** Exit codes the CLI uses: 0 ok (ready or already applied), 1 gate or checks, 2 usage, 3 conflict. */
|
|
68
|
+
export declare const SUGGESTION_APPLY_EXIT: Record<SuggestionApplyOutcome, 0 | 1 | 3>;
|
|
69
|
+
export interface SuggestionApplyGate {
|
|
70
|
+
/** The payload's review state on its current sha. */
|
|
71
|
+
review: 'needs-review' | 'changes-requested' | 'approved';
|
|
72
|
+
/** Approvals still missing for the tier. */
|
|
73
|
+
missing: ApprovalRole[];
|
|
74
|
+
approvals: SuggestionApprovals;
|
|
75
|
+
/** Applied without the approvals because the developer asked for it (recorded in the applied comment). */
|
|
76
|
+
forced: boolean;
|
|
77
|
+
/** The payload no longer matches what Bridge wrote; applied only because the developer accepted it. */
|
|
78
|
+
edited: boolean;
|
|
79
|
+
}
|
|
80
|
+
export interface SuggestionApplyChecks {
|
|
81
|
+
/** Tier when suggested and recomputed on the current tree. */
|
|
82
|
+
tierWhenSuggested: RiskTier;
|
|
83
|
+
tier: RiskResult;
|
|
84
|
+
tierIncreased: boolean;
|
|
85
|
+
/** Validation errors the result adds to today's tokens. */
|
|
86
|
+
newErrors: LayerIssue[];
|
|
87
|
+
/** Contrast pairs that pass today and would fail. */
|
|
88
|
+
newFailures: ContrastDelta[];
|
|
89
|
+
/** Every contrast pair that moved. */
|
|
90
|
+
contrast: ContrastDelta[];
|
|
91
|
+
/** Token changes the result makes against today's tokens. */
|
|
92
|
+
tokenChanges: LayeredChange[];
|
|
93
|
+
}
|
|
94
|
+
export interface SuggestionApplyPlan {
|
|
95
|
+
outcome: SuggestionApplyOutcome;
|
|
96
|
+
exitCode: 0 | 1 | 3;
|
|
97
|
+
/** One sentence for people: what happens, or what stops it and how to go on. */
|
|
98
|
+
message: string;
|
|
99
|
+
/** sha256 of the payload the plan applies (what approvals bound to). */
|
|
100
|
+
payloadSha: string;
|
|
101
|
+
payload: SuggestionPayloadV1;
|
|
102
|
+
gate: SuggestionApplyGate | null;
|
|
103
|
+
ops: SuggestionApplyOp[];
|
|
104
|
+
/** Files to write when the outcome is "ready" (null content: delete). Empty otherwise. */
|
|
105
|
+
writes: {
|
|
106
|
+
path: string;
|
|
107
|
+
content: string | null;
|
|
108
|
+
}[];
|
|
109
|
+
checks: SuggestionApplyChecks | null;
|
|
110
|
+
/** Why it is refused or blocked, as sentences. */
|
|
111
|
+
problems: string[];
|
|
112
|
+
/** Notes for the developer (token aliases left broken, a payload file the token edits replace...). */
|
|
113
|
+
notes: string[];
|
|
114
|
+
/** Component specs: the brief Claude or a developer implements from the spec file. */
|
|
115
|
+
brief?: string | undefined;
|
|
116
|
+
}
|
|
117
|
+
export interface PlanSuggestionApplyInput {
|
|
118
|
+
/** The parsed issue (parseSuggestionIssue). */
|
|
119
|
+
parsed: Extract<ParsedSuggestion, {
|
|
120
|
+
ok: true;
|
|
121
|
+
}>;
|
|
122
|
+
/** bridge.config.json as read from the code (the default branch or the checkout), never from the payload. */
|
|
123
|
+
config: BridgeConfig;
|
|
124
|
+
/** Issue comments, for the approvals. */
|
|
125
|
+
comments: readonly SuggestionCommentLike[];
|
|
126
|
+
/** Owners and rights from the same config (plus team members when read). Default: the config's. */
|
|
127
|
+
governance?: SuggestionGovernance | undefined;
|
|
128
|
+
/** The issue author: never approves their own suggestion. */
|
|
129
|
+
author?: string | null | undefined;
|
|
130
|
+
workspace: SuggestionWorkspace;
|
|
131
|
+
/** Apply even though the payload was edited by hand since Bridge wrote it. */
|
|
132
|
+
acceptEdited?: boolean | undefined;
|
|
133
|
+
/** Apply without every approval, or with a tier that went up (recorded in the applied comment). */
|
|
134
|
+
forceUnapproved?: boolean | undefined;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The plan to apply a suggestion to the current tree. Never writes; the adapter writes `writes` when the outcome is
|
|
138
|
+
* "ready". Stops before reading any file when a path is unsafe.
|
|
139
|
+
*/
|
|
140
|
+
export declare function planSuggestionApply(input: PlanSuggestionApplyInput): Promise<SuggestionApplyPlan>;
|
|
141
|
+
/** The comment recording where a planned suggestion was applied (posting it is the caller's job). */
|
|
142
|
+
export declare function suggestionAppliedComment(plan: Pick<SuggestionApplyPlan, 'payloadSha' | 'gate'>, where: {
|
|
143
|
+
by?: string | undefined;
|
|
144
|
+
branch?: string | undefined;
|
|
145
|
+
commit?: string | undefined;
|
|
146
|
+
pr?: number | undefined;
|
|
147
|
+
at: string;
|
|
148
|
+
}): string;
|
|
149
|
+
/** The per-change table of a plan, as Markdown (the CLI dry run, the MCP answer). */
|
|
150
|
+
export declare function suggestionApplyTable(plan: Pick<SuggestionApplyPlan, 'ops'>): string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Owners } from './config';
|
|
2
|
+
import { type ApprovalRole, type ApprovalSummary } from './review-gate';
|
|
3
|
+
import { type Rights } from './rights';
|
|
4
|
+
import type { RiskTier } from './risk';
|
|
5
|
+
import type { TeamStates } from './teams';
|
|
6
|
+
export declare const SUGGESTION_REVIEW_MARK = "bridge-suggestion-review";
|
|
7
|
+
export declare const SUGGESTION_APPLIED_MARK = "bridge-suggestion-applied";
|
|
8
|
+
export type SuggestionReviewEvent = 'approve' | 'request-changes' | 'comment';
|
|
9
|
+
export interface SuggestionReviewRecord {
|
|
10
|
+
event: SuggestionReviewEvent;
|
|
11
|
+
/** The role the reviewer approved for. Informative: what counts is the author against the owner lists. */
|
|
12
|
+
role?: ApprovalRole | undefined;
|
|
13
|
+
/** sha256 of the payload the reviewer saw (suggestionPayloadSha). */
|
|
14
|
+
payloadSha: string;
|
|
15
|
+
/** ISO date. */
|
|
16
|
+
at: string;
|
|
17
|
+
}
|
|
18
|
+
/** The comment a review posts: a sentence, the reviewer's message quoted, and the hidden record. */
|
|
19
|
+
export declare function composeSuggestionReviewComment(r: SuggestionReviewRecord & {
|
|
20
|
+
by?: string | null | undefined;
|
|
21
|
+
message?: string | undefined;
|
|
22
|
+
}): string;
|
|
23
|
+
/** The review record in a comment, or null when the comment is not a Bridge review. */
|
|
24
|
+
export declare function parseSuggestionReviewComment(body: string | null | undefined): SuggestionReviewRecord | null;
|
|
25
|
+
/** A developer applied the suggestion: where, and on which payload. */
|
|
26
|
+
export interface SuggestionAppliedRecord {
|
|
27
|
+
by?: string | undefined;
|
|
28
|
+
branch?: string | undefined;
|
|
29
|
+
commit?: string | undefined;
|
|
30
|
+
pr?: number | undefined;
|
|
31
|
+
payloadSha: string;
|
|
32
|
+
at: string;
|
|
33
|
+
/** Applied without the approvals (`--force-unapproved`). */
|
|
34
|
+
forced?: boolean | undefined;
|
|
35
|
+
}
|
|
36
|
+
/** The comment recording that a suggestion is being applied (a branch or a pull request exists). */
|
|
37
|
+
export declare function composeSuggestionAppliedComment(r: SuggestionAppliedRecord): string;
|
|
38
|
+
export declare function parseSuggestionAppliedComment(body: string | null | undefined): SuggestionAppliedRecord | null;
|
|
39
|
+
/** A comment as read from GitHub (listIssueComments), oldest first. */
|
|
40
|
+
export interface SuggestionCommentLike {
|
|
41
|
+
author: string;
|
|
42
|
+
body: string;
|
|
43
|
+
createdAt?: string | undefined;
|
|
44
|
+
/** GitHub's author_association: OWNER, MEMBER, COLLABORATOR, CONTRIBUTOR, NONE... */
|
|
45
|
+
authorAssociation?: string | undefined;
|
|
46
|
+
}
|
|
47
|
+
export interface SuggestionGovernance {
|
|
48
|
+
owners: Owners;
|
|
49
|
+
rights?: Rights | null | undefined;
|
|
50
|
+
/** Team members once read (teams.ts). Undefined: owner entries are used as listed. */
|
|
51
|
+
teams?: TeamStates | null | undefined;
|
|
52
|
+
}
|
|
53
|
+
export interface SuggestionApprovals extends ApprovalSummary {
|
|
54
|
+
/** People whose latest review is on an older payload: it no longer counts. */
|
|
55
|
+
stale: string[];
|
|
56
|
+
/** People whose review does not count because they are not owners (or not collaborators when no owner is set). */
|
|
57
|
+
ignored: string[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Approvals on a suggestion, as summarizeApprovals counts them on a pull request, from the review comments: only
|
|
61
|
+
* comments by people in the approver lists (governance owners and rights) count; with no owner configured at all, only
|
|
62
|
+
* repository owners, members and collaborators (GitHub's author association). Only reviews of the current payload
|
|
63
|
+
* sha count; the suggestion's author never approves their own suggestion.
|
|
64
|
+
*/
|
|
65
|
+
export declare function summarizeSuggestionApprovals(comments: readonly SuggestionCommentLike[], gov: SuggestionGovernance, opts: {
|
|
66
|
+
payloadSha: string;
|
|
67
|
+
author?: string | null | undefined;
|
|
68
|
+
}): SuggestionApprovals;
|
|
69
|
+
/** The review state for the status machine: changes requested beats approved; approved when no approval is missing. */
|
|
70
|
+
export declare function suggestionReviewState(tier: RiskTier, approvals: ApprovalSummary): 'needs-review' | 'changes-requested' | 'approved';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { GitHubClient, type IssueSummary, type PullRequestSummary } from './github-client';
|
|
2
|
+
import { type SuggestionAppliedRecord, type SuggestionApprovals, type SuggestionCommentLike, type SuggestionGovernance, type SuggestionReviewEvent } from './suggestion-review';
|
|
3
|
+
import { type ComposeSuggestionOptions, type ParsedSuggestion, type SuggestionKind, type SuggestionPayloadV1, type SuggestionStatus } from './suggestions';
|
|
4
|
+
import type { ApprovalRole } from './review-gate';
|
|
5
|
+
export interface CreatedSuggestion {
|
|
6
|
+
number: number;
|
|
7
|
+
url: string;
|
|
8
|
+
/** sha256 of the payload: what approvals bind to. */
|
|
9
|
+
sha: string;
|
|
10
|
+
/** Continuation comments posted. */
|
|
11
|
+
parts: number;
|
|
12
|
+
/** Sentences about parts that did not go through (labels refused). The suggestion itself holds. */
|
|
13
|
+
warnings: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Files a suggestion: the issue (title, body, labels), then its continuation comments in order when the payload is
|
|
17
|
+
* too large for the body. A part that fails leaves the suggestion incomplete (parsers refuse it) and throws with the
|
|
18
|
+
* issue number, so the caller can say so.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createSuggestionOnGitHub(gh: GitHubClient, payload: SuggestionPayloadV1, opts?: ComposeSuggestionOptions): Promise<CreatedSuggestion>;
|
|
21
|
+
/** Linked pull requests of an issue, from the bodies that close it ("Closes #n"). */
|
|
22
|
+
export declare function linkedPullsOf(n: number, pulls: readonly Pick<PullRequestSummary, 'number' | 'state' | 'body' | 'url'>[]): {
|
|
23
|
+
number: number;
|
|
24
|
+
url: string;
|
|
25
|
+
state: 'open' | 'closed';
|
|
26
|
+
merged: boolean;
|
|
27
|
+
}[];
|
|
28
|
+
export interface SuggestionListItem {
|
|
29
|
+
number: number;
|
|
30
|
+
title: string;
|
|
31
|
+
url: string;
|
|
32
|
+
author: string;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
state: 'open' | 'closed';
|
|
35
|
+
stateReason: string | null;
|
|
36
|
+
labels: string[];
|
|
37
|
+
kind: SuggestionKind | undefined;
|
|
38
|
+
/** Tier from the `tier: N` label. */
|
|
39
|
+
tier: 1 | 2 | 3 | undefined;
|
|
40
|
+
linkedPrs: {
|
|
41
|
+
number: number;
|
|
42
|
+
url: string;
|
|
43
|
+
state: 'open' | 'closed';
|
|
44
|
+
merged: boolean;
|
|
45
|
+
}[];
|
|
46
|
+
/**
|
|
47
|
+
* Status from the issue state, linked pull requests and the state label (a cache: getSuggestionOnGitHub reads the
|
|
48
|
+
* review comments for the real one).
|
|
49
|
+
*/
|
|
50
|
+
status: SuggestionStatus;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Suggestions in the repository, newest first. Status comes from labels and linked pull requests (one call per list,
|
|
54
|
+
* no comment reads); open getSuggestionOnGitHub for the counted approvals.
|
|
55
|
+
*/
|
|
56
|
+
export declare function listSuggestionsOnGitHub(gh: GitHubClient, opts?: {
|
|
57
|
+
state?: 'open' | 'closed' | 'all' | undefined;
|
|
58
|
+
perPage?: number | undefined;
|
|
59
|
+
pulls?: PullRequestSummary[] | undefined;
|
|
60
|
+
}): Promise<SuggestionListItem[]>;
|
|
61
|
+
export interface SuggestionDetail {
|
|
62
|
+
issue: IssueSummary;
|
|
63
|
+
parsed: ParsedSuggestion;
|
|
64
|
+
comments: (SuggestionCommentLike & {
|
|
65
|
+
id: number;
|
|
66
|
+
})[];
|
|
67
|
+
/** Counted on the current payload sha. Undefined when the payload does not parse. */
|
|
68
|
+
approvals: SuggestionApprovals | undefined;
|
|
69
|
+
linkedPrs: {
|
|
70
|
+
number: number;
|
|
71
|
+
url: string;
|
|
72
|
+
state: 'open' | 'closed';
|
|
73
|
+
merged: boolean;
|
|
74
|
+
}[];
|
|
75
|
+
applied: SuggestionAppliedRecord[];
|
|
76
|
+
status: SuggestionStatus;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* One suggestion with everything its status needs: the payload (parts reassembled from the author's comments), the
|
|
80
|
+
* review comments counted against the owners of the config read from the default branch, linked pull requests and
|
|
81
|
+
* applied markers. `outdated` comes from the caller's dry run (it needs the current files).
|
|
82
|
+
*/
|
|
83
|
+
export declare function getSuggestionOnGitHub(gh: GitHubClient, n: number, gov: SuggestionGovernance, opts?: {
|
|
84
|
+
pulls?: PullRequestSummary[] | undefined;
|
|
85
|
+
outdated?: boolean | undefined;
|
|
86
|
+
}): Promise<SuggestionDetail>;
|
|
87
|
+
/**
|
|
88
|
+
* Keeps exactly one state label on the issue, the one the status calls for (none once closed or outdated). Best
|
|
89
|
+
* effort: returns a warning sentence when the labels could not be changed.
|
|
90
|
+
*/
|
|
91
|
+
export declare function syncSuggestionStateLabel(gh: GitHubClient, n: number, labels: readonly string[], status: SuggestionStatus): Promise<string | null>;
|
|
92
|
+
export interface SuggestionReviewResult {
|
|
93
|
+
commentUrl: string;
|
|
94
|
+
payloadSha: string;
|
|
95
|
+
status: SuggestionStatus;
|
|
96
|
+
/** Whether this review counts (the reviewer is an owner, or a collaborator when no owner is set). */
|
|
97
|
+
counts: boolean;
|
|
98
|
+
warnings: string[];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Approves, requests changes on or comments on a suggestion as the token's user. `payloadSha` is the version the
|
|
102
|
+
* reviewer saw: when the payload changed since, nothing is posted and the error says to reload. Then the state label
|
|
103
|
+
* is re-synced from the recomputed status.
|
|
104
|
+
*/
|
|
105
|
+
export declare function reviewSuggestionOnGitHub(gh: GitHubClient, n: number, input: {
|
|
106
|
+
event: SuggestionReviewEvent;
|
|
107
|
+
role?: ApprovalRole | undefined;
|
|
108
|
+
message?: string | undefined;
|
|
109
|
+
payloadSha?: string | undefined;
|
|
110
|
+
by?: string | undefined;
|
|
111
|
+
at?: Date | undefined;
|
|
112
|
+
}, gov: SuggestionGovernance): Promise<SuggestionReviewResult>;
|
|
113
|
+
/**
|
|
114
|
+
* Records that a suggestion is being applied (the applied marker comment) and sets the `suggestion: applying` label.
|
|
115
|
+
* Called after a developer pushed the branch or opened the pull request that says "Closes #n".
|
|
116
|
+
*/
|
|
117
|
+
export declare function linkSuggestionPrOnGitHub(gh: GitHubClient, n: number, link: {
|
|
118
|
+
pr?: number | undefined;
|
|
119
|
+
branch?: string | undefined;
|
|
120
|
+
commit?: string | undefined;
|
|
121
|
+
by?: string | undefined;
|
|
122
|
+
payloadSha: string;
|
|
123
|
+
forced?: boolean | undefined;
|
|
124
|
+
at?: Date | undefined;
|
|
125
|
+
}): Promise<{
|
|
126
|
+
warnings: string[];
|
|
127
|
+
}>;
|
|
128
|
+
/** Declines a suggestion like a request (reason comment, closed as not planned), then clears its state label. */
|
|
129
|
+
export declare function declineSuggestionOnGitHub(gh: GitHubClient, n: number, reason: string, by?: string | null): Promise<{
|
|
130
|
+
warnings: string[];
|
|
131
|
+
}>;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { type BridgeConfig } from './config';
|
|
2
|
+
import type { LayeredChange } from './layered-review';
|
|
3
|
+
import { type NeededBy } from './prod-branch';
|
|
4
|
+
import type { RequestSource } from './requests';
|
|
5
|
+
import type { TokenEdit } from './token-edits';
|
|
6
|
+
export declare const SUGGESTION_SCHEMA = "bridge.suggestion/1";
|
|
7
|
+
/** Every suggestion issue carries this label; tools list suggestions by it. */
|
|
8
|
+
export declare const SUGGESTION_LABEL = "bridge-suggestion";
|
|
9
|
+
export type SuggestionKind = 'token-change' | 'docs' | 'component-spec' | 'brand' | 'revert' | 'config';
|
|
10
|
+
export declare const SUGGESTION_KINDS: readonly SuggestionKind[];
|
|
11
|
+
/** The states a suggestion label records, exactly one at a time while the issue is open. */
|
|
12
|
+
export type SuggestionState = 'needs-review' | 'changes-requested' | 'approved' | 'applying';
|
|
13
|
+
/** Where a suggestion stands (suggestionStatus). */
|
|
14
|
+
export type SuggestionStatus = SuggestionState | 'applied' | 'declined' | 'outdated';
|
|
15
|
+
export declare const SUGGESTION_STATE_LABELS: Record<SuggestionState, string>;
|
|
16
|
+
export declare const SUGGESTION_STATUS_LABEL: Record<SuggestionStatus, string>;
|
|
17
|
+
/** A whole-file result: the git blob sha at `base` (null: a new file), after (null: deleted), and the new content. */
|
|
18
|
+
export interface SuggestionFile {
|
|
19
|
+
path: string;
|
|
20
|
+
beforeSha: string | null;
|
|
21
|
+
afterSha: string | null;
|
|
22
|
+
/** The new text; null for a deleted file. Absent from the body when it moved to a continuation comment (`part`). */
|
|
23
|
+
content?: string | null | undefined;
|
|
24
|
+
/** Continuation comment (1-based) where the content starts. Transport only: not part of the payload sha. */
|
|
25
|
+
part?: number | undefined;
|
|
26
|
+
}
|
|
27
|
+
export interface SuggestionContrastCheck {
|
|
28
|
+
pair: string;
|
|
29
|
+
mode?: string | undefined;
|
|
30
|
+
before: number;
|
|
31
|
+
after: number;
|
|
32
|
+
min: number;
|
|
33
|
+
pass: boolean;
|
|
34
|
+
}
|
|
35
|
+
export type SuggestionTokenChange = Pick<LayeredChange, 'kind' | 'path' | 'from' | 'file' | 'before' | 'after' | 'beforeCss' | 'afterCss'>;
|
|
36
|
+
export interface SuggestionPayloadV1 {
|
|
37
|
+
schema: typeof SUGGESTION_SCHEMA;
|
|
38
|
+
kind: SuggestionKind;
|
|
39
|
+
title: string;
|
|
40
|
+
summary: string;
|
|
41
|
+
source: RequestSource;
|
|
42
|
+
/** GitHub login. */
|
|
43
|
+
suggestedBy?: string | undefined;
|
|
44
|
+
/** ISO date. */
|
|
45
|
+
createdAt: string;
|
|
46
|
+
/** Default branch and the commit the plan was computed on. */
|
|
47
|
+
base: {
|
|
48
|
+
ref: string;
|
|
49
|
+
sha: string;
|
|
50
|
+
};
|
|
51
|
+
tier: {
|
|
52
|
+
tier: 1 | 2 | 3;
|
|
53
|
+
bump: 'patch' | 'minor' | 'major';
|
|
54
|
+
};
|
|
55
|
+
/** Semantic token ops, applied with applyTokenEdits on the current token set, never as a text patch. */
|
|
56
|
+
tokens?: {
|
|
57
|
+
edits: TokenEdit[];
|
|
58
|
+
changes: SuggestionTokenChange[];
|
|
59
|
+
} | undefined;
|
|
60
|
+
/** docs.json edits: the component and the cleaned docs object. */
|
|
61
|
+
docs?: {
|
|
62
|
+
component: string;
|
|
63
|
+
path: string;
|
|
64
|
+
after: unknown;
|
|
65
|
+
}[] | undefined;
|
|
66
|
+
/** Figma component push spec, for Claude or a developer to implement. */
|
|
67
|
+
componentSpec?: {
|
|
68
|
+
component: string;
|
|
69
|
+
specPath: string;
|
|
70
|
+
spec: unknown;
|
|
71
|
+
brief: string;
|
|
72
|
+
} | undefined;
|
|
73
|
+
/** Whole-file results for everything else (changeset, brand files, revert, config). */
|
|
74
|
+
files?: SuggestionFile[] | undefined;
|
|
75
|
+
checks: {
|
|
76
|
+
newErrors: number;
|
|
77
|
+
contrast: SuggestionContrastCheck[];
|
|
78
|
+
/** The planner's `blocked` sentence at creation; a suggestion is never created when it is set. */
|
|
79
|
+
blocked: string | null;
|
|
80
|
+
impact: number;
|
|
81
|
+
};
|
|
82
|
+
links?: {
|
|
83
|
+
neededBy?: NeededBy | undefined;
|
|
84
|
+
figma?: {
|
|
85
|
+
fileKey?: string | undefined;
|
|
86
|
+
nodeId?: string | undefined;
|
|
87
|
+
url?: string | undefined;
|
|
88
|
+
} | undefined;
|
|
89
|
+
/** explore/<slug> */
|
|
90
|
+
exploration?: string | undefined;
|
|
91
|
+
/** A previous suggestion this one replaces. */
|
|
92
|
+
supersedes?: number | undefined;
|
|
93
|
+
} | undefined;
|
|
94
|
+
previews?: {
|
|
95
|
+
label: string;
|
|
96
|
+
url: string;
|
|
97
|
+
}[] | undefined;
|
|
98
|
+
}
|
|
99
|
+
/** GitHub caps an issue body and a comment at 65,536 characters. The budgets below keep every write under it. */
|
|
100
|
+
export declare const SUGGESTION_LIMITS: {
|
|
101
|
+
readonly body: 65536;
|
|
102
|
+
/** The readable part of the body (tables are cut with "and N more"). */
|
|
103
|
+
readonly human: 12000;
|
|
104
|
+
/** The JSON block in the body. */
|
|
105
|
+
readonly inline: 48000;
|
|
106
|
+
/** The JSON block of one continuation comment. */
|
|
107
|
+
readonly part: 60000;
|
|
108
|
+
/** Continuation comments at most (about 480 KB of file contents). */
|
|
109
|
+
readonly maxParts: 8;
|
|
110
|
+
};
|
|
111
|
+
export type SuggestionErrorCode = 'too-large' | 'invalid';
|
|
112
|
+
export declare class SuggestionError extends Error {
|
|
113
|
+
readonly code: SuggestionErrorCode;
|
|
114
|
+
constructor(code: SuggestionErrorCode, message: string);
|
|
115
|
+
}
|
|
116
|
+
export declare const SUGGESTION_TOO_LARGE = "Too large for a suggestion: split it, or ask a developer to open it directly.";
|
|
117
|
+
/** sha256 of a string's UTF-8 bytes, as 64 hex characters. */
|
|
118
|
+
export declare function sha256Hex(text: string): string;
|
|
119
|
+
/** The git blob sha of a text file (what `git hash-object` and the GitHub contents API return). */
|
|
120
|
+
export declare function gitBlobSha(text: string): string;
|
|
121
|
+
/** JSON with object keys sorted and no spaces; `undefined` members dropped, as JSON.stringify does. */
|
|
122
|
+
export declare function canonicalJson(value: unknown): string;
|
|
123
|
+
/** sha256 of the payload's canonical JSON: what approvals bind to and what the marker records. */
|
|
124
|
+
export declare function suggestionPayloadSha(p: SuggestionPayloadV1): string;
|
|
125
|
+
/** Subject of the title: the component for docs and component specs, else the kind ("Tokens", "Brand"). */
|
|
126
|
+
export declare function suggestionSubject(p: Pick<SuggestionPayloadV1, 'kind' | 'docs' | 'componentSpec'>): string;
|
|
127
|
+
/** Issue title, like requests: "Tokens: raise color.action.primary.bg contrast", "Button: docs for the danger tone". */
|
|
128
|
+
export declare function suggestionIssueTitle(p: Pick<SuggestionPayloadV1, 'kind' | 'title' | 'docs' | 'componentSpec'>): string;
|
|
129
|
+
/**
|
|
130
|
+
* The labels of a suggestion issue: the list key, kind, source, tier, one state label, then the marks it carries
|
|
131
|
+
* (needed by a product, from Figma, exploration) and the caller's (governance or integrations labels). Labels are a
|
|
132
|
+
* cache for filtering: status is always recomputed from comments and pull requests.
|
|
133
|
+
*/
|
|
134
|
+
export declare function suggestionLabels(p: SuggestionPayloadV1, opts?: {
|
|
135
|
+
state?: SuggestionState | undefined;
|
|
136
|
+
labels?: string[] | undefined;
|
|
137
|
+
}): string[];
|
|
138
|
+
/** The kind a label set names, when the issue has a `suggestion: <kind>` label. */
|
|
139
|
+
export declare function suggestionKindFromLabels(labels: readonly string[]): SuggestionKind | undefined;
|
|
140
|
+
/** The state a label set records (the cache), when it has one. */
|
|
141
|
+
export declare function suggestionStateFromLabels(labels: readonly string[]): SuggestionState | undefined;
|
|
142
|
+
export interface SuggestionStatusInput {
|
|
143
|
+
state: 'open' | 'closed';
|
|
144
|
+
stateReason?: string | null | undefined;
|
|
145
|
+
/** Pull requests whose body closes the issue ("Closes #n"). */
|
|
146
|
+
linkedPrs?: {
|
|
147
|
+
state: 'open' | 'closed';
|
|
148
|
+
merged?: boolean | undefined;
|
|
149
|
+
}[] | undefined;
|
|
150
|
+
/** From the review comments on the current payload sha (suggestionReviewState in suggestion-review.ts). */
|
|
151
|
+
review?: 'needs-review' | 'changes-requested' | 'approved' | undefined;
|
|
152
|
+
/** An "applied" marker comment exists (a developer applied it on a branch, see linkSuggestionPrOnGitHub). */
|
|
153
|
+
applied?: boolean | undefined;
|
|
154
|
+
/** The dry-run apply found a conflict with the current default branch. */
|
|
155
|
+
outdated?: boolean | undefined;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Where a suggestion stands. Closed: applied when a linked pull request merged or the issue was closed as completed,
|
|
159
|
+
* declined otherwise. Open: applying when a linked pull request is open or an applied marker exists; outdated when the
|
|
160
|
+
* dry run found a conflict; then the review state (changes requested, approved, needs review).
|
|
161
|
+
*/
|
|
162
|
+
export declare function suggestionStatus(i: SuggestionStatusInput): SuggestionStatus;
|
|
163
|
+
/** The state label a status keeps on the issue, or null when none should stay (closed, outdated). */
|
|
164
|
+
export declare function suggestionStateLabel(status: SuggestionStatus): string | null;
|
|
165
|
+
export interface SuggestionIssue {
|
|
166
|
+
title: string;
|
|
167
|
+
body: string;
|
|
168
|
+
labels: string[];
|
|
169
|
+
assignees: string[];
|
|
170
|
+
/** Continuation comments to post right after the issue, in order. Empty when everything fits in the body. */
|
|
171
|
+
parts: string[];
|
|
172
|
+
/** sha256 of the payload (canonical JSON, contents included). */
|
|
173
|
+
sha: string;
|
|
174
|
+
}
|
|
175
|
+
export interface ComposeSuggestionOptions {
|
|
176
|
+
labels?: string[] | undefined;
|
|
177
|
+
assignees?: string[] | undefined;
|
|
178
|
+
state?: SuggestionState | undefined;
|
|
179
|
+
/**
|
|
180
|
+
* Extra Markdown for the readable part, placed after the changes table (for example the risk tier reasons, contrast
|
|
181
|
+
* and impact sections of composeProposalBody). Cut to fit the readable budget.
|
|
182
|
+
*/
|
|
183
|
+
details?: string | undefined;
|
|
184
|
+
/** "owner/name", for the Claude Code install line in "How to apply". Absent: a placeholder. */
|
|
185
|
+
repo?: string | undefined;
|
|
186
|
+
}
|
|
187
|
+
/** The one-time command that gives Claude Code the Bridge MCP server for a repository. */
|
|
188
|
+
export declare const bridgeMcpInstallCommand: (repo?: string | null) => string;
|
|
189
|
+
/** The suggestion as a GitHub issue: readable Markdown, labels, the payload block, and continuation comments when large. */
|
|
190
|
+
export declare function composeSuggestionIssue(payload: SuggestionPayloadV1, opts?: ComposeSuggestionOptions): SuggestionIssue;
|
|
191
|
+
export type SuggestionParseFailure = 'no-block' | 'invalid' | 'unsupported-version' | 'missing-part';
|
|
192
|
+
export type ParsedSuggestion = {
|
|
193
|
+
ok: true;
|
|
194
|
+
payload: SuggestionPayloadV1;
|
|
195
|
+
/** sha256 of the payload as read now (what approvals bind to). */
|
|
196
|
+
sha: string;
|
|
197
|
+
/** The sha the marker recorded when Bridge wrote it. */
|
|
198
|
+
markedSha: string;
|
|
199
|
+
/** The payload or a part no longer matches what Bridge wrote: someone edited it by hand. */
|
|
200
|
+
edited: boolean;
|
|
201
|
+
/** Continuation comments read. */
|
|
202
|
+
parts: number;
|
|
203
|
+
} | {
|
|
204
|
+
ok: false;
|
|
205
|
+
reason: SuggestionParseFailure;
|
|
206
|
+
message: string;
|
|
207
|
+
};
|
|
208
|
+
export interface SuggestionCommentInput {
|
|
209
|
+
body: string;
|
|
210
|
+
author?: string | undefined;
|
|
211
|
+
}
|
|
212
|
+
/** Whether a body carries a suggestion block (any version). */
|
|
213
|
+
export declare function hasSuggestionBlock(body: string | null | undefined): boolean;
|
|
214
|
+
/**
|
|
215
|
+
* The suggestion in an issue body (plus its continuation comments when it has parts). Checks the schema, reassembles
|
|
216
|
+
* the parts (only comments by the issue author count when `author` is given), recomputes the sha and compares it with
|
|
217
|
+
* the marker: a mismatch sets `edited` (shown, applied only when the developer accepts it).
|
|
218
|
+
*/
|
|
219
|
+
export declare function parseSuggestionIssue(body: string | null | undefined, opts?: {
|
|
220
|
+
comments?: SuggestionCommentInput[] | undefined;
|
|
221
|
+
author?: string | undefined;
|
|
222
|
+
}): ParsedSuggestion;
|
|
223
|
+
/** Problems with a payload's shape, as sentences. Empty when it is a valid v1 payload. */
|
|
224
|
+
export declare function suggestionShapeProblems(raw: unknown): string[];
|
|
225
|
+
/** Problems with one repo-relative path, as sentences. Empty when it is a plain path inside the repo. */
|
|
226
|
+
export declare function unsafePathProblem(path: string): string | null;
|
|
227
|
+
/** Whether a path is a governance guardrail (a trailing "/" in the list matches a directory). */
|
|
228
|
+
export declare function isGuardrailPath(path: string, guardrails: readonly string[]): boolean;
|
|
229
|
+
/** Where each kind may write, repo-relative. Undefined: anywhere (revert), still subject to guardrails. */
|
|
230
|
+
export declare function suggestionAllowedRoots(kind: SuggestionKind, config: Pick<BridgeConfig, 'tokens' | 'changesets' | 'components'>): string[] | undefined;
|
|
231
|
+
/**
|
|
232
|
+
* Every unsafe path in a payload, as sentences that say what is wrong: not a plain repo-relative path, outside the
|
|
233
|
+
* folders its kind may touch, or a governance guardrail (bridge.config.json is allowed for config and brand only).
|
|
234
|
+
* Token edit files are checked under the tokens root. Symlinks are checked where files are read (apply).
|
|
235
|
+
*/
|
|
236
|
+
export declare function suggestionPathProblems(p: SuggestionPayloadV1, config: Pick<BridgeConfig, 'tokens' | 'changesets' | 'components' | 'governance'>): string[];
|
package/dist/token-proposal.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { BridgeConfig } from './config';
|
|
2
2
|
import type { GitHubClient } from './github-client';
|
|
3
|
+
import type { NeededBy } from './prod-branch';
|
|
4
|
+
import type { RequestSource } from './requests';
|
|
5
|
+
import { type SuggestionPayloadV1 } from './suggestions';
|
|
3
6
|
import { LayeredTokenSet, type LayerIssue } from './layered';
|
|
4
7
|
import { type ChangeTier, type ContrastDelta, type ImpactEntry, type LayeredChange } from './layered-review';
|
|
5
8
|
import { type TokenEdit } from './token-edits';
|
|
@@ -7,6 +10,8 @@ import type { ContrastPair } from './wcag';
|
|
|
7
10
|
export interface TokenProposalPlan {
|
|
8
11
|
title: string;
|
|
9
12
|
summary: string;
|
|
13
|
+
/** The token edits the plan applied: what a suggestion stores and re-applies on the current tokens. */
|
|
14
|
+
edits: TokenEdit[];
|
|
10
15
|
before: LayeredTokenSet;
|
|
11
16
|
after: LayeredTokenSet;
|
|
12
17
|
changes: LayeredChange[];
|
|
@@ -70,3 +75,49 @@ export declare function writeTokenProposalOnGitHub(gh: GitHubClient, plan: Token
|
|
|
70
75
|
/** Who proposes it, in the commit message: "Bridge MCP (source: mcp)". */
|
|
71
76
|
tool?: string | undefined;
|
|
72
77
|
}): Promise<OpenedTokenProposal>;
|
|
78
|
+
export interface SuggestedTokenProposal {
|
|
79
|
+
status: 'suggested';
|
|
80
|
+
/** The suggestion issue. */
|
|
81
|
+
number: number;
|
|
82
|
+
url: string;
|
|
83
|
+
/** sha256 of the payload: what approvals bind to. */
|
|
84
|
+
sha: string;
|
|
85
|
+
labels: string[];
|
|
86
|
+
/** Sentences about parts that did not go through (labels refused). The suggestion itself holds. */
|
|
87
|
+
warnings: string[];
|
|
88
|
+
}
|
|
89
|
+
/** The suggestion payload (bridge.suggestion/1) of a planned token change, computed on `base`. Pure. */
|
|
90
|
+
export declare function tokenSuggestionPayload(plan: TokenProposalPlan, ctx: {
|
|
91
|
+
base: {
|
|
92
|
+
ref: string;
|
|
93
|
+
sha: string;
|
|
94
|
+
};
|
|
95
|
+
source: RequestSource;
|
|
96
|
+
suggestedBy?: string | undefined;
|
|
97
|
+
createdAt?: string | undefined;
|
|
98
|
+
links?: SuggestionPayloadV1['links'];
|
|
99
|
+
previews?: SuggestionPayloadV1['previews'];
|
|
100
|
+
}): SuggestionPayloadV1;
|
|
101
|
+
/**
|
|
102
|
+
* Files a planned token change as a suggestion (suggest mode): one issue with the readable change, the checks and the
|
|
103
|
+
* exact edits, labelled like the pull request would be (tier, tokens, breaking), plus the suggestion labels. Needs
|
|
104
|
+
* Issues write only: no branch, no commit, no pull request. Refuses a blocked plan, like writeTokenProposalOnGitHub.
|
|
105
|
+
*/
|
|
106
|
+
export declare function suggestTokenProposalOnGitHub(gh: GitHubClient, plan: TokenProposalPlan, opts: {
|
|
107
|
+
/** Default branch the plan was computed on. Its head commit is recorded as the suggestion's base. */
|
|
108
|
+
base: string;
|
|
109
|
+
source: RequestSource;
|
|
110
|
+
/** GitHub login of who suggests it. */
|
|
111
|
+
suggestedBy?: string | undefined;
|
|
112
|
+
/** Labels before the tier ones (governance.labels), and after them (the product label). */
|
|
113
|
+
labels?: string[] | undefined;
|
|
114
|
+
labelsAfter?: string[] | undefined;
|
|
115
|
+
/** The product branch that needs it (a missing piece). */
|
|
116
|
+
neededBy?: NeededBy | undefined;
|
|
117
|
+
/** The Figma origin, a suggestion it replaces. */
|
|
118
|
+
links?: SuggestionPayloadV1['links'];
|
|
119
|
+
previews?: SuggestionPayloadV1['previews'];
|
|
120
|
+
assignees?: string[] | undefined;
|
|
121
|
+
/** Commit sha of `base`, when the caller already read it (saves a call). */
|
|
122
|
+
baseSha?: string | undefined;
|
|
123
|
+
}): Promise<SuggestedTokenProposal>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge-ds/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Bridge core: bridge.config.json, DTCG token loading (tiers and layered modes), alias resolution, WCAG contrast, diffs, minimal-diff edits, PR body, the shared risk tier classifier, changesets and a fetch-based GitHub client. No Figma API, no DOM; Node-only helpers (git, reading a product folder) under @bridge-ds/core/node; the adoption audit under @bridge-ds/core/audit.",
|
|
6
6
|
"license": "MIT",
|