@oss-autopilot/core 0.50.0 → 0.51.1
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 +44 -98
- package/dist/cli.bundle.cjs +43 -45
- package/dist/cli.bundle.cjs.map +4 -4
- package/dist/commands/daily.d.ts +1 -1
- package/dist/commands/daily.js +5 -42
- package/dist/commands/dashboard-server.d.ts +1 -1
- package/dist/commands/dashboard-server.js +31 -37
- package/dist/commands/dismiss.d.ts +1 -1
- package/dist/commands/dismiss.js +4 -4
- package/dist/commands/index.d.ts +3 -5
- package/dist/commands/index.js +2 -4
- package/dist/commands/move.d.ts +16 -0
- package/dist/commands/move.js +56 -0
- package/dist/commands/shelve.d.ts +4 -0
- package/dist/commands/shelve.js +8 -2
- package/dist/core/daily-logic.d.ts +1 -1
- package/dist/core/daily-logic.js +1 -3
- package/dist/core/pr-monitor.d.ts +2 -27
- package/dist/core/pr-monitor.js +4 -110
- package/dist/core/state.d.ts +15 -40
- package/dist/core/state.js +80 -93
- package/dist/core/status-determination.d.ts +35 -0
- package/dist/core/status-determination.js +112 -0
- package/dist/core/types.d.ts +10 -11
- package/dist/core/types.js +0 -1
- package/package.json +1 -1
- package/dist/commands/override.d.ts +0 -21
- package/dist/commands/override.js +0 -35
- package/dist/commands/snooze.d.ts +0 -24
- package/dist/commands/snooze.js +0 -40
package/dist/commands/daily.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface DailyCheckResult {
|
|
|
41
41
|
* 1. fetchPRData — fetch open PRs, merged/closed counts, issues
|
|
42
42
|
* 2. updateRepoScores — update signals, star counts, trust in state
|
|
43
43
|
* 3. updateAnalytics — store monthly chart data
|
|
44
|
-
* 4. partitionPRs —
|
|
44
|
+
* 4. partitionPRs — shelve/unshelve, generate digest
|
|
45
45
|
* 5. generateDigestOutput — capacity, dismiss filter, action menu assembly
|
|
46
46
|
*/
|
|
47
47
|
export declare function executeDailyCheck(token: string): Promise<DailyOutput>;
|
package/dist/commands/daily.js
CHANGED
|
@@ -230,25 +230,12 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
/**
|
|
233
|
-
* Phase 4:
|
|
233
|
+
* Phase 4: Partition PRs into active vs shelved buckets.
|
|
234
234
|
* Auto-unshelves PRs where maintainers have engaged, generates the digest,
|
|
235
235
|
* and persists state.
|
|
236
236
|
*/
|
|
237
237
|
function partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs) {
|
|
238
238
|
const stateManager = getStateManager();
|
|
239
|
-
// Expire any snoozes that have passed their expiresAt timestamp.
|
|
240
|
-
// Non-critical: corrupted snooze entries should not abort the daily check.
|
|
241
|
-
try {
|
|
242
|
-
const expiredSnoozes = stateManager.expireSnoozes();
|
|
243
|
-
if (expiredSnoozes.length > 0) {
|
|
244
|
-
const urls = expiredSnoozes.map((url) => ` - ${url}`).join('\n');
|
|
245
|
-
warn(MODULE, `${expiredSnoozes.length} snoozed PR(s) expired and will resurface:\n${urls}`);
|
|
246
|
-
stateManager.save();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
warn(MODULE, `Failed to expire/persist snoozes: ${errorMessage(error)}`);
|
|
251
|
-
}
|
|
252
239
|
// Apply dashboard/CLI status overrides before partitioning.
|
|
253
240
|
// This ensures PRs reclassified in the dashboard (e.g., "Need Attention" → "Waiting")
|
|
254
241
|
// are respected by the CLI pipeline.
|
|
@@ -328,31 +315,7 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
|
|
|
328
315
|
});
|
|
329
316
|
const issueResponses = filteredCommentedIssues.filter((i) => i.status === 'new_response');
|
|
330
317
|
const summary = formatSummary(digest, capacity, issueResponses);
|
|
331
|
-
|
|
332
|
-
// Filter dismissed PRs: suppress if dismissed after last activity, auto-undismiss if new activity (#416, #468)
|
|
333
|
-
const nonDismissedPRs = activePRs.filter((pr) => {
|
|
334
|
-
const dismissedAt = stateManager.getIssueDismissedAt(pr.url);
|
|
335
|
-
if (!dismissedAt)
|
|
336
|
-
return true; // Not dismissed — include
|
|
337
|
-
const activityTime = new Date(pr.updatedAt).getTime();
|
|
338
|
-
const dismissTime = new Date(dismissedAt).getTime();
|
|
339
|
-
if (isNaN(activityTime) || isNaN(dismissTime)) {
|
|
340
|
-
// Invalid timestamp — fail open (include PR to be safe) without
|
|
341
|
-
// permanently removing dismiss record (may be a transient data issue)
|
|
342
|
-
warn(MODULE, `Invalid timestamp in PR dismiss check for ${pr.url}, including PR`);
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
if (activityTime > dismissTime) {
|
|
346
|
-
// New activity after dismiss — auto-undismiss and resurface
|
|
347
|
-
warn(MODULE, `Auto-undismissing PR ${pr.url}: new activity at ${pr.updatedAt} after dismiss at ${dismissedAt}`);
|
|
348
|
-
stateManager.undismissIssue(pr.url);
|
|
349
|
-
hasAutoUndismissed = true;
|
|
350
|
-
return true;
|
|
351
|
-
}
|
|
352
|
-
// Still dismissed (last activity is at or before dismiss timestamp)
|
|
353
|
-
return false;
|
|
354
|
-
});
|
|
355
|
-
// Persist auto-undismiss state changes (issue + PR combined into one save)
|
|
318
|
+
// Persist auto-undismiss state changes for issues
|
|
356
319
|
if (hasAutoUndismissed) {
|
|
357
320
|
try {
|
|
358
321
|
stateManager.save();
|
|
@@ -361,7 +324,7 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
|
|
|
361
324
|
warn(MODULE, `Failed to persist auto-undismissed state: ${errorMessage(error)}`);
|
|
362
325
|
}
|
|
363
326
|
}
|
|
364
|
-
const actionableIssues = collectActionableIssues(
|
|
327
|
+
const actionableIssues = collectActionableIssues(activePRs, previousLastDigestAt);
|
|
365
328
|
digest.summary.totalNeedingAttention = actionableIssues.length;
|
|
366
329
|
const briefSummary = formatBriefSummary(digest, actionableIssues.length, issueResponses.length);
|
|
367
330
|
const actionMenu = computeActionMenu(actionableIssues, capacity, filteredCommentedIssues);
|
|
@@ -410,7 +373,7 @@ function toDailyOutput(result) {
|
|
|
410
373
|
* 1. fetchPRData — fetch open PRs, merged/closed counts, issues
|
|
411
374
|
* 2. updateRepoScores — update signals, star counts, trust in state
|
|
412
375
|
* 3. updateAnalytics — store monthly chart data
|
|
413
|
-
* 4. partitionPRs —
|
|
376
|
+
* 4. partitionPRs — shelve/unshelve, generate digest
|
|
414
377
|
* 5. generateDigestOutput — capacity, dismiss filter, action menu assembly
|
|
415
378
|
*/
|
|
416
379
|
export async function executeDailyCheck(token) {
|
|
@@ -432,7 +395,7 @@ async function executeDailyCheckInternal(token) {
|
|
|
432
395
|
// Capture lastDigestAt BEFORE Phase 4 overwrites it with the current run's timestamp.
|
|
433
396
|
// Used by collectActionableIssues to determine which PRs are "new" (created since last digest).
|
|
434
397
|
const previousLastDigestAt = getStateManager().getState().lastDigestAt;
|
|
435
|
-
// Phase 4:
|
|
398
|
+
// Phase 4: Partition PRs, generate and save digest
|
|
436
399
|
const { activePRs, shelvedPRs, digest } = partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs);
|
|
437
400
|
// Phase 5: Build structured output (capacity, dismiss filter, action menu)
|
|
438
401
|
return generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, failures, previousLastDigestAt);
|
|
@@ -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 (
|
|
4
|
+
* for live data fetching and state mutations (PR state transitions, issue dismiss).
|
|
5
5
|
*
|
|
6
6
|
* Uses Node's built-in http module — no Express/Fastify.
|
|
7
7
|
*/
|
|
@@ -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 (
|
|
4
|
+
* for live data fetching and state mutations (PR state transitions, issue dismiss).
|
|
5
5
|
*
|
|
6
6
|
* Uses Node's built-in http module — no Express/Fastify.
|
|
7
7
|
*/
|
|
@@ -11,7 +11,7 @@ import * as path from 'path';
|
|
|
11
11
|
import { getStateManager, getGitHubToken, getCLIVersion, applyStatusOverrides } from '../core/index.js';
|
|
12
12
|
import { errorMessage, ValidationError } from '../core/errors.js';
|
|
13
13
|
import { warn } from '../core/logger.js';
|
|
14
|
-
import { validateUrl, validateGitHubUrl, PR_URL_PATTERN,
|
|
14
|
+
import { validateUrl, validateGitHubUrl, PR_URL_PATTERN, ISSUE_URL_PATTERN } from './validation.js';
|
|
15
15
|
import { fetchDashboardData, computePRsByRepo, computeTopRepos, getMonthlyData, buildDashboardStats, storedToMergedPRs, storedToClosedPRs, } from './dashboard-data.js';
|
|
16
16
|
import { openInBrowser } from './startup.js';
|
|
17
17
|
import { writeDashboardServerInfo, removeDashboardServerInfo } from './dashboard-process.js';
|
|
@@ -20,12 +20,7 @@ import { isBelowMinStars, } from '../core/types.js';
|
|
|
20
20
|
// Re-export process management functions for backward compatibility
|
|
21
21
|
export { getDashboardPidPath, writeDashboardServerInfo, readDashboardServerInfo, removeDashboardServerInfo, isDashboardServerRunning, findRunningDashboardServer, } from './dashboard-process.js';
|
|
22
22
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
23
|
-
const VALID_ACTIONS = new Set([
|
|
24
|
-
'shelve',
|
|
25
|
-
'unshelve',
|
|
26
|
-
'override_status',
|
|
27
|
-
'dismiss_issue_response',
|
|
28
|
-
]);
|
|
23
|
+
const VALID_ACTIONS = new Set(['move', 'dismiss_issue_response']);
|
|
29
24
|
const MODULE = 'dashboard-server';
|
|
30
25
|
const MAX_BODY_BYTES = 10_240;
|
|
31
26
|
const REQUEST_TIMEOUT_MS = 30_000;
|
|
@@ -170,7 +165,7 @@ export async function startDashboardServer(options) {
|
|
|
170
165
|
// ── Rate limiters ───────────────────────────────────────────────────────
|
|
171
166
|
const dataLimiter = new RateLimiter({ maxRequests: 30, windowMs: 60_000 }); // 30/min
|
|
172
167
|
const actionLimiter = new RateLimiter({ maxRequests: 10, windowMs: 60_000 }); // 10/min
|
|
173
|
-
const refreshLimiter = new RateLimiter({ maxRequests:
|
|
168
|
+
const refreshLimiter = new RateLimiter({ maxRequests: 6, windowMs: 60_000 }); // 6/min
|
|
174
169
|
// ── Request handler ──────────────────────────────────────────────────────
|
|
175
170
|
const server = http.createServer(async (req, res) => {
|
|
176
171
|
const method = req.method || 'GET';
|
|
@@ -184,6 +179,16 @@ export async function startDashboardServer(options) {
|
|
|
184
179
|
sendError(res, 429, 'Too many requests');
|
|
185
180
|
return;
|
|
186
181
|
}
|
|
182
|
+
// Re-read state.json if CLI commands modified it externally
|
|
183
|
+
if (stateManager.reloadIfChanged()) {
|
|
184
|
+
try {
|
|
185
|
+
cachedJsonData = buildDashboardJson(cachedDigest, stateManager.getState(), cachedCommentedIssues);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
warn(MODULE, `Failed to rebuild dashboard data after state reload: ${errorMessage(error)}`);
|
|
189
|
+
// Intentional: serve previous cachedJsonData rather than returning 500
|
|
190
|
+
}
|
|
191
|
+
}
|
|
187
192
|
sendJson(res, 200, cachedJsonData);
|
|
188
193
|
return;
|
|
189
194
|
}
|
|
@@ -232,6 +237,8 @@ export async function startDashboardServer(options) {
|
|
|
232
237
|
server.requestTimeout = REQUEST_TIMEOUT_MS;
|
|
233
238
|
// ── POST /api/action handler ─────────────────────────────────────────────
|
|
234
239
|
async function handleAction(req, res) {
|
|
240
|
+
// Reload state before mutating to avoid overwriting external CLI changes
|
|
241
|
+
stateManager.reloadIfChanged();
|
|
235
242
|
let body;
|
|
236
243
|
try {
|
|
237
244
|
const raw = await readBody(req);
|
|
@@ -250,11 +257,11 @@ export async function startDashboardServer(options) {
|
|
|
250
257
|
sendError(res, 400, 'Missing or invalid "url" field');
|
|
251
258
|
return;
|
|
252
259
|
}
|
|
253
|
-
// Validate URL format —
|
|
260
|
+
// Validate URL format — move is PR-only, dismiss_issue_response is issue-only.
|
|
254
261
|
const isDismiss = body.action === 'dismiss_issue_response';
|
|
255
262
|
try {
|
|
256
263
|
validateUrl(body.url);
|
|
257
|
-
validateGitHubUrl(body.url, isDismiss ?
|
|
264
|
+
validateGitHubUrl(body.url, isDismiss ? ISSUE_URL_PATTERN : PR_URL_PATTERN, isDismiss ? 'issue' : 'PR');
|
|
258
265
|
}
|
|
259
266
|
catch (err) {
|
|
260
267
|
if (err instanceof ValidationError) {
|
|
@@ -266,35 +273,20 @@ export async function startDashboardServer(options) {
|
|
|
266
273
|
}
|
|
267
274
|
return;
|
|
268
275
|
}
|
|
269
|
-
// Validate override_status-specific fields
|
|
270
|
-
if (body.action === 'override_status') {
|
|
271
|
-
if (!body.status || (body.status !== 'needs_addressing' && body.status !== 'waiting_on_maintainer')) {
|
|
272
|
-
sendError(res, 400, 'override_status requires a valid "status" field (needs_addressing or waiting_on_maintainer)');
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
276
|
try {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
stateManager.unshelvePR(body.url);
|
|
283
|
-
break;
|
|
284
|
-
case 'override_status': {
|
|
285
|
-
// body.status is validated above — the early return ensures it's defined here
|
|
286
|
-
const overrideStatus = body.status;
|
|
287
|
-
// Find the PR to get its current updatedAt for auto-clear tracking
|
|
288
|
-
const targetPR = (cachedDigest?.openPRs || []).find((pr) => pr.url === body.url);
|
|
289
|
-
const lastActivityAt = targetPR?.updatedAt || new Date().toISOString();
|
|
290
|
-
stateManager.setStatusOverride(body.url, overrideStatus, lastActivityAt);
|
|
291
|
-
break;
|
|
277
|
+
if (body.action === 'move') {
|
|
278
|
+
const { VALID_TARGETS, runMove } = await import('./move.js');
|
|
279
|
+
if (!body.target || !VALID_TARGETS.includes(body.target)) {
|
|
280
|
+
sendError(res, 400, `move requires a valid "target" field (${VALID_TARGETS.join(', ')})`);
|
|
281
|
+
return;
|
|
292
282
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
283
|
+
await runMove({ prUrl: body.url, target: body.target });
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// dismiss_issue_response
|
|
287
|
+
stateManager.dismissIssue(body.url, new Date().toISOString());
|
|
288
|
+
stateManager.save();
|
|
296
289
|
}
|
|
297
|
-
stateManager.save();
|
|
298
290
|
}
|
|
299
291
|
catch (error) {
|
|
300
292
|
warn(MODULE, `Action failed: ${body.action} ${body.url} ${errorMessage(error)}`);
|
|
@@ -313,6 +305,7 @@ export async function startDashboardServer(options) {
|
|
|
313
305
|
return;
|
|
314
306
|
}
|
|
315
307
|
try {
|
|
308
|
+
stateManager.reloadIfChanged();
|
|
316
309
|
warn(MODULE, 'Refreshing dashboard data from GitHub...');
|
|
317
310
|
const result = await fetchDashboardData(currentToken);
|
|
318
311
|
cachedDigest = result.digest;
|
|
@@ -428,6 +421,7 @@ export async function startDashboardServer(options) {
|
|
|
428
421
|
if (token) {
|
|
429
422
|
fetchDashboardData(token)
|
|
430
423
|
.then((result) => {
|
|
424
|
+
stateManager.reloadIfChanged();
|
|
431
425
|
cachedDigest = result.digest;
|
|
432
426
|
cachedCommentedIssues = result.commentedIssues;
|
|
433
427
|
cachedJsonData = buildDashboardJson(cachedDigest, stateManager.getState(), cachedCommentedIssues, result.allMergedPRs, result.allClosedPRs);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dismiss/Undismiss commands
|
|
3
|
-
* Manages dismissing issue
|
|
3
|
+
* Manages dismissing issue notifications without posting a comment.
|
|
4
4
|
* Dismissed URLs resurface automatically when new responses arrive after the dismiss timestamp.
|
|
5
5
|
*/
|
|
6
6
|
export interface DismissOutput {
|
package/dist/commands/dismiss.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dismiss/Undismiss commands
|
|
3
|
-
* Manages dismissing issue
|
|
3
|
+
* Manages dismissing issue notifications without posting a comment.
|
|
4
4
|
* Dismissed URLs resurface automatically when new responses arrive after the dismiss timestamp.
|
|
5
5
|
*/
|
|
6
6
|
import { getStateManager } from '../core/index.js';
|
|
7
|
-
import {
|
|
7
|
+
import { ISSUE_URL_PATTERN, validateGitHubUrl, validateUrl } from './validation.js';
|
|
8
8
|
export async function runDismiss(options) {
|
|
9
9
|
validateUrl(options.url);
|
|
10
|
-
validateGitHubUrl(options.url,
|
|
10
|
+
validateGitHubUrl(options.url, ISSUE_URL_PATTERN, 'issue');
|
|
11
11
|
const stateManager = getStateManager();
|
|
12
12
|
const added = stateManager.dismissIssue(options.url, new Date().toISOString());
|
|
13
13
|
if (added) {
|
|
@@ -17,7 +17,7 @@ export async function runDismiss(options) {
|
|
|
17
17
|
}
|
|
18
18
|
export async function runUndismiss(options) {
|
|
19
19
|
validateUrl(options.url);
|
|
20
|
-
validateGitHubUrl(options.url,
|
|
20
|
+
validateGitHubUrl(options.url, ISSUE_URL_PATTERN, 'issue');
|
|
21
21
|
const stateManager = getStateManager();
|
|
22
22
|
const removed = stateManager.undismissIssue(options.url);
|
|
23
23
|
if (removed) {
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -35,14 +35,12 @@ export { runRead } from './read.js';
|
|
|
35
35
|
export { runShelve } from './shelve.js';
|
|
36
36
|
/** Restore a shelved PR to the daily digest. */
|
|
37
37
|
export { runUnshelve } from './shelve.js';
|
|
38
|
+
/** Move a PR between states: attention, waiting, shelved, auto. */
|
|
39
|
+
export { runMove } from './move.js';
|
|
38
40
|
/** Dismiss issue reply notifications (auto-resurfaces on new activity). */
|
|
39
41
|
export { runDismiss } from './dismiss.js';
|
|
40
42
|
/** Restore a dismissed issue to notifications. */
|
|
41
43
|
export { runUndismiss } from './dismiss.js';
|
|
42
|
-
/** Temporarily suppress CI failure notifications for a PR. */
|
|
43
|
-
export { runSnooze } from './snooze.js';
|
|
44
|
-
/** Restore CI failure notifications for a snoozed PR. */
|
|
45
|
-
export { runUnsnooze } from './snooze.js';
|
|
46
44
|
/** Fetch comments for tracked issues/PRs. */
|
|
47
45
|
export { runComments } from './comments.js';
|
|
48
46
|
/** Post a comment to a GitHub issue or PR. */
|
|
@@ -68,8 +66,8 @@ export type { VetOutput, CommentsOutput, PostOutput, ClaimOutput } from '../form
|
|
|
68
66
|
export type { ConfigOutput, ParseIssueListOutput, CheckIntegrationOutput, LocalReposOutput, } from '../formatters/json.js';
|
|
69
67
|
export type { ReadOutput } from './read.js';
|
|
70
68
|
export type { ShelveOutput, UnshelveOutput } from './shelve.js';
|
|
69
|
+
export type { MoveOutput, MoveTarget } from './move.js';
|
|
71
70
|
export type { DismissOutput, UndismissOutput } from './dismiss.js';
|
|
72
|
-
export type { SnoozeOutput, UnsnoozeOutput } from './snooze.js';
|
|
73
71
|
export type { UntrackOutput } from './track.js';
|
|
74
72
|
export type { InitOutput } from './init.js';
|
|
75
73
|
export type { ConfigSetOutput, ConfigCommandOutput } from './config.js';
|
package/dist/commands/index.js
CHANGED
|
@@ -37,14 +37,12 @@ export { runRead } from './read.js';
|
|
|
37
37
|
export { runShelve } from './shelve.js';
|
|
38
38
|
/** Restore a shelved PR to the daily digest. */
|
|
39
39
|
export { runUnshelve } from './shelve.js';
|
|
40
|
+
/** Move a PR between states: attention, waiting, shelved, auto. */
|
|
41
|
+
export { runMove } from './move.js';
|
|
40
42
|
/** Dismiss issue reply notifications (auto-resurfaces on new activity). */
|
|
41
43
|
export { runDismiss } from './dismiss.js';
|
|
42
44
|
/** Restore a dismissed issue to notifications. */
|
|
43
45
|
export { runUndismiss } from './dismiss.js';
|
|
44
|
-
/** Temporarily suppress CI failure notifications for a PR. */
|
|
45
|
-
export { runSnooze } from './snooze.js';
|
|
46
|
-
/** Restore CI failure notifications for a snoozed PR. */
|
|
47
|
-
export { runUnsnooze } from './snooze.js';
|
|
48
46
|
// ── Issue & Comment Management ──────────────────────────────────────────────
|
|
49
47
|
/** Fetch comments for tracked issues/PRs. */
|
|
50
48
|
export { runComments } from './comments.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move command — transition a PR between states:
|
|
3
|
+
* attention, waiting, shelved, or auto (reset to computed status).
|
|
4
|
+
*/
|
|
5
|
+
export declare const VALID_TARGETS: readonly ["attention", "waiting", "shelved", "auto"];
|
|
6
|
+
export type MoveTarget = (typeof VALID_TARGETS)[number];
|
|
7
|
+
export interface MoveOutput {
|
|
8
|
+
url: string;
|
|
9
|
+
target: MoveTarget;
|
|
10
|
+
/** Human-readable description of what happened. */
|
|
11
|
+
description: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function runMove(options: {
|
|
14
|
+
prUrl: string;
|
|
15
|
+
target: string;
|
|
16
|
+
}): Promise<MoveOutput>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move command — transition a PR between states:
|
|
3
|
+
* attention, waiting, shelved, or auto (reset to computed status).
|
|
4
|
+
*/
|
|
5
|
+
import { getStateManager } from '../core/index.js';
|
|
6
|
+
import { PR_URL_PATTERN, validateGitHubUrl, validateUrl } from './validation.js';
|
|
7
|
+
export const VALID_TARGETS = ['attention', 'waiting', 'shelved', 'auto'];
|
|
8
|
+
export async function runMove(options) {
|
|
9
|
+
validateUrl(options.prUrl);
|
|
10
|
+
validateGitHubUrl(options.prUrl, PR_URL_PATTERN, 'PR');
|
|
11
|
+
const target = options.target;
|
|
12
|
+
if (!VALID_TARGETS.includes(target)) {
|
|
13
|
+
throw new Error(`Invalid target "${options.target}". Must be one of: ${VALID_TARGETS.join(', ')}`);
|
|
14
|
+
}
|
|
15
|
+
const stateManager = getStateManager();
|
|
16
|
+
switch (target) {
|
|
17
|
+
case 'attention':
|
|
18
|
+
case 'waiting': {
|
|
19
|
+
const status = target === 'attention' ? 'needs_addressing' : 'waiting_on_maintainer';
|
|
20
|
+
const label = target === 'attention' ? 'Need Attention' : 'Waiting on Maintainer';
|
|
21
|
+
// Use current time — the CLI doesn't have cached PR data. The override
|
|
22
|
+
// will auto-clear on the next daily run if the PR has new activity after this.
|
|
23
|
+
const lastActivityAt = new Date().toISOString();
|
|
24
|
+
stateManager.setStatusOverride(options.prUrl, status, lastActivityAt);
|
|
25
|
+
stateManager.unshelvePR(options.prUrl);
|
|
26
|
+
stateManager.save();
|
|
27
|
+
return { url: options.prUrl, target, description: `Moved to ${label}` };
|
|
28
|
+
}
|
|
29
|
+
case 'shelved': {
|
|
30
|
+
stateManager.shelvePR(options.prUrl);
|
|
31
|
+
stateManager.clearStatusOverride(options.prUrl);
|
|
32
|
+
stateManager.save();
|
|
33
|
+
return {
|
|
34
|
+
url: options.prUrl,
|
|
35
|
+
target,
|
|
36
|
+
description: 'Shelved — excluded from capacity and actionable items',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
case 'auto': {
|
|
40
|
+
const clearedOverride = stateManager.clearStatusOverride(options.prUrl);
|
|
41
|
+
const unshelved = stateManager.unshelvePR(options.prUrl);
|
|
42
|
+
if (clearedOverride || unshelved) {
|
|
43
|
+
stateManager.save();
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
url: options.prUrl,
|
|
47
|
+
target,
|
|
48
|
+
description: 'Reset to computed status',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
default: {
|
|
52
|
+
const _exhaustive = target;
|
|
53
|
+
throw new Error(`Unhandled move target: ${_exhaustive}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Shelve/Unshelve commands
|
|
3
3
|
* Manages shelving PRs to exclude them from capacity and actionable issues.
|
|
4
4
|
* Shelved PRs are auto-unshelved when a maintainer engages.
|
|
5
|
+
*
|
|
6
|
+
* Note: The CLI and MCP shelve/unshelve commands delegate to runMove(),
|
|
7
|
+
* which also clears status overrides. These functions match that behavior
|
|
8
|
+
* to keep the library API consistent.
|
|
5
9
|
*/
|
|
6
10
|
import { PR_URL_PATTERN } from './validation.js';
|
|
7
11
|
export interface ShelveOutput {
|
package/dist/commands/shelve.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Shelve/Unshelve commands
|
|
3
3
|
* Manages shelving PRs to exclude them from capacity and actionable issues.
|
|
4
4
|
* Shelved PRs are auto-unshelved when a maintainer engages.
|
|
5
|
+
*
|
|
6
|
+
* Note: The CLI and MCP shelve/unshelve commands delegate to runMove(),
|
|
7
|
+
* which also clears status overrides. These functions match that behavior
|
|
8
|
+
* to keep the library API consistent.
|
|
5
9
|
*/
|
|
6
10
|
import { getStateManager } from '../core/index.js';
|
|
7
11
|
import { PR_URL_PATTERN, validateGitHubUrl, validateUrl } from './validation.js';
|
|
@@ -12,7 +16,8 @@ export async function runShelve(options) {
|
|
|
12
16
|
validateGitHubUrl(options.prUrl, PR_URL_PATTERN, 'PR');
|
|
13
17
|
const stateManager = getStateManager();
|
|
14
18
|
const added = stateManager.shelvePR(options.prUrl);
|
|
15
|
-
|
|
19
|
+
const clearedOverride = stateManager.clearStatusOverride(options.prUrl);
|
|
20
|
+
if (added || clearedOverride) {
|
|
16
21
|
stateManager.save();
|
|
17
22
|
}
|
|
18
23
|
return { shelved: added, url: options.prUrl };
|
|
@@ -22,7 +27,8 @@ export async function runUnshelve(options) {
|
|
|
22
27
|
validateGitHubUrl(options.prUrl, PR_URL_PATTERN, 'PR');
|
|
23
28
|
const stateManager = getStateManager();
|
|
24
29
|
const removed = stateManager.unshelvePR(options.prUrl);
|
|
25
|
-
|
|
30
|
+
const clearedOverride = stateManager.clearStatusOverride(options.prUrl);
|
|
31
|
+
if (removed || clearedOverride) {
|
|
26
32
|
stateManager.save();
|
|
27
33
|
}
|
|
28
34
|
return { unshelved: removed, url: options.prUrl };
|
|
@@ -54,7 +54,7 @@ export declare function assessCapacity(activePRs: FetchedPR[], maxActivePRs: num
|
|
|
54
54
|
* Note: Recently closed PRs are informational only and excluded from this list.
|
|
55
55
|
* They are available separately in digest.recentlyClosedPRs (#156).
|
|
56
56
|
*/
|
|
57
|
-
export declare function collectActionableIssues(prs: FetchedPR[],
|
|
57
|
+
export declare function collectActionableIssues(prs: FetchedPR[], lastDigestAt?: string): ActionableIssue[];
|
|
58
58
|
/**
|
|
59
59
|
* Format a maintainer action hint as a human-readable label
|
|
60
60
|
*/
|
package/dist/core/daily-logic.js
CHANGED
|
@@ -198,7 +198,7 @@ export function assessCapacity(activePRs, maxActivePRs, shelvedPRCount) {
|
|
|
198
198
|
* Note: Recently closed PRs are informational only and excluded from this list.
|
|
199
199
|
* They are available separately in digest.recentlyClosedPRs (#156).
|
|
200
200
|
*/
|
|
201
|
-
export function collectActionableIssues(prs,
|
|
201
|
+
export function collectActionableIssues(prs, lastDigestAt) {
|
|
202
202
|
const issues = [];
|
|
203
203
|
const actionPRs = prs.filter((pr) => pr.status === 'needs_addressing');
|
|
204
204
|
const lastDigestTime = lastDigestAt ? new Date(lastDigestAt).getTime() : NaN;
|
|
@@ -213,8 +213,6 @@ export function collectActionableIssues(prs, snoozedUrls = new Set(), lastDigest
|
|
|
213
213
|
for (const pr of actionPRs) {
|
|
214
214
|
if (pr.actionReason !== reason)
|
|
215
215
|
continue;
|
|
216
|
-
if (reason === 'failing_ci' && snoozedUrls.has(pr.url))
|
|
217
|
-
continue;
|
|
218
216
|
let label;
|
|
219
217
|
let type;
|
|
220
218
|
switch (reason) {
|
|
@@ -10,12 +10,14 @@
|
|
|
10
10
|
* - maintainer-analysis.ts: Maintainer action hint extraction
|
|
11
11
|
* - display-utils.ts: Display label computation
|
|
12
12
|
* - github-stats.ts: Merged/closed PR counts and star fetching
|
|
13
|
+
* - status-determination.ts: PR status classification logic
|
|
13
14
|
*/
|
|
14
15
|
import { FetchedPR, DailyDigest, ClosedPR, MergedPR, StarFilter } from './types.js';
|
|
15
16
|
import { type PRCountsResult } from './github-stats.js';
|
|
16
17
|
export { computeDisplayLabel } from './display-utils.js';
|
|
17
18
|
export { classifyCICheck, classifyFailingChecks } from './ci-analysis.js';
|
|
18
19
|
export { isConditionalChecklistItem } from './checklist-analysis.js';
|
|
20
|
+
export { determineStatus } from './status-determination.js';
|
|
19
21
|
export interface PRCheckFailure {
|
|
20
22
|
prUrl: string;
|
|
21
23
|
error: string;
|
|
@@ -42,33 +44,6 @@ export declare class PRMonitor {
|
|
|
42
44
|
* Centralizes PR construction and display label computation (#79).
|
|
43
45
|
*/
|
|
44
46
|
private buildFetchedPR;
|
|
45
|
-
/**
|
|
46
|
-
* Determine the overall status of a PR
|
|
47
|
-
*/
|
|
48
|
-
private determineStatus;
|
|
49
|
-
/**
|
|
50
|
-
* CI-fix bots that push commits as a direct result of the contributor's push (#568).
|
|
51
|
-
* Their commits represent contributor work and should count as addressing feedback.
|
|
52
|
-
* This is intentionally an allowlist — not all `[bot]` accounts are CI-fix bots
|
|
53
|
-
* (e.g. dependabot[bot] and renovate[bot] open their own PRs).
|
|
54
|
-
* Values must be lowercase — lookup uses .toLowerCase() for case-insensitive matching.
|
|
55
|
-
*/
|
|
56
|
-
private static readonly CI_FIX_BOTS;
|
|
57
|
-
/**
|
|
58
|
-
* Check whether the HEAD commit was authored by the contributor (#547).
|
|
59
|
-
* Returns true when the author matches, when the author is a known CI-fix
|
|
60
|
-
* bot (#568), or when author info is unavailable (graceful degradation).
|
|
61
|
-
*/
|
|
62
|
-
private isContributorCommit;
|
|
63
|
-
/** Minimum gap (ms) between maintainer comment and contributor commit for
|
|
64
|
-
* the commit to count as "addressing" the feedback (#547). Prevents false
|
|
65
|
-
* positives from race conditions, clock skew, and in-flight pushes. */
|
|
66
|
-
private static readonly MIN_RESPONSE_GAP_MS;
|
|
67
|
-
/**
|
|
68
|
-
* Check whether the contributor's commit is meaningfully after the maintainer's
|
|
69
|
-
* comment — i.e. the commit timestamp is at least MIN_RESPONSE_GAP_MS later (#547).
|
|
70
|
-
*/
|
|
71
|
-
private isCommitAfterComment;
|
|
72
47
|
/**
|
|
73
48
|
* Check if PR has merge conflict
|
|
74
49
|
*/
|