@oss-scout/core 0.2.0 → 0.3.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/cli.bundle.cjs +51 -47
- package/dist/cli.js +218 -87
- package/dist/commands/config.d.ts +2 -4
- package/dist/commands/config.js +76 -78
- 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 +25 -25
- package/dist/commands/skip.d.ts +33 -0
- package/dist/commands/skip.js +89 -0
- 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 +55 -28
- 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 +6 -6
- package/dist/core/issue-discovery.js +279 -286
- 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 -4
- package/dist/core/issue-vetting.js +65 -56
- 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 +17 -9
- package/dist/core/schemas.js +47 -19
- 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 +30 -6
- package/dist/scout.js +141 -34
- package/package.json +7 -3
package/dist/commands/config.js
CHANGED
|
@@ -1,41 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Config command — view and update oss-scout preferences.
|
|
3
3
|
*/
|
|
4
|
-
import { loadLocalState, saveLocalState } from
|
|
5
|
-
import { ScoutPreferencesSchema, IssueScopeSchema, ProjectCategorySchema, PersistenceModeSchema } from
|
|
6
|
-
import { ValidationError } from
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
persistence: PersistenceModeSchema.options,
|
|
4
|
+
import { loadLocalState, saveLocalState } from "../core/local-state.js";
|
|
5
|
+
import { ScoutPreferencesSchema, IssueScopeSchema, ProjectCategorySchema, PersistenceModeSchema, SearchStrategySchema, } from "../core/schemas.js";
|
|
6
|
+
import { ValidationError } from "../core/errors.js";
|
|
7
|
+
const FIELD_CONFIGS = {
|
|
8
|
+
languages: { type: "array" },
|
|
9
|
+
labels: { type: "array" },
|
|
10
|
+
excludeRepos: { type: "array" },
|
|
11
|
+
excludeOrgs: { type: "array" },
|
|
12
|
+
aiPolicyBlocklist: { type: "array" },
|
|
13
|
+
minStars: { type: "number" },
|
|
14
|
+
maxIssueAgeDays: { type: "number" },
|
|
15
|
+
minRepoScoreThreshold: { type: "number" },
|
|
16
|
+
includeDocIssues: { type: "boolean" },
|
|
17
|
+
scope: { type: "enum-array", validValues: IssueScopeSchema.options },
|
|
18
|
+
projectCategories: {
|
|
19
|
+
type: "enum-array",
|
|
20
|
+
validValues: ProjectCategorySchema.options,
|
|
21
|
+
},
|
|
22
|
+
persistence: { type: "enum", validValues: PersistenceModeSchema.options },
|
|
23
|
+
defaultStrategy: {
|
|
24
|
+
type: "enum-array",
|
|
25
|
+
validValues: SearchStrategySchema.options,
|
|
26
|
+
},
|
|
27
|
+
githubUsername: { type: "string" },
|
|
23
28
|
};
|
|
24
|
-
const ALL_FIELDS = new Set([
|
|
25
|
-
...ARRAY_FIELDS,
|
|
26
|
-
...NUMBER_FIELDS,
|
|
27
|
-
...BOOLEAN_FIELDS,
|
|
28
|
-
...STRING_FIELDS,
|
|
29
|
-
...Object.keys(ENUM_FIELDS),
|
|
30
|
-
SCOPE_FIELD,
|
|
31
|
-
]);
|
|
32
|
-
const VALID_SCOPES = IssueScopeSchema.options;
|
|
33
|
-
const VALID_CATEGORIES = ProjectCategorySchema.options;
|
|
34
29
|
function parseBoolean(value) {
|
|
35
30
|
const lower = value.toLowerCase();
|
|
36
|
-
if (lower ===
|
|
31
|
+
if (lower === "true" || lower === "yes")
|
|
37
32
|
return true;
|
|
38
|
-
if (lower ===
|
|
33
|
+
if (lower === "false" || lower === "no")
|
|
39
34
|
return false;
|
|
40
35
|
throw new ValidationError(`Invalid boolean value: "${value}". Use true/false or yes/no.`);
|
|
41
36
|
}
|
|
@@ -48,7 +43,7 @@ function parseNumber(value, key) {
|
|
|
48
43
|
}
|
|
49
44
|
function parseArrayValue(value) {
|
|
50
45
|
return value
|
|
51
|
-
.split(
|
|
46
|
+
.split(",")
|
|
52
47
|
.map((s) => s.trim())
|
|
53
48
|
.filter((s) => s.length > 0);
|
|
54
49
|
}
|
|
@@ -56,7 +51,7 @@ function parseArrayValue(value) {
|
|
|
56
51
|
* Apply an array update: plain set, +append, or -remove.
|
|
57
52
|
*/
|
|
58
53
|
function updateArray(current, value) {
|
|
59
|
-
if (value.startsWith(
|
|
54
|
+
if (value.startsWith("+")) {
|
|
60
55
|
const toAdd = parseArrayValue(value.slice(1));
|
|
61
56
|
const merged = [...current];
|
|
62
57
|
for (const item of toAdd) {
|
|
@@ -65,39 +60,35 @@ function updateArray(current, value) {
|
|
|
65
60
|
}
|
|
66
61
|
return merged;
|
|
67
62
|
}
|
|
68
|
-
if (value.startsWith(
|
|
63
|
+
if (value.startsWith("-")) {
|
|
69
64
|
const toRemove = new Set(parseArrayValue(value.slice(1)));
|
|
70
65
|
return current.filter((item) => !toRemove.has(item));
|
|
71
66
|
}
|
|
72
67
|
return parseArrayValue(value);
|
|
73
68
|
}
|
|
74
69
|
function formatArray(arr) {
|
|
75
|
-
return arr.length > 0 ? arr.join(
|
|
70
|
+
return arr.length > 0 ? arr.join(", ") : "(none)";
|
|
76
71
|
}
|
|
77
72
|
/**
|
|
78
73
|
* Display current preferences in human-readable format.
|
|
79
74
|
*/
|
|
80
|
-
export function runConfigShow(
|
|
75
|
+
export function runConfigShow() {
|
|
81
76
|
const state = loadLocalState();
|
|
82
77
|
const prefs = state.preferences;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
console.log('\n⚙️ oss-scout preferences\n');
|
|
88
|
-
console.log(` githubUsername: ${prefs.githubUsername || '(not set)'}`);
|
|
78
|
+
console.log("\n⚙️ oss-scout preferences\n");
|
|
79
|
+
console.log(` githubUsername: ${prefs.githubUsername || "(not set)"}`);
|
|
89
80
|
console.log(` languages: ${formatArray(prefs.languages)}`);
|
|
90
81
|
console.log(` labels: ${formatArray(prefs.labels)}`);
|
|
91
|
-
console.log(` scope: ${prefs.scope ? formatArray(prefs.scope) :
|
|
82
|
+
console.log(` scope: ${prefs.scope ? formatArray(prefs.scope) : "(all)"}`);
|
|
92
83
|
console.log(` minStars: ${prefs.minStars}`);
|
|
93
84
|
console.log(` maxIssueAgeDays: ${prefs.maxIssueAgeDays}`);
|
|
94
85
|
console.log(` minRepoScoreThreshold: ${prefs.minRepoScoreThreshold}`);
|
|
95
86
|
console.log(` includeDocIssues: ${prefs.includeDocIssues}`);
|
|
96
|
-
console.log(` preferredOrgs: ${formatArray(prefs.preferredOrgs)}`);
|
|
97
87
|
console.log(` projectCategories: ${formatArray(prefs.projectCategories)}`);
|
|
98
88
|
console.log(` excludeRepos: ${formatArray(prefs.excludeRepos)}`);
|
|
99
89
|
console.log(` excludeOrgs: ${formatArray(prefs.excludeOrgs)}`);
|
|
100
90
|
console.log(` aiPolicyBlocklist: ${formatArray(prefs.aiPolicyBlocklist)}`);
|
|
91
|
+
console.log(` defaultStrategy: ${prefs.defaultStrategy ? formatArray(prefs.defaultStrategy) : "(all)"}`);
|
|
101
92
|
console.log(` persistence: ${prefs.persistence}`);
|
|
102
93
|
console.log();
|
|
103
94
|
}
|
|
@@ -112,46 +103,53 @@ export function getConfigData() {
|
|
|
112
103
|
* Update a single preference by key.
|
|
113
104
|
*/
|
|
114
105
|
export function runConfigSet(key, value) {
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
const field = FIELD_CONFIGS[key];
|
|
107
|
+
if (!field) {
|
|
108
|
+
throw new ValidationError(`Unknown config key: "${key}". Valid keys: ${Object.keys(FIELD_CONFIGS).sort().join(", ")}`);
|
|
117
109
|
}
|
|
118
110
|
const state = loadLocalState();
|
|
119
111
|
const prefs = { ...state.preferences };
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
112
|
+
switch (field.type) {
|
|
113
|
+
case "string":
|
|
114
|
+
prefs[key] = value;
|
|
115
|
+
break;
|
|
116
|
+
case "boolean":
|
|
117
|
+
prefs[key] = parseBoolean(value);
|
|
118
|
+
break;
|
|
119
|
+
case "number":
|
|
120
|
+
prefs[key] = parseNumber(value, key);
|
|
121
|
+
break;
|
|
122
|
+
case "array": {
|
|
123
|
+
const current = prefs[key] ?? [];
|
|
124
|
+
prefs[key] = updateArray(current, value);
|
|
125
|
+
break;
|
|
134
126
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
127
|
+
case "enum": {
|
|
128
|
+
const validValues = field.validValues;
|
|
129
|
+
if (!validValues.includes(value)) {
|
|
130
|
+
throw new ValidationError(`Invalid value for "${key}": "${value}". Valid: ${validValues.join(", ")}`);
|
|
131
|
+
}
|
|
132
|
+
prefs[key] = value;
|
|
133
|
+
break;
|
|
142
134
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
135
|
+
case "enum-array": {
|
|
136
|
+
const current = prefs[key] ?? [];
|
|
137
|
+
const updated = updateArray(current, value);
|
|
138
|
+
const validValues = field.validValues;
|
|
139
|
+
const invalid = updated.filter((s) => !validValues.includes(s));
|
|
140
|
+
if (invalid.length > 0) {
|
|
141
|
+
throw new ValidationError(`Invalid value(s) for "${key}": ${invalid.join(", ")}. Valid: ${validValues.join(", ")}`);
|
|
142
|
+
}
|
|
143
|
+
// For 'scope', empty array means undefined (all scopes)
|
|
144
|
+
if (key === "scope") {
|
|
145
|
+
prefs[key] =
|
|
146
|
+
updated.length > 0 ? updated : undefined;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
prefs[key] = updated;
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
149
152
|
}
|
|
150
|
-
prefs[key] = value;
|
|
151
|
-
}
|
|
152
|
-
else if (ARRAY_FIELDS.has(key)) {
|
|
153
|
-
const current = prefs[key] ?? [];
|
|
154
|
-
prefs[key] = updateArray(current, value);
|
|
155
153
|
}
|
|
156
154
|
// Validate the full preferences object
|
|
157
155
|
const validated = ScoutPreferencesSchema.parse(prefs);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Results command — display and manage saved search results.
|
|
3
3
|
*/
|
|
4
|
-
import type { SavedCandidate } from
|
|
4
|
+
import type { SavedCandidate } from "../core/schemas.js";
|
|
5
5
|
export declare function runResults(_options: {
|
|
6
6
|
json?: boolean;
|
|
7
7
|
}): Promise<SavedCandidate[]>;
|
package/dist/commands/results.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Results command — display and manage saved search results.
|
|
3
3
|
*/
|
|
4
|
-
import { loadLocalState, saveLocalState } from
|
|
4
|
+
import { loadLocalState, saveLocalState } from "../core/local-state.js";
|
|
5
5
|
export async function runResults(_options) {
|
|
6
6
|
const state = loadLocalState();
|
|
7
7
|
return state.savedResults ?? [];
|
|
@@ -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,41 @@ 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 =
|
|
63
|
-
const langsInput = await ask(rl, `Preferred languages [${defaultLangs}]: `);
|
|
64
|
-
const languages = langsInput ? parseCSV(langsInput) : [
|
|
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"];
|
|
65
65
|
// Issue labels
|
|
66
|
-
const defaultLabels =
|
|
66
|
+
const defaultLabels = "good first issue, help wanted";
|
|
67
67
|
const labelsInput = await ask(rl, `Issue labels to search for [${defaultLabels}]: `);
|
|
68
|
-
const labels = labelsInput
|
|
68
|
+
const labels = labelsInput
|
|
69
|
+
? parseCSV(labelsInput)
|
|
70
|
+
: ["good first issue", "help wanted"];
|
|
69
71
|
// Difficulty scope
|
|
70
|
-
const scopeOptions = ALL_SCOPES.join(
|
|
72
|
+
const scopeOptions = ALL_SCOPES.join(", ");
|
|
71
73
|
const scopeInput = await ask(rl, `Difficulty scope (${scopeOptions}) [all]: `);
|
|
72
|
-
const scope = scopeInput
|
|
74
|
+
const scope = scopeInput
|
|
75
|
+
? parseMultiSelect(scopeInput, ALL_SCOPES)
|
|
76
|
+
: [...ALL_SCOPES];
|
|
73
77
|
// Minimum stars
|
|
74
|
-
const minStarsInput = await ask(rl,
|
|
78
|
+
const minStarsInput = await ask(rl, "Minimum repo stars [50]: ");
|
|
75
79
|
const minStars = minStarsInput ? parseInt(minStarsInput, 10) : 50;
|
|
76
|
-
// Preferred organizations
|
|
77
|
-
const orgsInput = await ask(rl, 'Preferred organizations (comma-separated, optional): ');
|
|
78
|
-
const preferredOrgs = parseCSV(orgsInput);
|
|
79
80
|
// Project categories
|
|
80
|
-
const categoryOptions = ALL_CATEGORIES.join(
|
|
81
|
+
const categoryOptions = ALL_CATEGORIES.join(", ");
|
|
81
82
|
const categoriesInput = await ask(rl, `Project categories (${categoryOptions}) [none]: `);
|
|
82
83
|
const projectCategories = parseMultiSelect(categoriesInput, ALL_CATEGORIES);
|
|
83
84
|
// Repos to exclude
|
|
84
|
-
const excludeInput = await ask(rl,
|
|
85
|
+
const excludeInput = await ask(rl, "Repos to exclude (owner/repo, comma-separated, optional): ");
|
|
85
86
|
const excludeRepos = parseCSV(excludeInput);
|
|
86
87
|
const prefs = ScoutPreferencesSchema.parse({
|
|
87
88
|
githubUsername,
|
|
@@ -89,11 +90,10 @@ export async function runSetup(options) {
|
|
|
89
90
|
labels,
|
|
90
91
|
scope: scope.length > 0 ? scope : undefined,
|
|
91
92
|
excludeRepos,
|
|
92
|
-
preferredOrgs,
|
|
93
93
|
projectCategories,
|
|
94
94
|
minStars: isNaN(minStars) ? 50 : minStars,
|
|
95
95
|
});
|
|
96
|
-
console.log(
|
|
96
|
+
console.log("\n✅ Setup complete! Preferences saved.\n");
|
|
97
97
|
return prefs;
|
|
98
98
|
}
|
|
99
99
|
finally {
|
|
@@ -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
|
+
}
|
|
@@ -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;
|