【AI 日记】如何使用大模型 API 帮你生成commit 、review代码

68 阅读6分钟

虽然,现在类似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 usessecontext 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)

image.png