git-aicommit 6.0.0 → 7.1.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 CHANGED
@@ -9,7 +9,7 @@ Tired of writing commit messages? Let the computer do it for you!
9
9
  ## Installation
10
10
 
11
11
  ```bash
12
- npm install -g git-aicommit
12
+ bun add -g git-aicommit
13
13
  ```
14
14
 
15
15
  ## Configuration
@@ -36,30 +36,33 @@ touch $HOME/.git-aicommitrc
36
36
  // $HOME/.git-aicommitrc
37
37
  export default {
38
38
  openAiKey: process.env.OPENAI_API_KEY,
39
- addAllChangesBeforeCommit: true,
39
+ azureOpenAiKey: process.env.AZURE_OPENAI_API_KEY,
40
+ azureOpenAiInstanceName: process.env.AZURE_OPENAI_API_INSTANCE_NAME,
41
+ azureOpenAiDeploymentName: process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME,
42
+ azureOpenAiVersion: process.env.AZURE_OPENAI_API_VERSION,
40
43
  autocommit: true,
41
44
  openCommitTextEditor: false,
42
45
  language: 'english',
43
46
  systemMessagePromptTemplate: '' +
44
- 'You are expert AI, your job is to write clear and concise Git commit messages.' +
47
+ 'You are expert software developer, your job is to write clear and concise Git commit messages. ' +
45
48
  'Your responsibility is to ensure that these messages accurately describe the changes made in each commit,' +
46
49
  'follow established guidelines. Provide a clear history of changes to the codebase.' +
47
50
  'Write 1-2 sentences. Output only the commit message without comments or other text.',
48
51
  humanPromptTemplate: '' +
49
52
  'Read the following git diff for a multiple files and ' +
50
53
  'write 1-2 sentences commit message in {language}' +
51
- 'without mentioning lines or files:\n' +
54
+ 'without mentioning lines or files.' +
55
+ 'If the reason behind the changed can be deducted from the changed, provide this reason:\n' +
52
56
  '{diff}',
53
57
  excludeFromDiff: [
54
- '*.lock', '*.lockb'
58
+ '*.lock', '*.lockb', '*-lock.json', '*-lock.yaml'
55
59
  ],
56
60
  diffFilter: 'ACMRTUXB',
57
- completionPromptParams: {
58
- model: "gpt-3.5-turbo",
59
- temperature: 0.0,
60
- maxTokens: 1000,
61
- }
61
+ modelName: "gpt-4.1-mini",
62
+ temperature: 0.0,
63
+ maxTokens: 2000,
62
64
  }
65
+
63
66
  ```
64
67
 
65
68
  ### Command line arguments
@@ -77,12 +80,10 @@ git-aicommit
77
80
  Or make an alias:
78
81
 
79
82
  ```bash
80
- alias gai='git-aicommit'
83
+ alias gai="git add --all && git-aicommit && git push"
81
84
 
82
85
  ## And run it:
83
86
  gai
84
87
  ```
85
88
 
86
- It that simple!
87
-
88
-
89
+ It's that simple!
package/autocommit.js CHANGED
@@ -1,16 +1,10 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { execSync, spawn } from "child_process";
4
4
  import rc from 'rc';
5
- import {
6
- ChatPromptTemplate,
7
- HumanMessagePromptTemplate,
8
- PromptTemplate,
9
- SystemMessagePromptTemplate
10
- } from "langchain/prompts";
11
5
  import defaultConfig from './config.js';
12
- import {ChatOpenAI} from "langchain/chat_models/openai";
13
- import {getModelContextSize} from "./count_tokens.js";
6
+ import { OpenAI } from "openai";
7
+ import {calculateMaxTokens, getModelContextSize} from "./count_tokens.js";
14
8
 
15
9
  const config = rc(
16
10
  'git-aicommit',
@@ -22,26 +16,26 @@ const config = rc(
22
16
  );
23
17
 
24
18
  try {
25
- execSync(
26
- 'git rev-parse --is-inside-work-tree',
27
- {encoding: 'utf8', stdio: 'ignore'}
28
- );
19
+ execSync(
20
+ 'git rev-parse --is-inside-work-tree',
21
+ {encoding: 'utf8', stdio: 'ignore'}
22
+ );
29
23
  } catch (e) {
30
- console.error("This is not a git repository");
31
- process.exit(1);
24
+ console.error("This is not a git repository");
25
+ process.exit(1);
32
26
  }
33
27
 
34
28
  if (!config.openAiKey && !config.azureOpenAiKey) {
35
- console.error("Please set OPENAI_API_KEY or AZURE_OPENAI_API_KEY");
36
- process.exit(1);
29
+ console.error("Please set OPENAI_API_KEY or AZURE_OPENAI_API_KEY");
30
+ process.exit(1);
37
31
  }
38
32
 
39
33
  // if any settings related to AZURE are set, if there are items that are not set, will error.
40
34
  if (config.azureOpenAiKey && !(
41
35
  config.azureOpenAiInstanceName && config.azureOpenAiDeploymentName && config.azureOpenAiVersion
42
36
  )){
43
- console.error("Please set AZURE_OPENAI_API_KEY, AZURE_OPENAI_API_INSTANCE_NAME, AZURE_OPENAI_API_DEPLOYMENT_NAME, AZURE_OPENAI_API_VERSION when Azure OpenAI Service.");
44
- process.exit(1);
37
+ console.error("Please set AZURE_OPENAI_API_KEY, AZURE_OPENAI_API_INSTANCE_NAME, AZURE_OPENAI_API_DEPLOYMENT_NAME, AZURE_OPENAI_API_VERSION when Azure OpenAI Service.");
38
+ process.exit(1);
45
39
  }
46
40
 
47
41
  const excludeFromDiff = config.excludeFromDiff || [];
@@ -50,8 +44,8 @@ const diffCommand = `git diff --staged \
50
44
  --no-ext-diff \
51
45
  --diff-filter=${diffFilter} \
52
46
  -- ${excludeFromDiff.map(
53
- (pattern) => `':(exclude)${pattern}'`
54
- ).join(' ')}
47
+ (pattern) => `':(exclude)${pattern}'`
48
+ ).join(' ')}
55
49
  `;
56
50
 
57
51
  let diff = execSync(diffCommand, {encoding: 'utf8'});
@@ -61,106 +55,63 @@ if (!diff) {
61
55
  process.exit(1);
62
56
  }
63
57
 
64
- const openai = new ChatOpenAI({
65
- modelName: config.modelName,
66
- openAIApiKey: config.openAiKey,
67
- azureOpenAIApiKey: config.azureOpenAiKey,
68
- azureOpenAIApiInstanceName: config.azureOpenAiInstanceName,
69
- azureOpenAIApiDeploymentName: config.azureOpenAiDeploymentName,
70
- azureOpenAIApiVersion: config.azureOpenAiVersion,
71
- temperature: config.temperature,
72
- maxTokens: config.maxTokens,
58
+ const openai = new OpenAI({
59
+ apiKey: config.openAiKey,
60
+ baseURL: config.azureOpenAiKey ? `https://${config.azureOpenAiInstanceName}.openai.azure.com/openai/deployments/${config.azureOpenAiDeploymentName}` : undefined,
61
+ defaultHeaders: config.azureOpenAiKey ? { 'api-key': config.azureOpenAiKey } : undefined,
62
+ defaultQuery: config.azureOpenAiKey ? { 'api-version': config.azureOpenAiVersion } : undefined, // defaultQuery for api-version as per OpenAI SDK v4+ for Azure
73
63
  });
74
64
 
75
- const systemMessagePromptTemplate = SystemMessagePromptTemplate.fromTemplate(
76
- config.systemMessagePromptTemplate
77
- );
65
+ async function getChatCompletion(messages) {
66
+ const response = await openai.chat.completions.create({
67
+ model: config.modelName || 'gpt-4.1-mini',
68
+ messages: messages,
69
+ temperature: config.temperature,
70
+ max_tokens: config.maxTokens,
71
+ });
78
72
 
79
- const humanPromptTemplate = HumanMessagePromptTemplate.fromTemplate(
80
- config.humanPromptTemplate
81
- );
73
+ return response.choices[0].message.content.trim();
74
+ }
82
75
 
83
- const chatPrompt = ChatPromptTemplate.fromPromptMessages([
84
- systemMessagePromptTemplate,
85
- humanPromptTemplate,
86
- ]);
76
+ const systemMessage = { role: "system", content: config.systemMessagePromptTemplate };
77
+ const userMessage = { role: "user", content: config.humanPromptTemplate.replace("{diff}", diff).replace("{language}", config.language) };
87
78
 
88
- const chatMessages = await chatPrompt.formatMessages({
89
- diff: diff,
90
- language: config.language,
79
+ const tokenCount = await calculateMaxTokens({
80
+ prompt: diff,
81
+ modelName: config.modelName || 'gpt-4.1-mini'
91
82
  });
92
83
 
93
- const tokenCount = (await openai.getNumTokensFromMessages(chatMessages)).totalCount
94
- const contextSize = getModelContextSize(config.modelName)
84
+ const contextSize = getModelContextSize(config.modelName || 'gpt-4.1-mini');
95
85
 
96
86
  if (tokenCount > contextSize) {
97
- console.log('Diff is too long. Splitting into multiple requests.')
98
- // TODO: split smarter
99
- const filenameRegex = /^a\/(.+?)\s+b\/(.+?)/;
100
- const diffByFiles = diff
101
- .split('diff ' + '--git ') // Wierd string concat in order to avoid splitting on this line when using autocommit in this repo :)
102
- .filter((fileDiff) => fileDiff.length > 0)
103
- .map((fileDiff) => {
104
- const match = fileDiff.match(filenameRegex);
105
- const filename = match ? match[1] : 'Unknown file';
106
-
107
- const content = fileDiff
108
- .replaceAll(filename, '')
109
- .replaceAll('a/ b/\n', '')
110
-
111
- return chatPrompt
112
- .formatMessages({
113
- diff: content,
114
- language: config.language,
115
- })
116
- .then((prompt) => {
117
- return openai.call(prompt)
118
- .then((res) => {
119
- return {
120
- filename: filename,
121
- changes: res.text.trim(),
122
- }
123
- })
124
- .catch((e) => {
125
- console.error(`Error during OpenAI request: ${e.message}`);
126
- process.exit(1);
127
- });
128
- });
129
- });
130
-
131
- // wait for all promises to resolve
132
- const mergeChanges = await Promise.all(diffByFiles);
133
-
134
- diff = mergeChanges
135
- .map((fileDiff) => {
136
- return `diff --git ${fileDiff.filename}\n${fileDiff.changes}`
137
-
138
- })
139
- .join('\n\n')
87
+ console.log('Diff is too long for the current model context. Please lower the amount of changes in the commit or switch to a model with a larger context window.');
88
+ process.exit(1);
140
89
  }
141
90
 
142
- const prompt = await chatPrompt.formatMessages({
143
- diff: diff,
144
- language: config.language,
145
- })
146
-
147
- const res = await openai.call(prompt)
148
- .catch((e) => {
149
- console.error(`Error during OpenAI request: ${e.message}`);
150
- process.exit(1);
151
- });
152
-
153
- const commitMessage = res.text.trim();
91
+ const messages = [
92
+ systemMessage,
93
+ userMessage
94
+ ];
154
95
 
96
+ const commitMessage = await getChatCompletion(messages);
155
97
 
156
98
  if (!config.autocommit) {
157
99
  console.log(`Autocommit is disabled. Here is the message:\n ${commitMessage}`);
158
100
  } else {
159
101
  console.log(`Committing with following message:\n ${commitMessage}`);
160
- execSync(
161
- `git commit -m "${commitMessage.replace(/"/g, '')}"`,
162
- {encoding: 'utf8'}
163
- );
102
+ try {
103
+ execSync(
104
+ 'git commit -F -',
105
+ {
106
+ input: commitMessage,
107
+ encoding: 'utf8',
108
+ stdio: 'inherit'
109
+ }
110
+ );
111
+ } catch (error) {
112
+ console.error("Failed to commit.", error);
113
+ process.exit(1);
114
+ }
164
115
  }
165
116
 
166
117
  if (config.openCommitTextEditor) {
package/config.js CHANGED
@@ -8,7 +8,7 @@ export default {
8
8
  openCommitTextEditor: false,
9
9
  language: 'english',
10
10
  systemMessagePromptTemplate: '' +
11
- 'You are expert AI, your job is to write clear and concise Git commit messages.' +
11
+ 'You are expert software developer, your job is to write clear and concise Git commit messages. ' +
12
12
  'Your responsibility is to ensure that these messages accurately describe the changes made in each commit,' +
13
13
  'follow established guidelines. Provide a clear history of changes to the codebase.' +
14
14
  'Write 1-2 sentences. Output only the commit message without comments or other text.',
@@ -16,13 +16,13 @@ export default {
16
16
  'Read the following git diff for a multiple files and ' +
17
17
  'write 1-2 sentences commit message in {language}' +
18
18
  'without mentioning lines or files.' +
19
- 'Explain why these changes were made (summarize the reasoning):\n' +
19
+ 'If the reason behind the changed can be deducted from the changed, provide this reason:\n' +
20
20
  '{diff}',
21
21
  excludeFromDiff: [
22
22
  '*.lock', '*.lockb', '*-lock.json', '*-lock.yaml'
23
23
  ],
24
24
  diffFilter: 'ACMRTUXB',
25
- modelName: "gpt-3.5-turbo-16k",
25
+ modelName: "gpt-4.1-mini",
26
26
  temperature: 0.0,
27
27
  maxTokens: 2000,
28
28
  }
package/count_tokens.js CHANGED
@@ -1,27 +1,27 @@
1
- // langchain/dist/base_language/count_tokens.js
2
1
  export const getModelNameForTiktoken = (modelName) => {
3
2
  if (modelName.startsWith("gpt-3.5-turbo-16k")) {
4
3
  return "gpt-3.5-turbo-16k";
5
4
  }
5
+
6
6
  if (modelName.startsWith("gpt-3.5-turbo-")) {
7
7
  return "gpt-3.5-turbo";
8
8
  }
9
+
9
10
  if (modelName.startsWith("gpt-4-32k-")) {
10
11
  return "gpt-4-32k";
11
12
  }
13
+
12
14
  if (modelName.startsWith("gpt-4-")) {
13
15
  return "gpt-4";
14
16
  }
15
- return modelName;
16
- };
17
- export const getEmbeddingContextSize = (modelName) => {
18
- switch (modelName) {
19
- case "text-embedding-ada-002":
20
- return 8191;
21
- default:
22
- return 2046;
17
+
18
+ if (modelName.startsWith("gpt-4o-")) {
19
+ return "gpt-4o";
23
20
  }
21
+ return modelName;
24
22
  };
23
+
24
+
25
25
  export const getModelContextSize = (modelName) => {
26
26
  switch (getModelNameForTiktoken(modelName)) {
27
27
  case "gpt-3.5-turbo-16k":
@@ -32,33 +32,24 @@ export const getModelContextSize = (modelName) => {
32
32
  return 32768;
33
33
  case "gpt-4":
34
34
  return 8192;
35
- case "text-davinci-003":
36
- return 4097;
37
- case "text-curie-001":
38
- return 2048;
39
- case "text-babbage-001":
40
- return 2048;
41
- case "text-ada-001":
42
- return 2048;
43
- case "code-davinci-002":
44
- return 8000;
45
- case "code-cushman-001":
46
- return 2048;
35
+ case "gpt-4o":
36
+ return 128000;
47
37
  default:
48
- return 4097;
38
+ return 4096;
49
39
  }
50
40
  };
41
+
51
42
  export const importTiktoken = async () => {
52
43
  try {
53
44
  const { encoding_for_model } = await import("@dqbd/tiktoken");
54
45
  return { encoding_for_model };
55
- }
56
- catch (error) {
46
+ } catch (error) {
57
47
  console.log(error);
58
48
  return { encoding_for_model: null };
59
49
  }
60
50
  };
61
- export const calculateMaxTokens = async ({ prompt, modelName, }) => {
51
+
52
+ export const calculateMaxTokens = async ({ prompt, modelName }) => {
62
53
  const { encoding_for_model } = await importTiktoken();
63
54
  // fallback to approximate calculation if tiktoken is not available
64
55
  let numTokens = Math.ceil(prompt.length / 4);
@@ -69,8 +60,7 @@ export const calculateMaxTokens = async ({ prompt, modelName, }) => {
69
60
  numTokens = tokenized.length;
70
61
  encoding.free();
71
62
  }
72
- }
73
- catch (error) {
63
+ } catch (error) {
74
64
  console.warn("Failed to calculate number of tokens with tiktoken, falling back to approximate count", error);
75
65
  }
76
66
  const maxTokens = getModelContextSize(modelName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-aicommit",
3
- "version": "6.0.0",
3
+ "version": "7.1.0",
4
4
  "description": "Generates auto commit messages with OpenAI GPT3 model",
5
5
  "main": "autocommit.js",
6
6
  "repository": "https://github.com/shanginn/autocommit",
@@ -8,10 +8,9 @@
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "dependencies": {
11
- "langchain": "^0.0.75",
12
- "openai": "^3.3.0",
11
+ "openai": "^4.52.7",
13
12
  "rc": "^1.2.8",
14
- "tiktoken": "^1.0.8"
13
+ "tiktoken": "^1.0.15"
15
14
  },
16
15
  "preferGlobal": true,
17
16
  "bin": {