@oss-autopilot/core 0.42.0 → 0.42.1

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.js CHANGED
@@ -10,7 +10,8 @@
10
10
  * async token fetch to avoid blocking the event loop on `gh auth token`.
11
11
  */
12
12
  import { Command } from 'commander';
13
- import { getGitHubTokenAsync, enableDebug, debug, formatRelativeTime } from './core/index.js';
13
+ import { getGitHubTokenAsync, enableDebug, debug, formatRelativeTime, getCLIVersion } from './core/index.js';
14
+ import { errorMessage } from './core/errors.js';
14
15
  import { outputJson, outputJsonError } from './formatters/json.js';
15
16
  /** Print local repos in human-readable format */
16
17
  function printRepos(repos) {
@@ -23,7 +24,7 @@ function printRepos(repos) {
23
24
  }
24
25
  /** Shared error handler for CLI action catch blocks. */
25
26
  function handleCommandError(err, json) {
26
- const msg = err instanceof Error ? err.message : String(err);
27
+ const msg = errorMessage(err);
27
28
  if (json) {
28
29
  outputJsonError(msg);
29
30
  }
@@ -32,20 +33,7 @@ function handleCommandError(err, json) {
32
33
  }
33
34
  process.exit(1);
34
35
  }
35
- const VERSION = (() => {
36
- try {
37
- // eslint-disable-next-line @typescript-eslint/no-require-imports
38
- const fs = require('fs');
39
- // eslint-disable-next-line @typescript-eslint/no-require-imports
40
- const path = require('path');
41
- const pkgPath = path.join(path.dirname(process.argv[1]), '..', 'package.json');
42
- return JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
43
- }
44
- catch (_err) {
45
- // package.json may not be readable in all bundle/install configurations — fall back to safe default
46
- return '0.0.0';
47
- }
48
- })();
36
+ const VERSION = getCLIVersion();
49
37
  // Commands that skip the preAction GitHub token check.
50
38
  // startup handles auth internally (returns authError in JSON instead of process.exit).
51
39
  const LOCAL_ONLY_COMMANDS = [
@@ -5,6 +5,7 @@
5
5
  import * as path from 'path';
6
6
  import { execFileSync } from 'child_process';
7
7
  import { debug } from '../core/index.js';
8
+ import { errorMessage } from '../core/errors.js';
8
9
  /** File extensions we consider "code" that should be imported/referenced */
9
10
  const CODE_EXTENSIONS = new Set([
10
11
  '.ts',
@@ -76,7 +77,7 @@ export async function runCheckIntegration(options) {
76
77
  newFiles = output ? output.split('\n').filter(Boolean) : [];
77
78
  }
78
79
  catch (error) {
79
- const msg = error instanceof Error ? error.message : String(error);
80
+ const msg = errorMessage(error);
80
81
  throw new Error(`Failed to run git diff: ${msg}`, { cause: error });
81
82
  }
82
83
  // Filter to code files, excluding tests, configs, etc.
@@ -133,9 +134,9 @@ export async function runCheckIntegration(options) {
133
134
  }
134
135
  catch (error) {
135
136
  // git grep exit code 1 = no matches (expected), exit code 2+ = real error
136
- const exitCode = error && typeof error === 'object' && 'status' in error ? error.status : null;
137
- if (exitCode !== null && exitCode !== 1) {
138
- const msg = error instanceof Error ? error.message : String(error);
137
+ const exitCode = error && typeof error === 'object' && 'status' in error ? error.status : undefined;
138
+ if (exitCode !== undefined && exitCode !== 1) {
139
+ const msg = errorMessage(error);
139
140
  debug('check-integration', `git grep failed for "${pattern}": ${msg}`);
140
141
  }
141
142
  }
@@ -18,30 +18,30 @@ export async function runComments(options) {
18
18
  const { owner, repo, number: pull_number } = parsed;
19
19
  // Get PR details
20
20
  const { data: pr } = await octokit.pulls.get({ owner, repo, pull_number });
21
- // Get review comments (inline code comments)
22
- const reviewComments = await paginateAll((page) => octokit.pulls.listReviewComments({
23
- owner,
24
- repo,
25
- pull_number,
26
- per_page: 100,
27
- page,
28
- }));
29
- // Get issue comments (general PR discussion)
30
- const issueComments = await paginateAll((page) => octokit.issues.listComments({
31
- owner,
32
- repo,
33
- issue_number: pull_number,
34
- per_page: 100,
35
- page,
36
- }));
37
- // Get reviews
38
- const reviews = await paginateAll((page) => octokit.pulls.listReviews({
39
- owner,
40
- repo,
41
- pull_number,
42
- per_page: 100,
43
- page,
44
- }));
21
+ // Fetch review comments, issue comments, and reviews in parallel
22
+ const [reviewComments, issueComments, reviews] = await Promise.all([
23
+ paginateAll((page) => octokit.pulls.listReviewComments({
24
+ owner,
25
+ repo,
26
+ pull_number,
27
+ per_page: 100,
28
+ page,
29
+ })),
30
+ paginateAll((page) => octokit.issues.listComments({
31
+ owner,
32
+ repo,
33
+ issue_number: pull_number,
34
+ per_page: 100,
35
+ page,
36
+ })),
37
+ paginateAll((page) => octokit.pulls.listReviews({
38
+ owner,
39
+ repo,
40
+ pull_number,
41
+ per_page: 100,
42
+ page,
43
+ })),
44
+ ]);
45
45
  // Filter out own comments, optionally show bots
46
46
  const username = stateManager.getState().config.githubUsername;
47
47
  const filterComment = (c) => {
@@ -7,6 +7,7 @@
7
7
  * orchestration layer that wires up the phases and handles I/O.
8
8
  */
9
9
  import { getStateManager, PRMonitor, IssueConversationMonitor, requireGitHubToken, CRITICAL_STATUSES, computeRepoSignals, groupPRsByRepo, assessCapacity, collectActionableIssues, computeActionMenu, toShelvedPRRef, formatBriefSummary, formatSummary, } from '../core/index.js';
10
+ import { errorMessage } from '../core/errors.js';
10
11
  import { deduplicateDigest, compactActionableIssues, compactRepoGroups, } from '../formatters/json.js';
11
12
  // Re-export domain functions so existing consumers (tests, dashboard, startup)
12
13
  // can continue importing from './daily.js' without changes.
@@ -33,15 +34,15 @@ async function fetchPRData(prMonitor, token) {
33
34
  prMonitor.fetchUserMergedPRCounts(),
34
35
  prMonitor.fetchUserClosedPRCounts(),
35
36
  prMonitor.fetchRecentlyClosedPRs().catch((err) => {
36
- console.error(`Warning: Failed to fetch recently closed PRs: ${err instanceof Error ? err.message : err}`);
37
+ console.error(`Warning: Failed to fetch recently closed PRs: ${errorMessage(err)}`);
37
38
  return [];
38
39
  }),
39
40
  prMonitor.fetchRecentlyMergedPRs().catch((err) => {
40
- console.error(`Warning: Failed to fetch recently merged PRs: ${err instanceof Error ? err.message : err}`);
41
+ console.error(`Warning: Failed to fetch recently merged PRs: ${errorMessage(err)}`);
41
42
  return [];
42
43
  }),
43
44
  issueMonitor.fetchCommentedIssues().catch((error) => {
44
- const msg = error instanceof Error ? error.message : String(error);
45
+ const msg = errorMessage(error);
45
46
  if (msg.includes('No GitHub username configured')) {
46
47
  console.error(`[DAILY] Issue conversation tracking requires setup: ${msg}`);
47
48
  }
@@ -103,7 +104,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
103
104
  }
104
105
  catch (error) {
105
106
  mergedCountFailures++;
106
- console.error(`[DAILY] Failed to update merged count for ${repo}:`, error instanceof Error ? error.message : error);
107
+ console.error(`[DAILY] Failed to update merged count for ${repo}:`, errorMessage(error));
107
108
  }
108
109
  }
109
110
  if (mergedCountFailures === mergedCounts.size && mergedCounts.size > 0) {
@@ -123,7 +124,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
123
124
  }
124
125
  catch (error) {
125
126
  closedCountFailures++;
126
- console.error(`[DAILY] Failed to update closed count for ${repo}:`, error instanceof Error ? error.message : error);
127
+ console.error(`[DAILY] Failed to update closed count for ${repo}:`, errorMessage(error));
127
128
  }
128
129
  }
129
130
  if (closedCountFailures === closedCounts.size && closedCounts.size > 0) {
@@ -142,7 +143,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
142
143
  }
143
144
  catch (error) {
144
145
  signalUpdateFailures++;
145
- console.error(`[DAILY] Failed to update signals for ${repo}:`, error instanceof Error ? error.message : error);
146
+ console.error(`[DAILY] Failed to update signals for ${repo}:`, errorMessage(error));
146
147
  }
147
148
  }
148
149
  if (signalUpdateFailures === repoSignals.size && repoSignals.size > 0) {
@@ -155,7 +156,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
155
156
  starCounts = await prMonitor.fetchRepoStarCounts(allRepos);
156
157
  }
157
158
  catch (error) {
158
- console.error('[DAILY] Failed to fetch repo star counts:', error instanceof Error ? error.message : error);
159
+ console.error('[DAILY] Failed to fetch repo star counts:', errorMessage(error));
159
160
  console.error('[DAILY] Dashboard minStars filter will use cached star counts (or be skipped for repos without cached data).');
160
161
  starCounts = new Map();
161
162
  }
@@ -166,7 +167,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
166
167
  }
167
168
  catch (error) {
168
169
  starUpdateFailures++;
169
- console.error(`[DAILY] Failed to update star count for ${repo}:`, error instanceof Error ? error.message : error);
170
+ console.error(`[DAILY] Failed to update star count for ${repo}:`, errorMessage(error));
170
171
  }
171
172
  }
172
173
  if (starUpdateFailures === starCounts.size && starCounts.size > 0) {
@@ -180,7 +181,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
180
181
  }
181
182
  catch (error) {
182
183
  trustSyncFailures++;
183
- console.error(`[DAILY] Failed to sync trusted project ${repo}:`, error instanceof Error ? error.message : error);
184
+ console.error(`[DAILY] Failed to sync trusted project ${repo}:`, errorMessage(error));
184
185
  }
185
186
  }
186
187
  if (trustSyncFailures === mergedCounts.size && mergedCounts.size > 0) {
@@ -199,13 +200,13 @@ function updateAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerg
199
200
  stateManager.setMonthlyMergedCounts(monthlyCounts);
200
201
  }
201
202
  catch (error) {
202
- console.error('[DAILY] Failed to store monthly merged counts:', error instanceof Error ? error.message : error);
203
+ console.error('[DAILY] Failed to store monthly merged counts:', errorMessage(error));
203
204
  }
204
205
  try {
205
206
  stateManager.setMonthlyClosedCounts(monthlyClosedCounts);
206
207
  }
207
208
  catch (error) {
208
- console.error('[DAILY] Failed to store monthly closed counts:', error instanceof Error ? error.message : error);
209
+ console.error('[DAILY] Failed to store monthly closed counts:', errorMessage(error));
209
210
  }
210
211
  try {
211
212
  // Build combined monthly opened counts from merged + closed + currently-open PRs
@@ -223,7 +224,7 @@ function updateAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerg
223
224
  stateManager.setMonthlyOpenedCounts(combinedOpenedCounts);
224
225
  }
225
226
  catch (error) {
226
- console.error('[DAILY] Failed to compute/store monthly opened counts:', error instanceof Error ? error.message : error);
227
+ console.error('[DAILY] Failed to compute/store monthly opened counts:', errorMessage(error));
227
228
  }
228
229
  }
229
230
  /**
@@ -246,7 +247,7 @@ function partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs) {
246
247
  }
247
248
  }
248
249
  catch (error) {
249
- console.error('[DAILY] Failed to expire/persist snoozes:', error instanceof Error ? error.message : error);
250
+ console.error('[DAILY] Failed to expire/persist snoozes:', errorMessage(error));
250
251
  }
251
252
  // Partition PRs into active vs shelved, auto-unshelving when maintainers engage
252
253
  const shelvedPRs = [];
@@ -4,6 +4,7 @@
4
4
  * Separates data concerns from template generation and command orchestration.
5
5
  */
6
6
  import { getStateManager, PRMonitor, IssueConversationMonitor } from '../core/index.js';
7
+ import { errorMessage } from '../core/errors.js';
7
8
  import { toShelvedPRRef } from './daily.js';
8
9
  /**
9
10
  * Fetch fresh dashboard data from GitHub.
@@ -17,17 +18,17 @@ export async function fetchDashboardData(token) {
17
18
  const [{ prs, failures }, recentlyClosedPRs, recentlyMergedPRs, mergedResult, closedResult, fetchedIssues] = await Promise.all([
18
19
  prMonitor.fetchUserOpenPRs(),
19
20
  prMonitor.fetchRecentlyClosedPRs().catch((err) => {
20
- console.error(`Warning: Failed to fetch recently closed PRs: ${err instanceof Error ? err.message : err}`);
21
+ console.error(`Warning: Failed to fetch recently closed PRs: ${errorMessage(err)}`);
21
22
  return [];
22
23
  }),
23
24
  prMonitor.fetchRecentlyMergedPRs().catch((err) => {
24
- console.error(`Warning: Failed to fetch recently merged PRs: ${err instanceof Error ? err.message : err}`);
25
+ console.error(`Warning: Failed to fetch recently merged PRs: ${errorMessage(err)}`);
25
26
  return [];
26
27
  }),
27
28
  prMonitor.fetchUserMergedPRCounts(),
28
29
  prMonitor.fetchUserClosedPRCounts(),
29
30
  issueMonitor.fetchCommentedIssues().catch((error) => {
30
- const msg = error instanceof Error ? error.message : String(error);
31
+ const msg = errorMessage(error);
31
32
  if (msg.includes('No GitHub username configured')) {
32
33
  console.error(`[DASHBOARD] Issue conversation tracking requires setup: ${msg}`);
33
34
  }
@@ -54,13 +55,13 @@ export async function fetchDashboardData(token) {
54
55
  stateManager.setMonthlyMergedCounts(monthlyCounts);
55
56
  }
56
57
  catch (error) {
57
- console.error('[DASHBOARD] Failed to store monthly merged counts:', error instanceof Error ? error.message : error);
58
+ console.error('[DASHBOARD] Failed to store monthly merged counts:', errorMessage(error));
58
59
  }
59
60
  try {
60
61
  stateManager.setMonthlyClosedCounts(monthlyClosedCounts);
61
62
  }
62
63
  catch (error) {
63
- console.error('[DASHBOARD] Failed to store monthly closed counts:', error instanceof Error ? error.message : error);
64
+ console.error('[DASHBOARD] Failed to store monthly closed counts:', errorMessage(error));
64
65
  }
65
66
  try {
66
67
  const combinedOpenedCounts = { ...openedFromMerged };
@@ -76,7 +77,7 @@ export async function fetchDashboardData(token) {
76
77
  stateManager.setMonthlyOpenedCounts(combinedOpenedCounts);
77
78
  }
78
79
  catch (error) {
79
- console.error('[DASHBOARD] Failed to store monthly opened counts:', error instanceof Error ? error.message : error);
80
+ console.error('[DASHBOARD] Failed to store monthly opened counts:', errorMessage(error));
80
81
  }
81
82
  const digest = prMonitor.generateDigest(prs, recentlyClosedPRs, recentlyMergedPRs);
82
83
  // Apply shelve partitioning for display (auto-unshelve only runs in daily check)
@@ -9,6 +9,7 @@ import * as http from 'http';
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
11
  import { getStateManager, getGitHubToken } from '../core/index.js';
12
+ import { errorMessage } from '../core/errors.js';
12
13
  import { fetchDashboardData, computePRsByRepo, computeTopRepos, getMonthlyData } from './dashboard-data.js';
13
14
  import { buildDashboardStats } from './dashboard-templates.js';
14
15
  // ── Constants ────────────────────────────────────────────────────────────────
@@ -203,7 +204,7 @@ export async function startDashboardServer(options) {
203
204
  }
204
205
  catch (error) {
205
206
  console.error('Action failed:', body.action, body.url, error);
206
- sendError(res, 500, `Action failed: ${error instanceof Error ? error.message : String(error)}`);
207
+ sendError(res, 500, `Action failed: ${errorMessage(error)}`);
207
208
  return;
208
209
  }
209
210
  // Rebuild dashboard data from cached digest + updated state
@@ -229,7 +230,7 @@ export async function startDashboardServer(options) {
229
230
  }
230
231
  catch (error) {
231
232
  console.error('Dashboard refresh failed:', error);
232
- sendError(res, 500, `Refresh failed: ${error instanceof Error ? error.message : String(error)}`);
233
+ sendError(res, 500, `Refresh failed: ${errorMessage(error)}`);
233
234
  }
234
235
  }
235
236
  // ── Static file serving ──────────────────────────────────────────────────
@@ -7,6 +7,7 @@ import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
  import { execFile } from 'child_process';
9
9
  import { getStateManager, getDashboardPath, getGitHubToken } from '../core/index.js';
10
+ import { errorMessage } from '../core/errors.js';
10
11
  import { outputJson } from '../formatters/json.js';
11
12
  import { fetchDashboardData, computePRsByRepo, computeTopRepos, getMonthlyData } from './dashboard-data.js';
12
13
  import { buildDashboardStats, generateDashboardHtml } from './dashboard-templates.js';
@@ -39,7 +40,7 @@ export async function runDashboard(options) {
39
40
  commentedIssues = result.commentedIssues;
40
41
  }
41
42
  catch (error) {
42
- console.error('Failed to fetch fresh data:', error instanceof Error ? error.message : error);
43
+ console.error('Failed to fetch fresh data:', errorMessage(error));
43
44
  console.error('Falling back to cached data (issue conversations unavailable)...');
44
45
  digest = stateManager.getState().lastDigest;
45
46
  }
@@ -7,6 +7,7 @@ import * as path from 'path';
7
7
  import * as os from 'os';
8
8
  import { execFileSync } from 'child_process';
9
9
  import { getStateManager, debug } from '../core/index.js';
10
+ import { errorMessage } from '../core/errors.js';
10
11
  /** Default directories to scan for local clones */
11
12
  const DEFAULT_SCAN_PATHS = [
12
13
  path.join(os.homedir(), 'Documents', 'oss'),
@@ -115,7 +116,7 @@ export async function runLocalRepos(options) {
115
116
  stateManager.save();
116
117
  }
117
118
  catch (error) {
118
- const msg = error instanceof Error ? error.message : String(error);
119
+ const msg = errorMessage(error);
119
120
  debug('local-repos', `Failed to cache scan results: ${msg}`);
120
121
  }
121
122
  return {
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
+ import { errorMessage } from '../core/errors.js';
7
8
  /** Extract GitHub issue/PR URLs from a markdown line */
8
9
  function extractGitHubUrl(line) {
9
10
  const match = line.match(/https:\/\/github\.com\/([^/]+\/[^/]+)\/issues\/(\d+)/);
@@ -100,7 +101,7 @@ export async function runParseList(options) {
100
101
  content = fs.readFileSync(filePath, 'utf-8');
101
102
  }
102
103
  catch (error) {
103
- const msg = error instanceof Error ? error.message : String(error);
104
+ const msg = errorMessage(error);
104
105
  throw new Error(`Failed to read file: ${msg}`, { cause: error });
105
106
  }
106
107
  return parseIssueList(content);
@@ -7,21 +7,11 @@
7
7
  * `node cli.bundle.cjs startup --json` call, reducing UI noise in Claude Code.
8
8
  */
9
9
  import * as fs from 'fs';
10
- import * as path from 'path';
11
10
  import { execFile } from 'child_process';
12
- import { getStateManager, getGitHubToken } from '../core/index.js';
11
+ import { getStateManager, getGitHubToken, getCLIVersion } from '../core/index.js';
12
+ import { errorMessage } from '../core/errors.js';
13
13
  import { executeDailyCheck } from './daily.js';
14
14
  import { writeDashboardFromState } from './dashboard.js';
15
- function getVersion() {
16
- try {
17
- const pkgPath = path.join(path.dirname(process.argv[1]), '..', 'package.json');
18
- return JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
19
- }
20
- catch (error) {
21
- console.error('[STARTUP] Failed to detect CLI version:', error instanceof Error ? error.message : error);
22
- return '0.0.0';
23
- }
24
- }
25
15
  /**
26
16
  * Parse issueListPath from a config file's YAML frontmatter.
27
17
  * Returns the path string or undefined if not found.
@@ -75,7 +65,7 @@ export function detectIssueList() {
75
65
  }
76
66
  }
77
67
  catch (error) {
78
- console.error('[STARTUP] Failed to read config:', error instanceof Error ? error.message : error);
68
+ console.error('[STARTUP] Failed to read config:', errorMessage(error));
79
69
  }
80
70
  }
81
71
  // 2. Probe known paths
@@ -98,7 +88,7 @@ export function detectIssueList() {
98
88
  return { path: issueListPath, source, availableCount, completedCount };
99
89
  }
100
90
  catch (error) {
101
- console.error(`[STARTUP] Failed to read issue list at ${issueListPath}:`, error instanceof Error ? error.message : error);
91
+ console.error(`[STARTUP] Failed to read issue list at ${issueListPath}:`, errorMessage(error));
102
92
  return { path: issueListPath, source, availableCount: 0, completedCount: 0 };
103
93
  }
104
94
  }
@@ -122,7 +112,7 @@ function openInBrowser(filePath) {
122
112
  * Errors from the daily check propagate to the caller.
123
113
  */
124
114
  export async function runStartup() {
125
- const version = getVersion();
115
+ const version = getCLIVersion();
126
116
  const stateManager = getStateManager();
127
117
  // 1. Check setup
128
118
  if (!stateManager.isSetupComplete()) {
@@ -151,7 +141,7 @@ export async function runStartup() {
151
141
  }
152
142
  }
153
143
  catch (error) {
154
- console.error('[STARTUP] Dashboard generation failed:', error instanceof Error ? error.message : error);
144
+ console.error('[STARTUP] Dashboard generation failed:', errorMessage(error));
155
145
  }
156
146
  // Append dashboard status to brief summary (only startup opens the browser, not daily)
157
147
  if (dashboardOpened) {
@@ -21,7 +21,7 @@ export function validateGitHubUrl(url, pattern, entityType) {
21
21
  if (pattern.test(url))
22
22
  return;
23
23
  const example = entityType === 'PR' ? 'https://github.com/owner/repo/pull/123' : 'https://github.com/owner/repo/issues/123';
24
- throw new Error(`Invalid ${entityType} URL: ${url}. Expected format: ${example}`);
24
+ throw new ValidationError(`Invalid ${entityType} URL: ${url}. Expected format: ${example}`);
25
25
  }
26
26
  /**
27
27
  * Validate that a URL does not exceed the maximum allowed length.
@@ -29,7 +29,7 @@ export function validateGitHubUrl(url, pattern, entityType) {
29
29
  */
30
30
  export function validateUrl(url) {
31
31
  if (url.length > MAX_URL_LENGTH) {
32
- throw new Error(`URL exceeds maximum length of ${MAX_URL_LENGTH} characters`);
32
+ throw new ValidationError(`URL exceeds maximum length of ${MAX_URL_LENGTH} characters`);
33
33
  }
34
34
  return url;
35
35
  }
@@ -39,7 +39,7 @@ export function validateUrl(url) {
39
39
  */
40
40
  export function validatePRNumber(num) {
41
41
  if (!Number.isInteger(num) || num < 1 || num > MAX_PR_NUMBER) {
42
- throw new Error(`PR number must be a positive integer up to ${MAX_PR_NUMBER}`);
42
+ throw new ValidationError(`PR number must be a positive integer up to ${MAX_PR_NUMBER}`);
43
43
  }
44
44
  return num;
45
45
  }
@@ -49,7 +49,7 @@ export function validatePRNumber(num) {
49
49
  */
50
50
  export function validateMessage(message) {
51
51
  if (message.length > MAX_MESSAGE_LENGTH) {
52
- throw new Error(`Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`);
52
+ throw new ValidationError(`Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`);
53
53
  }
54
54
  return message;
55
55
  }
@@ -59,7 +59,7 @@ export function validateMessage(message) {
59
59
  */
60
60
  export function validateRepoIdentifier(repo) {
61
61
  if (!REPO_PATTERN.test(repo)) {
62
- throw new Error(`Invalid repository format: "${repo}". Expected "owner/repo".`);
62
+ throw new ValidationError(`Invalid repository format: "${repo}". Expected "owner/repo".`);
63
63
  }
64
64
  return repo;
65
65
  }
@@ -22,3 +22,12 @@ export declare class ConfigurationError extends OssAutopilotError {
22
22
  export declare class ValidationError extends OssAutopilotError {
23
23
  constructor(message: string);
24
24
  }
25
+ /**
26
+ * Extract a human-readable message from an unknown error value.
27
+ */
28
+ export declare function errorMessage(e: unknown): string;
29
+ /**
30
+ * Safely extract an HTTP status code from an unknown error (e.g. Octokit errors).
31
+ * Returns undefined if the error doesn't have a numeric `status` property.
32
+ */
33
+ export declare function getHttpStatusCode(error: unknown): number | undefined;
@@ -32,3 +32,20 @@ export class ValidationError extends OssAutopilotError {
32
32
  this.name = 'ValidationError';
33
33
  }
34
34
  }
35
+ /**
36
+ * Extract a human-readable message from an unknown error value.
37
+ */
38
+ export function errorMessage(e) {
39
+ return e instanceof Error ? e.message : String(e);
40
+ }
41
+ /**
42
+ * Safely extract an HTTP status code from an unknown error (e.g. Octokit errors).
43
+ * Returns undefined if the error doesn't have a numeric `status` property.
44
+ */
45
+ export function getHttpStatusCode(error) {
46
+ if (error && typeof error === 'object' && 'status' in error) {
47
+ const status = error.status;
48
+ return typeof status === 'number' && Number.isFinite(status) ? status : undefined;
49
+ }
50
+ return undefined;
51
+ }
@@ -4,35 +4,28 @@
4
4
  */
5
5
  import { Octokit } from '@octokit/rest';
6
6
  import { ClosedPR, MergedPR } from './types.js';
7
+ /** TTL for cached PR count results (1 hour). */
8
+ export declare const PR_COUNTS_CACHE_TTL_MS: number;
9
+ /** Return type shared by both merged and closed PR count functions. */
10
+ export interface PRCountsResult<R> {
11
+ repos: Map<string, R>;
12
+ monthlyCounts: Record<string, number>;
13
+ monthlyOpenedCounts: Record<string, number>;
14
+ dailyActivityCounts: Record<string, number>;
15
+ }
7
16
  /**
8
17
  * Fetch merged PR counts and latest merge dates per repository for the configured user.
9
18
  * Also builds a monthly histogram of all merges for the contribution timeline.
10
19
  */
11
- export declare function fetchUserMergedPRCounts(octokit: Octokit, githubUsername: string): Promise<{
12
- repos: Map<string, {
13
- count: number;
14
- lastMergedAt: string;
15
- }>;
16
- monthlyCounts: Record<string, number>;
17
- monthlyOpenedCounts: Record<string, number>;
18
- dailyActivityCounts: Record<string, number>;
19
- }>;
20
+ export declare function fetchUserMergedPRCounts(octokit: Octokit, githubUsername: string): Promise<PRCountsResult<{
21
+ count: number;
22
+ lastMergedAt: string;
23
+ }>>;
20
24
  /**
21
25
  * Fetch closed-without-merge PR counts per repository for the configured user.
22
26
  * Used to populate closedWithoutMergeCount in repo scores for accurate merge rate.
23
27
  */
24
- export declare function fetchUserClosedPRCounts(octokit: Octokit, githubUsername: string): Promise<{
25
- repos: Map<string, number>;
26
- monthlyCounts: Record<string, number>;
27
- monthlyOpenedCounts: Record<string, number>;
28
- dailyActivityCounts: Record<string, number>;
29
- }>;
30
- /**
31
- * Fetch GitHub star counts for a list of repositories.
32
- * Used to populate stargazersCount in repo scores for dashboard filtering by minStars.
33
- * Fetches concurrently with per-repo error isolation (missing/private repos are skipped).
34
- */
35
- export declare function fetchRepoStarCounts(octokit: Octokit, repos: string[]): Promise<Map<string, number>>;
28
+ export declare function fetchUserClosedPRCounts(octokit: Octokit, githubUsername: string): Promise<PRCountsResult<number>>;
36
29
  /**
37
30
  * Shared helper: search for recent PRs and filter out own repos, excluded repos/orgs.
38
31
  * Returns parsed search results that pass all filters.