tinysemver 2.0.8__tar.gz → 2.1.0__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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tinysemver
3
- Version: 2.0.8
4
- Summary: Tiny Semantic Versioning (SemVer) library, that doesn't depend on 300K lines of JavaScript
3
+ Version: 2.1.0
4
+ Summary: Semantic-Versioning with LLMs and without 300,000 lines of JS
5
5
  Author-email: Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>, Guillaume de Rouville <31691250+grouville@users.noreply.github.com>
6
6
  License: Apache License
7
7
  Version 2.0, January 2004
@@ -210,7 +210,7 @@ Project-URL: Documentation, https://github.com/ashvardanian/tinysemver/blob/main
210
210
  Project-URL: Repository, https://github.com/ashvardanian/tinysemver.git
211
211
  Project-URL: Issues, https://github.com/ashvardanian/tinysemver/issues
212
212
  Project-URL: Changelog, https://github.com/ashvardanian/tinysemver/blob/main/CHANGELOG.md
213
- Classifier: Development Status :: 4 - Beta
213
+ Classifier: Development Status :: 5 - Production/Stable
214
214
  Classifier: Intended Audience :: Developers
215
215
  Classifier: Topic :: Software Development :: Build Tools
216
216
  Classifier: License :: OSI Approved :: Apache Software License
@@ -223,10 +223,12 @@ Classifier: Programming Language :: Python :: 3.10
223
223
  Classifier: Programming Language :: Python :: 3.11
224
224
  Description-Content-Type: text/markdown
225
225
  License-File: LICENSE
226
+ Requires-Dist: openai>=1.54.4
227
+ Requires-Dist: rich>=13.8.0
226
228
 
227
- ![](https://github.com/ashvardanian/ashvardanian/blob/master/repositories/TinySemVer.jpg)
229
+ ![TinySemVer Banner](https://github.com/ashvardanian/ashvardanian/blob/master/repositories/TinySemVer.jpg)
228
230
 
229
- __TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file.
231
+ __TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file, capable of generating changelogs and release notes with the use of OpenAI-compatible LLM APIs, or without them.
230
232
  In plain English, if your commit messages look like `feat: add new feature` or `fix: bugfix`, this package will automate releasing new "GIT tags" based on the commit messages.
231
233
  Here is how to integrate it into your project CI:
232
234
 
@@ -304,6 +306,19 @@ Alternatively, you can just ask for `--help`:
304
306
  $ tinysemver --help
305
307
  ```
306
308
 
309
+ ## AI and Rock-n-Roll
310
+
311
+ TinySemVer can leverage a language model to validate the commits and generate clean and infromative release notes.
312
+
313
+ ```sh
314
+ $ tinysemver --verbose \
315
+ --github-repository 'ashvardanian/tinysemver' \
316
+ --openai-base-url 'https://api.groq.com/openai/v1' \
317
+ --openai-api-key 'GET_YOURSELF_A_KEY' \
318
+ --openai-model 'llama-3.2-11b-text-preview' \
319
+ --dry-run
320
+ ```
321
+
307
322
  ## Usage Details for the GitHub CI Action
308
323
 
309
324
  TinySemVer can be easily integrated into your GitHub Actions CI pipeline.
@@ -1,6 +1,6 @@
1
- ![](https://github.com/ashvardanian/ashvardanian/blob/master/repositories/TinySemVer.jpg)
1
+ ![TinySemVer Banner](https://github.com/ashvardanian/ashvardanian/blob/master/repositories/TinySemVer.jpg)
2
2
 
3
- __TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file.
3
+ __TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file, capable of generating changelogs and release notes with the use of OpenAI-compatible LLM APIs, or without them.
4
4
  In plain English, if your commit messages look like `feat: add new feature` or `fix: bugfix`, this package will automate releasing new "GIT tags" based on the commit messages.
5
5
  Here is how to integrate it into your project CI:
6
6
 
@@ -78,6 +78,19 @@ Alternatively, you can just ask for `--help`:
78
78
  $ tinysemver --help
79
79
  ```
80
80
 
81
+ ## AI and Rock-n-Roll
82
+
83
+ TinySemVer can leverage a language model to validate the commits and generate clean and infromative release notes.
84
+
85
+ ```sh
86
+ $ tinysemver --verbose \
87
+ --github-repository 'ashvardanian/tinysemver' \
88
+ --openai-base-url 'https://api.groq.com/openai/v1' \
89
+ --openai-api-key 'GET_YOURSELF_A_KEY' \
90
+ --openai-model 'llama-3.2-11b-text-preview' \
91
+ --dry-run
92
+ ```
93
+
81
94
  ## Usage Details for the GitHub CI Action
82
95
 
83
96
  TinySemVer can be easily integrated into your GitHub Actions CI pipeline.
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tinysemver"
7
- version = "2.0.8"
8
- description = "Tiny Semantic Versioning (SemVer) library, that doesn't depend on 300K lines of JavaScript"
7
+ version = "2.1.0"
8
+ description = "Semantic-Versioning with LLMs and without 300,000 lines of JS"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
11
11
  authors = [
@@ -13,7 +13,7 @@ authors = [
13
13
  { name = "Guillaume de Rouville", email = "31691250+grouville@users.noreply.github.com" },
14
14
  ]
15
15
  classifiers = [
16
- "Development Status :: 4 - Beta",
16
+ "Development Status :: 5 - Production/Stable",
17
17
  "Intended Audience :: Developers",
18
18
  "Topic :: Software Development :: Build Tools",
19
19
  "License :: OSI Approved :: Apache Software License",
@@ -25,6 +25,7 @@ classifiers = [
25
25
  "Programming Language :: Python :: 3.10",
26
26
  "Programming Language :: Python :: 3.11",
27
27
  ]
28
+ dependencies = ["openai >= 1.54.4", "rich >= 13.8.0"]
28
29
 
29
30
  [project.scripts]
30
31
  tinysemver = "tinysemver.tinysemver:main"
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
- """TinySemVer is a tiny Python script that helps you manage your project's versioning.
3
+ """TinySemVer is a tiny but mighty Semantic Versioning tool with AI, Drugs, and Rock-n-Roll.
4
4
 
5
5
  This module traces the commit history of a Git repository after the last tag, and based
6
6
  on the commit messages, it determines the type of version bump (major, minor, or patch).
@@ -15,6 +15,7 @@ Example:
15
15
  --minor-verbs 'feature,minor,add,new' \
16
16
  --patch-verbs 'fix,patch,bug,improve,docs,make' \
17
17
  --changelog-file 'CHANGELOG.md' \
18
+ --guide-file 'CONTIBUTING.md' \
18
19
  --version-file 'VERSION' \
19
20
  --update-version-in 'package.json' '"version": "(.*)"' \
20
21
  --update-version-in 'CITATION.cff' '^version: (.*)' \
@@ -32,6 +33,7 @@ By default, the following conventions are used:
32
33
  - The repository must be a Git repository.
33
34
  - It must contain a "VERSION" and "CHANGELOG.md" files in the root directory.
34
35
  - The changelog is append-only - sorted in chronological order.
36
+ - The contibution guide is in the "CONTRIBUTING.md" file.
35
37
 
36
38
  Setting up a new project:
37
39
 
@@ -51,10 +53,13 @@ from typing import List, Tuple, Literal, Union, Optional, NamedTuple
51
53
  from datetime import datetime
52
54
  import traceback
53
55
 
56
+
54
57
  SemVer = Tuple[int, int, int]
55
58
  BumpType = Literal["major", "minor", "patch"]
56
59
  PathLike = Union[str, os.PathLike]
57
60
  Commit = NamedTuple("Commit", [("hash", str), ("message", str)])
61
+ ChangeDiff = str
62
+
58
63
 
59
64
  class NoNewCommitsError(Exception):
60
65
  """Raised when no new commits are found since the last tag."""
@@ -62,6 +67,69 @@ class NoNewCommitsError(Exception):
62
67
  pass
63
68
 
64
69
 
70
+ class MayContainVulnerability(Warning):
71
+ """Raised when the commit may contain a vulnerability."""
72
+
73
+ pass
74
+
75
+
76
+ class MayContainLogicalBugs(Warning):
77
+ """Raised when the commit may contain logical bugs."""
78
+
79
+ pass
80
+
81
+
82
+ class MayContainBreakingChange(Warning):
83
+ """Raised when the commit may contain a breaking change."""
84
+
85
+ pass
86
+
87
+
88
+ class MayLackDocumentation(Warning):
89
+ """Raised when the commit may lack documentation updates."""
90
+
91
+ pass
92
+
93
+
94
+ class UnknownCommitWarning(Warning):
95
+ """Something in the commit doesn't add up."""
96
+
97
+ pass
98
+
99
+
100
+ _console = None # Initialize global console variable
101
+ _console_is_rich = True # Initialize global console type variable
102
+ _openai_client = None # Initialize global OpenAI client variable
103
+
104
+
105
+ def print_to_console(message: str) -> None:
106
+ """Print a message to the console."""
107
+ global _console, _console_is_rich
108
+ if _console == None and _console_is_rich:
109
+ try:
110
+ from rich import Console
111
+
112
+ _console = Console()
113
+ except ImportError:
114
+ _console = None
115
+ _console_is_rich = False
116
+
117
+ if _console_is_rich:
118
+ _console.print(message)
119
+ else:
120
+ print(message)
121
+
122
+
123
+ def get_open_ai_client(base_url: str, api_key: str):
124
+ # Create a global variable for the client
125
+ global _openai_client
126
+ if not _openai_client:
127
+ from openai import OpenAI
128
+
129
+ _openai_client = OpenAI(base_url=base_url, api_key=api_key)
130
+ return _openai_client
131
+
132
+
65
133
  def get_last_tag(repository_path: PathLike) -> str:
66
134
  """Retrieve the last Git tag name from the repository."""
67
135
  result = subprocess.run(
@@ -75,7 +143,6 @@ def get_last_tag(repository_path: PathLike) -> str:
75
143
  return result.stdout.strip().decode("utf-8")
76
144
 
77
145
 
78
-
79
146
  def get_commits_since_tag(repository_path: PathLike, tag: str) -> List[Commit]:
80
147
  """Get commit hashes and messages since the specified Git tag."""
81
148
  result = subprocess.run(
@@ -93,6 +160,20 @@ def get_commits_since_tag(repository_path: PathLike, tag: str) -> List[Commit]:
93
160
  return [Commit(h, m) for h, m in zip(hashes, messages)]
94
161
 
95
162
 
163
+ def get_diff_for_commit(repository_path: PathLike, commit_hash: str) -> ChangeDiff:
164
+ """Retrieve the diff for a specific commit and parse into Change named tuples."""
165
+ result = subprocess.run(
166
+ ["git", "diff", f"{commit_hash}^", commit_hash, "--unified=3"],
167
+ stdout=subprocess.PIPE,
168
+ stderr=subprocess.PIPE,
169
+ cwd=repository_path,
170
+ )
171
+ if result.returncode != 0:
172
+ raise RuntimeError(f"Failed to retrieve diff for commit: {commit_hash}")
173
+
174
+ return result.stdout.decode("utf-8")
175
+
176
+
96
177
  def parse_version(tag: str) -> SemVer:
97
178
  """Parse a version string from a Git tag name, assuming it looks like `v1.2.3`."""
98
179
  match = re.match(r"v?(\d+)\.(\d+)\.(\d+)", tag)
@@ -211,7 +292,7 @@ def create_tag(
211
292
  ).stdout.strip()
212
293
 
213
294
  subprocess.run(["git", "tag", "-a", tag, "-m", message, new_commit_sha], cwd=repository_path, env=env)
214
- print(f"Created new tag: {tag}")
295
+ print_to_console(f"[bold green]Created new tag:[/bold green] {tag}")
215
296
  if push:
216
297
  url = None
217
298
  if github_token and github_repository:
@@ -222,11 +303,6 @@ def create_tag(
222
303
  assert github_repository and not github_token, "You can't provide the GitHub token without the repository"
223
304
  url = f"https://github.com/{github_repository}"
224
305
 
225
- # Pull the latest changes from the remote repository
226
- # pull_result = subprocess.run(["git", "pull", "--merge", url], cwd=repository_path, env=env)
227
- # if pull_result.returncode != 0:
228
- # raise RuntimeError("Failed to pull the latest changes from the remote repository")
229
-
230
306
  # Push both commits and the tag
231
307
  push_result = subprocess.run(
232
308
  ["git", "push", url, f"{new_commit_sha}:{default_branch}"],
@@ -236,15 +312,15 @@ def create_tag(
236
312
  )
237
313
  if push_result.returncode != 0:
238
314
  raise RuntimeError(
239
- f"Failed to push the new commits to the remote repository: '{url}' with error: {push_result.stderr}"
315
+ f"Failed to push the new commits to the remote repository: '{url}' with error: {push_result.stderr.decode('utf-8')}"
240
316
  )
241
317
 
242
318
  push_result = subprocess.run(["git", "push", url, "--tag"], cwd=repository_path, capture_output=True, env=env)
243
319
  if push_result.returncode != 0:
244
320
  raise RuntimeError(
245
- f"Failed to push the new tag to the remote repository: '{url}' with error: {push_result.stderr}"
321
+ f"Failed to push the new tag to the remote repository: '{url}' with error: {push_result.stderr.decode('utf-8')}"
246
322
  )
247
- print(f"Pushed to: {url}")
323
+ print_to_console(f"[bold green]Pushed to:[/bold green] {url}")
248
324
 
249
325
  # Create a release using GitHub CLI if available
250
326
  if create_release and github_repository:
@@ -274,13 +350,15 @@ def create_tag(
274
350
  )
275
351
 
276
352
  if release_result.returncode == 0:
277
- print(f"Created GitHub release for tag: {tag}")
353
+ print_to_console(f"[bold green]Created GitHub release for tag:[/bold green] {tag}")
278
354
  else:
279
- print(f"Failed to create GitHub release: {release_result.stderr}")
355
+ print_to_console(
356
+ f"[bold red]Failed to create GitHub release:[/bold red] {release_result.stderr.strip()}"
357
+ )
280
358
  except subprocess.CalledProcessError:
281
- print("GitHub CLI not available. Skipping release creation.")
359
+ print_to_console("[bold yellow]GitHub CLI not available. Skipping release creation.[/bold yellow]")
282
360
  except Exception as e:
283
- print(f"An error occurred while creating the release: {str(e)}")
361
+ print_to_console(f"[bold red]An error occurred while creating the release:[/bold red] {str(e)}")
284
362
 
285
363
 
286
364
  def patch_with_regex(
@@ -307,8 +385,7 @@ def patch_with_regex(
307
385
  )
308
386
  return updated
309
387
 
310
- # Without using the re.MULTILINE flag,
311
- # the ^ and $ anchors match the start and end of the whole string.
388
+ # Compile the regex pattern with multiline support
312
389
  regex_pattern = re.compile(regex_pattern, re.MULTILINE)
313
390
  matches = list(re.finditer(regex_pattern, old_content))
314
391
  new_content = re.sub(regex_pattern, replace_first_group, old_content, 1)
@@ -322,13 +399,120 @@ def patch_with_regex(
322
399
  new_slice = re.sub(regex_pattern, replace_first_group, old_slice, 1)
323
400
 
324
401
  if verbose:
325
- print(f"Will update file: {file_path}:{match_line}")
326
- print(f"- {old_slice}")
327
- print(f"+ {new_slice}")
402
+ print_to_console(f"[bold cyan]Will update file:[/bold cyan] {file_path}:{match_line}")
403
+ print_to_console(f"[red]- {old_slice}[/red]")
404
+ print_to_console(f"[green]+ {new_slice}[/green]")
328
405
 
329
406
  if not dry_run:
330
407
  with open(file_path, "w") as file:
331
408
  file.write(new_content)
409
+ if verbose:
410
+ print_to_console(f"[bold green]File updated successfully:[/bold green] {file_path}")
411
+
412
+
413
+ def validate_commit_with_llms(
414
+ base_url: str,
415
+ api_key: str,
416
+ model: Optional[str],
417
+ commit: Commit,
418
+ change: ChangeDiff,
419
+ ) -> Optional[Warning]:
420
+ prompt = f"""
421
+ You are a professional coding assistant helping me to validate a commit message and its changes.
422
+
423
+ 1. Check if the changed code is likely to introduce logical bugs.
424
+ 2. Check if the commit message is likely to contain a vulnerability.
425
+ 3. Check if it contains a breaking change that is likely to affect users.
426
+ 4. Check if the documentation wasn't updated for the changes made.
427
+
428
+ Don't let the commit message fool you, just look at the changes made in the code.
429
+ Reply by choosing any number from 0 to 4 and describe the issue, if anything is found.
430
+
431
+ 0. No issues found.
432
+ 1. The commit may introduce logical bugs, such as the overflow in ...
433
+ 2. The commit may contain a vulnerability, such as the SQL injection in ...
434
+ 3. The commit may contain a breaking change, such as the removal of ...
435
+ 4. The commit may lack documentation updates for the changes made in function ...
436
+ """
437
+ message = f"""
438
+ #{commit.hash}: {commit.message}
439
+
440
+ Changes:
441
+
442
+ {change}
443
+ """
444
+ client = get_open_ai_client(base_url=base_url, api_key=api_key)
445
+ response = client.chat.completions.create(
446
+ messages=[
447
+ {"role": "system", "content": prompt},
448
+ {"role": "user", "content": message},
449
+ ],
450
+ max_tokens=256,
451
+ model=model,
452
+ stream=False,
453
+ )
454
+ response_text = response.choices[0].message.content
455
+ if response_text.startswith("0."):
456
+ return None
457
+ if response_text.startswith("1."):
458
+ return MayContainLogicalBugs(response_text)
459
+ if response_text.startswith("2."):
460
+ return MayContainVulnerability(response_text)
461
+ if response_text.startswith("3."):
462
+ return MayContainBreakingChange(response_text)
463
+ if response_text.startswith("4."):
464
+ return MayLackDocumentation(response_text)
465
+ return UnknownCommitWarning(response_text)
466
+
467
+
468
+ def aggregate_release_notes_with_llms(
469
+ base_url: str,
470
+ api_key: str,
471
+ model: Optional[str],
472
+ github_repository: str,
473
+ commits: List[Commit],
474
+ changes: List[ChangeDiff],
475
+ ) -> str:
476
+
477
+ commits_with_hashes = "\n".join(f"- {commit.hash}: {commit.message}" for commit in commits)
478
+ prompt = f"""
479
+ You are a release notes generator for an advanced software project.
480
+
481
+ Aggregate the release notes for the upcoming version based on the commits and their
482
+ changes replying in a Github-flavored Markdown format.
483
+
484
+ - Mention the new features, improvements, and bug fixes.
485
+ - Warn about potential breaking changes and vulnerabilities.
486
+ - Tag people and teams responsible for the changes.
487
+ - For the most important commits provide a link to the issue or pull request and
488
+ a URL to that commit, like [commit](https://github.com/{github_repository}/commit/...).
489
+ - Don't guess anything, only use the information from the commits and their changes.
490
+ - Use inline math notation, like $\\beta=1$ or math blocks like the following, when needed:
491
+
492
+ ```math
493
+ \\S_i(A, B, \\alpha, \\beta) = \\alpha \\cdot A_i + \\beta \\cdot B_i
494
+ ```
495
+
496
+ - Use alerting quotes, like: [!CAUTION] or [!TIP], when needed.
497
+ """
498
+ header_message = f"""Commits:
499
+
500
+ {commits_with_hashes}
501
+ """
502
+ changes_messages = [f"#{commit.hash}: {commit.message}\n{change}" for commit, change in zip(commits, changes)]
503
+
504
+ client = OpenAI(base_url=base_url, api_key=api_key)
505
+ response = client.chat.completions.create(
506
+ messages=[
507
+ {"role": "system", "content": prompt},
508
+ {"role": "user", "content": header_message},
509
+ *[{"role": "user", "content": message} for message in changes_messages],
510
+ ],
511
+ max_tokens=2048,
512
+ model=model,
513
+ stream=False,
514
+ )
515
+ return response.choices[0].message.content
332
516
 
333
517
 
334
518
  def bump(
@@ -350,6 +534,9 @@ def bump(
350
534
  push: bool = True,
351
535
  github_token: Optional[str] = None,
352
536
  github_repository: Optional[str] = None,
537
+ openai_base_url: str = "https://api.openai.com",
538
+ openai_api_key: Optional[str] = None,
539
+ openai_model: Optional[str] = None,
353
540
  default_branch: str = "main",
354
541
  create_release: bool = False,
355
542
  ) -> SemVer:
@@ -375,6 +562,9 @@ def bump(
375
562
  push (bool): If True, pushes the changes to the remote GitHub repository. Defaults to True.
376
563
  github_token (Optional[str]): The GitHub token for authentication. If None, it attempts to use the GH_TOKEN environment variable. Defaults to None.
377
564
  github_repository (Optional[str]): The GitHub repository in 'owner/repo' format. If None, it attempts to use the GH_REPOSITORY environment variable. Defaults to None.
565
+ openai_base_url (str): The OpenAI API base URL. Defaults to "https://api.openai.com".
566
+ openai_api_key (Optional[str]): The OpenAI API key. Defaults to None.
567
+ openai_model (Optional[str]): The OpenAI model to use for text generation. Defaults to None.
378
568
  default_branch (str): The default branch to push the changes to. Defaults to "main".
379
569
 
380
570
  Returns:
@@ -490,6 +680,43 @@ def bump(
490
680
  for file_path, regex_pattern in update_patch_version_in:
491
681
  patch_with_regex(file_path, regex_pattern, str(new_version[2]), dry_run=dry_run, verbose=verbose)
492
682
 
683
+ # Now log the potential issues with the commits
684
+ if openai_api_key:
685
+ warnings_commits = []
686
+ warnings = []
687
+ changes = [get_diff_for_commit(repository_path, commit.hash) for commit in commits]
688
+ for commit, change in zip(commits, changes):
689
+ try:
690
+ warning = validate_commit_with_llms(
691
+ base_url=openai_base_url,
692
+ api_key=openai_api_key,
693
+ model=openai_model,
694
+ commit=commit,
695
+ change=change,
696
+ )
697
+ if warning:
698
+ warnings_commits.append(commit)
699
+ warnings.append(warning)
700
+ except Exception as e:
701
+ print(f"Failed to validate commit: {commit.hash} with error: {str(e)}")
702
+ traceback.print_exc()
703
+
704
+ if len(warnings):
705
+ print("## Potential issues")
706
+ for commit, warning in zip(warnings_commits, warnings):
707
+ print(f"- Commit #{commit.hash}: {warning}")
708
+
709
+ release_notes = aggregate_release_notes_with_llms(
710
+ base_url=openai_base_url,
711
+ api_key=openai_api_key,
712
+ model=openai_model,
713
+ github_repository=github_repository,
714
+ commits=commits,
715
+ changes=changes,
716
+ )
717
+ print("## Generated release notes:")
718
+ print(release_notes)
719
+
493
720
  if not dry_run:
494
721
  create_tag(
495
722
  repository_path=repository_path,
@@ -607,6 +834,19 @@ def main():
607
834
  "--github-repository",
608
835
  help="GitHub repository in the 'owner/repo' format, if not set will use GH_REPOSITORY env var",
609
836
  )
837
+ parser.add_argument(
838
+ "--openai-base-url",
839
+ default="https://api.openai.com",
840
+ help="OpenAI API base URL",
841
+ )
842
+ parser.add_argument(
843
+ "--openai-api-key",
844
+ help="OpenAI API key for text generation",
845
+ )
846
+ parser.add_argument(
847
+ "--openai-model",
848
+ help="OpenAI model to use for text generation (e.g., 'text-davinci-003' or 'text-davinci-002')",
849
+ )
610
850
  parser.add_argument(
611
851
  "--create-release",
612
852
  action="store_true",
@@ -654,6 +894,9 @@ def main():
654
894
  args.git_user_email = os.environ.get("TINYSEMVER_GIT_USER_EMAIL", "tinysemver@ashvardanian.com")
655
895
  args.github_token = os.environ.get("GITHUB_TOKEN")
656
896
  args.github_repository = os.environ.get("GITHUB_REPOSITORY")
897
+ args.openai_base_url = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com")
898
+ args.openai_api_key = os.environ.get("OPENAI_API_KEY")
899
+ args.openai_model = os.environ.get("OPENAI_MODEL")
657
900
  args.create_release = os.environ.get("TINYSEMVER_CREATE_RELEASE", "").lower() == "true"
658
901
 
659
902
  # It's common for a CI pipeline to have multiple broken settings or missing files.
@@ -682,6 +925,9 @@ def main():
682
925
  git_user_email=args.git_user_email,
683
926
  github_token=args.github_token,
684
927
  github_repository=args.github_repository,
928
+ openai_base_url=args.openai_base_url,
929
+ openai_api_key=args.openai_api_key,
930
+ openai_model=args.openai_model,
685
931
  push=args.push,
686
932
  create_release=args.create_release,
687
933
  )
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tinysemver
3
- Version: 2.0.8
4
- Summary: Tiny Semantic Versioning (SemVer) library, that doesn't depend on 300K lines of JavaScript
3
+ Version: 2.1.0
4
+ Summary: Semantic-Versioning with LLMs and without 300,000 lines of JS
5
5
  Author-email: Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>, Guillaume de Rouville <31691250+grouville@users.noreply.github.com>
6
6
  License: Apache License
7
7
  Version 2.0, January 2004
@@ -210,7 +210,7 @@ Project-URL: Documentation, https://github.com/ashvardanian/tinysemver/blob/main
210
210
  Project-URL: Repository, https://github.com/ashvardanian/tinysemver.git
211
211
  Project-URL: Issues, https://github.com/ashvardanian/tinysemver/issues
212
212
  Project-URL: Changelog, https://github.com/ashvardanian/tinysemver/blob/main/CHANGELOG.md
213
- Classifier: Development Status :: 4 - Beta
213
+ Classifier: Development Status :: 5 - Production/Stable
214
214
  Classifier: Intended Audience :: Developers
215
215
  Classifier: Topic :: Software Development :: Build Tools
216
216
  Classifier: License :: OSI Approved :: Apache Software License
@@ -223,10 +223,12 @@ Classifier: Programming Language :: Python :: 3.10
223
223
  Classifier: Programming Language :: Python :: 3.11
224
224
  Description-Content-Type: text/markdown
225
225
  License-File: LICENSE
226
+ Requires-Dist: openai>=1.54.4
227
+ Requires-Dist: rich>=13.8.0
226
228
 
227
- ![](https://github.com/ashvardanian/ashvardanian/blob/master/repositories/TinySemVer.jpg)
229
+ ![TinySemVer Banner](https://github.com/ashvardanian/ashvardanian/blob/master/repositories/TinySemVer.jpg)
228
230
 
229
- __TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file.
231
+ __TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file, capable of generating changelogs and release notes with the use of OpenAI-compatible LLM APIs, or without them.
230
232
  In plain English, if your commit messages look like `feat: add new feature` or `fix: bugfix`, this package will automate releasing new "GIT tags" based on the commit messages.
231
233
  Here is how to integrate it into your project CI:
232
234
 
@@ -304,6 +306,19 @@ Alternatively, you can just ask for `--help`:
304
306
  $ tinysemver --help
305
307
  ```
306
308
 
309
+ ## AI and Rock-n-Roll
310
+
311
+ TinySemVer can leverage a language model to validate the commits and generate clean and infromative release notes.
312
+
313
+ ```sh
314
+ $ tinysemver --verbose \
315
+ --github-repository 'ashvardanian/tinysemver' \
316
+ --openai-base-url 'https://api.groq.com/openai/v1' \
317
+ --openai-api-key 'GET_YOURSELF_A_KEY' \
318
+ --openai-model 'llama-3.2-11b-text-preview' \
319
+ --dry-run
320
+ ```
321
+
307
322
  ## Usage Details for the GitHub CI Action
308
323
 
309
324
  TinySemVer can be easily integrated into your GitHub Actions CI pipeline.
@@ -9,4 +9,5 @@ tinysemver.egg-info/PKG-INFO
9
9
  tinysemver.egg-info/SOURCES.txt
10
10
  tinysemver.egg-info/dependency_links.txt
11
11
  tinysemver.egg-info/entry_points.txt
12
+ tinysemver.egg-info/requires.txt
12
13
  tinysemver.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ openai>=1.54.4
2
+ rich>=13.8.0
File without changes
File without changes