git-repo-comitter 0.1.1

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/.env.example ADDED
@@ -0,0 +1,3 @@
1
+ LLM_API_KEY=your-api-key-here
2
+ LLM_ENDPOINT=https://api.openai.com/v1
3
+ LLM_MODEL=gpt-4
package/.eslintrc.cjs ADDED
@@ -0,0 +1,19 @@
1
+ module.exports = {
2
+ root: true,
3
+ parser: "@typescript-eslint/parser",
4
+ plugins: ["@typescript-eslint"],
5
+ extends: [
6
+ "eslint:recommended",
7
+ "plugin:@typescript-eslint/recommended",
8
+ "prettier",
9
+ ],
10
+ env: {
11
+ node: true,
12
+ es2020: true,
13
+ },
14
+ parserOptions: {
15
+ ecmaVersion: 2020,
16
+ sourceType: "module",
17
+ },
18
+ rules: {},
19
+ };
@@ -0,0 +1,29 @@
1
+ name: Publish Package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ id-token: write
10
+ contents: read
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: pnpm/action-setup@v4
19
+
20
+ - uses: actions/setup-node@v4
21
+ with:
22
+ node-version: "20"
23
+ registry-url: "https://registry.npmjs.org"
24
+
25
+ - run: pnpm install --frozen-lockfile
26
+
27
+ - run: pnpm build
28
+
29
+ - run: npm publish --provenance --access public
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "tabWidth": 2,
5
+ "trailingComma": "all",
6
+ "printWidth": 80
7
+ }
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org>
package/config.yaml ADDED
@@ -0,0 +1,16 @@
1
+ # Git Repo Committer 配置文件
2
+
3
+ # 大模型配置
4
+ llm:
5
+ model: "gpt-4"
6
+ temperature: 0.7
7
+ max_tokens: 256
8
+
9
+ # 生成风格模板
10
+ style:
11
+ language: "zh-CN"
12
+ template: "conventional" # conventional | conventional-commit | custom
13
+
14
+ # Git 目录路径(默认为当前目录)
15
+ git:
16
+ repoPath: "."
@@ -0,0 +1,143 @@
1
+ # 项目初始化
2
+
3
+ ## 项目概述
4
+
5
+ 本项目是一个基于 TypeScript 的前端工具,用于调用大语言模型(采用 OpenAI 兼容格式),管理 Git 差异(diff),自动生成 Git 提交信息(commit message),并执行提交操作。
6
+
7
+ ## 技术栈
8
+
9
+ - 编程语言:TypeScript
10
+ - 构建工具:Vite
11
+ - 包管理器:npm
12
+ - 代码格式化:Prettier + ESLint
13
+ - Git 钩子:husky + lint-staged
14
+
15
+ ## 功能需求
16
+
17
+ ### 核心功能
18
+
19
+ 1. **Git 差异管理**
20
+ - 读取当前 Git 仓库的变更内容(diff)
21
+ - 支持暂存区和工作区的差异提取
22
+
23
+ 2. **大模型调用**
24
+ - 调用外部大语言模型 API 生成提交信息(OpenAI 兼容格式)
25
+ - 支持从环境变量读取模型配置:
26
+ - `LLM_API_KEY`:API 密钥
27
+ - `LLM_ENDPOINT`:服务地址(例如 `https://api.openai.com/v1`)
28
+ - `LLM_MODEL`:模型名称(例如 `gpt-4`、`gpt-3.5-turbo`)
29
+
30
+ 3. **配置系统**
31
+ - 支持 YAML 配置文件(如 `.git-repo-committer.yaml`)
32
+ - 可配置项包括:
33
+ - 模型选择与参数(model、temperature、max_tokens 等)
34
+ - 生成风格或模板
35
+ - Git 目录路径
36
+
37
+ 4. **Git 提交**
38
+ - 自动执行 `git commit` 操作
39
+ - 使用生成的提交信息
40
+
41
+ ### 环境变量
42
+
43
+ 项目将依赖以下环境变量:
44
+
45
+ - `LLM_API_KEY`:大模型 API 密钥
46
+ - `LLM_ENDPOINT`:大模型服务地址(OpenAI 兼容端点)
47
+ - `LLM_MODEL`:模型名称
48
+
49
+ ## 项目结构
50
+
51
+ ```
52
+ git-repo-committer/
53
+ ├── src/
54
+ │ ├── index.ts # 入口文件
55
+ │ ├── git.ts # Git 差异处理
56
+ │ ├── llm.ts # 大模型调用(OpenAI 兼容)
57
+ │ ├── config.ts # 配置读取
58
+ │ └── commit.ts # 提交管理
59
+ ├── config.yaml # YAML 配置文件
60
+ ├── .env # 环境变量文件(不提交)
61
+ ├── .env.example # 环境变量示例
62
+ ├── .eslintrc.cjs # ESLint 配置
63
+ ├── .prettierrc # Prettier 配置
64
+ ├── tsconfig.json # TypeScript 配置
65
+ ├── vite.config.ts # Vite 配置
66
+ ├── package.json # 包描述文件
67
+ ├── README.md # 项目说明
68
+ └── .gitignore # Git 忽略规则
69
+ ```
70
+
71
+ ## 初始化步骤
72
+
73
+ 1. **创建项目目录并初始化 npm**
74
+
75
+ ```bash
76
+ mkdir git-repo-committer
77
+ cd git-repo-committer
78
+ npm init -y
79
+ ```
80
+
81
+ 2. **初始化 TypeScript 配置**
82
+
83
+ ```bash
84
+ npm install --save-dev typescript
85
+ npx tsc --init
86
+ ```
87
+
88
+ 3. **安装 Vite 及相关依赖**
89
+
90
+ ```bash
91
+ npm install --save-dev vite @types/node
92
+ ```
93
+
94
+ 4. **安装代码格式化工具**
95
+
96
+ ```bash
97
+ npm install --save-dev prettier eslint eslint-config-prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin
98
+ ```
99
+
100
+ 5. **安装 Git 钩子工具**
101
+
102
+ ```bash
103
+ npm install --save-dev husky lint-staged
104
+ npx husky init
105
+ ```
106
+
107
+ 6. **安装运行时依赖**
108
+
109
+ ```bash
110
+ npm install yaml dotenv openai
111
+ ```
112
+
113
+ 7. **创建配置文件**
114
+
115
+ - 创建 `.env.example` 和 `.env`(写入环境变量)
116
+ - 创建 `config.yaml`(定义模型参数和模板)
117
+ - 创建 `.eslintrc.cjs`、`.prettierrc` 配置规则
118
+ - 创建 `vite.config.ts` 定义构建目标
119
+
120
+ 8. **实现核心模块**
121
+
122
+ - `src/config.ts`:读取 YAML 配置和 `.env` 环境变量
123
+ - `src/git.ts`:执行 `git diff` 命令并解析结果
124
+ - `src/llm.ts`:使用 OpenAI SDK 调用兼容 API
125
+ - `src/commit.ts`:组装并执行 `git commit -m "..."`
126
+
127
+ 9. **完善 package.json 元数据**
128
+
129
+
130
+ ## 发布计划
131
+
132
+ 项目完成后将发布到 npm registry,作为命令行工具(CLI)供开发者使用。发布步骤:
133
+
134
+ 1. 确保 `package.json` 中已填入正确的 GitHub 仓库地址和元信息
135
+ 2. 运行 `npm run build` 构建产物
136
+ 3. 执行 `npm publish` 发布
137
+
138
+ ## 注意事项
139
+
140
+ - 确保 `.gitignore` 包含 `node_modules`、`dist`、`.env` 等目录
141
+ - 环境变量文件 `.env` 不应提交到版本控制系统
142
+ - 使用 `openai` 包时需配置 `baseURL` 为 `LLM_ENDPOINT` 的值
143
+ - 代码提交前会通过 husky + lint-staged 自动执行 ESLint 和 Prettier 格式化
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "git-repo-comitter",
3
+ "version": "0.1.1",
4
+ "description": "A CLI tool that uses LLM to generate Git commit messages and execute commits",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "bin": {
8
+ "git-repo-comitter": "dist/index.cjs"
9
+ },
10
+ "keywords": [
11
+ "git",
12
+ "commit",
13
+ "llm",
14
+ "ai",
15
+ "openai"
16
+ ],
17
+ "author": "",
18
+ "license": "unlicense",
19
+ "lint-staged": {
20
+ "*.ts": [
21
+ "prettier --write",
22
+ "eslint --fix"
23
+ ]
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^26.1.1",
27
+ "@typescript-eslint/eslint-plugin": "^8.63.0",
28
+ "@typescript-eslint/parser": "^8.63.0",
29
+ "eslint": "^10.7.0",
30
+ "eslint-config-prettier": "^10.1.8",
31
+ "husky": "^9.1.7",
32
+ "lint-staged": "^16.4.0",
33
+ "prettier": "^3.9.5",
34
+ "typescript": "^6.0.3",
35
+ "vite": "^8.1.4"
36
+ },
37
+ "dependencies": {
38
+ "dotenv": "^17.4.2",
39
+ "openai": "^6.46.0",
40
+ "yaml": "^2.9.0"
41
+ },
42
+ "scripts": {
43
+ "dev": "vite",
44
+ "build": "tsc && vite build",
45
+ "preview": "vite preview",
46
+ "lint": "eslint src/",
47
+ "format": "prettier --write \"src/**/*.ts\""
48
+ }
49
+ }
package/src/commit.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { execSync } from "child_process";
2
+
3
+ export function gitCommit(message: string, repoPath?: string): void {
4
+ const cwd = repoPath || process.cwd();
5
+ const escapedMessage = message.replace(/"/g, '\\"');
6
+ execSync(`git commit -m "${escapedMessage}"`, { cwd, encoding: "utf-8" });
7
+ }
8
+
9
+ export function gitAddAll(repoPath?: string): void {
10
+ const cwd = repoPath || process.cwd();
11
+ execSync("git add -A", { cwd, encoding: "utf-8" });
12
+ }
package/src/config.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { resolve } from "path";
3
+ import YAML from "yaml";
4
+ import dotenv from "dotenv";
5
+
6
+ dotenv.config();
7
+
8
+ export interface LLMConfig {
9
+ model: string;
10
+ temperature: number;
11
+ max_tokens: number;
12
+ }
13
+
14
+ export interface StyleConfig {
15
+ language: string;
16
+ template: string;
17
+ }
18
+
19
+ export interface GitConfig {
20
+ repoPath: string;
21
+ }
22
+
23
+ export interface AppConfig {
24
+ llm: LLMConfig;
25
+ style: StyleConfig;
26
+ git: GitConfig;
27
+ apiKey: string;
28
+ endpoint: string;
29
+ }
30
+
31
+ const DEFAULT_CONFIG: AppConfig = {
32
+ llm: {
33
+ model: "gpt-4",
34
+ temperature: 0.7,
35
+ max_tokens: 256,
36
+ },
37
+ style: {
38
+ language: "zh-CN",
39
+ template: "conventional",
40
+ },
41
+ git: {
42
+ repoPath: ".",
43
+ },
44
+ apiKey: "",
45
+ endpoint: "https://api.openai.com/v1",
46
+ };
47
+
48
+ export function loadConfig(configPath?: string): AppConfig {
49
+ const configFile =
50
+ configPath ||
51
+ [
52
+ ".git-repo-committer.yaml",
53
+ ".git-repo-committer.yml",
54
+ "config.yaml",
55
+ ].find((f) => existsSync(resolve(process.cwd(), f)));
56
+
57
+ let fileConfig: Partial<AppConfig> = {};
58
+
59
+ if (configFile) {
60
+ const content = readFileSync(resolve(process.cwd(), configFile), "utf-8");
61
+ fileConfig = YAML.parse(content);
62
+ }
63
+
64
+ return {
65
+ ...DEFAULT_CONFIG,
66
+ ...fileConfig,
67
+ llm: { ...DEFAULT_CONFIG.llm, ...(fileConfig.llm || {}) },
68
+ style: { ...DEFAULT_CONFIG.style, ...(fileConfig.style || {}) },
69
+ git: { ...DEFAULT_CONFIG.git, ...(fileConfig.git || {}) },
70
+ apiKey: process.env.LLM_API_KEY || fileConfig.apiKey || "",
71
+ endpoint: process.env.LLM_ENDPOINT || fileConfig.endpoint || DEFAULT_CONFIG.endpoint,
72
+ };
73
+ }
package/src/git.ts ADDED
@@ -0,0 +1,47 @@
1
+ import { execSync } from "child_process";
2
+
3
+ export function getStagedDiff(repoPath?: string): string {
4
+ const cwd = repoPath || process.cwd();
5
+ try {
6
+ return execSync("git diff --cached", { cwd, encoding: "utf-8" });
7
+ } catch {
8
+ return "";
9
+ }
10
+ }
11
+
12
+ export function getUnstagedDiff(repoPath?: string): string {
13
+ const cwd = repoPath || process.cwd();
14
+ try {
15
+ return execSync("git diff", { cwd, encoding: "utf-8" });
16
+ } catch {
17
+ return "";
18
+ }
19
+ }
20
+
21
+ export function getAllDiff(repoPath?: string): string {
22
+ const cwd = repoPath || process.cwd();
23
+ const staged = getStagedDiff(cwd);
24
+ const unstaged = getUnstagedDiff(cwd);
25
+
26
+ const parts: string[] = [];
27
+ if (staged.trim()) {
28
+ parts.push("=== Staged Changes ===\n" + staged);
29
+ }
30
+ if (unstaged.trim()) {
31
+ parts.push("=== Unstaged Changes ===\n" + unstaged);
32
+ }
33
+ return parts.join("\n\n");
34
+ }
35
+
36
+ export function hasStagedChanges(repoPath?: string): boolean {
37
+ const cwd = repoPath || process.cwd();
38
+ try {
39
+ const result = execSync("git diff --cached --name-only", {
40
+ cwd,
41
+ encoding: "utf-8",
42
+ });
43
+ return result.trim().length > 0;
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
package/src/index.ts ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { loadConfig } from "./config";
4
+ import { getAllDiff, hasStagedChanges } from "./git";
5
+ import { generateCommitMessage } from "./llm";
6
+ import { gitCommit, gitAddAll } from "./commit";
7
+
8
+ async function main() {
9
+ const config = loadConfig();
10
+
11
+ if (!hasStagedChanges(config.git.repoPath)) {
12
+ console.log("No staged changes found. Staging all changes...");
13
+ gitAddAll(config.git.repoPath);
14
+ }
15
+
16
+ const diff = getAllDiff(config.git.repoPath);
17
+ if (!diff.trim()) {
18
+ console.log("No changes to commit.");
19
+ process.exit(0);
20
+ }
21
+
22
+ console.log("Generating commit message...\n");
23
+ const message = await generateCommitMessage(diff, config);
24
+ console.log(`Commit message:\n ${message}\n`);
25
+
26
+ gitCommit(message, config.git.repoPath);
27
+ console.log("Commit successful!");
28
+ }
29
+
30
+ main().catch((err) => {
31
+ console.error("Error:", err.message);
32
+ process.exit(1);
33
+ });
package/src/llm.ts ADDED
@@ -0,0 +1,45 @@
1
+ import OpenAI from "openai";
2
+ import type { AppConfig } from "./config";
3
+
4
+ const SYSTEM_PROMPT = `You are an expert at writing Git commit messages.
5
+ Based on the provided Git diff, generate a concise and descriptive commit message.
6
+ Follow the Conventional Commits format: <type>(<scope>): <description>
7
+ Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
8
+ Keep the message under 72 characters for the subject line.
9
+ If there are significant changes, add a blank line followed by a more detailed body.
10
+ Respond with ONLY the commit message, no explanation.`;
11
+
12
+ export async function generateCommitMessage(
13
+ diff: string,
14
+ config: AppConfig,
15
+ ): Promise<string> {
16
+ if (!config.apiKey) {
17
+ throw new Error(
18
+ "LLM_API_KEY is not set. Please set it in .env or environment variables.",
19
+ );
20
+ }
21
+
22
+ const client = new OpenAI({
23
+ apiKey: config.apiKey,
24
+ baseURL: config.endpoint,
25
+ });
26
+
27
+ const response = await client.chat.completions.create({
28
+ model: config.llm.model,
29
+ temperature: config.llm.temperature,
30
+ max_tokens: config.llm.max_tokens,
31
+ messages: [
32
+ { role: "system", content: SYSTEM_PROMPT },
33
+ {
34
+ role: "user",
35
+ content: `Here is the Git diff:\n\n${diff}`,
36
+ },
37
+ ],
38
+ });
39
+
40
+ const message = response.choices[0]?.message?.content?.trim();
41
+ if (!message) {
42
+ throw new Error("LLM returned an empty commit message.");
43
+ }
44
+ return message;
45
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "resolveJsonModule": true,
11
+ "declaration": true,
12
+ "outDir": "./dist",
13
+ "rootDir": "./src",
14
+ "sourceMap": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from "vite";
2
+ import { resolve } from "path";
3
+
4
+ export default defineConfig({
5
+ build: {
6
+ lib: {
7
+ entry: resolve(__dirname, "src/index.ts"),
8
+ name: "git-repo-comitter",
9
+ fileName: "index",
10
+ formats: ["cjs"],
11
+ },
12
+ outDir: "dist",
13
+ sourcemap: true,
14
+ rollupOptions: {
15
+ external: ["openai", "yaml", "dotenv"],
16
+ },
17
+ },
18
+ });