devgen-cli 0.2.8__py3-none-any.whl → 0.2.9__py3-none-any.whl

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.
devgen/cli/commit.py CHANGED
@@ -120,7 +120,7 @@ def validate() -> None:
120
120
  for f in staged:
121
121
  typer.echo(f"- {f}")
122
122
  else:
123
- typer.secho("[i] No staged files.", fg=typer.colors.RED)
123
+ typer.secho("[i] No staged files.", fg=typer.colors.YELLOW)
124
124
 
125
125
 
126
126
  @app.command("undo")
devgen/cli/config.py CHANGED
@@ -48,6 +48,12 @@ def edit_config(
48
48
 
49
49
  if not key:
50
50
  choices = list(config.keys())
51
+ if not choices:
52
+ typer.secho(
53
+ "No configuration keys found. Run `devgen setup config` first.",
54
+ fg=typer.colors.YELLOW,
55
+ )
56
+ raise typer.Exit()
51
57
  key = questionary.select(
52
58
  "Select key to update:", choices=choices, style=style
53
59
  ).ask()
devgen/cli/setup.py CHANGED
@@ -86,13 +86,16 @@ def setup_config() -> None:
86
86
  ollama_host = ollama_host_input.strip() or ollama_host
87
87
 
88
88
  # Save Config — merge with existing to preserve extra keys
89
- new_config = {**current_config, **{
90
- "provider": provider,
91
- "model": model,
92
- "api_key": api_key,
93
- "emoji": emoji,
94
- "ollama_host": ollama_host,
95
- }}
89
+ new_config = {
90
+ **current_config,
91
+ **{
92
+ "provider": provider,
93
+ "model": model,
94
+ "api_key": api_key,
95
+ "emoji": emoji,
96
+ "ollama_host": ollama_host,
97
+ },
98
+ }
96
99
 
97
100
  try:
98
101
  with config_path.open("w", encoding="utf-8") as f:
@@ -32,17 +32,21 @@ class ChangelogGenerator:
32
32
  else:
33
33
  cmd = self._resolve_range(to_ref)
34
34
  try:
35
- return run_git_command(cmd).split("\n")
35
+ output = run_git_command(cmd)
36
+ # Use %x00 as separator to avoid splitting on newlines in commit bodies
37
+ # Then filter out empty strings
38
+ return [c for c in output.split("\0") if c.strip()]
36
39
  except subprocess.CalledProcessError as e:
37
40
  self.logger.error(f"Git command failed: {e}")
38
41
  raise RuntimeError(f"Git command failed: {e}") from e
39
42
 
40
43
  def _log_cmd(self, range_spec: str) -> List[str]:
41
- # Format: hash|author|date|subject|body
44
+ # Format: hash|author|date|subject|body (separated by %x00 to avoid
45
+ # splitting on newlines within commit bodies)
42
46
  return [
43
47
  "git",
44
48
  "log",
45
- "--format=%H|%an|%ad|%s|%b",
49
+ "--format=%H|%an|%ad|%s|%b%x00",
46
50
  "--date=short",
47
51
  range_spec,
48
52
  ]
@@ -54,7 +58,7 @@ class ChangelogGenerator:
54
58
  return self._log_cmd(f"{last_tag}..HEAD")
55
59
  except (RuntimeError, subprocess.CalledProcessError):
56
60
  self.logger.info("No tags found, generating for all commits.")
57
- return ["git", "log", "--format=%H|%an|%ad|%s|%b", "--date=short"]
61
+ return ["git", "log", "--format=%H|%an|%ad|%s|%b%x00", "--date=short"]
58
62
 
59
63
  # ----------------------------------------------------------------- parsing
60
64
 
@@ -33,8 +33,8 @@ from devgen.utils import (
33
33
  extract_commit_messages,
34
34
  get_commit_dry_run_path,
35
35
  is_file_recent,
36
- load_template_env,
37
- render_custom_template,
36
+ load_template,
37
+ render_template,
38
38
  sanitize_ai_commit_message,
39
39
  )
40
40
 
@@ -74,13 +74,13 @@ class CommitMessageBuilder:
74
74
 
75
75
  def __init__(
76
76
  self,
77
- template_env,
77
+ template_str: str,
78
78
  config: Dict[str, Any],
79
79
  provider: str,
80
80
  model: str,
81
81
  debug: bool = False,
82
82
  ) -> None:
83
- self.template_env = template_env
83
+ self.template_str = template_str
84
84
  self.config = config
85
85
  self.provider = provider
86
86
  self.model = model
@@ -97,25 +97,14 @@ class CommitMessageBuilder:
97
97
  use_emoji = self.config.get("emoji", True)
98
98
  ollama_host = self.config.get("ollama_host")
99
99
 
100
- if custom_template:
101
- prompt = render_custom_template(
102
- custom_template,
103
- group_name=group,
104
- diff_text=diff,
105
- use_emoji=use_emoji,
106
- context=manifest_context,
107
- )
108
- else:
109
- prompt = (
110
- self.template_env.get_template("commit_message.tpl")
111
- .render(
112
- group_name=group,
113
- diff_text=diff,
114
- use_emoji=use_emoji,
115
- context=manifest_context,
116
- )
117
- .strip()
118
- )
100
+ template = custom_template or self.template_str
101
+ prompt = render_template(
102
+ template,
103
+ group_name=group,
104
+ diff_text=diff,
105
+ use_emoji=use_emoji,
106
+ context=manifest_context,
107
+ ).strip()
119
108
 
120
109
  provider = (
121
110
  extra_kwargs.get("provider") or self.config.get("provider") or self.provider
@@ -198,10 +187,10 @@ class CommitEngine:
198
187
  ),
199
188
  logger=self.logger,
200
189
  )
201
- self.template_env = load_template_env("commit")
190
+ self.template_str = load_template("commit", "commit_message.tpl")
202
191
  self.cache = CacheManager(get_commit_dry_run_path())
203
192
  self.message_builder = CommitMessageBuilder(
204
- self.template_env,
193
+ self.template_str,
205
194
  self.config,
206
195
  provider=self.provider,
207
196
  model=self.model,
@@ -388,6 +377,7 @@ def run_commit_engine(**kwargs: Any) -> None:
388
377
  sys.exit(0)
389
378
  except Exception as e:
390
379
  logger.error(f"Commit engine failed: {e}", exc_info=True)
380
+ sys.exit(1)
391
381
 
392
382
 
393
383
  __all__ = ["CommitEngine", "CommitEngineError", "run_commit_engine"]
@@ -14,7 +14,7 @@ from collections import defaultdict
14
14
  from pathlib import Path
15
15
  from typing import Dict, List, Optional
16
16
 
17
- from devgen.utils import run_git_command
17
+ from devgen.utils import get_git_root, run_git_command
18
18
 
19
19
 
20
20
  # Lock files and other metadata blobs whose full diff would just bloat the
@@ -54,7 +54,7 @@ class DiffBuilder:
54
54
  logger=None,
55
55
  ) -> None:
56
56
  self.max_size = max_size
57
- self.cwd = cwd
57
+ self.cwd = cwd or get_git_root()
58
58
  self.logger = logger
59
59
 
60
60
  def build(self, files: List[str]) -> str:
@@ -129,10 +129,7 @@ class FileGrouper:
129
129
  parent_path = str(Path(deepest).parent)
130
130
  new_key = "root" if parent_path == "." else parent_path
131
131
  files = groups.pop(deepest)
132
- if new_key in groups:
133
- groups[new_key].extend(files)
134
- else:
135
- groups["root"].extend(files)
132
+ groups.setdefault(new_key, []).extend(files)
136
133
  return groups
137
134
 
138
135
 
devgen/modules/git_ops.py CHANGED
@@ -10,7 +10,7 @@ import subprocess
10
10
  from pathlib import Path
11
11
  from typing import Optional
12
12
 
13
- from devgen.utils import configure_logger, run_git_command
13
+ from devgen.utils import configure_logger, get_git_root, run_git_command
14
14
 
15
15
 
16
16
  class GitError(RuntimeError):
@@ -26,7 +26,7 @@ class GitOperator:
26
26
  logger=None,
27
27
  quiet_commands: bool = True,
28
28
  ) -> None:
29
- self.cwd = cwd
29
+ self.cwd = cwd or get_git_root()
30
30
  self.logger = logger or configure_logger("devgen.git")
31
31
  self.quiet_commands = quiet_commands
32
32
 
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from pathlib import Path
2
3
  from typing import List
3
4
 
@@ -5,6 +6,9 @@ import requests
5
6
 
6
7
  from devgen.utils import configure_logger
7
8
 
9
+ # Only allow safe characters in template names to prevent path traversal
10
+ _TEMPLATE_NAME_RE = re.compile(r"^[a-zA-Z0-9._-]+$")
11
+
8
12
 
9
13
  class GitignoreGenerator:
10
14
  """Fetches and manages .gitignore templates from GitHub."""
@@ -41,6 +45,11 @@ class GitignoreGenerator:
41
45
  self, name: str, use_cache: bool = True, offline: bool = False
42
46
  ) -> str:
43
47
  """Fetches template content from cache or GitHub."""
48
+ if not _TEMPLATE_NAME_RE.match(name):
49
+ raise ValueError(
50
+ f"Invalid template name '{name}'. "
51
+ "Names must contain only alphanumeric characters, hyphens, dots, or underscores."
52
+ )
44
53
  cache_file = self.cache_dir / f"{name}.gitignore"
45
54
 
46
55
  if offline:
@@ -16,4 +16,6 @@ class AnthropicProvider(BaseProvider):
16
16
  max_tokens=1024,
17
17
  messages=[{"role": "user", "content": prompt}],
18
18
  )
19
+ if not message.content:
20
+ raise RuntimeError(f"Anthropic returned no content for model {model!r}.")
19
21
  return message.content[0].text or ""
@@ -20,7 +20,9 @@ class HuggingfaceProvider(BaseProvider):
20
20
  }
21
21
 
22
22
  try:
23
- response = requests.post(api_url, headers=headers, json=payload)
23
+ response = requests.post(
24
+ api_url, headers=headers, json=payload, timeout=120
25
+ )
24
26
  response.raise_for_status()
25
27
  result = response.json()
26
28
  except requests.HTTPError as e:
@@ -18,18 +18,22 @@ class OllamaProvider(BaseProvider):
18
18
  DEFAULT_HOST = "http://localhost:11434"
19
19
  DEFAULT_MODEL = "llama3.2"
20
20
 
21
+ # Keys consumed by devgen itself — never forwarded to the SDK.
22
+ _DEVGEN_KEYS = frozenset({"debug", "ollama_host", "max_retries", "retry_delay"})
23
+
21
24
  def _generate(self, prompt, api_key, model, **kwargs):
22
25
  host = kwargs.get("ollama_host") or self.DEFAULT_HOST
23
26
  base_url = f"{host.rstrip('/')}/v1"
24
- kwargs.pop("debug", None)
25
- kwargs.pop("ollama_host", None)
26
-
27
- client = OpenAI(base_url=base_url, api_key=api_key or "ollama")
28
- response = client.chat.completions.create(
29
- model=model,
30
- messages=[{"role": "user", "content": prompt}],
31
- **kwargs,
32
- )
27
+
28
+ # Only forward known OpenAI-compatible parameters
29
+ safe_kwargs = {k: v for k, v in kwargs.items() if k not in self._DEVGEN_KEYS}
30
+
31
+ with OpenAI(base_url=base_url, api_key=api_key or "ollama") as client:
32
+ response = client.chat.completions.create(
33
+ model=model,
34
+ messages=[{"role": "user", "content": prompt}],
35
+ **safe_kwargs,
36
+ )
33
37
 
34
38
  if not response.choices:
35
39
  raise RuntimeError(
@@ -9,12 +9,17 @@ class OpenaiProvider(BaseProvider):
9
9
  DISPLAY_NAME = "OpenAI"
10
10
  DEFAULT_MODEL = "gpt-4o"
11
11
 
12
+ # Keys consumed by devgen itself — never forwarded to the SDK.
13
+ _DEVGEN_KEYS = frozenset({"debug", "ollama_host", "max_retries", "retry_delay"})
14
+
12
15
  def _generate(self, prompt, api_key, model, **kwargs):
13
- kwargs.pop("debug", None)
14
- client = OpenAI(api_key=api_key)
15
- response = client.chat.completions.create(
16
- model=model,
17
- messages=[{"role": "user", "content": prompt}],
18
- **kwargs,
19
- )
16
+ safe_kwargs = {k: v for k, v in kwargs.items() if k not in self._DEVGEN_KEYS}
17
+ with OpenAI(api_key=api_key) as client:
18
+ response = client.chat.completions.create(
19
+ model=model,
20
+ messages=[{"role": "user", "content": prompt}],
21
+ **safe_kwargs,
22
+ )
23
+ if not response.choices:
24
+ raise RuntimeError(f"OpenAI returned no choices for model {model!r}.")
20
25
  return response.choices[0].message.content or ""
@@ -10,16 +10,21 @@ class OpenrouterProvider(BaseProvider):
10
10
  BASE_URL = "https://openrouter.ai/api/v1"
11
11
  DEFAULT_MODEL = "openai/gpt-3.5-turbo"
12
12
 
13
+ # Keys consumed by devgen itself — never forwarded to the SDK.
14
+ _DEVGEN_KEYS = frozenset({"debug", "ollama_host", "max_retries", "retry_delay"})
15
+
13
16
  def _generate(self, prompt, api_key, model, **kwargs):
14
- kwargs.pop("debug", None)
15
- client = OpenAI(base_url=self.BASE_URL, api_key=api_key)
16
- response = client.chat.completions.create(
17
- model=model,
18
- messages=[{"role": "user", "content": prompt}],
19
- extra_headers={
20
- "HTTP-Referer": "https://github.com/S4NKALP/devgen",
21
- "X-Title": "devgen CLI",
22
- },
23
- **kwargs,
24
- )
25
- return response.choices[0].message.content
17
+ safe_kwargs = {k: v for k, v in kwargs.items() if k not in self._DEVGEN_KEYS}
18
+ with OpenAI(base_url=self.BASE_URL, api_key=api_key) as client:
19
+ response = client.chat.completions.create(
20
+ model=model,
21
+ messages=[{"role": "user", "content": prompt}],
22
+ extra_headers={
23
+ "HTTP-Referer": "https://github.com/S4NKALP/devgen",
24
+ "X-Title": "devgen CLI",
25
+ },
26
+ **safe_kwargs,
27
+ )
28
+ if not response.choices:
29
+ raise RuntimeError(f"OpenRouter returned no choices for model {model!r}.")
30
+ return response.choices[0].message.content or ""
devgen/utils.py CHANGED
@@ -6,7 +6,6 @@ from pathlib import Path
6
6
  from typing import Any, Dict, Optional
7
7
 
8
8
  import yaml
9
- from jinja2 import Environment, FileSystemLoader
10
9
 
11
10
 
12
11
  def ensure_log_directory() -> Path:
@@ -24,10 +23,10 @@ def get_commit_dry_run_path() -> Path:
24
23
 
25
24
 
26
25
  def is_file_recent(file_path: Path | str, max_age_minutes: int = 120) -> bool:
27
- path = Path(file_path)
28
- if not path.exists():
26
+ try:
27
+ return (time.time() - Path(file_path).stat().st_mtime) <= max_age_minutes * 60
28
+ except FileNotFoundError:
29
29
  return False
30
- return (time.time() - path.stat().st_mtime) <= max_age_minutes * 60
31
30
 
32
31
 
33
32
  def sanitize_ai_commit_message(raw_text: str) -> str:
@@ -142,6 +141,15 @@ def configure_logger(
142
141
  return logger
143
142
 
144
143
 
144
+ def get_git_root(cwd: Optional[Path] = None) -> Optional[Path]:
145
+ """Return the root directory of the current git repository, or None."""
146
+ try:
147
+ output = run_git_command(["git", "rev-parse", "--show-toplevel"], cwd=cwd)
148
+ return Path(output) if output else None
149
+ except (subprocess.CalledProcessError, FileNotFoundError):
150
+ return None
151
+
152
+
145
153
  def run_git_command(
146
154
  args: list[str],
147
155
  check: bool = True,
@@ -193,35 +201,74 @@ def get_git_staged_files() -> list[str]:
193
201
 
194
202
 
195
203
  def read_file_content(filepath: Path | str) -> Optional[str]:
196
- path = Path(filepath)
197
- if path.exists():
198
- return path.read_text(encoding="utf-8")
199
- return None
204
+ try:
205
+ return Path(filepath).read_text(encoding="utf-8")
206
+ except FileNotFoundError:
207
+ return None
200
208
 
201
209
 
202
210
  def delete_file(filepath: Path | str) -> bool:
203
- path = Path(filepath)
204
- if path.exists():
205
- path.unlink()
211
+ try:
212
+ Path(filepath).unlink()
206
213
  return True
207
- return False
214
+ except FileNotFoundError:
215
+ return False
208
216
 
209
217
 
210
- def load_template_env(sub_dir: str) -> Environment:
218
+ def load_template(sub_dir: str, name: str) -> str:
219
+ """Load a template file from the templates directory."""
211
220
  template_dir = Path(__file__).parent / "templates" / sub_dir
212
- if not template_dir.is_dir():
221
+ template_path = template_dir / name
222
+ if not template_path.is_file():
213
223
  raise RuntimeError(
214
- f"Template directory not found: {template_dir}. "
215
- "The installation may be corrupted."
224
+ f"Template not found: {template_path}. The installation may be corrupted."
216
225
  )
217
- return Environment(loader=FileSystemLoader(template_dir))
226
+ return template_path.read_text(encoding="utf-8")
227
+
228
+
229
+ def render_template(template_str: str, **context) -> str:
230
+ """Render a simple template with variable substitution and conditionals.
231
+
232
+ Supports:
233
+ - {{ variable }} - variable substitution
234
+ - {% if condition %}...{% endif %} - conditional blocks
235
+ - {% if condition %}...{% else %}...{% endif %} - conditional with else
236
+ """
237
+ result = template_str
238
+
239
+ # Process conditional blocks: {% if var %}...{% endif %}
240
+ # and {% if var %}...{% else %}...{% endif %}
241
+ if_pattern = re.compile(
242
+ r"\{%\s*if\s+(\w+)\s*%\}(.*?)\{%\s*endif\s*%\}",
243
+ re.DOTALL,
244
+ )
245
+
246
+ def replace_if(match):
247
+ var_name = match.group(1)
248
+ content = match.group(2)
249
+ value = context.get(var_name)
250
+ if value:
251
+ # Check for else block
252
+ else_pattern = re.compile(r"\{%\s*else\s*%\}", re.DOTALL)
253
+ parts = else_pattern.split(content, 1)
254
+ return parts[0]
255
+ else:
256
+ else_pattern = re.compile(r"\{%\s*else\s*%\}", re.DOTALL)
257
+ parts = else_pattern.split(content, 1)
258
+ return parts[1] if len(parts) > 1 else ""
259
+
260
+ result = if_pattern.sub(replace_if, result)
261
+
262
+ # Process variable substitutions: {{ variable }}
263
+ var_pattern = re.compile(r"\{\{\s*(\w+)\s*\}\}")
264
+
265
+ def replace_var(match):
266
+ var_name = match.group(1)
267
+ return str(context.get(var_name, ""))
218
268
 
269
+ result = var_pattern.sub(replace_var, result)
219
270
 
220
- def render_custom_template(template_str: str, **context) -> str:
221
- """Renders a Jinja2 template from a string."""
222
- env = Environment()
223
- template = env.from_string(template_str)
224
- return template.render(**context)
271
+ return result
225
272
 
226
273
 
227
274
  def load_config() -> Dict[str, Any]:
@@ -246,7 +293,13 @@ def load_config() -> Dict[str, Any]:
246
293
 
247
294
  try:
248
295
  with config_path.open("r", encoding="utf-8") as f:
249
- return yaml.safe_load(f) or {}
296
+ data = yaml.safe_load(f)
297
+ if not isinstance(data, dict):
298
+ print(
299
+ f"Warning: Config at {config_path} is not a dict, using defaults."
300
+ )
301
+ return {}
302
+ return data
250
303
  except Exception as e:
251
304
  print(f"Warning: Failed to load config from {config_path}: {e}")
252
305
  return {}
@@ -323,7 +376,8 @@ __all__ = [
323
376
  "get_git_staged_files",
324
377
  "read_file_content",
325
378
  "delete_file",
326
- "load_template_env",
379
+ "load_template",
380
+ "render_template",
327
381
  "load_config",
328
382
  "get_questionary_style",
329
383
  "is_token_limit_error",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devgen-cli
3
- Version: 0.2.8
3
+ Version: 0.2.9
4
4
  Summary: A collection of developer tools
5
5
  Home-page: https://github.com/S4NKALP/devgen
6
6
  Author: Sankalp Tharu
@@ -22,7 +22,6 @@ Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
23
  Requires-Dist: anthropic>=0.75.0
24
24
  Requires-Dist: google-genai>=0.1.0
25
- Requires-Dist: jinja2>=3.1.6
26
25
  Requires-Dist: openai>=2.11.0
27
26
  Requires-Dist: pyyaml>=6.0.3
28
27
  Requires-Dist: questionary>=2.1.1
@@ -1,32 +1,32 @@
1
1
  devgen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  devgen/ai.py,sha256=dUzAYTsYCa4p8c4mXGc1bkOYN9RznaFzDSif9BCjfx8,1821
3
- devgen/utils.py,sha256=UPmlzsjtNTJVEND76rhwSw3TencUowcOzvaweAkXoDI,10474
3
+ devgen/utils.py,sha256=s32wugfAxyMKD3Ad3RjcgZbXhSdQKn993cEaRYwaltw,12328
4
4
  devgen/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  devgen/cli/changelog.py,sha256=V8HhhzsIcI0CZQnJeUv06Zns4oCVS51ndFsnQC78HZ8,960
6
- devgen/cli/commit.py,sha256=MuK1VxJIRatKteeV1T9Pgzd29DCyNJZbld-NULKHj4s,4150
7
- devgen/cli/config.py,sha256=ddUH3dRWQWJrdEjBc5d3DUL6A4y0-DejC0d8up0EwXI,6637
6
+ devgen/cli/commit.py,sha256=Y6pp4WmGjWPxoy1E8y1hsPHj9N1fihaIJfZPRbuFQ9w,4153
7
+ devgen/cli/config.py,sha256=9AYxOR9po8963LklgxSFMOgiM_bu1jpGF4VnqJiHk7k,6852
8
8
  devgen/cli/gitignore.py,sha256=Nl7t_EZDhsOs5y-YkbOiuFLxHuAw48QTpw4_m9qJR2w,4446
9
9
  devgen/cli/license.py,sha256=LAwro2Mwf7PEQWORMFqwp__Mn0Kx3BPhqZR-ttuKAbk,2830
10
10
  devgen/cli/main.py,sha256=LBMZxMI7djpO2beccpRZWZ2DWS3tSblp3cmxAtTzu5c,3667
11
11
  devgen/cli/release.py,sha256=X0PfH7Vx__-CraiCNoMvxXWDmcmKdCdX16TYNubteV4,864
12
- devgen/cli/setup.py,sha256=aSEAf3Ny3ox3fynx5Go53AgqRwOVf0s4jTnnfQ_QcuQ,3077
12
+ devgen/cli/setup.py,sha256=ac3S5FIc0eApJYBVFdVLHY2zl4FiCcnaEjaOFRfq9-g,3124
13
13
  devgen/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- devgen/modules/changelog_generator.py,sha256=7n4KM9VCkhke0NVYelJfBu1CVLnVT39pvZbvZ6RybYg,5716
14
+ devgen/modules/changelog_generator.py,sha256=C7nhDoy4IzvY8Hg5Xxn_JpRZGTEsC7vgQ73bHyOANjA,5988
15
15
  devgen/modules/changelog_sections.py,sha256=MuBGRZW3PBFRFbN4075BYHs0G1wWHEdPi2ekjMxSbvo,2414
16
- devgen/modules/commit_generator.py,sha256=6WhOCmWYelgr_-lMVDvgWmqoadfnDARAq8zrR74l1OM,12799
17
- devgen/modules/diff_builder.py,sha256=Wj6ahAdbau5-LNmRJNO2nzx9MMkxX4WzQbinhFUU0O4,7432
18
- devgen/modules/git_ops.py,sha256=SB-ptIVsgvhQyrpGUKq-E1RxFEvbMBTPdSL0RQdKhY4,5543
19
- devgen/modules/gitignore_generator.py,sha256=DpHAOrKIwSDklv7CLelYro478MQGJ4EcMAFi5E--348,4379
16
+ devgen/modules/commit_generator.py,sha256=Y94zfGwzw0x22hXu4Fjw1wWrowyxneN5f9qt7EiuWUI,12476
17
+ devgen/modules/diff_builder.py,sha256=mjosqnmCkzY6xoE16g3L88b4EaaXdNQy2FYZDXevh5A,7378
18
+ devgen/modules/git_ops.py,sha256=H-X9pkzztcw4aCzc7fUJhAt-1QawRuSozHK5Fy-1aw0,5575
19
+ devgen/modules/gitignore_generator.py,sha256=6d2aeC5s9KONyyuYacKKzE2Haj3AllWQ1Ybil5tvoAc,4756
20
20
  devgen/modules/license_generator.py,sha256=YU25lNsD6_7zneuv9NpsT1sy_u5_FiC__0Qeou2O1kY,2634
21
21
  devgen/modules/release_note_generator.py,sha256=4FOi0rk9PWvECL84_uM5cPTZAFLvWfDGVRPuqsyCaQk,2267
22
22
  devgen/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- devgen/providers/anthropic.py,sha256=Fu6PcfIvL_Ey-t7VQ7V6OlhoXaMukC3RmIl9Jx5dSsg,569
23
+ devgen/providers/anthropic.py,sha256=7pJFk8Bzsmv_S9dwcOd2vR9X2tG15obmrm8WMaXVNuw,687
24
24
  devgen/providers/base.py,sha256=1pJJzrEX8HZ0F9B9Xv766fRpNJkUVdaSY-Dmey29P1U,3276
25
25
  devgen/providers/gemini.py,sha256=503wne2XX2mkE-l-3vWa8K0q6C6NSn-bEAo8Kk2iUNw,2723
26
- devgen/providers/huggingface.py,sha256=TgTDCGOOwDdKaHgZJVMrXFn5fYJ8W5YiF7uki3XSOf0,2242
27
- devgen/providers/ollama.py,sha256=667cGxtbi2GtyBvi6aeaRhRMzXZWWTvn2qSBB6dHTPU,1901
28
- devgen/providers/openai.py,sha256=MfPW4-tdLJ682i3Qqz9z9pPhX161eWfACqgeO_Jl0xY,595
29
- devgen/providers/openrouter.py,sha256=4-5SgUMNnY4nuTtlCtdbrDJIAQo5hFTciIFttELI_Bs,839
26
+ devgen/providers/huggingface.py,sha256=PkOlvS7dWk1FMGwcre2PDzgWmB352UxXsaSuh8v85Fk,2285
27
+ devgen/providers/ollama.py,sha256=T4sNcm2iNriPenpEV_5V3B9Gp3-oY71XQlvEbmFfU5o,2159
28
+ devgen/providers/openai.py,sha256=MUjwQwCJd8S-bycTNHW0FoZCim0kO1pzV_0Qb9ql6P8,950
29
+ devgen/providers/openrouter.py,sha256=lmc_rvtU2Ndt1WGkyvRZXWJIl7zxfxIOY5_gZVeQ4No,1220
30
30
  devgen/templates/commit/commit_message.tpl,sha256=vj9dUP_d1--TWiE0NMP3-EEBHmdJLdb2tQCDMdaPoAk,435
31
31
  devgen/templates/licenses/agpl-3.0.json,sha256=lYJObhc3-nBQQD2L3cw9C-Q9ceiV26qkzp6b1H7NnZ4,35832
32
32
  devgen/templates/licenses/apache-2.0.json,sha256=7U9zUdz8kbmrtL0hDgOgtYMhRnA7Qi7WuDxSMNQ0MIU,11989
@@ -41,9 +41,9 @@ devgen/templates/licenses/lgpl-2.1.json,sha256=M1uxVajrZBccW3gX5cYwMdZ0DyBLbxnmP
41
41
  devgen/templates/licenses/mit.json,sha256=NfRzL86O8TZOY2ZRdelQFEVX0t1o3bUJtqmib-pXM0s,1426
42
42
  devgen/templates/licenses/mpl-2.0.json,sha256=Fq-oj2-z5TZS2Qr9bQHea-Nm_baC2_7DQtuSKT476Yk,17729
43
43
  devgen/templates/licenses/unlicense.json,sha256=uWl_NnBn_Daqu-0df7y4E5H0q7KUdDby3TK1IHBDr4Y,1553
44
- devgen_cli-0.2.8.dist-info/licenses/LICENSE,sha256=-JHhLXXB2RRUeojKiRRTDHyJ1QiOCtrDfwbahUOb6Yc,35150
45
- devgen_cli-0.2.8.dist-info/METADATA,sha256=Gi-fskrat61KX5RVzSUzfllrOzk29p11ACsHo606G4k,8772
46
- devgen_cli-0.2.8.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
47
- devgen_cli-0.2.8.dist-info/entry_points.txt,sha256=NdqXSz3M3_0x0I3tFrXEF7r0mwCDmMfUNZbh2HDZSCg,47
48
- devgen_cli-0.2.8.dist-info/top_level.txt,sha256=u-4SzOUkP7II9PqLZ4PCpIAsxv4tuxuJ4gtdApYG4Bw,7
49
- devgen_cli-0.2.8.dist-info/RECORD,,
44
+ devgen_cli-0.2.9.dist-info/licenses/LICENSE,sha256=-JHhLXXB2RRUeojKiRRTDHyJ1QiOCtrDfwbahUOb6Yc,35150
45
+ devgen_cli-0.2.9.dist-info/METADATA,sha256=fMthC4wBv9iMpoeSFcDZBDOH3c2Z0sP6VLqVPPWeF0o,8743
46
+ devgen_cli-0.2.9.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
47
+ devgen_cli-0.2.9.dist-info/entry_points.txt,sha256=NdqXSz3M3_0x0I3tFrXEF7r0mwCDmMfUNZbh2HDZSCg,47
48
+ devgen_cli-0.2.9.dist-info/top_level.txt,sha256=u-4SzOUkP7II9PqLZ4PCpIAsxv4tuxuJ4gtdApYG4Bw,7
49
+ devgen_cli-0.2.9.dist-info/RECORD,,