diffprism 0.15.0 → 0.17.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,9 +1,13 @@
1
1
  import {
2
+ analyze,
2
3
  consumeReviewResult,
4
+ getCurrentBranch,
5
+ getDiff,
6
+ isServerAlive,
3
7
  readReviewResult,
4
8
  readWatchFile,
5
9
  startReview
6
- } from "./chunk-NGHUHDAM.js";
10
+ } from "./chunk-4WN4FIY4.js";
7
11
 
8
12
  // packages/mcp-server/src/index.ts
9
13
  import fs from "fs";
@@ -11,10 +15,68 @@ import path from "path";
11
15
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
16
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
17
  import { z } from "zod";
18
+ var lastGlobalSessionId = null;
19
+ var lastGlobalServerInfo = null;
20
+ async function reviewViaGlobalServer(serverInfo, diffRef, options) {
21
+ const cwd = options.cwd ?? process.cwd();
22
+ const { diffSet, rawDiff } = getDiff(diffRef, { cwd });
23
+ const currentBranch = getCurrentBranch({ cwd });
24
+ if (diffSet.files.length === 0) {
25
+ return {
26
+ decision: "approved",
27
+ comments: [],
28
+ summary: "No changes to review."
29
+ };
30
+ }
31
+ const briefing = analyze(diffSet);
32
+ const payload = {
33
+ reviewId: "",
34
+ // Server assigns the real ID
35
+ diffSet,
36
+ rawDiff,
37
+ briefing,
38
+ metadata: {
39
+ title: options.title,
40
+ description: options.description,
41
+ reasoning: options.reasoning,
42
+ currentBranch
43
+ }
44
+ };
45
+ const createResponse = await fetch(
46
+ `http://localhost:${serverInfo.httpPort}/api/reviews`,
47
+ {
48
+ method: "POST",
49
+ headers: { "Content-Type": "application/json" },
50
+ body: JSON.stringify({ payload, projectPath: cwd })
51
+ }
52
+ );
53
+ if (!createResponse.ok) {
54
+ throw new Error(`Global server returned ${createResponse.status} on create`);
55
+ }
56
+ const { sessionId } = await createResponse.json();
57
+ lastGlobalSessionId = sessionId;
58
+ lastGlobalServerInfo = serverInfo;
59
+ const pollIntervalMs = 2e3;
60
+ const maxWaitMs = 600 * 1e3;
61
+ const start = Date.now();
62
+ while (Date.now() - start < maxWaitMs) {
63
+ const resultResponse = await fetch(
64
+ `http://localhost:${serverInfo.httpPort}/api/reviews/${sessionId}/result`
65
+ );
66
+ if (resultResponse.ok) {
67
+ const data = await resultResponse.json();
68
+ if (data.result) {
69
+ return data.result;
70
+ }
71
+ }
72
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
73
+ }
74
+ throw new Error("Review timed out waiting for submission.");
75
+ }
14
76
  async function startMcpServer() {
15
77
  const server = new McpServer({
16
78
  name: "diffprism",
17
- version: true ? "0.15.0" : "0.0.0-dev"
79
+ version: true ? "0.17.0" : "0.0.0-dev"
18
80
  });
19
81
  server.tool(
20
82
  "open_review",
@@ -29,6 +91,23 @@ async function startMcpServer() {
29
91
  },
30
92
  async ({ diff_ref, title, description, reasoning }) => {
31
93
  try {
94
+ const serverInfo = await isServerAlive();
95
+ if (serverInfo) {
96
+ const result2 = await reviewViaGlobalServer(serverInfo, diff_ref, {
97
+ title,
98
+ description,
99
+ reasoning,
100
+ cwd: process.cwd()
101
+ });
102
+ return {
103
+ content: [
104
+ {
105
+ type: "text",
106
+ text: JSON.stringify(result2, null, 2)
107
+ }
108
+ ]
109
+ };
110
+ }
32
111
  const isDev = fs.existsSync(
33
112
  path.join(process.cwd(), "packages", "ui", "src", "App.tsx")
34
113
  );
@@ -39,7 +118,6 @@ async function startMcpServer() {
39
118
  reasoning,
40
119
  cwd: process.cwd(),
41
120
  silent: true,
42
- // Suppress stdout — MCP uses stdio
43
121
  dev: isDev
44
122
  });
45
123
  return {
@@ -66,7 +144,7 @@ async function startMcpServer() {
66
144
  );
67
145
  server.tool(
68
146
  "update_review_context",
69
- "Push reasoning/context to a running DiffPrism watch session. Non-blocking \u2014 returns immediately. Use this when `diffprism watch` is running to update the review UI with agent reasoning without opening a new review.",
147
+ "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.",
70
148
  {
71
149
  reasoning: z.string().optional().describe("Agent reasoning about the current changes"),
72
150
  title: z.string().optional().describe("Updated title for the review"),
@@ -74,21 +152,44 @@ async function startMcpServer() {
74
152
  },
75
153
  async ({ reasoning, title, description }) => {
76
154
  try {
155
+ const payload = {};
156
+ if (reasoning !== void 0) payload.reasoning = reasoning;
157
+ if (title !== void 0) payload.title = title;
158
+ if (description !== void 0) payload.description = description;
159
+ if (lastGlobalSessionId && lastGlobalServerInfo) {
160
+ const serverInfo = await isServerAlive();
161
+ if (serverInfo) {
162
+ const response2 = await fetch(
163
+ `http://localhost:${serverInfo.httpPort}/api/reviews/${lastGlobalSessionId}/context`,
164
+ {
165
+ method: "POST",
166
+ headers: { "Content-Type": "application/json" },
167
+ body: JSON.stringify(payload)
168
+ }
169
+ );
170
+ if (response2.ok) {
171
+ return {
172
+ content: [
173
+ {
174
+ type: "text",
175
+ text: "Context updated in DiffPrism global server session."
176
+ }
177
+ ]
178
+ };
179
+ }
180
+ }
181
+ }
77
182
  const watchInfo = readWatchFile();
78
183
  if (!watchInfo) {
79
184
  return {
80
185
  content: [
81
186
  {
82
187
  type: "text",
83
- text: "No DiffPrism watch session is running. Start one with `diffprism watch`."
188
+ text: "No DiffPrism session is running. Start one with `diffprism watch` or `diffprism server`."
84
189
  }
85
190
  ]
86
191
  };
87
192
  }
88
- const payload = {};
89
- if (reasoning !== void 0) payload.reasoning = reasoning;
90
- if (title !== void 0) payload.title = title;
91
- if (description !== void 0) payload.description = description;
92
193
  const response = await fetch(
93
194
  `http://localhost:${watchInfo.wsPort}/api/context`,
94
195
  {
@@ -114,7 +215,7 @@ async function startMcpServer() {
114
215
  content: [
115
216
  {
116
217
  type: "text",
117
- text: `Error updating watch context: ${message}`
218
+ text: `Error updating review context: ${message}`
118
219
  }
119
220
  ],
120
221
  isError: true
@@ -124,16 +225,75 @@ async function startMcpServer() {
124
225
  );
125
226
  server.tool(
126
227
  "get_review_result",
127
- "Fetch the most recent review result from a DiffPrism watch 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).",
228
+ "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).",
128
229
  {
129
230
  wait: z.boolean().optional().describe("If true, poll until a review result is available (blocks up to timeout)"),
130
231
  timeout: z.number().optional().describe("Max wait time in seconds when wait=true (default: 300, max: 600)")
131
232
  },
132
233
  async ({ wait, timeout }) => {
133
234
  try {
235
+ const maxWaitMs = Math.min(timeout ?? 300, 600) * 1e3;
236
+ const pollIntervalMs = 2e3;
237
+ if (lastGlobalSessionId && lastGlobalServerInfo) {
238
+ const serverInfo = await isServerAlive();
239
+ if (serverInfo) {
240
+ if (wait) {
241
+ const start = Date.now();
242
+ while (Date.now() - start < maxWaitMs) {
243
+ const response2 = await fetch(
244
+ `http://localhost:${serverInfo.httpPort}/api/reviews/${lastGlobalSessionId}/result`
245
+ );
246
+ if (response2.ok) {
247
+ const data2 = await response2.json();
248
+ if (data2.result) {
249
+ return {
250
+ content: [
251
+ {
252
+ type: "text",
253
+ text: JSON.stringify(data2.result, null, 2)
254
+ }
255
+ ]
256
+ };
257
+ }
258
+ }
259
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
260
+ }
261
+ return {
262
+ content: [
263
+ {
264
+ type: "text",
265
+ text: "No review result received within timeout."
266
+ }
267
+ ]
268
+ };
269
+ }
270
+ const response = await fetch(
271
+ `http://localhost:${serverInfo.httpPort}/api/reviews/${lastGlobalSessionId}/result`
272
+ );
273
+ if (response.ok) {
274
+ const data2 = await response.json();
275
+ if (data2.result) {
276
+ return {
277
+ content: [
278
+ {
279
+ type: "text",
280
+ text: JSON.stringify(data2.result, null, 2)
281
+ }
282
+ ]
283
+ };
284
+ }
285
+ }
286
+ return {
287
+ content: [
288
+ {
289
+ type: "text",
290
+ text: "No pending review result."
291
+ }
292
+ ]
293
+ };
294
+ }
295
+ }
134
296
  if (wait) {
135
- const maxWaitMs = Math.min(timeout ?? 300, 600) * 1e3;
136
- const pollIntervalMs = 2e3;
137
297
  const start = Date.now();
138
298
  while (Date.now() - start < maxWaitMs) {
139
299
  const data2 = readReviewResult();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffprism",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "description": "Local-first code review tool for agent-generated code changes",
6
6
  "bin": {