prreviewbuddy 0.91.1 → 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/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $a as runnableAgents, $i as explanationOf, $n as authorPreview, $r as kissDecisionsOf, $t as recordRuntimeBestEffort, A as settleQueue, Ai as createWithMembers, An as moveIds, Ar as attemptIdOf, At as UpdateAlreadyRunningError, B as humanizeModelId, Ba as agentIdOf, Bi as queueStamp, Bn as existingPaths, Br as phasesFor, C as parsePullRequestLink, Ca as objectWithAnyOf, Ci as lifecyclePillAction, Cn as createPrState, Cr as followedBranch, Ct as RefreshUnavailableError, D as activateQueued, Da as resolveTarget, Di as sectionOf$1, Dn as markAllRead, Dr as fillFileUrlTemplate, E as relativeTime$1, Ea as kissOutcome, Ei as parseLifecycle, En as listNotifications, Er as forgeResolver, Et as mergeUpdateInto, Fa as shorthandOf, Fi as setMembership, Fn as retireNotification, Fr as pruneJobResidue, Ft as prepareReview, G as reviewTag, Ga as STORE_ROOT, Gi as issuesOutstanding, Gn as storedConversation, Gr as isClaimed, Gt as recordTelemetryUploadConsent, H as findingRef, Ha as readAgentPreference, Hi as doneVerb, Hn as adoptLegacyBaseline, Hr as purposeOf, Ht as record, Ia as git, Ii as updateCollections, In as toneOf, Ir as saveJob, It as settleReview, Ja as agentFor$1, Ji as reviewerDispositions, Jn as isUnchanged, Jr as followedRefName, Jt as BUILD_VERSION, K as reviewTagsIn, Ka as AGENT_IDS, Ki as questionsOutstanding, Kn as unforeseenFailure, Kt as telemetryUploadConsent, L as modelProvenanceOf, La as refExists, Li as queueAdd, Ln as unreadAround, Lt as reanalyseReview, Ma as resolveBase, Mi as nameProblemText, Mn as readNotification, Mr as fail$1, Mt as runningJobs, N as cliEntry, Na as AmbiguousRefError, Ni as readCollections, Nn as readStateOf, Nr as isTerminal, Nt as failureOf$1, O as compactQueue, Oa as originRemote, Oi as collectionsStamp, On as markRead, Or as RUN_RECORD_RETENTION_MS, Ot as updateReview, Pa as shortNameOf, Pi as renameCollection, Pn as removeNotification, Pr as loadJob, Pt as observeReview, Q as markAskRunning, Qa as parkedReason, Qi as explanationActionOf, Qn as UNESTABLISHED_MOTIVATION, Qr as kissDecisionRefusal, Qt as mapRuntimes, R as provenanceDetailLines, Ra as resolveAgent, Ri as queueAddMany, Rn as unreadCount$1, Rr as isLiveJob, Rt as runJob, S as extractPullRequestLinks, Sa as extractJsonText, Si as lifecycleName$1, Sn as createArchitectureNotification, Sr as checkReview, St as FETCH_TIMEOUT_MS, T as runBatch, Ta as WITHHELD_REASON, Ti as offersLifecycle, Tn as eventName, Tr as fetchHead, Tt as cancellableHere, U as findingRefs, Ua as writeAgentPreference, Ui as isIssueOutstanding, Un as prepareConversationWithin, V as findingAtRef, Va as clearAgentPreference, Vi as readQueue, Vn as ASK_REREAD_CEILING_MS, Vr as progressSteps, Vt as readEvents, W as refOf, Wi as isQuestionOutstanding, Wn as saveRefresh, X as isRequestId, Xa as detectAgents, Xi as processDiscussion, Xn as EXPLAIN_SIMPLY_PROMPT, Xr as indexKeptIds, Xt as feedbackUrl, Ya as agentIdentityOf, Yi as verdictClosure, Yn as agentEnvOf, Yr as groupByLineage, Za as isAgentId, Zi as recommendationsOf, Zn as chatConversationSection, Zr as indexReviewGroups, Zt as waitingHistory, _ as endRun, _a as statusLookup, _i as activityAttributes, _n as writeJsonAtomically, _r as requestJobStop, _t as parseWorkspacePort, a as lineageIds, aa as assessmentLookup, ai as mutateWorkspace, an as loadArchitecture, ar as escapeHtml$1, b as stopRun, ba as SEVERITIES, bi as lifecycleChip, bn as subjectOf, br as findExistingReview, bt as writePortPreference, c as discardJob, ca as findingAssessment, ci as previousKissResult, cn as repositoryKey, cr as ReviewBeingDeletedError, ct as serverEntryExists, d as startKissJob, da as gradeScale, dn as trackedFiles, dr as MANAGED_ROOT, ea as isExplanationAction, ei as latestKissRun, en as RUNTIME_WORDS, eo as versionFrom, er as hasAuthorContent, f as isDriving, fa as isGradeFor, fi as summarise, fn as ownersOf, fr as createWorktree, g as completeRun, ga as sourcePhrase, gi as LIFECYCLES, gn as isArchitectureId, gr as removeWorktree, gt as MIN_WORKSPACE_PORT, h as beginRun, ha as reassessIn, hi as workspaceStamps, hn as ancestorsOf, hr as readMarker, ht as MAX_WORKSPACE_PORT, i as deleteReview, ia as analysisRefOf, in as checkArchitecture, ir as CODE_COPY_CLASS, ja as isCommitReview, ji as deleteCollection, jn as pruneNotifications, jr as endedAt, jt as liveUpdateFor, ka as DetachedHeadError, ki as createCollection, kn as markReadForReview, kr as allJobs, kt as askCheckout, l as kissAgentId, la as findingIn, li as recentWorkspaces, ln as storeArchitecture, lr as isBusy, lt as spawnServer, m as RUN_KINDS, ma as promptGrade, mi as workspaceRevision, mn as REPOSITORY_SCOPE, mr as isWorktreeHeld, mt as DEFAULT_WORKSPACE_PORT, n as MCP_TOOL_NAMES, na as ANALYSIS_ONLY, ni as lineagePosition, nn as formatDuration, no as AgentCancelledError, nr as renderMarkdown, o as liveJobsFor, oa as closesFinding, on as partFiles, or as renderInlineText, ot as readServerRecord, p as track, pa as isOffered, pi as touchWorkspace, pn as QUOTE_LIMITS, pr as holdWorktree, pt as writeServerRecord, q as endAskRequest, qa as agentById, qi as resolvedByConversation, qn as refreshPrContext, qr as decideKissRecommendation, r as deleteLineage, ra as NO_STATUS, ri as loadWorkspace, rn as ARCHITECTURE_DIR, ro as AgentUnavailableError, rr as CODE_BLOCK_CLASS, rt as clearServerRecordOwnedBy, s as wasBlocked, sa as currentGradeIn, si as presentedKissRunOf, sn as readArchitecture, ta as withExplanation, ti as lineageKey, tn as describeRuntimes, tr as GITHUB_MARKDOWN, tt as readIndexToken, ua as findingStatus, ui as reviewedCommit, un as storedScopes, ur as withReviewLock, ut as stopChild, v as heldRuns, va as wasReassessed, vi as activityLabel, vn as announceJob, vt as resolveWorkspacePort, w as unsupportedReason, wa as describeAuthorship, wi as lifecyclePillLabel, wn as createPrUpdate, wr as reviewedShaOf, wt as cancelUpdate, x as batchIdentity, xa as asString, xi as lifecycleItemLabel, xn as categoryOf, xr as checkCodeFreshness, y as reportRun, ya as followUpFindingId, yi as isLifecycle, yn as branchOf, yt as statedWorkspacePort, z as agentDisplayName, za as resolveModel, zi as queueRemove, zn as withUsageRecorded, zr as isTerminalJobPhase } from "./mcp_tools-BR7_XprF.js";
1
+ import { $a as versionFrom, $i as isExplanationAction, $n as hasAuthorContent, $r as latestKissRun, $t as waitingHistory, A as settleQueue, Aa as isCommitReview, Ai as deleteCollection, An as markRead, Ar as endedAt, At as askCheckout, B as humanizeModelId, Ba as clearAgentPreference, Bi as readQueue, Bn as unreadCount$1, Br as progressSteps, Bt as runJob, C as parsePullRequestLink, Ca as describeAuthorship, Ci as lifecyclePillLabel, Cn as categoryOf, Cr as reviewedShaOf, Ct as RefreshUnavailableError, D as activateQueued, Da as originRemote, Di as collectionsStamp, Dn as eventName, Dr as RUN_RECORD_RETENTION_MS, E as relativeTime$1, Ea as resolveTarget, Ei as sectionOf$1, En as createPrUpdate, Er as fillFileUrlTemplate, Et as mergeUpdateInto, Fa as git, Fi as updateCollections, Fn as readStateOf, Fr as saveJob, Ft as failureOf$1, G as reviewTag, Ga as AGENT_IDS, Gi as questionsOutstanding, Gn as saveRefresh, H as findingRef, Ha as writeAgentPreference, Hi as isIssueOutstanding, Hn as existingPaths, Ia as refExists, Ii as queueAdd, In as removeNotification, It as observeReview, Ja as agentIdentityOf, Ji as verdictClosure, Jn as agentEnvOf, Jr as groupByLineage, Jt as telemetryUploadConsent, K as reviewTagsIn, Ka as agentById, Ki as resolvedByConversation, Kn as storedConversation, Kr as decideKissRecommendation, L as modelProvenanceOf, La as resolveAgent, Li as queueAddMany, Ln as retireNotification, Lr as isLiveJob, Lt as prepareReview, Ma as AmbiguousRefError, Mi as readCollections, Mn as moveIds, Mr as isTerminal, Mt as UpdateAlreadyRunningError, N as cliEntry, Na as shortNameOf, Ni as renameCollection, Nn as pruneNotifications, Nr as loadJob, Nt as liveUpdateFor, O as compactQueue, Oa as DetachedHeadError, Oi as createCollection, On as listNotifications, Or as allJobs, Ot as recheckConversation, Pa as shorthandOf, Pi as setMembership, Pn as readNotification, Pr as pruneJobResidue, Pt as runningJobs, Q as markAskRunning, Qa as runnableAgents, Qi as explanationOf, Qn as authorPreview, Qr as kissDecisionsOf, Qt as feedbackUrl, R as provenanceDetailLines, Ra as resolveModel, Ri as queueRemove, Rn as toneOf, Rr as isTerminalJobPhase, Rt as settleReview, S as extractPullRequestLinks, Sa as objectWithAnyOf, Si as lifecyclePillAction, Sn as subjectOf, Sr as followedBranch, St as FETCH_TIMEOUT_MS, T as runBatch, Ta as kissOutcome, Ti as parseLifecycle, Tn as createPrState, Tr as forgeResolver, Tt as cancellableHere, U as findingRefs, Ui as isQuestionOutstanding, Un as ASK_REREAD_CEILING_MS, Ut as readEvents, V as findingAtRef, Va as readAgentPreference, Vi as doneVerb, Vn as withUsageRecorded, Vr as purposeOf, W as refOf, Wa as STORE_ROOT, Wi as issuesOutstanding, Wn as prepareConversationWithin, Wr as isClaimed, Wt as record, X as isRequestId, Xa as isAgentId, Xi as recommendationsOf, Xn as chatConversationSection, Xr as indexReviewGroups, Xt as BUILD_VERSION, Ya as detectAgents, Yi as processDiscussion, Yn as EXPLAIN_SIMPLY_PROMPT, Yr as indexKeptIds, Za as parkedReason, Zi as explanationActionOf, Zn as UNESTABLISHED_MOTIVATION, Zr as kissDecisionRefusal, _ as endRun, _a as wasReassessed, _i as activityLabel, _n as ancestorsOf, _t as parseWorkspacePort, a as lineageIds, aa as closesFinding, an as ARCHITECTURE_DIR, ar as renderInlineText, b as stopRun, ba as asString, bi as lifecycleItemLabel, bn as announceJob, br as checkCodeFreshness, bt as writePortPreference, c as discardJob, ca as findingIn, ci as recentWorkspaces, cn as partFiles, cr as isBusy, ct as serverEntryExists, d as startKissJob, da as isGradeFor, di as summarise, dn as storeArchitecture, dr as createWorktree, ea as withExplanation, ei as lineageKey, en as mapRuntimes, er as GITHUB_MARKDOWN, f as isDriving, fa as isOffered, fi as touchWorkspace, fn as storedScopes, fr as holdWorktree, g as completeRun, ga as statusLookup, gi as activityAttributes, gn as REPOSITORY_SCOPE, gr as requestJobStop, gt as MIN_WORKSPACE_PORT, h as beginRun, ha as sourcePhrase, hi as LIFECYCLES, hn as QUOTE_LIMITS, hr as removeWorktree, ht as MAX_WORKSPACE_PORT, i as deleteReview, ia as assessmentLookup, ii as mutateWorkspace, in as formatDuration, ir as escapeHtml$1, ja as resolveBase, ji as nameProblemText, jn as markReadForReview, jr as fail$1, jt as findingsChanged, ki as createWithMembers, kn as markAllRead, kr as attemptIdOf, kt as updateReview, l as kissAgentId, la as findingStatus, li as reviewedCommit, ln as readArchitecture, lr as withReviewLock, lt as spawnServer, m as RUN_KINDS, ma as reassessIn, mi as workspaceStamps, mn as ownersOf, mr as readMarker, mt as DEFAULT_WORKSPACE_PORT, n as MCP_TOOL_NAMES, na as NO_STATUS, ni as loadWorkspace, nn as RUNTIME_WORDS, no as AgentUnavailableError, nr as CODE_BLOCK_CLASS, o as liveJobsFor, oa as currentGradeIn, oi as presentedKissRunOf, on as checkArchitecture, ot as readServerRecord, p as track, pa as promptGrade, pi as workspaceRevision, pn as trackedFiles, pr as isWorktreeHeld, pt as writeServerRecord, q as endAskRequest, qa as agentFor$1, qi as reviewerDispositions, qn as isUnchanged, qr as followedRefName, qt as recordTelemetryUploadConsent, r as deleteLineage, ra as analysisRefOf, rn as describeRuntimes, rr as CODE_COPY_CLASS, rt as clearServerRecordOwnedBy, s as wasBlocked, sa as findingAssessment, si as previousKissResult, sn as loadArchitecture, sr as ReviewBeingDeletedError, ta as ANALYSIS_ONLY, ti as lineagePosition, tn as recordRuntimeBestEffort, to as AgentCancelledError, tr as renderMarkdown, tt as readIndexToken, ua as gradeScale, un as repositoryKey, ur as MANAGED_ROOT, ut as stopChild, v as heldRuns, va as followUpFindingId, vi as isLifecycle, vn as isArchitectureId, vt as resolveWorkspacePort, w as unsupportedReason, wa as WITHHELD_REASON, wi as offersLifecycle, wn as createArchitectureNotification, wr as fetchHead, wt as cancelUpdate, x as batchIdentity, xa as extractJsonText, xi as lifecycleName$1, xn as branchOf, xr as checkReview, y as reportRun, ya as SEVERITIES, yi as lifecycleChip, yn as writeJsonAtomically, yr as findExistingReview, yt as statedWorkspacePort, z as agentDisplayName, za as agentIdOf, zi as queueStamp, zn as unreadAround, zr as phasesFor, zt as reanalyseReview } from "./mcp_tools-DbFR9Z90.js";
2
2
  import { basename, delimiter, isAbsolute, join, relative, resolve } from "node:path";
3
3
  import { execFile, spawn } from "node:child_process";
4
4
  import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -1573,6 +1573,7 @@ function tallyEntries(entries) {
1573
1573
  bySection: {},
1574
1574
  verify: 0,
1575
1575
  decide: 0,
1576
+ fix: 0,
1576
1577
  step: 0
1577
1578
  };
1578
1579
  for (const entry of entries) {
@@ -1602,6 +1603,7 @@ function issuesAttentionLine(tally) {
1602
1603
  const steps = [
1603
1604
  tally.verify ? count(tally.verify, "verification step", "verification steps") : "",
1604
1605
  tally.decide ? count(tally.decide, "decision", "decisions") : "",
1606
+ tally.fix ? count(tally.fix, "fix", "fixes") : "",
1605
1607
  tally.step ? count(tally.step, "next step", "next steps") : ""
1606
1608
  ].filter(Boolean);
1607
1609
  return [count(total, "outstanding issue", "outstanding issues"), ...steps].join(" · ");
@@ -3443,8 +3445,13 @@ function renderIssuesTab(session, investigatingId, options) {
3443
3445
  ${renderFileLocation(issue.file, "pr-helper-issue-loc", issue.line)}
3444
3446
  ${renderRelatedFiles(issue.relatedFiles)}
3445
3447
  ${renderInvestigationProvenance("issue", issue.id, records, now)}`;
3448
+ const actions = `<div class="pr-helper-issue-actions">
3449
+ ${renderInvestigationActions("issue", issue.id, records, investigatingId)}
3450
+ ${renderDismissToggle(issue, isDismissed, stale, dismissals.get(issue.id), dispositions.isClosedByVerdict(issue))}
3451
+ </div>`;
3446
3452
  const body = framed ? `${fixed ? "" : renderConsequence(impact)}${explanation}
3447
3453
  ${renderReconciliationNote(issue.reconciliationNote)}
3454
+ <div class="pr-helper-card-footer">
3448
3455
  <details class="pr-helper-issue-technical">
3449
3456
  <summary class="pr-helper-issue-technical-summary">Technical details</summary>
3450
3457
  <div class="pr-helper-issue-technical-body">
@@ -3452,7 +3459,10 @@ function renderIssuesTab(session, investigatingId, options) {
3452
3459
  <p class="pr-helper-issue-technical-title">${renderInlineText(issue.title)}</p>
3453
3460
  ${technical}
3454
3461
  </div>
3455
- </details>` : technical;
3462
+ </details>
3463
+ ${actions}
3464
+ </div>` : `${technical}
3465
+ ${actions}`;
3456
3466
  const next = impact?.next ? ` data-issue-next="${impact.next.kind ?? "step"}"` : "";
3457
3467
  return `
3458
3468
  <div class="pr-helper-issue-card ${cardGradeClass("issue", assessment.current)}${issue.reconciliationStatus ? ` pr-helper-issue-${issue.reconciliationStatus}` : ""}${issue.investigationImpact ? ` pr-helper-issue-investigation-${issue.investigationImpact}` : ""}${isDismissed && !stale ? " pr-helper-issue-card-dismissed" : ""}" data-finding-card="${escapeHtml$1(issue.id)}" data-issue-section="${sectionOf(issue)}"${next}>
@@ -3476,10 +3486,6 @@ function renderIssuesTab(session, investigatingId, options) {
3476
3486
  ${renderStaleDismissal(stale)}
3477
3487
  ${renderAssessmentRegion("issue", assessment, grading, statusOf("issue", issue.id))}
3478
3488
  ${body}
3479
- <div class="pr-helper-issue-actions">
3480
- ${renderInvestigationActions("issue", issue.id, records, investigatingId)}
3481
- ${renderDismissToggle(issue, isDismissed, stale, dismissals.get(issue.id), dispositions.isClosedByVerdict(issue))}
3482
- </div>
3483
3489
  </div>
3484
3490
  `;
3485
3491
  };
@@ -3521,7 +3527,8 @@ function renderConsequence(impact) {
3521
3527
  }
3522
3528
  var STEP_LABELS = {
3523
3529
  verify: "To verify:",
3524
- decide: "To decide:"
3530
+ decide: "To decide:",
3531
+ fix: "To fix:"
3525
3532
  };
3526
3533
  function renderConsequenceStep(step) {
3527
3534
  if (!step) return "";
@@ -3616,13 +3623,47 @@ function renderQuestionCard(q, sections, answered, records, now = Date.now(), in
3616
3623
  const answerToggle = `<button type="button" class="pr-helper-question-answer-toggle" data-question-answered="${escapeHtml$1(q.id)}" data-answered="${answered}"${settled ? " data-settled=\"true\"" : ""}${verdictClosed ? " data-verdict-closed=\"true\"" : ""}>${answered ? "Mark as unanswered" : "Mark answered"}</button>`;
3617
3624
  const investigationAnswer = q.investigationAnswer?.trim() ? `<h5>Answer from investigation</h5><p class="pr-helper-issue-desc pr-helper-investigation-answer">${renderInlineText(q.investigationAnswer)}</p>` : "";
3618
3625
  const reconciliationAnswer = q.reconciliationAnswer?.trim() ? `<h5>${escapeHtml$1(reconciledAnswerHeading(q.reconciledAgainst))}</h5><p class="pr-helper-issue-desc pr-helper-investigation-answer">${renderInlineText(q.reconciliationAnswer)}</p>` : "";
3626
+ const why = q.whyThisMatters ? `<h5>Why this matters</h5><p class="pr-helper-issue-desc">${renderInlineText(q.whyThisMatters)}</p>` : "";
3627
+ const files = q.file || q.relatedFiles?.length ? `<h5>Files</h5>${renderFileLocation(q.file, "pr-helper-issue-loc", void 0)}${renderRelatedFiles(q.relatedFiles)}` : "";
3628
+ const provenance = renderInvestigationProvenance("question", q.id, records, now);
3629
+ const framing = q.framing;
3630
+ const actions = `<div class="pr-helper-question-actions">
3631
+ ${jumpLink}
3632
+ <div class="pr-helper-question-actions-group">
3633
+ ${renderInvestigationActions("question", q.id, records, investigatingId)}
3634
+ ${answerToggle}
3635
+ </div>
3636
+ </div>`;
3637
+ const answers = `${reconciliationAnswer}
3638
+ ${renderReconciliationNote(q.reconciliationNote)}
3639
+ ${investigationAnswer}`;
3640
+ const body = framing ? `${renderQuestionFraming(framing)}${renderCardExplanation(explanation, options)}
3641
+ ${answers}
3642
+ <div class="pr-helper-card-footer">
3643
+ <details class="pr-helper-issue-technical">
3644
+ <summary class="pr-helper-issue-technical-summary">Technical context</summary>
3645
+ <div class="pr-helper-issue-technical-body">
3646
+ <p class="pr-helper-issue-technical-title">${renderInlineText(q.question)}</p>
3647
+ ${why}
3648
+ ${renderInvestigationNote(q.investigationNote)}
3649
+ ${files}
3650
+ ${provenance}
3651
+ </div>
3652
+ </details>
3653
+ ${actions}
3654
+ </div>` : `${why}${renderCardExplanation(explanation, options)}
3655
+ ${answers}
3656
+ ${renderInvestigationNote(q.investigationNote)}
3657
+ ${files}
3658
+ ${provenance}
3659
+ ${actions}`;
3619
3660
  return `
3620
3661
  <div class="pr-helper-question-card${answered ? " pr-helper-question-card-answered" : ""}${q.investigationImpact ? ` pr-helper-issue-investigation-${q.investigationImpact}` : ""}" data-finding-card="${escapeHtml$1(q.id)}"${questionHome(q) ? ` data-section-id="${escapeHtml$1(questionHome(q))}"` : ""}>
3621
3662
  <div class="pr-helper-issue-header">
3622
3663
  <div class="pr-helper-issue-title-row">
3623
3664
  ${options?.feedbackSelect ? renderFeedbackPick("question", q.question, questionFeedbackMarkdown(q), ref) : ""}
3624
3665
  ${renderFindingRef(ref)}
3625
- <p class="pr-helper-issue-title">${renderInlineText(q.question)}</p>
3666
+ <p class="pr-helper-issue-title">${renderInlineText(framing?.question ?? q.question)}</p>
3626
3667
  ${renderFindingCopySource(questionMarkdown(q, assessment), "question")}
3627
3668
  ${options?.askBuddy ? renderAskBuddyMenu("question") : ""}
3628
3669
  </div>
@@ -3639,23 +3680,17 @@ function renderQuestionCard(q, sections, answered, records, now = Date.now(), in
3639
3680
  </div>
3640
3681
  </div>
3641
3682
  ${renderAssessmentRegion("question", assessment, grading, status)}
3642
- ${q.whyThisMatters ? `<h5>Why this matters</h5><p class="pr-helper-issue-desc">${renderInlineText(q.whyThisMatters)}</p>` : ""}${renderCardExplanation(explanation, options)}
3643
- ${reconciliationAnswer}
3644
- ${renderReconciliationNote(q.reconciliationNote)}
3645
- ${investigationAnswer}
3646
- ${renderInvestigationNote(q.investigationNote)}
3647
- ${q.file || q.relatedFiles?.length ? `<h5>Files</h5>${renderFileLocation(q.file, "pr-helper-issue-loc", void 0)}${renderRelatedFiles(q.relatedFiles)}` : ""}
3648
- ${renderInvestigationProvenance("question", q.id, records, now)}
3649
- <div class="pr-helper-question-actions">
3650
- ${jumpLink}
3651
- <div class="pr-helper-question-actions-group">
3652
- ${renderInvestigationActions("question", q.id, records, investigatingId)}
3653
- ${answerToggle}
3654
- </div>
3655
- </div>
3683
+ ${body}
3656
3684
  </div>
3657
3685
  `;
3658
3686
  }
3687
+ /** What the question means, read first: why the answer matters and what is needed. */
3688
+ function renderQuestionFraming(framing) {
3689
+ return `<div class="pr-helper-issue-impact">
3690
+ <p class="pr-helper-issue-scenario">${renderInlineText(framing.why)}</p>
3691
+ ${framing.needed ? `<p class="pr-helper-issue-next pr-helper-question-needed"><span class="pr-helper-issue-next-label">Needed:</span> ${renderInlineText(framing.needed)}</p>` : ""}
3692
+ </div>`;
3693
+ }
3659
3694
  /**
3660
3695
  * The counter's contents, split out so a caller that toggles one question can refresh it without
3661
3696
  * re-rendering the tab.
@@ -4980,13 +5015,12 @@ ${renderDecisions(processed)}
4980
5015
  ${processed.all.length === 0 ? "<p class=\"prb-pr-empty\">This pull request has no comments yet.</p>" : buckets}`;
4981
5016
  }
4982
5017
  /**
4983
- * Re-reading the threads, and what the last re-read found.
5018
+ * Re-checking the discussion, and what the last re-read found.
4984
5019
  *
4985
- * The narrowest useful action in the product, and the copy has to keep it narrow. This corrects
4986
- * what the review already claims about other people's threads — who raised one, whether it is
4987
- * still open — and it cannot produce a finding, because deciding whether a new comment changes
4988
- * anything is judgement and judgement is a fresh review. A reviewer who pressed this expecting a
4989
- * re-review would be worse off than one who never saw the button.
5020
+ * Two things, said in the order they happen: the threads are fetched and every "already raised"
5021
+ * note corrected, then the findings are reassessed against whatever in the discussion is new to
5022
+ * them. The copy keeps it narrow in the one way that still matters: the code is the commit the
5023
+ * review read, so this is not a re-review and a comment claiming a fix cannot settle it here.
4990
5024
  */
4991
5025
  function renderRefresh(refresh) {
4992
5026
  if (!refresh?.enabled) return "";
@@ -4994,10 +5028,12 @@ function renderRefresh(refresh) {
4994
5028
  return `<div class="prb-pr-refresh">
4995
5029
  <button type="button" data-pr-recheck>Re-check the conversation</button>
4996
5030
  <span class="prb-pr-refresh-note">
4997
- Fetches every thread again and updates what this review says about them. If someone has
4998
- replied or resolved a thread since the review ran, the "Already raised" notes on the Issues
4999
- view will be corrected to match, and any note pointing at a thread that has been deleted will
5000
- be removed. Your code is not looked at again, so no findings are added, changed or removed.
5031
+ Fetches the description and every thread again, corrects the "Already raised" notes, then
5032
+ reassesses the issues and questions against anything new. A reply can answer a question,
5033
+ resolve or reopen an issue, or suggest a different severity, and a concern it raises becomes a
5034
+ new issue only once it is confirmed in the reviewed code. A comment saying something is fixed
5035
+ is not taken on trust: the issue stays open until an update sees the fix in the code. Newer
5036
+ commits are left for Update review.
5001
5037
  </span>
5002
5038
  ${last ? `<p class="prb-pr-refresh-last" data-ok="${last.ok}">
5003
5039
  Last re-checked ${escapeHtml$1(relativeTime$1(last.at))}. ${escapeHtml$1(last.message)}
@@ -6015,6 +6051,7 @@ function renderFreshAlternative(command) {
6015
6051
  */
6016
6052
  function renderUpdateOutcome(outcome) {
6017
6053
  if (!outcome) return "";
6054
+ if (outcome.scope === "conversation") return renderRecheckOutcome(outcome);
6018
6055
  const state = outcome.ok ? "done" : outcome.stop === "refused" ? "refused" : outcome.stop === "cancelled" ? "cancelled" : "stopped";
6019
6056
  const title = outcome.ok ? "Review updated" : outcome.stop === "refused" ? "There was nothing to update against" : outcome.stop === "cancelled" ? "Update cancelled" : "The review was not updated";
6020
6057
  const facts = outcome.ok ? updateFacts(outcome) : null;
@@ -6027,6 +6064,72 @@ function renderUpdateOutcome(outcome) {
6027
6064
  </div>`;
6028
6065
  }
6029
6066
  /**
6067
+ * What a Re-check of the PR discussion did, in the same notice an update reports through.
6068
+ *
6069
+ * Five outcomes, each with its own headline, because the one the reviewer most needs to tell apart
6070
+ * is the partial: the discussion came in, so the "already raised" lines are current, but the
6071
+ * findings were not judged against it. That one says why and, when trying again could help, offers it.
6072
+ */
6073
+ function renderRecheckOutcome(outcome) {
6074
+ const retry = outcome.stop === "analysis-failed" && outcome.conversationOk === true;
6075
+ const { state, title, body } = recheckWords(outcome);
6076
+ const facts = outcome.ok && outcome.stop === null ? recheckFacts(outcome) : null;
6077
+ return `<div class="prb-update-result" data-state="${state}" data-update-result data-recheck-result role="status">
6078
+ <div class="prb-update-result-body">
6079
+ ${facts ? `<p class="prb-update-facts"><strong>${escapeHtml$1(title)}</strong>${facts.map((fact) => `<span class="prb-update-fact">${escapeHtml$1(fact)}</span>`).join("")}</p>` : `<p><strong>${escapeHtml$1(title)}</strong> ${escapeHtml$1(body)}</p>`}
6080
+ ${retry ? "<p class=\"prb-update-footnote\"><button type=\"button\" class=\"prb-fresh-quiet\" data-pr-recheck>Try again</button></p>" : ""}
6081
+ </div>
6082
+ <button type="button" class="prb-fresh-dismiss" data-update-close>Close</button>
6083
+ </div>`;
6084
+ }
6085
+ function recheckWords(outcome) {
6086
+ if (outcome.stop === "cancelled") return {
6087
+ state: "cancelled",
6088
+ title: "Re-check cancelled",
6089
+ body: "The findings and the discussion shown are exactly as they were."
6090
+ };
6091
+ if (outcome.conversationOk !== true) return {
6092
+ state: "refused",
6093
+ title: "The PR discussion was not re-checked",
6094
+ body: outcome.message
6095
+ };
6096
+ if (outcome.stop === "already-current") return {
6097
+ state: "done",
6098
+ title: "No findings changed",
6099
+ body: outcome.message
6100
+ };
6101
+ if (outcome.stop !== null) return {
6102
+ state: "stopped",
6103
+ title: "Discussion refreshed, findings not reassessed",
6104
+ body: `The latest comments are in, and the notes about who raised what are current. ${outcome.message}`
6105
+ };
6106
+ if (outcome.counts && !findingsChanged(outcome.counts)) return {
6107
+ state: "done",
6108
+ title: "No findings changed",
6109
+ body: "The new comments were read against every open issue and question, and each one stands as it was."
6110
+ };
6111
+ return {
6112
+ state: "done",
6113
+ title: "Findings reassessed against the PR discussion",
6114
+ body: outcome.message
6115
+ };
6116
+ }
6117
+ /** What a Re-check changed, largest news first, in the words an update uses. No commit: none moved. */
6118
+ function recheckFacts(outcome) {
6119
+ const counts = outcome.counts;
6120
+ if (!counts || !findingsChanged(counts)) return null;
6121
+ const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
6122
+ return [
6123
+ counts.questionsAnswered ? `${plural(counts.questionsAnswered, "question", "questions")} answered` : "",
6124
+ counts.resolved ? `${plural(counts.resolved, "issue", "issues")} resolved by the discussion` : "",
6125
+ counts.reopened ? `${plural(counts.reopened, "issue", "issues")} reopened` : "",
6126
+ counts.affected ? `${counts.affected} to look at again` : "",
6127
+ counts.newIssues ? plural(counts.newIssues, "new issue", "new issues") : "",
6128
+ counts.gradeChanges ? `${plural(counts.gradeChanges, "grade change", "grade changes")} suggested` : "",
6129
+ counts.decisionsChallenged ? `${plural(counts.decisionsChallenged, "accepted answer", "accepted answers")} put back to you` : ""
6130
+ ].filter(Boolean);
6131
+ }
6132
+ /**
6030
6133
  * A successful update as a row of facts: the commit it reached, then what changed, largest news first.
6031
6134
  *
6032
6135
  * Read at a glance rather than parsed. The sentence it replaces ("Updated to 7bc233f: 2 fixed,
@@ -6411,14 +6514,14 @@ function comparisonNote(code, hasPullRequest) {
6411
6514
  * button is the subject of the sentence now, so there is one name for one thing and the reviewer
6412
6515
  * presses the words they just read.
6413
6516
  *
6414
- * Safe to run from here for the reason it always was: re-reading threads can only correct the
6415
- * state of claims that already exist, never produce a finding.
6517
+ * It reads no new code: the findings are reassessed against the discussion at the reviewed commit,
6518
+ * and newer commits stay the Update button's.
6416
6519
  */
6417
6520
  function conversationNote(report, actions) {
6418
6521
  if (!report.conversation.attached) return "";
6419
6522
  const when = escapeHtml$1(relativeTime$1(report.conversation.importedAt));
6420
6523
  if (!actions) return `The PR conversation was last checked ${when}.`;
6421
- return `<button type="button" class="prb-fresh-quiet" data-pr-recheck>Re-check the conversation</button> to refresh comments and resolved threads without re-running the code review. Last checked ${when}.`;
6524
+ return `<button type="button" class="prb-fresh-quiet" data-pr-recheck>Re-check the conversation</button> to fetch new comments and reassess issues and questions against them, at the reviewed commit. Last checked ${when}.`;
6422
6525
  }
6423
6526
  //#endregion
6424
6527
  //#region ../../packages/review-harness/src/workspace/progress_view.ts
@@ -6516,7 +6619,7 @@ function renderProgressView(job, fileCount, cancellable = false, history) {
6516
6619
  * and can be carried on; an update has the previous review, which is why the two are worded apart.
6517
6620
  */
6518
6621
  function renderElapsed(job, cancel, history) {
6519
- const button = !cancel ? "" : cancel === "Cancel review" ? `<button type="button" class="prb-progress-cancel" data-review-cancel="${escapeHtml$1(job.id)}">Cancel review</button>` : `<button type="button" class="prb-progress-cancel" data-update-cancel="${escapeHtml$1(job.id)}">Cancel update</button>`;
6622
+ const button = !cancel ? "" : cancel === "Cancel review" ? `<button type="button" class="prb-progress-cancel" data-review-cancel="${escapeHtml$1(job.id)}">Cancel review</button>` : `<button type="button" class="prb-progress-cancel" data-update-cancel="${escapeHtml$1(job.id)}">${cancel}</button>`;
6520
6623
  return `<div class="prb-progress-foot">
6521
6624
  <div class="prb-progress-runtime">
6522
6625
  <p class="prb-progress-elapsed" data-started-at="${job.startedAt}">${escapeHtml$1(runningFor(job.startedAt))}</p>${renderHistory$1(history)}
@@ -6564,11 +6667,14 @@ function renderHistory$1(history) {
6564
6667
  */
6565
6668
  function renderUpdateProgress(job, cancellable = false, history) {
6566
6669
  if (!isLiveJob(job)) return "";
6567
- return `<div class="prb-update-progress" data-update-phase="${escapeHtml$1(job.phase)}" role="status" aria-live="polite">
6568
- <h3 class="prb-doc-title"><span class="prb-spinner" aria-hidden="true"></span>Updating this review</h3>
6569
- <p class="prb-progress-lead">The previous review will be replaced when reassessment finishes.</p>
6670
+ const recheck = job.scope === "conversation";
6671
+ const title = recheck ? "Re-checking the PR discussion" : "Updating this review";
6672
+ const lead = recheck ? "Issues and questions will be updated from the discussion when reassessment finishes." : "The previous review will be replaced when reassessment finishes.";
6673
+ return `<div class="prb-update-progress" data-update-phase="${escapeHtml$1(job.phase)}"${recheck ? " data-update-scope=\"conversation\"" : ""} role="status" aria-live="polite">
6674
+ <h3 class="prb-doc-title"><span class="prb-spinner" aria-hidden="true"></span>${title}</h3>
6675
+ <p class="prb-progress-lead">${lead}</p>
6570
6676
  ${renderSteps(job)}
6571
- ${renderElapsed(job, cancellable ? "Cancel update" : null, history)}
6677
+ ${renderElapsed(job, cancellable ? recheck ? "Cancel re-check" : "Cancel update" : null, history)}
6572
6678
  </div>`;
6573
6679
  }
6574
6680
  //#endregion
@@ -6957,20 +7063,28 @@ function renderStandardViews(view, sections) {
6957
7063
  * When the "already raised" claims below were last true.
6958
7064
  *
6959
7065
  * Every attribution on this view carries a thread's state — "still unresolved", "since marked
6960
- * resolved" — and that state was frozen the moment the conversation was imported. Nothing
6961
- * re-checks it, and nothing here can: the daemon reaches no forge. So a card can go on telling
6962
- * the reviewer that Sergei's thread is open an hour after Sergei closed it.
7066
+ * resolved" — and that state was frozen the moment the conversation was imported. Nothing on this
7067
+ * view re-checks it; the PR context view does, on request. Until then a card can go on telling the
7068
+ * reviewer that Sergei's thread is open an hour after Sergei closed it.
6963
7069
  *
6964
7070
  * Every other kind of staleness in this product degrades into vagueness. This one degrades into a
6965
7071
  * confident, specific, wrong claim about what a named colleague currently thinks, which is worth
6966
- * one line above the list rather than an explanation the reviewer has to go and find.
7072
+ * one line above the list: its age always in view, the explanation one disclosure away, and the way
7073
+ * to the re-check beside it.
6967
7074
  *
6968
7075
  * Only where a conversation was attached: there are no such claims to qualify otherwise.
6969
7076
  */
6970
7077
  function renderAttributionAge(view) {
6971
7078
  if (!view.prContext) return "";
6972
7079
  const imported = relativeTime$1(view.prContext.prContext.discussion.fetchedAt);
6973
- return `<p class="prb-doc-note">Any &ldquo;already raised&rdquo; note below describes the pull request as it stood ${escapeHtml$1(imported)}, when its discussion was fetched. That includes whether each thread was still open. If someone has replied or resolved a thread since then, this page has no way of knowing. Use <button type="button" class="prb-doc-note-link" data-view-nav="prcontext">Re-check the conversation</button> on the PR context view to bring it up to date.</p>`;
7080
+ return `<div class="prb-discussion-age">
7081
+ <details class="prb-discussion-age-detail">
7082
+ <summary class="prb-discussion-age-summary">PR discussion checked ${escapeHtml$1(imported)}</summary>
7083
+ <p class="prb-discussion-age-body">The issues and questions below, their &ldquo;already raised&rdquo; notes and whether each thread was still open describe the pull request as it stood when its discussion was fetched. A reply or a resolved thread since then is not reflected here until the conversation is re-checked, which also reassesses the issues and questions against it.</p>
7084
+ </details>
7085
+ <span class="prb-discussion-age-sep" aria-hidden="true">&middot;</span>
7086
+ <button type="button" class="prb-doc-note-link" data-view-nav="prcontext">Re-check in PR context</button>
7087
+ </div>`;
6974
7088
  }
6975
7089
  /**
6976
7090
  * The link out to this file on the pull request.
@@ -12690,11 +12804,28 @@ feature: E</pre>
12690
12804
  confirm</b> where the behaviour is right only if it was meant, and <b>Maintenance</b> for what it
12691
12805
  costs later. Within each group the most severe come first. Severity and confidence are shown on
12692
12806
  each card, separately from its group.</p>
12693
- <p>A card leads with what the issue means and when it happens, then the step that would settle
12694
- it, marked <b>To verify</b> or <b>To decide</b>. The technical account, where it is in the code and
12695
- its history are under <b>Technical details</b> on the card. Folding a group is remembered for the
12696
- next review. Issues from reviews made before this have no grouping and are under <b>Other
12697
- issues</b>, as they were.</p>
12807
+ <p>A card leads with what the issue means and when it happens, then the step it calls for, when
12808
+ there is one: <b>To verify</b> settles whether it happens, <b>To decide</b> asks whether it was
12809
+ meant, and <b>To fix</b> names the change that would correct it. The technical account, where it
12810
+ is in the code and its history are under <b>Technical details</b> on the card. Folding a group is
12811
+ remembered for the next review.</p>
12812
+ <p>Issues from older reviews have no summary yet and are under <b>Other issues</b>. The next
12813
+ Update that finds new commits or new discussion writes one for each issue still outstanding,
12814
+ from the same evidence; it does not re-check the issue. An Update that finds nothing new changes
12815
+ nothing.</p>
12816
+ <p>Questions read the same way: each leads with the question in plain terms, why the answer
12817
+ matters and what is needed, with the original question and its files under <b>Technical
12818
+ context</b>. Any answer so far stays on the card.</p>
12819
+ <p>When the review came with a pull request discussion, the line above the issues says when it
12820
+ was last checked. The issues, the questions and their “already raised” notes describe the
12821
+ discussion as it was then; <b>Re-check in PR context</b> takes you to bring it up to date.</p>
12822
+ <p>Re-checking fetches the latest comments and reassesses every issue and question against
12823
+ anything new in them. A reply can answer a question, resolve or reopen an issue, or suggest a
12824
+ different severity or importance, and a concern raised in a comment becomes a new issue once it
12825
+ is confirmed in the reviewed code. A thread marked resolved, or a comment saying something is
12826
+ fixed, does not resolve an issue on its own: it stays open until an Update sees the fix in the
12827
+ code. Re-checking reads no new commits; Update review does. When the comments only change how
12828
+ much one finding matters, <b>Reassess</b> on its card asks about that one alone.</p>
12698
12829
  <p>Resolved and dismissed issues are in the closed list at the bottom. One the new commits fixed
12699
12830
  leads with what the code now does, when the update could see it.</p>`
12700
12831
  },
@@ -12707,7 +12838,7 @@ feature: E</pre>
12707
12838
  <li><b>Update</b> brings the review up to date with commits added since it was made. Its
12708
12839
  findings are checked against the new commits rather than thrown away, and each says whether
12709
12840
  the new commits affected it. In the workspace, Update review appears on a review whose
12710
- branch has moved on.</li>
12841
+ branch has moved on. It also reads the pull request discussion, as Re-check does.</li>
12711
12842
  <li><b>Reanalyse</b> analyses the same commit again, for a fresh judgement of the same change,
12712
12843
  for example with a different model.</li>
12713
12844
  <li><b>Fresh review</b> reviews the target again as a new review, and keeps the existing one
@@ -18283,10 +18414,15 @@ async function sendWorkspacePage(res, workspace) {
18283
18414
  const updateJob = liveUpdateFor(workspace.id);
18284
18415
  const reviewJob = workspace.jobId ? loadJob(workspace.jobId) : void 0;
18285
18416
  const kissJob = kissRun ? loadJob(kissRun.jobId) : void 0;
18286
- const notice = workspace.lastUpdate?.acknowledged ? void 0 : workspace.lastUpdate;
18287
- if (notice) mutateWorkspace(workspace.id, (current) => {
18288
- if (current.lastUpdate?.at === notice.at) current.lastUpdate.acknowledged = true;
18289
- });
18417
+ const unseen = [workspace.lastUpdate, workspace.lastRecheck].filter((outcome) => outcome !== void 0 && !outcome.acknowledged);
18418
+ const notice = unseen.sort((a, b) => b.at - a.at)[0];
18419
+ if (notice) {
18420
+ const read = new Set(unseen.map((outcome) => outcome.at));
18421
+ mutateWorkspace(workspace.id, (current) => {
18422
+ if (current.lastUpdate && read.has(current.lastUpdate.at)) current.lastUpdate.acknowledged = true;
18423
+ if (current.lastRecheck && read.has(current.lastRecheck.at)) current.lastRecheck.acknowledged = true;
18424
+ });
18425
+ }
18290
18426
  const everyReview = recentWorkspaces(Number.MAX_SAFE_INTEGER);
18291
18427
  const html = renderWorkspace({
18292
18428
  session: workspace.session,
@@ -18515,33 +18651,78 @@ async function kissFreshnessOrNull(workspace) {
18515
18651
  }
18516
18652
  }
18517
18653
  /**
18518
- * Re-read the pull request conversation, at the reviewer's request.
18654
+ * Re-check the pull request discussion, at the reviewer's request: re-read it, then reconcile the
18655
+ * findings with whatever in it is new to them, at the commit the review read.
18656
+ *
18657
+ * Answers 200 with `ok: false` rather than an error status when it does not work, for the reason the
18658
+ * update route does: a failure is an outcome the page has to explain in a sentence. 409 when an update
18659
+ * or another Re-check holds the review, which the reloaded page then shows running.
18519
18660
  *
18520
- * Answers 200 with `ok: false` rather than an error status when it does not work. The page's job
18521
- * here is to tell the reviewer what happened in a sentence, and a failed re-read is an outcome —
18522
- * the conversation on disk is untouched and the review is exactly as usable as it was a moment
18523
- * ago. An HTTP error would make the page choose between a stack trace and silence.
18661
+ * Saved in two ways, because two different things can come of it. A reassessment that was applied
18662
+ * goes on the way an update does, onto the review as it is on disk. Anything short of that changed
18663
+ * only the conversation and the "already raised" lines, and goes on the way a re-read always has,
18664
+ * through `saveRefresh`, which will not put an older conversation over a newer one.
18524
18665
  */
18525
18666
  async function handleRefreshPr(res, workspace) {
18526
18667
  const reattributing = JSON.stringify(workspace.session.result);
18527
- adoptLegacyBaseline(workspace);
18528
- const startedAt = Date.now();
18668
+ const begun = Date.now();
18529
18669
  let outcome;
18530
18670
  try {
18531
- outcome = await refreshPrContext(workspace);
18532
- } catch {
18533
- outcome = unforeseenFailure(startedAt);
18671
+ outcome = await track(recheckConversation(workspace, { source: "workspace" }));
18672
+ } catch (error) {
18673
+ if (error instanceof UpdateAlreadyRunningError) return send(res, 409, { error: error.message });
18674
+ outcome = {
18675
+ at: Date.now(),
18676
+ ok: false,
18677
+ message: error instanceof RefreshUnavailableError || error instanceof ReviewBeingDeletedError ? error.message : "The findings could not be reassessed. Nothing about them has changed.",
18678
+ stop: "analysis-failed",
18679
+ fetchFailed: false,
18680
+ moved: false,
18681
+ ...rereadSince(workspace, begun) ? { conversationOk: workspace.lastPrRefresh.ok } : {},
18682
+ fromSha: null,
18683
+ toSha: null,
18684
+ scope: "conversation"
18685
+ };
18534
18686
  }
18535
- saveRefresh(workspace, outcome, reattributing);
18687
+ const applied = outcome.ok && outcome.stop === null;
18688
+ const cancelled = outcome.stop === "cancelled";
18689
+ workspace.lastRecheck = outcome;
18690
+ const reread = !applied && !cancelled ? rereadSince(workspace, begun) : void 0;
18691
+ if (reread) saveRefresh(workspace, reread, reattributing);
18692
+ const saved = mutateWorkspace(workspace.id, (fresh) => applied ? {
18693
+ ...mergeUpdateInto(fresh, workspace),
18694
+ lastRecheck: outcome
18695
+ } : {
18696
+ ...fresh,
18697
+ lastRecheck: outcome
18698
+ });
18699
+ const refresh = reread ?? (applied ? workspace.lastPrRefresh : void 0);
18536
18700
  record({
18537
18701
  event: "pr_conversation_rechecked",
18538
18702
  reviewId: workspace.id,
18539
- ok: outcome.ok,
18540
- changed: outcome.changes ? !isUnchanged(outcome.changes) : false,
18541
- attributionsRestated: outcome.attributionsRestated ?? 0,
18542
- attributionsWithdrawn: outcome.attributionsWithdrawn ?? 0
18703
+ ok: outcome.conversationOk === true,
18704
+ changed: refresh?.changes ? !isUnchanged(refresh.changes) : false,
18705
+ attributionsRestated: refresh?.attributionsRestated ?? 0,
18706
+ attributionsWithdrawn: refresh?.attributionsWithdrawn ?? 0,
18707
+ reassessment: reassessmentOf(outcome),
18708
+ resolved: outcome.counts?.resolved ?? 0,
18709
+ questionsAnswered: outcome.counts?.questionsAnswered ?? 0,
18710
+ newIssues: outcome.counts?.newIssues ?? 0
18543
18711
  });
18544
- send(res, 200, outcome);
18712
+ send(res, 200, saved?.lastRecheck ?? outcome);
18713
+ }
18714
+ /** The re-read this request made, if it made one: the copy's record, begun no earlier than the request. */
18715
+ function rereadSince(workspace, begun) {
18716
+ const refresh = workspace.lastPrRefresh;
18717
+ return refresh && (refresh.startedAt ?? refresh.at) >= begun ? refresh : void 0;
18718
+ }
18719
+ /** What a Re-check did to the findings, as the closed set the log records. */
18720
+ function reassessmentOf(outcome) {
18721
+ if (outcome.stop === "cancelled") return "cancelled";
18722
+ if (outcome.stop === "already-current") return "not-needed";
18723
+ if (outcome.stop === "analysis-failed") return "failed";
18724
+ if (outcome.stop !== null) return "skipped";
18725
+ return outcome.counts && findingsChanged(outcome.counts) ? "changed" : "unchanged";
18545
18726
  }
18546
18727
  /**
18547
18728
  * Bring the whole review up to date with the checkout, at the reviewer's request.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prreviewbuddy",
3
- "version": "0.91.1",
3
+ "version": "0.93.0",
4
4
  "description": "Understand code changes before you approve them. Local branch and pull request reviews with Claude Code or Codex.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://prreviewbuddy.com",
@@ -1 +1 @@
1
- import{bt as e}from"./chunk-DRtin0n-.js";var t=`button:not([disabled])`;function n(e){return e.querySelector(`[data-menu-panel]`)}function r(e){return e.querySelector(`[data-menu-trigger]`)}function i(e){let t=n(e);return t?[...t.querySelectorAll(`input:not([disabled]), textarea:not([disabled]), button:not([disabled])`)].filter(e=>!e.closest(`[hidden]`)):[]}function a(e){let r=n(e);return r?[...r.querySelectorAll(t)].filter(e=>!e.closest(`[hidden]`)):[]}var o=`data-menu-placement`,s=`data-menu-align`;function c(e){let t=e.ownerDocument.defaultView,n=0,r=t?.innerHeight??1/0,i=0,a=t?.innerWidth??1/0,o=e=>e!==`visible`&&e!==``;for(let s=e.parentElement;s&&t;s=s.parentElement){let e=t.getComputedStyle(s);if(!o(e.overflowY)&&!o(e.overflowX))continue;let c=s.getBoundingClientRect();o(e.overflowY)&&(n=Math.max(n,c.top),r=Math.min(r,c.bottom)),o(e.overflowX)&&(i=Math.max(i,c.left),a=Math.min(a,c.right))}return{top:n,bottom:r,left:i,right:a}}var l=8,u=120;function d(e){let t=n(e),i=r(e);if(!t||!i)return;t.removeAttribute(o),t.removeAttribute(s),t.style.removeProperty(`max-height`),t.style.removeProperty(`overflow-y`),t.style.removeProperty(`box-sizing`),t.scrollTop=0;let a=t.getBoundingClientRect(),d=i.getBoundingClientRect(),f=c(e);a.right>f.right-l&&d.right-a.width>=f.left+l&&t.setAttribute(s,`end`);let p=Math.max(0,a.top-d.bottom),m=f.bottom-d.bottom-p-l,h=d.top-f.top-p-l,g=a.height,_=g<=m?`below`:g<=h||h>m?`above`:`below`;_===`above`&&t.setAttribute(o,`above`);let v=_===`above`?h:m;g<=v||(t.style.boxSizing=`border-box`,t.style.maxHeight=`${Math.max(Math.floor(v),u)}px`,t.style.overflowY=`auto`)}function f(e){return!e.metaKey&&!e.ctrlKey&&!e.altKey&&!e.shiftKey?e.key===`j`?`ArrowDown`:e.key===`k`?`ArrowUp`:e.key:e.key}function p(t,o={}){let s=null,c=null,l=o.within?`${o.within} [data-menu]`:`[data-menu]`,u=e=>e&&(!o.within||e.closest(o.within))?e:null,p=(e,t={})=>{let i=n(e);if(!i||i.hidden)return;i.hidden=!0;let a=r(e);a?.setAttribute(`aria-expanded`,`false`);let l=s===e?c:null;s===e&&(s=null,c=null),o.onClose?.(e),t.returnFocus&&(l??(()=>a?.focus()))()},m=()=>{for(let e of t.querySelectorAll(l))p(e)},h=(e,t,i=null)=>{let l=n(e);l&&(m(),c=i,o.onOpen?.(e),l.hidden=!1,d(e),r(e)?.setAttribute(`aria-expanded`,`true`),s=e,t&&a(e)[0]?.focus())};return t.addEventListener(`click`,e=>{let t=e.target,r=u(t?.closest?.(`[data-menu]`)??null);if(!r){m();return}if(t?.closest(`[data-menu-trigger]`)){let t=e.detail===0;n(r)?.hidden?h(r,t):p(r);return}s&&s!==r&&p(s)}),t.addEventListener(`keydown`,t=>{if(s&&!s.isConnected&&(s=null),!s)return;let n=s;if(t.key===`Escape`){t.preventDefault(),p(n,{returnFocus:!0});return}if(!n.contains(t.target))return;if(t.key===`Tab`){if(n.hasAttribute(`data-menu-form`)){let e=i(n),r=e.indexOf(t.target);if(e.length>0){t.preventDefault(),e[(r+(t.shiftKey?-1:1)+e.length)%e.length].focus();return}}p(n);return}if(e(t.target))return;let r=a(n);if(r.length===0)return;let o=r.indexOf(t.target),c=f(t),l=c===`ArrowDown`?r[(o+1)%r.length]:c===`ArrowUp`?r[(o-1+r.length)%r.length]:c===`Home`?r[0]:c===`End`?r[r.length-1]:null;l&&(t.preventDefault(),l.focus(),l.scrollIntoView({block:`nearest`}))}),{open:(e,t={})=>h(e,t.focusFirst===!0,t.returnFocus??null),close:p,closeAll:m}}function m(e){return e<3e4?1e3:e<12e4?3e3:e<6e5?8e3:15e3}export{p as n,m as t};
1
+ import{bt as e}from"./chunk-DRtin0n-.js";var t=`button:not([disabled])`;function n(e){return e.querySelector(`[data-menu-panel]`)}function r(e){return e.querySelector(`[data-menu-trigger]`)}function i(e){let t=n(e);return t?[...t.querySelectorAll(`input:not([disabled]), textarea:not([disabled]), button:not([disabled])`)].filter(e=>!e.closest(`[hidden]`)):[]}function a(e){let r=n(e);return r?[...r.querySelectorAll(t)].filter(e=>!e.closest(`[hidden]`)):[]}var o=`data-menu-placement`,s=`data-menu-align`;function c(e){let t=e.ownerDocument.defaultView,n=0,r=t?.innerHeight??1/0,i=0,a=t?.innerWidth??1/0,o=e=>e!==`visible`&&e!==``;for(let s=e.parentElement;s&&t;s=s.parentElement){let e=t.getComputedStyle(s);if(!o(e.overflowY)&&!o(e.overflowX))continue;let c=s.getBoundingClientRect();o(e.overflowY)&&(n=Math.max(n,c.top),r=Math.min(r,c.bottom)),o(e.overflowX)&&(i=Math.max(i,c.left),a=Math.min(a,c.right))}return{top:n,bottom:r,left:i,right:a}}var l=8,u=120;function d(e){let t=n(e),i=r(e);if(!t||!i)return;t.removeAttribute(o),t.removeAttribute(s),t.style.removeProperty(`max-height`),t.style.removeProperty(`overflow-y`),t.style.removeProperty(`box-sizing`),t.scrollTop=0;let a=t.getBoundingClientRect(),d=i.getBoundingClientRect(),f=c(e);a.right>f.right-l&&d.right-a.width>=f.left+l&&t.setAttribute(s,`end`);let p=Math.max(0,a.top-d.bottom),m=f.bottom-d.bottom-p-l,h=d.top-f.top-p-l,g=a.height,_=g<=m?`below`:g<=h||h>m?`above`:`below`;_===`above`&&t.setAttribute(o,`above`);let v=_===`above`?h:m;g<=v||(t.style.boxSizing=`border-box`,t.style.maxHeight=`${Math.max(Math.floor(v),u)}px`,t.style.overflowY=`auto`)}function f(e){return!e.metaKey&&!e.ctrlKey&&!e.altKey&&!e.shiftKey?e.key===`j`?`ArrowDown`:e.key===`k`?`ArrowUp`:e.key:e.key}function p(t,o={}){let s=null,c=null,l=o.within?`${o.within} [data-menu]`:`[data-menu]`,u=e=>e&&(!o.within||e.closest(o.within))?e:null,p=(e,t={})=>{let i=n(e);if(!i||i.hidden)return;i.hidden=!0;let a=r(e);a?.setAttribute(`aria-expanded`,`false`);let l=s===e?c:null;s===e&&(s=null,c=null),o.onClose?.(e),t.returnFocus&&(l??(()=>a?.focus()))()},m=()=>{for(let e of t.querySelectorAll(l))p(e)},h=(e,t,i=null)=>{let l=n(e);l&&(m(),c=i,o.onOpen?.(e),l.hidden=!1,d(e),r(e)?.setAttribute(`aria-expanded`,`true`),s=e,t&&a(e)[0]?.focus())};return t.addEventListener(`click`,e=>{let t=e.target,r=u(t?.closest?.(`[data-menu]`)??null);if(!r){m();return}if(t?.closest(`[data-menu-trigger]`)){let t=e.detail===0;n(r)?.hidden?h(r,t):p(r);return}s&&s!==r&&p(s)}),t.addEventListener(`keydown`,t=>{if(s&&!s.isConnected&&(s=null),!s)return;let n=s;if(t.key===`Escape`){t.preventDefault(),p(n,{returnFocus:!0});return}if(!n.contains(t.target))return;if(t.key===`Tab`){if(n.hasAttribute(`data-menu-form`)){let e=i(n),r=e.indexOf(t.target);if(e.length>0){t.preventDefault(),e[(r+(t.shiftKey?-1:1)+e.length)%e.length].focus();return}}p(n);return}if(e(t.target))return;let r=a(n);if(r.length===0)return;let o=r.indexOf(t.target),c=f(t),l=c===`ArrowDown`?r[(o+1)%r.length]:c===`ArrowUp`?r[(o-1+r.length)%r.length]:c===`Home`?r[0]:c===`End`?r[r.length-1]:null;l&&(t.preventDefault(),l.focus(),l.scrollIntoView({block:`nearest`}))}),{open:(e,t={})=>h(e,t.focusFirst===!0,t.returnFocus??null),close:p,closeAll:m}}function m(e){return e===`done`||e===`failed`}function h(e,t){let n=e=>e.toLowerCase().replace(/\bthe\b/g,``).replace(/[^a-z]/g,``);return n(e)===n(t)}function g(e){return e<3e4?1e3:e<12e4?3e3:e<6e5?8e3:15e3}export{p as i,m as n,h as r,g as t};