lastlight-evals 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/datasets/dependency-merge/README.md +69 -0
- package/datasets/dependency-merge/instances.json +168 -0
- package/datasets/dependency-merge/tier.json +5 -0
- package/datasets/fix/README.md +57 -0
- package/datasets/fix/instances.json +229 -0
- package/datasets/fix/repos/fix__env-mismatch-node-version/.github/workflows/ci.yml +15 -0
- package/datasets/fix/repos/fix__env-mismatch-node-version/README.md +3 -0
- package/datasets/fix/repos/fix__env-mismatch-node-version/package.json +15 -0
- package/datasets/fix/repos/fix__env-mismatch-node-version/src/group.js +4 -0
- package/datasets/fix/repos/fix__env-mismatch-node-version/test/group.test.js +9 -0
- package/datasets/fix/repos/fix__flaky-promoted/.github/workflows/ci.yml +15 -0
- package/datasets/fix/repos/fix__flaky-promoted/README.md +3 -0
- package/datasets/fix/repos/fix__flaky-promoted/package.json +12 -0
- package/datasets/fix/repos/fix__flaky-promoted/src/poll.js +9 -0
- package/datasets/fix/repos/fix__flaky-promoted/test/poll.test.js +8 -0
- package/datasets/fix/repos/fix__flaky-timeout/.github/workflows/ci.yml +15 -0
- package/datasets/fix/repos/fix__flaky-timeout/README.md +3 -0
- package/datasets/fix/repos/fix__flaky-timeout/package.json +12 -0
- package/datasets/fix/repos/fix__flaky-timeout/src/poll.js +9 -0
- package/datasets/fix/repos/fix__flaky-timeout/test/poll.test.js +8 -0
- package/datasets/fix/repos/fix__infra-dependent-e2e/.github/workflows/ci.yml +28 -0
- package/datasets/fix/repos/fix__infra-dependent-e2e/README.md +3 -0
- package/datasets/fix/repos/fix__infra-dependent-e2e/e2e/smoke.test.js +10 -0
- package/datasets/fix/repos/fix__infra-dependent-e2e/package.json +12 -0
- package/datasets/fix/repos/fix__infra-dependent-e2e/src/retry.js +13 -0
- package/datasets/fix/repos/fix__infra-dependent-e2e/test/retry.test.js +8 -0
- package/datasets/fix/repos/fix__reproducible-api-removal/.github/workflows/ci.yml +15 -0
- package/datasets/fix/repos/fix__reproducible-api-removal/README.md +3 -0
- package/datasets/fix/repos/fix__reproducible-api-removal/package.json +15 -0
- package/datasets/fix/repos/fix__reproducible-api-removal/src/slug.js +6 -0
- package/datasets/fix/repos/fix__reproducible-api-removal/test/slug.test.js +7 -0
- package/datasets/fix/repos/fix__reproducible-api-removal/vendor/tiny-case/index.js +4 -0
- package/datasets/fix/repos/fix__upstream-broken-base-red/.github/workflows/ci.yml +15 -0
- package/datasets/fix/repos/fix__upstream-broken-base-red/README.md +3 -0
- package/datasets/fix/repos/fix__upstream-broken-base-red/package.json +12 -0
- package/datasets/fix/repos/fix__upstream-broken-base-red/src/format.js +6 -0
- package/datasets/fix/repos/fix__upstream-broken-base-red/src/rates.js +6 -0
- package/datasets/fix/repos/fix__upstream-broken-base-red/test/format.test.js +7 -0
- package/datasets/fix/repos/fix__upstream-broken-base-red/test/rates.test.js +7 -0
- package/datasets/fix/repos-head/fix__env-mismatch-node-version/package.json +15 -0
- package/datasets/fix/repos-head/fix__flaky-promoted/package.json +12 -0
- package/datasets/fix/repos-head/fix__flaky-timeout/package.json +12 -0
- package/datasets/fix/repos-head/fix__infra-dependent-e2e/package.json +12 -0
- package/datasets/fix/repos-head/fix__reproducible-api-removal/package.json +15 -0
- package/datasets/fix/repos-head/fix__reproducible-api-removal/vendor/tiny-case/index.js +4 -0
- package/datasets/fix/repos-head/fix__upstream-broken-base-red/package.json +12 -0
- package/datasets/fix/tier.json +5 -0
- package/dist/fake-github.js +160 -5
- package/dist/fake-github.js.map +1 -1
- package/dist/grade.js +90 -1
- package/dist/grade.js.map +1 -1
- package/dist/mechanism.test.js +230 -1
- package/dist/mechanism.test.js.map +1 -1
- package/dist/pr-context.js +162 -0
- package/dist/pr-context.js.map +1 -0
- package/dist/run-instance.js +86 -3
- package/dist/run-instance.js.map +1 -1
- package/dist/run.js +8 -0
- package/dist/run.js.map +1 -1
- package/dist/schema.js +0 -13
- package/dist/schema.js.map +1 -1
- package/dist/seed.js +7 -0
- package/dist/seed.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Resolve once `check()` returns true, polling every `intervalMs`. */
|
|
2
|
+
export async function pollUntil(check, { intervalMs = 10, timeoutMs = 200 } = {}) {
|
|
3
|
+
const deadline = Date.now() + timeoutMs;
|
|
4
|
+
for (;;) {
|
|
5
|
+
if (await check()) return true;
|
|
6
|
+
if (Date.now() > deadline) throw new Error("pollUntil: timed out");
|
|
7
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-node@v4
|
|
13
|
+
with:
|
|
14
|
+
node-version: "22"
|
|
15
|
+
- run: npm test
|
|
16
|
+
|
|
17
|
+
e2e:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: "22"
|
|
24
|
+
# Needs the deployed staging API and its credentials.
|
|
25
|
+
- run: node --test e2e/
|
|
26
|
+
env:
|
|
27
|
+
STAGING_BASE_URL: ${{ secrets.STAGING_BASE_URL }}
|
|
28
|
+
STAGING_TOKEN: ${{ secrets.STAGING_TOKEN }}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
// Runs only in the `e2e` job, against the deployed staging API.
|
|
5
|
+
test("staging answers /health", async () => {
|
|
6
|
+
const base = process.env.STAGING_BASE_URL;
|
|
7
|
+
assert.ok(base, "STAGING_BASE_URL is not set");
|
|
8
|
+
const res = await fetch(new URL("/health", base));
|
|
9
|
+
assert.equal(res.status, 200);
|
|
10
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Retry `fn` up to `attempts` times with a fixed delay. */
|
|
2
|
+
export async function retry(fn, { attempts = 3, delayMs = 0 } = {}) {
|
|
3
|
+
let last;
|
|
4
|
+
for (let i = 0; i < attempts; i++) {
|
|
5
|
+
try {
|
|
6
|
+
return await fn();
|
|
7
|
+
} catch (err) {
|
|
8
|
+
last = err;
|
|
9
|
+
if (delayMs) await new Promise((r) => setTimeout(r, delayMs));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
throw last;
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { retry } from "../src/retry.js";
|
|
4
|
+
|
|
5
|
+
test("retries until it succeeds", async () => {
|
|
6
|
+
let n = 0;
|
|
7
|
+
assert.equal(await retry(() => (++n < 3 ? Promise.reject(new Error("no")) : "ok")), "ok");
|
|
8
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "slugger",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Slug helper.",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node --test"
|
|
8
|
+
},
|
|
9
|
+
"imports": {
|
|
10
|
+
"#tiny-case": "./vendor/tiny-case/index.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"tiny-case": "^2.4.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Convert minor units at a rate, rounding to whole minor units. */
|
|
2
|
+
export function convert(minorUnits, rate) {
|
|
3
|
+
// Regression landed on main in #874: the rounding was dropped, so this
|
|
4
|
+
// returns a fractional amount and every downstream total is wrong.
|
|
5
|
+
return minorUnits * rate;
|
|
6
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "slugger",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Slug helper.",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node --test"
|
|
8
|
+
},
|
|
9
|
+
"imports": {
|
|
10
|
+
"#tiny-case": "./vendor/tiny-case/index.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"tiny-case": "^3.0.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
package/dist/fake-github.js
CHANGED
|
@@ -181,6 +181,62 @@ export async function startFakeGitHub(opts) {
|
|
|
181
181
|
}
|
|
182
182
|
return { status: "ok", defaultBranch, treeSha: repoConfigTreeSha, etag: repoConfigEtag, files, truncated };
|
|
183
183
|
}
|
|
184
|
+
// ── Actions fixture ───────────────────────────────────────────────────
|
|
185
|
+
// Ids are synthetic but stable within a run, which is all the tools need:
|
|
186
|
+
// `github_list_workflow_runs` hands the agent a run id and it asks for that
|
|
187
|
+
// run's jobs, then that job's logs.
|
|
188
|
+
const ACTIONS_RUN_ID = 900001;
|
|
189
|
+
const actionsJobs = (opts.actions?.jobs ?? []).map((j, i) => ({
|
|
190
|
+
id: 800001 + i,
|
|
191
|
+
name: j.name,
|
|
192
|
+
conclusion: j.conclusion ?? "failure",
|
|
193
|
+
log: j.log,
|
|
194
|
+
workflowPath: j.workflowPath,
|
|
195
|
+
failingStep: j.failingStep,
|
|
196
|
+
}));
|
|
197
|
+
function serializeRun() {
|
|
198
|
+
const a = opts.actions;
|
|
199
|
+
return {
|
|
200
|
+
id: ACTIONS_RUN_ID,
|
|
201
|
+
name: a.workflowName ?? "CI",
|
|
202
|
+
head_sha: a.headSha,
|
|
203
|
+
head_branch: a.headBranch ?? defaultBranch,
|
|
204
|
+
path: a.jobs[0]?.workflowPath ?? ".github/workflows/ci.yml",
|
|
205
|
+
event: "pull_request",
|
|
206
|
+
status: "completed",
|
|
207
|
+
conclusion: "failure",
|
|
208
|
+
created_at: NOW,
|
|
209
|
+
updated_at: NOW,
|
|
210
|
+
html_url: `https://github.com/${owner}/${repo}/actions/runs/${ACTIONS_RUN_ID}`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function serializeJob(j) {
|
|
214
|
+
return {
|
|
215
|
+
id: j.id,
|
|
216
|
+
run_id: ACTIONS_RUN_ID,
|
|
217
|
+
name: j.name,
|
|
218
|
+
status: "completed",
|
|
219
|
+
conclusion: j.conclusion,
|
|
220
|
+
started_at: NOW,
|
|
221
|
+
completed_at: NOW,
|
|
222
|
+
html_url: `https://github.com/${owner}/${repo}/actions/runs/${ACTIONS_RUN_ID}/job/${j.id}`,
|
|
223
|
+
steps: j.failingStep
|
|
224
|
+
? [{ name: j.failingStep, status: "completed", conclusion: "failure", number: 1 }]
|
|
225
|
+
: [],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The PR's patch, assembled from the per-file `patch` hunks already seeded
|
|
230
|
+
* for `GET /pulls/:n/files`. One fixture, two representations — a case that
|
|
231
|
+
* declares its changed files gets both the file list and the diff, and they
|
|
232
|
+
* cannot disagree.
|
|
233
|
+
*/
|
|
234
|
+
function diffOf(prNumber) {
|
|
235
|
+
const files = pullFiles.get(prNumber) ?? [];
|
|
236
|
+
return files
|
|
237
|
+
.map((f) => `diff --git a/${f.filename} b/${f.filename}\n${f.patch ?? ""}`)
|
|
238
|
+
.join("\n");
|
|
239
|
+
}
|
|
184
240
|
const repoBase = `/repos/${owner}/${repo}`;
|
|
185
241
|
const server = createServer((req, res) => {
|
|
186
242
|
const method = req.method ?? "GET";
|
|
@@ -207,7 +263,8 @@ export async function startFakeGitHub(opts) {
|
|
|
207
263
|
res.end(text);
|
|
208
264
|
};
|
|
209
265
|
try {
|
|
210
|
-
const
|
|
266
|
+
const accept = String(req.headers.accept ?? "");
|
|
267
|
+
const handled = route(method, path, body, json, accept, res);
|
|
211
268
|
if (!handled)
|
|
212
269
|
json(404, { message: `fake-github: no route for ${method} ${path}` });
|
|
213
270
|
}
|
|
@@ -216,7 +273,73 @@ export async function startFakeGitHub(opts) {
|
|
|
216
273
|
}
|
|
217
274
|
});
|
|
218
275
|
});
|
|
219
|
-
function route(method, path, body, json
|
|
276
|
+
function route(method, path, body, json,
|
|
277
|
+
/** Request `Accept` — the diff media type is how `pulls.get` asks for a patch. */
|
|
278
|
+
accept, res) {
|
|
279
|
+
// ── GraphQL: the auto-merge mutation ────────────────────────────────
|
|
280
|
+
//
|
|
281
|
+
// `github_enable_auto_merge` has NO REST equivalent — GitHub exposes
|
|
282
|
+
// `enablePullRequestAutoMerge` only through GraphQL, so a REST-only fake
|
|
283
|
+
// 404s the one call the merge workflow's preferred path depends on, and the
|
|
284
|
+
// agent silently falls back to a direct merge. That would make every
|
|
285
|
+
// dependency-merge case measure the fallback.
|
|
286
|
+
if (method === "POST" && path === "/graphql") {
|
|
287
|
+
const q = String(body?.query ?? "");
|
|
288
|
+
const vars = (body?.variables ??
|
|
289
|
+
{});
|
|
290
|
+
if (q.includes("enablePullRequestAutoMerge")) {
|
|
291
|
+
// The node id the client resolved via REST is `PR_<number>` here (see
|
|
292
|
+
// `serializePull`), so the mutation can find the PR the same way.
|
|
293
|
+
const num = Number(String(vars.id ?? "").replace(/^PR_/, ""));
|
|
294
|
+
const pr = pulls.find((p) => p.number === num);
|
|
295
|
+
if (!pr) {
|
|
296
|
+
json(200, { data: null, errors: [{ message: `Could not resolve to a node: ${vars.id}` }] });
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
const method_ = String(vars.mergeMethod ?? "SQUASH").toLowerCase();
|
|
300
|
+
pr.autoMerge = { method: method_, enabledAt: NOW };
|
|
301
|
+
json(200, {
|
|
302
|
+
data: {
|
|
303
|
+
enablePullRequestAutoMerge: {
|
|
304
|
+
pullRequest: { number: pr.number, autoMergeRequest: { enabledAt: NOW } },
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
json(200, { data: null, errors: [{ message: `fake-github: unsupported GraphQL operation` }] });
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
// ── Actions (CI reads) ──────────────────────────────────────────────
|
|
314
|
+
//
|
|
315
|
+
// Served from the `actions` fixture, which is built from the same seed as
|
|
316
|
+
// the prompt's CI summary — so `github_get_job_logs` corroborates
|
|
317
|
+
// `{{ciSection}}` instead of contradicting it. With NO fixture these stay
|
|
318
|
+
// 404, which is the loud default the rest of this file keeps.
|
|
319
|
+
if (opts.actions && method === "GET" && path.startsWith(`${repoBase}/actions/`)) {
|
|
320
|
+
const rest = path.slice(`${repoBase}/actions/`.length);
|
|
321
|
+
if (rest === "runs" || /^workflows\/[^/]+\/runs$/.test(rest)) {
|
|
322
|
+
json(200, { total_count: 1, workflow_runs: [serializeRun()] });
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
const jobsOf = /^runs\/(\d+)\/jobs$/.exec(rest);
|
|
326
|
+
if (jobsOf) {
|
|
327
|
+
json(200, { total_count: actionsJobs.length, jobs: actionsJobs.map(serializeJob) });
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
const logsOf = /^jobs\/(\d+)\/logs$/.exec(rest);
|
|
331
|
+
if (logsOf) {
|
|
332
|
+
const job = actionsJobs.find((j) => j.id === Number(logsOf[1]));
|
|
333
|
+
if (!job)
|
|
334
|
+
return false;
|
|
335
|
+
// Real GitHub 302s to a signed blob URL and octokit follows it; a direct
|
|
336
|
+
// 200 with the text is the same thing to the caller.
|
|
337
|
+
res.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
|
|
338
|
+
res.end(job.log);
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
220
343
|
// GET /repos/:owner/:repo
|
|
221
344
|
if (method === "GET" && path === repoBase) {
|
|
222
345
|
json(200, {
|
|
@@ -353,8 +476,8 @@ export async function startFakeGitHub(opts) {
|
|
|
353
476
|
return true;
|
|
354
477
|
}
|
|
355
478
|
}
|
|
356
|
-
// Per-PR routes: /repos/:owner/:repo/pulls/:n[/reviews|/comments|/files]
|
|
357
|
-
const pullMatch = path.match(new RegExp(`^${escapeRe(repoBase)}/pulls/(\\d+)(/reviews|/comments|/files)?$`));
|
|
479
|
+
// Per-PR routes: /repos/:owner/:repo/pulls/:n[/reviews|/comments|/files|/merge]
|
|
480
|
+
const pullMatch = path.match(new RegExp(`^${escapeRe(repoBase)}/pulls/(\\d+)(/reviews|/comments|/files|/merge)?$`));
|
|
358
481
|
if (pullMatch) {
|
|
359
482
|
const num = Number(pullMatch[1]);
|
|
360
483
|
const sub = pullMatch[2];
|
|
@@ -363,11 +486,36 @@ export async function startFakeGitHub(opts) {
|
|
|
363
486
|
json(404, { message: `pull ${num} not found` });
|
|
364
487
|
return true;
|
|
365
488
|
}
|
|
366
|
-
// /pulls/:n
|
|
489
|
+
// /pulls/:n — JSON, or the raw patch when the caller asked for the diff
|
|
490
|
+
// media type. `github_get_pull_request_diff` is `pulls.get` with
|
|
491
|
+
// `mediaType: { format: "diff" }`, so serving JSON to it would hand the
|
|
492
|
+
// agent an object where it expects a patch — the impact assessment then
|
|
493
|
+
// reasons about the wrong artifact entirely.
|
|
367
494
|
if (!sub && method === "GET") {
|
|
495
|
+
if (/vnd\.github(\.[^+\s]*)?\.diff/.test(accept) || /vnd\.github\.v3\.diff/.test(accept)) {
|
|
496
|
+
// `charset=utf-8` is load-bearing: octokit decodes a response body as
|
|
497
|
+
// TEXT only for `application/json`, `text/*` or a `charset=utf-8`
|
|
498
|
+
// content-type, and hands anything else back as an ArrayBuffer.
|
|
499
|
+
res.writeHead(200, { "content-type": "application/vnd.github.v3.diff; charset=utf-8" });
|
|
500
|
+
res.end(diffOf(pr.number));
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
368
503
|
json(200, serializePull(pr));
|
|
369
504
|
return true;
|
|
370
505
|
}
|
|
506
|
+
// /pulls/:n/merge — the direct merge (`github_merge_pull_request`).
|
|
507
|
+
if (sub === "/merge" && method === "PUT") {
|
|
508
|
+
const b = (body ?? {});
|
|
509
|
+
if (pr.merged) {
|
|
510
|
+
json(405, { message: "Pull Request is not mergeable" });
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
pr.merged = true;
|
|
514
|
+
pr.state = "closed";
|
|
515
|
+
pr.mergedBy = { method: b.merge_method ?? "merge" };
|
|
516
|
+
json(200, { sha: pr.head.sha, merged: true, message: "Pull Request successfully merged" });
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
371
519
|
// /pulls/:n/reviews — list existing, or SUBMIT one (create_pull_request_review).
|
|
372
520
|
if (sub === "/reviews") {
|
|
373
521
|
if (method === "GET") {
|
|
@@ -419,10 +567,15 @@ export async function startFakeGitHub(opts) {
|
|
|
419
567
|
function serializePull(pr) {
|
|
420
568
|
return {
|
|
421
569
|
number: pr.number,
|
|
570
|
+
// The GraphQL node id `enablePullRequestAutoMerge` is given. The real one
|
|
571
|
+
// is opaque; the client only ever round-trips it from here to the
|
|
572
|
+
// mutation, so a derivable form keeps the fake stateless about it.
|
|
573
|
+
node_id: `PR_${pr.number}`,
|
|
422
574
|
title: pr.title,
|
|
423
575
|
body: pr.body,
|
|
424
576
|
state: pr.state,
|
|
425
577
|
merged: pr.merged,
|
|
578
|
+
auto_merge: pr.autoMerge ? { enabled_by: { login: "last-light[bot]" }, merge_method: pr.autoMerge.method } : null,
|
|
426
579
|
head: pr.head,
|
|
427
580
|
base: pr.base,
|
|
428
581
|
user: pr.user,
|
|
@@ -473,6 +626,8 @@ export async function startFakeGitHub(opts) {
|
|
|
473
626
|
setPullFiles: (n, files) => pullFiles.set(n, files),
|
|
474
627
|
fetchRepoConfigTree,
|
|
475
628
|
repoConfigFetches: () => repoConfigFetches,
|
|
629
|
+
mergeOf: (n) => pulls.find((p) => p.number === n)?.mergedBy,
|
|
630
|
+
autoMergeOf: (n) => pulls.find((p) => p.number === n)?.autoMerge,
|
|
476
631
|
};
|
|
477
632
|
}
|
|
478
633
|
/** Inverse of {@link eventToState} — the grader reports the review's event. */
|