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/CHANGELOG.md +70 -0
- package/dist/{delete_review-DPRL5IPt.js → delete_review-BWMmO81Q.js} +257 -145
- package/dist/main.js +95 -14
- package/dist/server.js +221 -82
- package/package.json +1 -1
- package/static/shell.css +210 -1
- package/static/sidebar.css +58 -14
- package/static/workspace.js +35 -35
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
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])
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
|
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
|