@oss-autopilot/core 3.13.3 → 3.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli-registry.js +50 -83
- package/dist/cli.bundle.cjs +110 -107
- package/dist/cli.js +17 -3
- 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 +35 -7
- 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 +155 -222
- 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.js +4 -0
- 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 +4 -3
- package/dist/core/index.js +4 -3
- 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/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 +89 -6
- package/dist/formatters/json.js +65 -1
- package/package.json +2 -2
- package/dist/commands/shelve.d.ts +0 -45
- package/dist/commands/shelve.js +0 -54
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* re-exports them at the bottom so existing imports keep working without
|
|
15
15
|
* a sweep.
|
|
16
16
|
*/
|
|
17
|
+
import type { AttentionSummary } from './pr-attention.js';
|
|
17
18
|
import type { FetchedPR, FetchedPRStatus, StalenessTier, ActionReason, AgentState, ShelvedPRRef, ComputedRepoSignals, RepoGroup, CommentedIssue, CapacityAssessment, ActionableIssue, ActionMenu, StarFilter } from './types.js';
|
|
18
19
|
/**
|
|
19
20
|
* Statuses indicating action needed from the contributor.
|
|
@@ -38,9 +39,13 @@ export declare const STALE_STATUSES: ReadonlySet<StalenessTier>;
|
|
|
38
39
|
*
|
|
39
40
|
* @param prs - The fetched PR list to apply overrides to
|
|
40
41
|
* @param state - Current agent state containing status overrides
|
|
42
|
+
* @param failures - Optional collector (#1448): a message is pushed for each
|
|
43
|
+
* PR whose override lookup threw (that PR keeps its un-overridden status).
|
|
44
|
+
* Call sites surface these in warnings[]/partialFailures so the silently
|
|
45
|
+
* wrong status is visible in the envelope, not just stderr.
|
|
41
46
|
* @returns New PR array with overrides applied (original array is not mutated)
|
|
42
47
|
*/
|
|
43
|
-
export declare function applyStatusOverrides(prs: FetchedPR[], state: Readonly<AgentState
|
|
48
|
+
export declare function applyStatusOverrides(prs: FetchedPR[], state: Readonly<AgentState>, failures?: string[]): FetchedPR[];
|
|
44
49
|
/**
|
|
45
50
|
* Project a PR to a lightweight ShelvedPRRef for digest output.
|
|
46
51
|
* Only the fields needed for display are retained, reducing JSON payload size.
|
|
@@ -61,6 +66,22 @@ export declare function toShelvedPRRef(pr: ShelvedPRRef): ShelvedPRRef;
|
|
|
61
66
|
* @returns Star filter with minimum threshold and known counts, or undefined on first run
|
|
62
67
|
*/
|
|
63
68
|
export declare function buildStarFilter(state: Readonly<AgentState>): StarFilter | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Look up a just-merged/closed PR's `firstMaintainerResponseAt` from the
|
|
71
|
+
* previous run's persisted digest (#1461). At detection time the PR is no
|
|
72
|
+
* longer open, so it cannot be enriched without new API calls — but if it
|
|
73
|
+
* was open during a prior enriched run, the persisted digest's openPRs carry
|
|
74
|
+
* the timestamp. Best-effort: returns undefined when the PR never appeared
|
|
75
|
+
* in a persisted digest (e.g. opened and merged between runs, or the digest
|
|
76
|
+
* predates the field).
|
|
77
|
+
*
|
|
78
|
+
* Lives in core/daily-logic.ts so both ledger writers (daily Phase 3 and
|
|
79
|
+
* dashboard-data) share one lookup. Callers must read `state.lastDigest`
|
|
80
|
+
* BEFORE the current run overwrites it via setLastDigest.
|
|
81
|
+
*/
|
|
82
|
+
export declare function firstMaintainerResponseFromDigest(digest: {
|
|
83
|
+
openPRs: FetchedPR[];
|
|
84
|
+
} | undefined, url: string): string | undefined;
|
|
64
85
|
/**
|
|
65
86
|
* Group PRs by repository (#80).
|
|
66
87
|
* Ensures one agent per repo during parallel dispatch, preventing branch checkout conflicts.
|
|
@@ -108,7 +129,9 @@ export declare function collectActionableIssues(prs: FetchedPR[], lastDigestAt?:
|
|
|
108
129
|
* @param actionableIssues - Issues requiring attention
|
|
109
130
|
* @param capacity - Current capacity assessment
|
|
110
131
|
* @param commentedIssues - Issues with comment activity
|
|
132
|
+
* @param attention - Attention bucket counts; non-zero stuck-CI / dormant-followup buckets add a follow_up item
|
|
133
|
+
* @param unextractedMergeCount - Recently merged PRs whose learnings have not been extracted yet (#1463); non-zero adds an extract_learnings item
|
|
111
134
|
* @returns Action menu with context flags for orchestration
|
|
112
135
|
*/
|
|
113
|
-
export declare function computeActionMenu(actionableIssues: ActionableIssue[], capacity: CapacityAssessment, commentedIssues?: CommentedIssue[]): ActionMenu;
|
|
136
|
+
export declare function computeActionMenu(actionableIssues: ActionableIssue[], capacity: CapacityAssessment, commentedIssues?: CommentedIssue[], attention?: Pick<AttentionSummary, 'stuckCI' | 'dormantFollowup'>, unextractedMergeCount?: number): ActionMenu;
|
|
114
137
|
export { formatActionHint, formatBriefSummary, formatSummary, printDigest } from '../commands/daily-render.js';
|
package/dist/core/daily-logic.js
CHANGED
|
@@ -52,9 +52,13 @@ const VALID_OVERRIDE_STATUSES = new Set(['needs_addressing', 'waiting_on_maintai
|
|
|
52
52
|
*
|
|
53
53
|
* @param prs - The fetched PR list to apply overrides to
|
|
54
54
|
* @param state - Current agent state containing status overrides
|
|
55
|
+
* @param failures - Optional collector (#1448): a message is pushed for each
|
|
56
|
+
* PR whose override lookup threw (that PR keeps its un-overridden status).
|
|
57
|
+
* Call sites surface these in warnings[]/partialFailures so the silently
|
|
58
|
+
* wrong status is visible in the envelope, not just stderr.
|
|
55
59
|
* @returns New PR array with overrides applied (original array is not mutated)
|
|
56
60
|
*/
|
|
57
|
-
export function applyStatusOverrides(prs, state) {
|
|
61
|
+
export function applyStatusOverrides(prs, state, failures) {
|
|
58
62
|
const overrides = state.config.statusOverrides;
|
|
59
63
|
if (!overrides || Object.keys(overrides).length === 0)
|
|
60
64
|
return prs;
|
|
@@ -82,7 +86,9 @@ export function applyStatusOverrides(prs, state) {
|
|
|
82
86
|
return { ...pr, status: override.status, waitReason: undefined, actionReason: 'needs_response' };
|
|
83
87
|
}
|
|
84
88
|
catch (err) {
|
|
85
|
-
|
|
89
|
+
const message = `Failed to apply status override for ${pr.url}: ${errorMessage(err)}`;
|
|
90
|
+
warn('daily-logic', message);
|
|
91
|
+
failures?.push(message);
|
|
86
92
|
return pr;
|
|
87
93
|
}
|
|
88
94
|
});
|
|
@@ -152,6 +158,22 @@ export function buildStarFilter(state) {
|
|
|
152
158
|
return undefined;
|
|
153
159
|
return { minStars, knownStarCounts };
|
|
154
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Look up a just-merged/closed PR's `firstMaintainerResponseAt` from the
|
|
163
|
+
* previous run's persisted digest (#1461). At detection time the PR is no
|
|
164
|
+
* longer open, so it cannot be enriched without new API calls — but if it
|
|
165
|
+
* was open during a prior enriched run, the persisted digest's openPRs carry
|
|
166
|
+
* the timestamp. Best-effort: returns undefined when the PR never appeared
|
|
167
|
+
* in a persisted digest (e.g. opened and merged between runs, or the digest
|
|
168
|
+
* predates the field).
|
|
169
|
+
*
|
|
170
|
+
* Lives in core/daily-logic.ts so both ledger writers (daily Phase 3 and
|
|
171
|
+
* dashboard-data) share one lookup. Callers must read `state.lastDigest`
|
|
172
|
+
* BEFORE the current run overwrites it via setLastDigest.
|
|
173
|
+
*/
|
|
174
|
+
export function firstMaintainerResponseFromDigest(digest, url) {
|
|
175
|
+
return digest?.openPRs.find((pr) => pr.url === url)?.firstMaintainerResponseAt;
|
|
176
|
+
}
|
|
155
177
|
/**
|
|
156
178
|
* Group PRs by repository (#80).
|
|
157
179
|
* Ensures one agent per repo during parallel dispatch, preventing branch checkout conflicts.
|
|
@@ -328,9 +350,11 @@ export function collectActionableIssues(prs, lastDigestAt) {
|
|
|
328
350
|
* @param actionableIssues - Issues requiring attention
|
|
329
351
|
* @param capacity - Current capacity assessment
|
|
330
352
|
* @param commentedIssues - Issues with comment activity
|
|
353
|
+
* @param attention - Attention bucket counts; non-zero stuck-CI / dormant-followup buckets add a follow_up item
|
|
354
|
+
* @param unextractedMergeCount - Recently merged PRs whose learnings have not been extracted yet (#1463); non-zero adds an extract_learnings item
|
|
331
355
|
* @returns Action menu with context flags for orchestration
|
|
332
356
|
*/
|
|
333
|
-
export function computeActionMenu(actionableIssues, capacity, commentedIssues = []) {
|
|
357
|
+
export function computeActionMenu(actionableIssues, capacity, commentedIssues = [], attention, unextractedMergeCount) {
|
|
334
358
|
const issueResponses = commentedIssues.filter((i) => i.status === 'new_response');
|
|
335
359
|
const items = [];
|
|
336
360
|
const hasActionableIssues = actionableIssues.length > 0;
|
|
@@ -355,6 +379,37 @@ export function computeActionMenu(actionableIssues, capacity, commentedIssues =
|
|
|
355
379
|
description: 'Maintainers responded to your comments on issues',
|
|
356
380
|
});
|
|
357
381
|
}
|
|
382
|
+
// Follow-up nudges (#1462) — the stuck_ci / dormant_followup buckets from
|
|
383
|
+
// summarizeAttentionBuckets get a menu path to workflows/dormant-pr-follow-up.md
|
|
384
|
+
// instead of only a headline count. Emitted only when a bucket is non-zero,
|
|
385
|
+
// so menus without stuck/dormant PRs are unchanged.
|
|
386
|
+
const stuckCI = attention?.stuckCI ?? 0;
|
|
387
|
+
const dormantFollowup = attention?.dormantFollowup ?? 0;
|
|
388
|
+
if (stuckCI > 0 || dormantFollowup > 0) {
|
|
389
|
+
const parts = [];
|
|
390
|
+
if (stuckCI > 0)
|
|
391
|
+
parts.push(`${stuckCI} stuck-CI`);
|
|
392
|
+
if (dormantFollowup > 0)
|
|
393
|
+
parts.push(`${dormantFollowup} dormant`);
|
|
394
|
+
const total = stuckCI + dormantFollowup;
|
|
395
|
+
items.push({
|
|
396
|
+
key: 'follow_up',
|
|
397
|
+
label: `Follow up on ${parts.join(' and ')} PR${total === 1 ? '' : 's'}`,
|
|
398
|
+
description: 'Waiting PRs that may need a nudge (pending CI or no review activity)',
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
// Extract-learnings nudge (#1463) — recently merged PRs whose ledger
|
|
402
|
+
// entries carry no learningsExtractedAt stamp get a menu path to
|
|
403
|
+
// workflows/extract-learnings.md. The nudge persists across runs for the
|
|
404
|
+
// recently-merged window until the extraction runs, then self-clears.
|
|
405
|
+
const unextracted = unextractedMergeCount ?? 0;
|
|
406
|
+
if (unextracted > 0) {
|
|
407
|
+
items.push({
|
|
408
|
+
key: 'extract_learnings',
|
|
409
|
+
label: `Extract learnings from ${unextracted} recently merged PR${unextracted === 1 ? '' : 's'}`,
|
|
410
|
+
description: 'Distill maintainer review feedback into per-repo contribution guidelines',
|
|
411
|
+
});
|
|
412
|
+
}
|
|
358
413
|
// The orchestration layer (commands/oss.md Action Menu section) may insert issue-list
|
|
359
414
|
// options before the search item when a curated list is available.
|
|
360
415
|
const searchItem = {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One health source and one warning renderer for gist persistence (#1444).
|
|
3
|
+
*
|
|
4
|
+
* "Are we degraded?" used to be answered by three independent predicates
|
|
5
|
+
* (`config.persistence === 'gist' && !isGistMode()`, the
|
|
6
|
+
* `GistPersistenceStatus` union, `isGistMode() && isGistDegraded()`), each
|
|
7
|
+
* re-rendering its own warning prose. `StateManager.getGistHealth()` is now
|
|
8
|
+
* the single predicate for live-manager surfaces (daily warnings, dashboard
|
|
9
|
+
* probes), and {@link renderGistWarning} is the single prose renderer for
|
|
10
|
+
* every surface (CLI envelope, MCP injection, bootstrapGistBestEffort,
|
|
11
|
+
* daily warnings).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Degradation causes a live StateManager can attest to from its own fields
|
|
15
|
+
* (see `StateManager.getGistHealth()`):
|
|
16
|
+
*
|
|
17
|
+
* - `configured-but-local`: config says `persistence: 'gist'` but this
|
|
18
|
+
* process's manager has no gist store — init never ran here, or fell back
|
|
19
|
+
* to a local-only manager. Mutations save locally and will not sync.
|
|
20
|
+
* - `bootstrap-degraded`: the manager IS gist-backed, but the bootstrap fell
|
|
21
|
+
* back to the local cache and the store is disarmed (#1443) — reads may be
|
|
22
|
+
* stale and pushes fail.
|
|
23
|
+
*/
|
|
24
|
+
export type GistHealthDegradedCause = 'configured-but-local' | 'bootstrap-degraded';
|
|
25
|
+
/**
|
|
26
|
+
* Causes {@link renderGistWarning} can name. Superset of
|
|
27
|
+
* {@link GistHealthDegradedCause}: the process-level bootstrap outcomes are
|
|
28
|
+
* observed by config-peek paths (`ensureGistPersistence`, the MCP init memo)
|
|
29
|
+
* that run before or around manager creation, where there is no
|
|
30
|
+
* StateManager to ask:
|
|
31
|
+
*
|
|
32
|
+
* - `no-token`: gist is configured but no GitHub token was available.
|
|
33
|
+
* - `state-unreadable`: the state file could not be read for this attempt.
|
|
34
|
+
* - `init-fallback`: init resolved degraded — `ensureGistPersistence`
|
|
35
|
+
* reported `'degraded'` (transient network fallback, or a #1443 degraded
|
|
36
|
+
* bootstrap, which it deliberately conflates at that layer).
|
|
37
|
+
*/
|
|
38
|
+
export type GistWarningCause = GistHealthDegradedCause | 'no-token' | 'state-unreadable' | 'init-fallback';
|
|
39
|
+
/**
|
|
40
|
+
* Snapshot of gist persistence health from the one source of truth
|
|
41
|
+
* (`StateManager.getGistHealth()`), shaped per #1444.
|
|
42
|
+
*/
|
|
43
|
+
export interface GistHealth {
|
|
44
|
+
/** `'gist'` when the manager is gist-backed (a GistStateStore is attached),
|
|
45
|
+
* `'local'` otherwise — regardless of what the config asks for. */
|
|
46
|
+
mode: 'local' | 'gist';
|
|
47
|
+
/** `null` when healthy: either genuinely local (config agrees) or
|
|
48
|
+
* gist-backed with an armed store. Non-null when this process is not
|
|
49
|
+
* reliably syncing to the Gist. */
|
|
50
|
+
degraded: null | {
|
|
51
|
+
cause: GistHealthDegradedCause;
|
|
52
|
+
/** ISO timestamp when the degradation was first observed, when known
|
|
53
|
+
* (a degraded bootstrap seeds the staleness marker; a
|
|
54
|
+
* configured-but-local manager has no marker to date it by). */
|
|
55
|
+
since?: string;
|
|
56
|
+
/** Whether a later init/recovery attempt can heal this without user
|
|
57
|
+
* action. Both manager-level causes are recoverable by re-running
|
|
58
|
+
* `ensureGistPersistence` with a token (#1415/#1443); a PERMANENT halt
|
|
59
|
+
* (e.g. token lacking the gist scope) surfaces as a thrown
|
|
60
|
+
* ConfigurationError at the surface that attempted recovery — the
|
|
61
|
+
* manager itself cannot observe it, so it never reports false here. */
|
|
62
|
+
recoverable: boolean;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Render the one gist-degradation warning shown by every surface (#1444).
|
|
67
|
+
*
|
|
68
|
+
* Accepts either a known {@link GistWarningCause} or `{ reason }` for the
|
|
69
|
+
* hard-error paths (ConfigurationError repair carve-outs) whose reason text
|
|
70
|
+
* is built from the underlying error.
|
|
71
|
+
*
|
|
72
|
+
* @param cause - Known cause, or a free-form reason for hard init errors.
|
|
73
|
+
* @param opts.retryHint - Optional trailing sentence describing the
|
|
74
|
+
* surface-specific retry behavior (e.g. the MCP's "Will retry on the next
|
|
75
|
+
* tool call.").
|
|
76
|
+
*/
|
|
77
|
+
export declare function renderGistWarning(cause: GistWarningCause | {
|
|
78
|
+
reason: string;
|
|
79
|
+
}, opts?: {
|
|
80
|
+
retryHint?: string;
|
|
81
|
+
}): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One health source and one warning renderer for gist persistence (#1444).
|
|
3
|
+
*
|
|
4
|
+
* "Are we degraded?" used to be answered by three independent predicates
|
|
5
|
+
* (`config.persistence === 'gist' && !isGistMode()`, the
|
|
6
|
+
* `GistPersistenceStatus` union, `isGistMode() && isGistDegraded()`), each
|
|
7
|
+
* re-rendering its own warning prose. `StateManager.getGistHealth()` is now
|
|
8
|
+
* the single predicate for live-manager surfaces (daily warnings, dashboard
|
|
9
|
+
* probes), and {@link renderGistWarning} is the single prose renderer for
|
|
10
|
+
* every surface (CLI envelope, MCP injection, bootstrapGistBestEffort,
|
|
11
|
+
* daily warnings).
|
|
12
|
+
*/
|
|
13
|
+
/** Cause → human reason, the MCP's former `gistWarningText` mapping promoted
|
|
14
|
+
* to core (#1444). Keep these one-line and append-only: surfaces interpolate
|
|
15
|
+
* them mid-sentence. */
|
|
16
|
+
const GIST_DEGRADED_REASON = {
|
|
17
|
+
'no-token': 'no GitHub token was available',
|
|
18
|
+
'state-unreadable': 'the state file could not be read',
|
|
19
|
+
'init-fallback': 'initialization hit a transient network failure',
|
|
20
|
+
'configured-but-local': 'this process is running local-only (Gist init has not succeeded)',
|
|
21
|
+
'bootstrap-degraded': 'the Gist bootstrap fell back to the local cache (reads may be stale and pushes are failing)',
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Render the one gist-degradation warning shown by every surface (#1444).
|
|
25
|
+
*
|
|
26
|
+
* Accepts either a known {@link GistWarningCause} or `{ reason }` for the
|
|
27
|
+
* hard-error paths (ConfigurationError repair carve-outs) whose reason text
|
|
28
|
+
* is built from the underlying error.
|
|
29
|
+
*
|
|
30
|
+
* @param cause - Known cause, or a free-form reason for hard init errors.
|
|
31
|
+
* @param opts.retryHint - Optional trailing sentence describing the
|
|
32
|
+
* surface-specific retry behavior (e.g. the MCP's "Will retry on the next
|
|
33
|
+
* tool call.").
|
|
34
|
+
*/
|
|
35
|
+
export function renderGistWarning(cause, opts = {}) {
|
|
36
|
+
const reason = typeof cause === 'string' ? GIST_DEGRADED_REASON[cause] : cause.reason;
|
|
37
|
+
return (`Gist persistence is configured but ${reason}; writes are LOCAL-ONLY and may be ` +
|
|
38
|
+
`overwritten by the next successful Gist sync.${opts.retryHint ? ` ${opts.retryHint}` : ''}`);
|
|
39
|
+
}
|
|
@@ -232,7 +232,9 @@ export declare class GistStateStore {
|
|
|
232
232
|
push(): Promise<boolean>;
|
|
233
233
|
/**
|
|
234
234
|
* Re-fetch the Gist and update the in-memory cache.
|
|
235
|
-
* Throttled to at most once per 30 seconds
|
|
235
|
+
* Throttled to at most once per 30 seconds — ATTEMPTS, not just successes:
|
|
236
|
+
* a failed fetch stamps the throttle too (#1443), so an outage does not
|
|
237
|
+
* turn every SPA poll into an immediate full re-fetch.
|
|
236
238
|
*
|
|
237
239
|
* Returns a discriminated union so callers can tell apart the four
|
|
238
240
|
* outcomes that previously collapsed into a single boolean (#1209 L9):
|
|
@@ -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,22 +3,23 @@
|
|
|
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';
|
package/dist/core/index.js
CHANGED
|
@@ -3,23 +3,24 @@
|
|
|
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';
|
|
@@ -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);
|
|
@@ -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
|