prreviewbuddy 0.91.1 → 0.92.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 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-BKPpd9dN.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.
@@ -6957,20 +6992,28 @@ function renderStandardViews(view, sections) {
6957
6992
  * When the "already raised" claims below were last true.
6958
6993
  *
6959
6994
  * 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.
6995
+ * resolved" — and that state was frozen the moment the conversation was imported. Nothing on this
6996
+ * view re-checks it; the PR context view does, on request. Until then a card can go on telling the
6997
+ * reviewer that Sergei's thread is open an hour after Sergei closed it.
6963
6998
  *
6964
6999
  * Every other kind of staleness in this product degrades into vagueness. This one degrades into a
6965
7000
  * 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.
7001
+ * one line above the list: its age always in view, the explanation one disclosure away, and the way
7002
+ * to the re-check beside it.
6967
7003
  *
6968
7004
  * Only where a conversation was attached: there are no such claims to qualify otherwise.
6969
7005
  */
6970
7006
  function renderAttributionAge(view) {
6971
7007
  if (!view.prContext) return "";
6972
7008
  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>`;
7009
+ return `<div class="prb-discussion-age">
7010
+ <details class="prb-discussion-age-detail">
7011
+ <summary class="prb-discussion-age-summary">PR discussion checked ${escapeHtml$1(imported)}</summary>
7012
+ <p class="prb-discussion-age-body">Any &ldquo;already raised&rdquo; note below, and whether each thread was still open, describes 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.</p>
7013
+ </details>
7014
+ <span class="prb-discussion-age-sep" aria-hidden="true">&middot;</span>
7015
+ <button type="button" class="prb-doc-note-link" data-view-nav="prcontext">Re-check in PR context</button>
7016
+ </div>`;
6974
7017
  }
6975
7018
  /**
6976
7019
  * The link out to this file on the pull request.
@@ -12690,11 +12733,21 @@ feature: E</pre>
12690
12733
  confirm</b> where the behaviour is right only if it was meant, and <b>Maintenance</b> for what it
12691
12734
  costs later. Within each group the most severe come first. Severity and confidence are shown on
12692
12735
  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>
12736
+ <p>A card leads with what the issue means and when it happens, then the step it calls for, when
12737
+ there is one: <b>To verify</b> settles whether it happens, <b>To decide</b> asks whether it was
12738
+ meant, and <b>To fix</b> names the change that would correct it. The technical account, where it
12739
+ is in the code and its history are under <b>Technical details</b> on the card. Folding a group is
12740
+ remembered for the next review.</p>
12741
+ <p>Issues from older reviews have no summary yet and are under <b>Other issues</b>. The next
12742
+ Update that finds new commits or new discussion writes one for each issue still outstanding,
12743
+ from the same evidence; it does not re-check the issue. An Update that finds nothing new changes
12744
+ nothing.</p>
12745
+ <p>Questions read the same way: each leads with the question in plain terms, why the answer
12746
+ matters and what is needed, with the original question and its files under <b>Technical
12747
+ context</b>. Any answer so far stays on the card.</p>
12748
+ <p>When the review came with a pull request discussion, the line above the issues says when it
12749
+ was last checked. Its “already raised” notes describe the discussion as it was then; <b>Re-check
12750
+ in PR context</b> takes you to bring it up to date.</p>
12698
12751
  <p>Resolved and dismissed issues are in the closed list at the bottom. One the new commits fixed
12699
12752
  leads with what the code now does, when the update could see it.</p>`
12700
12753
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prreviewbuddy",
3
- "version": "0.91.1",
3
+ "version": "0.92.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",
package/static/shell.css CHANGED
@@ -2386,10 +2386,6 @@ a.prb-rail-item { text-decoration: none; }
2386
2386
  line-height: var(--lh-body);
2387
2387
  }
2388
2388
 
2389
- /* A caveat above a list, in the light pane. Pane literals, not shell tokens — see
2390
- pane_palette.test.ts for why this is a rule and not a preference. */
2391
- /* Full width, because it introduces the cards below it and a narrower block reads as a floating
2392
- aside rather than as the heading note it is. */
2393
2389
  /* The control named in the note, as the control rather than as bold text. It goes to the view that
2394
2390
  holds it, because a sentence that tells the reviewer where something is and cannot take them
2395
2391
  there is asking them to do the navigating twice. */
@@ -2405,16 +2401,41 @@ a.prb-rail-item { text-decoration: none; }
2405
2401
  }
2406
2402
  .prb-doc-note-link:hover { color: var(--pane-ink); }
2407
2403
 
2408
- .prb-doc-note {
2409
- margin: 0 0 14px;
2410
- padding: 8px 11px;
2411
- border-left: 3px solid var(--pane-line);
2412
- border-radius: 0 6px 6px 0;
2413
- background: rgba(15, 23, 42, 0.035);
2404
+ /* The age of the "already raised" claims, as one quiet line above the Issues orientation. */
2405
+ .prb-discussion-age {
2406
+ display: flex;
2407
+ flex-wrap: wrap;
2408
+ align-items: baseline;
2409
+ column-gap: 6px;
2410
+ margin: 0 0 12px;
2414
2411
  color: var(--pane-ink-muted);
2415
2412
  font-size: var(--t-meta);
2416
2413
  line-height: var(--lh-body);
2417
2414
  }
2415
+ .prb-discussion-age-summary {
2416
+ cursor: pointer;
2417
+ list-style: none;
2418
+ }
2419
+ .prb-discussion-age-summary::-webkit-details-marker { display: none; }
2420
+ .prb-discussion-age-summary::before {
2421
+ content: '';
2422
+ display: inline-block;
2423
+ margin-right: 6px;
2424
+ vertical-align: 1px;
2425
+ border-left: 5px solid currentColor;
2426
+ border-top: 4px solid transparent;
2427
+ border-bottom: 4px solid transparent;
2428
+ }
2429
+ .prb-discussion-age-detail[open] > .prb-discussion-age-summary::before { transform: rotate(90deg); }
2430
+ .prb-discussion-age-summary:hover { color: var(--pane-ink); }
2431
+ /* Open, the explanation takes the row, and the re-check follows it on its own line. */
2432
+ .prb-discussion-age-detail[open] { flex-basis: 100%; }
2433
+ .prb-discussion-age-detail[open] ~ .prb-discussion-age-sep { display: none; }
2434
+ .prb-discussion-age-body {
2435
+ margin: 6px 0 4px;
2436
+ padding-left: 11px;
2437
+ border-left: 2px solid var(--pane-line);
2438
+ }
2418
2439
 
2419
2440
  .prb-pr-asof {
2420
2441
  flex-basis: 100%;
@@ -1068,7 +1068,7 @@
1068
1068
  .pr-helper-issue-card {
1069
1069
  border: 1px solid var(--color-border-default, #d0d7de);
1070
1070
  border-radius: 8px;
1071
- padding: 16px;
1071
+ padding: 12px 14px;
1072
1072
  margin-bottom: 12px;
1073
1073
  /* Stated rather than inherited: the surface behind this is now the grey canvas. */
1074
1074
  background: #ffffff;
@@ -1437,7 +1437,7 @@
1437
1437
  .pr-helper-question-card {
1438
1438
  border: 1px solid var(--color-border-default, #d0d7de);
1439
1439
  border-radius: 8px;
1440
- padding: 16px;
1440
+ padding: 12px 14px;
1441
1441
  margin-bottom: 12px;
1442
1442
  /* Stated rather than inherited: the surface behind this is now the grey canvas. */
1443
1443
  background: #ffffff;
@@ -1447,6 +1447,9 @@
1447
1447
  card would throw away the reviewer's own record that they resolved it. The check mark is the
1448
1448
  whole "done" signal; nothing else about the card recedes. */
1449
1449
  .pr-helper-question-card-answered { box-shadow: inset 3px 0 0 #1a7f37; }
1450
+ /* What an answer still needs is moot once the question is settled, and a tick moves the card here
1451
+ without a redraw, so the drawer is what decides it. */
1452
+ .pr-helper-closed-findings .pr-helper-question-needed { display: none; }
1450
1453
 
1451
1454
  .pr-helper-question-card h5 {
1452
1455
  font-size: 10px;
@@ -1598,14 +1601,15 @@
1598
1601
  }
1599
1602
 
1600
1603
  .pr-helper-issues-orientation-text {
1601
- font-size: 13px;
1604
+ font-size: 14px;
1605
+ color: var(--color-fg-default, #1f2328);
1602
1606
  line-height: 1.5;
1603
1607
  margin: 0 0 4px;
1604
1608
  overflow-wrap: anywhere;
1605
1609
  }
1606
1610
 
1607
1611
  .pr-helper-issues-attention {
1608
- font-size: 12px;
1612
+ font-size: 13px;
1609
1613
  font-weight: 600;
1610
1614
  color: var(--color-fg-muted, #57606a);
1611
1615
  margin: 0;
@@ -1713,6 +1717,21 @@
1713
1717
  drawer, smaller and muted, because it belongs to one card rather than to the pane. */
1714
1718
  .pr-helper-issue-technical { margin-top: 8px; }
1715
1719
 
1720
+ /* A framed card's disclosure and actions share a row: closed, the summary sits left and the actions
1721
+ right; opened, the disclosure takes the row and the actions wrap below its body. */
1722
+ .pr-helper-card-footer {
1723
+ display: flex;
1724
+ flex-wrap: wrap;
1725
+ align-items: center;
1726
+ column-gap: 12px;
1727
+ row-gap: 8px;
1728
+ margin-top: 8px;
1729
+ }
1730
+ .pr-helper-card-footer > .pr-helper-issue-technical { flex: 1 1 auto; min-width: 0; margin-top: 0; }
1731
+ .pr-helper-card-footer > .pr-helper-issue-technical[open] { flex-basis: 100%; }
1732
+ .pr-helper-card-footer > .pr-helper-issue-actions,
1733
+ .pr-helper-card-footer > .pr-helper-question-actions { margin-top: 0; margin-left: auto; }
1734
+
1716
1735
  .pr-helper-issue-technical-summary {
1717
1736
  display: inline-flex;
1718
1737
  align-items: center;
@@ -1769,7 +1788,7 @@
1769
1788
  justify-content: space-between;
1770
1789
  flex-wrap: wrap;
1771
1790
  gap: 12px;
1772
- margin-top: 12px;
1791
+ margin-top: 8px;
1773
1792
  }
1774
1793
 
1775
1794
  /* Deliberately not a filled button, matching .pr-helper-guide-start-cta: this is "here's the
@@ -5000,7 +5019,7 @@
5000
5019
  align-items: center;
5001
5020
  justify-content: flex-end;
5002
5021
  gap: 12px;
5003
- margin-top: 12px;
5022
+ margin-top: 8px;
5004
5023
  }
5005
5024
 
5006
5025
  /* margin-left:auto rather than relying on the row's space-between: the jump link beside it is