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 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
@@ -55,7 +55,7 @@ export default {
55
55
  ],
56
56
  diffFilter: 'ACMRTUXB',
57
57
  completionPromptParams: {
58
- model: "gpt-3.5-turbo",
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 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 {RecursiveCharacterTextSplitter} from "langchain/text_splitter";
13
- import {loadSummarizationChain} from "langchain/chains";
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
- execSync(
29
- 'git rev-parse --is-inside-work-tree',
30
- {encoding: 'utf8', stdio: 'ignore'}
31
- );
19
+ execSync(
20
+ 'git rev-parse --is-inside-work-tree',
21
+ {encoding: 'utf8', stdio: 'ignore'}
22
+ );
32
23
  } catch (e) {
33
- console.error("This is not a git repository");
34
- process.exit(1);
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
- console.error("Please set OPENAI_API_KEY or AZURE_OPENAI_API_KEY");
39
- process.exit(1);
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
- 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.");
47
- 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);
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
- (pattern) => `':(exclude)${pattern}'`
57
- ).join(' ')}
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 ChatOpenAI({
68
- modelName: config.modelName,
69
- openAIApiKey: config.openAiKey,
70
- azureOpenAIApiKey: config.azureOpenAiKey,
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
- const systemMessagePromptTemplate = SystemMessagePromptTemplate.fromTemplate(
79
- config.systemMessagePromptTemplate
80
- );
81
-
82
- const humanPromptTemplate = HumanMessagePromptTemplate.fromTemplate(
83
- config.humanPromptTemplate
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
- const chatPrompt = ChatPromptTemplate.fromPromptMessages([
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 prompt = await chatPrompt.formatMessages({
137
- diff: diff,
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 res = await openai.call(prompt)
142
- .catch((e) => {
143
- console.error(`Error during OpenAI request: ${e.message}`);
144
- process.exit(1);
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 commitMessage = res.text.trim();
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 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.',
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:\n' +
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
- completionPromptParams: {
25
- model: "gpt-3.5-turbo",
26
- temperature: 0.0,
27
- maxTokens: 1000,
28
- }
25
+ modelName: "gpt-4o-mini",
26
+ temperature: 0.0,
27
+ maxTokens: 2000,
29
28
  }
@@ -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": "5.2.1",
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
- "@dqbd/tiktoken": "^1.0.7",
12
- "langchain": "^0.0.75",
13
- "openai": "^3.2.1",
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": {