prreviewbuddy 0.27.2 → 0.30.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 CHANGED
@@ -1,5 +1,77 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.30.0
4
+
5
+ **Usage telemetry is now on by default.** Non-identifying usage telemetry is now enabled by default
6
+ on installations that have not previously made a choice. Existing opt-outs remain respected: if you
7
+ answered no when PR Review Buddy asked, nothing is sent, and nothing about your machine changes.
8
+ The first-run question is gone, replaced by a one-time notice in the terminal that says what is
9
+ sent and how to stop it. That notice is shown only to installations the new default actually
10
+ changes, so a machine that already answered sees nothing.
11
+
12
+ Nothing new is collected. The same two events carry the same eight fields they did before: review
13
+ start and finish, a random installation ID, the version, the platform, the agent, whether the
14
+ analysis succeeded and how long it took. Never your code, diffs, file paths, repository names,
15
+ prompts or review content. Rows are still deleted after 90 days.
16
+
17
+ `prreviewbuddy config set telemetry-upload off` turns it off, as does the Privacy section of the
18
+ workspace Settings page, and `PRB_TELEMETRY=off` still stops the local log as well. The reasoning
19
+ for the change, and what it replaced, is written out at https://prreviewbuddy.com/privacy.
20
+
21
+ **Copy says "non-identifying" rather than "anonymous".** The events carry a random installation ID
22
+ that persists, and a stable identifier makes data pseudonymous however little else is attached to
23
+ it. The word changed; what is sent did not.
24
+
25
+ ## 0.29.1
26
+
27
+ **Icons in the review sidebar.** Every item in a review's sidebar now has an icon: Review, KISS,
28
+ Issues, Questions, Files, PR context, Diagrams, Recommendations, and the links at the bottom. All
29
+ reviews and Settings had small text symbols before; they now have full-size icons like the rest.
30
+ The themes in the review path keep their numbers.
31
+
32
+ **Review path themes start collapsed.** On a review's path page, each theme now opens folded to
33
+ its title, priority and size, so you can see the whole path at a glance. Click a theme to open it,
34
+ or choose it in the sidebar to jump straight to it opened.
35
+
36
+ ## 0.29.0
37
+
38
+ **Filter your reviews by author.** The reviews list has a dropdown beside the search that lists
39
+ everyone whose pull requests you have reviewed. Choose someone and the list shows only their
40
+ reviews. It works together with the filters beside it, so you can see, say, everything of one
41
+ person's that is still in progress. Reviews of a local branch with no pull request have no GitHub
42
+ username, so they show under Everyone only.
43
+
44
+ **Mark a review reviewed from the list.** Each review's menu in the reviews list now has Mark
45
+ reviewed, so you no longer have to open a review to mark it. Once it is reviewed, the same menu
46
+ offers Mark in progress to undo it. As before, marking a review reviewed takes it out of your
47
+ review queue.
48
+
49
+ **Delete a review from its own page.** The menu at the top of a review now has Delete review,
50
+ with the same confirmation the list uses. It deletes only the review you are looking at, not
51
+ earlier reviews of the same branch, and then takes you back to your reviews. A review that is
52
+ still being analysed cannot be deleted.
53
+
54
+ **Easier to scan sidebar.** In a review's sidebar, Issues, Questions, Files, PR context and the
55
+ links at the bottom are now white, so the review path above them stands out as its own list.
56
+
57
+ ## 0.28.0
58
+
59
+ **Save a review, and work through a queue.** The star on each review in your reviews list now
60
+ means Saved. A saved review still sits at the top of the list, and the new Saved filter shows only
61
+ those. For a batch you are working through right now, choose Add to review queue from a review's
62
+ menu: the Queue filter lists them in the order you added them, and Continue reviewing at the top of
63
+ the list opens the first one. Marking a review Reviewed takes it out of the queue. Moving it back
64
+ to In progress does not put it back, so the queue only ever holds what you chose to put there.
65
+
66
+ **Saved and queued reviews are kept for ninety days.** Other reviews are still kept for thirty
67
+ days. The ninety days count from the last time you opened the review, so one you keep coming back
68
+ to stays. A saved review is not kept forever: after ninety days without being opened it is removed
69
+ like any other.
70
+
71
+ **A saved review no longer disappears from the list.** The list shows your fifty most recent
72
+ branches, and a review saved or queued further back than that used to be missing from it
73
+ altogether. Saved and queued reviews are now always listed, in addition to the fifty most recent.
74
+
3
75
  ## 0.27.2
4
76
 
5
77
  **Choose the model a review runs under.** `prreviewbuddy review --model <model>` asks the agent for
package/README.md CHANGED
@@ -44,10 +44,11 @@ an assistant that has read the diff. The job is durable, so closing the terminal
44
44
  Your code goes to the coding agent you chose, exactly as it would if you had run that agent
45
45
  yourself. It does not pass through us, and no server of ours is in the path of a review.
46
46
 
47
- PR Review Buddy asks once, on first run, whether it may send anonymous usage counts: two event
48
- names, a random ID generated on your machine, the version, the platform, the agent and a duration.
49
- Never your code, diffs, file paths, repository names, prompts or review content, and never anything
50
- at all unless you said yes. [The full field table is published](https://prreviewbuddy.com/privacy).
47
+ PR Review Buddy sends non-identifying usage telemetry, on by default and off with one command: two
48
+ event names, a random ID generated on your machine, the version, the platform, the agent and a
49
+ duration. Never your code, diffs, file paths, repository names, prompts or review content.
50
+ `prreviewbuddy config set telemetry-upload off` stops it.
51
+ [The full field table is published](https://prreviewbuddy.com/privacy).
51
52
 
52
53
  More at [prreviewbuddy.com](https://prreviewbuddy.com).
53
54
 
@@ -80,7 +81,7 @@ prreviewbuddy open <review> open one review
80
81
  prreviewbuddy agents which coding agents you have
81
82
  prreviewbuddy config set agent <agent> remember which to use when you do not name one
82
83
  prreviewbuddy config set telemetry-upload on|off
83
- send anonymous usage counts, or stop
84
+ send usage telemetry, or stop. On by default
84
85
 
85
86
  prreviewbuddy uninstall remove everything this put on your machine
86
87
  ```
@@ -167,10 +168,10 @@ ID** and **Delete review**, and the menu on a card deletes every review of that
167
168
  </details>
168
169
 
169
170
  <details>
170
- <summary><b>Anonymous usage counts</b> — what is sent, and how to stop it</summary>
171
+ <summary><b>Usage telemetry</b> — what is sent, and how to stop it</summary>
171
172
 
172
- Two events reach a server we run, and only if you answered yes when asked on first run:
173
- `review_started` and `review_finished`. Between them they carry a random UUID generated on your
173
+ Two events reach a server we run, on by default since 0.30.0: `review_started` and
174
+ `review_finished`. Between them they carry a random UUID generated on your
174
175
  machine, the CLI version, the platform, which agent ran the analysis, a timestamp, whether it
175
176
  succeeded and how long it took. That is the whole list, and the server refuses any field that is
176
177
  not on it rather than accepting the request and dropping what it does not recognise.
@@ -3651,10 +3651,135 @@ function doneVerb(counts) {
3651
3651
  return counts.reviewerClosed > 0 ? "dealt with" : "resolved";
3652
3652
  }
3653
3653
  //#endregion
3654
+ //#region ../../packages/review-harness/src/workspace/queue.ts
3655
+ /**
3656
+ * The review queue: the set the reviewer is working through now, in the order they chose.
3657
+ *
3658
+ * **Its own file, not a field on `StoredWorkspace`.** The reason is already written at the top of
3659
+ * `store.ts`'s save path: every caller puts back a record it loaded earlier, `runJob` holds one for
3660
+ * an entire analysis, and the write clobbers whatever changed in between without saying so. Queue
3661
+ * membership changed from the index while an analysis was running would simply vanish. A record
3662
+ * also carries the whole diff inline, so a reorder would rewrite megabytes to move one string.
3663
+ *
3664
+ * **Not a key in `ui.json` either**, despite being reviewer state of exactly the kind that file
3665
+ * describes. `preferences.ts` read-modify-writes that file whole, and one unparsable byte in there
3666
+ * currently costs a pane width. Sharing it would make the same byte cost the working set.
3667
+ *
3668
+ * `STORE_ROOT` comes from `store_root.ts` rather than from `store.ts`, which is the import
3669
+ * `preferences.ts` uses. That one is safe only because nothing in `store.ts` imports preferences
3670
+ * back -- and the prune in there *does* import this module, so taking the same shortcut here would
3671
+ * close the cycle.
3672
+ */
3673
+ var QUEUE_PATH = join(STORE_ROOT, "queue.json");
3674
+ /**
3675
+ * An object rather than a bare array, because an array is the one JSON shape that cannot grow a
3676
+ * sibling field, and "when was this queued" is a plausible second column.
3677
+ *
3678
+ * `version` is not branched on today. It exists so that a later shape change is a decision rather
3679
+ * than a guess about what an old file meant.
3680
+ */
3681
+ var VERSION = 1;
3682
+ /**
3683
+ * What is queued, in order.
3684
+ *
3685
+ * Best-effort in the same way as `readPreferences`, and for the same reason: missing, unreadable
3686
+ * and corrupt all mean "nothing queued", and none of them is worth an error standing between the
3687
+ * reviewer and the list. Position is the array index throughout -- never stored per entry, because
3688
+ * two representations of one order eventually disagree about it.
3689
+ */
3690
+ function readQueue() {
3691
+ try {
3692
+ const parsed = JSON.parse(readFileSync(QUEUE_PATH, "utf8"));
3693
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [];
3694
+ const ids = parsed.ids;
3695
+ return Array.isArray(ids) ? normalise(ids) : [];
3696
+ } catch {
3697
+ return [];
3698
+ }
3699
+ }
3700
+ /**
3701
+ * Store a queue, and say what was stored.
3702
+ *
3703
+ * The return is what landed on disk, which is not necessarily what was handed in -- duplicates are
3704
+ * collapsed and the cap applies. Same contract as the prompt bank: the caller gets the list as
3705
+ * stored so a page can paint the truth rather than its own request.
3706
+ *
3707
+ * `null` on a write failure, and that is the point at which this stops resembling `preferences.ts`.
3708
+ * A pane width that fails to save costs one session and nothing depends on it. A queue entry that
3709
+ * fails to save is contradicted by a pill showing a count and by a button that navigates somewhere,
3710
+ * so the caller has to be able to tell the reviewer it did not happen.
3711
+ */
3712
+ function writeQueue(ids) {
3713
+ const next = normalise(ids);
3714
+ try {
3715
+ mkdirSync(STORE_ROOT, { recursive: true });
3716
+ writeFileSync(QUEUE_PATH, `${JSON.stringify({
3717
+ version: VERSION,
3718
+ ids: next
3719
+ }, null, 2)}\n`);
3720
+ return next;
3721
+ } catch {
3722
+ return null;
3723
+ }
3724
+ }
3725
+ /**
3726
+ * Append, and leave a review already queued exactly where it is.
3727
+ *
3728
+ * Adding something twice is a no-op rather than a move. Nothing in this UI reorders a queue, so a
3729
+ * second Add that silently promoted a review would be the only control in the product that did --
3730
+ * and it would do it without ever saying so.
3731
+ */
3732
+ function queueAdd(id) {
3733
+ const current = readQueue();
3734
+ if (current.includes(id)) return current;
3735
+ return writeQueue([...current, id]);
3736
+ }
3737
+ function queueRemove(id) {
3738
+ const current = readQueue();
3739
+ if (!current.includes(id)) return current;
3740
+ return writeQueue(current.filter((queued) => queued !== id));
3741
+ }
3742
+ /**
3743
+ * The invariant, as one named thing: the queue holds unfinished work only.
3744
+ *
3745
+ * Removing on completion is half of it. The other half is that this **does nothing** when
3746
+ * `complete` is false, and that half only exists as a written rule -- there is no line of code for
3747
+ * it to be visible in. Re-adding there would make membership something inferred from status rather
3748
+ * than chosen, so a reviewer who took a review out of the queue and later moved it back to In
3749
+ * progress would find it queued again with no idea why.
3750
+ */
3751
+ function dequeueOnComplete(id, complete) {
3752
+ if (!complete) return;
3753
+ queueRemove(id);
3754
+ }
3755
+ /**
3756
+ * A token for the index's revision, equality only.
3757
+ *
3758
+ * The queue changes only when a person deliberately changes it, so unlike a running job's progress
3759
+ * this cannot make the page reload itself in a loop. Absent reads as a stable empty string rather
3760
+ * than as a change, for the reason `refreshAction` gives: a missing field that reads as "different"
3761
+ * is a reload that asks again and reloads again.
3762
+ */
3763
+ function queueStamp() {
3764
+ try {
3765
+ return String(statSync(QUEUE_PATH).mtimeMs);
3766
+ } catch {
3767
+ return "";
3768
+ }
3769
+ }
3770
+ /** Strings only, first position wins, and never more than the cap. */
3771
+ function normalise(ids) {
3772
+ const seen = [];
3773
+ for (const id of ids) {
3774
+ if (typeof id !== "string" || id === "" || seen.includes(id)) continue;
3775
+ seen.push(id);
3776
+ if (seen.length === 50) break;
3777
+ }
3778
+ return seen;
3779
+ }
3780
+ //#endregion
3654
3781
  //#region ../../packages/review-harness/src/workspace/store.ts
3655
3782
  var WORKSPACES_DIR = join(STORE_ROOT, "workspaces");
3656
- /** Reviews older than this are cleared on write. A review of a branch from a month ago is history. */
3657
- var MAX_AGE_MS = 2592e6;
3658
3783
  function workspacePath(id) {
3659
3784
  return join(WORKSPACES_DIR, `${id}.json`);
3660
3785
  }
@@ -3944,14 +4069,38 @@ function followedRefName(branch, followedRef) {
3944
4069
  return followedRef;
3945
4070
  }
3946
4071
  /**
3947
- * The index's list: one entry per branch, each carrying its own history.
4072
+ * The index's list: recent branches, plus the ones the reviewer explicitly kept.
4073
+ *
4074
+ * `recentReviewGroups` above is a pure recency window, and it stays that way because other callers
4075
+ * want exactly that. This is the index's own question, and it is a different one, because Saved and
4076
+ * Queue exist to defeat recency: a review saved three weeks ago has to be reachable from the list
4077
+ * or the pill that claims to show it is simply broken.
3948
4078
  *
3949
- * The limit counts branches rather than reviews, and the grouping happens before it rather than
3950
- * after. Limiting first and grouping second would show a card claiming "2 reviews" for a branch
3951
- * that has five, which is worse than showing no count at all.
4079
+ * **Two windows, and the page shows their sum.** Drawing both from one limit would mean saving
4080
+ * eleven reviews pushed eleven recent ones off the page -- the working set eating the list it was
4081
+ * added to. So `limit` bounds recency, `keptLimit` bounds what was kept, and the most cards this
4082
+ * can return is the two added together. Neither constant says that on its own, which is why it is
4083
+ * said here.
3952
4084
  */
3953
- function recentReviewGroups(limit = 10) {
3954
- return groupByLineage(recentWorkspaces(Number.MAX_SAFE_INTEGER)).slice(0, limit);
4085
+ function indexReviewGroups(limit, keptLimit, keptIds) {
4086
+ return selectIndexGroups(groupByLineage(recentWorkspaces(Number.MAX_SAFE_INTEGER)), limit, keptLimit, keptIds);
4087
+ }
4088
+ /**
4089
+ * The choosing, with the disk left out, in the same spirit as `groupByLineage` next door.
4090
+ *
4091
+ * Membership is tested against a group's **whole history** rather than its newest review. A pin is
4092
+ * written on whichever review the star was clicked on, so a review saved and then re-analysed
4093
+ * carries its pin on the older entry; asking only `latest` would lose the card at exactly the
4094
+ * moment the reviewer was most likely to want it.
4095
+ *
4096
+ * The kept side is capped for the same reason the recent side is: `queue.json` is a file on a disk
4097
+ * somebody can edit, and an uncapped read of it is an index with five thousand cards on it.
4098
+ */
4099
+ function selectIndexGroups(groups, limit, keptLimit, keptIds) {
4100
+ const kept = [];
4101
+ const rest = [];
4102
+ for (const group of groups) (group.history.some((review) => keptIds.has(review.id)) ? kept : rest).push(group);
4103
+ return [...rest.slice(0, limit), ...kept.slice(0, keptLimit)].sort((a, b) => b.latest.createdAt - a.latest.createdAt);
3955
4104
  }
3956
4105
  /**
3957
4106
  * The grouping itself, with the disk left out.
@@ -4076,7 +4225,8 @@ function summarise(workspace) {
4076
4225
  paths: files.slice(0, MAX_INDEXED_PATHS).map((file) => file.name),
4077
4226
  prNumber: workspace.changeSet.data.pullRequest?.number || workspace.prContext?.pullRequest.number,
4078
4227
  commitSha: workspace.session.metadata?.commitSha || void 0,
4079
- author: describeAuthorship(workspace.changeSet, workspace.prContext?.pullRequest)?.label
4228
+ author: describeAuthorship(workspace.changeSet, workspace.prContext?.pullRequest)?.label,
4229
+ authorHandle: workspace.prContext?.pullRequest.author?.handle || void 0
4080
4230
  };
4081
4231
  }
4082
4232
  /**
@@ -4185,14 +4335,53 @@ function touchWorkspace(workspace) {
4185
4335
  saveWorkspace(workspace);
4186
4336
  } catch {}
4187
4337
  }
4338
+ /**
4339
+ * Housekeeping, in two tiers.
4340
+ *
4341
+ * The cheap gate comes first: anything inside the ordinary window is not a candidate under either
4342
+ * tier, so the great majority of files never reach the read below. Only those already past thirty
4343
+ * days pay for a parse -- and a parse here is over a record holding an entire diff, which is why
4344
+ * that ordering is load-bearing rather than tidy.
4345
+ *
4346
+ * The loop walks the directory without filtering by extension, which is what makes the suffix rule
4347
+ * matter: `<id>.tokens.jsonl` ages on its own mtime, and stripping only `.json` would leave a saved
4348
+ * review holding its record while its usage history was quietly deleted from under it. The tier
4349
+ * belongs to the review, not to the file.
4350
+ *
4351
+ * Exported for `retention.test.ts`. It is a real unit with a rule worth stating in cases, and the
4352
+ * alternative -- reaching it only through `createWorkspace` -- would test the retention policy
4353
+ * through a function that exists to do something else.
4354
+ */
4188
4355
  function pruneOldWorkspaces() {
4189
4356
  try {
4357
+ const queued = new Set(readQueue());
4190
4358
  for (const entry of readdirSync(WORKSPACES_DIR)) {
4191
4359
  const path = join(WORKSPACES_DIR, entry);
4192
- if (Date.now() - statSync(path).mtimeMs > MAX_AGE_MS) unlinkSync(path);
4360
+ const age = Date.now() - statSync(path).mtimeMs;
4361
+ if (age <= 2592e6) continue;
4362
+ if (age <= 7776e6 && isKept(entry, queued)) continue;
4363
+ unlinkSync(path);
4193
4364
  }
4194
4365
  } catch {}
4195
4366
  }
4367
+ /**
4368
+ * Whether the reviewer said to keep this one, asked of a directory entry rather than of a record.
4369
+ *
4370
+ * A record that will not parse cannot claim to be saved, so it falls to the ordinary window. That
4371
+ * is the safe direction: the alternative keeps unreadable files for three months on the strength of
4372
+ * a question nobody could answer.
4373
+ */
4374
+ function isKept(entry, queued) {
4375
+ const id = workspaceIdFrom(entry);
4376
+ if (id === null) return false;
4377
+ if (queued.has(id)) return true;
4378
+ return loadWorkspace(id)?.pinned === true;
4379
+ }
4380
+ /** The review an entry belongs to, whether it is the record or the usage sidecar beside it. */
4381
+ function workspaceIdFrom(entry) {
4382
+ for (const suffix of [".tokens.jsonl", ".json"]) if (entry.endsWith(suffix)) return entry.slice(0, -suffix.length);
4383
+ return null;
4384
+ }
4196
4385
  //#endregion
4197
4386
  //#region ../../packages/review-harness/src/workspace/exclusive_claim.ts
4198
4387
  /**
@@ -9109,26 +9298,32 @@ async function withUsageRecorded(workspaceId, kind, env, body, runId, requestedM
9109
9298
  */
9110
9299
  var UPLOAD_KEY = "telemetryUpload";
9111
9300
  /**
9112
- * The answer to the first-run question, or `null` if it has not been asked yet.
9301
+ * What this machine was told to do about transmission, or `null` if nobody ever said.
9113
9302
  *
9114
- * Three states rather than two, and the third is load-bearing: an install that has never been
9115
- * asked must be asked, and must not be treated as having declined. Collapsing `null` to `false`
9116
- * here would make the question unaskable the moment anything read the setting.
9303
+ * Three states rather than two, and the third is load-bearing in both directions: it is what lets
9304
+ * `upload.ts` apply a default without overwriting anybody's answer, and what lets the CLI disclose
9305
+ * the default to the machines it actually applies to and nobody else.
9117
9306
  */
9118
9307
  function telemetryUploadConsent() {
9119
9308
  return loadConfig().telemetryUpload;
9120
9309
  }
9121
- /** Whether a subset may go over the wire. Yes only if somebody said yes. */
9310
+ /**
9311
+ * Whether a subset may go over the wire. Yes unless somebody said no.
9312
+ *
9313
+ * `!== false` rather than `=== true`, and that is the whole of the 0.30.0 change in one operator.
9314
+ * Opt in measured who answers questions rather than who uses this: one or two installations
9315
+ * reporting against hundreds of weekly downloads. What is sent did not widen by a field.
9316
+ */
9122
9317
  function telemetryUploadEnabled() {
9123
9318
  const config = loadConfig();
9124
- return config.telemetry && config.telemetryUpload === true;
9319
+ return config.telemetry && config.telemetryUpload !== false;
9125
9320
  }
9126
9321
  /**
9127
- * Remember the answer, so the question is asked once on a machine and never again.
9322
+ * Remember a decision somebody made, so it outlives any change to the default.
9128
9323
  *
9129
9324
  * Read-modify-write through `config_file.ts`, and refuses outright while `PRB_TELEMETRY=off` is
9130
- * set: the kill switch means nothing is asked, so there is no answer to store, and writing one
9131
- * would record a decision nobody made.
9325
+ * set: the kill switch means nothing is being sent and nobody is being asked anything, so there is
9326
+ * no answer to store, and writing one would record a decision nobody made.
9132
9327
  */
9133
9328
  function recordTelemetryUploadConsent(answer) {
9134
9329
  if (process.env.PRB_TELEMETRY === "off") return;
@@ -9177,7 +9372,7 @@ function loadConfig() {
9177
9372
  } catch {}
9178
9373
  return config;
9179
9374
  }
9180
- /** Anything that is not exactly `true` or `false` is nobody having answered. */
9375
+ /** Anything that is not exactly `true` or `false` is nobody having chosen. */
9181
9376
  function consentOf(value) {
9182
9377
  return typeof value === "boolean" ? value : null;
9183
9378
  }
@@ -9226,7 +9421,7 @@ function project(record) {
9226
9421
  } : base;
9227
9422
  }
9228
9423
  /**
9229
- * Send one event, if there is one to send and somebody said it could be.
9424
+ * Send one event, if there is one to send and nobody has said not to.
9230
9425
  *
9231
9426
  * Returns nothing and awaits nothing. A failed send is a lost data point, which is a cost we
9232
9427
  * accept; a thrown error here would be a review that did not happen, which is not. The same
@@ -11383,4 +11578,4 @@ function lineageIds(id) {
11383
11578
  return [.../* @__PURE__ */ new Set([id, ...lineage.map((review) => review.id)])];
11384
11579
  }
11385
11580
  //#endregion
11386
- export { fillFileUrlTemplate as $, agentFor as $t, removeWorktree as A, workspaceRevision as At, workspaceUrl as B, resolveTarget as Bt, recordTelemetryUploadConsent as C, recentReviewGroups as Ct, ReviewBeingDeletedError as D, saveWorkspace as Dt, EXPLAIN_SIMPLY_PROMPT as E, reviewedRepositories as Et, clearServerRecord as F, issuesOutstanding as Ft, DEFAULT_WORKSPACE_PORT as G, clearAgentPreference as Gt, BUILD_VERSION as H, git as Ht, ensureServer as I, questionsOutstanding as It, parseWorkspacePort as J, CONFIG_PATH as Jt, MAX_WORKSPACE_PORT as K, readAgentPreference as Kt, readServerRecord as L, reviewerDispositions as Lt, relativeTime as M, doneVerb as Mt, readIndexToken as N, isIssueOutstanding as Nt, MANAGED_ROOT as O, summarise as Ot, bootstrapUrl as P, isQuestionOutstanding as Pt, forgeResolver as Q, agentById as Qt, reviewsUrl as R, processDiscussion as Rt, record as S, previousKissResult as St, withUsageRecorded as T, reviewedCommit as Tt, PACKAGE_NAME as U, resolveAgent as Ut, writeServerRecord as V, displayRef as Vt, feedbackUrl as W, resolveModel as Wt, statedWorkspacePort as X, AGENT_IDS as Xt, resolveWorkspacePort as Y, STORE_ROOT as Yt, writePortPreference as Z, DEFAULT_AGENT_ID as Zt, startKissJob as _, lineageKeyFor as _t, wasBlocked as a, isTerminal as at, startJob as b, matchingWorkspaceIds as bt, updateReview as c, PHASES as ct, askCheckout as d, purposeOf as dt, detectAgents as en, checkCodeFreshness as et, UpdateAlreadyRunningError as f, clearClaim as ft, recordKissRun as g, latestKissRun as gt, agentEnvOf as h, groupByLineage as ht, liveJobsFor as i, 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, AgentUnavailableError as nn, allJobs as nt, discardJob as o, loadJob as ot, liveUpdateFor as p, isClaimed as pt, MIN_WORKSPACE_PORT as q, writeAgentPreference as qt, lineageIds as r, endedAt as rt, RefreshUnavailableError as s, saveJob as st, deleteLineage as t, AgentCancelledError as tn, checkFreshness as tt, isUnchanged as u, progressSteps as ut, reanalyseReview as v, lineagePosition as vt, telemetryUploadConsent as w, recentWorkspaces as wt, readEvents as x, positionInLineage as xt, runJob as y, loadWorkspace as yt, stopServer as z, describeAuthorship as zt };
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, checkFreshness 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 };