Skip to content

Everduin94/better-commits

Repository files navigation

bc-gradient

better commits is enabled downloads discord

A CLI for writing better commits, following the conventional commits specification.

better-commits-demo.mp4

✨ Features

  • Generate conventional commits through a series of prompts
  • Highly configurable with sane defaults
  • Infers ticket and commit-type from branch for consistent & fast commits
  • Consistent branch creation with flexible workflow hooks via better-branch
  • Interactive git status/add on commit
  • Preview commit messages in color
  • Support for git emojis per commit-type
  • Configure globally or per repository
  • Config validation and error messaging
  • Lightweight (17kb)

As a side-effect of formatting messages

  • Auto populate PR title / body
  • Automate semantic releases
  • Automate changelogs
  • Automatically link & close related tickets / issues

πŸ“¦ Installation

npm install -g better-commits

πŸš€ Usage

To run the CLI in your terminal:

better-commits # Create a new commit
better-branch # Create a new branch

better-commits will prompt a series of questions. These prompts will build a commit message, which you can preview, before confirming the commit. - To better understand these prompts and their intention, read Conventional Commits Summary

Some of the values in these prompts will be inferred by your branch name and auto populated. You can adjust this in your .better-commits.jsonc (or .better-commits.json) configuration file.

For documentation on passing commit values to better-commits via the CLI, see CLI Flags.

Tip

The --no-interactive flag, allows automated workflows or AI agents like OpenCode and Claude Code, to use better-commits to generate consistent commit messages using less tokens.

Run better-commits --help / better-branch --help for more information.

βš™οΈ Configuration

Global

Your first time running better-commits, a default config will be generated in your $HOME directory, named .better-commits.jsonc (formerly .better-commits.json)

  • This config will be used if a repository-specific config cannot be found.

Repository

To create a repository-specific config, navigate to the root of your project.

  • Run better-commits-init
  • This will create a default config named .better-commits.jsonc
  • Properties such as confirm_with_editor and overrides will prefer the global config

πŸ’« Properties

Note

All properties are optional and can be removed from the config. They will be replaced by the default at run-time.

  • See .better-commits.json in this repository as an example
{
  // Run interactive `git status` before composing a commit
  "check_status": true,

  /* COMMIT FIELDS */
  "commit_type": {
    "enable": true,

    // Default selected type from options
    "initial_value": "feat",

    "max_items": 20,

    // Infer type from the current branch name: user/TYPE/my-branch
    "infer_type_from_branch": true,

    // Include emoji in prompt label
    "append_emoji_to_label": false,

    // Include emoji from prompt label in commit message
    "append_emoji_to_commit": false,

    // "Start" | "After-Colon"
    "emoji_commit_position": "Start",

    "options": [
      {
        "value": "feat",
        "label": "feat",
        "hint": "A new feature",
        "emoji": "🌟",
        "trailer": "Changelog: feature",
      },
      {
        "value": "fix",
        "label": "fix",
        "hint": "A bug fix",
        "emoji": "πŸ›",
        "trailer": "Changelog: fix",
      },
      {
        "value": "docs",
        "label": "docs",
        "hint": "Documentation only changes",
        "emoji": "πŸ“š",
        "trailer": "Changelog: documentation",
      },
      {
        "value": "refactor",
        "label": "refactor",
        "hint": "A code change that neither fixes a bug nor adds a feature",
        "emoji": "πŸ”¨",
        "trailer": "Changelog: refactor",
      },
      {
        "value": "perf",
        "label": "perf",
        "hint": "A code change that improves performance",
        "emoji": "πŸš€",
        "trailer": "Changelog: performance",
      },
      {
        "value": "test",
        "label": "test",
        "hint": "Adding missing tests or correcting existing tests",
        "emoji": "🚨",
        "trailer": "Changelog: test",
      },
      {
        "value": "build",
        "label": "build",
        "hint": "Changes that affect the build system or external dependencies",
        "emoji": "🚧",
        "trailer": "Changelog: build",
      },
      {
        "value": "ci",
        "label": "ci",
        "hint": "Changes to our CI configuration files and scripts",
        "emoji": "πŸ€–",
        "trailer": "Changelog: ci",
      },
      {
        "value": "chore",
        "label": "chore",
        "hint": "Other changes that do not modify src or test files",
        "emoji": "🧹",
        "trailer": "Changelog: chore",
      },
      {
        "value": "",
        "label": "none",
      },
    ],
  },

  "commit_scope": {
    "enable": true,

    // If true, users can type a scope not listed in options
    "custom_scope": false,

    // Default selected scope from options
    "initial_value": "app",

    "max_items": 20,
    "options": [
      { "value": "app", "label": "app" },
      { "value": "shared", "label": "shared" },
      { "value": "server", "label": "server" },
      { "value": "tools", "label": "tools" },
      { "value": "", "label": "none" },
    ],
  },

  "check_ticket": {
    // Infer ticket / issue from the branch name - user/type/TICKET-my-branch
    "infer_ticket": true,

    // Prompt for confirmation / edit before using an inferred ticket
    "confirm_ticket": true,

    // Add the ticket to the commit title - feat(app): TICKET my commit title
    "add_to_title": true,

    // Deprecated, prefer `prepend_hashtag`
    "append_hashtag": false,

    // "Never" | "Prompt" | "Always" - 12345 --> #12345
    "prepend_hashtag": "Never",

    // Wrap the ticket in the commit title: "" | "[]" | "()" | "{}"
    "surround": "",

    // "start" | "end" | "before-colon" | "beginning"
    "title_position": "start",
  },

  "commit_title": {
    // Includes total size of title + type + scope + ticket
    "max_size": 70,
  },

  "commit_body": {
    "enable": true,
    "required": false,

    // Split sentences into multiple lines automatically
    "split_by_period": false,
  },

  "commit_footer": {
    "enable": true,
    "initial_value": [],

    // "closes", "trailer", "breaking-change", "deprecated", "custom"
    "options": ["closes", "trailer", "breaking-change", "deprecated", "custom"],
  },

  "breaking_change": {
    // Adds `!` to the commit title when a breaking change is selected
    "add_exclamation_to_title": true,
  },

  // Confirm / edit with $GIT_EDITOR or $EDITOR
  "confirm_with_editor": false,

  // Show a final confirmation prompt before running git commit
  "confirm_commit": true,

  // Reuse the last known value from a previous canceled or failed commit
  "cache_last_value": true,

  // Pretty-print the final commit preview before execution
  "print_commit_output": true,

  /* BRANCH FIELDS */
  // Optional shell commands to run before / after creating branches or worktrees
  "branch_pre_commands": [],
  "branch_post_commands": [],
  "worktree_pre_commands": [],
  "worktree_post_commands": [],

  "branch_user": {
    "enable": true,
    "required": false,

    // "/" | "-" | "_" - user/feat/my-branch
    "separator": "/",
  },

  "branch_type": {
    "enable": true,
    "separator": "/",
  },

  "branch_ticket": {
    "enable": true,
    "required": false,
    "separator": "-",
  },

  "branch_version": {
    "enable": false,
    "required": false,
    "separator": "/",
  },

  "branch_description": {
    // Maximum length for the description segment of the branch name
    "max_length": 70,

    // Allowed values: "" | "/" | "-" | "_"
    "separator": "",
  },

  // "branch" | "worktree"
  "branch_action_default": "branch",

  // Order of values in the final branch name
  "branch_order": ["user", "version", "type", "ticket", "description"],

  // Deprecated, prefer `worktrees.enable`
  "enable_worktrees": true,

  "worktrees": {
    // If false, always create a branch instead of prompting for a worktree
    "enable": true,

    // Directory where worktrees are created
    "base_path": "..",

    // Available template variables include:
    // {{repo_name}}, {{branch_description}}, {{user}}, {{type}}, {{ticket}}, {{version}}
    "folder_template": "{{repo_name}}-{{ticket}}-{{branch_description}}",
  },

  /* OTHER FIELDS */
  "overrides": {
    // Useful on Windows or for shells with different multiline behavior
    "shell": "/bin/sh",
  },
}

πŸ”Ž Inference

better-commits will attempt to infer the ticket/issue and the commit-type from your branch name. It will auto populate the corresponding field if found.

Ticket / Issue-Number

  • If a STRING-NUMBER or NUMBER are at the start of the branch name or after a /

Commit Type

  • If a type is at the start of the branch or is followed by a /

🌳 Better Branch

Better branch is a secondary feature that works with better commits

  • Supports consistent branch naming conventions
  • Uses same type-list/prompt from your config
  • Enables better-commits to infer type & ticket
  • Caches your username for speedy branching
  • Convenient worktree creation

To run the CLI in your terminal:

better-branch

Worktree Support

better-branch will prompt for Branch or Worktree. The Worktree flow creates a folder/worktree from your branch description and a git branch inside with your full branch name.

Note

Creating a worktree named everduin94/feat/TAC-123-add-worktrees with the native git command would create a nested folder for each /. better-branch removes the hassle by creating 1 folder while still using the full name for the branch.

Tip

By default, better-branch will create worktrees as a sibling folder. To change this, see worktrees.base_path.

Pre/Post Branch Checkout Hooks

Optionally configure pre and post checkout commands, for example:

  • checkout and rebase main before branching
  • run npm install before branching
  • run npm run dev after branching

See branch_pre_commands and branch_post_commands in default config. (or worktree_pre_commands and worktree_post_commands for creating worktrees)

πŸ’‘ Tips & Tricks

Building / Versioning

better-commits works with Semantic Release

  • See package.json and .github/workflows/publish.yml for example

Github

If you use better-commits to create your first commit on a new branch

  • When you open a PR for that branch, it will properly auto-populate the title and body.
  • When you squash/merge, all later commits like "addressing comments" or "fixing mistake". Will be prefixed with an asterisk for easy deletion. This way, you maintain your pretty commit even when squashing.

If you're using Github issues to track your work, and select the closes footer option when writing your commit. Github will automatically link and close that issue when your pr is merged

Changelogs

better-commits can append a commit trailer per commit type. This allows you to automate change logs with tools like Gitlab.

Git

better-commits uses native git commands under the hood. So any hooks, tools, or staging should work as if it was a normal commit.

Setting confirm_with_editor=true will allow you to edit/confirm a commit with your editor.

  • For example, to edit with Neovim: git config --global core.editor "nvim"
  • For VS Code, git config --global core.editor "code -n --wait"

You can pass arguments to git through better-commits like so:

better-commits --git-dir="$HOME/.config" --work-tree="$HOME"

A practical example of this would be managing dotfiles, as described in this Atlassian Article

CLI Flags

Use CLI flags to pass commit values directly instead of answering prompts.

  • Use --no-interactive to skip prompts, confirmation, and editor flows. This is the recommended mode for OpenCode, Claude Code, and other coding agents.
  • Use --dry-run to validate the generated git commit command without creating a commit.
  • Supported commit field flags: --type, --scope, --title, --body, --ticket, --closes, --deprecates, --breaking-title, --breaking-body, --deprecates-title, --deprecates-body, --custom-footer, --trailer.
  • Supported branch field flags: --user, --type, --description, --ticket, --branch-version, --checkout.

Examples

better-commits --no-interactive --dry-run --type feat --scope cli --title "add parser"

better-branch --no-interactive --type feat --ticket TAC-123 --description "add parser" --checkout worktree

πŸͺŸ Troubleshooting Windows

Git Bash

TTY initialization failed: uv_tty_init returned EBADF (bad file descriptor). This may happen because you're running something like git-bash on Windows. Try another terminal/command-prompt or winpty to see if its still an issue.

Multi-line

If you are having issues with multilines for commits on windows, you can override the shell via your .better-commits.jsonc config.

Example

"overrides": {
   "shell": "c:\\Program Files\\Git\\bin\\bash.exe"
}

🌟 Sponsors

About

A CLI for creating better commits following the conventional commits specification

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors