diffprism 0.14.0 → 0.16.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 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-TVXIMP3G.js";
9
+ } from "./chunk-4WN4FIY4.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.14.0" : "0.0.0-dev");
581
+ program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.16.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();
@@ -1,7 +1,3 @@
1
- // packages/core/src/pipeline.ts
2
- import getPort from "get-port";
3
- import open from "open";
4
-
5
1
  // packages/git/src/local.ts
6
2
  import { execSync } from "child_process";
7
3
  import { readFileSync } from "fs";
@@ -537,15 +533,15 @@ var CONFIG_PATTERNS = [
537
533
  /vite\.config/,
538
534
  /vitest\.config/
539
535
  ];
540
- function isTestFile(path5) {
541
- return TEST_PATTERNS.some((re) => re.test(path5));
536
+ function isTestFile(path6) {
537
+ return TEST_PATTERNS.some((re) => re.test(path6));
542
538
  }
543
- function isNonCodeFile(path5) {
544
- const ext = path5.slice(path5.lastIndexOf("."));
539
+ function isNonCodeFile(path6) {
540
+ const ext = path6.slice(path6.lastIndexOf("."));
545
541
  return NON_CODE_EXTENSIONS.has(ext);
546
542
  }
547
- function isConfigFile(path5) {
548
- return CONFIG_PATTERNS.some((re) => re.test(path5));
543
+ function isConfigFile(path6) {
544
+ return CONFIG_PATTERNS.some((re) => re.test(path6));
549
545
  }
550
546
  function detectTestCoverageGaps(files) {
551
547
  const filePaths = new Set(files.map((f) => f.path));
@@ -717,17 +713,21 @@ function analyze(diffSet) {
717
713
  };
718
714
  }
719
715
 
716
+ // packages/core/src/pipeline.ts
717
+ import getPort from "get-port";
718
+ import open from "open";
719
+
720
720
  // packages/core/src/ws-bridge.ts
721
721
  import { WebSocketServer, WebSocket } from "ws";
722
722
  function createWsBridge(port) {
723
- const wss = new WebSocketServer({ port });
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
- wss.on("connection", (ws) => {
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 wss.clients) {
790
+ for (const ws of wss2.clients) {
791
791
  ws.close();
792
792
  }
793
- wss.close();
793
+ wss2.close();
794
794
  }
795
795
  };
796
796
  }
@@ -1061,6 +1061,76 @@ function consumeReviewResult(cwd) {
1061
1061
  }
1062
1062
  }
1063
1063
 
1064
+ // packages/core/src/server-file.ts
1065
+ import fs3 from "fs";
1066
+ import path5 from "path";
1067
+ import os from "os";
1068
+ function serverDir() {
1069
+ return path5.join(os.homedir(), ".diffprism");
1070
+ }
1071
+ function serverFilePath() {
1072
+ return path5.join(serverDir(), "server.json");
1073
+ }
1074
+ function isPidAlive2(pid) {
1075
+ try {
1076
+ process.kill(pid, 0);
1077
+ return true;
1078
+ } catch {
1079
+ return false;
1080
+ }
1081
+ }
1082
+ function writeServerFile(info) {
1083
+ const dir = serverDir();
1084
+ if (!fs3.existsSync(dir)) {
1085
+ fs3.mkdirSync(dir, { recursive: true });
1086
+ }
1087
+ fs3.writeFileSync(serverFilePath(), JSON.stringify(info, null, 2) + "\n");
1088
+ }
1089
+ function readServerFile() {
1090
+ const filePath = serverFilePath();
1091
+ if (!fs3.existsSync(filePath)) {
1092
+ return null;
1093
+ }
1094
+ try {
1095
+ const raw = fs3.readFileSync(filePath, "utf-8");
1096
+ const info = JSON.parse(raw);
1097
+ if (!isPidAlive2(info.pid)) {
1098
+ fs3.unlinkSync(filePath);
1099
+ return null;
1100
+ }
1101
+ return info;
1102
+ } catch {
1103
+ return null;
1104
+ }
1105
+ }
1106
+ function removeServerFile() {
1107
+ try {
1108
+ const filePath = serverFilePath();
1109
+ if (fs3.existsSync(filePath)) {
1110
+ fs3.unlinkSync(filePath);
1111
+ }
1112
+ } catch {
1113
+ }
1114
+ }
1115
+ async function isServerAlive() {
1116
+ const info = readServerFile();
1117
+ if (!info) {
1118
+ return null;
1119
+ }
1120
+ try {
1121
+ const response = await fetch(`http://localhost:${info.httpPort}/api/status`, {
1122
+ signal: AbortSignal.timeout(2e3)
1123
+ });
1124
+ if (response.ok) {
1125
+ return info;
1126
+ }
1127
+ return null;
1128
+ } catch {
1129
+ removeServerFile();
1130
+ return null;
1131
+ }
1132
+ }
1133
+
1064
1134
  // packages/core/src/watch.ts
1065
1135
  import { createHash } from "crypto";
1066
1136
  import getPort2 from "get-port";
@@ -1117,13 +1187,13 @@ function createWatchBridge(port, callbacks) {
1117
1187
  res.writeHead(404);
1118
1188
  res.end("Not found");
1119
1189
  });
1120
- const wss = new WebSocketServer2({ server: httpServer });
1190
+ const wss2 = new WebSocketServer2({ server: httpServer });
1121
1191
  function sendToClient(msg) {
1122
1192
  if (client && client.readyState === WebSocket2.OPEN) {
1123
1193
  client.send(JSON.stringify(msg));
1124
1194
  }
1125
1195
  }
1126
- wss.on("connection", (ws) => {
1196
+ wss2.on("connection", (ws) => {
1127
1197
  if (closeTimer) {
1128
1198
  clearTimeout(closeTimer);
1129
1199
  closeTimer = null;
@@ -1182,10 +1252,10 @@ function createWatchBridge(port, callbacks) {
1182
1252
  if (closeTimer) {
1183
1253
  clearTimeout(closeTimer);
1184
1254
  }
1185
- for (const ws of wss.clients) {
1255
+ for (const ws of wss2.clients) {
1186
1256
  ws.close();
1187
1257
  }
1188
- wss.close();
1258
+ wss2.close();
1189
1259
  await new Promise((resolve2) => {
1190
1260
  httpServer.close(() => resolve2());
1191
1261
  });
@@ -1369,10 +1439,371 @@ Review submitted: ${result.decision}`);
1369
1439
  return { stop, updateContext };
1370
1440
  }
1371
1441
 
1442
+ // packages/core/src/global-server.ts
1443
+ import http3 from "http";
1444
+ import { randomUUID } from "crypto";
1445
+ import getPort3 from "get-port";
1446
+ import open3 from "open";
1447
+ import { WebSocketServer as WebSocketServer3, WebSocket as WebSocket3 } from "ws";
1448
+ var sessions2 = /* @__PURE__ */ new Map();
1449
+ var clientSessions = /* @__PURE__ */ new Map();
1450
+ function toSummary(session) {
1451
+ const { payload } = session;
1452
+ const fileCount = payload.diffSet.files.length;
1453
+ let additions = 0;
1454
+ let deletions = 0;
1455
+ for (const file of payload.diffSet.files) {
1456
+ additions += file.additions;
1457
+ deletions += file.deletions;
1458
+ }
1459
+ return {
1460
+ id: session.id,
1461
+ projectPath: session.projectPath,
1462
+ branch: payload.metadata.currentBranch,
1463
+ title: payload.metadata.title,
1464
+ fileCount,
1465
+ additions,
1466
+ deletions,
1467
+ status: session.status,
1468
+ createdAt: session.createdAt
1469
+ };
1470
+ }
1471
+ function readBody(req) {
1472
+ return new Promise((resolve, reject) => {
1473
+ let body = "";
1474
+ req.on("data", (chunk) => {
1475
+ body += chunk.toString();
1476
+ });
1477
+ req.on("end", () => resolve(body));
1478
+ req.on("error", reject);
1479
+ });
1480
+ }
1481
+ function jsonResponse(res, status, data) {
1482
+ res.writeHead(status, { "Content-Type": "application/json" });
1483
+ res.end(JSON.stringify(data));
1484
+ }
1485
+ function matchRoute(method, url, expectedMethod, pattern) {
1486
+ if (method !== expectedMethod) return null;
1487
+ const patternParts = pattern.split("/");
1488
+ const urlParts = url.split("/");
1489
+ if (patternParts.length !== urlParts.length) return null;
1490
+ const params = {};
1491
+ for (let i = 0; i < patternParts.length; i++) {
1492
+ if (patternParts[i].startsWith(":")) {
1493
+ params[patternParts[i].slice(1)] = urlParts[i];
1494
+ } else if (patternParts[i] !== urlParts[i]) {
1495
+ return null;
1496
+ }
1497
+ }
1498
+ return params;
1499
+ }
1500
+ var wss = null;
1501
+ function broadcastToAll(msg) {
1502
+ if (!wss) return;
1503
+ const data = JSON.stringify(msg);
1504
+ for (const client of wss.clients) {
1505
+ if (client.readyState === WebSocket3.OPEN) {
1506
+ client.send(data);
1507
+ }
1508
+ }
1509
+ }
1510
+ function sendToSessionClients(sessionId, msg) {
1511
+ if (!wss) return;
1512
+ const data = JSON.stringify(msg);
1513
+ for (const [client, sid] of clientSessions.entries()) {
1514
+ if (sid === sessionId && client.readyState === WebSocket3.OPEN) {
1515
+ client.send(data);
1516
+ }
1517
+ }
1518
+ }
1519
+ async function handleApiRequest(req, res) {
1520
+ const method = req.method ?? "GET";
1521
+ const url = (req.url ?? "/").split("?")[0];
1522
+ res.setHeader("Access-Control-Allow-Origin", "*");
1523
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1524
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
1525
+ if (method === "OPTIONS") {
1526
+ res.writeHead(204);
1527
+ res.end();
1528
+ return true;
1529
+ }
1530
+ if (!url.startsWith("/api/")) {
1531
+ return false;
1532
+ }
1533
+ if (method === "GET" && url === "/api/status") {
1534
+ jsonResponse(res, 200, {
1535
+ running: true,
1536
+ pid: process.pid,
1537
+ sessions: sessions2.size,
1538
+ uptime: process.uptime()
1539
+ });
1540
+ return true;
1541
+ }
1542
+ if (method === "POST" && url === "/api/reviews") {
1543
+ try {
1544
+ const body = await readBody(req);
1545
+ const { payload, projectPath } = JSON.parse(body);
1546
+ const sessionId = `session-${randomUUID().slice(0, 8)}`;
1547
+ payload.reviewId = sessionId;
1548
+ const session = {
1549
+ id: sessionId,
1550
+ payload,
1551
+ projectPath,
1552
+ status: "pending",
1553
+ createdAt: Date.now(),
1554
+ result: null
1555
+ };
1556
+ sessions2.set(sessionId, session);
1557
+ broadcastToAll({
1558
+ type: "session:added",
1559
+ payload: toSummary(session)
1560
+ });
1561
+ jsonResponse(res, 201, { sessionId });
1562
+ } catch {
1563
+ jsonResponse(res, 400, { error: "Invalid request body" });
1564
+ }
1565
+ return true;
1566
+ }
1567
+ if (method === "GET" && url === "/api/reviews") {
1568
+ const summaries = Array.from(sessions2.values()).map(toSummary);
1569
+ jsonResponse(res, 200, { sessions: summaries });
1570
+ return true;
1571
+ }
1572
+ const getReviewParams = matchRoute(method, url, "GET", "/api/reviews/:id");
1573
+ if (getReviewParams) {
1574
+ const session = sessions2.get(getReviewParams.id);
1575
+ if (!session) {
1576
+ jsonResponse(res, 404, { error: "Session not found" });
1577
+ return true;
1578
+ }
1579
+ jsonResponse(res, 200, toSummary(session));
1580
+ return true;
1581
+ }
1582
+ const postResultParams = matchRoute(method, url, "POST", "/api/reviews/:id/result");
1583
+ if (postResultParams) {
1584
+ const session = sessions2.get(postResultParams.id);
1585
+ if (!session) {
1586
+ jsonResponse(res, 404, { error: "Session not found" });
1587
+ return true;
1588
+ }
1589
+ try {
1590
+ const body = await readBody(req);
1591
+ const result = JSON.parse(body);
1592
+ session.result = result;
1593
+ session.status = "submitted";
1594
+ jsonResponse(res, 200, { ok: true });
1595
+ } catch {
1596
+ jsonResponse(res, 400, { error: "Invalid request body" });
1597
+ }
1598
+ return true;
1599
+ }
1600
+ const getResultParams = matchRoute(method, url, "GET", "/api/reviews/:id/result");
1601
+ if (getResultParams) {
1602
+ const session = sessions2.get(getResultParams.id);
1603
+ if (!session) {
1604
+ jsonResponse(res, 404, { error: "Session not found" });
1605
+ return true;
1606
+ }
1607
+ if (session.result) {
1608
+ jsonResponse(res, 200, { result: session.result, status: "submitted" });
1609
+ } else {
1610
+ jsonResponse(res, 200, { result: null, status: session.status });
1611
+ }
1612
+ return true;
1613
+ }
1614
+ const postContextParams = matchRoute(method, url, "POST", "/api/reviews/:id/context");
1615
+ if (postContextParams) {
1616
+ const session = sessions2.get(postContextParams.id);
1617
+ if (!session) {
1618
+ jsonResponse(res, 404, { error: "Session not found" });
1619
+ return true;
1620
+ }
1621
+ try {
1622
+ const body = await readBody(req);
1623
+ const contextPayload = JSON.parse(body);
1624
+ if (contextPayload.reasoning !== void 0) {
1625
+ session.payload.metadata.reasoning = contextPayload.reasoning;
1626
+ }
1627
+ if (contextPayload.title !== void 0) {
1628
+ session.payload.metadata.title = contextPayload.title;
1629
+ }
1630
+ if (contextPayload.description !== void 0) {
1631
+ session.payload.metadata.description = contextPayload.description;
1632
+ }
1633
+ sendToSessionClients(session.id, {
1634
+ type: "context:update",
1635
+ payload: contextPayload
1636
+ });
1637
+ jsonResponse(res, 200, { ok: true });
1638
+ } catch {
1639
+ jsonResponse(res, 400, { error: "Invalid request body" });
1640
+ }
1641
+ return true;
1642
+ }
1643
+ const deleteParams = matchRoute(method, url, "DELETE", "/api/reviews/:id");
1644
+ if (deleteParams) {
1645
+ if (sessions2.delete(deleteParams.id)) {
1646
+ jsonResponse(res, 200, { ok: true });
1647
+ } else {
1648
+ jsonResponse(res, 404, { error: "Session not found" });
1649
+ }
1650
+ return true;
1651
+ }
1652
+ jsonResponse(res, 404, { error: "Not found" });
1653
+ return true;
1654
+ }
1655
+ async function startGlobalServer(options = {}) {
1656
+ const {
1657
+ httpPort: preferredHttpPort = 24680,
1658
+ wsPort: preferredWsPort = 24681,
1659
+ silent = false,
1660
+ dev = false
1661
+ } = options;
1662
+ const [httpPort, wsPort] = await Promise.all([
1663
+ getPort3({ port: preferredHttpPort }),
1664
+ getPort3({ port: preferredWsPort })
1665
+ ]);
1666
+ let uiPort;
1667
+ let uiHttpServer = null;
1668
+ let viteServer = null;
1669
+ if (dev) {
1670
+ uiPort = await getPort3();
1671
+ const uiRoot = resolveUiRoot();
1672
+ viteServer = await startViteDevServer(uiRoot, uiPort, silent);
1673
+ } else {
1674
+ uiPort = await getPort3();
1675
+ const uiDist = resolveUiDist();
1676
+ uiHttpServer = await createStaticServer(uiDist, uiPort);
1677
+ }
1678
+ const httpServer = http3.createServer(async (req, res) => {
1679
+ const handled = await handleApiRequest(req, res);
1680
+ if (!handled) {
1681
+ res.writeHead(404);
1682
+ res.end("Not found");
1683
+ }
1684
+ });
1685
+ wss = new WebSocketServer3({ port: wsPort });
1686
+ wss.on("connection", (ws, req) => {
1687
+ const url = new URL(req.url ?? "/", `http://localhost:${wsPort}`);
1688
+ const sessionId = url.searchParams.get("sessionId");
1689
+ if (sessionId) {
1690
+ clientSessions.set(ws, sessionId);
1691
+ const session = sessions2.get(sessionId);
1692
+ if (session) {
1693
+ session.status = "in_review";
1694
+ const msg = {
1695
+ type: "review:init",
1696
+ payload: session.payload
1697
+ };
1698
+ ws.send(JSON.stringify(msg));
1699
+ }
1700
+ } else {
1701
+ const summaries = Array.from(sessions2.values()).map(toSummary);
1702
+ const msg = {
1703
+ type: "session:list",
1704
+ payload: summaries
1705
+ };
1706
+ ws.send(JSON.stringify(msg));
1707
+ if (summaries.length === 1) {
1708
+ const session = sessions2.get(summaries[0].id);
1709
+ if (session) {
1710
+ clientSessions.set(ws, session.id);
1711
+ session.status = "in_review";
1712
+ ws.send(JSON.stringify({
1713
+ type: "review:init",
1714
+ payload: session.payload
1715
+ }));
1716
+ }
1717
+ }
1718
+ }
1719
+ ws.on("message", (data) => {
1720
+ try {
1721
+ const msg = JSON.parse(data.toString());
1722
+ if (msg.type === "review:submit") {
1723
+ const sid = clientSessions.get(ws);
1724
+ if (sid) {
1725
+ const session = sessions2.get(sid);
1726
+ if (session) {
1727
+ session.result = msg.payload;
1728
+ session.status = "submitted";
1729
+ }
1730
+ }
1731
+ } else if (msg.type === "session:select") {
1732
+ const session = sessions2.get(msg.payload.sessionId);
1733
+ if (session) {
1734
+ clientSessions.set(ws, session.id);
1735
+ session.status = "in_review";
1736
+ ws.send(JSON.stringify({
1737
+ type: "review:init",
1738
+ payload: session.payload
1739
+ }));
1740
+ }
1741
+ }
1742
+ } catch {
1743
+ }
1744
+ });
1745
+ ws.on("close", () => {
1746
+ clientSessions.delete(ws);
1747
+ });
1748
+ });
1749
+ await new Promise((resolve, reject) => {
1750
+ httpServer.on("error", reject);
1751
+ httpServer.listen(httpPort, () => resolve());
1752
+ });
1753
+ const serverInfo = {
1754
+ httpPort,
1755
+ wsPort,
1756
+ pid: process.pid,
1757
+ startedAt: Date.now()
1758
+ };
1759
+ writeServerFile(serverInfo);
1760
+ if (!silent) {
1761
+ console.log(`
1762
+ DiffPrism Global Server`);
1763
+ console.log(` API: http://localhost:${httpPort}`);
1764
+ console.log(` WS: ws://localhost:${wsPort}`);
1765
+ console.log(` UI: http://localhost:${uiPort}`);
1766
+ console.log(` PID: ${process.pid}`);
1767
+ console.log(`
1768
+ Waiting for reviews...
1769
+ `);
1770
+ }
1771
+ const uiUrl = `http://localhost:${uiPort}?wsPort=${wsPort}&serverMode=true`;
1772
+ await open3(uiUrl);
1773
+ async function stop() {
1774
+ if (wss) {
1775
+ for (const client of wss.clients) {
1776
+ client.close();
1777
+ }
1778
+ wss.close();
1779
+ wss = null;
1780
+ }
1781
+ clientSessions.clear();
1782
+ sessions2.clear();
1783
+ await new Promise((resolve) => {
1784
+ httpServer.close(() => resolve());
1785
+ });
1786
+ if (viteServer) {
1787
+ await viteServer.close();
1788
+ }
1789
+ if (uiHttpServer) {
1790
+ uiHttpServer.close();
1791
+ }
1792
+ removeServerFile();
1793
+ }
1794
+ return { httpPort, wsPort, stop };
1795
+ }
1796
+
1372
1797
  export {
1798
+ getCurrentBranch,
1799
+ getDiff,
1800
+ analyze,
1373
1801
  startReview,
1374
1802
  readWatchFile,
1375
1803
  readReviewResult,
1376
1804
  consumeReviewResult,
1377
- startWatch
1805
+ startWatch,
1806
+ readServerFile,
1807
+ isServerAlive,
1808
+ startGlobalServer
1378
1809
  };