@oss-autopilot/core 3.13.4 → 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 +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 +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 +148 -341
- 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 +81 -7
- 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
|
@@ -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;
|
|
@@ -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 { isBelowMinStars } from './types.js';
|
|
@@ -14,7 +14,7 @@ const MODULE = 'scoring';
|
|
|
14
14
|
// ── Scoring constants (#1054) ─────────────────────────────────────────
|
|
15
15
|
// Previously inlined as magic numbers in `calculateScore`. Extracted with
|
|
16
16
|
// rationale comments so the formula is auditable without source spelunking.
|
|
17
|
-
// Changing any of these is a behavior change — update docs/repo-
|
|
17
|
+
// Changing any of these is a behavior change — update docs/repo-scores.md
|
|
18
18
|
// and the tests below in lockstep.
|
|
19
19
|
/** Starting point before any signals are applied. Deliberately optimistic so first-time repos aren't punished. */
|
|
20
20
|
const BASE_SCORE = 5;
|
|
@@ -66,7 +66,7 @@ function createDefaultRepoScore(repo) {
|
|
|
66
66
|
* − (hasHostileComments ? HOSTILITY_PENALTY : 0)
|
|
67
67
|
* clamped to [SCORE_MIN, SCORE_MAX].
|
|
68
68
|
*
|
|
69
|
-
* See `docs/repo-
|
|
69
|
+
* See `docs/repo-scores.md` for user-facing intent and what a given
|
|
70
70
|
* score means in practice.
|
|
71
71
|
*/
|
|
72
72
|
export function calculateScore(repoScore) {
|
package/dist/core/repo-vet.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* supply pre-fetched repo signals so the score is reproducible
|
|
12
12
|
* against fixture data and offline replays.
|
|
13
13
|
*
|
|
14
|
-
* Rubric reference: docs/repo-
|
|
14
|
+
* Rubric reference: docs/repo-scores.md.
|
|
15
15
|
*/
|
|
16
16
|
export interface RepoVetInput {
|
|
17
17
|
/** Star count from the GitHub repo metadata. */
|
|
@@ -78,7 +78,7 @@ export interface RepoVetResult {
|
|
|
78
78
|
prTemplate: boolean;
|
|
79
79
|
codeOfConduct: boolean;
|
|
80
80
|
};
|
|
81
|
-
/** Weighted 1-10 score per docs/repo-
|
|
81
|
+
/** Weighted 1-10 score per docs/repo-scores.md. */
|
|
82
82
|
rubricScore: number;
|
|
83
83
|
/** Top-line verdict derived from the score and red-flag overrides. */
|
|
84
84
|
rubricVerdict: RepoVetVerdict;
|
package/dist/core/repo-vet.js
CHANGED
|
@@ -53,6 +53,25 @@ export declare function getInlineCommentBody(reviewId: number, reviewComments: R
|
|
|
53
53
|
* that also posted inline comments (#829).
|
|
54
54
|
*/
|
|
55
55
|
export declare function reviewHasInlineComments(reviewId: number, reviewComments: ReviewComment[]): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Earliest maintainer response timestamp across issue comments and submitted
|
|
58
|
+
* reviews (#1461). A "response" is any comment or review by a non-bot
|
|
59
|
+
* account other than the contributor — acknowledgments and approvals count,
|
|
60
|
+
* since for latency purposes any human maintainer reply is a response.
|
|
61
|
+
* Computed from data fetchPRDetails already holds in memory (zero extra API
|
|
62
|
+
* calls). Returns undefined when no maintainer has responded yet.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getFirstMaintainerResponseAt(comments: Array<{
|
|
65
|
+
user?: {
|
|
66
|
+
login?: string;
|
|
67
|
+
} | null;
|
|
68
|
+
created_at: string;
|
|
69
|
+
}>, reviews: Array<{
|
|
70
|
+
user?: {
|
|
71
|
+
login?: string;
|
|
72
|
+
} | null;
|
|
73
|
+
submitted_at?: string | null;
|
|
74
|
+
}>, username: string): string | undefined;
|
|
56
75
|
/**
|
|
57
76
|
* Check if there are unresponded comments from maintainers.
|
|
58
77
|
* Combines issue comments and review comments into a timeline,
|
|
@@ -97,6 +97,34 @@ export function getInlineCommentBody(reviewId, reviewComments) {
|
|
|
97
97
|
export function reviewHasInlineComments(reviewId, reviewComments) {
|
|
98
98
|
return reviewComments.some((c) => c.pull_request_review_id === reviewId);
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Earliest maintainer response timestamp across issue comments and submitted
|
|
102
|
+
* reviews (#1461). A "response" is any comment or review by a non-bot
|
|
103
|
+
* account other than the contributor — acknowledgments and approvals count,
|
|
104
|
+
* since for latency purposes any human maintainer reply is a response.
|
|
105
|
+
* Computed from data fetchPRDetails already holds in memory (zero extra API
|
|
106
|
+
* calls). Returns undefined when no maintainer has responded yet.
|
|
107
|
+
*/
|
|
108
|
+
export function getFirstMaintainerResponseAt(comments, reviews, username) {
|
|
109
|
+
const usernameLower = username.toLowerCase();
|
|
110
|
+
let earliest;
|
|
111
|
+
const consider = (author, createdAt) => {
|
|
112
|
+
if (!author || !createdAt)
|
|
113
|
+
return;
|
|
114
|
+
if (author.toLowerCase() === usernameLower)
|
|
115
|
+
return;
|
|
116
|
+
if (isBotAuthor(author))
|
|
117
|
+
return;
|
|
118
|
+
if (!earliest || new Date(createdAt).getTime() < new Date(earliest).getTime()) {
|
|
119
|
+
earliest = createdAt;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
for (const comment of comments)
|
|
123
|
+
consider(comment.user?.login, comment.created_at);
|
|
124
|
+
for (const review of reviews)
|
|
125
|
+
consider(review.user?.login, review.submitted_at);
|
|
126
|
+
return earliest;
|
|
127
|
+
}
|
|
100
128
|
/**
|
|
101
129
|
* Check if there are unresponded comments from maintainers.
|
|
102
130
|
* Combines issue comments and review comments into a timeline,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Unknown keys are stripped by default (Zod 4 behavior).
|
|
10
10
|
*/
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
+
import type { FetchedPR } from './types.js';
|
|
12
13
|
export declare const IssueStatusSchema: z.ZodEnum<{
|
|
13
14
|
candidate: "candidate";
|
|
14
15
|
claimed: "claimed";
|
|
@@ -63,6 +64,8 @@ export declare const StoredMergedPRSchema: z.ZodObject<{
|
|
|
63
64
|
url: z.ZodString;
|
|
64
65
|
title: z.ZodString;
|
|
65
66
|
mergedAt: z.ZodString;
|
|
67
|
+
openedAt: z.ZodOptional<z.ZodString>;
|
|
68
|
+
firstMaintainerResponseAt: z.ZodOptional<z.ZodString>;
|
|
66
69
|
commentsFetchedAt: z.ZodOptional<z.ZodString>;
|
|
67
70
|
learningsExtractedAt: z.ZodOptional<z.ZodString>;
|
|
68
71
|
}, z.core.$strip>;
|
|
@@ -70,6 +73,8 @@ export declare const StoredClosedPRSchema: z.ZodObject<{
|
|
|
70
73
|
url: z.ZodString;
|
|
71
74
|
title: z.ZodString;
|
|
72
75
|
closedAt: z.ZodString;
|
|
76
|
+
openedAt: z.ZodOptional<z.ZodString>;
|
|
77
|
+
firstMaintainerResponseAt: z.ZodOptional<z.ZodString>;
|
|
73
78
|
commentsFetchedAt: z.ZodOptional<z.ZodString>;
|
|
74
79
|
learningsExtractedAt: z.ZodOptional<z.ZodString>;
|
|
75
80
|
}, z.core.$strip>;
|
|
@@ -251,6 +256,8 @@ export declare const AgentConfigSchema: z.ZodObject<{
|
|
|
251
256
|
}>>>;
|
|
252
257
|
excludeRepos: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
253
258
|
excludeOrgs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
259
|
+
avoidRepos: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
260
|
+
boostIssueTypes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
254
261
|
trustedProjects: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
255
262
|
githubUsername: z.ZodDefault<z.ZodString>;
|
|
256
263
|
minRepoScoreThreshold: z.ZodDefault<z.ZodNumber>;
|
|
@@ -311,6 +318,7 @@ export declare const ClosedPRSchema: z.ZodObject<{
|
|
|
311
318
|
title: z.ZodString;
|
|
312
319
|
closedAt: z.ZodString;
|
|
313
320
|
closedBy: z.ZodOptional<z.ZodString>;
|
|
321
|
+
openedAt: z.ZodOptional<z.ZodString>;
|
|
314
322
|
}, z.core.$strip>;
|
|
315
323
|
export declare const MergedPRSchema: z.ZodObject<{
|
|
316
324
|
url: z.ZodString;
|
|
@@ -318,6 +326,7 @@ export declare const MergedPRSchema: z.ZodObject<{
|
|
|
318
326
|
number: z.ZodNumber;
|
|
319
327
|
title: z.ZodString;
|
|
320
328
|
mergedAt: z.ZodString;
|
|
329
|
+
openedAt: z.ZodOptional<z.ZodString>;
|
|
321
330
|
}, z.core.$strip>;
|
|
322
331
|
export declare const DailyDigestSummarySchema: z.ZodObject<{
|
|
323
332
|
totalActivePRs: z.ZodNumber;
|
|
@@ -325,11 +334,29 @@ export declare const DailyDigestSummarySchema: z.ZodObject<{
|
|
|
325
334
|
totalMergedAllTime: z.ZodNumber;
|
|
326
335
|
mergeRate: z.ZodNumber;
|
|
327
336
|
}, z.core.$strip>;
|
|
337
|
+
/**
|
|
338
|
+
* Minimal FetchedPR validation for persisted digest arrays (#1456). The
|
|
339
|
+
* persisted digest is rendered by the dashboard from cold start (before any
|
|
340
|
+
* refresh), so the fields it dereferences must be guaranteed present. Only
|
|
341
|
+
* the identity/status core is pinned — required on every real `FetchedPR`
|
|
342
|
+
* since the type's introduction — and `.passthrough()` keeps the dozens of
|
|
343
|
+
* volatile enrichment fields (CI, review, staleness) from rejecting digests
|
|
344
|
+
* persisted by older or newer producers.
|
|
345
|
+
*/
|
|
346
|
+
export declare const FetchedPRSchema: z.ZodObject<{
|
|
347
|
+
url: z.ZodString;
|
|
348
|
+
repo: z.ZodString;
|
|
349
|
+
number: z.ZodNumber;
|
|
350
|
+
status: z.ZodEnum<{
|
|
351
|
+
needs_addressing: "needs_addressing";
|
|
352
|
+
waiting_on_maintainer: "waiting_on_maintainer";
|
|
353
|
+
}>;
|
|
354
|
+
}, z.core.$loose>;
|
|
328
355
|
export declare const DailyDigestSchema: z.ZodObject<{
|
|
329
356
|
generatedAt: z.ZodString;
|
|
330
|
-
openPRs: z.
|
|
331
|
-
needsAddressingPRs: z.
|
|
332
|
-
waitingOnMaintainerPRs: z.
|
|
357
|
+
openPRs: z.ZodType<FetchedPR[], unknown, z.core.$ZodTypeInternals<FetchedPR[], unknown>>;
|
|
358
|
+
needsAddressingPRs: z.ZodType<FetchedPR[], unknown, z.core.$ZodTypeInternals<FetchedPR[], unknown>>;
|
|
359
|
+
waitingOnMaintainerPRs: z.ZodType<FetchedPR[], unknown, z.core.$ZodTypeInternals<FetchedPR[], unknown>>;
|
|
333
360
|
recentlyClosedPRs: z.ZodArray<z.ZodObject<{
|
|
334
361
|
url: z.ZodString;
|
|
335
362
|
repo: z.ZodString;
|
|
@@ -337,6 +364,7 @@ export declare const DailyDigestSchema: z.ZodObject<{
|
|
|
337
364
|
title: z.ZodString;
|
|
338
365
|
closedAt: z.ZodString;
|
|
339
366
|
closedBy: z.ZodOptional<z.ZodString>;
|
|
367
|
+
openedAt: z.ZodOptional<z.ZodString>;
|
|
340
368
|
}, z.core.$strip>>;
|
|
341
369
|
recentlyMergedPRs: z.ZodArray<z.ZodObject<{
|
|
342
370
|
url: z.ZodString;
|
|
@@ -344,6 +372,7 @@ export declare const DailyDigestSchema: z.ZodObject<{
|
|
|
344
372
|
number: z.ZodNumber;
|
|
345
373
|
title: z.ZodString;
|
|
346
374
|
mergedAt: z.ZodString;
|
|
375
|
+
openedAt: z.ZodOptional<z.ZodString>;
|
|
347
376
|
}, z.core.$strip>>;
|
|
348
377
|
shelvedPRs: z.ZodArray<z.ZodObject<{
|
|
349
378
|
number: z.ZodNumber;
|
|
@@ -413,6 +442,8 @@ export declare const AgentStateSchema: z.ZodObject<{
|
|
|
413
442
|
}>>>;
|
|
414
443
|
excludeRepos: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
415
444
|
excludeOrgs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
445
|
+
avoidRepos: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
446
|
+
boostIssueTypes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
416
447
|
trustedProjects: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
417
448
|
githubUsername: z.ZodDefault<z.ZodString>;
|
|
418
449
|
minRepoScoreThreshold: z.ZodDefault<z.ZodNumber>;
|
|
@@ -462,9 +493,9 @@ export declare const AgentStateSchema: z.ZodObject<{
|
|
|
462
493
|
lastStrategyAt: z.ZodOptional<z.ZodString>;
|
|
463
494
|
lastDigest: z.ZodOptional<z.ZodObject<{
|
|
464
495
|
generatedAt: z.ZodString;
|
|
465
|
-
openPRs: z.
|
|
466
|
-
needsAddressingPRs: z.
|
|
467
|
-
waitingOnMaintainerPRs: z.
|
|
496
|
+
openPRs: z.ZodType<FetchedPR[], unknown, z.core.$ZodTypeInternals<FetchedPR[], unknown>>;
|
|
497
|
+
needsAddressingPRs: z.ZodType<FetchedPR[], unknown, z.core.$ZodTypeInternals<FetchedPR[], unknown>>;
|
|
498
|
+
waitingOnMaintainerPRs: z.ZodType<FetchedPR[], unknown, z.core.$ZodTypeInternals<FetchedPR[], unknown>>;
|
|
468
499
|
recentlyClosedPRs: z.ZodArray<z.ZodObject<{
|
|
469
500
|
url: z.ZodString;
|
|
470
501
|
repo: z.ZodString;
|
|
@@ -472,6 +503,7 @@ export declare const AgentStateSchema: z.ZodObject<{
|
|
|
472
503
|
title: z.ZodString;
|
|
473
504
|
closedAt: z.ZodString;
|
|
474
505
|
closedBy: z.ZodOptional<z.ZodString>;
|
|
506
|
+
openedAt: z.ZodOptional<z.ZodString>;
|
|
475
507
|
}, z.core.$strip>>;
|
|
476
508
|
recentlyMergedPRs: z.ZodArray<z.ZodObject<{
|
|
477
509
|
url: z.ZodString;
|
|
@@ -479,6 +511,7 @@ export declare const AgentStateSchema: z.ZodObject<{
|
|
|
479
511
|
number: z.ZodNumber;
|
|
480
512
|
title: z.ZodString;
|
|
481
513
|
mergedAt: z.ZodString;
|
|
514
|
+
openedAt: z.ZodOptional<z.ZodString>;
|
|
482
515
|
}, z.core.$strip>>;
|
|
483
516
|
shelvedPRs: z.ZodArray<z.ZodObject<{
|
|
484
517
|
number: z.ZodNumber;
|
|
@@ -525,6 +558,8 @@ export declare const AgentStateSchema: z.ZodObject<{
|
|
|
525
558
|
url: z.ZodString;
|
|
526
559
|
title: z.ZodString;
|
|
527
560
|
mergedAt: z.ZodString;
|
|
561
|
+
openedAt: z.ZodOptional<z.ZodString>;
|
|
562
|
+
firstMaintainerResponseAt: z.ZodOptional<z.ZodString>;
|
|
528
563
|
commentsFetchedAt: z.ZodOptional<z.ZodString>;
|
|
529
564
|
learningsExtractedAt: z.ZodOptional<z.ZodString>;
|
|
530
565
|
}, z.core.$strip>>>;
|
|
@@ -532,6 +567,8 @@ export declare const AgentStateSchema: z.ZodObject<{
|
|
|
532
567
|
url: z.ZodString;
|
|
533
568
|
title: z.ZodString;
|
|
534
569
|
closedAt: z.ZodString;
|
|
570
|
+
openedAt: z.ZodOptional<z.ZodString>;
|
|
571
|
+
firstMaintainerResponseAt: z.ZodOptional<z.ZodString>;
|
|
535
572
|
commentsFetchedAt: z.ZodOptional<z.ZodString>;
|
|
536
573
|
learningsExtractedAt: z.ZodOptional<z.ZodString>;
|
|
537
574
|
}, z.core.$strip>>>;
|
|
@@ -44,6 +44,20 @@ export const StoredMergedPRSchema = z.object({
|
|
|
44
44
|
url: z.string(),
|
|
45
45
|
title: z.string(),
|
|
46
46
|
mergedAt: z.string(),
|
|
47
|
+
/**
|
|
48
|
+
* When the PR was opened (#1461). Optional: entries persisted before the
|
|
49
|
+
* outcome ledger existed don't carry it. Sourced from the Search API's
|
|
50
|
+
* `created_at` at merge-detection time — zero extra API calls.
|
|
51
|
+
*/
|
|
52
|
+
openedAt: z.string().optional(),
|
|
53
|
+
/**
|
|
54
|
+
* Earliest maintainer (non-bot, non-author) comment or review on the
|
|
55
|
+
* PR (#1461). Best-effort: only derivable when the PR was enriched while
|
|
56
|
+
* open (looked up from the previous run's persisted digest at detection
|
|
57
|
+
* time), so it is absent for PRs that merged before ever appearing in an
|
|
58
|
+
* enriched run.
|
|
59
|
+
*/
|
|
60
|
+
firstMaintainerResponseAt: z.string().optional(),
|
|
47
61
|
/** When the raw review-comment bundle for this PR was last fetched (#867). */
|
|
48
62
|
// ISO-8601 datetime guards against `markPRCommentsFetched(url, "garbage")`
|
|
49
63
|
// poisoning state through the stamping API (#1209 L4).
|
|
@@ -55,6 +69,13 @@ export const StoredClosedPRSchema = z.object({
|
|
|
55
69
|
url: z.string(),
|
|
56
70
|
title: z.string(),
|
|
57
71
|
closedAt: z.string(),
|
|
72
|
+
/** When the PR was opened (#1461). See StoredMergedPRSchema.openedAt. */
|
|
73
|
+
openedAt: z.string().optional(),
|
|
74
|
+
/**
|
|
75
|
+
* Earliest maintainer response (#1461). See
|
|
76
|
+
* StoredMergedPRSchema.firstMaintainerResponseAt.
|
|
77
|
+
*/
|
|
78
|
+
firstMaintainerResponseAt: z.string().optional(),
|
|
58
79
|
/** When the raw review-comment bundle for this PR was last fetched (#867). */
|
|
59
80
|
// ISO-8601 datetime guards against `markPRCommentsFetched(url, "garbage")`
|
|
60
81
|
// poisoning state through the stamping API (#1209 L4).
|
|
@@ -176,6 +197,21 @@ export const AgentConfigSchema = z.object({
|
|
|
176
197
|
scope: z.array(IssueScopeSchema).optional(),
|
|
177
198
|
excludeRepos: z.array(z.string()).default([]),
|
|
178
199
|
excludeOrgs: z.array(z.string()).optional(),
|
|
200
|
+
/**
|
|
201
|
+
* Repos (owner/repo) to softly downrank in discovery results (#1464).
|
|
202
|
+
* Milder than `excludeRepos`' hard filter: scout pushes matches below
|
|
203
|
+
* equally-recommended candidates but does not remove them, and a strong
|
|
204
|
+
* affinity boost can still outweigh the penalty (scout #168).
|
|
205
|
+
* Threaded to scout via `scout-bridge.ts`.
|
|
206
|
+
*/
|
|
207
|
+
avoidRepos: z.array(z.string()).default([]),
|
|
208
|
+
/**
|
|
209
|
+
* Issue label types (e.g. "bug", "good first issue") to softly boost in
|
|
210
|
+
* discovery ranking, matched case-insensitively against issue labels
|
|
211
|
+
* (scout #168 / #1464). Does not filter results or change viability
|
|
212
|
+
* scores. Threaded to scout via `scout-bridge.ts`.
|
|
213
|
+
*/
|
|
214
|
+
boostIssueTypes: z.array(z.string()).default([]),
|
|
179
215
|
trustedProjects: z.array(z.string()).default([]),
|
|
180
216
|
githubUsername: z.string().default(''),
|
|
181
217
|
minRepoScoreThreshold: z.number().default(4),
|
|
@@ -251,6 +287,12 @@ export const ClosedPRSchema = z.object({
|
|
|
251
287
|
title: z.string(),
|
|
252
288
|
closedAt: z.string(),
|
|
253
289
|
closedBy: z.string().optional(),
|
|
290
|
+
/**
|
|
291
|
+
* When the PR was opened (#1461). Carried from the Search API's
|
|
292
|
+
* `created_at` so close detection can persist it into the outcome ledger.
|
|
293
|
+
* Optional: digests persisted by older producers don't have it.
|
|
294
|
+
*/
|
|
295
|
+
openedAt: z.string().optional(),
|
|
254
296
|
});
|
|
255
297
|
export const MergedPRSchema = z.object({
|
|
256
298
|
url: z.string(),
|
|
@@ -258,6 +300,12 @@ export const MergedPRSchema = z.object({
|
|
|
258
300
|
number: z.number(),
|
|
259
301
|
title: z.string(),
|
|
260
302
|
mergedAt: z.string(),
|
|
303
|
+
/**
|
|
304
|
+
* When the PR was opened (#1461). Carried from the Search API's
|
|
305
|
+
* `created_at` so merge detection can persist it into the outcome ledger.
|
|
306
|
+
* Optional: digests persisted by older producers don't have it.
|
|
307
|
+
*/
|
|
308
|
+
openedAt: z.string().optional(),
|
|
261
309
|
});
|
|
262
310
|
export const DailyDigestSummarySchema = z.object({
|
|
263
311
|
totalActivePRs: z.number(),
|
|
@@ -265,12 +313,41 @@ export const DailyDigestSummarySchema = z.object({
|
|
|
265
313
|
totalMergedAllTime: z.number(),
|
|
266
314
|
mergeRate: z.number(),
|
|
267
315
|
});
|
|
316
|
+
/**
|
|
317
|
+
* Minimal FetchedPR validation for persisted digest arrays (#1456). The
|
|
318
|
+
* persisted digest is rendered by the dashboard from cold start (before any
|
|
319
|
+
* refresh), so the fields it dereferences must be guaranteed present. Only
|
|
320
|
+
* the identity/status core is pinned — required on every real `FetchedPR`
|
|
321
|
+
* since the type's introduction — and `.passthrough()` keeps the dozens of
|
|
322
|
+
* volatile enrichment fields (CI, review, staleness) from rejecting digests
|
|
323
|
+
* persisted by older or newer producers.
|
|
324
|
+
*/
|
|
325
|
+
export const FetchedPRSchema = z
|
|
326
|
+
.object({
|
|
327
|
+
url: z.string(),
|
|
328
|
+
repo: z.string(),
|
|
329
|
+
number: z.number(),
|
|
330
|
+
status: FetchedPRStatusSchema,
|
|
331
|
+
})
|
|
332
|
+
.passthrough();
|
|
333
|
+
/**
|
|
334
|
+
* The digest arrays hold full `FetchedPR` objects at runtime; validation is
|
|
335
|
+
* intentionally limited to the identity/status core above (everything else
|
|
336
|
+
* is ephemeral enrichment that older/newer producers legitimately differ
|
|
337
|
+
* on), so the inferred type is widened back to `FetchedPR[]` for consumers.
|
|
338
|
+
* The cast is sound in both directions: every real `FetchedPR` satisfies the
|
|
339
|
+
* core schema, and `.passthrough()` preserves the enrichment fields on parse
|
|
340
|
+
* output. This mirrors the pre-#1456 `z.array(z.any())` static contract
|
|
341
|
+
* while actually validating the fields the dashboard dereferences.
|
|
342
|
+
*/
|
|
343
|
+
const FetchedPRArraySchema = z.array(FetchedPRSchema);
|
|
268
344
|
export const DailyDigestSchema = z.object({
|
|
269
345
|
generatedAt: z.string(),
|
|
270
|
-
// FetchedPR arrays — ephemeral, regenerated each run. Validated
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
346
|
+
// FetchedPR arrays — ephemeral, regenerated each run. Validated for the
|
|
347
|
+
// identity/status core the dashboard dereferences; loose on the rest.
|
|
348
|
+
openPRs: FetchedPRArraySchema,
|
|
349
|
+
needsAddressingPRs: FetchedPRArraySchema,
|
|
350
|
+
waitingOnMaintainerPRs: FetchedPRArraySchema,
|
|
274
351
|
recentlyClosedPRs: z.array(ClosedPRSchema),
|
|
275
352
|
recentlyMergedPRs: z.array(MergedPRSchema),
|
|
276
353
|
shelvedPRs: z.array(ShelvedPRRefSchema),
|