@oss-autopilot/core 3.13.4 → 3.14.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/README.md +3 -3
- package/dist/cli-registry.js +59 -84
- package/dist/cli.bundle.cjs +112 -109
- package/dist/cli.js +5 -4
- package/dist/commands/comments.js +44 -10
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +50 -2
- package/dist/commands/curated-list.d.ts +17 -0
- package/dist/commands/curated-list.js +25 -0
- package/dist/commands/daily.d.ts +7 -1
- package/dist/commands/daily.js +136 -57
- package/dist/commands/dashboard-cache.d.ts +69 -0
- package/dist/commands/dashboard-cache.js +219 -0
- package/dist/commands/dashboard-data.d.ts +18 -10
- package/dist/commands/dashboard-data.js +58 -8
- package/dist/commands/dashboard-gist-sync.d.ts +93 -0
- package/dist/commands/dashboard-gist-sync.js +237 -0
- package/dist/commands/dashboard-server.d.ts +6 -10
- package/dist/commands/dashboard-server.js +181 -347
- package/dist/commands/features.js +6 -0
- package/dist/commands/guidelines.d.ts +6 -0
- package/dist/commands/guidelines.js +7 -0
- package/dist/commands/index.d.ts +2 -5
- package/dist/commands/index.js +2 -4
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +7 -1
- package/dist/commands/list-mark-done.js +6 -21
- package/dist/commands/list-move-tier.js +3 -5
- package/dist/commands/locate-issue-list.d.ts +25 -0
- package/dist/commands/locate-issue-list.js +67 -0
- package/dist/commands/merge-loop.d.ts +63 -0
- package/dist/commands/merge-loop.js +157 -0
- package/dist/commands/repo-vet.js +40 -1
- package/dist/commands/scout-bridge.d.ts +35 -2
- package/dist/commands/scout-bridge.js +65 -13
- package/dist/commands/search.d.ts +4 -6
- package/dist/commands/search.js +58 -11
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +56 -2
- package/dist/commands/skip-file-parser.d.ts +23 -0
- package/dist/commands/skip-file-parser.js +23 -10
- package/dist/commands/startup.d.ts +1 -6
- package/dist/commands/startup.js +25 -59
- package/dist/commands/track.d.ts +2 -2
- package/dist/commands/track.js +2 -2
- package/dist/commands/vet-list.d.ts +6 -6
- package/dist/commands/vet-list.js +194 -65
- package/dist/core/config-registry.js +36 -0
- package/dist/core/daily-logic.d.ts +25 -2
- package/dist/core/daily-logic.js +58 -3
- package/dist/core/gist-health.d.ts +81 -0
- package/dist/core/gist-health.js +39 -0
- package/dist/core/gist-state-store.d.ts +3 -1
- package/dist/core/gist-state-store.js +7 -2
- package/dist/core/github-stats.d.ts +2 -2
- package/dist/core/github-stats.js +20 -4
- package/dist/core/index.d.ts +5 -4
- package/dist/core/index.js +5 -4
- package/dist/core/issue-conversation.js +8 -2
- package/dist/core/issue-grading.d.ts +9 -0
- package/dist/core/issue-grading.js +9 -0
- package/dist/core/issue-verification.d.ts +39 -0
- package/dist/core/issue-verification.js +48 -0
- package/dist/core/pagination.d.ts +27 -0
- package/dist/core/pagination.js +23 -5
- package/dist/core/pr-comments-fetcher.d.ts +7 -0
- package/dist/core/pr-comments-fetcher.js +19 -8
- package/dist/core/pr-monitor.d.ts +2 -0
- package/dist/core/pr-monitor.js +26 -9
- package/dist/core/repo-score-manager.d.ts +2 -2
- package/dist/core/repo-score-manager.js +3 -3
- package/dist/core/repo-vet.d.ts +2 -2
- package/dist/core/repo-vet.js +1 -1
- package/dist/core/review-analysis.d.ts +19 -0
- package/dist/core/review-analysis.js +28 -0
- package/dist/core/state-schema.d.ts +43 -6
- package/dist/core/state-schema.js +81 -4
- package/dist/core/state.d.ts +36 -5
- package/dist/core/state.js +177 -28
- package/dist/core/strategy.js +6 -5
- package/dist/core/types.d.ts +8 -0
- package/dist/core/untrusted-content.d.ts +45 -0
- package/dist/core/untrusted-content.js +54 -0
- package/dist/formatters/json.d.ts +120 -12
- package/dist/formatters/json.js +55 -2
- package/package.json +2 -2
- package/dist/commands/shelve.d.ts +0 -45
- package/dist/commands/shelve.js +0 -54
|
@@ -522,7 +522,9 @@ export class GistStateStore {
|
|
|
522
522
|
}
|
|
523
523
|
/**
|
|
524
524
|
* Re-fetch the Gist and update the in-memory cache.
|
|
525
|
-
* Throttled to at most once per 30 seconds
|
|
525
|
+
* Throttled to at most once per 30 seconds — ATTEMPTS, not just successes:
|
|
526
|
+
* a failed fetch stamps the throttle too (#1443), so an outage does not
|
|
527
|
+
* turn every SPA poll into an immediate full re-fetch.
|
|
526
528
|
*
|
|
527
529
|
* Returns a discriminated union so callers can tell apart the four
|
|
528
530
|
* outcomes that previously collapsed into a single boolean (#1209 L9):
|
|
@@ -541,9 +543,12 @@ export class GistStateStore {
|
|
|
541
543
|
if (sinceLastMs < GistStateStore.REFRESH_THROTTLE_MS) {
|
|
542
544
|
return { status: 'throttled', sinceLastMs };
|
|
543
545
|
}
|
|
546
|
+
// Stamp the ATTEMPT, success or failure (#1443): the success-only stamp
|
|
547
|
+
// let failed refreshes bypass the throttle entirely, contradicting the
|
|
548
|
+
// doc comment and hammering the API during an outage.
|
|
549
|
+
this.lastRefreshAt = now;
|
|
544
550
|
try {
|
|
545
551
|
await this.fetchAndCache(this.gistId);
|
|
546
|
-
this.lastRefreshAt = now;
|
|
547
552
|
this.lastRefreshError = null;
|
|
548
553
|
return { status: 'refreshed' };
|
|
549
554
|
}
|
|
@@ -45,7 +45,7 @@ export declare function fetchRecentlyMergedPRs(octokit: Octokit, config: {
|
|
|
45
45
|
/**
|
|
46
46
|
* Fetch merged PRs since a watermark date for incremental storage.
|
|
47
47
|
* If no watermark is provided (first-ever fetch), fetches all merged PRs (up to pagination cap).
|
|
48
|
-
* Returns StoredMergedPR[] (
|
|
48
|
+
* Returns StoredMergedPR[] (url, title, mergedAt, openedAt) for state persistence.
|
|
49
49
|
*/
|
|
50
50
|
export declare function fetchMergedPRsSince(octokit: Octokit, config: {
|
|
51
51
|
githubUsername: string;
|
|
@@ -53,7 +53,7 @@ export declare function fetchMergedPRsSince(octokit: Octokit, config: {
|
|
|
53
53
|
/**
|
|
54
54
|
* Fetch closed-without-merge PRs since a watermark date for incremental storage.
|
|
55
55
|
* If no watermark is provided (first-ever fetch), fetches all closed PRs (up to pagination cap).
|
|
56
|
-
* Returns StoredClosedPR[] (
|
|
56
|
+
* Returns StoredClosedPR[] (url, title, closedAt, openedAt) for state persistence.
|
|
57
57
|
* Uses `is:unmerged` to exclude merged PRs (which are also "closed" in GitHub's model).
|
|
58
58
|
*/
|
|
59
59
|
export declare function fetchClosedPRsSince(octokit: Octokit, config: {
|
|
@@ -214,6 +214,8 @@ export async function fetchRecentlyClosedPRs(octokit, config, days = 7) {
|
|
|
214
214
|
number,
|
|
215
215
|
title: item.title,
|
|
216
216
|
closedAt: item.closed_at || '',
|
|
217
|
+
// Free on the search result — feeds the outcome ledger (#1461).
|
|
218
|
+
openedAt: item.created_at || undefined,
|
|
217
219
|
}));
|
|
218
220
|
}
|
|
219
221
|
/**
|
|
@@ -232,6 +234,8 @@ export async function fetchRecentlyMergedPRs(octokit, config, days = 7) {
|
|
|
232
234
|
number,
|
|
233
235
|
title: item.title,
|
|
234
236
|
mergedAt: mergedAt || item.closed_at || '',
|
|
237
|
+
// Free on the search result — feeds the outcome ledger (#1461).
|
|
238
|
+
openedAt: item.created_at || undefined,
|
|
235
239
|
};
|
|
236
240
|
});
|
|
237
241
|
}
|
|
@@ -286,7 +290,7 @@ async function fetchPRsSince(octokit, config, adapter, since) {
|
|
|
286
290
|
/**
|
|
287
291
|
* Fetch merged PRs since a watermark date for incremental storage.
|
|
288
292
|
* If no watermark is provided (first-ever fetch), fetches all merged PRs (up to pagination cap).
|
|
289
|
-
* Returns StoredMergedPR[] (
|
|
293
|
+
* Returns StoredMergedPR[] (url, title, mergedAt, openedAt) for state persistence.
|
|
290
294
|
*/
|
|
291
295
|
export async function fetchMergedPRsSince(octokit, config, since) {
|
|
292
296
|
return fetchPRsSince(octokit, config, {
|
|
@@ -294,13 +298,19 @@ export async function fetchMergedPRsSince(octokit, config, since) {
|
|
|
294
298
|
dateNoun: 'merge',
|
|
295
299
|
buildQuery: (u, s) => `is:pr is:merged author:${u} -user:${u}${s ? ` merged:>${s}` : ''}`,
|
|
296
300
|
extractDate: (item) => item.pull_request?.merged_at || item.closed_at || '',
|
|
297
|
-
buildRecord: (item, date) => ({
|
|
301
|
+
buildRecord: (item, date) => ({
|
|
302
|
+
url: item.html_url,
|
|
303
|
+
title: item.title,
|
|
304
|
+
mergedAt: date,
|
|
305
|
+
// Free on the search result — feeds the outcome ledger (#1461).
|
|
306
|
+
openedAt: item.created_at || undefined,
|
|
307
|
+
}),
|
|
298
308
|
}, since);
|
|
299
309
|
}
|
|
300
310
|
/**
|
|
301
311
|
* Fetch closed-without-merge PRs since a watermark date for incremental storage.
|
|
302
312
|
* If no watermark is provided (first-ever fetch), fetches all closed PRs (up to pagination cap).
|
|
303
|
-
* Returns StoredClosedPR[] (
|
|
313
|
+
* Returns StoredClosedPR[] (url, title, closedAt, openedAt) for state persistence.
|
|
304
314
|
* Uses `is:unmerged` to exclude merged PRs (which are also "closed" in GitHub's model).
|
|
305
315
|
*/
|
|
306
316
|
export async function fetchClosedPRsSince(octokit, config, since) {
|
|
@@ -309,6 +319,12 @@ export async function fetchClosedPRsSince(octokit, config, since) {
|
|
|
309
319
|
dateNoun: 'close',
|
|
310
320
|
buildQuery: (u, s) => `is:pr is:closed is:unmerged author:${u} -user:${u}${s ? ` closed:>${s}` : ''}`,
|
|
311
321
|
extractDate: (item) => item.closed_at || '',
|
|
312
|
-
buildRecord: (item, date) => ({
|
|
322
|
+
buildRecord: (item, date) => ({
|
|
323
|
+
url: item.html_url,
|
|
324
|
+
title: item.title,
|
|
325
|
+
closedAt: date,
|
|
326
|
+
// Free on the search result — feeds the outcome ledger (#1461).
|
|
327
|
+
openedAt: item.created_at || undefined,
|
|
328
|
+
}),
|
|
313
329
|
}, since);
|
|
314
330
|
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -3,26 +3,27 @@
|
|
|
3
3
|
* Re-exports all core functionality for convenient imports
|
|
4
4
|
*/
|
|
5
5
|
export { StateManager, getStateManager, getStateManagerAsync, ensureGistPersistence, bootstrapGistBestEffort, type GistPersistenceStatus, maybeCheckpoint, resetStateManager, type Stats, } from './state.js';
|
|
6
|
+
export { renderGistWarning, type GistHealth, type GistHealthDegradedCause, type GistWarningCause, } from './gist-health.js';
|
|
6
7
|
export { GistStateStore } from './gist-state-store.js';
|
|
7
8
|
export { guidelinesFilename, repoFromGuidelinesFilename, GUIDELINES_FILE_PREFIX, GUIDELINES_MAX_BYTES, GuidelinesNotAvailableError, GuidelinesTooLargeError, } from './guidelines-store.js';
|
|
8
9
|
export { PRMonitor, type PRCheckFailure, type FetchPRsResult, computeDisplayLabel, classifyCICheck, classifyFailingChecks, } from './pr-monitor.js';
|
|
9
10
|
export { IssueConversationMonitor } from './issue-conversation.js';
|
|
10
11
|
export { isBotAuthor, isAcknowledgmentComment } from './comment-utils.js';
|
|
11
|
-
export { wrapUntrustedContent, fenceFetchedPR, extractFromFence, safeExtractFromFence, UNTRUSTED_OPEN_TAG_NAME, UNTRUSTED_CLOSE_TAG, type UntrustedContentMeta, } from './untrusted-content.js';
|
|
12
|
+
export { wrapUntrustedContent, fenceFetchedPR, fenceFetchedPRTitles, labelGuidelinesContent, GUIDELINES_PROVENANCE_NOTE, extractFromFence, safeExtractFromFence, UNTRUSTED_OPEN_TAG_NAME, UNTRUSTED_CLOSE_TAG, type UntrustedContentMeta, } from './untrusted-content.js';
|
|
12
13
|
export { getOctokit, checkRateLimit, type RateLimitInfo } from './github.js';
|
|
13
14
|
export { parseGitHubUrl, splitRepo, isOwnRepo } from './urls.js';
|
|
14
15
|
export { daysBetween, formatRelativeTime, byDateDescending } from './dates.js';
|
|
15
16
|
export { getCLIVersion, getDataDir, getStatePath, getBackupDir, getCacheDir, stateFileExists } from './paths.js';
|
|
16
17
|
export { getGitHubToken, getGitHubTokenAsync, requireGitHubToken, resetGitHubTokenCache, detectGitHubUsername, } from './auth.js';
|
|
17
18
|
export { DEFAULT_CONCURRENCY } from './concurrency.js';
|
|
18
|
-
export { OssAutopilotError, ConfigurationError, ValidationError, GistPermissionError, errorMessage, getHttpStatusCode, isRateLimitError, isRateLimitOrAuthError, nonFatalCatch, resolveErrorCode, } from './errors.js';
|
|
19
|
+
export { OssAutopilotError, ConfigurationError, ValidationError, GistPermissionError, ConcurrencyError, GistConcurrencyError, errorMessage, getHttpStatusCode, isRateLimitError, isRateLimitOrAuthError, nonFatalCatch, resolveErrorCode, } from './errors.js';
|
|
19
20
|
export { enableDebug, isDebugEnabled, debug, info, warn, timed } from './logger.js';
|
|
20
21
|
export { HttpCache, getHttpCache, cachedRequest, type CacheEntry } from './http-cache.js';
|
|
21
|
-
export { CRITICAL_STATUSES, applyStatusOverrides, computeRepoSignals, groupPRsByRepo, assessCapacity, collectActionableIssues, computeActionMenu, toShelvedPRRef, buildStarFilter, formatActionHint, formatBriefSummary, formatSummary, printDigest, } from './daily-logic.js';
|
|
22
|
+
export { CRITICAL_STATUSES, applyStatusOverrides, computeRepoSignals, groupPRsByRepo, assessCapacity, collectActionableIssues, computeActionMenu, toShelvedPRRef, buildStarFilter, firstMaintainerResponseFromDigest, formatActionHint, formatBriefSummary, formatSummary, printDigest, } from './daily-logic.js';
|
|
22
23
|
export { computeContributionStats, type ContributionStats, type ComputeStatsInput } from './stats.js';
|
|
23
24
|
export { fetchPRTemplate, type PRTemplateResult } from './pr-template.js';
|
|
24
25
|
export { classifyLinkedPR, isLinkedPRStalled, STALLED_PR_THRESHOLD_DAYS, type LinkedPR, type LinkedPRClassification, type LinkedPRState, } from './linked-pr-classification.js';
|
|
25
|
-
export { classifyIssueAvailability, fetchIssueVerification, type IssueAvailabilityVerdict, type IssueVerification, type LinkedPRLinkType, type VerifiedLinkedPR, type VerifyIssueParams, } from './issue-verification.js';
|
|
26
|
+
export { classifyIssueAvailability, fetchIssueVerification, verifyIssuesBatch, MAX_VERIFY_CONCURRENCY, type BatchVerificationResult, type IssueAvailabilityVerdict, type IssueVerification, type LinkedPRLinkType, type VerifiedLinkedPR, type VerifyIssueParams, } from './issue-verification.js';
|
|
26
27
|
export { classifyAttentionBucket, summarizeAttentionBuckets, STUCK_CI_THRESHOLD_DAYS, DORMANT_FOLLOWUP_THRESHOLD_DAYS, type AttentionBucket, type AttentionInput, type AttentionSummary, } from './pr-attention.js';
|
|
27
28
|
export { scanForAntiLLMPolicy, type AntiLLMCategory, type AntiLLMMatch, type AntiLLMScanResult, } from './anti-llm-policy.js';
|
|
28
29
|
export { detectFormatters, diagnoseCIFormatterFailure, getPreferredFormatter, type DetectedFormatter, type FormatterDetectionResult, type CIFormatterDiagnosis, type FormatterName, } from './formatter-detection.js';
|
package/dist/core/index.js
CHANGED
|
@@ -3,27 +3,28 @@
|
|
|
3
3
|
* Re-exports all core functionality for convenient imports
|
|
4
4
|
*/
|
|
5
5
|
export { StateManager, getStateManager, getStateManagerAsync, ensureGistPersistence, bootstrapGistBestEffort, maybeCheckpoint, resetStateManager, } from './state.js';
|
|
6
|
+
export { renderGistWarning, } from './gist-health.js';
|
|
6
7
|
export { GistStateStore } from './gist-state-store.js';
|
|
7
8
|
export { guidelinesFilename, repoFromGuidelinesFilename, GUIDELINES_FILE_PREFIX, GUIDELINES_MAX_BYTES, GuidelinesNotAvailableError, GuidelinesTooLargeError, } from './guidelines-store.js';
|
|
8
9
|
export { PRMonitor, computeDisplayLabel, classifyCICheck, classifyFailingChecks, } from './pr-monitor.js';
|
|
9
10
|
// Search/vetting now delegated to @oss-scout/core via commands/scout-bridge.ts
|
|
10
11
|
export { IssueConversationMonitor } from './issue-conversation.js';
|
|
11
12
|
export { isBotAuthor, isAcknowledgmentComment } from './comment-utils.js';
|
|
12
|
-
export { wrapUntrustedContent, fenceFetchedPR, extractFromFence, safeExtractFromFence, UNTRUSTED_OPEN_TAG_NAME, UNTRUSTED_CLOSE_TAG, } from './untrusted-content.js';
|
|
13
|
+
export { wrapUntrustedContent, fenceFetchedPR, fenceFetchedPRTitles, labelGuidelinesContent, GUIDELINES_PROVENANCE_NOTE, extractFromFence, safeExtractFromFence, UNTRUSTED_OPEN_TAG_NAME, UNTRUSTED_CLOSE_TAG, } from './untrusted-content.js';
|
|
13
14
|
export { getOctokit, checkRateLimit } from './github.js';
|
|
14
15
|
export { parseGitHubUrl, splitRepo, isOwnRepo } from './urls.js';
|
|
15
16
|
export { daysBetween, formatRelativeTime, byDateDescending } from './dates.js';
|
|
16
17
|
export { getCLIVersion, getDataDir, getStatePath, getBackupDir, getCacheDir, stateFileExists } from './paths.js';
|
|
17
18
|
export { getGitHubToken, getGitHubTokenAsync, requireGitHubToken, resetGitHubTokenCache, detectGitHubUsername, } from './auth.js';
|
|
18
19
|
export { DEFAULT_CONCURRENCY } from './concurrency.js';
|
|
19
|
-
export { OssAutopilotError, ConfigurationError, ValidationError, GistPermissionError, errorMessage, getHttpStatusCode, isRateLimitError, isRateLimitOrAuthError, nonFatalCatch, resolveErrorCode, } from './errors.js';
|
|
20
|
+
export { OssAutopilotError, ConfigurationError, ValidationError, GistPermissionError, ConcurrencyError, GistConcurrencyError, errorMessage, getHttpStatusCode, isRateLimitError, isRateLimitOrAuthError, nonFatalCatch, resolveErrorCode, } from './errors.js';
|
|
20
21
|
export { enableDebug, isDebugEnabled, debug, info, warn, timed } from './logger.js';
|
|
21
22
|
export { HttpCache, getHttpCache, cachedRequest } from './http-cache.js';
|
|
22
|
-
export { CRITICAL_STATUSES, applyStatusOverrides, computeRepoSignals, groupPRsByRepo, assessCapacity, collectActionableIssues, computeActionMenu, toShelvedPRRef, buildStarFilter, formatActionHint, formatBriefSummary, formatSummary, printDigest, } from './daily-logic.js';
|
|
23
|
+
export { CRITICAL_STATUSES, applyStatusOverrides, computeRepoSignals, groupPRsByRepo, assessCapacity, collectActionableIssues, computeActionMenu, toShelvedPRRef, buildStarFilter, firstMaintainerResponseFromDigest, formatActionHint, formatBriefSummary, formatSummary, printDigest, } from './daily-logic.js';
|
|
23
24
|
export { computeContributionStats } from './stats.js';
|
|
24
25
|
export { fetchPRTemplate } from './pr-template.js';
|
|
25
26
|
export { classifyLinkedPR, isLinkedPRStalled, STALLED_PR_THRESHOLD_DAYS, } from './linked-pr-classification.js';
|
|
26
|
-
export { classifyIssueAvailability, fetchIssueVerification, } from './issue-verification.js';
|
|
27
|
+
export { classifyIssueAvailability, fetchIssueVerification, verifyIssuesBatch, MAX_VERIFY_CONCURRENCY, } from './issue-verification.js';
|
|
27
28
|
export { classifyAttentionBucket, summarizeAttentionBuckets, STUCK_CI_THRESHOLD_DAYS, DORMANT_FOLLOWUP_THRESHOLD_DAYS, } from './pr-attention.js';
|
|
28
29
|
export { scanForAntiLLMPolicy, } from './anti-llm-policy.js';
|
|
29
30
|
export { detectFormatters, diagnoseCIFormatterFailure, getPreferredFormatter, } from './formatter-detection.js';
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { getOctokit } from './github.js';
|
|
9
9
|
import { isBotAuthor, isAcknowledgmentComment } from './comment-utils.js';
|
|
10
|
-
import {
|
|
10
|
+
import { paginateAllDetailed } from './pagination.js';
|
|
11
11
|
import { getStateManager } from './state.js';
|
|
12
12
|
import { daysBetween } from './dates.js';
|
|
13
13
|
import { splitRepo, extractOwnerRepo, isOwnRepo } from './urls.js';
|
|
@@ -142,13 +142,19 @@ export class IssueConversationMonitor {
|
|
|
142
142
|
*/
|
|
143
143
|
async analyzeIssueConversation(item, repoFullName, username) {
|
|
144
144
|
const { owner, repo } = splitRepo(repoFullName);
|
|
145
|
-
const allComments = await
|
|
145
|
+
const { items: allComments, truncated } = await paginateAllDetailed((page) => this.octokit.issues.listComments({
|
|
146
146
|
owner,
|
|
147
147
|
repo,
|
|
148
148
|
issue_number: item.number,
|
|
149
149
|
per_page: 100,
|
|
150
150
|
page,
|
|
151
151
|
}));
|
|
152
|
+
if (truncated) {
|
|
153
|
+
// Comments arrive oldest-first, so hitting the pagination cap drops
|
|
154
|
+
// the NEWEST responses — conversation status for this issue may be
|
|
155
|
+
// stale (#1456). Same log-only channel as the search truncation above.
|
|
156
|
+
warn(MODULE, `Comment pagination cap reached for ${repoFullName}#${item.number}; the newest comments were not fetched and conversation status may be stale.`);
|
|
157
|
+
}
|
|
152
158
|
const timeline = [];
|
|
153
159
|
for (const comment of allComments) {
|
|
154
160
|
if (!comment.user?.login)
|
|
@@ -59,6 +59,15 @@ export declare function deriveGradeSignals(params: {
|
|
|
59
59
|
* End-to-end helper for vet callers: reads the repo score, derives
|
|
60
60
|
* signals from a scout candidate, and returns the grade. Callers pass
|
|
61
61
|
* the `projectHealth` straight through from `scout.vetIssue()`.
|
|
62
|
+
*
|
|
63
|
+
* Which "repo score" this grades from (#1465): the `getRepoScore` input is
|
|
64
|
+
* the cached HISTORY record (the user's own merge outcomes, see
|
|
65
|
+
* docs/repo-scores.md §History score) — NOT `repo-vet`'s fresh health
|
|
66
|
+
* rubric. The fresh side only enters through `projectHealth`, and only when
|
|
67
|
+
* scout actually fetched it: the `search` surface passes a `checkFailed`
|
|
68
|
+
* sentinel (health not fetched per candidate), so search grades purely from
|
|
69
|
+
* history-side signals, while `vet` re-grades with fresh health. Same letter
|
|
70
|
+
* scale, different inputs — the two surfaces can legitimately disagree.
|
|
62
71
|
*/
|
|
63
72
|
export declare function gradeFromCandidate(params: {
|
|
64
73
|
repo: string;
|
|
@@ -100,6 +100,15 @@ export function deriveGradeSignals(params) {
|
|
|
100
100
|
* End-to-end helper for vet callers: reads the repo score, derives
|
|
101
101
|
* signals from a scout candidate, and returns the grade. Callers pass
|
|
102
102
|
* the `projectHealth` straight through from `scout.vetIssue()`.
|
|
103
|
+
*
|
|
104
|
+
* Which "repo score" this grades from (#1465): the `getRepoScore` input is
|
|
105
|
+
* the cached HISTORY record (the user's own merge outcomes, see
|
|
106
|
+
* docs/repo-scores.md §History score) — NOT `repo-vet`'s fresh health
|
|
107
|
+
* rubric. The fresh side only enters through `projectHealth`, and only when
|
|
108
|
+
* scout actually fetched it: the `search` surface passes a `checkFailed`
|
|
109
|
+
* sentinel (health not fetched per candidate), so search grades purely from
|
|
110
|
+
* history-side signals, while `vet` re-grades with fresh health. Same letter
|
|
111
|
+
* scale, different inputs — the two surfaces can legitimately disagree.
|
|
103
112
|
*/
|
|
104
113
|
export function gradeFromCandidate(params) {
|
|
105
114
|
const repoScore = params.getRepoScore(params.repo);
|
|
@@ -89,3 +89,42 @@ export interface VerifyIssueParams {
|
|
|
89
89
|
number: number;
|
|
90
90
|
}
|
|
91
91
|
export declare function fetchIssueVerification(octokit: Octokit, params: VerifyIssueParams): Promise<IssueVerification>;
|
|
92
|
+
/**
|
|
93
|
+
* Hard ceiling on concurrent in-flight verifications. Each issue runs its own
|
|
94
|
+
* GraphQL round-trip (point-heavy, with timeline pagination), so we never run
|
|
95
|
+
* more than this many at once regardless of what the caller requests. The
|
|
96
|
+
* single-issue path already leans on ThrottledOctokit's secondary-rate-limit
|
|
97
|
+
* backoff — this cap keeps us from spraying enough parallel queries to trip it.
|
|
98
|
+
*/
|
|
99
|
+
export declare const MAX_VERIFY_CONCURRENCY = 5;
|
|
100
|
+
/**
|
|
101
|
+
* Per-item outcome of a batched verification run. Aligned by index with the
|
|
102
|
+
* input `items`. Error isolation: one item's failure lands as `{ error }` and
|
|
103
|
+
* never aborts the batch, so a single NOT_FOUND (or transient network error)
|
|
104
|
+
* does not poison every other entry — unlike an aliased mega-query would.
|
|
105
|
+
*/
|
|
106
|
+
export interface BatchVerificationResult {
|
|
107
|
+
params: VerifyIssueParams;
|
|
108
|
+
/** Present when verification succeeded for this item. */
|
|
109
|
+
verification?: IssueVerification;
|
|
110
|
+
/** Present when `fetchIssueVerification` threw for this item. */
|
|
111
|
+
error?: unknown;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Verify many issues with bounded concurrent fan-out of the single-issue
|
|
115
|
+
* {@link fetchIssueVerification}.
|
|
116
|
+
*
|
|
117
|
+
* This is a deliberately thin worker pool, NOT one aliased GraphQL query:
|
|
118
|
+
* per-issue timeline cursors make aliasing unmanageable, and all-or-nothing
|
|
119
|
+
* failure (one bad issue poisoning the batch) is exactly what we want to
|
|
120
|
+
* avoid. No retry layer is added here — `octokit` is expected to be a
|
|
121
|
+
* ThrottledOctokit whose backoff already covers secondary rate limits.
|
|
122
|
+
*
|
|
123
|
+
* @param octokit - Throttled Octokit instance (see `getOctokit`).
|
|
124
|
+
* @param items - Issues to verify; results align with this order.
|
|
125
|
+
* @param options.concurrency - Desired parallelism; capped at
|
|
126
|
+
* {@link MAX_VERIFY_CONCURRENCY} and floored at 1.
|
|
127
|
+
*/
|
|
128
|
+
export declare function verifyIssuesBatch(octokit: Octokit, items: readonly VerifyIssueParams[], options?: {
|
|
129
|
+
concurrency?: number;
|
|
130
|
+
}): Promise<BatchVerificationResult[]>;
|
|
@@ -268,3 +268,51 @@ export async function fetchIssueVerification(octokit, params) {
|
|
|
268
268
|
userLogin,
|
|
269
269
|
};
|
|
270
270
|
}
|
|
271
|
+
// ── Batched verification ───────────────────────────────────────────────
|
|
272
|
+
/**
|
|
273
|
+
* Hard ceiling on concurrent in-flight verifications. Each issue runs its own
|
|
274
|
+
* GraphQL round-trip (point-heavy, with timeline pagination), so we never run
|
|
275
|
+
* more than this many at once regardless of what the caller requests. The
|
|
276
|
+
* single-issue path already leans on ThrottledOctokit's secondary-rate-limit
|
|
277
|
+
* backoff — this cap keeps us from spraying enough parallel queries to trip it.
|
|
278
|
+
*/
|
|
279
|
+
export const MAX_VERIFY_CONCURRENCY = 5;
|
|
280
|
+
/**
|
|
281
|
+
* Verify many issues with bounded concurrent fan-out of the single-issue
|
|
282
|
+
* {@link fetchIssueVerification}.
|
|
283
|
+
*
|
|
284
|
+
* This is a deliberately thin worker pool, NOT one aliased GraphQL query:
|
|
285
|
+
* per-issue timeline cursors make aliasing unmanageable, and all-or-nothing
|
|
286
|
+
* failure (one bad issue poisoning the batch) is exactly what we want to
|
|
287
|
+
* avoid. No retry layer is added here — `octokit` is expected to be a
|
|
288
|
+
* ThrottledOctokit whose backoff already covers secondary rate limits.
|
|
289
|
+
*
|
|
290
|
+
* @param octokit - Throttled Octokit instance (see `getOctokit`).
|
|
291
|
+
* @param items - Issues to verify; results align with this order.
|
|
292
|
+
* @param options.concurrency - Desired parallelism; capped at
|
|
293
|
+
* {@link MAX_VERIFY_CONCURRENCY} and floored at 1.
|
|
294
|
+
*/
|
|
295
|
+
export async function verifyIssuesBatch(octokit, items, options = {}) {
|
|
296
|
+
const results = new Array(items.length);
|
|
297
|
+
if (items.length === 0)
|
|
298
|
+
return results;
|
|
299
|
+
const requested = options.concurrency ?? MAX_VERIFY_CONCURRENCY;
|
|
300
|
+
// Floor at 1 so a 0/negative/NaN request can never produce zero workers
|
|
301
|
+
// (which would leave the results array full of holes).
|
|
302
|
+
const concurrency = Math.max(1, Math.min(Number.isFinite(requested) ? requested : MAX_VERIFY_CONCURRENCY, MAX_VERIFY_CONCURRENCY, items.length));
|
|
303
|
+
let index = 0;
|
|
304
|
+
async function worker() {
|
|
305
|
+
while (index < items.length) {
|
|
306
|
+
const i = index++;
|
|
307
|
+
const params = items[i];
|
|
308
|
+
try {
|
|
309
|
+
results[i] = { params, verification: await fetchIssueVerification(octokit, params) };
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
results[i] = { params, error };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
await Promise.all(Array.from({ length: concurrency }, () => worker()));
|
|
317
|
+
return results;
|
|
318
|
+
}
|
|
@@ -1,7 +1,34 @@
|
|
|
1
|
+
/** Result of {@link paginateAllDetailed}: the items plus a truncation signal. */
|
|
2
|
+
export interface PaginateAllResult<T> {
|
|
3
|
+
items: T[];
|
|
4
|
+
/**
|
|
5
|
+
* True when the page cap was reached with a full final page — more data
|
|
6
|
+
* may exist on the server beyond what was fetched. GitHub list endpoints
|
|
7
|
+
* return oldest-first, so on truncation it is the NEWEST entries that are
|
|
8
|
+
* missing. Callers should surface this through their warning channel
|
|
9
|
+
* rather than silently presenting a partial view (#1456).
|
|
10
|
+
*/
|
|
11
|
+
truncated: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Auto-paginate an Octokit list endpoint, reporting whether the page cap
|
|
15
|
+
* truncated the results. Fetches additional pages when the result count
|
|
16
|
+
* equals per_page (indicating more data may exist).
|
|
17
|
+
*
|
|
18
|
+
* @param fetchPage Function that fetches a single page given a page number
|
|
19
|
+
* @param perPage Items per page (default 100)
|
|
20
|
+
* @param maxPages Maximum pages to fetch (default 10 = 1000 items)
|
|
21
|
+
*/
|
|
22
|
+
export declare function paginateAllDetailed<T>(fetchPage: (page: number) => Promise<{
|
|
23
|
+
data: T[];
|
|
24
|
+
}>, perPage?: number, maxPages?: number): Promise<PaginateAllResult<T>>;
|
|
1
25
|
/**
|
|
2
26
|
* Auto-paginate an Octokit list endpoint. Fetches additional pages when
|
|
3
27
|
* the result count equals per_page (indicating more data may exist).
|
|
4
28
|
*
|
|
29
|
+
* Silently drops anything past `maxPages` — use {@link paginateAllDetailed}
|
|
30
|
+
* when the caller needs to know the results were truncated.
|
|
31
|
+
*
|
|
5
32
|
* @param fetchPage Function that fetches a single page given a page number
|
|
6
33
|
* @param perPage Items per page (default 100)
|
|
7
34
|
* @param maxPages Maximum pages to fetch (default 10 = 1000 items)
|
package/dist/core/pagination.js
CHANGED
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
/** Maximum pages to fetch to prevent runaway pagination */
|
|
2
2
|
const MAX_PAGES = 10;
|
|
3
3
|
/**
|
|
4
|
-
* Auto-paginate an Octokit list endpoint
|
|
5
|
-
* the
|
|
4
|
+
* Auto-paginate an Octokit list endpoint, reporting whether the page cap
|
|
5
|
+
* truncated the results. Fetches additional pages when the result count
|
|
6
|
+
* equals per_page (indicating more data may exist).
|
|
6
7
|
*
|
|
7
8
|
* @param fetchPage Function that fetches a single page given a page number
|
|
8
9
|
* @param perPage Items per page (default 100)
|
|
9
10
|
* @param maxPages Maximum pages to fetch (default 10 = 1000 items)
|
|
10
11
|
*/
|
|
11
|
-
export async function
|
|
12
|
+
export async function paginateAllDetailed(fetchPage, perPage = 100, maxPages = MAX_PAGES) {
|
|
12
13
|
const allItems = [];
|
|
13
14
|
for (let page = 1; page <= maxPages; page++) {
|
|
14
15
|
const { data } = await fetchPage(page);
|
|
15
16
|
allItems.push(...data);
|
|
16
17
|
if (data.length < perPage)
|
|
17
|
-
|
|
18
|
+
return { items: allItems, truncated: false }; // No more pages
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
+
// Every fetched page (including the last allowed one) was full — more
|
|
21
|
+
// data may exist beyond the cap.
|
|
22
|
+
return { items: allItems, truncated: true };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Auto-paginate an Octokit list endpoint. Fetches additional pages when
|
|
26
|
+
* the result count equals per_page (indicating more data may exist).
|
|
27
|
+
*
|
|
28
|
+
* Silently drops anything past `maxPages` — use {@link paginateAllDetailed}
|
|
29
|
+
* when the caller needs to know the results were truncated.
|
|
30
|
+
*
|
|
31
|
+
* @param fetchPage Function that fetches a single page given a page number
|
|
32
|
+
* @param perPage Items per page (default 100)
|
|
33
|
+
* @param maxPages Maximum pages to fetch (default 10 = 1000 items)
|
|
34
|
+
*/
|
|
35
|
+
export async function paginateAll(fetchPage, perPage = 100, maxPages = MAX_PAGES) {
|
|
36
|
+
const { items } = await paginateAllDetailed(fetchPage, perPage, maxPages);
|
|
37
|
+
return items;
|
|
20
38
|
}
|
|
@@ -52,6 +52,13 @@ export interface PRCommentBundle {
|
|
|
52
52
|
reviews: PRReviewEntry[];
|
|
53
53
|
reviewComments: PRReviewCommentEntry[];
|
|
54
54
|
issueComments: PRIssueCommentEntry[];
|
|
55
|
+
/**
|
|
56
|
+
* Set to true when any of the three comment streams hit the pagination
|
|
57
|
+
* cap (#1456). These endpoints return oldest-first, so a truncated bundle
|
|
58
|
+
* is missing the NEWEST reviewer voices — corpus consumers should treat
|
|
59
|
+
* the bundle as partial. Omitted when every stream fetched completely.
|
|
60
|
+
*/
|
|
61
|
+
truncated?: boolean;
|
|
55
62
|
}
|
|
56
63
|
/**
|
|
57
64
|
* Fetch a single PR's comment bundle. Filters out the authenticated user's
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { paginateAllDetailed } from './pagination.js';
|
|
2
2
|
import { wrapUntrustedContent } from './untrusted-content.js';
|
|
3
3
|
import { isBotAuthor } from './comment-utils.js';
|
|
4
4
|
import { parseGitHubUrl } from './urls.js';
|
|
@@ -18,26 +18,29 @@ export async function fetchPRCommentBundle(octokit, prUrl, githubUsername) {
|
|
|
18
18
|
}
|
|
19
19
|
const { owner, repo, number: pull_number } = parsed;
|
|
20
20
|
const repoFull = `${owner}/${repo}`;
|
|
21
|
-
// Fetch the PR + all three comment streams in parallel. We
|
|
22
|
-
//
|
|
23
|
-
// just the first 100 comments.
|
|
24
|
-
|
|
21
|
+
// Fetch the PR + all three comment streams in parallel. We fetch every
|
|
22
|
+
// page up to the pagination cap — corpus quality depends on having every
|
|
23
|
+
// reviewer voice, not just the first 100 comments. When a stream still
|
|
24
|
+
// truncates (1000+ entries), the bundle is flagged `truncated` so callers
|
|
25
|
+
// can surface the partial-corpus signal instead of silently dropping the
|
|
26
|
+
// newest comments (#1456).
|
|
27
|
+
const [{ data: pr }, reviewsResult, reviewCommentsResult, issueCommentsResult] = await Promise.all([
|
|
25
28
|
octokit.pulls.get({ owner, repo, pull_number }),
|
|
26
|
-
|
|
29
|
+
paginateAllDetailed((page) => octokit.pulls.listReviews({
|
|
27
30
|
owner,
|
|
28
31
|
repo,
|
|
29
32
|
pull_number,
|
|
30
33
|
per_page: 100,
|
|
31
34
|
page,
|
|
32
35
|
})),
|
|
33
|
-
|
|
36
|
+
paginateAllDetailed((page) => octokit.pulls.listReviewComments({
|
|
34
37
|
owner,
|
|
35
38
|
repo,
|
|
36
39
|
pull_number,
|
|
37
40
|
per_page: 100,
|
|
38
41
|
page,
|
|
39
42
|
})),
|
|
40
|
-
|
|
43
|
+
paginateAllDetailed((page) => octokit.issues.listComments({
|
|
41
44
|
owner,
|
|
42
45
|
repo,
|
|
43
46
|
issue_number: pull_number,
|
|
@@ -45,6 +48,13 @@ export async function fetchPRCommentBundle(octokit, prUrl, githubUsername) {
|
|
|
45
48
|
page,
|
|
46
49
|
})),
|
|
47
50
|
]);
|
|
51
|
+
const { items: reviews } = reviewsResult;
|
|
52
|
+
const { items: reviewComments } = reviewCommentsResult;
|
|
53
|
+
const { items: issueComments } = issueCommentsResult;
|
|
54
|
+
const truncated = reviewsResult.truncated || reviewCommentsResult.truncated || issueCommentsResult.truncated;
|
|
55
|
+
if (truncated) {
|
|
56
|
+
warn(MODULE, `Comment streams truncated at pagination cap for ${repoFull}#${pull_number}; bundle is partial`);
|
|
57
|
+
}
|
|
48
58
|
const ownLogin = githubUsername.toLowerCase();
|
|
49
59
|
/**
|
|
50
60
|
* Drop entries that aren't useful corpus: the user's own comments, bots,
|
|
@@ -97,6 +107,7 @@ export async function fetchPRCommentBundle(octokit, prUrl, githubUsername) {
|
|
|
97
107
|
body: fence(c.body ?? '', 'pr-issue-comment', c.user?.login ?? '', c.author_association ?? 'NONE'),
|
|
98
108
|
createdAt: c.created_at ?? '',
|
|
99
109
|
})),
|
|
110
|
+
...(truncated ? { truncated: true } : {}),
|
|
100
111
|
};
|
|
101
112
|
}
|
|
102
113
|
export async function fetchPRCommentBundlesBatch(octokit, prUrls, githubUsername, concurrency = DEFAULT_BATCH_CONCURRENCY) {
|
|
@@ -40,6 +40,8 @@ export interface FetchPRsResult {
|
|
|
40
40
|
* - Post-fetch viewer-mismatch guardrail (configured username differs
|
|
41
41
|
* from the authenticated viewer when the search returned zero PRs).
|
|
42
42
|
* - Search API 1000-result truncation (#1057 M25).
|
|
43
|
+
* - Per-PR comment pagination truncation (#1456) — newest comments
|
|
44
|
+
* dropped, so unresponded-comment detection may be incomplete.
|
|
43
45
|
* Callers (daily, dashboard) surface these so users see the signal.
|
|
44
46
|
*/
|
|
45
47
|
warnings?: string[];
|
package/dist/core/pr-monitor.js
CHANGED
|
@@ -19,11 +19,11 @@ import { parseGitHubUrl, extractOwnerRepo, isOwnRepo } from './urls.js';
|
|
|
19
19
|
import { DEFAULT_CONCURRENCY, runWorkerPool } from './concurrency.js';
|
|
20
20
|
import { determineStatus } from './status-determination.js';
|
|
21
21
|
import { ConfigurationError, ValidationError, errorMessage, getHttpStatusCode, isInvalidUserSearchError, isRateLimitOrAuthError, } from './errors.js';
|
|
22
|
-
import {
|
|
22
|
+
import { paginateAllDetailed } from './pagination.js';
|
|
23
23
|
import { debug, warn, timed } from './logger.js';
|
|
24
24
|
import { getHttpCache, cachedRequest } from './http-cache.js';
|
|
25
25
|
import { categorizeCIStatus, classifyFailingChecks, getCIStatus } from './ci-analysis.js';
|
|
26
|
-
import { determineReviewDecision, getLatestChangesRequestedDate, checkUnrespondedComments, } from './review-analysis.js';
|
|
26
|
+
import { determineReviewDecision, getLatestChangesRequestedDate, checkUnrespondedComments, getFirstMaintainerResponseAt, } from './review-analysis.js';
|
|
27
27
|
import { analyzeChecklist } from './checklist-analysis.js';
|
|
28
28
|
import { extractMaintainerActionHints } from './maintainer-analysis.js';
|
|
29
29
|
import { computeDisplayLabel } from './display-utils.js';
|
|
@@ -239,7 +239,7 @@ export class PRMonitor {
|
|
|
239
239
|
await runWorkerPool(filteredItems, async (item) => {
|
|
240
240
|
try {
|
|
241
241
|
debug('pr-monitor', `Fetching details for ${item.html_url}`);
|
|
242
|
-
const pr = await this.fetchPRDetails(item.html_url);
|
|
242
|
+
const pr = await this.fetchPRDetails(item.html_url, warnings);
|
|
243
243
|
if (pr)
|
|
244
244
|
prs.push(pr);
|
|
245
245
|
}
|
|
@@ -261,7 +261,7 @@ export class PRMonitor {
|
|
|
261
261
|
/**
|
|
262
262
|
* Fetch detailed information for a single PR
|
|
263
263
|
*/
|
|
264
|
-
async fetchPRDetails(prUrl) {
|
|
264
|
+
async fetchPRDetails(prUrl, warnings) {
|
|
265
265
|
const parsed = parseGitHubUrl(prUrl);
|
|
266
266
|
if (!parsed || parsed.type !== 'pull') {
|
|
267
267
|
throw new ValidationError(`Invalid PR URL format: ${prUrl}`);
|
|
@@ -271,11 +271,11 @@ export class PRMonitor {
|
|
|
271
271
|
// Fetch PR data, comments, reviews, and inline review comments in parallel.
|
|
272
272
|
// listReviewComments is non-critical (used for self-reply detection), so degrade
|
|
273
273
|
// gracefully on failure rather than dropping the entire PR (#199).
|
|
274
|
-
const [prResponse,
|
|
274
|
+
const [prResponse, commentsResult, reviewsResponse, reviewCommentsResult] = await Promise.all([
|
|
275
275
|
this.octokit.pulls.get({ owner, repo, pull_number: number }),
|
|
276
|
-
|
|
276
|
+
paginateAllDetailed((page) => this.octokit.issues.listComments({ owner, repo, issue_number: number, per_page: 100, page })),
|
|
277
277
|
this.octokit.pulls.listReviews({ owner, repo, pull_number: number }),
|
|
278
|
-
|
|
278
|
+
paginateAllDetailed((page) => this.octokit.pulls.listReviewComments({ owner, repo, pull_number: number, per_page: 100, page })).catch((err) => {
|
|
279
279
|
const status = getHttpStatusCode(err);
|
|
280
280
|
// Rate limit errors must propagate — silently swallowing them hides
|
|
281
281
|
// a systemic problem and produces misleading results (#229).
|
|
@@ -289,7 +289,7 @@ export class PRMonitor {
|
|
|
289
289
|
}
|
|
290
290
|
// Non-rate-limit 403 (DMCA, private repo, SSO) — degrade gracefully
|
|
291
291
|
warn('pr-monitor', `403 fetching review comments for ${owner}/${repo}#${number}: ${msg}`);
|
|
292
|
-
return [];
|
|
292
|
+
return { items: [], truncated: false };
|
|
293
293
|
}
|
|
294
294
|
if (status === 404) {
|
|
295
295
|
debug('pr-monitor', `Review comments 404 for ${owner}/${repo}#${number} (likely no inline comments)`);
|
|
@@ -297,9 +297,21 @@ export class PRMonitor {
|
|
|
297
297
|
else {
|
|
298
298
|
warn('pr-monitor', `Failed to fetch review comments for ${owner}/${repo}#${number} (status ${status ?? 'unknown'}): self-reply detection will be skipped`);
|
|
299
299
|
}
|
|
300
|
-
return [];
|
|
300
|
+
return { items: [], truncated: false };
|
|
301
301
|
}),
|
|
302
302
|
]);
|
|
303
|
+
const { items: comments } = commentsResult;
|
|
304
|
+
const { items: reviewComments } = reviewCommentsResult;
|
|
305
|
+
// Surface pagination truncation through the caller's warnings channel
|
|
306
|
+
// (#1456): comments arrive oldest-first, so hitting the cap drops the
|
|
307
|
+
// NEWEST maintainer feedback — status determination (unresponded-comment
|
|
308
|
+
// detection) may be wrong for this PR.
|
|
309
|
+
if (commentsResult.truncated || reviewCommentsResult.truncated) {
|
|
310
|
+
const message = `Comment pagination cap reached for ${owner}/${repo}#${number}; the newest comments were not ` +
|
|
311
|
+
`fetched and unresponded-comment detection may be incomplete.`;
|
|
312
|
+
warnings?.push(message);
|
|
313
|
+
warn(MODULE, message);
|
|
314
|
+
}
|
|
303
315
|
const ghPR = prResponse.data;
|
|
304
316
|
const reviews = reviewsResponse.data;
|
|
305
317
|
// Determine review decision (delegated to review-analysis module)
|
|
@@ -308,6 +320,10 @@ export class PRMonitor {
|
|
|
308
320
|
const mergeConflict = hasMergeConflict(ghPR.mergeable, ghPR.mergeable_state);
|
|
309
321
|
// Check if there's an unresponded maintainer comment (delegated to review-analysis module)
|
|
310
322
|
const { hasUnrespondedComment, lastMaintainerComment } = checkUnrespondedComments(comments, reviews, reviewComments, config.githubUsername);
|
|
323
|
+
// Earliest maintainer response, computed from the comment/review timeline
|
|
324
|
+
// already in memory (#1461). Rides into the persisted digest so the
|
|
325
|
+
// outcome ledger can recover it after the PR merges or closes.
|
|
326
|
+
const firstMaintainerResponseAt = getFirstMaintainerResponseAt(comments, reviews, config.githubUsername);
|
|
311
327
|
// Fetch CI status and (conditionally) latest commit date in parallel
|
|
312
328
|
// We need the commit date when hasUnrespondedComment is true (to distinguish
|
|
313
329
|
// "needs_response" from "waiting_on_maintainer") OR when reviewDecision is "changes_requested"
|
|
@@ -391,6 +407,7 @@ export class PRMonitor {
|
|
|
391
407
|
actionReasons,
|
|
392
408
|
createdAt: ghPR.created_at,
|
|
393
409
|
updatedAt: ghPR.updated_at,
|
|
410
|
+
firstMaintainerResponseAt,
|
|
394
411
|
daysSinceActivity,
|
|
395
412
|
ciStatus,
|
|
396
413
|
failingCheckNames,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* and computing aggregate statistics. Mutation functions modify
|
|
5
5
|
* the passed state object in place; query functions are pure.
|
|
6
6
|
*
|
|
7
|
-
* **User-facing reference:** `docs/repo-
|
|
7
|
+
* **User-facing reference:** `docs/repo-scores.md` — plain-language
|
|
8
8
|
* explanation of the formula and what a given score means.
|
|
9
9
|
*/
|
|
10
10
|
import { AgentState, RepoScore, RepoScoreUpdate, StoredMergedPR, StoredClosedPR } from './types.js';
|
|
@@ -21,7 +21,7 @@ import { AgentState, RepoScore, RepoScoreUpdate, StoredMergedPR, StoredClosedPR
|
|
|
21
21
|
* − (hasHostileComments ? HOSTILITY_PENALTY : 0)
|
|
22
22
|
* clamped to [SCORE_MIN, SCORE_MAX].
|
|
23
23
|
*
|
|
24
|
-
* See `docs/repo-
|
|
24
|
+
* See `docs/repo-scores.md` for user-facing intent and what a given
|
|
25
25
|
* score means in practice.
|
|
26
26
|
*/
|
|
27
27
|
export declare function calculateScore(repoScore: RepoScore): number;
|