@oss-scout/core 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.bundle.cjs +29 -29
- package/dist/core/issue-discovery.js +21 -1
- package/dist/core/types.d.ts +15 -0
- package/dist/scout.js +12 -0
- package/package.json +1 -1
|
@@ -42,6 +42,15 @@ const PHASE0_PER_PAGE = 30;
|
|
|
42
42
|
* screens staleness, existing PRs, and claims downstream.
|
|
43
43
|
*/
|
|
44
44
|
const CONTRIBUTED_REPO_MAX_AGE_DAYS = 365;
|
|
45
|
+
/**
|
|
46
|
+
* Cap on Phase 0's share of `maxResults`. Phase 0 (contributed repos) fetches
|
|
47
|
+
* deeply (`PHASE0_PER_PAGE`) and can otherwise fill the entire result budget,
|
|
48
|
+
* which makes the `allCandidates.length < maxResults` gate false for every
|
|
49
|
+
* later phase so starred (Phase 1) and broad (Phases 2/3) never run. Reserving
|
|
50
|
+
* half the budget for the other strategies keeps each search round varied
|
|
51
|
+
* instead of returning only contributed-repo results.
|
|
52
|
+
*/
|
|
53
|
+
const PHASE0_MAX_SHARE = 0.5;
|
|
45
54
|
/** Build a reusable filter function from config. */
|
|
46
55
|
function buildIssueFilter(config) {
|
|
47
56
|
return (items) => {
|
|
@@ -421,8 +430,19 @@ export class IssueDiscovery {
|
|
|
421
430
|
break;
|
|
422
431
|
}
|
|
423
432
|
const phase0RepoSet = new Set(phase0Repos);
|
|
433
|
+
// Only cap Phase 0 when a later phase can actually consume the reserved
|
|
434
|
+
// budget — otherwise (no starred repos, broad/maintained disabled) the
|
|
435
|
+
// reservation would just shrink the result set with nothing to fill it.
|
|
436
|
+
const otherStrategiesCanRun = (starredRepos.length > 0 && enabledStrategies.has("starred")) ||
|
|
437
|
+
enabledStrategies.has("broad") ||
|
|
438
|
+
enabledStrategies.has("maintained");
|
|
424
439
|
if (phase0Repos.length > 0 && enabledStrategies.has("merged")) {
|
|
425
|
-
|
|
440
|
+
// Cap Phase 0's share so it can't consume the whole budget and starve
|
|
441
|
+
// the starred/broad phases (which gate on allCandidates < maxResults).
|
|
442
|
+
const phase0Cap = otherStrategiesCanRun
|
|
443
|
+
? Math.max(1, Math.ceil(maxResults * PHASE0_MAX_SHARE))
|
|
444
|
+
: maxResults;
|
|
445
|
+
const remaining = Math.min(maxResults - allCandidates.length, phase0Cap);
|
|
426
446
|
if (remaining > 0) {
|
|
427
447
|
const result = await runPhase0(this.octokit, this.vetter, phase0Repos, remaining, filterIssuesPhase0);
|
|
428
448
|
recordPhaseResult("0", result);
|
package/dist/core/types.d.ts
CHANGED
|
@@ -281,6 +281,21 @@ export interface SearchOptions {
|
|
|
281
281
|
* `interPhaseDelayMs` for the rationale (#143).
|
|
282
282
|
*/
|
|
283
283
|
broadPhaseDelayMs?: number;
|
|
284
|
+
/**
|
|
285
|
+
* Exclude issues already surfaced by a recent search so consecutive
|
|
286
|
+
* searches rotate to fresh candidates instead of returning the same set
|
|
287
|
+
* (#249). A result counts as "recently surfaced" when its `lastSeenAt`
|
|
288
|
+
* (recorded by `saveResults`) is within `recentlySurfacedTtlDays`.
|
|
289
|
+
* Defaults to `true`. Pass `false` to force-resurface (e.g. an explicit
|
|
290
|
+
* "search the same pool again" request).
|
|
291
|
+
*/
|
|
292
|
+
excludeRecentlySurfaced?: boolean;
|
|
293
|
+
/**
|
|
294
|
+
* TTL in days for the `excludeRecentlySurfaced` rotation window (#249).
|
|
295
|
+
* Results last surfaced more than this many days ago are eligible to
|
|
296
|
+
* resurface. Defaults to 7.
|
|
297
|
+
*/
|
|
298
|
+
recentlySurfacedTtlDays?: number;
|
|
284
299
|
}
|
|
285
300
|
/** Result of a search operation. */
|
|
286
301
|
export interface SearchResult {
|
package/dist/scout.js
CHANGED
|
@@ -170,6 +170,18 @@ export class OssScout {
|
|
|
170
170
|
// Auto-cull expired skips before searching
|
|
171
171
|
this.cullExpiredSkips();
|
|
172
172
|
const skippedUrls = new Set((this.state.skippedIssues ?? []).map((s) => s.url));
|
|
173
|
+
// Rotation (#249): also exclude issues surfaced by a recent search so
|
|
174
|
+
// consecutive searches return fresh candidates instead of the same set.
|
|
175
|
+
// Folded into the same exclusion set the issue filter already honors.
|
|
176
|
+
if (options?.excludeRecentlySurfaced ?? true) {
|
|
177
|
+
const ttlDays = options?.recentlySurfacedTtlDays ?? 7;
|
|
178
|
+
const cutoff = Date.now() - ttlDays * 24 * 60 * 60 * 1000;
|
|
179
|
+
for (const r of this.state.savedResults ?? []) {
|
|
180
|
+
const seen = Date.parse(r.lastSeenAt);
|
|
181
|
+
if (!Number.isNaN(seen) && seen >= cutoff)
|
|
182
|
+
skippedUrls.add(r.issueUrl);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
173
185
|
const discovery = new IssueDiscovery(this.githubToken, this.state.preferences, this);
|
|
174
186
|
// Per-call flags override the persisted personalization defaults (#168).
|
|
175
187
|
// An empty preference array reads as "no boost" just like an absent flag.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oss-scout/core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
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": {
|