git-cai-cli 0.17.2__tar.gz → 0.17.4__tar.gz

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.
Files changed (113) hide show
  1. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/ci/cai_config.ci.yml +5 -6
  2. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/workflows/python-tests.yml +1 -1
  3. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.mega-linter.yml +1 -1
  4. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/Makefile +9 -15
  5. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/PKG-INFO +1 -1
  6. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/cai_config.yml +6 -6
  7. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/docs/git-cai.txt +29 -24
  8. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/docs/man/git-cai.1 +31 -26
  9. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/_version.py +3 -3
  10. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/config.py +18 -6
  11. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/gitutils.py +39 -10
  12. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/llm.py +64 -8
  13. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/PKG-INFO +1 -1
  14. git_cai_cli-0.17.4/src/git_cai_cli.egg-info/scm_version.json +8 -0
  15. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_config.py +12 -0
  16. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_gitutils.py +50 -0
  17. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_llm.py +200 -3
  18. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_options.py +1 -1
  19. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/uv.lock +3 -3
  20. git_cai_cli-0.17.2/src/git_cai_cli.egg-info/scm_version.json +0 -8
  21. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.caiignore +0 -0
  22. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.gitattributes +0 -0
  23. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/cd/.SRCINFO +0 -0
  24. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/cd/PKGBUILD +0 -0
  25. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/ci/_version.py +0 -0
  26. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/ci/tokens.ci.yml +0 -0
  27. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/workflows/release.yml +0 -0
  28. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.github/workflows/release_aur.yml +0 -0
  29. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.gitignore +0 -0
  30. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.bandit.yml +0 -0
  31. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.checkov.yml +0 -0
  32. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.flake8 +0 -0
  33. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.ls-lint.yml +0 -0
  34. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.markdownlint.json +0 -0
  35. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.proselintrc.json +0 -0
  36. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.pylintrc +0 -0
  37. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/.yamllint.yml +0 -0
  38. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/check_git_branch_name.sh +0 -0
  39. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/lychee.toml +0 -0
  40. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.linters/pyrightconfig.json +0 -0
  41. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.markdownlintignore +0 -0
  42. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.semgrepignore +0 -0
  43. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/.trivyignore +0 -0
  44. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/CLAUDE.md +0 -0
  45. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/LICENSE +0 -0
  46. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/README.md +0 -0
  47. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/pyproject.toml +0 -0
  48. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/setup.cfg +0 -0
  49. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/__init__.py +0 -0
  50. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/cli/__init__.py +0 -0
  51. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/cli/cli.py +0 -0
  52. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/cli/helptext.py +0 -0
  53. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/cli/modes.py +0 -0
  54. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/__init__.py +0 -0
  55. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/changelog.py +0 -0
  56. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/completion.py +0 -0
  57. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/doctor.py +0 -0
  58. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/explain.py +0 -0
  59. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/generation.py +0 -0
  60. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/init.py +0 -0
  61. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/languages.py +0 -0
  62. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/options.py +0 -0
  63. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/pr.py +0 -0
  64. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/prompts_fallback.py +0 -0
  65. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/release.py +0 -0
  66. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/secrets.py +0 -0
  67. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/spinner.py +0 -0
  68. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/split.py +0 -0
  69. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/squash.py +0 -0
  70. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/stats.py +0 -0
  71. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/core/validate.py +0 -0
  72. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli/main.py +0 -0
  73. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/SOURCES.txt +0 -0
  74. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/dependency_links.txt +0 -0
  75. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/entry_points.txt +0 -0
  76. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/requires.txt +0 -0
  77. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/scm_file_list.json +0 -0
  78. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/src/git_cai_cli.egg-info/top_level.txt +0 -0
  79. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/conftest.py +0 -0
  80. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_cli_integration.py +0 -0
  81. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_config_integration.py +0 -0
  82. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_gitutils_integration.py +0 -0
  83. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_modes_integration.py +0 -0
  84. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_options_integration.py +0 -0
  85. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_pr_integration.py +0 -0
  86. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/integration/test_squash_integration.py +0 -0
  87. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_amend.py +0 -0
  88. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_branch_context.py +0 -0
  89. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_changelog.py +0 -0
  90. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_classification.py +0 -0
  91. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_cli.py +0 -0
  92. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_completion.py +0 -0
  93. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_conventional.py +0 -0
  94. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_custom_provider.py +0 -0
  95. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_doctor.py +0 -0
  96. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_explain.py +0 -0
  97. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_generation.py +0 -0
  98. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_helptext.py +0 -0
  99. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_init.py +0 -0
  100. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_main.py +0 -0
  101. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_modes.py +0 -0
  102. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_pr.py +0 -0
  103. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_print.py +0 -0
  104. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_prompt_loading.py +0 -0
  105. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_release.py +0 -0
  106. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_secrets.py +0 -0
  107. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_set_config.py +0 -0
  108. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_signoff.py +0 -0
  109. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_spinner.py +0 -0
  110. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_split.py +0 -0
  111. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_squash.py +0 -0
  112. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_stats.py +0 -0
  113. {git_cai_cli-0.17.2 → git_cai_cli-0.17.4}/tests/unit/test_validate.py +0 -0
@@ -6,14 +6,13 @@ load_tokens_from: /home/runner/work/.config/cai/tokens.yml
6
6
  timeout: 30
7
7
  full_files: false
8
8
  anthropic:
9
- model: claude-haiku-4-5
10
- temperature: 0
9
+ model: claude-haiku-5-5
11
10
  max_tokens: 32768
12
11
  deepseek:
13
- model: deepseek-chat
12
+ model: deepseek-flash
14
13
  temperature: 0
15
14
  gemini:
16
- model: gemini-3.1-flash-lite
15
+ model: gemini-3.5-flash-lite
17
16
  temperature: 0
18
17
  groq:
19
18
  model: openai/gpt-oss-20b
@@ -22,11 +21,11 @@ mistral:
22
21
  model: codestral-2508
23
22
  temperature: 0
24
23
  ollama:
25
- model: llama3.1
24
+ model: gemma4:e2b
26
25
  temperature: 0
27
26
  timeout: 300
28
27
  openai:
29
- model: gpt-5.4-mini
28
+ model: gpt-6-luna
30
29
  xai:
31
30
  model: grok-4.3
32
31
  temperature: 0
@@ -99,7 +99,7 @@ jobs:
99
99
 
100
100
  # MegaLinter
101
101
  - name: MegaLinter
102
- uses: oxsecurity/megalinter@15e5b45552097e318c93de385779ce3b1084052c # v10.0.0
102
+ uses: oxsecurity/megalinter@f2fe8c1cba37091848df3e524066c8c23ef353ed # v10.2.0
103
103
  id: ml
104
104
  env:
105
105
  VALIDATE_ALL_CODEBASE: false
@@ -54,7 +54,7 @@ LINTER_RULES_PATH: .linters
54
54
  LOG_LEVEL: INFO
55
55
  MARKDOWN_DEFAULT_STYLE: markdownlint
56
56
  # Pins the Docker image for local runs to the version the CI workflow pins.
57
- MEGALINTER_VERSION: v10.0.0
57
+ MEGALINTER_VERSION: v10.2.0
58
58
  PARALLEL: true
59
59
  PRINT_ALPACA: false
60
60
  PYTHON_BANDIT_RULES_PATH: .linters
@@ -1,10 +1,13 @@
1
1
  #!/usr/bin/make -f
2
2
 
3
- PIP := pipx
4
3
  UV := uv
5
4
  SHELL := /bin/bash
5
+ MEGALINTER_VERSION := $(shell sed -n 's/^MEGALINTER_VERSION: *//p' .mega-linter.yml)
6
+ MEGALINTER_IMAGE := ghcr.io/oxsecurity/megalinter:$(MEGALINTER_VERSION)
7
+ # --platform: the image is amd64 only. z: relabels the mount on SELinux hosts, ignored elsewhere.
8
+ MEGALINTER_RUN := docker run --rm --platform linux/amd64 -v "$(CURDIR):/tmp/lint:rw,z"
6
9
 
7
- .PHONY: help lint check-docker check-npx lint-fix add-lint-hook clean test
10
+ .PHONY: help lint check-docker lint-fix add-lint-hook clean test
8
11
 
9
12
  help: ## Shows this help message
10
13
  @echo "Available commands:"
@@ -27,25 +30,16 @@ check-docker: ## Checks if docker is installed
27
30
  docker --version; \
28
31
  fi
29
32
 
30
- check-npx: ## Checks if npx is installed
31
- @if ! command -v npx &> /dev/null; then \
32
- echo "npx is not installed. Please install it."; \
33
- exit 1; \
34
- else \
35
- echo "npx version:"; \
36
- npx --version; \
37
- fi
38
-
39
33
  clean: ## Clean cache of uv and delete virtual environment
40
34
  @$(UV) cache clean
41
35
  @rm -rf .venv
42
36
 
43
- lint:
37
+ lint: ## Checks the branch name and runs MegaLinter
44
38
  @sh ./.linters/check_git_branch_name.sh
45
- @npx mega-linter-runner@v10
39
+ @$(MEGALINTER_RUN) $(MEGALINTER_IMAGE)
46
40
 
47
- lint-fix: ## Lints the code using sqlfluff and fixes the issues
48
- @npx mega-linter-runner@v10 --fix
41
+ lint-fix: ## Runs MegaLinter and applies its fixes
42
+ @$(MEGALINTER_RUN) -e APPLY_FIXES=all $(MEGALINTER_IMAGE)
49
43
 
50
44
  test: ## Runs tests
51
45
  @$(UV) run pytest
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-cai-cli
3
- Version: 0.17.2
3
+ Version: 0.17.4
4
4
  Summary: Use LLM to create git commit messages
5
5
  Author-email: Thorsten Foltz <thorsten.foltz@live.com>
6
6
  License-Expression: MIT
@@ -18,15 +18,15 @@ pr_prompt_file: "/home/thorsten/.config/cai/pr_prompt.md"
18
18
  stats: true
19
19
  signoff: true
20
20
  anthropic:
21
- model: claude-haiku-4-5
22
- temperature: 0
21
+ model: claude-haiku-5-5
23
22
  max_tokens: 32768
24
23
  deepseek:
25
- model: deepseek-chat
24
+ model: deepseek-flash
26
25
  temperature: 0
27
26
  gemini:
28
- model: gemini-3.1-flash-lite
27
+ model: gemini-3.5-flash-lite
29
28
  temperature: 0
29
+ timeout: 60
30
30
  groq:
31
31
  model: openai/gpt-oss-20b
32
32
  temperature: 0
@@ -34,11 +34,11 @@ mistral:
34
34
  model: codestral-2508
35
35
  temperature: 0
36
36
  ollama:
37
- model: llama3.1
37
+ model: gemma4:e2b
38
38
  temperature: 0
39
39
  timeout: 300
40
40
  openai:
41
- model: gpt-5.6-luna
41
+ model: gpt-6-luna
42
42
  xai:
43
43
  model: grok-4.3
44
44
  temperature: 0
@@ -360,7 +360,7 @@ provider keys.
360
360
  git cai -H language=de
361
361
  git cai -H default=anthropic
362
362
  git cai -H emoji=false
363
- git cai -H groq.model=llama-3.3-70b
363
+ git cai -H groq.model=openai/gpt-oss-120b
364
364
  ----
365
365
 
366
366
  -h, --help::
@@ -455,7 +455,7 @@ identifier for that provider.
455
455
  +
456
456
  ----
457
457
  git cai -P openai -m gpt-4o
458
- git cai -P anthropic -m claude-haiku-4-5
458
+ git cai -P anthropic -m claude-haiku-5-5
459
459
  git cai -P ollama -m mistral
460
460
  ----
461
461
 
@@ -528,7 +528,7 @@ both.
528
528
  +
529
529
  ----
530
530
  git cai -P anthropic
531
- git cai -P groq -m llama-3.3-70b
531
+ git cai -P groq -m openai/gpt-oss-120b
532
532
  git cai -P openrouter
533
533
  ----
534
534
 
@@ -662,7 +662,7 @@ become integers or floats, everything else stays a string.
662
662
  ----
663
663
  git cai -S default=anthropic
664
664
  git cai -S emoji=false
665
- git cai -S groq.model=llama-3.3-70b
665
+ git cai -S groq.model=openai/gpt-oss-120b
666
666
  git cai -S openai.temperature=0.7
667
667
  git cai -S conventional=true
668
668
  ----
@@ -695,9 +695,10 @@ git cai -s a1b2c3d
695
695
 
696
696
  -T, --timeout SECONDS::
697
697
  Override the HTTP timeout for the LLM call in this invocation. The default
698
- is 30 seconds for remote providers and 300 seconds for Ollama (since local
699
- generation can be significantly slower). The override applies uniformly to
700
- whichever provider is selected for this run.
698
+ is 30 seconds for remote providers, 60 seconds for Gemini (Flash Lite can queue
699
+ for up to 30 seconds) and 300 seconds for Ollama (since local generation can be
700
+ significantly slower). The override applies uniformly to whichever provider is
701
+ selected for this run, including those with their own `<provider>.timeout`.
701
702
  +
702
703
  To change the default permanently, set `timeout: <seconds>` in
703
704
  `cai_config.yml` or the home config:
@@ -707,11 +708,12 @@ git cai -H timeout=60
707
708
  git cai -S timeout=45
708
709
  ----
709
710
  +
710
- Ollama keeps its own timeout under `ollama.timeout` (default 300). Override
711
- it separately:
711
+ Ollama and Gemini keep their own timeouts under `ollama.timeout` (default 300)
712
+ and `gemini.timeout` (default 60). Override them separately:
712
713
  +
713
714
  ----
714
715
  git cai -H ollama.timeout=600
716
+ git cai -H gemini.timeout=90
715
717
  ----
716
718
  +
717
719
  ----
@@ -938,13 +940,13 @@ Example output:
938
940
  ----
939
941
  Default models:
940
942
 
941
- anthropic → claude-haiku-4-5
942
- deepseek → deepseek-chat
943
- gemini → gemini-3.1-flash-lite
943
+ anthropic → claude-haiku-5-5
944
+ deepseek → deepseek-flash
945
+ gemini → gemini-3.5-flash-lite
944
946
  groq → openai/gpt-oss-20b
945
947
  mistral → codestral-2508
946
- ollama → llama3.1
947
- openai → gpt-5.4-mini
948
+ ollama → gemma4:e2b
949
+ openai → gpt-6-luna
948
950
  xai → grok-4.3
949
951
  ----
950
952
 
@@ -1003,13 +1005,13 @@ Example output:
1003
1005
  ----
1004
1006
  Supported providers:
1005
1007
 
1006
- anthropic model: claude-haiku-4-5 (token required)
1007
- deepseek model: deepseek-chat (token required)
1008
- gemini model: gemini-3.1-flash-lite (token required)
1008
+ anthropic model: claude-haiku-5-5 (token required)
1009
+ deepseek model: deepseek-flash (token required)
1010
+ gemini model: gemini-3.5-flash-lite (token required)
1009
1011
  groq model: openai/gpt-oss-20b (token required)
1010
1012
  mistral model: codestral-2508 (token required)
1011
- ollama model: llama3.1 (no token required)
1012
- openai model: gpt-5.4-mini (token required)
1013
+ ollama model: gemma4:e2b (no token required)
1014
+ openai model: gpt-6-luna (token required)
1013
1015
  xai model: grok-4.3 (token required)
1014
1016
 
1015
1017
  Custom providers (OpenAI compatible, from config):
@@ -1097,9 +1099,11 @@ Available configuration keys:
1097
1099
  - `full_files` -- send full working-tree contents of staged files alongside
1098
1100
  the diff (`true`/`false`, default `false`)
1099
1101
  - `max_diff_bytes` -- maximum size (in UTF-8 bytes) of the diff/commit-log
1100
- sent to the LLM. When the input exceeds this, it is truncated and a marker
1101
- line is appended so the model knows it was cut. `0` (the default) means no
1102
- limit. Useful to avoid context-window errors on very large staged changes.
1102
+ sent to the LLM. When the input exceeds this, the largest files are shortened
1103
+ first so every file stays represented, and marker lines tell the model what
1104
+ was cut. `0` (the default) means no limit. You rarely need this for very
1105
+ large staged changes: when a provider rejects the input as too large, git-cai
1106
+ shortens it the same way, warns, and retries up to two times.
1103
1107
  - `pr_to_file` -- when running `--PR`, write the generated PR description
1104
1108
  to a Markdown file in the repository root instead of printing it to
1105
1109
  stdout (`true`/`false`, default `false`)
@@ -1141,7 +1145,8 @@ Available configuration keys:
1141
1145
  `anthropic` the default is `32768` (`max_tokens` is still read as a legacy
1142
1146
  alias). For OpenAI compatible providers it is only sent when set.
1143
1147
  - `<provider>.timeout` -- HTTP timeout in seconds for one provider,
1144
- overriding the global `timeout` (`ollama` defaults to `300`)
1148
+ overriding the global `timeout` (`ollama` defaults to `300`, `gemini` to `60`).
1149
+ The `--timeout` flag overrides both.
1145
1150
  - `stats` -- opt in to local-only usage analytics (`true`/`false`,
1146
1151
  default `false`). When enabled, every generation appends one row to
1147
1152
  `~/.local/share/git-cai/stats.db` capturing kind (`commit`, `amend`,
@@ -1315,7 +1320,7 @@ Change a value in the repo config:
1315
1320
 
1316
1321
  ----
1317
1322
  git cai -S default=anthropic
1318
- git cai -S groq.model=llama-3.3-70b
1323
+ git cai -S groq.model=openai/gpt-oss-120b
1319
1324
  git cai -S emoji=false
1320
1325
  ----
1321
1326
 
@@ -2,12 +2,12 @@
2
2
  .\" Title: git-cai
3
3
  .\" Author: Thorsten Foltz
4
4
  .\" Generator: Asciidoctor 2.0.26
5
- .\" Date: 2026-10-11
5
+ .\" Date: 2026-11-06
6
6
  .\" Manual: \ \&
7
7
  .\" Source: \ \&
8
8
  .\" Language: English
9
9
  .\"
10
- .TH "GIT\-CAI" "1" "2026-10-11" "\ \&" "\ \&"
10
+ .TH "GIT\-CAI" "1" "2026-11-06" "\ \&" "\ \&"
11
11
  .ie \n(.g .ds Aq \(aq
12
12
  .el .ds Aq '
13
13
  .ss \n[.ss] 0
@@ -656,7 +656,7 @@ provider keys.
656
656
  git cai \-H language=de
657
657
  git cai \-H default=anthropic
658
658
  git cai \-H emoji=false
659
- git cai \-H groq.model=llama\-3.3\-70b
659
+ git cai \-H groq.model=openai/gpt\-oss\-120b
660
660
  .fam
661
661
  .fi
662
662
  .if n .RE
@@ -789,7 +789,7 @@ identifier for that provider.
789
789
  .nf
790
790
  .fam C
791
791
  git cai \-P openai \-m gpt\-4o
792
- git cai \-P anthropic \-m claude\-haiku\-4\-5
792
+ git cai \-P anthropic \-m claude\-haiku\-5\-5
793
793
  git cai \-P ollama \-m mistral
794
794
  .fam
795
795
  .fi
@@ -890,7 +890,7 @@ both.
890
890
  .nf
891
891
  .fam C
892
892
  git cai \-P anthropic
893
- git cai \-P groq \-m llama\-3.3\-70b
893
+ git cai \-P groq \-m openai/gpt\-oss\-120b
894
894
  git cai \-P openrouter
895
895
  .fam
896
896
  .fi
@@ -1066,7 +1066,7 @@ become integers or floats, everything else stays a string.
1066
1066
  .fam C
1067
1067
  git cai \-S default=anthropic
1068
1068
  git cai \-S emoji=false
1069
- git cai \-S groq.model=llama\-3.3\-70b
1069
+ git cai \-S groq.model=openai/gpt\-oss\-120b
1070
1070
  git cai \-S openai.temperature=0.7
1071
1071
  git cai \-S conventional=true
1072
1072
  .fam
@@ -1109,9 +1109,10 @@ git cai \-s a1b2c3d
1109
1109
  \-T, \-\-timeout SECONDS
1110
1110
  .RS 4
1111
1111
  Override the HTTP timeout for the LLM call in this invocation. The default
1112
- is 30 seconds for remote providers and 300 seconds for Ollama (since local
1113
- generation can be significantly slower). The override applies uniformly to
1114
- whichever provider is selected for this run.
1112
+ is 30 seconds for remote providers, 60 seconds for Gemini (Flash Lite can queue
1113
+ for up to 30 seconds) and 300 seconds for Ollama (since local generation can be
1114
+ significantly slower). The override applies uniformly to whichever provider is
1115
+ selected for this run, including those with their own \f(CR<provider>.timeout\fP.
1115
1116
  .sp
1116
1117
  To change the default permanently, set \f(CRtimeout: <seconds>\fP in
1117
1118
  \f(CRcai_config.yml\fP or the home config:
@@ -1125,13 +1126,14 @@ git cai \-S timeout=45
1125
1126
  .fi
1126
1127
  .if n .RE
1127
1128
  .sp
1128
- Ollama keeps its own timeout under \f(CRollama.timeout\fP (default 300). Override
1129
- it separately:
1129
+ Ollama and Gemini keep their own timeouts under \f(CRollama.timeout\fP (default 300)
1130
+ and \f(CRgemini.timeout\fP (default 60). Override them separately:
1130
1131
  .sp
1131
1132
  .if n .RS 4
1132
1133
  .nf
1133
1134
  .fam C
1134
1135
  git cai \-H ollama.timeout=600
1136
+ git cai \-H gemini.timeout=90
1135
1137
  .fam
1136
1138
  .fi
1137
1139
  .if n .RE
@@ -1441,13 +1443,13 @@ Example output:
1441
1443
  .fam C
1442
1444
  Default models:
1443
1445
 
1444
- anthropic\& → claude\-haiku\-4\-5
1445
- deepseek\& → deepseek\-chat
1446
- gemini\& → gemini\-3.1\-flash\-lite
1446
+ anthropic\& → claude\-haiku\-5\-5
1447
+ deepseek\& → deepseek\-flash
1448
+ gemini\& → gemini\-3.5\-flash\-lite
1447
1449
  groq\& → openai/gpt\-oss\-20b
1448
1450
  mistral\& → codestral\-2508
1449
- ollama\& → llama3.1
1450
- openai\& → gpt\-5.4\-mini
1451
+ ollama\& → gemma4:e2b
1452
+ openai\& → gpt\-6\-luna
1451
1453
  xai\& → grok\-4.3
1452
1454
  .fam
1453
1455
  .fi
@@ -1530,13 +1532,13 @@ Example output:
1530
1532
  .fam C
1531
1533
  Supported providers:
1532
1534
 
1533
- anthropic\& model: claude\-haiku\-4\-5\& (token required)
1534
- deepseek\& model: deepseek\-chat\& (token required)
1535
- gemini\& model: gemini\-3.1\-flash\-lite\& (token required)
1535
+ anthropic\& model: claude\-haiku\-5\-5\& (token required)
1536
+ deepseek\& model: deepseek\-flash\& (token required)
1537
+ gemini\& model: gemini\-3.5\-flash\-lite\& (token required)
1536
1538
  groq\& model: openai/gpt\-oss\-20b\& (token required)
1537
1539
  mistral\& model: codestral\-2508\& (token required)
1538
- ollama\& model: llama3.1\& (no token required)
1539
- openai\& model: gpt\-5.4\-mini\& (token required)
1540
+ ollama\& model: gemma4:e2b\& (no token required)
1541
+ openai\& model: gpt\-6\-luna\& (token required)
1540
1542
  xai\& model: grok\-4.3\& (token required)
1541
1543
 
1542
1544
  Custom providers (OpenAI compatible, from config):
@@ -1827,9 +1829,11 @@ the diff (\f(CRtrue\fP/\f(CRfalse\fP, default \f(CRfalse\fP)
1827
1829
  . IP \(bu 2.3
1828
1830
  .\}
1829
1831
  \f(CRmax_diff_bytes\fP \(em maximum size (in UTF\-8 bytes) of the diff/commit\-log
1830
- sent to the LLM. When the input exceeds this, it is truncated and a marker
1831
- line is appended so the model knows it was cut. \f(CR0\fP (the default) means no
1832
- limit. Useful to avoid context\-window errors on very large staged changes.
1832
+ sent to the LLM. When the input exceeds this, the largest files are shortened
1833
+ first so every file stays represented, and marker lines tell the model what
1834
+ was cut. \f(CR0\fP (the default) means no limit. You rarely need this for very
1835
+ large staged changes: when a provider rejects the input as too large, git\-cai
1836
+ shortens it the same way, warns, and retries up to two times.
1833
1837
  .RE
1834
1838
  .sp
1835
1839
  .RS 4
@@ -2021,7 +2025,8 @@ alias). For OpenAI compatible providers it is only sent when set.
2021
2025
  . IP \(bu 2.3
2022
2026
  .\}
2023
2027
  \f(CR<provider>.timeout\fP \(em HTTP timeout in seconds for one provider,
2024
- overriding the global \f(CRtimeout\fP (\f(CRollama\fP defaults to \f(CR300\fP)
2028
+ overriding the global \f(CRtimeout\fP (\f(CRollama\fP defaults to \f(CR300\fP, \f(CRgemini\fP to \f(CR60\fP).
2029
+ The \f(CR\-\-timeout\fP flag overrides both.
2025
2030
  .RE
2026
2031
  .sp
2027
2032
  .RS 4
@@ -2310,7 +2315,7 @@ Change a value in the repo config:
2310
2315
  .nf
2311
2316
  .fam C
2312
2317
  git cai \-S default=anthropic
2313
- git cai \-S groq.model=llama\-3.3\-70b
2318
+ git cai \-S groq.model=openai/gpt\-oss\-120b
2314
2319
  git cai \-S emoji=false
2315
2320
  .fam
2316
2321
  .fi
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.17.2'
22
- __version_tuple__ = version_tuple = (0, 17, 2)
21
+ __version__ = version = '0.17.4'
22
+ __version_tuple__ = version_tuple = (0, 17, 4)
23
23
 
24
- __commit_id__ = commit_id = 'ga453e832c'
24
+ __commit_id__ = commit_id = 'gf30a44c67'
@@ -35,19 +35,26 @@ TOKENS_FILE = CONFIG_DIR / "tokens.yml"
35
35
 
36
36
  DEFAULT_CONFIG: dict[str, Any] = {
37
37
  "anthropic": {
38
- "model": "claude-haiku-4-5",
39
- "temperature": 0,
38
+ "model": "claude-haiku-5-5",
40
39
  # ``max_tokens`` is still read as a legacy alias (see llm.py)
41
40
  "max_output_tokens": 32768,
42
41
  },
43
- "openai": {"model": "gpt-5.6-luna"},
44
- "deepseek": {"model": "deepseek-chat", "temperature": 0},
45
- "gemini": {"model": "gemini-3.1-flash-lite", "temperature": 0},
42
+ "openai": {"model": "gpt-6-luna"},
43
+ "deepseek": {"model": "deepseek-flash", "temperature": 0},
44
+ "gemini": {
45
+ "model": "gemini-3.5-flash-lite",
46
+ "temperature": 0,
47
+ # Flash Lite was measured taking up to 30 seconds server side, even for
48
+ # a single word reply, so the global 30 second timeout is too tight.
49
+ "timeout": 60,
50
+ },
46
51
  "groq": {"model": "openai/gpt-oss-20b", "temperature": 0},
47
52
  "xai": {"model": "grok-4.3", "temperature": 0},
53
+ # Not the cheapest on the list: ministral-3b wrapped messages in code
54
+ # fences and ministral-8b added a warning line, both unfit for a commit.
48
55
  "mistral": {"model": "codestral-2508", "temperature": 0},
49
56
  "ollama": {
50
- "model": "llama3.1",
57
+ "model": "gemma4:e2b",
51
58
  "temperature": 0,
52
59
  "timeout": 300,
53
60
  "startup_timeout": 8,
@@ -733,6 +740,11 @@ def apply_cli_overrides(
733
740
  config["branch_context"] = branch_context
734
741
  if timeout_override is not None:
735
742
  config["timeout"] = timeout_override
743
+ # The flag outranks ``<provider>.timeout``, which is checked first when
744
+ # resolving. Rebuild instead of pop: blocks may be shared with DEFAULT_CONFIG.
745
+ for name, block in list(config.items()):
746
+ if isinstance(block, dict) and "timeout" in block:
747
+ config[name] = {k: v for k, v in block.items() if k != "timeout"}
736
748
  if full_files_override is not None:
737
749
  config["full_files"] = full_files_override
738
750
  if sql_override is not None:
@@ -349,26 +349,55 @@ def git_diff_excluding(
349
349
  return result.stdout
350
350
 
351
351
 
352
+ # Where an independently trimmable part of an LLM payload starts: one file's
353
+ # diff, or a block appended after the diff. A stray match inside file content
354
+ # only creates one more part, which is harmless.
355
+ _PAYLOAD_PART_RE = re.compile(
356
+ r"^(?=diff --git "
357
+ r"|--- (?:Full file contents|Additional context from the author) ---)",
358
+ re.MULTILINE,
359
+ )
360
+
361
+
362
+ def shorten_diff(diff: str, max_bytes: int) -> str:
363
+ """Cut ``diff`` to about ``max_bytes`` UTF-8 bytes, largest parts first.
364
+
365
+ Parts below their fair share stay whole and the others split what is left,
366
+ so every file keeps its header and the start of its changes. Text without
367
+ file headers (a commit log) is one part, which makes this a plain tail cut.
368
+ Each cut adds a short marker, so the result can slightly exceed the limit.
369
+ """
370
+ parts = [part.encode("utf-8") for part in _PAYLOAD_PART_RE.split(diff) if part]
371
+ remaining = max_bytes
372
+ shares = [0] * len(parts)
373
+ by_size = sorted(range(len(parts)), key=lambda i: len(parts[i]))
374
+ for position, index in enumerate(by_size):
375
+ shares[index] = min(len(parts[index]), remaining // (len(parts) - position))
376
+ remaining -= shares[index]
377
+
378
+ shortened = []
379
+ for part, share in zip(parts, shares):
380
+ # errors="ignore" drops a multibyte character split by the byte slice.
381
+ text = part[:share].decode("utf-8", errors="ignore")
382
+ if share < len(part):
383
+ text += f"\n[... {len(part) - share} bytes omitted ...]\n"
384
+ shortened.append(text)
385
+ return "".join(shortened)
386
+
387
+
352
388
  def truncate_diff(diff: str, max_bytes: int) -> tuple[str, bool]:
353
- """Truncate ``diff`` to at most ``max_bytes`` UTF-8 bytes.
389
+ """Truncate ``diff`` to about ``max_bytes`` UTF-8 bytes.
354
390
 
355
391
  A ``max_bytes`` of 0 (or negative) means "no limit" and the diff is
356
392
  returned unchanged — preserving the default behavior. When truncation
357
393
  occurs, a marker line is appended so both the LLM and the user know the
358
394
  input was cut. Returns ``(possibly_truncated_diff, was_truncated)``.
359
395
  """
360
- if max_bytes <= 0:
361
- return diff, False
362
-
363
- encoded = diff.encode("utf-8")
364
- if len(encoded) <= max_bytes:
396
+ if max_bytes <= 0 or len(diff.encode("utf-8")) <= max_bytes:
365
397
  return diff, False
366
398
 
367
- # Cut on a UTF-8 safe boundary: decoding with errors="ignore" drops any
368
- # partial trailing multibyte sequence left by the byte-level slice.
369
- truncated = encoded[:max_bytes].decode("utf-8", errors="ignore")
370
399
  marker = f"\n\n[... diff truncated: exceeded max_diff_bytes={max_bytes} ...]"
371
- return truncated + marker, True
400
+ return shorten_diff(diff, max_bytes) + marker, True
372
401
 
373
402
 
374
403
  def apply_diff_limit(text: str, config: dict, *, label: str = "Input") -> str:
@@ -18,7 +18,11 @@ from urllib.parse import urlparse
18
18
 
19
19
  import requests
20
20
  from git_cai_cli.core.config import CONFIG_DIR, is_custom_provider
21
- from git_cai_cli.core.gitutils import classify_changed_paths, paths_from_diff
21
+ from git_cai_cli.core.gitutils import (
22
+ classify_changed_paths,
23
+ paths_from_diff,
24
+ shorten_diff,
25
+ )
22
26
  from git_cai_cli.core.languages import LANGUAGE_MAP
23
27
  from git_cai_cli.core.prompts_fallback import (
24
28
  HARDCODED_CHANGELOG_PROMPT,
@@ -30,6 +34,7 @@ from git_cai_cli.core.prompts_fallback import (
30
34
  HARDCODED_SPLIT_PROMPT,
31
35
  HARDCODED_SQUASH_PROMPT,
32
36
  )
37
+ from git_cai_cli.core.validate import _extract_api_error_message
33
38
  from requests.adapters import HTTPAdapter
34
39
  from urllib3.util.retry import Retry
35
40
 
@@ -41,10 +46,11 @@ log = logging.getLogger(__name__)
41
46
  # still handled — ``_http_post`` retries once without the field — so add
42
47
  # an entry only when a warning shows up in practice.
43
48
  _TEMPERATURE_UNSUPPORTED_PATTERNS = [
44
- re.compile(r"^gpt-5", re.IGNORECASE), # OpenAI GPT-5 family (incl. mini/nano)
49
+ re.compile(r"^gpt-[5-9]", re.IGNORECASE), # OpenAI GPT-5+ (incl. mini/nano/luna)
45
50
  re.compile(r"^o[134](-|$)", re.IGNORECASE), # OpenAI o1/o3/o4 reasoning series
46
51
  re.compile(r"opus-4-[7-9]", re.IGNORECASE), # Anthropic Opus 4.7 / 4.8 / 4.9
47
52
  re.compile(r"opus-[5-9]", re.IGNORECASE), # Anthropic Opus 5+
53
+ re.compile(r"haiku-[5-9]", re.IGNORECASE), # Anthropic Haiku 5+
48
54
  ]
49
55
 
50
56
 
@@ -178,6 +184,37 @@ def _http_post(*args, **kwargs):
178
184
  return response
179
185
 
180
186
 
187
+ # Model limits change too often for a table, and the provider's rejection
188
+ # names them, so an oversized request is shortened and retried instead.
189
+ _OVERFLOW_STATUS_CODES = (400, 413, 422)
190
+ _OVERFLOW_RE = re.compile(
191
+ r"too (?:long|large)|exceed|maximum (?:context|prompt)|reduce the length",
192
+ re.IGNORECASE,
193
+ )
194
+ # Skips numbers glued to a name or version, like a date in a model name.
195
+ _OVERFLOW_SIZE_RE = re.compile(r"(?<![\w.-])\d[\d,]{2,}\b")
196
+ _OVERFLOW_RETRIES = 2
197
+
198
+
199
+ def _overflow_shrink_factor(exc: requests.HTTPError) -> float | None:
200
+ """Return how much to shrink the input after a "too many tokens" rejection.
201
+
202
+ ``None`` means ``exc`` is some other error. Providers word the rejection
203
+ differently, but the first two numbers are the limit and the actual size,
204
+ and the smaller one is the limit. Without them, halve the input.
205
+ """
206
+ response = exc.response
207
+ if response is None or response.status_code not in _OVERFLOW_STATUS_CODES:
208
+ return None
209
+ message = _extract_api_error_message(response)
210
+ if not _OVERFLOW_RE.search(message):
211
+ return None
212
+ sizes = [int(n.replace(",", "")) for n in _OVERFLOW_SIZE_RE.findall(message)[:2]]
213
+ if len(sizes) < 2 or sizes[0] == sizes[1]:
214
+ return 0.5
215
+ return 0.9 * min(sizes) / max(sizes)
216
+
217
+
181
218
  def load_prompt_file(
182
219
  config_key: str,
183
220
  config: Dict[str, Any],
@@ -967,7 +1004,8 @@ class CommitMessageGenerator:
967
1004
  def _dispatch_generate(self, content: str, system_prompt: str) -> str:
968
1005
  """
969
1006
  Route to the right provider. ``system_prompt`` comes from the
970
- matching ``_build_*_prompt``; ``content`` is the git output.
1007
+ matching ``_build_*_prompt``; ``content`` is the git output. Input the
1008
+ provider rejects as too large is shortened and retried.
971
1009
  """
972
1010
  self._scan_for_secrets(content)
973
1011
 
@@ -986,12 +1024,30 @@ class CommitMessageGenerator:
986
1024
 
987
1025
  log.debug("Using provider '%s' for generation.", provider)
988
1026
 
989
- if openai_compatible:
990
- return self.generate_openai_compatible(
991
- content, provider, system_prompt_override=system_prompt
992
- )
1027
+ def call_provider(text: str) -> str:
1028
+ if openai_compatible:
1029
+ return self.generate_openai_compatible(
1030
+ text, provider, system_prompt_override=system_prompt
1031
+ )
1032
+ return model_dispatch[provider](text, system_prompt_override=system_prompt)
993
1033
 
994
- return model_dispatch[provider](content, system_prompt_override=system_prompt)
1034
+ sent = content
1035
+ for _ in range(_OVERFLOW_RETRIES):
1036
+ try:
1037
+ return call_provider(sent)
1038
+ except requests.HTTPError as exc:
1039
+ factor = _overflow_shrink_factor(exc)
1040
+ if factor is None:
1041
+ raise
1042
+ target = int(len(sent.encode("utf-8")) * factor)
1043
+ log.warning(
1044
+ "The input is too large for this model. Retrying with the largest "
1045
+ "files shortened to about %d bytes in total, so the result may "
1046
+ "miss details from the cut parts.",
1047
+ target,
1048
+ )
1049
+ sent = shorten_diff(content, target)
1050
+ return call_provider(sent)
995
1051
 
996
1052
  # ---------------------------
997
1053
  # MODEL CALLS