@himamshus06/git-auto 1.1.0 → 1.2.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 +50 -13
- package/knowledge.md +53 -0
- package/package.json +1 -1
- package/src/ai.js +68 -49
- package/src/index.js +28 -6
package/README.md
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
- **One-Command Workflow**: Stages all changes and commits them in one go.
|
|
8
8
|
- **AI-Powered Messages**: Analyzes your `git diff` to generate a meaningful commit message.
|
|
9
9
|
- **Conventional Commits**: Follows the [Conventional Commits](https://www.conventionalcommits.org/) specification (e.g., `feat:`, `fix:`, `chore:`).
|
|
10
|
-
- **
|
|
10
|
+
- **Smart Diff Handling**: Uses a Map-Reduce approach to summarize large changes, ensuring no detail is lost regardless of diff size.
|
|
11
|
+
- **Interactive Experience**: Preview and edit AI-generated messages before they are committed to your history.
|
|
11
12
|
|
|
12
13
|
## 📦 Installation
|
|
13
14
|
|
|
@@ -23,47 +24,83 @@ npm install
|
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
### 3. Install Globally
|
|
26
|
-
To use the `git
|
|
27
|
+
To use the `git auto` command anywhere on your system:
|
|
27
28
|
```bash
|
|
28
29
|
npm install -g @himamshus06/git-auto
|
|
29
30
|
```
|
|
30
31
|
|
|
32
|
+
To make it work as a git alias (`git ac commit` instead of `git-auto commit`), run:
|
|
33
|
+
```bash
|
|
34
|
+
git config --global alias.ac "!git-auto"
|
|
35
|
+
```
|
|
36
|
+
|
|
31
37
|
## ⚙️ Configuration
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
`git-auto` is provider-agnostic and supports multiple AI backends, including local LLMs.
|
|
40
|
+
|
|
41
|
+
1. Create a `.env` file in the root directory.
|
|
42
|
+
2. Choose your preferred provider configuration:
|
|
43
|
+
|
|
44
|
+
### Option A: Groq (Default - Fast & Free)
|
|
45
|
+
Get a free API key from [Groq Cloud](https://console.groq.com/).
|
|
46
|
+
```env
|
|
47
|
+
AI_PROVIDER=groq
|
|
48
|
+
AI_API_KEY=your_groq_api_key_here
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Option B: OpenAI
|
|
52
|
+
```env
|
|
53
|
+
AI_PROVIDER=openai
|
|
54
|
+
AI_API_KEY=your_openai_api_key_here
|
|
55
|
+
AI_BASE_URL=https://api.openai.com/v1
|
|
56
|
+
AI_MODEL=gpt-4o
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Option C: Local LLMs (via Ollama)
|
|
60
|
+
Install [Ollama](https://ollama.com/) and run a model (e.g., `ollama run llama3`).
|
|
61
|
+
```env
|
|
62
|
+
AI_PROVIDER=ollama
|
|
63
|
+
AI_MODEL=llama3
|
|
64
|
+
AI_BASE_URL=http://localhost:11434/v1
|
|
65
|
+
```
|
|
34
66
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
67
|
+
### Advanced Configuration
|
|
68
|
+
| Variable | Description | Default |
|
|
69
|
+
| :--- | :--- | :--- |
|
|
70
|
+
| `AI_PROVIDER` | The AI backend to use (`groq`, `openai`, `ollama`, `local`) | `groq` |
|
|
71
|
+
| `AI_API_KEY` | API key for cloud providers | (Required for cloud) |
|
|
72
|
+
| `AI_BASE_URL` | API endpoint URL | `https://api.groq.com/openai/v1` |
|
|
73
|
+
| `AI_MODEL` | The specific model ID to use | `qwen/qwen3.8-27b` |
|
|
40
74
|
|
|
41
75
|
## 🚀 Usage
|
|
42
76
|
|
|
43
77
|
### Basic Commit
|
|
44
78
|
Stages all changes and commits with an AI-generated message:
|
|
45
79
|
```bash
|
|
46
|
-
git
|
|
80
|
+
git auto commit
|
|
47
81
|
```
|
|
48
82
|
|
|
49
83
|
### Commit with Custom Message
|
|
50
84
|
Override the AI and provide your own message:
|
|
51
85
|
```bash
|
|
52
|
-
git
|
|
86
|
+
git auto commit -m "feat: add amazing new feature"
|
|
53
87
|
```
|
|
54
88
|
|
|
55
89
|
### Preview Message (Dry Run)
|
|
56
90
|
See what the AI would generate without actually committing:
|
|
57
91
|
```bash
|
|
58
|
-
git
|
|
92
|
+
git auto commit --dry-run
|
|
59
93
|
```
|
|
60
94
|
|
|
61
95
|
## 🛠️ How it Works
|
|
62
96
|
|
|
63
97
|
1. **Staging**: Runs `git add .` to stage all changes.
|
|
64
98
|
2. **Diffing**: Extracts the staged changes using `git diff --cached`.
|
|
65
|
-
3. **AI Generation**:
|
|
66
|
-
|
|
99
|
+
3. **AI Generation**:
|
|
100
|
+
- **Map Phase**: For large diffs, it splits the changes into manageable chunks and generates a concise summary for each.
|
|
101
|
+
- **Reduce Phase**: It then aggregates these summaries into a single, professional commit message following Conventional Commits.
|
|
102
|
+
4. **Interactive Review**: Presents the generated message for your approval or modification.
|
|
103
|
+
5. **Committing**: Executes `git commit -m "final_message"`.
|
|
67
104
|
|
|
68
105
|
## 📜 License
|
|
69
106
|
MIT
|
package/knowledge.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Knowledge Base: git-auto AI Implementation
|
|
2
|
+
|
|
3
|
+
This document serves as a technical reference for the AI architecture of `git-auto`, ensuring future maintainers can extend the provider system without breaking existing functionality.
|
|
4
|
+
|
|
5
|
+
## Architecture: The Strategy Pattern
|
|
6
|
+
|
|
7
|
+
The AI system is implemented using the **Strategy Pattern**. This decouples the high-level commit message generation logic from the low-level API communication details.
|
|
8
|
+
|
|
9
|
+
### Component Breakdown
|
|
10
|
+
|
|
11
|
+
#### 1. `AIProvider` (Abstract Base Class)
|
|
12
|
+
- **Purpose**: Defines the contract for all AI providers.
|
|
13
|
+
- **Key Method**: `generate(prompt, options)` - Must be implemented by all subclasses.
|
|
14
|
+
|
|
15
|
+
#### 2. `OpenAICompatibleProvider` (Concrete Strategy)
|
|
16
|
+
- **Purpose**: Handles any AI service that follows the OpenAI chat completion API specification.
|
|
17
|
+
- **Scope**: Covers Groq, OpenAI, LM Studio, and Ollama's `/v1` endpoint.
|
|
18
|
+
- **Implementation**: Uses the `openai` NPM package.
|
|
19
|
+
|
|
20
|
+
#### 3. `getAIProvider()` (Factory)
|
|
21
|
+
- **Purpose**: Instantiates the correct provider based on environment variables.
|
|
22
|
+
- **Logic**:
|
|
23
|
+
- Checks `AI_PROVIDER` to determine the strategy.
|
|
24
|
+
- Configures `baseURL` and `model` with sensible defaults based on the provider.
|
|
25
|
+
- Performs validation (e.g., ensures `AI_API_KEY` exists for cloud providers).
|
|
26
|
+
|
|
27
|
+
#### 4. `generateCommitMessage` (Client Logic)
|
|
28
|
+
- **Purpose**: Orchestrates the actual commit message generation.
|
|
29
|
+
- **Workflow**:
|
|
30
|
+
- Retrieves a provider instance via `getAIProvider()`.
|
|
31
|
+
- Implements a **Map-Reduce** approach for large diffs:
|
|
32
|
+
- **Map**: Splits the diff into chunks ($\approx 8000$ characters) and generates 1-sentence summaries for each.
|
|
33
|
+
- **Reduce**: Combines these summaries into a final, professional commit message.
|
|
34
|
+
|
|
35
|
+
## Configuration Mapping
|
|
36
|
+
|
|
37
|
+
| Variable | Purpose | Default (Groq) | Default (Ollama) |
|
|
38
|
+
| :--- | :--- | :--- | :--- |
|
|
39
|
+
| `AI_PROVIDER` | Determines the strategy class | `groq` | `ollama` |
|
|
40
|
+
| `AI_BASE_URL` | API endpoint | `https://api.groq.com/openai/v1` | `http://localhost:11434/v1` |
|
|
41
|
+
| `AI_MODEL` | Model Identifier | `qwen/qwen3.8-27b` | `llama3` |
|
|
42
|
+
|
|
43
|
+
## Future Extension Guide
|
|
44
|
+
|
|
45
|
+
### How to add a non-OpenAI compatible provider
|
|
46
|
+
If a new provider (e.g., Anthropic Claude) is added that does not use the OpenAI spec:
|
|
47
|
+
1. Create a new class `ClaudeProvider` extending `AIProvider`.
|
|
48
|
+
2. Implement the `generate()` method using the provider's specific SDK.
|
|
49
|
+
3. Add the new provider type to the `getAIProvider()` factory switch statement.
|
|
50
|
+
4. Update the `.env` documentation in `README.md`.
|
|
51
|
+
|
|
52
|
+
### How to adjust token limits
|
|
53
|
+
To handle larger diffs or more complex models, modify the `CHUNK_SIZE` constant in `generateCommitMessage`.
|
package/package.json
CHANGED
package/src/ai.js
CHANGED
|
@@ -1,55 +1,89 @@
|
|
|
1
1
|
const OpenAI = require('openai');
|
|
2
2
|
require('dotenv').config();
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Base class for AI Providers.
|
|
6
|
+
* Defines the interface that all providers must implement.
|
|
7
|
+
*/
|
|
8
|
+
class AIProvider {
|
|
9
|
+
async generate(prompt, options = {}) {
|
|
10
|
+
throw new Error('Method generate() must be implemented');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 1. Splits the diff into manageable chunks.
|
|
13
|
-
* 2. Summarizes each chunk.
|
|
14
|
-
* 3. Combines summaries into one final commit message.
|
|
15
|
-
*
|
|
16
|
-
* @param {string} diff - The git diff of staged changes.
|
|
17
|
-
* @returns {Promise<string>} - The generated commit message.
|
|
18
|
-
* @throws {Error} - If API key is missing or API call fails.
|
|
15
|
+
* Provider for any service that follows the OpenAI API specification.
|
|
16
|
+
* This covers Groq, OpenAI, LM Studio, and Ollama (/v1).
|
|
19
17
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
class OpenAICompatibleProvider extends AIProvider {
|
|
19
|
+
constructor(config) {
|
|
20
|
+
super();
|
|
21
|
+
this.client = new OpenAI({
|
|
22
|
+
apiKey: config.apiKey || 'ollama',
|
|
23
|
+
baseURL: config.baseURL
|
|
24
|
+
});
|
|
25
|
+
this.model = config.model;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async generate(prompt, options = {}) {
|
|
29
|
+
const response = await this.client.chat.completions.create({
|
|
30
|
+
model: this.model,
|
|
31
|
+
messages: [{ role: 'user', content: prompt }],
|
|
32
|
+
temperature: options.temperature ?? 0.2,
|
|
33
|
+
max_tokens: options.maxTokens ?? 100,
|
|
34
|
+
});
|
|
35
|
+
return response.choices[0].message.content.trim();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Factory to instantiate the appropriate AI provider based on configuration.
|
|
41
|
+
*/
|
|
42
|
+
function getAIProvider() {
|
|
43
|
+
const providerType = process.env.AI_PROVIDER || 'groq';
|
|
44
|
+
const apiKey = process.env.AI_API_KEY || process.env.GROQ_API_KEY;
|
|
45
|
+
const baseURL = process.env.AI_BASE_URL || (providerType === 'groq' ? 'https://api.groq.com/openai/v1' : 'http://localhost:11434/v1');
|
|
46
|
+
const model = process.env.AI_MODEL || (providerType === 'groq' ? 'qwen/qwen3.8-27b' : 'llama3');
|
|
47
|
+
|
|
48
|
+
// Validation for cloud providers
|
|
49
|
+
if (!apiKey && providerType !== 'ollama' && providerType !== 'local') {
|
|
50
|
+
throw new Error(`API key is missing for provider "${providerType}". Please set AI_API_KEY in your .env file.`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
switch (providerType.toLowerCase()) {
|
|
54
|
+
case 'openai':
|
|
55
|
+
case 'groq':
|
|
56
|
+
case 'ollama':
|
|
57
|
+
case 'local':
|
|
58
|
+
return new OpenAICompatibleProvider({ apiKey, baseURL, model });
|
|
59
|
+
default:
|
|
60
|
+
throw new Error(`Unsupported AI provider: ${providerType}`);
|
|
23
61
|
}
|
|
62
|
+
}
|
|
24
63
|
|
|
25
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Generates a professional commit message based on the provided git diff.
|
|
66
|
+
*/
|
|
67
|
+
async function generateCommitMessage(diff) {
|
|
68
|
+
const provider = getAIProvider();
|
|
69
|
+
const CHUNK_SIZE = 8000;
|
|
26
70
|
const isLargeDiff = diff.length > CHUNK_SIZE;
|
|
27
71
|
|
|
28
72
|
if (!isLargeDiff) {
|
|
29
|
-
return await getSingleCommitMessage(diff);
|
|
73
|
+
return await getSingleCommitMessage(provider, diff);
|
|
30
74
|
}
|
|
31
75
|
|
|
32
76
|
console.log(`Large diff detected (${diff.length} chars). Using chunked summary approach...`);
|
|
33
77
|
|
|
34
|
-
// 1. Map: Split diff into chunks and generate summaries for each
|
|
35
78
|
const chunks = [];
|
|
36
79
|
for (let i = 0; i < diff.length; i += CHUNK_SIZE) {
|
|
37
80
|
chunks.push(diff.substring(i, i + CHUNK_SIZE));
|
|
38
81
|
}
|
|
39
82
|
|
|
40
83
|
const summaryPromises = chunks.map(async (chunk, index) => {
|
|
41
|
-
const prompt = `Analyze this portion (${index + 1}/${chunks.length}) of a git diff and provide a 1-sentence summary of the changes
|
|
42
|
-
Diff snippet:
|
|
43
|
-
${chunk}`;
|
|
44
|
-
|
|
84
|
+
const prompt = `Analyze this portion (${index + 1}/${chunks.length}) of a git diff and provide a 1-sentence summary of the changes.\nDiff snippet:\n${chunk}`;
|
|
45
85
|
try {
|
|
46
|
-
|
|
47
|
-
model: 'qwen/qwen3.8-27b',
|
|
48
|
-
messages: [{ role: 'user', content: prompt }],
|
|
49
|
-
temperature: 0.2,
|
|
50
|
-
max_tokens: 100,
|
|
51
|
-
});
|
|
52
|
-
return response.choices[0].message.content.trim();
|
|
86
|
+
return await provider.generate(prompt);
|
|
53
87
|
} catch (e) {
|
|
54
88
|
return `Error summarizing chunk ${index + 1}: ${e.message}`;
|
|
55
89
|
}
|
|
@@ -58,7 +92,6 @@ ${chunk}`;
|
|
|
58
92
|
const summaries = await Promise.all(summaryPromises);
|
|
59
93
|
const combinedSummaries = summaries.join('\n');
|
|
60
94
|
|
|
61
|
-
// 2. Reduce: Combine all summaries into one final professional commit message
|
|
62
95
|
const finalPrompt = `
|
|
63
96
|
Based on the following summaries of changes across multiple files, write one professional, concise commit message.
|
|
64
97
|
Follow the Conventional Commits specification (e.g., feat: ..., fix: ..., chore: ..., docs: ..., style: ..., refactor: ..., perf: ..., test: ...).
|
|
@@ -69,23 +102,16 @@ ${combinedSummaries}
|
|
|
69
102
|
`.trim();
|
|
70
103
|
|
|
71
104
|
try {
|
|
72
|
-
|
|
73
|
-
model: 'qwen/qwen3.8-27b',
|
|
74
|
-
messages: [{ role: 'user', content: finalPrompt }],
|
|
75
|
-
temperature: 0.2,
|
|
76
|
-
max_tokens: 100,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
return response.choices[0].message.content.trim();
|
|
105
|
+
return await provider.generate(finalPrompt);
|
|
80
106
|
} catch (error) {
|
|
81
107
|
throw new Error(`Final reduction failed: ${error.message}`);
|
|
82
108
|
}
|
|
83
109
|
}
|
|
84
110
|
|
|
85
111
|
/**
|
|
86
|
-
* Helper to generate a commit message from a single diff
|
|
112
|
+
* Helper to generate a commit message from a single diff.
|
|
87
113
|
*/
|
|
88
|
-
async function getSingleCommitMessage(diff) {
|
|
114
|
+
async function getSingleCommitMessage(provider, diff) {
|
|
89
115
|
const prompt = `
|
|
90
116
|
Analyze the following git diff and write a professional, concise commit message.
|
|
91
117
|
Follow the Conventional Commits specification (e.g., feat: ..., fix: ..., chore: ..., docs: ..., style: ..., refactor: ..., perf: ..., test: ...).
|
|
@@ -96,14 +122,7 @@ ${diff}
|
|
|
96
122
|
`.trim();
|
|
97
123
|
|
|
98
124
|
try {
|
|
99
|
-
|
|
100
|
-
model: 'qwen/qwen3.8-27b',
|
|
101
|
-
messages: [{ role: 'user', content: prompt }],
|
|
102
|
-
temperature: 0.2,
|
|
103
|
-
max_tokens: 100,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
return response.choices[0].message.content.trim();
|
|
125
|
+
return await provider.generate(prompt);
|
|
107
126
|
} catch (error) {
|
|
108
127
|
throw new Error(`AI generation failed: ${error.message}`);
|
|
109
128
|
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const { Command } = require('commander');
|
|
3
|
+
const readline = require('node:readline/promises');
|
|
4
|
+
const { stdin: input, stdout: output } = require('node:process');
|
|
3
5
|
const git = require('./git');
|
|
4
6
|
const ai = require('./ai');
|
|
5
7
|
require('dotenv').config();
|
|
@@ -17,6 +19,7 @@ program
|
|
|
17
19
|
.option('-m, --message <message>', 'override the AI-generated commit message')
|
|
18
20
|
.option('--dry-run', 'preview the generated message without committing')
|
|
19
21
|
.action(async (options) => {
|
|
22
|
+
const rl = readline.createInterface({ input, output });
|
|
20
23
|
try {
|
|
21
24
|
if (!(await git.isGitRepo())) {
|
|
22
25
|
console.error('Error: Current directory is not a git repository.');
|
|
@@ -29,6 +32,7 @@ program
|
|
|
29
32
|
const diff = await git.getStagedDiff();
|
|
30
33
|
if (!diff) {
|
|
31
34
|
console.log('No changes to commit.');
|
|
35
|
+
rl.close();
|
|
32
36
|
return;
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -39,21 +43,39 @@ program
|
|
|
39
43
|
} else {
|
|
40
44
|
console.log('Generating AI commit message...');
|
|
41
45
|
commitMessage = await ai.generateCommitMessage(diff);
|
|
42
|
-
console.log(
|
|
46
|
+
console.log(`\nGenerated message: ${commitMessage}\n`);
|
|
47
|
+
|
|
48
|
+
if (!options.dryRun) {
|
|
49
|
+
const choice = await rl.question('(A)ccept, (E)dit, or (D)ecline? [a/e/d]: ');
|
|
50
|
+
const action = choice.toLowerCase().trim();
|
|
51
|
+
|
|
52
|
+
if (action === 'd') {
|
|
53
|
+
console.log('Commit cancelled by user.');
|
|
54
|
+
rl.close();
|
|
55
|
+
return;
|
|
56
|
+
} else if (action === 'e') {
|
|
57
|
+
const newMessage = await rl.question('Enter new commit message: ');
|
|
58
|
+
commitMessage = newMessage.trim() || commitMessage;
|
|
59
|
+
console.log(`Updated message: ${commitMessage}`);
|
|
60
|
+
} else {
|
|
61
|
+
// Default to accept
|
|
62
|
+
}
|
|
63
|
+
}
|
|
43
64
|
}
|
|
44
65
|
|
|
45
66
|
if (options.dryRun) {
|
|
46
67
|
console.log('Dry run enabled. Skipping commit.');
|
|
47
|
-
|
|
68
|
+
} else {
|
|
69
|
+
console.log('Committing changes...');
|
|
70
|
+
await git.commit(commitMessage);
|
|
71
|
+
console.log('Successfully committed changes!');
|
|
48
72
|
}
|
|
49
73
|
|
|
50
|
-
console.log('Committing changes...');
|
|
51
|
-
await git.commit(commitMessage);
|
|
52
|
-
console.log('Successfully committed changes!');
|
|
53
|
-
|
|
54
74
|
} catch (error) {
|
|
55
75
|
console.error(`Error: ${error.message}`);
|
|
56
76
|
process.exit(1);
|
|
77
|
+
} finally {
|
|
78
|
+
rl.close();
|
|
57
79
|
}
|
|
58
80
|
});
|
|
59
81
|
|