pierre-review 0.1.38 → 0.1.40
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/activity.js +45 -0
- package/dist/api/routes/claude-review.js +48 -1
- package/dist/api/routes/feed.js +3 -1
- package/dist/api/routes/me.js +2 -0
- package/dist/api/routes/repos.js +1 -1
- package/dist/app.js +2 -0
- package/dist/config.js +13 -0
- package/dist/db/queries.js +665 -22
- package/dist/github/queries.js +7 -6
- package/dist/index.js +6 -0
- package/dist/pro/bind.js +77 -0
- package/dist/pro/contract.js +12 -0
- package/dist/pro/migrate.js +64 -0
- package/dist/review/agent.js +1 -0
- package/dist/review/events.js +31 -0
- package/dist/review/llm.js +92 -0
- package/dist/review/prompt.js +11 -1
- package/dist/review/review-manager.js +10 -0
- package/dist/sync/upsert.js +11 -8
- package/package.json +1 -1
- package/public/assets/index-BKL9L_wy.js +1374 -0
- package/public/assets/index-DrkadOKE.css +10 -0
- package/public/index.html +2 -2
- package/public/assets/index-CtGajsI3.css +0 -10
- package/public/assets/index-D8tearaM.js +0 -1373
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getActivity, getConsolidatedFeed, listClaudeReviewsByRepo } from '../../db/queries.js';
|
|
2
|
+
import { accountIdOf } from '../plugins/auth.js';
|
|
3
|
+
function parseIntList(raw) {
|
|
4
|
+
if (!raw)
|
|
5
|
+
return null;
|
|
6
|
+
const ids = raw
|
|
7
|
+
.split(',')
|
|
8
|
+
.map((s) => Number.parseInt(s.trim(), 10))
|
|
9
|
+
.filter((n) => Number.isFinite(n));
|
|
10
|
+
return ids.length > 0 ? ids : null;
|
|
11
|
+
}
|
|
12
|
+
export async function activityRoutes(app) {
|
|
13
|
+
// The Activity aggregate: per repo, current-state stats + thread totals +
|
|
14
|
+
// attention/unread flags + open PRs. Scoped to the account; `repoIds` + `userIds`
|
|
15
|
+
// narrow to the active FilterBar repo + member selection (across ALL the account's
|
|
16
|
+
// repos — watched-only was dropped). Pure DB read — no GitHub sync, no AI.
|
|
17
|
+
app.get('/api/activity', async (req) => {
|
|
18
|
+
const q = req.query;
|
|
19
|
+
return getActivity(accountIdOf(req), parseIntList(q.repoIds), parseIntList(q.userIds));
|
|
20
|
+
});
|
|
21
|
+
// The consolidated Feed (the Activity "Feed" entry): one flat, chronological stream
|
|
22
|
+
// merging My Turn actionables + the activity feed, deduped. Scoped by the FilterBar
|
|
23
|
+
// repo + member selection across ALL the account's repos (watched-only dropped).
|
|
24
|
+
// Pure DB read — no GitHub sync, no AI.
|
|
25
|
+
app.get('/api/activity/feed', async (req) => {
|
|
26
|
+
const q = req.query;
|
|
27
|
+
const limit = q.limit != null ? Number(q.limit) : null;
|
|
28
|
+
const offset = q.offset != null ? Number(q.offset) : 0;
|
|
29
|
+
return getConsolidatedFeed(accountIdOf(req), {
|
|
30
|
+
repoIds: parseIntList(q.repoIds),
|
|
31
|
+
userIds: parseIntList(q.userIds),
|
|
32
|
+
limit: Number.isFinite(limit) && limit != null && limit > 0 ? limit : null,
|
|
33
|
+
offset: Number.isFinite(offset) && offset > 0 ? offset : 0,
|
|
34
|
+
excludeBots: q.excludeBots === 'true',
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
// Repo-scoped Claude-review history for the Activity single-repo console. Ownership +
|
|
38
|
+
// feature-gating are handled inside the query (an unowned repo / disabled feature
|
|
39
|
+
// both return an empty list), so the caller never leaks another account's data.
|
|
40
|
+
app.get('/api/repos/:id/claude-reviews', async (req) => {
|
|
41
|
+
const { id } = req.params;
|
|
42
|
+
return listClaudeReviewsByRepo(Number.parseInt(id, 10), accountIdOf(req));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=activity.js.map
|
|
@@ -7,6 +7,7 @@ import { hasUserAnthropicKey, setUserAnthropicKey, } from '../../review/local-se
|
|
|
7
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
|
+
import { reviewEvents } from '../../review/events.js';
|
|
10
11
|
const MODELS = ['claude-opus-4-8', 'claude-sonnet-4-6', 'claude-haiku-4-5'];
|
|
11
12
|
const VERDICTS = ['COMMENT', 'REQUEST_CHANGES', 'APPROVE'];
|
|
12
13
|
const REVIEW_MODES = ['auto', 'diff_only', 'worktree'];
|
|
@@ -158,6 +159,13 @@ export async function claudeReviewRoutes(app) {
|
|
|
158
159
|
: 'The review queue is full; try again once some finish.',
|
|
159
160
|
};
|
|
160
161
|
}
|
|
162
|
+
reviewEvents.emit({
|
|
163
|
+
type: 'review.requested',
|
|
164
|
+
accountId: accountIdOf(req),
|
|
165
|
+
prId: id,
|
|
166
|
+
model,
|
|
167
|
+
requestedMode: mode ?? 'auto',
|
|
168
|
+
});
|
|
161
169
|
reply.status(202);
|
|
162
170
|
return { reviewId: result.reviewId, status: 'queued' };
|
|
163
171
|
});
|
|
@@ -218,6 +226,12 @@ export async function claudeReviewRoutes(app) {
|
|
|
218
226
|
reply.status(404);
|
|
219
227
|
return { error: 'NotFound', message: `Review ${reviewId} not found` };
|
|
220
228
|
}
|
|
229
|
+
reviewEvents.emit({
|
|
230
|
+
type: 'review.draftUpdated',
|
|
231
|
+
accountId: accountIdOf(req),
|
|
232
|
+
reviewId,
|
|
233
|
+
change: { userBody: body.userBody, userVerdict: body.userVerdict },
|
|
234
|
+
});
|
|
221
235
|
return { status: 'ok' };
|
|
222
236
|
});
|
|
223
237
|
// Tick a finding for inline posting and/or save the user's reworded body.
|
|
@@ -231,6 +245,12 @@ export async function claudeReviewRoutes(app) {
|
|
|
231
245
|
reply.status(404);
|
|
232
246
|
return { error: 'NotFound', message: `Finding ${findingId} not found` };
|
|
233
247
|
}
|
|
248
|
+
reviewEvents.emit({
|
|
249
|
+
type: 'finding.updated',
|
|
250
|
+
accountId: accountIdOf(req),
|
|
251
|
+
findingId,
|
|
252
|
+
change: { included: body.included, editedBody: body.editedBody },
|
|
253
|
+
});
|
|
234
254
|
return { status: 'ok' };
|
|
235
255
|
});
|
|
236
256
|
// Post a single finding as a standalone comment (no review submitted). The
|
|
@@ -243,7 +263,8 @@ export async function claudeReviewRoutes(app) {
|
|
|
243
263
|
if (!config.claudeReviewEnabled)
|
|
244
264
|
return featureOff(reply);
|
|
245
265
|
const { findingId } = req.params;
|
|
246
|
-
const
|
|
266
|
+
const accountId = accountIdOf(req);
|
|
267
|
+
const ctx = await getFindingPostContext(findingId, accountId);
|
|
247
268
|
if (!ctx) {
|
|
248
269
|
reply.status(404);
|
|
249
270
|
return { error: 'NotFound', message: `Finding ${findingId} not found` };
|
|
@@ -276,6 +297,12 @@ export async function claudeReviewRoutes(app) {
|
|
|
276
297
|
}),
|
|
277
298
|
});
|
|
278
299
|
await markFindingPosted(findingId, commentId, 'inline');
|
|
300
|
+
reviewEvents.emit({
|
|
301
|
+
type: 'finding.posted',
|
|
302
|
+
accountId,
|
|
303
|
+
findingId,
|
|
304
|
+
postedCommentKind: 'inline',
|
|
305
|
+
});
|
|
279
306
|
const result = { githubCommentId: commentId, postedAt };
|
|
280
307
|
return result;
|
|
281
308
|
}
|
|
@@ -295,6 +322,12 @@ export async function claudeReviewRoutes(app) {
|
|
|
295
322
|
body: findingCommentBody({ body: f.body, editedBody: f.editedBody, suggestion: f.suggestion }, { fallbackNote: true }),
|
|
296
323
|
});
|
|
297
324
|
await markFindingPosted(findingId, commentId, 'inline');
|
|
325
|
+
reviewEvents.emit({
|
|
326
|
+
type: 'finding.posted',
|
|
327
|
+
accountId,
|
|
328
|
+
findingId,
|
|
329
|
+
postedCommentKind: 'inline',
|
|
330
|
+
});
|
|
298
331
|
const result = { githubCommentId: commentId, postedAt };
|
|
299
332
|
return result;
|
|
300
333
|
}
|
|
@@ -312,6 +345,12 @@ export async function claudeReviewRoutes(app) {
|
|
|
312
345
|
}),
|
|
313
346
|
});
|
|
314
347
|
await markFindingPosted(findingId, commentId, 'pr_comment');
|
|
348
|
+
reviewEvents.emit({
|
|
349
|
+
type: 'finding.posted',
|
|
350
|
+
accountId,
|
|
351
|
+
findingId,
|
|
352
|
+
postedCommentKind: 'pr_comment',
|
|
353
|
+
});
|
|
315
354
|
const result = { githubCommentId: commentId, postedAt };
|
|
316
355
|
return result;
|
|
317
356
|
}
|
|
@@ -398,6 +437,14 @@ export async function claudeReviewRoutes(app) {
|
|
|
398
437
|
}
|
|
399
438
|
}
|
|
400
439
|
await markReviewPosted(reviewId, ghReviewId, built.inlineFindingIds, prCommentResults);
|
|
440
|
+
reviewEvents.emit({
|
|
441
|
+
type: 'review.posted',
|
|
442
|
+
accountId,
|
|
443
|
+
reviewId,
|
|
444
|
+
userVerdict,
|
|
445
|
+
inlineFindingIds: built.inlineFindingIds,
|
|
446
|
+
prCommentFindingIds: prCommentResults.map((r) => r.findingId),
|
|
447
|
+
});
|
|
401
448
|
const result = {
|
|
402
449
|
postedReviewId: ghReviewId,
|
|
403
450
|
postedAt: new Date().toISOString(),
|
package/dist/api/routes/feed.js
CHANGED
|
@@ -4,6 +4,8 @@ import { accountIdOf } from '../plugins/auth.js';
|
|
|
4
4
|
// has Watched, newest first, commit pushes excluded. The frontend mirrors these into an
|
|
5
5
|
// append-only IndexedDB store (see lib/feedStore.ts). Account-scoped.
|
|
6
6
|
export async function feedRoutes(app) {
|
|
7
|
-
|
|
7
|
+
// getFeed's default is now all-repos; preserve this legacy mirror's watched-repo-only
|
|
8
|
+
// semantics explicitly (the consolidated Activity Feed supersedes this endpoint).
|
|
9
|
+
app.get('/api/feed', async (req) => getFeed(accountIdOf(req), { daysBefore: 14, watchedOnly: true }));
|
|
8
10
|
}
|
|
9
11
|
//# sourceMappingURL=feed.js.map
|
package/dist/api/routes/me.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { config } from '../../config.js';
|
|
2
2
|
import { accountToLocalUser } from '../../auth/account.js';
|
|
3
3
|
import { accountIdOf } from '../plugins/auth.js';
|
|
4
|
+
import { getProCapabilities } from '../../pro/contract.js';
|
|
4
5
|
import { dismissMyTurn, getCompletedDismissals, getMyTurn, undismissMyTurn, } from '../../db/queries.js';
|
|
5
6
|
const dismissSchema = {
|
|
6
7
|
body: {
|
|
@@ -32,6 +33,7 @@ export async function meRoutes(app) {
|
|
|
32
33
|
},
|
|
33
34
|
claudeReviewEnabled: config.claudeReviewEnabled,
|
|
34
35
|
deploymentMode: config.deploymentMode,
|
|
36
|
+
pro: getProCapabilities(),
|
|
35
37
|
};
|
|
36
38
|
});
|
|
37
39
|
app.get('/api/my-turn', async (req) => getMyTurn(accountIdOf(req)));
|
package/dist/api/routes/repos.js
CHANGED
|
@@ -230,7 +230,7 @@ export async function repoRoutes(app) {
|
|
|
230
230
|
reply.status(201);
|
|
231
231
|
return getRepo(repoId, accountId);
|
|
232
232
|
});
|
|
233
|
-
// Toggle "Watch for inbox" on a repo.
|
|
233
|
+
// Toggle "Watch for inbox" on a repo. Activity-only: it does not affect timeline
|
|
234
234
|
// visibility or syncing. Ownership-scoped → 404 for a repo this account doesn't own.
|
|
235
235
|
app.patch('/api/repos/:id', { schema: watchSchema }, async (req, reply) => {
|
|
236
236
|
const { id } = req.params;
|
package/dist/app.js
CHANGED
|
@@ -18,6 +18,7 @@ import { openPrsRoutes } from './api/routes/open-prs.js';
|
|
|
18
18
|
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
|
+
import { activityRoutes } from './api/routes/activity.js';
|
|
21
22
|
import { claudeReviewRoutes } from './api/routes/claude-review.js';
|
|
22
23
|
export async function buildApp() {
|
|
23
24
|
const app = Fastify({
|
|
@@ -130,6 +131,7 @@ export async function buildApp() {
|
|
|
130
131
|
await app.register(feedRoutes);
|
|
131
132
|
await app.register(mergersRoutes);
|
|
132
133
|
await app.register(insightsRoutes);
|
|
134
|
+
await app.register(activityRoutes);
|
|
133
135
|
// Claude Review is local-only + opt-in. Only register its routes when enabled,
|
|
134
136
|
// so the clone-manager / gh-CLI dependency is unreachable in cloud mode.
|
|
135
137
|
if (config.claudeReviewEnabled)
|
package/dist/config.js
CHANGED
|
@@ -118,6 +118,19 @@ export const config = {
|
|
|
118
118
|
// HSTS_MAX_AGE=0 as a kill switch. `preload` is intentionally NOT sent — it is
|
|
119
119
|
// hard to undo; opt in manually once the domain is proven.
|
|
120
120
|
hstsMaxAge: intFromEnv('HSTS_MAX_AGE', 31536000),
|
|
121
|
+
// Pro plugin master gate. Pro is local-only for now; bind.ts skips the dynamic
|
|
122
|
+
// import entirely when false.
|
|
123
|
+
proEnabled: !isCloud,
|
|
124
|
+
// Per-repo digest (Pro, Workstream 2) config — consumed by @pierre/pro, kept in
|
|
125
|
+
// core so the model id / budgets live in one place and aren't hardcoded at call
|
|
126
|
+
// sites. Inert until the plugin is present AND digestEnabled.
|
|
127
|
+
pro: {
|
|
128
|
+
digestModel: process.env.PRO_DIGEST_MODEL ?? 'claude-haiku-4-5',
|
|
129
|
+
digestEnabled: process.env.PRO_DIGEST_ENABLED === 'true',
|
|
130
|
+
digestMaxUsdPerRefresh: floatFromEnv('PRO_DIGEST_MAX_USD', 0.5),
|
|
131
|
+
digestMaxReposPerRefresh: intFromEnv('PRO_DIGEST_MAX_REPOS', 30),
|
|
132
|
+
digestMinIntervalSec: intFromEnv('PRO_DIGEST_MIN_INTERVAL_SEC', 60),
|
|
133
|
+
},
|
|
121
134
|
// ---- Claude Review (agentic PR review; opt-in, LOCAL-ONLY) ----
|
|
122
135
|
// OFF by default: the feature spends real money / Agent-SDK credits per run.
|
|
123
136
|
// Enable with ENABLE_CLAUDE_REVIEW=true. FORCE-DISABLED in cloud mode (it
|