feat: Add foundation for Jira issue creation workflow#166
feat: Add foundation for Jira issue creation workflow#166
Conversation
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension. |
plugins/titan-plugin-jira/titan_plugin_jira/steps/ai_enhance_issue_description_step.py
Show resolved
Hide resolved
plugins/titan-plugin-jira/tests/operations/test_issue_operations.py
Outdated
Show resolved
Hide resolved
plugins/titan-plugin-jira/titan_plugin_jira/clients/jira_client.py
Outdated
Show resolved
Hide resolved
|
|
||
| def find_ready_to_dev_transition( | ||
| jira_client: "JiraClient", issue_key: str | ||
| ) -> ClientResult["UITransition"]: |
There was a problem hiding this comment.
This should not return the ClientResult, just the UITransition
|
|
||
| def transition_issue_to_ready_for_dev( | ||
| jira_client: "JiraClient", issue_key: str | ||
| ) -> ClientResult[None]: |
There was a problem hiding this comment.
ClientResult, should not be returned in operations. It's just to Client/Service. The step that calls this should call try/except
There was a problem hiding this comment.
Already fixed. The function now returns None and raises exceptions instead of returning ClientResult[None], following the operations layer pattern.
There was a problem hiding this comment.
Already resolved. Function returns UITransition (UI model) and raises exceptions, following the operations pattern correctly.
plugins/titan-plugin-jira/titan_plugin_jira/utils/input_validation.py
Outdated
Show resolved
Hide resolved
plugins/titan-plugin-jira/titan_plugin_jira/steps/select_issue_priority_step.py
Outdated
Show resolved
Hide resolved
|
|
||
| selected_type = issue_types[index] | ||
|
|
||
| if not selected_type: |
There was a problem hiding this comment.
It should never be false here
| match transition_result: | ||
| case ClientSuccess(): | ||
| # Get transition details to show user | ||
| find_result = ctx.jira.get_transitions(issue_key) |
There was a problem hiding this comment.
You are calling the api twice. In line 128 you have already called the api. You need to add the necessary data to the clientSuccess.message, or in the data model
| value = int(selection) | ||
| index = value - 1 | ||
|
|
||
| if index < 0 or index >= max_value: |
There was a problem hiding this comment.
You should validate min_value as well
plugins/titan-plugin-jira/titan_plugin_jira/steps/prompt_issue_description_step.py
Outdated
Show resolved
Hide resolved
plugins/titan-plugin-jira/titan_plugin_jira/steps/select_issue_type_step.py
Outdated
Show resolved
Hide resolved
| """ | ||
| ctx.textual.begin_step(StepTitles.PRIORITY) | ||
|
|
||
| ctx.textual.markdown("## 🔥 Priority") |
There was a problem hiding this comment.
These should not be a markdown. Check the rest of the steps, cause the title of something's should not be a markdown,
finxo
left a comment
There was a problem hiding this comment.
Also testing the PR I found this bug:
────────────────────────────────────────────────────────────────────────────────
SESSION START 2026-03-02 10:14:41 UTC PID 85720
────────────────────────────────────────────────────────────────────────────────
{"version": "0.1.11", "mode": "development", "log_level": "WARNING", "pid": 85720, "log_file": "/home/alex/.local/state/titan/logs/titan.log", "event": "session_started", "level": "info", "logger": "titan", "timestamp": "2026-03-02T10:14:41.119017Z"}
{"name": "git", "event": "plugin_initialized", "level": "info", "logger": "titan_cli.core.plugins.plugin_registry", "timestamp": "2026-03-02T10:14:41.150177Z"}
{"name": "jira", "event": "plugin_initialized", "level": "info", "logger": "titan_cli.core.plugins.plugin_registry", "timestamp": "2026-03-02T10:14:41.226858Z"}
{"name": "github", "event": "plugin_initialized", "level": "info", "logger": "titan_cli.core.plugins.plugin_registry", "timestamp": "2026-03-02T10:14:41.591243Z"}
{"message": "Status retrieved", "result_type": "UIGitStatus", "duration": 0.015, "event": "get_status_success", "level": "info", "logger": "titan_plugin_git.clients.services.status_service", "timestamp": "2026-03-02T10:14:41.623199Z"}
{"message": "Status retrieved", "result_type": "UIGitStatus", "duration": 0.022, "event": "get_status_success", "level": "info", "logger": "titan_plugin_git.clients.services.status_service", "timestamp": "2026-03-02T10:14:42.668828Z"}
{"message": "Status retrieved", "result_type": "UIGitStatus", "duration": 0.021, "event": "get_status_success", "level": "info", "logger": "titan_plugin_git.clients.services.status_service", "timestamp": "2026-03-02T10:14:45.829840Z"}
{"message": "Status retrieved", "result_type": "UIGitStatus", "duration": 0.021, "event": "get_status_success", "level": "info", "logger": "titan_plugin_git.clients.services.status_service", "timestamp": "2026-03-02T10:15:00.475315Z"}
{"workflow": "Create Jira Issue", "source": "plugin", "total_steps": 7, "is_nested": false, "event": "workflow_started", "level": "info", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:00.581044Z"}
{"workflow": "Create Jira Issue", "step_id": "description", "message": "Brief description captured: 87 characters", "duration": 16.898, "event": "step_success", "level": "info", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:17.485003Z"}
{"message": "Found 21 issue types", "result_type": "list", "duration": 0.164, "event": "get_issue_types_success", "level": "info", "logger": "titan_plugin_jira.clients.services.metadata_service", "timestamp": "2026-03-02T10:15:17.657802Z"}
{"workflow": "Create Jira Issue", "step_id": "issue_type", "message": "Issue type selected: Epic", "duration": 13.333, "event": "step_success", "level": "info", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:30.846453Z"}
{"workflow": "Create Jira Issue", "step_id": "priority", "message": "Priority selected: Highest", "duration": 10.62, "event": "step_success", "level": "info", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:41.478548Z"}
{"event": "HTTP Request: POST https://llm.tools.cloud.masorange.es/v1/messages \"HTTP/1.1 200 OK\"", "timestamp": "2026-03-02T10:15:58.747430Z"}
{"workflow": "Create Jira Issue", "step_id": "generate_with_ai", "error": "Error executing step 'ai_enhance_issue_description' from plugin 'jira': object of type 'NoneType' has no len()", "on_error": "fail", "duration": 17.276, "event": "step_failed", "level": "error", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:58.755240Z"}
{"workflow": "Create Jira Issue", "failed_at_step": "generate_with_ai", "error": "Error executing step 'ai_enhance_issue_description' from plugin 'jira': object of type 'NoneType' has no len()", "steps_completed": 4, "duration": 58.186, "event": "workflow_failed", "level": "error", "logger": "titan_cli.ui.tui.textual_workflow_executor", "timestamp": "2026-03-02T10:15:58.761589Z"}
● Encontré el bug. La cadena del problema:
1. _parse_ai_response inicializa "title": ""
2. En el cleanup final: si queda vacío, lo pone a None → sections["title"] = None
3. parsed.pop("title", DEFAULT_TITLE) devuelve None (la clave existe pero vale None — el default solo aplica si la clave no existe)
4. len(None) → TypeError
By: finxo
By: finxo
By: finxo
By: finxo
By: finxo
By: finxo
By: finxo
By: finxo
By: finxo
By: finxo
By: finxo
|
|
| ) | ||
|
|
||
| except Exception as e: | ||
| import traceback |
There was a problem hiding this comment.
imports goes up in the file not here
| @@ -0,0 +1,167 @@ | |||
| # Plantillas Personalizadas para Issues | |||
| result = transition_issue_to_ready_for_dev(mock_client, "TEST-123") | ||
|
|
||
| # Assert | ||
| assert result is None |
There was a problem hiding this comment.
The function transition_issue_to_ready_to_dev returns UIJiraTransition not none. This is wrong test
| result = transition_issue_to_ready_for_dev(mock_client, "TEST-123") | ||
|
|
||
| # Assert | ||
| assert result is None |
There was a problem hiding this comment.
The function transition_issue_to_ready_to_dev returns UIJiraTransition not none. This is wrong test
| error_code="CREATE_SUBTASK_ERROR" | ||
| ) | ||
|
|
||
| # ==================== INTERNAL HELPERS ==================== |
There was a problem hiding this comment.
Remove this kind of comments
|
|
||
|
|
||
| def find_ready_to_dev_transition( | ||
| jira_client: "JiraClient", issue_key: str |
There was a problem hiding this comment.
this is the problem of type_checking, you are returning a string not a class. SAme in the rest of the file
| # Call AI | ||
| with ctx.textual.loading("Generating description with AI..."): | ||
| try: | ||
| from titan_cli.ai.models import AIMessage |
There was a problem hiding this comment.
imports goes up in the file
| Centralized to avoid hardcoding and enable easy i18n in the future. | ||
| """ | ||
|
|
||
| # ==================== Step Titles ==================== |
There was a problem hiding this comment.
Avoid this kind of comments
| from .steps.ai_analyze_issue_step import ai_analyze_issue_requirements_step | ||
| from .steps.list_versions_step import list_versions_step | ||
|
|
||
| # Technical Specification Workflow steps (COMMENTED - steps don't exist) |
There was a problem hiding this comment.
If this does not exist remove them
| "ai_analyze_issue_requirements": ai_analyze_issue_requirements_step, | ||
| "list_versions": list_versions_step, | ||
|
|
||
| # Technical Specification Workflow steps (COMMENTED - steps don't exist) |
There was a problem hiding this comment.
If this steps does not exist remove them
There was a problem hiding this comment.
This should return a UIModel not Network one
There was a problem hiding this comment.
This should return a UIModel not a dict
There was a problem hiding this comment.
This should return an UI Model not dict
There was a problem hiding this comment.
This should return a UIModel not List[dict]
By: finxo
By: finxo
By: finxo
|
|
1 similar comment
|
|
| priority=priority | ||
| ) | ||
| # Find issue type (delegated to operation) | ||
| issue_type_result = find_issue_type_by_name(self, project_key, issue_type) |
There was a problem hiding this comment.
Client is calling an operation and passing itself as a parameter. This is backwards - operations should call the client, not vice versa. This violates the 5-layer architecture where Client (layer 3) should never call Operations (layer 2).
| description=description | ||
| ) | ||
| # Find subtask issue type (delegated to operation) | ||
| subtask_result = find_subtask_issue_type(self, self.project_key) |
There was a problem hiding this comment.
Client is calling an operation and passing itself as a parameter. This is backwards - operations should call the client, not vice versa. This violates the 5-layer architecture where Client (layer 3) should never call Operations (layer 2).
| ) | ||
|
|
||
| except Exception as e: | ||
| import traceback |
There was a problem hiding this comment.
imports go up in the file, not in the exception handler. Move this to the top of the file with other imports.
Pull Request
📝 Summary
This PR lays the groundwork for integrating a Jira issue creation workflow. It enables the Jira plugin in the configuration and refactors the
create_branch_stepto use a more robustResultobject pattern for handling git operations. This shift from exception-based error handling to amatch/casestructure improves code clarity and resilience.🔧 Changes Made
[plugins.jira]in.titan/config.toml.create_branch_step.pyto useClientSuccessandClientErrorresult objects instead of raisingGitErrorexceptions.try/exceptblocks withmatchstatements for handling the outcomes of all git commands (get_branches,checkout,delete_branch,create_branch).resulttypes fromtitan_cli.core.result.🧪 Testing
poetry run pytest)make test)titan-devThe refactoring touches core git functionality. Existing unit tests for
create_branch_stepshould be updated to validate the newResult-based logic, ensuring all success and error paths are handled correctly.📊 Logs
✅ Checklist