local-diff-reviewer 1.0.6 → 1.0.10

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.
Files changed (76) hide show
  1. package/README.md +21 -0
  2. package/SKILL.md +1 -0
  3. package/dist/cli/start.js +182 -26
  4. package/dist/web/assets/{arc-DfZYUaz4.js → arc-FnFNT5Eh.js} +1 -1
  5. package/dist/web/assets/{architectureDiagram-3BPJPVTR-Un1C7dX5.js → architectureDiagram-3BPJPVTR-Co5Z_QNw.js} +1 -1
  6. package/dist/web/assets/{blockDiagram-GPEHLZMM-DoE66LOm.js → blockDiagram-GPEHLZMM-BNsiQyLf.js} +1 -1
  7. package/dist/web/assets/{c4Diagram-AAUBKEIU-Ch17mKwl.js → c4Diagram-AAUBKEIU-BAZdudVw.js} +1 -1
  8. package/dist/web/assets/channel-BPbPhJj8.js +1 -0
  9. package/dist/web/assets/{chunk-2J33WTMH-DsTTzQ06.js → chunk-2J33WTMH-B0vxrf0b.js} +1 -1
  10. package/dist/web/assets/{chunk-3OPIFGDE-C4BFnYAe.js → chunk-3OPIFGDE-DejuRnfm.js} +1 -1
  11. package/dist/web/assets/{chunk-4BX2VUAB-CBYOekG6.js → chunk-4BX2VUAB-Bg5xzq_y.js} +1 -1
  12. package/dist/web/assets/{chunk-55IACEB6-bkX7IfWj.js → chunk-55IACEB6-D0Fci-zx.js} +1 -1
  13. package/dist/web/assets/{chunk-5ZQYHXKU-DmYX07rl.js → chunk-5ZQYHXKU-BPK5ENee.js} +1 -1
  14. package/dist/web/assets/{chunk-727SXJPM-myW6yB1O.js → chunk-727SXJPM-7K2UgSc-.js} +1 -1
  15. package/dist/web/assets/{chunk-AQP2D5EJ-fa_HpDkc.js → chunk-AQP2D5EJ-BWk8xaA0.js} +1 -1
  16. package/dist/web/assets/{chunk-BSJP7CBP-NpOkPl8K.js → chunk-BSJP7CBP-qQ__7B7h.js} +1 -1
  17. package/dist/web/assets/{chunk-CSCIHK7Q-jWMYsJiR.js → chunk-CSCIHK7Q-CrFKiZka.js} +1 -1
  18. package/dist/web/assets/{chunk-FMBD7UC4-BuB89xp8.js → chunk-FMBD7UC4-DvZyaP9P.js} +1 -1
  19. package/dist/web/assets/{chunk-KSCS5N6A-BvSsBz45.js → chunk-KSCS5N6A-BbcBNuLx.js} +1 -1
  20. package/dist/web/assets/{chunk-L5ZTLDWV-Cslvwh3t.js → chunk-L5ZTLDWV-D9Jpxw4I.js} +1 -1
  21. package/dist/web/assets/{chunk-LZXEDZCA-lDGN1tUz.js → chunk-LZXEDZCA-C0KF6zNF.js} +2 -2
  22. package/dist/web/assets/{chunk-ND2GUHAM-DGtWurFf.js → chunk-ND2GUHAM-B4UYa5t-.js} +1 -1
  23. package/dist/web/assets/{chunk-NZK2D7GU-BhN9qm2L.js → chunk-NZK2D7GU-DwIixx-H.js} +1 -1
  24. package/dist/web/assets/{chunk-O5CBEL6O-DPNhEfdl.js → chunk-O5CBEL6O-B6AfXN2g.js} +1 -1
  25. package/dist/web/assets/chunk-QZHKN3VN-erTNME5J.js +1 -0
  26. package/dist/web/assets/chunk-WU5MYG2G-CbnTR5Pg.js +1 -0
  27. package/dist/web/assets/{chunk-XPW4576I-BRb6pBFA.js → chunk-XPW4576I-D15NjZ7Z.js} +1 -1
  28. package/dist/web/assets/classDiagram-4FO5ZUOK-CuafptIX.js +1 -0
  29. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-BIT1gXn5.js +1 -0
  30. package/dist/web/assets/{cose-bilkent-S5V4N54A-DP7aCqjn.js → cose-bilkent-S5V4N54A-BNwgjIv3.js} +1 -1
  31. package/dist/web/assets/{dagre-BM42HDAG-DW8OoOF9.js → dagre-BM42HDAG-CpwwUyr_.js} +1 -1
  32. package/dist/web/assets/{diagram-2AECGRRQ-Dhr52Lcq.js → diagram-2AECGRRQ-Bw8x1vsX.js} +1 -1
  33. package/dist/web/assets/{diagram-5GNKFQAL-00RS5q-p.js → diagram-5GNKFQAL-BB2GXRx9.js} +1 -1
  34. package/dist/web/assets/{diagram-KO2AKTUF-CV1YsijI.js → diagram-KO2AKTUF-BgaaH0hW.js} +1 -1
  35. package/dist/web/assets/{diagram-LMA3HP47-Cu6hlhhL.js → diagram-LMA3HP47-BisC4uiR.js} +1 -1
  36. package/dist/web/assets/{diagram-OG6HWLK6-f8Aglkk5.js → diagram-OG6HWLK6-BDj48Uho.js} +1 -1
  37. package/dist/web/assets/{dist-BbvzfnQH.js → dist-D2UD_g6s.js} +1 -1
  38. package/dist/web/assets/{erDiagram-TEJ5UH35-BOpwL6Hq.js → erDiagram-TEJ5UH35-cBF6TfGh.js} +1 -1
  39. package/dist/web/assets/{flowDiagram-I6XJVG4X-JpfaqfMd.js → flowDiagram-I6XJVG4X-CYc4faT9.js} +1 -1
  40. package/dist/web/assets/{ganttDiagram-6RSMTGT7-_07N56My.js → ganttDiagram-6RSMTGT7-CvZHLnnj.js} +1 -1
  41. package/dist/web/assets/{gitGraphDiagram-PVQCEYII-CW2hqk_8.js → gitGraphDiagram-PVQCEYII-B9EFahM9.js} +1 -1
  42. package/dist/web/assets/index-1vvZG57w.css +1 -0
  43. package/dist/web/assets/{index-CxFV1WHQ.js → index-wy40yeNf.js} +24 -24
  44. package/dist/web/assets/{infoDiagram-5YYISTIA-B35H3DgZ.js → infoDiagram-5YYISTIA-OwyPUHlr.js} +1 -1
  45. package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-DxVWMRSB.js → ishikawaDiagram-YF4QCWOH-DWNJFp-G.js} +1 -1
  46. package/dist/web/assets/{journeyDiagram-JHISSGLW-C__VorSx.js → journeyDiagram-JHISSGLW-Fx_1G-6i.js} +1 -1
  47. package/dist/web/assets/{kanban-definition-UN3LZRKU-BKSCqg5P.js → kanban-definition-UN3LZRKU-Dq4CooCl.js} +1 -1
  48. package/dist/web/assets/{line-SFaTyCgO.js → line-HQcA7ihX.js} +1 -1
  49. package/dist/web/assets/{linear-CCDSib_A.js → linear-BZeIaDEq.js} +1 -1
  50. package/dist/web/assets/{mermaid-parser.core-B4q5D_X_.js → mermaid-parser.core-DJsgXM22.js} +1 -1
  51. package/dist/web/assets/{mermaid.core-DiPBDfaO.js → mermaid.core-CkAku_bb.js} +3 -3
  52. package/dist/web/assets/{mindmap-definition-RKZ34NQL-BJZedUBK.js → mindmap-definition-RKZ34NQL-BWRzwBxY.js} +1 -1
  53. package/dist/web/assets/{pieDiagram-4H26LBE5-DtkdCUA9.js → pieDiagram-4H26LBE5-woyVH6aA.js} +1 -1
  54. package/dist/web/assets/{quadrantDiagram-W4KKPZXB-Cks8DI1_.js → quadrantDiagram-W4KKPZXB-DmMwbj7N.js} +1 -1
  55. package/dist/web/assets/{requirementDiagram-4Y6WPE33-DIJqz1Ht.js → requirementDiagram-4Y6WPE33-1vUYL3sj.js} +1 -1
  56. package/dist/web/assets/{sankeyDiagram-5OEKKPKP-waqn6y15.js → sankeyDiagram-5OEKKPKP-BiRxghpn.js} +1 -1
  57. package/dist/web/assets/{sequenceDiagram-3UESZ5HK-Cyzxtc0o.js → sequenceDiagram-3UESZ5HK-C-SgacOL.js} +1 -1
  58. package/dist/web/assets/{src-DboPYYAM.js → src-DMlkBZbY.js} +1 -1
  59. package/dist/web/assets/{stateDiagram-AJRCARHV-BIfp7CSY.js → stateDiagram-AJRCARHV-Ci958Ibo.js} +1 -1
  60. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-BcqZc5a0.js +1 -0
  61. package/dist/web/assets/{timeline-definition-PNZ67QCA-5e-cw7ns.js → timeline-definition-PNZ67QCA-CQzVyH6s.js} +1 -1
  62. package/dist/web/assets/{vennDiagram-CIIHVFJN-H4TiL4fv.js → vennDiagram-CIIHVFJN-DjtlRdrY.js} +1 -1
  63. package/dist/web/assets/{wardleyDiagram-YWT4CUSO-C9je1Qmh.js → wardleyDiagram-YWT4CUSO-Braalnw5.js} +1 -1
  64. package/dist/web/assets/{xychartDiagram-2RQKCTM6-CbnF4TLC.js → xychartDiagram-2RQKCTM6-YjueRiNp.js} +1 -1
  65. package/dist/web/index.html +2 -2
  66. package/docs/images/diff-review-intro.html +767 -0
  67. package/docs/images/diff-review-intro.png +0 -0
  68. package/docs/images/image.png +0 -0
  69. package/package.json +1 -1
  70. package/dist/web/assets/channel-tS6_tEZH.js +0 -1
  71. package/dist/web/assets/chunk-QZHKN3VN-MdvGD9q1.js +0 -1
  72. package/dist/web/assets/chunk-WU5MYG2G-Cot-8ei2.js +0 -1
  73. package/dist/web/assets/classDiagram-4FO5ZUOK-Ba9dn90z.js +0 -1
  74. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-AkPgLlFm.js +0 -1
  75. package/dist/web/assets/index-CND5NAGY.css +0 -1
  76. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-CBvj4HDj.js +0 -1
package/README.md CHANGED
@@ -42,6 +42,7 @@ npx skills add Mone-Lee/diff-review
42
42
  local-diff-reviewer
43
43
  local-diff-reviewer staged
44
44
  local-diff-reviewer HEAD~1 HEAD
45
+ local-diff-reviewer stop
45
46
  local-diff-reviewer --repo /path/to/project
46
47
  ```
47
48
 
@@ -51,6 +52,15 @@ local-diff-reviewer --repo /path/to/project
51
52
  - `staged`:审查已经 `git add`、但还没有提交的改动。
52
53
  - `revision`:审查两个 revision 之间的差异,例如 `local-diff-reviewer HEAD~1 HEAD` 会比较 `HEAD~1..HEAD`。
53
54
 
55
+ 停止当前项目已启动的 review 进程:
56
+
57
+ - `stop`:按当前 Git 仓库范围清理该仓库启动过的 review 运行进程(包含其 API 端口进程)。
58
+
59
+ ```bash
60
+ local-diff-reviewer stop
61
+ local-diff-reviewer --repo /path/to/project stop
62
+ ```
63
+
54
64
  如果命令不是在目标项目目录里启动,可以用 `--repo <path>` 显式指定要审查的 Git 仓库:
55
65
 
56
66
  ```bash
@@ -65,6 +75,8 @@ local-diff-reviewer --repo /path/to/project staged
65
75
  项目 B /diff-review -> http://127.0.0.1:<空闲端口> -> 项目 B diff
66
76
  ```
67
77
 
78
+ 页面打开后,代码 diff 和 Markdown 预览会固定为本次启动时的快照;工作区继续变动不会改写已打开页面里的代码内容。评论线程会在页面可见时持续同步。若需要审查最新工作区内容,请再次执行 `local-diff-reviewer` 或 `/diff-review` 打开新的 review 会话。
79
+
68
80
  注意:本地开发的 `--dev` 模式仍使用 Vite dev server,端口和 API proxy 是固定的;多项目并行审查请使用默认的构建页面模式。
69
81
 
70
82
  ## Skill 使用方式
@@ -75,6 +87,7 @@ local-diff-reviewer --repo /path/to/project staged
75
87
  /diff-review
76
88
  /diff-review staged
77
89
  /diff-review HEAD~1 HEAD
90
+ /diff-review stop
78
91
  ```
79
92
 
80
93
  安装 skill:
@@ -137,12 +150,20 @@ npm run release major
137
150
 
138
151
  该命令会按顺序执行:
139
152
 
153
+ - 发布前检查 npmjs 认证与连通性(`npm whoami --registry=https://registry.npmjs.org/` + `npm ping --registry=https://registry.npmjs.org/`)
140
154
  - `npm run release:check`
141
155
  - `npm version <patch|minor|major>`(默认 `patch`)
142
156
  - `npm publish`
143
157
  - `git push`
144
158
  - `git push --tags`
145
159
 
160
+ 若 npmjs 认证缺失或过期,发布会在 preflight 阶段提前失败,并提示执行:
161
+
162
+ ```bash
163
+ npm login --registry=https://registry.npmjs.org/
164
+ npm whoami --registry=https://registry.npmjs.org/
165
+ ```
166
+
146
167
  只有在 `npm publish` 成功后,才会自动推送提交和标签到 GitHub。
147
168
 
148
169
  ## 本地开发
package/SKILL.md CHANGED
@@ -15,6 +15,7 @@ Use this skill when the user asks for `/diff-review`, wants to inspect current w
15
15
  - `/diff-review working`: review current working tree diff.
16
16
  - `/diff-review staged`: review staged diff.
17
17
  - `/diff-review <base> <target>`: review diff between two Git revisions.
18
+ - `/diff-review stop`: stop all review runtimes created for the current workspace repository.
18
19
 
19
20
  Do not ask the user to run a shell CLI manually. Determine the target workspace/repository from the user's active environment context, then run the package command with that repository as the command working directory:
20
21
 
package/dist/cli/start.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/cli/start.ts
4
4
  import { spawn } from "node:child_process";
5
5
  import { existsSync as existsSync2 } from "node:fs";
6
- import { dirname as dirname2, join as join4, resolve as resolve2 } from "node:path";
6
+ import { basename as basename3, dirname as dirname3, join as join5, resolve as resolve2 } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
 
9
9
  // src/server/storage.ts
@@ -510,10 +510,96 @@ function binaryAddedDiff(path) {
510
510
  return [`diff --git a/${path} b/${path}`, "new file mode 100644", "index 0000000..0000000", `Binary files /dev/null and b/${path} differ`].join("\n");
511
511
  }
512
512
 
513
+ // src/cli/runtime-registry.ts
514
+ import { createHash as createHash3 } from "node:crypto";
515
+ import { access, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
516
+ import { homedir as homedir2, platform as platform2 } from "node:os";
517
+ import { basename as basename2, dirname as dirname2, join as join3 } from "node:path";
518
+ async function recordRuntime(entry) {
519
+ const path = runtimePath(entry.repoRoot);
520
+ const store = await readRuntime(path);
521
+ const aliveEntries = store.entries.filter((item) => isPidAlive(item.pid));
522
+ const deduped = aliveEntries.filter((item) => item.pid !== entry.pid);
523
+ deduped.push(entry);
524
+ await writeRuntime(path, { entries: deduped });
525
+ }
526
+ async function stopRecordedRuntimes(repoRoot) {
527
+ const path = runtimePath(repoRoot);
528
+ const store = await readRuntime(path);
529
+ const stopped = [];
530
+ const stale = [];
531
+ for (const entry of store.entries) {
532
+ const parentAlive = isPidAlive(entry.pid);
533
+ const viteAlive = typeof entry.vitePid === "number" && isPidAlive(entry.vitePid);
534
+ if (!parentAlive && !viteAlive) {
535
+ stale.push(entry);
536
+ continue;
537
+ }
538
+ try {
539
+ if (parentAlive) process.kill(entry.pid, "SIGTERM");
540
+ if (viteAlive) process.kill(entry.vitePid, "SIGTERM");
541
+ stopped.push(entry);
542
+ } catch {
543
+ stale.push(entry);
544
+ }
545
+ }
546
+ await writeRuntime(path, { entries: [] });
547
+ return { stopped, stale };
548
+ }
549
+ function runtimePath(repoRoot) {
550
+ const repoName = basename2(repoRoot) || "repo";
551
+ const repoHash = createHash3("sha256").update(repoRoot).digest("hex").slice(0, 12);
552
+ return join3(runtimeDir(), `${repoName}-${repoHash}.runtime.json`);
553
+ }
554
+ function runtimeDir() {
555
+ if (platform2() === "win32") {
556
+ return join3(process.env.LOCALAPPDATA ?? join3(homedir2(), "AppData", "Local"), "diff-review", "runtime");
557
+ }
558
+ return join3(homedir2(), ".local", "diff-review", "runtime");
559
+ }
560
+ async function readRuntime(path) {
561
+ try {
562
+ const text = await readFile3(path, "utf8");
563
+ const parsed = JSON.parse(text);
564
+ if (!Array.isArray(parsed.entries)) return { entries: [] };
565
+ return { entries: parsed.entries.filter(isRuntimeEntry) };
566
+ } catch {
567
+ return { entries: [] };
568
+ }
569
+ }
570
+ async function writeRuntime(path, store) {
571
+ await mkdir2(dirname2(path), { recursive: true });
572
+ await writeFile2(path, `${JSON.stringify(store, null, 2)}
573
+ `, "utf8");
574
+ }
575
+ function isRuntimeEntry(value) {
576
+ if (!value || typeof value !== "object") return false;
577
+ const entry = value;
578
+ return typeof entry.pid === "number" && (typeof entry.vitePid === "undefined" || typeof entry.vitePid === "number") && typeof entry.repoRoot === "string" && typeof entry.repoName === "string" && typeof entry.startedAt === "string" && typeof entry.apiPort === "number" && typeof entry.usesVite === "boolean";
579
+ }
580
+ function isPidAlive(pid) {
581
+ if (!Number.isInteger(pid) || pid <= 0) return false;
582
+ try {
583
+ process.kill(pid, 0);
584
+ return true;
585
+ } catch {
586
+ return false;
587
+ }
588
+ }
589
+ async function hasRuntimeRecord(repoRoot) {
590
+ try {
591
+ await access(runtimePath(repoRoot));
592
+ return true;
593
+ } catch {
594
+ return false;
595
+ }
596
+ }
597
+
513
598
  // src/server/index.ts
514
599
  import express from "express";
515
600
  import { existsSync } from "node:fs";
516
- import { join as join3, normalize as normalize2, resolve, sep } from "node:path";
601
+ import { createServer } from "node:http";
602
+ import { join as join4, normalize as normalize2, resolve, sep } from "node:path";
517
603
 
518
604
  // src/core/markdown-source-map.ts
519
605
  import GithubSlugger from "github-slugger";
@@ -621,6 +707,7 @@ function getAnchorLine(thread) {
621
707
 
622
708
  // src/server/index.ts
623
709
  async function startServer(state, port = 4966) {
710
+ const markdownPreviews = await buildMarkdownPreviewCache(state);
624
711
  const app = express();
625
712
  app.use(express.json({ limit: "2mb" }));
626
713
  app.get("/api/session", (_req, res) => {
@@ -629,21 +716,22 @@ async function startServer(state, port = 4966) {
629
716
  app.get("/api/diff", (_req, res) => {
630
717
  res.json({ files: state.diffFiles });
631
718
  });
719
+ app.get("/api/review-state", async (_req, res, next) => {
720
+ try {
721
+ const comments = await readComments(state.session.repoRoot);
722
+ res.json({ session: state.session, files: state.diffFiles, threads: comments.threads });
723
+ } catch (error) {
724
+ next(error);
725
+ }
726
+ });
632
727
  app.get("/api/markdown-preview", async (req, res, next) => {
633
728
  try {
634
729
  const filePath = String(req.query.path ?? "");
635
- const file = state.diffFiles.find((item) => item.path === filePath || item.oldPath === filePath);
636
- if (!file || !file.isMarkdown) {
730
+ const preview = markdownPreviews.get(filePath);
731
+ if (!preview) {
637
732
  res.status(404).json({ error: "Markdown file not found in diff" });
638
733
  return;
639
734
  }
640
- const { content, deleted } = await readFileForPreview(file, state.session.mode, state.session.repoRoot);
641
- const preview = {
642
- filePath: file.path,
643
- content,
644
- deleted,
645
- blocks: buildMarkdownBlocks(content)
646
- };
647
735
  res.json(preview);
648
736
  } catch (error) {
649
737
  next(error);
@@ -852,23 +940,36 @@ async function startServer(state, port = 4966) {
852
940
  next(error);
853
941
  }
854
942
  });
855
- const webDist = state.webDist ?? join3(process.cwd(), "dist", "web");
943
+ const webDist = state.webDist ?? join4(process.cwd(), "dist", "web");
856
944
  if (existsSync(webDist)) {
857
945
  app.use(express.static(webDist));
858
- app.get(/.*/, (_req, res) => res.sendFile(join3(webDist, "index.html")));
946
+ app.get(/.*/, (_req, res) => res.sendFile(join4(webDist, "index.html")));
859
947
  }
860
948
  app.use((error, _req, res, _next) => {
861
949
  res.status(500).json({ error: error.message });
862
950
  });
863
951
  return listen(app, port);
864
952
  }
953
+ async function buildMarkdownPreviewCache(state) {
954
+ const previews = /* @__PURE__ */ new Map();
955
+ await Promise.all(
956
+ state.diffFiles.filter((file) => file.isMarkdown).map(async (file) => {
957
+ const { content, deleted } = await readFileForPreview(file, state.session.mode, state.session.repoRoot);
958
+ const preview = {
959
+ filePath: file.path,
960
+ content,
961
+ deleted,
962
+ blocks: buildMarkdownBlocks(content)
963
+ };
964
+ previews.set(file.path, preview);
965
+ previews.set(file.oldPath, preview);
966
+ })
967
+ );
968
+ return previews;
969
+ }
865
970
  function listen(app, port) {
866
971
  return new Promise((resolve3, reject) => {
867
- const server = app.listen(port, "127.0.0.1", () => {
868
- const address = server.address();
869
- const actualPort = typeof address === "object" && address ? address.port : port;
870
- resolve3(`http://127.0.0.1:${actualPort}`);
871
- });
972
+ const server = createServer(app);
872
973
  server.once("error", (error) => {
873
974
  if (error.code === "EADDRINUSE" && port !== 0) {
874
975
  listen(app, 0).then(resolve3, reject);
@@ -876,6 +977,11 @@ function listen(app, port) {
876
977
  }
877
978
  reject(error);
878
979
  });
980
+ server.listen(port, "127.0.0.1", () => {
981
+ const address = server.address();
982
+ const actualPort = typeof address === "object" && address ? address.port : port;
983
+ resolve3(`http://127.0.0.1:${actualPort}`);
984
+ });
879
985
  });
880
986
  }
881
987
  function selectPromptThreads(threads, scope) {
@@ -887,31 +993,47 @@ function selectPromptThreads(threads, scope) {
887
993
  }
888
994
 
889
995
  // src/cli/start.ts
890
- var packageRoot = resolve2(dirname2(fileURLToPath(import.meta.url)), "../..");
891
- var builtWebDist = join4(packageRoot, "dist", "web");
996
+ var packageRoot = resolve2(dirname3(fileURLToPath(import.meta.url)), "../..");
997
+ var builtWebDist = join5(packageRoot, "dist", "web");
892
998
  async function main() {
893
- const { dev, repo, reviewArgs, comments } = parseCliOptions(process.argv.slice(2));
999
+ const { command, dev, repo, reviewArgs, comments } = parseCliOptions(process.argv.slice(2));
1000
+ if (command === "stop") {
1001
+ await stopCommand(repo);
1002
+ return;
1003
+ }
894
1004
  const mode = parseReviewMode(reviewArgs);
895
1005
  const repoRoot = await getRepoRoot(repo ?? process.cwd());
896
1006
  const diff = await getDiff(mode, repoRoot);
897
1007
  const diffFiles = parseUnifiedDiff(diff);
898
1008
  const session = {
899
1009
  id: crypto.randomUUID(),
1010
+ repoName: basename3(repoRoot),
900
1011
  repoRoot,
901
1012
  mode,
902
1013
  diffHash: diffHash(diff),
903
1014
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
904
1015
  };
905
1016
  const importResult = await importAgentComments(repoRoot, diffFiles, comments);
906
- const hasBuiltWeb = existsSync2(join4(builtWebDist, "index.html"));
1017
+ const hasBuiltWeb = existsSync2(join5(builtWebDist, "index.html"));
907
1018
  const apiUrl = await startServer({ session, diffFiles, webDist: hasBuiltWeb ? builtWebDist : void 0 });
908
1019
  const useVite = dev || !hasBuiltWeb;
909
1020
  const uiUrl = useVite ? "http://127.0.0.1:5173" : apiUrl;
910
- if (useVite) {
911
- startVite();
912
- }
1021
+ const vitePid = useVite ? startVite() : void 0;
1022
+ await recordRuntime({
1023
+ pid: process.pid,
1024
+ vitePid,
1025
+ repoRoot,
1026
+ repoName: session.repoName,
1027
+ startedAt: session.createdAt,
1028
+ apiPort: parsePort(apiUrl),
1029
+ usesVite: useVite
1030
+ });
913
1031
  openBrowser(uiUrl);
914
1032
  console.log(`Diff Review is running: ${uiUrl}`);
1033
+ console.log(`Repo: ${session.repoName} (${repoRoot})`);
1034
+ if (!useVite && uiUrl !== "http://127.0.0.1:4966") {
1035
+ console.log(`Default port 4966 is busy; using ${uiUrl}`);
1036
+ }
915
1037
  console.log(`Mode: ${modeLabel(mode)}`);
916
1038
  console.log(`Files: ${diffFiles.length}`);
917
1039
  if (comments.length > 0) {
@@ -922,6 +1044,7 @@ async function main() {
922
1044
  }
923
1045
  }
924
1046
  function parseCliOptions(args) {
1047
+ let command = "review";
925
1048
  const reviewArgs = [];
926
1049
  const comments = [];
927
1050
  let repo;
@@ -932,6 +1055,10 @@ function parseCliOptions(args) {
932
1055
  dev = true;
933
1056
  continue;
934
1057
  }
1058
+ if (arg === "stop") {
1059
+ command = "stop";
1060
+ continue;
1061
+ }
935
1062
  if (arg === "--repo") {
936
1063
  const value = args[index + 1];
937
1064
  if (!value) throw new Error("--repo requires a path value");
@@ -960,7 +1087,35 @@ function parseCliOptions(args) {
960
1087
  }
961
1088
  reviewArgs.push(arg);
962
1089
  }
963
- return { dev, repo, reviewArgs, comments };
1090
+ return { command, dev, repo, reviewArgs, comments };
1091
+ }
1092
+ async function stopCommand(repo) {
1093
+ const repoRoot = await getRepoRoot(repo ?? process.cwd());
1094
+ const hasRecord = await hasRuntimeRecord(repoRoot);
1095
+ const { stopped, stale } = await stopRecordedRuntimes(repoRoot);
1096
+ const total = stopped.length + stale.length;
1097
+ if (total === 0) {
1098
+ if (hasRecord) {
1099
+ console.log("No running review process found for this repo.");
1100
+ return;
1101
+ }
1102
+ console.log("No review runtime record found for this repo.");
1103
+ return;
1104
+ }
1105
+ console.log(`Stopped review runtimes: ${stopped.length}`);
1106
+ for (const entry of stopped) {
1107
+ console.log(
1108
+ `- pid=${entry.pid} vitePid=${entry.vitePid ?? "-"} apiPort=${entry.apiPort} vite=${entry.usesVite ? "yes" : "no"} startedAt=${entry.startedAt}`
1109
+ );
1110
+ }
1111
+ if (stale.length > 0) {
1112
+ console.log(`Skipped stale records: ${stale.length}`);
1113
+ }
1114
+ }
1115
+ function parsePort(url) {
1116
+ const parsed = new URL(url);
1117
+ const port = parsed.port ? Number(parsed.port) : 80;
1118
+ return Number.isNaN(port) ? 0 : port;
964
1119
  }
965
1120
  function modeLabel(mode) {
966
1121
  if (mode.kind === "revision") return `${mode.base}..${mode.target}`;
@@ -975,6 +1130,7 @@ function startVite() {
975
1130
  });
976
1131
  process.on("SIGINT", () => child.kill("SIGINT"));
977
1132
  process.on("SIGTERM", () => child.kill("SIGTERM"));
1133
+ return child.pid;
978
1134
  }
979
1135
  function openBrowser(url) {
980
1136
  const child = process.platform === "darwin" ? spawn("open", [url], { stdio: "ignore", detached: true }) : process.platform === "win32" ? spawn("cmd", ["/c", "start", "", url], {
@@ -1 +1 @@
1
- import{n as e,t}from"./path-BJQEcSo7.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-BbvzfnQH.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i)if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
1
+ import{n as e,t}from"./path-BJQEcSo7.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-D2UD_g6s.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i)if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};