@synmux/claude-commit 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,204 @@
1
+ # Changelog
2
+
3
+ All notable changes to `claude-commit` are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ Each version also has a [GitHub release](https://github.com/synmux/claude-commit/releases)
8
+ carrying the same notes at greater length.
9
+
10
+ ## [1.0.4] - 2026-09-11
11
+
12
+ ### [1.0.4] - Added
13
+
14
+ - **Filenames-only mode.** Set `"filenamesOnly": true` in your config, or
15
+ pass `-f` / `--filenames-only`, to skip the summariser and send only
16
+ filenames to the final model. This reduces model work at the cost of
17
+ broader, less useful messages; the default remains `false`.
18
+ - Filename lists retain `ignore` filtering and `lowPriorityPaths` weighting,
19
+ including both paths of renames and copies. Formatting, templates, custom
20
+ instructions and interactive options still apply; the model is instructed
21
+ to avoid inventing specific edits or motivations from filenames alone.
22
+ - Verbose output identifies when the summariser was skipped. In this mode,
23
+ library results contain an empty `summaries` array and `chunkCount: 0`,
24
+ and the summary model is never called or preloaded.
25
+
26
+ ## [1.0.3] - 2026-09-11
27
+
28
+ ### Added
29
+
30
+ - **Ollama models.** Any model can run on a local or self-hosted
31
+ [Ollama](https://ollama.com) server by prefixing its name with `ollama:`;
32
+ everything after the prefix is the Ollama model name verbatim, tag
33
+ included. The two pipeline stages resolve independently, so the diff can be
34
+ summarised locally and free while the final message still goes to Claude.
35
+ `cco` uses the native `/api/chat` endpoint rather than either compatibility
36
+ layer, because only the native API can set a context length.
37
+ - **`ollama.context`**, defaulting to `"auto"`. Ollama truncates an oversized
38
+ prompt silently - HTTP 200, oldest content dropped, nothing in the response
39
+ to say so - so `cco` never lets the window stay implicit: it preloads the
40
+ model with no window set, reads the server's own VRAM-based choice back from
41
+ `/api/ps`, pins that number on every request, sizes chunks against it, and
42
+ checks the returned token counts to catch a truncation that happened anyway.
43
+ A number pins the window instead.
44
+ - **`ollama.host`** (defaults to `$OLLAMA_HOST`, then `http://localhost:11434`)
45
+ and **`ollama.keepAlive`**, plus the `--ollama-host` and `--ollama-context`
46
+ flags.
47
+ - **`lowPriorityPaths`.** Gitignore-style patterns for churn-heavy paths whose
48
+ changes are summarised separately, so they cannot crowd out the code in the
49
+ final message. The content is still read and still costs the same; only its
50
+ weight changes. `--no-low-priority-paths` disables it for one run.
51
+ - **`ignore`.** The same pattern syntax, but matching file sections are removed
52
+ from the diff before anything else looks at it - before the low-priority
53
+ partition, before chunking, before any model call. The files are still
54
+ committed; `ignore` governs what the model reads, never what git stages. When
55
+ a pattern matches everything, `cco` stops with an error naming the directive
56
+ rather than inventing a message about changes you told it not to read.
57
+ `--no-ignore` disables it for one run.
58
+
59
+ ### Changed
60
+
61
+ - The default spinner is now `material`.
62
+ - `@opentui/core` bumped to 0.5.7, alongside SDK and skill dependency bumps.
63
+ - CI workflows bump `checkout`, `setup-node` and `claude-code-action`.
64
+
65
+ ## [1.0.2] - 2026-07-27
66
+
67
+ ### [1.0.2] - Changed
68
+
69
+ - The default spinner is now `dwarfFortress` instead of `bouncingBall`.
70
+
71
+ ## [1.0.1] - 2026-07-27
72
+
73
+ ### [1.0.1] - Added
74
+
75
+ - **A configurable spinner.** The `spinner` config key accepts any name from
76
+ the [cli-spinners](https://github.com/sindresorhus/cli-spinners) set bundled
77
+ with [ora](https://github.com/sindresorhus/ora). Unknown names fall back to
78
+ the default rather than throwing, because a cosmetic option must never be
79
+ able to break a commit.
80
+
81
+ ### [1.0.1] - Changed
82
+
83
+ - The progress spinner is an ora instance rather than a hand-rolled frame
84
+ timer. Enablement is still decided solely by the existing TTY and
85
+ `--no-spinner` checks - ora's own CI auto-detection is bypassed - and a final
86
+ success or failure line still prints when the animation is disabled.
87
+
88
+ ## [1.0.0] - 2026-07-24
89
+
90
+ ### [1.0.0] - Added
91
+
92
+ - **`--skip-armored`** and the matching `skipArmored` config key, replacing
93
+ each run of armoured or encoded lines with a short marker. Runs of one or two
94
+ lines survive, because a lone URL or hash is content. Ciphertext is
95
+ unreadable to the model and re-encrypts nondeterministically on every
96
+ `chezmoi re-add`, so this is the recommended mode for repos holding encrypted
97
+ files.
98
+
99
+ ### [1.0.0] - Fixed
100
+
101
+ - **Chunks are sized by real token density.** `Prompt is too long` returned,
102
+ and the isolation fix in 0.1.3 turned out to have treated a symptom. Armoured
103
+ and base64 content tokenises at roughly 1.14 chars per token on current
104
+ Claude models, while chunk budgets assumed the configured `charsPerToken` of
105
+ 3.5: a 1,247,318-character diff was estimated at ~356k tokens and really
106
+ counted ~1.19M. Long unbroken runs are now priced at their own, much denser
107
+ ratio. A third-party "real tokeniser" would not fix this class of bug -
108
+ tiktoken-style vocabularies compress base64 about three times better than
109
+ Claude's actual tokeniser, so they underestimate the same way.
110
+ - **The summary stage is a work queue.** If the backend rejects a chunk anyway
111
+ (its rejection is free, unbilled, and the only authoritative count), the
112
+ budget is halved, that chunk is re-split, and processing continues in place.
113
+
114
+ ### [1.0.0] - Changed
115
+
116
+ - The 0.1.3 documentation blaming these failures on MCP/skill leakage is
117
+ corrected. That isolation stays as hygiene and cost control, but density was
118
+ the bug.
119
+
120
+ ## [0.1.4] - 2026-07-23
121
+
122
+ ### [0.1.4] - Fixed
123
+
124
+ - `cco --version` reported a hardcoded string rather than the installed package
125
+ version.
126
+
127
+ ## [0.1.3] - 2026-07-23
128
+
129
+ ### [0.1.3] - Fixed
130
+
131
+ - **Requests no longer inherit your global Claude Code context.** Runs failed
132
+ reporting ~1,149k tokens against a conversation of only ~301k; the missing
133
+ ~848k were MCP tool definitions and skills pulled from the user's global
134
+ configuration into what should be an isolated prompt-in, text-out request.
135
+ The Agent SDK gates each context source separately, and `settingSources: []`
136
+ disables only settings files and `CLAUDE.md` - MCP servers and plugins load
137
+ regardless, and the CLI performs skill discovery even when the `skills`
138
+ option is omitted. Every switch is now set explicitly in one tested
139
+ `buildQueryOptions()`.
140
+ - **The chunk budget is clamped to the summary model's context window.**
141
+ `maxChunkTokens` was converted straight into a character budget regardless of
142
+ the configured model, so on any 200k-context model a single chunk could
143
+ exceed the whole window. It is now a cap rather than a promise, clamped to
144
+ the model's window minus a 32k reserve.
145
+
146
+ ### [0.1.3] - Added
147
+
148
+ - npm publishing on version tags via CI.
149
+
150
+ ## [0.1.2] - 2026-07-09
151
+
152
+ ### [0.1.2] - Changed
153
+
154
+ - **Both pipeline stages default to `sonnet`.** The final stage's input is a
155
+ handful of summaries and its output is the entire point of the tool, so a
156
+ strong model there costs almost nothing and writes a visibly better message.
157
+
158
+ ### [0.1.2] - Added
159
+
160
+ - MIT licence.
161
+
162
+ ## [0.1.1] - 2026-07-02
163
+
164
+ ### [0.1.1] - Changed
165
+
166
+ - Minor tweaks.
167
+
168
+ ## [0.1.0] - 2026-07-02
169
+
170
+ The first release, published as `@synmux/claude-commit` and providing the `cco`
171
+ and `claude-commit` binaries.
172
+
173
+ ### [0.1.0] - Added
174
+
175
+ - **The two-stage pipeline.** The diff is split into chunks that fit the
176
+ context window, each chunk is summarised, and the summaries are handed back
177
+ to a model to write the final message. Diffs too large for a single context
178
+ window simply produce more chunks.
179
+ - **Message formatting.** Conventional Commits (`-c`), gitmoji (`-g`),
180
+ multi-line subject and body (`-m`), a first-line template (`-t`), and
181
+ free-form extra instructions (`-p`).
182
+ - **Interactive mode.** `cco -i` lists candidate messages in a TUI, generated
183
+ at a higher temperature for variety, with the scrollable diff alongside.
184
+ - **Layered configuration.** Built-in defaults, a global user config, a
185
+ `claude-commit` key in the repo's `package.json`, the nearest dotted config
186
+ file, then CLI flags.
187
+ - **Structured output.** The final message is requested as JSON against a
188
+ `{ messages: string[] }` schema rather than parsed out of free-form text,
189
+ degrading to a temperature-free request and then to delimiter parsing.
190
+ - **`allowApiKey`, defaulting to false.** An exported `ANTHROPIC_API_KEY` would
191
+ otherwise silently switch every generation from subscription auth to
192
+ pay-as-you-go billing, because the SDK subprocess inherits `process.env`.
193
+ Those credentials are stripped unless you opt in.
194
+
195
+ [1.0.4]: https://github.com/synmux/claude-commit/compare/1.0.3...1.0.4
196
+ [1.0.3]: https://github.com/synmux/claude-commit/compare/1.0.2...1.0.3
197
+ [1.0.2]: https://github.com/synmux/claude-commit/compare/1.0.1...1.0.2
198
+ [1.0.1]: https://github.com/synmux/claude-commit/compare/1.0.0...1.0.1
199
+ [1.0.0]: https://github.com/synmux/claude-commit/compare/0.1.4...1.0.0
200
+ [0.1.4]: https://github.com/synmux/claude-commit/compare/0.1.3...0.1.4
201
+ [0.1.3]: https://github.com/synmux/claude-commit/compare/0.1.2...0.1.3
202
+ [0.1.2]: https://github.com/synmux/claude-commit/compare/0.1.1...0.1.2
203
+ [0.1.1]: https://github.com/synmux/claude-commit/compare/0.1.0...0.1.1
204
+ [0.1.0]: https://github.com/synmux/claude-commit/releases/tag/0.1.0
package/README.md CHANGED
@@ -20,18 +20,27 @@ feat(auth): add error handling and refresh token rotation to login
20
20
  Want more details? See [WALKTHROUGH.md](WALKTHROUGH.md).
21
21
 
22
22
  ```plaintext
23
- staged diff ──split──▶ [chunk, …] ──sonnet──▶ summaries ──sonnet──▶ commit message
23
+ staged diff ──ignore──▶ ──split──▶ [chunk, …] ──sonnet──▶ summaries ──sonnet──▶ commit message
24
24
  ```
25
25
 
26
26
  1. **Summarize** - the diff is split into chunks that fit the context window and
27
27
  each chunk is summarized by a strong model (`sonnet`, which carries a native
28
28
  1M-token context). Diffs larger than 1M tokens simply produce more chunks.
29
+ Paths listed in [`ignore`](#ignoring-paths-entirely) are dropped first and
30
+ never read at all; changes under configured
31
+ [low-priority paths](#low-priority-paths) are summarized separately, so churn
32
+ cannot crowd out the code.
29
33
  2. **Write** - the summaries are handed to the same model (`sonnet`) to write the
30
34
  final commit message according to your formatting rules. The message is the
31
35
  whole point of the tool, and its input is tiny, so a strong model here costs
32
36
  almost nothing extra.
33
37
 
34
- Both stages run through the [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview).
38
+ Either stage can run on a local [Ollama](#ollama-models) model instead; by
39
+ default both go through the
40
+ [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview).
41
+
42
+ For less model work at the cost of less useful messages, enable
43
+ [`filenamesOnly`](#filenames-only-mode) to skip summarisation entirely.
35
44
 
36
45
  ## Install
37
46
 
@@ -64,6 +73,9 @@ instead (pay-as-you-go), opt in explicitly in your configuration:
64
73
  { "allowApiKey": true }
65
74
  ```
66
75
 
76
+ Ollama models sit outside all of this: they run on a server you control, over
77
+ plain HTTP, with no credential at all. See [Ollama models](#ollama-models).
78
+
67
79
  ## Usage
68
80
 
69
81
  ```sh
@@ -86,8 +98,14 @@ and asks for confirmation before committing. Pass `-y` to skip the prompt, or
86
98
  | `-m, --multiline` / `--no-multiline` | Write a multi-line commit (subject + body), or force a single line |
87
99
  | `-t, --template <tpl>` | Template for the first line, e.g. `"[PROJ-1] {message}"` |
88
100
  | `-p, --prompt <text>` | Extra instructions appended to the prompt |
101
+ | `-f, --filenames-only` | Skip summarisation and send only filenames to the final model |
89
102
  | `--model-summary <model>` | Model used to summarize the diff (default `sonnet`) |
90
103
  | `--model-final <model>` | Model used to write the message (default `sonnet`) |
104
+ | `--skip-armored` | Omit armored/encoded lines (age/gpg armor, base64 blobs) from the summarized diff |
105
+ | `--no-low-priority-paths` | Ignore `lowPriorityPaths` for this run, so every change weighs the same |
106
+ | `--no-ignore` | Disregard `ignore` for this run, so every staged change is read |
107
+ | `--ollama-host <url>` | Base URL of the Ollama server for `ollama:` models |
108
+ | `--ollama-context <tokens>` | Context window requested from Ollama models |
91
109
  | `-d, --dry-run` | Print the message to stdout without committing |
92
110
  | `-y, --yes` | Commit without asking for confirmation |
93
111
  | `--no-spinner` | Disable the progress spinner |
@@ -101,6 +119,7 @@ cco # generate, confirm, and commit staged changes
101
119
  cco -a -c # stage everything and write a Conventional Commit
102
120
  cco -c -g -m # conventional + gitmoji + a body
103
121
  cco -i -n 5 # pick from 5 options interactively
122
+ cco -f --dry-run # generate from filenames only, without committing
104
123
  cco --dry-run | cat # print a message without committing (TUI-free, pipe-safe)
105
124
  git commit -F <(cco -d) # use the message with your own git invocation
106
125
  ```
@@ -108,6 +127,31 @@ git commit -F <(cco -d) # use the message with your own git invocation
108
127
  In a pipe (no TTY) there is no spinner and no confirmation prompt - `cco` just
109
128
  generates and commits (or prints, with `--dry-run`).
110
129
 
130
+ ## Filenames-only mode
131
+
132
+ Set `"filenamesOnly": true` in any config layer, or pass `-f` /
133
+ `--filenames-only`, to send only the list of filenames touched by staged
134
+ changes to `models.final`. The default is `false`.
135
+
136
+ ```json
137
+ { "filenamesOnly": true }
138
+ ```
139
+
140
+ The summariser is skipped entirely: no diff chunks, summary calls, or
141
+ summary-model preload. The final model receives no file contents or diff
142
+ hunks, so expect broader, less useful messages. It is instructed to describe
143
+ the affected areas without inventing specific edits or reasons for them.
144
+ Normal formatting, custom instructions and interactive options still apply.
145
+
146
+ `ignore` still removes matching file sections, and `lowPriorityPaths` still
147
+ groups and weights the remaining filenames. Renames and copies include both
148
+ paths; additions, deletions, binary files and mode changes are included.
149
+ If every staged file is ignored, generation still stops with an error.
150
+
151
+ `models.summary`, `maxChunkTokens`, `charsPerToken` and `skipArmored` have no
152
+ effect in this mode. `--verbose` reports that the summariser was skipped;
153
+ library results have an empty `summaries` array and `chunkCount: 0`.
154
+
111
155
  ## Interactive mode
112
156
 
113
157
  `cco -i` opens a TUI listing several candidate messages to choose from. The
@@ -151,14 +195,22 @@ keys are valid at every level:
151
195
  "interactive": true,
152
196
  "interactiveCount": 3,
153
197
  "interactiveTemperature": 1,
154
- "spinner": "dwarfFortress",
198
+ "spinner": "material",
155
199
  "models": {
156
200
  "summary": "sonnet",
157
201
  "final": "sonnet"
158
202
  },
159
203
  "maxChunkTokens": 600000,
160
204
  "charsPerToken": 3.5,
205
+ "filenamesOnly": false,
161
206
  "skipArmored": false,
207
+ "lowPriorityPaths": [],
208
+ "ignore": [],
209
+ "ollama": {
210
+ "host": "http://localhost:11434",
211
+ "context": "auto",
212
+ "keepAlive": null
213
+ },
162
214
  "allowApiKey": false
163
215
  }
164
216
  ```
@@ -166,11 +218,11 @@ keys are valid at every level:
166
218
  `spinner` chooses the progress animation: any name from the
167
219
  [cli-spinners](https://github.com/sindresorhus/cli-spinners) set bundled with
168
220
  [ora](https://github.com/sindresorhus/ora) (`"dots"`, `"moon"`, `"pong"`,
169
- `"dwarfFortress"`, ...). Unknown names are ignored and the default
170
- `dwarfFortress` is used. `--no-spinner` disables the animated spinner, but final
221
+ `"material"`, ...). Unknown names are ignored and the default
222
+ `material` is used. `--no-spinner` disables the animated spinner, but final
171
223
  status lines still print.
172
224
 
173
- The `dwarfFortress` spinner is chosen because it's fucking cool. Fight me.
225
+ The `material` spinner is chosen because it's fucking cool. Fight me.
174
226
 
175
227
  `maxChunkTokens` is a cap, not a promise: at run time it is clamped to the
176
228
  summary model's context window minus a fixed reserve (1M-window models such as
@@ -195,6 +247,186 @@ every `chezmoi re-add` re-encrypts nondeterministically and produces megabytes
195
247
  of churned armor. Drop a `.claude-commit.json` with `{ "skipArmored": true }`
196
248
  in the repo root to enable it per-repo.
197
249
 
250
+ ### Low-priority paths
251
+
252
+ Some paths change a lot without meaning much - generated docs, lockfiles,
253
+ vendored snapshots, build output. Left alone, a commit that touches twenty
254
+ lines of code and regenerates two thousand lines of tooling gets a subject
255
+ line about the tooling. `lowPriorityPaths` lists gitignore-style patterns for
256
+ those paths:
257
+
258
+ ```json
259
+ {
260
+ "lowPriorityPaths": [".agents/skills/*-skilld", "bun.lock", "!bun.lock.keep"]
261
+ }
262
+ ```
263
+
264
+ Changes under matching paths are summarised separately and briefly, and the
265
+ model is told that the subject line - and the commit type, scope and gitmoji
266
+ where you use them - comes from the _other_ changes, however small they are.
267
+ The low-priority changes are mentioned in the subject only if they fit, and in
268
+ the body (with `multiline`) only after the primary changes. When _every_
269
+ changed file is low priority there is nothing for it to yield to, so the
270
+ changes are described normally, exactly as if no patterns were configured.
271
+
272
+ Pattern rules follow `.gitignore` conventions, so trunk or gitignore lines can
273
+ usually be copied in:
274
+
275
+ - A pattern with a `/` in it is anchored at the repository root and matches a
276
+ path or any directory above it - `.agents/skills/*-skilld` covers every file
277
+ inside each matching directory.
278
+ - A pattern without a `/` matches any path segment at any depth - `bun.lock`
279
+ matches `packages/app/bun.lock`; `*-skilld` matches everything inside any
280
+ `*-skilld` directory.
281
+ - `*` matches dotfiles and does not cross `/`; `**` does; `{a,b}` expands. A
282
+ leading `/` or `./` anchors, a trailing `/` is ignored. The anchoring
283
+ decision looks at the whole pattern, so a `/` inside a brace group anchors
284
+ all of its alternatives - prefer one pattern per intent.
285
+ - A leading `!` negates, and the last matching pattern wins:
286
+ `["docs/**", "!docs/adr/**"]` deprioritises docs except the ADRs.
287
+ - Patterns are always matched against repository-root-relative paths with
288
+ `/` separators, whichever directory you run `cco` from. A backslash in a
289
+ pattern is an escape (`\[`, `\{`, `\!` for the literal characters), so
290
+ Windows-style `dist\**` matches nothing.
291
+ - Patterns are not validated: a typo such as an unbalanced `{` is parsed
292
+ rather than rejected and may match something unexpected, so check the
293
+ `--verbose` match counts when you add one.
294
+
295
+ A rename into or out of a low-priority path counts as primary (both sides
296
+ must match). The nearest config layer that sets the key wins outright - lists
297
+ are never merged - so `"lowPriorityPaths": []` in a project opts out of a
298
+ global list, and a project that wants the global patterns plus its own must
299
+ repeat them. `--no-low-priority-paths` switches the feature off for one run,
300
+ which is handy when the churn _is_ the story, or for comparing messages while
301
+ tuning patterns.
302
+
303
+ This changes how changes are _weighted_ in the message, not how much of the
304
+ diff is read: low-priority content is still summarised in full, at the same
305
+ cost. To skip content outright, see `skipArmored`. Under `--verbose`, `cco`
306
+ reports how many files matched (`low-priority paths: matched 3 of 41 files`),
307
+ which is the only way to tell a pattern that matched nothing from one that
308
+ matched everything and was promoted.
309
+
310
+ ### Ignoring paths entirely
311
+
312
+ `lowPriorityPaths` still reads everything it deprioritises, and pays for it.
313
+ Some content is worth neither the tokens nor the time: a vendored dependency
314
+ tree, a generated API client, a data fixture that changes wholesale.
315
+
316
+ `ignore` takes the same gitignore-style patterns and removes those file
317
+ sections from the diff **before anything else looks at it** - before the
318
+ low-priority partition, before chunking, before any model call:
319
+
320
+ ```json
321
+ { "ignore": ["vendor/**", "**/__snapshots__", "*.generated.ts"] }
322
+ ```
323
+
324
+ The stages compose in the order their names suggest:
325
+
326
+ ```text
327
+ diff ─ ignore ─▶ ─ skipArmored ─▶ ─ lowPriorityPaths ─▶ chunks ─▶ summaries
328
+ ```
329
+
330
+ Two things worth being clear about:
331
+
332
+ - **The files are still committed.** `ignore` governs what the model reads,
333
+ never what git stages. `cco` is writing a message, not choosing a changeset.
334
+ - **When it matches _everything_, `cco` stops** with an error naming the
335
+ directive, rather than inventing a message about changes you told it not to
336
+ read. This is deliberately unlike `lowPriorityPaths`, which promotes its
337
+ partition in the same situation - "this matters less" can degrade
338
+ gracefully, "do not look at this" has nothing to degrade to. Pass
339
+ `--no-ignore` for that one commit.
340
+
341
+ As with `lowPriorityPaths`, a section is dropped only when it names at least
342
+ one path and _all_ of them match, so a rename out of an ignored directory
343
+ survives. `--verbose` reports the count
344
+ (`ignore: dropped 3 of 41 files before reading`).
345
+
346
+ ## Ollama models
347
+
348
+ Any model can be run on a local (or self-hosted) [Ollama](https://ollama.com)
349
+ server instead of Claude, by prefixing its name with `ollama:`. Everything
350
+ after the prefix is the Ollama model name **verbatim**, tag included:
351
+
352
+ ```json
353
+ {
354
+ "models": {
355
+ "summary": "ollama:ornith-1.5:35b",
356
+ "final": "sonnet"
357
+ }
358
+ }
359
+ ```
360
+
361
+ The two stages resolve independently, so that mixed setup is the interesting
362
+ one: reading the diff is the bulk of the work and the most sensitive thing
363
+ `cco` touches, so it runs locally and free, while the final message - one
364
+ short, quality-sensitive call on a summary - still goes to Claude. The prefix
365
+ works anywhere a model name does, including the flags:
366
+
367
+ ```sh
368
+ cco --model-summary ollama:ornith-1.5:35b --dry-run -v
369
+ ```
370
+
371
+ The server needs no credential. `cco` talks to Ollama's native `/api/chat`
372
+ endpoint, not either of its OpenAI/Anthropic compatibility layers, because
373
+ only the native API can set a context length.
374
+
375
+ ### Context length is the setting that matters
376
+
377
+ Ollama picks a context window for each model from available VRAM (4k / 32k /
378
+ 256k tiers, capped at the model's trained maximum), and a prompt that
379
+ exceeds it is truncated **silently** - HTTP 200, oldest content dropped,
380
+ nothing on the response to say so. A summary written from half a diff is
381
+ worse than no summary, so `cco` never lets that number stay implicit: it
382
+ sends an explicit window on every request, sizes its diff chunks against the
383
+ same number, and checks the token counts afterwards to catch a truncation
384
+ that happened anyway (in which case it re-splits the chunk and retries,
385
+ exactly as it does for a Claude context overflow).
386
+
387
+ Where the number comes from is `ollama.context`:
388
+
389
+ ```json
390
+ {
391
+ "ollama": {
392
+ "host": "http://localhost:11434",
393
+ "context": "auto",
394
+ "keepAlive": "10m"
395
+ }
396
+ }
397
+ ```
398
+
399
+ - `context` - `"auto"` (the default) **asks the server** rather than
400
+ guessing: the model is preloaded with no window set, so Ollama applies
401
+ its own VRAM-based choice, and `cco` reads that choice back from
402
+ `/api/ps` before sizing anything. That is the largest window Ollama
403
+ believes this machine can actually run - 131072 for a Gemma model on a
404
+ large Mac, 4096 for the same model on a small laptop - resolved once per
405
+ model per run, and shown under `--verbose`. A number pins the window
406
+ instead: lower it when memory is tight (usage scales with it, multiplied
407
+ by `OLLAMA_NUM_PARALLEL`), or raise it past the tier if you know your
408
+ hardware better than the server does. A smaller window is never a
409
+ correctness problem - `cco` just splits the diff into more chunks.
410
+ - `host` - defaults to `$OLLAMA_HOST`, then `http://localhost:11434`. A bare
411
+ `box.local:11434` gains an `http://`, matching Ollama's own convention.
412
+ - `keepAlive` - how long the server keeps the model loaded after a request: a
413
+ duration string (`"10m"`), seconds as a number, `0` to unload immediately,
414
+ or negative to pin it. `null` leaves the server's own default. Pinning is
415
+ worth it if you commit often; a 35b model takes a while to load.
416
+
417
+ ### What differs from a Claude model
418
+
419
+ - **Cost is reported as zero**, because local inference is not billed. In a
420
+ mixed run, `--verbose`'s total is exactly the Claude half.
421
+ - **Structured output** is requested through Ollama's `format` field. A model
422
+ or server that cannot honour it (Ollama Cloud does not support it at all)
423
+ falls back to plain-text parsing automatically.
424
+ - **A missing model is an error, not a download.** `cco` tells you to run
425
+ `ollama pull <model>` rather than pulling tens of gigabytes on your behalf.
426
+ - **Reasoning is never requested**, and any the model volunteers is
427
+ discarded - models disagree about whether thinking can even be switched
428
+ off, and asking is a good way to earn a 400.
429
+
198
430
  ## Development
199
431
 
200
432
  ```sh
@@ -202,6 +434,10 @@ bun test # run the test suite
202
434
  bun run typecheck # tsc --noEmit
203
435
  ```
204
436
 
437
+ What changed between versions is in [CHANGELOG.md](CHANGELOG.md), and at
438
+ greater length on the
439
+ [releases page](https://github.com/synmux/claude-commit/releases).
440
+
205
441
  ## Did you vibe this?
206
442
 
207
443
  I distinguish vibe coding and AI-assisted development by
package/index.ts CHANGED
@@ -9,10 +9,37 @@ export type {
9
9
  GenerateOptions,
10
10
  GenerateProgress,
11
11
  GenerateResult,
12
+ IgnoreStats,
13
+ LowPriorityStats,
14
+ OllamaContextWindow,
12
15
  } from "./src/generate";
13
- export { runPrompt } from "./src/agent";
16
+ export { runClaudePrompt, runPrompt } from "./src/agent";
14
17
  export type { RunPromptOptions } from "./src/agent";
15
- export { splitDiff } from "./src/diff";
18
+ export {
19
+ probeOllamaContext,
20
+ resolveOllamaContext,
21
+ resolveOllamaHost,
22
+ runOllamaPrompt,
23
+ } from "./src/ollama";
24
+ export {
25
+ DEFAULT_OLLAMA_CONTEXT,
26
+ DEFAULT_OLLAMA_CONTEXT_TOKENS,
27
+ DEFAULT_OLLAMA_HOST,
28
+ isOllamaModel,
29
+ OLLAMA_PREFIX,
30
+ parseModelRef,
31
+ } from "./src/models";
32
+ export type { ModelProvider, ModelRef } from "./src/models";
33
+ export {
34
+ applyIgnorePatterns,
35
+ diffPaths,
36
+ partitionDiff,
37
+ sectionPaths,
38
+ splitDiff,
39
+ } from "./src/diff";
40
+ export type { DiffPartition, IgnoreResult } from "./src/diff";
41
+ export { createPathMatcher, matchesPathPatterns } from "./src/paths";
42
+ export type { PathMatcher } from "./src/paths";
16
43
  export {
17
44
  DEFAULT_CONFIG,
18
45
  loadFileConfig,
@@ -26,14 +53,18 @@ export {
26
53
  buildSummaryUser,
27
54
  buildFinalSystem,
28
55
  buildFinalUser,
56
+ buildFilenamesUser,
29
57
  parseOptions,
30
58
  cleanMessage,
31
59
  } from "./src/prompts";
32
60
  export * as git from "./src/git";
33
61
  export { ClaudeCommitError } from "./src/errors";
34
62
  export type {
63
+ ChangePriority,
35
64
  Config,
65
+ DiffSummary,
36
66
  ModelConfig,
67
+ OllamaConfig,
37
68
  PartialConfig,
38
69
  ModelResult,
39
70
  FileChange,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@synmux/claude-commit",
3
- "version": "1.0.2",
4
- "description": "Generate git commit messages with Claude, using your Claude Code subscription.",
3
+ "version": "1.0.4",
4
+ "description": "Generate git commit messages with Claude, using your Claude Code subscription and/or Ollama.",
5
5
  "main": "index.ts",
6
6
  "module": "index.ts",
7
7
  "type": "module",
@@ -13,39 +13,53 @@
13
13
  "files": [
14
14
  "bin",
15
15
  "src",
16
- "index.ts"
16
+ "index.ts",
17
+ "CHANGELOG.md"
17
18
  ],
18
19
  "claude-commit": {
20
+ "allowApiKey": false,
21
+ "charsPerToken": 3.5,
19
22
  "conventionalCommits": true,
20
- "gitmoji": true,
21
- "multiline": true,
22
- "template": null,
23
23
  "customPrompt": null,
24
+ "filenamesOnly": false,
25
+ "gitmoji": true,
24
26
  "interactive": false,
25
- "interactiveCount": 3,
27
+ "interactiveCount": 10,
26
28
  "interactiveTemperature": 1,
27
- "spinner": "dwarfFortress",
29
+ "lowPriorityPaths": [
30
+ ".agents/**",
31
+ ".claude/**",
32
+ "bun.lock",
33
+ ".serena"
34
+ ],
35
+ "maxChunkTokens": 32768,
28
36
  "models": {
29
- "summary": "sonnet",
30
- "final": "sonnet"
37
+ "summary": "ollama:gemma4:e4b-mlx",
38
+ "final": "ollama:gemma4:e4b-mlx"
31
39
  },
32
- "maxChunkTokens": 600000,
33
- "charsPerToken": 3.5,
34
- "allowApiKey": false
40
+ "multiline": true,
41
+ "ollama": {
42
+ "host": "http://localhost:11434",
43
+ "context": "auto",
44
+ "keepAlive": "10m"
45
+ },
46
+ "skipArmored": true,
47
+ "spinner": "pong",
48
+ "template": null
35
49
  },
36
50
  "devDependencies": {
37
- "@anthropic-ai/claude-code": "^2.1.218",
51
+ "@anthropic-ai/claude-code": "^2.1.241",
38
52
  "@trunkio/launcher": "^1.3.4",
39
- "@types/bun": "^1.3.14",
53
+ "@types/bun": "^1.4.0",
40
54
  "prettier": "3.9.4",
41
- "skilld": "^2.0.0"
55
+ "skilld": "^2.3.0"
42
56
  },
43
57
  "peerDependencies": {
44
58
  "typescript": "^6.0.3"
45
59
  },
46
60
  "dependencies": {
47
- "@anthropic-ai/claude-agent-sdk": "^0.3.218",
48
- "@opentui/core": "^0.4.5",
61
+ "@anthropic-ai/claude-agent-sdk": "^0.3.241",
62
+ "@opentui/core": "^0.5.7",
49
63
  "cli-spinners": "^3.4.0",
50
64
  "commander": "^15.0.0",
51
65
  "ora": "^9.4.1"