git-aicommit 5.2.1 → 7.0.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 +3 -5
- package/autocommit.js +48 -98
- package/config.js +7 -8
- package/count_tokens.js +68 -0
- package/package.json +4 -5
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
|
|
@@ -55,7 +55,7 @@ export default {
|
|
|
55
55
|
],
|
|
56
56
|
diffFilter: 'ACMRTUXB',
|
|
57
57
|
completionPromptParams: {
|
|
58
|
-
model: "gpt-
|
|
58
|
+
model: "gpt-4o-mini",
|
|
59
59
|
temperature: 0.0,
|
|
60
60
|
maxTokens: 1000,
|
|
61
61
|
}
|
|
@@ -83,6 +83,4 @@ alias gai='git-aicommit'
|
|
|
83
83
|
gai
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
It that simple!
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
It's that simple!
|
package/autocommit.js
CHANGED
|
@@ -1,19 +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 {
|
|
14
|
-
import {ChatOpenAI} from "langchain/chat_models/openai";
|
|
15
|
-
import {OpenAI} from "langchain/llms/openai";
|
|
16
|
-
import fs from "fs";
|
|
6
|
+
import { OpenAI } from "openai";
|
|
7
|
+
import {calculateMaxTokens, getModelContextSize} from "./count_tokens.js";
|
|
17
8
|
|
|
18
9
|
const config = rc(
|
|
19
10
|
'git-aicommit',
|
|
@@ -25,26 +16,26 @@ const config = rc(
|
|
|
25
16
|
);
|
|
26
17
|
|
|
27
18
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
execSync(
|
|
20
|
+
'git rev-parse --is-inside-work-tree',
|
|
21
|
+
{encoding: 'utf8', stdio: 'ignore'}
|
|
22
|
+
);
|
|
32
23
|
} catch (e) {
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
console.error("This is not a git repository");
|
|
25
|
+
process.exit(1);
|
|
35
26
|
}
|
|
36
27
|
|
|
37
28
|
if (!config.openAiKey && !config.azureOpenAiKey) {
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
console.error("Please set OPENAI_API_KEY or AZURE_OPENAI_API_KEY");
|
|
30
|
+
process.exit(1);
|
|
40
31
|
}
|
|
41
32
|
|
|
42
33
|
// if any settings related to AZURE are set, if there are items that are not set, will error.
|
|
43
34
|
if (config.azureOpenAiKey && !(
|
|
44
35
|
config.azureOpenAiInstanceName && config.azureOpenAiDeploymentName && config.azureOpenAiVersion
|
|
45
36
|
)){
|
|
46
|
-
|
|
47
|
-
|
|
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);
|
|
48
39
|
}
|
|
49
40
|
|
|
50
41
|
const excludeFromDiff = config.excludeFromDiff || [];
|
|
@@ -53,8 +44,8 @@ const diffCommand = `git diff --staged \
|
|
|
53
44
|
--no-ext-diff \
|
|
54
45
|
--diff-filter=${diffFilter} \
|
|
55
46
|
-- ${excludeFromDiff.map(
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
(pattern) => `':(exclude)${pattern}'`
|
|
48
|
+
).join(' ')}
|
|
58
49
|
`;
|
|
59
50
|
|
|
60
51
|
let diff = execSync(diffCommand, {encoding: 'utf8'});
|
|
@@ -64,88 +55,47 @@ if (!diff) {
|
|
|
64
55
|
process.exit(1);
|
|
65
56
|
}
|
|
66
57
|
|
|
67
|
-
const openai = new
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
azureOpenAIApiInstanceName: config.azureOpenAiInstanceName,
|
|
72
|
-
azureOpenAIApiDeploymentName: config.azureOpenAiDeploymentName,
|
|
73
|
-
azureOpenAIApiVersion: config.azureOpenAiVersion,
|
|
74
|
-
temperature: config.temperature,
|
|
75
|
-
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
|
|
76
62
|
});
|
|
77
63
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
);
|
|
64
|
+
async function getChatCompletion(messages) {
|
|
65
|
+
const response = await openai.chat.completions.create({
|
|
66
|
+
model: config.modelName || 'gpt-4o-mini',
|
|
67
|
+
messages: messages,
|
|
68
|
+
temperature: config.temperature,
|
|
69
|
+
max_tokens: config.maxTokens,
|
|
70
|
+
});
|
|
85
71
|
|
|
86
|
-
|
|
87
|
-
systemMessagePromptTemplate,
|
|
88
|
-
humanPromptTemplate,
|
|
89
|
-
]);
|
|
90
|
-
|
|
91
|
-
if (diff.length > 2000) {
|
|
92
|
-
console.log('Diff is too long. Splitting into multiple requests.')
|
|
93
|
-
const filenameRegex = /^a\/(.+?)\s+b\/(.+?)/;
|
|
94
|
-
const diffByFiles = diff
|
|
95
|
-
.split('diff ' + '--git ') // Wierd string concat in order to avoid splitting on this line when using autocommit in this repo :)
|
|
96
|
-
.filter((fileDiff) => fileDiff.length > 0)
|
|
97
|
-
.map((fileDiff) => {
|
|
98
|
-
const match = fileDiff.match(filenameRegex);
|
|
99
|
-
const filename = match ? match[1] : 'Unknown file';
|
|
100
|
-
|
|
101
|
-
const content = fileDiff
|
|
102
|
-
.replaceAll(filename, '')
|
|
103
|
-
.replaceAll('a/ b/\n', '')
|
|
104
|
-
|
|
105
|
-
return chatPrompt
|
|
106
|
-
.formatMessages({
|
|
107
|
-
diff: content,
|
|
108
|
-
language: config.language,
|
|
109
|
-
})
|
|
110
|
-
.then((prompt) => {
|
|
111
|
-
return openai.call(prompt)
|
|
112
|
-
.then((res) => {
|
|
113
|
-
return {
|
|
114
|
-
filename: filename,
|
|
115
|
-
changes: res.text.trim(),
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
.catch((e) => {
|
|
119
|
-
console.error(`Error during OpenAI request: ${e.message}`);
|
|
120
|
-
process.exit(1);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// wait for all promises to resolve
|
|
126
|
-
const mergeChanges = await Promise.all(diffByFiles);
|
|
127
|
-
|
|
128
|
-
diff = mergeChanges
|
|
129
|
-
.map((fileDiff) => {
|
|
130
|
-
return `diff --git ${fileDiff.filename}\n${fileDiff.changes}`
|
|
131
|
-
|
|
132
|
-
})
|
|
133
|
-
.join('\n\n')
|
|
72
|
+
return response.choices[0].message.content.trim();
|
|
134
73
|
}
|
|
135
74
|
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
language: config.language,
|
|
139
|
-
})
|
|
75
|
+
const systemMessage = { role: "system", content: config.systemMessagePromptTemplate };
|
|
76
|
+
const userMessage = { role: "user", content: config.humanPromptTemplate.replace("{diff}", diff).replace("{language}", config.language) };
|
|
140
77
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
78
|
+
const chatMessages = [systemMessage, userMessage];
|
|
79
|
+
|
|
80
|
+
const tokenCount = await calculateMaxTokens({
|
|
81
|
+
prompt: diff,
|
|
82
|
+
modelName: config.modelName || 'gpt-4o-mini'
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const contextSize = getModelContextSize(config.modelName || 'gpt-4o-mini');
|
|
86
|
+
|
|
87
|
+
if (tokenCount > contextSize) {
|
|
88
|
+
console.log('Diff is too long. Please lower the amount of changes in the commit or switch to a model with bigger context size');
|
|
89
|
+
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
146
92
|
|
|
147
|
-
const
|
|
93
|
+
const messages = [
|
|
94
|
+
{ role: "system", content: config.systemMessagePromptTemplate },
|
|
95
|
+
{ role: "user", content: config.humanPromptTemplate.replace("{diff}", diff).replace("{language}", config.language) }
|
|
96
|
+
];
|
|
148
97
|
|
|
98
|
+
const commitMessage = await getChatCompletion(messages);
|
|
149
99
|
|
|
150
100
|
if (!config.autocommit) {
|
|
151
101
|
console.log(`Autocommit is disabled. Here is the message:\n ${commitMessage}`);
|
package/config.js
CHANGED
|
@@ -8,22 +8,21 @@ 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.',
|
|
15
15
|
humanPromptTemplate: '' +
|
|
16
16
|
'Read the following git diff for a multiple files and ' +
|
|
17
17
|
'write 1-2 sentences commit message in {language}' +
|
|
18
|
-
'without mentioning lines or files
|
|
18
|
+
'without mentioning lines or files.' +
|
|
19
|
+
'If the reason behind the changed can be deducted from the changed, provide this reason:\n' +
|
|
19
20
|
'{diff}',
|
|
20
21
|
excludeFromDiff: [
|
|
21
|
-
'*.lock', '*.lockb'
|
|
22
|
+
'*.lock', '*.lockb', '*-lock.json', '*-lock.yaml'
|
|
22
23
|
],
|
|
23
24
|
diffFilter: 'ACMRTUXB',
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
maxTokens: 1000,
|
|
28
|
-
}
|
|
25
|
+
modelName: "gpt-4o-mini",
|
|
26
|
+
temperature: 0.0,
|
|
27
|
+
maxTokens: 2000,
|
|
29
28
|
}
|
package/count_tokens.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export const getModelNameForTiktoken = (modelName) => {
|
|
2
|
+
if (modelName.startsWith("gpt-3.5-turbo-16k")) {
|
|
3
|
+
return "gpt-3.5-turbo-16k";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (modelName.startsWith("gpt-3.5-turbo-")) {
|
|
7
|
+
return "gpt-3.5-turbo";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (modelName.startsWith("gpt-4-32k-")) {
|
|
11
|
+
return "gpt-4-32k";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (modelName.startsWith("gpt-4-")) {
|
|
15
|
+
return "gpt-4";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (modelName.startsWith("gpt-4o-")) {
|
|
19
|
+
return "gpt-4o";
|
|
20
|
+
}
|
|
21
|
+
return modelName;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
export const getModelContextSize = (modelName) => {
|
|
26
|
+
switch (getModelNameForTiktoken(modelName)) {
|
|
27
|
+
case "gpt-3.5-turbo-16k":
|
|
28
|
+
return 16384;
|
|
29
|
+
case "gpt-3.5-turbo":
|
|
30
|
+
return 4096;
|
|
31
|
+
case "gpt-4-32k":
|
|
32
|
+
return 32768;
|
|
33
|
+
case "gpt-4":
|
|
34
|
+
return 8192;
|
|
35
|
+
case "gpt-4o":
|
|
36
|
+
return 128000;
|
|
37
|
+
default:
|
|
38
|
+
return 4096;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const importTiktoken = async () => {
|
|
43
|
+
try {
|
|
44
|
+
const { encoding_for_model } = await import("@dqbd/tiktoken");
|
|
45
|
+
return { encoding_for_model };
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.log(error);
|
|
48
|
+
return { encoding_for_model: null };
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const calculateMaxTokens = async ({ prompt, modelName }) => {
|
|
53
|
+
const { encoding_for_model } = await importTiktoken();
|
|
54
|
+
// fallback to approximate calculation if tiktoken is not available
|
|
55
|
+
let numTokens = Math.ceil(prompt.length / 4);
|
|
56
|
+
try {
|
|
57
|
+
if (encoding_for_model) {
|
|
58
|
+
const encoding = encoding_for_model(getModelNameForTiktoken(modelName));
|
|
59
|
+
const tokenized = encoding.encode(prompt);
|
|
60
|
+
numTokens = tokenized.length;
|
|
61
|
+
encoding.free();
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.warn("Failed to calculate number of tokens with tiktoken, falling back to approximate count", error);
|
|
65
|
+
}
|
|
66
|
+
const maxTokens = getModelContextSize(modelName);
|
|
67
|
+
return maxTokens - numTokens;
|
|
68
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-aicommit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.1",
|
|
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
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"rc": "^1.2.8"
|
|
11
|
+
"openai": "^4.52.7",
|
|
12
|
+
"rc": "^1.2.8",
|
|
13
|
+
"tiktoken": "^1.0.15"
|
|
15
14
|
},
|
|
16
15
|
"preferGlobal": true,
|
|
17
16
|
"bin": {
|