From 70e9068082ce4d78befc772d65e2bf540bdfec6a Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Sat, 6 Jun 2026 21:33:51 +0545 Subject: [PATCH] fix: None crash in providers, _merge_deepest loop, lock-file false positives, stage() race, cache TTL, config merge, changelog indent, template check --- devgen/cli/setup.py | 6 +++--- devgen/modules/changelog_generator.py | 2 +- devgen/modules/commit_generator.py | 3 ++- devgen/modules/diff_builder.py | 8 ++++++-- devgen/modules/git_ops.py | 7 +++++-- devgen/providers/anthropic.py | 2 +- devgen/providers/base.py | 3 ++- devgen/providers/gemini.py | 2 +- devgen/providers/ollama.py | 2 +- devgen/providers/openai.py | 2 +- devgen/utils.py | 5 +++++ pyproject.toml | 2 +- setup.py | 2 +- uv.lock | 2 +- 14 files changed, 31 insertions(+), 17 deletions(-) diff --git a/devgen/cli/setup.py b/devgen/cli/setup.py index 9d349c1..f57c773 100644 --- a/devgen/cli/setup.py +++ b/devgen/cli/setup.py @@ -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: diff --git a/devgen/modules/changelog_generator.py b/devgen/modules/changelog_generator.py index fb44d9a..4339f86 100644 --- a/devgen/modules/changelog_generator.py +++ b/devgen/modules/changelog_generator.py @@ -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: diff --git a/devgen/modules/commit_generator.py b/devgen/modules/commit_generator.py index 0a20127..1c8ba41 100644 --- a/devgen/modules/commit_generator.py +++ b/devgen/modules/commit_generator.py @@ -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 {} diff --git a/devgen/modules/diff_builder.py b/devgen/modules/diff_builder.py index 02017b7..7c80a14 100644 --- a/devgen/modules/diff_builder.py +++ b/devgen/modules/diff_builder.py @@ -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.setdefault(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 diff --git a/devgen/modules/git_ops.py b/devgen/modules/git_ops.py index 75ea258..306a0a9 100644 --- a/devgen/modules/git_ops.py +++ b/devgen/modules/git_ops.py @@ -115,8 +115,11 @@ class GitOperator: self._run(["git", "add", *existing]) if deleted: self._run(["git", "rm", *deleted]) - except GitError as e: - raise GitError(f"Could not stage files: {e}") from e + 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.""" diff --git a/devgen/providers/anthropic.py b/devgen/providers/anthropic.py index 0ee17fb..73d5336 100644 --- a/devgen/providers/anthropic.py +++ b/devgen/providers/anthropic.py @@ -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 "" diff --git a/devgen/providers/base.py b/devgen/providers/base.py index 61aa2e1..72c881b 100644 --- a/devgen/providers/base.py +++ b/devgen/providers/base.py @@ -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) diff --git a/devgen/providers/gemini.py b/devgen/providers/gemini.py index 3779c84..bc9a42d 100644 --- a/devgen/providers/gemini.py +++ b/devgen/providers/gemini.py @@ -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 diff --git a/devgen/providers/ollama.py b/devgen/providers/ollama.py index 7c3afad..ca8347d 100644 --- a/devgen/providers/ollama.py +++ b/devgen/providers/ollama.py @@ -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.""" diff --git a/devgen/providers/openai.py b/devgen/providers/openai.py index 466362e..199837f 100644 --- a/devgen/providers/openai.py +++ b/devgen/providers/openai.py @@ -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 "" diff --git a/devgen/utils.py b/devgen/utils.py index e196ee1..fec659c 100644 --- a/devgen/utils.py +++ b/devgen/utils.py @@ -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)) diff --git a/pyproject.toml b/pyproject.toml index abc2c25..9593f00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "devgen-cli" -version = "0.2.7" +version = "0.2.8" description = "A collection of developer tools" readme = "README.md" requires-python = ">=3.10" diff --git a/setup.py b/setup.py index 435ea89..02ded0f 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name="devgen-cli", - version="0.2.7", + version="0.2.8", packages=find_packages(), include_package_data=True, install_requires=[ diff --git a/uv.lock b/uv.lock index 17e75b7..102c939 100644 --- a/uv.lock +++ b/uv.lock @@ -180,7 +180,7 @@ wheels = [ [[package]] name = "devgen-cli" -version = "0.2.7" +version = "0.2.8" source = { editable = "." } dependencies = [ { name = "anthropic" },