@oss-scout/core 0.2.1 → 0.4.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/LICENSE +21 -0
- package/dist/cli.bundle.cjs +42 -38
- package/dist/cli.js +108 -1
- package/dist/commands/config.d.ts +1 -3
- package/dist/commands/config.js +7 -7
- package/dist/commands/setup.js +3 -9
- package/dist/commands/skip.d.ts +33 -0
- package/dist/commands/skip.js +89 -0
- package/dist/core/bootstrap.js +2 -2
- package/dist/core/gist-state-store.js +19 -1
- package/dist/core/issue-discovery.d.ts +3 -3
- package/dist/core/issue-discovery.js +90 -94
- package/dist/core/issue-vetting.d.ts +0 -2
- package/dist/core/issue-vetting.js +0 -4
- package/dist/core/local-state.js +2 -2
- package/dist/core/schemas.d.ts +22 -8
- package/dist/core/schemas.js +13 -4
- package/dist/core/search-phases.d.ts +21 -0
- package/dist/core/search-phases.js +157 -11
- package/dist/core/types.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/scout.d.ts +27 -2
- package/dist/scout.js +69 -3
- package/package.json +17 -14
- package/dist/core/concurrency.d.ts +0 -6
- package/dist/core/concurrency.js +0 -25
package/dist/cli.js
CHANGED
|
@@ -223,7 +223,7 @@ const configCmd = program
|
|
|
223
223
|
console.log(formatJsonSuccess(getConfigData()));
|
|
224
224
|
}
|
|
225
225
|
else {
|
|
226
|
-
runConfigShow(
|
|
226
|
+
runConfigShow();
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
catch (err) {
|
|
@@ -322,6 +322,113 @@ program
|
|
|
322
322
|
handleCommandError(err, options);
|
|
323
323
|
}
|
|
324
324
|
});
|
|
325
|
+
// ── skip command ───────────────────────────────────────────────────
|
|
326
|
+
const skipCmd = program
|
|
327
|
+
.command("skip")
|
|
328
|
+
.description("Manage the skip list — exclude issues from future searches");
|
|
329
|
+
skipCmd
|
|
330
|
+
.command("add <issue-url>")
|
|
331
|
+
.description("Skip an issue by URL")
|
|
332
|
+
.option("--json", "Output as JSON")
|
|
333
|
+
.action(async (issueUrl, options) => {
|
|
334
|
+
try {
|
|
335
|
+
const { runSkip } = await import("./commands/skip.js");
|
|
336
|
+
const state = loadLocalState();
|
|
337
|
+
const result = await runSkip({ issueUrl, state });
|
|
338
|
+
if (options.json) {
|
|
339
|
+
console.log(formatJsonSuccess(result));
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
if (result.alreadySkipped) {
|
|
343
|
+
console.log("Issue already in skip list.");
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
console.log(`Skipped: ${issueUrl}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
handleCommandError(err, options);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
skipCmd
|
|
355
|
+
.command("list")
|
|
356
|
+
.description("Show all skipped issues")
|
|
357
|
+
.option("--json", "Output as JSON")
|
|
358
|
+
.action(async (options) => {
|
|
359
|
+
try {
|
|
360
|
+
const { runSkipList } = await import("./commands/skip.js");
|
|
361
|
+
const results = runSkipList();
|
|
362
|
+
if (options.json) {
|
|
363
|
+
console.log(formatJsonSuccess(results));
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
if (results.length === 0) {
|
|
367
|
+
console.log("\nNo skipped issues.\n");
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
console.log(`\nSkipped issues (${results.length}):\n`);
|
|
371
|
+
console.log(" Repo Issue Skipped Title");
|
|
372
|
+
console.log(" ──────────────────────────────── ────── ────────── ─────");
|
|
373
|
+
for (const s of results) {
|
|
374
|
+
const repo = (s.repo || "unknown").padEnd(32).slice(0, 32);
|
|
375
|
+
const issue = s.number ? `#${s.number}`.padEnd(6) : "—".padEnd(6);
|
|
376
|
+
const skippedDate = s.skippedAt.split("T")[0] ?? "";
|
|
377
|
+
const title = s.title.length > 50
|
|
378
|
+
? s.title.slice(0, 47) + "..."
|
|
379
|
+
: s.title || s.url;
|
|
380
|
+
console.log(` ${repo} ${issue} ${skippedDate} ${title}`);
|
|
381
|
+
}
|
|
382
|
+
console.log();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
handleCommandError(err, options);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
skipCmd
|
|
390
|
+
.command("remove <issue-url>")
|
|
391
|
+
.description("Remove an issue from the skip list (unskip)")
|
|
392
|
+
.option("--json", "Output as JSON")
|
|
393
|
+
.action(async (issueUrl, options) => {
|
|
394
|
+
try {
|
|
395
|
+
const { runSkipRemove } = await import("./commands/skip.js");
|
|
396
|
+
const result = await runSkipRemove({ issueUrl });
|
|
397
|
+
if (options.json) {
|
|
398
|
+
console.log(formatJsonSuccess(result));
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
if (result.removed) {
|
|
402
|
+
console.log(`Removed from skip list: ${issueUrl}`);
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
console.log("Issue was not in the skip list.");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
handleCommandError(err, options);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
skipCmd
|
|
414
|
+
.command("clear")
|
|
415
|
+
.description("Clear all skipped issues")
|
|
416
|
+
.option("--json", "Output as JSON")
|
|
417
|
+
.action(async (options) => {
|
|
418
|
+
try {
|
|
419
|
+
const { runSkipClear } = await import("./commands/skip.js");
|
|
420
|
+
await runSkipClear();
|
|
421
|
+
if (options.json) {
|
|
422
|
+
console.log(formatJsonSuccess({ cleared: true }));
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
console.log("Skip list cleared.");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
handleCommandError(err, options);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
325
432
|
program
|
|
326
433
|
.command("vet <issue-url>")
|
|
327
434
|
.description("Vet a specific GitHub issue for claimability and project health")
|
|
@@ -5,9 +5,7 @@ import type { ScoutPreferences } from "../core/schemas.js";
|
|
|
5
5
|
/**
|
|
6
6
|
* Display current preferences in human-readable format.
|
|
7
7
|
*/
|
|
8
|
-
export declare function runConfigShow(
|
|
9
|
-
json?: boolean;
|
|
10
|
-
}): void;
|
|
8
|
+
export declare function runConfigShow(): void;
|
|
11
9
|
/**
|
|
12
10
|
* Get current preferences for JSON output.
|
|
13
11
|
*/
|
package/dist/commands/config.js
CHANGED
|
@@ -10,10 +10,10 @@ const FIELD_CONFIGS = {
|
|
|
10
10
|
excludeRepos: { type: "array" },
|
|
11
11
|
excludeOrgs: { type: "array" },
|
|
12
12
|
aiPolicyBlocklist: { type: "array" },
|
|
13
|
-
preferredOrgs: { type: "array" },
|
|
14
13
|
minStars: { type: "number" },
|
|
15
14
|
maxIssueAgeDays: { type: "number" },
|
|
16
15
|
minRepoScoreThreshold: { type: "number" },
|
|
16
|
+
interPhaseDelayMs: { type: "number" },
|
|
17
17
|
includeDocIssues: { type: "boolean" },
|
|
18
18
|
scope: { type: "enum-array", validValues: IssueScopeSchema.options },
|
|
19
19
|
projectCategories: {
|
|
@@ -26,6 +26,8 @@ const FIELD_CONFIGS = {
|
|
|
26
26
|
validValues: SearchStrategySchema.options,
|
|
27
27
|
},
|
|
28
28
|
githubUsername: { type: "string" },
|
|
29
|
+
broadPhaseDelayMs: { type: "number" },
|
|
30
|
+
skipBroadWhenSufficientResults: { type: "number" },
|
|
29
31
|
};
|
|
30
32
|
function parseBoolean(value) {
|
|
31
33
|
const lower = value.toLowerCase();
|
|
@@ -73,13 +75,9 @@ function formatArray(arr) {
|
|
|
73
75
|
/**
|
|
74
76
|
* Display current preferences in human-readable format.
|
|
75
77
|
*/
|
|
76
|
-
export function runConfigShow(
|
|
78
|
+
export function runConfigShow() {
|
|
77
79
|
const state = loadLocalState();
|
|
78
80
|
const prefs = state.preferences;
|
|
79
|
-
if (options.json) {
|
|
80
|
-
// JSON output handled by caller
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
81
|
console.log("\n⚙️ oss-scout preferences\n");
|
|
84
82
|
console.log(` githubUsername: ${prefs.githubUsername || "(not set)"}`);
|
|
85
83
|
console.log(` languages: ${formatArray(prefs.languages)}`);
|
|
@@ -88,14 +86,16 @@ export function runConfigShow(options) {
|
|
|
88
86
|
console.log(` minStars: ${prefs.minStars}`);
|
|
89
87
|
console.log(` maxIssueAgeDays: ${prefs.maxIssueAgeDays}`);
|
|
90
88
|
console.log(` minRepoScoreThreshold: ${prefs.minRepoScoreThreshold}`);
|
|
89
|
+
console.log(` interPhaseDelayMs: ${prefs.interPhaseDelayMs}ms (${(prefs.interPhaseDelayMs / 1000).toFixed(0)}s)`);
|
|
91
90
|
console.log(` includeDocIssues: ${prefs.includeDocIssues}`);
|
|
92
|
-
console.log(` preferredOrgs: ${formatArray(prefs.preferredOrgs)}`);
|
|
93
91
|
console.log(` projectCategories: ${formatArray(prefs.projectCategories)}`);
|
|
94
92
|
console.log(` excludeRepos: ${formatArray(prefs.excludeRepos)}`);
|
|
95
93
|
console.log(` excludeOrgs: ${formatArray(prefs.excludeOrgs)}`);
|
|
96
94
|
console.log(` aiPolicyBlocklist: ${formatArray(prefs.aiPolicyBlocklist)}`);
|
|
97
95
|
console.log(` defaultStrategy: ${prefs.defaultStrategy ? formatArray(prefs.defaultStrategy) : "(all)"}`);
|
|
98
96
|
console.log(` persistence: ${prefs.persistence}`);
|
|
97
|
+
console.log(` broadPhaseDelayMs: ${prefs.broadPhaseDelayMs}ms (${(prefs.broadPhaseDelayMs / 1000).toFixed(0)}s)`);
|
|
98
|
+
console.log(` skipBroadWhenSufficientResults: ${prefs.skipBroadWhenSufficientResults}`);
|
|
99
99
|
console.log();
|
|
100
100
|
}
|
|
101
101
|
/**
|
package/dist/commands/setup.js
CHANGED
|
@@ -59,11 +59,9 @@ export async function runSetup(options) {
|
|
|
59
59
|
const usernameInput = await ask(rl, usernamePrompt);
|
|
60
60
|
const githubUsername = usernameInput || usernameDefault;
|
|
61
61
|
// Languages
|
|
62
|
-
const defaultLangs = "
|
|
63
|
-
const langsInput = await ask(rl, `Preferred languages [${defaultLangs}]: `);
|
|
64
|
-
const languages = langsInput
|
|
65
|
-
? parseCSV(langsInput)
|
|
66
|
-
: ["typescript", "javascript"];
|
|
62
|
+
const defaultLangs = "any (all languages)";
|
|
63
|
+
const langsInput = await ask(rl, `Preferred languages (comma-separated, or "any" for all) [${defaultLangs}]: `);
|
|
64
|
+
const languages = langsInput ? parseCSV(langsInput) : ["any"];
|
|
67
65
|
// Issue labels
|
|
68
66
|
const defaultLabels = "good first issue, help wanted";
|
|
69
67
|
const labelsInput = await ask(rl, `Issue labels to search for [${defaultLabels}]: `);
|
|
@@ -79,9 +77,6 @@ export async function runSetup(options) {
|
|
|
79
77
|
// Minimum stars
|
|
80
78
|
const minStarsInput = await ask(rl, "Minimum repo stars [50]: ");
|
|
81
79
|
const minStars = minStarsInput ? parseInt(minStarsInput, 10) : 50;
|
|
82
|
-
// Preferred organizations
|
|
83
|
-
const orgsInput = await ask(rl, "Preferred organizations (comma-separated, optional): ");
|
|
84
|
-
const preferredOrgs = parseCSV(orgsInput);
|
|
85
80
|
// Project categories
|
|
86
81
|
const categoryOptions = ALL_CATEGORIES.join(", ");
|
|
87
82
|
const categoriesInput = await ask(rl, `Project categories (${categoryOptions}) [none]: `);
|
|
@@ -95,7 +90,6 @@ export async function runSetup(options) {
|
|
|
95
90
|
labels,
|
|
96
91
|
scope: scope.length > 0 ? scope : undefined,
|
|
97
92
|
excludeRepos,
|
|
98
|
-
preferredOrgs,
|
|
99
93
|
projectCategories,
|
|
100
94
|
minStars: isNaN(minStars) ? 50 : minStars,
|
|
101
95
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skip command — manage the skip list for excluding issues from future searches.
|
|
3
|
+
*/
|
|
4
|
+
import type { SkippedIssue, ScoutState } from "../core/schemas.js";
|
|
5
|
+
/**
|
|
6
|
+
* Skip an issue by URL — adds it to the skip list and removes it from saved results.
|
|
7
|
+
* Tries to enrich metadata from saved results if available.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runSkip(options: {
|
|
10
|
+
issueUrl: string;
|
|
11
|
+
state?: ScoutState;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
skipped: boolean;
|
|
14
|
+
alreadySkipped: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* List all skipped issues.
|
|
18
|
+
*/
|
|
19
|
+
export declare function runSkipList(options?: {
|
|
20
|
+
state?: ScoutState;
|
|
21
|
+
}): SkippedIssue[];
|
|
22
|
+
/**
|
|
23
|
+
* Clear all skipped issues.
|
|
24
|
+
*/
|
|
25
|
+
export declare function runSkipClear(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Remove a specific issue from the skip list (unskip).
|
|
28
|
+
*/
|
|
29
|
+
export declare function runSkipRemove(options: {
|
|
30
|
+
issueUrl: string;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
removed: boolean;
|
|
33
|
+
}>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skip command — manage the skip list for excluding issues from future searches.
|
|
3
|
+
*/
|
|
4
|
+
import { loadLocalState, saveLocalState } from "../core/local-state.js";
|
|
5
|
+
import { createScout } from "../scout.js";
|
|
6
|
+
import { getGitHubToken } from "../core/utils.js";
|
|
7
|
+
/**
|
|
8
|
+
* Create an OssScout instance for skip operations.
|
|
9
|
+
* Uses gist persistence when a token is available, otherwise provided-state mode.
|
|
10
|
+
*/
|
|
11
|
+
async function createSkipScout(state) {
|
|
12
|
+
const token = getGitHubToken() ?? "";
|
|
13
|
+
if (token) {
|
|
14
|
+
return createScout({
|
|
15
|
+
githubToken: token,
|
|
16
|
+
persistence: "provided",
|
|
17
|
+
initialState: state,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return createScout({
|
|
21
|
+
githubToken: "",
|
|
22
|
+
persistence: "provided",
|
|
23
|
+
initialState: state,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Skip an issue by URL — adds it to the skip list and removes it from saved results.
|
|
28
|
+
* Tries to enrich metadata from saved results if available.
|
|
29
|
+
*/
|
|
30
|
+
export async function runSkip(options) {
|
|
31
|
+
const state = options.state ?? loadLocalState();
|
|
32
|
+
const scout = await createSkipScout(state);
|
|
33
|
+
const alreadySkipped = scout
|
|
34
|
+
.getSkippedIssues()
|
|
35
|
+
.some((s) => s.url === options.issueUrl);
|
|
36
|
+
if (alreadySkipped) {
|
|
37
|
+
return { skipped: false, alreadySkipped: true };
|
|
38
|
+
}
|
|
39
|
+
// Try to enrich metadata from saved results
|
|
40
|
+
const saved = scout
|
|
41
|
+
.getSavedResults()
|
|
42
|
+
.find((r) => r.issueUrl === options.issueUrl);
|
|
43
|
+
const metadata = saved
|
|
44
|
+
? { repo: saved.repo, number: saved.number, title: saved.title }
|
|
45
|
+
: parseIssueUrl(options.issueUrl);
|
|
46
|
+
scout.skipIssue(options.issueUrl, metadata);
|
|
47
|
+
saveLocalState(scout.getState());
|
|
48
|
+
await scout.checkpoint();
|
|
49
|
+
return { skipped: true, alreadySkipped: false };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* List all skipped issues.
|
|
53
|
+
*/
|
|
54
|
+
export function runSkipList(options) {
|
|
55
|
+
const state = options?.state ?? loadLocalState();
|
|
56
|
+
return state.skippedIssues ?? [];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Clear all skipped issues.
|
|
60
|
+
*/
|
|
61
|
+
export async function runSkipClear() {
|
|
62
|
+
const state = loadLocalState();
|
|
63
|
+
const scout = await createSkipScout(state);
|
|
64
|
+
scout.clearSkippedIssues();
|
|
65
|
+
saveLocalState(scout.getState());
|
|
66
|
+
await scout.checkpoint();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Remove a specific issue from the skip list (unskip).
|
|
70
|
+
*/
|
|
71
|
+
export async function runSkipRemove(options) {
|
|
72
|
+
const state = loadLocalState();
|
|
73
|
+
const scout = await createSkipScout(state);
|
|
74
|
+
const before = scout.getSkippedIssues().length;
|
|
75
|
+
scout.unskipIssue(options.issueUrl);
|
|
76
|
+
const removed = before !== scout.getSkippedIssues().length;
|
|
77
|
+
saveLocalState(scout.getState());
|
|
78
|
+
await scout.checkpoint();
|
|
79
|
+
return { removed };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Parse a GitHub issue URL to extract repo and number.
|
|
83
|
+
*/
|
|
84
|
+
function parseIssueUrl(url) {
|
|
85
|
+
const match = url.match(/github\.com\/([^/]+\/[^/]+)\/issues\/(\d+)/);
|
|
86
|
+
if (!match)
|
|
87
|
+
return undefined;
|
|
88
|
+
return { repo: match[1], number: parseInt(match[2], 10), title: "" };
|
|
89
|
+
}
|
package/dist/core/bootstrap.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { getOctokit, checkRateLimit } from "./github.js";
|
|
6
6
|
import { debug, warn } from "./logger.js";
|
|
7
|
-
import { errorMessage } from "./errors.js";
|
|
7
|
+
import { ConfigurationError, errorMessage } from "./errors.js";
|
|
8
8
|
import { extractRepoFromUrl } from "./utils.js";
|
|
9
9
|
const MODULE = "bootstrap";
|
|
10
10
|
const STARRED_MAX_PAGES = 5;
|
|
@@ -13,7 +13,7 @@ const PER_PAGE = 100;
|
|
|
13
13
|
export async function bootstrapScout(scout, token) {
|
|
14
14
|
const username = scout.getPreferences().githubUsername;
|
|
15
15
|
if (!username) {
|
|
16
|
-
throw new
|
|
16
|
+
throw new ConfigurationError("GitHub username not configured. Run `oss-scout setup` first.");
|
|
17
17
|
}
|
|
18
18
|
const rateLimit = await checkRateLimit(token);
|
|
19
19
|
debug(MODULE, `Rate limit: ${rateLimit.remaining}/${rateLimit.limit}, resets at ${rateLimit.resetAt}`);
|
|
@@ -50,11 +50,16 @@ export class GistStateStore {
|
|
|
50
50
|
warn(MODULE, "No gist ID — cannot push");
|
|
51
51
|
return false;
|
|
52
52
|
}
|
|
53
|
+
const json = JSON.stringify(state, null, 2);
|
|
54
|
+
if (json.length > 900000) {
|
|
55
|
+
warn(MODULE, `State too large for gist (${Math.round(json.length / 1024)}KB). Consider clearing old results with 'oss-scout results clear'.`);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
53
58
|
try {
|
|
54
59
|
await this.octokit.gists.update({
|
|
55
60
|
gist_id: this.gistId,
|
|
56
61
|
files: {
|
|
57
|
-
[GIST_FILENAME]: { content:
|
|
62
|
+
[GIST_FILENAME]: { content: json },
|
|
58
63
|
},
|
|
59
64
|
});
|
|
60
65
|
debug(MODULE, "State pushed to gist");
|
|
@@ -248,6 +253,7 @@ export function mergeStates(local, remote) {
|
|
|
248
253
|
mergedPRs: unionByUrl(local.mergedPRs, remote.mergedPRs),
|
|
249
254
|
closedPRs: unionByUrl(local.closedPRs, remote.closedPRs),
|
|
250
255
|
savedResults: mergeSavedResults(local.savedResults ?? [], remote.savedResults ?? []),
|
|
256
|
+
skippedIssues: mergeSkippedIssues(local.skippedIssues ?? [], remote.skippedIssues ?? []),
|
|
251
257
|
lastSearchAt: pickFresherTimestamp(local.lastSearchAt, remote.lastSearchAt),
|
|
252
258
|
lastRunAt: pickFresherTimestamp(local.lastRunAt, remote.lastRunAt) ??
|
|
253
259
|
new Date().toISOString(),
|
|
@@ -302,6 +308,18 @@ function mergeSavedResults(local, remote) {
|
|
|
302
308
|
}
|
|
303
309
|
return [...merged.values()];
|
|
304
310
|
}
|
|
311
|
+
function mergeSkippedIssues(local, remote) {
|
|
312
|
+
const merged = new Map();
|
|
313
|
+
for (const item of local)
|
|
314
|
+
merged.set(item.url, item);
|
|
315
|
+
for (const item of remote) {
|
|
316
|
+
const existing = merged.get(item.url);
|
|
317
|
+
if (!existing || item.skippedAt > existing.skippedAt) {
|
|
318
|
+
merged.set(item.url, item);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return [...merged.values()];
|
|
322
|
+
}
|
|
305
323
|
function pickFresherTimestamp(a, b) {
|
|
306
324
|
if (!a)
|
|
307
325
|
return b;
|
|
@@ -19,7 +19,6 @@ import { type ScoutStateReader } from "./issue-vetting.js";
|
|
|
19
19
|
*
|
|
20
20
|
* Search phases (in priority order):
|
|
21
21
|
* 0. Repos where user has merged PRs (highest merge probability)
|
|
22
|
-
* 0.5. Preferred organizations
|
|
23
22
|
* 1. Starred repos
|
|
24
23
|
* 2. General label-filtered search
|
|
25
24
|
* 3. Actively maintained repos
|
|
@@ -47,8 +46,8 @@ export declare class IssueDiscovery {
|
|
|
47
46
|
getStarredRepos(): string[];
|
|
48
47
|
/**
|
|
49
48
|
* Search for issues matching our criteria.
|
|
50
|
-
* Searches in priority order: merged-PR repos first (no label filter), then
|
|
51
|
-
*
|
|
49
|
+
* Searches in priority order: merged-PR repos first (no label filter), then starred
|
|
50
|
+
* repos, then general search, then actively maintained repos.
|
|
52
51
|
* Filters out issues from low-scoring and excluded repos.
|
|
53
52
|
*
|
|
54
53
|
* @param options - Search configuration
|
|
@@ -74,6 +73,7 @@ export declare class IssueDiscovery {
|
|
|
74
73
|
labels?: string[];
|
|
75
74
|
maxResults?: number;
|
|
76
75
|
strategies?: SearchStrategy[];
|
|
76
|
+
skippedUrls?: Set<string>;
|
|
77
77
|
}): Promise<{
|
|
78
78
|
candidates: IssueCandidate[];
|
|
79
79
|
strategiesUsed: SearchStrategy[];
|