devgen-cli 0.2.4__tar.gz → 0.2.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. devgen_cli-0.2.6/PKG-INFO +187 -0
  2. devgen_cli-0.2.6/README.md +150 -0
  3. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/ai.py +11 -2
  4. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/cli/commit.py +19 -1
  5. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/cli/config.py +28 -2
  6. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/cli/main.py +11 -3
  7. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/cli/setup.py +20 -1
  8. devgen_cli-0.2.6/devgen/modules/changelog_generator.py +155 -0
  9. devgen_cli-0.2.6/devgen/modules/changelog_sections.py +79 -0
  10. devgen_cli-0.2.6/devgen/modules/commit_generator.py +392 -0
  11. devgen_cli-0.2.6/devgen/modules/diff_builder.py +220 -0
  12. devgen_cli-0.2.6/devgen/modules/git_ops.py +144 -0
  13. devgen_cli-0.2.6/devgen/modules/release_note_generator.py +56 -0
  14. devgen_cli-0.2.6/devgen/providers/__init__.py +0 -0
  15. devgen_cli-0.2.6/devgen/providers/anthropic.py +19 -0
  16. devgen_cli-0.2.6/devgen/providers/base.py +90 -0
  17. devgen_cli-0.2.6/devgen/providers/gemini.py +63 -0
  18. devgen_cli-0.2.6/devgen/providers/huggingface.py +54 -0
  19. devgen_cli-0.2.6/devgen/providers/ollama.py +51 -0
  20. devgen_cli-0.2.6/devgen/providers/openai.py +20 -0
  21. devgen_cli-0.2.6/devgen/providers/openrouter.py +25 -0
  22. devgen_cli-0.2.6/devgen/templates/commit/commit_message.tpl +14 -0
  23. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/utils.py +62 -0
  24. devgen_cli-0.2.6/devgen_cli.egg-info/PKG-INFO +187 -0
  25. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen_cli.egg-info/SOURCES.txt +6 -1
  26. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen_cli.egg-info/requires.txt +1 -1
  27. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/pyproject.toml +2 -2
  28. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/setup.py +1 -1
  29. devgen_cli-0.2.4/PKG-INFO +0 -208
  30. devgen_cli-0.2.4/README.md +0 -171
  31. devgen_cli-0.2.4/devgen/modules/changelog_generator.py +0 -183
  32. devgen_cli-0.2.4/devgen/modules/commit_generator.py +0 -509
  33. devgen_cli-0.2.4/devgen/modules/release_note_generator.py +0 -66
  34. devgen_cli-0.2.4/devgen/providers/__init__.py +0 -24
  35. devgen_cli-0.2.4/devgen/providers/anthropic.py +0 -23
  36. devgen_cli-0.2.4/devgen/providers/gemini.py +0 -24
  37. devgen_cli-0.2.4/devgen/providers/huggingface.py +0 -45
  38. devgen_cli-0.2.4/devgen/providers/openai.py +0 -51
  39. devgen_cli-0.2.4/devgen/providers/openrouter.py +0 -36
  40. devgen_cli-0.2.4/devgen/templates/commit/commit_message.j2 +0 -11
  41. devgen_cli-0.2.4/devgen_cli.egg-info/PKG-INFO +0 -208
  42. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/LICENSE +0 -0
  43. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/MANIFEST.in +0 -0
  44. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/__init__.py +0 -0
  45. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/cli/__init__.py +0 -0
  46. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/cli/changelog.py +0 -0
  47. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/cli/gitignore.py +0 -0
  48. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/cli/license.py +0 -0
  49. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/cli/release.py +0 -0
  50. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/modules/__init__.py +0 -0
  51. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/modules/gitignore_generator.py +0 -0
  52. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/modules/license_generator.py +0 -0
  53. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/agpl-3.0.json +0 -0
  54. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/apache-2.0.json +0 -0
  55. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/bsd-2-clause.json +0 -0
  56. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/bsd-3-clause.json +0 -0
  57. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/bsl-1.0.json +0 -0
  58. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/cc0-1.0.json +0 -0
  59. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/epl-2.0.json +0 -0
  60. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/gpl-2.0.json +0 -0
  61. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/gpl-3.0.json +0 -0
  62. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/lgpl-2.1.json +0 -0
  63. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/mit.json +0 -0
  64. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/mpl-2.0.json +0 -0
  65. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen/templates/licenses/unlicense.json +0 -0
  66. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen_cli.egg-info/dependency_links.txt +0 -0
  67. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen_cli.egg-info/entry_points.txt +0 -0
  68. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/devgen_cli.egg-info/top_level.txt +0 -0
  69. {devgen_cli-0.2.4 → devgen_cli-0.2.6}/setup.cfg +0 -0
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.4
2
+ Name: devgen-cli
3
+ Version: 0.2.6
4
+ Summary: A collection of developer tools
5
+ Home-page: https://github.com/S4NKALP/devgen
6
+ Author: Sankalp Tharu
7
+ Author-email: Sankalp Tharu <sankalptharu50028@gmail.com>
8
+ License: GPL-3.0-or-later
9
+ Project-URL: Homepage, https://github.com/S4NKALP/devgen
10
+ Keywords: devgen,cli,git,changelog,gitignore,license,commit-generator,commit
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: anthropic>=0.75.0
24
+ Requires-Dist: google-genai>=0.1.0
25
+ Requires-Dist: jinja2>=3.1.6
26
+ Requires-Dist: openai>=2.11.0
27
+ Requires-Dist: pyyaml>=6.0.3
28
+ Requires-Dist: questionary>=2.1.1
29
+ Requires-Dist: requests>=2.32.5
30
+ Requires-Dist: rich>=14.2.0
31
+ Requires-Dist: toml>=0.10.2
32
+ Requires-Dist: typer>=0.20.0
33
+ Dynamic: author
34
+ Dynamic: home-page
35
+ Dynamic: license-file
36
+ Dynamic: requires-python
37
+
38
+ # DevGen
39
+
40
+ <div align="center">
41
+
42
+ **AI-Powered Git & Project Workflows in One CLI**
43
+
44
+ Stop wasting time on repetitive tasks. DevGen automates commits, changelogs, `.gitignore`, and license files using AI — from cloud providers or a local Ollama model.
45
+
46
+ > PyPI didn't allow the original name, so you'll find it as **devgen-cli** on PyPI
47
+
48
+ > <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>
49
+ > <img src="https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white" alt="Python">
50
+ > <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>
51
+ > <a href="https://github.com/S4NKALP/DevGen/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/S4NKALP/DevGen/ci.yml?branch=main&label=CI" alt="CI"></a>
52
+ > <a href="https://github.com/S4NKALP/DevGen/actions/workflows/pre-commit.yml"><img src="https://img.shields.io/github/actions/workflow/status/S4NKALP/DevGen/pre-commit.yml?branch=main&label=Pre-Commit" alt="Pre-Commit"></a>
53
+
54
+ </div>
55
+
56
+ ## Why DevGen
57
+
58
+ DevGen is a CLI for the parts of development that should be invisible. It writes Conventional Commits from your diff, drafts SemVer changelogs, fetches `.gitignore` and license templates, and routes everything through whichever AI provider you prefer — including a fully local Ollama model if you don't want to send code to the cloud.
59
+
60
+ ## Features
61
+
62
+ - **AI-Powered Commits** — Conventional Commit messages generated from your staged diff, grouped by directory, with optional emoji prefixes.
63
+ - **Multiple Providers** — Google Gemini, OpenAI, Anthropic, OpenRouter, HuggingFace, and local Ollama, all behind one CLI.
64
+ - **Smart Caching** — `.gitignore` and license templates are cached for offline use; AI responses are de-duplicated.
65
+ - **Conventional Changelogs** — `feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `build`, `ci`, `chore`, `style`, and a `BREAKING CHANGES` section.
66
+ - **Project Scaffolding** — Pull `.gitignore` from GitHub's collection and drop in SPDX license files (MIT, Apache-2.0, GPL-3.0, AGPL-3.0, BSD, MPL-2.0, …).
67
+ - **Custom Templates** — Override the commit-message prompt via a `.tpl` file in your config.
68
+ - **Interactive Setup** — `devgen setup` walks you through provider + API key + options.
69
+ - **Undo Support** — `devgen commit undo` rolls back the last AI commit while keeping changes staged.
70
+ - **Token-Limit Aware** — When a diff exceeds a model's context window, you get a single actionable error with `--max-groups` and `--max-diff-size` hints.
71
+
72
+ ## Supported AI Providers
73
+
74
+ | Provider | Notes |
75
+ | ----------------- | ---------------------------------------------------- |
76
+ | **Google Gemini** | Default-friendly, generous free tier |
77
+ | **OpenAI** | GPT-4o, GPT-4.1, o-series |
78
+ | **Anthropic** | Claude 3.5 / 3.7 / 4 |
79
+ | **OpenRouter** | Single key, many models |
80
+ | **HuggingFace** | Inference API |
81
+ | **Ollama** | Fully local, no API key, no data leaves your machine |
82
+
83
+ ## Installation
84
+
85
+ ```bash
86
+ # Recommended: isolated environment
87
+ pipx install devgen-cli
88
+
89
+ # Or use uv for speed
90
+ uv tool install devgen-cli
91
+
92
+ # Or plain pip
93
+ pip install devgen-cli
94
+
95
+ # Shell completion (bash/zsh/fish)
96
+ devgen --install-completion
97
+ ```
98
+
99
+ Requires **Python 3.10 or newer**.
100
+
101
+ ## Quick Start
102
+
103
+ ```bash
104
+ # 1. Configure a provider and API key
105
+ devgen setup config
106
+
107
+ # 2. Stage your work as usual
108
+ git add .
109
+
110
+ # 3. Let DevGen write the commit message
111
+ devgen commit run
112
+
113
+ # 4. Or preview first, then commit
114
+ devgen commit run --dry-run
115
+ devgen commit run --push
116
+ ```
117
+
118
+ ## Commands
119
+
120
+ | Command | Description |
121
+ | ------------------------------- | ----------------------------------------------------------------------------- |
122
+ | `devgen setup config` | Interactive provider / API key / options wizard |
123
+ | `devgen commit run` | Stage, generate message, commit (optionally `--push`, `--check`, `--dry-run`) |
124
+ | `devgen commit undo` | Undo the last AI commit, keep changes staged |
125
+ | `devgen changelog` | Generate a Conventional Commits changelog from recent history |
126
+ | `devgen release-notes` | Generate release notes for a version range |
127
+ | `devgen gitignore list` | List available GitHub `.gitignore` templates |
128
+ | `devgen gitignore add <name> …` | Add `.gitignore` entries to the current project |
129
+ | `devgen license list` | List available SPDX licenses |
130
+ | `devgen license add <spdx>` | Add a LICENSE file to the current project |
131
+
132
+ Run `devgen <command> --help` for full options on any subcommand.
133
+
134
+ ## Custom Templates
135
+
136
+ DevGen uses `.tpl` files for its commit prompt. To override the default, set `custom_template` in your config (see `devgen config info`) and point it at a `.tpl` file with `{{ diff_text }}`, `{{ context }}`, `{{ group_name }}`, and the conditional `{% if use_emoji %}` block.
137
+
138
+ Example minimal template:
139
+
140
+ ```
141
+ {{ diff_text }}
142
+ Summarize the change above in one Conventional Commit line.
143
+ ```
144
+
145
+ ## Architecture
146
+
147
+ DevGen is built around a small set of composable components:
148
+
149
+ - **`BaseProvider`** — abstract base for every AI provider; handles API-key validation, token-limit detection, and error wrapping. Adding a new provider is one subclass with a single `_generate()` method.
150
+ - **`GitOperator`** — all `subprocess` calls to `git` live here, behind a `GitError` exception.
151
+ - **`DiffBuilder` / `FileGrouper` / `ManifestInspector`** — split a staged diff into per-directory groups with compact project context, so the model sees a focused slice instead of a wall of text.
152
+ - **`Section` enum** — single source of truth for changelog ordering and emoji, shared by `ChangelogGenerator` and `ReleaseNotesGenerator`.
153
+
154
+ The CLI entry point is `devgen.cli.main:app` (Typer), exposed as the `devgen` script.
155
+
156
+ ## Development
157
+
158
+ ```bash
159
+ git clone https://github.com/S4NKALP/DevGen.git
160
+ cd DevGen
161
+ uv sync --all-extras --dev # install runtime + dev deps
162
+ uv run pre-commit run --all-files
163
+ uv run devgen --help
164
+ ```
165
+
166
+ The project uses `uv` for dependency management, `ruff` for lint + format, and `pyright` for type checking. CI runs all three on Python 3.10 through 3.13 (see `.github/workflows/ci.yml`).
167
+
168
+ ## Contributing
169
+
170
+ Issues and PRs welcome. Please run `uv run pre-commit run --all-files` before opening a PR so CI stays green.
171
+
172
+ ## License
173
+
174
+ GPL-3.0-or-later. See [LICENSE](LICENSE).
175
+
176
+ ## Acknowledgments
177
+
178
+ Built on the shoulders of:
179
+
180
+ - **Typer** & **Rich** — CLI and terminal UI
181
+ - **Questionary** — interactive prompts
182
+ - **Google Gemini**, **OpenAI**, **Anthropic**, **OpenRouter**, **HuggingFace**, and **Ollama** — AI providers
183
+ - **Ruff** — lint and format
184
+
185
+ <div align="center">
186
+ Made with ❤️ by <a href="https://github.com/S4NKALP">Sankalp</a>
187
+ </div>
@@ -0,0 +1,150 @@
1
+ # DevGen
2
+
3
+ <div align="center">
4
+
5
+ **AI-Powered Git & Project Workflows in One CLI**
6
+
7
+ Stop wasting time on repetitive tasks. DevGen automates commits, changelogs, `.gitignore`, and license files using AI — from cloud providers or a local Ollama model.
8
+
9
+ > PyPI didn't allow the original name, so you'll find it as **devgen-cli** on PyPI
10
+
11
+ > <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>
12
+ > <img src="https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white" alt="Python">
13
+ > <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>
14
+ > <a href="https://github.com/S4NKALP/DevGen/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/S4NKALP/DevGen/ci.yml?branch=main&label=CI" alt="CI"></a>
15
+ > <a href="https://github.com/S4NKALP/DevGen/actions/workflows/pre-commit.yml"><img src="https://img.shields.io/github/actions/workflow/status/S4NKALP/DevGen/pre-commit.yml?branch=main&label=Pre-Commit" alt="Pre-Commit"></a>
16
+
17
+ </div>
18
+
19
+ ## Why DevGen
20
+
21
+ DevGen is a CLI for the parts of development that should be invisible. It writes Conventional Commits from your diff, drafts SemVer changelogs, fetches `.gitignore` and license templates, and routes everything through whichever AI provider you prefer — including a fully local Ollama model if you don't want to send code to the cloud.
22
+
23
+ ## Features
24
+
25
+ - **AI-Powered Commits** — Conventional Commit messages generated from your staged diff, grouped by directory, with optional emoji prefixes.
26
+ - **Multiple Providers** — Google Gemini, OpenAI, Anthropic, OpenRouter, HuggingFace, and local Ollama, all behind one CLI.
27
+ - **Smart Caching** — `.gitignore` and license templates are cached for offline use; AI responses are de-duplicated.
28
+ - **Conventional Changelogs** — `feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `build`, `ci`, `chore`, `style`, and a `BREAKING CHANGES` section.
29
+ - **Project Scaffolding** — Pull `.gitignore` from GitHub's collection and drop in SPDX license files (MIT, Apache-2.0, GPL-3.0, AGPL-3.0, BSD, MPL-2.0, …).
30
+ - **Custom Templates** — Override the commit-message prompt via a `.tpl` file in your config.
31
+ - **Interactive Setup** — `devgen setup` walks you through provider + API key + options.
32
+ - **Undo Support** — `devgen commit undo` rolls back the last AI commit while keeping changes staged.
33
+ - **Token-Limit Aware** — When a diff exceeds a model's context window, you get a single actionable error with `--max-groups` and `--max-diff-size` hints.
34
+
35
+ ## Supported AI Providers
36
+
37
+ | Provider | Notes |
38
+ | ----------------- | ---------------------------------------------------- |
39
+ | **Google Gemini** | Default-friendly, generous free tier |
40
+ | **OpenAI** | GPT-4o, GPT-4.1, o-series |
41
+ | **Anthropic** | Claude 3.5 / 3.7 / 4 |
42
+ | **OpenRouter** | Single key, many models |
43
+ | **HuggingFace** | Inference API |
44
+ | **Ollama** | Fully local, no API key, no data leaves your machine |
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ # Recommended: isolated environment
50
+ pipx install devgen-cli
51
+
52
+ # Or use uv for speed
53
+ uv tool install devgen-cli
54
+
55
+ # Or plain pip
56
+ pip install devgen-cli
57
+
58
+ # Shell completion (bash/zsh/fish)
59
+ devgen --install-completion
60
+ ```
61
+
62
+ Requires **Python 3.10 or newer**.
63
+
64
+ ## Quick Start
65
+
66
+ ```bash
67
+ # 1. Configure a provider and API key
68
+ devgen setup config
69
+
70
+ # 2. Stage your work as usual
71
+ git add .
72
+
73
+ # 3. Let DevGen write the commit message
74
+ devgen commit run
75
+
76
+ # 4. Or preview first, then commit
77
+ devgen commit run --dry-run
78
+ devgen commit run --push
79
+ ```
80
+
81
+ ## Commands
82
+
83
+ | Command | Description |
84
+ | ------------------------------- | ----------------------------------------------------------------------------- |
85
+ | `devgen setup config` | Interactive provider / API key / options wizard |
86
+ | `devgen commit run` | Stage, generate message, commit (optionally `--push`, `--check`, `--dry-run`) |
87
+ | `devgen commit undo` | Undo the last AI commit, keep changes staged |
88
+ | `devgen changelog` | Generate a Conventional Commits changelog from recent history |
89
+ | `devgen release-notes` | Generate release notes for a version range |
90
+ | `devgen gitignore list` | List available GitHub `.gitignore` templates |
91
+ | `devgen gitignore add <name> …` | Add `.gitignore` entries to the current project |
92
+ | `devgen license list` | List available SPDX licenses |
93
+ | `devgen license add <spdx>` | Add a LICENSE file to the current project |
94
+
95
+ Run `devgen <command> --help` for full options on any subcommand.
96
+
97
+ ## Custom Templates
98
+
99
+ DevGen uses `.tpl` files for its commit prompt. To override the default, set `custom_template` in your config (see `devgen config info`) and point it at a `.tpl` file with `{{ diff_text }}`, `{{ context }}`, `{{ group_name }}`, and the conditional `{% if use_emoji %}` block.
100
+
101
+ Example minimal template:
102
+
103
+ ```
104
+ {{ diff_text }}
105
+ Summarize the change above in one Conventional Commit line.
106
+ ```
107
+
108
+ ## Architecture
109
+
110
+ DevGen is built around a small set of composable components:
111
+
112
+ - **`BaseProvider`** — abstract base for every AI provider; handles API-key validation, token-limit detection, and error wrapping. Adding a new provider is one subclass with a single `_generate()` method.
113
+ - **`GitOperator`** — all `subprocess` calls to `git` live here, behind a `GitError` exception.
114
+ - **`DiffBuilder` / `FileGrouper` / `ManifestInspector`** — split a staged diff into per-directory groups with compact project context, so the model sees a focused slice instead of a wall of text.
115
+ - **`Section` enum** — single source of truth for changelog ordering and emoji, shared by `ChangelogGenerator` and `ReleaseNotesGenerator`.
116
+
117
+ The CLI entry point is `devgen.cli.main:app` (Typer), exposed as the `devgen` script.
118
+
119
+ ## Development
120
+
121
+ ```bash
122
+ git clone https://github.com/S4NKALP/DevGen.git
123
+ cd DevGen
124
+ uv sync --all-extras --dev # install runtime + dev deps
125
+ uv run pre-commit run --all-files
126
+ uv run devgen --help
127
+ ```
128
+
129
+ The project uses `uv` for dependency management, `ruff` for lint + format, and `pyright` for type checking. CI runs all three on Python 3.10 through 3.13 (see `.github/workflows/ci.yml`).
130
+
131
+ ## Contributing
132
+
133
+ Issues and PRs welcome. Please run `uv run pre-commit run --all-files` before opening a PR so CI stays green.
134
+
135
+ ## License
136
+
137
+ GPL-3.0-or-later. See [LICENSE](LICENSE).
138
+
139
+ ## Acknowledgments
140
+
141
+ Built on the shoulders of:
142
+
143
+ - **Typer** & **Rich** — CLI and terminal UI
144
+ - **Questionary** — interactive prompts
145
+ - **Google Gemini**, **OpenAI**, **Anthropic**, **OpenRouter**, **HuggingFace**, and **Ollama** — AI providers
146
+ - **Ruff** — lint and format
147
+
148
+ <div align="center">
149
+ Made with ❤️ by <a href="https://github.com/S4NKALP">Sankalp</a>
150
+ </div>
@@ -21,8 +21,17 @@ def generate_with_ai(
21
21
  provider_module = import_module(f"devgen.providers.{provider}")
22
22
  class_name = "".join([x.capitalize() for x in provider.split("_")]) + "Provider"
23
23
  provider_class = getattr(provider_module, class_name)
24
- except (ModuleNotFoundError, AttributeError) as e:
25
- raise ImportError(f"Provider `{provider}` not found or invalid: {e}") from e
24
+ except ModuleNotFoundError as e:
25
+ raise ImportError(
26
+ f"Provider `{provider}` is not a built-in module: {e}. "
27
+ f"Available providers: gemini, openai, anthropic, huggingface, "
28
+ f"openrouter, ollama."
29
+ ) from e
30
+ except AttributeError as e:
31
+ raise ImportError(
32
+ f"Provider `{provider}` exists but does not expose a "
33
+ f"`{class_name}` class. This is a packaging bug."
34
+ ) from e
26
35
 
27
36
  provider_instance = provider_class()
28
37
  return provider_instance.generate(prompt, api_key=api_key, model=model, **kwargs)
@@ -58,12 +58,28 @@ def run_commit(
58
58
  help="Review/edit commit message before committing.",
59
59
  ),
60
60
  ] = False,
61
+ max_groups: Annotated[
62
+ int | None,
63
+ typer.Option(
64
+ "--max-groups",
65
+ help="Maximum number of commit groups. Lower this if you hit token-limit errors.",
66
+ ),
67
+ ] = None,
68
+ max_diff_size: Annotated[
69
+ int | None,
70
+ typer.Option(
71
+ "--max-diff-size",
72
+ help="Maximum diff size in chars per group (default 8000). "
73
+ "Lower this if you hit token-limit errors.",
74
+ ),
75
+ ] = None,
61
76
  ) -> None:
62
77
  log_file = get_main_log_path()
63
78
  logger = configure_logger("devgen.cli.commit", log_file, console=debug)
64
79
  logger.info(f"Log file: {log_file}")
65
80
  logger.info(
66
- f"Options: dry_run={dry_run}, push={push}, debug={debug}, force={force_rebuild}, check={check}"
81
+ f"Options: dry_run={dry_run}, push={push}, debug={debug}, force={force_rebuild}, "
82
+ f"check={check}, max_groups={max_groups}, max_diff_size={max_diff_size}"
67
83
  )
68
84
 
69
85
  run_commit_engine(
@@ -73,6 +89,8 @@ def run_commit(
73
89
  force_rebuild=force_rebuild,
74
90
  check=check,
75
91
  logger=logger,
92
+ max_groups=max_groups,
93
+ max_diff_size=max_diff_size,
76
94
  )
77
95
 
78
96
 
@@ -62,7 +62,14 @@ def edit_config(
62
62
  if key == "provider":
63
63
  value = questionary.select(
64
64
  "Select AI Provider:",
65
- choices=["gemini", "openai", "huggingface", "openrouter", "anthropic"],
65
+ choices=[
66
+ "gemini",
67
+ "openai",
68
+ "huggingface",
69
+ "openrouter",
70
+ "anthropic",
71
+ "ollama",
72
+ ],
66
73
  default=str(current_val) if current_val else "gemini",
67
74
  style=style,
68
75
  ).ask()
@@ -117,7 +124,14 @@ def set_config() -> None:
117
124
  # Questions
118
125
  provider = questionary.select(
119
126
  "Select AI Provider:",
120
- choices=["gemini", "openai", "huggingface", "openrouter", "anthropic"],
127
+ choices=[
128
+ "gemini",
129
+ "openai",
130
+ "huggingface",
131
+ "openrouter",
132
+ "anthropic",
133
+ "ollama",
134
+ ],
121
135
  default=config.get("provider", "gemini"),
122
136
  style=style,
123
137
  ).ask()
@@ -151,12 +165,24 @@ def set_config() -> None:
151
165
  raise typer.Exit(code=130)
152
166
  emoji = emoji_choice == "Yes"
153
167
 
168
+ ollama_host = config.get("ollama_host", "http://localhost:11434")
169
+ if provider == "ollama":
170
+ ollama_host_input = questionary.text(
171
+ "Ollama server URL:",
172
+ default=ollama_host,
173
+ style=style,
174
+ ).ask()
175
+ if ollama_host_input is None:
176
+ raise typer.Exit(code=130)
177
+ ollama_host = ollama_host_input.strip() or ollama_host
178
+
154
179
  # Save Config
155
180
  new_config = {
156
181
  "provider": provider,
157
182
  "model": model,
158
183
  "api_key": api_key,
159
184
  "emoji": emoji,
185
+ "ollama_host": ollama_host,
160
186
  }
161
187
 
162
188
  # Merge with existing config to preserve other keys?
@@ -48,13 +48,21 @@ def _version_callback(value: bool) -> None:
48
48
  typer.echo(f"devgen version: {version}")
49
49
  else:
50
50
  typer.secho(
51
- "Error: Version not found in pyproject.toml",
51
+ "Could not determine version: no `project.version` or "
52
+ "`tool.poetry.version` found in pyproject.toml.",
52
53
  fg=typer.colors.RED,
53
54
  err=True,
54
55
  )
55
- except (FileNotFoundError, KeyError, Exception) as e:
56
+ except FileNotFoundError:
56
57
  typer.secho(
57
- f"Error: Could not determine version: {e}",
58
+ f"pyproject.toml not found at {pyproject_path}. "
59
+ "The installation may be corrupt.",
60
+ fg=typer.colors.RED,
61
+ err=True,
62
+ )
63
+ except toml.TomlDecodeError as e:
64
+ typer.secho(
65
+ f"pyproject.toml is not valid TOML: {e}",
58
66
  fg=typer.colors.RED,
59
67
  err=True,
60
68
  )
@@ -33,7 +33,14 @@ def setup_config() -> None:
33
33
  # Questions
34
34
  provider = questionary.select(
35
35
  "Select AI Provider:",
36
- choices=["gemini", "openai", "huggingface", "openrouter", "anthropic"],
36
+ choices=[
37
+ "gemini",
38
+ "openai",
39
+ "huggingface",
40
+ "openrouter",
41
+ "anthropic",
42
+ "ollama",
43
+ ],
37
44
  default=current_config.get("provider", "gemini"),
38
45
  style=style,
39
46
  ).ask()
@@ -67,12 +74,24 @@ def setup_config() -> None:
67
74
  raise typer.Exit(code=130)
68
75
  emoji = emoji_choice == "Yes"
69
76
 
77
+ ollama_host = current_config.get("ollama_host", "http://localhost:11434")
78
+ if provider == "ollama":
79
+ ollama_host_input = questionary.text(
80
+ "Ollama server URL:",
81
+ default=ollama_host,
82
+ style=style,
83
+ ).ask()
84
+ if ollama_host_input is None:
85
+ raise typer.Exit(code=130)
86
+ ollama_host = ollama_host_input.strip() or ollama_host
87
+
70
88
  # Save Config
71
89
  new_config = {
72
90
  "provider": provider,
73
91
  "model": model,
74
92
  "api_key": api_key,
75
93
  "emoji": emoji,
94
+ "ollama_host": ollama_host,
76
95
  }
77
96
 
78
97
  try:
@@ -0,0 +1,155 @@
1
+ import re
2
+ import subprocess
3
+ from collections import defaultdict
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Dict, List, Optional
7
+
8
+ from devgen.modules.changelog_sections import (
9
+ DEFAULT_STYLES,
10
+ Section,
11
+ section_for_type,
12
+ )
13
+ from devgen.utils import configure_logger, run_git_command
14
+
15
+
16
+ class ChangelogGenerator:
17
+ """Generates a changelog from git history using Semantic Release style."""
18
+
19
+ #: Conventional commit header: ``type(scope)!: subject``
20
+ _CC_PATTERN = re.compile(r"^(\w+)(?:\(([^)]+)\))?(!?):\s+(.*)")
21
+
22
+ def __init__(self, logger=None):
23
+ self.logger = logger or configure_logger("devgen.changelog")
24
+
25
+ # ------------------------------------------------------------------ commits
26
+
27
+ def get_commits(self, from_ref: str = "", to_ref: str = "HEAD") -> List[str]:
28
+ """Fetch commit lines in the requested range."""
29
+ if from_ref:
30
+ range_spec = f"{from_ref}..{to_ref}"
31
+ cmd = self._log_cmd(range_spec)
32
+ else:
33
+ cmd = self._resolve_range(to_ref)
34
+ try:
35
+ return run_git_command(cmd).split("\n")
36
+ except subprocess.CalledProcessError as e:
37
+ self.logger.error(f"Git command failed: {e}")
38
+ raise RuntimeError(f"Git command failed: {e}") from e
39
+
40
+ def _log_cmd(self, range_spec: str) -> List[str]:
41
+ # Format: hash|author|date|subject|body
42
+ return [
43
+ "git",
44
+ "log",
45
+ "--format=%H|%an|%ad|%s|%b",
46
+ "--date=short",
47
+ range_spec,
48
+ ]
49
+
50
+ def _resolve_range(self, to_ref: str) -> List[str]:
51
+ try:
52
+ last_tag = run_git_command(["git", "describe", "--tags", "--abbrev=0"])
53
+ self.logger.info(f"Generating changelog from last tag: {last_tag}")
54
+ return self._log_cmd(f"{last_tag}..HEAD")
55
+ except (RuntimeError, subprocess.CalledProcessError):
56
+ self.logger.info("No tags found, generating for all commits.")
57
+ return ["git", "log", "--format=%H|%an|%ad|%s|%b", "--date=short"]
58
+
59
+ # ----------------------------------------------------------------- parsing
60
+
61
+ def parse_commits(self, raw_commits: List[str]) -> Dict[Section, List[Dict]]:
62
+ """Group raw commit lines into :class:`Section` buckets."""
63
+ groups: Dict[Section, List[Dict]] = defaultdict(list)
64
+ for line in raw_commits:
65
+ entry = self._parse_line(line)
66
+ if entry is None:
67
+ continue
68
+ section = section_for_type(entry["type"])
69
+ entry["section"] = section
70
+ if entry["breaking"]:
71
+ groups[Section.BREAKING].append(entry)
72
+ groups[section].append(entry)
73
+ return dict(groups)
74
+
75
+ def _parse_line(self, line: str) -> Dict | None:
76
+ if not line.strip():
77
+ return None
78
+ parts = line.split("|", 4)
79
+ if len(parts) < 4:
80
+ return None
81
+ commit_hash, author, date, subject = parts[:4]
82
+ body = parts[4] if len(parts) > 4 else ""
83
+
84
+ match = self._CC_PATTERN.match(subject)
85
+ if match:
86
+ c_type, c_scope, breaking, c_subject = match.groups()
87
+ return {
88
+ "type": c_type,
89
+ "hash": commit_hash,
90
+ "author": author,
91
+ "date": date,
92
+ "scope": c_scope,
93
+ "subject": c_subject,
94
+ "body": body,
95
+ "breaking": bool(breaking),
96
+ }
97
+ return {
98
+ "type": "",
99
+ "hash": commit_hash,
100
+ "author": author,
101
+ "date": date,
102
+ "scope": None,
103
+ "subject": subject,
104
+ "body": body,
105
+ "breaking": False,
106
+ }
107
+
108
+ # ------------------------------------------------------------------ output
109
+
110
+ def generate_markdown(
111
+ self,
112
+ groups: Dict[Section, List[Dict]],
113
+ version: str = "Unreleased",
114
+ ) -> str:
115
+ """Render groups as a markdown changelog block."""
116
+ date_str = datetime.now().strftime("%Y-%m-%d")
117
+ out = [f"## {version} ({date_str})\n"]
118
+ for section in Section.ordered():
119
+ commits = groups.get(section)
120
+ if not commits:
121
+ continue
122
+ style = DEFAULT_STYLES[section]
123
+ out.append(f"## {style.emoji} {style.heading}\n")
124
+ for c in commits:
125
+ scope = f"**{c['scope']}**: " if c.get("scope") else ""
126
+ out.append(f"- {scope}{c['subject']} ({c['hash'][:7]})")
127
+ out.append("")
128
+ return "\n".join(out)
129
+
130
+ def run(self, output_file: Optional[str] = "CHANGELOG.md", from_ref: str = ""):
131
+ """Fetch → parse → write (or print) the changelog."""
132
+ raw_commits = self.get_commits(from_ref)
133
+ if not raw_commits or not raw_commits[0]:
134
+ self.logger.warning("No commits found.")
135
+ return
136
+ groups = self.parse_commits(raw_commits)
137
+ md_content = self.generate_markdown(groups)
138
+
139
+ if not output_file:
140
+ print(md_content)
141
+ return
142
+
143
+ path = Path(output_file)
144
+ if path.exists():
145
+ old = path.read_text(encoding="utf-8")
146
+ if old.strip().startswith("# CHANGELOG"):
147
+ header, _, rest = old.partition("\n")
148
+ new_content = f"{header}\n\n{md_content}\n{rest.lstrip()}"
149
+ else:
150
+ new_content = f"# CHANGELOG\n\n{md_content}\n\n{old}"
151
+ else:
152
+ new_content = f"# CHANGELOG\n\n{md_content}"
153
+ path.write_text(new_content, encoding="utf-8")
154
+ self.logger.info(f"Changelog written to {output_file}")
155
+ print(f" Changelog updated: {output_file}")