linus-commits
v0.1.0
Published
CLI tool that enforces git hygiene standards for commit messages, branch names, and PR titles
Readme
linus-commits
A CLI tool that enforces git hygiene standards for commit messages, branch names, and PR titles. Installable via NPM, configurable via cosmiconfig, with git hook support and a GitHub Action wrapper.
Philosophy: Good commit hygiene makes repositories easier to navigate, review, and maintain. linus-commits codifies your team's git standards into enforceable rules that run locally (via git hooks) or in CI (via GitHub Actions).
Installation
# Install globally
npm install -g linus-commits
# Install as a dev dependency
npm install --save-dev linus-commits
# Or run directly with npx
npx linus-commits --helpRequirements: Node.js >= 18
Quick Start
# 1. Initialize a config file
linus-commits init
# 2. Install git hooks for local validation
linus-commits install
# 3. Make a commit — the hook validates your message automatically
git commit -m "TRX-123 Add user authentication module"Global Flags
These flags are available on all subcommands:
| Flag | Description |
|------|-------------|
| --config <path> | Path to config file (overrides cosmiconfig discovery) |
| --format <format> | Output format: human (default), json, or plain |
| --quiet | Suppress all stdout output |
| --color | Force colorized output |
| --no-color | Disable colorized output |
| --version, -V | Print version |
| --help, -h | Print help |
Exit Codes
| Code | Constant | Meaning |
|------|----------|---------|
| 0 | SUCCESS | No violations, or only warnings |
| 1 | VALIDATION_FAILURE | One or more error-level violations |
| 2 | CONFIG_ERROR | Configuration or usage error |
| 3 | GIT_ERROR | Git command failed (not a repo, branch not found, etc.) |
Note: Warnings do not cause a non-zero exit code. Only error-level violations cause exit code 1.
Commands
commit — Validate commit messages
# Validate a single commit message
linus-commits commit --message "TRX-123 Add user authentication module"
# Validate commits in a git range
linus-commits commit --range "main..HEAD"
# Validate all commits on current branch vs base branch (default behavior)
linus-commits commitOptions:
| Flag | Description |
|------|-------------|
| --message <msg> | Validate a single message (title, or title + blank line + description) |
| --range <range> | Validate commits in a git range |
When neither flag is provided, validates all commits on the current branch compared to the configured baseBranch (default: main).
Example output (human format):
error imperative-mood First word "Added" is non-imperative. Use "Add" instead
TRX-123 Added user authentication
1 problem (1 error, 0 warnings)Example output (json format):
linus-commits commit --message "TRX-123 Added auth" --format json[
{
"rule": "imperative-mood",
"severity": "error",
"message": "First word \"Added\" is non-imperative. Use \"Add\" instead",
"subject": "TRX-123 Added auth"
}
]branch — Validate branch names
# Validate the current branch name
linus-commits branch
# Validate a specific branch name
linus-commits branch --name "TRX-123-add-user-auth"Options:
| Flag | Description |
|------|-------------|
| --name <name> | Branch name to validate (defaults to current branch) |
Example output:
error no-gitflow Branch name must not use gitflow prefixes (feat/, fix/, chore/)
feat/TRX-123-add-auth
1 problem (1 error, 0 warnings)pr — Validate PR titles
# Validate a PR title
linus-commits pr --title "TRX-123 Add user authentication module"
# Auto-fix a PR title (prints corrected title to stdout)
linus-commits pr --title "trx-123 add feature" --fix
# Auto-fix a PR title in GitHub Actions (updates via API)
linus-commits pr --title "trx-123 add feature" --fix --github-token "$GITHUB_TOKEN"Options:
| Flag | Description |
|------|-------------|
| --title <title> | Required. PR title to validate |
| --fix | Print auto-corrected title to stdout |
| --github-token <token> | GitHub token for auto-fixing PR title via API |
Auto-fix behavior:
- Uppercases the ticket prefix (e.g.,
trx-123becomesTRX-123) - Normalizes spacing between ticket and description
- In GitHub Actions with
--github-token: automatically updates the PR title via the GitHub API - In GitHub Actions without a token: emits a warning to stderr and skips the API call
- Outside GitHub Actions: only prints the corrected title to stdout
staleness — Check PR staleness
# Check staleness using defaults (72 hours, baseBranch..HEAD)
linus-commits staleness
# Check with a custom range
linus-commits staleness --range "main..HEAD"
# Check with a custom threshold
linus-commits staleness --threshold 48Options:
| Flag | Description |
|------|-------------|
| --range <range> | Git range to check (default: baseBranch..HEAD) |
| --threshold <hours> | Staleness threshold in hours (overrides config) |
Fails if the PR age (time between earliest and latest commit) exceeds the threshold and no commit in the range has a description.
init — Initialize config file
# Create .linusrc.json with default config
linus-commits init
# Overwrite an existing config file
linus-commits init --forceOptions:
| Flag | Description |
|------|-------------|
| --force | Overwrite existing config file |
install — Install git hooks
# Install default commit-msg hook
linus-commits install
# Install a pre-push hook
linus-commits install --hook pre-push
# Force overwrite existing hook
linus-commits install --force
# Append to an existing hook file
linus-commits install --appendOptions:
| Flag | Description |
|------|-------------|
| --hook <type> | Hook type: commit-msg (default) or pre-push |
| --force | Overwrite existing hooks without prompting |
| --append | Append to existing hook files |
Husky detection: Automatically detects Husky by checking for a .husky/ directory or husky in package.json dependencies. When Husky is detected, hooks are installed in .husky/ following Husky conventions.
uninstall — Remove git hooks
linus-commits uninstallRemoves all linus-commits hooks. If a hook file contains other content besides linus-commits, only the linus-commits portion is removed.
Full PR validation workflow
To comprehensively validate all aspects of a branch/PR before merge, run all subcommands together:
linus-commits commit && \
linus-commits branch && \
linus-commits pr --title "TRX-123 Add user authentication" && \
linus-commits stalenessThis validates commit messages, branch name, PR title, and staleness in sequence, stopping at the first failure.
Configuration
Configuration is loaded via cosmiconfig from these locations (in order of precedence):
linus-commitskey inpackage.json.linusrc(JSON or YAML).linusrc.json.linusrc.yamllinus-commits.config.jslinus-commits.config.cjs
The --config <path> flag overrides automatic discovery.
Default Configuration
{
"rules": {
"ticket-prefix": "error",
"capitalization": "error",
"title-length": "error",
"title-punctuation": "error",
"min-words": "error",
"imperative-mood": "error",
"no-conventional-commits": "error",
"fix-requires-description": "error",
"description-line-length": "warn",
"description-punctuation": "warn",
"branch-format": "error",
"no-gitflow": "error",
"staleness": "error"
},
"ticketRequired": true,
"ticketPattern": "^[A-Z]{2,}-\\d{1,4}\\s",
"maxTitleLength": 72,
"minTitleWords": 3,
"maxDescriptionLineLength": 78,
"staleThresholdHours": 72,
"nonImperativeWords": [
"adds", "added", "creates", "created",
"updates", "updated", "fixes", "fixed",
"moves", "moved", "changes", "changed",
"removes", "removed", "uses", "used"
],
"exemptPatterns": ["revert", "merge", "\\[snyk\\]"],
"baseBranch": "main"
}Configuration Keys
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| rules | object | See above | Rule severity map ("error", "warn", or "off") |
| ticketRequired | boolean | true | Whether a Jira ticket prefix is required |
| ticketPattern | string | ^[A-Z]{2,}-\\d{1,4}\\s | Regex pattern for ticket prefix |
| maxTitleLength | number | 72 | Maximum title length in characters |
| minTitleWords | number | 3 | Minimum word count after ticket prefix |
| maxDescriptionLineLength | number | 78 | Maximum description line length |
| staleThresholdHours | number | 72 | Hours before a PR is considered stale |
| nonImperativeWords | string[] | See above | Words that violate imperative mood |
| exemptPatterns | string[] | See above | Regex patterns for exempt commit titles |
| baseBranch | string | "main" | Base branch for commit range detection |
Rule Severities
Each rule can be set to one of three severity levels:
"error"— Causes exit code 1 (validation failure)"warn"— Reported but does not affect exit code"off"— Rule is completely skipped
Example: disable the ticket-prefix rule:
{
"rules": {
"ticket-prefix": "off"
}
}Validation Rules
Commit/PR Title Rules
| Rule | Default | Description |
|------|---------|-------------|
| ticket-prefix | error | Title must start with a Jira ticket (e.g., TRX-123 ) |
| capitalization | error | First word after ticket must be capitalized |
| title-length | error | Title must not exceed maxTitleLength (72) characters |
| title-punctuation | error | Title must not end with ., ?, or trailing whitespace |
| min-words | error | At least minTitleWords (3) words after ticket prefix |
| imperative-mood | error | First word must be imperative (not past tense) |
| no-conventional-commits | error | No feat:, fix:, chore:, etc. prefixes |
Titles matching
exemptPatterns(revert, merge, [snyk]) bypassticket-prefixandtitle-lengthchecks.
Commit Description Rules
| Rule | Default | Description |
|------|---------|-------------|
| fix-requires-description | error | "Fix" commits must include a description (except "Fix typo") |
| description-line-length | warn | Lines must not exceed maxDescriptionLineLength (78) chars |
| description-punctuation | warn | Must start with capital letter and end with . , ? or : |
Description line length exempts: URLs, code blocks (
```), backtick lines, and lines with 2+ leading spaces.
Branch Name Rules
| Rule | Default | Description |
|------|---------|-------------|
| branch-format | error | Must match JIRA-123-kebab-case-description |
| no-gitflow | error | No feat/, feature/, fix/, chore/ prefixes |
Staleness Rule
| Rule | Default | Description |
|------|---------|-------------|
| staleness | error | PRs older than staleThresholdHours must have at least one commit with a description |
GitHub Action
Use linus-commits as a composite GitHub Action in your workflows:
name: Git Hygiene
on:
pull_request:
types: [opened, edited, synchronize]
jobs:
lint-commits:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: your-org/linus-commits@v1
with:
base-branch: main
check: all
github-token: ${{ secrets.GITHUB_TOKEN }}Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| base-branch | No | main | Base branch to compare commits against |
| config-path | No | | Path to linus-commits config file |
| check | No | all | Checks to run: commit, branch, pr, staleness, or all |
| github-token | No | | GitHub token for auto-fixing PR titles |
| node-version | No | 20 | Node.js version to use |
Outputs
| Output | Description |
|--------|-------------|
| result | Overall result: pass or fail |
| errors | JSON array of violations |
Run specific checks only
- uses: your-org/linus-commits@v1
with:
check: commit,branch
base-branch: developUse outputs in subsequent steps
- uses: your-org/linus-commits@v1
id: lint
with:
check: all
- run: echo "Result was ${{ steps.lint.outputs.result }}"License
MIT
