@jphutchins/code-review 0.1.0-alpha.67 → 0.1.0-alpha.69
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 +2 -2
- package/dist/index.js +154 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/comment.eta +2 -2
package/README.md
CHANGED
|
@@ -38,10 +38,10 @@ npx @jphutchins/code-review <subcommand>
|
|
|
38
38
|
| Subcommand | What it does |
|
|
39
39
|
| --- | --- |
|
|
40
40
|
| `post` | Post a complete review from findings + envelope + diff — the one-call path. The findings are listed in the sticky summary; `--inline` renders the in-diff ones as comments on the diff instead. Also renders the review into `$GITHUB_STEP_SUMMARY`: the sticky is overwritten each round, the run summary keeps each round as it stood |
|
|
41
|
-
| `gather` | Resolve the PR from the CI head SHA and gather the review inputs (diff with git-diff fallback, PR context, prior bot review, the implementer's answers (`Review-Response:` lines and verdict tables),
|
|
41
|
+
| `gather` | Resolve the PR from the CI head SHA and gather the review inputs (diff with git-diff fallback, PR context, prior bot review, the implementer's answers (`Review-Response:` lines and verdict tables), the CI run's job logs — the failing jobs', up to 20, on a failure, the jobs that ran, up to 64, on a pass) into the directory named by `--out-dir` (default: the current directory; the shipped workflows use `$RUNNER_TEMP/gather`) |
|
|
42
42
|
| `parse-command` | Resolve a PR's head from its number and parse a ChatOps trigger comment (`/code-review [24m] [$1.00] <instructions>`) into review overrides — the on-demand comment trigger |
|
|
43
43
|
| `react` | Add/remove a GitHub comment reaction — the ChatOps acknowledgement (👀 on receipt, 🚀 on completion) |
|
|
44
|
-
| `await-ci` | Wait for a PR head's CI run to conclude and emit its real conclusion
|
|
44
|
+
| `await-ci` | Wait for a PR head's CI run to conclude and emit its real conclusion, run id and run attempt — so an on-demand comment review routes on the CI result (success → full, failure → mechanic) instead of reviewing blind |
|
|
45
45
|
| `check-scope` | Validate + normalize the workflow's `scope` input (the languages/inputs the project accepts) and print the normalized list for the review prompt, or nothing when empty (the reviewer then infers scope from the README's first paragraph); a structurally malformed value is rejected rather than spliced in as-is |
|
|
46
46
|
| `render` | Render the sticky-comment markdown from findings + usage + prices |
|
|
47
47
|
| `inline` | Build the GitHub reviews `comments[]` payload from findings + diff (in-diff validation; strays demote to the summary) |
|
package/dist/index.js
CHANGED
|
@@ -574,7 +574,7 @@ var incompleteFindings = (summary) => ({
|
|
|
574
574
|
var isIncompleteFindings = (findings) => findings.verdict === "error" && findings.findings.length === 0;
|
|
575
575
|
|
|
576
576
|
// src/released.ts
|
|
577
|
-
var RELEASED = "2026-10-
|
|
577
|
+
var RELEASED = "2026-10-11";
|
|
578
578
|
|
|
579
579
|
// src/cost.ts
|
|
580
580
|
var defaultWarn = (message) => {
|
|
@@ -2373,6 +2373,7 @@ var render = (input) => {
|
|
|
2373
2373
|
answeredReRaiseNote: input.answeredReRaiseNote ?? "",
|
|
2374
2374
|
contested: contestedBudget.kept,
|
|
2375
2375
|
unfinished: shownUnfinished(input.findings, isFullReviewRound),
|
|
2376
|
+
unfinishedCaveat: unfinished.length > 0 ? ` \u2014 ${String(unfinished.length)} planned check${unfinished.length === 1 ? "" : "s"} went unfinished` : "",
|
|
2376
2377
|
reviewUrl: input.reviewUrl ?? null,
|
|
2377
2378
|
formatTokens: (n) => Number.isFinite(n) && n >= 0 ? n.toLocaleString("en-US") : "\u2014",
|
|
2378
2379
|
// N/A (never a false $0.00) when no real price map was provided — real tokens, no rates to
|
|
@@ -2788,7 +2789,7 @@ var timeClause = (i) => i.elapsedMs === null ? null : i.wallMs !== null && i.wal
|
|
|
2788
2789
|
var directive = (phase, draftPath, isSubagent) => {
|
|
2789
2790
|
if (isSubagent)
|
|
2790
2791
|
return phase.kind === "hard" ? `Budget nearly exhausted \u2014 STOP all new investigation now and report the findings you have back to the main agent in your reply, naming each check you planned but did not finish. Do not write ${draftPath} yourself; only the main agent writes it.` : `Wind down investigation and report the findings you have back to the main agent in your reply, naming each check you planned but did not finish \u2014 do not write ${draftPath} yourself; the main agent writes it, and you may run out of budget otherwise.`;
|
|
2791
|
-
return phase.kind === "hard" ? `Budget nearly exhausted \u2014 STOP all new investigation now. Do not wait for subagents still running in the background. Write your COMPLETE findings from what you already have to ${draftPath}, listing in its \`unfinished\` field, when its schema has one, every check you or a subagent planned but did not finish, and run \`code-review validate ${draftPath} --explain\` until it passes (--explain prints the exact schema when the shape is wrong). Other tools are blocked until that draft is written.` : `Wind down investigation and write your COMPLETE findings to ${draftPath} now, listing in its \`unfinished\` field, when its schema has one, every check you or a subagent planned but did not finish, then run \`code-review validate ${draftPath} --explain\` (it prints the exact schema if the shape is wrong) \u2014 fold in the subagent reports you already have rather than waiting on stragglers; you may run out of budget before you finish otherwise.`;
|
|
2792
|
+
return phase.kind === "hard" ? `Budget nearly exhausted \u2014 STOP all new investigation now. Do not wait for subagents still running in the background. Write your COMPLETE findings from what you already have to ${draftPath}, listing in its \`unfinished\` field, when its schema has one, every check you or a subagent planned but did not finish (never one a CI job already ran and passed), and run \`code-review validate ${draftPath} --explain\` until it passes (--explain prints the exact schema when the shape is wrong). Other tools are blocked until that draft is written.` : `Wind down investigation and write your COMPLETE findings to ${draftPath} now, listing in its \`unfinished\` field, when its schema has one, every check you or a subagent planned but did not finish (never one a CI job already ran and passed), then run \`code-review validate ${draftPath} --explain\` (it prints the exact schema if the shape is wrong) \u2014 fold in the subagent reports you already have rather than waiting on stragglers; you may run out of budget before you finish otherwise.`;
|
|
2792
2793
|
};
|
|
2793
2794
|
var budgetMessage = (i, phase, draftPath, isSubagent) => {
|
|
2794
2795
|
const status = [spendClause(i), timeClause(i)].filter((c) => c !== null).join(" \xB7 ");
|
|
@@ -5058,14 +5059,17 @@ var react = async (input, ghApi = runGhApi) => {
|
|
|
5058
5059
|
await removeReactions(input.repo, input.commentId, input.remove, ghApi);
|
|
5059
5060
|
}
|
|
5060
5061
|
};
|
|
5061
|
-
var RunCodec = t.
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5062
|
+
var RunCodec = t.intersection([
|
|
5063
|
+
t.type({
|
|
5064
|
+
id: t.number,
|
|
5065
|
+
name: t.union([t.string, t.null]),
|
|
5066
|
+
status: t.union([t.string, t.null]),
|
|
5067
|
+
conclusion: t.union([t.string, t.null]),
|
|
5068
|
+
run_number: t.number
|
|
5069
|
+
}),
|
|
5070
|
+
t.partial({ run_attempt: t.union([t.number, t.null]) })
|
|
5071
|
+
]);
|
|
5072
|
+
var RUN_JQ = ".workflow_runs[] | {id: .id, name: .name, status: .status, conclusion: .conclusion, run_number: .run_number, run_attempt: .run_attempt}";
|
|
5069
5073
|
var resolveCiRun = async (repo, headSha, workflowName, ghApi) => {
|
|
5070
5074
|
const endpoint = `repos/${repo}/actions/runs?head_sha=${headSha}&per_page=100`;
|
|
5071
5075
|
const rows = parseJsonl(await ghApi([endpoint, "--paginate", "--jq", RUN_JQ]));
|
|
@@ -5085,7 +5089,12 @@ var resolveCiRun = async (repo, headSha, workflowName, ghApi) => {
|
|
|
5085
5089
|
null
|
|
5086
5090
|
);
|
|
5087
5091
|
return {
|
|
5088
|
-
run: latest === null ? null : {
|
|
5092
|
+
run: latest === null ? null : {
|
|
5093
|
+
id: latest.id,
|
|
5094
|
+
status: latest.status ?? "unknown",
|
|
5095
|
+
conclusion: latest.conclusion,
|
|
5096
|
+
attempt: latest.run_attempt ?? null
|
|
5097
|
+
},
|
|
5089
5098
|
seenNames: [...new Set(runs.flatMap((r) => r.name === null ? [] : [r.name]))]
|
|
5090
5099
|
};
|
|
5091
5100
|
};
|
|
@@ -5104,7 +5113,12 @@ var awaitCiConclusion = async (repo, headSha, options, deps = { ghApi: runGhApi,
|
|
|
5104
5113
|
const poll = async (lastSeenNames, lastRunId) => {
|
|
5105
5114
|
const { run: run2, seenNames } = await safeResolve();
|
|
5106
5115
|
if (run2 !== null && run2.status === "completed" && run2.conclusion !== null)
|
|
5107
|
-
return {
|
|
5116
|
+
return {
|
|
5117
|
+
kind: "concluded",
|
|
5118
|
+
conclusion: run2.conclusion,
|
|
5119
|
+
runId: run2.id,
|
|
5120
|
+
runAttempt: run2.attempt
|
|
5121
|
+
};
|
|
5108
5122
|
const runId = run2 === null ? lastRunId : run2.id;
|
|
5109
5123
|
const names = seenNames.length > 0 ? seenNames : lastSeenNames;
|
|
5110
5124
|
if (deps.elapsedMs() >= options.timeoutMs)
|
|
@@ -5122,6 +5136,7 @@ var monotonicElapsed = () => {
|
|
|
5122
5136
|
var renderCiOutputs = (outcome) => outcome.kind === "concluded" ? `ci_settled=true
|
|
5123
5137
|
ci_conclusion=${outcome.conclusion}
|
|
5124
5138
|
ci_run_id=${String(outcome.runId)}
|
|
5139
|
+
ci_run_attempt=${outcome.runAttempt === null ? "" : String(outcome.runAttempt)}
|
|
5125
5140
|
` : `ci_settled=false
|
|
5126
5141
|
ci_run_id=${outcome.runId === null ? "" : String(outcome.runId)}
|
|
5127
5142
|
`;
|
|
@@ -5137,6 +5152,8 @@ stacked=${String(result.stacked)}
|
|
|
5137
5152
|
base_sha=${result.baseSha}
|
|
5138
5153
|
staged_job_logs=${String(result.stagedJobLogs)}
|
|
5139
5154
|
failing_jobs=${String(result.failingJobs)}
|
|
5155
|
+
ci_jobs_passed=${result.passedJobs === null ? "" : String(result.passedJobs.passed)}
|
|
5156
|
+
ci_jobs_ran=${result.passedJobs === null ? "" : String(result.passedJobs.ran)}
|
|
5140
5157
|
`;
|
|
5141
5158
|
}
|
|
5142
5159
|
};
|
|
@@ -5174,14 +5191,25 @@ var IssueCommentCodec = t.intersection([
|
|
|
5174
5191
|
author_association: t.union([t.string, t.null])
|
|
5175
5192
|
})
|
|
5176
5193
|
]);
|
|
5177
|
-
var JobCodec = t.
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5194
|
+
var JobCodec = t.intersection([
|
|
5195
|
+
t.type({
|
|
5196
|
+
id: t.number,
|
|
5197
|
+
// A null name (an API edge) degrades to a nameless entry rather than aborting the whole gather —
|
|
5198
|
+
// the identity the reproduction steer needs is best-effort, never load-bearing.
|
|
5199
|
+
name: t.union([t.string, t.null]),
|
|
5200
|
+
conclusion: t.union([t.string, t.null])
|
|
5201
|
+
}),
|
|
5202
|
+
t.partial({ steps: t.array(t.string) })
|
|
5203
|
+
]);
|
|
5204
|
+
var JOBS_JQ = ".jobs[] | {id: .id, name: .name, conclusion: .conclusion, steps: [.steps[]? | .name? | strings]}";
|
|
5205
|
+
var parseRunAttempt = (raw) => {
|
|
5206
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
5207
|
+
if (!/^[1-9][0-9]*$/.test(raw)) {
|
|
5208
|
+
throw new Error(`--run-attempt must be a positive integer, got ${JSON.stringify(raw)}`);
|
|
5209
|
+
}
|
|
5210
|
+
return raw;
|
|
5211
|
+
};
|
|
5212
|
+
var jobsEndpoint = (repo, runId, runAttempt) => runAttempt === void 0 ? `repos/${repo}/actions/runs/${runId}/jobs` : `repos/${repo}/actions/runs/${runId}/attempts/${runAttempt}/jobs`;
|
|
5185
5213
|
var fetchPrMeta = async (repo, prNumber, ghApi) => {
|
|
5186
5214
|
const stdout = await ghApi([
|
|
5187
5215
|
`repos/${repo}/pulls/${String(prNumber)}`,
|
|
@@ -5331,35 +5359,46 @@ var reviewsFrom = (reviews, botLogin) => boundedHuman(reviews, botLogin, "review
|
|
|
5331
5359
|
body: clip(r.body)
|
|
5332
5360
|
}));
|
|
5333
5361
|
var MAX_STAGED_JOB_LOGS = 20;
|
|
5334
|
-
var
|
|
5335
|
-
|
|
5336
|
-
await ghApi([`repos/${repo}/actions/
|
|
5362
|
+
var stageJobLog = async (repo, jobId, outDir, ghApi) => {
|
|
5363
|
+
try {
|
|
5364
|
+
const log = await ghApi([`repos/${repo}/actions/jobs/${String(jobId)}/logs`]);
|
|
5365
|
+
if (log.trim() === "no log available" || log.trim() === "") {
|
|
5366
|
+
process.stderr.write(
|
|
5367
|
+
`Warning: job ${String(jobId)} has no log available (placeholder body) \u2014 not staged
|
|
5368
|
+
`
|
|
5369
|
+
);
|
|
5370
|
+
return null;
|
|
5371
|
+
}
|
|
5372
|
+
const file = `job_${String(jobId)}.log`;
|
|
5373
|
+
writeFileSync(join(outDir, file), log);
|
|
5374
|
+
return file;
|
|
5375
|
+
} catch (err) {
|
|
5376
|
+
process.stderr.write(
|
|
5377
|
+
`Warning: failed to download logs for job ${String(jobId)}: ${errMsg(err)} \u2014 continuing with the logs retrieved so far
|
|
5378
|
+
`
|
|
5379
|
+
);
|
|
5380
|
+
return null;
|
|
5381
|
+
}
|
|
5382
|
+
};
|
|
5383
|
+
var listRunJobs = async (repo, runId, runAttempt, ghApi) => {
|
|
5384
|
+
const decoded = t.array(JobCodec).decode(
|
|
5385
|
+
parseJsonl(
|
|
5386
|
+
await ghApi([jobsEndpoint(repo, runId, runAttempt), "--paginate", "--jq", JOBS_JQ])
|
|
5387
|
+
)
|
|
5337
5388
|
);
|
|
5338
|
-
const decoded = t.array(JobCodec).decode(rows);
|
|
5339
5389
|
if (decoded._tag === "Left") {
|
|
5340
5390
|
throw new Error(`Jobs list for run ${runId} did not match the expected shape`);
|
|
5341
5391
|
}
|
|
5342
|
-
|
|
5392
|
+
return decoded.right;
|
|
5393
|
+
};
|
|
5394
|
+
var downloadFailingJobLogs = async (repo, runId, runAttempt, outDir, ghApi) => {
|
|
5395
|
+
const failing = (await listRunJobs(repo, runId, runAttempt, ghApi)).filter(
|
|
5396
|
+
(j) => j.conclusion === "failure"
|
|
5397
|
+
);
|
|
5343
5398
|
const selected = failing.slice(0, MAX_STAGED_JOB_LOGS);
|
|
5344
5399
|
let staged = 0;
|
|
5345
5400
|
for (const job of selected) {
|
|
5346
|
-
|
|
5347
|
-
const log = await ghApi([`repos/${repo}/actions/jobs/${String(job.id)}/logs`]);
|
|
5348
|
-
if (log.trim() === "no log available" || log.trim() === "") {
|
|
5349
|
-
process.stderr.write(
|
|
5350
|
-
`Warning: job ${String(job.id)} has no log available (placeholder body) \u2014 not staged
|
|
5351
|
-
`
|
|
5352
|
-
);
|
|
5353
|
-
continue;
|
|
5354
|
-
}
|
|
5355
|
-
writeFileSync(join(outDir, `job_${String(job.id)}.log`), log);
|
|
5356
|
-
staged += 1;
|
|
5357
|
-
} catch (err) {
|
|
5358
|
-
process.stderr.write(
|
|
5359
|
-
`Warning: failed to download logs for job ${String(job.id)}: ${errMsg(err)} \u2014 continuing with the logs retrieved so far
|
|
5360
|
-
`
|
|
5361
|
-
);
|
|
5362
|
-
}
|
|
5401
|
+
if (await stageJobLog(repo, job.id, outDir, ghApi) !== null) staged += 1;
|
|
5363
5402
|
}
|
|
5364
5403
|
writeFileSync(
|
|
5365
5404
|
join(outDir, "failed_jobs.json"),
|
|
@@ -5387,6 +5426,65 @@ var downloadFailingJobLogs = async (repo, runId, outDir, ghApi) => {
|
|
|
5387
5426
|
}
|
|
5388
5427
|
return { staged, failing: failing.length };
|
|
5389
5428
|
};
|
|
5429
|
+
var MAX_STAGED_PASSED_JOB_LOGS = 64;
|
|
5430
|
+
var PASSED_LOG_CONCURRENCY = 8;
|
|
5431
|
+
var stagePassedRun = async (repo, runId, runAttempt, outDir, ghApi) => {
|
|
5432
|
+
try {
|
|
5433
|
+
const jobs = await listRunJobs(repo, runId, runAttempt, ghApi);
|
|
5434
|
+
const writeIndex = (logs2) => {
|
|
5435
|
+
writeFileSync(
|
|
5436
|
+
join(outDir, "ci_jobs.json"),
|
|
5437
|
+
`${JSON.stringify(
|
|
5438
|
+
jobs.map((j) => ({
|
|
5439
|
+
name: j.name,
|
|
5440
|
+
conclusion: j.conclusion,
|
|
5441
|
+
steps: j.steps ?? [],
|
|
5442
|
+
log: logs2.get(j.id) ?? null
|
|
5443
|
+
})),
|
|
5444
|
+
null,
|
|
5445
|
+
2
|
|
5446
|
+
)}
|
|
5447
|
+
`
|
|
5448
|
+
);
|
|
5449
|
+
};
|
|
5450
|
+
writeIndex(/* @__PURE__ */ new Map());
|
|
5451
|
+
const ran = jobs.filter((j) => j.conclusion !== "skipped");
|
|
5452
|
+
const selected = ran.slice(0, MAX_STAGED_PASSED_JOB_LOGS);
|
|
5453
|
+
const logs = await Array.from(
|
|
5454
|
+
{ length: Math.ceil(selected.length / PASSED_LOG_CONCURRENCY) },
|
|
5455
|
+
(_, i) => selected.slice(i * PASSED_LOG_CONCURRENCY, (i + 1) * PASSED_LOG_CONCURRENCY)
|
|
5456
|
+
).reduce(async (staged, batch) => {
|
|
5457
|
+
const prior = await staged;
|
|
5458
|
+
const files = await Promise.all(
|
|
5459
|
+
batch.map(async (job) => [job.id, await stageJobLog(repo, job.id, outDir, ghApi)])
|
|
5460
|
+
);
|
|
5461
|
+
return new Map([
|
|
5462
|
+
...prior,
|
|
5463
|
+
...files.flatMap(([id, file]) => file === null ? [] : [[id, file]])
|
|
5464
|
+
]);
|
|
5465
|
+
}, Promise.resolve(/* @__PURE__ */ new Map()));
|
|
5466
|
+
writeIndex(logs);
|
|
5467
|
+
if (ran.length > selected.length) {
|
|
5468
|
+
process.stderr.write(
|
|
5469
|
+
`::warning::${annotationSafe(`${String(ran.length)} jobs ran in the passed run ${runId}; logs were staged for the first ${String(selected.length)} only`)}
|
|
5470
|
+
`
|
|
5471
|
+
);
|
|
5472
|
+
}
|
|
5473
|
+
return {
|
|
5474
|
+
staged: logs.size,
|
|
5475
|
+
passed: {
|
|
5476
|
+
passed: jobs.filter((j) => j.conclusion === "success").length,
|
|
5477
|
+
ran: ran.length
|
|
5478
|
+
}
|
|
5479
|
+
};
|
|
5480
|
+
} catch (err) {
|
|
5481
|
+
process.stderr.write(
|
|
5482
|
+
`::warning::${annotationSafe(`the passed CI run's jobs could not be staged (${errMsg(err)}) \u2014 the review knows CI passed, not what its jobs reported`)}
|
|
5483
|
+
`
|
|
5484
|
+
);
|
|
5485
|
+
return { staged: 0, passed: null };
|
|
5486
|
+
}
|
|
5487
|
+
};
|
|
5390
5488
|
var gather = async (input, ghApi = runGhApi, gitRun = runGit, readArtifact = ghArtifactReader) => {
|
|
5391
5489
|
const candidates = await fetchPrCandidates(input.repo, input.headSha, ghApi);
|
|
5392
5490
|
const resolution = resolvePr(candidates, input.headBranch);
|
|
@@ -5490,7 +5588,8 @@ var gather = async (input, ghApi = runGhApi, gitRun = runGit, readArtifact = ghA
|
|
|
5490
5588
|
reviews: reviews === null ? [] : reviewsFrom(reviews, input.botLogin)
|
|
5491
5589
|
})
|
|
5492
5590
|
);
|
|
5493
|
-
const jobLogs = input.conclusion === "failure" ? await downloadFailingJobLogs(input.repo, input.runId, input.outDir, ghApi) : { staged: 0, failing: 0 };
|
|
5591
|
+
const jobLogs = input.conclusion === "failure" ? await downloadFailingJobLogs(input.repo, input.runId, input.runAttempt, input.outDir, ghApi) : { staged: 0, failing: 0 };
|
|
5592
|
+
const passedRun = input.conclusion === "success" ? await stagePassedRun(input.repo, input.runId, input.runAttempt, input.outDir, ghApi) : { staged: 0, passed: null };
|
|
5494
5593
|
return {
|
|
5495
5594
|
kind: "gathered",
|
|
5496
5595
|
pr: prNumber,
|
|
@@ -5498,8 +5597,9 @@ var gather = async (input, ghApi = runGhApi, gitRun = runGit, readArtifact = ghA
|
|
|
5498
5597
|
diffSize: Buffer.byteLength(prDiff, "utf8"),
|
|
5499
5598
|
stacked,
|
|
5500
5599
|
baseSha: meta.base_sha,
|
|
5501
|
-
stagedJobLogs: jobLogs.staged,
|
|
5502
|
-
failingJobs: jobLogs.failing
|
|
5600
|
+
stagedJobLogs: jobLogs.staged + passedRun.staged,
|
|
5601
|
+
failingJobs: jobLogs.failing,
|
|
5602
|
+
passedJobs: passedRun.passed
|
|
5503
5603
|
};
|
|
5504
5604
|
};
|
|
5505
5605
|
|
|
@@ -7081,7 +7181,7 @@ var stopGateCmd = defineCommand({
|
|
|
7081
7181
|
var gatherCmd = defineCommand({
|
|
7082
7182
|
meta: {
|
|
7083
7183
|
name: "gather",
|
|
7084
|
-
description: "Resolve the PR from the CI head SHA and gather review inputs (diff with git-diff fallback, PR context, prior bot review, untrusted PR conversation to triage,
|
|
7184
|
+
description: "Resolve the PR from the CI head SHA and gather review inputs (diff with git-diff fallback, PR context, prior bot review, untrusted PR conversation to triage, the CI run's job logs \u2014 the failing jobs' with their identities in failed_jobs.json on a failure, the jobs that ran, up to 64, indexed in ci_jobs.json on a pass) as files for the review agent"
|
|
7085
7185
|
},
|
|
7086
7186
|
args: {
|
|
7087
7187
|
repo: { type: "string", description: "Repository (owner/name)", required: true },
|
|
@@ -7101,12 +7201,16 @@ var gatherCmd = defineCommand({
|
|
|
7101
7201
|
},
|
|
7102
7202
|
"run-id": {
|
|
7103
7203
|
type: "string",
|
|
7104
|
-
description: "CI run id (from workflow_run.id); its failing jobs'
|
|
7204
|
+
description: "CI run id (from workflow_run.id); its job logs are downloaded \u2014 the failing jobs' on failure, the jobs that ran (up to 64) on success",
|
|
7105
7205
|
required: true
|
|
7106
7206
|
},
|
|
7207
|
+
"run-attempt": {
|
|
7208
|
+
type: "string",
|
|
7209
|
+
description: "The CI run's attempt that triggered the review (from workflow_run.run_attempt); its jobs' logs are the ones staged, even after a re-run starts a newer attempt. Empty or omitted: the run's latest attempt"
|
|
7210
|
+
},
|
|
7107
7211
|
conclusion: {
|
|
7108
7212
|
type: "string",
|
|
7109
|
-
description: "CI conclusion (e.g. success | failure); failure
|
|
7213
|
+
description: "CI conclusion (e.g. success | failure); failure stages the failing jobs' logs, success the logs of the jobs that ran (up to 64) and ci_jobs.json",
|
|
7110
7214
|
required: true
|
|
7111
7215
|
},
|
|
7112
7216
|
"bot-login": {
|
|
@@ -7125,6 +7229,7 @@ var gatherCmd = defineCommand({
|
|
|
7125
7229
|
headBranch: args["head-branch"],
|
|
7126
7230
|
defaultBranch: args["default-branch"],
|
|
7127
7231
|
runId: args["run-id"],
|
|
7232
|
+
runAttempt: parseRunAttempt(args["run-attempt"]),
|
|
7128
7233
|
conclusion: args.conclusion,
|
|
7129
7234
|
botLogin: args["bot-login"] || "github-actions[bot]",
|
|
7130
7235
|
outDir: args["out-dir"] ? resolve(args["out-dir"]) : process.cwd()
|
|
@@ -7522,7 +7627,7 @@ var reactCmd = defineCommand({
|
|
|
7522
7627
|
var awaitCiCmd = defineCommand({
|
|
7523
7628
|
meta: {
|
|
7524
7629
|
name: "await-ci",
|
|
7525
|
-
description: "Wait for the PR head's CI workflow run to conclude, then emit its REAL conclusion
|
|
7630
|
+
description: "Wait for the PR head's CI workflow run to conclude, then emit its REAL conclusion, run id and run attempt to $GITHUB_OUTPUT (ci_settled, ci_conclusion, ci_run_id, ci_run_attempt). The on-demand comment trigger uses this so it routes on the same CI result the CI-completion trigger would \u2014 success \u2192 full review, failure \u2192 mechanic with that run's logs \u2014 instead of reviewing blind. Polls until the run completes or the timeout elapses; ci_settled=false \u21D2 no conclusive result (caller should decline to review, not guess)."
|
|
7526
7631
|
},
|
|
7527
7632
|
args: {
|
|
7528
7633
|
repo: { type: "string", description: "Repository (owner/name)", required: true },
|