@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
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,12 +213,12 @@ 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
|
}
|
|
@@ -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
|
}
|
|
@@ -303,33 +323,37 @@ program
|
|
|
303
323
|
}
|
|
304
324
|
});
|
|
305
325
|
program
|
|
306
|
-
.command(
|
|
307
|
-
.description(
|
|
308
|
-
.option(
|
|
326
|
+
.command("vet <issue-url>")
|
|
327
|
+
.description("Vet a specific GitHub issue for claimability and project health")
|
|
328
|
+
.option("--json", "Output as JSON")
|
|
309
329
|
.action(async (issueUrl, options) => {
|
|
310
330
|
try {
|
|
311
|
-
const { runVet } = await import(
|
|
331
|
+
const { runVet } = await import("./commands/vet.js");
|
|
312
332
|
const state = loadLocalState();
|
|
313
333
|
const result = await runVet({ issueUrl, state });
|
|
314
334
|
if (options.json) {
|
|
315
335
|
console.log(formatJsonSuccess(result));
|
|
316
336
|
}
|
|
317
337
|
else {
|
|
318
|
-
const icon = result.recommendation ===
|
|
338
|
+
const icon = result.recommendation === "approve"
|
|
339
|
+
? "✅"
|
|
340
|
+
: result.recommendation === "skip"
|
|
341
|
+
? "❌"
|
|
342
|
+
: "⚠️";
|
|
319
343
|
console.log(`\n${icon} ${result.issue.repo}#${result.issue.number}: ${result.recommendation.toUpperCase()}`);
|
|
320
344
|
console.log(` ${result.issue.title}`);
|
|
321
345
|
console.log(` ${result.issue.url}\n`);
|
|
322
346
|
if (result.reasonsToApprove.length > 0) {
|
|
323
|
-
console.log(
|
|
347
|
+
console.log("Reasons to approve:");
|
|
324
348
|
for (const r of result.reasonsToApprove)
|
|
325
349
|
console.log(` + ${r}`);
|
|
326
350
|
}
|
|
327
351
|
if (result.reasonsToSkip.length > 0) {
|
|
328
|
-
console.log(
|
|
352
|
+
console.log("Reasons to skip:");
|
|
329
353
|
for (const r of result.reasonsToSkip)
|
|
330
354
|
console.log(` - ${r}`);
|
|
331
355
|
}
|
|
332
|
-
console.log(`\nProject health: ${result.projectHealth.isActive ?
|
|
356
|
+
console.log(`\nProject health: ${result.projectHealth.isActive ? "Active" : "Inactive"}`);
|
|
333
357
|
console.log(` Last commit: ${result.projectHealth.daysSinceLastCommit} days ago`);
|
|
334
358
|
console.log(` CI status: ${result.projectHealth.ciStatus}`);
|
|
335
359
|
}
|
package/dist/commands/config.js
CHANGED
|
@@ -1,41 +1,37 @@
|
|
|
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
|
-
|
|
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
|
+
preferredOrgs: { type: "array" },
|
|
14
|
+
minStars: { type: "number" },
|
|
15
|
+
maxIssueAgeDays: { type: "number" },
|
|
16
|
+
minRepoScoreThreshold: { type: "number" },
|
|
17
|
+
includeDocIssues: { type: "boolean" },
|
|
18
|
+
scope: { type: "enum-array", validValues: IssueScopeSchema.options },
|
|
19
|
+
projectCategories: {
|
|
20
|
+
type: "enum-array",
|
|
21
|
+
validValues: ProjectCategorySchema.options,
|
|
22
|
+
},
|
|
23
|
+
persistence: { type: "enum", validValues: PersistenceModeSchema.options },
|
|
24
|
+
defaultStrategy: {
|
|
25
|
+
type: "enum-array",
|
|
26
|
+
validValues: SearchStrategySchema.options,
|
|
27
|
+
},
|
|
28
|
+
githubUsername: { type: "string" },
|
|
23
29
|
};
|
|
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
30
|
function parseBoolean(value) {
|
|
35
31
|
const lower = value.toLowerCase();
|
|
36
|
-
if (lower ===
|
|
32
|
+
if (lower === "true" || lower === "yes")
|
|
37
33
|
return true;
|
|
38
|
-
if (lower ===
|
|
34
|
+
if (lower === "false" || lower === "no")
|
|
39
35
|
return false;
|
|
40
36
|
throw new ValidationError(`Invalid boolean value: "${value}". Use true/false or yes/no.`);
|
|
41
37
|
}
|
|
@@ -48,7 +44,7 @@ function parseNumber(value, key) {
|
|
|
48
44
|
}
|
|
49
45
|
function parseArrayValue(value) {
|
|
50
46
|
return value
|
|
51
|
-
.split(
|
|
47
|
+
.split(",")
|
|
52
48
|
.map((s) => s.trim())
|
|
53
49
|
.filter((s) => s.length > 0);
|
|
54
50
|
}
|
|
@@ -56,7 +52,7 @@ function parseArrayValue(value) {
|
|
|
56
52
|
* Apply an array update: plain set, +append, or -remove.
|
|
57
53
|
*/
|
|
58
54
|
function updateArray(current, value) {
|
|
59
|
-
if (value.startsWith(
|
|
55
|
+
if (value.startsWith("+")) {
|
|
60
56
|
const toAdd = parseArrayValue(value.slice(1));
|
|
61
57
|
const merged = [...current];
|
|
62
58
|
for (const item of toAdd) {
|
|
@@ -65,14 +61,14 @@ function updateArray(current, value) {
|
|
|
65
61
|
}
|
|
66
62
|
return merged;
|
|
67
63
|
}
|
|
68
|
-
if (value.startsWith(
|
|
64
|
+
if (value.startsWith("-")) {
|
|
69
65
|
const toRemove = new Set(parseArrayValue(value.slice(1)));
|
|
70
66
|
return current.filter((item) => !toRemove.has(item));
|
|
71
67
|
}
|
|
72
68
|
return parseArrayValue(value);
|
|
73
69
|
}
|
|
74
70
|
function formatArray(arr) {
|
|
75
|
-
return arr.length > 0 ? arr.join(
|
|
71
|
+
return arr.length > 0 ? arr.join(", ") : "(none)";
|
|
76
72
|
}
|
|
77
73
|
/**
|
|
78
74
|
* Display current preferences in human-readable format.
|
|
@@ -84,11 +80,11 @@ export function runConfigShow(options) {
|
|
|
84
80
|
// JSON output handled by caller
|
|
85
81
|
return;
|
|
86
82
|
}
|
|
87
|
-
console.log(
|
|
88
|
-
console.log(` githubUsername: ${prefs.githubUsername ||
|
|
83
|
+
console.log("\n⚙️ oss-scout preferences\n");
|
|
84
|
+
console.log(` githubUsername: ${prefs.githubUsername || "(not set)"}`);
|
|
89
85
|
console.log(` languages: ${formatArray(prefs.languages)}`);
|
|
90
86
|
console.log(` labels: ${formatArray(prefs.labels)}`);
|
|
91
|
-
console.log(` scope: ${prefs.scope ? formatArray(prefs.scope) :
|
|
87
|
+
console.log(` scope: ${prefs.scope ? formatArray(prefs.scope) : "(all)"}`);
|
|
92
88
|
console.log(` minStars: ${prefs.minStars}`);
|
|
93
89
|
console.log(` maxIssueAgeDays: ${prefs.maxIssueAgeDays}`);
|
|
94
90
|
console.log(` minRepoScoreThreshold: ${prefs.minRepoScoreThreshold}`);
|
|
@@ -98,6 +94,7 @@ export function runConfigShow(options) {
|
|
|
98
94
|
console.log(` excludeRepos: ${formatArray(prefs.excludeRepos)}`);
|
|
99
95
|
console.log(` excludeOrgs: ${formatArray(prefs.excludeOrgs)}`);
|
|
100
96
|
console.log(` aiPolicyBlocklist: ${formatArray(prefs.aiPolicyBlocklist)}`);
|
|
97
|
+
console.log(` defaultStrategy: ${prefs.defaultStrategy ? formatArray(prefs.defaultStrategy) : "(all)"}`);
|
|
101
98
|
console.log(` persistence: ${prefs.persistence}`);
|
|
102
99
|
console.log();
|
|
103
100
|
}
|
|
@@ -112,46 +109,53 @@ export function getConfigData() {
|
|
|
112
109
|
* Update a single preference by key.
|
|
113
110
|
*/
|
|
114
111
|
export function runConfigSet(key, value) {
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
const field = FIELD_CONFIGS[key];
|
|
113
|
+
if (!field) {
|
|
114
|
+
throw new ValidationError(`Unknown config key: "${key}". Valid keys: ${Object.keys(FIELD_CONFIGS).sort().join(", ")}`);
|
|
117
115
|
}
|
|
118
116
|
const state = loadLocalState();
|
|
119
117
|
const prefs = { ...state.preferences };
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
118
|
+
switch (field.type) {
|
|
119
|
+
case "string":
|
|
120
|
+
prefs[key] = value;
|
|
121
|
+
break;
|
|
122
|
+
case "boolean":
|
|
123
|
+
prefs[key] = parseBoolean(value);
|
|
124
|
+
break;
|
|
125
|
+
case "number":
|
|
126
|
+
prefs[key] = parseNumber(value, key);
|
|
127
|
+
break;
|
|
128
|
+
case "array": {
|
|
129
|
+
const current = prefs[key] ?? [];
|
|
130
|
+
prefs[key] = updateArray(current, value);
|
|
131
|
+
break;
|
|
134
132
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
133
|
+
case "enum": {
|
|
134
|
+
const validValues = field.validValues;
|
|
135
|
+
if (!validValues.includes(value)) {
|
|
136
|
+
throw new ValidationError(`Invalid value for "${key}": "${value}". Valid: ${validValues.join(", ")}`);
|
|
137
|
+
}
|
|
138
|
+
prefs[key] = value;
|
|
139
|
+
break;
|
|
142
140
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
141
|
+
case "enum-array": {
|
|
142
|
+
const current = prefs[key] ?? [];
|
|
143
|
+
const updated = updateArray(current, value);
|
|
144
|
+
const validValues = field.validValues;
|
|
145
|
+
const invalid = updated.filter((s) => !validValues.includes(s));
|
|
146
|
+
if (invalid.length > 0) {
|
|
147
|
+
throw new ValidationError(`Invalid value(s) for "${key}": ${invalid.join(", ")}. Valid: ${validValues.join(", ")}`);
|
|
148
|
+
}
|
|
149
|
+
// For 'scope', empty array means undefined (all scopes)
|
|
150
|
+
if (key === "scope") {
|
|
151
|
+
prefs[key] =
|
|
152
|
+
updated.length > 0 ? updated : undefined;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
prefs[key] = updated;
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
149
158
|
}
|
|
150
|
-
prefs[key] = value;
|
|
151
|
-
}
|
|
152
|
-
else if (ARRAY_FIELDS.has(key)) {
|
|
153
|
-
const current = prefs[key] ?? [];
|
|
154
|
-
prefs[key] = updateArray(current, value);
|
|
155
159
|
}
|
|
156
160
|
// Validate the full preferences object
|
|
157
161
|
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 ?? [];
|