prreviewbuddy 0.71.2 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/{delete_review-BgIZpcsP.js → delete_review-CqnTq6yF.js} +55 -11
- package/dist/main.js +1 -1
- package/dist/server.js +85 -42
- package/package.json +1 -1
- package/static/workspace.js +81 -81
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.73.0
|
|
4
|
+
|
|
5
|
+
**KISS runs now count as reviews in usage telemetry.** With telemetry on, a KISS run sends the same
|
|
6
|
+
two events a standard review does, `review_started` and `review_finished`, with the same agent,
|
|
7
|
+
outcome and duration. Until now it sent neither, so somebody who only ran KISS looked as if they
|
|
8
|
+
had installed PR Review Buddy and never used it. Both review events now carry one more field,
|
|
9
|
+
`reviewType`, which is one of two words, `standard` or `kiss`: the kind of analysis, and nothing
|
|
10
|
+
about your code, your repository or what the analysis said. It is the only field added. Updating a
|
|
11
|
+
review with new commits still sends nothing. `prreviewbuddy config set telemetry-upload off` and
|
|
12
|
+
`PRB_TELEMETRY=off` stop these exactly as before.
|
|
13
|
+
|
|
14
|
+
## 0.72.1
|
|
15
|
+
|
|
16
|
+
**Local usage logs count themes on the first read.** The `workspace_opened` log entry waited for
|
|
17
|
+
the review to be marked Reviewed rather than for the analysis to finish, so it rarely fired before
|
|
18
|
+
a reading session ended and the session summary recorded zero themes. It now waits only for the
|
|
19
|
+
analysis. Nothing about what is sent changes: neither event leaves the machine.
|
|
20
|
+
|
|
21
|
+
## 0.72.0
|
|
22
|
+
|
|
23
|
+
**Both explanations stay on the card.** Ask Buddy on an issue or question now offers
|
|
24
|
+
**Explain (technical)** and **Explain (simple)**, and either one keeps its answer under the finding,
|
|
25
|
+
labelled **Explanation · Technical** or **Explanation · Simple**. A card holds one explanation: run
|
|
26
|
+
the other kind and it replaces the one there, while both answers stay in Buddy AI. If a new
|
|
27
|
+
explanation fails or is stopped, the card keeps the one it had. The same two names appear wherever
|
|
28
|
+
Ask Buddy offers them, including on a selection.
|
|
29
|
+
|
|
30
|
+
**Ask more follows up on the explanation you were reading.** If another tab replaces a card's
|
|
31
|
+
explanation after you press Ask more, your question still goes with the one you were reading.
|
|
32
|
+
|
|
33
|
+
**Simple explanations kept by 0.71 carry over.** They appear on their cards as
|
|
34
|
+
Explanation · Simple, and Ask more and updates use them as before.
|
|
35
|
+
|
|
3
36
|
## 0.71.2
|
|
4
37
|
|
|
5
38
|
**Buddy AI's opening line reads plainly.** Before your first question, Buddy AI now says "Answers
|
|
@@ -3277,12 +3277,44 @@ function describeAuthorship(changeSet, pullRequest) {
|
|
|
3277
3277
|
}
|
|
3278
3278
|
//#endregion
|
|
3279
3279
|
//#region ../../packages/review-harness/src/domain/ai_review/review_session.ts
|
|
3280
|
+
/**
|
|
3281
|
+
* The Ask Buddy actions whose answer is kept on the finding's card: `explain-detail` is
|
|
3282
|
+
* Explain (technical), `explain` is Explain (simple). The ids are the shared action ids in
|
|
3283
|
+
* selection_prompts.ts, which predate the labels.
|
|
3284
|
+
*/
|
|
3285
|
+
var EXPLANATION_ACTIONS = ["explain-detail", "explain"];
|
|
3286
|
+
function isExplanationAction(value) {
|
|
3287
|
+
return EXPLANATION_ACTIONS.includes(value);
|
|
3288
|
+
}
|
|
3289
|
+
function explanationActionOf(explanation) {
|
|
3290
|
+
return explanation.action ?? "explain";
|
|
3291
|
+
}
|
|
3280
3292
|
/** The explanation of this finding, if the reviewer has asked for one. */
|
|
3281
|
-
function
|
|
3282
|
-
return session.
|
|
3293
|
+
function explanationOf(session, kind, id) {
|
|
3294
|
+
return session.findingExplanations?.find((entry) => entry.kind === kind && entry.id === id);
|
|
3295
|
+
}
|
|
3296
|
+
/**
|
|
3297
|
+
* A session as stored by 0.71.0 to 0.71.2, which kept explanations under `simpleExplanations`, read
|
|
3298
|
+
* as this version stores it. Those were all Explain (simple), which an entry without an action
|
|
3299
|
+
* already means, so they move across unchanged. Where both lists name a finding the newer list's
|
|
3300
|
+
* entry wins: a later version wrote it. A session without the old list is returned as it was.
|
|
3301
|
+
*/
|
|
3302
|
+
function withLegacyExplanationsMoved(session) {
|
|
3303
|
+
const legacy = session.simpleExplanations;
|
|
3304
|
+
if (legacy === void 0) return session;
|
|
3305
|
+
const { simpleExplanations: _moved, ...rest } = session;
|
|
3306
|
+
const newer = session.findingExplanations ?? [];
|
|
3307
|
+
const merged = [...(Array.isArray(legacy) ? legacy : []).filter((entry) => !explanationOf(session, entry.kind, entry.id)), ...newer];
|
|
3308
|
+
return {
|
|
3309
|
+
...rest,
|
|
3310
|
+
...merged.length ? { findingExplanations: merged } : {}
|
|
3311
|
+
};
|
|
3283
3312
|
}
|
|
3284
|
-
/**
|
|
3285
|
-
|
|
3313
|
+
/**
|
|
3314
|
+
* The list with this finding's explanation replaced, never a second one beside it. One slot per
|
|
3315
|
+
* finding whichever action wrote it: the newest explanation is the card's.
|
|
3316
|
+
*/
|
|
3317
|
+
function withExplanation(entries, explanation) {
|
|
3286
3318
|
return [...(entries ?? []).filter((entry) => !(entry.kind === explanation.kind && entry.id === explanation.id)), explanation];
|
|
3287
3319
|
}
|
|
3288
3320
|
/**
|
|
@@ -4431,12 +4463,12 @@ var WORKSPACES_DIR = join(STORE_ROOT, "workspaces");
|
|
|
4431
4463
|
* Named so that a long run that rewrites the session, an update above all, can put these back from
|
|
4432
4464
|
* the copy on disk when it saves: the reviewer may have answered a question or dismissed a finding
|
|
4433
4465
|
* in the minutes it took. `handleState` writes the first two and nothing else; `handleAsk` writes
|
|
4434
|
-
* `
|
|
4466
|
+
* `findingExplanations`, and only when the reviewer asked for one.
|
|
4435
4467
|
*/
|
|
4436
4468
|
var REVIEWER_SESSION_FIELDS = [
|
|
4437
4469
|
"answeredQuestionIds",
|
|
4438
4470
|
"dismissedIssues",
|
|
4439
|
-
"
|
|
4471
|
+
"findingExplanations"
|
|
4440
4472
|
];
|
|
4441
4473
|
/**
|
|
4442
4474
|
* A session a long run is about to write, with the reviewer's own fields taken from disk instead.
|
|
@@ -4668,7 +4700,13 @@ function deleteWorkspace(id) {
|
|
|
4668
4700
|
}
|
|
4669
4701
|
function loadWorkspace(id) {
|
|
4670
4702
|
try {
|
|
4671
|
-
|
|
4703
|
+
const stored = JSON.parse(readFileSync(workspacePath(id), "utf8"));
|
|
4704
|
+
if (!stored?.session || typeof stored.session !== "object") return stored;
|
|
4705
|
+
const session = withLegacyExplanationsMoved(stored.session);
|
|
4706
|
+
return session === stored.session ? stored : {
|
|
4707
|
+
...stored,
|
|
4708
|
+
session
|
|
4709
|
+
};
|
|
4672
4710
|
} catch {
|
|
4673
4711
|
return null;
|
|
4674
4712
|
}
|
|
@@ -13273,6 +13311,7 @@ function project(record) {
|
|
|
13273
13311
|
cliVersion: record.cliVersion,
|
|
13274
13312
|
platform: record.platform,
|
|
13275
13313
|
agent: event.agent,
|
|
13314
|
+
reviewType: event.reviewType,
|
|
13276
13315
|
occurredAt: record.at
|
|
13277
13316
|
};
|
|
13278
13317
|
return event.event === "review_finished" ? {
|
|
@@ -13351,7 +13390,7 @@ var LOG_PATH = join(STORE_ROOT, "events.jsonl");
|
|
|
13351
13390
|
* disable is a different act from sending to a server, and the consent that suits one does not
|
|
13352
13391
|
* carry to the other, so `events.jsonl` keeps recording the full event union either way and the
|
|
13353
13392
|
* question asked on first run is about the wire. Twenty events are written here; three of them,
|
|
13354
|
-
* carrying
|
|
13393
|
+
* carrying nine fields between them, are the most that is ever sent.
|
|
13355
13394
|
*/
|
|
13356
13395
|
function record(event) {
|
|
13357
13396
|
try {
|
|
@@ -13693,10 +13732,11 @@ async function drive(jobId, workspaceId, runners, driver, signal) {
|
|
|
13693
13732
|
contextId: context.contextId
|
|
13694
13733
|
};
|
|
13695
13734
|
saveJob(job);
|
|
13696
|
-
|
|
13735
|
+
record({
|
|
13697
13736
|
event: "review_started",
|
|
13698
13737
|
reviewId: workspaceId,
|
|
13699
13738
|
agent: agentName(job.env.agentId),
|
|
13739
|
+
reviewType: reviewKindOf(job),
|
|
13700
13740
|
changedFiles: totalChangedFiles(context.changeSet.data),
|
|
13701
13741
|
linesChanged: context.changeSet.data.files.reduce((total, file) => total + (file.additions ?? 0) + (file.deletions ?? 0), 0),
|
|
13702
13742
|
analysedFiles: Math.max(0, context.changeSet.data.files.length - context.inventoryOnlyFiles.length)
|
|
@@ -13777,11 +13817,11 @@ async function drive(jobId, workspaceId, runners, driver, signal) {
|
|
|
13777
13817
|
* finished it. That is the number worth having: it is what somebody actually waited.
|
|
13778
13818
|
*/
|
|
13779
13819
|
function finished(job, workspaceId, startedAt, ok, now) {
|
|
13780
|
-
if (job.reviewType === "kiss") return;
|
|
13781
13820
|
record({
|
|
13782
13821
|
event: "review_finished",
|
|
13783
13822
|
reviewId: workspaceId,
|
|
13784
13823
|
agent: agentName(job.env.agentId),
|
|
13824
|
+
reviewType: reviewKindOf(job),
|
|
13785
13825
|
ok,
|
|
13786
13826
|
durationMs: now() - startedAt
|
|
13787
13827
|
});
|
|
@@ -13831,6 +13871,10 @@ function recordAnalysisRuntime(job, workspaceId, from, durationMs, completedAt,
|
|
|
13831
13871
|
* named ran under Claude. Recording those as unknown would invent a fourth agent out of the
|
|
13832
13872
|
* oldest reviews on disk.
|
|
13833
13873
|
*/
|
|
13874
|
+
/** A job with no `reviewType` predates KISS and is a standard review. */
|
|
13875
|
+
function reviewKindOf(job) {
|
|
13876
|
+
return job.reviewType === "kiss" ? "kiss" : "standard";
|
|
13877
|
+
}
|
|
13834
13878
|
function agentName(id) {
|
|
13835
13879
|
const resolved = id ?? DEFAULT_AGENT_ID;
|
|
13836
13880
|
return isAgentId(resolved) ? resolved : "other";
|
|
@@ -16708,4 +16752,4 @@ function lineageIds(id) {
|
|
|
16708
16752
|
return [.../* @__PURE__ */ new Set([id, ...lineage.map((review) => review.id)])];
|
|
16709
16753
|
}
|
|
16710
16754
|
//#endregion
|
|
16711
|
-
export { MAX_WORKSPACE_PORT as $, isClaimed as $n,
|
|
16755
|
+
export { MAX_WORKSPACE_PORT as $, isClaimed as $n, DetachedHeadError as $r, markReadForReview as $t, relativeTime as A, isWorktreeHeld as An, parseLifecycle as Ar, partFiles as At, readIndexToken as B, reviewedShaOf as Bn, issuesOutstanding as Br, isArchitectureId as Bt, track as C, originRemote as Cn, activityAttributes as Cr, mapRuntimes as Ct, endRun as D, MANAGED_ROOT as Dn, lifecycleItemLabel as Dr, ARCHITECTURE_DIR as Dt, completeRun as E, ReviewBeingDeletedError as En, lifecycleChip as Er, describeRuntimes as Et, runBatch as F, describeSubject as Fn, queueStamp as Fr, trackedFiles as Ft, reviewsUrl as G, loadJob as Gn, explanationActionOf as Gr, createArchitectureNotification as Gt, clearServerRecordOwnedBy as H, endedAt as Hn, reviewerDispositions as Hr, announceJob as Ht, activateQueued as I, findExistingReview as In, readQueue as Ir, ownersOf as It, stopChild as J, isLiveJob as Jn, withExplanation as Jr, eventName as Jt, serverEntryExists as K, saveJob as Kn, explanationOf as Kr, createPrState as Kt, compactQueue as L, checkCodeFreshness as Ln, doneVerb as Lr, QUOTE_LIMITS as Lt, extractPullRequestLinks as M, removeWorktree as Mn, queueAdd as Mr, repositoryKey as Mt, parsePullRequestLink as N, requestJobStop as Nn, queueAddMany as Nr, storeArchitecture as Nt, reportRun as O, createWorktree as On, lifecyclePill as Or, checkArchitecture as Ot, unsupportedReason as P, describeMovement as Pn, queueRemove as Pr, storedScopes as Pt, DEFAULT_WORKSPACE_PORT as Q, clearClaim as Qn, resolveTarget as Qr, markRead as Qt, queueForReview as R, checkReview as Rn, isIssueOutstanding as Rr, REPOSITORY_SCOPE as Rt, isDriving as S, remoteHost as Sn, LIFECYCLES as Sr, waitingHistory as St, beginRun as T, modelHelpLines as Tn, isLifecycle as Tr, RUNTIME_WORDS as Tt, ensureServer as U, fail as Un, processDiscussion as Ur, branchOf as Ut, bootstrapUrl as V, allJobs as Vn, questionsOutstanding as Vr, writeJsonAtomically as Vt, readServerRecord as W, isTerminal as Wn, recommendationsOf as Wr, subjectOf as Wt, workspaceUrl as X, progressSteps as Xn, WITHHELD_REASON as Xr, listNotifications as Xt, stopServer as Y, phasesFor as Yn, describeAuthorship as Yr, headline as Yt, writeServerRecord as Z, purposeOf as Zn, kissOutcome as Zr, markAllRead as Zt, liveUpdateFor as _, extractJsonText as _n, reviewedRepositories as _r, telemetryUploadConsent as _t, wasBlocked as a, clearAgentPreference as ai, withUsageRecorded as an, kissDecisionRefusal as ar, failureOf as at, recordKissRun as b, forgeResolver as bn, workspaceRevision as br, PACKAGE_NAME as bt, RefreshUnavailableError as c, CONFIG_PATH as ci, authorPreview as cn, lineagePosition as cr, reanalyseReview as ct, mergeUpdateInto as d, agentById as di, renderMarkdown as dn, mutateWorkspace as dr, append as dt, isCommitReview as ei, pruneNotifications as en, decideKissRecommendation as er, MIN_WORKSPACE_PORT as et, updateReview as f, agentFor as fi, CODE_BLOCK_CLASS as fn, positionInLineage as fr, readEvents as ft, UpdateAlreadyRunningError as g, asString as gn, reviewedCommit as gr, recordTelemetryUploadConsent as gt, askCheckout as h, AgentUnavailableError as hi, renderInlineText as hn, recentWorkspaces as hr, deliver as ht, liveJobsFor as i, resolveModel as ii, unreadCount as in, indexReviewGroups as ir, writePortPreference as it, batchIdentity as j, readMarker as jn, sectionOf as jr, readArchitecture as jt, stopRun as k, holdWorktree as kn, offersLifecycle as kr, loadArchitecture as kt, cancelUpdate as l, STORE_ROOT as li, hasAuthorContent as ln, loadWorkspace as lr, runJob as lt, isUnchanged as m, AgentCancelledError as mi, escapeHtml as mn, previousKissResult as mr, stamp as mt, deleteReview as n, git as ni, removeNotification as nn, groupByLineage as nr, resolveWorkspacePort as nt, discardJob as o, readAgentPreference as oi, EXPLAIN_SIMPLY_PROMPT as on, kissDecisionsOf as or, prepareReview as ot, refreshPrContext as p, detectAgents as pi, CODE_COPY_CLASS as pn, presentedKissRunOf as pr, record as pt, spawnServer as q, PHASES as qn, isExplanationAction as qr, createPrUpdate as qt, lineageIds as r, resolveAgent as ri, toneOf as rn, indexKeptIds as rr, statedWorkspacePort as rt, FETCH_TIMEOUT_MS as s, writeAgentPreference as si, UNESTABLISHED_MOTIVATION as sn, latestKissRun as sr, settleReview as st, deleteLineage as t, resolveBase as ti, readNotification as tn, followedRefName as tr, parseWorkspacePort as tt, cancellableHere as u, AGENT_IDS as ui, GITHUB_MARKDOWN as un, matchingWorkspaceIds as ur, startJob as ut, runningJobs as v, objectWithAnyOf as vn, summarise as vr, telemetryUploadEnabled as vt, RUN_KINDS as w, fillFileUrlTemplate as wn, activityLabel as wr, recordRuntimeBestEffort as wt, startKissJob as x, forgeFor as xn, workspaceStamps as xr, feedbackUrl as xt, agentEnvOf as y, claimRequestUrl as yn, touchWorkspace as yr, BUILD_VERSION as yt, settleQueue as z, followedBranch as zn, isQuestionOutstanding as zr, ancestorsOf as zt };
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as MAX_WORKSPACE_PORT, A as relativeTime, B as readIndexToken, Dn as MANAGED_ROOT, F as runBatch, Fn as describeSubject, G as reviewsUrl, Gn as loadJob, In as findExistingReview,
|
|
2
|
+
import { $ as MAX_WORKSPACE_PORT, A as relativeTime, B as readIndexToken, Dn as MANAGED_ROOT, F as runBatch, Fn as describeSubject, G as reviewsUrl, Gn as loadJob, In as findExistingReview, Mn as removeWorktree, Pn as describeMovement, Q as DEFAULT_WORKSPACE_PORT, Qn as clearClaim, Qr as resolveTarget, R as queueForReview, Tn as modelHelpLines, U as ensureServer, V as bootstrapUrl, Vn as allJobs, X as workspaceUrl, Xn as progressSteps, Xr as WITHHELD_REASON, Y as stopServer, Zn as purposeOf, Zr as kissOutcome, _ as liveUpdateFor, _r as reviewedRepositories, _t as telemetryUploadConsent, a as wasBlocked, b as recordKissRun, bn as forgeResolver, bt as PACKAGE_NAME, ci as CONFIG_PATH, ct as reanalyseReview, d as mergeUpdateInto, di as agentById, dr as mutateWorkspace, dt as append, et as MIN_WORKSPACE_PORT, f as updateReview, fr as positionInLineage, g as UpdateAlreadyRunningError, gt as recordTelemetryUploadConsent, hr as recentWorkspaces, ht as deliver, i as liveJobsFor, ii as resolveModel, it as writePortPreference, j as batchIdentity, jn as readMarker, li as STORE_ROOT, lr as loadWorkspace, mt as stamp, n as deleteReview$1, ni as git, oi as readAgentPreference, ot as prepareReview, pi as detectAgents, pt as record$1, qn as PHASES, ri as resolveAgent, rt as statedWorkspacePort, si as writeAgentPreference, st as settleReview, tr as followedRefName, tt as parseWorkspacePort, ui as AGENT_IDS, ur as matchingWorkspaceIds, ut as startJob, v as runningJobs, vr as summarise, vt as telemetryUploadEnabled, yt as BUILD_VERSION } from "./delete_review-CqnTq6yF.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 isClaimed, $r as
|
|
1
|
+
import { $ as MAX_WORKSPACE_PORT, $n as isClaimed, $r as DetachedHeadError, $t as markReadForReview, A as relativeTime$1, An as isWorktreeHeld, Ar as parseLifecycle, At as partFiles, B as readIndexToken, Bn as reviewedShaOf, Br as issuesOutstanding, Bt as isArchitectureId, C as track, Cn as originRemote, Cr as activityAttributes, Ct as mapRuntimes, D as endRun, Dn as MANAGED_ROOT, Dr as lifecycleItemLabel, Dt as ARCHITECTURE_DIR, E as completeRun, En as ReviewBeingDeletedError, Er as lifecycleChip, Et as describeRuntimes, F as runBatch, Fr as queueStamp, Ft as trackedFiles, Gn as loadJob, Gr as explanationActionOf, Gt as createArchitectureNotification, H as clearServerRecordOwnedBy, Hn as endedAt, Hr as reviewerDispositions, Ht as announceJob, I as activateQueued, In as findExistingReview, Ir as readQueue, It as ownersOf, J as stopChild, Jn as isLiveJob, Jr as withExplanation, Jt as eventName, K as serverEntryExists, Kn as saveJob, Kr as explanationOf, Kt as createPrState, L as compactQueue, Ln as checkCodeFreshness, Lr as doneVerb, Lt as QUOTE_LIMITS, M as extractPullRequestLinks, Mn as removeWorktree, Mr as queueAdd, Mt as repositoryKey, N as parsePullRequestLink, Nn as requestJobStop, Nr as queueAddMany, Nt as storeArchitecture, O as reportRun, On as createWorktree, Or as lifecyclePill, Ot as checkArchitecture, P as unsupportedReason, Pr as queueRemove, Pt as storedScopes, Q as DEFAULT_WORKSPACE_PORT, Qr as resolveTarget, Qt as markRead, Rn as checkReview, Rr as isIssueOutstanding, Rt as REPOSITORY_SCOPE, S as isDriving, Sn as remoteHost, Sr as LIFECYCLES, St as waitingHistory, T as beginRun, Tr as isLifecycle, Tt as RUNTIME_WORDS, Un as fail, Ur as processDiscussion, Ut as branchOf, Vn as allJobs, Vr as questionsOutstanding, Vt as writeJsonAtomically, W as readServerRecord, Wn as isTerminal, Wr as recommendationsOf, Wt as subjectOf, Xn as progressSteps, Xr as WITHHELD_REASON, Xt as listNotifications, Yn as phasesFor, Yr as describeAuthorship, Yt as headline, Z as writeServerRecord, Zn as purposeOf, Zr as kissOutcome, Zt as markAllRead, _ as liveUpdateFor, _n as extractJsonText, _t as telemetryUploadConsent, a as wasBlocked, ai as clearAgentPreference, an as withUsageRecorded, ar as kissDecisionRefusal, at as failureOf$1, bn as forgeResolver, br as workspaceRevision, c as RefreshUnavailableError, cn as authorPreview, cr as lineagePosition, ct as reanalyseReview, d as mergeUpdateInto, di as agentById, dn as renderMarkdown, dr as mutateWorkspace, ei as isCommitReview, en as pruneNotifications, er as decideKissRecommendation, et as MIN_WORKSPACE_PORT, f as updateReview, fi as agentFor$1, fn as CODE_BLOCK_CLASS, ft as readEvents, g as UpdateAlreadyRunningError, gn as asString, gr as reviewedCommit, gt as recordTelemetryUploadConsent, h as askCheckout, hi as AgentUnavailableError, hn as renderInlineText, hr as recentWorkspaces, i as liveJobsFor, ii as resolveModel, in as unreadCount$1, ir as indexReviewGroups, it as writePortPreference, j as batchIdentity, jn as readMarker, jr as sectionOf, jt as readArchitecture, k as stopRun, kn as holdWorktree, kr as offersLifecycle, kt as loadArchitecture, l as cancelUpdate, li as STORE_ROOT, ln as hasAuthorContent, lr as loadWorkspace, lt as runJob, m as isUnchanged, mi as AgentCancelledError, mn as escapeHtml$1, mr as previousKissResult, n as deleteReview, ni as git, nn as removeNotification, nr as groupByLineage, nt as resolveWorkspacePort, o as discardJob, oi as readAgentPreference, on as EXPLAIN_SIMPLY_PROMPT, or as kissDecisionsOf, ot as prepareReview, p as refreshPrContext, pi as detectAgents, pn as CODE_COPY_CLASS, pr as presentedKissRunOf, pt as record, q as spawnServer, qr as isExplanationAction, qt as createPrUpdate, r as lineageIds, ri as resolveAgent, rn as toneOf, rr as indexKeptIds, rt as statedWorkspacePort, s as FETCH_TIMEOUT_MS, si as writeAgentPreference, sn as UNESTABLISHED_MOTIVATION, sr as latestKissRun, st as settleReview, t as deleteLineage, ti as resolveBase, tn as readNotification, tr as followedRefName, tt as parseWorkspacePort, u as cancellableHere, ui as AGENT_IDS, un as GITHUB_MARKDOWN, vn as objectWithAnyOf, w as RUN_KINDS, wn as fillFileUrlTemplate, wr as activityLabel, wt as recordRuntimeBestEffort, x as startKissJob, xn as forgeFor, xr as workspaceStamps, xt as feedbackUrl, y as agentEnvOf, yn as claimRequestUrl, yr as touchWorkspace, yt as BUILD_VERSION, z as settleQueue, zn as followedBranch, zr as isQuestionOutstanding, zt as ancestorsOf } from "./delete_review-CqnTq6yF.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";
|
|
@@ -1097,25 +1097,22 @@ function renderAskBuddyMenu(kind) {
|
|
|
1097
1097
|
</div>`;
|
|
1098
1098
|
}
|
|
1099
1099
|
//#endregion
|
|
1100
|
-
//#region ../../packages/review-harness/src/domain/ui/components/
|
|
1101
|
-
/**
|
|
1102
|
-
* A finding's simple explanation: Buddy AI's plain-English account of it, kept on the card because
|
|
1103
|
-
* the reviewer asked for one.
|
|
1104
|
-
*
|
|
1105
|
-
* For reading the finding, not part of it. It carries no copy source, so Copy and Prepare feedback,
|
|
1106
|
-
* which take the card's first one, can only ever find the finding's own.
|
|
1107
|
-
*
|
|
1108
|
-
* A native disclosure, closed when the page loads and open when it has just been written. Whether a
|
|
1109
|
-
* reader left it open is not kept: the explanation is the durable part.
|
|
1110
|
-
*/
|
|
1111
|
-
var SIMPLE_EXPLANATION_LABEL = "Simple explanation";
|
|
1100
|
+
//#region ../../packages/review-harness/src/domain/ui/components/finding_explanation.ts
|
|
1112
1101
|
var ASK_MORE_LABEL = "Ask more →";
|
|
1113
|
-
|
|
1114
|
-
|
|
1102
|
+
var EXPLANATION_LABELS = {
|
|
1103
|
+
"explain-detail": "Explanation · Technical",
|
|
1104
|
+
explain: "Explanation · Simple"
|
|
1105
|
+
};
|
|
1106
|
+
/** The disclosure's label: which kind of explanation the card holds, so the other is one choice away. */
|
|
1107
|
+
function explanationLabel(explanation) {
|
|
1108
|
+
return EXPLANATION_LABELS[explanationActionOf(explanation)];
|
|
1109
|
+
}
|
|
1110
|
+
function renderFindingExplanation(explanation, options = {}) {
|
|
1111
|
+
const body = renderMarkdown(explanation.text);
|
|
1115
1112
|
const askMore = options.askMore ? `<button type="button" class="pr-helper-simple-explanation-more" data-simple-explanation-more>${ASK_MORE_LABEL}</button>` : "";
|
|
1116
1113
|
const content = askMore && body.endsWith("</p>") ? `${body.slice(0, -4)} ${askMore}</p>` : body + askMore;
|
|
1117
|
-
return `<details class="pr-helper-simple-explanation" data-simple-explanation${options.open ? " open" : ""}>
|
|
1118
|
-
<summary class="pr-helper-simple-explanation-summary">${
|
|
1114
|
+
return `<details class="pr-helper-simple-explanation" data-simple-explanation${!Number.isFinite(explanation.writtenAt) ? "" : ` data-explanation-written-at="${explanation.writtenAt}"`}${options.open ? " open" : ""}>
|
|
1115
|
+
<summary class="pr-helper-simple-explanation-summary">${explanationLabel(explanation)}</summary>
|
|
1119
1116
|
<div class="pr-helper-simple-explanation-body pr-helper-markdown">${content}</div>
|
|
1120
1117
|
</details>`;
|
|
1121
1118
|
}
|
|
@@ -2608,7 +2605,7 @@ function renderIssuesTab(session, investigatingId, options) {
|
|
|
2608
2605
|
</div>
|
|
2609
2606
|
${renderStaleDismissal(stale)}
|
|
2610
2607
|
${renderSeverityRevision(issue.priorSeverity, issue.severity)}
|
|
2611
|
-
<p class="pr-helper-issue-desc">${renderInlineText(issue.description)}</p>${renderCardExplanation(
|
|
2608
|
+
<p class="pr-helper-issue-desc">${renderInlineText(issue.description)}</p>${renderCardExplanation(explanationOf(session, "issue", issue.id), options)}
|
|
2612
2609
|
${renderReconciliationNote(issue.reconciliationNote)}
|
|
2613
2610
|
${renderInvestigationNote(issue.investigationNote)}
|
|
2614
2611
|
${renderExistingDiscussion(issue.existingDiscussion)}
|
|
@@ -2642,12 +2639,12 @@ function closedFindingsSummary(counts) {
|
|
|
2642
2639
|
return `Dealt with (${counts.done})`;
|
|
2643
2640
|
}
|
|
2644
2641
|
/**
|
|
2645
|
-
* The finding's
|
|
2646
|
-
*
|
|
2642
|
+
* The finding's explanation, under what the finding says and above where it stands. Nothing at all
|
|
2643
|
+
* when the reviewer has not asked for one, so such a card is the card it always was.
|
|
2647
2644
|
*/
|
|
2648
|
-
function renderCardExplanation(
|
|
2649
|
-
if (!text
|
|
2650
|
-
return
|
|
2645
|
+
function renderCardExplanation(explanation, options) {
|
|
2646
|
+
if (!explanation?.text.trim()) return "";
|
|
2647
|
+
return renderFindingExplanation(explanation, { askMore: options?.askBuddy });
|
|
2651
2648
|
}
|
|
2652
2649
|
/** The reviewer's own decision, said on the card so the closed pile is never one undifferentiated heap. */
|
|
2653
2650
|
function renderDismissedBadge(isDismissed) {
|
|
@@ -2787,7 +2784,7 @@ function renderQuestionsTab(session, investigatingId, options) {
|
|
|
2787
2784
|
const progress = renderQuestionsProgress(result.questions, answered);
|
|
2788
2785
|
const records = session.investigations;
|
|
2789
2786
|
const now = Date.now();
|
|
2790
|
-
const card = (q) => renderQuestionCard(q, sections, answered.has(q.id), records, now, investigatingId, options,
|
|
2787
|
+
const card = (q) => renderQuestionCard(q, sections, answered.has(q.id), records, now, investigatingId, options, explanationOf(session, "question", q.id));
|
|
2791
2788
|
const open = result.questions.filter((q) => isQuestionOutstanding(q, answered));
|
|
2792
2789
|
const settled = result.questions.filter((q) => !isQuestionOutstanding(q, answered));
|
|
2793
2790
|
const closed = settled.length === 0 ? "" : renderClosedFindings(`Answered (${settled.length})`, settled.map(card).join(""), "questions", open.length === 0);
|
|
@@ -11373,7 +11370,7 @@ function buildPrompt$2(workspace, question, sectionId, cwd, context = {}) {
|
|
|
11373
11370
|
].join("\n");
|
|
11374
11371
|
}
|
|
11375
11372
|
/**
|
|
11376
|
-
* The
|
|
11373
|
+
* The kept explanation a follow-up was asked from, however long ago it was written.
|
|
11377
11374
|
*
|
|
11378
11375
|
* Given in full rather than left to the recent turns above, which hold only the last four: a
|
|
11379
11376
|
* reviewer who comes back a week later and presses Ask more is continuing from the explanation on
|
|
@@ -11383,22 +11380,24 @@ function continuingFrom(workspace, continuing) {
|
|
|
11383
11380
|
if (!continuing) return [];
|
|
11384
11381
|
return [
|
|
11385
11382
|
"---",
|
|
11386
|
-
`THE REVIEWER IS CONTINUING FROM THIS PLAIN-ENGLISH EXPLANATION YOU GAVE EARLIER OF ${findingName(workspace, continuing)}:`,
|
|
11383
|
+
`THE REVIEWER IS CONTINUING FROM THIS ${explanationActionOf(continuing) === "explain" ? "PLAIN-ENGLISH" : "TECHNICAL"} EXPLANATION YOU GAVE EARLIER OF ${findingName(workspace, continuing)}:`,
|
|
11387
11384
|
continuing.text
|
|
11388
11385
|
];
|
|
11389
11386
|
}
|
|
11390
11387
|
/**
|
|
11391
|
-
* Explain
|
|
11392
|
-
* moved on. So
|
|
11393
|
-
* asserting what the code does now: a later update or investigation says
|
|
11394
|
-
* holds, and an explanation written as a present-tense fact about the code
|
|
11388
|
+
* Explain (technical) or Explain (simple) on a finding, whose answer is kept on the card and read
|
|
11389
|
+
* again after the code has moved on. So either explains the claim, what the finding says and why it
|
|
11390
|
+
* was raised, rather than asserting what the code does now: a later update or investigation says
|
|
11391
|
+
* whether the claim still holds, and an explanation written as a present-tense fact about the code
|
|
11392
|
+
* would contradict it. They differ only in register, as the visible instruction asks.
|
|
11395
11393
|
*/
|
|
11396
11394
|
function explainingFinding(workspace, explaining) {
|
|
11397
11395
|
if (!explaining) return [];
|
|
11396
|
+
const simple = explaining.action === "explain";
|
|
11398
11397
|
return [
|
|
11399
11398
|
"---",
|
|
11400
|
-
`THE REVIEWER ASKED FOR A SIMPLE EXPLANATION OF ${findingName(workspace, explaining)}. It is kept beside that finding and read again later, possibly after the code has changed.`,
|
|
11401
|
-
|
|
11399
|
+
`THE REVIEWER ASKED FOR ${simple ? "A SIMPLE" : "A TECHNICAL"} EXPLANATION OF ${findingName(workspace, explaining)}. It is kept beside that finding and read again later, possibly after the code has changed.`,
|
|
11400
|
+
`Explain ${simple ? "in plain English" : "with the technical detail kept"} what the finding is saying and why it was raised: the concern as the review stated it, in terms of what was reviewed (for example "this was raised because..."), not as a statement about how the code behaves now. Do not judge whether it still holds; the review tracks that separately.`
|
|
11402
11401
|
];
|
|
11403
11402
|
}
|
|
11404
11403
|
/** `THE ISSUE "<title>"` or `THE QUESTION "<question>"`, or just the kind when the finding is gone. */
|
|
@@ -13221,11 +13220,13 @@ var MAX_KEPT_BRANCHES = 50;
|
|
|
13221
13220
|
*
|
|
13222
13221
|
* Only once the analysis is complete. The page is served while a review is still being made, and
|
|
13223
13222
|
* recording that would file a row of zeroes — no risk, no themes, no findings — against a review
|
|
13224
|
-
* that has them, which is worse than recording nothing.
|
|
13223
|
+
* that has them, which is worse than recording nothing. `result` is that signal: a running or idle
|
|
13224
|
+
* session carries none. `completedAt` is not: it is the Reviewed status, and gating on it kept this
|
|
13225
|
+
* event off every first read.
|
|
13225
13226
|
*/
|
|
13226
13227
|
function workspaceOpenedEvent(workspace) {
|
|
13227
13228
|
const result = workspace.session.result;
|
|
13228
|
-
if (
|
|
13229
|
+
if (!result) return null;
|
|
13229
13230
|
const severities = {
|
|
13230
13231
|
critical: 0,
|
|
13231
13232
|
major: 0,
|
|
@@ -13694,9 +13695,8 @@ async function handleAsk(req, res, workspace) {
|
|
|
13694
13695
|
const question = typeof body?.question === "string" ? body.question.trim() : "";
|
|
13695
13696
|
if (!question) return send(res, 400, { error: "No question given." });
|
|
13696
13697
|
const sectionId = typeof body?.sectionId === "string" ? body.sectionId : null;
|
|
13697
|
-
const explains =
|
|
13698
|
-
const
|
|
13699
|
-
const continuing = continuesFrom ? simpleExplanationOf(workspace.session, continuesFrom.kind, continuesFrom.id) : void 0;
|
|
13698
|
+
const explains = explanationRequested(workspace.session, body?.explains);
|
|
13699
|
+
const continuing = explanationContinued(workspace, body?.continuesExplanation);
|
|
13700
13700
|
if (liveUpdateFor(workspace.id)) return send(res, 409, {
|
|
13701
13701
|
error: "This review is being updated. Ask again when the new review is ready.",
|
|
13702
13702
|
updating: true
|
|
@@ -13749,18 +13749,22 @@ async function handleAsk(req, res, workspace) {
|
|
|
13749
13749
|
});
|
|
13750
13750
|
let explanation;
|
|
13751
13751
|
mutateWorkspace(workspace.id, (fresh) => {
|
|
13752
|
+
const askedAt = Date.now();
|
|
13753
|
+
const kept = explains && findingNamed(fresh.session, explains) && answer.trim() ? explains : null;
|
|
13752
13754
|
fresh.transcript.push({
|
|
13753
13755
|
question,
|
|
13754
13756
|
answer,
|
|
13755
13757
|
sectionId,
|
|
13756
|
-
askedAt
|
|
13758
|
+
askedAt,
|
|
13759
|
+
...kept ? { explains: kept } : {}
|
|
13757
13760
|
});
|
|
13758
|
-
if (
|
|
13761
|
+
if (kept) {
|
|
13759
13762
|
explanation = {
|
|
13760
|
-
...
|
|
13761
|
-
text: answer.trim()
|
|
13763
|
+
...kept,
|
|
13764
|
+
text: answer.trim(),
|
|
13765
|
+
writtenAt: askedAt
|
|
13762
13766
|
};
|
|
13763
|
-
fresh.session.
|
|
13767
|
+
fresh.session.findingExplanations = withExplanation(fresh.session.findingExplanations, explanation);
|
|
13764
13768
|
}
|
|
13765
13769
|
});
|
|
13766
13770
|
sendIfOpen(res, 200, explanation ? {
|
|
@@ -13771,6 +13775,45 @@ async function handleAsk(req, res, workspace) {
|
|
|
13771
13775
|
endRun(run);
|
|
13772
13776
|
}
|
|
13773
13777
|
}
|
|
13778
|
+
/**
|
|
13779
|
+
* The explanation a follow-up was staged from, named by its finding and `writtenAt`.
|
|
13780
|
+
*
|
|
13781
|
+
* The card's current one when it is still that explanation. When another has replaced it since, the
|
|
13782
|
+
* turn that wrote it, which the transcript keeps, so a question written against one explanation is
|
|
13783
|
+
* never sent with another's words. Nothing when neither is there. A follow-up without `writtenAt`
|
|
13784
|
+
* (an explanation kept before 0.72.0, or a page from then) continues from the card's current one.
|
|
13785
|
+
*/
|
|
13786
|
+
function explanationContinued(workspace, value) {
|
|
13787
|
+
const finding = findingNamed(workspace.session, value);
|
|
13788
|
+
if (!finding) return void 0;
|
|
13789
|
+
const current = explanationOf(workspace.session, finding.kind, finding.id);
|
|
13790
|
+
const writtenAt = value.writtenAt;
|
|
13791
|
+
if (writtenAt === void 0 || current?.writtenAt === writtenAt) return current;
|
|
13792
|
+
const turn = workspace.transcript.find((exchange) => exchange.askedAt === writtenAt && exchange.explains?.kind === finding.kind && exchange.explains.id === finding.id);
|
|
13793
|
+
return turn?.explains && turn.answer.trim() ? {
|
|
13794
|
+
...finding,
|
|
13795
|
+
action: turn.explains.action,
|
|
13796
|
+
text: turn.answer.trim(),
|
|
13797
|
+
writtenAt: turn.askedAt
|
|
13798
|
+
} : void 0;
|
|
13799
|
+
}
|
|
13800
|
+
/**
|
|
13801
|
+
* The finding a turn explains and the action explaining it, or null when its answer is not to be kept.
|
|
13802
|
+
*
|
|
13803
|
+
* A missing action is Explain (simple): the only one a page sent before there were two, and a tab
|
|
13804
|
+
* opened then still sends it that way. An action that is named but not an explanation is not
|
|
13805
|
+
* guessed at; the turn is answered like any other and nothing is kept.
|
|
13806
|
+
*/
|
|
13807
|
+
function explanationRequested(session, value) {
|
|
13808
|
+
const finding = findingNamed(session, value);
|
|
13809
|
+
if (!finding) return null;
|
|
13810
|
+
const named = value.action;
|
|
13811
|
+
const action = named === void 0 ? "explain" : named;
|
|
13812
|
+
return isExplanationAction(action) ? {
|
|
13813
|
+
...finding,
|
|
13814
|
+
action
|
|
13815
|
+
} : null;
|
|
13816
|
+
}
|
|
13774
13817
|
/** An issue or question of this review, named by kind and id; null for anything else. */
|
|
13775
13818
|
function findingNamed(session, value) {
|
|
13776
13819
|
const named = value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prreviewbuddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.73.0",
|
|
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",
|