@oss-autopilot/core 0.44.2 → 0.44.15
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-registry.js +61 -0
- package/dist/cli.bundle.cjs +101 -127
- package/dist/cli.bundle.cjs.map +4 -4
- package/dist/commands/daily.d.ts +6 -1
- package/dist/commands/daily.js +29 -64
- package/dist/commands/dashboard-data.d.ts +22 -1
- package/dist/commands/dashboard-data.js +85 -62
- package/dist/commands/dashboard-lifecycle.js +39 -2
- package/dist/commands/dashboard-scripts.d.ts +1 -1
- package/dist/commands/dashboard-scripts.js +2 -1
- package/dist/commands/dashboard-server.d.ts +2 -1
- package/dist/commands/dashboard-server.js +120 -81
- package/dist/commands/dashboard-templates.js +15 -69
- package/dist/commands/override.d.ts +21 -0
- package/dist/commands/override.js +35 -0
- package/dist/core/checklist-analysis.js +3 -1
- package/dist/core/daily-logic.d.ts +13 -10
- package/dist/core/daily-logic.js +79 -166
- package/dist/core/display-utils.d.ts +4 -0
- package/dist/core/display-utils.js +53 -54
- package/dist/core/errors.d.ts +8 -0
- package/dist/core/errors.js +26 -0
- package/dist/core/github-stats.d.ts +3 -3
- package/dist/core/github-stats.js +15 -7
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/core/issue-conversation.js +2 -2
- package/dist/core/issue-discovery.d.ts +0 -5
- package/dist/core/issue-discovery.js +4 -11
- package/dist/core/issue-vetting.d.ts +0 -2
- package/dist/core/issue-vetting.js +31 -45
- package/dist/core/pr-monitor.d.ts +26 -3
- package/dist/core/pr-monitor.js +106 -93
- package/dist/core/state.d.ts +22 -1
- package/dist/core/state.js +50 -1
- package/dist/core/test-utils.js +6 -16
- package/dist/core/types.d.ts +51 -38
- package/dist/core/types.js +8 -0
- package/dist/core/utils.d.ts +2 -0
- package/dist/core/utils.js +5 -1
- package/dist/formatters/json.d.ts +1 -13
- package/dist/formatters/json.js +1 -13
- package/package.json +2 -2
|
@@ -10,17 +10,21 @@
|
|
|
10
10
|
* - formatActionHint — human-readable maintainer action hint label
|
|
11
11
|
* - formatBriefSummary / formatSummary / printDigest — rendering
|
|
12
12
|
*/
|
|
13
|
-
import type { FetchedPR, FetchedPRStatus, DailyDigest, ShelvedPRRef, MaintainerActionHint, ComputedRepoSignals, RepoGroup, CommentedIssue, CommentedIssueWithResponse } from './types.js';
|
|
13
|
+
import type { FetchedPR, FetchedPRStatus, StalenessTier, ActionReason, DailyDigest, ShelvedPRRef, MaintainerActionHint, ComputedRepoSignals, RepoGroup, CommentedIssue, CommentedIssueWithResponse } from './types.js';
|
|
14
14
|
import type { CapacityAssessment, ActionableIssue, ActionMenu } from '../formatters/json.js';
|
|
15
15
|
/**
|
|
16
|
-
* Statuses indicating
|
|
17
|
-
* Used
|
|
16
|
+
* Statuses indicating action needed from the contributor.
|
|
17
|
+
* Used for auto-unshelving shelved PRs.
|
|
18
18
|
*/
|
|
19
19
|
export declare const CRITICAL_STATUSES: ReadonlySet<FetchedPRStatus>;
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
/**
|
|
21
|
+
* ActionReason values that indicate high-priority issues blocking capacity.
|
|
22
|
+
* `incomplete_checklist` is excluded — it's actionable but not blocking.
|
|
23
|
+
* Used in capacity assessment to determine if user can take on new work.
|
|
24
|
+
*/
|
|
25
|
+
export declare const CRITICAL_ACTION_REASONS: ReadonlySet<ActionReason>;
|
|
26
|
+
/** Staleness tiers indicating staleness — maintainer comments during these tiers don't count as responsive. */
|
|
27
|
+
export declare const STALE_STATUSES: ReadonlySet<StalenessTier>;
|
|
24
28
|
/**
|
|
25
29
|
* Map a full FetchedPR to a lightweight ShelvedPRRef for digest output.
|
|
26
30
|
* Only the fields needed for display are retained, reducing JSON payload size.
|
|
@@ -33,9 +37,8 @@ export declare function toShelvedPRRef(pr: FetchedPR): ShelvedPRRef;
|
|
|
33
37
|
export declare function groupPRsByRepo(prs: FetchedPR[]): RepoGroup[];
|
|
34
38
|
/**
|
|
35
39
|
* Compute per-repo maintainer signals from observed open PR data.
|
|
36
|
-
* - isResponsive: true if any PR in the repo has a maintainer comment and
|
|
37
|
-
*
|
|
38
|
-
* - hasActiveMaintainers: true if any PR in the repo has a status in ACTIVE_MAINTAINER_STATUSES
|
|
40
|
+
* - isResponsive: true if any PR in the repo has a maintainer comment and stalenessTier is active
|
|
41
|
+
* - hasActiveMaintainers: true if any non-stale PR exists in the repo (stalenessTier is 'active')
|
|
39
42
|
*/
|
|
40
43
|
export declare function computeRepoSignals(prs: FetchedPR[]): Map<string, ComputedRepoSignals>;
|
|
41
44
|
/**
|
package/dist/core/daily-logic.js
CHANGED
|
@@ -16,24 +16,22 @@ import { warn } from './logger.js';
|
|
|
16
16
|
// Constants
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
/**
|
|
19
|
-
* Statuses indicating
|
|
20
|
-
* Used
|
|
19
|
+
* Statuses indicating action needed from the contributor.
|
|
20
|
+
* Used for auto-unshelving shelved PRs.
|
|
21
21
|
*/
|
|
22
|
-
export const CRITICAL_STATUSES = new Set([
|
|
22
|
+
export const CRITICAL_STATUSES = new Set(['needs_addressing']);
|
|
23
|
+
/**
|
|
24
|
+
* ActionReason values that indicate high-priority issues blocking capacity.
|
|
25
|
+
* `incomplete_checklist` is excluded — it's actionable but not blocking.
|
|
26
|
+
* Used in capacity assessment to determine if user can take on new work.
|
|
27
|
+
*/
|
|
28
|
+
export const CRITICAL_ACTION_REASONS = new Set([
|
|
23
29
|
'needs_response',
|
|
24
30
|
'needs_changes',
|
|
25
31
|
'failing_ci',
|
|
26
32
|
'merge_conflict',
|
|
27
33
|
]);
|
|
28
|
-
/**
|
|
29
|
-
export const ACTIVE_MAINTAINER_STATUSES = new Set([
|
|
30
|
-
'healthy',
|
|
31
|
-
'waiting_on_maintainer',
|
|
32
|
-
'changes_addressed',
|
|
33
|
-
'needs_response',
|
|
34
|
-
'needs_changes',
|
|
35
|
-
]);
|
|
36
|
-
/** Statuses indicating staleness — maintainer comments during these statuses don't count as responsive. */
|
|
34
|
+
/** Staleness tiers indicating staleness — maintainer comments during these tiers don't count as responsive. */
|
|
37
35
|
export const STALE_STATUSES = new Set(['dormant', 'approaching_dormant']);
|
|
38
36
|
// ---------------------------------------------------------------------------
|
|
39
37
|
// Internal helpers
|
|
@@ -86,16 +84,15 @@ export function groupPRsByRepo(prs) {
|
|
|
86
84
|
}
|
|
87
85
|
/**
|
|
88
86
|
* Compute per-repo maintainer signals from observed open PR data.
|
|
89
|
-
* - isResponsive: true if any PR in the repo has a maintainer comment and
|
|
90
|
-
*
|
|
91
|
-
* - hasActiveMaintainers: true if any PR in the repo has a status in ACTIVE_MAINTAINER_STATUSES
|
|
87
|
+
* - isResponsive: true if any PR in the repo has a maintainer comment and stalenessTier is active
|
|
88
|
+
* - hasActiveMaintainers: true if any non-stale PR exists in the repo (stalenessTier is 'active')
|
|
92
89
|
*/
|
|
93
90
|
export function computeRepoSignals(prs) {
|
|
94
91
|
const repoMap = buildRepoMap(prs, 'COMPUTE_SIGNALS');
|
|
95
92
|
const result = new Map();
|
|
96
93
|
for (const [repo, repoPRs] of repoMap) {
|
|
97
|
-
const isResponsive = repoPRs.some((pr) => pr.lastMaintainerComment && !STALE_STATUSES.has(pr.
|
|
98
|
-
const hasActiveMaintainers = repoPRs.some((pr) =>
|
|
94
|
+
const isResponsive = repoPRs.some((pr) => pr.lastMaintainerComment && !STALE_STATUSES.has(pr.stalenessTier));
|
|
95
|
+
const hasActiveMaintainers = repoPRs.some((pr) => !STALE_STATUSES.has(pr.stalenessTier));
|
|
99
96
|
result.set(repo, { isResponsive, hasActiveMaintainers });
|
|
100
97
|
}
|
|
101
98
|
return result;
|
|
@@ -106,7 +103,7 @@ export function computeRepoSignals(prs) {
|
|
|
106
103
|
*/
|
|
107
104
|
export function assessCapacity(activePRs, maxActivePRs, shelvedPRCount) {
|
|
108
105
|
const activePRCount = activePRs.length;
|
|
109
|
-
const criticalIssueCount = activePRs.filter((pr) =>
|
|
106
|
+
const criticalIssueCount = activePRs.filter((pr) => pr.status === 'needs_addressing' && pr.actionReason && CRITICAL_ACTION_REASONS.has(pr.actionReason)).length;
|
|
110
107
|
// Has capacity if: under PR limit AND no critical issues
|
|
111
108
|
const underPRLimit = activePRCount < maxActivePRs;
|
|
112
109
|
const noCriticalIssues = criticalIssueCount === 0;
|
|
@@ -145,37 +142,56 @@ export function assessCapacity(activePRs, maxActivePRs, shelvedPRCount) {
|
|
|
145
142
|
*/
|
|
146
143
|
export function collectActionableIssues(prs, snoozedUrls = new Set()) {
|
|
147
144
|
const issues = [];
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
145
|
+
const actionPRs = prs.filter((pr) => pr.status === 'needs_addressing');
|
|
146
|
+
const reasonOrder = [
|
|
147
|
+
'needs_response',
|
|
148
|
+
'needs_changes',
|
|
149
|
+
'failing_ci',
|
|
150
|
+
'merge_conflict',
|
|
151
|
+
'incomplete_checklist',
|
|
152
|
+
];
|
|
153
|
+
for (const reason of reasonOrder) {
|
|
154
|
+
for (const pr of actionPRs) {
|
|
155
|
+
if (pr.actionReason !== reason)
|
|
156
|
+
continue;
|
|
157
|
+
if (reason === 'failing_ci' && snoozedUrls.has(pr.url))
|
|
158
|
+
continue;
|
|
159
|
+
let label;
|
|
160
|
+
let type;
|
|
161
|
+
switch (reason) {
|
|
162
|
+
case 'needs_response':
|
|
163
|
+
label = '[Needs Response]';
|
|
164
|
+
type = 'needs_response';
|
|
165
|
+
break;
|
|
166
|
+
case 'needs_changes':
|
|
167
|
+
label = '[Needs Changes]';
|
|
168
|
+
type = 'needs_changes';
|
|
169
|
+
break;
|
|
170
|
+
case 'failing_ci': {
|
|
171
|
+
const checkInfo = pr.failingCheckNames.length > 0 ? ` (${pr.failingCheckNames.join(', ')})` : '';
|
|
172
|
+
label = `[CI Failing${checkInfo}]`;
|
|
173
|
+
type = 'ci_failing';
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'merge_conflict':
|
|
177
|
+
label = '[Merge Conflict]';
|
|
178
|
+
type = 'merge_conflict';
|
|
179
|
+
break;
|
|
180
|
+
case 'incomplete_checklist': {
|
|
181
|
+
const stats = pr.checklistStats ? ` (${pr.checklistStats.checked}/${pr.checklistStats.total})` : '';
|
|
182
|
+
label = `[Incomplete Checklist${stats}]`;
|
|
183
|
+
type = 'incomplete_checklist';
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
default:
|
|
187
|
+
// Defensive fallback for ActionReason values not explicitly handled
|
|
188
|
+
// above (e.g. ci_not_running, needs_rebase, missing_required_files).
|
|
189
|
+
// These aren't in reasonOrder today but this guards future additions.
|
|
190
|
+
warn('daily-logic', `Unhandled ActionReason "${reason}" for PR ${pr.url} — falling back to needs_response`);
|
|
191
|
+
label = `[${reason}]`;
|
|
192
|
+
type = 'needs_response';
|
|
193
|
+
}
|
|
194
|
+
issues.push({ type, pr, label });
|
|
179
195
|
}
|
|
180
196
|
}
|
|
181
197
|
return issues;
|
|
@@ -252,7 +268,7 @@ export function computeActionMenu(actionableIssues, capacity, commentedIssues =
|
|
|
252
268
|
* Format a brief one-liner summary for the action-first flow
|
|
253
269
|
*/
|
|
254
270
|
export function formatBriefSummary(digest, issueCount, issueResponseCount = 0) {
|
|
255
|
-
const attentionText = issueCount > 0 ? `${issueCount} need${issueCount === 1 ? 's' : ''} attention` : 'all
|
|
271
|
+
const attentionText = issueCount > 0 ? `${issueCount} need${issueCount === 1 ? 's' : ''} attention` : 'all on track';
|
|
256
272
|
const issueReplyText = issueResponseCount > 0 ? ` | ${issueResponseCount} issue repl${issueResponseCount === 1 ? 'y' : 'ies'}` : '';
|
|
257
273
|
return `\u{1F4CA} ${digest.summary.totalActivePRs} Active PRs | ${attentionText}${issueReplyText}`;
|
|
258
274
|
}
|
|
@@ -267,80 +283,21 @@ export function formatSummary(digest, capacity, issueResponses = []) {
|
|
|
267
283
|
lines.push(`\u{1F4CA} **${digest.summary.totalActivePRs} Active PRs** | ${digest.summary.totalMergedAllTime} Merged | ${digest.summary.mergeRate}% Merge Rate`);
|
|
268
284
|
lines.push('\u2713 Dashboard generated \u2014 say "open dashboard" to view in browser');
|
|
269
285
|
lines.push('');
|
|
270
|
-
//
|
|
271
|
-
if (digest.
|
|
272
|
-
lines.push('### \u274C
|
|
273
|
-
for (const pr of digest.
|
|
274
|
-
lines.push(`- [${pr.repo}#${pr.number}](${pr.url}): ${pr.title}`);
|
|
275
|
-
if (pr.failingCheckNames.length > 0) {
|
|
276
|
-
lines.push(` \u2514\u2500 Failing: ${pr.failingCheckNames.join(', ')}`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
lines.push('');
|
|
280
|
-
}
|
|
281
|
-
// Merge Conflicts
|
|
282
|
-
if (digest.mergeConflictPRs.length > 0) {
|
|
283
|
-
lines.push('### \u26A0\uFE0F Merge Conflicts');
|
|
284
|
-
for (const pr of digest.mergeConflictPRs) {
|
|
285
|
-
lines.push(`- [${pr.repo}#${pr.number}](${pr.url}): ${pr.title}`);
|
|
286
|
-
}
|
|
287
|
-
lines.push('');
|
|
288
|
-
}
|
|
289
|
-
// Needs Response
|
|
290
|
-
if (digest.prsNeedingResponse.length > 0) {
|
|
291
|
-
lines.push('### \u{1F4AC} Needs Response');
|
|
292
|
-
for (const pr of digest.prsNeedingResponse) {
|
|
293
|
-
const maintainer = pr.lastMaintainerComment?.author || 'maintainer';
|
|
294
|
-
lines.push(`- [${pr.repo}#${pr.number}](${pr.url}): ${pr.title}`);
|
|
295
|
-
lines.push(` \u2514\u2500 @${maintainer} commented`);
|
|
296
|
-
if (pr.maintainerActionHints.length > 0) {
|
|
297
|
-
const hintLabels = pr.maintainerActionHints.map(formatActionHint).join(', ');
|
|
298
|
-
lines.push(` \u2514\u2500 Action: ${hintLabels}`);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
lines.push('');
|
|
302
|
-
}
|
|
303
|
-
// Needs Changes (review requested changes, no new commits yet)
|
|
304
|
-
if (digest.needsChangesPRs.length > 0) {
|
|
305
|
-
lines.push('### \u{1F527} Needs Changes');
|
|
306
|
-
for (const pr of digest.needsChangesPRs) {
|
|
286
|
+
// Needs Addressing
|
|
287
|
+
if (digest.needsAddressingPRs.length > 0) {
|
|
288
|
+
lines.push('### \u274C Needs Addressing');
|
|
289
|
+
for (const pr of digest.needsAddressingPRs) {
|
|
307
290
|
lines.push(`- [${pr.repo}#${pr.number}](${pr.url}): ${pr.title}`);
|
|
308
|
-
lines.push(` \u2514\u2500
|
|
291
|
+
lines.push(` \u2514\u2500 ${pr.displayLabel} ${pr.displayDescription}`);
|
|
309
292
|
}
|
|
310
293
|
lines.push('');
|
|
311
294
|
}
|
|
312
|
-
//
|
|
313
|
-
if (digest.incompleteChecklistPRs.length > 0) {
|
|
314
|
-
lines.push('### \u{1F4CB} Incomplete Checklist');
|
|
315
|
-
for (const pr of digest.incompleteChecklistPRs) {
|
|
316
|
-
const stats = pr.checklistStats ? ` (${pr.checklistStats.checked}/${pr.checklistStats.total} checked)` : '';
|
|
317
|
-
lines.push(`- [${pr.repo}#${pr.number}](${pr.url}): ${pr.title}${stats}`);
|
|
318
|
-
}
|
|
319
|
-
lines.push('');
|
|
320
|
-
}
|
|
321
|
-
// Changes Addressed (waiting for maintainer re-review)
|
|
322
|
-
if (digest.changesAddressedPRs.length > 0) {
|
|
323
|
-
lines.push('### \u{1F4E4} Changes Addressed');
|
|
324
|
-
for (const pr of digest.changesAddressedPRs) {
|
|
325
|
-
const maintainer = pr.lastMaintainerComment?.author || 'maintainer';
|
|
326
|
-
lines.push(`- [${pr.repo}#${pr.number}](${pr.url}): ${pr.title}`);
|
|
327
|
-
lines.push(` \u2514\u2500 Waiting for @${maintainer} to re-review`);
|
|
328
|
-
}
|
|
329
|
-
lines.push('');
|
|
330
|
-
}
|
|
331
|
-
// Waiting on Maintainer (approved, no action needed from user)
|
|
295
|
+
// Waiting on Maintainer
|
|
332
296
|
if (digest.waitingOnMaintainerPRs.length > 0) {
|
|
333
297
|
lines.push('### \u23F3 Waiting on Maintainer');
|
|
334
298
|
for (const pr of digest.waitingOnMaintainerPRs) {
|
|
335
|
-
lines.push(`- [${pr.repo}#${pr.number}](${pr.url}): ${pr.title} (approved)`);
|
|
336
|
-
}
|
|
337
|
-
lines.push('');
|
|
338
|
-
}
|
|
339
|
-
// Healthy PRs
|
|
340
|
-
if (digest.healthyPRs.length > 0) {
|
|
341
|
-
lines.push('### \u2705 Healthy');
|
|
342
|
-
for (const pr of digest.healthyPRs) {
|
|
343
299
|
lines.push(`- [${pr.repo}#${pr.number}](${pr.url}): ${pr.title}`);
|
|
300
|
+
lines.push(` \u2514\u2500 ${pr.displayDescription}`);
|
|
344
301
|
}
|
|
345
302
|
lines.push('');
|
|
346
303
|
}
|
|
@@ -409,63 +366,19 @@ export function printDigest(digest, capacity, commentedIssues = []) {
|
|
|
409
366
|
console.log(`Merge Rate: ${digest.summary.mergeRate}%`);
|
|
410
367
|
console.log(`\nCapacity: ${capacity.hasCapacity ? '\u2705 Ready for new work' : '\u26A0\uFE0F Focus on existing work'}`);
|
|
411
368
|
console.log(` ${capacity.reason}\n`);
|
|
412
|
-
if (digest.
|
|
413
|
-
console.log('\u274C
|
|
414
|
-
for (const pr of digest.
|
|
415
|
-
console.log(` - ${pr.repo}#${pr.number}: ${pr.title}`);
|
|
416
|
-
if (pr.failingCheckNames.length > 0) {
|
|
417
|
-
console.log(` Failing: ${pr.failingCheckNames.join(', ')}`);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
console.log('');
|
|
421
|
-
}
|
|
422
|
-
if (digest.mergeConflictPRs.length > 0) {
|
|
423
|
-
console.log('\u26A0\uFE0F Merge Conflicts:');
|
|
424
|
-
for (const pr of digest.mergeConflictPRs) {
|
|
425
|
-
console.log(` - ${pr.repo}#${pr.number}: ${pr.title}`);
|
|
426
|
-
}
|
|
427
|
-
console.log('');
|
|
428
|
-
}
|
|
429
|
-
if (digest.prsNeedingResponse.length > 0) {
|
|
430
|
-
console.log('\u{1F4AC} Needs Response:');
|
|
431
|
-
for (const pr of digest.prsNeedingResponse) {
|
|
432
|
-
console.log(` - ${pr.repo}#${pr.number}: ${pr.title}`);
|
|
433
|
-
if (pr.maintainerActionHints.length > 0) {
|
|
434
|
-
const hintLabels = pr.maintainerActionHints.map(formatActionHint).join(', ');
|
|
435
|
-
console.log(` Action: ${hintLabels}`);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
console.log('');
|
|
439
|
-
}
|
|
440
|
-
if (digest.needsChangesPRs.length > 0) {
|
|
441
|
-
console.log('\u{1F527} Needs Changes:');
|
|
442
|
-
for (const pr of digest.needsChangesPRs) {
|
|
369
|
+
if (digest.needsAddressingPRs.length > 0) {
|
|
370
|
+
console.log('\u274C Needs Addressing:');
|
|
371
|
+
for (const pr of digest.needsAddressingPRs) {
|
|
443
372
|
console.log(` - ${pr.repo}#${pr.number}: ${pr.title}`);
|
|
444
|
-
console.log(`
|
|
445
|
-
}
|
|
446
|
-
console.log('');
|
|
447
|
-
}
|
|
448
|
-
if (digest.incompleteChecklistPRs.length > 0) {
|
|
449
|
-
console.log('\u{1F4CB} Incomplete Checklist:');
|
|
450
|
-
for (const pr of digest.incompleteChecklistPRs) {
|
|
451
|
-
const stats = pr.checklistStats ? ` (${pr.checklistStats.checked}/${pr.checklistStats.total} checked)` : '';
|
|
452
|
-
console.log(` - ${pr.repo}#${pr.number}: ${pr.title}${stats}`);
|
|
453
|
-
}
|
|
454
|
-
console.log('');
|
|
455
|
-
}
|
|
456
|
-
if (digest.changesAddressedPRs.length > 0) {
|
|
457
|
-
console.log('\u{1F4E4} Changes Addressed:');
|
|
458
|
-
for (const pr of digest.changesAddressedPRs) {
|
|
459
|
-
const maintainer = pr.lastMaintainerComment?.author || 'maintainer';
|
|
460
|
-
console.log(` - ${pr.repo}#${pr.number}: ${pr.title}`);
|
|
461
|
-
console.log(` Waiting for @${maintainer} to re-review`);
|
|
373
|
+
console.log(` ${pr.displayLabel} ${pr.displayDescription}`);
|
|
462
374
|
}
|
|
463
375
|
console.log('');
|
|
464
376
|
}
|
|
465
377
|
if (digest.waitingOnMaintainerPRs.length > 0) {
|
|
466
378
|
console.log('\u23F3 Waiting on Maintainer:');
|
|
467
379
|
for (const pr of digest.waitingOnMaintainerPRs) {
|
|
468
|
-
console.log(` - ${pr.repo}#${pr.number}: ${pr.title}
|
|
380
|
+
console.log(` - ${pr.repo}#${pr.number}: ${pr.title}`);
|
|
381
|
+
console.log(` ${pr.displayDescription}`);
|
|
469
382
|
}
|
|
470
383
|
console.log('');
|
|
471
384
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Display Utils - Human-readable display label computation for PR statuses.
|
|
3
3
|
* Extracted from PRMonitor to isolate presentation logic (#263).
|
|
4
|
+
*
|
|
5
|
+
* Uses two reason-keyed maps (ACTION_DISPLAY / WAIT_DISPLAY) instead of a
|
|
6
|
+
* single status-keyed map, reflecting the 2-status taxonomy where the
|
|
7
|
+
* granular reason lives in `actionReason` / `waitReason`.
|
|
4
8
|
*/
|
|
5
9
|
import { FetchedPR } from './types.js';
|
|
6
10
|
/** Compute display label and description for a FetchedPR (#79). */
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Display Utils - Human-readable display label computation for PR statuses.
|
|
3
3
|
* Extracted from PRMonitor to isolate presentation logic (#263).
|
|
4
|
+
*
|
|
5
|
+
* Uses two reason-keyed maps (ACTION_DISPLAY / WAIT_DISPLAY) instead of a
|
|
6
|
+
* single status-keyed map, reflecting the 2-status taxonomy where the
|
|
7
|
+
* granular reason lives in `actionReason` / `waitReason`.
|
|
4
8
|
*/
|
|
5
9
|
import { warn } from './logger.js';
|
|
6
|
-
const
|
|
7
|
-
/**
|
|
8
|
-
* Deterministic mapping from FetchedPRStatus -> human-readable display label (#79).
|
|
9
|
-
* Ensures consistent label text across sessions — agents no longer derive these.
|
|
10
|
-
*/
|
|
11
|
-
const STATUS_DISPLAY = {
|
|
10
|
+
const ACTION_DISPLAY = {
|
|
12
11
|
needs_response: {
|
|
13
12
|
label: '[Needs Response]',
|
|
14
13
|
description: (pr) => pr.lastMaintainerComment ? `@${pr.lastMaintainerComment.author} commented` : 'Maintainer awaiting response',
|
|
@@ -33,25 +32,20 @@ const STATUS_DISPLAY = {
|
|
|
33
32
|
return 'One or more CI checks are failing';
|
|
34
33
|
},
|
|
35
34
|
},
|
|
36
|
-
|
|
37
|
-
label: '[
|
|
38
|
-
description: (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
},
|
|
35
|
+
merge_conflict: {
|
|
36
|
+
label: '[Merge Conflict]',
|
|
37
|
+
description: () => 'PR has merge conflicts with the base branch',
|
|
38
|
+
},
|
|
39
|
+
incomplete_checklist: {
|
|
40
|
+
label: '[Incomplete Checklist]',
|
|
41
|
+
description: (pr) => pr.checklistStats
|
|
42
|
+
? `${pr.checklistStats.checked}/${pr.checklistStats.total} items checked`
|
|
43
|
+
: 'PR body has unchecked required checkboxes',
|
|
46
44
|
},
|
|
47
45
|
ci_not_running: {
|
|
48
46
|
label: '[CI Not Running]',
|
|
49
47
|
description: () => 'No CI checks have been triggered',
|
|
50
48
|
},
|
|
51
|
-
merge_conflict: {
|
|
52
|
-
label: '[Merge Conflict]',
|
|
53
|
-
description: () => 'PR has merge conflicts with the base branch',
|
|
54
|
-
},
|
|
55
49
|
needs_rebase: {
|
|
56
50
|
label: '[Needs Rebase]',
|
|
57
51
|
description: () => 'PR branch is significantly behind upstream',
|
|
@@ -60,48 +54,53 @@ const STATUS_DISPLAY = {
|
|
|
60
54
|
label: '[Missing Files]',
|
|
61
55
|
description: (pr) => pr.missingRequiredFiles ? `Missing: ${pr.missingRequiredFiles.join(', ')}` : 'Required files are missing',
|
|
62
56
|
},
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
-
changes_addressed: {
|
|
70
|
-
label: '[Changes Addressed]',
|
|
71
|
-
description: (pr) => pr.lastMaintainerComment
|
|
72
|
-
? `Waiting for @${pr.lastMaintainerComment.author} to re-review`
|
|
73
|
-
: 'Waiting for maintainer re-review',
|
|
74
|
-
},
|
|
75
|
-
waiting: {
|
|
76
|
-
label: '[Waiting]',
|
|
77
|
-
description: () => 'CI pending or awaiting review',
|
|
57
|
+
};
|
|
58
|
+
const WAIT_DISPLAY = {
|
|
59
|
+
pending_review: {
|
|
60
|
+
label: '[Waiting on Maintainer]',
|
|
61
|
+
description: () => 'Awaiting review',
|
|
78
62
|
},
|
|
79
|
-
|
|
63
|
+
pending_merge: {
|
|
80
64
|
label: '[Waiting on Maintainer]',
|
|
81
65
|
description: () => 'Approved and CI passes — waiting for merge',
|
|
82
66
|
},
|
|
83
|
-
|
|
84
|
-
label: '[
|
|
85
|
-
description: () =>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
67
|
+
changes_addressed: {
|
|
68
|
+
label: '[Waiting on Maintainer]',
|
|
69
|
+
description: (pr) => {
|
|
70
|
+
if (pr.hasUnrespondedComment && pr.lastMaintainerComment) {
|
|
71
|
+
return `Changes addressed — waiting for @${pr.lastMaintainerComment.author} to re-review`;
|
|
72
|
+
}
|
|
73
|
+
return 'Changes addressed — awaiting re-review';
|
|
74
|
+
},
|
|
90
75
|
},
|
|
91
|
-
|
|
92
|
-
label: '[
|
|
93
|
-
description: (pr) =>
|
|
76
|
+
ci_blocked: {
|
|
77
|
+
label: '[CI Blocked]',
|
|
78
|
+
description: (pr) => {
|
|
79
|
+
const checks = pr.classifiedChecks || [];
|
|
80
|
+
if (checks.length > 0 && checks.every((c) => c.category !== 'actionable')) {
|
|
81
|
+
const categories = [...new Set(checks.map((c) => c.category))];
|
|
82
|
+
return `All failing checks are non-actionable (${categories.join(', ')})`;
|
|
83
|
+
}
|
|
84
|
+
return 'CI checks are failing but no action is needed from you';
|
|
85
|
+
},
|
|
94
86
|
},
|
|
95
87
|
};
|
|
96
88
|
/** Compute display label and description for a FetchedPR (#79). */
|
|
97
89
|
export function computeDisplayLabel(pr) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
if (pr.status === 'needs_addressing' && pr.actionReason) {
|
|
91
|
+
const entry = ACTION_DISPLAY[pr.actionReason];
|
|
92
|
+
if (entry)
|
|
93
|
+
return { displayLabel: entry.label, displayDescription: entry.description(pr) };
|
|
94
|
+
}
|
|
95
|
+
if (pr.status === 'waiting_on_maintainer' && pr.waitReason) {
|
|
96
|
+
const entry = WAIT_DISPLAY[pr.waitReason];
|
|
97
|
+
if (entry)
|
|
98
|
+
return { displayLabel: entry.label, displayDescription: entry.description(pr) };
|
|
99
|
+
}
|
|
100
|
+
// Fallback for missing reason — log so we can identify data issues
|
|
101
|
+
warn('display-utils', `PR ${pr.url} has status "${pr.status}" but no matching reason (actionReason=${pr.actionReason}, waitReason=${pr.waitReason})`);
|
|
102
|
+
if (pr.status === 'needs_addressing') {
|
|
103
|
+
return { displayLabel: '[Needs Addressing]', displayDescription: 'Action required' };
|
|
102
104
|
}
|
|
103
|
-
return {
|
|
104
|
-
displayLabel: entry.label,
|
|
105
|
-
displayDescription: entry.description(pr),
|
|
106
|
-
};
|
|
105
|
+
return { displayLabel: '[Waiting on Maintainer]', displayDescription: 'Awaiting maintainer action' };
|
|
107
106
|
}
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Custom error type hierarchy for oss-autopilot.
|
|
3
3
|
* Provides structured error codes and specific error classes
|
|
4
4
|
* for different failure categories.
|
|
5
|
+
*
|
|
6
|
+
* Error strategy: Rate-limit and auth errors (429, 401, 403+rate-limit) always
|
|
7
|
+
* propagate to the caller via isRateLimitError/isRateLimitOrAuthError.
|
|
8
|
+
* Other errors degrade gracefully — modules return partial results and log warnings.
|
|
5
9
|
*/
|
|
6
10
|
/**
|
|
7
11
|
* Base error for all oss-autopilot errors.
|
|
@@ -31,3 +35,7 @@ export declare function errorMessage(e: unknown): string;
|
|
|
31
35
|
* Returns undefined if the error doesn't have a numeric `status` property.
|
|
32
36
|
*/
|
|
33
37
|
export declare function getHttpStatusCode(error: unknown): number | undefined;
|
|
38
|
+
/** Check if an error is a GitHub rate limit error (429 or rate-limit 403). */
|
|
39
|
+
export declare function isRateLimitError(error: unknown): boolean;
|
|
40
|
+
/** Return true for errors that should propagate (not degrade gracefully): rate limits, auth failures, abuse detection. */
|
|
41
|
+
export declare function isRateLimitOrAuthError(err: unknown): boolean;
|
package/dist/core/errors.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Custom error type hierarchy for oss-autopilot.
|
|
3
3
|
* Provides structured error codes and specific error classes
|
|
4
4
|
* for different failure categories.
|
|
5
|
+
*
|
|
6
|
+
* Error strategy: Rate-limit and auth errors (429, 401, 403+rate-limit) always
|
|
7
|
+
* propagate to the caller via isRateLimitError/isRateLimitOrAuthError.
|
|
8
|
+
* Other errors degrade gracefully — modules return partial results and log warnings.
|
|
5
9
|
*/
|
|
6
10
|
/**
|
|
7
11
|
* Base error for all oss-autopilot errors.
|
|
@@ -49,3 +53,25 @@ export function getHttpStatusCode(error) {
|
|
|
49
53
|
}
|
|
50
54
|
return undefined;
|
|
51
55
|
}
|
|
56
|
+
/** Check if an error is a GitHub rate limit error (429 or rate-limit 403). */
|
|
57
|
+
export function isRateLimitError(error) {
|
|
58
|
+
const status = getHttpStatusCode(error);
|
|
59
|
+
if (status === 429)
|
|
60
|
+
return true;
|
|
61
|
+
if (status === 403) {
|
|
62
|
+
const msg = errorMessage(error).toLowerCase();
|
|
63
|
+
return msg.includes('rate limit');
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
/** Return true for errors that should propagate (not degrade gracefully): rate limits, auth failures, abuse detection. */
|
|
68
|
+
export function isRateLimitOrAuthError(err) {
|
|
69
|
+
const status = getHttpStatusCode(err);
|
|
70
|
+
if (status === 401 || status === 429)
|
|
71
|
+
return true;
|
|
72
|
+
if (status === 403) {
|
|
73
|
+
const msg = errorMessage(err).toLowerCase();
|
|
74
|
+
return msg.includes('rate limit') || msg.includes('abuse detection');
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Extracted from PRMonitor to isolate statistics-gathering API calls (#263).
|
|
4
4
|
*/
|
|
5
5
|
import { Octokit } from '@octokit/rest';
|
|
6
|
-
import { ClosedPR, MergedPR } from './types.js';
|
|
6
|
+
import { ClosedPR, MergedPR, type StarFilter } from './types.js';
|
|
7
7
|
/** TTL for cached PR count results (24 hours — these stats change slowly). */
|
|
8
8
|
export declare const PR_COUNTS_CACHE_TTL_MS: number;
|
|
9
9
|
/** Return type shared by both merged and closed PR count functions. */
|
|
@@ -19,7 +19,7 @@ export declare function emptyPRCountsResult<R>(): PRCountsResult<R>;
|
|
|
19
19
|
* Fetch merged PR counts and latest merge dates per repository for the configured user.
|
|
20
20
|
* Also builds a monthly histogram of all merges for the contribution timeline.
|
|
21
21
|
*/
|
|
22
|
-
export declare function fetchUserMergedPRCounts(octokit: Octokit, githubUsername: string): Promise<PRCountsResult<{
|
|
22
|
+
export declare function fetchUserMergedPRCounts(octokit: Octokit, githubUsername: string, starFilter?: StarFilter): Promise<PRCountsResult<{
|
|
23
23
|
count: number;
|
|
24
24
|
lastMergedAt: string;
|
|
25
25
|
}>>;
|
|
@@ -27,7 +27,7 @@ export declare function fetchUserMergedPRCounts(octokit: Octokit, githubUsername
|
|
|
27
27
|
* Fetch closed-without-merge PR counts per repository for the configured user.
|
|
28
28
|
* Used to populate closedWithoutMergeCount in repo scores for accurate merge rate.
|
|
29
29
|
*/
|
|
30
|
-
export declare function fetchUserClosedPRCounts(octokit: Octokit, githubUsername: string): Promise<PRCountsResult<number>>;
|
|
30
|
+
export declare function fetchUserClosedPRCounts(octokit: Octokit, githubUsername: string, starFilter?: StarFilter): Promise<PRCountsResult<number>>;
|
|
31
31
|
/**
|
|
32
32
|
* Fetch PRs closed without merge in the last N days.
|
|
33
33
|
* Returns lightweight ClosedPR objects for surfacing in the daily digest.
|