Skip to content

trynna-be-nerdy/Auto-Commiter-No-Cost

Repository files navigation

git-auto-commit-NO COST

A zero-cost local background service that watches your repository for file changes, automatically stages them, generates a conventional commit message by analysing the diff, commits, and pushes to GitHub — with no manual intervention and no API keys required.


Quick Start

git clone https://github.com/trynna-be-nerdy/git-auto-commit.git
cd git-auto-commit
npm install
npm run build
cp auto-commit.json .auto-commit.json   # edit if needed
npm start

That's it. The service is now watching the current directory and will commit + push on every save.

For a persistent background process that survives reboots, use pm2 mode instead.


Features

  • Fully free — commit messages are generated by a local rule-based diff parser. No AI API, no credits, no network calls.
  • Conventional commits — output always follows the <type>(<scope>): <description> format.
  • Debounced batching — rapid saves are grouped into one commit instead of spamming your history.
  • Safe by default — never force-pushes. On push failure the commit stays local.
  • Dry-run mode — preview every action without touching Git.
  • pm2 integration — run as a persistent background process that survives reboots.
  • Structured logging — JSON log file with automatic 5 MB rotation.

How It Works

  1. Watch — chokidar monitors your repo for file saves (add, change, unlink).
  2. Debounce — rapid saves are batched; a commit fires only after a configurable period of inactivity.
  3. Stagegit add -A stages everything not in ignorePatterns.
  4. Analyse — a built-in rule-based parser reads the git diff --cached output and infers a conventional commit message from file types and change patterns. No AI, no network, no API keys.
  5. Commitgit commit -m "<generated message>".
  6. Push — if autoPush is true, git push origin main runs automatically.

Requirements

  • Node.js 20 LTS or later
  • Git installed and a configured remote (origin)
  • npm

Project Structure

git-auto-commit/
├── src/
│   ├── index.ts          # CLI entry point (--dry-run, --help, signal handlers)
│   ├── autocommit.ts     # Orchestrator — ties all modules together
│   ├── watcher.ts        # chokidar file watcher with debounce
│   ├── git.ts            # simple-git wrapper (stage, diff, commit, push)
│   ├── commit-message.ts # Rule-based conventional commit generator
│   ├── config.ts         # Zod schema + .auto-commit.json loader
│   └── logger.ts         # JSON logger with 5 MB rotation
├── dist/                 # Compiled output (generated by `npm run build`)
├── auto-commit.json           # Template config — copy to .auto-commit.json
├── ecosystem.config.cjs       # pm2 process config
├── start.ps1             # One-command Windows launcher
├── start.sh              # One-command macOS/Linux launcher
├── tsconfig.json
└── package.json

Installation

Standalone (own repo)

git clone https://github.com/trynna-be-nerdy/git-auto-commit.git
cd git-auto-commit
npm install
npm run build
cp auto-commit.json .auto-commit.json
npm start

Inside an existing project (recommended)

Clone git-auto-commit as a subdirectory of the project you want to watch:

cd my-project                          # your existing git repo
git clone https://github.com/trynna-be-nerdy/git-auto-commit.git
cd git-auto-commit
npm install
npm run build
cp auto-commit.json ../.auto-commit.json   # config goes in the project root

Then start the service from inside the git-auto-commit folder:

npm start

The service automatically detects it is running inside a subdirectory, walks up to the repo root (my-project/), and watches the entire project from there. The git-auto-commit/ folder itself is silently excluded from watching so it never commits its own files.

You will see this on startup:

[auto-commit] Detected subdirectory install — watching repo root: /path/to/my-project
[auto-commit] Auto-ignoring install directory: git-auto-commit

You can also add git-auto-commit/ to your project's .gitignore if you don't want it tracked:

echo "git-auto-commit/" >> ../.gitignore

Configuration

Edit .auto-commit.json in your repository root. The file is git-ignored so it never gets committed.

{
  "watchPaths": ["."],
  "ignorePatterns": ["node_modules", "dist", ".git", "*.log", ".auto-commit.log"],
  "debounceSeconds": 10,
  "autoPush": true,
  "remoteName": "origin",
  "branch": "main"
}
Field Default Description
watchPaths ["."] Directories to watch, relative to the repo root
ignorePatterns see above Glob patterns and directory names to ignore
debounceSeconds 10 Seconds of inactivity before a commit is triggered
autoPush true Push to remote after each commit
remoteName "origin" Git remote to push to
branch "main" Branch to push to

All fields are optional — if .auto-commit.json is missing entirely, the defaults above are used and a warning is printed on startup.

You can also override the config file path with an environment variable:

AUTO_COMMIT_CONFIG=/path/to/custom.json npm start

Running the Service

Development mode (foreground, live reload)

Best for testing. Restarts automatically when you edit source files.

npm run dev

Production mode (foreground, compiled)

npm run build
npm start

Dry-run mode

Prints every action (stage / commit / push) without actually executing any Git commands. Use this to verify the service is working before letting it commit.

npm start -- --dry-run
# or in dev mode:
npm run dev -- --dry-run

Example dry-run output:

[auto-commit] Service started — watching: . | debounce: 10s | autoPush: true | DRY-RUN
[auto-commit] Change detected: src/auth/login.ts
[auto-commit] Batch ready (1 file(s) changed).
[dry-run] Would run: git add -A
[auto-commit] Commit message: "feat(src/auth): add login.ts"
[dry-run] Would run: git commit -m "feat(src/auth): add login.ts"
[dry-run] Would run: git push origin main

Background mode with pm2 (recommended for daily use)

pm2 keeps the service running in the background and restarts it automatically if it crashes or the machine reboots.

# Start (builds first, then launches via pm2)
npm run pm2:start

# View live logs
npm run pm2:logs

# Restart after config changes
npm run pm2:restart

# Stop the service
npm run pm2:stop

Alternatively use the platform scripts:

# Windows
.\start.ps1
# macOS / Linux
chmod +x start.sh && ./start.sh

Help

npm start -- --help

Commit Message Format

Messages follow the Conventional Commits specification:

<type>(<scope>): <description>

Type is inferred from the files changed:

Type When it's used
test All changed files are test/spec files
docs All changed files are markdown / text
style All changed files are CSS / SCSS / SASS
chore All changed files are config / tooling / lock files
feat Any new source file added
fix Diff contains keywords: fix, fixed, bug, error, patch
chore Fallback for mixed or unclassified changes

Scope is the longest common parent directory of all changed files (e.g. src/auth). Omitted if files span multiple top-level directories.

Description lists file names by action:

  • add login.ts, register.ts
  • update user.ts, +3 more (capped at 3 names)
  • remove legacy.ts

The full subject line is truncated to 72 characters.

Examples:

feat(src/auth): add login.ts, register.ts
fix(src/api): update handler.ts
chore: update package.json, package-lock.json
test(src/utils): add parser.spec.ts
docs: update README.md

Logs

Structured JSON logs are written to .auto-commit.log in the repo root (git-ignored).

{"timestamp":"2026-04-24T10:30:00.000Z","level":"info","message":"Committing: \"feat(src): add index.ts\""}
{"timestamp":"2026-04-24T10:30:01.000Z","level":"info","message":"Push succeeded."}

The log file rotates automatically at 5 MB — the previous file is kept as .auto-commit.log.1.

To tail logs when running via pm2:

npm run pm2:logs

Troubleshooting

Service starts but no commits are created

  • Check debounceSeconds — the timer resets on every save, so rapid edits keep delaying the commit.
  • Verify the changed files are not matched by ignorePatterns.
  • Run with --dry-run to confirm the watcher is detecting changes.

Push is rejected

  • The remote branch has diverged. The commit is saved locally — pull/rebase manually then push.
  • The service will never force-push.

Merge conflict detected — skipping

  • Resolve the conflict manually, then the next save will trigger a normal commit cycle.

Nothing staged after git add

  • Git sees no diff (e.g. a file was saved without changing content, or changes were already committed). This is normal — the cycle is skipped silently.

pm2 not found

  • pm2 is installed as a dev dependency. Use npx pm2 or install globally: npm install -g pm2.

Technical Architecture

Overview

The system is a Node.js TypeScript process composed of five modules that form a linear pipeline: watch → stage → diff → generate message → commit/push.

┌─────────────────────────────────────────────────────────────────┐
│                      AutoCommitService                          │
│                                                                 │
│  FileWatcher ──(batch ready)──► GitService ──► CommitMessage   │
│      │                              │          Generator        │
│      │  chokidar                    │          (rule-based)     │
│      │  debounce timer              │                           │
│      └──────────────────────────────┴──► git commit + push      │
└─────────────────────────────────────────────────────────────────┘
         │                                          │
      Logger                                     Logger
  (.auto-commit.log)                         (.auto-commit.log)

Module Breakdown

src/config.ts — Configuration loader

Reads .auto-commit.json from the repo root (or the path in AUTO_COMMIT_CONFIG). Uses zod to validate the schema and fill in defaults. Returns a strongly-typed Config object consumed by all other modules.

If the file is missing, defaults are used and a warning is logged. If the file is malformed, the process exits with a descriptive error.

src/watcher.ts — File watcher

Wraps chokidar to watch one or more directories for add, change, and unlink events. Two key behaviours:

  1. Ignore filtering — plain directory names (e.g. node_modules) are converted to anchored regexes; glob patterns are passed through to chokidar directly.
  2. Debounce — every file event resets a setTimeout. Only when the timer expires with no new events does it call onBatchReady(paths[]). This means 50 rapid saves become one commit instead of 50.

awaitWriteFinish is enabled so chokidar waits for the OS file-write to settle before emitting the event — prevents partial-write commits.

src/git.ts — Git operations

Wraps simple-git with four operations:

Method What it runs
hasUnresolvedConflicts() git status --porcelain — checks for UU/AA/DD markers
stageAll() git add -A
getStagedDiff(maxTokens) git diff --cached — truncates at the character limit if needed
commit(message) git commit -m "<message>"
push(remote, branch) git push <remote> <branch> — returns false on failure, never throws

When constructed with dryRun: true, every write operation prints what it would do and returns immediately — reads (diff, status) still execute so the message can be generated.

src/commit-message.ts — Rule-based message generator

Parses the raw diff text with no external dependencies:

  1. Parse — regex scans for diff --git headers to extract file paths. Chunk context determines status (new file mode → added, deleted file mode → deleted, rename to → renamed, else modified).

  2. Detect type — ordered rule table:

    • All test files → test
    • All markdown/docs → docs
    • All CSS/SCSS → style
    • All config/lock files → chore
    • Any new .ts/.js file → feat
    • Diff text contains fix/bug/error/patch → fix
    • Fallback → chore
  3. Detect scope — iterates the directory parts of each changed file path to find the longest common prefix (e.g. src/auth/login.ts + src/auth/register.ts → scope src/auth).

  4. Build description — groups files by action verb (add, update, remove, rename). Lists up to 3 file basenames per verb, then appends +N more.

  5. Assemble + truncate — joins into type(scope): description, truncates to 72 chars.

The entire function is synchronous and pure — given the same diff it always produces the same message.

src/autocommit.ts — Orchestrator

Owns the lifecycle. On startup it loads config, constructs GitService, and starts FileWatcher. When the watcher fires onBatchReady:

  1. Conflict guard — skips if git status shows unresolved conflicts.
  2. Concurrency guardisProcessing flag prevents a second batch running while the first is still in progress.
  3. Stage → diff — calls git.stageAll() then git.getStagedDiff(). If diff is empty (nothing changed from Git's perspective), exits silently.
  4. Generate — calls generateCommitMessage(diff) synchronously.
  5. Commit — calls git.commit(message).
  6. Push — if autoPush is true, calls git.push(). Logs a warning on failure but does not throw.
  7. Error handling — entire flow is wrapped in try/catch/finally; isProcessing is always reset in finally.

src/logger.ts — Structured logger

Singleton (export const logger) used by all modules. Each log call:

  1. Prints to the console (console.log/warn/error) with a human-readable prefix.
  2. Appends a JSON line to .auto-commit.log.
  3. Before appending, checks the file size — if ≥ 5 MB it renames the file to .auto-commit.log.1 (overwriting any older backup) and starts a fresh file.

Logging failures are silently swallowed so a disk-full situation never crashes the watcher.

src/index.ts — CLI entry point

Parses --dry-run and --help flags from process.argv. Loads .env via dotenv. Constructs AutoCommitService and calls start(). Registers SIGINT/SIGTERM handlers that call service.stop() for a clean shutdown (closes the chokidar watcher, clears the debounce timer).


Data Flow (detailed)

1. User saves a file
        │
        ▼
2. chokidar emits 'change' event
        │
        ▼
3. FileWatcher adds path to changeBuffer, resets debounce timer
        │
        ▼  (timer expires after debounceSeconds with no new events)
        │
4. onBatchReady(paths[]) called
        │
        ▼
5. AutoCommitService.processBatch()
   ├─ hasUnresolvedConflicts? → skip
   ├─ isProcessing?           → skip
   ├─ git add -A
   ├─ git diff --cached       → empty? → skip
   ├─ generateCommitMessage(diff)
   ├─ git commit -m "<message>"
   └─ git push origin main    → fail? → log warning, continue
        │
        ▼
6. Logger writes JSON line to .auto-commit.log

Dependencies

Package Role
chokidar Cross-platform file system watching
simple-git Promise-based wrapper around the git CLI
zod Runtime config schema validation with TypeScript inference
dotenv Loads .env into process.env at startup
typescript Type safety and compilation
tsx TypeScript execution for dev mode (no build step)
pm2 Process manager — keeps the service alive, handles restarts

No AI SDK. No network calls for commit message generation. No API keys required.

About

A zero-cost local background service that watches your repository for file changes, automatically stages them, generates a conventional commit message by analysing the diff, commits, and pushes to GitHub, with no manual intervention and no API keys required.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors