Compare commits

...
3 Commits
14 changed files with 38 additions and 19 deletions

No files matched your search

+3 -3
View File
@@ -85,14 +85,14 @@ def setup_config() -> None:
raise typer.Exit(code=130)
ollama_host = ollama_host_input.strip() or ollama_host
# Save Config
new_config = {
# Save Config — merge with existing to preserve extra keys
new_config = {**current_config, **{
"provider": provider,
"model": model,
"api_key": api_key,
"emoji": emoji,
"ollama_host": ollama_host,
}
}}
try:
with config_path.open("w", encoding="utf-8") as f:
+1 -1
View File
@@ -145,7 +145,7 @@ class ChangelogGenerator:
old = path.read_text(encoding="utf-8")
if old.strip().startswith("# CHANGELOG"):
header, _, rest = old.partition("\n")
new_content = f"{header}\n\n{md_content}\n{rest.lstrip()}"
new_content = f"{header}\n\n{md_content}\n{rest}"
else:
new_content = f"# CHANGELOG\n\n{md_content}\n\n{old}"
else:
+2 -1
View File
@@ -48,6 +48,7 @@ class CacheManager:
def __init__(self, path: Path, max_age_minutes: int = 120) -> None:
self.path = path
self.max_age_minutes = max_age_minutes
def init_dry_run(self) -> None:
self.path.parent.mkdir(parents=True, exist_ok=True)
@@ -59,7 +60,7 @@ class CacheManager:
if dry_run:
self.init_dry_run()
return {}
if not force_rebuild and is_file_recent(self.path):
if not force_rebuild and is_file_recent(self.path, self.max_age_minutes):
return extract_commit_messages(self.path)
return {}
+6 -2
View File
@@ -70,7 +70,7 @@ class DiffBuilder:
def _partition(self, files: List[str]) -> tuple[list[str], list[str]]:
summary, to_diff = [], []
for f in files:
if any(p in f for p in IGNORED_DIFF_PATTERNS):
if Path(f).name in IGNORED_DIFF_PATTERNS:
summary.append(f"[METADATA UPDATED] {f}")
else:
to_diff.append(f)
@@ -128,7 +128,11 @@ class FileGrouper:
deepest = max(candidates, key=lambda p: len(Path(p).parts))
parent_path = str(Path(deepest).parent)
new_key = "root" if parent_path == "." else parent_path
groups[new_key].extend(groups.pop(deepest))
files = groups.pop(deepest)
if new_key in groups:
groups[new_key].extend(files)
else:
groups["root"].extend(files)
return groups
+12 -4
View File
@@ -105,13 +105,21 @@ class GitOperator:
# ------------------------------------------------------------------ actions
def stage(self, files: list[str]) -> None:
"""``git add`` the given paths."""
"""Stage the given paths — ``git add`` for existing files, ``git rm`` for deleted."""
if not files:
return
existing = [f for f in files if Path(self.cwd or ".").joinpath(f).exists()]
deleted = [f for f in files if f not in existing]
try:
self._run(["git", "add", *files])
except GitError as e:
raise GitError(f"Could not stage files: {e}") from e
if existing:
self._run(["git", "add", *existing])
if deleted:
self._run(["git", "rm", *deleted])
except GitError:
# Fallback: file state may have changed between check and command.
# Try a single git add --all for the whole set; git will handle
# both additions and removals correctly.
self._run(["git", "add", "--all", "--", *files])
def commit(self, message: str) -> None:
"""``git commit -m`` with the given message."""
+1 -1
View File
@@ -16,4 +16,4 @@ class AnthropicProvider(BaseProvider):
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return message.content[0].text
return message.content[0].text or ""
+2 -1
View File
@@ -53,7 +53,8 @@ class BaseProvider(ABC):
)
chosen_model = model or self.DEFAULT_MODEL
try:
return self._generate(prompt, api_key, chosen_model, **kwargs).strip()
text = self._generate(prompt, api_key, chosen_model, **kwargs)
return (text or "").strip()
except Exception as e:
self._handle_error(e)
+1 -1
View File
@@ -30,7 +30,7 @@ class GeminiProvider(BaseProvider):
max_output_tokens=kwargs.get("max_output_tokens", 2048),
),
)
return response.text
return response.text or ""
except Exception as e:
if is_token_limit_error(e):
raise # base class handles with friendly message
+1 -1
View File
@@ -36,7 +36,7 @@ class OllamaProvider(BaseProvider):
f"Ollama returned no choices for model {model!r}. "
"The model may not be installed locally."
)
return response.choices[0].message.content
return response.choices[0].message.content or ""
def _handle_error(self, error):
"""Override default wrapping — Ollama has no API key to check."""
+1 -1
View File
@@ -17,4 +17,4 @@ class OpenaiProvider(BaseProvider):
messages=[{"role": "user", "content": prompt}],
**kwargs,
)
return response.choices[0].message.content
return response.choices[0].message.content or ""
+5
View File
@@ -209,6 +209,11 @@ def delete_file(filepath: Path | str) -> bool:
def load_template_env(sub_dir: str) -> Environment:
template_dir = Path(__file__).parent / "templates" / sub_dir
if not template_dir.is_dir():
raise RuntimeError(
f"Template directory not found: {template_dir}. "
"The installation may be corrupted."
)
return Environment(loader=FileSystemLoader(template_dir))
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "devgen-cli"
version = "0.2.5"
version = "0.2.8"
description = "A collection of developer tools"
readme = "README.md"
requires-python = ">=3.10"
+1 -1
View File
@@ -6,7 +6,7 @@ from setuptools import find_packages, setup
setup(
name="devgen-cli",
version="0.2.5",
version="0.2.8",
packages=find_packages(),
include_package_data=True,
install_requires=[
Generated
+1 -1
View File
@@ -180,7 +180,7 @@ wheels = [
[[package]]
name = "devgen-cli"
version = "0.2.5"
version = "0.2.8"
source = { editable = "." }
dependencies = [
{ name = "anthropic" },