@oss-scout/core 0.2.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/cli.bundle.cjs +42 -42
- package/dist/cli.js +110 -86
- package/dist/commands/config.d.ts +1 -1
- package/dist/commands/config.js +76 -72
- package/dist/commands/results.d.ts +1 -1
- package/dist/commands/results.js +1 -1
- package/dist/commands/search.d.ts +2 -2
- package/dist/commands/search.js +16 -6
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +27 -21
- package/dist/commands/validation.d.ts +1 -1
- package/dist/commands/validation.js +1 -1
- package/dist/commands/vet-list.d.ts +2 -2
- package/dist/commands/vet-list.js +12 -5
- package/dist/commands/vet.d.ts +3 -3
- package/dist/commands/vet.js +9 -5
- package/dist/core/bootstrap.d.ts +1 -1
- package/dist/core/bootstrap.js +20 -16
- package/dist/core/category-mapping.d.ts +1 -1
- package/dist/core/category-mapping.js +104 -13
- package/dist/core/errors.d.ts +8 -1
- package/dist/core/errors.js +31 -19
- package/dist/core/gist-state-store.d.ts +1 -1
- package/dist/core/gist-state-store.js +36 -27
- package/dist/core/github.d.ts +1 -1
- package/dist/core/github.js +5 -5
- package/dist/core/http-cache.js +26 -22
- package/dist/core/issue-discovery.d.ts +3 -3
- package/dist/core/issue-discovery.js +325 -277
- package/dist/core/issue-eligibility.d.ts +2 -2
- package/dist/core/issue-eligibility.js +26 -21
- package/dist/core/issue-filtering.js +23 -15
- package/dist/core/issue-scoring.js +1 -1
- package/dist/core/issue-vetting.d.ts +2 -2
- package/dist/core/issue-vetting.js +66 -53
- package/dist/core/local-state.d.ts +1 -1
- package/dist/core/local-state.js +16 -14
- package/dist/core/repo-health.d.ts +2 -2
- package/dist/core/repo-health.js +46 -35
- package/dist/core/schemas.d.ts +1 -1
- package/dist/core/schemas.js +40 -18
- package/dist/core/search-budget.js +3 -3
- package/dist/core/search-phases.d.ts +6 -6
- package/dist/core/search-phases.js +23 -19
- package/dist/core/types.d.ts +9 -9
- package/dist/core/types.js +15 -3
- package/dist/core/utils.d.ts +10 -1
- package/dist/core/utils.js +44 -25
- package/dist/formatters/json.d.ts +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +5 -5
- package/dist/scout.d.ts +4 -5
- package/dist/scout.js +72 -31
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Search command — finds contributable issues using multi-strategy search.
|
|
3
3
|
*/
|
|
4
|
-
import type { ScoutState, SearchStrategy } from
|
|
4
|
+
import type { ScoutState, SearchStrategy } from "../core/schemas.js";
|
|
5
5
|
export interface SearchOutput {
|
|
6
6
|
candidates: Array<{
|
|
7
7
|
issue: {
|
|
@@ -12,7 +12,7 @@ export interface SearchOutput {
|
|
|
12
12
|
url: string;
|
|
13
13
|
labels: string[];
|
|
14
14
|
};
|
|
15
|
-
recommendation:
|
|
15
|
+
recommendation: "approve" | "skip" | "needs_review";
|
|
16
16
|
reasonsToApprove: string[];
|
|
17
17
|
reasonsToSkip: string[];
|
|
18
18
|
searchPriority: string;
|
package/dist/commands/search.js
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Search command — finds contributable issues using multi-strategy search.
|
|
3
3
|
*/
|
|
4
|
-
import { createScout } from
|
|
5
|
-
import { requireGitHubToken } from
|
|
6
|
-
import { saveLocalState } from
|
|
4
|
+
import { createScout } from "../scout.js";
|
|
5
|
+
import { requireGitHubToken } from "../core/utils.js";
|
|
6
|
+
import { saveLocalState } from "../core/local-state.js";
|
|
7
7
|
export async function runSearch(options) {
|
|
8
8
|
const token = requireGitHubToken();
|
|
9
9
|
const scout = options.state
|
|
10
|
-
? await createScout({
|
|
10
|
+
? await createScout({
|
|
11
|
+
githubToken: token,
|
|
12
|
+
persistence: "provided",
|
|
13
|
+
initialState: options.state,
|
|
14
|
+
})
|
|
11
15
|
: await createScout({ githubToken: token });
|
|
12
|
-
const result = await scout.search({
|
|
16
|
+
const result = await scout.search({
|
|
17
|
+
maxResults: options.maxResults,
|
|
18
|
+
strategies: options.strategies,
|
|
19
|
+
});
|
|
13
20
|
// Persist results to local state and gist
|
|
14
21
|
scout.saveResults(result.candidates);
|
|
15
22
|
saveLocalState(scout.getState());
|
|
16
|
-
await scout.checkpoint();
|
|
23
|
+
const persisted = await scout.checkpoint();
|
|
24
|
+
if (!persisted) {
|
|
25
|
+
console.error("Warning: changes saved locally but gist sync failed.");
|
|
26
|
+
}
|
|
17
27
|
return {
|
|
18
28
|
candidates: result.candidates.map((c) => {
|
|
19
29
|
const repoScoreRecord = scout.getRepoScoreRecord(c.issue.repo);
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Setup command — interactive first-run configuration for oss-scout.
|
|
3
3
|
*/
|
|
4
|
-
import type { ScoutPreferences } from
|
|
4
|
+
import type { ScoutPreferences } from "../core/schemas.js";
|
|
5
5
|
interface ReadlineInterface {
|
|
6
6
|
question(query: string, callback: (answer: string) => void): void;
|
|
7
7
|
close(): void;
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Setup command — interactive first-run configuration for oss-scout.
|
|
3
3
|
*/
|
|
4
|
-
import * as readline from
|
|
5
|
-
import { execFile } from
|
|
6
|
-
import { ScoutPreferencesSchema, ProjectCategorySchema, IssueScopeSchema } from
|
|
4
|
+
import * as readline from "readline";
|
|
5
|
+
import { execFile } from "child_process";
|
|
6
|
+
import { ScoutPreferencesSchema, ProjectCategorySchema, IssueScopeSchema, } from "../core/schemas.js";
|
|
7
7
|
const ALL_CATEGORIES = ProjectCategorySchema.options;
|
|
8
8
|
const ALL_SCOPES = IssueScopeSchema.options;
|
|
9
9
|
function createReadlineInterface() {
|
|
@@ -19,9 +19,9 @@ function ask(rl, query) {
|
|
|
19
19
|
}
|
|
20
20
|
function detectGitHubUsername() {
|
|
21
21
|
return new Promise((resolve) => {
|
|
22
|
-
execFile(
|
|
22
|
+
execFile("gh", ["api", "user", "--jq", ".login"], { timeout: 5000 }, (err, stdout) => {
|
|
23
23
|
if (err || !stdout.trim()) {
|
|
24
|
-
resolve(
|
|
24
|
+
resolve("");
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
27
27
|
resolve(stdout.trim());
|
|
@@ -31,7 +31,7 @@ function detectGitHubUsername() {
|
|
|
31
31
|
}
|
|
32
32
|
function parseCSV(input) {
|
|
33
33
|
return input
|
|
34
|
-
.split(
|
|
34
|
+
.split(",")
|
|
35
35
|
.map((s) => s.trim())
|
|
36
36
|
.filter((s) => s.length > 0);
|
|
37
37
|
}
|
|
@@ -48,40 +48,46 @@ export async function runSetup(options) {
|
|
|
48
48
|
const rl = options?.rl ?? createReadlineInterface();
|
|
49
49
|
const detect = options?.detectUsername ?? detectGitHubUsername;
|
|
50
50
|
try {
|
|
51
|
-
console.log(
|
|
51
|
+
console.log("\n🔧 oss-scout setup\n");
|
|
52
52
|
// Detect GitHub username
|
|
53
|
-
console.log(
|
|
53
|
+
console.log("Detecting GitHub username...");
|
|
54
54
|
const detectedUsername = await detect();
|
|
55
|
-
const usernameDefault = detectedUsername ||
|
|
55
|
+
const usernameDefault = detectedUsername || "";
|
|
56
56
|
const usernamePrompt = detectedUsername
|
|
57
57
|
? `GitHub username [${detectedUsername}]: `
|
|
58
|
-
:
|
|
58
|
+
: "GitHub username: ";
|
|
59
59
|
const usernameInput = await ask(rl, usernamePrompt);
|
|
60
60
|
const githubUsername = usernameInput || usernameDefault;
|
|
61
61
|
// Languages
|
|
62
|
-
const defaultLangs =
|
|
62
|
+
const defaultLangs = "typescript, javascript";
|
|
63
63
|
const langsInput = await ask(rl, `Preferred languages [${defaultLangs}]: `);
|
|
64
|
-
const languages = langsInput
|
|
64
|
+
const languages = langsInput
|
|
65
|
+
? parseCSV(langsInput)
|
|
66
|
+
: ["typescript", "javascript"];
|
|
65
67
|
// Issue labels
|
|
66
|
-
const defaultLabels =
|
|
68
|
+
const defaultLabels = "good first issue, help wanted";
|
|
67
69
|
const labelsInput = await ask(rl, `Issue labels to search for [${defaultLabels}]: `);
|
|
68
|
-
const labels = labelsInput
|
|
70
|
+
const labels = labelsInput
|
|
71
|
+
? parseCSV(labelsInput)
|
|
72
|
+
: ["good first issue", "help wanted"];
|
|
69
73
|
// Difficulty scope
|
|
70
|
-
const scopeOptions = ALL_SCOPES.join(
|
|
74
|
+
const scopeOptions = ALL_SCOPES.join(", ");
|
|
71
75
|
const scopeInput = await ask(rl, `Difficulty scope (${scopeOptions}) [all]: `);
|
|
72
|
-
const scope = scopeInput
|
|
76
|
+
const scope = scopeInput
|
|
77
|
+
? parseMultiSelect(scopeInput, ALL_SCOPES)
|
|
78
|
+
: [...ALL_SCOPES];
|
|
73
79
|
// Minimum stars
|
|
74
|
-
const minStarsInput = await ask(rl,
|
|
80
|
+
const minStarsInput = await ask(rl, "Minimum repo stars [50]: ");
|
|
75
81
|
const minStars = minStarsInput ? parseInt(minStarsInput, 10) : 50;
|
|
76
82
|
// Preferred organizations
|
|
77
|
-
const orgsInput = await ask(rl,
|
|
83
|
+
const orgsInput = await ask(rl, "Preferred organizations (comma-separated, optional): ");
|
|
78
84
|
const preferredOrgs = parseCSV(orgsInput);
|
|
79
85
|
// Project categories
|
|
80
|
-
const categoryOptions = ALL_CATEGORIES.join(
|
|
86
|
+
const categoryOptions = ALL_CATEGORIES.join(", ");
|
|
81
87
|
const categoriesInput = await ask(rl, `Project categories (${categoryOptions}) [none]: `);
|
|
82
88
|
const projectCategories = parseMultiSelect(categoriesInput, ALL_CATEGORIES);
|
|
83
89
|
// Repos to exclude
|
|
84
|
-
const excludeInput = await ask(rl,
|
|
90
|
+
const excludeInput = await ask(rl, "Repos to exclude (owner/repo, comma-separated, optional): ");
|
|
85
91
|
const excludeRepos = parseCSV(excludeInput);
|
|
86
92
|
const prefs = ScoutPreferencesSchema.parse({
|
|
87
93
|
githubUsername,
|
|
@@ -93,7 +99,7 @@ export async function runSetup(options) {
|
|
|
93
99
|
projectCategories,
|
|
94
100
|
minStars: isNaN(minStars) ? 50 : minStars,
|
|
95
101
|
});
|
|
96
|
-
console.log(
|
|
102
|
+
console.log("\n✅ Setup complete! Preferences saved.\n");
|
|
97
103
|
return prefs;
|
|
98
104
|
}
|
|
99
105
|
finally {
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Shared validation patterns and helpers for CLI commands.
|
|
3
3
|
*/
|
|
4
4
|
export declare const ISSUE_URL_PATTERN: RegExp;
|
|
5
|
-
export declare function validateGitHubUrl(url: string, pattern: RegExp, entityType:
|
|
5
|
+
export declare function validateGitHubUrl(url: string, pattern: RegExp, entityType: "issue"): void;
|
|
6
6
|
export declare function validateUrl(url: string): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared validation patterns and helpers for CLI commands.
|
|
3
3
|
*/
|
|
4
|
-
import { ValidationError } from
|
|
4
|
+
import { ValidationError } from "../core/errors.js";
|
|
5
5
|
export const ISSUE_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/\d+$/;
|
|
6
6
|
const MAX_URL_LENGTH = 2048;
|
|
7
7
|
export function validateGitHubUrl(url, pattern, entityType) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ScoutState } from
|
|
2
|
-
import type { VetListResult } from
|
|
1
|
+
import type { ScoutState } from "../core/schemas.js";
|
|
2
|
+
import type { VetListResult } from "../core/types.js";
|
|
3
3
|
interface VetListCommandOptions {
|
|
4
4
|
concurrency?: number;
|
|
5
5
|
prune?: boolean;
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
import { createScout } from
|
|
2
|
-
import { requireGitHubToken } from
|
|
3
|
-
import { saveLocalState } from
|
|
1
|
+
import { createScout } from "../scout.js";
|
|
2
|
+
import { requireGitHubToken } from "../core/utils.js";
|
|
3
|
+
import { saveLocalState } from "../core/local-state.js";
|
|
4
4
|
export async function runVetList(options) {
|
|
5
5
|
const token = requireGitHubToken();
|
|
6
6
|
const scout = options.state
|
|
7
|
-
? await createScout({
|
|
7
|
+
? await createScout({
|
|
8
|
+
githubToken: token,
|
|
9
|
+
persistence: "provided",
|
|
10
|
+
initialState: options.state,
|
|
11
|
+
})
|
|
8
12
|
: await createScout({ githubToken: token });
|
|
9
13
|
const result = await scout.vetList({
|
|
10
14
|
concurrency: options.concurrency,
|
|
11
15
|
prune: options.prune,
|
|
12
16
|
});
|
|
13
17
|
saveLocalState(scout.getState());
|
|
14
|
-
await scout.checkpoint();
|
|
18
|
+
const persisted = await scout.checkpoint();
|
|
19
|
+
if (!persisted) {
|
|
20
|
+
console.error("Warning: changes saved locally but gist sync failed.");
|
|
21
|
+
}
|
|
15
22
|
return result;
|
|
16
23
|
}
|
package/dist/commands/vet.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vet command — vets a specific issue for claimability.
|
|
3
3
|
*/
|
|
4
|
-
import type { ProjectHealth } from
|
|
5
|
-
import type { IssueVettingResult, ScoutState } from
|
|
4
|
+
import type { ProjectHealth } from "../core/types.js";
|
|
5
|
+
import type { IssueVettingResult, ScoutState } from "../core/schemas.js";
|
|
6
6
|
export interface VetOutput {
|
|
7
7
|
issue: {
|
|
8
8
|
repo: string;
|
|
@@ -11,7 +11,7 @@ export interface VetOutput {
|
|
|
11
11
|
url: string;
|
|
12
12
|
labels: string[];
|
|
13
13
|
};
|
|
14
|
-
recommendation:
|
|
14
|
+
recommendation: "approve" | "skip" | "needs_review";
|
|
15
15
|
reasonsToApprove: string[];
|
|
16
16
|
reasonsToSkip: string[];
|
|
17
17
|
projectHealth: ProjectHealth;
|
package/dist/commands/vet.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vet command — vets a specific issue for claimability.
|
|
3
3
|
*/
|
|
4
|
-
import { createScout } from
|
|
5
|
-
import { requireGitHubToken } from
|
|
6
|
-
import { ISSUE_URL_PATTERN, validateGitHubUrl, validateUrl } from
|
|
4
|
+
import { createScout } from "../scout.js";
|
|
5
|
+
import { requireGitHubToken } from "../core/utils.js";
|
|
6
|
+
import { ISSUE_URL_PATTERN, validateGitHubUrl, validateUrl, } from "./validation.js";
|
|
7
7
|
export async function runVet(options) {
|
|
8
8
|
validateUrl(options.issueUrl);
|
|
9
|
-
validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN,
|
|
9
|
+
validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, "issue");
|
|
10
10
|
const token = requireGitHubToken();
|
|
11
11
|
const scout = options.state
|
|
12
|
-
? await createScout({
|
|
12
|
+
? await createScout({
|
|
13
|
+
githubToken: token,
|
|
14
|
+
persistence: "provided",
|
|
15
|
+
initialState: options.state,
|
|
16
|
+
})
|
|
13
17
|
: await createScout({ githubToken: token });
|
|
14
18
|
const candidate = await scout.vetIssue(options.issueUrl);
|
|
15
19
|
return {
|
package/dist/core/bootstrap.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* First-run bootstrap — fetches starred repos and PR history from GitHub
|
|
3
3
|
* to seed the scout's state with the user's contribution context.
|
|
4
4
|
*/
|
|
5
|
-
import type { OssScout } from
|
|
5
|
+
import type { OssScout } from "../scout.js";
|
|
6
6
|
export interface BootstrapResult {
|
|
7
7
|
starredRepoCount: number;
|
|
8
8
|
mergedPRCount: number;
|
package/dist/core/bootstrap.js
CHANGED
|
@@ -2,22 +2,23 @@
|
|
|
2
2
|
* First-run bootstrap — fetches starred repos and PR history from GitHub
|
|
3
3
|
* to seed the scout's state with the user's contribution context.
|
|
4
4
|
*/
|
|
5
|
-
import { getOctokit, checkRateLimit } from
|
|
6
|
-
import { debug, warn } from
|
|
7
|
-
import { errorMessage } from
|
|
8
|
-
|
|
5
|
+
import { getOctokit, checkRateLimit } from "./github.js";
|
|
6
|
+
import { debug, warn } from "./logger.js";
|
|
7
|
+
import { errorMessage } from "./errors.js";
|
|
8
|
+
import { extractRepoFromUrl } from "./utils.js";
|
|
9
|
+
const MODULE = "bootstrap";
|
|
9
10
|
const STARRED_MAX_PAGES = 5;
|
|
10
11
|
const SEARCH_MAX_PAGES = 3;
|
|
11
12
|
const PER_PAGE = 100;
|
|
12
13
|
export async function bootstrapScout(scout, token) {
|
|
13
14
|
const username = scout.getPreferences().githubUsername;
|
|
14
15
|
if (!username) {
|
|
15
|
-
throw new Error(
|
|
16
|
+
throw new Error("GitHub username not configured. Run `oss-scout setup` first.");
|
|
16
17
|
}
|
|
17
18
|
const rateLimit = await checkRateLimit(token);
|
|
18
19
|
debug(MODULE, `Rate limit: ${rateLimit.remaining}/${rateLimit.limit}, resets at ${rateLimit.resetAt}`);
|
|
19
20
|
if (rateLimit.remaining < 15) {
|
|
20
|
-
debug(MODULE,
|
|
21
|
+
debug(MODULE, "Insufficient rate limit, skipping bootstrap");
|
|
21
22
|
return {
|
|
22
23
|
starredRepoCount: 0,
|
|
23
24
|
mergedPRCount: 0,
|
|
@@ -33,7 +34,10 @@ export async function bootstrapScout(scout, token) {
|
|
|
33
34
|
const starredRepos = [];
|
|
34
35
|
try {
|
|
35
36
|
let starredPage = 0;
|
|
36
|
-
for await (const response of octokit.paginate.iterator(octokit.activity.listReposStarredByAuthenticatedUser, {
|
|
37
|
+
for await (const response of octokit.paginate.iterator(octokit.activity.listReposStarredByAuthenticatedUser, {
|
|
38
|
+
per_page: PER_PAGE,
|
|
39
|
+
headers: { accept: "application/vnd.github.v3+json" },
|
|
40
|
+
})) {
|
|
37
41
|
for (const repo of response.data) {
|
|
38
42
|
const r = repo;
|
|
39
43
|
starredRepos.push(r.full_name);
|
|
@@ -47,7 +51,7 @@ export async function bootstrapScout(scout, token) {
|
|
|
47
51
|
}
|
|
48
52
|
catch (err) {
|
|
49
53
|
warn(MODULE, `Failed to fetch starred repos: ${errorMessage(err)}`);
|
|
50
|
-
errors.push(
|
|
54
|
+
errors.push("starred repos fetch failed");
|
|
51
55
|
}
|
|
52
56
|
// 2. Fetch merged PRs via Search API
|
|
53
57
|
let mergedPRCount = 0;
|
|
@@ -59,14 +63,14 @@ export async function bootstrapScout(scout, token) {
|
|
|
59
63
|
page,
|
|
60
64
|
});
|
|
61
65
|
for (const item of data.items) {
|
|
62
|
-
const
|
|
63
|
-
if (!
|
|
66
|
+
const repo = extractRepoFromUrl(item.html_url);
|
|
67
|
+
if (!repo)
|
|
64
68
|
continue;
|
|
65
69
|
scout.recordMergedPR({
|
|
66
70
|
url: item.html_url,
|
|
67
71
|
title: item.title,
|
|
68
72
|
mergedAt: item.closed_at ?? new Date().toISOString(),
|
|
69
|
-
repo
|
|
73
|
+
repo,
|
|
70
74
|
});
|
|
71
75
|
mergedPRCount++;
|
|
72
76
|
}
|
|
@@ -77,7 +81,7 @@ export async function bootstrapScout(scout, token) {
|
|
|
77
81
|
}
|
|
78
82
|
catch (err) {
|
|
79
83
|
warn(MODULE, `Failed to fetch merged PRs: ${errorMessage(err)}`);
|
|
80
|
-
errors.push(
|
|
84
|
+
errors.push("merged PR fetch failed");
|
|
81
85
|
}
|
|
82
86
|
// 3. Fetch closed-without-merge PRs via Search API
|
|
83
87
|
let closedPRCount = 0;
|
|
@@ -89,14 +93,14 @@ export async function bootstrapScout(scout, token) {
|
|
|
89
93
|
page,
|
|
90
94
|
});
|
|
91
95
|
for (const item of data.items) {
|
|
92
|
-
const
|
|
93
|
-
if (!
|
|
96
|
+
const repo = extractRepoFromUrl(item.html_url);
|
|
97
|
+
if (!repo)
|
|
94
98
|
continue;
|
|
95
99
|
scout.recordClosedPR({
|
|
96
100
|
url: item.html_url,
|
|
97
101
|
title: item.title,
|
|
98
102
|
closedAt: item.closed_at ?? new Date().toISOString(),
|
|
99
|
-
repo
|
|
103
|
+
repo,
|
|
100
104
|
});
|
|
101
105
|
closedPRCount++;
|
|
102
106
|
}
|
|
@@ -107,7 +111,7 @@ export async function bootstrapScout(scout, token) {
|
|
|
107
111
|
}
|
|
108
112
|
catch (err) {
|
|
109
113
|
warn(MODULE, `Failed to fetch closed PRs: ${errorMessage(err)}`);
|
|
110
|
-
errors.push(
|
|
114
|
+
errors.push("closed PR fetch failed");
|
|
111
115
|
}
|
|
112
116
|
const state = scout.getState();
|
|
113
117
|
const reposScoredCount = Object.keys(state.repoScores).length;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Used by issue discovery to prioritize repos matching user's category preferences.
|
|
5
5
|
*/
|
|
6
|
-
import type { ProjectCategory } from
|
|
6
|
+
import type { ProjectCategory } from "./types.js";
|
|
7
7
|
/** GitHub topics associated with each project category, used for `topic:` search queries. */
|
|
8
8
|
export declare const CATEGORY_TOPICS: Record<ProjectCategory, string[]>;
|
|
9
9
|
/** Well-known GitHub organizations associated with each project category. */
|
|
@@ -5,21 +5,112 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/** GitHub topics associated with each project category, used for `topic:` search queries. */
|
|
7
7
|
export const CATEGORY_TOPICS = {
|
|
8
|
-
nonprofit: [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
nonprofit: [
|
|
9
|
+
"nonprofit",
|
|
10
|
+
"social-good",
|
|
11
|
+
"humanitarian",
|
|
12
|
+
"charity",
|
|
13
|
+
"social-impact",
|
|
14
|
+
"civic-tech",
|
|
15
|
+
],
|
|
16
|
+
devtools: [
|
|
17
|
+
"developer-tools",
|
|
18
|
+
"devtools",
|
|
19
|
+
"cli",
|
|
20
|
+
"sdk",
|
|
21
|
+
"linter",
|
|
22
|
+
"formatter",
|
|
23
|
+
"build-tool",
|
|
24
|
+
],
|
|
25
|
+
infrastructure: [
|
|
26
|
+
"infrastructure",
|
|
27
|
+
"cloud",
|
|
28
|
+
"kubernetes",
|
|
29
|
+
"docker",
|
|
30
|
+
"devops",
|
|
31
|
+
"monitoring",
|
|
32
|
+
"observability",
|
|
33
|
+
],
|
|
34
|
+
"web-frameworks": [
|
|
35
|
+
"web-framework",
|
|
36
|
+
"frontend",
|
|
37
|
+
"backend",
|
|
38
|
+
"fullstack",
|
|
39
|
+
"nextjs",
|
|
40
|
+
"react",
|
|
41
|
+
"vue",
|
|
42
|
+
],
|
|
43
|
+
"data-ml": [
|
|
44
|
+
"machine-learning",
|
|
45
|
+
"data-science",
|
|
46
|
+
"deep-learning",
|
|
47
|
+
"nlp",
|
|
48
|
+
"data-pipeline",
|
|
49
|
+
"analytics",
|
|
50
|
+
],
|
|
51
|
+
education: [
|
|
52
|
+
"education",
|
|
53
|
+
"learning",
|
|
54
|
+
"tutorial",
|
|
55
|
+
"courseware",
|
|
56
|
+
"edtech",
|
|
57
|
+
"teaching",
|
|
58
|
+
],
|
|
14
59
|
};
|
|
15
60
|
/** Well-known GitHub organizations associated with each project category. */
|
|
16
61
|
export const CATEGORY_ORGS = {
|
|
17
|
-
nonprofit: [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
62
|
+
nonprofit: [
|
|
63
|
+
"code-for-america",
|
|
64
|
+
"opengovfoundation",
|
|
65
|
+
"ushahidi",
|
|
66
|
+
"hotosm",
|
|
67
|
+
"openfn",
|
|
68
|
+
"democracyearth",
|
|
69
|
+
],
|
|
70
|
+
devtools: [
|
|
71
|
+
"eslint",
|
|
72
|
+
"prettier",
|
|
73
|
+
"vitejs",
|
|
74
|
+
"biomejs",
|
|
75
|
+
"oxc-project",
|
|
76
|
+
"ast-grep",
|
|
77
|
+
"turbot",
|
|
78
|
+
],
|
|
79
|
+
infrastructure: [
|
|
80
|
+
"kubernetes",
|
|
81
|
+
"hashicorp",
|
|
82
|
+
"grafana",
|
|
83
|
+
"prometheus",
|
|
84
|
+
"open-telemetry",
|
|
85
|
+
"envoyproxy",
|
|
86
|
+
"cncf",
|
|
87
|
+
],
|
|
88
|
+
"web-frameworks": [
|
|
89
|
+
"vercel",
|
|
90
|
+
"remix-run",
|
|
91
|
+
"sveltejs",
|
|
92
|
+
"nuxt",
|
|
93
|
+
"astro",
|
|
94
|
+
"redwoodjs",
|
|
95
|
+
"blitz-js",
|
|
96
|
+
],
|
|
97
|
+
"data-ml": [
|
|
98
|
+
"huggingface",
|
|
99
|
+
"mlflow",
|
|
100
|
+
"apache",
|
|
101
|
+
"dbt-labs",
|
|
102
|
+
"dagster-io",
|
|
103
|
+
"prefecthq",
|
|
104
|
+
"langchain-ai",
|
|
105
|
+
],
|
|
106
|
+
education: [
|
|
107
|
+
"freeCodeCamp",
|
|
108
|
+
"TheOdinProject",
|
|
109
|
+
"exercism",
|
|
110
|
+
"codecademy",
|
|
111
|
+
"oppia",
|
|
112
|
+
"Khan",
|
|
113
|
+
],
|
|
23
114
|
};
|
|
24
115
|
/**
|
|
25
116
|
* Check if a repo belongs to any of the given categories based on its owner matching a category org.
|
|
@@ -28,7 +119,7 @@ export const CATEGORY_ORGS = {
|
|
|
28
119
|
export function repoBelongsToCategory(repoFullName, categories) {
|
|
29
120
|
if (categories.length === 0)
|
|
30
121
|
return false;
|
|
31
|
-
const owner = repoFullName.split(
|
|
122
|
+
const owner = repoFullName.split("/")[0]?.toLowerCase();
|
|
32
123
|
if (!owner)
|
|
33
124
|
return false;
|
|
34
125
|
for (const category of categories) {
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Custom error type hierarchy for oss-scout.
|
|
3
|
+
*
|
|
4
|
+
* Error strategy:
|
|
5
|
+
* - Auth errors (401) and rate limit errors (429, 403+rate-limit): ALWAYS propagate
|
|
6
|
+
* - Network errors (ENOTFOUND, ECONNREFUSED, ETIMEDOUT): propagate with context
|
|
7
|
+
* - Validation errors: propagate
|
|
8
|
+
* - Cache/filesystem errors: degrade gracefully with warn logging
|
|
9
|
+
* - API data errors (unexpected shapes): degrade gracefully with warn logging
|
|
3
10
|
*/
|
|
4
11
|
export declare class OssScoutError extends Error {
|
|
5
12
|
readonly code: string;
|
|
@@ -15,7 +22,7 @@ export declare function errorMessage(e: unknown): string;
|
|
|
15
22
|
export declare function getHttpStatusCode(error: unknown): number | undefined;
|
|
16
23
|
export declare function isRateLimitError(error: unknown): boolean;
|
|
17
24
|
/** Error codes for JSON output. */
|
|
18
|
-
export type ErrorCode =
|
|
25
|
+
export type ErrorCode = "AUTH_REQUIRED" | "CONFIGURATION" | "NETWORK" | "NOT_FOUND" | "RATE_LIMITED" | "STATE_CORRUPTED" | "UNKNOWN" | "VALIDATION";
|
|
19
26
|
/**
|
|
20
27
|
* Map an unknown error to a structured ErrorCode for JSON output.
|
|
21
28
|
*/
|
package/dist/core/errors.js
CHANGED
|
@@ -1,33 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Custom error type hierarchy for oss-scout.
|
|
3
|
+
*
|
|
4
|
+
* Error strategy:
|
|
5
|
+
* - Auth errors (401) and rate limit errors (429, 403+rate-limit): ALWAYS propagate
|
|
6
|
+
* - Network errors (ENOTFOUND, ECONNREFUSED, ETIMEDOUT): propagate with context
|
|
7
|
+
* - Validation errors: propagate
|
|
8
|
+
* - Cache/filesystem errors: degrade gracefully with warn logging
|
|
9
|
+
* - API data errors (unexpected shapes): degrade gracefully with warn logging
|
|
3
10
|
*/
|
|
4
11
|
export class OssScoutError extends Error {
|
|
5
12
|
code;
|
|
6
13
|
constructor(message, code) {
|
|
7
14
|
super(message);
|
|
8
15
|
this.code = code;
|
|
9
|
-
this.name =
|
|
16
|
+
this.name = "OssScoutError";
|
|
10
17
|
}
|
|
11
18
|
}
|
|
12
19
|
export class ConfigurationError extends OssScoutError {
|
|
13
20
|
constructor(message) {
|
|
14
|
-
super(message,
|
|
15
|
-
this.name =
|
|
21
|
+
super(message, "CONFIGURATION_ERROR");
|
|
22
|
+
this.name = "ConfigurationError";
|
|
16
23
|
}
|
|
17
24
|
}
|
|
18
25
|
export class ValidationError extends OssScoutError {
|
|
19
26
|
constructor(message) {
|
|
20
|
-
super(message,
|
|
21
|
-
this.name =
|
|
27
|
+
super(message, "VALIDATION_ERROR");
|
|
28
|
+
this.name = "ValidationError";
|
|
22
29
|
}
|
|
23
30
|
}
|
|
24
31
|
export function errorMessage(e) {
|
|
25
32
|
return e instanceof Error ? e.message : String(e);
|
|
26
33
|
}
|
|
27
34
|
export function getHttpStatusCode(error) {
|
|
28
|
-
if (error && typeof error ===
|
|
35
|
+
if (error && typeof error === "object" && "status" in error) {
|
|
29
36
|
const status = error.status;
|
|
30
|
-
return typeof status ===
|
|
37
|
+
return typeof status === "number" && Number.isFinite(status)
|
|
38
|
+
? status
|
|
39
|
+
: undefined;
|
|
31
40
|
}
|
|
32
41
|
return undefined;
|
|
33
42
|
}
|
|
@@ -37,7 +46,7 @@ export function isRateLimitError(error) {
|
|
|
37
46
|
return true;
|
|
38
47
|
if (status === 403) {
|
|
39
48
|
const msg = errorMessage(error).toLowerCase();
|
|
40
|
-
return msg.includes(
|
|
49
|
+
return msg.includes("rate limit");
|
|
41
50
|
}
|
|
42
51
|
return false;
|
|
43
52
|
}
|
|
@@ -46,24 +55,27 @@ export function isRateLimitError(error) {
|
|
|
46
55
|
*/
|
|
47
56
|
export function resolveErrorCode(err) {
|
|
48
57
|
if (err instanceof ConfigurationError)
|
|
49
|
-
return
|
|
58
|
+
return "CONFIGURATION";
|
|
50
59
|
if (err instanceof ValidationError)
|
|
51
|
-
return
|
|
60
|
+
return "VALIDATION";
|
|
52
61
|
const status = getHttpStatusCode(err);
|
|
53
62
|
if (status === 401)
|
|
54
|
-
return
|
|
63
|
+
return "AUTH_REQUIRED";
|
|
55
64
|
if (status === 403) {
|
|
56
65
|
const msg = errorMessage(err).toLowerCase();
|
|
57
|
-
if (msg.includes(
|
|
58
|
-
return
|
|
59
|
-
return
|
|
66
|
+
if (msg.includes("rate limit") || msg.includes("abuse detection"))
|
|
67
|
+
return "RATE_LIMITED";
|
|
68
|
+
return "AUTH_REQUIRED";
|
|
60
69
|
}
|
|
61
70
|
if (status === 404)
|
|
62
|
-
return
|
|
71
|
+
return "NOT_FOUND";
|
|
63
72
|
if (status === 429)
|
|
64
|
-
return
|
|
73
|
+
return "RATE_LIMITED";
|
|
65
74
|
const msg = errorMessage(err).toLowerCase();
|
|
66
|
-
if (msg.includes(
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
if (msg.includes("enotfound") ||
|
|
76
|
+
msg.includes("econnrefused") ||
|
|
77
|
+
msg.includes("etimedout") ||
|
|
78
|
+
msg.includes("fetch failed"))
|
|
79
|
+
return "NETWORK";
|
|
80
|
+
return "UNKNOWN";
|
|
69
81
|
}
|