@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
package/dist/commands/daily.d.ts
CHANGED
|
@@ -6,9 +6,14 @@
|
|
|
6
6
|
* Domain logic lives in src/core/daily-logic.ts; this file is a thin
|
|
7
7
|
* orchestration layer that wires up the phases and handles I/O.
|
|
8
8
|
*/
|
|
9
|
-
import { type DailyDigest, type CommentedIssue, type PRCheckFailure, type RepoGroup } from '../core/index.js';
|
|
9
|
+
import { type DailyDigest, type CommentedIssue, type PRCheckFailure, type RepoGroup, type AgentState, type StarFilter } from '../core/index.js';
|
|
10
10
|
import { type DailyOutput, type CapacityAssessment, type ActionableIssue, type ActionMenu } from '../formatters/json.js';
|
|
11
11
|
export { computeRepoSignals, groupPRsByRepo, assessCapacity, collectActionableIssues, computeActionMenu, toShelvedPRRef, formatBriefSummary, formatSummary, printDigest, CRITICAL_STATUSES, } from '../core/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Build a star filter from state for use in fetchUserPRCounts.
|
|
14
|
+
* Returns undefined if no star data is available (first run).
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildStarFilter(state: Readonly<AgentState>): StarFilter | undefined;
|
|
12
17
|
/**
|
|
13
18
|
* Internal result of the daily check, using full (non-deduplicated) types.
|
|
14
19
|
* Consumed by printDigest() (text mode) and converted to DailyOutput (JSON mode)
|
package/dist/commands/daily.js
CHANGED
|
@@ -7,25 +7,32 @@
|
|
|
7
7
|
* orchestration layer that wires up the phases and handles I/O.
|
|
8
8
|
*/
|
|
9
9
|
import { getStateManager, PRMonitor, IssueConversationMonitor, requireGitHubToken, CRITICAL_STATUSES, computeRepoSignals, groupPRsByRepo, assessCapacity, collectActionableIssues, computeActionMenu, toShelvedPRRef, formatBriefSummary, formatSummary, } from '../core/index.js';
|
|
10
|
-
import { errorMessage,
|
|
10
|
+
import { errorMessage, isRateLimitOrAuthError } from '../core/errors.js';
|
|
11
11
|
import { warn } from '../core/logger.js';
|
|
12
12
|
import { emptyPRCountsResult } from '../core/github-stats.js';
|
|
13
|
+
import { updateMonthlyAnalytics } from './dashboard-data.js';
|
|
13
14
|
import { deduplicateDigest, compactActionableIssues, compactRepoGroups, } from '../formatters/json.js';
|
|
14
15
|
const MODULE = 'daily';
|
|
15
|
-
/** Return true for errors that should propagate (not degrade gracefully). */
|
|
16
|
-
function isRateLimitOrAuthError(err) {
|
|
17
|
-
const status = getHttpStatusCode(err);
|
|
18
|
-
if (status === 401 || status === 429)
|
|
19
|
-
return true;
|
|
20
|
-
if (status === 403) {
|
|
21
|
-
const msg = errorMessage(err).toLowerCase();
|
|
22
|
-
return msg.includes('rate limit') || msg.includes('abuse detection');
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
16
|
// Re-export domain functions so existing consumers (tests, dashboard, startup)
|
|
27
17
|
// can continue importing from './daily.js' without changes.
|
|
28
18
|
export { computeRepoSignals, groupPRsByRepo, assessCapacity, collectActionableIssues, computeActionMenu, toShelvedPRRef, formatBriefSummary, formatSummary, printDigest, CRITICAL_STATUSES, } from '../core/index.js';
|
|
19
|
+
/**
|
|
20
|
+
* Build a star filter from state for use in fetchUserPRCounts.
|
|
21
|
+
* Returns undefined if no star data is available (first run).
|
|
22
|
+
*/
|
|
23
|
+
export function buildStarFilter(state) {
|
|
24
|
+
const minStars = state.config.minStars ?? 50;
|
|
25
|
+
const knownStarCounts = new Map();
|
|
26
|
+
for (const [repo, score] of Object.entries(state.repoScores)) {
|
|
27
|
+
if (score.stargazersCount !== undefined) {
|
|
28
|
+
knownStarCounts.set(repo, score.stargazersCount);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Only filter if we have some star data to work with
|
|
32
|
+
if (knownStarCounts.size === 0)
|
|
33
|
+
return undefined;
|
|
34
|
+
return { minStars, knownStarCounts };
|
|
35
|
+
}
|
|
29
36
|
// ---------------------------------------------------------------------------
|
|
30
37
|
// Phase functions
|
|
31
38
|
// ---------------------------------------------------------------------------
|
|
@@ -41,17 +48,21 @@ async function fetchPRData(prMonitor, token) {
|
|
|
41
48
|
if (failures.length > 0) {
|
|
42
49
|
warn(MODULE, `${failures.length} PR fetch(es) failed`);
|
|
43
50
|
}
|
|
51
|
+
// Build star filter from cached repoScores so low-star repos are excluded
|
|
52
|
+
// from merged/closed histograms (#576). Repos with no cached star data pass through.
|
|
53
|
+
const state = getStateManager().getState();
|
|
54
|
+
const starFilter = buildStarFilter(state);
|
|
44
55
|
// Fetch merged PR counts, closed PR counts, recently closed PRs, recently merged PRs, and commented issues in parallel
|
|
45
56
|
// All stats fetches are non-critical (cosmetic/scoring), so isolate their failure
|
|
46
57
|
const issueMonitor = new IssueConversationMonitor(token);
|
|
47
58
|
const [mergedResult, closedResult, recentlyClosedPRs, recentlyMergedPRs, issueConversationResult] = await Promise.all([
|
|
48
|
-
prMonitor.fetchUserMergedPRCounts().catch((err) => {
|
|
59
|
+
prMonitor.fetchUserMergedPRCounts(starFilter).catch((err) => {
|
|
49
60
|
if (isRateLimitOrAuthError(err))
|
|
50
61
|
throw err;
|
|
51
62
|
warn(MODULE, `Failed to fetch merged PR counts: ${errorMessage(err)}`);
|
|
52
63
|
return emptyPRCountsResult();
|
|
53
64
|
}),
|
|
54
|
-
prMonitor.fetchUserClosedPRCounts().catch((err) => {
|
|
65
|
+
prMonitor.fetchUserClosedPRCounts(starFilter).catch((err) => {
|
|
55
66
|
if (isRateLimitOrAuthError(err))
|
|
56
67
|
throw err;
|
|
57
68
|
warn(MODULE, `Failed to fetch closed PR counts: ${errorMessage(err)}`);
|
|
@@ -218,53 +229,6 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
|
|
|
218
229
|
warn(MODULE, `[ALL_TRUST_SYNCS_FAILED] All ${mergedCounts.size} trusted project sync(s) failed. This may indicate corrupted state.`);
|
|
219
230
|
}
|
|
220
231
|
}
|
|
221
|
-
/**
|
|
222
|
-
* Phase 3: Persist monthly chart analytics to state.
|
|
223
|
-
* Stores merged, closed, and combined opened counts per month.
|
|
224
|
-
* Each metric is isolated so partial failures don't produce inconsistent state.
|
|
225
|
-
*/
|
|
226
|
-
function updateAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerged, openedFromClosed) {
|
|
227
|
-
const stateManager = getStateManager();
|
|
228
|
-
// Store monthly chart data (non-critical — each metric isolated so partial failures don't leave inconsistent state).
|
|
229
|
-
// Guard: skip overwriting when the data is empty to avoid wiping existing chart data on transient API failures.
|
|
230
|
-
// An empty object means the fetch failed and fell back to emptyPRCountsResult(), so we preserve previous state.
|
|
231
|
-
try {
|
|
232
|
-
if (Object.keys(monthlyCounts).length > 0) {
|
|
233
|
-
stateManager.setMonthlyMergedCounts(monthlyCounts);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
catch (error) {
|
|
237
|
-
warn(MODULE, `Failed to store monthly merged counts: ${errorMessage(error)}`);
|
|
238
|
-
}
|
|
239
|
-
try {
|
|
240
|
-
if (Object.keys(monthlyClosedCounts).length > 0) {
|
|
241
|
-
stateManager.setMonthlyClosedCounts(monthlyClosedCounts);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
catch (error) {
|
|
245
|
-
warn(MODULE, `Failed to store monthly closed counts: ${errorMessage(error)}`);
|
|
246
|
-
}
|
|
247
|
-
try {
|
|
248
|
-
// Build combined monthly opened counts from merged + closed + currently-open PRs
|
|
249
|
-
const combinedOpenedCounts = { ...openedFromMerged };
|
|
250
|
-
for (const [month, count] of Object.entries(openedFromClosed)) {
|
|
251
|
-
combinedOpenedCounts[month] = (combinedOpenedCounts[month] || 0) + count;
|
|
252
|
-
}
|
|
253
|
-
// Add currently-open PR creation dates
|
|
254
|
-
for (const pr of prs) {
|
|
255
|
-
if (pr.createdAt) {
|
|
256
|
-
const month = pr.createdAt.slice(0, 7);
|
|
257
|
-
combinedOpenedCounts[month] = (combinedOpenedCounts[month] || 0) + 1;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (Object.keys(combinedOpenedCounts).length > 0) {
|
|
261
|
-
stateManager.setMonthlyOpenedCounts(combinedOpenedCounts);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
catch (error) {
|
|
265
|
-
warn(MODULE, `Failed to compute/store monthly opened counts: ${errorMessage(error)}`);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
232
|
/**
|
|
269
233
|
* Phase 4: Expire snoozes and partition PRs into active vs shelved buckets.
|
|
270
234
|
* Auto-unshelves PRs where maintainers have engaged, generates the digest,
|
|
@@ -300,8 +264,9 @@ function partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs) {
|
|
|
300
264
|
shelvedPRs.push(toShelvedPRRef(pr));
|
|
301
265
|
}
|
|
302
266
|
}
|
|
303
|
-
else if (pr.
|
|
304
|
-
// Dormant PRs are auto-shelved
|
|
267
|
+
else if (pr.stalenessTier === 'dormant' && !CRITICAL_STATUSES.has(pr.status)) {
|
|
268
|
+
// Dormant PRs are auto-shelved unless they need addressing
|
|
269
|
+
// (e.g. maintainer commented on a stale PR — it should resurface)
|
|
305
270
|
shelvedPRs.push(toShelvedPRRef(pr));
|
|
306
271
|
}
|
|
307
272
|
else {
|
|
@@ -459,7 +424,7 @@ async function executeDailyCheckInternal(token) {
|
|
|
459
424
|
// Phase 2: Update repo scores (signals, star counts, trust sync)
|
|
460
425
|
await updateRepoScores(prMonitor, prs, mergedCounts, closedCounts);
|
|
461
426
|
// Phase 3: Persist monthly analytics
|
|
462
|
-
|
|
427
|
+
updateMonthlyAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerged, openedFromClosed);
|
|
463
428
|
// Phase 4: Expire snoozes, partition PRs, generate and save digest
|
|
464
429
|
const { activePRs, shelvedPRs, digest } = partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs);
|
|
465
430
|
// Phase 5: Build structured output (capacity, dismiss filter, action menu)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Handles GitHub API calls, PR grouping, stats computation, and monthly chart data.
|
|
4
4
|
* Consumed by the dashboard HTTP server (dashboard-server.ts) for the SPA API.
|
|
5
5
|
*/
|
|
6
|
-
import type
|
|
6
|
+
import { type DailyDigest, type AgentState, type CommentedIssue } from '../core/types.js';
|
|
7
7
|
export interface DashboardStats {
|
|
8
8
|
activePRs: number;
|
|
9
9
|
shelvedPRs: number;
|
|
@@ -12,10 +12,31 @@ export interface DashboardStats {
|
|
|
12
12
|
mergeRate: string;
|
|
13
13
|
}
|
|
14
14
|
export declare function buildDashboardStats(digest: DailyDigest, state: Readonly<AgentState>): DashboardStats;
|
|
15
|
+
/**
|
|
16
|
+
* Merge fresh API counts into existing stored counts.
|
|
17
|
+
* Months present in the fresh data are updated; months only in the existing data are preserved.
|
|
18
|
+
* This prevents historical data loss when the API returns incomplete results
|
|
19
|
+
* (e.g. due to pagination limits or transient failures).
|
|
20
|
+
*/
|
|
21
|
+
export declare function mergeMonthlyCounts(existing: Record<string, number>, fresh: Record<string, number>): Record<string, number>;
|
|
22
|
+
/**
|
|
23
|
+
* Persist monthly chart analytics (merged, closed, opened) to state.
|
|
24
|
+
* Each metric is isolated so partial failures don't produce inconsistent state.
|
|
25
|
+
* Fresh API results are merged into existing data so historical months are preserved.
|
|
26
|
+
* Skips updating when fresh data is empty to avoid wiping chart data on transient API failures.
|
|
27
|
+
*/
|
|
28
|
+
export declare function updateMonthlyAnalytics(prs: Array<{
|
|
29
|
+
createdAt?: string;
|
|
30
|
+
}>, monthlyCounts: Record<string, number>, monthlyClosedCounts: Record<string, number>, openedFromMerged: Record<string, number>, openedFromClosed: Record<string, number>): void;
|
|
15
31
|
export interface DashboardFetchResult {
|
|
16
32
|
digest: DailyDigest;
|
|
17
33
|
commentedIssues: CommentedIssue[];
|
|
18
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Fetch fresh dashboard data from GitHub.
|
|
37
|
+
* Returns the digest and commented issues, updating state as a side effect.
|
|
38
|
+
* Throws if the fetch fails entirely (caller should fall back to cached data).
|
|
39
|
+
*/
|
|
19
40
|
export declare function fetchDashboardData(token: string): Promise<DashboardFetchResult>;
|
|
20
41
|
/**
|
|
21
42
|
* Compute PRs grouped by repository from a digest and state.
|
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
* Consumed by the dashboard HTTP server (dashboard-server.ts) for the SPA API.
|
|
5
5
|
*/
|
|
6
6
|
import { getStateManager, PRMonitor, IssueConversationMonitor } from '../core/index.js';
|
|
7
|
-
import { errorMessage,
|
|
7
|
+
import { errorMessage, isRateLimitOrAuthError } from '../core/errors.js';
|
|
8
|
+
import { warn } from '../core/logger.js';
|
|
8
9
|
import { emptyPRCountsResult } from '../core/github-stats.js';
|
|
9
|
-
import { toShelvedPRRef } from './daily.js';
|
|
10
|
+
import { toShelvedPRRef, buildStarFilter } from './daily.js';
|
|
11
|
+
const MODULE = 'dashboard-data';
|
|
12
|
+
import { isBelowMinStars, } from '../core/types.js';
|
|
10
13
|
export function buildDashboardStats(digest, state) {
|
|
11
14
|
const summary = digest.summary || {
|
|
12
15
|
totalActivePRs: 0,
|
|
@@ -22,58 +25,109 @@ export function buildDashboardStats(digest, state) {
|
|
|
22
25
|
mergeRate: `${(summary.mergeRate ?? 0).toFixed(1)}%`,
|
|
23
26
|
};
|
|
24
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Merge fresh API counts into existing stored counts.
|
|
30
|
+
* Months present in the fresh data are updated; months only in the existing data are preserved.
|
|
31
|
+
* This prevents historical data loss when the API returns incomplete results
|
|
32
|
+
* (e.g. due to pagination limits or transient failures).
|
|
33
|
+
*/
|
|
34
|
+
export function mergeMonthlyCounts(existing, fresh) {
|
|
35
|
+
const merged = { ...existing };
|
|
36
|
+
for (const [month, count] of Object.entries(fresh)) {
|
|
37
|
+
merged[month] = count;
|
|
38
|
+
}
|
|
39
|
+
return merged;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Persist monthly chart analytics (merged, closed, opened) to state.
|
|
43
|
+
* Each metric is isolated so partial failures don't produce inconsistent state.
|
|
44
|
+
* Fresh API results are merged into existing data so historical months are preserved.
|
|
45
|
+
* Skips updating when fresh data is empty to avoid wiping chart data on transient API failures.
|
|
46
|
+
*/
|
|
47
|
+
export function updateMonthlyAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerged, openedFromClosed) {
|
|
48
|
+
const stateManager = getStateManager();
|
|
49
|
+
const state = stateManager.getState();
|
|
50
|
+
try {
|
|
51
|
+
if (Object.keys(monthlyCounts).length > 0) {
|
|
52
|
+
stateManager.setMonthlyMergedCounts(mergeMonthlyCounts(state.monthlyMergedCounts || {}, monthlyCounts));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
warn(MODULE, `Failed to store monthly merged counts: ${errorMessage(error)}`);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
if (Object.keys(monthlyClosedCounts).length > 0) {
|
|
60
|
+
stateManager.setMonthlyClosedCounts(mergeMonthlyCounts(state.monthlyClosedCounts || {}, monthlyClosedCounts));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
warn(MODULE, `Failed to store monthly closed counts: ${errorMessage(error)}`);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const combinedOpenedCounts = { ...openedFromMerged };
|
|
68
|
+
for (const [month, count] of Object.entries(openedFromClosed)) {
|
|
69
|
+
combinedOpenedCounts[month] = (combinedOpenedCounts[month] || 0) + count;
|
|
70
|
+
}
|
|
71
|
+
for (const pr of prs) {
|
|
72
|
+
if (pr.createdAt) {
|
|
73
|
+
const month = pr.createdAt.slice(0, 7);
|
|
74
|
+
combinedOpenedCounts[month] = (combinedOpenedCounts[month] || 0) + 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (Object.keys(combinedOpenedCounts).length > 0) {
|
|
78
|
+
stateManager.setMonthlyOpenedCounts(mergeMonthlyCounts(state.monthlyOpenedCounts || {}, combinedOpenedCounts));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
warn(MODULE, `Failed to store monthly opened counts: ${errorMessage(error)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
25
85
|
/**
|
|
26
86
|
* Fetch fresh dashboard data from GitHub.
|
|
27
87
|
* Returns the digest and commented issues, updating state as a side effect.
|
|
28
88
|
* Throws if the fetch fails entirely (caller should fall back to cached data).
|
|
29
89
|
*/
|
|
30
|
-
function isRateLimitOrAuthError(err) {
|
|
31
|
-
const status = getHttpStatusCode(err);
|
|
32
|
-
if (status === 401 || status === 429)
|
|
33
|
-
return true;
|
|
34
|
-
if (status === 403) {
|
|
35
|
-
const msg = errorMessage(err).toLowerCase();
|
|
36
|
-
return msg.includes('rate limit') || msg.includes('abuse detection');
|
|
37
|
-
}
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
90
|
export async function fetchDashboardData(token) {
|
|
41
91
|
const stateManager = getStateManager();
|
|
42
92
|
const prMonitor = new PRMonitor(token);
|
|
43
93
|
const issueMonitor = new IssueConversationMonitor(token);
|
|
94
|
+
// Build star filter from cached repoScores (#576)
|
|
95
|
+
const starFilter = buildStarFilter(stateManager.getState());
|
|
44
96
|
const [{ prs, failures }, recentlyClosedPRs, recentlyMergedPRs, mergedResult, closedResult, fetchedIssues] = await Promise.all([
|
|
45
97
|
prMonitor.fetchUserOpenPRs(),
|
|
46
98
|
prMonitor.fetchRecentlyClosedPRs().catch((err) => {
|
|
47
99
|
if (isRateLimitOrAuthError(err))
|
|
48
100
|
throw err;
|
|
49
|
-
|
|
101
|
+
warn(MODULE, `Failed to fetch recently closed PRs: ${errorMessage(err)}`);
|
|
50
102
|
return [];
|
|
51
103
|
}),
|
|
52
104
|
prMonitor.fetchRecentlyMergedPRs().catch((err) => {
|
|
53
105
|
if (isRateLimitOrAuthError(err))
|
|
54
106
|
throw err;
|
|
55
|
-
|
|
107
|
+
warn(MODULE, `Failed to fetch recently merged PRs: ${errorMessage(err)}`);
|
|
56
108
|
return [];
|
|
57
109
|
}),
|
|
58
|
-
prMonitor.fetchUserMergedPRCounts().catch((err) => {
|
|
110
|
+
prMonitor.fetchUserMergedPRCounts(starFilter).catch((err) => {
|
|
59
111
|
if (isRateLimitOrAuthError(err))
|
|
60
112
|
throw err;
|
|
61
|
-
|
|
113
|
+
warn(MODULE, `Failed to fetch merged PR counts: ${errorMessage(err)}`);
|
|
62
114
|
return emptyPRCountsResult();
|
|
63
115
|
}),
|
|
64
|
-
prMonitor.fetchUserClosedPRCounts().catch((err) => {
|
|
116
|
+
prMonitor.fetchUserClosedPRCounts(starFilter).catch((err) => {
|
|
65
117
|
if (isRateLimitOrAuthError(err))
|
|
66
118
|
throw err;
|
|
67
|
-
|
|
119
|
+
warn(MODULE, `Failed to fetch closed PR counts: ${errorMessage(err)}`);
|
|
68
120
|
return emptyPRCountsResult();
|
|
69
121
|
}),
|
|
70
122
|
issueMonitor.fetchCommentedIssues().catch((error) => {
|
|
123
|
+
if (isRateLimitOrAuthError(error))
|
|
124
|
+
throw error;
|
|
71
125
|
const msg = errorMessage(error);
|
|
72
126
|
if (msg.includes('No GitHub username configured')) {
|
|
73
|
-
|
|
127
|
+
warn(MODULE, `Issue conversation tracking requires setup: ${msg}`);
|
|
74
128
|
}
|
|
75
129
|
else {
|
|
76
|
-
|
|
130
|
+
warn(MODULE, `Issue conversation fetch failed: ${msg}`);
|
|
77
131
|
}
|
|
78
132
|
return {
|
|
79
133
|
issues: [],
|
|
@@ -83,54 +137,20 @@ export async function fetchDashboardData(token) {
|
|
|
83
137
|
]);
|
|
84
138
|
const commentedIssues = fetchedIssues.issues;
|
|
85
139
|
if (fetchedIssues.failures.length > 0) {
|
|
86
|
-
|
|
140
|
+
warn(MODULE, `${fetchedIssues.failures.length} issue conversation check(s) failed`);
|
|
87
141
|
}
|
|
88
142
|
if (failures.length > 0) {
|
|
89
|
-
|
|
143
|
+
warn(MODULE, `${failures.length} PR fetch(es) failed`);
|
|
90
144
|
}
|
|
91
145
|
// Store monthly chart data (opened/merged/closed) so charts have data
|
|
92
146
|
const { monthlyCounts, monthlyOpenedCounts: openedFromMerged } = mergedResult;
|
|
93
147
|
const { monthlyCounts: monthlyClosedCounts, monthlyOpenedCounts: openedFromClosed } = closedResult;
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
if (Object.keys(monthlyCounts).length > 0) {
|
|
97
|
-
stateManager.setMonthlyMergedCounts(monthlyCounts);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
catch (error) {
|
|
101
|
-
console.error('[DASHBOARD] Failed to store monthly merged counts:', errorMessage(error));
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
if (Object.keys(monthlyClosedCounts).length > 0) {
|
|
105
|
-
stateManager.setMonthlyClosedCounts(monthlyClosedCounts);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
catch (error) {
|
|
109
|
-
console.error('[DASHBOARD] Failed to store monthly closed counts:', errorMessage(error));
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
const combinedOpenedCounts = { ...openedFromMerged };
|
|
113
|
-
for (const [month, count] of Object.entries(openedFromClosed)) {
|
|
114
|
-
combinedOpenedCounts[month] = (combinedOpenedCounts[month] || 0) + count;
|
|
115
|
-
}
|
|
116
|
-
for (const pr of prs) {
|
|
117
|
-
if (pr.createdAt) {
|
|
118
|
-
const month = pr.createdAt.slice(0, 7);
|
|
119
|
-
combinedOpenedCounts[month] = (combinedOpenedCounts[month] || 0) + 1;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (Object.keys(combinedOpenedCounts).length > 0) {
|
|
123
|
-
stateManager.setMonthlyOpenedCounts(combinedOpenedCounts);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
console.error('[DASHBOARD] Failed to store monthly opened counts:', errorMessage(error));
|
|
128
|
-
}
|
|
148
|
+
updateMonthlyAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerged, openedFromClosed);
|
|
129
149
|
const digest = prMonitor.generateDigest(prs, recentlyClosedPRs, recentlyMergedPRs);
|
|
130
150
|
// Apply shelve partitioning for display (auto-unshelve only runs in daily check)
|
|
131
|
-
// Dormant PRs are treated as shelved
|
|
151
|
+
// Dormant PRs are treated as shelved unless they need addressing
|
|
132
152
|
const shelvedUrls = new Set(stateManager.getState().config.shelvedPRUrls || []);
|
|
133
|
-
const freshShelved = prs.filter((pr) => shelvedUrls.has(pr.url) || pr.
|
|
153
|
+
const freshShelved = prs.filter((pr) => shelvedUrls.has(pr.url) || (pr.stalenessTier === 'dormant' && pr.status !== 'needs_addressing'));
|
|
134
154
|
digest.shelvedPRs = freshShelved.map(toShelvedPRRef);
|
|
135
155
|
digest.autoUnshelvedPRs = [];
|
|
136
156
|
digest.summary.totalActivePRs = prs.length - freshShelved.length;
|
|
@@ -139,9 +159,9 @@ export async function fetchDashboardData(token) {
|
|
|
139
159
|
stateManager.save();
|
|
140
160
|
}
|
|
141
161
|
catch (error) {
|
|
142
|
-
|
|
162
|
+
warn(MODULE, `Failed to save dashboard digest to state: ${errorMessage(error)}`);
|
|
143
163
|
}
|
|
144
|
-
|
|
164
|
+
warn(MODULE, `Refreshed: ${prs.length} PRs fetched`);
|
|
145
165
|
return { digest, commentedIssues };
|
|
146
166
|
}
|
|
147
167
|
/**
|
|
@@ -156,8 +176,11 @@ export function computePRsByRepo(digest, state) {
|
|
|
156
176
|
prsByRepo[pr.repo] = { active: 0, merged: 0, closed: 0 };
|
|
157
177
|
prsByRepo[pr.repo].active++;
|
|
158
178
|
}
|
|
159
|
-
// Add merged/closed counts from repo scores (historical data)
|
|
179
|
+
// Add merged/closed counts from repo scores (historical data), filtering by minStars (#576)
|
|
180
|
+
const minStars = state.config.minStars ?? 50;
|
|
160
181
|
for (const [repo, score] of Object.entries(state.repoScores || {})) {
|
|
182
|
+
if (isBelowMinStars(score.stargazersCount, minStars))
|
|
183
|
+
continue;
|
|
161
184
|
if (!prsByRepo[repo])
|
|
162
185
|
prsByRepo[repo] = { active: 0, merged: 0, closed: 0 };
|
|
163
186
|
prsByRepo[repo].merged = score.mergedPRCount;
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* and detecting whether a server is already running.
|
|
5
5
|
*/
|
|
6
6
|
import { spawn } from 'child_process';
|
|
7
|
-
import { findRunningDashboardServer, isDashboardServerRunning, readDashboardServerInfo } from './dashboard-server.js';
|
|
7
|
+
import { findRunningDashboardServer, isDashboardServerRunning, readDashboardServerInfo, removeDashboardServerInfo, } from './dashboard-server.js';
|
|
8
8
|
import { resolveAssetsDir } from './dashboard.js';
|
|
9
|
+
import { getCLIVersion } from '../core/index.js';
|
|
9
10
|
const DEFAULT_PORT = 3000;
|
|
10
11
|
const POLL_INTERVAL_MS = 200;
|
|
11
12
|
const MAX_POLL_ATTEMPTS = 25; // 5 seconds total
|
|
@@ -30,7 +31,43 @@ export async function launchDashboardServer(options) {
|
|
|
30
31
|
// 2. Check if a server is already running
|
|
31
32
|
const existing = await findRunningDashboardServer();
|
|
32
33
|
if (existing) {
|
|
33
|
-
|
|
34
|
+
// If the running server is from a different CLI version, kill it and relaunch
|
|
35
|
+
// so the dashboard uses the current version's code (#548)
|
|
36
|
+
const info = readDashboardServerInfo();
|
|
37
|
+
const currentVersion = getCLIVersion();
|
|
38
|
+
if (!info) {
|
|
39
|
+
// PID file disappeared between health check and now (race condition).
|
|
40
|
+
// Fall through to launch a new server.
|
|
41
|
+
}
|
|
42
|
+
else if (info.version && currentVersion !== '0.0.0' && info.version !== currentVersion) {
|
|
43
|
+
console.error(`[STARTUP] Dashboard server version mismatch (running: ${info.version}, current: ${currentVersion}). Restarting...`);
|
|
44
|
+
let killed = false;
|
|
45
|
+
try {
|
|
46
|
+
process.kill(info.pid, 'SIGTERM');
|
|
47
|
+
killed = true;
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
const code = err.code;
|
|
51
|
+
if (code === 'ESRCH') {
|
|
52
|
+
killed = true; // Already exited
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.error(`[STARTUP] Could not kill outdated dashboard (PID ${info.pid}): ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (killed) {
|
|
59
|
+
removeDashboardServerInfo();
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// Could not kill old server (e.g. EPERM); return it rather than
|
|
63
|
+
// attempting a doomed spawn on the same port.
|
|
64
|
+
return { url: existing.url, port: existing.port, alreadyRunning: true };
|
|
65
|
+
}
|
|
66
|
+
// Fall through to launch a new server
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
return { url: existing.url, port: existing.port, alreadyRunning: true };
|
|
70
|
+
}
|
|
34
71
|
}
|
|
35
72
|
// 3. Launch as detached child process
|
|
36
73
|
const port = options?.port ?? DEFAULT_PORT;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client-side JavaScript for the dashboard: theme toggle, filtering, Chart.js charts.
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
4
|
+
import { type DailyDigest, type AgentState } from '../core/types.js';
|
|
5
5
|
import type { DashboardStats } from './dashboard-data.js';
|
|
6
6
|
/** Generate the Chart.js JavaScript for the dashboard. */
|
|
7
7
|
export declare function generateDashboardScripts(stats: DashboardStats, monthlyMerged: Record<string, number>, monthlyClosed: Record<string, number>, monthlyOpened: Record<string, number>, digest: DailyDigest, state: Readonly<AgentState>): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client-side JavaScript for the dashboard: theme toggle, filtering, Chart.js charts.
|
|
3
3
|
*/
|
|
4
|
+
import { isBelowMinStars } from '../core/types.js';
|
|
4
5
|
/** Static client-side JS: theme toggle + filter/search logic. */
|
|
5
6
|
const THEME_AND_FILTER_SCRIPT = `
|
|
6
7
|
// === Theme Toggle ===
|
|
@@ -144,7 +145,7 @@ export function generateDashboardScripts(stats, monthlyMerged, monthlyClosed, mo
|
|
|
144
145
|
// Fail-open: repos without cached star data are shown (not excluded).
|
|
145
146
|
// Unlike issue-discovery (fail-closed), the dashboard shows the user's own
|
|
146
147
|
// contribution history — hiding repos just because a star fetch failed would be confusing.
|
|
147
|
-
if (score?.stargazersCount
|
|
148
|
+
if (isBelowMinStars(score?.stargazersCount, starThreshold))
|
|
148
149
|
return true;
|
|
149
150
|
return false;
|
|
150
151
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dashboard HTTP server.
|
|
3
3
|
* Serves the Preact SPA from packages/dashboard/dist/ and provides API endpoints
|
|
4
|
-
* for live data fetching and state mutations (shelve,
|
|
4
|
+
* for live data fetching and state mutations (shelve, unshelve, override, etc.).
|
|
5
5
|
*
|
|
6
6
|
* Uses Node's built-in http module — no Express/Fastify.
|
|
7
7
|
*/
|
|
@@ -15,6 +15,7 @@ export interface DashboardServerInfo {
|
|
|
15
15
|
pid: number;
|
|
16
16
|
port: number;
|
|
17
17
|
startedAt: string;
|
|
18
|
+
version?: string;
|
|
18
19
|
}
|
|
19
20
|
export declare function getDashboardPidPath(): string;
|
|
20
21
|
export declare function writeDashboardServerInfo(info: DashboardServerInfo): void;
|