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,553 @@
|
|
|
1
|
+
# plugins/titan-plugin-jira/titan_plugin_jira/agents/jira_agent.py
|
|
2
|
+
"""
|
|
3
|
+
JiraAgent - Intelligent orchestrator for JIRA workflows.
|
|
4
|
+
|
|
5
|
+
This agent analyzes JIRA issues and automatically:
|
|
6
|
+
1. Extracts technical requirements from descriptions
|
|
7
|
+
2. Generates enhanced descriptions with structured format
|
|
8
|
+
3. Suggests subtasks for complex issues
|
|
9
|
+
4. Generates helpful comments with technical insights
|
|
10
|
+
5. Identifies risks and dependencies
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Optional, List, Dict, Any
|
|
16
|
+
|
|
17
|
+
from titan_cli.ai.agents.base import BaseAIAgent, AgentRequest
|
|
18
|
+
from .config_loader import load_agent_config
|
|
19
|
+
from .response_parser import JiraAgentParser
|
|
20
|
+
from .validators import IssueValidator
|
|
21
|
+
from .token_tracker import TokenTracker, TokenBudget, OperationType
|
|
22
|
+
from .prompts import JiraAgentPrompts
|
|
23
|
+
|
|
24
|
+
# Set up logger
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class IssueAnalysis:
|
|
30
|
+
"""Complete analysis result from JiraAgent."""
|
|
31
|
+
|
|
32
|
+
# Requirements analysis
|
|
33
|
+
functional_requirements: List[str] = field(default_factory=list)
|
|
34
|
+
non_functional_requirements: List[str] = field(default_factory=list)
|
|
35
|
+
acceptance_criteria: List[str] = field(default_factory=list)
|
|
36
|
+
|
|
37
|
+
# Technical analysis
|
|
38
|
+
technical_approach: Optional[str] = None
|
|
39
|
+
dependencies: List[str] = field(default_factory=list)
|
|
40
|
+
risks: List[str] = field(default_factory=list)
|
|
41
|
+
edge_cases: List[str] = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
# Suggested content
|
|
44
|
+
enhanced_description: Optional[str] = None
|
|
45
|
+
suggested_subtasks: List[Dict[str, str]] = field(default_factory=list)
|
|
46
|
+
suggested_comments: List[str] = field(default_factory=list)
|
|
47
|
+
|
|
48
|
+
# Metadata
|
|
49
|
+
total_tokens_used: int = 0
|
|
50
|
+
complexity_score: Optional[str] = None # "low", "medium", "high", "very high"
|
|
51
|
+
estimated_effort: Optional[str] = None # "1-2 days", "3-5 days", "1-2 weeks", etc.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class JiraAgent(BaseAIAgent):
|
|
55
|
+
"""
|
|
56
|
+
AI agent for intelligent JIRA issue analysis and requirements generation.
|
|
57
|
+
|
|
58
|
+
This agent:
|
|
59
|
+
- Analyzes issue descriptions and extracts structured requirements
|
|
60
|
+
- Enhances descriptions with proper formatting and clarity
|
|
61
|
+
- Suggests subtasks for complex work breakdown
|
|
62
|
+
- Generates helpful technical comments
|
|
63
|
+
- Identifies risks, dependencies, and edge cases
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
```python
|
|
67
|
+
# In a workflow step
|
|
68
|
+
jira_agent = JiraAgent(ctx.ai, ctx.jira)
|
|
69
|
+
|
|
70
|
+
# Analyze an issue
|
|
71
|
+
analysis = jira_agent.analyze_issue(
|
|
72
|
+
issue_key="PROJ-123",
|
|
73
|
+
include_subtasks=True,
|
|
74
|
+
include_comments=True
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Use the analysis
|
|
78
|
+
if analysis.suggested_subtasks:
|
|
79
|
+
for subtask in analysis.suggested_subtasks:
|
|
80
|
+
# Create subtasks in JIRA
|
|
81
|
+
pass
|
|
82
|
+
```
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(self, ai_client, jira_client=None):
|
|
86
|
+
"""
|
|
87
|
+
Initialize JiraAgent.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
ai_client: The AIClient instance (provides AI capabilities)
|
|
91
|
+
jira_client: Optional JIRA client for issue operations
|
|
92
|
+
"""
|
|
93
|
+
super().__init__(ai_client)
|
|
94
|
+
self.jira = jira_client
|
|
95
|
+
|
|
96
|
+
# Load configuration from TOML (once per agent instance)
|
|
97
|
+
self.config = load_agent_config("jira_agent")
|
|
98
|
+
|
|
99
|
+
# Initialize robust response parser
|
|
100
|
+
self.parser = JiraAgentParser(strict=False)
|
|
101
|
+
|
|
102
|
+
# Initialize input validator
|
|
103
|
+
self.validator = IssueValidator(
|
|
104
|
+
strict=False,
|
|
105
|
+
min_description_length=10
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Initialize token tracker with budget
|
|
109
|
+
self.token_budget = TokenBudget(base_max_tokens=self.config.max_tokens)
|
|
110
|
+
self.token_tracker = TokenTracker(self.token_budget)
|
|
111
|
+
|
|
112
|
+
def get_system_prompt(self) -> str:
|
|
113
|
+
"""System prompt for requirements analysis (from config)."""
|
|
114
|
+
return self.config.requirements_system_prompt
|
|
115
|
+
|
|
116
|
+
def analyze_issue(
|
|
117
|
+
self,
|
|
118
|
+
issue_key: str,
|
|
119
|
+
include_subtasks: bool = True,
|
|
120
|
+
include_comments: bool = False,
|
|
121
|
+
include_linked_issues: bool = False
|
|
122
|
+
) -> IssueAnalysis:
|
|
123
|
+
"""
|
|
124
|
+
Analyze a JIRA issue and extract requirements, risks, and suggestions.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
issue_key: The JIRA issue key (e.g., "PROJ-123")
|
|
128
|
+
include_subtasks: Whether to suggest subtasks
|
|
129
|
+
include_comments: Whether to analyze existing comments
|
|
130
|
+
include_linked_issues: Whether to consider linked issues
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
IssueAnalysis with complete analysis (gracefully handles errors)
|
|
134
|
+
"""
|
|
135
|
+
if not self.jira:
|
|
136
|
+
logger.error("JiraClient not available for issue analysis")
|
|
137
|
+
return IssueAnalysis()
|
|
138
|
+
|
|
139
|
+
# Reset token tracker for this analysis
|
|
140
|
+
self.token_tracker.reset()
|
|
141
|
+
|
|
142
|
+
# Initialize with safe defaults
|
|
143
|
+
functional_reqs = []
|
|
144
|
+
non_functional_reqs = []
|
|
145
|
+
acceptance_criteria = []
|
|
146
|
+
technical_approach = None
|
|
147
|
+
dependencies = []
|
|
148
|
+
risks = []
|
|
149
|
+
edge_cases = []
|
|
150
|
+
enhanced_description = None
|
|
151
|
+
suggested_subtasks = []
|
|
152
|
+
suggested_comments = []
|
|
153
|
+
complexity_score = None
|
|
154
|
+
estimated_effort = None
|
|
155
|
+
|
|
156
|
+
# 1. Get issue from JIRA (with error handling)
|
|
157
|
+
try:
|
|
158
|
+
issue = self.jira.get_ticket(issue_key)
|
|
159
|
+
|
|
160
|
+
# 2. Analyze requirements (with AI error handling)
|
|
161
|
+
if self.config.enable_requirement_extraction:
|
|
162
|
+
try:
|
|
163
|
+
requirements_result = self._extract_requirements(issue)
|
|
164
|
+
functional_reqs = requirements_result.get("functional", [])
|
|
165
|
+
non_functional_reqs = requirements_result.get("non_functional", [])
|
|
166
|
+
acceptance_criteria = requirements_result.get("acceptance_criteria", [])
|
|
167
|
+
technical_approach = requirements_result.get("technical_approach")
|
|
168
|
+
# Track tokens
|
|
169
|
+
tokens_used = requirements_result.get("tokens_used", 0)
|
|
170
|
+
self.token_tracker.record_usage(
|
|
171
|
+
OperationType.REQUIREMENTS_EXTRACTION,
|
|
172
|
+
tokens_used,
|
|
173
|
+
issue_key=issue_key,
|
|
174
|
+
success=True
|
|
175
|
+
)
|
|
176
|
+
except Exception as e:
|
|
177
|
+
logger.warning(f"Failed to extract requirements: {e}")
|
|
178
|
+
self.token_tracker.record_usage(
|
|
179
|
+
OperationType.REQUIREMENTS_EXTRACTION,
|
|
180
|
+
0,
|
|
181
|
+
issue_key=issue_key,
|
|
182
|
+
success=False,
|
|
183
|
+
error=str(e)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# 3. Analyze risks and dependencies (with AI error handling)
|
|
187
|
+
if self.config.enable_risk_analysis:
|
|
188
|
+
try:
|
|
189
|
+
risk_result = self._analyze_risks(issue)
|
|
190
|
+
risks = risk_result.get("risks", [])
|
|
191
|
+
edge_cases = risk_result.get("edge_cases", [])
|
|
192
|
+
complexity_score = risk_result.get("complexity")
|
|
193
|
+
estimated_effort = risk_result.get("effort")
|
|
194
|
+
# Track tokens
|
|
195
|
+
tokens_used = risk_result.get("tokens_used", 0)
|
|
196
|
+
self.token_tracker.record_usage(
|
|
197
|
+
OperationType.RISK_ANALYSIS,
|
|
198
|
+
tokens_used,
|
|
199
|
+
issue_key=issue_key,
|
|
200
|
+
success=True
|
|
201
|
+
)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.warning(f"Failed to analyze risks: {e}")
|
|
204
|
+
self.token_tracker.record_usage(
|
|
205
|
+
OperationType.RISK_ANALYSIS,
|
|
206
|
+
0,
|
|
207
|
+
issue_key=issue_key,
|
|
208
|
+
success=False,
|
|
209
|
+
error=str(e)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# 4. Detect dependencies (with AI error handling)
|
|
213
|
+
if self.config.enable_dependency_detection:
|
|
214
|
+
try:
|
|
215
|
+
dep_result = self._detect_dependencies(issue)
|
|
216
|
+
dependencies = dep_result.get("dependencies", [])
|
|
217
|
+
# Track tokens
|
|
218
|
+
tokens_used = dep_result.get("tokens_used", 0)
|
|
219
|
+
self.token_tracker.record_usage(
|
|
220
|
+
OperationType.DEPENDENCY_DETECTION,
|
|
221
|
+
tokens_used,
|
|
222
|
+
issue_key=issue_key,
|
|
223
|
+
success=True
|
|
224
|
+
)
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.warning(f"Failed to detect dependencies: {e}")
|
|
227
|
+
self.token_tracker.record_usage(
|
|
228
|
+
OperationType.DEPENDENCY_DETECTION,
|
|
229
|
+
0,
|
|
230
|
+
issue_key=issue_key,
|
|
231
|
+
success=False,
|
|
232
|
+
error=str(e)
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# 5. Suggest subtasks (with AI error handling)
|
|
236
|
+
if include_subtasks and self.config.enable_subtasks:
|
|
237
|
+
try:
|
|
238
|
+
subtask_result = self._suggest_subtasks(issue)
|
|
239
|
+
suggested_subtasks = subtask_result.get("subtasks", [])
|
|
240
|
+
# Track tokens
|
|
241
|
+
tokens_used = subtask_result.get("tokens_used", 0)
|
|
242
|
+
self.token_tracker.record_usage(
|
|
243
|
+
OperationType.SUBTASK_SUGGESTION,
|
|
244
|
+
tokens_used,
|
|
245
|
+
issue_key=issue_key,
|
|
246
|
+
success=True
|
|
247
|
+
)
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logger.warning(f"Failed to suggest subtasks: {e}")
|
|
250
|
+
self.token_tracker.record_usage(
|
|
251
|
+
OperationType.SUBTASK_SUGGESTION,
|
|
252
|
+
0,
|
|
253
|
+
issue_key=issue_key,
|
|
254
|
+
success=False,
|
|
255
|
+
error=str(e)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.error(f"Failed to get issue {issue_key}: {e}")
|
|
260
|
+
# Return empty analysis on complete failure
|
|
261
|
+
|
|
262
|
+
return IssueAnalysis(
|
|
263
|
+
functional_requirements=functional_reqs,
|
|
264
|
+
non_functional_requirements=non_functional_reqs,
|
|
265
|
+
acceptance_criteria=acceptance_criteria,
|
|
266
|
+
technical_approach=technical_approach,
|
|
267
|
+
dependencies=dependencies,
|
|
268
|
+
risks=risks,
|
|
269
|
+
edge_cases=edge_cases,
|
|
270
|
+
enhanced_description=enhanced_description,
|
|
271
|
+
suggested_subtasks=suggested_subtasks,
|
|
272
|
+
suggested_comments=suggested_comments,
|
|
273
|
+
total_tokens_used=self.token_tracker.get_total_tokens(),
|
|
274
|
+
complexity_score=complexity_score,
|
|
275
|
+
estimated_effort=estimated_effort
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
def _extract_requirements(self, issue) -> Dict[str, Any]:
|
|
279
|
+
"""
|
|
280
|
+
Extract functional and non-functional requirements from issue.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
issue: JiraTicket object
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Dict with keys: functional, non_functional, acceptance_criteria,
|
|
287
|
+
technical_approach, tokens_used
|
|
288
|
+
|
|
289
|
+
Raises:
|
|
290
|
+
ValueError: If issue validation fails
|
|
291
|
+
Exception: If AI generation fails
|
|
292
|
+
"""
|
|
293
|
+
# Validate issue data before processing
|
|
294
|
+
is_valid, errors = self.validator.validate_for_requirements_extraction(issue)
|
|
295
|
+
|
|
296
|
+
if not is_valid:
|
|
297
|
+
error_msg = f"Issue validation failed for {issue.key}:\n" + "\n".join(f" - {e}" for e in errors)
|
|
298
|
+
raise ValueError(error_msg)
|
|
299
|
+
|
|
300
|
+
# Sanitize and truncate description
|
|
301
|
+
description = issue.description or ""
|
|
302
|
+
max_length = self.config.max_description_length
|
|
303
|
+
desc_preview = self.validator.sanitize_description(description, max_length)
|
|
304
|
+
|
|
305
|
+
# Use centralized prompt template
|
|
306
|
+
prompt = JiraAgentPrompts.requirements_extraction(
|
|
307
|
+
issue_key=issue.key,
|
|
308
|
+
summary=issue.summary,
|
|
309
|
+
issue_type=issue.issue_type,
|
|
310
|
+
priority=issue.priority,
|
|
311
|
+
description=desc_preview
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
request = AgentRequest(
|
|
315
|
+
context=prompt,
|
|
316
|
+
max_tokens=self.config.max_tokens,
|
|
317
|
+
temperature=self.config.temperature,
|
|
318
|
+
system_prompt=self.config.requirements_system_prompt
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
response = self.generate(request)
|
|
323
|
+
except Exception as e:
|
|
324
|
+
logger.error(f"AI generation failed for requirements extraction: {e}")
|
|
325
|
+
raise
|
|
326
|
+
|
|
327
|
+
# Debug: Log AI response if debug enabled
|
|
328
|
+
if self.config.enable_debug_output:
|
|
329
|
+
logger.info("=" * 80)
|
|
330
|
+
logger.info("AI RESPONSE FOR REQUIREMENTS EXTRACTION:")
|
|
331
|
+
logger.info(response.content)
|
|
332
|
+
logger.info("=" * 80)
|
|
333
|
+
|
|
334
|
+
# Parse response
|
|
335
|
+
result = self._parse_requirements_response(response.content)
|
|
336
|
+
|
|
337
|
+
# Debug: Log parsing result if debug enabled
|
|
338
|
+
if self.config.enable_debug_output:
|
|
339
|
+
logger.info("PARSED RESULT:")
|
|
340
|
+
logger.info(f" Functional: {len(result.get('functional', []))} items")
|
|
341
|
+
logger.info(f" Non-functional: {len(result.get('non_functional', []))} items")
|
|
342
|
+
logger.info(f" Acceptance criteria: {len(result.get('acceptance_criteria', []))} items")
|
|
343
|
+
logger.info(f" Technical approach: {result.get('technical_approach') is not None}")
|
|
344
|
+
|
|
345
|
+
result["tokens_used"] = response.tokens_used
|
|
346
|
+
|
|
347
|
+
return result
|
|
348
|
+
|
|
349
|
+
def _analyze_risks(self, issue) -> Dict[str, Any]:
|
|
350
|
+
"""
|
|
351
|
+
Analyze risks and edge cases for the issue.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
issue: JiraTicket object
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Dict with keys: risks, edge_cases, complexity, effort, tokens_used
|
|
358
|
+
"""
|
|
359
|
+
description = issue.description or ""
|
|
360
|
+
desc_preview = self.validator.sanitize_description(
|
|
361
|
+
description,
|
|
362
|
+
self.config.max_description_length
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Use centralized prompt template
|
|
366
|
+
prompt = JiraAgentPrompts.risk_analysis(
|
|
367
|
+
issue_key=issue.key,
|
|
368
|
+
summary=issue.summary,
|
|
369
|
+
issue_type=issue.issue_type,
|
|
370
|
+
priority=issue.priority,
|
|
371
|
+
description=desc_preview
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
request = AgentRequest(
|
|
375
|
+
context=prompt,
|
|
376
|
+
max_tokens=self.config.max_tokens,
|
|
377
|
+
temperature=self.config.temperature,
|
|
378
|
+
system_prompt=self.config.requirements_system_prompt
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
try:
|
|
382
|
+
response = self.generate(request)
|
|
383
|
+
result = self._parse_risk_response(response.content)
|
|
384
|
+
result["tokens_used"] = response.tokens_used
|
|
385
|
+
return result
|
|
386
|
+
except Exception as e:
|
|
387
|
+
logger.error(f"AI generation failed for risk analysis: {e}")
|
|
388
|
+
raise
|
|
389
|
+
|
|
390
|
+
def _detect_dependencies(self, issue) -> Dict[str, Any]:
|
|
391
|
+
"""
|
|
392
|
+
Detect technical dependencies from issue description.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
issue: JiraTicket object
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Dict with keys: dependencies, tokens_used
|
|
399
|
+
"""
|
|
400
|
+
description = issue.description or ""
|
|
401
|
+
desc_preview = self.validator.sanitize_description(
|
|
402
|
+
description,
|
|
403
|
+
self.config.max_description_length
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# Use centralized prompt template
|
|
407
|
+
prompt = JiraAgentPrompts.dependency_detection(
|
|
408
|
+
issue_key=issue.key,
|
|
409
|
+
summary=issue.summary,
|
|
410
|
+
issue_type=issue.issue_type,
|
|
411
|
+
description=desc_preview
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
request = AgentRequest(
|
|
415
|
+
context=prompt,
|
|
416
|
+
max_tokens=self.token_budget.get_budget(OperationType.DEPENDENCY_DETECTION),
|
|
417
|
+
temperature=self.config.temperature,
|
|
418
|
+
system_prompt=self.config.requirements_system_prompt
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
try:
|
|
422
|
+
response = self.generate(request)
|
|
423
|
+
result = self._parse_dependencies_response(response.content)
|
|
424
|
+
result["tokens_used"] = response.tokens_used
|
|
425
|
+
return result
|
|
426
|
+
except Exception as e:
|
|
427
|
+
logger.error(f"AI generation failed for dependency detection: {e}")
|
|
428
|
+
raise
|
|
429
|
+
|
|
430
|
+
def _suggest_subtasks(self, issue) -> Dict[str, Any]:
|
|
431
|
+
"""
|
|
432
|
+
Suggest subtasks for breaking down the issue.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
issue: JiraTicket object
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
Dict with keys: subtasks (list of dicts with summary and description), tokens_used
|
|
439
|
+
"""
|
|
440
|
+
description = issue.description or ""
|
|
441
|
+
desc_preview = self.validator.sanitize_description(
|
|
442
|
+
description,
|
|
443
|
+
self.config.max_description_length
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Use centralized prompt template
|
|
447
|
+
prompt = JiraAgentPrompts.subtask_suggestion(
|
|
448
|
+
issue_key=issue.key,
|
|
449
|
+
summary=issue.summary,
|
|
450
|
+
issue_type=issue.issue_type,
|
|
451
|
+
priority=issue.priority,
|
|
452
|
+
description=desc_preview,
|
|
453
|
+
max_subtasks=self.config.max_subtasks
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
request = AgentRequest(
|
|
457
|
+
context=prompt,
|
|
458
|
+
max_tokens=self.config.max_tokens,
|
|
459
|
+
temperature=self.config.temperature,
|
|
460
|
+
system_prompt=self.config.subtask_suggestion_prompt
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
response = self.generate(request)
|
|
465
|
+
result = self._parse_subtasks_response(response.content)
|
|
466
|
+
result["tokens_used"] = response.tokens_used
|
|
467
|
+
return result
|
|
468
|
+
except Exception as e:
|
|
469
|
+
logger.error(f"AI generation failed for subtask suggestion: {e}")
|
|
470
|
+
raise
|
|
471
|
+
|
|
472
|
+
def generate_comment(self, issue_key: str, comment_context: str) -> Optional[str]:
|
|
473
|
+
"""
|
|
474
|
+
Generate a helpful comment for a JIRA issue.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
issue_key: The JIRA issue key
|
|
478
|
+
comment_context: Context for the comment (e.g., "provide implementation guidance")
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
Generated comment text or None on failure
|
|
482
|
+
"""
|
|
483
|
+
if not self.jira:
|
|
484
|
+
logger.error("JiraClient not available")
|
|
485
|
+
return None
|
|
486
|
+
|
|
487
|
+
try:
|
|
488
|
+
issue = self.jira.get_ticket(issue_key)
|
|
489
|
+
description = issue.description or ""
|
|
490
|
+
desc_preview = self.validator.sanitize_description(
|
|
491
|
+
description,
|
|
492
|
+
self.config.max_description_length
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
# Use centralized prompt template
|
|
496
|
+
prompt = JiraAgentPrompts.comment_generation(
|
|
497
|
+
issue_key=issue.key,
|
|
498
|
+
summary=issue.summary,
|
|
499
|
+
issue_type=issue.issue_type,
|
|
500
|
+
status=issue.status,
|
|
501
|
+
description=desc_preview,
|
|
502
|
+
comment_context=comment_context
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
request = AgentRequest(
|
|
506
|
+
context=prompt,
|
|
507
|
+
max_tokens=self.config.max_tokens // 2,
|
|
508
|
+
temperature=self.config.temperature,
|
|
509
|
+
system_prompt=self.config.comment_generation_prompt
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
response = self.generate(request)
|
|
513
|
+
|
|
514
|
+
# Extract comment
|
|
515
|
+
if "COMMENT:" in response.content:
|
|
516
|
+
comment = response.content.split("COMMENT:", 1)[1].strip()
|
|
517
|
+
return comment
|
|
518
|
+
|
|
519
|
+
return None
|
|
520
|
+
|
|
521
|
+
except Exception as e:
|
|
522
|
+
logger.error(f"Failed to generate comment: {e}")
|
|
523
|
+
return None
|
|
524
|
+
|
|
525
|
+
# ==================== RESPONSE PARSING METHODS ====================
|
|
526
|
+
|
|
527
|
+
def _parse_requirements_response(self, content: str) -> Dict[str, Any]:
|
|
528
|
+
"""
|
|
529
|
+
Parse requirements extraction response.
|
|
530
|
+
Uses robust parser with JSON-first strategy and regex fallback.
|
|
531
|
+
"""
|
|
532
|
+
return self.parser.parse_requirements(content)
|
|
533
|
+
|
|
534
|
+
def _parse_risk_response(self, content: str) -> Dict[str, Any]:
|
|
535
|
+
"""
|
|
536
|
+
Parse risk analysis response.
|
|
537
|
+
Uses robust parser with JSON-first strategy and regex fallback.
|
|
538
|
+
"""
|
|
539
|
+
return self.parser.parse_risks(content)
|
|
540
|
+
|
|
541
|
+
def _parse_dependencies_response(self, content: str) -> Dict[str, Any]:
|
|
542
|
+
"""
|
|
543
|
+
Parse dependencies detection response.
|
|
544
|
+
Uses robust parser with JSON-first strategy and regex fallback.
|
|
545
|
+
"""
|
|
546
|
+
return self.parser.parse_dependencies(content)
|
|
547
|
+
|
|
548
|
+
def _parse_subtasks_response(self, content: str) -> Dict[str, Any]:
|
|
549
|
+
"""
|
|
550
|
+
Parse subtasks suggestion response.
|
|
551
|
+
Uses robust parser with JSON-first strategy and regex fallback.
|
|
552
|
+
"""
|
|
553
|
+
return self.parser.parse_subtasks(content)
|