@oss-autopilot/core 0.41.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.bundle.cjs +1552 -1318
- package/dist/cli.js +593 -69
- package/dist/commands/check-integration.d.ts +3 -3
- package/dist/commands/check-integration.js +10 -43
- package/dist/commands/comments.d.ts +6 -9
- package/dist/commands/comments.js +102 -252
- package/dist/commands/config.d.ts +8 -2
- package/dist/commands/config.js +6 -28
- package/dist/commands/daily.d.ts +28 -4
- package/dist/commands/daily.js +33 -45
- package/dist/commands/dashboard-data.js +7 -6
- package/dist/commands/dashboard-server.d.ts +14 -0
- package/dist/commands/dashboard-server.js +362 -0
- package/dist/commands/dashboard.d.ts +5 -0
- package/dist/commands/dashboard.js +51 -1
- package/dist/commands/dismiss.d.ts +13 -5
- package/dist/commands/dismiss.js +4 -24
- package/dist/commands/index.d.ts +33 -0
- package/dist/commands/index.js +22 -0
- package/dist/commands/init.d.ts +5 -4
- package/dist/commands/init.js +4 -14
- package/dist/commands/local-repos.d.ts +4 -5
- package/dist/commands/local-repos.js +6 -33
- package/dist/commands/parse-list.d.ts +3 -4
- package/dist/commands/parse-list.js +8 -39
- package/dist/commands/read.d.ts +11 -5
- package/dist/commands/read.js +4 -18
- package/dist/commands/search.d.ts +3 -3
- package/dist/commands/search.js +39 -65
- package/dist/commands/setup.d.ts +34 -5
- package/dist/commands/setup.js +75 -166
- package/dist/commands/shelve.d.ts +13 -5
- package/dist/commands/shelve.js +4 -24
- package/dist/commands/snooze.d.ts +15 -9
- package/dist/commands/snooze.js +16 -59
- package/dist/commands/startup.d.ts +11 -6
- package/dist/commands/startup.js +44 -82
- package/dist/commands/status.d.ts +3 -3
- package/dist/commands/status.js +10 -29
- package/dist/commands/track.d.ts +10 -9
- package/dist/commands/track.js +17 -39
- package/dist/commands/validation.d.ts +2 -2
- package/dist/commands/validation.js +7 -15
- package/dist/commands/vet.d.ts +3 -3
- package/dist/commands/vet.js +16 -26
- package/dist/core/errors.d.ts +9 -0
- package/dist/core/errors.js +17 -0
- package/dist/core/github-stats.d.ts +14 -21
- package/dist/core/github-stats.js +84 -138
- package/dist/core/http-cache.d.ts +6 -0
- package/dist/core/http-cache.js +16 -4
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +2 -1
- package/dist/core/issue-conversation.js +4 -4
- package/dist/core/issue-discovery.js +14 -14
- package/dist/core/issue-vetting.js +17 -17
- package/dist/core/pr-monitor.d.ts +6 -20
- package/dist/core/pr-monitor.js +11 -52
- package/dist/core/state.js +4 -5
- package/dist/core/utils.d.ts +11 -0
- package/dist/core/utils.js +21 -0
- package/dist/formatters/json.d.ts +58 -0
- package/package.json +5 -1
|
@@ -2,19 +2,51 @@
|
|
|
2
2
|
* GitHub Stats - Fetching merged/closed PR counts and repository star counts.
|
|
3
3
|
* Extracted from PRMonitor to isolate statistics-gathering API calls (#263).
|
|
4
4
|
*/
|
|
5
|
-
import { extractOwnerRepo, parseGitHubUrl } from './utils.js';
|
|
6
|
-
import { ValidationError } from './errors.js';
|
|
5
|
+
import { extractOwnerRepo, parseGitHubUrl, isOwnRepo } from './utils.js';
|
|
7
6
|
import { debug, warn } from './logger.js';
|
|
7
|
+
import { getHttpCache } from './http-cache.js';
|
|
8
8
|
const MODULE = 'github-stats';
|
|
9
|
+
/** TTL for cached PR count results (1 hour). */
|
|
10
|
+
export const PR_COUNTS_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
11
|
+
/** Type guard for deserialized cache data — prevents crashes on corrupt/stale cache. */
|
|
12
|
+
function isCachedPRCounts(v) {
|
|
13
|
+
if (typeof v !== 'object' || v === null)
|
|
14
|
+
return false;
|
|
15
|
+
const obj = v;
|
|
16
|
+
return (Array.isArray(obj.reposEntries) &&
|
|
17
|
+
typeof obj.monthlyCounts === 'object' &&
|
|
18
|
+
obj.monthlyCounts !== null &&
|
|
19
|
+
typeof obj.monthlyOpenedCounts === 'object' &&
|
|
20
|
+
obj.monthlyOpenedCounts !== null &&
|
|
21
|
+
typeof obj.dailyActivityCounts === 'object' &&
|
|
22
|
+
obj.dailyActivityCounts !== null);
|
|
23
|
+
}
|
|
9
24
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
25
|
+
* Shared paginated search for user PR counts with histogram tracking.
|
|
26
|
+
*
|
|
27
|
+
* Handles: pagination, owner extraction, skip-own-repos, monthly/daily histograms.
|
|
28
|
+
* The `accumulateRepo` callback handles per-repo data and returns the primary date
|
|
29
|
+
* string (e.g. mergedAt or closedAt) used for monthly counts and daily activity.
|
|
30
|
+
* Return an empty string to skip histogram tracking for that item.
|
|
12
31
|
*/
|
|
13
|
-
|
|
32
|
+
async function fetchUserPRCounts(octokit, githubUsername, query, label, accumulateRepo) {
|
|
14
33
|
if (!githubUsername) {
|
|
15
34
|
return { repos: new Map(), monthlyCounts: {}, monthlyOpenedCounts: {}, dailyActivityCounts: {} };
|
|
16
35
|
}
|
|
17
|
-
|
|
36
|
+
// Check for a fresh cached result (avoids 10-20 paginated API calls)
|
|
37
|
+
const cache = getHttpCache();
|
|
38
|
+
const cacheKey = `pr-counts:${label}:${githubUsername}`;
|
|
39
|
+
const cached = cache.getIfFresh(cacheKey, PR_COUNTS_CACHE_TTL_MS);
|
|
40
|
+
if (cached && isCachedPRCounts(cached)) {
|
|
41
|
+
debug(MODULE, `Using cached ${label} PR counts for @${githubUsername}`);
|
|
42
|
+
return {
|
|
43
|
+
repos: new Map(cached.reposEntries),
|
|
44
|
+
monthlyCounts: cached.monthlyCounts,
|
|
45
|
+
monthlyOpenedCounts: cached.monthlyOpenedCounts,
|
|
46
|
+
dailyActivityCounts: cached.dailyActivityCounts,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
debug(MODULE, `Fetching ${label} PR counts for @${githubUsername}...`);
|
|
18
50
|
const repos = new Map();
|
|
19
51
|
const monthlyCounts = {};
|
|
20
52
|
const monthlyOpenedCounts = {};
|
|
@@ -23,7 +55,7 @@ export async function fetchUserMergedPRCounts(octokit, githubUsername) {
|
|
|
23
55
|
let fetched = 0;
|
|
24
56
|
while (true) {
|
|
25
57
|
const { data } = await octokit.search.issuesAndPullRequests({
|
|
26
|
-
q: `is:pr
|
|
58
|
+
q: `is:pr ${query} author:${githubUsername}`,
|
|
27
59
|
sort: 'updated',
|
|
28
60
|
order: 'desc',
|
|
29
61
|
per_page: 100,
|
|
@@ -32,49 +64,35 @@ export async function fetchUserMergedPRCounts(octokit, githubUsername) {
|
|
|
32
64
|
for (const item of data.items) {
|
|
33
65
|
const parsed = extractOwnerRepo(item.html_url);
|
|
34
66
|
if (!parsed) {
|
|
35
|
-
warn(MODULE, `Skipping
|
|
67
|
+
warn(MODULE, `Skipping ${label} PR with unparseable URL: ${item.html_url}`);
|
|
36
68
|
continue;
|
|
37
69
|
}
|
|
38
70
|
const { owner } = parsed;
|
|
39
71
|
const repo = `${owner}/${parsed.repo}`;
|
|
40
72
|
// Skip own repos (PRs to your own repos aren't OSS contributions)
|
|
41
|
-
if (owner
|
|
73
|
+
if (isOwnRepo(owner, githubUsername))
|
|
42
74
|
continue;
|
|
43
75
|
// Note: excludeRepos/excludeOrgs are intentionally NOT filtered here.
|
|
44
76
|
// Those filters control issue discovery/search, not historical statistics.
|
|
45
|
-
//
|
|
46
|
-
const
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
existing.count += 1;
|
|
51
|
-
if (mergedAt && mergedAt > existing.lastMergedAt) {
|
|
52
|
-
existing.lastMergedAt = mergedAt;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
repos.set(repo, { count: 1, lastMergedAt: mergedAt });
|
|
57
|
-
}
|
|
58
|
-
// Monthly histogram (every PR counted individually)
|
|
59
|
-
if (mergedAt) {
|
|
60
|
-
const month = mergedAt.slice(0, 7); // "YYYY-MM"
|
|
77
|
+
// Per-repo accumulation + get primary date for histograms
|
|
78
|
+
const primaryDate = accumulateRepo(repos, repo, item);
|
|
79
|
+
// Monthly histogram for primary date (merged/closed)
|
|
80
|
+
if (primaryDate) {
|
|
81
|
+
const month = primaryDate.slice(0, 7); // "YYYY-MM"
|
|
61
82
|
monthlyCounts[month] = (monthlyCounts[month] || 0) + 1;
|
|
83
|
+
// Daily activity for primary date
|
|
84
|
+
const day = primaryDate.slice(0, 10);
|
|
85
|
+
if (day.length === 10)
|
|
86
|
+
dailyActivityCounts[day] = (dailyActivityCounts[day] || 0) + 1;
|
|
62
87
|
}
|
|
63
|
-
// Track when this PR was opened (for monthly opened histogram)
|
|
88
|
+
// Track when this PR was opened (for monthly opened histogram + daily activity)
|
|
64
89
|
if (item.created_at) {
|
|
65
90
|
const openedMonth = item.created_at.slice(0, 7); // "YYYY-MM"
|
|
66
91
|
monthlyOpenedCounts[openedMonth] = (monthlyOpenedCounts[openedMonth] || 0) + 1;
|
|
67
|
-
// Daily activity: PR opened
|
|
68
92
|
const openedDay = item.created_at.slice(0, 10);
|
|
69
93
|
if (openedDay.length === 10)
|
|
70
94
|
dailyActivityCounts[openedDay] = (dailyActivityCounts[openedDay] || 0) + 1;
|
|
71
95
|
}
|
|
72
|
-
// Daily activity: PR merged
|
|
73
|
-
if (mergedAt) {
|
|
74
|
-
const mergedDay = mergedAt.slice(0, 10);
|
|
75
|
-
if (mergedDay.length === 10)
|
|
76
|
-
dailyActivityCounts[mergedDay] = (dailyActivityCounts[mergedDay] || 0) + 1;
|
|
77
|
-
}
|
|
78
96
|
}
|
|
79
97
|
fetched += data.items.length;
|
|
80
98
|
// Stop if we've fetched all results or hit the API limit (1000)
|
|
@@ -83,120 +101,48 @@ export async function fetchUserMergedPRCounts(octokit, githubUsername) {
|
|
|
83
101
|
}
|
|
84
102
|
page++;
|
|
85
103
|
}
|
|
86
|
-
debug(MODULE, `Found ${fetched}
|
|
104
|
+
debug(MODULE, `Found ${fetched} ${label} PRs across ${repos.size} repos`);
|
|
105
|
+
// Cache the aggregated result (Map → entries array for JSON serialization)
|
|
106
|
+
cache.set(cacheKey, '', {
|
|
107
|
+
reposEntries: Array.from(repos.entries()),
|
|
108
|
+
monthlyCounts,
|
|
109
|
+
monthlyOpenedCounts,
|
|
110
|
+
dailyActivityCounts,
|
|
111
|
+
});
|
|
87
112
|
return { repos, monthlyCounts, monthlyOpenedCounts, dailyActivityCounts };
|
|
88
113
|
}
|
|
89
114
|
/**
|
|
90
|
-
* Fetch
|
|
91
|
-
*
|
|
115
|
+
* Fetch merged PR counts and latest merge dates per repository for the configured user.
|
|
116
|
+
* Also builds a monthly histogram of all merges for the contribution timeline.
|
|
92
117
|
*/
|
|
93
|
-
export
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
while (true) {
|
|
105
|
-
const { data } = await octokit.search.issuesAndPullRequests({
|
|
106
|
-
q: `is:pr is:closed is:unmerged author:${githubUsername}`,
|
|
107
|
-
sort: 'updated',
|
|
108
|
-
order: 'desc',
|
|
109
|
-
per_page: 100,
|
|
110
|
-
page,
|
|
111
|
-
});
|
|
112
|
-
for (const item of data.items) {
|
|
113
|
-
const parsed = extractOwnerRepo(item.html_url);
|
|
114
|
-
if (!parsed) {
|
|
115
|
-
warn(MODULE, `Skipping closed PR with unparseable URL: ${item.html_url}`);
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
const { owner } = parsed;
|
|
119
|
-
const repo = `${owner}/${parsed.repo}`;
|
|
120
|
-
// Skip own repos
|
|
121
|
-
if (owner.toLowerCase() === githubUsername.toLowerCase())
|
|
122
|
-
continue;
|
|
123
|
-
// Note: excludeRepos/excludeOrgs are intentionally NOT filtered here.
|
|
124
|
-
// Those filters control issue discovery/search, not historical statistics.
|
|
125
|
-
// A closed PR is a closed PR regardless of current tracking preferences.
|
|
126
|
-
repos.set(repo, (repos.get(repo) || 0) + 1);
|
|
127
|
-
// Track when this PR was closed (for monthly closed histogram)
|
|
128
|
-
if (item.closed_at) {
|
|
129
|
-
const closedMonth = item.closed_at.slice(0, 7); // "YYYY-MM"
|
|
130
|
-
monthlyCounts[closedMonth] = (monthlyCounts[closedMonth] || 0) + 1;
|
|
131
|
-
// Daily activity: PR closed
|
|
132
|
-
const closedDay = item.closed_at.slice(0, 10);
|
|
133
|
-
if (closedDay.length === 10)
|
|
134
|
-
dailyActivityCounts[closedDay] = (dailyActivityCounts[closedDay] || 0) + 1;
|
|
135
|
-
}
|
|
136
|
-
// Track when this PR was opened (for monthly opened histogram)
|
|
137
|
-
if (item.created_at) {
|
|
138
|
-
const openedMonth = item.created_at.slice(0, 7); // "YYYY-MM"
|
|
139
|
-
monthlyOpenedCounts[openedMonth] = (monthlyOpenedCounts[openedMonth] || 0) + 1;
|
|
140
|
-
// Daily activity: PR opened
|
|
141
|
-
const openedDay = item.created_at.slice(0, 10);
|
|
142
|
-
if (openedDay.length === 10)
|
|
143
|
-
dailyActivityCounts[openedDay] = (dailyActivityCounts[openedDay] || 0) + 1;
|
|
118
|
+
export function fetchUserMergedPRCounts(octokit, githubUsername) {
|
|
119
|
+
return fetchUserPRCounts(octokit, githubUsername, 'is:merged', 'merged', (repos, repo, item) => {
|
|
120
|
+
if (!item.pull_request?.merged_at) {
|
|
121
|
+
warn(MODULE, `merged_at missing for merged PR ${item.html_url}${item.closed_at ? ', falling back to closed_at' : ', no date available'}`);
|
|
122
|
+
}
|
|
123
|
+
const mergedAt = item.pull_request?.merged_at || item.closed_at || '';
|
|
124
|
+
const existing = repos.get(repo);
|
|
125
|
+
if (existing) {
|
|
126
|
+
existing.count += 1;
|
|
127
|
+
if (mergedAt && mergedAt > existing.lastMergedAt) {
|
|
128
|
+
existing.lastMergedAt = mergedAt;
|
|
144
129
|
}
|
|
145
130
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
break;
|
|
131
|
+
else {
|
|
132
|
+
repos.set(repo, { count: 1, lastMergedAt: mergedAt });
|
|
149
133
|
}
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
debug(MODULE, `Found ${fetched} closed (unmerged) PRs across ${repos.size} repos`);
|
|
153
|
-
return { repos, monthlyCounts, monthlyOpenedCounts, dailyActivityCounts };
|
|
134
|
+
return mergedAt;
|
|
135
|
+
});
|
|
154
136
|
}
|
|
155
137
|
/**
|
|
156
|
-
* Fetch
|
|
157
|
-
* Used to populate
|
|
158
|
-
* Fetches concurrently with per-repo error isolation (missing/private repos are skipped).
|
|
138
|
+
* Fetch closed-without-merge PR counts per repository for the configured user.
|
|
139
|
+
* Used to populate closedWithoutMergeCount in repo scores for accurate merge rate.
|
|
159
140
|
*/
|
|
160
|
-
export
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// Fetch in parallel chunks to avoid overwhelming the API
|
|
166
|
-
const chunkSize = 10;
|
|
167
|
-
for (let i = 0; i < repos.length; i += chunkSize) {
|
|
168
|
-
const chunk = repos.slice(i, i + chunkSize);
|
|
169
|
-
const settled = await Promise.allSettled(chunk.map(async (repo) => {
|
|
170
|
-
const parts = repo.split('/');
|
|
171
|
-
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
172
|
-
throw new ValidationError(`Malformed repo identifier: "${repo}"`);
|
|
173
|
-
}
|
|
174
|
-
const [owner, name] = parts;
|
|
175
|
-
const { data } = await octokit.repos.get({ owner, repo: name });
|
|
176
|
-
return { repo, stars: data.stargazers_count };
|
|
177
|
-
}));
|
|
178
|
-
let chunkFailures = 0;
|
|
179
|
-
for (let j = 0; j < settled.length; j++) {
|
|
180
|
-
const result = settled[j];
|
|
181
|
-
if (result.status === 'fulfilled') {
|
|
182
|
-
results.set(result.value.repo, result.value.stars);
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
chunkFailures++;
|
|
186
|
-
warn(MODULE, `Failed to fetch stars for ${chunk[j]}: ${result.reason instanceof Error ? result.reason.message : result.reason}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
// If entire chunk failed, likely a systemic issue (rate limit, auth, outage) — abort remaining
|
|
190
|
-
if (chunkFailures === chunk.length && chunk.length > 0) {
|
|
191
|
-
const remaining = repos.length - i - chunkSize;
|
|
192
|
-
if (remaining > 0) {
|
|
193
|
-
warn(MODULE, `Entire chunk failed, aborting remaining ${remaining} repos`);
|
|
194
|
-
}
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
debug(MODULE, `Fetched star counts for ${results.size}/${repos.length} repos`);
|
|
199
|
-
return results;
|
|
141
|
+
export function fetchUserClosedPRCounts(octokit, githubUsername) {
|
|
142
|
+
return fetchUserPRCounts(octokit, githubUsername, 'is:closed is:unmerged', 'closed', (repos, repo, item) => {
|
|
143
|
+
repos.set(repo, (repos.get(repo) || 0) + 1);
|
|
144
|
+
return item.closed_at || '';
|
|
145
|
+
});
|
|
200
146
|
}
|
|
201
147
|
/**
|
|
202
148
|
* Shared helper: search for recent PRs and filter out own repos, excluded repos/orgs.
|
|
@@ -226,7 +172,7 @@ export async function fetchRecentPRs(octokit, config, query, label, days, mapIte
|
|
|
226
172
|
}
|
|
227
173
|
const repo = `${parsed.owner}/${parsed.repo}`;
|
|
228
174
|
// Skip own repos
|
|
229
|
-
if (parsed.owner
|
|
175
|
+
if (isOwnRepo(parsed.owner, config.githubUsername))
|
|
230
176
|
continue;
|
|
231
177
|
// Skip excluded repos and orgs
|
|
232
178
|
if (config.excludeRepos.includes(repo))
|
|
@@ -32,6 +32,12 @@ export declare class HttpCache {
|
|
|
32
32
|
private keyFor;
|
|
33
33
|
/** Full path to the cache file for a given URL. */
|
|
34
34
|
private pathFor;
|
|
35
|
+
/**
|
|
36
|
+
* Return the cached body if the entry exists and is younger than `maxAgeMs`.
|
|
37
|
+
* Useful for time-based caching where ETag validation isn't applicable
|
|
38
|
+
* (e.g., caching aggregated results from paginated API calls).
|
|
39
|
+
*/
|
|
40
|
+
getIfFresh(key: string, maxAgeMs: number): unknown | null;
|
|
35
41
|
/**
|
|
36
42
|
* Look up a cached response. Returns `null` if no cache entry exists.
|
|
37
43
|
*/
|
package/dist/core/http-cache.js
CHANGED
|
@@ -14,6 +14,7 @@ import * as path from 'path';
|
|
|
14
14
|
import * as crypto from 'crypto';
|
|
15
15
|
import { getCacheDir } from './utils.js';
|
|
16
16
|
import { debug } from './logger.js';
|
|
17
|
+
import { getHttpStatusCode } from './errors.js';
|
|
17
18
|
const MODULE = 'http-cache';
|
|
18
19
|
/**
|
|
19
20
|
* Maximum age (in ms) before a cache entry is considered stale and eligible for
|
|
@@ -44,6 +45,20 @@ export class HttpCache {
|
|
|
44
45
|
pathFor(url) {
|
|
45
46
|
return path.join(this.cacheDir, `${this.keyFor(url)}.json`);
|
|
46
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Return the cached body if the entry exists and is younger than `maxAgeMs`.
|
|
50
|
+
* Useful for time-based caching where ETag validation isn't applicable
|
|
51
|
+
* (e.g., caching aggregated results from paginated API calls).
|
|
52
|
+
*/
|
|
53
|
+
getIfFresh(key, maxAgeMs) {
|
|
54
|
+
const entry = this.get(key);
|
|
55
|
+
if (!entry)
|
|
56
|
+
return null;
|
|
57
|
+
const age = Date.now() - new Date(entry.cachedAt).getTime();
|
|
58
|
+
if (!Number.isFinite(age) || age < 0 || age > maxAgeMs)
|
|
59
|
+
return null;
|
|
60
|
+
return entry.body;
|
|
61
|
+
}
|
|
47
62
|
/**
|
|
48
63
|
* Look up a cached response. Returns `null` if no cache entry exists.
|
|
49
64
|
*/
|
|
@@ -262,8 +277,5 @@ export async function cachedRequest(cache, url, fetcher) {
|
|
|
262
277
|
* Octokit throws a RequestError with status 304 for conditional requests.
|
|
263
278
|
*/
|
|
264
279
|
function isNotModifiedError(err) {
|
|
265
|
-
|
|
266
|
-
return err.status === 304;
|
|
267
|
-
}
|
|
268
|
-
return false;
|
|
280
|
+
return getHttpStatusCode(err) === 304;
|
|
269
281
|
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export { IssueDiscovery, type IssueCandidate, type SearchPriority, isDocOnlyIssu
|
|
|
8
8
|
export { IssueConversationMonitor } from './issue-conversation.js';
|
|
9
9
|
export { isBotAuthor, isAcknowledgmentComment } from './comment-utils.js';
|
|
10
10
|
export { getOctokit, checkRateLimit, type RateLimitInfo } from './github.js';
|
|
11
|
-
export { parseGitHubUrl, daysBetween, splitRepo, getDataDir, getStatePath, getBackupDir, getCacheDir, getDashboardPath, formatRelativeTime, byDateDescending, getGitHubToken, getGitHubTokenAsync, requireGitHubToken, resetGitHubTokenCache, } from './utils.js';
|
|
11
|
+
export { parseGitHubUrl, daysBetween, splitRepo, isOwnRepo, getCLIVersion, getDataDir, getStatePath, getBackupDir, getCacheDir, getDashboardPath, formatRelativeTime, byDateDescending, getGitHubToken, getGitHubTokenAsync, requireGitHubToken, resetGitHubTokenCache, } from './utils.js';
|
|
12
|
+
export { OssAutopilotError, ConfigurationError, ValidationError, errorMessage, getHttpStatusCode } from './errors.js';
|
|
12
13
|
export { enableDebug, isDebugEnabled, debug, warn, timed } from './logger.js';
|
|
13
14
|
export { HttpCache, getHttpCache, resetHttpCache, cachedRequest, type CacheEntry } from './http-cache.js';
|
|
14
15
|
export { CRITICAL_STATUSES, computeRepoSignals, groupPRsByRepo, assessCapacity, collectActionableIssues, computeActionMenu, toShelvedPRRef, formatActionHint, formatBriefSummary, formatSummary, printDigest, } from './daily-logic.js';
|
package/dist/core/index.js
CHANGED
|
@@ -8,7 +8,8 @@ export { IssueDiscovery, isDocOnlyIssue, applyPerRepoCap, DOC_ONLY_LABELS, } fro
|
|
|
8
8
|
export { IssueConversationMonitor } from './issue-conversation.js';
|
|
9
9
|
export { isBotAuthor, isAcknowledgmentComment } from './comment-utils.js';
|
|
10
10
|
export { getOctokit, checkRateLimit } from './github.js';
|
|
11
|
-
export { parseGitHubUrl, daysBetween, splitRepo, getDataDir, getStatePath, getBackupDir, getCacheDir, getDashboardPath, formatRelativeTime, byDateDescending, getGitHubToken, getGitHubTokenAsync, requireGitHubToken, resetGitHubTokenCache, } from './utils.js';
|
|
11
|
+
export { parseGitHubUrl, daysBetween, splitRepo, isOwnRepo, getCLIVersion, getDataDir, getStatePath, getBackupDir, getCacheDir, getDashboardPath, formatRelativeTime, byDateDescending, getGitHubToken, getGitHubTokenAsync, requireGitHubToken, resetGitHubTokenCache, } from './utils.js';
|
|
12
|
+
export { OssAutopilotError, ConfigurationError, ValidationError, errorMessage, getHttpStatusCode } from './errors.js';
|
|
12
13
|
export { enableDebug, isDebugEnabled, debug, warn, timed } from './logger.js';
|
|
13
14
|
export { HttpCache, getHttpCache, resetHttpCache, cachedRequest } from './http-cache.js';
|
|
14
15
|
export { CRITICAL_STATUSES, computeRepoSignals, groupPRsByRepo, assessCapacity, collectActionableIssues, computeActionMenu, toShelvedPRRef, formatActionHint, formatBriefSummary, formatSummary, printDigest, } from './daily-logic.js';
|
|
@@ -9,9 +9,9 @@ import { getOctokit } from './github.js';
|
|
|
9
9
|
import { isBotAuthor, isAcknowledgmentComment } from './comment-utils.js';
|
|
10
10
|
import { paginateAll } from './pagination.js';
|
|
11
11
|
import { getStateManager } from './state.js';
|
|
12
|
-
import { daysBetween, splitRepo, extractOwnerRepo } from './utils.js';
|
|
12
|
+
import { daysBetween, splitRepo, extractOwnerRepo, isOwnRepo } from './utils.js';
|
|
13
13
|
import { runWorkerPool } from './concurrency.js';
|
|
14
|
-
import { ConfigurationError } from './errors.js';
|
|
14
|
+
import { ConfigurationError, errorMessage } from './errors.js';
|
|
15
15
|
import { debug, warn } from './logger.js';
|
|
16
16
|
const MODULE = 'issue-conversation';
|
|
17
17
|
const MAX_CONCURRENT_REQUESTS = 5;
|
|
@@ -71,7 +71,7 @@ export class IssueConversationMonitor {
|
|
|
71
71
|
const { owner, repo } = parsed;
|
|
72
72
|
const repoFullName = `${owner}/${repo}`;
|
|
73
73
|
// Skip issues in user-owned repos (we only care about contributing to others' projects)
|
|
74
|
-
if (owner
|
|
74
|
+
if (isOwnRepo(owner, username))
|
|
75
75
|
continue;
|
|
76
76
|
// Skip user-authored issues
|
|
77
77
|
if (item.user?.login?.toLowerCase() === username.toLowerCase())
|
|
@@ -107,7 +107,7 @@ export class IssueConversationMonitor {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
catch (error) {
|
|
110
|
-
const msg =
|
|
110
|
+
const msg = errorMessage(error);
|
|
111
111
|
failures.push({ issueUrl: item.html_url, error: msg });
|
|
112
112
|
warn(MODULE, `Error analyzing issue ${item.html_url}: ${msg}`);
|
|
113
113
|
}
|
|
@@ -12,7 +12,7 @@ import { getOctokit, checkRateLimit } from './github.js';
|
|
|
12
12
|
import { getStateManager } from './state.js';
|
|
13
13
|
import { daysBetween, getDataDir } from './utils.js';
|
|
14
14
|
import { DEFAULT_CONFIG } from './types.js';
|
|
15
|
-
import { ValidationError } from './errors.js';
|
|
15
|
+
import { ValidationError, errorMessage, getHttpStatusCode } from './errors.js';
|
|
16
16
|
import { warn } from './logger.js';
|
|
17
17
|
import { isDocOnlyIssue, detectLabelFarmingRepos, applyPerRepoCap } from './issue-filtering.js';
|
|
18
18
|
import { IssueVetter } from './issue-vetting.js';
|
|
@@ -78,16 +78,16 @@ export class IssueDiscovery {
|
|
|
78
78
|
}
|
|
79
79
|
catch (error) {
|
|
80
80
|
const cachedRepos = this.stateManager.getStarredRepos();
|
|
81
|
-
const
|
|
82
|
-
warn(MODULE, 'Error fetching starred repos:',
|
|
81
|
+
const errMsg = errorMessage(error);
|
|
82
|
+
warn(MODULE, 'Error fetching starred repos:', errMsg);
|
|
83
83
|
if (cachedRepos.length === 0) {
|
|
84
84
|
warn(MODULE, `Failed to fetch starred repositories from GitHub API. ` +
|
|
85
|
-
`No cached repos available. Error: ${
|
|
85
|
+
`No cached repos available. Error: ${errMsg}\n` +
|
|
86
86
|
`Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
|
|
87
87
|
}
|
|
88
88
|
else {
|
|
89
89
|
warn(MODULE, `Failed to fetch starred repositories from GitHub API. ` +
|
|
90
|
-
`Using ${cachedRepos.length} cached repos instead. Error: ${
|
|
90
|
+
`Using ${cachedRepos.length} cached repos instead. Error: ${errMsg}`);
|
|
91
91
|
}
|
|
92
92
|
return cachedRepos;
|
|
93
93
|
}
|
|
@@ -128,11 +128,11 @@ export class IssueDiscovery {
|
|
|
128
128
|
}
|
|
129
129
|
catch (error) {
|
|
130
130
|
// Fail fast on auth errors — no point searching with a bad token
|
|
131
|
-
if (error
|
|
131
|
+
if (getHttpStatusCode(error) === 401) {
|
|
132
132
|
throw error;
|
|
133
133
|
}
|
|
134
134
|
// Non-fatal: proceed with search for transient/network errors
|
|
135
|
-
warn(MODULE, 'Could not check rate limit:', error
|
|
135
|
+
warn(MODULE, 'Could not check rate limit:', errorMessage(error));
|
|
136
136
|
}
|
|
137
137
|
// Get merged-PR repos (highest merge probability)
|
|
138
138
|
const mergedPRRepos = this.stateManager.getReposWithMergedPRs();
|
|
@@ -306,12 +306,12 @@ export class IssueDiscovery {
|
|
|
306
306
|
console.log(`Found ${starFiltered.length} candidates from general search`);
|
|
307
307
|
}
|
|
308
308
|
catch (error) {
|
|
309
|
-
const
|
|
310
|
-
phase2Error =
|
|
309
|
+
const errMsg = errorMessage(error);
|
|
310
|
+
phase2Error = errMsg;
|
|
311
311
|
if (IssueVetter.isRateLimitError(error)) {
|
|
312
312
|
rateLimitHitDuringSearch = true;
|
|
313
313
|
}
|
|
314
|
-
warn(MODULE, `Error in general issue search: ${
|
|
314
|
+
warn(MODULE, `Error in general issue search: ${errMsg}`);
|
|
315
315
|
}
|
|
316
316
|
}
|
|
317
317
|
// Phase 3: Actively maintained repos (#349)
|
|
@@ -376,12 +376,12 @@ export class IssueDiscovery {
|
|
|
376
376
|
console.log(`Found ${starFiltered.length} candidates from maintained-repo search`);
|
|
377
377
|
}
|
|
378
378
|
catch (error) {
|
|
379
|
-
const
|
|
380
|
-
phase3Error =
|
|
379
|
+
const errMsg = errorMessage(error);
|
|
380
|
+
phase3Error = errMsg;
|
|
381
381
|
if (IssueVetter.isRateLimitError(error)) {
|
|
382
382
|
rateLimitHitDuringSearch = true;
|
|
383
383
|
}
|
|
384
|
-
warn(MODULE, `Error in maintained-repo search: ${
|
|
384
|
+
warn(MODULE, `Error in maintained-repo search: ${errMsg}`);
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
387
|
if (allCandidates.length === 0) {
|
|
@@ -476,7 +476,7 @@ export class IssueDiscovery {
|
|
|
476
476
|
rateLimitFailures++;
|
|
477
477
|
}
|
|
478
478
|
const batchRepos = batch.join(', ');
|
|
479
|
-
warn(MODULE, `Error searching issues in batch [${batchRepos}]:`, error
|
|
479
|
+
warn(MODULE, `Error searching issues in batch [${batchRepos}]:`, errorMessage(error));
|
|
480
480
|
}
|
|
481
481
|
}
|
|
482
482
|
const allBatchesFailed = failedBatches === batches.length && batches.length > 0;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { paginateAll } from './pagination.js';
|
|
8
8
|
import { parseGitHubUrl, daysBetween } from './utils.js';
|
|
9
|
-
import { ValidationError } from './errors.js';
|
|
9
|
+
import { ValidationError, errorMessage, getHttpStatusCode } from './errors.js';
|
|
10
10
|
import { warn } from './logger.js';
|
|
11
11
|
import { getHttpCache, cachedRequest } from './http-cache.js';
|
|
12
12
|
import { calculateRepoQualityBonus, calculateViabilityScore } from './issue-scoring.js';
|
|
@@ -254,7 +254,7 @@ export class IssueVetter {
|
|
|
254
254
|
if (IssueVetter.isRateLimitError(error)) {
|
|
255
255
|
rateLimitFailures++;
|
|
256
256
|
}
|
|
257
|
-
warn(MODULE, `Error vetting issue ${url}:`, error
|
|
257
|
+
warn(MODULE, `Error vetting issue ${url}:`, errorMessage(error));
|
|
258
258
|
});
|
|
259
259
|
pending.push(task);
|
|
260
260
|
// Limit concurrency
|
|
@@ -275,11 +275,11 @@ export class IssueVetter {
|
|
|
275
275
|
}
|
|
276
276
|
/** Check if an error is a GitHub rate limit error (429 or rate-limit 403). */
|
|
277
277
|
static isRateLimitError(error) {
|
|
278
|
-
const status = error
|
|
278
|
+
const status = getHttpStatusCode(error);
|
|
279
279
|
if (status === 429)
|
|
280
280
|
return true;
|
|
281
281
|
if (status === 403) {
|
|
282
|
-
const msg =
|
|
282
|
+
const msg = errorMessage(error).toLowerCase();
|
|
283
283
|
return msg.includes('rate limit');
|
|
284
284
|
}
|
|
285
285
|
return false;
|
|
@@ -306,9 +306,9 @@ export class IssueVetter {
|
|
|
306
306
|
return { passed: data.total_count === 0 && linkedPRs.length === 0 };
|
|
307
307
|
}
|
|
308
308
|
catch (error) {
|
|
309
|
-
const
|
|
310
|
-
warn(MODULE, `Failed to check for existing PRs on ${owner}/${repo}#${issueNumber}: ${
|
|
311
|
-
return { passed: true, inconclusive: true, reason:
|
|
309
|
+
const errMsg = errorMessage(error);
|
|
310
|
+
warn(MODULE, `Failed to check for existing PRs on ${owner}/${repo}#${issueNumber}: ${errMsg}. Assuming no existing PR.`);
|
|
311
|
+
return { passed: true, inconclusive: true, reason: errMsg };
|
|
312
312
|
}
|
|
313
313
|
}
|
|
314
314
|
/**
|
|
@@ -325,8 +325,8 @@ export class IssueVetter {
|
|
|
325
325
|
return data.total_count;
|
|
326
326
|
}
|
|
327
327
|
catch (error) {
|
|
328
|
-
const
|
|
329
|
-
warn(MODULE, `Could not check merged PRs in ${owner}/${repo}: ${
|
|
328
|
+
const errMsg = errorMessage(error);
|
|
329
|
+
warn(MODULE, `Could not check merged PRs in ${owner}/${repo}: ${errMsg}. Defaulting to 0.`);
|
|
330
330
|
return 0;
|
|
331
331
|
}
|
|
332
332
|
}
|
|
@@ -370,9 +370,9 @@ export class IssueVetter {
|
|
|
370
370
|
return { passed: true };
|
|
371
371
|
}
|
|
372
372
|
catch (error) {
|
|
373
|
-
const
|
|
374
|
-
warn(MODULE, `Failed to check claim status on ${owner}/${repo}#${issueNumber}: ${
|
|
375
|
-
return { passed: true, inconclusive: true, reason:
|
|
373
|
+
const errMsg = errorMessage(error);
|
|
374
|
+
warn(MODULE, `Failed to check claim status on ${owner}/${repo}#${issueNumber}: ${errMsg}. Assuming not claimed.`);
|
|
375
|
+
return { passed: true, inconclusive: true, reason: errMsg };
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
378
|
async checkProjectHealth(owner, repo) {
|
|
@@ -403,8 +403,8 @@ export class IssueVetter {
|
|
|
403
403
|
}
|
|
404
404
|
}
|
|
405
405
|
catch (error) {
|
|
406
|
-
const
|
|
407
|
-
warn(MODULE, `Failed to check CI status for ${owner}/${repo}: ${
|
|
406
|
+
const errMsg = errorMessage(error);
|
|
407
|
+
warn(MODULE, `Failed to check CI status for ${owner}/${repo}: ${errMsg}. Defaulting to unknown.`);
|
|
408
408
|
}
|
|
409
409
|
return {
|
|
410
410
|
repo: `${owner}/${repo}`,
|
|
@@ -419,8 +419,8 @@ export class IssueVetter {
|
|
|
419
419
|
};
|
|
420
420
|
}
|
|
421
421
|
catch (error) {
|
|
422
|
-
const
|
|
423
|
-
warn(MODULE, `Error checking project health for ${owner}/${repo}: ${
|
|
422
|
+
const errMsg = errorMessage(error);
|
|
423
|
+
warn(MODULE, `Error checking project health for ${owner}/${repo}: ${errMsg}`);
|
|
424
424
|
return {
|
|
425
425
|
repo: `${owner}/${repo}`,
|
|
426
426
|
lastCommitAt: '',
|
|
@@ -430,7 +430,7 @@ export class IssueVetter {
|
|
|
430
430
|
ciStatus: 'unknown',
|
|
431
431
|
isActive: false,
|
|
432
432
|
checkFailed: true,
|
|
433
|
-
failureReason:
|
|
433
|
+
failureReason: errMsg,
|
|
434
434
|
};
|
|
435
435
|
}
|
|
436
436
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* - github-stats.ts: Merged/closed PR counts and star fetching
|
|
13
13
|
*/
|
|
14
14
|
import { FetchedPR, DailyDigest, ClosedPR, MergedPR } from './types.js';
|
|
15
|
+
import { type PRCountsResult } from './github-stats.js';
|
|
15
16
|
export { computeDisplayLabel } from './display-utils.js';
|
|
16
17
|
export { classifyCICheck, classifyFailingChecks } from './ci-analysis.js';
|
|
17
18
|
export { isConditionalChecklistItem } from './checklist-analysis.js';
|
|
@@ -59,35 +60,20 @@ export declare class PRMonitor {
|
|
|
59
60
|
* Fetch merged PR counts and latest merge dates per repository for the configured user.
|
|
60
61
|
* Delegates to github-stats module.
|
|
61
62
|
*/
|
|
62
|
-
fetchUserMergedPRCounts(): Promise<{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}>;
|
|
67
|
-
monthlyCounts: Record<string, number>;
|
|
68
|
-
monthlyOpenedCounts: Record<string, number>;
|
|
69
|
-
dailyActivityCounts: Record<string, number>;
|
|
70
|
-
}>;
|
|
63
|
+
fetchUserMergedPRCounts(): Promise<PRCountsResult<{
|
|
64
|
+
count: number;
|
|
65
|
+
lastMergedAt: string;
|
|
66
|
+
}>>;
|
|
71
67
|
/**
|
|
72
68
|
* Fetch closed-without-merge PR counts per repository for the configured user.
|
|
73
69
|
* Delegates to github-stats module.
|
|
74
70
|
*/
|
|
75
|
-
fetchUserClosedPRCounts(): Promise<
|
|
76
|
-
repos: Map<string, number>;
|
|
77
|
-
monthlyCounts: Record<string, number>;
|
|
78
|
-
monthlyOpenedCounts: Record<string, number>;
|
|
79
|
-
dailyActivityCounts: Record<string, number>;
|
|
80
|
-
}>;
|
|
71
|
+
fetchUserClosedPRCounts(): Promise<PRCountsResult<number>>;
|
|
81
72
|
/**
|
|
82
73
|
* Fetch GitHub star counts for a list of repositories.
|
|
83
74
|
* Delegates to github-stats module.
|
|
84
75
|
*/
|
|
85
76
|
fetchRepoStarCounts(repos: string[]): Promise<Map<string, number>>;
|
|
86
|
-
/**
|
|
87
|
-
* Shared helper: search for recent PRs and filter out own repos, excluded repos/orgs.
|
|
88
|
-
* Returns parsed search results that pass all filters.
|
|
89
|
-
*/
|
|
90
|
-
private fetchRecentPRs;
|
|
91
77
|
/**
|
|
92
78
|
* Fetch PRs closed without merge in the last N days.
|
|
93
79
|
* Delegates to github-stats module.
|