pierre-review 0.1.53 → 0.1.55
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/me.js +1 -1
- package/dist/app.js +3 -5
- package/dist/config.js +7 -37
- package/dist/db/queries.js +19 -266
- package/dist/index.js +2 -6
- package/dist/pro/bind.js +15 -1
- package/dist/pro/contract.js +1 -0
- package/dist/review/agent.js +67 -176
- package/dist/review/auth.js +6 -7
- package/dist/review/post-seam.js +149 -0
- package/dist/review/prepare.js +161 -0
- package/package.json +1 -1
- package/public/assets/{index-B18EPUv2.js → index-DPXMXoyL.js} +100 -100
- package/public/index.html +1 -1
- package/dist/api/routes/claude-review.js +0 -522
- package/dist/review/persist.js +0 -237
- package/dist/review/prompt.js +0 -264
- package/dist/review/review-manager.js +0 -259
- package/dist/review/routing.js +0 -164
package/dist/api/routes/me.js
CHANGED
|
@@ -41,7 +41,7 @@ export async function meRoutes(app) {
|
|
|
41
41
|
},
|
|
42
42
|
feedLastSeenAt: feedLastSeen ? feedLastSeen.toISOString() : null,
|
|
43
43
|
newFeedItems,
|
|
44
|
-
|
|
44
|
+
// Claude Review is now the Pro `claudeReview` capability (in `pro` below).
|
|
45
45
|
deploymentMode: config.deploymentMode,
|
|
46
46
|
pro: getProCapabilities(),
|
|
47
47
|
};
|
package/dist/app.js
CHANGED
|
@@ -19,7 +19,6 @@ import { feedRoutes } from './api/routes/feed.js';
|
|
|
19
19
|
import { mergersRoutes } from './api/routes/mergers.js';
|
|
20
20
|
import { insightsRoutes } from './api/routes/insights.js';
|
|
21
21
|
import { activityRoutes } from './api/routes/activity.js';
|
|
22
|
-
import { claudeReviewRoutes } from './api/routes/claude-review.js';
|
|
23
22
|
export async function buildApp() {
|
|
24
23
|
const app = Fastify({
|
|
25
24
|
// In production (the installed CLI) the per-request "incoming request" /
|
|
@@ -132,10 +131,9 @@ export async function buildApp() {
|
|
|
132
131
|
await app.register(mergersRoutes);
|
|
133
132
|
await app.register(insightsRoutes);
|
|
134
133
|
await app.register(activityRoutes);
|
|
135
|
-
// Claude Review
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
await app.register(claudeReviewRoutes);
|
|
134
|
+
// Claude Review moved into the @pierre/pro plugin (its routes register there, gated on the
|
|
135
|
+
// `claudeReview` capability). The SDK-run / diff-prep / GitHub-post infra + the tables stay
|
|
136
|
+
// in core behind the ctx.review seam; nothing to register here.
|
|
139
137
|
return app;
|
|
140
138
|
}
|
|
141
139
|
//# sourceMappingURL=app.js.map
|
package/dist/config.js
CHANGED
|
@@ -141,18 +141,16 @@ export const config = {
|
|
|
141
141
|
digestMaxReposPerRefresh: intFromEnv('PRO_DIGEST_MAX_REPOS', 30),
|
|
142
142
|
digestMinIntervalSec: intFromEnv('PRO_DIGEST_MIN_INTERVAL_SEC', 60),
|
|
143
143
|
},
|
|
144
|
-
// ---- Claude Review (
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
144
|
+
// ---- Claude Review — CORE infra knobs (the product moved to @pierre/pro) ----
|
|
145
|
+
// The SDK-run / diff-prep infra behind the ctx.review seam reads these. The old
|
|
146
|
+
// `claudeReviewEnabled` env flag was removed — Claude Review is now the Pro
|
|
147
|
+
// `claudeReview` capability (PRO_CLAUDE_REVIEW_ENABLED); the routing thresholds,
|
|
148
|
+
// concurrency + queue caps, and the default-model picker moved to PRO_REVIEW_* env.
|
|
149
149
|
// Partial clones + ephemeral worktrees live here (a user-writable home path,
|
|
150
150
|
// never the read-only install dir). CLONE_DIR overrides.
|
|
151
151
|
cloneDir: process.env.CLONE_DIR ?? resolve(homedir(), '.pierre-review', 'clones'),
|
|
152
152
|
// Soft cap on the clone cache before LRU cleanup evicts idle repos (default 2 GiB).
|
|
153
153
|
cloneCacheMaxBytes: intFromEnv('CLONE_CACHE_MAX_BYTES', 2 * 1024 * 1024 * 1024),
|
|
154
|
-
// Default model for the picker; per-run model still overrides on the request.
|
|
155
|
-
defaultReviewModel: process.env.DEFAULT_REVIEW_MODEL ?? 'claude-sonnet-4-6',
|
|
156
154
|
// Per-run caps (cost/disk/time runaway guards). The diff is inlined in full, so
|
|
157
155
|
// reviews need far fewer turns than the old default; 30 is still generous.
|
|
158
156
|
reviewMaxTurns: intFromEnv('REVIEW_MAX_TURNS', 30),
|
|
@@ -191,16 +189,8 @@ export const config = {
|
|
|
191
189
|
// Applied only to effort-capable models (Sonnet/Opus); Haiku ignores it.
|
|
192
190
|
reviewEffort: effortFromEnv('REVIEW_EFFORT', 'medium'),
|
|
193
191
|
reviewDiffOnlyEffort: effortFromEnv('REVIEW_DIFF_ONLY_EFFORT', 'low'),
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
// also DISABLES the per-run env auth adjustment (which mutates process.env and is
|
|
197
|
-
// only safe at concurrency 1 — see auth.applyClaudeReviewAuth); the raw ambient
|
|
198
|
-
// env is used instead. Set REVIEW_CONCURRENCY=1 to restore prefer-ambient + the
|
|
199
|
-
// API-key fallback.
|
|
200
|
-
reviewConcurrency: intFromEnv('REVIEW_CONCURRENCY', 4),
|
|
201
|
-
// Hard ceiling on QUEUED (not-yet-started) reviews, a runaway guard for bulk
|
|
202
|
-
// triggering; further starts return 'busy' until the queue drains.
|
|
203
|
-
reviewMaxQueued: intFromEnv('REVIEW_MAX_QUEUED', 50),
|
|
192
|
+
// (Review concurrency + queue caps + the routing thresholds moved to the plugin's
|
|
193
|
+
// PRO_REVIEW_* env — the plugin owns the queue/manager + the mode-routing decision.)
|
|
204
194
|
// ---- AI Fix (Pro: agentic code fixer) — core infra knobs ----
|
|
205
195
|
// The write-capable fixer (packages/pro/ai-fix) reuses the review clone/agent
|
|
206
196
|
// machinery. A runaway fixer is pricier than a review (it edits + re-reads files),
|
|
@@ -217,26 +207,6 @@ export const config = {
|
|
|
217
207
|
// rebased commit that conflicts gets one resolver pass, up to this many steps, then
|
|
218
208
|
// we abort the rebase rather than loop forever on a pathological history.
|
|
219
209
|
aiFixRebaseMaxSteps: intFromEnv('AI_FIX_REBASE_MAX_STEPS', 10),
|
|
220
|
-
// ---- Claude Review routing (diff-only vs worktree) — THE THRESHOLDS ----
|
|
221
|
-
// The deterministic pre-check (review/routing.ts) decides, BEFORE the agent runs,
|
|
222
|
-
// whether a PR can be reviewed from its diff alone (fast, tool-less, no worktree)
|
|
223
|
-
// or needs the full cloned worktree as explorable context. A change stays
|
|
224
|
-
// 'diff_only' only if it is within EVERY ceiling below AND touches no exported/
|
|
225
|
-
// public API; otherwise it routes to 'worktree'. The numbers are deliberately
|
|
226
|
-
// CONSERVATIVE — any tie routes to worktree, since over-reviewing is safe but
|
|
227
|
-
// under-reviewing a risky change is not. Every decision input is logged on the
|
|
228
|
-
// run (claude_reviews.route_reason) so these can be tuned against the agent's own
|
|
229
|
-
// scopeUsed self-report. This is the single place to review/adjust the thresholds.
|
|
230
|
-
reviewRouting: {
|
|
231
|
-
// Max (non-noise) files changed for a diff-only review.
|
|
232
|
-
maxFiles: intFromEnv('REVIEW_ROUTE_MAX_FILES', 5),
|
|
233
|
-
// Max added + deleted lines (non-noise files) for a diff-only review.
|
|
234
|
-
maxLines: intFromEnv('REVIEW_ROUTE_MAX_LINES', 150),
|
|
235
|
-
// Max distinct directories touched for a diff-only review.
|
|
236
|
-
maxDirs: intFromEnv('REVIEW_ROUTE_MAX_DIRS', 2),
|
|
237
|
-
// Max distinct top-level subsystems (first path segment) for a diff-only review.
|
|
238
|
-
maxSubsystems: intFromEnv('REVIEW_ROUTE_MAX_SUBSYSTEMS', 1),
|
|
239
|
-
},
|
|
240
210
|
};
|
|
241
211
|
// Fail loud at startup if a required cloud var is missing — mirrors the
|
|
242
212
|
// gh-auth loud failure. Called from index.ts only when deploymentMode==='cloud'.
|
package/dist/db/queries.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import { and, asc, count, desc, eq, exists, gt, gte, inArray, isNotNull, isNull, lt, lte, ne, notInArray, or, sql, } from 'drizzle-orm';
|
|
3
2
|
// Local copy of the shared `REASON_PRIORITY` value constant. `@pierre-review/shared`
|
|
4
3
|
// is a types-only workspace package that is NOT shipped in the published tarball,
|
|
@@ -16,6 +15,8 @@ const REASON_PRIORITY = [
|
|
|
16
15
|
import { db, schema, isPg } from './client.js';
|
|
17
16
|
import { runTransaction } from './client.js';
|
|
18
17
|
import { config } from '../config.js';
|
|
18
|
+
import { getProCapabilities } from '../pro/contract.js';
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
19
20
|
import { computeApprovalInfoByPr, computeTriage, } from './triage.js';
|
|
20
21
|
import { getAccountUserId } from '../auth/account.js';
|
|
21
22
|
import { ensureRoutingPrFiles } from '../sync/routing-files.js';
|
|
@@ -30,6 +31,11 @@ function iso(d) {
|
|
|
30
31
|
function emptyCounts() {
|
|
31
32
|
return { resolved: 0, likely_addressed: 0, replied_unresolved: 0, untouched: 0 };
|
|
32
33
|
}
|
|
34
|
+
// GitHub anchors a file in a PR's "Files changed" diff by the SHA-256 of its path; used to
|
|
35
|
+
// deep-link the Changes tab's per-file rows (getPrDetail).
|
|
36
|
+
function diffAnchorId(path) {
|
|
37
|
+
return createHash('sha256').update(path, 'utf8').digest('hex');
|
|
38
|
+
}
|
|
33
39
|
function mapUser(u) {
|
|
34
40
|
return {
|
|
35
41
|
id: u.id,
|
|
@@ -1441,7 +1447,7 @@ function myTurnReasonsFor(participation, prId) {
|
|
|
1441
1447
|
// Gated on the feature flag (force-off in cloud) so no items appear where Claude Review
|
|
1442
1448
|
// doesn't exist. Never member-/bot-filtered (a run has no member author).
|
|
1443
1449
|
async function getClaudeReviewFeedItems(accountId, repoIds, since) {
|
|
1444
|
-
if (!
|
|
1450
|
+
if (!getProCapabilities().claudeReview)
|
|
1445
1451
|
return [];
|
|
1446
1452
|
const conds = [
|
|
1447
1453
|
eq(repos.accountId, accountId),
|
|
@@ -2979,7 +2985,7 @@ async function getActionableActivityIds(accountId) {
|
|
|
2979
2985
|
for (const r of await listRepos(accountId))
|
|
2980
2986
|
repoNameById.set(r.id, r.fullName);
|
|
2981
2987
|
const threadIds = new Set((await getThreadsAwaiting(localUserId, accountId, repoNameById)).map((ta) => ta.threadId));
|
|
2982
|
-
const claudeReviewIds =
|
|
2988
|
+
const claudeReviewIds = getProCapabilities().claudeReview
|
|
2983
2989
|
? new Set((await getUnactionedClaudeReviews(accountId)).map((c) => c.reviewId))
|
|
2984
2990
|
: new Set();
|
|
2985
2991
|
return { reviewRequestPrIds, watchedPrIds, approvedPrIds, threadIds, claudeReviewIds };
|
|
@@ -3138,7 +3144,7 @@ export async function getMyTurn(accountId) {
|
|
|
3138
3144
|
}
|
|
3139
3145
|
// Completed-but-unactioned Claude reviews (local-only feature; empty otherwise).
|
|
3140
3146
|
// A manual "Done" hides the run until a newer run finishes (see claudeDismissedIds).
|
|
3141
|
-
const claudeReviewsToAction =
|
|
3147
|
+
const claudeReviewsToAction = getProCapabilities().claudeReview
|
|
3142
3148
|
? (await getUnactionedClaudeReviews(accountId)).filter((c) => !claudeDismissedIds.has(c.reviewId))
|
|
3143
3149
|
: [];
|
|
3144
3150
|
const users = referencedUsers.size > 0
|
|
@@ -3723,138 +3729,24 @@ export async function setRepoInboxWatch(accountId, repoId, watch) {
|
|
|
3723
3729
|
.execute();
|
|
3724
3730
|
return updated.length > 0;
|
|
3725
3731
|
}
|
|
3726
|
-
//
|
|
3727
|
-
//
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
return {
|
|
3733
|
-
id: r.id,
|
|
3734
|
-
reviewId: r.reviewId,
|
|
3735
|
-
path: r.path,
|
|
3736
|
-
line: r.line,
|
|
3737
|
-
side: r.side,
|
|
3738
|
-
diffAnchorId: diffAnchorId(r.path),
|
|
3739
|
-
severity: r.severity,
|
|
3740
|
-
title: r.title,
|
|
3741
|
-
body: r.body,
|
|
3742
|
-
editedBody: r.editedBody,
|
|
3743
|
-
suggestion: r.suggestion,
|
|
3744
|
-
diffHunk: r.diffHunk,
|
|
3745
|
-
anchored: r.anchored,
|
|
3746
|
-
fileInDiff: r.fileInDiff,
|
|
3747
|
-
included: r.included,
|
|
3748
|
-
postedAt: iso(r.postedAt),
|
|
3749
|
-
githubCommentId: r.githubCommentId,
|
|
3750
|
-
postedCommentKind: r.postedCommentKind,
|
|
3751
|
-
createdAt: r.createdAt.toISOString(),
|
|
3752
|
-
};
|
|
3753
|
-
}
|
|
3754
|
-
function mapReview(r, findings) {
|
|
3755
|
-
return {
|
|
3756
|
-
id: r.id,
|
|
3757
|
-
prId: r.prId,
|
|
3758
|
-
headSha: r.headSha,
|
|
3759
|
-
status: r.status,
|
|
3760
|
-
model: r.model,
|
|
3761
|
-
scope: r.scope,
|
|
3762
|
-
reviewMode: r.reviewMode,
|
|
3763
|
-
routeReason: r.routeReason,
|
|
3764
|
-
summary: r.summary,
|
|
3765
|
-
verdict: r.verdict,
|
|
3766
|
-
userBody: r.userBody,
|
|
3767
|
-
userVerdict: r.userVerdict,
|
|
3768
|
-
costUsd: r.costUsd,
|
|
3769
|
-
inputTokens: r.inputTokens,
|
|
3770
|
-
outputTokens: r.outputTokens,
|
|
3771
|
-
cacheReadTokens: r.cacheReadTokens,
|
|
3772
|
-
cacheCreationTokens: r.cacheCreationTokens,
|
|
3773
|
-
numTurns: r.numTurns,
|
|
3774
|
-
diffBytes: r.diffBytes,
|
|
3775
|
-
diffCapped: r.diffCapped,
|
|
3776
|
-
error: r.error,
|
|
3777
|
-
excludedFiles: r.excludedFiles ?? [],
|
|
3778
|
-
postedReviewId: r.postedReviewId,
|
|
3779
|
-
postedAt: iso(r.postedAt),
|
|
3780
|
-
createdAt: r.createdAt.toISOString(),
|
|
3781
|
-
finishedAt: iso(r.finishedAt),
|
|
3782
|
-
findings: findings.map(mapFinding),
|
|
3783
|
-
};
|
|
3784
|
-
}
|
|
3785
|
-
export async function getClaudeReviewById(reviewId, accountId) {
|
|
3786
|
-
const rows = await db
|
|
3787
|
-
.select({ review: claudeReviews })
|
|
3788
|
-
.from(claudeReviews)
|
|
3789
|
-
.innerJoin(pullRequests, eq(pullRequests.id, claudeReviews.prId))
|
|
3790
|
-
.innerJoin(repos, eq(repos.id, pullRequests.repoId))
|
|
3791
|
-
.where(and(eq(claudeReviews.id, reviewId), eq(repos.accountId, accountId)))
|
|
3792
|
-
.limit(1)
|
|
3793
|
-
.execute();
|
|
3794
|
-
const row = rows[0]?.review ?? null;
|
|
3795
|
-
if (!row)
|
|
3796
|
-
return null;
|
|
3797
|
-
const findings = await db
|
|
3798
|
-
.select()
|
|
3799
|
-
.from(claudeReviewFindings)
|
|
3800
|
-
.where(eq(claudeReviewFindings.reviewId, reviewId))
|
|
3801
|
-
.orderBy(asc(claudeReviewFindings.id))
|
|
3802
|
-
.execute();
|
|
3803
|
-
return mapReview(row, findings);
|
|
3804
|
-
}
|
|
3805
|
-
// The most recent run for a PR (with findings), or null if never run.
|
|
3806
|
-
export async function getLatestClaudeReview(prId, accountId) {
|
|
3807
|
-
const rows = await db
|
|
3808
|
-
.select({ id: claudeReviews.id })
|
|
3809
|
-
.from(claudeReviews)
|
|
3810
|
-
.innerJoin(pullRequests, eq(pullRequests.id, claudeReviews.prId))
|
|
3811
|
-
.innerJoin(repos, eq(repos.id, pullRequests.repoId))
|
|
3812
|
-
.where(and(eq(claudeReviews.prId, prId), eq(repos.accountId, accountId)))
|
|
3813
|
-
.orderBy(desc(claudeReviews.id))
|
|
3814
|
-
.limit(1)
|
|
3815
|
-
.execute();
|
|
3816
|
-
const row = rows[0] ?? null;
|
|
3817
|
-
if (!row)
|
|
3818
|
-
return null;
|
|
3819
|
-
return getClaudeReviewById(row.id, accountId);
|
|
3820
|
-
}
|
|
3821
|
-
// All runs for a PR (newest first), lighter shape for the history selector.
|
|
3822
|
-
export async function listClaudeReviewHistory(prId, accountId) {
|
|
3823
|
-
const rows = await db
|
|
3824
|
-
.select({ review: claudeReviews })
|
|
3825
|
-
.from(claudeReviews)
|
|
3826
|
-
.innerJoin(pullRequests, eq(pullRequests.id, claudeReviews.prId))
|
|
3827
|
-
.innerJoin(repos, eq(repos.id, pullRequests.repoId))
|
|
3828
|
-
.where(and(eq(claudeReviews.prId, prId), eq(repos.accountId, accountId)))
|
|
3829
|
-
.orderBy(desc(claudeReviews.id))
|
|
3830
|
-
.execute();
|
|
3831
|
-
return rows.map(({ review: r }) => ({
|
|
3832
|
-
id: r.id,
|
|
3833
|
-
headSha: r.headSha,
|
|
3834
|
-
status: r.status,
|
|
3835
|
-
model: r.model,
|
|
3836
|
-
scope: r.scope,
|
|
3837
|
-
reviewMode: r.reviewMode,
|
|
3838
|
-
verdict: r.verdict,
|
|
3839
|
-
userVerdict: r.userVerdict,
|
|
3840
|
-
costUsd: r.costUsd,
|
|
3841
|
-
postedAt: iso(r.postedAt),
|
|
3842
|
-
createdAt: r.createdAt.toISOString(),
|
|
3843
|
-
finishedAt: iso(r.finishedAt),
|
|
3844
|
-
}));
|
|
3845
|
-
}
|
|
3732
|
+
// ---- Claude Review reads (CORE-owned surfaces only) ----
|
|
3733
|
+
// The FEATURE-only reads (getClaudeReviewById/Latest/History/listAll + the post/PR
|
|
3734
|
+
// contexts + mapReview/mapFinding) MOVED to packages/pro/src/claude-review/persist.ts
|
|
3735
|
+
// when Claude Review became a Pro capability. The two readers below stay CORE because
|
|
3736
|
+
// CORE surfaces consume them: listClaudeReviewsByRepo (Activity console) +
|
|
3737
|
+
// getUnactionedClaudeReviews (My-Turn inbox). Both read the still-core tables.
|
|
3846
3738
|
// Repo-oriented Claude-review retrieval for the Activity single-repo console: ALL runs
|
|
3847
3739
|
// for a repo's PRs, grouped by PR (newest run first within each), PRs ordered by
|
|
3848
3740
|
// most-recent run desc. Richer than listAllClaudeReviews (which keeps only one
|
|
3849
3741
|
// latest-succeeded run per PR). IDOR-sensitive id getter: scoped by accountId, and
|
|
3850
|
-
// gated on
|
|
3742
|
+
// gated on getProCapabilities().claudeReview. An unowned repo (cross-account) → empty list.
|
|
3851
3743
|
export async function listClaudeReviewsByRepo(repoId, accountId) {
|
|
3852
|
-
if (!
|
|
3744
|
+
if (!getProCapabilities().claudeReview)
|
|
3853
3745
|
return { enabled: false, prs: [] };
|
|
3854
3746
|
// Ownership: a repo not owned by this account leaks nothing (404-equivalent).
|
|
3855
3747
|
const owned = await getRepo(repoId, accountId);
|
|
3856
3748
|
if (!owned)
|
|
3857
|
-
return { enabled:
|
|
3749
|
+
return { enabled: getProCapabilities().claudeReview, prs: [] };
|
|
3858
3750
|
const rows = await db
|
|
3859
3751
|
.select({
|
|
3860
3752
|
review: claudeReviews,
|
|
@@ -3902,60 +3794,6 @@ export async function listClaudeReviewsByRepo(repoId, accountId) {
|
|
|
3902
3794
|
}
|
|
3903
3795
|
return { enabled: true, prs: [...byPr.values()] };
|
|
3904
3796
|
}
|
|
3905
|
-
// Cross-PR list of prior Claude reviews: ONE entry per PR = that PR's most-recent
|
|
3906
|
-
// SUCCEEDED run, accountId-scoped, restricted to PRs still within the timeline
|
|
3907
|
-
// window (open, or last touched within `backfillDays`), newest-first by finish
|
|
3908
|
-
// time. Used by GET /api/claude-reviews to populate the "prior reviews" view.
|
|
3909
|
-
export async function listAllClaudeReviews(accountId) {
|
|
3910
|
-
// Same window cutoff getTimeline uses for its overlap predicate (now − backfillDays).
|
|
3911
|
-
const cutoff = new Date(Date.now() - config.backfillDays * 24 * 60 * 60 * 1000);
|
|
3912
|
-
const rows = await db
|
|
3913
|
-
.select({
|
|
3914
|
-
reviewId: claudeReviews.id,
|
|
3915
|
-
prId: claudeReviews.prId,
|
|
3916
|
-
owner: repos.owner,
|
|
3917
|
-
name: repos.name,
|
|
3918
|
-
prNumber: pullRequests.number,
|
|
3919
|
-
prTitle: pullRequests.title,
|
|
3920
|
-
prState: pullRequests.state,
|
|
3921
|
-
summary: claudeReviews.summary,
|
|
3922
|
-
verdict: claudeReviews.verdict,
|
|
3923
|
-
headSha: claudeReviews.headSha,
|
|
3924
|
-
status: claudeReviews.status,
|
|
3925
|
-
createdAt: claudeReviews.createdAt,
|
|
3926
|
-
finishedAt: claudeReviews.finishedAt,
|
|
3927
|
-
})
|
|
3928
|
-
.from(claudeReviews)
|
|
3929
|
-
.innerJoin(pullRequests, eq(pullRequests.id, claudeReviews.prId))
|
|
3930
|
-
.innerJoin(repos, eq(repos.id, pullRequests.repoId))
|
|
3931
|
-
.where(and(eq(repos.accountId, accountId), eq(claudeReviews.status, 'succeeded'), or(eq(pullRequests.state, 'open'), gte(sql `coalesce(${pullRequests.mergedAt}, ${pullRequests.closedAt}, ${pullRequests.openedAt})`, tsBound(cutoff)))))
|
|
3932
|
-
.orderBy(desc(claudeReviews.finishedAt), desc(claudeReviews.createdAt))
|
|
3933
|
-
.execute();
|
|
3934
|
-
// Keep the first (most-recent) succeeded run per PR. N is small (single local
|
|
3935
|
-
// user), so a JS pass is simpler and portable across both dialects.
|
|
3936
|
-
const seen = new Set();
|
|
3937
|
-
const items = [];
|
|
3938
|
-
for (const r of rows) {
|
|
3939
|
-
if (seen.has(r.prId))
|
|
3940
|
-
continue;
|
|
3941
|
-
seen.add(r.prId);
|
|
3942
|
-
items.push({
|
|
3943
|
-
reviewId: r.reviewId,
|
|
3944
|
-
prId: r.prId,
|
|
3945
|
-
repoFullName: `${r.owner}/${r.name}`,
|
|
3946
|
-
prNumber: r.prNumber,
|
|
3947
|
-
prTitle: r.prTitle,
|
|
3948
|
-
prState: r.prState,
|
|
3949
|
-
summary: r.summary,
|
|
3950
|
-
verdict: r.verdict,
|
|
3951
|
-
headSha: r.headSha,
|
|
3952
|
-
status: r.status,
|
|
3953
|
-
createdAt: r.createdAt.toISOString(),
|
|
3954
|
-
finishedAt: iso(r.finishedAt),
|
|
3955
|
-
});
|
|
3956
|
-
}
|
|
3957
|
-
return items;
|
|
3958
|
-
}
|
|
3959
3797
|
// Completed Claude reviews on OPEN PRs that haven't been actioned: each PR's
|
|
3960
3798
|
// MOST-RECENT succeeded run, kept only when it was never posted (postedAt null).
|
|
3961
3799
|
// Account-scoped. Feeds the My Turn "Claude reviews to action" section so finished
|
|
@@ -4003,91 +3841,6 @@ export async function getUnactionedClaudeReviews(accountId) {
|
|
|
4003
3841
|
}
|
|
4004
3842
|
return out;
|
|
4005
3843
|
}
|
|
4006
|
-
export async function getFindingPostContext(findingId, accountId) {
|
|
4007
|
-
const rows = await db
|
|
4008
|
-
.select({
|
|
4009
|
-
finding: claudeReviewFindings,
|
|
4010
|
-
reviewId: claudeReviews.id,
|
|
4011
|
-
reviewHeadSha: claudeReviews.headSha,
|
|
4012
|
-
owner: repos.owner,
|
|
4013
|
-
name: repos.name,
|
|
4014
|
-
prNumber: pullRequests.number,
|
|
4015
|
-
})
|
|
4016
|
-
.from(claudeReviewFindings)
|
|
4017
|
-
.innerJoin(claudeReviews, eq(claudeReviews.id, claudeReviewFindings.reviewId))
|
|
4018
|
-
.innerJoin(pullRequests, eq(pullRequests.id, claudeReviews.prId))
|
|
4019
|
-
.innerJoin(repos, eq(repos.id, pullRequests.repoId))
|
|
4020
|
-
.where(and(eq(claudeReviewFindings.id, findingId), eq(repos.accountId, accountId)))
|
|
4021
|
-
.limit(1)
|
|
4022
|
-
.execute();
|
|
4023
|
-
const row = rows[0] ?? null;
|
|
4024
|
-
if (!row)
|
|
4025
|
-
return null;
|
|
4026
|
-
return {
|
|
4027
|
-
finding: row.finding,
|
|
4028
|
-
reviewId: row.reviewId,
|
|
4029
|
-
reviewHeadSha: row.reviewHeadSha,
|
|
4030
|
-
owner: row.owner,
|
|
4031
|
-
name: row.name,
|
|
4032
|
-
prNumber: row.prNumber,
|
|
4033
|
-
};
|
|
4034
|
-
}
|
|
4035
|
-
export async function getClaudeReviewContext(reviewId, accountId) {
|
|
4036
|
-
const rows = await db
|
|
4037
|
-
.select({
|
|
4038
|
-
review: claudeReviews,
|
|
4039
|
-
owner: repos.owner,
|
|
4040
|
-
name: repos.name,
|
|
4041
|
-
prNumber: pullRequests.number,
|
|
4042
|
-
})
|
|
4043
|
-
.from(claudeReviews)
|
|
4044
|
-
.innerJoin(pullRequests, eq(pullRequests.id, claudeReviews.prId))
|
|
4045
|
-
.innerJoin(repos, eq(repos.id, pullRequests.repoId))
|
|
4046
|
-
.where(and(eq(claudeReviews.id, reviewId), eq(repos.accountId, accountId)))
|
|
4047
|
-
.limit(1)
|
|
4048
|
-
.execute();
|
|
4049
|
-
const row = rows[0] ?? null;
|
|
4050
|
-
if (!row)
|
|
4051
|
-
return null;
|
|
4052
|
-
return {
|
|
4053
|
-
review: row.review,
|
|
4054
|
-
owner: row.owner,
|
|
4055
|
-
name: row.name,
|
|
4056
|
-
prNumber: row.prNumber,
|
|
4057
|
-
};
|
|
4058
|
-
}
|
|
4059
|
-
export async function getReviewPrContext(prId, accountId) {
|
|
4060
|
-
const rows = await db
|
|
4061
|
-
.select({
|
|
4062
|
-
prId: pullRequests.id,
|
|
4063
|
-
owner: repos.owner,
|
|
4064
|
-
name: repos.name,
|
|
4065
|
-
number: pullRequests.number,
|
|
4066
|
-
title: pullRequests.title,
|
|
4067
|
-
body: pullRequests.body,
|
|
4068
|
-
baseRefName: pullRequests.baseRefName,
|
|
4069
|
-
headSha: pullRequests.headSha,
|
|
4070
|
-
})
|
|
4071
|
-
.from(pullRequests)
|
|
4072
|
-
.innerJoin(repos, eq(repos.id, pullRequests.repoId))
|
|
4073
|
-
.where(and(eq(pullRequests.id, prId), eq(repos.accountId, accountId)))
|
|
4074
|
-
.limit(1)
|
|
4075
|
-
.execute();
|
|
4076
|
-
const row = rows[0] ?? null;
|
|
4077
|
-
if (!row)
|
|
4078
|
-
return null;
|
|
4079
|
-
return {
|
|
4080
|
-
prId: row.prId,
|
|
4081
|
-
owner: row.owner,
|
|
4082
|
-
name: row.name,
|
|
4083
|
-
repoFullName: `${row.owner}/${row.name}`,
|
|
4084
|
-
number: row.number,
|
|
4085
|
-
title: row.title,
|
|
4086
|
-
body: row.body,
|
|
4087
|
-
baseRefName: row.baseRefName,
|
|
4088
|
-
headSha: row.headSha,
|
|
4089
|
-
};
|
|
4090
|
-
}
|
|
4091
3844
|
// Resolve a review thread to its GitHub node id + parent-PR coordinates, for a
|
|
4092
3845
|
// reply (addPullRequestReviewThreadReply) or resolve/unresolve mutation. The
|
|
4093
3846
|
// thread's GitHub node id is the GraphQL `pullRequestReviewThreadId` / `threadId`.
|
package/dist/index.js
CHANGED
|
@@ -28,12 +28,8 @@ export async function start() {
|
|
|
28
28
|
console.warn('local user unknown (gh api user failed) — "my turn" disabled');
|
|
29
29
|
}
|
|
30
30
|
const app = await buildApp();
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
if (config.claudeReviewEnabled) {
|
|
34
|
-
const { reconcileReviewsOnStartup } = await import('./review/review-manager.js');
|
|
35
|
-
await reconcileReviewsOnStartup(app.log);
|
|
36
|
-
}
|
|
31
|
+
// (Claude Review moved into @pierre/pro — its crash-orphan reconcile now runs inside
|
|
32
|
+
// plugin.register during bindProPlugin below, alongside the AI-Fix reconcile.)
|
|
37
33
|
// Bind the optional Pro plugin (dynamic import; no-ops in OSS mode). Same
|
|
38
34
|
// "optional subsystem, degrade gracefully" posture as the scheduler below.
|
|
39
35
|
{
|
package/dist/pro/bind.js
CHANGED
|
@@ -9,6 +9,7 @@ import { recordAiUsage, getAiUsageSummary } from '../db/usage.js';
|
|
|
9
9
|
import { reviewEvents, registerLearningsProvider } from '../review/events.js';
|
|
10
10
|
import { cheapComplete } from '../review/llm.js';
|
|
11
11
|
import { detectClaudeAuth } from '../review/auth.js';
|
|
12
|
+
import { hasUserAnthropicKey, setUserAnthropicKey, } from '../review/local-settings.js';
|
|
12
13
|
import { getAccessToken } from '../auth/account.js';
|
|
13
14
|
import { createPullRequest, fetchPrHeadInfo, fetchPrUnifiedDiff, } from '../github/mutations.js';
|
|
14
15
|
import { fetchActionsJobLog } from '../github/actions-logs.js';
|
|
@@ -42,7 +43,7 @@ export async function bindProPlugin(app) {
|
|
|
42
43
|
if (!mod)
|
|
43
44
|
return;
|
|
44
45
|
const plugin = (mod.default ?? mod);
|
|
45
|
-
if (plugin?.apiVersion !==
|
|
46
|
+
if (plugin?.apiVersion !== 7 || typeof plugin.register !== 'function') {
|
|
46
47
|
app.log.warn({ apiVersion: plugin?.apiVersion }, 'pro contract mismatch — skipped');
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
@@ -105,6 +106,19 @@ export async function bindProPlugin(app) {
|
|
|
105
106
|
mergeResolveAndPush: async (a) => (await import('../coding/merge.js')).mergeResolveAndPush(a),
|
|
106
107
|
pushResolved: async (a) => (await import('../coding/merge.js')).pushResolved(a),
|
|
107
108
|
},
|
|
109
|
+
// Claude Review infra: diff prep + the SDK run + the GitHub review POST. Lazy so the
|
|
110
|
+
// Agent SDK (agent.js) loads only when a review actually runs, not at every boot.
|
|
111
|
+
review: {
|
|
112
|
+
prepareReview: async (a) => (await import('../review/prepare.js')).prepareReview(a),
|
|
113
|
+
runReview: async (a) => (await import('../review/agent.js')).runReview(a),
|
|
114
|
+
postReview: async (a) => (await import('../review/post-seam.js')).postReview(a),
|
|
115
|
+
postFinding: async (a) => (await import('../review/post-seam.js')).postFinding(a),
|
|
116
|
+
getLocalKeyStatus: () => ({ hasUserKey: hasUserAnthropicKey() }),
|
|
117
|
+
setLocalKey: (k) => {
|
|
118
|
+
setUserAnthropicKey(k ?? '');
|
|
119
|
+
return { hasUserKey: hasUserAnthropicKey(), auth: detectClaudeAuth().status };
|
|
120
|
+
},
|
|
121
|
+
},
|
|
108
122
|
};
|
|
109
123
|
try {
|
|
110
124
|
setProCapabilities(await plugin.register(app, ctx));
|