@oss-scout/mcp 0.2.0 → 0.2.1
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.js +8 -8
- package/dist/mcp-server.bundle.cjs +563 -265
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +21 -15
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +110 -36
- package/package.json +1 -1
|
@@ -35495,6 +35495,15 @@ function getCacheDir() {
|
|
|
35495
35495
|
}
|
|
35496
35496
|
return dir;
|
|
35497
35497
|
}
|
|
35498
|
+
function extractRepoFromUrl(url2) {
|
|
35499
|
+
const apiMatch = url2.match(/api\.github\.com\/repos\/([^/]+\/[^/]+)/);
|
|
35500
|
+
if (apiMatch)
|
|
35501
|
+
return apiMatch[1];
|
|
35502
|
+
const webMatch = url2.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
35503
|
+
if (webMatch)
|
|
35504
|
+
return webMatch[1];
|
|
35505
|
+
return null;
|
|
35506
|
+
}
|
|
35498
35507
|
var OWNER_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
35499
35508
|
var REPO_PATTERN = /^[a-zA-Z0-9_.-]+$/;
|
|
35500
35509
|
function isValidOwnerRepo(owner, repo) {
|
|
@@ -35656,13 +35665,30 @@ function getSearchBudgetTracker() {
|
|
|
35656
35665
|
|
|
35657
35666
|
// ../core/dist/core/types.js
|
|
35658
35667
|
var SCOPE_LABELS = {
|
|
35659
|
-
beginner: [
|
|
35660
|
-
|
|
35668
|
+
beginner: [
|
|
35669
|
+
"good first issue",
|
|
35670
|
+
"help wanted",
|
|
35671
|
+
"easy",
|
|
35672
|
+
"up-for-grabs",
|
|
35673
|
+
"first-timers-only",
|
|
35674
|
+
"beginner"
|
|
35675
|
+
],
|
|
35676
|
+
intermediate: [
|
|
35677
|
+
"enhancement",
|
|
35678
|
+
"feature",
|
|
35679
|
+
"feature-request",
|
|
35680
|
+
"contributions welcome"
|
|
35681
|
+
],
|
|
35661
35682
|
advanced: ["proposal", "RFC", "accepted", "design"]
|
|
35662
35683
|
};
|
|
35663
35684
|
|
|
35664
35685
|
// ../core/dist/core/schemas.js
|
|
35665
|
-
var IssueStatusSchema = external_exports3.enum([
|
|
35686
|
+
var IssueStatusSchema = external_exports3.enum([
|
|
35687
|
+
"candidate",
|
|
35688
|
+
"claimed",
|
|
35689
|
+
"in_progress",
|
|
35690
|
+
"pr_submitted"
|
|
35691
|
+
]);
|
|
35666
35692
|
var ProjectCategorySchema = external_exports3.enum([
|
|
35667
35693
|
"nonprofit",
|
|
35668
35694
|
"devtools",
|
|
@@ -35671,9 +35697,26 @@ var ProjectCategorySchema = external_exports3.enum([
|
|
|
35671
35697
|
"data-ml",
|
|
35672
35698
|
"education"
|
|
35673
35699
|
]);
|
|
35674
|
-
var IssueScopeSchema = external_exports3.enum([
|
|
35675
|
-
|
|
35676
|
-
|
|
35700
|
+
var IssueScopeSchema = external_exports3.enum([
|
|
35701
|
+
"beginner",
|
|
35702
|
+
"intermediate",
|
|
35703
|
+
"advanced"
|
|
35704
|
+
]);
|
|
35705
|
+
var SearchStrategySchema = external_exports3.enum([
|
|
35706
|
+
"merged",
|
|
35707
|
+
"orgs",
|
|
35708
|
+
"starred",
|
|
35709
|
+
"broad",
|
|
35710
|
+
"maintained",
|
|
35711
|
+
"all"
|
|
35712
|
+
]);
|
|
35713
|
+
var CONCRETE_STRATEGIES = [
|
|
35714
|
+
"merged",
|
|
35715
|
+
"orgs",
|
|
35716
|
+
"starred",
|
|
35717
|
+
"broad",
|
|
35718
|
+
"maintained"
|
|
35719
|
+
];
|
|
35677
35720
|
var RepoSignalsSchema = external_exports3.object({
|
|
35678
35721
|
hasActiveMaintainers: external_exports3.boolean(),
|
|
35679
35722
|
isResponsive: external_exports3.boolean(),
|
|
@@ -35788,7 +35831,12 @@ var ScoutStateSchema = external_exports3.object({
|
|
|
35788
35831
|
});
|
|
35789
35832
|
|
|
35790
35833
|
// ../core/dist/core/issue-filtering.js
|
|
35791
|
-
var DOC_ONLY_LABELS = /* @__PURE__ */ new Set([
|
|
35834
|
+
var DOC_ONLY_LABELS = /* @__PURE__ */ new Set([
|
|
35835
|
+
"documentation",
|
|
35836
|
+
"docs",
|
|
35837
|
+
"typo",
|
|
35838
|
+
"spelling"
|
|
35839
|
+
]);
|
|
35792
35840
|
function isDocOnlyIssue(item) {
|
|
35793
35841
|
if (!item.labels || !Array.isArray(item.labels) || item.labels.length === 0)
|
|
35794
35842
|
return false;
|
|
@@ -35827,7 +35875,9 @@ function detectLabelFarmingRepos(items) {
|
|
|
35827
35875
|
const spamRepos = /* @__PURE__ */ new Set();
|
|
35828
35876
|
const repoSpamCounts = /* @__PURE__ */ new Map();
|
|
35829
35877
|
for (const item of items) {
|
|
35830
|
-
const repoFullName = item.repository_url
|
|
35878
|
+
const repoFullName = extractRepoFromUrl(item.repository_url);
|
|
35879
|
+
if (!repoFullName)
|
|
35880
|
+
continue;
|
|
35831
35881
|
if (isLabelFarming(item)) {
|
|
35832
35882
|
spamRepos.add(repoFullName);
|
|
35833
35883
|
continue;
|
|
@@ -35913,20 +35963,111 @@ function calculateViabilityScore(params) {
|
|
|
35913
35963
|
|
|
35914
35964
|
// ../core/dist/core/category-mapping.js
|
|
35915
35965
|
var CATEGORY_TOPICS = {
|
|
35916
|
-
nonprofit: [
|
|
35917
|
-
|
|
35918
|
-
|
|
35919
|
-
|
|
35920
|
-
|
|
35921
|
-
|
|
35966
|
+
nonprofit: [
|
|
35967
|
+
"nonprofit",
|
|
35968
|
+
"social-good",
|
|
35969
|
+
"humanitarian",
|
|
35970
|
+
"charity",
|
|
35971
|
+
"social-impact",
|
|
35972
|
+
"civic-tech"
|
|
35973
|
+
],
|
|
35974
|
+
devtools: [
|
|
35975
|
+
"developer-tools",
|
|
35976
|
+
"devtools",
|
|
35977
|
+
"cli",
|
|
35978
|
+
"sdk",
|
|
35979
|
+
"linter",
|
|
35980
|
+
"formatter",
|
|
35981
|
+
"build-tool"
|
|
35982
|
+
],
|
|
35983
|
+
infrastructure: [
|
|
35984
|
+
"infrastructure",
|
|
35985
|
+
"cloud",
|
|
35986
|
+
"kubernetes",
|
|
35987
|
+
"docker",
|
|
35988
|
+
"devops",
|
|
35989
|
+
"monitoring",
|
|
35990
|
+
"observability"
|
|
35991
|
+
],
|
|
35992
|
+
"web-frameworks": [
|
|
35993
|
+
"web-framework",
|
|
35994
|
+
"frontend",
|
|
35995
|
+
"backend",
|
|
35996
|
+
"fullstack",
|
|
35997
|
+
"nextjs",
|
|
35998
|
+
"react",
|
|
35999
|
+
"vue"
|
|
36000
|
+
],
|
|
36001
|
+
"data-ml": [
|
|
36002
|
+
"machine-learning",
|
|
36003
|
+
"data-science",
|
|
36004
|
+
"deep-learning",
|
|
36005
|
+
"nlp",
|
|
36006
|
+
"data-pipeline",
|
|
36007
|
+
"analytics"
|
|
36008
|
+
],
|
|
36009
|
+
education: [
|
|
36010
|
+
"education",
|
|
36011
|
+
"learning",
|
|
36012
|
+
"tutorial",
|
|
36013
|
+
"courseware",
|
|
36014
|
+
"edtech",
|
|
36015
|
+
"teaching"
|
|
36016
|
+
]
|
|
35922
36017
|
};
|
|
35923
36018
|
var CATEGORY_ORGS = {
|
|
35924
|
-
nonprofit: [
|
|
35925
|
-
|
|
35926
|
-
|
|
35927
|
-
|
|
35928
|
-
|
|
35929
|
-
|
|
36019
|
+
nonprofit: [
|
|
36020
|
+
"code-for-america",
|
|
36021
|
+
"opengovfoundation",
|
|
36022
|
+
"ushahidi",
|
|
36023
|
+
"hotosm",
|
|
36024
|
+
"openfn",
|
|
36025
|
+
"democracyearth"
|
|
36026
|
+
],
|
|
36027
|
+
devtools: [
|
|
36028
|
+
"eslint",
|
|
36029
|
+
"prettier",
|
|
36030
|
+
"vitejs",
|
|
36031
|
+
"biomejs",
|
|
36032
|
+
"oxc-project",
|
|
36033
|
+
"ast-grep",
|
|
36034
|
+
"turbot"
|
|
36035
|
+
],
|
|
36036
|
+
infrastructure: [
|
|
36037
|
+
"kubernetes",
|
|
36038
|
+
"hashicorp",
|
|
36039
|
+
"grafana",
|
|
36040
|
+
"prometheus",
|
|
36041
|
+
"open-telemetry",
|
|
36042
|
+
"envoyproxy",
|
|
36043
|
+
"cncf"
|
|
36044
|
+
],
|
|
36045
|
+
"web-frameworks": [
|
|
36046
|
+
"vercel",
|
|
36047
|
+
"remix-run",
|
|
36048
|
+
"sveltejs",
|
|
36049
|
+
"nuxt",
|
|
36050
|
+
"astro",
|
|
36051
|
+
"redwoodjs",
|
|
36052
|
+
"blitz-js"
|
|
36053
|
+
],
|
|
36054
|
+
"data-ml": [
|
|
36055
|
+
"huggingface",
|
|
36056
|
+
"mlflow",
|
|
36057
|
+
"apache",
|
|
36058
|
+
"dbt-labs",
|
|
36059
|
+
"dagster-io",
|
|
36060
|
+
"prefecthq",
|
|
36061
|
+
"langchain-ai"
|
|
36062
|
+
],
|
|
36063
|
+
education: [
|
|
36064
|
+
"freeCodeCamp",
|
|
36065
|
+
"TheOdinProject",
|
|
36066
|
+
"exercism",
|
|
36067
|
+
"codecademy",
|
|
36068
|
+
"oppia",
|
|
36069
|
+
"Khan"
|
|
36070
|
+
]
|
|
35930
36071
|
};
|
|
35931
36072
|
function repoBelongsToCategory(repoFullName, categories) {
|
|
35932
36073
|
if (categories.length === 0)
|
|
@@ -36046,7 +36187,10 @@ var HttpCache = class {
|
|
|
36046
36187
|
cachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
36047
36188
|
};
|
|
36048
36189
|
try {
|
|
36049
|
-
fs2.writeFileSync(this.pathFor(url2), JSON.stringify(entry), {
|
|
36190
|
+
fs2.writeFileSync(this.pathFor(url2), JSON.stringify(entry), {
|
|
36191
|
+
encoding: "utf-8",
|
|
36192
|
+
mode: 384
|
|
36193
|
+
});
|
|
36050
36194
|
debug(MODULE4, `Cached response for ${url2}`);
|
|
36051
36195
|
} catch (err) {
|
|
36052
36196
|
warn(MODULE4, `Failed to write cache for ${url2}: ${errorMessage(err)}`);
|
|
@@ -36301,7 +36445,12 @@ function analyzeRequirements(body) {
|
|
|
36301
36445
|
const hasSteps = /\d\.|[-*]\s/.test(body);
|
|
36302
36446
|
const hasCodeBlock = /```/.test(body);
|
|
36303
36447
|
const hasExpectedBehavior = /expect|should|must|want/i.test(body);
|
|
36304
|
-
const indicators = [
|
|
36448
|
+
const indicators = [
|
|
36449
|
+
hasSteps,
|
|
36450
|
+
hasCodeBlock,
|
|
36451
|
+
hasExpectedBehavior,
|
|
36452
|
+
body.length > 200
|
|
36453
|
+
];
|
|
36305
36454
|
return indicators.filter(Boolean).length >= 2;
|
|
36306
36455
|
}
|
|
36307
36456
|
|
|
@@ -36378,7 +36527,12 @@ async function fetchContributionGuidelines(octokit, owner, repo) {
|
|
|
36378
36527
|
if (cached2 && Date.now() - cached2.fetchedAt < CACHE_TTL_MS) {
|
|
36379
36528
|
return cached2.guidelines;
|
|
36380
36529
|
}
|
|
36381
|
-
const filesToCheck = [
|
|
36530
|
+
const filesToCheck = [
|
|
36531
|
+
"CONTRIBUTING.md",
|
|
36532
|
+
".github/CONTRIBUTING.md",
|
|
36533
|
+
"docs/CONTRIBUTING.md",
|
|
36534
|
+
"contributing.md"
|
|
36535
|
+
];
|
|
36382
36536
|
const results = await Promise.allSettled(filesToCheck.map((file2) => octokit.repos.getContent({ owner, repo, path: file2 }).then(({ data }) => {
|
|
36383
36537
|
if ("content" in data) {
|
|
36384
36538
|
return Buffer.from(data.content, "base64").toString("utf-8");
|
|
@@ -36400,7 +36554,10 @@ async function fetchContributionGuidelines(octokit, owner, repo) {
|
|
|
36400
36554
|
}
|
|
36401
36555
|
}
|
|
36402
36556
|
}
|
|
36403
|
-
guidelinesCache.set(cacheKey, {
|
|
36557
|
+
guidelinesCache.set(cacheKey, {
|
|
36558
|
+
guidelines: void 0,
|
|
36559
|
+
fetchedAt: Date.now()
|
|
36560
|
+
});
|
|
36404
36561
|
pruneCache();
|
|
36405
36562
|
return void 0;
|
|
36406
36563
|
}
|
|
@@ -36662,7 +36819,11 @@ var IssueVetter = class {
|
|
|
36662
36819
|
if (allFailed) {
|
|
36663
36820
|
warn(MODULE7, `All ${attemptedCount} issue(s) failed vetting. This may indicate a systemic issue (rate limit, auth, network).`);
|
|
36664
36821
|
}
|
|
36665
|
-
return {
|
|
36822
|
+
return {
|
|
36823
|
+
candidates: candidates.slice(0, maxResults),
|
|
36824
|
+
allFailed,
|
|
36825
|
+
rateLimitHit: rateLimitFailures > 0
|
|
36826
|
+
};
|
|
36666
36827
|
}
|
|
36667
36828
|
};
|
|
36668
36829
|
|
|
@@ -36763,11 +36924,13 @@ async function searchWithChunkedLabels(octokit, labels, reservedOps, buildQuery,
|
|
|
36763
36924
|
async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets, remainingNeeded, minStars, phaseLabel) {
|
|
36764
36925
|
const spamRepos = detectLabelFarmingRepos(items);
|
|
36765
36926
|
if (spamRepos.size > 0) {
|
|
36766
|
-
const spamCount = items.filter((i) => spamRepos.has(i.repository_url
|
|
36927
|
+
const spamCount = items.filter((i) => spamRepos.has(extractRepoFromUrl(i.repository_url) ?? "")).length;
|
|
36767
36928
|
debug(MODULE8, `[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`);
|
|
36768
36929
|
}
|
|
36769
36930
|
const itemsToVet = filterIssues(items).filter((item) => {
|
|
36770
|
-
const repoFullName = item.repository_url
|
|
36931
|
+
const repoFullName = extractRepoFromUrl(item.repository_url);
|
|
36932
|
+
if (!repoFullName)
|
|
36933
|
+
return false;
|
|
36771
36934
|
if (spamRepos.has(repoFullName))
|
|
36772
36935
|
return false;
|
|
36773
36936
|
return excludedRepoSets.every((s) => !s.has(repoFullName));
|
|
@@ -36835,6 +36998,182 @@ var MODULE9 = "issue-discovery";
|
|
|
36835
36998
|
var INTER_PHASE_DELAY_MS = 2e3;
|
|
36836
36999
|
var LOW_BUDGET_THRESHOLD = 20;
|
|
36837
37000
|
var CRITICAL_BUDGET_THRESHOLD = 10;
|
|
37001
|
+
function buildIssueFilter(config2) {
|
|
37002
|
+
return (items) => {
|
|
37003
|
+
return items.filter((item) => {
|
|
37004
|
+
const repoFullName = extractRepoFromUrl(item.repository_url);
|
|
37005
|
+
if (!repoFullName)
|
|
37006
|
+
return false;
|
|
37007
|
+
if (config2.excludedRepos.has(repoFullName))
|
|
37008
|
+
return false;
|
|
37009
|
+
if (config2.excludeOrgs.size > 0) {
|
|
37010
|
+
const orgName = repoFullName.split("/")[0]?.toLowerCase();
|
|
37011
|
+
if (orgName && config2.excludeOrgs.has(orgName))
|
|
37012
|
+
return false;
|
|
37013
|
+
}
|
|
37014
|
+
if (config2.aiBlocklisted.has(repoFullName))
|
|
37015
|
+
return false;
|
|
37016
|
+
if (config2.lowScoringRepos.has(repoFullName))
|
|
37017
|
+
return false;
|
|
37018
|
+
const updatedAt = new Date(item.updated_at);
|
|
37019
|
+
const ageDays = daysBetween(updatedAt, config2.now);
|
|
37020
|
+
if (ageDays > config2.maxAgeDays)
|
|
37021
|
+
return false;
|
|
37022
|
+
if (!config2.includeDocIssues && isDocOnlyIssue(item))
|
|
37023
|
+
return false;
|
|
37024
|
+
return true;
|
|
37025
|
+
});
|
|
37026
|
+
};
|
|
37027
|
+
}
|
|
37028
|
+
async function runPhase0(octokit, vetter, repos, baseQualifiers, maxResults, filterIssues) {
|
|
37029
|
+
info(MODULE9, `Phase 0: Searching issues in ${repos.length} merged-PR repos (no label filter)...`);
|
|
37030
|
+
const { candidates, allBatchesFailed, rateLimitHit } = await searchInRepos(octokit, vetter, repos, baseQualifiers, [], maxResults, "merged_pr", filterIssues);
|
|
37031
|
+
info(MODULE9, `Found ${candidates.length} candidates from merged-PR repos`);
|
|
37032
|
+
return {
|
|
37033
|
+
candidates,
|
|
37034
|
+
error: allBatchesFailed ? "All merged-PR repo batches failed" : null,
|
|
37035
|
+
rateLimitHit
|
|
37036
|
+
};
|
|
37037
|
+
}
|
|
37038
|
+
async function runPhase05(octokit, vetter, orgsToSearch, baseQualifiers, labels, maxResults, phase0RepoSet, filterIssues) {
|
|
37039
|
+
info(MODULE9, `Phase 0.5: Searching issues in ${orgsToSearch.length} preferred org(s)...`);
|
|
37040
|
+
const orgRepoFilter = orgsToSearch.map((org) => `org:${org}`).join(" OR ");
|
|
37041
|
+
const orgOps = orgsToSearch.length - 1;
|
|
37042
|
+
try {
|
|
37043
|
+
const allItems = await searchWithChunkedLabels(octokit, labels, orgOps, (labelQ) => `${baseQualifiers} ${labelQ} (${orgRepoFilter})`.replace(/ +/g, " ").trim(), maxResults * 3);
|
|
37044
|
+
if (allItems.length === 0) {
|
|
37045
|
+
return { candidates: [], error: null, rateLimitHit: false };
|
|
37046
|
+
}
|
|
37047
|
+
const filtered = filterIssues(allItems).filter((item) => {
|
|
37048
|
+
const repoFullName = extractRepoFromUrl(item.repository_url);
|
|
37049
|
+
if (!repoFullName)
|
|
37050
|
+
return false;
|
|
37051
|
+
return !phase0RepoSet.has(repoFullName);
|
|
37052
|
+
});
|
|
37053
|
+
const { candidates, allFailed: allVetFailed, rateLimitHit } = await vetter.vetIssuesParallel(filtered.slice(0, maxResults * 2).map((i) => i.html_url), maxResults, "preferred_org");
|
|
37054
|
+
info(MODULE9, `Found ${candidates.length} candidates from preferred orgs`);
|
|
37055
|
+
return {
|
|
37056
|
+
candidates,
|
|
37057
|
+
error: allVetFailed ? "All preferred org issue vetting failed" : null,
|
|
37058
|
+
rateLimitHit
|
|
37059
|
+
};
|
|
37060
|
+
} catch (error48) {
|
|
37061
|
+
const errMsg = errorMessage(error48);
|
|
37062
|
+
warn(MODULE9, `Error searching preferred orgs: ${errMsg}`);
|
|
37063
|
+
return {
|
|
37064
|
+
candidates: [],
|
|
37065
|
+
error: errMsg,
|
|
37066
|
+
rateLimitHit: isRateLimitError(error48)
|
|
37067
|
+
};
|
|
37068
|
+
}
|
|
37069
|
+
}
|
|
37070
|
+
async function runPhase1(octokit, vetter, repos, baseQualifiers, labels, maxResults, filterIssues) {
|
|
37071
|
+
info(MODULE9, `Phase 1: Searching issues in ${repos.length} starred repos...`);
|
|
37072
|
+
const phase1Labels = labels.slice(0, 3);
|
|
37073
|
+
const { candidates, allBatchesFailed, rateLimitHit } = await searchInRepos(octokit, vetter, repos.slice(0, 10), baseQualifiers, phase1Labels, maxResults, "starred", filterIssues);
|
|
37074
|
+
info(MODULE9, `Found ${candidates.length} candidates from starred repos`);
|
|
37075
|
+
return {
|
|
37076
|
+
candidates,
|
|
37077
|
+
error: allBatchesFailed ? "All starred repo batches failed" : null,
|
|
37078
|
+
rateLimitHit
|
|
37079
|
+
};
|
|
37080
|
+
}
|
|
37081
|
+
async function runPhase2(octokit, vetter, scopes, labels, configLabels, baseQualifiers, maxResults, minStars, phase0RepoSet, starredRepoSet, existingCandidates, filterIssues) {
|
|
37082
|
+
info(MODULE9, "Phase 2: General issue search...");
|
|
37083
|
+
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
37084
|
+
const tierLabelGroups = [];
|
|
37085
|
+
if (scopes && scopes.length > 1) {
|
|
37086
|
+
for (const scope of scopes) {
|
|
37087
|
+
const scopeLabels = SCOPE_LABELS[scope] ?? [];
|
|
37088
|
+
if (scopeLabels.length === 0) {
|
|
37089
|
+
warn(MODULE9, `Scope "${scope}" has no labels, skipping tier`);
|
|
37090
|
+
continue;
|
|
37091
|
+
}
|
|
37092
|
+
tierLabelGroups.push({ tier: scope, tierLabels: scopeLabels });
|
|
37093
|
+
}
|
|
37094
|
+
const allScopeLabels = new Set(scopes.flatMap((s) => SCOPE_LABELS[s] ?? []));
|
|
37095
|
+
const customOnly = configLabels.filter((l) => !allScopeLabels.has(l));
|
|
37096
|
+
if (customOnly.length > 0) {
|
|
37097
|
+
tierLabelGroups.push({ tier: "custom", tierLabels: customOnly });
|
|
37098
|
+
}
|
|
37099
|
+
} else {
|
|
37100
|
+
tierLabelGroups.push({ tier: "general", tierLabels: labels });
|
|
37101
|
+
}
|
|
37102
|
+
const budgetPerTier = Math.ceil(maxResults / tierLabelGroups.length);
|
|
37103
|
+
const tierResults = [];
|
|
37104
|
+
let error48 = null;
|
|
37105
|
+
let rateLimitHit = false;
|
|
37106
|
+
for (const { tier, tierLabels } of tierLabelGroups) {
|
|
37107
|
+
try {
|
|
37108
|
+
const allItems = await searchWithChunkedLabels(octokit, tierLabels, 0, (labelQ) => `${baseQualifiers} ${labelQ}`.replace(/ +/g, " ").trim(), budgetPerTier * 3);
|
|
37109
|
+
info(MODULE9, `Phase 2 [${tier}]: processing ${allItems.length} items...`);
|
|
37110
|
+
const { candidates: tierCandidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, allItems, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], budgetPerTier, minStars, `Phase 2 [${tier}]`);
|
|
37111
|
+
tierResults.push(tierCandidates);
|
|
37112
|
+
for (const c of tierCandidates)
|
|
37113
|
+
seenRepos.add(c.issue.repo);
|
|
37114
|
+
if (allVetFailed) {
|
|
37115
|
+
error48 = (error48 ? error48 + "; " : "") + `${tier}: all vetting failed`;
|
|
37116
|
+
}
|
|
37117
|
+
if (vetRateLimitHit) {
|
|
37118
|
+
rateLimitHit = true;
|
|
37119
|
+
}
|
|
37120
|
+
info(MODULE9, `Found ${tierCandidates.length} candidates from ${tier} tier`);
|
|
37121
|
+
} catch (err) {
|
|
37122
|
+
if (getHttpStatusCode(err) === 401)
|
|
37123
|
+
throw err;
|
|
37124
|
+
const errMsg = errorMessage(err);
|
|
37125
|
+
error48 = (error48 ? error48 + "; " : "") + `${tier}: ${errMsg}`;
|
|
37126
|
+
if (isRateLimitError(err)) {
|
|
37127
|
+
rateLimitHit = true;
|
|
37128
|
+
}
|
|
37129
|
+
warn(MODULE9, `Error in ${tier} tier search: ${errMsg}`);
|
|
37130
|
+
tierResults.push([]);
|
|
37131
|
+
}
|
|
37132
|
+
}
|
|
37133
|
+
const interleaved = interleaveArrays(tierResults);
|
|
37134
|
+
if (interleaved.length === 0 && error48) {
|
|
37135
|
+
warn(MODULE9, `All ${tierLabelGroups.length} scope tiers failed in Phase 2: ${error48}`);
|
|
37136
|
+
}
|
|
37137
|
+
return {
|
|
37138
|
+
candidates: interleaved.slice(0, maxResults),
|
|
37139
|
+
error: error48,
|
|
37140
|
+
rateLimitHit
|
|
37141
|
+
};
|
|
37142
|
+
}
|
|
37143
|
+
async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories, maxResults, phase0RepoSet, starredRepoSet, existingCandidates, filterIssues) {
|
|
37144
|
+
info(MODULE9, "Phase 3: Searching actively maintained repos...");
|
|
37145
|
+
const thirtyDaysAgo = /* @__PURE__ */ new Date();
|
|
37146
|
+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
37147
|
+
const pushedSince = thirtyDaysAgo.toISOString().split("T")[0];
|
|
37148
|
+
const categoryTopics = getTopicsForCategories(projectCategories);
|
|
37149
|
+
const topicQuery = categoryTopics.length > 0 ? `topic:${categoryTopics[0]}` : "";
|
|
37150
|
+
const phase3Query = `is:issue is:open no:assignee ${langQuery} ${topicQuery} stars:>=${minStars} pushed:>=${pushedSince} archived:false`.replace(/ +/g, " ").trim();
|
|
37151
|
+
try {
|
|
37152
|
+
const data = await cachedSearchIssues(octokit, {
|
|
37153
|
+
q: phase3Query,
|
|
37154
|
+
sort: "updated",
|
|
37155
|
+
order: "desc",
|
|
37156
|
+
per_page: maxResults * 3
|
|
37157
|
+
});
|
|
37158
|
+
info(MODULE9, `Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`);
|
|
37159
|
+
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
37160
|
+
const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, data.items, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], maxResults, minStars, "Phase 3");
|
|
37161
|
+
info(MODULE9, `Found ${candidates.length} candidates from maintained-repo search`);
|
|
37162
|
+
return {
|
|
37163
|
+
candidates,
|
|
37164
|
+
error: allVetFailed ? "all vetting failed" : null,
|
|
37165
|
+
rateLimitHit: vetRateLimitHit
|
|
37166
|
+
};
|
|
37167
|
+
} catch (error48) {
|
|
37168
|
+
const errMsg = errorMessage(error48);
|
|
37169
|
+
warn(MODULE9, `Error in maintained-repo search: ${errMsg}`);
|
|
37170
|
+
return {
|
|
37171
|
+
candidates: [],
|
|
37172
|
+
error: errMsg,
|
|
37173
|
+
rateLimitHit: isRateLimitError(error48)
|
|
37174
|
+
};
|
|
37175
|
+
}
|
|
37176
|
+
}
|
|
36838
37177
|
var IssueDiscovery = class {
|
|
36839
37178
|
preferences;
|
|
36840
37179
|
stateReader;
|
|
@@ -36898,8 +37237,7 @@ var IssueDiscovery = class {
|
|
|
36898
37237
|
const enabledStrategies = new Set(rawStrategies.includes("all") ? ALL_STRATEGIES : rawStrategies);
|
|
36899
37238
|
const strategiesUsed = [];
|
|
36900
37239
|
const allCandidates = [];
|
|
36901
|
-
|
|
36902
|
-
let phase1Error = null;
|
|
37240
|
+
const phaseErrors = {};
|
|
36903
37241
|
let rateLimitHitDuringSearch = false;
|
|
36904
37242
|
this.rateLimitWarning = null;
|
|
36905
37243
|
const tracker = getSearchBudgetTracker();
|
|
@@ -36919,71 +37257,44 @@ var IssueDiscovery = class {
|
|
|
36919
37257
|
info(MODULE9, `Search budget low (${searchBudget} remaining) \u2014 skipping heavy phases (2, 3)`);
|
|
36920
37258
|
}
|
|
36921
37259
|
} catch (error48) {
|
|
36922
|
-
if (getHttpStatusCode(error48) === 401)
|
|
37260
|
+
if (getHttpStatusCode(error48) === 401)
|
|
36923
37261
|
throw error48;
|
|
36924
|
-
}
|
|
36925
37262
|
tracker.init(CRITICAL_BUDGET_THRESHOLD, new Date(Date.now() + 6e4).toISOString());
|
|
36926
37263
|
warn(MODULE9, "Could not check rate limit \u2014 using conservative budget, skipping heavy phases:", errorMessage(error48));
|
|
36927
37264
|
}
|
|
36928
37265
|
const mergedPRRepos = this.stateReader.getReposWithMergedPRs();
|
|
36929
37266
|
const starredRepos = this.getStarredRepos();
|
|
36930
37267
|
const starredRepoSet = new Set(starredRepos);
|
|
36931
|
-
const
|
|
36932
|
-
const lowScoringRepos = new Set(this.deriveLowScoringRepos(minRepoScoreThreshold));
|
|
36933
|
-
const excludedRepos = new Set(config2.excludeRepos);
|
|
36934
|
-
const excludeOrgs = new Set((config2.excludeOrgs ?? []).map((o) => o.toLowerCase()));
|
|
36935
|
-
const maxAgeDays = config2.maxIssueAgeDays || 90;
|
|
36936
|
-
const now = /* @__PURE__ */ new Date();
|
|
37268
|
+
const lowScoringRepos = new Set(this.deriveLowScoringRepos(config2.minRepoScoreThreshold));
|
|
36937
37269
|
const isAnyLanguage = languages.some((l) => l.toLowerCase() === "any");
|
|
36938
37270
|
const langQuery = isAnyLanguage ? "" : languages.map((l) => `language:${l}`).join(" ");
|
|
36939
37271
|
const baseQualifiers = `is:issue is:open ${langQuery} no:assignee`.replace(/ +/g, " ").trim();
|
|
36940
|
-
const includeDocIssues = config2.includeDocIssues ?? true;
|
|
36941
37272
|
const aiBlocklisted = new Set(config2.aiPolicyBlocklist);
|
|
36942
37273
|
if (aiBlocklisted.size > 0) {
|
|
36943
37274
|
debug(MODULE9, `[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`);
|
|
36944
37275
|
}
|
|
36945
|
-
const filterIssues = (
|
|
36946
|
-
|
|
36947
|
-
|
|
36948
|
-
|
|
36949
|
-
|
|
36950
|
-
|
|
36951
|
-
|
|
36952
|
-
|
|
36953
|
-
|
|
36954
|
-
}
|
|
36955
|
-
if (aiBlocklisted.has(repoFullName))
|
|
36956
|
-
return false;
|
|
36957
|
-
if (lowScoringRepos.has(repoFullName))
|
|
36958
|
-
return false;
|
|
36959
|
-
const updatedAt = new Date(item.updated_at);
|
|
36960
|
-
const ageDays = daysBetween(updatedAt, now);
|
|
36961
|
-
if (ageDays > maxAgeDays)
|
|
36962
|
-
return false;
|
|
36963
|
-
if (!includeDocIssues && isDocOnlyIssue(item))
|
|
36964
|
-
return false;
|
|
36965
|
-
return true;
|
|
36966
|
-
});
|
|
36967
|
-
};
|
|
37276
|
+
const filterIssues = buildIssueFilter({
|
|
37277
|
+
excludedRepos: new Set(config2.excludeRepos),
|
|
37278
|
+
excludeOrgs: new Set((config2.excludeOrgs ?? []).map((o) => o.toLowerCase())),
|
|
37279
|
+
aiBlocklisted,
|
|
37280
|
+
lowScoringRepos,
|
|
37281
|
+
maxAgeDays: config2.maxIssueAgeDays || 90,
|
|
37282
|
+
now: /* @__PURE__ */ new Date(),
|
|
37283
|
+
includeDocIssues: config2.includeDocIssues ?? true
|
|
37284
|
+
});
|
|
36968
37285
|
const phase0Repos = mergedPRRepos.slice(0, 10);
|
|
36969
37286
|
const phase0RepoSet = new Set(phase0Repos);
|
|
36970
37287
|
if (phase0Repos.length > 0 && enabledStrategies.has("merged")) {
|
|
36971
|
-
|
|
36972
|
-
|
|
36973
|
-
|
|
36974
|
-
|
|
36975
|
-
|
|
36976
|
-
if (
|
|
36977
|
-
phase0Error = "All merged-PR repo batches failed";
|
|
36978
|
-
}
|
|
36979
|
-
if (rateLimitHit) {
|
|
37288
|
+
const remaining = maxResults - allCandidates.length;
|
|
37289
|
+
if (remaining > 0) {
|
|
37290
|
+
const result = await runPhase0(this.octokit, this.vetter, phase0Repos, baseQualifiers, remaining, filterIssues);
|
|
37291
|
+
allCandidates.push(...result.candidates);
|
|
37292
|
+
phaseErrors["0"] = result.error;
|
|
37293
|
+
if (result.rateLimitHit)
|
|
36980
37294
|
rateLimitHitDuringSearch = true;
|
|
36981
|
-
}
|
|
36982
|
-
info(MODULE9, `Found ${mergedCandidates.length} candidates from merged-PR repos`);
|
|
36983
37295
|
}
|
|
36984
37296
|
strategiesUsed.push("merged");
|
|
36985
37297
|
}
|
|
36986
|
-
let phase0_5Error = null;
|
|
36987
37298
|
const preferredOrgs = config2.preferredOrgs ?? [];
|
|
36988
37299
|
if (allCandidates.length < maxResults && preferredOrgs.length > 0 && searchBudget >= CRITICAL_BUDGET_THRESHOLD && enabledStrategies.has("orgs")) {
|
|
36989
37300
|
if (phase0Repos.length > 0)
|
|
@@ -36991,35 +37302,12 @@ var IssueDiscovery = class {
|
|
|
36991
37302
|
const phase0Orgs = new Set(phase0Repos.map((r) => r.split("/")[0]?.toLowerCase()));
|
|
36992
37303
|
const orgsToSearch = preferredOrgs.filter((org) => !phase0Orgs.has(org.toLowerCase())).slice(0, 5);
|
|
36993
37304
|
if (orgsToSearch.length > 0) {
|
|
36994
|
-
|
|
36995
|
-
const
|
|
36996
|
-
|
|
36997
|
-
|
|
36998
|
-
|
|
36999
|
-
|
|
37000
|
-
if (allItems.length > 0) {
|
|
37001
|
-
const filtered = filterIssues(allItems).filter((item) => {
|
|
37002
|
-
const repoFullName = item.repository_url.split("/").slice(-2).join("/");
|
|
37003
|
-
return !phase0RepoSet.has(repoFullName);
|
|
37004
|
-
});
|
|
37005
|
-
const { candidates: orgCandidates, allFailed: allVetFailed, rateLimitHit } = await this.vetter.vetIssuesParallel(filtered.slice(0, remainingNeeded * 2).map((i) => i.html_url), remainingNeeded, "preferred_org");
|
|
37006
|
-
allCandidates.push(...orgCandidates);
|
|
37007
|
-
if (allVetFailed) {
|
|
37008
|
-
phase0_5Error = "All preferred org issue vetting failed";
|
|
37009
|
-
}
|
|
37010
|
-
if (rateLimitHit) {
|
|
37011
|
-
rateLimitHitDuringSearch = true;
|
|
37012
|
-
}
|
|
37013
|
-
info(MODULE9, `Found ${orgCandidates.length} candidates from preferred orgs`);
|
|
37014
|
-
}
|
|
37015
|
-
} catch (error48) {
|
|
37016
|
-
const errMsg = errorMessage(error48);
|
|
37017
|
-
phase0_5Error = errMsg;
|
|
37018
|
-
if (isRateLimitError(error48)) {
|
|
37019
|
-
rateLimitHitDuringSearch = true;
|
|
37020
|
-
}
|
|
37021
|
-
warn(MODULE9, `Error searching preferred orgs: ${errMsg}`);
|
|
37022
|
-
}
|
|
37305
|
+
const remaining = maxResults - allCandidates.length;
|
|
37306
|
+
const result = await runPhase05(this.octokit, this.vetter, orgsToSearch, baseQualifiers, labels, remaining, phase0RepoSet, filterIssues);
|
|
37307
|
+
allCandidates.push(...result.candidates);
|
|
37308
|
+
phaseErrors["0.5"] = result.error;
|
|
37309
|
+
if (result.rateLimitHit)
|
|
37310
|
+
rateLimitHitDuringSearch = true;
|
|
37023
37311
|
}
|
|
37024
37312
|
strategiesUsed.push("orgs");
|
|
37025
37313
|
}
|
|
@@ -37027,127 +37315,35 @@ var IssueDiscovery = class {
|
|
|
37027
37315
|
await sleep(INTER_PHASE_DELAY_MS);
|
|
37028
37316
|
const reposToSearch = starredRepos.filter((r) => !phase0RepoSet.has(r));
|
|
37029
37317
|
if (reposToSearch.length > 0) {
|
|
37030
|
-
|
|
37031
|
-
|
|
37032
|
-
|
|
37033
|
-
|
|
37034
|
-
|
|
37035
|
-
|
|
37036
|
-
if (allBatchesFailed) {
|
|
37037
|
-
phase1Error = "All starred repo batches failed";
|
|
37038
|
-
}
|
|
37039
|
-
if (rateLimitHit) {
|
|
37318
|
+
const remaining = maxResults - allCandidates.length;
|
|
37319
|
+
if (remaining > 0) {
|
|
37320
|
+
const result = await runPhase1(this.octokit, this.vetter, reposToSearch, baseQualifiers, labels, remaining, filterIssues);
|
|
37321
|
+
allCandidates.push(...result.candidates);
|
|
37322
|
+
phaseErrors["1"] = result.error;
|
|
37323
|
+
if (result.rateLimitHit)
|
|
37040
37324
|
rateLimitHitDuringSearch = true;
|
|
37041
|
-
}
|
|
37042
|
-
info(MODULE9, `Found ${starredCandidates.length} candidates from starred repos`);
|
|
37043
37325
|
}
|
|
37044
37326
|
}
|
|
37045
37327
|
strategiesUsed.push("starred");
|
|
37046
37328
|
}
|
|
37047
|
-
let phase2Error = null;
|
|
37048
37329
|
if (allCandidates.length < maxResults && searchBudget >= LOW_BUDGET_THRESHOLD && enabledStrategies.has("broad")) {
|
|
37049
37330
|
await sleep(INTER_PHASE_DELAY_MS);
|
|
37050
|
-
|
|
37051
|
-
const
|
|
37052
|
-
|
|
37053
|
-
|
|
37054
|
-
if (
|
|
37055
|
-
|
|
37056
|
-
const scopeLabels = SCOPE_LABELS[scope] ?? [];
|
|
37057
|
-
if (scopeLabels.length === 0) {
|
|
37058
|
-
warn(MODULE9, `Scope "${scope}" has no labels, skipping tier`);
|
|
37059
|
-
continue;
|
|
37060
|
-
}
|
|
37061
|
-
tierLabelGroups.push({ tier: scope, tierLabels: scopeLabels });
|
|
37062
|
-
}
|
|
37063
|
-
const allScopeLabels = new Set(scopes.flatMap((s) => SCOPE_LABELS[s] ?? []));
|
|
37064
|
-
const customOnly = config2.labels.filter((l) => !allScopeLabels.has(l));
|
|
37065
|
-
if (customOnly.length > 0) {
|
|
37066
|
-
tierLabelGroups.push({ tier: "custom", tierLabels: customOnly });
|
|
37067
|
-
}
|
|
37068
|
-
} else {
|
|
37069
|
-
tierLabelGroups.push({ tier: "general", tierLabels: labels });
|
|
37070
|
-
}
|
|
37071
|
-
const budgetPerTier = Math.ceil(remainingNeeded / tierLabelGroups.length);
|
|
37072
|
-
const tierResults = [];
|
|
37073
|
-
for (const { tier, tierLabels } of tierLabelGroups) {
|
|
37074
|
-
try {
|
|
37075
|
-
const allItems = await searchWithChunkedLabels(
|
|
37076
|
-
this.octokit,
|
|
37077
|
-
tierLabels,
|
|
37078
|
-
0,
|
|
37079
|
-
// no repo/org ORs in Phase 2
|
|
37080
|
-
(labelQ) => `${baseQualifiers} ${labelQ}`.replace(/ +/g, " ").trim(),
|
|
37081
|
-
budgetPerTier * 3
|
|
37082
|
-
);
|
|
37083
|
-
info(MODULE9, `Phase 2 [${tier}]: processing ${allItems.length} items...`);
|
|
37084
|
-
const { candidates: tierCandidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(this.vetter, allItems, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], budgetPerTier, minStars, `Phase 2 [${tier}]`);
|
|
37085
|
-
tierResults.push(tierCandidates);
|
|
37086
|
-
for (const c of tierCandidates)
|
|
37087
|
-
seenRepos.add(c.issue.repo);
|
|
37088
|
-
if (allVetFailed) {
|
|
37089
|
-
phase2Error = (phase2Error ? phase2Error + "; " : "") + `${tier}: all vetting failed`;
|
|
37090
|
-
}
|
|
37091
|
-
if (vetRateLimitHit) {
|
|
37092
|
-
rateLimitHitDuringSearch = true;
|
|
37093
|
-
}
|
|
37094
|
-
info(MODULE9, `Found ${tierCandidates.length} candidates from ${tier} tier`);
|
|
37095
|
-
} catch (error48) {
|
|
37096
|
-
if (getHttpStatusCode(error48) === 401)
|
|
37097
|
-
throw error48;
|
|
37098
|
-
const errMsg = errorMessage(error48);
|
|
37099
|
-
phase2Error = (phase2Error ? phase2Error + "; " : "") + `${tier}: ${errMsg}`;
|
|
37100
|
-
if (isRateLimitError(error48)) {
|
|
37101
|
-
rateLimitHitDuringSearch = true;
|
|
37102
|
-
}
|
|
37103
|
-
warn(MODULE9, `Error in ${tier} tier search: ${errMsg}`);
|
|
37104
|
-
tierResults.push([]);
|
|
37105
|
-
}
|
|
37106
|
-
}
|
|
37107
|
-
const interleaved = interleaveArrays(tierResults);
|
|
37108
|
-
if (interleaved.length === 0 && phase2Error) {
|
|
37109
|
-
warn(MODULE9, `All ${tierLabelGroups.length} scope tiers failed in Phase 2: ${phase2Error}`);
|
|
37110
|
-
}
|
|
37111
|
-
allCandidates.push(...interleaved.slice(0, remainingNeeded));
|
|
37331
|
+
const remaining = maxResults - allCandidates.length;
|
|
37332
|
+
const result = await runPhase2(this.octokit, this.vetter, scopes, labels, config2.labels, baseQualifiers, remaining, minStars, phase0RepoSet, starredRepoSet, allCandidates, filterIssues);
|
|
37333
|
+
allCandidates.push(...result.candidates);
|
|
37334
|
+
phaseErrors["2"] = result.error;
|
|
37335
|
+
if (result.rateLimitHit)
|
|
37336
|
+
rateLimitHitDuringSearch = true;
|
|
37112
37337
|
strategiesUsed.push("broad");
|
|
37113
37338
|
}
|
|
37114
|
-
let phase3Error = null;
|
|
37115
37339
|
if (allCandidates.length < maxResults && searchBudget >= LOW_BUDGET_THRESHOLD && enabledStrategies.has("maintained")) {
|
|
37116
37340
|
await sleep(INTER_PHASE_DELAY_MS);
|
|
37117
|
-
|
|
37118
|
-
const
|
|
37119
|
-
|
|
37120
|
-
|
|
37121
|
-
|
|
37122
|
-
|
|
37123
|
-
const topicQuery = categoryTopics.length > 0 ? `topic:${categoryTopics[0]}` : "";
|
|
37124
|
-
const phase3Query = `is:issue is:open no:assignee ${langQuery} ${topicQuery} stars:>=${minStars} pushed:>=${pushedSince} archived:false`.replace(/ +/g, " ").trim();
|
|
37125
|
-
try {
|
|
37126
|
-
const data = await cachedSearchIssues(this.octokit, {
|
|
37127
|
-
q: phase3Query,
|
|
37128
|
-
sort: "updated",
|
|
37129
|
-
order: "desc",
|
|
37130
|
-
per_page: remainingNeeded * 3
|
|
37131
|
-
});
|
|
37132
|
-
info(MODULE9, `Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`);
|
|
37133
|
-
const seenRepos = new Set(allCandidates.map((c) => c.issue.repo));
|
|
37134
|
-
const { candidates: starFiltered, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(this.vetter, data.items, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], remainingNeeded, minStars, "Phase 3");
|
|
37135
|
-
allCandidates.push(...starFiltered);
|
|
37136
|
-
if (allVetFailed) {
|
|
37137
|
-
phase3Error = "all vetting failed";
|
|
37138
|
-
}
|
|
37139
|
-
if (vetRateLimitHit) {
|
|
37140
|
-
rateLimitHitDuringSearch = true;
|
|
37141
|
-
}
|
|
37142
|
-
info(MODULE9, `Found ${starFiltered.length} candidates from maintained-repo search`);
|
|
37143
|
-
} catch (error48) {
|
|
37144
|
-
const errMsg = errorMessage(error48);
|
|
37145
|
-
phase3Error = errMsg;
|
|
37146
|
-
if (isRateLimitError(error48)) {
|
|
37147
|
-
rateLimitHitDuringSearch = true;
|
|
37148
|
-
}
|
|
37149
|
-
warn(MODULE9, `Error in maintained-repo search: ${errMsg}`);
|
|
37150
|
-
}
|
|
37341
|
+
const remaining = maxResults - allCandidates.length;
|
|
37342
|
+
const result = await runPhase3(this.octokit, this.vetter, langQuery, minStars, config2.projectCategories ?? [], remaining, phase0RepoSet, starredRepoSet, allCandidates, filterIssues);
|
|
37343
|
+
allCandidates.push(...result.candidates);
|
|
37344
|
+
phaseErrors["3"] = result.error;
|
|
37345
|
+
if (result.rateLimitHit)
|
|
37346
|
+
rateLimitHitDuringSearch = true;
|
|
37151
37347
|
strategiesUsed.push("maintained");
|
|
37152
37348
|
}
|
|
37153
37349
|
const phasesSkippedForBudget = searchBudget < LOW_BUDGET_THRESHOLD;
|
|
@@ -37158,14 +37354,14 @@ var IssueDiscovery = class {
|
|
|
37158
37354
|
budgetNote = ` Some search phases were skipped due to low API quota (${searchBudget} remaining).`;
|
|
37159
37355
|
}
|
|
37160
37356
|
if (allCandidates.length === 0) {
|
|
37161
|
-
const
|
|
37162
|
-
|
|
37163
|
-
|
|
37164
|
-
|
|
37165
|
-
|
|
37166
|
-
|
|
37357
|
+
const errorDetails = [
|
|
37358
|
+
phaseErrors["0"] ? `Phase 0 (merged-PR repos): ${phaseErrors["0"]}` : null,
|
|
37359
|
+
phaseErrors["0.5"] ? `Phase 0.5 (preferred orgs): ${phaseErrors["0.5"]}` : null,
|
|
37360
|
+
phaseErrors["1"] ? `Phase 1 (starred repos): ${phaseErrors["1"]}` : null,
|
|
37361
|
+
phaseErrors["2"] ? `Phase 2 (general): ${phaseErrors["2"]}` : null,
|
|
37362
|
+
phaseErrors["3"] ? `Phase 3 (maintained repos): ${phaseErrors["3"]}` : null
|
|
37167
37363
|
].filter(Boolean);
|
|
37168
|
-
const details =
|
|
37364
|
+
const details = errorDetails.length > 0 ? ` ${errorDetails.join(". ")}.` : "";
|
|
37169
37365
|
if (rateLimitHitDuringSearch || phasesSkippedForBudget) {
|
|
37170
37366
|
this.rateLimitWarning = `Search returned no results due to GitHub API rate limits.${details}${budgetNote} Try again after the rate limit resets.`;
|
|
37171
37367
|
return { candidates: [], strategiesUsed };
|
|
@@ -37176,7 +37372,12 @@ var IssueDiscovery = class {
|
|
|
37176
37372
|
this.rateLimitWarning = `Search results may be incomplete: GitHub API rate limits were hit during search.${budgetNote} Found ${allCandidates.length} candidate${allCandidates.length === 1 ? "" : "s"} but some search phases were limited. Try again after the rate limit resets for complete results.`;
|
|
37177
37373
|
}
|
|
37178
37374
|
allCandidates.sort((a, b) => {
|
|
37179
|
-
const priorityOrder = {
|
|
37375
|
+
const priorityOrder = {
|
|
37376
|
+
merged_pr: 0,
|
|
37377
|
+
preferred_org: 1,
|
|
37378
|
+
starred: 2,
|
|
37379
|
+
normal: 3
|
|
37380
|
+
};
|
|
37180
37381
|
const priorityDiff = priorityOrder[a.searchPriority] - priorityOrder[b.searchPriority];
|
|
37181
37382
|
if (priorityDiff !== 0)
|
|
37182
37383
|
return priorityDiff;
|
|
@@ -37323,6 +37524,8 @@ var GistStateStore = class {
|
|
|
37323
37524
|
this.writeCache(state);
|
|
37324
37525
|
return { gistId: foundId, state, created: false };
|
|
37325
37526
|
}
|
|
37527
|
+
warn(MODULE10, `Found existing gist ${foundId} but content failed validation. Using local cache to avoid data loss.`);
|
|
37528
|
+
return this.bootstrapFromCache();
|
|
37326
37529
|
}
|
|
37327
37530
|
debug(MODULE10, "No existing gist found, creating new one");
|
|
37328
37531
|
const freshState = ScoutStateSchema.parse({ version: 1 });
|
|
@@ -37418,7 +37621,9 @@ var GistStateStore = class {
|
|
|
37418
37621
|
}
|
|
37419
37622
|
writeCache(state) {
|
|
37420
37623
|
try {
|
|
37421
|
-
fs3.writeFileSync(getCachePath(), JSON.stringify(state, null, 2) + "\n", {
|
|
37624
|
+
fs3.writeFileSync(getCachePath(), JSON.stringify(state, null, 2) + "\n", {
|
|
37625
|
+
mode: 384
|
|
37626
|
+
});
|
|
37422
37627
|
} catch (err) {
|
|
37423
37628
|
warn(MODULE10, `Failed to write cache: ${errorMessage(err)}`);
|
|
37424
37629
|
}
|
|
@@ -37521,13 +37726,50 @@ function loadLocalState() {
|
|
|
37521
37726
|
}
|
|
37522
37727
|
|
|
37523
37728
|
// ../core/dist/scout.js
|
|
37729
|
+
function toGistOctokit(octokit) {
|
|
37730
|
+
return {
|
|
37731
|
+
gists: {
|
|
37732
|
+
async get(params) {
|
|
37733
|
+
const { data } = await octokit.gists.get(params);
|
|
37734
|
+
if (!data.id)
|
|
37735
|
+
throw new Error("Gist get returned no id");
|
|
37736
|
+
const files = data.files ? Object.fromEntries(Object.entries(data.files).map(([k, v]) => [
|
|
37737
|
+
k,
|
|
37738
|
+
v ? { content: v.content } : void 0
|
|
37739
|
+
])) : null;
|
|
37740
|
+
return { data: { id: data.id, files } };
|
|
37741
|
+
},
|
|
37742
|
+
async create(params) {
|
|
37743
|
+
const { data } = await octokit.gists.create(params);
|
|
37744
|
+
if (!data.id)
|
|
37745
|
+
throw new Error("Gist create returned no id");
|
|
37746
|
+
return { data: { id: data.id } };
|
|
37747
|
+
},
|
|
37748
|
+
async update(params) {
|
|
37749
|
+
const { data } = await octokit.gists.update(params);
|
|
37750
|
+
if (!data.id)
|
|
37751
|
+
throw new Error("Gist update returned no id");
|
|
37752
|
+
return { data: { id: data.id } };
|
|
37753
|
+
},
|
|
37754
|
+
async list(params) {
|
|
37755
|
+
const { data } = await octokit.gists.list(params);
|
|
37756
|
+
return {
|
|
37757
|
+
data: data.filter((g) => g.id).map((g) => ({
|
|
37758
|
+
id: g.id,
|
|
37759
|
+
description: g.description ?? null
|
|
37760
|
+
}))
|
|
37761
|
+
};
|
|
37762
|
+
}
|
|
37763
|
+
}
|
|
37764
|
+
};
|
|
37765
|
+
}
|
|
37524
37766
|
async function createScout(config2) {
|
|
37525
37767
|
let state;
|
|
37526
37768
|
let gistStore = null;
|
|
37527
37769
|
if (config2.persistence === "provided") {
|
|
37528
37770
|
state = config2.initialState;
|
|
37529
37771
|
} else if (config2.persistence === "gist") {
|
|
37530
|
-
gistStore = new GistStateStore(getOctokit(config2.githubToken));
|
|
37772
|
+
gistStore = new GistStateStore(toGistOctokit(getOctokit(config2.githubToken)));
|
|
37531
37773
|
const result = await gistStore.bootstrap();
|
|
37532
37774
|
if (result.degraded) {
|
|
37533
37775
|
warn("scout", "Gist sync unavailable \u2014 running in offline mode. Changes will only be saved locally.");
|
|
@@ -37653,7 +37895,7 @@ var OssScout = class {
|
|
|
37653
37895
|
getReposWithMergedPRs() {
|
|
37654
37896
|
const repoCounts = /* @__PURE__ */ new Map();
|
|
37655
37897
|
for (const pr of this.state.mergedPRs ?? []) {
|
|
37656
|
-
const repo =
|
|
37898
|
+
const repo = extractRepoFromUrl(pr.url);
|
|
37657
37899
|
if (repo) {
|
|
37658
37900
|
repoCounts.set(repo, (repoCounts.get(repo) ?? 0) + 1);
|
|
37659
37901
|
}
|
|
@@ -37826,17 +38068,13 @@ var OssScout = class {
|
|
|
37826
38068
|
return this.state;
|
|
37827
38069
|
}
|
|
37828
38070
|
// ── Private helpers ─────────────────────────────────────────────────
|
|
37829
|
-
extractRepoFromUrl(url2) {
|
|
37830
|
-
const match = url2.match(/github\.com\/([^/]+\/[^/]+)\//);
|
|
37831
|
-
return match ? match[1] : null;
|
|
37832
|
-
}
|
|
37833
38071
|
updateRepoScoreFromPRs(repo) {
|
|
37834
|
-
const mergedCount = (this.state.mergedPRs ?? []).filter((p) =>
|
|
37835
|
-
const closedCount = (this.state.closedPRs ?? []).filter((p) =>
|
|
38072
|
+
const mergedCount = (this.state.mergedPRs ?? []).filter((p) => extractRepoFromUrl(p.url) === repo).length;
|
|
38073
|
+
const closedCount = (this.state.closedPRs ?? []).filter((p) => extractRepoFromUrl(p.url) === repo).length;
|
|
37836
38074
|
this.updateRepoScore(repo, {
|
|
37837
38075
|
mergedPRCount: mergedCount,
|
|
37838
38076
|
closedWithoutMergeCount: closedCount,
|
|
37839
|
-
lastMergedAt: mergedCount > 0 ? (this.state.mergedPRs ?? []).filter((p) =>
|
|
38077
|
+
lastMergedAt: mergedCount > 0 ? (this.state.mergedPRs ?? []).filter((p) => extractRepoFromUrl(p.url) === repo).sort((a, b) => b.mergedAt.localeCompare(a.mergedAt))[0]?.mergedAt : void 0
|
|
37840
38078
|
});
|
|
37841
38079
|
}
|
|
37842
38080
|
/**
|
|
@@ -37865,26 +38103,52 @@ function registerTools(server, scout) {
|
|
|
37865
38103
|
"Search for open source issues matching your preferences",
|
|
37866
38104
|
{
|
|
37867
38105
|
maxResults: external_exports3.number().optional().describe("Maximum number of results to return (default 10)"),
|
|
37868
|
-
strategies: external_exports3.string().optional().describe(
|
|
38106
|
+
strategies: external_exports3.string().optional().describe(
|
|
38107
|
+
"Comma-separated search strategies: merged, orgs, starred, broad, maintained, all"
|
|
38108
|
+
)
|
|
37869
38109
|
},
|
|
37870
38110
|
async ({ maxResults, strategies }) => {
|
|
37871
|
-
|
|
37872
|
-
|
|
37873
|
-
|
|
37874
|
-
|
|
37875
|
-
|
|
37876
|
-
|
|
38111
|
+
try {
|
|
38112
|
+
const parsedStrategies = strategies ? strategies.split(",").map((s) => SearchStrategySchema.parse(s.trim())) : void 0;
|
|
38113
|
+
const result = await scout.search({
|
|
38114
|
+
maxResults: maxResults ?? 10,
|
|
38115
|
+
strategies: parsedStrategies
|
|
38116
|
+
});
|
|
38117
|
+
scout.saveResults(result.candidates);
|
|
38118
|
+
await scout.checkpoint();
|
|
38119
|
+
return {
|
|
38120
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
38121
|
+
};
|
|
38122
|
+
} catch (err) {
|
|
38123
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38124
|
+
return {
|
|
38125
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
38126
|
+
isError: true
|
|
38127
|
+
};
|
|
38128
|
+
}
|
|
37877
38129
|
}
|
|
37878
38130
|
);
|
|
37879
38131
|
server.tool(
|
|
37880
38132
|
"vet",
|
|
37881
38133
|
"Vet a specific GitHub issue for contribution viability",
|
|
37882
38134
|
{
|
|
37883
|
-
issueUrl: external_exports3.string().describe(
|
|
38135
|
+
issueUrl: external_exports3.string().describe(
|
|
38136
|
+
"Full GitHub issue URL (e.g. https://github.com/owner/repo/issues/123)"
|
|
38137
|
+
)
|
|
37884
38138
|
},
|
|
37885
38139
|
async ({ issueUrl }) => {
|
|
37886
|
-
|
|
37887
|
-
|
|
38140
|
+
try {
|
|
38141
|
+
const candidate = await scout.vetIssue(issueUrl);
|
|
38142
|
+
return {
|
|
38143
|
+
content: [{ type: "text", text: JSON.stringify(candidate, null, 2) }]
|
|
38144
|
+
};
|
|
38145
|
+
} catch (err) {
|
|
38146
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38147
|
+
return {
|
|
38148
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
38149
|
+
isError: true
|
|
38150
|
+
};
|
|
38151
|
+
}
|
|
37888
38152
|
}
|
|
37889
38153
|
);
|
|
37890
38154
|
server.tool(
|
|
@@ -37892,7 +38156,9 @@ function registerTools(server, scout) {
|
|
|
37892
38156
|
"Show current oss-scout configuration preferences",
|
|
37893
38157
|
async () => {
|
|
37894
38158
|
const preferences = scout.getPreferences();
|
|
37895
|
-
return {
|
|
38159
|
+
return {
|
|
38160
|
+
content: [{ type: "text", text: JSON.stringify(preferences, null, 2) }]
|
|
38161
|
+
};
|
|
37896
38162
|
}
|
|
37897
38163
|
);
|
|
37898
38164
|
const VALID_KEYS = Object.keys(ScoutPreferencesSchema.shape);
|
|
@@ -37902,23 +38168,37 @@ function registerTools(server, scout) {
|
|
|
37902
38168
|
"preferredOrgs",
|
|
37903
38169
|
"projectCategories",
|
|
37904
38170
|
"excludeRepos",
|
|
38171
|
+
"excludeOrgs",
|
|
37905
38172
|
"aiPolicyBlocklist",
|
|
37906
38173
|
"scope",
|
|
37907
38174
|
"defaultStrategy"
|
|
37908
38175
|
]);
|
|
37909
|
-
const NUMBER_KEYS = /* @__PURE__ */ new Set([
|
|
38176
|
+
const NUMBER_KEYS = /* @__PURE__ */ new Set([
|
|
38177
|
+
"minStars",
|
|
38178
|
+
"maxIssueAgeDays",
|
|
38179
|
+
"minRepoScoreThreshold"
|
|
38180
|
+
]);
|
|
37910
38181
|
const BOOLEAN_KEYS = /* @__PURE__ */ new Set(["includeDocIssues"]);
|
|
37911
38182
|
server.tool(
|
|
37912
38183
|
"config-set",
|
|
37913
38184
|
"Update an oss-scout configuration preference",
|
|
37914
38185
|
{
|
|
37915
|
-
key: external_exports3.string().describe(
|
|
37916
|
-
|
|
38186
|
+
key: external_exports3.string().describe(
|
|
38187
|
+
"Preference key to update (e.g. languages, minStars, excludeRepos)"
|
|
38188
|
+
),
|
|
38189
|
+
value: external_exports3.string().describe(
|
|
38190
|
+
"New value \u2014 comma-separated for arrays, plain string for scalars"
|
|
38191
|
+
)
|
|
37917
38192
|
},
|
|
37918
38193
|
async ({ key, value }) => {
|
|
37919
38194
|
if (!VALID_KEYS.includes(key)) {
|
|
37920
38195
|
return {
|
|
37921
|
-
content: [
|
|
38196
|
+
content: [
|
|
38197
|
+
{
|
|
38198
|
+
type: "text",
|
|
38199
|
+
text: `Unknown config key: "${key}". Valid keys: ${VALID_KEYS.join(", ")}`
|
|
38200
|
+
}
|
|
38201
|
+
],
|
|
37922
38202
|
isError: true
|
|
37923
38203
|
};
|
|
37924
38204
|
}
|
|
@@ -37936,7 +38216,9 @@ function registerTools(server, scout) {
|
|
|
37936
38216
|
const num = Number(value);
|
|
37937
38217
|
if (isNaN(num)) {
|
|
37938
38218
|
return {
|
|
37939
|
-
content: [
|
|
38219
|
+
content: [
|
|
38220
|
+
{ type: "text", text: `Invalid number for "${key}": "${value}"` }
|
|
38221
|
+
],
|
|
37940
38222
|
isError: true
|
|
37941
38223
|
};
|
|
37942
38224
|
}
|
|
@@ -37947,7 +38229,12 @@ function registerTools(server, scout) {
|
|
|
37947
38229
|
else if (lower === "false" || lower === "no") parsed = false;
|
|
37948
38230
|
else {
|
|
37949
38231
|
return {
|
|
37950
|
-
content: [
|
|
38232
|
+
content: [
|
|
38233
|
+
{
|
|
38234
|
+
type: "text",
|
|
38235
|
+
text: `Invalid boolean for "${key}": "${value}". Use true/false or yes/no.`
|
|
38236
|
+
}
|
|
38237
|
+
],
|
|
37951
38238
|
isError: true
|
|
37952
38239
|
};
|
|
37953
38240
|
}
|
|
@@ -37965,8 +38252,11 @@ function registerTools(server, scout) {
|
|
|
37965
38252
|
};
|
|
37966
38253
|
}
|
|
37967
38254
|
scout.updatePreferences({ [key]: parsed });
|
|
38255
|
+
await scout.checkpoint();
|
|
37968
38256
|
const updated = scout.getPreferences();
|
|
37969
|
-
return {
|
|
38257
|
+
return {
|
|
38258
|
+
content: [{ type: "text", text: JSON.stringify(updated, null, 2) }]
|
|
38259
|
+
};
|
|
37970
38260
|
}
|
|
37971
38261
|
);
|
|
37972
38262
|
}
|
|
@@ -37978,11 +38268,13 @@ function registerResources(server, scout) {
|
|
|
37978
38268
|
"scout://config",
|
|
37979
38269
|
{ description: "Current oss-scout preferences" },
|
|
37980
38270
|
async () => ({
|
|
37981
|
-
contents: [
|
|
37982
|
-
|
|
37983
|
-
|
|
37984
|
-
|
|
37985
|
-
|
|
38271
|
+
contents: [
|
|
38272
|
+
{
|
|
38273
|
+
uri: "scout://config",
|
|
38274
|
+
mimeType: "application/json",
|
|
38275
|
+
text: JSON.stringify(scout.getPreferences(), null, 2)
|
|
38276
|
+
}
|
|
38277
|
+
]
|
|
37986
38278
|
})
|
|
37987
38279
|
);
|
|
37988
38280
|
server.resource(
|
|
@@ -37990,11 +38282,13 @@ function registerResources(server, scout) {
|
|
|
37990
38282
|
"scout://results",
|
|
37991
38283
|
{ description: "Saved search results from previous runs" },
|
|
37992
38284
|
async () => ({
|
|
37993
|
-
contents: [
|
|
37994
|
-
|
|
37995
|
-
|
|
37996
|
-
|
|
37997
|
-
|
|
38285
|
+
contents: [
|
|
38286
|
+
{
|
|
38287
|
+
uri: "scout://results",
|
|
38288
|
+
mimeType: "application/json",
|
|
38289
|
+
text: JSON.stringify(scout.getSavedResults(), null, 2)
|
|
38290
|
+
}
|
|
38291
|
+
]
|
|
37998
38292
|
})
|
|
37999
38293
|
);
|
|
38000
38294
|
server.resource(
|
|
@@ -38004,11 +38298,13 @@ function registerResources(server, scout) {
|
|
|
38004
38298
|
async () => {
|
|
38005
38299
|
const state = scout.getState();
|
|
38006
38300
|
return {
|
|
38007
|
-
contents: [
|
|
38008
|
-
|
|
38009
|
-
|
|
38010
|
-
|
|
38011
|
-
|
|
38301
|
+
contents: [
|
|
38302
|
+
{
|
|
38303
|
+
uri: "scout://scores",
|
|
38304
|
+
mimeType: "application/json",
|
|
38305
|
+
text: JSON.stringify(state.repoScores, null, 2)
|
|
38306
|
+
}
|
|
38307
|
+
]
|
|
38012
38308
|
};
|
|
38013
38309
|
}
|
|
38014
38310
|
);
|
|
@@ -38034,8 +38330,10 @@ async function main() {
|
|
|
38034
38330
|
await server.connect(transport);
|
|
38035
38331
|
}
|
|
38036
38332
|
main().catch((err) => {
|
|
38037
|
-
process.stderr.write(
|
|
38038
|
-
`)
|
|
38333
|
+
process.stderr.write(
|
|
38334
|
+
`oss-scout-mcp fatal: ${err instanceof Error ? err.message : String(err)}
|
|
38335
|
+
`
|
|
38336
|
+
);
|
|
38039
38337
|
process.exit(1);
|
|
38040
38338
|
});
|
|
38041
38339
|
/*! Bundled license information:
|