diffprism 0.12.2 → 0.13.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/README.md +38 -1
- package/dist/bin.js +140 -12
- package/dist/{chunk-LOX6GE37.js → chunk-TYUDIWD6.js} +423 -12
- package/dist/mcp-server.js +103 -2
- package/package.json +1 -1
- package/ui-dist/assets/{index-CpWq5K0r.js → index-Cs1-Woa_.js} +24 -24
- package/ui-dist/assets/index-fRamHsDg.css +1 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/index-GiCSAMgl.css +0 -1
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
// packages/core/src/pipeline.ts
|
|
2
|
-
import http from "http";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import path3 from "path";
|
|
5
2
|
import getPort from "get-port";
|
|
6
3
|
import open from "open";
|
|
7
|
-
import { fileURLToPath } from "url";
|
|
8
4
|
|
|
9
5
|
// packages/git/src/local.ts
|
|
10
6
|
import { execSync } from "child_process";
|
|
@@ -515,15 +511,15 @@ var CONFIG_PATTERNS = [
|
|
|
515
511
|
/vite\.config/,
|
|
516
512
|
/vitest\.config/
|
|
517
513
|
];
|
|
518
|
-
function isTestFile(
|
|
519
|
-
return TEST_PATTERNS.some((re) => re.test(
|
|
514
|
+
function isTestFile(path5) {
|
|
515
|
+
return TEST_PATTERNS.some((re) => re.test(path5));
|
|
520
516
|
}
|
|
521
|
-
function isNonCodeFile(
|
|
522
|
-
const ext =
|
|
517
|
+
function isNonCodeFile(path5) {
|
|
518
|
+
const ext = path5.slice(path5.lastIndexOf("."));
|
|
523
519
|
return NON_CODE_EXTENSIONS.has(ext);
|
|
524
520
|
}
|
|
525
|
-
function isConfigFile(
|
|
526
|
-
return CONFIG_PATTERNS.some((re) => re.test(
|
|
521
|
+
function isConfigFile(path5) {
|
|
522
|
+
return CONFIG_PATTERNS.some((re) => re.test(path5));
|
|
527
523
|
}
|
|
528
524
|
function detectTestCoverageGaps(files) {
|
|
529
525
|
const filePaths = new Set(files.map((f) => f.path));
|
|
@@ -794,7 +790,11 @@ function updateSession(id, update) {
|
|
|
794
790
|
}
|
|
795
791
|
}
|
|
796
792
|
|
|
797
|
-
// packages/core/src/
|
|
793
|
+
// packages/core/src/ui-server.ts
|
|
794
|
+
import http from "http";
|
|
795
|
+
import fs from "fs";
|
|
796
|
+
import path3 from "path";
|
|
797
|
+
import { fileURLToPath } from "url";
|
|
798
798
|
var MIME_TYPES = {
|
|
799
799
|
".html": "text/html",
|
|
800
800
|
".js": "application/javascript",
|
|
@@ -867,6 +867,8 @@ function createStaticServer(distPath, port) {
|
|
|
867
867
|
server.listen(port, () => resolve(server));
|
|
868
868
|
});
|
|
869
869
|
}
|
|
870
|
+
|
|
871
|
+
// packages/core/src/pipeline.ts
|
|
870
872
|
async function startReview(options) {
|
|
871
873
|
const { diffRef, title, description, reasoning, cwd, silent, dev } = options;
|
|
872
874
|
const { diffSet, rawDiff } = getDiff(diffRef, { cwd });
|
|
@@ -929,6 +931,415 @@ DiffPrism Review: ${title ?? briefing.summary}`);
|
|
|
929
931
|
}
|
|
930
932
|
}
|
|
931
933
|
|
|
934
|
+
// packages/core/src/watch-file.ts
|
|
935
|
+
import fs2 from "fs";
|
|
936
|
+
import path4 from "path";
|
|
937
|
+
import { execSync as execSync2 } from "child_process";
|
|
938
|
+
function findGitRoot(cwd) {
|
|
939
|
+
const root = execSync2("git rev-parse --show-toplevel", {
|
|
940
|
+
cwd: cwd ?? process.cwd(),
|
|
941
|
+
encoding: "utf-8"
|
|
942
|
+
}).trim();
|
|
943
|
+
return root;
|
|
944
|
+
}
|
|
945
|
+
function watchFilePath(cwd) {
|
|
946
|
+
const gitRoot = findGitRoot(cwd);
|
|
947
|
+
return path4.join(gitRoot, ".diffprism", "watch.json");
|
|
948
|
+
}
|
|
949
|
+
function isPidAlive(pid) {
|
|
950
|
+
try {
|
|
951
|
+
process.kill(pid, 0);
|
|
952
|
+
return true;
|
|
953
|
+
} catch {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function writeWatchFile(cwd, info) {
|
|
958
|
+
const filePath = watchFilePath(cwd);
|
|
959
|
+
const dir = path4.dirname(filePath);
|
|
960
|
+
if (!fs2.existsSync(dir)) {
|
|
961
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
962
|
+
}
|
|
963
|
+
fs2.writeFileSync(filePath, JSON.stringify(info, null, 2) + "\n");
|
|
964
|
+
}
|
|
965
|
+
function readWatchFile(cwd) {
|
|
966
|
+
const filePath = watchFilePath(cwd);
|
|
967
|
+
if (!fs2.existsSync(filePath)) {
|
|
968
|
+
return null;
|
|
969
|
+
}
|
|
970
|
+
try {
|
|
971
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
972
|
+
const info = JSON.parse(raw);
|
|
973
|
+
if (!isPidAlive(info.pid)) {
|
|
974
|
+
fs2.unlinkSync(filePath);
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
return info;
|
|
978
|
+
} catch {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
function removeWatchFile(cwd) {
|
|
983
|
+
try {
|
|
984
|
+
const filePath = watchFilePath(cwd);
|
|
985
|
+
if (fs2.existsSync(filePath)) {
|
|
986
|
+
fs2.unlinkSync(filePath);
|
|
987
|
+
}
|
|
988
|
+
} catch {
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
function reviewResultPath(cwd) {
|
|
992
|
+
const gitRoot = findGitRoot(cwd);
|
|
993
|
+
return path4.join(gitRoot, ".diffprism", "last-review.json");
|
|
994
|
+
}
|
|
995
|
+
function writeReviewResult(cwd, result) {
|
|
996
|
+
const filePath = reviewResultPath(cwd);
|
|
997
|
+
const dir = path4.dirname(filePath);
|
|
998
|
+
if (!fs2.existsSync(dir)) {
|
|
999
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1000
|
+
}
|
|
1001
|
+
const data = {
|
|
1002
|
+
result,
|
|
1003
|
+
timestamp: Date.now(),
|
|
1004
|
+
consumed: false
|
|
1005
|
+
};
|
|
1006
|
+
fs2.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
1007
|
+
}
|
|
1008
|
+
function readReviewResult(cwd) {
|
|
1009
|
+
try {
|
|
1010
|
+
const filePath = reviewResultPath(cwd);
|
|
1011
|
+
if (!fs2.existsSync(filePath)) {
|
|
1012
|
+
return null;
|
|
1013
|
+
}
|
|
1014
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
1015
|
+
const data = JSON.parse(raw);
|
|
1016
|
+
if (data.consumed) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
return data;
|
|
1020
|
+
} catch {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
function consumeReviewResult(cwd) {
|
|
1025
|
+
try {
|
|
1026
|
+
const filePath = reviewResultPath(cwd);
|
|
1027
|
+
if (!fs2.existsSync(filePath)) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
1031
|
+
const data = JSON.parse(raw);
|
|
1032
|
+
data.consumed = true;
|
|
1033
|
+
fs2.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
1034
|
+
} catch {
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// packages/core/src/watch.ts
|
|
1039
|
+
import { createHash } from "crypto";
|
|
1040
|
+
import getPort2 from "get-port";
|
|
1041
|
+
import open2 from "open";
|
|
1042
|
+
|
|
1043
|
+
// packages/core/src/watch-bridge.ts
|
|
1044
|
+
import http2 from "http";
|
|
1045
|
+
import { WebSocketServer as WebSocketServer2, WebSocket as WebSocket2 } from "ws";
|
|
1046
|
+
function createWatchBridge(port, callbacks) {
|
|
1047
|
+
let client = null;
|
|
1048
|
+
let initPayload = null;
|
|
1049
|
+
let pendingInit = null;
|
|
1050
|
+
let closeTimer = null;
|
|
1051
|
+
let submitCallback = null;
|
|
1052
|
+
const httpServer = http2.createServer((req, res) => {
|
|
1053
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1054
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1055
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1056
|
+
if (req.method === "OPTIONS") {
|
|
1057
|
+
res.writeHead(204);
|
|
1058
|
+
res.end();
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
if (req.method === "GET" && req.url === "/api/status") {
|
|
1062
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1063
|
+
res.end(JSON.stringify({ running: true, pid: process.pid }));
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
if (req.method === "POST" && req.url === "/api/context") {
|
|
1067
|
+
let body = "";
|
|
1068
|
+
req.on("data", (chunk) => {
|
|
1069
|
+
body += chunk.toString();
|
|
1070
|
+
});
|
|
1071
|
+
req.on("end", () => {
|
|
1072
|
+
try {
|
|
1073
|
+
const payload = JSON.parse(body);
|
|
1074
|
+
callbacks.onContextUpdate(payload);
|
|
1075
|
+
sendToClient({ type: "context:update", payload });
|
|
1076
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1077
|
+
res.end(JSON.stringify({ ok: true }));
|
|
1078
|
+
} catch {
|
|
1079
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1080
|
+
res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (req.method === "POST" && req.url === "/api/refresh") {
|
|
1086
|
+
callbacks.onRefreshRequest();
|
|
1087
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1088
|
+
res.end(JSON.stringify({ ok: true }));
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
res.writeHead(404);
|
|
1092
|
+
res.end("Not found");
|
|
1093
|
+
});
|
|
1094
|
+
const wss = new WebSocketServer2({ server: httpServer });
|
|
1095
|
+
function sendToClient(msg) {
|
|
1096
|
+
if (client && client.readyState === WebSocket2.OPEN) {
|
|
1097
|
+
client.send(JSON.stringify(msg));
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
wss.on("connection", (ws) => {
|
|
1101
|
+
if (closeTimer) {
|
|
1102
|
+
clearTimeout(closeTimer);
|
|
1103
|
+
closeTimer = null;
|
|
1104
|
+
}
|
|
1105
|
+
client = ws;
|
|
1106
|
+
const payload = pendingInit ?? initPayload;
|
|
1107
|
+
if (payload) {
|
|
1108
|
+
sendToClient({ type: "review:init", payload });
|
|
1109
|
+
pendingInit = null;
|
|
1110
|
+
}
|
|
1111
|
+
ws.on("message", (data) => {
|
|
1112
|
+
try {
|
|
1113
|
+
const msg = JSON.parse(data.toString());
|
|
1114
|
+
if (msg.type === "review:submit" && submitCallback) {
|
|
1115
|
+
submitCallback(msg.payload);
|
|
1116
|
+
}
|
|
1117
|
+
} catch {
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
ws.on("close", () => {
|
|
1121
|
+
client = null;
|
|
1122
|
+
closeTimer = setTimeout(() => {
|
|
1123
|
+
closeTimer = null;
|
|
1124
|
+
}, 2e3);
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
return new Promise((resolve, reject) => {
|
|
1128
|
+
httpServer.on("error", reject);
|
|
1129
|
+
httpServer.listen(port, () => {
|
|
1130
|
+
resolve({
|
|
1131
|
+
port,
|
|
1132
|
+
sendInit(payload) {
|
|
1133
|
+
initPayload = payload;
|
|
1134
|
+
if (client && client.readyState === WebSocket2.OPEN) {
|
|
1135
|
+
sendToClient({ type: "review:init", payload });
|
|
1136
|
+
} else {
|
|
1137
|
+
pendingInit = payload;
|
|
1138
|
+
}
|
|
1139
|
+
},
|
|
1140
|
+
sendDiffUpdate(payload) {
|
|
1141
|
+
sendToClient({ type: "diff:update", payload });
|
|
1142
|
+
},
|
|
1143
|
+
sendContextUpdate(payload) {
|
|
1144
|
+
sendToClient({ type: "context:update", payload });
|
|
1145
|
+
},
|
|
1146
|
+
onSubmit(callback) {
|
|
1147
|
+
submitCallback = callback;
|
|
1148
|
+
},
|
|
1149
|
+
triggerRefresh() {
|
|
1150
|
+
callbacks.onRefreshRequest();
|
|
1151
|
+
},
|
|
1152
|
+
async close() {
|
|
1153
|
+
if (closeTimer) {
|
|
1154
|
+
clearTimeout(closeTimer);
|
|
1155
|
+
}
|
|
1156
|
+
for (const ws of wss.clients) {
|
|
1157
|
+
ws.close();
|
|
1158
|
+
}
|
|
1159
|
+
wss.close();
|
|
1160
|
+
await new Promise((resolve2) => {
|
|
1161
|
+
httpServer.close(() => resolve2());
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
});
|
|
1165
|
+
});
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// packages/core/src/watch.ts
|
|
1170
|
+
function hashDiff(rawDiff) {
|
|
1171
|
+
return createHash("sha256").update(rawDiff).digest("hex");
|
|
1172
|
+
}
|
|
1173
|
+
function detectChangedFiles(oldDiffSet, newDiffSet) {
|
|
1174
|
+
if (!oldDiffSet) {
|
|
1175
|
+
return newDiffSet.files.map((f) => f.path);
|
|
1176
|
+
}
|
|
1177
|
+
const oldFiles = new Map(
|
|
1178
|
+
oldDiffSet.files.map((f) => [f.path, f])
|
|
1179
|
+
);
|
|
1180
|
+
const changed = [];
|
|
1181
|
+
for (const newFile of newDiffSet.files) {
|
|
1182
|
+
const oldFile = oldFiles.get(newFile.path);
|
|
1183
|
+
if (!oldFile) {
|
|
1184
|
+
changed.push(newFile.path);
|
|
1185
|
+
} else if (oldFile.additions !== newFile.additions || oldFile.deletions !== newFile.deletions) {
|
|
1186
|
+
changed.push(newFile.path);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
for (const oldFile of oldDiffSet.files) {
|
|
1190
|
+
if (!newDiffSet.files.some((f) => f.path === oldFile.path)) {
|
|
1191
|
+
changed.push(oldFile.path);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
return changed;
|
|
1195
|
+
}
|
|
1196
|
+
async function startWatch(options) {
|
|
1197
|
+
const {
|
|
1198
|
+
diffRef,
|
|
1199
|
+
title,
|
|
1200
|
+
description,
|
|
1201
|
+
reasoning,
|
|
1202
|
+
cwd,
|
|
1203
|
+
silent,
|
|
1204
|
+
dev,
|
|
1205
|
+
pollInterval = 1e3
|
|
1206
|
+
} = options;
|
|
1207
|
+
const { diffSet: initialDiffSet, rawDiff: initialRawDiff } = getDiff(diffRef, { cwd });
|
|
1208
|
+
const currentBranch = getCurrentBranch({ cwd });
|
|
1209
|
+
const initialBriefing = analyze(initialDiffSet);
|
|
1210
|
+
let lastDiffHash = hashDiff(initialRawDiff);
|
|
1211
|
+
let lastDiffSet = initialDiffSet;
|
|
1212
|
+
const metadata = {
|
|
1213
|
+
title,
|
|
1214
|
+
description,
|
|
1215
|
+
reasoning,
|
|
1216
|
+
currentBranch
|
|
1217
|
+
};
|
|
1218
|
+
const [bridgePort, uiPort] = await Promise.all([
|
|
1219
|
+
getPort2(),
|
|
1220
|
+
getPort2()
|
|
1221
|
+
]);
|
|
1222
|
+
let refreshRequested = false;
|
|
1223
|
+
const bridge = await createWatchBridge(bridgePort, {
|
|
1224
|
+
onRefreshRequest: () => {
|
|
1225
|
+
refreshRequested = true;
|
|
1226
|
+
},
|
|
1227
|
+
onContextUpdate: (payload) => {
|
|
1228
|
+
if (payload.reasoning !== void 0) metadata.reasoning = payload.reasoning;
|
|
1229
|
+
if (payload.title !== void 0) metadata.title = payload.title;
|
|
1230
|
+
if (payload.description !== void 0) metadata.description = payload.description;
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
let httpServer = null;
|
|
1234
|
+
let viteServer = null;
|
|
1235
|
+
if (dev) {
|
|
1236
|
+
const uiRoot = resolveUiRoot();
|
|
1237
|
+
viteServer = await startViteDevServer(uiRoot, uiPort, !!silent);
|
|
1238
|
+
} else {
|
|
1239
|
+
const uiDist = resolveUiDist();
|
|
1240
|
+
httpServer = await createStaticServer(uiDist, uiPort);
|
|
1241
|
+
}
|
|
1242
|
+
writeWatchFile(cwd, {
|
|
1243
|
+
wsPort: bridgePort,
|
|
1244
|
+
uiPort,
|
|
1245
|
+
pid: process.pid,
|
|
1246
|
+
cwd: cwd ?? process.cwd(),
|
|
1247
|
+
diffRef,
|
|
1248
|
+
startedAt: Date.now()
|
|
1249
|
+
});
|
|
1250
|
+
const reviewId = "watch-session";
|
|
1251
|
+
const url = `http://localhost:${uiPort}?wsPort=${bridgePort}&reviewId=${reviewId}`;
|
|
1252
|
+
if (!silent) {
|
|
1253
|
+
console.log(`
|
|
1254
|
+
DiffPrism Watch: ${title ?? `watching ${diffRef}`}`);
|
|
1255
|
+
console.log(`Browser: ${url}`);
|
|
1256
|
+
console.log(`API: http://localhost:${bridgePort}`);
|
|
1257
|
+
console.log(`Polling every ${pollInterval}ms
|
|
1258
|
+
`);
|
|
1259
|
+
}
|
|
1260
|
+
await open2(url);
|
|
1261
|
+
const initPayload = {
|
|
1262
|
+
reviewId,
|
|
1263
|
+
diffSet: initialDiffSet,
|
|
1264
|
+
rawDiff: initialRawDiff,
|
|
1265
|
+
briefing: initialBriefing,
|
|
1266
|
+
metadata,
|
|
1267
|
+
watchMode: true
|
|
1268
|
+
};
|
|
1269
|
+
bridge.sendInit(initPayload);
|
|
1270
|
+
bridge.onSubmit((result) => {
|
|
1271
|
+
if (!silent) {
|
|
1272
|
+
console.log(`
|
|
1273
|
+
Review submitted: ${result.decision}`);
|
|
1274
|
+
if (result.comments.length > 0) {
|
|
1275
|
+
console.log(` ${result.comments.length} comment(s)`);
|
|
1276
|
+
}
|
|
1277
|
+
console.log("Continuing to watch...\n");
|
|
1278
|
+
}
|
|
1279
|
+
writeReviewResult(cwd, result);
|
|
1280
|
+
});
|
|
1281
|
+
let pollRunning = true;
|
|
1282
|
+
const pollLoop = setInterval(() => {
|
|
1283
|
+
if (!pollRunning) return;
|
|
1284
|
+
try {
|
|
1285
|
+
const { diffSet: newDiffSet, rawDiff: newRawDiff } = getDiff(diffRef, { cwd });
|
|
1286
|
+
const newHash = hashDiff(newRawDiff);
|
|
1287
|
+
if (newHash !== lastDiffHash || refreshRequested) {
|
|
1288
|
+
refreshRequested = false;
|
|
1289
|
+
const newBriefing = analyze(newDiffSet);
|
|
1290
|
+
const changedFiles = detectChangedFiles(lastDiffSet, newDiffSet);
|
|
1291
|
+
lastDiffHash = newHash;
|
|
1292
|
+
lastDiffSet = newDiffSet;
|
|
1293
|
+
bridge.sendInit({
|
|
1294
|
+
reviewId,
|
|
1295
|
+
diffSet: newDiffSet,
|
|
1296
|
+
rawDiff: newRawDiff,
|
|
1297
|
+
briefing: newBriefing,
|
|
1298
|
+
metadata,
|
|
1299
|
+
watchMode: true
|
|
1300
|
+
});
|
|
1301
|
+
const updatePayload = {
|
|
1302
|
+
diffSet: newDiffSet,
|
|
1303
|
+
rawDiff: newRawDiff,
|
|
1304
|
+
briefing: newBriefing,
|
|
1305
|
+
changedFiles,
|
|
1306
|
+
timestamp: Date.now()
|
|
1307
|
+
};
|
|
1308
|
+
bridge.sendDiffUpdate(updatePayload);
|
|
1309
|
+
if (!silent && changedFiles.length > 0) {
|
|
1310
|
+
console.log(
|
|
1311
|
+
`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Diff updated: ${changedFiles.length} file(s) changed`
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
} catch {
|
|
1316
|
+
}
|
|
1317
|
+
}, pollInterval);
|
|
1318
|
+
async function stop() {
|
|
1319
|
+
pollRunning = false;
|
|
1320
|
+
clearInterval(pollLoop);
|
|
1321
|
+
await bridge.close();
|
|
1322
|
+
if (viteServer) {
|
|
1323
|
+
await viteServer.close();
|
|
1324
|
+
}
|
|
1325
|
+
if (httpServer) {
|
|
1326
|
+
httpServer.close();
|
|
1327
|
+
}
|
|
1328
|
+
removeWatchFile(cwd);
|
|
1329
|
+
}
|
|
1330
|
+
function updateContext(payload) {
|
|
1331
|
+
if (payload.reasoning !== void 0) metadata.reasoning = payload.reasoning;
|
|
1332
|
+
if (payload.title !== void 0) metadata.title = payload.title;
|
|
1333
|
+
if (payload.description !== void 0) metadata.description = payload.description;
|
|
1334
|
+
bridge.sendContextUpdate(payload);
|
|
1335
|
+
}
|
|
1336
|
+
return { stop, updateContext };
|
|
1337
|
+
}
|
|
1338
|
+
|
|
932
1339
|
export {
|
|
933
|
-
startReview
|
|
1340
|
+
startReview,
|
|
1341
|
+
readWatchFile,
|
|
1342
|
+
readReviewResult,
|
|
1343
|
+
consumeReviewResult,
|
|
1344
|
+
startWatch
|
|
934
1345
|
};
|
package/dist/mcp-server.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
consumeReviewResult,
|
|
3
|
+
readReviewResult,
|
|
4
|
+
readWatchFile,
|
|
2
5
|
startReview
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-TYUDIWD6.js";
|
|
4
7
|
|
|
5
8
|
// packages/mcp-server/src/index.ts
|
|
6
9
|
import fs from "fs";
|
|
@@ -11,7 +14,7 @@ import { z } from "zod";
|
|
|
11
14
|
async function startMcpServer() {
|
|
12
15
|
const server = new McpServer({
|
|
13
16
|
name: "diffprism",
|
|
14
|
-
version: true ? "0.
|
|
17
|
+
version: true ? "0.13.0" : "0.0.0-dev"
|
|
15
18
|
});
|
|
16
19
|
server.tool(
|
|
17
20
|
"open_review",
|
|
@@ -61,6 +64,104 @@ async function startMcpServer() {
|
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
);
|
|
67
|
+
server.tool(
|
|
68
|
+
"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.",
|
|
70
|
+
{
|
|
71
|
+
reasoning: z.string().optional().describe("Agent reasoning about the current changes"),
|
|
72
|
+
title: z.string().optional().describe("Updated title for the review"),
|
|
73
|
+
description: z.string().optional().describe("Updated description of the changes")
|
|
74
|
+
},
|
|
75
|
+
async ({ reasoning, title, description }) => {
|
|
76
|
+
try {
|
|
77
|
+
const watchInfo = readWatchFile();
|
|
78
|
+
if (!watchInfo) {
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: "text",
|
|
83
|
+
text: "No DiffPrism watch session is running. Start one with `diffprism watch`."
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
};
|
|
87
|
+
}
|
|
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
|
+
const response = await fetch(
|
|
93
|
+
`http://localhost:${watchInfo.wsPort}/api/context`,
|
|
94
|
+
{
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: { "Content-Type": "application/json" },
|
|
97
|
+
body: JSON.stringify(payload)
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw new Error(`Watch server returned ${response.status}`);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
content: [
|
|
105
|
+
{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: "Context updated in DiffPrism watch session."
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
};
|
|
111
|
+
} catch (err) {
|
|
112
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: `Error updating watch context: ${message}`
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
isError: true
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
server.tool(
|
|
126
|
+
"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.",
|
|
128
|
+
{},
|
|
129
|
+
async () => {
|
|
130
|
+
try {
|
|
131
|
+
const data = readReviewResult();
|
|
132
|
+
if (!data) {
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{
|
|
136
|
+
type: "text",
|
|
137
|
+
text: "No pending review result."
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
consumeReviewResult();
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: JSON.stringify(data.result, null, 2)
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
};
|
|
151
|
+
} catch (err) {
|
|
152
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: "text",
|
|
157
|
+
text: `Error reading review result: ${message}`
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
isError: true
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
);
|
|
64
165
|
const transport = new StdioServerTransport();
|
|
65
166
|
await server.connect(transport);
|
|
66
167
|
}
|