@oss-autopilot/core 0.57.0 → 0.59.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 (51) hide show
  1. package/dist/cli-registry.js +54 -0
  2. package/dist/cli.bundle.cjs +49 -45
  3. package/dist/commands/comments.d.ts +28 -0
  4. package/dist/commands/comments.js +28 -0
  5. package/dist/commands/config.d.ts +11 -0
  6. package/dist/commands/config.js +11 -0
  7. package/dist/commands/daily.d.ts +26 -2
  8. package/dist/commands/daily.js +26 -2
  9. package/dist/commands/detect-formatters.d.ts +11 -0
  10. package/dist/commands/detect-formatters.js +24 -0
  11. package/dist/commands/dismiss.d.ts +17 -0
  12. package/dist/commands/dismiss.js +17 -0
  13. package/dist/commands/index.d.ts +3 -1
  14. package/dist/commands/index.js +2 -0
  15. package/dist/commands/init.d.ts +8 -0
  16. package/dist/commands/init.js +8 -0
  17. package/dist/commands/move.d.ts +10 -0
  18. package/dist/commands/move.js +10 -0
  19. package/dist/commands/search.d.ts +18 -0
  20. package/dist/commands/search.js +18 -0
  21. package/dist/commands/setup.d.ts +17 -0
  22. package/dist/commands/setup.js +17 -0
  23. package/dist/commands/shelve.d.ts +16 -0
  24. package/dist/commands/shelve.js +16 -0
  25. package/dist/commands/startup.d.ts +16 -7
  26. package/dist/commands/startup.js +16 -7
  27. package/dist/commands/status.d.ts +8 -0
  28. package/dist/commands/status.js +8 -0
  29. package/dist/commands/track.d.ts +16 -0
  30. package/dist/commands/track.js +16 -0
  31. package/dist/commands/vet.d.ts +8 -0
  32. package/dist/commands/vet.js +8 -0
  33. package/dist/core/daily-logic.d.ts +60 -7
  34. package/dist/core/daily-logic.js +52 -7
  35. package/dist/core/formatter-detection.d.ts +61 -0
  36. package/dist/core/formatter-detection.js +360 -0
  37. package/dist/core/github.d.ts +25 -2
  38. package/dist/core/github.js +25 -2
  39. package/dist/core/index.d.ts +1 -0
  40. package/dist/core/index.js +1 -0
  41. package/dist/core/issue-discovery.d.ts +46 -6
  42. package/dist/core/issue-discovery.js +46 -6
  43. package/dist/core/logger.d.ts +13 -0
  44. package/dist/core/logger.js +13 -0
  45. package/dist/core/pr-monitor.d.ts +43 -8
  46. package/dist/core/pr-monitor.js +43 -8
  47. package/dist/core/state.d.ts +167 -0
  48. package/dist/core/state.js +167 -0
  49. package/dist/core/types.d.ts +2 -8
  50. package/dist/formatters/json.d.ts +5 -0
  51. package/package.json +6 -3
@@ -43,6 +43,7 @@ export class StateManager {
43
43
  /**
44
44
  * Execute multiple mutations as a single batch, deferring disk I/O until the
45
45
  * batch completes. Nested `batch()` calls are flattened — only the outermost saves.
46
+ * @param fn - The function containing mutations to batch
46
47
  */
47
48
  batch(fn) {
48
49
  if (this._batching) {
@@ -132,35 +133,64 @@ export class StateManager {
132
133
  return true;
133
134
  }
134
135
  // === Dashboard Data Setters ===
136
+ /**
137
+ * Store the latest daily digest and update the digest timestamp.
138
+ * @param digest - The daily digest to store
139
+ */
135
140
  setLastDigest(digest) {
136
141
  this.state.lastDigest = digest;
137
142
  this.state.lastDigestAt = digest.generatedAt;
138
143
  this.autoSave();
139
144
  }
145
+ /**
146
+ * Update monthly merged PR counts for dashboard display.
147
+ * @param counts - Monthly merged PR counts keyed by YYYY-MM
148
+ */
140
149
  setMonthlyMergedCounts(counts) {
141
150
  this.state.monthlyMergedCounts = counts;
142
151
  this.autoSave();
143
152
  }
153
+ /**
154
+ * Update monthly closed PR counts for dashboard display.
155
+ * @param counts - Monthly closed PR counts keyed by YYYY-MM
156
+ */
144
157
  setMonthlyClosedCounts(counts) {
145
158
  this.state.monthlyClosedCounts = counts;
146
159
  this.autoSave();
147
160
  }
161
+ /**
162
+ * Update monthly opened PR counts for dashboard display.
163
+ * @param counts - Monthly opened PR counts keyed by YYYY-MM
164
+ */
148
165
  setMonthlyOpenedCounts(counts) {
149
166
  this.state.monthlyOpenedCounts = counts;
150
167
  this.autoSave();
151
168
  }
169
+ /**
170
+ * Update daily activity counts for dashboard display.
171
+ * @param counts - Daily activity counts keyed by YYYY-MM-DD
172
+ */
152
173
  setDailyActivityCounts(counts) {
153
174
  this.state.dailyActivityCounts = counts;
154
175
  this.autoSave();
155
176
  }
177
+ /**
178
+ * Update the local repository cache.
179
+ * @param cache - Local repository cache mapping repo names to paths
180
+ */
156
181
  setLocalRepoCache(cache) {
157
182
  this.state.localRepoCache = cache;
158
183
  this.autoSave();
159
184
  }
160
185
  // === Merged PR Storage ===
186
+ /** Returns all stored merged PRs (sorted by merge date descending via addMergedPRs). */
161
187
  getMergedPRs() {
162
188
  return this.state.mergedPRs ?? [];
163
189
  }
190
+ /**
191
+ * Add merged PRs to storage, deduplicating by URL.
192
+ * @param prs - Merged PRs to add (duplicates by URL are ignored)
193
+ */
164
194
  addMergedPRs(prs) {
165
195
  if (prs.length === 0)
166
196
  return;
@@ -175,13 +205,19 @@ export class StateManager {
175
205
  debug(MODULE, `Added ${newPRs.length} merged PRs (total: ${this.state.mergedPRs.length})`);
176
206
  this.autoSave();
177
207
  }
208
+ /** Returns the most recent merge date, used as a watermark for incremental fetching. */
178
209
  getMergedPRWatermark() {
179
210
  return this.state.mergedPRs?.[0]?.mergedAt || undefined;
180
211
  }
181
212
  // === Closed PR Storage ===
213
+ /** Returns all stored closed-without-merge PRs (sorted by close date descending via addClosedPRs). */
182
214
  getClosedPRs() {
183
215
  return this.state.closedPRs ?? [];
184
216
  }
217
+ /**
218
+ * Add closed PRs to storage, deduplicating by URL.
219
+ * @param prs - Closed PRs to add (duplicates by URL are ignored)
220
+ */
185
221
  addClosedPRs(prs) {
186
222
  if (prs.length === 0)
187
223
  return;
@@ -196,15 +232,26 @@ export class StateManager {
196
232
  debug(MODULE, `Added ${newPRs.length} closed PRs (total: ${this.state.closedPRs.length})`);
197
233
  this.autoSave();
198
234
  }
235
+ /** Returns the most recent close date, used as a watermark for incremental fetching. */
199
236
  getClosedPRWatermark() {
200
237
  return this.state.closedPRs?.[0]?.closedAt || undefined;
201
238
  }
202
239
  // === Configuration ===
240
+ /**
241
+ * Merge partial config updates into the current configuration.
242
+ * @param config - Partial config object to merge
243
+ */
203
244
  updateConfig(config) {
204
245
  this.state.config = { ...this.state.config, ...config };
205
246
  this.autoSave();
206
247
  }
207
248
  // === Event Logging ===
249
+ /**
250
+ * Append a new event to the event log and auto-persist.
251
+ * Events are capped at 1000 to prevent unbounded growth.
252
+ * @param type - The event type identifier
253
+ * @param data - Arbitrary event payload
254
+ */
208
255
  appendEvent(type, data) {
209
256
  const event = {
210
257
  id: `evt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
@@ -219,9 +266,20 @@ export class StateManager {
219
266
  }
220
267
  this.autoSave();
221
268
  }
269
+ /**
270
+ * Filter events by type.
271
+ * @param type - The event type to filter by
272
+ * @returns Events matching the given type
273
+ */
222
274
  getEventsByType(type) {
223
275
  return this.state.events.filter((e) => e.type === type);
224
276
  }
277
+ /**
278
+ * Filter events within a date range.
279
+ * @param since - Start of range (inclusive)
280
+ * @param until - End of range (inclusive), defaults to now
281
+ * @returns Events within the date range
282
+ */
225
283
  getEventsInRange(since, until = new Date()) {
226
284
  return this.state.events.filter((e) => {
227
285
  const eventTime = new Date(e.at);
@@ -229,6 +287,10 @@ export class StateManager {
229
287
  });
230
288
  }
231
289
  // === Issue Management ===
290
+ /**
291
+ * Track a new issue. No-op if the issue URL is already tracked.
292
+ * @param issue - The issue to track
293
+ */
232
294
  addIssue(issue) {
233
295
  const existing = this.state.activeIssues.find((i) => i.url === issue.url);
234
296
  if (existing) {
@@ -240,6 +302,10 @@ export class StateManager {
240
302
  this.autoSave();
241
303
  }
242
304
  // === Trusted Projects ===
305
+ /**
306
+ * Add a repository to the trusted projects list. No-op if already trusted.
307
+ * @param repo - Repository in "owner/repo" format
308
+ */
243
309
  addTrustedProject(repo) {
244
310
  if (!this.state.config.trustedProjects.includes(repo)) {
245
311
  this.state.config.trustedProjects.push(repo);
@@ -255,6 +321,11 @@ export class StateManager {
255
321
  return true;
256
322
  return false;
257
323
  }
324
+ /**
325
+ * Remove excluded repos/orgs from trusted projects.
326
+ * @param repos - Repository names to exclude
327
+ * @param orgs - Organization names to exclude
328
+ */
258
329
  cleanupExcludedData(repos, orgs) {
259
330
  const matches = (repo) => StateManager.matchesExclusion(repo, repos, orgs);
260
331
  const beforeTrusted = this.state.config.trustedProjects.length;
@@ -266,15 +337,21 @@ export class StateManager {
266
337
  }
267
338
  }
268
339
  // === Starred Repos Management ===
340
+ /** Returns cached starred repository names. */
269
341
  getStarredRepos() {
270
342
  return this.state.config.starredRepos || [];
271
343
  }
344
+ /**
345
+ * Update the cached starred repositories and timestamp.
346
+ * @param repos - Repository names in "owner/repo" format
347
+ */
272
348
  setStarredRepos(repos) {
273
349
  this.state.config.starredRepos = repos;
274
350
  this.state.config.starredReposLastFetched = new Date().toISOString();
275
351
  debug(MODULE, `Updated starred repos: ${repos.length} repositories`);
276
352
  this.autoSave();
277
353
  }
354
+ /** Returns true if starred repos cache is older than 24 hours. */
278
355
  isStarredReposStale() {
279
356
  const lastFetched = this.state.config.starredReposLastFetched;
280
357
  if (!lastFetched) {
@@ -286,6 +363,11 @@ export class StateManager {
286
363
  return now.getTime() - lastFetchedDate.getTime() > staleThresholdMs;
287
364
  }
288
365
  // === Shelve/Unshelve ===
366
+ /**
367
+ * Shelve a PR URL, hiding it from daily digest and capacity.
368
+ * @param url - The PR URL to shelve
369
+ * @returns true if newly shelved, false if already shelved
370
+ */
289
371
  shelvePR(url) {
290
372
  if (!this.state.config.shelvedPRUrls) {
291
373
  this.state.config.shelvedPRUrls = [];
@@ -297,6 +379,11 @@ export class StateManager {
297
379
  this.autoSave();
298
380
  return true;
299
381
  }
382
+ /**
383
+ * Unshelve a PR URL, restoring it to daily digest.
384
+ * @param url - The PR URL to unshelve
385
+ * @returns true if removed from shelf, false if not shelved
386
+ */
300
387
  unshelvePR(url) {
301
388
  if (!this.state.config.shelvedPRUrls) {
302
389
  return false;
@@ -309,10 +396,21 @@ export class StateManager {
309
396
  this.autoSave();
310
397
  return true;
311
398
  }
399
+ /**
400
+ * Check if a PR is currently shelved.
401
+ * @param url - The PR URL to check
402
+ * @returns true if the PR is shelved
403
+ */
312
404
  isPRShelved(url) {
313
405
  return this.state.config.shelvedPRUrls?.includes(url) ?? false;
314
406
  }
315
407
  // === Dismiss / Undismiss Issues ===
408
+ /**
409
+ * Dismiss an issue's notifications. Auto-resurfaces on new activity.
410
+ * @param url - The issue URL to dismiss
411
+ * @param timestamp - ISO timestamp of dismissal
412
+ * @returns true if newly dismissed, false if already dismissed
413
+ */
316
414
  dismissIssue(url, timestamp) {
317
415
  if (!this.state.config.dismissedIssues) {
318
416
  this.state.config.dismissedIssues = {};
@@ -324,6 +422,11 @@ export class StateManager {
324
422
  this.autoSave();
325
423
  return true;
326
424
  }
425
+ /**
426
+ * Restore a dismissed issue to notifications.
427
+ * @param url - The issue URL to undismiss
428
+ * @returns true if undismissed, false if not currently dismissed
429
+ */
327
430
  undismissIssue(url) {
328
431
  if (!this.state.config.dismissedIssues || !(url in this.state.config.dismissedIssues)) {
329
432
  return false;
@@ -332,10 +435,20 @@ export class StateManager {
332
435
  this.autoSave();
333
436
  return true;
334
437
  }
438
+ /**
439
+ * Get the timestamp when an issue was dismissed, or undefined if not dismissed.
440
+ * @param url - The issue URL to check
441
+ */
335
442
  getIssueDismissedAt(url) {
336
443
  return this.state.config.dismissedIssues?.[url];
337
444
  }
338
445
  // === Status Overrides ===
446
+ /**
447
+ * Set a manual status override for a PR. Auto-clears when the PR has new activity.
448
+ * @param url - The PR URL
449
+ * @param status - The overridden status
450
+ * @param lastActivityAt - ISO timestamp of PR's last activity when override was set
451
+ */
339
452
  setStatusOverride(url, status, lastActivityAt) {
340
453
  if (!this.state.config.statusOverrides) {
341
454
  this.state.config.statusOverrides = {};
@@ -347,6 +460,11 @@ export class StateManager {
347
460
  };
348
461
  this.autoSave();
349
462
  }
463
+ /**
464
+ * Remove a manual status override for a PR.
465
+ * @param url - The PR URL
466
+ * @returns true if an override was removed, false if none existed
467
+ */
350
468
  clearStatusOverride(url) {
351
469
  if (!this.state.config.statusOverrides || !(url in this.state.config.statusOverrides)) {
352
470
  return false;
@@ -355,6 +473,12 @@ export class StateManager {
355
473
  this.autoSave();
356
474
  return true;
357
475
  }
476
+ /**
477
+ * Get the status override for a PR, auto-clearing if new activity has occurred.
478
+ * @param url - The PR URL
479
+ * @param currentUpdatedAt - PR's current updatedAt timestamp for staleness check
480
+ * @returns The override if still valid, undefined otherwise
481
+ */
358
482
  getStatusOverride(url, currentUpdatedAt) {
359
483
  const override = this.state.config.statusOverrides?.[url];
360
484
  if (!override)
@@ -367,37 +491,70 @@ export class StateManager {
367
491
  return override;
368
492
  }
369
493
  // === Repository Scoring (delegated to repo-score-manager) ===
494
+ /**
495
+ * Get the score record for a repository.
496
+ * @param repo - Repository in "owner/repo" format
497
+ * @returns Read-only score record, or undefined if not tracked
498
+ */
370
499
  getRepoScore(repo) {
371
500
  return repoScoring.getRepoScore(this.state, repo);
372
501
  }
502
+ /**
503
+ * Update scoring data for a repository.
504
+ * @param repo - Repository in "owner/repo" format
505
+ * @param updates - Partial score fields to merge
506
+ */
373
507
  updateRepoScore(repo, updates) {
374
508
  repoScoring.updateRepoScore(this.state, repo, updates);
375
509
  this.autoSave();
376
510
  }
511
+ /**
512
+ * Increment the merged PR count for a repository.
513
+ * @param repo - Repository in "owner/repo" format
514
+ */
377
515
  incrementMergedCount(repo) {
378
516
  repoScoring.incrementMergedCount(this.state, repo);
379
517
  this.autoSave();
380
518
  }
519
+ /**
520
+ * Increment the closed-without-merge PR count.
521
+ * @param repo - Repository in "owner/repo" format
522
+ */
381
523
  incrementClosedCount(repo) {
382
524
  repoScoring.incrementClosedCount(this.state, repo);
383
525
  this.autoSave();
384
526
  }
527
+ /**
528
+ * Mark a repository as hostile (score zeroed).
529
+ * @param repo - Repository in "owner/repo" format
530
+ */
385
531
  markRepoHostile(repo) {
386
532
  repoScoring.markRepoHostile(this.state, repo);
387
533
  this.autoSave();
388
534
  }
535
+ /** Returns repository names that have at least one merged PR. */
389
536
  getReposWithMergedPRs() {
390
537
  return repoScoring.getReposWithMergedPRs(this.state);
391
538
  }
539
+ /** Returns repository names with open PRs but no merged PRs yet. */
392
540
  getReposWithOpenPRs() {
393
541
  return repoScoring.getReposWithOpenPRs(this.state);
394
542
  }
543
+ /**
544
+ * Returns repos above the score threshold.
545
+ * @param minScore - Minimum score (default: config.minRepoScoreThreshold)
546
+ */
395
547
  getHighScoringRepos(minScore) {
396
548
  return repoScoring.getHighScoringRepos(this.state, minScore);
397
549
  }
550
+ /**
551
+ * Returns repos below the score threshold.
552
+ * @param maxScore - Maximum score (default: config.minRepoScoreThreshold)
553
+ */
398
554
  getLowScoringRepos(maxScore) {
399
555
  return repoScoring.getLowScoringRepos(this.state, maxScore);
400
556
  }
557
+ /** Returns aggregate contribution statistics (merge rate, PR counts, repo breakdown). */
401
558
  getStats() {
402
559
  return repoScoring.getStats(this.state);
403
560
  }
@@ -406,6 +563,16 @@ export class StateManager {
406
563
  let stateManager = null;
407
564
  /**
408
565
  * Get the singleton StateManager instance, creating it on first call.
566
+ * @returns The shared StateManager instance
567
+ *
568
+ * @example
569
+ * ```typescript
570
+ * import { getStateManager } from '@oss-autopilot/core';
571
+ *
572
+ * const state = getStateManager();
573
+ * const config = state.getState().config;
574
+ * console.log(config.githubUsername);
575
+ * ```
409
576
  */
410
577
  export function getStateManager() {
411
578
  if (!stateManager) {
@@ -162,15 +162,9 @@ export interface FetchedPR {
162
162
  * Lightweight reference used in {@link DailyDigest} for shelved and auto-unshelved PRs.
163
163
  * Contains only the fields needed for display, avoiding duplication of the full
164
164
  * {@link FetchedPR} objects already present in `openPRs` and the status-specific arrays.
165
+ * Derived from {@link FetchedPR} via `Pick<>` to stay in sync automatically.
165
166
  */
166
- export interface ShelvedPRRef {
167
- number: number;
168
- url: string;
169
- title: string;
170
- repo: string;
171
- daysSinceActivity: number;
172
- status: FetchedPRStatus;
173
- }
167
+ export type ShelvedPRRef = Pick<FetchedPR, 'number' | 'url' | 'title' | 'repo' | 'daysSinceActivity' | 'status'>;
174
168
  /** An issue tracked through the contribution pipeline from discovery to PR submission. */
175
169
  export interface TrackedIssue {
176
170
  id: number;
@@ -6,6 +6,7 @@ import type { FetchedPR, DailyDigest, AgentState, RepoGroup, CommentedIssue, She
6
6
  import type { ContributionStats } from '../core/stats.js';
7
7
  import type { PRCheckFailure } from '../core/pr-monitor.js';
8
8
  import type { SearchPriority } from '../core/types.js';
9
+ import type { CIFormatterDiagnosis, FormatterDetectionResult } from '../core/formatter-detection.js';
9
10
  export interface JsonOutput<T = unknown> {
10
11
  success: boolean;
11
12
  data?: T;
@@ -304,6 +305,10 @@ export interface LocalReposOutput {
304
305
  cachedAt: string;
305
306
  fromCache: boolean;
306
307
  }
308
+ /** Output of the detect-formatters command. Extends FormatterDetectionResult with optional CI diagnosis. */
309
+ export interface DetectFormattersOutput extends FormatterDetectionResult {
310
+ ciDiagnosis?: CIFormatterDiagnosis;
311
+ }
307
312
  /** Output of the stats command */
308
313
  export interface StatsOutput extends ContributionStats {
309
314
  mergeRateFormatted: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oss-autopilot/core",
3
- "version": "0.57.0",
3
+ "version": "0.59.0",
4
4
  "description": "CLI and core library for managing open source contributions",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,10 +53,11 @@
53
53
  "commander": "^14.0.3"
54
54
  },
55
55
  "devDependencies": {
56
- "@types/node": "^25.3.3",
56
+ "@types/node": "^25.4.0",
57
57
  "@vitest/coverage-v8": "^4.0.18",
58
58
  "esbuild": "^0.27.3",
59
59
  "tsx": "^4.21.0",
60
+ "typedoc": "^0.28.17",
60
61
  "typescript": "^5.9.3",
61
62
  "vitest": "^4.0.16"
62
63
  },
@@ -68,6 +69,8 @@
68
69
  "typecheck": "tsc --noEmit",
69
70
  "test": "vitest run",
70
71
  "test:coverage": "vitest run --coverage",
71
- "test:watch": "vitest"
72
+ "test:watch": "vitest",
73
+ "docs": "typedoc",
74
+ "docs:check": "typedoc --emit none"
72
75
  }
73
76
  }