prreviewbuddy 0.75.0 → 0.76.9

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/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as MAX_WORKSPACE_PORT, A as relativeTime, An as describeMovement, B as readIndexToken, Bn as allJobs, Ci as writeAgentPreference, Cn as MANAGED_ROOT, Di as agentById, Dn as readMarker, Ei as AGENT_IDS, F as runBatch, G as reviewsUrl, Kn as PHASES, Mn as findExistingReview, On as removeWorktree, Q as DEFAULT_WORKSPACE_PORT, R as queueForReview, Rn as forgeResolver, Si as readAgentPreference, Ti as STORE_ROOT, U as ensureServer, V as bootstrapUrl, Wn as loadJob, X as workspaceUrl, Xn as purposeOf, Y as stopServer, Yn as progressSteps, Zn as clearClaim, _ as liveUpdateFor, _r as summarise, _t as telemetryUploadConsent, a as wasBlocked, b as recordKissRun, bi as resolveModel, bt as PACKAGE_NAME, cr as loadWorkspace, ct as reanalyseReview, d as mergeUpdateInto, di as WITHHELD_REASON, dr as positionInLineage, dt as append, er as followedRefName, et as MIN_WORKSPACE_PORT, f as updateReview, fi as kissOutcome, g as UpdateAlreadyRunningError, gr as reviewedRepositories, gt as recordTelemetryUploadConsent, ht as deliver, i as liveJobsFor, it as writePortPreference, j as batchIdentity, jn as describeSubject, ki as detectAgents, lr as matchingWorkspaceIds, mr as recentWorkspaces, mt as stamp, n as deleteReview$1, ot as prepareReview, pi as resolveTarget, pt as record$1, rt as statedWorkspacePort, st as settleReview, tt as parseWorkspacePort, ur as mutateWorkspace, ut as startJob, v as runningJobs, vi as git, vt as telemetryUploadEnabled, wi as CONFIG_PATH, xn as modelHelpLines, yi as resolveAgent, yt as BUILD_VERSION } from "./delete_review-BWKGCqwI.js";
2
+ import { $n as PHASES, Ai as readAgentPreference, An as MANAGED_ROOT, B as ensureServer, Cr as reviewedRepositories, D as relativeTime, Di as resolveAgent, Ei as git, F as queueForReview, Fi as agentById, Fn as removeWorktree, Gn as forgeResolver, H as reviewsUrl, K as stopServer, L as readIndexToken, Li as detectAgents, Ln as describeMovement, M as runBatch, Mi as CONFIG_PATH, Ni as STORE_ROOT, O as batchIdentity, Oi as resolveModel, On as modelHelpLines, Pi as AGENT_IDS, Pn as readMarker, Q as parseWorkspacePort, R as bootstrapUrl, Rn as describeSubject, X as MAX_WORKSPACE_PORT, Y as DEFAULT_WORKSPACE_PORT, Z as MIN_WORKSPACE_PORT, Zn as loadJob, _ as recordKissRun, _r as mutateWorkspace, _t as PACKAGE_NAME, a as wasBlocked, at as reanalyseReview, bi as kissOutcome, ct as append, d as mergeUpdateInto, dt as stamp, et as statedWorkspacePort, f as updateReview, ft as deliver, g as runningJobs, gr as matchingWorkspaceIds, gt as BUILD_VERSION, h as liveUpdateFor, hr as loadWorkspace, ht as telemetryUploadEnabled, i as liveJobsFor, ir as clearClaim, it as settleReview, ji as writeAgentPreference, m as UpdateAlreadyRunningError, mt as telemetryUploadConsent, n as deleteReview$1, nr as progressSteps, pt as recordTelemetryUploadConsent, q as workspaceUrl, qn as allJobs, rr as purposeOf, rt as prepareReview, sr as followedRefName, st as startJob, tt as writePortPreference, ut as record$1, vr as positionInLineage, wr as summarise, xi as resolveTarget, xr as recentWorkspaces, yi as WITHHELD_REASON, zn as findExistingReview } from "./delete_review-BX3lu5Q7.js";
3
3
  import { basename, dirname, 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";
package/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as MAX_WORKSPACE_PORT, $n as decideKissRecommendation, $r as findingIn, $t as markReadForReview, A as relativeTime$1, Ai as AgentCancelledError, Ar as sectionOf, At as partFiles, B as readIndexToken, Bn as allJobs, Br as questionsOutstanding, Bt as isArchitectureId, C as track, Ci as writeAgentPreference, Cn as MANAGED_ROOT, Cr as activityLabel, Ct as mapRuntimes, D as endRun, Di as agentById, Dn as readMarker, Dr as lifecyclePill, Dt as ARCHITECTURE_DIR, E as completeRun, Ei as AGENT_IDS, En as isWorktreeHeld, Er as lifecycleItemLabel, Et as describeRuntimes, F as runBatch, Fn as followedBranch, Fr as readQueue, Ft as trackedFiles, Gn as saveJob, Gr as explanationOf, Gt as categoryOf, H as clearServerRecordOwnedBy, Hn as fail, Hr as processDiscussion, Ht as announceJob, I as activateQueued, In as reviewedShaOf, Ir as doneVerb, It as ownersOf, J as stopChild, Jn as phasesFor, Jr as ANALYSIS_ONLY, Jt as createPrUpdate, K as serverEntryExists, Kr as isExplanationAction, Kt as createArchitectureNotification, L as compactQueue, Ln as fetchHead, Lr as isIssueOutstanding, Lt as QUOTE_LIMITS, M as extractPullRequestLinks, Mn as findExistingReview, Mr as queueAddMany, Mt as repositoryKey, N as parsePullRequestLink, Nn as checkCodeFreshness, Nr as queueRemove, Nt as storeArchitecture, O as reportRun, Oi as agentFor$1, On as removeWorktree, Or as offersLifecycle, Ot as checkArchitecture, P as unsupportedReason, Pn as checkReview, Pr as queueStamp, Pt as storedScopes, Q as DEFAULT_WORKSPACE_PORT, Qn as isClaimed, Qr as findingAssessment, Qt as markRead, Rn as forgeResolver, Rr as isQuestionOutstanding, Rt as REPOSITORY_SCOPE, S as isDriving, Si as readAgentPreference, Sn as ReviewBeingDeletedError, Sr as activityAttributes, St as waitingHistory, T as beginRun, Ti as STORE_ROOT, Tn as holdWorktree, Tr as lifecycleChip, Tt as RUNTIME_WORDS, Un as isTerminal, Ur as recommendationsOf, Ut as branchOf, Vn as endedAt, Vr as reviewerDispositions, Vt as writeJsonAtomically, W as readServerRecord, Wn as loadJob, Wr as explanationActionOf, Wt as subjectOf, Xn as purposeOf, Xr as assessmentLookup, Xt as listNotifications, Yn as progressSteps, Yr as analysisRefOf, Yt as eventName, Z as writeServerRecord, Zr as currentGradeIn, Zt as markAllRead, _ as liveUpdateFor, _i as resolveBase, _n as CODE_BLOCK_CLASS, _t as telemetryUploadConsent, a as wasBlocked, ai as wasReassessed, an as retireNotification, ar as kissDecisionsOf, at as failureOf$1, bi as resolveModel, bn as renderInlineText, br as workspaceStamps, c as RefreshUnavailableError, ci as extractJsonText, cn as unreadCount$1, cr as loadWorkspace, ct as reanalyseReview, d as mergeUpdateInto, di as WITHHELD_REASON, dn as EXPLAIN_SIMPLY_PROMPT, ei as gradeScale, en as moveIds, er as followedRefName, et as MIN_WORKSPACE_PORT, f as updateReview, fi as kissOutcome, fn as UNESTABLISHED_MOTIVATION, fr as presentedKissRunOf, ft as readEvents, g as UpdateAlreadyRunningError, gi as isCommitReview, gn as renderMarkdown, gt as recordTelemetryUploadConsent, h as askCheckout, hi as DetachedHeadError, hn as GITHUB_MARKDOWN, hr as reviewedCommit, i as liveJobsFor, ii as sourcePhrase, in as removeNotification, ir as kissDecisionRefusal, it as writePortPreference, j as batchIdentity, ji as AgentUnavailableError, jr as queueAdd, jt as readArchitecture, k as stopRun, ki as detectAgents, kn as requestJobStop, kr as parseLifecycle, kt as loadArchitecture, l as cancelUpdate, li as objectWithAnyOf, ln as withUsageRecorded, lt as runJob, m as isUnchanged, mi as originRemote, mn as hasAuthorContent, mr as recentWorkspaces, n as deleteReview, ni as promptGrade, nn as readNotification, nr as indexKeptIds, nt as resolveWorkspacePort, o as discardJob, oi as followUpFindingId, on as toneOf, or as latestKissRun, ot as prepareReview, p as refreshPrContext, pi as resolveTarget, pn as authorPreview, pr as previousKissResult, pt as record, q as spawnServer, qn as isLiveJob, qr as withExplanation, qt as createPrState, r as lineageIds, ri as reassessIn, rn as readStateOf, rr as indexReviewGroups, rt as statedWorkspacePort, s as FETCH_TIMEOUT_MS, si as asString, sn as unreadAround, sr as lineagePosition, st as settleReview, t as deleteLineage, ti as isGradeFor, tn as pruneNotifications, tr as groupByLineage, tt as parseWorkspacePort, u as cancellableHere, ui as describeAuthorship, un as existingPaths, ur as mutateWorkspace, vi as git, vn as CODE_COPY_CLASS, vr as touchWorkspace, w as RUN_KINDS, wn as createWorktree, wr as isLifecycle, wt as recordRuntimeBestEffort, x as startKissJob, xi as clearAgentPreference, xr as LIFECYCLES, xt as feedbackUrl, y as agentEnvOf, yi as resolveAgent, yn as escapeHtml$1, yr as workspaceRevision, yt as BUILD_VERSION, z as settleQueue, zn as fillFileUrlTemplate, zr as issuesOutstanding, zt as ancestorsOf } from "./delete_review-BWKGCqwI.js";
1
+ import { $ as resolveWorkspacePort, $r as explanationOf, $t as readNotification, A as parsePullRequestLink, Ai as readAgentPreference, An as MANAGED_ROOT, Ar as activityLabel, At as storeArchitecture, Bn as checkCodeFreshness, Br as queueRemove, Bt as branchOf, C as completeRun, Ci as DetachedHeadError, Cn as renderMarkdown, Ct as describeRuntimes, D as relativeTime$1, Di as resolveAgent, Dn as renderInlineText, Dr as workspaceStamps, Dt as partFiles, E as stopRun, Ei as git, En as escapeHtml$1, Er as workspaceRevision, Et as loadArchitecture, Fi as agentById, Fn as removeWorktree, Fr as offersLifecycle, Ft as REPOSITORY_SCOPE, G as stopChild, Gn as forgeResolver, Gr as isQuestionOutstanding, Gt as createPrUpdate, Hn as followedBranch, Hr as readQueue, Ht as categoryOf, I as settleQueue, Ii as agentFor$1, In as requestJobStop, Ir as parseLifecycle, It as ancestorsOf, J as writeServerRecord, Jn as endedAt, Jr as resolvedByConversation, Jt as markAllRead, Kn as fillFileUrlTemplate, Kr as issuesOutstanding, Kt as eventName, L as readIndexToken, Li as detectAgents, Lr as sectionOf, Lt as isArchitectureId, M as runBatch, Mn as holdWorktree, Mr as lifecycleChip, Mt as trackedFiles, N as activateQueued, Ni as STORE_ROOT, Nn as isWorktreeHeld, Nr as lifecycleItemLabel, Nt as ownersOf, O as batchIdentity, Oi as resolveModel, Or as LIFECYCLES, Ot as readArchitecture, P as compactQueue, Pi as AGENT_IDS, Pn as readMarker, Pr as lifecyclePill, Pt as QUOTE_LIMITS, Q as parseWorkspacePort, Qn as saveJob, Qr as explanationActionOf, Qt as pruneNotifications, Ri as AgentCancelledError, Rr as queueAdd, Rt as writeJsonAtomically, S as beginRun, Si as originRemote, Sn as GITHUB_MARKDOWN, Sr as reviewedCommit, St as RUNTIME_WORDS, T as reportRun, Ti as resolveBase, Tn as CODE_COPY_CLASS, Tr as touchWorkspace, Tt as checkArchitecture, U as serverEntryExists, Un as reviewedShaOf, Ur as doneVerb, Ut as createArchitectureNotification, V as readServerRecord, Vn as checkReview, Vr as queueStamp, Vt as subjectOf, W as spawnServer, Wn as fetchHead, Wr as isIssueOutstanding, Wt as createPrState, X as MAX_WORKSPACE_PORT, Xn as isTerminal, Xr as processDiscussion, Xt as markReadForReview, Y as DEFAULT_WORKSPACE_PORT, Yn as fail, Yr as reviewerDispositions, Yt as markRead, Z as MIN_WORKSPACE_PORT, Zn as loadJob, Zr as recommendationsOf, Zt as moveIds, _i as objectWithAnyOf, _n as EXPLAIN_SIMPLY_PROMPT, _r as mutateWorkspace, a as wasBlocked, ai as currentGradeIn, an as unreadCount$1, ar as isClaimed, at as reanalyseReview, b as track, bi as kissOutcome, bn as authorPreview, br as previousKissResult, bt as mapRuntimes, c as RefreshUnavailableError, ci as gradeScale, cn as ASK_REREAD_CEILING_MS, cr as groupByLineage, d as mergeUpdateInto, di as reassessIn, dn as saveRefresh, dr as kissDecisionRefusal, ei as isExplanationAction, en as readStateOf, er as isLiveJob, et as statedWorkspacePort, f as updateReview, fi as sourcePhrase, fn as storedConversation, fr as kissDecisionsOf, gi as extractJsonText, gn as agentEnvOf, gt as BUILD_VERSION, h as liveUpdateFor, hi as asString, hn as isUnchanged, hr as loadWorkspace, i as liveJobsFor, ii as assessmentLookup, in as unreadAround, it as settleReview, j as unsupportedReason, ji as writeAgentPreference, jn as createWorktree, jr as isLifecycle, jt as storedScopes, k as extractPullRequestLinks, ki as clearAgentPreference, kn as ReviewBeingDeletedError, kr as activityAttributes, kt as repositoryKey, l as cancelUpdate, li as isGradeFor, ln as adoptLegacyBaseline, lr as indexKeptIds, lt as readEvents, m as UpdateAlreadyRunningError, mi as followUpFindingId, mn as refreshPrContext, mr as lineagePosition, mt as telemetryUploadConsent, n as deleteReview, ni as ANALYSIS_ONLY, nn as retireNotification, nr as progressSteps, nt as failureOf$1, o as discardJob, oi as findingAssessment, on as withUsageRecorded, or as decideKissRecommendation, ot as runJob, p as askCheckout, pi as wasReassessed, pn as unforeseenFailure, pr as latestKissRun, pt as recordTelemetryUploadConsent, qn as allJobs, qr as questionsOutstanding, qt as listNotifications, r as lineageIds, ri as analysisRefOf, rn as toneOf, rr as purposeOf, rt as prepareReview, s as FETCH_TIMEOUT_MS, si as findingIn, sn as existingPaths, sr as followedRefName, t as deleteLineage, ti as withExplanation, tn as removeNotification, tr as phasesFor, tt as writePortPreference, u as cancellableHere, ui as promptGrade, un as prepareConversationWithin, ur as indexReviewGroups, ut as record, v as startKissJob, vi as describeAuthorship, vn as chatConversationSection, vt as feedbackUrl, w as endRun, wi as isCommitReview, wn as CODE_BLOCK_CLASS, wt as ARCHITECTURE_DIR, x as RUN_KINDS, xi as resolveTarget, xn as hasAuthorContent, xr as recentWorkspaces, xt as recordRuntimeBestEffort, y as isDriving, yi as WITHHELD_REASON, yn as UNESTABLISHED_MOTIVATION, yr as presentedKissRunOf, yt as waitingHistory, z as clearServerRecordOwnedBy, zi as AgentUnavailableError, zn as findExistingReview, zr as queueAddMany, zt as announceJob } from "./delete_review-BX3lu5Q7.js";
2
2
  import { basename, isAbsolute, join, relative, resolve } from "node:path";
3
3
  import { execFile } from "node:child_process";
4
4
  import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -1210,13 +1210,43 @@ function importanceTip(importance) {
1210
1210
  * tell those apart from the badge — so the copy commits to the weaker of the two readings rather
1211
1211
  * than claiming a judgement that may not have been made.
1212
1212
  */
1213
- function findingStatusTip(status) {
1213
+ function findingStatusTip(status, against = "commits") {
1214
1214
  return {
1215
- resolved: "Resolved — the new commits appear to address this finding. Worth a glance to confirm before you close it out.",
1216
- "still-open": "Still open — the new commits do not address this finding. Findings the analysis did not comment on are shown this way too, so this never disappears on its own.",
1217
- affected: "Affected — the code this finding concerns has changed, but not in a way that settles it. Re-read this one.",
1218
- new: "New — introduced by the changes pushed since your previous analysis."
1219
- }[status];
1215
+ commits: {
1216
+ resolved: "Resolved — the new commits appear to address this finding. Worth a glance to confirm before you close it out.",
1217
+ "still-open": "Still open — the new commits do not address this finding. Findings the analysis did not comment on are shown this way too, so this never disappears on its own.",
1218
+ affected: "Affected — the code this finding concerns has changed, but not in a way that settles it. Re-read this one.",
1219
+ new: "New — introduced by the changes pushed since your previous analysis."
1220
+ },
1221
+ conversation: {
1222
+ resolved: "Resolved — a comment on the pull request shows this finding no longer applies. Worth a glance to confirm before you close it out.",
1223
+ "still-open": "Still open — nothing said on the pull request settles this finding. Findings the analysis did not comment on are shown this way too, so this never disappears on its own.",
1224
+ affected: "Affected — something said on the pull request bears on this finding without settling it. Re-read this one.",
1225
+ new: "New — raised in the pull request conversation since your previous analysis, and confirmed in the code."
1226
+ },
1227
+ "commits-and-conversation": {
1228
+ resolved: "Resolved — the new commits or the pull request conversation appear to settle this finding. Worth a glance to confirm before you close it out.",
1229
+ "still-open": "Still open — neither the new commits nor the pull request conversation settle this finding. Findings the analysis did not comment on are shown this way too, so this never disappears on its own.",
1230
+ affected: "Affected — the new commits or the pull request conversation bear on this finding without settling it. Re-read this one.",
1231
+ new: "New — found in the commits or the conversation since your previous analysis."
1232
+ }
1233
+ }[against][status];
1234
+ }
1235
+ /** What an update's note on a finding came after, as Copy heads it. */
1236
+ function reconciledAfter(against = "commits") {
1237
+ return {
1238
+ commits: "After the new commits",
1239
+ conversation: "After the pull request conversation",
1240
+ "commits-and-conversation": "After the new commits and comments"
1241
+ }[against];
1242
+ }
1243
+ /** Where an update's answer to a question came from, as its heading. */
1244
+ function reconciledAnswerHeading(against = "commits") {
1245
+ return {
1246
+ commits: "Answered by the new changes",
1247
+ conversation: "Answered by the pull request conversation",
1248
+ "commits-and-conversation": "Answered by the new changes or comments"
1249
+ }[against];
1220
1250
  }
1221
1251
  /** The reviewer-facing label for a status. Kept beside the tooltips so the two cannot drift. */
1222
1252
  function findingStatusLabel(status) {
@@ -1234,12 +1264,24 @@ function findingStatusLabel(status) {
1234
1264
  * for a question the reassessment never mentioned. The copy commits to the weaker reading, because
1235
1265
  * the reviewer must not read silence as an answer.
1236
1266
  */
1237
- function questionStatusTip(status) {
1267
+ function questionStatusTip(status, against = "commits") {
1238
1268
  return {
1239
- answered: "Answered — the new commits settle this, and the answer is below. Worth a glance to confirm before you drop it.",
1240
- "still-open": "Still open — the new commits do not settle this. Questions the reassessment did not comment on are shown this way too, so this never disappears on its own.",
1241
- affected: "Affected — the code this question is about has changed without answering it. Read the question again before you ask it."
1242
- }[status];
1269
+ commits: {
1270
+ answered: "Answered — the new commits settle this, and the answer is below. Worth a glance to confirm before you drop it.",
1271
+ "still-open": "Still open — the new commits do not settle this. Questions the reassessment did not comment on are shown this way too, so this never disappears on its own.",
1272
+ affected: "Affected — the code this question is about has changed without answering it. Read the question again before you ask it."
1273
+ },
1274
+ conversation: {
1275
+ answered: "Answered — the pull request conversation settles this, and the answer is below. Worth a glance to confirm before you drop it.",
1276
+ "still-open": "Still open — nothing said on the pull request settles this. Questions the reassessment did not comment on are shown this way too, so this never disappears on its own.",
1277
+ affected: "Affected — something said on the pull request bears on this question without answering it. Read the question again before you ask it."
1278
+ },
1279
+ "commits-and-conversation": {
1280
+ answered: "Answered — the new commits or the pull request conversation settle this, and the answer is below. Worth a glance to confirm before you drop it.",
1281
+ "still-open": "Still open — neither the new commits nor the pull request conversation settle this. Questions the reassessment did not comment on are shown this way too, so this never disappears on its own.",
1282
+ affected: "Affected — the new commits or the pull request conversation bear on this question without answering it. Read the question again before you ask it."
1283
+ }
1284
+ }[against][status];
1243
1285
  }
1244
1286
  /** The reviewer-facing label for a question status. Kept beside the tooltip so the two cannot drift. */
1245
1287
  function questionStatusLabel(status) {
@@ -1301,6 +1343,102 @@ function pillAttrs(classNames, tip) {
1301
1343
  return ` class="${escapeHtml$1(classNames)} pr-helper-tip" data-tip="${escapeHtml$1(tip)}"`;
1302
1344
  }
1303
1345
  //#endregion
1346
+ //#region ../../packages/review-harness/src/domain/utils/relative_time.ts
1347
+ function formatRelativeTime(ts, now = Date.now()) {
1348
+ const diffMs = Math.max(0, now - ts);
1349
+ const minute = 6e4;
1350
+ const hour = 60 * minute;
1351
+ const day = 24 * hour;
1352
+ if (diffMs < minute) return "Just now";
1353
+ if (diffMs < hour) {
1354
+ const mins = Math.floor(diffMs / minute);
1355
+ return `${mins} min${mins === 1 ? "" : "s"} ago`;
1356
+ }
1357
+ if (diffMs < day) {
1358
+ const hours = Math.floor(diffMs / hour);
1359
+ return `${hours} hour${hours === 1 ? "" : "s"} ago`;
1360
+ }
1361
+ const days = Math.floor(diffMs / day);
1362
+ return `${days} day${days === 1 ? "" : "s"} ago`;
1363
+ }
1364
+ //#endregion
1365
+ //#region ../../packages/review-harness/src/domain/ui/components/status_history.ts
1366
+ /**
1367
+ * What a step did, by where it came from. A settled finding's three outcomes are kept apart
1368
+ * because they are different statements: reopened says the finding is valid again, marked for
1369
+ * reassessment says nobody could tell, and still resolved says the resolution survived.
1370
+ */
1371
+ function statusStepHeadline(from, step) {
1372
+ if (step.carried) return `${findingStatusLabel(step.status)}, before history was kept`;
1373
+ if (from === "resolved") {
1374
+ if (step.status === "still-open") return "Reopened";
1375
+ if (step.status === "affected") return "Marked for reassessment";
1376
+ if (step.status === "resolved") return "Still resolved";
1377
+ }
1378
+ return findingStatusLabel(step.status);
1379
+ }
1380
+ /** Who or what decided a step. */
1381
+ function statusBasisPhrase(step) {
1382
+ switch (step.basis) {
1383
+ case "code": return step.commitSha ? `by the new commits, at ${step.commitSha.slice(0, 7)}` : "by the new commits";
1384
+ case "conversation": return "by the pull request conversation";
1385
+ case "manual": return "by you";
1386
+ default: return "";
1387
+ }
1388
+ }
1389
+ /** Why a settled finding was looked at again, when that is why. */
1390
+ function statusTriggerSentence(step) {
1391
+ if (step.trigger === "evidence-edited") return "The comment it was resolved by was edited.";
1392
+ if (step.trigger === "evidence-deleted") return "The comment it was resolved by was deleted.";
1393
+ return "";
1394
+ }
1395
+ /** Whose words these are, and what they were. */
1396
+ function evidenceAttribution(evidence) {
1397
+ const what = evidence.kind === "review" ? "review" : "comment";
1398
+ if (evidence.deleted) return `@${evidence.author}'s ${what}, since deleted`;
1399
+ return `@${evidence.author}'s ${what}, as it read then`;
1400
+ }
1401
+ /** The steps paired with the status each moved from: the previous step's, or none for the first. */
1402
+ function statusSteps(history) {
1403
+ return history.map((step, index) => ({
1404
+ from: index > 0 ? history[index - 1].status : void 0,
1405
+ step
1406
+ }));
1407
+ }
1408
+ /** Whether there is a history worth showing: at least one change recorded, not only a carried start. */
1409
+ function hasStatusHistory(history) {
1410
+ return !!history?.some((step) => !step.carried);
1411
+ }
1412
+ function renderEvidence$2(evidence, now) {
1413
+ if (!evidence) return "";
1414
+ const edited = evidence.editedAt && Number.isFinite(Date.parse(evidence.editedAt)) ? ` · edited ${escapeHtml$1(formatRelativeTime(Date.parse(evidence.editedAt), now))}` : "";
1415
+ const body = evidence.body ? `<p class="pr-helper-status-evidence-body">${renderInlineText(evidence.body)}</p>` : "";
1416
+ const link = evidence.url && /^https?:\/\//i.test(evidence.url) ? `<a class="pr-helper-status-evidence-link" href="${escapeHtml$1(evidence.url)}" target="_blank" rel="noopener noreferrer">Open the ${evidence.kind === "review" ? "review" : "comment"}</a>` : "";
1417
+ return `<blockquote class="pr-helper-status-evidence" data-status-evidence>
1418
+ <p class="pr-helper-status-evidence-who">${escapeHtml$1(evidenceAttribution(evidence))}${edited}</p>
1419
+ ${body}${link}
1420
+ </blockquote>`;
1421
+ }
1422
+ function renderStep$1(from, step, now) {
1423
+ const source = [statusBasisPhrase(step), step.at !== void 0 ? formatRelativeTime(step.at, now) : ""].filter(Boolean).join(" · ");
1424
+ const trigger = statusTriggerSentence(step);
1425
+ return `<li class="pr-helper-reassessment-step" data-status-step="${escapeHtml$1(step.status)}">
1426
+ <span class="pr-helper-reassessment-step-move pr-helper-status-step-headline">${escapeHtml$1(statusStepHeadline(from, step))}</span>
1427
+ ${source ? `<span class="pr-helper-reassessment-step-source">${escapeHtml$1(source)}</span>` : ""}
1428
+ ${trigger ? `<p class="pr-helper-reassessment-reason">${escapeHtml$1(trigger)}</p>` : ""}
1429
+ ${step.reason ? `<p class="pr-helper-reassessment-reason">${renderInlineText(step.reason)}</p>` : ""}
1430
+ ${renderEvidence$2(step.evidence, now)}
1431
+ </li>`;
1432
+ }
1433
+ /** The disclosure, closed. Nothing when the status has never changed. */
1434
+ function renderStatusHistory(history, now = Date.now()) {
1435
+ if (!hasStatusHistory(history)) return "";
1436
+ return `<details class="pr-helper-reassessment-history pr-helper-status-history" data-status-history>
1437
+ <summary class="pr-helper-reassessment-history-summary">Status history</summary>
1438
+ <ol class="pr-helper-reassessment-steps">${statusSteps(history).map(({ from, step }) => renderStep$1(from, step, now)).join("")}</ol>
1439
+ </details>`;
1440
+ }
1441
+ //#endregion
1304
1442
  //#region ../../packages/review-harness/src/domain/ui/components/finding_copy.ts
1305
1443
  /**
1306
1444
  * An issue's Copy: its title in bold, the description, the update, the investigation, how its grade
@@ -1325,8 +1463,12 @@ function issueText(issue, investigation, assessment) {
1325
1463
  return join$1([
1326
1464
  bold(issue.title),
1327
1465
  issue.description,
1328
- update(issue.reconciliationStatus && findingStatusLabel(issue.reconciliationStatus), issue.reconciliationNote),
1329
- ...investigation ? [investigated(issue.investigationImpact, issue.investigationNote), reassessed(assessment)] : [],
1466
+ update(issue.reconciliationStatus && findingStatusLabel(issue.reconciliationStatus), issue.reconciliationNote, issue.reconciledAgainst),
1467
+ ...investigation ? [
1468
+ investigated(issue.investigationImpact, issue.investigationNote),
1469
+ reassessed(assessment),
1470
+ statusHistory(issue.statusHistory)
1471
+ ] : [],
1330
1472
  where(issue.file, issue.line, issue.relatedFiles)
1331
1473
  ]);
1332
1474
  }
@@ -1334,8 +1476,8 @@ function questionText(question, investigation, assessment) {
1334
1476
  return join$1([
1335
1477
  bold(question.question),
1336
1478
  question.whyThisMatters,
1337
- labelled("Answered by the new changes", question.reconciliationAnswer),
1338
- update(question.reconciliationStatus && questionStatusLabel(question.reconciliationStatus), question.reconciliationNote),
1479
+ labelled(reconciledAnswerHeading(question.reconciledAgainst), question.reconciliationAnswer),
1480
+ update(question.reconciliationStatus && questionStatusLabel(question.reconciliationStatus), question.reconciliationNote, question.reconciledAgainst),
1339
1481
  ...investigation ? [
1340
1482
  labelled("Answer from investigation", question.investigationAnswer),
1341
1483
  investigated(question.investigationImpact, question.investigationNote),
@@ -1345,9 +1487,9 @@ function questionText(question, investigation, assessment) {
1345
1487
  ]);
1346
1488
  }
1347
1489
  /** What the last update said about the finding, under the status the card shows beside it. */
1348
- function update(status, note) {
1490
+ function update(status, note, against) {
1349
1491
  if (!note?.trim()) return void 0;
1350
- return `**After the new commits${status ? ` (${status.toLowerCase()})` : ""}:** ${note.trim()}`;
1492
+ return `**${reconciledAfter(against)}${status ? ` (${status.toLowerCase()})` : ""}:** ${note.trim()}`;
1351
1493
  }
1352
1494
  /**
1353
1495
  * What an investigation concluded, under the verdict the card's badge shows. "Investigated" is the
@@ -1367,6 +1509,24 @@ function reassessed(assessment) {
1367
1509
  const move = `${assessment.original ?? "ungraded"} → ${assessment.current}, ${sourcePhrase(last.source)}`;
1368
1510
  return last.reason?.trim() ? `**Reassessed (${move}):** ${last.reason.trim()}` : `**Reassessed:** ${move}.`;
1369
1511
  }
1512
+ /**
1513
+ * Every change of status, each with what it rested on and the evidence as it read then: the record
1514
+ * of why the review reached its conclusion, which a link to a since-edited comment could not keep.
1515
+ * Copy only, like the reassessed grade; a feedback draft is not the place for the review's working.
1516
+ */
1517
+ function statusHistory(history) {
1518
+ if (!hasStatusHistory(history)) return void 0;
1519
+ return `**Status history:**\n${statusSteps(history).map(({ from, step }) => {
1520
+ const basis = statusBasisPhrase(step);
1521
+ const said = [statusTriggerSentence(step), step.reason?.trim()].filter(Boolean).join(" ");
1522
+ const head = `- ${statusStepHeadline(from, step)}${basis ? ` ${basis}` : ""}${said ? `: ${said}` : ""}`;
1523
+ const evidence = step.evidence;
1524
+ if (!evidence) return head;
1525
+ const quoted = evidence.body ? `: ${evidence.body.replace(/\s+/g, " ")}` : "";
1526
+ const link = evidence.url ? ` (${evidence.url})` : "";
1527
+ return `${head}\n > ${evidenceAttribution(evidence)}${quoted}${link}`;
1528
+ }).join("\n")}`;
1529
+ }
1370
1530
  function labelled(label, text) {
1371
1531
  return text?.trim() ? `**${label}:** ${text.trim()}` : void 0;
1372
1532
  }
@@ -1409,25 +1569,6 @@ function shortSha$1(sha) {
1409
1569
  return sha.slice(0, MIN_ABBREVIATED_LENGTH);
1410
1570
  }
1411
1571
  //#endregion
1412
- //#region ../../packages/review-harness/src/domain/utils/relative_time.ts
1413
- function formatRelativeTime(ts, now = Date.now()) {
1414
- const diffMs = Math.max(0, now - ts);
1415
- const minute = 6e4;
1416
- const hour = 60 * minute;
1417
- const day = 24 * hour;
1418
- if (diffMs < minute) return "Just now";
1419
- if (diffMs < hour) {
1420
- const mins = Math.floor(diffMs / minute);
1421
- return `${mins} min${mins === 1 ? "" : "s"} ago`;
1422
- }
1423
- if (diffMs < day) {
1424
- const hours = Math.floor(diffMs / hour);
1425
- return `${hours} hour${hours === 1 ? "" : "s"} ago`;
1426
- }
1427
- const days = Math.floor(diffMs / day);
1428
- return `${days} day${days === 1 ? "" : "s"} ago`;
1429
- }
1430
- //#endregion
1431
1572
  //#region ../../packages/review-harness/src/domain/ui/components/intent_note.ts
1432
1573
  /**
1433
1574
  * The Overview's unclear motive, kept in step with its Intent question.
@@ -2613,9 +2754,9 @@ function renderIssueConfidence(confidence) {
2613
2754
  * is the axis the reviewer is scanning for. They already know what the findings were; what they
2614
2755
  * came back to learn is which ones the author dealt with.
2615
2756
  */
2616
- function renderFindingStatus(status) {
2757
+ function renderFindingStatus(status, against) {
2617
2758
  if (!status) return "";
2618
- return `<span${pillAttrs(`pr-helper-finding-status pr-helper-finding-status-${status}`, findingStatusTip(status))}>${escapeHtml$1(findingStatusLabel(status))}</span>`;
2759
+ return `<span${pillAttrs(`pr-helper-finding-status pr-helper-finding-status-${status}`, findingStatusTip(status, against))}>${escapeHtml$1(findingStatusLabel(status))}</span>`;
2619
2760
  }
2620
2761
  /**
2621
2762
  * The same pill, for a question.
@@ -2624,9 +2765,9 @@ function renderFindingStatus(status) {
2624
2765
  * a finding is `resolved` and a question is `answered`, which are different events. Nothing fixed
2625
2766
  * the question; somebody now knows the answer.
2626
2767
  */
2627
- function renderQuestionStatus(status) {
2768
+ function renderQuestionStatus(status, against) {
2628
2769
  if (!status) return "";
2629
- return `<span${pillAttrs(`pr-helper-finding-status pr-helper-finding-status-${status === "answered" ? "resolved" : status}`, questionStatusTip(status))}>${escapeHtml$1(questionStatusLabel(status))}</span>`;
2770
+ return `<span${pillAttrs(`pr-helper-finding-status pr-helper-finding-status-${status === "answered" ? "resolved" : status}`, questionStatusTip(status, against))}>${escapeHtml$1(questionStatusLabel(status))}</span>`;
2630
2771
  }
2631
2772
  /** The model's one-line reason for a status, when it gave one. */
2632
2773
  function renderReconciliationNote(note) {
@@ -2771,7 +2912,7 @@ function renderIssuesTab(session, investigatingId, options) {
2771
2912
  ${options?.askBuddy ? renderAskBuddyMenu("issue") : ""}
2772
2913
  </div>
2773
2914
  <div class="pr-helper-issue-badges">
2774
- ${renderFindingStatus(issue.reconciliationStatus)}
2915
+ ${renderFindingStatus(issue.reconciliationStatus, issue.reconciledAgainst)}
2775
2916
  ${renderDismissedBadge(isDismissed)}
2776
2917
  ${renderInvestigationImpact(issue.investigationImpact)}
2777
2918
  ${renderSectionTag(issue.sectionId, sections)}
@@ -2783,6 +2924,7 @@ function renderIssuesTab(session, investigatingId, options) {
2783
2924
  ${renderAssessmentRegion("issue", assessment, grading)}
2784
2925
  <p class="pr-helper-issue-desc">${renderInlineText(issue.description)}</p>${renderCardExplanation(explanationOf(session, "issue", issue.id), options)}
2785
2926
  ${renderReconciliationNote(issue.reconciliationNote)}
2927
+ ${renderStatusHistory(issue.statusHistory, now)}
2786
2928
  ${renderInvestigationNote(issue.investigationNote)}
2787
2929
  ${renderExistingDiscussion(issue.existingDiscussion)}
2788
2930
  ${renderFileLocation(issue.file, "pr-helper-issue-loc", issue.line)}
@@ -2811,7 +2953,12 @@ function renderIssuesTab(session, investigatingId, options) {
2811
2953
  */
2812
2954
  function closedFindingsSummary(counts) {
2813
2955
  const byCommits = counts.done - counts.reviewerClosed;
2814
- if (counts.reviewerClosed === 0) return `Resolved by the new commits (${counts.done})`;
2956
+ if (counts.reviewerClosed === 0) {
2957
+ const byConversation = counts.byConversation ?? 0;
2958
+ if (byConversation === 0) return `Resolved by the new commits (${counts.done})`;
2959
+ if (byConversation === counts.done) return `Resolved by the conversation (${counts.done})`;
2960
+ return `Resolved (${counts.done})`;
2961
+ }
2815
2962
  if (byCommits === 0) return `Dismissed by you (${counts.done})`;
2816
2963
  return `Dealt with (${counts.done})`;
2817
2964
  }
@@ -2854,7 +3001,8 @@ function renderStaleDismissal(stale) {
2854
3001
  function renderDismissToggle(issue, isDismissed, stale, dismissal) {
2855
3002
  const label = stale ? "Dismiss again" : isDismissed ? "Reopen" : "Dismiss";
2856
3003
  const settled = issue.reconciliationStatus === "resolved";
2857
- return `<button type="button" class="pr-helper-issue-dismiss-toggle" data-issue-dismissed="${escapeHtml$1(issue.id)}" data-dismissed="${isDismissed}"${stale ? " data-stale=\"true\"" : ""}${dismissal ? ` data-dismissed-in="${escapeHtml$1(dismissal.sessionId)}"` : ""}${settled ? " data-settled=\"true\"" : ""}>${label}</button>`;
3004
+ const settledBy = settled ? ` data-settled-by="${resolvedByConversation(issue) ? "conversation" : "code"}"` : "";
3005
+ return `<button type="button" class="pr-helper-issue-dismiss-toggle" data-issue-dismissed="${escapeHtml$1(issue.id)}" data-dismissed="${isDismissed}"${stale ? " data-stale=\"true\"" : ""}${dismissal ? ` data-dismissed-in="${escapeHtml$1(dismissal.sessionId)}"` : ""}${settled ? ` data-settled="true"${settledBy}` : ""}>${label}</button>`;
2858
3006
  }
2859
3007
  /**
2860
3008
  * The group an Intent question lives in, keyed like a theme's so the workspace client finds its
@@ -2873,7 +3021,7 @@ function renderQuestionCard(q, sections, answered, records, now = Date.now(), in
2873
3021
  const settled = q.reconciliationStatus === "answered";
2874
3022
  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\"" : ""}>${answered ? "Mark as unanswered" : "Mark answered"}</button>`;
2875
3023
  const investigationAnswer = q.investigationAnswer?.trim() ? `<h5>Answer from investigation</h5><p class="pr-helper-issue-desc pr-helper-investigation-answer">${renderInlineText(q.investigationAnswer)}</p>` : "";
2876
- const reconciliationAnswer = q.reconciliationAnswer?.trim() ? `<h5>Answered by the new changes</h5><p class="pr-helper-issue-desc pr-helper-investigation-answer">${renderInlineText(q.reconciliationAnswer)}</p>` : "";
3024
+ 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>` : "";
2877
3025
  return `
2878
3026
  <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))}"` : ""}>
2879
3027
  <div class="pr-helper-issue-header">
@@ -2884,7 +3032,7 @@ function renderQuestionCard(q, sections, answered, records, now = Date.now(), in
2884
3032
  ${options?.askBuddy ? renderAskBuddyMenu("question") : ""}
2885
3033
  </div>
2886
3034
  <div class="pr-helper-issue-badges">
2887
- ${renderQuestionStatus(q.reconciliationStatus)}
3035
+ ${renderQuestionStatus(q.reconciliationStatus, q.reconciledAgainst)}
2888
3036
  ${renderInvestigationImpact(q.investigationImpact)}
2889
3037
  <!-- Rendered on every card, hidden by CSS inside a theme group, because the heading
2890
3038
  above it already says the same word. The client moves cards between the group and
@@ -5217,14 +5365,19 @@ function updateFacts(outcome) {
5217
5365
  const counts = outcome.counts;
5218
5366
  if (!counts || !outcome.toSha) return null;
5219
5367
  const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
5368
+ const byConversation = counts.resolvedByConversation ?? 0;
5369
+ const fixed = counts.resolved - byConversation;
5220
5370
  const changes = [
5221
- counts.resolved ? `${plural(counts.resolved, "issue", "issues")} fixed` : "",
5371
+ fixed ? `${plural(fixed, "issue", "issues")} fixed` : "",
5372
+ byConversation ? `${plural(byConversation, "issue", "issues")} resolved by the conversation` : "",
5373
+ counts.reopened ? `${plural(counts.reopened, "issue", "issues")} reopened` : "",
5222
5374
  counts.newIssues ? plural(counts.newIssues, "new issue", "new issues") : "",
5223
5375
  counts.affected ? `${counts.affected} to look at again` : "",
5224
5376
  counts.questionsAnswered ? `${plural(counts.questionsAnswered, "question", "questions")} answered` : ""
5225
5377
  ].filter(Boolean);
5226
5378
  const grades = counts.gradeChanges ? [`${plural(counts.gradeChanges, "grade change", "grade changes")} suggested`] : [];
5227
- return [`<code>${escapeHtml$1(outcome.toSha.slice(0, 7))}</code>`, ...[...changes.length ? changes : ["No change to the findings"], ...grades].map(escapeHtml$1)];
5379
+ const basis = outcome.reassessed;
5380
+ return [...basis && !basis.code ? [escapeHtml$1("Reassessed against the conversation")] : [`<code>${escapeHtml$1(outcome.toSha.slice(0, 7))}</code>`, ...basis?.discussion ? [escapeHtml$1("with the conversation")] : []], ...[...changes.length ? changes : ["No change to the findings"], ...grades].map(escapeHtml$1)];
5228
5381
  }
5229
5382
  /**
5230
5383
  * The two things that can go quietly half-wrong, said out loud.
@@ -11951,7 +12104,7 @@ A developer is reviewing a specific code change and is looking at a review guide
11951
12104
 
11952
12105
  Answer only in relation to this change and the repository it lives in. You have the working tree, so open the files, trace callers and read history wherever that settles the question. Do not speculate about code you could have read.
11953
12106
 
11954
- If a question cannot be answered from this change and this repository, say so plainly and say what would answer it. Do not pad the answer out with general advice about the topic; a developer mid-review wants the specific answer or an honest "that isn't determinable from here".
12107
+ If a question cannot be answered from this change, this repository and the pull request context below, say so plainly and say what information is missing. Do not suggest shell commands, API calls or other ways to retrieve forge data yourself. Do not pad the answer out with general advice about the topic; a developer mid-review wants the specific answer or an honest "that isn't determinable from here".
11955
12108
 
11956
12109
  Write in short paragraphs and keep it to what was asked. Markdown renders, so use it where it carries meaning: backticks for identifiers and paths, a fenced block for a snippet worth reading as code rather than described in a sentence, a bullet list where the answer genuinely is a list. Prose is still the default. No headings, and do not break a paragraph into bullets to look organised.`;
11957
12110
  var AskUnavailableError = class extends Error {};
@@ -12058,6 +12211,8 @@ function buildPrompt$2(workspace, question, sectionId, cwd, context = {}) {
12058
12211
  "---",
12059
12212
  "DIFF:",
12060
12213
  workspace.diff,
12214
+ "---",
12215
+ chatConversationSection(context.conversation ?? storedConversation(workspace)),
12061
12216
  ...reading ? ["---", reading] : [],
12062
12217
  ...recent.length > 0 ? [
12063
12218
  "---",
@@ -14320,25 +14475,15 @@ async function kissFreshnessOrNull(workspace) {
14320
14475
  */
14321
14476
  async function handleRefreshPr(res, workspace) {
14322
14477
  const reattributing = JSON.stringify(workspace.session.result);
14478
+ adoptLegacyBaseline(workspace);
14479
+ const startedAt = Date.now();
14323
14480
  let outcome;
14324
14481
  try {
14325
14482
  outcome = await refreshPrContext(workspace);
14326
14483
  } catch {
14327
- outcome = {
14328
- at: Date.now(),
14329
- ok: false,
14330
- message: "The pull request could not be re-read. Nothing here has changed."
14331
- };
14484
+ outcome = unforeseenFailure(startedAt);
14332
14485
  }
14333
- workspace.lastPrRefresh = outcome;
14334
- mutateWorkspace(workspace.id, (current) => {
14335
- current.prContext = workspace.prContext;
14336
- current.lastPrRefresh = workspace.lastPrRefresh;
14337
- if (JSON.stringify(current.session.result) === reattributing) current.session = {
14338
- ...current.session,
14339
- result: workspace.session.result
14340
- };
14341
- });
14486
+ saveRefresh(workspace, outcome, reattributing);
14342
14487
  record({
14343
14488
  event: "pr_conversation_rechecked",
14344
14489
  reviewId: workspace.id,
@@ -14557,10 +14702,16 @@ async function handleAsk(req, res, workspace) {
14557
14702
  try {
14558
14703
  let answer;
14559
14704
  try {
14705
+ const reattributing = JSON.stringify(workspace.session.result);
14706
+ const { prepared, refresh } = await prepareConversationWithin(workspace, ASK_REREAD_CEILING_MS);
14707
+ if (refresh) try {
14708
+ saveRefresh(workspace, refresh, reattributing);
14709
+ } catch {}
14560
14710
  answer = await askAboutChange(workspace, question, sectionId, run.signal, {
14561
14711
  ...explains ? { explaining: explains } : {},
14562
14712
  ...continuing ? { continuing } : {},
14563
- ...reassessed ? { reassessing: reassessed } : {}
14713
+ ...reassessed ? { reassessing: reassessed } : {},
14714
+ conversation: prepared
14564
14715
  });
14565
14716
  } catch (error) {
14566
14717
  failed();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prreviewbuddy",
3
- "version": "0.75.0",
3
+ "version": "0.76.9",
4
4
  "description": "Understand code changes before you approve them. Local branch and pull request reviews with Claude Code, Codex or Gemini CLI.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://prreviewbuddy.com",
@@ -5468,6 +5468,19 @@
5468
5468
  .pr-helper-reassessment-evidence .pr-helper-issue-loc { margin-top: 2px; }
5469
5469
  /* Muted, not alarming: an unverified suggestion is still a reasoned one, and can still be accepted. */
5470
5470
  .pr-helper-reassessment-unverified { margin: 2px 0 0; font-size: 11px; font-style: italic; color: #9a6700; }
5471
+ /* A finding's status history reuses the grade history's disclosure and steps. The quote is the
5472
+ evidence as it read when the decision was made, which may no longer be what the thread says. */
5473
+ .pr-helper-status-step-headline { font-weight: 600; }
5474
+ .pr-helper-status-evidence {
5475
+ margin: 4px 0 0;
5476
+ padding: 2px 0 2px 8px;
5477
+ border-left: 2px solid rgba(15, 23, 42, 0.18);
5478
+ font-size: 11px;
5479
+ color: var(--color-fg-muted, #57606a);
5480
+ }
5481
+ .pr-helper-status-evidence-who { margin: 0; font-style: italic; }
5482
+ .pr-helper-status-evidence-body { margin: 2px 0 0; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
5483
+ .pr-helper-status-evidence-link { display: inline-block; margin-top: 2px; color: #0969da; }
5471
5484
  /* A proposal is a question put to the reviewer, so it gets the purple rule an investigation note
5472
5485
  has, and its three answers in a row beneath it. */
5473
5486
  .pr-helper-reassessment-proposal {