@reneturcios/greenlight 0.14.0 → 0.15.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.
@@ -0,0 +1,110 @@
1
+ /** One open PR, as the radar sees it. */
2
+ export interface RadarPR {
3
+ number: number;
4
+ title: string;
5
+ body: string;
6
+ author: string;
7
+ branch: string;
8
+ headSha: string;
9
+ createdAt: string;
10
+ url: string;
11
+ draft: boolean;
12
+ /** Fetched lazily, only for pre-filtered overlap candidates. */
13
+ changedFiles?: string[];
14
+ }
15
+ export type RadarClusterKind = "same-issue" | "file-overlap";
16
+ export interface RadarCluster {
17
+ kind: RadarClusterKind;
18
+ /** Set for same-issue clusters. */
19
+ issue?: {
20
+ number: number;
21
+ url: string;
22
+ };
23
+ prs: number[];
24
+ authors: string[];
25
+ evidence: string[];
26
+ /** A heuristic suggestion only: which PR to keep reviewing. */
27
+ suggestedKeep: number;
28
+ suggestedKeepReason: string;
29
+ }
30
+ export interface RadarResult {
31
+ repo: string;
32
+ prsScanned: number;
33
+ clusters: RadarCluster[];
34
+ /** PR numbers that matched nothing. */
35
+ unclustered: number[];
36
+ /** API calls consumed (measured, for budget honesty). */
37
+ apiCalls: number;
38
+ }
39
+ export interface RadarDeps {
40
+ /** Test seam / alternate listing. Default: live PR list via apiGet. */
41
+ listOpenPRs?: (owner: string, repo: string, limit: number) => Promise<RadarPR[]>;
42
+ /** Test seam for the file-list fetch. Default: live, cached. */
43
+ fetchFiles?: (owner: string, repo: string, prNumber: number) => Promise<string[]>;
44
+ /** Test seam for combined CI status. Default: live, only for top-2 age candidates. */
45
+ fetchCombinedState?: (owner: string, repo: string, sha: string) => Promise<string>;
46
+ }
47
+ /** Issue numbers referenced in a PR title/body, deduplicated. */
48
+ export declare function extractIssueRefs(text: string): number[];
49
+ /**
50
+ * Issue numbers referenced by a branch name, deduplicated. Pure.
51
+ *
52
+ * A number counts ONLY under a defensible convention:
53
+ * - explicit "#N" marker in the branch;
54
+ * - an issue-prefix token immediately before it ("fix-9483-wsdl", "issue-61");
55
+ * - first token of a branch segment ("feat/61-resume-block", "123-fix-x");
56
+ * - trailing token at the end of the branch when it is a 3+ digit,
57
+ * non-status-code number preceded by a word ("fix/studio-sms-...-6435");
58
+ * - bare mid-branch token when it is a 3+ digit, non-status-code number
59
+ * ("pay-852-partial-...").
60
+ * Never counted: GitHub web-UI "patch-N" names (patch-1, inian-patch-1),
61
+ * version numbers (1.0, 2.73.0-rc.5), sub-counters after another number
62
+ * (claude/task-22584764486-1), bare counters under 100 mid/trailing
63
+ * (bundle-size-3-migrate, ...-try-2), and alphanumerics like "oauth2"
64
+ * (letter boundaries are not delimiters).
65
+ *
66
+ * Found by the maintainer-seeding runs: the seeding reports document 6
67
+ * false-positive same-issue clusters, all from branch numbers that are not
68
+ * issue references (supabase #1/#2 from patch-N branches, posthog #500
69
+ * from "500 errors" branches, n8n #1 from "prior-to-1.0" and a bot task
70
+ * counter, langfuse #1/#3 from patch-1 and bundle-size-3-migrate). Each is
71
+ * pinned by a regression test naming the source.
72
+ */
73
+ export declare function branchIssueRefs(branch: string): number[];
74
+ /**
75
+ * The issue a PR is really working on: the referenced issue number for
76
+ * which classifyLinkStrength says "closing". Bare mentions (pdfcn #91
77
+ * listing sibling issues) never cluster. Candidate numbers come from
78
+ * title/body #N refs AND branchIssueRefs (the tightened branch rule set,
79
+ * so "oauth2", "patch-N", status codes, versions, and counters never
80
+ * yield candidates); both go through the same classifyLinkStrength
81
+ * gate. Pure.
82
+ */
83
+ export declare function closingIssueForPR(pr: RadarPR): number | undefined;
84
+ /** Significant lowercase title tokens for the cheap overlap pre-filter. Pure. */
85
+ export declare function titleTokens(title: string): string[];
86
+ /**
87
+ * Two PRs are overlap candidates when their title tokens overlap enough.
88
+ * Pure; the expensive file fetch happens only past this gate.
89
+ */
90
+ export declare function titlePrefilterHit(a: RadarPR, b: RadarPR): boolean;
91
+ /** Full file-set Jaccard; requires a shared top-level directory too. Pure. */
92
+ export declare function filesOverlap(a: string[], b: string[]): {
93
+ overlap: boolean;
94
+ jaccard: number;
95
+ sharedDir?: string;
96
+ };
97
+ /** Connected components over PR numbers. Pure. */
98
+ export declare function clusterPairs(members: number[], pairs: Array<[number, number]>): number[][];
99
+ /** Heuristic keep pick: earliest opened, then most files, then greenest CI. */
100
+ export declare function suggestKeep(cluster: RadarPR[], ciState: Map<number, string>): {
101
+ keep: number;
102
+ reason: string;
103
+ };
104
+ /**
105
+ * The radar pipeline. Best-effort: a failed file fetch removes that PR
106
+ * from overlap consideration instead of failing the whole run.
107
+ */
108
+ export declare function radarPRs(owner: string, repo: string, opts?: {
109
+ limit?: number;
110
+ }, deps?: RadarDeps): Promise<RadarResult>;
package/dist/radar.js ADDED
@@ -0,0 +1,468 @@
1
+ /**
2
+ * PR Radar: maintainer-side duplicate / competing PR detection.
3
+ *
4
+ * The maintainer control-room wedge from the roadmap. In the AI-PR era,
5
+ * maintainers drown in overlapping submissions: two contributors solving
6
+ * the same issue, three bots regenerating the same migration, an old
7
+ * stalled PR superseded by a fresh one. Review bots (CodeRabbit, Greptile)
8
+ * live inside one repo's pipeline and do not answer "which of these open
9
+ * PRs are doing the same work?" — that is the boundary layer Greenlight
10
+ * owns, and every claim here carries quoted evidence, never a generic
11
+ * AI opinion.
12
+ *
13
+ * Clustering (documented, deterministic):
14
+ * (a) same linked issue: a PR whose title/body/branch has CLOSING
15
+ * strength against an issue number it references (reusing
16
+ * classifyLinkStrength from linkstrength.js — the same machinery
17
+ * the check pipeline and Timeline X-Ray use; a bare "#N" mention
18
+ * is explicitly not enough, cf. issue #1).
19
+ * (b) no shared closing issue, but high changed-file overlap: pairs are
20
+ * cheaply pre-filtered by title-token overlap (Jaccard >= 0.4 on
21
+ * significant title tokens) so file lists are fetched only for
22
+ * plausible pairs; then Jaccard similarity on the full filename sets
23
+ * >= 0.6 AND a shared top-level directory -> cluster. File-list
24
+ * fetches are cached.
25
+ * Clusters are connected components over the pair graph: if A overlaps B
26
+ * and B overlaps C, all three land in one cluster even when A and C
27
+ * only touch lightly.
28
+ *
29
+ * Suggested keep (always labeled heuristic, never a verdict): earliest
30
+ * opened wins ties by PR number; within a cluster the pick is the earliest
31
+ * created_at, then the PR touching the most files, then the greenest
32
+ * combined CI state (fetched only for the top two age candidates, so CI
33
+ * never costs more than 2 calls per cluster).
34
+ *
35
+ * Budget (measured and reported in output):
36
+ * PR list: 1 call per 100 PRs (capped at 2 pages); linked-issue
37
+ * classification is free (title/body/branch regex); file lists: 1 call
38
+ * per 100 files per pre-filtered PR; CI status: <= 2 calls per cluster.
39
+ * Everything rides the existing file cache.
40
+ */
41
+ import { apiCallsUsed, apiGet, cacheGet, cacheSet } from "./github.js";
42
+ import { classifyLinkStrength } from "./linkstrength.js";
43
+ const MAX_PR_PAGES = 2;
44
+ const JACCARD_FILE_THRESHOLD = 0.6;
45
+ const JACCARD_TITLE_PREFILTER = 0.4;
46
+ function toRadarPR(item) {
47
+ return {
48
+ number: Number(item.number ?? 0),
49
+ title: String(item.title ?? ""),
50
+ body: String(item.body ?? ""),
51
+ author: String(item.user?.login ?? "unknown"),
52
+ branch: String(item.head?.ref ?? ""),
53
+ headSha: String(item.head?.sha ?? ""),
54
+ createdAt: String(item.created_at ?? ""),
55
+ url: String(item.html_url ?? ""),
56
+ draft: Boolean(item.draft),
57
+ };
58
+ }
59
+ async function defaultListOpenPRs(owner, repo, limit) {
60
+ const key = [owner, repo, `open-prs-${limit}`];
61
+ const cached = cacheGet("search", key);
62
+ if (cached)
63
+ return cached;
64
+ const out = [];
65
+ for (let page = 1; page <= MAX_PR_PAGES && out.length < limit; page++) {
66
+ const data = (await apiGet(`/repos/${owner}/${repo}/pulls?state=open&sort=created&direction=asc&per_page=100&page=${page}`));
67
+ out.push(...(data ?? []).map(toRadarPR));
68
+ if ((data ?? []).length < 100)
69
+ break;
70
+ }
71
+ const sliced = out.slice(0, limit);
72
+ cacheSet("search", key, sliced);
73
+ return sliced;
74
+ }
75
+ async function defaultFetchFiles(owner, repo, prNumber) {
76
+ const key = [owner, repo, String(prNumber), "files"];
77
+ const cached = cacheGet("search", key);
78
+ if (cached)
79
+ return cached;
80
+ const files = [];
81
+ for (let page = 1; page <= 5; page++) {
82
+ const data = (await apiGet(`/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100&page=${page}`));
83
+ for (const f of data ?? []) {
84
+ if (typeof f.filename === "string")
85
+ files.push(f.filename);
86
+ }
87
+ if ((data ?? []).length < 100)
88
+ break;
89
+ }
90
+ cacheSet("search", key, files);
91
+ return files;
92
+ }
93
+ async function defaultFetchCombinedState(owner, repo, sha) {
94
+ if (!sha)
95
+ return "unknown";
96
+ try {
97
+ const data = (await apiGet(`/repos/${owner}/${repo}/commits/${sha}/status`));
98
+ return String(data.state ?? "unknown");
99
+ }
100
+ catch {
101
+ return "unknown";
102
+ }
103
+ }
104
+ /** Issue numbers referenced in a PR title/body, deduplicated. */
105
+ export function extractIssueRefs(text) {
106
+ const seen = new Set();
107
+ const out = [];
108
+ const re = /#(\d{1,6})\b/g;
109
+ let m;
110
+ while ((m = re.exec(text)) !== null) {
111
+ const n = Number(m[1]);
112
+ if (n > 0 && !seen.has(n)) {
113
+ seen.add(n);
114
+ out.push(n);
115
+ }
116
+ }
117
+ return out;
118
+ }
119
+ /**
120
+ * Tokens that, immediately before a number in a branch name, mark it as an
121
+ * intentional issue reference ("fix-9483-wsdl", "issue-61"). Pure data.
122
+ */
123
+ const ISSUE_PREFIX_TOKENS = new Set([
124
+ "fix",
125
+ "fixes",
126
+ "fixed",
127
+ "bug",
128
+ "bugfix",
129
+ "hotfix",
130
+ "issue",
131
+ "issues",
132
+ "feat",
133
+ "feature",
134
+ "pr",
135
+ "gh",
136
+ "ticket",
137
+ ]);
138
+ /**
139
+ * Common HTTP status codes. A bare one in a branch name ("fix-mcp-oauth-500",
140
+ * "fix-experiment-exposure-500-missing-variants") is an error code, not an
141
+ * issue number; it only counts with an explicit marker (#N or a prefix token).
142
+ */
143
+ const HTTP_STATUS_CODES = new Set([
144
+ 400, 401, 402, 403, 404, 405, 406, 408, 409, 410, 413, 414, 415, 416, 417, 418,
145
+ 421, 422, 423, 425, 426, 428, 429, 431, 451,
146
+ 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511,
147
+ ]);
148
+ /**
149
+ * Issue numbers referenced by a branch name, deduplicated. Pure.
150
+ *
151
+ * A number counts ONLY under a defensible convention:
152
+ * - explicit "#N" marker in the branch;
153
+ * - an issue-prefix token immediately before it ("fix-9483-wsdl", "issue-61");
154
+ * - first token of a branch segment ("feat/61-resume-block", "123-fix-x");
155
+ * - trailing token at the end of the branch when it is a 3+ digit,
156
+ * non-status-code number preceded by a word ("fix/studio-sms-...-6435");
157
+ * - bare mid-branch token when it is a 3+ digit, non-status-code number
158
+ * ("pay-852-partial-...").
159
+ * Never counted: GitHub web-UI "patch-N" names (patch-1, inian-patch-1),
160
+ * version numbers (1.0, 2.73.0-rc.5), sub-counters after another number
161
+ * (claude/task-22584764486-1), bare counters under 100 mid/trailing
162
+ * (bundle-size-3-migrate, ...-try-2), and alphanumerics like "oauth2"
163
+ * (letter boundaries are not delimiters).
164
+ *
165
+ * Found by the maintainer-seeding runs: the seeding reports document 6
166
+ * false-positive same-issue clusters, all from branch numbers that are not
167
+ * issue references (supabase #1/#2 from patch-N branches, posthog #500
168
+ * from "500 errors" branches, n8n #1 from "prior-to-1.0" and a bot task
169
+ * counter, langfuse #1/#3 from patch-1 and bundle-size-3-migrate). Each is
170
+ * pinned by a regression test naming the source.
171
+ */
172
+ export function branchIssueRefs(branch) {
173
+ const seen = new Set();
174
+ const out = [];
175
+ const add = (n) => {
176
+ if (n > 0 && n <= 999999 && !seen.has(n)) {
177
+ seen.add(n);
178
+ out.push(n);
179
+ }
180
+ };
181
+ for (const segment of branch.split("/")) {
182
+ // Word tokens with raw offsets (letters/digits; "oauth2" stays one token).
183
+ const tokens = [];
184
+ const re = /[A-Za-z0-9]+/g;
185
+ let m;
186
+ while ((m = re.exec(segment)) !== null) {
187
+ tokens.push({ text: m[0], index: m.index });
188
+ }
189
+ for (let i = 0; i < tokens.length; i++) {
190
+ const { text: w, index } = tokens[i];
191
+ if (!/^\d+$/.test(w))
192
+ continue;
193
+ const n = Number(w);
194
+ const prev = i > 0 ? tokens[i - 1].text.toLowerCase() : "";
195
+ const after = segment.slice(index + w.length, index + w.length + 2);
196
+ if (/^\.\d/.test(after))
197
+ continue; // version: "1.0", "2.73.0-rc.5"
198
+ if (prev === "patch")
199
+ continue; // GitHub web-UI branch: patch-1, inian-patch-1
200
+ if (/^\d+$/.test(prev))
201
+ continue; // sub-counter after a number: task-22584764486-1
202
+ const marked = new RegExp(`#${n}\\b`).test(branch) || ISSUE_PREFIX_TOKENS.has(prev);
203
+ if (marked) {
204
+ add(n);
205
+ continue;
206
+ }
207
+ if (i === 0) {
208
+ // Issue-first branch convention: "feat/61-resume-block", "123-fix-x".
209
+ add(n);
210
+ continue;
211
+ }
212
+ if (HTTP_STATUS_CODES.has(n))
213
+ continue; // unmarked status code: an error code.
214
+ if (n >= 100)
215
+ add(n); // bare 3+ digit token, mid-branch or trailing.
216
+ // Bare 1-2 digit tokens mid-branch or trailing ("-3-", "-try-2") are
217
+ // counters, not issues.
218
+ }
219
+ }
220
+ return out;
221
+ }
222
+ /**
223
+ * The issue a PR is really working on: the referenced issue number for
224
+ * which classifyLinkStrength says "closing". Bare mentions (pdfcn #91
225
+ * listing sibling issues) never cluster. Candidate numbers come from
226
+ * title/body #N refs AND branchIssueRefs (the tightened branch rule set,
227
+ * so "oauth2", "patch-N", status codes, versions, and counters never
228
+ * yield candidates); both go through the same classifyLinkStrength
229
+ * gate. Pure.
230
+ */
231
+ export function closingIssueForPR(pr) {
232
+ const candidates = [];
233
+ for (const n of extractIssueRefs(`${pr.title}\n${pr.body}`)) {
234
+ if (n !== pr.number && !candidates.includes(n))
235
+ candidates.push(n);
236
+ }
237
+ for (const n of branchIssueRefs(pr.branch)) {
238
+ if (n !== pr.number && !candidates.includes(n))
239
+ candidates.push(n);
240
+ }
241
+ for (const n of candidates) {
242
+ if (classifyLinkStrength({ title: pr.title, body: pr.body, branch: pr.branch, issueNumber: n }) ===
243
+ "closing") {
244
+ return n;
245
+ }
246
+ }
247
+ return undefined;
248
+ }
249
+ const STOPWORDS = new Set("the,a,an,and,or,of,to,in,for,on,with,by,from,fix,fixes,fixed,add,adds,added,update,updates,updated,support,bug,issue,feature,request,via,into,vs,new,old,all,use,used,this,that,these,those,are,is,was,were,be,been,as,at,it,its,it’s,i".split(","));
250
+ /** Significant lowercase title tokens for the cheap overlap pre-filter. Pure. */
251
+ export function titleTokens(title) {
252
+ return title
253
+ .toLowerCase()
254
+ .split(/[^a-z0-9]+/)
255
+ .filter((t) => t.length >= 3 && !STOPWORDS.has(t));
256
+ }
257
+ function jaccard(a, b) {
258
+ if (a.size === 0 && b.size === 0)
259
+ return 0;
260
+ let inter = 0;
261
+ for (const x of a)
262
+ if (b.has(x))
263
+ inter++;
264
+ return inter / (a.size + b.size - inter);
265
+ }
266
+ const topLevel = (path) => path.split("/")[0] ?? "";
267
+ /**
268
+ * Two PRs are overlap candidates when their title tokens overlap enough.
269
+ * Pure; the expensive file fetch happens only past this gate.
270
+ */
271
+ export function titlePrefilterHit(a, b) {
272
+ const ta = new Set(titleTokens(a.title));
273
+ const tb = new Set(titleTokens(b.title));
274
+ if (ta.size === 0 || tb.size === 0)
275
+ return false;
276
+ return jaccard(ta, tb) >= JACCARD_TITLE_PREFILTER;
277
+ }
278
+ /** Full file-set Jaccard; requires a shared top-level directory too. Pure. */
279
+ export function filesOverlap(a, b) {
280
+ const sa = new Set(a);
281
+ const sb = new Set(b);
282
+ const j = jaccard(sa, sb);
283
+ if (j < JACCARD_FILE_THRESHOLD)
284
+ return { overlap: false, jaccard: j };
285
+ const dirsA = new Set(a.map(topLevel));
286
+ const shared = b.map(topLevel).find((d) => dirsA.has(d) && d !== "");
287
+ if (!shared)
288
+ return { overlap: false, jaccard: j };
289
+ return { overlap: true, jaccard: j, sharedDir: shared };
290
+ }
291
+ /** Connected components over PR numbers. Pure. */
292
+ export function clusterPairs(members, pairs) {
293
+ const parent = new Map();
294
+ const find = (x) => {
295
+ const p = parent.get(x) ?? x;
296
+ if (p !== x)
297
+ parent.set(x, find(p));
298
+ return parent.get(x) ?? x;
299
+ };
300
+ const union = (a, b) => parent.set(find(a), find(b));
301
+ for (const n of members)
302
+ parent.set(n, n);
303
+ for (const [a, b] of pairs)
304
+ union(a, b);
305
+ const groups = new Map();
306
+ for (const n of members) {
307
+ const r = find(n);
308
+ const g = groups.get(r) ?? [];
309
+ g.push(n);
310
+ groups.set(r, g);
311
+ }
312
+ return [...groups.values()].filter((g) => g.length > 1);
313
+ }
314
+ /** Heuristic keep pick: earliest opened, then most files, then greenest CI. */
315
+ export function suggestKeep(cluster, ciState) {
316
+ const sorted = [...cluster].sort((a, b) => {
317
+ if (a.createdAt !== b.createdAt)
318
+ return a.createdAt < b.createdAt ? -1 : 1;
319
+ return a.number - b.number;
320
+ });
321
+ const byFiles = [...sorted].sort((a, b) => (b.changedFiles?.length ?? 0) - (a.changedFiles?.length ?? 0));
322
+ const oldest = sorted[0];
323
+ const mostFiles = byFiles[0];
324
+ const reasons = [`earliest opened (${oldest.createdAt.slice(0, 10)})`];
325
+ let keep = oldest.number;
326
+ if (mostFiles.number !== oldest.number) {
327
+ reasons.push(`touches most files (#${mostFiles.number}: ${mostFiles.changedFiles?.length ?? 0})`);
328
+ }
329
+ // Greenest CI only among the top-2 age candidates (budget-capped).
330
+ const green = sorted.slice(0, 2).find((p) => ciState.get(p.number) === "success");
331
+ if (green && green.number !== keep) {
332
+ reasons.push(`#${green.number} has green CI`);
333
+ }
334
+ return {
335
+ keep,
336
+ reason: `heuristic: ${reasons.join("; ")} — suggestion only, not a verdict`,
337
+ };
338
+ }
339
+ const ageDays = (iso) => {
340
+ const t = Date.parse(iso);
341
+ if (!Number.isFinite(t))
342
+ return 0;
343
+ return Math.floor((Date.now() - t) / (24 * 60 * 60 * 1000));
344
+ };
345
+ /**
346
+ * The radar pipeline. Best-effort: a failed file fetch removes that PR
347
+ * from overlap consideration instead of failing the whole run.
348
+ */
349
+ export async function radarPRs(owner, repo, opts = {}, deps = {}) {
350
+ const callsBefore = apiCallsUsed();
351
+ const limit = Math.max(1, Math.min(opts.limit ?? 25, 100));
352
+ const listOpenPRs = deps.listOpenPRs ?? defaultListOpenPRs;
353
+ const fetchFiles = deps.fetchFiles ?? defaultFetchFiles;
354
+ const fetchCombinedState = deps.fetchCombinedState ?? defaultFetchCombinedState;
355
+ const prs = await listOpenPRs(owner, repo, limit);
356
+ const byNumber = new Map(prs.map((p) => [p.number, p]));
357
+ // Pass A: same-issue clusters via closing-strength links.
358
+ const issueGroups = new Map();
359
+ for (const pr of prs) {
360
+ const issue = closingIssueForPR(pr);
361
+ if (issue === undefined)
362
+ continue;
363
+ const g = issueGroups.get(issue) ?? [];
364
+ g.push(pr);
365
+ issueGroups.set(issue, g);
366
+ }
367
+ const clustered = new Set();
368
+ const clusters = [];
369
+ for (const [issue, group] of issueGroups) {
370
+ if (group.length < 2)
371
+ continue;
372
+ for (const p of group)
373
+ clustered.add(p.number);
374
+ const ciState = await ciStateFor(owner, repo, group, fetchCombinedState);
375
+ const { keep, reason } = suggestKeep(group, ciState);
376
+ clusters.push({
377
+ kind: "same-issue",
378
+ issue: { number: issue, url: `https://github.com/${owner}/${repo}/issues/${issue}` },
379
+ prs: group.map((p) => p.number).sort((a, b) => a - b),
380
+ authors: [...new Set(group.map((p) => p.author))],
381
+ evidence: group.map((p) => `#${p.number} "${p.title}" by @${p.author} (${ageDays(p.createdAt)}d old) closes #${issue} (${p.url})`),
382
+ suggestedKeep: keep,
383
+ suggestedKeepReason: reason,
384
+ });
385
+ }
386
+ // Pass B: file-overlap clusters among the rest. Files fetched only for
387
+ // title-prefiltered pairs; a PR failing its fetch just sits out.
388
+ const rest = prs.filter((p) => !clustered.has(p.number));
389
+ const pairs = [];
390
+ const evidenceForPair = new Map();
391
+ const fileCache = new Map();
392
+ const filesOf = async (p) => {
393
+ if (fileCache.has(p.number))
394
+ return fileCache.get(p.number);
395
+ try {
396
+ const f = await fetchFiles(owner, repo, p.number);
397
+ p.changedFiles = f;
398
+ fileCache.set(p.number, f);
399
+ return f;
400
+ }
401
+ catch {
402
+ fileCache.set(p.number, null);
403
+ return null;
404
+ }
405
+ };
406
+ for (let i = 0; i < rest.length; i++) {
407
+ for (let j = i + 1; j < rest.length; j++) {
408
+ const a = rest[i];
409
+ const b = rest[j];
410
+ if (!titlePrefilterHit(a, b))
411
+ continue;
412
+ const fa = await filesOf(a);
413
+ const fb = await filesOf(b);
414
+ if (!fa || !fb)
415
+ continue;
416
+ const fo = filesOverlap(fa, fb);
417
+ if (!fo.overlap)
418
+ continue;
419
+ pairs.push([a.number, b.number]);
420
+ evidenceForPair.set(`${Math.min(a.number, b.number)}-${Math.max(a.number, b.number)}`, `file overlap ${(fo.jaccard * 100).toFixed(0)}% (Jaccard) in ${fo.sharedDir ?? "?"}`);
421
+ }
422
+ }
423
+ for (const group of clusterPairs(rest.map((p) => p.number), pairs)) {
424
+ for (const n of group)
425
+ clustered.add(n);
426
+ const members = group.map((n) => byNumber.get(n));
427
+ const ciState = await ciStateFor(owner, repo, members, fetchCombinedState);
428
+ const { keep, reason } = suggestKeep(members, ciState);
429
+ const ev = [];
430
+ for (let i = 0; i < group.length; i++) {
431
+ for (let j = i + 1; j < group.length; j++) {
432
+ const k = `${Math.min(group[i], group[j])}-${Math.max(group[i], group[j])}`;
433
+ if (evidenceForPair.has(k))
434
+ ev.push(`#${group[i]} ~ #${group[j]}: ${evidenceForPair.get(k)}`);
435
+ }
436
+ }
437
+ clusters.push({
438
+ kind: "file-overlap",
439
+ prs: [...group].sort((a, b) => a - b),
440
+ authors: [...new Set(members.map((p) => p.author))],
441
+ evidence: ev,
442
+ suggestedKeep: keep,
443
+ suggestedKeepReason: reason,
444
+ });
445
+ }
446
+ const unclustered = prs.map((p) => p.number).filter((n) => !clustered.has(n));
447
+ return {
448
+ repo: `${owner}/${repo}`,
449
+ prsScanned: prs.length,
450
+ clusters: clusters.sort((a, b) => a.prs[0] - b.prs[0]),
451
+ unclustered,
452
+ apiCalls: apiCallsUsed() - callsBefore,
453
+ };
454
+ }
455
+ async function ciStateFor(owner, repo, group, fetchCombinedState) {
456
+ const out = new Map();
457
+ const byAge = [...group].sort((a, b) => a.number - b.number).slice(0, 2);
458
+ for (const p of byAge) {
459
+ try {
460
+ out.set(p.number, await fetchCombinedState(owner, repo, p.headSha));
461
+ }
462
+ catch {
463
+ out.set(p.number, "unknown");
464
+ }
465
+ }
466
+ return out;
467
+ }
468
+ //# sourceMappingURL=radar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radar.js","sourceRoot":"","sources":["../src/radar.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "@modelcontextprotocol/sdk": "^1",
7
7
  "zod": "^3"
8
8
  },
9
- "description": "Pre-flight checks for open-source contributions: is this issue actually landable?",
9
+ "description": "Pre-flight and coordination layer for open-source contribution: issue verdicts, Timeline X-Ray, PR radar and review briefs. CLI + MCP server.",
10
10
  "devDependencies": {
11
11
  "@types/node": "^20",
12
12
  "tsx": "^4",
@@ -25,7 +25,7 @@
25
25
  "test": "tsx --test test/*.test.ts"
26
26
  },
27
27
  "type": "module",
28
- "version": "0.14.0",
28
+ "version": "0.15.1",
29
29
  "mcpName": "io.github.CloudCorpRecords/greenlight",
30
30
  "publishConfig": {
31
31
  "access": "public"
package/server.json CHANGED
@@ -6,13 +6,13 @@
6
6
  "url": "https://github.com/CloudCorpRecords/greenlight",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.14.0",
9
+ "version": "0.15.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "registryBaseUrl": "https://registry.npmjs.org",
14
14
  "identifier": "@reneturcios/greenlight",
15
- "version": "0.14.0",
15
+ "version": "0.15.1",
16
16
  "runtimeHint": "npx",
17
17
  "packageArguments": [
18
18
  {