@oss-autopilot/core 0.42.2 → 0.42.4

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.
@@ -4387,10 +4387,10 @@ var init_state = __esm({
4387
4387
  }
4388
4388
  // === Dismiss / Undismiss Issues ===
4389
4389
  /**
4390
- * Dismiss an issue by URL. Dismissed issues are excluded from `new_response` notifications
4390
+ * Dismiss an issue or PR by URL. Dismissed URLs are excluded from `new_response` notifications
4391
4391
  * until new activity occurs after the dismiss timestamp.
4392
- * @param url - The full GitHub issue URL.
4393
- * @param timestamp - ISO timestamp of when the issue was dismissed.
4392
+ * @param url - The full GitHub issue or PR URL.
4393
+ * @param timestamp - ISO timestamp of when the issue/PR was dismissed.
4394
4394
  * @returns true if newly dismissed, false if already dismissed.
4395
4395
  */
4396
4396
  dismissIssue(url, timestamp) {
@@ -4404,8 +4404,8 @@ var init_state = __esm({
4404
4404
  return true;
4405
4405
  }
4406
4406
  /**
4407
- * Undismiss an issue by URL.
4408
- * @param url - The full GitHub issue URL.
4407
+ * Undismiss an issue or PR by URL.
4408
+ * @param url - The full GitHub issue or PR URL.
4409
4409
  * @returns true if found and removed, false if not dismissed.
4410
4410
  */
4411
4411
  undismissIssue(url) {
@@ -4416,8 +4416,8 @@ var init_state = __esm({
4416
4416
  return true;
4417
4417
  }
4418
4418
  /**
4419
- * Get the timestamp when an issue was dismissed.
4420
- * @param url - The full GitHub issue URL.
4419
+ * Get the timestamp when an issue or PR was dismissed.
4420
+ * @param url - The full GitHub issue or PR URL.
4421
4421
  * @returns The ISO dismiss timestamp, or undefined if not dismissed.
4422
4422
  */
4423
4423
  getIssueDismissedAt(url) {
@@ -11213,7 +11213,24 @@ var init_pr_monitor = __esm({
11213
11213
  );
11214
11214
  const ciPromise = this.getCIStatus(owner, repo, ghPR.head.sha);
11215
11215
  const needCommitDate = hasUnrespondedComment || reviewDecision === "changes_requested";
11216
- const commitDatePromise = needCommitDate ? this.octokit.repos.getCommit({ owner, repo, ref: ghPR.head.sha }).then((res) => res.data.commit.author?.date).catch(() => void 0) : Promise.resolve(void 0);
11216
+ const commitDatePromise = needCommitDate ? this.octokit.repos.getCommit({ owner, repo, ref: ghPR.head.sha }).then((res) => res.data.commit.author?.date).catch((err) => {
11217
+ const status2 = getHttpStatusCode(err);
11218
+ if (status2 === 429) throw err;
11219
+ if (status2 === 403) {
11220
+ const msg = errorMessage(err).toLowerCase();
11221
+ if (msg.includes("rate limit") || msg.includes("abuse detection")) throw err;
11222
+ warn(
11223
+ "pr-monitor",
11224
+ `403 fetching commit date for ${owner}/${repo}@${ghPR.head.sha.slice(0, 7)}: ${errorMessage(err)}`
11225
+ );
11226
+ return void 0;
11227
+ }
11228
+ warn(
11229
+ "pr-monitor",
11230
+ `Failed to fetch commit date for ${owner}/${repo}@${ghPR.head.sha.slice(0, 7)}: ${errorMessage(err)}`
11231
+ );
11232
+ return void 0;
11233
+ }) : Promise.resolve(void 0);
11217
11234
  const [{ status: ciStatus, failingCheckNames, failingCheckConclusions }, latestCommitDate] = await Promise.all([
11218
11235
  ciPromise,
11219
11236
  commitDatePromise
@@ -13685,11 +13702,12 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
13685
13702
  const dismissTime = new Date(dismissedAt).getTime();
13686
13703
  if (isNaN(responseTime) || isNaN(dismissTime)) {
13687
13704
  console.error(`[DAILY] Invalid timestamp in dismiss check for ${issue.url}, including issue`);
13688
- stateManager2.undismissIssue(issue.url);
13689
- hasAutoUndismissed = true;
13690
13705
  return true;
13691
13706
  }
13692
13707
  if (responseTime > dismissTime) {
13708
+ console.error(
13709
+ `[DAILY] Auto-undismissing issue ${issue.url}: new response at ${issue.lastResponseAt} after dismiss at ${dismissedAt}`
13710
+ );
13693
13711
  stateManager2.undismissIssue(issue.url);
13694
13712
  hasAutoUndismissed = true;
13695
13713
  return true;
@@ -13697,9 +13715,6 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
13697
13715
  }
13698
13716
  return false;
13699
13717
  });
13700
- if (hasAutoUndismissed) {
13701
- stateManager2.save();
13702
- }
13703
13718
  const issueResponses = filteredCommentedIssues.filter(
13704
13719
  (i) => i.status === "new_response"
13705
13720
  );
@@ -13707,8 +13722,32 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
13707
13722
  const snoozedUrls = new Set(
13708
13723
  Object.keys(stateManager2.getState().config.snoozedPRs ?? {}).filter((url) => stateManager2.isSnoozed(url))
13709
13724
  );
13710
- const dismissedUrls = new Set(Object.keys(stateManager2.getState().config.dismissedIssues ?? {}));
13711
- const nonDismissedPRs = activePRs.filter((pr) => !dismissedUrls.has(pr.url));
13725
+ const nonDismissedPRs = activePRs.filter((pr) => {
13726
+ const dismissedAt = stateManager2.getIssueDismissedAt(pr.url);
13727
+ if (!dismissedAt) return true;
13728
+ const activityTime = new Date(pr.updatedAt).getTime();
13729
+ const dismissTime = new Date(dismissedAt).getTime();
13730
+ if (isNaN(activityTime) || isNaN(dismissTime)) {
13731
+ console.error(`[DAILY] Invalid timestamp in PR dismiss check for ${pr.url}, including PR`);
13732
+ return true;
13733
+ }
13734
+ if (activityTime > dismissTime) {
13735
+ console.error(
13736
+ `[DAILY] Auto-undismissing PR ${pr.url}: new activity at ${pr.updatedAt} after dismiss at ${dismissedAt}`
13737
+ );
13738
+ stateManager2.undismissIssue(pr.url);
13739
+ hasAutoUndismissed = true;
13740
+ return true;
13741
+ }
13742
+ return false;
13743
+ });
13744
+ if (hasAutoUndismissed) {
13745
+ try {
13746
+ stateManager2.save();
13747
+ } catch (error) {
13748
+ console.error("[DAILY] Failed to persist auto-undismissed state:", errorMessage(error));
13749
+ }
13750
+ }
13712
13751
  const actionableIssues = collectActionableIssues(nonDismissedPRs, snoozedUrls);
13713
13752
  digest.summary.totalNeedingAttention = actionableIssues.length;
13714
13753
  const briefSummary = formatBriefSummary(digest, actionableIssues.length, issueResponses.length);
@@ -17199,7 +17238,6 @@ var init_shelve = __esm({
17199
17238
  // src/commands/dismiss.ts
17200
17239
  var dismiss_exports = {};
17201
17240
  __export(dismiss_exports, {
17202
- ISSUE_OR_PR_URL_PATTERN: () => ISSUE_OR_PR_URL_PATTERN,
17203
17241
  runDismiss: () => runDismiss,
17204
17242
  runUndismiss: () => runUndismiss
17205
17243
  });
@@ -305,14 +305,14 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
305
305
  const responseTime = new Date(issue.lastResponseAt).getTime();
306
306
  const dismissTime = new Date(dismissedAt).getTime();
307
307
  if (isNaN(responseTime) || isNaN(dismissTime)) {
308
- // Invalid timestamp — fail open (include issue to be safe)
308
+ // Invalid timestamp — fail open (include issue to be safe) without
309
+ // permanently removing dismiss record (may be a transient data issue)
309
310
  console.error(`[DAILY] Invalid timestamp in dismiss check for ${issue.url}, including issue`);
310
- stateManager.undismissIssue(issue.url);
311
- hasAutoUndismissed = true;
312
311
  return true;
313
312
  }
314
313
  if (responseTime > dismissTime) {
315
314
  // New activity after dismiss — auto-undismiss and resurface
315
+ console.error(`[DAILY] Auto-undismissing issue ${issue.url}: new response at ${issue.lastResponseAt} after dismiss at ${dismissedAt}`);
316
316
  stateManager.undismissIssue(issue.url);
317
317
  hasAutoUndismissed = true;
318
318
  return true;
@@ -321,15 +321,41 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
321
321
  // Still dismissed (last response is at or before dismiss timestamp)
322
322
  return false;
323
323
  });
324
- if (hasAutoUndismissed) {
325
- stateManager.save();
326
- }
327
324
  const issueResponses = filteredCommentedIssues.filter((i) => i.status === 'new_response');
328
325
  const summary = formatSummary(digest, capacity, issueResponses);
329
326
  const snoozedUrls = new Set(Object.keys(stateManager.getState().config.snoozedPRs ?? {}).filter((url) => stateManager.isSnoozed(url)));
330
- // Filter dismissed PR URLs from actionable issues (#416)
331
- const dismissedUrls = new Set(Object.keys(stateManager.getState().config.dismissedIssues ?? {}));
332
- const nonDismissedPRs = activePRs.filter((pr) => !dismissedUrls.has(pr.url));
327
+ // Filter dismissed PRs: suppress if dismissed after last activity, auto-undismiss if new activity (#416, #468)
328
+ const nonDismissedPRs = activePRs.filter((pr) => {
329
+ const dismissedAt = stateManager.getIssueDismissedAt(pr.url);
330
+ if (!dismissedAt)
331
+ return true; // Not dismissed — include
332
+ const activityTime = new Date(pr.updatedAt).getTime();
333
+ const dismissTime = new Date(dismissedAt).getTime();
334
+ if (isNaN(activityTime) || isNaN(dismissTime)) {
335
+ // Invalid timestamp — fail open (include PR to be safe) without
336
+ // permanently removing dismiss record (may be a transient data issue)
337
+ console.error(`[DAILY] Invalid timestamp in PR dismiss check for ${pr.url}, including PR`);
338
+ return true;
339
+ }
340
+ if (activityTime > dismissTime) {
341
+ // New activity after dismiss — auto-undismiss and resurface
342
+ console.error(`[DAILY] Auto-undismissing PR ${pr.url}: new activity at ${pr.updatedAt} after dismiss at ${dismissedAt}`);
343
+ stateManager.undismissIssue(pr.url);
344
+ hasAutoUndismissed = true;
345
+ return true;
346
+ }
347
+ // Still dismissed (last activity is at or before dismiss timestamp)
348
+ return false;
349
+ });
350
+ // Persist auto-undismiss state changes (issue + PR combined into one save)
351
+ if (hasAutoUndismissed) {
352
+ try {
353
+ stateManager.save();
354
+ }
355
+ catch (error) {
356
+ console.error('[DAILY] Failed to persist auto-undismissed state:', errorMessage(error));
357
+ }
358
+ }
333
359
  const actionableIssues = collectActionableIssues(nonDismissedPRs, snoozedUrls);
334
360
  digest.summary.totalNeedingAttention = actionableIssues.length;
335
361
  const briefSummary = formatBriefSummary(digest, actionableIssues.length, issueResponses.length);
@@ -3,7 +3,6 @@
3
3
  * Manages dismissing issue and PR notifications without posting a comment.
4
4
  * Dismissed URLs resurface automatically when new responses arrive after the dismiss timestamp.
5
5
  */
6
- import { ISSUE_OR_PR_URL_PATTERN } from './validation.js';
7
6
  export interface DismissOutput {
8
7
  dismissed: boolean;
9
8
  url: string;
@@ -12,7 +11,6 @@ export interface UndismissOutput {
12
11
  undismissed: boolean;
13
12
  url: string;
14
13
  }
15
- export { ISSUE_OR_PR_URL_PATTERN };
16
14
  export declare function runDismiss(options: {
17
15
  url: string;
18
16
  }): Promise<DismissOutput>;
@@ -5,8 +5,6 @@
5
5
  */
6
6
  import { getStateManager } from '../core/index.js';
7
7
  import { ISSUE_OR_PR_URL_PATTERN, validateGitHubUrl, validateUrl } from './validation.js';
8
- // Re-export for tests
9
- export { ISSUE_OR_PR_URL_PATTERN };
10
8
  export async function runDismiss(options) {
11
9
  validateUrl(options.url);
12
10
  validateGitHubUrl(options.url, ISSUE_OR_PR_URL_PATTERN, 'issue or PR');
@@ -204,7 +204,23 @@ export class PRMonitor {
204
204
  ? this.octokit.repos
205
205
  .getCommit({ owner, repo, ref: ghPR.head.sha })
206
206
  .then((res) => res.data.commit.author?.date)
207
- .catch(() => undefined)
207
+ .catch((err) => {
208
+ // Rate limit errors must propagate — silently swallowing them produces
209
+ // misleading status (e.g. needs_changes when changes were addressed) (#469).
210
+ const status = getHttpStatusCode(err);
211
+ if (status === 429)
212
+ throw err;
213
+ if (status === 403) {
214
+ const msg = errorMessage(err).toLowerCase();
215
+ if (msg.includes('rate limit') || msg.includes('abuse detection'))
216
+ throw err;
217
+ // Non-rate-limit 403 (DMCA, private repo, SSO) — degrade gracefully
218
+ warn('pr-monitor', `403 fetching commit date for ${owner}/${repo}@${ghPR.head.sha.slice(0, 7)}: ${errorMessage(err)}`);
219
+ return undefined;
220
+ }
221
+ warn('pr-monitor', `Failed to fetch commit date for ${owner}/${repo}@${ghPR.head.sha.slice(0, 7)}: ${errorMessage(err)}`);
222
+ return undefined;
223
+ })
208
224
  : Promise.resolve(undefined);
209
225
  const [{ status: ciStatus, failingCheckNames, failingCheckConclusions }, latestCommitDate] = await Promise.all([
210
226
  ciPromise,
@@ -108,7 +108,8 @@ export function checkUnrespondedComments(comments, reviews, reviewComments, user
108
108
  if (!body && review.state !== 'COMMENTED' && review.state !== 'CHANGES_REQUESTED')
109
109
  continue;
110
110
  const author = review.user?.login || 'unknown';
111
- // For inline-only COMMENTED reviews, skip pure self-replies (#199)
111
+ // For inline-only COMMENTED reviews, skip pure self-replies (#199).
112
+ // CHANGES_REQUESTED reviews are always actionable regardless of self-replies.
112
113
  if (!body && review.state === 'COMMENTED' && review.id != null) {
113
114
  if (isAllSelfReplies(review.id, reviewComments)) {
114
115
  continue;
@@ -206,22 +206,22 @@ export declare class StateManager {
206
206
  */
207
207
  isPRShelved(url: string): boolean;
208
208
  /**
209
- * Dismiss an issue by URL. Dismissed issues are excluded from `new_response` notifications
209
+ * Dismiss an issue or PR by URL. Dismissed URLs are excluded from `new_response` notifications
210
210
  * until new activity occurs after the dismiss timestamp.
211
- * @param url - The full GitHub issue URL.
212
- * @param timestamp - ISO timestamp of when the issue was dismissed.
211
+ * @param url - The full GitHub issue or PR URL.
212
+ * @param timestamp - ISO timestamp of when the issue/PR was dismissed.
213
213
  * @returns true if newly dismissed, false if already dismissed.
214
214
  */
215
215
  dismissIssue(url: string, timestamp: string): boolean;
216
216
  /**
217
- * Undismiss an issue by URL.
218
- * @param url - The full GitHub issue URL.
217
+ * Undismiss an issue or PR by URL.
218
+ * @param url - The full GitHub issue or PR URL.
219
219
  * @returns true if found and removed, false if not dismissed.
220
220
  */
221
221
  undismissIssue(url: string): boolean;
222
222
  /**
223
- * Get the timestamp when an issue was dismissed.
224
- * @param url - The full GitHub issue URL.
223
+ * Get the timestamp when an issue or PR was dismissed.
224
+ * @param url - The full GitHub issue or PR URL.
225
225
  * @returns The ISO dismiss timestamp, or undefined if not dismissed.
226
226
  */
227
227
  getIssueDismissedAt(url: string): string | undefined;
@@ -721,10 +721,10 @@ export class StateManager {
721
721
  }
722
722
  // === Dismiss / Undismiss Issues ===
723
723
  /**
724
- * Dismiss an issue by URL. Dismissed issues are excluded from `new_response` notifications
724
+ * Dismiss an issue or PR by URL. Dismissed URLs are excluded from `new_response` notifications
725
725
  * until new activity occurs after the dismiss timestamp.
726
- * @param url - The full GitHub issue URL.
727
- * @param timestamp - ISO timestamp of when the issue was dismissed.
726
+ * @param url - The full GitHub issue or PR URL.
727
+ * @param timestamp - ISO timestamp of when the issue/PR was dismissed.
728
728
  * @returns true if newly dismissed, false if already dismissed.
729
729
  */
730
730
  dismissIssue(url, timestamp) {
@@ -738,8 +738,8 @@ export class StateManager {
738
738
  return true;
739
739
  }
740
740
  /**
741
- * Undismiss an issue by URL.
742
- * @param url - The full GitHub issue URL.
741
+ * Undismiss an issue or PR by URL.
742
+ * @param url - The full GitHub issue or PR URL.
743
743
  * @returns true if found and removed, false if not dismissed.
744
744
  */
745
745
  undismissIssue(url) {
@@ -750,8 +750,8 @@ export class StateManager {
750
750
  return true;
751
751
  }
752
752
  /**
753
- * Get the timestamp when an issue was dismissed.
754
- * @param url - The full GitHub issue URL.
753
+ * Get the timestamp when an issue or PR was dismissed.
754
+ * @param url - The full GitHub issue or PR URL.
755
755
  * @returns The ISO dismiss timestamp, or undefined if not dismissed.
756
756
  */
757
757
  getIssueDismissedAt(url) {
@@ -451,7 +451,7 @@ export interface AgentConfig {
451
451
  aiPolicyBlocklist?: string[];
452
452
  /** PR URLs manually shelved by the user. Shelved PRs are excluded from capacity and actionable issues. Auto-unshelved when maintainers engage. */
453
453
  shelvedPRUrls?: string[];
454
- /** Issue URLs dismissed by the user, mapped to ISO timestamp of when dismissed. Issues with new responses after the dismiss timestamp resurface automatically. */
454
+ /** Issue/PR URLs dismissed by the user, mapped to ISO timestamp of when dismissed. Issues with new responses after the dismiss timestamp resurface automatically. Named dismissedIssues for state backward compatibility (#416). */
455
455
  dismissedIssues?: Record<string, string>;
456
456
  /** PR URLs with snoozed CI failures, mapped to snooze metadata. Snoozed PRs are excluded from actionable CI failure list until expiry. */
457
457
  snoozedPRs?: Record<string, SnoozeInfo>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oss-autopilot/core",
3
- "version": "0.42.2",
3
+ "version": "0.42.4",
4
4
  "description": "CLI and core library for managing open source contributions",
5
5
  "type": "module",
6
6
  "bin": {