ticket2pr 0.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ben Gabay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: ticket2pr
3
+ Version: 0.1.0
4
+ Summary: Automate Jira ticket to GitHub PR workflow
5
+ Home-page: https://github.com/bengabay11/ticket2pr
6
+ Author: Ben Gabay
7
+ Author-email: ben.gabay38@gmail.com
8
+ Requires-Python: >=3.13
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: claude-agent-sdk>=0.1.25
12
+ Requires-Dist: colorlog>=6.9.0
13
+ Requires-Dist: gitpython>=3.1.46
14
+ Requires-Dist: jira>=3.10.5
15
+ Requires-Dist: pydantic>=2.11.5
16
+ Requires-Dist: pydantic-settings>=2.9.1
17
+ Requires-Dist: pygithub>=2.8.1
18
+ Requires-Dist: python-dotenv>=1.1.0
19
+ Requires-Dist: rich>=14.0.0
20
+ Requires-Dist: tomli-w>=1.2.0
21
+ Requires-Dist: typer>=0.21.1
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: home-page
25
+ Dynamic: license-file
26
+ Dynamic: requires-python
27
+
28
+ # Ticket2PR
29
+
30
+ Ticket2PR is an AI-powered automation tool designed to streamline the process of converting development tickets into ready-to-merge pull requests. It integrates with Jira and GitHub to automate tasks such as branch creation, commit message generation, code linting fixes, and pull request content generation, significantly reducing manual effort and accelerating development workflows.
31
+
32
+ ## What's Included
33
+
34
+ Ticket2PR provides a comprehensive set of features to automate your development workflow:
35
+
36
+ - **🚀 Automated Workflow:** Orchestrates the entire process from ticket to pull request, handling branch creation, commit message generation, and PR content.
37
+ - **🤖 AI-Powered Agents:** Utilizes specialized agents for tasks like crafting intelligent commit messages, automatically fixing pre-commit issues, and assisting with ticket resolution.
38
+ - **🔗 Jira Integration:** Connects with Jira to fetch ticket details, enabling context-aware automation.
39
+ - **🐙 GitHub Integration:** Interacts with GitHub for branch management, pull request creation, and status updates.
40
+
41
+ ## Prerequisites
42
+
43
+ - **Jira Account and API Token:** An account with access to a Jira instance and a valid API token with necessary permissions to view tickets.
44
+ - **GitHub Account and Personal Access Token:** A GitHub account with permissions to create branches and pull requests in your target repository, and a [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) with `repo` scope for GitHub API access.
45
+ - **Claude Code API Token:** Obtain an API token for Claude Code for AI-powered code assistance.
46
+
47
+ ## Getting Started
48
+
49
+ Follow these steps to set up and start using Ticket2PR:
50
+
51
+ ### Installation
52
+
53
+ You can install `ticket2pr` directly from PyPI:
54
+
55
+ ```sh
56
+ pip install ticket2pr
57
+ ```
58
+
59
+ ### Configuration
60
+
61
+ Ticket2PR automatically guides you through the initial configuration process the first time you run the CLI.
62
+
63
+ To re-initialize the interactive configuration session, run:
64
+
65
+ ```sh
66
+ ticket2pr init
67
+ ```
68
+
69
+ Alternatively, you can manually configure settings by editing the `~/.ticket2pr/config.toml` file or by setting environment variables.
70
+
71
+ ### Usage
72
+
73
+ Run the Ticket2PR CLI to create a pull request from a Jira ticket:
74
+
75
+ ```sh
76
+ ticket2pr run <JIRA_ISSUE_KEY>
77
+ ```
78
+
79
+ Replace `<JIRA_ISSUE_KEY>` with the actual ID of your Jira ticket (e.g., `PROJ-123`).
80
+
81
+ ## License
82
+
83
+ This project is open-sourced under the terms of the [LICENSE](LICENSE).
@@ -0,0 +1,56 @@
1
+ # Ticket2PR
2
+
3
+ Ticket2PR is an AI-powered automation tool designed to streamline the process of converting development tickets into ready-to-merge pull requests. It integrates with Jira and GitHub to automate tasks such as branch creation, commit message generation, code linting fixes, and pull request content generation, significantly reducing manual effort and accelerating development workflows.
4
+
5
+ ## What's Included
6
+
7
+ Ticket2PR provides a comprehensive set of features to automate your development workflow:
8
+
9
+ - **🚀 Automated Workflow:** Orchestrates the entire process from ticket to pull request, handling branch creation, commit message generation, and PR content.
10
+ - **🤖 AI-Powered Agents:** Utilizes specialized agents for tasks like crafting intelligent commit messages, automatically fixing pre-commit issues, and assisting with ticket resolution.
11
+ - **🔗 Jira Integration:** Connects with Jira to fetch ticket details, enabling context-aware automation.
12
+ - **🐙 GitHub Integration:** Interacts with GitHub for branch management, pull request creation, and status updates.
13
+
14
+ ## Prerequisites
15
+
16
+ - **Jira Account and API Token:** An account with access to a Jira instance and a valid API token with necessary permissions to view tickets.
17
+ - **GitHub Account and Personal Access Token:** A GitHub account with permissions to create branches and pull requests in your target repository, and a [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) with `repo` scope for GitHub API access.
18
+ - **Claude Code API Token:** Obtain an API token for Claude Code for AI-powered code assistance.
19
+
20
+ ## Getting Started
21
+
22
+ Follow these steps to set up and start using Ticket2PR:
23
+
24
+ ### Installation
25
+
26
+ You can install `ticket2pr` directly from PyPI:
27
+
28
+ ```sh
29
+ pip install ticket2pr
30
+ ```
31
+
32
+ ### Configuration
33
+
34
+ Ticket2PR automatically guides you through the initial configuration process the first time you run the CLI.
35
+
36
+ To re-initialize the interactive configuration session, run:
37
+
38
+ ```sh
39
+ ticket2pr init
40
+ ```
41
+
42
+ Alternatively, you can manually configure settings by editing the `~/.ticket2pr/config.toml` file or by setting environment variables.
43
+
44
+ ### Usage
45
+
46
+ Run the Ticket2PR CLI to create a pull request from a Jira ticket:
47
+
48
+ ```sh
49
+ ticket2pr run <JIRA_ISSUE_KEY>
50
+ ```
51
+
52
+ Replace `<JIRA_ISSUE_KEY>` with the actual ID of your Jira ticket (e.g., `PROJ-123`).
53
+
54
+ ## License
55
+
56
+ This project is open-sourced under the terms of the [LICENSE](LICENSE).
@@ -0,0 +1,35 @@
1
+ [project]
2
+ name = "ticket2pr"
3
+ version = "0.1.0"
4
+ description = "Automate Jira ticket to GitHub PR workflow"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "claude-agent-sdk>=0.1.25",
9
+ "colorlog>=6.9.0",
10
+ "gitpython>=3.1.46",
11
+ "jira>=3.10.5",
12
+ "pydantic>=2.11.5",
13
+ "pydantic-settings>=2.9.1",
14
+ "pygithub>=2.8.1",
15
+ "python-dotenv>=1.1.0",
16
+ "rich>=14.0.0",
17
+ "tomli-w>=1.2.0",
18
+ "typer>=0.21.1",
19
+ ]
20
+
21
+ [project.scripts]
22
+ ticket2pr = "src.cli:cli_main"
23
+
24
+ [dependency-groups]
25
+ dev = [
26
+ "absolufy-imports>=0.3.1",
27
+ "bandit>=1.8.3",
28
+ "codespell>=2.4.1",
29
+ "mypy>=1.16.0",
30
+ "pre-commit>=4.2.0",
31
+ "pydantic[mypy]>=2.11.5",
32
+ "pytest>=8.3.5",
33
+ "pytest-cases>=3.8.6",
34
+ "ruff>=0.11.12",
35
+ ]
@@ -0,0 +1,10 @@
1
+ [bdist_wheel]
2
+ universal = 1
3
+
4
+ [metadata]
5
+ license_file = LICENSE
6
+
7
+ [egg_info]
8
+ tag_build =
9
+ tag_date = 0
10
+
@@ -0,0 +1,60 @@
1
+ """Setup script for ticket2pr package."""
2
+
3
+ from setuptools import find_packages, setup
4
+
5
+ # Read the long description from README
6
+ with open("README.md", encoding="utf-8") as f:
7
+ long_description = f.read()
8
+
9
+ setup(
10
+ name="ticket2pr",
11
+ version="0.1.0",
12
+ author="Ben Gabay",
13
+ author_email="ben.gabay38@gmail.com",
14
+ description="Automate Jira ticket to GitHub PR workflow",
15
+ long_description=long_description,
16
+ long_description_content_type="text/markdown",
17
+ url="https://github.com/bengabay11/ticket2pr",
18
+ packages=find_packages(),
19
+ classifiers=[
20
+ "Development Status :: 3 - Alpha",
21
+ "Intended Audience :: Developers",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Operating System :: OS Independent",
26
+ ],
27
+ python_requires=">=3.13",
28
+ install_requires=[
29
+ "claude-agent-sdk>=0.1.25",
30
+ "colorlog>=6.9.0",
31
+ "gitpython>=3.1.46",
32
+ "jira>=3.10.5",
33
+ "pydantic>=2.11.5",
34
+ "pydantic-settings>=2.9.1",
35
+ "pygithub>=2.8.1",
36
+ "python-dotenv>=1.1.0",
37
+ "rich>=14.0.0",
38
+ "tomli-w>=1.2.0",
39
+ "typer>=0.21.1",
40
+ ],
41
+ extras_require={
42
+ "dev": [
43
+ "absolufy-imports>=0.3.1",
44
+ "bandit>=1.8.3",
45
+ "codespell>=2.4.1",
46
+ "mypy>=1.16.0",
47
+ "pre-commit>=4.2.0",
48
+ "pytest>=8.3.5",
49
+ "pytest-cases>=3.8.6",
50
+ "ruff>=0.11.12",
51
+ ],
52
+ },
53
+ entry_points={
54
+ "console_scripts": [
55
+ "ticket2pr=src.cli:cli_main",
56
+ ],
57
+ },
58
+ include_package_data=True,
59
+ zip_safe=False,
60
+ )
File without changes
@@ -0,0 +1,49 @@
1
+ import re
2
+
3
+ from src.clients.github_client import GitHubClient
4
+ from src.clients.jira_client import JiraClient, JiraIssue
5
+
6
+
7
+ def sanitize_branch_name(name: str, max_length: int = 100) -> str:
8
+ name = name.lower()
9
+ # Replace any character that's not a lowercase letter, digit, or hyphen with a hyphen
10
+ name = re.sub(r"[^a-z0-9-]", "-", name)
11
+ # Replace one or more consecutive hyphens with a single hyphen
12
+ name = re.sub(r"-+", "-", name)
13
+ name = name.strip("-")
14
+ if len(name) > max_length:
15
+ name = name[:max_length].rstrip("-")
16
+ return name
17
+
18
+
19
+ def generate_branch_name(
20
+ issue_key: str,
21
+ issue_summary: str,
22
+ issue_type: str | None = None,
23
+ max_length: int = 255,
24
+ ) -> str:
25
+ sanitized_summary = sanitize_branch_name(issue_summary)
26
+ branch_name = f"{issue_key}-{sanitized_summary}"
27
+ if issue_type:
28
+ branch_name = f"{issue_type.lower()}/{branch_name}"
29
+
30
+ if len(branch_name) > max_length:
31
+ branch_name = branch_name[:max_length].rstrip("-")
32
+
33
+ return branch_name
34
+
35
+
36
+ def create_branch_from_jira_issue(
37
+ jira_issue: JiraIssue,
38
+ jira_client: JiraClient,
39
+ github_client: GitHubClient,
40
+ base_branch: str = "main",
41
+ ) -> str:
42
+ branch_name = generate_branch_name(jira_issue.key, jira_issue.summary, jira_issue.type)
43
+ # TODO: Consider replacing the 2 next lines with local git client
44
+ base_ref = github_client.get_base_branch_ref(base_branch)
45
+ branch_url = github_client.create_branch(branch_name, base_ref)
46
+
47
+ jira_client.link_branch(jira_issue.key, branch_url, branch_name)
48
+
49
+ return branch_name
@@ -0,0 +1,211 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING
7
+
8
+ import typer
9
+
10
+ from src.console_utils import (
11
+ format_dim,
12
+ format_success_with_checkmark,
13
+ format_yellow,
14
+ get_status,
15
+ print_empty_line,
16
+ print_error,
17
+ print_error_inline,
18
+ print_info,
19
+ print_label_value,
20
+ print_success,
21
+ print_warning,
22
+ )
23
+ from src.enhanced_git import EnhancedGit
24
+ from src.logging_setup import LoggerHandlerType, SetupLoggerParams, setup_logger
25
+ from src.settings import AppSettings
26
+ from src.shell.claude_auth_status import is_claude_logged_in
27
+
28
+ if TYPE_CHECKING:
29
+ from src.clients.github_client import GitHubClient
30
+ from src.clients.jira_client import JiraClient
31
+
32
+ app = typer.Typer(
33
+ name="ticket2pr",
34
+ help="Automate Jira ticket to GitHub PR workflow",
35
+ add_completion=False,
36
+ rich_markup_mode="rich",
37
+ context_settings={"help_option_names": ["-h", "--help"]},
38
+ )
39
+
40
+
41
+ def _load_settings() -> AppSettings:
42
+ from dotenv import load_dotenv
43
+
44
+ load_dotenv()
45
+ try:
46
+ return AppSettings()
47
+ except Exception as e:
48
+ print_error_inline(f"loading settings: {e}")
49
+ sys.exit(1)
50
+
51
+
52
+ def _initialize_clients(settings: AppSettings) -> tuple[GitHubClient, JiraClient, EnhancedGit]:
53
+ from src.clients.github_client import GitHubClient
54
+ from src.clients.jira_client import JiraClient
55
+ from src.enhanced_git import EnhancedGit
56
+
57
+ github_client = GitHubClient(
58
+ github_token=settings.github.api_token,
59
+ repo_full_name=settings.github.repo_full_name,
60
+ )
61
+ jira_client = JiraClient(
62
+ url=settings.jira.base_url,
63
+ username=settings.jira.username,
64
+ password=settings.jira.api_token,
65
+ )
66
+ git = EnhancedGit(settings.core.workspace_path)
67
+ return github_client, jira_client, git
68
+
69
+
70
+ def _init() -> None:
71
+ from src.settings import DEFAULT_CONFIG_DIR
72
+ from src.settings_init import initialize_settings
73
+
74
+ config_path = DEFAULT_CONFIG_DIR / "config.toml"
75
+ initialize_settings(config_path)
76
+
77
+
78
+ async def workflow_with_prints(
79
+ jira_issue_key: str,
80
+ workspace_path: Path,
81
+ base_branch: str,
82
+ github_client: GitHubClient,
83
+ jira_client: JiraClient,
84
+ local_git: EnhancedGit,
85
+ mcp_config_path: Path | None = None,
86
+ ) -> None:
87
+ header_msg = f"Running workflow for {format_yellow(jira_issue_key)}"
88
+ print_info(header_msg)
89
+ print_label_value("Workspace", workspace_path)
90
+ print_label_value("Base branch", base_branch)
91
+ print_label_value("Github repository", github_client.repo.full_name)
92
+ print_empty_line()
93
+
94
+ from src.workflow import workflow
95
+
96
+ result = await workflow(
97
+ github_client=github_client,
98
+ jira_client=jira_client,
99
+ jira_issue_key=jira_issue_key,
100
+ git=local_git,
101
+ base_branch=base_branch,
102
+ mcp_config_path=mcp_config_path,
103
+ )
104
+
105
+ success_msg = format_success_with_checkmark("Workflow completed successfully!")
106
+ issue_msg = format_dim(f"Issue: {result.jira_issue_permalink}")
107
+ pr_msg = format_dim(f"Pull Request: {result.pr_url}")
108
+ branch_msg = format_dim(f"Branch: {result.branch_name}")
109
+ base_branch_msg = format_dim(f"Base Branch: {base_branch}")
110
+
111
+ final_msg = "\n".join([success_msg, issue_msg, pr_msg, branch_msg, base_branch_msg])
112
+ print_success(final_msg)
113
+
114
+
115
+ @app.command()
116
+ def run(
117
+ jira_issue_key: str = typer.Argument(..., help="Jira issue key (e.g., PROJ-123)"),
118
+ workspace_path: Path | None = typer.Option( # noqa: B008
119
+ None, "--workspace-path", "-w", help="Workspace path (overrides settings)"
120
+ ),
121
+ base_branch: str | None = typer.Option(
122
+ None, "--base-branch", "-b", help="Base branch (overrides settings)"
123
+ ),
124
+ mcp_config_path: Path | None = typer.Option( # noqa: B008
125
+ None, "--mcp-config-path", "-m", help="Path to mcp.json config file for Claude agents"
126
+ ),
127
+ ) -> None:
128
+ """Execute the workflow for a specific Jira ticket."""
129
+
130
+ settings = _load_settings()
131
+
132
+ if not is_claude_logged_in():
133
+ hint = "Run /login or set the 'ANTHROPIC_API_KEY' environment variable."
134
+ message = "Claude Code authentication not found."
135
+ print_error(message)
136
+ print_label_value("Hint", hint)
137
+ sys.exit(1)
138
+
139
+ final_workspace_path = workspace_path or settings.core.workspace_path
140
+ final_base_branch = base_branch or settings.core.base_branch
141
+
142
+ setup_logger(
143
+ SetupLoggerParams(
144
+ level=settings.logging.min_log_level,
145
+ handler_types={LoggerHandlerType.STREAM, LoggerHandlerType.FILE},
146
+ file_path=settings.logging.log_file_path,
147
+ )
148
+ )
149
+
150
+ with get_status("Initializing clients...", spinner="dots"):
151
+ try:
152
+ github_client, jira_client, local_git = _initialize_clients(settings)
153
+ except Exception as e:
154
+ print_error(str(e))
155
+ sys.exit(1)
156
+
157
+ try:
158
+ asyncio.run(
159
+ workflow_with_prints(
160
+ jira_issue_key,
161
+ final_workspace_path,
162
+ final_base_branch,
163
+ github_client,
164
+ jira_client,
165
+ local_git,
166
+ mcp_config_path,
167
+ )
168
+ )
169
+ except KeyboardInterrupt:
170
+ print_empty_line()
171
+ print_warning("Workflow interrupted by user")
172
+ sys.exit(1)
173
+ except Exception as e:
174
+ print_error(str(e), title="Error")
175
+ sys.exit(1)
176
+
177
+
178
+ @app.command()
179
+ def init() -> None:
180
+ """Initialize settings configuration."""
181
+ _init()
182
+
183
+
184
+ @app.command(name="help")
185
+ def help_command(ctx: typer.Context) -> None:
186
+ """Show help information."""
187
+ typer.echo(ctx.find_root().get_help())
188
+
189
+
190
+ def settings_exist() -> bool:
191
+ config_file_path = Path.home() / ".ticket2pr" / "config.toml"
192
+ return config_file_path.exists()
193
+
194
+
195
+ @app.callback(invoke_without_command=True)
196
+ def main(ctx: typer.Context) -> None:
197
+ if not settings_exist():
198
+ _init()
199
+ sys.exit(0)
200
+
201
+ if ctx.invoked_subcommand is None:
202
+ typer.echo(ctx.get_help())
203
+
204
+
205
+ def cli_main() -> None:
206
+ """Entry point for the CLI."""
207
+ app()
208
+
209
+
210
+ if __name__ == "__main__":
211
+ cli_main()