# Git Commit Message

> Generate a commit message for all staged changes

The `gcm` script provides a guided flow to create commits with generated messages. It starts by generating a commit message based on the staged changes in your Git repository using an [inline prompt](/genaiscript/reference/scripts/inline-prompts), and then asks the user to commit the changes or regenerate the message.

```text
compute diff
loop
   generate commit message
   ask user to commit, edit message or regenerate
   if user says commit
       git commit and push
```

## Configuration

[Section titled “Configuration”](#configuration)

First, we define the `script` function, which sets up our GenAI script by providing a title and a description, and specifying the model we’ll be using:

```ts
script({
    title: "git commit message",
    description: "Generate a commit message for all staged changes",
    model: "openai:gpt-4o",
})
```

## Look for changes

[Section titled “Look for changes”](#look-for-changes)

Next, we check for any staged changes in your Git repository using `git diff`. If there is nothing staged, GenAI kindly offers to stage all changes for you:

```ts
// Check for staged changes and stage all changes if none are staged
const diff = await git.diff({
    staged: true,
    askStageOnEmpty: true,
})
if (!diff) cancel("no staged changes")
```

We then log the diff to the console so you can review what changes are about to be committed:

```ts
console.log(diff.stdout)
```

## Generate and refine commit message

[Section titled “Generate and refine commit message”](#generate-and-refine-commit-message)

Here comes the interesting part. We enter a loop where GenAI will generate a commit message for you based on the diff. If you’re not satisfied with the message, you can choose to edit it, accept it, or regenerate it:

```ts
let choice
let message
do {
    // generate a conventional commit message (https://www.conventionalcommits.org/en/v1.0.0/)
    const res = await runPrompt((_) => {
        _.def("GIT_DIFF", diff, { maxTokens: 20000, language: "diff" })
        _.$`Generate a git conventional commit message for the changes in GIT_DIFF.
        - do NOT add quotes
        - maximum 50 characters
        - use gitmojis`
    })
    // ... handle response and user choices
} while (choice !== "commit")
```

## Commit and push

[Section titled “Commit and push”](#commit-and-push)

If you choose to commit, GenAI runs the `git commit` command with your message, and if you’re feeling super confident, it can even push the changes to your repository right after:

```ts
if (choice === "commit" && message) {
    console.log(
        (await host.exec("git", ["commit", "-m", message, "-n"])).stdout
    )
    if (await host.confirm("Push changes?", { default: true }))
        console.log((await host.exec("git push")).stdout)
}
```

## Running the Script with GenAIScript CLI

[Section titled “Running the Script with GenAIScript CLI”](#running-the-script-with-genaiscript-cli)

Use the [cli](/genaiscript/reference/cli) to run the script:

```shell
npx genaiscript run gcm
```

## Full source ([GitHub](https://github.com/microsoft/genaiscript/blob/main/samples/sample/genaisrc/samples/gcm.genai.mts))

[Section titled “Full source (GitHub)”](#full-source-github)

gcm.genai.mts

```ts
/**
 * Script to automate the git commit process with AI-generated commit messages.
 * It checks for staged changes, generates a commit message, and prompts the user to review or edit the message before committing.
 */


script({
  title: "git commit message",
  description: "Generate a commit message for all staged changes",
  unlisted: true,
  parameters: {
    chunkSize: {
      type: "number",
      default: 10000,
      description: "Maximum number of tokens per chunk",
    },
    maxChunks: {
      type: "number",
      default: 4,
      description:
        "Safeguard against huge commits. Asks confirmation to the user before running more than maxChunks chunks",
    },
    gitmoji: {
      type: "boolean",
      default: true,
      description: "Use gitmoji in the commit message",
    },
  },
});
const { chunkSize, maxChunks, gitmoji } = env.vars;


// Check for staged changes and stage all changes if none are staged
const diff = await git.diff({
  staged: true,
  askStageOnEmpty: true,
  ignoreSpaceChange: true,
});


// If no staged changes are found, cancel the script with a message
if (!diff) cancel("no staged changes");


// Display the diff of staged changes in the console
console.debug(diff);


await git.pull();


// chunk if case of massive diff
const chunks = await tokenizers.chunk(diff, { chunkSize });
if (chunks.length > 1) {
  console.log(`staged changes chunked into ${chunks.length} parts`);
  if (chunks.length > maxChunks) {
    const res = await host.confirm(
      `This is a big diff with ${chunks.length} chunks, do you want to proceed?`,
    );
    if (!res) cancel("user cancelled");
  }
}


const gitPush = async () => {
  if (await host.confirm("Push changes?", { default: true })) console.log(await git.exec("push"));
};


const addInstructions = (ctx) => {
  ctx.$`


<type>: <description>
<body>


${gitmoji ? `- <type> is a gitmoji` : `- <type> can be one of the following: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert`}
- <description> is a short, imperative present-tense description of the change
- <body> is a short description of the changes
- Pretend you're writing an important newsworthy article. Give the headline in <description> that will sum up what happened and what is important. Then, provide further details in the <body> in an organized fashion.
- the diff is generated by "git diff"
- do NOT use markdown syntax
- do NOT add quotes, single quote or code blocks
- keep <description> short, 1 LINE ONLY, maximum 50 characters
- keep <body> short, 1 LINE ONLY, maximum 72 characters
- follow the conventional commit spec at https://www.conventionalcommits.org/en/v1.0.0/#specification
- do NOT confuse delete lines starting with '-' and add lines starting with '+'
`;
};


let choice;
let message;
do {
  // Generate a conventional commit message based on the staged changes diff
  message = "";
  for (const chunk of chunks) {
    const res = await runPrompt(
      (_) => {
        _.def("GIT_DIFF", chunk, {
          maxTokens: 10000,
          language: "diff",
          detectPromptInjection: "available",
        });
        _.$`Generate a git conventional commit message that summarizes the changes in GIT_DIFF.`;
        addInstructions(_);
      },
      {
        model: "large", // Specifies the LLM model to use for message generation
        label: "generate commit message", // Label for the prompt task
        system: ["system.assistant"],
        systemSafety: true,
        responseType: "text",
      },
    );
    if (res.error) throw res.error;
    message += (res.fences?.[0]?.content || res.text) + "\n";
  }


  // since we've concatenated the chunks, let's compress it back into a single sentence again
  if (chunks.length > 1) {
    const res = await runPrompt(
      (_) => {
        _.$`Generate a git conventional commit message that summarizes the <COMMIT_MESSAGES>.`;
        addInstructions(_);
        _.def("COMMIT_MESSAGES", message);
      },
      {
        model: "large",
        label: "summarize chunk commit messages",
        system: ["system.assistant"],
        systemSafety: true,
        responseType: "text",
      },
    );
    if (res.error) throw res.error;
    message = res.text;
  }


  message = message?.trim();
  if (!message) {
    console.log("No commit message generated, did you configure the LLM model?");
    break;
  }


  // Prompt user to accept, edit, or regenerate the commit message
  choice = await host.select(message, [
    {
      value: "commit",
      description: "accept message and commit",
    },
    {
      value: "edit",
      description: "edit message in git editor",
    },
    {
      value: "regenerate",
      description: "run LLM generation again",
    },
    {
      value: "cancel",
      description: "cancel commit",
    },
  ]);


  // Handle user's choice for commit message
  if (choice === "edit") {
    // @ts-ignore
    const { spawnSync } = await import("child_process");
    // 1) Launch git commit in an interactive editor
    const spawnResult = spawnSync("git", ["commit", "-m", message, "--edit"], {
      stdio: "inherit",
    });


    // 2) After the editor closes, forcibly exit the entire script
    console.debug("git editor closed with exit code ", spawnResult.status);
    if (spawnResult.status === 0) await gitPush();
    break;
  }
  // If user chooses to commit, execute the git commit and optionally push changes
  if (choice === "commit" && message) {
    console.log(await git.exec(["commit", "-m", message]));
    await gitPush();
    break;
  }


  if (choice === "cancel") {
    cancel("User cancelled commit");
    break;
  }
} while (choice !== "commit");
```

## Content Safety

[Section titled “Content Safety”](#content-safety)

The following measures are taken to ensure the safety of the generated content.

* This script includes system prompts to prevent prompt injection and harmful content generation.

  * [system.safety\_jailbreak](/genaiscript/reference/scripts/system#systemsafety_jailbreak)
  * [system.safety\_harmful\_content](/genaiscript/reference/scripts/system#systemsafety_harmful_content)

* The commit message is reviewed and approved by the user before committing the changes.

Additional measures to further enhance safety include running [a model with a safety filter](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/content-filter?tabs=warning%2Cuser-prompt%2Cpython-new) or validating the message with a [content safety service](/genaiscript/reference/scripts/content-safety).

Refer to the [Transparency Note](/genaiscript/reference/transparency-note/) for more information on content safety.