Compare commits

...
34 Commits
Author SHA1 Message Date
sankalp cc94b53d48 chore(root): bump version to 0.2.4
- Updated version in `pyproject.toml` and `setup.py` to `0.2.4`
- Lockfile (`uv.lock`) updated to reflect dependency changes
2025-12-30 14:40:57 +05:45
sankalp b09de402cc feat(devgen): add max_groups config option 2025-12-25 09:36:05 +05:45
sankalp c121a7fa59 feat(devgen/modules): add interactive commit review and smart file grouping
- New Features:
  - Added `check` parameter to enable interactive commit message review
  - Implemented user confirmation/editing/abort workflow using questionary
  - Added smart file grouping with configurable max_groups limit
  - Files are now merged into parent directories when group count exceeds limit

- Enhancements:
  - Improved `group_files()` method with hierarchical merging logic
  - Added graceful KeyboardInterrupt handling during user review
  - Enhanced logging for group merging operations
```
2025-12-25 09:35:33 +05:45
sankalp 525aebadf4 feat(devgen/modules): enhance commit generation with manifest context and custom templates
- New Features:
  - Added `_get_manifest_context()` to extract project metadata from manifest files (pyproject.toml, package.json, etc.) for richer commit message context.
  - Added support for custom commit message templates via `custom_template` config.
  - Added emoji instruction toggle based on global `use_emoji` setting.

- Enhancements:
  - Improved error handling for git push failures with specific hints for "no upstream branch" scenarios.
  - Optimized manifest context extraction by truncating large files to avoid token bloat.
  - Refactored prompt generation to dynamically include manifest context and custom templates.
```
2025-12-25 00:04:54 +05:45
sankalp 1219c1ad81 feat(devgen/templates/commit): add context to commit message template
- New Features:
  - Added `{{ context }}` variable to the commit message template to provide additional context.
- Enhancements:
  - Removed the emoji usage line from the template to simplify the output.
```
2025-12-25 00:04:50 +05:45
sankalp cfa891dcde feat(devgen/cli): add config info command and improve undo commit message formatting 2025-12-25 00:04:48 +05:45
sankalp 286c46d2b5 feat(root): enhance commit generation with context-aware features
- Add shell completion support for bash/zsh/fish via `devgen --install-completion`
- Introduce `devgen commit undo` to revert the last AI-generated commit while preserving staged changes
- Implement context-aware commit generation by analyzing project manifests (`package.json`, `pyproject.toml`, etc.) and lock files for accurate dependency updates
- Add support for custom Jinja2 templates in `~/.devgen.yaml` with `custom_template` configuration
- Update documentation to reflect new features and provide template variable reference

This release significantly improves commit accuracy by leveraging project context and offers greater customization through templates and shell integration.
```
2025-12-25 00:04:44 +05:45
sankalp f2ff3327c3 feat(devgen): add custom template rendering function
- New Features:
  - Introduced `render_custom_template()` function to render Jinja2 templates from strings.
  - Enables dynamic template rendering without requiring file-based templates.
```
2025-12-25 00:04:40 +05:45
sankalp 37e5d94373 feat(devgen): enhance sanitize_ai_commit_message to handle bolding and labels
- Improve header detection by stripping bolding and cleaning labels like "1. Title:", "### Message:", etc.
- Stop processing lines when encountering a sponsor section
```
2025-12-24 23:49:37 +05:45
sankalp 40c9b3ca57 refactor(devgen/templates/commit): update commit message template guidelines
- Simplify and clarify the structure of commit message generation guidelines.
- Replace "Format" with "Guidelines" for better readability.
- Remove redundant section numbering.
- Streamline emoji usage instruction into a single line.
- Clarify that the output should exclude labels like "Title:" or "Format:".

**Enhancements:**
- Improved template structure for better maintainability.
- More concise and direct instructions for users.
2025-12-24 23:49:34 +05:45
sankalp 4d309c313d 1. Title: feat(root): add TODO.md for project roadmap and tasks 2025-12-24 23:44:23 +05:45
sankalp ba747c727c feat(devgen): improve sanitize_ai_commit_message function
The `sanitize_ai_commit_message` function in `devgen/utils.py` has been enhanced for better readability and maintainability. The changes include minor formatting improvements and a more consistent regex pattern definition.

**Enhancements & Refinements:**
- Improved code formatting by removing trailing whitespace and adding a comma after `re.IGNORECASE` in the regex pattern for better readability.
- Enhanced comment clarity by fixing a minor typo in the regex examples section.
```
2025-12-24 23:34:35 +05:45
sankalp 3635887a85 feat(devgen/modules): enhance changelog and license generation with improved file handling
This update introduces improvements to the changelog generator's file handling logic, including proper prepending of new entries and automatic header management. It also refines the license generator's template directory structure and cleans up minor formatting issues in the commit generator.

**New Features**
- Added intelligent changelog prepending logic that preserves existing headers and maintains proper formatting when updating files
- Implemented automatic `# CHANGELOG` header insertion for new changelog files

**Enhancements & Refinements**
- Changed changelog version headers from `#` to `##` for better markdown hierarchy
- Updated license generator to use `templates/licenses` directory instead of `prompt_templates/licenses`
- Removed unnecessary whitespace and improved code formatting in commit generator
- Enhanced changelog file writing logic to handle both new and existing files more robustly
```
2025-12-24 23:34:32 +05:45
sankalp a7507255c7 feat(root): update devgen-cli to version 0.2.3
The dependency `devgen-cli` has been upgraded from version 0.2.2 to 0.2.3 in the project's lockfile.

- **Enhancements & Refinements**
  - Updated `devgen-cli` dependency to version 0.2.3 in `uv.lock`
```
2025-12-24 23:29:47 +05:45
sankalp d373f95388 refactor(devgen): enhance AI commit message sanitization
The `sanitize_ai_commit_message` function has been significantly improved to better handle AI-generated commit messages, including support for bolded headers, more robust header detection, and a fallback mechanism for non-conventional messages.

**Enhancements & Refinements:**
- Added comprehensive docstring explaining the function's purpose and behavior
- Improved regex pattern to match conventional commit headers with optional bolding (e.g., **feat: ...**)
- Added case-insensitive matching for commit types
- Implemented a state-based approach with `found_header` flag for clearer logic
- Added automatic removal of bold markers (**) from headers
- Introduced fallback behavior to return the first non-empty line if no conventional header is found
- Improved code structure with better variable naming and comments
- Added early return for empty input strings

**Bug Fixes:**
- Fixed potential issue where the function could return empty strings for non-conventional messages
- Resolved edge case handling for messages without proper headers
```
2025-12-24 23:29:45 +05:45
sankalp fe7da77ed6 chore(root): Bump version to 0.2.3 2025-12-14 07:58:09 +05:45
sankalp 407458149b fix: remove invalid debug argument from OpenAI and OpenRouter providers
The debug argument is used internally by devgen but is not accepted by the OpenAI API client. This change filters it out from kwargs before the API call to prevent TypeError.
2025-12-14 07:57:16 +05:45
sankalp e9a45cf140 chore(root): Bump version to 0.2.2
This commit updates the project's version from 0.2.1 to 0.2.2 across the `pyproject.toml`, `setup.py`, and `uv.lock` files.
2025-12-14 00:55:19 +05:45
sankalp 70e150611b refactor(docs): Overhaul README for improved clarity and quick start
The `README.md` has undergone a significant rewrite and restructuring to enhance readability, streamline the onboarding process, and better communicate the value proposition of DevGen. This includes a new introduction, a consolidated "Why DevGen?" section, simplified quick start guides, and a dedicated feature deep dive.

*   **Enhancements & Refinements**
    *   **Improved Introduction**: Added a concise tagline and refined the introductory paragraph, positioning DevGen as an AI co-pilot for Git and project management.
    *   **Streamlined Feature Presentation**: Replaced the previous feature list with a "Why DevGen?" section, highlighting key benefits such as AI Brain (multi-provider support), battle-tested compliance (Conventional Commits, Semantic Versioning), lightning-fast performance, and zero-friction setup.
    *   **Simplified Quick Start**: Refined the initial setup and commit generation steps for a quicker onboarding experience.
    *   **Enhanced Feature Deep Dive**: Introduced a dedicated section to provide detailed usage examples for AI-powered commits, changelogs, release notes, gitignore, and license generation.
    *   **Improved Installation Instructions**: Emphasized `pipx` and `uv` as recommended installation methods for isolated and fast CLI management.
    *   **Concise Configuration Table**: Replaced verbose configuration details with a clear markdown table for easier reference.
    *   **Updated PyPI Link**: Corrected the PyPI link to `devgen-cli`.
    *   **Minor Visual Updates**: Refined badge presentation and added a centered footer with author attribution.

*   **Refactoring**
    *   **Major Content Restructuring**: Completely reordered and rewrote large sections, removing redundant information and consolidating related content to improve overall flow and conciseness.
    *   **Removed Command Reference**: Integrated command examples directly into relevant usage sections, eliminating a separate, lengthy command reference.
    *   **Removed Redundant Sections**: Consolidated information previously found in "Use Cases" and "Requirements" into other sections, streamlining the document.
2025-12-13 22:26:20 +05:45
sankalp f85da0461f feat(dry-run): Display dry run messages on console
**Enhancements & Refinements**
* Dry run commit messages are now displayed directly on the console during execution.
* Utilizes rich `Panel` and `Markdown` to provide a structured and readable console output for dry run messages.
* Offers immediate visual feedback for generated commits when performing a dry run.
2025-12-13 21:42:33 +05:45
sankalp 7c15e2183b refactor(docs): Overhaul README for improved clarity and quick start
The `README.md` has undergone a significant rewrite and restructuring to enhance readability, streamline the onboarding process, and better communicate the value proposition of DevGen. This includes a new introduction, a consolidated "Why DevGen?" section, simplified quick start guides, and a dedicated feature deep dive.

*   **Enhancements & Refinements**
    *   **Improved Introduction**: Added a concise tagline and refined the introductory paragraph, positioning DevGen as an AI co-pilot for Git and project management.
    *   **Streamlined Feature Presentation**: Replaced the previous feature list with a "Why DevGen?" section, highlighting key benefits such as AI Brain (multi-provider support), battle-tested compliance (Conventional Commits, Semantic Versioning), lightning-fast performance, and zero-friction setup.
    *   **Simplified Quick Start**: Refined the initial setup and commit generation steps for a quicker onboarding experience.
    *   **Enhanced Feature Deep Dive**: Introduced a dedicated section to provide detailed usage examples for AI-powered commits, changelogs, release notes, gitignore, and license generation.
    *   **Improved Installation Instructions**: Emphasized `pipx` and `uv` as recommended installation methods for isolated and fast CLI management.
    *   **Concise Configuration Table**: Replaced verbose configuration details with a clear markdown table for easier reference.
    *   **Updated PyPI Link**: Corrected the PyPI link to `devgen-cli`.
    *   **Minor Visual Updates**: Refined badge presentation and added a centered footer with author attribution.

*   **Refactoring**
    *   **Major Content Restructuring**: Completely reordered and rewrote large sections, removing redundant information and consolidating related content to improve overall flow and conciseness.
    *   **Removed Command Reference**: Integrated command examples directly into relevant usage sections, eliminating a separate, lengthy command reference.
    *   **Removed Redundant Sections**: Consolidated information previously found in "Use Cases" and "Requirements" into other sections, streamlining the document.
2025-12-13 21:42:33 +05:45
sankalp a2f3657269 feat(git): Introduce a robust utility for running git commands
This commit introduces a new `run_git_command` utility function within `devgen.utils` to centralize and standardize the execution of Git commands. This improves code reusability and error handling for Git-related operations.

*   **New Features**
    *   Add `run_git_command` to `devgen/utils.py` to provide a standardized, robust way to execute Git commands, capture output, and handle errors.
    *   Expose `run_git_command` in `__all__` for public use.
*   **Enhancements & Refinements**
    *   Refactor `get_git_staged_files` to utilize the new `run_git_command` utility, reducing code duplication and improving consistency.
2025-12-13 21:19:18 +05:45
sankalp c50f8a4b89 refactor(modules): Standardize git command execution
This commit refactors the way git commands are executed across the `changelog_generator` and `commit_generator` modules, leveraging a shared utility function and enhancing error handling.

*   **Enhancements & Refinements**
    *   Replaced the internal `_exec_git` method in both `ChangelogGenerator` and `CommitEngine` with the common `run_git_command` utility from `devgen.utils`.
    *   Standardized and improved error handling for git command failures by wrapping `run_git_command` calls in `try-except subprocess.CalledProcessError` blocks, providing more precise logging and raising `CommitEngineError` where applicable.
    *   Adjusted error handling for `git reset HEAD` operations to gracefully ignore potential failures, ensuring smoother execution for non-critical resets.
    *   Refined the `is_ahead_of_remote` check in `CommitEngine` to correctly handle scenarios with no upstream configured by using `check=False` for `run_git_command` and broadening error catch.
2025-12-13 21:19:18 +05:45
sankalp a488d5e4ac fix(root): Update PyPI information and correct documentation links
This commit clarifies the project's name on PyPI and ensures all external links in the README are accurate.

*   **Enhancements & Refinements**
    *   Added a note to the README clarifying the project's name on PyPI as `devgen-cli`.
    *   Refined the project description for improved readability by adjusting punctuation.
*   **Bug Fixes**
    *   Corrected the repository case in the license badge URL.
    *   Updated the PyPI project URL to `https://pypi.org/project/devgen0cli` for accuracy.
2025-12-13 21:19:18 +05:45
sankalp 48836797b9 build(packaging): Remove .egg-info build artifacts
This commit removes the entire `devgen_cli.egg-info` directory and its contents from the repository. This cleanup is part of streamlining the project's packaging and build process, as `.egg-info` directories typically contain transient metadata generated during package installation or building, and are generally not intended to be version controlled.

*   **Enhancements & Refinements**
    *   Deleted the `devgen_cli.egg-info` directory, which contained build-time metadata.
    *   Removed `PKG-INFO`, `SOURCES.txt`, `dependency_links.txt`, `entry_points.txt`, `requires.txt`, and `top_level.txt`.
    *   Improved project hygiene by no longer tracking generated packaging metadata in version control.
    *   Streamlined project build artifacts, potentially reflecting a modernization of the packaging setup.
2025-12-12 19:11:51 +05:45
sankalp 09151decad feat(root): Prepare for new devgen_cli component
*   **New Features**
    *   Add `devgen_cli.egg-info/` to `.gitignore` to exclude build artifacts for the new `devgen_cli` component from version control.
2025-12-12 19:11:43 +05:45
sankalp 24dc7f57b4 build(packaging): Remove custom dependency link declarations
This commit updates the package metadata by removing declarations of custom dependency links and applies a minor file formatting correction.

*   **Enhancements & Refinements**
    *   Removed all declarations from `devgen_cli.egg-info/dependency_links.txt`, simplifying the package's metadata and indicating that no custom dependency links are used.
    *   Corrected the file format of `devgen_cli.egg-info/SOURCES.txt` by adding a missing newline at the end of the file, improving file integrity.
2025-12-12 19:09:24 +05:45
sankalp 449a955d15 refactor(commit_generator): Improve code readability and styling
Minor code cleanup to enhance readability and adhere to styling conventions.

*   **Enhancements & Refinements**:
    *   Wrapped long lines for better readability in logger configuration, console theme definition, commit message panel printing, and dry run output.
    *   Removed an unnecessary f-string from the commit generation status message.
2025-12-12 19:07:55 +05:45
sankalp 87ed8bf878 docs(root): Update PyPI badge URL
**Enhancements & Refinements**
*   Corrected the PyPI badge URL in `README.md` from `devgen` to `devgen-cli` to reflect the accurate package name.
2025-12-12 19:04:42 +05:45
sankalp 6e4b177fe3 feat(commit-engine): Enhance console output with rich library
The commit engine now leverages the `rich` library to provide a more visually appealing and informative console experience during various stages of the commit generation and execution process.

*   **New Features**
    *   Integrate the `rich` library for enhanced and structured console output.
    *   Introduce a custom `rich` theme for consistent styling of info, warning, and danger messages.

*   **Enhancements & Refinements**
    *   Display visual feedback with `rich` status spinners during AI message generation and remote push operations.
    *   Present generated commit messages in a formatted `rich` Panel with Markdown rendering for improved readability.
    *   Provide clearer, color-coded `rich` console messages for staging files, dry run completion, overall process completion, and identifying failed groups.
    *   Propagate the `debug` flag to the logger to control console output during debugging sessions, aligning logger and console behavior.
2025-12-12 18:57:46 +05:45
sankalp 8db30f14bc fix(devgen-cli): Update devgen-cli to v0.2.1
- **Bug Fixes**
    - `devgen-cli`: Updated the internal CLI tool to version `0.2.1`, addressing minor issues or improving stability.
2025-12-12 18:56:37 +05:45
sankalp d502849daf feat(commit-engine): Enhance console output with rich library
The commit engine now leverages the `rich` library to provide a more visually appealing and informative console experience during various stages of the commit generation and execution process.

*   **New Features**
    *   Integrate the `rich` library for enhanced and structured console output.
    *   Introduce a custom `rich` theme for consistent styling of info, warning, and danger messages.

*   **Enhancements & Refinements**
    *   Display visual feedback with `rich` status spinners during AI message generation and remote push operations.
    *   Present generated commit messages in a formatted `rich` Panel with Markdown rendering for improved readability.
    *   Provide clearer, color-coded `rich` console messages for staging files, dry run completion, overall process completion, and identifying failed groups.
    *   Propagate the `debug` flag to the logger to control console output during debugging sessions, aligning logger and console behavior.
2025-12-12 18:56:26 +05:45
sankalp f3c68aaf29 ci(.github/workflows): Update GitHub Actions and Python version
This update refines the `publish.yml` workflow by bumping the versions of several GitHub Actions and adjusting the Python version used for publishing.

*   **Enhancements & Refinements**
    *   Updated `actions/checkout` from v4 to v6.
    *   Updated `actions/setup-python` from v4 to v6.
    *   Changed the target Python version for the workflow from 3.12 to 3.10.
2025-12-12 18:56:25 +05:45
sankalp 7f6b247b40 feat(cli): Introduce AI-powered developer toolkit
Release v0.2.1 of DevGen CLI, a comprehensive toolkit automating common Git and project management tasks through AI. This initial packaging defines the project's metadata and a wide array of developer-centric features.

*   **New Features**
    *   **AI-Powered Commit Messages:** Automatically generate semantic commit messages based on staged changes, adhering to Conventional Commits format.
    *   **Changelog Generation:** Create detailed changelogs from Git history, with categories for BREAKING CHANGES, Features, Bug Fixes, Documentation, and Other Changes.
    *   **Release Note Generation:** Produce structured release notes from Git history, ready for publication.
    *   **Gitignore File Generation:** Generate `.gitignore` files by selecting from GitHub's official templates, supporting interactive selection, multiple templates, and offline mode.
    *   **License File Generation:** Interactively generate various open-source license files (e.g., MIT, Apache-2.0, GPL-3.0), customizable with author name and year.
    *   **Multi-Provider AI Support:** Seamless integration with leading AI providers including Gemini, OpenAI, Anthropic, HuggingFace, and OpenRouter.

*   **Enhancements & Refinements**
    *   **Unified CLI:** Provides a single command-line interface for all development automation tools.
    *   **Interactive Configuration:** Simple setup process for AI providers, models, and API keys via `devgen setup config`, stored persistently in `~/.devgen.yaml`.
    *   **Flexible Commit Workflow:** Offers options for dry runs, direct commits, automatic pushes, and force rebuilding of AI-generated commit messages.
    *   **Commit Caching:** Includes commands to list and clear cached commit messages for improved workflow.
    *   **Detailed Command Reference:** Comprehensive documentation for all `devgen` commands and their specific options.
    *   **Project Metadata:** Updated `PKG-INFO` with project summary, homepage, author details, license, keywords, classifiers, and Python requirements.
    *   **Dependency Management:** Specifies required Python packages including `anthropic`, `google-generativeai`, `openai`, `typer`, and `rich`.
    *   **Python 3.10+ Compatibility:** Ensures support for modern Python environments.
2025-12-12 18:56:25 +05:45
18 changed files with 671 additions and 337 deletions

No files matched your search

+3 -3
View File
@@ -12,11 +12,11 @@ jobs:
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.12'
python-version: '3.10'
cache: 'pip'
- name: Install dependencies
run: |
+1
View File
@@ -47,3 +47,4 @@ logs/
devgen.egg-info/
build/
devgen_utils.egg-info/
devgen_cli.egg-info/
+109 -200
View File
@@ -2,261 +2,170 @@
<div align="center">
<img src="https://img.shields.io/pypi/v/devgen?color=blue&label=PyPI&logo=pypi&logoColor=white" alt="PyPI">
> **Your AI Powerhouse for Git & Project Management.**
> Stop wasting time on repetitive tasks. Automate your commits, changelogs, and project essentials with a single CLI.
>
> PyPI didn't allow the original name, so you'll find it as **devgen-cli** on PyPI
<a href="https://pypi.org/project/devgen-cli"><img src="https://img.shields.io/pypi/v/devgen-cli?color=blue&label=PyPI&logo=pypi&logoColor=white" alt="PyPI"></a>
<img src="https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white" alt="Python">
<a href="https://github.com/S4NKALP/devgen/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-GPL--3.0--or--later-blue.svg" alt="License"></a>
<a href="https://github.com/S4NKALP/DevGen/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-GPL--3.0--or--later-blue.svg" alt="License"></a>
</div>
**DevGen** is an AI-powered developer toolkit that automates common Git and project management tasks. Generate semantic commit messages, changelogs, release notes, `.gitignore` files, and licenses—all from a single unified CLI.
---
## ✨ Features
**DevGen** isn't just another CLI tool it's your development workflow on steroids. By leveraging state of the art AI models, DevGen turns tedious manual tasks into one click magic. From semantic commit messages to comprehensive release notes, DevGen handles the boring stuff so you can focus on building.
- 🚀 **AI-Powered Commit Messages** - Automatically generate semantic commit messages from your staged changes using AI
- 📝 **Changelog Generation** - Create changelogs from git history using conventional commits
- 🚀 **Release Notes** - Generate clean, emoji-enhanced release notes for your releases
- 🙈 **Gitignore Templates** - Generate `.gitignore` files from GitHub's official templates
- 📄 **License Generation** - Quickly generate open source licenses with interactive setup
- ⚙️ **Multi-Provider AI Support** - Works with Gemini, OpenAI, Anthropic, HuggingFace, and OpenRouter
## ⚡ Why DevGen?
**🧠 AI Brain**
Semantic, context-aware commit messages powered by Gemini, OpenAI, Claude, HuggingFace, and OpenRouter. It reads your diffs, identifies project manifests, and understands your code.
**🚀 Battle Tested**
Generates **Conventional Commits** and **Semantic Versioning** compliant changelogs that actually make sense.
**⚡ Lightning Fast**
Smart caching and async operations mean you never wait longer than necessary.
**Project Essentials**
Quickly add `.gitignore` and license files to your existing projects. Access cached templates instantly, even without internet.
**🛠️ Zero Friction**
Interactive setup gets you running in seconds.
## 📦 Installation
```bash
# Stable release from PyPI
pip install devgen-cli
Get started in seconds.
# Using pipx (recommended for isolated CLI)
```bash
# Recommended: Install via pipx for an isolated environment
pipx install devgen-cli
# Using uv (fast Python package manager)
# Or use uv for blazing speed
uv tool install devgen-cli
# Standard pip install
pip install devgen-cli
# Enable Shell Completion (bash/zsh/fish)
devgen --install-completion
```
## 🚀 Quick Start
### 1. Initial Setup
Configure your AI provider and API key:
**1. Initialize & Configure**
Tell DevGen which AI provider to use.
```bash
devgen setup config
```
This will create a configuration file at `~/.devgen.yaml` with your preferred AI provider, model, and API key.
### 2. Generate Commit Messages
Stage your changes and let DevGen generate semantic commit messages:
**2. Stage & Commit**
Stage your files and let AI write the message.
```bash
# Dry run to preview commit messages
git add .
devgen commit run
```
_Boom. Done._
## 💡 Feature Deep Dive
### 🤖 AI Powered Commits
Stop writing "fix bug" or "wip". DevGen analyzes your staged changes, groups them by component, and generates meaningful, semantic commit messages.
```bash
# Preview what DevGen will generate
devgen commit run --dry-run
# Actually commit with AI-generated messages
devgen commit run
# Commit and automatically push
# Commit and push in one go
devgen commit run --push
# Force regeneration of commit messages
devgen commit run --force-rebuild
# Review and edit AI messages before committing
devgen commit run --check
# Made a mistake? Undo the last AI commit and keep changes staged
devgen commit undo
```
**How it works:**
### 🧠 Context Awareness
DevGen isn't just looking at the code changes; it's looking at the big picture. It automatically detects and analyzes:
- **Manifests**: `package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, etc.
- **Lock Files**: `uv.lock`, `package-lock.json`, `poetry.lock`, etc.
- DevGen detects all staged and unstaged changes
- Groups files by directory
- Generates semantic commit messages for each group using AI
- Follows [Conventional Commits](https://www.conventionalcommits.org/) format
This allows the AI to understand *exactly* which library versions were updated, leading to hyper-accurate dependency commit messages.
**Additional commit commands:**
### 📝 Changelogs & Release Notes
Turn your git history into beautiful, readable documentation.
```bash
# View cached commit messages
devgen commit list-cached
# Clear the commit cache
devgen commit clear-cache
# Check what files are staged
devgen commit validate
```
### 3. Generate Changelogs
Create changelogs from your git history:
```bash
# Generate changelog from last tag to HEAD
# Generate a changelog from the last tag
devgen changelog generate
# Generate from a specific reference
devgen changelog generate --from v1.0.0
# Output to a custom file
devgen changelog generate --output CHANGES.md
# Create release notes for v2.0.0
devgen release notes --version 2.0.0
```
The changelog follows Semantic Release conventions, categorizing commits into:
### 🛡️ Essential Files
- **BREAKING CHANGES**
- **Features**
- **Bug Fixes**
- **Documentation**
- **Other Changes**
### 4. Generate Release Notes
Create release notes for your releases:
Don't waste time searching for templates. Generate the essential files for your project instantly.
```bash
# Generate release notes
devgen release notes --version 1.4.0
# Custom output file
devgen release notes --version 1.4.0 --output RELEASE.md
# From a specific reference
devgen release notes --version 1.4.0 --from v1.3.0
```
### 5. Generate .gitignore Files
Create `.gitignore` files from GitHub templates:
```bash
# Interactive mode (search and select templates)
# Interactive search for gitignore templates
devgen gitignore generate
# Specify templates directly
devgen gitignore generate Python Node Docker
# List available templates
devgen gitignore list
# Use cached templates (offline mode)
devgen gitignore generate --offline Python
```
**Options:**
- `--append` / `--overwrite` - Append to or overwrite existing `.gitignore`
- `--output` - Specify output file path (default: `.gitignore`)
- `--offline` - Use only cached templates
### 6. Generate Licenses
Generate open source licenses interactively:
```bash
# Generate a license interactively
devgen license generate
```
This will:
1. Show available licenses (MIT, Apache-2.0, GPL-3.0, etc.)
2. Prompt for author name
3. Prompt for year (defaults to current year)
4. Generate the license file
**Available licenses:**
- MIT
- Apache-2.0
- GPL-2.0, GPL-3.0
- LGPL-2.1
- AGPL-3.0
- BSD-2-Clause, BSD-3-Clause
- MPL-2.0
- EPL-2.0
- BSL-1.0
- CC0-1.0
- Unlicense
## ⚙️ Configuration
Configuration is stored in `~/.devgen.yaml`. You can edit it manually or use the interactive setup:
Your settings live in `~/.devgen.yaml`. You can tweak your AI provider, model, and preferences there.
```bash
devgen setup config
| Option | Description |
| :--------- | :----------------------------------------------------------- |
| `provider` | `gemini`, `openai`, `anthropic`, `huggingface`, `openrouter` |
| `model` | Specific model name (e.g., `gemini-2.5-flash`, `gpt-4o`) |
| `emoji` | Enable/disable gitmojis in commits (`true`/`false`) |
| `custom_template` | Custom Jinja2 template for commit messages. |
### 🎨 Custom Templates
You can define your own commit message structure in `~/.devgen.yaml`. Use `devgen config info` to see available variables like `{{ group_name }}`, `{{ diff_text }}`, and `{{ context }}`.
Example:
```yaml
custom_template: |
{{ group_name }}: {{ diff_text }}
---
Manifests: {{ context }}
```
**Configuration options:**
- `provider` - AI provider: `gemini`, `openai`, `anthropic`, `huggingface`, or `openrouter`
- `model` - Model name (e.g., `gemini-2.5-flash`, `gpt-4`, `claude-3-opus`)
- `api_key` - Your API key
- `emoji` - Enable/disable emojis in commit messages (default: `true`)
## 📋 Command Reference
### Commit Commands
```bash
devgen commit run [--dry-run] [--push] [--debug] [--force-rebuild]
devgen commit validate
devgen commit list-cached
devgen commit clear-cache
```
### Changelog Commands
```bash
devgen changelog generate [--output FILE] [--from REF]
```
### Release Commands
```bash
devgen release notes [--version VERSION] [--output FILE] [--from REF]
```
### Gitignore Commands
```bash
devgen gitignore generate [TEMPLATES...] [--output FILE] [--append/--overwrite] [--offline]
devgen gitignore list [--cached]
```
### License Commands
```bash
devgen license generate [--output FILE]
```
### Setup Commands
```bash
devgen setup config
```
## 🎯 Use Cases
- **Automated Commit Workflow**: Let AI analyze your changes and generate meaningful commit messages
- **Release Preparation**: Automatically generate changelogs and release notes from git history
- **Project Setup**: Quickly add `.gitignore` and license files to new projects
- **CI/CD Integration**: Use in scripts to automate documentation generation
## 🔧 Requirements
- Python 3.10 or higher
- Git repository (for commit/changelog/release features)
- API key for your chosen AI provider
## 📝 License
This project is licensed under the GPL-3.0-or-later License - see the [LICENSE](LICENSE) file for details.
> [!TIP]
> Use `devgen config info` to see a full list of available variables and a template tip!
## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
We love contributions! Found a bug? Want a new feature? Open an issue or submit a PR.
## 📚 Links
## � Acknowledgments
- **Homepage**: [https://github.com/S4NKALP/devgen](https://github.com/S4NKALP/devgen)
- **PyPI**: [https://pypi.org/project/devgen](https://pypi.org/project/devgen)
DevGen wouldn't be possible without these amazing open-source projects and AI providers:
## 🙏 Acknowledgments
- **[Typer](https://typer.tiangolo.com/)** & **[Rich](https://rich.readthedocs.io/)** for building the beautiful, intuitive, and responsive CLI interface.
- **[Questionary](https://github.com/tmbo/questionary)** for creating interactive, user-friendly prompts and selection menus.
- **[Jinja2](https://jinja.palletsprojects.com/)** for the powerful template engine used to generate files and messages.
- **[Google Gemini](https://deepmind.google/technologies/gemini/)**, **[OpenAI](https://openai.com/)**, **[Anthropic](https://www.anthropic.com/)**, **[HuggingFace](https://huggingface.co/)**, and **[OpenRouter](https://openrouter.ai/)** for providing the advanced AI models that power the semantic generation features.
- Uses GitHub's official [gitignore templates](https://github.com/github/gitignore)
- Follows [Conventional Commits](https://www.conventionalcommits.org/) specification
## �📝 License
Proudly open source under the [GPL-3.0-or-later](LICENSE) License.
---
<div align="center">
Made with ❤️ by <a href="https://github.com/S4NKALP">Sankalp</a>
</div>
+30
View File
@@ -0,0 +1,30 @@
# 📋 Project Roadmap & TODOs
This document outlines future improvements, features, and technical debt for `devgen`.
## 🚀 Priority: Short-Term (Reliability & DX)
- [ ] **Full Test Suite**: Implement `pytest` for all modules, especially `commit_generator` and `utils`.
- [x] **Interactive Commit Review**: Add a `--check` flag to `devgen commit run` that allows users to approve/edit the AI message before committing.
- [ ] **Project-Level Config**: Support `.devgen.yaml` in the repository root that overrides global `~/.devgen.yaml`.
- [ ] **Improved Gitignore Detection**: Auto-detect project type (Python, Node, Go) and suggest relevant `.gitignore` templates during `devgen gitignore run`.
## 🤖 AI & Intelligence
- [x] **Custom Prompts**: Allow users to define their own Jinja2 templates for commits in the config file(~/.devgen.yaml).
- [x] **Context-Aware Commits**: Send the manifest file (`package.json`, `pyproject.toml`) to the AI to provide better context for dependency changes.
- [ ] **Token Usage Dashboard**: Add a `devgen config tokens` command to estimate or track AI token consumption.
- [ ] **More Providers**: Add support for **Mistral**, **Cohere**, and **Groq** for faster/cheaper generations.
## 🛠️ Developer Experience (DX)
- [ ] **Pre-commit Hook Integration**: Provide a command to easily install `devgen` as a pre-commit hook.
- [x] **Shell Completion**: Implement `typer` shell completion for `bash`, `zsh`, and `fish`.
- [x] **Commit Undo**: `devgen commit undo` to easily revert the last AI-generated commit and unstage files.
- [x] **Multi-Group Commits**: Better handling of repositories where changes are spread across many unrelated directories.
## 🏗️ Refactoring & Technical Debt
- [ ] **Code Split**: Move CLI logic out of `devgen/cli` into separate service classes to improve testability.
- [ ] **Consistent Logging**: Standardize log output across all modules.
- [ ] **API Documentation**: Use Sphinx or MkDocs to generate documentation for the core modules.
## 🌍 Ecosystem
- [ ] **GitHub Action**: Create an official `devgen` GitHub Action for automated changelog updates on PR merge.
- [ ] **VS Code Extension**: A simple extension to trigger `devgen commit` from the sidebar.
+36 -2
View File
@@ -1,5 +1,6 @@
from typing import Annotated
import questionary
import typer
from devgen.modules.commit_generator import run_commit_engine
@@ -50,12 +51,19 @@ def run_commit(
help="Force regeneration of commit messages.",
),
] = False,
check: Annotated[
bool,
typer.Option(
"--check",
help="Review/edit commit message before committing.",
),
] = False,
) -> None:
log_file = get_main_log_path()
logger = configure_logger("devgen.cli.commit", log_file)
logger = configure_logger("devgen.cli.commit", log_file, console=debug)
logger.info(f"Log file: {log_file}")
logger.info(
f"Options: dry_run={dry_run}, push={push}, debug={debug}, force={force_rebuild}"
f"Options: dry_run={dry_run}, push={push}, debug={debug}, force={force_rebuild}, check={check}"
)
run_commit_engine(
@@ -63,6 +71,7 @@ def run_commit(
push=push,
debug=debug,
force_rebuild=force_rebuild,
check=check,
logger=logger,
)
@@ -94,3 +103,28 @@ def validate() -> None:
typer.echo(f"- {f}")
else:
typer.secho("[i] No staged files.", fg=typer.colors.RED)
@app.command("undo")
def undo_commit() -> None:
"""Undoes the last commit but keeps changes staged."""
from devgen.utils import run_git_command
try:
# Check if there's at least one commit
run_git_command(["git", "rev-parse", "HEAD"])
except Exception:
typer.secho("No commits found to undo.", fg=typer.colors.RED)
return
if questionary.confirm(
"Are you sure you want to undo the last commit? (Changes will remain staged)",
default=False,
).ask():
try:
run_git_command(["git", "reset", "--soft", "HEAD~1"])
typer.secho(
"Last commit undone. Changes are still staged.", fg=typer.colors.GREEN
)
except Exception as e:
typer.secho(f"Failed to undo commit: {e}", fg=typer.colors.RED)
+27
View File
@@ -167,3 +167,30 @@ def set_config() -> None:
_save_config(config)
typer.secho("\nConfiguration saved.", fg=typer.colors.GREEN)
typer.echo(yaml.dump(new_config, default_flow_style=False))
@app.command("info")
def config_info() -> None:
"""Show information about configuration options and templates."""
from rich.console import Console
from rich.table import Table
console = Console()
table = Table(title="Custom Template Variables", box=None)
table.add_column("Variable", style="cyan")
table.add_column("Description", style="white")
table.add_row("{{ group_name }}", "The folder name being committed (or 'root').")
table.add_row("{{ diff_text }}", "The git diff of the changes.")
table.add_row("{{ context }}", "Project context (manifest files content).")
console.print(table)
console.print(
"\n[bold]Tip:[/bold] If you hardcode emojis in your template, the AI will likely include them regardless of the 'emoji' setting.",
style="yellow",
)
console.print("\n[bold]Example Template:[/bold]")
console.print(
"custom_template: |\n [type]: [desc]\n \n Diff: {{ diff_text }}\n",
style="dim",
)
+1 -1
View File
@@ -15,7 +15,7 @@ from devgen.cli.setup import app as setup_app
app = typer.Typer(
name="devgen",
help="devgen-py: AI-Powered Git Commit & Release Automation.",
add_completion=False,
add_completion=True,
no_args_is_help=True,
rich_markup_mode="markdown",
)
+18 -25
View File
@@ -5,7 +5,7 @@ from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional
from devgen.utils import configure_logger
from devgen.utils import configure_logger, run_git_command
class ChangelogGenerator:
@@ -14,22 +14,6 @@ class ChangelogGenerator:
def __init__(self, logger=None):
self.logger = logger or configure_logger("devgen.changelog")
def _exec_git(self, args: List[str]) -> str:
"""Executes a git command."""
try:
res = subprocess.run(
args,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
check=True,
)
return res.stdout.strip()
except subprocess.CalledProcessError as e:
self.logger.error(f"Git command failed: {e}")
raise RuntimeError(f"Git command failed: {e}")
def get_commits(self, from_ref: str = "", to_ref: str = "HEAD") -> List[str]:
"""Fetches commit messages in the specified range."""
range_spec = f"{from_ref}..{to_ref}" if from_ref else to_ref
@@ -40,7 +24,7 @@ class ChangelogGenerator:
if not from_ref:
# If no start ref, try to find the last tag
try:
last_tag = self._exec_git(["git", "describe", "--tags", "--abbrev=0"])
last_tag = run_git_command(["git", "describe", "--tags", "--abbrev=0"])
cmd = [
"git",
"log",
@@ -49,11 +33,15 @@ class ChangelogGenerator:
f"{last_tag}..HEAD",
]
self.logger.info(f"Generating changelog from last tag: {last_tag}")
except RuntimeError:
except (RuntimeError, subprocess.CalledProcessError):
self.logger.info("No tags found, generating for all commits.")
cmd = ["git", "log", f"--format={fmt}", "--date=short"]
return self._exec_git(cmd).split("\n")
try:
return run_git_command(cmd).split("\n")
except subprocess.CalledProcessError as e:
self.logger.error(f"Git command failed: {e}")
raise RuntimeError(f"Git command failed: {e}")
def parse_commits(self, raw_commits: List[str]) -> Dict[str, List[Dict]]:
"""Parses raw commit strings into structured data."""
@@ -132,7 +120,7 @@ class ChangelogGenerator:
) -> str:
"""Generates markdown changelog from parsed commits."""
date_str = datetime.now().strftime("%Y-%m-%d")
md = [f"# {version} ({date_str})\n"]
md = [f"## {version} ({date_str})\n"]
# Order: Breaking, Features, Fixes, Docs, Others
order = [
@@ -175,14 +163,19 @@ class ChangelogGenerator:
if output_file:
path = Path(output_file)
# If file exists, prepend? For now, just overwrite or append logic could be complex.
# Let's implement prepend logic if file exists.
if path.exists():
old_content = path.read_text(encoding="utf-8")
new_content = old_content + "\n\n" + md_content
# Prepend the new content, assume # CHANGELOG is at the top or needs to be
if old_content.strip().startswith("# CHANGELOG"):
lines = old_content.split("\n", 1)
header = lines[0]
rest = lines[1] if len(lines) > 1 else ""
new_content = f"{header}\n\n{md_content}\n{rest.lstrip()}"
else:
new_content = f"# CHANGELOG\n\n{md_content}\n\n{old_content}"
path.write_text(new_content, encoding="utf-8")
else:
path.write_text(md_content, encoding="utf-8")
path.write_text(f"# CHANGELOG\n\n{md_content}", encoding="utf-8")
self.logger.info(f"Changelog written to {output_file}")
print(f" Changelog updated: {output_file}")
+315 -63
View File
@@ -1,3 +1,5 @@
import sys
import questionary
import subprocess
from collections import defaultdict
from datetime import datetime
@@ -11,8 +13,14 @@ from devgen.utils import (
get_commit_dry_run_path,
is_file_recent,
load_template_env,
run_git_command,
sanitize_ai_commit_message,
render_custom_template,
)
from rich.console import Console
from rich.panel import Panel
from rich.markdown import Markdown
from rich.theme import Theme
class CommitEngineError(Exception):
@@ -21,6 +29,19 @@ class CommitEngineError(Exception):
pass
# Token Optimization Constants
MAX_DIFF_SIZE = 8000 # Maximum characters for a single group diff
IGNORE_PATTERNS = [
"uv.lock",
"package-lock.json",
"pnpm-lock.yaml",
"yarn.lock",
"composer.lock",
"Gemfile.lock",
"poetry.lock",
]
class CommitEngine:
"""
Engine for generating AI-powered commit messages.
@@ -33,6 +54,7 @@ class CommitEngine:
push: bool = False,
debug: bool = False,
force_rebuild: bool = False,
check: bool = False,
provider: str = "gemini",
model: str = "gemini-2.5-flash",
logger: Any | None = None,
@@ -42,63 +64,134 @@ class CommitEngine:
self.push = push
self.debug = debug
self.force_rebuild = force_rebuild
self.check = check
self.provider = provider
self.model = model
self.logger = logger or configure_logger(
"devgen.commit", Path.home() / ".cache" / "devgen" / "commit.log"
"devgen.commit",
Path.home() / ".cache" / "devgen" / "commit.log",
console=debug,
)
self.kwargs = kwargs
self.dry_run_path = get_commit_dry_run_path()
self.template_env = load_template_env("commit")
self.console = Console(
theme=Theme(
{"info": "dim cyan", "warning": "magenta", "danger": "bold red"}
)
)
# Load config from ~/.devgen.yaml
from devgen.utils import load_config
self.config = load_config()
def _exec_git(self, args: List[str], allow_error: bool = False) -> str:
"""Executes a git command."""
def detect_changes(self) -> List[str]:
"""Detects changed, deleted, or untracked files."""
try:
res = subprocess.run(
args,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
check=not allow_error,
# Get modified, deleted and untracked files
out = run_git_command(
[
"git",
"ls-files",
"--deleted",
"--modified",
"--others",
"--exclude-standard",
]
)
return res.stdout.strip()
files = [f.strip() for f in out.split("\n") if f.strip()]
# Also get staged files (in case of a previous failed run)
staged_out = run_git_command(["git", "diff", "--name-only", "--cached"])
staged_files = [f.strip() for f in staged_out.split("\n") if f.strip()]
# Combine and deduplicate
all_files = list(set(files + staged_files))
return all_files
except subprocess.CalledProcessError as e:
msg = f"Git command failed: {' '.join(e.cmd)}\nError: {e.stderr.strip()}"
self.logger.error(msg)
raise CommitEngineError(msg) from e
def detect_changes(self) -> List[str]:
"""Detects changed, deleted, or untracked files."""
out = self._exec_git(
[
"git",
"ls-files",
"--deleted",
"--modified",
"--others",
"--exclude-standard",
]
)
return [f.strip() for f in out.split("\n") if f.strip()]
def group_files(self, files: List[str]) -> Dict[str, List[str]]:
"""Groups files by their parent directory."""
"""Groups files by their parent directory with smart merging if limit is exceeded."""
max_groups = self.config.get("max_groups", 5)
# 1. Initial grouping by immediate parent
groups = defaultdict(list)
for f in files:
parent = str(Path(f).parent)
key = "root" if parent == "." else parent
groups[key].append(f)
return groups
if len(groups) <= max_groups:
return dict(groups)
self.logger.info(
f"Too many groups ({len(groups)}). Merging based on max_groups={max_groups}"
)
# 2. Iteratively merge the deepest group into its parent until we hit the limit
while len(groups) > max_groups:
# Find the deepest path among the current group keys
# Skip 'root' as it's the top level
potential_merges = [k for k in groups.keys() if k != "root"]
if not potential_merges:
# This could happen if only 'root' is left or if max_groups is very small
break
# Deepest path is the one with the most segments
deepest = max(potential_merges, key=lambda p: len(Path(p).parts))
# Find the parent of this deepest path
parent_path = str(Path(deepest).parent)
new_key = "root" if parent_path == "." else parent_path
# Merge files into the new key
self.logger.debug(f"Merging group '{deepest}' into '{new_key}'")
groups[new_key].extend(groups.pop(deepest))
return dict(groups)
def generate_diff(self, files: List[str]) -> str:
"""Generates diff for specific files."""
return self._exec_git(["git", "--no-pager", "diff", "--staged", "--", *files])
"""Generates diff for specific files, with truncation for token optimization."""
try:
# Filter out very large metadata files that don't need full diffs
summary_info = []
files_to_diff = []
for f in files:
if any(p in f for p in IGNORE_PATTERNS):
summary_info.append(f"[METADATA UPDATED] {f}")
else:
files_to_diff.append(f)
diff = ""
if files_to_diff:
diff = run_git_command(
["git", "--no-pager", "diff", "--staged", "--", *files_to_diff]
)
full_content = "\n".join(summary_info + [diff]).strip()
# Truncate if too large
if len(full_content) > MAX_DIFF_SIZE:
self.logger.info(
f"Truncating diff from {len(full_content)} to {MAX_DIFF_SIZE} chars"
)
half = MAX_DIFF_SIZE // 2
return (
full_content[:half]
+ "\n\n... [DIFF TRUNCATED FOR TOKEN OPTIMIZATION] ...\n\n"
+ full_content[-half:]
)
return full_content
except subprocess.CalledProcessError as e:
msg = f"Git command failed: {' '.join(e.cmd)}\nError: {e.stderr.strip()}"
self.logger.error(msg)
raise CommitEngineError(msg) from e
def _init_dry_run(self):
"""Initializes the dry-run file."""
@@ -108,7 +201,15 @@ class CommitEngine:
f.write(f"# Dry Run: Commit Messages\n_Generated: {ts}_\n\n")
def _log_dry_run(self, group: str, msg: str):
"""Appends a dry-run entry."""
"""Appends a dry-run entry and prints to console."""
self.console.print(
Panel(
Markdown(msg),
title=f"Dry Run: {group}",
border_style="yellow",
expand=False,
)
)
with self.dry_run_path.open("a", encoding="utf-8") as f:
f.write(f"## Group: `{group}`\n\n```md\n{msg}\n```\n\n---\n\n")
@@ -117,18 +218,83 @@ class CommitEngine:
if not files:
return
self.logger.info(f"Staging: {files}")
self._exec_git(["git", "add", *files])
self.console.print(f"[info]Staging {len(files)} files...[/info]")
try:
run_git_command(["git", "add", *files])
except subprocess.CalledProcessError as e:
msg = f"Git command failed: {' '.join(e.cmd)}\nError: {e.stderr.strip()}"
self.logger.error(msg)
raise CommitEngineError(msg) from e
def commit_staged(self, msg: str):
"""Commits staged changes."""
self.logger.info(f"Committing:\n{msg}")
self._exec_git(["git", "commit", "-m", msg])
self.console.print(
Panel(Markdown(msg), title="Commit Message", border_style="green")
)
try:
run_git_command(["git", "commit", "-m", msg])
except subprocess.CalledProcessError as e:
msg = f"Git command failed: {' '.join(e.cmd)}\nError: {e.stderr.strip()}"
self.logger.error(msg)
raise CommitEngineError(msg) from e
def push_commits(self):
"""Pushes commits to remote."""
self.logger.info("Pushing to remote...")
self._exec_git(["git", "push"])
self.logger.info("Push successful.")
with self.console.status("[bold green]Pushing to remote...[/bold green]"):
try:
run_git_command(["git", "push"])
except subprocess.CalledProcessError as e:
msg = (
f"Git command failed: {' '.join(e.cmd)}\nError: {e.stderr.strip()}"
)
self.logger.error(msg)
# Check for "no upstream branch" specifically to give a hint?
if "no upstream branch" in msg.lower():
self.console.print(
"[warning]No upstream branch. Skipping push.[/warning]"
)
return
raise CommitEngineError(msg) from e
self.console.print("[bold green]Push successful.[/bold green]")
def _get_manifest_context(self) -> str:
"""Finds and reads manifest files to provide context."""
manifests = [
"pyproject.toml",
"package.json",
"go.mod",
"Cargo.toml",
"Gemfile",
"requirements.txt",
"uv.lock",
"package-lock.json",
"pnpm-lock.yaml",
"yarn.lock",
"composer.lock",
"Gemfile.lock",
"poetry.lock",
]
found = []
for m in manifests:
path = Path(m)
if path.exists():
try:
content = path.read_text(encoding="utf-8")
# Take only first 100 lines to avoid token bloat
lines = content.splitlines()
summary = "\n".join(lines[:100])
if len(lines) > 100:
summary += "\n... (truncated)"
found.append(f"File: {m}\n---\n{summary}\n---")
except Exception:
continue
if not found:
return ""
return "\n\n### Project Context (Manifests)\n" + "\n".join(found)
def generate_message(self, group: str, diff: str, cache: Dict[str, str]) -> str:
"""Generates a commit message using AI or cache."""
@@ -143,32 +309,60 @@ class CommitEngine:
model = self.kwargs.get("model") or self.config.get("model") or self.model
api_key = self.kwargs.get("api_key") or self.config.get("api_key")
use_emoji = self.config.get("emoji", True)
custom_template = self.config.get("custom_template")
template = self.template_env.get_template("commit_message.j2")
prompt = template.render(group_name=group, diff_text=diff, use_emoji=use_emoji)
manifest_context = self._get_manifest_context()
if manifest_context:
self.logger.info("Including manifest context in prompt")
raw = generate_with_ai(
prompt,
provider=provider,
model=model,
api_key=api_key,
debug=self.debug,
**self.kwargs,
)
if custom_template:
self.logger.info("Using custom template from config")
prompt = render_custom_template(
custom_template,
group_name=group,
diff_text=diff,
use_emoji=use_emoji,
context=manifest_context,
)
else:
template = self.template_env.get_template("commit_message.j2")
prompt = template.render(
group_name=group,
diff_text=diff,
use_emoji=use_emoji,
context=manifest_context,
)
# Automatically append emoji instruction based on global setting
emoji_instr = "Use emojis (🚀, 🐛)." if use_emoji else "No emojis."
prompt = f"{prompt.strip()}\n\n- {emoji_instr}"
with self.console.status("[bold blue]Generating commit message...[/bold blue]"):
raw = generate_with_ai(
prompt,
provider=provider,
model=model,
api_key=api_key,
debug=self.debug,
**self.kwargs,
)
return sanitize_ai_commit_message(raw)
def is_ahead_of_remote(self) -> bool:
"""Checks if local branch has unpushed commits."""
try:
self._exec_git(["git", "fetch", "origin"])
count = self._exec_git(
["git", "rev-list", "--count", "@{u}..HEAD"], allow_error=True
run_git_command(["git", "fetch", "origin"])
count = run_git_command(
["git", "rev-list", "--count", "@{u}..HEAD"], check=False
)
if count and int(count) > 0:
return True
except CommitEngineError:
except (subprocess.CalledProcessError, CommitEngineError):
# Maybe no upstream
return bool(self._exec_git(["git", "rev-parse", "HEAD"], allow_error=True))
try:
return bool(run_git_command(["git", "rev-parse", "HEAD"], check=False))
except subprocess.CalledProcessError:
return False
return False
def load_cache(self) -> Dict[str, str]:
@@ -190,21 +384,73 @@ class CommitEngine:
if not diff.strip():
self.logger.info(f"Skipping empty diff for {group}")
self._exec_git(["git", "reset", "HEAD", "--", *files])
try:
run_git_command(["git", "reset", "HEAD", "--", *files])
except subprocess.CalledProcessError:
pass # Ignore reset errors
return True
msg = self.generate_message(group, diff, cache)
if not msg:
self.logger.error(f"Empty message for {group}")
try:
if not msg:
self.logger.error(f"Empty message for {group}")
self._reset_group(files)
return False
if self.dry_run:
self._log_dry_run(group, msg)
self._reset_group(files)
else:
if self.check:
self.console.print(
Panel(
Markdown(msg),
title=f"Proposed Commit Message [group: {group}]",
border_style="cyan",
)
)
choice = questionary.select(
"How would you like to proceed?",
choices=[
"Confirm",
"Edit",
"Abort",
],
default="Confirm",
).ask()
if not choice or choice == "Abort":
self.logger.info(f"Commit aborted by user at group {group}")
self._reset_group(files)
raise KeyboardInterrupt("User aborted")
if choice == "Edit":
msg = questionary.text(
"Edit commit message:",
multiline=True,
default=msg,
).ask()
if not msg:
self.logger.info(
f"Empty edit, commit cancelled for {group}"
)
self._reset_group(files)
return True
self.commit_staged(msg)
return True
except Exception as e:
self.logger.error(f"Failed to process group {group}: {e}")
self._reset_group(files)
return False
if self.dry_run:
self._log_dry_run(group, msg)
self._exec_git(["git", "reset", "HEAD", "--", *files])
else:
self.commit_staged(msg)
return True
def _reset_group(self, files: List[str]):
"""Unstages files for a group."""
try:
run_git_command(["git", "reset", "HEAD", "--", *files])
except subprocess.CalledProcessError:
pass # Ignore reset errors
def execute(self):
"""Main execution method."""
@@ -237,19 +483,25 @@ class CommitEngine:
self.logger.error("Push aborted due to failed commits.")
if self.dry_run:
self.logger.info(f"Dry run done. See {self.dry_run_path}")
self.console.print(
f"[bold green]Dry run done.[/bold green] See {self.dry_run_path}"
)
else:
self.logger.info("Done.")
self.console.print("[bold green]Done.[/bold green]")
if failed:
self.logger.warning(f"Failed groups: {failed}")
self.console.print(f"[bold red]Failed groups: {failed}[/bold red]")
def run_commit_engine(**kwargs):
"""Entry point for the commit engine."""
logger = configure_logger("devgen.commit")
debug = kwargs.get("debug", False)
logger = configure_logger("devgen.commit", console=debug)
try:
engine = CommitEngine(**kwargs)
engine.execute()
except KeyboardInterrupt:
logger.warning("Interrupted by user. Exiting...")
sys.exit(0)
except Exception as e:
logger.error(f"Commit engine failed: {e}", exc_info=True)
+1 -3
View File
@@ -8,9 +8,7 @@ class LicenseGenerator:
"""Generates license files from templates."""
def __init__(self):
self.templates_dir = (
Path(__file__).parent.parent / "prompt_templates" / "licenses"
)
self.templates_dir = Path(__file__).parent.parent / "templates" / "licenses"
def list_licenses(self) -> List[Dict[str, str]]:
"""Lists available license templates."""
+10 -7
View File
@@ -1,21 +1,24 @@
from devgen.providers.anthropic import AnthropicProvider
from devgen.providers.gemini import GeminiProvider
from devgen.providers.huggingface import HuggingfaceProvider
from devgen.providers.openai import OpenaiProvider
from devgen.providers.openrouter import OpenrouterProvider
def get_provider(name):
name_lower = name.lower()
if name_lower == "gemini":
from devgen.providers.gemini import GeminiProvider
return GeminiProvider()
elif name_lower == "openai":
from devgen.providers.openai import OpenaiProvider
return OpenaiProvider()
elif name_lower == "huggingface":
from devgen.providers.huggingface import HuggingfaceProvider
return HuggingfaceProvider()
elif name_lower == "openrouter":
from devgen.providers.openrouter import OpenrouterProvider
return OpenrouterProvider()
elif name_lower == "anthropic":
from devgen.providers.anthropic import AnthropicProvider
return AnthropicProvider()
raise NotImplementedError(f"Provider '{name}' is not implemented yet.")
+3
View File
@@ -39,6 +39,9 @@ class OpenaiProvider:
# Add error handling if the client fails to initialize
raise RuntimeError(f"Failed to initialize OpenAI client: {e}")
# Remove debug from kwargs if present
kwargs.pop("debug", None)
# 2. Use the modern API syntax: client.chat.completions.create
response = client.chat.completions.create(
model=model or self.DEFAULT_MODEL,
+3
View File
@@ -19,6 +19,9 @@ class OpenrouterProvider:
api_key=api_key,
)
# Remove debug from kwargs if present
kwargs.pop("debug", None)
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
+8 -12
View File
@@ -1,15 +1,11 @@
Analyze the following git diff for `{{ group_name }}` and generate a semantic commit message.
Analyze the following git diff for the directory `{{ group_name }}` and generate a detailed semantic release commit message4. Format the output as:
1. A summary title in semantic release format (type(scope): summary) — scope can be the directory or 'root'.
2. A short summary paragraph if needed.
3. Bullet points listing the main changes, grouped as 'New Features', 'Enhancements & Refinements', and 'Bug Fixes' if relevant. Use bold for feature/section names.
{% if use_emoji %}
4. Use appropriate emojis for the commit type and sections (e.g., 🚀 for features, 🐛 for bugs).
{% else %}
4. Do NOT use any emojis in the commit message.
{% endif %}
Output only the commit message, don't include explanations or extra text.
Here is the git diff for `{{ group_name }}`:
Guidelines:
- Title line: `<type>(<scope>): <summary>` (scope is `{{ group_name }}` or `root`).
- Optional body: A short summary of why.
- Details: Bullet points for `New Features`, `Enhancements`, `Bug Fixes`.
- Output ONLY the message itself, without any labels like "Title:" or "Format:".
Diff for `{{ group_name }}`:
{{ diff_text }}
{{ context }}
+103 -18
View File
@@ -31,25 +31,63 @@ def is_file_recent(file_path: Path | str, max_age_minutes: int = 120) -> bool:
def sanitize_ai_commit_message(raw_text: str) -> str:
"""
Cleans up the AI-generated commit message.
It looks for a conventional commit header and extracts everything from there.
It handles bolded headers and common AI prefixes.
"""
if not raw_text:
return ""
lines = raw_text.strip().split("\n")
cleaned_lines = []
in_block = False
# Regex for conventional commit header
# Regex for conventional commit header (including optional bolding)
# Examples:
# - feat(root): summary
# - **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)(\(.*\))?!?: .*",
re.IGNORECASE,
)
cleaned_lines = []
found_header = False
for line in lines:
stripped = line.strip()
if in_block:
if header_pattern.match(stripped) or "**Sponsor**" in line:
if not found_header:
# First, strip bolding if it wraps the whole line or parts of it
# This is simpler than handling it in the regex
candidate = stripped.replace("**", "").strip()
# Clean the line by removing labels like "1. Title:", "### Message:", etc.
clean_line = re.sub(
r"^(#+\s+)?(\d+\.?|\*|-)?\s*(Title|Commit Message|Message):\s*",
"",
candidate,
flags=re.IGNORECASE,
).strip()
if header_pattern.match(clean_line):
found_header = True
cleaned_lines.append(clean_line)
else:
# If we hit another header or a known separator, we stop
if "**Sponsor**" in line:
break
cleaned_lines.append(line)
elif header_pattern.match(stripped):
in_block = True
cleaned_lines.append(line)
return "\n".join(cleaned_lines).strip() if cleaned_lines else ""
if cleaned_lines:
return "\n".join(cleaned_lines).strip()
# Fallback: if no conventional commit header found, just take the first non-empty line
# to avoid failing completely, but log a warning if possible.
for line in lines:
if line.strip():
return line.strip()
return ""
def parse_markdown_sections(
@@ -75,7 +113,7 @@ def extract_commit_messages(filepath: Path | str) -> dict[str, str]:
def configure_logger(
name: str = "devgen", log_file: Optional[Path | str] = None
name: str = "devgen", log_file: Optional[Path | str] = None, console: bool = True
) -> logging.Logger:
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
@@ -88,9 +126,10 @@ def configure_logger(
)
# Console handler
ch = logging.StreamHandler()
ch.setFormatter(formatter)
logger.addHandler(ch)
if console:
ch = logging.StreamHandler()
ch.setFormatter(formatter)
logger.addHandler(ch)
# File handler
if log_file:
@@ -103,15 +142,52 @@ def configure_logger(
return logger
def get_git_staged_files() -> list[str]:
def run_git_command(
args: list[str],
check: bool = True,
cwd: Optional[Path] = None,
encoding: str = "utf-8",
errors: str = "replace",
) -> str:
"""
Executes a git command and returns the output.
Args:
args: List of command arguments (e.g., ["git", "status"]).
check: Whether to raise an exception on non-zero exit code.
cwd: Current working directory for the command.
encoding: Output encoding.
errors: Error handling strategy for encoding.
Returns:
The standard output of the command, stripped of leading/trailing whitespace.
Raises:
subprocess.CalledProcessError: If the command fails and check is True.
"""
try:
res = subprocess.run(
["git", "diff", "--name-only", "--cached"],
args,
capture_output=True,
text=True,
check=True,
encoding=encoding,
errors=errors,
check=check,
cwd=cwd,
)
return [f for f in res.stdout.splitlines() if f.strip()]
return res.stdout.strip()
except subprocess.CalledProcessError as e:
# Log the error if a logger is configured, but re-raise
# We don't have access to a specific logger here easily without passing it in,
# so we rely on the caller to handle logging if needed, or we could log to a default one.
# For now, just re-raise as the caller expects.
raise e
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:
return []
@@ -136,6 +212,13 @@ def load_template_env(sub_dir: str) -> Environment:
return Environment(loader=FileSystemLoader(template_dir))
def render_custom_template(template_str: str, **context) -> str:
"""Renders a Jinja2 template from a string."""
env = Environment()
template = env.from_string(template_str)
return template.render(**context)
def load_config() -> Dict[str, Any]:
config_path = Path.home() / ".devgen.yaml"
@@ -145,6 +228,7 @@ def load_config() -> Dict[str, Any]:
"model": "gemini-2.5-flash",
"api_key": "",
"emoji": True,
"max_groups": 5,
}
try:
with config_path.open("w", encoding="utf-8") as f:
@@ -170,6 +254,7 @@ __all__ = [
"sanitize_ai_commit_message",
"extract_commit_messages",
"configure_logger",
"run_git_command",
"get_git_staged_files",
"read_file_content",
"delete_file",
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "devgen-cli"
version = "0.2.1"
version = "0.2.4"
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.1",
version="0.2.4",
packages=find_packages(),
include_package_data=True,
install_requires=[
Generated
+1 -1
View File
@@ -189,7 +189,7 @@ wheels = [
[[package]]
name = "devgen-cli"
version = "0.2.0"
version = "0.2.4"
source = { editable = "." }
dependencies = [
{ name = "anthropic" },