argusqa-os 9.7.5 โ 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -9
- package/glama.json +2 -2
- package/package.json +12 -4
- package/src/adapters/browser.js +13 -1
- package/src/cli/pr-validate.js +275 -56
- package/src/mcp-server.js +142 -26
- package/src/orchestration/crawl-and-report.js +1 -1
- package/src/orchestration/orchestrator.js +64 -13
- package/src/utils/audit-depth.js +148 -0
- package/src/utils/deploy-preview.js +210 -0
- package/src/utils/github-api.js +242 -0
- package/src/utils/github-reporter.js +251 -39
- package/src/utils/html-reporter.js +283 -92
- package/src/utils/import-graph.js +290 -0
- package/src/utils/issues-analyzer.js +8 -2
- package/src/utils/lighthouse-checker.js +44 -4
- package/src/utils/parallel-crawler.js +202 -0
- package/src/utils/pr-baseline.js +230 -0
- package/src/utils/pr-diff-analyzer.js +378 -40
- package/src/utils/route-discoverer.js +25 -3
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { childLogger } from './logger.js';
|
|
30
|
+
import { parsePrUrl } from './pr-diff-analyzer.js';
|
|
31
|
+
import { githubFetch } from './github-api.js';
|
|
30
32
|
|
|
31
33
|
const logger = childLogger('github-reporter');
|
|
32
34
|
|
|
@@ -44,6 +46,45 @@ function mdCell(text, maxLen = 100) {
|
|
|
44
46
|
return String(text ?? '').slice(0, maxLen).replace(/\|/g, '\\|').replace(/\n/g, ' '); // lgtm[js/incomplete-string-escaping] โ escaping pipe and newline is correct and sufficient for GitHub Markdown table cells
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Build the PR-Validator banner lines (block verdict + reason + affected routes).
|
|
51
|
+
* Rendered at the top of the comment only when report.prValidation is present, so
|
|
52
|
+
* existing runCrawl()-sourced reports are unaffected.
|
|
53
|
+
*/
|
|
54
|
+
function prValidationBanner(pv) {
|
|
55
|
+
const bl = pv.baseline;
|
|
56
|
+
const baselineAware = !!(bl && bl.available);
|
|
57
|
+
const lines = [
|
|
58
|
+
pv.blocked
|
|
59
|
+
? `> ๐ด **Merge blocked** โ ${pv.reason} (block-on: \`${pv.blockOn}\`)`
|
|
60
|
+
: baselineAware
|
|
61
|
+
? `> โ
**Merge allowed** โ this PR introduces no findings at or above the \`${pv.blockOn}\` threshold`
|
|
62
|
+
: `> โ
**Merge allowed** โ no findings at or above the \`${pv.blockOn}\` threshold`,
|
|
63
|
+
];
|
|
64
|
+
// Baseline-aware surfacing (Phase B2): what this PR INTRODUCES vs what already existed on the
|
|
65
|
+
// affected routes, so a reviewer sees why the merge was (or wasn't) blocked. When no per-branch
|
|
66
|
+
// baseline was available the decision fell back to absolute counts โ say so, never silently.
|
|
67
|
+
if (baselineAware) {
|
|
68
|
+
lines.push(
|
|
69
|
+
'',
|
|
70
|
+
'Blocking on findings this PR **introduces** (vs the base-branch baseline): ',
|
|
71
|
+
`๐ด ${bl.newCritical} new critical ยท ๐ก ${bl.newWarning} new warning ยท ๐ต ${bl.newInfo} new info ยท ${bl.persisting} persisting ยท ${bl.resolved} resolved `,
|
|
72
|
+
);
|
|
73
|
+
} else if (bl && bl.available === false) {
|
|
74
|
+
lines.push('', `> โ ๏ธ ${bl.note ?? 'Baseline unavailable โ blocking on absolute finding counts.'} `);
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(pv.affectedRoutes) && pv.affectedRoutes.length > 0) {
|
|
77
|
+
const shown = pv.affectedRoutes.slice(0, 20).map(r => `\`${r}\``).join(', ');
|
|
78
|
+
const extra = pv.affectedRoutes.length > 20 ? ` _(+${pv.affectedRoutes.length - 20} more)_` : '';
|
|
79
|
+
lines.push('', `**Affected routes** (${pv.affectedRoutes.length}): ${shown}${extra} `);
|
|
80
|
+
}
|
|
81
|
+
if (typeof pv.changedFileCount === 'number') {
|
|
82
|
+
lines.push(`**Files changed**: ${pv.changedFileCount} `);
|
|
83
|
+
}
|
|
84
|
+
lines.push('');
|
|
85
|
+
return lines;
|
|
86
|
+
}
|
|
87
|
+
|
|
47
88
|
// โโ C2.1: PR comment formatter (pure โ no I/O) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
48
89
|
|
|
49
90
|
/**
|
|
@@ -89,6 +130,7 @@ export function formatPrComment(report, diff) {
|
|
|
89
130
|
`**Base URL**: ${baseUrl} `,
|
|
90
131
|
`**Run time**: ${runDate} `,
|
|
91
132
|
'',
|
|
133
|
+
...(report.prValidation ? prValidationBanner(report.prValidation) : []),
|
|
92
134
|
'| | ๐ด Critical | ๐ก Warning | ๐ต Info | Total |',
|
|
93
135
|
'|---|---|---|---|---|',
|
|
94
136
|
`| **Total** | ${summary.critical} | ${summary.warning} | ${summary.info} | ${summary.total} |`,
|
|
@@ -217,7 +259,7 @@ export function buildStatusPayload(report, diff) {
|
|
|
217
259
|
|
|
218
260
|
// โโ GitHub API helper โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
219
261
|
|
|
220
|
-
async function ghFetch(urlPath, method, body
|
|
262
|
+
async function ghFetch(urlPath, method, body) {
|
|
221
263
|
if (!process.env.GITHUB_TOKEN) {
|
|
222
264
|
throw new Error('GITHUB_TOKEN environment variable is not set โ GitHub reporting is disabled');
|
|
223
265
|
}
|
|
@@ -228,33 +270,16 @@ async function ghFetch(urlPath, method, body, attempt = 1) {
|
|
|
228
270
|
};
|
|
229
271
|
if (body) headers['Content-Type'] = 'application/json';
|
|
230
272
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (attempt < 3) {
|
|
242
|
-
await new Promise(r => setTimeout(r, attempt * 1000));
|
|
243
|
-
return ghFetch(urlPath, method, body, attempt + 1);
|
|
244
|
-
}
|
|
245
|
-
throw err;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Retry on transient server errors (5xx) and rate-limit (429) with exponential backoff
|
|
249
|
-
if ((res.status >= 500 || res.status === 429) && attempt < 3) {
|
|
250
|
-
await new Promise(r => setTimeout(r, attempt * 1000));
|
|
251
|
-
return ghFetch(urlPath, method, body, attempt + 1);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (!res.ok) {
|
|
255
|
-
const text = await res.text().catch(() => '');
|
|
256
|
-
throw new Error(`GitHub API ${method} ${urlPath} โ ${res.status}: ${text.slice(0, 200)}`);
|
|
257
|
-
}
|
|
273
|
+
// E2: shared resilient client โ retries a rate-limit (403 primary / 429 secondary)
|
|
274
|
+
// + transient 5xx + network error with backoff (Retry-After / X-RateLimit-Reset
|
|
275
|
+
// aware), throws a structured, secret-free error on 401/404/422/plain-403. The token
|
|
276
|
+
// rides only in the request headers above, never in any thrown message.
|
|
277
|
+
const res = await githubFetch(`${GITHUB_API}${urlPath}`, {
|
|
278
|
+
method,
|
|
279
|
+
headers,
|
|
280
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
281
|
+
context: `${method} ${urlPath}`,
|
|
282
|
+
});
|
|
258
283
|
return res.json();
|
|
259
284
|
}
|
|
260
285
|
|
|
@@ -264,9 +289,9 @@ async function ghFetch(urlPath, method, body, attempt = 1) {
|
|
|
264
289
|
* Create a PR comment, or update the existing Argus comment if one is already present.
|
|
265
290
|
* Idempotent: re-running on the same PR updates in-place rather than spamming new comments.
|
|
266
291
|
*/
|
|
267
|
-
export async function postPrComment(report, diff) {
|
|
268
|
-
const repo = process.env.GITHUB_REPOSITORY;
|
|
269
|
-
const prNum = process.env.GITHUB_PR_NUMBER;
|
|
292
|
+
export async function postPrComment(report, diff, opts = {}) {
|
|
293
|
+
const repo = opts.repo ?? process.env.GITHUB_REPOSITORY;
|
|
294
|
+
const prNum = opts.prNumber ?? process.env.GITHUB_PR_NUMBER;
|
|
270
295
|
if (!repo || !prNum) throw new Error('[ARGUS] C2: GITHUB_REPOSITORY or GITHUB_PR_NUMBER not set');
|
|
271
296
|
|
|
272
297
|
let body = formatPrComment(report, diff);
|
|
@@ -322,10 +347,14 @@ export async function setCommitStatus(report, diff) {
|
|
|
322
347
|
*
|
|
323
348
|
* @param {string} [name] - Check run name (default: GITHUB_CHECK_NAME ?? 'argus-qa')
|
|
324
349
|
* @param {string} [sha] - Commit SHA (default: GITHUB_SHA env var)
|
|
350
|
+
* @param {object} [opts]
|
|
351
|
+
* @param {string} [opts.repo] - "owner/repo" override (default: GITHUB_REPOSITORY env var).
|
|
352
|
+
* Lets callers that resolved the repo from a PR URL (the PR
|
|
353
|
+
* Validator) drive the Check Run without relying on env.
|
|
325
354
|
* @returns {Promise<number>} check run id
|
|
326
355
|
*/
|
|
327
|
-
export async function createCheckRun(name, sha) {
|
|
328
|
-
const repo = process.env.GITHUB_REPOSITORY;
|
|
356
|
+
export async function createCheckRun(name, sha, opts = {}) {
|
|
357
|
+
const repo = opts.repo ?? process.env.GITHUB_REPOSITORY;
|
|
329
358
|
const headSha = sha ?? process.env.GITHUB_SHA;
|
|
330
359
|
if (!repo || !headSha) throw new Error('[ARGUS] C2: GITHUB_REPOSITORY or GITHUB_SHA not set');
|
|
331
360
|
|
|
@@ -352,13 +381,30 @@ export async function createCheckRun(name, sha) {
|
|
|
352
381
|
* @param {number} checkRunId - id from createCheckRun()
|
|
353
382
|
* @param {object} report - runCrawl() report
|
|
354
383
|
* @param {object|null} diff - baseline diff (null = first run)
|
|
384
|
+
* @param {object} [opts]
|
|
385
|
+
* @param {string} [opts.repo] - "owner/repo" override (default: GITHUB_REPOSITORY env var)
|
|
355
386
|
*/
|
|
356
|
-
export async function completeCheckRun(checkRunId, report, diff) {
|
|
357
|
-
const repo = process.env.GITHUB_REPOSITORY;
|
|
387
|
+
export async function completeCheckRun(checkRunId, report, diff, opts = {}) {
|
|
388
|
+
const repo = opts.repo ?? process.env.GITHUB_REPOSITORY;
|
|
358
389
|
if (!repo) throw new Error('[ARGUS] C2: GITHUB_REPOSITORY not set');
|
|
359
390
|
|
|
360
|
-
|
|
361
|
-
|
|
391
|
+
// The conclusion must reflect the merge gate. For a PR-Validator report the authoritative
|
|
392
|
+
// gate is the block-on decision (report.prValidation.blocked), NOT buildStatusPayload's
|
|
393
|
+
// new-criticals-vs-ARGUS_CRITICAL_THRESHOLD rule โ the two diverge (e.g. block-on=warning
|
|
394
|
+
// with 0 criticals blocks the merge but has 0 new criticals). runCrawl reports carry no
|
|
395
|
+
// prValidation field, so they keep the existing threshold-based conclusion.
|
|
396
|
+
let conclusion, title;
|
|
397
|
+
const pv = report.prValidation;
|
|
398
|
+
if (pv) {
|
|
399
|
+
conclusion = pv.blocked ? 'failure' : 'success';
|
|
400
|
+
title = pv.blocked
|
|
401
|
+
? `Argus: merge blocked โ ${pv.reason}`
|
|
402
|
+
: `Argus: merge allowed โ no findings at or above block-on=${pv.blockOn}`;
|
|
403
|
+
} else {
|
|
404
|
+
const status = buildStatusPayload(report, diff);
|
|
405
|
+
conclusion = status.state === 'success' ? 'success' : 'failure';
|
|
406
|
+
title = status.description;
|
|
407
|
+
}
|
|
362
408
|
|
|
363
409
|
// Build rich text output (full findings table, without the COMMENT_MARKER sentinel)
|
|
364
410
|
const fullBody = formatPrComment(report, diff);
|
|
@@ -371,8 +417,8 @@ export async function completeCheckRun(checkRunId, report, diff) {
|
|
|
371
417
|
conclusion,
|
|
372
418
|
completed_at: new Date().toISOString(),
|
|
373
419
|
output: {
|
|
374
|
-
title:
|
|
375
|
-
summary:
|
|
420
|
+
title: title.slice(0, 255), // GitHub Check output.title limit
|
|
421
|
+
summary: title,
|
|
376
422
|
text: richText,
|
|
377
423
|
},
|
|
378
424
|
});
|
|
@@ -511,3 +557,169 @@ export async function reportToGitHub(report, diff) {
|
|
|
511
557
|
|
|
512
558
|
await Promise.all(tasks);
|
|
513
559
|
}
|
|
560
|
+
|
|
561
|
+
// โโ PR Validator reporting (Phase A) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Adapt a PR-Validator result (the src/cli/pr-validate.js + argus_pr_validate response
|
|
565
|
+
* shape) into the `report` object that formatPrComment / buildStatusPayload consume.
|
|
566
|
+
* Pure โ no I/O.
|
|
567
|
+
*
|
|
568
|
+
* The PR Validator has no per-run baseline yet (Phase B introduces head-vs-base
|
|
569
|
+
* diffing), so every finding on an affected route is surfaced as-is; callers pair this
|
|
570
|
+
* with a NON-first `diff` (see reportPrValidation) so formatPrComment renders the
|
|
571
|
+
* findings table rather than treating the run as a baseline-establishing first run.
|
|
572
|
+
*
|
|
573
|
+
* @param {object} result
|
|
574
|
+
* @param {string} [result.targetUrl]
|
|
575
|
+
* @param {{ critical: number, warning: number, info: number }} [result.summary]
|
|
576
|
+
* @param {Array<{ severity: string, type: string, message: string, url: string }>} [result.findings]
|
|
577
|
+
* @param {string[]} [result.affectedRoutes]
|
|
578
|
+
* @param {string[]} [result.changedFiles]
|
|
579
|
+
* @param {boolean} [result.blocked]
|
|
580
|
+
* @param {string} [result.blockOn]
|
|
581
|
+
* @returns {object} report consumable by formatPrComment
|
|
582
|
+
*/
|
|
583
|
+
export function prResultToReport(result = {}) {
|
|
584
|
+
const {
|
|
585
|
+
targetUrl,
|
|
586
|
+
summary = { critical: 0, warning: 0, info: 0 },
|
|
587
|
+
findings = [],
|
|
588
|
+
affectedRoutes = [],
|
|
589
|
+
changedFiles = [],
|
|
590
|
+
blocked = false,
|
|
591
|
+
blockOn = 'critical',
|
|
592
|
+
baseline, // B2: { available, newCritical, newWarning, newInfo, persisting, resolved } | { available:false, note }
|
|
593
|
+
} = result;
|
|
594
|
+
|
|
595
|
+
const base = String(targetUrl ?? '').replace(/\/$/, '');
|
|
596
|
+
|
|
597
|
+
// Group findings by their route path (derived from each finding's url), so the
|
|
598
|
+
// comment's findings table is sourced per-route โ formatPrComment uses
|
|
599
|
+
// report.routes[].route as the display source label for each finding.
|
|
600
|
+
const byRoute = new Map();
|
|
601
|
+
for (const f of findings) {
|
|
602
|
+
const url = String(f.url ?? '');
|
|
603
|
+
let label = url || '(unknown route)';
|
|
604
|
+
if (base && url.startsWith(base)) label = url.slice(base.length) || '/';
|
|
605
|
+
if (!byRoute.has(label)) byRoute.set(label, []);
|
|
606
|
+
byRoute.get(label).push(f);
|
|
607
|
+
}
|
|
608
|
+
const routes = [...byRoute.entries()].map(([route, errors]) => ({
|
|
609
|
+
route, errors, screenshot: null,
|
|
610
|
+
}));
|
|
611
|
+
|
|
612
|
+
const crit = summary.critical ?? 0;
|
|
613
|
+
const warn = summary.warning ?? 0;
|
|
614
|
+
const info = summary.info ?? 0;
|
|
615
|
+
|
|
616
|
+
// The block reason must reflect what the decision actually counted, so the banner reconciles
|
|
617
|
+
// with `blocked` (Phase B2): the NEW (PR-introduced) counts when a baseline was available, the
|
|
618
|
+
// absolute counts otherwise. The scope word ("new"/"total") matches decidePrBlock's phrasing;
|
|
619
|
+
// it is omitted entirely for legacy callers that pass no baseline field (back-compat).
|
|
620
|
+
const blPresent = !!(baseline && typeof baseline === 'object');
|
|
621
|
+
const blAvail = !!(blPresent && baseline.available);
|
|
622
|
+
const rCrit = blAvail ? (baseline.newCritical ?? 0) : crit;
|
|
623
|
+
const rWarn = blAvail ? (baseline.newWarning ?? 0) : warn;
|
|
624
|
+
const scope = !blPresent ? '' : blAvail ? 'new ' : 'total ';
|
|
625
|
+
const reason = !blocked ? null
|
|
626
|
+
: blockOn === 'warning'
|
|
627
|
+
? `${rCrit} critical + ${rWarn} warning ${scope}finding(s) at or above the block threshold`
|
|
628
|
+
: `${rCrit} critical ${scope}finding(s) found`;
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
baseUrl: base || String(targetUrl ?? ''),
|
|
632
|
+
generatedAt: new Date().toISOString(),
|
|
633
|
+
summary: { critical: crit, warning: warn, info, total: crit + warn + info },
|
|
634
|
+
routes,
|
|
635
|
+
codebase: [],
|
|
636
|
+
flows: [],
|
|
637
|
+
prValidation: {
|
|
638
|
+
blocked,
|
|
639
|
+
blockOn,
|
|
640
|
+
reason,
|
|
641
|
+
affectedRoutes: affectedRoutes
|
|
642
|
+
.map(r => (typeof r === 'string' ? r : r?.path))
|
|
643
|
+
.filter(Boolean),
|
|
644
|
+
changedFileCount: Array.isArray(changedFiles) ? changedFiles.length : 0,
|
|
645
|
+
baseline: baseline ?? null,
|
|
646
|
+
},
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Post (or idempotently update) the single Argus PR comment for a PR-Validator run.
|
|
652
|
+
*
|
|
653
|
+
* Gated on GITHUB_TOKEN plus a resolvable owner/repo + PR number โ taken from the
|
|
654
|
+
* GITHUB_REPOSITORY / GITHUB_PR_NUMBER env vars (set by the GitHub runner) or, failing
|
|
655
|
+
* that, parsed from the PR URL. A missing token or unresolvable PR context SKIPS
|
|
656
|
+
* reporting (returns a status) rather than throwing: a reporting misconfiguration must
|
|
657
|
+
* never crash or block the validation step. The GitHub token is never echoed into the
|
|
658
|
+
* return value, logs, or thrown errors (it rides only in the Authorization header).
|
|
659
|
+
*
|
|
660
|
+
* Idempotent: postPrComment finds the existing Argus comment by its HTML marker and
|
|
661
|
+
* PATCHes it in place, so re-running on the same PR updates rather than duplicates.
|
|
662
|
+
*
|
|
663
|
+
* In addition to the comment (A1), a GitHub Check Run is created + completed (A2) when a
|
|
664
|
+
* PR head SHA is resolvable (ARGUS_PR_HEAD_SHA โ set by action.yml to
|
|
665
|
+
* github.event.pull_request.head.sha โ or GITHUB_SHA). Its conclusion maps to the block
|
|
666
|
+
* decision (failure iff blocked). The Check Run is best-effort and isolated: a failure
|
|
667
|
+
* there never discards an already-posted comment and never changes the merge decision.
|
|
668
|
+
*
|
|
669
|
+
* @param {object} result - PR-validate result (see prResultToReport)
|
|
670
|
+
* @param {object} [opts]
|
|
671
|
+
* @param {string} [opts.prUrl] - PR URL used to derive owner/repo/prNumber when env vars are absent
|
|
672
|
+
* @returns {Promise<{ posted: boolean, checked: boolean, skipped: boolean, reason?: string }>}
|
|
673
|
+
*/
|
|
674
|
+
export async function reportPrValidation(result, { prUrl } = {}) {
|
|
675
|
+
if (!process.env.GITHUB_TOKEN) {
|
|
676
|
+
return { posted: false, checked: false, skipped: true, reason: 'GITHUB_TOKEN not set โ PR reporting skipped' };
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
let repo = process.env.GITHUB_REPOSITORY;
|
|
680
|
+
let prNumber = process.env.GITHUB_PR_NUMBER;
|
|
681
|
+
const url = prUrl ?? result?.prUrl;
|
|
682
|
+
if ((!repo || !prNumber) && url) {
|
|
683
|
+
try {
|
|
684
|
+
const { owner, repo: r, prNumber: n } = parsePrUrl(url);
|
|
685
|
+
repo = repo || `${owner}/${r}`;
|
|
686
|
+
prNumber = prNumber || n;
|
|
687
|
+
} catch { /* unparseable URL โ fall through to the skip below */ }
|
|
688
|
+
}
|
|
689
|
+
if (!repo || !prNumber) {
|
|
690
|
+
return { posted: false, checked: false, skipped: true, reason: 'no resolvable repo / PR number โ PR reporting skipped' };
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const report = prResultToReport(result);
|
|
694
|
+
// Non-first diff so findings render (not treated as a baseline-establishing first run). The
|
|
695
|
+
// new/persisting split rides on each finding's `isNew` tag (set in the PR-validate paths via
|
|
696
|
+
// tagFindingNovelty); `resolvedCount` comes from the head-vs-base diff (Phase B2) so the
|
|
697
|
+
// comment's Resolved row reconciles with the block decision. 0 when no baseline was available.
|
|
698
|
+
const diff = {
|
|
699
|
+
isFirstRun: false,
|
|
700
|
+
resolvedCount: (result && result.baseline && result.baseline.available) ? (result.baseline.resolved ?? 0) : 0,
|
|
701
|
+
flowResolvedCount: 0,
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// A1 โ idempotent PR comment (the primary visible surface). A failure here propagates to
|
|
705
|
+
// the caller, which logs a ::warning:: and leaves the already-computed merge decision intact.
|
|
706
|
+
await postPrComment(report, diff, { repo, prNumber });
|
|
707
|
+
|
|
708
|
+
// A2 โ Check Run whose conclusion maps to the block decision. Gated on a resolvable PR
|
|
709
|
+
// head SHA; isolated so a Check Run failure can't discard the posted comment.
|
|
710
|
+
let checked = false;
|
|
711
|
+
let checkError;
|
|
712
|
+
const headSha = process.env.ARGUS_PR_HEAD_SHA || process.env.GITHUB_SHA;
|
|
713
|
+
if (headSha) {
|
|
714
|
+
try {
|
|
715
|
+
const checkId = await createCheckRun(undefined, headSha, { repo });
|
|
716
|
+
await completeCheckRun(checkId, report, diff, { repo });
|
|
717
|
+
checked = true;
|
|
718
|
+
} catch (err) {
|
|
719
|
+
checkError = err.message;
|
|
720
|
+
logger.warn(`[ARGUS] C2: PR Check Run failed โ ${err.message}`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return { posted: true, checked, skipped: false, ...(checkError ? { reason: `check run failed: ${checkError}` } : {}) };
|
|
725
|
+
}
|