local-diff-reviewer 4.0.2 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,6 +29,7 @@ npx skills add Mone-Lee/diff-review
29
29
 
30
30
  - 查看当前工作区 diff、staged diff、指定 revision diff。
31
31
  - 代码文件使用 GitHub 风格 unified diff。
32
+ - 图片文件支持 diff 查看。
32
33
  - Markdown 文件支持 `Preview / Code diff` 切换;`Code diff` 当前只支持 side-by-side,不支持 inline。
33
34
  - 支持文件级评论、代码行级评论、Markdown source line 评论。
34
35
  - Markdown 评论在两种视图间会按视图能力降级展示:
@@ -59,7 +60,7 @@ local-diff-reviewer --repo /path/to/project
59
60
 
60
61
  停止当前项目已启动的 review 进程:
61
62
 
62
- - `stop`:按当前 Git 仓库范围清理该仓库启动过的 review 运行进程(包含其 API 端口进程)。
63
+ - `stop`:按当前 Git 仓库范围强制关闭该仓库启动过的 review 运行进程(包含其 API 端口进程);会先尝试优雅退出,超时后自动升级为强制终止。
63
64
 
64
65
  ```bash
65
66
  local-diff-reviewer stop
@@ -107,7 +108,7 @@ local-diff-reviewer --repo /path/to/project staged
107
108
  npx skills add Mone-Lee/diff-review
108
109
  ```
109
110
 
110
- skill 会以目标 workspace 作为命令工作目录运行 `npx --yes local-diff-reviewer [args...]`,因此 `/diff-review` 会审查当前项目,而不是 skill 安装目录。
111
+ skill 会以目标 workspace 作为命令工作目录运行 `npx --yes local-diff-reviewer [args...]`,因此 `/diff-review` 会审查当前项目,而不是 skill 安装目录。用户要求停止、关闭或结束当前项目的 Diff Review 时,skill 应直接执行 `/diff-review stop`,而不是只提示这条命令。
111
112
 
112
113
  ### 预置 agent 评论
113
114
 
package/SKILL.md CHANGED
@@ -16,7 +16,7 @@ Use this skill when the user asks for `/diff-review`, wants to inspect current w
16
16
  - `/diff-review staged`: review staged diff.
17
17
  - `/diff-review <base> <target>`: review diff between two Git revisions.
18
18
  - `/diff-review --new-session`: preserve the current snapshot and open a separate review session.
19
- - `/diff-review stop`: stop all review runtimes created for the current workspace repository.
19
+ - `/diff-review stop`: force close all review runtimes created for the current workspace repository.
20
20
 
21
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:
22
22
 
@@ -26,6 +26,8 @@ npx --yes local-diff-reviewer [args...]
26
26
 
27
27
  Set the shell/tool `cwd` to `/absolute/path/to/target/workspace` before running the command. Do not pass `--repo` from this skill; older published CLI versions treat unknown args as revision args. Do not use the skill package directory or this skill's install directory as the review target unless that is the workspace the user asked to review.
28
28
 
29
+ When the user asks to `stop`, `关闭`, `结束`, or otherwise shut down Diff Review for the current project, execute `/diff-review stop` immediately. Do not only explain the command or leave the existing runtime running.
30
+
29
31
  When you have concrete review findings or answers to existing review comments, preload them with one `--comment` JSON argument per comment before launching the viewer:
30
32
 
31
33
  ```bash
package/dist/cli/start.js CHANGED
@@ -718,6 +718,9 @@ import { createHash as createHash4 } from "node:crypto";
718
718
  import { access, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
719
719
  import { homedir as homedir2, platform as platform2 } from "node:os";
720
720
  import { basename as basename2, dirname as dirname2, join as join3 } from "node:path";
721
+ var TERM_WAIT_MS = 800;
722
+ var KILL_WAIT_MS = 1200;
723
+ var POLL_INTERVAL_MS = 100;
721
724
  async function recordRuntime(entry) {
722
725
  const path = runtimePath(entry.repoRoot);
723
726
  const store = await readRuntime(path);
@@ -734,6 +737,7 @@ async function stopRecordedRuntimes(repoRoot) {
734
737
  const path = runtimePath(repoRoot);
735
738
  const store = await readRuntime(path);
736
739
  const stopped = [];
740
+ const forced = [];
737
741
  const stale = [];
738
742
  for (const entry of store.entries) {
739
743
  const parentAlive = isPidAlive(entry.pid);
@@ -742,16 +746,21 @@ async function stopRecordedRuntimes(repoRoot) {
742
746
  stale.push(entry);
743
747
  continue;
744
748
  }
745
- try {
746
- if (parentAlive) process.kill(entry.pid, "SIGTERM");
747
- if (viteAlive) process.kill(entry.vitePid, "SIGTERM");
749
+ const termination = await terminateRuntime(entry);
750
+ if (termination.status === "stopped") {
748
751
  stopped.push(entry);
749
- } catch {
752
+ continue;
753
+ }
754
+ if (termination.status === "forced") {
755
+ forced.push(entry);
756
+ continue;
757
+ }
758
+ if (termination.status === "stale") {
750
759
  stale.push(entry);
751
760
  }
752
761
  }
753
762
  await writeRuntime(path, { entries: [] });
754
- return { stopped, stale };
763
+ return { stopped, forced, stale };
755
764
  }
756
765
  function runtimePath(repoRoot) {
757
766
  const repoName = basename2(repoRoot) || "repo";
@@ -793,6 +802,45 @@ function isPidAlive(pid) {
793
802
  return false;
794
803
  }
795
804
  }
805
+ async function terminateRuntime(entry) {
806
+ const pids = collectTargetPids(entry);
807
+ if (pids.length === 0) return { status: "stale" };
808
+ const terminated = await terminatePids(pids, "SIGTERM", TERM_WAIT_MS);
809
+ if (terminated) return { status: "stopped" };
810
+ const forced = await terminatePids(pids, "SIGKILL", KILL_WAIT_MS);
811
+ return forced ? { status: "forced" } : { status: "stale" };
812
+ }
813
+ function collectTargetPids(entry) {
814
+ const pids = [];
815
+ if (typeof entry.vitePid === "number" && isPidAlive(entry.vitePid)) pids.push(entry.vitePid);
816
+ if (isPidAlive(entry.pid)) pids.push(entry.pid);
817
+ return pids;
818
+ }
819
+ async function terminatePids(pids, signal, waitMs) {
820
+ let signaled = false;
821
+ for (const pid of pids) {
822
+ if (!isPidAlive(pid)) continue;
823
+ try {
824
+ process.kill(pid, signal);
825
+ signaled = true;
826
+ } catch {
827
+ return false;
828
+ }
829
+ }
830
+ if (!signaled) return true;
831
+ return waitForExit(pids, waitMs);
832
+ }
833
+ async function waitForExit(pids, waitMs) {
834
+ const deadline = Date.now() + waitMs;
835
+ while (Date.now() < deadline) {
836
+ if (pids.every((pid) => !isPidAlive(pid))) return true;
837
+ await sleep(POLL_INTERVAL_MS);
838
+ }
839
+ return pids.every((pid) => !isPidAlive(pid));
840
+ }
841
+ function sleep(ms) {
842
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
843
+ }
796
844
  async function hasRuntimeRecord(repoRoot) {
797
845
  try {
798
846
  await access(runtimePath(repoRoot));
@@ -1644,8 +1692,8 @@ function logImportResult(comments, result) {
1644
1692
  async function stopCommand(repo) {
1645
1693
  const repoRoot = await getRepoRoot(repo ?? process.cwd());
1646
1694
  const hasRecord = await hasRuntimeRecord(repoRoot);
1647
- const { stopped, stale } = await stopRecordedRuntimes(repoRoot);
1648
- const total = stopped.length + stale.length;
1695
+ const { stopped, forced, stale } = await stopRecordedRuntimes(repoRoot);
1696
+ const total = stopped.length + forced.length + stale.length;
1649
1697
  if (total === 0) {
1650
1698
  if (hasRecord) {
1651
1699
  console.log("No running review process found for this repo.");
@@ -1660,6 +1708,14 @@ async function stopCommand(repo) {
1660
1708
  `- pid=${entry.pid} vitePid=${entry.vitePid ?? "-"} apiPort=${entry.apiPort} vitePort=${entry.vitePort ?? "-"} vite=${entry.usesVite ? "yes" : "no"} startedAt=${entry.startedAt}`
1661
1709
  );
1662
1710
  }
1711
+ if (forced.length > 0) {
1712
+ console.log(`Force killed review runtimes: ${forced.length}`);
1713
+ for (const entry of forced) {
1714
+ console.log(
1715
+ `- pid=${entry.pid} vitePid=${entry.vitePid ?? "-"} apiPort=${entry.apiPort} vitePort=${entry.vitePort ?? "-"} vite=${entry.usesVite ? "yes" : "no"} startedAt=${entry.startedAt}`
1716
+ );
1717
+ }
1718
+ }
1663
1719
  if (stale.length > 0) {
1664
1720
  console.log(`Skipped stale records: ${stale.length}`);
1665
1721
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-diff-reviewer",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "private": false,
5
5
  "description": "Open a local GitHub-style diff review Web UI for the current repository.",
6
6
  "repository": {