@staff0rd/assist 0.522.1 → 0.523.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/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.522.1",
9
+ version: "0.523.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -25218,6 +25218,11 @@ function gatherContext() {
25218
25218
  // src/commands/review/postReviewToPr.ts
25219
25219
  import { readFileSync as readFileSync41 } from "fs";
25220
25220
 
25221
+ // src/commands/review/carriedUnanchoredFindings.ts
25222
+ function carriedUnanchoredFindings(unanchored) {
25223
+ return unanchored.filter((finding) => finding.source !== "already-raised");
25224
+ }
25225
+
25221
25226
  // src/commands/review/chainAddressComments.ts
25222
25227
  function chainAddressComments(prNumber, announce) {
25223
25228
  const args = ["review-pr-comments", String(prNumber)];
@@ -25317,11 +25322,30 @@ function formatSynthesisSummary(summary) {
25317
25322
  }
25318
25323
 
25319
25324
  // src/commands/review/buildReviewSummary.ts
25325
+ var UNANCHOR_REASONS = {
25326
+ "out-of-diff": "its line falls outside the PR diff",
25327
+ unlocated: "it has no parseable file:line"
25328
+ };
25320
25329
  function formatFindingLine(finding) {
25321
25330
  const severity = finding.severity ?? "finding";
25322
25331
  return `- **${severity}: ${finding.title}**`;
25323
25332
  }
25324
- function buildReviewSummary(markdown) {
25333
+ function formatLocation(finding) {
25334
+ const location = finding.location.trim();
25335
+ if (!location || location.toLowerCase() === "n/a") return "no location";
25336
+ return `\`${location}\``;
25337
+ }
25338
+ function formatUnanchoredFinding(finding) {
25339
+ const lines2 = [
25340
+ `${formatFindingLine(finding)} \u2014 ${formatLocation(finding)}`,
25341
+ ` - Not anchored: ${UNANCHOR_REASONS[finding.reason]}`
25342
+ ];
25343
+ if (finding.impact) lines2.push(` - Impact: ${finding.impact}`);
25344
+ if (finding.recommendation)
25345
+ lines2.push(` - Recommendation: ${finding.recommendation}`);
25346
+ return lines2;
25347
+ }
25348
+ function buildReviewSummary(markdown, unanchored = []) {
25325
25349
  const summary = summariseSynthesis(markdown);
25326
25350
  const findings = parseFindings(markdown);
25327
25351
  const lines2 = ["## Code review summary", "", formatSynthesisSummary(summary)];
@@ -25329,6 +25353,18 @@ function buildReviewSummary(markdown) {
25329
25353
  lines2.push("", "### Findings", "");
25330
25354
  for (const finding of findings) lines2.push(formatFindingLine(finding));
25331
25355
  }
25356
+ const carried = carriedUnanchoredFindings(unanchored);
25357
+ if (carried.length > 0) {
25358
+ lines2.push(
25359
+ "",
25360
+ "### Findings not anchored to the diff",
25361
+ "",
25362
+ "No line comment could be attached to these, so the detail is here instead.",
25363
+ ""
25364
+ );
25365
+ for (const finding of carried)
25366
+ lines2.push(...formatUnanchoredFinding(finding));
25367
+ }
25332
25368
  return lines2.join("\n");
25333
25369
  }
25334
25370
 
@@ -25367,13 +25403,12 @@ function postFindings(findings) {
25367
25403
  return { posted, failed: failed2 };
25368
25404
  }
25369
25405
 
25370
- // src/commands/review/submitPendingReview.ts
25371
- var MUTATION = `mutation($prId: ID!, $body: String) { submitPullRequestReview(input: { pullRequestId: $prId, event: COMMENT, body: $body }) { pullRequestReview { id } } }`;
25372
- function submitPendingReview(body) {
25406
+ // src/commands/review/runReviewSubmission.ts
25407
+ function runReviewSubmission(mutation, body) {
25373
25408
  try {
25374
25409
  const prId = getCurrentPrNodeId();
25375
- runGhGraphql(MUTATION, { prId, body });
25376
- console.log("Submitted pending review.");
25410
+ runGhGraphql(mutation, { prId, body });
25411
+ console.log("Submitted review.");
25377
25412
  } catch (error) {
25378
25413
  if (isGhNotInstalled(error)) {
25379
25414
  console.error("Error: GitHub CLI (gh) is not installed.");
@@ -25384,9 +25419,25 @@ function submitPendingReview(body) {
25384
25419
  }
25385
25420
  }
25386
25421
 
25422
+ // src/commands/review/submitBodyOnlyReview.ts
25423
+ var MUTATION = `mutation($prId: ID!, $body: String) { addPullRequestReview(input: { pullRequestId: $prId, event: COMMENT, body: $body }) { pullRequestReview { id } } }`;
25424
+ function submitBodyOnlyReview(body) {
25425
+ runReviewSubmission(MUTATION, body);
25426
+ }
25427
+
25428
+ // src/commands/review/submitPendingReview.ts
25429
+ var MUTATION2 = `mutation($prId: ID!, $body: String) { submitPullRequestReview(input: { pullRequestId: $prId, event: COMMENT, body: $body }) { pullRequestReview { id } } }`;
25430
+ function submitPendingReview(body) {
25431
+ runReviewSubmission(MUTATION2, body);
25432
+ }
25433
+
25387
25434
  // src/commands/review/postAndMaybeSubmit.ts
25388
- function buildReviewBody(markdown) {
25389
- return sanitiseReviewerNames(buildReviewSummary(markdown));
25435
+ function buildReviewBody(markdown, unanchored) {
25436
+ return sanitiseReviewerNames(buildReviewSummary(markdown, unanchored));
25437
+ }
25438
+ function submitReview(body, posted) {
25439
+ if (posted > 0) submitPendingReview(body);
25440
+ else submitBodyOnlyReview(body);
25390
25441
  }
25391
25442
  async function decideSubmit(options2) {
25392
25443
  if (!options2.prompt) return options2.submit;
@@ -25397,17 +25448,19 @@ async function decideSubmit(options2) {
25397
25448
  await setSessionStatus("running");
25398
25449
  }
25399
25450
  }
25400
- async function postAndMaybeSubmit(lineBound, markdown, options2) {
25451
+ async function postAndMaybeSubmit(lineBound, unanchored, markdown, options2) {
25401
25452
  const result = postFindings(lineBound);
25402
25453
  const failedSuffix = result.failed > 0 ? `, ${result.failed} failed` : "";
25403
25454
  console.log(`Posted ${result.posted} comment(s)${failedSuffix}.`);
25404
- if (result.posted === 0) return { posted: 0, submitted: false };
25455
+ const carried = carriedUnanchoredFindings(unanchored);
25456
+ if (result.posted === 0 && carried.length === 0)
25457
+ return { posted: 0, submitted: false };
25405
25458
  const shouldSubmit = await decideSubmit(options2);
25406
25459
  if (shouldSubmit) {
25407
- submitPendingReview(buildReviewBody(markdown));
25460
+ submitReview(buildReviewBody(markdown, unanchored), result.posted);
25408
25461
  return { posted: result.posted, submitted: true };
25409
25462
  }
25410
- console.log("Leaving pending review unsubmitted.");
25463
+ console.log("Leaving the review unsubmitted.");
25411
25464
  return { posted: result.posted, submitted: false };
25412
25465
  }
25413
25466
 
@@ -25503,7 +25556,7 @@ function warnOutOfDiff(outOfDiff) {
25503
25556
  if (outOfDiff.length === 0) return;
25504
25557
  console.warn(
25505
25558
  chalk196.yellow(
25506
- `Skipped ${outOfDiff.length} finding(s) whose lines fall outside the PR diff (GitHub would silently drop these):`
25559
+ `Moved ${outOfDiff.length} finding(s) whose lines fall outside the PR diff into the review body (GitHub cannot anchor a comment on these):`
25507
25560
  )
25508
25561
  );
25509
25562
  for (const finding of outOfDiff) {
@@ -25524,7 +25577,12 @@ function selectInDiffFindings(lineBound, prDiff) {
25524
25577
  buildDiffLineIndex(diff3)
25525
25578
  );
25526
25579
  warnOutOfDiff(outOfDiff);
25527
- return inDiff;
25580
+ return {
25581
+ inDiff,
25582
+ unanchored: outOfDiff.map(
25583
+ (finding) => ({ ...finding, reason: "out-of-diff" })
25584
+ )
25585
+ };
25528
25586
  }
25529
25587
 
25530
25588
  // src/commands/review/warnUnlocated.ts
@@ -25533,7 +25591,7 @@ function warnUnlocated(unlocated) {
25533
25591
  if (unlocated.length === 0) return;
25534
25592
  console.warn(
25535
25593
  chalk197.yellow(
25536
- `Skipped ${unlocated.length} finding(s) without a parseable file:line:`
25594
+ `Moved ${unlocated.length} finding(s) without a parseable file:line into the review body:`
25537
25595
  )
25538
25596
  );
25539
25597
  for (const finding of unlocated) {
@@ -25549,7 +25607,7 @@ function selectPostableFindings(markdown, prInfo) {
25549
25607
  const findings = parseFindings(markdown);
25550
25608
  if (findings.length === 0) {
25551
25609
  console.log("Synthesis contains no findings; nothing to post.");
25552
- return [];
25610
+ return { inDiff: [], unanchored: [] };
25553
25611
  }
25554
25612
  const { lineBound, unlocated, alreadyRaised } = partitionFindings(findings);
25555
25613
  warnUnlocated(unlocated);
@@ -25558,14 +25616,19 @@ function selectPostableFindings(markdown, prInfo) {
25558
25616
  `Skipped ${alreadyRaised.length} finding(s) already raised by prior comments.`
25559
25617
  );
25560
25618
  }
25619
+ const carriedUnlocated = unlocated.map(
25620
+ (finding) => ({ ...finding, reason: "unlocated" })
25621
+ );
25561
25622
  if (lineBound.length === 0) {
25562
- console.log("No line-bound findings to post.");
25563
- return [];
25623
+ console.log("No line-bound findings to comment on.");
25624
+ return { inDiff: [], unanchored: carriedUnlocated };
25564
25625
  }
25565
- const inDiff = selectInDiffFindings(lineBound, prInfo);
25626
+ const { inDiff, unanchored } = selectInDiffFindings(lineBound, prInfo);
25566
25627
  if (inDiff.length === 0)
25567
- console.log("No findings fall within the PR diff; nothing to post.");
25568
- return inDiff;
25628
+ console.log(
25629
+ "No findings fall within the PR diff; no line comments to post."
25630
+ );
25631
+ return { inDiff, unanchored: [...carriedUnlocated, ...unanchored] };
25569
25632
  }
25570
25633
 
25571
25634
  // src/commands/review/stillOnReviewedPr.ts
@@ -25590,23 +25653,29 @@ function stillOnReviewedPr(prNumber) {
25590
25653
 
25591
25654
  // src/commands/review/postReviewToPr.ts
25592
25655
  var NOTHING_POSTED = { posted: 0, submitted: false };
25593
- async function confirmPost(prNumber, count8, options2) {
25656
+ function describeWork(comments3, carried) {
25657
+ const parts = [];
25658
+ if (comments3 > 0) parts.push(`${comments3} line comment(s)`);
25659
+ if (carried > 0) parts.push(`${carried} finding(s) in the review body`);
25660
+ return parts.join(" and ");
25661
+ }
25662
+ async function confirmPost(prNumber, work, options2) {
25594
25663
  if (!options2.prompt) return true;
25595
- return promptConfirm(`Post ${count8} comment(s) to PR #${prNumber}?`, false);
25664
+ return promptConfirm(`Post ${work} to PR #${prNumber}?`, false);
25596
25665
  }
25597
25666
  async function postFindingsToPr(prInfo, synthesisPath, options2) {
25598
25667
  const markdown = readFileSync41(synthesisPath, "utf8");
25599
- const inDiff = selectPostableFindings(markdown, prInfo);
25600
- if (inDiff.length === 0) return NOTHING_POSTED;
25601
- console.log(
25602
- `Found PR #${prInfo.prNumber} with ${inDiff.length} line-bound finding(s) in the diff.`
25603
- );
25604
- const confirmed = await confirmPost(prInfo.prNumber, inDiff.length, options2);
25668
+ const { inDiff, unanchored } = selectPostableFindings(markdown, prInfo);
25669
+ const carried = carriedUnanchoredFindings(unanchored);
25670
+ if (inDiff.length === 0 && carried.length === 0) return NOTHING_POSTED;
25671
+ const work = describeWork(inDiff.length, carried.length);
25672
+ console.log(`Found PR #${prInfo.prNumber} with ${work} to post.`);
25673
+ const confirmed = await confirmPost(prInfo.prNumber, work, options2);
25605
25674
  if (!confirmed) {
25606
25675
  console.log("Skipped posting.");
25607
25676
  return NOTHING_POSTED;
25608
25677
  }
25609
- return postAndMaybeSubmit(inDiff, markdown, options2);
25678
+ return postAndMaybeSubmit(inDiff, unanchored, markdown, options2);
25610
25679
  }
25611
25680
  async function postReviewToPr(synthesisPath, prInfo, options2) {
25612
25681
  if (!stillOnReviewedPr(prInfo.prNumber)) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.522.1",
3
+ "version": "0.523.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {