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,28 +1,17 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __commonJS = (cb, mod) => function __require() {
8
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
- // If the importer is in node compatibility mode or this is not an ESM
20
- // file that has been converted to a CommonJS file using a Babel-
21
- // compatible transform (i.e. "__esModule" has not been set), then set
22
- // "default" to the CommonJS "module.exports" for node compatibility.
23
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
- mod
25
- ));
1
+ import {
2
+ getCurrentBranch,
3
+ getDiff,
4
+ listBranches,
5
+ listCommits,
6
+ parseDiff
7
+ } from "./chunk-QGWYCEJN.js";
8
+ import {
9
+ analyze
10
+ } from "./chunk-DHCVZGHE.js";
11
+ import {
12
+ __commonJS,
13
+ __toESM
14
+ } from "./chunk-JSBRDJBE.js";
26
15
 
27
16
  // node_modules/.pnpm/fast-content-type-parse@2.0.1/node_modules/fast-content-type-parse/index.js
28
17
  var require_fast_content_type_parse = __commonJS({
@@ -120,947 +109,15 @@ var require_fast_content_type_parse = __commonJS({
120
109
  }
121
110
  });
122
111
 
123
- // packages/git/src/local.ts
124
- import { execSync } from "child_process";
125
- import { readFileSync } from "fs";
126
- import path from "path";
127
- function getGitDiff(ref, options) {
128
- const cwd = options?.cwd ?? process.cwd();
129
- try {
130
- execSync("git --version", { cwd, stdio: "pipe" });
131
- } catch {
132
- throw new Error(
133
- "git is not available. Please install git and make sure it is on your PATH."
134
- );
135
- }
136
- try {
137
- execSync("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
138
- } catch {
139
- throw new Error(
140
- `The directory "${cwd}" is not inside a git repository.`
141
- );
142
- }
143
- let command;
144
- let includeUntracked = false;
145
- switch (ref) {
146
- case "staged":
147
- command = "git diff --staged --no-color";
148
- break;
149
- case "unstaged":
150
- command = "git diff --no-color";
151
- includeUntracked = true;
152
- break;
153
- case "all":
154
- command = "git diff HEAD --no-color";
155
- includeUntracked = true;
156
- break;
157
- default:
158
- command = `git diff --no-color ${ref}`;
159
- break;
160
- }
161
- let output;
162
- try {
163
- output = execSync(command, {
164
- cwd,
165
- encoding: "utf-8",
166
- maxBuffer: 50 * 1024 * 1024
167
- // 50 MB
168
- });
169
- } catch (err) {
170
- const message = err instanceof Error ? err.message : String(err);
171
- throw new Error(`git diff failed: ${message}`);
172
- }
173
- if (includeUntracked) {
174
- output += getUntrackedDiffs(cwd);
175
- }
176
- return output;
177
- }
178
- function getCurrentBranch(options) {
179
- const cwd = options?.cwd ?? process.cwd();
180
- try {
181
- return execSync("git rev-parse --abbrev-ref HEAD", {
182
- cwd,
183
- encoding: "utf-8",
184
- stdio: ["pipe", "pipe", "pipe"]
185
- }).trim();
186
- } catch {
187
- return "unknown";
188
- }
189
- }
190
- function listBranches(options) {
191
- const cwd = options?.cwd ?? process.cwd();
192
- try {
193
- const output = execSync(
194
- "git branch -a --format='%(refname:short)' --sort=-committerdate",
195
- { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
196
- ).trim();
197
- if (!output) return { local: [], remote: [] };
198
- const local = [];
199
- const remote = [];
200
- for (const line of output.split("\n")) {
201
- const name = line.trim();
202
- if (!name) continue;
203
- if (name.endsWith("/HEAD")) continue;
204
- if (name.includes("/")) {
205
- remote.push(name);
206
- } else {
207
- local.push(name);
208
- }
209
- }
210
- return { local, remote };
211
- } catch {
212
- return { local: [], remote: [] };
213
- }
214
- }
215
- function listCommits(options) {
216
- const cwd = options?.cwd ?? process.cwd();
217
- const limit = options?.limit ?? 50;
218
- try {
219
- const output = execSync(
220
- `git log --format='%H<<>>%h<<>>%s<<>>%an<<>>%aI' -n ${limit}`,
221
- { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
222
- ).trim();
223
- if (!output) return [];
224
- const commits = [];
225
- for (const line of output.split("\n")) {
226
- const parts = line.split("<<>>");
227
- if (parts.length < 5) continue;
228
- commits.push({
229
- hash: parts[0],
230
- shortHash: parts[1],
231
- subject: parts[2],
232
- author: parts[3],
233
- date: parts[4]
234
- });
235
- }
236
- return commits;
237
- } catch {
238
- return [];
239
- }
240
- }
241
- function detectWorktree(options) {
242
- const cwd = options?.cwd ?? process.cwd();
243
- try {
244
- const gitDir = execSync("git rev-parse --git-dir", {
245
- cwd,
246
- encoding: "utf-8",
247
- stdio: ["pipe", "pipe", "pipe"]
248
- }).trim();
249
- const gitCommonDir = execSync("git rev-parse --git-common-dir", {
250
- cwd,
251
- encoding: "utf-8",
252
- stdio: ["pipe", "pipe", "pipe"]
253
- }).trim();
254
- const resolvedGitDir = path.resolve(cwd, gitDir);
255
- const resolvedCommonDir = path.resolve(cwd, gitCommonDir);
256
- const isWorktree = resolvedGitDir !== resolvedCommonDir;
257
- if (!isWorktree) {
258
- return { isWorktree: false };
259
- }
260
- const worktreePath = execSync("git rev-parse --show-toplevel", {
261
- cwd,
262
- encoding: "utf-8",
263
- stdio: ["pipe", "pipe", "pipe"]
264
- }).trim();
265
- const mainWorktreePath = path.dirname(resolvedCommonDir);
266
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
267
- cwd,
268
- encoding: "utf-8",
269
- stdio: ["pipe", "pipe", "pipe"]
270
- }).trim();
271
- return {
272
- isWorktree: true,
273
- worktreePath,
274
- mainWorktreePath,
275
- branch: branch === "HEAD" ? void 0 : branch
276
- };
277
- } catch {
278
- return { isWorktree: false };
279
- }
280
- }
281
- function getUntrackedDiffs(cwd) {
282
- let untrackedList;
283
- try {
284
- untrackedList = execSync(
285
- "git ls-files --others --exclude-standard",
286
- { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
287
- ).trim();
288
- } catch {
289
- return "";
290
- }
291
- if (!untrackedList) return "";
292
- const files = untrackedList.split("\n");
293
- let result = "";
294
- for (const file of files) {
295
- const absPath = path.resolve(cwd, file);
296
- let content;
297
- try {
298
- content = readFileSync(absPath, "utf-8");
299
- } catch {
300
- continue;
301
- }
302
- const lines = content.split("\n");
303
- const hasTrailingNewline = content.length > 0 && content[content.length - 1] === "\n";
304
- const contentLines = hasTrailingNewline ? lines.slice(0, -1) : lines;
305
- result += `diff --git a/${file} b/${file}
306
- `;
307
- result += "new file mode 100644\n";
308
- result += "--- /dev/null\n";
309
- result += `+++ b/${file}
310
- `;
311
- result += `@@ -0,0 +1,${contentLines.length} @@
312
- `;
313
- for (const line of contentLines) {
314
- result += `+${line}
315
- `;
316
- }
317
- if (!hasTrailingNewline) {
318
- result += "\\n";
319
- }
320
- }
321
- return result;
322
- }
323
-
324
- // packages/git/src/parser.ts
325
- import path2 from "path";
326
- var EXTENSION_MAP = {
327
- ts: "typescript",
328
- tsx: "typescript",
329
- js: "javascript",
330
- jsx: "javascript",
331
- py: "python",
332
- rb: "ruby",
333
- go: "go",
334
- rs: "rust",
335
- java: "java",
336
- c: "c",
337
- h: "c",
338
- cpp: "cpp",
339
- hpp: "cpp",
340
- cc: "cpp",
341
- cs: "csharp",
342
- md: "markdown",
343
- json: "json",
344
- yaml: "yaml",
345
- yml: "yaml",
346
- html: "html",
347
- css: "css",
348
- scss: "scss",
349
- sql: "sql",
350
- sh: "shell",
351
- bash: "shell",
352
- zsh: "shell"
353
- };
354
- var FILENAME_MAP = {
355
- Dockerfile: "dockerfile",
356
- Makefile: "makefile"
357
- };
358
- function detectLanguage(filePath) {
359
- const basename = path2.basename(filePath);
360
- if (FILENAME_MAP[basename]) {
361
- return FILENAME_MAP[basename];
362
- }
363
- const ext = basename.includes(".") ? basename.slice(basename.lastIndexOf(".") + 1) : "";
364
- return EXTENSION_MAP[ext] ?? "text";
365
- }
366
- function stripPrefix(raw) {
367
- if (raw === "/dev/null") return raw;
368
- return raw.replace(/^[ab]\//, "");
369
- }
370
- function parseDiff(rawDiff, baseRef, headRef) {
371
- if (!rawDiff.trim()) {
372
- return { baseRef, headRef, files: [] };
373
- }
374
- const files = [];
375
- const lines = rawDiff.split("\n");
376
- let i = 0;
377
- while (i < lines.length) {
378
- if (!lines[i].startsWith("diff --git ")) {
379
- i++;
380
- continue;
381
- }
382
- let oldPath;
383
- let newPath;
384
- let status = "modified";
385
- let binary = false;
386
- let renameFrom;
387
- let renameTo;
388
- const diffLine = lines[i];
389
- const gitPathMatch = diffLine.match(/^diff --git a\/(.*) b\/(.*)$/);
390
- if (gitPathMatch) {
391
- oldPath = gitPathMatch[1];
392
- newPath = gitPathMatch[2];
393
- }
394
- i++;
395
- while (i < lines.length && !lines[i].startsWith("diff --git ")) {
396
- const line = lines[i];
397
- if (line.startsWith("--- ")) {
398
- const raw = line.slice(4);
399
- oldPath = stripPrefix(raw);
400
- if (raw === "/dev/null") {
401
- status = "added";
402
- }
403
- } else if (line.startsWith("+++ ")) {
404
- const raw = line.slice(4);
405
- newPath = stripPrefix(raw);
406
- if (raw === "/dev/null") {
407
- status = "deleted";
408
- }
409
- } else if (line.startsWith("rename from ")) {
410
- renameFrom = line.slice("rename from ".length);
411
- status = "renamed";
412
- } else if (line.startsWith("rename to ")) {
413
- renameTo = line.slice("rename to ".length);
414
- status = "renamed";
415
- } else if (line.startsWith("new file mode")) {
416
- status = "added";
417
- } else if (line.startsWith("deleted file mode")) {
418
- status = "deleted";
419
- } else if (line.startsWith("Binary files") || line === "GIT binary patch") {
420
- binary = true;
421
- if (line.includes("/dev/null") && line.includes(" and b/")) {
422
- status = "added";
423
- } else if (line.includes("a/") && line.includes("/dev/null")) {
424
- status = "deleted";
425
- }
426
- }
427
- if (line.startsWith("@@ ")) {
428
- break;
429
- }
430
- i++;
431
- }
432
- const filePath = status === "deleted" ? oldPath ?? newPath ?? "unknown" : newPath ?? oldPath ?? "unknown";
433
- const fileOldPath = status === "renamed" ? renameFrom ?? oldPath : oldPath;
434
- const hunks = [];
435
- let additions = 0;
436
- let deletions = 0;
437
- while (i < lines.length && !lines[i].startsWith("diff --git ")) {
438
- const line = lines[i];
439
- if (line.startsWith("@@ ")) {
440
- const hunkMatch = line.match(
441
- /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/
442
- );
443
- if (!hunkMatch) {
444
- i++;
445
- continue;
446
- }
447
- const oldStart = parseInt(hunkMatch[1], 10);
448
- const oldLines = hunkMatch[2] !== void 0 ? parseInt(hunkMatch[2], 10) : 1;
449
- const newStart = parseInt(hunkMatch[3], 10);
450
- const newLines = hunkMatch[4] !== void 0 ? parseInt(hunkMatch[4], 10) : 1;
451
- const changes = [];
452
- let oldLineNum = oldStart;
453
- let newLineNum = newStart;
454
- i++;
455
- while (i < lines.length) {
456
- const changeLine = lines[i];
457
- if (changeLine.startsWith("@@ ") || changeLine.startsWith("diff --git ")) {
458
- break;
459
- }
460
- if (changeLine.startsWith("\")) {
461
- i++;
462
- continue;
463
- }
464
- if (changeLine.startsWith("+")) {
465
- changes.push({
466
- type: "add",
467
- lineNumber: newLineNum,
468
- content: changeLine.slice(1)
469
- });
470
- newLineNum++;
471
- additions++;
472
- } else if (changeLine.startsWith("-")) {
473
- changes.push({
474
- type: "delete",
475
- lineNumber: oldLineNum,
476
- content: changeLine.slice(1)
477
- });
478
- oldLineNum++;
479
- deletions++;
480
- } else {
481
- changes.push({
482
- type: "context",
483
- lineNumber: newLineNum,
484
- content: changeLine.length > 0 ? changeLine.slice(1) : ""
485
- });
486
- oldLineNum++;
487
- newLineNum++;
488
- }
489
- i++;
490
- }
491
- hunks.push({ oldStart, oldLines, newStart, newLines, changes });
492
- } else {
493
- i++;
494
- }
495
- }
496
- const diffFile = {
497
- path: filePath,
498
- status,
499
- hunks,
500
- language: detectLanguage(filePath),
501
- binary,
502
- additions,
503
- deletions
504
- };
505
- if (status === "renamed" && fileOldPath) {
506
- diffFile.oldPath = fileOldPath;
507
- }
508
- files.push(diffFile);
509
- }
510
- return { baseRef, headRef, files };
511
- }
512
-
513
- // packages/git/src/index.ts
514
- function getDiff(ref, options) {
515
- if (ref === "working-copy") {
516
- return getWorkingCopyDiff(options);
517
- }
518
- const rawDiff = getGitDiff(ref, options);
519
- let baseRef;
520
- let headRef;
521
- if (ref === "staged") {
522
- baseRef = "HEAD";
523
- headRef = "staged";
524
- } else if (ref === "unstaged") {
525
- baseRef = "staged";
526
- headRef = "working tree";
527
- } else if (ref.includes("..")) {
528
- const [base, head] = ref.split("..");
529
- baseRef = base;
530
- headRef = head;
531
- } else {
532
- baseRef = ref;
533
- headRef = "HEAD";
534
- }
535
- const diffSet = parseDiff(rawDiff, baseRef, headRef);
536
- return { diffSet, rawDiff };
537
- }
538
- function getWorkingCopyDiff(options) {
539
- const stagedRaw = getGitDiff("staged", options);
540
- const unstagedRaw = getGitDiff("unstaged", options);
541
- const stagedDiffSet = parseDiff(stagedRaw, "HEAD", "staged");
542
- const unstagedDiffSet = parseDiff(unstagedRaw, "staged", "working tree");
543
- const stagedFiles = stagedDiffSet.files.map((f) => ({
544
- ...f,
545
- stage: "staged"
546
- }));
547
- const unstagedFiles = unstagedDiffSet.files.map((f) => ({
548
- ...f,
549
- stage: "unstaged"
550
- }));
551
- const rawDiff = [stagedRaw, unstagedRaw].filter(Boolean).join("");
552
- return {
553
- diffSet: {
554
- baseRef: "HEAD",
555
- headRef: "working tree",
556
- files: [...stagedFiles, ...unstagedFiles]
557
- },
558
- rawDiff
559
- };
560
- }
561
-
562
- // packages/analysis/src/deterministic.ts
563
- var MECHANICAL_CONFIG_PATTERNS = [
564
- /\.config\./,
565
- /\.eslintrc/,
566
- /\.prettierrc/,
567
- /tsconfig.*\.json$/,
568
- /\.gitignore$/,
569
- /\.lock$/
570
- ];
571
- var API_SURFACE_PATTERNS = [
572
- /\/api\//,
573
- /\/routes\//
574
- ];
575
- function isFormattingOnly(file) {
576
- if (file.hunks.length === 0) return false;
577
- for (const hunk of file.hunks) {
578
- const adds = hunk.changes.filter((c) => c.type === "add").map((c) => c.content.replace(/\s/g, ""));
579
- const deletes = hunk.changes.filter((c) => c.type === "delete").map((c) => c.content.replace(/\s/g, ""));
580
- if (adds.length === 0 || deletes.length === 0) return false;
581
- const deleteBag = [...deletes];
582
- for (const add of adds) {
583
- const idx = deleteBag.indexOf(add);
584
- if (idx === -1) return false;
585
- deleteBag.splice(idx, 1);
586
- }
587
- if (deleteBag.length > 0) return false;
588
- }
589
- return true;
590
- }
591
- function isImportOnly(file) {
592
- if (file.hunks.length === 0) return false;
593
- const importPattern = /^\s*(import\s|export\s.*from\s|const\s+\w+\s*=\s*require\(|require\()/;
594
- for (const hunk of file.hunks) {
595
- for (const change of hunk.changes) {
596
- if (change.type === "context") continue;
597
- const trimmed = change.content.trim();
598
- if (trimmed === "") continue;
599
- if (!importPattern.test(trimmed)) return false;
600
- }
601
- }
602
- return true;
603
- }
604
- function isMechanicalConfigFile(path8) {
605
- return MECHANICAL_CONFIG_PATTERNS.some((re) => re.test(path8));
606
- }
607
- function isApiSurface(file) {
608
- if (API_SURFACE_PATTERNS.some((re) => re.test(file.path))) return true;
609
- const basename = file.path.slice(file.path.lastIndexOf("/") + 1);
610
- if ((basename === "index.ts" || basename === "index.js") && file.additions >= 10) {
611
- return true;
612
- }
613
- return false;
614
- }
615
- function categorizeFiles(files) {
616
- const critical = [];
617
- const notable = [];
618
- const mechanical = [];
619
- const securityFlags = detectSecurityPatterns(files);
620
- const complexityScores = computeComplexityScores(files);
621
- const securityByFile = /* @__PURE__ */ new Map();
622
- for (const flag of securityFlags) {
623
- const existing = securityByFile.get(flag.file) || [];
624
- existing.push(flag);
625
- securityByFile.set(flag.file, existing);
626
- }
627
- const complexityByFile = /* @__PURE__ */ new Map();
628
- for (const score of complexityScores) {
629
- complexityByFile.set(score.path, score);
630
- }
631
- for (const file of files) {
632
- const description = `${file.status} (${file.language || "unknown"}) +${file.additions} -${file.deletions}`;
633
- const fileSecurityFlags = securityByFile.get(file.path);
634
- const fileComplexity = complexityByFile.get(file.path);
635
- const criticalReasons = [];
636
- if (fileSecurityFlags && fileSecurityFlags.length > 0) {
637
- const patterns = fileSecurityFlags.map((f) => f.pattern);
638
- const unique = [...new Set(patterns)];
639
- criticalReasons.push(`security patterns detected: ${unique.join(", ")}`);
640
- }
641
- if (fileComplexity && fileComplexity.score >= 8) {
642
- criticalReasons.push(`high complexity score (${fileComplexity.score}/10)`);
643
- }
644
- if (isApiSurface(file)) {
645
- criticalReasons.push("modifies public API surface");
646
- }
647
- if (criticalReasons.length > 0) {
648
- critical.push({
649
- file: file.path,
650
- description,
651
- reason: `Critical: ${criticalReasons.join("; ")}`
652
- });
653
- continue;
654
- }
655
- const isPureRename = file.status === "renamed" && file.additions === 0 && file.deletions === 0;
656
- if (isPureRename) {
657
- mechanical.push({
658
- file: file.path,
659
- description,
660
- reason: "Mechanical: pure rename with no content changes"
661
- });
662
- continue;
663
- }
664
- if (isFormattingOnly(file)) {
665
- mechanical.push({
666
- file: file.path,
667
- description,
668
- reason: "Mechanical: formatting/whitespace-only changes"
669
- });
670
- continue;
671
- }
672
- if (isMechanicalConfigFile(file.path)) {
673
- mechanical.push({
674
- file: file.path,
675
- description,
676
- reason: "Mechanical: config file change"
677
- });
678
- continue;
679
- }
680
- if (file.hunks.length > 0 && isImportOnly(file)) {
681
- mechanical.push({
682
- file: file.path,
683
- description,
684
- reason: "Mechanical: import/require-only changes"
685
- });
686
- continue;
687
- }
688
- notable.push({
689
- file: file.path,
690
- description,
691
- reason: "Notable: requires review"
692
- });
693
- }
694
- return { critical, notable, mechanical };
695
- }
696
- function computeFileStats(files) {
697
- return files.map((f) => ({
698
- path: f.path,
699
- language: f.language,
700
- status: f.status,
701
- additions: f.additions,
702
- deletions: f.deletions
703
- }));
704
- }
705
- function detectAffectedModules(files) {
706
- const dirs = /* @__PURE__ */ new Set();
707
- for (const f of files) {
708
- const lastSlash = f.path.lastIndexOf("/");
709
- if (lastSlash > 0) {
710
- dirs.add(f.path.slice(0, lastSlash));
711
- }
712
- }
713
- return [...dirs].sort();
714
- }
715
- var TEST_PATTERNS = [
716
- /\.test\./,
717
- /\.spec\./,
718
- /\/__tests__\//,
719
- /\/test\//
720
- ];
721
- function detectAffectedTests(files) {
722
- return files.filter((f) => TEST_PATTERNS.some((re) => re.test(f.path))).map((f) => f.path);
723
- }
724
- var DEPENDENCY_FIELDS = [
725
- '"dependencies"',
726
- '"devDependencies"',
727
- '"peerDependencies"',
728
- '"optionalDependencies"'
729
- ];
730
- function detectNewDependencies(files) {
731
- const deps = /* @__PURE__ */ new Set();
732
- const packageFiles = files.filter(
733
- (f) => f.path.endsWith("package.json") && f.hunks.length > 0
734
- );
735
- for (const file of packageFiles) {
736
- for (const hunk of file.hunks) {
737
- let inDependencyBlock = false;
738
- for (const change of hunk.changes) {
739
- const line = change.content;
740
- if (DEPENDENCY_FIELDS.some((field) => line.includes(field))) {
741
- inDependencyBlock = true;
742
- continue;
743
- }
744
- if (inDependencyBlock && line.trim().startsWith("}")) {
745
- inDependencyBlock = false;
746
- continue;
747
- }
748
- if (change.type === "add" && inDependencyBlock) {
749
- const match = line.match(/"([^"]+)"\s*:/);
750
- if (match) {
751
- deps.add(match[1]);
752
- }
753
- }
754
- }
755
- }
756
- }
757
- return [...deps].sort();
758
- }
759
- function generateSummary(files) {
760
- const totalFiles = files.length;
761
- const counts = {
762
- added: 0,
763
- modified: 0,
764
- deleted: 0,
765
- renamed: 0
766
- };
767
- let totalAdditions = 0;
768
- let totalDeletions = 0;
769
- for (const f of files) {
770
- counts[f.status]++;
771
- totalAdditions += f.additions;
772
- totalDeletions += f.deletions;
773
- }
774
- const parts = [];
775
- if (counts.modified > 0) parts.push(`${counts.modified} modified`);
776
- if (counts.added > 0) parts.push(`${counts.added} added`);
777
- if (counts.deleted > 0) parts.push(`${counts.deleted} deleted`);
778
- if (counts.renamed > 0) parts.push(`${counts.renamed} renamed`);
779
- const breakdown = parts.length > 0 ? `: ${parts.join(", ")}` : "";
780
- return `${totalFiles} files changed${breakdown} (+${totalAdditions} -${totalDeletions})`;
781
- }
782
- var BRANCH_PATTERN = /\b(if|else|switch|case|catch)\b|\?\s|&&|\|\|/;
783
- function computeComplexityScores(files) {
784
- const results = [];
785
- for (const file of files) {
786
- let score = 0;
787
- const factors = [];
788
- const totalChanges = file.additions + file.deletions;
789
- if (totalChanges > 100) {
790
- score += 3;
791
- factors.push(`large diff (+${file.additions} -${file.deletions})`);
792
- } else if (totalChanges > 50) {
793
- score += 2;
794
- factors.push(`medium diff (+${file.additions} -${file.deletions})`);
795
- } else if (totalChanges > 20) {
796
- score += 1;
797
- factors.push(`moderate diff (+${file.additions} -${file.deletions})`);
798
- }
799
- const hunkCount = file.hunks.length;
800
- if (hunkCount > 4) {
801
- score += 2;
802
- factors.push(`many hunks (${hunkCount})`);
803
- } else if (hunkCount > 2) {
804
- score += 1;
805
- factors.push(`multiple hunks (${hunkCount})`);
806
- }
807
- let branchCount = 0;
808
- let deepNestCount = 0;
809
- for (const hunk of file.hunks) {
810
- for (const change of hunk.changes) {
811
- if (change.type !== "add") continue;
812
- const line = change.content;
813
- if (BRANCH_PATTERN.test(line)) {
814
- branchCount++;
815
- }
816
- const leadingSpaces = line.match(/^(\s*)/);
817
- if (leadingSpaces) {
818
- const ws = leadingSpaces[1];
819
- const tabCount = (ws.match(/\t/g) || []).length;
820
- const spaceCount = ws.replace(/\t/g, "").length;
821
- if (tabCount >= 4 || spaceCount >= 16) {
822
- deepNestCount++;
823
- }
824
- }
825
- }
826
- }
827
- const branchScore = Math.floor(branchCount / 5);
828
- if (branchScore > 0) {
829
- score += branchScore;
830
- factors.push(`${branchCount} logic branches`);
831
- }
832
- const nestScore = Math.floor(deepNestCount / 5);
833
- if (nestScore > 0) {
834
- score += nestScore;
835
- factors.push(`${deepNestCount} deeply nested lines`);
836
- }
837
- score = Math.max(1, Math.min(10, score));
838
- results.push({ path: file.path, score, factors });
839
- }
840
- results.sort((a, b) => b.score - a.score);
841
- return results;
842
- }
843
- var NON_CODE_EXTENSIONS = /* @__PURE__ */ new Set([
844
- ".json",
845
- ".md",
846
- ".css",
847
- ".scss",
848
- ".less",
849
- ".svg",
850
- ".png",
851
- ".jpg",
852
- ".gif",
853
- ".ico",
854
- ".yaml",
855
- ".yml",
856
- ".toml",
857
- ".lock",
858
- ".html"
859
- ]);
860
- var CONFIG_PATTERNS = [
861
- /\.config\./,
862
- /\.rc\./,
863
- /eslint/,
864
- /prettier/,
865
- /tsconfig/,
866
- /tailwind/,
867
- /vite\.config/,
868
- /vitest\.config/
869
- ];
870
- function isTestFile(path8) {
871
- return TEST_PATTERNS.some((re) => re.test(path8));
872
- }
873
- function isNonCodeFile(path8) {
874
- const ext = path8.slice(path8.lastIndexOf("."));
875
- return NON_CODE_EXTENSIONS.has(ext);
876
- }
877
- function isConfigFile(path8) {
878
- return CONFIG_PATTERNS.some((re) => re.test(path8));
879
- }
880
- function detectTestCoverageGaps(files) {
881
- const filePaths = new Set(files.map((f) => f.path));
882
- const results = [];
883
- for (const file of files) {
884
- if (file.status !== "added" && file.status !== "modified") continue;
885
- if (isTestFile(file.path)) continue;
886
- if (isNonCodeFile(file.path)) continue;
887
- if (isConfigFile(file.path)) continue;
888
- const dir = file.path.slice(0, file.path.lastIndexOf("/") + 1);
889
- const basename = file.path.slice(file.path.lastIndexOf("/") + 1);
890
- const extDot = basename.lastIndexOf(".");
891
- const name = extDot > 0 ? basename.slice(0, extDot) : basename;
892
- const ext = extDot > 0 ? basename.slice(extDot) : "";
893
- const candidates = [
894
- `${dir}${name}.test${ext}`,
895
- `${dir}${name}.spec${ext}`,
896
- `${dir}__tests__/${name}${ext}`,
897
- `${dir}__tests__/${name}.test${ext}`,
898
- `${dir}__tests__/${name}.spec${ext}`
899
- ];
900
- const matchedTest = candidates.find((c) => filePaths.has(c));
901
- results.push({
902
- sourceFile: file.path,
903
- testFile: matchedTest ?? null
904
- });
905
- }
906
- return results;
907
- }
908
- var PATTERN_MATCHERS = [
909
- { pattern: "todo", test: (l) => /\btodo\b/i.test(l) },
910
- { pattern: "fixme", test: (l) => /\bfixme\b/i.test(l) },
911
- { pattern: "hack", test: (l) => /\bhack\b/i.test(l) },
912
- {
913
- pattern: "console",
914
- test: (l) => /\bconsole\.(log|debug|warn|error)\b/.test(l)
915
- },
916
- { pattern: "debug", test: (l) => /\bdebugger\b/.test(l) },
917
- {
918
- pattern: "disabled_test",
919
- test: (l) => /\.(skip)\(|(\bxit|xdescribe|xtest)\(/.test(l)
920
- }
921
- ];
922
- function detectPatterns(files) {
923
- const results = [];
924
- for (const file of files) {
925
- if (file.status === "added" && file.additions > 500) {
926
- results.push({
927
- file: file.path,
928
- line: 0,
929
- pattern: "large_file",
930
- content: `Large added file: ${file.additions} lines`
931
- });
932
- }
933
- for (const hunk of file.hunks) {
934
- for (const change of hunk.changes) {
935
- if (change.type !== "add") continue;
936
- for (const matcher of PATTERN_MATCHERS) {
937
- if (matcher.test(change.content)) {
938
- results.push({
939
- file: file.path,
940
- line: change.lineNumber,
941
- pattern: matcher.pattern,
942
- content: change.content.trim()
943
- });
944
- }
945
- }
946
- }
947
- }
948
- }
949
- results.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
950
- return results;
951
- }
952
- var SECURITY_MATCHERS = [
953
- {
954
- pattern: "eval",
955
- severity: "critical",
956
- test: (l) => /\beval\s*\(/.test(l)
957
- },
958
- {
959
- pattern: "inner_html",
960
- severity: "warning",
961
- test: (l) => /\.innerHTML\b|dangerouslySetInnerHTML/.test(l)
962
- },
963
- {
964
- pattern: "sql_injection",
965
- severity: "critical",
966
- test: (l) => /`[^`]*\b(SELECT|INSERT|UPDATE|DELETE)\b/i.test(l) || /\b(SELECT|INSERT|UPDATE|DELETE)\b[^`]*\$\{/i.test(l)
967
- },
968
- {
969
- pattern: "exec",
970
- severity: "critical",
971
- test: (l) => /child_process/.test(l) || /\bexec\s*\(/.test(l) || /\bexecSync\s*\(/.test(l)
972
- },
973
- {
974
- pattern: "hardcoded_secret",
975
- severity: "critical",
976
- test: (l) => /\b(token|secret|api_key|apikey|password|passwd|credential)\s*=\s*["']/i.test(l)
977
- },
978
- {
979
- pattern: "insecure_url",
980
- severity: "warning",
981
- test: (l) => /http:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)/.test(l)
982
- }
983
- ];
984
- function detectSecurityPatterns(files) {
985
- const results = [];
986
- for (const file of files) {
987
- for (const hunk of file.hunks) {
988
- for (const change of hunk.changes) {
989
- if (change.type !== "add") continue;
990
- for (const matcher of SECURITY_MATCHERS) {
991
- if (matcher.test(change.content)) {
992
- results.push({
993
- file: file.path,
994
- line: change.lineNumber,
995
- pattern: matcher.pattern,
996
- content: change.content.trim(),
997
- severity: matcher.severity
998
- });
999
- }
1000
- }
1001
- }
1002
- }
1003
- }
1004
- results.sort((a, b) => {
1005
- const severityOrder = { critical: 0, warning: 1 };
1006
- const aSev = severityOrder[a.severity];
1007
- const bSev = severityOrder[b.severity];
1008
- if (aSev !== bSev) return aSev - bSev;
1009
- return a.file.localeCompare(b.file) || a.line - b.line;
1010
- });
1011
- return results;
1012
- }
1013
-
1014
- // packages/analysis/src/index.ts
1015
- function analyze(diffSet) {
1016
- const { files } = diffSet;
1017
- const triage = categorizeFiles(files);
1018
- const fileStats = computeFileStats(files);
1019
- const affectedModules = detectAffectedModules(files);
1020
- const affectedTests = detectAffectedTests(files);
1021
- const newDependencies = detectNewDependencies(files);
1022
- const summary = generateSummary(files);
1023
- const complexity = computeComplexityScores(files);
1024
- const testCoverage = detectTestCoverageGaps(files);
1025
- const codePatterns = detectPatterns(files);
1026
- const securityPatterns = detectSecurityPatterns(files);
1027
- const patterns = [...securityPatterns, ...codePatterns];
1028
- return {
1029
- summary,
1030
- triage,
1031
- impact: {
1032
- affectedModules,
1033
- affectedTests,
1034
- publicApiChanges: false,
1035
- breakingChanges: [],
1036
- newDependencies
1037
- },
1038
- verification: {
1039
- testsPass: null,
1040
- typeCheck: null,
1041
- lintClean: null
1042
- },
1043
- fileStats,
1044
- complexity,
1045
- testCoverage,
1046
- patterns
1047
- };
1048
- }
1049
-
1050
- // packages/core/src/watch-file.ts
112
+ // packages/core/src/server-file.ts
1051
113
  import fs from "fs";
1052
- import path3 from "path";
1053
- import { execSync as execSync2 } from "child_process";
1054
- function findGitRoot(cwd) {
1055
- const root = execSync2("git rev-parse --show-toplevel", {
1056
- cwd: cwd ?? process.cwd(),
1057
- encoding: "utf-8"
1058
- }).trim();
1059
- return root;
114
+ import path from "path";
115
+ import os from "os";
116
+ function serverDir() {
117
+ return path.join(os.homedir(), ".diffprism");
1060
118
  }
1061
- function watchFilePath(cwd) {
1062
- const gitRoot = findGitRoot(cwd);
1063
- return path3.join(gitRoot, ".diffprism", "watch.json");
119
+ function serverFilePath() {
120
+ return path.join(serverDir(), "server.json");
1064
121
  }
1065
122
  function isPidAlive(pid) {
1066
123
  try {
@@ -1070,16 +127,15 @@ function isPidAlive(pid) {
1070
127
  return false;
1071
128
  }
1072
129
  }
1073
- function writeWatchFile(cwd, info) {
1074
- const filePath = watchFilePath(cwd);
1075
- const dir = path3.dirname(filePath);
130
+ function writeServerFile(info) {
131
+ const dir = serverDir();
1076
132
  if (!fs.existsSync(dir)) {
1077
133
  fs.mkdirSync(dir, { recursive: true });
1078
134
  }
1079
- fs.writeFileSync(filePath, JSON.stringify(info, null, 2) + "\n");
135
+ fs.writeFileSync(serverFilePath(), JSON.stringify(info, null, 2) + "\n");
1080
136
  }
1081
- function readWatchFile(cwd) {
1082
- const filePath = watchFilePath(cwd);
137
+ function readServerFile() {
138
+ const filePath = serverFilePath();
1083
139
  if (!fs.existsSync(filePath)) {
1084
140
  return null;
1085
141
  }
@@ -1095,305 +151,202 @@ function readWatchFile(cwd) {
1095
151
  return null;
1096
152
  }
1097
153
  }
1098
- function removeWatchFile(cwd) {
154
+ function removeServerFile() {
1099
155
  try {
1100
- const filePath = watchFilePath(cwd);
156
+ const filePath = serverFilePath();
1101
157
  if (fs.existsSync(filePath)) {
1102
158
  fs.unlinkSync(filePath);
1103
159
  }
1104
160
  } catch {
1105
161
  }
1106
162
  }
1107
- function reviewResultPath(cwd) {
1108
- const gitRoot = findGitRoot(cwd);
1109
- return path3.join(gitRoot, ".diffprism", "last-review.json");
1110
- }
1111
- function writeReviewResult(cwd, result) {
1112
- const filePath = reviewResultPath(cwd);
1113
- const dir = path3.dirname(filePath);
1114
- if (!fs.existsSync(dir)) {
1115
- fs.mkdirSync(dir, { recursive: true });
1116
- }
1117
- const data = {
1118
- result,
1119
- timestamp: Date.now(),
1120
- consumed: false
1121
- };
1122
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
1123
- }
1124
- function readReviewResult(cwd) {
1125
- try {
1126
- const filePath = reviewResultPath(cwd);
1127
- if (!fs.existsSync(filePath)) {
1128
- return null;
1129
- }
1130
- const raw = fs.readFileSync(filePath, "utf-8");
1131
- const data = JSON.parse(raw);
1132
- if (data.consumed) {
1133
- return null;
1134
- }
1135
- return data;
1136
- } catch {
163
+ async function isServerAlive() {
164
+ const info = readServerFile();
165
+ if (!info) {
1137
166
  return null;
1138
167
  }
1139
- }
1140
- function consumeReviewResult(cwd) {
1141
168
  try {
1142
- const filePath = reviewResultPath(cwd);
1143
- if (!fs.existsSync(filePath)) {
1144
- return;
169
+ const response = await fetch(`http://localhost:${info.httpPort}/api/status`, {
170
+ signal: AbortSignal.timeout(2e3)
171
+ });
172
+ if (response.ok) {
173
+ return info;
1145
174
  }
1146
- const raw = fs.readFileSync(filePath, "utf-8");
1147
- const data = JSON.parse(raw);
1148
- data.consumed = true;
1149
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
175
+ return null;
1150
176
  } catch {
177
+ removeServerFile();
178
+ return null;
1151
179
  }
1152
180
  }
1153
181
 
1154
- // packages/core/src/pipeline.ts
1155
- import getPort from "get-port";
1156
- import open from "open";
1157
-
1158
- // packages/core/src/review-history.ts
182
+ // packages/core/src/server-client.ts
183
+ import { spawn } from "child_process";
1159
184
  import fs2 from "fs";
1160
- import path4 from "path";
1161
- import { randomUUID } from "crypto";
1162
- function generateEntryId() {
1163
- return randomUUID();
1164
- }
1165
- function getHistoryPath(projectDir) {
1166
- return path4.join(projectDir, ".diffprism", "history", "reviews.json");
1167
- }
1168
- function readHistory(projectDir) {
1169
- const filePath = getHistoryPath(projectDir);
1170
- if (!fs2.existsSync(filePath)) {
1171
- return { version: 1, entries: [] };
1172
- }
1173
- try {
1174
- const raw = fs2.readFileSync(filePath, "utf-8");
1175
- const parsed = JSON.parse(raw);
1176
- return parsed;
1177
- } catch {
1178
- return { version: 1, entries: [] };
1179
- }
1180
- }
1181
- function appendHistory(projectDir, entry) {
1182
- const filePath = getHistoryPath(projectDir);
1183
- const dir = path4.dirname(filePath);
1184
- if (!fs2.existsSync(dir)) {
1185
- fs2.mkdirSync(dir, { recursive: true });
185
+ import path2 from "path";
186
+ import os2 from "os";
187
+ import { fileURLToPath } from "url";
188
+ async function ensureServer(options = {}) {
189
+ const existing = await isServerAlive();
190
+ if (existing) {
191
+ return existing;
192
+ }
193
+ const spawnArgs = options.spawnCommand ?? buildDefaultSpawnCommand(options);
194
+ const logDir = path2.join(os2.homedir(), ".diffprism");
195
+ if (!fs2.existsSync(logDir)) {
196
+ fs2.mkdirSync(logDir, { recursive: true });
197
+ }
198
+ const logPath = path2.join(logDir, "server.log");
199
+ const logFd = fs2.openSync(logPath, "a");
200
+ const [cmd, ...args] = spawnArgs;
201
+ const child = spawn(cmd, args, {
202
+ detached: true,
203
+ stdio: ["ignore", logFd, logFd],
204
+ env: { ...process.env }
205
+ });
206
+ child.unref();
207
+ fs2.closeSync(logFd);
208
+ const timeoutMs = options.timeoutMs ?? 15e3;
209
+ const startTime = Date.now();
210
+ while (Date.now() - startTime < timeoutMs) {
211
+ await new Promise((resolve) => setTimeout(resolve, 500));
212
+ const info = await isServerAlive();
213
+ if (info) {
214
+ return info;
215
+ }
1186
216
  }
1187
- const history = readHistory(projectDir);
1188
- history.entries.push(entry);
1189
- history.entries.sort((a, b) => a.timestamp - b.timestamp);
1190
- fs2.writeFileSync(filePath, JSON.stringify(history, null, 2) + "\n");
1191
- }
1192
- function getRecentHistory(projectDir, limit = 50) {
1193
- const history = readHistory(projectDir);
1194
- return history.entries.slice(-limit);
217
+ throw new Error(
218
+ `DiffPrism server failed to start within ${timeoutMs / 1e3}s. Check logs at ${logPath}`
219
+ );
1195
220
  }
1196
-
1197
- // packages/core/src/watch-bridge.ts
1198
- import http from "http";
1199
- import { WebSocketServer, WebSocket } from "ws";
1200
- function createWatchBridge(port, callbacks) {
1201
- let client = null;
1202
- let initPayload = null;
1203
- let pendingInit = null;
1204
- let closeTimer = null;
1205
- let submitCallback = null;
1206
- let resultReject = null;
1207
- const httpServer = http.createServer(async (req, res) => {
1208
- res.setHeader("Access-Control-Allow-Origin", "*");
1209
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1210
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
1211
- if (req.method === "OPTIONS") {
1212
- res.writeHead(204);
1213
- res.end();
1214
- return;
1215
- }
1216
- if (req.method === "GET" && req.url === "/api/status") {
1217
- res.writeHead(200, { "Content-Type": "application/json" });
1218
- res.end(JSON.stringify({ running: true, pid: process.pid }));
1219
- return;
1220
- }
1221
- if (req.method === "POST" && req.url === "/api/context") {
1222
- let body = "";
1223
- req.on("data", (chunk) => {
1224
- body += chunk.toString();
1225
- });
1226
- req.on("end", () => {
1227
- try {
1228
- const payload = JSON.parse(body);
1229
- callbacks.onContextUpdate(payload);
1230
- sendToClient({ type: "context:update", payload });
1231
- res.writeHead(200, { "Content-Type": "application/json" });
1232
- res.end(JSON.stringify({ ok: true }));
1233
- } catch {
1234
- res.writeHead(400, { "Content-Type": "application/json" });
1235
- res.end(JSON.stringify({ error: "Invalid JSON" }));
1236
- }
1237
- });
1238
- return;
1239
- }
1240
- if (req.method === "POST" && req.url === "/api/refresh") {
1241
- callbacks.onRefreshRequest();
1242
- res.writeHead(200, { "Content-Type": "application/json" });
1243
- res.end(JSON.stringify({ ok: true }));
1244
- return;
1245
- }
1246
- const pathname = (req.url ?? "").split("?")[0];
1247
- if (req.method === "GET" && (pathname === "/api/refs" || /^\/api\/reviews\/[^/]+\/refs$/.test(pathname))) {
1248
- if (callbacks.onRefsRequest) {
1249
- const refsPayload = await callbacks.onRefsRequest();
1250
- if (refsPayload) {
1251
- res.writeHead(200, { "Content-Type": "application/json" });
1252
- res.end(JSON.stringify(refsPayload));
1253
- } else {
1254
- res.writeHead(500, { "Content-Type": "application/json" });
1255
- res.end(JSON.stringify({ error: "Failed to list git refs" }));
1256
- }
1257
- } else {
1258
- res.writeHead(404);
1259
- res.end("Not found");
1260
- }
1261
- return;
1262
- }
1263
- if (req.method === "POST" && (pathname === "/api/compare" || /^\/api\/reviews\/[^/]+\/compare$/.test(pathname))) {
1264
- if (callbacks.onCompareRequest) {
1265
- let body = "";
1266
- req.on("data", (chunk) => {
1267
- body += chunk.toString();
1268
- });
1269
- req.on("end", async () => {
1270
- try {
1271
- const { ref } = JSON.parse(body);
1272
- if (!ref) {
1273
- res.writeHead(400, { "Content-Type": "application/json" });
1274
- res.end(JSON.stringify({ error: "Missing ref" }));
1275
- return;
1276
- }
1277
- const success = await callbacks.onCompareRequest(ref);
1278
- if (success) {
1279
- res.writeHead(200, { "Content-Type": "application/json" });
1280
- res.end(JSON.stringify({ ok: true }));
1281
- } else {
1282
- res.writeHead(400, { "Content-Type": "application/json" });
1283
- res.end(JSON.stringify({ error: "Failed to compute diff" }));
1284
- }
1285
- } catch {
1286
- res.writeHead(400, { "Content-Type": "application/json" });
1287
- res.end(JSON.stringify({ error: "Invalid JSON" }));
1288
- }
1289
- });
1290
- } else {
1291
- res.writeHead(404);
1292
- res.end("Not found");
221
+ function buildDefaultSpawnCommand(options) {
222
+ const thisFile = fileURLToPath(import.meta.url);
223
+ const thisDir = path2.dirname(thisFile);
224
+ const workspaceRoot = path2.resolve(thisDir, "..", "..", "..");
225
+ const devBin = path2.join(workspaceRoot, "cli", "bin", "diffprism.mjs");
226
+ let binPath = "diffprism";
227
+ if (fs2.existsSync(devBin)) {
228
+ binPath = devBin;
229
+ } else {
230
+ let searchDir = thisDir;
231
+ while (searchDir !== path2.dirname(searchDir)) {
232
+ const candidate = path2.join(
233
+ searchDir,
234
+ "node_modules",
235
+ ".bin",
236
+ "diffprism"
237
+ );
238
+ if (fs2.existsSync(candidate)) {
239
+ binPath = candidate;
240
+ break;
1293
241
  }
1294
- return;
1295
- }
1296
- res.writeHead(404);
1297
- res.end("Not found");
1298
- });
1299
- const wss2 = new WebSocketServer({ server: httpServer });
1300
- function sendToClient(msg) {
1301
- if (client && client.readyState === WebSocket.OPEN) {
1302
- client.send(JSON.stringify(msg));
242
+ searchDir = path2.dirname(searchDir);
1303
243
  }
1304
244
  }
1305
- wss2.on("connection", (ws) => {
1306
- if (closeTimer) {
1307
- clearTimeout(closeTimer);
1308
- closeTimer = null;
1309
- }
1310
- client = ws;
1311
- const payload = pendingInit ?? initPayload;
1312
- if (payload) {
1313
- sendToClient({ type: "review:init", payload });
1314
- pendingInit = null;
1315
- }
1316
- ws.on("message", (data) => {
1317
- try {
1318
- const msg = JSON.parse(data.toString());
1319
- if (msg.type === "review:submit" && submitCallback) {
1320
- submitCallback(msg.payload);
1321
- } else if (msg.type === "diff:change_ref" && callbacks.onDiffRefChange) {
1322
- callbacks.onDiffRefChange(msg.payload.diffRef);
1323
- }
1324
- } catch {
1325
- }
1326
- });
1327
- ws.on("close", () => {
1328
- client = null;
1329
- if (resultReject) {
1330
- closeTimer = setTimeout(() => {
1331
- closeTimer = null;
1332
- if (resultReject) {
1333
- resultReject(new Error("Browser closed before review was submitted"));
1334
- resultReject = null;
1335
- submitCallback = null;
1336
- }
1337
- }, 2e3);
1338
- } else {
1339
- closeTimer = setTimeout(() => {
1340
- closeTimer = null;
1341
- }, 2e3);
1342
- }
1343
- });
1344
- });
1345
- return new Promise((resolve, reject) => {
1346
- httpServer.on("error", reject);
1347
- httpServer.listen(port, () => {
1348
- resolve({
1349
- port,
1350
- sendInit(payload) {
1351
- initPayload = payload;
1352
- if (client && client.readyState === WebSocket.OPEN) {
1353
- sendToClient({ type: "review:init", payload });
1354
- } else {
1355
- pendingInit = payload;
1356
- }
1357
- },
1358
- storeInitPayload(payload) {
1359
- initPayload = payload;
1360
- },
1361
- sendDiffUpdate(payload) {
1362
- sendToClient({ type: "diff:update", payload });
1363
- },
1364
- sendContextUpdate(payload) {
1365
- sendToClient({ type: "context:update", payload });
1366
- },
1367
- sendDiffError(payload) {
1368
- sendToClient({ type: "diff:error", payload });
1369
- },
1370
- onSubmit(callback) {
1371
- submitCallback = callback;
1372
- },
1373
- waitForResult() {
1374
- return new Promise((resolve2, reject2) => {
1375
- submitCallback = resolve2;
1376
- resultReject = reject2;
1377
- });
1378
- },
1379
- triggerRefresh() {
1380
- callbacks.onRefreshRequest();
245
+ const args = [process.execPath, binPath, "server", "--_daemon"];
246
+ if (options.dev) {
247
+ args.push("--dev");
248
+ }
249
+ return args;
250
+ }
251
+ async function submitReviewToServer(serverInfo, diffRef, options = {}) {
252
+ const cwd = options.cwd ?? process.cwd();
253
+ const projectPath = options.projectPath ?? cwd;
254
+ let payload;
255
+ if (options.injectedPayload) {
256
+ payload = options.injectedPayload;
257
+ } else {
258
+ const { getDiff: getDiff2, getCurrentBranch: getCurrentBranch2, detectWorktree } = await import("./src-AMCPIYDZ.js");
259
+ const { analyze: analyze2 } = await import("./src-JMPTSU3P.js");
260
+ const { diffSet, rawDiff } = getDiff2(diffRef, { cwd });
261
+ if (diffSet.files.length === 0) {
262
+ return {
263
+ result: {
264
+ decision: "approved",
265
+ comments: [],
266
+ summary: "No changes to review."
1381
267
  },
1382
- async close() {
1383
- if (closeTimer) {
1384
- clearTimeout(closeTimer);
1385
- }
1386
- for (const ws of wss2.clients) {
1387
- ws.close();
1388
- }
1389
- wss2.close();
1390
- await new Promise((resolve2) => {
1391
- httpServer.close(() => resolve2());
1392
- });
268
+ sessionId: ""
269
+ };
270
+ }
271
+ const briefing = analyze2(diffSet);
272
+ const currentBranch = getCurrentBranch2({ cwd });
273
+ const worktreeInfo = detectWorktree({ cwd });
274
+ payload = {
275
+ reviewId: "",
276
+ // Server assigns the real ID
277
+ diffSet,
278
+ rawDiff,
279
+ briefing,
280
+ metadata: {
281
+ title: options.title,
282
+ description: options.description,
283
+ reasoning: options.reasoning,
284
+ currentBranch,
285
+ worktree: worktreeInfo.isWorktree ? {
286
+ isWorktree: true,
287
+ worktreePath: worktreeInfo.worktreePath,
288
+ mainWorktreePath: worktreeInfo.mainWorktreePath
289
+ } : void 0
290
+ }
291
+ };
292
+ }
293
+ const createResponse = await fetch(
294
+ `http://localhost:${serverInfo.httpPort}/api/reviews`,
295
+ {
296
+ method: "POST",
297
+ headers: { "Content-Type": "application/json" },
298
+ body: JSON.stringify({
299
+ payload,
300
+ projectPath,
301
+ diffRef: options.diffRef ?? diffRef
302
+ })
303
+ }
304
+ );
305
+ if (!createResponse.ok) {
306
+ throw new Error(
307
+ `Global server returned ${createResponse.status} on create`
308
+ );
309
+ }
310
+ const { sessionId } = await createResponse.json();
311
+ if (options.annotations?.length) {
312
+ for (const ann of options.annotations) {
313
+ await fetch(
314
+ `http://localhost:${serverInfo.httpPort}/api/reviews/${sessionId}/annotations`,
315
+ {
316
+ method: "POST",
317
+ headers: { "Content-Type": "application/json" },
318
+ body: JSON.stringify({
319
+ file: ann.file,
320
+ line: ann.line,
321
+ body: ann.body,
322
+ type: ann.type,
323
+ confidence: ann.confidence ?? 1,
324
+ category: ann.category ?? "other",
325
+ source: {
326
+ agent: ann.source_agent ?? "unknown",
327
+ tool: "open_review"
328
+ }
329
+ })
1393
330
  }
1394
- });
1395
- });
1396
- });
331
+ );
332
+ }
333
+ }
334
+ const pollIntervalMs = 2e3;
335
+ const maxWaitMs = options.timeoutMs ?? 6e5;
336
+ const start = Date.now();
337
+ while (Date.now() - start < maxWaitMs) {
338
+ const resultResponse = await fetch(
339
+ `http://localhost:${serverInfo.httpPort}/api/reviews/${sessionId}/result`
340
+ );
341
+ if (resultResponse.ok) {
342
+ const data = await resultResponse.json();
343
+ if (data.result) {
344
+ return { result: data.result, sessionId };
345
+ }
346
+ }
347
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
348
+ }
349
+ throw new Error("Review timed out waiting for submission.");
1397
350
  }
1398
351
 
1399
352
  // packages/core/src/diff-utils.ts
@@ -1494,32 +447,18 @@ function createDiffPoller(options) {
1494
447
  };
1495
448
  }
1496
449
 
1497
- // packages/core/src/review-manager.ts
1498
- var sessions = /* @__PURE__ */ new Map();
1499
- var idCounter = 0;
1500
- function createSession(options) {
1501
- const id = `review-${Date.now()}-${++idCounter}`;
1502
- const session = {
1503
- id,
1504
- options,
1505
- status: "pending",
1506
- createdAt: Date.now()
1507
- };
1508
- sessions.set(id, session);
1509
- return session;
1510
- }
1511
- function updateSession(id, update) {
1512
- const session = sessions.get(id);
1513
- if (session) {
1514
- Object.assign(session, update);
1515
- }
1516
- }
450
+ // packages/core/src/global-server.ts
451
+ import http2 from "http";
452
+ import { randomUUID as randomUUID2 } from "crypto";
453
+ import getPort from "get-port";
454
+ import open from "open";
455
+ import { WebSocketServer, WebSocket } from "ws";
1517
456
 
1518
457
  // packages/core/src/ui-server.ts
1519
- import http2 from "http";
458
+ import http from "http";
1520
459
  import fs3 from "fs";
1521
- import path5 from "path";
1522
- import { fileURLToPath } from "url";
460
+ import path3 from "path";
461
+ import { fileURLToPath as fileURLToPath2 } from "url";
1523
462
  var MIME_TYPES = {
1524
463
  ".html": "text/html",
1525
464
  ".js": "application/javascript",
@@ -1532,15 +471,15 @@ var MIME_TYPES = {
1532
471
  ".woff2": "font/woff2"
1533
472
  };
1534
473
  function resolveUiDist() {
1535
- const thisFile = fileURLToPath(import.meta.url);
1536
- const thisDir = path5.dirname(thisFile);
1537
- const publishedUiDist = path5.resolve(thisDir, "..", "ui-dist");
1538
- if (fs3.existsSync(path5.join(publishedUiDist, "index.html"))) {
474
+ const thisFile = fileURLToPath2(import.meta.url);
475
+ const thisDir = path3.dirname(thisFile);
476
+ const publishedUiDist = path3.resolve(thisDir, "..", "ui-dist");
477
+ if (fs3.existsSync(path3.join(publishedUiDist, "index.html"))) {
1539
478
  return publishedUiDist;
1540
479
  }
1541
- const workspaceRoot = path5.resolve(thisDir, "..", "..", "..");
1542
- const devUiDist = path5.join(workspaceRoot, "packages", "ui", "dist");
1543
- if (fs3.existsSync(path5.join(devUiDist, "index.html"))) {
480
+ const workspaceRoot = path3.resolve(thisDir, "..", "..", "..");
481
+ const devUiDist = path3.join(workspaceRoot, "packages", "ui", "dist");
482
+ if (fs3.existsSync(path3.join(devUiDist, "index.html"))) {
1544
483
  return devUiDist;
1545
484
  }
1546
485
  throw new Error(
@@ -1548,11 +487,11 @@ function resolveUiDist() {
1548
487
  );
1549
488
  }
1550
489
  function resolveUiRoot() {
1551
- const thisFile = fileURLToPath(import.meta.url);
1552
- const thisDir = path5.dirname(thisFile);
1553
- const workspaceRoot = path5.resolve(thisDir, "..", "..", "..");
1554
- const uiRoot = path5.join(workspaceRoot, "packages", "ui");
1555
- if (fs3.existsSync(path5.join(uiRoot, "index.html"))) {
490
+ const thisFile = fileURLToPath2(import.meta.url);
491
+ const thisDir = path3.dirname(thisFile);
492
+ const workspaceRoot = path3.resolve(thisDir, "..", "..", "..");
493
+ const uiRoot = path3.join(workspaceRoot, "packages", "ui");
494
+ if (fs3.existsSync(path3.join(uiRoot, "index.html"))) {
1556
495
  return uiRoot;
1557
496
  }
1558
497
  throw new Error(
@@ -1570,13 +509,13 @@ async function startViteDevServer(uiRoot, port, silent) {
1570
509
  return vite;
1571
510
  }
1572
511
  function createStaticServer(distPath, port) {
1573
- const server = http2.createServer((req, res) => {
512
+ const server = http.createServer((req, res) => {
1574
513
  const urlPath = req.url?.split("?")[0] ?? "/";
1575
- let filePath = path5.join(distPath, urlPath === "/" ? "index.html" : urlPath);
514
+ let filePath = path3.join(distPath, urlPath === "/" ? "index.html" : urlPath);
1576
515
  if (!fs3.existsSync(filePath)) {
1577
- filePath = path5.join(distPath, "index.html");
516
+ filePath = path3.join(distPath, "index.html");
1578
517
  }
1579
- const ext = path5.extname(filePath);
518
+ const ext = path3.extname(filePath);
1580
519
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
1581
520
  try {
1582
521
  const content = fs3.readFileSync(filePath);
@@ -1593,476 +532,50 @@ function createStaticServer(distPath, port) {
1593
532
  });
1594
533
  }
1595
534
 
1596
- // packages/core/src/pipeline.ts
1597
- async function startReview(options) {
1598
- const { diffRef, title, description, reasoning, cwd, silent, dev, injectedPayload } = options;
1599
- let diffSet;
1600
- let rawDiff;
1601
- let briefing;
1602
- let metadata;
1603
- if (injectedPayload) {
1604
- diffSet = injectedPayload.diffSet;
1605
- rawDiff = injectedPayload.rawDiff;
1606
- briefing = injectedPayload.briefing;
1607
- metadata = { ...injectedPayload.metadata };
1608
- } else {
1609
- const result = getDiff(diffRef, { cwd });
1610
- diffSet = result.diffSet;
1611
- rawDiff = result.rawDiff;
1612
- const currentBranch = getCurrentBranch({ cwd });
1613
- if (diffSet.files.length === 0) {
1614
- if (!silent) {
1615
- console.log("No changes to review.");
1616
- }
1617
- return {
1618
- decision: "approved",
1619
- comments: [],
1620
- summary: "No changes to review."
1621
- };
1622
- }
1623
- briefing = analyze(diffSet);
1624
- const worktreeInfo = detectWorktree({ cwd });
1625
- metadata = {
1626
- title,
1627
- description,
1628
- reasoning,
1629
- currentBranch,
1630
- worktree: worktreeInfo.isWorktree ? {
1631
- isWorktree: true,
1632
- worktreePath: worktreeInfo.worktreePath,
1633
- mainWorktreePath: worktreeInfo.mainWorktreePath
1634
- } : void 0
1635
- };
1636
- }
1637
- if (diffSet.files.length === 0) {
1638
- if (!silent) {
1639
- console.log("No changes to review.");
1640
- }
1641
- return {
1642
- decision: "approved",
1643
- comments: [],
1644
- summary: "No changes to review."
1645
- };
1646
- }
1647
- const session = createSession(options);
1648
- updateSession(session.id, { status: "in_progress" });
1649
- const isInjected = !!injectedPayload;
1650
- let poller = null;
1651
- const [bridgePort, httpPort] = await Promise.all([
1652
- getPort(),
1653
- getPort()
1654
- ]);
1655
- function handleDiffRefChange(newRef) {
1656
- if (isInjected) return;
1657
- const { diffSet: newDiffSet, rawDiff: newRawDiff } = getDiff(newRef, { cwd });
1658
- const newBriefing = analyze(newDiffSet);
1659
- bridge.sendDiffUpdate({
1660
- diffSet: newDiffSet,
1661
- rawDiff: newRawDiff,
1662
- briefing: newBriefing,
1663
- changedFiles: newDiffSet.files.map((f) => f.path),
1664
- timestamp: Date.now()
1665
- });
1666
- bridge.storeInitPayload({
1667
- reviewId: session.id,
1668
- diffSet: newDiffSet,
1669
- rawDiff: newRawDiff,
1670
- briefing: newBriefing,
1671
- metadata,
1672
- watchMode: true
1673
- });
1674
- poller?.setDiffRef(newRef);
1675
- }
1676
- const bridge = await createWatchBridge(bridgePort, {
1677
- onRefreshRequest: () => {
1678
- poller?.refresh();
1679
- },
1680
- onContextUpdate: (payload) => {
1681
- if (payload.reasoning !== void 0) metadata.reasoning = payload.reasoning;
1682
- if (payload.title !== void 0) metadata.title = payload.title;
1683
- if (payload.description !== void 0) metadata.description = payload.description;
1684
- },
1685
- onDiffRefChange: (newRef) => {
1686
- if (isInjected) return;
1687
- try {
1688
- handleDiffRefChange(newRef);
1689
- } catch (err) {
1690
- bridge.sendDiffError({
1691
- error: err instanceof Error ? err.message : String(err)
1692
- });
1693
- }
1694
- },
1695
- onRefsRequest: async () => {
1696
- if (isInjected) return null;
1697
- try {
1698
- const resolvedCwd = cwd ?? process.cwd();
1699
- const branches = listBranches({ cwd: resolvedCwd });
1700
- const commits = listCommits({ cwd: resolvedCwd });
1701
- const branch = getCurrentBranch({ cwd: resolvedCwd });
1702
- return { branches, commits, currentBranch: branch };
1703
- } catch {
1704
- return null;
1705
- }
1706
- },
1707
- onCompareRequest: async (ref) => {
1708
- if (isInjected) return false;
1709
- try {
1710
- handleDiffRefChange(ref);
1711
- return true;
1712
- } catch {
1713
- return false;
1714
- }
1715
- }
1716
- });
1717
- let httpServer = null;
1718
- let viteServer = null;
1719
- try {
1720
- if (dev) {
1721
- const uiRoot = resolveUiRoot();
1722
- viteServer = await startViteDevServer(uiRoot, httpPort, !!silent);
1723
- } else {
1724
- const uiDist = resolveUiDist();
1725
- httpServer = await createStaticServer(uiDist, httpPort);
1726
- }
1727
- writeWatchFile(cwd, {
1728
- wsPort: bridgePort,
1729
- uiPort: httpPort,
1730
- pid: process.pid,
1731
- cwd: cwd ?? process.cwd(),
1732
- diffRef,
1733
- startedAt: Date.now()
1734
- });
1735
- const url = `http://localhost:${httpPort}?wsPort=${bridgePort}&httpPort=${bridgePort}&reviewId=${session.id}`;
1736
- if (!silent) {
1737
- console.log(`
1738
- DiffPrism Review: ${title ?? briefing.summary}`);
1739
- console.log(`Opening browser at ${url}
1740
- `);
1741
- }
1742
- await open(url);
1743
- const initPayload = {
1744
- reviewId: session.id,
1745
- diffSet,
1746
- rawDiff,
1747
- briefing,
1748
- metadata,
1749
- watchMode: !isInjected
1750
- };
1751
- bridge.sendInit(initPayload);
1752
- if (!isInjected) {
1753
- poller = createDiffPoller({
1754
- diffRef,
1755
- cwd: cwd ?? process.cwd(),
1756
- pollInterval: 1e3,
1757
- onDiffChanged: (updatePayload) => {
1758
- bridge.storeInitPayload({
1759
- reviewId: session.id,
1760
- diffSet: updatePayload.diffSet,
1761
- rawDiff: updatePayload.rawDiff,
1762
- briefing: updatePayload.briefing,
1763
- metadata,
1764
- watchMode: true
1765
- });
1766
- bridge.sendDiffUpdate(updatePayload);
1767
- if (!silent && updatePayload.changedFiles.length > 0) {
1768
- console.log(
1769
- `[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Diff updated: ${updatePayload.changedFiles.length} file(s) changed`
1770
- );
1771
- }
1772
- }
1773
- });
1774
- poller.start();
1775
- }
1776
- const result = await bridge.waitForResult();
1777
- poller?.stop();
1778
- updateSession(session.id, { status: "completed", result });
1779
- try {
1780
- const projectDir = cwd ?? process.cwd();
1781
- const entry = {
1782
- id: generateEntryId(),
1783
- timestamp: Date.now(),
1784
- diffRef,
1785
- decision: result.decision,
1786
- filesReviewed: diffSet.files.length,
1787
- additions: diffSet.files.reduce((sum, f) => sum + f.additions, 0),
1788
- deletions: diffSet.files.reduce((sum, f) => sum + f.deletions, 0),
1789
- commentCount: result.comments.length,
1790
- branch: metadata.currentBranch,
1791
- title: metadata.title,
1792
- summary: result.summary ?? briefing.summary
1793
- };
1794
- appendHistory(projectDir, entry);
1795
- } catch {
1796
- }
1797
- return result;
1798
- } finally {
1799
- poller?.stop();
1800
- await bridge.close();
1801
- removeWatchFile(cwd);
1802
- if (viteServer) {
1803
- await viteServer.close();
1804
- }
1805
- if (httpServer) {
1806
- httpServer.close();
1807
- }
1808
- }
1809
- }
1810
-
1811
- // packages/core/src/server-file.ts
535
+ // packages/core/src/review-history.ts
1812
536
  import fs4 from "fs";
1813
- import path6 from "path";
1814
- import os from "os";
1815
- function serverDir() {
1816
- return path6.join(os.homedir(), ".diffprism");
1817
- }
1818
- function serverFilePath() {
1819
- return path6.join(serverDir(), "server.json");
1820
- }
1821
- function isPidAlive2(pid) {
1822
- try {
1823
- process.kill(pid, 0);
1824
- return true;
1825
- } catch {
1826
- return false;
1827
- }
537
+ import path4 from "path";
538
+ import { randomUUID } from "crypto";
539
+ function generateEntryId() {
540
+ return randomUUID();
1828
541
  }
1829
- function writeServerFile(info) {
1830
- const dir = serverDir();
1831
- if (!fs4.existsSync(dir)) {
1832
- fs4.mkdirSync(dir, { recursive: true });
1833
- }
1834
- fs4.writeFileSync(serverFilePath(), JSON.stringify(info, null, 2) + "\n");
542
+ function getHistoryPath(projectDir) {
543
+ return path4.join(projectDir, ".diffprism", "history", "reviews.json");
1835
544
  }
1836
- function readServerFile() {
1837
- const filePath = serverFilePath();
545
+ function readHistory(projectDir) {
546
+ const filePath = getHistoryPath(projectDir);
1838
547
  if (!fs4.existsSync(filePath)) {
1839
- return null;
548
+ return { version: 1, entries: [] };
1840
549
  }
1841
550
  try {
1842
551
  const raw = fs4.readFileSync(filePath, "utf-8");
1843
- const info = JSON.parse(raw);
1844
- if (!isPidAlive2(info.pid)) {
1845
- fs4.unlinkSync(filePath);
1846
- return null;
1847
- }
1848
- return info;
1849
- } catch {
1850
- return null;
1851
- }
1852
- }
1853
- function removeServerFile() {
1854
- try {
1855
- const filePath = serverFilePath();
1856
- if (fs4.existsSync(filePath)) {
1857
- fs4.unlinkSync(filePath);
1858
- }
552
+ const parsed = JSON.parse(raw);
553
+ return parsed;
1859
554
  } catch {
555
+ return { version: 1, entries: [] };
1860
556
  }
1861
557
  }
1862
- async function isServerAlive() {
1863
- const info = readServerFile();
1864
- if (!info) {
1865
- return null;
1866
- }
1867
- try {
1868
- const response = await fetch(`http://localhost:${info.httpPort}/api/status`, {
1869
- signal: AbortSignal.timeout(2e3)
1870
- });
1871
- if (response.ok) {
1872
- return info;
1873
- }
1874
- return null;
1875
- } catch {
1876
- removeServerFile();
1877
- return null;
558
+ function appendHistory(projectDir, entry) {
559
+ const filePath = getHistoryPath(projectDir);
560
+ const dir = path4.dirname(filePath);
561
+ if (!fs4.existsSync(dir)) {
562
+ fs4.mkdirSync(dir, { recursive: true });
1878
563
  }
564
+ const history = readHistory(projectDir);
565
+ history.entries.push(entry);
566
+ history.entries.sort((a, b) => a.timestamp - b.timestamp);
567
+ fs4.writeFileSync(filePath, JSON.stringify(history, null, 2) + "\n");
1879
568
  }
1880
-
1881
- // packages/core/src/watch.ts
1882
- import getPort2 from "get-port";
1883
- import open2 from "open";
1884
- async function startWatch(options) {
1885
- const {
1886
- diffRef,
1887
- title,
1888
- description,
1889
- reasoning,
1890
- cwd,
1891
- silent,
1892
- dev,
1893
- pollInterval = 1e3
1894
- } = options;
1895
- const { diffSet: initialDiffSet, rawDiff: initialRawDiff } = getDiff(diffRef, { cwd });
1896
- const currentBranch = getCurrentBranch({ cwd });
1897
- const initialBriefing = analyze(initialDiffSet);
1898
- const metadata = {
1899
- title,
1900
- description,
1901
- reasoning,
1902
- currentBranch
1903
- };
1904
- const [bridgePort, uiPort] = await Promise.all([
1905
- getPort2(),
1906
- getPort2()
1907
- ]);
1908
- const reviewId = "watch-session";
1909
- function handleDiffRefChange(newRef) {
1910
- const { diffSet: newDiffSet, rawDiff: newRawDiff } = getDiff(newRef, { cwd });
1911
- const newBriefing = analyze(newDiffSet);
1912
- bridge.sendDiffUpdate({
1913
- diffSet: newDiffSet,
1914
- rawDiff: newRawDiff,
1915
- briefing: newBriefing,
1916
- changedFiles: newDiffSet.files.map((f) => f.path),
1917
- timestamp: Date.now()
1918
- });
1919
- bridge.storeInitPayload({
1920
- reviewId,
1921
- diffSet: newDiffSet,
1922
- rawDiff: newRawDiff,
1923
- briefing: newBriefing,
1924
- metadata,
1925
- watchMode: true
1926
- });
1927
- poller.setDiffRef(newRef);
1928
- }
1929
- const bridge = await createWatchBridge(bridgePort, {
1930
- onRefreshRequest: () => {
1931
- poller.refresh();
1932
- },
1933
- onContextUpdate: (payload) => {
1934
- if (payload.reasoning !== void 0) metadata.reasoning = payload.reasoning;
1935
- if (payload.title !== void 0) metadata.title = payload.title;
1936
- if (payload.description !== void 0) metadata.description = payload.description;
1937
- },
1938
- onDiffRefChange: (newRef) => {
1939
- try {
1940
- handleDiffRefChange(newRef);
1941
- } catch (err) {
1942
- bridge.sendDiffError({
1943
- error: err instanceof Error ? err.message : String(err)
1944
- });
1945
- }
1946
- },
1947
- onRefsRequest: async () => {
1948
- try {
1949
- const resolvedCwd = cwd ?? process.cwd();
1950
- const branches = listBranches({ cwd: resolvedCwd });
1951
- const commits = listCommits({ cwd: resolvedCwd });
1952
- const branch = getCurrentBranch({ cwd: resolvedCwd });
1953
- return { branches, commits, currentBranch: branch };
1954
- } catch {
1955
- return null;
1956
- }
1957
- },
1958
- onCompareRequest: async (ref) => {
1959
- try {
1960
- handleDiffRefChange(ref);
1961
- return true;
1962
- } catch {
1963
- return false;
1964
- }
1965
- }
1966
- });
1967
- let httpServer = null;
1968
- let viteServer = null;
1969
- if (dev) {
1970
- const uiRoot = resolveUiRoot();
1971
- viteServer = await startViteDevServer(uiRoot, uiPort, !!silent);
1972
- } else {
1973
- const uiDist = resolveUiDist();
1974
- httpServer = await createStaticServer(uiDist, uiPort);
1975
- }
1976
- writeWatchFile(cwd, {
1977
- wsPort: bridgePort,
1978
- uiPort,
1979
- pid: process.pid,
1980
- cwd: cwd ?? process.cwd(),
1981
- diffRef,
1982
- startedAt: Date.now()
1983
- });
1984
- const url = `http://localhost:${uiPort}?wsPort=${bridgePort}&httpPort=${bridgePort}&reviewId=${reviewId}`;
1985
- if (!silent) {
1986
- console.log(`
1987
- DiffPrism Watch: ${title ?? `watching ${diffRef}`}`);
1988
- console.log(`Browser: ${url}`);
1989
- console.log(`API: http://localhost:${bridgePort}`);
1990
- console.log(`Polling every ${pollInterval}ms
1991
- `);
1992
- }
1993
- await open2(url);
1994
- const initPayload = {
1995
- reviewId,
1996
- diffSet: initialDiffSet,
1997
- rawDiff: initialRawDiff,
1998
- briefing: initialBriefing,
1999
- metadata,
2000
- watchMode: true
2001
- };
2002
- bridge.sendInit(initPayload);
2003
- bridge.onSubmit((result) => {
2004
- if (!silent) {
2005
- console.log(`
2006
- Review submitted: ${result.decision}`);
2007
- if (result.comments.length > 0) {
2008
- console.log(` ${result.comments.length} comment(s)`);
2009
- }
2010
- console.log("Continuing to watch...\n");
2011
- }
2012
- writeReviewResult(cwd, result);
2013
- });
2014
- const poller = createDiffPoller({
2015
- diffRef,
2016
- cwd: cwd ?? process.cwd(),
2017
- pollInterval,
2018
- onDiffChanged: (updatePayload) => {
2019
- bridge.storeInitPayload({
2020
- reviewId,
2021
- diffSet: updatePayload.diffSet,
2022
- rawDiff: updatePayload.rawDiff,
2023
- briefing: updatePayload.briefing,
2024
- metadata,
2025
- watchMode: true
2026
- });
2027
- bridge.sendDiffUpdate(updatePayload);
2028
- if (!silent && updatePayload.changedFiles.length > 0) {
2029
- console.log(
2030
- `[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Diff updated: ${updatePayload.changedFiles.length} file(s) changed`
2031
- );
2032
- }
2033
- }
2034
- });
2035
- poller.start();
2036
- async function stop() {
2037
- poller.stop();
2038
- await bridge.close();
2039
- if (viteServer) {
2040
- await viteServer.close();
2041
- }
2042
- if (httpServer) {
2043
- httpServer.close();
2044
- }
2045
- removeWatchFile(cwd);
2046
- }
2047
- function updateContext(payload) {
2048
- if (payload.reasoning !== void 0) metadata.reasoning = payload.reasoning;
2049
- if (payload.title !== void 0) metadata.title = payload.title;
2050
- if (payload.description !== void 0) metadata.description = payload.description;
2051
- bridge.sendContextUpdate(payload);
2052
- }
2053
- return { stop, updateContext };
569
+ function getRecentHistory(projectDir, limit = 50) {
570
+ const history = readHistory(projectDir);
571
+ return history.entries.slice(-limit);
2054
572
  }
2055
573
 
2056
574
  // packages/core/src/global-server.ts
2057
- import http3 from "http";
2058
- import { randomUUID as randomUUID2 } from "crypto";
2059
- import getPort3 from "get-port";
2060
- import open3 from "open";
2061
- import { WebSocketServer as WebSocketServer2, WebSocket as WebSocket2 } from "ws";
2062
575
  var SUBMITTED_TTL_MS = 5 * 60 * 1e3;
2063
576
  var ABANDONED_TTL_MS = 60 * 60 * 1e3;
2064
577
  var CLEANUP_INTERVAL_MS = 60 * 1e3;
2065
- var sessions2 = /* @__PURE__ */ new Map();
578
+ var sessions = /* @__PURE__ */ new Map();
2066
579
  var clientSessions = /* @__PURE__ */ new Map();
2067
580
  var sessionWatchers = /* @__PURE__ */ new Map();
2068
581
  var serverPollInterval = 2e3;
@@ -2124,7 +637,7 @@ function broadcastToAll(msg) {
2124
637
  if (!wss) return;
2125
638
  const data = JSON.stringify(msg);
2126
639
  for (const client of wss.clients) {
2127
- if (client.readyState === WebSocket2.OPEN) {
640
+ if (client.readyState === WebSocket.OPEN) {
2128
641
  client.send(data);
2129
642
  }
2130
643
  }
@@ -2133,7 +646,7 @@ function sendToSessionClients(sessionId, msg) {
2133
646
  if (!wss) return;
2134
647
  const data = JSON.stringify(msg);
2135
648
  for (const [client, sid] of clientSessions.entries()) {
2136
- if (sid === sessionId && client.readyState === WebSocket2.OPEN) {
649
+ if (sid === sessionId && client.readyState === WebSocket.OPEN) {
2137
650
  client.send(data);
2138
651
  }
2139
652
  }
@@ -2157,7 +670,7 @@ function broadcastSessionRemoved(sessionId) {
2157
670
  }
2158
671
  function hasViewersForSession(sessionId) {
2159
672
  for (const [client, sid] of clientSessions.entries()) {
2160
- if (sid === sessionId && client.readyState === WebSocket2.OPEN) {
673
+ if (sid === sessionId && client.readyState === WebSocket.OPEN) {
2161
674
  return true;
2162
675
  }
2163
676
  }
@@ -2165,14 +678,14 @@ function hasViewersForSession(sessionId) {
2165
678
  }
2166
679
  function startSessionWatcher(sessionId) {
2167
680
  if (sessionWatchers.has(sessionId)) return;
2168
- const session = sessions2.get(sessionId);
681
+ const session = sessions.get(sessionId);
2169
682
  if (!session?.diffRef) return;
2170
683
  const poller = createDiffPoller({
2171
684
  diffRef: session.diffRef,
2172
685
  cwd: session.projectPath,
2173
686
  pollInterval: serverPollInterval,
2174
687
  onDiffChanged: (updatePayload) => {
2175
- const s = sessions2.get(sessionId);
688
+ const s = sessions.get(sessionId);
2176
689
  if (!s) return;
2177
690
  s.payload = {
2178
691
  ...s.payload,
@@ -2205,7 +718,7 @@ function stopSessionWatcher(sessionId) {
2205
718
  }
2206
719
  }
2207
720
  function startAllWatchers() {
2208
- for (const [id, session] of sessions2.entries()) {
721
+ for (const [id, session] of sessions.entries()) {
2209
722
  if (session.diffRef && !sessionWatchers.has(id)) {
2210
723
  startSessionWatcher(id);
2211
724
  }
@@ -2220,12 +733,12 @@ function stopAllWatchers() {
2220
733
  function hasConnectedClients() {
2221
734
  if (!wss) return false;
2222
735
  for (const client of wss.clients) {
2223
- if (client.readyState === WebSocket2.OPEN) return true;
736
+ if (client.readyState === WebSocket.OPEN) return true;
2224
737
  }
2225
738
  return false;
2226
739
  }
2227
740
  function broadcastSessionList() {
2228
- const summaries = Array.from(sessions2.values()).map(toSummary);
741
+ const summaries = Array.from(sessions.values()).map(toSummary);
2229
742
  broadcastToAll({ type: "session:list", payload: summaries });
2230
743
  }
2231
744
  function recordReviewHistory(session, result) {
@@ -2267,7 +780,7 @@ async function handleApiRequest(req, res) {
2267
780
  jsonResponse(res, 200, {
2268
781
  running: true,
2269
782
  pid: process.pid,
2270
- sessions: sessions2.size,
783
+ sessions: sessions.size,
2271
784
  uptime: process.uptime()
2272
785
  });
2273
786
  return true;
@@ -2277,7 +790,7 @@ async function handleApiRequest(req, res) {
2277
790
  const body = await readBody(req);
2278
791
  const { payload, projectPath, diffRef } = JSON.parse(body);
2279
792
  let existingSession;
2280
- for (const session of sessions2.values()) {
793
+ for (const session of sessions.values()) {
2281
794
  if (session.projectPath === projectPath) {
2282
795
  existingSession = session;
2283
796
  break;
@@ -2299,7 +812,7 @@ async function handleApiRequest(req, res) {
2299
812
  existingSession.lastDiffSet = diffRef ? payload.diffSet : void 0;
2300
813
  existingSession.hasNewChanges = false;
2301
814
  existingSession.annotations = [];
2302
- if (diffRef && hasConnectedClients()) {
815
+ if (diffRef) {
2303
816
  startSessionWatcher(sessionId);
2304
817
  }
2305
818
  if (hasViewersForSession(sessionId)) {
@@ -2330,8 +843,8 @@ async function handleApiRequest(req, res) {
2330
843
  hasNewChanges: false,
2331
844
  annotations: []
2332
845
  };
2333
- sessions2.set(sessionId, session);
2334
- if (diffRef && hasConnectedClients()) {
846
+ sessions.set(sessionId, session);
847
+ if (diffRef) {
2335
848
  startSessionWatcher(sessionId);
2336
849
  }
2337
850
  broadcastToAll({
@@ -2347,13 +860,13 @@ async function handleApiRequest(req, res) {
2347
860
  return true;
2348
861
  }
2349
862
  if (method === "GET" && url === "/api/reviews") {
2350
- const summaries = Array.from(sessions2.values()).map(toSummary);
863
+ const summaries = Array.from(sessions.values()).map(toSummary);
2351
864
  jsonResponse(res, 200, { sessions: summaries });
2352
865
  return true;
2353
866
  }
2354
867
  const getReviewParams = matchRoute(method, url, "GET", "/api/reviews/:id");
2355
868
  if (getReviewParams) {
2356
- const session = sessions2.get(getReviewParams.id);
869
+ const session = sessions.get(getReviewParams.id);
2357
870
  if (!session) {
2358
871
  jsonResponse(res, 404, { error: "Session not found" });
2359
872
  return true;
@@ -2363,7 +876,7 @@ async function handleApiRequest(req, res) {
2363
876
  }
2364
877
  const postResultParams = matchRoute(method, url, "POST", "/api/reviews/:id/result");
2365
878
  if (postResultParams) {
2366
- const session = sessions2.get(postResultParams.id);
879
+ const session = sessions.get(postResultParams.id);
2367
880
  if (!session) {
2368
881
  jsonResponse(res, 404, { error: "Session not found" });
2369
882
  return true;
@@ -2387,7 +900,7 @@ async function handleApiRequest(req, res) {
2387
900
  }
2388
901
  const getResultParams = matchRoute(method, url, "GET", "/api/reviews/:id/result");
2389
902
  if (getResultParams) {
2390
- const session = sessions2.get(getResultParams.id);
903
+ const session = sessions.get(getResultParams.id);
2391
904
  if (!session) {
2392
905
  jsonResponse(res, 404, { error: "Session not found" });
2393
906
  return true;
@@ -2401,7 +914,7 @@ async function handleApiRequest(req, res) {
2401
914
  }
2402
915
  const postContextParams = matchRoute(method, url, "POST", "/api/reviews/:id/context");
2403
916
  if (postContextParams) {
2404
- const session = sessions2.get(postContextParams.id);
917
+ const session = sessions.get(postContextParams.id);
2405
918
  if (!session) {
2406
919
  jsonResponse(res, 404, { error: "Session not found" });
2407
920
  return true;
@@ -2430,7 +943,7 @@ async function handleApiRequest(req, res) {
2430
943
  }
2431
944
  const postAnnotationParams = matchRoute(method, url, "POST", "/api/reviews/:id/annotations");
2432
945
  if (postAnnotationParams) {
2433
- const session = sessions2.get(postAnnotationParams.id);
946
+ const session = sessions.get(postAnnotationParams.id);
2434
947
  if (!session) {
2435
948
  jsonResponse(res, 404, { error: "Session not found" });
2436
949
  return true;
@@ -2463,7 +976,7 @@ async function handleApiRequest(req, res) {
2463
976
  }
2464
977
  const getAnnotationsParams = matchRoute(method, url, "GET", "/api/reviews/:id/annotations");
2465
978
  if (getAnnotationsParams) {
2466
- const session = sessions2.get(getAnnotationsParams.id);
979
+ const session = sessions.get(getAnnotationsParams.id);
2467
980
  if (!session) {
2468
981
  jsonResponse(res, 404, { error: "Session not found" });
2469
982
  return true;
@@ -2473,7 +986,7 @@ async function handleApiRequest(req, res) {
2473
986
  }
2474
987
  const dismissAnnotationParams = matchRoute(method, url, "POST", "/api/reviews/:id/annotations/:annotationId/dismiss");
2475
988
  if (dismissAnnotationParams) {
2476
- const session = sessions2.get(dismissAnnotationParams.id);
989
+ const session = sessions.get(dismissAnnotationParams.id);
2477
990
  if (!session) {
2478
991
  jsonResponse(res, 404, { error: "Session not found" });
2479
992
  return true;
@@ -2494,7 +1007,7 @@ async function handleApiRequest(req, res) {
2494
1007
  const deleteParams = matchRoute(method, url, "DELETE", "/api/reviews/:id");
2495
1008
  if (deleteParams) {
2496
1009
  stopSessionWatcher(deleteParams.id);
2497
- if (sessions2.delete(deleteParams.id)) {
1010
+ if (sessions.delete(deleteParams.id)) {
2498
1011
  broadcastSessionRemoved(deleteParams.id);
2499
1012
  jsonResponse(res, 200, { ok: true });
2500
1013
  } else {
@@ -2504,7 +1017,7 @@ async function handleApiRequest(req, res) {
2504
1017
  }
2505
1018
  const getRefsParams = matchRoute(method, url, "GET", "/api/reviews/:id/refs");
2506
1019
  if (getRefsParams) {
2507
- const session = sessions2.get(getRefsParams.id);
1020
+ const session = sessions.get(getRefsParams.id);
2508
1021
  if (!session) {
2509
1022
  jsonResponse(res, 404, { error: "Session not found" });
2510
1023
  return true;
@@ -2525,7 +1038,7 @@ async function handleApiRequest(req, res) {
2525
1038
  }
2526
1039
  const postCompareParams = matchRoute(method, url, "POST", "/api/reviews/:id/compare");
2527
1040
  if (postCompareParams) {
2528
- const session = sessions2.get(postCompareParams.id);
1041
+ const session = sessions.get(postCompareParams.id);
2529
1042
  if (!session) {
2530
1043
  jsonResponse(res, 404, { error: "Session not found" });
2531
1044
  return true;
@@ -2577,7 +1090,7 @@ async function handleApiRequest(req, res) {
2577
1090
  }
2578
1091
  const getSessionHistoryParams = matchRoute(method, url, "GET", "/api/reviews/:id/history");
2579
1092
  if (getSessionHistoryParams) {
2580
- const session = sessions2.get(getSessionHistoryParams.id);
1093
+ const session = sessions.get(getSessionHistoryParams.id);
2581
1094
  if (!session) {
2582
1095
  jsonResponse(res, 404, { error: "Session not found" });
2583
1096
  return true;
@@ -2616,40 +1129,41 @@ async function startGlobalServer(options = {}) {
2616
1129
  wsPort: preferredWsPort = 24681,
2617
1130
  silent = false,
2618
1131
  dev = false,
2619
- pollInterval = 2e3
1132
+ pollInterval = 2e3,
1133
+ openBrowser = true
2620
1134
  } = options;
2621
1135
  serverPollInterval = pollInterval;
2622
1136
  const [httpPort, wsPort] = await Promise.all([
2623
- getPort3({ port: preferredHttpPort }),
2624
- getPort3({ port: preferredWsPort })
1137
+ getPort({ port: preferredHttpPort }),
1138
+ getPort({ port: preferredWsPort })
2625
1139
  ]);
2626
1140
  let uiPort;
2627
1141
  let uiHttpServer = null;
2628
1142
  let viteServer = null;
2629
1143
  if (dev) {
2630
- uiPort = await getPort3();
1144
+ uiPort = await getPort();
2631
1145
  const uiRoot = resolveUiRoot();
2632
1146
  viteServer = await startViteDevServer(uiRoot, uiPort, silent);
2633
1147
  } else {
2634
- uiPort = await getPort3();
1148
+ uiPort = await getPort();
2635
1149
  const uiDist = resolveUiDist();
2636
1150
  uiHttpServer = await createStaticServer(uiDist, uiPort);
2637
1151
  }
2638
- const httpServer = http3.createServer(async (req, res) => {
1152
+ const httpServer = http2.createServer(async (req, res) => {
2639
1153
  const handled = await handleApiRequest(req, res);
2640
1154
  if (!handled) {
2641
1155
  res.writeHead(404);
2642
1156
  res.end("Not found");
2643
1157
  }
2644
1158
  });
2645
- wss = new WebSocketServer2({ port: wsPort });
1159
+ wss = new WebSocketServer({ port: wsPort });
2646
1160
  wss.on("connection", (ws, req) => {
2647
1161
  startAllWatchers();
2648
1162
  const url = new URL(req.url ?? "/", `http://localhost:${wsPort}`);
2649
1163
  const sessionId = url.searchParams.get("sessionId");
2650
1164
  if (sessionId) {
2651
1165
  clientSessions.set(ws, sessionId);
2652
- const session = sessions2.get(sessionId);
1166
+ const session = sessions.get(sessionId);
2653
1167
  if (session) {
2654
1168
  session.status = "in_review";
2655
1169
  session.hasNewChanges = false;
@@ -2667,14 +1181,14 @@ async function startGlobalServer(options = {}) {
2667
1181
  }
2668
1182
  }
2669
1183
  } else {
2670
- const summaries = Array.from(sessions2.values()).map(toSummary);
1184
+ const summaries = Array.from(sessions.values()).map(toSummary);
2671
1185
  const msg = {
2672
1186
  type: "session:list",
2673
1187
  payload: summaries
2674
1188
  };
2675
1189
  ws.send(JSON.stringify(msg));
2676
1190
  if (summaries.length === 1) {
2677
- const session = sessions2.get(summaries[0].id);
1191
+ const session = sessions.get(summaries[0].id);
2678
1192
  if (session) {
2679
1193
  clientSessions.set(ws, session.id);
2680
1194
  session.status = "in_review";
@@ -2699,7 +1213,7 @@ async function startGlobalServer(options = {}) {
2699
1213
  if (msg.type === "review:submit") {
2700
1214
  const sid = clientSessions.get(ws);
2701
1215
  if (sid) {
2702
- const session = sessions2.get(sid);
1216
+ const session = sessions.get(sid);
2703
1217
  if (session) {
2704
1218
  session.result = msg.payload;
2705
1219
  session.status = "submitted";
@@ -2712,7 +1226,7 @@ async function startGlobalServer(options = {}) {
2712
1226
  }
2713
1227
  }
2714
1228
  } else if (msg.type === "session:select") {
2715
- const session = sessions2.get(msg.payload.sessionId);
1229
+ const session = sessions.get(msg.payload.sessionId);
2716
1230
  if (session) {
2717
1231
  clientSessions.set(ws, session.id);
2718
1232
  session.status = "in_review";
@@ -2733,7 +1247,7 @@ async function startGlobalServer(options = {}) {
2733
1247
  } else if (msg.type === "session:close") {
2734
1248
  const closedId = msg.payload.sessionId;
2735
1249
  stopSessionWatcher(closedId);
2736
- const closedSession = sessions2.get(closedId);
1250
+ const closedSession = sessions.get(closedId);
2737
1251
  if (closedSession && !closedSession.result) {
2738
1252
  closedSession.result = { decision: "dismissed", comments: [] };
2739
1253
  closedSession.status = "submitted";
@@ -2742,7 +1256,7 @@ async function startGlobalServer(options = {}) {
2742
1256
  } else if (msg.type === "diff:change_ref") {
2743
1257
  const sid = clientSessions.get(ws);
2744
1258
  if (sid) {
2745
- const session = sessions2.get(sid);
1259
+ const session = sessions.get(sid);
2746
1260
  if (session) {
2747
1261
  const newRef = msg.payload.diffRef;
2748
1262
  try {
@@ -2788,9 +1302,6 @@ async function startGlobalServer(options = {}) {
2788
1302
  });
2789
1303
  ws.on("close", () => {
2790
1304
  clientSessions.delete(ws);
2791
- if (!hasConnectedClients()) {
2792
- stopAllWatchers();
2793
- }
2794
1305
  });
2795
1306
  });
2796
1307
  await new Promise((resolve, reject) => {
@@ -2799,12 +1310,12 @@ async function startGlobalServer(options = {}) {
2799
1310
  });
2800
1311
  function cleanupExpiredSessions() {
2801
1312
  const now = Date.now();
2802
- for (const [id, session] of sessions2.entries()) {
1313
+ for (const [id, session] of sessions.entries()) {
2803
1314
  const age = now - session.createdAt;
2804
1315
  const expired = session.status === "submitted" && age > SUBMITTED_TTL_MS || session.status === "pending" && age > ABANDONED_TTL_MS;
2805
1316
  if (expired) {
2806
1317
  stopSessionWatcher(id);
2807
- sessions2.delete(id);
1318
+ sessions.delete(id);
2808
1319
  broadcastSessionRemoved(id);
2809
1320
  }
2810
1321
  }
@@ -2829,10 +1340,12 @@ Waiting for reviews...
2829
1340
  `);
2830
1341
  }
2831
1342
  const uiUrl = `http://localhost:${uiPort}?wsPort=${wsPort}&httpPort=${httpPort}&serverMode=true`;
2832
- await open3(uiUrl);
1343
+ if (openBrowser) {
1344
+ await open(uiUrl);
1345
+ }
2833
1346
  reopenBrowserIfNeeded = () => {
2834
1347
  if (!hasConnectedClients()) {
2835
- open3(uiUrl);
1348
+ open(uiUrl);
2836
1349
  }
2837
1350
  };
2838
1351
  async function stop() {
@@ -2846,7 +1359,7 @@ Waiting for reviews...
2846
1359
  wss = null;
2847
1360
  }
2848
1361
  clientSessions.clear();
2849
- sessions2.clear();
1362
+ sessions.clear();
2850
1363
  reopenBrowserIfNeeded = null;
2851
1364
  await new Promise((resolve) => {
2852
1365
  httpServer.close(() => resolve());
@@ -2863,17 +1376,17 @@ Waiting for reviews...
2863
1376
  }
2864
1377
 
2865
1378
  // packages/github/src/auth.ts
2866
- import { execSync as execSync3 } from "child_process";
1379
+ import { execSync } from "child_process";
2867
1380
  import fs5 from "fs";
2868
- import path7 from "path";
2869
- import os2 from "os";
1381
+ import path5 from "path";
1382
+ import os3 from "os";
2870
1383
  function resolveGitHubToken() {
2871
1384
  const envToken = process.env.GITHUB_TOKEN;
2872
1385
  if (envToken) {
2873
1386
  return envToken;
2874
1387
  }
2875
1388
  try {
2876
- const token = execSync3("gh auth token", {
1389
+ const token = execSync("gh auth token", {
2877
1390
  encoding: "utf-8",
2878
1391
  stdio: ["pipe", "pipe", "pipe"]
2879
1392
  }).trim();
@@ -2882,7 +1395,7 @@ function resolveGitHubToken() {
2882
1395
  }
2883
1396
  } catch {
2884
1397
  }
2885
- const configPath = path7.join(os2.homedir(), ".diffprism", "config.json");
1398
+ const configPath = path5.join(os3.homedir(), ".diffprism", "config.json");
2886
1399
  try {
2887
1400
  if (fs5.existsSync(configPath)) {
2888
1401
  const raw = fs5.readFileSync(configPath, "utf-8");
@@ -3846,17 +2359,17 @@ function requestLog(octokit) {
3846
2359
  octokit.log.debug("request", options);
3847
2360
  const start = Date.now();
3848
2361
  const requestOptions = octokit.request.endpoint.parse(options);
3849
- const path8 = requestOptions.url.replace(options.baseUrl, "");
2362
+ const path6 = requestOptions.url.replace(options.baseUrl, "");
3850
2363
  return request2(options).then((response) => {
3851
2364
  const requestId = response.headers["x-github-request-id"];
3852
2365
  octokit.log.info(
3853
- `${requestOptions.method} ${path8} - ${response.status} with id ${requestId} in ${Date.now() - start}ms`
2366
+ `${requestOptions.method} ${path6} - ${response.status} with id ${requestId} in ${Date.now() - start}ms`
3854
2367
  );
3855
2368
  return response;
3856
2369
  }).catch((error) => {
3857
2370
  const requestId = error.response?.headers["x-github-request-id"] || "UNKNOWN";
3858
2371
  octokit.log.error(
3859
- `${requestOptions.method} ${path8} - ${error.status} with id ${requestId} in ${Date.now() - start}ms`
2372
+ `${requestOptions.method} ${path6} - ${error.status} with id ${requestId} in ${Date.now() - start}ms`
3860
2373
  );
3861
2374
  throw error;
3862
2375
  });
@@ -6595,18 +5108,11 @@ function buildReviewBody(result) {
6595
5108
  }
6596
5109
 
6597
5110
  export {
6598
- getCurrentBranch,
6599
- detectWorktree,
6600
- getDiff,
6601
- analyze,
6602
- readWatchFile,
6603
- readReviewResult,
6604
- consumeReviewResult,
6605
- startReview,
6606
- startWatch,
6607
5111
  readServerFile,
6608
5112
  isServerAlive,
6609
5113
  startGlobalServer,
5114
+ ensureServer,
5115
+ submitReviewToServer,
6610
5116
  resolveGitHubToken,
6611
5117
  createGitHubClient,
6612
5118
  fetchPullRequest,