@himamshus06/git-auto 1.1.0 → 1.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/README.md CHANGED
@@ -23,39 +23,72 @@ npm install
23
23
  ```
24
24
 
25
25
  ### 3. Install Globally
26
- To use the `git-auto` command anywhere on your system:
26
+ To use the `git auto` command anywhere on your system:
27
27
  ```bash
28
28
  npm install -g @himamshus06/git-auto
29
29
  ```
30
30
 
31
+ To make it work as a git alias (`git ac commit` instead of `git-auto commit`), run:
32
+ ```bash
33
+ git config --global alias.ac "!git-auto"
34
+ ```
35
+
31
36
  ## ⚙️ Configuration
32
37
 
33
- The tool uses the Groq API for fast, free AI generation.
38
+ `git-auto` is provider-agnostic and supports multiple AI backends, including local LLMs.
39
+
40
+ 1. Create a `.env` file in the root directory.
41
+ 2. Choose your preferred provider configuration:
42
+
43
+ ### Option A: Groq (Default - Fast & Free)
44
+ Get a free API key from [Groq Cloud](https://console.groq.com/).
45
+ ```env
46
+ AI_PROVIDER=groq
47
+ AI_API_KEY=your_groq_api_key_here
48
+ ```
49
+
50
+ ### Option B: OpenAI
51
+ ```env
52
+ AI_PROVIDER=openai
53
+ AI_API_KEY=your_openai_api_key_here
54
+ AI_BASE_URL=https://api.openai.com/v1
55
+ AI_MODEL=gpt-4o
56
+ ```
57
+
58
+ ### Option C: Local LLMs (via Ollama)
59
+ Install [Ollama](https://ollama.com/) and run a model (e.g., `ollama run llama3`).
60
+ ```env
61
+ AI_PROVIDER=ollama
62
+ AI_MODEL=llama3
63
+ AI_BASE_URL=http://localhost:11434/v1
64
+ ```
34
65
 
35
- 1. Get a free API key from [Groq Cloud](https://console.groq.com/).
36
- 2. Create a `.env` file in the root directory:
37
- ```env
38
- GROQ_API_KEY=your_api_key_here
39
- ```
66
+ ### Advanced Configuration
67
+ | Variable | Description | Default |
68
+ | :--- | :--- | :--- |
69
+ | `AI_PROVIDER` | The AI backend to use (`groq`, `openai`, `ollama`, `local`) | `groq` |
70
+ | `AI_API_KEY` | API key for cloud providers | (Required for cloud) |
71
+ | `AI_BASE_URL` | API endpoint URL | `https://api.groq.com/openai/v1` |
72
+ | `AI_MODEL` | The specific model ID to use | `qwen/qwen3.8-27b` |
40
73
 
41
74
  ## 🚀 Usage
42
75
 
43
76
  ### Basic Commit
44
77
  Stages all changes and commits with an AI-generated message:
45
78
  ```bash
46
- git-auto commit
79
+ git auto commit
47
80
  ```
48
81
 
49
82
  ### Commit with Custom Message
50
83
  Override the AI and provide your own message:
51
84
  ```bash
52
- git-auto commit -m "feat: add amazing new feature"
85
+ git auto commit -m "feat: add amazing new feature"
53
86
  ```
54
87
 
55
88
  ### Preview Message (Dry Run)
56
89
  See what the AI would generate without actually committing:
57
90
  ```bash
58
- git-auto commit --dry-run
91
+ git auto commit --dry-run
59
92
  ```
60
93
 
61
94
  ## 🛠️ How it Works
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@himamshus06/git-auto",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/ai.js CHANGED
@@ -1,55 +1,89 @@
1
1
  const OpenAI = require('openai');
2
2
  require('dotenv').config();
3
3
 
4
- const openai = new OpenAI({
5
- apiKey: process.env.GROQ_API_KEY,
6
- baseURL: "https://api.groq.com/openai/v1"
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
- * Generates a professional commit message based on the provided git diff.
11
- * For large diffs, it uses a map-reduce approach:
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
- async function generateCommitMessage(diff) {
21
- if (!process.env.GROQ_API_KEY) {
22
- throw new Error('GROQ_API_KEY is missing from .env file.');
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
- const CHUNK_SIZE = 8000; // Characters per chunk to stay under token limits
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
- const response = await openai.chat.completions.create({
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
- const response = await openai.chat.completions.create({
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 (used for small changes).
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
- const response = await openai.chat.completions.create({
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(`Generated message: ${commitMessage}`);
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
- return;
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