@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/core/http-cache.js
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
* for the same endpoint (e.g., star counts for two PRs in the same repo)
|
|
10
10
|
* share a single HTTP round-trip.
|
|
11
11
|
*/
|
|
12
|
-
import * as fs from
|
|
13
|
-
import * as path from
|
|
14
|
-
import * as crypto from
|
|
15
|
-
import { getCacheDir } from
|
|
16
|
-
import { debug, warn } from
|
|
17
|
-
import { errorMessage, getHttpStatusCode } from
|
|
18
|
-
const MODULE =
|
|
12
|
+
import * as fs from "fs";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
import * as crypto from "crypto";
|
|
15
|
+
import { getCacheDir } from "./utils.js";
|
|
16
|
+
import { debug, warn } from "./logger.js";
|
|
17
|
+
import { errorMessage, getHttpStatusCode } from "./errors.js";
|
|
18
|
+
const MODULE = "http-cache";
|
|
19
19
|
/**
|
|
20
20
|
* Maximum age (in ms) before a cache entry is considered stale and eligible for
|
|
21
21
|
* eviction during cleanup. Defaults to 24 hours. Entries older than this are
|
|
@@ -39,7 +39,7 @@ export class HttpCache {
|
|
|
39
39
|
}
|
|
40
40
|
/** Derive a filesystem-safe cache key from a URL. */
|
|
41
41
|
keyFor(url) {
|
|
42
|
-
return crypto.createHash(
|
|
42
|
+
return crypto.createHash("sha256").update(url).digest("hex");
|
|
43
43
|
}
|
|
44
44
|
/** Full path to the cache file for a given URL. */
|
|
45
45
|
pathFor(url) {
|
|
@@ -65,7 +65,7 @@ export class HttpCache {
|
|
|
65
65
|
get(url) {
|
|
66
66
|
const filePath = this.pathFor(url);
|
|
67
67
|
try {
|
|
68
|
-
const raw = fs.readFileSync(filePath,
|
|
68
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
69
69
|
const entry = JSON.parse(raw);
|
|
70
70
|
// Sanity-check: the file should contain the URL we asked for
|
|
71
71
|
if (entry.url !== url) {
|
|
@@ -76,7 +76,7 @@ export class HttpCache {
|
|
|
76
76
|
}
|
|
77
77
|
catch (err) {
|
|
78
78
|
const code = err?.code;
|
|
79
|
-
if (code ===
|
|
79
|
+
if (code === "ENOENT")
|
|
80
80
|
return null;
|
|
81
81
|
if (err instanceof SyntaxError) {
|
|
82
82
|
debug(MODULE, `Corrupt cache entry, deleting: ${url}`);
|
|
@@ -103,7 +103,10 @@ export class HttpCache {
|
|
|
103
103
|
cachedAt: new Date().toISOString(),
|
|
104
104
|
};
|
|
105
105
|
try {
|
|
106
|
-
fs.writeFileSync(this.pathFor(url), JSON.stringify(entry), {
|
|
106
|
+
fs.writeFileSync(this.pathFor(url), JSON.stringify(entry), {
|
|
107
|
+
encoding: "utf-8",
|
|
108
|
+
mode: 0o600,
|
|
109
|
+
});
|
|
107
110
|
debug(MODULE, `Cached response for ${url}`);
|
|
108
111
|
}
|
|
109
112
|
catch (err) {
|
|
@@ -137,11 +140,11 @@ export class HttpCache {
|
|
|
137
140
|
const files = fs.readdirSync(this.cacheDir);
|
|
138
141
|
const now = Date.now();
|
|
139
142
|
for (const file of files) {
|
|
140
|
-
if (!file.endsWith(
|
|
143
|
+
if (!file.endsWith(".json"))
|
|
141
144
|
continue;
|
|
142
145
|
const filePath = path.join(this.cacheDir, file);
|
|
143
146
|
try {
|
|
144
|
-
const raw = fs.readFileSync(filePath,
|
|
147
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
145
148
|
const entry = JSON.parse(raw);
|
|
146
149
|
const age = now - new Date(entry.cachedAt).getTime();
|
|
147
150
|
if (age > maxAgeMs) {
|
|
@@ -163,7 +166,7 @@ export class HttpCache {
|
|
|
163
166
|
}
|
|
164
167
|
catch (err) {
|
|
165
168
|
const code = err?.code;
|
|
166
|
-
if (code !==
|
|
169
|
+
if (code !== "ENOENT") {
|
|
167
170
|
warn(MODULE, `Failed to evict stale cache entries: ${errorMessage(err)}`);
|
|
168
171
|
}
|
|
169
172
|
}
|
|
@@ -179,15 +182,15 @@ export class HttpCache {
|
|
|
179
182
|
try {
|
|
180
183
|
const files = fs.readdirSync(this.cacheDir);
|
|
181
184
|
for (const file of files) {
|
|
182
|
-
if (!file.endsWith(
|
|
185
|
+
if (!file.endsWith(".json"))
|
|
183
186
|
continue;
|
|
184
187
|
fs.unlinkSync(path.join(this.cacheDir, file));
|
|
185
188
|
}
|
|
186
|
-
debug(MODULE,
|
|
189
|
+
debug(MODULE, "Cache cleared");
|
|
187
190
|
}
|
|
188
191
|
catch (err) {
|
|
189
192
|
const code = err?.code;
|
|
190
|
-
if (code !==
|
|
193
|
+
if (code !== "ENOENT") {
|
|
191
194
|
warn(MODULE, `Failed to clear cache: ${errorMessage(err)}`);
|
|
192
195
|
}
|
|
193
196
|
}
|
|
@@ -197,11 +200,12 @@ export class HttpCache {
|
|
|
197
200
|
*/
|
|
198
201
|
size() {
|
|
199
202
|
try {
|
|
200
|
-
return fs.readdirSync(this.cacheDir).filter((f) => f.endsWith(
|
|
203
|
+
return fs.readdirSync(this.cacheDir).filter((f) => f.endsWith(".json"))
|
|
204
|
+
.length;
|
|
201
205
|
}
|
|
202
206
|
catch (err) {
|
|
203
207
|
const code = err?.code;
|
|
204
|
-
if (code !==
|
|
208
|
+
if (code !== "ENOENT") {
|
|
205
209
|
debug(MODULE, `Failed to read cache size: ${errorMessage(err)}`);
|
|
206
210
|
}
|
|
207
211
|
return 0;
|
|
@@ -253,12 +257,12 @@ export async function cachedRequest(cache, url, fetcher) {
|
|
|
253
257
|
const extraHeaders = {};
|
|
254
258
|
const cached = cache.get(url);
|
|
255
259
|
if (cached) {
|
|
256
|
-
extraHeaders[
|
|
260
|
+
extraHeaders["if-none-match"] = cached.etag;
|
|
257
261
|
}
|
|
258
262
|
try {
|
|
259
263
|
const response = await fetcher(extraHeaders);
|
|
260
264
|
// Store ETag if present (headers may be absent in test mocks)
|
|
261
|
-
const etag = response.headers?.[
|
|
265
|
+
const etag = response.headers?.["etag"];
|
|
262
266
|
if (etag) {
|
|
263
267
|
cache.set(url, etag, response.data);
|
|
264
268
|
}
|
|
@@ -302,7 +306,7 @@ export async function cachedTimeBased(cache, key, maxAgeMs, fetcher) {
|
|
|
302
306
|
return cached;
|
|
303
307
|
}
|
|
304
308
|
const result = await fetcher();
|
|
305
|
-
cache.set(key,
|
|
309
|
+
cache.set(key, "", result);
|
|
306
310
|
return result;
|
|
307
311
|
}
|
|
308
312
|
/**
|
|
@@ -11,15 +11,14 @@
|
|
|
11
11
|
*
|
|
12
12
|
* All state is injected via constructor parameters (ScoutStateReader + ScoutPreferences).
|
|
13
13
|
*/
|
|
14
|
-
import { type IssueCandidate } from
|
|
15
|
-
import type { ScoutPreferences, SearchStrategy } from
|
|
16
|
-
import { type ScoutStateReader } from
|
|
14
|
+
import { type IssueCandidate } from "./types.js";
|
|
15
|
+
import type { ScoutPreferences, SearchStrategy } from "./schemas.js";
|
|
16
|
+
import { type ScoutStateReader } from "./issue-vetting.js";
|
|
17
17
|
/**
|
|
18
18
|
* Multi-phase issue discovery engine that searches GitHub for contributable issues.
|
|
19
19
|
*
|
|
20
20
|
* Search phases (in priority order):
|
|
21
21
|
* 0. Repos where user has merged PRs (highest merge probability)
|
|
22
|
-
* 0.5. Preferred organizations
|
|
23
22
|
* 1. Starred repos
|
|
24
23
|
* 2. General label-filtered search
|
|
25
24
|
* 3. Actively maintained repos
|
|
@@ -47,8 +46,8 @@ export declare class IssueDiscovery {
|
|
|
47
46
|
getStarredRepos(): string[];
|
|
48
47
|
/**
|
|
49
48
|
* Search for issues matching our criteria.
|
|
50
|
-
* Searches in priority order: merged-PR repos first (no label filter), then
|
|
51
|
-
*
|
|
49
|
+
* Searches in priority order: merged-PR repos first (no label filter), then starred
|
|
50
|
+
* repos, then general search, then actively maintained repos.
|
|
52
51
|
* Filters out issues from low-scoring and excluded repos.
|
|
53
52
|
*
|
|
54
53
|
* @param options - Search configuration
|
|
@@ -74,6 +73,7 @@ export declare class IssueDiscovery {
|
|
|
74
73
|
labels?: string[];
|
|
75
74
|
maxResults?: number;
|
|
76
75
|
strategies?: SearchStrategy[];
|
|
76
|
+
skippedUrls?: Set<string>;
|
|
77
77
|
}): Promise<{
|
|
78
78
|
candidates: IssueCandidate[];
|
|
79
79
|
strategiesUsed: SearchStrategy[];
|