@staff0rd/assist 0.788.1 → 0.789.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/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.788.1",
9
+ version: "0.789.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -35132,149 +35132,14 @@ async function runHighLevelReview(number, options2 = {}) {
35132
35132
  await openHighLevelOverlay(sessionId, subject);
35133
35133
  }
35134
35134
 
35135
- // src/commands/review/gatherContext.ts
35136
- import { execSync as execSync59 } from "child_process";
35137
-
35138
- // src/commands/review/fetchPrDiff.ts
35139
- import { execSync as execSync57 } from "child_process";
35140
- function fetchPrDiff(prNumber, baseSha, headSha) {
35141
- const { org, repo } = getRepoInfo();
35142
- try {
35143
- return execSync57(`gh pr diff ${prNumber} -R ${org}/${repo}`, {
35144
- encoding: "utf8",
35145
- maxBuffer: 256 * 1024 * 1024,
35146
- stdio: ["ignore", "pipe", "pipe"]
35147
- });
35148
- } catch (error) {
35149
- if (!isDiffTooLarge(error)) throw error;
35150
- return fetchDiffViaGit(baseSha, headSha);
35151
- }
35152
- }
35153
- function isDiffTooLarge(error) {
35154
- return error instanceof Error && (error.message.includes("too_large") || error.message.includes("maximum number of files"));
35155
- }
35156
- function fetchDiffViaGit(baseSha, headSha) {
35157
- try {
35158
- execSync57(`git fetch origin ${baseSha} ${headSha}`, { stdio: "ignore" });
35159
- } catch {
35160
- }
35161
- return execSync57(`git diff ${baseSha}...${headSha}`, {
35162
- encoding: "utf8",
35163
- maxBuffer: 256 * 1024 * 1024
35164
- });
35165
- }
35166
-
35167
- // src/commands/review/fetchPrDiffInfo.ts
35168
- import { execSync as execSync58 } from "child_process";
35169
- function getCurrentBranch3() {
35170
- return execSync58("git rev-parse --abbrev-ref HEAD", {
35171
- encoding: "utf8"
35172
- }).trim();
35173
- }
35174
- var FIELDS3 = "number,baseRefName,baseRefOid,headRefName,headRefOid";
35175
- function runGh2(command) {
35176
- return execSync58(command, {
35177
- encoding: "utf8",
35178
- stdio: ["ignore", "pipe", "pipe"]
35179
- });
35180
- }
35181
- function fetchRawPr(org, repo, branch2) {
35182
- const pinned = getPinnedPr();
35183
- if (pinned !== void 0)
35184
- return JSON.parse(
35185
- runGh2(`gh pr view ${pinned} --json ${FIELDS3} -R ${org}/${repo}`)
35186
- );
35187
- const parsed = JSON.parse(
35188
- runGh2(
35189
- `gh pr list --state open --head ${branch2} --json ${FIELDS3} -R ${org}/${repo}`
35190
- )
35191
- );
35192
- return parsed[0];
35193
- }
35194
- function fetchPrDiffInfo() {
35195
- const { org, repo } = getRepoInfo();
35196
- const branch2 = getCurrentBranch3();
35197
- const pr = fetchRawPr(org, repo, branch2);
35198
- if (!pr) {
35199
- console.error(
35200
- `Error: No open pull request found for branch \`${branch2}\`. Open a PR for this branch before running \`assist review\`.`
35201
- );
35202
- process.exit(1);
35203
- }
35204
- return {
35205
- prNumber: pr.number,
35206
- baseRef: pr.baseRefName,
35207
- baseSha: pr.baseRefOid,
35208
- headRef: pr.headRefName,
35209
- headSha: pr.headRefOid
35210
- };
35211
- }
35212
- function fetchPrChangedFiles(prNumber) {
35213
- const { org, repo } = getRepoInfo();
35214
- const out = execSync58(
35215
- `gh api repos/${org}/${repo}/pulls/${prNumber}/files --paginate --jq ".[].filename"`,
35216
- {
35217
- encoding: "utf8",
35218
- maxBuffer: 64 * 1024 * 1024
35219
- }
35220
- );
35221
- return out.trim().split("\n").filter(Boolean);
35222
- }
35223
-
35224
- // src/commands/review/gatherContext.ts
35225
- function gatherContext() {
35226
- const branch2 = execSync59("git rev-parse --abbrev-ref HEAD", {
35227
- encoding: "utf8"
35228
- }).trim();
35229
- const sha = execSync59("git rev-parse HEAD", { encoding: "utf8" }).trim();
35230
- const shortSha = execSync59("git rev-parse --short=7 HEAD", {
35231
- encoding: "utf8"
35232
- }).trim();
35233
- const prInfo = fetchPrDiffInfo();
35234
- const changedFiles2 = fetchPrChangedFiles(prInfo.prNumber);
35235
- const diff3 = fetchPrDiff(prInfo.prNumber, prInfo.baseSha, prInfo.headSha);
35236
- return {
35237
- branch: branch2,
35238
- sha,
35239
- shortSha,
35240
- prNumber: prInfo.prNumber,
35241
- baseRef: prInfo.baseRef,
35242
- baseSha: prInfo.baseSha,
35243
- headRef: prInfo.headRef,
35244
- headSha: prInfo.headSha,
35245
- changedFiles: changedFiles2,
35246
- diff: diff3
35247
- };
35248
- }
35249
-
35250
- // src/commands/review/postReviewToPr.ts
35251
- import { readFileSync as readFileSync49 } from "fs";
35135
+ // src/commands/review/ci/writeJobSummary.ts
35136
+ import { appendFileSync as appendFileSync2, readFileSync as readFileSync49 } from "fs";
35252
35137
 
35253
35138
  // src/commands/review/carriedUnanchoredFindings.ts
35254
35139
  function carriedUnanchoredFindings(unanchored) {
35255
35140
  return unanchored.filter((finding) => finding.source !== "already-raised");
35256
35141
  }
35257
35142
 
35258
- // src/commands/review/chainAddressComments.ts
35259
- function chainAddressComments(prNumber, announce) {
35260
- const args = ["review-pr-comments", String(prNumber)];
35261
- if (announce) args.push("--announce");
35262
- return startChainedSession(
35263
- `an Address Comments session for PR #${prNumber}`,
35264
- () => requestAssistSession(args, process.cwd(), { inPlace: true })
35265
- );
35266
- }
35267
-
35268
- // src/commands/review/chainAfterReview.ts
35269
- async function chainAfterReview(prNumber, outcome, options2) {
35270
- const raisedComments = outcome.posted > 0 && outcome.submitted;
35271
- if (options2.addressComments && raisedComments) {
35272
- await chainAddressComments(prNumber, options2.announce);
35273
- return;
35274
- }
35275
- if (options2.announce) await announcePr(prNumber);
35276
- }
35277
-
35278
35143
  // src/commands/review/parseFindings.ts
35279
35144
  var SEVERITIES = ["blocker", "major", "minor", "nit"];
35280
35145
  var SOURCES = [
@@ -35400,105 +35265,6 @@ function buildReviewSummary(markdown, unanchored = []) {
35400
35265
  return lines2.join("\n");
35401
35266
  }
35402
35267
 
35403
- // src/commands/review/sanitiseReviewerNames.ts
35404
- var REVIEWER_LABEL = "the reviewer";
35405
- function sanitiseReviewerNames(value) {
35406
- return value.replace(/\bclaude\b/gi, REVIEWER_LABEL).replace(/\bopus\b/gi, REVIEWER_LABEL);
35407
- }
35408
-
35409
- // src/commands/review/postFindings.ts
35410
- function buildCommentBody(finding) {
35411
- const lines2 = [];
35412
- const severityLabel = finding.severity ?? "finding";
35413
- lines2.push(`**${severityLabel}: ${finding.title}**`);
35414
- if (finding.impact) lines2.push("", `Impact: ${finding.impact}`);
35415
- if (finding.recommendation)
35416
- lines2.push("", `Recommendation: ${finding.recommendation}`);
35417
- return sanitiseReviewerNames(lines2.join("\n"));
35418
- }
35419
- async function postFindings(findings) {
35420
- let posted = 0;
35421
- let failed3 = 0;
35422
- for (const finding of findings) {
35423
- const body = buildCommentBody(finding);
35424
- try {
35425
- await comment2(finding.file, finding.line, body, {
35426
- startLine: finding.startLine,
35427
- skipPreview: true
35428
- });
35429
- posted++;
35430
- } catch (error) {
35431
- failed3++;
35432
- const message3 = error instanceof Error ? error.message : String(error);
35433
- console.error(
35434
- `Failed to post comment on ${finding.file}:${finding.line}: ${message3}`
35435
- );
35436
- }
35437
- }
35438
- return { posted, failed: failed3 };
35439
- }
35440
-
35441
- // src/commands/review/runReviewSubmission.ts
35442
- function runReviewSubmission(mutation, body) {
35443
- try {
35444
- const prId = getCurrentPrNodeId();
35445
- runGhGraphql(mutation, { prId, body });
35446
- console.log("Submitted review.");
35447
- } catch (error) {
35448
- if (isGhNotInstalled(error)) {
35449
- console.error("Error: GitHub CLI (gh) is not installed.");
35450
- return;
35451
- }
35452
- const message3 = error instanceof Error ? error.message : String(error);
35453
- console.error(`Failed to submit review: ${message3}`);
35454
- }
35455
- }
35456
-
35457
- // src/commands/review/submitBodyOnlyReview.ts
35458
- var MUTATION6 = `mutation($prId: ID!, $body: String) { addPullRequestReview(input: { pullRequestId: $prId, event: COMMENT, body: $body }) { pullRequestReview { id } } }`;
35459
- function submitBodyOnlyReview(body) {
35460
- runReviewSubmission(MUTATION6, body);
35461
- }
35462
-
35463
- // src/commands/review/submitPendingReview.ts
35464
- var MUTATION7 = `mutation($prId: ID!, $body: String) { submitPullRequestReview(input: { pullRequestId: $prId, event: COMMENT, body: $body }) { pullRequestReview { id } } }`;
35465
- function submitPendingReview(body) {
35466
- runReviewSubmission(MUTATION7, body);
35467
- }
35468
-
35469
- // src/commands/review/postAndMaybeSubmit.ts
35470
- function buildReviewBody(markdown, unanchored) {
35471
- return sanitiseReviewerNames(buildReviewSummary(markdown, unanchored));
35472
- }
35473
- function submitReview(body, posted) {
35474
- if (posted > 0) submitPendingReview(body);
35475
- else submitBodyOnlyReview(body);
35476
- }
35477
- async function decideSubmit(options2) {
35478
- if (!options2.prompt) return options2.submit;
35479
- await setSessionStatus("waiting");
35480
- try {
35481
- return await promptConfirm("Submit the pending review?", options2.submit);
35482
- } finally {
35483
- await setSessionStatus("running");
35484
- }
35485
- }
35486
- async function postAndMaybeSubmit(lineBound, unanchored, markdown, options2) {
35487
- const result = await postFindings(lineBound);
35488
- const failedSuffix = result.failed > 0 ? `, ${result.failed} failed` : "";
35489
- console.log(`Posted ${result.posted} comment(s)${failedSuffix}.`);
35490
- const carried = carriedUnanchoredFindings(unanchored);
35491
- if (result.posted === 0 && carried.length === 0)
35492
- return { posted: 0, submitted: false };
35493
- const shouldSubmit = await decideSubmit(options2);
35494
- if (shouldSubmit) {
35495
- submitReview(buildReviewBody(markdown, unanchored), result.posted);
35496
- return { posted: result.posted, submitted: true };
35497
- }
35498
- console.log("Leaving the review unsubmitted.");
35499
- return { posted: result.posted, submitted: false };
35500
- }
35501
-
35502
35268
  // src/commands/review/partitionFindings.ts
35503
35269
  function isValidLine(n) {
35504
35270
  return Number.isInteger(n) && n >= 1;
@@ -35565,6 +35331,35 @@ function buildDiffLineIndex(diff3) {
35565
35331
  return index2;
35566
35332
  }
35567
35333
 
35334
+ // src/commands/review/fetchPrDiff.ts
35335
+ import { execSync as execSync57 } from "child_process";
35336
+ function fetchPrDiff(prNumber, baseSha, headSha) {
35337
+ const { org, repo } = getRepoInfo();
35338
+ try {
35339
+ return execSync57(`gh pr diff ${prNumber} -R ${org}/${repo}`, {
35340
+ encoding: "utf8",
35341
+ maxBuffer: 256 * 1024 * 1024,
35342
+ stdio: ["ignore", "pipe", "pipe"]
35343
+ });
35344
+ } catch (error) {
35345
+ if (!isDiffTooLarge(error)) throw error;
35346
+ return fetchDiffViaGit(baseSha, headSha);
35347
+ }
35348
+ }
35349
+ function isDiffTooLarge(error) {
35350
+ return error instanceof Error && (error.message.includes("too_large") || error.message.includes("maximum number of files"));
35351
+ }
35352
+ function fetchDiffViaGit(baseSha, headSha) {
35353
+ try {
35354
+ execSync57(`git fetch origin ${baseSha} ${headSha}`, { stdio: "ignore" });
35355
+ } catch {
35356
+ }
35357
+ return execSync57(`git diff ${baseSha}...${headSha}`, {
35358
+ encoding: "utf8",
35359
+ maxBuffer: 256 * 1024 * 1024
35360
+ });
35361
+ }
35362
+
35568
35363
  // src/commands/review/partitionFindingsByDiff.ts
35569
35364
  function isWithinDiff(finding, index2) {
35570
35365
  const lines2 = index2.get(finding.file);
@@ -35666,6 +35461,137 @@ function selectPostableFindings(markdown, prInfo) {
35666
35461
  return { inDiff, unanchored: [...carriedUnlocated, ...unanchored] };
35667
35462
  }
35668
35463
 
35464
+ // src/commands/review/ci/writeJobSummary.ts
35465
+ function writeJobSummary(synthesisPath, prInfo, summaryPath) {
35466
+ if (!summaryPath) return;
35467
+ const markdown = readFileSync49(synthesisPath, "utf8");
35468
+ const { unanchored } = selectPostableFindings(markdown, prInfo);
35469
+ appendFileSync2(summaryPath, `${buildReviewSummary(markdown, unanchored)}
35470
+ `);
35471
+ }
35472
+
35473
+ // src/commands/review/postReviewToPr.ts
35474
+ import { readFileSync as readFileSync50 } from "fs";
35475
+
35476
+ // src/commands/review/chainAddressComments.ts
35477
+ function chainAddressComments(prNumber, announce) {
35478
+ const args = ["review-pr-comments", String(prNumber)];
35479
+ if (announce) args.push("--announce");
35480
+ return startChainedSession(
35481
+ `an Address Comments session for PR #${prNumber}`,
35482
+ () => requestAssistSession(args, process.cwd(), { inPlace: true })
35483
+ );
35484
+ }
35485
+
35486
+ // src/commands/review/chainAfterReview.ts
35487
+ async function chainAfterReview(prNumber, outcome, options2) {
35488
+ const raisedComments = outcome.posted > 0 && outcome.submitted;
35489
+ if (options2.addressComments && raisedComments) {
35490
+ await chainAddressComments(prNumber, options2.announce);
35491
+ return;
35492
+ }
35493
+ if (options2.announce) await announcePr(prNumber);
35494
+ }
35495
+
35496
+ // src/commands/review/sanitiseReviewerNames.ts
35497
+ var REVIEWER_LABEL = "the reviewer";
35498
+ function sanitiseReviewerNames(value) {
35499
+ return value.replace(/\bclaude\b/gi, REVIEWER_LABEL).replace(/\bopus\b/gi, REVIEWER_LABEL);
35500
+ }
35501
+
35502
+ // src/commands/review/postFindings.ts
35503
+ function buildCommentBody(finding) {
35504
+ const lines2 = [];
35505
+ const severityLabel = finding.severity ?? "finding";
35506
+ lines2.push(`**${severityLabel}: ${finding.title}**`);
35507
+ if (finding.impact) lines2.push("", `Impact: ${finding.impact}`);
35508
+ if (finding.recommendation)
35509
+ lines2.push("", `Recommendation: ${finding.recommendation}`);
35510
+ return sanitiseReviewerNames(lines2.join("\n"));
35511
+ }
35512
+ async function postFindings(findings) {
35513
+ let posted = 0;
35514
+ let failed3 = 0;
35515
+ for (const finding of findings) {
35516
+ const body = buildCommentBody(finding);
35517
+ try {
35518
+ await comment2(finding.file, finding.line, body, {
35519
+ startLine: finding.startLine,
35520
+ skipPreview: true
35521
+ });
35522
+ posted++;
35523
+ } catch (error) {
35524
+ failed3++;
35525
+ const message3 = error instanceof Error ? error.message : String(error);
35526
+ console.error(
35527
+ `Failed to post comment on ${finding.file}:${finding.line}: ${message3}`
35528
+ );
35529
+ }
35530
+ }
35531
+ return { posted, failed: failed3 };
35532
+ }
35533
+
35534
+ // src/commands/review/runReviewSubmission.ts
35535
+ function runReviewSubmission(mutation, body) {
35536
+ try {
35537
+ const prId = getCurrentPrNodeId();
35538
+ runGhGraphql(mutation, { prId, body });
35539
+ console.log("Submitted review.");
35540
+ } catch (error) {
35541
+ if (isGhNotInstalled(error)) {
35542
+ console.error("Error: GitHub CLI (gh) is not installed.");
35543
+ return;
35544
+ }
35545
+ const message3 = error instanceof Error ? error.message : String(error);
35546
+ console.error(`Failed to submit review: ${message3}`);
35547
+ }
35548
+ }
35549
+
35550
+ // src/commands/review/submitBodyOnlyReview.ts
35551
+ var MUTATION6 = `mutation($prId: ID!, $body: String) { addPullRequestReview(input: { pullRequestId: $prId, event: COMMENT, body: $body }) { pullRequestReview { id } } }`;
35552
+ function submitBodyOnlyReview(body) {
35553
+ runReviewSubmission(MUTATION6, body);
35554
+ }
35555
+
35556
+ // src/commands/review/submitPendingReview.ts
35557
+ var MUTATION7 = `mutation($prId: ID!, $body: String) { submitPullRequestReview(input: { pullRequestId: $prId, event: COMMENT, body: $body }) { pullRequestReview { id } } }`;
35558
+ function submitPendingReview(body) {
35559
+ runReviewSubmission(MUTATION7, body);
35560
+ }
35561
+
35562
+ // src/commands/review/postAndMaybeSubmit.ts
35563
+ function buildReviewBody(markdown, unanchored) {
35564
+ return sanitiseReviewerNames(buildReviewSummary(markdown, unanchored));
35565
+ }
35566
+ function submitReview(body, posted) {
35567
+ if (posted > 0) submitPendingReview(body);
35568
+ else submitBodyOnlyReview(body);
35569
+ }
35570
+ async function decideSubmit(options2) {
35571
+ if (!options2.prompt) return options2.submit;
35572
+ await setSessionStatus("waiting");
35573
+ try {
35574
+ return await promptConfirm("Submit the pending review?", options2.submit);
35575
+ } finally {
35576
+ await setSessionStatus("running");
35577
+ }
35578
+ }
35579
+ async function postAndMaybeSubmit(lineBound, unanchored, markdown, options2) {
35580
+ const result = await postFindings(lineBound);
35581
+ const failedSuffix = result.failed > 0 ? `, ${result.failed} failed` : "";
35582
+ console.log(`Posted ${result.posted} comment(s)${failedSuffix}.`);
35583
+ const carried = carriedUnanchoredFindings(unanchored);
35584
+ if (result.posted === 0 && carried.length === 0)
35585
+ return { posted: 0, submitted: false };
35586
+ const shouldSubmit = await decideSubmit(options2);
35587
+ if (shouldSubmit) {
35588
+ submitReview(buildReviewBody(markdown, unanchored), result.posted);
35589
+ return { posted: result.posted, submitted: true };
35590
+ }
35591
+ console.log("Leaving the review unsubmitted.");
35592
+ return { posted: result.posted, submitted: false };
35593
+ }
35594
+
35669
35595
  // src/commands/review/stillOnReviewedPr.ts
35670
35596
  function currentPrNumber() {
35671
35597
  try {
@@ -35699,7 +35625,7 @@ async function confirmPost(prNumber, work, options2) {
35699
35625
  return promptConfirm(`Post ${work} to PR #${prNumber}?`, false);
35700
35626
  }
35701
35627
  async function postFindingsToPr(prInfo, synthesisPath, options2) {
35702
- const markdown = readFileSync49(synthesisPath, "utf8");
35628
+ const markdown = readFileSync50(synthesisPath, "utf8");
35703
35629
  const { inDiff, unanchored } = selectPostableFindings(markdown, prInfo);
35704
35630
  const carried = carriedUnanchoredFindings(unanchored);
35705
35631
  if (inDiff.length === 0 && carried.length === 0) return NOTHING_POSTED;
@@ -35886,7 +35812,7 @@ function renderEntry(entry, frame) {
35886
35812
  import { format } from "util";
35887
35813
 
35888
35814
  // src/commands/review/createReviewLogSink.ts
35889
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync24 } from "fs";
35815
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync24 } from "fs";
35890
35816
  import { join as join83 } from "path";
35891
35817
  var LOG_FILE = "review.log";
35892
35818
  function createReviewLogSink() {
@@ -35899,7 +35825,7 @@ function createReviewLogSink() {
35899
35825
  buffered.push(stripped);
35900
35826
  return;
35901
35827
  }
35902
- appendFileSync2(logPath2, `${stripped}
35828
+ appendFileSync3(logPath2, `${stripped}
35903
35829
  `);
35904
35830
  },
35905
35831
  attach(reviewDir) {
@@ -35912,7 +35838,7 @@ function createReviewLogSink() {
35912
35838
  ...buffered
35913
35839
  ];
35914
35840
  buffered = [];
35915
- appendFileSync2(logPath2, `${lines2.join("\n")}
35841
+ appendFileSync3(logPath2, `${lines2.join("\n")}
35916
35842
  `);
35917
35843
  }
35918
35844
  };
@@ -36643,12 +36569,13 @@ function runSlot(harness, spec) {
36643
36569
  // src/commands/review/resolveClaude.ts
36644
36570
  function resolveClaude(args) {
36645
36571
  if (args.cached) return Promise.resolve(args.cached);
36572
+ const name = args.slot?.label ?? "claude";
36646
36573
  const override = args.slot?.override;
36647
36574
  const spinner = args.multi?.create(
36648
- `${reviewerLabel("claude", override?.model)} \u2014 starting`
36575
+ `${reviewerLabel(name, override?.model)} \u2014 starting`
36649
36576
  );
36650
36577
  return runSlot(args.slot?.harness ?? "claude", {
36651
- name: "claude",
36578
+ name,
36652
36579
  reviewDir: args.reviewDir,
36653
36580
  stdin: args.stdin,
36654
36581
  outputPath: args.claudePath,
@@ -36663,12 +36590,13 @@ function resolveCodex(args) {
36663
36590
  if (args.plan.kind === "skipped") {
36664
36591
  return Promise.resolve(skippedCodexResult(args.codexPath));
36665
36592
  }
36593
+ const name = args.slot?.label ?? "codex";
36666
36594
  const override = args.slot?.override ?? buildCodexModelArgs();
36667
36595
  const spinner = args.multi?.create(
36668
- `${reviewerLabel("codex", override.model)} \u2014 starting`
36596
+ `${reviewerLabel(name, override.model)} \u2014 starting`
36669
36597
  );
36670
36598
  return runSlot(args.slot?.harness ?? "codex", {
36671
- name: "codex",
36599
+ name,
36672
36600
  reviewDir: args.reviewDir,
36673
36601
  stdin: args.stdin,
36674
36602
  outputPath: args.codexPath,
@@ -36701,7 +36629,7 @@ async function runReviewers(reviewDir, claudePath, codexPath, stdinPrompt, optio
36701
36629
  }
36702
36630
 
36703
36631
  // src/commands/review/synthesise.ts
36704
- import { readFileSync as readFileSync50 } from "fs";
36632
+ import { readFileSync as readFileSync51 } from "fs";
36705
36633
 
36706
36634
  // src/commands/review/buildSynthesisStdin.ts
36707
36635
  var SYNTHESIS_PROMPT = `You are consolidating two independent code reviews of the same change. The original review request is in request.md. The two reviews are in claude.md and codex.md in the current working directory.
@@ -36766,7 +36694,7 @@ Files:
36766
36694
 
36767
36695
  // src/commands/review/synthesise.ts
36768
36696
  function printSummary2(synthesisPath) {
36769
- const markdown = readFileSync50(synthesisPath, "utf8");
36697
+ const markdown = readFileSync51(synthesisPath, "utf8");
36770
36698
  console.log("");
36771
36699
  console.log(buildReviewSummary(markdown));
36772
36700
  console.log("");
@@ -37025,9 +36953,9 @@ function buildReviewPaths(repoRoot2, key) {
37025
36953
  }
37026
36954
 
37027
36955
  // src/commands/review/fetchExistingComments.ts
37028
- import { execSync as execSync60 } from "child_process";
36956
+ import { execSync as execSync58 } from "child_process";
37029
36957
  function fetchRawComments(org, repo, prNumber) {
37030
- const out = execSync60(
36958
+ const out = execSync58(
37031
36959
  `gh api --paginate repos/${org}/${repo}/pulls/${prNumber}/comments`,
37032
36960
  { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }
37033
36961
  );
@@ -37087,7 +37015,93 @@ function logPriorComments(count8) {
37087
37015
  console.log(`Including ${count8} prior review comment(s) in request.md.`);
37088
37016
  }
37089
37017
 
37090
- // src/commands/review/reviewPr.ts
37018
+ // src/commands/review/gatherContext.ts
37019
+ import { execSync as execSync60 } from "child_process";
37020
+
37021
+ // src/commands/review/fetchPrDiffInfo.ts
37022
+ import { execSync as execSync59 } from "child_process";
37023
+ function getCurrentBranch3() {
37024
+ return execSync59("git rev-parse --abbrev-ref HEAD", {
37025
+ encoding: "utf8"
37026
+ }).trim();
37027
+ }
37028
+ var FIELDS3 = "number,baseRefName,baseRefOid,headRefName,headRefOid";
37029
+ function runGh2(command) {
37030
+ return execSync59(command, {
37031
+ encoding: "utf8",
37032
+ stdio: ["ignore", "pipe", "pipe"]
37033
+ });
37034
+ }
37035
+ function fetchRawPr(org, repo, branch2) {
37036
+ const pinned = getPinnedPr();
37037
+ if (pinned !== void 0)
37038
+ return JSON.parse(
37039
+ runGh2(`gh pr view ${pinned} --json ${FIELDS3} -R ${org}/${repo}`)
37040
+ );
37041
+ const parsed = JSON.parse(
37042
+ runGh2(
37043
+ `gh pr list --state open --head ${branch2} --json ${FIELDS3} -R ${org}/${repo}`
37044
+ )
37045
+ );
37046
+ return parsed[0];
37047
+ }
37048
+ function fetchPrDiffInfo() {
37049
+ const { org, repo } = getRepoInfo();
37050
+ const branch2 = getCurrentBranch3();
37051
+ const pr = fetchRawPr(org, repo, branch2);
37052
+ if (!pr) {
37053
+ console.error(
37054
+ `Error: No open pull request found for branch \`${branch2}\`. Open a PR for this branch before running \`assist review\`.`
37055
+ );
37056
+ process.exit(1);
37057
+ }
37058
+ return {
37059
+ prNumber: pr.number,
37060
+ baseRef: pr.baseRefName,
37061
+ baseSha: pr.baseRefOid,
37062
+ headRef: pr.headRefName,
37063
+ headSha: pr.headRefOid
37064
+ };
37065
+ }
37066
+ function fetchPrChangedFiles(prNumber) {
37067
+ const { org, repo } = getRepoInfo();
37068
+ const out = execSync59(
37069
+ `gh api repos/${org}/${repo}/pulls/${prNumber}/files --paginate --jq ".[].filename"`,
37070
+ {
37071
+ encoding: "utf8",
37072
+ maxBuffer: 64 * 1024 * 1024
37073
+ }
37074
+ );
37075
+ return out.trim().split("\n").filter(Boolean);
37076
+ }
37077
+
37078
+ // src/commands/review/gatherContext.ts
37079
+ function gatherContext() {
37080
+ const branch2 = execSync60("git rev-parse --abbrev-ref HEAD", {
37081
+ encoding: "utf8"
37082
+ }).trim();
37083
+ const sha = execSync60("git rev-parse HEAD", { encoding: "utf8" }).trim();
37084
+ const shortSha = execSync60("git rev-parse --short=7 HEAD", {
37085
+ encoding: "utf8"
37086
+ }).trim();
37087
+ const prInfo = fetchPrDiffInfo();
37088
+ const changedFiles2 = fetchPrChangedFiles(prInfo.prNumber);
37089
+ const diff3 = fetchPrDiff(prInfo.prNumber, prInfo.baseSha, prInfo.headSha);
37090
+ return {
37091
+ branch: branch2,
37092
+ sha,
37093
+ shortSha,
37094
+ prNumber: prInfo.prNumber,
37095
+ baseRef: prInfo.baseRef,
37096
+ baseSha: prInfo.baseSha,
37097
+ headRef: prInfo.headRef,
37098
+ headSha: prInfo.headSha,
37099
+ changedFiles: changedFiles2,
37100
+ diff: diff3
37101
+ };
37102
+ }
37103
+
37104
+ // src/commands/review/gatherChangedContext.ts
37091
37105
  function gatherChangedContext() {
37092
37106
  const context = gatherContext();
37093
37107
  if (context.changedFiles.length > 0) return context;
@@ -37096,6 +37110,8 @@ function gatherChangedContext() {
37096
37110
  );
37097
37111
  process.exit(1);
37098
37112
  }
37113
+
37114
+ // src/commands/review/reviewPr.ts
37099
37115
  function runPostSynthesis(synthesisPath, prInfo, options2) {
37100
37116
  return handlePostSynthesis(synthesisPath, prInfo, {
37101
37117
  refine: options2.refine ?? false,
@@ -37120,6 +37136,8 @@ async function reviewPr(repoRoot2, options2) {
37120
37136
  console.error("Review failed; nothing was posted.");
37121
37137
  process.exit(1);
37122
37138
  }
37139
+ if (synthesisOk && options2.ci)
37140
+ writeJobSummary(paths.synthesisPath, context, options2.ci.summaryPath);
37123
37141
  if (synthesisOk)
37124
37142
  await runPostSynthesis(paths.synthesisPath, context, options2);
37125
37143
  console.log(`Done. Review folder: ${paths.reviewDir}`);
@@ -37290,7 +37308,7 @@ function registerReview(program2) {
37290
37308
  }
37291
37309
 
37292
37310
  // src/commands/rules/addRule.ts
37293
- import { existsSync as existsSync71, readFileSync as readFileSync54, writeFileSync as writeFileSync44 } from "fs";
37311
+ import { existsSync as existsSync71, readFileSync as readFileSync55, writeFileSync as writeFileSync44 } from "fs";
37294
37312
  import path85 from "path";
37295
37313
  import chalk240 from "chalk";
37296
37314
 
@@ -37315,7 +37333,7 @@ function insertRuleBullet(content, rule) {
37315
37333
  }
37316
37334
 
37317
37335
  // src/commands/rules/nextRuleCode.ts
37318
- import { readFileSync as readFileSync51 } from "fs";
37336
+ import { readFileSync as readFileSync52 } from "fs";
37319
37337
 
37320
37338
  // src/commands/rules/findClaudeFiles.ts
37321
37339
  import { readdirSync as readdirSync13 } from "fs";
@@ -37339,7 +37357,7 @@ function findClaudeFiles(dir) {
37339
37357
  var CODE_PREFIX = "R";
37340
37358
  function nextRuleCode(root2) {
37341
37359
  const numbers = findClaudeFiles(root2).flatMap(
37342
- (file) => parseRulesSection(readFileSync51(file, "utf8")).map(
37360
+ (file) => parseRulesSection(readFileSync52(file, "utf8")).map(
37343
37361
  (rule) => Number(/(\d+)\s*$/.exec(rule.code)?.[1] ?? 0)
37344
37362
  )
37345
37363
  );
@@ -37365,15 +37383,15 @@ function resolveRuleScope(target) {
37365
37383
  }
37366
37384
 
37367
37385
  // src/commands/rules/updateScopedRulesIndex.ts
37368
- import { existsSync as existsSync70, readFileSync as readFileSync53, writeFileSync as writeFileSync43 } from "fs";
37386
+ import { existsSync as existsSync70, readFileSync as readFileSync54, writeFileSync as writeFileSync43 } from "fs";
37369
37387
  import path84 from "path";
37370
37388
 
37371
37389
  // src/commands/rules/scopedRuleDirectories.ts
37372
- import { readFileSync as readFileSync52 } from "fs";
37390
+ import { readFileSync as readFileSync53 } from "fs";
37373
37391
  import path83 from "path";
37374
37392
  function scopedRuleDirectories(root2) {
37375
37393
  return findClaudeFiles(root2).filter(
37376
- (file) => path83.dirname(file) !== root2 && parseRulesSection(readFileSync52(file, "utf8")).length > 0
37394
+ (file) => path83.dirname(file) !== root2 && parseRulesSection(readFileSync53(file, "utf8")).length > 0
37377
37395
  ).map(
37378
37396
  (file) => `${path83.relative(root2, path83.dirname(file)).split(path83.sep).join("/")}/`
37379
37397
  ).sort();
@@ -37415,7 +37433,7 @@ function upsertScopedRulesPointer(content, directories) {
37415
37433
  function updateScopedRulesIndex(root2) {
37416
37434
  const directories = scopedRuleDirectories(root2);
37417
37435
  const rootFile = path84.join(root2, "CLAUDE.md");
37418
- const before = existsSync70(rootFile) ? readFileSync53(rootFile, "utf8") : "";
37436
+ const before = existsSync70(rootFile) ? readFileSync54(rootFile, "utf8") : "";
37419
37437
  const after = upsertScopedRulesPointer(before, directories);
37420
37438
  if (after !== before) writeFileSync43(rootFile, after);
37421
37439
  return directories;
@@ -37423,7 +37441,7 @@ function updateScopedRulesIndex(root2) {
37423
37441
 
37424
37442
  // src/commands/rules/addRule.ts
37425
37443
  function read2(file) {
37426
- return existsSync71(file) ? readFileSync54(file, "utf8") : "";
37444
+ return existsSync71(file) ? readFileSync55(file, "utf8") : "";
37427
37445
  }
37428
37446
  function addRule(text18, options2) {
37429
37447
  const rule = text18.trim();
@@ -37958,12 +37976,12 @@ import { mkdirSync as mkdirSync27, writeFileSync as writeFileSync46 } from "fs";
37958
37976
  import { mkdirSync as mkdirSync26, writeFileSync as writeFileSync45 } from "fs";
37959
37977
 
37960
37978
  // src/commands/slack/readSlackParts.ts
37961
- import { readFileSync as readFileSync55 } from "fs";
37979
+ import { readFileSync as readFileSync56 } from "fs";
37962
37980
  function readSlackParts(files) {
37963
37981
  return files.map((file) => {
37964
37982
  let contents;
37965
37983
  try {
37966
- contents = readFileSync55(file, "utf8");
37984
+ contents = readFileSync56(file, "utf8");
37967
37985
  } catch {
37968
37986
  console.error(`Error: Part file not found: ${file}`);
37969
37987
  process.exit(1);
@@ -39074,9 +39092,9 @@ function formatVttPassages(passages, notes = [], { sourceMarks = true } = {}) {
39074
39092
  }
39075
39093
 
39076
39094
  // src/commands/transcript/convert/readCleanedCues.ts
39077
- import { readFileSync as readFileSync60 } from "fs";
39095
+ import { readFileSync as readFileSync61 } from "fs";
39078
39096
  function readCleanedCues(inputPath) {
39079
- return deduplicateCues(parseVtt(readFileSync60(inputPath, "utf8")));
39097
+ return deduplicateCues(parseVtt(readFileSync61(inputPath, "utf8")));
39080
39098
  }
39081
39099
 
39082
39100
  // src/commands/transcript/clean.ts
@@ -39850,7 +39868,7 @@ function configKeys2() {
39850
39868
  }
39851
39869
 
39852
39870
  // src/commands/verify/forbiddenStrings.ts
39853
- import { existsSync as existsSync80, readFileSync as readFileSync61 } from "fs";
39871
+ import { existsSync as existsSync80, readFileSync as readFileSync62 } from "fs";
39854
39872
 
39855
39873
  // src/commands/verify/forbiddenStrings/findForbiddenStrings.ts
39856
39874
  import { minimatch as minimatch9 } from "minimatch";
@@ -39897,7 +39915,7 @@ function forbiddenStrings() {
39897
39915
  }
39898
39916
  let parsed;
39899
39917
  try {
39900
- parsed = JSON.parse(readFileSync61(file, "utf8"));
39918
+ parsed = JSON.parse(readFileSync62(file, "utf8"));
39901
39919
  } catch (error) {
39902
39920
  console.log(`Could not parse ${file}: ${error.message}`);
39903
39921
  process.exit(1);
@@ -39967,7 +39985,7 @@ Total: ${lines2.length} hardcoded color(s)`);
39967
39985
  }
39968
39986
 
39969
39987
  // src/commands/verify/migrations.ts
39970
- import { readFileSync as readFileSync62 } from "fs";
39988
+ import { readFileSync as readFileSync63 } from "fs";
39971
39989
  import path101 from "path";
39972
39990
 
39973
39991
  // src/commands/verify/migrations/checkAppendOnly.ts
@@ -40132,7 +40150,7 @@ function migrations2() {
40132
40150
  if (ref) {
40133
40151
  const baseline = readBaselineMigrations(REPO_RELATIVE_DIR, ref);
40134
40152
  const current = new Map(
40135
- files.map((file) => [file, readFileSync62(path101.join(dir, file), "utf8")])
40153
+ files.map((file) => [file, readFileSync63(path101.join(dir, file), "utf8")])
40136
40154
  );
40137
40155
  for (const finding of checkAppendOnly(baseline, current)) {
40138
40156
  problems.push(
@@ -41154,7 +41172,7 @@ async function auth() {
41154
41172
 
41155
41173
  // src/commands/roam/postRoamActivity.ts
41156
41174
  import { execFileSync as execFileSync22 } from "child_process";
41157
- import { readdirSync as readdirSync22, readFileSync as readFileSync63, statSync as statSync13 } from "fs";
41175
+ import { readdirSync as readdirSync22, readFileSync as readFileSync64, statSync as statSync13 } from "fs";
41158
41176
  import { join as join99 } from "path";
41159
41177
  function findPortFile(roamDir) {
41160
41178
  let entries;
@@ -41185,7 +41203,7 @@ function postRoamActivity(app, event) {
41185
41203
  if (!portFile) return;
41186
41204
  let port;
41187
41205
  try {
41188
- port = readFileSync63(portFile, "utf8").trim();
41206
+ port = readFileSync64(portFile, "utf8").trim();
41189
41207
  } catch {
41190
41208
  return;
41191
41209
  }
@@ -41654,11 +41672,11 @@ function applyLine(result, pending, line) {
41654
41672
  }
41655
41673
 
41656
41674
  // src/commands/sessions/daemon/readDaemonPidFile.ts
41657
- import { readFileSync as readFileSync64 } from "fs";
41675
+ import { readFileSync as readFileSync65 } from "fs";
41658
41676
  function readDaemonPidFile() {
41659
41677
  try {
41660
41678
  const pid = Number.parseInt(
41661
- readFileSync64(daemonPaths.pid, "utf8").trim(),
41679
+ readFileSync65(daemonPaths.pid, "utf8").trim(),
41662
41680
  10
41663
41681
  );
41664
41682
  return Number.isInteger(pid) ? pid : void 0;
@@ -42715,14 +42733,14 @@ async function runAutoUpdateLoop(deps2, maxLaps = Number.POSITIVE_INFINITY) {
42715
42733
  }
42716
42734
 
42717
42735
  // src/commands/sessions/daemon/autoUpdate/trimWatcherLog.ts
42718
- import { readFileSync as readFileSync66, writeFileSync as writeFileSync56 } from "fs";
42736
+ import { readFileSync as readFileSync67, writeFileSync as writeFileSync56 } from "fs";
42719
42737
  var KEEP_TAIL = 256 * 1024;
42720
42738
  var MAX_LOG_SIZE = 4 * 1024 * 1024;
42721
42739
  function trimWatcherLog(cwd) {
42722
42740
  const path106 = watcherLogPath(cwd);
42723
42741
  let log2;
42724
42742
  try {
42725
- log2 = readFileSync66(path106, "utf8");
42743
+ log2 = readFileSync67(path106, "utf8");
42726
42744
  } catch {
42727
42745
  return;
42728
42746
  }
@@ -42732,13 +42750,13 @@ function trimWatcherLog(cwd) {
42732
42750
  }
42733
42751
 
42734
42752
  // src/commands/sessions/daemon/worktree/appendWatcherLog.ts
42735
- import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync35 } from "fs";
42753
+ import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync35 } from "fs";
42736
42754
  import { dirname as dirname43 } from "path";
42737
42755
  function appendWatcherLog(cwd, data) {
42738
42756
  try {
42739
42757
  const path106 = watcherLogPath(cwd);
42740
42758
  mkdirSync35(dirname43(path106), { recursive: true });
42741
- appendFileSync3(path106, data);
42759
+ appendFileSync4(path106, data);
42742
42760
  } catch {
42743
42761
  }
42744
42762
  }
@@ -48304,7 +48322,7 @@ function handleConnection(socket, manager) {
48304
48322
  import { unlinkSync as unlinkSync22, writeFileSync as writeFileSync58 } from "fs";
48305
48323
 
48306
48324
  // src/commands/sessions/daemon/startPidFileWatchdog.ts
48307
- import { readFileSync as readFileSync67 } from "fs";
48325
+ import { readFileSync as readFileSync68 } from "fs";
48308
48326
  var WATCHDOG_INTERVAL_MS = 5e3;
48309
48327
  function startPidFileWatchdog(onLost, intervalMs = WATCHDOG_INTERVAL_MS) {
48310
48328
  const timer = setInterval(() => {
@@ -48315,7 +48333,7 @@ function startPidFileWatchdog(onLost, intervalMs = WATCHDOG_INTERVAL_MS) {
48315
48333
  }
48316
48334
  function ownsPidFile() {
48317
48335
  try {
48318
- return readFileSync67(daemonPaths.pid, "utf8").trim() === String(process.pid);
48336
+ return readFileSync68(daemonPaths.pid, "utf8").trim() === String(process.pid);
48319
48337
  } catch {
48320
48338
  return false;
48321
48339
  }