This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
git-better is a CLI tool suite that extends git with workflow-aware commands for managing branches. It provides opinionated workflows that automatically handle branch creation, updates, finishing, and promotion while ensuring base branches are up-to-date. The tool is installed globally via npm and registers custom git commands that users can run from any git repository.
npm test # Run all tests with coverage
npm run test:base # Run tests without coverage
npm run test:dev # Run tests in watch mode with coverageTo run a single test file:
npx mocha test/unit/path/to/test.spec.jsnpm run lint # Run ESLint with auto-fixThe project doesn't require a build step. To test locally:
npm link # Install commands globally for testingThe configuration system is layered and merge-based:
- Default config (
src/config/default.json) - Base defaults - Global config (
~/.gbrc.jsonor~/.gbrc.js) - User-wide settings - Repo config (
<repo-root>/.gbrc.jsonor<repo-root>/.gbrc.js) - Per-repository settings
Configs are merged with lodash's _.merge(), with repo config overriding global config, which overrides defaults. The config is cached in memory after first load (src/config/index.js).
Key config properties:
defaultBase- Default base branch (usually "main")defaultRemote- Remote to push/pull from (usually "origin")alwaysPush- Whether to automatically push after operationsworkflows- Object mapping namespace to workflow config (from/to branches)promotionPaths- Object mapping source branch to target branch for promotions
The Branch class (src/model/branch.js) parses and represents git branch names with namespace and version support:
- Simple branch:
feature-name→{branch: "feature-name"} - Namespaced branch:
feature/my-feature→{namespace: "feature", branch: "my-feature"} - Versioned branch:
hotfix/v2.0/fix-name→{namespace: "hotfix", version: "v2.0", branch: "fix-name"}
The namespace is used to look up workflows in the config. For example, a branch named hotfix/security-patch will use the hotfix workflow from config.
All commands follow the same pattern:
- Bin script (
bin/git-*.js) - Entry point that usesshell-wrapper.js - Main implementation (
src/*.js) - Core logic that exports an async function - Shell wrapper (
bin/shell-wrapper.js) - Handles:- Argument parsing with
minimist - Update notifications
- Process exit and error handling
- Special handling for
pushas both flag and argument
- Argument parsing with
src/utils.js provides shared functionality used across commands:
getRemote()- Resolves remote from config or CLI optionsshouldPush()- Determines if push should happen (config + CLI options)isClean()- Checks if working directory has uncommitted changesswitchToAndUpdateBase()- Checks out and pulls base branch, optionally returns to originalgetWorkflow()- Looks up workflow config by branch namespacegetBaseBranch()- Resolves base branch from workflow or defaultgetAllBaseBranches()- Extracts all base branches from promotionPathsgetUiUrl()- Converts git remote URL to web UI URL (handles SSH format)
When a command operates on a branch, it resolves the workflow as follows:
- Parse branch name to extract namespace (e.g.,
feature/my-branch→feature) - Look up
config.workflows[namespace] - If found, use
workflow.fromandworkflow.tofor base branches - If not found, fall back to
config.defaultBase
The to field in workflows can be:
- A single string (merge into one branch)
- An array of strings (merge into multiple branches sequentially)
Tests use Mocha + Chai + Sinon with proxyquire for dependency injection:
- Unit tests (
test/unit/) - Mock all dependencies usingproxyquireandsinon.stub() - Test files mirror source structure:
src/finish.js→test/unit/src/finish.spec.js - Bin scripts are tested in
test/unit/bin/ - All
simple-gitcalls are stubbed in tests to avoid real git operations
The project uses ESLint v9 with flat config format (eslint.config.js). Key rules:
- Complexity limit: 5 - Functions exceeding this must be refactored into smaller helpers
- Max params: 3 - Functions with more parameters should use options objects
- Uses
eslint-plugin-mochafor test-specific rules - Enforces JSDoc for all functions (removed in recent test updates but still enforced in source)
The ESLint complexity rule is set to 5. When refactoring complex functions:
- Extract conditional logic into separate helper functions
- Use early returns to reduce nesting
- Move nested if-else blocks into dedicated functions
Example: src/config/index.js was refactored to extract getConfigPath() and copyExampleConfig() helpers.
All git operations use simple-git library. Common patterns:
const git = require('simple-git')();
// Get current branch info
const status = await git.status();
const currentBranch = status.current;
// Get repo root
const repoRoot = await git.revparse(['--show-toplevel']);
// Branch operations
await git.checkout(branchName);
await git.pull(remote, branch);
await git.merge([sourceBranch]);
await git.push(remote, branch);When writing tests for functions that use simple-git:
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const gitStub = {
checkout: sinon.stub().resolves(),
pull: sinon.stub().resolves(),
// ... other methods
};
const moduleUnderTest = proxyquire('../src/module', {
'simple-git': () => gitStub
});The shell-wrapper.js handles common CLI patterns:
- Flags like
-pand--pushare aliased - The word
pushas a positional argument is converted to--pushflag - Remote flag:
-Ror--remote - Global flag:
-gor--global - Branch flag:
-bor--branch
git start - Creates new branch from base, updates base first git update - Merges base branch into current branch (with optional rebase) git finish - Merges current branch into base(s) defined by workflow git promote - Merges current base branch into next base per promotionPaths git rename - Renames current branch (preserves namespace) git open - Opens repo in web browser git pr - Opens pull request page in browser (pushes first if needed)
Requires Node.js >=10. Uses modern async/await throughout but maintains compatibility with older Node versions.