mirror of
https://github.com/di-sukharev/opencommit.git
synced 2026-01-12 23:28:16 -05:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eaf6600299 | ||
|
|
401be04b4d | ||
|
|
a9a2131ebf | ||
|
|
7dd8094760 | ||
|
|
a3d3363a01 | ||
|
|
75d0f57f09 | ||
|
|
8c92b92868 | ||
|
|
a33027b4db | ||
|
|
c1797de3da | ||
|
|
3d49081f6d | ||
|
|
8c318d96f4 | ||
|
|
9b7337f67f | ||
|
|
0b5adf104a | ||
|
|
ec699c48bf | ||
|
|
c9b45492a5 | ||
|
|
b0b90679a4 | ||
|
|
02cef105a6 | ||
|
|
407ca4b244 | ||
|
|
62e44e5e35 | ||
|
|
03fce6f5cf | ||
|
|
6155fca4b1 | ||
|
|
22d4af48c7 | ||
|
|
17d5a7143f | ||
|
|
011db5ad5e | ||
|
|
89682f0397 | ||
|
|
9ed9174b6f | ||
|
|
53ae8926fa | ||
|
|
6c743ba230 | ||
|
|
9852c36a98 |
28
.github/workflows/stale.yml
vendored
28
.github/workflows/stale.yml
vendored
@@ -1,28 +0,0 @@
|
||||
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
|
||||
#
|
||||
# You can adjust the behavior by modifying this file.
|
||||
# For more information, see:
|
||||
# https://github.com/actions/stale
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '27 21 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 40
|
||||
stale-issue-message: 'Stale issue message'
|
||||
stale-pr-message: 'Stale pull request message'
|
||||
stale-issue-label: 'no-issue-activity'
|
||||
stale-pr-label: 'no-pr-activity'
|
||||
27
README.md
27
README.md
@@ -7,7 +7,7 @@
|
||||
<h2>Auto-generate meaningful commits in 1 second</h2>
|
||||
<p>Killing lame commits with AI 🤯🔫</p>
|
||||
<a href="https://www.npmjs.com/package/opencommit"><img src="https://img.shields.io/npm/v/opencommit" alt="Current version"></a>
|
||||
<h4 align="center"><a href="https://twitter.com/_sukharev_/status/1683448136973582336">🪩 Winner of GitHub 2023 HACKATHON 🪩</a></h4>
|
||||
<h4 align="center">🪩 Winner of <a href="https://twitter.com/_sukharev_/status/1683448136973582336">GitHub 2023 hackathon</a> 🪩</h4>
|
||||
</div>
|
||||
|
||||
---
|
||||
@@ -58,6 +58,8 @@ git add <files...>
|
||||
oco
|
||||
```
|
||||
|
||||
Link to the GitMoji specification: https://gitmoji.dev/
|
||||
|
||||
You can also run it with local model through ollama:
|
||||
|
||||
- install and start ollama
|
||||
@@ -69,6 +71,17 @@ git add <files...>
|
||||
AI_PROVIDER='ollama' opencommit
|
||||
```
|
||||
|
||||
### Flags
|
||||
There are multiple optional flags that can be used with the `oco` command:
|
||||
|
||||
#### Use Full GitMoji Specification
|
||||
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 (🐛✨📝🚀✅♻️⬆️🔧🌐💡).
|
||||
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.
|
||||
|
||||
```
|
||||
oco --fgm
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Local per repo configuration
|
||||
@@ -77,7 +90,8 @@ Create a `.env` file and add OpenCommit config variables there like this:
|
||||
|
||||
```env
|
||||
OCO_OPENAI_API_KEY=<your OpenAI API token>
|
||||
OCO_OPENAI_MAX_TOKENS=<max response tokens from OpenAI API>
|
||||
OCO_TOKENS_MAX_INPUT=<max model token limit (default: 4096)>
|
||||
OCO_TOKENS_MAX_OUTPUT=<max response tokens (default: 500)>
|
||||
OCO_OPENAI_BASE_PATH=<may be used to set proxy path to OpenAI api>
|
||||
OCO_DESCRIPTION=<postface a message with ~3 sentences description of the changes>
|
||||
OCO_EMOJI=<boolean, add GitMoji>
|
||||
@@ -125,6 +139,12 @@ or for as a cheaper option:
|
||||
oco config set OCO_MODEL=gpt-3.5-turbo
|
||||
```
|
||||
|
||||
or for GPT-4 Turbo (Preview) which is more capable, has knowledge of world events up to April 2023, a 128k context window and 2-3x cheaper vs GPT-4:
|
||||
|
||||
```sh
|
||||
oco config set OCO_MODEL=gpt-4-1106-preview
|
||||
```
|
||||
|
||||
Make sure that you spell it `gpt-4` (lowercase) and that you have API access to the 4th model. Even if you have ChatGPT+, that doesn't necessarily mean that you have API access to GPT-4.
|
||||
|
||||
### Locale configuration
|
||||
@@ -323,7 +343,8 @@ jobs:
|
||||
OCO_OPENAI_API_KEY: ${{ secrets.OCO_OPENAI_API_KEY }}
|
||||
|
||||
# customization
|
||||
OCO_OPENAI_MAX_TOKENS: 500
|
||||
OCO_TOKENS_MAX_INPUT: 4096
|
||||
OCO_TOKENS_MAX_OUTPUT: 500
|
||||
OCO_OPENAI_BASE_PATH: ''
|
||||
OCO_DESCRIPTION: false
|
||||
OCO_EMOJI: false
|
||||
|
||||
143
out/cli.cjs
143
out/cli.cjs
@@ -16384,7 +16384,7 @@ function G3(t, e2) {
|
||||
// package.json
|
||||
var package_default = {
|
||||
name: "opencommit",
|
||||
version: "3.0.4",
|
||||
version: "3.0.10",
|
||||
description: "Auto-generate impressive commits in 1 second. Killing lame commits with AI \u{1F92F}\u{1F52B}",
|
||||
keywords: [
|
||||
"git",
|
||||
@@ -18650,7 +18650,6 @@ function getI18nLocal(value) {
|
||||
|
||||
// src/commands/config.ts
|
||||
dotenv.config();
|
||||
var DEFAULT_MODEL_TOKEN_LIMIT = 4096;
|
||||
var validateConfig = (key, condition, validationMessage) => {
|
||||
if (!condition) {
|
||||
ce(
|
||||
@@ -18682,17 +18681,33 @@ var configValidators = {
|
||||
);
|
||||
return value;
|
||||
},
|
||||
["OCO_OPENAI_MAX_TOKENS" /* OCO_OPENAI_MAX_TOKENS */](value) {
|
||||
["OCO_TOKENS_MAX_INPUT" /* OCO_TOKENS_MAX_INPUT */](value) {
|
||||
if (typeof value === "string") {
|
||||
value = parseInt(value);
|
||||
validateConfig(
|
||||
"OCO_OPENAI_MAX_TOKENS" /* OCO_OPENAI_MAX_TOKENS */,
|
||||
"OCO_TOKENS_MAX_INPUT" /* OCO_TOKENS_MAX_INPUT */,
|
||||
!isNaN(value),
|
||||
"Must be a number"
|
||||
);
|
||||
}
|
||||
validateConfig(
|
||||
"OCO_OPENAI_MAX_TOKENS" /* OCO_OPENAI_MAX_TOKENS */,
|
||||
"OCO_TOKENS_MAX_INPUT" /* OCO_TOKENS_MAX_INPUT */,
|
||||
value ? typeof value === "number" : void 0,
|
||||
"Must be a number"
|
||||
);
|
||||
return value;
|
||||
},
|
||||
["OCO_TOKENS_MAX_OUTPUT" /* OCO_TOKENS_MAX_OUTPUT */](value) {
|
||||
if (typeof value === "string") {
|
||||
value = parseInt(value);
|
||||
validateConfig(
|
||||
"OCO_TOKENS_MAX_OUTPUT" /* OCO_TOKENS_MAX_OUTPUT */,
|
||||
!isNaN(value),
|
||||
"Must be a number"
|
||||
);
|
||||
}
|
||||
validateConfig(
|
||||
"OCO_TOKENS_MAX_OUTPUT" /* OCO_TOKENS_MAX_OUTPUT */,
|
||||
value ? typeof value === "number" : void 0,
|
||||
"Must be a number"
|
||||
);
|
||||
@@ -18729,9 +18744,10 @@ var configValidators = {
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-4",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-0613"
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-4-1106-preview"
|
||||
].includes(value),
|
||||
`${value} is not supported yet, use 'gpt-4', 'gpt-3.5-turbo-16k' (default), 'gpt-3.5-turbo-0613' or 'gpt-3.5-turbo'`
|
||||
`${value} is not supported yet, use 'gpt-4', 'gpt-3.5-turbo-16k' (default), 'gpt-3.5-turbo-0613', 'gpt-3.5-turbo' or 'gpt-4-1106-preview'`
|
||||
);
|
||||
return value;
|
||||
},
|
||||
@@ -18768,7 +18784,8 @@ var configPath = (0, import_path.join)((0, import_os.homedir)(), ".opencommit");
|
||||
var getConfig = () => {
|
||||
const configFromEnv = {
|
||||
OCO_OPENAI_API_KEY: process.env.OCO_OPENAI_API_KEY,
|
||||
OCO_OPENAI_MAX_TOKENS: process.env.OCO_OPENAI_MAX_TOKENS ? Number(process.env.OCO_OPENAI_MAX_TOKENS) : void 0,
|
||||
OCO_TOKENS_MAX_INPUT: process.env.OCO_TOKENS_MAX_INPUT ? Number(process.env.OCO_TOKENS_MAX_INPUT) : void 0,
|
||||
OCO_TOKENS_MAX_OUTPUT: process.env.OCO_TOKENS_MAX_OUTPUT ? Number(process.env.OCO_TOKENS_MAX_OUTPUT) : void 0,
|
||||
OCO_OPENAI_BASE_PATH: process.env.OCO_OPENAI_BASE_PATH,
|
||||
OCO_DESCRIPTION: process.env.OCO_DESCRIPTION === "true" ? true : false,
|
||||
OCO_EMOJI: process.env.OCO_EMOJI === "true" ? true : false,
|
||||
@@ -19036,6 +19053,15 @@ var removeDoubleNewlines = (input) => {
|
||||
}
|
||||
return input;
|
||||
};
|
||||
var getJSONBlock = (input) => {
|
||||
const jsonIndex = input.search("```json");
|
||||
if (jsonIndex > -1) {
|
||||
input = input.slice(jsonIndex + 8);
|
||||
const endJsonIndex = consistency.search("```");
|
||||
input = input.slice(0, endJsonIndex);
|
||||
}
|
||||
return input;
|
||||
};
|
||||
var commitlintLLMConfigExists = async () => {
|
||||
let exists;
|
||||
try {
|
||||
@@ -21899,7 +21925,8 @@ function tokenCount(content) {
|
||||
|
||||
// src/engine/openAi.ts
|
||||
var config3 = getConfig();
|
||||
var maxTokens = config3?.OCO_OPENAI_MAX_TOKENS;
|
||||
var MAX_TOKENS_OUTPUT = config3?.OCO_TOKENS_MAX_OUTPUT || 500 /* DEFAULT_MAX_TOKENS_OUTPUT */;
|
||||
var MAX_TOKENS_INPUT = config3?.OCO_TOKENS_MAX_INPUT || 4096 /* DEFAULT_MAX_TOKENS_INPUT */;
|
||||
var basePath = config3?.OCO_OPENAI_BASE_PATH;
|
||||
var apiKey = config3?.OCO_OPENAI_API_KEY;
|
||||
var [command, mode] = process.argv.slice(2);
|
||||
@@ -21932,11 +21959,11 @@ var OpenAi = class {
|
||||
messages,
|
||||
temperature: 0,
|
||||
top_p: 0.1,
|
||||
max_tokens: maxTokens || 500
|
||||
max_tokens: MAX_TOKENS_OUTPUT
|
||||
};
|
||||
try {
|
||||
const REQUEST_TOKENS = messages.map((msg) => tokenCount(msg.content) + 4).reduce((a2, b6) => a2 + b6, 0);
|
||||
if (REQUEST_TOKENS > DEFAULT_MODEL_TOKEN_LIMIT - maxTokens) {
|
||||
if (REQUEST_TOKENS > MAX_TOKENS_INPUT - MAX_TOKENS_OUTPUT) {
|
||||
throw new Error("TOO_MUCH_TOKENS" /* tooMuchTokens */);
|
||||
}
|
||||
const { data } = await this.openAI.createChatCompletion(params);
|
||||
@@ -22020,15 +22047,16 @@ var configureCommitlintIntegration = async (force = false) => {
|
||||
const prompts = inferPromptsFromCommitlintConfig(commitLintConfig);
|
||||
const consistencyPrompts = commitlintPrompts.GEN_COMMITLINT_CONSISTENCY_PROMPT(prompts);
|
||||
const engine = getEngine();
|
||||
let consistency = await engine.generateCommitMessage(consistencyPrompts) || "{}";
|
||||
prompts.forEach((prompt) => consistency = consistency.replace(prompt, ""));
|
||||
consistency = removeDoubleNewlines(consistency);
|
||||
let consistency2 = await engine.generateCommitMessage(consistencyPrompts) || "{}";
|
||||
prompts.forEach((prompt) => consistency2 = consistency2.replace(prompt, ""));
|
||||
consistency2 = getJSONBlock(consistency2);
|
||||
consistency2 = removeDoubleNewlines(consistency2);
|
||||
const commitlintLLMConfig = {
|
||||
hash,
|
||||
prompts,
|
||||
consistency: {
|
||||
[translation2.localLanguage]: {
|
||||
...JSON.parse(consistency)
|
||||
...JSON.parse(consistency2)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -22036,14 +22064,19 @@ var configureCommitlintIntegration = async (force = false) => {
|
||||
spin.stop(`Done - please review contents of ${COMMITLINT_LLM_CONFIG_PATH}`);
|
||||
};
|
||||
|
||||
// src/utils/removeConventionalCommitWord.ts
|
||||
function removeConventionalCommitWord(message) {
|
||||
return message.replace(/^(fix|feat)\((.+?)\):/, "($2):");
|
||||
}
|
||||
|
||||
// src/prompts.ts
|
||||
var config5 = getConfig();
|
||||
var translation3 = i18n[config5?.OCO_LANGUAGE || "en"];
|
||||
var IDENTITY = "You are to act as the author of a commit message in git.";
|
||||
var INIT_MAIN_PROMPT2 = (language) => ({
|
||||
var INIT_MAIN_PROMPT2 = (language, fullGitMojiSpec) => ({
|
||||
role: import_openai3.ChatCompletionRequestMessageRoleEnum.System,
|
||||
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.
|
||||
${config5?.OCO_EMOJI ? "Use GitMoji convention to preface the commit." : "Do not preface the commit with anything."}
|
||||
content: `${IDENTITY} Your mission is to create clean and comprehensive commit messages as per the ${fullGitMojiSpec ? "GitMoji specification" : "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.
|
||||
${config5?.OCO_EMOJI ? `Use GitMoji convention to preface the commit. Here are some help to choose the right emoji (emoji, description): \u{1F41B}, Fix a bug; \u2728, Introduce new features; \u{1F4DD}, Add or update documentation; \u{1F680}, Deploy stuff; \u2705, Add, update, or pass tests; \u267B\uFE0F, Refactor code; \u2B06\uFE0F, Upgrade dependencies; \u{1F527}, Add or update configuration files; \u{1F310}, Internationalization and localization; \u{1F4A1}, Add or update comments in source code; ${fullGitMojiSpec ? "\u{1F3A8}, Improve structure / format of the code; \u26A1\uFE0F, Improve performance; \u{1F525}, Remove code or files; \u{1F691}\uFE0F, Critical hotfix; \u{1F484}, Add or update the UI and style files; \u{1F389}, Begin a project; \u{1F512}\uFE0F, Fix security issues; \u{1F510}, Add or update secrets; \u{1F516}, Release / Version tags; \u{1F6A8}, Fix compiler / linter warnings; \u{1F6A7}, Work in progress; \u{1F49A}, Fix CI Build; \u2B07\uFE0F, Downgrade dependencies; \u{1F4CC}, Pin dependencies to specific versions; \u{1F477}, Add or update CI build system; \u{1F4C8}, Add or update analytics or track code; \u2795, Add a dependency; \u2796, Remove a dependency; \u{1F528}, Add or update development scripts; \u270F\uFE0F, Fix typos; \u{1F4A9}, Write bad code that needs to be improved; \u23EA\uFE0F, Revert changes; \u{1F500}, Merge branches; \u{1F4E6}\uFE0F, Add or update compiled files or packages; \u{1F47D}\uFE0F, Update code due to external API changes; \u{1F69A}, Move or rename resources (e.g.: files, paths, routes); \u{1F4C4}, Add or update license; \u{1F4A5}, Introduce breaking changes; \u{1F371}, Add or update assets; \u267F\uFE0F, Improve accessibility; \u{1F37B}, Write code drunkenly; \u{1F4AC}, Add or update text and literals; \u{1F5C3}\uFE0F, Perform database related changes; \u{1F50A}, Add or update logs; \u{1F507}, Remove logs; \u{1F465}, Add or update contributor(s); \u{1F6B8}, Improve user experience / usability; \u{1F3D7}\uFE0F, Make architectural changes; \u{1F4F1}, Work on responsive design; \u{1F921}, Mock things; \u{1F95A}, Add or update an easter egg; \u{1F648}, Add or update a .gitignore file; \u{1F4F8}, Add or update snapshots; \u2697\uFE0F, Perform experiments; \u{1F50D}\uFE0F, Improve SEO; \u{1F3F7}\uFE0F, Add or update types; \u{1F331}, Add or update seed files; \u{1F6A9}, Add, update, or remove feature flags; \u{1F945}, Catch errors; \u{1F4AB}, Add or update animations and transitions; \u{1F5D1}\uFE0F, Deprecate code that needs to be cleaned up; \u{1F6C2}, Work on code related to authorization, roles and permissions; \u{1FA79}, Simple fix for a non-critical issue; \u{1F9D0}, Data exploration/inspection; \u26B0\uFE0F, Remove dead code; \u{1F9EA}, Add a failing test; \u{1F454}, Add or update business logic; \u{1FA7A}, Add or update healthcheck; \u{1F9F1}, Infrastructure related changes; \u{1F9D1}\u200D\u{1F4BB}, Improve developer experience; \u{1F4B8}, Add sponsorships or money related infrastructure; \u{1F9F5}, Add or update code related to multithreading or concurrency; \u{1F9BA}, Add or update code related to validation." : ""}` : "Do not preface the commit with anything. Conventional commit keywords:fix, feat, build, chore, ci, docs, style, refactor, perf, test."}
|
||||
${config5?.OCO_DESCRIPTION ? `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.` : "Don't add any descriptions to the commit, only commit message."}
|
||||
Use the present tense. Lines must not be longer than 74 characters. Use ${language} for the commit message.`
|
||||
});
|
||||
@@ -22076,11 +22109,11 @@ var INIT_DIFF_PROMPT = {
|
||||
};
|
||||
var INIT_CONSISTENCY_PROMPT = (translation4) => ({
|
||||
role: import_openai3.ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
content: `${config5?.OCO_EMOJI ? "\u{1F41B} " : ""}${translation4.commitFix}
|
||||
${config5?.OCO_EMOJI ? "\u2728 " : ""}${translation4.commitFeat}
|
||||
content: `${config5?.OCO_EMOJI ? `\u{1F41B} ${removeConventionalCommitWord(translation4.commitFix)}` : translation4.commitFix}
|
||||
${config5?.OCO_EMOJI ? `\u2728 ${removeConventionalCommitWord(translation4.commitFeat)}` : translation4.commitFeat}
|
||||
${config5?.OCO_DESCRIPTION ? translation4.commitDescription : ""}`
|
||||
});
|
||||
var getMainCommitPrompt = async () => {
|
||||
var getMainCommitPrompt = async (fullGitMojiSpec) => {
|
||||
switch (config5?.OCO_PROMPT_MODULE) {
|
||||
case "@commitlint":
|
||||
if (!await commitlintLLMConfigExists()) {
|
||||
@@ -22102,7 +22135,7 @@ var getMainCommitPrompt = async () => {
|
||||
];
|
||||
default:
|
||||
return [
|
||||
INIT_MAIN_PROMPT2(translation3.localLanguage),
|
||||
INIT_MAIN_PROMPT2(translation3.localLanguage, fullGitMojiSpec),
|
||||
INIT_DIFF_PROMPT,
|
||||
INIT_CONSISTENCY_PROMPT(translation3)
|
||||
];
|
||||
@@ -22127,8 +22160,10 @@ function mergeDiffs(arr, maxStringLength) {
|
||||
|
||||
// src/generateCommitMessageFromGitDiff.ts
|
||||
var config6 = getConfig();
|
||||
var generateCommitMessageChatCompletionPrompt = async (diff) => {
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt();
|
||||
var MAX_TOKENS_INPUT2 = config6?.OCO_TOKENS_MAX_INPUT || 4096 /* DEFAULT_MAX_TOKENS_INPUT */;
|
||||
var MAX_TOKENS_OUTPUT2 = config6?.OCO_TOKENS_MAX_OUTPUT || 500 /* DEFAULT_MAX_TOKENS_OUTPUT */;
|
||||
var generateCommitMessageChatCompletionPrompt = async (diff, fullGitMojiSpec) => {
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt(fullGitMojiSpec);
|
||||
const chatContextAsCompletionRequest = [...INIT_MESSAGES_PROMPT];
|
||||
chatContextAsCompletionRequest.push({
|
||||
role: import_openai4.ChatCompletionRequestMessageRoleEnum.User,
|
||||
@@ -22136,18 +22171,26 @@ var generateCommitMessageChatCompletionPrompt = async (diff) => {
|
||||
});
|
||||
return chatContextAsCompletionRequest;
|
||||
};
|
||||
var GenerateCommitMessageErrorEnum = ((GenerateCommitMessageErrorEnum2) => {
|
||||
GenerateCommitMessageErrorEnum2["tooMuchTokens"] = "TOO_MUCH_TOKENS";
|
||||
GenerateCommitMessageErrorEnum2["internalError"] = "INTERNAL_ERROR";
|
||||
GenerateCommitMessageErrorEnum2["emptyMessage"] = "EMPTY_MESSAGE";
|
||||
GenerateCommitMessageErrorEnum2[GenerateCommitMessageErrorEnum2["outputTokensTooHigh"] = `Token limit exceeded, OCO_TOKENS_MAX_OUTPUT must not be much higher than the default ${500 /* DEFAULT_MAX_TOKENS_OUTPUT */} tokens.`] = "outputTokensTooHigh";
|
||||
return GenerateCommitMessageErrorEnum2;
|
||||
})(GenerateCommitMessageErrorEnum || {});
|
||||
var ADJUSTMENT_FACTOR = 20;
|
||||
var generateCommitMessageByDiff = async (diff) => {
|
||||
var generateCommitMessageByDiff = async (diff, fullGitMojiSpec) => {
|
||||
try {
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt();
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt(fullGitMojiSpec);
|
||||
const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map(
|
||||
(msg) => tokenCount(msg.content) + 4
|
||||
).reduce((a2, b6) => a2 + b6, 0);
|
||||
const MAX_REQUEST_TOKENS = DEFAULT_MODEL_TOKEN_LIMIT - ADJUSTMENT_FACTOR - INIT_MESSAGES_PROMPT_LENGTH - config6?.OCO_OPENAI_MAX_TOKENS;
|
||||
const MAX_REQUEST_TOKENS = MAX_TOKENS_INPUT2 - ADJUSTMENT_FACTOR - INIT_MESSAGES_PROMPT_LENGTH - MAX_TOKENS_OUTPUT2;
|
||||
if (tokenCount(diff) >= MAX_REQUEST_TOKENS) {
|
||||
const commitMessagePromises = await getCommitMsgsPromisesFromFileDiffs(
|
||||
diff,
|
||||
MAX_REQUEST_TOKENS
|
||||
MAX_REQUEST_TOKENS,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
const commitMessages = [];
|
||||
for (const promise of commitMessagePromises) {
|
||||
@@ -22156,7 +22199,7 @@ var generateCommitMessageByDiff = async (diff) => {
|
||||
}
|
||||
return commitMessages.join("\n\n");
|
||||
}
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(diff);
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(diff, fullGitMojiSpec);
|
||||
const engine = getEngine();
|
||||
const commitMessage = await engine.generateCommitMessage(messages);
|
||||
if (!commitMessage)
|
||||
@@ -22166,7 +22209,7 @@ var generateCommitMessageByDiff = async (diff) => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
function getMessagesPromisesByChangesInFile(fileDiff, separator, maxChangeLength) {
|
||||
function getMessagesPromisesByChangesInFile(fileDiff, separator, maxChangeLength, fullGitMojiSpec) {
|
||||
const hunkHeaderSeparator = "@@ ";
|
||||
const [fileHeader, ...fileDiffByLines] = fileDiff.split(hunkHeaderSeparator);
|
||||
const mergedChanges = mergeDiffs(
|
||||
@@ -22187,7 +22230,8 @@ function getMessagesPromisesByChangesInFile(fileDiff, separator, maxChangeLength
|
||||
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map(
|
||||
async (lineDiff) => {
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(
|
||||
separator + lineDiff
|
||||
separator + lineDiff,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
return engine.generateCommitMessage(messages);
|
||||
}
|
||||
@@ -22198,6 +22242,9 @@ function splitDiff(diff, maxChangeLength) {
|
||||
const lines = diff.split("\n");
|
||||
const splitDiffs = [];
|
||||
let currentDiff = "";
|
||||
if (maxChangeLength <= 0) {
|
||||
throw new Error(GenerateCommitMessageErrorEnum.outputTokensTooHigh);
|
||||
}
|
||||
for (let line of lines) {
|
||||
while (tokenCount(line) > maxChangeLength) {
|
||||
const subLine = line.substring(0, maxChangeLength);
|
||||
@@ -22216,7 +22263,7 @@ function splitDiff(diff, maxChangeLength) {
|
||||
}
|
||||
return splitDiffs;
|
||||
}
|
||||
var getCommitMsgsPromisesFromFileDiffs = async (diff, maxDiffLength) => {
|
||||
var getCommitMsgsPromisesFromFileDiffs = async (diff, maxDiffLength, fullGitMojiSpec) => {
|
||||
const separator = "diff --git ";
|
||||
const diffByFiles = diff.split(separator).slice(1);
|
||||
const mergedFilesDiffs = mergeDiffs(diffByFiles, maxDiffLength);
|
||||
@@ -22226,12 +22273,14 @@ var getCommitMsgsPromisesFromFileDiffs = async (diff, maxDiffLength) => {
|
||||
const messagesPromises = getMessagesPromisesByChangesInFile(
|
||||
fileDiff,
|
||||
separator,
|
||||
maxDiffLength
|
||||
maxDiffLength,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
commitMessagePromises.push(...messagesPromises);
|
||||
} else {
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(
|
||||
separator + fileDiff
|
||||
separator + fileDiff,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
const engine = getEngine();
|
||||
commitMessagePromises.push(engine.generateCommitMessage(messages));
|
||||
@@ -22353,14 +22402,19 @@ var checkMessageTemplate = (extraArgs2) => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
var generateCommitMessageFromGitDiff = async (diff, extraArgs2) => {
|
||||
var generateCommitMessageFromGitDiff = async (diff, extraArgs2, fullGitMojiSpec) => {
|
||||
await assertGitRepo();
|
||||
const commitSpinner = le();
|
||||
commitSpinner.start("Generating the commit message");
|
||||
try {
|
||||
let commitMessage = await generateCommitMessageByDiff(diff);
|
||||
let commitMessage = await generateCommitMessageByDiff(
|
||||
diff,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
const messageTemplate = checkMessageTemplate(extraArgs2);
|
||||
if (config7?.OCO_MESSAGE_TEMPLATE_PLACEHOLDER && typeof messageTemplate === "string") {
|
||||
const messageTemplateIndex = extraArgs2.indexOf(messageTemplate);
|
||||
extraArgs2.splice(messageTemplateIndex, 1);
|
||||
commitMessage = messageTemplate.replace(
|
||||
config7?.OCO_MESSAGE_TEMPLATE_PLACEHOLDER,
|
||||
commitMessage
|
||||
@@ -22440,7 +22494,7 @@ ${source_default.grey("\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
async function commit(extraArgs2 = [], isStageAllFlag = false) {
|
||||
async function commit(extraArgs2 = [], isStageAllFlag = false, fullGitMojiSpec = false) {
|
||||
if (isStageAllFlag) {
|
||||
const changedFiles2 = await getChangedFiles();
|
||||
if (changedFiles2)
|
||||
@@ -22469,7 +22523,7 @@ async function commit(extraArgs2 = [], isStageAllFlag = false) {
|
||||
message: "Do you want to stage all files and generate commit message?"
|
||||
});
|
||||
if (isStageAllAndCommitConfirmedByUser && !eD2(isStageAllAndCommitConfirmedByUser)) {
|
||||
await commit(extraArgs2, true);
|
||||
await commit(extraArgs2, true, fullGitMojiSpec);
|
||||
process.exit(1);
|
||||
}
|
||||
if (stagedFiles.length === 0 && changedFiles.length > 0) {
|
||||
@@ -22484,7 +22538,7 @@ async function commit(extraArgs2 = [], isStageAllFlag = false) {
|
||||
process.exit(1);
|
||||
await gitAdd({ files });
|
||||
}
|
||||
await commit(extraArgs2, false);
|
||||
await commit(extraArgs2, false, fullGitMojiSpec);
|
||||
process.exit(1);
|
||||
}
|
||||
stagedFilesSpinner.stop(
|
||||
@@ -22494,7 +22548,8 @@ ${stagedFiles.map((file) => ` ${file}`).join("\n")}`
|
||||
const [, generateCommitError] = await trytm(
|
||||
generateCommitMessageFromGitDiff(
|
||||
await getDiff({ files: stagedFiles }),
|
||||
extraArgs2
|
||||
extraArgs2,
|
||||
fullGitMojiSpec
|
||||
)
|
||||
);
|
||||
if (generateCommitError) {
|
||||
@@ -22700,16 +22755,18 @@ Z2(
|
||||
version: package_default.version,
|
||||
name: "opencommit",
|
||||
commands: [configCommand, hookCommand, commitlintConfigCommand],
|
||||
flags: {},
|
||||
flags: {
|
||||
fgm: Boolean
|
||||
},
|
||||
ignoreArgv: (type) => type === "unknown-flag" || type === "argument",
|
||||
help: { description: package_default.description }
|
||||
},
|
||||
async () => {
|
||||
async ({ flags }) => {
|
||||
await checkIsLatestVersion();
|
||||
if (await isHookCalled()) {
|
||||
prepareCommitMessageHook();
|
||||
} else {
|
||||
commit(extraArgs);
|
||||
commit(extraArgs, flags.fgm);
|
||||
}
|
||||
},
|
||||
extraArgs
|
||||
|
||||
@@ -24145,7 +24145,6 @@ function getI18nLocal(value) {
|
||||
|
||||
// src/commands/config.ts
|
||||
dotenv.config();
|
||||
var DEFAULT_MODEL_TOKEN_LIMIT = 4096;
|
||||
var validateConfig = (key, condition, validationMessage) => {
|
||||
if (!condition) {
|
||||
ce(
|
||||
@@ -24177,17 +24176,33 @@ var configValidators = {
|
||||
);
|
||||
return value;
|
||||
},
|
||||
["OCO_OPENAI_MAX_TOKENS" /* OCO_OPENAI_MAX_TOKENS */](value) {
|
||||
["OCO_TOKENS_MAX_INPUT" /* OCO_TOKENS_MAX_INPUT */](value) {
|
||||
if (typeof value === "string") {
|
||||
value = parseInt(value);
|
||||
validateConfig(
|
||||
"OCO_OPENAI_MAX_TOKENS" /* OCO_OPENAI_MAX_TOKENS */,
|
||||
"OCO_TOKENS_MAX_INPUT" /* OCO_TOKENS_MAX_INPUT */,
|
||||
!isNaN(value),
|
||||
"Must be a number"
|
||||
);
|
||||
}
|
||||
validateConfig(
|
||||
"OCO_OPENAI_MAX_TOKENS" /* OCO_OPENAI_MAX_TOKENS */,
|
||||
"OCO_TOKENS_MAX_INPUT" /* OCO_TOKENS_MAX_INPUT */,
|
||||
value ? typeof value === "number" : void 0,
|
||||
"Must be a number"
|
||||
);
|
||||
return value;
|
||||
},
|
||||
["OCO_TOKENS_MAX_OUTPUT" /* OCO_TOKENS_MAX_OUTPUT */](value) {
|
||||
if (typeof value === "string") {
|
||||
value = parseInt(value);
|
||||
validateConfig(
|
||||
"OCO_TOKENS_MAX_OUTPUT" /* OCO_TOKENS_MAX_OUTPUT */,
|
||||
!isNaN(value),
|
||||
"Must be a number"
|
||||
);
|
||||
}
|
||||
validateConfig(
|
||||
"OCO_TOKENS_MAX_OUTPUT" /* OCO_TOKENS_MAX_OUTPUT */,
|
||||
value ? typeof value === "number" : void 0,
|
||||
"Must be a number"
|
||||
);
|
||||
@@ -24224,9 +24239,10 @@ var configValidators = {
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-4",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-0613"
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-4-1106-preview"
|
||||
].includes(value),
|
||||
`${value} is not supported yet, use 'gpt-4', 'gpt-3.5-turbo-16k' (default), 'gpt-3.5-turbo-0613' or 'gpt-3.5-turbo'`
|
||||
`${value} is not supported yet, use 'gpt-4', 'gpt-3.5-turbo-16k' (default), 'gpt-3.5-turbo-0613', 'gpt-3.5-turbo' or 'gpt-4-1106-preview'`
|
||||
);
|
||||
return value;
|
||||
},
|
||||
@@ -24263,7 +24279,8 @@ var configPath = (0, import_path.join)((0, import_os.homedir)(), ".opencommit");
|
||||
var getConfig = () => {
|
||||
const configFromEnv = {
|
||||
OCO_OPENAI_API_KEY: process.env.OCO_OPENAI_API_KEY,
|
||||
OCO_OPENAI_MAX_TOKENS: process.env.OCO_OPENAI_MAX_TOKENS ? Number(process.env.OCO_OPENAI_MAX_TOKENS) : void 0,
|
||||
OCO_TOKENS_MAX_INPUT: process.env.OCO_TOKENS_MAX_INPUT ? Number(process.env.OCO_TOKENS_MAX_INPUT) : void 0,
|
||||
OCO_TOKENS_MAX_OUTPUT: process.env.OCO_TOKENS_MAX_OUTPUT ? Number(process.env.OCO_TOKENS_MAX_OUTPUT) : void 0,
|
||||
OCO_OPENAI_BASE_PATH: process.env.OCO_OPENAI_BASE_PATH,
|
||||
OCO_DESCRIPTION: process.env.OCO_DESCRIPTION === "true" ? true : false,
|
||||
OCO_EMOJI: process.env.OCO_EMOJI === "true" ? true : false,
|
||||
@@ -24531,6 +24548,15 @@ var removeDoubleNewlines = (input) => {
|
||||
}
|
||||
return input;
|
||||
};
|
||||
var getJSONBlock = (input) => {
|
||||
const jsonIndex = input.search("```json");
|
||||
if (jsonIndex > -1) {
|
||||
input = input.slice(jsonIndex + 8);
|
||||
const endJsonIndex = consistency.search("```");
|
||||
input = input.slice(0, endJsonIndex);
|
||||
}
|
||||
return input;
|
||||
};
|
||||
var commitlintLLMConfigExists = async () => {
|
||||
let exists;
|
||||
try {
|
||||
@@ -27394,7 +27420,8 @@ function tokenCount(content) {
|
||||
|
||||
// src/engine/openAi.ts
|
||||
var config3 = getConfig();
|
||||
var maxTokens = config3?.OCO_OPENAI_MAX_TOKENS;
|
||||
var MAX_TOKENS_OUTPUT = config3?.OCO_TOKENS_MAX_OUTPUT || 500 /* DEFAULT_MAX_TOKENS_OUTPUT */;
|
||||
var MAX_TOKENS_INPUT = config3?.OCO_TOKENS_MAX_INPUT || 4096 /* DEFAULT_MAX_TOKENS_INPUT */;
|
||||
var basePath = config3?.OCO_OPENAI_BASE_PATH;
|
||||
var apiKey = config3?.OCO_OPENAI_API_KEY;
|
||||
var [command, mode] = process.argv.slice(2);
|
||||
@@ -27427,11 +27454,11 @@ var OpenAi = class {
|
||||
messages,
|
||||
temperature: 0,
|
||||
top_p: 0.1,
|
||||
max_tokens: maxTokens || 500
|
||||
max_tokens: MAX_TOKENS_OUTPUT
|
||||
};
|
||||
try {
|
||||
const REQUEST_TOKENS = messages.map((msg) => tokenCount(msg.content) + 4).reduce((a2, b2) => a2 + b2, 0);
|
||||
if (REQUEST_TOKENS > DEFAULT_MODEL_TOKEN_LIMIT - maxTokens) {
|
||||
if (REQUEST_TOKENS > MAX_TOKENS_INPUT - MAX_TOKENS_OUTPUT) {
|
||||
throw new Error("TOO_MUCH_TOKENS" /* tooMuchTokens */);
|
||||
}
|
||||
const { data } = await this.openAI.createChatCompletion(params);
|
||||
@@ -27515,15 +27542,16 @@ var configureCommitlintIntegration = async (force = false) => {
|
||||
const prompts = inferPromptsFromCommitlintConfig(commitLintConfig);
|
||||
const consistencyPrompts = commitlintPrompts.GEN_COMMITLINT_CONSISTENCY_PROMPT(prompts);
|
||||
const engine = getEngine();
|
||||
let consistency = await engine.generateCommitMessage(consistencyPrompts) || "{}";
|
||||
prompts.forEach((prompt) => consistency = consistency.replace(prompt, ""));
|
||||
consistency = removeDoubleNewlines(consistency);
|
||||
let consistency2 = await engine.generateCommitMessage(consistencyPrompts) || "{}";
|
||||
prompts.forEach((prompt) => consistency2 = consistency2.replace(prompt, ""));
|
||||
consistency2 = getJSONBlock(consistency2);
|
||||
consistency2 = removeDoubleNewlines(consistency2);
|
||||
const commitlintLLMConfig = {
|
||||
hash,
|
||||
prompts,
|
||||
consistency: {
|
||||
[translation2.localLanguage]: {
|
||||
...JSON.parse(consistency)
|
||||
...JSON.parse(consistency2)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -27531,14 +27559,19 @@ var configureCommitlintIntegration = async (force = false) => {
|
||||
spin.stop(`Done - please review contents of ${COMMITLINT_LLM_CONFIG_PATH}`);
|
||||
};
|
||||
|
||||
// src/utils/removeConventionalCommitWord.ts
|
||||
function removeConventionalCommitWord(message) {
|
||||
return message.replace(/^(fix|feat)\((.+?)\):/, "($2):");
|
||||
}
|
||||
|
||||
// src/prompts.ts
|
||||
var config5 = getConfig();
|
||||
var translation3 = i18n[config5?.OCO_LANGUAGE || "en"];
|
||||
var IDENTITY = "You are to act as the author of a commit message in git.";
|
||||
var INIT_MAIN_PROMPT2 = (language) => ({
|
||||
var INIT_MAIN_PROMPT2 = (language, fullGitMojiSpec) => ({
|
||||
role: import_openai3.ChatCompletionRequestMessageRoleEnum.System,
|
||||
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.
|
||||
${config5?.OCO_EMOJI ? "Use GitMoji convention to preface the commit." : "Do not preface the commit with anything."}
|
||||
content: `${IDENTITY} Your mission is to create clean and comprehensive commit messages as per the ${fullGitMojiSpec ? "GitMoji specification" : "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.
|
||||
${config5?.OCO_EMOJI ? `Use GitMoji convention to preface the commit. Here are some help to choose the right emoji (emoji, description): \u{1F41B}, Fix a bug; \u2728, Introduce new features; \u{1F4DD}, Add or update documentation; \u{1F680}, Deploy stuff; \u2705, Add, update, or pass tests; \u267B\uFE0F, Refactor code; \u2B06\uFE0F, Upgrade dependencies; \u{1F527}, Add or update configuration files; \u{1F310}, Internationalization and localization; \u{1F4A1}, Add or update comments in source code; ${fullGitMojiSpec ? "\u{1F3A8}, Improve structure / format of the code; \u26A1\uFE0F, Improve performance; \u{1F525}, Remove code or files; \u{1F691}\uFE0F, Critical hotfix; \u{1F484}, Add or update the UI and style files; \u{1F389}, Begin a project; \u{1F512}\uFE0F, Fix security issues; \u{1F510}, Add or update secrets; \u{1F516}, Release / Version tags; \u{1F6A8}, Fix compiler / linter warnings; \u{1F6A7}, Work in progress; \u{1F49A}, Fix CI Build; \u2B07\uFE0F, Downgrade dependencies; \u{1F4CC}, Pin dependencies to specific versions; \u{1F477}, Add or update CI build system; \u{1F4C8}, Add or update analytics or track code; \u2795, Add a dependency; \u2796, Remove a dependency; \u{1F528}, Add or update development scripts; \u270F\uFE0F, Fix typos; \u{1F4A9}, Write bad code that needs to be improved; \u23EA\uFE0F, Revert changes; \u{1F500}, Merge branches; \u{1F4E6}\uFE0F, Add or update compiled files or packages; \u{1F47D}\uFE0F, Update code due to external API changes; \u{1F69A}, Move or rename resources (e.g.: files, paths, routes); \u{1F4C4}, Add or update license; \u{1F4A5}, Introduce breaking changes; \u{1F371}, Add or update assets; \u267F\uFE0F, Improve accessibility; \u{1F37B}, Write code drunkenly; \u{1F4AC}, Add or update text and literals; \u{1F5C3}\uFE0F, Perform database related changes; \u{1F50A}, Add or update logs; \u{1F507}, Remove logs; \u{1F465}, Add or update contributor(s); \u{1F6B8}, Improve user experience / usability; \u{1F3D7}\uFE0F, Make architectural changes; \u{1F4F1}, Work on responsive design; \u{1F921}, Mock things; \u{1F95A}, Add or update an easter egg; \u{1F648}, Add or update a .gitignore file; \u{1F4F8}, Add or update snapshots; \u2697\uFE0F, Perform experiments; \u{1F50D}\uFE0F, Improve SEO; \u{1F3F7}\uFE0F, Add or update types; \u{1F331}, Add or update seed files; \u{1F6A9}, Add, update, or remove feature flags; \u{1F945}, Catch errors; \u{1F4AB}, Add or update animations and transitions; \u{1F5D1}\uFE0F, Deprecate code that needs to be cleaned up; \u{1F6C2}, Work on code related to authorization, roles and permissions; \u{1FA79}, Simple fix for a non-critical issue; \u{1F9D0}, Data exploration/inspection; \u26B0\uFE0F, Remove dead code; \u{1F9EA}, Add a failing test; \u{1F454}, Add or update business logic; \u{1FA7A}, Add or update healthcheck; \u{1F9F1}, Infrastructure related changes; \u{1F9D1}\u200D\u{1F4BB}, Improve developer experience; \u{1F4B8}, Add sponsorships or money related infrastructure; \u{1F9F5}, Add or update code related to multithreading or concurrency; \u{1F9BA}, Add or update code related to validation." : ""}` : "Do not preface the commit with anything. Conventional commit keywords:fix, feat, build, chore, ci, docs, style, refactor, perf, test."}
|
||||
${config5?.OCO_DESCRIPTION ? `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.` : "Don't add any descriptions to the commit, only commit message."}
|
||||
Use the present tense. Lines must not be longer than 74 characters. Use ${language} for the commit message.`
|
||||
});
|
||||
@@ -27571,11 +27604,11 @@ var INIT_DIFF_PROMPT = {
|
||||
};
|
||||
var INIT_CONSISTENCY_PROMPT = (translation4) => ({
|
||||
role: import_openai3.ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
content: `${config5?.OCO_EMOJI ? "\u{1F41B} " : ""}${translation4.commitFix}
|
||||
${config5?.OCO_EMOJI ? "\u2728 " : ""}${translation4.commitFeat}
|
||||
content: `${config5?.OCO_EMOJI ? `\u{1F41B} ${removeConventionalCommitWord(translation4.commitFix)}` : translation4.commitFix}
|
||||
${config5?.OCO_EMOJI ? `\u2728 ${removeConventionalCommitWord(translation4.commitFeat)}` : translation4.commitFeat}
|
||||
${config5?.OCO_DESCRIPTION ? translation4.commitDescription : ""}`
|
||||
});
|
||||
var getMainCommitPrompt = async () => {
|
||||
var getMainCommitPrompt = async (fullGitMojiSpec) => {
|
||||
switch (config5?.OCO_PROMPT_MODULE) {
|
||||
case "@commitlint":
|
||||
if (!await commitlintLLMConfigExists()) {
|
||||
@@ -27597,7 +27630,7 @@ var getMainCommitPrompt = async () => {
|
||||
];
|
||||
default:
|
||||
return [
|
||||
INIT_MAIN_PROMPT2(translation3.localLanguage),
|
||||
INIT_MAIN_PROMPT2(translation3.localLanguage, fullGitMojiSpec),
|
||||
INIT_DIFF_PROMPT,
|
||||
INIT_CONSISTENCY_PROMPT(translation3)
|
||||
];
|
||||
@@ -27622,8 +27655,10 @@ function mergeDiffs(arr, maxStringLength) {
|
||||
|
||||
// src/generateCommitMessageFromGitDiff.ts
|
||||
var config6 = getConfig();
|
||||
var generateCommitMessageChatCompletionPrompt = async (diff) => {
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt();
|
||||
var MAX_TOKENS_INPUT2 = config6?.OCO_TOKENS_MAX_INPUT || 4096 /* DEFAULT_MAX_TOKENS_INPUT */;
|
||||
var MAX_TOKENS_OUTPUT2 = config6?.OCO_TOKENS_MAX_OUTPUT || 500 /* DEFAULT_MAX_TOKENS_OUTPUT */;
|
||||
var generateCommitMessageChatCompletionPrompt = async (diff, fullGitMojiSpec) => {
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt(fullGitMojiSpec);
|
||||
const chatContextAsCompletionRequest = [...INIT_MESSAGES_PROMPT];
|
||||
chatContextAsCompletionRequest.push({
|
||||
role: import_openai4.ChatCompletionRequestMessageRoleEnum.User,
|
||||
@@ -27631,18 +27666,26 @@ var generateCommitMessageChatCompletionPrompt = async (diff) => {
|
||||
});
|
||||
return chatContextAsCompletionRequest;
|
||||
};
|
||||
var GenerateCommitMessageErrorEnum = ((GenerateCommitMessageErrorEnum2) => {
|
||||
GenerateCommitMessageErrorEnum2["tooMuchTokens"] = "TOO_MUCH_TOKENS";
|
||||
GenerateCommitMessageErrorEnum2["internalError"] = "INTERNAL_ERROR";
|
||||
GenerateCommitMessageErrorEnum2["emptyMessage"] = "EMPTY_MESSAGE";
|
||||
GenerateCommitMessageErrorEnum2[GenerateCommitMessageErrorEnum2["outputTokensTooHigh"] = `Token limit exceeded, OCO_TOKENS_MAX_OUTPUT must not be much higher than the default ${500 /* DEFAULT_MAX_TOKENS_OUTPUT */} tokens.`] = "outputTokensTooHigh";
|
||||
return GenerateCommitMessageErrorEnum2;
|
||||
})(GenerateCommitMessageErrorEnum || {});
|
||||
var ADJUSTMENT_FACTOR = 20;
|
||||
var generateCommitMessageByDiff = async (diff) => {
|
||||
var generateCommitMessageByDiff = async (diff, fullGitMojiSpec) => {
|
||||
try {
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt();
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt(fullGitMojiSpec);
|
||||
const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map(
|
||||
(msg) => tokenCount(msg.content) + 4
|
||||
).reduce((a2, b2) => a2 + b2, 0);
|
||||
const MAX_REQUEST_TOKENS = DEFAULT_MODEL_TOKEN_LIMIT - ADJUSTMENT_FACTOR - INIT_MESSAGES_PROMPT_LENGTH - config6?.OCO_OPENAI_MAX_TOKENS;
|
||||
const MAX_REQUEST_TOKENS = MAX_TOKENS_INPUT2 - ADJUSTMENT_FACTOR - INIT_MESSAGES_PROMPT_LENGTH - MAX_TOKENS_OUTPUT2;
|
||||
if (tokenCount(diff) >= MAX_REQUEST_TOKENS) {
|
||||
const commitMessagePromises = await getCommitMsgsPromisesFromFileDiffs(
|
||||
diff,
|
||||
MAX_REQUEST_TOKENS
|
||||
MAX_REQUEST_TOKENS,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
const commitMessages = [];
|
||||
for (const promise of commitMessagePromises) {
|
||||
@@ -27651,7 +27694,7 @@ var generateCommitMessageByDiff = async (diff) => {
|
||||
}
|
||||
return commitMessages.join("\n\n");
|
||||
}
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(diff);
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(diff, fullGitMojiSpec);
|
||||
const engine = getEngine();
|
||||
const commitMessage = await engine.generateCommitMessage(messages);
|
||||
if (!commitMessage)
|
||||
@@ -27661,7 +27704,7 @@ var generateCommitMessageByDiff = async (diff) => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
function getMessagesPromisesByChangesInFile(fileDiff, separator, maxChangeLength) {
|
||||
function getMessagesPromisesByChangesInFile(fileDiff, separator, maxChangeLength, fullGitMojiSpec) {
|
||||
const hunkHeaderSeparator = "@@ ";
|
||||
const [fileHeader, ...fileDiffByLines] = fileDiff.split(hunkHeaderSeparator);
|
||||
const mergedChanges = mergeDiffs(
|
||||
@@ -27682,7 +27725,8 @@ function getMessagesPromisesByChangesInFile(fileDiff, separator, maxChangeLength
|
||||
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map(
|
||||
async (lineDiff) => {
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(
|
||||
separator + lineDiff
|
||||
separator + lineDiff,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
return engine.generateCommitMessage(messages);
|
||||
}
|
||||
@@ -27693,6 +27737,9 @@ function splitDiff(diff, maxChangeLength) {
|
||||
const lines = diff.split("\n");
|
||||
const splitDiffs = [];
|
||||
let currentDiff = "";
|
||||
if (maxChangeLength <= 0) {
|
||||
throw new Error(GenerateCommitMessageErrorEnum.outputTokensTooHigh);
|
||||
}
|
||||
for (let line of lines) {
|
||||
while (tokenCount(line) > maxChangeLength) {
|
||||
const subLine = line.substring(0, maxChangeLength);
|
||||
@@ -27711,7 +27758,7 @@ function splitDiff(diff, maxChangeLength) {
|
||||
}
|
||||
return splitDiffs;
|
||||
}
|
||||
var getCommitMsgsPromisesFromFileDiffs = async (diff, maxDiffLength) => {
|
||||
var getCommitMsgsPromisesFromFileDiffs = async (diff, maxDiffLength, fullGitMojiSpec) => {
|
||||
const separator = "diff --git ";
|
||||
const diffByFiles = diff.split(separator).slice(1);
|
||||
const mergedFilesDiffs = mergeDiffs(diffByFiles, maxDiffLength);
|
||||
@@ -27721,12 +27768,14 @@ var getCommitMsgsPromisesFromFileDiffs = async (diff, maxDiffLength) => {
|
||||
const messagesPromises = getMessagesPromisesByChangesInFile(
|
||||
fileDiff,
|
||||
separator,
|
||||
maxDiffLength
|
||||
maxDiffLength,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
commitMessagePromises.push(...messagesPromises);
|
||||
} else {
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(
|
||||
separator + fileDiff
|
||||
separator + fileDiff,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
const engine = getEngine();
|
||||
commitMessagePromises.push(engine.generateCommitMessage(messages));
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "opencommit",
|
||||
"version": "3.0.5",
|
||||
"version": "3.0.11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "opencommit",
|
||||
"version": "3.0.5",
|
||||
"version": "3.0.11",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "opencommit",
|
||||
"version": "3.0.5",
|
||||
"version": "3.0.11",
|
||||
"description": "Auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
|
||||
"keywords": [
|
||||
"git",
|
||||
|
||||
@@ -17,17 +17,19 @@ cli(
|
||||
version: packageJSON.version,
|
||||
name: 'opencommit',
|
||||
commands: [configCommand, hookCommand, commitlintConfigCommand],
|
||||
flags: {},
|
||||
flags: {
|
||||
fgm: Boolean
|
||||
},
|
||||
ignoreArgv: (type) => type === 'unknown-flag' || type === 'argument',
|
||||
help: { description: packageJSON.description }
|
||||
},
|
||||
async () => {
|
||||
async ({ flags }) => {
|
||||
await checkIsLatestVersion();
|
||||
|
||||
if (await isHookCalled()) {
|
||||
prepareCommitMessageHook();
|
||||
} else {
|
||||
commit(extraArgs);
|
||||
commit(extraArgs, flags.fgm);
|
||||
}
|
||||
},
|
||||
extraArgs
|
||||
|
||||
@@ -40,20 +40,27 @@ const checkMessageTemplate = (extraArgs: string[]): string | false => {
|
||||
|
||||
const generateCommitMessageFromGitDiff = async (
|
||||
diff: string,
|
||||
extraArgs: string[]
|
||||
extraArgs: string[],
|
||||
fullGitMojiSpec: boolean
|
||||
): Promise<void> => {
|
||||
await assertGitRepo();
|
||||
const commitSpinner = spinner();
|
||||
commitSpinner.start('Generating the commit message');
|
||||
|
||||
try {
|
||||
let commitMessage = await generateCommitMessageByDiff(diff);
|
||||
let commitMessage = await generateCommitMessageByDiff(
|
||||
diff,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
|
||||
const messageTemplate = checkMessageTemplate(extraArgs);
|
||||
if (
|
||||
config?.OCO_MESSAGE_TEMPLATE_PLACEHOLDER &&
|
||||
typeof messageTemplate === 'string'
|
||||
) {
|
||||
const messageTemplateIndex = extraArgs.indexOf(messageTemplate);
|
||||
extraArgs.splice(messageTemplateIndex, 1);
|
||||
|
||||
commitMessage = messageTemplate.replace(
|
||||
config?.OCO_MESSAGE_TEMPLATE_PLACEHOLDER,
|
||||
commitMessage
|
||||
@@ -154,7 +161,8 @@ ${chalk.grey('——————————————————')}`
|
||||
|
||||
export async function commit(
|
||||
extraArgs: string[] = [],
|
||||
isStageAllFlag: Boolean = false
|
||||
isStageAllFlag: Boolean = false,
|
||||
fullGitMojiSpec: boolean = false
|
||||
) {
|
||||
if (isStageAllFlag) {
|
||||
const changedFiles = await getChangedFiles();
|
||||
@@ -194,7 +202,7 @@ export async function commit(
|
||||
isStageAllAndCommitConfirmedByUser &&
|
||||
!isCancel(isStageAllAndCommitConfirmedByUser)
|
||||
) {
|
||||
await commit(extraArgs, true);
|
||||
await commit(extraArgs, true, fullGitMojiSpec);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -212,7 +220,7 @@ export async function commit(
|
||||
await gitAdd({ files });
|
||||
}
|
||||
|
||||
await commit(extraArgs, false);
|
||||
await commit(extraArgs, false, fullGitMojiSpec);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -225,7 +233,8 @@ export async function commit(
|
||||
const [, generateCommitError] = await trytm(
|
||||
generateCommitMessageFromGitDiff(
|
||||
await getDiff({ files: stagedFiles }),
|
||||
extraArgs
|
||||
extraArgs,
|
||||
fullGitMojiSpec
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ dotenv.config();
|
||||
|
||||
export enum CONFIG_KEYS {
|
||||
OCO_OPENAI_API_KEY = 'OCO_OPENAI_API_KEY',
|
||||
OCO_OPENAI_MAX_TOKENS = 'OCO_OPENAI_MAX_TOKENS',
|
||||
OCO_TOKENS_MAX_INPUT = 'OCO_TOKENS_MAX_INPUT',
|
||||
OCO_TOKENS_MAX_OUTPUT = 'OCO_TOKENS_MAX_OUTPUT',
|
||||
OCO_OPENAI_BASE_PATH = 'OCO_OPENAI_BASE_PATH',
|
||||
OCO_DESCRIPTION = 'OCO_DESCRIPTION',
|
||||
OCO_EMOJI = 'OCO_EMOJI',
|
||||
@@ -26,13 +27,16 @@ export enum CONFIG_KEYS {
|
||||
OCO_AI_PROVIDER = 'OCO_AI_PROVIDER',
|
||||
}
|
||||
|
||||
export const DEFAULT_MODEL_TOKEN_LIMIT = 4096;
|
||||
|
||||
export enum CONFIG_MODES {
|
||||
get = 'get',
|
||||
set = 'set'
|
||||
}
|
||||
|
||||
export enum DEFAULT_TOKEN_LIMITS {
|
||||
DEFAULT_MAX_TOKENS_INPUT = 4096,
|
||||
DEFAULT_MAX_TOKENS_OUTPUT = 500
|
||||
}
|
||||
|
||||
const validateConfig = (
|
||||
key: string,
|
||||
condition: any,
|
||||
@@ -75,18 +79,37 @@ export const configValidators = {
|
||||
return value;
|
||||
},
|
||||
|
||||
[CONFIG_KEYS.OCO_OPENAI_MAX_TOKENS](value: any) {
|
||||
[CONFIG_KEYS.OCO_TOKENS_MAX_INPUT](value: any) {
|
||||
// If the value is a string, convert it to a number.
|
||||
if (typeof value === 'string') {
|
||||
value = parseInt(value);
|
||||
validateConfig(
|
||||
CONFIG_KEYS.OCO_OPENAI_MAX_TOKENS,
|
||||
CONFIG_KEYS.OCO_TOKENS_MAX_INPUT,
|
||||
!isNaN(value),
|
||||
'Must be a number'
|
||||
);
|
||||
}
|
||||
validateConfig(
|
||||
CONFIG_KEYS.OCO_OPENAI_MAX_TOKENS,
|
||||
CONFIG_KEYS.OCO_TOKENS_MAX_INPUT,
|
||||
value ? typeof value === 'number' : undefined,
|
||||
'Must be a number'
|
||||
);
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
[CONFIG_KEYS.OCO_TOKENS_MAX_OUTPUT](value: any) {
|
||||
// If the value is a string, convert it to a number.
|
||||
if (typeof value === 'string') {
|
||||
value = parseInt(value);
|
||||
validateConfig(
|
||||
CONFIG_KEYS.OCO_TOKENS_MAX_OUTPUT,
|
||||
!isNaN(value),
|
||||
'Must be a number'
|
||||
);
|
||||
}
|
||||
validateConfig(
|
||||
CONFIG_KEYS.OCO_TOKENS_MAX_OUTPUT,
|
||||
value ? typeof value === 'number' : undefined,
|
||||
'Must be a number'
|
||||
);
|
||||
@@ -129,9 +152,10 @@ export const configValidators = {
|
||||
'gpt-3.5-turbo',
|
||||
'gpt-4',
|
||||
'gpt-3.5-turbo-16k',
|
||||
'gpt-3.5-turbo-0613'
|
||||
'gpt-3.5-turbo-0613',
|
||||
'gpt-4-1106-preview'
|
||||
].includes(value),
|
||||
`${value} is not supported yet, use 'gpt-4', 'gpt-3.5-turbo-16k' (default), 'gpt-3.5-turbo-0613' or 'gpt-3.5-turbo'`
|
||||
`${value} is not supported yet, use 'gpt-4', 'gpt-3.5-turbo-16k' (default), 'gpt-3.5-turbo-0613', 'gpt-3.5-turbo' or 'gpt-4-1106-preview'`
|
||||
);
|
||||
return value;
|
||||
},
|
||||
@@ -177,8 +201,11 @@ const configPath = pathJoin(homedir(), '.opencommit');
|
||||
export const getConfig = (): ConfigType | null => {
|
||||
const configFromEnv = {
|
||||
OCO_OPENAI_API_KEY: process.env.OCO_OPENAI_API_KEY,
|
||||
OCO_OPENAI_MAX_TOKENS: process.env.OCO_OPENAI_MAX_TOKENS
|
||||
? Number(process.env.OCO_OPENAI_MAX_TOKENS)
|
||||
OCO_TOKENS_MAX_INPUT: process.env.OCO_TOKENS_MAX_INPUT
|
||||
? Number(process.env.OCO_TOKENS_MAX_INPUT)
|
||||
: undefined,
|
||||
OCO_TOKENS_MAX_OUTPUT: process.env.OCO_TOKENS_MAX_OUTPUT
|
||||
? Number(process.env.OCO_TOKENS_MAX_OUTPUT)
|
||||
: undefined,
|
||||
OCO_OPENAI_BASE_PATH: process.env.OCO_OPENAI_BASE_PATH,
|
||||
OCO_DESCRIPTION: process.env.OCO_DESCRIPTION === 'true' ? true : false,
|
||||
|
||||
@@ -94,7 +94,7 @@ export const hookCommand = command(
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Unsupported mode: ${mode}. Supported modes are: 'set' or 'unset'`
|
||||
`Unsupported mode: ${mode}. Supported modes are: 'set' or 'unset', do: \`oco hook set\``
|
||||
);
|
||||
} catch (error) {
|
||||
outro(`${chalk.red('✖')} ${error}`);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { intro, outro } from '@clack/prompts';
|
||||
|
||||
import {
|
||||
CONFIG_MODES,
|
||||
DEFAULT_MODEL_TOKEN_LIMIT,
|
||||
DEFAULT_TOKEN_LIMITS,
|
||||
getConfig
|
||||
} from '../commands/config';
|
||||
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
|
||||
@@ -20,7 +20,8 @@ import { AiEngine } from './Engine';
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
let maxTokens = config?.OCO_OPENAI_MAX_TOKENS;
|
||||
const MAX_TOKENS_OUTPUT = config?.OCO_TOKENS_MAX_OUTPUT || DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT;
|
||||
const MAX_TOKENS_INPUT = config?.OCO_TOKENS_MAX_INPUT || DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_INPUT;
|
||||
let basePath = config?.OCO_OPENAI_BASE_PATH;
|
||||
let apiKey = config?.OCO_OPENAI_API_KEY
|
||||
|
||||
@@ -65,14 +66,14 @@ class OpenAi implements AiEngine {
|
||||
messages,
|
||||
temperature: 0,
|
||||
top_p: 0.1,
|
||||
max_tokens: maxTokens || 500
|
||||
max_tokens: MAX_TOKENS_OUTPUT
|
||||
};
|
||||
try {
|
||||
const REQUEST_TOKENS = messages
|
||||
.map((msg) => tokenCount(msg.content) + 4)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
|
||||
if (REQUEST_TOKENS > DEFAULT_MODEL_TOKEN_LIMIT - maxTokens) {
|
||||
if (REQUEST_TOKENS > MAX_TOKENS_INPUT - MAX_TOKENS_OUTPUT) {
|
||||
throw new Error(GenerateCommitMessageErrorEnum.tooMuchTokens);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,18 +3,21 @@ import {
|
||||
ChatCompletionRequestMessageRoleEnum
|
||||
} from 'openai';
|
||||
|
||||
import { DEFAULT_MODEL_TOKEN_LIMIT, getConfig } from './commands/config';
|
||||
import { DEFAULT_TOKEN_LIMITS, getConfig } from './commands/config';
|
||||
import { getMainCommitPrompt } from './prompts';
|
||||
import { mergeDiffs } from './utils/mergeDiffs';
|
||||
import { tokenCount } from './utils/tokenCount';
|
||||
import { getEngine } from './utils/engine';
|
||||
|
||||
const config = getConfig();
|
||||
const MAX_TOKENS_INPUT = config?.OCO_TOKENS_MAX_INPUT || DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_INPUT;
|
||||
const MAX_TOKENS_OUTPUT = config?.OCO_TOKENS_MAX_OUTPUT || DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT;
|
||||
|
||||
const generateCommitMessageChatCompletionPrompt = async (
|
||||
diff: string
|
||||
diff: string,
|
||||
fullGitMojiSpec: boolean
|
||||
): Promise<Array<ChatCompletionRequestMessage>> => {
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt();
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt(fullGitMojiSpec);
|
||||
|
||||
const chatContextAsCompletionRequest = [...INIT_MESSAGES_PROMPT];
|
||||
|
||||
@@ -29,31 +32,34 @@ const generateCommitMessageChatCompletionPrompt = async (
|
||||
export enum GenerateCommitMessageErrorEnum {
|
||||
tooMuchTokens = 'TOO_MUCH_TOKENS',
|
||||
internalError = 'INTERNAL_ERROR',
|
||||
emptyMessage = 'EMPTY_MESSAGE'
|
||||
emptyMessage = 'EMPTY_MESSAGE',
|
||||
outputTokensTooHigh = `Token limit exceeded, OCO_TOKENS_MAX_OUTPUT must not be much higher than the default ${DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT} tokens.`
|
||||
}
|
||||
|
||||
const ADJUSTMENT_FACTOR = 20;
|
||||
|
||||
export const generateCommitMessageByDiff = async (
|
||||
diff: string
|
||||
diff: string,
|
||||
fullGitMojiSpec: boolean
|
||||
): Promise<string> => {
|
||||
try {
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt();
|
||||
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt(fullGitMojiSpec);
|
||||
|
||||
const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map(
|
||||
(msg) => tokenCount(msg.content) + 4
|
||||
).reduce((a, b) => a + b, 0);
|
||||
|
||||
const MAX_REQUEST_TOKENS =
|
||||
DEFAULT_MODEL_TOKEN_LIMIT -
|
||||
MAX_TOKENS_INPUT -
|
||||
ADJUSTMENT_FACTOR -
|
||||
INIT_MESSAGES_PROMPT_LENGTH -
|
||||
config?.OCO_OPENAI_MAX_TOKENS;
|
||||
MAX_TOKENS_OUTPUT;
|
||||
|
||||
if (tokenCount(diff) >= MAX_REQUEST_TOKENS) {
|
||||
const commitMessagePromises = await getCommitMsgsPromisesFromFileDiffs(
|
||||
diff,
|
||||
MAX_REQUEST_TOKENS
|
||||
MAX_REQUEST_TOKENS,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
|
||||
const commitMessages = [];
|
||||
@@ -65,7 +71,7 @@ export const generateCommitMessageByDiff = async (
|
||||
return commitMessages.join('\n\n');
|
||||
}
|
||||
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(diff);
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(diff, fullGitMojiSpec);
|
||||
|
||||
const engine = getEngine()
|
||||
const commitMessage = await engine.generateCommitMessage(messages);
|
||||
@@ -82,7 +88,8 @@ export const generateCommitMessageByDiff = async (
|
||||
function getMessagesPromisesByChangesInFile(
|
||||
fileDiff: string,
|
||||
separator: string,
|
||||
maxChangeLength: number
|
||||
maxChangeLength: number,
|
||||
fullGitMojiSpec: boolean
|
||||
) {
|
||||
const hunkHeaderSeparator = '@@ ';
|
||||
const [fileHeader, ...fileDiffByLines] = fileDiff.split(hunkHeaderSeparator);
|
||||
@@ -109,7 +116,8 @@ function getMessagesPromisesByChangesInFile(
|
||||
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map(
|
||||
async (lineDiff) => {
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(
|
||||
separator + lineDiff
|
||||
separator + lineDiff,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
|
||||
return engine.generateCommitMessage(messages);
|
||||
@@ -124,6 +132,10 @@ function splitDiff(diff: string, maxChangeLength: number) {
|
||||
const splitDiffs = [];
|
||||
let currentDiff = '';
|
||||
|
||||
if (maxChangeLength <= 0) {
|
||||
throw new Error(GenerateCommitMessageErrorEnum.outputTokensTooHigh);
|
||||
}
|
||||
|
||||
for (let line of lines) {
|
||||
// If a single line exceeds maxChangeLength, split it into multiple lines
|
||||
while (tokenCount(line) > maxChangeLength) {
|
||||
@@ -153,7 +165,8 @@ function splitDiff(diff: string, maxChangeLength: number) {
|
||||
|
||||
export const getCommitMsgsPromisesFromFileDiffs = async (
|
||||
diff: string,
|
||||
maxDiffLength: number
|
||||
maxDiffLength: number,
|
||||
fullGitMojiSpec: boolean
|
||||
) => {
|
||||
const separator = 'diff --git ';
|
||||
|
||||
@@ -170,13 +183,15 @@ export const getCommitMsgsPromisesFromFileDiffs = async (
|
||||
const messagesPromises = getMessagesPromisesByChangesInFile(
|
||||
fileDiff,
|
||||
separator,
|
||||
maxDiffLength
|
||||
maxDiffLength,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
|
||||
commitMessagePromises.push(...messagesPromises);
|
||||
} else {
|
||||
const messages = await generateCommitMessageChatCompletionPrompt(
|
||||
separator + fileDiff
|
||||
separator + fileDiff,
|
||||
fullGitMojiSpec
|
||||
);
|
||||
|
||||
const engine = getEngine()
|
||||
|
||||
@@ -61,6 +61,10 @@ export const configureCommitlintIntegration = async (force = false) => {
|
||||
|
||||
// Cleanup the consistency answer. Sometimes 'gpt-3.5-turbo' sends rule's back.
|
||||
prompts.forEach((prompt) => (consistency = consistency.replace(prompt, '')));
|
||||
|
||||
// sometimes consistency is preceded by explanatory text like "Here is your JSON:"
|
||||
consistency = utils.getJSONBlock(consistency);
|
||||
|
||||
// ... remaining might be extra set of "\n"
|
||||
consistency = utils.removeDoubleNewlines(consistency);
|
||||
|
||||
|
||||
@@ -16,6 +16,16 @@ export const removeDoubleNewlines = (input: string): string => {
|
||||
return input;
|
||||
};
|
||||
|
||||
export const getJSONBlock = (input: string): string => {
|
||||
const jsonIndex = input.search('```json');
|
||||
if(jsonIndex > -1) {
|
||||
input = input.slice(jsonIndex + 8);
|
||||
const endJsonIndex = consistency.search('```');
|
||||
input = input.slice(0, endJsonIndex);
|
||||
}
|
||||
return input;
|
||||
};
|
||||
|
||||
export const commitlintLLMConfigExists = async (): Promise<boolean> => {
|
||||
let exists;
|
||||
try {
|
||||
@@ -44,4 +54,4 @@ export const getCommitlintLLMConfig =
|
||||
content.toString()
|
||||
) as CommitlintLLMConfig;
|
||||
return commitLintLLMConfig;
|
||||
};
|
||||
};
|
||||
|
||||
118
src/prompts.ts
118
src/prompts.ts
@@ -11,6 +11,7 @@ import { configureCommitlintIntegration } from './modules/commitlint/config';
|
||||
import { commitlintPrompts } from './modules/commitlint/prompts';
|
||||
import { ConsistencyPrompt } from './modules/commitlint/types';
|
||||
import * as utils from './modules/commitlint/utils';
|
||||
import { removeConventionalCommitWord } from './utils/removeConventionalCommitWord';
|
||||
|
||||
const config = getConfig();
|
||||
const translation = i18n[(config?.OCO_LANGUAGE as I18nLocals) || 'en'];
|
||||
@@ -18,14 +19,97 @@ const translation = i18n[(config?.OCO_LANGUAGE as I18nLocals) || 'en'];
|
||||
export const IDENTITY =
|
||||
'You are to act as the author of a commit message in git.';
|
||||
|
||||
const INIT_MAIN_PROMPT = (language: string): ChatCompletionRequestMessage => ({
|
||||
const INIT_MAIN_PROMPT = (
|
||||
language: string,
|
||||
fullGitMojiSpec: boolean
|
||||
): ChatCompletionRequestMessage => ({
|
||||
role: ChatCompletionRequestMessageRoleEnum.System,
|
||||
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.
|
||||
${
|
||||
config?.OCO_EMOJI
|
||||
? 'Use GitMoji convention to preface the commit.'
|
||||
: 'Do not preface the commit with anything.'
|
||||
}
|
||||
content: `${IDENTITY} Your mission is to create clean and comprehensive commit messages as per the ${
|
||||
fullGitMojiSpec ? 'GitMoji specification' : '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.
|
||||
${
|
||||
config?.OCO_EMOJI
|
||||
? 'Use GitMoji convention to preface the commit. Here are some help to choose the right emoji (emoji, description): ' +
|
||||
'🐛, Fix a bug; ' +
|
||||
'✨, Introduce new features; ' +
|
||||
'📝, Add or update documentation; ' +
|
||||
'🚀, Deploy stuff; ' +
|
||||
'✅, Add, update, or pass tests; ' +
|
||||
'♻️, Refactor code; ' +
|
||||
'⬆️, Upgrade dependencies; ' +
|
||||
'🔧, Add or update configuration files; ' +
|
||||
'🌐, Internationalization and localization; ' +
|
||||
'💡, Add or update comments in source code; ' +
|
||||
`${
|
||||
fullGitMojiSpec
|
||||
? '🎨, Improve structure / format of the code; ' +
|
||||
'⚡️, Improve performance; ' +
|
||||
'🔥, Remove code or files; ' +
|
||||
'🚑️, Critical hotfix; ' +
|
||||
'💄, Add or update the UI and style files; ' +
|
||||
'🎉, Begin a project; ' +
|
||||
'🔒️, Fix security issues; ' +
|
||||
'🔐, Add or update secrets; ' +
|
||||
'🔖, Release / Version tags; ' +
|
||||
'🚨, Fix compiler / linter warnings; ' +
|
||||
'🚧, Work in progress; ' +
|
||||
'💚, Fix CI Build; ' +
|
||||
'⬇️, Downgrade dependencies; ' +
|
||||
'📌, Pin dependencies to specific versions; ' +
|
||||
'👷, Add or update CI build system; ' +
|
||||
'📈, Add or update analytics or track code; ' +
|
||||
'➕, Add a dependency; ' +
|
||||
'➖, Remove a dependency; ' +
|
||||
'🔨, Add or update development scripts; ' +
|
||||
'✏️, Fix typos; ' +
|
||||
'💩, Write bad code that needs to be improved; ' +
|
||||
'⏪️, Revert changes; ' +
|
||||
'🔀, Merge branches; ' +
|
||||
'📦️, Add or update compiled files or packages; ' +
|
||||
'👽️, Update code due to external API changes; ' +
|
||||
'🚚, Move or rename resources (e.g.: files, paths, routes); ' +
|
||||
'📄, Add or update license; ' +
|
||||
'💥, Introduce breaking changes; ' +
|
||||
'🍱, Add or update assets; ' +
|
||||
'♿️, Improve accessibility; ' +
|
||||
'🍻, Write code drunkenly; ' +
|
||||
'💬, Add or update text and literals; ' +
|
||||
'🗃️, Perform database related changes; ' +
|
||||
'🔊, Add or update logs; ' +
|
||||
'🔇, Remove logs; ' +
|
||||
'👥, Add or update contributor(s); ' +
|
||||
'🚸, Improve user experience / usability; ' +
|
||||
'🏗️, Make architectural changes; ' +
|
||||
'📱, Work on responsive design; ' +
|
||||
'🤡, Mock things; ' +
|
||||
'🥚, Add or update an easter egg; ' +
|
||||
'🙈, Add or update a .gitignore file; ' +
|
||||
'📸, Add or update snapshots; ' +
|
||||
'⚗️, Perform experiments; ' +
|
||||
'🔍️, Improve SEO; ' +
|
||||
'🏷️, Add or update types; ' +
|
||||
'🌱, Add or update seed files; ' +
|
||||
'🚩, Add, update, or remove feature flags; ' +
|
||||
'🥅, Catch errors; ' +
|
||||
'💫, Add or update animations and transitions; ' +
|
||||
'🗑️, Deprecate code that needs to be cleaned up; ' +
|
||||
'🛂, Work on code related to authorization, roles and permissions; ' +
|
||||
'🩹, Simple fix for a non-critical issue; ' +
|
||||
'🧐, Data exploration/inspection; ' +
|
||||
'⚰️, Remove dead code; ' +
|
||||
'🧪, Add a failing test; ' +
|
||||
'👔, Add or update business logic; ' +
|
||||
'🩺, Add or update healthcheck; ' +
|
||||
'🧱, Infrastructure related changes; ' +
|
||||
'🧑💻, Improve developer experience; ' +
|
||||
'💸, Add sponsorships or money related infrastructure; ' +
|
||||
'🧵, Add or update code related to multithreading or concurrency; ' +
|
||||
'🦺, Add or update code related to validation.'
|
||||
: ''
|
||||
}`
|
||||
: 'Do not preface the commit with anything. Conventional commit keywords:' +
|
||||
'fix, feat, build, chore, ci, docs, style, refactor, perf, test.'
|
||||
}
|
||||
${
|
||||
config?.OCO_DESCRIPTION
|
||||
? '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 = (
|
||||
translation: ConsistencyPrompt
|
||||
): ChatCompletionRequestMessage => ({
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
content: `${config?.OCO_EMOJI ? '🐛 ' : ''}${translation.commitFix}
|
||||
${config?.OCO_EMOJI ? '✨ ' : ''}${translation.commitFeat}
|
||||
content: `${
|
||||
config?.OCO_EMOJI
|
||||
? `🐛 ${removeConventionalCommitWord(translation.commitFix)}`
|
||||
: translation.commitFix
|
||||
}
|
||||
${
|
||||
config?.OCO_EMOJI
|
||||
? `✨ ${removeConventionalCommitWord(translation.commitFeat)}`
|
||||
: translation.commitFeat
|
||||
}
|
||||
${config?.OCO_DESCRIPTION ? translation.commitDescription : ''}`
|
||||
});
|
||||
|
||||
export const getMainCommitPrompt = async (): Promise<
|
||||
ChatCompletionRequestMessage[]
|
||||
> => {
|
||||
export const getMainCommitPrompt = async (
|
||||
fullGitMojiSpec: boolean
|
||||
): Promise<ChatCompletionRequestMessage[]> => {
|
||||
switch (config?.OCO_PROMPT_MODULE) {
|
||||
case '@commitlint':
|
||||
if (!(await utils.commitlintLLMConfigExists())) {
|
||||
@@ -102,7 +194,7 @@ export const getMainCommitPrompt = async (): Promise<
|
||||
default:
|
||||
// conventional-commit
|
||||
return [
|
||||
INIT_MAIN_PROMPT(translation.localLanguage),
|
||||
INIT_MAIN_PROMPT(translation.localLanguage, fullGitMojiSpec),
|
||||
INIT_DIFF_PROMPT,
|
||||
INIT_CONSISTENCY_PROMPT(translation)
|
||||
];
|
||||
|
||||
3
src/utils/removeConventionalCommitWord.ts
Normal file
3
src/utils/removeConventionalCommitWord.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function removeConventionalCommitWord(message: string): string {
|
||||
return message.replace(/^(fix|feat)\((.+?)\):/, '($2):');
|
||||
}
|
||||
Reference in New Issue
Block a user