@oss-scout/mcp 0.13.1 → 0.13.2
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 +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-server.bundle.cjs +610 -182
- package/package.json +2 -2
|
@@ -42269,6 +42269,12 @@ function isAuthError(error62) {
|
|
|
42269
42269
|
return status === 403 && !isRateLimitError(error62);
|
|
42270
42270
|
}
|
|
42271
42271
|
function isRateLimitError(error62) {
|
|
42272
|
+
const e = error62;
|
|
42273
|
+
for (const gqlErrors of [e?.errors, e?.data?.errors]) {
|
|
42274
|
+
if (Array.isArray(gqlErrors) && gqlErrors.some((g) => g?.type === "RATE_LIMITED")) {
|
|
42275
|
+
return true;
|
|
42276
|
+
}
|
|
42277
|
+
}
|
|
42272
42278
|
const status = getHttpStatusCode(error62);
|
|
42273
42279
|
if (status === 429)
|
|
42274
42280
|
return true;
|
|
@@ -42622,7 +42628,8 @@ var SearchBudgetTracker = class {
|
|
|
42622
42628
|
this.reservedCalls++;
|
|
42623
42629
|
return;
|
|
42624
42630
|
}
|
|
42625
|
-
const
|
|
42631
|
+
const externalExhausted = this.knownRemaining - this.callsSinceReset - this.reservedCalls <= 0;
|
|
42632
|
+
const waitUntil = externalExhausted && this.resetAt > Date.now() ? Math.min(oldestInWindow + SEARCH_WINDOW_MS, this.resetAt) : oldestInWindow + SEARCH_WINDOW_MS;
|
|
42626
42633
|
const waitMs = waitUntil - Date.now();
|
|
42627
42634
|
if (waitMs > 0) {
|
|
42628
42635
|
debug(MODULE3, `Budget full (${this.callTimestamps.length}/${EFFECTIVE_BUDGET} in window), waiting ${waitMs}ms`);
|
|
@@ -42826,6 +42833,7 @@ var SavedCandidateSchema = external_exports.looseObject({
|
|
|
42826
42833
|
lastStatus: external_exports.enum(["still_available", "claimed", "closed", "has_pr"]).optional()
|
|
42827
42834
|
});
|
|
42828
42835
|
var PersistenceModeSchema = external_exports.enum(["local", "gist"]);
|
|
42836
|
+
var ComplexIssuesModeSchema = external_exports.enum(["off", "warm"]);
|
|
42829
42837
|
var ScoutPreferencesSchema = external_exports.looseObject({
|
|
42830
42838
|
githubUsername: external_exports.string().default(""),
|
|
42831
42839
|
languages: external_exports.array(external_exports.string()).default(["any"]),
|
|
@@ -42888,6 +42896,17 @@ var ScoutPreferencesSchema = external_exports.looseObject({
|
|
|
42888
42896
|
* local network.
|
|
42889
42897
|
*/
|
|
42890
42898
|
slmTriageHost: external_exports.string().default(""),
|
|
42899
|
+
/**
|
|
42900
|
+
* Surface issues that are not starter-shaped. "warm" unlocks them in repos
|
|
42901
|
+
* where the user already has a merged PR, and nowhere else: there a PR
|
|
42902
|
+
* that merely mentions the issue no longer counts as an existing PR (only
|
|
42903
|
+
* one that closes it does), a maintainer-filed or triage-accepted issue
|
|
42904
|
+
* passes the requirements check without code in its body, and a
|
|
42905
|
+
* maintainer-filed issue is not dropped for a question-shaped or short
|
|
42906
|
+
* title. Unlocked candidates carry `complexityUnlocked: true`. "off" (the
|
|
42907
|
+
* default) keeps the same rules everywhere.
|
|
42908
|
+
*/
|
|
42909
|
+
complexIssues: ComplexIssuesModeSchema.default("off"),
|
|
42891
42910
|
/**
|
|
42892
42911
|
* Minimum merged-PR count for a repo to qualify as an anchor in
|
|
42893
42912
|
* `scout features` (#98). Lowering surfaces more anchors at the cost
|
|
@@ -42950,10 +42969,11 @@ var ScoutStateSchema = external_exports.looseObject({
|
|
|
42950
42969
|
* consumer wraps at use time so a shrinking language list never needs
|
|
42951
42970
|
* clamping here).
|
|
42952
42971
|
*
|
|
42953
|
-
* `
|
|
42954
|
-
*
|
|
42955
|
-
*
|
|
42956
|
-
*
|
|
42972
|
+
* `starredOffset` and `maintainedOffset` do the same for the repo lists
|
|
42973
|
+
* those phases cap per run (#324, #333): each run searches the next window
|
|
42974
|
+
* of the list instead of always the first N, so every starred repo gets
|
|
42975
|
+
* searched within a few runs. `phase0Offset` is still advanced but no
|
|
42976
|
+
* longer read: the merged phase reads every contributed repo each run.
|
|
42957
42977
|
*
|
|
42958
42978
|
* `strategyOffset` is the round-robin cursor (#336): a position in
|
|
42959
42979
|
* CONCRETE_STRATEGIES. A search runs the first usable strategy from there,
|
|
@@ -43019,15 +43039,23 @@ function isDocOnlyIssue(item) {
|
|
|
43019
43039
|
return false;
|
|
43020
43040
|
return nonEmptyLabels.every((n) => DOC_ONLY_LABELS.has(n));
|
|
43021
43041
|
}
|
|
43042
|
+
var MAINTAINER_ASSOCIATIONS = /* @__PURE__ */ new Set([
|
|
43043
|
+
"OWNER",
|
|
43044
|
+
"MEMBER",
|
|
43045
|
+
"COLLABORATOR"
|
|
43046
|
+
]);
|
|
43022
43047
|
var NON_ACTIONABLE_LABELS = /* @__PURE__ */ new Set(["question", "discussion", "support"]);
|
|
43023
|
-
|
|
43048
|
+
var QUESTION_TITLE = /^(?:(?:how|why|what|when|where|which|who)\b|(?:is|are|does|do|can|could|should|would|will|has|have)\s+(?:i|we|you|it|this|that|these|those|the|a|an|any|anyone|someone|there|my|your|our)\b)/i;
|
|
43049
|
+
function isNonActionableIssue(item, opts = {}) {
|
|
43024
43050
|
const labelNames = (item.labels ?? []).map((l) => (typeof l === "string" ? l : l.name || "").toLowerCase());
|
|
43025
43051
|
if (labelNames.some((n) => NON_ACTIONABLE_LABELS.has(n)))
|
|
43026
43052
|
return true;
|
|
43053
|
+
if (opts.labelsOnly)
|
|
43054
|
+
return false;
|
|
43027
43055
|
const title = (item.title ?? "").trim();
|
|
43028
43056
|
if (title.endsWith("?"))
|
|
43029
43057
|
return true;
|
|
43030
|
-
if (
|
|
43058
|
+
if (QUESTION_TITLE.test(title))
|
|
43031
43059
|
return true;
|
|
43032
43060
|
return title.split(/\s+/).filter(Boolean).length < 3;
|
|
43033
43061
|
}
|
|
@@ -43570,6 +43598,18 @@ function isNotModifiedError(err) {
|
|
|
43570
43598
|
return getHttpStatusCode(err) === 304;
|
|
43571
43599
|
}
|
|
43572
43600
|
|
|
43601
|
+
// ../core/dist/core/linked-pr.js
|
|
43602
|
+
var STALLED_PR_THRESHOLD_DAYS = 30;
|
|
43603
|
+
function isLinkedPRStalled(linkedPR, now = /* @__PURE__ */ new Date(), thresholdDays = STALLED_PR_THRESHOLD_DAYS) {
|
|
43604
|
+
if (!linkedPR || linkedPR.state !== "open" || !linkedPR.updatedAt)
|
|
43605
|
+
return false;
|
|
43606
|
+
const updated = new Date(linkedPR.updatedAt);
|
|
43607
|
+
if (Number.isNaN(updated.getTime()))
|
|
43608
|
+
return false;
|
|
43609
|
+
const ageDays = (now.getTime() - updated.getTime()) / (1e3 * 60 * 60 * 24);
|
|
43610
|
+
return ageDays >= thresholdDays;
|
|
43611
|
+
}
|
|
43612
|
+
|
|
43573
43613
|
// ../core/dist/core/issue-eligibility.js
|
|
43574
43614
|
function isLinkedPREvent(e) {
|
|
43575
43615
|
return e.event === "cross-referenced" && !!e.source?.issue?.pull_request;
|
|
@@ -43605,10 +43645,25 @@ var ISSUE_OBJECT = String.raw`(?:this\b|it\b|that\b|the\b|#\d+|issue\b)`;
|
|
|
43605
43645
|
var FIRST_PERSON_CLAIM_PATTERNS = [
|
|
43606
43646
|
new RegExp(String.raw`\bi\s*(?:'ll|will) take ${ISSUE_OBJECT}`),
|
|
43607
43647
|
new RegExp(String.raw`\bi\s*(?:'d|would) (?:like|love) to work on ${ISSUE_OBJECT}`),
|
|
43648
|
+
// "I'd like to investigate and fix this", "I'd love to tackle it".
|
|
43649
|
+
new RegExp(String.raw`\bi\s*(?:'d|would) (?:like|love) to (?:\w+ and )?(?:fix|tackle|take on|pick up|implement) ${ISSUE_OBJECT}`),
|
|
43608
43650
|
/\bi\s*(?:'m|am) on it\b/,
|
|
43609
43651
|
/\bi\s*(?:'ll|will) submit a pr\b/,
|
|
43610
43652
|
/\bassigned to me\b/
|
|
43611
43653
|
];
|
|
43654
|
+
var OFFER_SUBJECT = String.raw`(?:^\s*|\b(?:i\s*(?:'m|am|'d be|would be|'ll be|will be)|we\s*(?:'re|are|'d be|would be|'ll be|will be))\s+(?:(?:really|very|more than|absolutely|also)\s+)?)`;
|
|
43655
|
+
var OFFER_PATTERNS = [
|
|
43656
|
+
new RegExp(String.raw`${OFFER_SUBJECT}(?:happy|glad|willing|keen) to (?:open|send|submit|make|put up|raise|contribute|write) (?:a |the |an )?(?:[\w-]+ ){0,2}(?:pr|pull request|patch|fix)\b`),
|
|
43657
|
+
new RegExp(String.raw`${OFFER_SUBJECT}(?:happy|glad|willing|keen) to (?:have a go|take a stab)\b`),
|
|
43658
|
+
/\bi can (?:send|open|submit|put up) (?:this |it )?(?:as )?(?:a |the )?(?:pr|pull request|patch|fix)\b/,
|
|
43659
|
+
/\bi\s*(?:'ll|will) (?:open|send|put up|raise) a (?:pr|pull request)\b/,
|
|
43660
|
+
/\bi\s*(?:'d|would) love to contribute (?:this|it|a fix|the fix)\b/,
|
|
43661
|
+
/\bi plan to (?:make|send|submit|open) (?:a |the )?(?:[\w-]+ ){0,2}(?:pr|pull request|fix|change)\b/
|
|
43662
|
+
];
|
|
43663
|
+
var OFFER_VETO = /\bnot\b|n't\b|\bbut\b|\bunless\b/;
|
|
43664
|
+
function stripNonStatements(body) {
|
|
43665
|
+
return body.replace(/<!--[\s\S]*?-->/g, " ").replace(/```[\s\S]*?```/g, " ").replace(/^\s*[-*] \[ \].*$/gm, " ").replace(/^\s*>.*$/gm, " ");
|
|
43666
|
+
}
|
|
43612
43667
|
var GENERIC_WORKING_PATTERNS = [
|
|
43613
43668
|
/\bworking on (?:this|it)\b/,
|
|
43614
43669
|
/\bworking on a (?:fix|pr)\b/
|
|
@@ -43616,7 +43671,7 @@ var GENERIC_WORKING_PATTERNS = [
|
|
|
43616
43671
|
var PERMISSION_CLAIM_PATTERN = new RegExp(String.raw`\b(?:can|may|could) i (?:work on|take) ${ISSUE_OBJECT}`);
|
|
43617
43672
|
var NON_CLAIM_SUBJECTS = /\b(?:anyone|anybody|someone|somebody|who|whoever|nobody|no[- ]?one|not)\b/;
|
|
43618
43673
|
function commentClaimsIssue(body) {
|
|
43619
|
-
const clauses = body.toLowerCase().split(/(?<=[.!?])|\n+/);
|
|
43674
|
+
const clauses = stripNonStatements(body).toLowerCase().split(/(?<=[.!?])|\n+/);
|
|
43620
43675
|
for (const clause of clauses) {
|
|
43621
43676
|
if (PERMISSION_CLAIM_PATTERN.test(clause))
|
|
43622
43677
|
return true;
|
|
@@ -43624,6 +43679,8 @@ function commentClaimsIssue(body) {
|
|
|
43624
43679
|
continue;
|
|
43625
43680
|
if (FIRST_PERSON_CLAIM_PATTERNS.some((p) => p.test(clause)))
|
|
43626
43681
|
return true;
|
|
43682
|
+
if (!OFFER_VETO.test(clause) && OFFER_PATTERNS.some((p) => p.test(clause)))
|
|
43683
|
+
return true;
|
|
43627
43684
|
if (NON_CLAIM_SUBJECTS.test(clause))
|
|
43628
43685
|
continue;
|
|
43629
43686
|
if (GENERIC_WORKING_PATTERNS.some((p) => p.test(clause)))
|
|
@@ -43631,8 +43688,67 @@ function commentClaimsIssue(body) {
|
|
|
43631
43688
|
}
|
|
43632
43689
|
return false;
|
|
43633
43690
|
}
|
|
43634
|
-
|
|
43691
|
+
function toLinkedPR(pr) {
|
|
43692
|
+
return {
|
|
43693
|
+
number: pr.number,
|
|
43694
|
+
// A deleted account has no login; the PR still counts.
|
|
43695
|
+
author: pr.author?.login ?? "ghost",
|
|
43696
|
+
state: pr.state === "OPEN" ? "open" : "closed",
|
|
43697
|
+
merged: pr.state === "MERGED",
|
|
43698
|
+
url: pr.url,
|
|
43699
|
+
updatedAt: pr.updatedAt
|
|
43700
|
+
};
|
|
43701
|
+
}
|
|
43702
|
+
var CLOSING_PR_RANK = { MERGED: 0, OPEN: 1, CLOSED: 2 };
|
|
43703
|
+
async function checkNoClosingPR(octokit, owner, repo, issueNumber, username) {
|
|
43704
|
+
const data = await octokit.graphql(`query closingPRs($owner: String!, $repo: String!, $number: Int!) {
|
|
43705
|
+
repository(owner: $owner, name: $repo) {
|
|
43706
|
+
issue(number: $number) {
|
|
43707
|
+
closedByPullRequestsReferences(first: 20, includeClosedPrs: true) {
|
|
43708
|
+
nodes { number state url updatedAt author { login } }
|
|
43709
|
+
}
|
|
43710
|
+
timelineItems(itemTypes: [CROSS_REFERENCED_EVENT], last: 30) {
|
|
43711
|
+
nodes {
|
|
43712
|
+
... on CrossReferencedEvent {
|
|
43713
|
+
source {
|
|
43714
|
+
... on PullRequest { number state url updatedAt author { login } }
|
|
43715
|
+
}
|
|
43716
|
+
}
|
|
43717
|
+
}
|
|
43718
|
+
}
|
|
43719
|
+
}
|
|
43720
|
+
}
|
|
43721
|
+
}`, { owner, repo, number: issueNumber });
|
|
43722
|
+
const issue2 = data.repository?.issue;
|
|
43723
|
+
if (!issue2) {
|
|
43724
|
+
return {
|
|
43725
|
+
passed: true,
|
|
43726
|
+
inconclusive: true,
|
|
43727
|
+
reason: "issue not returned by GraphQL",
|
|
43728
|
+
linkedPR: null
|
|
43729
|
+
};
|
|
43730
|
+
}
|
|
43731
|
+
const nodes = (issue2.closedByPullRequestsReferences?.nodes ?? []).filter((n) => n != null);
|
|
43732
|
+
if (nodes.length > 0) {
|
|
43733
|
+
const pr = [...nodes].sort((a, b) => CLOSING_PR_RANK[a.state] - CLOSING_PR_RANK[b.state])[0];
|
|
43734
|
+
return { passed: false, linkedPR: toLinkedPR(pr) };
|
|
43735
|
+
}
|
|
43736
|
+
const openMentions = (issue2.timelineItems?.nodes ?? []).map((n) => n?.source).filter((s) => s?.state === "OPEN" && typeof s.number === "number" && typeof s.url === "string").map(toLinkedPR);
|
|
43737
|
+
const own2 = openMentions.find((pr) => username !== "" && pr.author.toLowerCase() === username.toLowerCase());
|
|
43738
|
+
if (own2)
|
|
43739
|
+
return { passed: false, linkedPR: own2 };
|
|
43740
|
+
const active = openMentions.filter((pr) => !isLinkedPRStalled(pr));
|
|
43741
|
+
return {
|
|
43742
|
+
passed: true,
|
|
43743
|
+
linkedPR: null,
|
|
43744
|
+
...active.length > 0 ? { openMentions: active } : {}
|
|
43745
|
+
};
|
|
43746
|
+
}
|
|
43747
|
+
async function checkNoExistingPR(octokit, owner, repo, issueNumber, opts = {}) {
|
|
43635
43748
|
try {
|
|
43749
|
+
if (opts.closingOnly) {
|
|
43750
|
+
return await checkNoClosingPR(octokit, owner, repo, issueNumber, opts.username ?? "");
|
|
43751
|
+
}
|
|
43636
43752
|
const timeline = await paginateAll((page) => octokit.issues.listEventsForTimeline({
|
|
43637
43753
|
owner,
|
|
43638
43754
|
repo,
|
|
@@ -43747,9 +43863,15 @@ function analyzeRequirements(body) {
|
|
|
43747
43863
|
|
|
43748
43864
|
// ../core/dist/core/probe-repo-file.js
|
|
43749
43865
|
var MODULE6 = "probe-repo-file";
|
|
43866
|
+
var MISSING_FILE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
43750
43867
|
async function probeRepoFile(octokit, owner, repo, path6) {
|
|
43868
|
+
const url2 = `/repos/${owner}/${repo}/contents/${path6}`;
|
|
43869
|
+
const missingKey = `missing:${url2}`;
|
|
43870
|
+
if (getHttpCache().getIfFresh(missingKey, MISSING_FILE_TTL_MS) !== null) {
|
|
43871
|
+
return { text: null, transient: false };
|
|
43872
|
+
}
|
|
43751
43873
|
try {
|
|
43752
|
-
const data = await cachedRequest(getHttpCache(),
|
|
43874
|
+
const data = await cachedRequest(getHttpCache(), url2, (headers) => octokit.repos.getContent({ owner, repo, path: path6, headers }));
|
|
43753
43875
|
if (data && typeof data === "object" && "content" in data && typeof data.content === "string") {
|
|
43754
43876
|
return {
|
|
43755
43877
|
text: Buffer.from(data.content, "base64").toString("utf-8"),
|
|
@@ -43759,8 +43881,10 @@ async function probeRepoFile(octokit, owner, repo, path6) {
|
|
|
43759
43881
|
return { text: null, transient: false };
|
|
43760
43882
|
} catch (error62) {
|
|
43761
43883
|
const status = getHttpStatusCode(error62);
|
|
43762
|
-
if (status === 404)
|
|
43884
|
+
if (status === 404) {
|
|
43885
|
+
getHttpCache().set(missingKey, "", true);
|
|
43763
43886
|
return { text: null, transient: false };
|
|
43887
|
+
}
|
|
43764
43888
|
rethrowIfFatal(error62);
|
|
43765
43889
|
warn(MODULE6, `Unexpected error fetching ${path6} from ${owner}/${repo}: ${errorMessage(error62)}`);
|
|
43766
43890
|
return { text: null, transient: true };
|
|
@@ -43770,23 +43894,18 @@ async function probeRepoFile(octokit, owner, repo, path6) {
|
|
|
43770
43894
|
// ../core/dist/core/repo-health.js
|
|
43771
43895
|
var MODULE7 = "repo-health";
|
|
43772
43896
|
var MERGE_WINDOW_DAYS = 90;
|
|
43773
|
-
async function fetchRepoMergeStats(octokit, owner, repo
|
|
43897
|
+
async function fetchRepoMergeStats(octokit, owner, repo) {
|
|
43774
43898
|
const since = new Date(Date.now() - MERGE_WINDOW_DAYS * 24 * 60 * 60 * 1e3).toISOString().slice(0, 10);
|
|
43775
|
-
const
|
|
43776
|
-
|
|
43777
|
-
|
|
43778
|
-
|
|
43779
|
-
|
|
43780
|
-
|
|
43781
|
-
|
|
43782
|
-
|
|
43783
|
-
|
|
43784
|
-
|
|
43785
|
-
tracker.recordCall();
|
|
43786
|
-
}
|
|
43787
|
-
};
|
|
43788
|
-
const merged = await count("is:pr is:merged");
|
|
43789
|
-
const closedUnmerged = await count("is:pr is:unmerged is:closed");
|
|
43899
|
+
const scope = `repo:${owner}/${repo} is:pr closed:>=${since}`;
|
|
43900
|
+
const data = await octokit.graphql(`query repoMergeStats($merged: String!, $closedUnmerged: String!) {
|
|
43901
|
+
merged: search(type: ISSUE, query: $merged, first: 0) { issueCount }
|
|
43902
|
+
closedUnmerged: search(type: ISSUE, query: $closedUnmerged, first: 0) { issueCount }
|
|
43903
|
+
}`, {
|
|
43904
|
+
merged: `${scope} is:merged`,
|
|
43905
|
+
closedUnmerged: `${scope} is:unmerged is:closed`
|
|
43906
|
+
});
|
|
43907
|
+
const merged = data.merged.issueCount;
|
|
43908
|
+
const closedUnmerged = data.closedUnmerged.issueCount;
|
|
43790
43909
|
const denom = merged + closedUnmerged;
|
|
43791
43910
|
return {
|
|
43792
43911
|
recentMergedPRCount: merged,
|
|
@@ -43812,7 +43931,7 @@ function pruneCache() {
|
|
|
43812
43931
|
}
|
|
43813
43932
|
}
|
|
43814
43933
|
}
|
|
43815
|
-
async function checkProjectHealth(octokit, owner, repo
|
|
43934
|
+
async function checkProjectHealth(octokit, owner, repo) {
|
|
43816
43935
|
const cache = getHttpCache();
|
|
43817
43936
|
const healthCacheKey = versionedCacheKey(`health:${owner}/${repo}`);
|
|
43818
43937
|
try {
|
|
@@ -43830,7 +43949,7 @@ async function checkProjectHealth(octokit, owner, repo, tracker = getSearchBudge
|
|
|
43830
43949
|
const lastCommitAt = lastCommit?.commit?.author?.date || repoData.pushed_at;
|
|
43831
43950
|
const daysSinceLastCommit = daysBetween(new Date(lastCommitAt));
|
|
43832
43951
|
const ciStatus = "unknown";
|
|
43833
|
-
const mergeStats = await fetchRepoMergeStats(octokit, owner, repo
|
|
43952
|
+
const mergeStats = await fetchRepoMergeStats(octokit, owner, repo);
|
|
43834
43953
|
return {
|
|
43835
43954
|
repo: `${owner}/${repo}`,
|
|
43836
43955
|
lastCommitAt,
|
|
@@ -43900,11 +44019,14 @@ async function fetchContributionGuidelinesUncached(octokit, owner, repo) {
|
|
|
43900
44019
|
return guidelines;
|
|
43901
44020
|
}
|
|
43902
44021
|
}
|
|
43903
|
-
|
|
43904
|
-
|
|
43905
|
-
|
|
43906
|
-
|
|
43907
|
-
|
|
44022
|
+
const transient = results.some((r) => r.status === "fulfilled" && r.value.transient);
|
|
44023
|
+
if (!transient) {
|
|
44024
|
+
guidelinesCache.set(cacheKey, {
|
|
44025
|
+
guidelines: void 0,
|
|
44026
|
+
fetchedAt: Date.now()
|
|
44027
|
+
});
|
|
44028
|
+
pruneCache();
|
|
44029
|
+
}
|
|
43908
44030
|
return void 0;
|
|
43909
44031
|
}
|
|
43910
44032
|
function parseContributionGuidelines(content) {
|
|
@@ -43976,7 +44098,15 @@ var ANTI_LLM_KEYWORDS = [
|
|
|
43976
44098
|
"no use of generative ai",
|
|
43977
44099
|
"ban on ai-generated",
|
|
43978
44100
|
"prohibit ai-generated",
|
|
43979
|
-
"prohibits ai-generated"
|
|
44101
|
+
"prohibits ai-generated",
|
|
44102
|
+
"not be completed by automated agents",
|
|
44103
|
+
"must not be done by automated agents",
|
|
44104
|
+
"tasks are for humans only",
|
|
44105
|
+
// AGENTS.md bans (e.g. servo). Most AGENTS.md files are agent guides
|
|
44106
|
+
// that welcome AI work, so only outright refusals are listed.
|
|
44107
|
+
"do not generate code",
|
|
44108
|
+
"explicitly rejected the generative use of agents",
|
|
44109
|
+
"you are explicitly forbidden from modifying"
|
|
43980
44110
|
];
|
|
43981
44111
|
function scanForAntiLLMPolicy(text) {
|
|
43982
44112
|
if (!text)
|
|
@@ -44004,6 +44134,16 @@ var SOURCE_FILE_FAMILIES = [
|
|
|
44004
44134
|
"code_of_conduct.md"
|
|
44005
44135
|
]
|
|
44006
44136
|
},
|
|
44137
|
+
{
|
|
44138
|
+
// Agent instruction files; matched only on the refusal keywords above.
|
|
44139
|
+
canonical: "AGENTS.md",
|
|
44140
|
+
paths: ["AGENTS.md"]
|
|
44141
|
+
},
|
|
44142
|
+
{
|
|
44143
|
+
// Repos that state an AI policy in a dedicated file (e.g. cot-rs/cot).
|
|
44144
|
+
canonical: "AI_POLICY.md",
|
|
44145
|
+
paths: ["AI_POLICY.md", ".github/AI_POLICY.md"]
|
|
44146
|
+
},
|
|
44007
44147
|
{
|
|
44008
44148
|
canonical: "README.md",
|
|
44009
44149
|
paths: ["README.md", "readme.md", "Readme.md"]
|
|
@@ -44082,6 +44222,11 @@ async function fetchAndScanAntiLLMPolicy(octokit, owner, repo, options) {
|
|
|
44082
44222
|
|
|
44083
44223
|
// ../core/dist/core/issue-graphql.js
|
|
44084
44224
|
var MODULE8 = "issue-graphql";
|
|
44225
|
+
function looksLikeBotLogin(login) {
|
|
44226
|
+
if (!login)
|
|
44227
|
+
return false;
|
|
44228
|
+
return /(?:^|-)bot(?:-|$)/i.test(login);
|
|
44229
|
+
}
|
|
44085
44230
|
function issueCoreKey(owner, repo, number4) {
|
|
44086
44231
|
return `${owner}/${repo}#${number4}`;
|
|
44087
44232
|
}
|
|
@@ -44094,7 +44239,11 @@ function normalizeNode(node2) {
|
|
|
44094
44239
|
labels: node2.labels.nodes.map((l) => l.name),
|
|
44095
44240
|
commentCount: node2.comments.totalCount,
|
|
44096
44241
|
createdAt: node2.createdAt,
|
|
44097
|
-
updatedAt: node2.updatedAt
|
|
44242
|
+
updatedAt: node2.updatedAt,
|
|
44243
|
+
authorLogin: node2.author?.login,
|
|
44244
|
+
authorIsBot: node2.author?.__typename === "Bot" || /\[bot\]$/i.test(node2.author?.login ?? ""),
|
|
44245
|
+
authorLooksLikeBot: looksLikeBotLogin(node2.author?.login),
|
|
44246
|
+
authorIsMaintainer: MAINTAINER_ASSOCIATIONS.has(node2.authorAssociation ?? "")
|
|
44098
44247
|
};
|
|
44099
44248
|
}
|
|
44100
44249
|
async function prefetchIssueCores(octokit, issues) {
|
|
@@ -44122,6 +44271,8 @@ async function prefetchIssueCores(octokit, issues) {
|
|
|
44122
44271
|
comments { totalCount }
|
|
44123
44272
|
createdAt
|
|
44124
44273
|
updatedAt
|
|
44274
|
+
author { login __typename }
|
|
44275
|
+
authorAssociation
|
|
44125
44276
|
}
|
|
44126
44277
|
}`);
|
|
44127
44278
|
});
|
|
@@ -44231,7 +44382,7 @@ async function graphqlSearchIssues(octokit, query, first) {
|
|
|
44231
44382
|
title
|
|
44232
44383
|
updatedAt
|
|
44233
44384
|
labels(first: 20) { nodes { name } }
|
|
44234
|
-
repository { nameWithOwner }
|
|
44385
|
+
repository { nameWithOwner stargazerCount }
|
|
44235
44386
|
}
|
|
44236
44387
|
}
|
|
44237
44388
|
}
|
|
@@ -44268,7 +44419,8 @@ async function graphqlSearchIssues(octokit, query, first) {
|
|
|
44268
44419
|
repository_url: `https://api.github.com/repos/${nameWithOwner}`,
|
|
44269
44420
|
updated_at: node2.updatedAt ?? "",
|
|
44270
44421
|
title: node2.title,
|
|
44271
|
-
labels: node2.labels?.nodes ?? []
|
|
44422
|
+
labels: node2.labels?.nodes ?? [],
|
|
44423
|
+
repositoryStars: node2.repository?.stargazerCount
|
|
44272
44424
|
});
|
|
44273
44425
|
}
|
|
44274
44426
|
return { total_count: search.issueCount ?? 0, items };
|
|
@@ -44432,6 +44584,9 @@ function isValidTriageShape(value) {
|
|
|
44432
44584
|
var MODULE10 = "issue-vetting";
|
|
44433
44585
|
var MAX_CONCURRENT_VETTING = 3;
|
|
44434
44586
|
var VETTING_CACHE_TTL_MS = 15 * 60 * 1e3;
|
|
44587
|
+
var DECIDED_LABEL = /\b(?:accepted|confirmed|approved|triaged|planned|help[- _]wanted)\b/i;
|
|
44588
|
+
var DECIDED_LABEL_VETO = /\b(?:not|needs?|to[- ]be|wontfix|duplicate|cla|answer)\b/i;
|
|
44589
|
+
var UNDECIDED_MARKER = /\b(?:tracking|epic|meta|rfc|roadmap|blocked|on[- ]hold|question|discussion|support|needs?[- ](?:info|design|discussion|triage|decision))\b/i;
|
|
44435
44590
|
function repoAcceptsContributionsFromHealth(projectHealth) {
|
|
44436
44591
|
if (projectHealth.checkFailed || projectHealth.recentMergedPRCount == null) {
|
|
44437
44592
|
return null;
|
|
@@ -44507,6 +44662,8 @@ function deriveRecommendation(input2) {
|
|
|
44507
44662
|
if (input2.issueClosed) {
|
|
44508
44663
|
reasonsToSkip.push("Issue is closed");
|
|
44509
44664
|
}
|
|
44665
|
+
if (input2.notTakeableReason)
|
|
44666
|
+
reasonsToSkip.push(input2.notTakeableReason);
|
|
44510
44667
|
let recommendation;
|
|
44511
44668
|
if (input2.issueClosed) {
|
|
44512
44669
|
recommendation = "skip";
|
|
@@ -44514,6 +44671,8 @@ function deriveRecommendation(input2) {
|
|
|
44514
44671
|
recommendation = "skip";
|
|
44515
44672
|
} else if (input2.ownPR) {
|
|
44516
44673
|
recommendation = "skip";
|
|
44674
|
+
} else if (input2.notTakeableReason) {
|
|
44675
|
+
recommendation = "skip";
|
|
44517
44676
|
} else if (input2.passedAllChecks && input2.repoAcceptsContributions !== false) {
|
|
44518
44677
|
recommendation = "approve";
|
|
44519
44678
|
} else if (reasonsToSkip.length > 2) {
|
|
@@ -44526,6 +44685,11 @@ function deriveRecommendation(input2) {
|
|
|
44526
44685
|
recommendation = "needs_review";
|
|
44527
44686
|
notes.push("Recommendation downgraded: one or more checks were inconclusive");
|
|
44528
44687
|
}
|
|
44688
|
+
if (input2.cautionReasons?.length) {
|
|
44689
|
+
reasonsToSkip.push(...input2.cautionReasons);
|
|
44690
|
+
if (recommendation === "approve")
|
|
44691
|
+
recommendation = "needs_review";
|
|
44692
|
+
}
|
|
44529
44693
|
return { notes, reasonsToApprove, reasonsToSkip, recommendation };
|
|
44530
44694
|
}
|
|
44531
44695
|
var IssueVetter = class {
|
|
@@ -44556,7 +44720,9 @@ var IssueVetter = class {
|
|
|
44556
44720
|
async vetIssue(issueUrl, opts) {
|
|
44557
44721
|
const cache = getHttpCache();
|
|
44558
44722
|
const sigKey = opts?.featureSignals ? `:r${opts.featureSignals.reactions}c${opts.featureSignals.comments}m${opts.featureSignals.hasMilestone ? 1 : 0}o${opts.featureSignals.onRoadmap ? 1 : 0}w${opts.featureSignals.wontfixNoContributor ? 1 : 0}` : "";
|
|
44559
|
-
const
|
|
44723
|
+
const complexMode = this.stateReader.getComplexIssuesMode?.() ?? "off";
|
|
44724
|
+
const modeKey = complexMode === "off" ? "" : `:cx-${complexMode}`;
|
|
44725
|
+
const cacheKey = versionedCacheKey(`vet:${issueUrl}${sigKey}${modeKey}`);
|
|
44560
44726
|
const cached2 = cache.getIfFresh(cacheKey, VETTING_CACHE_TTL_MS);
|
|
44561
44727
|
if (cached2 && typeof cached2 === "object" && "issue" in cached2 && "viabilityScore" in cached2) {
|
|
44562
44728
|
debug(MODULE10, `Vetting cache hit for ${issueUrl}`);
|
|
@@ -44570,23 +44736,45 @@ var IssueVetter = class {
|
|
|
44570
44736
|
const repoFullName = `${owner}/${repo}`;
|
|
44571
44737
|
const core = opts?.prefetched ?? await this.fetchIssueCore(owner, repo, number4);
|
|
44572
44738
|
const reposWithMergedPRs = this.stateReader.getReposWithMergedPRs();
|
|
44573
|
-
const
|
|
44739
|
+
const repoLower = repoFullName.toLowerCase();
|
|
44740
|
+
const hasMergedPRsInRepo = reposWithMergedPRs.some((r) => r.toLowerCase() === repoLower);
|
|
44741
|
+
const complexityUnlocked = complexMode === "warm" && hasMergedPRsInRepo;
|
|
44742
|
+
const username = this.stateReader.getGitHubUsername?.() ?? "";
|
|
44574
44743
|
const [existingPRCheck, claimCheck, projectHealth, contributionGuidelines, userMergedPRCount] = await Promise.all([
|
|
44575
|
-
checkNoExistingPR(this.octokit, owner, repo, number4
|
|
44744
|
+
checkNoExistingPR(this.octokit, owner, repo, number4, {
|
|
44745
|
+
closingOnly: complexityUnlocked,
|
|
44746
|
+
username
|
|
44747
|
+
}),
|
|
44576
44748
|
checkNotClaimed(this.octokit, owner, repo, number4, core.commentCount),
|
|
44577
|
-
checkProjectHealth(this.octokit, owner, repo
|
|
44749
|
+
checkProjectHealth(this.octokit, owner, repo),
|
|
44578
44750
|
fetchContributionGuidelines(this.octokit, owner, repo),
|
|
44579
44751
|
hasMergedPRsInRepo ? Promise.resolve(0) : checkUserMergedPRsInRepo(this.octokit, owner, repo, this.budgetTracker)
|
|
44580
44752
|
]);
|
|
44581
44753
|
const antiLLMPolicy = await fetchAndScanAntiLLMPolicy(this.octokit, owner, repo, { contributingText: contributionGuidelines?.rawContent });
|
|
44582
44754
|
const noExistingPR = existingPRCheck.passed;
|
|
44583
44755
|
const notClaimed = claimCheck.passed;
|
|
44584
|
-
const
|
|
44756
|
+
const isOwnIssue = username !== "" && core.authorLogin?.toLowerCase() === username.toLowerCase();
|
|
44757
|
+
let notTakeableReason;
|
|
44758
|
+
if (core.authorIsBot) {
|
|
44759
|
+
notTakeableReason = "Filed by a bot account";
|
|
44760
|
+
} else if (!isOwnIssue && commentClaimsIssue(core.body)) {
|
|
44761
|
+
notTakeableReason = "Reporter offered to send the fix";
|
|
44762
|
+
}
|
|
44763
|
+
const cautionReasons = [];
|
|
44764
|
+
if (!core.authorIsBot && core.authorLooksLikeBot)
|
|
44765
|
+
cautionReasons.push("Author login looks like a bot");
|
|
44766
|
+
const openMentions = existingPRCheck.openMentions ?? [];
|
|
44767
|
+
if (openMentions.length > 0)
|
|
44768
|
+
cautionReasons.push(`Open PR ${openMentions.map((pr) => `#${pr.number}`).join(", ")} mentions this issue`);
|
|
44769
|
+
if (antiLLMPolicy.matched)
|
|
44770
|
+
cautionReasons.push(`AI contribution policy in ${antiLLMPolicy.sourceFile ?? "repo docs"}`);
|
|
44585
44771
|
const linkedPR = existingPRCheck.linkedPR;
|
|
44586
44772
|
const ownPR = !noExistingPR && !!linkedPR && linkedPR.state === "open" && !linkedPR.merged && username !== "" && linkedPR.author.toLowerCase() === username.toLowerCase();
|
|
44587
44773
|
const linkedPRMerged = !!linkedPR && linkedPR.merged;
|
|
44588
44774
|
const linkedPRClosed = !!linkedPR && linkedPR.state === "closed" && !linkedPR.merged;
|
|
44589
|
-
const
|
|
44775
|
+
const bodyIsClear = analyzeRequirements(core.body);
|
|
44776
|
+
const maintainerDecided = complexityUnlocked && !bodyIsClear && core.body.trim().length >= 50 && ![core.title, ...core.labels].some((t) => UNDECIDED_MARKER.test(t)) && (!!core.authorIsMaintainer && !isOwnIssue || core.labels.some((l) => DECIDED_LABEL.test(l) && !DECIDED_LABEL_VETO.test(l)));
|
|
44777
|
+
const clearRequirements = bodyIsClear || maintainerDecided;
|
|
44590
44778
|
const projectActive = projectHealth.checkFailed ? true : projectHealth.isActive;
|
|
44591
44779
|
const repoAcceptsContributions = repoAcceptsContributionsFromHealth(projectHealth);
|
|
44592
44780
|
const vettingResult = {
|
|
@@ -44647,9 +44835,13 @@ var IssueVetter = class {
|
|
|
44647
44835
|
matchesCategory,
|
|
44648
44836
|
issueClosed,
|
|
44649
44837
|
passedAllChecks: vettingResult.passedAllChecks,
|
|
44650
|
-
repoAcceptsContributions
|
|
44838
|
+
repoAcceptsContributions,
|
|
44839
|
+
notTakeableReason,
|
|
44840
|
+
cautionReasons
|
|
44651
44841
|
});
|
|
44652
44842
|
vettingResult.notes = notes;
|
|
44843
|
+
if (maintainerDecided)
|
|
44844
|
+
notes.push("Requirements taken as decided: maintainer-filed or triage-accepted");
|
|
44653
44845
|
const repoQualityBonus = projectHealth.checkFailed ? 0 : calculateRepoQualityBonus(projectHealth.stargazersCount ?? 0, projectHealth.forksCount ?? 0);
|
|
44654
44846
|
if (projectHealth.checkFailed && repoQualityBonus === 0) {
|
|
44655
44847
|
vettingResult.notes.push("Repo quality bonus unavailable: could not fetch star/fork counts due to API error");
|
|
@@ -44698,7 +44890,8 @@ var IssueVetter = class {
|
|
|
44698
44890
|
reasonsToSkip,
|
|
44699
44891
|
reasonsToApprove,
|
|
44700
44892
|
viabilityScore,
|
|
44701
|
-
searchPriority
|
|
44893
|
+
searchPriority,
|
|
44894
|
+
...complexityUnlocked ? { complexityUnlocked: true } : {}
|
|
44702
44895
|
};
|
|
44703
44896
|
if (!hasInconclusiveChecks) {
|
|
44704
44897
|
cache.set(cacheKey, "", result);
|
|
@@ -44724,7 +44917,11 @@ var IssueVetter = class {
|
|
|
44724
44917
|
labels: ghIssue.labels.map((l) => typeof l === "string" ? l : l.name || ""),
|
|
44725
44918
|
commentCount: ghIssue.comments,
|
|
44726
44919
|
createdAt: ghIssue.created_at,
|
|
44727
|
-
updatedAt: ghIssue.updated_at
|
|
44920
|
+
updatedAt: ghIssue.updated_at,
|
|
44921
|
+
authorLogin: ghIssue.user?.login,
|
|
44922
|
+
authorIsBot: ghIssue.user?.type === "Bot" || /\[bot\]$/i.test(ghIssue.user?.login ?? ""),
|
|
44923
|
+
authorLooksLikeBot: looksLikeBotLogin(ghIssue.user?.login),
|
|
44924
|
+
authorIsMaintainer: MAINTAINER_ASSOCIATIONS.has(ghIssue.author_association ?? "")
|
|
44728
44925
|
};
|
|
44729
44926
|
}
|
|
44730
44927
|
/**
|
|
@@ -44913,7 +45110,7 @@ async function cachedSearchIssues(octokit, params, tracker = getSearchBudgetTrac
|
|
|
44913
45110
|
return data;
|
|
44914
45111
|
}
|
|
44915
45112
|
async function searchIssuesGraphQLFirst(octokit, params, tracker = getSearchBudgetTracker()) {
|
|
44916
|
-
const cacheKey = versionedCacheKey(`
|
|
45113
|
+
const cacheKey = versionedCacheKey(`gqlsearch2:${params.q}:${params.sort}:${params.order}:${params.per_page}`);
|
|
44917
45114
|
const cache = getHttpCache();
|
|
44918
45115
|
const cached2 = cache.getIfFresh(cacheKey, SEARCH_CACHE_TTL_MS);
|
|
44919
45116
|
if (cached2) {
|
|
@@ -45096,14 +45293,22 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
45096
45293
|
const spamCount = items.filter((i) => spamRepos.has(extractRepoFromUrl(i.repository_url) ?? "")).length;
|
|
45097
45294
|
debug(MODULE11, `[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`);
|
|
45098
45295
|
}
|
|
45296
|
+
let preVetStarDropped = 0;
|
|
45099
45297
|
const itemsToVet = filterIssues(items).filter((item) => {
|
|
45100
45298
|
const repoFullName = extractRepoFromUrl(item.repository_url);
|
|
45101
45299
|
if (!repoFullName)
|
|
45102
45300
|
return false;
|
|
45103
45301
|
if (spamRepos.has(repoFullName))
|
|
45104
45302
|
return false;
|
|
45303
|
+
if (typeof item.repositoryStars === "number" && item.repositoryStars < minStars) {
|
|
45304
|
+
preVetStarDropped++;
|
|
45305
|
+
return false;
|
|
45306
|
+
}
|
|
45105
45307
|
return excludedRepoSets.every((s) => !s.has(repoFullName));
|
|
45106
45308
|
}).slice(0, remainingNeeded * 2);
|
|
45309
|
+
if (preVetStarDropped > 0) {
|
|
45310
|
+
debug(MODULE11, `[STAR_FILTER] Dropped ${preVetStarDropped} ${phaseLabel} items below ${minStars} stars before vetting`);
|
|
45311
|
+
}
|
|
45107
45312
|
if (itemsToVet.length === 0) {
|
|
45108
45313
|
debug(MODULE11, `[${phaseLabel}] All ${items.length} items filtered before vetting`);
|
|
45109
45314
|
return { candidates: [], allVetFailed: false, rateLimitHit: false };
|
|
@@ -45122,6 +45327,216 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
45122
45327
|
return { candidates: starFiltered, allVetFailed, rateLimitHit };
|
|
45123
45328
|
}
|
|
45124
45329
|
|
|
45330
|
+
// ../core/dist/core/warm-repos.js
|
|
45331
|
+
var MODULE12 = "warm-repos";
|
|
45332
|
+
var BATCH_SIZE = 8;
|
|
45333
|
+
var ISSUES_PER_REPO = 30;
|
|
45334
|
+
var INTER_BATCH_DELAY_MS = 500;
|
|
45335
|
+
var MAX_DEAD_BATCHES = 2;
|
|
45336
|
+
var GH_NAME_PATTERN2 = /^[A-Za-z0-9_.-]+$/;
|
|
45337
|
+
var VET_PER_REPO = KNOWN_REPO_PER_REPO_CAP + 1;
|
|
45338
|
+
var MAX_VETS = 60;
|
|
45339
|
+
var SKIP_MEMORY_MS = 24 * 60 * 60 * 1e3;
|
|
45340
|
+
var PERMANENT_ERROR_TYPES = /* @__PURE__ */ new Set(["NOT_FOUND", "FORBIDDEN"]);
|
|
45341
|
+
async function queryRepos(octokit, repos, noRetry = false) {
|
|
45342
|
+
const varDefs = [];
|
|
45343
|
+
const selections = [];
|
|
45344
|
+
const variables = {};
|
|
45345
|
+
repos.forEach((fullName, i) => {
|
|
45346
|
+
const [owner, name] = fullName.split("/");
|
|
45347
|
+
varDefs.push(`$o${i}: String!, $n${i}: String!`);
|
|
45348
|
+
variables[`o${i}`] = owner;
|
|
45349
|
+
variables[`n${i}`] = name;
|
|
45350
|
+
selections.push(`r${i}: repository(owner: $o${i}, name: $n${i}) {
|
|
45351
|
+
nameWithOwner
|
|
45352
|
+
isArchived
|
|
45353
|
+
stargazerCount
|
|
45354
|
+
issues(first: ${ISSUES_PER_REPO}, states: OPEN, orderBy: { field: CREATED_AT, direction: DESC }) {
|
|
45355
|
+
nodes {
|
|
45356
|
+
url
|
|
45357
|
+
title
|
|
45358
|
+
createdAt
|
|
45359
|
+
updatedAt
|
|
45360
|
+
authorAssociation
|
|
45361
|
+
viewerDidAuthor
|
|
45362
|
+
assignees { totalCount }
|
|
45363
|
+
comments { totalCount }
|
|
45364
|
+
labels(first: 10) { nodes { name } }
|
|
45365
|
+
closedByPullRequestsReferences(first: 1, includeClosedPrs: false) { totalCount }
|
|
45366
|
+
}
|
|
45367
|
+
}
|
|
45368
|
+
}`);
|
|
45369
|
+
});
|
|
45370
|
+
const query = `query warmRepoIssues(${varDefs.join(", ")}) {
|
|
45371
|
+
${selections.join("\n")}
|
|
45372
|
+
}`;
|
|
45373
|
+
try {
|
|
45374
|
+
return {
|
|
45375
|
+
data: await octokit.graphql(query, {
|
|
45376
|
+
...variables,
|
|
45377
|
+
...noRetry ? { request: { retries: 0 } } : {}
|
|
45378
|
+
}),
|
|
45379
|
+
permanent: /* @__PURE__ */ new Set()
|
|
45380
|
+
};
|
|
45381
|
+
} catch (err) {
|
|
45382
|
+
const partial2 = err.data;
|
|
45383
|
+
if (!partial2 || isRateLimitError(err))
|
|
45384
|
+
throw err;
|
|
45385
|
+
const gqlErrors = err.errors ?? [];
|
|
45386
|
+
return {
|
|
45387
|
+
data: partial2,
|
|
45388
|
+
permanent: new Set(gqlErrors.filter((e) => PERMANENT_ERROR_TYPES.has(e.type ?? "")).map((e) => String(e.path?.[0])))
|
|
45389
|
+
};
|
|
45390
|
+
}
|
|
45391
|
+
}
|
|
45392
|
+
function takeableIssues(listedAs, node2, minStars) {
|
|
45393
|
+
const nodes = node2?.issues?.nodes;
|
|
45394
|
+
if (!node2 || !nodes)
|
|
45395
|
+
return null;
|
|
45396
|
+
if (node2.isArchived)
|
|
45397
|
+
return [];
|
|
45398
|
+
if ((node2.stargazerCount ?? 0) < minStars)
|
|
45399
|
+
return [];
|
|
45400
|
+
const repo = node2.nameWithOwner;
|
|
45401
|
+
return nodes.flatMap((n) => {
|
|
45402
|
+
if (!n)
|
|
45403
|
+
return [];
|
|
45404
|
+
if (n.assignees.totalCount > 0)
|
|
45405
|
+
return [];
|
|
45406
|
+
if ((n.closedByPullRequestsReferences?.totalCount ?? 0) > 0)
|
|
45407
|
+
return [];
|
|
45408
|
+
return [
|
|
45409
|
+
{
|
|
45410
|
+
html_url: n.url,
|
|
45411
|
+
repository_url: `https://api.github.com/repos/${repo}`,
|
|
45412
|
+
updated_at: n.updatedAt,
|
|
45413
|
+
title: n.title,
|
|
45414
|
+
labels: (n.labels?.nodes ?? []).map((l) => ({ name: l.name })),
|
|
45415
|
+
repo,
|
|
45416
|
+
listedAs,
|
|
45417
|
+
createdAt: n.createdAt,
|
|
45418
|
+
maintainerFiled: MAINTAINER_ASSOCIATIONS.has(n.authorAssociation) && !n.viewerDidAuthor,
|
|
45419
|
+
commentCount: n.comments.totalCount
|
|
45420
|
+
}
|
|
45421
|
+
];
|
|
45422
|
+
});
|
|
45423
|
+
}
|
|
45424
|
+
async function listWarmRepoIssues(octokit, repos, minStars = 0) {
|
|
45425
|
+
const valid = repos.filter((r) => {
|
|
45426
|
+
const parts = r.split("/");
|
|
45427
|
+
return parts.length === 2 && parts.every((p) => GH_NAME_PATTERN2.test(p));
|
|
45428
|
+
});
|
|
45429
|
+
const byUrl = /* @__PURE__ */ new Map();
|
|
45430
|
+
let failedRepos = repos.length - valid.length;
|
|
45431
|
+
let rateLimitHit = false;
|
|
45432
|
+
const read = async (batch, noRetry = false) => {
|
|
45433
|
+
try {
|
|
45434
|
+
const { data, permanent } = await queryRepos(octokit, batch, noRetry);
|
|
45435
|
+
const retry2 = [];
|
|
45436
|
+
batch.forEach((repo, j) => {
|
|
45437
|
+
const found = takeableIssues(repo, data[`r${j}`], minStars);
|
|
45438
|
+
if (found)
|
|
45439
|
+
for (const issue2 of found)
|
|
45440
|
+
byUrl.set(issue2.html_url, issue2);
|
|
45441
|
+
else if (batch.length > 1 && !permanent.has(`r${j}`))
|
|
45442
|
+
retry2.push(repo);
|
|
45443
|
+
else
|
|
45444
|
+
failedRepos++;
|
|
45445
|
+
});
|
|
45446
|
+
return retry2;
|
|
45447
|
+
} catch (err) {
|
|
45448
|
+
if (getHttpStatusCode(err) === 401)
|
|
45449
|
+
throw err;
|
|
45450
|
+
if (isRateLimitError(err)) {
|
|
45451
|
+
warn(MODULE12, `Rate limit hit listing warm repos: ${errorMessage(err)}`);
|
|
45452
|
+
rateLimitHit = true;
|
|
45453
|
+
return [];
|
|
45454
|
+
}
|
|
45455
|
+
warn(MODULE12, `Failed to list issues for ${batch.join(", ")}: ${errorMessage(err)}`);
|
|
45456
|
+
return null;
|
|
45457
|
+
}
|
|
45458
|
+
};
|
|
45459
|
+
let deadBatches = 0;
|
|
45460
|
+
for (let i = 0; i < valid.length && !rateLimitHit; i += BATCH_SIZE) {
|
|
45461
|
+
if (i > 0)
|
|
45462
|
+
await sleep(INTER_BATCH_DELAY_MS);
|
|
45463
|
+
const batch = valid.slice(i, i + BATCH_SIZE);
|
|
45464
|
+
const first = await read(batch);
|
|
45465
|
+
let alive = first !== null;
|
|
45466
|
+
if (first === null && batch.length === 1)
|
|
45467
|
+
failedRepos++;
|
|
45468
|
+
for (const repo of first ?? (batch.length > 1 ? batch : [])) {
|
|
45469
|
+
if (rateLimitHit)
|
|
45470
|
+
break;
|
|
45471
|
+
if (await read([repo], first === null) === null)
|
|
45472
|
+
failedRepos++;
|
|
45473
|
+
else
|
|
45474
|
+
alive = true;
|
|
45475
|
+
}
|
|
45476
|
+
deadBatches = alive ? 0 : deadBatches + 1;
|
|
45477
|
+
if (deadBatches >= MAX_DEAD_BATCHES) {
|
|
45478
|
+
warn(MODULE12, `${deadBatches} batches in a row returned nothing; stopping the warm-repo walk`);
|
|
45479
|
+
failedRepos += Math.max(0, valid.length - (i + batch.length));
|
|
45480
|
+
break;
|
|
45481
|
+
}
|
|
45482
|
+
}
|
|
45483
|
+
return { items: [...byUrl.values()], failedRepos, rateLimitHit };
|
|
45484
|
+
}
|
|
45485
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
45486
|
+
function rankWarmIssues(items, repoScore, now = /* @__PURE__ */ new Date()) {
|
|
45487
|
+
const score = (issue2) => {
|
|
45488
|
+
const ageDays = (now.getTime() - Date.parse(issue2.createdAt)) / DAY_MS;
|
|
45489
|
+
const freshness = ageDays <= 2 ? 3 : ageDays <= 14 ? 2 : ageDays <= 30 ? 1 : 0;
|
|
45490
|
+
return freshness + (issue2.maintainerFiled ? 3 : 0) + (issue2.commentCount === 0 ? 2 : 0) + (repoScore(issue2.repo) ?? repoScore(issue2.listedAs) ?? 5) / 2;
|
|
45491
|
+
};
|
|
45492
|
+
return items.map((issue2) => ({ issue: issue2, score: score(issue2) })).sort((a, b) => b.score - a.score || Date.parse(b.issue.createdAt) - Date.parse(a.issue.createdAt)).map((entry) => entry.issue);
|
|
45493
|
+
}
|
|
45494
|
+
var skipMemoryKey = (url2) => versionedCacheKey(`warm-skip:${url2}`);
|
|
45495
|
+
async function fetchWarmRepoCandidates(octokit, vetter, repos, maxResults, filterFn, repoScore, minStars = 0) {
|
|
45496
|
+
const cache = getHttpCache();
|
|
45497
|
+
const listed = await listWarmRepoIssues(octokit, repos, minStars);
|
|
45498
|
+
const passing = (items) => new Set(filterFn(items).map((i) => i.html_url));
|
|
45499
|
+
const kept = passing(listed.items);
|
|
45500
|
+
const keptAsListed = passing(listed.items.map((i) => ({
|
|
45501
|
+
...i,
|
|
45502
|
+
repository_url: `https://api.github.com/repos/${i.listedAs}`
|
|
45503
|
+
})));
|
|
45504
|
+
const ranked = rankWarmIssues(listed.items.filter((i) => kept.has(i.html_url) && keptAsListed.has(i.html_url) && cache.getEntryIfFresh(skipMemoryKey(i.html_url), SKIP_MEMORY_MS) === null), repoScore);
|
|
45505
|
+
const toVet = applyPerRepoCap(ranked.map((i) => ({ url: i.html_url, issue: { repo: i.repo } })), VET_PER_REPO).slice(0, Math.min(maxResults * 2, MAX_VETS)).map((i) => i.url);
|
|
45506
|
+
let candidates = [];
|
|
45507
|
+
let rateLimitHit = listed.rateLimitHit;
|
|
45508
|
+
let vetError = null;
|
|
45509
|
+
if (toVet.length > 0 && !listed.rateLimitHit) {
|
|
45510
|
+
try {
|
|
45511
|
+
const vetted = await vetter.vetIssuesParallel(toVet, toVet.length, "merged_pr");
|
|
45512
|
+
rateLimitHit ||= vetted.rateLimitHit;
|
|
45513
|
+
for (const c of vetted.candidates) {
|
|
45514
|
+
if (c.recommendation === "skip") {
|
|
45515
|
+
cache.set(skipMemoryKey(c.issue.url), "", true);
|
|
45516
|
+
}
|
|
45517
|
+
}
|
|
45518
|
+
const rank = new Map(toVet.map((url2, i) => [url2, i]));
|
|
45519
|
+
const byRank = [...vetted.candidates].sort((a, b) => (rank.get(a.issue.url) ?? 0) - (rank.get(b.issue.url) ?? 0));
|
|
45520
|
+
candidates = applyPerRepoCap([
|
|
45521
|
+
...byRank.filter((c) => c.recommendation !== "skip"),
|
|
45522
|
+
...byRank.filter((c) => c.recommendation === "skip")
|
|
45523
|
+
], KNOWN_REPO_PER_REPO_CAP).slice(0, maxResults);
|
|
45524
|
+
} catch (err) {
|
|
45525
|
+
if (getHttpStatusCode(err) === 401)
|
|
45526
|
+
throw err;
|
|
45527
|
+
rateLimitHit ||= isRateLimitError(err);
|
|
45528
|
+
vetError = errorMessage(err);
|
|
45529
|
+
warn(MODULE12, `Vetting warm-repo issues failed: ${vetError}`);
|
|
45530
|
+
}
|
|
45531
|
+
}
|
|
45532
|
+
return {
|
|
45533
|
+
candidates,
|
|
45534
|
+
allReposFailed: repos.length > 0 && listed.failedRepos >= repos.length,
|
|
45535
|
+
rateLimitHit,
|
|
45536
|
+
vetError
|
|
45537
|
+
};
|
|
45538
|
+
}
|
|
45539
|
+
|
|
45125
45540
|
// ../core/dist/core/personalization.js
|
|
45126
45541
|
var REPO_BOOST = 20;
|
|
45127
45542
|
var LANGUAGE_BOOST = 10;
|
|
@@ -45210,13 +45625,12 @@ function applyDiversityRatio(candidates, maxResults, diversityRatio) {
|
|
|
45210
45625
|
}
|
|
45211
45626
|
|
|
45212
45627
|
// ../core/dist/core/issue-discovery.js
|
|
45213
|
-
var
|
|
45628
|
+
var MODULE13 = "issue-discovery";
|
|
45214
45629
|
var LOW_BUDGET_THRESHOLD = 20;
|
|
45215
45630
|
var CRITICAL_BUDGET_THRESHOLD = 10;
|
|
45216
|
-
var PHASE0_PER_PAGE = 30;
|
|
45217
45631
|
var CONTRIBUTED_REPO_MAX_AGE_DAYS = 365;
|
|
45218
|
-
var PHASE0_MAX_SHARE = 0.
|
|
45219
|
-
var
|
|
45632
|
+
var PHASE0_MAX_SHARE = 0.8;
|
|
45633
|
+
var PHASE0_MAX_REPOS = 200;
|
|
45220
45634
|
var STARRED_REPOS_PER_RUN = 10;
|
|
45221
45635
|
var MAINTAINED_REPOS_PER_RUN = 15;
|
|
45222
45636
|
function rotatingWindow(items, size, offset) {
|
|
@@ -45253,34 +45667,35 @@ function buildIssueFilter(config2) {
|
|
|
45253
45667
|
return false;
|
|
45254
45668
|
if (!config2.includeDocIssues && isDocOnlyIssue(item))
|
|
45255
45669
|
return false;
|
|
45256
|
-
|
|
45670
|
+
const labelsOnly = item.maintainerFiled === true && config2.complexRepos.has(repoLower);
|
|
45671
|
+
if (isNonActionableIssue(item, { labelsOnly }))
|
|
45257
45672
|
return false;
|
|
45258
45673
|
return true;
|
|
45259
45674
|
});
|
|
45260
45675
|
};
|
|
45261
45676
|
}
|
|
45262
|
-
async function runPhase0(octokit, vetter, repos, maxResults, filterIssues) {
|
|
45263
|
-
info(
|
|
45264
|
-
const { candidates, allReposFailed, rateLimitHit } = await
|
|
45265
|
-
info(
|
|
45677
|
+
async function runPhase0(octokit, vetter, repos, maxResults, filterIssues, repoScore, minStars) {
|
|
45678
|
+
info(MODULE13, `Phase 0: Listing open issues in ${repos.length} merged-PR repos (no label filter)...`);
|
|
45679
|
+
const { candidates, allReposFailed, rateLimitHit, vetError } = await fetchWarmRepoCandidates(octokit, vetter, repos, maxResults, filterIssues, repoScore, minStars);
|
|
45680
|
+
info(MODULE13, `Found ${candidates.length} candidates from merged-PR repos`);
|
|
45266
45681
|
return {
|
|
45267
45682
|
candidates,
|
|
45268
|
-
error: allReposFailed ? "All merged-PR repo fetches failed" :
|
|
45683
|
+
error: allReposFailed ? "All merged-PR repo fetches failed" : vetError,
|
|
45269
45684
|
rateLimitHit
|
|
45270
45685
|
};
|
|
45271
45686
|
}
|
|
45272
45687
|
var MAX_QUERY_ORGS = 8;
|
|
45273
45688
|
async function runPhaseOrgs(octokit, vetter, orgs, languages, isAnyLanguage, labels, maxResults, minStars, phase0RepoSet, starredRepoSet, filterIssues, tracker) {
|
|
45274
|
-
info(
|
|
45689
|
+
info(MODULE13, `Orgs phase: searching ${orgs.length} preferred org(s)...`);
|
|
45275
45690
|
const queryOrgs = orgs.slice(0, MAX_QUERY_ORGS);
|
|
45276
45691
|
if (orgs.length > MAX_QUERY_ORGS) {
|
|
45277
|
-
warn(
|
|
45692
|
+
warn(MODULE13, `Orgs phase: capping query at ${MAX_QUERY_ORGS} org(s); dropping ${orgs.slice(MAX_QUERY_ORGS).join(", ")}`);
|
|
45278
45693
|
}
|
|
45279
45694
|
const orgQuery = queryOrgs.map((o) => `org:${o}`).join(" ");
|
|
45280
45695
|
try {
|
|
45281
45696
|
const allItems = await searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, labels, (langQ) => `is:issue is:open ${langQ} no:assignee ${orgQuery}`.replace(/ +/g, " ").trim(), maxResults * 3, tracker, 0, queryOrgs.length);
|
|
45282
45697
|
const { candidates, allVetFailed, rateLimitHit } = await filterVetAndScore(vetter, allItems, filterIssues, [phase0RepoSet, starredRepoSet], maxResults, minStars, "Orgs phase");
|
|
45283
|
-
info(
|
|
45698
|
+
info(MODULE13, `Found ${candidates.length} candidates from preferred orgs`);
|
|
45284
45699
|
return {
|
|
45285
45700
|
candidates,
|
|
45286
45701
|
error: allVetFailed ? "all vetting failed" : null,
|
|
@@ -45290,7 +45705,7 @@ async function runPhaseOrgs(octokit, vetter, orgs, languages, isAnyLanguage, lab
|
|
|
45290
45705
|
if (getHttpStatusCode(error62) === 401)
|
|
45291
45706
|
throw error62;
|
|
45292
45707
|
const errMsg = errorMessage(error62);
|
|
45293
|
-
warn(
|
|
45708
|
+
warn(MODULE13, `Error in preferred-orgs search: ${errMsg}`);
|
|
45294
45709
|
return {
|
|
45295
45710
|
candidates: [],
|
|
45296
45711
|
error: errMsg,
|
|
@@ -45299,11 +45714,11 @@ async function runPhaseOrgs(octokit, vetter, orgs, languages, isAnyLanguage, lab
|
|
|
45299
45714
|
}
|
|
45300
45715
|
}
|
|
45301
45716
|
async function runPhase1(octokit, vetter, repos, labels, maxResults, filterIssues) {
|
|
45302
|
-
info(
|
|
45717
|
+
info(MODULE13, `Phase 1: Searching issues in ${repos.length} starred repos...`);
|
|
45303
45718
|
const phase1Labels = labels.slice(0, 3);
|
|
45304
45719
|
const reposToSearch = repos.slice(0, STARRED_REPOS_PER_RUN);
|
|
45305
45720
|
const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, reposToSearch, phase1Labels, maxResults, "starred", filterIssues);
|
|
45306
|
-
info(
|
|
45721
|
+
info(MODULE13, `Found ${candidates.length} candidates from starred repos`);
|
|
45307
45722
|
return {
|
|
45308
45723
|
candidates,
|
|
45309
45724
|
error: allReposFailed ? "All starred repo fetches failed" : null,
|
|
@@ -45311,14 +45726,14 @@ async function runPhase1(octokit, vetter, repos, labels, maxResults, filterIssue
|
|
|
45311
45726
|
};
|
|
45312
45727
|
}
|
|
45313
45728
|
async function runPhase2(octokit, vetter, scopes, labels, configLabels, languages, isAnyLanguage, maxResults, minStars, phase0RepoSet, starredRepoSet, existingCandidates, filterIssues, tracker, languageRotationOffset = 0) {
|
|
45314
|
-
info(
|
|
45729
|
+
info(MODULE13, "Phase 2: General issue search...");
|
|
45315
45730
|
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
45316
45731
|
const tierLabelGroups = [];
|
|
45317
45732
|
if (scopes && scopes.length > 1) {
|
|
45318
45733
|
for (const scope of scopes) {
|
|
45319
45734
|
const scopeLabels = SCOPE_LABELS[scope] ?? [];
|
|
45320
45735
|
if (scopeLabels.length === 0) {
|
|
45321
|
-
warn(
|
|
45736
|
+
warn(MODULE13, `Scope "${scope}" has no labels, skipping tier`);
|
|
45322
45737
|
continue;
|
|
45323
45738
|
}
|
|
45324
45739
|
tierLabelGroups.push({ tier: scope, tierLabels: scopeLabels });
|
|
@@ -45338,7 +45753,7 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
45338
45753
|
for (const { tier, tierLabels } of tierLabelGroups) {
|
|
45339
45754
|
try {
|
|
45340
45755
|
const allItems = await searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, tierLabels, (langQ) => `is:issue is:open ${langQ} no:assignee`.replace(/ +/g, " ").trim(), budgetPerTier * 3, tracker, languageRotationOffset);
|
|
45341
|
-
info(
|
|
45756
|
+
info(MODULE13, `Phase 2 [${tier}]: processing ${allItems.length} items...`);
|
|
45342
45757
|
const { candidates: tierCandidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, allItems, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], budgetPerTier, minStars, `Phase 2 [${tier}]`);
|
|
45343
45758
|
tierResults.push(tierCandidates);
|
|
45344
45759
|
for (const c of tierCandidates)
|
|
@@ -45349,7 +45764,7 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
45349
45764
|
if (vetRateLimitHit) {
|
|
45350
45765
|
rateLimitHit = true;
|
|
45351
45766
|
}
|
|
45352
|
-
info(
|
|
45767
|
+
info(MODULE13, `Found ${tierCandidates.length} candidates from ${tier} tier`);
|
|
45353
45768
|
} catch (err) {
|
|
45354
45769
|
if (getHttpStatusCode(err) === 401)
|
|
45355
45770
|
throw err;
|
|
@@ -45358,13 +45773,13 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
45358
45773
|
if (isRateLimitError(err)) {
|
|
45359
45774
|
rateLimitHit = true;
|
|
45360
45775
|
}
|
|
45361
|
-
warn(
|
|
45776
|
+
warn(MODULE13, `Error in ${tier} tier search: ${errMsg}`);
|
|
45362
45777
|
tierResults.push([]);
|
|
45363
45778
|
}
|
|
45364
45779
|
}
|
|
45365
45780
|
const interleaved = interleaveArrays(tierResults);
|
|
45366
45781
|
if (interleaved.length === 0 && error62) {
|
|
45367
|
-
warn(
|
|
45782
|
+
warn(MODULE13, `All ${tierLabelGroups.length} scope tiers failed in Phase 2: ${error62}`);
|
|
45368
45783
|
}
|
|
45369
45784
|
return {
|
|
45370
45785
|
candidates: interleaved.slice(0, maxResults),
|
|
@@ -45373,17 +45788,17 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
45373
45788
|
};
|
|
45374
45789
|
}
|
|
45375
45790
|
async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories, maxResults, phase0RepoSet, starredRepoSet, starredRepos, existingCandidates, filterIssues, tracker, rotationOffset = 0) {
|
|
45376
|
-
info(
|
|
45791
|
+
info(MODULE13, "Phase 3: Searching actively maintained repos...");
|
|
45377
45792
|
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
45378
45793
|
const eligibleStarred = rotatingWindow(starredRepos, MAINTAINED_REPOS_PER_RUN, rotationOffset).filter((r) => !phase0RepoSet.has(r) && !seenRepos.has(r));
|
|
45379
45794
|
if (eligibleStarred.length > 0) {
|
|
45380
|
-
info(
|
|
45795
|
+
info(MODULE13, `Phase 3: Checking ${eligibleStarred.length} starred repos via REST API...`);
|
|
45381
45796
|
const restItems = await fetchIssuesFromMaintainedRepos(octokit, eligibleStarred, minStars, maxResults);
|
|
45382
45797
|
if (restItems.length > 0) {
|
|
45383
45798
|
try {
|
|
45384
45799
|
const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, restItems, filterIssues, [phase0RepoSet, seenRepos], maxResults, minStars, "Phase 3 (REST)");
|
|
45385
45800
|
if (candidates.length > 0) {
|
|
45386
|
-
info(
|
|
45801
|
+
info(MODULE13, `Found ${candidates.length} candidates from maintained-repo REST search`);
|
|
45387
45802
|
return {
|
|
45388
45803
|
candidates,
|
|
45389
45804
|
error: allVetFailed ? "all vetting failed" : null,
|
|
@@ -45393,11 +45808,11 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
45393
45808
|
} catch (error62) {
|
|
45394
45809
|
if (getHttpStatusCode(error62) === 401)
|
|
45395
45810
|
throw error62;
|
|
45396
|
-
warn(
|
|
45811
|
+
warn(MODULE13, `Phase 3 REST vetting failed, falling back to Search API:`, errorMessage(error62));
|
|
45397
45812
|
}
|
|
45398
45813
|
}
|
|
45399
45814
|
}
|
|
45400
|
-
info(
|
|
45815
|
+
info(MODULE13, "Phase 3: Falling back to Search API...");
|
|
45401
45816
|
const thirtyDaysAgo = /* @__PURE__ */ new Date();
|
|
45402
45817
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
45403
45818
|
const pushedSince = thirtyDaysAgo.toISOString().split("T")[0];
|
|
@@ -45411,9 +45826,9 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
45411
45826
|
order: "desc",
|
|
45412
45827
|
per_page: maxResults * 3
|
|
45413
45828
|
}, tracker);
|
|
45414
|
-
info(
|
|
45829
|
+
info(MODULE13, `Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`);
|
|
45415
45830
|
const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, data.items, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], maxResults, minStars, "Phase 3");
|
|
45416
|
-
info(
|
|
45831
|
+
info(MODULE13, `Found ${candidates.length} candidates from maintained-repo search`);
|
|
45417
45832
|
return {
|
|
45418
45833
|
candidates,
|
|
45419
45834
|
error: allVetFailed ? "all vetting failed" : null,
|
|
@@ -45423,7 +45838,7 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
45423
45838
|
if (getHttpStatusCode(error62) === 401)
|
|
45424
45839
|
throw error62;
|
|
45425
45840
|
const errMsg = errorMessage(error62);
|
|
45426
|
-
warn(
|
|
45841
|
+
warn(MODULE13, `Error in maintained-repo search: ${errMsg}`);
|
|
45427
45842
|
return {
|
|
45428
45843
|
candidates: [],
|
|
45429
45844
|
error: errMsg,
|
|
@@ -45507,7 +45922,7 @@ var IssueDiscovery = class {
|
|
|
45507
45922
|
let rateLimitHitDuringSearch = false;
|
|
45508
45923
|
const applyInterPhaseDelay = async () => {
|
|
45509
45924
|
if (interPhaseDelay > 0) {
|
|
45510
|
-
info(
|
|
45925
|
+
info(MODULE13, `Waiting ${(interPhaseDelay / 1e3).toFixed(0)}s between phases for rate limit management...`);
|
|
45511
45926
|
await sleep(interPhaseDelay);
|
|
45512
45927
|
}
|
|
45513
45928
|
};
|
|
@@ -45530,23 +45945,23 @@ var IssueDiscovery = class {
|
|
|
45530
45945
|
if (rateLimit.remaining < 5) {
|
|
45531
45946
|
const resetTime = new Date(rateLimit.resetAt).toLocaleTimeString("en-US", { hour12: false });
|
|
45532
45947
|
this.rateLimitWarning = `GitHub search API quota low (${rateLimit.remaining}/${rateLimit.limit} remaining, resets at ${resetTime}). Search may be slow.`;
|
|
45533
|
-
warn(
|
|
45948
|
+
warn(MODULE13, this.rateLimitWarning);
|
|
45534
45949
|
}
|
|
45535
45950
|
if (searchBudget < CRITICAL_BUDGET_THRESHOLD) {
|
|
45536
|
-
info(
|
|
45951
|
+
info(MODULE13, `Search budget critical (${searchBudget} remaining) \u2014 skipping the starred phase; broad/maintained still run on GraphQL (points bucket)`);
|
|
45537
45952
|
} else if (searchBudget < LOW_BUDGET_THRESHOLD) {
|
|
45538
|
-
info(
|
|
45953
|
+
info(MODULE13, `Search budget low (${searchBudget} remaining) \u2014 broad/maintained run on GraphQL (points bucket), unaffected by REST search quota`);
|
|
45539
45954
|
}
|
|
45540
45955
|
} catch (error62) {
|
|
45541
45956
|
if (getHttpStatusCode(error62) === 401)
|
|
45542
45957
|
throw error62;
|
|
45543
45958
|
preflightFailed = true;
|
|
45544
45959
|
tracker.init(CRITICAL_BUDGET_THRESHOLD, new Date(Date.now() + 6e4).toISOString());
|
|
45545
|
-
warn(
|
|
45960
|
+
warn(MODULE13, "Could not check rate limit \u2014 using conservative budget, skipping the starred phase:", errorMessage(error62));
|
|
45546
45961
|
}
|
|
45547
45962
|
if (searchBudget <= 0) {
|
|
45548
45963
|
this.rateLimitWarning = "GitHub search API quota exhausted \u2014 skipping the starred phase; other phases don't use it.";
|
|
45549
|
-
warn(
|
|
45964
|
+
warn(MODULE13, this.rateLimitWarning);
|
|
45550
45965
|
}
|
|
45551
45966
|
const mergedPRRepos = this.stateReader.getReposWithMergedPRs();
|
|
45552
45967
|
const openPRRepos = this.stateReader.getReposWithOpenPRs();
|
|
@@ -45557,7 +45972,7 @@ var IssueDiscovery = class {
|
|
|
45557
45972
|
const langQuery = isAnyLanguage ? "" : languages.map((l) => `language:${l}`).join(" ");
|
|
45558
45973
|
const aiBlocklisted = new Set(config2.aiPolicyBlocklist.map((r) => r.toLowerCase()));
|
|
45559
45974
|
if (aiBlocklisted.size > 0) {
|
|
45560
|
-
debug(
|
|
45975
|
+
debug(MODULE13, `[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`);
|
|
45561
45976
|
}
|
|
45562
45977
|
const baseFilterConfig = {
|
|
45563
45978
|
excludedRepos: new Set(config2.excludeRepos.map((r) => r.toLowerCase())),
|
|
@@ -45566,7 +45981,8 @@ var IssueDiscovery = class {
|
|
|
45566
45981
|
lowScoringRepos,
|
|
45567
45982
|
skippedUrls: options.skippedUrls ?? /* @__PURE__ */ new Set(),
|
|
45568
45983
|
now: /* @__PURE__ */ new Date(),
|
|
45569
|
-
includeDocIssues: config2.includeDocIssues ?? true
|
|
45984
|
+
includeDocIssues: config2.includeDocIssues ?? true,
|
|
45985
|
+
complexRepos: new Set(config2.complexIssues === "warm" ? mergedPRRepos.map((r) => r.toLowerCase()) : [])
|
|
45570
45986
|
};
|
|
45571
45987
|
const filterIssues = buildIssueFilter({
|
|
45572
45988
|
...baseFilterConfig,
|
|
@@ -45577,10 +45993,10 @@ var IssueDiscovery = class {
|
|
|
45577
45993
|
maxAgeDays: CONTRIBUTED_REPO_MAX_AGE_DAYS
|
|
45578
45994
|
});
|
|
45579
45995
|
const repoRotation = options.repoRotationOffsets ?? {};
|
|
45580
|
-
const phase0Repos =
|
|
45996
|
+
const phase0Repos = enabledStrategies.has("merged") ? [.../* @__PURE__ */ new Set([...mergedPRRepos, ...openPRRepos])].slice(0, PHASE0_MAX_REPOS) : [];
|
|
45581
45997
|
const phase0RepoSet = new Set(phase0Repos);
|
|
45582
45998
|
const preferredOrgs = config2.preferredOrgs ?? [];
|
|
45583
|
-
const starredToSearch = rotatingWindow(starredRepos
|
|
45999
|
+
const starredToSearch = rotatingWindow(starredRepos.filter((r) => !phase0RepoSet.has(r)), STARRED_REPOS_PER_RUN, repoRotation.starred ?? 0);
|
|
45584
46000
|
const canRun = {
|
|
45585
46001
|
all: false,
|
|
45586
46002
|
merged: phase0Repos.length > 0,
|
|
@@ -45592,7 +46008,7 @@ var IssueDiscovery = class {
|
|
|
45592
46008
|
const broadDelay = options.broadPhaseDelayMs ?? config2.broadPhaseDelayMs ?? 0;
|
|
45593
46009
|
const runStrategy = {
|
|
45594
46010
|
merged: async (wanted) => {
|
|
45595
|
-
recordPhaseResult("0", await runPhase0(this.octokit, this.vetter, phase0Repos, wanted, filterIssuesPhase0));
|
|
46011
|
+
recordPhaseResult("0", await runPhase0(this.octokit, this.vetter, phase0Repos, wanted, filterIssuesPhase0, (repo) => this.stateReader.getRepoScore(repo), minStars));
|
|
45596
46012
|
strategiesUsed.push("merged");
|
|
45597
46013
|
},
|
|
45598
46014
|
orgs: async (wanted) => {
|
|
@@ -45627,16 +46043,16 @@ var IssueDiscovery = class {
|
|
|
45627
46043
|
}
|
|
45628
46044
|
if (strategiesUsed.length > 0)
|
|
45629
46045
|
await applyInterPhaseDelay();
|
|
45630
|
-
info(
|
|
46046
|
+
info(MODULE13, `Round-robin: running the ${strategy} strategy`);
|
|
45631
46047
|
await runStrategy[strategy](maxResults - viableCandidateCount());
|
|
45632
46048
|
if (viableCandidateCount() > 0)
|
|
45633
46049
|
break;
|
|
45634
46050
|
}
|
|
45635
46051
|
} else {
|
|
45636
46052
|
const orgsPhaseEnabled = canRun.orgs && enabledStrategies.has("orgs");
|
|
45637
|
-
const otherStrategiesCanRun = orgsPhaseEnabled ||
|
|
46053
|
+
const otherStrategiesCanRun = orgsPhaseEnabled || canRun.starred && enabledStrategies.has("starred") || enabledStrategies.has("broad") || enabledStrategies.has("maintained");
|
|
45638
46054
|
if (canRun.merged && enabledStrategies.has("merged")) {
|
|
45639
|
-
const phase0Cap = otherStrategiesCanRun ? Math.max(1, Math.ceil(maxResults * PHASE0_MAX_SHARE)) : maxResults;
|
|
46055
|
+
const phase0Cap = otherStrategiesCanRun ? Math.max(1, Math.min(Math.ceil(maxResults * PHASE0_MAX_SHARE), maxResults - 1)) : maxResults;
|
|
45640
46056
|
await runStrategy.merged(Math.min(maxResults - allCandidates.length, phase0Cap));
|
|
45641
46057
|
}
|
|
45642
46058
|
if (allCandidates.length < maxResults && orgsPhaseEnabled) {
|
|
@@ -45653,26 +46069,26 @@ var IssueDiscovery = class {
|
|
|
45653
46069
|
const skipThreshold = configuredSkipThreshold > 0 ? Math.min(configuredSkipThreshold, maxResults - 1) : 0;
|
|
45654
46070
|
const viableBeforeBroad = viableCandidateCount();
|
|
45655
46071
|
if (viableBeforeBroad >= maxResults && enabledStrategies.has("broad")) {
|
|
45656
|
-
info(
|
|
46072
|
+
info(MODULE13, `Skipping broad search: ${viableBeforeBroad} viable candidate(s) already meet maxResults (${maxResults})`);
|
|
45657
46073
|
} else if (enabledStrategies.has("broad")) {
|
|
45658
46074
|
const preferredOrgSet = new Set(preferredOrgs.map((o) => o.toLowerCase()));
|
|
45659
46075
|
const newRepoCandidateCount = allCandidates.filter((c) => c.recommendation !== "skip" && !phase0RepoSet.has(c.issue.repo) && !starredRepoSet.has(c.issue.repo) && !preferredOrgSet.has(c.issue.repo.split("/")[0]?.toLowerCase() ?? "")).length;
|
|
45660
46076
|
if (skipThreshold > 0 && newRepoCandidateCount >= skipThreshold) {
|
|
45661
|
-
info(
|
|
46077
|
+
info(MODULE13, `Skipping broad search: already found ${newRepoCandidateCount} viable candidate(s) from new repos (threshold: ${skipThreshold})`);
|
|
45662
46078
|
} else {
|
|
45663
46079
|
await applyInterPhaseDelay();
|
|
45664
46080
|
if (allCandidates.length > 0 && broadDelay > 0) {
|
|
45665
|
-
info(
|
|
46081
|
+
info(MODULE13, `Waiting ${(broadDelay / 1e3).toFixed(0)}s for rate limit cooldown before broad search...`);
|
|
45666
46082
|
await sleep(broadDelay);
|
|
45667
46083
|
} else if (allCandidates.length === 0) {
|
|
45668
|
-
info(
|
|
46084
|
+
info(MODULE13, `Skipping broad phase delay: no results from previous phases, proceeding immediately`);
|
|
45669
46085
|
}
|
|
45670
46086
|
await runStrategy.broad(maxResults - viableBeforeBroad);
|
|
45671
46087
|
}
|
|
45672
46088
|
}
|
|
45673
46089
|
const viableBeforeMaintained = viableCandidateCount();
|
|
45674
46090
|
if (viableBeforeMaintained >= maxResults && enabledStrategies.has("maintained")) {
|
|
45675
|
-
info(
|
|
46091
|
+
info(MODULE13, `Skipping maintained search: ${viableBeforeMaintained} viable candidate(s) already meet maxResults (${maxResults})`);
|
|
45676
46092
|
} else if (enabledStrategies.has("maintained")) {
|
|
45677
46093
|
await applyInterPhaseDelay();
|
|
45678
46094
|
await runStrategy.maintained(maxResults - viableBeforeMaintained);
|
|
@@ -45728,7 +46144,7 @@ var IssueDiscovery = class {
|
|
|
45728
46144
|
];
|
|
45729
46145
|
const capped = applyPerRepoCap(viableFirst, 2);
|
|
45730
46146
|
const finalPicks = applyDiversityRatio(capped, maxResults, options.diversityRatio ?? 0);
|
|
45731
|
-
info(
|
|
46147
|
+
info(MODULE13, `Search complete: ${tracker.getTotalCalls()} REST Search API call(s) + ${getGraphQLSearchQueryCount()} GraphQL broad-search query/queries used, ${finalPicks.length} candidates returned`);
|
|
45732
46148
|
return { candidates: finalPicks, strategiesUsed };
|
|
45733
46149
|
}
|
|
45734
46150
|
/**
|
|
@@ -45850,7 +46266,7 @@ async function fetchRoadmapIssueRefsUncached(octokit, owner, repo, cacheKey) {
|
|
|
45850
46266
|
}
|
|
45851
46267
|
|
|
45852
46268
|
// ../core/dist/core/feature-discovery.js
|
|
45853
|
-
var
|
|
46269
|
+
var MODULE14 = "feature-discovery";
|
|
45854
46270
|
var INTER_REPO_DELAY_MS = 2e3;
|
|
45855
46271
|
var MIN_VIABILITY_SCORE = 40;
|
|
45856
46272
|
var ANCHOR_THRESHOLD = 3;
|
|
@@ -45954,7 +46370,7 @@ function isFeatureIssue(item) {
|
|
|
45954
46370
|
return false;
|
|
45955
46371
|
return labels.some((l) => FEATURE_LABELS.includes(l));
|
|
45956
46372
|
}
|
|
45957
|
-
async function vetAndClassify(item, vetter, roadmapRefs) {
|
|
46373
|
+
async function vetAndClassify(item, vetter, minStars, roadmapRefs) {
|
|
45958
46374
|
const labels = extractLabels(item);
|
|
45959
46375
|
const hasMilestone = !!item.milestone;
|
|
45960
46376
|
const reactions = item.reactions?.total_count ?? 0;
|
|
@@ -45975,7 +46391,10 @@ async function vetAndClassify(item, vetter, roadmapRefs) {
|
|
|
45975
46391
|
});
|
|
45976
46392
|
} catch (err) {
|
|
45977
46393
|
rethrowIfFatal(err);
|
|
45978
|
-
warn(
|
|
46394
|
+
warn(MODULE14, `vet failed for ${item.html_url}: ${errorMessage(err)}`);
|
|
46395
|
+
return null;
|
|
46396
|
+
}
|
|
46397
|
+
if (!candidate.projectHealth.checkFailed && (candidate.projectHealth.stargazersCount ?? 0) < minStars) {
|
|
45979
46398
|
return null;
|
|
45980
46399
|
}
|
|
45981
46400
|
const horizon = classifyHorizon({
|
|
@@ -46019,12 +46438,12 @@ async function discoverFeatures(opts) {
|
|
|
46019
46438
|
roadmapRefs = refs;
|
|
46020
46439
|
} catch (err) {
|
|
46021
46440
|
rethrowIfFatal(err);
|
|
46022
|
-
warn(
|
|
46441
|
+
warn(MODULE14, `failed to list issues for ${anchorRepos[i]}: ${errorMessage(err)}`);
|
|
46023
46442
|
continue;
|
|
46024
46443
|
}
|
|
46025
46444
|
const items = response.data.filter((it) => !it.pull_request && !it.assignee && isFeatureIssue(it));
|
|
46026
46445
|
for (const item of items) {
|
|
46027
|
-
const candidate = await vetAndClassify(item, opts.vetter, roadmapRefs);
|
|
46446
|
+
const candidate = await vetAndClassify(item, opts.vetter, opts.minStars ?? 0, roadmapRefs);
|
|
46028
46447
|
if (candidate)
|
|
46029
46448
|
candidates.push(candidate);
|
|
46030
46449
|
}
|
|
@@ -46101,7 +46520,7 @@ async function discoverFeaturesBroad(opts) {
|
|
|
46101
46520
|
}).slice(0, maxToVet);
|
|
46102
46521
|
} catch (err) {
|
|
46103
46522
|
rethrowIfFatal(err);
|
|
46104
|
-
warn(
|
|
46523
|
+
warn(MODULE14, `broad feature search failed: ${errorMessage(err)}`);
|
|
46105
46524
|
return {
|
|
46106
46525
|
quickWins: [],
|
|
46107
46526
|
biggerBets: [],
|
|
@@ -46111,7 +46530,7 @@ async function discoverFeaturesBroad(opts) {
|
|
|
46111
46530
|
}
|
|
46112
46531
|
const candidates = [];
|
|
46113
46532
|
for (const item of items) {
|
|
46114
|
-
const candidate = await vetAndClassify(item, opts.vetter);
|
|
46533
|
+
const candidate = await vetAndClassify(item, opts.vetter, opts.minStars ?? 0);
|
|
46115
46534
|
if (candidate)
|
|
46116
46535
|
candidates.push(candidate);
|
|
46117
46536
|
}
|
|
@@ -46126,9 +46545,58 @@ async function discoverFeaturesBroad(opts) {
|
|
|
46126
46545
|
}
|
|
46127
46546
|
|
|
46128
46547
|
// ../core/dist/core/gist-state-store.js
|
|
46548
|
+
var fs5 = __toESM(require("fs"), 1);
|
|
46549
|
+
var path5 = __toESM(require("path"), 1);
|
|
46550
|
+
|
|
46551
|
+
// ../core/dist/core/local-state.js
|
|
46129
46552
|
var fs4 = __toESM(require("fs"), 1);
|
|
46130
46553
|
var path4 = __toESM(require("path"), 1);
|
|
46131
|
-
var
|
|
46554
|
+
var MODULE15 = "local-state";
|
|
46555
|
+
function getStatePath() {
|
|
46556
|
+
return path4.join(getDataDir(), "state.json");
|
|
46557
|
+
}
|
|
46558
|
+
function loadLocalState() {
|
|
46559
|
+
const statePath = getStatePath();
|
|
46560
|
+
try {
|
|
46561
|
+
const raw = fs4.readFileSync(statePath, "utf-8");
|
|
46562
|
+
return parseScoutState(JSON.parse(raw));
|
|
46563
|
+
} catch (err) {
|
|
46564
|
+
const code = err?.code;
|
|
46565
|
+
if (code === "ENOENT") {
|
|
46566
|
+
return ScoutStateSchema.parse({ version: 1 });
|
|
46567
|
+
}
|
|
46568
|
+
warn(MODULE15, `Failed to load state from ${statePath}: ${errorMessage(err)}. Using defaults.`);
|
|
46569
|
+
try {
|
|
46570
|
+
const backupPath = `${statePath}.corrupt.${Date.now()}`;
|
|
46571
|
+
fs4.copyFileSync(statePath, backupPath);
|
|
46572
|
+
warn(MODULE15, `Corrupt state backed up to ${backupPath}`);
|
|
46573
|
+
} catch (backupErr) {
|
|
46574
|
+
warn(MODULE15, `Failed to back up corrupt state: ${errorMessage(backupErr)}`);
|
|
46575
|
+
}
|
|
46576
|
+
return ScoutStateSchema.parse({ version: 1 });
|
|
46577
|
+
}
|
|
46578
|
+
}
|
|
46579
|
+
var tmpCounter = 0;
|
|
46580
|
+
function saveLocalState(state) {
|
|
46581
|
+
writeFileAtomic(getStatePath(), JSON.stringify(state, null, 2) + "\n");
|
|
46582
|
+
debug(MODULE15, "State saved");
|
|
46583
|
+
}
|
|
46584
|
+
function writeFileAtomic(filePath, data) {
|
|
46585
|
+
const tmpPath = `${filePath}.tmp.${process.pid}.${tmpCounter++}`;
|
|
46586
|
+
fs4.writeFileSync(tmpPath, data, { mode: 384 });
|
|
46587
|
+
try {
|
|
46588
|
+
fs4.renameSync(tmpPath, filePath);
|
|
46589
|
+
} catch (err) {
|
|
46590
|
+
try {
|
|
46591
|
+
fs4.unlinkSync(tmpPath);
|
|
46592
|
+
} catch {
|
|
46593
|
+
}
|
|
46594
|
+
throw err;
|
|
46595
|
+
}
|
|
46596
|
+
}
|
|
46597
|
+
|
|
46598
|
+
// ../core/dist/core/gist-state-store.js
|
|
46599
|
+
var MODULE16 = "gist-state";
|
|
46132
46600
|
var GIST_DESCRIPTION = "oss-scout-state";
|
|
46133
46601
|
var GIST_FILENAME = "state.json";
|
|
46134
46602
|
var GIST_ID_FILE = "gist-id";
|
|
@@ -46152,10 +46620,10 @@ function classifyDegradedReason(err) {
|
|
|
46152
46620
|
return "unknown";
|
|
46153
46621
|
}
|
|
46154
46622
|
function getGistIdPath() {
|
|
46155
|
-
return
|
|
46623
|
+
return path5.join(getDataDir(), GIST_ID_FILE);
|
|
46156
46624
|
}
|
|
46157
46625
|
function getCachePath() {
|
|
46158
|
-
return
|
|
46626
|
+
return path5.join(getDataDir(), CACHE_FILE);
|
|
46159
46627
|
}
|
|
46160
46628
|
var GistStateStore = class {
|
|
46161
46629
|
octokit;
|
|
@@ -46173,7 +46641,7 @@ var GistStateStore = class {
|
|
|
46173
46641
|
} catch (err) {
|
|
46174
46642
|
if (getHttpStatusCode(err) === 401)
|
|
46175
46643
|
throw err;
|
|
46176
|
-
warn(
|
|
46644
|
+
warn(MODULE16, `API bootstrap failed: ${errorMessage(err)}`);
|
|
46177
46645
|
return this.bootstrapFromCache(classifyDegradedReason(err));
|
|
46178
46646
|
}
|
|
46179
46647
|
}
|
|
@@ -46182,7 +46650,7 @@ var GistStateStore = class {
|
|
|
46182
46650
|
*/
|
|
46183
46651
|
async push(state) {
|
|
46184
46652
|
if (!this.gistId) {
|
|
46185
|
-
warn(
|
|
46653
|
+
warn(MODULE16, "No gist ID \u2014 cannot push");
|
|
46186
46654
|
this.writeCache(state);
|
|
46187
46655
|
return false;
|
|
46188
46656
|
}
|
|
@@ -46193,17 +46661,17 @@ var GistStateStore = class {
|
|
|
46193
46661
|
toWrite = mergeStates(state, remote.state);
|
|
46194
46662
|
} else if (remote.kind === "invalid") {
|
|
46195
46663
|
this.writeCache(state);
|
|
46196
|
-
warn(
|
|
46664
|
+
warn(MODULE16, "Remote gist content failed validation \u2014 not overwriting it. Changes saved locally; gist sync skipped.");
|
|
46197
46665
|
return false;
|
|
46198
46666
|
}
|
|
46199
46667
|
} catch (err) {
|
|
46200
46668
|
rethrowIfFatal(err);
|
|
46201
|
-
warn(
|
|
46669
|
+
warn(MODULE16, `Could not fetch gist before push, writing local snapshot: ${errorMessage(err)}`);
|
|
46202
46670
|
}
|
|
46203
46671
|
this.writeCache(toWrite);
|
|
46204
46672
|
const json2 = JSON.stringify(toWrite, null, 2);
|
|
46205
46673
|
if (json2.length > 9e5) {
|
|
46206
|
-
warn(
|
|
46674
|
+
warn(MODULE16, `State too large for gist (${Math.round(json2.length / 1024)}KB). Consider clearing old results with 'oss-scout results clear'.`);
|
|
46207
46675
|
return false;
|
|
46208
46676
|
}
|
|
46209
46677
|
try {
|
|
@@ -46213,11 +46681,11 @@ var GistStateStore = class {
|
|
|
46213
46681
|
[GIST_FILENAME]: { content: json2 }
|
|
46214
46682
|
}
|
|
46215
46683
|
});
|
|
46216
|
-
debug(
|
|
46684
|
+
debug(MODULE16, "State pushed to gist");
|
|
46217
46685
|
return true;
|
|
46218
46686
|
} catch (err) {
|
|
46219
46687
|
rethrowIfFatal(err);
|
|
46220
|
-
warn(
|
|
46688
|
+
warn(MODULE16, `Failed to push: ${errorMessage(err)}`);
|
|
46221
46689
|
return false;
|
|
46222
46690
|
}
|
|
46223
46691
|
}
|
|
@@ -46229,7 +46697,7 @@ var GistStateStore = class {
|
|
|
46229
46697
|
async bootstrapFromApi() {
|
|
46230
46698
|
const cachedId = this.readCachedGistId();
|
|
46231
46699
|
if (cachedId) {
|
|
46232
|
-
debug(
|
|
46700
|
+
debug(MODULE16, `Trying cached gist ID: ${cachedId}`);
|
|
46233
46701
|
try {
|
|
46234
46702
|
const fetched = await this.fetchGistState(cachedId);
|
|
46235
46703
|
if (fetched.kind === "ok") {
|
|
@@ -46241,13 +46709,13 @@ var GistStateStore = class {
|
|
|
46241
46709
|
} catch (err) {
|
|
46242
46710
|
if (getHttpStatusCode(err) !== 404)
|
|
46243
46711
|
throw err;
|
|
46244
|
-
debug(
|
|
46712
|
+
debug(MODULE16, `Cached gist gone (404): ${errorMessage(err)}`);
|
|
46245
46713
|
}
|
|
46246
|
-
debug(
|
|
46714
|
+
debug(MODULE16, "Cached gist ID invalid, searching...");
|
|
46247
46715
|
}
|
|
46248
46716
|
const search = await this.searchForGist();
|
|
46249
46717
|
if (search.id) {
|
|
46250
|
-
debug(
|
|
46718
|
+
debug(MODULE16, `Found gist via search: ${search.id}`);
|
|
46251
46719
|
this.saveGistId(search.id);
|
|
46252
46720
|
this.gistId = search.id;
|
|
46253
46721
|
const fetched = await this.fetchGistState(search.id);
|
|
@@ -46256,13 +46724,13 @@ var GistStateStore = class {
|
|
|
46256
46724
|
this.writeCache(state);
|
|
46257
46725
|
return { gistId: search.id, state, created: false };
|
|
46258
46726
|
}
|
|
46259
|
-
warn(
|
|
46727
|
+
warn(MODULE16, `Found existing gist ${search.id} but content failed validation. Using local cache to avoid data loss.`);
|
|
46260
46728
|
return this.bootstrapFromCache("unknown");
|
|
46261
46729
|
}
|
|
46262
46730
|
if (!search.exhaustive) {
|
|
46263
|
-
warn(
|
|
46731
|
+
warn(MODULE16, `Scanned the first ${SEARCH_MAX_PAGES * 100} gists without finding an oss-scout state gist, but the account has more. Creating a new state gist; if one already exists, copy its id into the gist-id file in the oss-scout data directory to avoid a duplicate.`);
|
|
46264
46732
|
}
|
|
46265
|
-
debug(
|
|
46733
|
+
debug(MODULE16, "No existing gist found, creating new one");
|
|
46266
46734
|
const freshState = ScoutStateSchema.parse({ version: 1 });
|
|
46267
46735
|
const newId = await this.createGist(freshState);
|
|
46268
46736
|
this.saveGistId(newId);
|
|
@@ -46273,7 +46741,7 @@ var GistStateStore = class {
|
|
|
46273
46741
|
bootstrapFromCache(reason) {
|
|
46274
46742
|
const cached2 = this.readCache();
|
|
46275
46743
|
if (cached2) {
|
|
46276
|
-
debug(
|
|
46744
|
+
debug(MODULE16, "Bootstrapped from local cache (degraded mode)");
|
|
46277
46745
|
const cachedId = this.readCachedGistId();
|
|
46278
46746
|
if (cachedId)
|
|
46279
46747
|
this.gistId = cachedId;
|
|
@@ -46285,7 +46753,7 @@ var GistStateStore = class {
|
|
|
46285
46753
|
degradedReason: reason
|
|
46286
46754
|
};
|
|
46287
46755
|
}
|
|
46288
|
-
debug(
|
|
46756
|
+
debug(MODULE16, "No cache available, using fresh state (degraded mode)");
|
|
46289
46757
|
const fresh = ScoutStateSchema.parse({ version: 1 });
|
|
46290
46758
|
return {
|
|
46291
46759
|
gistId: "",
|
|
@@ -46312,7 +46780,7 @@ var GistStateStore = class {
|
|
|
46312
46780
|
const parsed = JSON.parse(file2.content);
|
|
46313
46781
|
return { kind: "ok", state: parseScoutState(parsed) };
|
|
46314
46782
|
} catch (err) {
|
|
46315
|
-
warn(
|
|
46783
|
+
warn(MODULE16, `Gist content failed validation: ${errorMessage(err)}`);
|
|
46316
46784
|
return { kind: "invalid" };
|
|
46317
46785
|
}
|
|
46318
46786
|
}
|
|
@@ -46350,18 +46818,18 @@ var GistStateStore = class {
|
|
|
46350
46818
|
// ── Local file helpers ───────────────────────────────────────────────
|
|
46351
46819
|
readCachedGistId() {
|
|
46352
46820
|
try {
|
|
46353
|
-
const id =
|
|
46821
|
+
const id = fs5.readFileSync(getGistIdPath(), "utf-8").trim();
|
|
46354
46822
|
return id || null;
|
|
46355
46823
|
} catch (err) {
|
|
46356
46824
|
const code = err?.code;
|
|
46357
46825
|
if (code !== "ENOENT") {
|
|
46358
|
-
warn(
|
|
46826
|
+
warn(MODULE16, `Failed to read cached gist ID: ${errorMessage(err)}`);
|
|
46359
46827
|
}
|
|
46360
46828
|
return null;
|
|
46361
46829
|
}
|
|
46362
46830
|
}
|
|
46363
46831
|
saveGistId(id) {
|
|
46364
|
-
|
|
46832
|
+
writeFileAtomic(getGistIdPath(), id + "\n");
|
|
46365
46833
|
}
|
|
46366
46834
|
/**
|
|
46367
46835
|
* Merge the local state-cache into a freshly fetched gist state before it
|
|
@@ -46376,23 +46844,21 @@ var GistStateStore = class {
|
|
|
46376
46844
|
}
|
|
46377
46845
|
readCache() {
|
|
46378
46846
|
try {
|
|
46379
|
-
const raw =
|
|
46847
|
+
const raw = fs5.readFileSync(getCachePath(), "utf-8");
|
|
46380
46848
|
return parseScoutState(JSON.parse(raw));
|
|
46381
46849
|
} catch (err) {
|
|
46382
46850
|
const code = err?.code;
|
|
46383
46851
|
if (code !== "ENOENT") {
|
|
46384
|
-
warn(
|
|
46852
|
+
warn(MODULE16, `Failed to read state cache: ${errorMessage(err)}`);
|
|
46385
46853
|
}
|
|
46386
46854
|
return null;
|
|
46387
46855
|
}
|
|
46388
46856
|
}
|
|
46389
46857
|
writeCache(state) {
|
|
46390
46858
|
try {
|
|
46391
|
-
|
|
46392
|
-
mode: 384
|
|
46393
|
-
});
|
|
46859
|
+
writeFileAtomic(getCachePath(), JSON.stringify(state, null, 2) + "\n");
|
|
46394
46860
|
} catch (err) {
|
|
46395
|
-
warn(
|
|
46861
|
+
warn(MODULE16, `Failed to write cache: ${errorMessage(err)}`);
|
|
46396
46862
|
}
|
|
46397
46863
|
}
|
|
46398
46864
|
};
|
|
@@ -46540,53 +47006,8 @@ function pickFresherTimestamp(a, b) {
|
|
|
46540
47006
|
return a >= b ? a : b;
|
|
46541
47007
|
}
|
|
46542
47008
|
|
|
46543
|
-
// ../core/dist/core/local-state.js
|
|
46544
|
-
var fs5 = __toESM(require("fs"), 1);
|
|
46545
|
-
var path5 = __toESM(require("path"), 1);
|
|
46546
|
-
var MODULE15 = "local-state";
|
|
46547
|
-
function getStatePath() {
|
|
46548
|
-
return path5.join(getDataDir(), "state.json");
|
|
46549
|
-
}
|
|
46550
|
-
function loadLocalState() {
|
|
46551
|
-
const statePath = getStatePath();
|
|
46552
|
-
try {
|
|
46553
|
-
const raw = fs5.readFileSync(statePath, "utf-8");
|
|
46554
|
-
return parseScoutState(JSON.parse(raw));
|
|
46555
|
-
} catch (err) {
|
|
46556
|
-
const code = err?.code;
|
|
46557
|
-
if (code === "ENOENT") {
|
|
46558
|
-
return ScoutStateSchema.parse({ version: 1 });
|
|
46559
|
-
}
|
|
46560
|
-
warn(MODULE15, `Failed to load state from ${statePath}: ${errorMessage(err)}. Using defaults.`);
|
|
46561
|
-
try {
|
|
46562
|
-
const backupPath = `${statePath}.corrupt.${Date.now()}`;
|
|
46563
|
-
fs5.copyFileSync(statePath, backupPath);
|
|
46564
|
-
warn(MODULE15, `Corrupt state backed up to ${backupPath}`);
|
|
46565
|
-
} catch (backupErr) {
|
|
46566
|
-
warn(MODULE15, `Failed to back up corrupt state: ${errorMessage(backupErr)}`);
|
|
46567
|
-
}
|
|
46568
|
-
return ScoutStateSchema.parse({ version: 1 });
|
|
46569
|
-
}
|
|
46570
|
-
}
|
|
46571
|
-
var tmpCounter = 0;
|
|
46572
|
-
function saveLocalState(state) {
|
|
46573
|
-
const statePath = getStatePath();
|
|
46574
|
-
const tmpPath = `${statePath}.tmp.${process.pid}.${tmpCounter++}`;
|
|
46575
|
-
const data = JSON.stringify(state, null, 2) + "\n";
|
|
46576
|
-
fs5.writeFileSync(tmpPath, data, { mode: 384 });
|
|
46577
|
-
try {
|
|
46578
|
-
fs5.renameSync(tmpPath, statePath);
|
|
46579
|
-
} catch (err) {
|
|
46580
|
-
try {
|
|
46581
|
-
fs5.unlinkSync(tmpPath);
|
|
46582
|
-
} catch {
|
|
46583
|
-
}
|
|
46584
|
-
throw err;
|
|
46585
|
-
}
|
|
46586
|
-
debug(MODULE15, "State saved");
|
|
46587
|
-
}
|
|
46588
|
-
|
|
46589
47009
|
// ../core/dist/scout.js
|
|
47010
|
+
var LINKED_PR_SKIP_REASON = /Linked PR already merged|Linked PR closed without merge|Has existing PR/;
|
|
46590
47011
|
function offlineModeMessage(reason) {
|
|
46591
47012
|
const tail = "Changes will only be saved locally.";
|
|
46592
47013
|
switch (reason) {
|
|
@@ -46696,7 +47117,8 @@ var OssScout = class {
|
|
|
46696
47117
|
async search(options) {
|
|
46697
47118
|
this.evictStaleCacheEntries();
|
|
46698
47119
|
this.cullExpiredSkips();
|
|
46699
|
-
const
|
|
47120
|
+
const unlockedRepos = new Set(this.getComplexIssuesMode() === "warm" ? this.getReposWithMergedPRs().map((r) => r.toLowerCase()) : []);
|
|
47121
|
+
const skippedUrls = new Set((this.state.skippedIssues ?? []).filter((s) => !(unlockedRepos.has(s.repo.toLowerCase()) && LINKED_PR_SKIP_REASON.test(s.reason ?? ""))).map((s) => s.url));
|
|
46700
47122
|
if (options?.excludeRecentlySurfaced ?? true) {
|
|
46701
47123
|
const ttlDays = options?.recentlySurfacedTtlDays ?? 7;
|
|
46702
47124
|
const cutoff = Date.now() - ttlDays * 24 * 60 * 60 * 1e3;
|
|
@@ -46843,6 +47265,7 @@ var OssScout = class {
|
|
|
46843
47265
|
excludeRepos: this.state.preferences.excludeRepos,
|
|
46844
47266
|
excludeOrgs: this.state.preferences.excludeOrgs,
|
|
46845
47267
|
aiPolicyBlocklist: this.state.preferences.aiPolicyBlocklist,
|
|
47268
|
+
minStars: this.state.preferences.minStars,
|
|
46846
47269
|
splitRatio: options?.splitRatio ?? this.state.preferences.featuresSplitRatio
|
|
46847
47270
|
}) : await discoverFeatures({
|
|
46848
47271
|
octokit,
|
|
@@ -46852,6 +47275,7 @@ var OssScout = class {
|
|
|
46852
47275
|
excludeRepos: this.state.preferences.excludeRepos,
|
|
46853
47276
|
excludeOrgs: this.state.preferences.excludeOrgs,
|
|
46854
47277
|
aiPolicyBlocklist: this.state.preferences.aiPolicyBlocklist,
|
|
47278
|
+
minStars: this.state.preferences.minStars,
|
|
46855
47279
|
anchorThreshold: options?.anchorThreshold ?? this.state.preferences.featuresAnchorThreshold,
|
|
46856
47280
|
splitRatio: options?.splitRatio ?? this.state.preferences.featuresSplitRatio
|
|
46857
47281
|
});
|
|
@@ -47025,6 +47449,9 @@ var OssScout = class {
|
|
|
47025
47449
|
return null;
|
|
47026
47450
|
return { model, host: this.state.preferences.slmTriageHost ?? "" };
|
|
47027
47451
|
}
|
|
47452
|
+
getComplexIssuesMode() {
|
|
47453
|
+
return this.state.preferences.complexIssues ?? "off";
|
|
47454
|
+
}
|
|
47028
47455
|
/** Get current preferences (read-only). */
|
|
47029
47456
|
getPreferences() {
|
|
47030
47457
|
return this.state.preferences;
|
|
@@ -47456,6 +47883,7 @@ var FIELD_CONFIGS = {
|
|
|
47456
47883
|
boostIssueTypes: { type: "array" },
|
|
47457
47884
|
slmTriageModel: { type: "string" },
|
|
47458
47885
|
slmTriageHost: { type: "string" },
|
|
47886
|
+
complexIssues: { type: "enum", validValues: ComplexIssuesModeSchema.options },
|
|
47459
47887
|
featuresAnchorThreshold: { type: "number" },
|
|
47460
47888
|
featuresSplitRatio: { type: "float" }
|
|
47461
47889
|
};
|
|
@@ -47948,7 +48376,7 @@ function registerResources(server, scout) {
|
|
|
47948
48376
|
// src/index.ts
|
|
47949
48377
|
var SERVER_INFO = {
|
|
47950
48378
|
name: "oss-scout-mcp",
|
|
47951
|
-
version: "0.13.
|
|
48379
|
+
version: "0.13.2"
|
|
47952
48380
|
// x-release-please-version
|
|
47953
48381
|
};
|
|
47954
48382
|
function createServer(scout) {
|