@oss-autopilot/core 0.44.3 → 0.44.16
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 +99 -124
- package/dist/cli.bundle.cjs.map +4 -4
- package/dist/commands/daily.d.ts +6 -1
- package/dist/commands/daily.js +27 -5
- package/dist/commands/dashboard-data.d.ts +10 -2
- package/dist/commands/dashboard-data.js +35 -11
- package/dist/commands/dashboard-lifecycle.js +39 -2
- package/dist/commands/dashboard-scripts.d.ts +1 -1
- package/dist/commands/dashboard-scripts.js +4 -4
- package/dist/commands/dashboard-server.d.ts +2 -1
- package/dist/commands/dashboard-server.js +61 -53
- package/dist/commands/dashboard-templates.js +14 -68
- package/dist/commands/override.d.ts +21 -0
- package/dist/commands/override.js +35 -0
- 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/github-stats.d.ts +3 -3
- package/dist/core/github-stats.js +14 -7
- package/dist/core/issue-vetting.js +1 -1
- package/dist/core/pr-monitor.d.ts +26 -3
- package/dist/core/pr-monitor.js +103 -89
- 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 +50 -38
- package/dist/core/types.js +7 -0
- package/dist/formatters/json.d.ts +1 -13
- package/dist/formatters/json.js +1 -13
- package/package.json +2 -2
|
@@ -13,29 +13,15 @@ import { generateDashboardScripts } from './dashboard-scripts.js';
|
|
|
13
13
|
export { escapeHtml } from './dashboard-formatters.js';
|
|
14
14
|
export { buildDashboardStats } from './dashboard-data.js';
|
|
15
15
|
export function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state, issueResponses = []) {
|
|
16
|
-
const approachingDormantDays = state.config?.approachingDormantDays ?? 25;
|
|
17
16
|
const shelvedPRs = digest.shelvedPRs || [];
|
|
18
17
|
const autoUnshelvedPRs = digest.autoUnshelvedPRs || [];
|
|
19
18
|
const recentlyMerged = digest.recentlyMergedPRs || [];
|
|
20
19
|
const shelvedUrls = new Set(shelvedPRs.map((pr) => pr.url));
|
|
21
20
|
const activePRList = (digest.openPRs || []).filter((pr) => !shelvedUrls.has(pr.url));
|
|
22
21
|
// Action Required: contributor must do something
|
|
23
|
-
const actionRequired = [
|
|
24
|
-
...(digest.prsNeedingResponse || []),
|
|
25
|
-
...(digest.needsChangesPRs || []),
|
|
26
|
-
...(digest.ciFailingPRs || []),
|
|
27
|
-
...(digest.mergeConflictPRs || []),
|
|
28
|
-
...(digest.incompleteChecklistPRs || []),
|
|
29
|
-
...(digest.missingRequiredFilesPRs || []),
|
|
30
|
-
...(digest.needsRebasePRs || []),
|
|
31
|
-
];
|
|
22
|
+
const actionRequired = digest.needsAddressingPRs || [];
|
|
32
23
|
// Waiting on Others: informational, no contributor action needed
|
|
33
|
-
const waitingOnOthers = [
|
|
34
|
-
...(digest.changesAddressedPRs || []),
|
|
35
|
-
...(digest.waitingOnMaintainerPRs || []),
|
|
36
|
-
...(digest.ciBlockedPRs || []),
|
|
37
|
-
...(digest.ciNotRunningPRs || []),
|
|
38
|
-
];
|
|
24
|
+
const waitingOnOthers = digest.waitingOnMaintainerPRs || [];
|
|
39
25
|
return `<!DOCTYPE html>
|
|
40
26
|
<html lang="en">
|
|
41
27
|
<head>
|
|
@@ -127,22 +113,12 @@ export function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, month
|
|
|
127
113
|
<input type="text" class="filter-search" id="searchInput" placeholder="Search by PR title..." />
|
|
128
114
|
<select class="filter-select" id="statusFilter">
|
|
129
115
|
<option value="all">All Statuses</option>
|
|
130
|
-
<option value="needs-
|
|
131
|
-
<option value="
|
|
132
|
-
<option value="ci-failing">CI Failing</option>
|
|
133
|
-
<option value="conflict">Merge Conflict</option>
|
|
134
|
-
<option value="changes-addressed">Changes Addressed</option>
|
|
135
|
-
<option value="waiting-maintainer">Waiting on Maintainer</option>
|
|
136
|
-
<option value="ci-blocked">CI Blocked</option>
|
|
137
|
-
<option value="ci-not-running">CI Not Running</option>
|
|
138
|
-
<option value="incomplete-checklist">Incomplete Checklist</option>
|
|
139
|
-
<option value="missing-files">Missing Files</option>
|
|
140
|
-
<option value="needs-rebase">Needs Rebase</option>
|
|
116
|
+
<option value="needs-addressing">Needs Addressing</option>
|
|
117
|
+
<option value="waiting-on-maintainer">Waiting on Maintainer</option>
|
|
141
118
|
<option value="shelved">Shelved</option>
|
|
142
119
|
<option value="merged">Recently Merged</option>
|
|
143
120
|
<option value="closed">Recently Closed</option>
|
|
144
121
|
<option value="auto-unshelved">Auto-Unshelved</option>
|
|
145
|
-
<option value="active">Active (No Issues)</option>
|
|
146
122
|
</select>
|
|
147
123
|
<select class="filter-select" id="repoFilter">
|
|
148
124
|
<option value="all">All Repositories</option>
|
|
@@ -184,15 +160,7 @@ export function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, month
|
|
|
184
160
|
<span class="health-badge">${actionRequired.length} issue${actionRequired.length !== 1 ? 's' : ''}</span>
|
|
185
161
|
</div>
|
|
186
162
|
<div class="health-items">
|
|
187
|
-
${renderHealthItems(
|
|
188
|
-
? `@${escapeHtml(pr.lastMaintainerComment.author)}: ${truncateTitle(pr.lastMaintainerComment.body, 40)}`
|
|
189
|
-
: truncateTitle(pr.title))}
|
|
190
|
-
${renderHealthItems(digest.needsChangesPRs || [], 'needs-changes', SVG_ICONS.edit, 'Needs Changes', titleMeta)}
|
|
191
|
-
${renderHealthItems(digest.ciFailingPRs || [], 'ci-failing', SVG_ICONS.xCircle, 'CI Failing', titleMeta)}
|
|
192
|
-
${renderHealthItems(digest.mergeConflictPRs || [], 'conflict', SVG_ICONS.conflict, 'Merge Conflict', titleMeta)}
|
|
193
|
-
${renderHealthItems(digest.incompleteChecklistPRs || [], 'incomplete-checklist', SVG_ICONS.checklist, (pr) => `Incomplete Checklist${pr.checklistStats ? ` (${pr.checklistStats.checked}/${pr.checklistStats.total})` : ''}`, titleMeta)}
|
|
194
|
-
${renderHealthItems(digest.missingRequiredFilesPRs || [], 'missing-files', SVG_ICONS.file, 'Missing Required Files', (pr) => (pr.missingRequiredFiles ? escapeHtml(pr.missingRequiredFiles.join(', ')) : truncateTitle(pr.title)))}
|
|
195
|
-
${renderHealthItems(digest.needsRebasePRs || [], 'needs-rebase', SVG_ICONS.refresh, (pr) => `Needs Rebase${pr.commitsBehindUpstream ? ` (${pr.commitsBehindUpstream} behind)` : ''}`, titleMeta)}
|
|
163
|
+
${renderHealthItems(actionRequired, 'needs-addressing', SVG_ICONS.xCircle, (pr) => pr.displayLabel, (pr) => escapeHtml(pr.displayDescription))}
|
|
196
164
|
</div>
|
|
197
165
|
</section>
|
|
198
166
|
`
|
|
@@ -210,10 +178,7 @@ export function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, month
|
|
|
210
178
|
<span class="health-badge" style="background: var(--accent-info-dim); color: var(--accent-info);">${waitingOnOthers.length} PR${waitingOnOthers.length !== 1 ? 's' : ''}</span>
|
|
211
179
|
</div>
|
|
212
180
|
<div class="health-items">
|
|
213
|
-
${renderHealthItems(
|
|
214
|
-
${renderHealthItems(digest.waitingOnMaintainerPRs || [], 'waiting-maintainer', SVG_ICONS.clock, 'Waiting on Maintainer', titleMeta)}
|
|
215
|
-
${renderHealthItems(digest.ciBlockedPRs || [], 'ci-blocked', SVG_ICONS.lock, 'CI Blocked', titleMeta)}
|
|
216
|
-
${renderHealthItems(digest.ciNotRunningPRs || [], 'ci-not-running', SVG_ICONS.infoCircle, 'CI Not Running', titleMeta)}
|
|
181
|
+
${renderHealthItems(waitingOnOthers, 'waiting-on-maintainer', SVG_ICONS.clock, (pr) => pr.displayLabel, (pr) => escapeHtml(pr.displayDescription))}
|
|
217
182
|
</div>
|
|
218
183
|
</section>
|
|
219
184
|
`
|
|
@@ -230,7 +195,7 @@ export function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, month
|
|
|
230
195
|
<h2>Health Status</h2>
|
|
231
196
|
</div>
|
|
232
197
|
<div class="health-empty">
|
|
233
|
-
All PRs are
|
|
198
|
+
All PRs are on track - no CI failures, conflicts, or pending responses
|
|
234
199
|
</div>
|
|
235
200
|
</section>
|
|
236
201
|
`
|
|
@@ -313,7 +278,7 @@ export function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, month
|
|
|
313
278
|
<span class="health-badge" style="background: var(--accent-info-dim); color: var(--accent-info);">${autoUnshelvedPRs.length} unshelved</span>
|
|
314
279
|
</div>
|
|
315
280
|
<div class="health-items">
|
|
316
|
-
${renderHealthItems(autoUnshelvedPRs, 'auto-unshelved', SVG_ICONS.bell, (pr) =>
|
|
281
|
+
${renderHealthItems(autoUnshelvedPRs, 'auto-unshelved', SVG_ICONS.bell, (pr) => `Auto-Unshelved (${pr.status.replace(/_/g, ' ')})`, titleMeta)}
|
|
317
282
|
</div>
|
|
318
283
|
</section>
|
|
319
284
|
`
|
|
@@ -396,27 +361,14 @@ export function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, month
|
|
|
396
361
|
<div class="pr-list">
|
|
397
362
|
${activePRList
|
|
398
363
|
.map((pr) => {
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const isStale = pr.daysSinceActivity >= approachingDormantDays;
|
|
404
|
-
const itemClass = hasIssues ? 'has-issues' : isStale ? 'stale' : '';
|
|
405
|
-
const prStatus = pr.ciStatus === 'failing'
|
|
406
|
-
? 'ci-failing'
|
|
407
|
-
: pr.hasMergeConflict
|
|
408
|
-
? 'conflict'
|
|
409
|
-
: pr.hasUnrespondedComment && pr.status !== 'changes_addressed' && pr.status !== 'failing_ci'
|
|
410
|
-
? 'needs-response'
|
|
411
|
-
: pr.status === 'needs_changes'
|
|
412
|
-
? 'needs-changes'
|
|
413
|
-
: pr.status === 'changes_addressed'
|
|
414
|
-
? 'changes-addressed'
|
|
415
|
-
: 'active';
|
|
364
|
+
const isNeedsAddressing = pr.status === 'needs_addressing';
|
|
365
|
+
const isStale = pr.stalenessTier !== 'active';
|
|
366
|
+
const itemClass = isNeedsAddressing ? 'has-issues' : isStale ? 'stale' : '';
|
|
367
|
+
const prStatus = pr.status === 'needs_addressing' ? 'needs-addressing' : 'waiting-on-maintainer';
|
|
416
368
|
return `
|
|
417
369
|
<div class="pr-item ${itemClass}" data-status="${prStatus}" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
418
370
|
<div class="pr-status-indicator">
|
|
419
|
-
${
|
|
371
|
+
${isNeedsAddressing
|
|
420
372
|
? `
|
|
421
373
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
422
374
|
<circle cx="12" cy="12" r="10"/>
|
|
@@ -438,13 +390,7 @@ export function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, month
|
|
|
438
390
|
<span class="pr-repo">${escapeHtml(pr.repo)}#${pr.number}</span>
|
|
439
391
|
</div>
|
|
440
392
|
<div class="pr-badges">
|
|
441
|
-
|
|
442
|
-
${pr.ciStatus === 'passing' ? '<span class="badge badge-passing">CI Passing</span>' : ''}
|
|
443
|
-
${pr.ciStatus === 'pending' ? '<span class="badge badge-pending">CI Pending</span>' : ''}
|
|
444
|
-
${pr.hasMergeConflict ? '<span class="badge badge-conflict">Merge Conflict</span>' : ''}
|
|
445
|
-
${pr.hasUnrespondedComment && pr.status === 'changes_addressed' ? '<span class="badge badge-changes-addressed">Changes Addressed</span>' : ''}
|
|
446
|
-
${pr.hasUnrespondedComment && pr.status !== 'changes_addressed' && pr.status !== 'failing_ci' ? '<span class="badge badge-needs-response">Needs Response</span>' : ''}
|
|
447
|
-
${pr.reviewDecision === 'changes_requested' ? '<span class="badge badge-changes-requested">Changes Requested</span>' : ''}
|
|
393
|
+
<span class="badge ${isNeedsAddressing ? 'badge-ci-failing' : 'badge-passing'}">${escapeHtml(pr.displayLabel)}</span>
|
|
448
394
|
${isStale ? `<span class="badge badge-stale">${pr.daysSinceActivity}d inactive</span>` : ''}
|
|
449
395
|
</div>
|
|
450
396
|
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Override command
|
|
3
|
+
* Manually override a PR's status (needs_addressing ↔ waiting_on_maintainer).
|
|
4
|
+
* Overrides auto-clear when the PR has new activity.
|
|
5
|
+
*/
|
|
6
|
+
import type { FetchedPRStatus } from '../core/types.js';
|
|
7
|
+
export interface OverrideOutput {
|
|
8
|
+
url: string;
|
|
9
|
+
status: FetchedPRStatus;
|
|
10
|
+
}
|
|
11
|
+
export interface ClearOverrideOutput {
|
|
12
|
+
url: string;
|
|
13
|
+
cleared: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function runOverride(options: {
|
|
16
|
+
prUrl: string;
|
|
17
|
+
status: string;
|
|
18
|
+
}): Promise<OverrideOutput>;
|
|
19
|
+
export declare function runClearOverride(options: {
|
|
20
|
+
prUrl: string;
|
|
21
|
+
}): Promise<ClearOverrideOutput>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Override command
|
|
3
|
+
* Manually override a PR's status (needs_addressing ↔ waiting_on_maintainer).
|
|
4
|
+
* Overrides auto-clear when the PR has new activity.
|
|
5
|
+
*/
|
|
6
|
+
import { getStateManager } from '../core/index.js';
|
|
7
|
+
import { PR_URL_PATTERN, validateGitHubUrl, validateUrl } from './validation.js';
|
|
8
|
+
const VALID_STATUSES = ['needs_addressing', 'waiting_on_maintainer'];
|
|
9
|
+
export async function runOverride(options) {
|
|
10
|
+
validateUrl(options.prUrl);
|
|
11
|
+
validateGitHubUrl(options.prUrl, PR_URL_PATTERN, 'PR');
|
|
12
|
+
if (!VALID_STATUSES.includes(options.status)) {
|
|
13
|
+
throw new Error(`Invalid status "${options.status}". Must be one of: ${VALID_STATUSES.join(', ')}`);
|
|
14
|
+
}
|
|
15
|
+
const status = options.status;
|
|
16
|
+
const stateManager = getStateManager();
|
|
17
|
+
// Use current time as lastActivityAt — the CLI doesn't have cached PR data.
|
|
18
|
+
// This means the override will auto-clear on the next daily run if the PR's
|
|
19
|
+
// updatedAt is after this timestamp (which is the desired behavior: the override
|
|
20
|
+
// will persist until new activity occurs on the PR).
|
|
21
|
+
const lastActivityAt = new Date().toISOString();
|
|
22
|
+
stateManager.setStatusOverride(options.prUrl, status, lastActivityAt);
|
|
23
|
+
stateManager.save();
|
|
24
|
+
return { url: options.prUrl, status };
|
|
25
|
+
}
|
|
26
|
+
export async function runClearOverride(options) {
|
|
27
|
+
validateUrl(options.prUrl);
|
|
28
|
+
validateGitHubUrl(options.prUrl, PR_URL_PATTERN, 'PR');
|
|
29
|
+
const stateManager = getStateManager();
|
|
30
|
+
const cleared = stateManager.clearStatusOverride(options.prUrl);
|
|
31
|
+
if (cleared) {
|
|
32
|
+
stateManager.save();
|
|
33
|
+
}
|
|
34
|
+
return { url: options.prUrl, cleared };
|
|
35
|
+
}
|
|
@@ -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). */
|