prreviewbuddy 0.30.0 → 0.31.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 +32 -0
- package/dist/{delete_review-B_2J2Wn9.js → delete_review-C2wax0ED.js} +256 -20
- package/dist/main.js +51 -10
- package/dist/server.js +92 -53
- package/package.json +1 -1
- package/static/shell.css +41 -23
- package/static/workspace.js +11 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.31.0
|
|
4
|
+
|
|
5
|
+
**Review a single commit.** `prreviewbuddy review --commit <commit>` reviews the change one commit
|
|
6
|
+
made, compared against the commit before it. Name the commit however git lets you: a sha, `HEAD~2`
|
|
7
|
+
or a tag. A merge commit is compared against its first parent, and the review says so; the very
|
|
8
|
+
first commit in a repository is compared against nothing, so every file in it is new.
|
|
9
|
+
|
|
10
|
+
A commit never changes, so its review does not go out of date. The review page says "Freshness:
|
|
11
|
+
Fixed" instead of offering to update it, and `--update` is refused with a pointer to `--reanalyse`,
|
|
12
|
+
which analyses the same commit again and keeps the earlier analysis beside the new one. If the commit
|
|
13
|
+
later disappears from your repository, after a force-push for example, the review still opens in
|
|
14
|
+
full and says the commit is gone. `--commit` cannot be combined with `--base`, because the commit
|
|
15
|
+
before it is the base; to compare a branch against a base of your choosing, use `--branch` with
|
|
16
|
+
`--base` as before.
|
|
17
|
+
|
|
18
|
+
Reviews of the same commit share one card on the reviews list, named `commit` and its short sha, and
|
|
19
|
+
searching the list for the sha finds it. A review of a commit and a review of the branch that
|
|
20
|
+
contains it stay separate.
|
|
21
|
+
|
|
22
|
+
**A detached checkout points at `--commit HEAD`.** Running `prreviewbuddy review` on a detached HEAD
|
|
23
|
+
is still refused, because there is no branch to file the review under, and the message now offers
|
|
24
|
+
`prreviewbuddy review --commit HEAD` to review the commit you are on.
|
|
25
|
+
|
|
26
|
+
**A calmer Files tab.** Each file's issue and question counts are now small icons with a number, in
|
|
27
|
+
a column of their own on the right, instead of labels that squeezed long filenames onto several
|
|
28
|
+
lines. The label naming a second theme that also mentions a file is gone: the file is already listed
|
|
29
|
+
under its own theme, and its counts still include every finding. Added, modified, removed and renamed
|
|
30
|
+
files now each have their own colour, and the labels are all one width so the paths line up.
|
|
31
|
+
|
|
32
|
+
**The review header stays on one line.** On a narrow window the branch, commit and controls at the
|
|
33
|
+
top of a review no longer wrap onto a second line.
|
|
34
|
+
|
|
3
35
|
## 0.30.0
|
|
4
36
|
|
|
5
37
|
**Usage telemetry is now on by default.** Non-identifying usage telemetry is now enabled by default
|
|
@@ -2060,6 +2060,17 @@ var EmptyChangeError = class extends Error {
|
|
|
2060
2060
|
}
|
|
2061
2061
|
};
|
|
2062
2062
|
/**
|
|
2063
|
+
* Whether a stored change set is a review of one commit. See `ChangeSet.targetType`.
|
|
2064
|
+
*
|
|
2065
|
+
* A function rather than the comparison written out, and not only for tidiness:
|
|
2066
|
+
* `freshness_read_only.test.ts` refuses any quoted git subcommand in `freshness.ts`, and the
|
|
2067
|
+
* discriminator's value is spelled the same as one. That guard is right to be blunt, so the
|
|
2068
|
+
* comparison lives here, beside the field it reads.
|
|
2069
|
+
*/
|
|
2070
|
+
function isCommitReview(changeSet) {
|
|
2071
|
+
return changeSet.targetType === "commit";
|
|
2072
|
+
}
|
|
2073
|
+
/**
|
|
2063
2074
|
* Decide what this change is being compared against.
|
|
2064
2075
|
*
|
|
2065
2076
|
* Refuses rather than guessing. A base we picked wrongly produces a guide that looks completely
|
|
@@ -2142,7 +2153,7 @@ async function defaultRemoteBranch(repoPath) {
|
|
|
2142
2153
|
async function buildChangeSet(options) {
|
|
2143
2154
|
const repoPath = (await git(options.repoPath, ["rev-parse", "--show-toplevel"])).trim();
|
|
2144
2155
|
const base = options.resolvedBase ?? await resolveBase(repoPath, options.base);
|
|
2145
|
-
const mergeBase = await mergeBaseOf(repoPath, base, "HEAD");
|
|
2156
|
+
const mergeBase = base.exact ? base.revision : await mergeBaseOf(repoPath, base, "HEAD");
|
|
2146
2157
|
const headRef = await currentBranch(repoPath);
|
|
2147
2158
|
const isLinkedWorktree = await linkedWorktree(repoPath);
|
|
2148
2159
|
const files = await collectFiles(repoPath, [mergeBase, "HEAD"]);
|
|
@@ -2150,7 +2161,7 @@ async function buildChangeSet(options) {
|
|
|
2150
2161
|
disagreement(files.length, base.display, options.expected);
|
|
2151
2162
|
const blobShas = await headBlobShas(repoPath);
|
|
2152
2163
|
for (const file of files) file.sha = blobShas.get(file.name) ?? "";
|
|
2153
|
-
const { subjects, authors } = await commitLog(repoPath, mergeBase);
|
|
2164
|
+
const { subjects, authors } = await commitLog(repoPath, base.exact && !await refExists(repoPath, mergeBase) ? "HEAD" : `${mergeBase}..HEAD`);
|
|
2154
2165
|
return {
|
|
2155
2166
|
data: {
|
|
2156
2167
|
pullRequest: {
|
|
@@ -2286,12 +2297,12 @@ async function currentBranch(repoPath) {
|
|
|
2286
2297
|
* Uncommitted work has no author in the log, and needs none. It belongs to whoever is sitting at
|
|
2287
2298
|
* this checkout, and that is the person reading the page.
|
|
2288
2299
|
*/
|
|
2289
|
-
async function commitLog(repoPath,
|
|
2300
|
+
async function commitLog(repoPath, range) {
|
|
2290
2301
|
const out = await git(repoPath, [
|
|
2291
2302
|
"log",
|
|
2292
2303
|
"--reverse",
|
|
2293
2304
|
"--format=%an%x1f%s",
|
|
2294
|
-
|
|
2305
|
+
range
|
|
2295
2306
|
]);
|
|
2296
2307
|
const subjects = [];
|
|
2297
2308
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -2365,7 +2376,7 @@ async function mergeBaseOf(repoPath, base, head) {
|
|
|
2365
2376
|
*/
|
|
2366
2377
|
async function hasCommittedChanges(repoPath, base, head) {
|
|
2367
2378
|
return parseNameStatus(await git(repoPath, [
|
|
2368
|
-
...rangeDiff([await mergeBaseOf(repoPath, base, head), head]),
|
|
2379
|
+
...rangeDiff([base.exact ? base.revision : await mergeBaseOf(repoPath, base, head), head]),
|
|
2369
2380
|
"--name-status",
|
|
2370
2381
|
"-z"
|
|
2371
2382
|
])).length > 0;
|
|
@@ -2610,6 +2621,97 @@ function ambiguous(named, found) {
|
|
|
2610
2621
|
].join("\n");
|
|
2611
2622
|
}
|
|
2612
2623
|
//#endregion
|
|
2624
|
+
//#region ../../packages/review-harness/src/workspace/commit_target.ts
|
|
2625
|
+
/**
|
|
2626
|
+
* One commit the reviewer named, resolved to what it introduced, or refused.
|
|
2627
|
+
*
|
|
2628
|
+
* `prreviewbuddy review --commit 9907c64` reviews *the change that commit made*, which is the diff
|
|
2629
|
+
* against its first parent. That is a different object from everything else this product reviews,
|
|
2630
|
+
* and the difference is not the spelling of the target but what can be done to it afterwards.
|
|
2631
|
+
*
|
|
2632
|
+
* **A branch is followed; a commit is not.** `branch_target.ts` refuses a commit and says why: a
|
|
2633
|
+
* review follows its branch so it can be brought up to date as commits arrive, and a commit has
|
|
2634
|
+
* nowhere to move to. That refusal stands. This module is the other surface that docblock asked for,
|
|
2635
|
+
* and the thing it has to supply that `--branch` could not is an answer to what the lineage key is:
|
|
2636
|
+
* the commit's own full sha, never a ref manufactured from it. See `lineageKeyFor`.
|
|
2637
|
+
*
|
|
2638
|
+
* **The parent is the base, and the reviewer cannot name another one.** `--commit` with `--base` is
|
|
2639
|
+
* refused in the parser, because the two together are arbitrary range review arriving by the back
|
|
2640
|
+
* door: the findings would be about a different change under a page still naming the commit.
|
|
2641
|
+
*
|
|
2642
|
+
* **Nothing fetches,** for the same reason as `branch_target.ts`: the reviewer named something they
|
|
2643
|
+
* believe is here, and a commit-ish that does not resolve locally is far more often a typo than a
|
|
2644
|
+
* missing fetch.
|
|
2645
|
+
*/
|
|
2646
|
+
async function resolveNamedCommit(repoPath, value) {
|
|
2647
|
+
const named = value.trim();
|
|
2648
|
+
if (!named) throw new TargetError("No commit was named. Give `--commit` a commit, such as `--commit 9907c64` or `--commit HEAD~2`.");
|
|
2649
|
+
const sha = await peel(repoPath, named);
|
|
2650
|
+
const parents = (await git(repoPath, [
|
|
2651
|
+
"rev-list",
|
|
2652
|
+
"--parents",
|
|
2653
|
+
"-n",
|
|
2654
|
+
"1",
|
|
2655
|
+
sha
|
|
2656
|
+
])).trim().split(/\s+/).slice(1);
|
|
2657
|
+
const subject = (await git(repoPath, [
|
|
2658
|
+
"log",
|
|
2659
|
+
"-1",
|
|
2660
|
+
"--format=%s",
|
|
2661
|
+
sha
|
|
2662
|
+
])).trim();
|
|
2663
|
+
return {
|
|
2664
|
+
sha,
|
|
2665
|
+
parentSha: parents[0] ?? null,
|
|
2666
|
+
parentCount: parents.length,
|
|
2667
|
+
subject
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* The empty tree, asked of the repository rather than written down.
|
|
2672
|
+
*
|
|
2673
|
+
* A root commit is compared against nothing, and git spells nothing as the empty tree object. The
|
|
2674
|
+
* constant everybody knows, `4b825dc642cb6eb9a060e54bf8d69288fbee4904`, is the **sha-1** spelling of
|
|
2675
|
+
* it, and a repository created with `--object-format=sha256` has a different one. Hardcoding it
|
|
2676
|
+
* would make root-commit review silently wrong there rather than loudly broken, so the repository is
|
|
2677
|
+
* asked. `/dev/null` is an empty file, and hashing one as a tree is the documented way to name it.
|
|
2678
|
+
*/
|
|
2679
|
+
async function emptyTreeSha(repoPath) {
|
|
2680
|
+
return (await git(repoPath, [
|
|
2681
|
+
"hash-object",
|
|
2682
|
+
"-t",
|
|
2683
|
+
"tree",
|
|
2684
|
+
"/dev/null"
|
|
2685
|
+
])).trim();
|
|
2686
|
+
}
|
|
2687
|
+
/**
|
|
2688
|
+
* The full commit sha behind whatever was typed, or a refusal naming what it found instead.
|
|
2689
|
+
*
|
|
2690
|
+
* A tree or a blob is refused **by name**, the way `branch_target.ts` refuses `refs/tags/v1` by
|
|
2691
|
+
* name: `--commit HEAD^{tree}` resolves perfectly well to an object that is right there, and telling
|
|
2692
|
+
* the reviewer it could not be found would send them looking for a commit they have.
|
|
2693
|
+
*/
|
|
2694
|
+
async function peel(repoPath, named) {
|
|
2695
|
+
try {
|
|
2696
|
+
return (await git(repoPath, [
|
|
2697
|
+
"rev-parse",
|
|
2698
|
+
"--verify",
|
|
2699
|
+
"--quiet",
|
|
2700
|
+
`${named}^{commit}`
|
|
2701
|
+
])).trim();
|
|
2702
|
+
} catch {}
|
|
2703
|
+
let type = "";
|
|
2704
|
+
try {
|
|
2705
|
+
type = (await git(repoPath, [
|
|
2706
|
+
"cat-file",
|
|
2707
|
+
"-t",
|
|
2708
|
+
named
|
|
2709
|
+
])).trim();
|
|
2710
|
+
} catch {}
|
|
2711
|
+
if (type === "tree" || type === "blob") throw new TargetError(`\`${named}\` is a ${type}, and a review is of a commit.\n\nA commit review is the change that commit introduced, which is its diff against its parent. A ${type} is a snapshot with nothing to compare it to. Name the commit instead.`);
|
|
2712
|
+
throw new TargetError(`There is nothing called \`${named}\` in this repository.\n\n\`--commit\` takes anything git can resolve to a commit: a sha, \`HEAD~2\`, a tag. If the commit is on a remote this clone has not seen, run \`git fetch\` and try again.`);
|
|
2713
|
+
}
|
|
2714
|
+
//#endregion
|
|
2613
2715
|
//#region ../../packages/review-harness/src/workspace/target.ts
|
|
2614
2716
|
/**
|
|
2615
2717
|
* What is being reviewed, decided once and used everywhere.
|
|
@@ -2628,6 +2730,7 @@ function ambiguous(named, found) {
|
|
|
2628
2730
|
*/
|
|
2629
2731
|
async function resolveTarget(input) {
|
|
2630
2732
|
const root = await repositoryRoot(input.repoPath);
|
|
2733
|
+
if (input.commitRef) return commitTarget(root, input.commitRef);
|
|
2631
2734
|
if (input.base) await verifyBase(root, input.base);
|
|
2632
2735
|
else await verifyDefaultBase(root);
|
|
2633
2736
|
const named = input.base ? { base: input.base } : {};
|
|
@@ -2684,6 +2787,46 @@ async function resolveTarget(input) {
|
|
|
2684
2787
|
};
|
|
2685
2788
|
}
|
|
2686
2789
|
/**
|
|
2790
|
+
* One commit, and the parent it will be compared against, settled here and never re-derived.
|
|
2791
|
+
*
|
|
2792
|
+
* The base is built as a `ResolvedBase` rather than left as a name for `resolveBase` to work out,
|
|
2793
|
+
* which is the seam the pull request path already uses: `ChangeSetOptions.resolvedBase` is
|
|
2794
|
+
* documented as "a base already decided by the caller... The caller that set it had an authority
|
|
2795
|
+
* this module does not". The forge is that authority there. Here it is git's own commit graph, which
|
|
2796
|
+
* is a stronger one still, and there is nothing for a ladder of branch names to contribute to it.
|
|
2797
|
+
*
|
|
2798
|
+
* `followRef` is deliberately absent. It exists so a later rebuild can re-resolve a moving base, and
|
|
2799
|
+
* this base cannot move: a commit's parent is the same commit for ever. Recording a ref here would
|
|
2800
|
+
* invite exactly the re-resolution the immutability is for.
|
|
2801
|
+
*
|
|
2802
|
+
* `exact` is set because the parent *is* the comparison point. For an ordinary commit that agrees
|
|
2803
|
+
* with `merge-base(parent, commit)` anyway; for a merge it is what pins the diff to the first parent
|
|
2804
|
+
* rather than to wherever the two histories converge, and for a root commit it is the only way the
|
|
2805
|
+
* empty tree can be a base at all.
|
|
2806
|
+
*/
|
|
2807
|
+
async function commitTarget(root, commitRef) {
|
|
2808
|
+
const commit = await resolveNamedCommit(root, commitRef);
|
|
2809
|
+
const short = commit.sha.slice(0, 7);
|
|
2810
|
+
const parent = commit.parentSha;
|
|
2811
|
+
const resolvedBase = parent ? {
|
|
2812
|
+
revision: parent,
|
|
2813
|
+
display: parent.slice(0, 7),
|
|
2814
|
+
exact: true
|
|
2815
|
+
} : {
|
|
2816
|
+
revision: await emptyTreeSha(root),
|
|
2817
|
+
display: "the empty tree",
|
|
2818
|
+
exact: true
|
|
2819
|
+
};
|
|
2820
|
+
return {
|
|
2821
|
+
originRepoPath: root,
|
|
2822
|
+
branch: short,
|
|
2823
|
+
sha: commit.sha,
|
|
2824
|
+
base: resolvedBase.display,
|
|
2825
|
+
resolvedBase,
|
|
2826
|
+
commit
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
/**
|
|
2687
2830
|
* Which commit this request was cut from, decided before anything expensive happens.
|
|
2688
2831
|
*
|
|
2689
2832
|
* Strict, and it refuses rather than falling back to a local branch of the right name. The local
|
|
@@ -2818,6 +2961,14 @@ async function uncommittedIn(root, target) {
|
|
|
2818
2961
|
* to the display name, which `resolveBase` now resolves remote-first anyway.
|
|
2819
2962
|
*/
|
|
2820
2963
|
function baseOf(changeSet) {
|
|
2964
|
+
if (changeSet.targetType === "commit" && changeSet.mergeBase) return {
|
|
2965
|
+
base: changeSet.baseRef,
|
|
2966
|
+
resolvedBase: {
|
|
2967
|
+
revision: changeSet.mergeBase,
|
|
2968
|
+
display: changeSet.baseRef,
|
|
2969
|
+
exact: true
|
|
2970
|
+
}
|
|
2971
|
+
};
|
|
2821
2972
|
if (!changeSet.baseRef) return {};
|
|
2822
2973
|
const followRef = changeSet.baseResolvedRef;
|
|
2823
2974
|
return {
|
|
@@ -2885,7 +3036,7 @@ async function branchOf(root) {
|
|
|
2885
3036
|
branch = "";
|
|
2886
3037
|
}
|
|
2887
3038
|
if (branch) return branch;
|
|
2888
|
-
throw new TargetError("This checkout is on a detached HEAD, so there is no branch to file a review under.
|
|
3039
|
+
throw new TargetError("This checkout is on a detached HEAD, so there is no branch to file a review under. To review the commit you are on, run `prreviewbuddy review --commit HEAD`. Otherwise check out a branch, or name the pull request you want reviewed.");
|
|
2889
3040
|
}
|
|
2890
3041
|
/**
|
|
2891
3042
|
* The commit has to be here before a worktree can be pinned to it.
|
|
@@ -4036,9 +4187,22 @@ function reviewedRepositories() {
|
|
|
4036
4187
|
*
|
|
4037
4188
|
* A newline separates the halves because it cannot appear in any of them, so no set of values can
|
|
4038
4189
|
* collide by running together.
|
|
4190
|
+
*
|
|
4191
|
+
* **A commit review keys by the commit, not by a branch.** It follows nothing, so the ref half has
|
|
4192
|
+
* no meaning for it, and its display name is a short sha that two different commits can share.
|
|
4039
4193
|
*/
|
|
4040
4194
|
function lineageKey(review) {
|
|
4041
|
-
|
|
4195
|
+
if (review.targetType === "commit") return lineageKeyFor({
|
|
4196
|
+
kind: "commit",
|
|
4197
|
+
repoPath: review.repoPath,
|
|
4198
|
+
sha: review.commitSha ?? ""
|
|
4199
|
+
});
|
|
4200
|
+
return lineageKeyFor({
|
|
4201
|
+
kind: "branch",
|
|
4202
|
+
repoPath: review.repoPath,
|
|
4203
|
+
branch: review.branch,
|
|
4204
|
+
followedRef: review.headResolvedRef
|
|
4205
|
+
});
|
|
4042
4206
|
}
|
|
4043
4207
|
/**
|
|
4044
4208
|
* The same key, from the pieces, so a target and a stored review cannot arrive at it differently.
|
|
@@ -4047,8 +4211,19 @@ function lineageKey(review) {
|
|
|
4047
4211
|
* asks whether a target is the target of a stored review, and if it compared its own three fields
|
|
4048
4212
|
* by hand then widening the key here and widening the match there would be two changes that have
|
|
4049
4213
|
* to stay in step forever.
|
|
4214
|
+
*
|
|
4215
|
+
* **The commit arm is decided before the branch arm's defaulting, and that order is the point.** The
|
|
4216
|
+
* branch arm reads an absent ref as `refs/heads/<branch>`, so a commit reaching it would derive
|
|
4217
|
+
* `refs/heads/<sha>` and file under a ref nobody has. The middle segment is the literal `commit`,
|
|
4218
|
+
* which no branch key can produce in that position with a full sha after it, because a branch key's
|
|
4219
|
+
* last segment always starts with `refs/`.
|
|
4220
|
+
*
|
|
4221
|
+
* The branch arm is byte-for-byte the key every review on disk already has. Changing it re-files
|
|
4222
|
+
* every stored review under a new card, silently, on every machine.
|
|
4050
4223
|
*/
|
|
4051
|
-
function lineageKeyFor(
|
|
4224
|
+
function lineageKeyFor(subject) {
|
|
4225
|
+
if (subject.kind === "commit") return `${subject.repoPath}\ncommit\n${subject.sha}`;
|
|
4226
|
+
const { repoPath, branch, followedRef } = subject;
|
|
4052
4227
|
return `${repoPath}\n${branch}\n${followedRef ?? `refs/heads/${branch}`}`;
|
|
4053
4228
|
}
|
|
4054
4229
|
/**
|
|
@@ -4206,6 +4381,11 @@ function summarise(workspace) {
|
|
|
4206
4381
|
branch: workspace.changeSet.headRef,
|
|
4207
4382
|
baseRef: workspace.changeSet.baseRef ?? "",
|
|
4208
4383
|
...workspace.changeSet.headResolvedRef ? { headResolvedRef: workspace.changeSet.headResolvedRef } : {},
|
|
4384
|
+
...workspace.changeSet.targetType === "commit" ? {
|
|
4385
|
+
targetType: "commit",
|
|
4386
|
+
parentSha: workspace.changeSet.parentSha ?? null,
|
|
4387
|
+
...workspace.changeSet.parentCount !== void 0 ? { parentCount: workspace.changeSet.parentCount } : {}
|
|
4388
|
+
} : {},
|
|
4209
4389
|
repoPath: workspace.repoPath,
|
|
4210
4390
|
changedFiles: totalChangedFiles(workspace.changeSet.data),
|
|
4211
4391
|
additions: files.reduce((total, file) => total + (file.additions ?? 0), 0),
|
|
@@ -4224,7 +4404,7 @@ function summarise(workspace) {
|
|
|
4224
4404
|
pinned: workspace.pinned === true,
|
|
4225
4405
|
paths: files.slice(0, MAX_INDEXED_PATHS).map((file) => file.name),
|
|
4226
4406
|
prNumber: workspace.changeSet.data.pullRequest?.number || workspace.prContext?.pullRequest.number,
|
|
4227
|
-
commitSha: workspace.session.metadata?.commitSha || void 0,
|
|
4407
|
+
commitSha: workspace.session.metadata?.commitSha || (workspace.changeSet.targetType === "commit" ? workspace.changeSet.data.pullRequest.head.sha : "") || void 0,
|
|
4228
4408
|
author: describeAuthorship(workspace.changeSet, workspace.prContext?.pullRequest)?.label,
|
|
4229
4409
|
authorHandle: workspace.prContext?.pullRequest.author?.handle || void 0
|
|
4230
4410
|
};
|
|
@@ -5110,18 +5290,47 @@ async function refSha(repoPath, ref) {
|
|
|
5110
5290
|
* - **Describe, do not re-analyse.** The workspace says what changed. Deciding what it means for
|
|
5111
5291
|
* the findings is the analyser's job, and the analyser is Claude Code.
|
|
5112
5292
|
*/
|
|
5113
|
-
|
|
5293
|
+
/**
|
|
5294
|
+
* Freshness, and for a commit review whether its commit is still here, in one pass.
|
|
5295
|
+
*
|
|
5296
|
+
* **`latest()` is never asked about a commit review.** Its null already means "the branch is not in
|
|
5297
|
+
* this repository", a real answer with two documented causes. A commit review has no branch to look
|
|
5298
|
+
* for at all, and teaching that function to return null for this too would make one value mean two
|
|
5299
|
+
* unrelated things, told apart only by knowing the target type: the ambiguity a commit target exists
|
|
5300
|
+
* to remove. So this path does not reach it.
|
|
5301
|
+
*/
|
|
5302
|
+
async function checkReview(workspace) {
|
|
5114
5303
|
const reviewedSha = workspace.session.metadata?.commitSha || workspace.changeSet.data.pullRequest.head.sha || null;
|
|
5115
5304
|
const headSha = await readHead(workspace.repoPath);
|
|
5305
|
+
const commitReview = isCommitReview(workspace.changeSet);
|
|
5116
5306
|
return {
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5307
|
+
freshness: {
|
|
5308
|
+
checkedAt: Date.now(),
|
|
5309
|
+
code: commitReview ? fixedCode(reviewedSha) : await codeFreshness(workspace, reviewedSha, headSha, await latest(workspace)),
|
|
5310
|
+
checkout: await checkoutFreshness(workspace, headSha),
|
|
5311
|
+
conversation: workspace.prContext ? {
|
|
5312
|
+
attached: true,
|
|
5313
|
+
state: "unknown",
|
|
5314
|
+
importedAt: workspace.prContext.discussion.fetchedAt
|
|
5315
|
+
} : { attached: false }
|
|
5316
|
+
},
|
|
5317
|
+
availability: commitReview && reviewedSha ? {
|
|
5318
|
+
state: await commitExists(workspace.repoPath, reviewedSha) ? "available" : "missing",
|
|
5319
|
+
sha: reviewedSha
|
|
5320
|
+
} : null
|
|
5321
|
+
};
|
|
5322
|
+
}
|
|
5323
|
+
/** The code answer for a commit review. Nothing is compared, so every count is absent rather than zero. */
|
|
5324
|
+
function fixedCode(reviewedSha) {
|
|
5325
|
+
return {
|
|
5326
|
+
state: "fixed",
|
|
5327
|
+
reviewedSha,
|
|
5328
|
+
latestSha: null,
|
|
5329
|
+
comparison: null,
|
|
5330
|
+
newCommits: null,
|
|
5331
|
+
changedFiles: null,
|
|
5332
|
+
rewritten: false,
|
|
5333
|
+
reason: null
|
|
5125
5334
|
};
|
|
5126
5335
|
}
|
|
5127
5336
|
/**
|
|
@@ -5139,6 +5348,7 @@ async function checkFreshness(workspace) {
|
|
|
5139
5348
|
* a whole `FreshnessReport` around a single run would imply it had its own answer to both.
|
|
5140
5349
|
*/
|
|
5141
5350
|
async function checkCodeFreshness(workspace, reviewedSha) {
|
|
5351
|
+
if (isCommitReview(workspace.changeSet)) return fixedCode(reviewedSha);
|
|
5142
5352
|
return codeFreshness(workspace, reviewedSha, await readHead(workspace.repoPath), await latest(workspace));
|
|
5143
5353
|
}
|
|
5144
5354
|
/**
|
|
@@ -9748,6 +9958,7 @@ async function drive(jobId, workspaceId, runners, driver, signal) {
|
|
|
9748
9958
|
repoPath: job.target.originRepoPath,
|
|
9749
9959
|
headRef: job.target.branch,
|
|
9750
9960
|
...job.target.ref ? { headResolvedRef: job.target.ref } : {},
|
|
9961
|
+
...commitIdentity(job.target, stored.changeSet),
|
|
9751
9962
|
checkoutName,
|
|
9752
9963
|
isLinkedWorktree,
|
|
9753
9964
|
data: {
|
|
@@ -9916,7 +10127,7 @@ function pendingChangeSet(target) {
|
|
|
9916
10127
|
data: {
|
|
9917
10128
|
pullRequest: {
|
|
9918
10129
|
number: target.pullRequest?.number ?? 0,
|
|
9919
|
-
title: target.branch,
|
|
10130
|
+
title: target.commit?.subject || target.branch,
|
|
9920
10131
|
body: null,
|
|
9921
10132
|
base: {
|
|
9922
10133
|
ref: target.base ?? "",
|
|
@@ -9935,6 +10146,7 @@ function pendingChangeSet(target) {
|
|
|
9935
10146
|
repoPath: target.originRepoPath,
|
|
9936
10147
|
headRef: target.branch,
|
|
9937
10148
|
...target.ref ? { headResolvedRef: target.ref } : {},
|
|
10149
|
+
...commitIdentity(target, void 0),
|
|
9938
10150
|
baseRef: target.base ?? "",
|
|
9939
10151
|
mergeBase: "",
|
|
9940
10152
|
includesUncommitted: false,
|
|
@@ -9943,6 +10155,27 @@ function pendingChangeSet(target) {
|
|
|
9943
10155
|
};
|
|
9944
10156
|
}
|
|
9945
10157
|
/**
|
|
10158
|
+
* What makes a stored change set a commit review, carried from wherever it is known.
|
|
10159
|
+
*
|
|
10160
|
+
* Two sources, because a target is rebuilt from a stored review in three places (`reanalyseReview`,
|
|
10161
|
+
* `pinned_checkout.ts`, `kiss_run.ts`) and none of them has the `NamedCommit` the original command
|
|
10162
|
+
* resolved. They do not need it: the stored record already says what it is, and that answer is the
|
|
10163
|
+
* one to keep. A fresh target wins when there is one, since it is where the answer was decided.
|
|
10164
|
+
*/
|
|
10165
|
+
function commitIdentity(target, stored) {
|
|
10166
|
+
if (target.commit) return {
|
|
10167
|
+
targetType: "commit",
|
|
10168
|
+
parentSha: target.commit.parentSha,
|
|
10169
|
+
parentCount: target.commit.parentCount
|
|
10170
|
+
};
|
|
10171
|
+
if (stored?.targetType === "commit") return {
|
|
10172
|
+
targetType: "commit",
|
|
10173
|
+
parentSha: stored.parentSha ?? null,
|
|
10174
|
+
...stored.parentCount !== void 0 ? { parentCount: stored.parentCount } : {}
|
|
10175
|
+
};
|
|
10176
|
+
return {};
|
|
10177
|
+
}
|
|
10178
|
+
/**
|
|
9946
10179
|
* Rebuild the prompt a phase needs, from what the job and the workspace already hold.
|
|
9947
10180
|
*
|
|
9948
10181
|
* Not cached in memory: the daemon can restart between `context` finishing and `analysing`
|
|
@@ -10965,6 +11198,8 @@ function explainUpdateFailure(error, env, requestedModel) {
|
|
|
10965
11198
|
var RefreshUnavailableError = class extends Error {};
|
|
10966
11199
|
/** A fetch reaches the network, so it gets a ceiling. Nothing else here can hang. */
|
|
10967
11200
|
var FETCH_TIMEOUT_MS = 6e4;
|
|
11201
|
+
/** Said wherever an update is asked of a commit review. One sentence, so both surfaces agree. */
|
|
11202
|
+
var COMMIT_REVIEW_CANNOT_UPDATE = "This review is of one commit, and a commit cannot move, so there is nothing to bring it up to date with. To analyse the same commit again, reanalyse it instead.";
|
|
10968
11203
|
/**
|
|
10969
11204
|
* Computes and mutates the workspace in memory; does not persist.
|
|
10970
11205
|
*
|
|
@@ -10973,6 +11208,7 @@ var FETCH_TIMEOUT_MS = 6e4;
|
|
|
10973
11208
|
*/
|
|
10974
11209
|
async function updateReview(workspace, runners = {}) {
|
|
10975
11210
|
const reviewedSha = workspace.session.metadata?.commitSha || null;
|
|
11211
|
+
if (workspace.changeSet.targetType === "commit") return refuse(reviewedSha, false, COMMIT_REVIEW_CANNOT_UPDATE);
|
|
10976
11212
|
const held = await claimCheckoutJob(workspace, reviewedSha ?? "", {
|
|
10977
11213
|
purpose: "update",
|
|
10978
11214
|
...workspace.prContext ? { conversation: {
|
|
@@ -11578,4 +11814,4 @@ function lineageIds(id) {
|
|
|
11578
11814
|
return [.../* @__PURE__ */ new Set([id, ...lineage.map((review) => review.id)])];
|
|
11579
11815
|
}
|
|
11580
11816
|
//#endregion
|
|
11581
|
-
export { fillFileUrlTemplate as $, writeAgentPreference as $t, removeWorktree as A, workspaceRevision as At, workspaceUrl as B, isQuestionOutstanding as Bt, recordTelemetryUploadConsent as C, previousKissResult as Ct, ReviewBeingDeletedError as D, saveWorkspace as Dt, EXPLAIN_SIMPLY_PROMPT as E, reviewedRepositories as Et, clearServerRecord as F, queueStamp as Ft, DEFAULT_WORKSPACE_PORT as G, describeAuthorship as Gt, BUILD_VERSION as H, questionsOutstanding as Ht, ensureServer as I, readQueue as It, parseWorkspacePort as J, git as Jt, MAX_WORKSPACE_PORT as K, resolveTarget as Kt, readServerRecord as L, writeQueue as Lt, relativeTime as M, dequeueOnComplete as Mt, readIndexToken as N, queueAdd as Nt, MANAGED_ROOT as O, summarise as Ot, bootstrapUrl as P, queueRemove as Pt, forgeResolver as Q, readAgentPreference as Qt, reviewsUrl as R, doneVerb as Rt, record as S, positionInLineage as St, withUsageRecorded as T, reviewedCommit as Tt, PACKAGE_NAME as U, reviewerDispositions as Ut, writeServerRecord as V, issuesOutstanding as Vt, feedbackUrl as W, processDiscussion as Wt, statedWorkspacePort as X, resolveModel as Xt, resolveWorkspacePort as Y, resolveAgent as Yt, writePortPreference as Z, clearAgentPreference as Zt, startKissJob as _, latestKissRun as _t, wasBlocked as a, agentFor as an, isTerminal as at, startJob as b, loadWorkspace as bt, updateReview as c, AgentUnavailableError as cn, PHASES as ct, askCheckout as d, purposeOf as dt, CONFIG_PATH as en, checkCodeFreshness as et, UpdateAlreadyRunningError as f, clearClaim as ft, recordKissRun as g, indexReviewGroups as gt, agentEnvOf as h, groupByLineage as ht, liveJobsFor as i, agentById as in, fail as it, modelHelpLines as j, workspaceStamps as jt, readMarker as k, touchWorkspace as kt, refreshPrContext as l, phasesFor as lt, runningJobs as m, followedRefName as mt, deleteReview as n, AGENT_IDS as nn, allJobs as nt, discardJob as o, detectAgents as on, loadJob as ot, liveUpdateFor as p, isClaimed as pt, MIN_WORKSPACE_PORT as q, displayRef as qt, lineageIds as r, DEFAULT_AGENT_ID as rn, endedAt as rt, RefreshUnavailableError as s, AgentCancelledError as sn, saveJob as st, deleteLineage as t, STORE_ROOT as tn,
|
|
11817
|
+
export { fillFileUrlTemplate as $, writeAgentPreference as $t, removeWorktree as A, workspaceRevision as At, workspaceUrl as B, isQuestionOutstanding as Bt, recordTelemetryUploadConsent as C, previousKissResult as Ct, ReviewBeingDeletedError as D, saveWorkspace as Dt, EXPLAIN_SIMPLY_PROMPT as E, reviewedRepositories as Et, clearServerRecord as F, queueStamp as Ft, DEFAULT_WORKSPACE_PORT as G, describeAuthorship as Gt, BUILD_VERSION as H, questionsOutstanding as Ht, ensureServer as I, readQueue as It, parseWorkspacePort as J, git as Jt, MAX_WORKSPACE_PORT as K, resolveTarget as Kt, readServerRecord as L, writeQueue as Lt, relativeTime as M, dequeueOnComplete as Mt, readIndexToken as N, queueAdd as Nt, MANAGED_ROOT as O, summarise as Ot, bootstrapUrl as P, queueRemove as Pt, forgeResolver as Q, readAgentPreference as Qt, reviewsUrl as R, doneVerb as Rt, record as S, positionInLineage as St, withUsageRecorded as T, reviewedCommit as Tt, PACKAGE_NAME as U, reviewerDispositions as Ut, writeServerRecord as V, issuesOutstanding as Vt, feedbackUrl as W, processDiscussion as Wt, statedWorkspacePort as X, resolveModel as Xt, resolveWorkspacePort as Y, resolveAgent as Yt, writePortPreference as Z, clearAgentPreference as Zt, startKissJob as _, latestKissRun as _t, wasBlocked as a, agentFor as an, isTerminal as at, startJob as b, loadWorkspace as bt, updateReview as c, AgentUnavailableError as cn, PHASES as ct, askCheckout as d, purposeOf as dt, CONFIG_PATH as en, checkCodeFreshness as et, UpdateAlreadyRunningError as f, clearClaim as ft, recordKissRun as g, indexReviewGroups as gt, agentEnvOf as h, groupByLineage as ht, liveJobsFor as i, agentById as in, fail as it, modelHelpLines as j, workspaceStamps as jt, readMarker as k, touchWorkspace as kt, refreshPrContext as l, phasesFor as lt, runningJobs as m, followedRefName as mt, deleteReview as n, AGENT_IDS as nn, allJobs as nt, discardJob as o, detectAgents as on, loadJob as ot, liveUpdateFor as p, isClaimed as pt, MIN_WORKSPACE_PORT as q, displayRef as qt, lineageIds as r, DEFAULT_AGENT_ID as rn, endedAt as rt, RefreshUnavailableError as s, AgentCancelledError as sn, saveJob as st, deleteLineage as t, STORE_ROOT as tn, checkReview as tt, isUnchanged as u, progressSteps as ut, reanalyseReview as v, lineageKeyFor as vt, telemetryUploadConsent as w, recentWorkspaces as wt, readEvents as x, matchingWorkspaceIds as xt, runJob as y, lineagePosition as yt, stopServer as z, isIssueOutstanding as zt };
|