pierre-review 0.1.80 → 0.1.81

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.
@@ -35,6 +35,7 @@ export async function activityRoutes(app) {
35
35
  offset: Number.isFinite(offset) && offset > 0 ? offset : 0,
36
36
  excludeBots: q.excludeBots === 'true',
37
37
  allowBotIds: parseIntList(q.allowBotIds),
38
+ botsOnly: q.botsOnly === 'true',
38
39
  });
39
40
  });
40
41
  // Mark the Activity Feed as seen (bumps the account's server-side "seen" marker to
@@ -636,7 +636,7 @@ export async function getTimeline(filters) {
636
636
  return { prs, events: timelineEvents };
637
637
  }
638
638
  export async function getFeed(accountId, opts = {}) {
639
- const { daysBefore = 14, repoIds = null, userIds = null, watchedOnly = false, prId = null } = opts;
639
+ const { daysBefore = 14, repoIds = null, userIds = null, watchedOnly = false, prId = null, botActorIds = null } = opts;
640
640
  // Isolated to one PR → no time window (epoch 0); otherwise the rolling `daysBefore` window.
641
641
  const since = prId != null ? new Date(0) : new Date(Date.now() - daysBefore * 24 * 60 * 60 * 1000);
642
642
  const conds = [
@@ -652,6 +652,10 @@ export async function getFeed(accountId, opts = {}) {
652
652
  conds.push(inArray(events.repoId, repoIds));
653
653
  if (userIds && userIds.length > 0)
654
654
  conds.push(inArray(events.actorId, userIds));
655
+ // Bot feed: restrict to the automated-reviewer actor set (before the cap). An empty set
656
+ // means "no bots" → force an unsatisfiable predicate so the feed is empty (not unfiltered).
657
+ if (botActorIds != null)
658
+ conds.push(botActorIds.length > 0 ? inArray(events.actorId, botActorIds) : sql `1 = 0`);
655
659
  const rows = await db
656
660
  .select({
657
661
  id: events.id,
@@ -1479,6 +1483,10 @@ export async function getActivity(accountId, repoIds, userIds = null) {
1479
1483
  // Every My Turn (participated) event is always kept; the plain activity rows are bounded to
1480
1484
  // the most recent, so a busy multi-repo account doesn't render thousands of them.
1481
1485
  const FEED_EVENT_CAP = 250;
1486
+ // The Bots pane's bot-only feed filters to automated reviewers IN SQL, so its cap governs bot
1487
+ // activity alone (not a slice of all activity). Set generously so it spans the full window and
1488
+ // tracks the ROI thread counts; the feed is paginated + DOM-windowed, so a high total is cheap.
1489
+ const BOT_FEED_EVENT_CAP = 1000;
1482
1490
  // A top-level PR comment and a coinciding "host" event by the SAME actor on the SAME PR are
1483
1491
  // folded into ONE card when they land within this window of each other (issue comments carry
1484
1492
  // no head SHA, so time is the only proxy for "posted together"). Symmetric around the host.
@@ -2868,7 +2876,7 @@ scopeRepoIds) {
2868
2876
  // `repoIds` further narrows WITHIN watched (null → all watched). One flat, newest-first
2869
2877
  // stream of real activity, each row flagged isMyTurn by participation (CORE; feed/my-turn.ts).
2870
2878
  export async function getConsolidatedFeed(accountId, opts = {}) {
2871
- const { repoIds = null, userIds = null, prId = null, limit = null, offset = 0, excludeBots = false, allowBotIds = null, } = opts;
2879
+ const { repoIds = null, userIds = null, prId = null, limit = null, offset = 0, excludeBots = false, allowBotIds = null, botsOnly = false, } = opts;
2872
2880
  // Restrict to the account's WATCHED repos; a passed repoIds narrows within them. An
2873
2881
  // out-of-scope / empty selection → a valid empty page (also avoids an inArray([]) below).
2874
2882
  const watchedIds = (await listRepos(accountId)).filter((r) => r.inboxWatch).map((r) => r.id);
@@ -2882,7 +2890,8 @@ export async function getConsolidatedFeed(accountId, opts = {}) {
2882
2890
  // their activity stays visible. getFeed doesn't filter bots, so we do it here; the commit
2883
2891
  // helper filters in its own SQL.
2884
2892
  const allowBots = new Set(allowBotIds ?? []);
2885
- const botIds = excludeBots
2893
+ // excludeBots is meaningless in the bot-only feed (it would drop everything) — force it off.
2894
+ const botIds = !botsOnly && excludeBots
2886
2895
  ? new Set((await botUserIds()).filter((id) => !allowBots.has(id)))
2887
2896
  : new Set();
2888
2897
  const notBot = (id) => !excludeBots || id == null || !botIds.has(id);
@@ -2891,19 +2900,32 @@ export async function getConsolidatedFeed(accountId, opts = {}) {
2891
2900
  // cheap, and the reader sees the opened event + all activity even on a long-idle PR — not an
2892
2901
  // empty pane. The un-isolated feed keeps the rolling 14-day window (a live activity stream).
2893
2902
  const feedSince = prId != null ? new Date(0) : new Date(Date.now() - 14 * 24 * 60 * 60 * 1000);
2903
+ // Bot-only feed: resolve the automated-reviewer actor set (vendors + classified in-house /
2904
+ // Pierre — the SAME set the ROI panel counts, so it catches deepsource-io etc. that aren't
2905
+ // users.isBot). Empty → nobody's classified → an empty feed. Filtered IN SQL by getFeed.
2906
+ const botActorIds = botsOnly ? await automatedReviewerUserIds(accountId) : null;
2907
+ if (botsOnly && (botActorIds == null || botActorIds.length === 0)) {
2908
+ return { items: [], users: [], total: 0, generatedAt: new Date().toISOString() };
2909
+ }
2894
2910
  const [feed, commitItems, claudeItems] = await Promise.all([
2895
- getFeed(accountId, { daysBefore: 14, prId, repoIds: effectiveRepoIds, userIds }),
2911
+ getFeed(accountId, { daysBefore: 14, prId, repoIds: effectiveRepoIds, userIds, botActorIds }),
2896
2912
  // Commit-push activity that ADDRESSED a review thread (the only commit rows we surface
2897
- // — plain pushes are noise). Each carries the affected threads inline.
2898
- getCommitThreadItems(accountId, {
2899
- repoIds: effectiveRepoIds,
2900
- userIds,
2901
- botIds,
2902
- since: feedSince,
2903
- prId,
2904
- }),
2913
+ // — plain pushes are noise). Each carries the affected threads inline. Skipped in the
2914
+ // bot-only feed (a commit push is the AUTHOR responding, not review-bot activity).
2915
+ botsOnly
2916
+ ? Promise.resolve([])
2917
+ : getCommitThreadItems(accountId, {
2918
+ repoIds: effectiveRepoIds,
2919
+ userIds,
2920
+ botIds,
2921
+ since: feedSince,
2922
+ prId,
2923
+ }),
2905
2924
  // Claude Review runs surfaced as their own feed item kind (local-only; empty in cloud).
2906
- getClaudeReviewFeedItems(accountId, effectiveRepoIds, feedSince, prId),
2925
+ // Skipped in the bot-only feed (they're the user's own runs, not review-bot activity).
2926
+ botsOnly
2927
+ ? Promise.resolve([])
2928
+ : getClaudeReviewFeedItems(accountId, effectiveRepoIds, feedSince, prId),
2907
2929
  ]);
2908
2930
  // The "My Turn" participation flag (isMyTurn / myTurnReasons / reasonTag) is CORE / free
2909
2931
  // (see feed/my-turn.ts). Core builds every item as a PLAIN row (isMyTurn:false), then
@@ -3007,7 +3029,7 @@ export async function getConsolidatedFeed(accountId, opts = {}) {
3007
3029
  const feedRows = scoped
3008
3030
  .filter((i) => !i.isMyTurn && i.kind !== 'claude_review')
3009
3031
  .sort((a, b) => b.occurredAt.localeCompare(a.occurredAt))
3010
- .slice(0, FEED_EVENT_CAP);
3032
+ .slice(0, botsOnly ? BOT_FEED_EVENT_CAP : FEED_EVENT_CAP);
3011
3033
  const ordered = [...alwaysRows, ...feedRows].sort((a, b) => b.occurredAt.localeCompare(a.occurredAt));
3012
3034
  // Paginate: `total` is the full stream length; `page` is the requested window. Only
3013
3035
  // the page is enriched + has its users backfilled, so hidden items cost nothing.
@@ -5520,6 +5542,39 @@ scopeRepoIds) {
5520
5542
  }
5521
5543
  const kindMap = await classificationKindForUser(accountId);
5522
5544
  const hideRules = (await listBotMuteRules(accountId)).filter((r) => r.action === 'hide');
5545
+ // Per-REVIEWER identity (so in-house bots — all kind 'in_house' — separate into their own rows
5546
+ // instead of collapsing). Label preference: the account's custom classification label →
5547
+ // the vendor's pretty name (for known vendors) → the reviewer's login/display name.
5548
+ const classLabel = new Map();
5549
+ for (const r of await db
5550
+ .select({ id: botReviewClassification.authorUserId, label: botReviewClassification.label })
5551
+ .from(botReviewClassification)
5552
+ .where(eq(botReviewClassification.accountId, accountId))
5553
+ .execute()) {
5554
+ if (r.label != null && r.label.trim() !== '')
5555
+ classLabel.set(r.id, r.label.trim());
5556
+ }
5557
+ const loginById = new Map(); // display fallback (name || login)
5558
+ const rawLoginById = new Map(); // the github login — the per-bot cost key
5559
+ if (automatedIds.length > 0) {
5560
+ for (const r of await db
5561
+ .select({ id: users.id, login: users.githubLogin, name: users.displayName })
5562
+ .from(users)
5563
+ .where(inArray(users.id, automatedIds))
5564
+ .execute()) {
5565
+ loginById.set(r.id, r.name?.trim() || r.login || `#${r.id}`);
5566
+ if (r.login)
5567
+ rawLoginById.set(r.id, r.login);
5568
+ }
5569
+ }
5570
+ const reviewerLabel = (userId, kind) => {
5571
+ const custom = classLabel.get(userId);
5572
+ if (custom)
5573
+ return custom;
5574
+ if (kind !== 'in_house' && kind !== 'pierre')
5575
+ return labelForKind(kind);
5576
+ return loginById.get(userId) ?? labelForKind(kind);
5577
+ };
5523
5578
  // Automated-reviewer threads over the 12-week trend span (⊇ the selected window).
5524
5579
  const threadRows = await db
5525
5580
  .select({
@@ -5567,21 +5622,24 @@ scopeRepoIds) {
5567
5622
  }
5568
5623
  return false;
5569
5624
  };
5570
- const byKind = new Map();
5571
- const accFor = (kind) => {
5572
- let a = byKind.get(kind);
5625
+ // Keyed by REVIEWER user id (not kind) so each bot — including every in-house bot that shares
5626
+ // kind 'in_house' — gets its own row. `kind` rides along for colour / cost / verdict semantics.
5627
+ const byUser = new Map();
5628
+ const accFor = (userId, kind) => {
5629
+ let a = byUser.get(userId);
5573
5630
  if (!a) {
5574
5631
  a = {
5632
+ kind,
5575
5633
  reviewers: new Set(),
5576
5634
  threads: 0,
5577
5635
  actedOn: 0,
5578
5636
  untouched: 0,
5579
5637
  oldestUntouchedMs: null,
5580
5638
  humanFollow: 0,
5581
- weekly: Array.from({ length: 12 }, () => ({ threads: 0, actedOn: 0 })),
5639
+ weekly: Array.from({ length: 12 }, () => ({ threads: 0, actedOn: 0, untouched: 0 })),
5582
5640
  buckets: new Map(),
5583
5641
  };
5584
- byKind.set(kind, a);
5642
+ byUser.set(userId, a);
5585
5643
  }
5586
5644
  return a;
5587
5645
  };
@@ -5595,7 +5653,7 @@ scopeRepoIds) {
5595
5653
  continue;
5596
5654
  if (isHidden(t, kind))
5597
5655
  continue;
5598
- const acc = accFor(kind);
5656
+ const acc = accFor(t.userId, kind);
5599
5657
  const acted = t.state === 'resolved' || t.state === 'likely_addressed';
5600
5658
  // Trend bucket by created week.
5601
5659
  const wk = Math.min(11, Math.max(0, Math.floor((t.createdAt.getTime() - trendFrom.getTime()) / (7 * 86_400_000))));
@@ -5603,6 +5661,8 @@ scopeRepoIds) {
5603
5661
  bucket.threads += 1;
5604
5662
  if (acted)
5605
5663
  bucket.actedOn += 1;
5664
+ if (t.state === 'untouched')
5665
+ bucket.untouched += 1;
5606
5666
  // Headline metrics use only the selected window. NOTE: acc.actedOn is accumulated LATER
5607
5667
  // (after the human-follow-up pass) under the merged "acted-on" definition (item 6) —
5608
5668
  // resolved | likely_addressed | a human replied after the bot's last comment.
@@ -5621,7 +5681,7 @@ scopeRepoIds) {
5621
5681
  if (t.state === 'untouched')
5622
5682
  b.untouched += 1;
5623
5683
  acc.buckets.set(pb, b);
5624
- windowThreads.push({ id: t.id, kind, path: t.path, state: t.state, createdAt: t.createdAt });
5684
+ windowThreads.push({ id: t.id, userId: t.userId, kind, path: t.path, state: t.state, createdAt: t.createdAt });
5625
5685
  }
5626
5686
  }
5627
5687
  // Human follow-through: of the bot's window threads, the ones where a human commented after
@@ -5645,10 +5705,10 @@ scopeRepoIds) {
5645
5705
  arr.push({ authorId: r.authorId, at: r.createdAt.getTime() });
5646
5706
  byThread.set(r.threadId, arr);
5647
5707
  }
5648
- const kindByThread = new Map(windowThreads.map((t) => [t.id, t.kind]));
5708
+ const reviewerByThread = new Map(windowThreads.map((t) => [t.id, { userId: t.userId, kind: t.kind }]));
5649
5709
  for (const [threadId, comments] of byThread) {
5650
- const kind = kindByThread.get(threadId);
5651
- if (!kind)
5710
+ const rv = reviewerByThread.get(threadId);
5711
+ if (!rv)
5652
5712
  continue;
5653
5713
  let botLastAt = -Infinity;
5654
5714
  for (const c of comments) {
@@ -5658,7 +5718,7 @@ scopeRepoIds) {
5658
5718
  const humanAfter = comments.some((c) => c.authorId != null && !automatedIds.includes(c.authorId) && c.at > botLastAt);
5659
5719
  if (humanAfter) {
5660
5720
  humanFollowSet.add(threadId);
5661
- accFor(kind).humanFollow += 1;
5721
+ accFor(rv.userId, rv.kind).humanFollow += 1;
5662
5722
  }
5663
5723
  }
5664
5724
  }
@@ -5667,31 +5727,32 @@ scopeRepoIds) {
5667
5727
  for (const t of windowThreads) {
5668
5728
  const baseActed = t.state === 'resolved' || t.state === 'likely_addressed';
5669
5729
  if (baseActed || humanFollowSet.has(t.id))
5670
- accFor(t.kind).actedOn += 1;
5730
+ accFor(t.userId, t.kind).actedOn += 1;
5671
5731
  }
5672
- // Comments volume per kind (bot-authored review comments in the window).
5732
+ // Comments volume per REVIEWER (bot-authored review comments in the window).
5673
5733
  const commentRows = await db
5674
5734
  .select({ authorId: reviewComments.authorId })
5675
5735
  .from(reviewComments)
5676
5736
  .innerJoin(pullRequests, eq(pullRequests.id, reviewComments.prId))
5677
5737
  .where(and(eq(pullRequests.accountId, accountId), inArray(reviewComments.authorId, automatedIds), gte(reviewComments.createdAt, from), lte(reviewComments.createdAt, to), ...repoScopeFilter))
5678
5738
  .execute();
5679
- const commentsByKind = new Map();
5739
+ const commentsByUser = new Map();
5680
5740
  for (const r of commentRows) {
5681
5741
  if (r.authorId == null)
5682
5742
  continue;
5683
- const kind = kindMap.get(r.authorId);
5684
- if (!kind)
5743
+ if (!kindMap.get(r.authorId))
5685
5744
  continue;
5686
- commentsByKind.set(kind, (commentsByKind.get(kind) ?? 0) + 1);
5745
+ commentsByUser.set(r.authorId, (commentsByUser.get(r.authorId) ?? 0) + 1);
5687
5746
  }
5688
5747
  const suggestions = [];
5689
5748
  const vendors = [];
5690
- for (const [kind, acc] of byKind) {
5691
- if (acc.threads === 0 && (commentsByKind.get(kind) ?? 0) === 0)
5749
+ for (const [userId, acc] of byUser) {
5750
+ const comments = commentsByUser.get(userId) ?? 0;
5751
+ if (acc.threads === 0 && comments === 0)
5692
5752
  continue;
5753
+ const kind = acc.kind;
5754
+ const label = reviewerLabel(userId, kind);
5693
5755
  const actedOnPct = acc.threads > 0 ? Math.round((acc.actedOn / acc.threads) * 100) : null;
5694
- const comments = commentsByKind.get(kind) ?? 0;
5695
5756
  const oldestUntouchedDays = acc.oldestUntouchedMs == null ? null : Math.floor((nowMs - acc.oldestUntouchedMs) / 86_400_000);
5696
5757
  const humanFollowThroughPct = acc.threads > 0 ? Math.round((acc.humanFollow / acc.threads) * 100) : null;
5697
5758
  // Noise ratio: untouched-share proxy (see the header — true severity is often unknowable).
@@ -5700,10 +5761,15 @@ scopeRepoIds) {
5700
5761
  weekStart: new Date(trendFrom.getTime() + i * 7 * 86_400_000).toISOString(),
5701
5762
  threads: w.threads,
5702
5763
  actedOnPct: w.threads > 0 ? Math.round((w.actedOn / w.threads) * 100) : null,
5764
+ untouched: w.untouched,
5703
5765
  }));
5704
5766
  vendors.push({
5767
+ // A stable per-reviewer row key (kind repeats across in-house bots, so the table can't key
5768
+ // on kind). `kind` still drives colour / cost / verdict; `label` is the per-bot name.
5769
+ key: `u${userId}`,
5705
5770
  kind,
5706
- label: labelForKind(kind),
5771
+ label,
5772
+ login: rawLoginById.get(userId) ?? null,
5707
5773
  reviewers: acc.reviewers.size,
5708
5774
  threads: acc.threads,
5709
5775
  comments,
@@ -5718,8 +5784,8 @@ scopeRepoIds) {
5718
5784
  costPerActedOnUsd: null,
5719
5785
  trend,
5720
5786
  });
5721
- // Deterministic tuning suggestions (§3h): a (kind, path-bucket) with volume ≥ 5 and
5722
- // untouchedPct ≥ 70 → "mute this".
5787
+ // Deterministic tuning suggestions (§3h): a (reviewer, path-bucket) with volume ≥ 5 and
5788
+ // untouchedPct ≥ 70 → "mute this". vendorKind stays the kind (mute rules match by kind).
5723
5789
  for (const [pb, b] of acc.buckets) {
5724
5790
  if (b.volume < 5)
5725
5791
  continue;
@@ -5728,12 +5794,12 @@ scopeRepoIds) {
5728
5794
  continue;
5729
5795
  suggestions.push({
5730
5796
  vendorKind: kind,
5731
- label: labelForKind(kind),
5797
+ label,
5732
5798
  pathGlob: pb,
5733
5799
  severity: null,
5734
5800
  untouchedPct,
5735
5801
  volume: b.volume,
5736
- rationale: `${untouchedPct}% of ${labelForKind(kind)}'s ${b.volume} threads in ${pb} went untouched — mute them?`,
5802
+ rationale: `${untouchedPct}% of ${label}'s ${b.volume} threads in ${pb} went untouched — mute them?`,
5737
5803
  });
5738
5804
  }
5739
5805
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pierre-review",
3
- "version": "0.1.80",
3
+ "version": "0.1.81",
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-_-VIX-je.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(`
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-BzIHcEkg.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 ye,s as we,x as Ce,o as Re,L as Pe,K as Se,B as Ee,z as Te,v as Ae,t as $e,C as Fe,b as _e,R as Ie,h as fe,g as se,e as ve,D as re,w as Be,u as Me,H as Le,M as ee}from"./index-_-VIX-je.js";import"./markdown-BUNN1sk6.js";import"./highlight-C8vPehS1.js";import"./vis-C3BCkP5U.js";function He(t,s){return be({queryKey:["review-learnings",t],queryFn:()=>ye.reviewLearnings(t),enabled:s&&t!=null})}function Oe(t,s){return be({queryKey:["review-actions",t],queryFn:()=>ye.reviewActions(t),enabled:s&&t!=null})}function De({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"},Ve=[{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"},Ue={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 qe({verdict:t}){return e.jsx("span",{className:`inline-flex items-center rounded px-1.5 py-0.5 text-xs font-medium ${Ue[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(`${fe(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 Ke(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 K(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}k`:String(t)}function ze({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," ",K(i.value)]},i.key))})}function Je({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 Y="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",B="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 Xe({hunk:t}){const[s,n]=c.useState(!1),a=t.replace(/\n$/,"").split(`
1
+ import{e as be,r as c,j as e}from"./tanstack-R-s9abdD.js";import{f as ye,s as we,x as Ce,o as Re,L as Pe,K as Se,B as Ee,z as Te,v as Ae,t as $e,C as Fe,b as _e,R as Ie,h as fe,g as se,e as ve,D as re,w as Be,u as Me,H as Le,M as ee}from"./index-BzIHcEkg.js";import"./markdown-BUNN1sk6.js";import"./highlight-C8vPehS1.js";import"./vis-C3BCkP5U.js";function He(t,s){return be({queryKey:["review-learnings",t],queryFn:()=>ye.reviewLearnings(t),enabled:s&&t!=null})}function Oe(t,s){return be({queryKey:["review-actions",t],queryFn:()=>ye.reviewActions(t),enabled:s&&t!=null})}function De({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"},Ve=[{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"},Ue={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 qe({verdict:t}){return e.jsx("span",{className:`inline-flex items-center rounded px-1.5 py-0.5 text-xs font-medium ${Ue[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(`${fe(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 Ke(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 K(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}k`:String(t)}function ze({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," ",K(i.value)]},i.key))})}function Je({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 Y="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",B="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 Xe({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 Ze({prId:t,finding:s,editable:n,prUrl:a,repoFullName:r,headSha:o,posting:d,postError:i,onToggle:b,onReword:k,onPostComment:g}){const[P,E]=c.useState(!1),x=c.useRef(null);c.useEffect(()=>()=>{x.current!=null&&clearTimeout(x.current)},[]);const[p,j]=c.useState(!1),[S,m]=c.useState(s.editedBody??"");c.useEffect(()=>{p||m(s.editedBody??"")},[s.editedBody,p]);const y=s.editedBody!=null&&s.editedBody.trim()!=="",h=p&&S.trim()!==""&&S!==(s.editedBody??"")?S:null,T=h!=null||y,J=h??(y?s.editedBody:s.body),A=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}`,G=o!=null&&s.line!=null&&s.side==="RIGHT"?`https://github.com/${r}/blob/${o}/${s.path.split("/").map(encodeURIComponent).join("/")}#L${s.line}`:null,N=v??$,w=v==null?G:null,U=n,q=()=>{let _=`${s.title}
3
3
 
4
4
  ${J}`;s.suggestion!=null&&s.suggestion!==""&&(_+=`