虽然,现在类似cursor的code agent完全可以实现一键commit和review代码,但是如果公司因为安全限制不能用这些工具呢,那么就要自己接入信任的大模型写脚本
实现 code review 脚本
- 触发机制: 必须是git commit 阶段
- 触发hooks: pre-commit
- 实现步骤
1、你的项目中装了 husky, 通常项目会在 pre-commit 阶段执行 lint-statged 校验
package.json
// package.json
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix --max-warnings=0"
]
}
pre-commit
npx lint-staged
# 代码 Review(如果启用)
tsx scripts/review-code.ts || true
review-code
// review-code.ts
import axios, { AxiosError } from "axios";
import dotenv from "dotenv";
import path from "path";
import { execSync } from "child_process";
import { recordTokenUsage, generateSessionId } from "./token-tracker";
// 加载环境变量
dotenv.config({ path: path.resolve(process.cwd(), ".env") });
/**
* Moonshot/OpenAI 兼容 API 请求格式
*/
interface MoonshotApiRequest {
model: string;
messages: Array<{
role: "system" | "user" | "assistant";
content: string;
}>;
temperature?: number;
max_tokens?: number;
}
/**
* Moonshot/OpenAI 兼容 API 响应格式
*/
interface MoonshotApiResponse {
id: string;
object: string;
created: number;
model: string;
choices: Array<{
index: number;
message: {
role: string;
content: string;
};
finish_reason: string;
}>;
usage: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
error?: {
message: string;
type: string;
};
}
/**
* Review 结果
*/
interface ReviewResult {
hasIssues: boolean;
hasBlockingIssues: boolean;
review: string;
}
/**
* 验证环境变量是否配置
*/
const validateEnv = (): void => {
const requiredEnv = ["COMMIT_API_URL", "COMMIT_API_KEY"];
const missingEnv = requiredEnv.filter((key) => !process.env[key]);
if (missingEnv.length > 0) {
throw new Error(
`缺少必要环境变量:${missingEnv.join(", ")}\n请在 .env 文件中配置`
);
}
};
/**
* 获取 Git diff(暂存区的变更)
*/
const getGitDiff = (): string => {
try {
const diff = execSync("git diff --cached", { encoding: "utf-8" });
return diff || "";
} catch (error) {
console.warn("⚠️ 无法获取 Git diff,可能没有暂存的文件");
return "";
}
};
/**
* 获取暂存的文件列表
*/
const getStagedFiles = (): string[] => {
try {
const files = execSync("git diff --cached --name-only", {
encoding: "utf-8",
})
.trim()
.split("\n")
.filter((file) => file.length > 0);
return files;
} catch (error) {
return [];
}
};
/**
* 获取当前分支名
*/
const getCurrentBranch = (): string => {
try {
const branch = execSync("git branch --show-current", {
encoding: "utf-8",
}).trim();
return branch || "main";
} catch (error) {
return "main";
}
};
/**
* 构建代码 Review 的 Prompt
*/
const buildReviewPrompt = (
diff: string,
files: string[],
branch: string
): string => {
let prompt = `请对以下代码变更进行专业的代码 Review,重点关注:
1. **代码质量**:代码风格、可读性、命名规范
2. **潜在 Bug**:逻辑错误、边界情况、异常处理
3. **安全性**:安全漏洞、敏感信息泄露、注入风险
4. **性能**:性能问题、不必要的计算、内存泄漏
5. **最佳实践**:设计模式、架构合理性、可维护性
6. **TypeScript/JavaScript 规范**:类型安全、ESLint 规则
请按照以下格式输出 Review 结果:
## 🔍 代码 Review 结果
### ✅ 优点
- [优点1]
- [优点2]
### ⚠️ 建议改进
- [建议1]
- [建议2]
### 🐛 潜在问题
- [问题1]
- [问题2]
### 🔒 安全问题
- [安全问题1]
- [安全问题2]
### 📝 总结
[总体评价和建议]
**严重程度**:如果发现严重问题(如安全漏洞、会导致崩溃的 Bug),请在最后一行单独标注:\`BLOCKING: 问题描述\`
`;
if (files.length > 0) {
prompt += `变更的文件列表:\n${files.map((f) => `- ${f}`).join("\n")}\n\n`;
}
prompt += `当前分支:${branch}\n\n`;
if (diff) {
// 限制 diff 长度,避免超出 token 限制
const maxDiffLength = 12000; // 大约 3000 tokens
const truncatedDiff =
diff.length > maxDiffLength
? diff.substring(0, maxDiffLength) + "\n\n... (diff 已截断)"
: diff;
prompt += `代码变更 (Git Diff):\n\`\`\`\n${truncatedDiff}\n\`\`\`\n\n`;
}
prompt += `请提供详细的代码 Review 意见。`;
return prompt;
};
/**
* 调用 Moonshot API 进行代码 Review
*/
const reviewCodeByApi = async (
diff: string,
files: string[],
branch: string
): Promise<ReviewResult> => {
const apiUrl = process.env.COMMIT_API_URL!;
const apiKey = process.env.COMMIT_API_KEY!;
const model = process.env.COMMIT_MODEL || "moonshot-v1-8k";
try {
console.log("🔍 正在进行代码 Review...");
console.log(`📁 变更文件数:${files.length}`);
if (diff) {
console.log(`📝 Diff 大小:${(diff.length / 1024).toFixed(2)} KB`);
}
console.log(`🤖 使用模型:${model}`);
const prompt = buildReviewPrompt(diff, files, branch);
const apiRequest: MoonshotApiRequest = {
model: model,
messages: [
{
role: "system",
content:
"你是一个资深的代码审查专家,擅长发现代码中的问题、安全漏洞、性能问题和最佳实践。请提供专业、详细、可操作的代码 Review 意见。",
},
{
role: "user",
content: prompt,
},
],
temperature: 0.3, // 降低温度以获得更一致和准确的 review
max_tokens: 2000,
};
const response = await axios.post<MoonshotApiResponse>(apiUrl, apiRequest, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
timeout: 30000,
});
const result = response.data;
if (result.error) {
throw new Error(
`API 返回错误:${result.error.message} (${result.error.type})`
);
}
if (
!result.choices ||
result.choices.length === 0 ||
!result.choices[0].message?.content
) {
throw new Error("API 返回的响应格式不正确");
}
const review = result.choices[0].message.content.trim();
// 检查是否有阻塞性问题
const hasBlockingIssues = review.includes("BLOCKING:");
const hasIssues =
review.includes("⚠️") ||
review.includes("🐛") ||
review.includes("🔒") ||
review.includes("潜在问题") ||
review.includes("安全问题");
return {
hasIssues,
hasBlockingIssues,
review,
};
} catch (error) {
const axiosError = error as AxiosError;
if (axiosError.response) {
const errorData = axiosError.response.data as any;
const errorMessage =
errorData?.error?.message ||
(typeof errorData === "string" ? errorData : JSON.stringify(errorData));
throw new Error(
`API 响应错误 [${axiosError.response.status}]:${errorMessage}`
);
} else if (axiosError.request) {
throw new Error(
`API 请求失败:${axiosError.message || "网络错误或超时"}`
);
} else {
throw new Error(`请求配置错误:${axiosError.message}`);
}
}
};
/**
* 主函数
*/
const main = async () => {
const enableAutoReview = process.env.CODE_REVIEW_ENABLE !== "false"; // 默认启用
const blockOnIssues = process.env.CODE_REVIEW_BLOCK_ON_ISSUES === "true"; // 默认不阻塞
if (!enableAutoReview) {
console.log("ℹ️ 代码 Review 已禁用(CODE_REVIEW_ENABLE=false)");
return;
}
try {
// 1. 验证环境变量
validateEnv();
// 2. 获取 Git 变更信息
const diff = getGitDiff();
const files = getStagedFiles();
const branch = getCurrentBranch();
if (files.length === 0) {
console.log("ℹ️ 没有暂存的文件,跳过代码 Review");
return;
}
if (!diff || diff.trim().length === 0) {
console.log("ℹ️ 没有代码变更,跳过代码 Review");
return;
}
// 3. 进行代码 Review
const reviewResult = await reviewCodeByApi(diff, files, branch);
// 4. 输出 Review 结果
console.log("\n" + "=".repeat(60));
console.log("📋 代码 Review 结果");
console.log("=".repeat(60) + "\n");
console.log(reviewResult.review);
console.log("\n" + "=".repeat(60) + "\n");
// 5. 根据结果决定是否阻止提交
if (reviewResult.hasBlockingIssues && blockOnIssues) {
console.error("❌ 发现严重问题,提交已阻止!");
console.error(
"💡 提示:修复问题后重新提交,或设置 CODE_REVIEW_BLOCK_ON_ISSUES=false 来允许提交"
);
process.exit(1);
} else if (reviewResult.hasBlockingIssues) {
console.warn("⚠️ 发现严重问题,但不会阻止提交");
console.warn(
"💡 建议:修复问题后再提交,或设置 CODE_REVIEW_BLOCK_ON_ISSUES=true 来强制修复"
);
} else if (reviewResult.hasIssues) {
console.log("ℹ️ 发现一些建议改进项,但不影响提交");
} else {
console.log("✅ 代码 Review 通过,未发现明显问题");
}
} catch (error) {
const errorMessage = (error as Error).message;
console.error("\n❌ 代码 Review 失败:");
console.error(errorMessage);
console.error("\n💡 提示:Review 失败不会阻止提交,但建议检查网络和配置");
// Review 失败不阻止提交,只打印警告
}
};
// 执行主函数
main();
主要是上下文传入
// 获得暂存区的变更
git diff --cached
// 获得暂存文件列表
git diff --cached --name-only
关键代码, 传入prmpt,调用大模型API
/**
* 调用 Moonshot API 进行代码 Review
*/
const reviewCodeByApi = async (
diff: string,
files: string[],
branch: string
): Promise<ReviewResult> => {
const apiUrl = process.env.COMMIT_API_URL!;
const apiKey = process.env.COMMIT_API_KEY!;
const model = process.env.COMMIT_MODEL || "moonshot-v1-8k";
try {
console.log("🔍 正在进行代码 Review...");
console.log(`📁 变更文件数:${files.length}`);
if (diff) {
console.log(`📝 Diff 大小:${(diff.length / 1024).toFixed(2)} KB`);
}
console.log(`🤖 使用模型:${model}`);
const prompt = buildReviewPrompt(diff, files, branch);
const apiRequest: MoonshotApiRequest = {
model: model,
messages: [
{
role: "system",
content:
"你是一个资深的代码审查专家,擅长发现代码中的问题、安全漏洞、性能问题和最佳实践。请提供专业、详细、可操作的代码 Review 意见。",
},
{
role: "user",
content: prompt,
},
],
temperature: 0.3, // 降低温度以获得更一致和准确的 review
max_tokens: 2000,
};
const response = await axios.post<MoonshotApiResponse>(apiUrl, apiRequest, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
timeout: 30000,
});
const result = response.data;
if (result.error) {
throw new Error(
`API 返回错误:${result.error.message} (${result.error.type})`
);
}
if (
!result.choices ||
result.choices.length === 0 ||
!result.choices[0].message?.content
) {
throw new Error("API 返回的响应格式不正确");
}
const review = result.choices[0].message.content.trim();
// 检查是否有阻塞性问题
const hasBlockingIssues = review.includes("BLOCKING:");
const hasIssues =
review.includes("⚠️") ||
review.includes("🐛") ||
review.includes("🔒") ||
review.includes("潜在问题") ||
review.includes("安全问题");
return {
hasIssues,
hasBlockingIssues,
review,
};
} catch (error) {
const axiosError = error as AxiosError;
if (axiosError.response) {
const errorData = axiosError.response.data as any;
const errorMessage =
errorData?.error?.message ||
(typeof errorData === "string" ? errorData : JSON.stringify(errorData));
throw new Error(
`API 响应错误 [${axiosError.response.status}]:${errorMessage}`
);
} else if (axiosError.request) {
throw new Error(
`API 请求失败:${axiosError.message || "网络错误或超时"}`
);
} else {
throw new Error(`请求配置错误:${axiosError.message}`);
}
}
};
prompt 传入, 传入了上下文
/**
* 构建代码 Review 的 Prompt
*/
const buildReviewPrompt = (
diff: string,
files: string[],
branch: string
): string => {
let prompt = `请对以下代码变更进行专业的代码 Review,重点关注:
1. **代码质量**:代码风格、可读性、命名规范
2. **潜在 Bug**:逻辑错误、边界情况、异常处理
3. **安全性**:安全漏洞、敏感信息泄露、注入风险
4. **性能**:性能问题、不必要的计算、内存泄漏
5. **最佳实践**:设计模式、架构合理性、可维护性
6. **TypeScript/JavaScript 规范**:类型安全、ESLint 规则
请按照以下格式输出 Review 结果:
## 🔍 代码 Review 结果
### ✅ 优点
- [优点1]
- [优点2]
### ⚠️ 建议改进
- [建议1]
- [建议2]
### 🐛 潜在问题
- [问题1]
- [问题2]
### 🔒 安全问题
- [安全问题1]
- [安全问题2]
### 📝 总结
[总体评价和建议]
**严重程度**:如果发现严重问题(如安全漏洞、会导致崩溃的 Bug),请在最后一行单独标注:\`BLOCKING: 问题描述\`
`;
if (files.length > 0) {
prompt += `变更的文件列表:\n${files.map((f) => `- ${f}`).join("\n")}\n\n`;
}
prompt += `当前分支:${branch}\n\n`;
if (diff) {
// 限制 diff 长度,避免超出 token 限制
const maxDiffLength = 12000; // 大约 3000 tokens
const truncatedDiff =
diff.length > maxDiffLength
? diff.substring(0, maxDiffLength) + "\n\n... (diff 已截断)"
: diff;
prompt += `代码变更 (Git Diff):\n\`\`\`\n${truncatedDiff}\n\`\`\`\n\n`;
}
prompt += `请提供详细的代码 Review 意见。`;
return prompt;
};
总结: 这应该是大模型最基本的应用,code review 就是在 pre-commit 阶段,通过执行git diff --cached 获取暂存区的 diff 和git diff --cached --name-only 获取暂存的文件列表, 结合 prompt,让ai进行审查,这是最基本的操作,毕竟这里没有涉及到 多轮多话、 tool use、 sse、context caching 等
实现 commit 脚本
- 触发事件,在review 代码之后
- 触发hook,pre-commit-msg
- 实现步骤
1、pre-commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 自动生成 commit message(如果启用)
# 可以通过设置 COMMIT_AUTO_GENERATE=false 来禁用
tsx scripts/generate-commit.ts "$1" "$2" "$3" || true
2、gen-commit-msg的文件
import axios, { AxiosError } from "axios";
import dotenv from "dotenv";
import fs from "fs/promises";
import path from "path";
import { execSync } from "child_process";
import { recordTokenUsage, generateSessionId } from "./token-tracker";
// 加载环境变量(API 地址、密钥等)
dotenv.config({ path: path.resolve(process.cwd(), ".env") });
/**
* 类型定义:Git 变更元数据(内部使用)
*/
interface CommitMetadata {
// 提交类型(feat/fix/docs/style/refactor/test/chore 或 "auto" 让 API 自动判断)
type?: string;
// 提交范围(如 user、order、payment,无则填 "global")
scope?: string;
// 核心描述(简洁,不超过 50 字),如果为空则让 API 自动生成
subject?: string;
// 详细描述(换行分隔),可以包含文件列表等信息
body?: string;
// 可选:关闭的 Issue(如 "Closes #123, Fixes #456")
footer?: string;
// 可选:是否破坏性变更(true/false)
isBreakingChange?: boolean;
// 可选:Git diff 内容(用于自动分析)
diff?: string;
// 可选:变更的文件列表
files?: string[];
// 可选:当前分支名
branch?: string;
}
/**
* Moonshot/OpenAI 兼容 API 请求格式
*/
interface MoonshotApiRequest {
model: string;
messages: Array<{
role: "system" | "user" | "assistant";
content: string;
}>;
temperature?: number;
max_tokens?: number;
}
/**
* Moonshot/OpenAI 兼容 API 响应格式
*/
interface MoonshotApiResponse {
id: string;
object: string;
created: number;
model: string;
choices: Array<{
index: number;
message: {
role: string;
content: string;
};
finish_reason: string;
}>;
usage: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
error?: {
message: string;
type: string;
};
}
/**
* 验证环境变量是否配置
*/
const validateEnv = (): void => {
const requiredEnv = ["COMMIT_API_URL", "COMMIT_API_KEY"];
const missingEnv = requiredEnv.filter((key) => !process.env[key]);
if (missingEnv.length > 0) {
throw new Error(
`缺少必要环境变量:${missingEnv.join(", ")}\n请在 .env 文件中配置`
);
}
};
/**
* 获取 Git diff(暂存区的变更)
*/
const getGitDiff = (): string => {
try {
// 获取暂存区的 diff
const diff = execSync("git diff --cached", { encoding: "utf-8" });
return diff || "";
} catch (error) {
console.warn("⚠️ 无法获取 Git diff,可能没有暂存的文件");
return "";
}
};
/**
* 获取暂存的文件列表
*/
const getStagedFiles = (): string[] => {
try {
const files = execSync("git diff --cached --name-only", {
encoding: "utf-8",
})
.trim()
.split("\n")
.filter((file) => file.length > 0);
return files;
} catch (error) {
return [];
}
};
/**
* 获取当前分支名
*/
const getCurrentBranch = (): string => {
try {
const branch = execSync("git branch --show-current", {
encoding: "utf-8",
}).trim();
return branch || "main";
} catch (error) {
return "main";
}
};
/**
* 自动分析 Git 变更并生成提交元数据
* 将 diff 和文件信息发送给 API,让 API 自动生成 commit message
*/
const analyzeGitChanges = async (): Promise<CommitMetadata> => {
const diff = getGitDiff();
const stagedFiles = getStagedFiles();
const branch = getCurrentBranch();
if (stagedFiles.length === 0) {
throw new Error("❌ 没有暂存的文件!请先使用 'git add' 添加文件。");
}
// 分析文件路径,尝试推断 scope
const scope = inferScopeFromFiles(stagedFiles);
// 构建请求数据,将 diff 和文件信息发送给 API
// API 会根据这些信息自动生成 commit message
return {
type: "auto", // 让 API 自动判断类型
scope: scope || "global",
subject: "", // API 会自动生成
body: `变更文件列表:\n${stagedFiles
.map((f) => `- ${f}`)
.join("\n")}\n\n当前分支:${branch}`,
diff: diff, // Git diff 内容
files: stagedFiles, // 变更的文件列表
branch: branch, // 当前分支
};
};
/**
* 从文件路径推断 scope
*/
const inferScopeFromFiles = (files: string[]): string | undefined => {
// 分析文件路径,提取可能的 scope
// 例如:app/blog/page.tsx -> blog
// components/Header.tsx -> components
const scopes = new Set<string>();
for (const file of files) {
const parts = file.split("/");
if (parts.length > 1) {
// 提取第一层目录作为可能的 scope
const firstDir = parts[0];
if (
firstDir !== "app" &&
firstDir !== "components" &&
firstDir !== "lib" &&
firstDir !== "scripts"
) {
scopes.add(firstDir);
} else if (parts.length > 2) {
// 如果是 app/xxx/... 格式,提取第二层
scopes.add(parts[1]);
}
}
}
if (scopes.size === 1) {
return Array.from(scopes)[0];
} else if (scopes.size > 1) {
// 多个 scope,返回第一个
return Array.from(scopes)[0];
}
return undefined;
};
/**
* 构建生成 Commit Message 的 Prompt
*/
const buildCommitPrompt = (metadata: CommitMetadata): string => {
const { files, diff, branch, scope } = metadata;
let prompt = `请根据以下 Git 变更信息,生成一个符合 Conventional Commits 规范的 commit message。
要求:
1. 使用中文描述
2. 格式:<type>(<scope>): <subject>
3. type 必须是:feat, fix, docs, style, refactor, test, chore, perf, ci, build 之一
4. scope 是可选的,表示影响范围
5. subject 是简洁的描述,不超过 50 字
6. 如果变更较大,可以在 subject 后添加详细描述(用空行分隔)
`;
if (files && files.length > 0) {
prompt += `变更的文件列表:\n${files.map((f) => `- ${f}`).join("\n")}\n\n`;
}
if (branch) {
prompt += `当前分支:${branch}\n\n`;
}
if (scope && scope !== "global") {
prompt += `建议的 scope:${scope}\n\n`;
}
if (diff) {
// 限制 diff 长度,避免超出 token 限制
const maxDiffLength = 8000; // 大约 2000 tokens
const truncatedDiff =
diff.length > maxDiffLength
? diff.substring(0, maxDiffLength) + "\n\n... (diff 已截断)"
: diff;
prompt += `Git Diff 内容:\n\`\`\`\n${truncatedDiff}\n\`\`\`\n\n`;
}
prompt += `请只返回生成的 commit message,不要包含其他解释或说明。`;
return prompt;
};
/**
* 调用 Moonshot API 生成 Commit Message
*/
const generateCommitByApi = async (
metadata: CommitMetadata
): Promise<string> => {
const apiUrl = process.env.COMMIT_API_URL!;
const apiKey = process.env.COMMIT_API_KEY!;
const model = process.env.COMMIT_MODEL || "moonshot-v1-8k";
try {
console.log("📤 正在调用 Moonshot API 生成 Commit Message...");
console.log(`📁 变更文件数:${metadata.files?.length || 0}`);
if (metadata.diff) {
console.log(
`📝 Diff 大小:${(metadata.diff.length / 1024).toFixed(2)} KB`
);
}
console.log(`🤖 使用模型:${model}`);
// 构建符合 Moonshot API 格式的请求
const prompt = buildCommitPrompt(metadata);
const apiRequest: MoonshotApiRequest = {
model: model,
messages: [
{
role: "system",
content:
"你是一个专业的 Git commit message 生成助手,擅长根据代码变更生成符合 Conventional Commits 规范的 commit message。",
},
{
role: "user",
content: prompt,
},
],
temperature: 0.7,
max_tokens: 500,
};
const response = await axios.post<MoonshotApiResponse>(apiUrl, apiRequest, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
timeout: 30000, // 30 秒超时
});
const result = response.data;
// 检查是否有错误
if (result.error) {
throw new Error(
`API 返回错误:${result.error.message} (${result.error.type})`
);
}
// 提取生成的 commit message
if (
!result.choices ||
result.choices.length === 0 ||
!result.choices[0].message?.content
) {
throw new Error("API 返回的响应格式不正确,未找到生成的 commit message");
}
const commitMessage = result.choices[0].message.content.trim();
// 记录 token 使用情况
if (result.usage) {
const sessionId =
process.env.GIT_COMMIT_SESSION_ID || generateSessionId();
await recordTokenUsage({
type: "commit",
model: model,
prompt_tokens: result.usage.prompt_tokens,
completion_tokens: result.usage.completion_tokens,
total_tokens: result.usage.total_tokens,
session_id: sessionId,
});
}
return commitMessage;
} catch (error) {
const axiosError = error as AxiosError;
if (axiosError.response) {
// API 返回错误状态码(如 400/500)
const errorData = axiosError.response.data as any;
const errorMessage =
errorData?.error?.message ||
(typeof errorData === "string" ? errorData : JSON.stringify(errorData));
throw new Error(
`API 响应错误 [${axiosError.response.status}]:${errorMessage}`
);
} else if (axiosError.request) {
// 无响应(网络问题/超时)
throw new Error(
`API 请求失败:${axiosError.message || "网络错误或超时"}`
);
} else {
// 请求配置错误
throw new Error(`请求配置错误:${axiosError.message}`);
}
}
};
/**
* 检查是否在 Git Hook 模式下运行
*/
const isHookMode = (): boolean => {
return !!process.argv[2]; // prepare-commit-msg hook 会传入文件路径
};
/**
* 检查 commit message 文件是否已有内容(用户可能已经输入)
*/
const hasExistingCommitMessage = async (
commitFilePath: string
): Promise<boolean> => {
try {
const content = await fs.readFile(commitFilePath, "utf-8");
// 如果文件不为空且不是默认的注释行,则认为用户已输入
const trimmed = content.trim();
return (
trimmed.length > 0 &&
!trimmed.startsWith("#") &&
!trimmed.match(/^(Merge|Revert)/)
);
} catch {
return false;
}
};
/**
* 将生成的 Commit Message 写入 Git 提交文件(用于 prepare-commit-msg 钩子)
* 如果不使用 Git Hooks,可直接打印结果
*/
const writeCommitToGitFile = async (commitMessage: string): Promise<void> => {
// Git 会将提交文件路径作为第 1 个参数传入(prepare-commit-msg 钩子场景)
const commitFilePath = process.argv[2];
if (commitFilePath) {
try {
// 检查是否已有用户输入的 commit message
if (await hasExistingCommitMessage(commitFilePath)) {
console.log("ℹ️ 检测到已有 commit message,跳过自动生成");
return;
}
// 覆盖 Git 默认的提交文件(清空原有内容,写入新 Message)
await fs.writeFile(commitFilePath, commitMessage, "utf-8");
console.log("✅ Commit Message 已自动生成:");
console.log("\n" + commitMessage + "\n");
console.log("💡 提示:你可以在编辑器中修改这个 commit message");
} catch (error) {
throw new Error(`写入 Git 提交文件失败:${(error as Error).message}`);
}
} else {
// 非钩子场景:直接打印结果
console.log("✅ 生成的 Commit Message:");
console.log("\n" + commitMessage + "\n");
}
};
/**
* 主函数:串联整个流程
*/
const main = async () => {
const hookMode = isHookMode();
const enableAutoCommit = process.env.COMMIT_AUTO_GENERATE !== "false"; // 默认启用
// 在 hook 模式下,如果禁用了自动生成,直接返回
if (hookMode && !enableAutoCommit) {
return;
}
try {
// 1. 验证环境变量
validateEnv();
// 2. 自动分析 Git 变更
if (hookMode) {
console.log("🤖 正在自动生成 Commit Message...");
} else {
console.log("🔍 正在分析 Git 变更...");
}
const commitMetadata = await analyzeGitChanges();
if (!hookMode) {
console.log("📋 变更分析完成");
}
// 3. 调用 API 生成 Commit Message
const commitMessage = await generateCommitByApi(commitMetadata);
// 4. 写入 Git 文件或打印结果
await writeCommitToGitFile(commitMessage);
} catch (error) {
const errorMessage = (error as Error).message;
if (hookMode) {
// 在 hook 模式下,失败时不阻止提交,只打印警告
console.warn("\n⚠️ 自动生成 Commit Message 失败:");
console.warn(errorMessage);
console.warn("\n💡 你可以手动输入 commit message\n");
// 不退出,让用户继续提交
} else {
// 非 hook 模式,正常报错并退出
console.error("\n❌ 生成 Commit Message 失败:");
console.error(errorMessage + "\n");
process.exit(1);
}
}
};
// 执行主函数
main();
这里是个大概的演示,优化的空间还有很多,比如自动生成commit 和code reivew 的上下文是同一个,能否合并呢,这样可以减少token的消耗
补充:接口几个参数解释
- 并发: 同一时间内我们最多处理的来自您的请求数
- RPM: request per minute 指一分钟内您最多向我们发起的请求数
- TPM: token per minute 指一分钟内您最多和我们交互的token数
- TPD: token per day 指一天内您最多和我们交互的token数
temperature
- 拧向 “低温度”,模型是 “严谨的专家”,只说确定的事;
- 拧向 “高温度”,模型是 “发散的创意者”,会尝试更多可能性。日常使用时,可根据 “是否需要标准答案” 来快速判断:需要精准选 “低温”,需要创意选 “高温”,介于两者之间就取 “中间值”(如 0.5)