@oss-scout/mcp 0.1.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 +564 -259
- 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(),
|
|
@@ -35762,6 +35805,7 @@ var ScoutPreferencesSchema = external_exports3.object({
|
|
|
35762
35805
|
labels: external_exports3.array(external_exports3.string()).default(["good first issue", "help wanted"]),
|
|
35763
35806
|
scope: external_exports3.array(IssueScopeSchema).optional(),
|
|
35764
35807
|
excludeRepos: external_exports3.array(external_exports3.string()).default([]),
|
|
35808
|
+
excludeOrgs: external_exports3.array(external_exports3.string()).default([]),
|
|
35765
35809
|
aiPolicyBlocklist: external_exports3.array(external_exports3.string()).default(["matplotlib/matplotlib"]),
|
|
35766
35810
|
preferredOrgs: external_exports3.array(external_exports3.string()).default([]),
|
|
35767
35811
|
projectCategories: external_exports3.array(ProjectCategorySchema).default([]),
|
|
@@ -35787,7 +35831,12 @@ var ScoutStateSchema = external_exports3.object({
|
|
|
35787
35831
|
});
|
|
35788
35832
|
|
|
35789
35833
|
// ../core/dist/core/issue-filtering.js
|
|
35790
|
-
var DOC_ONLY_LABELS = /* @__PURE__ */ new Set([
|
|
35834
|
+
var DOC_ONLY_LABELS = /* @__PURE__ */ new Set([
|
|
35835
|
+
"documentation",
|
|
35836
|
+
"docs",
|
|
35837
|
+
"typo",
|
|
35838
|
+
"spelling"
|
|
35839
|
+
]);
|
|
35791
35840
|
function isDocOnlyIssue(item) {
|
|
35792
35841
|
if (!item.labels || !Array.isArray(item.labels) || item.labels.length === 0)
|
|
35793
35842
|
return false;
|
|
@@ -35826,7 +35875,9 @@ function detectLabelFarmingRepos(items) {
|
|
|
35826
35875
|
const spamRepos = /* @__PURE__ */ new Set();
|
|
35827
35876
|
const repoSpamCounts = /* @__PURE__ */ new Map();
|
|
35828
35877
|
for (const item of items) {
|
|
35829
|
-
const repoFullName = item.repository_url
|
|
35878
|
+
const repoFullName = extractRepoFromUrl(item.repository_url);
|
|
35879
|
+
if (!repoFullName)
|
|
35880
|
+
continue;
|
|
35830
35881
|
if (isLabelFarming(item)) {
|
|
35831
35882
|
spamRepos.add(repoFullName);
|
|
35832
35883
|
continue;
|
|
@@ -35912,20 +35963,111 @@ function calculateViabilityScore(params) {
|
|
|
35912
35963
|
|
|
35913
35964
|
// ../core/dist/core/category-mapping.js
|
|
35914
35965
|
var CATEGORY_TOPICS = {
|
|
35915
|
-
nonprofit: [
|
|
35916
|
-
|
|
35917
|
-
|
|
35918
|
-
|
|
35919
|
-
|
|
35920
|
-
|
|
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
|
+
]
|
|
35921
36017
|
};
|
|
35922
36018
|
var CATEGORY_ORGS = {
|
|
35923
|
-
nonprofit: [
|
|
35924
|
-
|
|
35925
|
-
|
|
35926
|
-
|
|
35927
|
-
|
|
35928
|
-
|
|
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
|
+
]
|
|
35929
36071
|
};
|
|
35930
36072
|
function repoBelongsToCategory(repoFullName, categories) {
|
|
35931
36073
|
if (categories.length === 0)
|
|
@@ -36045,7 +36187,10 @@ var HttpCache = class {
|
|
|
36045
36187
|
cachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
36046
36188
|
};
|
|
36047
36189
|
try {
|
|
36048
|
-
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
|
+
});
|
|
36049
36194
|
debug(MODULE4, `Cached response for ${url2}`);
|
|
36050
36195
|
} catch (err) {
|
|
36051
36196
|
warn(MODULE4, `Failed to write cache for ${url2}: ${errorMessage(err)}`);
|
|
@@ -36300,7 +36445,12 @@ function analyzeRequirements(body) {
|
|
|
36300
36445
|
const hasSteps = /\d\.|[-*]\s/.test(body);
|
|
36301
36446
|
const hasCodeBlock = /```/.test(body);
|
|
36302
36447
|
const hasExpectedBehavior = /expect|should|must|want/i.test(body);
|
|
36303
|
-
const indicators = [
|
|
36448
|
+
const indicators = [
|
|
36449
|
+
hasSteps,
|
|
36450
|
+
hasCodeBlock,
|
|
36451
|
+
hasExpectedBehavior,
|
|
36452
|
+
body.length > 200
|
|
36453
|
+
];
|
|
36304
36454
|
return indicators.filter(Boolean).length >= 2;
|
|
36305
36455
|
}
|
|
36306
36456
|
|
|
@@ -36377,7 +36527,12 @@ async function fetchContributionGuidelines(octokit, owner, repo) {
|
|
|
36377
36527
|
if (cached2 && Date.now() - cached2.fetchedAt < CACHE_TTL_MS) {
|
|
36378
36528
|
return cached2.guidelines;
|
|
36379
36529
|
}
|
|
36380
|
-
const filesToCheck = [
|
|
36530
|
+
const filesToCheck = [
|
|
36531
|
+
"CONTRIBUTING.md",
|
|
36532
|
+
".github/CONTRIBUTING.md",
|
|
36533
|
+
"docs/CONTRIBUTING.md",
|
|
36534
|
+
"contributing.md"
|
|
36535
|
+
];
|
|
36381
36536
|
const results = await Promise.allSettled(filesToCheck.map((file2) => octokit.repos.getContent({ owner, repo, path: file2 }).then(({ data }) => {
|
|
36382
36537
|
if ("content" in data) {
|
|
36383
36538
|
return Buffer.from(data.content, "base64").toString("utf-8");
|
|
@@ -36399,7 +36554,10 @@ async function fetchContributionGuidelines(octokit, owner, repo) {
|
|
|
36399
36554
|
}
|
|
36400
36555
|
}
|
|
36401
36556
|
}
|
|
36402
|
-
guidelinesCache.set(cacheKey, {
|
|
36557
|
+
guidelinesCache.set(cacheKey, {
|
|
36558
|
+
guidelines: void 0,
|
|
36559
|
+
fetchedAt: Date.now()
|
|
36560
|
+
});
|
|
36403
36561
|
pruneCache();
|
|
36404
36562
|
return void 0;
|
|
36405
36563
|
}
|
|
@@ -36661,7 +36819,11 @@ var IssueVetter = class {
|
|
|
36661
36819
|
if (allFailed) {
|
|
36662
36820
|
warn(MODULE7, `All ${attemptedCount} issue(s) failed vetting. This may indicate a systemic issue (rate limit, auth, network).`);
|
|
36663
36821
|
}
|
|
36664
|
-
return {
|
|
36822
|
+
return {
|
|
36823
|
+
candidates: candidates.slice(0, maxResults),
|
|
36824
|
+
allFailed,
|
|
36825
|
+
rateLimitHit: rateLimitFailures > 0
|
|
36826
|
+
};
|
|
36665
36827
|
}
|
|
36666
36828
|
};
|
|
36667
36829
|
|
|
@@ -36762,11 +36924,13 @@ async function searchWithChunkedLabels(octokit, labels, reservedOps, buildQuery,
|
|
|
36762
36924
|
async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets, remainingNeeded, minStars, phaseLabel) {
|
|
36763
36925
|
const spamRepos = detectLabelFarmingRepos(items);
|
|
36764
36926
|
if (spamRepos.size > 0) {
|
|
36765
|
-
const spamCount = items.filter((i) => spamRepos.has(i.repository_url
|
|
36927
|
+
const spamCount = items.filter((i) => spamRepos.has(extractRepoFromUrl(i.repository_url) ?? "")).length;
|
|
36766
36928
|
debug(MODULE8, `[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`);
|
|
36767
36929
|
}
|
|
36768
36930
|
const itemsToVet = filterIssues(items).filter((item) => {
|
|
36769
|
-
const repoFullName = item.repository_url
|
|
36931
|
+
const repoFullName = extractRepoFromUrl(item.repository_url);
|
|
36932
|
+
if (!repoFullName)
|
|
36933
|
+
return false;
|
|
36770
36934
|
if (spamRepos.has(repoFullName))
|
|
36771
36935
|
return false;
|
|
36772
36936
|
return excludedRepoSets.every((s) => !s.has(repoFullName));
|
|
@@ -36834,6 +36998,182 @@ var MODULE9 = "issue-discovery";
|
|
|
36834
36998
|
var INTER_PHASE_DELAY_MS = 2e3;
|
|
36835
36999
|
var LOW_BUDGET_THRESHOLD = 20;
|
|
36836
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
|
+
}
|
|
36837
37177
|
var IssueDiscovery = class {
|
|
36838
37178
|
preferences;
|
|
36839
37179
|
stateReader;
|
|
@@ -36897,8 +37237,7 @@ var IssueDiscovery = class {
|
|
|
36897
37237
|
const enabledStrategies = new Set(rawStrategies.includes("all") ? ALL_STRATEGIES : rawStrategies);
|
|
36898
37238
|
const strategiesUsed = [];
|
|
36899
37239
|
const allCandidates = [];
|
|
36900
|
-
|
|
36901
|
-
let phase1Error = null;
|
|
37240
|
+
const phaseErrors = {};
|
|
36902
37241
|
let rateLimitHitDuringSearch = false;
|
|
36903
37242
|
this.rateLimitWarning = null;
|
|
36904
37243
|
const tracker = getSearchBudgetTracker();
|
|
@@ -36918,65 +37257,44 @@ var IssueDiscovery = class {
|
|
|
36918
37257
|
info(MODULE9, `Search budget low (${searchBudget} remaining) \u2014 skipping heavy phases (2, 3)`);
|
|
36919
37258
|
}
|
|
36920
37259
|
} catch (error48) {
|
|
36921
|
-
if (getHttpStatusCode(error48) === 401)
|
|
37260
|
+
if (getHttpStatusCode(error48) === 401)
|
|
36922
37261
|
throw error48;
|
|
36923
|
-
}
|
|
36924
37262
|
tracker.init(CRITICAL_BUDGET_THRESHOLD, new Date(Date.now() + 6e4).toISOString());
|
|
36925
37263
|
warn(MODULE9, "Could not check rate limit \u2014 using conservative budget, skipping heavy phases:", errorMessage(error48));
|
|
36926
37264
|
}
|
|
36927
37265
|
const mergedPRRepos = this.stateReader.getReposWithMergedPRs();
|
|
36928
37266
|
const starredRepos = this.getStarredRepos();
|
|
36929
37267
|
const starredRepoSet = new Set(starredRepos);
|
|
36930
|
-
const
|
|
36931
|
-
const lowScoringRepos = new Set(this.deriveLowScoringRepos(minRepoScoreThreshold));
|
|
36932
|
-
const excludedRepos = new Set(config2.excludeRepos);
|
|
36933
|
-
const maxAgeDays = config2.maxIssueAgeDays || 90;
|
|
36934
|
-
const now = /* @__PURE__ */ new Date();
|
|
37268
|
+
const lowScoringRepos = new Set(this.deriveLowScoringRepos(config2.minRepoScoreThreshold));
|
|
36935
37269
|
const isAnyLanguage = languages.some((l) => l.toLowerCase() === "any");
|
|
36936
37270
|
const langQuery = isAnyLanguage ? "" : languages.map((l) => `language:${l}`).join(" ");
|
|
36937
37271
|
const baseQualifiers = `is:issue is:open ${langQuery} no:assignee`.replace(/ +/g, " ").trim();
|
|
36938
|
-
const includeDocIssues = config2.includeDocIssues ?? true;
|
|
36939
37272
|
const aiBlocklisted = new Set(config2.aiPolicyBlocklist);
|
|
36940
37273
|
if (aiBlocklisted.size > 0) {
|
|
36941
37274
|
debug(MODULE9, `[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`);
|
|
36942
37275
|
}
|
|
36943
|
-
const filterIssues = (
|
|
36944
|
-
|
|
36945
|
-
|
|
36946
|
-
|
|
36947
|
-
|
|
36948
|
-
|
|
36949
|
-
|
|
36950
|
-
|
|
36951
|
-
|
|
36952
|
-
const updatedAt = new Date(item.updated_at);
|
|
36953
|
-
const ageDays = daysBetween(updatedAt, now);
|
|
36954
|
-
if (ageDays > maxAgeDays)
|
|
36955
|
-
return false;
|
|
36956
|
-
if (!includeDocIssues && isDocOnlyIssue(item))
|
|
36957
|
-
return false;
|
|
36958
|
-
return true;
|
|
36959
|
-
});
|
|
36960
|
-
};
|
|
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
|
+
});
|
|
36961
37285
|
const phase0Repos = mergedPRRepos.slice(0, 10);
|
|
36962
37286
|
const phase0RepoSet = new Set(phase0Repos);
|
|
36963
37287
|
if (phase0Repos.length > 0 && enabledStrategies.has("merged")) {
|
|
36964
|
-
|
|
36965
|
-
|
|
36966
|
-
|
|
36967
|
-
|
|
36968
|
-
|
|
36969
|
-
if (
|
|
36970
|
-
phase0Error = "All merged-PR repo batches failed";
|
|
36971
|
-
}
|
|
36972
|
-
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)
|
|
36973
37294
|
rateLimitHitDuringSearch = true;
|
|
36974
|
-
}
|
|
36975
|
-
info(MODULE9, `Found ${mergedCandidates.length} candidates from merged-PR repos`);
|
|
36976
37295
|
}
|
|
36977
37296
|
strategiesUsed.push("merged");
|
|
36978
37297
|
}
|
|
36979
|
-
let phase0_5Error = null;
|
|
36980
37298
|
const preferredOrgs = config2.preferredOrgs ?? [];
|
|
36981
37299
|
if (allCandidates.length < maxResults && preferredOrgs.length > 0 && searchBudget >= CRITICAL_BUDGET_THRESHOLD && enabledStrategies.has("orgs")) {
|
|
36982
37300
|
if (phase0Repos.length > 0)
|
|
@@ -36984,35 +37302,12 @@ var IssueDiscovery = class {
|
|
|
36984
37302
|
const phase0Orgs = new Set(phase0Repos.map((r) => r.split("/")[0]?.toLowerCase()));
|
|
36985
37303
|
const orgsToSearch = preferredOrgs.filter((org) => !phase0Orgs.has(org.toLowerCase())).slice(0, 5);
|
|
36986
37304
|
if (orgsToSearch.length > 0) {
|
|
36987
|
-
|
|
36988
|
-
const
|
|
36989
|
-
|
|
36990
|
-
|
|
36991
|
-
|
|
36992
|
-
|
|
36993
|
-
if (allItems.length > 0) {
|
|
36994
|
-
const filtered = filterIssues(allItems).filter((item) => {
|
|
36995
|
-
const repoFullName = item.repository_url.split("/").slice(-2).join("/");
|
|
36996
|
-
return !phase0RepoSet.has(repoFullName);
|
|
36997
|
-
});
|
|
36998
|
-
const { candidates: orgCandidates, allFailed: allVetFailed, rateLimitHit } = await this.vetter.vetIssuesParallel(filtered.slice(0, remainingNeeded * 2).map((i) => i.html_url), remainingNeeded, "preferred_org");
|
|
36999
|
-
allCandidates.push(...orgCandidates);
|
|
37000
|
-
if (allVetFailed) {
|
|
37001
|
-
phase0_5Error = "All preferred org issue vetting failed";
|
|
37002
|
-
}
|
|
37003
|
-
if (rateLimitHit) {
|
|
37004
|
-
rateLimitHitDuringSearch = true;
|
|
37005
|
-
}
|
|
37006
|
-
info(MODULE9, `Found ${orgCandidates.length} candidates from preferred orgs`);
|
|
37007
|
-
}
|
|
37008
|
-
} catch (error48) {
|
|
37009
|
-
const errMsg = errorMessage(error48);
|
|
37010
|
-
phase0_5Error = errMsg;
|
|
37011
|
-
if (isRateLimitError(error48)) {
|
|
37012
|
-
rateLimitHitDuringSearch = true;
|
|
37013
|
-
}
|
|
37014
|
-
warn(MODULE9, `Error searching preferred orgs: ${errMsg}`);
|
|
37015
|
-
}
|
|
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;
|
|
37016
37311
|
}
|
|
37017
37312
|
strategiesUsed.push("orgs");
|
|
37018
37313
|
}
|
|
@@ -37020,127 +37315,35 @@ var IssueDiscovery = class {
|
|
|
37020
37315
|
await sleep(INTER_PHASE_DELAY_MS);
|
|
37021
37316
|
const reposToSearch = starredRepos.filter((r) => !phase0RepoSet.has(r));
|
|
37022
37317
|
if (reposToSearch.length > 0) {
|
|
37023
|
-
|
|
37024
|
-
|
|
37025
|
-
|
|
37026
|
-
|
|
37027
|
-
|
|
37028
|
-
|
|
37029
|
-
if (allBatchesFailed) {
|
|
37030
|
-
phase1Error = "All starred repo batches failed";
|
|
37031
|
-
}
|
|
37032
|
-
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)
|
|
37033
37324
|
rateLimitHitDuringSearch = true;
|
|
37034
|
-
}
|
|
37035
|
-
info(MODULE9, `Found ${starredCandidates.length} candidates from starred repos`);
|
|
37036
37325
|
}
|
|
37037
37326
|
}
|
|
37038
37327
|
strategiesUsed.push("starred");
|
|
37039
37328
|
}
|
|
37040
|
-
let phase2Error = null;
|
|
37041
37329
|
if (allCandidates.length < maxResults && searchBudget >= LOW_BUDGET_THRESHOLD && enabledStrategies.has("broad")) {
|
|
37042
37330
|
await sleep(INTER_PHASE_DELAY_MS);
|
|
37043
|
-
|
|
37044
|
-
const
|
|
37045
|
-
|
|
37046
|
-
|
|
37047
|
-
if (
|
|
37048
|
-
|
|
37049
|
-
const scopeLabels = SCOPE_LABELS[scope] ?? [];
|
|
37050
|
-
if (scopeLabels.length === 0) {
|
|
37051
|
-
warn(MODULE9, `Scope "${scope}" has no labels, skipping tier`);
|
|
37052
|
-
continue;
|
|
37053
|
-
}
|
|
37054
|
-
tierLabelGroups.push({ tier: scope, tierLabels: scopeLabels });
|
|
37055
|
-
}
|
|
37056
|
-
const allScopeLabels = new Set(scopes.flatMap((s) => SCOPE_LABELS[s] ?? []));
|
|
37057
|
-
const customOnly = config2.labels.filter((l) => !allScopeLabels.has(l));
|
|
37058
|
-
if (customOnly.length > 0) {
|
|
37059
|
-
tierLabelGroups.push({ tier: "custom", tierLabels: customOnly });
|
|
37060
|
-
}
|
|
37061
|
-
} else {
|
|
37062
|
-
tierLabelGroups.push({ tier: "general", tierLabels: labels });
|
|
37063
|
-
}
|
|
37064
|
-
const budgetPerTier = Math.ceil(remainingNeeded / tierLabelGroups.length);
|
|
37065
|
-
const tierResults = [];
|
|
37066
|
-
for (const { tier, tierLabels } of tierLabelGroups) {
|
|
37067
|
-
try {
|
|
37068
|
-
const allItems = await searchWithChunkedLabels(
|
|
37069
|
-
this.octokit,
|
|
37070
|
-
tierLabels,
|
|
37071
|
-
0,
|
|
37072
|
-
// no repo/org ORs in Phase 2
|
|
37073
|
-
(labelQ) => `${baseQualifiers} ${labelQ}`.replace(/ +/g, " ").trim(),
|
|
37074
|
-
budgetPerTier * 3
|
|
37075
|
-
);
|
|
37076
|
-
info(MODULE9, `Phase 2 [${tier}]: processing ${allItems.length} items...`);
|
|
37077
|
-
const { candidates: tierCandidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(this.vetter, allItems, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], budgetPerTier, minStars, `Phase 2 [${tier}]`);
|
|
37078
|
-
tierResults.push(tierCandidates);
|
|
37079
|
-
for (const c of tierCandidates)
|
|
37080
|
-
seenRepos.add(c.issue.repo);
|
|
37081
|
-
if (allVetFailed) {
|
|
37082
|
-
phase2Error = (phase2Error ? phase2Error + "; " : "") + `${tier}: all vetting failed`;
|
|
37083
|
-
}
|
|
37084
|
-
if (vetRateLimitHit) {
|
|
37085
|
-
rateLimitHitDuringSearch = true;
|
|
37086
|
-
}
|
|
37087
|
-
info(MODULE9, `Found ${tierCandidates.length} candidates from ${tier} tier`);
|
|
37088
|
-
} catch (error48) {
|
|
37089
|
-
if (getHttpStatusCode(error48) === 401)
|
|
37090
|
-
throw error48;
|
|
37091
|
-
const errMsg = errorMessage(error48);
|
|
37092
|
-
phase2Error = (phase2Error ? phase2Error + "; " : "") + `${tier}: ${errMsg}`;
|
|
37093
|
-
if (isRateLimitError(error48)) {
|
|
37094
|
-
rateLimitHitDuringSearch = true;
|
|
37095
|
-
}
|
|
37096
|
-
warn(MODULE9, `Error in ${tier} tier search: ${errMsg}`);
|
|
37097
|
-
tierResults.push([]);
|
|
37098
|
-
}
|
|
37099
|
-
}
|
|
37100
|
-
const interleaved = interleaveArrays(tierResults);
|
|
37101
|
-
if (interleaved.length === 0 && phase2Error) {
|
|
37102
|
-
warn(MODULE9, `All ${tierLabelGroups.length} scope tiers failed in Phase 2: ${phase2Error}`);
|
|
37103
|
-
}
|
|
37104
|
-
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;
|
|
37105
37337
|
strategiesUsed.push("broad");
|
|
37106
37338
|
}
|
|
37107
|
-
let phase3Error = null;
|
|
37108
37339
|
if (allCandidates.length < maxResults && searchBudget >= LOW_BUDGET_THRESHOLD && enabledStrategies.has("maintained")) {
|
|
37109
37340
|
await sleep(INTER_PHASE_DELAY_MS);
|
|
37110
|
-
|
|
37111
|
-
const
|
|
37112
|
-
|
|
37113
|
-
|
|
37114
|
-
|
|
37115
|
-
|
|
37116
|
-
const topicQuery = categoryTopics.length > 0 ? `topic:${categoryTopics[0]}` : "";
|
|
37117
|
-
const phase3Query = `is:issue is:open no:assignee ${langQuery} ${topicQuery} stars:>=${minStars} pushed:>=${pushedSince} archived:false`.replace(/ +/g, " ").trim();
|
|
37118
|
-
try {
|
|
37119
|
-
const data = await cachedSearchIssues(this.octokit, {
|
|
37120
|
-
q: phase3Query,
|
|
37121
|
-
sort: "updated",
|
|
37122
|
-
order: "desc",
|
|
37123
|
-
per_page: remainingNeeded * 3
|
|
37124
|
-
});
|
|
37125
|
-
info(MODULE9, `Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`);
|
|
37126
|
-
const seenRepos = new Set(allCandidates.map((c) => c.issue.repo));
|
|
37127
|
-
const { candidates: starFiltered, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(this.vetter, data.items, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], remainingNeeded, minStars, "Phase 3");
|
|
37128
|
-
allCandidates.push(...starFiltered);
|
|
37129
|
-
if (allVetFailed) {
|
|
37130
|
-
phase3Error = "all vetting failed";
|
|
37131
|
-
}
|
|
37132
|
-
if (vetRateLimitHit) {
|
|
37133
|
-
rateLimitHitDuringSearch = true;
|
|
37134
|
-
}
|
|
37135
|
-
info(MODULE9, `Found ${starFiltered.length} candidates from maintained-repo search`);
|
|
37136
|
-
} catch (error48) {
|
|
37137
|
-
const errMsg = errorMessage(error48);
|
|
37138
|
-
phase3Error = errMsg;
|
|
37139
|
-
if (isRateLimitError(error48)) {
|
|
37140
|
-
rateLimitHitDuringSearch = true;
|
|
37141
|
-
}
|
|
37142
|
-
warn(MODULE9, `Error in maintained-repo search: ${errMsg}`);
|
|
37143
|
-
}
|
|
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;
|
|
37144
37347
|
strategiesUsed.push("maintained");
|
|
37145
37348
|
}
|
|
37146
37349
|
const phasesSkippedForBudget = searchBudget < LOW_BUDGET_THRESHOLD;
|
|
@@ -37151,14 +37354,14 @@ var IssueDiscovery = class {
|
|
|
37151
37354
|
budgetNote = ` Some search phases were skipped due to low API quota (${searchBudget} remaining).`;
|
|
37152
37355
|
}
|
|
37153
37356
|
if (allCandidates.length === 0) {
|
|
37154
|
-
const
|
|
37155
|
-
|
|
37156
|
-
|
|
37157
|
-
|
|
37158
|
-
|
|
37159
|
-
|
|
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
|
|
37160
37363
|
].filter(Boolean);
|
|
37161
|
-
const details =
|
|
37364
|
+
const details = errorDetails.length > 0 ? ` ${errorDetails.join(". ")}.` : "";
|
|
37162
37365
|
if (rateLimitHitDuringSearch || phasesSkippedForBudget) {
|
|
37163
37366
|
this.rateLimitWarning = `Search returned no results due to GitHub API rate limits.${details}${budgetNote} Try again after the rate limit resets.`;
|
|
37164
37367
|
return { candidates: [], strategiesUsed };
|
|
@@ -37169,7 +37372,12 @@ var IssueDiscovery = class {
|
|
|
37169
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.`;
|
|
37170
37373
|
}
|
|
37171
37374
|
allCandidates.sort((a, b) => {
|
|
37172
|
-
const priorityOrder = {
|
|
37375
|
+
const priorityOrder = {
|
|
37376
|
+
merged_pr: 0,
|
|
37377
|
+
preferred_org: 1,
|
|
37378
|
+
starred: 2,
|
|
37379
|
+
normal: 3
|
|
37380
|
+
};
|
|
37173
37381
|
const priorityDiff = priorityOrder[a.searchPriority] - priorityOrder[b.searchPriority];
|
|
37174
37382
|
if (priorityDiff !== 0)
|
|
37175
37383
|
return priorityDiff;
|
|
@@ -37316,6 +37524,8 @@ var GistStateStore = class {
|
|
|
37316
37524
|
this.writeCache(state);
|
|
37317
37525
|
return { gistId: foundId, state, created: false };
|
|
37318
37526
|
}
|
|
37527
|
+
warn(MODULE10, `Found existing gist ${foundId} but content failed validation. Using local cache to avoid data loss.`);
|
|
37528
|
+
return this.bootstrapFromCache();
|
|
37319
37529
|
}
|
|
37320
37530
|
debug(MODULE10, "No existing gist found, creating new one");
|
|
37321
37531
|
const freshState = ScoutStateSchema.parse({ version: 1 });
|
|
@@ -37411,7 +37621,9 @@ var GistStateStore = class {
|
|
|
37411
37621
|
}
|
|
37412
37622
|
writeCache(state) {
|
|
37413
37623
|
try {
|
|
37414
|
-
fs3.writeFileSync(getCachePath(), JSON.stringify(state, null, 2) + "\n", {
|
|
37624
|
+
fs3.writeFileSync(getCachePath(), JSON.stringify(state, null, 2) + "\n", {
|
|
37625
|
+
mode: 384
|
|
37626
|
+
});
|
|
37415
37627
|
} catch (err) {
|
|
37416
37628
|
warn(MODULE10, `Failed to write cache: ${errorMessage(err)}`);
|
|
37417
37629
|
}
|
|
@@ -37514,13 +37726,50 @@ function loadLocalState() {
|
|
|
37514
37726
|
}
|
|
37515
37727
|
|
|
37516
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
|
+
}
|
|
37517
37766
|
async function createScout(config2) {
|
|
37518
37767
|
let state;
|
|
37519
37768
|
let gistStore = null;
|
|
37520
37769
|
if (config2.persistence === "provided") {
|
|
37521
37770
|
state = config2.initialState;
|
|
37522
37771
|
} else if (config2.persistence === "gist") {
|
|
37523
|
-
gistStore = new GistStateStore(getOctokit(config2.githubToken));
|
|
37772
|
+
gistStore = new GistStateStore(toGistOctokit(getOctokit(config2.githubToken)));
|
|
37524
37773
|
const result = await gistStore.bootstrap();
|
|
37525
37774
|
if (result.degraded) {
|
|
37526
37775
|
warn("scout", "Gist sync unavailable \u2014 running in offline mode. Changes will only be saved locally.");
|
|
@@ -37646,7 +37895,7 @@ var OssScout = class {
|
|
|
37646
37895
|
getReposWithMergedPRs() {
|
|
37647
37896
|
const repoCounts = /* @__PURE__ */ new Map();
|
|
37648
37897
|
for (const pr of this.state.mergedPRs ?? []) {
|
|
37649
|
-
const repo =
|
|
37898
|
+
const repo = extractRepoFromUrl(pr.url);
|
|
37650
37899
|
if (repo) {
|
|
37651
37900
|
repoCounts.set(repo, (repoCounts.get(repo) ?? 0) + 1);
|
|
37652
37901
|
}
|
|
@@ -37819,17 +38068,13 @@ var OssScout = class {
|
|
|
37819
38068
|
return this.state;
|
|
37820
38069
|
}
|
|
37821
38070
|
// ── Private helpers ─────────────────────────────────────────────────
|
|
37822
|
-
extractRepoFromUrl(url2) {
|
|
37823
|
-
const match = url2.match(/github\.com\/([^/]+\/[^/]+)\//);
|
|
37824
|
-
return match ? match[1] : null;
|
|
37825
|
-
}
|
|
37826
38071
|
updateRepoScoreFromPRs(repo) {
|
|
37827
|
-
const mergedCount = (this.state.mergedPRs ?? []).filter((p) =>
|
|
37828
|
-
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;
|
|
37829
38074
|
this.updateRepoScore(repo, {
|
|
37830
38075
|
mergedPRCount: mergedCount,
|
|
37831
38076
|
closedWithoutMergeCount: closedCount,
|
|
37832
|
-
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
|
|
37833
38078
|
});
|
|
37834
38079
|
}
|
|
37835
38080
|
/**
|
|
@@ -37858,26 +38103,52 @@ function registerTools(server, scout) {
|
|
|
37858
38103
|
"Search for open source issues matching your preferences",
|
|
37859
38104
|
{
|
|
37860
38105
|
maxResults: external_exports3.number().optional().describe("Maximum number of results to return (default 10)"),
|
|
37861
|
-
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
|
+
)
|
|
37862
38109
|
},
|
|
37863
38110
|
async ({ maxResults, strategies }) => {
|
|
37864
|
-
|
|
37865
|
-
|
|
37866
|
-
|
|
37867
|
-
|
|
37868
|
-
|
|
37869
|
-
|
|
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
|
+
}
|
|
37870
38129
|
}
|
|
37871
38130
|
);
|
|
37872
38131
|
server.tool(
|
|
37873
38132
|
"vet",
|
|
37874
38133
|
"Vet a specific GitHub issue for contribution viability",
|
|
37875
38134
|
{
|
|
37876
|
-
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
|
+
)
|
|
37877
38138
|
},
|
|
37878
38139
|
async ({ issueUrl }) => {
|
|
37879
|
-
|
|
37880
|
-
|
|
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
|
+
}
|
|
37881
38152
|
}
|
|
37882
38153
|
);
|
|
37883
38154
|
server.tool(
|
|
@@ -37885,7 +38156,9 @@ function registerTools(server, scout) {
|
|
|
37885
38156
|
"Show current oss-scout configuration preferences",
|
|
37886
38157
|
async () => {
|
|
37887
38158
|
const preferences = scout.getPreferences();
|
|
37888
|
-
return {
|
|
38159
|
+
return {
|
|
38160
|
+
content: [{ type: "text", text: JSON.stringify(preferences, null, 2) }]
|
|
38161
|
+
};
|
|
37889
38162
|
}
|
|
37890
38163
|
);
|
|
37891
38164
|
const VALID_KEYS = Object.keys(ScoutPreferencesSchema.shape);
|
|
@@ -37895,23 +38168,37 @@ function registerTools(server, scout) {
|
|
|
37895
38168
|
"preferredOrgs",
|
|
37896
38169
|
"projectCategories",
|
|
37897
38170
|
"excludeRepos",
|
|
38171
|
+
"excludeOrgs",
|
|
37898
38172
|
"aiPolicyBlocklist",
|
|
37899
38173
|
"scope",
|
|
37900
38174
|
"defaultStrategy"
|
|
37901
38175
|
]);
|
|
37902
|
-
const NUMBER_KEYS = /* @__PURE__ */ new Set([
|
|
38176
|
+
const NUMBER_KEYS = /* @__PURE__ */ new Set([
|
|
38177
|
+
"minStars",
|
|
38178
|
+
"maxIssueAgeDays",
|
|
38179
|
+
"minRepoScoreThreshold"
|
|
38180
|
+
]);
|
|
37903
38181
|
const BOOLEAN_KEYS = /* @__PURE__ */ new Set(["includeDocIssues"]);
|
|
37904
38182
|
server.tool(
|
|
37905
38183
|
"config-set",
|
|
37906
38184
|
"Update an oss-scout configuration preference",
|
|
37907
38185
|
{
|
|
37908
|
-
key: external_exports3.string().describe(
|
|
37909
|
-
|
|
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
|
+
)
|
|
37910
38192
|
},
|
|
37911
38193
|
async ({ key, value }) => {
|
|
37912
38194
|
if (!VALID_KEYS.includes(key)) {
|
|
37913
38195
|
return {
|
|
37914
|
-
content: [
|
|
38196
|
+
content: [
|
|
38197
|
+
{
|
|
38198
|
+
type: "text",
|
|
38199
|
+
text: `Unknown config key: "${key}". Valid keys: ${VALID_KEYS.join(", ")}`
|
|
38200
|
+
}
|
|
38201
|
+
],
|
|
37915
38202
|
isError: true
|
|
37916
38203
|
};
|
|
37917
38204
|
}
|
|
@@ -37929,7 +38216,9 @@ function registerTools(server, scout) {
|
|
|
37929
38216
|
const num = Number(value);
|
|
37930
38217
|
if (isNaN(num)) {
|
|
37931
38218
|
return {
|
|
37932
|
-
content: [
|
|
38219
|
+
content: [
|
|
38220
|
+
{ type: "text", text: `Invalid number for "${key}": "${value}"` }
|
|
38221
|
+
],
|
|
37933
38222
|
isError: true
|
|
37934
38223
|
};
|
|
37935
38224
|
}
|
|
@@ -37940,7 +38229,12 @@ function registerTools(server, scout) {
|
|
|
37940
38229
|
else if (lower === "false" || lower === "no") parsed = false;
|
|
37941
38230
|
else {
|
|
37942
38231
|
return {
|
|
37943
|
-
content: [
|
|
38232
|
+
content: [
|
|
38233
|
+
{
|
|
38234
|
+
type: "text",
|
|
38235
|
+
text: `Invalid boolean for "${key}": "${value}". Use true/false or yes/no.`
|
|
38236
|
+
}
|
|
38237
|
+
],
|
|
37944
38238
|
isError: true
|
|
37945
38239
|
};
|
|
37946
38240
|
}
|
|
@@ -37958,8 +38252,11 @@ function registerTools(server, scout) {
|
|
|
37958
38252
|
};
|
|
37959
38253
|
}
|
|
37960
38254
|
scout.updatePreferences({ [key]: parsed });
|
|
38255
|
+
await scout.checkpoint();
|
|
37961
38256
|
const updated = scout.getPreferences();
|
|
37962
|
-
return {
|
|
38257
|
+
return {
|
|
38258
|
+
content: [{ type: "text", text: JSON.stringify(updated, null, 2) }]
|
|
38259
|
+
};
|
|
37963
38260
|
}
|
|
37964
38261
|
);
|
|
37965
38262
|
}
|
|
@@ -37971,11 +38268,13 @@ function registerResources(server, scout) {
|
|
|
37971
38268
|
"scout://config",
|
|
37972
38269
|
{ description: "Current oss-scout preferences" },
|
|
37973
38270
|
async () => ({
|
|
37974
|
-
contents: [
|
|
37975
|
-
|
|
37976
|
-
|
|
37977
|
-
|
|
37978
|
-
|
|
38271
|
+
contents: [
|
|
38272
|
+
{
|
|
38273
|
+
uri: "scout://config",
|
|
38274
|
+
mimeType: "application/json",
|
|
38275
|
+
text: JSON.stringify(scout.getPreferences(), null, 2)
|
|
38276
|
+
}
|
|
38277
|
+
]
|
|
37979
38278
|
})
|
|
37980
38279
|
);
|
|
37981
38280
|
server.resource(
|
|
@@ -37983,11 +38282,13 @@ function registerResources(server, scout) {
|
|
|
37983
38282
|
"scout://results",
|
|
37984
38283
|
{ description: "Saved search results from previous runs" },
|
|
37985
38284
|
async () => ({
|
|
37986
|
-
contents: [
|
|
37987
|
-
|
|
37988
|
-
|
|
37989
|
-
|
|
37990
|
-
|
|
38285
|
+
contents: [
|
|
38286
|
+
{
|
|
38287
|
+
uri: "scout://results",
|
|
38288
|
+
mimeType: "application/json",
|
|
38289
|
+
text: JSON.stringify(scout.getSavedResults(), null, 2)
|
|
38290
|
+
}
|
|
38291
|
+
]
|
|
37991
38292
|
})
|
|
37992
38293
|
);
|
|
37993
38294
|
server.resource(
|
|
@@ -37997,11 +38298,13 @@ function registerResources(server, scout) {
|
|
|
37997
38298
|
async () => {
|
|
37998
38299
|
const state = scout.getState();
|
|
37999
38300
|
return {
|
|
38000
|
-
contents: [
|
|
38001
|
-
|
|
38002
|
-
|
|
38003
|
-
|
|
38004
|
-
|
|
38301
|
+
contents: [
|
|
38302
|
+
{
|
|
38303
|
+
uri: "scout://scores",
|
|
38304
|
+
mimeType: "application/json",
|
|
38305
|
+
text: JSON.stringify(state.repoScores, null, 2)
|
|
38306
|
+
}
|
|
38307
|
+
]
|
|
38005
38308
|
};
|
|
38006
38309
|
}
|
|
38007
38310
|
);
|
|
@@ -38027,8 +38330,10 @@ async function main() {
|
|
|
38027
38330
|
await server.connect(transport);
|
|
38028
38331
|
}
|
|
38029
38332
|
main().catch((err) => {
|
|
38030
|
-
process.stderr.write(
|
|
38031
|
-
`)
|
|
38333
|
+
process.stderr.write(
|
|
38334
|
+
`oss-scout-mcp fatal: ${err instanceof Error ? err.message : String(err)}
|
|
38335
|
+
`
|
|
38336
|
+
);
|
|
38032
38337
|
process.exit(1);
|
|
38033
38338
|
});
|
|
38034
38339
|
/*! Bundled license information:
|