prreviewbuddy 0.43.0 → 0.45.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/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as ensureServer, $t as reviewedRepositories, An as AGENT_IDS, At as loadJob, Bt as followedRefName, Cn as resolveAgent, Ct as checkReview, Dn as writeAgentPreference, E as telemetryUploadConsent, En as readAgentPreference, Et as allJobs, Ft as progressSteps, G as MANAGED_ROOT, It as purposeOf, J as modelHelpLines, Jt as mutateWorkspace, K as readMarker, Kt as loadWorkspace, Lt as clearClaim, Mn as agentById, Mt as PHASES, On as CONFIG_PATH, Pn as detectAgents, Rt as driverGone, S as startJob, Sn as git, T as recordTelemetryUploadConsent, Vt as groupByLineage, Wt as lineageKeyFor, X as readIndexToken, Y as relativeTime, Yt as positionInLineage, Z as bootstrapUrl, Zt as recentWorkspaces, a as wasBlocked, at as workspaceUrl, b as reanalyseReview, bn as displayRef, ct as PACKAGE_NAME, dt as MAX_WORKSPACE_PORT, en as summarise, ft as MIN_WORKSPACE_PORT, g as runningJobs, gt as writePortPreference, h as liveUpdateFor, ht as statedWorkspacePort, i as liveJobsFor, it as stopServer, jn as DEFAULT_AGENT_ID, kn as STORE_ROOT, kt as isTerminal, l as mergeUpdateInto, m as UpdateAlreadyRunningError, n as deleteReview$1, pt as parseWorkspacePort, q as removeWorktree, qt as matchingWorkspaceIds, st as BUILD_VERSION, tt as reviewsUrl, u as updateReview, ut as DEFAULT_WORKSPACE_PORT, v as recordKissRun, vt as forgeResolver, w as record$1, wn as resolveModel, x as runJob, yn as resolveTarget, zt as isClaimed } from "./delete_review-DPRL5IPt.js";
2
+ import { $ as bootstrapUrl, $t as recentWorkspaces, An as STORE_ROOT, Bt as driverGone, Cn as git, Dn as readAgentPreference, E as telemetryUploadConsent, Fn as detectAgents, G as MANAGED_ROOT, Ht as followedRefName, J as modelHelpLines, Jt as loadWorkspace, K as readMarker, Kt as lineageKeyFor, Lt as progressSteps, Mn as DEFAULT_AGENT_ID, Mt as loadJob, Nn as agentById, On as writeAgentPreference, Ot as allJobs, Pt as PHASES, Q as readIndexToken, Rt as purposeOf, S as startJob, T as recordTelemetryUploadConsent, Tn as resolveModel, Tt as checkReview, Ut as groupByLineage, Vt as isClaimed, Xt as mutateWorkspace, Y as relativeTime, Yt as matchingWorkspaceIds, Z as queueForReview, Zt as positionInLineage, _t as statedWorkspacePort, a as wasBlocked, b as reanalyseReview, bn as resolveTarget, bt as forgeResolver, ft as DEFAULT_WORKSPACE_PORT, g as runningJobs, h as liveUpdateFor, ht as parseWorkspacePort, i as liveJobsFor, jn as AGENT_IDS, jt as isTerminal, kn as CONFIG_PATH, l as mergeUpdateInto, lt as BUILD_VERSION, m as UpdateAlreadyRunningError, mt as MIN_WORKSPACE_PORT, n as deleteReview$1, nn as summarise, ot as stopServer, pt as MAX_WORKSPACE_PORT, q as removeWorktree, rt as reviewsUrl, st as workspaceUrl, tn as reviewedRepositories, tt as ensureServer, u as updateReview, ut as PACKAGE_NAME, v as recordKissRun, vt as writePortPreference, w as record$1, wn as resolveAgent, x as runJob, xn as displayRef, zt as clearClaim } from "./delete_review-BWMmO81Q.js";
3
3
  import { basename, dirname, join, resolve } from "node:path";
4
4
  import { spawn } from "node:child_process";
5
5
  import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -33,7 +33,8 @@ var BARE = {
33
33
  "--update",
34
34
  "--reanalyse",
35
35
  "--delete",
36
- "--yes"
36
+ "--yes",
37
+ "--queue"
37
38
  ],
38
39
  open: [],
39
40
  agents: [],
@@ -117,16 +118,21 @@ function parse(argv) {
117
118
  const values = {};
118
119
  const flags = /* @__PURE__ */ new Set();
119
120
  const positional = [];
121
+ /** The flag each positional came after, as typed, so a stray URL can be told where it went wrong. */
122
+ const after = /* @__PURE__ */ new Map();
123
+ let lastFlag;
120
124
  /** Every URL `--pr` took. `review` has no positionals, so what follows `--pr` up to the next flag is its. */
121
125
  const prs = [];
122
126
  for (let index = 0; index < rest.length; index += 1) {
123
127
  const argument = rest[index];
124
128
  if (!argument.startsWith("--")) {
125
129
  positional.push(argument);
130
+ if (lastFlag && !after.has(argument)) after.set(argument, lastFlag);
126
131
  continue;
127
132
  }
128
133
  const equals = argument.indexOf("=");
129
134
  const name = equals === -1 ? argument : argument.slice(0, equals);
135
+ lastFlag = name;
130
136
  if (VALUED[head]?.includes(name)) {
131
137
  const value = equals === -1 ? rest[index + 1] : argument.slice(equals + 1);
132
138
  if (!value) return {
@@ -153,10 +159,18 @@ function parse(argv) {
153
159
  message: `\`${head}\` does not take ${name}. Run \`prreviewbuddy --help\` to see what it takes.`
154
160
  };
155
161
  }
156
- if (head === "review" && positional[0]) return {
157
- name: "error",
158
- message: strayArgument(positional[0])
159
- };
162
+ if (head === "review" && positional[0]) {
163
+ const stray = positional[0];
164
+ const flag = after.get(stray);
165
+ if (prs.length > 0 && flag && LOOKS_LIKE_A_REQUEST.test(stray)) return {
166
+ name: "error",
167
+ message: `\`${stray}\` comes after \`${flag}\`, so \`--pr\` did not take it: \`--pr\` takes the URLs straight after it, up to the next flag. Put all the URLs together after \`--pr\`, and the other flags before or after them.`
168
+ };
169
+ return {
170
+ name: "error",
171
+ message: strayArgument(stray)
172
+ };
173
+ }
160
174
  if (head === "uninstall" && positional[0]) return {
161
175
  name: "error",
162
176
  message: `\`uninstall\` removes everything on this machine, so it takes no target and \`${positional[0]}\` means nothing to it. It takes \`--keep-reviews\`, \`--yes\` and \`--force\`.`
@@ -184,7 +198,8 @@ function parse(argv) {
184
198
  ...values["--concurrency"] ? { concurrency: Number(values["--concurrency"]) } : {},
185
199
  ...values["--agent"] ? { agentId: values["--agent"] } : {},
186
200
  ...values["--model"] ? { requestedModel: values["--model"] } : {},
187
- ...flags.has("--fresh") ? { operation: "fresh" } : {}
201
+ ...flags.has("--fresh") ? { operation: "fresh" } : {},
202
+ ...flags.has("--queue") ? { queue: true } : {}
188
203
  };
189
204
  const target = Object.keys(TARGETS).find((flag) => values[flag]);
190
205
  const operation = Object.keys(OPERATIONS).find((flag) => flags.has(flag));
@@ -197,7 +212,8 @@ function parse(argv) {
197
212
  ...operation ? { operation: OPERATIONS[operation][0] } : {},
198
213
  ...values["--lens"] ? { lens: values["--lens"] } : {},
199
214
  ...values["--audience"] ? { audience: values["--audience"] } : {},
200
- ...flags.has("--yes") ? { yes: true } : {}
215
+ ...flags.has("--yes") ? { yes: true } : {},
216
+ ...flags.has("--queue") ? { queue: true } : {}
201
217
  };
202
218
  }
203
219
  if (head === "uninstall") return {
@@ -313,6 +329,7 @@ function refuseCombination(values, flags) {
313
329
  }
314
330
  if (values["--agent"]) return "`--agent` chooses who analyses a change, and `--delete` analyses nothing. Drop it, or drop `--delete` if you meant to review something.";
315
331
  if (values["--model"]) return "`--model` chooses which model analyses a change, and `--delete` analyses nothing. Drop it, or drop `--delete` if you meant to review something.";
332
+ if (flags.has("--queue")) return "`--queue` puts a review on your review queue to work through, and `--delete` removes the review, so there would be nothing left to queue. Ask for one of them.";
316
333
  }
317
334
  if (flags.has("--yes") && !flags.has("--delete")) return "`--yes` answers the confirmation that `--delete` asks, and nothing here is asking one. Add `--delete` if you meant to remove a review.";
318
335
  if (values["--review"]) {
@@ -980,6 +997,8 @@ your own checkout is never read or written while it works.
980
997
  prreviewbuddy review --pr <url> <url> ...
981
998
  review several pull requests, each on its own, two at a
982
999
  time; --concurrency <n> changes how many
1000
+ prreviewbuddy review ... --queue also put each review on your review queue, as soon as
1001
+ it exists rather than when it is ready
983
1002
  prreviewbuddy review --base <ref> compare against a base of your choosing
984
1003
  prreviewbuddy review --agent <agent> review with ${AGENT_NAMES}
985
1004
  prreviewbuddy review --model <model> ask that agent for a particular model. Reviews keep
@@ -1408,11 +1427,52 @@ function batchFinished(rows, index) {
1408
1427
  lines.push(`${lead}${row.detail ?? row.url ?? ""}`.trimEnd());
1409
1428
  if (row.detail && row.url) lines.push(`${" ".repeat(lead.length)}${row.url}`);
1410
1429
  }
1411
- if (rows.some((row) => row.outcome === "stopped" || row.outcome === "not-started" || row.outcome === "failed" && row.url)) lines.push("", "Run the same command again to carry on with the reviews that did not finish, from where each got to.");
1430
+ const unfinished = rows.some((row) => row.outcome === "stopped" || row.outcome === "not-started" || row.outcome === "failed" && row.url);
1431
+ lines.push(...batchQueueLines(rows));
1432
+ if (unfinished) lines.push("", "Run the same command again to carry on with the reviews that did not finish, from where each got to.");
1412
1433
  const all = indexLinks(index);
1413
1434
  if (all) lines.push("", all);
1414
1435
  return lines.join("\n");
1415
1436
  }
1437
+ /**
1438
+ * What `--queue` did across a batch, or nothing at all when it was not asked for.
1439
+ *
1440
+ * One line for what is in the queue, rather than a word on every row: the rows say how each review
1441
+ * ended, and the queue is where the reviewer goes next, so it is said once, together. What was left
1442
+ * out is named with its reason, because each reason has a different way round it.
1443
+ */
1444
+ function batchQueueLines(rows) {
1445
+ const asked = rows.filter((row) => row.queue !== void 0);
1446
+ if (asked.length === 0) return [];
1447
+ const lines = [""];
1448
+ const queued = asked.filter((row) => row.queue === "added" || row.queue === "already");
1449
+ if (queued.length > 0) lines.push(`In your review queue: ${queued.map((row) => row.label).join(", ")}`);
1450
+ for (const row of asked) {
1451
+ if (row.queue === "added" || row.queue === "already") continue;
1452
+ lines.push(`Not added to your review queue: ${notQueuedBecause(row.label, row.queue)}`);
1453
+ }
1454
+ return lines;
1455
+ }
1456
+ function notQueuedBecause(subject, result) {
1457
+ switch (result) {
1458
+ case "complete": return `${subject} is marked complete. Move it back to In progress in the workspace to queue it.`;
1459
+ case "full": return `${subject}, because the review queue is full. It holds 50 reviews at most.`;
1460
+ default: return `${subject}, because the review queue could not be saved.`;
1461
+ }
1462
+ }
1463
+ /**
1464
+ * What `--queue` did with the one review a single command acted on. To stderr, like every line that
1465
+ * is not the review's own link.
1466
+ */
1467
+ function reviewQueued(result) {
1468
+ switch (result) {
1469
+ case "added": return "Added to review queue.";
1470
+ case "already": return "Already in your review queue.";
1471
+ case "complete": return "Not added to your review queue: this review is marked complete. Move it back to In progress in the workspace to queue it.";
1472
+ case "full": return `Not added to your review queue: it is full, and holds 50 reviews at most.`;
1473
+ default: return "Not added to your review queue: the queue could not be saved.";
1474
+ }
1475
+ }
1416
1476
  function noReviews() {
1417
1477
  return "No reviews stored yet. Run `prreviewbuddy review` in a repository to make one.";
1418
1478
  }
@@ -1908,16 +1968,16 @@ async function reviewBatch(command, repoPath, deps = realDeps()) {
1908
1968
  }
1909
1969
  claimed.set(identity, index);
1910
1970
  const prepared = await deps.prepare(target, agent.id, command.operation === "fresh", command.requestedModel);
1971
+ const workspaceId = prepared.kind === "new" ? prepared.workspaceId : prepared.existing.id;
1972
+ row.workspaceId = workspaceId;
1973
+ if (command.queue) row.reviewQueue = deps.enqueue(workspaceId);
1911
1974
  if (prepared.kind === "existing") {
1912
- row.workspaceId = prepared.existing.id;
1913
1975
  row.state = {
1914
1976
  kind: "existing",
1915
1977
  inProgress: prepared.existing.inProgress
1916
1978
  };
1917
1979
  return;
1918
1980
  }
1919
- const workspaceId = prepared.kind === "resume" ? prepared.existing.id : prepared.workspaceId;
1920
- row.workspaceId = workspaceId;
1921
1981
  row.state = {
1922
1982
  kind: "running",
1923
1983
  jobId: deps.jobIdOf(workspaceId) ?? ""
@@ -1949,7 +2009,11 @@ async function reviewBatch(command, repoPath, deps = realDeps()) {
1949
2009
  }
1950
2010
  function summaryRow(row, link) {
1951
2011
  const url = row.workspaceId ? link(row.workspaceId) : void 0;
1952
- const withUrl = url ? { url } : {};
2012
+ const queued = row.reviewQueue ? { queue: row.reviewQueue } : {};
2013
+ const withUrl = {
2014
+ ...url ? { url } : {},
2015
+ ...queued
2016
+ };
1953
2017
  switch (row.state.kind) {
1954
2018
  case "ready": return {
1955
2019
  label: row.label,
@@ -1979,7 +2043,8 @@ function summaryRow(row, link) {
1979
2043
  };
1980
2044
  default: return {
1981
2045
  label: row.label,
1982
- outcome: "not-started"
2046
+ outcome: "not-started",
2047
+ ...queued
1983
2048
  };
1984
2049
  }
1985
2050
  }
@@ -1994,6 +2059,7 @@ function realDeps() {
1994
2059
  prepare: prepareReview,
1995
2060
  settle: (workspaceId, signal) => settleReview(workspaceId, "standard", signal),
1996
2061
  jobIdOf: (workspaceId) => loadWorkspace(workspaceId)?.jobId,
2062
+ enqueue: queueForReview,
1997
2063
  links: async () => {
1998
2064
  try {
1999
2065
  const port = await ensureServer();
@@ -2257,6 +2323,7 @@ async function review(command, repoPath) {
2257
2323
  const agent = await chooseFor(command);
2258
2324
  if (!agent) return 1;
2259
2325
  const prepared = await prepareReview(target, agent.id, command.operation === "fresh", command.requestedModel);
2326
+ enqueue(command, prepared.kind === "new" ? prepared.workspaceId : prepared.existing.id);
2260
2327
  if (prepared.kind === "existing") {
2261
2328
  const url = linkTo(await ensureServer(), prepared.existing.id, prepared.existing.token);
2262
2329
  out(existingReview(prepared.existing, url));
@@ -2311,6 +2378,7 @@ async function kiss(command, target) {
2311
2378
  ...workspace ? { review: { session: workspace.session } } : {}
2312
2379
  }) ?? void 0;
2313
2380
  if (workspace) {
2381
+ enqueue(command, workspace.id);
2314
2382
  const job = await recordKissRun({
2315
2383
  workspaceId: workspace.id,
2316
2384
  audience,
@@ -2330,6 +2398,7 @@ async function kiss(command, target) {
2330
2398
  agentId: agent.id,
2331
2399
  ...requestedModel ? { requestedModel } : {}
2332
2400
  });
2401
+ enqueue(command, workspaceId);
2333
2402
  const fresh = loadWorkspace(workspaceId);
2334
2403
  const run = (fresh.kissRuns ?? [])[0];
2335
2404
  if (!run) {
@@ -2390,6 +2459,7 @@ async function operate(command, workspace) {
2390
2459
  err(commitReviewCannotUpdate(workspace.changeSet.data.pullRequest.head.sha));
2391
2460
  return 1;
2392
2461
  }
2462
+ enqueue(command, workspace.id);
2393
2463
  if (command.operation === "reanalyse") {
2394
2464
  const agent = await chooseFor(command, workspace);
2395
2465
  if (!agent) return 1;
@@ -2443,6 +2513,17 @@ async function operate(command, workspace) {
2443
2513
  return 0;
2444
2514
  }
2445
2515
  /**
2516
+ * `--queue`: put the review on the reviewer's review queue, now, and say how that went.
2517
+ *
2518
+ * Called the moment a review exists and before its analysis is awaited, so the card is in the
2519
+ * workspace's Queue while the review is still being made. Never a reason to fail the command: the
2520
+ * review is what was asked for, and the queue is bookkeeping around it. To stderr, so a piped link
2521
+ * is still only a link.
2522
+ */
2523
+ function enqueue(command, workspaceId) {
2524
+ if (command.queue) err(reviewQueued(queueForReview(workspaceId)));
2525
+ }
2526
+ /**
2446
2527
  * Draw a job's progress block, from what the job itself records about its target.
2447
2528
  *
2448
2529
  * From the record rather than the caller, for the reason `drive` gives: the job knows what it is