@reneturcios/greenlight 0.3.5 → 0.3.7
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 +16 -0
- package/dist/automate.d.ts +4 -0
- package/dist/automate.js +60 -3
- package/dist/automate.js.map +1 -1
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/prep-detail.d.ts +125 -0
- package/dist/prep-detail.js +316 -0
- package/dist/prep-detail.js.map +1 -0
- package/dist/review-triage.d.ts +129 -0
- package/dist/review-triage.js +363 -0
- package/dist/review-triage.js.map +1 -0
- package/package.json +1 -1
- package/server.json +3 -3
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { type ServerSideDeps } from "./server-side.js";
|
|
2
|
+
import type { Gate, RepoConventions } from "./types.js";
|
|
3
|
+
/** One policy text: CONTRIBUTING variants, AGENTS.md, CLAUDE.md. */
|
|
4
|
+
export interface PolicyText {
|
|
5
|
+
file: string;
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The exact signing path a repo demands. Named so the prep checklist can
|
|
10
|
+
* tell the contributor the real method with the real link or phrase:
|
|
11
|
+
* cla-assistant (standard phrase), a repo-specific CLA bot with its own
|
|
12
|
+
* signing page (the n8n pattern), or DCO instead of a CLA.
|
|
13
|
+
*/
|
|
14
|
+
export interface ClaPath {
|
|
15
|
+
kind: "cla-assistant" | "custom-cla-bot" | "dco" | "none";
|
|
16
|
+
/** One-line how-to for the checklist. */
|
|
17
|
+
method: string;
|
|
18
|
+
/** Signing page URL when one was found. */
|
|
19
|
+
link?: string;
|
|
20
|
+
/** Exact phrase to comment when the flow is comment-based. */
|
|
21
|
+
signaturePhrase?: string;
|
|
22
|
+
/** Policy files the call was drawn from. */
|
|
23
|
+
sources: string[];
|
|
24
|
+
/** Extra notes (e.g. "also requires DCO sign-off on every commit"). */
|
|
25
|
+
notes: string[];
|
|
26
|
+
}
|
|
27
|
+
export declare function detectClaPath(sources: PolicyText[], gates?: Gate[]): ClaPath;
|
|
28
|
+
/** The release-note artifact a PR owes the repo. */
|
|
29
|
+
export interface ChangesetConvention {
|
|
30
|
+
style: "changesets" | "changes-dir" | "towncrier" | "changelog-notes";
|
|
31
|
+
/** Path pattern, e.g. ".changeset/*.md". */
|
|
32
|
+
pattern: string;
|
|
33
|
+
/** Example filename built from the issue number. */
|
|
34
|
+
example: string;
|
|
35
|
+
/** What the file should contain. */
|
|
36
|
+
note: string;
|
|
37
|
+
/** Where it was detected: repo root listing or a policy file name. */
|
|
38
|
+
source: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Detect the release-note ritual: changesets (.changeset/), the reticle
|
|
42
|
+
* .changes/ pattern (one file per change, assembled into the changelog),
|
|
43
|
+
* towncrier fragments (towncrier.toml or [tool.towncrier] in pyproject),
|
|
44
|
+
* or a CONTRIBUTING instruction to update CHANGELOG by hand.
|
|
45
|
+
*/
|
|
46
|
+
export declare function detectChangesetConvention(args: {
|
|
47
|
+
rootNames: string[];
|
|
48
|
+
policyText: string;
|
|
49
|
+
policySource?: string;
|
|
50
|
+
pyprojectText?: string;
|
|
51
|
+
issueNumber: number;
|
|
52
|
+
slug: string;
|
|
53
|
+
}): ChangesetConvention | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Satellite-repo hint: the repo's own docs name a companion repo where the
|
|
56
|
+
* behavior actually lives (e.g. a label bot or CLI in a second repo).
|
|
57
|
+
* Only fires on tight textual evidence: a github.com/org/repo URL
|
|
58
|
+
* immediately preceded (within ~60 chars) by a relationship verb like
|
|
59
|
+
* "source lives at" or "enforced by", and which is not the repo being
|
|
60
|
+
* prepped. Never guesses.
|
|
61
|
+
*/
|
|
62
|
+
export declare function detectSatelliteRepo(owner: string, repo: string, policyText: string): {
|
|
63
|
+
candidate: string;
|
|
64
|
+
evidence: string;
|
|
65
|
+
} | undefined;
|
|
66
|
+
/** Warnings that the fix may not be buildable from this repo. */
|
|
67
|
+
export interface FixLivesHereWarning {
|
|
68
|
+
/** "server-side" or "satellite". */
|
|
69
|
+
kind: string;
|
|
70
|
+
/** Prominent warning line for the prep checklist. */
|
|
71
|
+
line: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Combine the v0.3.4 server-side signal with the satellite hint into
|
|
75
|
+
* checklist warnings. Best-effort: code-search failures or empty policy
|
|
76
|
+
* text yield no warnings, never guesses.
|
|
77
|
+
*/
|
|
78
|
+
export declare function detectFixLivesHere(args: {
|
|
79
|
+
owner: string;
|
|
80
|
+
repo: string;
|
|
81
|
+
issueBody: string;
|
|
82
|
+
policyText: string;
|
|
83
|
+
deps?: ServerSideDeps;
|
|
84
|
+
}): Promise<FixLivesHereWarning[]>;
|
|
85
|
+
/**
|
|
86
|
+
* One-glance house rules at the top of the prep checklist: the required
|
|
87
|
+
* claim style, PR title conventions, draft-first norm, and the repo's test
|
|
88
|
+
* command. Every line names its source; absent rules are stated plainly
|
|
89
|
+
* instead of invented.
|
|
90
|
+
*/
|
|
91
|
+
export declare function buildHouseRulesSummary(args: {
|
|
92
|
+
gates: Gate[];
|
|
93
|
+
policyText: string;
|
|
94
|
+
policyFiles: string[];
|
|
95
|
+
conventions?: RepoConventions;
|
|
96
|
+
}): string[];
|
|
97
|
+
/** All four enhancements gathered for a prep run. */
|
|
98
|
+
export interface PrepDetail {
|
|
99
|
+
claPath: ClaPath;
|
|
100
|
+
changeset?: ChangesetConvention;
|
|
101
|
+
fixLivesHereWarnings: FixLivesHereWarning[];
|
|
102
|
+
houseRulesSummary: string[];
|
|
103
|
+
}
|
|
104
|
+
export interface PrepDetailDeps extends ServerSideDeps {
|
|
105
|
+
/** Root names for the changeset/towncrier probes. */
|
|
106
|
+
rootNames?: () => Promise<string[]>;
|
|
107
|
+
/** pyproject.toml text for the towncrier probe, or undefined. */
|
|
108
|
+
pyprojectText?: () => Promise<string | undefined>;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Gather all four prep enhancements best-effort for a prep run. The
|
|
112
|
+
* snapshot's houseRules carry {file, text}; snapshot.contributing carries
|
|
113
|
+
* the joined text. Any failing probe degrades to "not detected".
|
|
114
|
+
*/
|
|
115
|
+
export declare function gatherPrepDetail(args: {
|
|
116
|
+
owner: string;
|
|
117
|
+
repo: string;
|
|
118
|
+
issueNumber: number;
|
|
119
|
+
slug: string;
|
|
120
|
+
policyTexts: PolicyText[];
|
|
121
|
+
contributing: string | undefined;
|
|
122
|
+
issueBody?: string;
|
|
123
|
+
conventions?: RepoConventions;
|
|
124
|
+
deps?: PrepDetailDeps;
|
|
125
|
+
}): Promise<PrepDetail>;
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claim-to-PR prep hardening (v0.3.7).
|
|
3
|
+
*
|
|
4
|
+
* The `prep` command already learned fork/branch basics and the repo's
|
|
5
|
+
* ritual files. What it never answered: how do I actually sign what the
|
|
6
|
+
* repo demands, what release-note artifact do I owe, is the fix even in
|
|
7
|
+
* this repo, and what are the house rules in one glance.
|
|
8
|
+
*
|
|
9
|
+
* Every detector below is a pure function over injected text/data (offline
|
|
10
|
+
* tests), and prepIssue calls them best-effort against live probes. When
|
|
11
|
+
* evidence is thin the detectors say "not detected" instead of guessing.
|
|
12
|
+
*/
|
|
13
|
+
import { parseGatesFromSources } from "./gates.js";
|
|
14
|
+
import { detectServerSideOnly } from "./server-side.js";
|
|
15
|
+
function gateDetected(gates, kind) {
|
|
16
|
+
return gates.some((g) => g.kind === kind && g.detected);
|
|
17
|
+
}
|
|
18
|
+
function gateSource(gates, kind) {
|
|
19
|
+
return gates.find((g) => g.kind === kind && g.detected)?.source;
|
|
20
|
+
}
|
|
21
|
+
const CLA_SIGNATURE_PHRASE = "I have read the CLA Document and I hereby sign the CLA";
|
|
22
|
+
/** Pull bare and markdown-link URLs out of a text. */
|
|
23
|
+
function extractUrls(text) {
|
|
24
|
+
const out = [];
|
|
25
|
+
for (const m of text.matchAll(/https?:\/\/[^\s)"'>]+/g)) {
|
|
26
|
+
const url = m[0].replace(/[.,;:!?]+$/, "");
|
|
27
|
+
if (!out.includes(url))
|
|
28
|
+
out.push(url);
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A repo-specific CLA bot leaves a signing-page URL in policy text:
|
|
34
|
+
* /webhook/cla paths, cla-bot subdomains/hostnames, or any URL in the
|
|
35
|
+
* same sentence as "CLA". Returns the first such URL found.
|
|
36
|
+
*/
|
|
37
|
+
function findCustomClaUrl(text) {
|
|
38
|
+
const urls = extractUrls(text);
|
|
39
|
+
// Strong patterns first: webhook/cla paths and cla bot hostnames.
|
|
40
|
+
const strong = urls.find((u) => /webhook\/cla|cla[-_]?bot/i.test(u));
|
|
41
|
+
if (strong)
|
|
42
|
+
return strong;
|
|
43
|
+
// Any URL in a line that mentions CLA signing.
|
|
44
|
+
for (const line of text.split("\n")) {
|
|
45
|
+
if (/cla/i.test(line) && /sign|accept|agree/i.test(line)) {
|
|
46
|
+
const m = line.match(/https?:\/\/[^\s)"'>]+/);
|
|
47
|
+
if (m)
|
|
48
|
+
return m[0].replace(/[.,;:!?]+$/, "");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
export function detectClaPath(sources, gates) {
|
|
54
|
+
const resolvedGates = gates ?? parseGatesFromSources(sources);
|
|
55
|
+
const combined = sources.map((s) => s.text).join("\n");
|
|
56
|
+
const files = sources.map((s) => s.file);
|
|
57
|
+
const notes = [];
|
|
58
|
+
// (b) Repo-specific CLA bot with its own signing page, checked first
|
|
59
|
+
// because a cla_required gate is usually also detected underneath it.
|
|
60
|
+
const customUrl = findCustomClaUrl(combined);
|
|
61
|
+
const customUrlSource = customUrl
|
|
62
|
+
? files.find((f, i) => sources[i].text.includes(customUrl))
|
|
63
|
+
: undefined;
|
|
64
|
+
if (customUrl) {
|
|
65
|
+
if (gateDetected(resolvedGates, "dco_required")) {
|
|
66
|
+
notes.push(`Also requires DCO sign-off on every commit (${gateSource(resolvedGates, "dco_required") ?? "house rules"}).`);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
kind: "custom-cla-bot",
|
|
70
|
+
method: `Sign this repo's own CLA at the signing page below (GitHub OAuth), then the CLA bot marks the PR.`,
|
|
71
|
+
link: customUrl,
|
|
72
|
+
sources: customUrlSource ? [customUrlSource] : files,
|
|
73
|
+
notes,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// (a) cla-assistant: explicit mention, or a bare cla_required gate.
|
|
77
|
+
const claAssistantMention = /cla[- ]?assistant(\.io)?/i.test(combined);
|
|
78
|
+
if (claAssistantMention || gateDetected(resolvedGates, "cla_required")) {
|
|
79
|
+
const explicit = claAssistantMention;
|
|
80
|
+
if (gateDetected(resolvedGates, "dco_required")) {
|
|
81
|
+
notes.push(`Also requires DCO sign-off on every commit (${gateSource(resolvedGates, "dco_required") ?? "house rules"}).`);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
kind: "cla-assistant",
|
|
85
|
+
method: explicit
|
|
86
|
+
? "Sign via CLA Assistant: the CLA Assistant bot comments on the PR; reply with the signature phrase and it marks the check green."
|
|
87
|
+
: "A CLA is required (detected in house rules); the standard flow is CLA Assistant, which prompts on the PR.",
|
|
88
|
+
link: "https://cla-assistant.io",
|
|
89
|
+
signaturePhrase: CLA_SIGNATURE_PHRASE,
|
|
90
|
+
sources: files,
|
|
91
|
+
notes,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// (c) DCO instead of a CLA.
|
|
95
|
+
if (gateDetected(resolvedGates, "dco_required")) {
|
|
96
|
+
return {
|
|
97
|
+
kind: "dco",
|
|
98
|
+
method: "No CLA. Sign every commit with `git commit -s` (Signed-off-by trailer); the PR check verifies it.",
|
|
99
|
+
sources: gateSource(resolvedGates, "dco_required") ? [gateSource(resolvedGates, "dco_required")] : files,
|
|
100
|
+
notes,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// (d) Nothing detected.
|
|
104
|
+
return {
|
|
105
|
+
kind: "none",
|
|
106
|
+
method: "No CLA or DCO requirement detected in the repo's policy files.",
|
|
107
|
+
sources: files,
|
|
108
|
+
notes,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Detect the release-note ritual: changesets (.changeset/), the reticle
|
|
113
|
+
* .changes/ pattern (one file per change, assembled into the changelog),
|
|
114
|
+
* towncrier fragments (towncrier.toml or [tool.towncrier] in pyproject),
|
|
115
|
+
* or a CONTRIBUTING instruction to update CHANGELOG by hand.
|
|
116
|
+
*/
|
|
117
|
+
export function detectChangesetConvention(args) {
|
|
118
|
+
const { rootNames, policyText, policySource, pyprojectText, issueNumber, slug } = args;
|
|
119
|
+
const has = (n) => rootNames.some((r) => r.toLowerCase() === n);
|
|
120
|
+
if (has(".changeset")) {
|
|
121
|
+
return {
|
|
122
|
+
style: "changesets",
|
|
123
|
+
pattern: ".changeset/*.md",
|
|
124
|
+
example: `.changeset/${issueNumber}-${slug}.md`,
|
|
125
|
+
note: "changesets: one markdown file per change describing it for the changelog (frontmatter with semver bump, then prose).",
|
|
126
|
+
source: "repo root (.changeset/)",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (has(".changes")) {
|
|
130
|
+
return {
|
|
131
|
+
style: "changes-dir",
|
|
132
|
+
pattern: ".changes/*.md",
|
|
133
|
+
example: `.changes/${issueNumber}-${slug}.md`,
|
|
134
|
+
note: "one file per issue in .changes/; the repo assembles its changelog from this directory, never edit CHANGELOG.md directly.",
|
|
135
|
+
source: "repo root (.changes/)",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const towncrierConfig = has("towncrier.toml") || /\[tool\.towncrier\]/i.test(pyprojectText ?? "");
|
|
139
|
+
if (towncrierConfig) {
|
|
140
|
+
// The fragment directory is project-specific; towncrier's own default
|
|
141
|
+
// is "newsfragments", overridden by the `directory` key. Without
|
|
142
|
+
// reading the config value, name the common candidates.
|
|
143
|
+
return {
|
|
144
|
+
style: "towncrier",
|
|
145
|
+
pattern: "<fragment-dir>/<issue>.<type>.md",
|
|
146
|
+
example: `newsfragments/${issueNumber}.bugfix.md`,
|
|
147
|
+
note: "towncrier: one news fragment named <issue-or-PR-number>.<type> (feature, bugfix, doc, removal, misc) in the fragment directory set by the `directory` key of [tool.towncrier]; common dirs: newsfragments/, changelog.d/, changes/.",
|
|
148
|
+
source: has("towncrier.toml") ? "towncrier.toml" : "pyproject.toml [tool.towncrier]",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (/update (the )?changelog|changelog.{0,40}(entry|note|update)/i.test(policyText)) {
|
|
152
|
+
return {
|
|
153
|
+
style: "changelog-notes",
|
|
154
|
+
pattern: "CHANGELOG.md",
|
|
155
|
+
example: "an entry under the Unreleased section of CHANGELOG.md",
|
|
156
|
+
note: "add a changelog entry by hand; check the file's Unreleased section format first.",
|
|
157
|
+
source: policySource ?? "house rules",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
/* ------------------------------------------------------------------ */
|
|
163
|
+
/* 3. Fix-lives-here check */
|
|
164
|
+
/* ------------------------------------------------------------------ */
|
|
165
|
+
/**
|
|
166
|
+
* Satellite-repo hint: the repo's own docs name a companion repo where the
|
|
167
|
+
* behavior actually lives (e.g. a label bot or CLI in a second repo).
|
|
168
|
+
* Only fires on tight textual evidence: a github.com/org/repo URL
|
|
169
|
+
* immediately preceded (within ~60 chars) by a relationship verb like
|
|
170
|
+
* "source lives at" or "enforced by", and which is not the repo being
|
|
171
|
+
* prepped. Never guesses.
|
|
172
|
+
*/
|
|
173
|
+
export function detectSatelliteRepo(owner, repo, policyText) {
|
|
174
|
+
const urlRe = /github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)/g;
|
|
175
|
+
const seen = new Set();
|
|
176
|
+
let m;
|
|
177
|
+
while ((m = urlRe.exec(policyText)) !== null) {
|
|
178
|
+
const candidate = `${m[1]}/${m[2]}`.replace(/[.]+$/, "");
|
|
179
|
+
if (candidate.toLowerCase() === `${owner}/${repo}`.toLowerCase() || seen.has(candidate))
|
|
180
|
+
continue;
|
|
181
|
+
seen.add(candidate);
|
|
182
|
+
// Code-location evidence in the ~60 chars before the URL: a phrase
|
|
183
|
+
// like "source lives at" or "enforced by our backend". Plain
|
|
184
|
+
// "documentation lives at" for a docs repo, or install
|
|
185
|
+
// instructions, deliberately do not count.
|
|
186
|
+
const before = policyText.slice(Math.max(0, m.index - 60), m.index).replace(/\s+/g, " ");
|
|
187
|
+
const codeLocated = /\b(source|code(base)?|implementation)\b.{0,40}\b(lives?|maintained|hosted|located|found)\b/i.test(before);
|
|
188
|
+
const enforcedBy = /\b(enforced|handled|maintained|implemented|hosted)\s+by\b/i.test(before);
|
|
189
|
+
if ((codeLocated || enforcedBy) && !/\b(install|download|uninstall)\b/i.test(before)) {
|
|
190
|
+
const evidence = policyText
|
|
191
|
+
.slice(Math.max(0, m.index - 160), Math.min(policyText.length, m.index + 40))
|
|
192
|
+
.replace(/\s+/g, " ");
|
|
193
|
+
return {
|
|
194
|
+
candidate,
|
|
195
|
+
evidence: `the behavior may live in ${candidate}: policy text names it near "...${evidence.trim()}..."`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Combine the v0.3.4 server-side signal with the satellite hint into
|
|
203
|
+
* checklist warnings. Best-effort: code-search failures or empty policy
|
|
204
|
+
* text yield no warnings, never guesses.
|
|
205
|
+
*/
|
|
206
|
+
export async function detectFixLivesHere(args) {
|
|
207
|
+
const { owner, repo, issueBody, policyText, deps } = args;
|
|
208
|
+
const warnings = [];
|
|
209
|
+
const serverSide = await detectServerSideOnly(owner, repo, issueBody, deps ?? {});
|
|
210
|
+
if (serverSide) {
|
|
211
|
+
warnings.push({
|
|
212
|
+
kind: "server-side",
|
|
213
|
+
line: `WARNING: the fix may not be buildable from this repo. ${serverSide.evidence}. Check with the maintainers before building.`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
const satellite = detectSatelliteRepo(owner, repo, policyText);
|
|
217
|
+
if (satellite) {
|
|
218
|
+
warnings.push({
|
|
219
|
+
kind: "satellite",
|
|
220
|
+
line: `WARNING: ${satellite.evidence}.`,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return warnings;
|
|
224
|
+
}
|
|
225
|
+
/* ------------------------------------------------------------------ */
|
|
226
|
+
/* 4. House-rules summary block */
|
|
227
|
+
/* ------------------------------------------------------------------ */
|
|
228
|
+
/**
|
|
229
|
+
* One-glance house rules at the top of the prep checklist: the required
|
|
230
|
+
* claim style, PR title conventions, draft-first norm, and the repo's test
|
|
231
|
+
* command. Every line names its source; absent rules are stated plainly
|
|
232
|
+
* instead of invented.
|
|
233
|
+
*/
|
|
234
|
+
export function buildHouseRulesSummary(args) {
|
|
235
|
+
const { gates, policyText, policyFiles, conventions } = args;
|
|
236
|
+
const out = [];
|
|
237
|
+
const files = policyFiles.length > 0 ? ` (${policyFiles.join(", ")})` : "";
|
|
238
|
+
// Claim style.
|
|
239
|
+
if (gateDetected(gates, "assignment_required")) {
|
|
240
|
+
out.push(`Claim style: request assignment on the issue first (${gateSource(gates, "assignment_required") ?? "house rules"}).`);
|
|
241
|
+
}
|
|
242
|
+
else if (gateDetected(gates, "claim_first")) {
|
|
243
|
+
out.push(`Claim style: comment on the issue before starting (${gateSource(gates, "claim_first") ?? "house rules"}).`);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
out.push(`Claim style: none required${files}.`);
|
|
247
|
+
}
|
|
248
|
+
// PR title convention.
|
|
249
|
+
if (/conventional commits/i.test(policyText)) {
|
|
250
|
+
out.push(`PR titles: Conventional Commits${files}.`);
|
|
251
|
+
}
|
|
252
|
+
else if (/semantic (pr )?titles?/i.test(policyText)) {
|
|
253
|
+
out.push(`PR titles: semantic titles${files}.`);
|
|
254
|
+
}
|
|
255
|
+
// Draft-first norm.
|
|
256
|
+
if (/\bopen.{0,30}as (a )?draft/i.test(policyText) || /\bdraft.{0,30}(first|before)/i.test(policyText)) {
|
|
257
|
+
out.push(`Draft-first: open the PR as a draft${files}.`);
|
|
258
|
+
}
|
|
259
|
+
// Test command.
|
|
260
|
+
if (conventions) {
|
|
261
|
+
if (conventions.verifyCommands.length > 0) {
|
|
262
|
+
out.push(`Test command: \`${conventions.verifyCommands[0]}\`${files || " (house rules)"}.`);
|
|
263
|
+
}
|
|
264
|
+
else if (conventions.packageScripts.length > 0) {
|
|
265
|
+
out.push(`Before pushing: ${conventions.packageScripts.map((s) => `\`pnpm ${s}\``).join(", ")} (package.json).`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Gather all four prep enhancements best-effort for a prep run. The
|
|
272
|
+
* snapshot's houseRules carry {file, text}; snapshot.contributing carries
|
|
273
|
+
* the joined text. Any failing probe degrades to "not detected".
|
|
274
|
+
*/
|
|
275
|
+
export async function gatherPrepDetail(args) {
|
|
276
|
+
const { owner, repo, issueNumber, slug, policyTexts, contributing, issueBody, conventions, deps } = args;
|
|
277
|
+
const policyText = [contributing ?? "", ...policyTexts.map((p) => p.text)].join("\n\n");
|
|
278
|
+
const policyFiles = policyTexts.map((p) => p.file).filter((f, i, a) => a.indexOf(f) === i);
|
|
279
|
+
const gates = parseGatesFromSources(policyTexts.length > 0 ? policyTexts : [{ file: "CONTRIBUTING.md", text: contributing ?? "" }]);
|
|
280
|
+
const claPath = detectClaPath(policyTexts.length > 0 ? policyTexts : [{ file: "CONTRIBUTING.md", text: contributing ?? "" }], gates);
|
|
281
|
+
let rootNames = [];
|
|
282
|
+
try {
|
|
283
|
+
if (deps?.rootNames)
|
|
284
|
+
rootNames = await deps.rootNames();
|
|
285
|
+
}
|
|
286
|
+
catch { /* root listing failed; skip */ }
|
|
287
|
+
let pyprojectText;
|
|
288
|
+
try {
|
|
289
|
+
if (deps?.pyprojectText)
|
|
290
|
+
pyprojectText = await deps.pyprojectText();
|
|
291
|
+
}
|
|
292
|
+
catch { /* no pyproject; skip */ }
|
|
293
|
+
const changeset = detectChangesetConvention({
|
|
294
|
+
rootNames,
|
|
295
|
+
policyText,
|
|
296
|
+
policySource: policyFiles[0],
|
|
297
|
+
pyprojectText,
|
|
298
|
+
issueNumber,
|
|
299
|
+
slug,
|
|
300
|
+
});
|
|
301
|
+
const fixLivesHereWarnings = await detectFixLivesHere({
|
|
302
|
+
owner,
|
|
303
|
+
repo,
|
|
304
|
+
issueBody: issueBody ?? "",
|
|
305
|
+
policyText,
|
|
306
|
+
deps,
|
|
307
|
+
});
|
|
308
|
+
const houseRulesSummary = buildHouseRulesSummary({
|
|
309
|
+
gates,
|
|
310
|
+
policyText,
|
|
311
|
+
policyFiles,
|
|
312
|
+
conventions,
|
|
313
|
+
});
|
|
314
|
+
return { claPath, changeset, fixLivesHereWarnings, houseRulesSummary };
|
|
315
|
+
}
|
|
316
|
+
//# sourceMappingURL=prep-detail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prep-detail.js","sourceRoot":"","sources":["../src/prep-detail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export type ReviewFindingClass = "likely-real" | "likely-noise" | "needs-decision";
|
|
2
|
+
export type ReviewTriageVerdict = "HAS LIKELY-REAL" | "NEEDS DECISIONS" | "ALL NOISE" | "NO FINDINGS";
|
|
3
|
+
export type AuthorKind = "bot" | "human";
|
|
4
|
+
export interface RawFinding {
|
|
5
|
+
/** Review-bot or human login, as GitHub reports it. */
|
|
6
|
+
author: string;
|
|
7
|
+
body: string;
|
|
8
|
+
/** File path the comment anchors to; absent for review-level comments. */
|
|
9
|
+
path?: string;
|
|
10
|
+
/** Right-side line the comment anchors to, when known. */
|
|
11
|
+
line?: number;
|
|
12
|
+
/** Diff hunk attached to the comment, when known. */
|
|
13
|
+
diffHunk?: string;
|
|
14
|
+
/** Parent review's state (APPROVED/CHANGES_REQUESTED/COMMENTED/DISMISSED). */
|
|
15
|
+
reviewState?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ClassifiedFinding {
|
|
18
|
+
author: string;
|
|
19
|
+
authorKind: AuthorKind;
|
|
20
|
+
path?: string;
|
|
21
|
+
line?: number;
|
|
22
|
+
summary: string;
|
|
23
|
+
class: ReviewFindingClass;
|
|
24
|
+
/** The deterministic signal that decided the class. */
|
|
25
|
+
reason: string;
|
|
26
|
+
/** A ready-to-adapt reply draft. Never posted by Greenlight. */
|
|
27
|
+
draft: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ReviewCounts {
|
|
30
|
+
likelyReal: number;
|
|
31
|
+
likelyNoise: number;
|
|
32
|
+
needsDecision: number;
|
|
33
|
+
/** Skipped by rule: summaries, walkthroughs, praise. */
|
|
34
|
+
noise: number;
|
|
35
|
+
/** Skipped because the parent review was dismissed. */
|
|
36
|
+
resolved: number;
|
|
37
|
+
}
|
|
38
|
+
export interface ReviewTriageInput {
|
|
39
|
+
prUrl: string;
|
|
40
|
+
prTitle: string;
|
|
41
|
+
/** The PR author's login: their own comments are their replies, not incoming findings. */
|
|
42
|
+
prAuthor: string;
|
|
43
|
+
candidates: RawFinding[];
|
|
44
|
+
}
|
|
45
|
+
export interface ReviewTriageResult {
|
|
46
|
+
pr: string;
|
|
47
|
+
title: string;
|
|
48
|
+
findings: ClassifiedFinding[];
|
|
49
|
+
counts: ReviewCounts;
|
|
50
|
+
verdict: ReviewTriageVerdict;
|
|
51
|
+
}
|
|
52
|
+
export declare function authorKindOf(login: string): AuthorKind;
|
|
53
|
+
export declare function isNoiseBody(body: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Backticked and double-quoted code tokens in the finding body, in order.
|
|
56
|
+
* Used for anchor matching: the comment's claim against the hunk's code.
|
|
57
|
+
*
|
|
58
|
+
* Pairing note: the regex allows 1-char spans in *matching* so backtick
|
|
59
|
+
* pairing stays aligned (a skipped 1-char span would re-pair the
|
|
60
|
+
* surrounding backticks into phantom tokens like " uses "); tokens
|
|
61
|
+
* shorter than 2 chars are dropped after matching. Newlines are excluded
|
|
62
|
+
* so fenced code blocks are never swallowed into one token.
|
|
63
|
+
*/
|
|
64
|
+
export declare function quotedTokens(body: string): string[];
|
|
65
|
+
/** First sentence, trimmed to a line. */
|
|
66
|
+
export declare function oneLineSummary(body: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Classify one actionable finding. Pure: all evidence comes from the
|
|
69
|
+
* candidate itself (author kind, body, anchor, hunk).
|
|
70
|
+
*/
|
|
71
|
+
export declare function classifyFinding(c: RawFinding): ClassifiedFinding;
|
|
72
|
+
/** Roll up per-finding classes into the verdict line. */
|
|
73
|
+
export declare function reviewVerdict(findings: ClassifiedFinding[]): ReviewTriageVerdict;
|
|
74
|
+
/**
|
|
75
|
+
* The pure engine: split noise, the author's own comments, and resolved
|
|
76
|
+
* comments off, classify the rest, roll up. Noise bodies (summaries,
|
|
77
|
+
* walkthroughs, praise) and findings on dismissed reviews never become
|
|
78
|
+
* findings. The author's own comments count as noise: they are the
|
|
79
|
+
* author's replies, not incoming review feedback.
|
|
80
|
+
*/
|
|
81
|
+
export declare function classifyReviewTriage(input: ReviewTriageInput): ReviewTriageResult;
|
|
82
|
+
interface ReviewApiItem {
|
|
83
|
+
id?: unknown;
|
|
84
|
+
user?: {
|
|
85
|
+
login?: unknown;
|
|
86
|
+
};
|
|
87
|
+
state?: unknown;
|
|
88
|
+
body?: unknown;
|
|
89
|
+
html_url?: unknown;
|
|
90
|
+
submitted_at?: unknown;
|
|
91
|
+
}
|
|
92
|
+
interface ReviewCommentApiItem {
|
|
93
|
+
id?: unknown;
|
|
94
|
+
user?: {
|
|
95
|
+
login?: unknown;
|
|
96
|
+
};
|
|
97
|
+
body?: unknown;
|
|
98
|
+
path?: unknown;
|
|
99
|
+
line?: unknown;
|
|
100
|
+
original_line?: unknown;
|
|
101
|
+
diff_hunk?: unknown;
|
|
102
|
+
pull_request_review_id?: unknown;
|
|
103
|
+
in_reply_to_id?: unknown;
|
|
104
|
+
html_url?: unknown;
|
|
105
|
+
created_at?: unknown;
|
|
106
|
+
}
|
|
107
|
+
interface IssueCommentApiItem {
|
|
108
|
+
id?: unknown;
|
|
109
|
+
user?: {
|
|
110
|
+
login?: unknown;
|
|
111
|
+
};
|
|
112
|
+
body?: unknown;
|
|
113
|
+
created_at?: unknown;
|
|
114
|
+
}
|
|
115
|
+
export interface ReviewTriageDeps {
|
|
116
|
+
fetchPull?: (owner: string, repo: string, number: number) => Promise<{
|
|
117
|
+
title: string;
|
|
118
|
+
author: string;
|
|
119
|
+
}>;
|
|
120
|
+
fetchReviews?: (owner: string, repo: string, number: number) => Promise<ReviewApiItem[]>;
|
|
121
|
+
fetchReviewComments?: (owner: string, repo: string, number: number) => Promise<ReviewCommentApiItem[]>;
|
|
122
|
+
fetchIssueComments?: (owner: string, repo: string, number: number) => Promise<IssueCommentApiItem[]>;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Full triage for a PR URL. Best-effort: a source that cannot be read
|
|
126
|
+
* degrades to "no findings from that source" rather than an error.
|
|
127
|
+
*/
|
|
128
|
+
export declare function triageReview(prUrl: string, deps?: ReviewTriageDeps): Promise<ReviewTriageResult>;
|
|
129
|
+
export {};
|