devgen-cli 0.2.7__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
@@ -85,13 +85,16 @@ def setup_config() -> None:
85
85
  raise typer.Exit(code=130)
86
86
  ollama_host = ollama_host_input.strip() or ollama_host
87
87
 
88
- # Save Config
88
+ # Save Config — merge with existing to preserve extra keys
89
89
  new_config = {
90
- "provider": provider,
91
- "model": model,
92
- "api_key": api_key,
93
- "emoji": emoji,
94
- "ollama_host": ollama_host,
90
+ **current_config,
91
+ **{
92
+ "provider": provider,
93
+ "model": model,
94
+ "api_key": api_key,
95
+ "emoji": emoji,
96
+ "ollama_host": ollama_host,
97
+ },
95
98
  }
96
99
 
97
100
  try:
@@ -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
 
@@ -145,7 +149,7 @@ class ChangelogGenerator:
145
149
  old = path.read_text(encoding="utf-8")
146
150
  if old.strip().startswith("# CHANGELOG"):
147
151
  header, _, rest = old.partition("\n")
148
- new_content = f"{header}\n\n{md_content}\n{rest.lstrip()}"
152
+ new_content = f"{header}\n\n{md_content}\n{rest}"
149
153
  else:
150
154
  new_content = f"# CHANGELOG\n\n{md_content}\n\n{old}"
151
155
  else:
@@ -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
 
@@ -48,6 +48,7 @@ class CacheManager:
48
48
 
49
49
  def __init__(self, path: Path, max_age_minutes: int = 120) -> None:
50
50
  self.path = path
51
+ self.max_age_minutes = max_age_minutes
51
52
 
52
53
  def init_dry_run(self) -> None:
53
54
  self.path.parent.mkdir(parents=True, exist_ok=True)
@@ -59,7 +60,7 @@ class CacheManager:
59
60
  if dry_run:
60
61
  self.init_dry_run()
61
62
  return {}
62
- if not force_rebuild and is_file_recent(self.path):
63
+ if not force_rebuild and is_file_recent(self.path, self.max_age_minutes):
63
64
  return extract_commit_messages(self.path)
64
65
  return {}
65
66
 
@@ -73,13 +74,13 @@ class CommitMessageBuilder:
73
74
 
74
75
  def __init__(
75
76
  self,
76
- template_env,
77
+ template_str: str,
77
78
  config: Dict[str, Any],
78
79
  provider: str,
79
80
  model: str,
80
81
  debug: bool = False,
81
82
  ) -> None:
82
- self.template_env = template_env
83
+ self.template_str = template_str
83
84
  self.config = config
84
85
  self.provider = provider
85
86
  self.model = model
@@ -96,25 +97,14 @@ class CommitMessageBuilder:
96
97
  use_emoji = self.config.get("emoji", True)
97
98
  ollama_host = self.config.get("ollama_host")
98
99
 
99
- if custom_template:
100
- prompt = render_custom_template(
101
- custom_template,
102
- group_name=group,
103
- diff_text=diff,
104
- use_emoji=use_emoji,
105
- context=manifest_context,
106
- )
107
- else:
108
- prompt = (
109
- self.template_env.get_template("commit_message.tpl")
110
- .render(
111
- group_name=group,
112
- diff_text=diff,
113
- use_emoji=use_emoji,
114
- context=manifest_context,
115
- )
116
- .strip()
117
- )
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()
118
108
 
119
109
  provider = (
120
110
  extra_kwargs.get("provider") or self.config.get("provider") or self.provider
@@ -197,10 +187,10 @@ class CommitEngine:
197
187
  ),
198
188
  logger=self.logger,
199
189
  )
200
- self.template_env = load_template_env("commit")
190
+ self.template_str = load_template("commit", "commit_message.tpl")
201
191
  self.cache = CacheManager(get_commit_dry_run_path())
202
192
  self.message_builder = CommitMessageBuilder(
203
- self.template_env,
193
+ self.template_str,
204
194
  self.config,
205
195
  provider=self.provider,
206
196
  model=self.model,
@@ -387,6 +377,7 @@ def run_commit_engine(**kwargs: Any) -> None:
387
377
  sys.exit(0)
388
378
  except Exception as e:
389
379
  logger.error(f"Commit engine failed: {e}", exc_info=True)
380
+ sys.exit(1)
390
381
 
391
382
 
392
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:
@@ -70,7 +70,7 @@ class DiffBuilder:
70
70
  def _partition(self, files: List[str]) -> tuple[list[str], list[str]]:
71
71
  summary, to_diff = [], []
72
72
  for f in files:
73
- if any(p in f for p in IGNORED_DIFF_PATTERNS):
73
+ if Path(f).name in IGNORED_DIFF_PATTERNS:
74
74
  summary.append(f"[METADATA UPDATED] {f}")
75
75
  else:
76
76
  to_diff.append(f)
@@ -128,7 +128,8 @@ class FileGrouper:
128
128
  deepest = max(candidates, key=lambda p: len(Path(p).parts))
129
129
  parent_path = str(Path(deepest).parent)
130
130
  new_key = "root" if parent_path == "." else parent_path
131
- groups.setdefault(new_key, []).extend(groups.pop(deepest))
131
+ files = groups.pop(deepest)
132
+ groups.setdefault(new_key, []).extend(files)
132
133
  return groups
133
134
 
134
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
 
@@ -115,8 +115,11 @@ class GitOperator:
115
115
  self._run(["git", "add", *existing])
116
116
  if deleted:
117
117
  self._run(["git", "rm", *deleted])
118
- except GitError as e:
119
- raise GitError(f"Could not stage files: {e}") from e
118
+ except GitError:
119
+ # Fallback: file state may have changed between check and command.
120
+ # Try a single git add --all for the whole set; git will handle
121
+ # both additions and removals correctly.
122
+ self._run(["git", "add", "--all", "--", *files])
120
123
 
121
124
  def commit(self, message: str) -> None:
122
125
  """``git commit -m`` with the given message."""
@@ -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
- return message.content[0].text
19
+ if not message.content:
20
+ raise RuntimeError(f"Anthropic returned no content for model {model!r}.")
21
+ return message.content[0].text or ""
devgen/providers/base.py CHANGED
@@ -53,7 +53,8 @@ class BaseProvider(ABC):
53
53
  )
54
54
  chosen_model = model or self.DEFAULT_MODEL
55
55
  try:
56
- return self._generate(prompt, api_key, chosen_model, **kwargs).strip()
56
+ text = self._generate(prompt, api_key, chosen_model, **kwargs)
57
+ return (text or "").strip()
57
58
  except Exception as e:
58
59
  self._handle_error(e)
59
60
 
@@ -30,7 +30,7 @@ class GeminiProvider(BaseProvider):
30
30
  max_output_tokens=kwargs.get("max_output_tokens", 2048),
31
31
  ),
32
32
  )
33
- return response.text
33
+ return response.text or ""
34
34
  except Exception as e:
35
35
  if is_token_limit_error(e):
36
36
  raise # base class handles with friendly message
@@ -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,25 +18,29 @@ 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
 
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
- )
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(
36
40
  f"Ollama returned no choices for model {model!r}. "
37
41
  "The model may not be installed locally."
38
42
  )
39
- return response.choices[0].message.content
43
+ return response.choices[0].message.content or ""
40
44
 
41
45
  def _handle_error(self, error):
42
46
  """Override default wrapping — Ollama has no API key to check."""
@@ -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
- )
20
- return response.choices[0].message.content
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}.")
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,30 +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
- return Environment(loader=FileSystemLoader(template_dir))
221
+ template_path = template_dir / name
222
+ if not template_path.is_file():
223
+ raise RuntimeError(
224
+ f"Template not found: {template_path}. The installation may be corrupted."
225
+ )
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, ""))
213
268
 
269
+ result = var_pattern.sub(replace_var, result)
214
270
 
215
- def render_custom_template(template_str: str, **context) -> str:
216
- """Renders a Jinja2 template from a string."""
217
- env = Environment()
218
- template = env.from_string(template_str)
219
- return template.render(**context)
271
+ return result
220
272
 
221
273
 
222
274
  def load_config() -> Dict[str, Any]:
@@ -241,7 +293,13 @@ def load_config() -> Dict[str, Any]:
241
293
 
242
294
  try:
243
295
  with config_path.open("r", encoding="utf-8") as f:
244
- 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
245
303
  except Exception as e:
246
304
  print(f"Warning: Failed to load config from {config_path}: {e}")
247
305
  return {}
@@ -318,7 +376,8 @@ __all__ = [
318
376
  "get_git_staged_files",
319
377
  "read_file_content",
320
378
  "delete_file",
321
- "load_template_env",
379
+ "load_template",
380
+ "render_template",
322
381
  "load_config",
323
382
  "get_questionary_style",
324
383
  "is_token_limit_error",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devgen-cli
3
- Version: 0.2.7
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=-Qcr0eddNa8iissjHxcxG_ziE4-KGwZcszuhPN7a71k,10291
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=KvNcSoJox6FVEyk5k_8g-yGghWExgKD719I63TNAywo,3008
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=xtIfJbHQaFf2mfumshQezb2s_4pQVxDcqWy-p-w9_z8,5725
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=hpop2bNTkkzCrCSgtM_EHRhykxtxYi_oYyYN9P0jfJU,12730
17
- devgen/modules/diff_builder.py,sha256=KUMiVITfwfPZ7jF-dZWtkMGGwSr-W9K8m-FhrZCufME,7325
18
- devgen/modules/git_ops.py,sha256=D4wS4cfn-FAgaogmOq25Fpig7TZE5DvtAe_qABoSyGA,5344
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=OhExEt951Y94A0Q1jOye0LJqhvcFqNrquxz3eeGOX48,563
24
- devgen/providers/base.py,sha256=000kK4DCwwNmkEvWY8jDvcuVWVt3LxmlPYOZV26wXPc,3244
25
- devgen/providers/gemini.py,sha256=mhPtpGn4RU9TLa0o05wrvPMnq-WH0PUdsnWRp079Fp8,2717
26
- devgen/providers/huggingface.py,sha256=TgTDCGOOwDdKaHgZJVMrXFn5fYJ8W5YiF7uki3XSOf0,2242
27
- devgen/providers/ollama.py,sha256=NZqglIJBdPn94W_2oXqCCPFprRJghi0XDp7VEniz99A,1895
28
- devgen/providers/openai.py,sha256=km8cUkMdc_41OOXFozy5DIDvu8BYumviOFNA9rGRcMY,589
29
- devgen/providers/openrouter.py,sha256=4-5SgUMNnY4nuTtlCtdbrDJIAQo5hFTciIFttELI_Bs,839
23
+ devgen/providers/anthropic.py,sha256=7pJFk8Bzsmv_S9dwcOd2vR9X2tG15obmrm8WMaXVNuw,687
24
+ devgen/providers/base.py,sha256=1pJJzrEX8HZ0F9B9Xv766fRpNJkUVdaSY-Dmey29P1U,3276
25
+ devgen/providers/gemini.py,sha256=503wne2XX2mkE-l-3vWa8K0q6C6NSn-bEAo8Kk2iUNw,2723
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.7.dist-info/licenses/LICENSE,sha256=-JHhLXXB2RRUeojKiRRTDHyJ1QiOCtrDfwbahUOb6Yc,35150
45
- devgen_cli-0.2.7.dist-info/METADATA,sha256=eQikHq_pImn0Zil0Nihs4kxGWOXVO-Z7YGdSL_W3dS8,8772
46
- devgen_cli-0.2.7.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
47
- devgen_cli-0.2.7.dist-info/entry_points.txt,sha256=NdqXSz3M3_0x0I3tFrXEF7r0mwCDmMfUNZbh2HDZSCg,47
48
- devgen_cli-0.2.7.dist-info/top_level.txt,sha256=u-4SzOUkP7II9PqLZ4PCpIAsxv4tuxuJ4gtdApYG4Bw,7
49
- devgen_cli-0.2.7.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,,