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.
Files changed (146) hide show
  1. titan_cli/__init__.py +3 -0
  2. titan_cli/__main__.py +4 -0
  3. titan_cli/ai/__init__.py +0 -0
  4. titan_cli/ai/agents/__init__.py +15 -0
  5. titan_cli/ai/agents/base.py +152 -0
  6. titan_cli/ai/client.py +170 -0
  7. titan_cli/ai/constants.py +56 -0
  8. titan_cli/ai/exceptions.py +48 -0
  9. titan_cli/ai/models.py +34 -0
  10. titan_cli/ai/oauth_helper.py +120 -0
  11. titan_cli/ai/providers/__init__.py +9 -0
  12. titan_cli/ai/providers/anthropic.py +117 -0
  13. titan_cli/ai/providers/base.py +75 -0
  14. titan_cli/ai/providers/gemini.py +278 -0
  15. titan_cli/cli.py +59 -0
  16. titan_cli/clients/__init__.py +1 -0
  17. titan_cli/clients/gcloud_client.py +52 -0
  18. titan_cli/core/__init__.py +3 -0
  19. titan_cli/core/config.py +274 -0
  20. titan_cli/core/discovery.py +51 -0
  21. titan_cli/core/errors.py +81 -0
  22. titan_cli/core/models.py +52 -0
  23. titan_cli/core/plugins/available.py +36 -0
  24. titan_cli/core/plugins/models.py +67 -0
  25. titan_cli/core/plugins/plugin_base.py +108 -0
  26. titan_cli/core/plugins/plugin_registry.py +163 -0
  27. titan_cli/core/secrets.py +141 -0
  28. titan_cli/core/workflows/__init__.py +22 -0
  29. titan_cli/core/workflows/models.py +88 -0
  30. titan_cli/core/workflows/project_step_source.py +86 -0
  31. titan_cli/core/workflows/workflow_exceptions.py +17 -0
  32. titan_cli/core/workflows/workflow_filter_service.py +137 -0
  33. titan_cli/core/workflows/workflow_registry.py +419 -0
  34. titan_cli/core/workflows/workflow_sources.py +307 -0
  35. titan_cli/engine/__init__.py +39 -0
  36. titan_cli/engine/builder.py +159 -0
  37. titan_cli/engine/context.py +82 -0
  38. titan_cli/engine/mock_context.py +176 -0
  39. titan_cli/engine/results.py +91 -0
  40. titan_cli/engine/steps/ai_assistant_step.py +185 -0
  41. titan_cli/engine/steps/command_step.py +93 -0
  42. titan_cli/engine/utils/__init__.py +3 -0
  43. titan_cli/engine/utils/venv.py +31 -0
  44. titan_cli/engine/workflow_executor.py +187 -0
  45. titan_cli/external_cli/__init__.py +0 -0
  46. titan_cli/external_cli/configs.py +17 -0
  47. titan_cli/external_cli/launcher.py +65 -0
  48. titan_cli/messages.py +121 -0
  49. titan_cli/ui/tui/__init__.py +205 -0
  50. titan_cli/ui/tui/__previews__/statusbar_preview.py +88 -0
  51. titan_cli/ui/tui/app.py +113 -0
  52. titan_cli/ui/tui/icons.py +70 -0
  53. titan_cli/ui/tui/screens/__init__.py +24 -0
  54. titan_cli/ui/tui/screens/ai_config.py +498 -0
  55. titan_cli/ui/tui/screens/ai_config_wizard.py +882 -0
  56. titan_cli/ui/tui/screens/base.py +110 -0
  57. titan_cli/ui/tui/screens/cli_launcher.py +151 -0
  58. titan_cli/ui/tui/screens/global_setup_wizard.py +363 -0
  59. titan_cli/ui/tui/screens/main_menu.py +162 -0
  60. titan_cli/ui/tui/screens/plugin_config_wizard.py +550 -0
  61. titan_cli/ui/tui/screens/plugin_management.py +377 -0
  62. titan_cli/ui/tui/screens/project_setup_wizard.py +686 -0
  63. titan_cli/ui/tui/screens/workflow_execution.py +592 -0
  64. titan_cli/ui/tui/screens/workflows.py +249 -0
  65. titan_cli/ui/tui/textual_components.py +537 -0
  66. titan_cli/ui/tui/textual_workflow_executor.py +405 -0
  67. titan_cli/ui/tui/theme.py +102 -0
  68. titan_cli/ui/tui/widgets/__init__.py +40 -0
  69. titan_cli/ui/tui/widgets/button.py +108 -0
  70. titan_cli/ui/tui/widgets/header.py +116 -0
  71. titan_cli/ui/tui/widgets/panel.py +81 -0
  72. titan_cli/ui/tui/widgets/status_bar.py +115 -0
  73. titan_cli/ui/tui/widgets/table.py +77 -0
  74. titan_cli/ui/tui/widgets/text.py +177 -0
  75. titan_cli/utils/__init__.py +0 -0
  76. titan_cli/utils/autoupdate.py +155 -0
  77. titan_cli-0.1.0.dist-info/METADATA +149 -0
  78. titan_cli-0.1.0.dist-info/RECORD +146 -0
  79. titan_cli-0.1.0.dist-info/WHEEL +4 -0
  80. titan_cli-0.1.0.dist-info/entry_points.txt +9 -0
  81. titan_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
  82. titan_plugin_git/__init__.py +1 -0
  83. titan_plugin_git/clients/__init__.py +8 -0
  84. titan_plugin_git/clients/git_client.py +772 -0
  85. titan_plugin_git/exceptions.py +40 -0
  86. titan_plugin_git/messages.py +112 -0
  87. titan_plugin_git/models.py +39 -0
  88. titan_plugin_git/plugin.py +118 -0
  89. titan_plugin_git/steps/__init__.py +1 -0
  90. titan_plugin_git/steps/ai_commit_message_step.py +171 -0
  91. titan_plugin_git/steps/branch_steps.py +104 -0
  92. titan_plugin_git/steps/commit_step.py +80 -0
  93. titan_plugin_git/steps/push_step.py +63 -0
  94. titan_plugin_git/steps/status_step.py +59 -0
  95. titan_plugin_git/workflows/__previews__/__init__.py +1 -0
  96. titan_plugin_git/workflows/__previews__/commit_ai_preview.py +124 -0
  97. titan_plugin_git/workflows/commit-ai.yaml +28 -0
  98. titan_plugin_github/__init__.py +11 -0
  99. titan_plugin_github/agents/__init__.py +6 -0
  100. titan_plugin_github/agents/config_loader.py +130 -0
  101. titan_plugin_github/agents/issue_generator.py +353 -0
  102. titan_plugin_github/agents/pr_agent.py +528 -0
  103. titan_plugin_github/clients/__init__.py +8 -0
  104. titan_plugin_github/clients/github_client.py +1105 -0
  105. titan_plugin_github/config/__init__.py +0 -0
  106. titan_plugin_github/config/pr_agent.toml +85 -0
  107. titan_plugin_github/exceptions.py +28 -0
  108. titan_plugin_github/messages.py +88 -0
  109. titan_plugin_github/models.py +330 -0
  110. titan_plugin_github/plugin.py +131 -0
  111. titan_plugin_github/steps/__init__.py +12 -0
  112. titan_plugin_github/steps/ai_pr_step.py +172 -0
  113. titan_plugin_github/steps/create_pr_step.py +86 -0
  114. titan_plugin_github/steps/github_prompt_steps.py +171 -0
  115. titan_plugin_github/steps/issue_steps.py +143 -0
  116. titan_plugin_github/steps/preview_step.py +40 -0
  117. titan_plugin_github/utils.py +82 -0
  118. titan_plugin_github/workflows/__previews__/__init__.py +1 -0
  119. titan_plugin_github/workflows/__previews__/create_pr_ai_preview.py +140 -0
  120. titan_plugin_github/workflows/create-issue-ai.yaml +32 -0
  121. titan_plugin_github/workflows/create-pr-ai.yaml +49 -0
  122. titan_plugin_jira/__init__.py +8 -0
  123. titan_plugin_jira/agents/__init__.py +6 -0
  124. titan_plugin_jira/agents/config_loader.py +154 -0
  125. titan_plugin_jira/agents/jira_agent.py +553 -0
  126. titan_plugin_jira/agents/prompts.py +364 -0
  127. titan_plugin_jira/agents/response_parser.py +435 -0
  128. titan_plugin_jira/agents/token_tracker.py +223 -0
  129. titan_plugin_jira/agents/validators.py +246 -0
  130. titan_plugin_jira/clients/jira_client.py +745 -0
  131. titan_plugin_jira/config/jira_agent.toml +92 -0
  132. titan_plugin_jira/config/templates/issue_analysis.md.j2 +78 -0
  133. titan_plugin_jira/exceptions.py +37 -0
  134. titan_plugin_jira/formatters/__init__.py +6 -0
  135. titan_plugin_jira/formatters/markdown_formatter.py +245 -0
  136. titan_plugin_jira/messages.py +115 -0
  137. titan_plugin_jira/models.py +89 -0
  138. titan_plugin_jira/plugin.py +264 -0
  139. titan_plugin_jira/steps/ai_analyze_issue_step.py +105 -0
  140. titan_plugin_jira/steps/get_issue_step.py +82 -0
  141. titan_plugin_jira/steps/prompt_select_issue_step.py +80 -0
  142. titan_plugin_jira/steps/search_saved_query_step.py +238 -0
  143. titan_plugin_jira/utils/__init__.py +13 -0
  144. titan_plugin_jira/utils/issue_sorter.py +140 -0
  145. titan_plugin_jira/utils/saved_queries.py +150 -0
  146. 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)