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,246 @@
1
+ # plugins/titan-plugin-jira/titan_plugin_jira/agents/validators.py
2
+ """
3
+ Input validation for JiraAgent.
4
+
5
+ Provides comprehensive validation for issue data before processing.
6
+ Addresses PR #74 comment: "Falta Validación de Issue Data"
7
+ """
8
+
9
+ from typing import Optional, List, Tuple
10
+ from ..models import JiraTicket
11
+
12
+
13
+ class IssueValidationError(ValueError):
14
+ """Raised when issue data validation fails."""
15
+ pass
16
+
17
+
18
+ class IssueValidator:
19
+ """
20
+ Validates JIRA issue data before AI processing.
21
+
22
+ Ensures that issues have sufficient data for meaningful analysis:
23
+ - Non-empty description
24
+ - Valid issue type
25
+ - No malformed data
26
+ - Reasonable length constraints
27
+ """
28
+
29
+ # Valid JIRA issue types
30
+ VALID_ISSUE_TYPES = {
31
+ "Story", "Task", "Bug", "Epic", "Sub-task",
32
+ "Improvement", "New Feature", "Technical Debt",
33
+ "Spike", "User Story"
34
+ }
35
+
36
+ # Length constraints
37
+ MIN_DESCRIPTION_LENGTH = 10 # Characters
38
+ MAX_DESCRIPTION_LENGTH = 100000 # 100k characters (safety limit)
39
+ MAX_SUMMARY_LENGTH = 255 # Standard JIRA limit
40
+
41
+ def __init__(
42
+ self,
43
+ strict: bool = False,
44
+ min_description_length: int = MIN_DESCRIPTION_LENGTH
45
+ ):
46
+ """
47
+ Initialize validator.
48
+
49
+ Args:
50
+ strict: If True, raise errors on validation failures.
51
+ If False, return validation results with warnings.
52
+ min_description_length: Minimum required description length
53
+ """
54
+ self.strict = strict
55
+ self.min_description_length = min_description_length
56
+
57
+ def validate_issue(self, issue: JiraTicket) -> Tuple[bool, List[str]]:
58
+ """
59
+ Validate issue data for AI processing.
60
+
61
+ Args:
62
+ issue: The JIRA issue to validate
63
+
64
+ Returns:
65
+ Tuple of (is_valid, errors)
66
+ - is_valid: True if issue passes all validations
67
+ - errors: List of validation error messages
68
+
69
+ Raises:
70
+ IssueValidationError: If strict=True and validation fails
71
+ """
72
+ errors = []
73
+
74
+ # 1. Validate issue key
75
+ if not issue.key or not issue.key.strip():
76
+ errors.append("Issue key is empty")
77
+ elif not self._is_valid_key_format(issue.key):
78
+ errors.append(f"Issue key '{issue.key}' has invalid format (expected: PROJECT-123)")
79
+
80
+ # 2. Validate summary
81
+ if not issue.summary or not issue.summary.strip():
82
+ errors.append("Issue summary is empty")
83
+ elif len(issue.summary) > self.MAX_SUMMARY_LENGTH:
84
+ errors.append(
85
+ f"Issue summary is too long ({len(issue.summary)} chars, "
86
+ f"max {self.MAX_SUMMARY_LENGTH})"
87
+ )
88
+
89
+ # 3. Validate description (critical for AI analysis)
90
+ desc_errors = self._validate_description(issue.description)
91
+ errors.extend(desc_errors)
92
+
93
+ # 4. Validate issue type
94
+ if issue.issue_type:
95
+ if issue.issue_type not in self.VALID_ISSUE_TYPES:
96
+ errors.append(
97
+ f"Unknown issue type '{issue.issue_type}'. "
98
+ f"Expected one of: {', '.join(sorted(self.VALID_ISSUE_TYPES))}"
99
+ )
100
+
101
+ # 5. Check for suspicious/malformed data
102
+ suspicious_errors = self._check_suspicious_data(issue)
103
+ errors.extend(suspicious_errors)
104
+
105
+ is_valid = len(errors) == 0
106
+
107
+ if self.strict and not is_valid:
108
+ raise IssueValidationError(
109
+ f"Issue validation failed for {issue.key}:\n" +
110
+ "\n".join(f" - {error}" for error in errors)
111
+ )
112
+
113
+ return is_valid, errors
114
+
115
+ def _validate_description(self, description: Optional[str]) -> List[str]:
116
+ """Validate issue description."""
117
+ errors = []
118
+
119
+ if not description:
120
+ errors.append("Issue description is empty or None")
121
+ return errors
122
+
123
+ description = description.strip()
124
+
125
+ if len(description) == 0:
126
+ errors.append("Issue description is empty (whitespace only)")
127
+ elif len(description) < self.min_description_length:
128
+ errors.append(
129
+ f"Issue description is too short ({len(description)} chars, "
130
+ f"minimum {self.min_description_length})"
131
+ )
132
+ elif len(description) > self.MAX_DESCRIPTION_LENGTH:
133
+ errors.append(
134
+ f"Issue description is too long ({len(description)} chars, "
135
+ f"max {self.MAX_DESCRIPTION_LENGTH})"
136
+ )
137
+
138
+ return errors
139
+
140
+ def _is_valid_key_format(self, key: str) -> bool:
141
+ """
142
+ Validate JIRA issue key format.
143
+
144
+ Expected format: PROJECT-123 (letters, hyphen, numbers)
145
+ """
146
+ import re
147
+ pattern = r'^[A-Z][A-Z0-9]+-\d+$'
148
+ return bool(re.match(pattern, key))
149
+
150
+ def _check_suspicious_data(self, issue: JiraTicket) -> List[str]:
151
+ """
152
+ Check for suspicious or malformed data.
153
+
154
+ Common issues:
155
+ - HTML/XML tags in plain text fields (indicates parsing error)
156
+ - Null bytes or control characters
157
+ - Excessive whitespace
158
+ """
159
+ errors = []
160
+
161
+ # Check for HTML tags (suggests improper parsing)
162
+ if issue.description and ('<html>' in issue.description.lower() or
163
+ '<!doctype' in issue.description.lower()):
164
+ errors.append(
165
+ "Description contains HTML tags (possible parsing error)"
166
+ )
167
+
168
+ # Check for null bytes
169
+ if issue.description and '\x00' in issue.description:
170
+ errors.append("Description contains null bytes")
171
+
172
+ # Check for excessive consecutive whitespace (> 10 newlines)
173
+ if issue.description:
174
+ max_consecutive_newlines = 10
175
+ if '\n' * max_consecutive_newlines in issue.description:
176
+ errors.append(
177
+ f"Description has excessive whitespace "
178
+ f"({max_consecutive_newlines}+ consecutive newlines)"
179
+ )
180
+
181
+ return errors
182
+
183
+ def validate_for_requirements_extraction(
184
+ self,
185
+ issue: JiraTicket
186
+ ) -> Tuple[bool, List[str]]:
187
+ """
188
+ Validate issue specifically for requirements extraction.
189
+
190
+ More strict than general validation - requires meaningful description.
191
+
192
+ Returns:
193
+ Tuple of (is_valid, errors)
194
+ """
195
+ is_valid, errors = self.validate_issue(issue)
196
+
197
+ # Additional requirement: description must have substance
198
+ if issue.description:
199
+ # Check for placeholder text
200
+ placeholder_phrases = [
201
+ "to be defined", "tbd", "todo", "placeholder",
202
+ "add description here", "fill this in"
203
+ ]
204
+ desc_lower = issue.description.lower()
205
+
206
+ for phrase in placeholder_phrases:
207
+ if phrase in desc_lower and len(issue.description.strip()) < 100:
208
+ errors.append(
209
+ f"Description appears to be a placeholder (contains '{phrase}')"
210
+ )
211
+ is_valid = False
212
+ break
213
+
214
+ return is_valid, errors
215
+
216
+ def sanitize_description(self, description: str, max_length: int = 5000) -> str:
217
+ """
218
+ Sanitize and truncate description for AI processing.
219
+
220
+ Args:
221
+ description: Raw description text
222
+ max_length: Maximum length to truncate to
223
+
224
+ Returns:
225
+ Sanitized description
226
+ """
227
+ if not description:
228
+ return ""
229
+
230
+ # Remove null bytes
231
+ sanitized = description.replace('\x00', '')
232
+
233
+ # Normalize excessive whitespace
234
+ import re
235
+ # Replace 5+ consecutive newlines with 3
236
+ sanitized = re.sub(r'\n{5,}', '\n\n\n', sanitized)
237
+
238
+ # Replace 10+ consecutive spaces with single space
239
+ sanitized = re.sub(r' {10,}', ' ', sanitized)
240
+
241
+ # Truncate if needed
242
+ if len(sanitized) > max_length:
243
+ sanitized = sanitized[:max_length]
244
+ sanitized += "\n\n... (description truncated for processing)"
245
+
246
+ return sanitized.strip()