pierre-review 0.1.77 → 0.1.78
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/api/routes/prs.js +49 -46
- package/dist/db/queries.js +81 -15
- package/package.json +1 -1
- package/public/assets/{AiFixTab-CMJeCtop.js → AiFixTab-DMoHaevz.js} +1 -1
- package/public/assets/{ClaudeReviewTab-zKtMbB1-.js → ClaudeReviewTab-CBzfgOYF.js} +1 -1
- package/public/assets/{index-CxFDrc36.js → index-C1obqq3D.js} +27 -27
- package/public/index.html +1 -1
package/dist/api/routes/prs.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { config } from '../../config.js';
|
|
3
3
|
import { getAccessToken, getAccountUserId } from '../../auth/account.js';
|
|
4
4
|
import { fetchActionsJobLog } from '../../github/actions-logs.js';
|
|
5
|
-
import { getMentionCandidates, getPrDetail, getPrFilesContext, getPrWriteContext, getReviewerLogins, getResolvableBotThreads, getUsersByLogins, markAllViewed, markPrMergedLocally, markPrViewed, upsertLocalPrComment, upsertLocalReview, } from '../../db/queries.js';
|
|
5
|
+
import { getMentionCandidates, getPrDetail, getPrFilesContext, getPrWriteContext, getReviewerLogins, getResolvableBotThreads, getSuggestedReviewersBasis, getUsersByLogins, markAllViewed, markPrMergedLocally, markPrViewed, stampReviewRequests, upsertLocalPrComment, upsertLocalReview, } from '../../db/queries.js';
|
|
6
6
|
import { getCodeownersMatch } from '../../github/codeowners.js';
|
|
7
7
|
import { suggestTeamsFromHistory } from '../../github/team-reviewers.js';
|
|
8
8
|
import { buildFileAnchors, fallbackAnchor, isFindingAnchored, } from '../../github/diff-anchor.js';
|
|
@@ -155,29 +155,25 @@ const requestReviewersSchema = {
|
|
|
155
155
|
},
|
|
156
156
|
},
|
|
157
157
|
};
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
158
|
+
// Build the CORE "Suggested reviewers" set for a PR — served as its OWN live query so it's
|
|
159
|
+
// never frozen inside the cached PR detail (it must empty the instant a reviewer is
|
|
160
|
+
// requested). Combines the history-USER basis (from synced data) with two best-effort,
|
|
161
|
+
// per-repo-cached network sources fetched in parallel:
|
|
161
162
|
// • CODEOWNERS — declared ownership for the touched paths (users + teams).
|
|
162
163
|
// • Team history — which team(s) are usually REQUESTED to review this repo (the behavioural
|
|
163
164
|
// fallback when CODEOWNERS declares no team; repo-level, so it runs even when the PR
|
|
164
165
|
// touches no owned path). See github/team-reviewers.ts.
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
// inferred team(s), then history users,
|
|
168
|
-
async function
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return detail;
|
|
175
|
-
const [owner, name] = detail.repoFullName.split('/');
|
|
176
|
-
if (!owner || !name)
|
|
177
|
-
return detail;
|
|
166
|
+
// Returns empty when the PR doesn't warrant suggestions. Any network failure (no CODEOWNERS,
|
|
167
|
+
// org wall, a repo that doesn't use team requests) degrades to just the history-user set.
|
|
168
|
+
// Precedence: declared CODEOWNERS owners, then the inferred team(s), then history users, cap 5.
|
|
169
|
+
async function buildSuggestedReviewers(basis, accountId) {
|
|
170
|
+
if (!basis.wants)
|
|
171
|
+
return { suggestedReviewers: [], users: [] };
|
|
172
|
+
const { owner, name, authorLogin, paths, suggestions, users } = basis;
|
|
173
|
+
const extraUsers = [];
|
|
174
|
+
let merged = suggestions;
|
|
178
175
|
try {
|
|
179
176
|
const token = await getAccessToken(accountId);
|
|
180
|
-
const paths = detail.files.map((f) => f.path);
|
|
181
177
|
const emptyMatch = { logins: [], teams: [] };
|
|
182
178
|
const [match, historyTeams] = await Promise.all([
|
|
183
179
|
paths.length > 0
|
|
@@ -185,13 +181,6 @@ async function enrichSuggestions(detail, accountId) {
|
|
|
185
181
|
: Promise.resolve(emptyMatch),
|
|
186
182
|
suggestTeamsFromHistory(token, owner, name, accountId),
|
|
187
183
|
]);
|
|
188
|
-
if (match.logins.length === 0 &&
|
|
189
|
-
match.teams.length === 0 &&
|
|
190
|
-
historyTeams.length === 0)
|
|
191
|
-
return detail;
|
|
192
|
-
const authorLogin = detail.authorId != null
|
|
193
|
-
? detail.users.find((u) => u.id === detail.authorId)?.githubLogin ?? null
|
|
194
|
-
: null;
|
|
195
184
|
// Resolve @user owners to synced users (avatar/link); unsynced owners show login-only.
|
|
196
185
|
// Exclude the PR author (GitHub rejects self-review requests).
|
|
197
186
|
const uniqueLogins = [...new Set(match.logins)].filter((l) => l !== authorLogin);
|
|
@@ -226,17 +215,11 @@ async function enrichSuggestions(detail, accountId) {
|
|
|
226
215
|
}));
|
|
227
216
|
// Merge = precedence: CODEOWNERS users, CODEOWNERS teams (declared), inferred teams, then
|
|
228
217
|
// the history-user suggestions. Dedup users by login and teams by slug (so a team that's
|
|
229
|
-
// both a CODEOWNER and historically requested shows once, as the CODEOWNER)
|
|
230
|
-
// row stays digestible.
|
|
218
|
+
// both a CODEOWNER and historically requested shows once, as the CODEOWNER).
|
|
231
219
|
const seenLogins = new Set();
|
|
232
220
|
const seenTeams = new Set();
|
|
233
|
-
|
|
234
|
-
for (const s of [
|
|
235
|
-
...codeownerUsers,
|
|
236
|
-
...codeownerTeams,
|
|
237
|
-
...historyTeamSuggestions,
|
|
238
|
-
...detail.suggestedReviewers,
|
|
239
|
-
]) {
|
|
221
|
+
merged = [];
|
|
222
|
+
for (const s of [...codeownerUsers, ...codeownerTeams, ...historyTeamSuggestions, ...suggestions]) {
|
|
240
223
|
if (s.kind === 'team') {
|
|
241
224
|
if (s.teamSlug && !seenTeams.has(s.teamSlug)) {
|
|
242
225
|
seenTeams.add(s.teamSlug);
|
|
@@ -248,17 +231,16 @@ async function enrichSuggestions(detail, accountId) {
|
|
|
248
231
|
merged.push(s);
|
|
249
232
|
}
|
|
250
233
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const known = new Set(detail.users.map((u) => u.id));
|
|
234
|
+
// The newly-resolved codeowner users the basis didn't already carry.
|
|
235
|
+
const known = new Set(users.map((u) => u.id));
|
|
254
236
|
for (const u of resolved)
|
|
255
237
|
if (!known.has(u.id))
|
|
256
|
-
|
|
238
|
+
extraUsers.push(u);
|
|
257
239
|
}
|
|
258
240
|
catch {
|
|
259
|
-
/* best-effort:
|
|
241
|
+
/* best-effort: fall back to the history-user suggestions */
|
|
260
242
|
}
|
|
261
|
-
return
|
|
243
|
+
return { suggestedReviewers: merged.slice(0, 5), users: [...users, ...extraUsers] };
|
|
262
244
|
}
|
|
263
245
|
export async function prRoutes(app) {
|
|
264
246
|
// Bulk "mark all seen": stamp every open PR (optionally scoped to repoIds) viewed
|
|
@@ -279,11 +261,25 @@ export async function prRoutes(app) {
|
|
|
279
261
|
}
|
|
280
262
|
// Cloud lean mode: fill in bulky text from GitHub (no-op in local). The client
|
|
281
263
|
// caches the result in IndexedDB keyed by updatedAt so unchanged PRs don't refetch.
|
|
282
|
-
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
264
|
+
// (Suggested reviewers are NOT here — they're a separate live query, see below — so the
|
|
265
|
+
// cached detail never freezes a stale suggestion.)
|
|
266
|
+
return hydratePrDetail(pr, accountId);
|
|
267
|
+
});
|
|
268
|
+
// Suggested reviewers — its OWN live query (not embedded in the cached PR detail) so it
|
|
269
|
+
// always reflects current state: it empties the instant a reviewer is requested (the assign
|
|
270
|
+
// route stamps review_requests locally), rather than staying frozen until the PR's updatedAt
|
|
271
|
+
// next bumps. Best-effort network enrichment (CODEOWNERS + inferred team) on top of the
|
|
272
|
+
// synced history basis. 404s when the PR isn't the caller's.
|
|
273
|
+
app.get('/api/prs/:id/suggested-reviewers', { schema: idParamSchema }, async (req, reply) => {
|
|
274
|
+
const { id } = req.params;
|
|
275
|
+
const accountId = accountIdOf(req);
|
|
276
|
+
const basis = await getSuggestedReviewersBasis(id, accountId);
|
|
277
|
+
if (!basis) {
|
|
278
|
+
reply.status(404);
|
|
279
|
+
return { error: 'NotFound', message: `PR ${id} not found` };
|
|
280
|
+
}
|
|
281
|
+
const result = await buildSuggestedReviewers(basis, accountId);
|
|
282
|
+
return result;
|
|
287
283
|
});
|
|
288
284
|
// Candidates for an @mention autocomplete, ranked by proximity to this PR
|
|
289
285
|
// (participants first, then repo people), self + bots excluded. Account-scoped:
|
|
@@ -790,7 +786,8 @@ export async function prRoutes(app) {
|
|
|
790
786
|
// resolve to logins (also drops bots + unknown ids). Union with any direct logins
|
|
791
787
|
// (suggested reviewers we haven't synced as users), deduped.
|
|
792
788
|
const wanted = userIds.filter((uid) => uid !== ctx.authorId);
|
|
793
|
-
const
|
|
789
|
+
const resolved = await getReviewerLogins(wanted); // [{ userId, login }]
|
|
790
|
+
const resolvedLogins = resolved.map((r) => r.login);
|
|
794
791
|
const logins = [...new Set([...resolvedLogins, ...directLogins])];
|
|
795
792
|
const teams = [...new Set(teamSlugs)];
|
|
796
793
|
if (logins.length === 0 && teams.length === 0) {
|
|
@@ -803,6 +800,12 @@ export async function prRoutes(app) {
|
|
|
803
800
|
try {
|
|
804
801
|
const token = await getAccessToken(accountId);
|
|
805
802
|
await requestReviewers(token, ctx.owner, ctx.name, ctx.number, logins, teams);
|
|
803
|
+
// Optimistically stamp the request locally (mirrors approve/comment/merge) so the
|
|
804
|
+
// "Requested" row + the suggestion gate reflect it immediately; the next sync
|
|
805
|
+
// re-derives review_requests idempotently. Team handle = `owner/slug` (matches how a
|
|
806
|
+
// CODEOWNERS team + the suggestion render). Only the synced-user ids are stamped;
|
|
807
|
+
// unsynced direct logins land on the next sync.
|
|
808
|
+
await stampReviewRequests(ctx.prId, resolved.map((r) => r.userId), teams.map((slug) => `${ctx.owner}/${slug}`));
|
|
806
809
|
const result = {
|
|
807
810
|
status: 'ok',
|
|
808
811
|
requestedLogins: logins,
|
package/dist/db/queries.js
CHANGED
|
@@ -3606,6 +3606,83 @@ export async function getUsersByLogins(logins) {
|
|
|
3606
3606
|
.execute();
|
|
3607
3607
|
return rows.map(mapUser);
|
|
3608
3608
|
}
|
|
3609
|
+
export async function getSuggestedReviewersBasis(id, accountId) {
|
|
3610
|
+
const rows = await db
|
|
3611
|
+
.select()
|
|
3612
|
+
.from(pullRequests)
|
|
3613
|
+
.innerJoin(repos, eq(repos.id, pullRequests.repoId))
|
|
3614
|
+
.where(and(eq(pullRequests.id, id), eq(repos.accountId, accountId)))
|
|
3615
|
+
.limit(1)
|
|
3616
|
+
.execute();
|
|
3617
|
+
const row = rows[0] ?? null;
|
|
3618
|
+
if (!row)
|
|
3619
|
+
return null;
|
|
3620
|
+
const pr = row.pull_requests;
|
|
3621
|
+
const repo = row.repos;
|
|
3622
|
+
const [reviewRows, reqRows] = await Promise.all([
|
|
3623
|
+
db.select({ id: reviews.id }).from(reviews).where(eq(reviews.prId, id)).execute(),
|
|
3624
|
+
db
|
|
3625
|
+
.select({ id: schema.reviewRequests.id })
|
|
3626
|
+
.from(schema.reviewRequests)
|
|
3627
|
+
.where(eq(schema.reviewRequests.prId, id))
|
|
3628
|
+
.execute(),
|
|
3629
|
+
]);
|
|
3630
|
+
const wants = pr.state === 'open' &&
|
|
3631
|
+
!pr.isDraft &&
|
|
3632
|
+
reqRows.length === 0 &&
|
|
3633
|
+
reviewRows.length === 0;
|
|
3634
|
+
const paths = (pr.files ?? []).map((f) => f.path);
|
|
3635
|
+
const suggestions = wants
|
|
3636
|
+
? await suggestReviewersFromHistory(accountId, pr.repoId, pr.authorId, paths)
|
|
3637
|
+
: [];
|
|
3638
|
+
const uids = new Set();
|
|
3639
|
+
for (const s of suggestions)
|
|
3640
|
+
if (s.userId != null)
|
|
3641
|
+
uids.add(s.userId);
|
|
3642
|
+
if (pr.authorId)
|
|
3643
|
+
uids.add(pr.authorId);
|
|
3644
|
+
const usersOut = uids.size > 0
|
|
3645
|
+
? (await db.select().from(users).where(inArray(users.id, [...uids])).execute()).map(mapUser)
|
|
3646
|
+
: [];
|
|
3647
|
+
const authorLogin = pr.authorId != null
|
|
3648
|
+
? usersOut.find((u) => u.id === pr.authorId)?.githubLogin ?? null
|
|
3649
|
+
: null;
|
|
3650
|
+
return {
|
|
3651
|
+
wants,
|
|
3652
|
+
owner: repo.owner,
|
|
3653
|
+
name: repo.name,
|
|
3654
|
+
authorId: pr.authorId,
|
|
3655
|
+
authorLogin,
|
|
3656
|
+
paths,
|
|
3657
|
+
suggestions,
|
|
3658
|
+
users: usersOut,
|
|
3659
|
+
};
|
|
3660
|
+
}
|
|
3661
|
+
// Optimistically record just-requested reviewers locally so the "Requested" row + the
|
|
3662
|
+
// suggestion gate reflect the assignment immediately, before the next sync re-derives
|
|
3663
|
+
// `review_requests` (idempotently). Inserts only rows not already present (the table has no
|
|
3664
|
+
// unique constraint, and a re-request must not duplicate). Mirrors the other write routes'
|
|
3665
|
+
// local-stamp pattern (approve / comment / merge).
|
|
3666
|
+
export async function stampReviewRequests(prId, userIds, teamNames) {
|
|
3667
|
+
if (userIds.length === 0 && teamNames.length === 0)
|
|
3668
|
+
return;
|
|
3669
|
+
const existing = await db
|
|
3670
|
+
.select()
|
|
3671
|
+
.from(schema.reviewRequests)
|
|
3672
|
+
.where(eq(schema.reviewRequests.prId, prId))
|
|
3673
|
+
.execute();
|
|
3674
|
+
const haveUser = new Set(existing.filter((r) => r.userId != null).map((r) => r.userId));
|
|
3675
|
+
const haveTeam = new Set(existing.filter((r) => r.teamName != null).map((r) => r.teamName));
|
|
3676
|
+
const toInsert = [];
|
|
3677
|
+
for (const uid of new Set(userIds))
|
|
3678
|
+
if (!haveUser.has(uid))
|
|
3679
|
+
toInsert.push({ prId, userId: uid, teamName: null });
|
|
3680
|
+
for (const tn of new Set(teamNames))
|
|
3681
|
+
if (!haveTeam.has(tn))
|
|
3682
|
+
toInsert.push({ prId, userId: null, teamName: tn });
|
|
3683
|
+
if (toInsert.length > 0)
|
|
3684
|
+
await db.insert(schema.reviewRequests).values(toInsert).execute();
|
|
3685
|
+
}
|
|
3609
3686
|
export async function getPrDetail(id, accountId) {
|
|
3610
3687
|
const rows = await db
|
|
3611
3688
|
.select()
|
|
@@ -3745,17 +3822,10 @@ export async function getPrDetail(id, accountId) {
|
|
|
3745
3822
|
userId: r.userId,
|
|
3746
3823
|
teamName: r.teamName,
|
|
3747
3824
|
}));
|
|
3748
|
-
//
|
|
3749
|
-
//
|
|
3750
|
-
//
|
|
3751
|
-
//
|
|
3752
|
-
const wantsSuggestions = pr.state === 'open' &&
|
|
3753
|
-
!pr.isDraft &&
|
|
3754
|
-
requestedReviewers.length === 0 &&
|
|
3755
|
-
reviewsOut.length === 0;
|
|
3756
|
-
const suggestedReviewers = wantsSuggestions
|
|
3757
|
-
? await suggestReviewersFromHistory(accountId, pr.repoId, pr.authorId, (pr.files ?? []).map((f) => f.path))
|
|
3758
|
-
: [];
|
|
3825
|
+
// NOTE: suggested reviewers are NOT part of PrDetail — they're served by their own live
|
|
3826
|
+
// query (GET /api/prs/:id/suggested-reviewers → getSuggestedReviewers below) so they never
|
|
3827
|
+
// freeze inside the aggressively-cached detail payload (they must empty the instant a
|
|
3828
|
+
// reviewer is requested). See SuggestedReviewersResponse.
|
|
3759
3829
|
// Gather referenced users for client-side lookup.
|
|
3760
3830
|
const userIds = new Set();
|
|
3761
3831
|
if (pr.authorId)
|
|
@@ -3781,9 +3851,6 @@ export async function getPrDetail(id, accountId) {
|
|
|
3781
3851
|
for (const r of reviewerRows)
|
|
3782
3852
|
if (r.userId)
|
|
3783
3853
|
userIds.add(r.userId);
|
|
3784
|
-
for (const s of suggestedReviewers)
|
|
3785
|
-
if (s.userId != null)
|
|
3786
|
-
userIds.add(s.userId);
|
|
3787
3854
|
// A maintainer who only merged the PR (never authored/reviewed/commented) is
|
|
3788
3855
|
// otherwise absent from userList, leaving "Merged by" unresolved.
|
|
3789
3856
|
if (pr.mergedById)
|
|
@@ -3891,7 +3958,6 @@ export async function getPrDetail(id, accountId) {
|
|
|
3891
3958
|
changedFilesCount: pr.changedFiles,
|
|
3892
3959
|
files: filesOut,
|
|
3893
3960
|
requestedReviewers,
|
|
3894
|
-
suggestedReviewers,
|
|
3895
3961
|
viewerCanApprove,
|
|
3896
3962
|
viewerCanPush,
|
|
3897
3963
|
viewerHasApprovedStanding,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pierre-review",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.78",
|
|
4
4
|
"description": "Dashboard for tracking your team's GitHub PR activity across repos — local (SQLite + gh) or self-hosted multi-tenant cloud (Postgres + GitHub App).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Alex Wakeman",
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{r as o,j as e}from"./tanstack-R-s9abdD.js";import{D as te,w as W,A as re,c as ne,d as ae,q as ie,G as ce,I as J,M as K,i as le,l as oe,k as de,C as ue,b as xe,R as Q,a as he,p as X,F as Z,r as I,E as ge,J as me,n as be,m as pe,y as fe,j as Y}from"./index-
|
|
1
|
+
import{r as o,j as e}from"./tanstack-R-s9abdD.js";import{D as te,w as W,A as re,c as ne,d as ae,q as ie,G as ce,I as J,M as K,i as le,l as oe,k as de,C as ue,b as xe,R as Q,a as he,p as X,F as Z,r as I,E as ge,J as me,n as be,m as pe,y as fe,j as Y}from"./index-C1obqq3D.js";import"./markdown-BUNN1sk6.js";import"./highlight-C8vPehS1.js";import"./vis-C3BCkP5U.js";const S="whitespace-nowrap rounded border border-blue-400 px-2.5 py-1 text-xs text-blue-600 hover:bg-blue-50 disabled:opacity-50 dark:border-blue-600 dark:text-blue-400 dark:hover:bg-blue-900/30",j="whitespace-nowrap rounded border border-gray-300 px-2.5 py-1 text-xs hover:border-gray-400 disabled:opacity-50 dark:border-gray-700 dark:hover:border-gray-500",ye={fetching_diff:"Reading the PR",cloning:"Checking out the code",fixing:"Applying the fix",capturing:"Capturing changes",persisting:"Saving"};function je(s){var t;if(!s||s.status==="idle")return null;if(s.status==="queued")return 6;const r=s.progress;if(!r)return 10;switch(r.phase){case"fetching_diff":return 12;case"cloning":return 28;case"fixing":return Math.min(90,45+(((t=r.recentActivity)==null?void 0:t.length)??0)*3);case"capturing":return 92;case"persisting":return 96;default:return 20}}const ke={cloning:"Checking out the code",applying_fix:"Applying the fix",fetching_trunk:"Fetching the trunk",rebasing:"Rebasing onto the trunk",merging:"Merging the trunk in",resolving_conflicts:"Resolving conflicts with Claude",verifying:"Verifying the result",pushing:"Pushing"};function Ne(s){var t;if(!s||s.status==="idle")return null;if(s.status==="queued")return 6;const r=s.progress;if(!r)return 10;switch(r.phase){case"cloning":return 15;case"applying_fix":return 25;case"fetching_trunk":return 35;case"rebasing":case"merging":return 45;case"resolving_conflicts":return Math.min(90,55+(((t=r.recentActivity)==null?void 0:t.length)??0)*3);case"verifying":return 92;case"pushing":return 96;default:return 20}}function L({children:s}){return e.jsx("div",{className:"px-4 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-gray-400",children:s})}const ve={high:"bg-green-100 text-green-700 dark:bg-green-950/40 dark:text-green-300",medium:"bg-amber-100 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300",low:"bg-red-100 text-red-700 dark:bg-red-950/40 dark:text-red-300"};function z({label:s,value:r}){return r?e.jsxs("span",{className:`rounded px-1.5 py-0.5 text-[10px] font-medium uppercase ${ve[r]}`,title:s==="Fixability"?"How confident the analysis is that Pierre's agent could fix this":"How confident the analysis is about the root cause",children:[s,": ",r]}):null}function De({pr:s}){const{aiAnalysis:r,aiFix:t}=te(),n=W(x=>x.aiFixTabFocus),i=W(x=>x.consumeAiFixTabFocus),[c,m]=o.useState(null);return o.useEffect(()=>{n&&n.prId===s.id&&(n.reviewText&&m(n.reviewText),i())},[n,s.id,i]),!r&&!t?e.jsx("div",{className:"p-4 text-sm text-gray-500",children:"AI Analysis and Fix is not enabled."}):e.jsxs("div",{className:"pb-6",children:[r&&e.jsx("div",{className:"border-b border-gray-200 dark:border-gray-800",children:e.jsx(re,{pr:s})}),e.jsx(Ce,{pr:s}),r&&e.jsx(Pe,{pr:s,canFix:t}),t&&e.jsx(Re,{pr:s,seedReviewText:c,onSeedConsumed:()=>m(null)})]})}function Ce({pr:s}){const r=s.checkRuns;return r.length===0?null:e.jsxs("div",{className:"border-b border-gray-200 dark:border-gray-800",children:[e.jsx(L,{children:"CI status"}),e.jsxs("div",{className:"px-4 pb-3",children:[e.jsx(ne,{prId:s.id,checks:r}),e.jsx(ae,{prId:s.id,checks:r,viewerCanPush:s.viewerCanPush})]})]})}function Pe({pr:s,canFix:r}){const{data:t}=ie(s.id,!0),n=ce(s.id),i=J(s.id),c=o.useMemo(()=>s.checkRuns.filter(l=>l.state==="failure"||l.state==="error").map(l=>({name:l.name,jobId:l.jobId,state:l.state})),[s.checkRuns]);if(!(c.length>0||((t==null?void 0:t.hasFailures)??!1))&&!(t!=null&&t.analysis))return null;const x=(t==null?void 0:t.fixability)==="low";return e.jsxs("div",{className:"border-b border-gray-200 dark:border-gray-800",children:[e.jsx(L,{children:"CI failure analysis"}),e.jsxs("div",{className:"px-4 pb-3",children:[(t==null?void 0:t.analysis)&&(t.rootCauseConfidence||t.fixability)&&e.jsxs("div",{className:"mb-1.5 flex flex-wrap items-center gap-1.5",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-wide text-gray-400",title:"How confident the analysis is — not a severity rating",children:"Confidence"}),e.jsx(z,{label:"Root cause",value:t.rootCauseConfidence}),e.jsx(z,{label:"Fixability",value:t.fixability})]}),t!=null&&t.analysis?e.jsx("div",{className:"prose prose-sm max-w-none dark:prose-invert",children:e.jsx(K,{children:t.analysis})}):e.jsx("p",{className:"text-sm text-gray-500",children:"Diagnose the failing CI: likely root cause + a suggested fix, from the full logs of every failing check."}),e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",className:j,disabled:n.isPending||c.length===0,onClick:()=>n.mutate(c),children:n.isPending?"Analyzing…":t!=null&&t.analysis?"Re-analyze":"Analyze CI failure"}),r&&(t==null?void 0:t.analysis)&&e.jsx("button",{type:"button",className:S,disabled:i.isPending,onClick:()=>i.mutate({model:"claude-sonnet-5",seed:"ci_analysis"}),title:"Launch an agent to fix the CI failure",children:"Fix it →"}),r&&x&&(t==null?void 0:t.analysis)&&e.jsx("span",{className:"text-[11px] text-gray-500",children:"low confidence this is auto-fixable"}),n.isError&&e.jsx("span",{className:"text-[11px] text-red-500",children:D(n.error)})]})]})]})}function Re({pr:s,seedReviewText:r,onSeedConsumed:t}){var k,F,N,A;const{data:n,isLoading:i}=le(s.id,!0),[c,m]=o.useState("claude-sonnet-5"),x=J(s.id),l=oe(s.id),p=((k=n==null?void 0:n.fix)==null?void 0:k.status)??null,h=p==="running"||p==="queued"||x.isPending,{status:d}=de(s.id,h),R=(d==null?void 0:d.status)??p??"idle",f=R==="running"||R==="queued",w=r?"review":"plain",M=()=>{x.mutate({model:c,seed:w,reviewText:r??void 0},{onSuccess:()=>t()})},u=(n==null?void 0:n.fix)??null;return e.jsxs("div",{children:[e.jsx(L,{children:"AI Fix"}),e.jsxs("div",{className:"px-4",children:[(n==null?void 0:n.enabled)===!1?e.jsx("p",{className:"text-sm text-gray-500",children:"The agentic fixer is disabled."}):(n==null?void 0:n.auth)==="none"?e.jsx("p",{className:"rounded border border-amber-300 bg-amber-50 p-2 text-xs text-amber-700 dark:border-amber-700 dark:bg-amber-950/30 dark:text-amber-300",children:n.authMessage??"No Claude authentication found. Sign in to Claude or set an API key, then restart."}):e.jsxs(e.Fragment,{children:[r&&!f&&e.jsx("div",{className:"mb-2 rounded border border-blue-200 bg-blue-50 p-2 text-xs text-blue-700 dark:border-blue-800 dark:bg-blue-950/30 dark:text-blue-300",children:"Ready to generate a fix from the selected review."}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("select",{className:"rounded border border-gray-300 bg-transparent px-2 py-1 text-xs dark:border-gray-700",value:c,onChange:y=>m(y.target.value),disabled:f,children:ue.map(y=>e.jsx("option",{value:y,children:xe[y]},y))}),f?e.jsx("button",{type:"button",className:j,onClick:()=>l.mutate(),children:"Cancel"}):e.jsx("button",{type:"button",className:S,disabled:x.isPending,onClick:M,children:u?"Generate new fix":"Generate fix"}),x.isError&&e.jsx("span",{className:"text-[11px] text-red-500",children:D(x.error)})]}),f&&e.jsxs("div",{className:"mt-3",children:[e.jsx(Q,{active:!0,label:"Running AI fix",value:je(d),timeConstantSec:40}),e.jsx("div",{className:"mt-1 text-[11px] text-gray-500",children:ye[((F=d==null?void 0:d.progress)==null?void 0:F.phase)??""]??"Working…"}),((N=d==null?void 0:d.progress)==null?void 0:N.recentActivity)&&d.progress.recentActivity.length>0&&e.jsx("pre",{className:"mt-1 max-h-32 overflow-auto rounded bg-gray-50 p-2 text-[11px] leading-relaxed text-gray-500 dark:bg-gray-900",children:d.progress.recentActivity.slice(-8).join(`
|
|
2
2
|
`)})]}),!f&&u&&e.jsx(we,{pr:s,fix:u,headInfo:(n==null?void 0:n.headInfo)??null,viewerCanPush:(n==null?void 0:n.viewerCanPush)??!1}),i&&!u&&e.jsx("p",{className:"mt-3 text-xs text-gray-400",children:"Loading…"})]}),e.jsx(Fe,{history:(n==null?void 0:n.history)??[],currentFixId:((A=n==null?void 0:n.fix)==null?void 0:A.id)??null})]})]})}function we({pr:s,fix:r,headInfo:t,viewerCanPush:n}){const i=o.useMemo(()=>X(r.patch),[r.patch]);if(r.status==="failed")return e.jsxs("div",{className:"mt-3 rounded border border-red-200 bg-red-50 p-2 text-xs text-red-700 dark:border-red-800 dark:bg-red-950/30 dark:text-red-300",children:["The fix run failed: ",r.error??"unknown error"]});if(r.status==="cancelled")return e.jsx("p",{className:"mt-3 text-xs text-gray-500",children:"The fix run was cancelled."});if(r.status!=="succeeded")return e.jsx(e.Fragment,{});const c=!r.patch||r.filesChanged.length===0;return e.jsxs("div",{className:"mt-3",children:[r.summary&&e.jsx("div",{className:"prose prose-sm max-w-none dark:prose-invert",children:e.jsx(K,{children:r.summary})}),c?e.jsx("p",{className:"mt-2 text-xs text-gray-500",children:"The agent made no changes."}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mb-1 mt-2 text-[11px] text-gray-500",children:[r.filesChanged.length," file",r.filesChanged.length===1?"":"s"," changed"]}),e.jsx("div",{className:"overflow-hidden rounded border border-gray-200 text-gray-800 dark:border-gray-800 dark:text-gray-200",children:e.jsx(Z,{files:i})}),e.jsx(Se,{pr:s,fix:r,headInfo:t,viewerCanPush:n})]})]})}function _({fix:s}){return e.jsxs("div",{className:"mb-2 rounded border border-green-200 bg-green-50 p-2 text-xs text-green-700 dark:border-green-800 dark:bg-green-950/30 dark:text-green-300",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:s.pushedPrUrl,target:"_blank",rel:"noreferrer",className:"underline",children:["PR #",s.pushedPrNumber]})]}),s.pushedAt&&e.jsxs(e.Fragment,{children:[" · ",I(s.pushedAt)]})]}),s.commitMessage&&e.jsx("div",{className:"mt-1 font-mono text-[11px] text-green-800 dark:text-green-300",children:s.commitMessage})]})}function Fe({history:s,currentFixId:r}){const t=s.filter(n=>n.pushedAt!=null&&n.id!==r);return t.length===0?null:e.jsxs("div",{className:"mt-4 border-t border-gray-200 pt-3 dark:border-gray-800",children:[e.jsx("div",{className:"mb-1.5 text-xs font-semibold uppercase tracking-wide text-gray-400",children:"Fixes pushed via Pierre"}),e.jsx("ul",{className:"space-y-2",children:t.map(n=>e.jsxs("li",{className:"text-xs",children:[e.jsx("div",{className:"font-mono text-gray-700 dark:text-gray-200",children:n.commitMessage??"(no commit message)"}),e.jsxs("div",{className:"mt-0.5 text-[11px] text-gray-400",children:[n.pushedBranch&&e.jsx("span",{className:"font-mono",children:n.pushedBranch}),n.pushedPrUrl&&e.jsxs(e.Fragment,{children:[" · ",e.jsxs("a",{href:n.pushedPrUrl,target:"_blank",rel:"noreferrer",className:"underline",children:["PR #",n.pushedPrNumber]})]}),n.pushedAt&&e.jsxs(e.Fragment,{children:[" · ",I(n.pushedAt)]})]})]},n.id))})]})}function Ae({preview:s,loading:r}){if(r)return e.jsx("p",{className:"text-[11px] text-gray-400",children:"Checking against the trunk…"});if(!s||!s.available||!s.trunkSha)return null;if(!s.clean){const t=s.conflictFiles;return e.jsxs("div",{className:"rounded border border-amber-300 bg-amber-50 p-2 text-[11px] text-amber-700 dark:border-amber-700 dark:bg-amber-950/30 dark:text-amber-300",children:["⚠ Conflicts with ",e.jsx("span",{className:"font-mono",children:s.trunk}),t.length>0?e.jsxs(e.Fragment,{children:[" in ",t.length," file",t.length===1?"":"s",e.jsxs("span",{className:"text-amber-600/90 dark:text-amber-400/90",children:[": ",t.slice(0,6).join(", "),t.length>6?"…":""]})]}):null,". Resolving before you push avoids a conflicted PR."]})}return s.behindBy>0?e.jsxs("p",{className:"text-[11px] text-gray-500",children:[e.jsx("span",{className:"font-mono",children:s.trunk})," is ",s.behindBy," ","commit",s.behindBy===1?"":"s"," ahead — the fix merges cleanly."]}):e.jsxs("p",{className:"text-[11px] text-green-600 dark:text-green-400",children:["Up to date with ",e.jsx("span",{className:"font-mono",children:s.trunk})," — no conflicts."]})}function Ee({resolved:s,target:r,pushing:t,onPush:n,onRedo:i,disabled:c}){const m=o.useMemo(()=>X(s.diff),[s.diff]);return e.jsxs("div",{className:"mt-2 space-y-2",children:[e.jsxs("div",{className:"rounded border border-green-200 bg-green-50 p-2 text-[11px] text-green-700 dark:border-green-800 dark:bg-green-950/30 dark:text-green-300",children:["Rebased onto ",e.jsx("span",{className:"font-mono",children:s.trunk}),".",s.resolvedConflicts?` Claude resolved conflicts in ${s.conflictFiles.length} file${s.conflictFiles.length===1?"":"s"}${s.conflictFiles.length?`: ${s.conflictFiles.join(", ")}`:""}.`:" No conflicts."," ","Review the result below, then push."]}),e.jsxs("div",{className:"text-[11px] text-gray-500",children:[s.filesChanged.length," file",s.filesChanged.length===1?"":"s"," in the rebased result"]}),e.jsx("div",{className:"overflow-hidden rounded border border-gray-200 text-gray-800 dark:border-gray-800 dark:text-gray-200",children:e.jsx(Z,{files:m})}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",className:S,disabled:t||c,onClick:n,children:t?"Pushing…":r==="new"?"Push rebased + open PR":"Push rebased (force-with-lease)"}),e.jsx("button",{type:"button",className:j,disabled:t,onClick:i,children:"Re-rebase"})]})]})}function Se({pr:s,fix:r,headInfo:t,viewerCanPush:n}){var V,G;const i=ge(s.id),c=me(),m=be(),x=pe(),l=fe(),p=(t==null?void 0:t.canPushSameBranch)??!1,[h,d]=o.useState(p?"existing":"new"),[R,f]=o.useState((t==null?void 0:t.suggestedBranch)??""),[w,M]=o.useState(!0),[u,k]=o.useState("idle"),[F,N]=o.useState(null),A=o.useRef(!1),y=o.useRef(!1);o.useEffect(()=>{!A.current&&(t!=null&&t.suggestedBranch)&&(f(t.suggestedBranch),A.current=!0,t.canPushSameBranch||d("new"))},[t]);const g=r.pushedAt!=null,q=g&&r.pushedPrNumber==null,U=!g||q;o.useEffect(()=>{!y.current&&n&&U&&(y.current=!0,l.mutate(r.id))},[n,U,r.id]);const a=l.data??null,{status:v}=Y(s.id,r.id,"rebase",u==="rebasing"),{status:C}=Y(s.id,r.id,"push",u==="pushing");if(o.useEffect(()=>{u==="rebasing"&&v&&v.status!=="running"&&v.status!=="queued"&&(k("idle"),v.error&&N(v.error))},[u,v]),o.useEffect(()=>{u==="pushing"&&C&&C.status!=="running"&&C.status!=="queued"&&(k("idle"),C.error&&N(C.error))},[u,C]),!n)return g?e.jsx(_,{fix:r}):e.jsx("p",{className:"mt-2 text-xs text-gray-500",children:"You need write access to this repository to push this fix."});if(g&&!q)return e.jsx(_,{fix:r});const B=h==="new"&&R.trim().length===0,P=u!=="idle"||i.isPending||c.isPending,H=r.model,ee=g&&!!a&&a.available&&a.clean&&a.behindBy===0,T=E=>{N(null),i.mutate({fixId:r.id,body:{target:h,branch:h==="new"?R.trim():void 0,strategy:E,autoResolve:w,model:H}},{onSuccess:se=>{"pushedBranch"in se||k("pushing")}})},O=()=>{N(null),c.mutate({fixId:r.id,body:{autoResolve:w,model:H}},{onSuccess:()=>k("rebasing")})},b=u==="rebasing"?v:C,$=r.resolved;return e.jsxs("div",{className:"mt-3 rounded border border-gray-200 p-2 dark:border-gray-800",children:[g&&e.jsx(_,{fix:r}),e.jsx("div",{className:"mb-2 text-xs font-semibold text-gray-600 dark:text-gray-300",children:g?"Reconcile with the trunk":"Push this fix"}),!g&&r.commitMessage&&e.jsxs("div",{className:"mb-2 rounded bg-gray-50 p-2 text-[11px] dark:bg-gray-900",children:[e.jsx("span",{className:"uppercase tracking-wide text-gray-400",children:"Commit message"}),e.jsx("div",{className:"mt-0.5 font-mono text-gray-700 dark:text-gray-200",children:r.commitMessage})]}),e.jsxs("label",{className:`flex items-center gap-2 text-xs ${p?"":"opacity-40"}`,title:p?void 0:"The PR head is a fork you cannot push to — use a new branch",children:[e.jsx("input",{type:"radio",checked:h==="existing",disabled:!p||P,onChange:()=>d("existing")}),"Push to the PR branch",(t==null?void 0:t.headRef)&&e.jsxs("span",{className:"font-mono text-gray-400",children:["(",t.headRef,")"]})]}),e.jsxs("label",{className:"mt-1 flex items-center gap-2 text-xs",children:[e.jsx("input",{type:"radio",checked:h==="new",disabled:P,onChange:()=>d("new")}),"New branch + open a PR"]}),h==="new"&&e.jsx("input",{type:"text",className:"mt-1 w-full rounded border border-gray-300 bg-transparent px-2 py-1 font-mono text-xs dark:border-gray-700",value:R,disabled:P,onChange:E=>f(E.target.value),placeholder:"branch-name"}),e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2",children:[e.jsx("div",{className:"min-w-0 flex-1",children:e.jsx(Ae,{preview:a,loading:l.isPending})}),e.jsx("button",{type:"button",className:j,disabled:l.isPending||P,onClick:()=>l.mutate(r.id),title:"Re-check this fix against the current trunk",children:l.isPending?"Checking…":"Re-check trunk"}),l.isError&&!l.isPending&&e.jsx("span",{className:"text-[11px] text-red-500",children:"Couldn't check the trunk — try again."})]}),u!=="idle"?e.jsxs("div",{className:"mt-2",children:[e.jsx(Q,{active:!0,label:u==="rebasing"?"Rebasing & resolving":"Pushing",value:Ne(b),timeConstantSec:40}),e.jsxs("div",{className:"mt-1 flex items-center gap-2 text-[11px] text-gray-500",children:[e.jsx("span",{children:ke[((V=b==null?void 0:b.progress)==null?void 0:V.phase)??""]??"Working…"}),e.jsx("button",{type:"button",className:j,onClick:()=>u==="rebasing"?m.mutate(r.id):x.mutate(r.id),children:"Cancel"})]}),((G=b==null?void 0:b.progress)==null?void 0:G.recentActivity)&&b.progress.recentActivity.length>0&&e.jsx("pre",{className:"mt-1 max-h-32 overflow-auto rounded bg-gray-50 p-2 text-[11px] leading-relaxed text-gray-500 dark:bg-gray-900",children:b.progress.recentActivity.slice(-8).join(`
|
|
3
3
|
`)})]}):$?e.jsx(Ee,{resolved:$,target:h,pushing:i.isPending,disabled:B,onPush:()=>T("rebase"),onRedo:O}):ee?e.jsxs("p",{className:"mt-2 text-[11px] text-gray-500",children:["No trunk changes to reconcile — the pushed fix is up to date with"," ",e.jsx("span",{className:"font-mono",children:a==null?void 0:a.trunk}),"."]}):e.jsxs("div",{className:"mt-2 space-y-2",children:[a&&a.available&&!a.clean&&e.jsxs("div",{className:"text-[11px] text-gray-500",children:["Recommended: rebase onto"," ",e.jsx("span",{className:"font-mono",children:a.trunk})," — Claude resolves the conflicts and you review the result before it pushes."]}),e.jsxs("label",{className:"flex items-center gap-2 text-[11px] text-gray-600 dark:text-gray-300",children:[e.jsx("input",{type:"checkbox",checked:w,onChange:E=>M(E.target.checked)}),"Let Claude resolve conflicts"]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsxs("button",{type:"button",className:a&&!a.clean?S:j,disabled:P||B,onClick:O,title:"Rebase the fix onto the trunk; Claude resolves conflicts and you review before a force-with-lease push",children:["Rebase onto ",(a==null?void 0:a.trunk)??"trunk"]}),!g&&e.jsxs("button",{type:"button",className:j,disabled:P||B,onClick:()=>T("merge"),title:"Merge the trunk into the fix branch (a merge commit; never force-pushes)",children:["Merge ",(a==null?void 0:a.trunk)??"trunk"," in"]}),!g&&e.jsxs("button",{type:"button",className:!a||a.clean?S:j,disabled:P||B,onClick:()=>T("plain"),title:"Push the fix as-is (never force-pushes). If it conflicts with the trunk, the PR will show as conflicted.",children:[h==="new"?"Push + open PR":"Push",a&&!a.clean?" anyway":""]})]}),g&&e.jsxs("p",{className:"text-[11px] text-gray-400",children:["Rebasing replays the fix onto"," ",e.jsx("span",{className:"font-mono",children:(a==null?void 0:a.trunk)??"the trunk"})," ","and force-pushes (with lease) onto the PR branch — the safe way to reconcile an already-pushed fix."]}),h==="existing"&&e.jsxs("p",{className:"text-[11px] text-gray-400",children:["Rebase force-pushes (with lease) onto"," ",e.jsx("span",{className:"font-mono",children:t==null?void 0:t.headRef}),"; merge and push do not."]})]}),(F||i.isError||c.isError)&&e.jsx("div",{className:"mt-2 text-[11px] text-red-500",children:F??D(i.error??c.error)})]})}function D(s){return s instanceof he||s instanceof Error?s.message:"Something went wrong"}export{De as AiFixTab};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{e as be,r as c,j as e}from"./tanstack-R-s9abdD.js";import{f as fe,s as Ce,x as Re,o as Pe,L as Se,K as Ee,B as Ae,z as Te,v as $e,t as Ie,C as _e,b as Fe,R as Le,h as ve,g as se,e as ke,H as Me,D as re,w as Be,u as He,M as ee}from"./index-
|
|
1
|
+
import{e as be,r as c,j as e}from"./tanstack-R-s9abdD.js";import{f as fe,s as Ce,x as Re,o as Pe,L as Se,K as Ee,B as Ae,z as Te,v as $e,t as Ie,C as _e,b as Fe,R as Le,h as ve,g as se,e as ke,H as Me,D as re,w as Be,u as He,M as ee}from"./index-C1obqq3D.js";import"./markdown-BUNN1sk6.js";import"./highlight-C8vPehS1.js";import"./vis-C3BCkP5U.js";function Oe(t,s){return be({queryKey:["review-learnings",t],queryFn:()=>fe.reviewLearnings(t),enabled:s&&t!=null})}function De(t,s){return be({queryKey:["review-actions",t],queryFn:()=>fe.reviewActions(t),enabled:s&&t!=null})}function Ve({label:t,children:s}){return e.jsxs("div",{className:"flex gap-3 px-4 py-1.5 text-sm",children:[e.jsx("span",{className:"w-28 shrink-0 text-xs uppercase tracking-wide text-gray-400",children:t}),e.jsx("div",{className:"min-w-0 flex-1",children:s})]})}const Z=t=>t?t.slice(0,7):"—",z={skip:"Skipped",diff_only:"Quick",worktree:"Deep"},Ue=[{value:"auto",label:"Auto (router decides)"},{value:"diff_only",label:"Quick — diff only"},{value:"worktree",label:"Deep — full worktree"}],V=(t,s)=>`${t} ${s}${t===1?"":"s"}`;function Ge(t){const s=t.routeReason;if(!s)return;const n=`${V(s.changedFiles,"file")} · ${V(s.linesChanged,"line")} · ${V(s.dirsTouched,"dir")}`;return s.decidedBy==="user"?`You chose ${z[t.reviewMode??"worktree"]} for this run (${n}).`:s.trippedBy?`Auto chose Deep — ${n}; over the ${s.trippedBy} limit.`:`Auto chose ${z[t.reviewMode??"diff_only"]} — ${n}.`}const te={COMMENT:"Comment",REQUEST_CHANGES:"Request changes",APPROVE:"Approve"},qe={APPROVE:"bg-green-500/10 text-green-700 dark:text-green-400",REQUEST_CHANGES:"bg-red-500/10 text-red-700 dark:text-red-400",COMMENT:"bg-gray-500/10 text-gray-600 dark:text-gray-300"};function Ke({verdict:t}){return e.jsx("span",{className:`inline-flex items-center rounded px-1.5 py-0.5 text-xs font-medium ${qe[t]}`,children:te[t]})}const me={blocker:0,warning:1,nit:2,question:3,praise:4},We={blocker:"bg-red-500/10 text-red-700 dark:text-red-400",warning:"bg-orange-500/10 text-orange-700 dark:text-orange-400",nit:"bg-yellow-500/10 text-yellow-700 dark:text-yellow-500",question:"bg-blue-500/10 text-blue-700 dark:text-blue-400",praise:"bg-green-500/10 text-green-700 dark:text-green-400"};function Qe(t){const s=[t.model];return t.costUsd!=null&&s.push(`${ve(t.costUsd)} cr`),t.numTurns!=null&&s.push(`${t.numTurns} turns`),t.excludedFiles.length>0&&s.push(`${t.excludedFiles.length} noise files excluded`),s.join(" · ")}const Ye={cloning:"Cloning the worktree",fetching_diff:"Fetching the diff",deciding:"Deciding scope",reviewing:"Reviewing",persisting:"Saving findings"};function ze(t){var n,a,r;if(t==null)return null;if(t.status==="queued")return 5;const s=(n=t.progress)==null?void 0:n.phase;if(s==null)return 8;switch(s){case"fetching_diff":return 15;case"deciding":return 28;case"cloning":return 42;case"reviewing":{const o=((r=(a=t.progress)==null?void 0:a.recentActivity)==null?void 0:r.length)??0;return Math.min(90,55+o*3)}case"persisting":return 95;default:return null}}function Y(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}k`:String(t)}function Je({review:t}){const{inputTokens:s,outputTokens:n,cacheReadTokens:a,cacheCreationTokens:r}=t;if((s??0)+(n??0)+(a??0)+(r??0)<=0)return null;const d=[];return n!=null&&d.push({key:"out",label:"↓ out",value:n,title:"Output tokens generated (billed at the output rate — the priciest per token)"}),s!=null&&d.push({key:"in",label:"↑ in",value:s,title:"New (uncached) input tokens"}),a!=null&&d.push({key:"cr",label:"⟳ cache read",value:a,title:"Cached input tokens re-read each turn — billed at ~10% of the input rate, but the volume driver of a multi-turn run"}),r!=null&&d.push({key:"cw",label:"✎ cache write",value:r,title:"Tokens written to the prompt cache (billed at ~1.25× the input rate)"}),e.jsx("div",{className:"flex flex-wrap items-center gap-x-3 gap-y-0.5 font-mono text-xs text-gray-500 dark:text-gray-400",children:d.map(i=>e.jsxs("span",{title:i.title,children:[i.label," ",Y(i.value)]},i.key))})}function Xe({lines:t}){const s=c.useRef(null);return c.useEffect(()=>{const n=s.current;n!=null&&(n.scrollTop=n.scrollHeight)},[t]),t.length===0?null:e.jsx("div",{ref:s,className:"mt-2 max-h-32 overflow-y-auto rounded border border-gray-100 bg-gray-50 px-2 py-1.5 font-mono text-[11px] leading-snug text-gray-500 dark:border-gray-800 dark:bg-gray-900/60 dark:text-gray-400",children:t.map((n,a)=>e.jsx("div",{className:"whitespace-pre-wrap break-words",children:n===""?" ":n},a))})}function ge(t){return t.startsWith("@@")?"text-violet-500":t.startsWith("+")?"text-green-700 dark:text-green-400":t.startsWith("-")?"text-red-700 dark:text-red-400":"text-gray-500 dark:text-gray-400"}const Q="whitespace-nowrap rounded border border-blue-400 px-2 py-0.5 text-xs text-blue-600 hover:bg-blue-50 disabled:opacity-50 dark:border-blue-600 dark:text-blue-400 dark:hover:bg-blue-900/30",L="whitespace-nowrap rounded border border-gray-300 px-2 py-0.5 text-xs hover:border-gray-400 disabled:opacity-50 dark:border-gray-700 dark:hover:border-gray-500";function Ze({hunk:t}){const[s,n]=c.useState(!1),a=t.replace(/\n$/,"").split(`
|
|
2
2
|
`),r=a.find(o=>o.startsWith("@@"))??a.at(-1)??"";return s?e.jsxs("div",{className:"mt-1 rounded bg-gray-50 dark:bg-gray-900/60",children:[e.jsx("pre",{className:"overflow-x-auto px-2 py-1.5 font-mono text-xs leading-snug",children:a.map((o,d)=>e.jsx("div",{className:ge(o),children:o===""?" ":o},d))}),e.jsx("button",{type:"button",onClick:()=>n(!1),className:"px-2 pb-1.5 text-[11px] text-gray-400 hover:text-gray-600 dark:hover:text-gray-300",children:"⌃ Hide code"})]}):e.jsxs("button",{type:"button",onClick:()=>n(!0),title:"Show the code hunk",className:"mt-1 flex w-full items-center gap-2 overflow-hidden rounded bg-gray-50 px-2 py-1.5 text-left font-mono text-xs dark:bg-gray-900/60",children:[e.jsx("span",{className:"shrink-0 text-gray-400",children:"▸"}),e.jsx("span",{className:`min-w-0 flex-1 truncate ${ge(r)}`,children:r===""?" ":r}),a.length>1&&e.jsxs("span",{className:"shrink-0 font-sans text-[10px] text-gray-400",children:[a.length," lines"]})]})}function et({prId:t,finding:s,editable:n,prUrl:a,repoFullName:r,headSha:o,posting:d,postError:i,onToggle:h,onReword:k,onPostComment:j}){const[g,E]=c.useState(!1),x=c.useRef(null);c.useEffect(()=>()=>{x.current!=null&&clearTimeout(x.current)},[]);const[p,N]=c.useState(!1),[S,m]=c.useState(s.editedBody??"");c.useEffect(()=>{p||m(s.editedBody??"")},[s.editedBody,p]);const b=s.editedBody!=null&&s.editedBody.trim()!=="",y=p&&S.trim()!==""&&S!==(s.editedBody??"")?S:null,A=y!=null||b,J=y??(b?s.editedBody:s.body),T=s.line!=null?`${s.path}:${s.line}`:s.path,f=s.postedAt!=null,v=s.githubCommentId!=null?s.postedCommentKind==="pr_comment"?`${a}#issuecomment-${s.githubCommentId}`:`${a}#discussion_r${s.githubCommentId}`:null,$=s.line!=null?`${a}/files#diff-${s.diffAnchorId}${s.side==="LEFT"?"L":"R"}${s.line}`:`${a}/files#diff-${s.diffAnchorId}`,U=o!=null&&s.line!=null&&s.side==="RIGHT"?`https://github.com/${r}/blob/${o}/${s.path.split("/").map(encodeURIComponent).join("/")}#L${s.line}`:null,w=v??$,C=v==null?U:null,G=n,q=()=>{let _=`${s.title}
|
|
3
3
|
|
|
4
4
|
${J}`;s.suggestion!=null&&s.suggestion!==""&&(_+=`
|