Skip to content

Commit 9b7337f

Browse files
malpoudi-sukharev
andauthored
Support Gitmoji Format in Commit Messages (#249)
* 📝 docs(prompts.ts): update prompt message to include information about GitMoji convention and descriptions of changes 📝 docs(prompts.ts): update prompt message to include information about GitMoji convention and descriptions of changes * 🎨 (prompts.ts): import `removeConventionalCommitWord` function to remove conventional commit word from commit prompts 🐛 (prompts.ts): remove conventional commit word from `commitFix` and `commitFeat` prompts to improve clarity 📝 (removeConventionalCommitWord.ts): add `removeConventionalCommitWord` function to remove conventional commit word from commit message * 📝 (package.json): update version from 3.0.3 to 3.0.0 to align with the latest release * 🔧 (cli.ts): add a new flag 'fgm' to the 'flags' object to support the '--fgm' flag in the CLI command 🔧 (commit.ts): pass the value of the 'fgm' flag to the 'commit' function to enable or disable full GitMoji specification ♻️ (commit.ts): refactor the 'commit' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'commit' function recursively ♻️ (generateCommitMessageFromGitDiff.ts): refactor the 'generateCommitMessageByDiff' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'generateCommitMessageChatCompletionPrompt' function ♻️ (generateCommitMessageFromGitDiff.ts): refactor the 'generateCommitMessageChatCompletionPrompt' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'getMainCommitPrompt' function ♻️ (generateCommitMessageFromGitDiff.ts): refactor the 'getCommitMsgsPromisesFromFileDiffs' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'getMessagesPromisesByChangesInFile' function ♻️ (generateCommitMessageFromGitDiff.ts): refactor the 'getMessagesPromisesByChangesInFile' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'generateCommitMessageChatCompletionPrompt' function ♻️ (prompts.ts): refactor the 'getMainCommitPrompt' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'INIT_MAIN_PROMPT' function * 📝 (README.md): add documentation for the `--fgm` flag in the `oco` command to enable the use of the full GitMoji specification * 📝 (README.md): update flag description for using full GitMoji specification 📝 (README.md): add link to the GitMoji specification for reference * 🔧 (README.md): fix a typo in the description of the `Use Full GitMoji Specification` flag 🔧 (api.ts): update the default value of the `apiKey` variable to a placeholder value for testing purposes * Revert "🔧 (README.md): fix a typo in the description of the `Use Full GitMoji Specification` flag" This reverts commit 230a4aa. * 🔧 (README.md): fix a typo in the description of the `Use Full GitMoji Specification` flag * 📝 (prompts.ts): update INIT_MAIN_PROMPT content to include information about the fullGitMojiSpec flag and provide instructions on how to choose the right emoji for the commit message --------- Co-authored-by: GPT10 <57486732+di-sukharev@users.noreply.github.com>
1 parent ec699c4 commit 9b7337f

6 files changed

Lines changed: 154 additions & 33 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ git add <files...>
5858
oco
5959
```
6060

61+
Link to the GitMoji specification: https://gitmoji.dev/
62+
6163
You can also run it with local model through ollama:
6264

6365
- install and start ollama
@@ -69,6 +71,17 @@ git add <files...>
6971
AI_PROVIDER='ollama' opencommit
7072
```
7173

74+
### Flags
75+
There are multiple optional flags that can be used with the `oco` command:
76+
77+
#### Use Full GitMoji Specification
78+
This flag can only be used if the `OCO_EMOJI` configuration item is set to `true`. This flag allows users to use all emojis in the GitMoji specification, By default, the GitMoji full specification is set to `false`, which only includes 10 emojis (🐛✨📝🚀✅♻️⬆️🔧🌐💡).
79+
This is due to limit the number of tokens sent in each request. However, if you would like to use the full GitMoji specification, you can use the `--fgm` flag.
80+
81+
```
82+
oco --fgm
83+
```
84+
7285
## Configuration
7386

7487
### Local per repo configuration

src/cli.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@ cli(
1717
version: packageJSON.version,
1818
name: 'opencommit',
1919
commands: [configCommand, hookCommand, commitlintConfigCommand],
20-
flags: {},
20+
flags: {
21+
fgm: Boolean
22+
},
2123
ignoreArgv: (type) => type === 'unknown-flag' || type === 'argument',
2224
help: { description: packageJSON.description }
2325
},
24-
async () => {
26+
async ({ flags }) => {
2527
await checkIsLatestVersion();
2628

2729
if (await isHookCalled()) {
2830
prepareCommitMessageHook();
2931
} else {
30-
commit(extraArgs);
32+
commit(extraArgs, flags.fgm);
3133
}
3234
},
3335
extraArgs

src/commands/commit.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,15 @@ const checkMessageTemplate = (extraArgs: string[]): string | false => {
4040

4141
const generateCommitMessageFromGitDiff = async (
4242
diff: string,
43-
extraArgs: string[]
43+
extraArgs: string[],
44+
fullGitMojiSpec: boolean
4445
): Promise<void> => {
4546
await assertGitRepo();
4647
const commitSpinner = spinner();
4748
commitSpinner.start('Generating the commit message');
4849

4950
try {
50-
let commitMessage = await generateCommitMessageByDiff(diff);
51+
let commitMessage = await generateCommitMessageByDiff(diff, fullGitMojiSpec);
5152

5253
const messageTemplate = checkMessageTemplate(extraArgs);
5354
if (
@@ -154,7 +155,8 @@ ${chalk.grey('——————————————————')}`
154155

155156
export async function commit(
156157
extraArgs: string[] = [],
157-
isStageAllFlag: Boolean = false
158+
fullGitMojiSpec: boolean = false,
159+
isStageAllFlag: Boolean = false,
158160
) {
159161
if (isStageAllFlag) {
160162
const changedFiles = await getChangedFiles();
@@ -194,7 +196,7 @@ export async function commit(
194196
isStageAllAndCommitConfirmedByUser &&
195197
!isCancel(isStageAllAndCommitConfirmedByUser)
196198
) {
197-
await commit(extraArgs, true);
199+
await commit(extraArgs, true, fullGitMojiSpec);
198200
process.exit(1);
199201
}
200202

@@ -212,7 +214,7 @@ export async function commit(
212214
await gitAdd({ files });
213215
}
214216

215-
await commit(extraArgs, false);
217+
await commit(extraArgs, false, fullGitMojiSpec);
216218
process.exit(1);
217219
}
218220

@@ -225,7 +227,8 @@ export async function commit(
225227
const [, generateCommitError] = await trytm(
226228
generateCommitMessageFromGitDiff(
227229
await getDiff({ files: stagedFiles }),
228-
extraArgs
230+
extraArgs,
231+
fullGitMojiSpec
229232
)
230233
);
231234

src/generateCommitMessageFromGitDiff.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ const MAX_TOKENS_INPUT = config?.OCO_TOKENS_MAX_INPUT || DEFAULT_TOKEN_LIMITS.DE
1414
const MAX_TOKENS_OUTPUT = config?.OCO_TOKENS_MAX_OUTPUT || DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT;
1515

1616
const generateCommitMessageChatCompletionPrompt = async (
17-
diff: string
17+
diff: string,
18+
fullGitMojiSpec: boolean
1819
): Promise<Array<ChatCompletionRequestMessage>> => {
19-
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt();
20+
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt(fullGitMojiSpec);
2021

2122
const chatContextAsCompletionRequest = [...INIT_MESSAGES_PROMPT];
2223

@@ -38,10 +39,11 @@ export enum GenerateCommitMessageErrorEnum {
3839
const ADJUSTMENT_FACTOR = 20;
3940

4041
export const generateCommitMessageByDiff = async (
41-
diff: string
42+
diff: string,
43+
fullGitMojiSpec: boolean
4244
): Promise<string> => {
4345
try {
44-
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt();
46+
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt(fullGitMojiSpec);
4547

4648
const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map(
4749
(msg) => tokenCount(msg.content) + 4
@@ -56,7 +58,8 @@ export const generateCommitMessageByDiff = async (
5658
if (tokenCount(diff) >= MAX_REQUEST_TOKENS) {
5759
const commitMessagePromises = await getCommitMsgsPromisesFromFileDiffs(
5860
diff,
59-
MAX_REQUEST_TOKENS
61+
MAX_REQUEST_TOKENS,
62+
fullGitMojiSpec
6063
);
6164

6265
const commitMessages = [];
@@ -68,7 +71,7 @@ export const generateCommitMessageByDiff = async (
6871
return commitMessages.join('\n\n');
6972
}
7073

71-
const messages = await generateCommitMessageChatCompletionPrompt(diff);
74+
const messages = await generateCommitMessageChatCompletionPrompt(diff, fullGitMojiSpec);
7275

7376
const engine = getEngine()
7477
const commitMessage = await engine.generateCommitMessage(messages);
@@ -85,7 +88,8 @@ export const generateCommitMessageByDiff = async (
8588
function getMessagesPromisesByChangesInFile(
8689
fileDiff: string,
8790
separator: string,
88-
maxChangeLength: number
91+
maxChangeLength: number,
92+
fullGitMojiSpec: boolean
8993
) {
9094
const hunkHeaderSeparator = '@@ ';
9195
const [fileHeader, ...fileDiffByLines] = fileDiff.split(hunkHeaderSeparator);
@@ -112,7 +116,8 @@ function getMessagesPromisesByChangesInFile(
112116
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map(
113117
async (lineDiff) => {
114118
const messages = await generateCommitMessageChatCompletionPrompt(
115-
separator + lineDiff
119+
separator + lineDiff,
120+
fullGitMojiSpec
116121
);
117122

118123
return engine.generateCommitMessage(messages);
@@ -160,7 +165,8 @@ function splitDiff(diff: string, maxChangeLength: number) {
160165

161166
export const getCommitMsgsPromisesFromFileDiffs = async (
162167
diff: string,
163-
maxDiffLength: number
168+
maxDiffLength: number,
169+
fullGitMojiSpec: boolean
164170
) => {
165171
const separator = 'diff --git ';
166172

@@ -177,13 +183,15 @@ export const getCommitMsgsPromisesFromFileDiffs = async (
177183
const messagesPromises = getMessagesPromisesByChangesInFile(
178184
fileDiff,
179185
separator,
180-
maxDiffLength
186+
maxDiffLength,
187+
fullGitMojiSpec
181188
);
182189

183190
commitMessagePromises.push(...messagesPromises);
184191
} else {
185192
const messages = await generateCommitMessageChatCompletionPrompt(
186-
separator + fileDiff
193+
separator + fileDiff,
194+
fullGitMojiSpec
187195
);
188196

189197
const engine = getEngine()

src/prompts.ts

Lines changed: 105 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,105 @@ import { configureCommitlintIntegration } from './modules/commitlint/config';
1111
import { commitlintPrompts } from './modules/commitlint/prompts';
1212
import { ConsistencyPrompt } from './modules/commitlint/types';
1313
import * as utils from './modules/commitlint/utils';
14+
import { removeConventionalCommitWord } from './utils/removeConventionalCommitWord';
1415

1516
const config = getConfig();
1617
const translation = i18n[(config?.OCO_LANGUAGE as I18nLocals) || 'en'];
1718

1819
export const IDENTITY =
1920
'You are to act as the author of a commit message in git.';
2021

21-
const INIT_MAIN_PROMPT = (language: string): ChatCompletionRequestMessage => ({
22+
const INIT_MAIN_PROMPT = (
23+
language: string,
24+
fullGitMojiSpec: boolean
25+
): ChatCompletionRequestMessage => ({
2226
role: ChatCompletionRequestMessageRoleEnum.System,
23-
content: `${IDENTITY} Your mission is to create clean and comprehensive commit messages as per the conventional commit convention and explain WHAT were the changes and mainly WHY the changes were done. I'll send you an output of 'git diff --staged' command, and you are to convert it into a commit message.
24-
${
25-
config?.OCO_EMOJI
26-
? 'Use GitMoji convention to preface the commit.'
27-
: 'Do not preface the commit with anything.'
28-
}
27+
content: `${IDENTITY} Your mission is to create clean and comprehensive commit messages as per the ${
28+
fullGitMojiSpec ? 'GitMoji specification' : 'conventional commit convention'
29+
} and explain WHAT were the changes and mainly WHY the changes were done. I'll send you an output of 'git diff --staged' command, and you are to convert it into a commit message.
30+
${
31+
config?.OCO_EMOJI
32+
? 'Use GitMoji convention to preface the commit. Here are some help to choose the right emoji (emoji, description): ' +
33+
'🐛, Fix a bug; ' +
34+
'✨, Introduce new features; ' +
35+
'📝, Add or update documentation; ' +
36+
'🚀, Deploy stuff; ' +
37+
'✅, Add, update, or pass tests; ' +
38+
'♻️, Refactor code; ' +
39+
'⬆️, Upgrade dependencies; ' +
40+
'🔧, Add or update configuration files; ' +
41+
'🌐, Internationalization and localization; ' +
42+
'💡, Add or update comments in source code; ' +
43+
`${
44+
fullGitMojiSpec
45+
? '🎨, Improve structure / format of the code; ' +
46+
'⚡️, Improve performance; ' +
47+
'🔥, Remove code or files; ' +
48+
'🚑️, Critical hotfix; ' +
49+
'💄, Add or update the UI and style files; ' +
50+
'🎉, Begin a project; ' +
51+
'🔒️, Fix security issues; ' +
52+
'🔐, Add or update secrets; ' +
53+
'🔖, Release / Version tags; ' +
54+
'🚨, Fix compiler / linter warnings; ' +
55+
'🚧, Work in progress; ' +
56+
'💚, Fix CI Build; ' +
57+
'⬇️, Downgrade dependencies; ' +
58+
'📌, Pin dependencies to specific versions; ' +
59+
'👷, Add or update CI build system; ' +
60+
'📈, Add or update analytics or track code; ' +
61+
'➕, Add a dependency; ' +
62+
'➖, Remove a dependency; ' +
63+
'🔨, Add or update development scripts; ' +
64+
'✏️, Fix typos; ' +
65+
'💩, Write bad code that needs to be improved; ' +
66+
'⏪️, Revert changes; ' +
67+
'🔀, Merge branches; ' +
68+
'📦️, Add or update compiled files or packages; ' +
69+
'👽️, Update code due to external API changes; ' +
70+
'🚚, Move or rename resources (e.g.: files, paths, routes); ' +
71+
'📄, Add or update license; ' +
72+
'💥, Introduce breaking changes; ' +
73+
'🍱, Add or update assets; ' +
74+
'♿️, Improve accessibility; ' +
75+
'🍻, Write code drunkenly; ' +
76+
'💬, Add or update text and literals; ' +
77+
'🗃️, Perform database related changes; ' +
78+
'🔊, Add or update logs; ' +
79+
'🔇, Remove logs; ' +
80+
'👥, Add or update contributor(s); ' +
81+
'🚸, Improve user experience / usability; ' +
82+
'🏗️, Make architectural changes; ' +
83+
'📱, Work on responsive design; ' +
84+
'🤡, Mock things; ' +
85+
'🥚, Add or update an easter egg; ' +
86+
'🙈, Add or update a .gitignore file; ' +
87+
'📸, Add or update snapshots; ' +
88+
'⚗️, Perform experiments; ' +
89+
'🔍️, Improve SEO; ' +
90+
'🏷️, Add or update types; ' +
91+
'🌱, Add or update seed files; ' +
92+
'🚩, Add, update, or remove feature flags; ' +
93+
'🥅, Catch errors; ' +
94+
'💫, Add or update animations and transitions; ' +
95+
'🗑️, Deprecate code that needs to be cleaned up; ' +
96+
'🛂, Work on code related to authorization, roles and permissions; ' +
97+
'🩹, Simple fix for a non-critical issue; ' +
98+
'🧐, Data exploration/inspection; ' +
99+
'⚰️, Remove dead code; ' +
100+
'🧪, Add a failing test; ' +
101+
'👔, Add or update business logic; ' +
102+
'🩺, Add or update healthcheck; ' +
103+
'🧱, Infrastructure related changes; ' +
104+
'🧑‍💻, Improve developer experience; ' +
105+
'💸, Add sponsorships or money related infrastructure; ' +
106+
'🧵, Add or update code related to multithreading or concurrency; ' +
107+
'🦺, Add or update code related to validation.'
108+
: ''
109+
}`
110+
: 'Do not preface the commit with anything. Conventional commit keywords:' +
111+
'fix, feat, build, chore, ci, docs, style, refactor, perf, test.'
112+
}
29113
${
30114
config?.OCO_DESCRIPTION
31115
? 'Add a short description of WHY the changes are done after the commit message. Don\'t start it with "This commit", just describe the changes.'
@@ -66,14 +150,22 @@ const INIT_CONSISTENCY_PROMPT = (
66150
translation: ConsistencyPrompt
67151
): ChatCompletionRequestMessage => ({
68152
role: ChatCompletionRequestMessageRoleEnum.Assistant,
69-
content: `${config?.OCO_EMOJI ? '🐛 ' : ''}${translation.commitFix}
70-
${config?.OCO_EMOJI ? '✨ ' : ''}${translation.commitFeat}
153+
content: `${
154+
config?.OCO_EMOJI
155+
? `🐛 ${removeConventionalCommitWord(translation.commitFix)}`
156+
: translation.commitFix
157+
}
158+
${
159+
config?.OCO_EMOJI
160+
? `✨ ${removeConventionalCommitWord(translation.commitFeat)}`
161+
: translation.commitFeat
162+
}
71163
${config?.OCO_DESCRIPTION ? translation.commitDescription : ''}`
72164
});
73165

74-
export const getMainCommitPrompt = async (): Promise<
75-
ChatCompletionRequestMessage[]
76-
> => {
166+
export const getMainCommitPrompt = async (
167+
fullGitMojiSpec: boolean
168+
): Promise<ChatCompletionRequestMessage[]> => {
77169
switch (config?.OCO_PROMPT_MODULE) {
78170
case '@commitlint':
79171
if (!(await utils.commitlintLLMConfigExists())) {
@@ -102,7 +194,7 @@ export const getMainCommitPrompt = async (): Promise<
102194
default:
103195
// conventional-commit
104196
return [
105-
INIT_MAIN_PROMPT(translation.localLanguage),
197+
INIT_MAIN_PROMPT(translation.localLanguage, fullGitMojiSpec),
106198
INIT_DIFF_PROMPT,
107199
INIT_CONSISTENCY_PROMPT(translation)
108200
];
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function removeConventionalCommitWord(message: string): string {
2+
return message.replace(/^(fix|feat)\((.+?)\):/, '($2):');
3+
}

0 commit comments

Comments
 (0)