limn-review 0.1.175 → 0.1.176
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/db/teammate-reviews.js +53 -1
- package/dist/review/claude-review/persist.js +61 -1
- package/dist/review/claude-review/routes.js +7 -2
- package/dist/review/claude-review/team-view.js +265 -0
- package/package.json +1 -1
- package/public/assets/{AiFixTab-RcpIGMNx.js → AiFixTab-D_r2JEy_.js} +1 -1
- package/public/assets/ClaudeReviewTab-ncqvtble.js +20 -0
- package/public/assets/{PrBotBehaviourTab-BQoWzCtq.js → PrBotBehaviourTab-Da_67MPm.js} +1 -1
- package/public/assets/index-DIq-VB3X.css +1 -0
- package/public/assets/index-DVYEmdnp.js +87 -0
- package/public/index.html +2 -2
- package/public/assets/ClaudeReviewTab-Bj5G53IE.js +0 -20
- package/public/assets/index-BUMVu-Jk.css +0 -1
- package/public/assets/index-DzvTPkIq.js +0 -87
|
@@ -6,6 +6,13 @@ import * as schema from './schema.sqlite.js';
|
|
|
6
6
|
const { users, reviewComments, commits, pullRequests, repos } = schema;
|
|
7
7
|
const cr = schema.claudeReviews;
|
|
8
8
|
const crf = schema.claudeReviewFindings;
|
|
9
|
+
// Limn's "_Posted automatically by Limn’s … review(s)._" footer closes an auto-posted summary on
|
|
10
|
+
// GitHub. In the app it is noise: the review is already labelled as the teammate's.
|
|
11
|
+
const AUTO_POST_FOOTER_RE = /\n*_Posted automatically by Limn[’'][^_\n]*_\s*$/;
|
|
12
|
+
/** A posted review summary without Limn's auto-post footer. Pure. */
|
|
13
|
+
export function withoutAutoPostFooter(summary) {
|
|
14
|
+
return summary.replace(AUTO_POST_FOOTER_RE, '').trimEnd();
|
|
15
|
+
}
|
|
9
16
|
/** The `claude_reviews.kind` of an imported teammate review. */
|
|
10
17
|
export const TEAMMATE_KIND = 'teammate';
|
|
11
18
|
/**
|
|
@@ -106,7 +113,7 @@ export async function listTeammateReviews(exec, accountId, prId) {
|
|
|
106
113
|
headInferred: rec?.headInferred ?? true,
|
|
107
114
|
postedAt: at.toISOString(),
|
|
108
115
|
githubUrl: rec?.githubReviewId ? `${prUrl}#pullrequestreview-${rec.githubReviewId}` : null,
|
|
109
|
-
summary: r.summary ?? '',
|
|
116
|
+
summary: withoutAutoPostFooter(r.summary ?? ''),
|
|
110
117
|
verdict: r.verdict,
|
|
111
118
|
run: rec?.run ?? null,
|
|
112
119
|
pair: rec?.pair ?? false,
|
|
@@ -116,6 +123,51 @@ export async function listTeammateReviews(exec, accountId, prId) {
|
|
|
116
123
|
};
|
|
117
124
|
});
|
|
118
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* The NEWEST teammate review of each listed PR, in ONE query for the whole batch (never one per PR):
|
|
128
|
+
* newest by posted time (else import time), ties to the higher id — the review `foldTeamReview`
|
|
129
|
+
* calls `newest`. Account-scoped; a PR with none is absent. DB only.
|
|
130
|
+
*/
|
|
131
|
+
export async function newestTeammateReviewsForPrs(exec, accountId, prIds) {
|
|
132
|
+
const out = new Map();
|
|
133
|
+
if (prIds.length === 0)
|
|
134
|
+
return out;
|
|
135
|
+
const rows = await exec
|
|
136
|
+
.select({
|
|
137
|
+
id: cr.id,
|
|
138
|
+
prId: cr.prId,
|
|
139
|
+
headSha: cr.headSha,
|
|
140
|
+
verdict: cr.verdict,
|
|
141
|
+
postedAt: cr.postedAt,
|
|
142
|
+
createdAt: cr.createdAt,
|
|
143
|
+
userId: users.id,
|
|
144
|
+
login: users.githubLogin,
|
|
145
|
+
displayName: users.displayName,
|
|
146
|
+
avatarUrl: users.avatarUrl,
|
|
147
|
+
prHeadSha: pullRequests.headSha,
|
|
148
|
+
})
|
|
149
|
+
.from(cr)
|
|
150
|
+
.innerJoin(pullRequests, eq(pullRequests.id, cr.prId))
|
|
151
|
+
.innerJoin(repos, eq(repos.id, pullRequests.repoId))
|
|
152
|
+
.innerJoin(users, eq(users.id, cr.sourceUserId))
|
|
153
|
+
.where(and(inArray(cr.prId, [...prIds]), eq(cr.accountId, accountId), eq(repos.accountId, accountId), eq(cr.kind, TEAMMATE_KIND)))
|
|
154
|
+
.execute();
|
|
155
|
+
for (const r of rows) {
|
|
156
|
+
const at = r.postedAt ?? r.createdAt;
|
|
157
|
+
const cur = out.get(r.prId);
|
|
158
|
+
if (cur != null && (cur.postedAt.getTime() > at.getTime() || (cur.postedAt.getTime() === at.getTime() && cur.reviewId > r.id)))
|
|
159
|
+
continue;
|
|
160
|
+
out.set(r.prId, {
|
|
161
|
+
reviewId: r.id,
|
|
162
|
+
poster: { userId: r.userId, login: r.login, displayName: r.displayName, avatarUrl: r.avatarUrl },
|
|
163
|
+
verdict: r.verdict,
|
|
164
|
+
headSha: r.headSha,
|
|
165
|
+
postedAt: at,
|
|
166
|
+
prHeadSha: r.prHeadSha ?? null,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
119
171
|
/**
|
|
120
172
|
* THE LATEST FULL REVIEW AT OR BEFORE `headSha`, THE READER'S OWN OR A TEAMMATE'S POSTED ONE — what a
|
|
121
173
|
* follow-up may anchor on when a teammate's review counts (task A2: the PR author reviewed their own
|
|
@@ -4,7 +4,8 @@ import { aiRunProvider, CLAUDE_FINDING_LENSES, threadAssessmentCounts } from '..
|
|
|
4
4
|
import { storedList, stripStoredStoryLead } from '../../shared/index.js';
|
|
5
5
|
import { referenceDocCitationsFromStored, reviewDocReferencesFromStored } from '../../shared/index.js';
|
|
6
6
|
import { automaticRetryTarget, pressRetryTarget } from '../../db/auto-retry-at-head.js';
|
|
7
|
-
import { TEAMMATE_KIND, ownFindingsOnly, ownRunsOnly } from '../../db/teammate-reviews.js';
|
|
7
|
+
import { TEAMMATE_KIND, newestTeammateReviewsForPrs, ownFindingsOnly, ownRunsOnly } from '../../db/teammate-reviews.js';
|
|
8
|
+
import { teamReviewLead } from './team-view.js';
|
|
8
9
|
import { ticketEntriesOf } from './ticket.js';
|
|
9
10
|
import { overlayFollowUpItems } from './replies-pass.js';
|
|
10
11
|
import { findFindingThread } from './finding-thread.js';
|
|
@@ -829,6 +830,7 @@ fixRunning) {
|
|
|
829
830
|
verdict: cr.verdict,
|
|
830
831
|
headSha: cr.headSha,
|
|
831
832
|
finishedAt: cr.finishedAt,
|
|
833
|
+
createdAt: cr.createdAt,
|
|
832
834
|
ticket: cr.ticket,
|
|
833
835
|
prHeadSha: prs.headSha,
|
|
834
836
|
trigger: cr.trigger,
|
|
@@ -901,8 +903,66 @@ fixRunning) {
|
|
|
901
903
|
await foldStateSummaries(ctx, out, latestByPr, repliesAfter);
|
|
902
904
|
if (fixRunning)
|
|
903
905
|
await foldFixStates(ctx, out, latestByPr, accountId, fixRunning);
|
|
906
|
+
await foldTeammateLeads(ctx, out, latestByPr, prIds, accountId);
|
|
904
907
|
return out;
|
|
905
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* THE NEWEST REVIEW LEADS on the Open PRs column and the Pending cards too (`teammateLead`): ONE
|
|
911
|
+
* batched read of every listed PR's newest teammate review, then THE lead rule (`teamReviewLead`,
|
|
912
|
+
* the AI Review tab's own). Runs AFTER the own-only folds (summary, fix), which it leaves alone. A
|
|
913
|
+
* PR with no run of the reader's but a teammate review is appended, so its column is not blank.
|
|
914
|
+
* Mutates `states` in place. DB-only.
|
|
915
|
+
*/
|
|
916
|
+
async function foldTeammateLeads(ctx, states, latestByPr, prIds, accountId) {
|
|
917
|
+
const newest = await newestTeammateReviewsForPrs(ctx.db, accountId, prIds);
|
|
918
|
+
if (newest.size === 0)
|
|
919
|
+
return;
|
|
920
|
+
const byPr = new Map(states.map((s) => [s.prId, s]));
|
|
921
|
+
for (const prId of prIds) {
|
|
922
|
+
const mate = newest.get(prId);
|
|
923
|
+
if (mate == null)
|
|
924
|
+
continue;
|
|
925
|
+
const row = latestByPr.get(prId);
|
|
926
|
+
const state = byPr.get(prId);
|
|
927
|
+
// The state's status is the pair's for a Claude + Codex pair (busy halves read 'running').
|
|
928
|
+
const own = row != null && state != null
|
|
929
|
+
? {
|
|
930
|
+
status: state.status,
|
|
931
|
+
reviewMode: row.reviewMode,
|
|
932
|
+
at: (row.finishedAt ?? row.createdAt).getTime(),
|
|
933
|
+
}
|
|
934
|
+
: null;
|
|
935
|
+
if (teamReviewLead(own, mate.postedAt.getTime()) !== 'teammate')
|
|
936
|
+
continue;
|
|
937
|
+
const teammateLead = {
|
|
938
|
+
reviewId: mate.reviewId,
|
|
939
|
+
login: mate.poster.login,
|
|
940
|
+
name: mate.poster.displayName,
|
|
941
|
+
avatarUrl: mate.poster.avatarUrl,
|
|
942
|
+
verdict: mate.verdict,
|
|
943
|
+
postedAt: mate.postedAt.toISOString(),
|
|
944
|
+
reviewedHeadSha: mate.headSha,
|
|
945
|
+
headMoved: mate.prHeadSha != null && mate.prHeadSha !== mate.headSha,
|
|
946
|
+
};
|
|
947
|
+
if (state != null) {
|
|
948
|
+
state.teammateLead = teammateLead;
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
states.push({
|
|
952
|
+
prId,
|
|
953
|
+
reviewId: null,
|
|
954
|
+
status: 'succeeded',
|
|
955
|
+
verdict: null,
|
|
956
|
+
reviewedHeadSha: null,
|
|
957
|
+
finishedAt: null,
|
|
958
|
+
ticket: null,
|
|
959
|
+
tickets: [],
|
|
960
|
+
headMoved: false,
|
|
961
|
+
currentHeadSha: mate.prHeadSha,
|
|
962
|
+
teammateLead,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
}
|
|
906
966
|
/**
|
|
907
967
|
* What the run ran on (`ClaudeReviewPrState.ranOn`), oldest half first: one entry for a single run,
|
|
908
968
|
* both halves for a Claude + Codex pair. A skipped run ran no model, and a row with no model names
|
|
@@ -15,6 +15,7 @@ import { pairProvidersReady, pairRaisedByLead } from './pair.js';
|
|
|
15
15
|
import { notApprovedForView } from './not-approved.js';
|
|
16
16
|
import { limnMetaForReview } from './post-meta.js';
|
|
17
17
|
import { listTeammateReviews } from '../../db/teammate-reviews.js';
|
|
18
|
+
import { loadTeamReviewView } from './team-view.js';
|
|
18
19
|
// The Claude Review product API (CORE, free, LOCAL ONLY). The paths are the historical ones
|
|
19
20
|
// (/api/prs/:id/claude-review*, /api/claude-reviews/*, /api/claude-findings/*,
|
|
20
21
|
// /api/claude-review/*), unchanged through both moves so the SPA client never changed. Registered
|
|
@@ -174,6 +175,10 @@ export function registerClaudeReviewRoutes(app, ctx) {
|
|
|
174
175
|
const teamClaimPending = await teamClaimPendingFor(ctx, accountId, id, syncedHead, autoReview != null).catch(() => null);
|
|
175
176
|
// The teammate lines this Limn stood down for at the head — durable (team-claim-seen.ts).
|
|
176
177
|
const teamClaimTeammates = await teammateLinesAtHead(ctx, accountId, id, syncedHead).catch(() => []);
|
|
178
|
+
// Reviews teammates' Limn POSTED to GitHub, imported at sync (db/teammate-reviews.ts). Read-only.
|
|
179
|
+
const teammateReviews = await listTeammateReviews(ctx.db, accountId, id).catch(() => []);
|
|
180
|
+
// THE MERGED REVIEW (team-view.ts): the newest review leads, teammates' open findings join.
|
|
181
|
+
const teamView = await loadTeamReviewView(ctx.db, accountId, id, latest, teammateReviews, current).catch(() => undefined);
|
|
177
182
|
return {
|
|
178
183
|
enabled: true,
|
|
179
184
|
auth: auth.status,
|
|
@@ -195,8 +200,8 @@ export function registerClaudeReviewRoutes(app, ctx) {
|
|
|
195
200
|
currentThreadAssessmentCounts: current ? threadAssessmentCounts(current) : null,
|
|
196
201
|
// The stored row only (live progress is the thread-check GET's — this route stays DB-only).
|
|
197
202
|
threadCheck: await getLatestThreadCheck(ctx, accountId, id).then((r) => (r ? threadCheckWire(r) : null)),
|
|
198
|
-
|
|
199
|
-
|
|
203
|
+
teammateReviews,
|
|
204
|
+
...(teamView != null ? { teamView } : {}),
|
|
200
205
|
teamClaimSkip,
|
|
201
206
|
...(teamClaimTeammates.length > 0 ? { teamClaimTeammates } : {}),
|
|
202
207
|
teamClaimPending,
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// THE MERGED REVIEW (docs/CLAUDE-REVIEW.md § Teammates' reviews → One review). The reader's latest
|
|
2
|
+
// review and the reviews teammates' Limn POSTED to GitHub read as ONE review:
|
|
3
|
+
// - the NEWEST review leads (its summary and verdict head the AI Review tab);
|
|
4
|
+
// - a teammate's finding stays in the one findings list while its GitHub thread is UNRESOLVED and no
|
|
5
|
+
// later review (the reader's follow-up, or the reader's thread judgement) judged it addressed;
|
|
6
|
+
// - the same point raised by the reader's latest review and a teammate shows ONCE, naming both
|
|
7
|
+
// (linked through `prior_finding_id`, or same path + nearby line + similar title — the dedupe's
|
|
8
|
+
// own fingerprint, `similarTitles`; two PR-level points match on the title alone);
|
|
9
|
+
// - a finding with no GitHub thread (PR-level, or a comment that mapped to no thread) also drops out
|
|
10
|
+
// once the reader's newer review at a DIFFERENT head no longer raises it (it has no other way out);
|
|
11
|
+
// - the reader's follow-up settling its own finding settles a teammate's twin of it (same spot).
|
|
12
|
+
// ⚠ Folded HERE ONLY (`foldTeamReview`, pure; `loadTeamReviewView` reads its inputs). The AI Review
|
|
13
|
+
// tab, the PR overview and the review count read the result (`ClaudeReviewResponse.teamView`).
|
|
14
|
+
// ⚠ Own-only readers (caps, budget, posting, follow-up) never read this: it is a view.
|
|
15
|
+
// This module opens NO database at import (the loader takes an executor).
|
|
16
|
+
import { and, eq, inArray, isNotNull, isNull } from 'drizzle-orm';
|
|
17
|
+
import * as schema from '../../db/schema.sqlite.js';
|
|
18
|
+
import { ownRunsOnly } from '../../db/teammate-reviews.js';
|
|
19
|
+
import { similarTitles } from './settled-by-reply.js';
|
|
20
|
+
/** A follow-up status that settles a finding: it drops out of the list. */
|
|
21
|
+
const SETTLED = new Set(['addressed', 'no_longer_applies', 'reply_accepted']);
|
|
22
|
+
/** Two lines this close (or either unknown) are the same spot. */
|
|
23
|
+
const NEAR_LINES = 3;
|
|
24
|
+
const ms = (iso) => new Date(iso).getTime();
|
|
25
|
+
function sameSpot(a, b) {
|
|
26
|
+
if (a.path !== b.path)
|
|
27
|
+
return false;
|
|
28
|
+
// Two PR-level points (no file): the title alone decides.
|
|
29
|
+
if (a.path === '')
|
|
30
|
+
return similarTitles(a.title, b.title);
|
|
31
|
+
if (a.line != null && b.line != null && Math.abs(a.line - b.line) > NEAR_LINES)
|
|
32
|
+
return false;
|
|
33
|
+
return similarTitles(a.title, b.title);
|
|
34
|
+
}
|
|
35
|
+
function addPoster(list, p) {
|
|
36
|
+
if (!list.some((x) => x.userId === p.userId))
|
|
37
|
+
list.push(p);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* THE LEAD RULE (pure), the ONE copy: who heads the PR's review — the AI Review tab
|
|
41
|
+
* (`foldTeamReview`) and the Open PRs column / Pending cards (`getLatestReviewStates`
|
|
42
|
+
* `teammateLead`) both ask it. The NEWEST review leads: a teammate's POSTED review (newest posted at,
|
|
43
|
+
* ms) leads when it is newer than the reader's latest run that SUCCEEDED and was not a skip. A run of
|
|
44
|
+
* the reader's in progress (queued / running) is the newest work and leads; a failed, cancelled or
|
|
45
|
+
* skipped run reviewed nothing, so any teammate review outranks it. null = nothing on the PR.
|
|
46
|
+
*/
|
|
47
|
+
export function teamReviewLead(own, newestTeammateAt) {
|
|
48
|
+
if (newestTeammateAt == null)
|
|
49
|
+
return own != null ? 'own' : null;
|
|
50
|
+
if (own == null)
|
|
51
|
+
return 'teammate';
|
|
52
|
+
if (own.status === 'queued' || own.status === 'running')
|
|
53
|
+
return 'own';
|
|
54
|
+
const ownReviewed = own.status === 'succeeded' && own.reviewMode !== 'skip';
|
|
55
|
+
return !ownReviewed || newestTeammateAt > own.at ? 'teammate' : 'own';
|
|
56
|
+
}
|
|
57
|
+
/** THE FOLD (pure). */
|
|
58
|
+
export function foldTeamReview(input) {
|
|
59
|
+
const { teammateReviews, own } = input;
|
|
60
|
+
// Oldest first, so the first poster of a point is its canonical entry.
|
|
61
|
+
const byAge = [...teammateReviews].sort((a, b) => ms(a.postedAt) - ms(b.postedAt) || a.reviewId - b.reviewId);
|
|
62
|
+
const teammateIds = new Set();
|
|
63
|
+
for (const r of byAge)
|
|
64
|
+
for (const f of r.findings)
|
|
65
|
+
teammateIds.add(f.id);
|
|
66
|
+
// The teammate finding an id leads back to: itself, or the reader's finding chain ending on one.
|
|
67
|
+
const rootOf = (start) => {
|
|
68
|
+
let cur = start;
|
|
69
|
+
for (let hops = 0; cur != null && hops < 100; hops += 1) {
|
|
70
|
+
if (teammateIds.has(cur))
|
|
71
|
+
return cur;
|
|
72
|
+
cur = input.ownChain.get(cur) ?? null;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
};
|
|
76
|
+
// The LATEST follow-up judgement of each teammate finding (a later review overrides an earlier one).
|
|
77
|
+
// Through the chain, else through the spot of the reader's own finding the item judged (a twin
|
|
78
|
+
// shown once under the reader's finding is settled with it).
|
|
79
|
+
const judged = new Map();
|
|
80
|
+
for (const j of [...input.judgements].sort((a, b) => a.reviewId - b.reviewId)) {
|
|
81
|
+
for (const it of j.items) {
|
|
82
|
+
const root = rootOf(it.priorFindingId);
|
|
83
|
+
if (root != null) {
|
|
84
|
+
judged.set(root, it.status);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const spot = input.ownSpots?.get(it.priorFindingId);
|
|
88
|
+
if (spot == null)
|
|
89
|
+
continue;
|
|
90
|
+
for (const r of byAge) {
|
|
91
|
+
if (j.at != null && ms(r.postedAt) > j.at)
|
|
92
|
+
continue;
|
|
93
|
+
for (const f of r.findings)
|
|
94
|
+
if (sameSpot(spot, f))
|
|
95
|
+
judged.set(f.id, it.status);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// The reader's latest run counts as a REVIEW only when it succeeded and was not a skip.
|
|
100
|
+
const ownReviewed = own != null && own.status === 'succeeded' && own.reviewMode !== 'skip';
|
|
101
|
+
// A threadless finding has no thread to resolve or judge: the reader's newer review at a different
|
|
102
|
+
// head that no longer raises it supersedes it.
|
|
103
|
+
const ownRaises = (f) => own != null && own.findings.some((o) => o.severity !== 'praise' && (rootOf(o.priorFindingId) === f.id || sameSpot(o, f)));
|
|
104
|
+
const superseded = (r, f) => f.threadId == null &&
|
|
105
|
+
ownReviewed &&
|
|
106
|
+
own.headSha != null &&
|
|
107
|
+
own.headSha !== r.headSha &&
|
|
108
|
+
own.at > ms(r.postedAt) &&
|
|
109
|
+
!ownRaises(f);
|
|
110
|
+
const threadAddressed = new Map((input.threadJudgements ?? []).map((t) => [t.threadId, t.addressed]));
|
|
111
|
+
const open = (f) => {
|
|
112
|
+
if (f.threadId != null && input.resolvedThreads.has(f.threadId))
|
|
113
|
+
return false;
|
|
114
|
+
const status = judged.get(f.id);
|
|
115
|
+
// A follow-up judgement names this finding; else the reader's judgement of its thread.
|
|
116
|
+
if (status != null)
|
|
117
|
+
return !SETTLED.has(status);
|
|
118
|
+
return !(f.threadId != null && threadAddressed.get(f.threadId) === 'addressed');
|
|
119
|
+
};
|
|
120
|
+
// Teammates' open findings, one entry per point (two teammates on the same point fold into one).
|
|
121
|
+
const entries = [];
|
|
122
|
+
const entryOf = new Map();
|
|
123
|
+
for (const r of byAge) {
|
|
124
|
+
for (const f of r.findings) {
|
|
125
|
+
if (!open(f) || superseded(r, f))
|
|
126
|
+
continue;
|
|
127
|
+
const same = entries.find((e) => (e.threadId != null && e.threadId === f.threadId) || sameSpot(e, f));
|
|
128
|
+
if (same != null) {
|
|
129
|
+
addPoster(same.by, r.poster);
|
|
130
|
+
entryOf.set(f.id, same);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const e = { ...f, reviewId: r.reviewId, postedAt: r.postedAt, by: [r.poster] };
|
|
134
|
+
entries.push(e);
|
|
135
|
+
entryOf.set(f.id, e);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// The reader's latest review raised the same point: it shows once, naming the teammate(s).
|
|
139
|
+
const alsoRaisedBy = [];
|
|
140
|
+
const taken = new Set();
|
|
141
|
+
if (ownReviewed) {
|
|
142
|
+
for (const f of own.findings) {
|
|
143
|
+
if (f.severity === 'praise')
|
|
144
|
+
continue;
|
|
145
|
+
const root = rootOf(f.priorFindingId);
|
|
146
|
+
const linked = root != null ? entryOf.get(root) : undefined;
|
|
147
|
+
const match = linked != null && !taken.has(linked) ? linked : entries.find((e) => !taken.has(e) && sameSpot(e, f));
|
|
148
|
+
if (match == null)
|
|
149
|
+
continue;
|
|
150
|
+
taken.add(match);
|
|
151
|
+
alsoRaisedBy.push({ findingId: f.id, by: match.by });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// The newest review leads (`teamReviewLead`, THE lead rule).
|
|
155
|
+
const newest = byAge[byAge.length - 1] ?? null;
|
|
156
|
+
const lead = teamReviewLead(own, newest != null ? ms(newest.postedAt) : null);
|
|
157
|
+
const reviewAt = new Map(byAge.map((r) => [r.reviewId, ms(r.postedAt)]));
|
|
158
|
+
const findings = entries
|
|
159
|
+
.filter((e) => !taken.has(e))
|
|
160
|
+
.map((e, i) => ({ e, i }))
|
|
161
|
+
// Newest review first; within a review, as posted.
|
|
162
|
+
.sort((a, b) => (reviewAt.get(b.e.reviewId) ?? 0) - (reviewAt.get(a.e.reviewId) ?? 0) || a.i - b.i)
|
|
163
|
+
.map(({ e }) => e);
|
|
164
|
+
return {
|
|
165
|
+
lead,
|
|
166
|
+
teammateReviewId: lead === 'teammate' && newest != null ? newest.reviewId : null,
|
|
167
|
+
findings,
|
|
168
|
+
alsoRaisedBy,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* THE REVIEW COUNT (pure): how many Limn reviews have run on the PR so far. `ownRuns` are the reader's
|
|
173
|
+
* own SUCCEEDED full runs (`kind IS NULL`, skips left out); a Claude + Codex pair (one `pairId` on both
|
|
174
|
+
* halves) counts ONCE. Every teammate review counts once, under its poster.
|
|
175
|
+
*/
|
|
176
|
+
export function teamReviewCount(ownRuns, teammateReviews) {
|
|
177
|
+
const own = new Set(ownRuns.map((r) => r.pairId ?? r.id)).size;
|
|
178
|
+
const by = new Map();
|
|
179
|
+
for (const r of teammateReviews) {
|
|
180
|
+
const e = by.get(r.poster.userId);
|
|
181
|
+
if (e != null)
|
|
182
|
+
e.count += 1;
|
|
183
|
+
else
|
|
184
|
+
by.set(r.poster.userId, { poster: r.poster, count: 1 });
|
|
185
|
+
}
|
|
186
|
+
const byTeammate = [...by.values()].sort((a, b) => b.count - a.count || a.poster.login.localeCompare(b.poster.login));
|
|
187
|
+
return { total: own + teammateReviews.length, byTeammate };
|
|
188
|
+
}
|
|
189
|
+
const cr = schema.claudeReviews;
|
|
190
|
+
const crf = schema.claudeReviewFindings;
|
|
191
|
+
/**
|
|
192
|
+
* Read the fold's inputs for one PR (account-scoped, DB only) and fold. `own` is the latest full
|
|
193
|
+
* review as the tab shows it; `threadJudgements` the server's current thread judgements.
|
|
194
|
+
*/
|
|
195
|
+
export async function loadTeamReviewView(exec, accountId, prId, own, teammateReviews, threadJudgements) {
|
|
196
|
+
// The review count: the reader's own succeeded full runs (a skip ran no review), pairs once.
|
|
197
|
+
const ownRuns = await exec
|
|
198
|
+
.select({ id: cr.id, pairId: cr.pairId, reviewMode: cr.reviewMode })
|
|
199
|
+
.from(cr)
|
|
200
|
+
.where(and(eq(cr.prId, prId), eq(cr.accountId, accountId), isNull(cr.kind), eq(cr.status, 'succeeded')))
|
|
201
|
+
.execute();
|
|
202
|
+
const reviewCount = teamReviewCount(ownRuns.filter((r) => r.reviewMode !== 'skip'), teammateReviews);
|
|
203
|
+
if (teammateReviews.length === 0)
|
|
204
|
+
return { lead: own != null ? 'own' : null, teammateReviewId: null, findings: [], alsoRaisedBy: [], reviewCount };
|
|
205
|
+
const [chainRows, judgeRows] = await Promise.all([
|
|
206
|
+
exec
|
|
207
|
+
.select({ id: crf.id, priorFindingId: crf.priorFindingId, path: crf.path, line: crf.line, title: crf.title })
|
|
208
|
+
.from(crf)
|
|
209
|
+
.innerJoin(cr, eq(cr.id, crf.reviewId))
|
|
210
|
+
.where(and(eq(cr.prId, prId), eq(cr.accountId, accountId), ownRunsOnly(cr.kind)))
|
|
211
|
+
.execute(),
|
|
212
|
+
exec
|
|
213
|
+
.select({ id: cr.id, followUp: cr.followUp, createdAt: cr.createdAt, finishedAt: cr.finishedAt })
|
|
214
|
+
.from(cr)
|
|
215
|
+
.where(and(eq(cr.prId, prId), eq(cr.accountId, accountId), isNull(cr.kind), eq(cr.status, 'succeeded'), isNotNull(cr.followUp)))
|
|
216
|
+
.execute(),
|
|
217
|
+
]);
|
|
218
|
+
const threadIds = [
|
|
219
|
+
...new Set(teammateReviews.flatMap((r) => r.findings.map((f) => f.threadId)).filter((t) => t != null)),
|
|
220
|
+
];
|
|
221
|
+
const resolved = new Set();
|
|
222
|
+
if (threadIds.length > 0) {
|
|
223
|
+
const rt = schema.reviewThreads;
|
|
224
|
+
for (const t of await exec
|
|
225
|
+
.select({ id: rt.id, isResolved: rt.isResolved })
|
|
226
|
+
.from(rt)
|
|
227
|
+
.where(and(eq(rt.prId, prId), inArray(rt.id, threadIds)))
|
|
228
|
+
.execute())
|
|
229
|
+
if (t.isResolved)
|
|
230
|
+
resolved.add(t.id);
|
|
231
|
+
}
|
|
232
|
+
const view = foldTeamReview({
|
|
233
|
+
teammateReviews,
|
|
234
|
+
own: own == null
|
|
235
|
+
? null
|
|
236
|
+
: {
|
|
237
|
+
status: own.status,
|
|
238
|
+
reviewMode: own.reviewMode,
|
|
239
|
+
headSha: own.headSha,
|
|
240
|
+
at: ms(own.finishedAt ?? own.createdAt),
|
|
241
|
+
findings: own.findings.map((f) => ({
|
|
242
|
+
id: f.id,
|
|
243
|
+
path: f.path,
|
|
244
|
+
line: f.line,
|
|
245
|
+
title: f.title,
|
|
246
|
+
severity: f.severity,
|
|
247
|
+
priorFindingId: f.priorFindingId ?? null,
|
|
248
|
+
})),
|
|
249
|
+
},
|
|
250
|
+
ownChain: new Map(chainRows.map((r) => [r.id, r.priorFindingId ?? null])),
|
|
251
|
+
ownSpots: new Map(chainRows.map((r) => [r.id, { path: r.path, line: r.line ?? null, title: r.title }])),
|
|
252
|
+
judgements: judgeRows.map((r) => ({
|
|
253
|
+
reviewId: r.id,
|
|
254
|
+
at: (r.finishedAt ?? r.createdAt).getTime(),
|
|
255
|
+
items: (r.followUp?.items ?? []).map((it) => ({
|
|
256
|
+
priorFindingId: it.priorFindingId,
|
|
257
|
+
status: it.status,
|
|
258
|
+
})),
|
|
259
|
+
})),
|
|
260
|
+
resolvedThreads: resolved,
|
|
261
|
+
threadJudgements: threadJudgements ?? own?.threadAssessments ?? null,
|
|
262
|
+
});
|
|
263
|
+
return { ...view, reviewCount };
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=team-view.js.map
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{d as Ie,r as x,j as e}from"./tanstack-BnbsafB1.js";import{aY as G,aX as Qe,cN as je,cU as Ze,cu as es,cL as ss,cw as Te,aR as ts,cM as O,A as Ne,aW as Se,V as ne,x as ns,a8 as is,aq as ie,bs as Ee,a4 as _e,e as Me,f as K,a7 as as,aU as Be,i as Le,r as rs,k as ls,a as cs,C as os,dr as us,bX as ds,aZ as ms,di as hs,dj as xs,$ as ps,I as fs,U as gs,u as V,bF as bs,cK as js,cJ as Ns,ct as vs,bk as ws,bY as ks,aC as ys,bj as Rs,b8 as Ps,c6 as Ue,bI as Cs,cp as $e,cq as As,cD as Fs,cS as Is,cR as Ts,db as Ss,dc as Es,aV as _s,aS as Ms,cs as Bs,cr as Ls,cE as Us,d8 as $s,ax as Os,a0 as Ds,aT as Hs,bM as Ys,bV as ae,X as Ws,v as qs,M as Ks,T as Vs,c2 as Oe,d0 as zs,al as ve,w as Gs,K as Xs}from"./index-DzvTPkIq.js";import{r as Js,t as Qs,S as Zs,C as et,T as st,F as tt,f as nt}from"./ClaudeReviewTab-Bj5G53IE.js";import"./markdown-CcoLFczy.js";import"./highlight-DIEV0qGG.js";import"./vis-C3BCkP5U.js";const it="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3",we="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3",ke="inline-flex items-center rounded-ui bg-ui-raise px-1.5 py-px font-mono text-meta text-ui-ink";function at(s){return s instanceof Le||s instanceof Error?s.message:"Something went wrong"}function De({plan:s}){return e.jsx("ul",{className:"list-disc space-y-0.5 pl-5 text-body text-ui-ink",children:s.summary.map((t,n)=>e.jsx("li",{children:t},n))})}function ye(s,t){var n;return((n=s==null?void 0:s.find(a=>a.ref===t))==null?void 0:n.title)??null}function rt({plan:s,items:t}){return e.jsxs("div",{className:"mt-2 space-y-3 text-meta text-ui-ink",children:[s.steps.length>0&&e.jsx("ol",{className:"list-decimal space-y-2 pl-5",children:s.steps.map((n,a)=>e.jsxs("li",{children:[e.jsx("div",{className:"font-medium text-ui-ink",children:n.title}),n.files.length>0&&e.jsx("div",{className:"mt-0.5 font-mono text-meta text-ui-mute",children:n.files.join(", ")}),e.jsx("p",{className:"mt-0.5 whitespace-pre-wrap",children:n.change}),n.refs.length>0&&e.jsx("div",{className:"mt-1 flex flex-wrap gap-1",children:n.refs.map(i=>e.jsx("span",{className:ke,children:i},i))})]},a))}),s.notAddressed.length>0&&e.jsxs("div",{children:[e.jsx("div",{className:"mb-1 font-semibold text-ui-mute",children:"Will not fix"}),e.jsx("ul",{className:"space-y-1",children:s.notAddressed.map(n=>e.jsxs("li",{children:[e.jsx("span",{className:ke,children:n.ref})," ",ye(t,n.ref)&&e.jsxs("span",{className:"font-medium",children:[ye(t,n.ref),": "]}),n.reason]},n.ref))})]}),s.notCovered.length>0&&e.jsxs("p",{children:[e.jsx("span",{className:"font-semibold text-ui-mute",children:"Not in the plan: "}),s.notCovered.join(", ")]})]})}function lt({pr:s,fix:t,liveStatus:n,passRunning:a,auth:i}){var w,p;const l=t.plan,o=G(t.plan??t),g=Qe((t.plan??t).provider,(t.plan??t).model),c=je(g,"plan"),m=je(g,"fix"),v=Be(c),F=Ze(s.id),u=es(s.id),k=ss(s.id),M=Te(s.id),C=Ie({mutationKey:ts(s.id)})>0,B=O(N=>N.noteAiFixRun),[E,I]=x.useState(""),[y,D]=x.useState(!1);if(!l)return null;const R=l.plan,_=l.status==="plan_ready"&&!a,L=E.trim(),P=L.length>Ne,r=()=>{!_||C||L===""||P||F.mutate({fixId:t.id,text:L},{onSuccess:()=>I("")})},d=()=>{B({prId:s.id,repoFullName:s.repoFullName,prNumber:s.number,prTitle:s.title}),u.mutate(t.id)},f=F.error??u.error??k.error;return e.jsxs("div",{className:"mt-3 rounded-ui bg-ui-surface p-3",children:[e.jsxs("div",{className:"mb-1 flex items-center gap-1 text-meta font-semibold text-ui-ink",children:["Plan",R&&l.revision>1&&e.jsxs("span",{className:"font-normal text-ui-mute",children:["· version ",l.revision]}),l.model&&e.jsxs("span",{className:"font-normal text-ui-mute",children:["· ",Se(l.provider,l.model,l.effort)]}),e.jsxs(ne,{title:"Plan first",children:[e.jsxs("p",{children:[v," reads the code and writes a plan. It changes nothing."]}),e.jsxs("p",{children:["Tell ",v," what to change and it rewrites the plan, taking the whole conversation into account. Each message runs ",v," again."]}),e.jsxs("p",{children:["When you approve, ",v," builds the fix from this plan. You still review the diff and press Push."]})]})]}),R?e.jsxs(e.Fragment,{children:[e.jsx(De,{plan:R}),e.jsxs("button",{type:"button",className:"hit mt-2 inline-flex items-center gap-1 text-meta text-ui-mute hover:text-ui-ink","aria-expanded":y,onClick:()=>D(N=>!N),children:[e.jsx(ns,{dir:y?"down":"right"}),"Details"]}),y&&e.jsx(rt,{plan:R,items:t.reviewItems})]}):!a&&e.jsx("p",{className:"text-meta text-ui-mute",children:"No plan yet."}),t.error&&e.jsx("p",{className:"mt-2 rounded-ui bg-ui-raise p-2 text-meta text-ui-warn",children:t.error}),e.jsx(is,{transcript:l.transcript,plannedBy:o}),a&&e.jsxs("div",{className:"mt-3",children:[e.jsx(ie,{active:!0,label:R?"Updating the plan":"Making the plan",value:Ee(n),timeConstantSec:30}),e.jsxs("div",{className:"mt-1 flex items-center gap-2 text-meta text-ui-mute",children:[_e[((w=n==null?void 0:n.progress)==null?void 0:w.phase)??""]??"Working…",e.jsx("button",{type:"button",className:we,onClick:()=>M.mutate(),children:"Cancel"})]}),e.jsx(Me,{lines:(p=n==null?void 0:n.progress)==null?void 0:p.recentActivity,className:"mt-1"})]}),_&&R&&e.jsxs("div",{className:"mt-3",children:[e.jsx(K,{kind:"plan",auth:i,meter:!1,followUp:!0,provider:c,children:e.jsx(as,{name:v,draft:E,setDraft:I,busy:C,maxChars:Ne,onSend:r})}),e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2",children:[e.jsx(K,{kind:"fix",auth:i,followUp:!0,provider:m,children:e.jsx("button",{type:"button","data-ai-run":!0,className:it,disabled:C,onClick:d,children:"Approve and build"})}),e.jsx("button",{type:"button",className:we,disabled:C,onClick:()=>k.mutate(t.id),children:"Discard plan"})]})]}),f!=null&&e.jsx("p",{className:"mt-1 text-meta text-ui-danger",children:at(f)})]})}function He({fix:s}){var n;const t=((n=s.plan)==null?void 0:n.status)==="approved"?s.plan.plan:null;return t?e.jsxs("div",{className:"mb-2 rounded-ui bg-ui-surface p-2",children:[e.jsx("div",{className:"mb-1 text-meta font-semibold text-ui-mute",children:"Built from your plan"}),e.jsx(De,{plan:t})]}):null}function ct(s,t,n){var a;return s??((t==null?void 0:t.status)==="succeeded"?t.id:((a=n.find(i=>i.status==="succeeded"))==null?void 0:a.id)??null)}function ot(s,t,n){return t==null||n==null||t===n.id?null:s!=null?"Uses the review you picked, not the latest.":n.status==="failed"?"Uses the last review that finished. The latest one failed.":n.status==="running"||n.status==="queued"?"Uses the last review that finished. A newer one is running.":"Uses the last review that finished."}const Re=os,ut=ls;function dt(s){return s.kind==="finding"&&s.findingId!=null?`claude-finding-${s.findingId}`:null}function mt(s){const t=O(n=>n.openClaudeReview);return n=>{t({id:s.id,number:s.number,title:s.title,repoFullName:s.repoFullName,authorLogin:null,authorDisplayName:null,authorAvatarUrl:null});const a=Date.now()+3e3,i=()=>{const l=document.getElementById(n);if(l){l.scrollIntoView({behavior:"smooth",block:"center"});return}Date.now()<a&&requestAnimationFrame(i)};requestAnimationFrame(i)}}function Pe({refId:s,item:t,onJump:n}){const a=t?`${cs[t.kind]} ${s}`:s,i=e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"shrink-0",children:a}),t&&e.jsxs("span",{className:"min-w-0 truncate font-normal",children:["· ",t.title]})]}),l=t?dt(t):null;return l?e.jsx("button",{type:"button",onClick:()=>n(l),"aria-label":`${a}: ${(t==null?void 0:t.title)??""}. Show in AI Review`,className:`${Re} max-w-[22rem] hover:bg-ui-hover`,children:i}):e.jsx("span",{className:`${Re} max-w-[22rem]`,children:i})}function ht(s){var t;switch(s.seed){case"review":{const n=((t=s.reviewItems)==null?void 0:t.length)??0;return`Fix from ${s.sourceReviewId==null?"the CI check":"the AI review"}${n>0?` (${n} item${n===1?"":"s"})`:""}.`}case"plain":return"Fix from your instruction.";case"comments":return"Fix from picked comments (no longer offered).";case"ci_analysis":return"Fix from the CI analysis (no longer offered).";default:return""}}function xt({pr:s,fix:t}){const n=G(t),a=mt(s),i=t.changeReport,l=new Map((t.reviewItems??[]).map(c=>[c.ref,c])),o=(t.reviewItems??[]).filter(c=>!c.included);if(!i&&o.length===0)return null;const g=l.size>0;return e.jsxs("div",{className:"mt-2 text-meta",children:[e.jsx(rs,{report:i,filesChanged:t.filesChanged,by:n,refsInfo:g?e.jsx("p",{children:"Each tag is a review item the change answers. A finding tag opens it in the AI Review tab."}):null,renderRef:c=>e.jsx(Pe,{refId:c,item:l.get(c),onJump:a},c)}),o.length>0&&e.jsxs("section",{children:[e.jsxs("div",{className:ut,children:["Left out",e.jsx(ne,{title:"Left out",children:e.jsxs("p",{children:["These review items did not fit in what ",n," was sent, so it never saw them."]})})]}),e.jsx("div",{className:"flex flex-wrap gap-1",children:o.map(c=>e.jsx(Pe,{refId:c.ref,item:c,onJump:a},c.ref))})]})]})}const pt=["findings","earlier_findings","judged_threads","untouched_threads","ci_failures","sonar","style_bots","story"],ft={findings:"Claude’s findings",earlier_findings:"Earlier findings still open",judged_threads:"Threads Claude says need a fix",untouched_threads:"Unanswered threads",ci_failures:"CI failures",sonar:"SonarCloud issues",style_bots:"Style bot comments",story:"Ticket gaps"};function Ce(s,t="Claude"){return s==="findings"?`${t}’s findings`:s==="judged_threads"?`Threads ${t} says need a fix`:ft[s]}function z(s,t){return t[s.key]??s.defaultIncluded}function gt(s,t){return s.items.filter(n=>z(n,t)).map(n=>n.key)}function Ye(s,t){const n=new Set;let a=0;for(const i of s.items)if(z(i,t)){if(a>0&&a+i.chars>s.budgetChars){n.add(i.key);continue}a+=i.chars}return n}function bt(s,t){const n={};for(const a of s.items)n[a.key]=a.section===t;return n}const jt={blocker:"Blocker",warning:"Warning",nit:"Nit",question:"Question",praise:"Praise"},Ae={blocker:"bg-ui-danger/10 text-ui-danger",warning:"bg-ui-raise text-ui-warn",nit:"bg-ui-raise text-ui-mute"},We="whitespace-nowrap rounded-ui bg-ui-raise px-1 py-px text-meta text-ui-warn",Z=new Set;function Nt(s,t){return s?t!=null?`${s}:${t}`:s:null}function vt({item:s,picked:t,cut:n,disabled:a,onToggle:i}){return e.jsxs("span",{className:"inline-flex shrink-0 items-center gap-1.5 pt-0.5",children:[e.jsx("label",{className:"flex cursor-pointer items-center touch:-my-3 touch:min-h-10 touch:min-w-10 touch:justify-center",children:e.jsx(V,{checked:t,disabled:a,onChange:l=>i(l.target.checked),"aria-label":`Include in the fix: ${s.label}`})}),t&&n&&e.jsx("span",{className:We,children:"Won’t fit"})]})}function wt({item:s,picked:t,cut:n,disabled:a,onToggle:i}){const l=Nt(s.path,s.line);return e.jsx("li",{children:e.jsxs("label",{className:`flex cursor-pointer items-start gap-2 rounded-ui border px-2 py-1.5 text-meta ${t?"border-ui-line bg-ui-sel":"border-ui-line"}`,children:[e.jsx(V,{className:"mt-0.5",checked:t,disabled:a,onChange:o=>i(o.target.checked)}),e.jsxs("span",{className:"min-w-0 flex-1",children:[e.jsxs("span",{className:"flex flex-wrap items-center gap-1.5",children:[s.severity&&Ae[s.severity]&&e.jsx("span",{className:`rounded-ui px-1 py-px text-meta font-medium ${Ae[s.severity]}`,children:jt[s.severity]}),e.jsx("span",{className:"break-words text-ui-ink",children:s.label}),t&&n&&e.jsx("span",{className:We,children:"Won’t fit"})]}),(l||s.detail)&&e.jsxs("span",{className:"mt-0.5 block break-words text-meta text-ui-mute",children:[l&&e.jsx("span",{className:"font-mono",children:l}),l&&s.detail?" · ":"",s.detail]})]})]})})}function kt({preview:s,overrides:t,onChange:n,disabled:a,pr:i,review:l}){const o=Ye(s,t),g=new Map;for(const r of s.items){const d=g.get(r.section)??[];d.push(r),g.set(r.section,d)}const c=(r,d)=>{const f={...t};for(const w of r)f[w]=d;n(f)},m=l!=null&&l.id===s.sourceReviewId?l:null,v=Js(l),F=Qs(l),u=x.useMemo(()=>m!=null?us(m):null,[m]),k=x.useMemo(()=>new Map(((u==null?void 0:u.findings)??[]).map(r=>[r.id,r])),[u]),M=x.useMemo(()=>{var r;return new Map((((r=u==null?void 0:u.followUp)==null?void 0:r.items)??[]).map(d=>[d.priorFindingId,d]))},[u]),C=x.useMemo(()=>u!=null?ds(u):new Map,[u]),B=x.useMemo(()=>u!=null?ms(u):new Set,[u]),E=x.useMemo(()=>{var d;const r=new Set;for(const f of s.items)((d=f.card)==null?void 0:d.kind)==="story"&&r.add(f.card.ticketReviewId);return[...r].sort((f,w)=>f-w)},[s.items]),I=hs(i.id,E.length>0),y=x.useMemo(()=>{var d;const r=new Map;for(const f of((d=I.data)==null?void 0:d.tickets)??[])f.review!=null&&r.set(f.review.id,f.review);return r},[I.data]),D=x.useMemo(()=>I.data==null?[]:E.filter(r=>!y.has(r)),[I.data,E,y]),R=xs(D),_=x.useMemo(()=>{const r=new Map(y);for(const d of R)r.set(d.id,d);return r},[y,R]);if(s.items.length===0)return e.jsx("p",{className:"text-meta text-ui-mute",children:"The review found nothing to fix."});const L=(m==null?void 0:m.headSha)??i.headSha,P=r=>{var H,T;const d=z(r,t),f=o.has(r.key),w=j=>c([r.key],j),p=e.jsx(vt,{item:r,picked:d,cut:f,disabled:a,onToggle:w}),N=r.card;switch(N==null?void 0:N.kind){case"finding":{const j=k.get(N.findingId);if(j==null)break;return e.jsx(tt,{authorName:nt(l,j),prId:i.id,finding:j,editable:!1,prUrl:i.githubUrl,repoFullName:i.repoFullName,headSha:L,posting:!1,postError:null,inChangeset:j.fileInDiff,priorStatus:C.get(j.id),alreadyPosted:B.has(j.id),actions:!1,selection:p,docReferences:(m==null?void 0:m.docReferences)??null},r.key)}case"earlier_finding":{const j=M.get(N.priorFindingId);if(j==null)break;return e.jsx(gs,{item:j,muted:!1,findingsById:k,headMoved:((H=u==null?void 0:u.followUp)==null?void 0:H.headMoved)??!1,changedPaths:Z,actions:!1,selection:p,by:v},r.key)}case"thread":return e.jsx(st,{t:N.assessment,muted:!1,actions:!1,selection:p,notCheckedNote:N.inReview?void 0:`${F} didn’t check this one.`,by:F},r.key);case"ci":return e.jsx(et,{f:N.item,suggestion:N.item.suggestion,changedPaths:Z,selection:p},r.key);case"sonar":return e.jsx(Zs,{issue:N.issue,selection:p},r.key);case"story":{const j=_.get(N.ticketReviewId),S=j==null?void 0:j.items.find(U=>U.id===N.itemId);if(j==null||S==null)break;const b={review:j,viewedPrId:i.id,changedPaths:Z,actions:!1,selection:p};if(S.status==="missing")return e.jsx(ps,{item:S,...b},r.key);const h=(T=j.assessment)==null?void 0:T.criteria.find(U=>U.ref===S.ref);if(h==null||h.status==="met")break;return e.jsx(fs,{c:h,item:S,...b},r.key)}}return e.jsx(wt,{item:r,picked:d,cut:f,disabled:a,onToggle:w},r.key)};return e.jsxs("div",{className:"space-y-3",children:[pt.filter(r=>g.has(r)).map(r=>{const d=g.get(r),f=d.filter(p=>z(p,t)).length,w=f===d.length;return e.jsxs("section",{"aria-label":Ce(r,v),children:[e.jsxs("div",{className:"mb-1 flex items-center gap-2",children:[e.jsx("h4",{className:"text-meta font-semibold text-ui-ink",children:Ce(r,v)}),e.jsxs("span",{className:"text-meta text-ui-mute",children:[f," of ",d.length]}),e.jsx("button",{type:"button",className:"hit ml-auto text-meta text-ui-accent hover:underline disabled:opacity-50",disabled:a,onClick:()=>c(d.map(p=>p.key),!w),children:w?"Untick all":"Tick all"})]}),e.jsx("ul",{className:"space-y-1.5",children:d.map(P)})]},r)}),o.size>0&&e.jsxs("p",{className:"text-meta text-ui-warn",children:[o.size," ticked ",o.size===1?"item does":"items do"," not fit in one fix and will be left out. Untick something to make room."]})]})}const yt="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3",Fe="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3";function qe(s){return{prId:s.id,repoId:s.repoId,repoFullName:s.repoFullName,prNumber:s.number,prTitle:s.title,githubUrl:s.githubUrl}}function Rt(s){const t=bs({mergeable:s.mergeable,mergeStateStatus:s.mergeStateStatus,isDraft:s.isDraft,inMergeQueue:s.inMergeQueue===!0}).verdict;return js({state:s.state,verdict:t,viewerCanPush:s.viewerCanPush})}function Pt({pr:s}){const t=Ns(s.id),n=vs(),a=ws(t),i=s.baseRefName??"the base branch";x.useEffect(()=>{ks(s.id)},[s.id]);const l=qe(s),o=(t==null?void 0:t.phase)==="succeeded"?t.resolution:null;return e.jsxs("div",{className:"mb-3 rounded-ui bg-ui-surface p-3",children:[e.jsxs("div",{className:"flex items-center gap-1.5 text-body font-medium text-ui-ink",children:[e.jsx(ys,{size:13,className:"text-ai-signal"}),"This branch conflicts with ",i]}),e.jsxs("p",{className:"mt-1 text-meta text-ui-mute",children:[n," reads the code and picks a result for each conflict. You check its choices in the resolver before anything is pushed."]}),a&&t!=null?e.jsxs("div",{className:"mt-2",children:[e.jsx(ie,{active:!0,label:`Resolving conflicts with ${n}`,timeConstantSec:60}),e.jsxs("div",{className:"mt-1 flex items-center gap-2 text-meta text-ui-mute",children:[Rs(t.phase,n),e.jsx("button",{type:"button",className:Fe,onClick:()=>Ps(s.id),children:"Cancel"})]})]}):e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2",children:[e.jsx(K,{kind:"resolve",children:e.jsx("button",{type:"button",className:yt,onClick:()=>Ue(l,{providerName:n}),children:o!=null?"Run again":`Resolve merge conflicts with ${n}`})}),o!=null&&e.jsxs(e.Fragment,{children:[e.jsx("button",{type:"button",className:Fe,onClick:()=>Cs(l),children:"Open the resolver"}),e.jsxs("span",{className:"text-meta text-ui-mute",children:[G(o)," decided ",o.choices.length," of ",o.decidableTotal," change",o.decidableTotal===1?"":"s","."]})]}),(t==null?void 0:t.phase)==="failed"&&t.error!=null&&e.jsx("span",{className:"text-meta text-ui-danger",children:t.error}),(t==null?void 0:t.phase)==="cancelled"&&e.jsx("span",{className:"text-meta text-ui-mute",children:"Cancelled."})]})]})}const se="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3";function te(s){return s instanceof Le||s instanceof Error?s.message:"Something went wrong"}const ee="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3";function Ct({children:s,info:t}){return e.jsxs("div",{className:"flex items-center gap-1.5 px-4 pb-2 pt-4 text-title text-ui-ink md:px-5",children:[e.jsx("h3",{children:s}),t]})}function Ot({pr:s}){const t=$e().enabled,n=O(c=>c.aiFixTabFocus),a=O(c=>c.consumeAiFixTabFocus),[i,l]=x.useState(null),[o,g]=x.useState(null);return x.useEffect(()=>{n&&n.prId===s.id&&(n.reviewId!=null&&l(n.reviewId),n.only!=null&&g(n.only),a())},[n,s.id,a]),t?e.jsx("div",{className:"pb-6",children:e.jsx(At,{pr:s,handoffReviewId:i,onlySection:o,onOnlySectionApplied:()=>g(null)})}):e.jsx(e.Fragment,{})}function At({pr:s,handoffReviewId:t,onlySection:n,onOnlySectionApplied:a}){var oe,ue,de,me,he,xe,pe,fe,ge,be;const{data:i,isLoading:l}=As(s.id,!0),{data:o,isLoading:g}=Fs(s.id),c=(o==null?void 0:o.review)??null,m=ct(t,c,(o==null?void 0:o.history)??[]),v=ot(t,m,c),F=O(A=>A.setPrDetailTab),u=Is("fix",Xs),k=Be(u.chosen.provider),M=Ts().data,C=Ss(s.id),B=Es(s.id),E=Te(s.id),I=$e(),y=_s(M,u.chosen.provider,"plan"),[D,R]=x.useState(!1),_=D&&y,L=O(A=>A.noteAiFixRun),P=Ie({mutationKey:Ms(s.id)})>0,r=((oe=i==null?void 0:i.fix)==null?void 0:oe.status)??null,d=r==="planned",f=d&&((de=(ue=i==null?void 0:i.fix)==null?void 0:ue.plan)==null?void 0:de.status)==="planning",w=r==="running"||r==="queued"||P||f,{status:p}=Bs(s.id,w),N=(p==null?void 0:p.status)??r??"idle",H=N==="running"||N==="queued",T=H&&!d,j=d&&(f||H),S=d&&(((he=(me=i==null?void 0:i.fix)==null?void 0:me.plan)==null?void 0:he.status)==="planning"||((pe=(xe=i==null?void 0:i.fix)==null?void 0:xe.plan)==null?void 0:pe.status)==="plan_ready"),b=(i==null?void 0:i.fix)??null,h=Ls(s.id,m,(i==null?void 0:i.enabled)!==!1&&(m!=null||!g)),U=m==null&&h.data!=null&&h.data.sourceReviewId==null&&h.data.items.length>0,q=m!=null||U,Ve=Us(m!=null&&(c==null?void 0:c.id)!==m?m:null),ze=m!=null&&(c==null?void 0:c.id)===m?c:Ve.data??null,[X,J]=x.useState({});x.useEffect(()=>J({}),[m]);const[Ge,Y]=x.useState(!1);x.useEffect(()=>{n==null||!h.data||(J(bt(h.data,n)),Y(!0),a())},[n,h.data,a]);const re=!S&&(Ge||!b||b.status!=="succeeded"),Q=Rt(s),[le,Xe]=x.useState(!1),W=$s(s.id,s.repoFullName),$=h.data?gt(h.data,X):[],ce=h.data?Ye(h.data,X).size:0,Je=()=>{if(!q||!h.data||$.length===0)return;const A={model:u.chosen.model,provider:u.chosen.provider,seed:"review",...m!=null?{sourceReviewId:m}:{},include:$,...W.names.length>0?{checks:W.names}:{}};_?B.mutate(A,{onSuccess:()=>Y(!1)}):(L({prId:s.id,repoFullName:s.repoFullName,prNumber:s.number,prTitle:s.title}),C.mutate(A,{onSuccess:()=>Y(!1)})),le&&Q&&Ue(qe(s),{model:Hs(u.chosen.model),providerName:I.providerName})};return e.jsxs("div",{children:[e.jsx(Ct,{info:e.jsxs(ne,{title:"AI Fix",children:[e.jsxs("p",{children:[k," reads the PR's code and edits files in a private copy. It has no shell. Tick Run tests to let it run the project's own test, lint and type checks in a sandbox."]}),e.jsx("p",{children:"A fix starts from an AI review. Pick what it should work on: the review's findings, earlier findings still open, threads the review says need a fix, unanswered threads, CI failures and ticket gaps are ticked; style bot comments are not."}),e.jsx("p",{children:"With no AI review, a fix can start from a finished CI check: it works on the failures the check says this PR can fix."}),e.jsxs("p",{children:["Tick Plan first to read ",k,"'s plan, steer it and approve it before anything changes."]}),e.jsxs("p",{children:[k," reports what it changed in each file and what it left alone, and why."]}),e.jsx("p",{children:"Nothing is pushed until you press Push. An automatic fix on your own PR is pushed for you only when Push automatically is on in Settings."})]}),children:"AI Fix"}),e.jsxs("div",{className:"px-4",children:[Q&&e.jsx(Pt,{pr:s}),(i==null?void 0:i.enabled)===!1?e.jsx("p",{className:"text-body text-ui-mute",children:"The agentic fixer is turned off."}):e.jsxs(e.Fragment,{children:[!T&&!S&&!q&&!g&&!h.isLoading&&e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("p",{className:"text-meta text-ui-mute",children:(c==null?void 0:c.status)==="running"||(c==null?void 0:c.status)==="queued"?"A fix starts from an AI review. One is running.":"A fix starts from a finished AI review."}),e.jsx("button",{type:"button",className:se,onClick:()=>F(s.id,"claude_review"),children:"Open AI Review"})]}),!T&&!S&&q&&!re&&e.jsx("button",{type:"button",className:ee,onClick:()=>Y(!0),children:"New fix"}),!T&&q&&re&&e.jsxs("div",{children:[v&&e.jsx("p",{className:"mb-2 text-meta text-ui-mute",children:v}),U&&e.jsx("p",{className:"mb-2 text-meta text-ui-mute",children:"No AI review yet. This fix works on the CI check’s failures."}),h.isLoading&&e.jsx("p",{className:"text-meta text-ui-mute",children:"Loading…"}),h.isError&&m!=null&&e.jsx("p",{className:"text-meta text-ui-danger",children:te(h.error)}),h.data&&e.jsx(kt,{preview:h.data,overrides:X,onChange:J,disabled:P,pr:s,review:ze}),h.data&&h.data.items.length>0&&Q&&e.jsxs("label",{className:"mt-3 flex cursor-pointer items-start gap-2 text-meta text-ui-ink",children:[e.jsx(V,{className:"mt-0.5",checked:le,disabled:P,onChange:A=>Xe(A.target.checked)}),e.jsxs("span",{children:["Also resolve merge conflicts with ",s.baseRefName??"the base branch",e.jsx("span",{className:"block text-meta text-ui-mute",children:"You check the merge in the resolver. It lands as its own commit, apart from the fix."})]})]}),h.data&&h.data.items.length>0&&e.jsx(Os,{state:W,disabled:P}),h.data&&h.data.items.length>0&&e.jsxs("div",{className:"mt-3 flex flex-wrap items-center gap-2 max-md:sticky max-md:bottom-0 max-md:z-20 max-md:-mx-4 max-md:bg-ui-surface max-md:px-4 max-md:py-3",children:[e.jsx(Ds,{state:u,onPick:u.pick,disabled:P}),y&&e.jsxs("label",{className:"flex cursor-pointer items-center gap-1.5 text-meta text-ui-ink",children:[e.jsx(V,{checked:_,disabled:P,onChange:A=>R(A.target.checked)}),"Plan first"]}),e.jsx(K,{kind:"fix",auth:i==null?void 0:i.auth,anyProvider:!0,provider:u.chosen.provider,children:e.jsx("button",{type:"button",className:se,disabled:P||$.length===0||W.on&&W.query.isLoading,onClick:Je,children:_?"Make a plan":"Start fix"})}),e.jsx("span",{className:"text-meta text-ui-mute",children:$.length===0?"Nothing ticked.":`${$.length-ce} item${$.length-ce===1?"":"s"}`}),(b==null?void 0:b.status)==="succeeded"&&e.jsx("button",{type:"button",className:ee,onClick:()=>Y(!1),children:"Close"})]})]}),T&&e.jsx("button",{type:"button",className:ee,onClick:()=>E.mutate(),children:"Cancel"}),(C.isError||B.isError)&&e.jsx("p",{className:"mt-1 text-meta text-ui-danger",children:te(C.error??B.error)}),b&&d&&e.jsx(lt,{pr:s,fix:b,liveStatus:p,passRunning:j,auth:i==null?void 0:i.auth}),T&&e.jsxs("div",{className:"mt-3",children:[b&&e.jsx(He,{fix:b}),e.jsx(ie,{active:!0,label:"Running AI fix",value:Ee(p),timeConstantSec:40}),e.jsxs("div",{className:"mt-1 flex items-center gap-2 text-meta text-ui-mute",children:[(b==null?void 0:b.trigger)==="auto"&&e.jsx(Ke,{}),_e[((fe=p==null?void 0:p.progress)==null?void 0:fe.phase)??""]??"Working…"]}),e.jsx(Me,{lines:(ge=p==null?void 0:p.progress)==null?void 0:ge.recentActivity,runKey:(b==null?void 0:b.id)??null,className:"mt-1"})]}),!T&&b&&!d&&e.jsx(It,{pr:s,fix:b,headInfo:(i==null?void 0:i.headInfo)??null,viewerCanPush:(i==null?void 0:i.viewerCanPush)??!1}),l&&!b&&e.jsx("p",{className:"mt-3 text-meta text-ui-mute",children:"Loading…"})]}),e.jsx(St,{history:(i==null?void 0:i.history)??[],currentFixId:((be=i==null?void 0:i.fix)==null?void 0:be.id)??null})]})]})}function Ke(){return e.jsx("span",{className:"inline-flex items-center rounded-ui bg-ui-raise px-1.5 py-px text-meta font-medium text-ui-mute",children:"Auto fix"})}function Ft(s){return Se(s.provider,s.model,s.effort)}function It({pr:s,fix:t,headInfo:n,viewerCanPush:a}){var o,g,c;const i=x.useMemo(()=>Ys(t.patch),[t.patch]);if(t.status==="failed"&&((o=t.plan)==null?void 0:o.status)==="failed")return e.jsxs("div",{className:"mt-3 rounded-ui border border-ui-danger/40 bg-ui-danger/10 p-2 text-meta text-ui-danger",children:[G(t.plan??t)," could not make a plan: ",t.error??"unknown error"]});if(t.status==="cancelled"&&((g=t.plan)==null?void 0:g.status)==="discarded")return e.jsx("p",{className:"mt-3 text-meta text-ui-mute",children:"The plan was discarded."});if(t.status==="failed")return e.jsxs("div",{className:"mt-3 rounded-ui border border-ui-danger/40 bg-ui-danger/10 p-2 text-meta text-ui-danger",children:["The fix run failed: ",t.error??"unknown error"]});if(t.status==="cancelled")return e.jsx("p",{className:"mt-3 text-meta text-ui-mute",children:"The fix run was cancelled."});if(t.status!=="succeeded")return e.jsx(e.Fragment,{});const l=!t.patch||t.filesChanged.length===0;return e.jsxs("div",{className:"mt-3 border-t border-ui-line pt-3",children:[e.jsxs("p",{className:"mb-1 text-meta text-ui-mute",children:[t.trigger==="auto"&&e.jsxs(e.Fragment,{children:[e.jsx(Ke,{})," "]}),ht(t)," · ",Ft(t),t.finishedAt&&e.jsxs(e.Fragment,{children:[" · ",ae(t.finishedAt)]})]}),t.pushedAt==null&&t.error&&e.jsx("p",{className:"mb-2 rounded-ui bg-ui-raise p-2 text-meta text-ui-warn",children:t.error}),e.jsx(He,{fix:t}),t.summary&&e.jsx("div",{className:"prose prose-sm max-w-none dark:prose-invert",children:e.jsx(Ws,{children:t.summary})}),e.jsx(xt,{pr:s,fix:t}),e.jsx(qs,{checks:t.checks}),l?e.jsx("p",{className:"mt-2 text-meta text-ui-mute",children:"No files changed."}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mb-1 mt-2 flex items-center justify-between gap-3 text-meta text-ui-mute",children:[e.jsxs("span",{children:[t.filesChanged.length," file",t.filesChanged.length===1?"":"s"," changed",!((c=t.checks)!=null&&c.ran)&&" · Not built or tested here."]}),e.jsx(Ks,{})]}),e.jsx("div",{className:"overflow-hidden rounded-ui bg-ui-surface text-ui-ink",children:e.jsx(Vs,{files:i})}),e.jsx(Et,{pr:s,fix:t,headInfo:n,viewerCanPush:a})]})]})}function Tt({fix:s}){return e.jsxs("div",{className:"mb-2 rounded-ui bg-ui-raise p-2 text-meta text-ui-success",children:[e.jsxs("div",{children:["Pushed to ",e.jsx("span",{className:"font-mono",children:s.pushedBranch}),s.pushedPrUrl&&e.jsxs(e.Fragment,{children:[" · ",e.jsxs("a",{href:Oe(s.pushedPrUrl),target:"_blank",rel:"noreferrer",className:"hit underline",children:["PR #",s.pushedPrNumber]})]}),s.pushedAt&&e.jsxs(e.Fragment,{children:[" · ",ae(s.pushedAt)]})]}),s.commitMessage&&e.jsx("div",{className:"mt-1 font-mono text-meta text-ui-success",children:s.commitMessage})]})}function St({history:s,currentFixId:t}){const n=s.filter(a=>a.pushedAt!=null&&a.id!==t);return n.length===0?null:e.jsxs("div",{className:"mt-6",children:[e.jsx("div",{className:"mb-2 text-title text-ui-ink",children:"Earlier fixes pushed"}),e.jsx("ul",{className:"space-y-2",children:n.map(a=>e.jsxs("li",{className:"text-meta",children:[e.jsx("div",{className:"font-mono text-ui-ink",children:a.commitMessage??"(no commit message)"}),e.jsxs("div",{className:"mt-0.5 text-meta text-ui-faint",children:[a.pushedBranch&&e.jsx("span",{className:"font-mono",children:a.pushedBranch}),a.pushedPrUrl&&e.jsxs(e.Fragment,{children:[" · ",e.jsxs("a",{href:Oe(a.pushedPrUrl),target:"_blank",rel:"noreferrer",className:"hit underline",children:["PR #",a.pushedPrNumber]})]}),a.pushedAt&&e.jsxs(e.Fragment,{children:[" · ",ae(a.pushedAt)]})]})]},a.id))})]})}function Et({pr:s,fix:t,headInfo:n,viewerCanPush:a}){const i=zs(s.id),l=(n==null?void 0:n.canPushSameBranch)??!1,[o,g]=x.useState(l?"existing":"new"),[c,m]=x.useState((n==null?void 0:n.suggestedBranch)??""),v=x.useRef(!1);if(x.useEffect(()=>{!v.current&&(n!=null&&n.suggestedBranch)&&(m(n.suggestedBranch),v.current=!0,n.canPushSameBranch||g("new"))},[n]),t.pushedAt!=null)return e.jsx(Tt,{fix:t});if(!a)return e.jsx("p",{className:"mt-2 text-meta text-ui-mute",children:"You need write access to push this fix."});const F=o==="new"&&c.trim().length===0,u=i.isPending,k=()=>{i.mutate({fixId:t.id,body:{target:o,branch:o==="new"?c.trim():void 0}})};return e.jsxs("div",{className:"mt-3 rounded-ui bg-ui-surface p-2",children:[t.commitMessage&&e.jsx("div",{className:"mb-2 rounded-ui bg-ui-surface px-2 py-1 font-mono text-meta text-ui-ink",title:"Commit message",children:t.commitMessage}),e.jsxs("label",{className:`flex items-center gap-2 text-meta ${l?"":"opacity-40"}`,title:l?void 0:"The PR head is a fork you cannot push to — use a new branch",children:[e.jsx(ve,{className:"mt-0",checked:o==="existing",disabled:!l||u,onChange:()=>g("existing")}),"Push to the PR branch",(n==null?void 0:n.headRef)&&e.jsxs("span",{className:"font-mono text-ui-faint",children:["(",n.headRef,")"]})]}),e.jsxs("label",{className:"mt-1 flex items-center gap-2 text-meta",children:[e.jsx(ve,{className:"mt-0",checked:o==="new",disabled:u,onChange:()=>g("new")}),"New branch + open a PR"]}),o==="new"&&e.jsx("input",{type:"text",className:"mt-1 w-full rounded-ui bg-ui-raise px-2 py-1 font-mono text-meta ring-1 ring-inset ring-ui-line focus:outline-none focus:ring-2 focus:ring-ui-accent touch:min-h-10",value:c,disabled:u,onChange:M=>m(M.target.value),placeholder:"branch-name"}),e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",className:se,disabled:u||F,onClick:k,children:u?"Pushing…":o==="new"?"Push + open PR":"Push"}),e.jsx(Gs,{checks:t.checks})]}),i.isError&&e.jsx("div",{className:"mt-2 text-meta text-ui-danger",children:te(i.error)})]})}export{Ot as AiFixTab};
|
|
1
|
+
import{d as Ie,r as x,j as e}from"./tanstack-BnbsafB1.js";import{aZ as G,aY as Je,cQ as je,cX as Ze,cx as es,cO as ss,cz as Te,aS as ts,cP as O,A as Ne,aX as Se,V as ne,x as ns,a7 as is,ao as ie,bv as Ee,a3 as _e,e as Me,f as q,a6 as as,aV as Be,i as Le,r as rs,k as ls,a as cs,C as os,du as us,b$ as ds,a_ as ms,dl as hs,dm as xs,$ as ps,I as fs,U as gs,u as V,bJ as bs,cN as js,cM as Ns,cw as vs,bn as ws,c0 as ks,aA as ys,bm as Rs,bb as Ps,ca as $e,bM as Cs,cs as Ue,ct as As,cG as Fs,cV as Is,cU as Ts,de as Ss,df as Es,aW as _s,aT as Ms,cv as Bs,cu as Ls,cH as $s,db as Us,av as Os,a0 as Ds,aU as Hs,bQ as Ws,bZ as ae,X as Ys,v as Ks,M as qs,T as Vs,c6 as Oe,d3 as zs,ak as ve,w as Gs,K as Xs}from"./index-DVYEmdnp.js";import{r as Qs,t as Js,S as Zs,C as et,T as st,F as tt,f as nt}from"./ClaudeReviewTab-ncqvtble.js";import"./markdown-CcoLFczy.js";import"./highlight-DIEV0qGG.js";import"./vis-C3BCkP5U.js";const it="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3",we="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3",ke="inline-flex items-center rounded-ui bg-ui-raise px-1.5 py-px font-mono text-meta text-ui-ink";function at(s){return s instanceof Le||s instanceof Error?s.message:"Something went wrong"}function De({plan:s}){return e.jsx("ul",{className:"list-disc space-y-0.5 pl-5 text-body text-ui-ink",children:s.summary.map((t,n)=>e.jsx("li",{children:t},n))})}function ye(s,t){var n;return((n=s==null?void 0:s.find(a=>a.ref===t))==null?void 0:n.title)??null}function rt({plan:s,items:t}){return e.jsxs("div",{className:"mt-2 space-y-3 text-meta text-ui-ink",children:[s.steps.length>0&&e.jsx("ol",{className:"list-decimal space-y-2 pl-5",children:s.steps.map((n,a)=>e.jsxs("li",{children:[e.jsx("div",{className:"font-medium text-ui-ink",children:n.title}),n.files.length>0&&e.jsx("div",{className:"mt-0.5 font-mono text-meta text-ui-mute",children:n.files.join(", ")}),e.jsx("p",{className:"mt-0.5 whitespace-pre-wrap",children:n.change}),n.refs.length>0&&e.jsx("div",{className:"mt-1 flex flex-wrap gap-1",children:n.refs.map(i=>e.jsx("span",{className:ke,children:i},i))})]},a))}),s.notAddressed.length>0&&e.jsxs("div",{children:[e.jsx("div",{className:"mb-1 font-semibold text-ui-mute",children:"Will not fix"}),e.jsx("ul",{className:"space-y-1",children:s.notAddressed.map(n=>e.jsxs("li",{children:[e.jsx("span",{className:ke,children:n.ref})," ",ye(t,n.ref)&&e.jsxs("span",{className:"font-medium",children:[ye(t,n.ref),": "]}),n.reason]},n.ref))})]}),s.notCovered.length>0&&e.jsxs("p",{children:[e.jsx("span",{className:"font-semibold text-ui-mute",children:"Not in the plan: "}),s.notCovered.join(", ")]})]})}function lt({pr:s,fix:t,liveStatus:n,passRunning:a,auth:i}){var w,p;const l=t.plan,o=G(t.plan??t),g=Je((t.plan??t).provider,(t.plan??t).model),c=je(g,"plan"),m=je(g,"fix"),v=Be(c),F=Ze(s.id),u=es(s.id),k=ss(s.id),M=Te(s.id),C=Ie({mutationKey:ts(s.id)})>0,B=O(N=>N.noteAiFixRun),[E,I]=x.useState(""),[y,D]=x.useState(!1);if(!l)return null;const R=l.plan,_=l.status==="plan_ready"&&!a,L=E.trim(),P=L.length>Ne,r=()=>{!_||C||L===""||P||F.mutate({fixId:t.id,text:L},{onSuccess:()=>I("")})},d=()=>{B({prId:s.id,repoFullName:s.repoFullName,prNumber:s.number,prTitle:s.title}),u.mutate(t.id)},f=F.error??u.error??k.error;return e.jsxs("div",{className:"mt-3 rounded-ui bg-ui-surface p-3",children:[e.jsxs("div",{className:"mb-1 flex items-center gap-1 text-meta font-semibold text-ui-ink",children:["Plan",R&&l.revision>1&&e.jsxs("span",{className:"font-normal text-ui-mute",children:["· version ",l.revision]}),l.model&&e.jsxs("span",{className:"font-normal text-ui-mute",children:["· ",Se(l.provider,l.model,l.effort)]}),e.jsxs(ne,{title:"Plan first",children:[e.jsxs("p",{children:[v," reads the code and writes a plan. It changes nothing."]}),e.jsxs("p",{children:["Tell ",v," what to change and it rewrites the plan, taking the whole conversation into account. Each message runs ",v," again."]}),e.jsxs("p",{children:["When you approve, ",v," builds the fix from this plan. You still review the diff and press Push."]})]})]}),R?e.jsxs(e.Fragment,{children:[e.jsx(De,{plan:R}),e.jsxs("button",{type:"button",className:"hit mt-2 inline-flex items-center gap-1 text-meta text-ui-mute hover:text-ui-ink","aria-expanded":y,onClick:()=>D(N=>!N),children:[e.jsx(ns,{dir:y?"down":"right"}),"Details"]}),y&&e.jsx(rt,{plan:R,items:t.reviewItems})]}):!a&&e.jsx("p",{className:"text-meta text-ui-mute",children:"No plan yet."}),t.error&&e.jsx("p",{className:"mt-2 rounded-ui bg-ui-raise p-2 text-meta text-ui-warn",children:t.error}),e.jsx(is,{transcript:l.transcript,plannedBy:o}),a&&e.jsxs("div",{className:"mt-3",children:[e.jsx(ie,{active:!0,label:R?"Updating the plan":"Making the plan",value:Ee(n),timeConstantSec:30}),e.jsxs("div",{className:"mt-1 flex items-center gap-2 text-meta text-ui-mute",children:[_e[((w=n==null?void 0:n.progress)==null?void 0:w.phase)??""]??"Working…",e.jsx("button",{type:"button",className:we,onClick:()=>M.mutate(),children:"Cancel"})]}),e.jsx(Me,{lines:(p=n==null?void 0:n.progress)==null?void 0:p.recentActivity,className:"mt-1"})]}),_&&R&&e.jsxs("div",{className:"mt-3",children:[e.jsx(q,{kind:"plan",auth:i,meter:!1,followUp:!0,provider:c,children:e.jsx(as,{name:v,draft:E,setDraft:I,busy:C,maxChars:Ne,onSend:r})}),e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2",children:[e.jsx(q,{kind:"fix",auth:i,followUp:!0,provider:m,children:e.jsx("button",{type:"button","data-ai-run":!0,className:it,disabled:C,onClick:d,children:"Approve and build"})}),e.jsx("button",{type:"button",className:we,disabled:C,onClick:()=>k.mutate(t.id),children:"Discard plan"})]})]}),f!=null&&e.jsx("p",{className:"mt-1 text-meta text-ui-danger",children:at(f)})]})}function He({fix:s}){var n;const t=((n=s.plan)==null?void 0:n.status)==="approved"?s.plan.plan:null;return t?e.jsxs("div",{className:"mb-2 rounded-ui bg-ui-surface p-2",children:[e.jsx("div",{className:"mb-1 text-meta font-semibold text-ui-mute",children:"Built from your plan"}),e.jsx(De,{plan:t})]}):null}function ct(s,t,n){var a;return s??((t==null?void 0:t.status)==="succeeded"?t.id:((a=n.find(i=>i.status==="succeeded"))==null?void 0:a.id)??null)}function ot(s,t,n){return t==null||n==null||t===n.id?null:s!=null?"Uses the review you picked, not the latest.":n.status==="failed"?"Uses the last review that finished. The latest one failed.":n.status==="running"||n.status==="queued"?"Uses the last review that finished. A newer one is running.":"Uses the last review that finished."}const Re=os,ut=ls;function dt(s){return s.kind==="finding"&&s.findingId!=null?`claude-finding-${s.findingId}`:null}function mt(s){const t=O(n=>n.openClaudeReview);return n=>{t({id:s.id,number:s.number,title:s.title,repoFullName:s.repoFullName,authorLogin:null,authorDisplayName:null,authorAvatarUrl:null});const a=Date.now()+3e3,i=()=>{const l=document.getElementById(n);if(l){l.scrollIntoView({behavior:"smooth",block:"center"});return}Date.now()<a&&requestAnimationFrame(i)};requestAnimationFrame(i)}}function Pe({refId:s,item:t,onJump:n}){const a=t?`${cs[t.kind]} ${s}`:s,i=e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"shrink-0",children:a}),t&&e.jsxs("span",{className:"min-w-0 truncate font-normal",children:["· ",t.title]})]}),l=t?dt(t):null;return l?e.jsx("button",{type:"button",onClick:()=>n(l),"aria-label":`${a}: ${(t==null?void 0:t.title)??""}. Show in AI Review`,className:`${Re} max-w-[22rem] hover:bg-ui-hover`,children:i}):e.jsx("span",{className:`${Re} max-w-[22rem]`,children:i})}function ht(s){var t;switch(s.seed){case"review":{const n=((t=s.reviewItems)==null?void 0:t.length)??0;return`Fix from ${s.sourceReviewId==null?"the CI check":"the AI review"}${n>0?` (${n} item${n===1?"":"s"})`:""}.`}case"plain":return"Fix from your instruction.";case"comments":return"Fix from picked comments (no longer offered).";case"ci_analysis":return"Fix from the CI analysis (no longer offered).";default:return""}}function xt({pr:s,fix:t}){const n=G(t),a=mt(s),i=t.changeReport,l=new Map((t.reviewItems??[]).map(c=>[c.ref,c])),o=(t.reviewItems??[]).filter(c=>!c.included);if(!i&&o.length===0)return null;const g=l.size>0;return e.jsxs("div",{className:"mt-2 text-meta",children:[e.jsx(rs,{report:i,filesChanged:t.filesChanged,by:n,refsInfo:g?e.jsx("p",{children:"Each tag is a review item the change answers. A finding tag opens it in the AI Review tab."}):null,renderRef:c=>e.jsx(Pe,{refId:c,item:l.get(c),onJump:a},c)}),o.length>0&&e.jsxs("section",{children:[e.jsxs("div",{className:ut,children:["Left out",e.jsx(ne,{title:"Left out",children:e.jsxs("p",{children:["These review items did not fit in what ",n," was sent, so it never saw them."]})})]}),e.jsx("div",{className:"flex flex-wrap gap-1",children:o.map(c=>e.jsx(Pe,{refId:c.ref,item:c,onJump:a},c.ref))})]})]})}const pt=["findings","earlier_findings","judged_threads","untouched_threads","ci_failures","sonar","style_bots","story"],ft={findings:"Claude’s findings",earlier_findings:"Earlier findings still open",judged_threads:"Threads Claude says need a fix",untouched_threads:"Unanswered threads",ci_failures:"CI failures",sonar:"SonarCloud issues",style_bots:"Style bot comments",story:"Ticket gaps"};function Ce(s,t="Claude"){return s==="findings"?`${t}’s findings`:s==="judged_threads"?`Threads ${t} says need a fix`:ft[s]}function z(s,t){return t[s.key]??s.defaultIncluded}function gt(s,t){return s.items.filter(n=>z(n,t)).map(n=>n.key)}function We(s,t){const n=new Set;let a=0;for(const i of s.items)if(z(i,t)){if(a>0&&a+i.chars>s.budgetChars){n.add(i.key);continue}a+=i.chars}return n}function bt(s,t){const n={};for(const a of s.items)n[a.key]=a.section===t;return n}const jt={blocker:"Blocker",warning:"Warning",nit:"Nit",question:"Question",praise:"Praise"},Ae={blocker:"bg-ui-danger/10 text-ui-danger",warning:"bg-ui-raise text-ui-warn",nit:"bg-ui-raise text-ui-mute"},Ye="whitespace-nowrap rounded-ui bg-ui-raise px-1 py-px text-meta text-ui-warn",Z=new Set;function Nt(s,t){return s?t!=null?`${s}:${t}`:s:null}function vt({item:s,picked:t,cut:n,disabled:a,onToggle:i}){return e.jsxs("span",{className:"inline-flex shrink-0 items-center gap-1.5 pt-0.5",children:[e.jsx("label",{className:"flex cursor-pointer items-center touch:-my-3 touch:min-h-10 touch:min-w-10 touch:justify-center",children:e.jsx(V,{checked:t,disabled:a,onChange:l=>i(l.target.checked),"aria-label":`Include in the fix: ${s.label}`})}),t&&n&&e.jsx("span",{className:Ye,children:"Won’t fit"})]})}function wt({item:s,picked:t,cut:n,disabled:a,onToggle:i}){const l=Nt(s.path,s.line);return e.jsx("li",{children:e.jsxs("label",{className:`flex cursor-pointer items-start gap-2 rounded-ui border px-2 py-1.5 text-meta ${t?"border-ui-line bg-ui-sel":"border-ui-line"}`,children:[e.jsx(V,{className:"mt-0.5",checked:t,disabled:a,onChange:o=>i(o.target.checked)}),e.jsxs("span",{className:"min-w-0 flex-1",children:[e.jsxs("span",{className:"flex flex-wrap items-center gap-1.5",children:[s.severity&&Ae[s.severity]&&e.jsx("span",{className:`rounded-ui px-1 py-px text-meta font-medium ${Ae[s.severity]}`,children:jt[s.severity]}),e.jsx("span",{className:"break-words text-ui-ink",children:s.label}),t&&n&&e.jsx("span",{className:Ye,children:"Won’t fit"})]}),(l||s.detail)&&e.jsxs("span",{className:"mt-0.5 block break-words text-meta text-ui-mute",children:[l&&e.jsx("span",{className:"font-mono",children:l}),l&&s.detail?" · ":"",s.detail]})]})]})})}function kt({preview:s,overrides:t,onChange:n,disabled:a,pr:i,review:l}){const o=We(s,t),g=new Map;for(const r of s.items){const d=g.get(r.section)??[];d.push(r),g.set(r.section,d)}const c=(r,d)=>{const f={...t};for(const w of r)f[w]=d;n(f)},m=l!=null&&l.id===s.sourceReviewId?l:null,v=Qs(l),F=Js(l),u=x.useMemo(()=>m!=null?us(m):null,[m]),k=x.useMemo(()=>new Map(((u==null?void 0:u.findings)??[]).map(r=>[r.id,r])),[u]),M=x.useMemo(()=>{var r;return new Map((((r=u==null?void 0:u.followUp)==null?void 0:r.items)??[]).map(d=>[d.priorFindingId,d]))},[u]),C=x.useMemo(()=>u!=null?ds(u):new Map,[u]),B=x.useMemo(()=>u!=null?ms(u):new Set,[u]),E=x.useMemo(()=>{var d;const r=new Set;for(const f of s.items)((d=f.card)==null?void 0:d.kind)==="story"&&r.add(f.card.ticketReviewId);return[...r].sort((f,w)=>f-w)},[s.items]),I=hs(i.id,E.length>0),y=x.useMemo(()=>{var d;const r=new Map;for(const f of((d=I.data)==null?void 0:d.tickets)??[])f.review!=null&&r.set(f.review.id,f.review);return r},[I.data]),D=x.useMemo(()=>I.data==null?[]:E.filter(r=>!y.has(r)),[I.data,E,y]),R=xs(D),_=x.useMemo(()=>{const r=new Map(y);for(const d of R)r.set(d.id,d);return r},[y,R]);if(s.items.length===0)return e.jsx("p",{className:"text-meta text-ui-mute",children:"The review found nothing to fix."});const L=(m==null?void 0:m.headSha)??i.headSha,P=r=>{var H,T;const d=z(r,t),f=o.has(r.key),w=j=>c([r.key],j),p=e.jsx(vt,{item:r,picked:d,cut:f,disabled:a,onToggle:w}),N=r.card;switch(N==null?void 0:N.kind){case"finding":{const j=k.get(N.findingId);if(j==null)break;return e.jsx(tt,{authorName:nt(l,j),prId:i.id,finding:j,editable:!1,prUrl:i.githubUrl,repoFullName:i.repoFullName,headSha:L,posting:!1,postError:null,inChangeset:j.fileInDiff,priorStatus:C.get(j.id),alreadyPosted:B.has(j.id),actions:!1,selection:p,docReferences:(m==null?void 0:m.docReferences)??null},r.key)}case"earlier_finding":{const j=M.get(N.priorFindingId);if(j==null)break;return e.jsx(gs,{item:j,muted:!1,findingsById:k,headMoved:((H=u==null?void 0:u.followUp)==null?void 0:H.headMoved)??!1,changedPaths:Z,actions:!1,selection:p,by:v},r.key)}case"thread":return e.jsx(st,{t:N.assessment,muted:!1,actions:!1,selection:p,notCheckedNote:N.inReview?void 0:`${F} didn’t check this one.`,by:F},r.key);case"ci":return e.jsx(et,{f:N.item,suggestion:N.item.suggestion,changedPaths:Z,selection:p},r.key);case"sonar":return e.jsx(Zs,{issue:N.issue,selection:p},r.key);case"story":{const j=_.get(N.ticketReviewId),S=j==null?void 0:j.items.find($=>$.id===N.itemId);if(j==null||S==null)break;const b={review:j,viewedPrId:i.id,changedPaths:Z,actions:!1,selection:p};if(S.status==="missing")return e.jsx(ps,{item:S,...b},r.key);const h=(T=j.assessment)==null?void 0:T.criteria.find($=>$.ref===S.ref);if(h==null||h.status==="met")break;return e.jsx(fs,{c:h,item:S,...b},r.key)}}return e.jsx(wt,{item:r,picked:d,cut:f,disabled:a,onToggle:w},r.key)};return e.jsxs("div",{className:"space-y-3",children:[pt.filter(r=>g.has(r)).map(r=>{const d=g.get(r),f=d.filter(p=>z(p,t)).length,w=f===d.length;return e.jsxs("section",{"aria-label":Ce(r,v),children:[e.jsxs("div",{className:"mb-1 flex items-center gap-2",children:[e.jsx("h4",{className:"text-meta font-semibold text-ui-ink",children:Ce(r,v)}),e.jsxs("span",{className:"text-meta text-ui-mute",children:[f," of ",d.length]}),e.jsx("button",{type:"button",className:"hit ml-auto text-meta text-ui-accent hover:underline disabled:opacity-50",disabled:a,onClick:()=>c(d.map(p=>p.key),!w),children:w?"Untick all":"Tick all"})]}),e.jsx("ul",{className:"space-y-1.5",children:d.map(P)})]},r)}),o.size>0&&e.jsxs("p",{className:"text-meta text-ui-warn",children:[o.size," ticked ",o.size===1?"item does":"items do"," not fit in one fix and will be left out. Untick something to make room."]})]})}const yt="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3",Fe="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3";function Ke(s){return{prId:s.id,repoId:s.repoId,repoFullName:s.repoFullName,prNumber:s.number,prTitle:s.title,githubUrl:s.githubUrl}}function Rt(s){const t=bs({mergeable:s.mergeable,mergeStateStatus:s.mergeStateStatus,isDraft:s.isDraft,inMergeQueue:s.inMergeQueue===!0}).verdict;return js({state:s.state,verdict:t,viewerCanPush:s.viewerCanPush})}function Pt({pr:s}){const t=Ns(s.id),n=vs(),a=ws(t),i=s.baseRefName??"the base branch";x.useEffect(()=>{ks(s.id)},[s.id]);const l=Ke(s),o=(t==null?void 0:t.phase)==="succeeded"?t.resolution:null;return e.jsxs("div",{className:"mb-3 rounded-ui bg-ui-surface p-3",children:[e.jsxs("div",{className:"flex items-center gap-1.5 text-body font-medium text-ui-ink",children:[e.jsx(ys,{size:13,className:"text-ai-signal"}),"This branch conflicts with ",i]}),e.jsxs("p",{className:"mt-1 text-meta text-ui-mute",children:[n," reads the code and picks a result for each conflict. You check its choices in the resolver before anything is pushed."]}),a&&t!=null?e.jsxs("div",{className:"mt-2",children:[e.jsx(ie,{active:!0,label:`Resolving conflicts with ${n}`,timeConstantSec:60}),e.jsxs("div",{className:"mt-1 flex items-center gap-2 text-meta text-ui-mute",children:[Rs(t.phase,n),e.jsx("button",{type:"button",className:Fe,onClick:()=>Ps(s.id),children:"Cancel"})]})]}):e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2",children:[e.jsx(q,{kind:"resolve",children:e.jsx("button",{type:"button",className:yt,onClick:()=>$e(l,{providerName:n}),children:o!=null?"Run again":`Resolve merge conflicts with ${n}`})}),o!=null&&e.jsxs(e.Fragment,{children:[e.jsx("button",{type:"button",className:Fe,onClick:()=>Cs(l),children:"Open the resolver"}),e.jsxs("span",{className:"text-meta text-ui-mute",children:[G(o)," decided ",o.choices.length," of ",o.decidableTotal," change",o.decidableTotal===1?"":"s","."]})]}),(t==null?void 0:t.phase)==="failed"&&t.error!=null&&e.jsx("span",{className:"text-meta text-ui-danger",children:t.error}),(t==null?void 0:t.phase)==="cancelled"&&e.jsx("span",{className:"text-meta text-ui-mute",children:"Cancelled."})]})]})}const se="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3";function te(s){return s instanceof Le||s instanceof Error?s.message:"Something went wrong"}const ee="inline-flex h-7 items-center whitespace-nowrap rounded-ui bg-ui-raise px-2.5 text-meta font-medium text-ui-ink hover:bg-ui-hover disabled:cursor-not-allowed disabled:opacity-50 touch:h-10 touch:px-3";function Ct({children:s,info:t}){return e.jsxs("div",{className:"flex items-center gap-1.5 px-4 pb-2 pt-4 text-title text-ui-ink md:px-5",children:[e.jsx("h3",{children:s}),t]})}function Ot({pr:s}){const t=Ue().enabled,n=O(c=>c.aiFixTabFocus),a=O(c=>c.consumeAiFixTabFocus),[i,l]=x.useState(null),[o,g]=x.useState(null);return x.useEffect(()=>{n&&n.prId===s.id&&(n.reviewId!=null&&l(n.reviewId),n.only!=null&&g(n.only),a())},[n,s.id,a]),t?e.jsx("div",{className:"pb-6",children:e.jsx(At,{pr:s,handoffReviewId:i,onlySection:o,onOnlySectionApplied:()=>g(null)})}):e.jsx(e.Fragment,{})}function At({pr:s,handoffReviewId:t,onlySection:n,onOnlySectionApplied:a}){var oe,ue,de,me,he,xe,pe,fe,ge,be;const{data:i,isLoading:l}=As(s.id,!0),{data:o,isLoading:g}=Fs(s.id),c=(o==null?void 0:o.review)??null,m=ct(t,c,(o==null?void 0:o.history)??[]),v=ot(t,m,c),F=O(A=>A.setPrDetailTab),u=Is("fix",Xs),k=Be(u.chosen.provider),M=Ts().data,C=Ss(s.id),B=Es(s.id),E=Te(s.id),I=Ue(),y=_s(M,u.chosen.provider,"plan"),[D,R]=x.useState(!1),_=D&&y,L=O(A=>A.noteAiFixRun),P=Ie({mutationKey:Ms(s.id)})>0,r=((oe=i==null?void 0:i.fix)==null?void 0:oe.status)??null,d=r==="planned",f=d&&((de=(ue=i==null?void 0:i.fix)==null?void 0:ue.plan)==null?void 0:de.status)==="planning",w=r==="running"||r==="queued"||P||f,{status:p}=Bs(s.id,w),N=(p==null?void 0:p.status)??r??"idle",H=N==="running"||N==="queued",T=H&&!d,j=d&&(f||H),S=d&&(((he=(me=i==null?void 0:i.fix)==null?void 0:me.plan)==null?void 0:he.status)==="planning"||((pe=(xe=i==null?void 0:i.fix)==null?void 0:xe.plan)==null?void 0:pe.status)==="plan_ready"),b=(i==null?void 0:i.fix)??null,h=Ls(s.id,m,(i==null?void 0:i.enabled)!==!1&&(m!=null||!g)),$=m==null&&h.data!=null&&h.data.sourceReviewId==null&&h.data.items.length>0,K=m!=null||$,Ve=$s(m!=null&&(c==null?void 0:c.id)!==m?m:null),ze=m!=null&&(c==null?void 0:c.id)===m?c:Ve.data??null,[X,Q]=x.useState({});x.useEffect(()=>Q({}),[m]);const[Ge,W]=x.useState(!1);x.useEffect(()=>{n==null||!h.data||(Q(bt(h.data,n)),W(!0),a())},[n,h.data,a]);const re=!S&&(Ge||!b||b.status!=="succeeded"),J=Rt(s),[le,Xe]=x.useState(!1),Y=Us(s.id,s.repoFullName),U=h.data?gt(h.data,X):[],ce=h.data?We(h.data,X).size:0,Qe=()=>{if(!K||!h.data||U.length===0)return;const A={model:u.chosen.model,provider:u.chosen.provider,seed:"review",...m!=null?{sourceReviewId:m}:{},include:U,...Y.names.length>0?{checks:Y.names}:{}};_?B.mutate(A,{onSuccess:()=>W(!1)}):(L({prId:s.id,repoFullName:s.repoFullName,prNumber:s.number,prTitle:s.title}),C.mutate(A,{onSuccess:()=>W(!1)})),le&&J&&$e(Ke(s),{model:Hs(u.chosen.model),providerName:I.providerName})};return e.jsxs("div",{children:[e.jsx(Ct,{info:e.jsxs(ne,{title:"AI Fix",children:[e.jsxs("p",{children:[k," reads the PR's code and edits files in a private copy. It has no shell. Tick Run tests to let it run the project's own test, lint and type checks in a sandbox."]}),e.jsx("p",{children:"A fix starts from an AI review. Pick what it should work on: the review's findings, earlier findings still open, threads the review says need a fix, unanswered threads, CI failures and ticket gaps are ticked; style bot comments are not."}),e.jsx("p",{children:"With no AI review, a fix can start from a finished CI check: it works on the failures the check says this PR can fix."}),e.jsxs("p",{children:["Tick Plan first to read ",k,"'s plan, steer it and approve it before anything changes."]}),e.jsxs("p",{children:[k," reports what it changed in each file and what it left alone, and why."]}),e.jsx("p",{children:"Nothing is pushed until you press Push. An automatic fix on your own PR is pushed for you only when Push automatically is on in Settings."})]}),children:"AI Fix"}),e.jsxs("div",{className:"px-4",children:[J&&e.jsx(Pt,{pr:s}),(i==null?void 0:i.enabled)===!1?e.jsx("p",{className:"text-body text-ui-mute",children:"The agentic fixer is turned off."}):e.jsxs(e.Fragment,{children:[!T&&!S&&!K&&!g&&!h.isLoading&&e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("p",{className:"text-meta text-ui-mute",children:(c==null?void 0:c.status)==="running"||(c==null?void 0:c.status)==="queued"?"A fix starts from an AI review. One is running.":"A fix starts from a finished AI review."}),e.jsx("button",{type:"button",className:se,onClick:()=>F(s.id,"claude_review"),children:"Open AI Review"})]}),!T&&!S&&K&&!re&&e.jsx("button",{type:"button",className:ee,onClick:()=>W(!0),children:"New fix"}),!T&&K&&re&&e.jsxs("div",{children:[v&&e.jsx("p",{className:"mb-2 text-meta text-ui-mute",children:v}),$&&e.jsx("p",{className:"mb-2 text-meta text-ui-mute",children:"No AI review yet. This fix works on the CI check’s failures."}),h.isLoading&&e.jsx("p",{className:"text-meta text-ui-mute",children:"Loading…"}),h.isError&&m!=null&&e.jsx("p",{className:"text-meta text-ui-danger",children:te(h.error)}),h.data&&e.jsx(kt,{preview:h.data,overrides:X,onChange:Q,disabled:P,pr:s,review:ze}),h.data&&h.data.items.length>0&&J&&e.jsxs("label",{className:"mt-3 flex cursor-pointer items-start gap-2 text-meta text-ui-ink",children:[e.jsx(V,{className:"mt-0.5",checked:le,disabled:P,onChange:A=>Xe(A.target.checked)}),e.jsxs("span",{children:["Also resolve merge conflicts with ",s.baseRefName??"the base branch",e.jsx("span",{className:"block text-meta text-ui-mute",children:"You check the merge in the resolver. It lands as its own commit, apart from the fix."})]})]}),h.data&&h.data.items.length>0&&e.jsx(Os,{state:Y,disabled:P}),h.data&&h.data.items.length>0&&e.jsxs("div",{className:"mt-3 flex flex-wrap items-center gap-2 max-md:sticky max-md:bottom-0 max-md:z-20 max-md:-mx-4 max-md:bg-ui-surface max-md:px-4 max-md:py-3",children:[e.jsx(Ds,{state:u,onPick:u.pick,disabled:P}),y&&e.jsxs("label",{className:"flex cursor-pointer items-center gap-1.5 text-meta text-ui-ink",children:[e.jsx(V,{checked:_,disabled:P,onChange:A=>R(A.target.checked)}),"Plan first"]}),e.jsx(q,{kind:"fix",auth:i==null?void 0:i.auth,anyProvider:!0,provider:u.chosen.provider,children:e.jsx("button",{type:"button",className:se,disabled:P||U.length===0||Y.on&&Y.query.isLoading,onClick:Qe,children:_?"Make a plan":"Start fix"})}),e.jsx("span",{className:"text-meta text-ui-mute",children:U.length===0?"Nothing ticked.":`${U.length-ce} item${U.length-ce===1?"":"s"}`}),(b==null?void 0:b.status)==="succeeded"&&e.jsx("button",{type:"button",className:ee,onClick:()=>W(!1),children:"Close"})]})]}),T&&e.jsx("button",{type:"button",className:ee,onClick:()=>E.mutate(),children:"Cancel"}),(C.isError||B.isError)&&e.jsx("p",{className:"mt-1 text-meta text-ui-danger",children:te(C.error??B.error)}),b&&d&&e.jsx(lt,{pr:s,fix:b,liveStatus:p,passRunning:j,auth:i==null?void 0:i.auth}),T&&e.jsxs("div",{className:"mt-3",children:[b&&e.jsx(He,{fix:b}),e.jsx(ie,{active:!0,label:"Running AI fix",value:Ee(p),timeConstantSec:40}),e.jsxs("div",{className:"mt-1 flex items-center gap-2 text-meta text-ui-mute",children:[(b==null?void 0:b.trigger)==="auto"&&e.jsx(qe,{}),_e[((fe=p==null?void 0:p.progress)==null?void 0:fe.phase)??""]??"Working…"]}),e.jsx(Me,{lines:(ge=p==null?void 0:p.progress)==null?void 0:ge.recentActivity,runKey:(b==null?void 0:b.id)??null,className:"mt-1"})]}),!T&&b&&!d&&e.jsx(It,{pr:s,fix:b,headInfo:(i==null?void 0:i.headInfo)??null,viewerCanPush:(i==null?void 0:i.viewerCanPush)??!1}),l&&!b&&e.jsx("p",{className:"mt-3 text-meta text-ui-mute",children:"Loading…"})]}),e.jsx(St,{history:(i==null?void 0:i.history)??[],currentFixId:((be=i==null?void 0:i.fix)==null?void 0:be.id)??null})]})]})}function qe(){return e.jsx("span",{className:"inline-flex items-center rounded-ui bg-ui-raise px-1.5 py-px text-meta font-medium text-ui-mute",children:"Auto fix"})}function Ft(s){return Se(s.provider,s.model,s.effort)}function It({pr:s,fix:t,headInfo:n,viewerCanPush:a}){var o,g,c;const i=x.useMemo(()=>Ws(t.patch),[t.patch]);if(t.status==="failed"&&((o=t.plan)==null?void 0:o.status)==="failed")return e.jsxs("div",{className:"mt-3 rounded-ui border border-ui-danger/40 bg-ui-danger/10 p-2 text-meta text-ui-danger",children:[G(t.plan??t)," could not make a plan: ",t.error??"unknown error"]});if(t.status==="cancelled"&&((g=t.plan)==null?void 0:g.status)==="discarded")return e.jsx("p",{className:"mt-3 text-meta text-ui-mute",children:"The plan was discarded."});if(t.status==="failed")return e.jsxs("div",{className:"mt-3 rounded-ui border border-ui-danger/40 bg-ui-danger/10 p-2 text-meta text-ui-danger",children:["The fix run failed: ",t.error??"unknown error"]});if(t.status==="cancelled")return e.jsx("p",{className:"mt-3 text-meta text-ui-mute",children:"The fix run was cancelled."});if(t.status!=="succeeded")return e.jsx(e.Fragment,{});const l=!t.patch||t.filesChanged.length===0;return e.jsxs("div",{className:"mt-3 border-t border-ui-line pt-3",children:[e.jsxs("p",{className:"mb-1 text-meta text-ui-mute",children:[t.trigger==="auto"&&e.jsxs(e.Fragment,{children:[e.jsx(qe,{})," "]}),ht(t)," · ",Ft(t),t.finishedAt&&e.jsxs(e.Fragment,{children:[" · ",ae(t.finishedAt)]})]}),t.pushedAt==null&&t.error&&e.jsx("p",{className:"mb-2 rounded-ui bg-ui-raise p-2 text-meta text-ui-warn",children:t.error}),e.jsx(He,{fix:t}),t.summary&&e.jsx("div",{className:"prose prose-sm max-w-none dark:prose-invert",children:e.jsx(Ys,{children:t.summary})}),e.jsx(xt,{pr:s,fix:t}),e.jsx(Ks,{checks:t.checks}),l?e.jsx("p",{className:"mt-2 text-meta text-ui-mute",children:"No files changed."}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mb-1 mt-2 flex items-center justify-between gap-3 text-meta text-ui-mute",children:[e.jsxs("span",{children:[t.filesChanged.length," file",t.filesChanged.length===1?"":"s"," changed",!((c=t.checks)!=null&&c.ran)&&" · Not built or tested here."]}),e.jsx(qs,{})]}),e.jsx("div",{className:"overflow-hidden rounded-ui bg-ui-surface text-ui-ink",children:e.jsx(Vs,{files:i})}),e.jsx(Et,{pr:s,fix:t,headInfo:n,viewerCanPush:a})]})]})}function Tt({fix:s}){return e.jsxs("div",{className:"mb-2 rounded-ui bg-ui-raise p-2 text-meta text-ui-success",children:[e.jsxs("div",{children:["Pushed to ",e.jsx("span",{className:"font-mono",children:s.pushedBranch}),s.pushedPrUrl&&e.jsxs(e.Fragment,{children:[" · ",e.jsxs("a",{href:Oe(s.pushedPrUrl),target:"_blank",rel:"noreferrer",className:"hit underline",children:["PR #",s.pushedPrNumber]})]}),s.pushedAt&&e.jsxs(e.Fragment,{children:[" · ",ae(s.pushedAt)]})]}),s.commitMessage&&e.jsx("div",{className:"mt-1 font-mono text-meta text-ui-success",children:s.commitMessage})]})}function St({history:s,currentFixId:t}){const n=s.filter(a=>a.pushedAt!=null&&a.id!==t);return n.length===0?null:e.jsxs("div",{className:"mt-6",children:[e.jsx("div",{className:"mb-2 text-title text-ui-ink",children:"Earlier fixes pushed"}),e.jsx("ul",{className:"space-y-2",children:n.map(a=>e.jsxs("li",{className:"text-meta",children:[e.jsx("div",{className:"font-mono text-ui-ink",children:a.commitMessage??"(no commit message)"}),e.jsxs("div",{className:"mt-0.5 text-meta text-ui-faint",children:[a.pushedBranch&&e.jsx("span",{className:"font-mono",children:a.pushedBranch}),a.pushedPrUrl&&e.jsxs(e.Fragment,{children:[" · ",e.jsxs("a",{href:Oe(a.pushedPrUrl),target:"_blank",rel:"noreferrer",className:"hit underline",children:["PR #",a.pushedPrNumber]})]}),a.pushedAt&&e.jsxs(e.Fragment,{children:[" · ",ae(a.pushedAt)]})]})]},a.id))})]})}function Et({pr:s,fix:t,headInfo:n,viewerCanPush:a}){const i=zs(s.id),l=(n==null?void 0:n.canPushSameBranch)??!1,[o,g]=x.useState(l?"existing":"new"),[c,m]=x.useState((n==null?void 0:n.suggestedBranch)??""),v=x.useRef(!1);if(x.useEffect(()=>{!v.current&&(n!=null&&n.suggestedBranch)&&(m(n.suggestedBranch),v.current=!0,n.canPushSameBranch||g("new"))},[n]),t.pushedAt!=null)return e.jsx(Tt,{fix:t});if(!a)return e.jsx("p",{className:"mt-2 text-meta text-ui-mute",children:"You need write access to push this fix."});const F=o==="new"&&c.trim().length===0,u=i.isPending,k=()=>{i.mutate({fixId:t.id,body:{target:o,branch:o==="new"?c.trim():void 0}})};return e.jsxs("div",{className:"mt-3 rounded-ui bg-ui-surface p-2",children:[t.commitMessage&&e.jsx("div",{className:"mb-2 rounded-ui bg-ui-surface px-2 py-1 font-mono text-meta text-ui-ink",title:"Commit message",children:t.commitMessage}),e.jsxs("label",{className:`flex items-center gap-2 text-meta ${l?"":"opacity-40"}`,title:l?void 0:"The PR head is a fork you cannot push to — use a new branch",children:[e.jsx(ve,{className:"mt-0",checked:o==="existing",disabled:!l||u,onChange:()=>g("existing")}),"Push to the PR branch",(n==null?void 0:n.headRef)&&e.jsxs("span",{className:"font-mono text-ui-faint",children:["(",n.headRef,")"]})]}),e.jsxs("label",{className:"mt-1 flex items-center gap-2 text-meta",children:[e.jsx(ve,{className:"mt-0",checked:o==="new",disabled:u,onChange:()=>g("new")}),"New branch + open a PR"]}),o==="new"&&e.jsx("input",{type:"text",className:"mt-1 w-full rounded-ui bg-ui-raise px-2 py-1 font-mono text-meta ring-1 ring-inset ring-ui-line focus:outline-none focus:ring-2 focus:ring-ui-accent touch:min-h-10",value:c,disabled:u,onChange:M=>m(M.target.value),placeholder:"branch-name"}),e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",className:se,disabled:u||F,onClick:k,children:u?"Pushing…":o==="new"?"Push + open PR":"Push"}),e.jsx(Gs,{checks:t.checks})]}),i.isError&&e.jsx("div",{className:"mt-2 text-meta text-ui-danger",children:te(i.error)})]})}export{Ot as AiFixTab};
|