prreviewbuddy 0.92.0 → 0.93.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,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.93.0
4
+
5
+ **Re-check reconciles your findings with the PR discussion.** **Re-check in PR context** used to
6
+ fetch the latest comments and correct who raised what, and leave every issue and question as it
7
+ was until Update or a card's own Reassess ran. It now runs the same discussion pass as Update,
8
+ against the commit the review read: a comment that answers a question answers it, a comment that
9
+ settles an issue resolves it, and a concern a comment raises becomes a new issue once it is
10
+ confirmed in the reviewed code. A claim that something was fixed is never taken on trust, and a
11
+ thread resolved on the forge is not, by itself, a fix: either stays open for Update to check against
12
+ newer commits, which remain Update's job. Reassess on a single card is unchanged.
13
+
14
+ The steps show while it runs (fetching the discussion, preparing the reviewed code, reassessing
15
+ issues and questions), and Cancel stops it. The result says what happened: the findings that
16
+ changed, that no findings changed, or, when the comments were fetched but the reassessment failed,
17
+ that the discussion was refreshed and the findings were not, with **Try again**. When nothing in
18
+ the discussion is new since the findings were last assessed, no model is asked.
19
+
20
+ The discussion counts as analysed only once a Re-check or an Update has finished reassessing it,
21
+ and only as far as it read, so a comment that arrives during a Re-check is still new to the next
22
+ one. A Re-check never stands in for an Update, and the last update's outcome is left as it was.
23
+
3
24
  ## 0.92.0
4
25
 
5
26
  **Older issues catch up on Update.** An issue from an earlier review that is still outstanding now
package/dist/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as readAskRequest, $a as runnableAgents, $i as explanationOf, Aa as TargetError, Ar as attemptIdOf, At as UpdateAlreadyRunningError, Ba as agentIdOf, Bt as append, Da as resolveTarget, Dt as parkedUpdateRefusal, E as relativeTime, Ea as kissOutcome, Er as forgeResolver, F as spawnRunner, Ft as prepareReview, G as reviewTag, Ga as STORE_ROOT, Gr as isClaimed, Gt as recordTelemetryUploadConsent, H as findingRef, Ha as readAgentPreference, Hr as purposeOf, I as applyUpdate, Ia as git, It as settleReview, J as forgetAskRequest, Ji as reviewerDispositions, Jr as followedRefName, Jt as BUILD_VERSION, Ka as AGENT_IDS, Kt as telemetryUploadConsent, L as modelProvenanceOf, Lr as PHASES, Lt as reanalyseReview, Mt as runningJobs, Na as AmbiguousRefError, Nr as isTerminal$1, Nt as failureOf, P as runDetached, Pr as loadJob, Qa as parkedReason, Ra as resolveAgent, T as runBatch, Ta as WITHHELD_REASON, Ua as writeAgentPreference, Ui as isIssueOutstanding, Ur as clearClaim, Ut as stamp, Vr as progressSteps, Wa as CONFIG_PATH, Wi as isQuestionOutstanding, Wr as driverGone, Wt as deliver, Xa as detectAgents, Y as interruption, Ya as agentIdentityOf, Yi as verdictClosure, Yt as PACKAGE_NAME, Z as isTerminalAsk, Za as isAgentId, _t as parseWorkspacePort, at as healthyPort, br as findExistingReview, bt as writePortPreference, ca as findingAssessment, di as reviewedRepositories, dr as MANAGED_ROOT, dt as stopServer, et as recordAskAccepted, fi as summarise, ft as workspaceUrl, gr as removeWorktree, gt as MIN_WORKSPACE_PORT, hr as readMarker, ht as MAX_WORKSPACE_PORT, i as deleteReview$1, ii as matchingWorkspaceIds, it as ensureServer, j as RUNNER_KINDS, jt as liveUpdateFor, k as queueForReview, kr as allJobs, li as recentWorkspaces, mi as workspaceRevision, mt as DEFAULT_WORKSPACE_PORT, ni as lineagePosition, nt as bootstrapUrl, o as liveJobsFor, oi as positionInLineage, ot as readServerRecord, q as endAskRequest, qa as agentById, qt as telemetryUploadEnabled, ri as loadWorkspace, s as wasBlocked, sr as modelHelpLines, st as reviewsUrl, t as MCP_SERVER_INFO_NAME, tt as readIndexToken, u as recordKissRun, ua as findingStatus, ui as reviewedCommit, vr as describeMovement, x as batchIdentity, xt as COMMIT_REVIEW_CANNOT_UPDATE, yr as describeSubject, yt as statedWorkspacePort, za as resolveModel, zt as startJob } from "./mcp_tools-BKPpd9dN.js";
2
+ import { $ as readAskRequest, Br as progressSteps, Dt as parkedUpdateRefusal, E as relativeTime, Ea as resolveTarget, F as spawnRunner, Fa as git, Ft as failureOf, G as reviewTag, Ga as AGENT_IDS, Gt as stamp, H as findingRef, Ha as writeAgentPreference, Hi as isIssueOutstanding, Hr as clearClaim, Ht as append, I as applyUpdate, Ir as PHASES, J as forgetAskRequest, Ja as agentIdentityOf, Ji as verdictClosure, Jt as telemetryUploadConsent, Ka as agentById, Kt as deliver, L as modelProvenanceOf, La as resolveAgent, Lt as prepareReview, Ma as AmbiguousRefError, Mr as isTerminal$1, Mt as UpdateAlreadyRunningError, Nr as loadJob, Nt as liveUpdateFor, Or as allJobs, P as runDetached, Pt as runningJobs, Qa as runnableAgents, Qi as explanationOf, Ra as resolveModel, Rt as settleReview, T as runBatch, Ta as kissOutcome, Tr as forgeResolver, Ua as CONFIG_PATH, Ui as isQuestionOutstanding, Ur as driverGone, Va as readAgentPreference, Vr as purposeOf, Vt as startJob, Wa as STORE_ROOT, Wr as isClaimed, Xa as isAgentId, Xt as BUILD_VERSION, Y as interruption, Ya as detectAgents, Yt as telemetryUploadEnabled, Z as isTerminalAsk, Za as parkedReason, Zt as PACKAGE_NAME, _r as describeMovement, _t as parseWorkspacePort, ai as positionInLineage, at as healthyPort, bt as writePortPreference, ci as recentWorkspaces, di as summarise, dt as stopServer, et as recordAskAccepted, ft as workspaceUrl, gt as MIN_WORKSPACE_PORT, hr as removeWorktree, ht as MAX_WORKSPACE_PORT, i as deleteReview$1, it as ensureServer, j as RUNNER_KINDS, k as queueForReview, ka as TargetError, kr as attemptIdOf, la as findingStatus, li as reviewedCommit, mr as readMarker, mt as DEFAULT_WORKSPACE_PORT, ni as loadWorkspace, nt as bootstrapUrl, o as liveJobsFor, or as modelHelpLines, ot as readServerRecord, pi as workspaceRevision, q as endAskRequest, qi as reviewerDispositions, qr as followedRefName, qt as recordTelemetryUploadConsent, ri as matchingWorkspaceIds, s as wasBlocked, sa as findingAssessment, st as reviewsUrl, t as MCP_SERVER_INFO_NAME, ti as lineagePosition, tt as readIndexToken, u as recordKissRun, ui as reviewedRepositories, ur as MANAGED_ROOT, vr as describeSubject, wa as WITHHELD_REASON, x as batchIdentity, xt as COMMIT_REVIEW_CANNOT_UPDATE, yr as findExistingReview, yt as statedWorkspacePort, za as agentIdOf, zt as reanalyseReview } from "./mcp_tools-DbFR9Z90.js";
3
3
  import { basename, dirname, isAbsolute, 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";
@@ -1526,7 +1526,7 @@ async function requestUpdate(id, source, deps = defaultDeps) {
1526
1526
  };
1527
1527
  const latest = loadWorkspace(workspace.id)?.lastUpdate;
1528
1528
  if (latest && latest.at !== before) {
1529
- const finished = jobsOf(workspace.id).find((candidate) => purposeOf(candidate) === "update" && candidate.startedAt >= (before ?? 0));
1529
+ const finished = jobsOf(workspace.id).find((candidate) => purposeOf(candidate) === "update" && !candidate.scope && candidate.startedAt >= (before ?? 0));
1530
1530
  return {
1531
1531
  review: await getReview(workspace.id, deps),
1532
1532
  job: finished ? toJobRecord(finished, false) : null,
@@ -7741,9 +7741,34 @@ var UPDATE_ORDER = [
7741
7741
  "context",
7742
7742
  "analysing"
7743
7743
  ];
7744
+ /**
7745
+ * The same phases, as a Re-check of the pull request discussion runs them. Nothing is fetched, so
7746
+ * there is no `preparing` step to show, and the checkout is of the commit the review read.
7747
+ */
7748
+ var RECHECK_PHASES = {
7749
+ ...UPDATE_PHASES,
7750
+ conversation: {
7751
+ doing: "Fetching the PR discussion",
7752
+ done: "Fetched the PR discussion"
7753
+ },
7754
+ context: {
7755
+ doing: "Preparing the reviewed code",
7756
+ done: "Prepared the reviewed code"
7757
+ },
7758
+ analysing: {
7759
+ doing: "Reassessing issues and questions",
7760
+ done: "Reassessed issues and questions"
7761
+ }
7762
+ };
7763
+ var RECHECK_ORDER = [
7764
+ "conversation",
7765
+ "context",
7766
+ "analysing"
7767
+ ];
7744
7768
  /** The names this job's phases go by. */
7745
7769
  function phasesFor(job) {
7746
- return purposeOf(job) === "update" ? UPDATE_PHASES : PHASES;
7770
+ if (purposeOf(job) !== "update") return PHASES;
7771
+ return job.scope === "conversation" ? RECHECK_PHASES : UPDATE_PHASES;
7747
7772
  }
7748
7773
  /**
7749
7774
  * Whether a phase is part of *this* review, rather than part of the pipeline in general.
@@ -7783,7 +7808,7 @@ function progressSteps(job) {
7783
7808
  const failedAt = job.phase === "failed" ? job.failure?.phase : void 0;
7784
7809
  const update = purposeOf(job) === "update";
7785
7810
  const names = phasesFor(job);
7786
- const order = (update ? UPDATE_ORDER : WORK_PHASES).filter((phase) => appliesTo(job, phase));
7811
+ const order = (update ? job.scope === "conversation" ? RECHECK_ORDER : UPDATE_ORDER : WORK_PHASES).filter((phase) => appliesTo(job, phase));
7787
7812
  return (update && job.phase === "done" ? order.filter((phase) => job.completed.includes(phase)) : order).map((phase) => {
7788
7813
  const complete = job.completed.includes(phase) || !update && job.phase === "done";
7789
7814
  const failed = failedAt === phase;
@@ -7876,6 +7901,7 @@ function strippedForDeletedReview(job) {
7876
7901
  },
7877
7902
  ...job.purpose ? { purpose: job.purpose } : {},
7878
7903
  ...job.reanalysis ? { reanalysis: true } : {},
7904
+ ...job.scope ? { scope: job.scope } : {},
7879
7905
  ...job.reviewType ? { reviewType: job.reviewType } : {},
7880
7906
  phase: job.phase,
7881
7907
  startedAt: job.startedAt,
@@ -18774,6 +18800,13 @@ function countUpdateChanges(previous, result) {
18774
18800
  ...reopened ? { reopened } : {}
18775
18801
  };
18776
18802
  }
18803
+ /**
18804
+ * Whether a reconciliation moved any finding or put anything to the reviewer. Still-open findings
18805
+ * are the ones it left alone, so they are the one count that is not a change.
18806
+ */
18807
+ function findingsChanged(counts) {
18808
+ return counts.resolved + counts.affected + counts.newIssues + counts.questionsAnswered + (counts.reopened ?? 0) + (counts.gradeChanges ?? 0) + (counts.decisionsChallenged ?? 0) > 0;
18809
+ }
18777
18810
  //#endregion
18778
18811
  //#region ../../packages/review-harness/src/domain/ai_review/update_reconciliation.ts
18779
18812
  function reconciledAgainstOf(basis) {
@@ -19099,8 +19132,93 @@ async function updateReview(workspace, runners = {}) {
19099
19132
  if (workspace.changeSet.targetType === "commit") return refuse(reviewedSha, false, COMMIT_REVIEW_CANNOT_UPDATE);
19100
19133
  const parked = parkedUpdateRefusal(workspace);
19101
19134
  if (parked) return refuse(reviewedSha, false, parked);
19135
+ let fetchStartedAt = 0;
19136
+ return driveUpdateJob(workspace, runners, reviewedSha, {
19137
+ startedAt,
19138
+ now
19139
+ }, void 0, async (held, step, signal, clock) => {
19140
+ fetchStartedAt = Date.now();
19141
+ const outcome = await bringForward(workspace, runners, reviewedSha, held, step, signal, clock);
19142
+ if (!signal.aborted && outcome.ok && !outcome.fetchFailed && outcome.toSha) workspace.headSeenAt = fetchStartedAt;
19143
+ return outcome;
19144
+ });
19145
+ }
19146
+ /**
19147
+ * Reconcile the review's findings with its pull request discussion as it is now, at the commit the
19148
+ * review read: the Re-check button.
19149
+ *
19150
+ * The discussion half of an update and nothing else. The conversation is re-read exactly as an
19151
+ * update re-reads it, so every "already raised" line is corrected first; then, when something in it
19152
+ * is new to the analysis, the same reconciliation an update runs is asked of the same agent with no
19153
+ * code delta. It may answer a question, resolve or reopen an issue on the evidence the discussion
19154
+ * rules allow, suggest a grade, or raise a concern as a new issue once it has confirmed it in the
19155
+ * reviewed code. It never fetches and never moves the review to a newer commit: that is an update's,
19156
+ * and the review's commit stays where it was so the next update still sees those commits as new.
19157
+ *
19158
+ * Computes and mutates in memory, like `updateReview`; the route saves.
19159
+ */
19160
+ async function recheckConversation(workspace, runners = {}) {
19161
+ const now = runners.now ?? Date.now;
19162
+ const startedAt = now();
19163
+ const reviewedSha = workspace.session.metadata?.commitSha || null;
19164
+ const recheck = (outcome) => ({
19165
+ ...outcome,
19166
+ scope: "conversation"
19167
+ });
19168
+ if (!workspace.prContext) return recheck(refuse(reviewedSha, false, "This review has no pull request conversation attached, so there is nothing to re-read."));
19169
+ const cannotReassess = parkedUpdateRefusal(workspace) ?? (!workspace.session.result || !reviewedSha ? "This review has no analysis to reassess." : null);
19170
+ if (cannotReassess) {
19171
+ adoptLegacyBaseline(workspace);
19172
+ const conversationOk = await reReadConversation(workspace, runners.runRefresh);
19173
+ return recheck(settled(reviewedSha, reviewedSha, false, conversationOk, "refused", conversationOk ? cannotReassess : rereadFailureOf(workspace)));
19174
+ }
19175
+ return driveUpdateJob(workspace, runners, reviewedSha, {
19176
+ startedAt,
19177
+ now
19178
+ }, "conversation", async (held, step, signal, clock) => recheck(await reconcileConversation(workspace, runners, reviewedSha, held, step, signal, clock)));
19179
+ }
19180
+ /** What a failed re-read said, for an outcome that reports it. */
19181
+ function rereadFailureOf(workspace) {
19182
+ return workspace.lastPrRefresh && !workspace.lastPrRefresh.ok ? workspace.lastPrRefresh.message : "The pull request conversation could not be re-read. Nothing here has changed.";
19183
+ }
19184
+ /** Said when the conversation holds nothing the findings have not already been weighed against. */
19185
+ var NOTHING_NEW_IN_DISCUSSION = "Nothing in the PR discussion is new since the findings were last assessed, so no findings changed.";
19186
+ async function reconcileConversation(workspace, runners, reviewedSha, held, step, signal, clock) {
19187
+ adoptLegacyBaseline(workspace);
19188
+ step("conversation");
19189
+ const conversationOk = await reReadConversation(workspace, runners.runRefresh);
19190
+ signal.throwIfAborted();
19191
+ if (!conversationOk) return settled(reviewedSha, reviewedSha, false, false, "refused", rereadFailureOf(workspace));
19192
+ const { prepared } = await prepareConversation(workspace, {
19193
+ reread: false,
19194
+ ...workspace.lastPrRefresh ? { rereadOutcome: workspace.lastPrRefresh } : {}
19195
+ });
19196
+ if (!conversationMoved(prepared)) return settled(reviewedSha, reviewedSha, false, true, "already-current", NOTHING_NEW_IN_DISCUSSION);
19197
+ if (!await commitPresent(workspace.repoPath, reviewedSha)) return settled(reviewedSha, reviewedSha, false, true, "baseline-gone", BASELINE_GONE_MESSAGE);
19198
+ step("context");
19199
+ let worktreePath;
19200
+ try {
19201
+ ({path: worktreePath} = await pinCheckout(workspace, reviewedSha, held));
19202
+ } catch (error) {
19203
+ if (error instanceof ReviewBeingDeletedError) throw error;
19204
+ return settled(reviewedSha, reviewedSha, false, true, "analysis-failed", "A checkout of the reviewed commit could not be prepared, so the findings were not reassessed.");
19205
+ }
19206
+ signal.throwIfAborted();
19207
+ return reconcile(workspace, runners, reviewedSha, {
19208
+ sha: reviewedSha,
19209
+ fetchFailed: false
19210
+ }, true, worktreePath, step, held.job, signal, clock, prepared, true, "conversation");
19211
+ }
19212
+ /**
19213
+ * The life of an update's job, whatever the update does inside it: the claim, the cancel, the steps,
19214
+ * how it ends and what it announces. Shared by Update and Re-check so the two cannot disagree about
19215
+ * how a run is watched, cancelled or reported.
19216
+ */
19217
+ async function driveUpdateJob(workspace, runners, reviewedSha, clock, scope, work) {
19218
+ const { startedAt, now } = clock;
19102
19219
  const held = await claimCheckoutJob(workspace, reviewedSha ?? "", {
19103
19220
  purpose: "update",
19221
+ ...scope ? { scope } : {},
19104
19222
  ...runners.source ? { source: runners.source } : {},
19105
19223
  ...workspace.prContext ? { conversation: {
19106
19224
  kind: "attached",
@@ -19114,16 +19232,11 @@ async function updateReview(workspace, runners = {}) {
19114
19232
  const current = () => loadJob(held.job.id) ?? held.job;
19115
19233
  const step = (phase) => saveJob(advance(current(), phase));
19116
19234
  try {
19117
- const fetchStartedAt = Date.now();
19118
- const outcome = await bringForward(workspace, runners, reviewedSha, held, step, signal, {
19119
- startedAt,
19120
- now
19121
- });
19122
- if (signal.aborted) return cancelled(held, reviewedSha);
19123
- if (outcome.ok && !outcome.fetchFailed && outcome.toSha) workspace.headSeenAt = fetchStartedAt;
19124
- const ended = outcome.stop === "analysis-failed" ? fail(current(), outcome.message) : advance(current(), "done", outcome.message);
19235
+ const outcome = await work(held, step, signal, clock);
19236
+ if (signal.aborted) return scoped(cancelled(held, reviewedSha), scope);
19237
+ const ended = outcome.stop === "analysis-failed" || scope && outcome.conversationOk === false ? fail(current(), outcome.message) : advance(current(), "done", outcome.message);
19125
19238
  saveJob(ended);
19126
- if (outcome.ok && outcome.stop === null) {
19239
+ if (!scope && outcome.ok && outcome.stop === null) {
19127
19240
  const completedAt = now();
19128
19241
  recordRuntimeBestEffort({
19129
19242
  operation: "update",
@@ -19139,7 +19252,7 @@ async function updateReview(workspace, runners = {}) {
19139
19252
  announceJob(ended, updateFacts(outcome, workspace));
19140
19253
  return outcome;
19141
19254
  } catch (error) {
19142
- if (signal.aborted) return cancelled(held, reviewedSha);
19255
+ if (signal.aborted) return scoped(cancelled(held, reviewedSha), scope);
19143
19256
  const failed = fail(current(), error instanceof Error ? error.message : String(error));
19144
19257
  saveJob(failed);
19145
19258
  announceJob(failed);
@@ -19149,6 +19262,12 @@ async function updateReview(workspace, runners = {}) {
19149
19262
  held.claim.release();
19150
19263
  }
19151
19264
  }
19265
+ function scoped(outcome, scope) {
19266
+ return scope ? {
19267
+ ...outcome,
19268
+ scope
19269
+ } : outcome;
19270
+ }
19152
19271
  /**
19153
19272
  * The reviewer cancelled. Recorded as a stop, not a failure, so nothing announces it or offers a
19154
19273
  * retry, and answered as an outcome the page reports on the review it left untouched.
@@ -19192,7 +19311,7 @@ async function bringForward(workspace, runners, reviewedSha, held, step, signal,
19192
19311
  signal.throwIfAborted();
19193
19312
  return reconcile(workspace, runners, reviewedSha, remote, conversationOk, worktreePath, step, held.job, signal, clock, prepared, discussionMoved);
19194
19313
  }
19195
- async function reconcile(workspace, runners, reviewedSha, remote, conversationOk, worktreePath, step, job, signal, clock, prepared, discussionMoved) {
19314
+ async function reconcile(workspace, runners, reviewedSha, remote, conversationOk, worktreePath, step, job, signal, clock, prepared, discussionMoved, scope) {
19196
19315
  const { id: jobId, env } = job;
19197
19316
  const newHead = remote.sha;
19198
19317
  let delta = null;
@@ -19227,7 +19346,7 @@ async function reconcile(workspace, runners, reviewedSha, remote, conversationOk
19227
19346
  const requestedModel = resolveModel({ review: { session: workspace.session } }) ?? void 0;
19228
19347
  let raw;
19229
19348
  try {
19230
- const prompt = buildUpdatePrompt(workspace, delta, prepared, reviewedSha, newHead, worktreePath);
19349
+ const prompt = buildUpdatePrompt(workspace, delta, prepared, reviewedSha, newHead, worktreePath, scope);
19231
19350
  raw = runners.run ? await runners.run(prompt, worktreePath, env, onProgress, onObservedModel, signal) : await runUpdate(workspace.id, prompt, worktreePath, workspace.repoPath, env, onProgress, requestedModel, onObservedModel, signal);
19232
19351
  } catch (error) {
19233
19352
  return settled(reviewedSha, newHead, remote.fetchFailed, conversationOk, "analysis-failed", explainUpdateFailure(error, env, requestedModel));
@@ -19444,11 +19563,36 @@ function mergeUpdateInto(fresh, updated) {
19444
19563
  for (const field of UPDATE_OWNED_FIELDS) if (field in updated) target[field] = source[field];
19445
19564
  else delete target[field];
19446
19565
  merged.session = keepReviewerFields(fresh, updated.session);
19566
+ keepNewerConversation(merged, fresh, updated);
19447
19567
  carryUpdateSuggestions(merged, updated);
19448
19568
  dropStaleChangeMap(merged);
19449
19569
  return merged;
19450
19570
  }
19451
19571
  /**
19572
+ * A conversation re-read that landed on disk while this ran, kept rather than put back.
19573
+ *
19574
+ * An Ask turn re-reads the conversation and saves it, and an update holds its copy for minutes. Laid
19575
+ * over whole, the update's older copy replaced the newer one and the "already raised" lines went
19576
+ * back with it. The newer copy stays, and the attributions are derived again from it: arithmetic,
19577
+ * the same pass the re-read itself runs. `analysedConversation` is untouched either way: it is the
19578
+ * snapshot this run actually reconciled against, so whatever arrived since is still new to the next.
19579
+ */
19580
+ function keepNewerConversation(merged, fresh, updated) {
19581
+ const theirs = fresh.prContext ? orderOf(fresh.prContext.discussion) : void 0;
19582
+ const ours = updated.prContext ? orderOf(updated.prContext.discussion) : void 0;
19583
+ if (theirs === void 0 || ours === void 0 || theirs <= ours || !fresh.prContext) return;
19584
+ merged.prContext = fresh.prContext;
19585
+ if (fresh.lastPrRefresh) merged.lastPrRefresh = fresh.lastPrRefresh;
19586
+ const result = merged.session.result;
19587
+ if (result) {
19588
+ const { result: attributed } = attributeIssuesToDiscussion(result, processDiscussion(fresh.prContext.discussion));
19589
+ merged.session = {
19590
+ ...merged.session,
19591
+ result: attributed
19592
+ };
19593
+ }
19594
+ }
19595
+ /**
19452
19596
  * The grade changes this update suggested, onto the review as it is on disk.
19453
19597
  *
19454
19598
  * The ledger is the reviewer's and comes from the fresh read, so the update's proposals, written on
@@ -19725,20 +19869,20 @@ function stillPresent(counts) {
19725
19869
  * Everything after it is the shared builder, so the two clients cannot drift into asking for
19726
19870
  * different shapes.
19727
19871
  */
19728
- function buildUpdatePrompt(workspace, delta, conversation, reviewedSha, newHead, worktreePath) {
19872
+ function buildUpdatePrompt(workspace, delta, conversation, reviewedSha, newHead, worktreePath, scope) {
19729
19873
  const previous = workspace.session.result;
19730
19874
  const discussionChanged = conversationMoved(conversation);
19731
19875
  const conversationBlock = updateConversationBlock(conversation);
19732
- const opening = delta ? `An earlier review of this branch is being brought up to date with the commits pushed since it was written${discussionChanged ? ", and with what has changed in its pull request conversation since" : ""}.` : "An earlier review of this branch is being brought up to date with what has changed in its pull request conversation since it was written. No commits have been pushed since.";
19876
+ const opening = delta ? `An earlier review of this branch is being brought up to date with the commits pushed since it was written${discussionChanged ? ", and with what has changed in its pull request conversation since" : ""}.` : scope === "conversation" ? "An earlier review of this branch is being reconciled with what has changed in its pull request conversation since it was written. Only the conversation is reassessed in this pass, against the commit the review read. Newer commits on the branch, if any, are not part of it: a comment saying a fix landed in one cannot be confirmed from this checkout, so that finding stays open for a later update to check against the code." : "An earlier review of this branch is being brought up to date with what has changed in its pull request conversation since it was written. No commits have been pushed since.";
19733
19877
  const code = delta ? `The change below is what happened between ${reviewedSha} and ${newHead}. It is not the whole branch: the earlier review already covered everything up to ${reviewedSha}.
19734
19878
 
19735
19879
  ${renderChangeInventory(delta.changeSet.data.files, inlinePathsOf(delta))}
19736
19880
 
19737
- ${delta.diff}` : `The code is unchanged since the earlier review: ${newHead} is the commit it read.`;
19881
+ ${delta.diff}` : scope === "conversation" ? `There is no code delta in this pass: ${newHead} is the commit the earlier review read.` : `The code is unchanged since the earlier review: ${newHead} is the commit it read.`;
19738
19882
  const commits = delta ? (delta.changeSet.authors ?? []).reduce((total, author) => total + author.commits, 0) : 0;
19739
19883
  return `${opening}
19740
19884
 
19741
- The working tree at ${worktreePath} is checked out at ${newHead}, which is the code as it now stands. You may read any file in it, and you should: the point of doing this here rather than from a diff alone is that you can open the code and see whether a fix actually holds. Read only. Change nothing, commit nothing, and run no command that writes.
19885
+ The working tree at ${worktreePath} is checked out at ${newHead}, which is ${scope === "conversation" ? "the code the review read" : "the code as it now stands"}. You may read any file in it, and you should: the point of doing this here rather than from a diff alone is that you can open the code and see whether a fix actually holds. Read only. Change nothing, commit nothing, and run no command that writes.
19742
19886
 
19743
19887
  ${code}
19744
19888
 
@@ -21472,4 +21616,4 @@ var MCP_TOOL_NAMES = [
21472
21616
  "ask_buddy"
21473
21617
  ];
21474
21618
  //#endregion
21475
- export { readAskRequest as $, runnableAgents as $a, explanationOf as $i, authorPreview as $n, kissDecisionsOf as $r, recordRuntimeBestEffort as $t, settleQueue as A, TargetError as Aa, createWithMembers as Ai, moveIds as An, attemptIdOf as Ar, UpdateAlreadyRunningError as At, humanizeModelId as B, agentIdOf as Ba, queueStamp as Bi, existingPaths as Bn, phasesFor as Br, append as Bt, parsePullRequestLink as C, objectWithAnyOf as Ca, lifecyclePillAction as Ci, createPrState as Cn, followedBranch as Cr, RefreshUnavailableError as Ct, activateQueued as D, resolveTarget as Da, sectionOf as Di, markAllRead as Dn, fillFileUrlTemplate as Dr, parkedUpdateRefusal as Dt, relativeTime as E, kissOutcome as Ea, parseLifecycle as Ei, listNotifications as En, forgeResolver as Er, mergeUpdateInto as Et, spawnRunner as F, shorthandOf as Fa, setMembership as Fi, retireNotification as Fn, pruneJobResidue as Fr, prepareReview as Ft, reviewTag as G, STORE_ROOT as Ga, issuesOutstanding as Gi, storedConversation as Gn, isClaimed as Gr, recordTelemetryUploadConsent as Gt, findingRef as H, readAgentPreference as Ha, doneVerb as Hi, adoptLegacyBaseline as Hn, purposeOf as Hr, record as Ht, applyUpdate as I, git as Ia, updateCollections as Ii, toneOf as In, saveJob as Ir, settleReview as It, forgetAskRequest as J, agentFor as Ja, reviewerDispositions as Ji, isUnchanged as Jn, followedRefName as Jr, BUILD_VERSION as Jt, reviewTagsIn as K, AGENT_IDS as Ka, questionsOutstanding as Ki, unforeseenFailure as Kn, CLAIM_LEASE_MS as Kr, telemetryUploadConsent as Kt, modelProvenanceOf as L, refExists as La, queueAdd as Li, unreadAround as Ln, PHASES as Lr, reanalyseReview as Lt, RUNNER_MARKER as M, resolveBase as Ma, nameProblemText as Mi, readNotification as Mn, fail as Mr, runningJobs as Mt, cliEntry as N, AmbiguousRefError as Na, readCollections as Ni, readStateOf as Nn, isTerminal as Nr, failureOf as Nt, compactQueue as O, originRemote as Oa, collectionsStamp as Oi, markRead as On, RUN_RECORD_RETENTION_MS as Or, updateReview as Ot, runDetached as P, shortNameOf as Pa, renameCollection as Pi, removeNotification as Pn, loadJob as Pr, observeReview as Pt, markAskRunning as Q, parkedReason as Qa, explanationActionOf as Qi, UNESTABLISHED_MOTIVATION as Qn, kissDecisionRefusal as Qr, mapRuntimes as Qt, provenanceDetailLines as R, resolveAgent as Ra, queueAddMany as Ri, unreadCount as Rn, isLiveJob as Rr, runJob as Rt, extractPullRequestLinks as S, extractJsonText as Sa, lifecycleName as Si, createArchitectureNotification as Sn, checkReview as Sr, FETCH_TIMEOUT_MS as St, runBatch as T, WITHHELD_REASON as Ta, offersLifecycle as Ti, eventName as Tn, fetchHead as Tr, cancellableHere as Tt, findingRefs as U, writeAgentPreference as Ua, isIssueOutstanding as Ui, prepareConversationWithin as Un, clearClaim as Ur, stamp as Ut, findingAtRef as V, clearAgentPreference as Va, readQueue as Vi, ASK_REREAD_CEILING_MS as Vn, progressSteps as Vr, readEvents as Vt, refOf as W, CONFIG_PATH as Wa, isQuestionOutstanding as Wi, saveRefresh as Wn, driverGone as Wr, deliver as Wt, isRequestId as X, detectAgents as Xa, processDiscussion as Xi, EXPLAIN_SIMPLY_PROMPT as Xn, indexKeptIds as Xr, feedbackUrl as Xt, interruption as Y, agentIdentityOf as Ya, verdictClosure as Yi, agentEnvOf as Yn, groupByLineage as Yr, PACKAGE_NAME as Yt, isTerminalAsk as Z, isAgentId as Za, recommendationsOf as Zi, chatConversationSection as Zn, indexReviewGroups as Zr, waitingHistory as Zt, endRun as _, statusLookup as _a, activityAttributes as _i, writeJsonAtomically as _n, requestJobStop as _r, parseWorkspacePort as _t, lineageIds as a, assessmentLookup as aa, mutateWorkspace as ai, loadArchitecture as an, escapeHtml as ar, healthyPort as at, stopRun as b, SEVERITIES as ba, lifecycleChip as bi, subjectOf as bn, findExistingReview as br, writePortPreference as bt, discardJob as c, findingAssessment as ca, previousKissResult as ci, repositoryKey as cn, ReviewBeingDeletedError as cr, serverEntryExists as ct, startKissJob as d, gradeScale as da, reviewedRepositories as di, trackedFiles as dn, MANAGED_ROOT as dr, stopServer as dt, isExplanationAction as ea, latestKissRun as ei, RUNTIME_WORDS as en, versionFrom as eo, hasAuthorContent as er, recordAskAccepted as et, isDriving as f, isGradeFor as fa, summarise as fi, ownersOf as fn, createWorktree as fr, workspaceUrl as ft, completeRun as g, sourcePhrase as ga, LIFECYCLES as gi, isArchitectureId as gn, removeWorktree as gr, MIN_WORKSPACE_PORT as gt, beginRun as h, reassessIn as ha, workspaceStamps as hi, ancestorsOf as hn, readMarker as hr, MAX_WORKSPACE_PORT as ht, deleteReview as i, analysisRefOf as ia, matchingWorkspaceIds as ii, checkArchitecture as in, CODE_COPY_CLASS as ir, ensureServer as it, RUNNER_KINDS as j, isCommitReview as ja, deleteCollection as ji, pruneNotifications as jn, endedAt as jr, liveUpdateFor as jt, queueForReview as k, DetachedHeadError as ka, createCollection as ki, markReadForReview as kn, allJobs as kr, askCheckout as kt, kissAgentId as l, findingIn as la, recentWorkspaces as li, storeArchitecture as ln, isBusy as lr, spawnServer as lt, RUN_KINDS as m, promptGrade as ma, workspaceRevision as mi, REPOSITORY_SCOPE as mn, isWorktreeHeld as mr, DEFAULT_WORKSPACE_PORT as mt, MCP_TOOL_NAMES as n, ANALYSIS_ONLY as na, lineagePosition as ni, formatDuration as nn, AgentCancelledError as no, renderMarkdown as nr, bootstrapUrl as nt, liveJobsFor as o, closesFinding as oa, positionInLineage as oi, partFiles as on, renderInlineText as or, readServerRecord as ot, track as p, isOffered as pa, touchWorkspace as pi, QUOTE_LIMITS as pn, holdWorktree as pr, writeServerRecord as pt, endAskRequest as q, agentById as qa, resolvedByConversation as qi, refreshPrContext as qn, decideKissRecommendation as qr, telemetryUploadEnabled as qt, deleteLineage as r, NO_STATUS as ra, loadWorkspace as ri, ARCHITECTURE_DIR as rn, AgentUnavailableError as ro, CODE_BLOCK_CLASS as rr, clearServerRecordOwnedBy as rt, wasBlocked as s, currentGradeIn as sa, presentedKissRunOf as si, readArchitecture as sn, modelHelpLines as sr, reviewsUrl as st, MCP_SERVER_INFO_NAME as t, withExplanation as ta, lineageKey as ti, describeRuntimes as tn, AGENT_RUN_MARKER as to, GITHUB_MARKDOWN as tr, readIndexToken as tt, recordKissRun as u, findingStatus as ua, reviewedCommit as ui, storedScopes as un, withReviewLock as ur, stopChild as ut, heldRuns as v, wasReassessed as va, activityLabel as vi, announceJob as vn, describeMovement as vr, resolveWorkspacePort as vt, unsupportedReason as w, describeAuthorship as wa, lifecyclePillLabel as wi, createPrUpdate as wn, reviewedShaOf as wr, cancelUpdate as wt, batchIdentity as x, asString$1 as xa, lifecycleItemLabel as xi, categoryOf as xn, checkCodeFreshness as xr, COMMIT_REVIEW_CANNOT_UPDATE as xt, reportRun as y, followUpFindingId as ya, isLifecycle as yi, branchOf as yn, describeSubject as yr, statedWorkspacePort as yt, agentDisplayName as z, resolveModel as za, queueRemove as zi, withUsageRecorded as zn, isTerminalJobPhase as zr, startJob as zt };
21619
+ export { readAskRequest as $, versionFrom as $a, isExplanationAction as $i, hasAuthorContent as $n, latestKissRun as $r, waitingHistory as $t, settleQueue as A, isCommitReview as Aa, deleteCollection as Ai, markRead as An, endedAt as Ar, askCheckout as At, humanizeModelId as B, clearAgentPreference as Ba, readQueue as Bi, unreadCount as Bn, progressSteps as Br, runJob as Bt, parsePullRequestLink as C, describeAuthorship as Ca, lifecyclePillLabel as Ci, categoryOf as Cn, reviewedShaOf as Cr, RefreshUnavailableError as Ct, activateQueued as D, originRemote as Da, collectionsStamp as Di, eventName as Dn, RUN_RECORD_RETENTION_MS as Dr, parkedUpdateRefusal as Dt, relativeTime as E, resolveTarget as Ea, sectionOf as Ei, createPrUpdate as En, fillFileUrlTemplate as Er, mergeUpdateInto as Et, spawnRunner as F, git as Fa, updateCollections as Fi, readStateOf as Fn, saveJob as Fr, failureOf as Ft, reviewTag as G, AGENT_IDS as Ga, questionsOutstanding as Gi, saveRefresh as Gn, CLAIM_LEASE_MS as Gr, stamp as Gt, findingRef as H, writeAgentPreference as Ha, isIssueOutstanding as Hi, existingPaths as Hn, clearClaim as Hr, append as Ht, applyUpdate as I, refExists as Ia, queueAdd as Ii, removeNotification as In, PHASES as Ir, observeReview as It, forgetAskRequest as J, agentIdentityOf as Ja, verdictClosure as Ji, agentEnvOf as Jn, groupByLineage as Jr, telemetryUploadConsent as Jt, reviewTagsIn as K, agentById as Ka, resolvedByConversation as Ki, storedConversation as Kn, decideKissRecommendation as Kr, deliver as Kt, modelProvenanceOf as L, resolveAgent as La, queueAddMany as Li, retireNotification as Ln, isLiveJob as Lr, prepareReview as Lt, RUNNER_MARKER as M, AmbiguousRefError as Ma, readCollections as Mi, moveIds as Mn, isTerminal as Mr, UpdateAlreadyRunningError as Mt, cliEntry as N, shortNameOf as Na, renameCollection as Ni, pruneNotifications as Nn, loadJob as Nr, liveUpdateFor as Nt, compactQueue as O, DetachedHeadError as Oa, createCollection as Oi, listNotifications as On, allJobs as Or, recheckConversation as Ot, runDetached as P, shorthandOf as Pa, setMembership as Pi, readNotification as Pn, pruneJobResidue as Pr, runningJobs as Pt, markAskRunning as Q, runnableAgents as Qa, explanationOf as Qi, authorPreview as Qn, kissDecisionsOf as Qr, feedbackUrl as Qt, provenanceDetailLines as R, resolveModel as Ra, queueRemove as Ri, toneOf as Rn, isTerminalJobPhase as Rr, settleReview as Rt, extractPullRequestLinks as S, objectWithAnyOf as Sa, lifecyclePillAction as Si, subjectOf as Sn, followedBranch as Sr, FETCH_TIMEOUT_MS as St, runBatch as T, kissOutcome as Ta, parseLifecycle as Ti, createPrState as Tn, forgeResolver as Tr, cancellableHere as Tt, findingRefs as U, CONFIG_PATH as Ua, isQuestionOutstanding as Ui, ASK_REREAD_CEILING_MS as Un, driverGone as Ur, readEvents as Ut, findingAtRef as V, readAgentPreference as Va, doneVerb as Vi, withUsageRecorded as Vn, purposeOf as Vr, startJob as Vt, refOf as W, STORE_ROOT as Wa, issuesOutstanding as Wi, prepareConversationWithin as Wn, isClaimed as Wr, record as Wt, isRequestId as X, isAgentId as Xa, recommendationsOf as Xi, chatConversationSection as Xn, indexReviewGroups as Xr, BUILD_VERSION as Xt, interruption as Y, detectAgents as Ya, processDiscussion as Yi, EXPLAIN_SIMPLY_PROMPT as Yn, indexKeptIds as Yr, telemetryUploadEnabled as Yt, isTerminalAsk as Z, parkedReason as Za, explanationActionOf as Zi, UNESTABLISHED_MOTIVATION as Zn, kissDecisionRefusal as Zr, PACKAGE_NAME as Zt, endRun as _, wasReassessed as _a, activityLabel as _i, ancestorsOf as _n, describeMovement as _r, parseWorkspacePort as _t, lineageIds as a, closesFinding as aa, positionInLineage as ai, ARCHITECTURE_DIR as an, renderInlineText as ar, healthyPort as at, stopRun as b, asString$1 as ba, lifecycleItemLabel as bi, announceJob as bn, checkCodeFreshness as br, writePortPreference as bt, discardJob as c, findingIn as ca, recentWorkspaces as ci, partFiles as cn, isBusy as cr, serverEntryExists as ct, startKissJob as d, isGradeFor as da, summarise as di, storeArchitecture as dn, createWorktree as dr, stopServer as dt, withExplanation as ea, lineageKey as ei, mapRuntimes as en, AGENT_RUN_MARKER as eo, GITHUB_MARKDOWN as er, recordAskAccepted as et, isDriving as f, isOffered as fa, touchWorkspace as fi, storedScopes as fn, holdWorktree as fr, workspaceUrl as ft, completeRun as g, statusLookup as ga, activityAttributes as gi, REPOSITORY_SCOPE as gn, requestJobStop as gr, MIN_WORKSPACE_PORT as gt, beginRun as h, sourcePhrase as ha, LIFECYCLES as hi, QUOTE_LIMITS as hn, removeWorktree as hr, MAX_WORKSPACE_PORT as ht, deleteReview as i, assessmentLookup as ia, mutateWorkspace as ii, formatDuration as in, escapeHtml as ir, ensureServer as it, RUNNER_KINDS as j, resolveBase as ja, nameProblemText as ji, markReadForReview as jn, fail as jr, findingsChanged as jt, queueForReview as k, TargetError as ka, createWithMembers as ki, markAllRead as kn, attemptIdOf as kr, updateReview as kt, kissAgentId as l, findingStatus as la, reviewedCommit as li, readArchitecture as ln, withReviewLock as lr, spawnServer as lt, RUN_KINDS as m, reassessIn as ma, workspaceStamps as mi, ownersOf as mn, readMarker as mr, DEFAULT_WORKSPACE_PORT as mt, MCP_TOOL_NAMES as n, NO_STATUS as na, loadWorkspace as ni, RUNTIME_WORDS as nn, AgentUnavailableError as no, CODE_BLOCK_CLASS as nr, bootstrapUrl as nt, liveJobsFor as o, currentGradeIn as oa, presentedKissRunOf as oi, checkArchitecture as on, modelHelpLines as or, readServerRecord as ot, track as p, promptGrade as pa, workspaceRevision as pi, trackedFiles as pn, isWorktreeHeld as pr, writeServerRecord as pt, endAskRequest as q, agentFor as qa, reviewerDispositions as qi, isUnchanged as qn, followedRefName as qr, recordTelemetryUploadConsent as qt, deleteLineage as r, analysisRefOf as ra, matchingWorkspaceIds as ri, describeRuntimes as rn, CODE_COPY_CLASS as rr, clearServerRecordOwnedBy as rt, wasBlocked as s, findingAssessment as sa, previousKissResult as si, loadArchitecture as sn, ReviewBeingDeletedError as sr, reviewsUrl as st, MCP_SERVER_INFO_NAME as t, ANALYSIS_ONLY as ta, lineagePosition as ti, recordRuntimeBestEffort as tn, AgentCancelledError as to, renderMarkdown as tr, readIndexToken as tt, recordKissRun as u, gradeScale as ua, reviewedRepositories as ui, repositoryKey as un, MANAGED_ROOT as ur, stopChild as ut, heldRuns as v, followUpFindingId as va, isLifecycle as vi, isArchitectureId as vn, describeSubject as vr, resolveWorkspacePort as vt, unsupportedReason as w, WITHHELD_REASON as wa, offersLifecycle as wi, createArchitectureNotification as wn, fetchHead as wr, cancelUpdate as wt, batchIdentity as x, extractJsonText as xa, lifecycleName as xi, branchOf as xn, checkReview as xr, COMMIT_REVIEW_CANNOT_UPDATE as xt, reportRun as y, SEVERITIES as ya, lifecycleChip as yi, writeJsonAtomically as yn, findExistingReview as yr, statedWorkspacePort as yt, agentDisplayName as z, agentIdOf as za, queueStamp as zi, unreadAround as zn, phasesFor as zr, reanalyseReview as zt };