@oss-autopilot/core 0.53.1 → 0.55.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 +63 -63
- package/dist/commands/comments.js +0 -1
- package/dist/commands/config.js +45 -5
- package/dist/commands/daily.js +197 -162
- package/dist/commands/dashboard-data.js +37 -30
- package/dist/commands/dashboard-server.js +8 -1
- package/dist/commands/dismiss.js +0 -6
- package/dist/commands/init.js +0 -1
- package/dist/commands/local-repos.js +1 -2
- package/dist/commands/move.js +12 -11
- package/dist/commands/setup.d.ts +2 -1
- package/dist/commands/setup.js +166 -130
- package/dist/commands/shelve.js +10 -10
- package/dist/commands/startup.js +30 -14
- package/dist/core/ci-analysis.d.ts +6 -0
- package/dist/core/ci-analysis.js +91 -12
- package/dist/core/daily-logic.js +24 -33
- 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/index.d.ts +2 -1
- package/dist/core/index.js +2 -1
- package/dist/core/issue-discovery.d.ts +7 -44
- package/dist/core/issue-discovery.js +83 -188
- package/dist/core/issue-eligibility.d.ts +35 -0
- package/dist/core/issue-eligibility.js +126 -0
- package/dist/core/issue-vetting.d.ts +6 -21
- package/dist/core/issue-vetting.js +15 -279
- package/dist/core/pr-monitor.d.ts +14 -16
- package/dist/core/pr-monitor.js +26 -90
- package/dist/core/repo-health.d.ts +24 -0
- package/dist/core/repo-health.js +193 -0
- package/dist/core/repo-score-manager.js +2 -0
- package/dist/core/search-phases.d.ts +55 -0
- package/dist/core/search-phases.js +155 -0
- package/dist/core/state.d.ts +11 -0
- package/dist/core/state.js +63 -4
- package/dist/core/status-determination.d.ts +2 -0
- package/dist/core/status-determination.js +82 -22
- package/dist/core/types.d.ts +23 -2
- package/dist/core/types.js +7 -0
- package/dist/formatters/json.d.ts +1 -1
- package/package.json +1 -1
|
@@ -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
|
@@ -25,7 +25,7 @@ export interface ClassifiedCheck {
|
|
|
25
25
|
category: CIFailureCategory;
|
|
26
26
|
conclusion?: string;
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
28
|
+
/** CI status result returned by getCIStatus(). */
|
|
29
29
|
export interface CIStatusResult {
|
|
30
30
|
status: CIStatus;
|
|
31
31
|
failingCheckNames: string[];
|
|
@@ -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.
|
|
@@ -454,6 +468,8 @@ export interface AgentConfig {
|
|
|
454
468
|
languages: string[];
|
|
455
469
|
/** GitHub labels to filter issues by (e.g., `["good first issue", "help wanted"]`). */
|
|
456
470
|
labels: string[];
|
|
471
|
+
/** Issue scope tiers to search (e.g., `["beginner", "intermediate"]`). When set, scope tier labels are merged with custom `labels`. When absent, only `labels` is used (legacy behavior). */
|
|
472
|
+
scope?: IssueScope[];
|
|
457
473
|
/** Repos to exclude from issue discovery/search, in `"owner/repo"` format. */
|
|
458
474
|
excludeRepos: string[];
|
|
459
475
|
/** Organizations to exclude from issue discovery/search (case-insensitive match on owner segment). */
|
|
@@ -484,6 +500,8 @@ export interface AgentConfig {
|
|
|
484
500
|
dismissedIssues?: Record<string, string>;
|
|
485
501
|
/** Manual status overrides for PRs. Maps PR URL to override metadata. Auto-clears when the PR has new activity. */
|
|
486
502
|
statusOverrides?: Record<string, StatusOverride>;
|
|
503
|
+
/** Path to the user's curated issue list file. Replaces config.md as the primary source for detectIssueList(). */
|
|
504
|
+
issueListPath?: string;
|
|
487
505
|
/** Project categories the user is interested in (e.g., devtools, nonprofit). Used to prioritize search results. */
|
|
488
506
|
projectCategories?: ProjectCategory[];
|
|
489
507
|
/** GitHub organizations the user wants to prioritize in issue search. Org names only (not owner/repo). */
|
|
@@ -528,6 +546,9 @@ export declare const DEFAULT_CONFIG: AgentConfig;
|
|
|
528
546
|
export declare const INITIAL_STATE: AgentState;
|
|
529
547
|
export declare const PROJECT_CATEGORIES: readonly ["nonprofit", "devtools", "infrastructure", "web-frameworks", "data-ml", "education"];
|
|
530
548
|
export type ProjectCategory = (typeof PROJECT_CATEGORIES)[number];
|
|
549
|
+
export declare const ISSUE_SCOPES: readonly ["beginner", "intermediate", "advanced"];
|
|
550
|
+
export type IssueScope = (typeof ISSUE_SCOPES)[number];
|
|
551
|
+
export declare const SCOPE_LABELS: Record<IssueScope, string[]>;
|
|
531
552
|
/** Priority tier for issue search results. Ordered: merged_pr > preferred_org > starred > normal. */
|
|
532
553
|
export type SearchPriority = 'merged_pr' | 'preferred_org' | 'starred' | 'normal';
|
|
533
554
|
export interface IssueCandidate {
|
package/dist/core/types.js
CHANGED
|
@@ -49,3 +49,10 @@ export const PROJECT_CATEGORIES = [
|
|
|
49
49
|
'data-ml',
|
|
50
50
|
'education',
|
|
51
51
|
];
|
|
52
|
+
// -- Issue scope types --
|
|
53
|
+
export const ISSUE_SCOPES = ['beginner', 'intermediate', 'advanced'];
|
|
54
|
+
export const SCOPE_LABELS = {
|
|
55
|
+
beginner: ['good first issue', 'help wanted', 'easy', 'up-for-grabs', 'first-timers-only', 'beginner'],
|
|
56
|
+
intermediate: ['enhancement', 'feature', 'feature-request', 'contributions welcome'],
|
|
57
|
+
advanced: ['proposal', 'RFC', 'accepted', 'design'],
|
|
58
|
+
};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { FetchedPR, DailyDigest, AgentState, RepoGroup, CommentedIssue, ShelvedPRRef } from '../core/types.js';
|
|
6
6
|
import type { ContributionStats } from '../core/stats.js';
|
|
7
7
|
import type { PRCheckFailure } from '../core/pr-monitor.js';
|
|
8
|
-
import type { SearchPriority } from '../core/
|
|
8
|
+
import type { SearchPriority } from '../core/types.js';
|
|
9
9
|
export interface JsonOutput<T = unknown> {
|
|
10
10
|
success: boolean;
|
|
11
11
|
data?: T;
|