@oss-autopilot/core 0.53.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.bundle.cjs +38 -38
- package/dist/commands/daily.js +15 -13
- package/dist/commands/dashboard-server.js +8 -0
- package/dist/core/ci-analysis.js +2 -0
- package/dist/core/display-utils.js +22 -2
- package/dist/core/github-stats.d.ts +1 -1
- package/dist/core/github-stats.js +1 -1
- package/dist/core/pr-monitor.d.ts +7 -4
- package/dist/core/pr-monitor.js +12 -10
- package/dist/core/repo-score-manager.js +2 -0
- package/dist/core/status-determination.d.ts +2 -0
- package/dist/core/status-determination.js +82 -22
- package/dist/core/types.d.ts +15 -1
- package/package.json +1 -1
package/dist/commands/daily.js
CHANGED
|
@@ -190,29 +190,31 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
|
|
|
190
190
|
if (signalUpdateFailures === repoSignals.size && repoSignals.size > 0) {
|
|
191
191
|
warn(MODULE, `[ALL_SIGNAL_UPDATES_FAILED] All ${repoSignals.size} signal update(s) failed. This may indicate corrupted state.`);
|
|
192
192
|
}
|
|
193
|
-
// Fetch
|
|
193
|
+
// Fetch metadata (stars + language) for all scored repos (used by dashboard minStars filter and merged PR view, #216, #677)
|
|
194
194
|
const allRepos = Object.keys(stateManager.getState().repoScores);
|
|
195
|
-
let
|
|
195
|
+
let repoMetadata;
|
|
196
196
|
try {
|
|
197
|
-
|
|
197
|
+
repoMetadata = await prMonitor.fetchRepoMetadata(allRepos);
|
|
198
198
|
}
|
|
199
199
|
catch (error) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
if (isRateLimitOrAuthError(error))
|
|
201
|
+
throw error;
|
|
202
|
+
warn(MODULE, `Failed to fetch repo metadata: ${errorMessage(error)}`);
|
|
203
|
+
warn(MODULE, 'Repos without cached metadata will be excluded from dashboard stats and metadata badges until fetched on the next successful run.');
|
|
204
|
+
repoMetadata = new Map();
|
|
203
205
|
}
|
|
204
|
-
let
|
|
205
|
-
for (const [repo, stars] of
|
|
206
|
+
let metadataUpdateFailures = 0;
|
|
207
|
+
for (const [repo, { stars, language }] of repoMetadata) {
|
|
206
208
|
try {
|
|
207
|
-
stateManager.updateRepoScore(repo, { stargazersCount: stars });
|
|
209
|
+
stateManager.updateRepoScore(repo, { stargazersCount: stars, language });
|
|
208
210
|
}
|
|
209
211
|
catch (error) {
|
|
210
|
-
|
|
211
|
-
warn(MODULE, `Failed to update
|
|
212
|
+
metadataUpdateFailures++;
|
|
213
|
+
warn(MODULE, `Failed to update metadata for ${repo}: ${errorMessage(error)}`);
|
|
212
214
|
}
|
|
213
215
|
}
|
|
214
|
-
if (
|
|
215
|
-
warn(MODULE, `[
|
|
216
|
+
if (metadataUpdateFailures === repoMetadata.size && repoMetadata.size > 0) {
|
|
217
|
+
warn(MODULE, `[ALL_METADATA_UPDATES_FAILED] All ${repoMetadata.size} metadata update(s) failed.`);
|
|
216
218
|
}
|
|
217
219
|
// Auto-sync trustedProjects from repos with merged PRs
|
|
218
220
|
let trustSyncFailures = 0;
|
|
@@ -53,6 +53,13 @@ function buildDashboardJson(digest, state, commentedIssues, allMergedPRs, allClo
|
|
|
53
53
|
const stats = buildDashboardStats(digest, state, filteredMergedPRs.length, filteredClosedPRs.length);
|
|
54
54
|
const dismissedIssues = state.config.dismissedIssues || {};
|
|
55
55
|
const issueResponses = commentedIssues.filter((i) => i.status === 'new_response' && !(i.url in dismissedIssues));
|
|
56
|
+
// Build repo metadata map from repoScores — omit repos without stars or language to avoid empty entries
|
|
57
|
+
const repoMetadata = {};
|
|
58
|
+
for (const [repo, score] of Object.entries(repoScores)) {
|
|
59
|
+
if (score.stargazersCount !== undefined || score.language !== undefined) {
|
|
60
|
+
repoMetadata[repo] = { stars: score.stargazersCount, language: score.language };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
56
63
|
return {
|
|
57
64
|
stats,
|
|
58
65
|
prsByRepo,
|
|
@@ -69,6 +76,7 @@ function buildDashboardJson(digest, state, commentedIssues, allMergedPRs, allClo
|
|
|
69
76
|
issueResponses,
|
|
70
77
|
allMergedPRs: filteredMergedPRs,
|
|
71
78
|
allClosedPRs: filteredClosedPRs,
|
|
79
|
+
repoMetadata,
|
|
72
80
|
};
|
|
73
81
|
}
|
|
74
82
|
/**
|
package/dist/core/ci-analysis.js
CHANGED
|
@@ -15,6 +15,7 @@ const FORK_LIMITATION_PATTERNS = [
|
|
|
15
15
|
/chromatic/i,
|
|
16
16
|
/percy/i,
|
|
17
17
|
/cloudflare pages/i,
|
|
18
|
+
/\binternal\b/i,
|
|
18
19
|
];
|
|
19
20
|
/**
|
|
20
21
|
* Known CI check name patterns that indicate authorization gates (#81).
|
|
@@ -30,6 +31,7 @@ const INFRASTRUCTURE_PATTERNS = [
|
|
|
30
31
|
/\bsetup\s+fail(ed|ure)?\b/i,
|
|
31
32
|
/\bservice\s*unavailable/i,
|
|
32
33
|
/\binfrastructure/i,
|
|
34
|
+
/\bblacksmith\b/i,
|
|
33
35
|
];
|
|
34
36
|
/**
|
|
35
37
|
* Classify a failing CI check as actionable, fork_limitation, auth_gate, or infrastructure (#81, #145).
|
|
@@ -84,13 +84,33 @@ const WAIT_DISPLAY = {
|
|
|
84
84
|
return 'CI checks are failing but no action is needed from you';
|
|
85
85
|
},
|
|
86
86
|
},
|
|
87
|
+
stale_ci_failure: {
|
|
88
|
+
label: '[Stale CI Failure]',
|
|
89
|
+
description: (pr) => `CI failing for ${pr.daysSinceActivity}+ days — likely pre-existing or non-actionable`,
|
|
90
|
+
},
|
|
87
91
|
};
|
|
92
|
+
/** Convert a bracketed display label like "[CI Failing]" to a plain lowercase string like "ci failing". */
|
|
93
|
+
function labelToPlainText(reason) {
|
|
94
|
+
const label = ACTION_DISPLAY[reason]?.label;
|
|
95
|
+
if (!label)
|
|
96
|
+
return reason;
|
|
97
|
+
return label.replace(/[[\]]/g, '').toLowerCase();
|
|
98
|
+
}
|
|
88
99
|
/** Compute display label and description for a FetchedPR (#79). */
|
|
89
100
|
export function computeDisplayLabel(pr) {
|
|
90
101
|
if (pr.status === 'needs_addressing' && pr.actionReason) {
|
|
91
102
|
const entry = ACTION_DISPLAY[pr.actionReason];
|
|
92
|
-
if (entry)
|
|
93
|
-
|
|
103
|
+
if (entry) {
|
|
104
|
+
let displayDescription = entry.description(pr);
|
|
105
|
+
// Append secondary action reasons when multiple issues exist (#675)
|
|
106
|
+
if (pr.actionReasons && pr.actionReasons.length > 1) {
|
|
107
|
+
const secondary = pr.actionReasons.filter((r) => r !== pr.actionReason).map(labelToPlainText);
|
|
108
|
+
if (secondary.length > 0) {
|
|
109
|
+
displayDescription += ` (also: ${secondary.join(', ')})`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { displayLabel: entry.label, displayDescription };
|
|
113
|
+
}
|
|
94
114
|
}
|
|
95
115
|
if (pr.status === 'waiting_on_maintainer' && pr.waitReason) {
|
|
96
116
|
const entry = WAIT_DISPLAY[pr.waitReason];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GitHub Stats - Fetching merged/closed PR counts
|
|
2
|
+
* GitHub Stats - Fetching merged/closed PR counts with star-based filtering.
|
|
3
3
|
* Extracted from PRMonitor to isolate statistics-gathering API calls (#263).
|
|
4
4
|
*/
|
|
5
5
|
import { Octokit } from '@octokit/rest';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GitHub Stats - Fetching merged/closed PR counts
|
|
2
|
+
* GitHub Stats - Fetching merged/closed PR counts with star-based filtering.
|
|
3
3
|
* Extracted from PRMonitor to isolate statistics-gathering API calls (#263).
|
|
4
4
|
*/
|
|
5
5
|
import { extractOwnerRepo, parseGitHubUrl, isOwnRepo } from './utils.js';
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - checklist-analysis.ts: PR body checklist analysis
|
|
10
10
|
* - maintainer-analysis.ts: Maintainer action hint extraction
|
|
11
11
|
* - display-utils.ts: Display label computation
|
|
12
|
-
* - github-stats.ts: Merged/closed PR counts and star
|
|
12
|
+
* - github-stats.ts: Merged/closed PR counts and star-based filtering
|
|
13
13
|
* - status-determination.ts: PR status classification logic
|
|
14
14
|
*/
|
|
15
15
|
import { FetchedPR, DailyDigest, ClosedPR, MergedPR, StarFilter } from './types.js';
|
|
@@ -68,10 +68,13 @@ export declare class PRMonitor {
|
|
|
68
68
|
*/
|
|
69
69
|
fetchUserClosedPRCounts(starFilter?: StarFilter): Promise<PRCountsResult<number>>;
|
|
70
70
|
/**
|
|
71
|
-
* Fetch
|
|
72
|
-
*
|
|
71
|
+
* Fetch metadata (star count and primary language) for a list of repositories.
|
|
72
|
+
* Both fields come from the same `repos.get()` call — zero additional API cost.
|
|
73
73
|
*/
|
|
74
|
-
|
|
74
|
+
fetchRepoMetadata(repos: string[]): Promise<Map<string, {
|
|
75
|
+
stars: number;
|
|
76
|
+
language: string | null;
|
|
77
|
+
}>>;
|
|
75
78
|
/**
|
|
76
79
|
* Fetch PRs closed without merge in the last N days.
|
|
77
80
|
* Delegates to github-stats module.
|
package/dist/core/pr-monitor.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - checklist-analysis.ts: PR body checklist analysis
|
|
10
10
|
* - maintainer-analysis.ts: Maintainer action hint extraction
|
|
11
11
|
* - display-utils.ts: Display label computation
|
|
12
|
-
* - github-stats.ts: Merged/closed PR counts and star
|
|
12
|
+
* - github-stats.ts: Merged/closed PR counts and star-based filtering
|
|
13
13
|
* - status-determination.ts: PR status classification logic
|
|
14
14
|
*/
|
|
15
15
|
import { getOctokit } from './github.js';
|
|
@@ -220,7 +220,7 @@ export class PRMonitor {
|
|
|
220
220
|
const classifiedChecks = classifyFailingChecks(failingCheckNames, failingCheckConclusions);
|
|
221
221
|
// Determine status
|
|
222
222
|
const hasActionableCIFailure = ciStatus === 'failing' && classifiedChecks.some((c) => c.category === 'actionable');
|
|
223
|
-
const { status, actionReason, waitReason, stalenessTier } = determineStatus({
|
|
223
|
+
const { status, actionReason, waitReason, stalenessTier, actionReasons } = determineStatus({
|
|
224
224
|
ciStatus,
|
|
225
225
|
hasMergeConflict,
|
|
226
226
|
hasUnrespondedComment,
|
|
@@ -246,6 +246,7 @@ export class PRMonitor {
|
|
|
246
246
|
actionReason,
|
|
247
247
|
waitReason,
|
|
248
248
|
stalenessTier,
|
|
249
|
+
actionReasons,
|
|
249
250
|
createdAt: ghPR.created_at,
|
|
250
251
|
updatedAt: ghPR.updated_at,
|
|
251
252
|
daysSinceActivity,
|
|
@@ -367,13 +368,13 @@ export class PRMonitor {
|
|
|
367
368
|
return fetchUserClosedPRCountsImpl(this.octokit, config.githubUsername, starFilter);
|
|
368
369
|
}
|
|
369
370
|
/**
|
|
370
|
-
* Fetch
|
|
371
|
-
*
|
|
371
|
+
* Fetch metadata (star count and primary language) for a list of repositories.
|
|
372
|
+
* Both fields come from the same `repos.get()` call — zero additional API cost.
|
|
372
373
|
*/
|
|
373
|
-
async
|
|
374
|
+
async fetchRepoMetadata(repos) {
|
|
374
375
|
if (repos.length === 0)
|
|
375
376
|
return new Map();
|
|
376
|
-
debug(MODULE, `Fetching
|
|
377
|
+
debug(MODULE, `Fetching repo metadata for ${repos.length} repos...`);
|
|
377
378
|
const results = new Map();
|
|
378
379
|
const cache = getHttpCache();
|
|
379
380
|
// Deduplicate repos to avoid fetching the same repo twice
|
|
@@ -394,17 +395,18 @@ export class PRMonitor {
|
|
|
394
395
|
repo: name,
|
|
395
396
|
headers,
|
|
396
397
|
}));
|
|
397
|
-
|
|
398
|
+
const metadata = { stars: data.stargazers_count, language: data.language ?? null };
|
|
399
|
+
return { repo, metadata };
|
|
398
400
|
}));
|
|
399
401
|
let chunkFailures = 0;
|
|
400
402
|
for (let j = 0; j < settled.length; j++) {
|
|
401
403
|
const result = settled[j];
|
|
402
404
|
if (result.status === 'fulfilled') {
|
|
403
|
-
results.set(result.value.repo, result.value.
|
|
405
|
+
results.set(result.value.repo, result.value.metadata);
|
|
404
406
|
}
|
|
405
407
|
else {
|
|
406
408
|
chunkFailures++;
|
|
407
|
-
warn(MODULE, `Failed to fetch
|
|
409
|
+
warn(MODULE, `Failed to fetch metadata for ${chunk[j]}: ${errorMessage(result.reason)}`);
|
|
408
410
|
}
|
|
409
411
|
}
|
|
410
412
|
// If entire chunk failed, likely a systemic issue (rate limit, auth, outage) — abort remaining
|
|
@@ -416,7 +418,7 @@ export class PRMonitor {
|
|
|
416
418
|
break;
|
|
417
419
|
}
|
|
418
420
|
}
|
|
419
|
-
debug(MODULE, `Fetched
|
|
421
|
+
debug(MODULE, `Fetched repo metadata for ${results.size}/${repos.length} repos`);
|
|
420
422
|
return results;
|
|
421
423
|
}
|
|
422
424
|
/**
|
|
@@ -94,6 +94,8 @@ export function updateRepoScore(state, repo, updates) {
|
|
|
94
94
|
repoScore.lastMergedAt = updates.lastMergedAt;
|
|
95
95
|
if (updates.stargazersCount !== undefined)
|
|
96
96
|
repoScore.stargazersCount = updates.stargazersCount;
|
|
97
|
+
if (updates.language !== undefined)
|
|
98
|
+
repoScore.language = updates.language;
|
|
97
99
|
if (updates.signals) {
|
|
98
100
|
repoScore.signals = { ...repoScore.signals, ...updates.signals };
|
|
99
101
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* its CI, review, merge-conflict, and timeline signals.
|
|
7
7
|
*/
|
|
8
8
|
import type { DetermineStatusInput, DetermineStatusResult } from './types.js';
|
|
9
|
+
/** Days of inactivity after which an actionable CI failure is demoted to stale_ci_failure (#675). */
|
|
10
|
+
export declare const STALE_CI_DEMOTION_DAYS = 5;
|
|
9
11
|
/**
|
|
10
12
|
* CI-fix bots that push commits as a direct result of the contributor's push (#568).
|
|
11
13
|
* Their commits represent contributor work and should count as addressing feedback.
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* granular action/wait reasons, and staleness tier for a single PR based on
|
|
6
6
|
* its CI, review, merge-conflict, and timeline signals.
|
|
7
7
|
*/
|
|
8
|
+
/** Days of inactivity after which an actionable CI failure is demoted to stale_ci_failure (#675). */
|
|
9
|
+
export const STALE_CI_DEMOTION_DAYS = 5;
|
|
8
10
|
/**
|
|
9
11
|
* CI-fix bots that push commits as a direct result of the contributor's push (#568).
|
|
10
12
|
* Their commits represent contributor work and should count as addressing feedback.
|
|
@@ -43,35 +45,89 @@ export function isCommitAfterComment(commitDate, commentDate) {
|
|
|
43
45
|
}
|
|
44
46
|
return commitMs - commentMs >= MIN_RESPONSE_GAP_MS;
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the latest commit date, filtering out non-contributor commits (#547, #568).
|
|
50
|
+
* Returns undefined when the commit was by a non-contributor or when no date is available.
|
|
51
|
+
*/
|
|
52
|
+
function resolveContributorCommitDate(input) {
|
|
53
|
+
const { latestCommitDate, latestCommitAuthor, contributorUsername } = input;
|
|
54
|
+
if (!latestCommitDate)
|
|
55
|
+
return undefined;
|
|
56
|
+
return isContributorCommit(latestCommitAuthor, contributorUsername) ? latestCommitDate : undefined;
|
|
57
|
+
}
|
|
58
|
+
/** Check whether an unresponded comment has been addressed by a subsequent contributor commit. */
|
|
59
|
+
function isCommentAddressedByCommit(commitDate, commentDate, changesRequestedDate) {
|
|
60
|
+
if (!commitDate || !commentDate)
|
|
61
|
+
return false;
|
|
62
|
+
if (!isCommitAfterComment(commitDate, commentDate))
|
|
63
|
+
return false;
|
|
64
|
+
// Safety net (#431): if a CHANGES_REQUESTED review came after the commit, it's not addressed
|
|
65
|
+
if (changesRequestedDate && commitDate < changesRequestedDate)
|
|
66
|
+
return false;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/** Check whether a changes_requested review has been addressed by a subsequent contributor commit. */
|
|
70
|
+
function isChangesAddressedByCommit(commitDate, changesRequestedDate) {
|
|
71
|
+
if (!commitDate || !changesRequestedDate)
|
|
72
|
+
return false;
|
|
73
|
+
return commitDate >= changesRequestedDate;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Collect all applicable action reasons independently, without short-circuiting (#675).
|
|
77
|
+
* Used alongside the priority-based decision tree to surface secondary issues.
|
|
78
|
+
*/
|
|
79
|
+
function collectAllActionReasons(input) {
|
|
80
|
+
const { ciStatus, hasMergeConflict, hasUnrespondedComment, hasIncompleteChecklist, reviewDecision, lastMaintainerCommentDate, latestChangesRequestedDate, hasActionableCIFailure = true, } = input;
|
|
81
|
+
const commitDate = resolveContributorCommitDate(input);
|
|
82
|
+
const reasons = [];
|
|
83
|
+
if (hasUnrespondedComment &&
|
|
84
|
+
!isCommentAddressedByCommit(commitDate, lastMaintainerCommentDate, latestChangesRequestedDate)) {
|
|
85
|
+
reasons.push('needs_response');
|
|
86
|
+
}
|
|
87
|
+
if (reviewDecision === 'changes_requested' &&
|
|
88
|
+
latestChangesRequestedDate &&
|
|
89
|
+
!isChangesAddressedByCommit(commitDate, latestChangesRequestedDate)) {
|
|
90
|
+
reasons.push('needs_changes');
|
|
91
|
+
}
|
|
92
|
+
if (ciStatus === 'failing' && hasActionableCIFailure) {
|
|
93
|
+
reasons.push('failing_ci');
|
|
94
|
+
}
|
|
95
|
+
if (hasMergeConflict) {
|
|
96
|
+
reasons.push('merge_conflict');
|
|
97
|
+
}
|
|
98
|
+
if (hasIncompleteChecklist) {
|
|
99
|
+
reasons.push('incomplete_checklist');
|
|
100
|
+
}
|
|
101
|
+
return reasons.length > 0 ? reasons : undefined;
|
|
102
|
+
}
|
|
46
103
|
/**
|
|
47
104
|
* Determine the overall status of a PR based on its signals.
|
|
48
105
|
*/
|
|
49
106
|
export function determineStatus(input) {
|
|
50
|
-
const
|
|
107
|
+
const primary = determinePrimaryStatus(input);
|
|
108
|
+
const actionReasons = collectAllActionReasons(input);
|
|
109
|
+
if (actionReasons) {
|
|
110
|
+
return { ...primary, actionReasons };
|
|
111
|
+
}
|
|
112
|
+
return primary;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Priority-based decision tree for the primary status classification.
|
|
116
|
+
* Returns the single highest-priority status; `determineStatus` augments
|
|
117
|
+
* this with the full `actionReasons` array.
|
|
118
|
+
*/
|
|
119
|
+
function determinePrimaryStatus(input) {
|
|
120
|
+
const { ciStatus, hasMergeConflict, hasUnrespondedComment, hasIncompleteChecklist, reviewDecision, daysSinceActivity, dormantThreshold, approachingThreshold, lastMaintainerCommentDate, latestChangesRequestedDate, hasActionableCIFailure = true, } = input;
|
|
51
121
|
// Compute staleness tier (independent of status)
|
|
52
122
|
let stalenessTier = 'active';
|
|
53
123
|
if (daysSinceActivity >= dormantThreshold)
|
|
54
124
|
stalenessTier = 'dormant';
|
|
55
125
|
else if (daysSinceActivity >= approachingThreshold)
|
|
56
126
|
stalenessTier = 'approaching_dormant';
|
|
57
|
-
|
|
58
|
-
// CI bot (#547, #568). Non-contributor commits (maintainer merge commits,
|
|
59
|
-
// GitHub suggestion commits) should not mask unaddressed feedback.
|
|
60
|
-
const latestCommitDate = rawCommitDate && isContributorCommit(latestCommitAuthor, contributorUsername) ? rawCommitDate : undefined;
|
|
127
|
+
const commitDate = resolveContributorCommitDate(input);
|
|
61
128
|
// Priority order: needs_addressing (response/changes/ci/conflict/checklist) > waiting_on_maintainer (review/merge/addressed/ci_blocked)
|
|
62
129
|
if (hasUnrespondedComment) {
|
|
63
|
-
|
|
64
|
-
// the changes have been addressed — waiting for maintainer re-review.
|
|
65
|
-
// Require a minimum 2-minute gap to avoid false positives from race
|
|
66
|
-
// conditions (pushing while review is being submitted) (#547).
|
|
67
|
-
if (latestCommitDate &&
|
|
68
|
-
lastMaintainerCommentDate &&
|
|
69
|
-
isCommitAfterComment(latestCommitDate, lastMaintainerCommentDate)) {
|
|
70
|
-
// Safety net (#431): if a CHANGES_REQUESTED review was submitted after
|
|
71
|
-
// the commit, the maintainer still expects changes — don't mask it
|
|
72
|
-
if (latestChangesRequestedDate && latestCommitDate < latestChangesRequestedDate) {
|
|
73
|
-
return { status: 'needs_addressing', actionReason: 'needs_response', stalenessTier };
|
|
74
|
-
}
|
|
130
|
+
if (isCommentAddressedByCommit(commitDate, lastMaintainerCommentDate, latestChangesRequestedDate)) {
|
|
75
131
|
if (ciStatus === 'failing' && hasActionableCIFailure)
|
|
76
132
|
return { status: 'needs_addressing', actionReason: 'failing_ci', stalenessTier };
|
|
77
133
|
// Non-actionable CI failures (infrastructure, fork, auth) don't block changes_addressed —
|
|
@@ -81,9 +137,8 @@ export function determineStatus(input) {
|
|
|
81
137
|
return { status: 'needs_addressing', actionReason: 'needs_response', stalenessTier };
|
|
82
138
|
}
|
|
83
139
|
// Review requested changes but no unresponded comment.
|
|
84
|
-
// If the latest commit is before the review, the contributor hasn't addressed it yet.
|
|
85
140
|
if (reviewDecision === 'changes_requested' && latestChangesRequestedDate) {
|
|
86
|
-
if (!
|
|
141
|
+
if (!isChangesAddressedByCommit(commitDate, latestChangesRequestedDate)) {
|
|
87
142
|
return { status: 'needs_addressing', actionReason: 'needs_changes', stalenessTier };
|
|
88
143
|
}
|
|
89
144
|
// Commit is after review — changes have been addressed
|
|
@@ -93,9 +148,14 @@ export function determineStatus(input) {
|
|
|
93
148
|
return { status: 'waiting_on_maintainer', waitReason: 'changes_addressed', stalenessTier };
|
|
94
149
|
}
|
|
95
150
|
if (ciStatus === 'failing') {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
151
|
+
if (hasActionableCIFailure) {
|
|
152
|
+
// Demote stale CI failures: if failing for 5+ days with no activity, likely pre-existing (#675)
|
|
153
|
+
if (daysSinceActivity >= STALE_CI_DEMOTION_DAYS) {
|
|
154
|
+
return { status: 'waiting_on_maintainer', waitReason: 'stale_ci_failure', stalenessTier };
|
|
155
|
+
}
|
|
156
|
+
return { status: 'needs_addressing', actionReason: 'failing_ci', stalenessTier };
|
|
157
|
+
}
|
|
158
|
+
return { status: 'waiting_on_maintainer', waitReason: 'ci_blocked', stalenessTier };
|
|
99
159
|
}
|
|
100
160
|
if (hasMergeConflict) {
|
|
101
161
|
return { status: 'needs_addressing', actionReason: 'merge_conflict', stalenessTier };
|
package/dist/core/types.d.ts
CHANGED
|
@@ -67,6 +67,8 @@ export interface DetermineStatusResult {
|
|
|
67
67
|
actionReason?: ActionReason;
|
|
68
68
|
waitReason?: WaitReason;
|
|
69
69
|
stalenessTier: StalenessTier;
|
|
70
|
+
/** All applicable action reasons, ordered by priority. */
|
|
71
|
+
actionReasons?: ActionReason[];
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
72
74
|
* Granular reason why a PR needs addressing (contributor's turn).
|
|
@@ -77,7 +79,7 @@ export interface DetermineStatusResult {
|
|
|
77
79
|
*/
|
|
78
80
|
export type ActionReason = 'needs_response' | 'needs_changes' | 'failing_ci' | 'merge_conflict' | 'incomplete_checklist' | 'ci_not_running' | 'needs_rebase' | 'missing_required_files';
|
|
79
81
|
/** Granular reason why a PR is waiting on the maintainer. */
|
|
80
|
-
export type WaitReason = 'pending_review' | 'pending_merge' | 'changes_addressed' | 'ci_blocked';
|
|
82
|
+
export type WaitReason = 'pending_review' | 'pending_merge' | 'changes_addressed' | 'ci_blocked' | 'stale_ci_failure';
|
|
81
83
|
/** How stale is the PR based on days since activity. Orthogonal to status. */
|
|
82
84
|
export type StalenessTier = 'active' | 'approaching_dormant' | 'dormant';
|
|
83
85
|
/**
|
|
@@ -110,6 +112,8 @@ export interface FetchedPR {
|
|
|
110
112
|
actionReason?: ActionReason;
|
|
111
113
|
/** Granular reason for waiting_on_maintainer status. Undefined when needs_addressing. */
|
|
112
114
|
waitReason?: WaitReason;
|
|
115
|
+
/** All applicable action reasons, ordered by priority. Primary reason is first. */
|
|
116
|
+
actionReasons?: ActionReason[];
|
|
113
117
|
/** How stale the PR is based on activity age. Independent of status — a PR can be both needs_addressing and dormant. */
|
|
114
118
|
stalenessTier: StalenessTier;
|
|
115
119
|
/** Human-readable status label for consistent display (#79). E.g., "[CI Failing]", "[Needs Response]". */
|
|
@@ -264,6 +268,8 @@ export interface RepoScore {
|
|
|
264
268
|
signals: RepoSignals;
|
|
265
269
|
/** GitHub star count, fetched during daily check for dashboard filtering. */
|
|
266
270
|
stargazersCount?: number;
|
|
271
|
+
/** Primary programming language of the repo, fetched during daily check. */
|
|
272
|
+
language?: string | null;
|
|
267
273
|
}
|
|
268
274
|
/** Full set of qualitative signals about a repo's maintainer culture. */
|
|
269
275
|
export interface RepoSignals {
|
|
@@ -285,6 +291,14 @@ export interface RepoScoreUpdate {
|
|
|
285
291
|
lastMergedAt?: string;
|
|
286
292
|
signals?: Partial<RepoSignals>;
|
|
287
293
|
stargazersCount?: number;
|
|
294
|
+
/** Primary programming language of the repo. */
|
|
295
|
+
language?: string | null;
|
|
296
|
+
}
|
|
297
|
+
/** Repo metadata entry used in dashboard API responses. Shared between server and SPA. */
|
|
298
|
+
export interface RepoMetadataEntry {
|
|
299
|
+
/** Star count, derived from RepoScore.stargazersCount. */
|
|
300
|
+
stars?: number;
|
|
301
|
+
language?: string | null;
|
|
288
302
|
}
|
|
289
303
|
/**
|
|
290
304
|
* Event types recorded in the {@link AgentState} audit log.
|