lgit-cli 3.7.0__py3-none-any.whl
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.
- lgit/__init__.py +75 -0
- lgit/__main__.py +8 -0
- lgit/analysis.py +326 -0
- lgit/api.py +1077 -0
- lgit/cache.py +338 -0
- lgit/changelog.py +523 -0
- lgit/cli.py +1104 -0
- lgit/compose.py +2110 -0
- lgit/config.py +437 -0
- lgit/diffing.py +384 -0
- lgit/errors.py +137 -0
- lgit/git.py +852 -0
- lgit/map_reduce.py +508 -0
- lgit/markdown_output.py +709 -0
- lgit/models.py +924 -0
- lgit/normalization.py +411 -0
- lgit/patch.py +784 -0
- lgit/profile.py +426 -0
- lgit/py.typed +0 -0
- lgit/repo.py +287 -0
- lgit/resources/__init__.py +1 -0
- lgit/resources/commit_types.json +242 -0
- lgit/resources/prompts/analysis/default.md +237 -0
- lgit/resources/prompts/analysis/markdown.md +112 -0
- lgit/resources/prompts/changelog/default.md +89 -0
- lgit/resources/prompts/changelog/markdown.md +60 -0
- lgit/resources/prompts/compose-bind/default.md +40 -0
- lgit/resources/prompts/compose-bind/markdown.md +41 -0
- lgit/resources/prompts/compose-intent/default.md +63 -0
- lgit/resources/prompts/compose-intent/markdown.md +59 -0
- lgit/resources/prompts/fast/default.md +46 -0
- lgit/resources/prompts/fast/markdown.md +51 -0
- lgit/resources/prompts/map/default.md +67 -0
- lgit/resources/prompts/map/markdown.md +63 -0
- lgit/resources/prompts/reduce/default.md +81 -0
- lgit/resources/prompts/reduce/markdown.md +68 -0
- lgit/resources/prompts/summary/default.md +74 -0
- lgit/resources/prompts/summary/markdown.md +77 -0
- lgit/resources/validation_data.json +1 -0
- lgit/rewrite.py +392 -0
- lgit/style.py +295 -0
- lgit/templates.py +385 -0
- lgit/testing/__init__.py +62 -0
- lgit/testing/compare.py +57 -0
- lgit/testing/fixture.py +386 -0
- lgit/testing/report.py +201 -0
- lgit/testing/runner.py +256 -0
- lgit/tokens.py +90 -0
- lgit/validation.py +545 -0
- lgit_cli-3.7.0.dist-info/METADATA +288 -0
- lgit_cli-3.7.0.dist-info/RECORD +54 -0
- lgit_cli-3.7.0.dist-info/WHEEL +4 -0
- lgit_cli-3.7.0.dist-info/entry_points.txt +2 -0
- lgit_cli-3.7.0.dist-info/licenses/LICENSE +21 -0
lgit/config.py
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"""Configuration loading for the llm-git Python runtime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import tomllib
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Self
|
|
12
|
+
|
|
13
|
+
from .errors import ConfigError
|
|
14
|
+
from .models import (
|
|
15
|
+
ApiMode,
|
|
16
|
+
CategoryConfig,
|
|
17
|
+
CategoryMatch,
|
|
18
|
+
ResolvedApiMode,
|
|
19
|
+
TypeConfig,
|
|
20
|
+
default_categories,
|
|
21
|
+
default_classifier_hint,
|
|
22
|
+
default_types,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
DEFAULT_API_BASE_URL = "http://localhost:4000"
|
|
26
|
+
DEFAULT_ANALYSIS_MODEL = "claude-opus-4.5"
|
|
27
|
+
DEFAULT_SUMMARY_MODEL = "claude-haiku-4-5"
|
|
28
|
+
DEFAULT_CONFIG_SUBPATH = Path(".config/llm-git/config.toml")
|
|
29
|
+
|
|
30
|
+
DEFAULT_EXCLUDED_FILES = [
|
|
31
|
+
"Cargo.lock",
|
|
32
|
+
"package-lock.json",
|
|
33
|
+
"npm-shrinkwrap.json",
|
|
34
|
+
"yarn.lock",
|
|
35
|
+
"pnpm-lock.yaml",
|
|
36
|
+
"shrinkwrap.yaml",
|
|
37
|
+
"bun.lock",
|
|
38
|
+
"bun.lockb",
|
|
39
|
+
"deno.lock",
|
|
40
|
+
"composer.lock",
|
|
41
|
+
"Gemfile.lock",
|
|
42
|
+
"poetry.lock",
|
|
43
|
+
"Pipfile.lock",
|
|
44
|
+
"pdm.lock",
|
|
45
|
+
"uv.lock",
|
|
46
|
+
"go.sum",
|
|
47
|
+
"flake.lock",
|
|
48
|
+
"pubspec.lock",
|
|
49
|
+
"Podfile.lock",
|
|
50
|
+
"Packages.resolved",
|
|
51
|
+
"mix.lock",
|
|
52
|
+
"packages.lock.json",
|
|
53
|
+
"gradle.lockfile",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
DEFAULT_LOW_PRIORITY_EXTENSIONS = [
|
|
57
|
+
".lock",
|
|
58
|
+
".snap",
|
|
59
|
+
".sum",
|
|
60
|
+
".toml",
|
|
61
|
+
".yaml",
|
|
62
|
+
".yml",
|
|
63
|
+
".json",
|
|
64
|
+
".md",
|
|
65
|
+
".txt",
|
|
66
|
+
".log",
|
|
67
|
+
".tmp",
|
|
68
|
+
".bak",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
_TRUE_VALUES = frozenset({"1", "true", "yes", "on"})
|
|
72
|
+
_FALSE_VALUES = frozenset({"0", "false", "no", "off"})
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(slots=True)
|
|
76
|
+
class CommitConfig:
|
|
77
|
+
"""Runtime configuration loaded from defaults, TOML, and environment."""
|
|
78
|
+
|
|
79
|
+
api_base_url: str = DEFAULT_API_BASE_URL
|
|
80
|
+
api_mode: ApiMode = ApiMode.AUTO
|
|
81
|
+
api_key: str | None = None
|
|
82
|
+
request_timeout_secs: int = 120
|
|
83
|
+
connect_timeout_secs: int = 30
|
|
84
|
+
disable_git_background_features: bool = True
|
|
85
|
+
compose_max_rounds: int = 5
|
|
86
|
+
summary_guideline: int = 72
|
|
87
|
+
summary_soft_limit: int = 96
|
|
88
|
+
summary_hard_limit: int = 128
|
|
89
|
+
max_retries: int = 3
|
|
90
|
+
initial_backoff_ms: int = 1000
|
|
91
|
+
auto_fast_threshold_lines: int = 200
|
|
92
|
+
max_diff_length: int = 100000
|
|
93
|
+
max_diff_tokens: int = 25000
|
|
94
|
+
wide_change_threshold: float = 0.50
|
|
95
|
+
analysis_model: str = DEFAULT_ANALYSIS_MODEL
|
|
96
|
+
summary_model: str = DEFAULT_SUMMARY_MODEL
|
|
97
|
+
legacy_model: str | None = None
|
|
98
|
+
excluded_files: list[str] = field(default_factory=lambda: list(DEFAULT_EXCLUDED_FILES))
|
|
99
|
+
low_priority_extensions: list[str] = field(default_factory=lambda: list(DEFAULT_LOW_PRIORITY_EXTENSIONS))
|
|
100
|
+
max_detail_tokens: int = 200
|
|
101
|
+
analysis_prompt_variant: str = "default"
|
|
102
|
+
summary_prompt_variant: str = "default"
|
|
103
|
+
wide_change_abstract: bool = True
|
|
104
|
+
markdown_output: bool = True
|
|
105
|
+
exclude_old_message: bool = True
|
|
106
|
+
gpg_sign: bool = False
|
|
107
|
+
signoff: bool = False
|
|
108
|
+
types: dict[str, TypeConfig] = field(default_factory=default_types)
|
|
109
|
+
classifier_hint: str = field(default_factory=default_classifier_hint)
|
|
110
|
+
categories: list[CategoryConfig] = field(default_factory=default_categories)
|
|
111
|
+
changelog_enabled: bool = True
|
|
112
|
+
map_reduce_enabled: bool = True
|
|
113
|
+
map_reduce_threshold: int = 5000
|
|
114
|
+
map_batch_token_budget: int = 16000
|
|
115
|
+
cache_enabled: bool = True
|
|
116
|
+
cache_ttl_days: int = 14
|
|
117
|
+
cache_dir: str | None = None
|
|
118
|
+
analysis_prompt: str = ""
|
|
119
|
+
summary_prompt: str = ""
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def load(cls, path: str | os.PathLike[str] | None = None) -> Self:
|
|
123
|
+
"""Load configuration from the default path or ``LLM_GIT_CONFIG``."""
|
|
124
|
+
config_path = _selected_config_path(path)
|
|
125
|
+
if config_path is not None and config_path.exists():
|
|
126
|
+
return cls.from_file(config_path)
|
|
127
|
+
config = cls()
|
|
128
|
+
config._finalize()
|
|
129
|
+
return config
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def from_file(cls, path: str | os.PathLike[str]) -> Self:
|
|
133
|
+
"""Load configuration from a TOML file, then apply environment overrides."""
|
|
134
|
+
config_path = Path(path).expanduser()
|
|
135
|
+
try:
|
|
136
|
+
contents = config_path.read_text(encoding="utf-8")
|
|
137
|
+
except OSError as exc:
|
|
138
|
+
raise ConfigError(f"Failed to read config {config_path}: {exc}") from exc
|
|
139
|
+
try:
|
|
140
|
+
data = tomllib.loads(contents)
|
|
141
|
+
except tomllib.TOMLDecodeError as exc:
|
|
142
|
+
raise ConfigError(f"Failed to parse config {config_path}: {exc}") from exc
|
|
143
|
+
config = cls.from_mapping(data)
|
|
144
|
+
config._finalize()
|
|
145
|
+
return config
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def from_mapping(cls, data: Mapping[str, Any]) -> Self:
|
|
149
|
+
"""Build configuration from a TOML-compatible mapping."""
|
|
150
|
+
kwargs: dict[str, Any] = {}
|
|
151
|
+
for raw_key, value in data.items():
|
|
152
|
+
key = _normalize_config_key(str(raw_key))
|
|
153
|
+
if key == "model":
|
|
154
|
+
kwargs["legacy_model"] = None if value is None else str(value)
|
|
155
|
+
elif key == "api_mode":
|
|
156
|
+
kwargs[key] = ApiMode.from_raw(str(value))
|
|
157
|
+
elif key == "types":
|
|
158
|
+
kwargs[key] = _parse_types(value)
|
|
159
|
+
elif key == "categories":
|
|
160
|
+
kwargs[key] = _parse_categories(value)
|
|
161
|
+
elif key in _FIELD_COERCERS:
|
|
162
|
+
kwargs[key] = _FIELD_COERCERS[key](value)
|
|
163
|
+
return cls(**kwargs)
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def resolved_api_mode(self) -> ResolvedApiMode:
|
|
167
|
+
"""Return the concrete API protocol selected for this configuration."""
|
|
168
|
+
return ResolvedApiMode.from_api_mode(self.api_mode, self.api_base_url)
|
|
169
|
+
|
|
170
|
+
def resolve_api_mode(self, model_name: str | None = None) -> ResolvedApiMode:
|
|
171
|
+
"""Return the concrete API protocol; ``model_name`` is accepted for compatibility."""
|
|
172
|
+
return self.resolved_api_mode
|
|
173
|
+
|
|
174
|
+
def _finalize(self) -> None:
|
|
175
|
+
_apply_env_overrides(self)
|
|
176
|
+
self._normalize_models()
|
|
177
|
+
if self.api_key is not None:
|
|
178
|
+
self.api_key = _resolve_config_value(self.api_key)
|
|
179
|
+
self._load_prompts()
|
|
180
|
+
|
|
181
|
+
def _normalize_models(self) -> None:
|
|
182
|
+
if self.legacy_model:
|
|
183
|
+
model = self.legacy_model
|
|
184
|
+
self.analysis_model = model
|
|
185
|
+
if self.summary_model == DEFAULT_SUMMARY_MODEL:
|
|
186
|
+
self.summary_model = model
|
|
187
|
+
|
|
188
|
+
def _load_prompts(self) -> None:
|
|
189
|
+
from .templates import ensure_prompts_dir
|
|
190
|
+
|
|
191
|
+
ensure_prompts_dir()
|
|
192
|
+
self.analysis_prompt = ""
|
|
193
|
+
self.summary_prompt = ""
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def default_config_path() -> Path:
|
|
197
|
+
"""Return the default llm-git TOML config path for the current user."""
|
|
198
|
+
home = os.environ.get("HOME") or os.environ.get("USERPROFILE")
|
|
199
|
+
if not home:
|
|
200
|
+
raise ConfigError("No home directory found (tried HOME and USERPROFILE)")
|
|
201
|
+
return Path(home).joinpath(DEFAULT_CONFIG_SUBPATH)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _selected_config_path(path: str | os.PathLike[str] | None) -> Path | None:
|
|
205
|
+
if path is not None:
|
|
206
|
+
return Path(path).expanduser()
|
|
207
|
+
if custom_path := os.environ.get("LLM_GIT_CONFIG"):
|
|
208
|
+
return Path(custom_path).expanduser()
|
|
209
|
+
try:
|
|
210
|
+
return default_config_path()
|
|
211
|
+
except ConfigError:
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _apply_env_overrides(config: CommitConfig) -> None:
|
|
216
|
+
if "LLM_GIT_API_URL" in os.environ:
|
|
217
|
+
config.api_base_url = os.environ["LLM_GIT_API_URL"]
|
|
218
|
+
if "LLM_GIT_API_KEY" in os.environ:
|
|
219
|
+
config.api_key = os.environ["LLM_GIT_API_KEY"]
|
|
220
|
+
if "LLM_GIT_API_MODE" in os.environ:
|
|
221
|
+
config.api_mode = _parse_api_mode(os.environ["LLM_GIT_API_MODE"])
|
|
222
|
+
if value := os.environ.get("LLM_GIT_DISABLE_GIT_BACKGROUND_FEATURES"):
|
|
223
|
+
parsed = _parse_env_bool(value)
|
|
224
|
+
if parsed is not None:
|
|
225
|
+
config.disable_git_background_features = parsed
|
|
226
|
+
if value := os.environ.get("LLM_GIT_CACHE_DISABLED"):
|
|
227
|
+
parsed = _parse_env_bool(value)
|
|
228
|
+
if parsed is not None:
|
|
229
|
+
config.cache_enabled = not parsed
|
|
230
|
+
if value := os.environ.get("LLM_GIT_CACHE_TTL_DAYS"):
|
|
231
|
+
try:
|
|
232
|
+
config.cache_ttl_days = int(value.strip())
|
|
233
|
+
except ValueError:
|
|
234
|
+
pass
|
|
235
|
+
if "LLM_GIT_CACHE_DIR" in os.environ:
|
|
236
|
+
value = os.environ["LLM_GIT_CACHE_DIR"].strip()
|
|
237
|
+
config.cache_dir = value or None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _parse_env_bool(value: str) -> bool | None:
|
|
241
|
+
normalized = value.strip().lower()
|
|
242
|
+
if normalized in _TRUE_VALUES:
|
|
243
|
+
return True
|
|
244
|
+
if normalized in _FALSE_VALUES:
|
|
245
|
+
return False
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _parse_api_mode(value: str) -> ApiMode:
|
|
250
|
+
match value.strip().lower().replace("_", "-"):
|
|
251
|
+
case "chat" | "chat-completions":
|
|
252
|
+
return ApiMode.CHAT_COMPLETIONS
|
|
253
|
+
case "anthropic" | "messages" | "anthropic-messages":
|
|
254
|
+
return ApiMode.ANTHROPIC_MESSAGES
|
|
255
|
+
case _:
|
|
256
|
+
return ApiMode.AUTO
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _resolve_config_value(raw: str) -> str:
|
|
260
|
+
value = raw.strip()
|
|
261
|
+
if not value.startswith("!"):
|
|
262
|
+
return raw
|
|
263
|
+
command = value[1:].strip()
|
|
264
|
+
if not command:
|
|
265
|
+
raise ConfigError("api_key command is empty")
|
|
266
|
+
if command == "cat" or command.startswith("cat "):
|
|
267
|
+
path_text = command[3:].strip()
|
|
268
|
+
if not path_text:
|
|
269
|
+
raise ConfigError("api_key `!cat` command requires a path")
|
|
270
|
+
path = Path(path_text).expanduser()
|
|
271
|
+
try:
|
|
272
|
+
return path.read_text(encoding="utf-8").strip()
|
|
273
|
+
except OSError as exc:
|
|
274
|
+
raise ConfigError(f"api_key `!cat` failed to read {path}: {exc}") from exc
|
|
275
|
+
try:
|
|
276
|
+
output = subprocess.run(
|
|
277
|
+
["/bin/sh", "-c", command],
|
|
278
|
+
check=False,
|
|
279
|
+
capture_output=True,
|
|
280
|
+
stdin=subprocess.DEVNULL,
|
|
281
|
+
text=True,
|
|
282
|
+
)
|
|
283
|
+
except OSError as exc:
|
|
284
|
+
raise ConfigError(f"api_key `!{command}` failed to spawn: {exc}") from exc
|
|
285
|
+
if output.returncode != 0:
|
|
286
|
+
stderr = output.stderr.strip()
|
|
287
|
+
raise ConfigError(f"api_key `!{command}` exited with status {output.returncode}: {stderr}")
|
|
288
|
+
return output.stdout.strip()
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _normalize_config_key(key: str) -> str:
|
|
292
|
+
return key.strip().replace("-", "_")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _to_bool(value: Any) -> bool:
|
|
296
|
+
if isinstance(value, bool):
|
|
297
|
+
return value
|
|
298
|
+
if isinstance(value, str):
|
|
299
|
+
parsed = _parse_env_bool(value)
|
|
300
|
+
if parsed is not None:
|
|
301
|
+
return parsed
|
|
302
|
+
return bool(value)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _to_int(value: Any) -> int:
|
|
306
|
+
return int(value)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _to_float(value: Any) -> float:
|
|
310
|
+
return float(value)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _to_str(value: Any) -> str:
|
|
314
|
+
return str(value)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _to_optional_str(value: Any) -> str | None:
|
|
318
|
+
if value is None:
|
|
319
|
+
return None
|
|
320
|
+
text = str(value).strip()
|
|
321
|
+
return text or None
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _to_str_list(value: Any) -> list[str]:
|
|
325
|
+
if value is None:
|
|
326
|
+
return []
|
|
327
|
+
if isinstance(value, str):
|
|
328
|
+
return [value]
|
|
329
|
+
return [str(item) for item in value]
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _to_str_tuple(value: Any) -> tuple[str, ...]:
|
|
333
|
+
return tuple(_to_str_list(value))
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _parse_types(value: Any) -> dict[str, TypeConfig]:
|
|
337
|
+
if value is None:
|
|
338
|
+
return {}
|
|
339
|
+
if isinstance(value, Mapping):
|
|
340
|
+
return {str(name).strip().lower(): _parse_type_config(config) for name, config in value.items()}
|
|
341
|
+
types: dict[str, TypeConfig] = {}
|
|
342
|
+
for item in value:
|
|
343
|
+
if not isinstance(item, Mapping) or "name" not in item:
|
|
344
|
+
raise ConfigError("types entries must be tables with a name")
|
|
345
|
+
name = str(item["name"]).strip().lower()
|
|
346
|
+
types[name] = _parse_type_config(item)
|
|
347
|
+
return types
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _parse_type_config(value: Any) -> TypeConfig:
|
|
351
|
+
if isinstance(value, str):
|
|
352
|
+
return TypeConfig(description=value)
|
|
353
|
+
if not isinstance(value, Mapping):
|
|
354
|
+
raise ConfigError("type config entries must be tables or strings")
|
|
355
|
+
return TypeConfig(
|
|
356
|
+
description=str(value.get("description", "")),
|
|
357
|
+
diff_indicators=_to_str_tuple(value.get("diff_indicators", ())),
|
|
358
|
+
file_patterns=_to_str_tuple(value.get("file_patterns", ())),
|
|
359
|
+
examples=_to_str_tuple(value.get("examples", ())),
|
|
360
|
+
hint=str(value.get("hint", "")),
|
|
361
|
+
aliases=_to_str_tuple(value.get("aliases", ())),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _parse_categories(value: Any) -> list[CategoryConfig]:
|
|
366
|
+
if value is None:
|
|
367
|
+
return []
|
|
368
|
+
return [_parse_category_config(item) for item in value]
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _parse_category_config(value: Any) -> CategoryConfig:
|
|
372
|
+
if isinstance(value, str):
|
|
373
|
+
return CategoryConfig(name=value)
|
|
374
|
+
if not isinstance(value, Mapping):
|
|
375
|
+
raise ConfigError("category entries must be tables or strings")
|
|
376
|
+
match_data = value.get("match", {})
|
|
377
|
+
if not isinstance(match_data, Mapping):
|
|
378
|
+
raise ConfigError("category match entries must be tables")
|
|
379
|
+
return CategoryConfig(
|
|
380
|
+
name=str(value.get("name", "")),
|
|
381
|
+
header=_to_optional_str(value.get("header")),
|
|
382
|
+
match=CategoryMatch(
|
|
383
|
+
types=_to_str_tuple(match_data.get("types", ())),
|
|
384
|
+
body_contains=_to_str_tuple(match_data.get("body_contains", ())),
|
|
385
|
+
),
|
|
386
|
+
default=_to_bool(value.get("default", False)),
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
_FIELD_COERCERS = {
|
|
391
|
+
"api_base_url": _to_str,
|
|
392
|
+
"api_key": _to_optional_str,
|
|
393
|
+
"request_timeout_secs": _to_int,
|
|
394
|
+
"connect_timeout_secs": _to_int,
|
|
395
|
+
"disable_git_background_features": _to_bool,
|
|
396
|
+
"compose_max_rounds": _to_int,
|
|
397
|
+
"summary_guideline": _to_int,
|
|
398
|
+
"summary_soft_limit": _to_int,
|
|
399
|
+
"summary_hard_limit": _to_int,
|
|
400
|
+
"max_retries": _to_int,
|
|
401
|
+
"initial_backoff_ms": _to_int,
|
|
402
|
+
"auto_fast_threshold_lines": _to_int,
|
|
403
|
+
"max_diff_length": _to_int,
|
|
404
|
+
"max_diff_tokens": _to_int,
|
|
405
|
+
"wide_change_threshold": _to_float,
|
|
406
|
+
"analysis_model": _to_str,
|
|
407
|
+
"summary_model": _to_str,
|
|
408
|
+
"legacy_model": _to_optional_str,
|
|
409
|
+
"excluded_files": _to_str_list,
|
|
410
|
+
"low_priority_extensions": _to_str_list,
|
|
411
|
+
"max_detail_tokens": _to_int,
|
|
412
|
+
"analysis_prompt_variant": _to_str,
|
|
413
|
+
"summary_prompt_variant": _to_str,
|
|
414
|
+
"wide_change_abstract": _to_bool,
|
|
415
|
+
"markdown_output": _to_bool,
|
|
416
|
+
"exclude_old_message": _to_bool,
|
|
417
|
+
"gpg_sign": _to_bool,
|
|
418
|
+
"signoff": _to_bool,
|
|
419
|
+
"classifier_hint": _to_str,
|
|
420
|
+
"changelog_enabled": _to_bool,
|
|
421
|
+
"map_reduce_enabled": _to_bool,
|
|
422
|
+
"map_reduce_threshold": _to_int,
|
|
423
|
+
"map_batch_token_budget": _to_int,
|
|
424
|
+
"cache_enabled": _to_bool,
|
|
425
|
+
"cache_ttl_days": _to_int,
|
|
426
|
+
"cache_dir": _to_optional_str,
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
__all__ = [
|
|
430
|
+
"CommitConfig",
|
|
431
|
+
"DEFAULT_API_BASE_URL",
|
|
432
|
+
"DEFAULT_ANALYSIS_MODEL",
|
|
433
|
+
"DEFAULT_SUMMARY_MODEL",
|
|
434
|
+
"DEFAULT_EXCLUDED_FILES",
|
|
435
|
+
"DEFAULT_LOW_PRIORITY_EXTENSIONS",
|
|
436
|
+
"default_config_path",
|
|
437
|
+
]
|