@clearlydev/nx-archweave 0.2.0 → 0.2.2
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/dist/bin/review-verdict.js +36 -20
- package/dist/generators/add-review/generator.js +251 -182
- package/dist/generators/doctor/generator.js +254 -184
- package/dist/generators/init/generator.js +251 -182
- package/package.json +1 -1
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/bin/review-verdict.ts
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
|
-
import {
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { appendFileSync, realpathSync } from "node:fs";
|
|
6
7
|
|
|
7
8
|
// ../review-policy/dist/schema/review-finding.js
|
|
8
9
|
function computeVerdict(findings) {
|
|
@@ -231,7 +232,19 @@ var realExec = (command, args, stdin) => {
|
|
|
231
232
|
const result = spawnSync(command, args, { encoding: "utf-8", input: stdin });
|
|
232
233
|
return { status: result.status ?? null, stdout: result.stdout ?? "" };
|
|
233
234
|
};
|
|
234
|
-
|
|
235
|
+
var realWriteOutput = (path, text) => appendFileSync(path, text);
|
|
236
|
+
function outputModePath(env) {
|
|
237
|
+
return env["ARCHWEAVE_VERDICT_OUTPUT"] === "github-output" && env["GITHUB_OUTPUT"] ? env["GITHUB_OUTPUT"] : void 0;
|
|
238
|
+
}
|
|
239
|
+
function renderStepOutputs(commentBody, previousId, blocked) {
|
|
240
|
+
let delimiter;
|
|
241
|
+
do
|
|
242
|
+
delimiter = `ARCHWEAVE_${randomBytes(8).toString("hex")}`;
|
|
243
|
+
while (commentBody.split("\n").includes(delimiter));
|
|
244
|
+
return [`comment-body<<${delimiter}`, commentBody, delimiter, `previous-comment-id=${previousId ?? ""}`, `blocked=${blocked}`, ""].join("\n");
|
|
245
|
+
}
|
|
246
|
+
var UNREAD_COMMENTS_ERROR = "::error title=Review verdict not computed::Could not read PR comments, so the verdict could not be computed. Re-run the failed jobs to retry. If it fails again, check that the review job has pull-requests: read.";
|
|
247
|
+
function runReviewVerdictBin(env, exec, writeOutput = realWriteOutput) {
|
|
235
248
|
const logs = [];
|
|
236
249
|
const repo = env["GITHUB_REPOSITORY"];
|
|
237
250
|
const pr = env["ARCHWEAVE_PR_NUMBER"];
|
|
@@ -245,14 +258,14 @@ function runReviewVerdictBin(env, exec) {
|
|
|
245
258
|
const roundAfterId = watermark !== void 0 && /^\d+$/.test(watermark) ? Number(watermark) : void 0;
|
|
246
259
|
const sha = env["ARCHWEAVE_HEAD_SHA"];
|
|
247
260
|
const headSha = sha !== void 0 && /^[0-9a-f]{7,40}$/i.test(sha) ? sha : void 0;
|
|
261
|
+
const outputPath = outputModePath(env);
|
|
248
262
|
const fetched = exec("gh", ["api", `repos/${repo}/issues/${pr}/comments`, "--paginate"]);
|
|
249
|
-
|
|
250
|
-
logs.push("verdict gate: could not fetch PR comments via gh \u2014 skipping (check stays green).");
|
|
251
|
-
return { exitCode: 0, logs };
|
|
252
|
-
}
|
|
253
|
-
const pages = splitConcatenatedJson(fetched.stdout);
|
|
263
|
+
const pages = fetched.status === 0 ? splitConcatenatedJson(fetched.stdout) : null;
|
|
254
264
|
if (pages === null) {
|
|
255
|
-
|
|
265
|
+
if (outputPath !== void 0) return { exitCode: 1, logs: [UNREAD_COMMENTS_ERROR] };
|
|
266
|
+
logs.push(
|
|
267
|
+
fetched.status !== 0 ? "verdict gate: could not fetch PR comments via gh \u2014 skipping (check stays green)." : "verdict gate: unexpected gh output \u2014 skipping (check stays green)."
|
|
268
|
+
);
|
|
256
269
|
return { exitCode: 0, logs };
|
|
257
270
|
}
|
|
258
271
|
const comments = pages.flatMap((page) => Array.isArray(page) ? page : []).filter(
|
|
@@ -264,22 +277,25 @@ function runReviewVerdictBin(env, exec) {
|
|
|
264
277
|
return { exitCode: 0, logs };
|
|
265
278
|
}
|
|
266
279
|
const previous = [...comments].reverse().find((c) => c.body.includes(VERDICT_MARKER) && c.id !== void 0);
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
280
|
+
const settled = result.kind === "no-round" || result.kind === "unparseable";
|
|
281
|
+
if (outputPath !== void 0) {
|
|
282
|
+
writeOutput(outputPath, renderStepOutputs(result.commentBody, previous?.id, !settled && result.exitCode !== 0));
|
|
283
|
+
} else {
|
|
284
|
+
const payload = JSON.stringify({ body: result.commentBody });
|
|
285
|
+
const updated = previous !== void 0 && exec("gh", ["api", "-X", "PATCH", `repos/${repo}/issues/comments/${previous.id}`, "--input", "-"], payload).status === 0;
|
|
286
|
+
if (!updated) {
|
|
287
|
+
const post = exec("gh", ["api", "-X", "POST", `repos/${repo}/issues/${pr}/comments`, "--input", "-"], payload);
|
|
288
|
+
if (post.status !== 0) logs.push("verdict gate: could not post the verdict comment (continuing).");
|
|
289
|
+
}
|
|
276
290
|
}
|
|
277
|
-
if (
|
|
278
|
-
logs.push(
|
|
291
|
+
if (settled) {
|
|
292
|
+
logs.push(
|
|
293
|
+
result.kind === "no-round" ? "verdict gate: this run posted no review round for the current commit \u2014 earlier rounds are not judged again; check stays green." : "verdict gate: review output was not machine-readable \u2014 human review requested, check stays green."
|
|
294
|
+
);
|
|
279
295
|
return { exitCode: 0, logs };
|
|
280
296
|
}
|
|
281
297
|
logs.push(`verdict gate: ${result.verdict}`);
|
|
282
|
-
return { exitCode: result.exitCode, logs };
|
|
298
|
+
return { exitCode: outputPath === void 0 ? result.exitCode : 0, logs };
|
|
283
299
|
}
|
|
284
300
|
function splitConcatenatedJson(stdout) {
|
|
285
301
|
const text = stdout.trim();
|
|
@@ -150,9 +150,9 @@ function buildReviewConfigSnippet(input) {
|
|
|
150
150
|
|
|
151
151
|
// ../template-engine/dist/review/action-pins.js
|
|
152
152
|
var ACTION_PINS = {
|
|
153
|
-
"actions/checkout": "actions/checkout@
|
|
154
|
-
"actions/setup-node": "actions/setup-node@
|
|
155
|
-
"pnpm/action-setup": "pnpm/action-setup@
|
|
153
|
+
"actions/checkout": "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1",
|
|
154
|
+
"actions/setup-node": "actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0",
|
|
155
|
+
"pnpm/action-setup": "pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0",
|
|
156
156
|
"oven-sh/setup-bun": "oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0",
|
|
157
157
|
"anthropics/claude-code-action": "anthropics/claude-code-action@6b6870e649b5ff408753befeaee5fc7989d4e6a9 # v1.0.258",
|
|
158
158
|
"google-github-actions/run-gemini-cli": "google-github-actions/run-gemini-cli@f77273f4c914e4bf38440cf36a0369cb64a37489 # v0.1.22",
|
|
@@ -177,13 +177,14 @@ function renderConcurrencyBlock() {
|
|
|
177
177
|
" cancel-in-progress: false"
|
|
178
178
|
].join("\n");
|
|
179
179
|
}
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
var REVIEW_GATE_CONDITION = "steps.secret-gate.outputs.enabled == 'true' && steps.round-gate.outputs.allowed == 'true'";
|
|
181
|
+
function renderReviewPermissions(idToken) {
|
|
182
|
+
return ["permissions:", " contents: read", " pull-requests: write", ...idToken ? [" id-token: write"] : []].join("\n");
|
|
182
183
|
}
|
|
183
184
|
function renderCheckoutStep(gateCondition, extraWith = []) {
|
|
184
185
|
return [
|
|
185
186
|
"- name: Checkout repository",
|
|
186
|
-
` if: ${gateCondition}
|
|
187
|
+
...gateCondition === void 0 ? [] : [` if: ${gateCondition}`],
|
|
187
188
|
` uses: ${pinnedUses("actions/checkout")}`,
|
|
188
189
|
" with:",
|
|
189
190
|
...extraWith.map((line) => ` ${line}`),
|
|
@@ -225,10 +226,46 @@ function renderRoundGateStep(maxRounds) {
|
|
|
225
226
|
" fi"
|
|
226
227
|
].join("\n");
|
|
227
228
|
}
|
|
229
|
+
var JUDGE_CONDITION = "steps.ai-review-result.outputs.run == 'true'";
|
|
230
|
+
function renderAiReviewResultStep() {
|
|
231
|
+
return [
|
|
232
|
+
"- name: Check the AI review result",
|
|
233
|
+
" id: ai-review-result",
|
|
234
|
+
" env:",
|
|
235
|
+
" AI_REVIEW_RESULT: ${{ needs.ai-review.result }}",
|
|
236
|
+
" REVIEW_ENABLED: ${{ needs.ai-review.outputs.enabled }}",
|
|
237
|
+
" REVIEW_ALLOWED: ${{ needs.ai-review.outputs.allowed }}",
|
|
238
|
+
" LAST_COMMENT_ID: ${{ needs.ai-review.outputs.last-comment-id }}",
|
|
239
|
+
" run: |",
|
|
240
|
+
' if [ "$AI_REVIEW_RESULT" != success ]; then',
|
|
241
|
+
` echo "::error title=AI review did not succeed::The AI review job ended with '$AI_REVIEW_RESULT'."`,
|
|
242
|
+
" exit 1",
|
|
243
|
+
" fi",
|
|
244
|
+
' case "$REVIEW_ENABLED/$REVIEW_ALLOWED" in',
|
|
245
|
+
" false/* | true/false)",
|
|
246
|
+
' echo "The AI review did not run (no secret, or the round limit) - nothing to judge."',
|
|
247
|
+
' echo "run=false" >> "$GITHUB_OUTPUT"',
|
|
248
|
+
" exit 0",
|
|
249
|
+
" ;;",
|
|
250
|
+
" true/true)",
|
|
251
|
+
' case "$LAST_COMMENT_ID" in',
|
|
252
|
+
" '' | *[!0-9]*) ;;",
|
|
253
|
+
" *)",
|
|
254
|
+
' echo "run=true" >> "$GITHUB_OUTPUT"',
|
|
255
|
+
" exit 0",
|
|
256
|
+
" ;;",
|
|
257
|
+
" esac",
|
|
258
|
+
" ;;",
|
|
259
|
+
" esac",
|
|
260
|
+
` echo "::error title=Review verdict not computed::The AI review job's results did not reach this job, so no verdict can be computed. Re-run all jobs."`,
|
|
261
|
+
" exit 1"
|
|
262
|
+
].join("\n");
|
|
263
|
+
}
|
|
228
264
|
function renderVerdictGateStep(maxRounds, locale) {
|
|
229
265
|
return [
|
|
230
266
|
"- name: Compute review verdict",
|
|
231
|
-
"
|
|
267
|
+
" id: verdict",
|
|
268
|
+
` if: ${JUDGE_CONDITION}`,
|
|
232
269
|
" env:",
|
|
233
270
|
" GH_TOKEN: ${{ github.token }}",
|
|
234
271
|
" GITHUB_REPOSITORY: ${{ github.repository }}",
|
|
@@ -236,7 +273,8 @@ function renderVerdictGateStep(maxRounds, locale) {
|
|
|
236
273
|
` ARCHWEAVE_MAX_ROUNDS: '${maxRounds}'`,
|
|
237
274
|
` ARCHWEAVE_LOCALE: '${locale}'`,
|
|
238
275
|
" ARCHWEAVE_HEAD_SHA: ${{ github.event.pull_request.head.sha }}",
|
|
239
|
-
" ARCHWEAVE_ROUND_AFTER_ID: ${{
|
|
276
|
+
" ARCHWEAVE_ROUND_AFTER_ID: ${{ needs.ai-review.outputs.last-comment-id }}",
|
|
277
|
+
" ARCHWEAVE_VERDICT_OUTPUT: 'github-output'",
|
|
240
278
|
" run: |",
|
|
241
279
|
" if [ -x node_modules/.bin/archweave-review-verdict ]; then",
|
|
242
280
|
" node_modules/.bin/archweave-review-verdict",
|
|
@@ -245,16 +283,46 @@ function renderVerdictGateStep(maxRounds, locale) {
|
|
|
245
283
|
" fi"
|
|
246
284
|
].join("\n");
|
|
247
285
|
}
|
|
286
|
+
function renderBlockStep() {
|
|
287
|
+
return [
|
|
288
|
+
"- name: Fail on a blocking verdict",
|
|
289
|
+
" if: steps.verdict.outputs.blocked == 'true'",
|
|
290
|
+
" run: |",
|
|
291
|
+
' echo "The review verdict is BLOCK - see the verdict comment on this pull request."',
|
|
292
|
+
" exit 1"
|
|
293
|
+
].join("\n");
|
|
294
|
+
}
|
|
295
|
+
function renderVerdictCommentStep() {
|
|
296
|
+
return [
|
|
297
|
+
"- name: Post review verdict",
|
|
298
|
+
" env:",
|
|
299
|
+
" GH_TOKEN: ${{ github.token }}",
|
|
300
|
+
" REPOSITORY: ${{ github.repository }}",
|
|
301
|
+
" PR_NUMBER: ${{ github.event.pull_request.number }}",
|
|
302
|
+
" VERDICT_BODY: ${{ needs.review.outputs.comment-body }}",
|
|
303
|
+
" PREVIOUS_ID: ${{ needs.review.outputs.previous-comment-id }}",
|
|
304
|
+
" run: |",
|
|
305
|
+
` payload=$(jq -n --arg body "$VERDICT_BODY" '{body: $body}')`,
|
|
306
|
+
' case "$PREVIOUS_ID" in',
|
|
307
|
+
" ''|*[!0-9]*) PREVIOUS_ID= ;;",
|
|
308
|
+
" esac",
|
|
309
|
+
` if [ -n "$PREVIOUS_ID" ] && printf '%s' "$payload" | gh api -X PATCH "repos/$REPOSITORY/issues/comments/$PREVIOUS_ID" --input - > /dev/null; then`,
|
|
310
|
+
" exit 0",
|
|
311
|
+
" fi",
|
|
312
|
+
` printf '%s' "$payload" | gh api -X POST "repos/$REPOSITORY/issues/$PR_NUMBER/comments" --input - > /dev/null \\`,
|
|
313
|
+
' || echo "Could not post the verdict comment (continuing)."'
|
|
314
|
+
].join("\n");
|
|
315
|
+
}
|
|
248
316
|
var SETUP_NODE_VERSION = "22";
|
|
249
317
|
var FALLBACK_PNPM_VERSION = "10";
|
|
250
318
|
var INSTALL_LINES = {
|
|
251
|
-
pnpm: [" pnpm install --frozen-lockfile"],
|
|
252
|
-
npm: [" npm ci"],
|
|
319
|
+
pnpm: [" pnpm install --frozen-lockfile --ignore-scripts"],
|
|
320
|
+
npm: [" npm ci --ignore-scripts"],
|
|
253
321
|
yarn: [
|
|
254
322
|
" corepack enable yarn",
|
|
255
|
-
' if [ "$(yarn --version | cut -d. -f1)" -ge 2 ]; then yarn install --immutable; else yarn install --frozen-lockfile; fi'
|
|
323
|
+
' if [ "$(yarn --version | cut -d. -f1)" -ge 2 ]; then YARN_ENABLE_SCRIPTS=false yarn install --immutable --mode=skip-build; else yarn install --frozen-lockfile --ignore-scripts; fi'
|
|
256
324
|
],
|
|
257
|
-
bun: [" bun install --frozen-lockfile"]
|
|
325
|
+
bun: [" bun install --frozen-lockfile --ignore-scripts"]
|
|
258
326
|
};
|
|
259
327
|
function renderPnpmVersionStep(gate) {
|
|
260
328
|
return [
|
|
@@ -270,7 +338,7 @@ function renderPnpmVersionStep(gate) {
|
|
|
270
338
|
].join("\n");
|
|
271
339
|
}
|
|
272
340
|
function renderWorkspaceSetupSteps(packageManager) {
|
|
273
|
-
const gate =
|
|
341
|
+
const gate = ` if: ${JUDGE_CONDITION}`;
|
|
274
342
|
const steps = [];
|
|
275
343
|
if (packageManager === "pnpm") {
|
|
276
344
|
steps.push(renderPnpmVersionStep(gate));
|
|
@@ -305,6 +373,60 @@ function renderWorkspaceSetupSteps(packageManager) {
|
|
|
305
373
|
].join("\n"));
|
|
306
374
|
return steps.join("\n\n");
|
|
307
375
|
}
|
|
376
|
+
function renderReviewWorkflow(input, parts) {
|
|
377
|
+
const aiReviewSteps = [renderSecretGateStep(parts.secretName), renderRoundGateStep(input.maxRounds), ...parts.reviewSteps];
|
|
378
|
+
const reviewSteps = [
|
|
379
|
+
renderAiReviewResultStep(),
|
|
380
|
+
renderCheckoutStep(JUDGE_CONDITION),
|
|
381
|
+
renderWorkspaceSetupSteps(input.packageManager),
|
|
382
|
+
renderVerdictGateStep(input.maxRounds, input.locale),
|
|
383
|
+
renderBlockStep()
|
|
384
|
+
];
|
|
385
|
+
return [
|
|
386
|
+
`name: ${parts.name}`,
|
|
387
|
+
"",
|
|
388
|
+
renderTriggerBlock(),
|
|
389
|
+
"",
|
|
390
|
+
renderConcurrencyBlock(),
|
|
391
|
+
"",
|
|
392
|
+
"permissions: {}",
|
|
393
|
+
"",
|
|
394
|
+
"jobs:",
|
|
395
|
+
" ai-review:",
|
|
396
|
+
" runs-on: ubuntu-latest",
|
|
397
|
+
indentBlock(renderReviewPermissions(parts.idToken), 4),
|
|
398
|
+
" outputs:",
|
|
399
|
+
" enabled: ${{ steps.secret-gate.outputs.enabled }}",
|
|
400
|
+
" allowed: ${{ steps.round-gate.outputs.allowed }}",
|
|
401
|
+
" last-comment-id: ${{ steps.round-gate.outputs.last-comment-id }}",
|
|
402
|
+
" steps:",
|
|
403
|
+
indentBlock(aiReviewSteps.join("\n\n"), 6),
|
|
404
|
+
"",
|
|
405
|
+
" review:",
|
|
406
|
+
" needs: ai-review",
|
|
407
|
+
" if: always()",
|
|
408
|
+
" runs-on: ubuntu-latest",
|
|
409
|
+
" permissions:",
|
|
410
|
+
" contents: read",
|
|
411
|
+
" pull-requests: read",
|
|
412
|
+
" outputs:",
|
|
413
|
+
" comment-body: ${{ steps.verdict.outputs.comment-body }}",
|
|
414
|
+
" previous-comment-id: ${{ steps.verdict.outputs.previous-comment-id }}",
|
|
415
|
+
" steps:",
|
|
416
|
+
indentBlock(reviewSteps.join("\n\n"), 6),
|
|
417
|
+
"",
|
|
418
|
+
" review-comment:",
|
|
419
|
+
" needs: review",
|
|
420
|
+
// Runs after a BLOCK failed the review job, too: the comment explains the red check.
|
|
421
|
+
" if: ${{ !cancelled() && needs.review.outputs.comment-body != '' }}",
|
|
422
|
+
" runs-on: ubuntu-latest",
|
|
423
|
+
" permissions:",
|
|
424
|
+
" pull-requests: write",
|
|
425
|
+
" steps:",
|
|
426
|
+
indentBlock(renderVerdictCommentStep(), 6),
|
|
427
|
+
""
|
|
428
|
+
].join("\n");
|
|
429
|
+
}
|
|
308
430
|
|
|
309
431
|
// ../template-engine/dist/review/claude/claude-provider.js
|
|
310
432
|
var SECRET_NAME = "CLAUDE_CODE_OAUTH_TOKEN";
|
|
@@ -337,40 +459,26 @@ function composePrompt(input) {
|
|
|
337
459
|
return sections.join("\n\n---\n\n");
|
|
338
460
|
}
|
|
339
461
|
function buildWorkflowYaml(input) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
"",
|
|
361
|
-
renderTriggerBlock(),
|
|
362
|
-
"",
|
|
363
|
-
renderConcurrencyBlock(),
|
|
364
|
-
"",
|
|
365
|
-
renderPermissionsBlock(),
|
|
366
|
-
"",
|
|
367
|
-
"jobs:",
|
|
368
|
-
" review:",
|
|
369
|
-
" runs-on: ubuntu-latest",
|
|
370
|
-
" steps:",
|
|
371
|
-
indentBlock(steps.join("\n\n"), 6),
|
|
372
|
-
""
|
|
373
|
-
].join("\n");
|
|
462
|
+
return renderReviewWorkflow(input, {
|
|
463
|
+
name: "Claude Code Review",
|
|
464
|
+
secretName: SECRET_NAME,
|
|
465
|
+
// The Claude GitHub App signs in with the workflow's OIDC token (claude-code-action's FAQ).
|
|
466
|
+
idToken: true,
|
|
467
|
+
reviewSteps: [
|
|
468
|
+
renderCheckoutStep(REVIEW_GATE_CONDITION),
|
|
469
|
+
[
|
|
470
|
+
"- name: Run Claude review",
|
|
471
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
472
|
+
` uses: ${pinnedUses("anthropics/claude-code-action")}`,
|
|
473
|
+
" with:",
|
|
474
|
+
` claude_code_oauth_token: \${{ secrets.${SECRET_NAME} }}`,
|
|
475
|
+
" prompt: |",
|
|
476
|
+
indentBlock(composePrompt(input), 6),
|
|
477
|
+
" claude_args: |",
|
|
478
|
+
` --allowedTools "${ALLOWED_TOOLS}"`
|
|
479
|
+
].join("\n")
|
|
480
|
+
]
|
|
481
|
+
});
|
|
374
482
|
}
|
|
375
483
|
var claudeProvider = {
|
|
376
484
|
id: "claude",
|
|
@@ -410,49 +518,39 @@ function composePrompt2(input) {
|
|
|
410
518
|
return sections.join("\n\n---\n\n");
|
|
411
519
|
}
|
|
412
520
|
function buildWorkflowYaml2(input) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
[
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
"
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
"
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
"",
|
|
447
|
-
renderPermissionsBlock(),
|
|
448
|
-
"",
|
|
449
|
-
"jobs:",
|
|
450
|
-
" review:",
|
|
451
|
-
" runs-on: ubuntu-latest",
|
|
452
|
-
" steps:",
|
|
453
|
-
indentBlock(steps.join("\n\n"), 6),
|
|
454
|
-
""
|
|
455
|
-
].join("\n");
|
|
521
|
+
return renderReviewWorkflow(input, {
|
|
522
|
+
name: "Cursor Code Review",
|
|
523
|
+
secretName: SECRET_NAME2,
|
|
524
|
+
// An API key: the Cursor CLI needs no OIDC token.
|
|
525
|
+
idToken: false,
|
|
526
|
+
reviewSteps: [
|
|
527
|
+
renderCheckoutStep(REVIEW_GATE_CONDITION),
|
|
528
|
+
[
|
|
529
|
+
"- name: Install cursor-agent CLI",
|
|
530
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
531
|
+
" run: |",
|
|
532
|
+
// HTTPS only, TLS 1.2 or newer. Cursor publishes no pinned version or checksum for
|
|
533
|
+
// this installer; docs/pr-review.md states what that leaves open.
|
|
534
|
+
" curl --proto '=https' --tlsv1.2 -fsS https://cursor.com/install | bash",
|
|
535
|
+
// The docs name ~/.cursor/bin; the installer currently links `agent` into ~/.local/bin.
|
|
536
|
+
// Both go on the PATH, so the next step finds it wherever the installer puts it.
|
|
537
|
+
' echo "$HOME/.local/bin" >> "$GITHUB_PATH"',
|
|
538
|
+
' echo "$HOME/.cursor/bin" >> "$GITHUB_PATH"'
|
|
539
|
+
].join("\n"),
|
|
540
|
+
[
|
|
541
|
+
"- name: Run Cursor review",
|
|
542
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
543
|
+
" env:",
|
|
544
|
+
` CURSOR_API_KEY: \${{ secrets.${SECRET_NAME2} }}`,
|
|
545
|
+
" GH_TOKEN: ${{ github.token }}",
|
|
546
|
+
" run: |",
|
|
547
|
+
` agent -p "$(cat <<'${PROMPT_HEREDOC_DELIMITER}'`,
|
|
548
|
+
indentBlock(composePrompt2(input), 4),
|
|
549
|
+
` ${PROMPT_HEREDOC_DELIMITER}`,
|
|
550
|
+
' )"'
|
|
551
|
+
].join("\n")
|
|
552
|
+
]
|
|
553
|
+
});
|
|
456
554
|
}
|
|
457
555
|
var cursorProvider = {
|
|
458
556
|
id: "cursor",
|
|
@@ -491,40 +589,26 @@ function composePrompt3(input) {
|
|
|
491
589
|
return sections.join("\n\n---\n\n");
|
|
492
590
|
}
|
|
493
591
|
function buildWorkflowYaml3(input) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
"",
|
|
515
|
-
renderTriggerBlock(),
|
|
516
|
-
"",
|
|
517
|
-
renderConcurrencyBlock(),
|
|
518
|
-
"",
|
|
519
|
-
renderPermissionsBlock(),
|
|
520
|
-
"",
|
|
521
|
-
"jobs:",
|
|
522
|
-
" review:",
|
|
523
|
-
" runs-on: ubuntu-latest",
|
|
524
|
-
" steps:",
|
|
525
|
-
indentBlock(steps.join("\n\n"), 6),
|
|
526
|
-
""
|
|
527
|
-
].join("\n");
|
|
592
|
+
return renderReviewWorkflow(input, {
|
|
593
|
+
name: "Gemini Code Review",
|
|
594
|
+
secretName: SECRET_NAME3,
|
|
595
|
+
// An API key: run-gemini-cli uses OIDC only for Google Cloud workload identity federation.
|
|
596
|
+
idToken: false,
|
|
597
|
+
reviewSteps: [
|
|
598
|
+
renderCheckoutStep(REVIEW_GATE_CONDITION),
|
|
599
|
+
[
|
|
600
|
+
"- name: Run Gemini review",
|
|
601
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
602
|
+
` uses: ${pinnedUses("google-github-actions/run-gemini-cli")}`,
|
|
603
|
+
" env:",
|
|
604
|
+
" GITHUB_TOKEN: ${{ github.token }}",
|
|
605
|
+
" with:",
|
|
606
|
+
` gemini_api_key: \${{ secrets.${SECRET_NAME3} }}`,
|
|
607
|
+
" prompt: |",
|
|
608
|
+
indentBlock(composePrompt3(input), 6)
|
|
609
|
+
].join("\n")
|
|
610
|
+
]
|
|
611
|
+
});
|
|
528
612
|
}
|
|
529
613
|
var geminiProvider = {
|
|
530
614
|
id: "gemini",
|
|
@@ -563,64 +647,49 @@ function composePrompt4(input) {
|
|
|
563
647
|
return sections.join("\n\n---\n\n");
|
|
564
648
|
}
|
|
565
649
|
function buildWorkflowYaml4(input) {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
[
|
|
572
|
-
"
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
"
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
"
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
"name: GPT Code Review",
|
|
610
|
-
"",
|
|
611
|
-
renderTriggerBlock(),
|
|
612
|
-
"",
|
|
613
|
-
renderConcurrencyBlock(),
|
|
614
|
-
"",
|
|
615
|
-
renderPermissionsBlock(),
|
|
616
|
-
"",
|
|
617
|
-
"jobs:",
|
|
618
|
-
" review:",
|
|
619
|
-
" runs-on: ubuntu-latest",
|
|
620
|
-
" steps:",
|
|
621
|
-
indentBlock(steps.join("\n\n"), 6),
|
|
622
|
-
""
|
|
623
|
-
].join("\n");
|
|
650
|
+
return renderReviewWorkflow(input, {
|
|
651
|
+
name: "GPT Code Review",
|
|
652
|
+
secretName: SECRET_NAME4,
|
|
653
|
+
// An API key: codex-action needs no OIDC token.
|
|
654
|
+
idToken: false,
|
|
655
|
+
reviewSteps: [
|
|
656
|
+
renderCheckoutStep(REVIEW_GATE_CONDITION, ["ref: refs/pull/${{ github.event.pull_request.number }}/merge"]),
|
|
657
|
+
[
|
|
658
|
+
"- name: Fetch base and head refs for the PR",
|
|
659
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
660
|
+
" env:",
|
|
661
|
+
" PR_BASE_REF: ${{ github.event.pull_request.base.ref }}",
|
|
662
|
+
" PR_NUMBER: ${{ github.event.pull_request.number }}",
|
|
663
|
+
" run: |",
|
|
664
|
+
" # Explicit destinations: the sandboxed review step has no network, so both sides",
|
|
665
|
+
" # of the diff must survive as stable local refs (FETCH_HEAD alone is ambiguous).",
|
|
666
|
+
" git fetch --no-tags origin \\",
|
|
667
|
+
' "+refs/heads/$PR_BASE_REF:refs/remotes/origin/pr-base" \\',
|
|
668
|
+
' "+refs/pull/$PR_NUMBER/head:refs/remotes/origin/pr-head"'
|
|
669
|
+
].join("\n"),
|
|
670
|
+
[
|
|
671
|
+
"- name: Run Codex review",
|
|
672
|
+
" id: codex-review",
|
|
673
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
674
|
+
` uses: ${pinnedUses("openai/codex-action")}`,
|
|
675
|
+
" with:",
|
|
676
|
+
` openai-api-key: \${{ secrets.${SECRET_NAME4} }}`,
|
|
677
|
+
' permission-profile: ":read-only"',
|
|
678
|
+
" prompt: |",
|
|
679
|
+
indentBlock(composePrompt4(input), 6)
|
|
680
|
+
].join("\n"),
|
|
681
|
+
[
|
|
682
|
+
"- name: Post review comment",
|
|
683
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
684
|
+
" env:",
|
|
685
|
+
" GH_TOKEN: ${{ github.token }}",
|
|
686
|
+
" PR_NUMBER: ${{ github.event.pull_request.number }}",
|
|
687
|
+
" REVIEW_BODY: ${{ steps.codex-review.outputs.final-message }}",
|
|
688
|
+
" run: |",
|
|
689
|
+
' printf %s "$REVIEW_BODY" | gh pr comment "$PR_NUMBER" --body-file -'
|
|
690
|
+
].join("\n")
|
|
691
|
+
]
|
|
692
|
+
});
|
|
624
693
|
}
|
|
625
694
|
var gptProvider = {
|
|
626
695
|
id: "gpt",
|