@jphutchins/code-review 0.1.0-alpha.32 → 0.1.0-alpha.33

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
@@ -1068,9 +1068,9 @@ var formatErrors = (errors) => errors.map(describeValidationError);
1068
1068
  var declaredVersion = (raw) => typeof raw === "object" && raw !== null && "schema_version" in raw ? typeof raw.schema_version === "string" ? raw.schema_version : void 0 : void 0;
1069
1069
  var supportedVersions = (kind) => tableFor(kind).map((entry) => entry.minor);
1070
1070
  var defaultVersion = (kind) => {
1071
- const latest = tableFor(kind).find((entry) => entry.latest);
1072
- if (!latest) throw new Error(`Registry invariant violated \u2014 no latest entry for "${kind}"`);
1073
- return latest.defaultVersion;
1071
+ const latest2 = tableFor(kind).find((entry) => entry.latest);
1072
+ if (!latest2) throw new Error(`Registry invariant violated \u2014 no latest entry for "${kind}"`);
1073
+ return latest2.defaultVersion;
1074
1074
  };
1075
1075
  var bundledSchemaPath = (relativePath) => resolve$1(import.meta.dirname, "..", "schema", relativePath);
1076
1076
  var schemaPathFor = (kind, version) => {
@@ -1759,6 +1759,134 @@ var announce = async (input, ghApi = runGhApi) => {
1759
1759
  ghApi
1760
1760
  );
1761
1761
  };
1762
+ var incompleteBody = (headSha, runUrl, existingBody) => {
1763
+ const notice = `${DEFAULT_MARKER}
1764
+
1765
+ \u26A0\uFE0F **Code review did not complete** for \`${headSha.slice(0, 7)}\` \u2014 the review job failed ([run](${runUrl})). Re-request the review; do not treat this round as spent.`;
1766
+ const carried = existingBody ? carryForwardMarkers(existingBody) : "";
1767
+ return carried ? `${notice}
1768
+
1769
+ ${carried}` : notice;
1770
+ };
1771
+ var reportIncomplete = async (input, ghApi = runGhApi) => {
1772
+ const candidates = await fetchPrCandidates(input.repo, input.headSha, ghApi);
1773
+ const resolution = resolvePr(candidates, input.headBranch);
1774
+ if (resolution.kind !== "open") {
1775
+ process.stderr.write(
1776
+ `No open PR for ${input.headSha} \u2014 nothing to report (${resolution.kind})
1777
+ `
1778
+ );
1779
+ return;
1780
+ }
1781
+ const existing = await findBotComment(
1782
+ input.repo,
1783
+ resolution.prNumber,
1784
+ input.botLogin,
1785
+ DEFAULT_MARKER,
1786
+ ghApi
1787
+ );
1788
+ if (existing !== null && parseReviewComplete(existing.body)) {
1789
+ process.stderr.write(`Sticky already reflects a completed review \u2014 leaving it in place
1790
+ `);
1791
+ return;
1792
+ }
1793
+ if (existing !== null && !existing.body.includes(input.runUrl)) {
1794
+ process.stderr.write(`Sticky belongs to another run \u2014 leaving it in place
1795
+ `);
1796
+ return;
1797
+ }
1798
+ await upsertSticky(
1799
+ input.repo,
1800
+ resolution.prNumber,
1801
+ existing,
1802
+ incompleteBody(input.headSha, input.runUrl, existing?.body),
1803
+ ghApi
1804
+ );
1805
+ };
1806
+
1807
+ // src/checkrun.ts
1808
+ var CHECK_RUN_NAME = "Code review";
1809
+ var latest = (checks) => checks.reduce(
1810
+ (best, c) => best === null || c.id > best.id ? c : best,
1811
+ null
1812
+ );
1813
+ var isOpen = (status) => status === "in_progress" || status === "queued";
1814
+ var settled = /* @__PURE__ */ new Set(["success", "neutral", "skipped"]);
1815
+ var decideCheckAction = (checks, intent) => {
1816
+ const head = latest(checks);
1817
+ switch (intent) {
1818
+ case "in_progress":
1819
+ return head !== null && isOpen(head.status) ? { kind: "noop", reason: "a check is already in progress for this head" } : { kind: "create", status: "in_progress" };
1820
+ case "neutral":
1821
+ if (head === null) return { kind: "create", status: "completed", conclusion: "neutral" };
1822
+ return head.status === "completed" && head.conclusion === "neutral" ? { kind: "noop", reason: "the check already records this completed review" } : { kind: "patch", id: head.id, status: "completed", conclusion: "neutral" };
1823
+ case "failure":
1824
+ if (head === null) return { kind: "create", status: "completed", conclusion: "failure" };
1825
+ if (head.status === "completed" && head.conclusion !== null && settled.has(head.conclusion))
1826
+ return { kind: "noop", reason: "a completed review already recorded this head" };
1827
+ return head.status === "completed" && head.conclusion === "failure" ? { kind: "noop", reason: "the check already records this failure" } : { kind: "patch", id: head.id, status: "completed", conclusion: "failure" };
1828
+ }
1829
+ };
1830
+ var CHECK_JQ = ".check_runs[] | {id: .id, status: .status, conclusion: .conclusion}";
1831
+ var fetchChecks = async (repo, headSha, ghApi) => parseJsonl(
1832
+ await ghApi([
1833
+ `repos/${repo}/commits/${headSha}/check-runs?check_name=${encodeURIComponent(CHECK_RUN_NAME)}&per_page=100`,
1834
+ "--paginate",
1835
+ "--jq",
1836
+ CHECK_JQ
1837
+ ])
1838
+ );
1839
+ var output = (intent, runUrl) => {
1840
+ switch (intent) {
1841
+ case "in_progress":
1842
+ return {
1843
+ title: "Code review in progress",
1844
+ summary: `The review is running \u2014 [see the run](${runUrl}).`
1845
+ };
1846
+ case "neutral":
1847
+ return {
1848
+ title: "Code review complete",
1849
+ summary: `The review was posted \u2014 [see the run](${runUrl}).`
1850
+ };
1851
+ case "failure":
1852
+ return {
1853
+ title: "Code review did not complete",
1854
+ summary: `The review job failed \u2014 [see the run](${runUrl}). Re-request the review; do not treat this round as spent.`
1855
+ };
1856
+ }
1857
+ };
1858
+ var checkRun = async (input, ghApi = runGhApi) => {
1859
+ const action = decideCheckAction(
1860
+ await fetchChecks(input.repo, input.headSha, ghApi),
1861
+ input.intent
1862
+ );
1863
+ if (action.kind === "noop") {
1864
+ process.stderr.write(`code-review check-run: ${action.reason} \u2014 leaving it
1865
+ `);
1866
+ return;
1867
+ }
1868
+ const body = action.kind === "create" ? {
1869
+ name: CHECK_RUN_NAME,
1870
+ head_sha: input.headSha,
1871
+ status: action.status,
1872
+ details_url: input.runUrl,
1873
+ ...action.conclusion ? { conclusion: action.conclusion } : {},
1874
+ output: output(input.intent, input.runUrl)
1875
+ } : {
1876
+ status: action.status,
1877
+ conclusion: action.conclusion,
1878
+ details_url: input.runUrl,
1879
+ output: output(input.intent, input.runUrl)
1880
+ };
1881
+ const endpoint = action.kind === "create" ? [`--method`, `POST`, `repos/${input.repo}/check-runs`, `--input`, `-`] : [
1882
+ `--method`,
1883
+ `PATCH`,
1884
+ `repos/${input.repo}/check-runs/${String(action.id)}`,
1885
+ `--input`,
1886
+ `-`
1887
+ ];
1888
+ await ghApi(endpoint, JSON.stringify(body));
1889
+ };
1762
1890
  var DURATION_RE = /^(\d+)(h|m|s)$/;
1763
1891
  var USD_RE = /^\$(\d+(?:\.\d+)?)$/;
1764
1892
  var toSeconds = (n, unit) => unit === "h" ? n * 3600 : unit === "m" ? n * 60 : n;
@@ -1965,12 +2093,12 @@ var resolveCiRun = async (repo, headSha, workflowName, ghApi) => {
1965
2093
  `
1966
2094
  );
1967
2095
  }
1968
- const latest = runs.filter((r) => r.name === workflowName).reduce(
2096
+ const latest2 = runs.filter((r) => r.name === workflowName).reduce(
1969
2097
  (best, r) => best === null || r.run_number > best.run_number ? r : best,
1970
2098
  null
1971
2099
  );
1972
2100
  return {
1973
- run: latest === null ? null : { id: latest.id, status: latest.status ?? "unknown", conclusion: latest.conclusion },
2101
+ run: latest2 === null ? null : { id: latest2.id, status: latest2.status ?? "unknown", conclusion: latest2.conclusion },
1974
2102
  seenNames: [...new Set(runs.flatMap((r) => r.name === null ? [] : [r.name]))]
1975
2103
  };
1976
2104
  };
@@ -2722,7 +2850,7 @@ var renderCmd = defineCommand({
2722
2850
  const prices = decode(PriceMapCodec.decode(readJSON(priceResolution.path)), "prices");
2723
2851
  const template = readFileSync(templatePath, "utf-8");
2724
2852
  const testReport = args["test-report"] ? decode(TestSummaryCodec.decode(readJSON(args["test-report"])), "test report") : void 0;
2725
- const output = render({
2853
+ const output2 = render({
2726
2854
  findings,
2727
2855
  envelope,
2728
2856
  prices,
@@ -2734,7 +2862,7 @@ var renderCmd = defineCommand({
2734
2862
  testReport,
2735
2863
  postedAt: formatUtc(/* @__PURE__ */ new Date())
2736
2864
  });
2737
- process.stdout.write(output);
2865
+ process.stdout.write(output2);
2738
2866
  }
2739
2867
  });
2740
2868
  var inlineCmd = defineCommand({
@@ -2924,7 +3052,7 @@ var budgetHookCmd = defineCommand({
2924
3052
  const prices = args.prices ? tryReadPrices(args.prices) : null;
2925
3053
  const spentUsd = prices !== null && usage ? computeCost(usage.models, prices).totalCostUSD : null;
2926
3054
  const wallMs = args.wall ? parseWallMs(args.wall) : null;
2927
- const output = evaluateBudgetHook(input, {
3055
+ const output2 = evaluateBudgetHook(input, {
2928
3056
  spentUsd,
2929
3057
  budgetUsd: parseBudgetUsd(args["budget-usd"]),
2930
3058
  elapsedMs: anchoredElapsedMs({
@@ -2948,7 +3076,7 @@ var budgetHookCmd = defineCommand({
2948
3076
  });
2949
3077
  if (asRecord(input)?.["hook_event_name"] === "PostToolBatch" && !isSubagentHookInput(input))
2950
3078
  snapshotIfValid(draftPath);
2951
- process.stdout.write(`${JSON.stringify(output)}
3079
+ process.stdout.write(`${JSON.stringify(output2)}
2952
3080
  `);
2953
3081
  } catch (err) {
2954
3082
  process.stderr.write(`code-review budget-hook: degrading to no-op \u2014 ${errMsg(err)}
@@ -3762,6 +3890,92 @@ var announceCmd = defineCommand({
3762
3890
  );
3763
3891
  }
3764
3892
  });
3893
+ var isCheckIntent = (s) => s === "in_progress" || s === "neutral" || s === "failure";
3894
+ var checkRunCmd = defineCommand({
3895
+ meta: {
3896
+ name: "check-run",
3897
+ description: "Upsert the native 'Code review' check-run on the head SHA \u2014 the attribution surface that appears in the PR's own checks list and (writing to the base repo) works for fork PRs too. `in_progress` at review start, `neutral` when the review completes, `failure` when it didn't. Forward-only: `failure` never overwrites a completed review."
3898
+ },
3899
+ args: {
3900
+ repo: { type: "string", description: "Repository (owner/name)", required: true },
3901
+ "head-sha": {
3902
+ type: "string",
3903
+ description: "Head SHA the check-run is anchored to",
3904
+ required: true
3905
+ },
3906
+ status: {
3907
+ type: "positional",
3908
+ description: "One of: in_progress, neutral, failure",
3909
+ required: true
3910
+ },
3911
+ "run-url": {
3912
+ type: "string",
3913
+ description: "Workflow run URL the check-run's details link to",
3914
+ required: true
3915
+ }
3916
+ },
3917
+ run: async ({ args }) => {
3918
+ if (!isCheckIntent(args.status)) {
3919
+ process.stderr.write(
3920
+ `::warning::code-review check-run: unrecognized status "${annotationSafe(args.status)}" \u2014 expected in_progress, neutral, or failure; skipping
3921
+ `
3922
+ );
3923
+ return;
3924
+ }
3925
+ await checkRun({
3926
+ repo: args.repo,
3927
+ headSha: args["head-sha"],
3928
+ intent: args.status,
3929
+ runUrl: args["run-url"]
3930
+ }).catch(
3931
+ (err) => process.stderr.write(
3932
+ `::warning::code-review check-run: could not upsert the check-run (${annotationSafe(errMsg(err))}) \u2014 continuing (attribution aid)
3933
+ `
3934
+ )
3935
+ );
3936
+ }
3937
+ });
3938
+ var reportIncompleteCmd = defineCommand({
3939
+ meta: {
3940
+ name: "report-incomplete",
3941
+ description: "Post (or update) the sticky when a review job hard-failed and posted nothing \u2014 an attributed 'did not complete' notice linking the run, telling the reader to re-request. Never buries a completed review, and never overwrites a superseding run's live in-progress placeholder. (A cancelled review is left to the superseding run that took over.)"
3942
+ },
3943
+ args: {
3944
+ repo: { type: "string", description: "Repository (owner/name)", required: true },
3945
+ "head-sha": {
3946
+ type: "string",
3947
+ description: "Trusted head SHA to resolve the PR (from workflow_run.head_sha)",
3948
+ required: true
3949
+ },
3950
+ "run-url": {
3951
+ type: "string",
3952
+ description: "Workflow run URL the notice links to",
3953
+ required: true
3954
+ },
3955
+ "bot-login": {
3956
+ type: "string",
3957
+ description: "Bot login to trust for the sticky comment upsert (default: github-actions[bot])"
3958
+ },
3959
+ "head-branch": {
3960
+ type: "string",
3961
+ description: "Head branch to disambiguate the PR when multiple share a commit"
3962
+ }
3963
+ },
3964
+ run: async ({ args }) => {
3965
+ await reportIncomplete({
3966
+ repo: args.repo,
3967
+ headSha: args["head-sha"],
3968
+ botLogin: args["bot-login"] || "github-actions[bot]",
3969
+ runUrl: args["run-url"],
3970
+ headBranch: args["head-branch"]
3971
+ }).catch(
3972
+ (err) => process.stderr.write(
3973
+ `::warning::code-review report-incomplete: could not post the failure notice (${annotationSafe(errMsg(err))}) \u2014 continuing
3974
+ `
3975
+ )
3976
+ );
3977
+ }
3978
+ });
3765
3979
  var requireCeilingSec = (raw) => {
3766
3980
  if (raw === void 0) return null;
3767
3981
  const ms = parseWallMs(raw);
@@ -3935,6 +4149,8 @@ var main = defineCommand({
3935
4149
  inline: inlineCmd,
3936
4150
  post: postCmd,
3937
4151
  announce: announceCmd,
4152
+ "check-run": checkRunCmd,
4153
+ "report-incomplete": reportIncompleteCmd,
3938
4154
  cost: costCmd,
3939
4155
  "check-cost": checkCostCmd,
3940
4156
  validate: validateCmd,