This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
pnpm install # install dependencies
pnpm test # run all tests
npx tsx --test test/git.test.ts # run a single test file
npx tsx bin/cli.ts --help # run CLI locally without installing
# Configure commai in the repo (run once)
npx tsx bin/cli.ts install --model sonnet@latest --interactive
# Generate a commit message (called by git hook; uses config from .commai/.config)
npx tsx bin/cli.ts generateNo build step — TypeScript is executed directly via tsx.
commai is a CLI tool that generates git commit messages using AI. It integrates with git via the prepare-commit-msg hook.
The npm package is published as @luctst/commai, but the CLI command installed globally or locally is simply commai.
bin/commai.js (thin Node shim) → bin/cli.ts (commander dispatch) → command handlers in src/
install command: Runs once per repo to configure commai.
- Accepts CLI options:
--model(required),--interactive,--auto-commit - Creates
.commai/directory and hook scripts - Writes
CommaiConfigto.commai/.config(JSON) with the provided options - Sets
git config core.hooksPath .commai
generate command (core runtime path, called by git hook):
- Reads
CommaiConfigfrom.commai/.config src/git.tsreads the staged diff (git diff --cached)src/services/ai/resolveModel.tsdetermines the provider (e.g.,"claude"or"openai") from a model string like"sonnet@latest","gpt-4-turbo", or a raw model IDsrc/services/ai/ai.tsfactorycreateAIService()instantiates the provider's service (ClaudeServiceorOpenAIService)src/prompt.tsruns the interactive accept/regenerate/cancel loop vianode:readline(ifinteractive: true)src/generate.tsorchestrates the above and writes the result to the commit message file (or callsgit commit -mdirectly whenautoCommit: true)
Services use native fetch instead of SDK clients for HTTP communication. This reduces dependencies and gives explicit control over API contracts.
Provider resolution is split into two concerns:
resolveProvider(modelString)insrc/services/ai/resolveModel.ts— synchronous function that determines which AI provider a model string maps to. Supports Claude families (sonnet,opus,haiku) and OpenAI families (gpt,o) in both alias format ("sonnet@latest","gpt-4@latest") and raw model IDs ("claude-sonnet-4-20250514","gpt-4-turbo"). Throws if no known family is found.createAIService(provider, { model? })insrc/services/ai/ai.ts— factory that instantiates the service for a given provider ("claude"→ClaudeService,"openai"→OpenAIService).
Each service handles its own model ID resolution. ClaudeService.getModel() and OpenAIService.getModel() each resolve aliases to concrete model IDs via their respective models.list() APIs if the model is an alias (contains @); raw model IDs (e.g., "claude-sonnet-4-20250514", "gpt-4-turbo") skip the API call entirely and are used as-is. Falls back to input as-is on API errors.
Dependency injection for testing: ClaudeService and OpenAIService constructors accept:
model?: string— override the model passed tocreateAIService()fetchFn?: FetchFn— inject a customfetchfunction. Tests provide a mock that accepts a URL and returns aResponseobject with the appropriate shape for the API.
AIService interface is intentionally minimal: generateCommitMessage(diff, instructions?). New providers go in src/services/ai/<provider>/ and get a case in the createAIService() factory switch.
src/install.ts exports install() and uninstall().
install():
- Creates
.commai/at the repo root. - Writes
.commai/prepare-commit-msg— runscommai generate, skips amend/merge/squash commits, then falls through to.husky/prepare-commit-msgand.git/hooks/prepare-commit-msgfor chain compatibility. - Writes forwarder scripts for all 12 standard hooks (
pre-commit,commit-msg,post-commit,pre-push,pre-rebase,post-checkout,post-merge,post-rewrite,pre-auto-gc,applypatch-msg,pre-applypatch,post-applypatch) — each delegates to the matching.husky/<hook>then.git/hooks/<hook>. - Sets
git config core.hooksPath .commai— redirects all git hook dispatch to.commai/. - Writes
CommaiConfigto.commai/.config(JSON) containingmodel,interactive,autoCommit, and the previouscore.hooksPathvalue (for uninstall restoration).
uninstall():
- Verifies
# managed-by-commaimarker in.commai/prepare-commit-msg. Exits 1 if absent (foreign directory) or if.commai/doesn't exist. - Removes
.commai/entirely. - Restores
core.hooksPathto its saved value, or unsets it if it was previously empty.
Idempotency: install overwrites its own .commai/ (marker present). Refuses to overwrite a .commai/ not created by commai — exits 1.
AI/network failures exit 0 (non-fatal — never block a commit). API errors are thrown with HTTP status codes and error messages (e.g., "API request failed: 401 Unauthorized"), but these are caught in generate() and logged without exiting with error code. Only configuration errors (missing API key like ANTHROPIC_API_KEY or OPENAI_API_KEY, not a git repo) exit 1.
Tests use node:test (built-in) + node:assert/strict, run via tsx --test. No external test framework.
- git.test.ts / install.test.ts: Integration tests using real temporary git repos (
mkdtemp+git init). Testschdirinto the temp repo and restore cwd infinallyblocks. - generate.test.ts: Injects a mock
AIServicevia theserviceoption ongenerate(). - services/claude.test.ts: Injects a mock
fetchfunction via theClaudeServiceconstructor. The mock accepts a URL and returns aResponseobject matching the Anthropic API shape (e.g.,{ content: [{ type: "text", text: "..." }] }). Also tests raw model IDs (e.g.,"claude-sonnet-4-20250514") which skip themodels.list()API call. - services/openai.test.ts: Injects a mock
fetchfunction via theOpenAIServiceconstructor. The mock returns aResponsematching the OpenAI API shape (e.g.,{ choices: [{ message: { content: "..." } }] }). Also tests raw model IDs which bypassmodels.list().
Both generate() and service constructors (ClaudeService, OpenAIService) accept optional dependency injection parameters specifically for testability — no module mocking needed.
.github/workflows/ci.yml— tests on push/PR, Node 18/20/22 matrix.github/workflows/publish.yml— npm publish with provenance onv*tag push (requiresNPM_TOKENsecret).github/actions/commai/action.yml— composite GitHub Action for CI workflows. Installs@luctst/commai, auto-detects the API provider from the model family, configures hooks, and generates commit messages. Use with- uses: luctst/commai@v1.