nyegosh
Version:
AI generated commit messages, like Njegoš wrote them.
147 lines (129 loc) • 4.67 kB
JavaScript
require("colors");
const simpleGit = require("simple-git");
const { exec } = require("child_process");
const { promisify } = require("util");
const path = require("path");
const dotenv = require("dotenv");
const { conventionalCommitsDoc } = require("./conventional-commits");
const { initializeModelConnection } = require("./auth");
const { writeToGitCommitEditmsg } = require("./commit-editmsg");
const { promptUser } = require("./lib/readline");
const envPath = path.join(require("os").homedir(), ".nyegosh.env");
dotenv.config({ path: envPath });
const aiClient = initializeModelConnection();
const gitHandler = simpleGit();
const execAsync = promisify(exec);
var messages = [
{
role: "system",
content: `You are a helpful coding assistant for generating meaningful commit messages
no longer than 300 characters based on the user provided code diff.`,
},
{
role: "system",
content: `Specification for generating commit message: ${conventionalCommitsDoc}.`,
},
];
async function getStagedDiff() {
try {
const diff = await gitHandler.diff(["--staged"]);
if (!diff.trim()) {
console.log("No staged changes to commit.".red);
process.exit(1);
}
return diff;
} catch (error) {
console.error("Error getting staged diff:", error.message.red);
throw error;
}
}
function getPrompt(diff) {
const prompt = `
Given the following git diff generate a single commit message:
\n\n ${diff}
`;
return prompt;
}
async function generateCommitMessage(refinementPrompt = undefined) {
if (refinementPrompt) {
messages.push(refinementPrompt);
}
try {
const response = await aiClient.chat.completions.create({
model: process.env.NYEGOSH_GENERATOR_DEPLOYMENT,
stream: false,
messages: messages,
});
const generatedCommitMessage = response.choices?.[0]?.message?.content?.trim();
if (!generatedCommitMessage) {
throw new Error("AI did not generate a commit message.");
}
messages.push({ role: "assistant", content: generatedCommitMessage });
return generatedCommitMessage;
} catch (error) {
// TODO: Handle token limit exceeded
console.error("Error generating commit message:", error.message.red);
throw error;
}
}
async function writeCommitMessage(message) {
console.log("AI-Suggested Commit Message:".blue);
console.log(`\n${message}\n`);
writeToGitCommitEditmsg(message);
const userResponse = await promptUser(
"Do you want to (c)ommit suggested message, (w)rite your own, (r)efine the current message or (a)bort? (c/w/r/a): "
.blue,
);
switch (userResponse.toLowerCase()) {
case "c":
await commitChanges(message);
break;
case "w":
const customMessage = await promptUser("\nEnter your commit message:\n".cyan);
if (customMessage.trim()) {
await commitChanges(customMessage);
} else {
console.log("Commit message is empty. Aborting.".red);
}
break;
case "a":
writeToGitCommitEditmsg("");
console.log("Commit aborted.".yellow);
break;
case "r":
let userPrompt = await promptUser("\nHow can I make the message better?:\n".cyan);
let refinementPrompt = { role: "user", content: userPrompt };
let refinedMessage = await generateCommitMessage(refinementPrompt);
writeCommitMessage(refinedMessage);
break;
default:
writeToGitCommitEditmsg("");
console.log("Invalid option. Commit aborted.".red);
}
}
async function commitChanges(message) {
try {
const escapedMessage = message.replace(/`/g, "\\`");
await execAsync(`git commit -m "${escapedMessage}"`);
console.info("Changes committed successfully.".green);
} catch (error) {
console.error("Error committing changes:", error.message.red);
throw error;
}
}
(async () => {
try {
const diff = await getStagedDiff();
if (!diff) {
console.log("No staged changes to commit.".yellow);
process.exit(1);
}
const prompt = getPrompt(diff);
messages.push({ role: "user", content: prompt });
const commitMessage = await generateCommitMessage();
writeCommitMessage(commitMessage);
} catch (error) {
process.exit(1);
}
})();