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.
Files changed (48) hide show
  1. package/README.md +118 -0
  2. package/bin/code-gate.js +29 -0
  3. package/dist/__tests__/config.test.d.ts +1 -0
  4. package/dist/__tests__/config.test.js +28 -0
  5. package/dist/__tests__/config.test.js.map +1 -0
  6. package/dist/__tests__/render.test.d.ts +1 -0
  7. package/dist/__tests__/render.test.js +16 -0
  8. package/dist/__tests__/render.test.js.map +1 -0
  9. package/dist/cli.d.ts +1 -0
  10. package/dist/cli.js +32 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands/hook.d.ts +1 -0
  13. package/dist/commands/hook.js +71 -0
  14. package/dist/commands/hook.js.map +1 -0
  15. package/dist/commands/init.d.ts +1 -0
  16. package/dist/commands/init.js +129 -0
  17. package/dist/commands/init.js.map +1 -0
  18. package/dist/commands/setup.d.ts +1 -0
  19. package/dist/commands/setup.js +19 -0
  20. package/dist/commands/setup.js.map +1 -0
  21. package/dist/config.d.ts +40 -0
  22. package/dist/config.js +86 -0
  23. package/dist/config.js.map +1 -0
  24. package/dist/core/review-flow.d.ts +1 -0
  25. package/dist/core/review-flow.js +109 -0
  26. package/dist/core/review-flow.js.map +1 -0
  27. package/dist/git.d.ts +4 -0
  28. package/dist/git.js +32 -0
  29. package/dist/git.js.map +1 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +2 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/llm/deepseek.d.ts +6 -0
  34. package/dist/llm/deepseek.js +24 -0
  35. package/dist/llm/deepseek.js.map +1 -0
  36. package/dist/llm/ollama.d.ts +6 -0
  37. package/dist/llm/ollama.js +81 -0
  38. package/dist/llm/ollama.js.map +1 -0
  39. package/dist/log.d.ts +3 -0
  40. package/dist/log.js +10 -0
  41. package/dist/log.js.map +1 -0
  42. package/dist/ui/render.d.ts +18 -0
  43. package/dist/ui/render.js +138 -0
  44. package/dist/ui/render.js.map +1 -0
  45. package/dist/ui/server.d.ts +2 -0
  46. package/dist/ui/server.js +49 -0
  47. package/dist/ui/server.js.map +1 -0
  48. 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
@@ -0,0 +1,4 @@
1
+ export declare function getStagedFiles(): string[];
2
+ export declare function getStagedDiff(): string;
3
+ export declare function getStagedDiffForFile(file: string): string;
4
+ export declare function filterFiles(files: string[], fileTypes?: string[]): string[];
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
@@ -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"}
@@ -0,0 +1 @@
1
+ export * from './cli.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './cli.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA"}
@@ -0,0 +1,6 @@
1
+ import { Config } from '../config.js';
2
+ export interface ReviewInput {
3
+ prompt: string;
4
+ diff: string;
5
+ }
6
+ export declare function deepseekReview(cfg: Config, input: ReviewInput): Promise<string>;
@@ -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,6 @@
1
+ import { Config } from '../config.js';
2
+ export interface ReviewInput {
3
+ prompt: string;
4
+ diff: string;
5
+ }
6
+ export declare function ollamaReview(cfg: Config, input: ReviewInput): Promise<string>;
@@ -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":"AAOA,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAW,EAAE,KAAkB;IAChE,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,IAAI,wBAAwB,CAAA;IAC/D,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,IAAI,eAAe,CAAA;IAClD,MAAM,OAAO,GAAG,GAAG,OAAO,WAAW,CAAA;IACrC,MAAM,QAAQ,GAAG;QACf,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;QACD,MAAM,EAAE,KAAK;KACd,CAAA;IACD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;IACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CAAA;IAClE,IAAI,GAAa,CAAA;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE;YACzB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;YAC9B,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,YAAY,CAAC,KAAK,CAAC,CAAA;QACnB,oDAAoD;QACpD,MAAM,MAAM,GAAG,GAAG,OAAO,eAAe,CAAA;QACxC,MAAM,OAAO,GAAG;YACd,KAAK;YACL,MAAM,EACJ,KAAK,CAAC,MAAM;gBACZ,sDAAsD;gBACtD,KAAK,CAAC,IAAI;YACZ,MAAM,EAAE,KAAK;SACd,CAAA;QACD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;YAC/B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,CAAC,CAAA;QACF,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;YAC7C,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC,CAAA;QAClE,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;QAC/B,OAAQ,KAAK,EAAE,QAAmB,IAAI,EAAE,CAAA;IAC1C,CAAC;IACD,YAAY,CAAC,KAAK,CAAC,CAAA;IACnB,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;QAC5C,8CAA8C;QAC9C,MAAM,MAAM,GAAG,GAAG,OAAO,eAAe,CAAA;QACxC,MAAM,OAAO,GAAG;YACd,KAAK;YACL,MAAM,EACJ,KAAK,CAAC,MAAM;gBACZ,sDAAsD;gBACtD,KAAK,CAAC,IAAI;YACZ,MAAM,EAAE,KAAK;SACd,CAAA;QACD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;YAC/B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,CAAC,CAAA;QACF,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;YAC9C,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,MAAM,IAAI,GAAG,sBAAsB,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAA;QACzG,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;QAC/B,OAAQ,KAAK,EAAE,QAAmB,IAAI,EAAE,CAAA;IAC1C,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;IAC7B,MAAM,OAAO,GACV,IAAI,EAAE,OAAO,EAAE,OAAkB;QACjC,IAAI,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,OAAkB;QAC/D,EAAE,CAAA;IACJ,OAAO,OAAO,CAAA;AAChB,CAAC"}
package/dist/log.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function info(msg: string): void;
2
+ export declare function warn(msg: string): void;
3
+ export declare function error(msg: string): void;
package/dist/log.js ADDED
@@ -0,0 +1,10 @@
1
+ export function info(msg) {
2
+ process.stdout.write(msg + '\n');
3
+ }
4
+ export function warn(msg) {
5
+ process.stderr.write(msg + '\n');
6
+ }
7
+ export function error(msg) {
8
+ process.stderr.write(msg + '\n');
9
+ }
10
+ //# sourceMappingURL=log.js.map
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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,2 @@
1
+ import { Config } from '../config.js';
2
+ export declare function serveReview(cfg: Config, html: string): Promise<string>;
@@ -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
+ }