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.
@@ -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 ctx = await getFindingPostContext(findingId, accountIdOf(req));
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(),
@@ -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
- app.get('/api/feed', async (req) => getFeed(accountIdOf(req), 14));
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
@@ -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)));
@@ -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. Inbox-only: it does not affect timeline
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