titan-cli 0.1.0__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.
- titan_cli/__init__.py +3 -0
- titan_cli/__main__.py +4 -0
- titan_cli/ai/__init__.py +0 -0
- titan_cli/ai/agents/__init__.py +15 -0
- titan_cli/ai/agents/base.py +152 -0
- titan_cli/ai/client.py +170 -0
- titan_cli/ai/constants.py +56 -0
- titan_cli/ai/exceptions.py +48 -0
- titan_cli/ai/models.py +34 -0
- titan_cli/ai/oauth_helper.py +120 -0
- titan_cli/ai/providers/__init__.py +9 -0
- titan_cli/ai/providers/anthropic.py +117 -0
- titan_cli/ai/providers/base.py +75 -0
- titan_cli/ai/providers/gemini.py +278 -0
- titan_cli/cli.py +59 -0
- titan_cli/clients/__init__.py +1 -0
- titan_cli/clients/gcloud_client.py +52 -0
- titan_cli/core/__init__.py +3 -0
- titan_cli/core/config.py +274 -0
- titan_cli/core/discovery.py +51 -0
- titan_cli/core/errors.py +81 -0
- titan_cli/core/models.py +52 -0
- titan_cli/core/plugins/available.py +36 -0
- titan_cli/core/plugins/models.py +67 -0
- titan_cli/core/plugins/plugin_base.py +108 -0
- titan_cli/core/plugins/plugin_registry.py +163 -0
- titan_cli/core/secrets.py +141 -0
- titan_cli/core/workflows/__init__.py +22 -0
- titan_cli/core/workflows/models.py +88 -0
- titan_cli/core/workflows/project_step_source.py +86 -0
- titan_cli/core/workflows/workflow_exceptions.py +17 -0
- titan_cli/core/workflows/workflow_filter_service.py +137 -0
- titan_cli/core/workflows/workflow_registry.py +419 -0
- titan_cli/core/workflows/workflow_sources.py +307 -0
- titan_cli/engine/__init__.py +39 -0
- titan_cli/engine/builder.py +159 -0
- titan_cli/engine/context.py +82 -0
- titan_cli/engine/mock_context.py +176 -0
- titan_cli/engine/results.py +91 -0
- titan_cli/engine/steps/ai_assistant_step.py +185 -0
- titan_cli/engine/steps/command_step.py +93 -0
- titan_cli/engine/utils/__init__.py +3 -0
- titan_cli/engine/utils/venv.py +31 -0
- titan_cli/engine/workflow_executor.py +187 -0
- titan_cli/external_cli/__init__.py +0 -0
- titan_cli/external_cli/configs.py +17 -0
- titan_cli/external_cli/launcher.py +65 -0
- titan_cli/messages.py +121 -0
- titan_cli/ui/tui/__init__.py +205 -0
- titan_cli/ui/tui/__previews__/statusbar_preview.py +88 -0
- titan_cli/ui/tui/app.py +113 -0
- titan_cli/ui/tui/icons.py +70 -0
- titan_cli/ui/tui/screens/__init__.py +24 -0
- titan_cli/ui/tui/screens/ai_config.py +498 -0
- titan_cli/ui/tui/screens/ai_config_wizard.py +882 -0
- titan_cli/ui/tui/screens/base.py +110 -0
- titan_cli/ui/tui/screens/cli_launcher.py +151 -0
- titan_cli/ui/tui/screens/global_setup_wizard.py +363 -0
- titan_cli/ui/tui/screens/main_menu.py +162 -0
- titan_cli/ui/tui/screens/plugin_config_wizard.py +550 -0
- titan_cli/ui/tui/screens/plugin_management.py +377 -0
- titan_cli/ui/tui/screens/project_setup_wizard.py +686 -0
- titan_cli/ui/tui/screens/workflow_execution.py +592 -0
- titan_cli/ui/tui/screens/workflows.py +249 -0
- titan_cli/ui/tui/textual_components.py +537 -0
- titan_cli/ui/tui/textual_workflow_executor.py +405 -0
- titan_cli/ui/tui/theme.py +102 -0
- titan_cli/ui/tui/widgets/__init__.py +40 -0
- titan_cli/ui/tui/widgets/button.py +108 -0
- titan_cli/ui/tui/widgets/header.py +116 -0
- titan_cli/ui/tui/widgets/panel.py +81 -0
- titan_cli/ui/tui/widgets/status_bar.py +115 -0
- titan_cli/ui/tui/widgets/table.py +77 -0
- titan_cli/ui/tui/widgets/text.py +177 -0
- titan_cli/utils/__init__.py +0 -0
- titan_cli/utils/autoupdate.py +155 -0
- titan_cli-0.1.0.dist-info/METADATA +149 -0
- titan_cli-0.1.0.dist-info/RECORD +146 -0
- titan_cli-0.1.0.dist-info/WHEEL +4 -0
- titan_cli-0.1.0.dist-info/entry_points.txt +9 -0
- titan_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- titan_plugin_git/__init__.py +1 -0
- titan_plugin_git/clients/__init__.py +8 -0
- titan_plugin_git/clients/git_client.py +772 -0
- titan_plugin_git/exceptions.py +40 -0
- titan_plugin_git/messages.py +112 -0
- titan_plugin_git/models.py +39 -0
- titan_plugin_git/plugin.py +118 -0
- titan_plugin_git/steps/__init__.py +1 -0
- titan_plugin_git/steps/ai_commit_message_step.py +171 -0
- titan_plugin_git/steps/branch_steps.py +104 -0
- titan_plugin_git/steps/commit_step.py +80 -0
- titan_plugin_git/steps/push_step.py +63 -0
- titan_plugin_git/steps/status_step.py +59 -0
- titan_plugin_git/workflows/__previews__/__init__.py +1 -0
- titan_plugin_git/workflows/__previews__/commit_ai_preview.py +124 -0
- titan_plugin_git/workflows/commit-ai.yaml +28 -0
- titan_plugin_github/__init__.py +11 -0
- titan_plugin_github/agents/__init__.py +6 -0
- titan_plugin_github/agents/config_loader.py +130 -0
- titan_plugin_github/agents/issue_generator.py +353 -0
- titan_plugin_github/agents/pr_agent.py +528 -0
- titan_plugin_github/clients/__init__.py +8 -0
- titan_plugin_github/clients/github_client.py +1105 -0
- titan_plugin_github/config/__init__.py +0 -0
- titan_plugin_github/config/pr_agent.toml +85 -0
- titan_plugin_github/exceptions.py +28 -0
- titan_plugin_github/messages.py +88 -0
- titan_plugin_github/models.py +330 -0
- titan_plugin_github/plugin.py +131 -0
- titan_plugin_github/steps/__init__.py +12 -0
- titan_plugin_github/steps/ai_pr_step.py +172 -0
- titan_plugin_github/steps/create_pr_step.py +86 -0
- titan_plugin_github/steps/github_prompt_steps.py +171 -0
- titan_plugin_github/steps/issue_steps.py +143 -0
- titan_plugin_github/steps/preview_step.py +40 -0
- titan_plugin_github/utils.py +82 -0
- titan_plugin_github/workflows/__previews__/__init__.py +1 -0
- titan_plugin_github/workflows/__previews__/create_pr_ai_preview.py +140 -0
- titan_plugin_github/workflows/create-issue-ai.yaml +32 -0
- titan_plugin_github/workflows/create-pr-ai.yaml +49 -0
- titan_plugin_jira/__init__.py +8 -0
- titan_plugin_jira/agents/__init__.py +6 -0
- titan_plugin_jira/agents/config_loader.py +154 -0
- titan_plugin_jira/agents/jira_agent.py +553 -0
- titan_plugin_jira/agents/prompts.py +364 -0
- titan_plugin_jira/agents/response_parser.py +435 -0
- titan_plugin_jira/agents/token_tracker.py +223 -0
- titan_plugin_jira/agents/validators.py +246 -0
- titan_plugin_jira/clients/jira_client.py +745 -0
- titan_plugin_jira/config/jira_agent.toml +92 -0
- titan_plugin_jira/config/templates/issue_analysis.md.j2 +78 -0
- titan_plugin_jira/exceptions.py +37 -0
- titan_plugin_jira/formatters/__init__.py +6 -0
- titan_plugin_jira/formatters/markdown_formatter.py +245 -0
- titan_plugin_jira/messages.py +115 -0
- titan_plugin_jira/models.py +89 -0
- titan_plugin_jira/plugin.py +264 -0
- titan_plugin_jira/steps/ai_analyze_issue_step.py +105 -0
- titan_plugin_jira/steps/get_issue_step.py +82 -0
- titan_plugin_jira/steps/prompt_select_issue_step.py +80 -0
- titan_plugin_jira/steps/search_saved_query_step.py +238 -0
- titan_plugin_jira/utils/__init__.py +13 -0
- titan_plugin_jira/utils/issue_sorter.py +140 -0
- titan_plugin_jira/utils/saved_queries.py +150 -0
- titan_plugin_jira/workflows/analyze-jira-issues.yaml +34 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
[agent]
|
|
2
|
+
name = "JiraAgent"
|
|
3
|
+
description = "AI agent specialized in JIRA issue analysis for iOS, Android, and BFF development"
|
|
4
|
+
version = "1.0.0"
|
|
5
|
+
|
|
6
|
+
[agent.capabilities]
|
|
7
|
+
issue_analysis = true
|
|
8
|
+
requirements_extraction = true
|
|
9
|
+
subtask_generation = true
|
|
10
|
+
description_enhancement = true
|
|
11
|
+
label_suggestion = true
|
|
12
|
+
risk_scoring = true
|
|
13
|
+
gherkin_generation = true
|
|
14
|
+
|
|
15
|
+
[agent.limits]
|
|
16
|
+
max_description_length = 8000
|
|
17
|
+
max_subtasks = 6
|
|
18
|
+
max_comments_to_analyze = 8
|
|
19
|
+
max_linked_issues = 5
|
|
20
|
+
temperature = 0.3
|
|
21
|
+
max_tokens = 2000
|
|
22
|
+
|
|
23
|
+
[agent.prompts.requirements_analysis]
|
|
24
|
+
system = """You are a technical analyst. Extract requirements from JIRA issues.
|
|
25
|
+
CRITICAL: You MUST follow the exact format specified in the user prompt.
|
|
26
|
+
Do not add extra sections or change the format."""
|
|
27
|
+
[agent.prompts.description_enhancement]
|
|
28
|
+
system = """You are a Technical Writer and QA Specialist.
|
|
29
|
+
Structure:
|
|
30
|
+
- **Overview**: What and Why.
|
|
31
|
+
- **Technical Context**: Specifics for iOS, Android, or BFF.
|
|
32
|
+
- **Acceptance Criteria**: Using [ ] checkboxes.
|
|
33
|
+
- **Gherkin Scenarios**:
|
|
34
|
+
Generate Given/When/Then scenarios for the main success path and at least one edge case.
|
|
35
|
+
Use code blocks:
|
|
36
|
+
Scenario: [Name]
|
|
37
|
+
Given [context]
|
|
38
|
+
When [action]
|
|
39
|
+
Then [outcome]
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
[agent.prompts.subtask_suggestion]
|
|
43
|
+
system = """You are a Tech Lead. Suggest subtasks ONLY for iOS, Android, or BFF.
|
|
44
|
+
Include always:
|
|
45
|
+
- 1 subtask for Implementation.
|
|
46
|
+
- 1 subtask for QA/Gherkin validation.
|
|
47
|
+
- 1 subtask for Documentation/Code Review.
|
|
48
|
+
Complexity: (XS, S, M, L, XL).
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
[agent.prompts.smart_labeling]
|
|
52
|
+
system = """You are a classification bot. STRICTLY use only these labels. DO NOT create new ones:
|
|
53
|
+
- Platforms: [iOS, Android, BFF]
|
|
54
|
+
- Types: [bug, feature, tech-debt, spike, refactor]
|
|
55
|
+
- Priority: [blocker, high-impact, low-priority]
|
|
56
|
+
|
|
57
|
+
If the issue does not fit any platform, flag it as 'Needs Triage'.
|
|
58
|
+
Output: comma-separated list only."""
|
|
59
|
+
|
|
60
|
+
[agent.prompts.comment_generation]
|
|
61
|
+
system = """You are an experienced software engineer providing technical guidance.
|
|
62
|
+
Your task is to generate helpful comments for JIRA issues.
|
|
63
|
+
|
|
64
|
+
Guidelines:
|
|
65
|
+
1. Be constructive and helpful
|
|
66
|
+
2. Provide technical insights specific to iOS, Android, or BFF
|
|
67
|
+
3. Suggest solutions or alternatives
|
|
68
|
+
4. Ask clarifying questions when needed
|
|
69
|
+
5. Reference related issues when relevant
|
|
70
|
+
6. Keep it concise but informative
|
|
71
|
+
7. Consider multi-platform implications
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
[agent.features]
|
|
75
|
+
enable_requirement_extraction = true
|
|
76
|
+
enable_subtasks = true
|
|
77
|
+
enable_risk_analysis = true # Enable risk and complexity analysis
|
|
78
|
+
enable_dependency_detection = true # Enable dependency detection
|
|
79
|
+
enable_acceptance_criteria = true # Enable acceptance criteria extraction
|
|
80
|
+
enable_debug_output = true
|
|
81
|
+
|
|
82
|
+
[agent.formatting]
|
|
83
|
+
# Optional: Specify a Jinja2 template for formatting analysis output
|
|
84
|
+
# If not set or template not found, uses built-in Python formatter
|
|
85
|
+
# Template must be in config/templates/ directory
|
|
86
|
+
template = "issue_analysis.md.j2"
|
|
87
|
+
|
|
88
|
+
[agent.metadata]
|
|
89
|
+
author = "Titan CLI Team"
|
|
90
|
+
updated = "2024-12-22"
|
|
91
|
+
stack = ["iOS", "Android", "BFF"]
|
|
92
|
+
testing_framework = "Gherkin/Cucumber"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# JIRA Issue Analysis
|
|
2
|
+
|
|
3
|
+
{% if functional_requirements or non_functional_requirements %}
|
|
4
|
+
## 1. Requirements Breakdown
|
|
5
|
+
|
|
6
|
+
{% if functional_requirements %}
|
|
7
|
+
**Functional Requirements:**
|
|
8
|
+
{% for req in functional_requirements %}
|
|
9
|
+
- {{ req }}
|
|
10
|
+
{% endfor %}
|
|
11
|
+
{% endif %}
|
|
12
|
+
|
|
13
|
+
{% if non_functional_requirements %}
|
|
14
|
+
**Non-Functional Requirements:**
|
|
15
|
+
{% for req in non_functional_requirements %}
|
|
16
|
+
- {{ req }}
|
|
17
|
+
{% endfor %}
|
|
18
|
+
{% endif %}
|
|
19
|
+
{% endif %}
|
|
20
|
+
|
|
21
|
+
{% if acceptance_criteria %}
|
|
22
|
+
## 2. Acceptance Criteria
|
|
23
|
+
|
|
24
|
+
{% for criterion in acceptance_criteria %}
|
|
25
|
+
- [ ] {{ criterion }}
|
|
26
|
+
{% endfor %}
|
|
27
|
+
{% endif %}
|
|
28
|
+
|
|
29
|
+
{% if technical_approach %}
|
|
30
|
+
## 3. Technical Approach
|
|
31
|
+
|
|
32
|
+
{{ technical_approach }}
|
|
33
|
+
{% endif %}
|
|
34
|
+
|
|
35
|
+
{% if dependencies %}
|
|
36
|
+
## 4. Dependencies
|
|
37
|
+
|
|
38
|
+
{% for dep in dependencies %}
|
|
39
|
+
- {{ dep }}
|
|
40
|
+
{% endfor %}
|
|
41
|
+
{% endif %}
|
|
42
|
+
|
|
43
|
+
{% if risks %}
|
|
44
|
+
## 5. Potential Risks
|
|
45
|
+
|
|
46
|
+
{% for risk in risks %}
|
|
47
|
+
- ⚠️ {{ risk }}
|
|
48
|
+
{% endfor %}
|
|
49
|
+
{% endif %}
|
|
50
|
+
|
|
51
|
+
{% if edge_cases %}
|
|
52
|
+
## 6. Edge Cases to Consider
|
|
53
|
+
|
|
54
|
+
{% for edge_case in edge_cases %}
|
|
55
|
+
- {{ edge_case }}
|
|
56
|
+
{% endfor %}
|
|
57
|
+
{% endif %}
|
|
58
|
+
|
|
59
|
+
{% if suggested_subtasks %}
|
|
60
|
+
## 7. Suggested Subtasks
|
|
61
|
+
|
|
62
|
+
{% for subtask in suggested_subtasks %}
|
|
63
|
+
**{{ loop.index }}. {{ subtask.summary }}**
|
|
64
|
+
{{ subtask.description }}
|
|
65
|
+
|
|
66
|
+
{% endfor %}
|
|
67
|
+
{% endif %}
|
|
68
|
+
|
|
69
|
+
{% if complexity_score or estimated_effort %}
|
|
70
|
+
## 8. Complexity Assessment
|
|
71
|
+
|
|
72
|
+
{% if complexity_score %}
|
|
73
|
+
**Complexity:** {{ complexity_score|title }}
|
|
74
|
+
{% endif %}
|
|
75
|
+
{% if estimated_effort %}
|
|
76
|
+
**Estimated Effort:** {{ estimated_effort }}
|
|
77
|
+
{% endif %}
|
|
78
|
+
{% endif %}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# plugins/titan-plugin-jira/titan_plugin_jira/exceptions.py
|
|
2
|
+
"""Custom exceptions for JIRA plugin."""
|
|
3
|
+
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class JiraPluginError(Exception):
|
|
8
|
+
"""Base exception for JIRA plugin errors."""
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class JiraConfigurationError(JiraPluginError):
|
|
13
|
+
"""Raised when JIRA plugin configuration is invalid or missing."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class JiraClientError(JiraPluginError):
|
|
18
|
+
"""Raised when JIRA client operations fail."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class JiraAPIError(Exception):
|
|
23
|
+
"""Exception raised for JIRA API errors"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, message: str, status_code: Optional[int] = None, response: Optional[Dict] = None):
|
|
26
|
+
self.message = message
|
|
27
|
+
self.status_code = status_code
|
|
28
|
+
self.response = response
|
|
29
|
+
super().__init__(self.message)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"JiraPluginError",
|
|
34
|
+
"JiraConfigurationError",
|
|
35
|
+
"JiraClientError",
|
|
36
|
+
"JiraAPIError",
|
|
37
|
+
]
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# plugins/titan-plugin-jira/titan_plugin_jira/formatters/markdown_formatter.py
|
|
2
|
+
"""Markdown formatter for JIRA issue analysis."""
|
|
3
|
+
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from dataclasses import asdict
|
|
7
|
+
from ..agents.jira_agent import IssueAnalysis
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IssueAnalysisMarkdownFormatter:
|
|
11
|
+
"""
|
|
12
|
+
Formats IssueAnalysis into markdown for display.
|
|
13
|
+
|
|
14
|
+
This class separates presentation logic from business logic,
|
|
15
|
+
making it easy to:
|
|
16
|
+
- Modify output format without touching agent code
|
|
17
|
+
- Test formatting independently
|
|
18
|
+
- Add other formatters (HTML, JSON, etc.) in the future
|
|
19
|
+
- Optionally use Jinja2 templates for custom formatting
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
>>> # Use built-in formatter (default)
|
|
23
|
+
>>> formatter = IssueAnalysisMarkdownFormatter()
|
|
24
|
+
>>> markdown = formatter.format(analysis)
|
|
25
|
+
|
|
26
|
+
>>> # Use custom Jinja2 template
|
|
27
|
+
>>> formatter = IssueAnalysisMarkdownFormatter(template_path="custom.md.j2")
|
|
28
|
+
>>> markdown = formatter.format(analysis)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, template_path: Optional[str] = None):
|
|
32
|
+
"""
|
|
33
|
+
Initialize the formatter.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
template_path: Optional path to Jinja2 template file.
|
|
37
|
+
If None, uses built-in Python formatter.
|
|
38
|
+
If provided, must be relative to config/templates/
|
|
39
|
+
"""
|
|
40
|
+
self.template = None
|
|
41
|
+
if template_path:
|
|
42
|
+
self.template = self._load_template(template_path)
|
|
43
|
+
|
|
44
|
+
def _load_template(self, template_name: str):
|
|
45
|
+
"""
|
|
46
|
+
Load Jinja2 template from config/templates/.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
template_name: Template filename (e.g., "issue_analysis.md.j2")
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Jinja2 Template object or None if Jinja2 not available
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
from jinja2 import Environment, FileSystemLoader
|
|
56
|
+
except ImportError:
|
|
57
|
+
# Jinja2 not installed, fall back to built-in formatter
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
# Templates directory is relative to this file
|
|
62
|
+
# This works both in development and when installed
|
|
63
|
+
templates_dir = Path(__file__).parent.parent / "config" / "templates"
|
|
64
|
+
|
|
65
|
+
if not templates_dir.exists():
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
# Enable autoescape to prevent XSS vulnerabilities (CWE-116)
|
|
69
|
+
# This ensures all template variables are HTML-escaped by default
|
|
70
|
+
env = Environment(
|
|
71
|
+
loader=FileSystemLoader(str(templates_dir)),
|
|
72
|
+
autoescape=False
|
|
73
|
+
)
|
|
74
|
+
return env.get_template(template_name)
|
|
75
|
+
except Exception:
|
|
76
|
+
# Template not found or other error, fall back to built-in formatter
|
|
77
|
+
# Silently fail and use built-in formatter
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def format(self, analysis: IssueAnalysis) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Format IssueAnalysis into markdown.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
analysis: IssueAnalysis object from JiraAgent
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Formatted markdown string
|
|
89
|
+
"""
|
|
90
|
+
if self.template:
|
|
91
|
+
# Use Jinja2 template
|
|
92
|
+
return self._format_with_template(analysis)
|
|
93
|
+
else:
|
|
94
|
+
# Use built-in Python formatter
|
|
95
|
+
return self._format_builtin(analysis)
|
|
96
|
+
|
|
97
|
+
def _format_with_template(self, analysis: IssueAnalysis) -> str:
|
|
98
|
+
"""
|
|
99
|
+
Format using Jinja2 template.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
analysis: IssueAnalysis object
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Rendered markdown string
|
|
106
|
+
"""
|
|
107
|
+
# Convert dataclass to dict for template
|
|
108
|
+
analysis_dict = asdict(analysis)
|
|
109
|
+
return self.template.render(**analysis_dict)
|
|
110
|
+
|
|
111
|
+
def _format_builtin(self, analysis: IssueAnalysis) -> str:
|
|
112
|
+
"""
|
|
113
|
+
Format using built-in Python methods.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
analysis: IssueAnalysis object
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Formatted markdown string
|
|
120
|
+
"""
|
|
121
|
+
sections = []
|
|
122
|
+
|
|
123
|
+
# 1. Requirements Breakdown
|
|
124
|
+
if analysis.functional_requirements or analysis.non_functional_requirements:
|
|
125
|
+
sections.extend(self._format_requirements(analysis))
|
|
126
|
+
|
|
127
|
+
# 2. Acceptance Criteria
|
|
128
|
+
if analysis.acceptance_criteria:
|
|
129
|
+
sections.extend(self._format_acceptance_criteria(analysis))
|
|
130
|
+
|
|
131
|
+
# 3. Technical Approach
|
|
132
|
+
if analysis.technical_approach:
|
|
133
|
+
sections.extend(self._format_technical_approach(analysis))
|
|
134
|
+
|
|
135
|
+
# 4. Dependencies
|
|
136
|
+
if analysis.dependencies:
|
|
137
|
+
sections.extend(self._format_dependencies(analysis))
|
|
138
|
+
|
|
139
|
+
# 5. Potential Risks
|
|
140
|
+
if analysis.risks:
|
|
141
|
+
sections.extend(self._format_risks(analysis))
|
|
142
|
+
|
|
143
|
+
# 6. Edge Cases
|
|
144
|
+
if analysis.edge_cases:
|
|
145
|
+
sections.extend(self._format_edge_cases(analysis))
|
|
146
|
+
|
|
147
|
+
# 7. Suggested Subtasks
|
|
148
|
+
if analysis.suggested_subtasks:
|
|
149
|
+
sections.extend(self._format_subtasks(analysis))
|
|
150
|
+
|
|
151
|
+
# 8. Complexity & Effort
|
|
152
|
+
if analysis.complexity_score or analysis.estimated_effort:
|
|
153
|
+
sections.extend(self._format_complexity(analysis))
|
|
154
|
+
|
|
155
|
+
return "\n".join(sections)
|
|
156
|
+
|
|
157
|
+
def _format_requirements(self, analysis: IssueAnalysis) -> List[str]:
|
|
158
|
+
"""Format requirements section."""
|
|
159
|
+
sections = ["## 1. Requirements Breakdown"]
|
|
160
|
+
|
|
161
|
+
if analysis.functional_requirements:
|
|
162
|
+
sections.append("\n**Functional Requirements:**")
|
|
163
|
+
for req in analysis.functional_requirements:
|
|
164
|
+
sections.append(f"- {req}")
|
|
165
|
+
|
|
166
|
+
if analysis.non_functional_requirements:
|
|
167
|
+
sections.append("\n**Non-Functional Requirements:**")
|
|
168
|
+
for req in analysis.non_functional_requirements:
|
|
169
|
+
sections.append(f"- {req}")
|
|
170
|
+
|
|
171
|
+
sections.append("")
|
|
172
|
+
return sections
|
|
173
|
+
|
|
174
|
+
def _format_acceptance_criteria(self, analysis: IssueAnalysis) -> List[str]:
|
|
175
|
+
"""Format acceptance criteria section."""
|
|
176
|
+
sections = ["## 2. Acceptance Criteria"]
|
|
177
|
+
|
|
178
|
+
for criterion in analysis.acceptance_criteria:
|
|
179
|
+
sections.append(f"- [ ] {criterion}")
|
|
180
|
+
|
|
181
|
+
sections.append("")
|
|
182
|
+
return sections
|
|
183
|
+
|
|
184
|
+
def _format_technical_approach(self, analysis: IssueAnalysis) -> List[str]:
|
|
185
|
+
"""Format technical approach section."""
|
|
186
|
+
sections = [
|
|
187
|
+
"## 3. Technical Approach",
|
|
188
|
+
analysis.technical_approach,
|
|
189
|
+
""
|
|
190
|
+
]
|
|
191
|
+
return sections
|
|
192
|
+
|
|
193
|
+
def _format_dependencies(self, analysis: IssueAnalysis) -> List[str]:
|
|
194
|
+
"""Format dependencies section."""
|
|
195
|
+
sections = ["## 4. Dependencies"]
|
|
196
|
+
|
|
197
|
+
for dep in analysis.dependencies:
|
|
198
|
+
sections.append(f"- {dep}")
|
|
199
|
+
|
|
200
|
+
sections.append("")
|
|
201
|
+
return sections
|
|
202
|
+
|
|
203
|
+
def _format_risks(self, analysis: IssueAnalysis) -> List[str]:
|
|
204
|
+
"""Format risks section."""
|
|
205
|
+
sections = ["## 5. Potential Risks"]
|
|
206
|
+
|
|
207
|
+
for risk in analysis.risks:
|
|
208
|
+
sections.append(f"- ⚠️ {risk}")
|
|
209
|
+
|
|
210
|
+
sections.append("")
|
|
211
|
+
return sections
|
|
212
|
+
|
|
213
|
+
def _format_edge_cases(self, analysis: IssueAnalysis) -> List[str]:
|
|
214
|
+
"""Format edge cases section."""
|
|
215
|
+
sections = ["## 6. Edge Cases to Consider"]
|
|
216
|
+
|
|
217
|
+
for edge_case in analysis.edge_cases:
|
|
218
|
+
sections.append(f"- {edge_case}")
|
|
219
|
+
|
|
220
|
+
sections.append("")
|
|
221
|
+
return sections
|
|
222
|
+
|
|
223
|
+
def _format_subtasks(self, analysis: IssueAnalysis) -> List[str]:
|
|
224
|
+
"""Format subtasks section."""
|
|
225
|
+
sections = ["## 7. Suggested Subtasks"]
|
|
226
|
+
|
|
227
|
+
for i, subtask in enumerate(analysis.suggested_subtasks, 1):
|
|
228
|
+
sections.append(f"\n**{i}. {subtask.get('summary', 'Subtask')}**")
|
|
229
|
+
sections.append(f"{subtask.get('description', '')}")
|
|
230
|
+
|
|
231
|
+
sections.append("")
|
|
232
|
+
return sections
|
|
233
|
+
|
|
234
|
+
def _format_complexity(self, analysis: IssueAnalysis) -> List[str]:
|
|
235
|
+
"""Format complexity assessment section."""
|
|
236
|
+
sections = ["## 8. Complexity Assessment"]
|
|
237
|
+
|
|
238
|
+
if analysis.complexity_score:
|
|
239
|
+
sections.append(f"**Complexity:** {analysis.complexity_score.title()}")
|
|
240
|
+
|
|
241
|
+
if analysis.estimated_effort:
|
|
242
|
+
sections.append(f"**Estimated Effort:** {analysis.estimated_effort}")
|
|
243
|
+
|
|
244
|
+
sections.append("")
|
|
245
|
+
return sections
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
User-facing messages for JIRA plugin
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Messages:
|
|
7
|
+
"""Container for all user-facing messages"""
|
|
8
|
+
|
|
9
|
+
class Plugin:
|
|
10
|
+
"""Plugin-level messages"""
|
|
11
|
+
CLIENT_INIT_WARNING: str = "Warning: JiraPlugin could not initialize JiraClient: {e}"
|
|
12
|
+
CLIENT_NOT_AVAILABLE: str = "JiraPlugin not initialized or JIRA API not available."
|
|
13
|
+
CLIENT_NOT_AVAILABLE_IN_CONTEXT: str = "JIRA client is not available in the workflow context."
|
|
14
|
+
JIRA_CLIENT_NOT_AVAILABLE: str = "JIRA client not initialized. Please configure the plugin first."
|
|
15
|
+
|
|
16
|
+
class Steps:
|
|
17
|
+
"""Step-level messages"""
|
|
18
|
+
|
|
19
|
+
class Search:
|
|
20
|
+
"""Search issues step messages"""
|
|
21
|
+
SEARCHING: str = "Searching JIRA issues with JQL: {jql}"
|
|
22
|
+
SEARCH_SUCCESS: str = "Found {count} JIRA issue(s)"
|
|
23
|
+
NO_RESULTS: str = "No JIRA issues found matching the query"
|
|
24
|
+
SEARCH_FAILED: str = "Failed to search JIRA issues: {e}"
|
|
25
|
+
QUERY_NOT_FOUND: str = "Saved query '{query_name}' not found."
|
|
26
|
+
AVAILABLE_PREDEFINED: str = "Available predefined queries (first 15):"
|
|
27
|
+
MORE_QUERIES: str = " ... and {count} more"
|
|
28
|
+
CUSTOM_QUERIES_HEADER: str = "Custom queries from config:"
|
|
29
|
+
ADD_CUSTOM_HINT: str = "💡 Add custom queries to .titan/config.toml:"
|
|
30
|
+
CUSTOM_QUERY_EXAMPLE: str = "[jira.saved_queries]\nmy_custom = \"assignee = currentUser() AND status != Done\""
|
|
31
|
+
QUERY_NAME_REQUIRED: str = "query_name parameter is required"
|
|
32
|
+
PROJECT_REQUIRED: str = (
|
|
33
|
+
"Query '{query_name}' requires a 'project' parameter.\n"
|
|
34
|
+
"JQL template: {jql}\n\n"
|
|
35
|
+
"Provide it in workflow:\n"
|
|
36
|
+
" params:\n"
|
|
37
|
+
" query_name: \"{query_name}\"\n"
|
|
38
|
+
" project: \"PROJ\""
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
class GetIssue:
|
|
42
|
+
"""Get issue step messages"""
|
|
43
|
+
GETTING_ISSUE: str = "Fetching JIRA issue: {issue_key}"
|
|
44
|
+
GET_SUCCESS: str = "Retrieved JIRA issue: {issue_key}"
|
|
45
|
+
GET_FAILED: str = "Failed to get JIRA issue: {e}"
|
|
46
|
+
ISSUE_NOT_FOUND: str = "JIRA issue not found: {issue_key}"
|
|
47
|
+
|
|
48
|
+
class CreateIssue:
|
|
49
|
+
"""Create issue step messages"""
|
|
50
|
+
CREATING_ISSUE: str = "Creating JIRA issue: {summary}"
|
|
51
|
+
CREATE_SUCCESS: str = "Created JIRA issue: {issue_key}"
|
|
52
|
+
CREATE_FAILED: str = "Failed to create JIRA issue: {e}"
|
|
53
|
+
MISSING_SUMMARY: str = "Issue summary is required"
|
|
54
|
+
|
|
55
|
+
class UpdateStatus:
|
|
56
|
+
"""Update status step messages"""
|
|
57
|
+
UPDATING_STATUS: str = "Updating JIRA issue {issue_key} to status: {status}"
|
|
58
|
+
UPDATE_SUCCESS: str = "Updated JIRA issue {issue_key} to {status}"
|
|
59
|
+
UPDATE_FAILED: str = "Failed to update JIRA issue status: {e}"
|
|
60
|
+
INVALID_TRANSITION: str = "Cannot transition to '{status}'. Available: {transitions}"
|
|
61
|
+
|
|
62
|
+
class AddComment:
|
|
63
|
+
"""Add comment step messages"""
|
|
64
|
+
ADDING_COMMENT: str = "Adding comment to JIRA issue: {issue_key}"
|
|
65
|
+
COMMENT_SUCCESS: str = "Added comment to JIRA issue {issue_key}"
|
|
66
|
+
COMMENT_FAILED: str = "Failed to add comment: {e}"
|
|
67
|
+
|
|
68
|
+
class LinkPR:
|
|
69
|
+
"""Link PR step messages"""
|
|
70
|
+
LINKING_PR: str = "Linking PR to JIRA issue: {issue_key}"
|
|
71
|
+
LINK_SUCCESS: str = "Linked PR to JIRA issue {issue_key}"
|
|
72
|
+
LINK_FAILED: str = "Failed to link PR: {e}"
|
|
73
|
+
|
|
74
|
+
class AIIssue:
|
|
75
|
+
"""AI issue generation step messages"""
|
|
76
|
+
AI_NOT_CONFIGURED: str = "AI not configured. Run 'titan ai configure' to enable AI features."
|
|
77
|
+
AI_NOT_CONFIGURED_SKIP: str = "AI not configured - skipping analysis"
|
|
78
|
+
GENERATING_ISSUE: str = "Generating JIRA issue with AI..."
|
|
79
|
+
GENERATION_SUCCESS: str = "AI generated JIRA issue successfully"
|
|
80
|
+
GENERATION_FAILED: str = "AI generation failed: {e}"
|
|
81
|
+
INVALID_ISSUE_TYPE: str = "Invalid issue type: {issue_type}. Use: bug, feature, or task"
|
|
82
|
+
USER_REJECTED: str = "User rejected AI-generated issue"
|
|
83
|
+
CONFIRM_USE_AI: str = "Use this AI-generated issue?"
|
|
84
|
+
NO_ISSUE_FOUND: str = "No issue found to analyze"
|
|
85
|
+
ANALYZING: str = "Analyzing issue with AI..."
|
|
86
|
+
|
|
87
|
+
class ExtractKey:
|
|
88
|
+
"""Extract issue key step messages"""
|
|
89
|
+
EXTRACTING_KEY: str = "Extracting JIRA key from branch: {branch}"
|
|
90
|
+
KEY_FOUND: str = "Found JIRA key: {issue_key}"
|
|
91
|
+
KEY_NOT_FOUND: str = "No JIRA key found in branch name: {branch}"
|
|
92
|
+
INVALID_BRANCH_FORMAT: str = "Branch name doesn't follow pattern: feature/PROJ-123-description"
|
|
93
|
+
|
|
94
|
+
class PromptSelectIssue:
|
|
95
|
+
"""Prompt select issue step messages"""
|
|
96
|
+
NO_ISSUES_AVAILABLE: str = "No JIRA issues available to select from"
|
|
97
|
+
NO_ISSUE_SELECTED: str = "No issue selected"
|
|
98
|
+
UI_NOT_AVAILABLE: str = "UI not available for prompting"
|
|
99
|
+
ASK_ISSUE_NUMBER: str = "Enter issue number to analyze"
|
|
100
|
+
ISSUE_SELECTED: str = "Selected: {key} - {summary}"
|
|
101
|
+
ISSUE_SELECTION_CONFIRM: str = "Selected: {key} - {summary}"
|
|
102
|
+
SELECT_SUCCESS: str = "Selected issue: {key}"
|
|
103
|
+
|
|
104
|
+
class JIRA:
|
|
105
|
+
"""JIRA-specific messages"""
|
|
106
|
+
AUTHENTICATION_FAILED: str = "JIRA authentication failed. Check your API token."
|
|
107
|
+
RATE_LIMIT_EXCEEDED: str = "JIRA API rate limit exceeded. Please wait and try again."
|
|
108
|
+
NETWORK_ERROR: str = "Network error connecting to JIRA: {e}"
|
|
109
|
+
INVALID_PROJECT: str = "Invalid JIRA project: {project}"
|
|
110
|
+
INVALID_JQL: str = "Invalid JQL query: {jql}"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
msg = Messages()
|
|
114
|
+
|
|
115
|
+
__all__ = ["msg", "Messages"]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
JIRA Plugin Data Models
|
|
4
|
+
|
|
5
|
+
All Pydantic models and dataclasses for the JIRA plugin.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Dict, List, Optional, Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IssueStatus(str, Enum):
|
|
14
|
+
"""Common JIRA issue statuses"""
|
|
15
|
+
TODO = "To Do"
|
|
16
|
+
IN_PROGRESS = "In Progress"
|
|
17
|
+
IN_REVIEW = "In Review"
|
|
18
|
+
READY_FOR_QA = "Ready for QA"
|
|
19
|
+
IN_QA = "In QA"
|
|
20
|
+
DONE = "Done"
|
|
21
|
+
BLOCKED = "Blocked"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class JiraProject:
|
|
26
|
+
"""Represents a JIRA project"""
|
|
27
|
+
id: str
|
|
28
|
+
key: str
|
|
29
|
+
name: str
|
|
30
|
+
description: Optional[str] = None
|
|
31
|
+
project_type: Optional[str] = None
|
|
32
|
+
lead: Optional[str] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class JiraIssueType:
|
|
37
|
+
"""Represents a JIRA issue type"""
|
|
38
|
+
id: str
|
|
39
|
+
name: str
|
|
40
|
+
description: Optional[str] = None
|
|
41
|
+
subtask: bool = False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class JiraTransition:
|
|
46
|
+
"""Represents a JIRA workflow transition"""
|
|
47
|
+
id: str
|
|
48
|
+
name: str
|
|
49
|
+
to_status: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class JiraComment:
|
|
54
|
+
"""Represents a JIRA comment"""
|
|
55
|
+
id: str
|
|
56
|
+
author: str
|
|
57
|
+
body: str
|
|
58
|
+
created: str
|
|
59
|
+
updated: Optional[str] = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class JiraTicket:
|
|
64
|
+
"""Represents a JIRA ticket/issue"""
|
|
65
|
+
key: str
|
|
66
|
+
id: str
|
|
67
|
+
summary: str
|
|
68
|
+
description: Optional[str]
|
|
69
|
+
status: str
|
|
70
|
+
issue_type: str
|
|
71
|
+
assignee: Optional[str]
|
|
72
|
+
reporter: str
|
|
73
|
+
priority: str
|
|
74
|
+
created: str
|
|
75
|
+
updated: str
|
|
76
|
+
labels: List[str]
|
|
77
|
+
components: List[str]
|
|
78
|
+
fix_versions: List[str]
|
|
79
|
+
raw: Dict[str, Any] # Original API response
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
__all__ = [
|
|
83
|
+
"IssueStatus",
|
|
84
|
+
"JiraProject",
|
|
85
|
+
"JiraIssueType",
|
|
86
|
+
"JiraTransition",
|
|
87
|
+
"JiraComment",
|
|
88
|
+
"JiraTicket",
|
|
89
|
+
]
|