local-diff-reviewer 1.0.1 → 1.0.5
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 +42 -54
- package/SKILL.md +5 -3
- package/dist/cli/start.js +29 -5
- package/docs/images/diff-review-ui.jpg +0 -0
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# diff-review
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
AI chat 里的本地代码审查工具。可以直接用 CLI 打开,也可以安装成 agent skill。
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ AI chat 里的本地代码审查工具。可以直接用 CLI 打开,也可以
|
|
|
9
9
|
Try it first:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx local-diff-reviewer
|
|
12
|
+
npx --yes local-diff-reviewer
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Install and use:
|
|
@@ -36,59 +36,13 @@ npx skills add Mone-Lee/diff-review
|
|
|
36
36
|
- 支持通过内部 `--comment` 参数预置 agent findings / replies。
|
|
37
37
|
- 支持复制极简 AI prompt。
|
|
38
38
|
|
|
39
|
-
## 评论定位原理(新手友好版)
|
|
40
|
-
|
|
41
|
-
很多人会直觉认为:评论只和“第几行”绑定。
|
|
42
|
-
比如删掉第 10 行后,原来第 11 行顶上来变成第 10 行,评论也应该跟着走。
|
|
43
|
-
|
|
44
|
-
这个工具不是这样做的。它用的是“锚点”定位,锚点至少包含:
|
|
45
|
-
|
|
46
|
-
- 文件路径(`filePath`)
|
|
47
|
-
- 行号(`lineNumber`)
|
|
48
|
-
- 行所在版本(`side`:旧版本 `old` / 新版本 `new`,代码 Diff 专用)
|
|
49
|
-
|
|
50
|
-
这意味着评论绑定的是“哪一个版本里的哪一行”,而不是“屏幕上现在第几行”。
|
|
51
|
-
|
|
52
|
-
### 为什么删除内容后评论可能不显示?
|
|
53
|
-
|
|
54
|
-
- 对代码 Diff 行评论:
|
|
55
|
-
- 如果评论当时加在被删除的旧行上,它的锚点是 `old + 行号`。
|
|
56
|
-
- 删除后右侧展示的是新版本内容(`new`),`old` 锚点找不到对应渲染位置时,就不会显示在行内。
|
|
57
|
-
- 对 Markdown 行评论:
|
|
58
|
-
- 评论绑定的是 source line(源文件行号)。
|
|
59
|
-
- 被评论的那段 Markdown 真被删掉后,预览里没有对应块可挂载,行内自然不显示。
|
|
60
|
-
|
|
61
|
-
### 一个非常短的例子
|
|
62
|
-
|
|
63
|
-
1. 你在 `old` 版本第 20 行加了评论。
|
|
64
|
-
2. 后续改动把第 20 行删除,并让后面内容上移。
|
|
65
|
-
3. 新内容虽然“占了第 20 行的位置”,但它属于 `new` 版本,不是原来的 `old` 行。
|
|
66
|
-
4. 所以系统不会把旧评论自动贴到新内容上。
|
|
67
|
-
|
|
68
|
-
这样做的好处是:避免评论“串行”到语义完全不同的内容上,减少误导。
|
|
69
|
-
|
|
70
|
-
复制出的 prompt 包含 thread id、定位信息和评论内容。thread id 用于让 agent 后续通过 `--comment '{"type":"reply",...}'` 精确回复原评论:
|
|
71
|
-
|
|
72
|
-
```text
|
|
73
|
-
[thread:<thread-id>]
|
|
74
|
-
文件路径:行号
|
|
75
|
-
评论内容
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
文件级评论不包含行号:
|
|
79
|
-
|
|
80
|
-
```text
|
|
81
|
-
[thread:<thread-id>]
|
|
82
|
-
文件路径
|
|
83
|
-
评论内容
|
|
84
|
-
```
|
|
85
|
-
|
|
86
39
|
## CLI 使用方式
|
|
87
40
|
|
|
88
41
|
```bash
|
|
89
42
|
local-diff-reviewer
|
|
90
43
|
local-diff-reviewer staged
|
|
91
44
|
local-diff-reviewer HEAD~1 HEAD
|
|
45
|
+
local-diff-reviewer --repo /path/to/project
|
|
92
46
|
```
|
|
93
47
|
|
|
94
48
|
这三种模式分别表示:
|
|
@@ -97,6 +51,22 @@ local-diff-reviewer HEAD~1 HEAD
|
|
|
97
51
|
- `staged`:审查已经 `git add`、但还没有提交的改动。
|
|
98
52
|
- `revision`:审查两个 revision 之间的差异,例如 `local-diff-reviewer HEAD~1 HEAD` 会比较 `HEAD~1..HEAD`。
|
|
99
53
|
|
|
54
|
+
如果命令不是在目标项目目录里启动,可以用 `--repo <path>` 显式指定要审查的 Git 仓库:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
local-diff-reviewer --repo /path/to/project
|
|
58
|
+
local-diff-reviewer --repo /path/to/project staged
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
每次启动都会创建独立的本地 review 会话。多个项目里分别执行 `local-diff-reviewer` 或 `/diff-review` 时,打开的页面会分别绑定启动时的项目,不会被最后一次启动覆盖。默认优先使用 `127.0.0.1:4966`;如果端口已被占用,会自动选择一个空闲端口。
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
项目 A /diff-review -> http://127.0.0.1:4966 -> 项目 A diff
|
|
65
|
+
项目 B /diff-review -> http://127.0.0.1:<空闲端口> -> 项目 B diff
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
注意:本地开发的 `--dev` 模式仍使用 Vite dev server,端口和 API proxy 是固定的;多项目并行审查请使用默认的构建页面模式。
|
|
69
|
+
|
|
100
70
|
## Skill 使用方式
|
|
101
71
|
|
|
102
72
|
在 AI chat 中使用:
|
|
@@ -113,7 +83,7 @@ local-diff-reviewer HEAD~1 HEAD
|
|
|
113
83
|
npx skills add Mone-Lee/diff-review
|
|
114
84
|
```
|
|
115
85
|
|
|
116
|
-
skill
|
|
86
|
+
skill 会针对目标 workspace 运行 `npx --yes local-diff-reviewer --repo <workspace> [args...]`。
|
|
117
87
|
|
|
118
88
|
### 预置 agent 评论
|
|
119
89
|
|
|
@@ -122,28 +92,28 @@ CLI 支持重复传入 `--comment <json>`,用于在打开 UI 前把 agent 审
|
|
|
122
92
|
代码 diff 行评论:
|
|
123
93
|
|
|
124
94
|
```bash
|
|
125
|
-
npx local-diff-reviewer \
|
|
95
|
+
npx --yes local-diff-reviewer \
|
|
126
96
|
--comment '{"type":"thread","filePath":"src/foo.ts","position":{"side":"new","line":36},"body":"这里没有处理空数组,可能导致运行时报错。"}'
|
|
127
97
|
```
|
|
128
98
|
|
|
129
99
|
Markdown source line 评论:
|
|
130
100
|
|
|
131
101
|
```bash
|
|
132
|
-
npx local-diff-reviewer \
|
|
102
|
+
npx --yes local-diff-reviewer \
|
|
133
103
|
--comment '{"type":"thread","filePath":"README.md","position":{"type":"markdown","line":22},"body":"这里可以补充 old/new side 的例子。"}'
|
|
134
104
|
```
|
|
135
105
|
|
|
136
106
|
文件级评论:
|
|
137
107
|
|
|
138
108
|
```bash
|
|
139
|
-
npx local-diff-reviewer \
|
|
109
|
+
npx --yes local-diff-reviewer \
|
|
140
110
|
--comment '{"type":"thread","filePath":"src/foo.ts","body":"这个文件的错误处理策略需要统一。"}'
|
|
141
111
|
```
|
|
142
112
|
|
|
143
113
|
回复已有 thread:
|
|
144
114
|
|
|
145
115
|
```bash
|
|
146
|
-
npx local-diff-reviewer \
|
|
116
|
+
npx --yes local-diff-reviewer \
|
|
147
117
|
--comment '{"type":"reply","threadId":"<thread-id>","body":"同意,这里应该按 repoRoot 隔离评论存储。"}'
|
|
148
118
|
```
|
|
149
119
|
|
|
@@ -157,6 +127,24 @@ npx local-diff-reviewer \
|
|
|
157
127
|
- `replied`:已有 agent 内容,或从 resolved 重新打开。
|
|
158
128
|
- `resolved`:用户确认完成后的状态。
|
|
159
129
|
|
|
130
|
+
## 发布流程
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm run release
|
|
134
|
+
npm run release minor
|
|
135
|
+
npm run release major
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
该命令会按顺序执行:
|
|
139
|
+
|
|
140
|
+
- `npm run release:check`
|
|
141
|
+
- `npm version <patch|minor|major>`(默认 `patch`)
|
|
142
|
+
- `npm publish`
|
|
143
|
+
- `git push`
|
|
144
|
+
- `git push --tags`
|
|
145
|
+
|
|
146
|
+
只有在 `npm publish` 成功后,才会自动推送提交和标签到 GitHub。
|
|
147
|
+
|
|
160
148
|
## 本地开发
|
|
161
149
|
|
|
162
150
|
```bash
|
package/SKILL.md
CHANGED
|
@@ -16,16 +16,18 @@ 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
|
|
|
19
|
-
Do not ask the user to run a shell CLI manually.
|
|
19
|
+
Do not ask the user to run a shell CLI manually. Determine the target workspace/repository from the user's active environment context, then pass it explicitly with `--repo`:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npx local-diff-reviewer [args...]
|
|
22
|
+
npx --yes local-diff-reviewer --repo /absolute/path/to/target/workspace [args...]
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
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.
|
|
26
|
+
|
|
25
27
|
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:
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
|
-
npx local-diff-reviewer [args...] \
|
|
30
|
+
npx --yes local-diff-reviewer --repo /absolute/path/to/target/workspace [args...] \
|
|
29
31
|
--comment '{"type":"thread","filePath":"src/foo.ts","position":{"side":"new","line":36},"body":"Explain the finding in the user language."}' \
|
|
30
32
|
--comment '{"type":"reply","threadId":"existing-thread-id","body":"Answer the existing thread as the agent."}'
|
|
31
33
|
```
|
package/dist/cli/start.js
CHANGED
|
@@ -860,12 +860,22 @@ async function startServer(state, port = 4966) {
|
|
|
860
860
|
app.use((error, _req, res, _next) => {
|
|
861
861
|
res.status(500).json({ error: error.message });
|
|
862
862
|
});
|
|
863
|
-
return
|
|
863
|
+
return listen(app, port);
|
|
864
|
+
}
|
|
865
|
+
function listen(app, port) {
|
|
866
|
+
return new Promise((resolve3, reject) => {
|
|
864
867
|
const server = app.listen(port, "127.0.0.1", () => {
|
|
865
868
|
const address = server.address();
|
|
866
869
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
867
870
|
resolve3(`http://127.0.0.1:${actualPort}`);
|
|
868
871
|
});
|
|
872
|
+
server.once("error", (error) => {
|
|
873
|
+
if (error.code === "EADDRINUSE" && port !== 0) {
|
|
874
|
+
listen(app, 0).then(resolve3, reject);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
reject(error);
|
|
878
|
+
});
|
|
869
879
|
});
|
|
870
880
|
}
|
|
871
881
|
function selectPromptThreads(threads, scope) {
|
|
@@ -880,9 +890,9 @@ function selectPromptThreads(threads, scope) {
|
|
|
880
890
|
var packageRoot = resolve2(dirname2(fileURLToPath(import.meta.url)), "../..");
|
|
881
891
|
var builtWebDist = join4(packageRoot, "dist", "web");
|
|
882
892
|
async function main() {
|
|
883
|
-
const { dev, reviewArgs, comments } = parseCliOptions(process.argv.slice(2));
|
|
893
|
+
const { dev, repo, reviewArgs, comments } = parseCliOptions(process.argv.slice(2));
|
|
884
894
|
const mode = parseReviewMode(reviewArgs);
|
|
885
|
-
const repoRoot = await getRepoRoot(process.cwd());
|
|
895
|
+
const repoRoot = await getRepoRoot(repo ?? process.cwd());
|
|
886
896
|
const diff = await getDiff(mode, repoRoot);
|
|
887
897
|
const diffFiles = parseUnifiedDiff(diff);
|
|
888
898
|
const session = {
|
|
@@ -914,6 +924,7 @@ async function main() {
|
|
|
914
924
|
function parseCliOptions(args) {
|
|
915
925
|
const reviewArgs = [];
|
|
916
926
|
const comments = [];
|
|
927
|
+
let repo;
|
|
917
928
|
let dev = false;
|
|
918
929
|
for (let index = 0; index < args.length; index += 1) {
|
|
919
930
|
const arg = args[index];
|
|
@@ -921,6 +932,19 @@ function parseCliOptions(args) {
|
|
|
921
932
|
dev = true;
|
|
922
933
|
continue;
|
|
923
934
|
}
|
|
935
|
+
if (arg === "--repo") {
|
|
936
|
+
const value = args[index + 1];
|
|
937
|
+
if (!value) throw new Error("--repo requires a path value");
|
|
938
|
+
repo = resolve2(value);
|
|
939
|
+
index += 1;
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
if (arg.startsWith("--repo=")) {
|
|
943
|
+
const value = arg.slice("--repo=".length);
|
|
944
|
+
if (!value) throw new Error("--repo requires a path value");
|
|
945
|
+
repo = resolve2(value);
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
924
948
|
if (arg === "--comment") {
|
|
925
949
|
const comment = args[index + 1];
|
|
926
950
|
if (!comment) throw new Error("--comment requires a JSON value");
|
|
@@ -936,7 +960,7 @@ function parseCliOptions(args) {
|
|
|
936
960
|
}
|
|
937
961
|
reviewArgs.push(arg);
|
|
938
962
|
}
|
|
939
|
-
return { dev, reviewArgs, comments };
|
|
963
|
+
return { dev, repo, reviewArgs, comments };
|
|
940
964
|
}
|
|
941
965
|
function modeLabel(mode) {
|
|
942
966
|
if (mode.kind === "revision") return `${mode.base}..${mode.target}`;
|
|
@@ -944,7 +968,7 @@ function modeLabel(mode) {
|
|
|
944
968
|
}
|
|
945
969
|
function startVite() {
|
|
946
970
|
const child = spawn("npm", ["run", "web:dev"], {
|
|
947
|
-
cwd:
|
|
971
|
+
cwd: packageRoot,
|
|
948
972
|
stdio: "inherit",
|
|
949
973
|
shell: process.platform === "win32",
|
|
950
974
|
env: { ...process.env, BROWSER: "none" }
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "local-diff-reviewer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Open a local GitHub-style diff review Web UI for the current repository.",
|
|
6
6
|
"repository": {
|
|
@@ -12,11 +12,15 @@
|
|
|
12
12
|
"url": "https://github.com/Mone-Lee/diff-review/issues"
|
|
13
13
|
},
|
|
14
14
|
"type": "module",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"registry": "https://registry.npmjs.org/"
|
|
17
|
+
},
|
|
15
18
|
"bin": {
|
|
16
|
-
"local-diff-reviewer": "
|
|
19
|
+
"local-diff-reviewer": "dist/cli/start.js"
|
|
17
20
|
},
|
|
18
21
|
"files": [
|
|
19
22
|
"dist",
|
|
23
|
+
"docs/images",
|
|
20
24
|
"SKILL.md",
|
|
21
25
|
"README.md"
|
|
22
26
|
],
|
|
@@ -29,7 +33,8 @@
|
|
|
29
33
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
30
34
|
"skill:check": "node scripts/validate-skill.mjs",
|
|
31
35
|
"release:check": "node scripts/release-check.mjs",
|
|
32
|
-
"prepack": "npm run build"
|
|
36
|
+
"prepack": "npm run build",
|
|
37
|
+
"release": "node scripts/release.mjs"
|
|
33
38
|
},
|
|
34
39
|
"dependencies": {
|
|
35
40
|
"express": "^5.2.1",
|
|
@@ -55,4 +60,4 @@
|
|
|
55
60
|
"typescript": "^5.9.3",
|
|
56
61
|
"vite": "^8.0.12"
|
|
57
62
|
}
|
|
58
|
-
}
|
|
63
|
+
}
|