Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d904a8b
feat: create jira issue workflow
r-pedraza Feb 24, 2026
7b9cafb
Fix: test_issue_operations.py
r-pedraza Mar 18, 2026
5444d51
Fix: jira_client.py
r-pedraza Mar 19, 2026
35475c5
Fix: issue_operations.py
r-pedraza Mar 19, 2026
fb6a2cc
Fix: input_validation.py
r-pedraza Mar 19, 2026
dfb91ea
Fix: select_issue_priority_step.py
r-pedraza Mar 19, 2026
43b4468
Fix: select_issue_type_step.py
r-pedraza Mar 19, 2026
eb94498
Fix: create_generic_issue_step.py
r-pedraza Mar 19, 2026
4755538
Fix: input_validation.py
r-pedraza Mar 19, 2026
e671fc0
Fix: prompt_issue_description_step.py
r-pedraza Mar 19, 2026
d973713
Fix: select_issue_type_step.py
r-pedraza Mar 19, 2026
24a5d4c
Fix: select_issue_priority_step.py
r-pedraza Mar 19, 2026
9db65a4
Fix: config.toml
r-pedraza Mar 24, 2026
0fcbaf1
Fix: jira_client.py
r-pedraza Mar 24, 2026
8c43a6d
Fix: issue_operations.py
r-pedraza Mar 24, 2026
3fbff32
Merge branch 'master' into feat/crerate-issue-jira-core
r-pedraza Mar 26, 2026
f3bef1c
fix: Return class not a String
r-pedraza Mar 26, 2026
6dbc07a
Fix: create_branch_step.py
r-pedraza Mar 26, 2026
3b4bea3
Fix: custom-templates.md
r-pedraza Mar 26, 2026
f0c9d69
Fix: test_issue_operations.py
r-pedraza Mar 26, 2026
22ca7bc
Fix: issue_service.py
r-pedraza Mar 26, 2026
31f2067
Fix: metadata_service.py
r-pedraza Mar 26, 2026
759c48c
Fix: jira_client.py
r-pedraza Mar 26, 2026
dfef61f
Fix: jira_client.py
r-pedraza Mar 27, 2026
2e3c65d
Fix: jira_client.py
r-pedraza Mar 27, 2026
d7de2ba
fix: issue operations
r-pedraza Mar 27, 2026
207c8ed
fix: issue services
r-pedraza Mar 27, 2026
b861d1c
fix(jira): move business logic from Client to Service
r-pedraza Mar 27, 2026
ccf3519
style(jira): replace markdown() with bold_text() for simple headers
r-pedraza Mar 27, 2026
58fe9cb
Merge branch 'master' into feat/crerate-issue-jira-core
r-pedraza Mar 27, 2026
4fd6d68
refactor(jira): add JiraPriority StrEnum and remove docstring examples
r-pedraza Mar 27, 2026
e7c30f6
fix(jira): remove unnecessary TYPE_CHECKING and quoted type annotations
r-pedraza Mar 27, 2026
55d9084
refactor(jira): replace dict constants with StrEnum for type safety
r-pedraza Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 70 additions & 45 deletions plugins/titan-plugin-git/titan_plugin_git/steps/create_branch_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
Create a new Git branch.
"""

import traceback

from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
from titan_plugin_git.exceptions import GitError
from titan_cli.core.result import ClientSuccess, ClientError
from ..operations import (
check_branch_exists,
determine_safe_checkout_target,
Expand Down Expand Up @@ -54,46 +56,63 @@ def create_branch_step(ctx: WorkflowContext) -> WorkflowResult:
ctx.textual.dim_text(f"From: {start_point}")

# Check if branch exists using operations
all_branches = ctx.git.get_branches()
branch_names = [b.name for b in all_branches]
branch_exists = check_branch_exists(new_branch, branch_names)
branches_result = ctx.git.get_branches()

match branches_result:
case ClientSuccess(data=all_branches):
branch_names = [b.name for b in all_branches]
branch_exists = check_branch_exists(new_branch, branch_names)
case ClientError(error_message=err):
ctx.textual.error_text(f"Failed to get branches: {err}")
ctx.textual.end_step("error")
return Error(f"Failed to get branches: {err}")

# Delete if exists and requested using operations
if should_delete_before_create(branch_exists, delete_if_exists):
ctx.textual.text("")
ctx.textual.warning_text(f"Branch {new_branch} exists, deleting...")

# If we're on the branch, checkout another one first using operations
current_branch = ctx.git.get_current_branch()
safe_target = determine_safe_checkout_target(
current_branch=current_branch,
branch_to_delete=new_branch,
main_branch=ctx.git.main_branch,
all_branches=branch_names
)

if safe_target:
try:
ctx.git.checkout(safe_target)
ctx.textual.dim_text(f"Switched to {safe_target}")
except GitError as e:
ctx.textual.error_text(f"Failed to checkout {safe_target}: {str(e)}")
current_branch_result = ctx.git.get_current_branch()

match current_branch_result:
case ClientSuccess(data=current_branch):
safe_target = determine_safe_checkout_target(
current_branch=current_branch,
branch_to_delete=new_branch,
main_branch=ctx.git.main_branch,
all_branches=branch_names
)

if safe_target:
checkout_result = ctx.git.checkout(safe_target)
match checkout_result:
case ClientSuccess():
ctx.textual.dim_text(f"Switched to {safe_target}")
case ClientError(error_message=err):
ctx.textual.error_text(f"Failed to checkout {safe_target}: {err}")
ctx.textual.end_step("error")
return Error(f"Cannot checkout {safe_target}: {err}")
elif current_branch == new_branch:
# Cannot delete current branch and no safe target available
ctx.textual.error_text(f"Cannot delete current branch {new_branch}")
ctx.textual.end_step("error")
return Error("Cannot delete current branch")

# Delete the branch
delete_result = ctx.git.safe_delete_branch(new_branch, force=True)
match delete_result:
case ClientSuccess():
ctx.textual.success_text(f"βœ“ Deleted existing branch {new_branch}")
case ClientError(error_message=err):
ctx.textual.error_text(f"Failed to delete {new_branch}: {err}")
ctx.textual.end_step("error")
return Error(f"Failed to delete branch: {err}")

case ClientError(error_message=err):
ctx.textual.error_text(f"Failed to get current branch: {err}")
ctx.textual.end_step("error")
return Error(f"Cannot checkout {safe_target}: {str(e)}")
elif current_branch == new_branch:
# Cannot delete current branch and no safe target available
ctx.textual.error_text(f"Cannot delete current branch {new_branch}")
ctx.textual.end_step("error")
return Error("Cannot delete current branch")

# Delete the branch
try:
ctx.git.safe_delete_branch(new_branch, force=True)
ctx.textual.success_text(f"βœ“ Deleted existing branch {new_branch}")
except GitError as e:
ctx.textual.error_text(f"Failed to delete {new_branch}: {str(e)}")
ctx.textual.end_step("error")
return Error(f"Failed to delete branch: {str(e)}")
return Error(f"Failed to get current branch: {err}")

elif branch_exists:
ctx.textual.error_text(f"Branch {new_branch} already exists")
Expand All @@ -103,21 +122,23 @@ def create_branch_step(ctx: WorkflowContext) -> WorkflowResult:

# Create the branch
ctx.textual.text("")
try:
ctx.git.create_branch(new_branch, start_point=start_point)
ctx.textual.success_text(f"βœ“ Created branch {new_branch}")
except GitError as e:
ctx.textual.error_text(f"Failed to create {new_branch}: {str(e)}")
ctx.textual.end_step("error")
return Error(f"Failed to create branch: {str(e)}")
create_result = ctx.git.create_branch(new_branch, start_point=start_point)
match create_result:
case ClientSuccess():
ctx.textual.success_text(f"βœ“ Created branch {new_branch}")
case ClientError(error_message=err):
ctx.textual.error_text(f"Failed to create {new_branch}: {err}")
ctx.textual.end_step("error")
return Error(f"Failed to create branch: {err}")

# Checkout if requested
if checkout:
try:
ctx.git.checkout(new_branch)
ctx.textual.success_text(f"βœ“ Checked out {new_branch}")
except GitError as e:
ctx.textual.warning_text(f"Branch created but failed to checkout: {str(e)}")
checkout_result = ctx.git.checkout(new_branch)
match checkout_result:
case ClientSuccess():
ctx.textual.success_text(f"βœ“ Checked out {new_branch}")
case ClientError(error_message=err):
ctx.textual.warning_text(f"Branch created but failed to checkout: {err}")

ctx.textual.text("")
ctx.textual.end_step("success")
Expand All @@ -131,9 +152,13 @@ def create_branch_step(ctx: WorkflowContext) -> WorkflowResult:
)

except Exception as e:
tb = traceback.format_exc()
ctx.textual.text("")
ctx.textual.error_text(f"Failed to create branch: {str(e)}")
ctx.textual.text("")
ctx.textual.dim_text("Full traceback:")
for line in tb.split('\n'):
ctx.textual.dim_text(line)
ctx.textual.end_step("error")
return Error(f"Failed to create branch: {str(e)}")

Expand Down
167 changes: 167 additions & 0 deletions plugins/titan-plugin-jira/docs/custom-templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Custom Templates for Issues

The **Create JIRA Issue** workflow allows using custom templates to generate issue descriptions.

## Template Locations

### Project Template (Recommended)

Create your custom template at:

```
.titan/templates/issue_templates/default.md.j2
```

This template will be automatically used when you run the workflow.

### Plugin Default Template

If no project template exists, the plugin's default template is used:

```
plugins/titan-plugin-jira/titan_plugin_jira/config/templates/generic_issue.md.j2
```

## Template Format

Templates use **Jinja2** and receive the following variables from the AI:

| Variable | Type | Description |
|----------|------|-------------|
| `description` | string | Extended task description |
| `objective` | string | Issue objective |
| `acceptance_criteria` | string | Acceptance criteria (checkboxes) |
| `technical_notes` | string or None | Technical notes (optional) |
| `dependencies` | string or None | Dependencies (optional) |

## Custom Template Example

```jinja2
## πŸ“‹ Description

{{ description }}

## 🎯 Objective

{{ objective }}

## βœ… Acceptance Criteria

{{ acceptance_criteria }}

{% if technical_notes %}
---

### πŸ”§ Technical Notes

{{ technical_notes }}
{% endif %}

{% if dependencies %}
---

### πŸ”— Dependencies

{{ dependencies }}
{% endif %}

---

*Created with Titan CLI*
```

## Creating Your Custom Template

1. **Create the directory** (if it doesn't exist):

```bash
mkdir -p .titan/templates/issue_templates
```

2. **Create the template**:

```bash
cat > .titan/templates/issue_templates/default.md.j2 << 'EOF'
## Description

{{ description }}

## Objective

{{ objective }}

## Acceptance Criteria

{{ acceptance_criteria }}

{% if technical_notes %}
### Technical Notes

{{ technical_notes }}
{% endif %}
EOF
```

3. **Run the workflow**:

The workflow will automatically detect and use your template.

## Tips

- **Use Markdown**: Templates support full Markdown
- **Optional sections**: Use `{% if variable %}` for conditional content
- **Clean format**: The AI generates the content, your template structures it
- **Emojis**: Add emojis for better readability (optional)
- **Commits**: Version your template with Git to share it with the team

## Advanced Example: Template with QA Checklist

```jinja2
## πŸ“‹ Description

{{ description }}

## 🎯 Objective

{{ objective }}

## βœ… Acceptance Criteria

{{ acceptance_criteria }}

{% if technical_notes %}
---

### πŸ”§ Implementation

{{ technical_notes }}
{% endif %}

{% if dependencies %}
---

### πŸ”— Dependencies

{{ dependencies }}
{% endif %}

---

## πŸ§ͺ QA Checklist

- [ ] Unit tests implemented
- [ ] Integration tests passing
- [ ] Documentation updated
- [ ] Code review approved
- [ ] Works in staging

---

*Automatically generated by Titan CLI*
```

## Hooks and Extensibility

This workflow is extensible via hooks in Titan. You can add custom steps before or after any workflow step.

Refer to Titan documentation for more information about hooks.
Loading