@oss-scout/core 1.1.0 → 1.2.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.
@@ -28,6 +28,20 @@ const MODULE = "issue-discovery";
28
28
  const LOW_BUDGET_THRESHOLD = 20;
29
29
  /** If remaining search quota is below this, only run Phase 0. */
30
30
  const CRITICAL_BUDGET_THRESHOLD = 10;
31
+ /**
32
+ * Page size for Phase 0 (repos the user has contributed to). Larger than the
33
+ * default 5 so the backlog of open issues in known repos is reachable, not
34
+ * just the 5 newest-created. One `listForRepo` call regardless of page size,
35
+ * so this widens the candidate pool at no extra REST cost.
36
+ */
37
+ const PHASE0_PER_PAGE = 30;
38
+ /**
39
+ * Max issue age (by last activity) for Phase 0 contributed repos. Relaxed well
40
+ * past the default `maxIssueAgeDays` (90) because in a repo the user already
41
+ * knows, an older-but-still-open issue is still worth evaluating — the vetter
42
+ * screens staleness, existing PRs, and claims downstream.
43
+ */
44
+ const CONTRIBUTED_REPO_MAX_AGE_DAYS = 365;
31
45
  /** Build a reusable filter function from config. */
32
46
  function buildIssueFilter(config) {
33
47
  return (items) => {
@@ -63,8 +77,8 @@ function buildIssueFilter(config) {
63
77
  }
64
78
  /** Phase 0: Search repos where user has merged PRs (highest merge probability). */
65
79
  async function runPhase0(octokit, vetter, repos, maxResults, filterIssues) {
66
- info(MODULE, `Phase 0: Searching issues in ${repos.length} merged-PR repos (no label filter)...`);
67
- const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, repos, [], maxResults, "merged_pr", filterIssues);
80
+ info(MODULE, `Phase 0: Searching issues in ${repos.length} merged-PR repos (no label filter, ${PHASE0_PER_PAGE}/repo)...`);
81
+ const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, repos, [], maxResults, "merged_pr", filterIssues, PHASE0_PER_PAGE);
68
82
  info(MODULE, `Found ${candidates.length} candidates from merged-PR repos`);
69
83
  return {
70
84
  candidates,
@@ -374,15 +388,24 @@ export class IssueDiscovery {
374
388
  if (aiBlocklisted.size > 0) {
375
389
  debug(MODULE, `[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`);
376
390
  }
377
- const filterIssues = buildIssueFilter({
391
+ const baseFilterConfig = {
378
392
  excludedRepos: new Set(config.excludeRepos.map((r) => r.toLowerCase())),
379
393
  excludeOrgs: new Set((config.excludeOrgs ?? []).map((o) => o.toLowerCase())),
380
394
  aiBlocklisted,
381
395
  lowScoringRepos,
382
396
  skippedUrls: options.skippedUrls ?? new Set(),
383
- maxAgeDays: config.maxIssueAgeDays || 90,
384
397
  now: new Date(),
385
398
  includeDocIssues: config.includeDocIssues ?? true,
399
+ };
400
+ const filterIssues = buildIssueFilter({
401
+ ...baseFilterConfig,
402
+ maxAgeDays: config.maxIssueAgeDays || 90,
403
+ });
404
+ // Phase 0 (contributed repos) gets a relaxed age window so the existing
405
+ // backlog surfaces, not just issues active in the last 90 days.
406
+ const filterIssuesPhase0 = buildIssueFilter({
407
+ ...baseFilterConfig,
408
+ maxAgeDays: CONTRIBUTED_REPO_MAX_AGE_DAYS,
386
409
  });
387
410
  // Phase 0: Repos the user has engaged with — merged PRs first (strongest
388
411
  // signal), then open PRs (active engagement even without a merge yet).
@@ -401,7 +424,7 @@ export class IssueDiscovery {
401
424
  if (phase0Repos.length > 0 && enabledStrategies.has("merged")) {
402
425
  const remaining = maxResults - allCandidates.length;
403
426
  if (remaining > 0) {
404
- const result = await runPhase0(this.octokit, this.vetter, phase0Repos, remaining, filterIssues);
427
+ const result = await runPhase0(this.octokit, this.vetter, phase0Repos, remaining, filterIssuesPhase0);
405
428
  recordPhaseResult("0", result);
406
429
  }
407
430
  strategiesUsed.push("merged");
@@ -43,7 +43,7 @@ export declare function fetchIssuesFromMaintainedRepos(octokit: Octokit, repos:
43
43
  * calls `GET /repos/{owner}/{repo}/issues` which counts against the much
44
44
  * larger Core API rate limit and avoids consuming the scarce Search quota.
45
45
  */
46
- export declare function fetchIssuesFromKnownRepos(octokit: Octokit, vetter: IssueVetter, repos: string[], labels: string[], maxResults: number, priority: SearchPriority, filterFn: (items: GitHubSearchItem[]) => GitHubSearchItem[]): Promise<{
46
+ export declare function fetchIssuesFromKnownRepos(octokit: Octokit, vetter: IssueVetter, repos: string[], labels: string[], maxResults: number, priority: SearchPriority, filterFn: (items: GitHubSearchItem[]) => GitHubSearchItem[], perPage?: number): Promise<{
47
47
  candidates: IssueCandidate[];
48
48
  allReposFailed: boolean;
49
49
  rateLimitHit: boolean;
@@ -186,7 +186,7 @@ export async function fetchIssuesFromMaintainedRepos(octokit, repos, minStars, m
186
186
  * calls `GET /repos/{owner}/{repo}/issues` which counts against the much
187
187
  * larger Core API rate limit and avoids consuming the scarce Search quota.
188
188
  */
189
- export async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResults, priority, filterFn) {
189
+ export async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResults, priority, filterFn, perPage = 5) {
190
190
  const candidates = [];
191
191
  let failedRepos = 0;
192
192
  let rateLimitFailures = 0;
@@ -213,7 +213,7 @@ export async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels,
213
213
  state: "open",
214
214
  sort: "created",
215
215
  direction: "desc",
216
- per_page: 5,
216
+ per_page: perPage,
217
217
  ...(label !== undefined ? { labels: label } : {}),
218
218
  });
219
219
  for (const issue of response.data) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oss-scout/core",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Personalized GitHub issue finder with multi-strategy search, deep vetting, and viability scoring — CLI, library, MCP server, and Claude Code plugin",
5
5
  "type": "module",
6
6
  "bin": {