@perrylink/dsh-github 0.4.1 → 0.6.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.es.md +30 -13
- package/README.hi.md +30 -13
- package/README.md +53 -13
- package/README.pt.md +31 -14
- package/README.zh-CN.md +53 -13
- package/action.yml +161 -0
- package/lib/approval-gate.d.ts +14 -9
- package/lib/approval-gate.d.ts.map +1 -1
- package/lib/approval-gate.js +39 -1
- package/lib/approval-gate.js.map +1 -1
- package/lib/ci/bot.d.ts +50 -0
- package/lib/ci/bot.d.ts.map +1 -0
- package/lib/ci/bot.js +168 -0
- package/lib/ci/bot.js.map +1 -0
- package/lib/ci/pipeline.d.ts +84 -0
- package/lib/ci/pipeline.d.ts.map +1 -0
- package/lib/ci/pipeline.js +380 -0
- package/lib/ci/pipeline.js.map +1 -0
- package/lib/ci/review-rules.d.ts +41 -0
- package/lib/ci/review-rules.d.ts.map +1 -0
- package/lib/ci/review-rules.js +108 -0
- package/lib/ci/review-rules.js.map +1 -0
- package/lib/ci/tool.d.ts +4 -0
- package/lib/ci/tool.d.ts.map +1 -0
- package/lib/ci/tool.js +155 -0
- package/lib/ci/tool.js.map +1 -0
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +5 -2
- package/lib/commands.js.map +1 -1
- package/lib/config.d.ts +65 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +75 -2
- package/lib/config.js.map +1 -1
- package/lib/github.d.ts +11 -7
- package/lib/github.d.ts.map +1 -1
- package/lib/github.js +28 -7
- package/lib/github.js.map +1 -1
- package/lib/index.d.ts +15 -9
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +13 -1
- package/lib/index.js.map +1 -1
- package/lib/jobs.js +4 -3
- package/lib/jobs.js.map +1 -1
- package/lib/present.d.ts +146 -0
- package/lib/present.d.ts.map +1 -1
- package/lib/present.js +140 -1
- package/lib/present.js.map +1 -1
- package/lib/review.d.ts +11 -1
- package/lib/review.d.ts.map +1 -1
- package/lib/review.js +12 -9
- package/lib/review.js.map +1 -1
- package/lib/state.d.ts +3 -1
- package/lib/state.d.ts.map +1 -1
- package/lib/state.js +11 -6
- package/lib/state.js.map +1 -1
- package/lib/tools.d.ts +71 -0
- package/lib/tools.d.ts.map +1 -1
- package/lib/tools.js +379 -16
- package/lib/tools.js.map +1 -1
- package/package.json +6 -2
- package/scripts/action-patch.mjs +152 -0
- package/scripts/action-post.mjs +61 -0
- package/scripts/check-readmes.mjs +113 -0
- package/scripts/local-test.mjs +259 -0
- package/scripts/prepare.mjs +34 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CI review pipeline — one deterministic pass over a pull request,
|
|
3
|
+
* shared by the polling bot, the `/ci run` command, and the one-shot
|
|
4
|
+
* `ci_run` tool used by the composite action's headless session.
|
|
5
|
+
*
|
|
6
|
+
* Order of operations: resolve the token → fetch PR metadata (head SHA,
|
|
7
|
+
* labels, totals) → apply label/path filters → idempotency checks (completed
|
|
8
|
+
* check run with our name, and the review-body marker) → fetch the capped
|
|
9
|
+
* diff and changed-file list → run the deterministic analyzers (line rules
|
|
10
|
+
* from src/review.ts, PR-level rules from ./review-rules.ts) → post inline
|
|
11
|
+
* review comments → publish the status check (success / failure / neutral
|
|
12
|
+
* per `ci.blocking`) → write `dsh-github-ci-result.json` and
|
|
13
|
+
* `dsh-github-ci-summary.md` (+ `$GITHUB_OUTPUT` lines when present).
|
|
14
|
+
*
|
|
15
|
+
* Token discipline matches the rest of the plugin: the value is resolved per
|
|
16
|
+
* operation and handed only to the REST client — never into findings,
|
|
17
|
+
* reports, check output, or thrown messages.
|
|
18
|
+
* @module dsh-github/ci/pipeline
|
|
19
|
+
*/
|
|
20
|
+
import { appendFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { analyzeDiff, sanitizeFileName } from "../review.js";
|
|
23
|
+
import { analyzePr, matchesAnyGlob } from "./review-rules.js";
|
|
24
|
+
import { rateLimitValue } from "../state.js";
|
|
25
|
+
/** First line of the review body — the idempotency marker. */
|
|
26
|
+
export function reviewMarker(headSha) {
|
|
27
|
+
return `<!-- dsh-github-review:${headSha} -->`;
|
|
28
|
+
}
|
|
29
|
+
/** Whether a review body carries our marker for the given head commit. */
|
|
30
|
+
export function hasReviewMarker(body, headSha) {
|
|
31
|
+
return body !== undefined && body.includes(reviewMarker(headSha));
|
|
32
|
+
}
|
|
33
|
+
/** A review-timeout signal combining the caller's and the configured deadline. */
|
|
34
|
+
function timeoutSignal(state, signal) {
|
|
35
|
+
const deadline = AbortSignal.timeout(state.config.requestTimeoutMs);
|
|
36
|
+
return signal === undefined ? deadline : AbortSignal.any([signal, deadline]);
|
|
37
|
+
}
|
|
38
|
+
/** Conclude the verdict from the findings under the configured `failOn` policy. */
|
|
39
|
+
export function verdictFor(findings, failOn) {
|
|
40
|
+
const severity = (finding) => finding.severity === 'error' ? 2 : finding.severity === 'warning' ? 1 : 0;
|
|
41
|
+
const threshold = failOn === 'warning' ? 1 : 2;
|
|
42
|
+
return findings.some(finding => severity(finding) >= threshold) ? 'needs-changes' : 'pass';
|
|
43
|
+
}
|
|
44
|
+
/** Merge model-authored findings into the static set, deduping by file+line+rule. */
|
|
45
|
+
function mergeFindings(staticFindings, extra) {
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
const merged = [];
|
|
48
|
+
for (const finding of [...staticFindings, ...extra]) {
|
|
49
|
+
const key = `${finding.file}\u0000${finding.line ?? ''}\u0000${finding.rule}`;
|
|
50
|
+
if (seen.has(key))
|
|
51
|
+
continue;
|
|
52
|
+
seen.add(key);
|
|
53
|
+
merged.push({ ...finding });
|
|
54
|
+
}
|
|
55
|
+
return merged;
|
|
56
|
+
}
|
|
57
|
+
/** Summary line for a finished pass. */
|
|
58
|
+
function summarize(verdict, findings, truncated) {
|
|
59
|
+
const counts = (severity) => findings.filter(finding => finding.severity === severity).length;
|
|
60
|
+
if (verdict === 'skipped')
|
|
61
|
+
return 'skipped: no label/path filter match';
|
|
62
|
+
return `verdict ${verdict}: ${findings.length} finding(s) (${counts('error')} error / ${counts('warning')} warning / ${counts('info')} info)${truncated ? ' (diff truncated)' : ''}`;
|
|
63
|
+
}
|
|
64
|
+
/** Markdown report body written next to the JSON result. */
|
|
65
|
+
export function formatMarkdownReport(result, options) {
|
|
66
|
+
const lines = [
|
|
67
|
+
`# dsh-github CI review — PR #${result.pr} (${result.repo})`,
|
|
68
|
+
'',
|
|
69
|
+
`Verdict: **${result.verdict}**${result.verdict === 'needs-changes' ? ` (${options.blocking ? 'blocking' : 'non-blocking'})` : ''}${result.alreadyReviewed ? ' — already reviewed at this head commit' : ''}`,
|
|
70
|
+
'',
|
|
71
|
+
];
|
|
72
|
+
if (result.checkRun !== undefined)
|
|
73
|
+
lines.push(`Check: [${options.checkName}](${result.checkRun.url}) — conclusion \`${result.checkRun.conclusion}\``, '');
|
|
74
|
+
if (result.review !== undefined)
|
|
75
|
+
lines.push(`Review: ${result.review.url} (${result.review.inlineComments} inline comment(s))`, '');
|
|
76
|
+
if (result.findings.length === 0) {
|
|
77
|
+
lines.push('No findings.', '');
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
lines.push(`## Findings (${result.findings.length})`, '');
|
|
81
|
+
for (const finding of result.findings) {
|
|
82
|
+
const anchor = finding.file.length === 0 ? 'PR' : `\`${sanitizeFileName(finding.file)}\`${finding.line !== null ? `:${finding.line}` : ''}`;
|
|
83
|
+
lines.push(`- **${finding.severity}** \`${finding.rule}\` ${anchor}: ${finding.message}`);
|
|
84
|
+
}
|
|
85
|
+
lines.push('');
|
|
86
|
+
}
|
|
87
|
+
if (result.truncated)
|
|
88
|
+
lines.push('> Note: the diff exceeded the cap and was truncated; this report covers only the examined range.', '');
|
|
89
|
+
lines.push('*Generated by [dsh-github](https://github.com/PerryLink/dsh-github) CI.*');
|
|
90
|
+
return lines.join('\n');
|
|
91
|
+
}
|
|
92
|
+
/** Directory the report files land in: env var → config → workspace dir. */
|
|
93
|
+
function reportDirectory(state) {
|
|
94
|
+
const fromEnv = process.env.DSH_GITHUB_CI_OUTPUT_DIR?.trim();
|
|
95
|
+
if (fromEnv !== undefined && fromEnv.length > 0)
|
|
96
|
+
return fromEnv;
|
|
97
|
+
if (state.config.ci.reportDir.trim().length > 0)
|
|
98
|
+
return state.config.ci.reportDir;
|
|
99
|
+
return state.workspaceDir;
|
|
100
|
+
}
|
|
101
|
+
/** Write the JSON + Markdown reports and, when present, `$GITHUB_OUTPUT` lines. */
|
|
102
|
+
function writeReports(state, result) {
|
|
103
|
+
const dir = reportDirectory(state);
|
|
104
|
+
mkdirSync(dir, { recursive: true });
|
|
105
|
+
const jsonPath = join(dir, 'dsh-github-ci-result.json');
|
|
106
|
+
const markdownPath = join(dir, 'dsh-github-ci-summary.md');
|
|
107
|
+
writeFileSync(jsonPath, JSON.stringify({ schemaVersion: 1, ...result, files: { json: jsonPath, markdown: markdownPath } }, null, 2) + '\n');
|
|
108
|
+
writeFileSync(markdownPath, formatMarkdownReport(result, { blocking: state.config.ci.blocking, checkName: state.config.ci.checkName }) + '\n');
|
|
109
|
+
const outputFile = process.env.GITHUB_OUTPUT;
|
|
110
|
+
if (outputFile !== undefined && outputFile.length > 0) {
|
|
111
|
+
appendFileSync(outputFile, `verdict=${result.verdict}\n`
|
|
112
|
+
+ `report-json=${jsonPath}\n`
|
|
113
|
+
+ `report-markdown=${markdownPath}\n`
|
|
114
|
+
+ `${result.checkRun !== undefined ? `check-url=${result.checkRun.url}\n` : ''}`);
|
|
115
|
+
}
|
|
116
|
+
return { json: jsonPath, markdown: markdownPath };
|
|
117
|
+
}
|
|
118
|
+
/** The published review body: marker, PR-level findings, and (optionally) the model override. */
|
|
119
|
+
function buildReviewBody(headSha, prFindings, override, truncated) {
|
|
120
|
+
const sections = [reviewMarker(headSha), '## dsh-github review'];
|
|
121
|
+
if (prFindings.length > 0) {
|
|
122
|
+
sections.push('');
|
|
123
|
+
for (const finding of prFindings) {
|
|
124
|
+
const anchor = finding.file.length === 0 ? 'PR' : `\`${sanitizeFileName(finding.file)}\``;
|
|
125
|
+
sections.push(`- **${finding.severity}** \`${finding.rule}\` ${anchor}: ${finding.message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (override !== undefined && override.trim().length > 0) {
|
|
129
|
+
sections.push('', '## Review', '', override.trim());
|
|
130
|
+
}
|
|
131
|
+
if (truncated)
|
|
132
|
+
sections.push('', '> Note: the diff exceeded the cap and was truncated; this report covers only the examined range.');
|
|
133
|
+
return sections.join('\n');
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Run one CI pass over a pull request. Pure in the sense that every GitHub
|
|
137
|
+
* interaction goes through the per-operation token and the shared client;
|
|
138
|
+
* callers (bot, tool) own their own concurrency control.
|
|
139
|
+
* @param state - shared plugin state.
|
|
140
|
+
* @param options - target repo/PR, task, caps, and overrides.
|
|
141
|
+
* @returns the structured outcome; failures are token-free error variants.
|
|
142
|
+
*/
|
|
143
|
+
export async function runCiPipeline(state, options) {
|
|
144
|
+
const signal = timeoutSignal(state, options.signal);
|
|
145
|
+
const maxDiffChars = options.maxDiffChars !== undefined && options.maxDiffChars > 0 ? Math.floor(options.maxDiffChars) : state.config.maxDiffChars;
|
|
146
|
+
const postComments = options.task !== 'analyze' && (options.postComments ?? state.config.ci.postComments);
|
|
147
|
+
const postCheck = options.task !== 'analyze' && (options.postCheck ?? true);
|
|
148
|
+
const token = await state.resolveToken(signal);
|
|
149
|
+
if (!token.ok) {
|
|
150
|
+
return { status: 'error', code: token.error.code, message: token.error.message, guidance: token.error.guidance };
|
|
151
|
+
}
|
|
152
|
+
const client = state.client(token.token.value);
|
|
153
|
+
const errorCode = (error) => error instanceof Error && error.name === 'AbortError' ? 'aborted'
|
|
154
|
+
: error instanceof Error && 'status' in error && typeof error.status === 'number' ? `github-api-${error.status}` : 'fetch-failed';
|
|
155
|
+
const rateLimitOf = (error) => {
|
|
156
|
+
if (error instanceof Error && 'rateLimit' in error) {
|
|
157
|
+
const info = error.rateLimit;
|
|
158
|
+
if (info !== undefined)
|
|
159
|
+
return { remaining: info.remaining, resetAt: info.resetAt };
|
|
160
|
+
}
|
|
161
|
+
return undefined;
|
|
162
|
+
};
|
|
163
|
+
const fail = (error) => ({
|
|
164
|
+
status: 'error',
|
|
165
|
+
code: errorCode(error),
|
|
166
|
+
message: error instanceof Error ? error.message : 'CI review failed',
|
|
167
|
+
...rateLimitOf(error) !== undefined ? { rateLimit: rateLimitOf(error) } : {},
|
|
168
|
+
});
|
|
169
|
+
let meta;
|
|
170
|
+
let rateLimit = { remaining: null, resetAt: null };
|
|
171
|
+
try {
|
|
172
|
+
const response = await client.requestJson('GET', `/repos/${options.repo}/pulls/${options.pr}`, { signal });
|
|
173
|
+
meta = response.data;
|
|
174
|
+
rateLimit = rateLimitValue(response.rateLimit);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
return fail(error);
|
|
178
|
+
}
|
|
179
|
+
const headSha = meta.head?.sha ?? '';
|
|
180
|
+
if (headSha.length === 0)
|
|
181
|
+
return { status: 'error', code: 'no-head-sha', message: `PR #${options.pr} reports no head commit` };
|
|
182
|
+
const additions = meta.additions ?? 0;
|
|
183
|
+
const deletions = meta.deletions ?? 0;
|
|
184
|
+
const labels = (meta.labels ?? []).map(label => label.name ?? '').filter(name => name.length > 0);
|
|
185
|
+
// Filter step: label and path filters skip the PR without posting anything.
|
|
186
|
+
const labelMatch = state.config.ci.labelFilters.length === 0
|
|
187
|
+
|| labels.some(label => state.config.ci.labelFilters.some(filter => filter.trim() === label));
|
|
188
|
+
let files = [];
|
|
189
|
+
try {
|
|
190
|
+
for (let page = 1; page <= 4; page += 1) {
|
|
191
|
+
const response = await client.requestJson('GET', `/repos/${options.repo}/pulls/${options.pr}/files?per_page=100&page=${page}`, { signal });
|
|
192
|
+
for (const entry of response.data) {
|
|
193
|
+
files.push({ path: entry.filename ?? '', added: entry.additions ?? 0, removed: entry.deletions ?? 0 });
|
|
194
|
+
}
|
|
195
|
+
if (response.data.length < 100 || files.length >= 400)
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
files = []; // path filtering degrades to "match all" when the file list is unavailable.
|
|
201
|
+
}
|
|
202
|
+
const pathMatch = state.config.ci.pathFilters.length === 0
|
|
203
|
+
|| files.length === 0
|
|
204
|
+
|| files.some(file => matchesAnyGlob(file.path, state.config.ci.pathFilters));
|
|
205
|
+
if (!labelMatch || !pathMatch) {
|
|
206
|
+
let checkRun;
|
|
207
|
+
if (postCheck) {
|
|
208
|
+
checkRun = await publishCheck(client, options.repo, headSha, state, 'skipped', 0, 'skipped: no label/path filter match');
|
|
209
|
+
}
|
|
210
|
+
const result = {
|
|
211
|
+
status: 'ok',
|
|
212
|
+
repo: options.repo,
|
|
213
|
+
pr: options.pr,
|
|
214
|
+
headSha,
|
|
215
|
+
verdict: 'skipped',
|
|
216
|
+
engine: state.config.ci.engine,
|
|
217
|
+
findings: [],
|
|
218
|
+
summary: summarize('skipped', [], false),
|
|
219
|
+
truncated: false,
|
|
220
|
+
alreadyReviewed: false,
|
|
221
|
+
...checkRun !== undefined ? { checkRun } : {},
|
|
222
|
+
rateLimit,
|
|
223
|
+
};
|
|
224
|
+
const reportFiles = options.task !== 'analyze' ? writeReports(state, result) : undefined;
|
|
225
|
+
return { ...result, ...reportFiles !== undefined ? { files: reportFiles } : {} };
|
|
226
|
+
}
|
|
227
|
+
// Idempotency: a completed check run for this head commit means the gate was
|
|
228
|
+
// already published; a review-body marker means comments were already posted.
|
|
229
|
+
// The marker scan always runs (not only when the check is missing), so a
|
|
230
|
+
// rerun after a comment-only failure can repair the comments without
|
|
231
|
+
// duplicating them.
|
|
232
|
+
let existingCheck;
|
|
233
|
+
let commentsPosted = false;
|
|
234
|
+
try {
|
|
235
|
+
const checkName = encodeURIComponent(state.config.ci.checkName);
|
|
236
|
+
const response = await client.requestJson('GET', `/repos/${options.repo}/commits/${headSha}/check-runs?check_name=${checkName}`, { signal });
|
|
237
|
+
existingCheck = (response.data.check_runs ?? []).find(run => run.status === 'completed');
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
existingCheck = undefined;
|
|
241
|
+
}
|
|
242
|
+
if (postComments) {
|
|
243
|
+
try {
|
|
244
|
+
const response = await client.requestJson('GET', `/repos/${options.repo}/pulls/${options.pr}/reviews?per_page=100`, { signal });
|
|
245
|
+
commentsPosted = response.data.some(review => hasReviewMarker(review.body, headSha));
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
commentsPosted = false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// The deterministic analysis runs even for an already-reviewed PR, so a
|
|
252
|
+
// re-run can refresh the gate from the same inputs (zero cost, stable).
|
|
253
|
+
let diffText = '';
|
|
254
|
+
let truncated = false;
|
|
255
|
+
try {
|
|
256
|
+
const text = await client.requestText('GET', `/repos/${options.repo}/pulls/${options.pr}`, { signal });
|
|
257
|
+
truncated = text.text.length > maxDiffChars;
|
|
258
|
+
diffText = truncated ? text.text.slice(0, maxDiffChars) : text.text;
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
return fail(error);
|
|
262
|
+
}
|
|
263
|
+
const lineFindings = analyzeDiff(diffText, maxDiffChars, { maxFindings: state.config.maxFindings, maxLineLength: state.config.maxLineLength }).findings;
|
|
264
|
+
const prFindings = analyzePr({
|
|
265
|
+
files,
|
|
266
|
+
additions,
|
|
267
|
+
deletions,
|
|
268
|
+
options: state.config.ci,
|
|
269
|
+
});
|
|
270
|
+
const staticFindings = [...prFindings, ...lineFindings];
|
|
271
|
+
const extraFindings = options.task === 'publish' ? options.findings ?? [] : [];
|
|
272
|
+
const findings = mergeFindings(staticFindings, extraFindings);
|
|
273
|
+
const verdict = verdictFor(findings, state.config.ci.failOn);
|
|
274
|
+
if (options.task === 'analyze') {
|
|
275
|
+
return {
|
|
276
|
+
status: 'ok',
|
|
277
|
+
repo: options.repo,
|
|
278
|
+
pr: options.pr,
|
|
279
|
+
headSha,
|
|
280
|
+
verdict: 'skipped',
|
|
281
|
+
engine: state.config.ci.engine,
|
|
282
|
+
findings: staticFindings,
|
|
283
|
+
summary: 'analysis prepared; no comments or checks were posted',
|
|
284
|
+
truncated,
|
|
285
|
+
alreadyReviewed: existingCheck !== undefined || commentsPosted,
|
|
286
|
+
diffText,
|
|
287
|
+
rateLimit,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
let review;
|
|
291
|
+
if (postComments && !commentsPosted) {
|
|
292
|
+
const inline = findings.filter(finding => finding.line !== null).slice(0, state.config.maxFindings);
|
|
293
|
+
const prLevel = findings.filter(finding => finding.line === null);
|
|
294
|
+
try {
|
|
295
|
+
const posted = await client.requestJson('POST', `/repos/${options.repo}/pulls/${options.pr}/reviews`, {
|
|
296
|
+
signal,
|
|
297
|
+
body: {
|
|
298
|
+
body: buildReviewBody(headSha, prLevel, options.body, truncated),
|
|
299
|
+
event: 'COMMENT',
|
|
300
|
+
...inline.length > 0 ? {
|
|
301
|
+
comments: inline.map(finding => ({
|
|
302
|
+
path: finding.file,
|
|
303
|
+
line: finding.line,
|
|
304
|
+
body: `**${finding.severity}** \`${finding.rule}\`: ${finding.message}`,
|
|
305
|
+
})),
|
|
306
|
+
} : {},
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
review = { url: posted.data.html_url ?? '', inlineComments: inline.length };
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
return fail(error);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else if (postComments) {
|
|
316
|
+
review = { url: '', inlineComments: 0 }; // already posted at this head commit
|
|
317
|
+
}
|
|
318
|
+
let checkRun;
|
|
319
|
+
if (postCheck && existingCheck === undefined) {
|
|
320
|
+
checkRun = await publishCheck(client, options.repo, headSha, state, verdict, findings.length, summarize(verdict, findings, truncated));
|
|
321
|
+
}
|
|
322
|
+
else if (existingCheck !== undefined) {
|
|
323
|
+
checkRun = {
|
|
324
|
+
id: existingCheck.id ?? 0,
|
|
325
|
+
url: existingCheck.html_url ?? '',
|
|
326
|
+
conclusion: existingCheck.conclusion ?? '',
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const effectiveVerdict = existingCheck !== undefined ? verdictFromConclusion(existingCheck.conclusion) : verdict;
|
|
330
|
+
const result = {
|
|
331
|
+
status: 'ok',
|
|
332
|
+
repo: options.repo,
|
|
333
|
+
pr: options.pr,
|
|
334
|
+
headSha,
|
|
335
|
+
verdict: effectiveVerdict,
|
|
336
|
+
engine: state.config.ci.engine,
|
|
337
|
+
findings,
|
|
338
|
+
summary: existingCheck !== undefined ? `already reviewed at ${headSha.slice(0, 7)}: ${summarize(effectiveVerdict, findings, truncated)}` : summarize(effectiveVerdict, findings, truncated),
|
|
339
|
+
truncated,
|
|
340
|
+
alreadyReviewed: existingCheck !== undefined || commentsPosted,
|
|
341
|
+
...review !== undefined ? { review } : {},
|
|
342
|
+
...checkRun !== undefined ? { checkRun } : {},
|
|
343
|
+
rateLimit,
|
|
344
|
+
};
|
|
345
|
+
const reportFiles = writeReports(state, result);
|
|
346
|
+
return { ...result, files: reportFiles };
|
|
347
|
+
}
|
|
348
|
+
/** Verdict recovered from an existing check conclusion. */
|
|
349
|
+
function verdictFromConclusion(conclusion) {
|
|
350
|
+
if (conclusion === 'failure' || conclusion === 'action_required')
|
|
351
|
+
return 'needs-changes';
|
|
352
|
+
if (conclusion === 'neutral')
|
|
353
|
+
return 'skipped';
|
|
354
|
+
return 'pass';
|
|
355
|
+
}
|
|
356
|
+
/** Create one completed status check for the run. */
|
|
357
|
+
async function publishCheck(client, repo, headSha, state, verdict, findingCount, summary) {
|
|
358
|
+
const conclusion = verdict === 'pass' ? 'success' : verdict === 'needs-changes' ? (state.config.ci.blocking ? 'failure' : 'neutral') : 'neutral';
|
|
359
|
+
try {
|
|
360
|
+
const posted = await client.requestJson('POST', `/repos/${repo}/commits/${headSha}/check-runs`, {
|
|
361
|
+
body: {
|
|
362
|
+
name: state.config.ci.checkName,
|
|
363
|
+
head_sha: headSha,
|
|
364
|
+
status: 'completed',
|
|
365
|
+
conclusion,
|
|
366
|
+
completed_at: new Date().toISOString(),
|
|
367
|
+
output: {
|
|
368
|
+
title: `dsh-github review: ${verdict}`,
|
|
369
|
+
summary: `${summary}\n\n${verdict === 'needs-changes' ? (state.config.ci.blocking ? 'Blocking: merge is held until the findings are addressed.' : 'Non-blocking: the gate reports findings without failing the check.') : ''}`,
|
|
370
|
+
annotations: [],
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
return { id: posted.data.id ?? 0, url: posted.data.html_url ?? '', conclusion };
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return undefined; // the gate degrades to the report files; callers see the verdict in the result.
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/ci/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR-level review rules for the CI pipeline: change scope, test existence,
|
|
3
|
+
* and sensitive files. Line-level findings stay in src/review.ts; these rules
|
|
4
|
+
* reason about the pull request as a whole (file list, totals) and produce
|
|
5
|
+
* findings anchored to a file (`line: null`) or to the PR itself (`file: ''`).
|
|
6
|
+
*
|
|
7
|
+
* The rules are deterministic and model-free — the same review runs in the
|
|
8
|
+
* polling bot, the headless action, and the one-shot `ci_run` tool with
|
|
9
|
+
* identical output.
|
|
10
|
+
* @module dsh-github/ci/review-rules
|
|
11
|
+
*/
|
|
12
|
+
import type { Finding } from '../review.ts';
|
|
13
|
+
import type { CiConfig } from '../config.ts';
|
|
14
|
+
/** One changed file's stats, as reported by the PR files endpoint. */
|
|
15
|
+
export interface ChangedFileStat {
|
|
16
|
+
path: string;
|
|
17
|
+
added: number;
|
|
18
|
+
removed: number;
|
|
19
|
+
}
|
|
20
|
+
/** Facts the PR-level rules reason over. */
|
|
21
|
+
export interface PrReviewInput {
|
|
22
|
+
/** Changed files with per-file added/removed counts. */
|
|
23
|
+
files: ChangedFileStat[];
|
|
24
|
+
/** PR-wide added/removed line totals. */
|
|
25
|
+
additions: number;
|
|
26
|
+
deletions: number;
|
|
27
|
+
/** Tunable rule options from the CI config. */
|
|
28
|
+
options: Pick<CiConfig, 'sensitivePathPatterns' | 'sensitiveSeverity' | 'codeExtensions' | 'testPathPatterns' | 'maxChangedFiles' | 'maxAddedLines' | 'maxRemovedLines'>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Translate one glob into an anchored RegExp supporting `*`, `**`, and `?`.
|
|
32
|
+
* A pattern without a `/` is a basename glob and matches at any depth
|
|
33
|
+
* (`.env` matches `config/.env`, `*.pem` matches `certs/server.pem`);
|
|
34
|
+
* patterns containing `/` match against the whole repository path.
|
|
35
|
+
*/
|
|
36
|
+
export declare function globToRegExp(pattern: string): RegExp;
|
|
37
|
+
/** Whether a repository path matches any of the configured globs. */
|
|
38
|
+
export declare function matchesAnyGlob(path: string, patterns: readonly string[]): boolean;
|
|
39
|
+
/** PR-level findings for one pull request; deterministic and ordered. */
|
|
40
|
+
export declare function analyzePr(input: PrReviewInput): Finding[];
|
|
41
|
+
//# sourceMappingURL=review-rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-rules.d.ts","sourceRoot":"","sources":["../../src/ci/review-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAE5C,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC5B,wDAAwD;IACxD,KAAK,EAAE,eAAe,EAAE,CAAA;IACxB,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,+CAA+C;IAC/C,OAAO,EAAE,IAAI,CAAC,QAAQ,EAClB,uBAAuB,GAAG,mBAAmB,GAC7C,gBAAgB,GAAG,kBAAkB,GACrC,iBAAiB,GAAG,eAAe,GAAG,iBAAiB,CAC1D,CAAA;CACF;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA4BpD;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAGjF;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,EAAE,CAmEzD"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate one glob into an anchored RegExp supporting `*`, `**`, and `?`.
|
|
3
|
+
* A pattern without a `/` is a basename glob and matches at any depth
|
|
4
|
+
* (`.env` matches `config/.env`, `*.pem` matches `certs/server.pem`);
|
|
5
|
+
* patterns containing `/` match against the whole repository path.
|
|
6
|
+
*/
|
|
7
|
+
export function globToRegExp(pattern) {
|
|
8
|
+
let source = '';
|
|
9
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
10
|
+
const char = pattern[index] ?? '';
|
|
11
|
+
if (char === '*') {
|
|
12
|
+
if (pattern[index + 1] === '*') {
|
|
13
|
+
// `**/` matches any directory depth (including none).
|
|
14
|
+
if (pattern[index + 2] === '/') {
|
|
15
|
+
source += '(?:.*/)?';
|
|
16
|
+
index += 2;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
source += '.*';
|
|
20
|
+
index += 1;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
source += '[^/]*';
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (char === '?') {
|
|
27
|
+
source += '[^/]';
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if ('\\^$.*+?()[]{}|'.includes(char))
|
|
31
|
+
source += `\\${char}`;
|
|
32
|
+
else
|
|
33
|
+
source += char;
|
|
34
|
+
}
|
|
35
|
+
const prefix = pattern.includes('/') ? '' : '(?:.*/)?';
|
|
36
|
+
return new RegExp(`^${prefix}${source}$`);
|
|
37
|
+
}
|
|
38
|
+
/** Whether a repository path matches any of the configured globs. */
|
|
39
|
+
export function matchesAnyGlob(path, patterns) {
|
|
40
|
+
const regexes = patterns.map(pattern => globToRegExp(pattern));
|
|
41
|
+
return regexes.some(regex => regex.test(path));
|
|
42
|
+
}
|
|
43
|
+
/** PR-level findings for one pull request; deterministic and ordered. */
|
|
44
|
+
export function analyzePr(input) {
|
|
45
|
+
const findings = [];
|
|
46
|
+
const { files, options } = input;
|
|
47
|
+
// Sensitive files: credentials, keys, secrets, and CI workflow edits need
|
|
48
|
+
// human eyes regardless of what the diff text itself looks like.
|
|
49
|
+
if (options.sensitivePathPatterns.length > 0) {
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
if (matchesAnyGlob(file.path, options.sensitivePathPatterns)) {
|
|
52
|
+
findings.push({
|
|
53
|
+
file: file.path,
|
|
54
|
+
line: null,
|
|
55
|
+
severity: options.sensitiveSeverity,
|
|
56
|
+
rule: 'sensitive-file',
|
|
57
|
+
message: 'sensitive path changed (credentials, keys, secrets, or CI workflows); review carefully and never embed secrets',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Change scope: total file count and line totals beyond the caps.
|
|
63
|
+
if (files.length > options.maxChangedFiles) {
|
|
64
|
+
findings.push({
|
|
65
|
+
file: '',
|
|
66
|
+
line: null,
|
|
67
|
+
severity: 'info',
|
|
68
|
+
rule: 'large-change',
|
|
69
|
+
message: `PR touches ${files.length} files (cap ${options.maxChangedFiles}); consider splitting it into smaller changes`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (input.additions > options.maxAddedLines) {
|
|
73
|
+
findings.push({
|
|
74
|
+
file: '',
|
|
75
|
+
line: null,
|
|
76
|
+
severity: 'info',
|
|
77
|
+
rule: 'large-change',
|
|
78
|
+
message: `PR adds ${input.additions} lines (cap ${options.maxAddedLines}); consider splitting it into smaller changes`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (input.deletions > options.maxRemovedLines) {
|
|
82
|
+
findings.push({
|
|
83
|
+
file: '',
|
|
84
|
+
line: null,
|
|
85
|
+
severity: 'info',
|
|
86
|
+
rule: 'large-change',
|
|
87
|
+
message: `PR removes ${input.deletions} lines (cap ${options.maxRemovedLines}); double-check the removed behavior`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Test existence: code changed without any test change.
|
|
91
|
+
if (options.codeExtensions.length > 0 && options.testPathPatterns.length > 0) {
|
|
92
|
+
const isCode = (path) => options.codeExtensions.some(ext => path.toLowerCase().endsWith(ext.toLowerCase()));
|
|
93
|
+
const isTest = (path) => matchesAnyGlob(path, options.testPathPatterns);
|
|
94
|
+
const codeFiles = files.filter(file => isCode(file.path));
|
|
95
|
+
const testFiles = files.filter(file => isTest(file.path));
|
|
96
|
+
if (codeFiles.length > 0 && testFiles.length === 0) {
|
|
97
|
+
findings.push({
|
|
98
|
+
file: '',
|
|
99
|
+
line: null,
|
|
100
|
+
severity: 'warning',
|
|
101
|
+
rule: 'missing-tests',
|
|
102
|
+
message: `${codeFiles.length} code file(s) changed with no test changes; consider adding coverage for the new behavior`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return findings;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=review-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-rules.js","sourceRoot":"","sources":["../../src/ci/review-rules.ts"],"names":[],"mappings":"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"}
|
package/lib/ci/tool.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/ci/tool.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAmB9C,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,mDAiH3C"}
|