pierre-review 0.1.46 → 0.1.47
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/claude-review.js +58 -2
- package/dist/config.js +4 -3
- package/dist/review/agent.js +5 -4
- package/dist/review/auth.js +69 -19
- package/dist/review/llm.js +21 -8
- package/dist/review/local-settings.js +3 -28
- package/dist/review/review-manager.js +63 -2
- package/package.json +1 -1
- package/public/assets/{index-TYZ3EpC2.js → index-BpyDHxQS.js} +116 -111
- package/public/assets/index-D0f_s30R.css +10 -0
- package/public/index.html +2 -2
- package/public/assets/index-CYgBb8KO.css +0 -10
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { config } from '../../config.js';
|
|
2
2
|
import { getClaudeReviewById, getClaudeReviewContext, getFindingPostContext, getLatestClaudeReview, listAllClaudeReviews, listClaudeReviewHistory, } from '../../db/queries.js';
|
|
3
3
|
import { markFindingPosted, markReviewPosted, updateFinding, updateReviewDraft, } from '../../review/persist.js';
|
|
4
|
-
import { getReviewStatus, listActiveReviews, requestReviewCancel, startReview, } from '../../review/review-manager.js';
|
|
4
|
+
import { getReviewStatus, listActiveReviews, requestReviewCancel, startReview, subscribeReviewStream, } from '../../review/review-manager.js';
|
|
5
5
|
import { detectClaudeAuth } from '../../review/auth.js';
|
|
6
6
|
import { hasUserAnthropicKey, setUserAnthropicKey, } from '../../review/local-settings.js';
|
|
7
7
|
import { buildAnchorIndex, buildReview, fallbackAnchor, fetchCurrentHeadSha, fetchPrDiff, findingCommentBody, prLevelFindingBody, stripNoiseFromDiff, submitGithubComment, submitGithubIssueComment, submitGithubReview, } from '../../review/post-review.js';
|
|
@@ -169,7 +169,7 @@ export async function claudeReviewRoutes(app) {
|
|
|
169
169
|
reply.status(202);
|
|
170
170
|
return { reviewId: result.reviewId, status: 'queued' };
|
|
171
171
|
});
|
|
172
|
-
// Live progress poll target.
|
|
172
|
+
// Live progress poll target (kept as a fallback + for the initial snapshot).
|
|
173
173
|
app.get('/api/prs/:id/claude-review/status', { schema: idParam }, async (req) => {
|
|
174
174
|
const { id } = req.params;
|
|
175
175
|
if (!config.claudeReviewEnabled) {
|
|
@@ -177,6 +177,62 @@ export async function claudeReviewRoutes(app) {
|
|
|
177
177
|
}
|
|
178
178
|
return await getReviewStatus(id);
|
|
179
179
|
});
|
|
180
|
+
// Live progress STREAM (SSE) — pushes each phase/activity/usage change in real
|
|
181
|
+
// time and one terminal `done`, so the UI progress bar tracks the run without
|
|
182
|
+
// polling. Local-only (Claude Review is gated off in cloud). We `hijack()` the
|
|
183
|
+
// reply and own the raw socket for the connection's lifetime.
|
|
184
|
+
app.get('/api/prs/:id/claude-review/stream', { schema: idParam }, async (req, reply) => {
|
|
185
|
+
const { id } = req.params;
|
|
186
|
+
reply.hijack();
|
|
187
|
+
const raw = reply.raw;
|
|
188
|
+
raw.writeHead(200, {
|
|
189
|
+
'Content-Type': 'text/event-stream',
|
|
190
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
191
|
+
Connection: 'keep-alive',
|
|
192
|
+
// Disable proxy buffering (nginx) so frames flush immediately.
|
|
193
|
+
'X-Accel-Buffering': 'no',
|
|
194
|
+
});
|
|
195
|
+
const send = (data) => {
|
|
196
|
+
if (!raw.writableEnded)
|
|
197
|
+
raw.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
198
|
+
};
|
|
199
|
+
if (!config.claudeReviewEnabled) {
|
|
200
|
+
send({ type: 'done', status: 'idle', reviewId: null });
|
|
201
|
+
raw.end();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
let closed = false;
|
|
205
|
+
let unsubscribe = null;
|
|
206
|
+
const heartbeat = setInterval(() => {
|
|
207
|
+
if (!raw.writableEnded)
|
|
208
|
+
raw.write(': hb\n\n');
|
|
209
|
+
}, 15000);
|
|
210
|
+
const cleanup = () => {
|
|
211
|
+
if (closed)
|
|
212
|
+
return;
|
|
213
|
+
closed = true;
|
|
214
|
+
clearInterval(heartbeat);
|
|
215
|
+
unsubscribe?.();
|
|
216
|
+
if (!raw.writableEnded)
|
|
217
|
+
raw.end();
|
|
218
|
+
};
|
|
219
|
+
// Subscribe BEFORE reading the snapshot so a terminal `done` emitted during
|
|
220
|
+
// setup can't slip through the gap (it'd be delivered to this callback). If
|
|
221
|
+
// the run already finished, the snapshot check below sends `done` itself.
|
|
222
|
+
unsubscribe = subscribeReviewStream(id, (e) => {
|
|
223
|
+
send(e);
|
|
224
|
+
if (e.type === 'done')
|
|
225
|
+
cleanup();
|
|
226
|
+
});
|
|
227
|
+
req.raw.on('close', cleanup);
|
|
228
|
+
// Initial snapshot so a client that connects mid-run gets current state at once.
|
|
229
|
+
const snap = await getReviewStatus(id);
|
|
230
|
+
send({ type: 'snapshot', ...snap });
|
|
231
|
+
if (snap.status !== 'running' && snap.status !== 'queued') {
|
|
232
|
+
send({ type: 'done', status: snap.status, reviewId: snap.reviewId });
|
|
233
|
+
cleanup();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
180
236
|
app.post('/api/prs/:id/claude-review/cancel', { schema: idParam }, async (req, reply) => {
|
|
181
237
|
const { id } = req.params;
|
|
182
238
|
if (!config.claudeReviewEnabled)
|
package/dist/config.js
CHANGED
|
@@ -183,9 +183,10 @@ export const config = {
|
|
|
183
183
|
reviewDiffOnlyEffort: effortFromEnv('REVIEW_DIFF_ONLY_EFFORT', 'low'),
|
|
184
184
|
// At most one review per PR; this caps concurrent reviews across all PRs. Default
|
|
185
185
|
// 4 so the user can bulk-review (extras queue, see review-manager). Raising this
|
|
186
|
-
// also DISABLES the
|
|
187
|
-
// safe at concurrency 1 — see
|
|
188
|
-
// is used instead. Set REVIEW_CONCURRENCY=1 to restore
|
|
186
|
+
// also DISABLES the per-run env auth adjustment (which mutates process.env and is
|
|
187
|
+
// only safe at concurrency 1 — see auth.applyClaudeReviewAuth); the raw ambient
|
|
188
|
+
// env is used instead. Set REVIEW_CONCURRENCY=1 to restore prefer-ambient + the
|
|
189
|
+
// API-key fallback.
|
|
189
190
|
reviewConcurrency: intFromEnv('REVIEW_CONCURRENCY', 4),
|
|
190
191
|
// Hard ceiling on QUEUED (not-yet-started) reviews, a runaway guard for bulk
|
|
191
192
|
// triggering; further starts return 'busy' until the queue drains.
|
package/dist/review/agent.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { createSdkMcpServer, query, tool, } from '@anthropic-ai/claude-agent-sdk';
|
|
5
5
|
import { config } from '../config.js';
|
|
6
6
|
import { submitReviewShape } from './schema.js';
|
|
7
|
-
import {
|
|
7
|
+
import { applyClaudeReviewAuth } from './auth.js';
|
|
8
8
|
import { cleanupCloneCache, prepWorktree, removeWorktreeLocked, } from './clone-manager.js';
|
|
9
9
|
import { buildUserPrompt, isNoiseFile, systemPromptForMode, } from './prompt.js';
|
|
10
10
|
import { buildAnchorIndex, capDiff, extractHunk, fetchPrDiff, isFindingAnchored, splitDiffByFile, stripNoiseFromDiff, } from './post-review.js';
|
|
@@ -169,9 +169,10 @@ export async function runReview(args) {
|
|
|
169
169
|
omittedFiles = capped.omittedFiles;
|
|
170
170
|
diffCapped = capped.capped;
|
|
171
171
|
}
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
|
|
172
|
+
// Establish this run's auth: prefer the ambient Claude subscription, falling
|
|
173
|
+
// back to an API key only when no ambient session exists (restored in finally).
|
|
174
|
+
// Safe only at reviewConcurrency === 1.
|
|
175
|
+
restoreEnv = applyClaudeReviewAuth();
|
|
175
176
|
// ---- run the agent ----
|
|
176
177
|
let captured = null;
|
|
177
178
|
const server = createSdkMcpServer({
|
package/dist/review/auth.js
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { config } from '../config.js';
|
|
4
5
|
import { getUserAnthropicKey } from './local-settings.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (apiKey && apiKey.length > 0) {
|
|
13
|
-
return { status: 'ok', method: 'api_key' };
|
|
14
|
-
}
|
|
15
|
-
const oauthToken = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
16
|
-
if (oauthToken && oauthToken.length > 0) {
|
|
17
|
-
return { status: 'ok', method: 'oauth_token' };
|
|
18
|
-
}
|
|
19
|
-
// Ambient logged-in Claude Code session: any of these on disk is good enough.
|
|
20
|
-
// The last two are weak signals (config dir present, but maybe not signed in) —
|
|
21
|
-
// a wrong guess just means the run fails with a clear SDK auth error.
|
|
6
|
+
// Is an ambient Claude SUBSCRIPTION available (OAuth token or a logged-in Claude
|
|
7
|
+
// Code session on disk)? Deliberately excludes API keys — this answers "can Claude
|
|
8
|
+
// Review run on the subscription (no per-token billing)?", which drives the
|
|
9
|
+
// prefer-ambient policy below.
|
|
10
|
+
export function hasAmbientClaudeAuth() {
|
|
11
|
+
if (process.env.CLAUDE_CODE_OAUTH_TOKEN)
|
|
12
|
+
return true;
|
|
22
13
|
const home = homedir();
|
|
23
14
|
const ambientCandidates = [
|
|
24
15
|
join(home, '.claude', '.credentials.json'),
|
|
@@ -26,12 +17,71 @@ export function detectClaudeAuth() {
|
|
|
26
17
|
join(home, '.claude.json'),
|
|
27
18
|
join(home, '.claude'),
|
|
28
19
|
];
|
|
29
|
-
|
|
20
|
+
return ambientCandidates.some((path) => existsSync(path));
|
|
21
|
+
}
|
|
22
|
+
export function detectClaudeAuth() {
|
|
23
|
+
// Claude Review PREFERS the ambient subscription (see applyClaudeReviewAuth), so
|
|
24
|
+
// report it first when present — even if an API key also exists.
|
|
25
|
+
if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
26
|
+
return { status: 'ok', method: 'oauth_token' };
|
|
27
|
+
}
|
|
28
|
+
if (hasAmbientClaudeAuth()) {
|
|
30
29
|
return { status: 'ok', method: 'ambient' };
|
|
31
30
|
}
|
|
31
|
+
// No ambient → fall back to an API key (the user's local key, then the env key).
|
|
32
|
+
if (getUserAnthropicKey() || process.env.ANTHROPIC_API_KEY) {
|
|
33
|
+
return { status: 'ok', method: 'api_key' };
|
|
34
|
+
}
|
|
32
35
|
return {
|
|
33
36
|
status: 'none',
|
|
34
|
-
message: 'No Claude authentication found.
|
|
37
|
+
message: 'No Claude authentication found. Run `claude` once to sign in to an eligible Claude plan (Pro/Max/Team/Enterprise), or set an Anthropic API key, then restart pierre-review.',
|
|
35
38
|
};
|
|
36
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Establish the auth for ONE Claude Review run by mutating process.env, returning a
|
|
42
|
+
* restore fn (always call it in a finally). Policy — Claude Review prefers the
|
|
43
|
+
* ambient subscription so the user's plan/usage credits cover it:
|
|
44
|
+
*
|
|
45
|
+
* • ambient available → STRIP any ANTHROPIC_API_KEY for the run so the Agent SDK
|
|
46
|
+
* authenticates via the subscription/OAuth (an API key would otherwise win and
|
|
47
|
+
* silently meter the run).
|
|
48
|
+
* • no ambient → fall back to an API key (the user's local key, else the env key).
|
|
49
|
+
*
|
|
50
|
+
* process.env is process-global, so this only mutates at reviewConcurrency === 1
|
|
51
|
+
* (the env-race guard); above that it no-ops and the raw ambient env is used. The
|
|
52
|
+
* Pro summary is unaffected either way — it passes its own key explicitly to the
|
|
53
|
+
* llm seam and never reads this env.
|
|
54
|
+
*/
|
|
55
|
+
export function applyClaudeReviewAuth() {
|
|
56
|
+
if (config.reviewConcurrency !== 1)
|
|
57
|
+
return () => { };
|
|
58
|
+
const prevApiKey = process.env.ANTHROPIC_API_KEY;
|
|
59
|
+
const prevOauth = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
60
|
+
const restore = () => {
|
|
61
|
+
if (prevApiKey === undefined)
|
|
62
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
63
|
+
else
|
|
64
|
+
process.env.ANTHROPIC_API_KEY = prevApiKey;
|
|
65
|
+
if (prevOauth === undefined)
|
|
66
|
+
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
67
|
+
else
|
|
68
|
+
process.env.CLAUDE_CODE_OAUTH_TOKEN = prevOauth;
|
|
69
|
+
};
|
|
70
|
+
if (hasAmbientClaudeAuth()) {
|
|
71
|
+
// Prefer the subscription: hide any API key so the Agent SDK uses ambient auth.
|
|
72
|
+
if (process.env.ANTHROPIC_API_KEY === undefined)
|
|
73
|
+
return () => { };
|
|
74
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
75
|
+
return restore;
|
|
76
|
+
}
|
|
77
|
+
// No ambient → use an API key. A user-supplied local key wins over the env key.
|
|
78
|
+
const key = getUserAnthropicKey();
|
|
79
|
+
if (key) {
|
|
80
|
+
process.env.ANTHROPIC_API_KEY = key;
|
|
81
|
+
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
82
|
+
return restore;
|
|
83
|
+
}
|
|
84
|
+
// Nothing to change — leave any existing ANTHROPIC_API_KEY in place.
|
|
85
|
+
return () => { };
|
|
86
|
+
}
|
|
37
87
|
//# sourceMappingURL=auth.js.map
|
package/dist/review/llm.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
// The cheap-tier ("Haiku") completion seam. Core-owned so the optional @pierre/pro
|
|
2
|
+
// plugin can do a single-shot LLM call (e.g. the per-repo digest) through ctx.llm
|
|
3
|
+
// WITHOUT adding its own Anthropic dependency. Non-agentic: one completion, no
|
|
4
|
+
// tools, no thinking.
|
|
5
|
+
//
|
|
6
|
+
// AUTH is CALLER-OWNED (this is deliberate — it keeps each feature's auth discrete):
|
|
7
|
+
// • `opts.apiKey` given → the raw, metered `@anthropic-ai/sdk` with THAT key. The
|
|
8
|
+
// key is passed EXPLICITLY and this seam never reads/writes process.env, so a
|
|
9
|
+
// summary key can never leak into (or force metering on) Claude Review, which
|
|
10
|
+
// resolves auth separately and prefers the ambient subscription.
|
|
11
|
+
// • no `apiKey` → the Claude Agent SDK's `query()` (the SAME runtime Claude Review
|
|
12
|
+
// uses), which resolves a CLAUDE_CODE_OAUTH_TOKEN / ambient logged-in session.
|
|
13
|
+
// So the Pro summary passes its own dedicated key (fast, metered) while an OSS/dev
|
|
14
|
+
// caller with no key still works via the ambient session.
|
|
15
|
+
//
|
|
16
|
+
// Both SDK imports are LAZY (inside the function) so merely importing this module
|
|
17
|
+
// needs nothing loaded — only an actual caller pays. In OSS mode nothing calls it.
|
|
2
18
|
const DEFAULT_MODEL = 'claude-haiku-4-5';
|
|
3
19
|
export async function cheapComplete(opts) {
|
|
4
20
|
const model = opts.model ?? DEFAULT_MODEL;
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return rawComplete(apiKey, model, opts);
|
|
10
|
-
// No explicit key → use the Agent SDK, which resolves a CLAUDE_CODE_OAUTH_TOKEN
|
|
11
|
-
// or an ambient logged-in Claude session exactly as Claude Review does.
|
|
21
|
+
// An explicit key → the cheap, metered, exact-cost raw path. No key → the ambient
|
|
22
|
+
// session via the Agent SDK. (No implicit env / Claude-Review-key fallback here.)
|
|
23
|
+
if (opts.apiKey)
|
|
24
|
+
return rawComplete(opts.apiKey, model, opts);
|
|
12
25
|
return agentComplete(model, opts);
|
|
13
26
|
}
|
|
14
27
|
// ---- raw @anthropic-ai/sdk path (explicit API key) ----------------------------
|
|
@@ -40,32 +40,7 @@ export function setUserAnthropicKey(key) {
|
|
|
40
40
|
delete settings.anthropicApiKey;
|
|
41
41
|
write(settings);
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
*
|
|
47
|
-
* process.env is process-global, so this is ONLY safe when at most one review
|
|
48
|
-
* runs at a time — gated on reviewConcurrency === 1. With concurrency > 1 the
|
|
49
|
-
* override is skipped (the ambient auth is used) to avoid a cross-run env race.
|
|
50
|
-
* Always call the returned restore fn in a finally.
|
|
51
|
-
*/
|
|
52
|
-
export function applyUserAnthropicKey() {
|
|
53
|
-
const key = getUserAnthropicKey();
|
|
54
|
-
if (!key || config.reviewConcurrency !== 1)
|
|
55
|
-
return () => { };
|
|
56
|
-
const prevApiKey = process.env.ANTHROPIC_API_KEY;
|
|
57
|
-
const prevOauth = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
58
|
-
process.env.ANTHROPIC_API_KEY = key;
|
|
59
|
-
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
60
|
-
return () => {
|
|
61
|
-
if (prevApiKey === undefined)
|
|
62
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
63
|
-
else
|
|
64
|
-
process.env.ANTHROPIC_API_KEY = prevApiKey;
|
|
65
|
-
if (prevOauth === undefined)
|
|
66
|
-
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
67
|
-
else
|
|
68
|
-
process.env.CLAUDE_CODE_OAUTH_TOKEN = prevOauth;
|
|
69
|
-
};
|
|
70
|
-
}
|
|
43
|
+
// NOTE: the per-run env override for Claude Review now lives in review/auth.ts as
|
|
44
|
+
// `applyClaudeReviewAuth` (it implements the prefer-ambient policy and needs the
|
|
45
|
+
// ambient-session probe). This module stays a pure key store.
|
|
71
46
|
//# sourceMappingURL=local-settings.js.map
|
|
@@ -13,6 +13,43 @@ const claimed = new Set(); // prIds either running OR pending — the sync guard
|
|
|
13
13
|
const reviewIdByPr = new Map();
|
|
14
14
|
const progressByReview = new Map();
|
|
15
15
|
const controllers = new Map();
|
|
16
|
+
// SSE fan-out (keyed by prId): the live-progress stream endpoint subscribes here,
|
|
17
|
+
// so each phase/activity/usage change is PUSHED to connected clients in real time
|
|
18
|
+
// instead of being polled. Empty in the common case (no open stream). See
|
|
19
|
+
// subscribeReviewStream + GET /api/prs/:id/claude-review/stream.
|
|
20
|
+
const streamSubs = new Map();
|
|
21
|
+
function emitReviewStream(prId, e) {
|
|
22
|
+
const subs = streamSubs.get(prId);
|
|
23
|
+
if (!subs)
|
|
24
|
+
return;
|
|
25
|
+
for (const cb of subs) {
|
|
26
|
+
try {
|
|
27
|
+
cb(e);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
/* a broken subscriber must never break the run */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Subscribe to a PR's live review stream. Returns an unsubscribe fn. The manager
|
|
35
|
+
// emits 'progress' events across the queued→running lifecycle and one terminal
|
|
36
|
+
// 'done' (with the persisted final status) when the run settles.
|
|
37
|
+
export function subscribeReviewStream(prId, cb) {
|
|
38
|
+
let set = streamSubs.get(prId);
|
|
39
|
+
if (!set) {
|
|
40
|
+
set = new Set();
|
|
41
|
+
streamSubs.set(prId, set);
|
|
42
|
+
}
|
|
43
|
+
set.add(cb);
|
|
44
|
+
return () => {
|
|
45
|
+
const s = streamSubs.get(prId);
|
|
46
|
+
if (!s)
|
|
47
|
+
return;
|
|
48
|
+
s.delete(cb);
|
|
49
|
+
if (s.size === 0)
|
|
50
|
+
streamSubs.delete(prId);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
16
53
|
export async function startReview(prId, model, requestedMode, log) {
|
|
17
54
|
if (!config.claudeReviewEnabled)
|
|
18
55
|
return { ok: false, reason: 'disabled' };
|
|
@@ -78,7 +115,14 @@ function launch(item) {
|
|
|
78
115
|
controllers.set(reviewId, controller);
|
|
79
116
|
// The diff is fetched (and the mode decided) before any clone, so the first real
|
|
80
117
|
// phase is always 'fetching_diff' — not 'cloning' (which a diff-only run skips).
|
|
81
|
-
|
|
118
|
+
const initial = { phase: 'fetching_diff' };
|
|
119
|
+
progressByReview.set(reviewId, initial);
|
|
120
|
+
emitReviewStream(prId, {
|
|
121
|
+
type: 'progress',
|
|
122
|
+
status: 'running',
|
|
123
|
+
reviewId,
|
|
124
|
+
progress: initial,
|
|
125
|
+
});
|
|
82
126
|
void runReview({
|
|
83
127
|
reviewId,
|
|
84
128
|
prId,
|
|
@@ -94,7 +138,15 @@ function launch(item) {
|
|
|
94
138
|
requestedMode: item.requestedMode,
|
|
95
139
|
priorReviewContext: item.priorReviewContext,
|
|
96
140
|
abortController: controller,
|
|
97
|
-
onProgress: (p) =>
|
|
141
|
+
onProgress: (p) => {
|
|
142
|
+
progressByReview.set(reviewId, p);
|
|
143
|
+
emitReviewStream(prId, {
|
|
144
|
+
type: 'progress',
|
|
145
|
+
status: 'running',
|
|
146
|
+
reviewId,
|
|
147
|
+
progress: p,
|
|
148
|
+
});
|
|
149
|
+
},
|
|
98
150
|
})
|
|
99
151
|
.catch((err) => {
|
|
100
152
|
item.log.error(`claude review pr ${prId} failed: ${err instanceof Error ? err.message : err}`);
|
|
@@ -105,6 +157,15 @@ function launch(item) {
|
|
|
105
157
|
controllers.delete(reviewId);
|
|
106
158
|
progressByReview.delete(reviewId);
|
|
107
159
|
claimed.delete(prId);
|
|
160
|
+
// Push a terminal event to any open stream with the PERSISTED final status
|
|
161
|
+
// (succeeded / failed / cancelled), then start the next queued review.
|
|
162
|
+
void getLatestClaudeReview(prId, LOCAL_ACCOUNT_ID)
|
|
163
|
+
.then((latest) => emitReviewStream(prId, {
|
|
164
|
+
type: 'done',
|
|
165
|
+
status: latest?.status ?? 'failed',
|
|
166
|
+
reviewId,
|
|
167
|
+
}))
|
|
168
|
+
.catch(() => emitReviewStream(prId, { type: 'done', status: 'failed', reviewId }));
|
|
108
169
|
pump(); // a slot freed — start the next queued review
|
|
109
170
|
});
|
|
110
171
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pierre-review",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.47",
|
|
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",
|