@reneturcios/greenlight 0.8.2 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -3
- package/dist/automate.js +13 -0
- package/dist/automate.js.map +1 -1
- package/dist/babysit.d.ts +73 -1
- package/dist/babysit.js +261 -71
- package/dist/babysit.js.map +1 -1
- package/dist/claim-health.d.ts +44 -0
- package/dist/claim-health.js +88 -0
- package/dist/claim-health.js.map +1 -0
- package/dist/claim-import.d.ts +59 -0
- package/dist/claim-import.js +164 -0
- package/dist/claim-import.js.map +1 -0
- package/dist/claims-registry.d.ts +35 -0
- package/dist/claims-registry.js +71 -0
- package/dist/claims-registry.js.map +1 -0
- package/dist/cli.js +187 -14
- package/dist/cli.js.map +1 -1
- package/dist/digest.d.ts +32 -1
- package/dist/digest.js +46 -4
- package/dist/digest.js.map +1 -1
- package/dist/doctor.js +14 -6
- package/dist/doctor.js.map +1 -1
- package/dist/gates.d.ts +1 -1
- package/dist/gates.js +39 -2
- package/dist/gates.js.map +1 -1
- package/dist/github.d.ts +4 -0
- package/dist/github.js +11 -0
- package/dist/github.js.map +1 -1
- package/dist/guard.d.ts +13 -1
- package/dist/guard.js +15 -1
- package/dist/guard.js.map +1 -1
- package/dist/help.js +51 -7
- package/dist/help.js.map +1 -1
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/dist/init.js +1 -1
- package/dist/proxy.d.ts +50 -0
- package/dist/proxy.js +111 -0
- package/dist/proxy.js.map +1 -0
- package/dist/scout.d.ts +114 -0
- package/dist/scout.js +266 -0
- package/dist/scout.js.map +1 -0
- package/dist/triage.d.ts +23 -0
- package/dist/triage.js +83 -0
- package/dist/triage.js.map +1 -0
- package/dist/types.d.ts +17 -1
- package/dist/verdict.d.ts +15 -6
- package/dist/verdict.js +14 -0
- package/dist/verdict.js.map +1 -1
- package/integrations/README.md +3 -1
- package/integrations/claude-code/CLAUDE.snippet.md +3 -2
- package/integrations/cline/README.md +1 -1
- package/integrations/windsurf/README.md +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `greenlight proxy`: a tiny localhost forwarder that attaches the user's
|
|
3
|
+
* GitHub token server-side. Greenlight's own reads are unauthenticated at
|
|
4
|
+
* 60/hr on the shared anonymous quota; the proxy lifts a user's setup to
|
|
5
|
+
* the authenticated 5000/hr budget without their token ever touching a
|
|
6
|
+
* browser, a log, or a tool's arguments beyond one env var.
|
|
7
|
+
*
|
|
8
|
+
* export GITHUB_TOKEN=<classic-token>
|
|
9
|
+
* greenlight proxy & # default 127.0.0.1:8792
|
|
10
|
+
* export GREENLIGHT_GITHUB_BASE_URL=http://127.0.0.1:8792 GITHUB_TOKEN=proxy
|
|
11
|
+
*
|
|
12
|
+
* Security model, stated plainly: the token is read once at startup from
|
|
13
|
+
* GITHUB_TOKEN (or GH_TOKEN) and is never logged, never stored, and never
|
|
14
|
+
* echoed. Incoming Authorization headers are ignored so a stray value
|
|
15
|
+
* cannot leak through. The server binds 127.0.0.1 only, and it forwards
|
|
16
|
+
* to exactly one upstream: https://api.github.com. Absolute-form request
|
|
17
|
+
* targets (http://...) are rejected so the proxy cannot be aimed at other
|
|
18
|
+
* hosts.
|
|
19
|
+
*/
|
|
20
|
+
import { createServer, request as httpRequest } from "node:http";
|
|
21
|
+
import { request as httpsRequest } from "node:https";
|
|
22
|
+
export const PROXY_DEFAULT_PORT = 8792;
|
|
23
|
+
export const PROXY_UPSTREAM = "https://api.github.com";
|
|
24
|
+
/** The exact client env line the command prints on startup. */
|
|
25
|
+
export function proxyClientEnv(port) {
|
|
26
|
+
return `GREENLIGHT_GITHUB_BASE_URL=http://127.0.0.1:${port} GITHUB_TOKEN=proxy`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Read the proxy configuration from the environment. Pure: returns the
|
|
30
|
+
* config or the human-readable refusal, and never the token on the
|
|
31
|
+
* refusal path either.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveProxyConfig(env, port = PROXY_DEFAULT_PORT, upstream = PROXY_UPSTREAM) {
|
|
34
|
+
const tokenVar = env.GITHUB_TOKEN ? "GITHUB_TOKEN" : env.GH_TOKEN ? "GH_TOKEN" : undefined;
|
|
35
|
+
const token = tokenVar ? env[tokenVar] : undefined;
|
|
36
|
+
if (!tokenVar || !token) {
|
|
37
|
+
return {
|
|
38
|
+
error: "greenlight proxy needs a token: export GITHUB_TOKEN=<your-classic-token> first " +
|
|
39
|
+
"(a classic personal access token; no scopes needed for public repos). The token is read once at startup and never logged or stored.",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return { token, tokenVar, port, upstream };
|
|
43
|
+
}
|
|
44
|
+
function forwardRequest(upstream, token, clientReq, clientRes) {
|
|
45
|
+
const target = clientReq.url ?? "/";
|
|
46
|
+
// Absolute-form targets would let a client aim the proxy at another
|
|
47
|
+
// host; refuse them outright.
|
|
48
|
+
if (/^https?:\/\//i.test(target)) {
|
|
49
|
+
clientRes.writeHead(400, { "content-type": "text/plain" });
|
|
50
|
+
clientRes.end("greenlight proxy: absolute-form request targets are rejected");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const upstreamUrl = new URL(target, upstream);
|
|
54
|
+
const useHttps = upstreamUrl.protocol === "https:";
|
|
55
|
+
const doRequest = useHttps ? httpsRequest : httpRequest;
|
|
56
|
+
const headers = { ...clientReq.headers };
|
|
57
|
+
delete headers["authorization"];
|
|
58
|
+
delete headers["host"];
|
|
59
|
+
delete headers["connection"];
|
|
60
|
+
// The whole point: attach the startup token server-side.
|
|
61
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
62
|
+
const upstreamReq = doRequest({
|
|
63
|
+
protocol: upstreamUrl.protocol,
|
|
64
|
+
hostname: upstreamUrl.hostname,
|
|
65
|
+
port: upstreamUrl.port || (useHttps ? 443 : 80),
|
|
66
|
+
path: upstreamUrl.pathname + upstreamUrl.search,
|
|
67
|
+
method: clientReq.method,
|
|
68
|
+
headers: headers,
|
|
69
|
+
}, (upstreamRes) => {
|
|
70
|
+
const out = {};
|
|
71
|
+
for (const [k, v] of Object.entries(upstreamRes.headers)) {
|
|
72
|
+
if (k.toLowerCase() === "transfer-encoding")
|
|
73
|
+
continue;
|
|
74
|
+
out[k] = v;
|
|
75
|
+
}
|
|
76
|
+
clientRes.writeHead(upstreamRes.statusCode ?? 502, out);
|
|
77
|
+
upstreamRes.pipe(clientRes);
|
|
78
|
+
});
|
|
79
|
+
upstreamReq.on("error", () => {
|
|
80
|
+
if (!clientRes.headersSent)
|
|
81
|
+
clientRes.writeHead(502, { "content-type": "text/plain" });
|
|
82
|
+
clientRes.end("greenlight proxy: upstream unreachable");
|
|
83
|
+
});
|
|
84
|
+
upstreamReq.setTimeout(60000, () => upstreamReq.destroy(new Error("upstream timeout")));
|
|
85
|
+
clientReq.pipe(upstreamReq);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Start the proxy. Resolves when the socket is bound; the returned stop()
|
|
89
|
+
* drains it. The token travels only in the Authorization header toward the
|
|
90
|
+
* fixed upstream.
|
|
91
|
+
*/
|
|
92
|
+
export async function startProxy(config) {
|
|
93
|
+
const server = createServer((req, res) => forwardRequest(config.upstream, config.token, req, res));
|
|
94
|
+
await new Promise((resolve, reject) => {
|
|
95
|
+
server.once("error", reject);
|
|
96
|
+
server.listen(config.port, "127.0.0.1", () => {
|
|
97
|
+
server.off("error", reject);
|
|
98
|
+
resolve();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
const address = server.address();
|
|
102
|
+
const port = typeof address === "object" && address ? address.port : config.port;
|
|
103
|
+
return {
|
|
104
|
+
server,
|
|
105
|
+
port,
|
|
106
|
+
stop: () => new Promise((resolve, reject) => {
|
|
107
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
108
|
+
}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/scout.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { type MergeVelocityStats } from "./merge-velocity.js";
|
|
2
|
+
import type { FrictionReport } from "./types.js";
|
|
3
|
+
/** One row of the campaign file. */
|
|
4
|
+
export interface CampaignEntry {
|
|
5
|
+
repo: string;
|
|
6
|
+
issue: number;
|
|
7
|
+
status: string;
|
|
8
|
+
pr?: string | null;
|
|
9
|
+
/** Verdict at scout time. */
|
|
10
|
+
verdict?: string;
|
|
11
|
+
/** One-line evidence summary at scout time. */
|
|
12
|
+
evidence?: string;
|
|
13
|
+
scannedAt?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ScoutCursor {
|
|
16
|
+
/** Index into the repo list where the next run should resume. */
|
|
17
|
+
repoIndex: number;
|
|
18
|
+
}
|
|
19
|
+
export interface CampaignFile {
|
|
20
|
+
entries: CampaignEntry[];
|
|
21
|
+
cursor: ScoutCursor | null;
|
|
22
|
+
}
|
|
23
|
+
export declare function loadCampaignFile(path: string): CampaignFile;
|
|
24
|
+
export declare function saveCampaignFile(path: string, campaign: CampaignFile): void;
|
|
25
|
+
export declare function campaignExists(path: string): boolean;
|
|
26
|
+
/** True when the issue is already tracked in ANY status. */
|
|
27
|
+
export declare function campaignHas(campaign: CampaignFile, repo: string, issue: number): boolean;
|
|
28
|
+
export interface ScoutCandidate {
|
|
29
|
+
repo: string;
|
|
30
|
+
number: number;
|
|
31
|
+
title: string;
|
|
32
|
+
url: string;
|
|
33
|
+
verdict: "GO" | "RISKY";
|
|
34
|
+
reasons: string[];
|
|
35
|
+
flags: string[];
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
friction: number;
|
|
38
|
+
outsideMergeShare: number;
|
|
39
|
+
medianMergeDays: number;
|
|
40
|
+
/** Rank score: higher ranks first. */
|
|
41
|
+
score: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Pure ranking: the documented formula. Friction and velocity inputs are
|
|
45
|
+
* per-repo (fetched once per repo); verdict strength and freshness are
|
|
46
|
+
* per-issue.
|
|
47
|
+
*/
|
|
48
|
+
export declare function rankCandidates(candidates: Array<{
|
|
49
|
+
repo: string;
|
|
50
|
+
number: number;
|
|
51
|
+
title: string;
|
|
52
|
+
url: string;
|
|
53
|
+
verdict: "GO" | "RISKY";
|
|
54
|
+
reasons: string[];
|
|
55
|
+
flags: string[];
|
|
56
|
+
updatedAt: string;
|
|
57
|
+
}>, repoSignals: {
|
|
58
|
+
friction: number;
|
|
59
|
+
outsideMergeShare: number;
|
|
60
|
+
medianMergeDays: number;
|
|
61
|
+
}, now: Date): ScoutCandidate[];
|
|
62
|
+
export interface ScannedIssue {
|
|
63
|
+
number: number;
|
|
64
|
+
title: string;
|
|
65
|
+
url: string;
|
|
66
|
+
verdict: string;
|
|
67
|
+
reasons: string[];
|
|
68
|
+
flags: string[];
|
|
69
|
+
updatedAt: string;
|
|
70
|
+
}
|
|
71
|
+
export interface ScoutDeps {
|
|
72
|
+
/** List open issues for a repo (newest first). */
|
|
73
|
+
listIssues?: (owner: string, repo: string, limit: number) => Promise<ScannedIssue[]>;
|
|
74
|
+
/** The per-issue pipeline (defaults to the check pipeline). */
|
|
75
|
+
scanIssue?: (owner: string, repo: string, number: number) => Promise<ScannedIssue>;
|
|
76
|
+
friction?: (owner: string, repo: string) => Promise<FrictionReport>;
|
|
77
|
+
velocity?: (owner: string, repo: string) => Promise<MergeVelocityStats | null>;
|
|
78
|
+
/** How many API calls have been used so far. */
|
|
79
|
+
budgetUsed?: () => number;
|
|
80
|
+
now?: () => Date;
|
|
81
|
+
}
|
|
82
|
+
export interface ScoutOptions {
|
|
83
|
+
repos: string[];
|
|
84
|
+
campaignPath: string;
|
|
85
|
+
budget: number;
|
|
86
|
+
write: boolean;
|
|
87
|
+
/** Issues scanned per repo. */
|
|
88
|
+
scanLimit?: number;
|
|
89
|
+
/** Survivors kept per repo. */
|
|
90
|
+
keepPerRepo?: number;
|
|
91
|
+
deps?: ScoutDeps;
|
|
92
|
+
now?: Date;
|
|
93
|
+
}
|
|
94
|
+
export interface ScoutResult {
|
|
95
|
+
/** Ranked survivors across all processed repos. */
|
|
96
|
+
candidates: ScoutCandidate[];
|
|
97
|
+
/** Entries appended to the campaign file this run. */
|
|
98
|
+
appended: CampaignEntry[];
|
|
99
|
+
/** True when the run stopped on the budget. */
|
|
100
|
+
budgetHit: boolean;
|
|
101
|
+
/** Where the run stopped (or null when every repo completed). */
|
|
102
|
+
cursor: ScoutCursor | null;
|
|
103
|
+
/** Repos fully processed this run. */
|
|
104
|
+
reposProcessed: string[];
|
|
105
|
+
/** Repos skipped because the cursor had passed them or the budget hit. */
|
|
106
|
+
reposSkipped: string[];
|
|
107
|
+
apiCallsUsed: number;
|
|
108
|
+
markdown: string;
|
|
109
|
+
json: Record<string, unknown>;
|
|
110
|
+
}
|
|
111
|
+
export declare const SCOUT_DEFAULT_BUDGET = 300;
|
|
112
|
+
export declare const SCOUT_DEFAULT_SCAN_LIMIT = 10;
|
|
113
|
+
export declare const SCOUT_DEFAULT_KEEP_PER_REPO = 5;
|
|
114
|
+
export declare function scoutCampaign(opts: ScoutOptions): Promise<ScoutResult>;
|
package/dist/scout.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Campaign scout mode (Greenlight v0.9.0): turn a list of repos into
|
|
3
|
+
* ranked campaign targets. For each repo it runs the same per-issue
|
|
4
|
+
* pipeline as check (legacy verdict + optional LLM layer + Claim
|
|
5
|
+
* Protocol), drops every issue already in the campaign file in ANY
|
|
6
|
+
* status, ranks the survivors, and (with --write) appends them as
|
|
7
|
+
* SCOUTED with their verdict and evidence summary.
|
|
8
|
+
*
|
|
9
|
+
* A --budget caps total GitHub API calls; when it hits, the campaign
|
|
10
|
+
* file keeps a resume cursor and the scout exits cleanly saying where
|
|
11
|
+
* it stopped. A rerun resumes from the cursor.
|
|
12
|
+
*
|
|
13
|
+
* Ranking formula (also documented in `greenlight scout --help`):
|
|
14
|
+
*
|
|
15
|
+
* score = 30 * verdictWeight
|
|
16
|
+
* + 0.25 * friction (0..100, higher = easier)
|
|
17
|
+
* + 20 * outsideMergeShare (0..1)
|
|
18
|
+
* + 15 / (1 + medianMergeDays / 14)
|
|
19
|
+
* + 5 * freshness (1 / (1 + daysSinceUpdated / 30))
|
|
20
|
+
*
|
|
21
|
+
* verdictWeight: GO = 2, RISKY = 1 (other verdicts never reach ranking).
|
|
22
|
+
*
|
|
23
|
+
* Everything network-shaped comes in on ScoutDeps, so fixtures prove
|
|
24
|
+
* dedupe, ranking order, and budget stop + resume without touching the
|
|
25
|
+
* API.
|
|
26
|
+
*/
|
|
27
|
+
import { apiGet, apiCallsUsed } from "./github.js";
|
|
28
|
+
import { checkIssueWithProtocol } from "./protocol-fetch.js";
|
|
29
|
+
import { fetchFrictionSummary } from "./friction.js";
|
|
30
|
+
import { fetchMergeVelocity } from "./merge-velocity.js";
|
|
31
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
32
|
+
import { dirname } from "node:path";
|
|
33
|
+
export function loadCampaignFile(path) {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
36
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.entries)) {
|
|
37
|
+
return {
|
|
38
|
+
entries: parsed.entries,
|
|
39
|
+
cursor: parsed.cursor ?? null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Missing or broken: start empty.
|
|
45
|
+
}
|
|
46
|
+
return { entries: [], cursor: null };
|
|
47
|
+
}
|
|
48
|
+
export function saveCampaignFile(path, campaign) {
|
|
49
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
50
|
+
writeFileSync(path, JSON.stringify(campaign, null, 2) + "\n", "utf8");
|
|
51
|
+
}
|
|
52
|
+
export function campaignExists(path) {
|
|
53
|
+
return existsSync(path);
|
|
54
|
+
}
|
|
55
|
+
/** True when the issue is already tracked in ANY status. */
|
|
56
|
+
export function campaignHas(campaign, repo, issue) {
|
|
57
|
+
return campaign.entries.some((e) => e.repo.toLowerCase() === repo.toLowerCase() && e.issue === issue);
|
|
58
|
+
}
|
|
59
|
+
/* ------------------------------------------------------------------ */
|
|
60
|
+
/* Ranking */
|
|
61
|
+
/* ------------------------------------------------------------------ */
|
|
62
|
+
const VERDICT_WEIGHT = { GO: 2, RISKY: 1 };
|
|
63
|
+
/**
|
|
64
|
+
* Pure ranking: the documented formula. Friction and velocity inputs are
|
|
65
|
+
* per-repo (fetched once per repo); verdict strength and freshness are
|
|
66
|
+
* per-issue.
|
|
67
|
+
*/
|
|
68
|
+
export function rankCandidates(candidates, repoSignals, now) {
|
|
69
|
+
const out = [];
|
|
70
|
+
for (const c of candidates) {
|
|
71
|
+
const daysSinceUpdated = Math.max(0, (now.getTime() - new Date(c.updatedAt).getTime()) / 86400000);
|
|
72
|
+
const freshness = 1 / (1 + daysSinceUpdated / 30);
|
|
73
|
+
const score = 30 * (VERDICT_WEIGHT[c.verdict] ?? 0) +
|
|
74
|
+
0.25 * repoSignals.friction +
|
|
75
|
+
20 * repoSignals.outsideMergeShare +
|
|
76
|
+
15 / (1 + repoSignals.medianMergeDays / 14) +
|
|
77
|
+
5 * freshness;
|
|
78
|
+
out.push({
|
|
79
|
+
...c,
|
|
80
|
+
friction: repoSignals.friction,
|
|
81
|
+
outsideMergeShare: repoSignals.outsideMergeShare,
|
|
82
|
+
medianMergeDays: repoSignals.medianMergeDays,
|
|
83
|
+
score,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
out.sort((a, b) => b.score - a.score || a.number - b.number);
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
async function defaultListIssues(owner, repo, limit) {
|
|
90
|
+
const q = encodeURIComponent(`repo:${owner}/${repo} type:issue state:open sort:updated-desc`);
|
|
91
|
+
const data = (await apiGet(`/search/issues?q=${q}&per_page=${limit}`));
|
|
92
|
+
return (data.items ?? []).map((i) => ({
|
|
93
|
+
number: Number(i.number),
|
|
94
|
+
title: String(i.title ?? ""),
|
|
95
|
+
url: String(i.html_url ?? `https://github.com/${owner}/${repo}/issues/${i.number}`),
|
|
96
|
+
verdict: "",
|
|
97
|
+
reasons: [],
|
|
98
|
+
flags: [],
|
|
99
|
+
updatedAt: String(i.updated_at ?? new Date(0).toISOString()),
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
async function defaultScanIssue(owner, repo, number) {
|
|
103
|
+
const { snapshot, merged } = await checkIssueWithProtocol(owner, repo, number, {});
|
|
104
|
+
return {
|
|
105
|
+
number,
|
|
106
|
+
title: snapshot.title,
|
|
107
|
+
url: snapshot.sourceUrl,
|
|
108
|
+
verdict: merged.verdict,
|
|
109
|
+
reasons: merged.reasons,
|
|
110
|
+
flags: merged.flags,
|
|
111
|
+
updatedAt: snapshot.updatedAt,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function parseRepoRef(ref) {
|
|
115
|
+
const m = /^([^/]+)\/([^/]+)$/.exec(ref.trim());
|
|
116
|
+
return m ? { owner: m[1], repo: m[2] } : null;
|
|
117
|
+
}
|
|
118
|
+
export const SCOUT_DEFAULT_BUDGET = 300;
|
|
119
|
+
export const SCOUT_DEFAULT_SCAN_LIMIT = 10;
|
|
120
|
+
export const SCOUT_DEFAULT_KEEP_PER_REPO = 5;
|
|
121
|
+
export async function scoutCampaign(opts) {
|
|
122
|
+
const deps = opts.deps ?? {};
|
|
123
|
+
const now = opts.now ?? new Date();
|
|
124
|
+
const scanLimit = opts.scanLimit ?? SCOUT_DEFAULT_SCAN_LIMIT;
|
|
125
|
+
const keepPerRepo = opts.keepPerRepo ?? SCOUT_DEFAULT_KEEP_PER_REPO;
|
|
126
|
+
const budgetUsed = deps.budgetUsed ?? apiCallsUsed;
|
|
127
|
+
const listIssues = deps.listIssues ?? defaultListIssues;
|
|
128
|
+
const scanIssue = deps.scanIssue ?? defaultScanIssue;
|
|
129
|
+
const friction = deps.friction ?? fetchFrictionSummary;
|
|
130
|
+
const velocity = deps.velocity ?? fetchMergeVelocity;
|
|
131
|
+
const campaign = loadCampaignFile(opts.campaignPath);
|
|
132
|
+
const resumeAt = campaign.cursor?.repoIndex ?? 0;
|
|
133
|
+
const candidates = [];
|
|
134
|
+
const appended = [];
|
|
135
|
+
const reposProcessed = [];
|
|
136
|
+
const reposSkipped = [];
|
|
137
|
+
let budgetHit = false;
|
|
138
|
+
let stoppedAt = null;
|
|
139
|
+
for (let i = 0; i < opts.repos.length; i++) {
|
|
140
|
+
if (i < resumeAt) {
|
|
141
|
+
reposSkipped.push(opts.repos[i]);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (budgetUsed() >= opts.budget) {
|
|
145
|
+
budgetHit = true;
|
|
146
|
+
stoppedAt = i;
|
|
147
|
+
reposSkipped.push(...opts.repos.slice(i));
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
const ref = parseRepoRef(opts.repos[i]);
|
|
151
|
+
if (!ref) {
|
|
152
|
+
reposSkipped.push(opts.repos[i]);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const repoTag = `${ref.owner}/${ref.repo}`;
|
|
156
|
+
const raw = await listIssues(ref.owner, ref.repo, scanLimit);
|
|
157
|
+
const ranked = [];
|
|
158
|
+
// Fetch repo-level ranking inputs once per repo, in parallel with the
|
|
159
|
+
// first issue's verdict only when needed: keep the budget check cheap
|
|
160
|
+
// by doing friction/velocity up front (2 calls) then the issues.
|
|
161
|
+
const [frictionReport, mergeVelocity] = await Promise.all([
|
|
162
|
+
friction(ref.owner, ref.repo).catch(() => null),
|
|
163
|
+
velocity(ref.owner, ref.repo).catch(() => null),
|
|
164
|
+
]);
|
|
165
|
+
const repoSignals = {
|
|
166
|
+
friction: frictionReport?.score ?? 50,
|
|
167
|
+
outsideMergeShare: mergeVelocity?.externalShare ?? 0,
|
|
168
|
+
medianMergeDays: mergeVelocity?.medianMergeDays ?? 14,
|
|
169
|
+
};
|
|
170
|
+
const issues = [];
|
|
171
|
+
for (const item of raw) {
|
|
172
|
+
if (budgetUsed() >= opts.budget) {
|
|
173
|
+
budgetHit = true;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
issues.push(await scanIssue(ref.owner, ref.repo, item.number));
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// One bad issue never kills the repo scan.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (budgetHit) {
|
|
184
|
+
stoppedAt = i;
|
|
185
|
+
reposSkipped.push(...opts.repos.slice(i + 1));
|
|
186
|
+
// The survivors already found for this repo are kept in memory and
|
|
187
|
+
// reported; the cursor restarts at this repo so the rerun rescans
|
|
188
|
+
// it (the campaign dedupe keeps appended entries idempotent).
|
|
189
|
+
}
|
|
190
|
+
const survivors = issues.filter((s) => (s.verdict === "GO" || s.verdict === "RISKY") &&
|
|
191
|
+
!campaignHas(campaign, repoTag, s.number) &&
|
|
192
|
+
!appended.some((a) => a.repo.toLowerCase() === repoTag.toLowerCase() && a.issue === s.number));
|
|
193
|
+
const withRepo = survivors.map((s) => ({ ...s, repo: repoTag, verdict: s.verdict }));
|
|
194
|
+
for (const c of rankCandidates(withRepo, repoSignals, now).slice(0, keepPerRepo)) {
|
|
195
|
+
ranked.push(c);
|
|
196
|
+
}
|
|
197
|
+
candidates.push(...ranked);
|
|
198
|
+
reposProcessed.push(repoTag);
|
|
199
|
+
}
|
|
200
|
+
candidates.sort((a, b) => b.score - a.score || a.number - b.number);
|
|
201
|
+
const cursor = budgetHit && stoppedAt !== null ? { repoIndex: stoppedAt } : null;
|
|
202
|
+
if (opts.write) {
|
|
203
|
+
for (const c of candidates) {
|
|
204
|
+
const evidence = c.reasons[0] ?? "";
|
|
205
|
+
const entry = {
|
|
206
|
+
repo: c.repo,
|
|
207
|
+
issue: c.number,
|
|
208
|
+
status: "SCOUTED",
|
|
209
|
+
pr: null,
|
|
210
|
+
verdict: c.verdict,
|
|
211
|
+
evidence: evidence.length > 160 ? evidence.slice(0, 160) + "..." : evidence,
|
|
212
|
+
scannedAt: now.toISOString(),
|
|
213
|
+
};
|
|
214
|
+
appended.push(entry);
|
|
215
|
+
if (!campaignHas(campaign, c.repo, c.number))
|
|
216
|
+
campaign.entries.push(entry);
|
|
217
|
+
}
|
|
218
|
+
campaign.cursor = cursor;
|
|
219
|
+
saveCampaignFile(opts.campaignPath, campaign);
|
|
220
|
+
}
|
|
221
|
+
const lines = [];
|
|
222
|
+
lines.push(`**Campaign scout: ${candidates.length} survivors from ${reposProcessed.length} repos**${budgetHit ? ` — budget hit (${opts.budget} calls), stopped at repo ${stoppedAt !== null ? opts.repos[stoppedAt] : "?"}` : ""}`);
|
|
223
|
+
if (budgetHit)
|
|
224
|
+
lines.push(`_Resume cursor saved in the campaign file: rerun the same command to continue._`);
|
|
225
|
+
lines.push("");
|
|
226
|
+
for (const c of candidates) {
|
|
227
|
+
lines.push(`- **${c.repo}#${c.number}** "${c.title.slice(0, 70)}" — ${c.verdict} (score ${c.score.toFixed(1)})`);
|
|
228
|
+
lines.push(` ${c.url}`);
|
|
229
|
+
}
|
|
230
|
+
if (candidates.length === 0)
|
|
231
|
+
lines.push("_None._");
|
|
232
|
+
lines.push("");
|
|
233
|
+
lines.push(`_Scout v0.9.0; budget ${budgetUsed()} calls used of ${opts.budget}._`);
|
|
234
|
+
return {
|
|
235
|
+
candidates,
|
|
236
|
+
appended,
|
|
237
|
+
budgetHit,
|
|
238
|
+
cursor,
|
|
239
|
+
reposProcessed,
|
|
240
|
+
reposSkipped,
|
|
241
|
+
apiCallsUsed: budgetUsed(),
|
|
242
|
+
markdown: lines.join("\n"),
|
|
243
|
+
json: {
|
|
244
|
+
candidates: candidates.map((c) => ({
|
|
245
|
+
repo: c.repo,
|
|
246
|
+
issue: c.number,
|
|
247
|
+
title: c.title,
|
|
248
|
+
url: c.url,
|
|
249
|
+
verdict: c.verdict,
|
|
250
|
+
score: c.score,
|
|
251
|
+
friction: c.friction,
|
|
252
|
+
outsideMergeShare: c.outsideMergeShare,
|
|
253
|
+
medianMergeDays: c.medianMergeDays,
|
|
254
|
+
reasons: c.reasons,
|
|
255
|
+
flags: c.flags,
|
|
256
|
+
})),
|
|
257
|
+
appended: appended.length,
|
|
258
|
+
budgetHit,
|
|
259
|
+
cursor,
|
|
260
|
+
reposProcessed,
|
|
261
|
+
reposSkipped,
|
|
262
|
+
apiCallsUsed: budgetUsed(),
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=scout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scout.js","sourceRoot":"","sources":["../src/scout.ts"],"names":[],"mappings":"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"}
|
package/dist/triage.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface TriageHold {
|
|
2
|
+
held: boolean;
|
|
3
|
+
/** What caused the hold: the matched label, or the quoted bot notice. */
|
|
4
|
+
evidence: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function hasTriageLabel(labels: string[] | undefined): string | undefined;
|
|
7
|
+
export interface TriageComment {
|
|
8
|
+
author: string;
|
|
9
|
+
body: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function hasTriageNoticeComment(comments: TriageComment[] | undefined): {
|
|
12
|
+
author: string;
|
|
13
|
+
quote: string;
|
|
14
|
+
} | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Detect a triage hold from an issue's labels and comments. Labels alone
|
|
17
|
+
* are enough (the cheap digest path); the bot-notice variant covers the
|
|
18
|
+
* comment case the verdict engine sees (mastra's dane-ai-mastra notice).
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectTriageHold(args: {
|
|
21
|
+
labels?: string[];
|
|
22
|
+
comments?: TriageComment[];
|
|
23
|
+
}): TriageHold;
|
package/dist/triage.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Triage-hold detection (Greenlight v0.9.0).
|
|
3
|
+
*
|
|
4
|
+
* Field case 2026-10-17: mastra-ai/mastra issues all carried a bot
|
|
5
|
+
* notice telling contributors to wait until maintainers remove the
|
|
6
|
+
* triage or approval label before opening a PR, and every one of them
|
|
7
|
+
* scored GO because the verdict engine had no gate kind for it. nuxt/nuxt
|
|
8
|
+
* uses a standing "pending triage" label with the same practical effect.
|
|
9
|
+
*
|
|
10
|
+
* A triage hold is not a claim and never produces TAKEN. It is a
|
|
11
|
+
* downgrade signal: the issue is real work that nobody is allowed to
|
|
12
|
+
* build yet, so GO caps at RISKY with the hold named, and scan/digest
|
|
13
|
+
* treat held issues as future pipeline rather than current targets.
|
|
14
|
+
*/
|
|
15
|
+
import { isBotAuthor } from "./claims.js";
|
|
16
|
+
/** Label texts that mean "do not build yet" (compared case-insensitively). */
|
|
17
|
+
const TRIAGE_LABELS = new Set([
|
|
18
|
+
"needs triage",
|
|
19
|
+
"pending triage",
|
|
20
|
+
"status: needs triage",
|
|
21
|
+
"needs-triage",
|
|
22
|
+
"triage needed",
|
|
23
|
+
"awaiting triage",
|
|
24
|
+
"triage",
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* Bot triage notices: a comment telling contributors to wait for triage
|
|
28
|
+
* before opening a PR. Anchored on the "wait ... before opening a pull
|
|
29
|
+
* request" shape mastra's bot uses, plus the label-clearance variant.
|
|
30
|
+
*/
|
|
31
|
+
const TRIAGE_NOTICE_RES = [
|
|
32
|
+
/wait until.{0,120}(triage|approval).{0,60}label.{0,60}before (opening|submitting)/i,
|
|
33
|
+
/before (opening|submitting) (a )?pull request.{0,80}(until|unless).{0,80}triag/i,
|
|
34
|
+
/do not open.{0,60}pull request.{0,60}(until|before).{0,60}triag/i,
|
|
35
|
+
];
|
|
36
|
+
export function hasTriageLabel(labels) {
|
|
37
|
+
if (!labels)
|
|
38
|
+
return undefined;
|
|
39
|
+
for (const label of labels) {
|
|
40
|
+
const norm = label.trim().toLowerCase();
|
|
41
|
+
if (TRIAGE_LABELS.has(norm))
|
|
42
|
+
return label;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
export function hasTriageNoticeComment(comments) {
|
|
47
|
+
if (!comments)
|
|
48
|
+
return undefined;
|
|
49
|
+
for (const c of comments) {
|
|
50
|
+
// Triage notices come from bots (mastra's dane-ai-mastra[bot]) or from
|
|
51
|
+
// maintainer-named accounts. A random human matching the notice shape
|
|
52
|
+
// is almost always quoting the bot, not issuing a hold.
|
|
53
|
+
if (!isBotAuthor(c.author) && !/maintainer|owner/i.test(c.author))
|
|
54
|
+
continue;
|
|
55
|
+
const re = TRIAGE_NOTICE_RES.find((r) => r.test(c.body));
|
|
56
|
+
if (!re)
|
|
57
|
+
continue;
|
|
58
|
+
const match = re.exec(c.body);
|
|
59
|
+
const quote = match ? match[0].replace(/\s+/g, " ").trim().slice(0, 140) : "";
|
|
60
|
+
return { author: c.author, quote };
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Detect a triage hold from an issue's labels and comments. Labels alone
|
|
66
|
+
* are enough (the cheap digest path); the bot-notice variant covers the
|
|
67
|
+
* comment case the verdict engine sees (mastra's dane-ai-mastra notice).
|
|
68
|
+
*/
|
|
69
|
+
export function detectTriageHold(args) {
|
|
70
|
+
const label = hasTriageLabel(args.labels);
|
|
71
|
+
if (label) {
|
|
72
|
+
return { held: true, evidence: `label "${label}" means the issue is still in triage; do not build until it clears` };
|
|
73
|
+
}
|
|
74
|
+
const notice = hasTriageNoticeComment(args.comments);
|
|
75
|
+
if (notice) {
|
|
76
|
+
return {
|
|
77
|
+
held: true,
|
|
78
|
+
evidence: `triage notice from @${notice.author}: "${notice.quote}"`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return { held: false, evidence: "" };
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=triage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"triage.js","sourceRoot":"","sources":["../src/triage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,8EAA8E;AAC9E,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,cAAc;IACd,gBAAgB;IAChB,sBAAsB;IACtB,cAAc;IACd,eAAe;IACf,iBAAiB;IACjB,QAAQ;CACT,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,iBAAiB,GAAG;IACxB,oFAAoF;IACpF,iFAAiF;IACjF,kEAAkE;CACnE,CAAC;AAQF,MAAM,UAAU,cAAc,CAAC,MAA4B;IACzD,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACxC,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;IAC5C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAOD,MAAM,UAAU,sBAAsB,CAAC,QAAqC;IAC1E,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,uEAAuE;QACvE,sEAAsE;QACtE,wDAAwD;QACxD,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;YAAE,SAAS;QAC5E,MAAM,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,EAAE;YAAE,SAAS;QAClB,MAAM,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9E,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;IACrC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAGhC;IACC,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,KAAK,oEAAoE,EAAE,CAAC;IACvH,CAAC;IACD,MAAM,MAAM,GAAG,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO;YACL,IAAI,EAAE,IAAI;YACV,QAAQ,EAAE,uBAAuB,MAAM,CAAC,MAAM,MAAM,MAAM,CAAC,KAAK,GAAG;SACpE,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AACvC,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -143,7 +143,23 @@ export type GateKind = "assignment_required" | "claim_first" | "vouch_required"
|
|
|
143
143
|
* A repo workflow closes external PRs automatically ("Close external
|
|
144
144
|
* PRs" style), the sharp end of the paused-PR policy above.
|
|
145
145
|
*/
|
|
146
|
-
| "external_prs_closed"
|
|
146
|
+
| "external_prs_closed"
|
|
147
|
+
/**
|
|
148
|
+
* The repo bans autonomous-agent contributions outright, usually in
|
|
149
|
+
* AGENTS.md or CLAUDE.md rather than CONTRIBUTING (nuxt/nuxt's
|
|
150
|
+
* AGENTS.md: "Contributions may only be submitted by a human who
|
|
151
|
+
* fully understands them... autonomous agents are not allowed"). This
|
|
152
|
+
* is a hard block for anyone working through an agent: only a human
|
|
153
|
+
* may submit. Never satisfied by tooling; claim refuses.
|
|
154
|
+
*/
|
|
155
|
+
| "agents_banned"
|
|
156
|
+
/**
|
|
157
|
+
* The repo permits agent-assisted work but requires disclosure, often
|
|
158
|
+
* plus an [AI] prefix on PR titles and commits (actualbudget/actual's
|
|
159
|
+
* AI Usage Policy + AGENTS.md). A flag, never a verdict change: prep
|
|
160
|
+
* turns it into concrete required actions.
|
|
161
|
+
*/
|
|
162
|
+
| "agents_disclosure";
|
|
147
163
|
/** Another issue this one is marked a duplicate of, with its live state. */
|
|
148
164
|
export interface DuplicateTarget {
|
|
149
165
|
number: number;
|