@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.
Files changed (56) hide show
  1. package/dist/cli.bundle.cjs +51 -47
  2. package/dist/cli.js +218 -87
  3. package/dist/commands/config.d.ts +2 -4
  4. package/dist/commands/config.js +76 -78
  5. package/dist/commands/results.d.ts +1 -1
  6. package/dist/commands/results.js +1 -1
  7. package/dist/commands/search.d.ts +2 -2
  8. package/dist/commands/search.js +16 -6
  9. package/dist/commands/setup.d.ts +1 -1
  10. package/dist/commands/setup.js +25 -25
  11. package/dist/commands/skip.d.ts +33 -0
  12. package/dist/commands/skip.js +89 -0
  13. package/dist/commands/validation.d.ts +1 -1
  14. package/dist/commands/validation.js +1 -1
  15. package/dist/commands/vet-list.d.ts +2 -2
  16. package/dist/commands/vet-list.js +12 -5
  17. package/dist/commands/vet.d.ts +3 -3
  18. package/dist/commands/vet.js +9 -5
  19. package/dist/core/bootstrap.d.ts +1 -1
  20. package/dist/core/bootstrap.js +20 -16
  21. package/dist/core/category-mapping.d.ts +1 -1
  22. package/dist/core/category-mapping.js +104 -13
  23. package/dist/core/errors.d.ts +8 -1
  24. package/dist/core/errors.js +31 -19
  25. package/dist/core/gist-state-store.d.ts +1 -1
  26. package/dist/core/gist-state-store.js +55 -28
  27. package/dist/core/github.d.ts +1 -1
  28. package/dist/core/github.js +5 -5
  29. package/dist/core/http-cache.js +26 -22
  30. package/dist/core/issue-discovery.d.ts +6 -6
  31. package/dist/core/issue-discovery.js +279 -286
  32. package/dist/core/issue-eligibility.d.ts +2 -2
  33. package/dist/core/issue-eligibility.js +26 -21
  34. package/dist/core/issue-filtering.js +23 -15
  35. package/dist/core/issue-scoring.js +1 -1
  36. package/dist/core/issue-vetting.d.ts +2 -4
  37. package/dist/core/issue-vetting.js +65 -56
  38. package/dist/core/local-state.d.ts +1 -1
  39. package/dist/core/local-state.js +16 -14
  40. package/dist/core/repo-health.d.ts +2 -2
  41. package/dist/core/repo-health.js +46 -35
  42. package/dist/core/schemas.d.ts +17 -9
  43. package/dist/core/schemas.js +47 -19
  44. package/dist/core/search-budget.js +3 -3
  45. package/dist/core/search-phases.d.ts +6 -6
  46. package/dist/core/search-phases.js +23 -19
  47. package/dist/core/types.d.ts +9 -9
  48. package/dist/core/types.js +15 -3
  49. package/dist/core/utils.d.ts +10 -1
  50. package/dist/core/utils.js +44 -25
  51. package/dist/formatters/json.d.ts +1 -1
  52. package/dist/index.d.ts +7 -7
  53. package/dist/index.js +5 -5
  54. package/dist/scout.d.ts +30 -6
  55. package/dist/scout.js +141 -34
  56. package/package.json +7 -3
@@ -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 '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';
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('sha256').update(url).digest('hex');
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, 'utf-8');
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 === 'ENOENT')
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), { encoding: 'utf-8', mode: 0o600 });
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('.json'))
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, 'utf-8');
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 !== 'ENOENT') {
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('.json'))
185
+ if (!file.endsWith(".json"))
183
186
  continue;
184
187
  fs.unlinkSync(path.join(this.cacheDir, file));
185
188
  }
186
- debug(MODULE, 'Cache cleared');
189
+ debug(MODULE, "Cache cleared");
187
190
  }
188
191
  catch (err) {
189
192
  const code = err?.code;
190
- if (code !== 'ENOENT') {
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('.json')).length;
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 !== 'ENOENT') {
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['if-none-match'] = cached.etag;
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?.['etag'];
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, '', result);
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 './types.js';
15
- import type { ScoutPreferences, SearchStrategy } from './schemas.js';
16
- import { type ScoutStateReader } from './issue-vetting.js';
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 preferred
51
- * organizations, then starred repos, then general search, then actively maintained repos.
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[];