diffprism 0.34.1 → 0.35.0

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.
@@ -1,119 +1,33 @@
1
1
  import {
2
- analyze,
3
- consumeReviewResult,
4
2
  createGitHubClient,
5
- detectWorktree,
3
+ ensureServer,
6
4
  fetchPullRequest,
7
5
  fetchPullRequestDiff,
8
- getCurrentBranch,
9
- getDiff,
10
6
  isServerAlive,
11
7
  normalizePr,
12
8
  parsePrRef,
13
- readReviewResult,
14
- readWatchFile,
15
9
  resolveGitHubToken,
16
- startReview,
17
- submitGitHubReview
18
- } from "./chunk-VASCXEMN.js";
10
+ submitGitHubReview,
11
+ submitReviewToServer
12
+ } from "./chunk-LUUR6LNP.js";
13
+ import {
14
+ getDiff
15
+ } from "./chunk-QGWYCEJN.js";
16
+ import {
17
+ analyze
18
+ } from "./chunk-DHCVZGHE.js";
19
+ import "./chunk-JSBRDJBE.js";
19
20
 
20
21
  // packages/mcp-server/src/index.ts
21
- import fs from "fs";
22
- import path from "path";
23
22
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
24
23
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
25
24
  import { z } from "zod";
26
25
  var lastGlobalSessionId = null;
27
26
  var lastGlobalServerInfo = null;
28
- async function reviewViaGlobalServer(serverInfo, diffRef, options) {
29
- const cwd = options.cwd ?? process.cwd();
30
- const { diffSet, rawDiff } = getDiff(diffRef, { cwd });
31
- const currentBranch = getCurrentBranch({ cwd });
32
- if (diffSet.files.length === 0) {
33
- return {
34
- decision: "approved",
35
- comments: [],
36
- summary: "No changes to review."
37
- };
38
- }
39
- const briefing = analyze(diffSet);
40
- const worktreeInfo = detectWorktree({ cwd });
41
- const payload = {
42
- reviewId: "",
43
- // Server assigns the real ID
44
- diffSet,
45
- rawDiff,
46
- briefing,
47
- metadata: {
48
- title: options.title,
49
- description: options.description,
50
- reasoning: options.reasoning,
51
- currentBranch,
52
- worktree: worktreeInfo.isWorktree ? {
53
- isWorktree: true,
54
- worktreePath: worktreeInfo.worktreePath,
55
- mainWorktreePath: worktreeInfo.mainWorktreePath
56
- } : void 0
57
- }
58
- };
59
- const createResponse = await fetch(
60
- `http://localhost:${serverInfo.httpPort}/api/reviews`,
61
- {
62
- method: "POST",
63
- headers: { "Content-Type": "application/json" },
64
- body: JSON.stringify({ payload, projectPath: cwd, diffRef })
65
- }
66
- );
67
- if (!createResponse.ok) {
68
- throw new Error(`Global server returned ${createResponse.status} on create`);
69
- }
70
- const { sessionId } = await createResponse.json();
71
- lastGlobalSessionId = sessionId;
72
- lastGlobalServerInfo = serverInfo;
73
- if (options.annotations?.length) {
74
- for (const ann of options.annotations) {
75
- await fetch(
76
- `http://localhost:${serverInfo.httpPort}/api/reviews/${sessionId}/annotations`,
77
- {
78
- method: "POST",
79
- headers: { "Content-Type": "application/json" },
80
- body: JSON.stringify({
81
- file: ann.file,
82
- line: ann.line,
83
- body: ann.body,
84
- type: ann.type,
85
- confidence: ann.confidence ?? 1,
86
- category: ann.category ?? "other",
87
- source: {
88
- agent: ann.source_agent ?? "unknown",
89
- tool: "open_review"
90
- }
91
- })
92
- }
93
- );
94
- }
95
- }
96
- const pollIntervalMs = 2e3;
97
- const maxWaitMs = 600 * 1e3;
98
- const start = Date.now();
99
- while (Date.now() - start < maxWaitMs) {
100
- const resultResponse = await fetch(
101
- `http://localhost:${serverInfo.httpPort}/api/reviews/${sessionId}/result`
102
- );
103
- if (resultResponse.ok) {
104
- const data = await resultResponse.json();
105
- if (data.result) {
106
- return data.result;
107
- }
108
- }
109
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
110
- }
111
- throw new Error("Review timed out waiting for submission.");
112
- }
113
27
  async function startMcpServer() {
114
28
  const server = new McpServer({
115
29
  name: "diffprism",
116
- version: true ? "0.34.1" : "0.0.0-dev"
30
+ version: true ? "0.35.0" : "0.0.0-dev"
117
31
  });
118
32
  server.tool(
119
33
  "open_review",
@@ -148,36 +62,21 @@ async function startMcpServer() {
148
62
  },
149
63
  async ({ diff_ref, title, description, reasoning, annotations }) => {
150
64
  try {
151
- const serverInfo = await isServerAlive();
152
- if (serverInfo) {
153
- const result2 = await reviewViaGlobalServer(serverInfo, diff_ref, {
65
+ const serverInfo = await ensureServer({ silent: true });
66
+ const { result, sessionId } = await submitReviewToServer(
67
+ serverInfo,
68
+ diff_ref,
69
+ {
154
70
  title,
155
71
  description,
156
72
  reasoning,
157
73
  cwd: process.cwd(),
158
- annotations
159
- });
160
- return {
161
- content: [
162
- {
163
- type: "text",
164
- text: JSON.stringify(result2, null, 2)
165
- }
166
- ]
167
- };
168
- }
169
- const isDev = fs.existsSync(
170
- path.join(process.cwd(), "packages", "ui", "src", "App.tsx")
74
+ annotations,
75
+ diffRef: diff_ref
76
+ }
171
77
  );
172
- const result = await startReview({
173
- diffRef: diff_ref,
174
- title,
175
- description,
176
- reasoning,
177
- cwd: process.cwd(),
178
- silent: true,
179
- dev: isDev
180
- });
78
+ lastGlobalSessionId = sessionId;
79
+ lastGlobalServerInfo = serverInfo;
181
80
  return {
182
81
  content: [
183
82
  {
@@ -202,7 +101,7 @@ async function startMcpServer() {
202
101
  );
203
102
  server.tool(
204
103
  "update_review_context",
205
- "Push reasoning/context to a running DiffPrism review session. Non-blocking \u2014 returns immediately. Use this when `diffprism watch` or `diffprism server` is running to update the review UI with agent reasoning without opening a new review.",
104
+ "Push reasoning/context to a running DiffPrism review session. Non-blocking \u2014 returns immediately. Updates the review UI with agent reasoning without opening a new review. Requires a prior `open_review` call in this session.",
206
105
  {
207
106
  reasoning: z.string().optional().describe("Agent reasoning about the current changes"),
208
107
  title: z.string().optional().describe("Updated title for the review"),
@@ -214,42 +113,19 @@ async function startMcpServer() {
214
113
  if (reasoning !== void 0) payload.reasoning = reasoning;
215
114
  if (title !== void 0) payload.title = title;
216
115
  if (description !== void 0) payload.description = description;
217
- if (lastGlobalSessionId && lastGlobalServerInfo) {
218
- const serverInfo = await isServerAlive();
219
- if (serverInfo) {
220
- const response2 = await fetch(
221
- `http://localhost:${serverInfo.httpPort}/api/reviews/${lastGlobalSessionId}/context`,
222
- {
223
- method: "POST",
224
- headers: { "Content-Type": "application/json" },
225
- body: JSON.stringify(payload)
226
- }
227
- );
228
- if (response2.ok) {
229
- return {
230
- content: [
231
- {
232
- type: "text",
233
- text: "Context updated in DiffPrism global server session."
234
- }
235
- ]
236
- };
237
- }
238
- }
239
- }
240
- const watchInfo = readWatchFile();
241
- if (!watchInfo) {
116
+ const serverInfo = lastGlobalServerInfo ?? await isServerAlive();
117
+ if (!serverInfo || !lastGlobalSessionId) {
242
118
  return {
243
119
  content: [
244
120
  {
245
121
  type: "text",
246
- text: "No DiffPrism session is running. Start one with `diffprism watch` or `diffprism server`."
122
+ text: "No DiffPrism session is running. Use `open_review` to start a review."
247
123
  }
248
124
  ]
249
125
  };
250
126
  }
251
127
  const response = await fetch(
252
- `http://localhost:${watchInfo.wsPort}/api/context`,
128
+ `http://localhost:${serverInfo.httpPort}/api/reviews/${lastGlobalSessionId}/context`,
253
129
  {
254
130
  method: "POST",
255
131
  headers: { "Content-Type": "application/json" },
@@ -257,13 +133,13 @@ async function startMcpServer() {
257
133
  }
258
134
  );
259
135
  if (!response.ok) {
260
- throw new Error(`Watch server returned ${response.status}`);
136
+ throw new Error(`Server returned ${response.status}`);
261
137
  }
262
138
  return {
263
139
  content: [
264
140
  {
265
141
  type: "text",
266
- text: "Context updated in DiffPrism watch session."
142
+ text: "Context updated in DiffPrism session."
267
143
  }
268
144
  ]
269
145
  };
@@ -283,7 +159,7 @@ async function startMcpServer() {
283
159
  );
284
160
  server.tool(
285
161
  "get_review_result",
286
- "Fetch the most recent review result from a DiffPrism session. Returns the reviewer's decision and comments if a review has been submitted, or a message indicating no pending result. The result is marked as consumed after retrieval so it won't be returned again. Use wait=true to block until a result is available (recommended after pushing context to a watch session).",
162
+ "Fetch the most recent review result from a DiffPrism session. Returns the reviewer's decision and comments if a review has been submitted, or a message indicating no pending result. Use wait=true to block until a result is available. Note: `open_review` already blocks and returns the result \u2014 this tool is only needed for advanced workflows where you want to check results separately.",
287
163
  {
288
164
  wait: z.boolean().optional().describe("If true, poll until a review result is available (blocks up to timeout)"),
289
165
  timeout: z.number().optional().describe("Max wait time in seconds when wait=true (default: 300, max: 600)")
@@ -292,80 +168,36 @@ async function startMcpServer() {
292
168
  try {
293
169
  const maxWaitMs = Math.min(timeout ?? 300, 600) * 1e3;
294
170
  const pollIntervalMs = 2e3;
295
- if (lastGlobalSessionId && lastGlobalServerInfo) {
296
- const serverInfo = await isServerAlive();
297
- if (serverInfo) {
298
- if (wait) {
299
- const start = Date.now();
300
- while (Date.now() - start < maxWaitMs) {
301
- const response2 = await fetch(
302
- `http://localhost:${serverInfo.httpPort}/api/reviews/${lastGlobalSessionId}/result`
303
- );
304
- if (response2.ok) {
305
- const data2 = await response2.json();
306
- if (data2.result) {
307
- return {
308
- content: [
309
- {
310
- type: "text",
311
- text: JSON.stringify(data2.result, null, 2)
312
- }
313
- ]
314
- };
315
- }
316
- }
317
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
171
+ const serverInfo = lastGlobalServerInfo ?? await isServerAlive();
172
+ if (!serverInfo || !lastGlobalSessionId) {
173
+ return {
174
+ content: [
175
+ {
176
+ type: "text",
177
+ text: "No DiffPrism session is running. Use `open_review` to start a review."
318
178
  }
319
- return {
320
- content: [
321
- {
322
- type: "text",
323
- text: "No review result received within timeout."
324
- }
325
- ]
326
- };
327
- }
328
- const response = await fetch(
179
+ ]
180
+ };
181
+ }
182
+ if (wait) {
183
+ const start = Date.now();
184
+ while (Date.now() - start < maxWaitMs) {
185
+ const response2 = await fetch(
329
186
  `http://localhost:${serverInfo.httpPort}/api/reviews/${lastGlobalSessionId}/result`
330
187
  );
331
- if (response.ok) {
332
- const data2 = await response.json();
333
- if (data2.result) {
188
+ if (response2.ok) {
189
+ const data = await response2.json();
190
+ if (data.result) {
334
191
  return {
335
192
  content: [
336
193
  {
337
194
  type: "text",
338
- text: JSON.stringify(data2.result, null, 2)
195
+ text: JSON.stringify(data.result, null, 2)
339
196
  }
340
197
  ]
341
198
  };
342
199
  }
343
200
  }
344
- return {
345
- content: [
346
- {
347
- type: "text",
348
- text: "No pending review result."
349
- }
350
- ]
351
- };
352
- }
353
- }
354
- if (wait) {
355
- const start = Date.now();
356
- while (Date.now() - start < maxWaitMs) {
357
- const data2 = readReviewResult();
358
- if (data2) {
359
- consumeReviewResult();
360
- return {
361
- content: [
362
- {
363
- type: "text",
364
- text: JSON.stringify(data2.result, null, 2)
365
- }
366
- ]
367
- };
368
- }
369
201
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
370
202
  }
371
203
  return {
@@ -377,23 +209,27 @@ async function startMcpServer() {
377
209
  ]
378
210
  };
379
211
  }
380
- const data = readReviewResult();
381
- if (!data) {
382
- return {
383
- content: [
384
- {
385
- type: "text",
386
- text: "No pending review result."
387
- }
388
- ]
389
- };
212
+ const response = await fetch(
213
+ `http://localhost:${serverInfo.httpPort}/api/reviews/${lastGlobalSessionId}/result`
214
+ );
215
+ if (response.ok) {
216
+ const data = await response.json();
217
+ if (data.result) {
218
+ return {
219
+ content: [
220
+ {
221
+ type: "text",
222
+ text: JSON.stringify(data.result, null, 2)
223
+ }
224
+ ]
225
+ };
226
+ }
390
227
  }
391
- consumeReviewResult();
392
228
  return {
393
229
  content: [
394
230
  {
395
231
  type: "text",
396
- text: JSON.stringify(data.result, null, 2)
232
+ text: "No pending review result."
397
233
  }
398
234
  ]
399
235
  };
@@ -814,59 +650,18 @@ async function startMcpServer() {
814
650
  };
815
651
  }
816
652
  const { payload } = normalizePr(rawDiff, prMetadata, { title, reasoning });
817
- const serverInfo = await isServerAlive();
818
- let result;
819
- if (serverInfo) {
820
- const createResponse = await fetch(
821
- `http://localhost:${serverInfo.httpPort}/api/reviews`,
822
- {
823
- method: "POST",
824
- headers: { "Content-Type": "application/json" },
825
- body: JSON.stringify({
826
- payload,
827
- projectPath: `github:${owner}/${repo}`,
828
- diffRef: `PR #${number}`
829
- })
830
- }
831
- );
832
- if (!createResponse.ok) {
833
- throw new Error(`Global server returned ${createResponse.status}`);
834
- }
835
- const { sessionId } = await createResponse.json();
836
- lastGlobalSessionId = sessionId;
837
- lastGlobalServerInfo = serverInfo;
838
- const pollIntervalMs = 2e3;
839
- const maxWaitMs = 600 * 1e3;
840
- const start = Date.now();
841
- while (Date.now() - start < maxWaitMs) {
842
- const resultResponse = await fetch(
843
- `http://localhost:${serverInfo.httpPort}/api/reviews/${sessionId}/result`
844
- );
845
- if (resultResponse.ok) {
846
- const data = await resultResponse.json();
847
- if (data.result) {
848
- result = data.result;
849
- break;
850
- }
851
- }
852
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
653
+ const serverInfo = await ensureServer({ silent: true });
654
+ const { result, sessionId } = await submitReviewToServer(
655
+ serverInfo,
656
+ `PR #${number}`,
657
+ {
658
+ injectedPayload: payload,
659
+ projectPath: `github:${owner}/${repo}`,
660
+ diffRef: `PR #${number}`
853
661
  }
854
- result ??= { decision: "dismissed", comments: [], summary: "Review timed out." };
855
- } else {
856
- const isDev = fs.existsSync(
857
- path.join(process.cwd(), "packages", "ui", "src", "App.tsx")
858
- );
859
- result = await startReview({
860
- diffRef: `PR #${number}`,
861
- title: payload.metadata.title,
862
- description: payload.metadata.description,
863
- reasoning: payload.metadata.reasoning,
864
- cwd: process.cwd(),
865
- silent: true,
866
- dev: isDev,
867
- injectedPayload: payload
868
- });
869
- }
662
+ );
663
+ lastGlobalSessionId = sessionId;
664
+ lastGlobalServerInfo = serverInfo;
870
665
  if ((post_to_github || result.postToGithub) && result.decision !== "dismissed") {
871
666
  const posted = await submitGitHubReview(client, owner, repo, number, result);
872
667
  if (posted) {
@@ -0,0 +1,19 @@
1
+ import {
2
+ detectWorktree,
3
+ getCurrentBranch,
4
+ getDiff,
5
+ getGitDiff,
6
+ listBranches,
7
+ listCommits,
8
+ parseDiff
9
+ } from "./chunk-QGWYCEJN.js";
10
+ import "./chunk-JSBRDJBE.js";
11
+ export {
12
+ detectWorktree,
13
+ getCurrentBranch,
14
+ getDiff,
15
+ getGitDiff,
16
+ listBranches,
17
+ listCommits,
18
+ parseDiff
19
+ };
@@ -0,0 +1,27 @@
1
+ import {
2
+ analyze,
3
+ categorizeFiles,
4
+ computeComplexityScores,
5
+ computeFileStats,
6
+ detectAffectedModules,
7
+ detectAffectedTests,
8
+ detectNewDependencies,
9
+ detectPatterns,
10
+ detectSecurityPatterns,
11
+ detectTestCoverageGaps,
12
+ generateSummary
13
+ } from "./chunk-DHCVZGHE.js";
14
+ import "./chunk-JSBRDJBE.js";
15
+ export {
16
+ analyze,
17
+ categorizeFiles,
18
+ computeComplexityScores,
19
+ computeFileStats,
20
+ detectAffectedModules,
21
+ detectAffectedTests,
22
+ detectNewDependencies,
23
+ detectPatterns,
24
+ detectSecurityPatterns,
25
+ detectTestCoverageGaps,
26
+ generateSummary
27
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffprism",
3
- "version": "0.34.1",
3
+ "version": "0.35.0",
4
4
  "type": "module",
5
5
  "description": "Local-first code review tool for agent-generated code changes",
6
6
  "bin": {