@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/cli.js
CHANGED
|
@@ -2,41 +2,41 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* oss-scout CLI — Find open source issues personalized to your contribution history.
|
|
4
4
|
*/
|
|
5
|
-
import { Command } from
|
|
6
|
-
import { enableDebug } from
|
|
7
|
-
import { getCLIVersion } from
|
|
8
|
-
import { formatJsonSuccess, formatJsonError } from
|
|
9
|
-
import { errorMessage, resolveErrorCode } from
|
|
10
|
-
import { hasLocalState, loadLocalState, saveLocalState } from
|
|
11
|
-
import { CONCRETE_STRATEGIES, SearchStrategySchema } from
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { enableDebug } from "./core/logger.js";
|
|
7
|
+
import { getCLIVersion } from "./core/utils.js";
|
|
8
|
+
import { formatJsonSuccess, formatJsonError } from "./formatters/json.js";
|
|
9
|
+
import { errorMessage, resolveErrorCode } from "./core/errors.js";
|
|
10
|
+
import { hasLocalState, loadLocalState, saveLocalState, } from "./core/local-state.js";
|
|
11
|
+
import { CONCRETE_STRATEGIES, SearchStrategySchema } from "./core/schemas.js";
|
|
12
12
|
function handleCommandError(err, options) {
|
|
13
13
|
if (options.json) {
|
|
14
14
|
console.log(formatJsonError(errorMessage(err), resolveErrorCode(err)));
|
|
15
15
|
}
|
|
16
16
|
else {
|
|
17
|
-
console.error(
|
|
17
|
+
console.error("Error:", errorMessage(err));
|
|
18
18
|
}
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
21
21
|
const program = new Command();
|
|
22
22
|
program
|
|
23
|
-
.name(
|
|
24
|
-
.description(
|
|
23
|
+
.name("oss-scout")
|
|
24
|
+
.description("Find open source issues personalized to your contribution history")
|
|
25
25
|
.version(getCLIVersion())
|
|
26
|
-
.option(
|
|
26
|
+
.option("--debug", "Enable debug output");
|
|
27
27
|
// Parse --debug early so it's available in preAction hooks
|
|
28
|
-
program.hook(
|
|
28
|
+
program.hook("preAction", (_thisCommand, _actionCommand) => {
|
|
29
29
|
const opts = program.opts();
|
|
30
30
|
if (opts.debug)
|
|
31
31
|
enableDebug();
|
|
32
32
|
});
|
|
33
33
|
program
|
|
34
|
-
.command(
|
|
35
|
-
.description(
|
|
36
|
-
.option(
|
|
34
|
+
.command("setup")
|
|
35
|
+
.description("Interactive first-run configuration")
|
|
36
|
+
.option("--json", "Output as JSON")
|
|
37
37
|
.action(async (options) => {
|
|
38
38
|
try {
|
|
39
|
-
const { runSetup } = await import(
|
|
39
|
+
const { runSetup } = await import("./commands/setup.js");
|
|
40
40
|
const prefs = await runSetup();
|
|
41
41
|
const state = loadLocalState();
|
|
42
42
|
state.preferences = prefs;
|
|
@@ -50,17 +50,21 @@ program
|
|
|
50
50
|
}
|
|
51
51
|
});
|
|
52
52
|
program
|
|
53
|
-
.command(
|
|
54
|
-
.description(
|
|
55
|
-
.option(
|
|
53
|
+
.command("bootstrap")
|
|
54
|
+
.description("Import starred repos and PR history from GitHub")
|
|
55
|
+
.option("--json", "Output as JSON")
|
|
56
56
|
.action(async (options) => {
|
|
57
57
|
try {
|
|
58
|
-
const { bootstrapScout } = await import(
|
|
59
|
-
const { createScout } = await import(
|
|
60
|
-
const { requireGitHubToken } = await import(
|
|
58
|
+
const { bootstrapScout } = await import("./core/bootstrap.js");
|
|
59
|
+
const { createScout } = await import("./scout.js");
|
|
60
|
+
const { requireGitHubToken } = await import("./core/utils.js");
|
|
61
61
|
const token = requireGitHubToken();
|
|
62
62
|
const state = loadLocalState();
|
|
63
|
-
const scout = await createScout({
|
|
63
|
+
const scout = await createScout({
|
|
64
|
+
githubToken: token,
|
|
65
|
+
persistence: "provided",
|
|
66
|
+
initialState: state,
|
|
67
|
+
});
|
|
64
68
|
const result = await bootstrapScout(scout, token);
|
|
65
69
|
saveLocalState(scout.getState());
|
|
66
70
|
if (options.json) {
|
|
@@ -68,7 +72,7 @@ program
|
|
|
68
72
|
}
|
|
69
73
|
else {
|
|
70
74
|
if (result.skippedDueToRateLimit) {
|
|
71
|
-
console.log(
|
|
75
|
+
console.log("Skipped: GitHub API rate limit too low. Try again later.");
|
|
72
76
|
}
|
|
73
77
|
else {
|
|
74
78
|
console.log(`Imported ${result.mergedPRCount} merged PRs, ${result.closedPRCount} closed PRs, ${result.starredRepoCount} starred repos`);
|
|
@@ -81,35 +85,41 @@ program
|
|
|
81
85
|
}
|
|
82
86
|
});
|
|
83
87
|
program
|
|
84
|
-
.command(
|
|
85
|
-
.description(
|
|
86
|
-
.option(
|
|
87
|
-
.option(
|
|
88
|
+
.command("search [count]")
|
|
89
|
+
.description("Search for contributable issues using multi-strategy discovery")
|
|
90
|
+
.option("--json", "Output as JSON")
|
|
91
|
+
.option("--strategy <strategies>", `Search strategies (${CONCRETE_STRATEGIES.join(",")},all)`, "all")
|
|
88
92
|
.action(async (count, options) => {
|
|
89
93
|
try {
|
|
90
94
|
if (!hasLocalState()) {
|
|
91
|
-
console.log(
|
|
95
|
+
console.log("💡 Run `oss-scout setup` to configure your preferences for personalized search results.\n");
|
|
92
96
|
}
|
|
93
|
-
const { runSearch } = await import(
|
|
97
|
+
const { runSearch } = await import("./commands/search.js");
|
|
94
98
|
const maxResults = count ? parseInt(count, 10) : 10;
|
|
95
99
|
if (isNaN(maxResults) || maxResults < 1) {
|
|
96
|
-
console.error(
|
|
100
|
+
console.error("Error: count must be a positive integer");
|
|
97
101
|
process.exit(1);
|
|
98
102
|
}
|
|
99
103
|
const state = loadLocalState();
|
|
100
104
|
if (state.mergedPRs.length === 0 &&
|
|
101
105
|
state.starredRepos.length === 0 &&
|
|
102
106
|
state.preferences.githubUsername) {
|
|
103
|
-
console.log(
|
|
107
|
+
console.log("Run `oss-scout bootstrap` to import your starred repos and PR history for better results.\n");
|
|
104
108
|
}
|
|
105
109
|
// Parse --strategy option
|
|
106
|
-
const strategyTokens = (options.strategy ??
|
|
110
|
+
const strategyTokens = (options.strategy ?? "all")
|
|
111
|
+
.split(",")
|
|
112
|
+
.map((s) => s.trim())
|
|
113
|
+
.filter(Boolean);
|
|
107
114
|
const strategies = [];
|
|
108
115
|
for (const token of strategyTokens) {
|
|
109
116
|
const parsed = SearchStrategySchema.safeParse(token);
|
|
110
117
|
if (!parsed.success) {
|
|
111
|
-
const valid = [...CONCRETE_STRATEGIES,
|
|
112
|
-
console.error('Error: unknown strategy "' +
|
|
118
|
+
const valid = [...CONCRETE_STRATEGIES, "all"].join(", ");
|
|
119
|
+
console.error('Error: unknown strategy "' +
|
|
120
|
+
token +
|
|
121
|
+
'". Valid strategies: ' +
|
|
122
|
+
valid);
|
|
113
123
|
process.exit(1);
|
|
114
124
|
}
|
|
115
125
|
strategies.push(parsed.data);
|
|
@@ -122,7 +132,11 @@ program
|
|
|
122
132
|
// Human-readable output
|
|
123
133
|
console.log(`\nFound ${results.candidates.length} issue candidates:\n`);
|
|
124
134
|
for (const c of results.candidates) {
|
|
125
|
-
const icon = c.recommendation ===
|
|
135
|
+
const icon = c.recommendation === "approve"
|
|
136
|
+
? "✅"
|
|
137
|
+
: c.recommendation === "skip"
|
|
138
|
+
? "❌"
|
|
139
|
+
: "⚠️";
|
|
126
140
|
console.log(` ${icon} ${c.issue.repo}#${c.issue.number} [${c.viabilityScore}/100]`);
|
|
127
141
|
console.log(` ${c.issue.title}`);
|
|
128
142
|
console.log(` ${c.issue.url}`);
|
|
@@ -142,33 +156,33 @@ program
|
|
|
142
156
|
});
|
|
143
157
|
// ── results command ────────────────────────────────────────────────
|
|
144
158
|
const resultsCmd = program
|
|
145
|
-
.command(
|
|
146
|
-
.description(
|
|
159
|
+
.command("results")
|
|
160
|
+
.description("Show saved search results");
|
|
147
161
|
resultsCmd
|
|
148
|
-
.command(
|
|
149
|
-
.description(
|
|
150
|
-
.option(
|
|
162
|
+
.command("show", { isDefault: true })
|
|
163
|
+
.description("Display saved search results")
|
|
164
|
+
.option("--json", "Output as JSON")
|
|
151
165
|
.action(async (options) => {
|
|
152
166
|
try {
|
|
153
|
-
const { runResults } = await import(
|
|
167
|
+
const { runResults } = await import("./commands/results.js");
|
|
154
168
|
const results = await runResults(options);
|
|
155
169
|
if (options.json) {
|
|
156
170
|
console.log(formatJsonSuccess(results));
|
|
157
171
|
}
|
|
158
172
|
else {
|
|
159
173
|
if (results.length === 0) {
|
|
160
|
-
console.log(
|
|
174
|
+
console.log("\nNo saved results. Run `oss-scout search` to find issues.\n");
|
|
161
175
|
return;
|
|
162
176
|
}
|
|
163
177
|
console.log(`\nSaved results (${results.length}):\n`);
|
|
164
|
-
console.log(
|
|
165
|
-
console.log(
|
|
178
|
+
console.log(" Score Repo Issue Recommendation Title");
|
|
179
|
+
console.log(" ───── ──────────────────────────────── ────── ────────────── ─────");
|
|
166
180
|
for (const r of results) {
|
|
167
181
|
const score = String(r.viabilityScore).padStart(3);
|
|
168
182
|
const repo = r.repo.padEnd(32).slice(0, 32);
|
|
169
183
|
const issue = `#${r.number}`.padEnd(6);
|
|
170
184
|
const rec = r.recommendation.padEnd(14);
|
|
171
|
-
const title = r.title.length > 50 ? r.title.slice(0, 47) +
|
|
185
|
+
const title = r.title.length > 50 ? r.title.slice(0, 47) + "..." : r.title;
|
|
172
186
|
console.log(` ${score} ${repo} ${issue} ${rec} ${title}`);
|
|
173
187
|
}
|
|
174
188
|
console.log();
|
|
@@ -179,18 +193,18 @@ resultsCmd
|
|
|
179
193
|
}
|
|
180
194
|
});
|
|
181
195
|
resultsCmd
|
|
182
|
-
.command(
|
|
183
|
-
.description(
|
|
184
|
-
.option(
|
|
196
|
+
.command("clear")
|
|
197
|
+
.description("Clear all saved results")
|
|
198
|
+
.option("--json", "Output as JSON")
|
|
185
199
|
.action(async (options) => {
|
|
186
200
|
try {
|
|
187
|
-
const { runResultsClear } = await import(
|
|
201
|
+
const { runResultsClear } = await import("./commands/results.js");
|
|
188
202
|
await runResultsClear();
|
|
189
203
|
if (options.json) {
|
|
190
204
|
console.log(formatJsonSuccess({ cleared: true }));
|
|
191
205
|
}
|
|
192
206
|
else {
|
|
193
|
-
console.log(
|
|
207
|
+
console.log("Saved results cleared.");
|
|
194
208
|
}
|
|
195
209
|
}
|
|
196
210
|
catch (err) {
|
|
@@ -199,17 +213,17 @@ resultsCmd
|
|
|
199
213
|
});
|
|
200
214
|
// ── config command ──────────────────────────────────────────────────
|
|
201
215
|
const configCmd = program
|
|
202
|
-
.command(
|
|
203
|
-
.description(
|
|
204
|
-
.option(
|
|
216
|
+
.command("config")
|
|
217
|
+
.description("View and update preferences")
|
|
218
|
+
.option("--json", "Output as JSON")
|
|
205
219
|
.action(async (options) => {
|
|
206
220
|
try {
|
|
207
|
-
const { runConfigShow, getConfigData } = await import(
|
|
221
|
+
const { runConfigShow, getConfigData } = await import("./commands/config.js");
|
|
208
222
|
if (options.json) {
|
|
209
223
|
console.log(formatJsonSuccess(getConfigData()));
|
|
210
224
|
}
|
|
211
225
|
else {
|
|
212
|
-
runConfigShow(
|
|
226
|
+
runConfigShow();
|
|
213
227
|
}
|
|
214
228
|
}
|
|
215
229
|
catch (err) {
|
|
@@ -217,12 +231,12 @@ const configCmd = program
|
|
|
217
231
|
}
|
|
218
232
|
});
|
|
219
233
|
configCmd
|
|
220
|
-
.command(
|
|
221
|
-
.description(
|
|
222
|
-
.option(
|
|
234
|
+
.command("set <key> <value>")
|
|
235
|
+
.description("Update a single preference (e.g. config set minStars 100)")
|
|
236
|
+
.option("--json", "Output as JSON")
|
|
223
237
|
.action(async (key, value, options) => {
|
|
224
238
|
try {
|
|
225
|
-
const { runConfigSet } = await import(
|
|
239
|
+
const { runConfigSet } = await import("./commands/config.js");
|
|
226
240
|
const updated = runConfigSet(key, value);
|
|
227
241
|
if (options.json) {
|
|
228
242
|
console.log(formatJsonSuccess(updated));
|
|
@@ -236,18 +250,18 @@ configCmd
|
|
|
236
250
|
}
|
|
237
251
|
});
|
|
238
252
|
configCmd
|
|
239
|
-
.command(
|
|
240
|
-
.description(
|
|
241
|
-
.option(
|
|
253
|
+
.command("reset")
|
|
254
|
+
.description("Reset all preferences to defaults")
|
|
255
|
+
.option("--json", "Output as JSON")
|
|
242
256
|
.action(async (options) => {
|
|
243
257
|
try {
|
|
244
|
-
const { runConfigReset } = await import(
|
|
258
|
+
const { runConfigReset } = await import("./commands/config.js");
|
|
245
259
|
const defaults = runConfigReset();
|
|
246
260
|
if (options.json) {
|
|
247
261
|
console.log(formatJsonSuccess(defaults));
|
|
248
262
|
}
|
|
249
263
|
else {
|
|
250
|
-
console.log(
|
|
264
|
+
console.log("✅ Preferences reset to defaults.");
|
|
251
265
|
}
|
|
252
266
|
}
|
|
253
267
|
catch (err) {
|
|
@@ -255,18 +269,19 @@ configCmd
|
|
|
255
269
|
}
|
|
256
270
|
});
|
|
257
271
|
program
|
|
258
|
-
.command(
|
|
259
|
-
.description(
|
|
260
|
-
.option(
|
|
261
|
-
.option(
|
|
262
|
-
.option(
|
|
272
|
+
.command("vet-list")
|
|
273
|
+
.description("Re-vet all saved search results and classify their current status")
|
|
274
|
+
.option("--prune", "Remove unavailable issues from saved results")
|
|
275
|
+
.option("--concurrency <n>", "Max concurrent API requests (default: 5)", parseInt)
|
|
276
|
+
.option("--json", "Output as JSON")
|
|
263
277
|
.action(async (options) => {
|
|
264
278
|
try {
|
|
265
|
-
if (options.concurrency !== undefined &&
|
|
266
|
-
|
|
279
|
+
if (options.concurrency !== undefined &&
|
|
280
|
+
(isNaN(options.concurrency) || options.concurrency < 1)) {
|
|
281
|
+
console.error("Error: --concurrency must be a positive integer");
|
|
267
282
|
process.exit(1);
|
|
268
283
|
}
|
|
269
|
-
const { runVetList } = await import(
|
|
284
|
+
const { runVetList } = await import("./commands/vet-list.js");
|
|
270
285
|
const state = loadLocalState();
|
|
271
286
|
const result = await runVetList({
|
|
272
287
|
state,
|
|
@@ -278,16 +293,21 @@ program
|
|
|
278
293
|
}
|
|
279
294
|
else {
|
|
280
295
|
if (result.results.length === 0) {
|
|
281
|
-
console.log(
|
|
296
|
+
console.log("\nNo saved results to vet. Run `oss-scout search` first.\n");
|
|
282
297
|
return;
|
|
283
298
|
}
|
|
284
299
|
console.log(`\nVet-list results (${result.summary.total}):\n`);
|
|
285
300
|
for (const r of result.results) {
|
|
286
|
-
const icon = r.status ===
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
301
|
+
const icon = r.status === "still_available"
|
|
302
|
+
? "✅"
|
|
303
|
+
: r.status === "claimed"
|
|
304
|
+
? "🔒"
|
|
305
|
+
: r.status === "has_pr"
|
|
306
|
+
? "🔀"
|
|
307
|
+
: r.status === "closed"
|
|
308
|
+
? "🚫"
|
|
309
|
+
: "❌";
|
|
310
|
+
const score = r.viabilityScore != null ? ` [${r.viabilityScore}/100]` : "";
|
|
291
311
|
console.log(` ${icon} ${r.repo}#${r.number} — ${r.status}${score}`);
|
|
292
312
|
console.log(` ${r.title}`);
|
|
293
313
|
}
|
|
@@ -302,34 +322,145 @@ program
|
|
|
302
322
|
handleCommandError(err, options);
|
|
303
323
|
}
|
|
304
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
|
+
});
|
|
305
432
|
program
|
|
306
|
-
.command(
|
|
307
|
-
.description(
|
|
308
|
-
.option(
|
|
433
|
+
.command("vet <issue-url>")
|
|
434
|
+
.description("Vet a specific GitHub issue for claimability and project health")
|
|
435
|
+
.option("--json", "Output as JSON")
|
|
309
436
|
.action(async (issueUrl, options) => {
|
|
310
437
|
try {
|
|
311
|
-
const { runVet } = await import(
|
|
438
|
+
const { runVet } = await import("./commands/vet.js");
|
|
312
439
|
const state = loadLocalState();
|
|
313
440
|
const result = await runVet({ issueUrl, state });
|
|
314
441
|
if (options.json) {
|
|
315
442
|
console.log(formatJsonSuccess(result));
|
|
316
443
|
}
|
|
317
444
|
else {
|
|
318
|
-
const icon = result.recommendation ===
|
|
445
|
+
const icon = result.recommendation === "approve"
|
|
446
|
+
? "✅"
|
|
447
|
+
: result.recommendation === "skip"
|
|
448
|
+
? "❌"
|
|
449
|
+
: "⚠️";
|
|
319
450
|
console.log(`\n${icon} ${result.issue.repo}#${result.issue.number}: ${result.recommendation.toUpperCase()}`);
|
|
320
451
|
console.log(` ${result.issue.title}`);
|
|
321
452
|
console.log(` ${result.issue.url}\n`);
|
|
322
453
|
if (result.reasonsToApprove.length > 0) {
|
|
323
|
-
console.log(
|
|
454
|
+
console.log("Reasons to approve:");
|
|
324
455
|
for (const r of result.reasonsToApprove)
|
|
325
456
|
console.log(` + ${r}`);
|
|
326
457
|
}
|
|
327
458
|
if (result.reasonsToSkip.length > 0) {
|
|
328
|
-
console.log(
|
|
459
|
+
console.log("Reasons to skip:");
|
|
329
460
|
for (const r of result.reasonsToSkip)
|
|
330
461
|
console.log(` - ${r}`);
|
|
331
462
|
}
|
|
332
|
-
console.log(`\nProject health: ${result.projectHealth.isActive ?
|
|
463
|
+
console.log(`\nProject health: ${result.projectHealth.isActive ? "Active" : "Inactive"}`);
|
|
333
464
|
console.log(` Last commit: ${result.projectHealth.daysSinceLastCommit} days ago`);
|
|
334
465
|
console.log(` CI status: ${result.projectHealth.ciStatus}`);
|
|
335
466
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Config command — view and update oss-scout preferences.
|
|
3
3
|
*/
|
|
4
|
-
import type { ScoutPreferences } from
|
|
4
|
+
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
|
*/
|