@oss-scout/core 0.4.0 → 0.5.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 +44 -44
- package/dist/core/bootstrap.d.ts +1 -0
- package/dist/core/bootstrap.js +39 -1
- package/dist/core/gist-state-store.d.ts +1 -1
- package/dist/core/gist-state-store.js +2 -1
- package/dist/core/issue-discovery.js +14 -2
- package/dist/core/issue-vetting.d.ts +2 -0
- package/dist/core/schemas.d.ts +11 -0
- package/dist/core/schemas.js +6 -0
- package/dist/core/types.d.ts +7 -0
- package/dist/index.d.ts +2 -2
- package/dist/scout.d.ts +7 -1
- package/dist/scout.js +26 -0
- package/package.json +4 -3
package/dist/core/bootstrap.d.ts
CHANGED
package/dist/core/bootstrap.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { getOctokit, checkRateLimit } from "./github.js";
|
|
6
6
|
import { debug, warn } from "./logger.js";
|
|
7
|
-
import { ConfigurationError, errorMessage } from "./errors.js";
|
|
7
|
+
import { ConfigurationError, errorMessage, getHttpStatusCode, isRateLimitError, } from "./errors.js";
|
|
8
8
|
import { extractRepoFromUrl } from "./utils.js";
|
|
9
9
|
const MODULE = "bootstrap";
|
|
10
10
|
const STARRED_MAX_PAGES = 5;
|
|
@@ -23,6 +23,7 @@ export async function bootstrapScout(scout, token) {
|
|
|
23
23
|
starredRepoCount: 0,
|
|
24
24
|
mergedPRCount: 0,
|
|
25
25
|
closedPRCount: 0,
|
|
26
|
+
openPRCount: 0,
|
|
26
27
|
reposScoredCount: 0,
|
|
27
28
|
skippedDueToRateLimit: true,
|
|
28
29
|
errors: [],
|
|
@@ -80,6 +81,8 @@ export async function bootstrapScout(scout, token) {
|
|
|
80
81
|
debug(MODULE, `Imported ${mergedPRCount} merged PRs`);
|
|
81
82
|
}
|
|
82
83
|
catch (err) {
|
|
84
|
+
if (getHttpStatusCode(err) === 401 || isRateLimitError(err))
|
|
85
|
+
throw err;
|
|
83
86
|
warn(MODULE, `Failed to fetch merged PRs: ${errorMessage(err)}`);
|
|
84
87
|
errors.push("merged PR fetch failed");
|
|
85
88
|
}
|
|
@@ -110,15 +113,50 @@ export async function bootstrapScout(scout, token) {
|
|
|
110
113
|
debug(MODULE, `Imported ${closedPRCount} closed PRs`);
|
|
111
114
|
}
|
|
112
115
|
catch (err) {
|
|
116
|
+
if (getHttpStatusCode(err) === 401 || isRateLimitError(err))
|
|
117
|
+
throw err;
|
|
113
118
|
warn(MODULE, `Failed to fetch closed PRs: ${errorMessage(err)}`);
|
|
114
119
|
errors.push("closed PR fetch failed");
|
|
115
120
|
}
|
|
121
|
+
// 4. Fetch currently-open PRs via Search API
|
|
122
|
+
let openPRCount = 0;
|
|
123
|
+
try {
|
|
124
|
+
for (let page = 1; page <= SEARCH_MAX_PAGES; page++) {
|
|
125
|
+
const { data } = await octokit.search.issuesAndPullRequests({
|
|
126
|
+
q: `is:pr is:open author:${username}`,
|
|
127
|
+
per_page: PER_PAGE,
|
|
128
|
+
page,
|
|
129
|
+
});
|
|
130
|
+
for (const item of data.items) {
|
|
131
|
+
const repo = extractRepoFromUrl(item.html_url);
|
|
132
|
+
if (!repo)
|
|
133
|
+
continue;
|
|
134
|
+
scout.recordOpenPR({
|
|
135
|
+
url: item.html_url,
|
|
136
|
+
title: item.title,
|
|
137
|
+
openedAt: item.created_at ?? new Date().toISOString(),
|
|
138
|
+
repo,
|
|
139
|
+
});
|
|
140
|
+
openPRCount++;
|
|
141
|
+
}
|
|
142
|
+
if (data.items.length < PER_PAGE)
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
debug(MODULE, `Imported ${openPRCount} open PRs`);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
if (getHttpStatusCode(err) === 401 || isRateLimitError(err))
|
|
149
|
+
throw err;
|
|
150
|
+
warn(MODULE, `Failed to fetch open PRs: ${errorMessage(err)}`);
|
|
151
|
+
errors.push("open PR fetch failed");
|
|
152
|
+
}
|
|
116
153
|
const state = scout.getState();
|
|
117
154
|
const reposScoredCount = Object.keys(state.repoScores).length;
|
|
118
155
|
return {
|
|
119
156
|
starredRepoCount: starredRepos.length,
|
|
120
157
|
mergedPRCount,
|
|
121
158
|
closedPRCount,
|
|
159
|
+
openPRCount,
|
|
122
160
|
reposScoredCount,
|
|
123
161
|
skippedDueToRateLimit: false,
|
|
124
162
|
errors,
|
|
@@ -88,7 +88,7 @@ export declare class GistStateStore {
|
|
|
88
88
|
/**
|
|
89
89
|
* Merge two ScoutState objects with conflict resolution:
|
|
90
90
|
* - repoScores: per-repo, keep the one with more total PR activity
|
|
91
|
-
* - mergedPRs/closedPRs: union by URL
|
|
91
|
+
* - mergedPRs/closedPRs/openPRs: union by URL
|
|
92
92
|
* - preferences: remote wins
|
|
93
93
|
* - starredRepos: keep the list with the fresher timestamp
|
|
94
94
|
* - savedResults: union by issueUrl, keep newer lastSeenAt
|
|
@@ -238,7 +238,7 @@ export class GistStateStore {
|
|
|
238
238
|
/**
|
|
239
239
|
* Merge two ScoutState objects with conflict resolution:
|
|
240
240
|
* - repoScores: per-repo, keep the one with more total PR activity
|
|
241
|
-
* - mergedPRs/closedPRs: union by URL
|
|
241
|
+
* - mergedPRs/closedPRs/openPRs: union by URL
|
|
242
242
|
* - preferences: remote wins
|
|
243
243
|
* - starredRepos: keep the list with the fresher timestamp
|
|
244
244
|
* - savedResults: union by issueUrl, keep newer lastSeenAt
|
|
@@ -252,6 +252,7 @@ export function mergeStates(local, remote) {
|
|
|
252
252
|
starredReposLastFetched: pickFresherTimestamp(local.starredReposLastFetched, remote.starredReposLastFetched),
|
|
253
253
|
mergedPRs: unionByUrl(local.mergedPRs, remote.mergedPRs),
|
|
254
254
|
closedPRs: unionByUrl(local.closedPRs, remote.closedPRs),
|
|
255
|
+
openPRs: unionByUrl(local.openPRs ?? [], remote.openPRs ?? []),
|
|
255
256
|
savedResults: mergeSavedResults(local.savedResults ?? [], remote.savedResults ?? []),
|
|
256
257
|
skippedIssues: mergeSkippedIssues(local.skippedIssues ?? [], remote.skippedIssues ?? []),
|
|
257
258
|
lastSearchAt: pickFresherTimestamp(local.lastSearchAt, remote.lastSearchAt),
|
|
@@ -326,6 +326,7 @@ export class IssueDiscovery {
|
|
|
326
326
|
}
|
|
327
327
|
// Derive search context
|
|
328
328
|
const mergedPRRepos = this.stateReader.getReposWithMergedPRs();
|
|
329
|
+
const openPRRepos = this.stateReader.getReposWithOpenPRs();
|
|
329
330
|
const starredRepos = this.getStarredRepos();
|
|
330
331
|
const starredRepoSet = new Set(starredRepos);
|
|
331
332
|
const lowScoringRepos = new Set(this.deriveLowScoringRepos(config.minRepoScoreThreshold));
|
|
@@ -352,8 +353,19 @@ export class IssueDiscovery {
|
|
|
352
353
|
now: new Date(),
|
|
353
354
|
includeDocIssues: config.includeDocIssues ?? true,
|
|
354
355
|
});
|
|
355
|
-
// Phase 0:
|
|
356
|
-
|
|
356
|
+
// Phase 0: Repos the user has engaged with — merged PRs first (strongest
|
|
357
|
+
// signal), then open PRs (active engagement even without a merge yet).
|
|
358
|
+
// Deduped and capped so REST cost stays bounded.
|
|
359
|
+
const seenPhase0 = new Set();
|
|
360
|
+
const phase0Repos = [];
|
|
361
|
+
for (const repo of [...mergedPRRepos, ...openPRRepos]) {
|
|
362
|
+
if (seenPhase0.has(repo))
|
|
363
|
+
continue;
|
|
364
|
+
seenPhase0.add(repo);
|
|
365
|
+
phase0Repos.push(repo);
|
|
366
|
+
if (phase0Repos.length >= 10)
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
357
369
|
const phase0RepoSet = new Set(phase0Repos);
|
|
358
370
|
if (phase0Repos.length > 0 && enabledStrategies.has("merged")) {
|
|
359
371
|
const remaining = maxResults - allCandidates.length;
|
|
@@ -15,6 +15,8 @@ import { type SearchPriority, type IssueCandidate, type ProjectCategory } from "
|
|
|
15
15
|
export interface ScoutStateReader {
|
|
16
16
|
/** Repos where the user has at least one merged PR. */
|
|
17
17
|
getReposWithMergedPRs(): string[];
|
|
18
|
+
/** Repos where the user has at least one open PR. */
|
|
19
|
+
getReposWithOpenPRs(): string[];
|
|
18
20
|
/** User's starred repos (from GitHub). */
|
|
19
21
|
getStarredRepos(): string[];
|
|
20
22
|
/** Preferred project categories from user preferences. */
|
package/dist/core/schemas.d.ts
CHANGED
|
@@ -64,6 +64,11 @@ export declare const StoredClosedPRSchema: z.ZodObject<{
|
|
|
64
64
|
title: z.ZodString;
|
|
65
65
|
closedAt: z.ZodString;
|
|
66
66
|
}, z.core.$strip>;
|
|
67
|
+
export declare const StoredOpenPRSchema: z.ZodObject<{
|
|
68
|
+
url: z.ZodString;
|
|
69
|
+
title: z.ZodString;
|
|
70
|
+
openedAt: z.ZodString;
|
|
71
|
+
}, z.core.$strip>;
|
|
67
72
|
export declare const ContributionGuidelinesSchema: z.ZodObject<{
|
|
68
73
|
branchNamingConvention: z.ZodOptional<z.ZodString>;
|
|
69
74
|
commitMessageFormat: z.ZodOptional<z.ZodString>;
|
|
@@ -287,6 +292,11 @@ export declare const ScoutStateSchema: z.ZodObject<{
|
|
|
287
292
|
title: z.ZodString;
|
|
288
293
|
closedAt: z.ZodString;
|
|
289
294
|
}, z.core.$strip>>>;
|
|
295
|
+
openPRs: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
296
|
+
url: z.ZodString;
|
|
297
|
+
title: z.ZodString;
|
|
298
|
+
openedAt: z.ZodString;
|
|
299
|
+
}, z.core.$strip>>>;
|
|
290
300
|
savedResults: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
291
301
|
issueUrl: z.ZodString;
|
|
292
302
|
repo: z.ZodString;
|
|
@@ -322,6 +332,7 @@ export type RepoSignals = z.infer<typeof RepoSignalsSchema>;
|
|
|
322
332
|
export type RepoScore = z.infer<typeof RepoScoreSchema>;
|
|
323
333
|
export type StoredMergedPR = z.infer<typeof StoredMergedPRSchema>;
|
|
324
334
|
export type StoredClosedPR = z.infer<typeof StoredClosedPRSchema>;
|
|
335
|
+
export type StoredOpenPR = z.infer<typeof StoredOpenPRSchema>;
|
|
325
336
|
export type ContributionGuidelines = z.infer<typeof ContributionGuidelinesSchema>;
|
|
326
337
|
export type IssueVettingResult = z.infer<typeof IssueVettingResultSchema>;
|
|
327
338
|
export type TrackedIssue = z.infer<typeof TrackedIssueSchema>;
|
package/dist/core/schemas.js
CHANGED
|
@@ -67,6 +67,11 @@ export const StoredClosedPRSchema = z.object({
|
|
|
67
67
|
title: z.string(),
|
|
68
68
|
closedAt: z.string(),
|
|
69
69
|
});
|
|
70
|
+
export const StoredOpenPRSchema = z.object({
|
|
71
|
+
url: z.string(),
|
|
72
|
+
title: z.string(),
|
|
73
|
+
openedAt: z.string(),
|
|
74
|
+
});
|
|
70
75
|
// ── Contribution schemas ────────────────────────────────────────────
|
|
71
76
|
export const ContributionGuidelinesSchema = z.object({
|
|
72
77
|
branchNamingConvention: z.string().optional(),
|
|
@@ -161,6 +166,7 @@ export const ScoutStateSchema = z.object({
|
|
|
161
166
|
starredReposLastFetched: z.string().optional(),
|
|
162
167
|
mergedPRs: z.array(StoredMergedPRSchema).default([]),
|
|
163
168
|
closedPRs: z.array(StoredClosedPRSchema).default([]),
|
|
169
|
+
openPRs: z.array(StoredOpenPRSchema).default([]),
|
|
164
170
|
savedResults: z.array(SavedCandidateSchema).default([]),
|
|
165
171
|
skippedIssues: z.array(SkippedIssueSchema).default([]),
|
|
166
172
|
lastSearchAt: z.string().optional(),
|
package/dist/core/types.d.ts
CHANGED
|
@@ -122,3 +122,10 @@ export interface ClosedPRRecord {
|
|
|
122
122
|
closedAt: string;
|
|
123
123
|
repo: string;
|
|
124
124
|
}
|
|
125
|
+
/** Record of an open PR for state contribution. */
|
|
126
|
+
export interface OpenPRRecord {
|
|
127
|
+
url: string;
|
|
128
|
+
title: string;
|
|
129
|
+
openedAt: string;
|
|
130
|
+
repo: string;
|
|
131
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* @packageDocumentation
|
|
16
16
|
*/
|
|
17
17
|
export { createScout, OssScout } from "./scout.js";
|
|
18
|
-
export type { ScoutConfig, SearchOptions, SearchResult, IssueCandidate, MergedPRRecord, ClosedPRRecord, RepoScoreUpdate, ProjectHealth, SearchPriority, CheckResult, VetListOptions, VetListResult, VetListEntry, VetListSummary, } from "./core/types.js";
|
|
19
|
-
export type { ScoutState, ScoutPreferences, RepoScore, RepoSignals, IssueVettingResult, ContributionGuidelines, TrackedIssue, IssueScope, ProjectCategory, StoredMergedPR, StoredClosedPR, SearchStrategy, SkippedIssue, } from "./core/schemas.js";
|
|
18
|
+
export type { ScoutConfig, SearchOptions, SearchResult, IssueCandidate, MergedPRRecord, ClosedPRRecord, OpenPRRecord, RepoScoreUpdate, ProjectHealth, SearchPriority, CheckResult, VetListOptions, VetListResult, VetListEntry, VetListSummary, } from "./core/types.js";
|
|
19
|
+
export type { ScoutState, ScoutPreferences, RepoScore, RepoSignals, IssueVettingResult, ContributionGuidelines, TrackedIssue, IssueScope, ProjectCategory, StoredMergedPR, StoredClosedPR, StoredOpenPR, SearchStrategy, SkippedIssue, } from "./core/schemas.js";
|
|
20
20
|
export { ScoutStateSchema, ScoutPreferencesSchema, RepoScoreSchema, IssueScopeSchema, ProjectCategorySchema, SearchStrategySchema, SkippedIssueSchema, } from "./core/schemas.js";
|
|
21
21
|
export { requireGitHubToken, getGitHubToken } from "./core/utils.js";
|
|
22
22
|
export { IssueDiscovery } from "./core/issue-discovery.js";
|
package/dist/scout.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { ScoutStateReader } from "./core/issue-vetting.js";
|
|
8
8
|
import type { ScoutState, ScoutPreferences, RepoScore, SavedCandidate, SkippedIssue } from "./core/schemas.js";
|
|
9
|
-
import type { ScoutConfig, SearchOptions, SearchResult, IssueCandidate, MergedPRRecord, ClosedPRRecord, RepoScoreUpdate, ProjectCategory, VetListOptions, VetListResult } from "./core/types.js";
|
|
9
|
+
import type { ScoutConfig, SearchOptions, SearchResult, IssueCandidate, MergedPRRecord, ClosedPRRecord, OpenPRRecord, RepoScoreUpdate, ProjectCategory, VetListOptions, VetListResult } from "./core/types.js";
|
|
10
10
|
import { GistStateStore } from "./core/gist-state-store.js";
|
|
11
11
|
/**
|
|
12
12
|
* Create an OssScout instance.
|
|
@@ -59,6 +59,7 @@ export declare class OssScout implements ScoutStateReader {
|
|
|
59
59
|
vetList(options?: VetListOptions): Promise<VetListResult>;
|
|
60
60
|
private classifyVetResult;
|
|
61
61
|
getReposWithMergedPRs(): string[];
|
|
62
|
+
getReposWithOpenPRs(): string[];
|
|
62
63
|
getStarredRepos(): string[];
|
|
63
64
|
getProjectCategories(): ProjectCategory[];
|
|
64
65
|
getRepoScore(repo: string): number | null;
|
|
@@ -75,6 +76,11 @@ export declare class OssScout implements ScoutStateReader {
|
|
|
75
76
|
* Record that a PR was closed without merge.
|
|
76
77
|
*/
|
|
77
78
|
recordClosedPR(pr: ClosedPRRecord): void;
|
|
79
|
+
/**
|
|
80
|
+
* Record that a PR is currently open in this repo.
|
|
81
|
+
* Open PRs signal active engagement even when nothing is merged yet.
|
|
82
|
+
*/
|
|
83
|
+
recordOpenPR(pr: OpenPRRecord): void;
|
|
78
84
|
/**
|
|
79
85
|
* Update repo score with observed signals.
|
|
80
86
|
*/
|
package/dist/scout.js
CHANGED
|
@@ -236,6 +236,18 @@ export class OssScout {
|
|
|
236
236
|
.sort((a, b) => b[1] - a[1])
|
|
237
237
|
.map(([repo]) => repo);
|
|
238
238
|
}
|
|
239
|
+
getReposWithOpenPRs() {
|
|
240
|
+
const repoCounts = new Map();
|
|
241
|
+
for (const pr of this.state.openPRs ?? []) {
|
|
242
|
+
const repo = extractRepoFromUrl(pr.url);
|
|
243
|
+
if (repo) {
|
|
244
|
+
repoCounts.set(repo, (repoCounts.get(repo) ?? 0) + 1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return [...repoCounts.entries()]
|
|
248
|
+
.sort((a, b) => b[1] - a[1])
|
|
249
|
+
.map(([repo]) => repo);
|
|
250
|
+
}
|
|
239
251
|
getStarredRepos() {
|
|
240
252
|
return this.state.starredRepos;
|
|
241
253
|
}
|
|
@@ -285,6 +297,20 @@ export class OssScout {
|
|
|
285
297
|
this.updateRepoScoreFromPRs(pr.repo);
|
|
286
298
|
this.dirty = true;
|
|
287
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Record that a PR is currently open in this repo.
|
|
302
|
+
* Open PRs signal active engagement even when nothing is merged yet.
|
|
303
|
+
*/
|
|
304
|
+
recordOpenPR(pr) {
|
|
305
|
+
const existing = this.state.openPRs ?? [];
|
|
306
|
+
if (existing.some((p) => p.url === pr.url))
|
|
307
|
+
return;
|
|
308
|
+
this.state.openPRs = [
|
|
309
|
+
...existing,
|
|
310
|
+
{ url: pr.url, title: pr.title, openedAt: pr.openedAt },
|
|
311
|
+
];
|
|
312
|
+
this.dirty = true;
|
|
313
|
+
}
|
|
288
314
|
/**
|
|
289
315
|
* Update repo score with observed signals.
|
|
290
316
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oss-scout/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Personalized GitHub issue finder with multi-strategy search, deep vetting, and viability scoring — CLI, library, MCP server, and Claude Code plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -55,11 +55,12 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/node": "^25.5.0",
|
|
58
|
-
"@vitest/coverage-v8": "^4.1.
|
|
58
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
59
59
|
"esbuild": "^0.27.4",
|
|
60
60
|
"tsx": "^4.21.0",
|
|
61
61
|
"typescript": "^5.9.3",
|
|
62
|
-
"
|
|
62
|
+
"vite": "^8.0.5",
|
|
63
|
+
"vitest": "^4.1.4"
|
|
63
64
|
},
|
|
64
65
|
"scripts": {
|
|
65
66
|
"build": "tsc",
|