@snappedly-tools/shipyard 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -43
- package/dist/{MountConfig-bZoCs4Dd.d.ts → MountConfig-K5ILnfht.d.ts} +1 -1
- package/dist/SandboxProvider-oUAwYlWm.d.ts +120 -0
- package/dist/{chunk-FDYOTN55.js → chunk-57EEKW3R.js} +361 -984
- package/dist/chunk-57EEKW3R.js.map +1 -0
- package/dist/{chunk-56TDSWFU.js → chunk-HXSZM52J.js} +3 -3
- package/dist/{chunk-56TDSWFU.js.map → chunk-HXSZM52J.js.map} +1 -1
- package/dist/{chunk-WAXEMZUV.js → chunk-JZUBT4WG.js} +241 -33
- package/dist/chunk-JZUBT4WG.js.map +1 -0
- package/dist/chunk-NQRFVKCU.js +1840 -0
- package/dist/chunk-NQRFVKCU.js.map +1 -0
- package/dist/chunk-Z5C4LHVP.js +137 -0
- package/dist/chunk-Z5C4LHVP.js.map +1 -0
- package/dist/createSandbox-DmbnWAZv.d.ts +739 -0
- package/dist/index.d.ts +99 -2606
- package/dist/index.js +412 -7487
- package/dist/index.js.map +1 -1
- package/dist/integrations/github.d.ts +52 -0
- package/dist/integrations/github.js +1049 -0
- package/dist/integrations/github.js.map +1 -0
- package/dist/integrations/releases.d.ts +136 -0
- package/dist/integrations/releases.js +500 -0
- package/dist/integrations/releases.js.map +1 -0
- package/dist/main.js +826 -472
- package/dist/main.js.map +1 -1
- package/dist/publication-BPoy_M9M.d.ts +1200 -0
- package/dist/sandboxes/docker.d.ts +2 -3
- package/dist/sandboxes/docker.js +2 -4
- package/dist/templates/parallel-planner/main.mts +78 -16
- package/dist/templates/parallel-planner/planner-branch.mts +151 -0
- package/dist/templates/parallel-planner/setup.sh +1 -0
- package/dist/templates/parallel-planner-with-review/main.mts +81 -19
- package/dist/templates/parallel-planner-with-review/planner-branch.mts +151 -0
- package/dist/templates/parallel-planner-with-review/setup.sh +1 -0
- package/dist/templates/sequential-reviewer/main.mts +59 -6
- package/dist/templates/sequential-reviewer/setup.sh +1 -0
- package/dist/templates/shared/setup.sh +1 -0
- package/dist/templates/simple-loop/main.mts +58 -5
- package/dist/templates/simple-loop/setup.sh +1 -0
- package/dist/workflow/coordinator/migrations/002_workflow_phase_records.sql +11 -0
- package/dist/workflow/coordinator/migrations/003_phase_record_schema_version.sql +6 -0
- package/dist/workflow.d.ts +472 -0
- package/dist/workflow.js +4345 -0
- package/dist/workflow.js.map +1 -0
- package/package.json +11 -15
- package/dist/SandboxProvider-XJQqEdSf.d.ts +0 -261
- package/dist/chunk-ACD46ZM4.js +0 -136
- package/dist/chunk-ACD46ZM4.js.map +0 -1
- package/dist/chunk-FDYOTN55.js.map +0 -1
- package/dist/chunk-KMGNFXKN.js +0 -38
- package/dist/chunk-KMGNFXKN.js.map +0 -1
- package/dist/chunk-SOJTAJTF.js +0 -78
- package/dist/chunk-SOJTAJTF.js.map +0 -1
- package/dist/chunk-WAXEMZUV.js.map +0 -1
- package/dist/sandboxes/no-sandbox.d.ts +0 -37
- package/dist/sandboxes/no-sandbox.js +0 -4
- package/dist/sandboxes/no-sandbox.js.map +0 -1
- package/dist/sandboxes/vercel.d.ts +0 -104
- package/dist/sandboxes/vercel.js +0 -166
- package/dist/sandboxes/vercel.js.map +0 -1
- package/dist/templates/blank/main.mts +0 -13
- package/dist/templates/blank/prompt.md +0 -12
- package/dist/templates/blank/template.json +0 -4
|
@@ -0,0 +1,1049 @@
|
|
|
1
|
+
import { createWorkBrief, parseRepositoryPolicy, processHumanReviewDecision, runTriage, parseWorkBrief } from '../chunk-NQRFVKCU.js';
|
|
2
|
+
import { createHmac, timingSafeEqual, createHash } from 'crypto';
|
|
3
|
+
|
|
4
|
+
var asBytes = (value) => typeof value === "string" ? Buffer.from(value, "utf8") : Buffer.from(value);
|
|
5
|
+
var verifyGitHubWebhookSignature = (input) => {
|
|
6
|
+
const expected = createHmac("sha256", asBytes(input.secret)).update(asBytes(input.body)).digest();
|
|
7
|
+
const signature = input.signature ?? "";
|
|
8
|
+
const prefix = "sha256=";
|
|
9
|
+
const encoded = signature.startsWith(prefix) ? signature.slice(prefix.length) : "";
|
|
10
|
+
const isHexDigest = /^[0-9a-f]{64}$/i.test(encoded);
|
|
11
|
+
const candidate = isHexDigest ? Buffer.from(encoded, "hex") : Buffer.alloc(expected.length);
|
|
12
|
+
return timingSafeEqual(expected, candidate) && isHexDigest;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/integrations/github/store.ts
|
|
16
|
+
var InMemoryGitHubStore = class {
|
|
17
|
+
deliveries = /* @__PURE__ */ new Map();
|
|
18
|
+
trackedPullRequests = /* @__PURE__ */ new Map();
|
|
19
|
+
async recordDeliveryIfAbsent(delivery) {
|
|
20
|
+
const existing = this.deliveries.get(delivery.deliveryId);
|
|
21
|
+
if (existing !== void 0) {
|
|
22
|
+
return { delivery: existing, inserted: false };
|
|
23
|
+
}
|
|
24
|
+
this.deliveries.set(delivery.deliveryId, delivery);
|
|
25
|
+
return { delivery, inserted: true };
|
|
26
|
+
}
|
|
27
|
+
async getDelivery(deliveryId) {
|
|
28
|
+
return this.deliveries.get(deliveryId);
|
|
29
|
+
}
|
|
30
|
+
async updateDelivery(delivery) {
|
|
31
|
+
const existing = this.deliveries.get(delivery.deliveryId);
|
|
32
|
+
if (existing === void 0) {
|
|
33
|
+
throw new Error(`GitHub delivery ${delivery.deliveryId} does not exist`);
|
|
34
|
+
}
|
|
35
|
+
this.deliveries.set(delivery.deliveryId, delivery);
|
|
36
|
+
}
|
|
37
|
+
async findTrackedPullRequest(repository, pullRequestNumber2) {
|
|
38
|
+
return this.trackedPullRequests.get(
|
|
39
|
+
`${repository.toLowerCase()}\0${pullRequestNumber2}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
async saveTrackedPullRequest(pullRequest) {
|
|
43
|
+
this.trackedPullRequests.set(
|
|
44
|
+
`${pullRequest.repository.toLowerCase()}\0${pullRequest.pullRequestNumber}`,
|
|
45
|
+
pullRequest
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/integrations/github/integration.ts
|
|
51
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
52
|
+
var record = (value) => isRecord(value) ? value : void 0;
|
|
53
|
+
var stringValue = (value, fallback = "") => typeof value === "string" ? value : fallback;
|
|
54
|
+
var requiredStringValue = (value) => {
|
|
55
|
+
const result2 = stringValue(value).trim();
|
|
56
|
+
return result2.length === 0 ? void 0 : result2;
|
|
57
|
+
};
|
|
58
|
+
var numberValue = (value) => typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
59
|
+
var header = (headers, name) => {
|
|
60
|
+
const wanted = name.toLowerCase();
|
|
61
|
+
const key = Object.keys(headers).find(
|
|
62
|
+
(candidate) => candidate.toLowerCase() === wanted
|
|
63
|
+
);
|
|
64
|
+
return key === void 0 ? void 0 : headers[key];
|
|
65
|
+
};
|
|
66
|
+
var bodyBytes = (body) => typeof body === "string" ? new TextEncoder().encode(body) : body;
|
|
67
|
+
var bodyText = (body) => typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
68
|
+
var payloadHash = (body) => createHash("sha256").update(bodyBytes(body)).digest("hex");
|
|
69
|
+
var MAX_WEBHOOK_BODY_BYTES = 10 * 1024 * 1024;
|
|
70
|
+
var parseBody = (body) => {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(body);
|
|
73
|
+
} catch {
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var actor = (value) => {
|
|
78
|
+
const candidate = record(value);
|
|
79
|
+
return {
|
|
80
|
+
login: stringValue(candidate?.login, "unknown"),
|
|
81
|
+
type: stringValue(candidate?.type) || void 0
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
var repositoryName = (payload) => requiredStringValue(record(payload.repository)?.full_name);
|
|
85
|
+
var sender = (payload) => actor(payload.sender ?? record(payload.comment)?.user ?? void 0);
|
|
86
|
+
var labels = (value) => {
|
|
87
|
+
if (!Array.isArray(value)) return [];
|
|
88
|
+
return value.map(
|
|
89
|
+
(entry) => typeof entry === "string" ? entry : stringValue(record(entry)?.name)
|
|
90
|
+
).filter((entry) => entry.length > 0);
|
|
91
|
+
};
|
|
92
|
+
var state = (value) => value === "closed" ? "closed" : "open";
|
|
93
|
+
var allowedEventNames = /* @__PURE__ */ new Set([
|
|
94
|
+
"issues",
|
|
95
|
+
"issue_comment",
|
|
96
|
+
"pull_request",
|
|
97
|
+
"pull_request_review",
|
|
98
|
+
"pull_request_review_comment",
|
|
99
|
+
"check_run",
|
|
100
|
+
"check_suite"
|
|
101
|
+
]);
|
|
102
|
+
var supportedActions = {
|
|
103
|
+
issues: ["opened", "edited", "reopened", "closed"],
|
|
104
|
+
issue_comment: ["created", "edited"],
|
|
105
|
+
pull_request: [
|
|
106
|
+
"opened",
|
|
107
|
+
"edited",
|
|
108
|
+
"reopened",
|
|
109
|
+
"closed",
|
|
110
|
+
"synchronize",
|
|
111
|
+
"ready_for_review",
|
|
112
|
+
"converted_to_draft"
|
|
113
|
+
],
|
|
114
|
+
pull_request_review: ["submitted", "edited", "dismissed"],
|
|
115
|
+
pull_request_review_comment: ["created", "edited", "deleted"],
|
|
116
|
+
check_run: ["created", "rerequested", "completed"],
|
|
117
|
+
check_suite: ["completed", "requested", "rerequested"]
|
|
118
|
+
};
|
|
119
|
+
var ignored = (input) => ({ disposition: "ignored", ...input });
|
|
120
|
+
var issueSnapshot = (payload) => {
|
|
121
|
+
const value = record(payload.issue);
|
|
122
|
+
const number = numberValue(value?.number);
|
|
123
|
+
if (value === void 0 || number === void 0) return void 0;
|
|
124
|
+
return {
|
|
125
|
+
number,
|
|
126
|
+
title: stringValue(value.title, "Untitled issue"),
|
|
127
|
+
body: stringValue(value.body),
|
|
128
|
+
state: state(value.state),
|
|
129
|
+
updatedAt: stringValue(value.updated_at),
|
|
130
|
+
htmlUrl: requiredStringValue(value.html_url),
|
|
131
|
+
authorLogin: requiredStringValue(record(value.user)?.login),
|
|
132
|
+
labels: labels(value.labels),
|
|
133
|
+
pullRequestNumber: numberValue(record(value.pull_request)?.number)
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
var commentSnapshot = (payload) => {
|
|
137
|
+
const value = record(payload.comment);
|
|
138
|
+
const id = requiredStringValue(value?.id);
|
|
139
|
+
if (value === void 0 || id === void 0) return void 0;
|
|
140
|
+
return {
|
|
141
|
+
id,
|
|
142
|
+
body: stringValue(value.body),
|
|
143
|
+
updatedAt: stringValue(value.updated_at),
|
|
144
|
+
htmlUrl: requiredStringValue(value.html_url),
|
|
145
|
+
authorLogin: requiredStringValue(record(value.user)?.login)
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
var reviewState = (value) => {
|
|
149
|
+
const normalized = requiredStringValue(value)?.toLowerCase().replaceAll("_", "-");
|
|
150
|
+
switch (normalized) {
|
|
151
|
+
case "approved":
|
|
152
|
+
case "changes-requested":
|
|
153
|
+
case "commented":
|
|
154
|
+
case "dismissed":
|
|
155
|
+
case "pending":
|
|
156
|
+
return normalized;
|
|
157
|
+
default:
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
var reviewSnapshot = (payload) => {
|
|
162
|
+
const value = record(payload.review);
|
|
163
|
+
if (value === void 0) return void 0;
|
|
164
|
+
const rawId = value.id;
|
|
165
|
+
const id = typeof rawId === "number" && Number.isInteger(rawId) && rawId > 0 ? String(rawId) : requiredStringValue(rawId);
|
|
166
|
+
const state2 = reviewState(value.state);
|
|
167
|
+
const pullRequest = record(payload.pull_request);
|
|
168
|
+
const headSha = requiredStringValue(value.commit_id) ?? requiredStringValue(record(pullRequest?.head)?.sha);
|
|
169
|
+
const submittedAt = requiredStringValue(value.submitted_at) ?? requiredStringValue(value.updated_at) ?? requiredStringValue(value.created_at);
|
|
170
|
+
if (id === void 0 || state2 === void 0 || headSha === void 0 || submittedAt === void 0) {
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
id,
|
|
175
|
+
state: state2,
|
|
176
|
+
headSha,
|
|
177
|
+
submittedAt,
|
|
178
|
+
authorLogin: requiredStringValue(record(value.user)?.login)
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
var reviewDecisionFor = (review) => {
|
|
182
|
+
switch (review.state) {
|
|
183
|
+
case "approved":
|
|
184
|
+
return { decision: "approved" };
|
|
185
|
+
case "changes-requested":
|
|
186
|
+
return { decision: "changes-requested" };
|
|
187
|
+
case "dismissed":
|
|
188
|
+
return { decision: "rejected", reason: "GitHub review was dismissed" };
|
|
189
|
+
case "commented":
|
|
190
|
+
case "pending":
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
var pullRequestNumber = (payload) => {
|
|
195
|
+
const direct = numberValue(record(payload.pull_request)?.number);
|
|
196
|
+
if (direct !== void 0) return direct;
|
|
197
|
+
const issue = numberValue(record(payload.issue)?.number);
|
|
198
|
+
if (issue !== void 0 && record(payload.issue)?.pull_request !== void 0) {
|
|
199
|
+
return issue;
|
|
200
|
+
}
|
|
201
|
+
const check = record(payload.check_run) ?? record(payload.check_suite);
|
|
202
|
+
const pullRequests = check?.pull_requests;
|
|
203
|
+
if (Array.isArray(pullRequests)) {
|
|
204
|
+
return numberValue(record(pullRequests[0])?.number);
|
|
205
|
+
}
|
|
206
|
+
return void 0;
|
|
207
|
+
};
|
|
208
|
+
var pullRequestHead = (payload, fallback) => requiredStringValue(record(record(payload.pull_request)?.head)?.sha) ?? requiredStringValue(record(payload.check_run)?.head_sha) ?? requiredStringValue(record(payload.check_suite)?.head_sha) ?? fallback;
|
|
209
|
+
var issueKind = (issue) => {
|
|
210
|
+
const normalized = new Set(issue.labels.map((label) => label.toLowerCase()));
|
|
211
|
+
if (normalized.has("planning-spec") || normalized.has("planning")) {
|
|
212
|
+
return "planning-spec";
|
|
213
|
+
}
|
|
214
|
+
if (normalized.has("pr-repair") || normalized.has("repair")) {
|
|
215
|
+
return "pr-repair";
|
|
216
|
+
}
|
|
217
|
+
return "executable-issue";
|
|
218
|
+
};
|
|
219
|
+
var sameIssueContent = (brief, issue) => (brief.problem === `${issue.title}
|
|
220
|
+
|
|
221
|
+
${issue.body || "(empty issue body)"}` || brief.problem === issue.title) && brief.source.originalBody === (issue.body || "(empty issue body)");
|
|
222
|
+
var createGitHubIssueBrief = (input) => {
|
|
223
|
+
const issue = input.event;
|
|
224
|
+
return createWorkBrief({
|
|
225
|
+
id: `${issue.repository}:${issue.kind}:${issue.issueNumber}`,
|
|
226
|
+
revision: input.revision,
|
|
227
|
+
identity: {
|
|
228
|
+
repository: issue.repository,
|
|
229
|
+
itemId: String(issue.issueNumber),
|
|
230
|
+
kind: input.itemKind
|
|
231
|
+
},
|
|
232
|
+
source: {
|
|
233
|
+
provider: "github",
|
|
234
|
+
repository: issue.repository,
|
|
235
|
+
itemId: String(issue.issueNumber),
|
|
236
|
+
url: issue.reply?.htmlUrl ?? issue.trackedPullRequest?.brief.source.url,
|
|
237
|
+
originalBody: issue.body || "(empty issue body)",
|
|
238
|
+
author: issue.reply?.authorLogin
|
|
239
|
+
},
|
|
240
|
+
problem: issue.title + "\n\n" + (issue.body || "(empty issue body)"),
|
|
241
|
+
evidence: ["Submitted through authenticated GitHub intake."],
|
|
242
|
+
acceptanceCriteria: input.defaults?.acceptanceCriteria ?? [],
|
|
243
|
+
exclusions: input.defaults?.exclusions ?? [],
|
|
244
|
+
risk: input.defaults?.risk ?? "medium",
|
|
245
|
+
verification: {
|
|
246
|
+
checks: input.policy.checks.map((check) => check.command),
|
|
247
|
+
artifacts: input.defaults?.verificationArtifacts ?? []
|
|
248
|
+
},
|
|
249
|
+
unresolvedQuestions: input.defaults?.unresolvedQuestions ?? [
|
|
250
|
+
"A maintainer must confirm acceptance criteria and execution authorization."
|
|
251
|
+
],
|
|
252
|
+
authorization: { status: "pending" },
|
|
253
|
+
base: input.base,
|
|
254
|
+
policyRevision: input.policy.revision,
|
|
255
|
+
skillRevision: input.policy.worker.skillRevision,
|
|
256
|
+
createdAt: issue.observedAt
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
var eventName = (value) => allowedEventNames.has(value) ? value : void 0;
|
|
260
|
+
var GitHubIntegration = class {
|
|
261
|
+
options;
|
|
262
|
+
now;
|
|
263
|
+
constructor(options) {
|
|
264
|
+
this.options = {
|
|
265
|
+
...options,
|
|
266
|
+
policy: parseRepositoryPolicy(options.policy)
|
|
267
|
+
};
|
|
268
|
+
this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
269
|
+
}
|
|
270
|
+
async receiveWebhook(request) {
|
|
271
|
+
const deliveryId = header(request.headers, "x-github-delivery");
|
|
272
|
+
const eventHeader = header(request.headers, "x-github-event");
|
|
273
|
+
if (deliveryId === void 0 || eventHeader === void 0) {
|
|
274
|
+
return {
|
|
275
|
+
status: "rejected",
|
|
276
|
+
deliveryId: deliveryId ?? "unknown",
|
|
277
|
+
reason: "missing-delivery-or-event-header"
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (bodyBytes(request.body).byteLength > MAX_WEBHOOK_BODY_BYTES) {
|
|
281
|
+
return { status: "rejected", deliveryId, reason: "body-too-large" };
|
|
282
|
+
}
|
|
283
|
+
const body = bodyText(request.body);
|
|
284
|
+
if (!verifyGitHubWebhookSignature({
|
|
285
|
+
body: request.body,
|
|
286
|
+
signature: header(request.headers, "x-hub-signature-256"),
|
|
287
|
+
secret: this.options.webhookSecret
|
|
288
|
+
})) {
|
|
289
|
+
return { status: "rejected", deliveryId, reason: "invalid-signature" };
|
|
290
|
+
}
|
|
291
|
+
return this.processEnvelope({
|
|
292
|
+
eventName: eventHeader,
|
|
293
|
+
deliveryId,
|
|
294
|
+
payload: parseBody(body),
|
|
295
|
+
receivedAt: this.now(),
|
|
296
|
+
body
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
async normalize(envelope) {
|
|
300
|
+
const payload = record(envelope.payload);
|
|
301
|
+
const name = eventName(envelope.eventName);
|
|
302
|
+
const repository = payload ? repositoryName(payload) ?? "unknown" : "unknown";
|
|
303
|
+
const source = payload ? sender(payload) : { login: "unknown" };
|
|
304
|
+
if (name === void 0) {
|
|
305
|
+
return ignored({
|
|
306
|
+
reason: "unsupported-event",
|
|
307
|
+
eventName: envelope.eventName,
|
|
308
|
+
deliveryId: envelope.deliveryId,
|
|
309
|
+
repository,
|
|
310
|
+
sender: source
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
const action = stringValue(payload?.action);
|
|
314
|
+
if (!supportedActions[name].includes(action)) {
|
|
315
|
+
return ignored({
|
|
316
|
+
reason: "unsupported-action",
|
|
317
|
+
eventName: name,
|
|
318
|
+
action,
|
|
319
|
+
deliveryId: envelope.deliveryId,
|
|
320
|
+
repository,
|
|
321
|
+
sender: source
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
if (payload === void 0) {
|
|
325
|
+
return ignored({
|
|
326
|
+
reason: "unsupported-event",
|
|
327
|
+
eventName: name,
|
|
328
|
+
action,
|
|
329
|
+
deliveryId: envelope.deliveryId,
|
|
330
|
+
repository,
|
|
331
|
+
sender: source
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
if (name === "issues" || name === "issue_comment") {
|
|
335
|
+
const issue = issueSnapshot(payload);
|
|
336
|
+
if (issue === void 0) {
|
|
337
|
+
return ignored({
|
|
338
|
+
reason: "unsupported-event",
|
|
339
|
+
eventName: name,
|
|
340
|
+
action,
|
|
341
|
+
deliveryId: envelope.deliveryId,
|
|
342
|
+
repository,
|
|
343
|
+
sender: source
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
const issueIsPullRequest = issue.pullRequestNumber !== void 0 || record(record(payload.issue)?.pull_request) !== void 0;
|
|
347
|
+
if (issueIsPullRequest) {
|
|
348
|
+
return this.normalizeTrackedPullRequest(
|
|
349
|
+
envelope,
|
|
350
|
+
payload,
|
|
351
|
+
issue.pullRequestNumber ?? issue.number,
|
|
352
|
+
name === "issue_comment" ? commentSnapshot(payload) : void 0
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
const kind = issueKind(issue);
|
|
356
|
+
const draft = {
|
|
357
|
+
kind: name === "issue_comment" ? "issue-replied" : action === "opened" || action === "reopened" ? "issue-created" : "issue-edited",
|
|
358
|
+
eventName: name,
|
|
359
|
+
action,
|
|
360
|
+
deliveryId: envelope.deliveryId,
|
|
361
|
+
repository,
|
|
362
|
+
sender: source,
|
|
363
|
+
issueNumber: issue.number,
|
|
364
|
+
title: issue.title,
|
|
365
|
+
body: issue.body,
|
|
366
|
+
labels: issue.labels,
|
|
367
|
+
relevantRevision: issue.updatedAt || envelope.receivedAt,
|
|
368
|
+
observedAt: issue.updatedAt || envelope.receivedAt,
|
|
369
|
+
sourceState: issue.state,
|
|
370
|
+
reply: name === "issue_comment" ? commentSnapshot(payload) : void 0
|
|
371
|
+
};
|
|
372
|
+
const briefResolution = await this.briefForIssue(draft, issue, kind);
|
|
373
|
+
if ("ignoredReason" in briefResolution) {
|
|
374
|
+
return ignored({
|
|
375
|
+
reason: briefResolution.ignoredReason,
|
|
376
|
+
eventName: envelope.eventName,
|
|
377
|
+
action,
|
|
378
|
+
deliveryId: envelope.deliveryId,
|
|
379
|
+
repository,
|
|
380
|
+
sender: source
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
const brief = briefResolution.brief;
|
|
384
|
+
return {
|
|
385
|
+
disposition: "accepted",
|
|
386
|
+
event: {
|
|
387
|
+
...draft,
|
|
388
|
+
workflowEvent: {
|
|
389
|
+
deliveryId: envelope.deliveryId,
|
|
390
|
+
brief,
|
|
391
|
+
policy: this.options.policy,
|
|
392
|
+
phase: "triage",
|
|
393
|
+
relevantRevision: draft.relevantRevision,
|
|
394
|
+
observedAt: draft.observedAt,
|
|
395
|
+
sourceState: draft.sourceState,
|
|
396
|
+
payload: envelope.payload
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
const number = pullRequestNumber(payload);
|
|
402
|
+
if (number === void 0) {
|
|
403
|
+
return ignored({
|
|
404
|
+
reason: "unrelated-pull-request",
|
|
405
|
+
eventName: name,
|
|
406
|
+
action,
|
|
407
|
+
deliveryId: envelope.deliveryId,
|
|
408
|
+
repository,
|
|
409
|
+
sender: source
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
const review = name === "pull_request_review" ? reviewSnapshot(payload) : void 0;
|
|
413
|
+
if (name === "pull_request_review" && review === void 0) {
|
|
414
|
+
return ignored({
|
|
415
|
+
reason: "unsupported-event",
|
|
416
|
+
eventName: name,
|
|
417
|
+
action,
|
|
418
|
+
deliveryId: envelope.deliveryId,
|
|
419
|
+
repository,
|
|
420
|
+
sender: source
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return this.normalizeTrackedPullRequest(
|
|
424
|
+
envelope,
|
|
425
|
+
payload,
|
|
426
|
+
number,
|
|
427
|
+
void 0,
|
|
428
|
+
review
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
async reconcile(input) {
|
|
432
|
+
const hasIssue = input.issueNumber !== void 0;
|
|
433
|
+
const hasPullRequest = input.pullRequestNumber !== void 0;
|
|
434
|
+
if (hasIssue === hasPullRequest) {
|
|
435
|
+
throw new Error(
|
|
436
|
+
"Reconciliation requires exactly one issue or pull request"
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
if (hasIssue) {
|
|
440
|
+
const issue = await input.transport.fetchIssue({
|
|
441
|
+
repository: input.repository,
|
|
442
|
+
issueNumber: input.issueNumber
|
|
443
|
+
});
|
|
444
|
+
if (issue === void 0) {
|
|
445
|
+
return {
|
|
446
|
+
status: "not-found",
|
|
447
|
+
deliveryId: `reconcile:issue:${input.repository}:${input.issueNumber}`
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
const payload = this.issuePayload(issue, input.repository);
|
|
451
|
+
return this.toReconciliationResult(
|
|
452
|
+
await this.processEnvelope(
|
|
453
|
+
{
|
|
454
|
+
eventName: "issues",
|
|
455
|
+
deliveryId: `reconcile:issue:${input.repository}:${issue.number}:${issue.updatedAt}`,
|
|
456
|
+
payload,
|
|
457
|
+
receivedAt: this.now()
|
|
458
|
+
},
|
|
459
|
+
true
|
|
460
|
+
)
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
const pullRequest = await input.transport.fetchPullRequest({
|
|
464
|
+
repository: input.repository,
|
|
465
|
+
pullRequestNumber: input.pullRequestNumber
|
|
466
|
+
});
|
|
467
|
+
if (pullRequest === void 0) {
|
|
468
|
+
return {
|
|
469
|
+
status: "not-found",
|
|
470
|
+
deliveryId: `reconcile:pull-request:${input.repository}:${input.pullRequestNumber}`
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
return this.toReconciliationResult(
|
|
474
|
+
await this.processEnvelope(
|
|
475
|
+
{
|
|
476
|
+
eventName: "pull_request",
|
|
477
|
+
deliveryId: `reconcile:pull-request:${input.repository}:${pullRequest.number}:${pullRequest.headSha}`,
|
|
478
|
+
payload: this.pullRequestPayload(pullRequest, input.repository),
|
|
479
|
+
receivedAt: this.now()
|
|
480
|
+
},
|
|
481
|
+
true
|
|
482
|
+
)
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
async processEnvelope(envelope, trustedReconciliation = false) {
|
|
486
|
+
const payload = record(envelope.payload);
|
|
487
|
+
const repository = payload ? repositoryName(payload) ?? "unknown" : "unknown";
|
|
488
|
+
const source = payload ? sender(payload) : { login: "unknown" };
|
|
489
|
+
const body = envelope.body ?? JSON.stringify(envelope.payload);
|
|
490
|
+
const delivery = {
|
|
491
|
+
deliveryId: envelope.deliveryId,
|
|
492
|
+
eventName: envelope.eventName,
|
|
493
|
+
repository,
|
|
494
|
+
senderLogin: source.login,
|
|
495
|
+
receivedAt: envelope.receivedAt,
|
|
496
|
+
payloadHash: payloadHash(body),
|
|
497
|
+
payload: envelope.payload,
|
|
498
|
+
status: "received"
|
|
499
|
+
};
|
|
500
|
+
const stored = await this.options.deliveryStore.recordDeliveryIfAbsent(delivery);
|
|
501
|
+
if (!stored.inserted && stored.delivery.status !== "received") {
|
|
502
|
+
return { status: "duplicate", deliveryId: envelope.deliveryId };
|
|
503
|
+
}
|
|
504
|
+
const effectiveEnvelope = stored.inserted ? envelope : {
|
|
505
|
+
...envelope,
|
|
506
|
+
payload: stored.delivery.payload,
|
|
507
|
+
receivedAt: stored.delivery.receivedAt,
|
|
508
|
+
body: JSON.stringify(stored.delivery.payload)
|
|
509
|
+
};
|
|
510
|
+
const effectiveDelivery = stored.inserted ? delivery : stored.delivery;
|
|
511
|
+
if (!allowedRepository(
|
|
512
|
+
this.options.authorization,
|
|
513
|
+
effectiveDelivery.repository
|
|
514
|
+
)) {
|
|
515
|
+
return this.rejectStored(effectiveDelivery, "disallowed-repository");
|
|
516
|
+
}
|
|
517
|
+
const effectivePayload = record(effectiveEnvelope.payload);
|
|
518
|
+
const effectiveSource = effectivePayload ? sender(effectivePayload) : { login: "unknown" };
|
|
519
|
+
if (!trustedReconciliation && isBot(effectiveSource, this.options.authorization)) {
|
|
520
|
+
return this.ignoreStored(effectiveDelivery, "bot-originated");
|
|
521
|
+
}
|
|
522
|
+
if (!trustedReconciliation) {
|
|
523
|
+
const isReview = effectiveEnvelope.eventName === "pull_request_review";
|
|
524
|
+
const authorized = isReview ? allowedReviewer(this.options.authorization, effectiveSource) : allowedSender(this.options.authorization, effectiveSource);
|
|
525
|
+
if (!authorized) {
|
|
526
|
+
return this.rejectStored(
|
|
527
|
+
effectiveDelivery,
|
|
528
|
+
isReview ? "disallowed-reviewer" : "disallowed-sender"
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const normalized = await this.normalize(effectiveEnvelope);
|
|
533
|
+
if (normalized.disposition === "ignored") {
|
|
534
|
+
return this.ignoreStored(effectiveDelivery, normalized.reason);
|
|
535
|
+
}
|
|
536
|
+
if (normalized.event.review !== void 0) {
|
|
537
|
+
const tracked = normalized.event.trackedPullRequest;
|
|
538
|
+
const handler = this.options.reviewHandler;
|
|
539
|
+
const decision = reviewDecisionFor(normalized.event.review);
|
|
540
|
+
if (handler === void 0) {
|
|
541
|
+
return this.rejectStored(
|
|
542
|
+
effectiveDelivery,
|
|
543
|
+
"review-handler-unconfigured"
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
if (tracked === void 0) {
|
|
547
|
+
return this.rejectStored(
|
|
548
|
+
effectiveDelivery,
|
|
549
|
+
"review-missing-tracked-pull-request"
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
if (decision === void 0) {
|
|
553
|
+
return this.ignoreStored(effectiveDelivery, "review-without-decision");
|
|
554
|
+
}
|
|
555
|
+
const candidate = {
|
|
556
|
+
base: tracked.brief.base,
|
|
557
|
+
head: {
|
|
558
|
+
branch: tracked.branch,
|
|
559
|
+
sha: normalized.event.review.headSha
|
|
560
|
+
},
|
|
561
|
+
briefHash: tracked.brief.hash
|
|
562
|
+
};
|
|
563
|
+
let reviewResult;
|
|
564
|
+
try {
|
|
565
|
+
reviewResult = await processHumanReviewDecision({
|
|
566
|
+
candidate,
|
|
567
|
+
decision,
|
|
568
|
+
pullRequestNumber: tracked.pullRequestNumber,
|
|
569
|
+
readCurrent: () => handler.readCurrent({
|
|
570
|
+
candidate,
|
|
571
|
+
trackedPullRequest: tracked
|
|
572
|
+
}),
|
|
573
|
+
requestRepair: handler.requestRepair
|
|
574
|
+
});
|
|
575
|
+
} catch (error) {
|
|
576
|
+
return this.rejectStored(
|
|
577
|
+
effectiveDelivery,
|
|
578
|
+
`review-handler-failed: ${error instanceof Error ? error.message : String(error)}`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
const status = reviewResult.outcome === "blocked" ? "ignored" : "accepted";
|
|
582
|
+
await this.options.deliveryStore.updateDelivery({
|
|
583
|
+
...effectiveDelivery,
|
|
584
|
+
status,
|
|
585
|
+
eventKind: normalized.event.kind,
|
|
586
|
+
jobId: tracked.jobId,
|
|
587
|
+
reason: reviewResult.reason
|
|
588
|
+
});
|
|
589
|
+
return {
|
|
590
|
+
status,
|
|
591
|
+
deliveryId: envelope.deliveryId,
|
|
592
|
+
event: normalized.event,
|
|
593
|
+
review: reviewResult,
|
|
594
|
+
reason: reviewResult.reason
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
const ingest = await this.options.coordinator.ingest(
|
|
598
|
+
normalized.event.workflowEvent
|
|
599
|
+
);
|
|
600
|
+
await this.options.deliveryStore.updateDelivery({
|
|
601
|
+
...effectiveDelivery,
|
|
602
|
+
status: ingest.disposition === "ignored" ? "ignored" : "accepted",
|
|
603
|
+
eventKind: normalized.event.kind,
|
|
604
|
+
jobId: ingest.job?.id,
|
|
605
|
+
reason: ingest.reason
|
|
606
|
+
});
|
|
607
|
+
return {
|
|
608
|
+
status: ingest.disposition === "duplicate" ? "duplicate" : ingest.disposition === "ignored" ? "ignored" : "accepted",
|
|
609
|
+
deliveryId: envelope.deliveryId,
|
|
610
|
+
event: normalized.event,
|
|
611
|
+
ingest,
|
|
612
|
+
reason: ingest.reason
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
async rejectStored(delivery, reason) {
|
|
616
|
+
await this.options.deliveryStore.updateDelivery({
|
|
617
|
+
...delivery,
|
|
618
|
+
status: "rejected",
|
|
619
|
+
reason
|
|
620
|
+
});
|
|
621
|
+
return { status: "rejected", deliveryId: delivery.deliveryId, reason };
|
|
622
|
+
}
|
|
623
|
+
async ignoreStored(delivery, reason) {
|
|
624
|
+
await this.options.deliveryStore.updateDelivery({
|
|
625
|
+
...delivery,
|
|
626
|
+
status: "ignored",
|
|
627
|
+
reason
|
|
628
|
+
});
|
|
629
|
+
return { status: "ignored", deliveryId: delivery.deliveryId, reason };
|
|
630
|
+
}
|
|
631
|
+
async briefForIssue(draft, issue, kind) {
|
|
632
|
+
const identity = {
|
|
633
|
+
repository: draft.repository,
|
|
634
|
+
itemId: String(issue.number),
|
|
635
|
+
kind
|
|
636
|
+
};
|
|
637
|
+
const current = await this.options.coordinator.getCurrentJob(identity);
|
|
638
|
+
if (this.options.triage === void 0 && current !== void 0 && current.control === "active" && sameIssueContent(current.brief, issue)) {
|
|
639
|
+
return { brief: current.brief };
|
|
640
|
+
}
|
|
641
|
+
if (this.options.triage !== void 0) {
|
|
642
|
+
const source = {
|
|
643
|
+
provider: "github",
|
|
644
|
+
repository: draft.repository,
|
|
645
|
+
itemId: String(issue.number),
|
|
646
|
+
title: issue.title,
|
|
647
|
+
body: issue.body,
|
|
648
|
+
author: issue.authorLogin ?? draft.sender.login,
|
|
649
|
+
url: issue.htmlUrl,
|
|
650
|
+
updatedAt: issue.updatedAt || draft.observedAt,
|
|
651
|
+
kind,
|
|
652
|
+
labels: issue.labels
|
|
653
|
+
};
|
|
654
|
+
const triage = await runTriage({
|
|
655
|
+
source,
|
|
656
|
+
policy: this.options.policy,
|
|
657
|
+
base: this.options.base,
|
|
658
|
+
store: this.options.triage.store,
|
|
659
|
+
investigator: this.options.triage.investigator,
|
|
660
|
+
clarificationReply: draft.kind === "issue-replied" && draft.reply !== void 0 ? {
|
|
661
|
+
id: draft.reply.id,
|
|
662
|
+
body: draft.reply.body,
|
|
663
|
+
author: draft.reply.authorLogin,
|
|
664
|
+
updatedAt: draft.reply.updatedAt
|
|
665
|
+
} : void 0,
|
|
666
|
+
now: this.now
|
|
667
|
+
});
|
|
668
|
+
if (triage.record.sourceConflict !== void 0) {
|
|
669
|
+
return { ignoredReason: "triage-source-conflict" };
|
|
670
|
+
}
|
|
671
|
+
if (triage.brief !== void 0) {
|
|
672
|
+
if (triage.brief.authorization.status === "approved") {
|
|
673
|
+
return {
|
|
674
|
+
brief: createWorkBrief({
|
|
675
|
+
...triage.brief,
|
|
676
|
+
authorization: { status: "pending" }
|
|
677
|
+
})
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
return { brief: triage.brief };
|
|
681
|
+
}
|
|
682
|
+
return { ignoredReason: "triage-incomplete" };
|
|
683
|
+
}
|
|
684
|
+
const event = draft;
|
|
685
|
+
const revision = current === void 0 ? 1 : current.brief.revision + 1;
|
|
686
|
+
const factory = this.options.briefFactory ?? createGitHubIssueBrief;
|
|
687
|
+
const candidate = factory({
|
|
688
|
+
event,
|
|
689
|
+
itemKind: kind,
|
|
690
|
+
policy: this.options.policy,
|
|
691
|
+
base: this.options.base,
|
|
692
|
+
authorization: { status: "pending" },
|
|
693
|
+
revision,
|
|
694
|
+
defaults: this.options.briefDefaults
|
|
695
|
+
});
|
|
696
|
+
const brief = parseWorkBrief(candidate);
|
|
697
|
+
if (brief.authorization.status !== "pending") {
|
|
698
|
+
throw new Error(
|
|
699
|
+
"GitHub intake cannot authorize work from webhook content"
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
return { brief };
|
|
703
|
+
}
|
|
704
|
+
async normalizeTrackedPullRequest(envelope, payload, pullRequestNumberValue, reply, review) {
|
|
705
|
+
const repository = repositoryName(payload) ?? "unknown";
|
|
706
|
+
const source = sender(payload);
|
|
707
|
+
const trackingStore = this.options.trackingStore;
|
|
708
|
+
const tracked = trackingStore ? await trackingStore.findTrackedPullRequest(
|
|
709
|
+
repository,
|
|
710
|
+
pullRequestNumberValue
|
|
711
|
+
) : void 0;
|
|
712
|
+
if (tracked === void 0) {
|
|
713
|
+
return ignored({
|
|
714
|
+
reason: "unrelated-pull-request",
|
|
715
|
+
eventName: envelope.eventName,
|
|
716
|
+
action: stringValue(payload.action),
|
|
717
|
+
deliveryId: envelope.deliveryId,
|
|
718
|
+
repository,
|
|
719
|
+
sender: source
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
const pullRequest = record(payload.pull_request);
|
|
723
|
+
const observedAt = requiredStringValue(pullRequest?.updated_at) ?? requiredStringValue(record(payload.issue)?.updated_at) ?? reply?.updatedAt ?? envelope.receivedAt;
|
|
724
|
+
const headSha = pullRequestHead(payload, tracked.headSha);
|
|
725
|
+
const sourceState = state(pullRequest?.state);
|
|
726
|
+
const event = {
|
|
727
|
+
kind: "tracked-pr-updated",
|
|
728
|
+
eventName: eventName(envelope.eventName) ?? "pull_request",
|
|
729
|
+
action: stringValue(payload.action),
|
|
730
|
+
deliveryId: envelope.deliveryId,
|
|
731
|
+
repository,
|
|
732
|
+
sender: source,
|
|
733
|
+
issueNumber: Number(tracked.itemId) || pullRequestNumberValue,
|
|
734
|
+
pullRequestNumber: pullRequestNumberValue,
|
|
735
|
+
title: stringValue(pullRequest?.title, tracked.brief.problem),
|
|
736
|
+
body: stringValue(pullRequest?.body, tracked.brief.source.originalBody),
|
|
737
|
+
labels: labels(pullRequest?.labels),
|
|
738
|
+
relevantRevision: headSha,
|
|
739
|
+
observedAt,
|
|
740
|
+
sourceState,
|
|
741
|
+
reply,
|
|
742
|
+
review,
|
|
743
|
+
trackedPullRequest: tracked
|
|
744
|
+
};
|
|
745
|
+
return {
|
|
746
|
+
disposition: "accepted",
|
|
747
|
+
event: {
|
|
748
|
+
...event,
|
|
749
|
+
workflowEvent: {
|
|
750
|
+
deliveryId: envelope.deliveryId,
|
|
751
|
+
brief: tracked.brief,
|
|
752
|
+
policy: tracked.policy,
|
|
753
|
+
phase: "checking",
|
|
754
|
+
relevantRevision: headSha,
|
|
755
|
+
observedAt,
|
|
756
|
+
sourceState,
|
|
757
|
+
payload: envelope.payload
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
issuePayload(issue, repository) {
|
|
763
|
+
return {
|
|
764
|
+
action: issue.state === "closed" ? "closed" : "edited",
|
|
765
|
+
issue: {
|
|
766
|
+
number: issue.number,
|
|
767
|
+
title: issue.title,
|
|
768
|
+
body: issue.body,
|
|
769
|
+
state: issue.state,
|
|
770
|
+
updated_at: issue.updatedAt,
|
|
771
|
+
html_url: issue.htmlUrl,
|
|
772
|
+
user: issue.authorLogin ? { login: issue.authorLogin } : void 0,
|
|
773
|
+
labels: issue.labels.map((name) => ({ name })),
|
|
774
|
+
pull_request: issue.pullRequestNumber === void 0 ? void 0 : { number: issue.pullRequestNumber }
|
|
775
|
+
},
|
|
776
|
+
repository: { full_name: repository },
|
|
777
|
+
sender: {
|
|
778
|
+
login: this.options.authorization.allowedSenders[0] ?? "shipyard"
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
pullRequestPayload(pullRequest, repository) {
|
|
783
|
+
return {
|
|
784
|
+
action: "synchronize",
|
|
785
|
+
pull_request: {
|
|
786
|
+
number: pullRequest.number,
|
|
787
|
+
title: pullRequest.title,
|
|
788
|
+
body: pullRequest.body,
|
|
789
|
+
state: pullRequest.state,
|
|
790
|
+
updated_at: pullRequest.updatedAt,
|
|
791
|
+
html_url: pullRequest.htmlUrl,
|
|
792
|
+
user: pullRequest.authorLogin ? { login: pullRequest.authorLogin } : void 0,
|
|
793
|
+
head: { ref: pullRequest.branch, sha: pullRequest.headSha },
|
|
794
|
+
base: { ref: pullRequest.baseBranch }
|
|
795
|
+
},
|
|
796
|
+
repository: { full_name: repository },
|
|
797
|
+
sender: {
|
|
798
|
+
login: this.options.authorization.allowedSenders[0] ?? "shipyard"
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
toReconciliationResult(receipt) {
|
|
803
|
+
return {
|
|
804
|
+
status: receipt.status === "rejected" ? "ignored" : receipt.status,
|
|
805
|
+
deliveryId: receipt.deliveryId,
|
|
806
|
+
reason: receipt.reason,
|
|
807
|
+
event: receipt.event,
|
|
808
|
+
ingest: receipt.ingest
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
var includesCaseInsensitive = (candidates, value) => candidates.some(
|
|
813
|
+
(candidate) => candidate.toLowerCase() === value.toLowerCase()
|
|
814
|
+
);
|
|
815
|
+
var allowedRepository = (policy, repository) => includesCaseInsensitive(policy.allowedRepositories, repository);
|
|
816
|
+
var allowedSender = (policy, source) => includesCaseInsensitive(policy.allowedSenders, source.login);
|
|
817
|
+
var allowedReviewer = (policy, source) => includesCaseInsensitive(policy.allowedReviewers, source.login);
|
|
818
|
+
var isBot = (source, policy) => source.type === "Bot" || includesCaseInsensitive(policy.botLogins ?? [], source.login);
|
|
819
|
+
var markerText = (marker) => `<!-- shipyard:${marker} -->`;
|
|
820
|
+
var markerPart = (value) => encodeURIComponent(String(value));
|
|
821
|
+
var markerHash = (value) => createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
822
|
+
var result = (marker, execution) => ({
|
|
823
|
+
marker,
|
|
824
|
+
remote: execution.externalRef,
|
|
825
|
+
disposition: execution.disposition,
|
|
826
|
+
effect: execution.effect
|
|
827
|
+
});
|
|
828
|
+
var GitHubPublication = class {
|
|
829
|
+
options;
|
|
830
|
+
constructor(options) {
|
|
831
|
+
this.options = options;
|
|
832
|
+
}
|
|
833
|
+
async publishComment(input) {
|
|
834
|
+
const marker = `comment:${markerPart(input.jobId)}:${markerPart(input.key ?? "default")}`;
|
|
835
|
+
const body = `${markerText(marker)}
|
|
836
|
+
${input.body}`;
|
|
837
|
+
const execution = await this.options.coordinator.publishEffect({
|
|
838
|
+
jobId: input.jobId,
|
|
839
|
+
lease: input.lease,
|
|
840
|
+
itemId: String(input.issueNumber),
|
|
841
|
+
kind: "github-comment",
|
|
842
|
+
marker,
|
|
843
|
+
payload: {
|
|
844
|
+
repository: input.lease.repository,
|
|
845
|
+
issueNumber: input.issueNumber
|
|
846
|
+
},
|
|
847
|
+
reconcile: () => this.options.transport.findCommentByMarker({
|
|
848
|
+
repository: input.lease.repository,
|
|
849
|
+
issueNumber: input.issueNumber,
|
|
850
|
+
marker: markerText(marker)
|
|
851
|
+
}),
|
|
852
|
+
publish: () => this.options.transport.createComment({
|
|
853
|
+
repository: input.lease.repository,
|
|
854
|
+
issueNumber: input.issueNumber,
|
|
855
|
+
body
|
|
856
|
+
})
|
|
857
|
+
});
|
|
858
|
+
return result(marker, execution);
|
|
859
|
+
}
|
|
860
|
+
async publishBrief(input) {
|
|
861
|
+
const marker = `brief:${markerPart(input.jobId)}:${markerPart(input.brief.revision)}`;
|
|
862
|
+
const body = [
|
|
863
|
+
markerText(marker),
|
|
864
|
+
"## Shipyard executable brief",
|
|
865
|
+
`- Revision: ${input.brief.revision}`,
|
|
866
|
+
`- Brief hash: \`${input.brief.hash}\``,
|
|
867
|
+
`- Risk: ${input.brief.risk}`,
|
|
868
|
+
"",
|
|
869
|
+
input.brief.problem,
|
|
870
|
+
"",
|
|
871
|
+
"### Acceptance criteria",
|
|
872
|
+
...input.brief.acceptanceCriteria.map((criterion) => `- ${criterion}`),
|
|
873
|
+
"",
|
|
874
|
+
"### Unresolved questions",
|
|
875
|
+
...input.brief.unresolvedQuestions.map((question) => `- ${question}`)
|
|
876
|
+
].join("\n");
|
|
877
|
+
const execution = await this.options.coordinator.publishEffect({
|
|
878
|
+
jobId: input.jobId,
|
|
879
|
+
lease: input.lease,
|
|
880
|
+
itemId: String(input.issueNumber),
|
|
881
|
+
kind: "github-brief",
|
|
882
|
+
marker,
|
|
883
|
+
payload: {
|
|
884
|
+
repository: input.lease.repository,
|
|
885
|
+
issueNumber: input.issueNumber
|
|
886
|
+
},
|
|
887
|
+
reconcile: () => this.options.transport.findCommentByMarker({
|
|
888
|
+
repository: input.lease.repository,
|
|
889
|
+
issueNumber: input.issueNumber,
|
|
890
|
+
marker: markerText(marker)
|
|
891
|
+
}),
|
|
892
|
+
publish: () => this.options.transport.createComment({
|
|
893
|
+
repository: input.lease.repository,
|
|
894
|
+
issueNumber: input.issueNumber,
|
|
895
|
+
body
|
|
896
|
+
})
|
|
897
|
+
});
|
|
898
|
+
return result(marker, execution);
|
|
899
|
+
}
|
|
900
|
+
async publishBranch(input) {
|
|
901
|
+
const marker = `branch:${markerPart(input.jobId)}:${markerPart(input.branch)}`;
|
|
902
|
+
const execution = await this.options.coordinator.publishEffect({
|
|
903
|
+
jobId: input.jobId,
|
|
904
|
+
lease: input.lease,
|
|
905
|
+
branch: input.branch,
|
|
906
|
+
headSha: input.headSha,
|
|
907
|
+
kind: "github-branch",
|
|
908
|
+
marker,
|
|
909
|
+
payload: { repository: input.lease.repository, branch: input.branch },
|
|
910
|
+
reconcile: () => this.options.transport.findBranchByName({
|
|
911
|
+
repository: input.lease.repository,
|
|
912
|
+
branch: input.branch
|
|
913
|
+
}),
|
|
914
|
+
publish: () => this.options.transport.createBranch({
|
|
915
|
+
repository: input.lease.repository,
|
|
916
|
+
branch: input.branch,
|
|
917
|
+
headSha: input.headSha,
|
|
918
|
+
marker: markerText(marker)
|
|
919
|
+
})
|
|
920
|
+
});
|
|
921
|
+
return result(marker, execution);
|
|
922
|
+
}
|
|
923
|
+
async publishPullRequest(input) {
|
|
924
|
+
const marker = `pull-request:${markerPart(input.jobId)}:${markerPart(input.branch)}`;
|
|
925
|
+
const execution = await this.options.coordinator.publishEffect({
|
|
926
|
+
jobId: input.jobId,
|
|
927
|
+
lease: input.lease,
|
|
928
|
+
branch: input.branch,
|
|
929
|
+
headSha: input.headSha,
|
|
930
|
+
kind: "github-pull-request",
|
|
931
|
+
marker,
|
|
932
|
+
payload: { repository: input.lease.repository, branch: input.branch },
|
|
933
|
+
reconcile: () => this.options.transport.findPullRequestByMarker({
|
|
934
|
+
repository: input.lease.repository,
|
|
935
|
+
marker: markerText(marker)
|
|
936
|
+
}),
|
|
937
|
+
publish: () => this.options.transport.createPullRequest({
|
|
938
|
+
repository: input.lease.repository,
|
|
939
|
+
title: input.title,
|
|
940
|
+
body: `${markerText(marker)}
|
|
941
|
+
${input.body}`,
|
|
942
|
+
branch: input.branch,
|
|
943
|
+
baseBranch: input.baseBranch,
|
|
944
|
+
draft: input.draft ?? true,
|
|
945
|
+
marker: markerText(marker)
|
|
946
|
+
})
|
|
947
|
+
});
|
|
948
|
+
const publication = result(marker, execution);
|
|
949
|
+
if (publication.remote !== void 0 && publication.remote.branch === input.branch && publication.remote.baseBranch === input.baseBranch && publication.remote.headSha === input.headSha) {
|
|
950
|
+
const job = await this.options.coordinator.getJob(input.jobId);
|
|
951
|
+
if (job !== void 0) {
|
|
952
|
+
await this.options.trackingStore.saveTrackedPullRequest({
|
|
953
|
+
repository: input.lease.repository,
|
|
954
|
+
pullRequestNumber: publication.remote.number,
|
|
955
|
+
jobId: input.jobId,
|
|
956
|
+
itemId: job.key.itemId,
|
|
957
|
+
branch: input.branch,
|
|
958
|
+
headSha: input.headSha,
|
|
959
|
+
marker,
|
|
960
|
+
brief: job.brief,
|
|
961
|
+
policy: job.policy,
|
|
962
|
+
createdAt: this.options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return publication;
|
|
967
|
+
}
|
|
968
|
+
async publishCheck(input) {
|
|
969
|
+
const marker = `check:${markerPart(input.jobId)}:${markerPart(input.key ?? input.name)}:${markerPart(input.headSha)}`;
|
|
970
|
+
const execution = await this.options.coordinator.publishEffect({
|
|
971
|
+
jobId: input.jobId,
|
|
972
|
+
lease: input.lease,
|
|
973
|
+
branch: input.branch,
|
|
974
|
+
headSha: input.headSha,
|
|
975
|
+
kind: "github-check",
|
|
976
|
+
marker,
|
|
977
|
+
payload: { repository: input.lease.repository, name: input.name },
|
|
978
|
+
reconcile: () => this.options.transport.findCheckByMarker({
|
|
979
|
+
repository: input.lease.repository,
|
|
980
|
+
marker: markerText(marker),
|
|
981
|
+
headSha: input.headSha
|
|
982
|
+
}),
|
|
983
|
+
publish: () => this.options.transport.createCheck({
|
|
984
|
+
repository: input.lease.repository,
|
|
985
|
+
name: input.name,
|
|
986
|
+
headSha: input.headSha,
|
|
987
|
+
marker: markerText(marker),
|
|
988
|
+
status: input.status,
|
|
989
|
+
conclusion: input.conclusion,
|
|
990
|
+
summary: input.summary
|
|
991
|
+
})
|
|
992
|
+
});
|
|
993
|
+
return result(marker, execution);
|
|
994
|
+
}
|
|
995
|
+
async publishRepairIssue(input) {
|
|
996
|
+
const marker = `repair-issue:${markerPart(input.jobId)}:${markerHash(input.title)}`;
|
|
997
|
+
const execution = await this.options.coordinator.publishEffect({
|
|
998
|
+
jobId: input.jobId,
|
|
999
|
+
lease: input.lease,
|
|
1000
|
+
kind: "github-repair-issue",
|
|
1001
|
+
marker,
|
|
1002
|
+
payload: { repository: input.lease.repository },
|
|
1003
|
+
reconcile: () => this.options.transport.findIssueByMarker({
|
|
1004
|
+
repository: input.lease.repository,
|
|
1005
|
+
marker: markerText(marker)
|
|
1006
|
+
}),
|
|
1007
|
+
publish: () => this.options.transport.createRepairIssue({
|
|
1008
|
+
repository: input.lease.repository,
|
|
1009
|
+
title: input.title,
|
|
1010
|
+
body: `${markerText(marker)}
|
|
1011
|
+
${input.body}`,
|
|
1012
|
+
marker: markerText(marker),
|
|
1013
|
+
labels: input.labels ?? ["shipyard:pr-repair"]
|
|
1014
|
+
})
|
|
1015
|
+
});
|
|
1016
|
+
return result(marker, execution);
|
|
1017
|
+
}
|
|
1018
|
+
async publishRepairLink(input) {
|
|
1019
|
+
const marker = `repair-link:${markerPart(input.jobId)}:${markerHash(input.repairIssueUrl)}`;
|
|
1020
|
+
const body = `${markerText(marker)}
|
|
1021
|
+
Linked repair issue: ${input.repairIssueUrl}`;
|
|
1022
|
+
const execution = await this.options.coordinator.publishEffect({
|
|
1023
|
+
jobId: input.jobId,
|
|
1024
|
+
lease: input.lease,
|
|
1025
|
+
itemId: String(input.issueNumber),
|
|
1026
|
+
kind: "github-repair-link",
|
|
1027
|
+
marker,
|
|
1028
|
+
payload: {
|
|
1029
|
+
repository: input.lease.repository,
|
|
1030
|
+
issueNumber: input.issueNumber
|
|
1031
|
+
},
|
|
1032
|
+
reconcile: () => this.options.transport.findCommentByMarker({
|
|
1033
|
+
repository: input.lease.repository,
|
|
1034
|
+
issueNumber: input.issueNumber,
|
|
1035
|
+
marker: markerText(marker)
|
|
1036
|
+
}),
|
|
1037
|
+
publish: () => this.options.transport.createComment({
|
|
1038
|
+
repository: input.lease.repository,
|
|
1039
|
+
issueNumber: input.issueNumber,
|
|
1040
|
+
body
|
|
1041
|
+
})
|
|
1042
|
+
});
|
|
1043
|
+
return result(marker, execution);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
export { GitHubIntegration, GitHubPublication, InMemoryGitHubStore, createGitHubIssueBrief, reviewDecisionFor, verifyGitHubWebhookSignature };
|
|
1048
|
+
//# sourceMappingURL=github.js.map
|
|
1049
|
+
//# sourceMappingURL=github.js.map
|