diffprism 0.14.0 → 0.15.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.
- package/dist/bin.js +91 -2
- package/dist/{chunk-TVXIMP3G.js → chunk-NGHUHDAM.js} +417 -15
- package/dist/mcp-server.js +2 -2
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
isServerAlive,
|
|
4
|
+
readServerFile,
|
|
3
5
|
readWatchFile,
|
|
6
|
+
startGlobalServer,
|
|
4
7
|
startReview,
|
|
5
8
|
startWatch
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-NGHUHDAM.js";
|
|
7
10
|
|
|
8
11
|
// cli/src/index.ts
|
|
9
12
|
import { Command } from "commander";
|
|
@@ -490,9 +493,92 @@ async function notifyStop() {
|
|
|
490
493
|
process.exit(0);
|
|
491
494
|
}
|
|
492
495
|
|
|
496
|
+
// cli/src/commands/server.ts
|
|
497
|
+
async function server(flags) {
|
|
498
|
+
const existing = await isServerAlive();
|
|
499
|
+
if (existing) {
|
|
500
|
+
console.log(`DiffPrism server is already running on port ${existing.httpPort} (PID ${existing.pid})`);
|
|
501
|
+
console.log(`Use 'diffprism server stop' to stop it first.`);
|
|
502
|
+
process.exit(1);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const httpPort = flags.port ? parseInt(flags.port, 10) : void 0;
|
|
506
|
+
const wsPort = flags.wsPort ? parseInt(flags.wsPort, 10) : void 0;
|
|
507
|
+
try {
|
|
508
|
+
const handle = await startGlobalServer({
|
|
509
|
+
httpPort,
|
|
510
|
+
wsPort,
|
|
511
|
+
dev: flags.dev
|
|
512
|
+
});
|
|
513
|
+
const shutdown = async () => {
|
|
514
|
+
console.log("\nStopping server...");
|
|
515
|
+
await handle.stop();
|
|
516
|
+
process.exit(0);
|
|
517
|
+
};
|
|
518
|
+
process.on("SIGINT", shutdown);
|
|
519
|
+
process.on("SIGTERM", shutdown);
|
|
520
|
+
await new Promise(() => {
|
|
521
|
+
});
|
|
522
|
+
} catch (err) {
|
|
523
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
524
|
+
console.error(`Error starting server: ${message}`);
|
|
525
|
+
process.exit(1);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async function serverStatus() {
|
|
529
|
+
const info = await isServerAlive();
|
|
530
|
+
if (!info) {
|
|
531
|
+
console.log("No DiffPrism server is running.");
|
|
532
|
+
process.exit(1);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const response = await fetch(`http://localhost:${info.httpPort}/api/status`, {
|
|
537
|
+
signal: AbortSignal.timeout(2e3)
|
|
538
|
+
});
|
|
539
|
+
const status = await response.json();
|
|
540
|
+
console.log(`DiffPrism Server`);
|
|
541
|
+
console.log(` API: http://localhost:${info.httpPort}`);
|
|
542
|
+
console.log(` WS: ws://localhost:${info.wsPort}`);
|
|
543
|
+
console.log(` PID: ${status.pid}`);
|
|
544
|
+
console.log(` Sessions: ${status.sessions}`);
|
|
545
|
+
console.log(` Uptime: ${Math.floor(status.uptime)}s`);
|
|
546
|
+
if (status.sessions > 0) {
|
|
547
|
+
const sessionsResponse = await fetch(
|
|
548
|
+
`http://localhost:${info.httpPort}/api/reviews`,
|
|
549
|
+
{ signal: AbortSignal.timeout(2e3) }
|
|
550
|
+
);
|
|
551
|
+
const { sessions } = await sessionsResponse.json();
|
|
552
|
+
console.log(`
|
|
553
|
+
Active Sessions:`);
|
|
554
|
+
for (const s of sessions) {
|
|
555
|
+
const label = s.title ?? s.branch ?? s.projectPath;
|
|
556
|
+
console.log(` ${s.id} \u2014 ${label} (${s.status}, ${s.fileCount} files, +${s.additions}/-${s.deletions})`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
} catch (err) {
|
|
560
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
561
|
+
console.error(`Error checking server status: ${message}`);
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
async function serverStop() {
|
|
566
|
+
const info = readServerFile();
|
|
567
|
+
if (!info) {
|
|
568
|
+
console.log("No DiffPrism server is running.");
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
try {
|
|
572
|
+
process.kill(info.pid, "SIGTERM");
|
|
573
|
+
console.log(`Sent stop signal to DiffPrism server (PID ${info.pid}).`);
|
|
574
|
+
} catch {
|
|
575
|
+
console.log(`Server process (PID ${info.pid}) is no longer running. Cleaning up.`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
493
579
|
// cli/src/index.ts
|
|
494
580
|
var program = new Command();
|
|
495
|
-
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.
|
|
581
|
+
program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.15.0" : "0.0.0-dev");
|
|
496
582
|
program.command("review [ref]").description("Open a browser-based diff review").option("--staged", "Review staged changes").option("--unstaged", "Review unstaged changes").option("-t, --title <title>", "Review title").option("--dev", "Use Vite dev server with HMR instead of static files").action(review);
|
|
497
583
|
program.command("start [ref]").description("Set up DiffPrism and start watching for changes").option("--staged", "Watch staged changes").option("--unstaged", "Watch unstaged changes").option("-t, --title <title>", "Review title").option("--interval <ms>", "Poll interval in milliseconds (default: 1000)").option("--dev", "Use Vite dev server with HMR instead of static files").option("--global", "Install skill globally (~/.claude/skills/)").option("--force", "Overwrite existing configuration files").action(start);
|
|
498
584
|
program.command("watch [ref]").description("Start a persistent diff watcher with live-updating browser UI").option("--staged", "Watch staged changes").option("--unstaged", "Watch unstaged changes").option("-t, --title <title>", "Review title").option("--interval <ms>", "Poll interval in milliseconds (default: 1000)").option("--dev", "Use Vite dev server with HMR instead of static files").action(watch);
|
|
@@ -501,4 +587,7 @@ program.command("serve").description("Start the MCP server for Claude Code integ
|
|
|
501
587
|
program.command("setup").description("Configure DiffPrism for Claude Code integration").option("--global", "Install skill globally (~/.claude/skills/)").option("--force", "Overwrite existing configuration files").action((flags) => {
|
|
502
588
|
setup(flags);
|
|
503
589
|
});
|
|
590
|
+
var serverCmd = program.command("server").description("Start the global DiffPrism server for multi-session reviews").option("-p, --port <port>", "HTTP API port (default: 24680)").option("--ws-port <port>", "WebSocket port (default: 24681)").option("--dev", "Use Vite dev server with HMR instead of static files").action(server);
|
|
591
|
+
serverCmd.command("status").description("Check if the global server is running and list active sessions").action(serverStatus);
|
|
592
|
+
serverCmd.command("stop").description("Stop the running global server").action(serverStop);
|
|
504
593
|
program.parse();
|
|
@@ -537,15 +537,15 @@ var CONFIG_PATTERNS = [
|
|
|
537
537
|
/vite\.config/,
|
|
538
538
|
/vitest\.config/
|
|
539
539
|
];
|
|
540
|
-
function isTestFile(
|
|
541
|
-
return TEST_PATTERNS.some((re) => re.test(
|
|
540
|
+
function isTestFile(path6) {
|
|
541
|
+
return TEST_PATTERNS.some((re) => re.test(path6));
|
|
542
542
|
}
|
|
543
|
-
function isNonCodeFile(
|
|
544
|
-
const ext =
|
|
543
|
+
function isNonCodeFile(path6) {
|
|
544
|
+
const ext = path6.slice(path6.lastIndexOf("."));
|
|
545
545
|
return NON_CODE_EXTENSIONS.has(ext);
|
|
546
546
|
}
|
|
547
|
-
function isConfigFile(
|
|
548
|
-
return CONFIG_PATTERNS.some((re) => re.test(
|
|
547
|
+
function isConfigFile(path6) {
|
|
548
|
+
return CONFIG_PATTERNS.some((re) => re.test(path6));
|
|
549
549
|
}
|
|
550
550
|
function detectTestCoverageGaps(files) {
|
|
551
551
|
const filePaths = new Set(files.map((f) => f.path));
|
|
@@ -720,14 +720,14 @@ function analyze(diffSet) {
|
|
|
720
720
|
// packages/core/src/ws-bridge.ts
|
|
721
721
|
import { WebSocketServer, WebSocket } from "ws";
|
|
722
722
|
function createWsBridge(port) {
|
|
723
|
-
const
|
|
723
|
+
const wss2 = new WebSocketServer({ port });
|
|
724
724
|
let client = null;
|
|
725
725
|
let resultResolve = null;
|
|
726
726
|
let resultReject = null;
|
|
727
727
|
let pendingInit = null;
|
|
728
728
|
let initPayload = null;
|
|
729
729
|
let closeTimer = null;
|
|
730
|
-
|
|
730
|
+
wss2.on("connection", (ws) => {
|
|
731
731
|
if (closeTimer) {
|
|
732
732
|
clearTimeout(closeTimer);
|
|
733
733
|
closeTimer = null;
|
|
@@ -787,10 +787,10 @@ function createWsBridge(port) {
|
|
|
787
787
|
});
|
|
788
788
|
},
|
|
789
789
|
close() {
|
|
790
|
-
for (const ws of
|
|
790
|
+
for (const ws of wss2.clients) {
|
|
791
791
|
ws.close();
|
|
792
792
|
}
|
|
793
|
-
|
|
793
|
+
wss2.close();
|
|
794
794
|
}
|
|
795
795
|
};
|
|
796
796
|
}
|
|
@@ -1117,13 +1117,13 @@ function createWatchBridge(port, callbacks) {
|
|
|
1117
1117
|
res.writeHead(404);
|
|
1118
1118
|
res.end("Not found");
|
|
1119
1119
|
});
|
|
1120
|
-
const
|
|
1120
|
+
const wss2 = new WebSocketServer2({ server: httpServer });
|
|
1121
1121
|
function sendToClient(msg) {
|
|
1122
1122
|
if (client && client.readyState === WebSocket2.OPEN) {
|
|
1123
1123
|
client.send(JSON.stringify(msg));
|
|
1124
1124
|
}
|
|
1125
1125
|
}
|
|
1126
|
-
|
|
1126
|
+
wss2.on("connection", (ws) => {
|
|
1127
1127
|
if (closeTimer) {
|
|
1128
1128
|
clearTimeout(closeTimer);
|
|
1129
1129
|
closeTimer = null;
|
|
@@ -1182,10 +1182,10 @@ function createWatchBridge(port, callbacks) {
|
|
|
1182
1182
|
if (closeTimer) {
|
|
1183
1183
|
clearTimeout(closeTimer);
|
|
1184
1184
|
}
|
|
1185
|
-
for (const ws of
|
|
1185
|
+
for (const ws of wss2.clients) {
|
|
1186
1186
|
ws.close();
|
|
1187
1187
|
}
|
|
1188
|
-
|
|
1188
|
+
wss2.close();
|
|
1189
1189
|
await new Promise((resolve2) => {
|
|
1190
1190
|
httpServer.close(() => resolve2());
|
|
1191
1191
|
});
|
|
@@ -1369,10 +1369,412 @@ Review submitted: ${result.decision}`);
|
|
|
1369
1369
|
return { stop, updateContext };
|
|
1370
1370
|
}
|
|
1371
1371
|
|
|
1372
|
+
// packages/core/src/global-server.ts
|
|
1373
|
+
import http3 from "http";
|
|
1374
|
+
import { randomUUID } from "crypto";
|
|
1375
|
+
import getPort3 from "get-port";
|
|
1376
|
+
import open3 from "open";
|
|
1377
|
+
import { WebSocketServer as WebSocketServer3, WebSocket as WebSocket3 } from "ws";
|
|
1378
|
+
|
|
1379
|
+
// packages/core/src/server-file.ts
|
|
1380
|
+
import fs3 from "fs";
|
|
1381
|
+
import path5 from "path";
|
|
1382
|
+
import os from "os";
|
|
1383
|
+
function serverDir() {
|
|
1384
|
+
return path5.join(os.homedir(), ".diffprism");
|
|
1385
|
+
}
|
|
1386
|
+
function serverFilePath() {
|
|
1387
|
+
return path5.join(serverDir(), "server.json");
|
|
1388
|
+
}
|
|
1389
|
+
function isPidAlive2(pid) {
|
|
1390
|
+
try {
|
|
1391
|
+
process.kill(pid, 0);
|
|
1392
|
+
return true;
|
|
1393
|
+
} catch {
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
function writeServerFile(info) {
|
|
1398
|
+
const dir = serverDir();
|
|
1399
|
+
if (!fs3.existsSync(dir)) {
|
|
1400
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1401
|
+
}
|
|
1402
|
+
fs3.writeFileSync(serverFilePath(), JSON.stringify(info, null, 2) + "\n");
|
|
1403
|
+
}
|
|
1404
|
+
function readServerFile() {
|
|
1405
|
+
const filePath = serverFilePath();
|
|
1406
|
+
if (!fs3.existsSync(filePath)) {
|
|
1407
|
+
return null;
|
|
1408
|
+
}
|
|
1409
|
+
try {
|
|
1410
|
+
const raw = fs3.readFileSync(filePath, "utf-8");
|
|
1411
|
+
const info = JSON.parse(raw);
|
|
1412
|
+
if (!isPidAlive2(info.pid)) {
|
|
1413
|
+
fs3.unlinkSync(filePath);
|
|
1414
|
+
return null;
|
|
1415
|
+
}
|
|
1416
|
+
return info;
|
|
1417
|
+
} catch {
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
function removeServerFile() {
|
|
1422
|
+
try {
|
|
1423
|
+
const filePath = serverFilePath();
|
|
1424
|
+
if (fs3.existsSync(filePath)) {
|
|
1425
|
+
fs3.unlinkSync(filePath);
|
|
1426
|
+
}
|
|
1427
|
+
} catch {
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
async function isServerAlive() {
|
|
1431
|
+
const info = readServerFile();
|
|
1432
|
+
if (!info) {
|
|
1433
|
+
return null;
|
|
1434
|
+
}
|
|
1435
|
+
try {
|
|
1436
|
+
const response = await fetch(`http://localhost:${info.httpPort}/api/status`, {
|
|
1437
|
+
signal: AbortSignal.timeout(2e3)
|
|
1438
|
+
});
|
|
1439
|
+
if (response.ok) {
|
|
1440
|
+
return info;
|
|
1441
|
+
}
|
|
1442
|
+
return null;
|
|
1443
|
+
} catch {
|
|
1444
|
+
removeServerFile();
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// packages/core/src/global-server.ts
|
|
1450
|
+
var sessions2 = /* @__PURE__ */ new Map();
|
|
1451
|
+
var clientSessions = /* @__PURE__ */ new Map();
|
|
1452
|
+
function toSummary(session) {
|
|
1453
|
+
const { payload } = session;
|
|
1454
|
+
const fileCount = payload.diffSet.files.length;
|
|
1455
|
+
let additions = 0;
|
|
1456
|
+
let deletions = 0;
|
|
1457
|
+
for (const file of payload.diffSet.files) {
|
|
1458
|
+
additions += file.additions;
|
|
1459
|
+
deletions += file.deletions;
|
|
1460
|
+
}
|
|
1461
|
+
return {
|
|
1462
|
+
id: session.id,
|
|
1463
|
+
projectPath: session.projectPath,
|
|
1464
|
+
branch: payload.metadata.currentBranch,
|
|
1465
|
+
title: payload.metadata.title,
|
|
1466
|
+
fileCount,
|
|
1467
|
+
additions,
|
|
1468
|
+
deletions,
|
|
1469
|
+
status: session.status,
|
|
1470
|
+
createdAt: session.createdAt
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
function readBody(req) {
|
|
1474
|
+
return new Promise((resolve, reject) => {
|
|
1475
|
+
let body = "";
|
|
1476
|
+
req.on("data", (chunk) => {
|
|
1477
|
+
body += chunk.toString();
|
|
1478
|
+
});
|
|
1479
|
+
req.on("end", () => resolve(body));
|
|
1480
|
+
req.on("error", reject);
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
function jsonResponse(res, status, data) {
|
|
1484
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
1485
|
+
res.end(JSON.stringify(data));
|
|
1486
|
+
}
|
|
1487
|
+
function matchRoute(method, url, expectedMethod, pattern) {
|
|
1488
|
+
if (method !== expectedMethod) return null;
|
|
1489
|
+
const patternParts = pattern.split("/");
|
|
1490
|
+
const urlParts = url.split("/");
|
|
1491
|
+
if (patternParts.length !== urlParts.length) return null;
|
|
1492
|
+
const params = {};
|
|
1493
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
1494
|
+
if (patternParts[i].startsWith(":")) {
|
|
1495
|
+
params[patternParts[i].slice(1)] = urlParts[i];
|
|
1496
|
+
} else if (patternParts[i] !== urlParts[i]) {
|
|
1497
|
+
return null;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return params;
|
|
1501
|
+
}
|
|
1502
|
+
var wss = null;
|
|
1503
|
+
function broadcastToAll(msg) {
|
|
1504
|
+
if (!wss) return;
|
|
1505
|
+
const data = JSON.stringify(msg);
|
|
1506
|
+
for (const client of wss.clients) {
|
|
1507
|
+
if (client.readyState === WebSocket3.OPEN) {
|
|
1508
|
+
client.send(data);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
function sendToSessionClients(sessionId, msg) {
|
|
1513
|
+
if (!wss) return;
|
|
1514
|
+
const data = JSON.stringify(msg);
|
|
1515
|
+
for (const [client, sid] of clientSessions.entries()) {
|
|
1516
|
+
if (sid === sessionId && client.readyState === WebSocket3.OPEN) {
|
|
1517
|
+
client.send(data);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
async function handleApiRequest(req, res) {
|
|
1522
|
+
const method = req.method ?? "GET";
|
|
1523
|
+
const url = (req.url ?? "/").split("?")[0];
|
|
1524
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1525
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1526
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1527
|
+
if (method === "OPTIONS") {
|
|
1528
|
+
res.writeHead(204);
|
|
1529
|
+
res.end();
|
|
1530
|
+
return true;
|
|
1531
|
+
}
|
|
1532
|
+
if (!url.startsWith("/api/")) {
|
|
1533
|
+
return false;
|
|
1534
|
+
}
|
|
1535
|
+
if (method === "GET" && url === "/api/status") {
|
|
1536
|
+
jsonResponse(res, 200, {
|
|
1537
|
+
running: true,
|
|
1538
|
+
pid: process.pid,
|
|
1539
|
+
sessions: sessions2.size,
|
|
1540
|
+
uptime: process.uptime()
|
|
1541
|
+
});
|
|
1542
|
+
return true;
|
|
1543
|
+
}
|
|
1544
|
+
if (method === "POST" && url === "/api/reviews") {
|
|
1545
|
+
try {
|
|
1546
|
+
const body = await readBody(req);
|
|
1547
|
+
const { payload, projectPath } = JSON.parse(body);
|
|
1548
|
+
const sessionId = `session-${randomUUID().slice(0, 8)}`;
|
|
1549
|
+
payload.reviewId = sessionId;
|
|
1550
|
+
const session = {
|
|
1551
|
+
id: sessionId,
|
|
1552
|
+
payload,
|
|
1553
|
+
projectPath,
|
|
1554
|
+
status: "pending",
|
|
1555
|
+
createdAt: Date.now(),
|
|
1556
|
+
result: null
|
|
1557
|
+
};
|
|
1558
|
+
sessions2.set(sessionId, session);
|
|
1559
|
+
broadcastToAll({
|
|
1560
|
+
type: "session:added",
|
|
1561
|
+
payload: toSummary(session)
|
|
1562
|
+
});
|
|
1563
|
+
jsonResponse(res, 201, { sessionId });
|
|
1564
|
+
} catch {
|
|
1565
|
+
jsonResponse(res, 400, { error: "Invalid request body" });
|
|
1566
|
+
}
|
|
1567
|
+
return true;
|
|
1568
|
+
}
|
|
1569
|
+
if (method === "GET" && url === "/api/reviews") {
|
|
1570
|
+
const summaries = Array.from(sessions2.values()).map(toSummary);
|
|
1571
|
+
jsonResponse(res, 200, { sessions: summaries });
|
|
1572
|
+
return true;
|
|
1573
|
+
}
|
|
1574
|
+
const getReviewParams = matchRoute(method, url, "GET", "/api/reviews/:id");
|
|
1575
|
+
if (getReviewParams) {
|
|
1576
|
+
const session = sessions2.get(getReviewParams.id);
|
|
1577
|
+
if (!session) {
|
|
1578
|
+
jsonResponse(res, 404, { error: "Session not found" });
|
|
1579
|
+
return true;
|
|
1580
|
+
}
|
|
1581
|
+
jsonResponse(res, 200, toSummary(session));
|
|
1582
|
+
return true;
|
|
1583
|
+
}
|
|
1584
|
+
const postResultParams = matchRoute(method, url, "POST", "/api/reviews/:id/result");
|
|
1585
|
+
if (postResultParams) {
|
|
1586
|
+
const session = sessions2.get(postResultParams.id);
|
|
1587
|
+
if (!session) {
|
|
1588
|
+
jsonResponse(res, 404, { error: "Session not found" });
|
|
1589
|
+
return true;
|
|
1590
|
+
}
|
|
1591
|
+
try {
|
|
1592
|
+
const body = await readBody(req);
|
|
1593
|
+
const result = JSON.parse(body);
|
|
1594
|
+
session.result = result;
|
|
1595
|
+
session.status = "submitted";
|
|
1596
|
+
jsonResponse(res, 200, { ok: true });
|
|
1597
|
+
} catch {
|
|
1598
|
+
jsonResponse(res, 400, { error: "Invalid request body" });
|
|
1599
|
+
}
|
|
1600
|
+
return true;
|
|
1601
|
+
}
|
|
1602
|
+
const getResultParams = matchRoute(method, url, "GET", "/api/reviews/:id/result");
|
|
1603
|
+
if (getResultParams) {
|
|
1604
|
+
const session = sessions2.get(getResultParams.id);
|
|
1605
|
+
if (!session) {
|
|
1606
|
+
jsonResponse(res, 404, { error: "Session not found" });
|
|
1607
|
+
return true;
|
|
1608
|
+
}
|
|
1609
|
+
if (session.result) {
|
|
1610
|
+
jsonResponse(res, 200, { result: session.result, status: "submitted" });
|
|
1611
|
+
} else {
|
|
1612
|
+
jsonResponse(res, 200, { result: null, status: session.status });
|
|
1613
|
+
}
|
|
1614
|
+
return true;
|
|
1615
|
+
}
|
|
1616
|
+
const postContextParams = matchRoute(method, url, "POST", "/api/reviews/:id/context");
|
|
1617
|
+
if (postContextParams) {
|
|
1618
|
+
const session = sessions2.get(postContextParams.id);
|
|
1619
|
+
if (!session) {
|
|
1620
|
+
jsonResponse(res, 404, { error: "Session not found" });
|
|
1621
|
+
return true;
|
|
1622
|
+
}
|
|
1623
|
+
try {
|
|
1624
|
+
const body = await readBody(req);
|
|
1625
|
+
const contextPayload = JSON.parse(body);
|
|
1626
|
+
if (contextPayload.reasoning !== void 0) {
|
|
1627
|
+
session.payload.metadata.reasoning = contextPayload.reasoning;
|
|
1628
|
+
}
|
|
1629
|
+
if (contextPayload.title !== void 0) {
|
|
1630
|
+
session.payload.metadata.title = contextPayload.title;
|
|
1631
|
+
}
|
|
1632
|
+
if (contextPayload.description !== void 0) {
|
|
1633
|
+
session.payload.metadata.description = contextPayload.description;
|
|
1634
|
+
}
|
|
1635
|
+
sendToSessionClients(session.id, {
|
|
1636
|
+
type: "context:update",
|
|
1637
|
+
payload: contextPayload
|
|
1638
|
+
});
|
|
1639
|
+
jsonResponse(res, 200, { ok: true });
|
|
1640
|
+
} catch {
|
|
1641
|
+
jsonResponse(res, 400, { error: "Invalid request body" });
|
|
1642
|
+
}
|
|
1643
|
+
return true;
|
|
1644
|
+
}
|
|
1645
|
+
const deleteParams = matchRoute(method, url, "DELETE", "/api/reviews/:id");
|
|
1646
|
+
if (deleteParams) {
|
|
1647
|
+
if (sessions2.delete(deleteParams.id)) {
|
|
1648
|
+
jsonResponse(res, 200, { ok: true });
|
|
1649
|
+
} else {
|
|
1650
|
+
jsonResponse(res, 404, { error: "Session not found" });
|
|
1651
|
+
}
|
|
1652
|
+
return true;
|
|
1653
|
+
}
|
|
1654
|
+
jsonResponse(res, 404, { error: "Not found" });
|
|
1655
|
+
return true;
|
|
1656
|
+
}
|
|
1657
|
+
async function startGlobalServer(options = {}) {
|
|
1658
|
+
const {
|
|
1659
|
+
httpPort: preferredHttpPort = 24680,
|
|
1660
|
+
wsPort: preferredWsPort = 24681,
|
|
1661
|
+
silent = false,
|
|
1662
|
+
dev = false
|
|
1663
|
+
} = options;
|
|
1664
|
+
const [httpPort, wsPort] = await Promise.all([
|
|
1665
|
+
getPort3({ port: preferredHttpPort }),
|
|
1666
|
+
getPort3({ port: preferredWsPort })
|
|
1667
|
+
]);
|
|
1668
|
+
let uiPort;
|
|
1669
|
+
let uiHttpServer = null;
|
|
1670
|
+
let viteServer = null;
|
|
1671
|
+
if (dev) {
|
|
1672
|
+
uiPort = await getPort3();
|
|
1673
|
+
const uiRoot = resolveUiRoot();
|
|
1674
|
+
viteServer = await startViteDevServer(uiRoot, uiPort, silent);
|
|
1675
|
+
} else {
|
|
1676
|
+
uiPort = await getPort3();
|
|
1677
|
+
const uiDist = resolveUiDist();
|
|
1678
|
+
uiHttpServer = await createStaticServer(uiDist, uiPort);
|
|
1679
|
+
}
|
|
1680
|
+
const httpServer = http3.createServer(async (req, res) => {
|
|
1681
|
+
const handled = await handleApiRequest(req, res);
|
|
1682
|
+
if (!handled) {
|
|
1683
|
+
res.writeHead(404);
|
|
1684
|
+
res.end("Not found");
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
wss = new WebSocketServer3({ port: wsPort });
|
|
1688
|
+
wss.on("connection", (ws, req) => {
|
|
1689
|
+
const url = new URL(req.url ?? "/", `http://localhost:${wsPort}`);
|
|
1690
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
1691
|
+
if (sessionId) {
|
|
1692
|
+
clientSessions.set(ws, sessionId);
|
|
1693
|
+
const session = sessions2.get(sessionId);
|
|
1694
|
+
if (session) {
|
|
1695
|
+
session.status = "in_review";
|
|
1696
|
+
const msg = {
|
|
1697
|
+
type: "review:init",
|
|
1698
|
+
payload: session.payload
|
|
1699
|
+
};
|
|
1700
|
+
ws.send(JSON.stringify(msg));
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
ws.on("message", (data) => {
|
|
1704
|
+
try {
|
|
1705
|
+
const msg = JSON.parse(data.toString());
|
|
1706
|
+
if (msg.type === "review:submit") {
|
|
1707
|
+
const sid = clientSessions.get(ws);
|
|
1708
|
+
if (sid) {
|
|
1709
|
+
const session = sessions2.get(sid);
|
|
1710
|
+
if (session) {
|
|
1711
|
+
session.result = msg.payload;
|
|
1712
|
+
session.status = "submitted";
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
} catch {
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
ws.on("close", () => {
|
|
1720
|
+
clientSessions.delete(ws);
|
|
1721
|
+
});
|
|
1722
|
+
});
|
|
1723
|
+
await new Promise((resolve, reject) => {
|
|
1724
|
+
httpServer.on("error", reject);
|
|
1725
|
+
httpServer.listen(httpPort, () => resolve());
|
|
1726
|
+
});
|
|
1727
|
+
const serverInfo = {
|
|
1728
|
+
httpPort,
|
|
1729
|
+
wsPort,
|
|
1730
|
+
pid: process.pid,
|
|
1731
|
+
startedAt: Date.now()
|
|
1732
|
+
};
|
|
1733
|
+
writeServerFile(serverInfo);
|
|
1734
|
+
if (!silent) {
|
|
1735
|
+
console.log(`
|
|
1736
|
+
DiffPrism Global Server`);
|
|
1737
|
+
console.log(` API: http://localhost:${httpPort}`);
|
|
1738
|
+
console.log(` WS: ws://localhost:${wsPort}`);
|
|
1739
|
+
console.log(` UI: http://localhost:${uiPort}`);
|
|
1740
|
+
console.log(` PID: ${process.pid}`);
|
|
1741
|
+
console.log(`
|
|
1742
|
+
Waiting for reviews...
|
|
1743
|
+
`);
|
|
1744
|
+
}
|
|
1745
|
+
const uiUrl = `http://localhost:${uiPort}?wsPort=${wsPort}&serverMode=true`;
|
|
1746
|
+
await open3(uiUrl);
|
|
1747
|
+
async function stop() {
|
|
1748
|
+
if (wss) {
|
|
1749
|
+
for (const client of wss.clients) {
|
|
1750
|
+
client.close();
|
|
1751
|
+
}
|
|
1752
|
+
wss.close();
|
|
1753
|
+
wss = null;
|
|
1754
|
+
}
|
|
1755
|
+
clientSessions.clear();
|
|
1756
|
+
sessions2.clear();
|
|
1757
|
+
await new Promise((resolve) => {
|
|
1758
|
+
httpServer.close(() => resolve());
|
|
1759
|
+
});
|
|
1760
|
+
if (viteServer) {
|
|
1761
|
+
await viteServer.close();
|
|
1762
|
+
}
|
|
1763
|
+
if (uiHttpServer) {
|
|
1764
|
+
uiHttpServer.close();
|
|
1765
|
+
}
|
|
1766
|
+
removeServerFile();
|
|
1767
|
+
}
|
|
1768
|
+
return { httpPort, wsPort, stop };
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1372
1771
|
export {
|
|
1373
1772
|
startReview,
|
|
1374
1773
|
readWatchFile,
|
|
1375
1774
|
readReviewResult,
|
|
1376
1775
|
consumeReviewResult,
|
|
1377
|
-
startWatch
|
|
1776
|
+
startWatch,
|
|
1777
|
+
readServerFile,
|
|
1778
|
+
isServerAlive,
|
|
1779
|
+
startGlobalServer
|
|
1378
1780
|
};
|
package/dist/mcp-server.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
readReviewResult,
|
|
4
4
|
readWatchFile,
|
|
5
5
|
startReview
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-NGHUHDAM.js";
|
|
7
7
|
|
|
8
8
|
// packages/mcp-server/src/index.ts
|
|
9
9
|
import fs from "fs";
|
|
@@ -14,7 +14,7 @@ import { z } from "zod";
|
|
|
14
14
|
async function startMcpServer() {
|
|
15
15
|
const server = new McpServer({
|
|
16
16
|
name: "diffprism",
|
|
17
|
-
version: true ? "0.
|
|
17
|
+
version: true ? "0.15.0" : "0.0.0-dev"
|
|
18
18
|
});
|
|
19
19
|
server.tool(
|
|
20
20
|
"open_review",
|