@pratik7368patil/anchor-core 0.1.15 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +138 -1
- package/dist/index.js +1051 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/db/schema.sql +8 -0
package/dist/index.js
CHANGED
|
@@ -551,6 +551,14 @@ CREATE TABLE IF NOT EXISTS sync_state (
|
|
|
551
551
|
history_coverage TEXT,
|
|
552
552
|
history_limit INTEGER,
|
|
553
553
|
history_since TEXT,
|
|
554
|
+
graphql_cursor TEXT,
|
|
555
|
+
graphql_cursor_scope TEXT,
|
|
556
|
+
graphql_cursor_scanned_prs INTEGER,
|
|
557
|
+
graphql_cursor_matched_prs INTEGER,
|
|
558
|
+
graphql_cursor_page_size INTEGER,
|
|
559
|
+
graphql_cursor_reset_at TEXT,
|
|
560
|
+
graphql_cursor_reason TEXT,
|
|
561
|
+
graphql_cursor_updated_at TEXT,
|
|
554
562
|
updated_at TEXT NOT NULL
|
|
555
563
|
);
|
|
556
564
|
|
|
@@ -1418,6 +1426,14 @@ function initializeSchema(db) {
|
|
|
1418
1426
|
ensureColumn(db, "sync_state", "history_coverage", "TEXT");
|
|
1419
1427
|
ensureColumn(db, "sync_state", "history_limit", "INTEGER");
|
|
1420
1428
|
ensureColumn(db, "sync_state", "history_since", "TEXT");
|
|
1429
|
+
ensureColumn(db, "sync_state", "graphql_cursor", "TEXT");
|
|
1430
|
+
ensureColumn(db, "sync_state", "graphql_cursor_scope", "TEXT");
|
|
1431
|
+
ensureColumn(db, "sync_state", "graphql_cursor_scanned_prs", "INTEGER");
|
|
1432
|
+
ensureColumn(db, "sync_state", "graphql_cursor_matched_prs", "INTEGER");
|
|
1433
|
+
ensureColumn(db, "sync_state", "graphql_cursor_page_size", "INTEGER");
|
|
1434
|
+
ensureColumn(db, "sync_state", "graphql_cursor_reset_at", "TEXT");
|
|
1435
|
+
ensureColumn(db, "sync_state", "graphql_cursor_reason", "TEXT");
|
|
1436
|
+
ensureColumn(db, "sync_state", "graphql_cursor_updated_at", "TEXT");
|
|
1421
1437
|
}
|
|
1422
1438
|
function ensureColumn(db, tableName, columnName, definition) {
|
|
1423
1439
|
const columns = db.prepare(`PRAGMA table_info(${tableName})`).all();
|
|
@@ -1488,6 +1504,83 @@ function updateSyncState(db, repo, lastIndexedPr, metadata = {}) {
|
|
|
1488
1504
|
now
|
|
1489
1505
|
);
|
|
1490
1506
|
}
|
|
1507
|
+
function graphQLFetchCheckpointScope(input) {
|
|
1508
|
+
const historyScope = input.all ? "all" : `limit:${input.limit ?? 200}`;
|
|
1509
|
+
return `${input.repo}|${historyScope}|since:${input.since ?? ""}`;
|
|
1510
|
+
}
|
|
1511
|
+
function getGraphQLFetchCheckpoint(db, repo, scope) {
|
|
1512
|
+
initializeSchema(db);
|
|
1513
|
+
const row = db.prepare(
|
|
1514
|
+
`SELECT graphql_cursor, graphql_cursor_scope, graphql_cursor_scanned_prs,
|
|
1515
|
+
graphql_cursor_matched_prs, graphql_cursor_page_size, graphql_cursor_reset_at,
|
|
1516
|
+
graphql_cursor_reason, graphql_cursor_updated_at
|
|
1517
|
+
FROM sync_state
|
|
1518
|
+
WHERE repo = ?`
|
|
1519
|
+
).get(repo);
|
|
1520
|
+
if (!row?.graphql_cursor_scope || row.graphql_cursor_scope !== scope) return void 0;
|
|
1521
|
+
return {
|
|
1522
|
+
repo,
|
|
1523
|
+
scope,
|
|
1524
|
+
cursor: row.graphql_cursor ?? null,
|
|
1525
|
+
scannedPullRequests: row.graphql_cursor_scanned_prs ?? 0,
|
|
1526
|
+
matchedMergedPullRequests: row.graphql_cursor_matched_prs ?? 0,
|
|
1527
|
+
pageSize: row.graphql_cursor_page_size ?? 50,
|
|
1528
|
+
resetAt: row.graphql_cursor_reset_at ?? void 0,
|
|
1529
|
+
reason: row.graphql_cursor_reason ?? "GraphQL budget checkpoint",
|
|
1530
|
+
updatedAt: row.graphql_cursor_updated_at ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
function saveGraphQLFetchCheckpoint(db, checkpoint) {
|
|
1534
|
+
initializeSchema(db);
|
|
1535
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1536
|
+
db.prepare(
|
|
1537
|
+
`INSERT INTO sync_state
|
|
1538
|
+
(repo, last_sync_at, last_indexed_pr, history_coverage, history_limit, history_since,
|
|
1539
|
+
graphql_cursor, graphql_cursor_scope, graphql_cursor_scanned_prs,
|
|
1540
|
+
graphql_cursor_matched_prs, graphql_cursor_page_size, graphql_cursor_reset_at,
|
|
1541
|
+
graphql_cursor_reason, graphql_cursor_updated_at, updated_at)
|
|
1542
|
+
VALUES (?, NULL, NULL, 'unknown', NULL, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1543
|
+
ON CONFLICT(repo) DO UPDATE SET
|
|
1544
|
+
graphql_cursor = excluded.graphql_cursor,
|
|
1545
|
+
graphql_cursor_scope = excluded.graphql_cursor_scope,
|
|
1546
|
+
graphql_cursor_scanned_prs = excluded.graphql_cursor_scanned_prs,
|
|
1547
|
+
graphql_cursor_matched_prs = excluded.graphql_cursor_matched_prs,
|
|
1548
|
+
graphql_cursor_page_size = excluded.graphql_cursor_page_size,
|
|
1549
|
+
graphql_cursor_reset_at = excluded.graphql_cursor_reset_at,
|
|
1550
|
+
graphql_cursor_reason = excluded.graphql_cursor_reason,
|
|
1551
|
+
graphql_cursor_updated_at = excluded.graphql_cursor_updated_at,
|
|
1552
|
+
updated_at = excluded.updated_at`
|
|
1553
|
+
).run(
|
|
1554
|
+
checkpoint.repo,
|
|
1555
|
+
checkpoint.cursor ?? null,
|
|
1556
|
+
checkpoint.scope,
|
|
1557
|
+
checkpoint.scannedPullRequests,
|
|
1558
|
+
checkpoint.matchedMergedPullRequests,
|
|
1559
|
+
checkpoint.pageSize,
|
|
1560
|
+
checkpoint.resetAt ?? null,
|
|
1561
|
+
checkpoint.reason,
|
|
1562
|
+
checkpoint.updatedAt,
|
|
1563
|
+
now
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
function clearGraphQLFetchCheckpoint(db, repo, scope) {
|
|
1567
|
+
initializeSchema(db);
|
|
1568
|
+
const row = db.prepare("SELECT graphql_cursor_scope FROM sync_state WHERE repo = ?").get(repo);
|
|
1569
|
+
if (scope && row?.graphql_cursor_scope && row.graphql_cursor_scope !== scope) return;
|
|
1570
|
+
db.prepare(
|
|
1571
|
+
`UPDATE sync_state SET
|
|
1572
|
+
graphql_cursor = NULL,
|
|
1573
|
+
graphql_cursor_scope = NULL,
|
|
1574
|
+
graphql_cursor_scanned_prs = NULL,
|
|
1575
|
+
graphql_cursor_matched_prs = NULL,
|
|
1576
|
+
graphql_cursor_page_size = NULL,
|
|
1577
|
+
graphql_cursor_reset_at = NULL,
|
|
1578
|
+
graphql_cursor_reason = NULL,
|
|
1579
|
+
graphql_cursor_updated_at = NULL,
|
|
1580
|
+
updated_at = ?
|
|
1581
|
+
WHERE repo = ?`
|
|
1582
|
+
).run((/* @__PURE__ */ new Date()).toISOString(), repo);
|
|
1583
|
+
}
|
|
1491
1584
|
function deleteExistingPrData(db, prId) {
|
|
1492
1585
|
const unitRows = db.prepare("SELECT id FROM wisdom_units WHERE pr_id = ?").all(prId);
|
|
1493
1586
|
const deleteFts = db.prepare("DELETE FROM wisdom_units_fts WHERE unitId = ?");
|
|
@@ -5942,6 +6035,25 @@ function getGitHubRateLimitDelayMs(error, attempt, now = Date.now()) {
|
|
|
5942
6035
|
reason: `secondary rate limit backoff for ${backoffSeconds} seconds`
|
|
5943
6036
|
};
|
|
5944
6037
|
}
|
|
6038
|
+
function isGitHubGraphQLResourceLimitError(error) {
|
|
6039
|
+
const message = (error.message ?? "").toLowerCase();
|
|
6040
|
+
return message.includes("resource limit") || message.includes("timeout") || message.includes("timed out") || message.includes("couldn't respond") || message.includes("could not respond") || message.includes("exceeded") && message.includes("node");
|
|
6041
|
+
}
|
|
6042
|
+
function updateGitHubGraphQLRateLimitState(controller, rateLimit, requestName) {
|
|
6043
|
+
if (!rateLimit || rateLimit.remaining !== 0 || !rateLimit.resetAt) return;
|
|
6044
|
+
const resetAtMs = Date.parse(rateLimit.resetAt);
|
|
6045
|
+
if (!Number.isFinite(resetAtMs)) return;
|
|
6046
|
+
const now = controller.now?.() ?? Date.now();
|
|
6047
|
+
const retryAtMs = Math.max(resetAtMs + 2e3, now);
|
|
6048
|
+
controller.blockedUntilMs = Math.max(controller.blockedUntilMs ?? 0, retryAtMs);
|
|
6049
|
+
controller.onRateLimit?.({
|
|
6050
|
+
waitSeconds: Math.ceil(Math.max(0, retryAtMs - now) / 1e3),
|
|
6051
|
+
retryAt: new Date(retryAtMs).toISOString(),
|
|
6052
|
+
reason: `GraphQL rate limit exhausted${rateLimit.cost ? ` after query cost ${rateLimit.cost}` : ""}`,
|
|
6053
|
+
request: requestName,
|
|
6054
|
+
attempt: 1
|
|
6055
|
+
});
|
|
6056
|
+
}
|
|
5945
6057
|
async function sleep(milliseconds) {
|
|
5946
6058
|
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
5947
6059
|
}
|
|
@@ -5986,7 +6098,8 @@ async function paginateWithGitHubRateLimit(requestPage, options) {
|
|
|
5986
6098
|
for (let page = 1; ; page += 1) {
|
|
5987
6099
|
const response = await requestWithGitHubRateLimit(() => requestPage(page), {
|
|
5988
6100
|
controller: options.controller,
|
|
5989
|
-
requestName: `${options.requestName} page ${page}
|
|
6101
|
+
requestName: `${options.requestName} page ${page}`,
|
|
6102
|
+
maxRetries: options.maxRetries
|
|
5990
6103
|
});
|
|
5991
6104
|
results.push(...response.data);
|
|
5992
6105
|
if (!hasNextPage(response.headers) && response.data.length < 100) break;
|
|
@@ -5996,6 +6109,84 @@ async function paginateWithGitHubRateLimit(requestPage, options) {
|
|
|
5996
6109
|
return results;
|
|
5997
6110
|
}
|
|
5998
6111
|
|
|
6112
|
+
// src/github/graphql-client.ts
|
|
6113
|
+
var GitHubGraphQLError = class extends Error {
|
|
6114
|
+
status;
|
|
6115
|
+
response;
|
|
6116
|
+
constructor(message, options) {
|
|
6117
|
+
super(message);
|
|
6118
|
+
this.name = "GitHubGraphQLError";
|
|
6119
|
+
this.status = options.status;
|
|
6120
|
+
this.response = { headers: options.headers };
|
|
6121
|
+
}
|
|
6122
|
+
};
|
|
6123
|
+
function headersToRecord(headers) {
|
|
6124
|
+
const result = {};
|
|
6125
|
+
headers.forEach((value, key) => {
|
|
6126
|
+
result[key.toLowerCase()] = value;
|
|
6127
|
+
});
|
|
6128
|
+
return result;
|
|
6129
|
+
}
|
|
6130
|
+
function errorStatus(status, errors) {
|
|
6131
|
+
if (status === 403 || status === 429) return status;
|
|
6132
|
+
const message = (errors ?? []).map((error) => error.message ?? "").join("\n").toLowerCase();
|
|
6133
|
+
if (message.includes("rate limit") || message.includes("secondary limit")) return 403;
|
|
6134
|
+
return status >= 400 ? status : 500;
|
|
6135
|
+
}
|
|
6136
|
+
function errorMessage(status, errors) {
|
|
6137
|
+
const messages = (errors ?? []).map((error) => error.message).filter((message) => Boolean(message?.trim()));
|
|
6138
|
+
if (messages.length > 0) return messages.join("; ");
|
|
6139
|
+
return `GitHub GraphQL request failed with status ${status}.`;
|
|
6140
|
+
}
|
|
6141
|
+
function createGitHubGraphQLRequester(options) {
|
|
6142
|
+
if (!options.token.trim()) {
|
|
6143
|
+
throw new Error("GitHub authentication is required. Run gh auth login, or export GITHUB_TOKEN/GH_TOKEN.");
|
|
6144
|
+
}
|
|
6145
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
6146
|
+
if (!fetchImpl) throw new Error("Global fetch is unavailable in this Node.js runtime.");
|
|
6147
|
+
return async function requestGitHubGraphQL(query, variables, requestOptions) {
|
|
6148
|
+
return requestWithGitHubRateLimit(
|
|
6149
|
+
async () => {
|
|
6150
|
+
const response = await fetchImpl("https://api.github.com/graphql", {
|
|
6151
|
+
method: "POST",
|
|
6152
|
+
headers: {
|
|
6153
|
+
accept: "application/vnd.github+json",
|
|
6154
|
+
authorization: `Bearer ${options.token}`,
|
|
6155
|
+
"content-type": "application/json",
|
|
6156
|
+
"user-agent": "anchor-local-mcp"
|
|
6157
|
+
},
|
|
6158
|
+
body: JSON.stringify({ query, variables })
|
|
6159
|
+
});
|
|
6160
|
+
const headers = headersToRecord(response.headers);
|
|
6161
|
+
const raw = await response.json();
|
|
6162
|
+
if (!response.ok || raw.errors?.length) {
|
|
6163
|
+
throw new GitHubGraphQLError(errorMessage(response.status, raw.errors), {
|
|
6164
|
+
status: errorStatus(response.status, raw.errors),
|
|
6165
|
+
headers
|
|
6166
|
+
});
|
|
6167
|
+
}
|
|
6168
|
+
if (!raw.data) {
|
|
6169
|
+
throw new GitHubGraphQLError("GitHub GraphQL response did not include data.", {
|
|
6170
|
+
status: response.status,
|
|
6171
|
+
headers
|
|
6172
|
+
});
|
|
6173
|
+
}
|
|
6174
|
+
updateGitHubGraphQLRateLimitState(
|
|
6175
|
+
requestOptions.controller,
|
|
6176
|
+
raw.data.rateLimit,
|
|
6177
|
+
requestOptions.requestName
|
|
6178
|
+
);
|
|
6179
|
+
return { data: raw.data, headers };
|
|
6180
|
+
},
|
|
6181
|
+
{
|
|
6182
|
+
controller: requestOptions.controller,
|
|
6183
|
+
requestName: requestOptions.requestName,
|
|
6184
|
+
maxRetries: requestOptions.maxRetries
|
|
6185
|
+
}
|
|
6186
|
+
);
|
|
6187
|
+
};
|
|
6188
|
+
}
|
|
6189
|
+
|
|
5999
6190
|
// src/github/fetch-pr-details.ts
|
|
6000
6191
|
async function fetchPullRequestDetails(octokit, repoFullName, pullNumber, controller = {}) {
|
|
6001
6192
|
const [owner, repo] = repoFullName.split("/");
|
|
@@ -6116,6 +6307,756 @@ async function fetchPullRequestDetails(octokit, repoFullName, pullNumber, contro
|
|
|
6116
6307
|
};
|
|
6117
6308
|
}
|
|
6118
6309
|
|
|
6310
|
+
// src/github/fetch-prs-graphql.ts
|
|
6311
|
+
var MIN_PULL_REQUEST_PAGE_SIZE = 5;
|
|
6312
|
+
var INITIAL_PULL_REQUEST_PAGE_SIZE = 50;
|
|
6313
|
+
var MAX_PULL_REQUEST_PAGE_SIZE = 100;
|
|
6314
|
+
var REDUCED_PULL_REQUEST_PAGE_SIZES = [10, 5];
|
|
6315
|
+
var CONNECTION_PAGE_SIZE = 100;
|
|
6316
|
+
var GRAPHQL_RATE_LIMIT_RESERVE = 250;
|
|
6317
|
+
var GraphQLBudget = class {
|
|
6318
|
+
constructor(reserve) {
|
|
6319
|
+
this.reserve = reserve;
|
|
6320
|
+
}
|
|
6321
|
+
reserve;
|
|
6322
|
+
activePageCost = 0;
|
|
6323
|
+
averageCostPerPr;
|
|
6324
|
+
latestRateLimit;
|
|
6325
|
+
beginPage() {
|
|
6326
|
+
this.activePageCost = 0;
|
|
6327
|
+
}
|
|
6328
|
+
observe(rateLimit) {
|
|
6329
|
+
this.latestRateLimit = rateLimit ?? this.latestRateLimit;
|
|
6330
|
+
if (typeof rateLimit?.cost === "number" && Number.isFinite(rateLimit.cost)) {
|
|
6331
|
+
this.activePageCost += Math.max(0, rateLimit.cost);
|
|
6332
|
+
}
|
|
6333
|
+
}
|
|
6334
|
+
completePage(prCount) {
|
|
6335
|
+
if (prCount <= 0 || this.activePageCost <= 0) return;
|
|
6336
|
+
const pageCostPerPr = this.activePageCost / prCount;
|
|
6337
|
+
this.averageCostPerPr = this.averageCostPerPr === void 0 ? pageCostPerPr : this.averageCostPerPr * 0.65 + pageCostPerPr * 0.35;
|
|
6338
|
+
}
|
|
6339
|
+
shouldDefer() {
|
|
6340
|
+
const remaining = this.latestRateLimit?.remaining;
|
|
6341
|
+
return typeof remaining === "number" && remaining <= this.reserve;
|
|
6342
|
+
}
|
|
6343
|
+
rateLimit() {
|
|
6344
|
+
return this.latestRateLimit;
|
|
6345
|
+
}
|
|
6346
|
+
choosePageSize(currentPageSize, remainingPrs) {
|
|
6347
|
+
const remaining = this.latestRateLimit?.remaining;
|
|
6348
|
+
const averageCostPerPr = this.averageCostPerPr;
|
|
6349
|
+
if (typeof remaining !== "number" || remaining <= this.reserve || averageCostPerPr === void 0 || averageCostPerPr <= 0) {
|
|
6350
|
+
return { pageSize: currentPageSize, averageCostPerPr };
|
|
6351
|
+
}
|
|
6352
|
+
const safeBudget = Math.max(0, remaining - this.reserve);
|
|
6353
|
+
const budgetPageSize = Math.max(
|
|
6354
|
+
MIN_PULL_REQUEST_PAGE_SIZE,
|
|
6355
|
+
Math.min(MAX_PULL_REQUEST_PAGE_SIZE, Math.floor(safeBudget / averageCostPerPr))
|
|
6356
|
+
);
|
|
6357
|
+
const growthLimitedPageSize = budgetPageSize > currentPageSize ? Math.min(budgetPageSize, currentPageSize * 2) : budgetPageSize;
|
|
6358
|
+
const cappedPageSize = remainingPrs === void 0 ? growthLimitedPageSize : Math.min(growthLimitedPageSize, Math.max(MIN_PULL_REQUEST_PAGE_SIZE, remainingPrs));
|
|
6359
|
+
return {
|
|
6360
|
+
pageSize: Math.max(MIN_PULL_REQUEST_PAGE_SIZE, Math.min(MAX_PULL_REQUEST_PAGE_SIZE, cappedPageSize)),
|
|
6361
|
+
averageCostPerPr
|
|
6362
|
+
};
|
|
6363
|
+
}
|
|
6364
|
+
};
|
|
6365
|
+
var PULL_REQUEST_FIELDS = `
|
|
6366
|
+
number
|
|
6367
|
+
url
|
|
6368
|
+
title
|
|
6369
|
+
body
|
|
6370
|
+
createdAt
|
|
6371
|
+
mergedAt
|
|
6372
|
+
updatedAt
|
|
6373
|
+
author { login }
|
|
6374
|
+
labels(first: 100) {
|
|
6375
|
+
nodes { name }
|
|
6376
|
+
pageInfo { hasNextPage endCursor }
|
|
6377
|
+
}
|
|
6378
|
+
files(first: 100) {
|
|
6379
|
+
nodes { path additions deletions }
|
|
6380
|
+
pageInfo { hasNextPage endCursor }
|
|
6381
|
+
}
|
|
6382
|
+
comments(first: 100) {
|
|
6383
|
+
nodes { author { login } body createdAt }
|
|
6384
|
+
pageInfo { hasNextPage endCursor }
|
|
6385
|
+
}
|
|
6386
|
+
reviews(first: 100) {
|
|
6387
|
+
nodes {
|
|
6388
|
+
id
|
|
6389
|
+
author { login }
|
|
6390
|
+
body
|
|
6391
|
+
submittedAt
|
|
6392
|
+
comments(first: 100) {
|
|
6393
|
+
nodes { author { login } body path createdAt }
|
|
6394
|
+
pageInfo { hasNextPage endCursor }
|
|
6395
|
+
}
|
|
6396
|
+
}
|
|
6397
|
+
pageInfo { hasNextPage endCursor }
|
|
6398
|
+
}
|
|
6399
|
+
commits(first: 100) {
|
|
6400
|
+
nodes { commit { message } }
|
|
6401
|
+
pageInfo { hasNextPage endCursor }
|
|
6402
|
+
}
|
|
6403
|
+
`;
|
|
6404
|
+
var LIST_MERGED_PULL_REQUESTS_QUERY = `
|
|
6405
|
+
query AnchorMergedPullRequests($owner: String!, $name: String!, $first: Int!, $after: String) {
|
|
6406
|
+
repository(owner: $owner, name: $name) {
|
|
6407
|
+
pullRequests(states: MERGED, orderBy: { field: UPDATED_AT, direction: DESC }, first: $first, after: $after) {
|
|
6408
|
+
nodes {
|
|
6409
|
+
${PULL_REQUEST_FIELDS}
|
|
6410
|
+
}
|
|
6411
|
+
pageInfo { hasNextPage endCursor }
|
|
6412
|
+
}
|
|
6413
|
+
}
|
|
6414
|
+
rateLimit { cost remaining resetAt }
|
|
6415
|
+
}
|
|
6416
|
+
`;
|
|
6417
|
+
var PULL_REQUEST_FILES_QUERY = `
|
|
6418
|
+
query AnchorPullRequestFiles($owner: String!, $name: String!, $number: Int!, $first: Int!, $after: String) {
|
|
6419
|
+
repository(owner: $owner, name: $name) {
|
|
6420
|
+
pullRequest(number: $number) {
|
|
6421
|
+
files(first: $first, after: $after) {
|
|
6422
|
+
nodes { path additions deletions }
|
|
6423
|
+
pageInfo { hasNextPage endCursor }
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
rateLimit { cost remaining resetAt }
|
|
6428
|
+
}
|
|
6429
|
+
`;
|
|
6430
|
+
var PULL_REQUEST_COMMENTS_QUERY = `
|
|
6431
|
+
query AnchorPullRequestComments($owner: String!, $name: String!, $number: Int!, $first: Int!, $after: String) {
|
|
6432
|
+
repository(owner: $owner, name: $name) {
|
|
6433
|
+
pullRequest(number: $number) {
|
|
6434
|
+
comments(first: $first, after: $after) {
|
|
6435
|
+
nodes { author { login } body createdAt }
|
|
6436
|
+
pageInfo { hasNextPage endCursor }
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
rateLimit { cost remaining resetAt }
|
|
6441
|
+
}
|
|
6442
|
+
`;
|
|
6443
|
+
var PULL_REQUEST_REVIEWS_QUERY = `
|
|
6444
|
+
query AnchorPullRequestReviews($owner: String!, $name: String!, $number: Int!, $first: Int!, $after: String) {
|
|
6445
|
+
repository(owner: $owner, name: $name) {
|
|
6446
|
+
pullRequest(number: $number) {
|
|
6447
|
+
reviews(first: $first, after: $after) {
|
|
6448
|
+
nodes {
|
|
6449
|
+
id
|
|
6450
|
+
author { login }
|
|
6451
|
+
body
|
|
6452
|
+
submittedAt
|
|
6453
|
+
comments(first: 100) {
|
|
6454
|
+
nodes { author { login } body path createdAt }
|
|
6455
|
+
pageInfo { hasNextPage endCursor }
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
pageInfo { hasNextPage endCursor }
|
|
6459
|
+
}
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
6462
|
+
rateLimit { cost remaining resetAt }
|
|
6463
|
+
}
|
|
6464
|
+
`;
|
|
6465
|
+
var PULL_REQUEST_COMMITS_QUERY = `
|
|
6466
|
+
query AnchorPullRequestCommits($owner: String!, $name: String!, $number: Int!, $first: Int!, $after: String) {
|
|
6467
|
+
repository(owner: $owner, name: $name) {
|
|
6468
|
+
pullRequest(number: $number) {
|
|
6469
|
+
commits(first: $first, after: $after) {
|
|
6470
|
+
nodes { commit { message } }
|
|
6471
|
+
pageInfo { hasNextPage endCursor }
|
|
6472
|
+
}
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
rateLimit { cost remaining resetAt }
|
|
6476
|
+
}
|
|
6477
|
+
`;
|
|
6478
|
+
var REVIEW_COMMENTS_QUERY = `
|
|
6479
|
+
query AnchorPullRequestReviewComments($reviewId: ID!, $first: Int!, $after: String) {
|
|
6480
|
+
node(id: $reviewId) {
|
|
6481
|
+
... on PullRequestReview {
|
|
6482
|
+
comments(first: $first, after: $after) {
|
|
6483
|
+
nodes { author { login } body path createdAt }
|
|
6484
|
+
pageInfo { hasNextPage endCursor }
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
rateLimit { cost remaining resetAt }
|
|
6489
|
+
}
|
|
6490
|
+
`;
|
|
6491
|
+
var RATE_LIMIT_QUERY = `
|
|
6492
|
+
query AnchorGraphQLRateLimit {
|
|
6493
|
+
rateLimit { cost remaining resetAt }
|
|
6494
|
+
}
|
|
6495
|
+
`;
|
|
6496
|
+
function connectionNodes(connection) {
|
|
6497
|
+
return (connection?.nodes ?? []).filter((node) => Boolean(node));
|
|
6498
|
+
}
|
|
6499
|
+
async function requestGraphQLWithBudget(requestGraphQL, query, variables, options) {
|
|
6500
|
+
const response = await requestGraphQL(query, variables, {
|
|
6501
|
+
controller: options.controller,
|
|
6502
|
+
requestName: options.requestName
|
|
6503
|
+
});
|
|
6504
|
+
options.budget.observe(response.data.rateLimit);
|
|
6505
|
+
return response;
|
|
6506
|
+
}
|
|
6507
|
+
function pageInfo(connection) {
|
|
6508
|
+
return connection?.pageInfo ?? { hasNextPage: false, endCursor: null };
|
|
6509
|
+
}
|
|
6510
|
+
function labelName(label) {
|
|
6511
|
+
return label.name ? { name: label.name } : void 0;
|
|
6512
|
+
}
|
|
6513
|
+
function mapChangedFile(file) {
|
|
6514
|
+
if (!file.path) return void 0;
|
|
6515
|
+
return {
|
|
6516
|
+
filename: file.path,
|
|
6517
|
+
additions: file.additions ?? 0,
|
|
6518
|
+
deletions: file.deletions ?? 0
|
|
6519
|
+
};
|
|
6520
|
+
}
|
|
6521
|
+
function mapIssueComment(comment) {
|
|
6522
|
+
return {
|
|
6523
|
+
user: comment.author?.login ? { login: comment.author.login } : null,
|
|
6524
|
+
body: comment.body ?? "",
|
|
6525
|
+
created_at: comment.createdAt ?? void 0
|
|
6526
|
+
};
|
|
6527
|
+
}
|
|
6528
|
+
function mapReviewComment(comment) {
|
|
6529
|
+
return {
|
|
6530
|
+
user: comment.author?.login ? { login: comment.author.login } : null,
|
|
6531
|
+
body: comment.body ?? "",
|
|
6532
|
+
path: comment.path ?? void 0,
|
|
6533
|
+
created_at: comment.createdAt ?? void 0
|
|
6534
|
+
};
|
|
6535
|
+
}
|
|
6536
|
+
function mapReviewSummary(review) {
|
|
6537
|
+
return {
|
|
6538
|
+
user: review.author?.login ? { login: review.author.login } : null,
|
|
6539
|
+
body: review.body ?? "",
|
|
6540
|
+
created_at: review.submittedAt ?? void 0,
|
|
6541
|
+
submitted_at: review.submittedAt ?? void 0
|
|
6542
|
+
};
|
|
6543
|
+
}
|
|
6544
|
+
function mapPullRequest(repo, pull) {
|
|
6545
|
+
return {
|
|
6546
|
+
repo,
|
|
6547
|
+
number: pull.number,
|
|
6548
|
+
html_url: pull.url,
|
|
6549
|
+
title: pull.title,
|
|
6550
|
+
body: pull.body ?? "",
|
|
6551
|
+
user: pull.author?.login ? { login: pull.author.login } : null,
|
|
6552
|
+
labels: connectionNodes(pull.labels).map(labelName).filter((label) => Boolean(label)),
|
|
6553
|
+
created_at: pull.createdAt,
|
|
6554
|
+
merged_at: pull.mergedAt ?? void 0,
|
|
6555
|
+
updated_at: pull.updatedAt ?? pull.mergedAt ?? pull.createdAt,
|
|
6556
|
+
files: connectionNodes(pull.files).map(mapChangedFile).filter((file) => Boolean(file)),
|
|
6557
|
+
reviews: connectionNodes(pull.reviews).map(mapReviewSummary),
|
|
6558
|
+
reviewComments: connectionNodes(pull.reviews).flatMap(
|
|
6559
|
+
(review) => connectionNodes(review.comments).map(mapReviewComment)
|
|
6560
|
+
),
|
|
6561
|
+
issueComments: connectionNodes(pull.comments).map(mapIssueComment),
|
|
6562
|
+
commits: connectionNodes(pull.commits).map((commit) => ({
|
|
6563
|
+
commit: { message: commit.commit?.message ?? "" }
|
|
6564
|
+
}))
|
|
6565
|
+
};
|
|
6566
|
+
}
|
|
6567
|
+
async function requestConnection(requestGraphQL, query, connectionName, variables, options) {
|
|
6568
|
+
const response = await requestGraphQLWithBudget(requestGraphQL, query, variables, options);
|
|
6569
|
+
return response.data.repository?.pullRequest?.[connectionName];
|
|
6570
|
+
}
|
|
6571
|
+
async function appendAdditionalFiles(requestGraphQL, record, initialConnection, options) {
|
|
6572
|
+
let info = pageInfo(initialConnection);
|
|
6573
|
+
while (info.hasNextPage && info.endCursor) {
|
|
6574
|
+
const connection = await requestConnection(
|
|
6575
|
+
requestGraphQL,
|
|
6576
|
+
PULL_REQUEST_FILES_QUERY,
|
|
6577
|
+
"files",
|
|
6578
|
+
{
|
|
6579
|
+
owner: options.owner,
|
|
6580
|
+
name: options.name,
|
|
6581
|
+
number: record.number,
|
|
6582
|
+
first: CONNECTION_PAGE_SIZE,
|
|
6583
|
+
after: info.endCursor
|
|
6584
|
+
},
|
|
6585
|
+
{
|
|
6586
|
+
controller: options.controller,
|
|
6587
|
+
requestName: `GraphQL /repos/${record.repo}/pulls/${record.number}/files`,
|
|
6588
|
+
budget: options.budget
|
|
6589
|
+
}
|
|
6590
|
+
);
|
|
6591
|
+
record.files.push(
|
|
6592
|
+
...connectionNodes(connection).map(mapChangedFile).filter((file) => Boolean(file))
|
|
6593
|
+
);
|
|
6594
|
+
info = pageInfo(connection);
|
|
6595
|
+
}
|
|
6596
|
+
}
|
|
6597
|
+
async function appendAdditionalIssueComments(requestGraphQL, record, initialConnection, options) {
|
|
6598
|
+
let info = pageInfo(initialConnection);
|
|
6599
|
+
while (info.hasNextPage && info.endCursor) {
|
|
6600
|
+
const connection = await requestConnection(
|
|
6601
|
+
requestGraphQL,
|
|
6602
|
+
PULL_REQUEST_COMMENTS_QUERY,
|
|
6603
|
+
"comments",
|
|
6604
|
+
{
|
|
6605
|
+
owner: options.owner,
|
|
6606
|
+
name: options.name,
|
|
6607
|
+
number: record.number,
|
|
6608
|
+
first: CONNECTION_PAGE_SIZE,
|
|
6609
|
+
after: info.endCursor
|
|
6610
|
+
},
|
|
6611
|
+
{
|
|
6612
|
+
controller: options.controller,
|
|
6613
|
+
requestName: `GraphQL /repos/${record.repo}/issues/${record.number}/comments`,
|
|
6614
|
+
budget: options.budget
|
|
6615
|
+
}
|
|
6616
|
+
);
|
|
6617
|
+
record.issueComments?.push(...connectionNodes(connection).map(mapIssueComment));
|
|
6618
|
+
info = pageInfo(connection);
|
|
6619
|
+
}
|
|
6620
|
+
}
|
|
6621
|
+
async function appendAdditionalCommits(requestGraphQL, record, initialConnection, options) {
|
|
6622
|
+
let info = pageInfo(initialConnection);
|
|
6623
|
+
while (info.hasNextPage && info.endCursor) {
|
|
6624
|
+
const connection = await requestConnection(
|
|
6625
|
+
requestGraphQL,
|
|
6626
|
+
PULL_REQUEST_COMMITS_QUERY,
|
|
6627
|
+
"commits",
|
|
6628
|
+
{
|
|
6629
|
+
owner: options.owner,
|
|
6630
|
+
name: options.name,
|
|
6631
|
+
number: record.number,
|
|
6632
|
+
first: CONNECTION_PAGE_SIZE,
|
|
6633
|
+
after: info.endCursor
|
|
6634
|
+
},
|
|
6635
|
+
{
|
|
6636
|
+
controller: options.controller,
|
|
6637
|
+
requestName: `GraphQL /repos/${record.repo}/pulls/${record.number}/commits`,
|
|
6638
|
+
budget: options.budget
|
|
6639
|
+
}
|
|
6640
|
+
);
|
|
6641
|
+
record.commits?.push(
|
|
6642
|
+
...connectionNodes(connection).map((commit) => ({
|
|
6643
|
+
commit: { message: commit.commit?.message ?? "" }
|
|
6644
|
+
}))
|
|
6645
|
+
);
|
|
6646
|
+
info = pageInfo(connection);
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
6649
|
+
async function appendAdditionalReviewComments(requestGraphQL, record, review, options) {
|
|
6650
|
+
let info = pageInfo(review.comments);
|
|
6651
|
+
while (info.hasNextPage && info.endCursor) {
|
|
6652
|
+
const response = await requestGraphQLWithBudget(
|
|
6653
|
+
requestGraphQL,
|
|
6654
|
+
REVIEW_COMMENTS_QUERY,
|
|
6655
|
+
{
|
|
6656
|
+
reviewId: review.id,
|
|
6657
|
+
first: CONNECTION_PAGE_SIZE,
|
|
6658
|
+
after: info.endCursor
|
|
6659
|
+
},
|
|
6660
|
+
{
|
|
6661
|
+
controller: options.controller,
|
|
6662
|
+
requestName: `GraphQL /pull-request-reviews/${review.id}/comments`,
|
|
6663
|
+
budget: options.budget
|
|
6664
|
+
}
|
|
6665
|
+
);
|
|
6666
|
+
const connection = response.data.node?.comments;
|
|
6667
|
+
record.reviewComments?.push(...connectionNodes(connection).map(mapReviewComment));
|
|
6668
|
+
info = pageInfo(connection);
|
|
6669
|
+
}
|
|
6670
|
+
}
|
|
6671
|
+
async function appendAdditionalReviews(requestGraphQL, record, initialConnection, options) {
|
|
6672
|
+
const reviewsToHydrate = [...connectionNodes(initialConnection)];
|
|
6673
|
+
let info = pageInfo(initialConnection);
|
|
6674
|
+
while (info.hasNextPage && info.endCursor) {
|
|
6675
|
+
const connection = await requestConnection(
|
|
6676
|
+
requestGraphQL,
|
|
6677
|
+
PULL_REQUEST_REVIEWS_QUERY,
|
|
6678
|
+
"reviews",
|
|
6679
|
+
{
|
|
6680
|
+
owner: options.owner,
|
|
6681
|
+
name: options.name,
|
|
6682
|
+
number: record.number,
|
|
6683
|
+
first: CONNECTION_PAGE_SIZE,
|
|
6684
|
+
after: info.endCursor
|
|
6685
|
+
},
|
|
6686
|
+
{
|
|
6687
|
+
controller: options.controller,
|
|
6688
|
+
requestName: `GraphQL /repos/${record.repo}/pulls/${record.number}/reviews`,
|
|
6689
|
+
budget: options.budget
|
|
6690
|
+
}
|
|
6691
|
+
);
|
|
6692
|
+
const reviewNodes = connectionNodes(connection);
|
|
6693
|
+
reviewsToHydrate.push(...reviewNodes);
|
|
6694
|
+
record.reviews?.push(...reviewNodes.map(mapReviewSummary));
|
|
6695
|
+
record.reviewComments?.push(
|
|
6696
|
+
...reviewNodes.flatMap((review) => connectionNodes(review.comments).map(mapReviewComment))
|
|
6697
|
+
);
|
|
6698
|
+
info = pageInfo(connection);
|
|
6699
|
+
}
|
|
6700
|
+
for (const review of reviewsToHydrate) {
|
|
6701
|
+
await appendAdditionalReviewComments(requestGraphQL, record, review, {
|
|
6702
|
+
controller: options.controller,
|
|
6703
|
+
budget: options.budget
|
|
6704
|
+
});
|
|
6705
|
+
}
|
|
6706
|
+
}
|
|
6707
|
+
async function hydratePullRequestNestedConnections(requestGraphQL, record, pull, options) {
|
|
6708
|
+
await appendAdditionalFiles(requestGraphQL, record, pull.files, options);
|
|
6709
|
+
await appendAdditionalIssueComments(requestGraphQL, record, pull.comments, options);
|
|
6710
|
+
await appendAdditionalReviews(requestGraphQL, record, pull.reviews, options);
|
|
6711
|
+
await appendAdditionalCommits(requestGraphQL, record, pull.commits, options);
|
|
6712
|
+
}
|
|
6713
|
+
function mergePatchFiles(record, patchFiles) {
|
|
6714
|
+
const byFilename = new Map(patchFiles.map((file) => [file.filename, file]));
|
|
6715
|
+
let patches = 0;
|
|
6716
|
+
record.files = record.files.map((file) => {
|
|
6717
|
+
const patchFile = byFilename.get(file.filename);
|
|
6718
|
+
if (!patchFile) return file;
|
|
6719
|
+
if (patchFile.patch) patches += 1;
|
|
6720
|
+
return {
|
|
6721
|
+
...file,
|
|
6722
|
+
additions: patchFile.additions ?? file.additions,
|
|
6723
|
+
deletions: patchFile.deletions ?? file.deletions,
|
|
6724
|
+
patch: patchFile.patch ?? file.patch
|
|
6725
|
+
};
|
|
6726
|
+
});
|
|
6727
|
+
const existing = new Set(record.files.map((file) => file.filename));
|
|
6728
|
+
for (const patchFile of patchFiles) {
|
|
6729
|
+
if (!existing.has(patchFile.filename)) {
|
|
6730
|
+
record.files.push(patchFile);
|
|
6731
|
+
if (patchFile.patch) patches += 1;
|
|
6732
|
+
}
|
|
6733
|
+
}
|
|
6734
|
+
return patches;
|
|
6735
|
+
}
|
|
6736
|
+
async function fetchPullRequestPatchFiles(octokit, repoFullName, pullNumber, controller) {
|
|
6737
|
+
const [owner, repo] = repoFullName.split("/");
|
|
6738
|
+
if (!owner || !repo) throw new Error(`Invalid repo '${repoFullName}'. Expected owner/name.`);
|
|
6739
|
+
const files = await paginateWithGitHubRateLimit(
|
|
6740
|
+
(page) => octokit.pulls.listFiles({
|
|
6741
|
+
owner,
|
|
6742
|
+
repo,
|
|
6743
|
+
pull_number: pullNumber,
|
|
6744
|
+
per_page: 100,
|
|
6745
|
+
page
|
|
6746
|
+
}),
|
|
6747
|
+
{
|
|
6748
|
+
controller,
|
|
6749
|
+
requestName: `GET /repos/${repoFullName}/pulls/${pullNumber}/files`,
|
|
6750
|
+
maxRetries: 0
|
|
6751
|
+
}
|
|
6752
|
+
);
|
|
6753
|
+
return files.map((file) => ({
|
|
6754
|
+
filename: file.filename,
|
|
6755
|
+
patch: "patch" in file ? file.patch : void 0,
|
|
6756
|
+
additions: file.additions,
|
|
6757
|
+
deletions: file.deletions
|
|
6758
|
+
}));
|
|
6759
|
+
}
|
|
6760
|
+
async function enrichPullRequestPatchesWithRest(options) {
|
|
6761
|
+
const octokit = options.restClient ?? createGitHubClient(options.token);
|
|
6762
|
+
let nextIndex = 0;
|
|
6763
|
+
let completed = 0;
|
|
6764
|
+
const workerCount = Math.min(options.detailConcurrency, options.records.length);
|
|
6765
|
+
async function worker() {
|
|
6766
|
+
while (nextIndex < options.records.length) {
|
|
6767
|
+
const index = nextIndex;
|
|
6768
|
+
nextIndex += 1;
|
|
6769
|
+
const record = options.records[index];
|
|
6770
|
+
if (!record) continue;
|
|
6771
|
+
options.onProgress?.({
|
|
6772
|
+
stage: "enriching_pull_request_patches",
|
|
6773
|
+
repo: options.repo,
|
|
6774
|
+
current: index + 1,
|
|
6775
|
+
total: options.records.length,
|
|
6776
|
+
prNumber: record.number,
|
|
6777
|
+
detailConcurrency: options.detailConcurrency
|
|
6778
|
+
});
|
|
6779
|
+
try {
|
|
6780
|
+
const patchFiles = await fetchPullRequestPatchFiles(
|
|
6781
|
+
octokit,
|
|
6782
|
+
options.repo,
|
|
6783
|
+
record.number,
|
|
6784
|
+
options.controller
|
|
6785
|
+
);
|
|
6786
|
+
const patches = mergePatchFiles(record, patchFiles);
|
|
6787
|
+
completed += 1;
|
|
6788
|
+
options.onProgress?.({
|
|
6789
|
+
stage: "enriched_pull_request_patches",
|
|
6790
|
+
repo: options.repo,
|
|
6791
|
+
current: completed,
|
|
6792
|
+
total: options.records.length,
|
|
6793
|
+
prNumber: record.number,
|
|
6794
|
+
detailConcurrency: options.detailConcurrency,
|
|
6795
|
+
patches
|
|
6796
|
+
});
|
|
6797
|
+
} catch (error) {
|
|
6798
|
+
completed += 1;
|
|
6799
|
+
if (!isGitHubRateLimitError(error)) {
|
|
6800
|
+
options.onProgress?.({
|
|
6801
|
+
stage: "skipped_pull_request_patch_enrichment",
|
|
6802
|
+
repo: options.repo,
|
|
6803
|
+
current: completed,
|
|
6804
|
+
total: options.records.length,
|
|
6805
|
+
prNumber: record.number,
|
|
6806
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
6807
|
+
});
|
|
6808
|
+
continue;
|
|
6809
|
+
}
|
|
6810
|
+
options.onProgress?.({
|
|
6811
|
+
stage: "skipped_pull_request_patch_enrichment",
|
|
6812
|
+
repo: options.repo,
|
|
6813
|
+
current: completed,
|
|
6814
|
+
total: options.records.length,
|
|
6815
|
+
prNumber: record.number,
|
|
6816
|
+
reason: "GitHub REST rate limit reached during patch enrichment"
|
|
6817
|
+
});
|
|
6818
|
+
}
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
if (workerCount > 0) await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
6822
|
+
}
|
|
6823
|
+
function nextReducedPageSize(current) {
|
|
6824
|
+
return REDUCED_PULL_REQUEST_PAGE_SIZES.find((candidate) => candidate < current);
|
|
6825
|
+
}
|
|
6826
|
+
function checkpointFromState(options) {
|
|
6827
|
+
return {
|
|
6828
|
+
repo: options.repo,
|
|
6829
|
+
scope: options.scope,
|
|
6830
|
+
cursor: options.cursor ?? null,
|
|
6831
|
+
scannedPullRequests: options.scannedPullRequests,
|
|
6832
|
+
matchedMergedPullRequests: options.matchedMergedPullRequests,
|
|
6833
|
+
pageSize: options.pageSize,
|
|
6834
|
+
resetAt: options.rateLimit?.resetAt ?? void 0,
|
|
6835
|
+
reason: options.reason,
|
|
6836
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6837
|
+
};
|
|
6838
|
+
}
|
|
6839
|
+
async function fetchMergedPullRequestsWithGraphQL(options) {
|
|
6840
|
+
const [owner, name] = options.repo.split("/");
|
|
6841
|
+
if (!owner || !name) throw new Error(`Invalid repo '${options.repo}'. Expected owner/name.`);
|
|
6842
|
+
const requestGraphQL = createGitHubGraphQLRequester({
|
|
6843
|
+
token: options.token,
|
|
6844
|
+
fetchImpl: options.fetchImpl
|
|
6845
|
+
});
|
|
6846
|
+
const sinceTime = options.since ? Date.parse(options.since) : void 0;
|
|
6847
|
+
const records = [];
|
|
6848
|
+
const checkpoint = options.graphQLCheckpoint;
|
|
6849
|
+
const baseScannedPullRequests = checkpoint?.scannedPullRequests ?? 0;
|
|
6850
|
+
const baseMatchedMergedPullRequests = checkpoint?.matchedMergedPullRequests ?? 0;
|
|
6851
|
+
let scannedPullRequests = baseScannedPullRequests;
|
|
6852
|
+
let reachedSinceBoundary = false;
|
|
6853
|
+
let cursor = checkpoint?.cursor ?? void 0;
|
|
6854
|
+
let pageSize = Math.min(
|
|
6855
|
+
MAX_PULL_REQUEST_PAGE_SIZE,
|
|
6856
|
+
checkpoint?.pageSize ?? Math.min(INITIAL_PULL_REQUEST_PAGE_SIZE, options.limit ?? INITIAL_PULL_REQUEST_PAGE_SIZE)
|
|
6857
|
+
);
|
|
6858
|
+
const budget = new GraphQLBudget(GRAPHQL_RATE_LIMIT_RESERVE);
|
|
6859
|
+
const checkpointScope = checkpoint?.scope ?? `${options.repo}|${options.limit === void 0 ? "all" : `limit:${options.limit}`}|since:${options.since ?? ""}`;
|
|
6860
|
+
options.onProgress?.({
|
|
6861
|
+
stage: "discovering_pull_requests",
|
|
6862
|
+
repo: options.repo,
|
|
6863
|
+
all: options.limit === void 0,
|
|
6864
|
+
limit: options.limit,
|
|
6865
|
+
since: options.since,
|
|
6866
|
+
backend: "graphql"
|
|
6867
|
+
});
|
|
6868
|
+
if (checkpoint) {
|
|
6869
|
+
options.onProgress?.({
|
|
6870
|
+
stage: "github_graphql_checkpoint_resumed",
|
|
6871
|
+
repo: options.repo,
|
|
6872
|
+
scannedPullRequests: checkpoint.scannedPullRequests,
|
|
6873
|
+
matchedMergedPullRequests: checkpoint.matchedMergedPullRequests,
|
|
6874
|
+
pageSize: checkpoint.pageSize,
|
|
6875
|
+
resetAt: checkpoint.resetAt
|
|
6876
|
+
});
|
|
6877
|
+
}
|
|
6878
|
+
await requestGraphQLWithBudget(
|
|
6879
|
+
requestGraphQL,
|
|
6880
|
+
RATE_LIMIT_QUERY,
|
|
6881
|
+
{},
|
|
6882
|
+
{
|
|
6883
|
+
controller: options.controller,
|
|
6884
|
+
requestName: "GraphQL rate limit preflight",
|
|
6885
|
+
budget
|
|
6886
|
+
}
|
|
6887
|
+
);
|
|
6888
|
+
const preflightRateLimit = budget.rateLimit();
|
|
6889
|
+
if (budget.shouldDefer()) {
|
|
6890
|
+
options.onGraphQLCheckpoint?.(
|
|
6891
|
+
checkpointFromState({
|
|
6892
|
+
repo: options.repo,
|
|
6893
|
+
scope: checkpointScope,
|
|
6894
|
+
cursor: cursor ?? null,
|
|
6895
|
+
scannedPullRequests,
|
|
6896
|
+
matchedMergedPullRequests: baseMatchedMergedPullRequests,
|
|
6897
|
+
pageSize,
|
|
6898
|
+
rateLimit: preflightRateLimit,
|
|
6899
|
+
reason: "GraphQL budget safety reserve reached before fetching another page"
|
|
6900
|
+
})
|
|
6901
|
+
);
|
|
6902
|
+
options.onProgress?.({
|
|
6903
|
+
stage: "github_graphql_budget_deferred",
|
|
6904
|
+
repo: options.repo,
|
|
6905
|
+
remaining: preflightRateLimit?.remaining,
|
|
6906
|
+
reserve: GRAPHQL_RATE_LIMIT_RESERVE,
|
|
6907
|
+
resetAt: preflightRateLimit?.resetAt,
|
|
6908
|
+
matchedMergedPullRequests: baseMatchedMergedPullRequests
|
|
6909
|
+
});
|
|
6910
|
+
return records;
|
|
6911
|
+
}
|
|
6912
|
+
if (typeof preflightRateLimit?.remaining === "number") {
|
|
6913
|
+
const preflightPageSize = Math.max(
|
|
6914
|
+
MIN_PULL_REQUEST_PAGE_SIZE,
|
|
6915
|
+
Math.min(
|
|
6916
|
+
pageSize,
|
|
6917
|
+
Math.floor((preflightRateLimit.remaining - GRAPHQL_RATE_LIMIT_RESERVE) / 4)
|
|
6918
|
+
)
|
|
6919
|
+
);
|
|
6920
|
+
if (preflightPageSize !== pageSize) {
|
|
6921
|
+
options.onProgress?.({
|
|
6922
|
+
stage: "github_graphql_page_size_selected",
|
|
6923
|
+
repo: options.repo,
|
|
6924
|
+
previousPageSize: pageSize,
|
|
6925
|
+
nextPageSize: preflightPageSize,
|
|
6926
|
+
remaining: preflightRateLimit.remaining
|
|
6927
|
+
});
|
|
6928
|
+
pageSize = preflightPageSize;
|
|
6929
|
+
}
|
|
6930
|
+
}
|
|
6931
|
+
while (true) {
|
|
6932
|
+
let response;
|
|
6933
|
+
budget.beginPage();
|
|
6934
|
+
try {
|
|
6935
|
+
response = await requestGraphQLWithBudget(
|
|
6936
|
+
requestGraphQL,
|
|
6937
|
+
LIST_MERGED_PULL_REQUESTS_QUERY,
|
|
6938
|
+
{
|
|
6939
|
+
owner,
|
|
6940
|
+
name,
|
|
6941
|
+
first: pageSize,
|
|
6942
|
+
after: cursor ?? null
|
|
6943
|
+
},
|
|
6944
|
+
{
|
|
6945
|
+
controller: options.controller,
|
|
6946
|
+
requestName: `GraphQL /repos/${options.repo}/pullRequests`,
|
|
6947
|
+
budget
|
|
6948
|
+
}
|
|
6949
|
+
);
|
|
6950
|
+
} catch (error) {
|
|
6951
|
+
const reducedPageSize = isGitHubGraphQLResourceLimitError(error) ? nextReducedPageSize(pageSize) : void 0;
|
|
6952
|
+
if (!reducedPageSize) throw error;
|
|
6953
|
+
options.onProgress?.({
|
|
6954
|
+
stage: "github_graphql_page_size_reduced",
|
|
6955
|
+
repo: options.repo,
|
|
6956
|
+
previousPageSize: pageSize,
|
|
6957
|
+
nextPageSize: reducedPageSize,
|
|
6958
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
6959
|
+
});
|
|
6960
|
+
pageSize = reducedPageSize;
|
|
6961
|
+
continue;
|
|
6962
|
+
}
|
|
6963
|
+
const connection = response.data.repository?.pullRequests;
|
|
6964
|
+
const pullNodes = connectionNodes(connection);
|
|
6965
|
+
scannedPullRequests += pullNodes.length;
|
|
6966
|
+
const recordsBeforePage = records.length;
|
|
6967
|
+
for (const pull of pullNodes) {
|
|
6968
|
+
if (sinceTime && Date.parse(pull.updatedAt ?? pull.mergedAt ?? pull.createdAt) < sinceTime) {
|
|
6969
|
+
reachedSinceBoundary = true;
|
|
6970
|
+
break;
|
|
6971
|
+
}
|
|
6972
|
+
if (!pull.mergedAt) continue;
|
|
6973
|
+
const record = mapPullRequest(options.repo, pull);
|
|
6974
|
+
await hydratePullRequestNestedConnections(requestGraphQL, record, pull, {
|
|
6975
|
+
owner,
|
|
6976
|
+
name,
|
|
6977
|
+
controller: options.controller,
|
|
6978
|
+
budget
|
|
6979
|
+
});
|
|
6980
|
+
records.push(record);
|
|
6981
|
+
if (options.limit !== void 0 && records.length >= options.limit) break;
|
|
6982
|
+
}
|
|
6983
|
+
const pageMatchedPullRequests = records.length - recordsBeforePage;
|
|
6984
|
+
budget.completePage(pageMatchedPullRequests);
|
|
6985
|
+
options.onProgress?.({
|
|
6986
|
+
stage: "scanned_pull_request_page",
|
|
6987
|
+
repo: options.repo,
|
|
6988
|
+
all: options.limit === void 0,
|
|
6989
|
+
limit: options.limit,
|
|
6990
|
+
scannedPullRequests,
|
|
6991
|
+
matchedMergedPullRequests: baseMatchedMergedPullRequests + records.length,
|
|
6992
|
+
backend: "graphql",
|
|
6993
|
+
pageSize
|
|
6994
|
+
});
|
|
6995
|
+
const info = pageInfo(connection);
|
|
6996
|
+
const totalMatchedMergedPullRequests = baseMatchedMergedPullRequests + records.length;
|
|
6997
|
+
if (info.hasNextPage && info.endCursor && budget.shouldDefer()) {
|
|
6998
|
+
const rateLimit = budget.rateLimit();
|
|
6999
|
+
const checkpointToSave = checkpointFromState({
|
|
7000
|
+
repo: options.repo,
|
|
7001
|
+
scope: checkpointScope,
|
|
7002
|
+
cursor: info.endCursor,
|
|
7003
|
+
scannedPullRequests,
|
|
7004
|
+
matchedMergedPullRequests: totalMatchedMergedPullRequests,
|
|
7005
|
+
pageSize,
|
|
7006
|
+
rateLimit,
|
|
7007
|
+
reason: "GraphQL budget safety reserve reached"
|
|
7008
|
+
});
|
|
7009
|
+
options.onGraphQLCheckpoint?.(checkpointToSave);
|
|
7010
|
+
options.onProgress?.({
|
|
7011
|
+
stage: "github_graphql_budget_deferred",
|
|
7012
|
+
repo: options.repo,
|
|
7013
|
+
remaining: rateLimit?.remaining,
|
|
7014
|
+
reserve: GRAPHQL_RATE_LIMIT_RESERVE,
|
|
7015
|
+
resetAt: rateLimit?.resetAt,
|
|
7016
|
+
matchedMergedPullRequests: totalMatchedMergedPullRequests
|
|
7017
|
+
});
|
|
7018
|
+
break;
|
|
7019
|
+
}
|
|
7020
|
+
if (reachedSinceBoundary || options.limit !== void 0 && records.length >= options.limit || !info.hasNextPage || !info.endCursor) {
|
|
7021
|
+
options.onGraphQLCheckpoint?.(null);
|
|
7022
|
+
break;
|
|
7023
|
+
}
|
|
7024
|
+
cursor = info.endCursor;
|
|
7025
|
+
const remainingPrs = options.limit === void 0 ? void 0 : Math.max(0, options.limit - records.length);
|
|
7026
|
+
const decision = budget.choosePageSize(pageSize, remainingPrs);
|
|
7027
|
+
if (decision.pageSize !== pageSize) {
|
|
7028
|
+
options.onProgress?.({
|
|
7029
|
+
stage: "github_graphql_page_size_selected",
|
|
7030
|
+
repo: options.repo,
|
|
7031
|
+
previousPageSize: pageSize,
|
|
7032
|
+
nextPageSize: decision.pageSize,
|
|
7033
|
+
remaining: budget.rateLimit()?.remaining,
|
|
7034
|
+
averageCostPerPr: decision.averageCostPerPr
|
|
7035
|
+
});
|
|
7036
|
+
pageSize = decision.pageSize;
|
|
7037
|
+
}
|
|
7038
|
+
}
|
|
7039
|
+
options.onProgress?.({
|
|
7040
|
+
stage: "discovered_pull_requests",
|
|
7041
|
+
repo: options.repo,
|
|
7042
|
+
all: options.limit === void 0,
|
|
7043
|
+
total: records.length,
|
|
7044
|
+
limit: options.limit,
|
|
7045
|
+
detailConcurrency: options.detailConcurrency,
|
|
7046
|
+
backend: "graphql"
|
|
7047
|
+
});
|
|
7048
|
+
await enrichPullRequestPatchesWithRest({
|
|
7049
|
+
records,
|
|
7050
|
+
repo: options.repo,
|
|
7051
|
+
token: options.token,
|
|
7052
|
+
detailConcurrency: options.detailConcurrency,
|
|
7053
|
+
controller: options.restController ?? options.controller,
|
|
7054
|
+
onProgress: options.onProgress,
|
|
7055
|
+
restClient: options.restClient
|
|
7056
|
+
});
|
|
7057
|
+
return records;
|
|
7058
|
+
}
|
|
7059
|
+
|
|
6119
7060
|
// src/github/fetch-prs.ts
|
|
6120
7061
|
function resolvePullRequestFetchLimit(options) {
|
|
6121
7062
|
return options.all ? void 0 : Math.max(1, Math.min(options.limit ?? 200, 1e3));
|
|
@@ -6125,6 +7066,18 @@ function resolvePullRequestDetailConcurrency(options) {
|
|
|
6125
7066
|
if (!Number.isFinite(value)) return 5;
|
|
6126
7067
|
return Math.max(1, Math.min(Math.trunc(value), 10));
|
|
6127
7068
|
}
|
|
7069
|
+
function createProgressRateLimitController(repo, onProgress) {
|
|
7070
|
+
return {
|
|
7071
|
+
onRateLimit: (progress) => onProgress?.({
|
|
7072
|
+
stage: "github_rate_limited",
|
|
7073
|
+
repo,
|
|
7074
|
+
...progress
|
|
7075
|
+
})
|
|
7076
|
+
};
|
|
7077
|
+
}
|
|
7078
|
+
function shouldFallbackToRestAfterGraphQLError(error) {
|
|
7079
|
+
return !isGitHubRateLimitError(error) && !isGitHubGraphQLResourceLimitError(error);
|
|
7080
|
+
}
|
|
6128
7081
|
async function fetchPullRequestDetailsConcurrently(options) {
|
|
6129
7082
|
const results = new Array(options.pullNumbers.length);
|
|
6130
7083
|
let nextIndex = 0;
|
|
@@ -6169,19 +7122,12 @@ async function fetchPullRequestDetailsConcurrently(options) {
|
|
|
6169
7122
|
return result;
|
|
6170
7123
|
});
|
|
6171
7124
|
}
|
|
6172
|
-
async function
|
|
7125
|
+
async function fetchMergedPullRequestsWithRest(options, rateLimitController) {
|
|
6173
7126
|
const [owner, repo] = options.repo.split("/");
|
|
6174
7127
|
if (!owner || !repo) throw new Error(`Invalid repo '${options.repo}'. Expected owner/name.`);
|
|
6175
|
-
const octokit = createGitHubClient(options.token);
|
|
7128
|
+
const octokit = options.restClient ?? createGitHubClient(options.token);
|
|
6176
7129
|
const limit = resolvePullRequestFetchLimit(options);
|
|
6177
7130
|
const detailConcurrency = resolvePullRequestDetailConcurrency(options);
|
|
6178
|
-
const rateLimitController = {
|
|
6179
|
-
onRateLimit: (progress) => options.onProgress?.({
|
|
6180
|
-
stage: "github_rate_limited",
|
|
6181
|
-
repo: options.repo,
|
|
6182
|
-
...progress
|
|
6183
|
-
})
|
|
6184
|
-
};
|
|
6185
7131
|
const sinceTime = options.since ? Date.parse(options.since) : void 0;
|
|
6186
7132
|
const pullNumbers = [];
|
|
6187
7133
|
let scannedPullRequests = 0;
|
|
@@ -6192,7 +7138,8 @@ async function fetchMergedPullRequests(options) {
|
|
|
6192
7138
|
repo: options.repo,
|
|
6193
7139
|
all: limit === void 0,
|
|
6194
7140
|
limit,
|
|
6195
|
-
since: options.since
|
|
7141
|
+
since: options.since,
|
|
7142
|
+
backend: "rest"
|
|
6196
7143
|
});
|
|
6197
7144
|
while (true) {
|
|
6198
7145
|
const response = await requestWithGitHubRateLimit(
|
|
@@ -6226,7 +7173,8 @@ async function fetchMergedPullRequests(options) {
|
|
|
6226
7173
|
all: limit === void 0,
|
|
6227
7174
|
limit,
|
|
6228
7175
|
scannedPullRequests,
|
|
6229
|
-
matchedMergedPullRequests: pullNumbers.length
|
|
7176
|
+
matchedMergedPullRequests: pullNumbers.length,
|
|
7177
|
+
backend: "rest"
|
|
6230
7178
|
});
|
|
6231
7179
|
const hasNextPage2 = String(response.headers.link ?? "").includes('rel="next"');
|
|
6232
7180
|
if (reachedSinceBoundary || limit !== void 0 && pullNumbers.length >= limit || !hasNextPage2) {
|
|
@@ -6240,7 +7188,8 @@ async function fetchMergedPullRequests(options) {
|
|
|
6240
7188
|
all: limit === void 0,
|
|
6241
7189
|
total: pullNumbers.length,
|
|
6242
7190
|
limit,
|
|
6243
|
-
detailConcurrency
|
|
7191
|
+
detailConcurrency,
|
|
7192
|
+
backend: "rest"
|
|
6244
7193
|
});
|
|
6245
7194
|
return fetchPullRequestDetailsConcurrently({
|
|
6246
7195
|
octokit,
|
|
@@ -6251,6 +7200,42 @@ async function fetchMergedPullRequests(options) {
|
|
|
6251
7200
|
onProgress: options.onProgress
|
|
6252
7201
|
});
|
|
6253
7202
|
}
|
|
7203
|
+
async function fetchMergedPullRequests(options) {
|
|
7204
|
+
const limit = resolvePullRequestFetchLimit(options);
|
|
7205
|
+
const detailConcurrency = resolvePullRequestDetailConcurrency(options);
|
|
7206
|
+
const graphqlRateLimitController = createProgressRateLimitController(
|
|
7207
|
+
options.repo,
|
|
7208
|
+
options.onProgress
|
|
7209
|
+
);
|
|
7210
|
+
const restRateLimitController = createProgressRateLimitController(options.repo, options.onProgress);
|
|
7211
|
+
try {
|
|
7212
|
+
return await fetchMergedPullRequestsWithGraphQL({
|
|
7213
|
+
token: options.token,
|
|
7214
|
+
repo: options.repo,
|
|
7215
|
+
limit,
|
|
7216
|
+
all: options.all,
|
|
7217
|
+
detailConcurrency,
|
|
7218
|
+
since: options.since,
|
|
7219
|
+
controller: graphqlRateLimitController,
|
|
7220
|
+
restController: restRateLimitController,
|
|
7221
|
+
graphQLCheckpoint: options.graphQLCheckpoint,
|
|
7222
|
+
onGraphQLCheckpoint: options.onGraphQLCheckpoint,
|
|
7223
|
+
onProgress: options.onProgress,
|
|
7224
|
+
fetchImpl: options.fetchImpl,
|
|
7225
|
+
restClient: options.restClient
|
|
7226
|
+
});
|
|
7227
|
+
} catch (error) {
|
|
7228
|
+
if (!shouldFallbackToRestAfterGraphQLError(error)) throw error;
|
|
7229
|
+
options.onProgress?.({
|
|
7230
|
+
stage: "github_fetch_backend_fallback",
|
|
7231
|
+
repo: options.repo,
|
|
7232
|
+
from: "graphql",
|
|
7233
|
+
to: "rest",
|
|
7234
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
7235
|
+
});
|
|
7236
|
+
return fetchMergedPullRequestsWithRest(options, restRateLimitController);
|
|
7237
|
+
}
|
|
7238
|
+
}
|
|
6254
7239
|
|
|
6255
7240
|
// src/doctor.ts
|
|
6256
7241
|
import fs9 from "fs";
|
|
@@ -6315,6 +7300,49 @@ async function runDoctor(options) {
|
|
|
6315
7300
|
)
|
|
6316
7301
|
);
|
|
6317
7302
|
}
|
|
7303
|
+
if (token) {
|
|
7304
|
+
try {
|
|
7305
|
+
const graphqlOk = options.githubGraphQLCheck !== void 0 ? Boolean(await options.githubGraphQLCheck(token)) : options.githubClientFactory !== void 0 ? true : Boolean(
|
|
7306
|
+
await createGitHubGraphQLRequester({ token })(
|
|
7307
|
+
`query AnchorDoctorGraphQL {
|
|
7308
|
+
viewer { login }
|
|
7309
|
+
rateLimit { cost remaining resetAt }
|
|
7310
|
+
}`,
|
|
7311
|
+
{},
|
|
7312
|
+
{
|
|
7313
|
+
controller: {},
|
|
7314
|
+
requestName: "GraphQL doctor reachability check"
|
|
7315
|
+
}
|
|
7316
|
+
)
|
|
7317
|
+
);
|
|
7318
|
+
checks.push(
|
|
7319
|
+
check(
|
|
7320
|
+
"GitHub GraphQL reachable",
|
|
7321
|
+
graphqlOk,
|
|
7322
|
+
graphqlOk ? "GitHub GraphQL API is reachable." : "GitHub GraphQL API check returned an unsuccessful result.",
|
|
7323
|
+
"Check token scope, network access, and GraphQL rate limits. Use read-only repo access."
|
|
7324
|
+
)
|
|
7325
|
+
);
|
|
7326
|
+
} catch (error) {
|
|
7327
|
+
checks.push(
|
|
7328
|
+
check(
|
|
7329
|
+
"GitHub GraphQL reachable",
|
|
7330
|
+
false,
|
|
7331
|
+
`GitHub GraphQL check failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
7332
|
+
"Check token scope, network access, and GraphQL rate limits. Use read-only repo access."
|
|
7333
|
+
)
|
|
7334
|
+
);
|
|
7335
|
+
}
|
|
7336
|
+
} else {
|
|
7337
|
+
checks.push(
|
|
7338
|
+
check(
|
|
7339
|
+
"GitHub GraphQL reachable",
|
|
7340
|
+
false,
|
|
7341
|
+
"Skipped because token is missing.",
|
|
7342
|
+
githubAuthFixMessage()
|
|
7343
|
+
)
|
|
7344
|
+
);
|
|
7345
|
+
}
|
|
6318
7346
|
const cursorConfigPath = path20.join(gitRoot ?? cwd, ".cursor", "mcp.json");
|
|
6319
7347
|
let cursorConfig;
|
|
6320
7348
|
let cursorConfigValid = false;
|
|
@@ -6444,6 +7472,7 @@ export {
|
|
|
6444
7472
|
DEMO_CODE_FILES,
|
|
6445
7473
|
DEMO_PULL_REQUESTS,
|
|
6446
7474
|
DEMO_REPO,
|
|
7475
|
+
GitHubGraphQLError,
|
|
6447
7476
|
SCHEMA_SQL,
|
|
6448
7477
|
TEAM_RULES_FILE,
|
|
6449
7478
|
addRetrievalEval,
|
|
@@ -6467,6 +7496,7 @@ export {
|
|
|
6467
7496
|
claimKeyFor,
|
|
6468
7497
|
clampMaxResults,
|
|
6469
7498
|
classifyArchitectureArea,
|
|
7499
|
+
clearGraphQLFetchCheckpoint,
|
|
6470
7500
|
clipSentence,
|
|
6471
7501
|
confidenceAtLeast,
|
|
6472
7502
|
confidenceLevelFor,
|
|
@@ -6474,6 +7504,7 @@ export {
|
|
|
6474
7504
|
confidenceReasonsFor,
|
|
6475
7505
|
countValidTeamRules,
|
|
6476
7506
|
createGitHubClient,
|
|
7507
|
+
createGitHubGraphQLRequester,
|
|
6477
7508
|
defaultDatabasePath,
|
|
6478
7509
|
detectGitHubRepo,
|
|
6479
7510
|
detectGitRoot,
|
|
@@ -6498,6 +7529,7 @@ export {
|
|
|
6498
7529
|
extractWisdomUnits,
|
|
6499
7530
|
feedbackAdjustedScore,
|
|
6500
7531
|
fetchMergedPullRequests,
|
|
7532
|
+
fetchMergedPullRequestsWithGraphQL,
|
|
6501
7533
|
fetchPullRequestDetails,
|
|
6502
7534
|
filesFromDiff,
|
|
6503
7535
|
formatAnchorContext,
|
|
@@ -6507,6 +7539,7 @@ export {
|
|
|
6507
7539
|
getArchitectureContext,
|
|
6508
7540
|
getArchitectureMapContext,
|
|
6509
7541
|
getGitHubRateLimitDelayMs,
|
|
7542
|
+
getGraphQLFetchCheckpoint,
|
|
6510
7543
|
getIndexStatus,
|
|
6511
7544
|
getLastSyncTime,
|
|
6512
7545
|
getPlaybook,
|
|
@@ -6515,6 +7548,7 @@ export {
|
|
|
6515
7548
|
getSuggestedPrompts,
|
|
6516
7549
|
getWisdomCategoryCounts,
|
|
6517
7550
|
githubAuthFixMessage,
|
|
7551
|
+
graphQLFetchCheckpointScope,
|
|
6518
7552
|
hasHighSignalLanguage,
|
|
6519
7553
|
indexCodebase,
|
|
6520
7554
|
indexPullRequests,
|
|
@@ -6522,6 +7556,7 @@ export {
|
|
|
6522
7556
|
initPlaybooks,
|
|
6523
7557
|
initRetrievalEvals,
|
|
6524
7558
|
initializeSchema,
|
|
7559
|
+
isGitHubGraphQLResourceLimitError,
|
|
6525
7560
|
isGitHubRateLimitError,
|
|
6526
7561
|
isHardExcludedCodePath,
|
|
6527
7562
|
isTestFilePath,
|
|
@@ -6557,6 +7592,8 @@ export {
|
|
|
6557
7592
|
runDoctor,
|
|
6558
7593
|
runRetrievalEvals,
|
|
6559
7594
|
sanitizeHistoricalText,
|
|
7595
|
+
saveGraphQLFetchCheckpoint,
|
|
7596
|
+
shouldFallbackToRestAfterGraphQLError,
|
|
6560
7597
|
shouldSyncSince,
|
|
6561
7598
|
sourceTypeLabel,
|
|
6562
7599
|
stripPromptInjection,
|
|
@@ -6566,6 +7603,7 @@ export {
|
|
|
6566
7603
|
tokenizeSearchText,
|
|
6567
7604
|
truncateText,
|
|
6568
7605
|
uniqueStrings,
|
|
7606
|
+
updateGitHubGraphQLRateLimitState,
|
|
6569
7607
|
updateSyncState,
|
|
6570
7608
|
upsertPullRequest,
|
|
6571
7609
|
validateTeamRulesFile,
|