code-gate 0.1.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.
- package/README.md +118 -0
- package/bin/code-gate.js +29 -0
- package/dist/__tests__/config.test.d.ts +1 -0
- package/dist/__tests__/config.test.js +28 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/render.test.d.ts +1 -0
- package/dist/__tests__/render.test.js +16 -0
- package/dist/__tests__/render.test.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +32 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/hook.d.ts +1 -0
- package/dist/commands/hook.js +71 -0
- package/dist/commands/hook.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +129 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +19 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.js +86 -0
- package/dist/config.js.map +1 -0
- package/dist/core/review-flow.d.ts +1 -0
- package/dist/core/review-flow.js +109 -0
- package/dist/core/review-flow.js.map +1 -0
- package/dist/git.d.ts +4 -0
- package/dist/git.js +32 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/deepseek.d.ts +6 -0
- package/dist/llm/deepseek.js +24 -0
- package/dist/llm/deepseek.js.map +1 -0
- package/dist/llm/ollama.d.ts +6 -0
- package/dist/llm/ollama.js +81 -0
- package/dist/llm/ollama.js.map +1 -0
- package/dist/log.d.ts +3 -0
- package/dist/log.js +10 -0
- package/dist/log.js.map +1 -0
- package/dist/ui/render.d.ts +18 -0
- package/dist/ui/render.js +138 -0
- package/dist/ui/render.js.map +1 -0
- package/dist/ui/server.d.ts +2 -0
- package/dist/ui/server.js +49 -0
- package/dist/ui/server.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { loadConfig } from '../config.js';
|
|
2
|
+
import { getStagedFiles, getStagedDiff, filterFiles, getStagedDiffForFile } from '../git.js';
|
|
3
|
+
import { deepseekReview } from '../llm/deepseek.js';
|
|
4
|
+
import { ollamaReview } from '../llm/ollama.js';
|
|
5
|
+
import { renderHTML, renderHTMLTabs } from '../ui/render.js';
|
|
6
|
+
import { serveReview } from '../ui/server.js';
|
|
7
|
+
import { info, warn } from '../log.js';
|
|
8
|
+
export async function runReviewFlow() {
|
|
9
|
+
const cfg = await loadConfig();
|
|
10
|
+
const providerUsed = (cfg.review?.provider || cfg.provider);
|
|
11
|
+
const modelUsed = providerUsed === 'deepseek' ? cfg.deepseek?.model : cfg.ollama?.model;
|
|
12
|
+
const files = filterFiles(getStagedFiles(), cfg.fileTypes);
|
|
13
|
+
if (files.length === 0) {
|
|
14
|
+
info('code-gate: 没有可审查的文件');
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
let diff = getStagedDiff();
|
|
18
|
+
if (!diff) {
|
|
19
|
+
warn('code-gate: 未获取到 diff');
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const prompt = cfg.prompt || '';
|
|
23
|
+
let content = '';
|
|
24
|
+
let aiInvoked = false;
|
|
25
|
+
let aiSucceeded = false;
|
|
26
|
+
let status = '';
|
|
27
|
+
try {
|
|
28
|
+
if (providerUsed === 'deepseek') {
|
|
29
|
+
const apiKeyEnv = cfg.deepseek?.apiKeyEnv || 'DEEPSEEK_API_KEY';
|
|
30
|
+
if (!process.env[apiKeyEnv]) {
|
|
31
|
+
status = `缺少 DeepSeek 密钥 ${apiKeyEnv}`;
|
|
32
|
+
content =
|
|
33
|
+
`未生成 AI 审查结果。\n原因:缺少 DeepSeek 密钥 ${apiKeyEnv}。\n` +
|
|
34
|
+
`解决方案:在当前环境设置 ${apiKeyEnv},或切换 provider 为 ollama。\n` +
|
|
35
|
+
`示例:export ${apiKeyEnv}="你的密钥"`;
|
|
36
|
+
aiInvoked = false;
|
|
37
|
+
aiSucceeded = false;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
aiInvoked = true;
|
|
41
|
+
content = await deepseekReview(cfg, { prompt, diff });
|
|
42
|
+
aiSucceeded = !!content;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
aiInvoked = true;
|
|
47
|
+
content = await ollamaReview(cfg, { prompt, diff });
|
|
48
|
+
aiSucceeded = !!content;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
warn(`code-gate: LLM 调用失败:${e?.message || e}`);
|
|
53
|
+
status = `LLM 调用失败:${e?.message || e}`;
|
|
54
|
+
content =
|
|
55
|
+
`未生成 AI 审查结果。\n可能原因:网络不可达、服务未启动或配置错误。\n` +
|
|
56
|
+
`当前 provider:${cfg.provider}\n` +
|
|
57
|
+
(cfg.provider === 'ollama'
|
|
58
|
+
? `请检查本地 Ollama 是否运行(默认 http://localhost:11434),模型是否可用。\n示例:ollama list`
|
|
59
|
+
: `请检查 ${cfg.deepseek?.apiKeyEnv || 'DEEPSEEK_API_KEY'} 是否已设置、baseURL 是否为 https://api.deepseek.com`);
|
|
60
|
+
aiInvoked = true;
|
|
61
|
+
aiSucceeded = false;
|
|
62
|
+
}
|
|
63
|
+
if (cfg.review?.mode === 'per_file') {
|
|
64
|
+
const items = [];
|
|
65
|
+
for (const f of files.slice(0, cfg.limits?.maxFiles || files.length)) {
|
|
66
|
+
const fdiff = getStagedDiffForFile(f);
|
|
67
|
+
let frev = '';
|
|
68
|
+
try {
|
|
69
|
+
if (providerUsed === 'deepseek') {
|
|
70
|
+
const apiKeyEnv = cfg.deepseek?.apiKeyEnv || 'DEEPSEEK_API_KEY';
|
|
71
|
+
if (process.env[apiKeyEnv]) {
|
|
72
|
+
aiInvoked = true;
|
|
73
|
+
frev = await deepseekReview(cfg, { prompt, diff: fdiff });
|
|
74
|
+
aiSucceeded = aiSucceeded || !!frev;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
aiInvoked = true;
|
|
79
|
+
frev = await ollamaReview(cfg, { prompt, diff: fdiff });
|
|
80
|
+
aiSucceeded = aiSucceeded || !!frev;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
warn(`code-gate: 文件 ${f} 审查失败:${e?.message || e}`);
|
|
85
|
+
}
|
|
86
|
+
items.push({ file: f, review: frev, diff: fdiff || 'diff --git a/' + f + ' b/' + f });
|
|
87
|
+
}
|
|
88
|
+
const html = renderHTMLTabs(items, {
|
|
89
|
+
aiInvoked,
|
|
90
|
+
aiSucceeded,
|
|
91
|
+
provider: providerUsed,
|
|
92
|
+
model: modelUsed,
|
|
93
|
+
status
|
|
94
|
+
});
|
|
95
|
+
await serveReview(cfg, html);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const html = renderHTML(diff, content, {
|
|
99
|
+
aiInvoked,
|
|
100
|
+
aiSucceeded,
|
|
101
|
+
provider: providerUsed,
|
|
102
|
+
model: modelUsed,
|
|
103
|
+
status
|
|
104
|
+
});
|
|
105
|
+
await serveReview(cfg, html);
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=review-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-flow.js","sourceRoot":"","sources":["../../src/core/review-flow.ts"],"names":[],"mappings":"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"}
|
package/dist/git.d.ts
ADDED
package/dist/git.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
function runGit(args) {
|
|
3
|
+
const res = spawnSync('git', args, { encoding: 'utf8' });
|
|
4
|
+
if (res.status !== 0)
|
|
5
|
+
return '';
|
|
6
|
+
return res.stdout || '';
|
|
7
|
+
}
|
|
8
|
+
export function getStagedFiles() {
|
|
9
|
+
const out = runGit(['diff', '--staged', '--name-only']);
|
|
10
|
+
return out
|
|
11
|
+
.split('\n')
|
|
12
|
+
.map((s) => s.trim())
|
|
13
|
+
.filter(Boolean);
|
|
14
|
+
}
|
|
15
|
+
export function getStagedDiff() {
|
|
16
|
+
return runGit(['diff', '--staged']);
|
|
17
|
+
}
|
|
18
|
+
export function getStagedDiffForFile(file) {
|
|
19
|
+
return runGit(['diff', '--staged', '--', file]);
|
|
20
|
+
}
|
|
21
|
+
export function filterFiles(files, fileTypes) {
|
|
22
|
+
if (!fileTypes || fileTypes.length === 0)
|
|
23
|
+
return files;
|
|
24
|
+
const exts = new Set(fileTypes.map((t) => t.replace(/^\./, '').toLowerCase()));
|
|
25
|
+
return files.filter((f) => {
|
|
26
|
+
const m = f.split('.').pop();
|
|
27
|
+
if (!m)
|
|
28
|
+
return false;
|
|
29
|
+
return exts.has(m.toLowerCase());
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=git.js.map
|
package/dist/git.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C,SAAS,MAAM,CAAC,IAAc;IAC5B,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;IACxD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAC/B,OAAO,GAAG,CAAC,MAAM,IAAI,EAAE,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC,CAAA;IACvD,OAAO,GAAG;SACP,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAA;AACpB,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;AACrC,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,OAAO,MAAM,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AACjD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAe,EAAE,SAAoB;IAC/D,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACtD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;IAC9E,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACxB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAA;QAC5B,IAAI,CAAC,CAAC;YAAE,OAAO,KAAK,CAAA;QACpB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './cli.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
export async function deepseekReview(cfg, input) {
|
|
3
|
+
const baseURL = (cfg.deepseek?.baseURL || 'https://api.deepseek.com').replace(/`/g, '').trim();
|
|
4
|
+
const apiKeyEnv = cfg.deepseek?.apiKeyEnv || 'DEEPSEEK_API_KEY';
|
|
5
|
+
const apiKey = process.env[apiKeyEnv];
|
|
6
|
+
if (!apiKey)
|
|
7
|
+
throw new Error(`Missing DeepSeek API key in ${apiKeyEnv}`);
|
|
8
|
+
const client = new OpenAI({ baseURL, apiKey });
|
|
9
|
+
const model = cfg.deepseek?.model || 'deepseek-chat';
|
|
10
|
+
const res = await client.chat.completions.create({
|
|
11
|
+
model,
|
|
12
|
+
messages: [
|
|
13
|
+
{ role: 'system', content: input.prompt },
|
|
14
|
+
{
|
|
15
|
+
role: 'user',
|
|
16
|
+
content: '请根据以下 git diff 进行代码审查,输出问题清单与改进建议,必要时给出补丁示例:\n\n' +
|
|
17
|
+
input.diff
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
});
|
|
21
|
+
const content = res.choices?.[0]?.message?.content || '';
|
|
22
|
+
return content;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=deepseek.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deepseek.js","sourceRoot":"","sources":["../../src/llm/deepseek.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAQ3B,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW,EAAE,KAAkB;IAClE,MAAM,OAAO,GACX,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,IAAI,0BAA0B,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAChF,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,EAAE,SAAS,IAAI,kBAAkB,CAAA;IAC/D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACrC,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,SAAS,EAAE,CAAC,CAAA;IACxE,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAA;IAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,EAAE,KAAK,IAAI,eAAe,CAAA;IACpD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;QAC/C,KAAK;QACL,QAAQ,EAAE;YACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE;YACzC;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EACL,kDAAkD;oBAClD,KAAK,CAAC,IAAI;aACb;SACF;KACF,CAAC,CAAA;IACF,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE,CAAA;IACxD,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export async function ollamaReview(cfg, input) {
|
|
2
|
+
const baseURL = cfg.ollama?.baseURL || 'http://localhost:11434';
|
|
3
|
+
const model = cfg.ollama?.model || 'qwen2.5-coder';
|
|
4
|
+
const chatUrl = `${baseURL}/api/chat`;
|
|
5
|
+
const chatBody = {
|
|
6
|
+
model,
|
|
7
|
+
messages: [
|
|
8
|
+
{ role: 'system', content: input.prompt },
|
|
9
|
+
{
|
|
10
|
+
role: 'user',
|
|
11
|
+
content: '请根据以下 git diff 进行代码审查,输出问题清单与改进建议,必要时给出补丁示例:\n\n' +
|
|
12
|
+
input.diff
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
stream: false
|
|
16
|
+
};
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const timer = setTimeout(() => controller.abort('timeout'), 15000);
|
|
19
|
+
let res;
|
|
20
|
+
try {
|
|
21
|
+
res = await fetch(chatUrl, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify(chatBody),
|
|
25
|
+
signal: controller.signal
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
// Fallback to generate endpoint on network or abort
|
|
31
|
+
const genUrl = `${baseURL}/api/generate`;
|
|
32
|
+
const genBody = {
|
|
33
|
+
model,
|
|
34
|
+
prompt: input.prompt +
|
|
35
|
+
'\n\n请根据以下 git diff 进行代码审查,输出问题清单与改进建议,必要时给出补丁示例:\n\n' +
|
|
36
|
+
input.diff,
|
|
37
|
+
stream: false
|
|
38
|
+
};
|
|
39
|
+
const res2 = await fetch(genUrl, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify(genBody)
|
|
43
|
+
});
|
|
44
|
+
if (!res2.ok) {
|
|
45
|
+
const txt = await res2.text().catch(() => '');
|
|
46
|
+
throw new Error(`Ollama generate failed: ${res2.status} ${txt}`);
|
|
47
|
+
}
|
|
48
|
+
const data2 = await res2.json();
|
|
49
|
+
return data2?.response || '';
|
|
50
|
+
}
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
const txt = await res.text().catch(() => '');
|
|
54
|
+
// Fallback to generate endpoint on bad status
|
|
55
|
+
const genUrl = `${baseURL}/api/generate`;
|
|
56
|
+
const genBody = {
|
|
57
|
+
model,
|
|
58
|
+
prompt: input.prompt +
|
|
59
|
+
'\n\n请根据以下 git diff 进行代码审查,输出问题清单与改进建议,必要时给出补丁示例:\n\n' +
|
|
60
|
+
input.diff,
|
|
61
|
+
stream: false
|
|
62
|
+
};
|
|
63
|
+
const res2 = await fetch(genUrl, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'Content-Type': 'application/json' },
|
|
66
|
+
body: JSON.stringify(genBody)
|
|
67
|
+
});
|
|
68
|
+
if (!res2.ok) {
|
|
69
|
+
const txt2 = await res2.text().catch(() => '');
|
|
70
|
+
throw new Error(`Ollama request failed: ${res.status} ${txt}; generate failed: ${res2.status} ${txt2}`);
|
|
71
|
+
}
|
|
72
|
+
const data2 = await res2.json();
|
|
73
|
+
return data2?.response || '';
|
|
74
|
+
}
|
|
75
|
+
const data = await res.json();
|
|
76
|
+
const content = data?.message?.content ||
|
|
77
|
+
data?.messages?.[data.messages.length - 1]?.content ||
|
|
78
|
+
'';
|
|
79
|
+
return content;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=ollama.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/llm/ollama.ts"],"names":[],"mappings":"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"}
|
package/dist/log.d.ts
ADDED
package/dist/log.js
ADDED
package/dist/log.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAA;AAClC,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAA;AAClC,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAW;IAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAA;AAClC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function renderHTML(diff: string, review: string, meta?: {
|
|
2
|
+
aiInvoked?: boolean;
|
|
3
|
+
aiSucceeded?: boolean;
|
|
4
|
+
provider?: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
}): string;
|
|
8
|
+
export declare function renderHTMLTabs(files: Array<{
|
|
9
|
+
file: string;
|
|
10
|
+
review: string;
|
|
11
|
+
diff: string;
|
|
12
|
+
}>, meta?: {
|
|
13
|
+
aiInvoked?: boolean;
|
|
14
|
+
aiSucceeded?: boolean;
|
|
15
|
+
provider?: string;
|
|
16
|
+
model?: string;
|
|
17
|
+
status?: string;
|
|
18
|
+
}): string;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { parse, html } from 'diff2html';
|
|
2
|
+
export function renderHTML(diff, review, meta) {
|
|
3
|
+
const json = parse(diff, { inputFormat: 'diff' });
|
|
4
|
+
const diffHtml = html(json, { showFiles: true, matching: 'lines' });
|
|
5
|
+
const badge = meta
|
|
6
|
+
? `<div class="meta">
|
|
7
|
+
<span class="badge">AI: ${meta.aiInvoked ? (meta.aiSucceeded ? '参与' : '尝试失败') : '未参与'}</span>
|
|
8
|
+
${meta.provider ? `<span class="badge">Provider: ${escapeHtml(meta.provider)}</span>` : ''}
|
|
9
|
+
${meta.model ? `<span class="badge">Model: ${escapeHtml(meta.model)}</span>` : ''}
|
|
10
|
+
${meta.status ? `<div class="status">${escapeHtml(meta.status)}</div>` : ''}
|
|
11
|
+
</div>`
|
|
12
|
+
: '';
|
|
13
|
+
const reviewHtml = review ? `<div class="review"><pre>${escapeHtml(review)}</pre></div>` : '';
|
|
14
|
+
const page = `<!doctype html>
|
|
15
|
+
<html>
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="utf-8">
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
19
|
+
<title>code-gate review</title>
|
|
20
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css">
|
|
21
|
+
<style>
|
|
22
|
+
body{font-family: ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial}
|
|
23
|
+
.container{max-width:1200px;margin:24px auto;padding:0 16px}
|
|
24
|
+
.review{background:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;padding:16px;margin-bottom:16px;white-space:pre-wrap}
|
|
25
|
+
.meta{display:flex;gap:8px;align-items:center;margin-bottom:12px}
|
|
26
|
+
.badge{display:inline-block;background:#eaeef2;border:1px solid #d0d7de;border-radius:999px;padding:4px 10px;font-size:12px;color:#24292f}
|
|
27
|
+
.status{background:#fff8c5;border:1px solid #d0d7de;border-radius:6px;padding:8px 12px;font-size:12px;color:#4b4b00}
|
|
28
|
+
</style>
|
|
29
|
+
</head>
|
|
30
|
+
<body>
|
|
31
|
+
<div class="container">
|
|
32
|
+
<h1>Code Review</h1>
|
|
33
|
+
${badge}
|
|
34
|
+
${reviewHtml}
|
|
35
|
+
${diffHtml}
|
|
36
|
+
</div>
|
|
37
|
+
</body>
|
|
38
|
+
</html>`;
|
|
39
|
+
return page;
|
|
40
|
+
}
|
|
41
|
+
function escapeHtml(s) {
|
|
42
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
43
|
+
}
|
|
44
|
+
export function renderHTMLTabs(files, meta) {
|
|
45
|
+
const tabs = files
|
|
46
|
+
.map((f, i) => `<button type="button" class="tab" data-idx="${i}" title="${escapeHtml(f.file)}">${escapeHtml(f.file)}</button>`)
|
|
47
|
+
.join('');
|
|
48
|
+
const panes = files
|
|
49
|
+
.map((f, i) => {
|
|
50
|
+
const json = parse(f.diff, { inputFormat: 'diff' });
|
|
51
|
+
const diffHtml = html(json, { showFiles: false, matching: 'lines' });
|
|
52
|
+
const reviewHtml = f.review ? `<div class="review-body"><pre>${escapeHtml(f.review)}</pre></div>` : `<div class="review-body empty">暂无审查内容</div>`;
|
|
53
|
+
return `<div class="pane" data-idx="${i}">
|
|
54
|
+
<div class="split">
|
|
55
|
+
<div class="panel panel-left">
|
|
56
|
+
<div class="panel-title">AI Review</div>
|
|
57
|
+
${reviewHtml}
|
|
58
|
+
</div>
|
|
59
|
+
<div class="panel panel-right">
|
|
60
|
+
<div class="panel-title">Diff</div>
|
|
61
|
+
<div class="diff-body">${diffHtml}</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>`;
|
|
65
|
+
})
|
|
66
|
+
.join('');
|
|
67
|
+
const badge = meta
|
|
68
|
+
? `<div class="meta">
|
|
69
|
+
<span class="badge">AI: ${meta.aiInvoked ? (meta.aiSucceeded ? '参与' : '尝试失败') : '未参与'}</span>
|
|
70
|
+
${meta.provider ? `<span class="badge">Provider: ${escapeHtml(meta.provider)}</span>` : ''}
|
|
71
|
+
${meta.model ? `<span class="badge">Model: ${escapeHtml(meta.model)}</span>` : ''}
|
|
72
|
+
${meta.status ? `<div class="status">${escapeHtml(meta.status)}</div>` : ''}
|
|
73
|
+
</div>`
|
|
74
|
+
: '';
|
|
75
|
+
const page = `<!doctype html>
|
|
76
|
+
<html>
|
|
77
|
+
<head>
|
|
78
|
+
<meta charset="utf-8">
|
|
79
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
80
|
+
<title>code-gate review</title>
|
|
81
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css">
|
|
82
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
83
|
+
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js"></script>
|
|
84
|
+
<style>
|
|
85
|
+
*{box-sizing:border-box}
|
|
86
|
+
html,body{height:100%;}
|
|
87
|
+
body{font-family: ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;overflow:hidden}
|
|
88
|
+
.container{width:100%;max-width:100%;margin:0 auto;padding:16px}
|
|
89
|
+
.tabs{display:flex;gap:8px;overflow-x:auto;padding:8px 0;border-bottom:1px solid #d0d7de}
|
|
90
|
+
.tab{flex:0 0 auto;background:#f0f2f5;border:1px solid #d0d7de;border-radius:4px;padding:8px 12px;font-size:12px;color:#24292f;cursor:pointer}
|
|
91
|
+
.tab.active{background:#0969da;color:#fff;border-color:#0969da}
|
|
92
|
+
.pane{display:none;padding-top:12px}
|
|
93
|
+
.pane.active{display:block}
|
|
94
|
+
.split{display:flex;gap:12px;align-items:stretch}
|
|
95
|
+
.panel{border:1px solid #d0d7de;border-radius:6px;background:#fff;display:flex;flex-direction:column;height:calc(100vh - 180px);flex:1 1 50%;min-width:0;max-width:50%}
|
|
96
|
+
.panel-title{font-weight:600;padding:8px 12px;border-bottom:1px solid #d0d7de;background:#f6f8fa}
|
|
97
|
+
.review-body{padding:12px;overflow:auto}
|
|
98
|
+
.review-body.empty{color:#57606a}
|
|
99
|
+
.diff-body{padding:12px;overflow:auto}
|
|
100
|
+
.diff-body .d2h-code-side-linenumber,.diff-body .d2h-code-linenumber{position:static!important}
|
|
101
|
+
.meta{display:flex;gap:8px;align-items:center;margin-bottom:12px}
|
|
102
|
+
.badge{display:inline-block;background:#eaeef2;border:1px solid #d0d7de;border-radius:999px;padding:4px 10px;font-size:12px;color:#24292f}
|
|
103
|
+
.status{background:#fff8c5;border:1px solid #d0d7de;border-radius:6px;padding:8px 12px;font-size:12px;color:#4b4b00}
|
|
104
|
+
</style>
|
|
105
|
+
</head>
|
|
106
|
+
<body>
|
|
107
|
+
<div class="container">
|
|
108
|
+
<h1>Code Review</h1>
|
|
109
|
+
${badge}
|
|
110
|
+
<div class="tabs">${tabs}</div>
|
|
111
|
+
${panes}
|
|
112
|
+
</div>
|
|
113
|
+
<script>
|
|
114
|
+
const tabs=[...document.querySelectorAll('.tab')];
|
|
115
|
+
const panes=[...document.querySelectorAll('.pane')];
|
|
116
|
+
function activate(i){
|
|
117
|
+
tabs.forEach(t=>t.classList.toggle('active',t.dataset.idx==i));
|
|
118
|
+
panes.forEach(p=>p.classList.toggle('active',p.dataset.idx==i));
|
|
119
|
+
}
|
|
120
|
+
tabs.forEach(t=>t.addEventListener('click',()=>activate(t.dataset.idx)));
|
|
121
|
+
activate(0);
|
|
122
|
+
// Render markdown reviews
|
|
123
|
+
try{
|
|
124
|
+
const reviews=${JSON.stringify(files.map(f => f.review || ''))};
|
|
125
|
+
reviews.forEach((md,i)=>{
|
|
126
|
+
const el=document.querySelector(\`.pane[data-idx="\${i}"] .review-body\`);
|
|
127
|
+
if(el && md){
|
|
128
|
+
const html=DOMPurify.sanitize(marked.parse(md));
|
|
129
|
+
el.innerHTML=html;
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
}catch(e){}
|
|
133
|
+
</script>
|
|
134
|
+
</body>
|
|
135
|
+
</html>`;
|
|
136
|
+
return page;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/ui/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
export async function serveReview(cfg, html) {
|
|
7
|
+
const port = cfg.ui?.port ?? 5175;
|
|
8
|
+
const open = cfg.ui?.openBrowser ?? true;
|
|
9
|
+
const id = Date.now().toString(36);
|
|
10
|
+
const route = `/review/${id}`;
|
|
11
|
+
const server = http.createServer((req, res) => {
|
|
12
|
+
if (req.url && req.url.startsWith(route)) {
|
|
13
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
14
|
+
res.end(html);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
res.statusCode = 404;
|
|
18
|
+
res.end('Not Found');
|
|
19
|
+
});
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
server.listen(port, () => {
|
|
22
|
+
const url = `http://localhost:${port}${route}`;
|
|
23
|
+
process.stdout.write(`预览地址:${url}\n`);
|
|
24
|
+
if (open)
|
|
25
|
+
openBrowser(url);
|
|
26
|
+
server.unref();
|
|
27
|
+
resolve(url);
|
|
28
|
+
});
|
|
29
|
+
server.on('error', () => {
|
|
30
|
+
const p = path.join(os.tmpdir(), `code-gate-${id}.html`);
|
|
31
|
+
fs.writeFileSync(p, html, 'utf8');
|
|
32
|
+
const url = `file://${p}`;
|
|
33
|
+
process.stdout.write(`预览地址:${url}\n`);
|
|
34
|
+
if (open)
|
|
35
|
+
openBrowser(url);
|
|
36
|
+
resolve(url);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function openBrowser(url) {
|
|
41
|
+
const platform = process.platform;
|
|
42
|
+
if (platform === 'darwin')
|
|
43
|
+
spawn('open', [url], { stdio: 'ignore', detached: true });
|
|
44
|
+
else if (platform === 'win32')
|
|
45
|
+
spawn('cmd', ['/c', 'start', url], { stdio: 'ignore', detached: true });
|
|
46
|
+
else
|
|
47
|
+
spawn('xdg-open', [url], { stdio: 'ignore', detached: true });
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/ui/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "code-gate",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-assisted code review at git commit using local Ollama or DeepSeek API, with GitHub-style diff UI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"code-gate": "bin/code-gate.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"bin",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"git",
|
|
17
|
+
"commit",
|
|
18
|
+
"code review",
|
|
19
|
+
"DeepSeek",
|
|
20
|
+
"Ollama",
|
|
21
|
+
"diff2html"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://example.com/your-repo-url.git"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.json",
|
|
30
|
+
"build:watch": "tsc -w -p tsconfig.json",
|
|
31
|
+
"clean": "rimraf dist",
|
|
32
|
+
"dev": "node --enable-source-maps dist/cli.js",
|
|
33
|
+
"setup": "node --enable-source-maps dist/commands/setup.js",
|
|
34
|
+
"hook": "node --enable-source-maps dist/commands/hook.js",
|
|
35
|
+
"prepare": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"commander": "^12.1.0",
|
|
42
|
+
"diff2html": "^3.4.47",
|
|
43
|
+
"highlight.js": "^11.9.0",
|
|
44
|
+
"openai": "^4.60.0",
|
|
45
|
+
"yaml": "^2.6.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^22.7.5",
|
|
49
|
+
"typescript": "^5.6.3",
|
|
50
|
+
"rimraf": "^6.0.1"
|
|
51
|
+
}
|
|
52
|
+
}
|