local-diff-reviewer 1.0.11 → 2.0.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.
Files changed (73) hide show
  1. package/README.md +9 -2
  2. package/SKILL.md +3 -1
  3. package/dist/cli/start.js +97 -18
  4. package/dist/web/assets/{arc-BOpgS1XG.js → arc-BTM4ghNf.js} +1 -1
  5. package/dist/web/assets/{architectureDiagram-3BPJPVTR-C7QsBMbs.js → architectureDiagram-3BPJPVTR-DyOV0Jtr.js} +1 -1
  6. package/dist/web/assets/{blockDiagram-GPEHLZMM-QaQaVyDL.js → blockDiagram-GPEHLZMM-CxJbhmIT.js} +1 -1
  7. package/dist/web/assets/{c4Diagram-AAUBKEIU-CufBBkVk.js → c4Diagram-AAUBKEIU-BJQFA8CQ.js} +1 -1
  8. package/dist/web/assets/channel-zJdz6BA1.js +1 -0
  9. package/dist/web/assets/{chunk-2J33WTMH-Cj_oaVb4.js → chunk-2J33WTMH-C5OtbA_r.js} +1 -1
  10. package/dist/web/assets/{chunk-3OPIFGDE-DmJyquPX.js → chunk-3OPIFGDE-B9KuazSf.js} +1 -1
  11. package/dist/web/assets/{chunk-4BX2VUAB-CwaF4zJY.js → chunk-4BX2VUAB-B8LRminS.js} +1 -1
  12. package/dist/web/assets/{chunk-55IACEB6-DqeKiemL.js → chunk-55IACEB6-SkDhErOo.js} +1 -1
  13. package/dist/web/assets/{chunk-5ZQYHXKU-DNp0ZNVB.js → chunk-5ZQYHXKU-Bb82bV79.js} +1 -1
  14. package/dist/web/assets/{chunk-727SXJPM-BKKrSCOF.js → chunk-727SXJPM-Dl1e3M-k.js} +1 -1
  15. package/dist/web/assets/{chunk-AQP2D5EJ-BCNSIQmO.js → chunk-AQP2D5EJ-ds7_Qz_Y.js} +1 -1
  16. package/dist/web/assets/{chunk-BSJP7CBP-Ce9WIScW.js → chunk-BSJP7CBP-Ddngm4Zu.js} +1 -1
  17. package/dist/web/assets/{chunk-CSCIHK7Q-Cj2N3A_X.js → chunk-CSCIHK7Q-zBbkk0kR.js} +1 -1
  18. package/dist/web/assets/{chunk-FMBD7UC4-BADdZIdh.js → chunk-FMBD7UC4-C9gGg2hs.js} +1 -1
  19. package/dist/web/assets/{chunk-KSCS5N6A-BeYyq8hv.js → chunk-KSCS5N6A-CYRCkspU.js} +1 -1
  20. package/dist/web/assets/{chunk-L5ZTLDWV-vyV2WecR.js → chunk-L5ZTLDWV-at0vaITZ.js} +1 -1
  21. package/dist/web/assets/{chunk-LZXEDZCA-DFAA6FIb.js → chunk-LZXEDZCA-CB2tlbwU.js} +2 -2
  22. package/dist/web/assets/{chunk-ND2GUHAM-BvZh95FB.js → chunk-ND2GUHAM-SF9ziKuY.js} +1 -1
  23. package/dist/web/assets/{chunk-NZK2D7GU-Cd_AyI2R.js → chunk-NZK2D7GU-cDloGQoC.js} +1 -1
  24. package/dist/web/assets/{chunk-O5CBEL6O-BWTFeGAA.js → chunk-O5CBEL6O-BKhNShTj.js} +1 -1
  25. package/dist/web/assets/chunk-QZHKN3VN-DOXAr87O.js +1 -0
  26. package/dist/web/assets/chunk-WU5MYG2G-HE1Sw8W0.js +1 -0
  27. package/dist/web/assets/{chunk-XPW4576I-BoQ5PHbI.js → chunk-XPW4576I-C5N-bWNh.js} +1 -1
  28. package/dist/web/assets/classDiagram-4FO5ZUOK-DhMmSdQh.js +1 -0
  29. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-C-ONPelI.js +1 -0
  30. package/dist/web/assets/{cose-bilkent-S5V4N54A-DEOEnRu1.js → cose-bilkent-S5V4N54A-CVt4gjy_.js} +1 -1
  31. package/dist/web/assets/{dagre-BM42HDAG-3EFNtJfA.js → dagre-BM42HDAG-DsFFRdXb.js} +1 -1
  32. package/dist/web/assets/{diagram-2AECGRRQ-Dl6nD1tR.js → diagram-2AECGRRQ-DU-SS203.js} +1 -1
  33. package/dist/web/assets/{diagram-5GNKFQAL-C2ZqLXAR.js → diagram-5GNKFQAL-Caxxw6ez.js} +1 -1
  34. package/dist/web/assets/{diagram-KO2AKTUF-CLemszwR.js → diagram-KO2AKTUF-C86fE-G2.js} +1 -1
  35. package/dist/web/assets/{diagram-LMA3HP47-WJtVAgyf.js → diagram-LMA3HP47-CbblsEig.js} +1 -1
  36. package/dist/web/assets/{diagram-OG6HWLK6-DLcglDzh.js → diagram-OG6HWLK6-VVwPWCNJ.js} +1 -1
  37. package/dist/web/assets/{dist-BD8BTEle.js → dist-DP09l7dv.js} +1 -1
  38. package/dist/web/assets/{erDiagram-TEJ5UH35-DB0o5lHM.js → erDiagram-TEJ5UH35-DybhgsWs.js} +1 -1
  39. package/dist/web/assets/{flowDiagram-I6XJVG4X-DGNyzO3n.js → flowDiagram-I6XJVG4X-L175L0f8.js} +1 -1
  40. package/dist/web/assets/{ganttDiagram-6RSMTGT7-DFcI8gjU.js → ganttDiagram-6RSMTGT7-Bnn1hKvs.js} +1 -1
  41. package/dist/web/assets/{gitGraphDiagram-PVQCEYII-q30GMhck.js → gitGraphDiagram-PVQCEYII-C0ZBrF0f.js} +1 -1
  42. package/dist/web/assets/{index-mFrEtl6e.js → index-B4AzQzzw.js} +24 -24
  43. package/dist/web/assets/index-uLsBVssy.css +1 -0
  44. package/dist/web/assets/{infoDiagram-5YYISTIA-Bn7G90QN.js → infoDiagram-5YYISTIA-BjE1edtT.js} +1 -1
  45. package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-BV3Sotps.js → ishikawaDiagram-YF4QCWOH-CA7-Z3nV.js} +1 -1
  46. package/dist/web/assets/{journeyDiagram-JHISSGLW-BtSXI5Am.js → journeyDiagram-JHISSGLW-7yRPAxui.js} +1 -1
  47. package/dist/web/assets/{kanban-definition-UN3LZRKU-B8tZB50Y.js → kanban-definition-UN3LZRKU-CxJbCLrw.js} +1 -1
  48. package/dist/web/assets/{line-ig55n5ES.js → line-BZChgTw_.js} +1 -1
  49. package/dist/web/assets/{linear-qGjeU4AP.js → linear-Ch3uwdzh.js} +1 -1
  50. package/dist/web/assets/{mermaid-parser.core-DxxQqVaE.js → mermaid-parser.core-BwmBog8o.js} +1 -1
  51. package/dist/web/assets/{mermaid.core-BBkLU4xy.js → mermaid.core-DbfGaZdz.js} +3 -3
  52. package/dist/web/assets/{mindmap-definition-RKZ34NQL-D61BQDKI.js → mindmap-definition-RKZ34NQL-DY-t2KPo.js} +1 -1
  53. package/dist/web/assets/{pieDiagram-4H26LBE5-GrXCe3Mv.js → pieDiagram-4H26LBE5-BkM6-CcI.js} +1 -1
  54. package/dist/web/assets/{quadrantDiagram-W4KKPZXB-Bmj1Zhyf.js → quadrantDiagram-W4KKPZXB-Bo2ow9Oq.js} +1 -1
  55. package/dist/web/assets/{requirementDiagram-4Y6WPE33-C4Wt9tYO.js → requirementDiagram-4Y6WPE33-BpPkW4oY.js} +1 -1
  56. package/dist/web/assets/{sankeyDiagram-5OEKKPKP-BpEXiP3R.js → sankeyDiagram-5OEKKPKP-Clh6BTES.js} +1 -1
  57. package/dist/web/assets/{sequenceDiagram-3UESZ5HK-BY0QnrHB.js → sequenceDiagram-3UESZ5HK-Cj1hGXAx.js} +1 -1
  58. package/dist/web/assets/{src-DKCcYPqn.js → src-Bm_zkrgy.js} +1 -1
  59. package/dist/web/assets/{stateDiagram-AJRCARHV-BNe3FO3i.js → stateDiagram-AJRCARHV-D34qh_xR.js} +1 -1
  60. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-zAS4zz8v.js +1 -0
  61. package/dist/web/assets/{timeline-definition-PNZ67QCA-BlWgTk_0.js → timeline-definition-PNZ67QCA-C14XIn0F.js} +1 -1
  62. package/dist/web/assets/{vennDiagram-CIIHVFJN-Dpfbo-af.js → vennDiagram-CIIHVFJN-CmRloXuF.js} +1 -1
  63. package/dist/web/assets/{wardleyDiagram-YWT4CUSO-BKnl9Arx.js → wardleyDiagram-YWT4CUSO-BJfba87H.js} +1 -1
  64. package/dist/web/assets/{xychartDiagram-2RQKCTM6-C5VJlVCG.js → xychartDiagram-2RQKCTM6-DnEMDZBA.js} +1 -1
  65. package/dist/web/index.html +2 -2
  66. package/package.json +1 -1
  67. package/dist/web/assets/channel-BPEgeY7j.js +0 -1
  68. package/dist/web/assets/chunk-QZHKN3VN-BM3ywZyg.js +0 -1
  69. package/dist/web/assets/chunk-WU5MYG2G-DwPzycK6.js +0 -1
  70. package/dist/web/assets/classDiagram-4FO5ZUOK-B-Ye1HF8.js +0 -1
  71. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-DnIZKORc.js +0 -1
  72. package/dist/web/assets/index-1vvZG57w.css +0 -1
  73. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-DMtybq8y.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 --new-session
45
46
  local-diff-reviewer stop
46
47
  local-diff-reviewer --repo /path/to/project
47
48
  ```
@@ -68,14 +69,19 @@ local-diff-reviewer --repo /path/to/project
68
69
  local-diff-reviewer --repo /path/to/project staged
69
70
  ```
70
71
 
71
- 每次启动都会创建独立的本地 review 会话。多个项目里分别执行 `local-diff-reviewer` 或 `/diff-review` 时,打开的页面会分别绑定启动时的项目,不会被最后一次启动覆盖。默认优先使用 `127.0.0.1:4966`;如果端口已被占用,会自动选择一个空闲端口。
72
+ 同一项目里再次执行 `local-diff-reviewer` 或 `/diff-review` 时,默认会复用仍在运行的 review 页面和端口,并将该页面刷新为最新 diff。使用 `--new-session` 可以保留已有快照,再打开一个独立页面。不同项目的页面始终分别绑定各自的项目,不会被最后一次启动覆盖。首次启动默认优先使用 `127.0.0.1:4966`;独立页面或其他项目遇到端口占用时会自动选择空闲端口。
72
73
 
73
74
  ```text
74
75
  项目 A /diff-review -> http://127.0.0.1:4966 -> 项目 A diff
76
+ 项目 A /diff-review -> http://127.0.0.1:4966 -> 刷新为项目 A 最新 diff
77
+ 项目 A /diff-review --new-session -> http://127.0.0.1:<空闲端口> -> 项目 A 独立快照
75
78
  项目 B /diff-review -> http://127.0.0.1:<空闲端口> -> 项目 B diff
76
79
  ```
77
80
 
78
- 页面打开后,代码 diff 和 Markdown 预览会固定为本次启动时的快照;工作区继续变动不会改写已打开页面里的代码内容。评论线程会在页面可见时持续同步。若需要审查最新工作区内容,请再次执行 `local-diff-reviewer` 或 `/diff-review` 打开新的 review 会话。
81
+ 页面打开后,代码 diff 和 Markdown 预览会固定为当前审查快照;工作区继续变动不会自行改写页面内容。再次执行 `local-diff-reviewer` 或 `/diff-review` 会让默认页面自动同步到新快照;使用 `--new-session` 打开的独立页面继续保留旧快照。评论线程与其创建时的 diff 快照绑定:旧线程会在评论侧栏中保留并标记为历史快照,但不会因另一份快照中恰好有相同行号而贴到错误代码上。
82
+
83
+ 评论、快照、代码行之间的绑定关系及页面更新判断详见
84
+ [`docs/comment-snapshot-binding.md`](docs/comment-snapshot-binding.md)。
79
85
 
80
86
  注意:本地开发的 `--dev` 模式仍使用 Vite dev server,端口和 API proxy 是固定的;多项目并行审查请使用默认的构建页面模式。
81
87
 
@@ -87,6 +93,7 @@ local-diff-reviewer --repo /path/to/project staged
87
93
  /diff-review
88
94
  /diff-review staged
89
95
  /diff-review HEAD~1 HEAD
96
+ /diff-review --new-session
90
97
  /diff-review stop
91
98
  ```
92
99
 
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 --new-session`: preserve the current snapshot and open a separate review session.
18
19
  - `/diff-review stop`: stop all review runtimes created for the current workspace repository.
19
20
 
20
21
  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:
@@ -33,7 +34,7 @@ npx --yes local-diff-reviewer [args...] \
33
34
  --comment '{"type":"reply","threadId":"existing-thread-id","body":"Answer the existing thread as the agent."}'
34
35
  ```
35
36
 
36
- After the script prints a local URL, open it in the Codex browser when available. If browser automation is not available, report the URL.
37
+ After a newly started script prints a local URL, open it in the Codex browser when available. If the script reports `Diff Review refreshed`, do not open another page: the already open review page updates automatically. If browser automation is not available, report the URL.
37
38
 
38
39
  When the user gives you copied prompt text containing `[thread:<id>]`, treat it as an existing review thread. After you answer or make code/doc changes for that thread, append a concise agent reply to the same thread with `--comment '{"type":"reply","threadId":"<id>","body":"..."}'` the next time you launch or refresh the viewer. If the viewer is already running and you can reach the API, post the same reply to `/api/threads/<id>/comments` with `author: "agent"`. Do this for every handled thread unless the user explicitly asks you not to write back. The reply should say what changed or why no change was made, not repeat the full diff.
39
40
 
@@ -46,6 +47,7 @@ When the user gives you copied prompt text containing `[thread:<id>]`, treat it
46
47
  - AI prompt copy output includes `[thread:<id>]`, file path, line number or Markdown source line, and comment body.
47
48
  - Agent findings can be preloaded as comments with `--comment`.
48
49
  - A thread can contain multiple comments. New findings for the same anchor are appended to the existing thread, and repeated agent comments with identical bodies in the same thread are skipped.
50
+ - Comments are associated with the diff snapshot where they were created. Refreshed workbenches retain older threads in the comment rail as history, without attaching old line comments to changed content.
49
51
 
50
52
  ## Comment Arguments
51
53
 
package/dist/cli/start.js CHANGED
@@ -42,7 +42,23 @@ function getMergedThreadStatus(threads) {
42
42
  }
43
43
 
44
44
  // src/server/storage.ts
45
- async function readComments(repoRoot) {
45
+ async function readComments(repoRoot, diffHash2) {
46
+ const store = await readCommentStore(repoRoot);
47
+ if (!diffHash2) return store;
48
+ return { threads: store.threads.filter((thread) => thread.diffHash === diffHash2) };
49
+ }
50
+ async function attachLegacyComments(repoRoot, diffHash2) {
51
+ const store = await readCommentStore(repoRoot);
52
+ let changed = false;
53
+ for (const thread of store.threads) {
54
+ if (!thread.diffHash) {
55
+ thread.diffHash = diffHash2;
56
+ changed = true;
57
+ }
58
+ }
59
+ if (changed) await writeComments(repoRoot, store);
60
+ }
61
+ async function readCommentStore(repoRoot) {
46
62
  const path = commentsPath(repoRoot);
47
63
  try {
48
64
  return normalizeStore(JSON.parse(await readFile(path, "utf8")));
@@ -77,7 +93,7 @@ async function readLegacyComments(repoRoot) {
77
93
  function normalizeStore(store) {
78
94
  const groups = /* @__PURE__ */ new Map();
79
95
  for (const thread of store.threads) {
80
- const key = anchorKey(thread.anchor);
96
+ const key = `${thread.diffHash ?? "legacy"}:${anchorKey(thread.anchor)}`;
81
97
  groups.set(key, [...groups.get(key) ?? [], thread]);
82
98
  }
83
99
  return {
@@ -105,7 +121,7 @@ function earliestTimestamp(values) {
105
121
  }
106
122
 
107
123
  // src/core/comment-import.ts
108
- async function importAgentComments(repoRoot, diffFiles, rawComments) {
124
+ async function importAgentComments(repoRoot, diffHash2, diffFiles, rawComments) {
109
125
  const result = { imported: 0, skipped: [] };
110
126
  if (rawComments.length === 0) return result;
111
127
  const store = await readComments(repoRoot);
@@ -119,9 +135,9 @@ async function importAgentComments(repoRoot, diffFiles, rawComments) {
119
135
  }
120
136
  continue;
121
137
  }
122
- const thread = buildAgentThread(parsed, diffFiles, result, label);
138
+ const thread = buildAgentThread(parsed, diffHash2, diffFiles, result, label);
123
139
  if (!thread) continue;
124
- const existingThread = store.threads.find((item) => sameAnchor(item.anchor, thread.anchor));
140
+ const existingThread = store.threads.find((item) => item.diffHash === diffHash2 && sameAnchor(item.anchor, thread.anchor));
125
141
  if (hasDuplicateAgentComment(store.threads, thread)) {
126
142
  result.skipped.push(`${label}: duplicate agent comment skipped`);
127
143
  continue;
@@ -197,7 +213,7 @@ function appendAgentReply(threads, comment, result, label) {
197
213
  thread.updatedAt = now;
198
214
  return true;
199
215
  }
200
- function buildAgentThread(comment, diffFiles, result, label) {
216
+ function buildAgentThread(comment, diffHash2, diffFiles, result, label) {
201
217
  const file = diffFiles.find((item) => item.path === comment.filePath || item.oldPath === comment.filePath || item.newPath === comment.filePath);
202
218
  if (!file) {
203
219
  result.skipped.push(`${label}: ${comment.filePath} is not present in the current diff`);
@@ -210,6 +226,7 @@ function buildAgentThread(comment, diffFiles, result, label) {
210
226
  id: crypto.randomUUID(),
211
227
  filePath: anchor.filePath,
212
228
  anchor,
229
+ diffHash: diffHash2,
213
230
  status: "replied",
214
231
  comments: [
215
232
  {
@@ -256,7 +273,7 @@ function buildAnchor(file, comment, result, label) {
256
273
  function hasDuplicateAgentComment(threads, nextThread) {
257
274
  const nextComment = nextThread.comments[0]?.body.trim();
258
275
  return threads.some((thread) => {
259
- return thread.comments.some((comment) => comment.author === "agent" && comment.body.trim() === nextComment) && thread.filePath === nextThread.filePath && sameAnchor(thread.anchor, nextThread.anchor);
276
+ return thread.comments.some((comment) => comment.author === "agent" && comment.body.trim() === nextComment) && thread.diffHash === nextThread.diffHash && thread.filePath === nextThread.filePath && sameAnchor(thread.anchor, nextThread.anchor);
260
277
  });
261
278
  }
262
279
  function diffLineExists(file, side, lineNumber) {
@@ -523,6 +540,10 @@ async function recordRuntime(entry) {
523
540
  deduped.push(entry);
524
541
  await writeRuntime(path, { entries: deduped });
525
542
  }
543
+ async function getLiveRuntimes(repoRoot) {
544
+ const store = await readRuntime(runtimePath(repoRoot));
545
+ return store.entries.filter((entry) => isPidAlive(entry.pid)).sort((left, right) => right.startedAt.localeCompare(left.startedAt));
546
+ }
526
547
  async function stopRecordedRuntimes(repoRoot) {
527
548
  const path = runtimePath(repoRoot);
528
549
  const store = await readRuntime(path);
@@ -707,7 +728,7 @@ function getAnchorLine(thread) {
707
728
 
708
729
  // src/server/index.ts
709
730
  async function startServer(state, port = 4966) {
710
- const markdownPreviews = await buildMarkdownPreviewCache(state);
731
+ let markdownPreviews = await buildMarkdownPreviewCache(state);
711
732
  const app = express();
712
733
  app.use(express.json({ limit: "2mb" }));
713
734
  app.get("/api/session", (_req, res) => {
@@ -724,6 +745,26 @@ async function startServer(state, port = 4966) {
724
745
  next(error);
725
746
  }
726
747
  });
748
+ app.post("/api/review-state", async (req, res, next) => {
749
+ try {
750
+ const nextState = req.body;
751
+ if (!nextState.session || nextState.session.repoRoot !== state.session.repoRoot || !Array.isArray(nextState.diffFiles)) {
752
+ res.status(400).json({ error: "Review state must target the running repository" });
753
+ return;
754
+ }
755
+ const nextReviewState = {
756
+ session: nextState.session,
757
+ diffFiles: nextState.diffFiles,
758
+ webDist: state.webDist
759
+ };
760
+ markdownPreviews = await buildMarkdownPreviewCache(nextReviewState);
761
+ state.session = nextReviewState.session;
762
+ state.diffFiles = nextReviewState.diffFiles;
763
+ res.json({ session: state.session, files: state.diffFiles });
764
+ } catch (error) {
765
+ next(error);
766
+ }
767
+ });
727
768
  app.get("/api/markdown-preview", async (req, res, next) => {
728
769
  try {
729
770
  const filePath = String(req.query.path ?? "");
@@ -770,7 +811,7 @@ async function startServer(state, port = 4966) {
770
811
  return;
771
812
  }
772
813
  const store = await readComments(state.session.repoRoot);
773
- const existingThread = store.threads.find((thread2) => sameAnchor(thread2.anchor, body.anchor));
814
+ const existingThread = store.threads.find((thread2) => thread2.diffHash === state.session.diffHash && sameAnchor(thread2.anchor, body.anchor));
774
815
  const comment = {
775
816
  id: crypto.randomUUID(),
776
817
  body: commentBody,
@@ -790,6 +831,7 @@ async function startServer(state, port = 4966) {
790
831
  id: crypto.randomUUID(),
791
832
  filePath: body.filePath,
792
833
  anchor: body.anchor,
834
+ diffHash: state.session.diffHash,
793
835
  status: "submit",
794
836
  comments: [comment],
795
837
  createdAt: now,
@@ -996,7 +1038,7 @@ function selectPromptThreads(threads, scope) {
996
1038
  var packageRoot = resolve2(dirname3(fileURLToPath(import.meta.url)), "../..");
997
1039
  var builtWebDist = join5(packageRoot, "dist", "web");
998
1040
  async function main() {
999
- const { command, dev, repo, reviewArgs, comments } = parseCliOptions(process.argv.slice(2));
1041
+ const { command, dev, newSession, repo, reviewArgs, comments } = parseCliOptions(process.argv.slice(2));
1000
1042
  if (command === "stop") {
1001
1043
  await stopCommand(repo);
1002
1044
  return;
@@ -1013,7 +1055,20 @@ async function main() {
1013
1055
  diffHash: diffHash(diff),
1014
1056
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1015
1057
  };
1016
- const importResult = await importAgentComments(repoRoot, diffFiles, comments);
1058
+ await attachLegacyComments(repoRoot, session.diffHash);
1059
+ const importResult = await importAgentComments(repoRoot, session.diffHash, diffFiles, comments);
1060
+ if (!newSession && !dev) {
1061
+ const reusedUrl = await refreshRunningReview(session, diffFiles);
1062
+ if (reusedUrl) {
1063
+ console.log(`Diff Review refreshed: ${reusedUrl}`);
1064
+ console.log(`Repo: ${session.repoName} (${repoRoot})`);
1065
+ console.log(`Mode: ${modeLabel(mode)}`);
1066
+ console.log(`Files: ${diffFiles.length}`);
1067
+ console.log("The existing review page will update automatically.");
1068
+ logImportResult(comments, importResult);
1069
+ return;
1070
+ }
1071
+ }
1017
1072
  const hasBuiltWeb = existsSync2(join5(builtWebDist, "index.html"));
1018
1073
  const apiUrl = await startServer({ session, diffFiles, webDist: hasBuiltWeb ? builtWebDist : void 0 });
1019
1074
  const useVite = dev || !hasBuiltWeb;
@@ -1036,12 +1091,7 @@ async function main() {
1036
1091
  }
1037
1092
  console.log(`Mode: ${modeLabel(mode)}`);
1038
1093
  console.log(`Files: ${diffFiles.length}`);
1039
- if (comments.length > 0) {
1040
- console.log(`Agent comments imported: ${importResult.imported}`);
1041
- for (const skipped of importResult.skipped) {
1042
- console.warn(`Skipped ${skipped}`);
1043
- }
1044
- }
1094
+ logImportResult(comments, importResult);
1045
1095
  }
1046
1096
  function parseCliOptions(args) {
1047
1097
  let command = "review";
@@ -1049,12 +1099,17 @@ function parseCliOptions(args) {
1049
1099
  const comments = [];
1050
1100
  let repo;
1051
1101
  let dev = false;
1102
+ let newSession = false;
1052
1103
  for (let index = 0; index < args.length; index += 1) {
1053
1104
  const arg = args[index];
1054
1105
  if (arg === "--dev") {
1055
1106
  dev = true;
1056
1107
  continue;
1057
1108
  }
1109
+ if (arg === "--new-session") {
1110
+ newSession = true;
1111
+ continue;
1112
+ }
1058
1113
  if (arg === "stop") {
1059
1114
  command = "stop";
1060
1115
  continue;
@@ -1087,7 +1142,31 @@ function parseCliOptions(args) {
1087
1142
  }
1088
1143
  reviewArgs.push(arg);
1089
1144
  }
1090
- return { command, dev, repo, reviewArgs, comments };
1145
+ return { command, dev, newSession, repo, reviewArgs, comments };
1146
+ }
1147
+ async function refreshRunningReview(session, diffFiles) {
1148
+ const runtimes = await getLiveRuntimes(session.repoRoot);
1149
+ for (const runtime of runtimes) {
1150
+ const apiUrl = `http://127.0.0.1:${runtime.apiPort}`;
1151
+ try {
1152
+ const response = await fetch(`${apiUrl}/api/review-state`, {
1153
+ method: "POST",
1154
+ headers: { "Content-Type": "application/json" },
1155
+ body: JSON.stringify({ session, diffFiles })
1156
+ });
1157
+ if (!response.ok) continue;
1158
+ return runtime.usesVite ? "http://127.0.0.1:5173" : apiUrl;
1159
+ } catch {
1160
+ }
1161
+ }
1162
+ return void 0;
1163
+ }
1164
+ function logImportResult(comments, result) {
1165
+ if (comments.length === 0) return;
1166
+ console.log(`Agent comments imported: ${result.imported}`);
1167
+ for (const skipped of result.skipped) {
1168
+ console.warn(`Skipped ${skipped}`);
1169
+ }
1091
1170
  }
1092
1171
  async function stopCommand(repo) {
1093
1172
  const repoRoot = await getRepoRoot(repo ?? process.cwd());
@@ -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-BD8BTEle.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-DP09l7dv.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};