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 +15 -14
- package/autocommit.js +55 -104
- package/config.js +3 -3
- package/count_tokens.js +17 -27
- package/package.json +3 -4
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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=
|
|
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
|
|
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 {
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
execSync(
|
|
20
|
+
'git rev-parse --is-inside-work-tree',
|
|
21
|
+
{encoding: 'utf8', stdio: 'ignore'}
|
|
22
|
+
);
|
|
29
23
|
} catch (e) {
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
);
|
|
73
|
+
return response.choices[0].message.content.trim();
|
|
74
|
+
}
|
|
82
75
|
|
|
83
|
-
const
|
|
84
|
-
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
79
|
+
const tokenCount = await calculateMaxTokens({
|
|
80
|
+
prompt: diff,
|
|
81
|
+
modelName: config.modelName || 'gpt-4.1-mini'
|
|
91
82
|
});
|
|
92
83
|
|
|
93
|
-
const
|
|
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.
|
|
98
|
-
|
|
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
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
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
|
-
'
|
|
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-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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 "
|
|
36
|
-
return
|
|
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
|
|
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
|
-
|
|
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": "
|
|
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
|
-
"
|
|
12
|
-
"openai": "^3.3.0",
|
|
11
|
+
"openai": "^4.52.7",
|
|
13
12
|
"rc": "^1.2.8",
|
|
14
|
-
"tiktoken": "^1.0.
|
|
13
|
+
"tiktoken": "^1.0.15"
|
|
15
14
|
},
|
|
16
15
|
"preferGlobal": true,
|
|
17
16
|
"bin": {
|