git-cai-cli 0.15.3__tar.gz → 0.15.5__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 (104) hide show
  1. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.github/ci/cai_config.ci.yml +4 -5
  2. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.github/workflows/python-tests.yml +6 -6
  3. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.github/workflows/release.yml +3 -3
  4. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/PKG-INFO +1 -1
  5. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/cai_config.yml +4 -5
  6. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/docs/git-cai.txt +13 -11
  7. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/docs/man/git-cai.1 +15 -13
  8. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/_version.py +3 -3
  9. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/config.py +26 -8
  10. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/doctor.py +2 -2
  11. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/init.py +15 -11
  12. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/llm.py +82 -26
  13. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/validate.py +34 -27
  14. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/PKG-INFO +1 -1
  15. git_cai_cli-0.15.5/src/git_cai_cli.egg-info/scm_version.json +8 -0
  16. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_config.py +25 -3
  17. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_init.py +36 -0
  18. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_llm.py +182 -12
  19. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_validate.py +20 -2
  20. git_cai_cli-0.15.3/src/git_cai_cli.egg-info/scm_version.json +0 -8
  21. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.caiignore +0 -0
  22. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.gitattributes +0 -0
  23. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.github/cd/.SRCINFO +0 -0
  24. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.github/cd/PKGBUILD +0 -0
  25. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.github/ci/_version.py +0 -0
  26. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.github/ci/tokens.ci.yml +0 -0
  27. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.github/workflows/release_aur.yml +0 -0
  28. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.gitignore +0 -0
  29. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/.bandit.yml +0 -0
  30. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/.checkov.yml +0 -0
  31. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/.flake8 +0 -0
  32. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/.ls-lint.yml +0 -0
  33. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/.markdown-link-check.json +0 -0
  34. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/.markdownlint.json +0 -0
  35. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/.proselintrc +0 -0
  36. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/.pylintrc +0 -0
  37. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/.yamllint.yml +0 -0
  38. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/check_git_branch_name.sh +0 -0
  39. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/lychee.toml +0 -0
  40. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.linters/pyrightconfig.json +0 -0
  41. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.markdownlintignore +0 -0
  42. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.mega-linter.yml +0 -0
  43. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.semgrepignore +0 -0
  44. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/.trivyignore +0 -0
  45. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/CLAUDE.md +0 -0
  46. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/LICENSE +0 -0
  47. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/Makefile +0 -0
  48. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/README.md +0 -0
  49. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/pyproject.toml +0 -0
  50. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/setup.cfg +0 -0
  51. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/__init__.py +0 -0
  52. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/__init__.py +0 -0
  53. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/cli.py +0 -0
  54. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/helptext.py +0 -0
  55. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/modes.py +0 -0
  56. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/__init__.py +0 -0
  57. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/completion.py +0 -0
  58. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/editors.py +0 -0
  59. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/gitutils.py +0 -0
  60. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/languages.py +0 -0
  61. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/options.py +0 -0
  62. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/pr.py +0 -0
  63. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/prompts_fallback.py +0 -0
  64. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/secrets.py +0 -0
  65. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/spinner.py +0 -0
  66. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/squash.py +0 -0
  67. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/core/stats.py +0 -0
  68. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli/main.py +0 -0
  69. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/SOURCES.txt +0 -0
  70. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/dependency_links.txt +0 -0
  71. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/entry_points.txt +0 -0
  72. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/requires.txt +0 -0
  73. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/scm_file_list.json +0 -0
  74. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/top_level.txt +0 -0
  75. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/conftest.py +0 -0
  76. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/integration/test_cli_integration.py +0 -0
  77. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/integration/test_config_integration.py +0 -0
  78. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/integration/test_gitutils_integration.py +0 -0
  79. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/integration/test_modes_integration.py +0 -0
  80. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/integration/test_options_integration.py +0 -0
  81. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/integration/test_pr_integration.py +0 -0
  82. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/integration/test_squash_integration.py +0 -0
  83. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_amend.py +0 -0
  84. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_branch_context.py +0 -0
  85. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_classification.py +0 -0
  86. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_cli.py +0 -0
  87. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_completion.py +0 -0
  88. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_conventional.py +0 -0
  89. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_doctor.py +0 -0
  90. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_gitutils.py +0 -0
  91. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_helptext.py +0 -0
  92. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_main.py +0 -0
  93. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_modes.py +0 -0
  94. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_options.py +0 -0
  95. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_pr.py +0 -0
  96. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_print.py +0 -0
  97. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_prompt_loading.py +0 -0
  98. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_secrets.py +0 -0
  99. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_set_config.py +0 -0
  100. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_signoff.py +0 -0
  101. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_spinner.py +0 -0
  102. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_squash.py +0 -0
  103. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/tests/unit/test_stats.py +0 -0
  104. {git_cai_cli-0.15.3 → git_cai_cli-0.15.5}/uv.lock +0 -0
@@ -13,10 +13,10 @@ deepseek:
13
13
  model: deepseek-chat
14
14
  temperature: 0
15
15
  gemini:
16
- model: gemini-2.5-flash
16
+ model: gemini-3.1-flash-lite
17
17
  temperature: 0
18
18
  groq:
19
- model: moonshotai/kimi-k2-instruct
19
+ model: openai/gpt-oss-20b
20
20
  temperature: 0
21
21
  mistral:
22
22
  model: codestral-2508
@@ -26,8 +26,7 @@ ollama:
26
26
  temperature: 0
27
27
  timeout: 300
28
28
  openai:
29
- model: gpt-5.1
30
- temperature: 0
29
+ model: gpt-5.4-mini
31
30
  xai:
32
- model: grok-4-1-fast-reasoning
31
+ model: grok-4.3
33
32
  temperature: 0
@@ -15,7 +15,7 @@ jobs:
15
15
 
16
16
  steps:
17
17
  - name: Checkout code
18
- uses: actions/checkout@v4
18
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
19
19
  with:
20
20
  ref: ${{ github.head_ref }}
21
21
 
@@ -35,10 +35,10 @@ jobs:
35
35
  python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
36
36
 
37
37
  steps:
38
- - uses: actions/checkout@v4
38
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
39
39
 
40
40
  - name: Set up Python ${{ matrix.python-version }}
41
- uses: actions/setup-python@v4
41
+ uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4
42
42
  with:
43
43
  python-version: ${{ matrix.python-version }}
44
44
 
@@ -49,7 +49,7 @@ jobs:
49
49
  run: uv venv
50
50
 
51
51
  - name: Restore cache uv
52
- uses: actions/cache@v4
52
+ uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
53
53
  with:
54
54
  path: |
55
55
  ~/.cache/uv
@@ -89,7 +89,7 @@ jobs:
89
89
  steps:
90
90
  # Git Checkout
91
91
  - name: Checkout Code
92
- uses: actions/checkout@v6
92
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
93
93
  with:
94
94
  token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
95
95
  fetch-depth: 0
@@ -108,7 +108,7 @@ jobs:
108
108
  # Upload MegaLinter artifacts
109
109
  - name: Archive production artifacts
110
110
  if: success() || failure()
111
- uses: actions/upload-artifact@v4
111
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
112
112
  with:
113
113
  name: MegaLinter reports
114
114
  include-hidden-files: "true"
@@ -18,12 +18,12 @@ jobs:
18
18
 
19
19
  steps:
20
20
  - name: Checkout tagged commit
21
- uses: actions/checkout@v4
21
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
22
22
  with:
23
23
  ref: ${{ github.ref }}
24
24
 
25
25
  - name: Set up Python
26
- uses: actions/setup-python@v5
26
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
27
27
  with:
28
28
  python-version: "3.13"
29
29
 
@@ -34,7 +34,7 @@ jobs:
34
34
  run: uv venv
35
35
 
36
36
  - name: Restore uv cache
37
- uses: actions/cache@v4
37
+ uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
38
38
  with:
39
39
  path: |
40
40
  ~/.cache/uv
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-cai-cli
3
- Version: 0.15.3
3
+ Version: 0.15.5
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
@@ -24,10 +24,10 @@ deepseek:
24
24
  model: deepseek-chat
25
25
  temperature: 0
26
26
  gemini:
27
- model: gemini-2.5-flash
27
+ model: gemini-3.1-flash-lite
28
28
  temperature: 0
29
29
  groq:
30
- model: moonshotai/kimi-k2-instruct
30
+ model: openai/gpt-oss-20b
31
31
  temperature: 0
32
32
  mistral:
33
33
  model: codestral-2508
@@ -37,8 +37,7 @@ ollama:
37
37
  temperature: 0
38
38
  timeout: 300
39
39
  openai:
40
- model: gpt-5.4
41
- temperature: 0
40
+ model: gpt-5.4-mini
42
41
  xai:
43
- model: grok-4-1-fast-reasoning
42
+ model: grok-4.3
44
43
  temperature: 0
@@ -204,7 +204,9 @@ git cai -d
204
204
  Override the sampling temperature of the active provider for this invocation
205
205
  only. Higher values produce more varied wording; lower values are more
206
206
  deterministic. The override is provider-scoped, mirroring `--model`, and does
207
- not change the persisted `cai_config.yml`.
207
+ not change the persisted `cai_config.yml`. Some models no longer accept a
208
+ temperature (for example OpenAI GPT-5 models and Anthropic Opus 4.7 and newer);
209
+ for those the value is ignored and a warning is logged.
208
210
  +
209
211
  ----
210
212
  git cai -e 0.7
@@ -326,7 +328,7 @@ git cai -i
326
328
  -k, --check::
327
329
  Run configuration diagnostics and exit. The check is offline by default: it
328
330
  reports which config is authoritative (repository, home, or built-in), validates
329
- the config keys, confirms the default provider has a model/temperature and a
331
+ the config keys, confirms the default provider has a model and a
330
332
  usable token, that the commit/squash/full-files/PR prompts resolve, and that the
331
333
  configured editor is on `PATH`. Exits non-zero if any check fails. Add `--ping`
332
334
  to also probe the active provider with a tiny live request.
@@ -734,7 +736,7 @@ Active configuration:
734
736
  style: professional
735
737
  emoji: true
736
738
  groq:
737
- model: moonshotai/kimi-k2-instruct
739
+ model: openai/gpt-oss-20b
738
740
  temperature: 0
739
741
  ...
740
742
  ----
@@ -786,12 +788,12 @@ Default models:
786
788
 
787
789
  anthropic → claude-haiku-4-5
788
790
  deepseek → deepseek-chat
789
- gemini → gemini-2.5-flash
790
- groq → moonshotai/kimi-k2-instruct
791
+ gemini → gemini-3.1-flash-lite
792
+ groq → openai/gpt-oss-20b
791
793
  mistral → codestral-2508
792
794
  ollama → llama3.1
793
- openai → gpt-5.2
794
- xai → grok-4-1-fast-reasoning
795
+ openai → gpt-5.4-mini
796
+ xai → grok-4.3
795
797
  ----
796
798
 
797
799
  path::
@@ -849,12 +851,12 @@ Supported providers:
849
851
 
850
852
  anthropic model: claude-haiku-4-5 (token required)
851
853
  deepseek model: deepseek-chat (token required)
852
- gemini model: gemini-2.5-flash (token required)
853
- groq model: moonshotai/kimi-k2-instruct (token required)
854
+ gemini model: gemini-3.1-flash-lite (token required)
855
+ groq model: openai/gpt-oss-20b (token required)
854
856
  mistral model: codestral-2508 (token required)
855
857
  ollama model: llama3.1 (no token required)
856
- openai model: gpt-5.2 (token required)
857
- xai model: grok-4-1-fast-reasoning (token required)
858
+ openai model: gpt-5.4-mini (token required)
859
+ xai model: grok-4.3 (token required)
858
860
  ----
859
861
 
860
862
  style::
@@ -2,12 +2,12 @@
2
2
  .\" Title: git-cai
3
3
  .\" Author: Thorsten Foltz
4
4
  .\" Generator: Asciidoctor 2.0.26
5
- .\" Date: 2026-06-29
5
+ .\" Date: 2026-07-05
6
6
  .\" Manual: \ \&
7
7
  .\" Source: \ \&
8
8
  .\" Language: English
9
9
  .\"
10
- .TH "GIT\-CAI" "1" "2026-06-29" "\ \&" "\ \&"
10
+ .TH "GIT\-CAI" "1" "2026-07-05" "\ \&" "\ \&"
11
11
  .ie \n(.g .ds Aq \(aq
12
12
  .el .ds Aq '
13
13
  .ss \n[.ss] 0
@@ -380,7 +380,9 @@ git cai \-d
380
380
  Override the sampling temperature of the active provider for this invocation
381
381
  only. Higher values produce more varied wording; lower values are more
382
382
  deterministic. The override is provider\-scoped, mirroring \f(CR\-\-model\fP, and does
383
- not change the persisted \f(CRcai_config.yml\fP.
383
+ not change the persisted \f(CRcai_config.yml\fP. Some models no longer accept a
384
+ temperature (for example OpenAI GPT\-5 models and Anthropic Opus 4.7 and newer);
385
+ for those the value is ignored and a warning is logged.
384
386
  .sp
385
387
  .if n .RS 4
386
388
  .nf
@@ -554,7 +556,7 @@ git cai \-i
554
556
  .RS 4
555
557
  Run configuration diagnostics and exit. The check is offline by default: it
556
558
  reports which config is authoritative (repository, home, or built\-in), validates
557
- the config keys, confirms the default provider has a model/temperature and a
559
+ the config keys, confirms the default provider has a model and a
558
560
  usable token, that the commit/squash/full\-files/PR prompts resolve, and that the
559
561
  configured editor is on \f(CRPATH\fP. Exits non\-zero if any check fails. Add \f(CR\-\-ping\fP
560
562
  to also probe the active provider with a tiny live request.
@@ -1117,7 +1119,7 @@ Active configuration:
1117
1119
  style: professional
1118
1120
  emoji: true
1119
1121
  groq:
1120
- model: moonshotai/kimi\-k2\-instruct
1122
+ model: openai/gpt\-oss\-20b
1121
1123
  temperature: 0
1122
1124
  ...
1123
1125
  .fam
@@ -1191,12 +1193,12 @@ Default models:
1191
1193
 
1192
1194
  anthropic\& → claude\-haiku\-4\-5
1193
1195
  deepseek\& → deepseek\-chat
1194
- gemini\& → gemini\-2.5\-flash
1195
- groq\& → moonshotai/kimi\-k2\-instruct
1196
+ gemini\& → gemini\-3.1\-flash\-lite
1197
+ groq\& → openai/gpt\-oss\-20b
1196
1198
  mistral\& → codestral\-2508
1197
1199
  ollama\& → llama3.1
1198
- openai\& → gpt\-5.2
1199
- xai\& → grok\-4\-1\-fast\-reasoning
1200
+ openai\& → gpt\-5.4\-mini
1201
+ xai\& → grok\-4.3
1200
1202
  .fam
1201
1203
  .fi
1202
1204
  .if n .RE
@@ -1278,12 +1280,12 @@ Supported providers:
1278
1280
 
1279
1281
  anthropic\& model: claude\-haiku\-4\-5\& (token required)
1280
1282
  deepseek\& model: deepseek\-chat\& (token required)
1281
- gemini\& model: gemini\-2.5\-flash\& (token required)
1282
- groq\& model: moonshotai/kimi\-k2\-instruct\& (token required)
1283
+ gemini\& model: gemini\-3.1\-flash\-lite\& (token required)
1284
+ groq\& model: openai/gpt\-oss\-20b\& (token required)
1283
1285
  mistral\& model: codestral\-2508\& (token required)
1284
1286
  ollama\& model: llama3.1\& (no token required)
1285
- openai\& model: gpt\-5.2\& (token required)
1286
- xai\& model: grok\-4\-1\-fast\-reasoning\& (token required)
1287
+ openai\& model: gpt\-5.4\-mini\& (token required)
1288
+ xai\& model: grok\-4.3\& (token required)
1287
1289
  .fam
1288
1290
  .fi
1289
1291
  .if n .RE
@@ -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.15.3'
22
- __version_tuple__ = version_tuple = (0, 15, 3)
21
+ __version__ = version = '0.15.5'
22
+ __version_tuple__ = version_tuple = (0, 15, 5)
23
23
 
24
- __commit_id__ = commit_id = 'g0517f2f82'
24
+ __commit_id__ = commit_id = 'g717dd683a'
@@ -37,15 +37,21 @@ DEFAULT_CONFIG: dict[str, Any] = {
37
37
  "anthropic": {
38
38
  "model": "claude-haiku-4-5",
39
39
  "temperature": 0,
40
- "max_tokens": 32768,
40
+ # ``max_tokens`` is still read as a legacy alias (see llm.py)
41
+ "max_output_tokens": 32768,
41
42
  },
42
- "openai": {"model": "gpt-5.2", "temperature": 0},
43
+ "openai": {"model": "gpt-5.4-mini"},
43
44
  "deepseek": {"model": "deepseek-chat", "temperature": 0},
44
- "gemini": {"model": "gemini-2.5-flash", "temperature": 0},
45
- "groq": {"model": "moonshotai/kimi-k2-instruct", "temperature": 0},
46
- "xai": {"model": "grok-4-1-fast-reasoning", "temperature": 0},
45
+ "gemini": {"model": "gemini-3.1-flash-lite", "temperature": 0},
46
+ "groq": {"model": "openai/gpt-oss-20b", "temperature": 0},
47
+ "xai": {"model": "grok-4.3", "temperature": 0},
47
48
  "mistral": {"model": "codestral-2508", "temperature": 0},
48
- "ollama": {"model": "llama3.1", "temperature": 0, "timeout": 300},
49
+ "ollama": {
50
+ "model": "llama3.1",
51
+ "temperature": 0,
52
+ "timeout": 300,
53
+ "startup_timeout": 8,
54
+ },
49
55
  "language": "en",
50
56
  "default": "groq",
51
57
  "style": "professional",
@@ -65,6 +71,7 @@ DEFAULT_CONFIG: dict[str, Any] = {
65
71
  "pr_file_name": "PR_DESCRIPTION.md",
66
72
  "pr_prompt_file": "",
67
73
  "stats": False,
74
+ "stats_db_path": "", # empty → XDG default (see stats.resolve_db_path)
68
75
  "signoff": False,
69
76
  "secret_scan": True,
70
77
  "secret_scan_exclude": [],
@@ -139,7 +146,8 @@ def _load_home_stats(home_config_file: Path) -> dict[str, Any] | None:
139
146
  out: dict[str, Any] = {}
140
147
  if "stats" in data:
141
148
  out["stats"] = data["stats"]
142
- if "stats_db_path" in data:
149
+ # An empty stats_db_path means "no override" — don't propagate it.
150
+ if data.get("stats_db_path"):
143
151
  out["stats_db_path"] = data["stats_db_path"]
144
152
  return out or None
145
153
 
@@ -445,6 +453,11 @@ def ordered_default_config(
445
453
  ) -> dict[str, Any]:
446
454
  """
447
455
  Return DEFAULT_CONFIG ordered for human-readable YAML output.
456
+
457
+ Global keys come first (the curated order below, then any remaining
458
+ ones alphabetically), provider blocks last. Keeping the provider
459
+ blocks strictly at the end means a newly added global key can never
460
+ get buried between them.
448
461
  """
449
462
  if default_config is None:
450
463
  default_config = DEFAULT_CONFIG
@@ -471,6 +484,8 @@ def ordered_default_config(
471
484
  "stats",
472
485
  "stats_db_path",
473
486
  "signoff",
487
+ "secret_scan",
488
+ "secret_scan_exclude",
474
489
  ]
475
490
 
476
491
  ordered: dict[str, Any] = {}
@@ -479,7 +494,10 @@ def ordered_default_config(
479
494
  if key in default_config:
480
495
  ordered[key] = default_config[key]
481
496
 
482
- for key in sorted(k for k in default_config if k not in priority_keys):
497
+ remaining = [k for k in default_config if k not in ordered]
498
+ for key in sorted(k for k in remaining if k not in KNOWN_PROVIDERS):
499
+ ordered[key] = default_config[key]
500
+ for key in sorted(k for k in remaining if k in KNOWN_PROVIDERS):
483
501
  ordered[key] = default_config[key]
484
502
 
485
503
  return ordered
@@ -81,8 +81,8 @@ def _run_check_impl(*, live: bool = False) -> int:
81
81
  if not provider:
82
82
  _line(_FAIL, "No default provider set")
83
83
  ok = False
84
- elif not isinstance(block, dict) or not {"model", "temperature"} <= set(block):
85
- _line(_FAIL, f"Provider '{provider}' block missing model/temperature")
84
+ elif not isinstance(block, dict) or "model" not in block:
85
+ _line(_FAIL, f"Provider '{provider}' block missing model")
86
86
  ok = False
87
87
  else:
88
88
  _line(_OK, f"Default provider: {provider} (model {block.get('model')})")
@@ -22,6 +22,8 @@ from git_cai_cli.core.config import (
22
22
  KNOWN_PROVIDERS,
23
23
  TOKENLESS_PROVIDERS,
24
24
  TOKENS_FILE,
25
+ _serialize_config,
26
+ ordered_default_config,
25
27
  )
26
28
  from git_cai_cli.core.languages import LANGUAGE_MAP
27
29
 
@@ -146,17 +148,19 @@ def _write_config_file(
146
148
  style: str,
147
149
  emoji: bool,
148
150
  ) -> None:
149
- block = DEFAULT_CONFIG.get(provider, {})
150
- provider_block = dict(block) if isinstance(block, dict) else {}
151
-
152
- config: dict[str, Any] = {
153
- "default": provider,
154
- "language": language,
155
- "style": style,
156
- "emoji": emoji,
157
- "load_tokens_from": str(TOKENS_FILE),
158
- provider: provider_block,
159
- }
151
+ # Same full surface as `git cai -g`: every setting is written out with
152
+ # its default, so users can see and edit what exists. The wizard's
153
+ # answers override the four keys it asked about.
154
+ config: dict[str, Any] = _serialize_config(ordered_default_config())
155
+ config.update(
156
+ {
157
+ "default": provider,
158
+ "language": language,
159
+ "style": style,
160
+ "emoji": emoji,
161
+ "load_tokens_from": str(TOKENS_FILE),
162
+ }
163
+ )
160
164
 
161
165
  config_path.parent.mkdir(parents=True, exist_ok=True)
162
166
  with config_path.open("w", encoding="utf-8") as f:
@@ -5,6 +5,7 @@ Use LLMs to generate git commit messages from diffs or multiple commits.
5
5
  import functools
6
6
  import logging
7
7
  import os
8
+ import re
8
9
  import shutil
9
10
  import signal
10
11
  import subprocess
@@ -32,6 +33,40 @@ from urllib3.util.retry import Retry
32
33
  log = logging.getLogger(__name__)
33
34
 
34
35
 
36
+ # Models that reject any non-default ``temperature`` (return HTTP 400).
37
+ # Extend this list as new restricted models appear.
38
+ _TEMPERATURE_UNSUPPORTED_PATTERNS = [
39
+ re.compile(r"^gpt-5", re.IGNORECASE), # OpenAI GPT-5 family (incl. mini/nano)
40
+ re.compile(r"^o[134](-|$)", re.IGNORECASE), # OpenAI o1/o3/o4 reasoning series
41
+ re.compile(r"opus-4-[7-9]", re.IGNORECASE), # Anthropic Opus 4.7 / 4.8 / 4.9
42
+ re.compile(r"opus-[5-9]", re.IGNORECASE), # Anthropic Opus 5+
43
+ ]
44
+
45
+
46
+ def _model_rejects_temperature(model: str) -> bool:
47
+ """Return True if ``model`` rejects a non-default temperature."""
48
+ return any(pattern.search(model) for pattern in _TEMPERATURE_UNSUPPORTED_PATTERNS)
49
+
50
+
51
+ def _resolve_temperature(model: str, temperature: float | int | None) -> float | None:
52
+ """Resolve the temperature to send for ``model``.
53
+
54
+ Returns ``None`` (send no temperature) when unset or when the model
55
+ rejects it, warning in the latter case. Otherwise returns the float.
56
+ """
57
+ if temperature is None:
58
+ return None
59
+ if _model_rejects_temperature(model):
60
+ log.warning(
61
+ "Model '%s' does not support temperature; "
62
+ "ignoring configured temperature=%s.",
63
+ model,
64
+ temperature,
65
+ )
66
+ return None
67
+ return float(temperature)
68
+
69
+
35
70
  _RETRY_STATUS_CODES = (429, 500, 502, 503, 504)
36
71
  _RETRY_TOTAL = 3
37
72
  _RETRY_BACKOFF_FACTOR = 0.5
@@ -242,7 +277,8 @@ class CommitMessageGenerator:
242
277
  if isinstance(block, dict):
243
278
  temp = block.get("temperature")
244
279
  if isinstance(temp, (int, float)):
245
- temperature_val = float(temp)
280
+ model_name = block.get("model", "")
281
+ temperature_val = _resolve_temperature(str(model_name), temp)
246
282
 
247
283
  # Full-files mode swaps the commit prompt file for a dedicated
248
284
  # one — surface that when active, regardless of kind.
@@ -811,7 +847,9 @@ class CommitMessageGenerator:
811
847
  }
812
848
 
813
849
  model = self.config["anthropic"]["model"]
814
- temperature = self.config["anthropic"]["temperature"]
850
+ temperature = _resolve_temperature(
851
+ model, self.config["anthropic"].get("temperature")
852
+ )
815
853
  # ``max_output_tokens`` is the canonical config key (consistent
816
854
  # naming across providers); ``max_tokens`` is kept for backwards
817
855
  # compatibility with existing user configs.
@@ -829,9 +867,10 @@ class CommitMessageGenerator:
829
867
  request: dict[str, Any] = {
830
868
  "model": model,
831
869
  "max_tokens": max_tokens,
832
- "temperature": temperature,
833
870
  "messages": messages,
834
871
  }
872
+ if temperature is not None:
873
+ request["temperature"] = temperature
835
874
 
836
875
  if system_prompt_override:
837
876
  request["system"] = system_prompt_override
@@ -865,7 +904,7 @@ class CommitMessageGenerator:
865
904
  """
866
905
  url = "https://api.deepseek.com"
867
906
  model = self.config["deepseek"]["model"]
868
- temperature = self.config["deepseek"]["temperature"]
907
+ temperature = self.config["deepseek"].get("temperature")
869
908
  return self.generate_openai(
870
909
  content=content,
871
910
  system_prompt_override=system_prompt_override,
@@ -885,7 +924,9 @@ class CommitMessageGenerator:
885
924
  Uses direct HTTP API instead of the Google SDK.
886
925
  """
887
926
  model = self.config["gemini"]["model"]
888
- temperature = self.config["gemini"]["temperature"]
927
+ temperature = _resolve_temperature(
928
+ model, self.config["gemini"].get("temperature")
929
+ )
889
930
 
890
931
  log.debug("Using gemini model '%s'.", model)
891
932
 
@@ -900,11 +941,12 @@ class CommitMessageGenerator:
900
941
  if system_prompt_override:
901
942
  text = f"{system_prompt_override}\n\n{text}"
902
943
 
944
+ generation_config: dict[str, Any] = {}
945
+ if temperature is not None:
946
+ generation_config["temperature"] = temperature
903
947
  request = {
904
948
  "contents": [{"parts": [{"text": text}]}],
905
- "generationConfig": {
906
- "temperature": temperature,
907
- },
949
+ "generationConfig": generation_config,
908
950
  }
909
951
 
910
952
  start = time.perf_counter()
@@ -942,7 +984,9 @@ class CommitMessageGenerator:
942
984
  }
943
985
 
944
986
  model = self.config["groq"]["model"]
945
- temperature = self.config["groq"]["temperature"]
987
+ temperature = _resolve_temperature(
988
+ model, self.config["groq"].get("temperature")
989
+ )
946
990
 
947
991
  log.debug("Using groq model '%s'.", model)
948
992
 
@@ -966,8 +1010,9 @@ class CommitMessageGenerator:
966
1010
  request = {
967
1011
  "model": model,
968
1012
  "messages": messages,
969
- "temperature": temperature,
970
1013
  }
1014
+ if temperature is not None:
1015
+ request["temperature"] = temperature
971
1016
 
972
1017
  start = time.perf_counter()
973
1018
  response = _http_post( # nosec B113
@@ -1002,7 +1047,9 @@ class CommitMessageGenerator:
1002
1047
  }
1003
1048
 
1004
1049
  model = self.config["mistral"]["model"]
1005
- temperature = self.config["mistral"]["temperature"]
1050
+ temperature = _resolve_temperature(
1051
+ model, self.config["mistral"].get("temperature")
1052
+ )
1006
1053
 
1007
1054
  log.debug("Using mistral model '%s'.", model)
1008
1055
 
@@ -1026,8 +1073,9 @@ class CommitMessageGenerator:
1026
1073
  request = {
1027
1074
  "model": model,
1028
1075
  "messages": messages,
1029
- "temperature": temperature,
1030
1076
  }
1077
+ if temperature is not None:
1078
+ request["temperature"] = temperature
1031
1079
 
1032
1080
  start = time.perf_counter()
1033
1081
  response = _http_post( # nosec B113
@@ -1187,7 +1235,9 @@ class CommitMessageGenerator:
1187
1235
  self._start_ollama_server_if_needed()
1188
1236
 
1189
1237
  model = self.config["ollama"]["model"]
1190
- temperature = self.config["ollama"]["temperature"]
1238
+ temperature = _resolve_temperature(
1239
+ model, self.config["ollama"].get("temperature")
1240
+ )
1191
1241
 
1192
1242
  log.debug("Using ollama model '%s'.", model)
1193
1243
 
@@ -1198,13 +1248,14 @@ class CommitMessageGenerator:
1198
1248
  messages.append({"role": "system", "content": system_prompt_override})
1199
1249
  messages.append({"role": "user", "content": content})
1200
1250
 
1251
+ options: dict[str, Any] = {}
1252
+ if temperature is not None:
1253
+ options["temperature"] = temperature
1201
1254
  request: dict[str, Any] = {
1202
1255
  "model": model,
1203
1256
  "messages": messages,
1204
1257
  "stream": False,
1205
- "options": {
1206
- "temperature": temperature,
1207
- },
1258
+ "options": options,
1208
1259
  }
1209
1260
 
1210
1261
  start = time.perf_counter()
@@ -1278,11 +1329,12 @@ class CommitMessageGenerator:
1278
1329
  if model_override is not None
1279
1330
  else self.config["openai"]["model"]
1280
1331
  )
1281
- temperature = (
1332
+ raw_temperature = (
1282
1333
  temperature_override
1283
1334
  if temperature_override is not None
1284
- else self.config["openai"]["temperature"]
1335
+ else self.config.get(provider_name, {}).get("temperature")
1285
1336
  )
1337
+ temperature = _resolve_temperature(model, raw_temperature)
1286
1338
 
1287
1339
  log.debug("Using %s model '%s'.", provider_name, model)
1288
1340
 
@@ -1293,13 +1345,16 @@ class CommitMessageGenerator:
1293
1345
 
1294
1346
  messages.append({"role": "user", "content": content})
1295
1347
 
1348
+ create_kwargs: dict[str, Any] = {
1349
+ "model": model,
1350
+ "messages": messages,
1351
+ "stream": False,
1352
+ }
1353
+ if temperature is not None:
1354
+ create_kwargs["temperature"] = temperature
1355
+
1296
1356
  start = time.perf_counter()
1297
- completion = client.chat.completions.create(
1298
- model=model,
1299
- messages=messages,
1300
- temperature=temperature,
1301
- stream=False,
1302
- )
1357
+ completion = client.chat.completions.create(**create_kwargs)
1303
1358
  self._last_latency_ms = int((time.perf_counter() - start) * 1000)
1304
1359
 
1305
1360
  usage = completion.usage
@@ -1333,7 +1388,7 @@ class CommitMessageGenerator:
1333
1388
  }
1334
1389
 
1335
1390
  model = self.config["xai"]["model"]
1336
- temperature = self.config["xai"]["temperature"]
1391
+ temperature = _resolve_temperature(model, self.config["xai"].get("temperature"))
1337
1392
 
1338
1393
  log.debug("Using xai model '%s'.", model)
1339
1394
 
@@ -1357,8 +1412,9 @@ class CommitMessageGenerator:
1357
1412
  request = {
1358
1413
  "model": model,
1359
1414
  "messages": messages,
1360
- "temperature": temperature,
1361
1415
  }
1416
+ if temperature is not None:
1417
+ request["temperature"] = temperature
1362
1418
  start = time.perf_counter()
1363
1419
  response = _http_post( # nosec B113
1364
1420
  url, json=request, headers=headers, timeout=self._timeout("xai")