pierre-review 0.1.27 → 0.1.29
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/plugins/auth.js +1 -0
- package/dist/api/routes/auth.js +1 -0
- package/dist/api/routes/claude-review.js +78 -36
- package/dist/auth/account.js +12 -1
- package/dist/db/migrations/0017_account_display_name.sql +7 -0
- package/dist/db/migrations/0018_finding_comment_kind.sql +7 -0
- package/dist/db/migrations/0019_finding_file_in_diff.sql +7 -0
- package/dist/db/migrations/meta/_journal.json +21 -0
- package/dist/db/migrations-pg/0007_same_human_torch.sql +2 -0
- package/dist/db/migrations-pg/0008_slimy_toxin.sql +1 -0
- package/dist/db/migrations-pg/meta/0007_snapshot.json +2219 -0
- package/dist/db/migrations-pg/meta/0008_snapshot.json +2226 -0
- package/dist/db/migrations-pg/meta/_journal.json +14 -0
- package/dist/db/queries.js +20 -0
- package/dist/db/schema.pg.js +15 -0
- package/dist/db/schema.sqlite.js +15 -0
- package/dist/review/agent.js +4 -0
- package/dist/review/persist.js +28 -10
- package/dist/review/post-review.js +43 -12
- package/package.json +1 -1
- package/public/assets/{index-miJLSfq1.js → index-Bt3PcfJN.js} +90 -90
- package/public/assets/index-Df7dQP0l.css +10 -0
- package/public/index.html +2 -2
- package/public/assets/index-BbKz1lfU.css +0 -10
package/dist/api/plugins/auth.js
CHANGED
package/dist/api/routes/auth.js
CHANGED
|
@@ -4,7 +4,7 @@ import { markFindingPosted, markReviewPosted, updateFinding, updateReviewDraft,
|
|
|
4
4
|
import { getReviewStatus, listActiveReviews, requestReviewCancel, startReview, } from '../../review/review-manager.js';
|
|
5
5
|
import { detectClaudeAuth } from '../../review/auth.js';
|
|
6
6
|
import { hasUserAnthropicKey, setUserAnthropicKey, } from '../../review/local-settings.js';
|
|
7
|
-
import { buildAnchorIndex, buildReview, fallbackAnchor, fetchCurrentHeadSha, fetchPrDiff, findingCommentBody, stripNoiseFromDiff, submitGithubComment, submitGithubReview, } from '../../review/post-review.js';
|
|
7
|
+
import { buildAnchorIndex, buildReview, fallbackAnchor, fetchCurrentHeadSha, fetchPrDiff, findingCommentBody, prLevelFindingBody, stripNoiseFromDiff, submitGithubComment, submitGithubIssueComment, submitGithubReview, } from '../../review/post-review.js';
|
|
8
8
|
import { isNoiseFile } from '../../review/prompt.js';
|
|
9
9
|
import { accountIdOf } from '../plugins/auth.js';
|
|
10
10
|
const MODELS = ['claude-opus-4-8', 'claude-sonnet-4-6'];
|
|
@@ -233,8 +233,12 @@ export async function claudeReviewRoutes(app) {
|
|
|
233
233
|
}
|
|
234
234
|
return { status: 'ok' };
|
|
235
235
|
});
|
|
236
|
-
// Post a single
|
|
237
|
-
//
|
|
236
|
+
// Post a single finding as a standalone comment (no review submitted). The
|
|
237
|
+
// destination is chosen AUTOMATICALLY from the live diff: anchorable on its own
|
|
238
|
+
// line → an inline comment there; unanchored but its file is in the diff → an
|
|
239
|
+
// inline comment on the file's first change; its file is NOT in the diff (e.g. a
|
|
240
|
+
// deep review on an unchanged file) → a standalone PR-level comment marked as
|
|
241
|
+
// outside the PR's diff. Pins to the run's head SHA; 409 if the PR head has moved.
|
|
238
242
|
app.post('/api/claude-findings/:findingId/post', { schema: findingIdParam }, async (req, reply) => {
|
|
239
243
|
if (!config.claudeReviewEnabled)
|
|
240
244
|
return featureOff(reply);
|
|
@@ -254,47 +258,61 @@ export async function claudeReviewRoutes(app) {
|
|
|
254
258
|
message: 'The PR head has moved since this review. Re-review before posting.',
|
|
255
259
|
};
|
|
256
260
|
}
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
let line;
|
|
260
|
-
let side = f.side;
|
|
261
|
-
let onFallback = false;
|
|
261
|
+
const postedAt = new Date().toISOString();
|
|
262
|
+
// Anchorable on its own line → inline comment there.
|
|
262
263
|
if (f.line != null && f.anchored) {
|
|
263
|
-
|
|
264
|
+
const { commentId } = await submitGithubComment({
|
|
265
|
+
owner: ctx.owner,
|
|
266
|
+
name: ctx.name,
|
|
267
|
+
prNumber: ctx.prNumber,
|
|
268
|
+
commitId: ctx.reviewHeadSha,
|
|
269
|
+
path: f.path,
|
|
270
|
+
line: f.line,
|
|
271
|
+
side: f.side,
|
|
272
|
+
body: findingCommentBody({
|
|
273
|
+
body: f.body,
|
|
274
|
+
editedBody: f.editedBody,
|
|
275
|
+
suggestion: f.suggestion,
|
|
276
|
+
}),
|
|
277
|
+
});
|
|
278
|
+
await markFindingPosted(findingId, commentId, 'inline');
|
|
279
|
+
const result = { githubCommentId: commentId, postedAt };
|
|
280
|
+
return result;
|
|
264
281
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
282
|
+
// Otherwise consult the diff. If the file IS in the diff, re-anchor to its
|
|
283
|
+
// first change (inline). If it isn't, post a standalone PR-level comment.
|
|
284
|
+
const { diff } = stripNoiseFromDiff(await fetchPrDiff(ctx.owner, ctx.name, ctx.prNumber), isNoiseFile);
|
|
285
|
+
const fb = fallbackAnchor(buildAnchorIndex(diff), f.path);
|
|
286
|
+
if (fb) {
|
|
287
|
+
const { commentId } = await submitGithubComment({
|
|
288
|
+
owner: ctx.owner,
|
|
289
|
+
name: ctx.name,
|
|
290
|
+
prNumber: ctx.prNumber,
|
|
291
|
+
commitId: ctx.reviewHeadSha,
|
|
292
|
+
path: f.path,
|
|
293
|
+
line: fb.line,
|
|
294
|
+
side: fb.side,
|
|
295
|
+
body: findingCommentBody({ body: f.body, editedBody: f.editedBody, suggestion: f.suggestion }, { fallbackNote: true }),
|
|
296
|
+
});
|
|
297
|
+
await markFindingPosted(findingId, commentId, 'inline');
|
|
298
|
+
const result = { githubCommentId: commentId, postedAt };
|
|
299
|
+
return result;
|
|
278
300
|
}
|
|
279
|
-
|
|
301
|
+
// File outside the PR's diff → standalone PR-level (issue) comment.
|
|
302
|
+
const { commentId } = await submitGithubIssueComment({
|
|
280
303
|
owner: ctx.owner,
|
|
281
304
|
name: ctx.name,
|
|
282
305
|
prNumber: ctx.prNumber,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
side,
|
|
287
|
-
body: findingCommentBody({
|
|
306
|
+
body: prLevelFindingBody({
|
|
307
|
+
path: f.path,
|
|
308
|
+
line: f.line,
|
|
288
309
|
body: f.body,
|
|
289
310
|
editedBody: f.editedBody,
|
|
290
311
|
suggestion: f.suggestion,
|
|
291
|
-
}
|
|
312
|
+
}),
|
|
292
313
|
});
|
|
293
|
-
await markFindingPosted(findingId, commentId);
|
|
294
|
-
const result = {
|
|
295
|
-
githubCommentId: commentId,
|
|
296
|
-
postedAt: new Date().toISOString(),
|
|
297
|
-
};
|
|
314
|
+
await markFindingPosted(findingId, commentId, 'pr_comment');
|
|
315
|
+
const result = { githubCommentId: commentId, postedAt };
|
|
298
316
|
return result;
|
|
299
317
|
}
|
|
300
318
|
catch (err) {
|
|
@@ -356,12 +374,36 @@ export async function claudeReviewRoutes(app) {
|
|
|
356
374
|
event: userVerdict,
|
|
357
375
|
comments: built.preview.comments,
|
|
358
376
|
});
|
|
359
|
-
|
|
377
|
+
// The review is now LIVE on GitHub and can't be un-posted. Findings whose
|
|
378
|
+
// file isn't in the diff post as standalone PR-level comments alongside it
|
|
379
|
+
// (one issue comment each), so a deep review's findings on unchanged files
|
|
380
|
+
// still land rather than being dropped. Each post is best-effort: a single
|
|
381
|
+
// failed comment must NOT strand the already-posted review (that would leave
|
|
382
|
+
// the run unstamped and tempt a duplicate re-post), so we collect what lands
|
|
383
|
+
// and ALWAYS stamp afterwards. Findings that fail simply stay un-posted and
|
|
384
|
+
// can be posted individually from their row.
|
|
385
|
+
const prCommentResults = [];
|
|
386
|
+
for (const pc of built.preview.prComments) {
|
|
387
|
+
try {
|
|
388
|
+
const { commentId } = await submitGithubIssueComment({
|
|
389
|
+
owner: ctx.owner,
|
|
390
|
+
name: ctx.name,
|
|
391
|
+
prNumber: ctx.prNumber,
|
|
392
|
+
body: pc.body,
|
|
393
|
+
});
|
|
394
|
+
prCommentResults.push({ findingId: pc.findingId, commentId });
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
req.log.warn({ err, findingId: pc.findingId, path: pc.path }, 'failed to post a PR-level comment for an off-diff finding');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
await markReviewPosted(reviewId, ghReviewId, built.inlineFindingIds, prCommentResults);
|
|
360
401
|
const result = {
|
|
361
402
|
postedReviewId: ghReviewId,
|
|
362
403
|
postedAt: new Date().toISOString(),
|
|
363
404
|
postedCommentCount: built.preview.comments.length,
|
|
364
|
-
|
|
405
|
+
// Count what actually posted (a comment may have failed best-effort above).
|
|
406
|
+
prCommentCount: prCommentResults.length,
|
|
365
407
|
};
|
|
366
408
|
return result;
|
|
367
409
|
}
|
package/dist/auth/account.js
CHANGED
|
@@ -15,6 +15,7 @@ function fetchFromGh() {
|
|
|
15
15
|
return {
|
|
16
16
|
login: parsed.login,
|
|
17
17
|
node_id: parsed.node_id,
|
|
18
|
+
name: parsed.name ?? null,
|
|
18
19
|
avatar_url: parsed.avatar_url ?? null,
|
|
19
20
|
};
|
|
20
21
|
}
|
|
@@ -28,6 +29,7 @@ function rowToAccount(row) {
|
|
|
28
29
|
id: row.id,
|
|
29
30
|
githubUserId: row.githubUserId,
|
|
30
31
|
githubLogin: row.githubLogin,
|
|
32
|
+
displayName: row.displayName,
|
|
31
33
|
avatarUrl: row.avatarUrl,
|
|
32
34
|
isLocal: row.isLocal,
|
|
33
35
|
};
|
|
@@ -53,7 +55,11 @@ export async function ensureLocalAccount() {
|
|
|
53
55
|
const fresh = existing &&
|
|
54
56
|
existing.githubUserId !== '' &&
|
|
55
57
|
existing.lastLoginAt != null &&
|
|
56
|
-
Date.now() - existing.lastLoginAt.getTime() < STALE_MS
|
|
58
|
+
Date.now() - existing.lastLoginAt.getTime() < STALE_MS &&
|
|
59
|
+
// Backfill the display name on the first run after it was added (older rows have
|
|
60
|
+
// it NULL). A genuinely name-less GitHub user re-fetches each startup — cheap;
|
|
61
|
+
// the daily refresh would repopulate it anyway.
|
|
62
|
+
existing.displayName != null;
|
|
57
63
|
if (existing && fresh) {
|
|
58
64
|
cachedLocalAccount = rowToAccount(existing);
|
|
59
65
|
return cachedLocalAccount;
|
|
@@ -70,6 +76,7 @@ export async function ensureLocalAccount() {
|
|
|
70
76
|
id: LOCAL_ACCOUNT_ID,
|
|
71
77
|
githubUserId: gh.node_id,
|
|
72
78
|
githubLogin: gh.login,
|
|
79
|
+
displayName: gh.name,
|
|
73
80
|
avatarUrl: gh.avatar_url,
|
|
74
81
|
isLocal: true,
|
|
75
82
|
lastLoginAt: new Date(),
|
|
@@ -79,6 +86,7 @@ export async function ensureLocalAccount() {
|
|
|
79
86
|
set: {
|
|
80
87
|
githubUserId: gh.node_id,
|
|
81
88
|
githubLogin: gh.login,
|
|
89
|
+
displayName: gh.name,
|
|
82
90
|
avatarUrl: gh.avatar_url,
|
|
83
91
|
isLocal: true,
|
|
84
92
|
lastLoginAt: new Date(),
|
|
@@ -106,6 +114,7 @@ export async function upsertCloudAccount(input) {
|
|
|
106
114
|
.values({
|
|
107
115
|
githubUserId: input.githubUserId,
|
|
108
116
|
githubLogin: input.githubLogin,
|
|
117
|
+
displayName: input.displayName,
|
|
109
118
|
avatarUrl: input.avatarUrl,
|
|
110
119
|
accessTokenEnc: input.accessTokenEnc,
|
|
111
120
|
isLocal: false,
|
|
@@ -118,6 +127,7 @@ export async function upsertCloudAccount(input) {
|
|
|
118
127
|
target: accounts.githubUserId,
|
|
119
128
|
set: {
|
|
120
129
|
githubLogin: input.githubLogin,
|
|
130
|
+
displayName: input.displayName,
|
|
121
131
|
avatarUrl: input.avatarUrl,
|
|
122
132
|
accessTokenEnc: input.accessTokenEnc,
|
|
123
133
|
lastLoginAt: now,
|
|
@@ -218,6 +228,7 @@ export function accountToLocalUser(account) {
|
|
|
218
228
|
login: account.githubLogin,
|
|
219
229
|
githubId: account.githubUserId,
|
|
220
230
|
avatarUrl: account.avatarUrl,
|
|
231
|
+
displayName: account.displayName,
|
|
221
232
|
};
|
|
222
233
|
}
|
|
223
234
|
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- The signed-in user's GitHub display name (additive). `display_name` captures the
|
|
2
|
+
-- `name` field from `gh api user` (local) / OAuth `GET /user` (cloud), shown wherever
|
|
3
|
+
-- the logged-in identity appears (header, greeting) in place of the @login. Nullable;
|
|
4
|
+
-- existing rows stay NULL until the next identity refresh repopulates them (local:
|
|
5
|
+
-- ensureLocalAccount refetches when display_name is NULL; cloud: on next sign-in).
|
|
6
|
+
-- Postgres baseline is regenerated separately via db:generate:pg.
|
|
7
|
+
ALTER TABLE `accounts` ADD `display_name` text;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Records how a posted Claude-review finding was attached to the PR (additive):
|
|
2
|
+
-- 'inline' = a review comment on a diff line, 'pr_comment' = a standalone PR-level
|
|
3
|
+
-- issue comment (used when the user posts an UNANCHORED finding individually rather
|
|
4
|
+
-- than forcing it onto a diff line). Null until posted; the UI uses it to build the
|
|
5
|
+
-- correct GitHub permalink (#discussion_r vs #issuecomment). SQLite-only — Claude
|
|
6
|
+
-- Review is force-disabled in cloud, so the Postgres table is never populated.
|
|
7
|
+
ALTER TABLE `claude_review_findings` ADD `posted_comment_kind` text;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Whether a Claude-review finding's file is part of the PR's diff (additive). true ⇒
|
|
2
|
+
-- an unanchored finding posts inline on the file's first change; false ⇒ the file is
|
|
3
|
+
-- outside the PR's diff (e.g. a deep review on an unchanged file) so it posts as a
|
|
4
|
+
-- standalone PR-level comment instead of being forced onto a diff line. NOT NULL
|
|
5
|
+
-- DEFAULT 1 so pre-existing findings keep the inline-on-first-change behavior.
|
|
6
|
+
-- SQLite-only — Claude Review is force-disabled in cloud.
|
|
7
|
+
ALTER TABLE `claude_review_findings` ADD `file_in_diff` integer NOT NULL DEFAULT 1;
|
|
@@ -120,6 +120,27 @@
|
|
|
120
120
|
"when": 1780800000007,
|
|
121
121
|
"tag": "0016_repo_viewer_permission",
|
|
122
122
|
"breakpoints": true
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"idx": 17,
|
|
126
|
+
"version": "6",
|
|
127
|
+
"when": 1780800000008,
|
|
128
|
+
"tag": "0017_account_display_name",
|
|
129
|
+
"breakpoints": true
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"idx": 18,
|
|
133
|
+
"version": "6",
|
|
134
|
+
"when": 1780800000009,
|
|
135
|
+
"tag": "0018_finding_comment_kind",
|
|
136
|
+
"breakpoints": true
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"idx": 19,
|
|
140
|
+
"version": "6",
|
|
141
|
+
"when": 1780800000010,
|
|
142
|
+
"tag": "0019_finding_file_in_diff",
|
|
143
|
+
"breakpoints": true
|
|
123
144
|
}
|
|
124
145
|
]
|
|
125
146
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "claude_review_findings" ADD COLUMN "file_in_diff" boolean DEFAULT true NOT NULL;
|