@oss-scout/mcp 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp-server.bundle.cjs +243 -80
- package/dist/tools.js +3 -0
- package/package.json +5 -4
|
@@ -35742,6 +35742,11 @@ var StoredClosedPRSchema = external_exports3.object({
|
|
|
35742
35742
|
title: external_exports3.string(),
|
|
35743
35743
|
closedAt: external_exports3.string()
|
|
35744
35744
|
});
|
|
35745
|
+
var StoredOpenPRSchema = external_exports3.object({
|
|
35746
|
+
url: external_exports3.string(),
|
|
35747
|
+
title: external_exports3.string(),
|
|
35748
|
+
openedAt: external_exports3.string()
|
|
35749
|
+
});
|
|
35745
35750
|
var ContributionGuidelinesSchema = external_exports3.object({
|
|
35746
35751
|
branchNamingConvention: external_exports3.string().optional(),
|
|
35747
35752
|
commitMessageFormat: external_exports3.string().optional(),
|
|
@@ -35817,8 +35822,11 @@ var ScoutPreferencesSchema = external_exports3.object({
|
|
|
35817
35822
|
maxIssueAgeDays: external_exports3.number().default(90),
|
|
35818
35823
|
includeDocIssues: external_exports3.boolean().default(true),
|
|
35819
35824
|
minRepoScoreThreshold: external_exports3.number().default(4),
|
|
35825
|
+
interPhaseDelayMs: external_exports3.number().min(0).max(12e4).default(3e4),
|
|
35820
35826
|
persistence: PersistenceModeSchema.default("local"),
|
|
35821
|
-
defaultStrategy: external_exports3.array(SearchStrategySchema).optional()
|
|
35827
|
+
defaultStrategy: external_exports3.array(SearchStrategySchema).optional(),
|
|
35828
|
+
broadPhaseDelayMs: external_exports3.number().min(0).max(3e5).default(9e4),
|
|
35829
|
+
skipBroadWhenSufficientResults: external_exports3.number().int().min(0).max(100).default(15)
|
|
35822
35830
|
});
|
|
35823
35831
|
var ScoutStateSchema = external_exports3.object({
|
|
35824
35832
|
version: external_exports3.literal(1),
|
|
@@ -35828,6 +35836,7 @@ var ScoutStateSchema = external_exports3.object({
|
|
|
35828
35836
|
starredReposLastFetched: external_exports3.string().optional(),
|
|
35829
35837
|
mergedPRs: external_exports3.array(StoredMergedPRSchema).default([]),
|
|
35830
35838
|
closedPRs: external_exports3.array(StoredClosedPRSchema).default([]),
|
|
35839
|
+
openPRs: external_exports3.array(StoredOpenPRSchema).default([]),
|
|
35831
35840
|
savedResults: external_exports3.array(SavedCandidateSchema).default([]),
|
|
35832
35841
|
skippedIssues: external_exports3.array(SkippedIssueSchema).default([]),
|
|
35833
35842
|
lastSearchAt: external_exports3.string().optional(),
|
|
@@ -36833,7 +36842,6 @@ var IssueVetter = class {
|
|
|
36833
36842
|
var MODULE8 = "search-phases";
|
|
36834
36843
|
var GITHUB_MAX_BOOLEAN_OPS = 5;
|
|
36835
36844
|
var INTER_QUERY_DELAY_MS = 2e3;
|
|
36836
|
-
var BATCH_SIZE = 3;
|
|
36837
36845
|
function chunkLabels(labels, reservedOps = 0) {
|
|
36838
36846
|
const maxPerChunk = GITHUB_MAX_BOOLEAN_OPS - reservedOps + 1;
|
|
36839
36847
|
if (maxPerChunk < 1) {
|
|
@@ -36879,26 +36887,138 @@ function interleaveArrays(arrays) {
|
|
|
36879
36887
|
}
|
|
36880
36888
|
return result;
|
|
36881
36889
|
}
|
|
36882
|
-
function batchRepos(repos, batchSize) {
|
|
36883
|
-
const batches = [];
|
|
36884
|
-
for (let i = 0; i < repos.length; i += batchSize) {
|
|
36885
|
-
batches.push(repos.slice(i, i + batchSize));
|
|
36886
|
-
}
|
|
36887
|
-
return batches;
|
|
36888
|
-
}
|
|
36889
36890
|
var SEARCH_CACHE_TTL_MS = 15 * 60 * 1e3;
|
|
36890
36891
|
async function cachedSearchIssues(octokit, params) {
|
|
36891
36892
|
const cacheKey = `search:${params.q}:${params.sort}:${params.order}:${params.per_page}`;
|
|
36892
|
-
|
|
36893
|
-
|
|
36894
|
-
|
|
36893
|
+
const cache = getHttpCache();
|
|
36894
|
+
const cached2 = cache.getIfFresh(cacheKey, SEARCH_CACHE_TTL_MS);
|
|
36895
|
+
if (cached2) {
|
|
36896
|
+
debug(MODULE8, `Search cache hit for query`);
|
|
36897
|
+
return cached2;
|
|
36898
|
+
}
|
|
36899
|
+
const tracker = getSearchBudgetTracker();
|
|
36900
|
+
await tracker.waitForBudget();
|
|
36901
|
+
let data;
|
|
36902
|
+
try {
|
|
36903
|
+
const response = await octokit.search.issuesAndPullRequests(params);
|
|
36904
|
+
data = response.data;
|
|
36905
|
+
} finally {
|
|
36906
|
+
tracker.recordCall();
|
|
36907
|
+
}
|
|
36908
|
+
if (data.items.length > 0) {
|
|
36909
|
+
cache.set(cacheKey, "", data);
|
|
36910
|
+
} else {
|
|
36911
|
+
debug(MODULE8, `Skipping cache for empty search result (possible rate limit artifact)`);
|
|
36912
|
+
}
|
|
36913
|
+
return data;
|
|
36914
|
+
}
|
|
36915
|
+
async function fetchIssuesFromMaintainedRepos(octokit, repos, minStars, maxResults) {
|
|
36916
|
+
const items = [];
|
|
36917
|
+
for (const repoFullName of repos) {
|
|
36918
|
+
if (items.length >= maxResults * 3)
|
|
36919
|
+
break;
|
|
36920
|
+
const [owner, repo] = repoFullName.split("/");
|
|
36921
|
+
if (!owner || !repo)
|
|
36922
|
+
continue;
|
|
36895
36923
|
try {
|
|
36896
|
-
const { data } = await octokit.
|
|
36897
|
-
|
|
36898
|
-
|
|
36899
|
-
|
|
36924
|
+
const { data: repoData } = await octokit.repos.get({ owner, repo });
|
|
36925
|
+
if (!repoData.pushed_at)
|
|
36926
|
+
continue;
|
|
36927
|
+
const pushedAt = new Date(repoData.pushed_at);
|
|
36928
|
+
const thirtyDaysAgo = /* @__PURE__ */ new Date();
|
|
36929
|
+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
36930
|
+
if (pushedAt < thirtyDaysAgo)
|
|
36931
|
+
continue;
|
|
36932
|
+
if ((repoData.stargazers_count ?? 0) < minStars)
|
|
36933
|
+
continue;
|
|
36934
|
+
if (repoData.archived)
|
|
36935
|
+
continue;
|
|
36936
|
+
const { data: issues } = await octokit.issues.listForRepo({
|
|
36937
|
+
owner,
|
|
36938
|
+
repo,
|
|
36939
|
+
state: "open",
|
|
36940
|
+
sort: "created",
|
|
36941
|
+
direction: "desc",
|
|
36942
|
+
per_page: 5
|
|
36943
|
+
});
|
|
36944
|
+
const realIssues = issues.filter((i) => !i.pull_request && !i.assignee);
|
|
36945
|
+
for (const issue2 of realIssues) {
|
|
36946
|
+
items.push({
|
|
36947
|
+
html_url: issue2.html_url,
|
|
36948
|
+
repository_url: `https://api.github.com/repos/${repoFullName}`,
|
|
36949
|
+
updated_at: issue2.updated_at ?? "",
|
|
36950
|
+
title: issue2.title,
|
|
36951
|
+
labels: issue2.labels
|
|
36952
|
+
});
|
|
36953
|
+
}
|
|
36954
|
+
await sleep(INTER_QUERY_DELAY_MS);
|
|
36955
|
+
} catch (error48) {
|
|
36956
|
+
if (getHttpStatusCode(error48) === 401)
|
|
36957
|
+
throw error48;
|
|
36958
|
+
if (isRateLimitError(error48)) {
|
|
36959
|
+
warn(MODULE8, `Rate limit hit fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
36960
|
+
break;
|
|
36961
|
+
}
|
|
36962
|
+
warn(MODULE8, `Error fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
36900
36963
|
}
|
|
36901
|
-
}
|
|
36964
|
+
}
|
|
36965
|
+
return items;
|
|
36966
|
+
}
|
|
36967
|
+
async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResults, priority, filterFn) {
|
|
36968
|
+
const candidates = [];
|
|
36969
|
+
let failedRepos = 0;
|
|
36970
|
+
let rateLimitFailures = 0;
|
|
36971
|
+
for (let i = 0; i < repos.length; i++) {
|
|
36972
|
+
if (candidates.length >= maxResults)
|
|
36973
|
+
break;
|
|
36974
|
+
if (i > 0)
|
|
36975
|
+
await sleep(INTER_QUERY_DELAY_MS);
|
|
36976
|
+
const repoFullName = repos[i];
|
|
36977
|
+
const [owner, repo] = repoFullName.split("/");
|
|
36978
|
+
try {
|
|
36979
|
+
const response = await octokit.issues.listForRepo({
|
|
36980
|
+
owner,
|
|
36981
|
+
repo,
|
|
36982
|
+
state: "open",
|
|
36983
|
+
sort: "created",
|
|
36984
|
+
direction: "desc",
|
|
36985
|
+
per_page: 5,
|
|
36986
|
+
...labels.length > 0 ? { labels: labels.join(",") } : {}
|
|
36987
|
+
});
|
|
36988
|
+
const issuesOnly = response.data.filter((item) => !("pull_request" in item) && !item.assignee);
|
|
36989
|
+
const mapped = issuesOnly.map((issue2) => ({
|
|
36990
|
+
html_url: issue2.html_url,
|
|
36991
|
+
repository_url: `https://api.github.com/repos/${repoFullName}`,
|
|
36992
|
+
updated_at: issue2.updated_at ?? "",
|
|
36993
|
+
title: issue2.title,
|
|
36994
|
+
labels: issue2.labels
|
|
36995
|
+
}));
|
|
36996
|
+
if (mapped.length > 0) {
|
|
36997
|
+
const filtered = filterFn(mapped);
|
|
36998
|
+
if (filtered.length > 0) {
|
|
36999
|
+
const remainingNeeded = maxResults - candidates.length;
|
|
37000
|
+
const { candidates: vetted, rateLimitHit: vetRateLimitHit } = await vetter.vetIssuesParallel(filtered.slice(0, remainingNeeded * 2).map((item) => item.html_url), remainingNeeded, priority);
|
|
37001
|
+
candidates.push(...vetted);
|
|
37002
|
+
if (vetRateLimitHit)
|
|
37003
|
+
rateLimitFailures++;
|
|
37004
|
+
}
|
|
37005
|
+
}
|
|
37006
|
+
} catch (error48) {
|
|
37007
|
+
if (getHttpStatusCode(error48) === 401)
|
|
37008
|
+
throw error48;
|
|
37009
|
+
failedRepos++;
|
|
37010
|
+
if (isRateLimitError(error48)) {
|
|
37011
|
+
rateLimitFailures++;
|
|
37012
|
+
}
|
|
37013
|
+
warn(MODULE8, `Error fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
37014
|
+
}
|
|
37015
|
+
}
|
|
37016
|
+
const allReposFailed = failedRepos === repos.length && repos.length > 0;
|
|
37017
|
+
const rateLimitHit = rateLimitFailures > 0;
|
|
37018
|
+
if (allReposFailed) {
|
|
37019
|
+
warn(MODULE8, `All ${repos.length} repo(s) failed for ${priority} phase. This may indicate a systemic issue (rate limit, auth, network).`);
|
|
37020
|
+
}
|
|
37021
|
+
return { candidates, allReposFailed, rateLimitHit };
|
|
36902
37022
|
}
|
|
36903
37023
|
async function searchWithChunkedLabels(octokit, labels, reservedOps, buildQuery, perPage) {
|
|
36904
37024
|
const labelChunks = chunkLabels(labels, reservedOps);
|
|
@@ -36954,50 +37074,9 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
36954
37074
|
}
|
|
36955
37075
|
return { candidates: starFiltered, allVetFailed, rateLimitHit };
|
|
36956
37076
|
}
|
|
36957
|
-
async function searchInRepos(octokit, vetter, repos, baseQualifiers, labels, maxResults, priority, filterFn) {
|
|
36958
|
-
const candidates = [];
|
|
36959
|
-
const batches = batchRepos(repos, BATCH_SIZE);
|
|
36960
|
-
let failedBatches = 0;
|
|
36961
|
-
let rateLimitFailures = 0;
|
|
36962
|
-
for (let batchIdx = 0; batchIdx < batches.length; batchIdx++) {
|
|
36963
|
-
const batch = batches[batchIdx];
|
|
36964
|
-
if (candidates.length >= maxResults)
|
|
36965
|
-
break;
|
|
36966
|
-
if (batchIdx > 0)
|
|
36967
|
-
await sleep(INTER_QUERY_DELAY_MS);
|
|
36968
|
-
try {
|
|
36969
|
-
const repoFilter = batch.map((r) => `repo:${r}`).join(" OR ");
|
|
36970
|
-
const repoOps = batch.length - 1;
|
|
36971
|
-
const perPage = Math.min(30, (maxResults - candidates.length) * 3);
|
|
36972
|
-
const allItems = await searchWithChunkedLabels(octokit, labels, repoOps, (labelQ) => `${baseQualifiers} ${labelQ} (${repoFilter})`.replace(/ +/g, " ").trim(), perPage);
|
|
36973
|
-
if (allItems.length > 0) {
|
|
36974
|
-
const filtered = filterFn(allItems);
|
|
36975
|
-
const remainingNeeded = maxResults - candidates.length;
|
|
36976
|
-
const { candidates: vetted, rateLimitHit: vetRateLimitHit } = await vetter.vetIssuesParallel(filtered.slice(0, remainingNeeded * 2).map((i) => i.html_url), remainingNeeded, priority);
|
|
36977
|
-
candidates.push(...vetted);
|
|
36978
|
-
if (vetRateLimitHit)
|
|
36979
|
-
rateLimitFailures++;
|
|
36980
|
-
}
|
|
36981
|
-
} catch (error48) {
|
|
36982
|
-
failedBatches++;
|
|
36983
|
-
if (isRateLimitError(error48)) {
|
|
36984
|
-
rateLimitFailures++;
|
|
36985
|
-
}
|
|
36986
|
-
const batchReposStr = batch.join(", ");
|
|
36987
|
-
warn(MODULE8, `Error searching issues in batch [${batchReposStr}]:`, errorMessage(error48));
|
|
36988
|
-
}
|
|
36989
|
-
}
|
|
36990
|
-
const allBatchesFailed = failedBatches === batches.length && batches.length > 0;
|
|
36991
|
-
const rateLimitHit = rateLimitFailures > 0;
|
|
36992
|
-
if (allBatchesFailed) {
|
|
36993
|
-
warn(MODULE8, `All ${batches.length} batch(es) failed for ${priority} phase. This may indicate a systemic issue (rate limit, auth, network).`);
|
|
36994
|
-
}
|
|
36995
|
-
return { candidates, allBatchesFailed, rateLimitHit };
|
|
36996
|
-
}
|
|
36997
37077
|
|
|
36998
37078
|
// ../core/dist/core/issue-discovery.js
|
|
36999
37079
|
var MODULE9 = "issue-discovery";
|
|
37000
|
-
var INTER_PHASE_DELAY_MS = 2e3;
|
|
37001
37080
|
var LOW_BUDGET_THRESHOLD = 20;
|
|
37002
37081
|
var CRITICAL_BUDGET_THRESHOLD = 10;
|
|
37003
37082
|
function buildIssueFilter(config2) {
|
|
@@ -37029,24 +37108,25 @@ function buildIssueFilter(config2) {
|
|
|
37029
37108
|
});
|
|
37030
37109
|
};
|
|
37031
37110
|
}
|
|
37032
|
-
async function runPhase0(octokit, vetter, repos,
|
|
37111
|
+
async function runPhase0(octokit, vetter, repos, maxResults, filterIssues) {
|
|
37033
37112
|
info(MODULE9, `Phase 0: Searching issues in ${repos.length} merged-PR repos (no label filter)...`);
|
|
37034
|
-
const { candidates,
|
|
37113
|
+
const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, repos, [], maxResults, "merged_pr", filterIssues);
|
|
37035
37114
|
info(MODULE9, `Found ${candidates.length} candidates from merged-PR repos`);
|
|
37036
37115
|
return {
|
|
37037
37116
|
candidates,
|
|
37038
|
-
error:
|
|
37117
|
+
error: allReposFailed ? "All merged-PR repo fetches failed" : null,
|
|
37039
37118
|
rateLimitHit
|
|
37040
37119
|
};
|
|
37041
37120
|
}
|
|
37042
|
-
async function runPhase1(octokit, vetter, repos,
|
|
37121
|
+
async function runPhase1(octokit, vetter, repos, labels, maxResults, filterIssues) {
|
|
37043
37122
|
info(MODULE9, `Phase 1: Searching issues in ${repos.length} starred repos...`);
|
|
37044
37123
|
const phase1Labels = labels.slice(0, 3);
|
|
37045
|
-
const
|
|
37124
|
+
const reposToSearch = repos.slice(0, 10);
|
|
37125
|
+
const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, reposToSearch, phase1Labels, maxResults, "starred", filterIssues);
|
|
37046
37126
|
info(MODULE9, `Found ${candidates.length} candidates from starred repos`);
|
|
37047
37127
|
return {
|
|
37048
37128
|
candidates,
|
|
37049
|
-
error:
|
|
37129
|
+
error: allReposFailed ? "All starred repo fetches failed" : null,
|
|
37050
37130
|
rateLimitHit
|
|
37051
37131
|
};
|
|
37052
37132
|
}
|
|
@@ -37112,8 +37192,32 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, baseQual
|
|
|
37112
37192
|
rateLimitHit
|
|
37113
37193
|
};
|
|
37114
37194
|
}
|
|
37115
|
-
async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories, maxResults, phase0RepoSet, starredRepoSet, existingCandidates, filterIssues) {
|
|
37195
|
+
async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories, maxResults, phase0RepoSet, starredRepoSet, starredRepos, existingCandidates, filterIssues) {
|
|
37116
37196
|
info(MODULE9, "Phase 3: Searching actively maintained repos...");
|
|
37197
|
+
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
37198
|
+
const eligibleStarred = starredRepos.filter((r) => !phase0RepoSet.has(r) && !seenRepos.has(r));
|
|
37199
|
+
if (eligibleStarred.length > 0) {
|
|
37200
|
+
info(MODULE9, `Phase 3: Checking ${eligibleStarred.length} starred repos via REST API...`);
|
|
37201
|
+
const restItems = await fetchIssuesFromMaintainedRepos(octokit, eligibleStarred.slice(0, 15), minStars, maxResults);
|
|
37202
|
+
if (restItems.length > 0) {
|
|
37203
|
+
try {
|
|
37204
|
+
const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, restItems, filterIssues, [phase0RepoSet, seenRepos], maxResults, minStars, "Phase 3 (REST)");
|
|
37205
|
+
if (candidates.length > 0) {
|
|
37206
|
+
info(MODULE9, `Found ${candidates.length} candidates from maintained-repo REST search`);
|
|
37207
|
+
return {
|
|
37208
|
+
candidates,
|
|
37209
|
+
error: allVetFailed ? "all vetting failed" : null,
|
|
37210
|
+
rateLimitHit: vetRateLimitHit
|
|
37211
|
+
};
|
|
37212
|
+
}
|
|
37213
|
+
} catch (error48) {
|
|
37214
|
+
if (getHttpStatusCode(error48) === 401)
|
|
37215
|
+
throw error48;
|
|
37216
|
+
warn(MODULE9, `Phase 3 REST vetting failed, falling back to Search API:`, errorMessage(error48));
|
|
37217
|
+
}
|
|
37218
|
+
}
|
|
37219
|
+
}
|
|
37220
|
+
info(MODULE9, "Phase 3: Falling back to Search API...");
|
|
37117
37221
|
const thirtyDaysAgo = /* @__PURE__ */ new Date();
|
|
37118
37222
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
37119
37223
|
const pushedSince = thirtyDaysAgo.toISOString().split("T")[0];
|
|
@@ -37128,7 +37232,6 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
37128
37232
|
per_page: maxResults * 3
|
|
37129
37233
|
});
|
|
37130
37234
|
info(MODULE9, `Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`);
|
|
37131
|
-
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
37132
37235
|
const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, data.items, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], maxResults, minStars, "Phase 3");
|
|
37133
37236
|
info(MODULE9, `Found ${candidates.length} candidates from maintained-repo search`);
|
|
37134
37237
|
return {
|
|
@@ -37204,6 +37307,7 @@ var IssueDiscovery = class {
|
|
|
37204
37307
|
const labels = options.labels || (scopes ? buildEffectiveLabels(scopes, config2.labels) : config2.labels);
|
|
37205
37308
|
const maxResults = options.maxResults || 10;
|
|
37206
37309
|
const minStars = config2.minStars ?? 50;
|
|
37310
|
+
const interPhaseDelay = config2.interPhaseDelayMs ?? 3e4;
|
|
37207
37311
|
const ALL_STRATEGIES = CONCRETE_STRATEGIES;
|
|
37208
37312
|
const rawStrategies = options.strategies ?? config2.defaultStrategy ?? ["all"];
|
|
37209
37313
|
const enabledStrategies = new Set(rawStrategies.includes("all") ? ALL_STRATEGIES : rawStrategies);
|
|
@@ -37239,6 +37343,7 @@ var IssueDiscovery = class {
|
|
|
37239
37343
|
return { candidates: [], strategiesUsed: [] };
|
|
37240
37344
|
}
|
|
37241
37345
|
const mergedPRRepos = this.stateReader.getReposWithMergedPRs();
|
|
37346
|
+
const openPRRepos = this.stateReader.getReposWithOpenPRs();
|
|
37242
37347
|
const starredRepos = this.getStarredRepos();
|
|
37243
37348
|
const starredRepoSet = new Set(starredRepos);
|
|
37244
37349
|
const lowScoringRepos = new Set(this.deriveLowScoringRepos(config2.minRepoScoreThreshold));
|
|
@@ -37259,12 +37364,21 @@ var IssueDiscovery = class {
|
|
|
37259
37364
|
now: /* @__PURE__ */ new Date(),
|
|
37260
37365
|
includeDocIssues: config2.includeDocIssues ?? true
|
|
37261
37366
|
});
|
|
37262
|
-
const
|
|
37367
|
+
const seenPhase0 = /* @__PURE__ */ new Set();
|
|
37368
|
+
const phase0Repos = [];
|
|
37369
|
+
for (const repo of [...mergedPRRepos, ...openPRRepos]) {
|
|
37370
|
+
if (seenPhase0.has(repo))
|
|
37371
|
+
continue;
|
|
37372
|
+
seenPhase0.add(repo);
|
|
37373
|
+
phase0Repos.push(repo);
|
|
37374
|
+
if (phase0Repos.length >= 10)
|
|
37375
|
+
break;
|
|
37376
|
+
}
|
|
37263
37377
|
const phase0RepoSet = new Set(phase0Repos);
|
|
37264
37378
|
if (phase0Repos.length > 0 && enabledStrategies.has("merged")) {
|
|
37265
37379
|
const remaining = maxResults - allCandidates.length;
|
|
37266
37380
|
if (remaining > 0) {
|
|
37267
|
-
const result = await runPhase0(this.octokit, this.vetter, phase0Repos,
|
|
37381
|
+
const result = await runPhase0(this.octokit, this.vetter, phase0Repos, remaining, filterIssues);
|
|
37268
37382
|
allCandidates.push(...result.candidates);
|
|
37269
37383
|
phaseErrors["0"] = result.error;
|
|
37270
37384
|
if (result.rateLimitHit)
|
|
@@ -37273,12 +37387,15 @@ var IssueDiscovery = class {
|
|
|
37273
37387
|
strategiesUsed.push("merged");
|
|
37274
37388
|
}
|
|
37275
37389
|
if (allCandidates.length < maxResults && starredRepos.length > 0 && searchBudget >= CRITICAL_BUDGET_THRESHOLD && enabledStrategies.has("starred")) {
|
|
37276
|
-
|
|
37390
|
+
if (interPhaseDelay > 0) {
|
|
37391
|
+
info(MODULE9, `Waiting ${(interPhaseDelay / 1e3).toFixed(0)}s between phases for rate limit management...`);
|
|
37392
|
+
await sleep(interPhaseDelay);
|
|
37393
|
+
}
|
|
37277
37394
|
const reposToSearch = starredRepos.filter((r) => !phase0RepoSet.has(r));
|
|
37278
37395
|
if (reposToSearch.length > 0) {
|
|
37279
37396
|
const remaining = maxResults - allCandidates.length;
|
|
37280
37397
|
if (remaining > 0) {
|
|
37281
|
-
const result = await runPhase1(this.octokit, this.vetter, reposToSearch,
|
|
37398
|
+
const result = await runPhase1(this.octokit, this.vetter, reposToSearch, labels, remaining, filterIssues);
|
|
37282
37399
|
allCandidates.push(...result.candidates);
|
|
37283
37400
|
phaseErrors["1"] = result.error;
|
|
37284
37401
|
if (result.rateLimitHit)
|
|
@@ -37287,20 +37404,38 @@ var IssueDiscovery = class {
|
|
|
37287
37404
|
}
|
|
37288
37405
|
strategiesUsed.push("starred");
|
|
37289
37406
|
}
|
|
37407
|
+
const broadDelay = config2.broadPhaseDelayMs ?? 9e4;
|
|
37408
|
+
const skipThreshold = config2.skipBroadWhenSufficientResults ?? 15;
|
|
37290
37409
|
if (allCandidates.length < maxResults && searchBudget >= LOW_BUDGET_THRESHOLD && enabledStrategies.has("broad")) {
|
|
37291
|
-
|
|
37292
|
-
|
|
37293
|
-
|
|
37294
|
-
|
|
37295
|
-
|
|
37296
|
-
|
|
37297
|
-
|
|
37410
|
+
if (skipThreshold > 0 && allCandidates.length >= skipThreshold) {
|
|
37411
|
+
info(MODULE9, `Skipping broad search: already found ${allCandidates.length} candidates (threshold: ${skipThreshold})`);
|
|
37412
|
+
} else {
|
|
37413
|
+
if (interPhaseDelay > 0) {
|
|
37414
|
+
info(MODULE9, `Waiting ${(interPhaseDelay / 1e3).toFixed(0)}s between phases for rate limit management...`);
|
|
37415
|
+
await sleep(interPhaseDelay);
|
|
37416
|
+
}
|
|
37417
|
+
if (allCandidates.length > 0 && broadDelay > 0) {
|
|
37418
|
+
info(MODULE9, `Waiting ${(broadDelay / 1e3).toFixed(0)}s for rate limit cooldown before broad search...`);
|
|
37419
|
+
await sleep(broadDelay);
|
|
37420
|
+
} else if (allCandidates.length === 0) {
|
|
37421
|
+
info(MODULE9, `Skipping broad phase delay: no results from previous phases, proceeding immediately`);
|
|
37422
|
+
}
|
|
37423
|
+
const remaining = maxResults - allCandidates.length;
|
|
37424
|
+
const result = await runPhase2(this.octokit, this.vetter, scopes, labels, config2.labels, baseQualifiers, remaining, minStars, phase0RepoSet, starredRepoSet, allCandidates, filterIssues);
|
|
37425
|
+
allCandidates.push(...result.candidates);
|
|
37426
|
+
phaseErrors["2"] = result.error;
|
|
37427
|
+
if (result.rateLimitHit)
|
|
37428
|
+
rateLimitHitDuringSearch = true;
|
|
37429
|
+
}
|
|
37298
37430
|
strategiesUsed.push("broad");
|
|
37299
37431
|
}
|
|
37300
37432
|
if (allCandidates.length < maxResults && searchBudget >= LOW_BUDGET_THRESHOLD && enabledStrategies.has("maintained")) {
|
|
37301
|
-
|
|
37433
|
+
if (interPhaseDelay > 0) {
|
|
37434
|
+
info(MODULE9, `Waiting ${(interPhaseDelay / 1e3).toFixed(0)}s between phases for rate limit management...`);
|
|
37435
|
+
await sleep(interPhaseDelay);
|
|
37436
|
+
}
|
|
37302
37437
|
const remaining = maxResults - allCandidates.length;
|
|
37303
|
-
const result = await runPhase3(this.octokit, this.vetter, langQuery, minStars, config2.projectCategories ?? [], remaining, phase0RepoSet, starredRepoSet, allCandidates, filterIssues);
|
|
37438
|
+
const result = await runPhase3(this.octokit, this.vetter, langQuery, minStars, config2.projectCategories ?? [], remaining, phase0RepoSet, starredRepoSet, starredRepos, allCandidates, filterIssues);
|
|
37304
37439
|
allCandidates.push(...result.candidates);
|
|
37305
37440
|
phaseErrors["3"] = result.error;
|
|
37306
37441
|
if (result.rateLimitHit)
|
|
@@ -37602,6 +37737,7 @@ function mergeStates(local, remote) {
|
|
|
37602
37737
|
starredReposLastFetched: pickFresherTimestamp(local.starredReposLastFetched, remote.starredReposLastFetched),
|
|
37603
37738
|
mergedPRs: unionByUrl(local.mergedPRs, remote.mergedPRs),
|
|
37604
37739
|
closedPRs: unionByUrl(local.closedPRs, remote.closedPRs),
|
|
37740
|
+
openPRs: unionByUrl(local.openPRs ?? [], remote.openPRs ?? []),
|
|
37605
37741
|
savedResults: mergeSavedResults(local.savedResults ?? [], remote.savedResults ?? []),
|
|
37606
37742
|
skippedIssues: mergeSkippedIssues(local.skippedIssues ?? [], remote.skippedIssues ?? []),
|
|
37607
37743
|
lastSearchAt: pickFresherTimestamp(local.lastSearchAt, remote.lastSearchAt),
|
|
@@ -37884,6 +38020,16 @@ var OssScout = class {
|
|
|
37884
38020
|
}
|
|
37885
38021
|
return [...repoCounts.entries()].sort((a, b) => b[1] - a[1]).map(([repo]) => repo);
|
|
37886
38022
|
}
|
|
38023
|
+
getReposWithOpenPRs() {
|
|
38024
|
+
const repoCounts = /* @__PURE__ */ new Map();
|
|
38025
|
+
for (const pr of this.state.openPRs ?? []) {
|
|
38026
|
+
const repo = extractRepoFromUrl(pr.url);
|
|
38027
|
+
if (repo) {
|
|
38028
|
+
repoCounts.set(repo, (repoCounts.get(repo) ?? 0) + 1);
|
|
38029
|
+
}
|
|
38030
|
+
}
|
|
38031
|
+
return [...repoCounts.entries()].sort((a, b) => b[1] - a[1]).map(([repo]) => repo);
|
|
38032
|
+
}
|
|
37887
38033
|
getStarredRepos() {
|
|
37888
38034
|
return this.state.starredRepos;
|
|
37889
38035
|
}
|
|
@@ -37932,6 +38078,20 @@ var OssScout = class {
|
|
|
37932
38078
|
this.updateRepoScoreFromPRs(pr.repo);
|
|
37933
38079
|
this.dirty = true;
|
|
37934
38080
|
}
|
|
38081
|
+
/**
|
|
38082
|
+
* Record that a PR is currently open in this repo.
|
|
38083
|
+
* Open PRs signal active engagement even when nothing is merged yet.
|
|
38084
|
+
*/
|
|
38085
|
+
recordOpenPR(pr) {
|
|
38086
|
+
const existing = this.state.openPRs ?? [];
|
|
38087
|
+
if (existing.some((p) => p.url === pr.url))
|
|
38088
|
+
return;
|
|
38089
|
+
this.state.openPRs = [
|
|
38090
|
+
...existing,
|
|
38091
|
+
{ url: pr.url, title: pr.title, openedAt: pr.openedAt }
|
|
38092
|
+
];
|
|
38093
|
+
this.dirty = true;
|
|
38094
|
+
}
|
|
37935
38095
|
/**
|
|
37936
38096
|
* Update repo score with observed signals.
|
|
37937
38097
|
*/
|
|
@@ -38313,7 +38473,10 @@ function registerTools(server, scout) {
|
|
|
38313
38473
|
const NUMBER_KEYS = /* @__PURE__ */ new Set([
|
|
38314
38474
|
"minStars",
|
|
38315
38475
|
"maxIssueAgeDays",
|
|
38316
|
-
"minRepoScoreThreshold"
|
|
38476
|
+
"minRepoScoreThreshold",
|
|
38477
|
+
"interPhaseDelayMs",
|
|
38478
|
+
"broadPhaseDelayMs",
|
|
38479
|
+
"skipBroadWhenSufficientResults"
|
|
38317
38480
|
]);
|
|
38318
38481
|
const BOOLEAN_KEYS = /* @__PURE__ */ new Set(["includeDocIssues"]);
|
|
38319
38482
|
server.tool(
|
package/dist/tools.js
CHANGED
|
@@ -181,6 +181,9 @@ export function registerTools(server, scout) {
|
|
|
181
181
|
"minStars",
|
|
182
182
|
"maxIssueAgeDays",
|
|
183
183
|
"minRepoScoreThreshold",
|
|
184
|
+
"interPhaseDelayMs",
|
|
185
|
+
"broadPhaseDelayMs",
|
|
186
|
+
"skipBroadWhenSufficientResults",
|
|
184
187
|
]);
|
|
185
188
|
const BOOLEAN_KEYS = new Set(["includeDocIssues"]);
|
|
186
189
|
server.tool("config-set", "Update an oss-scout configuration preference", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oss-scout/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "MCP server for oss-scout — exposes issue discovery and vetting as MCP tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,15 +18,16 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
20
20
|
"zod": "^4.3.6",
|
|
21
|
-
"@oss-scout/core": "^0.
|
|
21
|
+
"@oss-scout/core": "^0.5.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^25.5.0",
|
|
25
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
25
26
|
"esbuild": "^0.27.4",
|
|
26
27
|
"tsx": "^4.21.0",
|
|
27
28
|
"typescript": "^5.9.3",
|
|
28
|
-
"
|
|
29
|
-
"vitest": "^4.1.
|
|
29
|
+
"vite": "^8.0.5",
|
|
30
|
+
"vitest": "^4.1.4"
|
|
30
31
|
},
|
|
31
32
|
"keywords": [
|
|
32
33
|
"mcp",
|