prreviewbuddy 0.85.0 → 0.86.1
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 +23 -0
- package/dist/{delete_review-BED4mPxB.js → delete_review-rwn_-SnJ.js} +303 -40
- package/dist/main.js +1 -1
- package/dist/server.js +916 -71
- package/package.json +1 -1
- package/static/shell.css +42 -0
- package/static/sidebar.css +15 -0
- package/static/workspace.js +66 -59
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.86.1
|
|
4
|
+
|
|
5
|
+
**Reconcile reads a reply against the right review.** A feedback draft now ends with the review its
|
|
6
|
+
references belong to, and a reply naming a different analysis of the review is refused rather than
|
|
7
|
+
matched to findings numbered the same way. A reply naming none is read with its references checked
|
|
8
|
+
against what each finding asks. A resolution resting on code must cite a line, and a suggestion
|
|
9
|
+
shows that line and what it rests on before you accept it. The button says it reads only the message
|
|
10
|
+
it sits under. Extension 0.29.7 carries the shared changes.
|
|
11
|
+
|
|
12
|
+
## 0.86.0
|
|
13
|
+
|
|
14
|
+
**The author's reply can update the review.** Every issue and question now shows a reference, such
|
|
15
|
+
as I3 or Q2, and a feedback draft leads each finding with it, so the author can answer by reference.
|
|
16
|
+
Paste the reply into Buddy AI and press Reconcile with findings under it, which reads that message
|
|
17
|
+
only: Buddy AI suggests what the reply settles (a question answered or partly answered, an issue
|
|
18
|
+
resolved or needing investigation), each on its card with the words it rests on. Nothing changes
|
|
19
|
+
until you accept it, one at a time or all at once. An issue is suggested as resolved only where Buddy
|
|
20
|
+
AI cites the code that shows it; the cited files are checked to exist, and the code is yours to read
|
|
21
|
+
before accepting. An unpushed or promised fix is never suggested as resolved, partial answers stay
|
|
22
|
+
open, a reply naming a different analysis of the review is refused, and an answer you accepted is
|
|
23
|
+
put back to you, not undone, if a later reply or new commits contradict it.
|
|
24
|
+
Extension 0.29.6 carries the shared changes; its sidebar does not offer reconciling yet.
|
|
25
|
+
|
|
3
26
|
## 0.85.0
|
|
4
27
|
|
|
5
28
|
**Replay the guided tour from any review.** A review's ⋯ menu now has Replay guided tour, which
|
|
@@ -4189,6 +4189,27 @@ function followUpFindingId(runId, id) {
|
|
|
4189
4189
|
}
|
|
4190
4190
|
//#endregion
|
|
4191
4191
|
//#region ../../packages/review-harness/src/domain/ai_review/reassessment.ts
|
|
4192
|
+
var ISSUE_VERDICTS = [
|
|
4193
|
+
"resolved",
|
|
4194
|
+
"needs-investigation",
|
|
4195
|
+
"open"
|
|
4196
|
+
];
|
|
4197
|
+
var QUESTION_VERDICTS = [
|
|
4198
|
+
"answered",
|
|
4199
|
+
"partially-answered",
|
|
4200
|
+
"unanswered"
|
|
4201
|
+
];
|
|
4202
|
+
function isVerdictFor(kind, value) {
|
|
4203
|
+
return (kind === "issue" ? ISSUE_VERDICTS : QUESTION_VERDICTS).includes(value);
|
|
4204
|
+
}
|
|
4205
|
+
/** Whether an accepted verdict closes the finding. Every other verdict leaves it outstanding. */
|
|
4206
|
+
function closesFinding(verdict) {
|
|
4207
|
+
return verdict === "resolved" || verdict === "answered";
|
|
4208
|
+
}
|
|
4209
|
+
/** The verdicts that only take an earlier one back. With none accepted, the finding is already there. */
|
|
4210
|
+
function isReopening(verdict) {
|
|
4211
|
+
return verdict === "open" || verdict === "unanswered";
|
|
4212
|
+
}
|
|
4192
4213
|
/** The same analysis revision: a reconciliation or a re-analysis changes both. */
|
|
4193
4214
|
function sameAnalysis(a, b) {
|
|
4194
4215
|
return a.commitSha === b.commitSha && a.generatedAt === b.generatedAt;
|
|
@@ -4210,7 +4231,7 @@ function inDecisionOrder(entries) {
|
|
|
4210
4231
|
* answered has been made since, by somebody, about a review that has moved on.
|
|
4211
4232
|
*/
|
|
4212
4233
|
function superseded(proposal, accepted) {
|
|
4213
|
-
return accepted.some((other) => other.entry !== proposal.entry && (decidedAt(other.entry) > proposal.entry.proposedAt || decidedAt(other.entry) === proposal.entry.proposedAt && other.at > proposal.at));
|
|
4234
|
+
return accepted.some((other) => other.entry !== proposal.entry && other.entry.axis === proposal.entry.axis && (decidedAt(other.entry) > proposal.entry.proposedAt || decidedAt(other.entry) === proposal.entry.proposedAt && other.at > proposal.at));
|
|
4214
4235
|
}
|
|
4215
4236
|
/** The scale a kind is graded on, most serious first. */
|
|
4216
4237
|
function gradeScale(kind) {
|
|
@@ -4229,6 +4250,20 @@ function entriesFor(ledger, kind, findingId) {
|
|
|
4229
4250
|
at
|
|
4230
4251
|
}] : []);
|
|
4231
4252
|
}
|
|
4253
|
+
/** A finding's grade entries, each with its place in the whole ledger. */
|
|
4254
|
+
function gradeEntriesFor(ledger, kind, findingId) {
|
|
4255
|
+
return entriesFor(ledger, kind, findingId).flatMap(({ entry, at }) => entry.axis === void 0 ? [{
|
|
4256
|
+
entry,
|
|
4257
|
+
at
|
|
4258
|
+
}] : []);
|
|
4259
|
+
}
|
|
4260
|
+
/** A finding's status entries, each with its place in the whole ledger. */
|
|
4261
|
+
function statusEntriesFor(ledger, kind, findingId) {
|
|
4262
|
+
return entriesFor(ledger, kind, findingId).flatMap(({ entry, at }) => entry.axis === "status" ? [{
|
|
4263
|
+
entry,
|
|
4264
|
+
at
|
|
4265
|
+
}] : []);
|
|
4266
|
+
}
|
|
4232
4267
|
/**
|
|
4233
4268
|
* The legacy step, when an investigation revised an issue's severity before the ledger existed.
|
|
4234
4269
|
* `priorSeverity` is only ever set on a real change (see `applyReassessment`), and checked again.
|
|
@@ -4254,7 +4289,7 @@ function legacyStep(kind, finding) {
|
|
|
4254
4289
|
* proposal at all: a proposal is evidence about one revision of the code, never about "whichever".
|
|
4255
4290
|
*/
|
|
4256
4291
|
function findingAssessment(ledger, kind, finding, analysis) {
|
|
4257
|
-
const entries =
|
|
4292
|
+
const entries = gradeEntriesFor(ledger, kind, finding.id);
|
|
4258
4293
|
const accepted = inDecisionOrder(entries.filter(({ entry }) => entry.state === "accepted"));
|
|
4259
4294
|
const legacy = legacyStep(kind, finding);
|
|
4260
4295
|
const history = [...legacy ? [legacy] : [], ...accepted.map(({ entry }) => ({
|
|
@@ -4275,6 +4310,50 @@ function findingAssessment(ledger, kind, finding, analysis) {
|
|
|
4275
4310
|
pending: analysis === null ? null : [...entries].reverse().find((candidate) => candidate.entry.state === "proposed" && candidate.entry.from === current && candidate.entry.to !== current && sameAnalysis(candidate.entry.madeAgainst, analysis) && !superseded(candidate, accepted))?.entry ?? null
|
|
4276
4311
|
};
|
|
4277
4312
|
}
|
|
4313
|
+
/**
|
|
4314
|
+
* The status projection, by the grade projection's rules: current is the move accepted last, by
|
|
4315
|
+
* when it was decided; pending is the newest undecided proposal written against the current verdict
|
|
4316
|
+
* and `analysis`, and not overtaken by a status decision since. A grade decision never overtakes it.
|
|
4317
|
+
*/
|
|
4318
|
+
function findingStatus(ledger, kind, findingId, analysis) {
|
|
4319
|
+
const entries = statusEntriesFor(ledger, kind, findingId);
|
|
4320
|
+
const accepted = inDecisionOrder(entries.filter(({ entry }) => entry.state === "accepted"));
|
|
4321
|
+
const history = accepted.map(({ entry }) => ({
|
|
4322
|
+
from: entry.from,
|
|
4323
|
+
to: entry.to,
|
|
4324
|
+
source: entry.source,
|
|
4325
|
+
...entry.reason ? { reason: entry.reason } : {},
|
|
4326
|
+
...entry.answer ? { answer: entry.answer } : {},
|
|
4327
|
+
...entry.quotes?.length ? { quotes: entry.quotes } : {},
|
|
4328
|
+
...entry.basis ? { basis: entry.basis } : {},
|
|
4329
|
+
...entry.evidence?.length ? { evidence: entry.evidence } : {},
|
|
4330
|
+
at: decidedAt(entry)
|
|
4331
|
+
}));
|
|
4332
|
+
const last = accepted.length ? accepted[accepted.length - 1].entry : null;
|
|
4333
|
+
const current = last?.to ?? null;
|
|
4334
|
+
return {
|
|
4335
|
+
kind,
|
|
4336
|
+
current,
|
|
4337
|
+
history,
|
|
4338
|
+
pending: analysis === null ? null : [...entries].reverse().find((candidate) => candidate.entry.state === "proposed" && candidate.entry.from === current && candidate.entry.to !== current && sameAnalysis(candidate.entry.madeAgainst, analysis) && !superseded(candidate, accepted))?.entry ?? null,
|
|
4339
|
+
currentEntryId: last?.id ?? null
|
|
4340
|
+
};
|
|
4341
|
+
}
|
|
4342
|
+
function statusLookup(session) {
|
|
4343
|
+
const analysis = {
|
|
4344
|
+
commitSha: session.metadata.commitSha,
|
|
4345
|
+
generatedAt: session.metadata.generatedAt
|
|
4346
|
+
};
|
|
4347
|
+
return (kind, findingId) => findingStatus(session.findingReassessments, kind, findingId, analysis);
|
|
4348
|
+
}
|
|
4349
|
+
/** For a surface with no session: nothing accepted, nothing waiting. */
|
|
4350
|
+
var NO_STATUS = (kind) => ({
|
|
4351
|
+
kind,
|
|
4352
|
+
current: null,
|
|
4353
|
+
history: [],
|
|
4354
|
+
pending: null,
|
|
4355
|
+
currentEntryId: null
|
|
4356
|
+
});
|
|
4278
4357
|
function assessmentLookup(session) {
|
|
4279
4358
|
const analysis = {
|
|
4280
4359
|
commitSha: session.metadata.commitSha,
|
|
@@ -4396,7 +4475,7 @@ function decideProposal(ledger, proposalId, decision, currentOf, analysis, at) {
|
|
|
4396
4475
|
ok: false,
|
|
4397
4476
|
refusal: "outdated"
|
|
4398
4477
|
};
|
|
4399
|
-
const current = currentOf(proposal
|
|
4478
|
+
const current = currentOf(proposal);
|
|
4400
4479
|
if (current === void 0 || proposal.from !== current) return {
|
|
4401
4480
|
ok: false,
|
|
4402
4481
|
refusal: "stale"
|
|
@@ -4429,6 +4508,7 @@ function sourcePhrase(source) {
|
|
|
4429
4508
|
case "investigation":
|
|
4430
4509
|
case "legacy-investigation": return "after a repository investigation";
|
|
4431
4510
|
case "update": return "after the new commits";
|
|
4511
|
+
case "buddy-conversation": return "after the author’s reply";
|
|
4432
4512
|
}
|
|
4433
4513
|
}
|
|
4434
4514
|
/**
|
|
@@ -4471,6 +4551,18 @@ function proposalsOnCurrentAnalysis(session, sourceKind) {
|
|
|
4471
4551
|
const analysis = analysisRefOf(session);
|
|
4472
4552
|
return ledger.filter((entry) => entry.state === "proposed" && entry.source.kind === sourceKind && entry.madeAgainst.commitSha === analysis.commitSha && entry.madeAgainst.generatedAt === analysis.generatedAt);
|
|
4473
4553
|
}
|
|
4554
|
+
/**
|
|
4555
|
+
* Whether a card offers this entry now: it is the pending proposal of its finding on its own axis, by
|
|
4556
|
+
* the projection's rules. A proposal still marked `proposed` may be outdated or overtaken, and is then
|
|
4557
|
+
* offered to nobody.
|
|
4558
|
+
*/
|
|
4559
|
+
function isOffered(session, entry) {
|
|
4560
|
+
if (entry.state !== "proposed") return false;
|
|
4561
|
+
const analysis = analysisRefOf(session);
|
|
4562
|
+
if (entry.axis === "status") return findingStatus(session.findingReassessments, entry.kind, entry.findingId, analysis).pending?.id === entry.id;
|
|
4563
|
+
const finding = findingIn(session, entry.kind, entry.findingId);
|
|
4564
|
+
return finding !== null && findingAssessment(session.findingReassessments, entry.kind, finding, analysis).pending?.id === entry.id;
|
|
4565
|
+
}
|
|
4474
4566
|
/** The analysis this session currently holds, as an entry records it. */
|
|
4475
4567
|
function analysisRefOf(session) {
|
|
4476
4568
|
return {
|
|
@@ -4502,6 +4594,66 @@ function currentGradeIn(session, kind, findingId) {
|
|
|
4502
4594
|
const finding = findingIn(session, kind, findingId);
|
|
4503
4595
|
return finding ? findingAssessment(session.findingReassessments, kind, finding, analysisRefOf(session)).current : void 0;
|
|
4504
4596
|
}
|
|
4597
|
+
/** The finding's accepted verdict, or undefined when the analysis no longer has the finding. */
|
|
4598
|
+
function currentStatusIn(session, kind, findingId) {
|
|
4599
|
+
return findingIn(session, kind, findingId) ? findingStatus(session.findingReassessments, kind, findingId, analysisRefOf(session)).current : void 0;
|
|
4600
|
+
}
|
|
4601
|
+
/** The value an entry was written against, on its own axis, as the session holds it now. */
|
|
4602
|
+
function currentOnAxis(session, entry) {
|
|
4603
|
+
return entry.axis === "status" ? currentStatusIn(session, entry.kind, entry.findingId) : currentGradeIn(session, entry.kind, entry.findingId);
|
|
4604
|
+
}
|
|
4605
|
+
/**
|
|
4606
|
+
* Checks a status proposal against the verdict the finding has now, and writes it as `proposed`.
|
|
4607
|
+
*
|
|
4608
|
+
* Refused as `stale` when written against another verdict, `unchanged` when it is the verdict the
|
|
4609
|
+
* finding already has (and a reopening verdict is that already when nothing was accepted), and
|
|
4610
|
+
* `no-reason` without a reason. One that reverses an accepted closure records which entry it would
|
|
4611
|
+
* take back, so the card puts the contradiction to the reviewer.
|
|
4612
|
+
*/
|
|
4613
|
+
function proposeStatus(ledger, input, status) {
|
|
4614
|
+
if (!isVerdictFor(input.kind, input.to)) return {
|
|
4615
|
+
ok: false,
|
|
4616
|
+
refusal: "not-a-grade"
|
|
4617
|
+
};
|
|
4618
|
+
if (input.from !== status.current) return {
|
|
4619
|
+
ok: false,
|
|
4620
|
+
refusal: "stale"
|
|
4621
|
+
};
|
|
4622
|
+
if (input.to === status.current || status.current === null && isReopening(input.to)) return {
|
|
4623
|
+
ok: false,
|
|
4624
|
+
refusal: "unchanged"
|
|
4625
|
+
};
|
|
4626
|
+
const reason = cleanReason(input.reason);
|
|
4627
|
+
if (!reason) return {
|
|
4628
|
+
ok: false,
|
|
4629
|
+
refusal: "no-reason"
|
|
4630
|
+
};
|
|
4631
|
+
const answer = cleanReason(input.answer);
|
|
4632
|
+
const contradicts = closesFinding(status.current) && !closesFinding(input.to) ? status.currentEntryId : null;
|
|
4633
|
+
const entry = {
|
|
4634
|
+
axis: "status",
|
|
4635
|
+
id: nextReassessmentId(input.at),
|
|
4636
|
+
kind: input.kind,
|
|
4637
|
+
findingId: input.findingId,
|
|
4638
|
+
from: input.from,
|
|
4639
|
+
to: input.to,
|
|
4640
|
+
reason,
|
|
4641
|
+
...answer ? { answer } : {},
|
|
4642
|
+
...input.quotes?.length ? { quotes: input.quotes } : {},
|
|
4643
|
+
...input.basis && input.to === "resolved" ? { basis: input.basis } : {},
|
|
4644
|
+
...input.evidence?.length ? { evidence: input.evidence } : {},
|
|
4645
|
+
...contradicts ? { contradicts } : {},
|
|
4646
|
+
source: input.source,
|
|
4647
|
+
state: "proposed",
|
|
4648
|
+
proposedAt: input.at,
|
|
4649
|
+
madeAgainst: input.madeAgainst
|
|
4650
|
+
};
|
|
4651
|
+
return {
|
|
4652
|
+
ok: true,
|
|
4653
|
+
ledger: [...ledger ?? [], entry],
|
|
4654
|
+
entry
|
|
4655
|
+
};
|
|
4656
|
+
}
|
|
4505
4657
|
/**
|
|
4506
4658
|
* One write onto a session, checked against that session as it is. Mutates `session` only when the
|
|
4507
4659
|
* write is accepted, so a caller holding a fresh copy inside a store write can call it there and
|
|
@@ -4509,7 +4661,25 @@ function currentGradeIn(session, kind, findingId) {
|
|
|
4509
4661
|
*/
|
|
4510
4662
|
function reassessIn(session, request, at) {
|
|
4511
4663
|
if (request.op === "decide") {
|
|
4512
|
-
const write = decideProposal(session.findingReassessments, request.proposalId, request.decision, (
|
|
4664
|
+
const write = decideProposal(session.findingReassessments, request.proposalId, request.decision, (proposal) => currentOnAxis(session, proposal), analysisRefOf(session), at);
|
|
4665
|
+
if (!write.ok) return write;
|
|
4666
|
+
session.findingReassessments = write.ledger;
|
|
4667
|
+
return {
|
|
4668
|
+
ok: true,
|
|
4669
|
+
entry: write.entry
|
|
4670
|
+
};
|
|
4671
|
+
}
|
|
4672
|
+
if (request.op === "propose-status") {
|
|
4673
|
+
if (!findingIn(session, request.kind, request.findingId)) return {
|
|
4674
|
+
ok: false,
|
|
4675
|
+
refusal: "no-finding"
|
|
4676
|
+
};
|
|
4677
|
+
const { op: _op, ...proposal } = request;
|
|
4678
|
+
const write = proposeStatus(session.findingReassessments, {
|
|
4679
|
+
...proposal,
|
|
4680
|
+
madeAgainst: analysisRefOf(session),
|
|
4681
|
+
at
|
|
4682
|
+
}, findingStatus(session.findingReassessments, request.kind, request.findingId, analysisRefOf(session)));
|
|
4513
4683
|
if (!write.ok) return write;
|
|
4514
4684
|
session.findingReassessments = write.ledger;
|
|
4515
4685
|
return {
|
|
@@ -5225,9 +5395,28 @@ function inferLanguage(files) {
|
|
|
5225
5395
|
}
|
|
5226
5396
|
//#endregion
|
|
5227
5397
|
//#region ../../packages/review-harness/src/domain/ai_review/outstanding.ts
|
|
5398
|
+
/**
|
|
5399
|
+
* Whether the reviewer has accepted a closing verdict for a finding. Reads the ledger's status axis
|
|
5400
|
+
* through its one projection, so a card, a count and a prompt cannot disagree about it.
|
|
5401
|
+
*/
|
|
5402
|
+
function verdictClosure(session) {
|
|
5403
|
+
const ledger = session.findingReassessments;
|
|
5404
|
+
if (!ledger?.some((entry) => entry.axis === "status" && entry.state === "accepted")) return NO_VERDICTS;
|
|
5405
|
+
const analysis = session.metadata ?? null;
|
|
5406
|
+
return {
|
|
5407
|
+
issue: (id) => closesFinding(findingStatus(ledger, "issue", id, analysis).current),
|
|
5408
|
+
question: (id) => closesFinding(findingStatus(ledger, "question", id, analysis).current)
|
|
5409
|
+
};
|
|
5410
|
+
}
|
|
5411
|
+
var NO_VERDICTS = {
|
|
5412
|
+
issue: () => false,
|
|
5413
|
+
question: () => false
|
|
5414
|
+
};
|
|
5228
5415
|
function reviewerDispositions(session) {
|
|
5229
5416
|
const byId = new Map((session.dismissedIssues ?? []).map((entry) => [entry.id, entry]));
|
|
5417
|
+
const verdicts = verdictClosure(session);
|
|
5230
5418
|
return {
|
|
5419
|
+
isClosedByVerdict: (issue) => verdicts.issue(issue.id),
|
|
5231
5420
|
isDismissed: (issue) => byId.has(issue.id),
|
|
5232
5421
|
isStale: (issue) => issue.reconciliationStatus === "affected" && (byId.get(issue.id)?.sessionId ?? session.id) !== session.id
|
|
5233
5422
|
};
|
|
@@ -5244,6 +5433,7 @@ function reviewerDispositions(session) {
|
|
|
5244
5433
|
*/
|
|
5245
5434
|
function isIssueOutstanding(issue, dispositions) {
|
|
5246
5435
|
if (issue.reconciliationStatus === "resolved") return false;
|
|
5436
|
+
if (dispositions.isClosedByVerdict(issue)) return false;
|
|
5247
5437
|
if (dispositions.isDismissed(issue)) return dispositions.isStale(issue);
|
|
5248
5438
|
return true;
|
|
5249
5439
|
}
|
|
@@ -5259,8 +5449,8 @@ function isIssueOutstanding(issue, dispositions) {
|
|
|
5259
5449
|
* dismissal is a bet on that code; a question the reviewer has answered was answered out of their
|
|
5260
5450
|
* own head, and a commit somewhere near it does not un-know what they now know.
|
|
5261
5451
|
*/
|
|
5262
|
-
function isQuestionOutstanding(question, answered) {
|
|
5263
|
-
return !answered.has(question.id) && question.reconciliationStatus !== "answered";
|
|
5452
|
+
function isQuestionOutstanding(question, answered, closedByVerdict) {
|
|
5453
|
+
return !answered.has(question.id) && question.reconciliationStatus !== "answered" && !closedByVerdict(question.id);
|
|
5264
5454
|
}
|
|
5265
5455
|
function tally(items, outstanding, byReviewer) {
|
|
5266
5456
|
let open = 0;
|
|
@@ -5275,19 +5465,21 @@ function tally(items, outstanding, byReviewer) {
|
|
|
5275
5465
|
};
|
|
5276
5466
|
}
|
|
5277
5467
|
function issuesOutstanding(issues, dispositions) {
|
|
5278
|
-
const counts = tally(issues, (issue) => isIssueOutstanding(issue, dispositions), (issue) => issue.reconciliationStatus !== "resolved" && dispositions.isDismissed(issue));
|
|
5468
|
+
const counts = tally(issues, (issue) => isIssueOutstanding(issue, dispositions), (issue) => issue.reconciliationStatus !== "resolved" && (dispositions.isClosedByVerdict(issue) || dispositions.isDismissed(issue)));
|
|
5279
5469
|
const byConversation = issues.filter(resolvedByConversation).length;
|
|
5280
|
-
|
|
5470
|
+
const byVerdict = issues.filter((issue) => issue.reconciliationStatus !== "resolved" && dispositions.isClosedByVerdict(issue)).length;
|
|
5471
|
+
return {
|
|
5281
5472
|
...counts,
|
|
5282
|
-
byConversation
|
|
5283
|
-
|
|
5473
|
+
...byConversation > 0 ? { byConversation } : {},
|
|
5474
|
+
...byVerdict > 0 ? { byVerdict } : {}
|
|
5475
|
+
};
|
|
5284
5476
|
}
|
|
5285
5477
|
/** A finding whose resolution, as it stands, rests on the pull request conversation rather than a commit. */
|
|
5286
5478
|
function resolvedByConversation(issue) {
|
|
5287
5479
|
return issue.reconciliationStatus === "resolved" && issue.statusHistory?.at(-1)?.basis === "conversation";
|
|
5288
5480
|
}
|
|
5289
|
-
function questionsOutstanding(questions, answered) {
|
|
5290
|
-
return tally(questions, (question) => isQuestionOutstanding(question, answered), (question) => question.reconciliationStatus !== "answered" && answered.has(question.id));
|
|
5481
|
+
function questionsOutstanding(questions, answered, closedByVerdict) {
|
|
5482
|
+
return tally(questions, (question) => isQuestionOutstanding(question, answered, closedByVerdict), (question) => question.reconciliationStatus !== "answered" && (answered.has(question.id) || closedByVerdict(question.id)));
|
|
5291
5483
|
}
|
|
5292
5484
|
/**
|
|
5293
5485
|
* What to call the pile of findings nobody has to act on any more.
|
|
@@ -6589,7 +6781,7 @@ function summarise(workspace) {
|
|
|
6589
6781
|
issues: result?.issues.length ?? 0,
|
|
6590
6782
|
openIssues: issuesOutstanding(result?.issues ?? [], reviewerDispositions(workspace.session)).open,
|
|
6591
6783
|
questions: questions.length,
|
|
6592
|
-
unansweredQuestions: questionsOutstanding(questions, answered).open,
|
|
6784
|
+
unansweredQuestions: questionsOutstanding(questions, answered, verdictClosure(workspace.session).question).open,
|
|
6593
6785
|
messages: workspace.transcript.length,
|
|
6594
6786
|
...workspace.jobId && result ? { analysisJobId: workspace.jobId } : {},
|
|
6595
6787
|
...(() => {
|
|
@@ -18375,6 +18567,17 @@ function priorVerdict(item) {
|
|
|
18375
18567
|
return ` [previously: ${status}]`;
|
|
18376
18568
|
}
|
|
18377
18569
|
/**
|
|
18570
|
+
* A verdict the reviewer accepted from later evidence (an author's reply), said as their decision and
|
|
18571
|
+
* never as a settled status. Unlike `[previously: resolved]`, which code earned and which stays, this
|
|
18572
|
+
* is historical evidence: the round judges the finding like any open one, and `ACCEPTED_TASK` says
|
|
18573
|
+
* how to report new code that undoes it. Only a closing verdict is marked; an open one is no claim.
|
|
18574
|
+
*/
|
|
18575
|
+
function acceptedVerdict(status) {
|
|
18576
|
+
if (!closesFinding(status.current)) return "";
|
|
18577
|
+
const last = status.history[status.history.length - 1];
|
|
18578
|
+
return ` [the reviewer accepted "${status.current}" ${sourcePhrase(last.source)}${last.reason ? `: ${last.reason}` : ""}]`;
|
|
18579
|
+
}
|
|
18580
|
+
/**
|
|
18378
18581
|
* A finding a comment resolved says which comment, so the model can tell it from one the code
|
|
18379
18582
|
* resolved. And one whose comment has changed since says that too: it is the one settled finding
|
|
18380
18583
|
* the model is asked to judge again. Every other finding reads exactly as `priorVerdict` has it.
|
|
@@ -18391,13 +18594,13 @@ function priorFindingVerdict(issue, flag) {
|
|
|
18391
18594
|
* reviewer may have moved it, and a model told only the analysis's grade would re-judge from where
|
|
18392
18595
|
* the review no longer stands. A caller with no ledger passes `ANALYSIS_ONLY`.
|
|
18393
18596
|
*/
|
|
18394
|
-
function formatPriorFindings(issues, assess, reassess = /* @__PURE__ */ new Map()) {
|
|
18597
|
+
function formatPriorFindings(issues, assess, reassess = /* @__PURE__ */ new Map(), status = NO_STATUS) {
|
|
18395
18598
|
if (!issues.length) return "(The previous analysis raised no issues.)";
|
|
18396
18599
|
return issues.map((issue, index) => {
|
|
18397
18600
|
const related = (issue.relatedFiles ?? []).map((file) => ` [related: ${file}, not changed]`).join("");
|
|
18398
18601
|
const location = (issue.file ? ` [${issue.file}${issue.line ? `:${issue.line}` : ""}]` : "") + related;
|
|
18399
18602
|
const description = issue.description.trim();
|
|
18400
|
-
return `${index}. (${promptGrade(assess("issue", issue))})${location}${priorFindingVerdict(issue, reassess.get(index))} ${issue.title.trim()}${description ? `\n ${description}` : ""}`;
|
|
18603
|
+
return `${index}. (${promptGrade(assess("issue", issue))})${location}${priorFindingVerdict(issue, reassess.get(index))}${acceptedVerdict(status("issue", issue.id))} ${issue.title.trim()}${description ? `\n ${description}` : ""}`;
|
|
18401
18604
|
}).join("\n");
|
|
18402
18605
|
}
|
|
18403
18606
|
/**
|
|
@@ -18451,7 +18654,7 @@ function reconciliationShape(includeQuestions, withDiscussion = false) {
|
|
|
18451
18654
|
* question's own id is withheld so it cannot be echoed back in place of an index, and enough
|
|
18452
18655
|
* context travels for a judgement without inviting a rewrite of the question itself.
|
|
18453
18656
|
*/
|
|
18454
|
-
function formatPriorQuestions(questions, assess) {
|
|
18657
|
+
function formatPriorQuestions(questions, assess, status = NO_STATUS) {
|
|
18455
18658
|
if (!questions.length) return "(The previous analysis raised no questions.)";
|
|
18456
18659
|
return questions.map((question, index) => {
|
|
18457
18660
|
const related = (question.relatedFiles ?? []).map((file) => ` [related: ${file}, not changed]`).join("");
|
|
@@ -18459,7 +18662,8 @@ function formatPriorQuestions(questions, assess) {
|
|
|
18459
18662
|
const why = question.whyThisMatters?.trim();
|
|
18460
18663
|
const verdict = alreadyAnswered(question) ? " [previously: answered]" : priorVerdict(question);
|
|
18461
18664
|
const assessment = assess("question", question);
|
|
18462
|
-
|
|
18665
|
+
const importance = assessment.current ? ` [importance: ${promptGrade(assessment)}]` : "";
|
|
18666
|
+
return `${index}.${location}${verdict}${acceptedVerdict(status("question", question.id))}${importance} ${question.question.trim()}${why ? `\n ${why}` : ""}`;
|
|
18463
18667
|
}).join("\n");
|
|
18464
18668
|
}
|
|
18465
18669
|
/**
|
|
@@ -18498,6 +18702,13 @@ var REASSESS_TASK = ` The one exception: a finding marked "that comment has si
|
|
|
18498
18702
|
- "affected": the evidence changed and you cannot establish whether the finding is still valid. Use this, not "still-open", whenever you are unsure.
|
|
18499
18703
|
Keep it as this entry. Do not raise it again in "newIssues" unless the problem is now materially different.`;
|
|
18500
18704
|
/**
|
|
18705
|
+
* How to treat a finding the reviewer closed on later evidence: as open, because that evidence was a
|
|
18706
|
+
* claim about the code as it was, and new code can undo it. Stated only when such a finding exists.
|
|
18707
|
+
* "affected" is the word that puts it back to the reviewer; "still-open" is what nothing touching it
|
|
18708
|
+
* means, so an untouched finding never reaches them as a contradiction.
|
|
18709
|
+
*/
|
|
18710
|
+
var ACCEPTED_TASK = ` A finding or question marked [the reviewer accepted ...] was closed by the reviewer on later evidence, such as an author's reply, not by code. It is not settled: judge it like any open one. Where these changes undo or contradict what that evidence established, mark it "affected" and say in "note" what changed. Where nothing here bears on it, "still-open" is right, and it stays as the reviewer left it.`;
|
|
18711
|
+
/**
|
|
18501
18712
|
* The one optional judgement about a grade: a suggestion the reviewer answers, separate from the
|
|
18502
18713
|
* status, reasoned from the grade the review holds now (each list states it), and only where the
|
|
18503
18714
|
* new changes move it. Unasked for, a model restates every grade it was shown, and each would reach
|
|
@@ -18527,12 +18738,13 @@ function buildReconciliationInstructions(input, options = {}) {
|
|
|
18527
18738
|
const { previous, totalCommits, changedFiles, possiblyTruncated } = input;
|
|
18528
18739
|
const reassess = input.reassess ?? /* @__PURE__ */ new Map();
|
|
18529
18740
|
const includeQuestions = options.includeQuestions === true;
|
|
18741
|
+
const hasAccepted = previous.issues.some((issue) => closesFinding(input.status("issue", issue.id).current)) || includeQuestions && previous.questions.some((question) => closesFinding(input.status("question", question.id).current));
|
|
18530
18742
|
const commitCount = totalCommits === 1 ? "1 new commit" : `${totalCommits} new commits`;
|
|
18531
18743
|
const fileCount = changedFiles === 1 ? "1 file" : `${changedFiles} files`;
|
|
18532
18744
|
const discussion = options.discussion;
|
|
18533
18745
|
const conversationOnly = discussion !== void 0 && !discussion.codeChanged;
|
|
18534
18746
|
const truncationWarning = possiblyTruncated ? `\n\nIMPORTANT: the list of changes above is INCOMPLETE — more files changed than could be retrieved. Do not mark a previous finding "resolved" on the strength of absence: a file you cannot see may or may not contain the fix. Prefer "still-open" or "affected" where you cannot see the relevant code, and say so in the note.` : "";
|
|
18535
|
-
const questionList = includeQuestions ? `\n\nPrevious open questions, numbered — refer to each by its number in "sourceIndex":\n${formatPriorQuestions(previous.questions, input.assess)}` : "";
|
|
18747
|
+
const questionList = includeQuestions ? `\n\nPrevious open questions, numbered — refer to each by its number in "sourceIndex":\n${formatPriorQuestions(previous.questions, input.assess, input.status)}` : "";
|
|
18536
18748
|
const tasks = [
|
|
18537
18749
|
`${discussion ? `For EVERY numbered finding above, decide what ${conversationOnly ? "the changes in the conversation" : "the new commits and the changes in the conversation"} did to it:
|
|
18538
18750
|
- "resolved": ${conversationOnly ? "" : "the new commits fix it, and you can point at the change that does so; or "}the conversation gives evidence that invalidates its premise, under the rules below.
|
|
@@ -18542,7 +18754,8 @@ function buildReconciliationInstructions(input, options = {}) {
|
|
|
18542
18754
|
- "still-open" — the new changes do not address it. This includes findings the changes never touch.
|
|
18543
18755
|
- "affected" — the code it concerns changed, but not in a way that clearly settles it either way. The reviewer needs to look again.`}
|
|
18544
18756
|
You MUST return one entry per numbered finding. If you are unsure, return "still-open" or "affected" with a note — do NOT omit the finding. Omitting a finding does not make it go away; it will be reported as still open regardless, without your reasoning attached.
|
|
18545
|
-
A finding marked [previously: resolved] was already fixed by an earlier commit on this branch, which is code you are NOT being shown a diff of. It stays resolved, and "still-open" is not an available answer for it — these changes not being where the fix was says nothing about whether it holds. If you can see that later code has undone that fix, report it in "newIssues" with what you saw; do not reopen the old entry.${
|
|
18757
|
+
A finding marked [previously: resolved] was already fixed by an earlier commit on this branch, which is code you are NOT being shown a diff of. It stays resolved, and "still-open" is not an available answer for it — these changes not being where the fix was says nothing about whether it holds. If you can see that later code has undone that fix, report it in "newIssues" with what you saw; do not reopen the old entry.${hasAccepted ? `
|
|
18758
|
+
${ACCEPTED_TASK}` : ""}${reassess.size > 0 ? `
|
|
18546
18759
|
${REASSESS_TASK}` : ""}
|
|
18547
18760
|
${GRADE_TASK.issue}`,
|
|
18548
18761
|
...includeQuestions ? [`For EVERY numbered question above, decide what the new changes did to it:
|
|
@@ -18569,7 +18782,7 @@ Overall risk: ${previous.overallRisk}
|
|
|
18569
18782
|
Recommendation: ${previous.recommendation}
|
|
18570
18783
|
|
|
18571
18784
|
Previous findings, numbered — refer to each by its number in "sourceIndex":
|
|
18572
|
-
${formatPriorFindings(previous.issues, input.assess, reassess)}${questionList}${truncationWarning}
|
|
18785
|
+
${formatPriorFindings(previous.issues, input.assess, reassess, input.status)}${questionList}${truncationWarning}
|
|
18573
18786
|
${discussion ? `\n${DISCUSSION_RULES}\n` : ""}
|
|
18574
18787
|
YOUR TASK
|
|
18575
18788
|
|
|
@@ -18629,6 +18842,29 @@ function reconciliationGradeSuggestions(previous, reconciled, assess) {
|
|
|
18629
18842
|
}
|
|
18630
18843
|
return suggestions;
|
|
18631
18844
|
}
|
|
18845
|
+
function verdictChallenges(previous, reconciled, status) {
|
|
18846
|
+
const challenges = [];
|
|
18847
|
+
const consider = (kind, findingId, verdict, note) => {
|
|
18848
|
+
const current = status(kind, findingId).current;
|
|
18849
|
+
if (!closesFinding(current) || verdict !== "affected") return;
|
|
18850
|
+
challenges.push({
|
|
18851
|
+
kind,
|
|
18852
|
+
findingId,
|
|
18853
|
+
from: current,
|
|
18854
|
+
to: kind === "issue" ? "open" : "unanswered",
|
|
18855
|
+
reason: note?.trim() || "The new changes touch what the accepted answer rested on."
|
|
18856
|
+
});
|
|
18857
|
+
};
|
|
18858
|
+
for (const finding of reconciled.findings) {
|
|
18859
|
+
const issue = previous.issues[finding.sourceIndex];
|
|
18860
|
+
if (issue) consider("issue", issue.id, finding.status, finding.note);
|
|
18861
|
+
}
|
|
18862
|
+
for (const entry of reconciled.questions) {
|
|
18863
|
+
const question = previous.questions[entry.sourceIndex];
|
|
18864
|
+
if (question) consider("question", question.id, entry.status, entry.note);
|
|
18865
|
+
}
|
|
18866
|
+
return challenges;
|
|
18867
|
+
}
|
|
18632
18868
|
/** A citation as the reply gave it: a comment by thread and comment id, or a review by its id. Anything else is no citation. */
|
|
18633
18869
|
function parseCitation(value) {
|
|
18634
18870
|
if (typeof value !== "object" || value === null) return void 0;
|
|
@@ -19411,6 +19647,7 @@ async function reconcile(workspace, runners, reviewedSha, remote, conversationOk
|
|
|
19411
19647
|
const parsed = parseReconciliationResult(raw, previous.issues.length, { priorQuestionCount: previous.questions.length });
|
|
19412
19648
|
if (!parsed) return settled(reviewedSha, newHead, remote.fetchFailed, conversationOk, "analysis-failed", "The reassessment came back in a form this could not read, so nothing was changed.");
|
|
19413
19649
|
const motivation = carryMotivation(previous.motivation, workspace.session.metadata?.authorDescription, workspace.prContext?.pullRequest.body);
|
|
19650
|
+
const challenges = verdictChallenges(previous, parsed, statusLookup(workspace.session));
|
|
19414
19651
|
const { result, suggestions } = applyReconciliation(previous, parsed, {
|
|
19415
19652
|
at: clock.now(),
|
|
19416
19653
|
commitSha: newHead,
|
|
@@ -19421,14 +19658,15 @@ async function reconcile(workspace, runners, reviewedSha, remote, conversationOk
|
|
|
19421
19658
|
});
|
|
19422
19659
|
signal.throwIfAborted();
|
|
19423
19660
|
cancellers.delete(jobId);
|
|
19424
|
-
const gradeChanges = await applyUpdate(workspace, result, previous, newHead, worktreePath, {
|
|
19661
|
+
const { gradeChanges, decisionsChallenged } = await applyUpdate(workspace, result, previous, newHead, worktreePath, {
|
|
19425
19662
|
...requestedModel ? { requestedModel } : {},
|
|
19426
19663
|
...observedModel ? { observedModel } : {},
|
|
19427
19664
|
...await (runners.agentVersion ?? installedAgentVersion)(env)
|
|
19428
|
-
}, clock, remote.base, suggestions, conversationTakenIntoAccount(prepared, analysedBaselineOf(workspace)));
|
|
19665
|
+
}, clock, remote.base, suggestions, conversationTakenIntoAccount(prepared, analysedBaselineOf(workspace)), challenges);
|
|
19429
19666
|
const counts = {
|
|
19430
19667
|
...countUpdateChanges(previous, result),
|
|
19431
|
-
...gradeChanges ? { gradeChanges } : {}
|
|
19668
|
+
...gradeChanges ? { gradeChanges } : {},
|
|
19669
|
+
...decisionsChallenged ? { decisionsChallenged } : {}
|
|
19432
19670
|
};
|
|
19433
19671
|
return {
|
|
19434
19672
|
at: Date.now(),
|
|
@@ -19635,24 +19873,40 @@ function carryUpdateSuggestions(merged, updated) {
|
|
|
19635
19873
|
const onDisk = new Set((merged.session.findingReassessments ?? []).map((entry) => entry.id));
|
|
19636
19874
|
for (const entry of made.filter((entry) => !onDisk.has(entry.id))) {
|
|
19637
19875
|
const source = entry.source;
|
|
19638
|
-
|
|
19639
|
-
|
|
19640
|
-
|
|
19641
|
-
|
|
19642
|
-
|
|
19643
|
-
|
|
19644
|
-
|
|
19645
|
-
|
|
19876
|
+
let written;
|
|
19877
|
+
if (entry.axis === "status") {
|
|
19878
|
+
const { id: _id, axis: _axis, state: _state, proposedAt: _at, madeAgainst: _against, contradicts: _c, decidedAt: _d, ...proposal } = entry;
|
|
19879
|
+
const write = reassessIn(merged.session, {
|
|
19880
|
+
op: "propose-status",
|
|
19881
|
+
...proposal,
|
|
19882
|
+
reason: entry.reason ?? "",
|
|
19883
|
+
source
|
|
19884
|
+
}, entry.proposedAt);
|
|
19885
|
+
if (write.ok) written = write.entry;
|
|
19886
|
+
} else {
|
|
19887
|
+
const suggestion = {
|
|
19888
|
+
kind: entry.kind,
|
|
19889
|
+
findingId: entry.findingId,
|
|
19890
|
+
from: entry.from,
|
|
19891
|
+
to: entry.to,
|
|
19892
|
+
reason: entry.reason ?? ""
|
|
19893
|
+
};
|
|
19894
|
+
[written] = proposeGradeSuggestions(merged.session, [suggestion], source, entry.proposedAt);
|
|
19895
|
+
}
|
|
19646
19896
|
if (written) written.id = entry.id;
|
|
19647
19897
|
}
|
|
19648
19898
|
const ours = new Set(made.map((entry) => entry.id));
|
|
19649
|
-
const
|
|
19899
|
+
const keptEntries = proposalsOnCurrentAnalysis(merged.session, "update").filter((entry) => ours.has(entry.id));
|
|
19900
|
+
const kept = keptEntries.filter((entry) => entry.axis !== "status").length;
|
|
19901
|
+
const challenged = keptEntries.length - kept;
|
|
19650
19902
|
const outcome = merged.lastUpdate;
|
|
19651
|
-
if (!outcome?.counts ||
|
|
19652
|
-
|
|
19903
|
+
if (!outcome?.counts || !outcome.toSha) return;
|
|
19904
|
+
if ((outcome.counts.gradeChanges ?? 0) === kept && (outcome.counts.decisionsChallenged ?? 0) === challenged) return;
|
|
19905
|
+
const { gradeChanges: _, decisionsChallenged: __, ...rest } = outcome.counts;
|
|
19653
19906
|
const counts = {
|
|
19654
19907
|
...rest,
|
|
19655
|
-
...kept ? { gradeChanges: kept } : {}
|
|
19908
|
+
...kept ? { gradeChanges: kept } : {},
|
|
19909
|
+
...challenged ? { decisionsChallenged: challenged } : {}
|
|
19656
19910
|
};
|
|
19657
19911
|
merged.lastUpdate = {
|
|
19658
19912
|
...outcome,
|
|
@@ -19668,7 +19922,7 @@ function carryUpdateSuggestions(merged, updated) {
|
|
|
19668
19922
|
* to browse the review's past. There is no restore button yet, and that is fine. Insurance that is
|
|
19669
19923
|
* only written once somebody has already built the claim form is not insurance.
|
|
19670
19924
|
*/
|
|
19671
|
-
async function applyUpdate(workspace, result, replaced, newHead, worktreePath, run, clock, freshBase, suggestions = [], conversation) {
|
|
19925
|
+
async function applyUpdate(workspace, result, replaced, newHead, worktreePath, run, clock, freshBase, suggestions = [], conversation, challenges = []) {
|
|
19672
19926
|
workspace.previousResult = {
|
|
19673
19927
|
...workspace.analysedConversation ? { analysedConversation: workspace.analysedConversation } : {},
|
|
19674
19928
|
result: replaced,
|
|
@@ -19707,10 +19961,18 @@ async function applyUpdate(workspace, result, replaced, newHead, worktreePath, r
|
|
|
19707
19961
|
sessionId: workspace.session.id,
|
|
19708
19962
|
discussion: conversation ?? null
|
|
19709
19963
|
};
|
|
19710
|
-
|
|
19964
|
+
const source = {
|
|
19711
19965
|
kind: "update",
|
|
19712
19966
|
commitSha: newHead
|
|
19713
|
-
}
|
|
19967
|
+
};
|
|
19968
|
+
return {
|
|
19969
|
+
gradeChanges: proposeGradeSuggestions(workspace.session, suggestions, source, Date.now()).length,
|
|
19970
|
+
decisionsChallenged: challenges.filter((challenge) => reassessIn(workspace.session, {
|
|
19971
|
+
op: "propose-status",
|
|
19972
|
+
...challenge,
|
|
19973
|
+
source
|
|
19974
|
+
}, Date.now()).ok).length
|
|
19975
|
+
};
|
|
19714
19976
|
}
|
|
19715
19977
|
/**
|
|
19716
19978
|
* What the review is of, which the rebuild above is not entitled to answer.
|
|
@@ -19850,7 +20112,7 @@ function describeUpdate(counts, sha, basis = {
|
|
|
19850
20112
|
if (counts.affected) parts.push(`${counts.affected} to look at again`);
|
|
19851
20113
|
if (counts.questionsAnswered) parts.push(`${counts.questionsAnswered} question${counts.questionsAnswered === 1 ? "" : "s"} answered`);
|
|
19852
20114
|
if (counts.stillOpen && parts.length) parts.push(`${counts.stillOpen} still present`);
|
|
19853
|
-
const grades = counts.gradeChanges ? ` ${counts.gradeChanges} grade ${counts.gradeChanges === 1 ? "change" : "changes"} suggested.` : "";
|
|
20115
|
+
const grades = (counts.gradeChanges ? ` ${counts.gradeChanges} grade ${counts.gradeChanges === 1 ? "change" : "changes"} suggested.` : "") + (counts.decisionsChallenged ? ` ${counts.decisionsChallenged === 1 ? "1 answer you accepted is" : `${counts.decisionsChallenged} answers you accepted are`} put back to you.` : "");
|
|
19854
20116
|
if (!basis.code) {
|
|
19855
20117
|
const at = "Reassessed against the pull request conversation; no new commits";
|
|
19856
20118
|
if (!parts.length) return `${at}. The conversation changed nothing about the findings${stillPresent(counts)}.${grades}`;
|
|
@@ -19900,6 +20162,7 @@ ${conversationBlock}
|
|
|
19900
20162
|
${buildReconciliationInstructions({
|
|
19901
20163
|
previous,
|
|
19902
20164
|
assess: assessmentLookup(workspace.session),
|
|
20165
|
+
status: statusLookup(workspace.session),
|
|
19903
20166
|
fromSha: reviewedSha,
|
|
19904
20167
|
toSha: newHead,
|
|
19905
20168
|
totalCommits: commits,
|
|
@@ -20189,4 +20452,4 @@ function lineageIds(id) {
|
|
|
20189
20452
|
return [.../* @__PURE__ */ new Set([id, ...lineage.map((review) => review.id)])];
|
|
20190
20453
|
}
|
|
20191
20454
|
//#endregion
|
|
20192
|
-
export { MIN_WORKSPACE_PORT as $,
|
|
20455
|
+
export { MIN_WORKSPACE_PORT as $, AmbiguousRefError as $i, RUN_RECORD_RETENTION_MS as $n, deleteCollection as $r, markRead as $t, batchIdentity as A, findingAssessment as Ai, escapeHtml as An, positionInLineage as Ar, loadArchitecture as At, bootstrapUrl as B, wasReassessed as Bi, readMarker as Bn, LIFECYCLES as Br, ancestorsOf as Bt, RUN_KINDS as C, withExplanation as Ci, UNESTABLISHED_MOTIVATION as Cn, kissDecisionsOf as Cr, mapRuntimes as Ct, reportRun as D, assessmentLookup as Di, renderMarkdown as Dn, loadWorkspace as Dr, formatDuration as Dt, endRun as E, analysisRefOf as Ei, GITHUB_MARKDOWN as En, lineagePosition as Er, describeRuntimes as Et, activateQueued as F, isOffered as Fi, withReviewLock as Fn, reviewedRepositories as Fr, storedScopes as Ft, serverEntryExists as G, describeAuthorship as Gi, findExistingReview as Gn, lifecycleItemLabel as Gr, subjectOf as Gt, ensureServer as H, asString$1 as Hi, requestJobStop as Hn, activityLabel as Hr, writeJsonAtomically as Ht, compactQueue as I, promptGrade as Ii, MANAGED_ROOT as In, summarise as Ir, trackedFiles as It, stopServer as J, resolveTarget as Ji, followedBranch as Jn, parseLifecycle as Jr, createPrState as Jt, spawnServer as K, WITHHELD_REASON as Ki, checkCodeFreshness as Kn, lifecyclePill as Kr, categoryOf as Kt, queueForReview as L, reassessIn as Li, createWorktree as Ln, touchWorkspace as Lr, ownersOf as Lt, parsePullRequestLink as M, findingStatus as Mi, modelHelpLines as Mn, previousKissResult as Mr, readArchitecture as Mt, unsupportedReason as N, gradeScale as Ni, ReviewBeingDeletedError as Nn, recentWorkspaces as Nr, repositoryKey as Nt, stopRun as O, closesFinding as Oi, CODE_BLOCK_CLASS as On, matchingWorkspaceIds as Or, ARCHITECTURE_DIR as Ot, runBatch as P, isGradeFor as Pi, isBusy as Pn, reviewedCommit as Pr, storeArchitecture as Pt, MAX_WORKSPACE_PORT as Q, resolveBase as Qi, fillFileUrlTemplate as Qn, createWithMembers as Qr, markAllRead as Qt, settleQueue as R, sourcePhrase as Ri, holdWorktree as Rn, workspaceRevision as Rr, QUOTE_LIMITS as Rt, track as S, isExplanationAction as Si, chatConversationSection as Sn, kissDecisionRefusal as Sr, waitingHistory as St, completeRun as T, NO_STATUS as Ti, hasAuthorContent as Tn, lineageKey as Tr, RUNTIME_WORDS as Tt, readServerRecord as U, extractJsonText as Ui, describeMovement as Un, isLifecycle as Ur, announceJob as Ut, clearServerRecordOwnedBy as V, followUpFindingId as Vi, removeWorktree as Vn, activityAttributes as Vr, isArchitectureId as Vt, reviewsUrl as W, objectWithAnyOf as Wi, describeSubject as Wn, lifecycleChip as Wr, branchOf as Wt, writeServerRecord as X, DetachedHeadError as Xi, fetchHead as Xn, collectionsStamp as Xr, eventName as Xt, workspaceUrl as Y, originRemote as Yi, reviewedShaOf as Yn, sectionOf as Yr, createPrUpdate as Yt, DEFAULT_WORKSPACE_PORT as Z, isCommitReview as Zi, forgeResolver as Zn, createCollection as Zr, listNotifications as Zt, runningJobs as _, isAgentId as _a, verdictClosure as _i, unforeseenFailure as _n, decideKissRecommendation as _r, telemetryUploadConsent as _t, wasBlocked as a, resolveModel as aa, queueAdd as ai, removeNotification as an, loadJob as ar, observeReview as at, startKissJob as b, AgentCancelledError as ba, explanationActionOf as bi, agentEnvOf as bn, indexKeptIds as br, PACKAGE_NAME as bt, RefreshUnavailableError as c, readAgentPreference as ca, queueStamp as ci, unreadAround as cn, PHASES as cr, reanalyseReview as ct, mergeUpdateInto as d, STORE_ROOT as da, isIssueOutstanding as di, existingPaths as dn, phasesFor as dr, append as dt, shortNameOf as ea, nameProblemText as ei, markReadForReview as en, allJobs as er, parseWorkspacePort as et, parkedUpdateRefusal as f, AGENT_IDS as fa, isQuestionOutstanding as fi, ASK_REREAD_CEILING_MS as fn, progressSteps as fr, readEvents as ft, liveUpdateFor as g, detectAgents as ga, reviewerDispositions as gi, storedConversation as gn, CLAIM_LEASE_MS as gr, recordTelemetryUploadConsent as gt, UpdateAlreadyRunningError as h, agentIdentityOf as ha, resolvedByConversation as hi, saveRefresh as hn, isClaimed as hr, deliver as ht, liveJobsFor as i, resolveAgent as ia, updateCollections as ii, readStateOf as in, isTerminal as ir, failureOf as it, extractPullRequestLinks as j, findingIn as ji, renderInlineText as jn, presentedKissRunOf as jr, partFiles as jt, relativeTime as k, currentGradeIn as ki, CODE_COPY_CLASS as kn, mutateWorkspace as kr, checkArchitecture as kt, cancelUpdate as l, writeAgentPreference as la, readQueue as li, unreadCount as ln, isLiveJob as lr, runJob as lt, askCheckout as m, agentFor as ma, questionsOutstanding as mi, prepareConversationWithin as mn, clearClaim as mr, stamp as mt, deleteReview as n, git as na, renameCollection as ni, pruneNotifications as nn, endedAt as nr, statedWorkspacePort as nt, discardJob as o, agentIdOf as oa, queueAddMany as oi, retireNotification as on, pruneJobResidue as or, prepareReview as ot, updateReview as p, agentById as pa, issuesOutstanding as pi, adoptLegacyBaseline as pn, purposeOf as pr, record as pt, stopChild as q, kissOutcome as qi, checkReview as qn, offersLifecycle as qr, createArchitectureNotification as qt, lineageIds as r, refExists as ra, setMembership as ri, readNotification as rn, fail as rr, writePortPreference as rt, FETCH_TIMEOUT_MS as s, clearAgentPreference as sa, queueRemove as si, toneOf as sn, saveJob as sr, settleReview as st, deleteLineage as t, shorthandOf as ta, readCollections as ti, moveIds as tn, attemptIdOf as tr, resolveWorkspacePort as tt, cancellableHere as u, CONFIG_PATH as ua, doneVerb as ui, withUsageRecorded as un, isTerminalJobPhase as ur, startJob as ut, kissAgentId as v, parkedReason as va, processDiscussion as vi, refreshPrContext as vn, followedRefName as vr, telemetryUploadEnabled as vt, beginRun as w, ANALYSIS_ONLY as wi, authorPreview as wn, latestKissRun as wr, recordRuntimeBestEffort as wt, isDriving as x, AgentUnavailableError as xa, explanationOf as xi, EXPLAIN_SIMPLY_PROMPT as xn, indexReviewGroups as xr, feedbackUrl as xt, recordKissRun as y, runnableAgents as ya, recommendationsOf as yi, isUnchanged as yn, groupByLineage as yr, BUILD_VERSION as yt, readIndexToken as z, statusLookup as zi, isWorktreeHeld as zn, workspaceStamps as zr, REPOSITORY_SCOPE as zt };
|