@masgeek/oco-lite
v1.0.1
Published
Minimal, dependency-free git commit message generator using a local Ollama model. Single-line, per-file, per-hunk, or grouped-by-related-change commits, with breaking-change detection.
Maintainers
Readme
oco-lite
Minimal, dependency-free git commit message generator powered by a local
Ollama model. Built as a lightweight replacement for
OpenCommit after hitting two
unresolved OpenCommit issues: multi-line output for single-file changes
(#315,
#114) and a Windows
ESM path crash in @commitlint prompt-module mode.
oco-lite talks to your local Ollama instance directly with a strict,
structured prompt — no OpenCommit dependency, no ESM config loading, full
control over the output format.
Features
- Single-line, strictly-formatted Conventional Commits messages
- Structured JSON model output (reliable to parse — no prose-scraping)
- Breaking change detection — flags and marks commits per spec (
type(scope)!: subject+BREAKING CHANGE:footer) - Four granularities:
- one message for all staged changes
- one commit per staged file (
--split) - one commit per logical diff hunk (
--split-hunks) - one commit per group of related files (
--group) — the only mode that can combine multiple files into a single commit
- Pre-flight checks (git repo detection, Ollama reachability)
- Request timeout handling — no hangs on a stuck Ollama call
- Zero npm dependencies — pure Node.js (
node:child_process,node:readline, nativefetch)
Requirements
- Node.js 18+ (uses native
fetch) - Ollama running locally with a pulled model, e.g.:
ollama pull qwen2.5-coder:7b - Git
Installation
Option A — Global CLI (recommended)
cd D:\Dev\js\oco-lite
pnpm add -g file:.
pnpm add -g .may fail on some pnpm/Windows setups withERR_PNPM_PACKAGE_MANAGER_ADD_RESOLVE_LATEST(pnpm treats the bare.as a registry package name instead of a local path). Use the explicitfile:protocol as shown above. If that still fails, pack and install the tarball instead:pnpm pack pnpm add -g ./oco-lite-1.0.0.tgz
Verify:
Get-Command oco-lite
oco-lite --helpIf oco-lite isn't found, open a fresh terminal — PATH changes from a
global install don't apply to already-open shells.
To update after editing oco-lite.mjs, re-run pnpm add -g file:. from
the project folder (this is a real install, not a live symlink — edits
aren't picked up automatically).
To remove:
pnpm remove -g oco-liteOption B — Git alias (no packaging)
git config --global alias.aicommit '!node D:/Dev/js/oco-lite/oco-lite.mjs --commit'
git config --global alias.aicommit-split '!node D:/Dev/js/oco-lite/oco-lite.mjs --split --commit'
git config --global alias.aicommit-hunks '!node D:/Dev/js/oco-lite/oco-lite.mjs --split-hunks --commit'Then from any repo: git aicommit, git aicommit-split, git aicommit-hunks.
Option C — Per-project npm script
Add to package.json:
{
"scripts": {
"commit": "node oco-lite.mjs",
"commit:go": "node oco-lite.mjs --commit",
"commit:split": "node oco-lite.mjs --split --commit",
"commit:hunks": "node oco-lite.mjs --split-hunks --commit"
}
}Adjust the path to oco-lite.mjs if it isn't at the project root.
Usage
git add <files>
oco-lite # print a message for all staged changes (no commit)
oco-lite --commit # generate, confirm, then commit
oco-lite -y # generate and commit, no confirmation
oco-lite --split # print one message per staged file
oco-lite --split --commit # one commit per staged file, confirm each
oco-lite --split -y # one commit per staged file, no prompts
oco-lite --split-hunks --commit # one commit per logical diff hunk
oco-lite --split-hunks -y # same, no confirmation prompts
oco-lite --group --commit # cluster related staged files, one commit per group
oco-lite --group -y # same, no confirmation prompts
oco-lite --no-breaking-detection # skip breaking-change classification
oco-lite --help # usage summaryWhich mode should I use?
| Mode | Granularity | Use when |
|---|---|---|
| default | one commit for everything staged | quick, single logical change across files |
| --split | one commit per file | changes to unrelated files staged together |
| --split-hunks | one commit per diff hunk | one file with several distinct, unrelated edits (e.g. multiple refactors in one seeder/class) |
| --group | one commit per detected group of related files | several files staged together, some of which belong to the same feature/fix and some don't (e.g. a migration + model + seeder for one feature, plus an unrelated typo fix elsewhere) |
--group — how it works and its limits
- Sends all staged files' diffs to the model in a single request, each
truncated to
OCOLITE_GROUP_MAX_DIFF_PER_FILEcharacters - The model returns a JSON list of groups, each with its own file list, type, scope, subject, and breaking-change flag — files are clustered together only when they implement the same logical change
- Every staged file is guaranteed to end up in exactly one commit:
- if the model misses or duplicates a file, leftover files are swept
into a trailing
chore: update remaining filesgroup rather than silently dropped - if the model's response doesn't parse into a valid grouping at all,
it automatically falls back to
--split(one commit per file)
- if the model misses or duplicates a file, leftover files are swept
into a trailing
- If only one file is staged, there's nothing to group — it falls back to the default single-commit mode
Caveats:
- Sends every staged file's diff in one request, so it's more sensitive to
OCOLITE_TIMEOUT_MSand model context length on large changesets than the other modes. LowerOCOLITE_GROUP_MAX_DIFF_PER_FILEif you're staging many files at once. - Grouping quality depends on the model actually recognizing the relationship between files (e.g. a migration and the model it backs) — weaker models may under- or over-group. Review the printed groups before confirming each commit.
Breaking change detection
Every generated message is classified for breaking-change risk:
- removed or renamed public function/class/route
- changed method signature
- migration that alters an existing schema/column
- changed config key or environment variable
When flagged, the message is marked per the Conventional Commits spec:
feat(api)!: remove legacy /v1/schools endpoint
BREAKING CHANGE: clients calling /v1/schools must migrate to /v2/schoolsDetection is deliberately conservative — routine refactors, new files, and
additive/optional changes are not flagged. If it's still too
trigger-happy for your codebase, disable it with --no-breaking-detection.
--split-hunks — how it works and its limits
- Unstages everything (
git reset) - Parses each file's staged diff into individual
@@hunks - Re-stages one hunk at a time via
git apply --cached— the same underlying mechanismgit add -puses - Generates a message scoped to just that hunk and commits it before moving to the next
Caveats:
- New, deleted, and binary files can't be meaningfully split — they're committed whole, as a single commit, automatically.
- If a hunk's patch fails to apply cleanly during the sequential process (rare — usually adjacent/overlapping context conflicts), that file falls back to committing all its remaining changes as one commit. A warning is printed; nothing is lost.
- If the script is interrupted mid-run, you may end up with some hunks
committed and the rest left unstaged in your working tree. Just
git addand re-run.
Test on a low-stakes branch or file before trusting it on real work.
Configuration
All configuration is via environment variables — no config file.
| Variable | Default | Description |
|---|---|---|
| OCOLITE_MODEL | qwen2.5-coder:7b | Ollama model to use |
| OCOLITE_HOST | http://localhost:11434 | Ollama API base URL |
| OCOLITE_MAX_DIFF | 6000 | Max characters of diff sent per request (larger diffs are truncated) |
| OCOLITE_TIMEOUT_MS | 60000 | Per-request timeout to Ollama, in milliseconds |
| OCOLITE_GROUP_MAX_DIFF_PER_FILE | 1200 | Max characters of each file's diff sent in --group mode (all staged files are sent together in one request) |
Set persistently on Windows (per-user):
[System.Environment]::SetEnvironmentVariable('OCOLITE_MODEL', 'qwen2.5-coder:7b', 'User')(requires a fresh terminal to take effect)
Why not OpenCommit?
oco-lite exists because of two blocking bugs hit while using OpenCommit
with a local Ollama model:
- Multi-line commit messages for a single logical change —
OCO_ONE_LINE_COMMIT=trueandOCO_DESCRIPTION=falsedo not reliably collapse output to one line; this is a known, unresolved upstream issue (#315, #114) baked into theconventional-commitprompt module's template, independent of which model answers it. - Windows ESM path crash in
@commitlintmode — switchingOCO_PROMPT_MODULE=@commitlintfails withOnly URLs with a scheme in: file, data, and node are supported by the default ESM loaderwhen trying toimport()commitlint.config.jsusing a rawD:\...path instead of afile://URL.
oco-lite sidesteps both by owning the full prompt → response → format
pipeline directly.
Troubleshooting
"Cannot reach Ollama at http://localhost:11434"
Ollama isn't running. Start it with ollama serve, or set OCOLITE_HOST
if it's running elsewhere.
Message still splits into multiple commits for one file
Use --split-hunks instead of the default mode — it separates by logical
diff hunk rather than relying on the model to compress everything into one
line.
Ollama request timed out
Increase OCOLITE_TIMEOUT_MS, reduce OCOLITE_MAX_DIFF, or switch to a
smaller/faster model via OCOLITE_MODEL.
git commit failed
Usually a pre-commit hook or lint-staged failure unrelated to oco-lite —
check the git output printed above the error; it's passed through via
stdio: 'inherit'.
