Compare commits

...
Author SHA1 Message Date
dependabot[bot] d222779cdb build(deps): bump google-genai from 2.10.0 to 2.25.0
Bumps [google-genai](https://github.com/googleapis/python-genai) from 2.10.0 to 2.25.0.
- [Release notes](https://github.com/googleapis/python-genai/releases)
- [Changelog](https://github.com/googleapis/python-genai/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-genai/compare/v2.10.0...v2.25.0)

---
updated-dependencies:
- dependency-name: google-genai
  dependency-version: 2.25.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-02 01:13:43 +00:00
sankalp e69de1d99a Merge pull request #63 from S4NKALP/dependabot/github_actions/main/actions/checkout-7
build(deps): bump actions/checkout from 6 to 7
2026-07-03 19:07:59 +05:45
dependabot[bot] e5f22bca85 build(deps): bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-03 12:45:02 +00:00
sankalp 277f197a71 fix(.github/workflows): change fail-on-scopes to runtime
Update CI to enforce runtime dependency reviews, improving coverage of actual runtime packages.
2026-07-03 18:26:35 +05:45
sankalp 2edbf95ed5 feat(provider): refactor Gemini provider to use struct schema
Improves validation and aligns with latest API changes.
2026-07-03 18:24:48 +05:45
sankalp 109fcd584f feat(root): bump version to 0.3.0 and update dependencies
Update metadata and pin production dependencies for reproducible builds.
2026-07-03 18:23:37 +05:45
sankalp 61e51b5187 chore(deps): bump dependency versions
Upgrade to latest stable releases for security and compatibility.
2026-07-03 18:21:18 +05:45
sankalp d4b694e77d fix(devgen): handle missing git binary gracefully
Ensure the function returns an empty list when git is not found, avoiding crashes.
2026-07-03 18:08:00 +05:45
sankalp 261d04c56f refactor(devgen/providers): centralize retry and quota handling in BaseProvider
Move retry logic and quota detection from provider-specific code to BaseProvider to reduce duplication and simplify error handling.
2026-07-03 18:07:43 +05:45
sankalp f7001d4243 feat(devgen/modules): streamline provider guard and enhance ManifestInspector flexibility
Add token‑limit error handling and switch git diff to null‑delimited output for reliable file detection.
2026-07-03 18:07:26 +05:45
sankalp 11a9db7986 feat(devgen): extend header regex to include deps and wip scopes and ignore markdown fences
Prevent false positives on markdown fences and enable parsing of dependency-related commit types.
2026-07-03 17:02:18 +05:45
sankalp 34f5d090ce chore(devgen): extend commit message regex to include perf and revert
Update the regex in devgen/utils.py to recognize `perf` and `revert` as valid Conventional Commit types, ensuring alignment with the specification and preventing false negatives in automated analysis.
2026-07-03 16:46:05 +05:45
sankalp d20a31bf38 feat(devgen/templates/commit): update commit_message.tpl with expanded conventional commit format
Add detailed format description, examples, and diff handling to improve commit message clarity.
2026-07-03 16:45:47 +05:45
sankalp a43e09e924 feat(.github/workflows): enable auto-merge for dependabot
Allow automatic merging of Dependabot PRs to keep the repository up‑to‑date without manual intervention.
2026-06-16 15:43:46 +05:45
17 changed files with 776 additions and 597 deletions

No files matched your search

+2 -2
View File
@@ -14,7 +14,7 @@ jobs:
name: Lint & format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -42,7 +42,7 @@ jobs:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
language: [python]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -19,4 +19,5 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
target: minor
use-github-auto-merge: true
merge-method: squash
+2 -2
View File
@@ -13,13 +13,13 @@ jobs:
name: Audit dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Dependency Review
uses: actions/dependency-review-action@v5
with:
fail-on-severity: high
fail-on-scopes: direct
fail-on-scopes: runtime
license-check: true
# Project is GPL-3.0-or-later; flag licenses that would taint it.
deny-licenses: AGPL-1.0-only, AGPL-1.0-or-later, AGPL-3.0-only, AGPL-3.0-or-later, SSPL-1.0, BUSL-1.1
+1 -1
View File
@@ -6,7 +6,7 @@ jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
with:
+15 -7
View File
@@ -33,6 +33,8 @@ from devgen.utils import (
extract_commit_messages,
get_commit_dry_run_path,
is_file_recent,
is_token_limit_error,
format_token_limit_error,
load_template,
render_template,
sanitize_ai_commit_message,
@@ -241,9 +243,10 @@ class CommitEngine:
if not self._process_group(group, group_files, cache_map):
failed.append(group)
if len(groups) > 1 and i < len(groups) - 1:
import time
if self.provider != "ollama":
import time
time.sleep(15) # Conservative 4 RPM guard for free-tier
time.sleep(15) # Conservative 4 RPM guard for free-tier
return failed
def _process_group(
@@ -265,7 +268,7 @@ class CommitEngine:
return self._apply_message(group, cache[group], files)
try:
manifest_context = ManifestInspector.summary()
manifest_context = ManifestInspector.summary(cwd=self.git.cwd)
if manifest_context:
self.logger.info("Including manifest context in prompt")
with self.console.status(
@@ -279,10 +282,15 @@ class CommitEngine:
)
message = sanitize_ai_commit_message(raw)
except Exception as e:
self.logger.error(
f"Failed to generate message for group {group!r}: {e}",
exc_info=True,
)
if is_token_limit_error(e):
msg = format_token_limit_error(self.provider, e, group=group)
self.console.print(f"[bold red]{msg}[/bold red]")
self.logger.error(msg)
else:
self.logger.error(
f"Failed to generate message for group {group!r}: {e}",
exc_info=True,
)
self.git.reset(files)
return False
+11 -10
View File
@@ -143,13 +143,14 @@ class ManifestInspector:
"""
@staticmethod
def summary() -> str:
def summary(cwd: Optional[Path] = None) -> str:
cwd = cwd or get_git_root() or Path(".")
lines = [
s
for s in (
ManifestInspector._pyproject(),
ManifestInspector._package_json(),
ManifestInspector._requirements(),
ManifestInspector._pyproject(cwd),
ManifestInspector._package_json(cwd),
ManifestInspector._requirements(cwd),
)
if s
]
@@ -164,8 +165,8 @@ class ManifestInspector:
return re.split(r"[><=!~\[]", spec, 1)[0].strip()
@staticmethod
def _pyproject() -> Optional[str]:
path = Path("pyproject.toml")
def _pyproject(cwd: Path) -> Optional[str]:
path = cwd / "pyproject.toml"
if not path.exists():
return None
try:
@@ -186,8 +187,8 @@ class ManifestInspector:
return f"[pyproject.toml] {' '.join(parts)}" if parts else None
@staticmethod
def _package_json() -> Optional[str]:
path = Path("package.json")
def _package_json(cwd: Path) -> Optional[str]:
path = cwd / "package.json"
if not path.exists():
return None
try:
@@ -203,8 +204,8 @@ class ManifestInspector:
return f"[package.json] {' '.join(parts)}" if parts else None
@staticmethod
def _requirements() -> Optional[str]:
path = Path("requirements.txt")
def _requirements(cwd: Path) -> Optional[str]:
path = cwd / "requirements.txt"
if not path.exists():
return None
try:
+4 -3
View File
@@ -91,15 +91,16 @@ class GitOperator:
[
"git",
"ls-files",
"-z",
"--deleted",
"--modified",
"--others",
"--exclude-standard",
]
)
files = {f.strip() for f in out.split("\n") if f.strip()}
staged_out = self._run(["git", "diff", "--name-only", "--cached"])
files.update(f.strip() for f in staged_out.split("\n") if f.strip())
files = {f for f in out.split("\0") if f}
staged_out = self._run(["git", "diff", "-z", "--name-only", "--cached"])
files.update(f for f in staged_out.split("\0") if f)
return sorted(files)
# ------------------------------------------------------------------ actions
+43 -20
View File
@@ -30,33 +30,56 @@ class BaseProvider(ABC):
model: str | None = None,
**kwargs: Any,
) -> str:
"""Public entry point. Validates input, dispatches, and wraps errors.
"""Public entry point. Validates input, dispatches, and wraps errors."""
import time
Args:
prompt: The prompt to send to the model.
api_key: Provider API key. May be ``None`` for local providers
(see :attr:`REQUIRES_API_KEY`).
model: Model id. Falls back to :attr:`DEFAULT_MODEL` when ``None``.
**kwargs: Forwarded to :meth:`_generate`.
Returns:
The generated text, stripped.
Raises:
ValueError: If the provider requires an API key and none was given.
RuntimeError: For any underlying failure, with a friendly message.
"""
if self.REQUIRES_API_KEY and not api_key:
raise ValueError(
f"{self.DISPLAY_NAME} API key is missing. "
"Set it via `devgen setup config` or pass --api-key."
)
chosen_model = model or self.DEFAULT_MODEL
try:
text = self._generate(prompt, api_key, chosen_model, **kwargs)
return (text or "").strip()
except Exception as e:
self._handle_error(e)
max_retries = kwargs.get("max_retries", 5)
base_delay = kwargs.get("retry_delay", 10)
for attempt in range(max_retries + 1):
try:
text = self._generate(prompt, api_key, chosen_model, **kwargs)
return (text or "").strip()
except Exception as e:
if is_token_limit_error(e):
self._handle_error(e)
if self._is_quota_error(e) and attempt < max_retries:
delay = base_delay * (2**attempt)
print(
f"\n[{self.DISPLAY_NAME}] Quota/Rate limit hit (429). "
f"Waiting {delay}s and retrying... "
f"(Attempt {attempt + 1}/{max_retries})"
)
time.sleep(delay)
continue
if self._is_quota_error(e) and attempt >= max_retries:
raise RuntimeError(
f"{self.DISPLAY_NAME} API quota exhausted (429) after multiple retries."
) from e
self._handle_error(e)
return ""
@staticmethod
def _is_quota_error(error: Exception) -> bool:
text = str(error).upper()
return any(
marker in text
for marker in (
"429",
"RESOURCE_EXHAUSTED",
"QUOTA_EXCEEDED",
"THROTTLED",
"RATE_LIMIT_EXCEEDED",
"TOO_MANY_REQUESTS",
)
)
def _handle_error(self, error: Exception) -> None:
"""Translate an internal exception into a user-facing RuntimeError.
+10 -48
View File
@@ -1,10 +1,7 @@
import time
from google import genai
from google.genai import types
from devgen.providers.base import BaseProvider
from devgen.utils import is_token_limit_error
class GeminiProvider(BaseProvider):
@@ -14,50 +11,15 @@ class GeminiProvider(BaseProvider):
DEFAULT_MODEL = "gemini-2.5-flash"
def _generate(self, prompt, api_key, model, **kwargs):
max_retries = kwargs.get("max_retries", 5)
base_delay = kwargs.get("retry_delay", 10) # 10s base for 429 backoff
client = genai.Client(api_key=api_key)
for attempt in range(max_retries + 1):
try:
response = client.models.generate_content(
model=model,
contents=prompt,
config=types.GenerateContentConfig(
temperature=kwargs.get("temperature", 0.7),
top_p=kwargs.get("top_p", 0.95),
top_k=kwargs.get("top_k", 40),
max_output_tokens=kwargs.get("max_output_tokens", 2048),
),
)
return response.text or ""
except Exception as e:
if is_token_limit_error(e):
raise # base class handles with friendly message
if self._is_quota_error(e) and attempt < max_retries:
delay = base_delay * (2**attempt)
print(
f"\n[Gemini] Quota limit hit (429). This is often a temporary "
f"rate limit (RPM). Waiting {delay}s and retrying... "
f"(Attempt {attempt + 1}/{max_retries})"
)
time.sleep(delay)
continue
if self._is_quota_error(e):
raise RuntimeError(
"Gemini API quota exhausted (429) after multiple retries.\n\n"
"POSSIBLE CAUSES:\n"
"1. Rate Limit (RPM/RPD): You are sending requests too fast for the Free Tier.\n"
"2. Daily Limit: You have reached the maximum free requests allowed for today (typically 1,500).\n"
"3. Credit/Billing: If on a paid plan, your credit may be exhausted.\n\n"
"Please check your usage metrics at: https://aistudio.google.com/app/usage"
) from e
raise
@staticmethod
def _is_quota_error(error: Exception) -> bool:
text = str(error).upper()
return any(
marker in text
for marker in ("429", "RESOURCE_EXHAUSTED", "QUOTA_EXCEEDED", "THROTTLED")
response = client.models.generate_content(
model=model,
contents=prompt,
config=types.GenerateContentConfig(
temperature=kwargs.get("temperature", 0.7),
top_p=kwargs.get("top_p", 0.95),
top_k=kwargs.get("top_k", 40),
max_output_tokens=kwargs.get("max_output_tokens", 2048),
),
)
return response.text or ""
+21 -6
View File
@@ -1,14 +1,29 @@
You write Conventional Commits. Output ONLY the message — no preamble, no labels, no quotes, no markdown.
Format:
<type>(<scope>): <summary>
[optional 1-2 line body on WHY]
<type>[optional scope][optional !]: <description>
[optional blank line]
[optional body]
[optional blank line]
[optional footer(s)]
types: feat fix refactor perf docs test build ci chore style
scope: {{ group_name }}{% if use_emoji %} (prefix summary with one matching emoji){% endif %}
scope: {% if group_name and group_name != 'root' %}({{ group_name }}){% else %}optional{% endif %}{% if use_emoji %} (prefix description with one matching emoji){% endif %}
Example:
feat(auth): add login rate limit
Prevents brute-force attacks.
Examples:
feat: add email notifications on new direct messages
feat(shopping cart): add the amazing button
feat!: remove ticket list endpoint
refers to JIRA-1337
BREAKING CHANGE: ticket endpoints no longer supports list all entities.
fix(shopping-cart): prevent order an empty shopping cart
fix(api): fix wrong calculation of request body checksum
fix: add missing parameter to service call
The error occurred due to <reasons>.
perf: decrease memory footprint for determine unique visitors by using HyperLogLog
build: update dependencies
build(release): bump version to 1.0.0
refactor: implement fibonacci number calculation as recursion
style: remove empty line
{{ diff_text }}{{ context }}
+5 -2
View File
@@ -46,7 +46,7 @@ def sanitize_ai_commit_message(raw_text: str) -> str:
# - **fix: bug fix**
# - chore(deps)!: breaking change
header_pattern = re.compile(
r"^(\*\*)?(feat|fix|chore|refactor|docs|style|test|build|ci)(\(.*\))?!?: .*",
r"^(\*\*)?(feat|fix|chore|refactor|docs|style|test|build|ci|perf|revert|deps|wip)(\(.*\))?!?: .*",
re.IGNORECASE,
)
@@ -75,6 +75,9 @@ def sanitize_ai_commit_message(raw_text: str) -> str:
# If we hit another header or a known separator, we stop
if "**Sponsor**" in line:
break
# Ignore trailing markdown code block markers
if line.strip() in ("```", "```md", "```markdown"):
continue
cleaned_lines.append(line)
if cleaned_lines:
@@ -196,7 +199,7 @@ def get_git_staged_files() -> list[str]:
try:
output = run_git_command(["git", "diff", "--name-only", "--cached"])
return [f for f in output.splitlines() if f.strip()]
except subprocess.CalledProcessError:
except (subprocess.CalledProcessError, FileNotFoundError):
return []
+9 -9
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "devgen-cli"
version = "0.2.9"
version = "0.3.0"
description = "A collection of developer tools"
readme = "README.md"
requires-python = ">=3.10"
@@ -25,15 +25,15 @@ classifiers = [
"Programming Language :: Python :: 3.10",
]
dependencies = [
"anthropic>=0.75.0",
"google-genai>=0.1.0",
"openai>=2.11.0",
"anthropic>=0.116.0",
"google-genai>=2.10.0",
"openai>=2.44.0",
"pyyaml>=6.0.3",
"questionary>=2.1.1",
"requests>=2.32.5",
"rich>=14.2.0",
"requests>=2.34.2",
"rich>=15.0.0",
"toml>=0.10.2",
"typer>=0.20.0",
"typer>=0.26.8",
]
[project.urls]
Homepage = "https://github.com/S4NKALP/devgen"
@@ -49,6 +49,6 @@ include = ["devgen*"]
[dependency-groups]
dev = [
"pre-commit>=4.5.0",
"ruff>=0.14.9",
"pre-commit>=4.6.0",
"ruff>=0.15.20",
]
+8 -8
View File
@@ -1,11 +1,11 @@
anthropic==0.76.0
google-genai==1.60.0
openai==2.15.0
pre-commit==4.5.1
anthropic==0.116.0
google-genai==2.25.0
openai==2.44.0
pre-commit==4.6.0
pyyaml==6.0.3
questionary==2.1.1
requests==2.32.5
rich==14.2.0
ruff==0.14.13
requests==2.34.2
rich==15.0.0
ruff==0.15.20
toml==0.10.2
typer==0.21.1
typer==0.26.8
+1 -1
View File
@@ -6,7 +6,7 @@ from setuptools import find_packages, setup
setup(
name="devgen-cli",
version="0.2.9",
version="0.3.0",
packages=find_packages(),
include_package_data=True,
install_requires=[
Generated
+641 -476
View File
File diff suppressed because it is too large. Load diff