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.
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/PKG-INFO +1 -1
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/_version.py +3 -3
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/config.py +22 -4
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/init.py +15 -11
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/validate.py +32 -25
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/PKG-INFO +1 -1
- git_cai_cli-0.15.5/src/git_cai_cli.egg-info/scm_version.json +8 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_config.py +25 -3
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_init.py +36 -0
- git_cai_cli-0.15.4/src/git_cai_cli.egg-info/scm_version.json +0 -8
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.caiignore +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.gitattributes +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/cd/.SRCINFO +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/cd/PKGBUILD +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/ci/_version.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/ci/cai_config.ci.yml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/ci/tokens.ci.yml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/workflows/python-tests.yml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/workflows/release.yml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.github/workflows/release_aur.yml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.gitignore +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.bandit.yml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.checkov.yml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.flake8 +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.ls-lint.yml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.markdown-link-check.json +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.markdownlint.json +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.proselintrc +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.pylintrc +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/.yamllint.yml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/check_git_branch_name.sh +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/lychee.toml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.linters/pyrightconfig.json +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.markdownlintignore +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.mega-linter.yml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.semgrepignore +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/.trivyignore +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/CLAUDE.md +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/LICENSE +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/Makefile +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/README.md +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/cai_config.yml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/docs/git-cai.txt +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/docs/man/git-cai.1 +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/pyproject.toml +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/setup.cfg +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/__init__.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/__init__.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/cli.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/helptext.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/cli/modes.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/__init__.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/completion.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/doctor.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/editors.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/gitutils.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/languages.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/llm.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/options.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/pr.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/prompts_fallback.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/secrets.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/spinner.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/squash.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/core/stats.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli/main.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/SOURCES.txt +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/dependency_links.txt +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/entry_points.txt +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/requires.txt +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/scm_file_list.json +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/src/git_cai_cli.egg-info/top_level.txt +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/conftest.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_cli_integration.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_config_integration.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_gitutils_integration.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_modes_integration.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_options_integration.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_pr_integration.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/integration/test_squash_integration.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_amend.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_branch_context.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_classification.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_cli.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_completion.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_conventional.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_doctor.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_gitutils.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_helptext.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_llm.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_main.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_modes.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_options.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_pr.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_print.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_prompt_loading.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_secrets.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_set_config.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_signoff.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_spinner.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_squash.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_stats.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/tests/unit/test_validate.py +0 -0
- {git_cai_cli-0.15.4 → git_cai_cli-0.15.5}/uv.lock +0 -0
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 15,
|
|
21
|
+
__version__ = version = '0.15.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 15, 5)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
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
|
-
|
|
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": {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
config: dict[str, Any] =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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"}
|
|
@@ -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
|
|
282
|
-
assert DEFAULT_CONFIG["anthropic"]["
|
|
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"]["
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|