local-diff-reviewer 1.0.7 → 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.
- package/README.md +19 -0
- package/SKILL.md +1 -0
- package/dist/cli/start.js +143 -13
- package/docs/images/diff-review-intro.html +767 -0
- package/docs/images/diff-review-intro.png +0 -0
- package/docs/images/image.png +0 -0
- package/package.json +1 -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
|
|
@@ -77,6 +87,7 @@ local-diff-reviewer --repo /path/to/project staged
|
|
|
77
87
|
/diff-review
|
|
78
88
|
/diff-review staged
|
|
79
89
|
/diff-review HEAD~1 HEAD
|
|
90
|
+
/diff-review stop
|
|
80
91
|
```
|
|
81
92
|
|
|
82
93
|
安装 skill:
|
|
@@ -139,12 +150,20 @@ npm run release major
|
|
|
139
150
|
|
|
140
151
|
该命令会按顺序执行:
|
|
141
152
|
|
|
153
|
+
- 发布前检查 npmjs 认证与连通性(`npm whoami --registry=https://registry.npmjs.org/` + `npm ping --registry=https://registry.npmjs.org/`)
|
|
142
154
|
- `npm run release:check`
|
|
143
155
|
- `npm version <patch|minor|major>`(默认 `patch`)
|
|
144
156
|
- `npm publish`
|
|
145
157
|
- `git push`
|
|
146
158
|
- `git push --tags`
|
|
147
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
|
+
|
|
148
167
|
只有在 `npm publish` 成功后,才会自动推送提交和标签到 GitHub。
|
|
149
168
|
|
|
150
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 { basename as
|
|
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,11 +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
601
|
import { createServer } from "node:http";
|
|
517
|
-
import { join as
|
|
602
|
+
import { join as join4, normalize as normalize2, resolve, sep } from "node:path";
|
|
518
603
|
|
|
519
604
|
// src/core/markdown-source-map.ts
|
|
520
605
|
import GithubSlugger from "github-slugger";
|
|
@@ -855,10 +940,10 @@ async function startServer(state, port = 4966) {
|
|
|
855
940
|
next(error);
|
|
856
941
|
}
|
|
857
942
|
});
|
|
858
|
-
const webDist = state.webDist ??
|
|
943
|
+
const webDist = state.webDist ?? join4(process.cwd(), "dist", "web");
|
|
859
944
|
if (existsSync(webDist)) {
|
|
860
945
|
app.use(express.static(webDist));
|
|
861
|
-
app.get(/.*/, (_req, res) => res.sendFile(
|
|
946
|
+
app.get(/.*/, (_req, res) => res.sendFile(join4(webDist, "index.html")));
|
|
862
947
|
}
|
|
863
948
|
app.use((error, _req, res, _next) => {
|
|
864
949
|
res.status(500).json({ error: error.message });
|
|
@@ -908,30 +993,41 @@ function selectPromptThreads(threads, scope) {
|
|
|
908
993
|
}
|
|
909
994
|
|
|
910
995
|
// src/cli/start.ts
|
|
911
|
-
var packageRoot = resolve2(
|
|
912
|
-
var builtWebDist =
|
|
996
|
+
var packageRoot = resolve2(dirname3(fileURLToPath(import.meta.url)), "../..");
|
|
997
|
+
var builtWebDist = join5(packageRoot, "dist", "web");
|
|
913
998
|
async function main() {
|
|
914
|
-
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
|
+
}
|
|
915
1004
|
const mode = parseReviewMode(reviewArgs);
|
|
916
1005
|
const repoRoot = await getRepoRoot(repo ?? process.cwd());
|
|
917
1006
|
const diff = await getDiff(mode, repoRoot);
|
|
918
1007
|
const diffFiles = parseUnifiedDiff(diff);
|
|
919
1008
|
const session = {
|
|
920
1009
|
id: crypto.randomUUID(),
|
|
921
|
-
repoName:
|
|
1010
|
+
repoName: basename3(repoRoot),
|
|
922
1011
|
repoRoot,
|
|
923
1012
|
mode,
|
|
924
1013
|
diffHash: diffHash(diff),
|
|
925
1014
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
926
1015
|
};
|
|
927
1016
|
const importResult = await importAgentComments(repoRoot, diffFiles, comments);
|
|
928
|
-
const hasBuiltWeb = existsSync2(
|
|
1017
|
+
const hasBuiltWeb = existsSync2(join5(builtWebDist, "index.html"));
|
|
929
1018
|
const apiUrl = await startServer({ session, diffFiles, webDist: hasBuiltWeb ? builtWebDist : void 0 });
|
|
930
1019
|
const useVite = dev || !hasBuiltWeb;
|
|
931
1020
|
const uiUrl = useVite ? "http://127.0.0.1:5173" : apiUrl;
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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
|
+
});
|
|
935
1031
|
openBrowser(uiUrl);
|
|
936
1032
|
console.log(`Diff Review is running: ${uiUrl}`);
|
|
937
1033
|
console.log(`Repo: ${session.repoName} (${repoRoot})`);
|
|
@@ -948,6 +1044,7 @@ async function main() {
|
|
|
948
1044
|
}
|
|
949
1045
|
}
|
|
950
1046
|
function parseCliOptions(args) {
|
|
1047
|
+
let command = "review";
|
|
951
1048
|
const reviewArgs = [];
|
|
952
1049
|
const comments = [];
|
|
953
1050
|
let repo;
|
|
@@ -958,6 +1055,10 @@ function parseCliOptions(args) {
|
|
|
958
1055
|
dev = true;
|
|
959
1056
|
continue;
|
|
960
1057
|
}
|
|
1058
|
+
if (arg === "stop") {
|
|
1059
|
+
command = "stop";
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
961
1062
|
if (arg === "--repo") {
|
|
962
1063
|
const value = args[index + 1];
|
|
963
1064
|
if (!value) throw new Error("--repo requires a path value");
|
|
@@ -986,7 +1087,35 @@ function parseCliOptions(args) {
|
|
|
986
1087
|
}
|
|
987
1088
|
reviewArgs.push(arg);
|
|
988
1089
|
}
|
|
989
|
-
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;
|
|
990
1119
|
}
|
|
991
1120
|
function modeLabel(mode) {
|
|
992
1121
|
if (mode.kind === "revision") return `${mode.base}..${mode.target}`;
|
|
@@ -1001,6 +1130,7 @@ function startVite() {
|
|
|
1001
1130
|
});
|
|
1002
1131
|
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
1003
1132
|
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
1133
|
+
return child.pid;
|
|
1004
1134
|
}
|
|
1005
1135
|
function openBrowser(url) {
|
|
1006
1136
|
const child = process.platform === "darwin" ? spawn("open", [url], { stdio: "ignore", detached: true }) : process.platform === "win32" ? spawn("cmd", ["/c", "start", "", url], {
|
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>diff-review intro poster</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--ink: #071945;
|
|
10
|
+
--muted: #52617e;
|
|
11
|
+
--blue: #0f63ff;
|
|
12
|
+
--blue-2: #2f7cff;
|
|
13
|
+
--line: #dce7f7;
|
|
14
|
+
--soft: #f4f8ff;
|
|
15
|
+
--green: #2ca260;
|
|
16
|
+
--green-soft: #edf9f1;
|
|
17
|
+
--gold: #f1a600;
|
|
18
|
+
--red: #ef6b73;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
* {
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
body {
|
|
26
|
+
margin: 0;
|
|
27
|
+
background: #eaf1fb;
|
|
28
|
+
color: var(--ink);
|
|
29
|
+
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "SF Pro Display",
|
|
30
|
+
"SF Pro Text", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.poster {
|
|
34
|
+
position: relative;
|
|
35
|
+
width: 1440px;
|
|
36
|
+
min-height: 2048px;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
background:
|
|
39
|
+
radial-gradient(circle at 83% 7%, rgba(15, 99, 255, 0.13), transparent 29%),
|
|
40
|
+
linear-gradient(180deg, #ffffff 0%, #f7fbff 52%, #ffffff 100%);
|
|
41
|
+
padding: 54px 34px 42px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.poster::before {
|
|
45
|
+
content: "";
|
|
46
|
+
position: absolute;
|
|
47
|
+
inset: 0;
|
|
48
|
+
pointer-events: none;
|
|
49
|
+
background-image:
|
|
50
|
+
linear-gradient(rgba(15, 99, 255, 0.035) 1px, transparent 1px),
|
|
51
|
+
linear-gradient(90deg, rgba(15, 99, 255, 0.035) 1px, transparent 1px);
|
|
52
|
+
background-size: 44px 44px;
|
|
53
|
+
mask-image: linear-gradient(180deg, #000 0%, transparent 46%);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.content {
|
|
57
|
+
position: relative;
|
|
58
|
+
z-index: 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.topbar {
|
|
62
|
+
display: flex;
|
|
63
|
+
justify-content: space-between;
|
|
64
|
+
align-items: center;
|
|
65
|
+
margin: 0 8px 26px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.release {
|
|
69
|
+
color: var(--blue);
|
|
70
|
+
font-size: 36px;
|
|
71
|
+
font-weight: 900;
|
|
72
|
+
letter-spacing: 2px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.pill {
|
|
76
|
+
border-radius: 999px;
|
|
77
|
+
background: #eaf2ff;
|
|
78
|
+
color: var(--blue);
|
|
79
|
+
font-size: 24px;
|
|
80
|
+
font-weight: 800;
|
|
81
|
+
padding: 14px 28px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.hero {
|
|
85
|
+
display: grid;
|
|
86
|
+
grid-template-columns: 604px 1fr;
|
|
87
|
+
gap: 26px;
|
|
88
|
+
align-items: start;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
h1 {
|
|
92
|
+
margin: 0;
|
|
93
|
+
color: #061747;
|
|
94
|
+
font-size: 96px;
|
|
95
|
+
line-height: 0.96;
|
|
96
|
+
letter-spacing: -1px;
|
|
97
|
+
font-weight: 950;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.subtitle {
|
|
101
|
+
margin: 30px 0 72px;
|
|
102
|
+
color: #39476b;
|
|
103
|
+
font-size: 25px;
|
|
104
|
+
line-height: 1.45;
|
|
105
|
+
font-weight: 600;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.intro-card {
|
|
109
|
+
display: grid;
|
|
110
|
+
grid-template-columns: 80px 1fr;
|
|
111
|
+
gap: 26px;
|
|
112
|
+
align-items: start;
|
|
113
|
+
border: 2px solid #d8e6fb;
|
|
114
|
+
border-radius: 22px;
|
|
115
|
+
background: linear-gradient(180deg, #f7fbff 0%, #f0f6ff 100%);
|
|
116
|
+
padding: 34px 38px;
|
|
117
|
+
box-shadow: 0 18px 50px rgba(10, 42, 99, 0.08);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.spark {
|
|
121
|
+
width: 76px;
|
|
122
|
+
height: 76px;
|
|
123
|
+
display: grid;
|
|
124
|
+
place-items: center;
|
|
125
|
+
border-radius: 18px;
|
|
126
|
+
background: linear-gradient(145deg, #0757ff, #1478ff);
|
|
127
|
+
color: white;
|
|
128
|
+
font-size: 42px;
|
|
129
|
+
font-weight: 900;
|
|
130
|
+
box-shadow: 0 18px 34px rgba(15, 99, 255, 0.26);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.intro-card p {
|
|
134
|
+
margin: 0 0 20px;
|
|
135
|
+
color: #263657;
|
|
136
|
+
font-size: 25px;
|
|
137
|
+
line-height: 1.72;
|
|
138
|
+
font-weight: 650;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.visual-stage {
|
|
142
|
+
position: relative;
|
|
143
|
+
height: 692px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.browser-shot {
|
|
147
|
+
position: absolute;
|
|
148
|
+
border: 1px solid #d8e1ee;
|
|
149
|
+
border-radius: 20px;
|
|
150
|
+
overflow: hidden;
|
|
151
|
+
background: #fff;
|
|
152
|
+
box-shadow: 0 30px 70px rgba(7, 25, 69, 0.15);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.browser-bar {
|
|
156
|
+
height: 42px;
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
gap: 8px;
|
|
160
|
+
padding: 0 18px;
|
|
161
|
+
background: #f8fbff;
|
|
162
|
+
border-bottom: 1px solid #dce5f2;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.dot {
|
|
166
|
+
width: 11px;
|
|
167
|
+
height: 11px;
|
|
168
|
+
border-radius: 50%;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.red { background: #ff5d57; }
|
|
172
|
+
.yellow { background: #ffbd2e; }
|
|
173
|
+
.green { background: #28c840; }
|
|
174
|
+
|
|
175
|
+
.shot-one {
|
|
176
|
+
right: 0;
|
|
177
|
+
top: 0;
|
|
178
|
+
width: 648px;
|
|
179
|
+
height: 458px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.shot-two {
|
|
183
|
+
right: 42px;
|
|
184
|
+
top: 388px;
|
|
185
|
+
width: 560px;
|
|
186
|
+
height: 338px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.mock-ui {
|
|
190
|
+
height: calc(100% - 42px);
|
|
191
|
+
display: grid;
|
|
192
|
+
grid-template-columns: 168px 1fr 190px;
|
|
193
|
+
background: #fff;
|
|
194
|
+
color: #18213a;
|
|
195
|
+
font-size: 13px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.mock-side {
|
|
199
|
+
border-right: 1px solid #dde6f3;
|
|
200
|
+
padding: 16px 14px;
|
|
201
|
+
background: #f9fbff;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.mock-brand {
|
|
205
|
+
display: flex;
|
|
206
|
+
gap: 10px;
|
|
207
|
+
align-items: center;
|
|
208
|
+
margin-bottom: 18px;
|
|
209
|
+
font-weight: 900;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.mark {
|
|
213
|
+
width: 42px;
|
|
214
|
+
height: 42px;
|
|
215
|
+
display: grid;
|
|
216
|
+
place-items: center;
|
|
217
|
+
border-radius: 12px;
|
|
218
|
+
color: #fff;
|
|
219
|
+
background: linear-gradient(150deg, #174bd0, #2c7dff);
|
|
220
|
+
font-weight: 900;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.file-row {
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
justify-content: space-between;
|
|
227
|
+
gap: 10px;
|
|
228
|
+
padding: 11px 10px;
|
|
229
|
+
border-radius: 8px;
|
|
230
|
+
color: #34415f;
|
|
231
|
+
font-weight: 700;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.file-row.active {
|
|
235
|
+
background: #e8f0ff;
|
|
236
|
+
color: #0c3f9c;
|
|
237
|
+
border-left: 4px solid var(--blue);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.mock-code {
|
|
241
|
+
padding: 18px 18px 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.mock-title {
|
|
245
|
+
display: flex;
|
|
246
|
+
justify-content: space-between;
|
|
247
|
+
align-items: center;
|
|
248
|
+
margin-bottom: 16px;
|
|
249
|
+
font-size: 20px;
|
|
250
|
+
font-weight: 900;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.tiny-btn {
|
|
254
|
+
border: 1px solid #d4ddea;
|
|
255
|
+
border-radius: 6px;
|
|
256
|
+
padding: 7px 10px;
|
|
257
|
+
background: #fff;
|
|
258
|
+
color: #35425c;
|
|
259
|
+
font-size: 12px;
|
|
260
|
+
font-weight: 700;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.code-line {
|
|
264
|
+
display: grid;
|
|
265
|
+
grid-template-columns: 44px 1fr;
|
|
266
|
+
gap: 12px;
|
|
267
|
+
height: 29px;
|
|
268
|
+
align-items: center;
|
|
269
|
+
border-top: 1px solid #edf2f8;
|
|
270
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.ln {
|
|
274
|
+
color: #7a8aa4;
|
|
275
|
+
text-align: right;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.add { background: #e9faee; color: #12833f; }
|
|
279
|
+
.del { background: #fff0f0; color: #bf2638; }
|
|
280
|
+
|
|
281
|
+
.thread {
|
|
282
|
+
margin-top: 10px;
|
|
283
|
+
border: 2px solid #7bb5ff;
|
|
284
|
+
border-left: 7px solid var(--blue);
|
|
285
|
+
border-radius: 8px;
|
|
286
|
+
padding: 14px;
|
|
287
|
+
background: #fbfdff;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.agent-note {
|
|
291
|
+
margin-top: 10px;
|
|
292
|
+
border-radius: 8px;
|
|
293
|
+
background: #ddfae6;
|
|
294
|
+
color: #1d6539;
|
|
295
|
+
padding: 12px 14px;
|
|
296
|
+
line-height: 1.45;
|
|
297
|
+
font-weight: 700;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.mock-comments {
|
|
301
|
+
border-left: 1px solid #dde6f3;
|
|
302
|
+
padding: 16px 12px;
|
|
303
|
+
background: #fbfdff;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.comment-title {
|
|
307
|
+
display: flex;
|
|
308
|
+
justify-content: space-between;
|
|
309
|
+
align-items: center;
|
|
310
|
+
margin-bottom: 12px;
|
|
311
|
+
font-size: 18px;
|
|
312
|
+
font-weight: 900;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.comment-card {
|
|
316
|
+
margin-bottom: 10px;
|
|
317
|
+
border: 1px solid #d6e0ef;
|
|
318
|
+
border-radius: 10px;
|
|
319
|
+
padding: 10px;
|
|
320
|
+
background: #fff;
|
|
321
|
+
line-height: 1.45;
|
|
322
|
+
color: #2f3b56;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.tag {
|
|
326
|
+
display: inline-flex;
|
|
327
|
+
align-items: center;
|
|
328
|
+
border-radius: 7px;
|
|
329
|
+
padding: 3px 8px;
|
|
330
|
+
background: #fff7db;
|
|
331
|
+
color: #a66a00;
|
|
332
|
+
font-size: 12px;
|
|
333
|
+
font-weight: 800;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.screenshot-img {
|
|
337
|
+
width: 100%;
|
|
338
|
+
height: calc(100% - 42px);
|
|
339
|
+
object-fit: cover;
|
|
340
|
+
object-position: center top;
|
|
341
|
+
display: block;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
section {
|
|
345
|
+
margin-top: 46px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
h2 {
|
|
349
|
+
margin: 0 0 20px;
|
|
350
|
+
color: var(--blue);
|
|
351
|
+
font-size: 38px;
|
|
352
|
+
line-height: 1;
|
|
353
|
+
font-weight: 950;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.why-grid {
|
|
357
|
+
display: grid;
|
|
358
|
+
grid-template-columns: repeat(3, 1fr);
|
|
359
|
+
gap: 18px;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.reason-card,
|
|
363
|
+
.value-card,
|
|
364
|
+
.cap-card,
|
|
365
|
+
.scene-card {
|
|
366
|
+
border: 1.5px solid var(--line);
|
|
367
|
+
border-radius: 16px;
|
|
368
|
+
background: rgba(255, 255, 255, 0.9);
|
|
369
|
+
box-shadow: 0 10px 30px rgba(11, 40, 92, 0.06);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.reason-card {
|
|
373
|
+
display: grid;
|
|
374
|
+
grid-template-columns: 78px 1fr;
|
|
375
|
+
gap: 16px;
|
|
376
|
+
align-items: center;
|
|
377
|
+
min-height: 118px;
|
|
378
|
+
padding: 22px 24px;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.icon {
|
|
382
|
+
width: 64px;
|
|
383
|
+
height: 64px;
|
|
384
|
+
display: grid;
|
|
385
|
+
place-items: center;
|
|
386
|
+
border-radius: 15px;
|
|
387
|
+
background: #edf5ff;
|
|
388
|
+
color: var(--blue);
|
|
389
|
+
font-size: 34px;
|
|
390
|
+
font-weight: 950;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.reason-card strong {
|
|
394
|
+
display: block;
|
|
395
|
+
margin-bottom: 5px;
|
|
396
|
+
color: #15244b;
|
|
397
|
+
font-size: 24px;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.reason-card span {
|
|
401
|
+
color: #45536f;
|
|
402
|
+
font-size: 22px;
|
|
403
|
+
font-weight: 650;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.blue-band {
|
|
407
|
+
display: grid;
|
|
408
|
+
grid-template-columns: 110px 1fr;
|
|
409
|
+
gap: 24px;
|
|
410
|
+
align-items: center;
|
|
411
|
+
margin-top: 24px;
|
|
412
|
+
border-radius: 15px;
|
|
413
|
+
background: linear-gradient(135deg, #075bff 0%, #0a69ff 100%);
|
|
414
|
+
color: #fff;
|
|
415
|
+
padding: 22px 48px;
|
|
416
|
+
box-shadow: 0 22px 42px rgba(15, 99, 255, 0.22);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.check {
|
|
420
|
+
width: 86px;
|
|
421
|
+
height: 86px;
|
|
422
|
+
display: grid;
|
|
423
|
+
place-items: center;
|
|
424
|
+
border-radius: 50%;
|
|
425
|
+
background: #fff;
|
|
426
|
+
color: var(--blue);
|
|
427
|
+
font-size: 54px;
|
|
428
|
+
font-weight: 950;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.blue-band p {
|
|
432
|
+
margin: 0;
|
|
433
|
+
font-size: 34px;
|
|
434
|
+
line-height: 1.32;
|
|
435
|
+
font-weight: 900;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.value-grid {
|
|
439
|
+
display: grid;
|
|
440
|
+
grid-template-columns: repeat(3, 1fr);
|
|
441
|
+
gap: 18px;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.value-card {
|
|
445
|
+
display: grid;
|
|
446
|
+
grid-template-columns: 72px 1fr;
|
|
447
|
+
gap: 18px;
|
|
448
|
+
align-items: center;
|
|
449
|
+
min-height: 118px;
|
|
450
|
+
padding: 22px 24px;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.value-card strong {
|
|
454
|
+
display: block;
|
|
455
|
+
color: #26365f;
|
|
456
|
+
font-size: 22px;
|
|
457
|
+
line-height: 1.35;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.value-card.green-card {
|
|
461
|
+
border: 3px solid var(--green);
|
|
462
|
+
background: var(--green-soft);
|
|
463
|
+
color: var(--green);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.green-card .icon {
|
|
467
|
+
background: #dff5e7;
|
|
468
|
+
color: var(--green);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.green-card strong {
|
|
472
|
+
color: var(--green);
|
|
473
|
+
font-size: 27px;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.green-card span {
|
|
477
|
+
display: block;
|
|
478
|
+
margin-top: 5px;
|
|
479
|
+
font-size: 20px;
|
|
480
|
+
font-weight: 850;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.scene-grid {
|
|
484
|
+
display: grid;
|
|
485
|
+
grid-template-columns: 1fr 1.26fr;
|
|
486
|
+
gap: 28px;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.scene-card {
|
|
490
|
+
min-height: 260px;
|
|
491
|
+
padding: 28px 34px;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.scene-title {
|
|
495
|
+
display: flex;
|
|
496
|
+
align-items: center;
|
|
497
|
+
gap: 24px;
|
|
498
|
+
margin-bottom: 16px;
|
|
499
|
+
color: #12234e;
|
|
500
|
+
font-size: 28px;
|
|
501
|
+
font-weight: 950;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.scene-card ul {
|
|
505
|
+
margin: 10px 0 22px 112px;
|
|
506
|
+
padding: 0;
|
|
507
|
+
color: #2d3d62;
|
|
508
|
+
font-size: 21px;
|
|
509
|
+
line-height: 1.58;
|
|
510
|
+
font-weight: 650;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.scene-note {
|
|
514
|
+
margin: 0 0 0 68px;
|
|
515
|
+
border-radius: 999px;
|
|
516
|
+
background: #eef5ff;
|
|
517
|
+
color: var(--blue);
|
|
518
|
+
text-align: center;
|
|
519
|
+
padding: 12px 24px;
|
|
520
|
+
font-size: 23px;
|
|
521
|
+
font-weight: 950;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.flow {
|
|
525
|
+
display: grid;
|
|
526
|
+
grid-template-columns: repeat(5, 1fr);
|
|
527
|
+
gap: 0;
|
|
528
|
+
align-items: start;
|
|
529
|
+
margin-top: 18px;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.flow-step {
|
|
533
|
+
position: relative;
|
|
534
|
+
text-align: center;
|
|
535
|
+
color: #213457;
|
|
536
|
+
font-size: 20px;
|
|
537
|
+
font-weight: 850;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.flow-step:not(:last-child)::after {
|
|
541
|
+
content: "→";
|
|
542
|
+
position: absolute;
|
|
543
|
+
top: 26px;
|
|
544
|
+
right: -14px;
|
|
545
|
+
color: #8a98ad;
|
|
546
|
+
font-size: 34px;
|
|
547
|
+
font-weight: 500;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.flow-icon {
|
|
551
|
+
width: 66px;
|
|
552
|
+
height: 66px;
|
|
553
|
+
margin: 0 auto 10px;
|
|
554
|
+
display: grid;
|
|
555
|
+
place-items: center;
|
|
556
|
+
border: 4px solid #aeb8c8;
|
|
557
|
+
border-radius: 14px;
|
|
558
|
+
color: var(--blue);
|
|
559
|
+
font-size: 32px;
|
|
560
|
+
background: #fff;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.flow-step:first-child .flow-icon,
|
|
564
|
+
.flow-step:last-child .flow-icon {
|
|
565
|
+
border-color: var(--green);
|
|
566
|
+
color: var(--green);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.flow-note {
|
|
570
|
+
margin: 24px auto 0;
|
|
571
|
+
width: 520px;
|
|
572
|
+
border-radius: 999px;
|
|
573
|
+
background: #edf9f1;
|
|
574
|
+
color: var(--green);
|
|
575
|
+
text-align: center;
|
|
576
|
+
padding: 12px 24px;
|
|
577
|
+
font-size: 24px;
|
|
578
|
+
font-weight: 950;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.cap-grid {
|
|
582
|
+
display: grid;
|
|
583
|
+
grid-template-columns: repeat(3, 1fr);
|
|
584
|
+
gap: 16px;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.cap-card {
|
|
588
|
+
min-height: 136px;
|
|
589
|
+
display: grid;
|
|
590
|
+
grid-template-columns: 64px 1fr;
|
|
591
|
+
gap: 14px;
|
|
592
|
+
align-items: center;
|
|
593
|
+
padding: 18px 22px;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.cap-card.wide {
|
|
597
|
+
grid-column: span 2;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.cap-card.green-outline {
|
|
601
|
+
border: 3px solid var(--green);
|
|
602
|
+
background: var(--green-soft);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.cap-card strong {
|
|
606
|
+
display: block;
|
|
607
|
+
color: #24365d;
|
|
608
|
+
font-size: 23px;
|
|
609
|
+
line-height: 1.32;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.cap-card span {
|
|
613
|
+
display: block;
|
|
614
|
+
margin-top: 4px;
|
|
615
|
+
color: #5b6881;
|
|
616
|
+
font-size: 20px;
|
|
617
|
+
line-height: 1.32;
|
|
618
|
+
font-weight: 700;
|
|
619
|
+
white-space: nowrap;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.cap-card.green-outline strong,
|
|
623
|
+
.cap-card.green-outline span {
|
|
624
|
+
color: var(--green);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.footer {
|
|
628
|
+
display: flex;
|
|
629
|
+
justify-content: space-between;
|
|
630
|
+
align-items: center;
|
|
631
|
+
margin-top: 34px;
|
|
632
|
+
padding: 20px 26px;
|
|
633
|
+
border-radius: 16px;
|
|
634
|
+
background: #061747;
|
|
635
|
+
color: #fff;
|
|
636
|
+
font-size: 19px;
|
|
637
|
+
font-weight: 800;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.footer b {
|
|
641
|
+
color: #7db0ff;
|
|
642
|
+
}
|
|
643
|
+
</style>
|
|
644
|
+
</head>
|
|
645
|
+
<body>
|
|
646
|
+
<main class="poster">
|
|
647
|
+
<div class="content">
|
|
648
|
+
<div class="topbar">
|
|
649
|
+
<div class="release">【新工具发布】</div>
|
|
650
|
+
<div class="pill">LOCAL AI REVIEW TOOL</div>
|
|
651
|
+
</div>
|
|
652
|
+
|
|
653
|
+
<div class="hero">
|
|
654
|
+
<div>
|
|
655
|
+
<h1>diff-review</h1>
|
|
656
|
+
<p class="subtitle">让 coding agent 在关键节点停下来,给你一次真正的 code review</p>
|
|
657
|
+
|
|
658
|
+
<div class="intro-card">
|
|
659
|
+
<div class="spark">✦</div>
|
|
660
|
+
<div>
|
|
661
|
+
<p>Vibe coding 场景下的 AI 机协作 code review 工具。</p>
|
|
662
|
+
<p>Agent 把 diff / plan 推进浏览器给你看一眼,</p>
|
|
663
|
+
<p>你在行号上评评论,agent 读完评论自动改代码 — 直到你说 OK。</p>
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
</div>
|
|
667
|
+
|
|
668
|
+
<div class="visual-stage">
|
|
669
|
+
<div class="browser-shot shot-one">
|
|
670
|
+
<div class="browser-bar">
|
|
671
|
+
<span class="dot red"></span>
|
|
672
|
+
<span class="dot yellow"></span>
|
|
673
|
+
<span class="dot green"></span>
|
|
674
|
+
<span style="margin-left: 16px; font-weight: 850; color: #283754">Diff 审查台</span>
|
|
675
|
+
<span style="margin-left: auto; color: #2d7c45; font-weight: 800">127.0.0.1</span>
|
|
676
|
+
</div>
|
|
677
|
+
<img class="screenshot-img" src="./diff-review-ui.jpg" alt="diff-review side-by-side review screenshot" />
|
|
678
|
+
</div>
|
|
679
|
+
|
|
680
|
+
<div class="browser-shot shot-two">
|
|
681
|
+
<div class="browser-bar">
|
|
682
|
+
<span class="dot red"></span>
|
|
683
|
+
<span class="dot yellow"></span>
|
|
684
|
+
<span class="dot green"></span>
|
|
685
|
+
<span style="margin-left: 16px; font-weight: 850; color: #283754">Review Comments</span>
|
|
686
|
+
</div>
|
|
687
|
+
<img class="screenshot-img" src="./image.png" alt="diff-review side-by-side review screenshot" />
|
|
688
|
+
</div>
|
|
689
|
+
</div>
|
|
690
|
+
</div>
|
|
691
|
+
|
|
692
|
+
<section>
|
|
693
|
+
<h2>为什么做这个</h2>
|
|
694
|
+
<div class="why-grid">
|
|
695
|
+
<div class="reason-card">
|
|
696
|
+
<div class="icon">↯</div>
|
|
697
|
+
<div><strong>Agent 跑完就 apply</strong><span>人来不及看</span></div>
|
|
698
|
+
</div>
|
|
699
|
+
<div class="reason-card">
|
|
700
|
+
<div class="icon">⌁</div>
|
|
701
|
+
<div><strong>终端跑 diff</strong><span>文本体验很差</span></div>
|
|
702
|
+
</div>
|
|
703
|
+
<div class="reason-card">
|
|
704
|
+
<div class="icon">☷</div>
|
|
705
|
+
<div><strong>修改路径分散</strong><span>难以查看脉络</span></div>
|
|
706
|
+
</div>
|
|
707
|
+
</div>
|
|
708
|
+
<div class="blue-band">
|
|
709
|
+
<div class="check">✓</div>
|
|
710
|
+
<p>让 agent 主动把 diff 推到浏览器里给你 review。你只负责读代码和打字。</p>
|
|
711
|
+
</div>
|
|
712
|
+
</section>
|
|
713
|
+
|
|
714
|
+
<section>
|
|
715
|
+
<h2>核心价值</h2>
|
|
716
|
+
<div class="value-grid">
|
|
717
|
+
<div class="value-card"><div class="icon">★</div><strong>把方向问题<br />拦在动手前</strong></div>
|
|
718
|
+
<div class="value-card"><div class="icon">✓</div><strong>把人工判断<br />嵌进 agent 迭代循环</strong></div>
|
|
719
|
+
<div class="value-card"><div class="icon">⏱</div><strong>一次 review<br />3-10 分钟</strong></div>
|
|
720
|
+
</div>
|
|
721
|
+
</section>
|
|
722
|
+
|
|
723
|
+
<section>
|
|
724
|
+
<h2>两个核心场景</h2>
|
|
725
|
+
<div class="scene-grid">
|
|
726
|
+
<div class="scene-card">
|
|
727
|
+
<div class="scene-title"><div class="icon">▤</div><span>场景 1:Plan / Spec Review</span></div>
|
|
728
|
+
<ul>
|
|
729
|
+
<li>先 review 方案,再动代码</li>
|
|
730
|
+
<li>plan markdown 预览,按需删评论</li>
|
|
731
|
+
<li>Agent 读评论 -> 改 plan -> 连续 review</li>
|
|
732
|
+
</ul>
|
|
733
|
+
<p class="scene-note">把推倒重来的成本挡在动手前</p>
|
|
734
|
+
</div>
|
|
735
|
+
<div class="scene-card">
|
|
736
|
+
<div class="scene-title"><div class="icon" style="color: var(--green)">⌘</div><span>场景 2:本地 diff review</span></div>
|
|
737
|
+
<div class="flow">
|
|
738
|
+
<div class="flow-step"><div class="flow-icon"></></div>生成 diff</div>
|
|
739
|
+
<div class="flow-step"><div class="flow-icon">▭</div>打开<br />浏览器</div>
|
|
740
|
+
<div class="flow-step"><div class="flow-icon">☰</div>行号<br />评论</div>
|
|
741
|
+
<div class="flow-step"><div class="flow-icon">◎</div>agent<br />自动改代码</div>
|
|
742
|
+
<div class="flow-step"><div class="flow-icon">✓</div>OK 后<br />提交</div>
|
|
743
|
+
</div>
|
|
744
|
+
<div class="flow-note">一次 review 3-10 分钟</div>
|
|
745
|
+
</div>
|
|
746
|
+
</div>
|
|
747
|
+
</section>
|
|
748
|
+
|
|
749
|
+
<section>
|
|
750
|
+
<h2>关键能力</h2>
|
|
751
|
+
<div class="cap-grid">
|
|
752
|
+
<div class="cap-card"><div class="icon">☰</div><strong>行级、文件级评论</strong><span>支持多行范围评论</span></div>
|
|
753
|
+
<div class="cap-card"><div class="icon">↻</div><strong>多轮状态流转</strong><span>submit -> replied -> resolved</span></div>
|
|
754
|
+
<div class="cap-card"><div class="icon">▧</div><strong>Agent findings</strong><span>自动提炼并回显</span></div>
|
|
755
|
+
<div class="cap-card wide"><div class="icon">▰</div><strong>评论记录自动归档</strong><span>~/.local/diff-review/logs</span></div>
|
|
756
|
+
<div class="cap-card green-outline"><div class="icon">✓</div><strong>纯本地:127.0.0.1</strong><span>代码不出本机</span></div>
|
|
757
|
+
</div>
|
|
758
|
+
</section>
|
|
759
|
+
|
|
760
|
+
<div class="footer">
|
|
761
|
+
<span><b>diff-review</b> · local GitHub-style diff review for AI coding agents</span>
|
|
762
|
+
<span>Browser review · Agent loop · Local first</span>
|
|
763
|
+
</div>
|
|
764
|
+
</div>
|
|
765
|
+
</main>
|
|
766
|
+
</body>
|
|
767
|
+
</html>
|
|
Binary file
|
|
Binary file
|