prreviewbuddy 0.49.0 → 0.50.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/CHANGELOG.md +48 -0
- package/dist/{delete_review-BBjXPfMV.js → delete_review-BIXqUnpx.js} +91 -12
- package/dist/main.js +41 -1
- package/dist/server.js +182 -12
- package/package.json +1 -1
- package/static/help.js +1 -0
- package/static/shell.css +118 -11
- package/static/sidebar.css +250 -34
- package/static/workspace.js +46 -46
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.50.1
|
|
4
|
+
|
|
5
|
+
**Copy and Ask Buddy look and behave as a pair.** On every issue and question, both icons are the
|
|
6
|
+
same grey at rest and share one soft highlight when you point at them. Copy no longer turns purple on
|
|
7
|
+
hover. Ask Buddy turns purple only while its menu is open. After you copy, the icon turns into a green
|
|
8
|
+
check for a moment and "Copied" appears just below it. It used to appear beside it, on top of Ask
|
|
9
|
+
Buddy. "Copy failed" shows in the same place.
|
|
10
|
+
|
|
11
|
+
The questions you can ask Buddy AI about a finding or a selection, and the wording that tells Buddy
|
|
12
|
+
which theme a question was asked in, now live in the shared review engine, so the browser extension
|
|
13
|
+
offers the same ones.
|
|
14
|
+
|
|
15
|
+
In a short window, the list of questions you can ask about a selection now scrolls instead of being
|
|
16
|
+
cut off at the bottom. Escape closes a menu again after the page has redrawn around it.
|
|
17
|
+
|
|
18
|
+
## 0.50.0
|
|
19
|
+
|
|
20
|
+
**Every review says which commits it covers.** Beside the branch at the top of a review is its
|
|
21
|
+
commit count. Open it to see where your branch split off from the base, every commit in the review,
|
|
22
|
+
and a reminder that uncommitted changes are never included. A review is always the combined change
|
|
23
|
+
across all of those commits, not just the latest one.
|
|
24
|
+
|
|
25
|
+
**Help, in the workspace.** A new **Help** place in the sidebar explains what gets reviewed: how
|
|
26
|
+
the base sets the split point, why your local main and origin/main can give different reviews, and
|
|
27
|
+
how to choose between them. The commit count on a review links to it.
|
|
28
|
+
|
|
29
|
+
**Choosing between local main and origin/main tells you what each would review.** When the two are
|
|
30
|
+
different commits and you have not named a base, PR Review Buddy still asks which one you mean,
|
|
31
|
+
and now lists under each choice how many commits it would review and which ones.
|
|
32
|
+
|
|
33
|
+
**`prreviewbuddy review --help` works.** It used to say `review` does not take `--help`. Every
|
|
34
|
+
command now prints the help, which has a new section on what gets reviewed, with examples.
|
|
35
|
+
|
|
36
|
+
## 0.49.2
|
|
37
|
+
|
|
38
|
+
**Buddy AI remembers which theme you asked in.** Your earlier questions now reach Buddy with the
|
|
39
|
+
theme you had open when you asked them, or a note that they were about the whole change. Asking
|
|
40
|
+
"what is risky here?" in one theme and then "how does that compare?" in another no longer reads to
|
|
41
|
+
Buddy as two questions about the same thing.
|
|
42
|
+
|
|
43
|
+
## 0.49.1
|
|
44
|
+
|
|
45
|
+
**Buddy AI says what your question is about.** A line under **Ask Buddy AI** now reads **Asking
|
|
46
|
+
about: the whole change**, or the name of the theme you have open in the Review Path. Buddy always
|
|
47
|
+
sees the whole change; an open theme tells it where to focus, so "what is the risk here?" is
|
|
48
|
+
answered about that theme. The line used to be a small grey label beside the title, cut short, with
|
|
49
|
+
its meaning only in a tooltip. Theme names are now shown in full.
|
|
50
|
+
|
|
3
51
|
## 0.49.0
|
|
4
52
|
|
|
5
53
|
**Change a review from the review itself.** The **⋯** menu at the top of a review now has
|
|
@@ -1876,6 +1876,11 @@ var AmbiguousRefError = class extends Error {
|
|
|
1876
1876
|
divergence;
|
|
1877
1877
|
/** Whether the name was named by the reviewer or discovered as the default. Changes the wording. */
|
|
1878
1878
|
discovered;
|
|
1879
|
+
/**
|
|
1880
|
+
* What a review against each of the two would contain. Absent until `withReviewRanges` adds it,
|
|
1881
|
+
* which needs the head being reviewed and so cannot happen where the name is resolved.
|
|
1882
|
+
*/
|
|
1883
|
+
ranges;
|
|
1879
1884
|
constructor(detail) {
|
|
1880
1885
|
super(describeAmbiguity(detail));
|
|
1881
1886
|
this.name = "AmbiguousRefError";
|
|
@@ -1884,9 +1889,49 @@ var AmbiguousRefError = class extends Error {
|
|
|
1884
1889
|
this.remote = detail.remote;
|
|
1885
1890
|
if (detail.divergence) this.divergence = detail.divergence;
|
|
1886
1891
|
this.discovered = detail.discovered;
|
|
1892
|
+
if (detail.ranges) this.ranges = detail.ranges;
|
|
1887
1893
|
}
|
|
1888
1894
|
};
|
|
1889
1895
|
/**
|
|
1896
|
+
* The same refusal, now saying what choosing each ref would review.
|
|
1897
|
+
*
|
|
1898
|
+
* Counted from the split point, `git merge-base <ref> <head>`, and not as `<ref>..<head>`: the
|
|
1899
|
+
* review diffs from the merge-base, so this has to walk the same boundary or the refusal and the
|
|
1900
|
+
* review it leads to could disagree about which commits are in it. Added after resolution because
|
|
1901
|
+
* the resolver is shared with the review's own target, which has no head to measure against.
|
|
1902
|
+
*/
|
|
1903
|
+
async function withReviewRanges(error, repoPath, head) {
|
|
1904
|
+
const [local, remote] = await Promise.all([reviewedCommits(repoPath, error.local.ref, head), reviewedCommits(repoPath, error.remote.ref, head)]);
|
|
1905
|
+
return new AmbiguousRefError({
|
|
1906
|
+
requested: error.requested,
|
|
1907
|
+
local: error.local,
|
|
1908
|
+
remote: error.remote,
|
|
1909
|
+
...error.divergence ? { divergence: error.divergence } : {},
|
|
1910
|
+
discovered: error.discovered,
|
|
1911
|
+
ranges: {
|
|
1912
|
+
...local ? { local } : {},
|
|
1913
|
+
...remote ? { remote } : {}
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
async function reviewedCommits(repoPath, ref, head) {
|
|
1918
|
+
try {
|
|
1919
|
+
const split = (await git(repoPath, [
|
|
1920
|
+
"merge-base",
|
|
1921
|
+
ref,
|
|
1922
|
+
head
|
|
1923
|
+
])).trim();
|
|
1924
|
+
if (!split) return void 0;
|
|
1925
|
+
return (await git(repoPath, [
|
|
1926
|
+
"rev-list",
|
|
1927
|
+
"--abbrev-commit",
|
|
1928
|
+
`${split}..${head}`
|
|
1929
|
+
])).split("\n").filter(Boolean);
|
|
1930
|
+
} catch {
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1890
1935
|
* `refs/heads/feat/foo` and `refs/remotes/origin/feat/foo` are both `feat/foo`.
|
|
1891
1936
|
*
|
|
1892
1937
|
* Structural rather than asked of git: the first path segment under `refs/remotes/` is the
|
|
@@ -1996,10 +2041,19 @@ function describeAmbiguity(detail) {
|
|
|
1996
2041
|
...refs.map((one) => ` ${one.ref.padEnd(width)} ${one.sha.slice(0, 7)}`),
|
|
1997
2042
|
...detail.divergence ? ["", divergenceLine(name, shorthandOf(detail.remote.ref), detail.divergence)] : [],
|
|
1998
2043
|
"",
|
|
2044
|
+
...detail.ranges ? [SPLIT_POINT, ""] : [],
|
|
1999
2045
|
"Name the one you mean:",
|
|
2000
|
-
...
|
|
2046
|
+
...[[detail.local, detail.ranges?.local], [detail.remote, detail.ranges?.remote]].flatMap(([one, commits]) => [` prreviewbuddy review --base ${shorthandOf(one.ref)}`, ...commits ? [` ${rangeLine(commits)}`] : []])
|
|
2001
2047
|
].join("\n");
|
|
2002
2048
|
}
|
|
2049
|
+
var SPLIT_POINT = "The base determines the split point. PR Review Buddy reviews the changes on your branch since that point.";
|
|
2050
|
+
/** "changes across", because a review is one combined diff and not a review of each commit. */
|
|
2051
|
+
function rangeLine(commits) {
|
|
2052
|
+
if (commits.length === 0) return "no commits: nothing on your branch is new against this base";
|
|
2053
|
+
const listed = commits.slice(0, 10).join(" ");
|
|
2054
|
+
const more = commits.length > 10 ? ` and ${commits.length - 10} more` : "";
|
|
2055
|
+
return `changes across ${commits.length} commit${commits.length === 1 ? "" : "s"}: ${listed}${more}`;
|
|
2056
|
+
}
|
|
2003
2057
|
/**
|
|
2004
2058
|
* How a person spells this ref: the shortest form that still says which one.
|
|
2005
2059
|
*
|
|
@@ -2176,7 +2230,7 @@ async function buildChangeSet(options) {
|
|
|
2176
2230
|
disagreement(files.length, base.display, options.expected);
|
|
2177
2231
|
const blobShas = await headBlobShas(repoPath);
|
|
2178
2232
|
for (const file of files) file.sha = blobShas.get(file.name) ?? "";
|
|
2179
|
-
const { subjects, authors } = await commitLog(repoPath, base.exact && !await refExists(repoPath, mergeBase) ? "HEAD" : `${mergeBase}..HEAD`);
|
|
2233
|
+
const { subjects, authors, commits } = await commitLog(repoPath, base.exact && !await refExists(repoPath, mergeBase) ? "HEAD" : `${mergeBase}..HEAD`);
|
|
2180
2234
|
return {
|
|
2181
2235
|
data: {
|
|
2182
2236
|
pullRequest: {
|
|
@@ -2205,7 +2259,8 @@ async function buildChangeSet(options) {
|
|
|
2205
2259
|
includesUncommitted: false,
|
|
2206
2260
|
isLinkedWorktree,
|
|
2207
2261
|
checkoutName: basename(repoPath),
|
|
2208
|
-
authors
|
|
2262
|
+
authors,
|
|
2263
|
+
commits
|
|
2209
2264
|
};
|
|
2210
2265
|
}
|
|
2211
2266
|
/**
|
|
@@ -2316,21 +2371,22 @@ async function commitLog(repoPath, range) {
|
|
|
2316
2371
|
const out = await git(repoPath, [
|
|
2317
2372
|
"log",
|
|
2318
2373
|
"--reverse",
|
|
2319
|
-
"--format=%an%x1f%s",
|
|
2374
|
+
"--format=%H%x1f%an%x1f%s",
|
|
2320
2375
|
range
|
|
2321
2376
|
]);
|
|
2322
2377
|
const subjects = [];
|
|
2323
2378
|
const counts = /* @__PURE__ */ new Map();
|
|
2379
|
+
const commits = [];
|
|
2324
2380
|
for (const line of out.split("\n")) {
|
|
2325
|
-
const
|
|
2326
|
-
if (
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
if (
|
|
2330
|
-
if (name) counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
2381
|
+
const [sha, name, subject] = line.split("");
|
|
2382
|
+
if (!sha || name === void 0) continue;
|
|
2383
|
+
commits.push(sha.trim());
|
|
2384
|
+
if (subject?.trim()) subjects.push(subject.trim());
|
|
2385
|
+
if (name.trim()) counts.set(name.trim(), (counts.get(name.trim()) ?? 0) + 1);
|
|
2331
2386
|
}
|
|
2332
2387
|
return {
|
|
2333
2388
|
subjects,
|
|
2389
|
+
commits: commits.reverse(),
|
|
2334
2390
|
authors: [...counts].map(([name, commits]) => ({
|
|
2335
2391
|
name,
|
|
2336
2392
|
commits
|
|
@@ -2746,8 +2802,14 @@ async function peel(repoPath, named) {
|
|
|
2746
2802
|
async function resolveTarget(input) {
|
|
2747
2803
|
const root = await repositoryRoot(input.repoPath);
|
|
2748
2804
|
if (input.commitRef) return commitTarget(root, input.commitRef);
|
|
2749
|
-
|
|
2750
|
-
|
|
2805
|
+
try {
|
|
2806
|
+
if (input.base) await verifyBase(root, input.base);
|
|
2807
|
+
else await verifyDefaultBase(root);
|
|
2808
|
+
} catch (error) {
|
|
2809
|
+
if (!(error instanceof AmbiguousRefError)) throw error;
|
|
2810
|
+
const head = await headToMeasure(root, input);
|
|
2811
|
+
throw head ? await withReviewRanges(error, root, head) : error;
|
|
2812
|
+
}
|
|
2751
2813
|
const named = input.base ? { base: input.base } : {};
|
|
2752
2814
|
if (input.prUrl) {
|
|
2753
2815
|
if (!input.resolveRequest) throw new TargetError(`${input.prUrl} is a pull request, but nothing here can look one up. Check the request out locally and review the branch instead.`);
|
|
@@ -2896,6 +2958,23 @@ async function verifyBase(root, base) {
|
|
|
2896
2958
|
throw new TargetError(`There is nothing called \`${base}\` in this repository to compare against. Tried ${candidateRefs(base).map((one) => `\`${one}\``).join(" and ")}. Run \`git fetch\` if it is on the remote and this clone has not seen it yet.`);
|
|
2897
2959
|
}
|
|
2898
2960
|
/**
|
|
2961
|
+
* The commit the review would be of, as far as it is known before the target is resolved.
|
|
2962
|
+
*
|
|
2963
|
+
* A pull request's head is not known until the forge is asked, and the refusal exists to come
|
|
2964
|
+
* before that, so it gets none and its refusal lists no ranges. A named branch is resolved rather
|
|
2965
|
+
* than handed to git as typed, because a name that is itself ambiguous would be settled by git's own
|
|
2966
|
+
* preference, which is the guess this module refuses to make.
|
|
2967
|
+
*/
|
|
2968
|
+
async function headToMeasure(root, input) {
|
|
2969
|
+
if (input.prUrl) return void 0;
|
|
2970
|
+
if (!input.branchRef) return "HEAD";
|
|
2971
|
+
try {
|
|
2972
|
+
return (await resolveBranchRef(root, input.branchRef))?.sha;
|
|
2973
|
+
} catch {
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
/**
|
|
2899
2978
|
* The same check for the base nobody named, which is the one the reported failure came down.
|
|
2900
2979
|
*
|
|
2901
2980
|
* A bare `prreviewbuddy review` has no base to verify, so this ran nothing and the refusal landed
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $t as matchingWorkspaceIds, Bn as writeAgentPreference, Bt as progressSteps, Ct as forgeResolver, E as telemetryUploadConsent, Fn as git, Ft as loadJob, Gn as agentById, Gt as followedRefName, Hn as STORE_ROOT, Ht as clearClaim, In as resolveAgent, J as readMarker, Kt as groupByLineage, Ln as resolveModel, Lt as PHASES, Mn as resolveTarget, Nn as displayRef, Ot as checkReview, Pt as isTerminal, Qt as loadWorkspace, S as startJob, T as recordTelemetryUploadConsent, Un as AGENT_IDS, Ut as driverGone, Vn as CONFIG_PATH, Vt as purposeOf, Wn as DEFAULT_AGENT_ID, Wt as isClaimed, X as modelHelpLines, Xt as lineageKeyFor, Y as removeWorktree, Z as relativeTime, _t as MIN_WORKSPACE_PORT, a as wasBlocked, an as reviewedRepositories, b as reanalyseReview, bt as statedWorkspacePort, en as mutateWorkspace, et as queueForReview, ft as BUILD_VERSION, g as runningJobs, gt as MAX_WORKSPACE_PORT, h as liveUpdateFor, ht as DEFAULT_WORKSPACE_PORT, i as liveJobsFor, it as ensureServer, jt as allJobs, l as mergeUpdateInto, lt as stopServer, m as UpdateAlreadyRunningError, n as deleteReview$1, nt as bootstrapUrl, on as summarise, ot as reviewsUrl, pt as PACKAGE_NAME, q as MANAGED_ROOT, qn as detectAgents, rn as recentWorkspaces, tn as positionInLineage, tt as readIndexToken, u as updateReview, ut as workspaceUrl, v as recordKissRun, vt as parseWorkspacePort, w as record$1, x as runJob, xt as writePortPreference, zn as readAgentPreference } from "./delete_review-
|
|
2
|
+
import { $t as matchingWorkspaceIds, Bn as writeAgentPreference, Bt as progressSteps, Ct as forgeResolver, E as telemetryUploadConsent, Fn as git, Ft as loadJob, Gn as agentById, Gt as followedRefName, Hn as STORE_ROOT, Ht as clearClaim, In as resolveAgent, J as readMarker, Kt as groupByLineage, Ln as resolveModel, Lt as PHASES, Mn as resolveTarget, Nn as displayRef, Ot as checkReview, Pt as isTerminal, Qt as loadWorkspace, S as startJob, T as recordTelemetryUploadConsent, Un as AGENT_IDS, Ut as driverGone, Vn as CONFIG_PATH, Vt as purposeOf, Wn as DEFAULT_AGENT_ID, Wt as isClaimed, X as modelHelpLines, Xt as lineageKeyFor, Y as removeWorktree, Z as relativeTime, _t as MIN_WORKSPACE_PORT, a as wasBlocked, an as reviewedRepositories, b as reanalyseReview, bt as statedWorkspacePort, en as mutateWorkspace, et as queueForReview, ft as BUILD_VERSION, g as runningJobs, gt as MAX_WORKSPACE_PORT, h as liveUpdateFor, ht as DEFAULT_WORKSPACE_PORT, i as liveJobsFor, it as ensureServer, jt as allJobs, l as mergeUpdateInto, lt as stopServer, m as UpdateAlreadyRunningError, n as deleteReview$1, nt as bootstrapUrl, on as summarise, ot as reviewsUrl, pt as PACKAGE_NAME, q as MANAGED_ROOT, qn as detectAgents, rn as recentWorkspaces, tn as positionInLineage, tt as readIndexToken, u as updateReview, ut as workspaceUrl, v as recordKissRun, vt as parseWorkspacePort, w as record$1, x as runJob, xt as writePortPreference, zn as readAgentPreference } from "./delete_review-BIXqUnpx.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";
|
|
@@ -115,6 +115,7 @@ function parse(argv) {
|
|
|
115
115
|
name: "error",
|
|
116
116
|
message: unknownCommand(head)
|
|
117
117
|
};
|
|
118
|
+
if (rest.includes("--help") || rest.includes("-h")) return { name: "help" };
|
|
118
119
|
const values = {};
|
|
119
120
|
const flags = /* @__PURE__ */ new Set();
|
|
120
121
|
const positional = [];
|
|
@@ -1064,6 +1065,45 @@ running the same command again carries each one on from where it got to.
|
|
|
1064
1065
|
|
|
1065
1066
|
Reviews can also be deleted from the list in your browser, one at a time or a whole branch at once.
|
|
1066
1067
|
|
|
1068
|
+
What gets reviewed
|
|
1069
|
+
|
|
1070
|
+
A review is the combined change between a base and the branch you review, as one diff. It is not
|
|
1071
|
+
just the latest commit. The base determines the split point (git's merge-base of the base and your
|
|
1072
|
+
branch), and PR Review Buddy reviews the changes on your branch since that point.
|
|
1073
|
+
|
|
1074
|
+
main: A---B---C
|
|
1075
|
+
\\
|
|
1076
|
+
feature: D---E
|
|
1077
|
+
|
|
1078
|
+
prreviewbuddy review --base main reviews D and E together
|
|
1079
|
+
|
|
1080
|
+
C is on main but not on your branch, so it is not part of the review, and the review does not show
|
|
1081
|
+
its changes as removed. Commits already in the base are never reviewed again.
|
|
1082
|
+
|
|
1083
|
+
When your local main and origin/main are different commits, which one you name moves the split
|
|
1084
|
+
point:
|
|
1085
|
+
|
|
1086
|
+
origin/main: A---B
|
|
1087
|
+
\\
|
|
1088
|
+
local main: C---D
|
|
1089
|
+
\\
|
|
1090
|
+
feature: E
|
|
1091
|
+
|
|
1092
|
+
--base refs/heads/main reviews E
|
|
1093
|
+
--base origin/main reviews C, D and E together
|
|
1094
|
+
|
|
1095
|
+
Without --base, the repository's default branch is the base. If its local and remote copies are
|
|
1096
|
+
different commits, PR Review Buddy asks which one you mean and says what each would review.
|
|
1097
|
+
Uncommitted changes are never included: a review is a fixed snapshot of your commits.
|
|
1098
|
+
|
|
1099
|
+
Common examples
|
|
1100
|
+
|
|
1101
|
+
prreviewbuddy review --base refs/heads/main
|
|
1102
|
+
everything on this branch since it left your local main
|
|
1103
|
+
prreviewbuddy review --base origin/main
|
|
1104
|
+
everything on this branch not yet on origin/main
|
|
1105
|
+
prreviewbuddy review --commit <commit> one commit, against its parent
|
|
1106
|
+
|
|
1067
1107
|
There is no account and no API key. PR Review Buddy uses a coding agent installed on your machine
|
|
1068
1108
|
and that agent's existing authentication, and nothing here ever asks you for a credential.
|
|
1069
1109
|
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as compactQueue, A as createPrState, An as processDiscussion, At as reviewedShaOf, B as toneOf, Bn as writeAgentPreference, Bt as progressSteps, C as readEvents, Cn as readQueue, D as announceJob, Dn as issuesOutstanding, Dt as checkCodeFreshness, E as telemetryUploadConsent, En as isQuestionOutstanding, Et as fillFileUrlTemplate, F as markAllRead, Fn as git, Ft as loadJob, G as extractJsonText, Gt as followedRefName, H as withUsageRecorded, Hn as STORE_ROOT, I as markRead, It as saveJob, J as readMarker, Jn as AgentCancelledError, Jt as indexReviewGroups, K as ReviewBeingDeletedError, Kn as agentFor, Kt as groupByLineage, L as pruneNotifications, Ln as resolveModel, M as eventName, Mt as endedAt, N as headline, Nt as fail, O as branchOf, On as questionsOutstanding, Ot as checkReview, P as listNotifications, Pn as isCommitReview, Pt as isTerminal, Q as activateQueued, Qt as loadWorkspace, R as readNotification, Rn as clearAgentPreference, Rt as isLiveJob, Sn as queueStamp, St as claimRequestUrl, T as recordTelemetryUploadConsent, Tn as isIssueOutstanding, Tt as remoteHost, U as EXPLAIN_SIMPLY_PROMPT, Un as AGENT_IDS, V as unreadCount$1, Vt as purposeOf, W as asString, Wt as isClaimed, Y as removeWorktree, Yn as AgentUnavailableError, Yt as latestKissRun, Z as relativeTime$1, Zt as lineagePosition, _ as agentEnvOf, _n as parseLifecycle, _t as MIN_WORKSPACE_PORT, a as wasBlocked, at as readServerRecord, bn as queueAddMany, bt as statedWorkspacePort, c as RefreshUnavailableError, cn as workspaceRevision, ct as spawnServer, d as refreshPrContext, dn as activityLabel, dt as writeServerRecord, en as mutateWorkspace, f as isUnchanged, fn as isLifecycle, ft as BUILD_VERSION, gn as offersLifecycle, gt as MAX_WORKSPACE_PORT, h as liveUpdateFor, hn as lifecyclePill, ht as DEFAULT_WORKSPACE_PORT, i as liveJobsFor, in as reviewedCommit, j as createPrUpdate, jn as describeAuthorship, jt as allJobs, k as subjectOf, kn as reviewerDispositions, kt as followedBranch, l as mergeUpdateInto, ln as workspaceStamps, m as UpdateAlreadyRunningError, mn as lifecycleChip, mt as feedbackUrl, n as deleteReview, nn as previousKissResult, o as discardJob, p as askCheckout, pn as lifecycleAction, q as MANAGED_ROOT, qt as indexKeptIds, r as lineageIds, rn as recentWorkspaces, rt as clearServerRecord, s as FETCH_TIMEOUT_MS, sn as touchWorkspace, st as serverEntryExists, t as deleteLineage, tt as readIndexToken, u as updateReview, un as LIFECYCLES, vn as sectionOf, vt as parseWorkspacePort, w as record, wn as doneVerb, wt as forgeFor, x as runJob, xn as queueRemove, xt as writePortPreference, y as startKissJob, yn as queueAdd, yt as resolveWorkspacePort, z as removeNotification, zn as readAgentPreference, zt as phasesFor } from "./delete_review-
|
|
1
|
+
import { $ as compactQueue, A as createPrState, An as processDiscussion, At as reviewedShaOf, B as toneOf, Bn as writeAgentPreference, Bt as progressSteps, C as readEvents, Cn as readQueue, D as announceJob, Dn as issuesOutstanding, Dt as checkCodeFreshness, E as telemetryUploadConsent, En as isQuestionOutstanding, Et as fillFileUrlTemplate, F as markAllRead, Fn as git, Ft as loadJob, G as extractJsonText, Gt as followedRefName, H as withUsageRecorded, Hn as STORE_ROOT, I as markRead, It as saveJob, J as readMarker, Jn as AgentCancelledError, Jt as indexReviewGroups, K as ReviewBeingDeletedError, Kn as agentFor, Kt as groupByLineage, L as pruneNotifications, Ln as resolveModel, M as eventName, Mt as endedAt, N as headline, Nt as fail, O as branchOf, On as questionsOutstanding, Ot as checkReview, P as listNotifications, Pn as isCommitReview, Pt as isTerminal, Q as activateQueued, Qt as loadWorkspace, R as readNotification, Rn as clearAgentPreference, Rt as isLiveJob, Sn as queueStamp, St as claimRequestUrl, T as recordTelemetryUploadConsent, Tn as isIssueOutstanding, Tt as remoteHost, U as EXPLAIN_SIMPLY_PROMPT, Un as AGENT_IDS, V as unreadCount$1, Vt as purposeOf, W as asString, Wt as isClaimed, Y as removeWorktree, Yn as AgentUnavailableError, Yt as latestKissRun, Z as relativeTime$1, Zt as lineagePosition, _ as agentEnvOf, _n as parseLifecycle, _t as MIN_WORKSPACE_PORT, a as wasBlocked, at as readServerRecord, bn as queueAddMany, bt as statedWorkspacePort, c as RefreshUnavailableError, cn as workspaceRevision, ct as spawnServer, d as refreshPrContext, dn as activityLabel, dt as writeServerRecord, en as mutateWorkspace, f as isUnchanged, fn as isLifecycle, ft as BUILD_VERSION, gn as offersLifecycle, gt as MAX_WORKSPACE_PORT, h as liveUpdateFor, hn as lifecyclePill, ht as DEFAULT_WORKSPACE_PORT, i as liveJobsFor, in as reviewedCommit, j as createPrUpdate, jn as describeAuthorship, jt as allJobs, k as subjectOf, kn as reviewerDispositions, kt as followedBranch, l as mergeUpdateInto, ln as workspaceStamps, m as UpdateAlreadyRunningError, mn as lifecycleChip, mt as feedbackUrl, n as deleteReview, nn as previousKissResult, o as discardJob, p as askCheckout, pn as lifecycleAction, q as MANAGED_ROOT, qt as indexKeptIds, r as lineageIds, rn as recentWorkspaces, rt as clearServerRecord, s as FETCH_TIMEOUT_MS, sn as touchWorkspace, st as serverEntryExists, t as deleteLineage, tt as readIndexToken, u as updateReview, un as LIFECYCLES, vn as sectionOf, vt as parseWorkspacePort, w as record, wn as doneVerb, wt as forgeFor, x as runJob, xn as queueRemove, xt as writePortPreference, y as startKissJob, yn as queueAdd, yt as resolveWorkspacePort, z as removeNotification, zn as readAgentPreference, zt as phasesFor } from "./delete_review-BIXqUnpx.js";
|
|
2
2
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
@@ -208,6 +208,30 @@ function resolveSectionId(run, sectionId) {
|
|
|
208
208
|
return run.result.sections.some((section) => section.id === sectionId) ? namespaced(run.id, sectionId) : sectionId;
|
|
209
209
|
}
|
|
210
210
|
//#endregion
|
|
211
|
+
//#region ../../packages/review-harness/src/domain/ai_review/ask_scope.ts
|
|
212
|
+
/**
|
|
213
|
+
* Where an earlier question was asked, which the question alone does not say.
|
|
214
|
+
*
|
|
215
|
+
* "What is risky here?" in one theme followed by "and how does that compare?" in another reads, as
|
|
216
|
+
* bare text, as two questions about the same thing. `undefined` is a question recorded before
|
|
217
|
+
* scopes were, and gets no label: calling it "the whole change" would state something nobody
|
|
218
|
+
* recorded. A theme that has since gone, because the review was updated, is said to have gone.
|
|
219
|
+
*/
|
|
220
|
+
function askedWhere(sections, sectionId) {
|
|
221
|
+
if (sectionId === void 0) return null;
|
|
222
|
+
if (sectionId === null) return "(asked about the whole change)";
|
|
223
|
+
const section = sections.find((s) => s.id === sectionId);
|
|
224
|
+
return section ? `(asked while reading the "${section.title}" theme)` : "(asked while reading a theme that is no longer in the review)";
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The theme the reviewer has open, named rather than used to filter what the model sees: they
|
|
228
|
+
* routinely ask "does this interact with X", where X is in another theme.
|
|
229
|
+
*/
|
|
230
|
+
function readingThemeLine(sections, sectionId) {
|
|
231
|
+
const section = sectionId === null ? void 0 : sections.find((s) => s.id === sectionId);
|
|
232
|
+
return section ? `The reviewer is currently reading the "${section.title}" theme. Answer with that in view, but do not ignore the rest of the change if it bears on the question.` : null;
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
211
235
|
//#region ../../packages/review-harness/src/domain/utils/html.ts
|
|
212
236
|
function escapeHtml$1(value) {
|
|
213
237
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -371,6 +395,7 @@ var PATHS = {
|
|
|
371
395
|
reviews: "<path d=\"M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01\"/>",
|
|
372
396
|
notifications: "<path d=\"M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.7 21a2 2 0 0 1-3.4 0\"/>",
|
|
373
397
|
settings: "<circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z\"/>",
|
|
398
|
+
help: "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3\"/><path d=\"M12 17h.01\"/>",
|
|
374
399
|
feedback: "<path d=\"m22 2-7 20-4-9-9-4z\"/><path d=\"M22 2 11 13\"/>",
|
|
375
400
|
stop: "<path d=\"M12 2v10\"/><path d=\"M18.4 6.6a9 9 0 1 1-12.8 0\"/>",
|
|
376
401
|
restart: "<path d=\"M21 12a9 9 0 1 1-2.64-6.36\"/><path d=\"M21 3v6h-6\"/>"
|
|
@@ -444,6 +469,12 @@ var WORKSPACE_PLACES = [
|
|
|
444
469
|
href: "/settings",
|
|
445
470
|
icon: "settings",
|
|
446
471
|
label: "Settings"
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
place: "help",
|
|
475
|
+
href: "/help",
|
|
476
|
+
icon: "help",
|
|
477
|
+
label: "Help"
|
|
447
478
|
}
|
|
448
479
|
];
|
|
449
480
|
/**
|
|
@@ -766,9 +797,10 @@ var FINDING_COPY_CLASS = "pr-helper-finding-copy";
|
|
|
766
797
|
* buttons, where an icon alone read as decoration. In the heading it is on its own, and a word
|
|
767
798
|
* would give a utility the weight of the title beside it. What it does is said by a hover
|
|
768
799
|
* tooltip (`data-tip`, the same CSS tooltip the pills use, not a native `title`) and by its
|
|
769
|
-
* accessible name.
|
|
770
|
-
*
|
|
771
|
-
*
|
|
800
|
+
* accessible name. After a click it shows a green check and says "Copied" (or "Copy failed") in
|
|
801
|
+
* the bubble below it, the words used everywhere else, but not inline: beside it is Ask Buddy. The
|
|
802
|
+
* resting name and tip are kept on the button so the reset returns to "Copy finding" rather than to
|
|
803
|
+
* a chat reply's "Copy message".
|
|
772
804
|
*/
|
|
773
805
|
function renderFindingCopySource(source, kind) {
|
|
774
806
|
const name = kind === "issue" ? "Copy finding" : "Copy question";
|
|
@@ -781,11 +813,12 @@ function renderFindingCopySource(source, kind) {
|
|
|
781
813
|
/** Where a finding's button keeps its resting word and name. Absent on a chat reply's. */
|
|
782
814
|
var IDLE_LABEL_ATTR = "data-copy-idle-label";
|
|
783
815
|
var IDLE_ARIA_ATTR = "data-copy-idle-aria";
|
|
816
|
+
var IDLE_TIP_ATTR = "data-copy-idle-tip";
|
|
784
817
|
function button(handle, idle) {
|
|
785
818
|
const { icon } = STATES$1.idle;
|
|
786
819
|
const label = idle?.label ?? STATES$1.idle.label;
|
|
787
820
|
const aria = idle?.aria ?? STATES$1.idle.aria;
|
|
788
|
-
const remembered = idle ? ` ${IDLE_LABEL_ATTR}="${escapeHtml$1(label)}" ${IDLE_ARIA_ATTR}="${escapeHtml$1(aria)}"` : "";
|
|
821
|
+
const remembered = idle ? ` ${IDLE_LABEL_ATTR}="${escapeHtml$1(label)}" ${IDLE_ARIA_ATTR}="${escapeHtml$1(aria)}" ${IDLE_TIP_ATTR}="${escapeHtml$1(idle.tip)}"` : "";
|
|
789
822
|
const control = `<button type="button" class="${idle ? `${MESSAGE_COPY_CLASS} ${FINDING_COPY_CLASS}` : MESSAGE_COPY_CLASS}" ${handle}${remembered}${idle ? ` data-tip="${escapeHtml$1(idle.tip)}"` : ""} aria-label="${escapeHtml$1(aria)}"><span class="${MESSAGE_COPY_CLASS}-icon" aria-hidden="true">${svg(icon)}</span><span class="${MESSAGE_COPY_CLASS}-label"${label ? "" : " hidden"}>${escapeHtml$1(label)}</span></button>`;
|
|
790
823
|
return idle ? control : `<div class="${MESSAGE_ACTIONS_CLASS}">${control}</div>`;
|
|
791
824
|
}
|
|
@@ -806,8 +839,7 @@ function svg(paths) {
|
|
|
806
839
|
* closes and positions exactly as that one does. The panel is empty here and filled when it opens,
|
|
807
840
|
* because it lists the reviewer's saved prompts and those change without the page re-rendering.
|
|
808
841
|
*
|
|
809
|
-
*
|
|
810
|
-
* there for this to open, so `ReviewTabOptions.askBuddy` gates it and the extension never sets it.
|
|
842
|
+
* The host decides where the answer appears; `ReviewTabOptions.askBuddy` says the host has wired it.
|
|
811
843
|
*/
|
|
812
844
|
var ASK_BUDDY_LABEL = "Ask Buddy";
|
|
813
845
|
/** Four-point sparkle, the conventional mark for "ask the AI". currentColor, like Copy beside it. */
|
|
@@ -5011,6 +5043,43 @@ function renderSubject(changeSet) {
|
|
|
5011
5043
|
<span>${against}</span>`;
|
|
5012
5044
|
}
|
|
5013
5045
|
/**
|
|
5046
|
+
* Which commits this review is of, opened from the bar beside what it compares.
|
|
5047
|
+
*
|
|
5048
|
+
* Beside `.prb-change-ref` rather than inside it, because that element clips to keep the bar one
|
|
5049
|
+
* line and a panel inside it would be cut off. The pill's own label is a fact (how many commits),
|
|
5050
|
+
* so the bar gains information even closed. Facts only, and about this review: the general
|
|
5051
|
+
* explanation is the Help page's, linked when there is a server to answer the link.
|
|
5052
|
+
*
|
|
5053
|
+
* The count comes from the stored commit list, or from the authors' counts for a review made before
|
|
5054
|
+
* the list was recorded, which then says how many without listing them. Neither: no count at all,
|
|
5055
|
+
* never a zero.
|
|
5056
|
+
*/
|
|
5057
|
+
function renderRange(changeSet, live) {
|
|
5058
|
+
const commits = changeSet.commits;
|
|
5059
|
+
const count = commits?.length ?? changeSet.authors?.reduce((sum, author) => sum + author.commits, 0);
|
|
5060
|
+
const counted = count !== void 0 && count > 0 ? count : void 0;
|
|
5061
|
+
const plural = (n) => `${n} commit${n === 1 ? "" : "s"}`;
|
|
5062
|
+
const sha = (value) => `<code>${escapeHtml$1(value.slice(0, 7))}</code>`;
|
|
5063
|
+
const lines = [];
|
|
5064
|
+
if (changeSet.targetType === "commit") {
|
|
5065
|
+
const parent = changeSet.parentSha;
|
|
5066
|
+
const against = !parent ? "the empty tree" : `its ${(changeSet.parentCount ?? 1) > 1 ? "first parent" : "parent"} ${sha(parent)}`;
|
|
5067
|
+
lines.push(`The change commit ${sha(changeSet.data.pullRequest.head.sha)} made, compared against ${against}.`);
|
|
5068
|
+
} else {
|
|
5069
|
+
lines.push(changeSet.mergeBase ? `Compared against <code>${escapeHtml$1(changeSet.baseRef)}</code>, from where this branch split off at ${sha(changeSet.mergeBase)}.` : `Compared against <code>${escapeHtml$1(changeSet.baseRef)}</code>.`);
|
|
5070
|
+
if (commits && commits.length > 0) {
|
|
5071
|
+
const more = commits.length > 10 ? ` and ${commits.length - 10} more` : "";
|
|
5072
|
+
lines.push(`Changes across ${plural(commits.length)}: ${commits.slice(0, 10).map(sha).join(" ")}${more}.`);
|
|
5073
|
+
} else if (counted) lines.push(`Changes across ${plural(counted)}.`);
|
|
5074
|
+
}
|
|
5075
|
+
lines.push(changeSet.includesUncommitted ? "This review also read uncommitted changes in the working tree." : "Uncommitted changes are never part of a review.");
|
|
5076
|
+
if (live) lines.push("<a href=\"/help#what-gets-reviewed\">How review ranges work</a>");
|
|
5077
|
+
return `<details class="prb-range" data-range-details>
|
|
5078
|
+
<summary class="prb-range-summary">${changeSet.targetType === "commit" ? "1 commit" : counted ? plural(counted) : "Range"}</summary>
|
|
5079
|
+
<div class="prb-range-panel">${lines.map((line) => `<p>${line}</p>`).join("")}</div>
|
|
5080
|
+
</details>`;
|
|
5081
|
+
}
|
|
5082
|
+
/**
|
|
5014
5083
|
* The review's own bar, below the global strip.
|
|
5015
5084
|
*
|
|
5016
5085
|
* Everything here is about this review and nothing else is: the strip above carries the workspace's
|
|
@@ -5031,6 +5100,7 @@ function renderReviewBar(view, freshness) {
|
|
|
5031
5100
|
${changeSet.includesUncommitted ? "<span class=\"prb-file-status\">+ working tree</span>" : ""}
|
|
5032
5101
|
${renderAuthor(view)}
|
|
5033
5102
|
</span>
|
|
5103
|
+
${renderRange(changeSet, Boolean(view.token))}
|
|
5034
5104
|
<div class="prb-header-stats">
|
|
5035
5105
|
<span>${fileCount} file${fileCount === 1 ? "" : "s"}</span>
|
|
5036
5106
|
<span><span class="prb-add">+${additions}</span> <span class="prb-del">−${deletions}</span></span>
|
|
@@ -5635,8 +5705,10 @@ function renderAsk(view, sections, paused = false) {
|
|
|
5635
5705
|
tabindex="0"
|
|
5636
5706
|
></div>
|
|
5637
5707
|
<div class="prb-ask-header">
|
|
5638
|
-
<
|
|
5639
|
-
|
|
5708
|
+
<hgroup class="prb-ask-heading">
|
|
5709
|
+
<h2>Ask Buddy AI</h2>
|
|
5710
|
+
<p class="prb-ask-scope">Asking about: <span data-ask-scope>the whole change</span></p>
|
|
5711
|
+
</hgroup>
|
|
5640
5712
|
${renderFocusToggle("buddy", "Buddy AI")}
|
|
5641
5713
|
<button type="button" class="prb-ask-hide" data-ask-toggle aria-controls="prb-ask" aria-label="Hide Buddy AI">${ASK_HIDE_ICON}</button>
|
|
5642
5714
|
</div>
|
|
@@ -7900,6 +7972,87 @@ function defaultFetchDeps() {
|
|
|
7900
7972
|
};
|
|
7901
7973
|
}
|
|
7902
7974
|
//#endregion
|
|
7975
|
+
//#region ../../packages/review-harness/src/workspace/help_page.ts
|
|
7976
|
+
/** The diagrams are text on purpose: they are the same ones `prreviewbuddy --help` prints. */
|
|
7977
|
+
var TOPICS = [{
|
|
7978
|
+
id: "what-gets-reviewed",
|
|
7979
|
+
title: "What gets reviewed",
|
|
7980
|
+
body: `<p>A review is the combined change between a base and the branch you review, shown as one
|
|
7981
|
+
diff. It is not just the latest commit. The base determines the split point: the commit where
|
|
7982
|
+
your branch left it (git calls this the merge-base). PR Review Buddy reviews the changes on your
|
|
7983
|
+
branch since that point.</p>
|
|
7984
|
+
<pre class="prb-help-diagram">main: A---B---C
|
|
7985
|
+
\\
|
|
7986
|
+
feature: D---E</pre>
|
|
7987
|
+
<p>Reviewing <code>feature</code> against <code>main</code> covers D and E together. C is on
|
|
7988
|
+
main but not on your branch, so it is not part of the review, and the review does not show its
|
|
7989
|
+
changes as removed. Commits already in the base are never reviewed again.</p>
|
|
7990
|
+
|
|
7991
|
+
<h3>Local main and origin/main</h3>
|
|
7992
|
+
<p>Your local <code>main</code> can hold commits that are not on <code>origin/main</code> yet,
|
|
7993
|
+
such as a merge you have not pushed. When the two are different commits, the one you choose
|
|
7994
|
+
moves the split point.</p>
|
|
7995
|
+
<pre class="prb-help-diagram">origin/main: A---B
|
|
7996
|
+
\\
|
|
7997
|
+
local main: C---D
|
|
7998
|
+
\\
|
|
7999
|
+
feature: E</pre>
|
|
8000
|
+
<ul>
|
|
8001
|
+
<li>Against local <code>main</code>, the review covers E.</li>
|
|
8002
|
+
<li>Against <code>origin/main</code>, it covers C, D and E together.</li>
|
|
8003
|
+
</ul>
|
|
8004
|
+
<p>When nobody names a base and the two differ, PR Review Buddy does not guess. It asks which
|
|
8005
|
+
one you mean and says which commits each would review.</p>
|
|
8006
|
+
|
|
8007
|
+
<h3>Uncommitted changes</h3>
|
|
8008
|
+
<p>Never included. A review is a fixed snapshot of your commits, made in a checkout of its own,
|
|
8009
|
+
so work you have not committed is not part of it.</p>
|
|
8010
|
+
|
|
8011
|
+
<h3>Choosing a base</h3>
|
|
8012
|
+
<p>The base is chosen when the review is made, in the terminal:</p>
|
|
8013
|
+
<ul>
|
|
8014
|
+
<li><code>prreviewbuddy review --base refs/heads/main</code> for everything on your branch since it left your local main</li>
|
|
8015
|
+
<li><code>prreviewbuddy review --base origin/main</code> for everything on your branch not yet on origin/main</li>
|
|
8016
|
+
<li><code>prreviewbuddy review --commit <commit></code> for one commit, against its parent</li>
|
|
8017
|
+
</ul>
|
|
8018
|
+
<p>Every review shows its own range. The commit count beside the branch name at the top of a
|
|
8019
|
+
review opens its split point and the commits it covers.</p>`
|
|
8020
|
+
}];
|
|
8021
|
+
function renderHelpPage(view) {
|
|
8022
|
+
return `<!doctype html>
|
|
8023
|
+
<html lang="en"${themeAttribute(view.theme)}>
|
|
8024
|
+
<head>
|
|
8025
|
+
<meta charset="utf-8">
|
|
8026
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8027
|
+
<title>Help · PR Review Buddy</title>
|
|
8028
|
+
<link rel="icon" type="image/png" href="/assets/icon128.png">
|
|
8029
|
+
<link rel="stylesheet" href="/assets/sidebar.css">
|
|
8030
|
+
<link rel="stylesheet" href="/assets/shell.css">
|
|
8031
|
+
</head>
|
|
8032
|
+
<body class="prb-index-body" data-way-back="${escapeHtml$1(wayBackSentence())}">
|
|
8033
|
+
${renderGlobalBar({
|
|
8034
|
+
current: "help",
|
|
8035
|
+
unread: view.unreadNotifications ?? 0,
|
|
8036
|
+
driving: view.driving
|
|
8037
|
+
})}
|
|
8038
|
+
${renderContextBar("Help", "")}
|
|
8039
|
+
<div class="prb-shell">
|
|
8040
|
+
${renderWorkspaceRail({
|
|
8041
|
+
current: "help",
|
|
8042
|
+
unread: view.unreadNotifications ?? 0
|
|
8043
|
+
})}
|
|
8044
|
+
<main class="prb-index prb-help">
|
|
8045
|
+
${TOPICS.map((topic) => `<section class="prb-help-topic" id="${topic.id}">
|
|
8046
|
+
<h2>${escapeHtml$1(topic.title)}</h2>
|
|
8047
|
+
${topic.body}
|
|
8048
|
+
</section>`).join("")}
|
|
8049
|
+
</main>
|
|
8050
|
+
</div>
|
|
8051
|
+
<script src="/assets/help.js" type="module"><\/script>
|
|
8052
|
+
</body>
|
|
8053
|
+
</html>`;
|
|
8054
|
+
}
|
|
8055
|
+
//#endregion
|
|
7903
8056
|
//#region ../../packages/review-harness/src/workspace/notifications_page.ts
|
|
7904
8057
|
function renderNotificationsPage(view) {
|
|
7905
8058
|
const unread = view.notifications.filter((notification) => notification.readAt === void 0).length;
|
|
@@ -8234,7 +8387,8 @@ function extractAnswer(raw) {
|
|
|
8234
8387
|
* and wrongly.
|
|
8235
8388
|
*/
|
|
8236
8389
|
function buildPrompt$2(workspace, question, sectionId, cwd) {
|
|
8237
|
-
const
|
|
8390
|
+
const sections = workspace.session.result?.sections ?? [];
|
|
8391
|
+
const reading = readingThemeLine(sections, sectionId);
|
|
8238
8392
|
const recent = workspace.transcript.slice(-4);
|
|
8239
8393
|
return [
|
|
8240
8394
|
SYSTEM_PROMPT$1,
|
|
@@ -8246,11 +8400,14 @@ function buildPrompt$2(workspace, question, sectionId, cwd) {
|
|
|
8246
8400
|
"---",
|
|
8247
8401
|
"DIFF:",
|
|
8248
8402
|
workspace.diff,
|
|
8249
|
-
...
|
|
8403
|
+
...reading ? ["---", reading] : [],
|
|
8250
8404
|
...recent.length > 0 ? [
|
|
8251
8405
|
"---",
|
|
8252
8406
|
"EARLIER IN THIS CONVERSATION:",
|
|
8253
|
-
recent.map((entry) =>
|
|
8407
|
+
recent.map((entry) => {
|
|
8408
|
+
const where = askedWhere(sections, entry.sectionId);
|
|
8409
|
+
return `Q${where ? ` ${where}` : ""}: ${entry.question}\nA: ${entry.answer}`;
|
|
8410
|
+
}).join("\n\n")
|
|
8254
8411
|
] : [],
|
|
8255
8412
|
"---",
|
|
8256
8413
|
`QUESTION: ${question}`
|
|
@@ -9263,6 +9420,7 @@ async function handle(req, res, options) {
|
|
|
9263
9420
|
if (url.pathname === "/open") return handleOpen(req, res, url.searchParams, options.indexToken);
|
|
9264
9421
|
if (url.pathname === "/settings") return void handleSettings(req, res, url.searchParams, options.indexToken);
|
|
9265
9422
|
if (url.pathname === "/notifications") return void handleNotifications(req, res, url.searchParams, options.indexToken);
|
|
9423
|
+
if (url.pathname === "/help") return void handleHelp(req, res, url.searchParams, options.indexToken);
|
|
9266
9424
|
if (url.pathname === "/notifications/open") return handleNotificationOpen(req, res, url.searchParams, options.indexToken);
|
|
9267
9425
|
if (url.pathname === "/notifications/recent") return handleRecentNotifications(req, res, url.searchParams, options.indexToken);
|
|
9268
9426
|
if (url.pathname === "/pin") return handlePin(req, res, options.indexToken);
|
|
@@ -9472,6 +9630,18 @@ async function handleSettings(req, res, params, indexToken) {
|
|
|
9472
9630
|
}), indexCookieHeader(indexToken));
|
|
9473
9631
|
}
|
|
9474
9632
|
/**
|
|
9633
|
+
* Help. Static, but still behind the index's credential like every other workspace place: its
|
|
9634
|
+
* rail and strip carry the unread count, which is this machine's state.
|
|
9635
|
+
*/
|
|
9636
|
+
function handleHelp(req, res, params, indexToken) {
|
|
9637
|
+
if (!indexAuthorised(req, params, indexToken)) return refuseIndex(req, res);
|
|
9638
|
+
sendHtml(res, 200, renderHelpPage({
|
|
9639
|
+
unreadNotifications: unreadCount$1(),
|
|
9640
|
+
driving: isDriving(),
|
|
9641
|
+
theme: readPreferences().theme
|
|
9642
|
+
}), indexCookieHeader(indexToken));
|
|
9643
|
+
}
|
|
9644
|
+
/**
|
|
9475
9645
|
* Whether the caller is a browser navigating, rather than something reading the API.
|
|
9476
9646
|
*
|
|
9477
9647
|
* `Accept` is the only signal available and browsers are consistent about it: a navigation asks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prreviewbuddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.1",
|
|
4
4
|
"description": "Review a branch or pull request in an isolated checkout, from your terminal, with the coding agent you already have installed.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://prreviewbuddy.com",
|
package/static/help.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{c as e,t}from"./chunk-BeL0x-wt.js";t(`/restart`),e(window,()=>window.location.reload());
|