@oss-scout/core 0.11.0 → 1.0.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 +78 -61
- package/dist/cli.js +401 -425
- package/dist/commands/command-scout.d.ts +21 -0
- package/dist/commands/command-scout.js +21 -0
- package/dist/commands/config.js +10 -128
- package/dist/commands/features.js +15 -28
- package/dist/commands/results.d.ts +13 -2
- package/dist/commands/results.js +29 -2
- package/dist/commands/search.js +63 -70
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +35 -6
- package/dist/commands/skip.d.ts +4 -0
- package/dist/commands/skip.js +45 -55
- package/dist/commands/sync.d.ts +10 -0
- package/dist/commands/sync.js +10 -0
- package/dist/commands/vet-list.js +3 -19
- package/dist/commands/vet.js +18 -25
- package/dist/commands/with-scout.d.ts +32 -0
- package/dist/commands/with-scout.js +41 -0
- package/dist/core/anti-llm-policy.js +4 -5
- package/dist/core/bootstrap.d.ts +2 -2
- package/dist/core/bootstrap.js +5 -9
- package/dist/core/errors.d.ts +10 -0
- package/dist/core/errors.js +20 -5
- package/dist/core/feature-discovery.d.ts +13 -1
- package/dist/core/feature-discovery.js +104 -81
- package/dist/core/gist-state-store.d.ts +13 -12
- package/dist/core/gist-state-store.js +128 -53
- package/dist/core/http-cache.d.ts +32 -2
- package/dist/core/http-cache.js +74 -19
- package/dist/core/issue-discovery.d.ts +2 -0
- package/dist/core/issue-discovery.js +44 -29
- package/dist/core/issue-eligibility.d.ts +10 -4
- package/dist/core/issue-eligibility.js +119 -67
- package/dist/core/issue-graphql.d.ts +58 -0
- package/dist/core/issue-graphql.js +108 -0
- package/dist/core/issue-vetting.d.ts +105 -8
- package/dist/core/issue-vetting.js +234 -107
- package/dist/core/local-state.d.ts +6 -2
- package/dist/core/local-state.js +23 -5
- package/dist/core/logger.d.ts +12 -4
- package/dist/core/logger.js +33 -7
- package/dist/core/personalization.d.ts +15 -10
- package/dist/core/personalization.js +30 -22
- package/dist/core/preference-fields.d.ts +47 -0
- package/dist/core/preference-fields.js +178 -0
- package/dist/core/repo-health.js +31 -15
- package/dist/core/roadmap.js +17 -3
- package/dist/core/schemas.d.ts +144 -26
- package/dist/core/schemas.js +74 -17
- package/dist/core/search-budget.d.ts +9 -0
- package/dist/core/search-budget.js +36 -3
- package/dist/core/search-phases.d.ts +0 -18
- package/dist/core/search-phases.js +27 -82
- package/dist/core/types.d.ts +136 -38
- package/dist/core/utils.js +60 -26
- package/dist/formatters/markdown.d.ts +10 -0
- package/dist/formatters/markdown.js +31 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +8 -0
- package/dist/scout.d.ts +59 -10
- package/dist/scout.js +244 -20
- package/package.json +1 -1
package/dist/core/schemas.d.ts
CHANGED
|
@@ -7,9 +7,6 @@
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
export declare const IssueStatusSchema: z.ZodEnum<{
|
|
9
9
|
candidate: "candidate";
|
|
10
|
-
claimed: "claimed";
|
|
11
|
-
in_progress: "in_progress";
|
|
12
|
-
pr_submitted: "pr_submitted";
|
|
13
10
|
}>;
|
|
14
11
|
export declare const ProjectCategorySchema: z.ZodEnum<{
|
|
15
12
|
nonprofit: "nonprofit";
|
|
@@ -37,7 +34,7 @@ export declare const RepoSignalsSchema: z.ZodObject<{
|
|
|
37
34
|
hasActiveMaintainers: z.ZodBoolean;
|
|
38
35
|
isResponsive: z.ZodBoolean;
|
|
39
36
|
hasHostileComments: z.ZodBoolean;
|
|
40
|
-
}, z.core.$
|
|
37
|
+
}, z.core.$loose>;
|
|
41
38
|
export declare const RepoScoreSchema: z.ZodObject<{
|
|
42
39
|
repo: z.ZodString;
|
|
43
40
|
score: z.ZodNumber;
|
|
@@ -50,25 +47,25 @@ export declare const RepoScoreSchema: z.ZodObject<{
|
|
|
50
47
|
hasActiveMaintainers: z.ZodBoolean;
|
|
51
48
|
isResponsive: z.ZodBoolean;
|
|
52
49
|
hasHostileComments: z.ZodBoolean;
|
|
53
|
-
}, z.core.$
|
|
50
|
+
}, z.core.$loose>;
|
|
54
51
|
stargazersCount: z.ZodOptional<z.ZodNumber>;
|
|
55
52
|
language: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
56
|
-
}, z.core.$
|
|
53
|
+
}, z.core.$loose>;
|
|
57
54
|
export declare const StoredMergedPRSchema: z.ZodObject<{
|
|
58
55
|
url: z.ZodString;
|
|
59
56
|
title: z.ZodString;
|
|
60
57
|
mergedAt: z.ZodString;
|
|
61
|
-
}, z.core.$
|
|
58
|
+
}, z.core.$loose>;
|
|
62
59
|
export declare const StoredClosedPRSchema: z.ZodObject<{
|
|
63
60
|
url: z.ZodString;
|
|
64
61
|
title: z.ZodString;
|
|
65
62
|
closedAt: z.ZodString;
|
|
66
|
-
}, z.core.$
|
|
63
|
+
}, z.core.$loose>;
|
|
67
64
|
export declare const StoredOpenPRSchema: z.ZodObject<{
|
|
68
65
|
url: z.ZodString;
|
|
69
66
|
title: z.ZodString;
|
|
70
67
|
openedAt: z.ZodString;
|
|
71
|
-
}, z.core.$
|
|
68
|
+
}, z.core.$loose>;
|
|
72
69
|
export declare const ContributionGuidelinesSchema: z.ZodObject<{
|
|
73
70
|
branchNamingConvention: z.ZodOptional<z.ZodString>;
|
|
74
71
|
commitMessageFormat: z.ZodOptional<z.ZodString>;
|
|
@@ -142,9 +139,6 @@ export declare const TrackedIssueSchema: z.ZodObject<{
|
|
|
142
139
|
title: z.ZodString;
|
|
143
140
|
status: z.ZodEnum<{
|
|
144
141
|
candidate: "candidate";
|
|
145
|
-
claimed: "claimed";
|
|
146
|
-
in_progress: "in_progress";
|
|
147
|
-
pr_submitted: "pr_submitted";
|
|
148
142
|
}>;
|
|
149
143
|
labels: z.ZodArray<z.ZodString>;
|
|
150
144
|
createdAt: z.ZodString;
|
|
@@ -195,7 +189,7 @@ export declare const SkippedIssueSchema: z.ZodObject<{
|
|
|
195
189
|
number: z.ZodNumber;
|
|
196
190
|
title: z.ZodString;
|
|
197
191
|
skippedAt: z.ZodString;
|
|
198
|
-
}, z.core.$
|
|
192
|
+
}, z.core.$loose>;
|
|
199
193
|
export declare const HorizonSchema: z.ZodEnum<{
|
|
200
194
|
"quick-win": "quick-win";
|
|
201
195
|
"bigger-bet": "bigger-bet";
|
|
@@ -212,7 +206,11 @@ export declare const SavedCandidateSchema: z.ZodObject<{
|
|
|
212
206
|
needs_review: "needs_review";
|
|
213
207
|
}>;
|
|
214
208
|
viabilityScore: z.ZodNumber;
|
|
215
|
-
searchPriority: z.
|
|
209
|
+
searchPriority: z.ZodCatch<z.ZodEnum<{
|
|
210
|
+
normal: "normal";
|
|
211
|
+
starred: "starred";
|
|
212
|
+
merged_pr: "merged_pr";
|
|
213
|
+
}>>;
|
|
216
214
|
firstSeenAt: z.ZodString;
|
|
217
215
|
lastSeenAt: z.ZodString;
|
|
218
216
|
lastScore: z.ZodNumber;
|
|
@@ -220,7 +218,18 @@ export declare const SavedCandidateSchema: z.ZodObject<{
|
|
|
220
218
|
"quick-win": "quick-win";
|
|
221
219
|
"bigger-bet": "bigger-bet";
|
|
222
220
|
}>>;
|
|
223
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Availability status recorded by the previous `vet-list` run, so the next
|
|
223
|
+
* run can report transitions ("was available, now claimed") instead of
|
|
224
|
+
* re-diffing full snapshots (#165). "error" is never stored.
|
|
225
|
+
*/
|
|
226
|
+
lastStatus: z.ZodOptional<z.ZodEnum<{
|
|
227
|
+
closed: "closed";
|
|
228
|
+
still_available: "still_available";
|
|
229
|
+
claimed: "claimed";
|
|
230
|
+
has_pr: "has_pr";
|
|
231
|
+
}>>;
|
|
232
|
+
}, z.core.$loose>;
|
|
224
233
|
export declare const PersistenceModeSchema: z.ZodEnum<{
|
|
225
234
|
local: "local";
|
|
226
235
|
gist: "gist";
|
|
@@ -261,13 +270,48 @@ export declare const ScoutPreferencesSchema: z.ZodObject<{
|
|
|
261
270
|
broad: "broad";
|
|
262
271
|
maintained: "maintained";
|
|
263
272
|
}>>>;
|
|
273
|
+
/**
|
|
274
|
+
* Persisted personalization defaults (#168). The `--prefer-languages`,
|
|
275
|
+
* `--prefer-repos`, and `--diversity-ratio` search flags override these when
|
|
276
|
+
* passed, so a stored preference removes the need to retype the boost every
|
|
277
|
+
* search. Empty / 0 disables the corresponding signal (same as the flags).
|
|
278
|
+
*/
|
|
279
|
+
preferLanguages: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
280
|
+
preferRepos: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
281
|
+
diversityRatio: z.ZodDefault<z.ZodNumber>;
|
|
264
282
|
broadPhaseDelayMs: z.ZodDefault<z.ZodNumber>;
|
|
283
|
+
/**
|
|
284
|
+
* Skip the expensive broad phase once this many candidates were found by
|
|
285
|
+
* the cheaper phases. Clamped at runtime to maxResults - 1 so it stays
|
|
286
|
+
* satisfiable; 0 disables skipping entirely.
|
|
287
|
+
*/
|
|
265
288
|
skipBroadWhenSufficientResults: z.ZodDefault<z.ZodNumber>;
|
|
289
|
+
/**
|
|
290
|
+
* Optional Ollama model id used for SLM pre-triage during vetting
|
|
291
|
+
* (oss-autopilot#1122). Empty disables the feature. Recommended values:
|
|
292
|
+
* `gemma4:e4b` (default for capable hardware) or `gemma4:e2b` /
|
|
293
|
+
* `qwen3:1.7b` for low-RAM machines.
|
|
294
|
+
*/
|
|
266
295
|
slmTriageModel: z.ZodDefault<z.ZodString>;
|
|
296
|
+
/**
|
|
297
|
+
* Override the Ollama HTTP host. Defaults to `http://127.0.0.1:11434`
|
|
298
|
+
* when empty. Useful when Ollama runs on a different machine on the
|
|
299
|
+
* local network.
|
|
300
|
+
*/
|
|
267
301
|
slmTriageHost: z.ZodDefault<z.ZodString>;
|
|
302
|
+
/**
|
|
303
|
+
* Minimum merged-PR count for a repo to qualify as an anchor in
|
|
304
|
+
* `scout features` (#98). Lowering surfaces more anchors at the cost
|
|
305
|
+
* of weaker prior engagement signal.
|
|
306
|
+
*/
|
|
268
307
|
featuresAnchorThreshold: z.ZodDefault<z.ZodNumber>;
|
|
308
|
+
/**
|
|
309
|
+
* Quick-wins / bigger-bets split ratio for `scout features` (#99).
|
|
310
|
+
* 0.6 means 60% quick wins, 40% bigger bets when both pools are
|
|
311
|
+
* abundant. Deficits redirect to the other bucket.
|
|
312
|
+
*/
|
|
269
313
|
featuresSplitRatio: z.ZodDefault<z.ZodNumber>;
|
|
270
|
-
}, z.core.$
|
|
314
|
+
}, z.core.$loose>;
|
|
271
315
|
export declare const ScoutStateSchema: z.ZodObject<{
|
|
272
316
|
version: z.ZodLiteral<1>;
|
|
273
317
|
preferences: z.ZodDefault<z.ZodObject<{
|
|
@@ -306,13 +350,48 @@ export declare const ScoutStateSchema: z.ZodObject<{
|
|
|
306
350
|
broad: "broad";
|
|
307
351
|
maintained: "maintained";
|
|
308
352
|
}>>>;
|
|
353
|
+
/**
|
|
354
|
+
* Persisted personalization defaults (#168). The `--prefer-languages`,
|
|
355
|
+
* `--prefer-repos`, and `--diversity-ratio` search flags override these when
|
|
356
|
+
* passed, so a stored preference removes the need to retype the boost every
|
|
357
|
+
* search. Empty / 0 disables the corresponding signal (same as the flags).
|
|
358
|
+
*/
|
|
359
|
+
preferLanguages: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
360
|
+
preferRepos: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
361
|
+
diversityRatio: z.ZodDefault<z.ZodNumber>;
|
|
309
362
|
broadPhaseDelayMs: z.ZodDefault<z.ZodNumber>;
|
|
363
|
+
/**
|
|
364
|
+
* Skip the expensive broad phase once this many candidates were found by
|
|
365
|
+
* the cheaper phases. Clamped at runtime to maxResults - 1 so it stays
|
|
366
|
+
* satisfiable; 0 disables skipping entirely.
|
|
367
|
+
*/
|
|
310
368
|
skipBroadWhenSufficientResults: z.ZodDefault<z.ZodNumber>;
|
|
369
|
+
/**
|
|
370
|
+
* Optional Ollama model id used for SLM pre-triage during vetting
|
|
371
|
+
* (oss-autopilot#1122). Empty disables the feature. Recommended values:
|
|
372
|
+
* `gemma4:e4b` (default for capable hardware) or `gemma4:e2b` /
|
|
373
|
+
* `qwen3:1.7b` for low-RAM machines.
|
|
374
|
+
*/
|
|
311
375
|
slmTriageModel: z.ZodDefault<z.ZodString>;
|
|
376
|
+
/**
|
|
377
|
+
* Override the Ollama HTTP host. Defaults to `http://127.0.0.1:11434`
|
|
378
|
+
* when empty. Useful when Ollama runs on a different machine on the
|
|
379
|
+
* local network.
|
|
380
|
+
*/
|
|
312
381
|
slmTriageHost: z.ZodDefault<z.ZodString>;
|
|
382
|
+
/**
|
|
383
|
+
* Minimum merged-PR count for a repo to qualify as an anchor in
|
|
384
|
+
* `scout features` (#98). Lowering surfaces more anchors at the cost
|
|
385
|
+
* of weaker prior engagement signal.
|
|
386
|
+
*/
|
|
313
387
|
featuresAnchorThreshold: z.ZodDefault<z.ZodNumber>;
|
|
388
|
+
/**
|
|
389
|
+
* Quick-wins / bigger-bets split ratio for `scout features` (#99).
|
|
390
|
+
* 0.6 means 60% quick wins, 40% bigger bets when both pools are
|
|
391
|
+
* abundant. Deficits redirect to the other bucket.
|
|
392
|
+
*/
|
|
314
393
|
featuresSplitRatio: z.ZodDefault<z.ZodNumber>;
|
|
315
|
-
}, z.core.$
|
|
394
|
+
}, z.core.$loose>>;
|
|
316
395
|
repoScores: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
317
396
|
repo: z.ZodString;
|
|
318
397
|
score: z.ZodNumber;
|
|
@@ -325,27 +404,27 @@ export declare const ScoutStateSchema: z.ZodObject<{
|
|
|
325
404
|
hasActiveMaintainers: z.ZodBoolean;
|
|
326
405
|
isResponsive: z.ZodBoolean;
|
|
327
406
|
hasHostileComments: z.ZodBoolean;
|
|
328
|
-
}, z.core.$
|
|
407
|
+
}, z.core.$loose>;
|
|
329
408
|
stargazersCount: z.ZodOptional<z.ZodNumber>;
|
|
330
409
|
language: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
331
|
-
}, z.core.$
|
|
410
|
+
}, z.core.$loose>>>;
|
|
332
411
|
starredRepos: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
333
412
|
starredReposLastFetched: z.ZodOptional<z.ZodString>;
|
|
334
413
|
mergedPRs: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
335
414
|
url: z.ZodString;
|
|
336
415
|
title: z.ZodString;
|
|
337
416
|
mergedAt: z.ZodString;
|
|
338
|
-
}, z.core.$
|
|
417
|
+
}, z.core.$loose>>>;
|
|
339
418
|
closedPRs: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
340
419
|
url: z.ZodString;
|
|
341
420
|
title: z.ZodString;
|
|
342
421
|
closedAt: z.ZodString;
|
|
343
|
-
}, z.core.$
|
|
422
|
+
}, z.core.$loose>>>;
|
|
344
423
|
openPRs: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
345
424
|
url: z.ZodString;
|
|
346
425
|
title: z.ZodString;
|
|
347
426
|
openedAt: z.ZodString;
|
|
348
|
-
}, z.core.$
|
|
427
|
+
}, z.core.$loose>>>;
|
|
349
428
|
savedResults: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
350
429
|
issueUrl: z.ZodString;
|
|
351
430
|
repo: z.ZodString;
|
|
@@ -358,7 +437,11 @@ export declare const ScoutStateSchema: z.ZodObject<{
|
|
|
358
437
|
needs_review: "needs_review";
|
|
359
438
|
}>;
|
|
360
439
|
viabilityScore: z.ZodNumber;
|
|
361
|
-
searchPriority: z.
|
|
440
|
+
searchPriority: z.ZodCatch<z.ZodEnum<{
|
|
441
|
+
normal: "normal";
|
|
442
|
+
starred: "starred";
|
|
443
|
+
merged_pr: "merged_pr";
|
|
444
|
+
}>>;
|
|
362
445
|
firstSeenAt: z.ZodString;
|
|
363
446
|
lastSeenAt: z.ZodString;
|
|
364
447
|
lastScore: z.ZodNumber;
|
|
@@ -366,18 +449,53 @@ export declare const ScoutStateSchema: z.ZodObject<{
|
|
|
366
449
|
"quick-win": "quick-win";
|
|
367
450
|
"bigger-bet": "bigger-bet";
|
|
368
451
|
}>>;
|
|
369
|
-
|
|
452
|
+
/**
|
|
453
|
+
* Availability status recorded by the previous `vet-list` run, so the next
|
|
454
|
+
* run can report transitions ("was available, now claimed") instead of
|
|
455
|
+
* re-diffing full snapshots (#165). "error" is never stored.
|
|
456
|
+
*/
|
|
457
|
+
lastStatus: z.ZodOptional<z.ZodEnum<{
|
|
458
|
+
closed: "closed";
|
|
459
|
+
still_available: "still_available";
|
|
460
|
+
claimed: "claimed";
|
|
461
|
+
has_pr: "has_pr";
|
|
462
|
+
}>>;
|
|
463
|
+
}, z.core.$loose>>>;
|
|
370
464
|
skippedIssues: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
371
465
|
url: z.ZodString;
|
|
372
466
|
repo: z.ZodString;
|
|
373
467
|
number: z.ZodNumber;
|
|
374
468
|
title: z.ZodString;
|
|
375
469
|
skippedAt: z.ZodString;
|
|
376
|
-
}, z.core.$
|
|
470
|
+
}, z.core.$loose>>>;
|
|
471
|
+
/**
|
|
472
|
+
* Deletion tombstones (#117). Without these, the gist union-merge would
|
|
473
|
+
* resurrect any saved result or skipped issue removed on one machine the
|
|
474
|
+
* next time it merged against another machine's copy that still had it.
|
|
475
|
+
* A tombstone suppresses an item across a merge until the item is
|
|
476
|
+
* re-added with a newer timestamp. Pruned after 90 days.
|
|
477
|
+
*/
|
|
478
|
+
tombstones: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
479
|
+
url: z.ZodString;
|
|
480
|
+
removedAt: z.ZodString;
|
|
481
|
+
}, z.core.$loose>>>;
|
|
482
|
+
/**
|
|
483
|
+
* When preferences were last changed (#117). The gist merge previously
|
|
484
|
+
* always took the remote preferences, silently reverting a local edit;
|
|
485
|
+
* the side with the fresher timestamp now wins.
|
|
486
|
+
*/
|
|
487
|
+
preferencesUpdatedAt: z.ZodOptional<z.ZodString>;
|
|
377
488
|
lastSearchAt: z.ZodOptional<z.ZodString>;
|
|
378
489
|
lastRunAt: z.ZodDefault<z.ZodString>;
|
|
379
490
|
gistId: z.ZodOptional<z.ZodString>;
|
|
380
|
-
}, z.core.$
|
|
491
|
+
}, z.core.$loose>;
|
|
492
|
+
/**
|
|
493
|
+
* Single entry point for parsing persisted state (local file, gist, gist
|
|
494
|
+
* cache). Version migrations belong here: when a version 2 ships, transform
|
|
495
|
+
* older raw shapes before validation so no load site ever sees unmigrated
|
|
496
|
+
* data. Unknown keys round-trip via the loose schemas above.
|
|
497
|
+
*/
|
|
498
|
+
export declare function parseScoutState(raw: unknown): ScoutState;
|
|
381
499
|
export type ProjectCategory = z.infer<typeof ProjectCategorySchema>;
|
|
382
500
|
export type IssueScope = z.infer<typeof IssueScopeSchema>;
|
|
383
501
|
export type SearchStrategy = z.infer<typeof SearchStrategySchema>;
|
package/dist/core/schemas.js
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
// ── Enum schemas ────────────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
]);
|
|
9
|
+
// Only "candidate" is ever assigned (TrackedIssue.status is hardcoded in
|
|
10
|
+
// issue-vetting). The "claimed" / "in_progress" / "pr_submitted" values were
|
|
11
|
+
// vestigial from the autopilot extraction and never set; TrackedIssue is an
|
|
12
|
+
// in-memory type, not part of persisted ScoutState, so trimming them carries
|
|
13
|
+
// no migration risk (#155).
|
|
14
|
+
export const IssueStatusSchema = z.enum(["candidate"]);
|
|
15
15
|
export const ProjectCategorySchema = z.enum([
|
|
16
16
|
"nonprofit",
|
|
17
17
|
"devtools",
|
|
@@ -40,12 +40,12 @@ export const CONCRETE_STRATEGIES = [
|
|
|
40
40
|
"maintained",
|
|
41
41
|
];
|
|
42
42
|
// ── Leaf schemas ────────────────────────────────────────────────────
|
|
43
|
-
export const RepoSignalsSchema = z.
|
|
43
|
+
export const RepoSignalsSchema = z.looseObject({
|
|
44
44
|
hasActiveMaintainers: z.boolean(),
|
|
45
45
|
isResponsive: z.boolean(),
|
|
46
46
|
hasHostileComments: z.boolean(),
|
|
47
47
|
});
|
|
48
|
-
export const RepoScoreSchema = z.
|
|
48
|
+
export const RepoScoreSchema = z.looseObject({
|
|
49
49
|
repo: z.string(),
|
|
50
50
|
score: z.number(),
|
|
51
51
|
mergedPRCount: z.number(),
|
|
@@ -57,17 +57,17 @@ export const RepoScoreSchema = z.object({
|
|
|
57
57
|
stargazersCount: z.number().optional(),
|
|
58
58
|
language: z.string().nullable().optional(),
|
|
59
59
|
});
|
|
60
|
-
export const StoredMergedPRSchema = z.
|
|
60
|
+
export const StoredMergedPRSchema = z.looseObject({
|
|
61
61
|
url: z.string(),
|
|
62
62
|
title: z.string(),
|
|
63
63
|
mergedAt: z.string(),
|
|
64
64
|
});
|
|
65
|
-
export const StoredClosedPRSchema = z.
|
|
65
|
+
export const StoredClosedPRSchema = z.looseObject({
|
|
66
66
|
url: z.string(),
|
|
67
67
|
title: z.string(),
|
|
68
68
|
closedAt: z.string(),
|
|
69
69
|
});
|
|
70
|
-
export const StoredOpenPRSchema = z.
|
|
70
|
+
export const StoredOpenPRSchema = z.looseObject({
|
|
71
71
|
url: z.string(),
|
|
72
72
|
title: z.string(),
|
|
73
73
|
openedAt: z.string(),
|
|
@@ -129,7 +129,7 @@ export const TrackedIssueSchema = z.object({
|
|
|
129
129
|
vettingResult: IssueVettingResultSchema.optional(),
|
|
130
130
|
});
|
|
131
131
|
// ── Skipped issue schema ──────────────────────────────────────────
|
|
132
|
-
export const SkippedIssueSchema = z.
|
|
132
|
+
export const SkippedIssueSchema = z.looseObject({
|
|
133
133
|
url: z.string(),
|
|
134
134
|
repo: z.string(),
|
|
135
135
|
number: z.number(),
|
|
@@ -138,7 +138,7 @@ export const SkippedIssueSchema = z.object({
|
|
|
138
138
|
});
|
|
139
139
|
// ── Saved candidate schema ─────────────────────────────────────────
|
|
140
140
|
export const HorizonSchema = z.enum(["quick-win", "bigger-bet"]);
|
|
141
|
-
export const SavedCandidateSchema = z.
|
|
141
|
+
export const SavedCandidateSchema = z.looseObject({
|
|
142
142
|
issueUrl: z.string(),
|
|
143
143
|
repo: z.string(),
|
|
144
144
|
number: z.number(),
|
|
@@ -146,15 +146,26 @@ export const SavedCandidateSchema = z.object({
|
|
|
146
146
|
labels: z.array(z.string()),
|
|
147
147
|
recommendation: z.enum(["approve", "skip", "needs_review"]),
|
|
148
148
|
viabilityScore: z.number(),
|
|
149
|
-
|
|
149
|
+
// Tightened from z.string() to the actual 3-value union (#158). `.catch`
|
|
150
|
+
// keeps old persisted state loadable: any unrecognized stored value (or a
|
|
151
|
+
// future-version value) decodes to "normal" instead of failing the parse.
|
|
152
|
+
searchPriority: z.enum(["merged_pr", "starred", "normal"]).catch("normal"),
|
|
150
153
|
firstSeenAt: z.string(),
|
|
151
154
|
lastSeenAt: z.string(),
|
|
152
155
|
lastScore: z.number(),
|
|
153
156
|
horizon: HorizonSchema.optional(),
|
|
157
|
+
/**
|
|
158
|
+
* Availability status recorded by the previous `vet-list` run, so the next
|
|
159
|
+
* run can report transitions ("was available, now claimed") instead of
|
|
160
|
+
* re-diffing full snapshots (#165). "error" is never stored.
|
|
161
|
+
*/
|
|
162
|
+
lastStatus: z
|
|
163
|
+
.enum(["still_available", "claimed", "closed", "has_pr"])
|
|
164
|
+
.optional(),
|
|
154
165
|
});
|
|
155
166
|
// ── Scout preferences schema ────────────────────────────────────────
|
|
156
167
|
export const PersistenceModeSchema = z.enum(["local", "gist"]);
|
|
157
|
-
export const ScoutPreferencesSchema = z.
|
|
168
|
+
export const ScoutPreferencesSchema = z.looseObject({
|
|
158
169
|
githubUsername: z.string().default(""),
|
|
159
170
|
languages: z.array(z.string()).default(["any"]),
|
|
160
171
|
labels: z.array(z.string()).default(["good first issue", "help wanted"]),
|
|
@@ -170,8 +181,22 @@ export const ScoutPreferencesSchema = z.object({
|
|
|
170
181
|
interPhaseDelayMs: z.number().min(0).max(120000).default(30000),
|
|
171
182
|
persistence: PersistenceModeSchema.default("local"),
|
|
172
183
|
defaultStrategy: z.array(SearchStrategySchema).optional(),
|
|
184
|
+
/**
|
|
185
|
+
* Persisted personalization defaults (#168). The `--prefer-languages`,
|
|
186
|
+
* `--prefer-repos`, and `--diversity-ratio` search flags override these when
|
|
187
|
+
* passed, so a stored preference removes the need to retype the boost every
|
|
188
|
+
* search. Empty / 0 disables the corresponding signal (same as the flags).
|
|
189
|
+
*/
|
|
190
|
+
preferLanguages: z.array(z.string()).default([]),
|
|
191
|
+
preferRepos: z.array(z.string()).default([]),
|
|
192
|
+
diversityRatio: z.number().min(0).max(1).default(0),
|
|
173
193
|
broadPhaseDelayMs: z.number().min(0).max(300000).default(90000),
|
|
174
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Skip the expensive broad phase once this many candidates were found by
|
|
196
|
+
* the cheaper phases. Clamped at runtime to maxResults - 1 so it stays
|
|
197
|
+
* satisfiable; 0 disables skipping entirely.
|
|
198
|
+
*/
|
|
199
|
+
skipBroadWhenSufficientResults: z.number().int().min(0).max(100).default(8),
|
|
175
200
|
/**
|
|
176
201
|
* Optional Ollama model id used for SLM pre-triage during vetting
|
|
177
202
|
* (oss-autopilot#1122). Empty disables the feature. Recommended values:
|
|
@@ -199,7 +224,10 @@ export const ScoutPreferencesSchema = z.object({
|
|
|
199
224
|
featuresSplitRatio: z.number().min(0).max(1).default(0.6),
|
|
200
225
|
});
|
|
201
226
|
// ── Root state schema ───────────────────────────────────────────────
|
|
202
|
-
|
|
227
|
+
// Persisted schemas are loose (unknown keys round-trip) so an older binary
|
|
228
|
+
// loading state written by a newer one cannot silently strip and then
|
|
229
|
+
// persist away the newer fields (#137).
|
|
230
|
+
export const ScoutStateSchema = z.looseObject({
|
|
203
231
|
version: z.literal(1),
|
|
204
232
|
preferences: ScoutPreferencesSchema.default(() => ScoutPreferencesSchema.parse({})),
|
|
205
233
|
repoScores: z.record(z.string(), RepoScoreSchema).default({}),
|
|
@@ -210,7 +238,36 @@ export const ScoutStateSchema = z.object({
|
|
|
210
238
|
openPRs: z.array(StoredOpenPRSchema).default([]),
|
|
211
239
|
savedResults: z.array(SavedCandidateSchema).default([]),
|
|
212
240
|
skippedIssues: z.array(SkippedIssueSchema).default([]),
|
|
241
|
+
/**
|
|
242
|
+
* Deletion tombstones (#117). Without these, the gist union-merge would
|
|
243
|
+
* resurrect any saved result or skipped issue removed on one machine the
|
|
244
|
+
* next time it merged against another machine's copy that still had it.
|
|
245
|
+
* A tombstone suppresses an item across a merge until the item is
|
|
246
|
+
* re-added with a newer timestamp. Pruned after 90 days.
|
|
247
|
+
*/
|
|
248
|
+
tombstones: z
|
|
249
|
+
.array(z.looseObject({
|
|
250
|
+
url: z.string(),
|
|
251
|
+
removedAt: z.string(),
|
|
252
|
+
}))
|
|
253
|
+
.default([]),
|
|
254
|
+
/**
|
|
255
|
+
* When preferences were last changed (#117). The gist merge previously
|
|
256
|
+
* always took the remote preferences, silently reverting a local edit;
|
|
257
|
+
* the side with the fresher timestamp now wins.
|
|
258
|
+
*/
|
|
259
|
+
preferencesUpdatedAt: z.string().optional(),
|
|
213
260
|
lastSearchAt: z.string().optional(),
|
|
214
261
|
lastRunAt: z.string().default(() => new Date().toISOString()),
|
|
215
262
|
gistId: z.string().optional(),
|
|
216
263
|
});
|
|
264
|
+
/**
|
|
265
|
+
* Single entry point for parsing persisted state (local file, gist, gist
|
|
266
|
+
* cache). Version migrations belong here: when a version 2 ships, transform
|
|
267
|
+
* older raw shapes before validation so no load site ever sees unmigrated
|
|
268
|
+
* data. Unknown keys round-trip via the loose schemas above.
|
|
269
|
+
*/
|
|
270
|
+
export function parseScoutState(raw) {
|
|
271
|
+
// No migrations yet; version 1 is current.
|
|
272
|
+
return ScoutStateSchema.parse(raw);
|
|
273
|
+
}
|
|
@@ -20,6 +20,8 @@ export declare class SearchBudgetTracker {
|
|
|
20
20
|
private resetAt;
|
|
21
21
|
/** Total calls recorded since init (for diagnostics). */
|
|
22
22
|
private totalCalls;
|
|
23
|
+
/** Calls made since the last known quota reset (external accounting). */
|
|
24
|
+
private callsSinceReset;
|
|
23
25
|
/**
|
|
24
26
|
* Initialize with pre-flight rate limit data from GitHub.
|
|
25
27
|
*/
|
|
@@ -28,6 +30,13 @@ export declare class SearchBudgetTracker {
|
|
|
28
30
|
* Record that a Search API call was just made.
|
|
29
31
|
*/
|
|
30
32
|
recordCall(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Replenish the external budget once GitHub's quota window has reset.
|
|
35
|
+
* Without this, the pre-flight remaining acted as a never-replenishing
|
|
36
|
+
* run-lifetime total, throttling long multi-phase runs to ~1 call/min
|
|
37
|
+
* even though GitHub fully resets every 60 seconds (#119).
|
|
38
|
+
*/
|
|
39
|
+
private refreshExternalBudget;
|
|
31
40
|
/**
|
|
32
41
|
* Remove timestamps older than the sliding window.
|
|
33
42
|
*/
|
|
@@ -30,6 +30,8 @@ export class SearchBudgetTracker {
|
|
|
30
30
|
resetAt = 0;
|
|
31
31
|
/** Total calls recorded since init (for diagnostics). */
|
|
32
32
|
totalCalls = 0;
|
|
33
|
+
/** Calls made since the last known quota reset (external accounting). */
|
|
34
|
+
callsSinceReset = 0;
|
|
33
35
|
/**
|
|
34
36
|
* Initialize with pre-flight rate limit data from GitHub.
|
|
35
37
|
*/
|
|
@@ -38,6 +40,7 @@ export class SearchBudgetTracker {
|
|
|
38
40
|
this.resetAt = new Date(resetAt).getTime();
|
|
39
41
|
this.callTimestamps = [];
|
|
40
42
|
this.totalCalls = 0;
|
|
43
|
+
this.callsSinceReset = 0;
|
|
41
44
|
debug(MODULE, `Initialized: ${remaining} remaining, resets at ${new Date(this.resetAt).toLocaleTimeString()}`);
|
|
42
45
|
}
|
|
43
46
|
/**
|
|
@@ -46,8 +49,28 @@ export class SearchBudgetTracker {
|
|
|
46
49
|
recordCall() {
|
|
47
50
|
this.callTimestamps.push(Date.now());
|
|
48
51
|
this.totalCalls++;
|
|
52
|
+
this.callsSinceReset++;
|
|
49
53
|
this.pruneOldTimestamps();
|
|
50
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Replenish the external budget once GitHub's quota window has reset.
|
|
57
|
+
* Without this, the pre-flight remaining acted as a never-replenishing
|
|
58
|
+
* run-lifetime total, throttling long multi-phase runs to ~1 call/min
|
|
59
|
+
* even though GitHub fully resets every 60 seconds (#119).
|
|
60
|
+
*/
|
|
61
|
+
refreshExternalBudget() {
|
|
62
|
+
if (this.resetAt <= 0)
|
|
63
|
+
return;
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
if (now < this.resetAt)
|
|
66
|
+
return;
|
|
67
|
+
this.knownRemaining = SEARCH_RATE_LIMIT;
|
|
68
|
+
this.callsSinceReset = 0;
|
|
69
|
+
while (this.resetAt <= now) {
|
|
70
|
+
this.resetAt += SEARCH_WINDOW_MS;
|
|
71
|
+
}
|
|
72
|
+
debug(MODULE, "Quota window reset; external search budget replenished");
|
|
73
|
+
}
|
|
51
74
|
/**
|
|
52
75
|
* Remove timestamps older than the sliding window.
|
|
53
76
|
*/
|
|
@@ -69,9 +92,11 @@ export class SearchBudgetTracker {
|
|
|
69
92
|
* and the pre-flight remaining quota from GitHub.
|
|
70
93
|
*/
|
|
71
94
|
getEffectiveBudget() {
|
|
72
|
-
|
|
95
|
+
this.refreshExternalBudget();
|
|
96
|
+
// Use the stricter of: local window limit vs. known remaining quota
|
|
97
|
+
// minus calls made since the last reset
|
|
73
98
|
const localBudget = EFFECTIVE_BUDGET - this.callTimestamps.length;
|
|
74
|
-
const externalBudget = this.knownRemaining - this.
|
|
99
|
+
const externalBudget = this.knownRemaining - this.callsSinceReset;
|
|
75
100
|
return Math.max(0, Math.min(localBudget, externalBudget));
|
|
76
101
|
}
|
|
77
102
|
/**
|
|
@@ -97,7 +122,15 @@ export class SearchBudgetTracker {
|
|
|
97
122
|
// Wait until the oldest call in the window ages out
|
|
98
123
|
const oldestInWindow = this.callTimestamps[0];
|
|
99
124
|
if (!oldestInWindow) {
|
|
100
|
-
|
|
125
|
+
// No calls in window — the external quota is exhausted. Wait for
|
|
126
|
+
// GitHub's reset when we know it, otherwise proceed (#119).
|
|
127
|
+
const untilReset = this.resetAt - Date.now();
|
|
128
|
+
if (this.resetAt > 0 && untilReset > 0) {
|
|
129
|
+
debug(MODULE, `External quota exhausted, waiting ${untilReset}ms for reset`);
|
|
130
|
+
await sleep(untilReset + 100);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
101
134
|
}
|
|
102
135
|
const waitUntil = oldestInWindow + SEARCH_WINDOW_MS;
|
|
103
136
|
const waitMs = waitUntil - Date.now();
|
|
@@ -94,21 +94,3 @@ export declare function filterVetAndScore(vetter: IssueVetter, items: GitHubSear
|
|
|
94
94
|
allVetFailed: boolean;
|
|
95
95
|
rateLimitHit: boolean;
|
|
96
96
|
}>;
|
|
97
|
-
/**
|
|
98
|
-
* Search for issues within specific repos using batched queries.
|
|
99
|
-
*
|
|
100
|
-
* To avoid GitHub's secondary rate limit (30 requests/minute), we batch
|
|
101
|
-
* multiple repos into a single search query using OR syntax:
|
|
102
|
-
* repo:owner1/repo1 OR repo:owner2/repo2 OR repo:owner3/repo3
|
|
103
|
-
*
|
|
104
|
-
* Labels are chunked separately to stay within GitHub's 5 boolean operator limit.
|
|
105
|
-
* Each batch of repos consumes (batch.length - 1) OR operators, and the remaining
|
|
106
|
-
* budget is used for label OR operators.
|
|
107
|
-
*
|
|
108
|
-
* This reduces API calls from N (one per repo) to ceil(N/BATCH_SIZE) * label_chunks.
|
|
109
|
-
*/
|
|
110
|
-
export declare function searchInRepos(octokit: Octokit, vetter: IssueVetter, repos: string[], baseQualifiers: string, labels: string[], maxResults: number, priority: SearchPriority, filterFn: (items: GitHubSearchItem[]) => GitHubSearchItem[]): Promise<{
|
|
111
|
-
candidates: IssueCandidate[];
|
|
112
|
-
allBatchesFailed: boolean;
|
|
113
|
-
rateLimitHit: boolean;
|
|
114
|
-
}>;
|