git-cai-cli 0.15.4__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.4 → git_cai_cli-0.15.5}/PKG-INFO +1 -1
  2. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/_version.py +3 -3
  3. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/config.py +22 -4
  4. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/init.py +15 -11
  5. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/validate.py +32 -25
  6. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/PKG-INFO +1 -1
  7. git_cai_cli-0.15.5/src/git_cai_cli.egg-info/scm_version.json +8 -0
  8. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_config.py +25 -3
  9. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_init.py +36 -0
  10. git_cai_cli-0.15.4/src/git_cai_cli.egg-info/scm_version.json +0 -8
  11. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.caiignore +0 -0
  12. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.gitattributes +0 -0
  13. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/cd/.SRCINFO +0 -0
  14. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/cd/PKGBUILD +0 -0
  15. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/ci/_version.py +0 -0
  16. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/ci/cai_config.ci.yml +0 -0
  17. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/ci/tokens.ci.yml +0 -0
  18. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/workflows/python-tests.yml +0 -0
  19. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/workflows/release.yml +0 -0
  20. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/workflows/release_aur.yml +0 -0
  21. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.gitignore +0 -0
  22. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.bandit.yml +0 -0
  23. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.checkov.yml +0 -0
  24. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.flake8 +0 -0
  25. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.ls-lint.yml +0 -0
  26. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.markdown-link-check.json +0 -0
  27. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.markdownlint.json +0 -0
  28. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.proselintrc +0 -0
  29. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.pylintrc +0 -0
  30. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.yamllint.yml +0 -0
  31. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/check_git_branch_name.sh +0 -0
  32. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/lychee.toml +0 -0
  33. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/pyrightconfig.json +0 -0
  34. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.markdownlintignore +0 -0
  35. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.mega-linter.yml +0 -0
  36. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.semgrepignore +0 -0
  37. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.trivyignore +0 -0
  38. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/CLAUDE.md +0 -0
  39. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/LICENSE +0 -0
  40. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/Makefile +0 -0
  41. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/README.md +0 -0
  42. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/cai_config.yml +0 -0
  43. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/docs/git-cai.txt +0 -0
  44. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/docs/man/git-cai.1 +0 -0
  45. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/pyproject.toml +0 -0
  46. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/setup.cfg +0 -0
  47. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/__init__.py +0 -0
  48. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/__init__.py +0 -0
  49. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/cli.py +0 -0
  50. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/helptext.py +0 -0
  51. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/modes.py +0 -0
  52. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/__init__.py +0 -0
  53. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/completion.py +0 -0
  54. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/doctor.py +0 -0
  55. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/editors.py +0 -0
  56. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/gitutils.py +0 -0
  57. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/languages.py +0 -0
  58. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/llm.py +0 -0
  59. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/options.py +0 -0
  60. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/pr.py +0 -0
  61. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/prompts_fallback.py +0 -0
  62. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/secrets.py +0 -0
  63. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/spinner.py +0 -0
  64. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/squash.py +0 -0
  65. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/stats.py +0 -0
  66. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/main.py +0 -0
  67. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/SOURCES.txt +0 -0
  68. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/dependency_links.txt +0 -0
  69. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/entry_points.txt +0 -0
  70. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/requires.txt +0 -0
  71. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/scm_file_list.json +0 -0
  72. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/top_level.txt +0 -0
  73. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/conftest.py +0 -0
  74. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_cli_integration.py +0 -0
  75. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_config_integration.py +0 -0
  76. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_gitutils_integration.py +0 -0
  77. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_modes_integration.py +0 -0
  78. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_options_integration.py +0 -0
  79. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_pr_integration.py +0 -0
  80. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_squash_integration.py +0 -0
  81. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_amend.py +0 -0
  82. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_branch_context.py +0 -0
  83. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_classification.py +0 -0
  84. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_cli.py +0 -0
  85. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_completion.py +0 -0
  86. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_conventional.py +0 -0
  87. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_doctor.py +0 -0
  88. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_gitutils.py +0 -0
  89. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_helptext.py +0 -0
  90. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_llm.py +0 -0
  91. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_main.py +0 -0
  92. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_modes.py +0 -0
  93. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_options.py +0 -0
  94. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_pr.py +0 -0
  95. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_print.py +0 -0
  96. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_prompt_loading.py +0 -0
  97. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_secrets.py +0 -0
  98. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_set_config.py +0 -0
  99. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_signoff.py +0 -0
  100. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_spinner.py +0 -0
  101. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_squash.py +0 -0
  102. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_stats.py +0 -0
  103. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_validate.py +0 -0
  104. {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-cai-cli
3
- Version: 0.15.4
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
@@ -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.4'
22
- __version_tuple__ = version_tuple = (0, 15, 4)
21
+ __version__ = version = '0.15.5'
22
+ __version_tuple__ = version_tuple = (0, 15, 5)
23
23
 
24
- __commit_id__ = commit_id = 'gb94513fc4'
24
+ __commit_id__ = commit_id = 'g717dd683a'
@@ -37,7 +37,8 @@ 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
43
  "openai": {"model": "gpt-5.4-mini"},
43
44
  "deepseek": {"model": "deepseek-chat", "temperature": 0},
@@ -45,7 +46,12 @@ DEFAULT_CONFIG: dict[str, Any] = {
45
46
  "groq": {"model": "openai/gpt-oss-20b", "temperature": 0},
46
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
@@ -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:
@@ -13,6 +13,37 @@ log = logging.getLogger(__name__)
13
13
  _AUTH_STATUS_CODES = frozenset({401, 403})
14
14
  _RATE_LIMIT_STATUS_CODES = frozenset({429})
15
15
 
16
+ # Every top-level (non-provider) config key git-cai understands. Anything
17
+ # here must also exist in ``DEFAULT_CONFIG`` so that ``git cai -g`` writes
18
+ # it out; ``test_config.py`` asserts the two stay in sync.
19
+ ALLOWED_GLOBAL_KEYS = frozenset(
20
+ {
21
+ "branch_context",
22
+ "conventional",
23
+ "language",
24
+ "default",
25
+ "style",
26
+ "emoji",
27
+ "load_tokens_from",
28
+ "prompt_file",
29
+ "squash_prompt_file",
30
+ "full_files_prompt_file",
31
+ "token_logging",
32
+ "measure_time",
33
+ "timeout",
34
+ "full_files",
35
+ "max_diff_bytes",
36
+ "pr_to_file",
37
+ "pr_file_name",
38
+ "pr_prompt_file",
39
+ "stats",
40
+ "stats_db_path",
41
+ "signoff",
42
+ "secret_scan",
43
+ "secret_scan_exclude",
44
+ }
45
+ )
46
+
16
47
 
17
48
  def _extract_api_error_message(response: requests.Response | None) -> str:
18
49
  """Best-effort extraction of an upstream API error body's human message.
@@ -50,31 +81,7 @@ def _validate_config_keys(config: dict[str, Any], reference: dict[str, Any]) ->
50
81
  """
51
82
  log.debug("Validating configuration keys")
52
83
 
53
- allowed_global_keys = {
54
- "branch_context",
55
- "conventional",
56
- "language",
57
- "default",
58
- "style",
59
- "emoji",
60
- "load_tokens_from",
61
- "prompt_file",
62
- "squash_prompt_file",
63
- "full_files_prompt_file",
64
- "token_logging",
65
- "measure_time",
66
- "timeout",
67
- "full_files",
68
- "max_diff_bytes",
69
- "pr_to_file",
70
- "pr_file_name",
71
- "pr_prompt_file",
72
- "stats",
73
- "stats_db_path",
74
- "signoff",
75
- "secret_scan",
76
- "secret_scan_exclude",
77
- }
84
+ allowed_global_keys = set(ALLOWED_GLOBAL_KEYS)
78
85
  # Internal escape-hatch keys: accepted if a user sets them, but never
79
86
  # reported as "missing" — they aren't part of the documented surface.
80
87
  internal_only_keys = {"stats_db_path", "secret_scan_exclude"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-cai-cli
3
- Version: 0.15.4
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
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.15.5",
3
+ "distance": 0,
4
+ "node": "g717dd683ac826d23864fc37782ddc4dd4e3fd57e",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-11"
8
+ }
@@ -5,13 +5,16 @@ from unittest.mock import patch
5
5
  import yaml
6
6
  from git_cai_cli.core.config import (
7
7
  DEFAULT_CONFIG,
8
+ KNOWN_PROVIDERS,
8
9
  TOKEN_TEMPLATE,
9
10
  _serialize_config,
10
11
  apply_cli_overrides,
11
12
  load_config,
12
13
  load_token,
14
+ ordered_default_config,
13
15
  set_config_value,
14
16
  )
17
+ from git_cai_cli.core.validate import ALLOWED_GLOBAL_KEYS
15
18
 
16
19
 
17
20
  def test_load_config_creates_fallback(tmp_path, monkeypatch):
@@ -278,8 +281,27 @@ def test_default_config_contains_timeout_and_full_files():
278
281
 
279
282
 
280
283
  def test_default_config_anthropic_max_tokens():
281
- """Anthropic block must default max_tokens to 32768."""
282
- assert DEFAULT_CONFIG["anthropic"]["max_tokens"] == 32768
284
+ """Anthropic block must default max_output_tokens to 32768."""
285
+ assert DEFAULT_CONFIG["anthropic"]["max_output_tokens"] == 32768
286
+
287
+
288
+ def test_generated_config_contains_every_global_key():
289
+ """`git cai -g` must write out every global key the code understands.
290
+
291
+ Guards against a new setting (secret_scan, stats_db_path, …) being
292
+ added to the reader but forgotten in the generated file.
293
+ """
294
+ generated = ordered_default_config()
295
+ missing = ALLOWED_GLOBAL_KEYS - generated.keys()
296
+ assert not missing, f"missing from generated config: {sorted(missing)}"
297
+
298
+
299
+ def test_generated_config_lists_provider_blocks_last():
300
+ """Global keys come before provider blocks, so none get buried."""
301
+ keys = list(ordered_default_config())
302
+ first_provider = min(keys.index(p) for p in KNOWN_PROVIDERS)
303
+ globals_after = [k for k in keys[first_provider:] if k not in KNOWN_PROVIDERS]
304
+ assert not globals_after
283
305
 
284
306
 
285
307
  def test_default_config_ollama_timeout():
@@ -303,7 +325,7 @@ def test_fresh_config_contains_timeout_and_full_files(tmp_path, monkeypatch):
303
325
  data = yaml.safe_load(fallback.read_text())
304
326
  assert data["timeout"] == 30
305
327
  assert data["full_files"] is False
306
- assert data["anthropic"]["max_tokens"] == 32768
328
+ assert data["anthropic"]["max_output_tokens"] == 32768
307
329
  assert data["ollama"]["timeout"] == 300
308
330
 
309
331
 
@@ -11,6 +11,8 @@ import stat
11
11
 
12
12
  import yaml
13
13
  from git_cai_cli.core import init as init_module
14
+ from git_cai_cli.core.config import DEFAULT_CONFIG, KNOWN_PROVIDERS
15
+ from git_cai_cli.core.validate import ALLOWED_GLOBAL_KEYS, _validate_config_keys
14
16
 
15
17
 
16
18
  class _InputDriver:
@@ -80,6 +82,40 @@ def test_wizard_writes_config_and_tokens_for_provider_requiring_key(
80
82
  assert mode == 0o600
81
83
 
82
84
 
85
+ def test_wizard_writes_every_global_key(tmp_path, monkeypatch):
86
+ """The wizard writes the full setting surface, not just what it asked."""
87
+ config_path = tmp_path / "cai_config.yml"
88
+ tokens_path = tmp_path / "tokens.yml"
89
+
90
+ driver = _InputDriver(
91
+ prompts=["ollama", "de", "funny"],
92
+ confirms=[False],
93
+ passwords=[],
94
+ )
95
+ _install(monkeypatch, driver)
96
+
97
+ assert (
98
+ init_module.run_init_wizard(config_path=config_path, tokens_path=tokens_path)
99
+ == 0
100
+ )
101
+
102
+ config = yaml.safe_load(config_path.read_text())
103
+ missing = ALLOWED_GLOBAL_KEYS - config.keys()
104
+ assert not missing, f"wizard did not write: {sorted(missing)}"
105
+
106
+ # Wizard answers still win over the defaults.
107
+ assert config["default"] == "ollama"
108
+ assert config["language"] == "de"
109
+ assert config["style"] == "funny"
110
+ assert config["emoji"] is False
111
+
112
+ # Every provider block is present, so `--provider x` works without edits.
113
+ assert KNOWN_PROVIDERS <= config.keys()
114
+
115
+ # And the result is a config git-cai accepts.
116
+ _validate_config_keys(config, DEFAULT_CONFIG)
117
+
118
+
83
119
  def test_wizard_skips_token_prompt_for_ollama(tmp_path, monkeypatch):
84
120
  config_path = tmp_path / "cai_config.yml"
85
121
  tokens_path = tmp_path / "tokens.yml"
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.15.4",
3
- "distance": 0,
4
- "node": "gb94513fc4b1c16786ba4fadfd86b22120a49c041",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-07-05"
8
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes